Parcourir la source

feature(消息): 系统创建消息

ZZH-wl il y a 2 ans
Parent
commit
2ea457c191

+ 325 - 3
doc/订单全流程管理平台.pdma.json

@@ -2,9 +2,9 @@
   "name": "订单全流程管理平台",
   "describe": "订单全流程管理平台",
   "avatar": "",
-  "version": "4.3.0",
+  "version": "4.5.0",
   "createdTime": "2023-1-6 11:43:05",
-  "updatedTime": "2023-3-15 09:46:54",
+  "updatedTime": "2023-3-22 14:50:00",
   "dbConns": [],
   "profile": {
     "default": {
@@ -11975,6 +11975,23 @@
           "domain": "9092C4E0-1A54-4859-ABBB-5B62DBC27573",
           "id": "912A5A49-42C7-4042-B11C-57ABD5851D6E"
         },
+        {
+          "defKey": "send_type",
+          "defName": "发送方式 默认10 websocket推送",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": false,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "refDict": "",
+          "extProps": {},
+          "domain": "9092C4E0-1A54-4859-ABBB-5B62DBC27573",
+          "id": "14ACF79F-B89F-4138-AA2B-D7ACC5B57763"
+        },
         {
           "defKey": "opn_url",
           "defName": "操作链接",
@@ -17033,6 +17050,310 @@
       ],
       "correlations": [],
       "indexes": []
+    },
+    {
+      "id": "76427041-DE47-4EE2-AFA0-594B15E1E2DB",
+      "env": {
+        "base": {
+          "nameSpace": "",
+          "codeRoot": ""
+        }
+      },
+      "defKey": "plat_tablecols_config",
+      "defName": "前端表格显示列配置",
+      "comment": "",
+      "properties": {
+        "partitioned by": "(date string)",
+        "row format delimited": "",
+        "fields terminated by ','": "",
+        "collection items terminated by '-'": "",
+        "map keys terminated by ':'": "",
+        "store as textfile;": ""
+      },
+      "nameTemplate": "{defKey}[{defName}]",
+      "notes": {},
+      "headers": [
+        {
+          "refKey": "hideInGraph",
+          "hideInGraph": true
+        },
+        {
+          "refKey": "defKey",
+          "hideInGraph": false
+        },
+        {
+          "refKey": "defName",
+          "hideInGraph": false
+        },
+        {
+          "refKey": "primaryKey",
+          "hideInGraph": false
+        },
+        {
+          "refKey": "notNull",
+          "hideInGraph": true
+        },
+        {
+          "refKey": "autoIncrement",
+          "hideInGraph": true
+        },
+        {
+          "refKey": "domain",
+          "hideInGraph": true
+        },
+        {
+          "refKey": "type",
+          "hideInGraph": false
+        },
+        {
+          "refKey": "len",
+          "hideInGraph": false
+        },
+        {
+          "refKey": "scale",
+          "hideInGraph": false
+        },
+        {
+          "refKey": "comment",
+          "hideInGraph": true
+        },
+        {
+          "refKey": "refDict",
+          "hideInGraph": true
+        },
+        {
+          "refKey": "defaultValue",
+          "hideInGraph": true
+        },
+        {
+          "refKey": "isStandard",
+          "hideInGraph": false
+        },
+        {
+          "refKey": "uiHint",
+          "hideInGraph": true
+        },
+        {
+          "refKey": "extProps",
+          "hideInGraph": true
+        }
+      ],
+      "fields": [
+        {
+          "defKey": "id",
+          "defName": "主键",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": true,
+          "notNull": true,
+          "autoIncrement": true,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "refDict": "",
+          "extProps": {},
+          "domain": "16120F75-6AA7-4483-868D-F07F511BB081",
+          "id": "E678C627-87E5-487C-8C21-7D1405C77120"
+        },
+        {
+          "defKey": "user_id",
+          "defName": "关联用户",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": true,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "refDict": "",
+          "extProps": {},
+          "domain": "16120F75-6AA7-4483-868D-F07F511BB081",
+          "id": "ABAF48F3-131C-4313-981C-3267967F4E8A"
+        },
+        {
+          "defKey": "table",
+          "defName": "表格",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": true,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "refDict": "",
+          "extProps": {},
+          "domain": "F22E7B6D-ADF0-4D4A-84EF-B7B9C0532DF2",
+          "id": "9ED1741D-E9F8-49CF-BCDB-80B98B20B898"
+        },
+        {
+          "defKey": "columns",
+          "defName": "显示列",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": true,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "refDict": "",
+          "extProps": {},
+          "domain": "3E948CEC-3070-472C-AF92-F3CA11EC9D15",
+          "id": "62B0E316-1DA7-495E-AB41-928C79A23C97"
+        },
+        {
+          "defKey": "remark",
+          "defName": "备注",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": false,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "3E948CEC-3070-472C-AF92-F3CA11EC9D15",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "E478A56F-8EDB-49EC-9488-CDC4AC4F0591"
+        },
+        {
+          "defKey": "created_by",
+          "defName": "创建者",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": true,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "16120F75-6AA7-4483-868D-F07F511BB081",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "1238EB46-840A-4297-9D82-385350D83D57"
+        },
+        {
+          "defKey": "created_name",
+          "defName": "创建人",
+          "comment": "",
+          "type": "VARCHAR",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": true,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "54611CCC-CA4B-42E1-9F32-4944C85B85A6",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "3399DA14-0372-4147-BBAE-25323A2FD084"
+        },
+        {
+          "defKey": "created_time",
+          "defName": "创建时间",
+          "comment": "",
+          "type": "DATETIME",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": true,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "7CFFA0D3-6A93-4DDC-BC10-DF21211064DC",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "E48D0EA6-F8EE-44E9-9A13-34236BEDC67F"
+        },
+        {
+          "defKey": "updated_by",
+          "defName": "更新者",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": false,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "16120F75-6AA7-4483-868D-F07F511BB081",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "0F817976-15E7-4C74-BE24-4A4542A0CD62"
+        },
+        {
+          "defKey": "updated_name",
+          "defName": "更新人",
+          "comment": "",
+          "type": "VARCHAR",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": false,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "54611CCC-CA4B-42E1-9F32-4944C85B85A6",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "CD3DD756-101C-49A5-BFCD-E4B8E9B7401A"
+        },
+        {
+          "defKey": "updated_time",
+          "defName": "更新时间",
+          "comment": "",
+          "type": "DATETIME",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": false,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "7CFFA0D3-6A93-4DDC-BC10-DF21211064DC",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "A872B2CD-D10F-490C-9B0A-C5BA8832526F"
+        },
+        {
+          "defKey": "deleted_time",
+          "defName": "删除时间",
+          "comment": "",
+          "type": "DATETIME",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": false,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "7CFFA0D3-6A93-4DDC-BC10-DF21211064DC",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "357A8F3F-5A53-48A9-A36D-7CE43CAB9741"
+        }
+      ],
+      "correlations": [],
+      "indexes": []
     }
   ],
   "views": [],
