Pārlūkot izejas kodu

feature(项目跟进提醒): 1、钉钉、邮箱、websocket,已实现,微信小程序暂未实现

ZZH-wl 2 gadi atpakaļ
vecāks
revīzija
dbbe099329

+ 1 - 0
opms_admin/app/handler/message.go

@@ -118,6 +118,7 @@ func (h *SystemMessageHandler) Create(ctx context.Context, req g.MapStrStr, rsp
 		return err
 	}
 	// 检查请求参数
+	reqData.MsgStatus = "10"
 	if err := gvalid.CheckStruct(ctx, reqData, nil); err != nil {
 		return err
 	}

+ 4 - 4
opms_admin/app/service/sys_dict_type.go

@@ -26,15 +26,15 @@ func NewDictTypeService(ctx context.Context) (svc *DictTypeService, err error) {
 }
 
 func (s *DictTypeService) GetList(req *model.ListSysDictTypeReq) (total int, list []*model.SysDictType, err error) {
-	db := s.Dao
+	db := s.Dao.M
 	if req.DictName != "" {
-		db.Where(s.Dao.C.DictName+" like ?", "%"+req.DictName+"%")
+		db = db.Where(s.Dao.C.DictName+" like ?", "%"+req.DictName+"%")
 	}
 	if req.DictType != "" {
-		db.Where(s.Dao.C.DictType+" like ?", "%"+req.DictType+"%")
+		db = db.Where(s.Dao.C.DictType+" like ?", "%"+req.DictType+"%")
 	}
 	if req.Status != "" {
-		db.Where(s.Dao.C.Status+" = ", gconv.Int(req.Status))
+		db = db.Where(s.Dao.C.Status+" = ", gconv.Int(req.Status))
 	}
 	total, err = db.Count()
 	if err != nil {

+ 21 - 15
opms_admin/app/service/sys_message.go

@@ -5,6 +5,7 @@ import (
 	"dashoo.cn/micro/app/dao"
 	"dashoo.cn/micro/app/model"
 	"fmt"
+	"github.com/gogf/gf/container/gset"
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/os/gtime"
 	"github.com/gogf/gf/util/gconv"
@@ -98,6 +99,10 @@ func (s *MessageService) Create(req *model.CreateSysMessageReq) (err error) {
 	}
 	data.Id = int(lastId)
 	userIds := strings.Split(data.RecvUserIds, ",")
+	if len(userIds) == 0 {
+		return nil
+	}
+	userIds = gset.NewStrSetFrom(userIds).Slice()
 	for _, userId := range userIds {
 		if userId == "" {
 			continue
@@ -108,22 +113,23 @@ func (s *MessageService) Create(req *model.CreateSysMessageReq) (err error) {
 			s.logDao.C.IsRead: "10",
 		}
 		s.logDao.Data(log).Insert()
-		sendMsgType := strings.Split(data.SendType, ",")
-		for _, v := range sendMsgType {
-			switch v {
-			case "10": // 10:websocket
-				MessageNotify(userId, *data)
-				fmt.Println(v, "1")
-			case "20": // 20:邮件
-				fmt.Println(v, "20")
-			case "30": // 30:钉钉
-				fmt.Println(v, "3")
-			case "40": // 40:小程序模版
-				fmt.Println(v, "4")
-			case "50": //
-				fmt.Println(v, "5")
+	}
+	sendMsgType := strings.Split(data.SendType, ",")
+	for _, v := range gset.NewStrSetFrom(sendMsgType).Slice() {
+		switch v {
+		case "10": // 10:websocket
+			go BatchSendMessageNotify(userIds, *data)
+			fmt.Println(v, "10")
+		case "20": // 20:邮件
+			go s.BatchSendUserEmailMsg(userIds, data.MsgTitle, data.MsgContent)
+			fmt.Println(v, "20")
+		case "30": // 30:钉钉
+			go s.BatchSendUserDingTalkTextMsg(userIds, data.MsgTitle, data.MsgContent)
+			fmt.Println(v, "30")
+		case "40": // 40:微信小程序订阅消息
+
+			fmt.Println(v, "40")
 
-			}
 		}
 	}
 	return

+ 226 - 0
opms_admin/app/service/sys_send_message.go

@@ -0,0 +1,226 @@
+package service
+
+import (
+	"dashoo.cn/micro/app/dao"
+	"dashoo.cn/opms_libary/myerrors"
+	"dashoo.cn/opms_libary/plugin/dingtalk"
+	"dashoo.cn/opms_libary/plugin/dingtalk/message/corpconversation"
+	"dashoo.cn/opms_libary/plugin/email"
+	"dashoo.cn/opms_libary/plugin/wechat"
+	"github.com/gogf/gf/frame/g"
+	"strings"
+)
+
+// 发送邮箱
+func (c *contextService) SendUserEmailMsg(userId, msgTitle, msgContent string) error {
+	userInfo, err := dao.NewSysUserDao(c.Tenant).Fields(dao.SysUser.C.NickName, dao.SysUser.C.Email).WherePri(userId).One()
+	if err != nil {
+		g.Log().Error(err)
+		return err
+	}
+	if userInfo.Email == "" {
+		errMsg := "发送用户 " + userInfo.NickName + " 邮箱邮件失败:用户邮箱为空"
+		g.Log().Error(errMsg)
+		return myerrors.TipsError(errMsg)
+	}
+	m := email.NewMessage()
+	m.SetHeader("From", g.Config().GetString("email.From"))
+	m.SetHeader("To", userInfo.Email)
+	m.SetHeader("Subject", msgTitle)
+	m.SetBody("text/plain", msgContent)
+
+	err = email.Client.DialAndSend(m)
+	if err != nil {
+		g.Log().Error("发送用户 "+userInfo.NickName+" 邮箱邮件失败:"+userInfo.Email, err)
+		return err
+	}
+	return nil
+}
+
+// 批量发送邮箱
+func (c *contextService) BatchSendUserEmailMsg(userIds []string, msgTitle, msgContent string) error {
+	userInfos, err := dao.NewSysUserDao(c.Tenant).Fields(dao.SysUser.C.NickName, dao.SysUser.C.Email).WherePri(userIds).All()
+	if err != nil {
+		g.Log().Error(err)
+		return err
+	}
+	for _, userInfo := range userInfos {
+		if userInfo.Email == "" {
+			g.Log().Error("发送用户 " + userInfo.NickName + " 邮箱邮件失败:用户邮箱为空")
+			continue
+		}
+		m := email.NewMessage()
+		m.SetHeader("From", g.Config().GetString("email.From"))
+		m.SetHeader("To", userInfo.Email)
+		m.SetHeader("Subject", msgTitle)
+		m.SetBody("text/plain", msgContent)
+
+		err = email.Client.DialAndSend(m)
+		if err != nil {
+			g.Log().Error("发送用户邮箱邮件失败:"+userInfo.Email, err)
+			continue
+		}
+	}
+	return nil
+}
+
+// 发送钉钉
+func (c *contextService) SendUserDingTalkTextMsg(userId, msgTitle, msgContent string) error {
+	userInfo, err := dao.NewSysUserDao(c.Tenant).Fields(dao.SysUser.C.NickName, dao.SysUser.C.DingtalkUid).WherePri(userId).One()
+	if err != nil {
+		g.Log().Error(err)
+		return err
+	}
+	if userInfo.DingtalkUid == "" {
+		errMsg := "发送用户 " + userInfo.NickName + " 钉钉失败:钉钉账号信息为空"
+		g.Log().Error(errMsg)
+		return myerrors.TipsError(errMsg)
+	}
+	dingtalkSendMsgReq := &corpconversation.SendCorpConversationRequest{
+		UseridList: userInfo.DingtalkUid,
+		Msg: corpconversation.Msg{
+			Msgtype: "text",
+			Text: corpconversation.TextMsg{
+				Content: msgContent,
+			},
+		},
+	}
+
+	err = c.BaseSendUserDingTalkMsg(dingtalkSendMsgReq, userInfo.NickName)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// 批量发送钉钉
+func (c *contextService) BatchSendUserDingTalkTextMsg(userIds []string, msgTitle, msgContent string) error {
+	userInfos, err := dao.NewSysUserDao(c.Tenant).Fields(dao.SysUser.C.NickName, dao.SysUser.C.DingtalkUid).WherePri(userIds).All()
+	if err != nil {
+		g.Log().Error(err)
+		return err
+	}
+	var dingtalkUids []string
+	var userNickNameList []string
+	for _, userInfo := range userInfos {
+		if userInfo.DingtalkUid == "" {
+			errMsg := "发送用户 " + userInfo.NickName + " 钉钉消息失败:钉钉账号信息为空"
+			g.Log().Error(errMsg)
+			continue
+		}
+		dingtalkUids = append(dingtalkUids, userInfo.DingtalkUid)
+		userNickNameList = append(userNickNameList, userInfo.NickName)
+	}
+	dingtalkSendMsgReq := &corpconversation.SendCorpConversationRequest{
+		UseridList: strings.Join(dingtalkUids, ","),
+		Msg: corpconversation.Msg{
+			Msgtype: "text",
+			Text: corpconversation.TextMsg{
+				Content: msgContent,
+			},
+		},
+	}
+	err = c.BaseSendUserDingTalkMsg(dingtalkSendMsgReq, userNickNameList)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 钉钉基础调用
+func (c *contextService) BaseSendUserDingTalkMsg(dingtalkSendMsgReq *corpconversation.SendCorpConversationRequest, remark interface{}) error {
+	err := c.CreateDingTalkLog("10", dingtalkSendMsgReq, remark)
+	if err != nil {
+		g.Log().Error("创建发送钉钉消息请求log失败:", err)
+	}
+
+	resp, err := dingtalk.Client.GetCorpConversation().SendCorpConversation(dingtalkSendMsgReq)
+	if err != nil {
+		g.Log().Error("发送用户钉钉消息失败:", err)
+		return err
+	}
+
+	err = c.CreateDingTalkLog("20", resp, remark)
+	if err != nil {
+		g.Log().Error("创建钉钉消息返回log失败:", err)
+	}
+
+	getSendResultRequest := &corpconversation.GetSendResultRequest{
+		TaskId: resp.TaskId,
+	}
+	err = c.CreateDingTalkLog("10", getSendResultRequest, nil)
+	if err != nil {
+		g.Log().Error("创建获取发送钉钉消息结果请求log失败:", err)
+	}
+
+	response, err := dingtalk.Client.GetCorpConversation().GetSendResult(getSendResultRequest)
+	if err != nil {
+		g.Log().Error("获取发送用户钉钉消息结果失败:", err)
+		return err
+	}
+
+	err = c.CreateDingTalkLog("20", response, nil)
+	if err != nil {
+		g.Log().Error("创建获取发送钉钉消息结果返回log失败:", err)
+	}
+	return err
+}
+
+// 创建钉钉调用log
+func (c *contextService) CreateDingTalkLog(reqType string, content, remark interface{}) error {
+	data := g.Map{
+		"type":    reqType,
+		"content": content,
+		"remark":  remark,
+	}
+	SetCreatedInfo(data, 0, "系统发送消息创建")
+	_, err := g.DB(c.Tenant).Model("dingtalk_log").Safe().Data(data).Insert()
+	if err != nil {
+		g.Log().Error("创建钉钉log失败:", err)
+	}
+	return err
+}
+
+// 发送微信小程序
+func (c *contextService) SendUserMiniWechatMsg(userId, msgTitle, msgContent string) error {
+	userInfo, err := dao.NewSysUserDao(c.Tenant).Fields(dao.SysUser.C.NickName, dao.SysUser.C.WechatId).WherePri(userId).One()
+	if err != nil {
+		g.Log().Error(err)
+		return err
+	}
+	if userInfo.WechatId == "" {
+		errMsg := "发送用户 " + userInfo.NickName + " 微信小程序消息失败:用户邮箱为空"
+		g.Log().Error(errMsg)
+		return myerrors.TipsError(errMsg)
+	}
+	client, err := wechat.Client.MiniClient()
+	client.GetMessage()
+
+	if err != nil {
+		g.Log().Error("发送用户 "+userInfo.NickName+" 微信小程序消息失败:"+userInfo.WechatId, err)
+		return err
+	}
+	return nil
+}
+
+// 批量发送微信小程序
+func (c *contextService) BatchSendUserMiniWechatMsg(userIds []string, msgTitle, msgContent string) error {
+	userInfos, err := dao.NewSysUserDao(c.Tenant).Fields(dao.SysUser.C.NickName, dao.SysUser.C.WechatId).WherePri(userIds).All()
+	if err != nil {
+		g.Log().Error(err)
+		return err
+	}
+	for _, userInfo := range userInfos {
+		if userInfo.WechatId == "" {
+			g.Log().Error("发送用户 " + userInfo.NickName + " 微信小程序消息失败:用户邮箱为空")
+			continue
+		}
+
+		if err != nil {
+			g.Log().Error("发送用户邮箱邮件失败:"+userInfo.Email, err)
+			continue
+		}
+	}
+	return nil
+}

+ 25 - 0
opms_admin/app/service/websocket_manager.go

@@ -269,6 +269,31 @@ func MessageNotify(userId string, data model.SysMessage) {
 	}()
 }
 
+func BatchSendMessageNotify(userIds []string, data model.SysMessage) {
+	var onlineUserId []string
+	for _, userId := range userIds {
+		arr, ok := Manager.Accounts[userId]
+		if !ok || len(arr) == 0 { // 无匹配数据,直接报错
+			g.Log("websocket").Error(fmt.Sprintf("用户ID:%v 无匹配连接", userId))
+			continue
+		}
+		onlineUserId = append(onlineUserId, userId)
+	}
+
+	// 发送消息
+	go func() {
+		var msg ServiceMessage
+		msg.Type = "SendMessage"
+		msg.Content = ServiceMessageContent{
+			Body: data,
+		}
+		err := Send(userIds, msg)
+		if err != nil {
+			g.Log("websocket").Error(err)
+		}
+	}()
+}
+
 func CreateConnection(accountId, link string, r *ghttp.Request) {
 	// 将http升级为websocket
 	conn, err := r.WebSocket()

+ 8 - 1
opms_admin/config/config.toml

@@ -53,4 +53,11 @@
     app-key="dinguytykawticadfoht"
     app-secret="zPlj4ZpITsUbeq2C0GrwJ78-e8knH_kIeyvznaNQacqtrSb9zbeZcOajgBKdolky"
     aes-key="oUjmeWea8Ow1jsdK4UHoDthy6EMQKq3RGbM2rEeTgnm"
-    token="WaasHsYk8V3wqwN5xRGsCmiiRDB"
+    token="WaasHsYk8V3wqwN5xRGsCmiiRDB"
+
+[email]
+    From="likai@dashoo.cn"
+    host="hwsmtp.exmail.qq.com"
+    port="465"
+    user="likai@dashoo.cn"
+    password="LLLkkk0210"

+ 1 - 2
opms_libary/go.mod

@@ -5,12 +5,11 @@ go 1.16
 require (
 	dashoo.cn/common_definition v0.0.0
 	github.com/gogf/gf v1.16.9
-	github.com/lenaten/hl7 v0.0.0-20181009090854-63c5c49a56d9 // indirect
 	github.com/mojocn/base64Captcha v1.3.5
 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
 	github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
 	github.com/rpcxio/rpcx-consul v0.0.0-20220730062257-1ff0472e730f
-	github.com/sijms/go-ora/v2 v2.5.31 // indirect
+	github.com/sijms/go-ora/v2 v2.5.31
 	github.com/smallnest/rpcx v1.8.0
 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
 )

+ 2 - 0
opms_libary/go.sum

@@ -406,6 +406,8 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l
 github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
 github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
+github.com/sijms/go-ora/v2 v2.5.31 h1:8gOO4PpvhlBvF3wLURxfg3sYCeM3hKPJC+y42Hq5ouU=
+github.com/sijms/go-ora/v2 v2.5.31/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/smallnest/quick v0.0.0-20220703133648-f13409fa6c67 h1:eIa+/FyZCItCSwE1tjTEzYYHEk+5vtAw3jCW8DlnP3g=

+ 12 - 6
opms_libary/plugin/dingtalk/client.go

@@ -7,6 +7,7 @@ import (
 	"dashoo.cn/opms_libary/plugin/dingtalk/context"
 	"dashoo.cn/opms_libary/plugin/dingtalk/jsapi"
 	"dashoo.cn/opms_libary/plugin/dingtalk/message"
+	"dashoo.cn/opms_libary/plugin/dingtalk/message/corpconversation"
 	"dashoo.cn/opms_libary/plugin/dingtalk/storage"
 	"dashoo.cn/opms_libary/plugin/dingtalk/workflow"
 	"github.com/gogf/gf/frame/g"
@@ -57,32 +58,37 @@ func initContext(cfg *context.Config, context *context.Context) {
 	context.SetAccessTokenLock(new(sync.RWMutex))
 }
 
-//GetAccessToken 获取access_token
+// GetAccessToken 获取access_token
 func (c *ClientImpl) GetAccessToken() (string, error) {
 	return c.Context.GetAccessToken()
 }
 
-//GetJsapi 获取Jsapi
+// GetJsapi 获取Jsapi
 func (c *ClientImpl) GetJsapi() *jsapi.Jsapi {
 	return jsapi.NewJsapi(c.Context)
 }
 
-//GetContact 通讯录
+// GetContact 通讯录
 func (c *ClientImpl) GetContact() *contact.Contact {
 	return contact.NewContact(c.Context)
 }
 
-//GetWorkflow OA审批
+// GetContact 通讯录
+func (c *ClientImpl) GetCorpConversation() *corpconversation.CorpConversation {
+	return corpconversation.NewCorpConversation(c.Context)
+}
+
+// GetWorkflow OA审批
 func (c *ClientImpl) GetWorkflow() *workflow.Workflow {
 	return workflow.NewWorkflow(c.Context)
 }
 
-//GetCalendar 日程
+// GetCalendar 日程
 func (c *ClientImpl) GetCalendar() *calendar.Calendar {
 	return calendar.NewCalendar(c.Context)
 }
 
-//GetStorage 获取Jsapi
+// GetStorage 获取Jsapi
 func (c *ClientImpl) GetStorage() *storage.Storage {
 	return storage.NewStorage(c.Context)
 }

+ 50 - 0
opms_libary/plugin/dingtalk/message/corpconversation/corpconversation.go

@@ -0,0 +1,50 @@
+package corpconversation
+
+import (
+	"dashoo.cn/opms_libary/plugin/dingtalk/base"
+	"dashoo.cn/opms_libary/plugin/dingtalk/context"
+	"encoding/json"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/util/gconv"
+)
+
+const (
+	//POST https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2?access_token=ACCESS_TOKEN
+	SendCorpConversationUrl = "https://oapi.dingtalk.com/topapi/message/corpconversation/asyncsend_v2"  //添加权限
+	GetSendResultUrl        = "https://oapi.dingtalk.com/topapi/message/corpconversation/getsendresult" //添加权限
+
+)
+
+// CorpConversation 工作通知
+type CorpConversation struct {
+	base.Base
+}
+
+// NewCorpConversation init
+func NewCorpConversation(context *context.Context) *CorpConversation {
+	material := new(CorpConversation)
+	material.Context = context
+	return material
+}
+
+// SendCorpConversation 发送工作通知
+func (c *CorpConversation) SendCorpConversation(request *SendCorpConversationRequest) (response SendCorpConversationResponse, err error) {
+	request.AgentId = gconv.Int64(g.Config().GetString("dingtalk.agent-id"))
+	resp, _ := c.HTTPPostJSONWithAccessToken(SendCorpConversationUrl, request)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(resp, &response)
+	return response, err
+}
+
+// GetSendResult 获取工作通知消息的发送结果
+func (c *CorpConversation) GetSendResult(request *GetSendResultRequest) (response GetSendResultResponse, err error) {
+	request.AgentId = gconv.Int64(g.Config().GetString("dingtalk.agent-id"))
+	resp, _ := c.HTTPPostJSONWithAccessToken(GetSendResultUrl, request)
+	if err != nil {
+		return
+	}
+	err = json.Unmarshal(resp, &response)
+	return response, err
+}

+ 131 - 0
opms_libary/plugin/dingtalk/message/corpconversation/entity.go

@@ -0,0 +1,131 @@
+package corpconversation
+
+// 获取工作通知消息的发送结果请求
+type GetSendResultRequest struct {
+	AgentId int64 `json:"agent_id"` // 发送消息时使用的微应用的AgentID。
+	TaskId  int64 `json:"task_id"`  // 发送消息时钉钉返回的任务ID。
+}
+
+// 发获取工作通知消息的发送结果响应
+type GetSendResultResponse struct {
+	RequestId  string     `json:"request_id"` // 请求ID。
+	Errcode    int64      `json:"errcode"`
+	Errmsg     string     `json:"errmsg"`
+	SendResult SendResult `json:"send_result"`
+}
+
+type SendResult struct {
+	InvalidUserIdList   []string `json:"invalid_user_id_list"`   // 无效的userId
+	ForbiddenUserIdList []string `json:"forbidden_user_id_list"` // 因发送消息过于频繁或超量而被流控过滤后实际未发送的userId。 未被限流的接收者仍会被成功发送。
+	FailedUserIdList    []string `json:"failed_user_id_list"`    // 发送失败的userId。
+	ReadUserIdList      []string `json:"read_user_id_list"`      // 已读消息的userId
+	UnreadUserIdList    []string `json:"unread_user_id_list"`    // 未读消息的userId。
+	InvalidDeptIdList   []int64  `json:"invalid_dept_id_list"`   // 无效的部门ID。
+
+	ForbiddenList []SendForbiddenModel `json:"forbidden_list"`
+}
+type SendForbiddenModel struct {
+	Code   string `json:"code"`   // 流控code。
+	Count  int64  `json:"count"`  // 流控阀值
+	Userid string `json:"userid"` // 被流控员工的userId。
+}
+
+// 发送工作通知请求
+type SendCorpConversationRequest struct {
+	AgentId    int64  `json:"agent_id"`               // 发送消息时使用的微应用的AgentID。
+	UseridList string `json:"userid_list,omitempty"`  // 接收者的userid列表,最大用户列表长度100
+	DeptIdList string `json:"dept_id_list,omitempty"` // 接收者的部门id列表,最大列表长度20。 接收者是部门ID时,包括子部门下的所有用户
+	ToAllUser  string `json:"to_all_user,omitempty"`  // 是否发送给企业全部用户。
+	Msg        Msg    `json:"msg"`
+}
+
+// 发送工作通知响应
+type SendCorpConversationResponse struct {
+	RequestId string `json:"request_id"` // 请求ID。
+	TaskId    int64  `json:"task_id"`    // 发送消息时钉钉返回的任务ID。
+	Errcode   int64  `json:"errcode"`
+	Errmsg    string `json:"errmsg"`
+}
+
+type Msg struct {
+	Msgtype    string        `json:"msgtype"` // text,image,voice,file,link,oa,markdown,action_card
+	Text       TextMsg       `json:"text,omitempty"`
+	Image      ImageMsg      `json:"image,omitempty"`
+	Voice      VoiceMsg      `json:"voice,omitempty"`
+	File       FileMsg       `json:"file,omitempty"`
+	Link       LinkMsg       `json:"link,omitempty"`
+	OA         OAMsg         `json:"oa,omitempty"`
+	Markdown   MarkdownMsg   `json:"markdown,omitempty"`
+	ActionCard ActionCardMsg `json:"action_card,omitempty"`
+}
+
+type TextMsg struct {
+	Content string `json:"content"` // 消息内容,建议500字符以内。
+}
+
+type ImageMsg struct {
+	MediaId string `json:"media_id"` // 媒体文件mediaid,建议宽600像素 x 400像素,宽高比3 : 2
+}
+
+type VoiceMsg struct {
+	MediaId  string `json:"media_id"` // 媒体文件ID。
+	Duration string `json:"duration"` // 正整数,小于60,表示音频时长。
+}
+
+type FileMsg struct {
+	MediaId string `json:"media_id"` // 媒体文件ID,引用的媒体文件最大10MB。
+}
+
+type LinkMsg struct {
+	Title      string `json:"title"` // 消息标题,建议100字符以内。
+	Text       string `json:"text"`  // 消息描述,建议500字符以内。
+	PicUrl     string `json:"picUrl"`
+	MessageUrl string `json:"messageUrl"` // 消息点击链接地址,当发送消息为小程序时支持小程序跳转链接
+}
+
+type OAMsg struct {
+	MessageUrl   string   `json:"message_url"`    // 消息点击链接地址,当发送消息为小程序时支持小程序跳转链接。
+	PcMessageUrl string   `json:"pc_message_url"` // PC端点击消息时跳转到的地址。
+	Head         struct { // 消息头部内容。
+		Text    string `json:"text"`    // 消息的头部标题。
+		Bgcolor string `json:"bgcolor"` // 消息头部的背景颜色。 长度限制为8个英文字符,其中前2为表示透明度,后6位表示颜色值。不要添加0x。
+	} `json:"head"`
+	StatusBar struct { // 消息状态栏,只支持接收者的userid列表,userid最多不能超过5个人。
+		StatusBg    string `json:"status_bg"`    // 状态栏背景色,默认为黑色,推荐0xFF加六位颜色值。
+		StatusValue string `json:"status_value"` // 状态栏文案。
+	} `json:"status_bar"`
+	Body struct {
+		Title     string     `json:"title"`      // 消息体的标题,建议50个字符以内。
+		Content   string     `json:"content"`    // 消息体的内容,最多显示3行。
+		Image     string     `json:"image"`      // 消息体中的图片,支持图片资源@mediaId。建议宽600像素 x 400像素,宽高比3 : 2。
+		FileCount string     `json:"file_count"` // 自定义的附件数目。此数字仅供显示,钉钉不作验证。
+		Form      OABodyForm `json:"form"`       // 消息体的表单,最多显示6个,超过会被隐藏。
+		Author    string     `json:"author"`     // 自定义的作者名字。
+		Rich      struct {   // 单行富文本信息。
+			Num  string `json:"num"`  // 单行富文本信息的数目。
+			Unit string `json:"unit"` // 单行富文本信息的单位。
+		} `json:"rich"`
+	} `json:"body"`
+}
+type OABodyForm struct {
+	Key   string `json:"key"`
+	Value string `json:"value"`
+}
+
+type MarkdownMsg struct {
+	Title string `json:"title"` // 首屏会话透出的展示内容。
+	Text  string `json:"text"`  // markdown格式的消息,最大不超过5000字符。
+}
+
+type ActionCardMsg struct {
+	Title          string        `json:"title"`           // 透出到会话列表和通知的文案。
+	Markdown       string        `json:"markdown"`        // 消息内容,支持markdown,语法参考标准markdown语法。建议1000个字符以内。
+	SingleTitle    string        `json:"single_title"`    // 使用整体跳转ActionCard样式时的标题。必须与single_url同时设置,最长20个字符。
+	SingleUrl      string        `json:"single_url"`      // 消息点击链接地址,当发送消息为小程序时支持小程序跳转链接,最长500个字符。
+	BtnOrientation string        `json:"btn_orientation"` // 使用独立跳转ActionCard样式时的按钮排列方式: 0:竖直排列 1:横向排列 必须与btn_json_list同时设置。
+	BtnJsonList    []BtnJsonList `json:"btn_json_list"`   // 使用独立跳转ActionCard样式时的按钮列表;必须与btn_orientation同时设置,且长度不超过1000字符。
+}
+type BtnJsonList struct {
+	Title     string `json:"title"`      // 使用独立跳转ActionCard样式时的按钮的标题,最长20个字符。
+	ActionUrl string `json:"action_url"` // 使用独立跳转ActionCard样式时的跳转链接,最长700个字符。
+}

+ 1 - 1
opms_parent/app/service/plat/plat_task_cron.go

@@ -32,7 +32,7 @@ type taskCron struct {
 
 // Run 督办定时任务逻辑
 func (c taskCron) Run() {
-	tenant := g.Config().GetString("setting.task-cron-tenant")
+	tenant := g.Config().GetString("micro_srv.tenant")
 	if tenant == "" {
 		glog.Error("督办定时任务租户码未设置,请前往配置")
 		return

+ 339 - 0
opms_parent/app/service/proj/business_cron.go

@@ -0,0 +1,339 @@
+package proj
+
+import (
+	projDao "dashoo.cn/micro/app/dao/proj"
+	"dashoo.cn/micro/app/service"
+	"database/sql"
+	"fmt"
+	"github.com/gogf/gf/container/gset"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gcron"
+	"github.com/gogf/gf/os/glog"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/text/gstr"
+	"github.com/gogf/gf/util/gconv"
+	"strings"
+)
+
+// 初始化,创建每10分钟执行一次的定时任务
+func init() {
+	// 定时任务
+	spec := "0 0 9 * * *" // 每天凌晨9点执行
+	gcron.AddSingleton(spec, businessFollowRun)
+}
+
+// businessFollowRun 项目跟进超时任务逻辑
+func businessFollowRun() {
+	tenant := g.Config().GetString("micro_srv.tenant")
+	if tenant == "" {
+		glog.Error("定时任务租户码未设置,请前往配置")
+		return
+	}
+	// 从配置中获取消息提醒设置
+	configs, err := g.DB(tenant).Model("sys_config").Where("config_key IN ('SalesDirector','SalesAssociate')").FindAll()
+	if err != nil && err != sql.ErrNoRows {
+		glog.Error(err)
+		return
+	}
+	// 销售总监用户Id
+	salesDirector := []string{}
+	// 销售助理用户Id
+	salesAssociate := []string{}
+	for _, config := range configs {
+		if config["config_key"].String() == "SalesDirector" {
+			salesDirector = strings.Split(config["config_value"].String(), ",")
+		} else if config["config_key"].String() == "SalesAssociate" {
+			salesAssociate = strings.Split(config["config_value"].String(), ",")
+		}
+	}
+
+	businessFollowOverdueSalesAssociate(tenant, []string{"13"})
+	if len(salesDirector) > 0 {
+		go businessFollowOverdueSalesDirector(tenant, salesDirector)
+	}
+	if len(salesAssociate) > 0 {
+		go businessFollowOverdueSalesDirector(tenant, salesAssociate)
+	}
+	go businessFollowOverdueSalesEngineer(tenant)
+
+}
+
+// 超期3天提醒销售总监
+func businessFollowOverdueSalesDirector(tenant string, userIds []string) {
+	now := gtime.Now().StartOfDay()
+	LastWeekDay := now.AddDate(0, 0, -10)
+	LastTwoWeekDay := now.AddDate(0, 0, -17)
+	LastMonthDay := now.AddDate(0, -1, -3)
+
+	businessACount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).Count()
+	if err != nil {
+		g.Log().Error("获取A类超期3天未跟进项目", err)
+	}
+	businessBCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).Count()
+	if err != nil {
+		g.Log().Error("获取B类超期3天未跟进项目", err)
+	}
+	businessCCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).Count()
+	if err != nil {
+		g.Log().Error("获取C类超期3天未跟进项目", err)
+	}
+	var msg string
+	if businessACount > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进A类项目%v个,", businessACount)
+	}
+	if businessBCount > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进B类项目%v个,", businessBCount)
+	}
+	if businessCCount > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进C类项目%v个,", businessCCount)
+	}
+	if msg != "" {
+		businessNotifyMessage(userIds, gstr.TrimRightStr(msg, ","))
+	}
+}
+
+// 超期1天后,超期3天 提醒销售助理
+func businessFollowOverdueSalesAssociate(tenant string, userIds []string) {
+	now := gtime.Now().StartOfDay()
+	LastWeekDay := now.AddDate(0, 0, -8)
+	LastTwoWeekDay := now.AddDate(0, 0, -15)
+	LastMonthDay := now.AddDate(0, -1, -1)
+
+	LastWeekTridDay := now.AddDate(0, 0, -10)
+	LastTwoWeekTridDay := now.AddDate(0, 0, -17)
+	LastMonthTridDay := now.AddDate(0, -1, -3)
+
+	businessATridCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).Count()
+	if err != nil {
+		g.Log().Error("获取A类超期3天未跟进项目", err)
+	}
+	businessBTridCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).Count()
+	if err != nil {
+		g.Log().Error("获取B类超期3天未跟进项目", err)
+	}
+	businessCTridCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).Count()
+	if err != nil {
+		g.Log().Error("获取C类超期3天未跟进项目", err)
+	}
+
+	businessACount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekDay).WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekTridDay).
+		Count()
+	if err != nil {
+		g.Log().Error("获取A类超期1天未跟进项目", err)
+	}
+	businessBCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekDay).WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekTridDay).
+		Count()
+	if err != nil {
+		g.Log().Error("获取B类超期1天未跟进项目", err)
+	}
+	businessCCount, err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthDay).WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthTridDay).
+		Count()
+	if err != nil {
+		g.Log().Error("获取C类超期1天未跟进项目", err)
+	}
+	var msg string
+	if businessATridCount > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进A类项目%v个,", businessATridCount)
+	}
+	if businessBTridCount > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进B类项目%v个,", businessBTridCount)
+	}
+	if businessCTridCount > 0 {
+		msg += fmt.Sprintf("您有超期3天未跟进C类项目%v个,", businessCTridCount)
+	}
+	if businessACount > 0 {
+		msg += fmt.Sprintf("您有超期1天未跟进A类项目%v个,", businessACount)
+	}
+	if businessBCount > 0 {
+		msg += fmt.Sprintf("您有超期1天未跟进B类项目%v个,", businessBCount)
+	}
+	if businessCCount > 0 {
+		msg += fmt.Sprintf("您有超期1天未跟进C类项目%v个,", businessCCount)
+	}
+	if msg != "" {
+		businessNotifyMessage(userIds, gstr.TrimRightStr(msg, ","))
+	}
+}
+
+type FollowOverdue struct {
+	SaleId int64 `json:"saleId"`
+	Count  int64 `json:"count"`
+}
+
+// 在到达超期时间前3天进行提醒、当天进行提醒,这两次提醒只提醒销售工程师, 超期1天后提醒
+func businessFollowOverdueSalesEngineer(tenant string) {
+	now := gtime.Now().StartOfDay()
+	// 超期一天
+	LastWeekOneDay := now.AddDate(0, 0, -8)
+	LastTwoWeekOneDay := now.AddDate(0, 0, -15)
+	LastMonthOneDay := now.AddDate(0, -1, -1)
+	// 超期当天
+	LastWeekCurrentDay := now.AddDate(0, 0, -7)
+	LastTwoWeekCurrentDay := now.AddDate(0, 0, -14)
+	LastMonthCurrentDay := now.AddDate(0, -1, 0)
+	//  在到达超期时间前3天进行提醒
+	LastWeekBeforeTridDay := now.AddDate(0, 0, -4)
+	LastTwoWeekBeforeTridDay := now.AddDate(0, 0, -11)
+	LastMonthBeforeTridDay := now.AddDate(0, -1, 3)
+
+	LastWeekOneCount, LastTwoWeekOneCount, LastMonthOneCount := make([]FollowOverdue, 0), make([]FollowOverdue, 0), make([]FollowOverdue, 0)
+	LastWeekCurrentCount, LastTwoWeekCurrentCount, LastMonthCurrentCount := make([]FollowOverdue, 0), make([]FollowOverdue, 0), make([]FollowOverdue, 0)
+	LastWeekBeforeTridCount, LastTwoWeekBeforeTridCount, LastMonthBeforeTridCount := make([]FollowOverdue, 0), make([]FollowOverdue, 0), make([]FollowOverdue, 0)
+	err := projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekOneDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastWeekOneCount)
+	if err != nil {
+		g.Log().Error("获取A类超期一天未跟进项目", err)
+	}
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekOneDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastTwoWeekOneCount)
+	if err != nil {
+		g.Log().Error("获取B类超期一天未跟进项目", err)
+	}
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthOneDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastMonthOneCount)
+	if err != nil {
+		g.Log().Error("获取C类超期一天未跟进项目", err)
+	}
+
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekCurrentDay).
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekOneDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastWeekCurrentCount)
+	if err != nil {
+		g.Log().Error("获取A类超期当天未跟进项目", err)
+	}
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekCurrentDay).
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekOneDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastTwoWeekCurrentCount)
+	if err != nil {
+		g.Log().Error("获取B类超期当天未跟进项目", err)
+	}
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthCurrentDay).
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthOneDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastMonthCurrentCount)
+	if err != nil {
+		g.Log().Error("获取C类超期当天未跟进项目", err)
+	}
+
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusA).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekBeforeTridDay).
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastWeekCurrentDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastWeekBeforeTridCount)
+	if err != nil {
+		g.Log().Error("获取A类超期当天未跟进项目", err)
+	}
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusB).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekBeforeTridDay).
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastTwoWeekCurrentDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastTwoWeekBeforeTridCount)
+	if err != nil {
+		g.Log().Error("获取B类超期当天未跟进项目", err)
+	}
+	err = projDao.NewProjBusinessDao(tenant).Where(projDao.ProjBusiness.C.NboType, StatusC).
+		WhereLTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthBeforeTridDay).
+		WhereGTE(projDao.ProjBusiness.C.FinalFollowTime, LastMonthCurrentDay).
+		Fields("sale_id, count(id) as count").Group("sale_id").OrderAsc("sale_id").Scan(&LastMonthBeforeTridCount)
+	if err != nil {
+		g.Log().Error("获取C类超期当天未跟进项目", err)
+	}
+
+	allSaleIds := gset.NewStrSet(true)
+	saleIds, lastWeekOneCountMap := handleSalesEngineerData(LastWeekOneCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	saleIds, LastTwoWeekOneCountMap := handleSalesEngineerData(LastTwoWeekOneCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	saleIds, LastMonthOneCountMap := handleSalesEngineerData(LastMonthOneCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	saleIds, LastWeekCurrentCountMap := handleSalesEngineerData(LastWeekCurrentCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	saleIds, LastTwoWeekCurrentCountMap := handleSalesEngineerData(LastTwoWeekCurrentCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	saleIds, LastMonthCurrentCountMap := handleSalesEngineerData(LastMonthCurrentCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	saleIds, LastWeekBeforeTridCountMap := handleSalesEngineerData(LastWeekBeforeTridCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	saleIds, LastTwoWeekBeforeTridCountMap := handleSalesEngineerData(LastTwoWeekBeforeTridCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	saleIds, LastMonthBeforeTridCountMap := handleSalesEngineerData(LastMonthBeforeTridCount)
+	allSaleIds = allSaleIds.Union(saleIds)
+	for _, saleId := range allSaleIds.Slice() {
+		var msg string
+		if lastWeekOneCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您有超期1天未跟进A类项目%v个,", lastWeekOneCountMap[saleId])
+		}
+		if LastTwoWeekOneCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您有超期1天未跟进B类项目%v个,", LastTwoWeekOneCountMap[saleId])
+		}
+		if LastMonthOneCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您有超期1天未跟进C类项目%v个,", LastMonthOneCountMap[saleId])
+		}
+		if LastWeekCurrentCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您今天有超期未跟进A类项目%v个,", LastWeekCurrentCountMap[saleId])
+		}
+		if LastTwoWeekCurrentCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您今天有超期未跟进B类项目%v个,", LastTwoWeekCurrentCountMap[saleId])
+		}
+		if LastMonthCurrentCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您今天有超期未跟进C类项目%v个,", LastMonthCurrentCountMap[saleId])
+		}
+		if LastWeekBeforeTridCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您3天后有超期未跟进A类项目%v个,", LastWeekBeforeTridCountMap[saleId])
+		}
+		if LastTwoWeekBeforeTridCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您3天后有超期未跟进B类项目%v个,", LastTwoWeekBeforeTridCountMap[saleId])
+		}
+		if LastMonthBeforeTridCountMap[saleId] != "" {
+			msg += fmt.Sprintf("您3天后有超期未跟进C类项目%v个,", LastMonthBeforeTridCountMap[saleId])
+		}
+		if msg != "" {
+			businessNotifyMessage([]string{saleId}, gstr.TrimRightStr(msg, ","))
+		}
+	}
+}
+
+func handleSalesEngineerData(countData []FollowOverdue) (saleIds *gset.StrSet, data g.MapStrStr) {
+	saleIds = gset.NewStrSet(true)
+	data = make(g.MapStrStr)
+	for _, v := range countData {
+		saleId := gconv.String(v.SaleId)
+		saleIds.Add(saleId)
+		data[saleId] = gconv.String(v.Count)
+	}
+	return
+}
+
+// 项目跟进的消息通知
+func businessNotifyMessage(userIds []string, message string) {
+	ids := strings.Join(userIds, ",")
+	// 调用统一的消息通知方式
+	notifyMessage(ids, message)
+}
+
+// notifyMessage 发送消息通知
+func notifyMessage(ids, message string) {
+	msg := g.MapStrStr{
+		"msgTitle":    "项目跟进超期提醒",
+		"msgContent":  message,
+		"msgType":     "20",
+		"recvUserIds": ids,
+		"msgStatus":   "10",
+		"sendType":    "10,20,30,40",
+	}
+	if err := service.CreateSystemMessage(msg); err != nil {
+		glog.Error("消息提醒异常:", err)
+	}
+}

+ 1 - 1
opms_parent/config/config.toml

@@ -7,7 +7,6 @@
     srv-name = "dashoo.opms.parent-0.0.1"
     env = "dev"
     swagger = true
-    task-cron-tenant = "default"
 
 # 微服务注册中心配置
 [service_registry]
@@ -31,6 +30,7 @@
 
 [micro_srv]
     auth = "dashoo.opms.admin-0.0.1,127.0.0.1:8888"
+    tenant = "default"
 
 
 # 钉钉配置

+ 1 - 0
opms_parent/go.sum

@@ -416,6 +416,7 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l
 github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
 github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
+github.com/sijms/go-ora/v2 v2.5.31/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/smallnest/quick v0.0.0-20220703133648-f13409fa6c67 h1:eIa+/FyZCItCSwE1tjTEzYYHEk+5vtAw3jCW8DlnP3g=