| @@ -4,6 +4,7 @@ import ( | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| "github.com/go-resty/resty/v2" | |||
| "strconv" | |||
| @@ -19,6 +20,7 @@ const ( | |||
| ACCESS_TOKEN_PATH = "/cgi-bin/token" | |||
| QR_CODE_PATH = "/cgi-bin/qrcode/create" | |||
| GET_MATERIAL_PATH = "/cgi-bin/material/batchget_material" | |||
| SEND_TEMPLATE_PATH = "/cgi-bin/message/template/send" | |||
| ACTION_QR_STR_SCENE = "QR_STR_SCENE" | |||
| ERR_CODE_ACCESSTOKEN_EXPIRE = 42001 | |||
| @@ -41,12 +43,33 @@ type QRCodeRequest struct { | |||
| Action_info ActionInfo `json:"action_info"` | |||
| Expire_seconds int `json:"expire_seconds"` | |||
| } | |||
| type MaterialRequest struct { | |||
| Type string `json:"type"` | |||
| Offset int `json:"offset"` | |||
| Count int `json:"count"` | |||
| } | |||
| type TemplateMsgRequest struct { | |||
| ToUser string `json:"touser"` | |||
| TemplateId string `json:"template_id"` | |||
| Url string `json:"url"` | |||
| ClientMsgId string `json:"client_msg_id"` | |||
| Data interface{} `json:"data"` | |||
| } | |||
| type TemplateValue struct { | |||
| Value string `json:"value"` | |||
| Color string `json:"color"` | |||
| } | |||
| type CloudbrainTaskData struct { | |||
| First TemplateValue `json:"first"` | |||
| Keyword1 TemplateValue `json:"keyword1"` | |||
| Keyword2 TemplateValue `json:"keyword2"` | |||
| Keyword3 TemplateValue `json:"keyword3"` | |||
| Remark TemplateValue `json:"remark"` | |||
| } | |||
| type ActionInfo struct { | |||
| Scene Scene `json:"scene"` | |||
| } | |||
| @@ -161,3 +184,30 @@ func getErrorCodeFromResponse(r *resty.Response) int { | |||
| c, _ := strconv.Atoi(fmt.Sprint(code)) | |||
| return c | |||
| } | |||
| func sendTemplateMsg(req TemplateMsgRequest) (error, bool) { | |||
| client := getWechatRestyClient() | |||
| bodyJson, _ := json.Marshal(req) | |||
| r, err := client.R(). | |||
| SetHeader("Content-Type", "application/json"). | |||
| SetQueryParam("access_token", GetWechatAccessToken()). | |||
| SetBody(bodyJson). | |||
| Post(setting.WechatApiHost + SEND_TEMPLATE_PATH) | |||
| if err != nil { | |||
| log.Error("sendTemplateMsg,e=%v", err) | |||
| return nil, false | |||
| } | |||
| a := r.Body() | |||
| resultMap := make(map[string]interface{}, 0) | |||
| json.Unmarshal(a, &resultMap) | |||
| errcode := resultMap["errcode"] | |||
| log.Info("sendTemplateMsg,%v", r) | |||
| if errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_EXPIRE) || errcode == fmt.Sprint(ERR_CODE_ACCESSTOKEN_INVALID) { | |||
| return nil, true | |||
| } | |||
| if errcode != "" { | |||
| return errors.New(fmt.Sprintf("sendTemplateMsg error[%s]", errcode)), false | |||
| } | |||
| return nil, false | |||
| } | |||
| @@ -0,0 +1,110 @@ | |||
| package wechat | |||
| import ( | |||
| "code.gitea.io/gitea/models" | |||
| "code.gitea.io/gitea/modules/log" | |||
| "code.gitea.io/gitea/modules/setting" | |||
| "errors" | |||
| "fmt" | |||
| "time" | |||
| ) | |||
| type JobOperateType string | |||
| const ( | |||
| JobOperateTypeStart JobOperateType = "start" | |||
| JobOperateTypeStop JobOperateType = "stop" | |||
| JobOperateTypeFailed JobOperateType = "failed" | |||
| ) | |||
| func SendCloudbrainTemplateMsg(operateType JobOperateType, cloudbrain models.Cloudbrain, date time.Time) error { | |||
| openId := models.GetUserWechatOpenId(cloudbrain.UserID) | |||
| if openId == "" { | |||
| return errors.New("Wechat openId not exist") | |||
| } | |||
| data := CloudbrainTaskData{ | |||
| First: TemplateValue{Value: getCloudbrainTemplateTitle(operateType)}, | |||
| Keyword1: TemplateValue{Value: cloudbrain.DisplayJobName}, | |||
| Keyword2: TemplateValue{Value: getJobTypeDisplayName(cloudbrain.JobType)}, | |||
| Keyword3: TemplateValue{Value: date.Format("2006-01-02 15:04:05")}, | |||
| Remark: TemplateValue{Value: cloudbrain.DisplayJobName}, | |||
| } | |||
| req := TemplateMsgRequest{ | |||
| ToUser: openId, | |||
| TemplateId: setting.CloudbrainTemplateId, | |||
| Url: getCloudbrainTemplateUrl(cloudbrain), | |||
| ClientMsgId: string(operateType) + "_" + fmt.Sprint(cloudbrain.ID), | |||
| Data: data, | |||
| } | |||
| err, retryFlag := sendTemplateMsg(req) | |||
| if retryFlag { | |||
| log.Info("retrySendCloudbrainTemplateMsg calling") | |||
| refreshAccessToken() | |||
| err, _ = sendTemplateMsg(req) | |||
| if err != nil { | |||
| log.Error("SendCloudbrainTemplateMsg err. %v", err) | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| if err != nil { | |||
| log.Error("SendCloudbrainTemplateMsg err. %v", err) | |||
| return err | |||
| } | |||
| return nil | |||
| } | |||
| func getCloudbrainTemplateUrl(cloudbrain models.Cloudbrain) string { | |||
| url := setting.AppURL | |||
| switch cloudbrain.JobType { | |||
| case string(models.JobTypeDebug): | |||
| if cloudbrain.ComputeResource == "CPU/GPU" { | |||
| url += "/cloudbrain/" + fmt.Sprint(cloudbrain.ID) | |||
| } else { | |||
| url += "/modelarts/notebook/" + fmt.Sprint(cloudbrain.ID) | |||
| } | |||
| case string(models.JobTypeBenchmark): | |||
| url += "/cloudbrain/benchmark/" + fmt.Sprint(cloudbrain.ID) | |||
| case string(models.JobTypeTrain): | |||
| if cloudbrain.Type == models.TypeCloudBrainOne { | |||
| url += "/cloudbrain/train-job/" + fmt.Sprint(cloudbrain.JobID) | |||
| } else if cloudbrain.Type == models.TypeCloudBrainTwo { | |||
| url += "/modelarts/train-job" + fmt.Sprint(cloudbrain.JobID) | |||
| } else if cloudbrain.Type == models.TypeC2Net { | |||
| url += "/grampus/train-job/" + fmt.Sprint(cloudbrain.JobID) | |||
| } | |||
| case string(models.JobTypeInference): | |||
| url += "/modelarts/inference-job/" + fmt.Sprint(cloudbrain.JobID) | |||
| } | |||
| return url | |||
| } | |||
| func getCloudbrainTemplateTitle(operateType JobOperateType) string { | |||
| var title string | |||
| switch operateType { | |||
| case JobOperateTypeStart: | |||
| title = "您好,您提交的算力资源申请已通过,任务已启动,请您关注运行情况。" | |||
| case JobOperateTypeFailed: | |||
| title = "您好,您提交的任务启动失败,您可以通过日志查看失败原因。" | |||
| case JobOperateTypeStop: | |||
| title = "您好,您提交的任务已运行结束。" | |||
| } | |||
| return title | |||
| } | |||
| func getJobTypeDisplayName(jobType string) string { | |||
| switch jobType { | |||
| case string(models.JobTypeDebug): | |||
| return "调试任务" | |||
| case string(models.JobTypeBenchmark): | |||
| return "评测任务" | |||
| case string(models.JobTypeTrain): | |||
| return "训练任务" | |||
| case string(models.JobTypeInference): | |||
| return "推理任务" | |||
| } | |||
| return "" | |||
| } | |||
| @@ -582,6 +582,9 @@ var ( | |||
| TreePathOfAutoMsgReply string | |||
| TreePathOfSubscribe string | |||
| //wechat template msg config | |||
| CloudbrainTemplateId string | |||
| //nginx proxy | |||
| PROXYURL string | |||
| RadarMap = struct { | |||
| @@ -1440,6 +1443,7 @@ func NewContext() { | |||
| RefNameOfWechatReply = sec.Key("AUTO_REPLY_REF_NAME").MustString("master") | |||
| TreePathOfAutoMsgReply = sec.Key("AUTO_REPLY_TREE_PATH").MustString("wechat/auto_reply.json") | |||
| TreePathOfSubscribe = sec.Key("SUBSCRIBE_TREE_PATH").MustString("wechat/subscribe_reply.json") | |||
| CloudbrainTemplateId = sec.Key("CLOUDBRAIN_TEMPLATE_ID").MustString("4qtOaImiFnzIrzLxxP99lYc12EYxvtAE1fNqd7fcihw") | |||
| SetRadarMapConfig() | |||
| @@ -373,7 +373,6 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
| } | |||
| } | |||
| func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBrainInferencForm) { | |||
| ctx.Data["PageIsCloudBrain"] = true | |||
| displayJobName := form.DisplayJobName | |||
| @@ -494,6 +493,7 @@ func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBra | |||
| ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/inference-job") | |||
| } | |||
| /** | |||
| 检查用户传输的参数是否符合专属资源池 | |||
| */ | |||