diff --git a/README.md b/README.md index 30e17ef..1664863 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,3 @@ -生成一个方法 -## 方法名 -ToTmplStr - -## 参数 -string - -## 逻辑 -将字符串转换成一行的字符串格式,并再进行一次转义输出 +## 游戏服务更新 +此成果由@钉三多确认完成 diff --git a/Type/t.go b/Type/t.go index 7e19cba..fc957fa 100644 --- a/Type/t.go +++ b/Type/t.go @@ -17,19 +17,20 @@ type App struct { } type Node struct { - Id int `json:"NodeId" db:"id"` - Name string `json:"NodeName" db:"name"` - Host string `json:"NodeIp" db:"host"` - InternalHost string `json:"NodeInternalIp" db:"internalHost"` - Hardware string `json:"NodeHardware" db:"hardware"` - Area string `json:"NodeArea" db:"area"` - Operator string `json:"NodeOperator" db:"operator"` - Status int `json:"NodeStatus" db:"status"` - CreateTime int64 `json:"createTime" db:"createTime"` - UpdateTime int64 `json:"updateTime" db:"updateTime"` - User string `json:"NodeUser" db:"user"` - Password string `json:"NodePassword" db:"password"` - Tz string `json:"NodeTz" db:"tz"` + Id int `json:"NodeId" db:"id"` + Name string `json:"NodeName" db:"name"` + Host string `json:"NodeIp" db:"host"` + InternalHost string `json:"NodeInternalIp" db:"internalHost"` + Hardware string `json:"NodeHardware" db:"hardware"` + Area string `json:"NodeArea" db:"area"` + Operator string `json:"NodeOperator" db:"operator"` + Status int `json:"NodeStatus" db:"status"` + CreateTime int64 `json:"createTime" db:"createTime"` + UpdateTime int64 `json:"updateTime" db:"updateTime"` + User string `json:"NodeUser" db:"user"` + Password string `json:"NodePassword" db:"password"` + Tz string `json:"NodeTz" db:"tz"` + PrivateKeyPath string } type Mysql struct { @@ -137,6 +138,16 @@ type NotifyData struct { NotifyMsg string `json:"notify_msg"` } +type NotifyRecoveryData struct { + Host string `json:"host"` + EventName string `json:"event_name"` + Severity string `json:"severity"` + AlarmTime string `json:"alarm_time"` + Recovery string `json:"recovery"` + Age string `json:"age"` + NotifyMsg string `json:"notify_msg"` +} + type NotifyClientData struct { Log string `json:"log"` StackTrace string `json:"stackTrace"` @@ -158,3 +169,18 @@ type OrderData struct { EventRecovery string `db:"EventRecovery"` EventAge string `db:"EventAge"` } + +type AlibabaUpdateCardParam struct { + Server string + UpdateTime string + Duration string + SrcGitLog string + DocGitLog string +} + +type AlibabaNotifyData struct { + Stack string `json:"stack"` + FuncName string `json:"func_name"` + AlarmTime string `json:"alarm_time"` + NotifyMsg string `json:"notify_msg"` +} diff --git a/alibaba/card.go b/alibaba/card.go new file mode 100644 index 0000000..18d0990 --- /dev/null +++ b/alibaba/card.go @@ -0,0 +1,459 @@ +package alibaba + +import ( + "backend/Type" + "encoding/json" + "fmt" + "time" + + clientv2 "github.com/alibabacloud-go/darabonba-openapi/v2/client" + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + dingtalkcard_1_0 "github.com/alibabacloud-go/dingtalk/card_1_0" + dingtalkim_1_0 "github.com/alibabacloud-go/dingtalk/im_1_0" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" +) + +var NOTIFY_TITLE_COLOR = map[string]string{ + "Not classified": "grey", + "Information": "blue", + "Warning": "yellow", + "Average": "orange", + "High": "pink", + "Disaster": "red", +} + +// 由于接口要求卡片数据的键值对均为 string 类型,需要对卡片数据进行预处理 +func convertJSONValuesToString(obj map[string]any) map[string]*string { + result := make(map[string]*string) + for key, value := range obj { + switch v := value.(type) { + case string: + result[key] = tea.String(v) + default: + bytes, err := json.Marshal(v) + if err != nil { + result[key] = tea.String("") + } else { + result[key] = tea.String(string(bytes)) + } + } + } + return result +} + +// Description: +// 使用 Token 初始化账号Client +// @return Client +// @throws Exception +func createCardClient() (_result *dingtalkcard_1_0.Client, _err error) { + config := &openapi.Config{} + config.Protocol = tea.String("https") + config.RegionId = tea.String("central") + _result = &dingtalkcard_1_0.Client{} + _result, _err = dingtalkcard_1_0.NewClient(config) + return _result, _err +} + +func SendCard() (_err error) { + client, _err := createCardClient() + if _err != nil { + return _err + } + + accessToken, _ := GetToken() + robotCode := "dingrmgtodzxaik76jpc" + userId := "" + openConversationId := "cidivmW+tO/JGyIFM/XHNeQcA==" + templateId := "679d07b8-f649-4b5d-b223-d082db55b0d6.schema" + lastMessage := "<用于消息列表展示、搜索结果展示的文案>" + searchIcon := "<搜索结果展示的图标>" + searchDesc := "<用于搜索的字段,最多 200 个字符>" + + createAndDeliverHeaders := &dingtalkcard_1_0.CreateAndDeliverHeaders{} + createAndDeliverHeaders.XAcsDingtalkAccessToken = tea.String(accessToken) + + imGroupOpenDeliverModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ + RobotCode: tea.String(robotCode), + // 卡片接收人 + Recipients: []*string{}, + } + imGroupOpenSpaceModelLastMessageI18n := map[string]*string{ + "ZH_CN": tea.String(lastMessage), + } + imGroupOpenSpaceModelSearchSupport := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModelSearchSupport{ + SearchIcon: tea.String(searchIcon), + SearchDesc: tea.String(searchDesc), + } + imGroupOpenSpaceModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{ + SupportForward: tea.Bool(true), + LastMessageI18n: imGroupOpenSpaceModelLastMessageI18n, + SearchSupport: imGroupOpenSpaceModelSearchSupport, + } + + // 此处使用了 MockData 作为测试数据,请结合真实场景设置卡片公有数据 + cardDataJSONStr := `{ + "grid_object_list": [ + { + "text": "Grid 11" + }, + { + "text": "Grid 12" + }, + { + "text": "Grid 13" + }, + { + "text": "Grid 21" + }, + { + "text": "Grid 22" + }, + { + "text": "Grid 23" + } + ], + "host": "测试主机" + }` + var cardDataCardParamMap map[string]any + cardDataError := json.Unmarshal([]byte(cardDataJSONStr), &cardDataCardParamMap) + if cardDataError != nil { + panic(cardDataError) + } + cardData := &dingtalkcard_1_0.CreateAndDeliverRequestCardData{ + CardParamMap: convertJSONValuesToString(cardDataCardParamMap), + } + createAndDeliverRequest := &dingtalkcard_1_0.CreateAndDeliverRequest{ + UserId: tea.String(userId), + CardTemplateId: tea.String(templateId), + // 用于标识卡片的唯一 ID,业务需自行建立关联关系,用于后续的卡片更新 + OutTrackId: tea.String(fmt.Sprintf("test-out-track-id-%d", time.Now().Unix())), + CallbackType: tea.String("STREAM"), + CardData: cardData, + ImGroupOpenSpaceModel: imGroupOpenSpaceModel, + ImGroupOpenDeliverModel: imGroupOpenDeliverModel, + OpenSpaceId: tea.String(fmt.Sprintf("dtv1.card//im_group.%s", openConversationId)), + UserIdType: tea.Int32(1), + } + tryErr := func() (_e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + fmt.Println("createAndDeliverRequest", createAndDeliverRequest) + _, _err = client.CreateAndDeliverWithOptions(createAndDeliverRequest, createAndDeliverHeaders, &util.RuntimeOptions{}) + if _err != nil { + return _err + } + + return nil + }() + + if tryErr != nil { + var err = &tea.SDKError{} + if _t, ok := tryErr.(*tea.SDKError); ok { + err = _t + } else { + err.Message = tea.String(tryErr.Error()) + } + if !tea.BoolValue(util.Empty(err.Code)) && !tea.BoolValue(util.Empty(err.Message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + } + } + return _err +} +func CreateImClient() (_result *dingtalkim_1_0.Client, _err error) { + config := &clientv2.Config{} + config.Protocol = tea.String("https") + config.RegionId = tea.String("central") + _result = &dingtalkim_1_0.Client{} + _result, _err = dingtalkim_1_0.NewClient(config) + return _result, _err +} + +func SendZabbixMsg(data *Type.NotifyData) (_err error) { + client, _err := createCardClient() + if _err != nil { + return _err + } + + accessToken, _ := GetToken() + robotCode := "dingrmgtodzxaik76jpc" + userId := "" + openConversationId := "cidivmW+tO/JGyIFM/XHNeQcA==" + templateId := "15624ae4-b499-40b4-bf04-af03c38ee8d7.schema" + lastMessage := "Zabbix告警" + searchIcon := "" + searchDesc := "" + + createAndDeliverHeaders := &dingtalkcard_1_0.CreateAndDeliverHeaders{} + createAndDeliverHeaders.XAcsDingtalkAccessToken = tea.String(accessToken) + imGroupOpenDeliverModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ + RobotCode: tea.String(robotCode), + // 卡片接收人 + Recipients: []*string{}, + } + if data.Severity == "Disaster" || data.Severity == "High" { + imGroupOpenDeliverModel.Recipients = []*string{tea.String("035105216620273488")} + imGroupOpenDeliverModel.AtUserIds = map[string]*string{ + "035105216620273488": tea.String("伍敏哲"), + } + } + imGroupOpenSpaceModelLastMessageI18n := map[string]*string{ + "ZH_CN": tea.String(lastMessage), + } + imGroupOpenSpaceModelSearchSupport := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModelSearchSupport{ + SearchIcon: tea.String(searchIcon), + SearchDesc: tea.String(searchDesc), + } + imGroupOpenSpaceModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{ + SupportForward: tea.Bool(true), + LastMessageI18n: imGroupOpenSpaceModelLastMessageI18n, + SearchSupport: imGroupOpenSpaceModelSearchSupport, + } + + // 此处使用了 MockData 作为测试数据,请结合真实场景设置卡片公有数据 + cardDataJSONStr := `{ + "host": "` + data.Host + `", + "notify_msg": "` + data.NotifyMsg + `", + "event_name": "` + data.EventName + `", + "severity": "` + data.Severity + `", + "alarm_time": "` + data.AlarmTime + `", + "title_color": "` + NOTIFY_TITLE_COLOR[data.Severity] + `" + }` + var cardDataCardParamMap map[string]any + cardDataError := json.Unmarshal([]byte(cardDataJSONStr), &cardDataCardParamMap) + if cardDataError != nil { + panic(cardDataError) + } + cardData := &dingtalkcard_1_0.CreateAndDeliverRequestCardData{ + CardParamMap: convertJSONValuesToString(cardDataCardParamMap), + } + createAndDeliverRequest := &dingtalkcard_1_0.CreateAndDeliverRequest{ + UserId: tea.String(userId), + CardTemplateId: tea.String(templateId), + // 用于标识卡片的唯一 ID,业务需自行建立关联关系,用于后续的卡片更新 + OutTrackId: tea.String(fmt.Sprintf("test-out-track-id-%d", time.Now().Unix())), + CallbackType: tea.String("STREAM"), + CardData: cardData, + ImGroupOpenSpaceModel: imGroupOpenSpaceModel, + ImGroupOpenDeliverModel: imGroupOpenDeliverModel, + OpenSpaceId: tea.String(fmt.Sprintf("dtv1.card//im_group.%s", openConversationId)), + UserIdType: tea.Int32(1), + } + tryErr := func() (_e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + fmt.Println("createAndDeliverRequest", createAndDeliverRequest) + _, _err = client.CreateAndDeliverWithOptions(createAndDeliverRequest, createAndDeliverHeaders, &util.RuntimeOptions{}) + if _err != nil { + return _err + } + + return nil + }() + + if tryErr != nil { + var err = &tea.SDKError{} + if _t, ok := tryErr.(*tea.SDKError); ok { + err = _t + } else { + err.Message = tea.String(tryErr.Error()) + } + if !tea.BoolValue(util.Empty(err.Code)) && !tea.BoolValue(util.Empty(err.Message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + } + } + return _err +} + +func SendZabbixRecoveryMsg(data *Type.NotifyRecoveryData) (_err error) { + client, _err := createCardClient() + if _err != nil { + return _err + } + + accessToken, _ := GetToken() + robotCode := "dingrmgtodzxaik76jpc" + userId := "" + openConversationId := "cidivmW+tO/JGyIFM/XHNeQcA==" + templateId := "9a6ae88a-ba93-419e-b640-2e5fcd2556cf.schema" + lastMessage := "Zabbix告警" + searchIcon := "" + searchDesc := "" + + createAndDeliverHeaders := &dingtalkcard_1_0.CreateAndDeliverHeaders{} + createAndDeliverHeaders.XAcsDingtalkAccessToken = tea.String(accessToken) + + imGroupOpenDeliverModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ + RobotCode: tea.String(robotCode), + // 卡片接收人 + Recipients: []*string{}, + } + imGroupOpenSpaceModelLastMessageI18n := map[string]*string{ + "ZH_CN": tea.String(lastMessage), + } + imGroupOpenSpaceModelSearchSupport := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModelSearchSupport{ + SearchIcon: tea.String(searchIcon), + SearchDesc: tea.String(searchDesc), + } + imGroupOpenSpaceModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{ + SupportForward: tea.Bool(true), + LastMessageI18n: imGroupOpenSpaceModelLastMessageI18n, + SearchSupport: imGroupOpenSpaceModelSearchSupport, + } + + // 此处使用了 MockData 作为测试数据,请结合真实场景设置卡片公有数据 + cardDataJSONStr := `{ + "host": "` + data.Host + `", + "notify_msg": "` + data.NotifyMsg + `", + "event_name": "` + data.EventName + `", + "severity": "` + data.Severity + `", + "alarm_time": "` + data.AlarmTime + `", + "recovery": "` + data.Recovery + `", + "age": "` + data.Age + `", + "title_color": "` + NOTIFY_TITLE_COLOR[data.Severity] + `" + }` + var cardDataCardParamMap map[string]any + cardDataError := json.Unmarshal([]byte(cardDataJSONStr), &cardDataCardParamMap) + if cardDataError != nil { + panic(cardDataError) + } + cardData := &dingtalkcard_1_0.CreateAndDeliverRequestCardData{ + CardParamMap: convertJSONValuesToString(cardDataCardParamMap), + } + createAndDeliverRequest := &dingtalkcard_1_0.CreateAndDeliverRequest{ + UserId: tea.String(userId), + CardTemplateId: tea.String(templateId), + // 用于标识卡片的唯一 ID,业务需自行建立关联关系,用于后续的卡片更新 + OutTrackId: tea.String(fmt.Sprintf("test-out-track-id-%d", time.Now().Unix())), + CallbackType: tea.String("STREAM"), + CardData: cardData, + ImGroupOpenSpaceModel: imGroupOpenSpaceModel, + ImGroupOpenDeliverModel: imGroupOpenDeliverModel, + OpenSpaceId: tea.String(fmt.Sprintf("dtv1.card//im_group.%s", openConversationId)), + UserIdType: tea.Int32(1), + } + tryErr := func() (_e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + fmt.Println("createAndDeliverRequest", createAndDeliverRequest) + _, _err = client.CreateAndDeliverWithOptions(createAndDeliverRequest, createAndDeliverHeaders, &util.RuntimeOptions{}) + if _err != nil { + return _err + } + + return nil + }() + + if tryErr != nil { + var err = &tea.SDKError{} + if _t, ok := tryErr.(*tea.SDKError); ok { + err = _t + } else { + err.Message = tea.String(tryErr.Error()) + } + if !tea.BoolValue(util.Empty(err.Code)) && !tea.BoolValue(util.Empty(err.Message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + } + } + return _err +} + +func SendStandardMsg(title, content, color string) (_err error) { + client, _err := createCardClient() + if _err != nil { + return _err + } + + accessToken, _ := GetToken() + robotCode := "dingrmgtodzxaik76jpc" + userId := "" + openConversationId := "cidSdyTEELI8btxKdGnSprffg==" + templateId := "843a23ff-29d2-4efc-b7f4-2dea2766d7db.schema" + lastMessage := title + searchIcon := "" + searchDesc := "" + + createAndDeliverHeaders := &dingtalkcard_1_0.CreateAndDeliverHeaders{} + createAndDeliverHeaders.XAcsDingtalkAccessToken = tea.String(accessToken) + + imGroupOpenDeliverModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ + RobotCode: tea.String(robotCode), + // 卡片接收人 + Recipients: []*string{}, + } + imGroupOpenSpaceModelLastMessageI18n := map[string]*string{ + "ZH_CN": tea.String(lastMessage), + } + imGroupOpenSpaceModelSearchSupport := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModelSearchSupport{ + SearchIcon: tea.String(searchIcon), + SearchDesc: tea.String(searchDesc), + } + imGroupOpenSpaceModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{ + SupportForward: tea.Bool(true), + LastMessageI18n: imGroupOpenSpaceModelLastMessageI18n, + SearchSupport: imGroupOpenSpaceModelSearchSupport, + } + + // 此处使用了 MockData 作为测试数据,请结合真实场景设置卡片公有数据 + cardDataCardParamMap := map[string]any{ + "title": title, + "markdown": content, + "color": color, + "config": map[string]any{ + "autoLayout": true, + }, + } + cardDataError := error(nil) + _ = cardDataError + cardData := &dingtalkcard_1_0.CreateAndDeliverRequestCardData{ + CardParamMap: convertJSONValuesToString(cardDataCardParamMap), + } + createAndDeliverRequest := &dingtalkcard_1_0.CreateAndDeliverRequest{ + UserId: tea.String(userId), + CardTemplateId: tea.String(templateId), + // 用于标识卡片的唯一 ID,业务需自行建立关联关系,用于后续的卡片更新 + OutTrackId: tea.String(fmt.Sprintf("standard-out-track-id-%d", time.Now().Unix())), + CallbackType: tea.String("STREAM"), + CardData: cardData, + ImGroupOpenSpaceModel: imGroupOpenSpaceModel, + ImGroupOpenDeliverModel: imGroupOpenDeliverModel, + OpenSpaceId: tea.String(fmt.Sprintf("dtv1.card//im_group.%s", openConversationId)), + UserIdType: tea.Int32(1), + } + tryErr := func() (_e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + fmt.Println("createAndDeliverRequest", createAndDeliverRequest) + _, _err = client.CreateAndDeliverWithOptions(createAndDeliverRequest, createAndDeliverHeaders, &util.RuntimeOptions{}) + if _err != nil { + return _err + } + + return nil + }() + + if tryErr != nil { + var err = &tea.SDKError{} + if _t, ok := tryErr.(*tea.SDKError); ok { + err = _t + } else { + err.Message = tea.String(tryErr.Error()) + } + if !tea.BoolValue(util.Empty(err.Code)) && !tea.BoolValue(util.Empty(err.Message)) { + // err 中含有 code 和 message 属性,可帮助开发定位问题 + } + } + return _err +} diff --git a/alibaba/card_example.go b/alibaba/card_example.go new file mode 100644 index 0000000..7392600 --- /dev/null +++ b/alibaba/card_example.go @@ -0,0 +1,291 @@ +package alibaba + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "os" + "strings" + "time" + + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + dingtalkcard_1_0 "github.com/alibabacloud-go/dingtalk/card_1_0" + dingtalkim_1_0 "github.com/alibabacloud-go/dingtalk/im_1_0" + dingtalkoauth2_1_0 "github.com/alibabacloud-go/dingtalk/oauth2_1_0" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" + "github.com/google/uuid" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/card" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/chatbot" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/client" + "github.com/open-dingtalk/dingtalk-stream-sdk-go/logger" +) + +type DingTalkClient struct { + ClientID string + clientSecret string + imClient *dingtalkim_1_0.Client + oauthClient *dingtalkoauth2_1_0.Client + cardClient *dingtalkcard_1_0.Client +} + +var ( + dingtalkClient *DingTalkClient = nil +) + +func NewDingTalkClient(clientId, clientSecret string) *DingTalkClient { + config := &openapi.Config{} + config.Protocol = tea.String("https") + config.RegionId = tea.String("central") + oauthClient, _ := dingtalkoauth2_1_0.NewClient(config) + imClient, _ := dingtalkim_1_0.NewClient(config) + cardClient, _ := dingtalkcard_1_0.NewClient(config) + return &DingTalkClient{ + ClientID: clientId, + clientSecret: clientSecret, + oauthClient: oauthClient, + imClient: imClient, + cardClient: cardClient, + } +} + +func (c *DingTalkClient) GetAccessToken() (string, error) { + request := &dingtalkoauth2_1_0.GetAccessTokenRequest{ + AppKey: tea.String(c.ClientID), + AppSecret: tea.String(c.clientSecret), + } + response, tryErr := func() (_resp *dingtalkoauth2_1_0.GetAccessTokenResponse, _e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + _resp, _err := c.oauthClient.GetAccessToken(request) + if _err != nil { + return nil, _err + } + + return _resp, nil + }() + if tryErr != nil { + return "", tryErr + } + return *response.Body.AccessToken, nil +} + +func (c *DingTalkClient) SendCard(request *dingtalkcard_1_0.CreateAndDeliverRequest) (*dingtalkcard_1_0.CreateAndDeliverResponse, error) { + accessToken, err := c.GetAccessToken() + if err != nil { + return nil, err + } + headers := &dingtalkcard_1_0.CreateAndDeliverHeaders{} + headers.XAcsDingtalkAccessToken = tea.String(accessToken) + + resp, tryErr := func() (resp *dingtalkcard_1_0.CreateAndDeliverResponse, _e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + result, _err := c.cardClient.CreateAndDeliverWithOptions(request, headers, &util.RuntimeOptions{}) + if _err != nil { + return nil, _err + } + + return result, nil + }() + if tryErr != nil { + var sdkError = &tea.SDKError{} + if _t, ok := tryErr.(*tea.SDKError); ok { + sdkError = _t + } else { + sdkError.Message = tea.String(tryErr.Error()) + } + if !tea.BoolValue(util.Empty(sdkError.Code)) && !tea.BoolValue(util.Empty(sdkError.Message)) { + logger.GetLogger().Errorf("CreateAndDeliverWithOptions failed, clientId=%s, err=%+v", c.ClientID, sdkError) + } + return nil, tryErr + } + + return resp, nil +} + +func (c *DingTalkClient) UpdateCard(request *dingtalkcard_1_0.UpdateCardRequest) (*dingtalkcard_1_0.UpdateCardResponse, error) { + accessToken, err := c.GetAccessToken() + if err != nil { + return nil, err + } + headers := &dingtalkcard_1_0.UpdateCardHeaders{} + headers.XAcsDingtalkAccessToken = tea.String(accessToken) + + resp, tryErr := func() (resp *dingtalkcard_1_0.UpdateCardResponse, _e error) { + defer func() { + if r := tea.Recover(recover()); r != nil { + _e = r + } + }() + result, _err := c.cardClient.UpdateCardWithOptions(request, headers, &util.RuntimeOptions{}) + if _err != nil { + return nil, _err + } + + return result, nil + }() + if tryErr != nil { + var sdkError = &tea.SDKError{} + if _t, ok := tryErr.(*tea.SDKError); ok { + sdkError = _t + } else { + sdkError.Message = tea.String(tryErr.Error()) + } + if !tea.BoolValue(util.Empty(sdkError.Code)) && !tea.BoolValue(util.Empty(sdkError.Message)) { + logger.GetLogger().Errorf("UpdateCardWithOptions failed, clientId=%s, err=%+v", c.ClientID, sdkError) + } + return nil, tryErr + } + + return resp, nil +} + +func OnChatBotMessageReceived(ctx context.Context, data *chatbot.BotCallbackDataModel) ([]byte, error) { + content := strings.TrimSpace(data.Text.Content) + logger.GetLogger().Infof("received message: %v", content) + + // 卡片模板 ID + CARD_TEMPLATE_ID := "2c278d79-fc0b-41b4-b14e-8b8089dc08e8.schema" // 该模板只用于测试使用,如需投入线上使用,请导入卡片模板 json 到自己的应用下 + // 卡片公有数据,非字符串类型的卡片数据参考文档:https://open.dingtalk.com/document/orgapp/instructions-for-filling-in-api-card-data + cardData := &dingtalkcard_1_0.CreateAndDeliverRequestCardData{ + CardParamMap: make(map[string]*string), + } + cardData.CardParamMap["title"] = tea.String("钉钉互动卡片") + cardData.CardParamMap["markdown"] = tea.String(content) + cardData.CardParamMap["submitted"] = tea.String("false") + cardData.CardParamMap["tag"] = tea.String("标签") + imGroupOpenSpaceModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{ + SupportForward: tea.Bool(true), + } + imGroupOpenDeliverModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ + Extension: make(map[string]*string), + RobotCode: tea.String(dingtalkClient.ClientID), + } + imRobotOpenSpaceModel := &dingtalkcard_1_0.CreateAndDeliverRequestImRobotOpenSpaceModel{ + SupportForward: tea.Bool(true), + } + imRobotOpenDeliverModel := &dingtalkcard_1_0.CreateAndDeliverRequestImRobotOpenDeliverModel{ + Extension: make(map[string]*string), + RobotCode: tea.String(dingtalkClient.ClientID), + SpaceType: tea.String("IM_ROBOT"), + } + u, _ := uuid.NewUUID() + outTrackId := u.String() + + sendCardRequest := &dingtalkcard_1_0.CreateAndDeliverRequest{ + UserIdType: tea.Int32(1), // 1(默认):userid模式;2:unionId模式; + CardTemplateId: tea.String(CARD_TEMPLATE_ID), + OutTrackId: tea.String(outTrackId), + CallbackType: tea.String("STREAM"), // 采用 Stream 模式接收回调事件 + CardData: cardData, + } + if data.ConversationType == "2" { + // 群聊 + sendCardRequest.OpenSpaceId = tea.String(fmt.Sprintf("dtv1.card//IM_GROUP.%s", data.ConversationId)) + sendCardRequest.ImGroupOpenSpaceModel = imGroupOpenSpaceModel + sendCardRequest.ImGroupOpenDeliverModel = imGroupOpenDeliverModel + } else { + // 单聊 + sendCardRequest.OpenSpaceId = tea.String(fmt.Sprintf("dtv1.card//IM_ROBOT.%s", data.SenderStaffId)) + sendCardRequest.ImRobotOpenSpaceModel = imRobotOpenSpaceModel + sendCardRequest.ImRobotOpenDeliverModel = imRobotOpenDeliverModel + } + + // 创建并投放卡片: https://open.dingtalk.com/document/isvapp/create-and-deliver-cards + sendCardResponse, err := dingtalkClient.SendCard(sendCardRequest) + if err != nil { + logger.GetLogger().Errorf("reply card failed: %+v", sendCardResponse) + return nil, err + } + logger.GetLogger().Infof("reply card: %v %+v", outTrackId, sendCardRequest.CardData) + + time.Sleep(2 * time.Second) + + updateCardData := &dingtalkcard_1_0.UpdateCardRequestCardData{ + CardParamMap: make(map[string]*string), + } + updateCardData.CardParamMap["tag"] = tea.String("更新后的标签") + updateOptions := &dingtalkcard_1_0.UpdateCardRequestCardUpdateOptions{ + UpdateCardDataByKey: tea.Bool(true), + } + updateCardRequest := &dingtalkcard_1_0.UpdateCardRequest{ + OutTrackId: tea.String(outTrackId), + CardData: updateCardData, + CardUpdateOptions: updateOptions, + } + // 更新卡片: https://open.dingtalk.com/document/orgapp/interactive-card-update-interface + updateCardResponse, err := dingtalkClient.UpdateCard(updateCardRequest) + if err != nil { + logger.GetLogger().Errorf("update card failed: %+v", updateCardResponse) + return nil, err + } + logger.GetLogger().Infof("update card: %v %+v", outTrackId, updateCardRequest.CardData) + + return []byte(""), nil +} + +func onCardCallback(ctx context.Context, request *card.CardRequest) (*card.CardResponse, error) { + /** + * 卡片事件回调文档:https://open.dingtalk.com/document/orgapp/event-callback-card + */ + logger.GetLogger().Infof("card callback message: %v", request) + + userPrivateData := make(map[string]string) + + params := request.CardActionData.CardPrivateData.Params + if localInput, ok := params["local_input"]; ok && localInput != nil { + userPrivateData["priavte_input"] = localInput.(string) + userPrivateData["submitted"] = "true" + } + + response := &card.CardResponse{ + CardUpdateOptions: &card.CardUpdateOptions{ + UpdateCardDataByKey: true, + UpdatePrivateDataByKey: true, + }, + UserPrivateData: &card.CardDataDto{ + CardParamMap: userPrivateData, + }, + } + + responseJSON, err := json.MarshalIndent(response, "", " ") // 使用 MarshalIndent 来美化输出 + if err == nil { + logger.GetLogger().Infof("card callback response: \n%s", string(responseJSON)) + } + return response, nil +} + +func main() { + var clientId, clientSecret string + flag.StringVar(&clientId, "client_id", os.Getenv("DINGTALK_APP_CLIENT_ID"), "your-client-id") + flag.StringVar(&clientSecret, "client_secret", os.Getenv("DINGTALK_APP_CLIENT_SECRET"), "your-client-secret") + flag.Parse() + if len(clientId) == 0 || len(clientSecret) == 0 { + panic("command line options --client_id and --client_secret required") + } + + logger.SetLogger(logger.NewStdTestLogger()) + + dingtalkClient = NewDingTalkClient(clientId, clientSecret) + + cli := client.NewStreamClient(client.WithAppCredential(client.NewAppCredentialConfig(clientId, clientSecret))) + cli.RegisterChatBotCallbackRouter(OnChatBotMessageReceived) + cli.RegisterCardCallbackRouter(onCardCallback) + + err := cli.Start(context.Background()) + if err != nil { + panic(err) + } + + defer cli.Close() + + select {} +} diff --git a/alibaba/robot.go b/alibaba/robot.go new file mode 100644 index 0000000..f225210 --- /dev/null +++ b/alibaba/robot.go @@ -0,0 +1,65 @@ +// This file is auto-generated, don't edit it. Thanks. +package alibaba + +import ( + "encoding/json" + "fmt" + + openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client" + dingtalkrobot_1_0 "github.com/alibabacloud-go/dingtalk/robot_1_0" + util "github.com/alibabacloud-go/tea-utils/v2/service" + "github.com/alibabacloud-go/tea/tea" +) + +const ( + AUTO_NOTIFY_CHAT_ID = "cidSdyTEELI8btxKdGnSprffg==" + ROBOT_TOKEN = "dingrmgtodzxaik76jpc" +) + +/** + * 使用 Token 初始化账号Client + * @return Client + * @throws Exception + */ +func CreateRobotClient() (_result *dingtalkrobot_1_0.Client, _err error) { + config := &openapi.Config{} + config.Protocol = tea.String("https") + config.RegionId = tea.String("central") + _result = &dingtalkrobot_1_0.Client{} + _result, _err = dingtalkrobot_1_0.NewClient(config) + return _result, _err +} + +func SendMessage() (_err error) { + client, _err := CreateRobotClient() + if _err != nil { + return _err + } + token, err := GetToken() + if err != nil { + return err + } + + type msgP struct { + Title string `json:"title"` + Text string `json:"text"` + } + message := msgP{ + Title: "更新通知", + Text: "```golang\n{\n\"message\": \"新版本 1.2.3 已发布,请及时更新!\"\n}\n```", + } + + msgStr, _ := json.Marshal(message) + orgGroupSendHeaders := &dingtalkrobot_1_0.OrgGroupSendHeaders{} + orgGroupSendHeaders.XAcsDingtalkAccessToken = tea.String(token) + orgGroupSendRequest := &dingtalkrobot_1_0.OrgGroupSendRequest{ + MsgParam: tea.String(string(msgStr)), + MsgKey: tea.String("sampleMarkdown"), + OpenConversationId: tea.String("cidSdyTEELI8btxKdGnSprffg=="), + RobotCode: tea.String(ROBOT_TOKEN), + CoolAppCode: tea.String(""), + } + resp, _err := client.OrgGroupSendWithOptions(orgGroupSendRequest, orgGroupSendHeaders, &util.RuntimeOptions{}) + fmt.Println(resp) + return _err +} diff --git a/common/yaml.go b/common/yaml.go index 27555ee..089d214 100644 --- a/common/yaml.go +++ b/common/yaml.go @@ -9,11 +9,12 @@ import ( ) type SshConfig struct { - Name string `yaml:"name"` - Host string `yaml:"host"` - Port int `yaml:"port"` - Username string `yaml:"username"` - Password string `yaml:"password"` + Name string `yaml:"name"` + Host string `yaml:"host"` + Port int `yaml:"port"` + Username string `yaml:"username"` + Password string `yaml:"password"` + PrivateKeyPath string } type MysqlConfig struct { diff --git a/controller/alibaba.go b/controller/alibaba.go new file mode 100644 index 0000000..8a8f64f --- /dev/null +++ b/controller/alibaba.go @@ -0,0 +1,49 @@ +package controller + +import ( + "backend/Type" + "backend/alibaba" + "backend/util" + "log" + + "github.com/gin-gonic/gin" +) + +func AlibabaNotify(c *gin.Context) { + r := Type.NotifyData{} + if err := c.ShouldBindJSON(&r); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + err := alibaba.SendZabbixMsg(&r) + if err != nil { + log.Printf("failed to send notify message: %v", err) + } +} + +func AlibabaGameNotify(c *gin.Context) { + r := Type.AlibabaNotifyData{} + if err := c.ShouldBindJSON(&r); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + content := util.ParseTmpl("./template/alibaba_notify.tmpl", r) + err := alibaba.SendStandardMsg("游戏服务报警通知", content, "red") + if err != nil { + log.Printf("failed to send notify message: %v", err) + } +} + +func AlibabaRecovery(c *gin.Context) { + r := Type.NotifyRecoveryData{} + if err := c.ShouldBindJSON(&r); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + err := alibaba.SendZabbixRecoveryMsg(&r) + if err != nil { + log.Printf("failed to send recovery message: %v", err) + } +} diff --git a/controller/operation.go b/controller/operation.go index 0a1b8ab..83a5f0b 100644 --- a/controller/operation.go +++ b/controller/operation.go @@ -1,8 +1,10 @@ package controller import ( + "backend/model" "backend/util" "fmt" + "time" "github.com/gin-gonic/gin" ) @@ -54,17 +56,18 @@ func CopyUserOperation(srcAppID, dstAppID, srcUid, dstUid int) error { defer DstDb.Close() // 复制 Mod 表 srcData := DbData{} - // _, err = model.UserGM(dstAppID, 1, dstUid, "logout") - // time.Sleep(time.Second * 2) - // if err != nil { - // return err - // } + err = SrcDb.Get(&srcData, "SELECT dwUin, mData, updateTime FROM t_player_mod WHERE dwUin = ? LIMIT 1", srcUid) if err != nil { return err } var auto_id int64 _ = DstDb.Get(&auto_id, "SELECT auto_id FROM t_account WHERE user_name = ?", srcData.DwUin) + _, err = model.UserGM(dstAppID, 1, dstUid, "logout") + time.Sleep(time.Second * 2) + if err != nil { + return err + } if auto_id == 0 { result, err := DstDb.Exec("INSERT INTO t_account (user_name, user_password) VALUES (?, ?) ON DUPLICATE KEY UPDATE user_password = VALUES(user_password)", srcData.DwUin, "123456") if err != nil { diff --git a/controller/server.go b/controller/server.go index 3ea8292..eb5e99d 100644 --- a/controller/server.go +++ b/controller/server.go @@ -51,7 +51,11 @@ func AddNode(c *gin.Context) { func ServerList(c *gin.Context) { Server := model.Server{} - c.BindJSON(&Server) + err := c.BindJSON(&Server) + if err != nil { + failed(c, "参数绑定失败: "+err.Error()) + return + } ServerList, err := Server.ServerList() if err != nil { failed(c, err.Error()) @@ -96,6 +100,18 @@ func UpdateApp(c *gin.Context) { success(c, map[string]string{"msg": msg}) } +func UpdateAppReview(c *gin.Context) { + Server := model.Server{} + c.BindJSON(&Server) + msg, err := Server.UpdateAppReview() + if err != nil { + failed(c, err.Error()) + return + } + util.AddAdminLog(c, "更新Review应用", Server) + success(c, map[string]string{"msg": msg}) +} + func UpdateAppFeishu(c *gin.Context) { Server := model.Server{} c.BindJSON(&Server) diff --git a/go.mod b/go.mod index 92b2064..ade0fb4 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect + github.com/alibabacloud-go/darabonba-openapi v0.2.1 // indirect github.com/alibabacloud-go/debug v1.0.1 // indirect github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect github.com/alibabacloud-go/gateway-dingtalk v1.0.2 // indirect @@ -64,6 +65,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 // indirect github.com/oschwald/maxminddb-golang v1.13.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 211de95..881264c 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi v0.2.1 h1:WyzxxKvhdVDlwpAMOHgAiCJ+NXa6g5ZWPFEzaK/ewwY= +github.com/alibabacloud-go/darabonba-openapi v0.2.1/go.mod h1:zXOqLbpIqq543oioL9IuuZYOQgHQ5B8/n5OPrnko8aY= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.0/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.11/go.mod h1:wHxkgZT1ClZdcwEVP/pDgYK/9HucsnCfMipmJgCz4xY= github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.12/go.mod h1:cgtLEj8i4ddXMcQgq4PnpVQvlzS+y5B+QtdSfmcLM3A= @@ -24,6 +26,7 @@ github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9 h1:7P0KWfed/YMtpeuW3E2iwo github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9/go.mod h1:kgnXaV74AVjM3ZWJu1GhyXGuCtxljJ677oUfz6MyJOE= github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= @@ -65,6 +68,7 @@ github.com/alibabacloud-go/tea-oss-sdk v1.1.5/go.mod h1:5fhlKMa/kWRJNgPYRt+5qSg3 github.com/alibabacloud-go/tea-oss-utils v1.1.0 h1:y65crjjcZ2Pbb6UZtC2deuIZHDVTS3IaDWE7M9nVLRc= github.com/alibabacloud-go/tea-oss-utils v1.1.0/go.mod h1:PFCF12e9yEKyBUIn7X1IrF/pNjvxgkHy0CgxX4+xRuY= github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils v1.4.5 h1:h0/6Xd2f3bPE4XHTvkpjwxowIwRCJAJOqY6Eq8f3zfA= github.com/alibabacloud-go/tea-utils v1.4.5/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4= @@ -188,6 +192,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 h1:Lb/Uzkiw2Ugt2Xf03J5wmv81PdkYOiWbI8CNBi1boC8= +github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU= github.com/oschwald/geoip2-golang v1.13.0 h1:Q44/Ldc703pasJeP5V9+aFSZFmBN7DKHbNsSFzQATJI= github.com/oschwald/geoip2-golang v1.13.0/go.mod h1:P9zG+54KPEFOliZ29i7SeYZ/GM6tfEL+rgSn03hYuUo= github.com/oschwald/maxminddb-golang v1.13.0 h1:R8xBorY71s84yO06NgTmQvqvTvlS/bnYZrrWX1MElnU= diff --git a/main.go b/main.go index cbc5d14..9668ee1 100644 --- a/main.go +++ b/main.go @@ -97,6 +97,13 @@ func main() { feishuApi.POST("/notify/client", controller.FeishuNotifyClient) // 客户端报警 feishuApi.POST("/notify/order", controller.FeishuNotifyOrder) // 订单通知 } + alibabaApi := r.Group("/api/alibaba") + { + // 阿里云 + alibabaApi.POST("/zabbix/notify", controller.AlibabaNotify) // 系统报警 + alibabaApi.POST("/zabbix/recovery", controller.AlibabaRecovery) // 系统报警 + alibabaApi.POST("/game/notify", controller.AlibabaGameNotify) // 游戏报警 + } api := r.Group("/api", middleware.ValidateToken()) { // 账号管理 @@ -123,6 +130,7 @@ func main() { api.POST("/server/editServer", controller.EditServer) api.POST("/server/updateApp", controller.UpdateApp) + api.POST("/server/updateAppReview", controller.UpdateAppReview) api.POST("/server/updateAppFeishu", controller.UpdateAppFeishu) api.POST("/server/restart", controller.RestartServer) api.POST("/server/reload", controller.ReloadServer) @@ -151,11 +159,11 @@ func main() { { scripts.POST("/copywriting", controller.Copywriting) // 下载文案文件 } - go util.ScheduleDailyTask() + //go util.ScheduleDailyTask() go server.Server() - go model.InitToken() // 初始化 Token 列表 - go controller.USSendInfo() // 启动定时任务发送信息 - go monitor.UserAliveMonitor(0) // 用户存活监控 + go model.InitToken() // 初始化 Token 列表 + //go controller.USSendInfo() // 启动定时任务发送信息 + //go monitor.UserAliveMonitor(0) // 用户存活监控 go monitor.ServerInfoMonitor() // 服务器信息监控 defer func() { if err := recover(); err != nil { diff --git a/model/mail.go b/model/mail.go index e17fab6..5b376df 100644 --- a/model/mail.go +++ b/model/mail.go @@ -81,19 +81,22 @@ func (m *Mail) SendMail() error { if err != nil { return fmt.Errorf("failed to insert mail: %v", err) } - go func() { - ws, err := util.GetWebsocket(m.AppId, m.ServerId) - if err != nil { - log.Printf("failed to get websocket: %v", err) - } - defer ws.Close() + ServerList := util.GetServerInfo(m.AppId, 0) + for _, server := range ServerList { + go func() { + ws, err := util.GetWebsocket(m.AppId, server.ServerId) + if err != nil { + log.Printf("failed to get websocket: %v", err) + } + defer ws.Close() - req := &msg.ReqReloadServerMail{} - _, err = util.SendAdminMsg(ws, req) - if err != nil { - log.Printf("failed to send admin message: %v", err) - } - }() + req := &msg.ReqReloadServerMail{} + _, err = util.SendAdminMsg(ws, req) + if err != nil { + log.Printf("failed to send admin message: %v", err) + } + }() + } return nil } diff --git a/model/server.go b/model/server.go index ee00141..29e3c8c 100644 --- a/model/server.go +++ b/model/server.go @@ -2,12 +2,13 @@ package model import ( "backend/Type" - "backend/common" + "backend/alibaba" "backend/feishu" "backend/msg" util "backend/util" "fmt" "log" + "strings" "github.com/Ullaakut/nmap" ) @@ -25,6 +26,11 @@ type Server struct { Type int `json:"Type"` Tags string `json:"Tags"` ClientVersion string `json:"ClientVersion"` + Host string `json:"Host"` + Port int `json:"Port"` + WsPort int `json:"WsPort"` + WorkDir string `json:"WorkDir"` + Ecs int `json:"Ecs"` } func (s *Server) AppList() ([]*Type.App, error) { @@ -58,8 +64,8 @@ func (s *Server) ServerList() ([]*Type.ServerInfo, error) { func (s *Server) AddServer() error { Db := util.MPool.GetGameDB() defer Db.Close() - _, err := Db.Exec("INSERT INTO server (`AppId`, `ServerId`, `ServerName`, `Status`, `CreateTime`, `OpenServerTime`) VALUES (?, ?, ?, ?, ?, ?)", - s.AppId, s.ServerId, s.ServerName, s.Status, util.Now(), s.OpenServerTime) + _, err := Db.Exec("INSERT INTO server (`AppId`, `ServerId`, `ServerName`, `Status`, `CreateTime`, `OpenServerTime`, `Host`, `Port`, `version`, `ws_port`, `work_dir`, `ecs`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?)", + s.AppId, s.ServerId, s.ServerName, s.Status, util.Now(), s.OpenServerTime, s.Host, s.Port, s.ClientVersion, s.WsPort, s.WorkDir, s.Ecs) if err != nil { return fmt.Errorf("failed to insert: %v", err) } @@ -78,52 +84,171 @@ func (s *Server) EditServer() error { } func (s *Server) UpdateApp() (string, error) { + nodeInfo := util.GetNodeByName("devops") AppConfig, err := util.GetAppConfig(s.AppId) if err != nil { return "", err } - ServerConfig, err := common.GetServerConfig(DEVOPS_SERVER) + SshClient, err := util.NewSshClient(nodeInfo) if err != nil { return "", err } + param := Type.AlibabaUpdateCardParam{} - SshClient, err := NewSshClient(ServerConfig) - if err != nil { - return "", err - } - defer SshClient.client.Close() + defer SshClient.Close() _, err = SshClient.RunCommand("cd /data/devops && git pull") if err != nil { return "", fmt.Errorf("failed to git pull: %v", err) } + now := util.Now() cmd := fmt.Sprintf("ansible-playbook /data/devops/playbook/%s.yml -i /data/devops/playbook/hosts", AppConfig.AppName) output, err := SshClient.RunCommand(cmd) + param.UpdateTime = util.NowFormat() + param.Duration = fmt.Sprintf("%d 秒", util.Now()-now) + param.Server = AppConfig.AppName + if err != nil { + param.SrcGitLog = fmt.Sprintf("- 更新失败: %v\n", err) + param.DocGitLog = "- output:\n" + output + content := util.ParseTmpl("./template/update.tmpl", param) + alibaba.SendStandardMsg("游戏服务更新失败", content, "red") return "", err } + // 记录git 更新状态 + cmd = "cd /codes/pet_home_server && git rev-parse HEAD" + srcHead, err := SshClient.RunCommand(cmd) + if err != nil { + log.Printf("警告: 无法获取源码 git head (目录或git命令可能不存在): %v", err) + // 继续执行,不中断流程 + } else { + srcHead = strings.TrimSpace(srcHead) + oldSrcHead, _ := util.GetGitHead("src") + util.SaveGitHead("src", srcHead) + if oldSrcHead != "" && oldSrcHead != srcHead { + cmd = fmt.Sprintf("cd /codes/pet_home_server && git log %s..%s --pretty=format:'%%h-%%s'", oldSrcHead, srcHead) + commitMsg, _ := SshClient.RunCommand(cmd) + if commitMsg != "" { + param.SrcGitLog = "- server-git log\n" + commitMsg + } + } + } + + cmd = "cd /data/docs && git rev-parse HEAD" + docHead, err := SshClient.RunCommand(cmd) + if err != nil { + log.Printf("警告: 无法获取文档 git head (目录或git命令可能不存在): %v", err) + // 继续执行,不中断流程 + } else { + docHead = strings.TrimSpace(docHead) + oldDocHead, _ := util.GetGitHead("doc") + util.SaveGitHead("doc", docHead) + if oldDocHead != "" && oldDocHead != docHead { + cmd = fmt.Sprintf("cd /data/docs && git log %s..%s --pretty=format:'%%h-%%s'", oldDocHead, docHead) + commitMsg, _ := SshClient.RunCommand(cmd) + if commitMsg != "" { + param.DocGitLog = "- docs-git log\n" + commitMsg + } + } + } + + content := util.ParseTmpl("./template/update.tmpl", param) + DB := util.MPool.GetGameDB() defer DB.Close() DB.Exec("UPDATE app SET `Update` = ? WHERE `AppId` = ?", util.Now(), s.AppId) - feishu.SendFeishuMsg(fmt.Sprintf("AppName: %s, 执行文件更新完成", AppConfig.AppName)) - + alibaba.SendStandardMsg("游戏服务更新完成", content, "green") return output, nil } -func (s *Server) UpdateAppFeishu() (string, error) { +func (s *Server) UpdateAppReview() (string, error) { + nodeInfo := util.GetNodeByName("devops") AppConfig, err := util.GetAppConfig(s.AppId) if err != nil { return "", err } - ServerConfig, err := common.GetServerConfig(DEVOPS_SERVER) + SshClient, err := util.NewSshClient(nodeInfo) if err != nil { return "", err } + param := Type.AlibabaUpdateCardParam{} + + defer SshClient.Close() + _, err = SshClient.RunCommand("cd /data/devops && git pull") + if err != nil { + return "", fmt.Errorf("failed to git pull: %v", err) + } + now := util.Now() + cmd := fmt.Sprintf("ansible-playbook /data/devops/playbook/%s-review.yml -i /data/devops/playbook/hosts", AppConfig.AppName) + output, err := SshClient.RunCommand(cmd) + param.UpdateTime = util.NowFormat() + param.Duration = fmt.Sprintf("%d 秒", util.Now()-now) + param.Server = AppConfig.AppName + + if err != nil { + param.SrcGitLog = fmt.Sprintf("- 更新失败: %v\n", err) + param.DocGitLog = "- output:\n" + output + content := util.ParseTmpl("./template/update.tmpl", param) + alibaba.SendStandardMsg("游戏服务更新失败", content, "red") + return "", err + } + // 记录git 更新状态 + cmd = "cd /codes/pet_home_server && git rev-parse HEAD" + srcHead, err := SshClient.RunCommand(cmd) + if err != nil { + log.Printf("警告: 无法获取源码 git head (目录或git命令可能不存在): %v", err) + // 继续执行,不中断流程 + } else { + srcHead = strings.TrimSpace(srcHead) + oldSrcHead, _ := util.GetGitHead("src") + util.SaveGitHead("src", srcHead) + if oldSrcHead != "" && oldSrcHead != srcHead { + cmd = fmt.Sprintf("cd /codes/pet_home_server && git log %s..%s --pretty=format:'%%h-%%s'", oldSrcHead, srcHead) + commitMsg, _ := SshClient.RunCommand(cmd) + if commitMsg != "" { + param.SrcGitLog = "- server-git log\n" + commitMsg + } + } + } - SshClient, err := NewSshClient(ServerConfig) + cmd = "cd /data/docs && git rev-parse HEAD" + docHead, err := SshClient.RunCommand(cmd) + if err != nil { + log.Printf("警告: 无法获取文档 git head (目录或git命令可能不存在): %v", err) + // 继续执行,不中断流程 + } else { + docHead = strings.TrimSpace(docHead) + oldDocHead, _ := util.GetGitHead("doc") + util.SaveGitHead("doc", docHead) + if oldDocHead != "" && oldDocHead != docHead { + cmd = fmt.Sprintf("cd /data/docs && git log %s..%s --pretty=format:'%%h-%%s'", oldDocHead, docHead) + commitMsg, _ := SshClient.RunCommand(cmd) + if commitMsg != "" { + param.DocGitLog = "- docs-git log\n" + commitMsg + } + } + } + + content := util.ParseTmpl("./template/update.tmpl", param) + + DB := util.MPool.GetGameDB() + defer DB.Close() + DB.Exec("UPDATE app SET `Update` = ? WHERE `AppId` = ?", util.Now(), s.AppId) + alibaba.SendStandardMsg("游戏review服务更新完成", content, "green") + return output, nil +} + +func (s *Server) UpdateAppFeishu() (string, error) { + serverInfo := util.GetServer(s.AppId, s.ServerId) + nodeInfo := util.GetNodeById(serverInfo.Ecs) + AppConfig, err := util.GetAppConfig(s.AppId) if err != nil { return "", err } - defer SshClient.client.Close() + SshClient, err := util.NewSshClient(nodeInfo) + if err != nil { + return "", err + } + defer SshClient.Close() _, err = SshClient.RunCommand("cd /data/devops && git pull") if err != nil { return "", fmt.Errorf("failed to git pull: %v", err) @@ -141,22 +266,19 @@ func (s *Server) UpdateAppFeishu() (string, error) { } func (s *Server) RestartServer() (string, error) { + serverInfo := util.GetServer(s.AppId, s.ServerId) + nodeInfo := util.GetNodeById(serverInfo.Ecs) AppConfig, err := util.GetAppConfig(s.AppId) if err != nil { return "", err } - ServerConfig, err := common.GetServerConfig(AppConfig.NodeName) + SshClient, err := util.NewSshClient(nodeInfo) if err != nil { return "", err } - - SshClient, err := NewSshClient(ServerConfig) - if err != nil { - return "", err - } - defer SshClient.client.Close() - - cmd := fmt.Sprintf("cd %s && ./tool/tool restart node %d", AppConfig.Path, s.ServerId) + defer SshClient.Close() + workDir := serverInfo.WorkDir + cmd := fmt.Sprintf("cd %s && ./tool/tool restart node %d", workDir, s.ServerId) output, err := SshClient.RunCommand(cmd) if err != nil { return "", err diff --git a/model/ssh.go b/model/ssh.go deleted file mode 100644 index 65c73fe..0000000 --- a/model/ssh.go +++ /dev/null @@ -1,47 +0,0 @@ -package model - -import ( - "backend/common" - "backend/util" - "fmt" - - "golang.org/x/crypto/ssh" -) - -type SshClient struct { - client *ssh.Client -} - -func NewSshClient(config *common.SshConfig) (*SshClient, error) { - SP, _ := util.Decrypt(config.Password) - sshconfig := &ssh.ClientConfig{ - User: config.Username, - Auth: []ssh.AuthMethod{ - ssh.Password(SP), - }, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - } - - addr := fmt.Sprintf("%s:%d", config.Host, config.Port) - client, err := ssh.Dial("tcp", addr, sshconfig) - if err != nil { - return nil, err - } - - return &SshClient{client: client}, nil -} - -func (s *SshClient) RunCommand(cmd string) (string, error) { - session, err := s.client.NewSession() - if err != nil { - return "", err - } - defer session.Close() - - output, err := session.CombinedOutput(cmd) - if err != nil { - return "", err - } - - return string(output), nil -} diff --git a/monitor/Monitor.go b/monitor/Monitor.go index 61262b5..0db1555 100644 --- a/monitor/Monitor.go +++ b/monitor/Monitor.go @@ -66,7 +66,6 @@ func ServerInfoMonitor() { now := time.Now() next := now.Truncate(1 * time.Minute).Add(1 * time.Minute) time.Sleep(time.Until(next)) - monitorServerInfo() } } @@ -80,7 +79,7 @@ func monitorServerInfo() { return } for _, v := range server { - if v.AppId == 5 { + if v.Status == 2 || v.Status == 3 { // 维护中或停用跳过 continue } go func(v *Type.ServerInfo) { @@ -131,5 +130,4 @@ func monitorServerInfo() { tmpDb.Exec("update server set Status=1, Online=?,free_mem=?,cpu=?,weight=? where AppId=? and ServerId=?", util.Int(serverInfo["PlayerNum"]), usage_mem, cpu, weight, v.AppId, v.ServerId) }(v) } - } diff --git a/template/alibaba_notify.tmpl b/template/alibaba_notify.tmpl new file mode 100644 index 0000000..c659227 --- /dev/null +++ b/template/alibaba_notify.tmpl @@ -0,0 +1,9 @@ + +- 游戏环境:`{{.NotifyMsg}}` +- 报错函数:`{{.FuncName}}` +- 发生时间:`{{.AlarmTime}}` +------------------------ +- 堆栈信息: +```golang +{{.Stack}} +``` \ No newline at end of file diff --git a/template/update.tmpl b/template/update.tmpl new file mode 100644 index 0000000..0ac1a61 --- /dev/null +++ b/template/update.tmpl @@ -0,0 +1,9 @@ + +- 游戏环境:`{{.Server}}` +- 更新时间:`{{.UpdateTime}}` +- 耗时:`{{.Duration}}`[鼓掌] +------------------------ +```txt +{{.SrcGitLog}} +{{.DocGitLog}} +``` \ No newline at end of file diff --git a/unit_test.go b/unit_test.go index 094b980..fbc5e63 100644 --- a/unit_test.go +++ b/unit_test.go @@ -102,3 +102,50 @@ func TestAlibaba(t *testing.T) { version := alibaba.DownloadFile() fmt.Println("version:", version) } + +func TestAlibabaRobot(t *testing.T) { + title := "【meowment】订单发货成功" + content := ` +- 玩家账号:test_user_001 +- 玩家等级:25 +- 订单号:e50b32601160423c7f + +- 金额 :1.99 + +- 发货时间:2026-01-15 18:22:12 +- 充值总数:10.99 元 + +[鼓掌][鼓掌][鼓掌] + + ` + err := alibaba.SendStandardMsg(title, content, "green") + if err != nil { + fmt.Println("err:", err) + } else { + fmt.Println("消息发送成功") + } +} + +func TestAlibabaCard(t *testing.T) { + r := Type.NotifyData{ + NotifyMsg: "测试报警消息内容", + Host: "测试主机", + EventName: "测试事件名称", + Severity: "High", + AlarmTime: time.Unix(time.Now().Unix(), 0).Format("2006-01-02 15:04:05"), + } + err := alibaba.SendZabbixMsg(&r) + if err != nil { + fmt.Println("err:", err) + } else { + fmt.Println("卡片发送成功") + } +} + +func TestServerUpdateApp(t *testing.T) { + +} + +func TestXxxx(t *testing.T) { + util.SaveGitHead("src", "7d6a040cec2841c728fc798d720bac30c5084ceb") +} diff --git a/util/bbolt.go b/util/bbolt.go index c5cebe5..013c1b8 100644 --- a/util/bbolt.go +++ b/util/bbolt.go @@ -1,6 +1,7 @@ package util import ( + "fmt" "os" "path/filepath" @@ -76,3 +77,20 @@ func GetLanguageExportLastUpdate() (int, error) { v, e := getBBolt("language_export_last_update") return Int(v), e } + +func GetGitHead(key string) (string, error) { + return getBBolt(fmt.Sprintf("git_head_%s", key)) +} + +func SaveGitHead(key string, head interface{}) error { + return saveBBolt(fmt.Sprintf("git_head_%s", key), head) +} + +func GetUserConnectNode(Uid string) (int, error) { + v, e := getBBolt(fmt.Sprintf("user_connect_node_%s", Uid)) + return Int(v), e +} + +func SaveUserConnectNode(Uid string, node interface{}) error { + return saveBBolt(fmt.Sprintf("user_connect_node_%s", Uid), node) +} diff --git a/util/login.go b/util/login.go index fe1ce9a..daa0d82 100644 --- a/util/login.go +++ b/util/login.go @@ -17,6 +17,9 @@ type ServerConfig struct { Version string `db:"version"` Status int `db:"Status"` Weight int `db:"Weight"` + WorkDir string `db:"work_dir"` + Ecs int `db:"ecs"` + Name string `db:"ServerName"` } func LoginResponse(c *gin.Context, AppId, AreaCode int, Version string) { @@ -185,6 +188,17 @@ func GetServerInfo(AppId, AreaCode int) []ServerConfig { return servers } +func GetServer(AppId, ServerId int) ServerConfig { + Db := MPool.GetGameDB() + defer Db.Close() + var server ServerConfig + err := Db.Get(&server, "SELECT ServerId, Status, Host, Port, MaxOnline, Online, version, ecs, work_dir FROM server WHERE AppId = ? AND ServerId = ?", AppId, ServerId) + if err != nil { + return ServerConfig{} + } + return server +} + // VersionDistance 计算两个版本号之间的跨度 // 版本号格式为 major.minor.patch (例如: 1.0.0) // 返回值越大表示版本差异越大 diff --git a/util/node.go b/util/node.go new file mode 100644 index 0000000..080627d --- /dev/null +++ b/util/node.go @@ -0,0 +1,27 @@ +package util + +import ( + "backend/Type" +) + +func GetNodeById(id int) *Type.Node { + Db := MPool.GetGameDB() + defer Db.Close() + node := &Type.Node{} + err := Db.Get(node, "SELECT * FROM node WHERE id = ? LIMIT 1", id) + if err != nil { + return nil + } + return node +} + +func GetNodeByName(name string) *Type.Node { + Db := MPool.GetGameDB() + defer Db.Close() + node := &Type.Node{} + err := Db.Get(node, "SELECT * FROM node WHERE name = ? LIMIT 1", name) + if err != nil { + return nil + } + return node +} diff --git a/util/ssh.go b/util/ssh.go new file mode 100644 index 0000000..4293a15 --- /dev/null +++ b/util/ssh.go @@ -0,0 +1,77 @@ +package util + +import ( + "backend/Type" + "fmt" + "os" + + "golang.org/x/crypto/ssh" +) + +type SshClient struct { + client *ssh.Client +} + +func (ssh *SshClient) Close() { + ssh.client.Close() +} + +func NewSshClient(config *Type.Node) (*SshClient, error) { + var authMethods []ssh.AuthMethod + + // 支持证书连接 + // 从文件读取私钥 + if config.PrivateKeyPath != "" { + keyBytes, err := os.ReadFile(config.PrivateKeyPath) + if err != nil { + return nil, fmt.Errorf("read private key file failed: %w", err) + } + key, err := ssh.ParsePrivateKey(keyBytes) + if err != nil { + return nil, fmt.Errorf("parse private key failed: %w", err) + } + authMethods = append(authMethods, ssh.PublicKeys(key)) + } + + // 支持密码连接 + if config.Password != "" { + SP, err := Decrypt(config.Password) + if err != nil { + return nil, fmt.Errorf("decrypt password failed: %w", err) + } + authMethods = append(authMethods, ssh.Password(SP)) + } + + if len(authMethods) == 0 { + return nil, fmt.Errorf("no authentication method provided") + } + + sshconfig := &ssh.ClientConfig{ + User: "root", + Auth: authMethods, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + addr := fmt.Sprintf("%s:22", config.Host) + client, err := ssh.Dial("tcp", addr, sshconfig) + if err != nil { + return nil, err + } + + return &SshClient{client: client}, nil +} + +func (s *SshClient) RunCommand(cmd string) (string, error) { + session, err := s.client.NewSession() + if err != nil { + return "", err + } + defer session.Close() + + output, err := session.CombinedOutput(cmd) + if err != nil { + return "", err + } + + return string(output), nil +} diff --git a/util/util.go b/util/util.go index a52bf4e..fd18ddd 100644 --- a/util/util.go +++ b/util/util.go @@ -209,6 +209,10 @@ func Year() int { return time.Now().Year() } +func NowFormat() string { + return time.Now().Format("2006-01-02 15:04:05") +} + func SendAdminMsg(ws *websocket.Conn, req proto.Message) (map[string]interface{}, error) { reqBuf := PackMsg(req) _, err := ws.Write(reqBuf)