版本更新

This commit is contained in:
hahwu 2026-01-19 18:53:24 +08:00
parent 2176a9dd4b
commit 4d8c3dbfcf
24 changed files with 1323 additions and 123 deletions

View File

@ -1,10 +1,3 @@
生成一个方法
## 方法名
ToTmplStr
## 参数
string
## 逻辑
将字符串转换成一行的字符串格式,并再进行一次转义输出
## 游戏服务更新
<font sizeToken=common_footnote_text_style__font_size>此成果由<font colorTokenV2=common_blue1_color>@钉三多</font>确认完成</font>

View File

@ -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"`
}

459
alibaba/card.go Normal file
View File

@ -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
}

291
alibaba/card_example.go Normal file
View File

@ -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模式2unionId模式;
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 {}
}

65
alibaba/robot.go Normal file
View File

@ -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
}

View File

@ -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 {

49
controller/alibaba.go Normal file
View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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)

2
go.mod
View File

@ -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

6
go.sum
View File

@ -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=

16
main.go
View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -0,0 +1,9 @@
- 游戏环境:`{{.NotifyMsg}}`
- 报错函数:`{{.FuncName}}`
- 发生时间:`{{.AlarmTime}}`
------------------------
- 堆栈信息:
```golang
{{.Stack}}
```

9
template/update.tmpl Normal file
View File

@ -0,0 +1,9 @@
- 游戏环境:`{{.Server}}`
- 更新时间:`{{.UpdateTime}}`
- 耗时:`{{.Duration}}`[鼓掌]
------------------------
```txt
{{.SrcGitLog}}
{{.DocGitLog}}
```

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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)
// 返回值越大表示版本差异越大

27
util/node.go Normal file
View File

@ -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
}

77
util/ssh.go Normal file
View File

@ -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
}

View File

@ -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)