From 397ead9ea53f79d12d8ad0a48f34c3461c52f952 Mon Sep 17 00:00:00 2001 From: herenshan112 Date: Thu, 20 Jan 2022 13:09:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=81=E4=B8=9A=E5=BE=AE=E4=BF=A1=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E6=8E=A8=E9=80=81=E7=9B=B8=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 7 + .../api/admin/systemuser/systemmenu.go | 291 ++++++++++++- .../api/admin/systemuser/usertype.go | 28 ++ .../api/index/assessment/assesshandle.go | 26 +- .../api/index/assessment/assesstype.go | 1 + gin_server_admin/api/json_callback/README.md | 12 + .../api/json_callback/httpserver.go | 127 ++++++ gin_server_admin/api/json_callback/sample.go | 140 +++++++ .../wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go | 313 ++++++++++++++ .../api/v1/custom/customhandle.go | 126 +++--- .../api/v1/examtestpage/healthreportstat.go | 60 ++- .../v1/examtestpage/healthreportstathand.go | 6 +- gin_server_admin/api/v1/shiyan/shiyan.go | 391 ++++++++++++++++++ .../v1/wechatcallback/wechatcallbackhanlde.go | 30 ++ .../v1/wechatcallback/wechatcallbackmain.go | 250 ++++++++++- .../v1/wechatcallback/workwechattypejson.go | 14 + .../v1/wechatcallback/workwechattypexml.go | 77 ++++ .../callback/callbacktype.go | 4 + .../api/workwechatcallback/callback/enter.go | 6 + .../workwechatcallback/callback/workwechat.go | 8 + .../api/workwechatcallback/enter.go | 10 + gin_server_admin/api/xml_callback/.gitignore | 17 + gin_server_admin/api/xml_callback/README.md | 36 ++ gin_server_admin/api/xml_callback/go.mod | 5 + gin_server_admin/api/xml_callback/sample.go | 132 ++++++ .../wxbizmsgcrypt/wxbizmsgcrypt.go | 315 ++++++++++++++ gin_server_admin/commonus/messagepush.go | 91 ++++ gin_server_admin/commonus/messagepushtype.go | 328 ++++++++++++++- gin_server_admin/commonus/publicstruct.go | 11 + .../commonus/updatemessagetype.go | 52 +++ gin_server_admin/commonus/wechatapp.go | 11 +- gin_server_admin/config.yaml | 24 ++ gin_server_admin/config/config.go | 1 + gin_server_admin/config/wechat.go | 10 +- gin_server_admin/global/global.go | 1 + gin_server_admin/initialize/gorm.go | 2 + gin_server_admin/main.go | 4 + .../model/wechatcallback/callbacklog.go | 18 + gin_server_admin/router/shiyan/sys_shiyan.go | 5 + gin_server_admin/router/system/sys_admin.go | 11 +- .../wechatcallbackhandle.go | 6 + gin_server_admin/其他支持文件/config.yaml | 20 +- 42 files changed, 2912 insertions(+), 115 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 gin_server_admin/api/json_callback/README.md create mode 100644 gin_server_admin/api/json_callback/httpserver.go create mode 100644 gin_server_admin/api/json_callback/sample.go create mode 100644 gin_server_admin/api/json_callback/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go create mode 100644 gin_server_admin/api/v1/wechatcallback/wechatcallbackhanlde.go create mode 100644 gin_server_admin/api/v1/wechatcallback/workwechattypejson.go create mode 100644 gin_server_admin/api/v1/wechatcallback/workwechattypexml.go create mode 100644 gin_server_admin/api/workwechatcallback/callback/callbacktype.go create mode 100644 gin_server_admin/api/workwechatcallback/callback/enter.go create mode 100644 gin_server_admin/api/workwechatcallback/callback/workwechat.go create mode 100644 gin_server_admin/api/workwechatcallback/enter.go create mode 100644 gin_server_admin/api/xml_callback/.gitignore create mode 100644 gin_server_admin/api/xml_callback/README.md create mode 100644 gin_server_admin/api/xml_callback/go.mod create mode 100644 gin_server_admin/api/xml_callback/sample.go create mode 100644 gin_server_admin/api/xml_callback/wxbizmsgcrypt/wxbizmsgcrypt.go create mode 100644 gin_server_admin/commonus/updatemessagetype.go create mode 100644 gin_server_admin/model/wechatcallback/callbacklog.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f980ab9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/gin_server_admin/api/admin/systemuser/systemmenu.go b/gin_server_admin/api/admin/systemuser/systemmenu.go index f5f4f8f..a2102f8 100644 --- a/gin_server_admin/api/admin/systemuser/systemmenu.go +++ b/gin_server_admin/api/admin/systemuser/systemmenu.go @@ -1,6 +1,10 @@ package systemuser import ( + "strconv" + "time" + + "github.com/flipped-aurora/gin-vue-admin/server/commonus" "github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/model/common/response" "github.com/flipped-aurora/gin-vue-admin/server/model/systemuser" @@ -11,13 +15,15 @@ import ( func (s *SysTemMenuApi) SystemMenuList(c *gin.Context) { // response.Result(101, global.Gva_Authority_Authentication, "数据获取失败!", c) var systemMenuList []systemuser.SystemMenu - menuOperErr := global.GVA_DB_Master.Where("`m_steat` IN ?", []int{1, 2}).Order("m_id desc").Find(&systemMenuList).Error + menuOperErr := global.GVA_DB_Master.Where("`m_steat` IN ?", []int{1, 2}).Order("m_sort asc").Order("m_id desc").Find(&systemMenuList).Error if menuOperErr != nil { response.Result(101, menuOperErr, "数据获取失败!", c) return } systemListMenu := GetMenuThree(1, 0, systemMenuList) - response.Result(0, systemListMenu, "数据获取成功!", c) + sendData := commonus.MapOut() + sendData["list"] = systemListMenu + response.Result(0, sendData, "数据获取成功!", c) } //递归无限树 @@ -74,5 +80,284 @@ func (s *SysTemMenuApi) GetMenu(c *gin.Context) { return } systemListMenu := GetMenuThree(2, 0, systemMenuList) - response.Result(0, systemListMenu, "数据获取成功!", c) + sendData := commonus.MapOut() + sendData["list"] = systemListMenu + response.Result(0, sendData, "数据获取成功!", c) +} + +//菜单列表(不带操作) +func (s *SysTemMenuApi) GetMenuList(c *gin.Context) { + var systemMenuList []systemuser.SystemMenu + menuOperErr := global.GVA_DB_Master.Where("`m_steat` IN ? AND `m_parent` = 0", []int{1, 2}).Order("m_sort asc").Order("m_id desc").Find(&systemMenuList).Error + if menuOperErr != nil { + response.Result(101, menuOperErr, "数据获取失败!", c) + return + } + sendData := commonus.MapOut() + sendData["list"] = systemMenuList + // systemListMenu := GetMenuThree(1, 0, systemMenuList) + response.Result(0, sendData, "数据获取成功!", c) +} + +//添加菜单 +func (s *SysTemMenuApi) AddMenu(c *gin.Context) { + isTrue, userCont := commonus.AdminClientIdentity() + if isTrue != true { + response.Result(101, isTrue, "您的身份令牌已经失效!请重新登录获取身份令牌!", c) + return + } + var requestData addMenuData + err := c.ShouldBindJSON(&requestData) + if err != nil { + response.Result(102, err, "参数错误!请重新提交!", c) + return + } + if requestData.Title == "" { + response.Result(103, err, "请输入栏目名称!", c) + return + } + if requestData.MenuUrl == "" { + response.Result(104, err, "请输入栏目URL!", c) + return + } + if requestData.Sort == 0 { + requestData.Sort = 50 + } + // _, attriButeErr := strconv.ParseInt(userCont.AttriBute, 10, 64) //转换管理员属性部门 + // if attriButeErr != nil { + // response.Result(105, isTrue, "您的身份令牌已经失效!请重新登录获取身份令牌!", c) + // return + // } + var menuCont systemuser.SystemMenu + judgeErr := global.GVA_DB_Master.Model(&systemuser.SystemMenu{}).Select("m_id").Where("`m_title` = ?", requestData.Title).First(&menuCont).Error + if judgeErr == nil { + response.Result(106, isTrue, "该菜单名称已经存在!请不要重复添加!", c) + return + } + judgeUrlErr := global.GVA_DB_Master.Model(&systemuser.SystemMenu{}).Select("m_id").Where("`m_url` = ?", requestData.MenuUrl).First(&menuCont).Error + if judgeUrlErr == nil { + response.Result(107, isTrue, "该菜单URL已经存在!请不要重复添加!", c) + return + } + var saveMenu systemuser.SystemMenu + saveMenu.Title = requestData.Title + saveMenu.State = 1 + saveMenu.ParentId = int64(requestData.ParentId) + saveMenu.ApiUrl = requestData.MenuUrl + saveMenu.Time = time.Now().Unix() + saveMenu.EiteTime = time.Now().Unix() + userKey, userKeyErr := strconv.ParseInt(userCont.UserKey, 10, 64) + if userKeyErr == nil { + saveMenu.UserId = userKey + } + saveMenu.Sort = requestData.Sort + addassessInfoErr := global.GVA_DB_Master.Create(&saveMenu).Error + if addassessInfoErr != nil { + response.Result(108, saveMenu, "数据写入失败!", c) + } else { + response.Result(0, saveMenu, "数据写入成功!", c) + } +} + +//修改菜单 +func (s *SysTemMenuApi) EiteMenu(c *gin.Context) { + isTrue, _ := commonus.AdminClientIdentity() + if isTrue != true { + response.Result(101, isTrue, "您的身份令牌已经失效!请重新登录获取身份令牌!", c) + return + } + var requestData eiteMenuData + err := c.ShouldBindJSON(&requestData) + if err != nil { + response.Result(102, err, "参数错误!请重新提交!", c) + return + } + if requestData.Id == 0 { + response.Result(103, err, "请选择您要修改的菜单!", c) + return + } + if requestData.Title == "" { + response.Result(104, err, "请输入栏目名称!", c) + return + } + if requestData.MenuUrl == "" { + response.Result(105, err, "请输入栏目URL!", c) + return + } + if requestData.Sort == 0 { + requestData.Sort = 50 + } + + var menuCont systemuser.SystemMenu + judgeOldErr := global.GVA_DB_Master.Model(&systemuser.SystemMenu{}).Select("m_id", "m_title", "m_url").Where("`m_id` = ?", requestData.Id).First(&menuCont).Error + if judgeOldErr != nil { + response.Result(106, isTrue, "该菜单不存在!请确认后再修改", c) + return + } + + judgeErr := global.GVA_DB_Master.Model(&systemuser.SystemMenu{}).Select("m_id").Where("`m_title` = ? AND `m_title` <> ?", requestData.Title, menuCont.Title).First(&menuCont).Error + if judgeErr == nil { + response.Result(107, isTrue, "该菜单名称已经存在!请不要重复添加!", c) + return + } + var menuConts systemuser.SystemMenu + judgeUrlErr := global.GVA_DB_Master.Model(&systemuser.SystemMenu{}).Select("m_id").Where("`m_url` = ? AND `m_url` <> ?", requestData.MenuUrl, menuCont.ApiUrl).First(&menuConts).Error + if judgeUrlErr == nil { + response.Result(108, isTrue, "该菜单URL已经存在!请不要重复添加!", c) + return + } + saveData := commonus.MapOut() + saveData["m_title"] = requestData.Title + saveData["m_parent"] = int64(requestData.ParentId) + saveData["m_url"] = requestData.MenuUrl + saveData["m_sort"] = requestData.Sort + saveData["m_eite_time"] = time.Now().Unix() + isErr := eiteAssEssInfoCont(int64(requestData.Id), saveData) + if isErr != nil { + response.Result(109, isErr, "修改失败!", c) + return + } + response.Result(0, saveData, "修改成功!", c) +} + +//菜单编辑操作 +func eiteAssEssInfoCont(saveId int64, saveData map[string]interface{}) (menuErr error) { + var menuCont systemuser.SystemMenu + menuErr = global.GVA_DB_Master.Model(&menuCont).Where("m_id = ?", saveId).Updates(saveData).Error + return +} + +//菜单删除 +func (s *SysTemMenuApi) DelMenu(c *gin.Context) { + isTrue, _ := commonus.AdminClientIdentity() + if isTrue != true { + response.Result(101, isTrue, "您的身份令牌已经失效!请重新登录获取身份令牌!", c) + return + } + var requestData commonus.DelParameter + err := c.ShouldBindJSON(&requestData) + if err != nil { + response.Result(101, err, "参数错误!请重新提交!", c) + return + } + if requestData.Id == 0 && requestData.OutId == "" { + response.Result(102, err, "参数错误!请重新提交!", c) + return + } + if requestData.OutId != "" { + idInt, inIntErr := strconv.ParseInt(requestData.OutId, 10, 64) + if inIntErr == nil { + requestData.Id = idInt + } + } + if requestData.State == 0 { + requestData.State = 1 + } + saveData := commonus.MapOut() + saveData["m_steat"] = requestData.State + saveData["m_eite_time"] = time.Now().Unix() + if requestData.IsDel != 1 { + isErr := eiteAssEssInfoCont(requestData.Id, saveData) + if isErr != nil { + response.Result(109, isErr, "修改失败!", c) + return + } + response.Result(0, saveData, "修改成功!", c) + } else { + if requestData.State == 3 { + var menuConts systemuser.SystemMenu + archiveInfoErr := global.GVA_DB_Master.Where("m_id = ? OR `m_parent` = ?", requestData.Id, requestData.Id).Delete(&menuConts).Error + if archiveInfoErr != nil { + response.Result(104, requestData, "删除失败!", c) + return + } + response.Result(0, requestData, "删除成功!", c) + } else { + isErr := eiteAssEssInfoCont(requestData.Id, saveData) + if isErr != nil { + response.Result(109, isErr, "修改失败!", c) + return + } + response.Result(0, saveData, "修改成功!", c) + } + } +} + +//添加菜单操作 +func (s *SysTemMenuApi) AddMenuOperation(c *gin.Context) { + isTrue, _ := commonus.AdminClientIdentity() + if isTrue != true { + response.Result(101, isTrue, "您的身份令牌已经失效!请重新登录获取身份令牌!", c) + return + } + var requestData addMenuOper + err := c.ShouldBindJSON(&requestData) + if err != nil { + response.Result(101, err, "参数错误!请重新提交!", c) + return + } + if requestData.MenuId == "" { + response.Result(102, err, "参数错误!请重新提交!", c) + return + } + var menuIdInt int64 + idInt, inIntErr := strconv.ParseInt(requestData.MenuId, 10, 64) + if inIntErr == nil { + menuIdInt = idInt + } else { + response.Result(102, err, "参数错误!请重新提交!", c) + return + } + if requestData.Title == "" { + response.Result(102, err, "参数错误!请重新提交!", c) + return + } + var menuCont systemuser.MenuOperation + judgeErr := global.GVA_DB_Master.Model(&systemuser.MenuOperation{}).Select("m_id").Where("`menu_id` = ?", menuIdInt).Where("`oper_title` = ?", requestData.Title).First(&menuCont).Error + if judgeErr == nil { + response.Result(106, isTrue, "该菜单的此操作名称已经存在!请不要重复添加!", c) + return + } + var saveMenuOper systemuser.MenuOperation + saveMenuOper.OperTitle = requestData.Title + saveMenuOper.MenuId = menuIdInt + addassessInfoErr := global.GVA_DB_Master.Create(&saveMenuOper).Error + if addassessInfoErr != nil { + response.Result(108, saveMenuOper, "数据写入失败!", c) + } else { + response.Result(0, saveMenuOper, "数据写入成功!", c) + } +} + +//删除菜单操作 +func (s *SysTemMenuApi) DelMenuOperation(c *gin.Context) { + isTrue, _ := commonus.AdminClientIdentity() + if isTrue != true { + response.Result(101, isTrue, "您的身份令牌已经失效!请重新登录获取身份令牌!", c) + return + } + var requestData commonus.DelParameter + err := c.ShouldBindJSON(&requestData) + if err != nil { + response.Result(101, err, "参数错误!请重新提交!", c) + return + } + if requestData.Id == 0 && requestData.OutId == "" { + response.Result(102, err, "参数错误!请重新提交!", c) + return + } + if requestData.OutId != "" { + idInt, inIntErr := strconv.ParseInt(requestData.OutId, 10, 64) + if inIntErr == nil { + requestData.Id = idInt + } + } + + var menuContOper systemuser.MenuOperation + archiveInfoErr := global.GVA_DB_Master.Where("oper_id = ?", requestData.Id).Delete(&menuContOper).Error + if archiveInfoErr != nil { + response.Result(104, requestData, "删除失败!", c) + return + } + response.Result(0, requestData, "删除成功!", c) } diff --git a/gin_server_admin/api/admin/systemuser/usertype.go b/gin_server_admin/api/admin/systemuser/usertype.go index 2c9851e..3ef0d7f 100644 --- a/gin_server_admin/api/admin/systemuser/usertype.go +++ b/gin_server_admin/api/admin/systemuser/usertype.go @@ -49,3 +49,31 @@ type SystemMenuThree struct { Child []SystemMenuThree `json:"child"` MenuOperation []systemuser.MenuOperation `json:"menuOperation"` } + +//查询菜单列表 +type getMenuData struct { + StateOperation int `json:"stateOperation"` +} + +//添加菜单参数 +type addMenuData struct { + Title string `json:"title"` + ParentId int `json:"parentId"` + MenuUrl string `json:"menuurl"` + Sort int `json:"sort"` +} + +//修改菜单参数 +type eiteMenuData struct { + Id int `json:"id"` + Title string `json:"title"` + ParentId int `json:"parentId"` + MenuUrl string `json:"menuurl"` + Sort int `json:"sort"` +} + +//添加菜单功能操作 +type addMenuOper struct { + MenuId string `json:"menuid"` + Title string `json:"title"` +} diff --git a/gin_server_admin/api/index/assessment/assesshandle.go b/gin_server_admin/api/index/assessment/assesshandle.go index eda7cc0..fc8de98 100644 --- a/gin_server_admin/api/index/assessment/assesshandle.go +++ b/gin_server_admin/api/index/assessment/assesshandle.go @@ -56,6 +56,7 @@ func (a *Assessment) MyAssEssMentList(c *gin.Context) { for _, val := range assessList { var assessOutCont assessOut assessOutCont.Id = val.Id + assessOutCont.OutId = strconv.FormatInt(val.Id, 10) assessOutCont.Title = val.Title assessOutCont.Time = val.Time @@ -162,18 +163,27 @@ func (a *Assessment) AddAssessmentScore(c *gin.Context) { return } - classId, dutyIdErr := strconv.ParseInt(requestData.ClassId, 10, 64) - if dutyIdErr != nil { - response.Result(102, err, "未知考核分类!", c) - return - } + // classId, dutyIdErr := strconv.ParseInt(requestData.ClassId, 10, 64) + // if dutyIdErr != nil { + // response.Result(102, err, "未知考核分类!", c) + // return + // } - assessId, dutyIdErr := strconv.ParseInt(requestData.AssessId, 10, 64) - if dutyIdErr != nil { - response.Result(102, err, "未知考核项目!", c) + // assessId, dutyIdErr := strconv.ParseInt(requestData.AssessId, 10, 64) + // if dutyIdErr != nil { + // response.Result(102, err, "未知考核项目!", c) + // return + // } + + assIsTrue, assInfo := assessment.GetDutyInfoContAll(dutyId) + if assIsTrue != true { + response.Result(104, dutyId, "请指定考核项目已禁止使用!", c) return } + classId := assInfo.ClassId + assessId := assInfo.AssessId + if requestData.FileTime == "" { response.Result(103, err, "未知考核年月!", c) return diff --git a/gin_server_admin/api/index/assessment/assesstype.go b/gin_server_admin/api/index/assessment/assesstype.go index 418d6d8..e1d111b 100644 --- a/gin_server_admin/api/index/assessment/assesstype.go +++ b/gin_server_admin/api/index/assessment/assesstype.go @@ -22,6 +22,7 @@ type assessOut struct { AssessId int64 `json:"assessId"` AssessTitle string `json:"assessTitle"` AssessDepart []assessDepart `json:"assessDepart"` + OutId string `json:"outId"` } //要考核的部门评分 diff --git a/gin_server_admin/api/json_callback/README.md b/gin_server_admin/api/json_callback/README.md new file mode 100644 index 0000000..d5dbdd4 --- /dev/null +++ b/gin_server_admin/api/json_callback/README.md @@ -0,0 +1,12 @@ +# weworkapi_cplusplus +official lib of wework api https://work.weixin.qq.com/api/doc + +# 注意事项 + +* 1.回调sdk json版本 + +* 2.wxbizjsonmsgcrypt.go文件中声明并实现了WXBizJsonMsgCrypt类,提供用户接入企业微信的三个接口。sample.go文件提供了如何使用这三个接口的示例。 + +* 3.WXBizJsonMsgCrypt类封装了VerifyURL, DecryptMsg, EncryptMsg三个接口,分别用于开发者验证回调url,收到用户回复消息的解密以及开发者回复消息的加密过程。使用方法可以参考sample.go文件。 + +* 4.加解密协议请参考企业微信官方文档。 \ No newline at end of file diff --git a/gin_server_admin/api/json_callback/httpserver.go b/gin_server_admin/api/json_callback/httpserver.go new file mode 100644 index 0000000..9325f4a --- /dev/null +++ b/gin_server_admin/api/json_callback/httpserver.go @@ -0,0 +1,127 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + + "github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizjsonmsgcrypt" +) + +const token = "gY1AGR3mjBhzy" +const receiverId = "wwabfd0cec7171e769" +const encodingAeskey = "g8VGfQEqluUhoKOlyjmmll8Q9C5tVFUTX5T2qkmI9Sv" + +func getString(str, endstr string, start int, msg *string) int { + end := strings.Index(str, endstr) + *msg = str[start:end] + return end + len(endstr) +} + +func VerifyURL(w http.ResponseWriter, r *http.Request) { + //httpstr := `&{GET /?msg_signature=825075c093249d5a60967fe4a613cae93146636b×tamp=1597998748&nonce=1597483820&echostr=neLB8CftccHiz19tluVb%2BUBnUVMT3xpUMZU8qvDdD17eH8XfEsbPYC%2FkJyPsZOOc6GdsCeu8jSIa2noSJ%2Fez2w%3D%3D HTTP/1.1 1 1 map[Cache-Control:[no-cache] Accept:[*/*] Pragma:[no-cache] User-Agent:[Mozilla/4.0]] 0x86c180 0 [] false 100.108.211.112:8893 map[] map[] map[] 100.108.79.233:59663 /?msg_signature=825075c093249d5a60967fe4a613cae93146636b×tamp=1597998748&nonce=1597483820&echostr=neLB8CftccHiz19tluVb%2BUBnUVMT3xpUMZU8qvDdD17eH8XfEsbPYC%2FkJyPsZOOc6GdsCeu8jSIa2noSJ%2Fez2w%3D%3D }` + fmt.Println(r, r.Body) + httpstr := r.URL.RawQuery + start := strings.Index(httpstr, "msg_signature=") + start += len("msg_signature=") + + var msg_signature string + next := getString(httpstr, "×tamp=", start, &msg_signature) + + var timestamp string + next = getString(httpstr, "&nonce=", next, ×tamp) + + var nonce string + next = getString(httpstr, "&echostr=", next, &nonce) + + echostr := httpstr[next:len(httpstr)] + + echostr, _ = url.QueryUnescape(echostr) + fmt.Println(msg_signature, timestamp, nonce, echostr, next) + + wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizjsonmsgcrypt.JsonType) + echoStr, cryptErr := wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr) + if nil != cryptErr { + fmt.Println("verifyUrl fail", cryptErr) + } + fmt.Println("verifyUrl success echoStr", string(echoStr)) + fmt.Fprintf(w, string(echoStr)) + +} + +type MsgContent struct { + ToUsername string `json:"ToUserName"` + FromUsername string `json:"FromUserName"` + CreateTime uint32 `json:"CreateTime"` + MsgType string `json:"MsgType"` + Content string `json:"Content"` + Msgid uint64 `json:"MsgId"` + Agentid uint32 `json:"AgentId"` +} + +func MsgHandler(w http.ResponseWriter, r *http.Request) { + httpstr := r.URL.RawQuery + start := strings.Index(httpstr, "msg_signature=") + start += len("msg_signature=") + + var msg_signature string + next := getString(httpstr, "×tamp=", start, &msg_signature) + + var timestamp string + next = getString(httpstr, "&nonce=", next, ×tamp) + + nonce := httpstr[next:len(httpstr)] + fmt.Println(msg_signature, timestamp, nonce) + + body, err := ioutil.ReadAll(r.Body) + fmt.Println(string(body), err) + wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizjsonmsgcrypt.JsonType) + + msg, err_ := wxcpt.DecryptMsg(msg_signature, timestamp, nonce, body) + fmt.Println(string(msg), err_) + var msgContent MsgContent + err = json.Unmarshal(msg, &msgContent) + if nil != err { + fmt.Println("Unmarshal fail", err) + } else { + fmt.Println("struct", msgContent) + } + + fmt.Println(msgContent, err) + ToUsername := msgContent.ToUsername + msgContent.ToUsername = msgContent.FromUsername + msgContent.FromUsername = ToUsername + fmt.Println("replaymsg", msgContent) + replayJson, err := json.Marshal(&msgContent) + + encryptMsg, cryptErr := wxcpt.EncryptMsg(string(replayJson), "1409659589", "1409659589") + if nil != cryptErr { + fmt.Println("DecryptMsg fail", cryptErr) + } + + sEncryptMsg := string(encryptMsg) + + fmt.Println("after encrypt sEncryptMsg: ", sEncryptMsg) + fmt.Fprintf(w, sEncryptMsg) +} + +func CallbackHandler(w http.ResponseWriter, r *http.Request) { + httpstr := r.URL.RawQuery + echo := strings.Index(httpstr, "echostr") + if echo != -1 { + VerifyURL(w, r) + } else { + MsgHandler(w, r) + } + + fmt.Println("finished CallbackHandler", httpstr) +} + +func main() { + http.HandleFunc("/", CallbackHandler) // 设置访问路由 + log.Fatal(http.ListenAndServe(":8893", nil)) +} diff --git a/gin_server_admin/api/json_callback/sample.go b/gin_server_admin/api/json_callback/sample.go new file mode 100644 index 0000000..7258c41 --- /dev/null +++ b/gin_server_admin/api/json_callback/sample.go @@ -0,0 +1,140 @@ +package main + +import ( + "encoding/json" + "fmt" + + "github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizjsonmsgcrypt" +) + +// type MsgContent struct { +// ToUsername string `json:"ToUserName"` +// FromUsername string `json:"FromUserName"` +// CreateTime uint32 `json:"CreateTime"` +// MsgType string `json:"MsgType"` +// Content string `json:"Content"` +// Msgid uint64 `json:"MsgId"` +// Agentid uint32 `json:"AgentId"` +// } + +func mains() { + token := "QDG6eK" + receiverId := "wx5823bf96d3bd56c7" + encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C" + wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizjsonmsgcrypt.JsonType) + /* + ------------使用示例一:验证回调URL--------------- + *企业开启回调模式时,企业微信会向验证url发送一个get请求 + 假设点击验证时,企业收到类似请求: + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + + 接收到该请求时,企业应 + 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), + 这一步注意作URL解码。 + 2.验证消息体签名的正确性 + 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 + 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 + + */ + // 解析出url上的参数值如下: + // verifyMsgSign := HttpUtils.ParseUrl("msg_signature") + verifyMsgSign := "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3" + // verifyTimestamp := HttpUtils.ParseUrl("timestamp") + verifyTimestamp := "1409659589" + // verifyNonce := HttpUtils.ParseUrl("nonce") + verifyNonce := "263014780" + // verifyEchoStr := HttpUtils.ParseUrl("echoStr") + verifyEchoStr := "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==" + echoStr, cryptErr := wxcpt.VerifyURL(verifyMsgSign, verifyTimestamp, verifyNonce, verifyEchoStr) + if nil != cryptErr { + fmt.Println("verifyUrl fail", cryptErr) + } + fmt.Println("verifyUrl success echoStr", string(echoStr)) + // 验证URL成功,将sEchoStr返回 + // HttpUtils.SetResponse(sEchoStr) + + /* + ------------使用示例二:对用户回复的消息解密--------------- + 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 + 假设企业收到企业微信的回调消息如下: + POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1 + Host: qy.weixin.qq.com + Content-Length: 613 + { + "tousername":"wx5823bf96d3bd56c7", + "encrypt":"CZWs4CWRpI4VolQlvn4dlPBlXke6+HgmuI7p0LueFp1fKH40TNL+YHWJZwqIiYV+3kTrhdNU7fZwc+PmtgBvxSczkFeRz+oaVSsomrrtP2Z91LE313djjbWujqInRT+7ChGbCeo7ZzszByf8xnDSunPBxRX1MfX3kAxpKq7dqduW1kpMAx8O8xUzZ9oC0TLuZchbpxaml4epzGfF21O+zyXDwTxbCEiO0E87mChtzuh/VPlznXYbfqVrnyLNZ5pr", + "agentid":"218" + } + + 企业收到post请求之后应该: + 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) + 2.验证消息体签名的正确性。 + 3.将post请求的数据进行json解析,并将"Encrypt"标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 + 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 + */ + + // reqMsgSign := HttpUtils.ParseUrl("msg_signature") + reqMsgSign := "0623cbc5a8cbee5bcc137c70de99575366fc2af3" + // reqTimestamp := HttpUtils.ParseUrl("timestamp") + reqTimestamp := "1409659813" + // reqNonce := HttpUtils.ParseUrl("nonce") + reqNonce := "1372623149" + // post请求的密文数据 + // reqData = HttpUtils.PostData() + + reqData := []byte(`{"tousername":"wx5823bf96d3bd56c7","encrypt":"CZWs4CWRpI4VolQlvn4dlEC1alN2MUEY2VklGehgBVLBrlVF7SyT+SV+Toj43l4ayJ9UMGKphktKKmP7B2j/P1ey67XB8PBgS7Wr5/8+w/yWriZv3Vmoo/MH3/1HsIWZrPQ3N2mJrelStIfI2Y8kLKXA7EhfZgZX4o+ffdkZDM76SEl79Ib9mw7TGjZ9Aw/x/A2VjNbV1E8BtEbRxYYcQippYNw7hr8sFfa3nW1xLdxokt8QkRX83vK3DFP2F6TQFPL2Tu98UwhcUpPvdJBuu1/yiOQIScppV3eOuLWEsko=","agentid":"218"}`) + + msg, cryptErr := wxcpt.DecryptMsg(reqMsgSign, reqTimestamp, reqNonce, reqData) + if nil != cryptErr { + fmt.Println("DecryptMsg fail", cryptErr) + } + fmt.Println("after decrypt msg: ", string(msg)) + // TODO: 解析出明文json标签的内容进行处理 + // For example: + + var msgContent MsgContent + err := json.Unmarshal(msg, &msgContent) + if nil != err { + fmt.Println("Unmarshal fail", err) + } else { + fmt.Println("struct", msgContent) + } + + /* + ------------使用示例三:企业回复用户消息的加密--------------- + 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的json串。 + 假设企业需要回复用户的明文如下: + + { + "ToUserName": "mycreate", + "FromUserName":"wx5823bf96d3bd56c7", + "CreateTime": 1348831860, + "MsgType": "text", + "Content": "this is a test", + "MsgId": 1234567890123456, + "AgentID": 128 + } + + 为了将此段明文回复给用户,企业应: + 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 + 2.将明文加密得到密文。 + 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 + 4.将密文,消息体签名,时间戳,随机数字串拼接成json格式的字符串,发送给企业。 + 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 + */ + respData := "{\"ToUserName\":\"wx5823bf96d3bd56c7\",\"FromUserName\":\"mycreate\",\"CreateTime\": 1409659813,\"MsgType\":\"text\",\"Content\":\"hello\",\"MsgId\":4561255354251345929,\"AgentID\": 218}" + //respData := `{"ToUserName":"wx5823bf96d3bd56c7","FromUserName":"mycreate","CreateTime": 1409659813,"MsgType":"text","Content":"hello","MsgId":4561255354251345929,"AgentID": 218}` + //respData := `{"FromUserName":"mycreate","CreateTime": 1409659813,"MsgType":"text","Content":"hello","MsgId":4561255354251345929,"AgentID": 218}` + encryptMsg, cryptErr := wxcpt.EncryptMsg(respData, reqTimestamp, reqNonce) + if nil != cryptErr { + fmt.Println("DecryptMsg fail", cryptErr) + } + + sEncryptMsg := string(encryptMsg) + + fmt.Println("after encrypt sEncryptMsg: ", sEncryptMsg) + // 加密成功 + // TODO: + // HttpUtils.SetResponse(sEncryptMsg) +} diff --git a/gin_server_admin/api/json_callback/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go b/gin_server_admin/api/json_callback/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go new file mode 100644 index 0000000..689cc84 --- /dev/null +++ b/gin_server_admin/api/json_callback/wxbizjsonmsgcrypt/wxbizjsonmsgcrypt.go @@ -0,0 +1,313 @@ +package wxbizjsonmsgcrypt + +import( + "crypto/sha1" + "crypto/aes" + "crypto/cipher" + "bytes" + "strings" + "fmt" + "sort" + "encoding/base64" + "math/rand" + "encoding/binary" + "encoding/json" +) + +const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +const ( + ValidateSignatureError int = -40001 + ParseJsonError int = -40002 + ComputeSignatureError int = -40003 + IllegalAesKey int = -40004 + ValidateCorpidError int = -40005 + EncryptAESError int = -40006 + DecryptAESError int = -40007 + IllegalBuffer int = -40008 + EncodeBase64Error int = -40009 + DecodeBase64Error int = -40010 + GenJsonError int = -40011 + IllegalProtocolType int = -40012 +) + +type ProtocolType int +const ( + JsonType ProtocolType = 1 +) + +type CryptError struct{ + ErrCode int + ErrMsg string +} + +func NewCryptError(err_code int, err_msg string) * CryptError{ + return &CryptError{ErrCode:err_code, ErrMsg: err_msg} +} + +type WXBizJsonMsg4Recv struct { + Tousername string `json:"tousername"` + Encrypt string `json:"encrypt"` + Agentid string `json:"agentid"` +} + + +type WXBizJsonMsg4Send struct { + Encrypt string `json:"encrypt"` + Signature string `json:"msgsignature"` + Timestamp string `json:"timestamp"` + Nonce string `json:"nonce"` +} + +func NewWXBizJsonMsg4Send(encrypt, signature, timestamp, nonce string) * WXBizJsonMsg4Send { + return &WXBizJsonMsg4Send{Encrypt:encrypt, Signature:signature, Timestamp:timestamp, Nonce:nonce} +} + +type ProtocolProcessor interface { + parse(src_data []byte) (* WXBizJsonMsg4Recv, * CryptError) + serialize(msg_send * WXBizJsonMsg4Send) ([]byte, * CryptError) +} + +type WXBizMsgCrypt struct{ + token string + encoding_aeskey string + receiver_id string + protocol_processor ProtocolProcessor +} + +type JsonProcessor struct{ +} + +func (self * JsonProcessor) parse(src_data []byte) (* WXBizJsonMsg4Recv, * CryptError) { + var msg4_recv WXBizJsonMsg4Recv + err := json.Unmarshal(src_data, &msg4_recv) + if nil != err { + fmt.Println("Unmarshal fail", err) + return nil, NewCryptError(ParseJsonError, "json to msg fail") + } + return &msg4_recv, nil +} + +func (self * JsonProcessor) serialize(msg4_send * WXBizJsonMsg4Send) ([]byte, * CryptError){ + json_msg, err := json.Marshal(msg4_send) + if nil != err { + return nil, NewCryptError(GenJsonError, err.Error()) + } + + return json_msg, nil +} + +func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) * WXBizMsgCrypt{ + var protocol_processor ProtocolProcessor + if protocol_type != JsonType { + panic("unsupport protocal") + } else { + protocol_processor = new(JsonProcessor) + } + + return &WXBizMsgCrypt{token:token, encoding_aeskey:(encoding_aeskey+"="), receiver_id:receiver_id, protocol_processor:protocol_processor} +} + +func (self * WXBizMsgCrypt) randString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))] + } + return string(b) +} + +func (self * WXBizMsgCrypt) pKCS7Padding(plaintext string, block_size int) []byte { + padding := block_size- (len(plaintext) % block_size) + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + var buffer bytes.Buffer + buffer.WriteString(plaintext) + buffer.Write(padtext) + return buffer.Bytes() +} + +func (self * WXBizMsgCrypt) pKCS7Unpadding(plaintext []byte, block_size int) ([]byte, * CryptError) { + plaintext_len := len(plaintext) + if nil == plaintext || plaintext_len == 0 { + return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding error nil or zero") + } + if plaintext_len % block_size != 0 { + return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding text not a multiple of the block size") + } + padding_len := int(plaintext[plaintext_len - 1]) + return plaintext[:plaintext_len - padding_len], nil +} + +func (self * WXBizMsgCrypt) cbcEncrypter(plaintext string) ([]byte, * CryptError) { + aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + const block_size = 32 + pad_msg := self.pKCS7Padding(plaintext, block_size) + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, NewCryptError(EncryptAESError, err.Error()) + } + + ciphertext := make([]byte, len(pad_msg)) + iv := aeskey[:aes.BlockSize] + + mode := cipher.NewCBCEncrypter(block, iv) + + mode.CryptBlocks(ciphertext, pad_msg) + base64_msg := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext))) + base64.StdEncoding.Encode(base64_msg, ciphertext) + + return base64_msg, nil +} + +func (self * WXBizMsgCrypt) cbcDecrypter(base64_encrypt_msg string) ([]byte, * CryptError){ + aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + + encrypt_msg, err := base64.StdEncoding.DecodeString(base64_encrypt_msg) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, NewCryptError(DecryptAESError, err.Error()) + } + + if len(encrypt_msg) < aes.BlockSize { + return nil, NewCryptError(DecryptAESError, "encrypt_msg size is not valid") + } + + iv := aeskey[:aes.BlockSize] + + if len(encrypt_msg) % aes.BlockSize != 0 { + return nil, NewCryptError(DecryptAESError, "encrypt_msg not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + + mode.CryptBlocks(encrypt_msg, encrypt_msg) + + return encrypt_msg, nil +} + +func (self * WXBizMsgCrypt) calSignature(timestamp, nonce, data string) string{ + sort_arr := []string{self.token, timestamp, nonce, data} + sort.Strings(sort_arr); + var buffer bytes.Buffer + for _, value := range sort_arr { + buffer.WriteString(value) + } + + sha := sha1.New() + sha.Write(buffer.Bytes()) + signature := fmt.Sprintf("%x", sha.Sum(nil)) + return string(signature) +} + +func (self * WXBizMsgCrypt) ParsePlainText(plaintext[]byte)([]byte, uint32, []byte, []byte, * CryptError){ + const block_size = 32 + plaintext, err := self.pKCS7Unpadding(plaintext, block_size) + if nil != err { + return nil, 0, nil, nil, err + } + + text_len := uint32(len(plaintext)) + if text_len < 20 { + return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 1") + } + random := plaintext[:16] + msg_len := binary.BigEndian.Uint32(plaintext[16:20]) + if text_len < (20 + msg_len) { + return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 2") + } + + msg := plaintext[20: 20 + msg_len] + receiver_id := plaintext[20+msg_len:] + + return random, msg_len, msg, receiver_id, nil +} + +func (self * WXBizMsgCrypt) VerifyURL(msg_signature, timestamp, nonce, echostr string) ([]byte, * CryptError){ + signature := self.calSignature(timestamp, nonce, echostr) + + if strings.Compare(signature, msg_signature) != 0{ + return nil, NewCryptError(ValidateSignatureError, "signature not equal") + } + + plaintext, err := self.cbcDecrypter(echostr) + if nil != err { + return nil, err + } + + _, _, msg, receiver_id, err := self.ParsePlainText(plaintext) + if nil != err { + return nil, err + } + + if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { + fmt.Println(string(receiver_id), self.receiver_id, len(receiver_id), len(self.receiver_id)) + return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") + } + + return msg, nil +} + + +func (self * WXBizMsgCrypt) EncryptMsg(reply_msg, timestamp, nonce string) ([]byte, * CryptError){ + rand_str := self.randString(16) + var buffer bytes.Buffer + buffer.WriteString(rand_str) + + msg_len_buf := make([]byte, 4) + binary.BigEndian.PutUint32(msg_len_buf, uint32(len(reply_msg))) + buffer.Write(msg_len_buf) + buffer.WriteString(reply_msg); + buffer.WriteString(self.receiver_id); + + tmp_ciphertext, err := self.cbcEncrypter(buffer.String()); + if nil != err { + return nil, err + } + ciphertext := string(tmp_ciphertext) + + signature := self.calSignature(timestamp, nonce, ciphertext) + + msg4_send := NewWXBizJsonMsg4Send(ciphertext, signature, timestamp, nonce) + return self.protocol_processor.serialize(msg4_send) +} + +func (self * WXBizMsgCrypt) DecryptMsg(msg_signature, timestamp, nonce string, post_data []byte) ([]byte, * CryptError){ + msg4_recv, crypt_err := self.protocol_processor.parse(post_data) + if nil != crypt_err { + return nil, crypt_err + } + + signature := self.calSignature(timestamp, nonce, msg4_recv.Encrypt) + + if strings.Compare(signature, msg_signature) != 0{ + return nil, NewCryptError(ValidateSignatureError, "signature not equal") + } + + + plaintext, crypt_err := self.cbcDecrypter(msg4_recv.Encrypt) + if nil != crypt_err { + return nil, crypt_err + } + + _, _, msg, receiver_id, crypt_err := self.ParsePlainText(plaintext) + if nil != crypt_err { + return nil, crypt_err + } + + if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { + return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") + } + + return msg, nil +} + diff --git a/gin_server_admin/api/v1/custom/customhandle.go b/gin_server_admin/api/v1/custom/customhandle.go index 46629fb..aa4d409 100644 --- a/gin_server_admin/api/v1/custom/customhandle.go +++ b/gin_server_admin/api/v1/custom/customhandle.go @@ -26,73 +26,73 @@ func (cu *CustomHandle) CustomLogin(c *gin.Context) { } userAgent := c.Request.Header.Get("User-Agent") // store - // if store.Verify(l.CaptchaId, l.Captcha, true) { - userErr, user := staff.GetUserWork(l.Username, l.Password) - if userErr != true { - // // global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Any("err", err)) - response.Result(101, userErr, "登陆失败! 用户名不存在或者密码错误!!", c) - return - } else { - if user.State == 2 { - response.Result(102, userErr, "登陆失败! 该账号已经被禁用!", c) - return - } - if user.State == 3 { - response.Result(102, userErr, "登陆失败! 该账号不存在!", c) + if store.Verify(l.CaptchaId, l.Captcha, true) { + userErr, user := staff.GetUserWork(l.Username, l.Password) + if userErr != true { + // // global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Any("err", err)) + response.Result(101, userErr, "登陆失败! 用户名不存在或者密码错误!!", c) return - } - if user.HireSet != 1 { - response.Result(102, userErr, "登陆失败! 该员工已经离职!", c) - return - } - var md5JiaMi commonus.Md5Encryption - md5JiaMi.Md5EncryptionInit(userAgent) - md5Token := md5JiaMi.Md5EncryptionAlgorithm() - - sha1Str := user.KeyStr + user.Number + user.Password + md5Token - sha1Token := commonus.Sha1Encryption(sha1Str) - - saveData := commonus.MapOut() - saveData["key"] = user.KeyStr - saveData["token"] = sha1Token - saveData["userinfo"] = user + } else { + if user.State == 2 { + response.Result(102, userErr, "登陆失败! 该账号已经被禁用!", c) + return + } + if user.State == 3 { + response.Result(102, userErr, "登陆失败! 该账号不存在!", c) + return + } + if user.HireSet != 1 { + response.Result(102, userErr, "登陆失败! 该员工已经离职!", c) + return + } + var md5JiaMi commonus.Md5Encryption + md5JiaMi.Md5EncryptionInit(userAgent) + md5Token := md5JiaMi.Md5EncryptionAlgorithm() + + sha1Str := user.KeyStr + user.Number + user.Password + md5Token + sha1Token := commonus.Sha1Encryption(sha1Str) + + saveData := commonus.MapOut() + saveData["key"] = user.KeyStr + saveData["token"] = sha1Token + saveData["userinfo"] = user + + redisClient := redishandel.RunRedis() + redisClient.SetRedisTime(10800) + writeRedisData := map[string]interface{}{ + "userkey": user.KeyStr, + "usernumber": user.Number, + "userpwd": user.Password, + "usertoken": sha1Token, + } - redisClient := redishandel.RunRedis() - redisClient.SetRedisTime(10800) - writeRedisData := map[string]interface{}{ - "userkey": user.KeyStr, - "usernumber": user.Number, - "userpwd": user.Password, - "usertoken": sha1Token, + // global.GVA_INDEX_USERKEY = user.Key + + redisClient.HashMsetAdd("system:Identification_"+global.GVA_CONFIG.RedisPrefix.Alias+"_"+user.KeyStr, writeRedisData) + + userInfo := commonus.MapOut() + userInfo["id"] = user.Id + userInfo["number"] = user.Number + userInfo["departmentid"] = user.DepartmentId + userInfo["workshopid"] = user.WorkshopId + userInfo["postid"] = user.PostId + userInfo["key"] = user.Key + userInfo["group"] = user.Group + userInfo["tema"] = user.Tema + userInfo["workwechatid"] = user.WorkWechatId + userInfo["wechatid"] = user.WechatId + userInfo["name"] = user.Name + userInfo["nickname"] = user.NickName + + redisUserClient := redishandel.RunRedis() + redisUserClient.SetRedisTime(0) + redisUserClient.HashMsetAdd("system:userContent_"+global.GVA_CONFIG.RedisPrefix.Alias+"_"+user.KeyStr, userInfo) + // // b.tokenNext(c, *user) + response.Result(0, saveData, "登录成功!", c) } - - // global.GVA_INDEX_USERKEY = user.Key - - redisClient.HashMsetAdd("system:Identification_"+global.GVA_CONFIG.RedisPrefix.Alias+"_"+user.KeyStr, writeRedisData) - - userInfo := commonus.MapOut() - userInfo["id"] = user.Id - userInfo["number"] = user.Number - userInfo["departmentid"] = user.DepartmentId - userInfo["workshopid"] = user.WorkshopId - userInfo["postid"] = user.PostId - userInfo["key"] = user.Key - userInfo["group"] = user.Group - userInfo["tema"] = user.Tema - userInfo["workwechatid"] = user.WorkWechatId - userInfo["wechatid"] = user.WechatId - userInfo["name"] = user.Name - userInfo["nickname"] = user.NickName - - redisUserClient := redishandel.RunRedis() - redisUserClient.SetRedisTime(0) - redisUserClient.HashMsetAdd("system:userContent_"+global.GVA_CONFIG.RedisPrefix.Alias+"_"+user.KeyStr, userInfo) - // // b.tokenNext(c, *user) - response.Result(0, saveData, "登录成功!", c) + } else { + response.FailWithMessage("验证码错误", c) } - // } else { - // response.FailWithMessage("验证码错误", c) - // } } //自定义登录 diff --git a/gin_server_admin/api/v1/examtestpage/healthreportstat.go b/gin_server_admin/api/v1/examtestpage/healthreportstat.go index 7de2461..13cc25d 100644 --- a/gin_server_admin/api/v1/examtestpage/healthreportstat.go +++ b/gin_server_admin/api/v1/examtestpage/healthreportstat.go @@ -12,6 +12,7 @@ import ( "github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/model/common/response" "github.com/flipped-aurora/gin-vue-admin/server/model/locationing" + "github.com/flipped-aurora/gin-vue-admin/server/utils/redishandel" "github.com/gin-gonic/gin" ) @@ -333,19 +334,27 @@ func sendMessAgeAlarm(calCulTime int64, alarmUser []map[string]interface{}) (sen sendImgMsg.DuplicateCheckInterval = 1800 callbakcMsg, isTrueCall, callBackCont := sendImgMsg.SendImgMessage() fmt.Printf("发送信息返回:%v-----------%v----------->%v\n", string(callbakcMsg), isTrueCall, callBackCont) - sendBakc["callbakcMsg"] = string(callbakcMsg) - sendBakc["isTrueCall"] = isTrueCall - sendBakc["callBackCont"] = callBackCont + sendBakcs := commonus.MapOut() + sendBakcs["callbakcMsg"] = string(callbakcMsg) + sendBakcs["isTrueCall"] = isTrueCall + sendBakcs["callBackCont"] = callBackCont + // sendBakcs["errmsg"] = "没有要通知的人" + sendBakc = sendBakcs return } else { fmt.Println("没有要通知的人") - sendBakc["errmsg"] = "没有要通知的人" + sendBakcs := commonus.MapOut() + sendBakcs["errmsg"] = "没有要通知的人" + sendBakc = sendBakcs return } } else { fmt.Println("没有要通知的人") - sendBakc["errmsg"] = "没有要通知的人" + // sendBakc["errmsg"] = "没有要通知的人" + sendBakcs := commonus.MapOut() + sendBakcs["errmsg"] = "没有要通知的人" + sendBakc = sendBakcs return } return @@ -363,6 +372,9 @@ func (d *dataLockStatistics) getUserAnswerList(jobid, tadayTime, calCulTime stri // var judgeWriteInfoUser locationing.ReportAddress // getReportAddressErr := global.GVA_DB_HealthReport.Where("calcultime = ?", timeStamp).Take(&judgeWriteInfoUser).Error // if getReportAddressErr != nil { + //设置redis数据 + redisUserClient := redishandel.RunRedis() + redisUserClient.SetRedisTime(1123200) _, reportAnswerInfoList, _, _ := commonus.GetUserWriteAnswer(jobid, tadayTime, page, pageSize) var AdmDivStruct []commonus.AdministrativeDivision @@ -496,17 +508,23 @@ func (d *dataLockStatistics) getUserAnswerList(jobid, tadayTime, calCulTime stri if arrAy[0] == 1 { writeInfoUser.GroupId = arrAy[1] groupIdCont = arrAy[1] - isTrues, myDepartments := commonus.GetGroupInfo(arrAy[1]) - if isTrues == true { - writeInfoUser.GroupName = fmt.Sprintf("%s", myDepartments.Name) + if arrAy[1] != 0 { + isTrues, myDepartments := commonus.GetGroupInfo(arrAy[1]) + if isTrues == true { + writeInfoUser.GroupName = fmt.Sprintf("%s", myDepartments.Name) + } } + } else { writeInfoUser.GroupId = arrAy[0] groupIdCont = arrAy[0] - isTrues, myDepartments := commonus.GetGroupInfo(arrAy[0]) - if isTrues == true { - writeInfoUser.GroupName = fmt.Sprintf("%s", myDepartments.Name) + if arrAy[0] != 0 { + isTrues, myDepartments := commonus.GetGroupInfo(arrAy[0]) + if isTrues == true { + writeInfoUser.GroupName = fmt.Sprintf("%s", myDepartments.Name) + } } + } } } @@ -518,6 +536,9 @@ func (d *dataLockStatistics) getUserAnswerList(jobid, tadayTime, calCulTime stri AdmDivStruct = append(AdmDivStruct, AdmDiv) d.dataMap = append(d.dataMap, AdmDivMap) if v.Userid != "" { + + redisUserClient.HashMsetAdd("epidemic:userInclude_"+global.GVA_CONFIG.RedisPrefix.Alias+"_"+v.Userid+"_"+tadayTime, AdmDivMap) + writeInfo = append(writeInfo, writeInfoUser) } @@ -727,7 +748,7 @@ func (h *HealthReportStat) PostReportAnswerListTodayAll(c *gin.Context) { returData["baojing"] = len(readBaoJinf) returData["baojinglist"] = readBaoJinf - + response.Result(0, returData, msg, c) //计算未上报人员 // CalculationNotReport(tadayTime, calCulTime, readDataMap) //判断报警人是不是超员 @@ -735,6 +756,19 @@ func (h *HealthReportStat) PostReportAnswerListTodayAll(c *gin.Context) { //给相关人员发送信息 // sendMessAgeAlarm(calCulTime, readBaoJinf) } + if len(readDataMap) > 0 { + reportDataWrittenToDatabase(readDataMap) + } - response.Result(0, returData, msg, c) +} + +//上报数据写入数据库 +func reportDataWrittenToDatabase(saveAry []map[string]interface{}) { + if len(saveAry) > 0 { + for intdx, val := range saveAry { + fmt.Printf("写入数据:%v---->%v\n", intdx, val) + } + } else { + fmt.Printf("没有数据") + } } diff --git a/gin_server_admin/api/v1/examtestpage/healthreportstathand.go b/gin_server_admin/api/v1/examtestpage/healthreportstathand.go index 7c20a95..bceba8e 100644 --- a/gin_server_admin/api/v1/examtestpage/healthreportstathand.go +++ b/gin_server_admin/api/v1/examtestpage/healthreportstathand.go @@ -490,8 +490,12 @@ func (h *HealthReportStat) SendRelevantPersonnelTodayMsg(c *gin.Context) { } //给相关人员发送信息 sendMsg := sendMessAgeAlarm(calCulTime, readBaoJinf) - response.Result(0, sendMsg, "获取成功", c) + sendMsgMap := commonus.MapOut() + sendMsgMap["list"] = sendMsg + fmt.Printf("sendMessAgeAlarm ====> %v\n", sendMsgMap) + response.Result(0, sendMsgMap, "获取成功", c) } else { fmt.Println("没有异常人员!") + response.Result(0, "", "获取成功", c) } } diff --git a/gin_server_admin/api/v1/shiyan/shiyan.go b/gin_server_admin/api/v1/shiyan/shiyan.go index e92293c..59045ff 100644 --- a/gin_server_admin/api/v1/shiyan/shiyan.go +++ b/gin_server_admin/api/v1/shiyan/shiyan.go @@ -816,3 +816,394 @@ func ApprovalCriteria(execStatus int, process ProcessStruct, examineApprove Exam } return } + +//实验发送按钮交互模板 +func (s *ShiyanApi) SendButtonMsg(c *gin.Context) { + // var sendMsg commonus.ButtonTemplateAll + var sendMsg commonus.ButtonTemplate + sendMsg.Touser = "KaiXinGuo" + sendMsg.MsgType = "template_card" + appId, _ := strconv.ParseInt(global.GVA_CONFIG.WorkWechatSchool.AgentId, 10, 64) + sendMsg.AgentId = appId + sendMsg.EnableIdTrans = 0 + sendMsg.EnableDuplicateCheck = 0 + sendMsg.DuplicateCheckInterval = 1800 + //模板卡片类型,按钮交互型卡片填写"button_interaction" (必填) + sendMsg.TemplateCard.CardType = "button_interaction" + //卡片来源样式信息,不需要来源样式可不填写(非必填) + sendMsg.TemplateCard.Source.IconUrl = "https://docu.hxgk.group/images/2022_01/3f7a1120a559e9bee3991b85eb34d103.png" + sendMsg.TemplateCard.Source.Desc = "知行学院" + sendMsg.TemplateCard.Source.DescColor = 2 + //卡片右上角更多操作按钮 (非必填) + // sendMsg.TemplateCard.ActionMenu = + // sendMsg.TemplateCard.ActionMenu.Desc = "卡片副交互辅助文本说明" + // var actionList []commonus.ActionListStruct + // var actionCont commonus.ActionListStruct + // actionCont.Text = "接受推送" + // actionCont.Key = "A" + // actionList = append(actionList, actionCont) + // actionCont.Text = "不再推" + // actionCont.Key = "b" + // actionList = append(actionList, actionCont) + // sendMsg.TemplateCard.ActionMenu.ActionList = actionList + //一级标题,建议不超过36个字,(支持id转译) + sendMsg.TemplateCard.MainTitle.Title = "公务用车审批" + sendMsg.TemplateCard.MainTitle.Desc = "请你认真查询审批内容!" //标题辅助信息,建议不超过44个字,(支持id转译) + //引用文献样式 + sendMsg.TemplateCard.QuoteArea.Type = 1 + sendMsg.TemplateCard.QuoteArea.Url = "http://www.hxgk.group" + sendMsg.TemplateCard.QuoteArea.Title = "用车编号" + sendMsg.TemplateCard.QuoteArea.QuoteText = "用车理由:\n工作需要 \n目的地:宁阳" + + //二级普通文本,建议不超过160个字(非必填) + // sendMsg.TemplateCard.SubTitleText = "二级普通文本,下载企业微信还能抢红包!" + + //二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6(非必填) + var htalConListStrMap []commonus.HorizontalContentListStruct + var htalConListStrCon commonus.HorizontalContentListStruct + htalConListStrCon.Type = 3 + htalConListStrCon.KeyName = "申请人" + htalConListStrCon.Value = "秦东" + htalConListStrCon.UserId = "KaiXinGuo" + htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + // htalConListStrCon.Type = 1 + // htalConListStrCon.KeyName = "企业微信官网" + // htalConListStrCon.Value = "点击访问" + // htalConListStrCon.Url = "http://admin.hxgk.group" + // htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + htalConListStrCon.Type = 3 + htalConListStrCon.KeyName = "乘车人" + htalConListStrCon.Value = "秦东" + htalConListStrCon.UserId = "KaiXinGuo" + htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + sendMsg.TemplateCard.HorizontalContentList = htalConListStrMap + //整体卡片的点击跳转事件 (非必填) + sendMsg.TemplateCard.CardAction.Type = 1 + sendMsg.TemplateCard.CardAction.Url = "http://admin.hxgk.group" + //任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节 (必填) + taskId := commonus.GetFileNumberEs() + sendMsg.TemplateCard.TaskId = strconv.FormatInt(taskId, 10) + //下拉式的选择器 (未知是否必填) + // questionKey := commonus.GetFileNumberEs() + // sendMsg.TemplateCard.ButtonSelection.QuestionKey = strconv.FormatInt(questionKey, 10) + // sendMsg.TemplateCard.ButtonSelection.Title = "下拉标题" + // //下拉内容设置 + // var optionListMap []commonus.OptionListStructes + // var optionListCont commonus.OptionListStructes + // optionListCont.Id = "btn_selection_id1" + // optionListCont.Text = "100分" + // optionListMap = append(optionListMap, optionListCont) + // optionListCont.Id = "btn_selection_id2" + // optionListCont.Text = "110分" + // optionListMap = append(optionListMap, optionListCont) + // sendMsg.TemplateCard.ButtonSelection.OptionList = optionListMap + // sendMsg.TemplateCard.ButtonSelection.SelectedId = "btn_selection_id1" + //按钮列表,列表长度不超过6(必填) + var buttonMap []commonus.ButtonListStruct + var buttonCont commonus.ButtonListStruct + buttonCont.Type = 0 + buttonCont.Text = "批准" + buttonCont.Style = 1 + buttonCont.Key = "button_key_1" + buttonMap = append(buttonMap, buttonCont) + buttonCont.Type = 0 + buttonCont.Text = "驳回" + buttonCont.Style = 3 + buttonCont.Key = "button_key_2" + buttonMap = append(buttonMap, buttonCont) + sendMsg.TemplateCard.ButtonList = buttonMap + response.Result(0, sendMsg, "查询成功", c) + + callbakcMsg, isTrueCall, callBackCont := sendMsg.SendButtonMessage() + fmt.Printf("发送信息返回:%v-----------%v----------->%v\n", string(callbakcMsg), isTrueCall, callBackCont) +} + +func (s *ShiyanApi) SendButtonMessageAll(c *gin.Context) { + var sendMsg commonus.ButtonTemplateAll + // var sendMsg commonus.ButtonTemplate + sendMsg.Touser = "KaiXinGuo" + sendMsg.MsgType = "template_card" + appId, _ := strconv.ParseInt(global.GVA_CONFIG.WorkWechatSchool.AgentId, 10, 64) + sendMsg.AgentId = appId + sendMsg.EnableIdTrans = 0 + sendMsg.EnableDuplicateCheck = 0 + sendMsg.DuplicateCheckInterval = 1800 + //模板卡片类型,按钮交互型卡片填写"button_interaction" (必填) + sendMsg.TemplateCard.CardType = "button_interaction" + //卡片来源样式信息,不需要来源样式可不填写(非必填) + sendMsg.TemplateCard.Source.IconUrl = "https://docu.hxgk.group/images/2022_01/3f7a1120a559e9bee3991b85eb34d103.png" + sendMsg.TemplateCard.Source.Desc = "知行学院" + sendMsg.TemplateCard.Source.DescColor = 2 + //卡片右上角更多操作按钮 (非必填) + // sendMsg.TemplateCard.ActionMenu = + sendMsg.TemplateCard.ActionMenu.Desc = "卡片副交互辅助文本说明" + var actionList []commonus.ActionListStruct + var actionCont commonus.ActionListStruct + actionCont.Text = "接受推送" + actionCont.Key = "A" + actionList = append(actionList, actionCont) + actionCont.Text = "不再推" + actionCont.Key = "b" + actionList = append(actionList, actionCont) + sendMsg.TemplateCard.ActionMenu.ActionList = actionList + //一级标题,建议不超过36个字,(支持id转译) + sendMsg.TemplateCard.MainTitle.Title = "公务用车审批" + sendMsg.TemplateCard.MainTitle.Desc = "请你认真查询审批内容!" //标题辅助信息,建议不超过44个字,(支持id转译) + //引用文献样式 + sendMsg.TemplateCard.QuoteArea.Type = 1 + sendMsg.TemplateCard.QuoteArea.Url = "http://www.hxgk.group" + sendMsg.TemplateCard.QuoteArea.Title = "用车编号" + sendMsg.TemplateCard.QuoteArea.QuoteText = "用车理由:\n工作需要 \n目的地:宁阳" + + //二级普通文本,建议不超过160个字(非必填) + sendMsg.TemplateCard.SubTitleText = "二级普通文本,下载企业微信还能抢红包!" + + //二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6(非必填) + var htalConListStrMap []commonus.HorizontalContentListStruct + var htalConListStrCon commonus.HorizontalContentListStruct + htalConListStrCon.Type = 3 + htalConListStrCon.KeyName = "申请人" + htalConListStrCon.Value = "秦东" + htalConListStrCon.UserId = "KaiXinGuo" + htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + htalConListStrCon.Type = 1 + htalConListStrCon.KeyName = "企业微信官网" + htalConListStrCon.Value = "点击访问" + htalConListStrCon.Url = "tel:15069130853" + htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + htalConListStrCon.Type = 3 + htalConListStrCon.KeyName = "乘车人" + htalConListStrCon.Value = "秦东" + htalConListStrCon.UserId = "KaiXinGuo" + htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + sendMsg.TemplateCard.HorizontalContentList = htalConListStrMap + //整体卡片的点击跳转事件 (非必填) + sendMsg.TemplateCard.CardAction.Type = 1 + sendMsg.TemplateCard.CardAction.Url = "http://admin.hxgk.group" + //任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节 (必填) + taskId := commonus.GetFileNumberEs() + sendMsg.TemplateCard.TaskId = strconv.FormatInt(taskId, 10) + //下拉式的选择器 (未知是否必填) + questionKey := commonus.GetFileNumberEs() + sendMsg.TemplateCard.ButtonSelection.QuestionKey = strconv.FormatInt(questionKey, 10) + sendMsg.TemplateCard.ButtonSelection.Title = "下拉标题" + // //下拉内容设置 + var optionListMap []commonus.OptionListStructes + var optionListCont commonus.OptionListStructes + optionListCont.Id = "btn_selection_id1" + optionListCont.Text = "100分" + optionListMap = append(optionListMap, optionListCont) + optionListCont.Id = "btn_selection_id2" + optionListCont.Text = "110分" + optionListMap = append(optionListMap, optionListCont) + sendMsg.TemplateCard.ButtonSelection.OptionList = optionListMap + sendMsg.TemplateCard.ButtonSelection.SelectedId = "btn_selection_id1" + //按钮列表,列表长度不超过6(必填) + var buttonMap []commonus.ButtonListStruct + var buttonCont commonus.ButtonListStruct + buttonCont.Type = 0 + buttonCont.Text = "批准" + buttonCont.Style = 1 + buttonCont.Key = "button_key_1" + buttonMap = append(buttonMap, buttonCont) + buttonCont.Type = 0 + buttonCont.Text = "驳回" + buttonCont.Style = 3 + buttonCont.Key = "button_key_2" + buttonMap = append(buttonMap, buttonCont) + sendMsg.TemplateCard.ButtonList = buttonMap + response.Result(0, sendMsg, "查询成功", c) + + callbakcMsg, isTrueCall, callBackCont := sendMsg.SendButtonMessage() + fmt.Printf("发送信息返回:%v-----------%v----------->%v\n", string(callbakcMsg), isTrueCall, callBackCont) + +} + +//模板卡片消息 +//文本通知型 +func (s *ShiyanApi) SendTextMessageAll(c *gin.Context) { + var sendTextMsg commonus.TextNotice + sendTextMsg.Touser = "KaiXinGuo" + sendTextMsg.MsgType = "template_card" + appId, _ := strconv.ParseInt(global.GVA_CONFIG.WorkWechatSchool.AgentId, 10, 64) + sendTextMsg.AgentId = appId + sendTextMsg.EnableIdTrans = 0 + sendTextMsg.EnableDuplicateCheck = 0 + sendTextMsg.DuplicateCheckInterval = 1800 + + sendTextMsg.TemplateCard.CardType = "text_notice" + //卡片来源样式信息,不需要来源样式可不填写(非必填) + sendTextMsg.TemplateCard.Source.IconUrl = "https://docu.hxgk.group/images/2022_01/3f7a1120a559e9bee3991b85eb34d103.png" + sendTextMsg.TemplateCard.Source.Desc = "知行学院" + sendTextMsg.TemplateCard.Source.DescColor = 2 + + sendTextMsg.TemplateCard.ActionMenu.Desc = "是否继续关注" + var actionList []commonus.ActionListStruct + var actionCont commonus.ActionListStruct + actionCont.Text = "接受推送" + actionCont.Key = "A" + actionList = append(actionList, actionCont) + actionCont.Text = "不再推" + actionCont.Key = "b" + actionList = append(actionList, actionCont) + sendTextMsg.TemplateCard.ActionMenu.ActionList = actionList + + //任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节 (必填) + taskId := commonus.GetFileNumberEs() + sendTextMsg.TemplateCard.TaskId = strconv.FormatInt(taskId, 10) + + sendTextMsg.TemplateCard.MainTitle.Title = "公务用车审批" + sendTextMsg.TemplateCard.MainTitle.Desc = "请你认真查询审批内容!" //标题辅助信息,建议不超过44个字,(支持id转译) + + //引用文献样式 + sendTextMsg.TemplateCard.QuoteArea.Type = 1 + sendTextMsg.TemplateCard.QuoteArea.Url = "http://www.hxgk.group" + sendTextMsg.TemplateCard.QuoteArea.Title = "用车编号" + sendTextMsg.TemplateCard.QuoteArea.QuoteText = "用车理由:\n工作需要 \n目的地:宁阳" + + sendTextMsg.TemplateCard.EmphasisContent.Title = "100" + sendTextMsg.TemplateCard.EmphasisContent.DescStruct.Desc = "核心数据" + + //二级普通文本,建议不超过160个字(非必填) + sendTextMsg.TemplateCard.SubTitleText = "二级普通文本,下载企业微信还能抢红包!" + + //二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6(非必填) + var htalConListStrMap []commonus.HorizontalContentListStruct + var htalConListStrCon commonus.HorizontalContentListStruct + htalConListStrCon.Type = 3 + htalConListStrCon.KeyName = "申请人" + htalConListStrCon.Value = "秦东" + htalConListStrCon.UserId = "KaiXinGuo" + htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + htalConListStrCon.Type = 1 + htalConListStrCon.KeyName = "企业微信官网" + htalConListStrCon.Value = "点击访问" + htalConListStrCon.Url = "tel:15069130853" + htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + htalConListStrCon.Type = 3 + htalConListStrCon.KeyName = "乘车人" + htalConListStrCon.Value = "秦东" + htalConListStrCon.UserId = "KaiXinGuo" + htalConListStrMap = append(htalConListStrMap, htalConListStrCon) + sendTextMsg.TemplateCard.HorizontalContentList = htalConListStrMap + + var jumpListStruct commonus.JumpListStruct + + jumpListStruct.Type = 1 + jumpListStruct.Title = "知行学院官网" + jumpListStruct.Url = "http://www.hxgk.group" + + sendTextMsg.TemplateCard.JumpList = append(sendTextMsg.TemplateCard.JumpList, jumpListStruct) + + sendTextMsg.TemplateCard.CardAction.Type = 1 + sendTextMsg.TemplateCard.CardAction.Url = "http://admin.hxgk.group" + + callbakcMsg, isTrueCall, callBackCont := sendTextMsg.SendTextTemplateCard() + outData := commonus.MapOut() + outData["callbakcMsg"] = string(callbakcMsg) + outData["isTrueCall"] = isTrueCall + outData["callBackCont"] = callBackCont + response.Result(0, outData, "查询成功", c) +} + +//图文展示型 +func (s *ShiyanApi) SendImageMessageAll(c *gin.Context) { + var sendTextMsg commonus.ImageTextTemplate + + sendTextMsg.Touser = "KaiXinGuo" + sendTextMsg.MsgType = "template_card" + appId, _ := strconv.ParseInt(global.GVA_CONFIG.WorkWechatSchool.AgentId, 10, 64) + sendTextMsg.AgentId = appId + sendTextMsg.EnableIdTrans = 0 + sendTextMsg.EnableDuplicateCheck = 0 + sendTextMsg.DuplicateCheckInterval = 1800 + + sendTextMsg.TemplateCard.CardType = "news_notice" + //卡片来源样式信息,不需要来源样式可不填写(非必填) + sendTextMsg.TemplateCard.Source.IconUrl = "https://docu.hxgk.group/images/2022_01/3f7a1120a559e9bee3991b85eb34d103.png" + sendTextMsg.TemplateCard.Source.Desc = "知行学院" + sendTextMsg.TemplateCard.Source.DescColor = 2 + + sendTextMsg.TemplateCard.ActionMenu.Desc = "是否继续关注" + var actionList []commonus.ActionListStruct + var actionCont commonus.ActionListStruct + actionCont.Text = "接受推送" + actionCont.Key = "A" + actionList = append(actionList, actionCont) + actionCont.Text = "不再推" + actionCont.Key = "b" + actionList = append(actionList, actionCont) + sendTextMsg.TemplateCard.ActionMenu.ActionList = actionList + + //任务id,同一个应用任务id不能重复,只能由数字、字母和“_-@”组成,最长128字节 (必填) + taskId := commonus.GetFileNumberEs() + sendTextMsg.TemplateCard.TaskId = strconv.FormatInt(taskId, 10) + + sendTextMsg.TemplateCard.MainTitle.Title = "公务用车审批" + sendTextMsg.TemplateCard.MainTitle.Desc = "请你认真查询审批内容!" //标题辅助信息,建议不超过44个字,(支持id转译) + + //引用文献样式 + sendTextMsg.TemplateCard.QuoteArea.Type = 1 + sendTextMsg.TemplateCard.QuoteArea.Url = "http://www.hxgk.group" + sendTextMsg.TemplateCard.QuoteArea.Title = "用车编号" + sendTextMsg.TemplateCard.QuoteArea.QuoteText = "用车理由:\n工作需要 \n目的地:宁阳" + + sendTextMsg.TemplateCard.ImageTextArea.Type = 1 + sendTextMsg.TemplateCard.ImageTextArea.Url = "http://www.baidu.com" + sendTextMsg.TemplateCard.ImageTextArea.Title = "企业微信的左图右文样式" + sendTextMsg.TemplateCard.ImageTextArea.Desc = "企业微信真好用呀真好用" + sendTextMsg.TemplateCard.ImageTextArea.ImageUrl = "https://img.iplaysoft.com/wp-content/uploads/2019/free-images/free_stock_photo_2x.jpg" + + sendTextMsg.TemplateCard.CardImage.Url = "https://docu.hxgk.group/images/2022_01/3f7a1120a559e9bee3991b85eb34d103.png" + sendTextMsg.TemplateCard.CardImage.AspectRatio = 1.3 + + var verCont commonus.VerticalContentListStr + verCont.Title = "惊喜红包等你来拿" + verCont.Desc = "下载企业微信还能抢红包!" + sendTextMsg.TemplateCard.VerticalContentList = append(sendTextMsg.TemplateCard.VerticalContentList, verCont) + verCont.Title = "====>惊喜红包等你来拿" + verCont.Desc = "====>下载企业微信还能抢红包!" + sendTextMsg.TemplateCard.VerticalContentList = append(sendTextMsg.TemplateCard.VerticalContentList, verCont) + + var htalConListStrCon commonus.HorizontalContentListStruct + htalConListStrCon.Type = 3 + htalConListStrCon.KeyName = "申请人" + htalConListStrCon.Value = "秦东" + htalConListStrCon.UserId = "KaiXinGuo" + sendTextMsg.TemplateCard.HorizontalContentList = append(sendTextMsg.TemplateCard.HorizontalContentList, htalConListStrCon) + htalConListStrCon.Type = 1 + htalConListStrCon.KeyName = "企业微信官网" + htalConListStrCon.Value = "点击访问" + htalConListStrCon.Url = "tel:15069130853" + sendTextMsg.TemplateCard.HorizontalContentList = append(sendTextMsg.TemplateCard.HorizontalContentList, htalConListStrCon) + htalConListStrCon.Type = 3 + htalConListStrCon.KeyName = "乘车人" + htalConListStrCon.Value = "秦东" + htalConListStrCon.UserId = "KaiXinGuo" + sendTextMsg.TemplateCard.HorizontalContentList = append(sendTextMsg.TemplateCard.HorizontalContentList, htalConListStrCon) + + var jumpListStruct commonus.JumpListStruct + + jumpListStruct.Type = 1 + jumpListStruct.Title = "知行学院官网" + jumpListStruct.Url = "http://www.hxgk.group" + + sendTextMsg.TemplateCard.JumpList = append(sendTextMsg.TemplateCard.JumpList, jumpListStruct) + + jumpListStruct.Type = 1 + jumpListStruct.Title = "知行学院官网====>" + jumpListStruct.Url = "http://www.hxgk.group" + + sendTextMsg.TemplateCard.JumpList = append(sendTextMsg.TemplateCard.JumpList, jumpListStruct) + + sendTextMsg.TemplateCard.CardAction.Type = 1 + sendTextMsg.TemplateCard.CardAction.Url = "http://admin.hxgk.group" + + callbakcMsg, isTrueCall, callBackCont := sendTextMsg.SendImageTemplateCard() + outData := commonus.MapOut() + outData["callbakcMsg"] = string(callbakcMsg) + outData["isTrueCall"] = isTrueCall + outData["callBackCont"] = callBackCont + outData["sendTextMsg"] = sendTextMsg + response.Result(0, outData, "查询成功", c) +} diff --git a/gin_server_admin/api/v1/wechatcallback/wechatcallbackhanlde.go b/gin_server_admin/api/v1/wechatcallback/wechatcallbackhanlde.go new file mode 100644 index 0000000..739e4c3 --- /dev/null +++ b/gin_server_admin/api/v1/wechatcallback/wechatcallbackhanlde.go @@ -0,0 +1,30 @@ +package wechatcallback + +import ( + "encoding/json" + "encoding/xml" + "fmt" + + "github.com/flipped-aurora/gin-vue-admin/server/commonus" +) + +func templateEventPush(eventMsg []byte) { + var msgContent TemplateCardPush + err := xml.Unmarshal(eventMsg, &msgContent) + if nil != err { + fmt.Println("***********Unmarshal fail") + } + fmt.Printf("button======>%v\n", msgContent) + + jsonStr, _ := json.Marshal(msgContent) + fmt.Printf("jsonStr======>%v\n", string(jsonStr)) + + var updateButtonNotClickable commonus.UpdateButtonNotClickable + updateButtonNotClickable.Userids = append(updateButtonNotClickable.Userids, "KaiXinGuo") + updateButtonNotClickable.Atall = 0 + updateButtonNotClickable.Agentid = msgContent.Agentid + updateButtonNotClickable.ResponseCode = msgContent.ResponseCode + updateButtonNotClickable.Button.ReplaceName = "已提交更新" + callbakcMsg, isTrueCall, callBackCont := updateButtonNotClickable.UpdateSendButtonMessage() + fmt.Printf("更新销售发送信息返回:%v-----------%v----------->%v\n", string(callbakcMsg), isTrueCall, callBackCont) +} diff --git a/gin_server_admin/api/v1/wechatcallback/wechatcallbackmain.go b/gin_server_admin/api/v1/wechatcallback/wechatcallbackmain.go index b70539b..c8a06aa 100644 --- a/gin_server_admin/api/v1/wechatcallback/wechatcallbackmain.go +++ b/gin_server_admin/api/v1/wechatcallback/wechatcallbackmain.go @@ -1,31 +1,255 @@ package wechatcallback import ( + "encoding/json" + "encoding/xml" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/flipped-aurora/gin-vue-admin/server/commonus" + "github.com/flipped-aurora/gin-vue-admin/server/global" "github.com/flipped-aurora/gin-vue-admin/server/model/common/response" + "github.com/flipped-aurora/gin-vue-admin/server/model/wechatcallback" "github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizmsgcrypt" "github.com/gin-gonic/gin" ) -type WeChatCallBackApi struct{} - -type MsgContent struct { - ToUsername string `xml:"ToUserName"` - FromUsername string `xml:"FromUserName"` - CreateTime uint32 `xml:"CreateTime"` - MsgType string `xml:"MsgType"` - Content string `xml:"Content"` - Msgid string `xml:"MsgId"` - Agentid uint32 `xml:"AgentId"` -} - //回调首页 func (w *WeChatCallBackApi) Index(c *gin.Context) { + // token := global.GVA_CONFIG.WorkWechatId.Token + // receiverId := global.GVA_CONFIG.WorkWechatId.CompanyId + // encodingAeskey := global.GVA_CONFIG.WorkWechatId.EncodingAESKey + token := "QDG6eK" receiverId := "wx5823bf96d3bd56c7" encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C" wxcpt := wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType) - response.Result(0, wxcpt, "获取成功", c) + MsgSignature := c.Query("msg_signature") //企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。 + Timestamp := c.Query("timestamp") //时间戳。与nonce结合使用,用于防止请求重放攻击。 + Nonce := c.Query("nonce") //随机数。与timestamp结合使用,用于防止请求重放攻击。 + Echostr := c.Query("echostr") //加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文 + + var xmlStr CallBackVerificationXml + xmlErr := c.ShouldBindXML(&xmlStr) + + sendData := commonus.MapOut() + + sendData["wxcpt"] = wxcpt + + sendData["msg_signature"] = MsgSignature + sendData["timestamp"] = Timestamp + sendData["nonce"] = Nonce + sendData["echostr"] = Echostr + + sendData["xmlErr"] = xmlErr + sendData["xmlStr"] = xmlStr + + sendData["StatusBadRequest"] = http.StatusBadRequest + + echoStrs, cryptErr := wxcpt.VerifyURL(MsgSignature, Timestamp, Nonce, Echostr) + if nil != cryptErr { + fmt.Println("verifyUrl fail", cryptErr) + } + // fmt.Println("verifyUrl success echoStr", string(echoStrs)) + sendData["echoStrsErr"] = cryptErr + sendData["echoStrrr"] = string(echoStrs) + + reqData := []byte("") + msg, cryptErr := wxcpt.DecryptMsg(MsgSignature, Timestamp, Nonce, reqData) + if nil != cryptErr { + fmt.Println("DecryptMsg fail", cryptErr) + } + // fmt.Println("after decrypt msg: ", string(msg)) + + sendData["cryptErr"] = cryptErr + sendData["cryptErrmsg"] = string(msg) + sendData["reqDataesddd"] = string(reqData) + + response.Result(0, sendData, "获取成功", c) +} + +//API接收消息 +func (w *WeChatCallBackApi) CallbackApiMessage(c *gin.Context) { + MsgSignature := c.Query("msg_signature") //企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。 + Timestamp := c.Query("timestamp") //时间戳。与nonce结合使用,用于防止请求重放攻击。 + Nonce := c.Query("nonce") //随机数。与timestamp结合使用,用于防止请求重放攻击。 + Echostr := c.Query("echostr") //加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文 + + var decryptCont CallBackData + decryptCont.MsgSignature = MsgSignature + timeStampInt, timeStampIntErr := strconv.ParseInt(Timestamp, 10, 64) + if timeStampIntErr == nil { + decryptCont.Timestamp = timeStampInt + } + decryptCont.Nonce = Nonce + + if Echostr != "" { + + decryptCont.Echostr = Echostr + msgStr := decryptCont.VerificationUrl() + c.String(200, msgStr) + // fmt.Printf("1------------>%v\n", Echostr) + } else { + // fmt.Printf("2------------>%v\n", Echostr) + var xmlMessageStr CallBackVerificationXml + xmlErr := c.ShouldBindXML(&xmlMessageStr) + if xmlErr != nil { + response.Result(101, xmlErr, "XML获取错误!", c) + return + } + decryptCont.ToUserName = xmlMessageStr.ToUserName.Text + decryptCont.Encrypt = xmlMessageStr.Encrypt.Text + decryptCont.AgentID = xmlMessageStr.AgentID.Text + decryptCont.DecryptMessage() + } +} + +//启动企业微信验证程序 +func WechatVerification() (wxcpt *wxbizmsgcrypt.WXBizMsgCrypt) { + //正式数据 + token := "kkUA3s2s3" + // tokenEs := global.GVA_CONFIG.WorkWechatSchool.WechatTokening + receiverId := global.GVA_CONFIG.WorkWechatId.CompanyId + encodingAeskey := global.GVA_CONFIG.WorkWechatSchool.EncodingAESKey + + // fmt.Printf("11111========>%v----->%v------>%v\n", token, receiverId, encodingAeskey) + //测试数据 + // token := "QDG6eK" + // receiverId := "wx5823bf96d3bd56c7" + // encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C" + wxcpt = wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType) + return +} + +//验证URL +func (c *CallBackData) VerificationUrl() (msg string) { + wecahtCpt := WechatVerification() + timestampStr := strconv.FormatInt(c.Timestamp, 10) + echoStr, cryptErr := wecahtCpt.VerifyURL(c.MsgSignature, timestampStr, c.Nonce, c.Echostr) + var callbackLog wechatcallback.CallbackLog + // + callbackLog.MsgSignature = c.MsgSignature + callbackLog.TimeStamp = c.Timestamp + callbackLog.Nonce = c.Nonce + callbackLog.Echostr = c.Echostr + callbackLog.Xmlstr = string(echoStr) + // callbackLog.Reqdata = string(reqData) + callbackLog.AddTime = time.Now().Unix() + global.GVA_DB_WechatCallBack.Create(&callbackLog) + if nil != cryptErr { + fmt.Println("verifyUrl fail", cryptErr) + } + msg = string(echoStr) + return + // fmt.Println(string(echoStr)) + // fmt.Print(string(echoStr)) + +} + +//解析消息结构 +func (c *CallBackData) DecryptMessage() { + wecahtCpt := WechatVerification() + timestampStr := strconv.FormatInt(c.Timestamp, 10) + reqData := []byte("") + msg, cryptErr := wecahtCpt.DecryptMsg(c.MsgSignature, timestampStr, c.Nonce, reqData) + // fmt.Printf("%v=====>%v=====>%v\n: ", c.ToUserName, c.Encrypt, c.AgentID) + if nil != cryptErr { + fmt.Println("DecryptMsg fail", cryptErr) + } + // fmt.Println("after decrypt msg: ", string(msg)) + var msgContent MsgContent + err := xml.Unmarshal(msg, &msgContent) + if nil != err { + fmt.Println("Unmarshal fail") + } + fmt.Printf("1========>%v========>%v\n ", msgContent.MsgType, msgContent.Event) + switch msgContent.MsgType { + /*消息格式类型 + */ + case "text": //文本 + case "image": //图片 + case "voice": //语音 + case "video": //视频 + case "location": //位置 + case "link": //链接 + /*事件格式类型*/ + case "event": + /* + 事件附属格式 + */ + EventProcessing(msgContent.Event, msg) + + default: + } + + var callbackLog wechatcallback.CallbackLog + // + callbackLog.MsgSignature = c.MsgSignature + callbackLog.TimeStamp = c.Timestamp + callbackLog.Nonce = c.Nonce + callbackLog.Echostr = c.Echostr + callbackLog.Xmlstr = string(msg) + callbackLog.Reqdata = string(reqData) + msgCont, jsonErr := json.Marshal(msgContent) + if jsonErr == nil { + callbackLog.Jsonstr = string(msgCont) + } + callbackLog.AddTime = time.Now().Unix() + global.GVA_DB_WechatCallBack.Create(&callbackLog) +} + +//企业微信事件处理 +func EventProcessing(event string, decryptMsg []byte) { + var msgContent MsgContentMailList + err := xml.Unmarshal(decryptMsg, &msgContent) + if nil != err { + fmt.Println("Unmarshal fail") + } + switch event { + case "subscribe": //关注 + case "unsubscribe": //取消关注 + case "enter_agent": //本事件在成员进入企业微信的应用时触发 + case "LOCATION": //上报地理位置 + case "batch_job_result": //异步任务完成事件推送 + case "change_contact": //通讯录变更事件 + WorkWechatMailList(msgContent.ChangeType, decryptMsg) + case "click": //点击菜单拉取消息的事件推送 + case "view": //点击菜单跳转链接的事件推送 + case "scancode_push": //扫码推事件的事件推送 + case "scancode_waitmsg": //扫码推事件且弹出“消息接收中”提示框的事件推送 + case "pic_sysphoto": //弹出系统拍照发图的事件推送 + case "pic_photo_or_album": //弹出拍照或者相册发图的事件推送 + case "pic_weixin": //弹出微信相册发图器的事件推送 + case "location_select": //弹出地理位置选择器的事件推送 + case "open_approval_change": //审批状态通知事件 + case "share_agent_change": //企业互联共享应用事件回调 + case "share_chain_change": //上下游共享应用事件回调 + case "template_card_event": //模板卡片事件推送 + templateEventPush(decryptMsg) + case "template_card_menu_event": //通用模板卡片右上角菜单事件推送 + default: + } +} + +//企业微信通讯录变更事件处理 +func WorkWechatMailList(changeType string, decryptMsg []byte) { + switch changeType { + case "create_party": //新增部门事件 + case "update_party": //更新部门事件 + case "delete_party": //删除部门事件 + case "update_tag": //标签成员变更事件 + case "batch_job_result": //异步任务完成事件推送 + case "change_contact": //通讯录变更事件 + + case "create_user": //新增成员事件 + case "update_user": //更新成员事件 + case "delete_user": //删除成员事件 + + default: + } } diff --git a/gin_server_admin/api/v1/wechatcallback/workwechattypejson.go b/gin_server_admin/api/v1/wechatcallback/workwechattypejson.go new file mode 100644 index 0000000..5915d3a --- /dev/null +++ b/gin_server_admin/api/v1/wechatcallback/workwechattypejson.go @@ -0,0 +1,14 @@ +package wechatcallback + +//企业微信回调基础参数 +type CallBackData struct { + MsgSignature string `json:"msg_signature"` + Timestamp int64 `json:"timestamp"` + Nonce string `json:"nonce"` + Echostr string `json:"echostr"` + ToUserName string `json:"tousername"` + AgentID string `json:"agentid"` + Encrypt string `json:"encrypt"` +} + +//更新按钮为不可点击状态 diff --git a/gin_server_admin/api/v1/wechatcallback/workwechattypexml.go b/gin_server_admin/api/v1/wechatcallback/workwechattypexml.go new file mode 100644 index 0000000..8f91f01 --- /dev/null +++ b/gin_server_admin/api/v1/wechatcallback/workwechattypexml.go @@ -0,0 +1,77 @@ +package wechatcallback + +type WeChatCallBackApi struct{} + +//通用更新切片 +type CurrencyMessage struct { + ToUsername string `xml:"ToUserName"` + FromUsername string `xml:"FromUserName"` + CreateTime uint32 `xml:"CreateTime"` + MsgType string `xml:"MsgType"` + Agentid uint32 `xml:"AgentID"` + Event string `xml:"Event"` + EventKey string `xml:"EventKey"` + // TaskId string `xml:"TaskId"` +} + +type MsgContent struct { + CurrencyMessage + Content string `xml:"Content"` + Msgid string `xml:"MsgId"` +} + +//通讯录主表 +type MsgContentMailList struct { + MsgContent + ChangeType string `xml:"ChangeType"` +} + +//企业微信回调验证 +type CallBackVerificationXml struct { + ToUserName struct { + CallBackText + } `xml:"ToUserName"` + AgentID struct { + CallBackText + } `xml:"AgentID"` + Encrypt struct { + CallBackText + } `xml:"Encrypt"` +} +type CallBackText struct { + Text string `xml:",chardata"` +} + +type Login struct { + User string `form:"user" json:"user" xml:"user" binding:"required"` + Password string `form:"password" json:"password" xml:"password" binding:"required"` +} + +/* +模板卡片事件推送 +*/ +//通用模板卡片右上角菜单事件推送 +type TemplateCardPushCurrency struct { + CurrencyMessage + TaskId string `xml:"TaskId"` + CardType string `xml:"CardType"` + ResponseCode string `xml:"ResponseCode"` +} + +//模板卡片事件推送 +type TemplateCardPush struct { + TemplateCardPushCurrency + SelectedItems SelectedItemStruct `xml:"SelectedItems"` +} + +//卡片右上角事件 +type SelectedItemStruct struct { + SelectedItem []SelectedItemList `xml:"SelectedItem"` +} +type SelectedItemList struct { + QuestionKey string `xml:"QuestionKey"` + OptionIds []OptionIdsStr `xml:"OptionIds"` +} +type OptionIdsStr struct { + OptionId string `xml:"OptionId"` +} diff --git a/gin_server_admin/api/workwechatcallback/callback/callbacktype.go b/gin_server_admin/api/workwechatcallback/callback/callbacktype.go new file mode 100644 index 0000000..1832c4b --- /dev/null +++ b/gin_server_admin/api/workwechatcallback/callback/callbacktype.go @@ -0,0 +1,4 @@ +package callback + +//企业微信回调 +type WorkWeChatCallBackApi struct{} diff --git a/gin_server_admin/api/workwechatcallback/callback/enter.go b/gin_server_admin/api/workwechatcallback/callback/enter.go new file mode 100644 index 0000000..5bed61b --- /dev/null +++ b/gin_server_admin/api/workwechatcallback/callback/enter.go @@ -0,0 +1,6 @@ +package callback + +//企业微信回调 +type ApiGroup struct { + WorkWeChatApi WorkWeChatCallBackApi +} diff --git a/gin_server_admin/api/workwechatcallback/callback/workwechat.go b/gin_server_admin/api/workwechatcallback/callback/workwechat.go new file mode 100644 index 0000000..cca24f2 --- /dev/null +++ b/gin_server_admin/api/workwechatcallback/callback/workwechat.go @@ -0,0 +1,8 @@ +package callback + +import "github.com/gin-gonic/gin" + +//验证URL +func (w *WorkWeChatCallBackApi) Index(c *gin.Context) { + +} diff --git a/gin_server_admin/api/workwechatcallback/enter.go b/gin_server_admin/api/workwechatcallback/enter.go new file mode 100644 index 0000000..f4f0d69 --- /dev/null +++ b/gin_server_admin/api/workwechatcallback/enter.go @@ -0,0 +1,10 @@ +package workwechatcallback + +import "github.com/flipped-aurora/gin-vue-admin/server/api/workwechatcallback/callback" + +//企业微信回调 +type ApiGroup struct { + WorkWeChatApi callback.ApiGroup +} + +var ApiGroupApp = new(ApiGroup) diff --git a/gin_server_admin/api/xml_callback/.gitignore b/gin_server_admin/api/xml_callback/.gitignore new file mode 100644 index 0000000..c0dbbe0 --- /dev/null +++ b/gin_server_admin/api/xml_callback/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# IDE +.idea +.vs +.vscode diff --git a/gin_server_admin/api/xml_callback/README.md b/gin_server_admin/api/xml_callback/README.md new file mode 100644 index 0000000..ff30436 --- /dev/null +++ b/gin_server_admin/api/xml_callback/README.md @@ -0,0 +1,36 @@ +## weworkapi_golang + +weworkapi_golang 是为了简化开发者对企业微信API接口的使用而设计的,API调用加解密库之golang版本 + +## Usage + +将本项目下载到你的目录,既可直接引用相关文件 +详细使用方法参考[sample.go](https://github.com/sbzhu/weworkapi_golang/blob/master/sample.go)代码 + +## About + +**本库仅做示范用,并不保证完全无bug** + +作者会不定期更新本库,但不保证与官方API接口文档同步,因此一切以[官方文档](https://work.weixin.qq.com/api/doc)为准。 + +更多来自个人开发者的其它语言的库推荐: + +python: + +* https://github.com/sbzhu/weworkapi_python abelzhu@tencent.com(企业微信团队) + +ruby: + +* https://github.com/mycolorway/wework MyColorway(个人开发者) + +php: + +* https://github.com/sbzhu/weworkapi_php abelzhu@tencent.com; xiqunpan@tencent.com(企业微信团队) + +golang: + +* https://github.com/sbzhu/weworkapi_golang ryanjelin@tencent.com(企业微信团队) +* https://github.com/doubliekill/EnterpriseWechatSDK 1006401052yh@gmail.com(个人开发者) + +## Contact us +ryanjelin@tencent.com diff --git a/gin_server_admin/api/xml_callback/go.mod b/gin_server_admin/api/xml_callback/go.mod new file mode 100644 index 0000000..6033b44 --- /dev/null +++ b/gin_server_admin/api/xml_callback/go.mod @@ -0,0 +1,5 @@ +module github.com/sbzhu/weworkapi_golang + +go 1.14 + +replace github.com/sbzhu/weworkapi_golang/wxbizmsgcrypt => ./wxbizmsgcrypt diff --git a/gin_server_admin/api/xml_callback/sample.go b/gin_server_admin/api/xml_callback/sample.go new file mode 100644 index 0000000..50c0171 --- /dev/null +++ b/gin_server_admin/api/xml_callback/sample.go @@ -0,0 +1,132 @@ +package main + +import ( + "encoding/xml" + "fmt" + + "github.com/flipped-aurora/gin-vue-admin/server/wechatjiexi/wxbizmsgcrypt" +) + +type MsgContent struct { + ToUsername string `xml:"ToUserName"` + FromUsername string `xml:"FromUserName"` + CreateTime uint32 `xml:"CreateTime"` + MsgType string `xml:"MsgType"` + Content string `xml:"Content"` + Msgid string `xml:"MsgId"` + Agentid uint32 `xml:"AgentId"` +} + +func main() { + token := "QDG6eK" + receiverId := "wx5823bf96d3bd56c7" + encodingAeskey := "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C" + wxcpt := wxbizmsgcrypt.NewWXBizMsgCrypt(token, encodingAeskey, receiverId, wxbizmsgcrypt.XmlType) + /* + ------------使用示例一:验证回调URL--------------- + *企业开启回调模式时,企业微信会向验证url发送一个get请求 + 假设点击验证时,企业收到类似请求: + * GET /cgi-bin/wxpush?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3×tamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho%2FqYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp%2B4RPcs8TgAE7OaBO%2BFZXvnaqQ%3D%3D + * HTTP/1.1 Host: qy.weixin.qq.com + + 接收到该请求时,企业应 + 1.解析出Get请求的参数,包括消息体签名(msg_signature),时间戳(timestamp),随机数字串(nonce)以及企业微信推送过来的随机加密字符串(echostr), + 这一步注意作URL解码。 + 2.验证消息体签名的正确性 + 3. 解密出echostr原文,将原文当作Get请求的response,返回给企业微信 + 第2,3步可以用企业微信提供的库函数VerifyURL来实现。 + + */ + // 解析出url上的参数值如下: + // verifyMsgSign := HttpUtils.ParseUrl("msg_signature") + verifyMsgSign := "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3" + // verifyTimestamp := HttpUtils.ParseUrl("timestamp") + verifyTimestamp := "1409659589" + // verifyNonce := HttpUtils.ParseUrl("nonce") + verifyNonce := "263014780" + // verifyEchoStr := HttpUtils.ParseUrl("echoStr") + verifyEchoStr := "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==" + echoStr, cryptErr := wxcpt.VerifyURL(verifyMsgSign, verifyTimestamp, verifyNonce, verifyEchoStr) + if nil != cryptErr { + fmt.Println("verifyUrl fail", cryptErr) + } + fmt.Println("verifyUrl success echoStr", string(echoStr)) + // 验证URL成功,将sEchoStr返回 + // HttpUtils.SetResponse(sEchoStr) + + /* + ------------使用示例二:对用户回复的消息解密--------------- + 用户回复消息或者点击事件响应时,企业会收到回调消息,此消息是经过企业微信加密之后的密文以post形式发送给企业,密文格式请参考官方文档 + 假设企业收到企业微信的回调消息如下: + POST /cgi-bin/wxpush? msg_signature=477715d11cdb4164915debcba66cb864d751f3e6×tamp=1409659813&nonce=1372623149 HTTP/1.1 + Host: qy.weixin.qq.com + Content-Length: 613 + + + + + 企业收到post请求之后应该: + 1.解析出url上的参数,包括消息体签名(msg_signature),时间戳(timestamp)以及随机数字串(nonce) + 2.验证消息体签名的正确性。 + 3.将post请求的数据进行xml解析,并将标签的内容进行解密,解密出来的明文即是用户回复消息的明文,明文格式请参考官方文档 + 第2,3步可以用企业微信提供的库函数DecryptMsg来实现。 + */ + // reqMsgSign := HttpUtils.ParseUrl("msg_signature") + reqMsgSign := "477715d11cdb4164915debcba66cb864d751f3e6" + // reqTimestamp := HttpUtils.ParseUrl("timestamp") + reqTimestamp := "1409659813" + // reqNonce := HttpUtils.ParseUrl("nonce") + reqNonce := "1372623149" + // post请求的密文数据 + // reqData = HttpUtils.PostData() + reqData := []byte("") + + msg, cryptErr := wxcpt.DecryptMsg(reqMsgSign, reqTimestamp, reqNonce, reqData) + if nil != cryptErr { + fmt.Println("DecryptMsg fail", cryptErr) + } + fmt.Println("after decrypt msg: ", string(msg)) + // TODO: 解析出明文xml标签的内容进行处理 + // For example: + + var msgContent MsgContent + err := xml.Unmarshal(msg, &msgContent) + if nil != err { + fmt.Println("Unmarshal fail") + } else { + fmt.Println("struct", msgContent) + } + + /* + ------------使用示例三:企业回复用户消息的加密--------------- + 企业被动回复用户的消息也需要进行加密,并且拼接成密文格式的xml串。 + 假设企业需要回复用户的明文如下: + + + + 1348831860 + + + 1234567890123456 + 128 + + + 为了将此段明文回复给用户,企业应: + 1.自己生成时间时间戳(timestamp),随机数字串(nonce)以便生成消息体签名,也可以直接用从企业微信的post url上解析出的对应值。 + 2.将明文加密得到密文。 + 3.用密文,步骤1生成的timestamp,nonce和企业在企业微信设定的token生成消息体签名。 + 4.将密文,消息体签名,时间戳,随机数字串拼接成xml格式的字符串,发送给企业。 + 以上2,3,4步可以用企业微信提供的库函数EncryptMsg来实现。 + */ + respData := "13488318601234567890123456128" + encryptMsg, cryptErr := wxcpt.EncryptMsg(respData, reqTimestamp, reqNonce) + if nil != cryptErr { + fmt.Println("DecryptMsg fail", cryptErr) + } + + sEncryptMsg := string(encryptMsg) + fmt.Println("after encrypt sEncryptMsg: ", sEncryptMsg) + // 加密成功 + // TODO: + // HttpUtils.SetResponse(sEncryptMsg) +} diff --git a/gin_server_admin/api/xml_callback/wxbizmsgcrypt/wxbizmsgcrypt.go b/gin_server_admin/api/xml_callback/wxbizmsgcrypt/wxbizmsgcrypt.go new file mode 100644 index 0000000..9f30dca --- /dev/null +++ b/gin_server_admin/api/xml_callback/wxbizmsgcrypt/wxbizmsgcrypt.go @@ -0,0 +1,315 @@ +package wxbizmsgcrypt + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/sha1" + "encoding/base64" + "encoding/binary" + "encoding/xml" + "fmt" + "math/rand" + "sort" + "strings" +) + +const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + +const ( + ValidateSignatureError int = -40001 + ParseXmlError int = -40002 + ComputeSignatureError int = -40003 + IllegalAesKey int = -40004 + ValidateCorpidError int = -40005 + EncryptAESError int = -40006 + DecryptAESError int = -40007 + IllegalBuffer int = -40008 + EncodeBase64Error int = -40009 + DecodeBase64Error int = -40010 + GenXmlError int = -40010 + ParseJsonError int = -40012 + GenJsonError int = -40013 + IllegalProtocolType int = -40014 +) + +type ProtocolType int + +const ( + XmlType ProtocolType = 1 +) + +type CryptError struct { + ErrCode int + ErrMsg string +} + +func NewCryptError(err_code int, err_msg string) *CryptError { + return &CryptError{ErrCode: err_code, ErrMsg: err_msg} +} + +type WXBizMsg4Recv struct { + Tousername string `xml:"ToUserName"` + Encrypt string `xml:"Encrypt"` + Agentid string `xml:"AgentID"` +} + +type CDATA struct { + Value string `xml:",cdata"` +} + +type WXBizMsg4Send struct { + XMLName xml.Name `xml:"xml"` + Encrypt CDATA `xml:"Encrypt"` + Signature CDATA `xml:"MsgSignature"` + Timestamp string `xml:"TimeStamp"` + Nonce CDATA `xml:"Nonce"` +} + +func NewWXBizMsg4Send(encrypt, signature, timestamp, nonce string) *WXBizMsg4Send { + return &WXBizMsg4Send{Encrypt: CDATA{Value: encrypt}, Signature: CDATA{Value: signature}, Timestamp: timestamp, Nonce: CDATA{Value: nonce}} +} + +type ProtocolProcessor interface { + parse(src_data []byte) (*WXBizMsg4Recv, *CryptError) + serialize(msg_send *WXBizMsg4Send) ([]byte, *CryptError) +} + +type WXBizMsgCrypt struct { + token string + encoding_aeskey string + receiver_id string + protocol_processor ProtocolProcessor +} + +type XmlProcessor struct { +} + +func (self *XmlProcessor) parse(src_data []byte) (*WXBizMsg4Recv, *CryptError) { + var msg4_recv WXBizMsg4Recv + err := xml.Unmarshal(src_data, &msg4_recv) + if nil != err { + return nil, NewCryptError(ParseXmlError, "xml to msg fail") + } + return &msg4_recv, nil +} + +func (self *XmlProcessor) serialize(msg4_send *WXBizMsg4Send) ([]byte, *CryptError) { + xml_msg, err := xml.Marshal(msg4_send) + if nil != err { + return nil, NewCryptError(GenXmlError, err.Error()) + } + return xml_msg, nil +} + +func NewWXBizMsgCrypt(token, encoding_aeskey, receiver_id string, protocol_type ProtocolType) *WXBizMsgCrypt { + var protocol_processor ProtocolProcessor + if protocol_type != XmlType { + panic("unsupport protocal") + } else { + protocol_processor = new(XmlProcessor) + } + + return &WXBizMsgCrypt{token: token, encoding_aeskey: (encoding_aeskey + "="), receiver_id: receiver_id, protocol_processor: protocol_processor} +} + +func (self *WXBizMsgCrypt) randString(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))] + } + return string(b) +} + +func (self *WXBizMsgCrypt) pKCS7Padding(plaintext string, block_size int) []byte { + padding := block_size - (len(plaintext) % block_size) + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + var buffer bytes.Buffer + buffer.WriteString(plaintext) + buffer.Write(padtext) + return buffer.Bytes() +} + +func (self *WXBizMsgCrypt) pKCS7Unpadding(plaintext []byte, block_size int) ([]byte, *CryptError) { + plaintext_len := len(plaintext) + if nil == plaintext || plaintext_len == 0 { + return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding error nil or zero") + } + if plaintext_len%block_size != 0 { + return nil, NewCryptError(DecryptAESError, "pKCS7Unpadding text not a multiple of the block size") + } + padding_len := int(plaintext[plaintext_len-1]) + return plaintext[:plaintext_len-padding_len], nil +} + +func (self *WXBizMsgCrypt) cbcEncrypter(plaintext string) ([]byte, *CryptError) { + aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + const block_size = 32 + pad_msg := self.pKCS7Padding(plaintext, block_size) + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, NewCryptError(EncryptAESError, err.Error()) + } + + ciphertext := make([]byte, len(pad_msg)) + iv := aeskey[:aes.BlockSize] + + mode := cipher.NewCBCEncrypter(block, iv) + + mode.CryptBlocks(ciphertext, pad_msg) + base64_msg := make([]byte, base64.StdEncoding.EncodedLen(len(ciphertext))) + base64.StdEncoding.Encode(base64_msg, ciphertext) + + return base64_msg, nil +} + +func (self *WXBizMsgCrypt) cbcDecrypter(base64_encrypt_msg string) ([]byte, *CryptError) { + aeskey, err := base64.StdEncoding.DecodeString(self.encoding_aeskey) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + + encrypt_msg, err := base64.StdEncoding.DecodeString(base64_encrypt_msg) + if nil != err { + return nil, NewCryptError(DecodeBase64Error, err.Error()) + } + + block, err := aes.NewCipher(aeskey) + if err != nil { + return nil, NewCryptError(DecryptAESError, err.Error()) + } + + if len(encrypt_msg) < aes.BlockSize { + return nil, NewCryptError(DecryptAESError, "encrypt_msg size is not valid") + } + + iv := aeskey[:aes.BlockSize] + + if len(encrypt_msg)%aes.BlockSize != 0 { + return nil, NewCryptError(DecryptAESError, "encrypt_msg not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + + mode.CryptBlocks(encrypt_msg, encrypt_msg) + + return encrypt_msg, nil +} + +func (self *WXBizMsgCrypt) calSignature(timestamp, nonce, data string) string { + sort_arr := []string{self.token, timestamp, nonce, data} + sort.Strings(sort_arr) + var buffer bytes.Buffer + for _, value := range sort_arr { + buffer.WriteString(value) + } + + sha := sha1.New() + sha.Write(buffer.Bytes()) + signature := fmt.Sprintf("%x", sha.Sum(nil)) + return string(signature) +} + +func (self *WXBizMsgCrypt) ParsePlainText(plaintext []byte) ([]byte, uint32, []byte, []byte, *CryptError) { + const block_size = 32 + plaintext, err := self.pKCS7Unpadding(plaintext, block_size) + if nil != err { + return nil, 0, nil, nil, err + } + + text_len := uint32(len(plaintext)) + if text_len < 20 { + return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 1") + } + random := plaintext[:16] + msg_len := binary.BigEndian.Uint32(plaintext[16:20]) + if text_len < (20 + msg_len) { + return nil, 0, nil, nil, NewCryptError(IllegalBuffer, "plain is to small 2") + } + + msg := plaintext[20 : 20+msg_len] + receiver_id := plaintext[20+msg_len:] + + return random, msg_len, msg, receiver_id, nil +} + +func (self *WXBizMsgCrypt) VerifyURL(msg_signature, timestamp, nonce, echostr string) ([]byte, *CryptError) { + signature := self.calSignature(timestamp, nonce, echostr) + + if strings.Compare(signature, msg_signature) != 0 { + return nil, NewCryptError(ValidateSignatureError, "signature not equal") + } + + plaintext, err := self.cbcDecrypter(echostr) + if nil != err { + return nil, err + } + + _, _, msg, receiver_id, err := self.ParsePlainText(plaintext) + if nil != err { + return nil, err + } + + if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { + fmt.Println(string(receiver_id), self.receiver_id, len(receiver_id), len(self.receiver_id)) + return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") + } + + return msg, nil +} + +func (self *WXBizMsgCrypt) EncryptMsg(reply_msg, timestamp, nonce string) ([]byte, *CryptError) { + rand_str := self.randString(16) + var buffer bytes.Buffer + buffer.WriteString(rand_str) + + msg_len_buf := make([]byte, 4) + binary.BigEndian.PutUint32(msg_len_buf, uint32(len(reply_msg))) + buffer.Write(msg_len_buf) + buffer.WriteString(reply_msg) + buffer.WriteString(self.receiver_id) + + tmp_ciphertext, err := self.cbcEncrypter(buffer.String()) + if nil != err { + return nil, err + } + ciphertext := string(tmp_ciphertext) + + signature := self.calSignature(timestamp, nonce, ciphertext) + + msg4_send := NewWXBizMsg4Send(ciphertext, signature, timestamp, nonce) + return self.protocol_processor.serialize(msg4_send) +} + +func (self *WXBizMsgCrypt) DecryptMsg(msg_signature, timestamp, nonce string, post_data []byte) ([]byte, *CryptError) { + msg4_recv, crypt_err := self.protocol_processor.parse(post_data) + if nil != crypt_err { + return nil, crypt_err + } + + signature := self.calSignature(timestamp, nonce, msg4_recv.Encrypt) + + if strings.Compare(signature, msg_signature) != 0 { + return nil, NewCryptError(ValidateSignatureError, "signature not equal") + } + + plaintext, crypt_err := self.cbcDecrypter(msg4_recv.Encrypt) + if nil != crypt_err { + return nil, crypt_err + } + + _, _, msg, receiver_id, crypt_err := self.ParsePlainText(plaintext) + if nil != crypt_err { + return nil, crypt_err + } + + if len(self.receiver_id) > 0 && strings.Compare(string(receiver_id), self.receiver_id) != 0 { + return nil, NewCryptError(ValidateCorpidError, "receiver_id is not equil") + } + + return msg, nil +} diff --git a/gin_server_admin/commonus/messagepush.go b/gin_server_admin/commonus/messagepush.go index c83a50f..4ecd73f 100644 --- a/gin_server_admin/commonus/messagepush.go +++ b/gin_server_admin/commonus/messagepush.go @@ -19,6 +19,20 @@ func sendUrlSet() (sendUrlstr string, isTrue bool, msg string) { return } +//更新模版卡片消息 +func UpdateSendTemplateCard() (sendUrlstr string, isTrue bool, msg string) { + isTrue = false + ton, _, err := GetWechatTokenType("") + if err != nil { + msg = "获取ToKen失败!" + return + } + isTrue = true + msg = "获取ToKen成功!" + sendUrlstr = "https://qyapi.weixin.qq.com/cgi-bin/message/update_template_card?access_token=" + ton + return +} + //发送文本信息 func (t *SendText) SendTextMessage() ([]byte, bool, string) { sendUrl, IsTrue, msg := sendUrlSet() @@ -54,3 +68,80 @@ func (s *SendImgCont) SendImgMessage() ([]byte, bool, string) { // fmt.Printf("%v-------------->%v\n", string(sendJsonData), string(addDePartMent)) return addDePartMent, true, msg } + +//发送按钮交互型模板文件 +func (b *ButtonTemplateAll) SendButtonMessage() (receiveData []byte, IsTrue bool, msg string) { + sendUrl, IsTrue, msg := sendUrlSet() + if IsTrue == false { + return + } + sendJsonData, sendErr := json.Marshal(b) + if sendErr != nil { + IsTrue = false + msg = "发送数据不正确" + return + } + receiveData = CurlPostJosn(sendUrl, sendJsonData) + return +} +func (b *ButtonTemplate) SendButtonMessage() (receiveData []byte, IsTrue bool, msg string) { + sendUrl, IsTrue, msg := sendUrlSet() + if IsTrue == false { + return + } + sendJsonData, sendErr := json.Marshal(b) + if sendErr != nil { + IsTrue = false + msg = "发送数据不正确" + return + } + receiveData = CurlPostJosn(sendUrl, sendJsonData) + return +} + +//跟新按钮交互型模板文件 +func (u *UpdateButtonNotClickable) UpdateSendButtonMessage() (receiveData []byte, IsTrue bool, msg string) { + sendUrl, IsTrue, msg := UpdateSendTemplateCard() + if IsTrue == false { + return + } + sendJsonData, sendErr := json.Marshal(u) + if sendErr != nil { + IsTrue = false + msg = "发送数据不正确" + return + } + receiveData = CurlPostJosn(sendUrl, sendJsonData) + return +} + +//文本通知型 --- 模板卡片消息 +func (t *TextNotice) SendTextTemplateCard() (receiveData []byte, IsTrue bool, msg string) { + sendUrl, IsTrue, msg := sendUrlSet() + if IsTrue == false { + return + } + sendJsonData, sendErr := json.Marshal(t) + if sendErr != nil { + IsTrue = false + msg = "发送数据不正确" + return + } + receiveData = CurlPostJosn(sendUrl, sendJsonData) + return +} + +func (i *ImageTextTemplate) SendImageTemplateCard() (receiveData []byte, IsTrue bool, msg string) { + sendUrl, IsTrue, msg := sendUrlSet() + if IsTrue == false { + return + } + sendJsonData, sendErr := json.Marshal(i) + if sendErr != nil { + IsTrue = false + msg = "发送数据不正确" + return + } + receiveData = CurlPostJosn(sendUrl, sendJsonData) + return +} diff --git a/gin_server_admin/commonus/messagepushtype.go b/gin_server_admin/commonus/messagepushtype.go index 2bb49df..4df0428 100644 --- a/gin_server_admin/commonus/messagepushtype.go +++ b/gin_server_admin/commonus/messagepushtype.go @@ -1,6 +1,6 @@ package commonus -//文本消息 +//消息类型 type SendPublic struct { Touser string `json:"touser" form:"touser"` ToParty string `json:"toparty" form:"toparty"` @@ -11,6 +11,7 @@ type SendPublic struct { DuplicateCheckInterval int `json:"duplicate_check_interval" form:"duplicate_check_interval"` } +//文本消息 type SendText struct { SendPublic Safe int `json:"safe" form:"safe"` @@ -28,7 +29,7 @@ type SendMarkDown struct { Text SendTextStruct `json:"markdown" form:"markdown"` } -//发送图文西悉尼 +//发送图文信息 type SendImgCont struct { SendPublic EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` @@ -47,3 +48,326 @@ type ArticlesStruct struct { Appid string `json:"appid" form:"appid"` Pagepath string `json:"pagepath" form:"pagepath"` } + +//图片类型 +type SendPicture struct { + SendPublic + Images MediaIdStruct `json:"image" form:"image"` + Safe int `json:"safe" form:"safe"` +} +type MediaIdStruct struct { + MediaId string `json:"media_id" form:"media_id"` +} + +//语音信息 +type VoiceMsg struct { + SendPublic + Voice MediaIdStruct `json:"image" form:"voice"` +} + +//视频消息 +type VideoStruct struct { + SendPublic + Safe int `json:"safe" form:"safe"` + Video VideoListStruct `json:"image" form:"video"` +} +type VideoListStruct struct { + MediaIdStruct + Title string `json:"title" form:"title"` + Description string `json:"description" form:"description"` +} + +//文件消息 +type FileStruct struct { + SendPublic + Safe int `json:"safe" form:"safe"` + File MediaIdStruct `json:"file" form:"file"` +} + +//文本卡片消息 +type TextcardStruct struct { + SendPublic + EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` + Textcard TextcardCont `json:"textcard" form:"textcard"` +} +type TextcardCont struct { + Title string `json:"title" form:"title"` + Description string `json:"description" form:"description"` + URL string `json:"url" form:"url"` + Btntxt string `json:"btntxt" form:"btntxt"` +} + +//图文消息 +type NewsImages struct { + SendPublic + EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` + Mpnews SendNewsStructs `json:"mpnews" form:"mpnews"` + Safe int `json:"safe" form:"safe"` +} + +type SendNewsStructs struct { + Articles []ArticlesStructs `json:"articles" form:"articles"` +} +type ArticlesStructs struct { + Title string `json:"title" form:"title"` + ThumbMediaId string `json:"thumb_media_id" form:"thumb_media_id"` + Author string `json:"author" form:"author"` + ContentSourceUrl string `json:"content_source_url" form:"content_source_url"` + Content string `json:"content" form:"content"` + Digest string `json:"digest" form:"digest"` +} + +//模板卡片消息 + +//文本通知型 +type TextNotice struct { + SendPublic + TemplateCard TemplatecardStruct `json:"template_card"` + EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` +} +type TemplatecardStruct struct { + CardType string `json:"card_type"` + Source SourceStruct `json:"source"` + ActionMenu ActionMenuStruct `json:"action_menu"` + TaskId string `json:"task_id"` + MainTitle MainTitleStruct `json:"main_title"` + QuoteArea QuoteAreaStruct `json:"quote_area"` + EmphasisContent EmphasisContentStruct `json:"emphasis_content"` //关键数据样式 + SubTitleText string `json:"sub_title_text"` //二级普通文本,建议不超过160个字, + HorizontalContentList []HorizontalContentListStruct `json:"horizontal_content_list"` //二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 + JumpList []JumpListStruct `json:"jump_list"` //跳转指引样式的列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过3 + CardAction CardActionStruct `json:"card_action"` //整体卡片的点击跳转事件,text_notice必填本字段 +} + +//更多操作界面的描述 +type DescStruct struct { + Desc string `json:"desc"` //操作描述 +} + +//卡片来源样式信息,不需要来源样式可不填写 +type SourceStruct struct { + IconUrl string `json:"icon_url"` + DescStruct + DescColor int `json:"desc_color"` +} + +//卡片右上角更多操作按钮 +type ActionMenuStruct struct { + DescStruct + ActionList []ActionListStruct `json:"action_list"` +} + +//操作列表,列表长度取值范围为 [1, 3] +type ActionListStruct struct { + Text string `json:"text"` //操作的描述文案 + Key string `json:"key"` //操作key值,用户点击后,会产生回调事件将本参数作为EventKey返回,回调事件会带上该key值,最长支持1024字节,不可重复 +} + +//一级标题,建议不超过36个字,文本通知型卡片本字段非必填,但不可本字段和sub_title_text都不填,(支持id转译) +type MainTitleStruct struct { + Title string `json:"title"` //操作的描述文案 + DescStruct +} + +//引用文献样式 +type QuoteAreaStruct struct { + Type int `json:"type"` + Url string `json:"url"` + Title string `json:"title"` + QuoteText string `json:"quote_text"` + Appid string `json:"appid" form:"appid"` + Pagepath string `json:"pagepath" form:"pagepath"` +} + +//关键数据样式 +type EmphasisContentStruct struct { + MainTitleStruct +} + +//二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 +type HorizontalContentListStruct struct { + Type int `json:"type"` + KeyName string `json:"keyname"` + Value string `json:"value"` + Url string `json:"url"` + MediaId string `json:"media_id"` + UserId string `json:"userid"` +} + +//跳转指引样式的列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过3 +type JumpListStruct struct { + Type int `json:"type"` + Title string `json:"title"` + Url string `json:"url"` + Appid string `json:"appid"` + Pagepath string `json:"pagepath"` +} + +//整体卡片的点击跳转事件,text_notice必填本字段 +type CardActionStruct struct { + Type int `json:"type"` + Url string `json:"url"` + Appid string `json:"appid"` + Pagepath string `json:"pagepath"` +} + +//图文展示型-模板卡片 +type ImageTextTemplate struct { + SendPublic + TemplateCard ImgTxtTemplatecardStruct `json:"template_card"` + EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` +} + +//图文展示 +type ImgTxtTemplatecardStruct struct { + CardType string `json:"card_type"` + Source SourceStruct `json:"source"` + ActionMenu ActionMenuStruct `json:"action_menu"` + TaskId string `json:"task_id"` + MainTitle MainTitleStruct `json:"main_title"` + QuoteArea QuoteAreaStruct `json:"quote_area"` + ImageTextArea ImageTextAreaStruct `json:"image_text_area"` //左图右文样式,news_notice类型的卡片,card_image和image_text_area两者必填一个字段,不可都不填 + CardImage CardImageStruct `json:"card_image"` //图片样式,news_notice类型的卡片,card_image和image_text_area两者必填一个字段,不可都不填 + VerticalContentList []VerticalContentListStr `json:"vertical_content_list"` //卡片二级垂直内容,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过4 + HorizontalContentList []HorizontalContentListStruct `json:"horizontal_content_list"` //二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 + JumpList []JumpListStruct `json:"jump_list"` //跳转指引样式的列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过3 + CardAction CardActionStruct `json:"card_action"` //整体卡片的点击跳转事件,text_notice必填本字段 +} + +//左图右文样式,news_notice类型的卡片,card_image和image_text_area两者必填一个字段,不可都不填 +type ImageTextAreaStruct struct { + Type int `json:"type"` + Title string `json:"title"` + Url string `json:"url"` + Desc string `json:"desc"` + ImageUrl string `json:"image_url"` +} + +//图片样式,news_notice类型的卡片,card_image和image_text_area两者必填一个字段,不可都不填 +type CardImageStruct struct { + Url string `json:"url"` + AspectRatio float32 `json:"aspect_ratio"` +} + +//卡片二级垂直内容,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过4 +type VerticalContentListStr struct { + MainTitleStruct +} + +//按钮交互型-----模板卡片 +type ButtonTemplateAll struct { + SendPublic + TemplateCard ButtonTemplateStructAll `json:"template_card"` + EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` +} +type ButtonTemplate struct { + SendPublic + TemplateCard ButtonTemplateStruct `json:"template_card"` + EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` +} + +//按钮交互模板 +type ButtonTemplateStructAll struct { + CurrencyButtonTemplateAll + TaskId string `json:"task_id"` +} + +type ButtonTemplateStruct struct { + CurrencyButtonTemplateStruct + TaskId string `json:"task_id"` +} + +//下拉式选择器 +type ButtonSelectionStr struct { + QuestionKey string `json:"question_key"` + Title string `json:"title"` + SelectedId string `json:"selected_id"` + OptionList []OptionListStructes `json:"option_list"` //选项列表,下拉选项不超过 10 个,最少1个 +} + +//选项列表,下拉选项不超过 10 个,最少1个 +type OptionListStructes struct { + Id string `json:"id"` + Text string `json:"text"` + IsChecked bool `json:"is_checked"` +} + +//按钮列表,列表长度不超过6 +type ButtonListStruct struct { + Type int `json:"type"` + Text string `json:"text"` + Style int `json:"style"` + Key string `json:"key"` + Url string `json:"url"` +} + +//投票选择型---模板卡片 +type VoteTemplate struct { + SendPublic + TemplateCard VoteTemplateStruct `json:"template_card"` + EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` +} + +//投票模型 +type VoteTemplateStruct struct { + CardType string `json:"card_type"` + Source SourceStruct `json:"source"` + MainTitle MainTitleStruct `json:"main_title"` + TaskId string `json:"task_id"` + CheckBox CheckBoxStruct `json:"checkbox"` //选择题样式 + SubmitButton SubmitButtonStr `json:"submit_button"` //提交按钮样式 +} + +//选择题样式 +type CheckBoxStruct struct { + QuestionKey string `json:"question_key"` + OptionList []OptionListStructes `json:"option_list"` //选项列表,下拉选项不超过 10 个,最少1个 + Mode int `json:"mode"` +} + +//提交按钮样式 +type SubmitButtonStr struct { + ActionListStruct +} + +//多项选择型 --- 模板卡片 +type MultipleChoiceTemplate struct { + SendPublic + TemplateCard MultipleChoiceTemplateStruct `json:"template_card"` + EnableIdTrans int `json:"enable_id_trans" form:"enable_id_trans"` +} + +//多项选择型 +type MultipleChoiceTemplateStruct struct { + CardType string `json:"card_type"` + Source SourceStruct `json:"source"` + MainTitle MainTitleStruct `json:"main_title"` + TaskId string `json:"task_id"` + SelectList []ButtonSelectionStr `json:"select_list"` + SubmitButton SubmitButtonStr `json:"submit_button"` //提交按钮样式 +} + +//通用按钮模板 +type CurrencyButtonTemplateAll struct { + CardType string `json:"card_type"` + Source SourceStruct `json:"source"` + ActionMenu ActionMenuStruct `json:"action_menu"` + MainTitle MainTitleStruct `json:"main_title"` + QuoteArea QuoteAreaStruct `json:"quote_area"` + SubTitleText string `json:"sub_title_text"` //二级普通文本,建议不超过160个字, + HorizontalContentList []HorizontalContentListStruct `json:"horizontal_content_list"` //二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 + CardAction CardActionStruct `json:"card_action"` //整体卡片的点击跳转事件,text_notice必填本字段 + ButtonSelection ButtonSelectionStr `json:"button_selection"` + ButtonList []ButtonListStruct `json:"button_list"` //按钮列表,列表长度不超过6 +} + +type CurrencyButtonTemplateStruct struct { + CardType string `json:"card_type"` + Source SourceStruct `json:"source"` + MainTitle MainTitleStruct `json:"main_title"` + QuoteArea QuoteAreaStruct `json:"quote_area"` + SubTitleText string `json:"sub_title_text"` //二级普通文本,建议不超过160个字, + HorizontalContentList []HorizontalContentListStruct `json:"horizontal_content_list"` //二级标题+文本列表,该字段可为空数组,但有数据的话需确认对应字段是否必填,列表长度不超过6 + CardAction CardActionStruct `json:"card_action"` //整体卡片的点击跳转事件,text_notice必填本字段 + ButtonList []ButtonListStruct `json:"button_list"` //按钮列表,列表长度不超过6 +} diff --git a/gin_server_admin/commonus/publicstruct.go b/gin_server_admin/commonus/publicstruct.go index fc8b009..cd3a268 100644 --- a/gin_server_admin/commonus/publicstruct.go +++ b/gin_server_admin/commonus/publicstruct.go @@ -6,8 +6,19 @@ type SetId struct { OutId string `json:"outID"` } +type SetIds struct { + Id int64 `json:"id"` + OutId string `json:"outid"` +} + //批量ID type BatchId struct { Id []int64 `json:"id"` OutId string `json:"outID"` } + +type DelParameter struct { + SetIds + State int `json:"state"` // 状态 + IsDel int `json:"isdel"` // 是否强制删除 +} diff --git a/gin_server_admin/commonus/updatemessagetype.go b/gin_server_admin/commonus/updatemessagetype.go new file mode 100644 index 0000000..cb3cd1a --- /dev/null +++ b/gin_server_admin/commonus/updatemessagetype.go @@ -0,0 +1,52 @@ +package commonus + +//更新卡片公共部门 +type UpdateTemplateCardPublic struct { + Touser string `json:"touser" form:"touser"` + ToParty string `json:"toparty" form:"toparty"` + ToTag string `json:"totag" form:"totag"` + AgentId int64 `json:"agentid" form:"agentid"` + ResponseCode string `json:"response_code" form:"response_code"` +} + +//更新按钮为不可点击状态 +type UpdateTemplateCardNotClickAll struct { + UpdateTemplateCardPublic + TemplateCard UpdateButtonTempClickAll `json:"template_card"` +} +type UpdateTemplateCardNotClick struct { + UpdateTemplateCardPublic + TemplateCard UpdateButtonTempClick `json:"template_card"` +} + +//更新按钮模板 +type UpdateButtonTempClickAll struct { + CurrencyButtonTemplateAll + ReplaceText string `json:"replace_text" form:"replace_text"` +} +type UpdateButtonTempClick struct { + CurrencyButtonTemplateStruct + ReplaceText string `json:"replace_text" form:"replace_text"` +} + +/* +更新模版卡片消息 +*/ +//更新模板卡片通用部门 +type UpdateTemplateCardCurrency struct { + Userids []string `json:"userids"` //企业的成员ID列表(最多支持1000个) + Partyids []int `json:"partyids"` //企业的部门ID列表(最多支持100个) + Tagids []int `json:"tagids"` //企业的标签ID列表(最多支持100个) + Atall int `json:"atall"` //更新整个任务接收人员 + Agentid uint32 `json:"agentid"` //应用的agentid + ResponseCode string `json:"response_code"` +} + +//更新按钮为不可点击状态 +type UpdateButtonNotClickable struct { + UpdateTemplateCardCurrency + Button ButtonNotClick `json:"button"` +} +type ButtonNotClick struct { + ReplaceName string `json:"replace_name"` +} diff --git a/gin_server_admin/commonus/wechatapp.go b/gin_server_admin/commonus/wechatapp.go index 1e80a44..4736ffe 100644 --- a/gin_server_admin/commonus/wechatapp.go +++ b/gin_server_admin/commonus/wechatapp.go @@ -20,12 +20,13 @@ func GetWechatTokenType(types string) (tokenInfo string, redisClient *redishande secretStr := global.GVA_CONFIG.WorkWechatMailList.SecretStr switch types { case "maillist": - redisPrefix = redisPrefix + "_mail_" + redisPrefix = redisPrefix + "mail_" secretStr = global.GVA_CONFIG.WorkWechatMailList.SecretStr case "health": - redisPrefix = redisPrefix + "_health_" + redisPrefix = redisPrefix + "health_" secretStr = global.GVA_CONFIG.WorkHealthReport.SecretStr default: + redisPrefix = redisPrefix + "sholle_" secretStr = global.GVA_CONFIG.WorkWechatSchool.SecretStr } // fmt.Printf("token=====>%v***************%v------------------%v\n", redisPrefix, global.GVA_CONFIG.RedisPrefix.Alias, global.GVA_REDIS) @@ -49,7 +50,7 @@ func GetWechatTokenType(types string) (tokenInfo string, redisClient *redishande } // fmt.Printf("%v\n", callBackData) tokenInfo = callBackData.Accesstoken - redisClient.SetRedisTime(7200) + redisClient.SetRedisTime(7080) // redisClient.Set("workWeChatToken_"+global.GVA_CONFIG.RedisPrefix.Alias, tokenInfo) redisClient.Set(redisPrefix+global.GVA_CONFIG.RedisPrefix.Alias, tokenInfo) } @@ -78,7 +79,7 @@ func GetWechatToken() (tokenInfo string, redisClient *redishandel.RedisStoreType } // fmt.Printf("%v\n", callBackData) tokenInfo = callBackData.Accesstoken - redisClient.SetRedisTime(7200) + redisClient.SetRedisTime(7080) redisClient.Set("workWeChatToken_"+global.GVA_CONFIG.RedisPrefix.Alias, tokenInfo) } return @@ -579,7 +580,7 @@ func GetUserWriteAnswer(jobid, date string, page int, pageSize int64) (btyDate [ } addDePartMent := CurlPostJosn(getWechatApiUrl, sendJsonData) btyDate = addDePartMent - fmt.Printf("结果---->%v\n", string(addDePartMent)) + // fmt.Printf("结果---->%v\n", string(addDePartMent)) var reportAnswerJson ReportAnswerResult err = json.Unmarshal(addDePartMent, &reportAnswerJson) r = &reportAnswerJson diff --git a/gin_server_admin/config.yaml b/gin_server_admin/config.yaml index 99c4840..ec7b00f 100644 --- a/gin_server_admin/config.yaml +++ b/gin_server_admin/config.yaml @@ -291,13 +291,30 @@ mysqlPerformanceappraisal: log-zap: "" + #企业微信回调记录 +mysqlWechatCallBack: + path: '127.0.0.1:3306' + config: 'charset=utf8mb4&parseTime=True&loc=Local' + db-name: 'wechatlog' + username: 'root' + password: 'root' + max-idle-conns: 100 + max-open-conns: 1500 + log-mode: false + log-zap: "" + + #企业微信相关设置 workwechatid: companyid: 'ww02f310301953277a' #企业ID + workwechatschool: #知行学院 agentid: 1000008 secretstr: 'YJOHrmHtvevAdctg-06TMLnPokIaLHdfrQMyQolZQC8' + #知行学院API接收消息 + workwechattoken: 'kkUA3s2s3' #Token + encodingaeskey: 'ZI29of85mTgQPik8LLjDnYKlAECDbI23Pq886VJ9Azf' #EncodingAESKey workwechatappmaillist: #通讯录 secretstr: 'yjcQXkh6_116QKjfZfbRSyzdrFFZ9jbVlKJtL2tn3OU' @@ -307,9 +324,16 @@ workwechatappmaillist: #通讯录 healthreport: #健康上报 secretstr: 'smjpGmFo5wp18BZGiLaECFr84Blv429v_GFdKp4_0YQ' + + + + #测试企业 workwechatids: companyid: 'ww708746402de33ba7' #企业ID + #知行学院API接收消息 + workwechattoken: 'kkUA3s2s3' #Token + encodingaeskey: 'ZI29of85mTgQPik8LLjDnYKlAECDbI23Pq886VJ9Azf' #EncodingAESKey workwechatschools: #测试 agentid: 1000021 diff --git a/gin_server_admin/config/config.go b/gin_server_admin/config/config.go index 29ec105..69d5670 100644 --- a/gin_server_admin/config/config.go +++ b/gin_server_admin/config/config.go @@ -26,6 +26,7 @@ type Server struct { MysqlHealthReportDate Mysql `mapstructure:"mysqlHealthReportDate" json:"mysqlHealthReportDate" yaml:"mysqlHealthReportDate"` MysqlApprovalProcess Mysql `mapstructure:"mysqlApprovalProcess" json:"mysqlApprovalProcess" yaml:"mysqlApprovalProcess"` MysqlPerformanceappraisal Mysql `mapstructure:"mysqlPerformanceappraisal" json:"mysqlPerformanceappraisal" yaml:"mysqlPerformanceappraisal"` + MysqlWechatCallBack Mysql `mapstructure:"mysqlWechatCallBack" json:"mysqlWechatCallBack" yaml:"mysqlWechatCallBack"` // oss Local Local `mapstructure:"local" json:"local" yaml:"local"` diff --git a/gin_server_admin/config/wechat.go b/gin_server_admin/config/wechat.go index ab4c448..df0ca8a 100644 --- a/gin_server_admin/config/wechat.go +++ b/gin_server_admin/config/wechat.go @@ -4,13 +4,17 @@ package config //企业微信ID type workWechatId struct { - CompanyId string `json:id` //企业ID + CompanyId string `json:id` //企业ID + WechatToken string `json:workwechattoken` //企业ID + EncodingAESKey string `json:encodingaeskey` //企业ID } //企业微信应用配置 type workWechatApplication struct { - AgentId string `json:agentid` //应用ID - SecretStr string `json:secretstr` //应用Secret + AgentId string `json:agentid` //应用ID + SecretStr string `json:secretstr` //应用Secret + WechatTokening string `json:workwechattoken` //企业ID + EncodingAESKey string `json:encodingaeskey` //企业ID } //企业微信内置应用 diff --git a/gin_server_admin/global/global.go b/gin_server_admin/global/global.go index bfc4e81..e0599b7 100644 --- a/gin_server_admin/global/global.go +++ b/gin_server_admin/global/global.go @@ -40,6 +40,7 @@ var ( GVA_DB_HealthReport *gorm.DB GVA_DB_ApprovalProcess *gorm.DB GVA_DB_Performanceappraisal *gorm.DB + GVA_DB_WechatCallBack *gorm.DB //个人配置 // GVA_MyConfig config.MyConfig diff --git a/gin_server_admin/initialize/gorm.go b/gin_server_admin/initialize/gorm.go index 793bf7b..5993c39 100644 --- a/gin_server_admin/initialize/gorm.go +++ b/gin_server_admin/initialize/gorm.go @@ -156,6 +156,8 @@ func GormMysqlChange(setDataBaseName string) *gorm.DB { m = global.GVA_CONFIG.MysqlApprovalProcess case "mysqlPerformanceappraisal": m = global.GVA_CONFIG.MysqlPerformanceappraisal + case "mysqlWechatCallBack": + m = global.GVA_CONFIG.MysqlWechatCallBack default: m = global.GVA_CONFIG.Mysql } diff --git a/gin_server_admin/main.go b/gin_server_admin/main.go index 0b95886..c21f71b 100644 --- a/gin_server_admin/main.go +++ b/gin_server_admin/main.go @@ -83,6 +83,10 @@ func main() { if global.GVA_DB_Performanceappraisal != nil { fmt.Printf("%v==>数据库mysqlApprovalProcess初始化成功\n", global.GVA_DB_Performanceappraisal) } + global.GVA_DB_WechatCallBack = initialize.GormMysqlChange("mysqlWechatCallBack") + if global.GVA_DB_Performanceappraisal != nil { + fmt.Printf("%v==>数据库mysqlWechatCallBack初始化成功\n", global.GVA_DB_WechatCallBack) + } // fmt.Printf("WEiXin------>%v===>%v----->%v----->%v\n", global.GVA_CONFIG.WorkWechatId, global.GVA_CONFIG.WorkWechatSchool, global.GVA_CONFIG.WorkWechatMailList, global.GVA_CONFIG.WorkHealthReport) // fmt.Printf("jkskd => %v===>%v----->%v\n", global.GVA_CONFIG.MyConfig.AppKey, global.GVA_CONFIG.MyConfig.Visit, global.GVA_CONFIG) diff --git a/gin_server_admin/model/wechatcallback/callbacklog.go b/gin_server_admin/model/wechatcallback/callbacklog.go new file mode 100644 index 0000000..899cf5a --- /dev/null +++ b/gin_server_admin/model/wechatcallback/callbacklog.go @@ -0,0 +1,18 @@ +package wechatcallback + +//企业微信回调记录 +type CallbackLog struct { + Id int64 `json:"id" gorm:"column:id;type:bigint(20);;primaryKey;unique;not null;autoIncrement;index"` + MsgSignature string `json:"msgSignature" gorm:"column:msg_signature;type:varchar(255);not null;comment:组织名称"` + TimeStamp int64 `json:"timestamp" gorm:"column:timestamp;type:bigint(20) unsigned;default:0;not null;comment:编辑时间"` + Nonce string `json:"nonce" gorm:"column:nonce;type:varchar(255);not null;comment:组织名称"` + Echostr string `json:"echostr" gorm:"column:echostr;type:varchar(255);not null;comment:组织名称"` + Xmlstr string `json:"xmlstr" gorm:"column:xmlstr;type:text;comment:组织名称"` + Jsonstr string `json:"jsonstr" gorm:"column:jsonstr;type:text;comment:组织名称"` + AddTime int64 `json:"addtime" gorm:"column:addtime;type:bigint(20) unsigned;default:0;not null;comment:编辑时间"` + Reqdata string `json:"reqdata" gorm:"column:reqdata;type:text;comment:组织名称"` +} + +func (CallbackLog *CallbackLog) TableName() string { + return "callback_log" +} diff --git a/gin_server_admin/router/shiyan/sys_shiyan.go b/gin_server_admin/router/shiyan/sys_shiyan.go index ee5b0f8..8d47f15 100644 --- a/gin_server_admin/router/shiyan/sys_shiyan.go +++ b/gin_server_admin/router/shiyan/sys_shiyan.go @@ -18,5 +18,10 @@ func (s *ShiyanRouter) InitShiyanRouter(Router *gin.RouterGroup) { shiyanCodeRouter.POST("/xml", authorityApi.XmlToJson) shiyanCodeRouter.POST("/approve", authorityApi.EstablishApprove) //审批操作 + shiyanCodeRouter.POST("/sendbuttonmsg", authorityApi.SendButtonMsg) //实验发送按钮操作 + shiyanCodeRouter.POST("/sendbuttonmsgall", authorityApi.SendButtonMessageAll) //实验发送按钮操作全按钮 + + shiyanCodeRouter.POST("/sendtextmsgall", authorityApi.SendTextMessageAll) //实验发送按钮操作全按钮 + shiyanCodeRouter.POST("/sendimagemsgall", authorityApi.SendImageMessageAll) //实验发送按钮操作全按钮 } } diff --git a/gin_server_admin/router/system/sys_admin.go b/gin_server_admin/router/system/sys_admin.go index 38f401f..f8a0d0d 100644 --- a/gin_server_admin/router/system/sys_admin.go +++ b/gin_server_admin/router/system/sys_admin.go @@ -28,8 +28,13 @@ func (a *AdminUserApi) InitSystemAdminRouter(Router *gin.RouterGroup) { muneRouter := Router.Group("systemmenu") var systemMuneApi = v1.ApiGroupApp.SystemApi.SysTemMenuApi { - muneRouter.POST("/systemmenulist", systemMuneApi.SystemMenuList) //系统菜单列表 - - muneRouter.POST("/getmenu", systemMuneApi.GetMenu) //获取系统做出菜单 + muneRouter.POST("/systemmenulist", systemMuneApi.SystemMenuList) //系统菜单列表 + muneRouter.POST("/getmenu", systemMuneApi.GetMenu) //获取系统做出菜单 GetMenuList + muneRouter.POST("/getmenulist", systemMuneApi.GetMenuList) //菜单列表(不带操作) + muneRouter.POST("/addmenu", systemMuneApi.AddMenu) //添加菜单 + muneRouter.POST("/eitemenu", systemMuneApi.EiteMenu) //修改菜单 + muneRouter.POST("/delmenu", systemMuneApi.DelMenu) //删除菜单 + muneRouter.POST("/addmenuoperation", systemMuneApi.AddMenuOperation) //添加菜单功能 + muneRouter.POST("/delmenuperation", systemMuneApi.DelMenuOperation) //删除菜单功能 } } diff --git a/gin_server_admin/router/wechatcallbackrouter/wechatcallbackhandle.go b/gin_server_admin/router/wechatcallbackrouter/wechatcallbackhandle.go index 09f9cc2..868dfb3 100644 --- a/gin_server_admin/router/wechatcallbackrouter/wechatcallbackhandle.go +++ b/gin_server_admin/router/wechatcallbackrouter/wechatcallbackhandle.go @@ -13,7 +13,13 @@ func (g *WeChatCallBackRoute) InitGroupRouter(Router *gin.RouterGroup) { { groupCodeRouter.POST("", authorityApi.Index) groupCodeRouter.POST("/", authorityApi.Index) + groupCodeRouter.GET("", authorityApi.Index) groupCodeRouter.GET("/", authorityApi.Index) + groupCodeRouter.POST("callbackcpimessage", authorityApi.CallbackApiMessage) //回调接收消息 + groupCodeRouter.POST("callbackcpimessage/", authorityApi.CallbackApiMessage) //回调接收消息 + groupCodeRouter.GET("callbackcpimessage", authorityApi.CallbackApiMessage) //回调接收消息 + groupCodeRouter.GET("callbackcpimessage/", authorityApi.CallbackApiMessage) //回调接收消息 + } } diff --git a/gin_server_admin/其他支持文件/config.yaml b/gin_server_admin/其他支持文件/config.yaml index f8a1635..cdf93e8 100644 --- a/gin_server_admin/其他支持文件/config.yaml +++ b/gin_server_admin/其他支持文件/config.yaml @@ -249,17 +249,31 @@ mysqlPerformanceappraisal: max-open-conns: 1500 log-mode: false log-zap: "" - + #企业微信回调记录 +mysqlWechatCallBack: + path: '127.0.0.1:3306' + config: 'charset=utf8mb4&parseTime=True&loc=Local' + db-name: 'wechatlog' + username: 'wechatlog' + password: 'j7Hs8Tb6SkZzy2ee' + max-idle-conns: 100 + max-open-conns: 1500 + log-mode: false + log-zap: "" #企业微信相关设置 workwechatid: companyid: 'ww02f310301953277a' #企业ID + workwechatschool: #知行学院 agentid: 1000008 secretstr: 'YJOHrmHtvevAdctg-06TMLnPokIaLHdfrQMyQolZQC8' + #知行学院API接收消息 + workwechattoken: 'kkUA3s2s3' #workwechattoken + encodingaeskey: 'ZI29of85mTgQPik8LLjDnYKlAECDbI23Pq886VJ9Azf' #EncodingAESKey workwechatappmaillist: #通讯录 secretstr: 'yjcQXkh6_116QKjfZfbRSyzdrFFZ9jbVlKJtL2tn3OU' @@ -270,10 +284,14 @@ healthreport: #健康上报 #测试企业 workwechatids: companyid: 'ww708746402de33ba7' #企业ID + workwechatschools: #测试 agentid: 1000021 secretstr: 'rbqos2un6vVY5k_c2aOFK6HUuONeJsiBqwRZXTDVBKU' + #知行学院API接收消息 + workwechattoken: 'kkUA3s2s3' #Token + encodingaeskey: 'ZI29of85mTgQPik8LLjDnYKlAECDbI23Pq886VJ9Azf' #EncodingAESKey workwechatappmaillists: #通讯录 secretstr: 'TSSsJXiqh3RKl0NYIoB-sPc43MUIRJ1ppALWtzyLY94'