@@ -17487,7 +17808,8 @@
         "9DEB5CFB-923E-4C90-A6BC-602093A99B3A",
         "1213FF4D-0686-4882-A33A-8E48C7D239CA",
         "C9C0EFF0-7A8A-44AC-9504-1EC2730C47D0",
-        "F4A7EC79-1B42-4530-B710-DE7C73AA68C8"
+        "F4A7EC79-1B42-4530-B710-DE7C73AA68C8",
+        "76427041-DE47-4EE2-AFA0-594B15E1E2DB"
       ],
       "refViews": [],
       "refDiagrams": [],

+ 58 - 18
opms_admin/app/dao/internal/sys_message.go

@@ -7,6 +7,7 @@ package internal
 import (
 	"context"
 	"database/sql"
+	"github.com/gogf/gf/container/garray"
 	"github.com/gogf/gf/database/gdb"
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/frame/gmvc"
@@ -34,6 +35,7 @@ type sysMessageColumns struct {
 	MsgType     string // 消息类别(10公告20消息30审批)
 	MsgStatus   string // 消息状态(10正常20关闭)
 	RecvUserIds string // 接收用户
+	SendType    string // 发送方式 默认10 websocket推送
 	OpnUrl      string // 操作链接
 	IsRead      string // 是否已读(10否20是)
 	ReadTime    string // 已读时间
@@ -60,6 +62,7 @@ var (
 			MsgType:     "msg_type",
 			MsgStatus:   "msg_status",
 			RecvUserIds: "recv_user_ids",
+			SendType:    "send_type",
 			OpnUrl:      "opn_url",
 			IsRead:      "is_read",
 			ReadTime:    "read_time",
@@ -88,6 +91,7 @@ func NewSysMessageDao(tenant string) SysMessageDao {
 			MsgType:     "msg_type",
 			MsgStatus:   "msg_status",
 			RecvUserIds: "recv_user_ids",
+			SendType:    "send_type",
 			OpnUrl:      "opn_url",
 			IsRead:      "is_read",
 			ReadTime:    "read_time",
@@ -699,36 +703,72 @@ func (d *SysMessageDao) Unscoped() *SysMessageDao {
 }
 
 // DataScope enables the DataScope feature.
-func (d *SysMessageDao) DataScope(ctx context.Context, userCol ...string) *SysMessageDao {
+func (d *SysMessageDao) DataScope(ctx context.Context, args ...interface{}) *SysMessageDao {
 	cs := ctx.Value("contextService")
-	dataScope := gconv.Map(cs)["dataScope"].(g.Map)
+	dataScope := gconv.Map(gconv.String(gconv.Map(cs)["dataScope"]))
 	if dataScope != nil {
+		var specialFlag bool
+		var tableAs string
+		if d.TableAs != "" && len(args) <= 1 {
+			tableAs = d.TableAs + "."
+		}
+		if len(args) > 1 {
+			specialFlag = true
+			if val, ok := args[1].(string); ok {
+				tableAs = val + "."
+			}
+		}
 		userIds, ok := dataScope["userIds"]
 		delete(dataScope, "userIds")
-		var columns []string
-		var values []interface{}
+		var orColumns []string
+		var orValues []interface{}
 		if ok && userIds != "-1" {
 			column := "created_by"
-			if len(userCol) == 1 {
-				column = userCol[0]
+			if len(args) > 0 {
+				column = args[0].(string)
 			}
-			dataScope[column] = userIds
-			if d.Table == "sys_user" {
-				dataScope["id"] = userIds
+			if ok, _ := d.M.HasField(column); ok || specialFlag {
+				orColumns = append(orColumns, tableAs+column+" IN (?) ")
+				orValues = append(orValues, userIds)
 			}
 		}
-		tableAs := d.TableAs
-		if d.TableAs != "" {
-			tableAs += "."
+		// 销售工程师判断
+		var salesEngineerFlag bool
+		if roles, ok := dataScope["roles"]; ok {
+			delete(dataScope, "roles")
+			delete(dataScope, "posts")
+			arr := garray.NewArrayFrom(roles.([]interface{}), true)
+			if arr.Len() == 1 && arr.Contains("SalesEngineer") {
+				salesEngineerFlag = true
+			}
 		}
-		for k, v := range dataScope {
-			if ok, _ := d.M.HasField(k); ok {
-				columns = append(columns, tableAs+k+" IN (?) ")
-				values = append(values, v)
+		// 非销售工程师权限加成
+		if !salesEngineerFlag {
+			bigColumns := "is_big"
+			if ok, _ := d.M.HasField("is_big"); ok || specialFlag {
+				if val, ok := dataScope[bigColumns]; ok && val != "" {
+					orColumns = append(orColumns, tableAs+bigColumns+" = ? ")
+					orValues = append(orValues, val)
+				}
+				delete(dataScope, bigColumns)
+			}
+			var andColumns []string
+			var andValues []interface{}
+			for k, v := range dataScope {
+				if ok, _ := d.M.HasField(k); ok || specialFlag {
+					andColumns = append(andColumns, tableAs+k+" IN (?) ")
+					andValues = append(andValues, v)
+				}
+			}
+			if len(andColumns) > 0 {
+				andWhereSql := strings.Join(andColumns, " AND ")
+				orColumns = append(orColumns, "("+andWhereSql+")")
+				orValues = append(orValues, andValues...)
 			}
 		}
-		whereSql := strings.Join(columns, " OR ")
-		return &SysMessageDao{M: d.M.Where(whereSql, values).Ctx(ctx), Table: d.Table, TableAs: d.TableAs}
+
+		whereSql := strings.Join(orColumns, " OR ")
+		return &SysMessageDao{M: d.M.Where(whereSql, orValues...).Ctx(ctx), Table: d.Table, TableAs: d.TableAs}
 	}
 	return d
 }

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

@@ -4,7 +4,9 @@ import (
 	"context"
 	"dashoo.cn/common_definition/comm_def"
 	"dashoo.cn/opms_libary/myerrors"
+	"dashoo.cn/opms_libary/request"
 	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/util/gconv"
 	"github.com/gogf/gf/util/gvalid"
 
 	"dashoo.cn/micro/app/model"
@@ -106,3 +108,28 @@ func (h *MessageHandler) AllRead(ctx context.Context, null, rsp *comm_def.Common
 	err = msgService.AllRead()
 	return err
 }
+
+type SystemMessageHandler struct{}
+
+// 系统内部创建使用
+func (h *SystemMessageHandler) Create(ctx context.Context, req g.MapStrStr, rsp *comm_def.CommonMsg) error {
+	reqData := new(model.CreateSysMessageReq)
+	if err := gconv.Struct(req, reqData); err != nil {
+		return err
+	}
+	// 检查请求参数
+	if err := gvalid.CheckStruct(ctx, reqData, nil); err != nil {
+		return err
+	}
+	msgService, err := service.NewMessageService(ctx)
+	if err != nil {
+		return err
+	}
+	msgService.CxtUser = &request.UserInfo{
+		Id:       0,
+		UserName: "system",
+		NickName: "系统自动创建",
+	}
+	err = msgService.Create(reqData)
+	return err
+}

+ 1 - 0
opms_admin/app/model/internal/sys_message.go

@@ -16,6 +16,7 @@ type SysMessage struct {
 	MsgType     string      `orm:"msg_type"      json:"msgType"`     // 消息类别(10公告20消息30审批)
 	MsgStatus   string      `orm:"msg_status"    json:"msgStatus"`   // 消息状态(10正常20关闭)
 	RecvUserIds string      `orm:"recv_user_ids" json:"recvUserIds"` // 接收用户
+	SendType    string      `orm:"send_type"     json:"sendType"`    // 发送方式 默认10 websocket推送
 	OpnUrl      string      `orm:"opn_url"       json:"opnUrl"`      // 操作链接
 	IsRead      string      `orm:"is_read"       json:"isRead"`      // 是否已读(10否20是)
 	ReadTime    *gtime.Time `orm:"read_time"     json:"readTime"`    // 已读时间

+ 9 - 9
opms_admin/app/model/sys_message.go

@@ -22,15 +22,15 @@ type SysMessageSearchReq struct {
 }
 
 type CreateSysMessageReq struct {
-	MsgTitle    string      `json:"msgTitle"      v:"required#消息标题不能为空"`                             // 消息标题
-	MsgContent  string      `json:"msgContent"    v:"required#消息内容不能为空"`                             // 消息内容
-	MsgType     string      `json:"msgType"       v:"required|in:10,20,30#消息类别不能为空|消息类别只能为10、20、30"` // 消息类别(10公告20消息30审批)
-	MsgStatus   string      `json:"msgStatus"     v:"required|in:10,20#消息状态不能为空|消息状态只能为10或20"`       // 消息状态(10正常20关闭)
-	RecvUserIds string      `json:"recvUserIds"`                                                     // 接收用户
-	OpnUrl      string      `json:"opnUrl"`                                                          // 操作链接
-	IsRead      string      `json:"isRead"`                                                          // 是否已读(10否20是)
-	ReadTime    *gtime.Time `json:"readTime"`                                                        // 已读时间
-	Remark      string      `json:"remark"`                                                          // 备注
+	MsgTitle    string `json:"msgTitle"      v:"required#消息标题不能为空"`                             // 消息标题
+	MsgContent  string `json:"msgContent"    v:"required#消息内容不能为空"`                             // 消息内容
+	MsgType     string `json:"msgType"       v:"required|in:10,20,30#消息类别不能为空|消息类别只能为10、20、30"` // 消息类别(10公告20消息30审批)
+	MsgStatus   string `json:"msgStatus"     v:"required|in:10,20#消息状态不能为空|消息状态只能为10或20"`       // 消息状态(10正常20关闭)
+	RecvUserIds string `json:"recvUserIds"`                                                     // 接收用户
+	SendType    string `json:"sendType"`                                                        // 发送方式
+	OpnUrl      string `json:"opnUrl"`                                                          // 操作链接
+	IsRead      string `json:"isRead"`                                                          // 是否已读(10否20是)
+	Remark      string `json:"remark"`                                                          // 备注
 }
 
 type UpdateSysMessageReq struct {

+ 24 - 4
opms_admin/app/service/sys_message.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"dashoo.cn/micro/app/dao"
 	"dashoo.cn/micro/app/model"
+	"fmt"
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/os/gtime"
 	"github.com/gogf/gf/util/gconv"
@@ -87,6 +88,9 @@ func (s *MessageService) Create(req *model.CreateSysMessageReq) (err error) {
 	if err := gconv.Struct(req, data); err != nil {
 		return err
 	}
+	if data.SendType == "" {
+		data.SendType = "10"
+	}
 	SetCreatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
 	lastId, err := s.Dao.InsertAndGetId(data)
 	if err != nil {
@@ -94,17 +98,33 @@ func (s *MessageService) Create(req *model.CreateSysMessageReq) (err error) {
 	}
 	data.Id = int(lastId)
 	userIds := strings.Split(data.RecvUserIds, ",")
-	for _, uid := range userIds {
-		if uid == "" {
+	for _, userId := range userIds {
+		if userId == "" {
 			continue
 		}
 		log := g.Map{
 			s.logDao.C.MsgId:  lastId,
-			s.logDao.C.UserId: uid,
+			s.logDao.C.UserId: userId,
 			s.logDao.C.IsRead: "10",
 		}
 		s.logDao.Data(log).Insert()
-		MessageNotify(uid, *data)
+		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")
+
+			}
+		}
 	}
 	return
 }

+ 43 - 23
opms_admin/app/service/websocket_manager.go

@@ -4,6 +4,7 @@ import (
 	"dashoo.cn/micro/app/model"
 	"encoding/json"
 	"fmt"
+	"github.com/gogf/gf/container/gset"
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/net/ghttp"
 	"github.com/gorilla/websocket"
@@ -57,16 +58,16 @@ const (
 
 // 客户端管理
 type ClientManager struct {
-	Clients map[string]*Client // 保存连接
-	//Accounts map[string][]string // 账号和连接关系,map的key是账号id即:AccountId,这里主要考虑到一个账号多个连接 // 本系统无多连接情况
-	mu *sync.Mutex
+	Clients  map[string]*Client  // 保存连接
+	Accounts map[string][]string // 账号和连接关系,map的key是账号id即:AccountId,这里主要考虑到一个账号多个连接 // 本系统无多连接情况
+	mu       *sync.Mutex
 }
 
 // 定义一个管理Manager
 var Manager = ClientManager{
-	Clients: make(map[string]*Client), // 参与连接的用户,出于性能的考虑,需要设置最大连接数
-	//Accounts: make(map[string][]string), // 账号和连接关系 // 本系统无多连接情况
-	mu: new(sync.Mutex),
+	Clients:  make(map[string]*Client),  // 参与连接的用户,出于性能的考虑,需要设置最大连接数
+	Accounts: make(map[string][]string), // 账号和连接关系 // 本系统无多连接情况
+	mu:       new(sync.Mutex),
 }
 
 var (
@@ -123,6 +124,9 @@ func accountBind(c *Client) {
 	Manager.mu.Lock()
 	// 如果不存在链接,加入到连接;若已存在,则替换
 	Manager.Clients[c.ID] = c
+	if !gset.NewStrSetFrom(Manager.Accounts[c.AccountId]).Contains(c.ID) {
+		Manager.Accounts[c.AccountId] = append(Manager.Accounts[c.AccountId], c.ID)
+	}
 	Manager.mu.Unlock()
 }
 
@@ -134,6 +138,11 @@ func accountUnBind(c *Client) {
 		g.Log("websocket").Error(err)
 	}
 	delete(Manager.Clients, c.ID)
+	accountSet := gset.NewStrSetFrom(Manager.Accounts[c.AccountId])
+	if accountSet.Contains(c.ID) {
+		accountSet.Remove(c.ID)
+		Manager.Accounts[c.AccountId] = accountSet.Slice()
+	}
 	Manager.mu.Unlock()
 }
 
@@ -161,9 +170,9 @@ func heartbeat() {
 }
 
 // 根据账号获取连接
-func GetClient(uuid string) *Client {
+func GetClient(userId string) *Client {
 	Manager.mu.Lock()
-	if c, ok := Manager.Clients[uuid]; ok {
+	if c, ok := Manager.Clients[userId]; ok {
 		Manager.mu.Unlock()
 		return c
 	}
@@ -171,6 +180,15 @@ func GetClient(uuid string) *Client {
 	Manager.mu.Unlock()
 	return nil
 }
+func GetAccountClient(userId string) []string {
+	Manager.mu.Lock()
+	if c, ok := Manager.Accounts[userId]; ok {
+		Manager.mu.Unlock()
+		return c
+	}
+	Manager.mu.Unlock()
+	return nil
+}
 
 // 读取信息,即收到消息 TODO 服务端读取客户的返回消息,并维持心跳
 func (c *Client) Read() {
@@ -210,28 +228,30 @@ func (c *Client) Read() {
 }
 
 // 发送消息 TODO 服务端向客户端发送消息
-func Send(uuids []string, message ServiceMessage) error {
+func Send(userIds []string, message ServiceMessage) error {
 	msg, err := json.Marshal(message)
 	if err != nil {
 		return err
 	}
 
-	for _, uuid := range uuids {
-		// 获取连接id
-		client := GetClient(uuid)
-
-		if client != nil {
-			_ = client.Socket.WriteMessage(websocket.TextMessage, msg)
+	for _, userId := range userIds {
+		links := GetAccountClient(userId)
+		for _, link := range links {
+			// 获取连接id
+			client := GetClient(link)
+			if client != nil {
+				_ = client.Socket.WriteMessage(websocket.TextMessage, msg)
+			}
 		}
 	}
 
 	return nil
 }
 
-func MessageNotify(uuid string, data model.SysMessage) {
-	_, ok := Manager.Clients[uuid]
-	if !ok { // 无匹配数据,直接报错
-		g.Log("websocket").Error(fmt.Sprintf("uuid:%v 无匹配连接", uuid))
+func MessageNotify(userId string, data model.SysMessage) {
+	arr, ok := Manager.Accounts[userId]
+	if !ok || len(arr) == 0 { // 无匹配数据,直接报错
+		g.Log("websocket").Error(fmt.Sprintf("用户ID:%v 无匹配连接", userId))
 		return
 	}
 
@@ -242,14 +262,14 @@ func MessageNotify(uuid string, data model.SysMessage) {
 		msg.Content = ServiceMessageContent{
 			Body: data,
 		}
-		err := Send([]string{uuid}, msg)
+		err := Send([]string{userId}, msg)
 		if err != nil {
 			g.Log("websocket").Error(err)
 		}
 	}()
 }
 
-func CreateConnection(uuid string, r *ghttp.Request) {
+func CreateConnection(accountId, link string, r *ghttp.Request) {
 	// 将http升级为websocket
 	conn, err := r.WebSocket()
 	if err != nil {
@@ -260,8 +280,8 @@ func CreateConnection(uuid string, r *ghttp.Request) {
 
 	// 创建一个实例连接
 	client := &Client{
-		ID:            uuid, // 连接id
-		AccountId:     uuid,
+		ID:            link, // 连接id
+		AccountId:     accountId,
 		HeartbeatTime: time.Now().Unix(),
 		Socket:        conn,
 	}

+ 8 - 2
opms_admin/main.go

@@ -9,6 +9,7 @@ import (
 	"github.com/gogf/gf/frame/g"
 	"github.com/gogf/gf/net/ghttp"
 	"github.com/smallnest/rpcx/protocol"
+	"strings"
 )
 
 func main() {
@@ -30,6 +31,7 @@ func main() {
 	s.RegisterName("Message", new(handler.MessageHandler), "")
 	s.RegisterName("BaseProductAuth", new(handler.BaseProductAuthHandler), "")
 	s.RegisterName("BaseRegionAuth", new(handler.BaseRegionAuthHandler), "")
+	s.RegisterName("SystemMessage", new(handler.SystemMessageHandler), "")
 
 	// 注册文件处理Service对象
 	//dynamic.BeanFactory.BeanRegister(service.NewRoleService())
@@ -57,6 +59,7 @@ var AuthExcludePaths = []string{
 	"/Organize/GetOrgTreeList",
 	"/User/SendResetPasswordEmail",
 	"/User/ResetPasswordFromEmail",
+	"/SystemMessage/Create",
 }
 
 // 处理Auth
@@ -75,8 +78,11 @@ func websocketStart() {
 	}
 	s := g.Server()
 	s.BindHandler("/ws", func(r *ghttp.Request) {
-		id := r.GetString("uid")
-		service.CreateConnection(id, r)
+		link := r.GetString("uid")
+		if link != "" {
+			userId := strings.Split(link, "-")[0]
+			service.CreateConnection(userId, link, r)
+		}
 	})
 	go service.CheckRun()
 	// 配置文件获取参数

+ 799 - 0
opms_parent/app/dao/work/internal/work_order_1.go

@@ -0,0 +1,799 @@
+// // ==========================================================================
+// // This is auto-generated by gf cli tool. DO NOT EDIT THIS FILE MANUALLY.
+// // ==========================================================================
+package internal
+
+//
+//import (
+//	"context"
+//	"database/sql"
+//	"github.com/gogf/gf/container/garray"
+//	"github.com/gogf/gf/database/gdb"
+//	"github.com/gogf/gf/frame/g"
+//	"github.com/gogf/gf/frame/gmvc"
+//	"github.com/gogf/gf/util/gconv"
+//	"strings"
+//	"time"
+//
+//	model "dashoo.cn/micro/app/model/work"
+//)
+//
+//// WorkOrderDao is the manager for logic model data accessing and custom defined data operations functions management.
+//type WorkOrderDao struct {
+//	gmvc.M                   // M is the core and embedded struct that inherits all chaining operations from gdb.Model.
+//	C       workOrderColumns // C is the short type for Columns, which contains all the column names of Table for convenient usage.
+//	DB      gdb.DB           // DB is the raw underlying database management object.
+//	Table   string           // Table is the underlying table name of the DAO.
+//	TableAs string           // TableAs is the underlying table alias name of the DAO.
+//}
+//
+//// WorkOrderColumns defines and stores column names for table work_order.
+//type workOrderColumns struct {
+//	Id             string // 主键
+//	Name           string // 工单名称
+//	NboId          string // 关联项目
+//	NboCode        string // 项目编码
+//	NboName        string // 项目名称
+//	CustId         string // 关联客户
+//	CustName       string // 客户名称
+//	OrderTypeId    string // 工单类型
+//	OrderTypeName  string // 工单类型名称
+//	OrderStatus    string // 工单状态(10发起20审批中30审批通过40审批拒绝50关闭)
+//	FormData       string // 表单数据
+//	AssignUserId   string // 分派人员ID
+//	AssignUserName string // 分派人员姓名
+//	Feedback       string // 反馈信息
+//	File           string // 相关文件
+//	Remark         string // 备注
+//	CreatedBy      string // 创建者
+//	CreatedName    string // 创建人
+//	CreatedTime    string // 创建时间
+//	UpdatedBy      string // 更新者
+//	UpdatedName    string // 更新人
+//	UpdatedTime    string // 更新时间
+//	DeletedTime    string // 删除时间
+//}
+//
+//var (
+//	// WorkOrder is globally public accessible object for table work_order operations.
+//	WorkOrder = WorkOrderDao{
+//		M:     g.DB("default").Model("work_order").Safe(),
+//		DB:    g.DB("default"),
+//		Table: "work_order",
+//		C: workOrderColumns{
+//			Id:             "id",
+//			Name:           "name",
+//			NboId:          "nbo_id",
+//			NboCode:        "nbo_code",
+//			NboName:        "nbo_name",
+//			CustId:         "cust_id",
+//			CustName:       "cust_name",
+//			OrderTypeId:    "order_type_id",
+//			OrderTypeName:  "order_type_name",
+//			OrderStatus:    "order_status",
+//			FormData:       "form_data",
+//			AssignUserId:   "assign_user_id",
+//			AssignUserName: "assign_user_name",
+//			Feedback:       "feedback",
+//			File:           "file",
+//			Remark:         "remark",
+//			CreatedBy:      "created_by",
+//			CreatedName:    "created_name",
+//			CreatedTime:    "created_time",
+//			UpdatedBy:      "updated_by",
+//			UpdatedName:    "updated_name",
+//			UpdatedTime:    "updated_time",
+//			DeletedTime:    "deleted_time",
+//		},
+//	}
+//)
+//
+//func NewWorkOrderDao(tenant string) WorkOrderDao {
+//	var dao WorkOrderDao
+//	dao = WorkOrderDao{
+//		M:     g.DB(tenant).Model("work_order").Safe(),
+//		DB:    g.DB(tenant),
+//		Table: "work_order",
+//		C: workOrderColumns{
+//			Id:             "id",
+//			Name:           "name",
+//			NboId:          "nbo_id",
+//			NboCode:        "nbo_code",
+//			NboName:        "nbo_name",
+//			CustId:         "cust_id",
+//			CustName:       "cust_name",
+//			OrderTypeId:    "order_type_id",
+//			OrderTypeName:  "order_type_name",
+//			OrderStatus:    "order_status",
+//			FormData:       "form_data",
+//			AssignUserId:   "assign_user_id",
+//			AssignUserName: "assign_user_name",
+//			Feedback:       "feedback",
+//			File:           "file",
+//			Remark:         "remark",
+//			CreatedBy:      "created_by",
+//			CreatedName:    "created_name",
+//			CreatedTime:    "created_time",
+//			UpdatedBy:      "updated_by",
+//			UpdatedName:    "updated_name",
+//			UpdatedTime:    "updated_time",
+//			DeletedTime:    "deleted_time",
+//		},
+//	}
+//	return dao
+//}
+//
+//// Ctx is a chaining function, which creates and returns a new DB that is a shallow copy
+//// of current DB object and with given context in it.
+//// Note that this returned DB object can be used only once, so do not assign it to
+//// a global or package variable for long using.
+//func (d *WorkOrderDao) Ctx(ctx context.Context) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Ctx(ctx), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// GetCtx returns the context for current Model.
+//// It returns "context.Background() i"s there's no context previously set.
+//func (d *WorkOrderDao) GetCtx() context.Context {
+//	return d.M.GetCtx()
+//}
+//
+//// As sets an alias name for current table.
+//func (d *WorkOrderDao) As(as string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.As(as), Table: d.Table, TableAs: as}
+//}
+//
+//// TX sets the transaction for current operation.
+//func (d *WorkOrderDao) TX(tx *gdb.TX) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.TX(tx), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Master marks the following operation on master node.
+//func (d *WorkOrderDao) Master() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Master(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Slave marks the following operation on slave node.
+//// Note that it makes sense only if there's any slave node configured.
+//func (d *WorkOrderDao) Slave() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Slave(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Args sets custom arguments for model operation.
+//func (d *WorkOrderDao) Args(args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Args(args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Handler calls each of "handlers" on current Model and returns a new Model.
+//// ModelHandler is a function that handles given Model and returns a new Model that is custom modified.
+//func (d *WorkOrderDao) Handler(handlers ...gdb.ModelHandler) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Handler(handlers...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// LeftJoin does "LEFT JOIN ... ON ..." statement on the model.
+//// The parameter <table> can be joined table and its joined condition,
+//// and also with its alias name, like:
+//// Table("user").LeftJoin("user_detail", "user_detail.uid=user.uid")
+//// Table("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid")
+//func (d *WorkOrderDao) LeftJoin(table ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.LeftJoin(table...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// RightJoin does "RIGHT JOIN ... ON ..." statement on the model.
+//// The parameter <table> can be joined table and its joined condition,
+//// and also with its alias name, like:
+//// Table("user").RightJoin("user_detail", "user_detail.uid=user.uid")
+//// Table("user", "u").RightJoin("user_detail", "ud", "ud.uid=u.uid")
+//func (d *WorkOrderDao) RightJoin(table ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.RightJoin(table...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// InnerJoin does "INNER JOIN ... ON ..." statement on the model.
+//// The parameter <table> can be joined table and its joined condition,
+//// and also with its alias name, like:
+//// Table("user").InnerJoin("user_detail", "user_detail.uid=user.uid")
+//// Table("user", "u").InnerJoin("user_detail", "ud", "ud.uid=u.uid")
+//func (d *WorkOrderDao) InnerJoin(table ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.InnerJoin(table...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Fields sets the operation fields of the model, multiple fields joined using char ','.
+//// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
+//func (d *WorkOrderDao) Fields(fieldNamesOrMapStruct ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Fields(fieldNamesOrMapStruct...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
+//// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
+//func (d *WorkOrderDao) FieldsEx(fieldNamesOrMapStruct ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.FieldsEx(fieldNamesOrMapStruct...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// FieldCount formats and appends commonly used field "COUNT(column)" to the select fields of model.
+//func (d *WorkOrderDao) FieldCount(column string, as ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.FieldCount(column, as...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// FieldSum formats and appends commonly used field "SUM(column)" to the select fields of model.
+//func (d *WorkOrderDao) FieldSum(column string, as ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.FieldSum(column, as...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// FieldMin formats and appends commonly used field "MIN(column)" to the select fields of model.
+//func (d *WorkOrderDao) FieldMin(column string, as ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.FieldMin(column, as...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// FieldMax formats and appends commonly used field "MAX(column)" to the select fields of model.
+//func (d *WorkOrderDao) FieldMax(column string, as ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.FieldMax(column, as...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// FieldAvg formats and appends commonly used field "AVG(column)" to the select fields of model.
+//func (d *WorkOrderDao) FieldAvg(column string, as ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.FieldAvg(column, as...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Option adds extra operation option for the model.
+//// Deprecated, use separate operations instead.
+//func (d *WorkOrderDao) Option(option int) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Option(option), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+//// the data and where attributes for empty values.
+//func (d *WorkOrderDao) OmitEmpty() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OmitEmpty(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OmitEmptyWhere sets optionOmitEmptyWhere option for the model, which automatically filers
+//// the Where/Having parameters for "empty" values.
+//func (d *WorkOrderDao) OmitEmptyWhere() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OmitEmptyWhere(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OmitEmptyData sets optionOmitEmptyData option for the model, which automatically filers
+//// the Data parameters for "empty" values.
+//func (d *WorkOrderDao) OmitEmptyData() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OmitEmptyData(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OmitNil sets optionOmitNil option for the model, which automatically filers
+//// the data and where parameters for "nil" values.
+//func (d *WorkOrderDao) OmitNil() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OmitNil(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OmitNilWhere sets optionOmitNilWhere option for the model, which automatically filers
+//// the Where/Having parameters for "nil" values.
+//func (d *WorkOrderDao) OmitNilWhere() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OmitNilWhere(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OmitNilData sets optionOmitNilData option for the model, which automatically filers
+//// the Data parameters for "nil" values.
+//func (d *WorkOrderDao) OmitNilData() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OmitNilData(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Filter marks filtering the fields which does not exist in the fields of the operated table.
+//// Note that this function supports only single table operations.
+//// Deprecated, filter feature is automatically enabled from GoFrame v1.16.0, it is so no longer used.
+//func (d *WorkOrderDao) Filter() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Filter(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Where sets the condition statement for the model. The parameter <where> can be type of
+//// string/map/gmap/slice/struct/*struct, etc. Note that, if it's called more than one times,
+//// multiple conditions will be joined into where statement using "AND".
+//// Eg:
+//// Where("uid=10000")
+//// Where("uid", 10000)
+//// Where("money>? AND name like ?", 99999, "vip_%")
+//// Where("uid", 1).Where("name", "john")
+//// Where("status IN (?)", g.Slice{1,2,3})
+//// Where("age IN(?,?)", 18, 50)
+//// Where(User{ Id : 1, UserName : "john"})
+//func (d *WorkOrderDao) Where(where interface{}, args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Where(where, args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WherePri does the same logic as M.Where except that if the parameter <where>
+//// is a single condition like int/string/float/slice, it treats the condition as the primary
+//// key value. That is, if primary key is "id" and given <where> parameter as "123", the
+//// WherePri function treats the condition as "id=123", but M.Where treats the condition
+//// as string "123".
+//func (d *WorkOrderDao) WherePri(where interface{}, args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WherePri(where, args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Having sets the having statement for the model.
+//// The parameters of this function usage are as the same as function Where.
+//// See Where.
+//func (d *WorkOrderDao) Having(having interface{}, args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Having(having, args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Wheref builds condition string using fmt.Sprintf and arguments.
+//// Note that if the number of "args" is more than the place holder in "format",
+//// the extra "args" will be used as the where condition arguments of the Model.
+//func (d *WorkOrderDao) Wheref(format string, args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Wheref(format, args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereLT builds "column < value" statement.
+//func (d *WorkOrderDao) WhereLT(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereLT(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereLTE builds "column <= value" statement.
+//func (d *WorkOrderDao) WhereLTE(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereLTE(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereGT builds "column > value" statement.
+//func (d *WorkOrderDao) WhereGT(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereGT(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereGTE builds "column >= value" statement.
+//func (d *WorkOrderDao) WhereGTE(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereGTE(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereBetween builds "column BETWEEN min AND max" statement.
+//func (d *WorkOrderDao) WhereBetween(column string, min, max interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereBetween(column, min, max), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereLike builds "column LIKE like" statement.
+//func (d *WorkOrderDao) WhereLike(column string, like interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereLike(column, like), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereIn builds "column IN (in)" statement.
+//func (d *WorkOrderDao) WhereIn(column string, in interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereIn(column, in), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereNull builds "columns[0] IS NULL AND columns[1] IS NULL ..." statement.
+//func (d *WorkOrderDao) WhereNull(columns ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereNull(columns...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereNotBetween builds "column NOT BETWEEN min AND max" statement.
+//func (d *WorkOrderDao) WhereNotBetween(column string, min, max interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereNotBetween(column, min, max), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereNotLike builds "column NOT LIKE like" statement.
+//func (d *WorkOrderDao) WhereNotLike(column string, like interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereNotLike(column, like), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereNot builds "column != value" statement.
+//func (d *WorkOrderDao) WhereNot(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereNot(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereNotIn builds "column NOT IN (in)" statement.
+//func (d *WorkOrderDao) WhereNotIn(column string, in interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereNotIn(column, in), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereNotNull builds "columns[0] IS NOT NULL AND columns[1] IS NOT NULL ..." statement.
+//func (d *WorkOrderDao) WhereNotNull(columns ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereNotNull(columns...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOr adds "OR" condition to the where statement.
+//func (d *WorkOrderDao) WhereOr(where interface{}, args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOr(where, args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrf builds "OR" condition string using fmt.Sprintf and arguments.
+//func (d *WorkOrderDao) WhereOrf(format string, args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrf(format, args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrLT builds "column < value" statement in "OR" conditions..
+//func (d *WorkOrderDao) WhereOrLT(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrLT(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrLTE builds "column <= value" statement in "OR" conditions..
+//func (d *WorkOrderDao) WhereOrLTE(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrLTE(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrGT builds "column > value" statement in "OR" conditions..
+//func (d *WorkOrderDao) WhereOrGT(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrGT(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrGTE builds "column >= value" statement in "OR" conditions..
+//func (d *WorkOrderDao) WhereOrGTE(column string, value interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrGTE(column, value), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrBetween builds "column BETWEEN min AND max" statement in "OR" conditions.
+//func (d *WorkOrderDao) WhereOrBetween(column string, min, max interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrBetween(column, min, max), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrLike builds "column LIKE like" statement in "OR" conditions.
+//func (d *WorkOrderDao) WhereOrLike(column string, like interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrLike(column, like), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrIn builds "column IN (in)" statement in "OR" conditions.
+//func (d *WorkOrderDao) WhereOrIn(column string, in interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrIn(column, in), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrNull builds "columns[0] IS NULL OR columns[1] IS NULL ..." statement in "OR" conditions.
+//func (d *WorkOrderDao) WhereOrNull(columns ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrNull(columns...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrNotBetween builds "column NOT BETWEEN min AND max" statement in "OR" conditions.
+//func (d *WorkOrderDao) WhereOrNotBetween(column string, min, max interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrNotBetween(column, min, max), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrNotLike builds "column NOT LIKE like" statement in "OR" conditions.
+//func (d *WorkOrderDao) WhereOrNotLike(column string, like interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrNotLike(column, like), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrNotIn builds "column NOT IN (in)" statement.
+//func (d *WorkOrderDao) WhereOrNotIn(column string, in interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrNotIn(column, in), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// WhereOrNotNull builds "columns[0] IS NOT NULL OR columns[1] IS NOT NULL ..." statement in "OR" conditions.
+//func (d *WorkOrderDao) WhereOrNotNull(columns ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.WhereOrNotNull(columns...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Group sets the "GROUP BY" statement for the model.
+//func (d *WorkOrderDao) Group(groupBy ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Group(groupBy...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// And adds "AND" condition to the where statement.
+//// Deprecated, use Where instead.
+//func (d *WorkOrderDao) And(where interface{}, args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.And(where, args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Or adds "OR" condition to the where statement.
+//// Deprecated, use WhereOr instead.
+//func (d *WorkOrderDao) Or(where interface{}, args ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Or(where, args...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// GroupBy sets the "GROUP BY" statement for the model.
+//func (d *WorkOrderDao) GroupBy(groupBy string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Group(groupBy), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Order sets the "ORDER BY" statement for the model.
+//func (d *WorkOrderDao) Order(orderBy ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Order(orderBy...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OrderAsc sets the "ORDER BY xxx ASC" statement for the model.
+//func (d *WorkOrderDao) OrderAsc(column string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OrderAsc(column), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OrderDesc sets the "ORDER BY xxx DESC" statement for the model.
+//func (d *WorkOrderDao) OrderDesc(column string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OrderDesc(column), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OrderRandom sets the "ORDER BY RANDOM()" statement for the model.
+//func (d *WorkOrderDao) OrderRandom() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.OrderRandom(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// OrderBy is alias of Model.Order.
+//// See Model.Order.
+//// Deprecated, use Order instead.
+//func (d *WorkOrderDao) OrderBy(orderBy string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Order(orderBy), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Limit sets the "LIMIT" statement for the model.
+//// The parameter <limit> can be either one or two number, if passed two number is passed,
+//// it then sets "LIMIT limit[0],limit[1]" statement for the model, or else it sets "LIMIT limit[0]"
+//// statement.
+//func (d *WorkOrderDao) Limit(limit ...int) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Limit(limit...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Offset sets the "OFFSET" statement for the model.
+//// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+//func (d *WorkOrderDao) Offset(offset int) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Offset(offset), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Distinct forces the query to only return distinct results.
+//func (d *WorkOrderDao) Distinct() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Distinct(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Page sets the paging number for the model.
+//// The parameter <page> is started from 1 for paging.
+//// Note that, it differs that the Limit function start from 0 for "LIMIT" statement.
+//func (d *WorkOrderDao) Page(page, limit int) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Page(page, limit), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Batch sets the batch operation number for the model.
+//func (d *WorkOrderDao) Batch(batch int) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Batch(batch), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Cache sets the cache feature for the model. It caches the result of the sql, which means
+//// if there's another same sql request, it just reads and returns the result from cache, it
+//// but not committed and executed into the database.
+////
+//// If the parameter <duration> < 0, which means it clear the cache with given <name>.
+//// If the parameter <duration> = 0, which means it never expires.
+//// If the parameter <duration> > 0, which means it expires after <duration>.
+////
+//// The optional parameter <name> is used to bind a name to the cache, which means you can later
+//// control the cache like changing the <duration> or clearing the cache with specified <name>.
+////
+//// Note that, the cache feature is disabled if the model is operating on a transaction.
+//func (d *WorkOrderDao) Cache(duration time.Duration, name ...string) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Cache(duration, name...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Data sets the operation data for the model.
+//// The parameter <data> can be type of string/map/gmap/slice/struct/*struct, etc.
+//// Eg:
+//// Data("uid=10000")
+//// Data("uid", 10000)
+//// Data(g.Map{"uid": 10000, "name":"john"})
+//// Data(g.Slice{g.Map{"uid": 10000, "name":"john"}, g.Map{"uid": 20000, "name":"smith"})
+//func (d *WorkOrderDao) Data(data ...interface{}) *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Data(data...), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// All does "SELECT FROM ..." statement for the model.
+//// It retrieves the records from table and returns the result as []*model.WorkOrder.
+//// It returns nil if there's no record retrieved with the given conditions from table.
+////
+//// The optional parameter <where> is the same as the parameter of M.Where function,
+//// see M.Where.
+//func (d *WorkOrderDao) All(where ...interface{}) ([]*model.WorkOrder, error) {
+//	all, err := d.M.All(where...)
+//	if err != nil {
+//		return nil, err
+//	}
+//	var entities []*model.WorkOrder
+//	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+//		return nil, err
+//	}
+//	return entities, nil
+//}
+//
+//// One retrieves one record from table and returns the result as *model.WorkOrder.
+//// It returns nil if there's no record retrieved with the given conditions from table.
+////
+//// The optional parameter <where> is the same as the parameter of M.Where function,
+//// see M.Where.
+//func (d *WorkOrderDao) One(where ...interface{}) (*model.WorkOrder, error) {
+//	one, err := d.M.One(where...)
+//	if err != nil {
+//		return nil, err
+//	}
+//	var entity *model.WorkOrder
+//	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+//		return nil, err
+//	}
+//	return entity, nil
+//}
+//
+//// FindOne retrieves and returns a single Record by M.WherePri and M.One.
+//// Also see M.WherePri and M.One.
+//func (d *WorkOrderDao) FindOne(where ...interface{}) (*model.WorkOrder, error) {
+//	one, err := d.M.FindOne(where...)
+//	if err != nil {
+//		return nil, err
+//	}
+//	var entity *model.WorkOrder
+//	if err = one.Struct(&entity); err != nil && err != sql.ErrNoRows {
+//		return nil, err
+//	}
+//	return entity, nil
+//}
+//
+//// FindAll retrieves and returns Result by by M.WherePri and M.All.
+//// Also see M.WherePri and M.All.
+//func (d *WorkOrderDao) FindAll(where ...interface{}) ([]*model.WorkOrder, error) {
+//	all, err := d.M.FindAll(where...)
+//	if err != nil {
+//		return nil, err
+//	}
+//	var entities []*model.WorkOrder
+//	if err = all.Structs(&entities); err != nil && err != sql.ErrNoRows {
+//		return nil, err
+//	}
+//	return entities, nil
+//}
+//
+//// Struct retrieves one record from table and converts it into given struct.
+//// The parameter <pointer> should be type of *struct/**struct. If type **struct is given,
+//// it can create the struct internally during converting.
+////
+//// The optional parameter <where> is the same as the parameter of Model.Where function,
+//// see Model.Where.
+////
+//// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
+//// from table and <pointer> is not nil.
+////
+//// Eg:
+//// user := new(User)
+//// err  := dao.User.Where("id", 1).Struct(user)
+////
+//// user := (*User)(nil)
+//// err  := dao.User.Where("id", 1).Struct(&user)
+//func (d *WorkOrderDao) Struct(pointer interface{}, where ...interface{}) error {
+//	return d.M.Struct(pointer, where...)
+//}
+//
+//// Structs retrieves records from table and converts them into given struct slice.
+//// The parameter <pointer> should be type of *[]struct/*[]*struct. It can create and fill the struct
+//// slice internally during converting.
+////
+//// The optional parameter <where> is the same as the parameter of Model.Where function,
+//// see Model.Where.
+////
+//// Note that it returns sql.ErrNoRows if there's no record retrieved with the given conditions
+//// from table and <pointer> is not empty.
+////
+//// Eg:
+//// users := ([]User)(nil)
+//// err   := dao.User.Structs(&users)
+////
+//// users := ([]*User)(nil)
+//// err   := dao.User.Structs(&users)
+//func (d *WorkOrderDao) Structs(pointer interface{}, where ...interface{}) error {
+//	return d.M.Structs(pointer, where...)
+//}
+//
+//// Scan automatically calls Struct or Structs function according to the type of parameter <pointer>.
+//// It calls function Struct if <pointer> is type of *struct/**struct.
+//// It calls function Structs if <pointer> is type of *[]struct/*[]*struct.
+////
+//// The optional parameter <where> is the same as the parameter of Model.Where function,
+//// see Model.Where.
+////
+//// Note that it returns sql.ErrNoRows if there's no record retrieved and given pointer is not empty or nil.
+////
+//// Eg:
+//// user  := new(User)
+//// err   := dao.User.Where("id", 1).Scan(user)
+////
+//// user  := (*User)(nil)
+//// err   := dao.User.Where("id", 1).Scan(&user)
+////
+//// users := ([]User)(nil)
+//// err   := dao.User.Scan(&users)
+////
+//// users := ([]*User)(nil)
+//// err   := dao.User.Scan(&users)
+//func (d *WorkOrderDao) Scan(pointer interface{}, where ...interface{}) error {
+//	return d.M.Scan(pointer, where...)
+//}
+//
+//// Chunk iterates the table with given size and callback function.
+//func (d *WorkOrderDao) Chunk(limit int, callback func(entities []*model.WorkOrder, err error) bool) {
+//	d.M.Chunk(limit, func(result gdb.Result, err error) bool {
+//		var entities []*model.WorkOrder
+//		err = result.Structs(&entities)
+//		if err == sql.ErrNoRows {
+//			return false
+//		}
+//		return callback(entities, err)
+//	})
+//}
+//
+//// LockUpdate sets the lock for update for current operation.
+//func (d *WorkOrderDao) LockUpdate() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.LockUpdate(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// LockShared sets the lock in share mode for current operation.
+//func (d *WorkOrderDao) LockShared() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.LockShared(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// Unscoped enables/disables the soft deleting feature.
+//func (d *WorkOrderDao) Unscoped() *WorkOrderDao {
+//	return &WorkOrderDao{M: d.M.Unscoped(), Table: d.Table, TableAs: d.TableAs}
+//}
+//
+//// DataScope enables the DataScope feature.
+//func (d *WorkOrderDao) DataScope(ctx context.Context, args ...interface{}) *WorkOrderDao {
+//	cs := ctx.Value("contextService")
+//	dataScope := gconv.Map(gconv.String(gconv.Map(cs)["dataScope"]))
+//	if dataScope != nil {
+//		var specialFlag bool
+//		var tableAs string
+//		if d.TableAs != "" && len(args) <= 1 {
+//			tableAs = d.TableAs + "."
+//		}
+//		if len(args) > 1 {
+//			specialFlag = true
+//			if val, ok := args[1].(string); ok {
+//				tableAs = val + "."
+//			}
+//		}
+//		userIds, ok := dataScope["userIds"]
+//		delete(dataScope, "userIds")
+//		var orColumns []string
+//		var orValues []interface{}
+//		if ok && userIds != "-1" {
+//			columns := []string{"created_by"}
+//			if len(args) > 0 {
+//				column, ok := args[0].(string)
+//				if ok {
+//					columns[0] = column
+//				}
+//				cols, ok := args[0].([]string)
+//				if ok {
+//					columns = cols
+//				}
+//			}
+//			for _, column := range columns {
+//				if ok, _ := d.M.HasField(column); ok || specialFlag {
+//					orColumns = append(orColumns, tableAs+column+" IN (?) ")
+//					orValues = append(orValues, userIds)
+//				}
+//			}
+//
+//		}
+//		// 销售工程师判断
+//		var salesEngineerFlag bool
+//		if roles, ok := dataScope["roles"]; ok {
+//			delete(dataScope, "roles")
+//			delete(dataScope, "posts")
+//			arr := garray.NewArrayFrom(roles.([]interface{}), true)
+//			if arr.Len() == 1 && arr.Contains("SalesEngineer") {
+//				salesEngineerFlag = true
+//			}
+//		}
+//		// 非销售工程师权限加成
+//		if !salesEngineerFlag {
+//			bigColumns := "is_big"
+//			if ok, _ := d.M.HasField("is_big"); ok || specialFlag {
+//				if val, ok := dataScope[bigColumns]; ok && val != "" {
+//					orColumns = append(orColumns, tableAs+bigColumns+" = ? ")
+//					orValues = append(orValues, val)
+//				}
+//				delete(dataScope, bigColumns)
+//			}
+//			var andColumns []string
+//			var andValues []interface{}
+//			for k, v := range dataScope {
+//				if ok, _ := d.M.HasField(k); ok || specialFlag {
+//					andColumns = append(andColumns, tableAs+k+" IN (?) ")
+//					andValues = append(andValues, v)
+//				}
+//			}
+//			if len(andColumns) > 0 {
+//				andWhereSql := strings.Join(andColumns, " AND ")
+//				orColumns = append(orColumns, "("+andWhereSql+")")
+//				orValues = append(orValues, andValues...)
+//			}
+//		}
+//
+//		whereSql := strings.Join(orColumns, " OR ")
+//		return &WorkOrderDao{M: d.M.Where(whereSql, orValues...).Ctx(ctx), Table: d.Table, TableAs: d.TableAs}
+//	}
+//	return d
+//}

+ 18 - 0
opms_parent/app/service/base.go

@@ -2,7 +2,10 @@ package service
 
 import (
 	"context"
+	"dashoo.cn/opms_libary/myerrors"
 	"fmt"
+	"github.com/gogf/gf/frame/g"
+	"github.com/smallnest/rpcx/share"
 	"log"
 	"reflect"
 
@@ -170,3 +173,18 @@ func StringSlicecontains(s []string, ele string) bool {
 	}
 	return false
 }
+
+func CreateSystemMessage(msg g.MapStrStr) error {
+	srv := micro_srv.InitMicroSrvClient("SystemMessage", "micro_srv.auth")
+	defer srv.Close()
+	resp := &comm_def.CommonMsg{}
+	tenant := g.Config().GetString("micro_srv.tenant")
+	ctx := context.WithValue(context.TODO(), share.ReqMetaDataKey, map[string]string{"tenant": tenant})
+	err := srv.Call(ctx, "Create", msg, resp)
+	if err != nil {
+		g.Log().Error(err)
+		return myerrors.MicroCallError("系统创建消息失败")
+	}
+	fmt.Println(resp.Data)
+	return nil
+}

+ 21 - 0
opms_parent/app/service/base_test.go

@@ -0,0 +1,21 @@
+package service
+
+import (
+	"github.com/gogf/gf/frame/g"
+	"testing"
+)
+
+func TestCreateSystemMessage(t *testing.T) {
+	msg := g.MapStrStr{
+		"msgTitle":    "ces",
+		"msgContent":  "<p>111111111</p>",
+		"msgType":     "20",
+		"msgStatus":   "10",
+		"recvUserIds": "1",
+		"recvUser":    "系统管理员",
+		"sendType":    "10,20,30",
+	}
+	if err := CreateSystemMessage(msg); err != nil {
+		t.Errorf("CreateSystemMessage() error = %v", err)
+	}
+}