Browse Source

feature(公告):通知公告管理

ZZH-wl 2 years ago
parent
commit
0625895910

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

@@ -2,9 +2,9 @@
   "name": "订单全流程管理平台",
   "describe": "订单全流程管理平台",
   "avatar": "",
-  "version": "4.2.2",
+  "version": "4.3.0",
   "createdTime": "2023-1-6 11:43:05",
-  "updatedTime": "2023-2-14 08:54:18",
+  "updatedTime": "2023-2-16 10:23:30",
   "dbConns": [],
   "profile": {
     "default": {
@@ -11882,7 +11882,7 @@
           "scale": "",
           "primaryKey": true,
           "notNull": true,
-          "autoIncrement": false,
+          "autoIncrement": true,
           "defaultValue": "",
           "hideInGraph": false,
           "refDict": "",
@@ -12000,7 +12000,7 @@
           "len": "",
           "scale": "",
           "primaryKey": false,
-          "notNull": true,
+          "notNull": false,
           "autoIncrement": false,
           "defaultValue": "",
           "hideInGraph": false,
@@ -16035,6 +16035,184 @@
       ],
       "correlations": [],
       "indexes": []
+    },
+    {
+      "id": "9DEB5CFB-923E-4C90-A6BC-602093A99B3A",
+      "env": {
+        "base": {
+          "nameSpace": "",
+          "codeRoot": ""
+        }
+      },
+      "defKey": "sys_message_log",
+      "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": "7AC71DFB-899E-4A94-B336-F3F2C24C1727"
+        },
+        {
+          "defKey": "msg_id",
+          "defName": "关联消息",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": true,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "domain": "16120F75-6AA7-4483-868D-F07F511BB081",
+          "refDict": "",
+          "extProps": {},
+          "notes": {},
+          "id": "0A1B0C4F-C8B8-4098-879B-CA659772FE6E"
+        },
+        {
+          "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": "1CFACBDA-D075-4C80-BA2A-13C78AB212BB"
+        },
+        {
+          "defKey": "is_read",
+          "defName": "是否已读(10否20是)",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": true,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "refDict": "",
+          "extProps": {},
+          "domain": "73FD2BAD-2358-4336-B96D-45DC897BD792",
+          "id": "A6A74114-C282-4EC6-8301-D3A4E31AA7FA"
+        },
+        {
+          "defKey": "read_time",
+          "defName": "已读时间",
+          "comment": "",
+          "type": "",
+          "len": "",
+          "scale": "",
+          "primaryKey": false,
+          "notNull": false,
+          "autoIncrement": false,
+          "defaultValue": "",
+          "hideInGraph": false,
+          "refDict": "",
+          "extProps": {},
+          "domain": "7CFFA0D3-6A93-4DDC-BC10-DF21211064DC",
+          "id": "36EBB55D-0394-4F76-8CDC-C361DC21B037"
+        }
+      ],
+      "correlations": [],
+      "indexes": []
     }
   ],
   "views": [],
@@ -16486,6 +16664,7 @@
         "AF48D518-71C2-4FA5-B0A8-CEBE4AEF9835",
         "88239EF6-71E2-4A03-87A1-7DEA239C32BD",
         "DCB362BC-4FD6-489F-852D-E5728570275E",
+        "9DEB5CFB-923E-4C90-A6BC-602093A99B3A",
         "1213FF4D-0686-4882-A33A-8E48C7D239CA",
         "C9C0EFF0-7A8A-44AC-9504-1EC2730C47D0"
       ],

+ 716 - 0
opms_admin/app/dao/internal/sys_message.go

@@ -0,0 +1,716 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. DO NOT EDIT THIS FILE MANUALLY.
+// ==========================================================================
+
+package internal
+
+import (
+	"context"
+	"database/sql"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/frame/gmvc"
+	"github.com/gogf/gf/util/gconv"
+	"time"
+
+	"dashoo.cn/micro/app/model"
+)
+
+// SysMessageDao is the manager for logic model data accessing
+// and custom defined data operations functions management.
+type SysMessageDao struct {
+	gmvc.M
+	DB      gdb.DB
+	Table   string
+	Columns sysMessageColumns
+}
+
+// SysMessageColumns defines and stores column names for table sys_message.
+type sysMessageColumns struct {
+	Id          string // 主键
+	MsgTitle    string // 消息标题
+	MsgContent  string // 消息内容
+	MsgType     string // 消息类别(10公告20消息30审批)
+	MsgStatus   string // 消息状态(10正常20关闭)
+	RecvUserIds string // 接收用户
+	OpnUrl      string // 操作链接
+	IsRead      string // 是否已读(10否20是)
+	ReadTime    string // 已读时间
+	Remark      string // 备注
+	CreatedBy   string // 创建者
+	CreatedName string // 创建人
+	CreatedTime string // 创建时间
+	UpdatedBy   string // 更新者
+	UpdatedName string // 更新人
+	UpdatedTime string // 更新时间
+	DeletedTime string // 删除时间
+}
+
+var (
+	// SysMessage is globally public accessible object for table sys_message operations.
+	SysMessage = SysMessageDao{
+		M:     g.DB("default").Model("sys_message").Safe(),
+		DB:    g.DB("default"),
+		Table: "sys_message",
+		Columns: sysMessageColumns{
+			Id:          "id",
+			MsgTitle:    "msg_title",
+			MsgContent:  "msg_content",
+			MsgType:     "msg_type",
+			MsgStatus:   "msg_status",
+			RecvUserIds: "recv_user_ids",
+			OpnUrl:      "opn_url",
+			IsRead:      "is_read",
+			ReadTime:    "read_time",
+			Remark:      "remark",
+			CreatedBy:   "created_by",
+			CreatedName: "created_name",
+			CreatedTime: "created_time",
+			UpdatedBy:   "updated_by",
+			UpdatedName: "updated_name",
+			UpdatedTime: "updated_time",
+			DeletedTime: "deleted_time",
+		},
+	}
+)
+
+func NewSysMessageDao(tenant string) SysMessageDao {
+	var dao SysMessageDao
+	dao = SysMessageDao{
+		M:     g.DB(tenant).Model("sys_message").Safe(),
+		DB:    g.DB(tenant),
+		Table: "sys_message",
+		Columns: sysMessageColumns{
+			Id:          "id",
+			MsgTitle:    "msg_title",
+			MsgContent:  "msg_content",
+			MsgType:     "msg_type",
+			MsgStatus:   "msg_status",
+			RecvUserIds: "recv_user_ids",
+			OpnUrl:      "opn_url",
+			IsRead:      "is_read",
+			ReadTime:    "read_time",
+			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 *SysMessageDao) Ctx(ctx context.Context) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Ctx(ctx)}
+}
+
+// GetCtx returns the context for current Model.
+// It returns "context.Background() i"s there's no context previously set.
+func (d *SysMessageDao) GetCtx() context.Context {
+	return d.M.GetCtx()
+}
+
+// As sets an alias name for current table.
+func (d *SysMessageDao) As(as string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (d *SysMessageDao) TX(tx *gdb.TX) *SysMessageDao {
+	return &SysMessageDao{M: d.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (d *SysMessageDao) Master() *SysMessageDao {
+	return &SysMessageDao{M: d.M.Master()}
+}
+
+// Slave marks the following operation on slave node.
+// Note that it makes sense only if there's any slave node configured.
+func (d *SysMessageDao) Slave() *SysMessageDao {
+	return &SysMessageDao{M: d.M.Slave()}
+}
+
+// Args sets custom arguments for model operation.
+func (d *SysMessageDao) Args(args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Args(args...)}
+}
+
+// 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 *SysMessageDao) Handler(handlers ...gdb.ModelHandler) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Handler(handlers...)}
+}
+
+// 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 *SysMessageDao) LeftJoin(table ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.LeftJoin(table...)}
+}
+
+// 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 *SysMessageDao) RightJoin(table ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.RightJoin(table...)}
+}
+
+// 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 *SysMessageDao) InnerJoin(table ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.InnerJoin(table...)}
+}
+
+// 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 *SysMessageDao) Fields(fieldNamesOrMapStruct ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Fields(fieldNamesOrMapStruct...)}
+}
+
+// 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 *SysMessageDao) FieldsEx(fieldNamesOrMapStruct ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.FieldsEx(fieldNamesOrMapStruct...)}
+}
+
+// FieldCount formats and appends commonly used field "COUNT(column)" to the select fields of model.
+func (d *SysMessageDao) FieldCount(column string, as ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.FieldCount(column, as...)}
+}
+
+// FieldSum formats and appends commonly used field "SUM(column)" to the select fields of model.
+func (d *SysMessageDao) FieldSum(column string, as ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.FieldSum(column, as...)}
+}
+
+// FieldMin formats and appends commonly used field "MIN(column)" to the select fields of model.
+func (d *SysMessageDao) FieldMin(column string, as ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.FieldMin(column, as...)}
+}
+
+// FieldMax formats and appends commonly used field "MAX(column)" to the select fields of model.
+func (d *SysMessageDao) FieldMax(column string, as ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.FieldMax(column, as...)}
+}
+
+// FieldAvg formats and appends commonly used field "AVG(column)" to the select fields of model.
+func (d *SysMessageDao) FieldAvg(column string, as ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.FieldAvg(column, as...)}
+}
+
+// Option adds extra operation option for the model.
+// Deprecated, use separate operations instead.
+func (d *SysMessageDao) Option(option int) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Option(option)}
+}
+
+// OmitEmpty sets OPTION_OMITEMPTY option for the model, which automatically filers
+// the data and where attributes for empty values.
+func (d *SysMessageDao) OmitEmpty() *SysMessageDao {
+	return &SysMessageDao{M: d.M.OmitEmpty()}
+}
+
+// OmitEmptyWhere sets optionOmitEmptyWhere option for the model, which automatically filers
+// the Where/Having parameters for "empty" values.
+func (d *SysMessageDao) OmitEmptyWhere() *SysMessageDao {
+	return &SysMessageDao{M: d.M.OmitEmptyWhere()}
+}
+
+// OmitEmptyData sets optionOmitEmptyData option for the model, which automatically filers
+// the Data parameters for "empty" values.
+func (d *SysMessageDao) OmitEmptyData() *SysMessageDao {
+	return &SysMessageDao{M: d.M.OmitEmptyData()}
+}
+
+// OmitNil sets optionOmitNil option for the model, which automatically filers
+// the data and where parameters for "nil" values.
+func (d *SysMessageDao) OmitNil() *SysMessageDao {
+	return &SysMessageDao{M: d.M.OmitNil()}
+}
+
+// OmitNilWhere sets optionOmitNilWhere option for the model, which automatically filers
+// the Where/Having parameters for "nil" values.
+func (d *SysMessageDao) OmitNilWhere() *SysMessageDao {
+	return &SysMessageDao{M: d.M.OmitNilWhere()}
+}
+
+// OmitNilData sets optionOmitNilData option for the model, which automatically filers
+// the Data parameters for "nil" values.
+func (d *SysMessageDao) OmitNilData() *SysMessageDao {
+	return &SysMessageDao{M: d.M.OmitNilData()}
+}
+
+// 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 *SysMessageDao) Filter() *SysMessageDao {
+	return &SysMessageDao{M: d.M.Filter()}
+}
+
+// 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 *SysMessageDao) Where(where interface{}, args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Where(where, args...)}
+}
+
+// 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 *SysMessageDao) WherePri(where interface{}, args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WherePri(where, args...)}
+}
+
+// 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 *SysMessageDao) Having(having interface{}, args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Having(having, args...)}
+}
+
+// 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 *SysMessageDao) Wheref(format string, args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Wheref(format, args...)}
+}
+
+// WhereLT builds "column < value" statement.
+func (d *SysMessageDao) WhereLT(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereLT(column, value)}
+}
+
+// WhereLTE builds "column <= value" statement.
+func (d *SysMessageDao) WhereLTE(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereLTE(column, value)}
+}
+
+// WhereGT builds "column > value" statement.
+func (d *SysMessageDao) WhereGT(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereGT(column, value)}
+}
+
+// WhereGTE builds "column >= value" statement.
+func (d *SysMessageDao) WhereGTE(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereGTE(column, value)}
+}
+
+// WhereBetween builds "column BETWEEN min AND max" statement.
+func (d *SysMessageDao) WhereBetween(column string, min, max interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereBetween(column, min, max)}
+}
+
+// WhereLike builds "column LIKE like" statement.
+func (d *SysMessageDao) WhereLike(column string, like interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereLike(column, like)}
+}
+
+// WhereIn builds "column IN (in)" statement.
+func (d *SysMessageDao) WhereIn(column string, in interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereIn(column, in)}
+}
+
+// WhereNull builds "columns[0] IS NULL AND columns[1] IS NULL ..." statement.
+func (d *SysMessageDao) WhereNull(columns ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereNull(columns...)}
+}
+
+// WhereNotBetween builds "column NOT BETWEEN min AND max" statement.
+func (d *SysMessageDao) WhereNotBetween(column string, min, max interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereNotBetween(column, min, max)}
+}
+
+// WhereNotLike builds "column NOT LIKE like" statement.
+func (d *SysMessageDao) WhereNotLike(column string, like interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereNotLike(column, like)}
+}
+
+// WhereNot builds "column != value" statement.
+func (d *SysMessageDao) WhereNot(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereNot(column, value)}
+}
+
+// WhereNotIn builds "column NOT IN (in)" statement.
+func (d *SysMessageDao) WhereNotIn(column string, in interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereNotIn(column, in)}
+}
+
+// WhereNotNull builds "columns[0] IS NOT NULL AND columns[1] IS NOT NULL ..." statement.
+func (d *SysMessageDao) WhereNotNull(columns ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereNotNull(columns...)}
+}
+
+// WhereOr adds "OR" condition to the where statement.
+func (d *SysMessageDao) WhereOr(where interface{}, args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOr(where, args...)}
+}
+
+// WhereOrf builds "OR" condition string using fmt.Sprintf and arguments.
+func (d *SysMessageDao) WhereOrf(format string, args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrf(format, args...)}
+}
+
+// WhereOrLT builds "column < value" statement in "OR" conditions..
+func (d *SysMessageDao) WhereOrLT(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrLT(column, value)}
+}
+
+// WhereOrLTE builds "column <= value" statement in "OR" conditions..
+func (d *SysMessageDao) WhereOrLTE(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrLTE(column, value)}
+}
+
+// WhereOrGT builds "column > value" statement in "OR" conditions..
+func (d *SysMessageDao) WhereOrGT(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrGT(column, value)}
+}
+
+// WhereOrGTE builds "column >= value" statement in "OR" conditions..
+func (d *SysMessageDao) WhereOrGTE(column string, value interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrGTE(column, value)}
+}
+
+// WhereOrBetween builds "column BETWEEN min AND max" statement in "OR" conditions.
+func (d *SysMessageDao) WhereOrBetween(column string, min, max interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrBetween(column, min, max)}
+}
+
+// WhereOrLike builds "column LIKE like" statement in "OR" conditions.
+func (d *SysMessageDao) WhereOrLike(column string, like interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrLike(column, like)}
+}
+
+// WhereOrIn builds "column IN (in)" statement in "OR" conditions.
+func (d *SysMessageDao) WhereOrIn(column string, in interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrIn(column, in)}
+}
+
+// WhereOrNull builds "columns[0] IS NULL OR columns[1] IS NULL ..." statement in "OR" conditions.
+func (d *SysMessageDao) WhereOrNull(columns ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrNull(columns...)}
+}
+
+// WhereOrNotBetween builds "column NOT BETWEEN min AND max" statement in "OR" conditions.
+func (d *SysMessageDao) WhereOrNotBetween(column string, min, max interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrNotBetween(column, min, max)}
+}
+
+// WhereOrNotLike builds "column NOT LIKE like" statement in "OR" conditions.
+func (d *SysMessageDao) WhereOrNotLike(column string, like interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrNotLike(column, like)}
+}
+
+// WhereOrNotIn builds "column NOT IN (in)" statement.
+func (d *SysMessageDao) WhereOrNotIn(column string, in interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrNotIn(column, in)}
+}
+
+// WhereOrNotNull builds "columns[0] IS NOT NULL OR columns[1] IS NOT NULL ..." statement in "OR" conditions.
+func (d *SysMessageDao) WhereOrNotNull(columns ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.WhereOrNotNull(columns...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (d *SysMessageDao) Group(groupBy ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Group(groupBy...)}
+}
+
+// And adds "AND" condition to the where statement.
+// Deprecated, use Where instead.
+func (d *SysMessageDao) And(where interface{}, args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+// Deprecated, use WhereOr instead.
+func (d *SysMessageDao) Or(where interface{}, args ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Or(where, args...)}
+}
+
+// GroupBy sets the "GROUP BY" statement for the model.
+func (d *SysMessageDao) GroupBy(groupBy string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (d *SysMessageDao) Order(orderBy ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Order(orderBy...)}
+}
+
+// OrderAsc sets the "ORDER BY xxx ASC" statement for the model.
+func (d *SysMessageDao) OrderAsc(column string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.OrderAsc(column)}
+}
+
+// OrderDesc sets the "ORDER BY xxx DESC" statement for the model.
+func (d *SysMessageDao) OrderDesc(column string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.OrderDesc(column)}
+}
+
+// OrderRandom sets the "ORDER BY RANDOM()" statement for the model.
+func (d *SysMessageDao) OrderRandom() *SysMessageDao {
+	return &SysMessageDao{M: d.M.OrderRandom()}
+}
+
+// OrderBy is alias of Model.Order.
+// See Model.Order.
+// Deprecated, use Order instead.
+func (d *SysMessageDao) OrderBy(orderBy string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Order(orderBy)}
+}
+
+// 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 *SysMessageDao) Limit(limit ...int) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Limit(limit...)}
+}
+
+// Offset sets the "OFFSET" statement for the model.
+// It only makes sense for some databases like SQLServer, PostgreSQL, etc.
+func (d *SysMessageDao) Offset(offset int) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Offset(offset)}
+}
+
+// Distinct forces the query to only return distinct results.
+func (d *SysMessageDao) Distinct() *SysMessageDao {
+	return &SysMessageDao{M: d.M.Distinct()}
+}
+
+// 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 *SysMessageDao) Page(page, limit int) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (d *SysMessageDao) Batch(batch int) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Batch(batch)}
+}
+
+// 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 *SysMessageDao) Cache(duration time.Duration, name ...string) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Cache(duration, name...)}
+}
+
+// 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 *SysMessageDao) Data(data ...interface{}) *SysMessageDao {
+	return &SysMessageDao{M: d.M.Data(data...)}
+}
+
+// All does "SELECT FROM ..." statement for the model.
+// It retrieves the records from table and returns the result as []*model.SysMessage.
+// 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 *SysMessageDao) All(where ...interface{}) ([]*model.SysMessage, error) {
+	all, err := d.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*model.SysMessage
+	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.SysMessage.
+// 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 *SysMessageDao) One(where ...interface{}) (*model.SysMessage, error) {
+	one, err := d.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *model.SysMessage
+	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 *SysMessageDao) FindOne(where ...interface{}) (*model.SysMessage, error) {
+	one, err := d.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *model.SysMessage
+	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 *SysMessageDao) FindAll(where ...interface{}) ([]*model.SysMessage, error) {
+	all, err := d.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*model.SysMessage
+	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 *SysMessageDao) 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 *SysMessageDao) 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 *SysMessageDao) Scan(pointer interface{}, where ...interface{}) error {
+	return d.M.Scan(pointer, where...)
+}
+
+// Chunk iterates the table with given size and callback function.
+func (d *SysMessageDao) Chunk(limit int, callback func(entities []*model.SysMessage, err error) bool) {
+	d.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*model.SysMessage
+		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 *SysMessageDao) LockUpdate() *SysMessageDao {
+	return &SysMessageDao{M: d.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (d *SysMessageDao) LockShared() *SysMessageDao {
+	return &SysMessageDao{M: d.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (d *SysMessageDao) Unscoped() *SysMessageDao {
+	return &SysMessageDao{M: d.M.Unscoped()}
+}
+
+// DataScope enables the DataScope feature.
+func (d *SysMessageDao) DataScope(userCol ...string) *SysMessageDao {
+	ctx := d.GetCtx().Value("contextService")
+	dataScope := gconv.Map(ctx)["dataScope"].(g.Map)
+	if dataScope != nil {
+		if userIds, ok := dataScope["userIds"]; ok {
+			column := "created_by"
+			if len(userCol) == 1 {
+				column = userCol[0]
+			}
+			delete(dataScope, "userIds")
+			dataScope[column] = userIds
+		}
+		return &SysMessageDao{M: d.M.Where(dataScope)}
+	}
+	return d
+}

+ 36 - 0
opms_admin/app/dao/sys_message.go

@@ -0,0 +1,36 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package dao
+
+import (
+	"dashoo.cn/micro/app/dao/internal"
+)
+
+// sysMessageDao is the manager for logic model data accessing
+// and custom defined data operations functions management. You can define
+// methods on it to extend its functionality as you wish.
+type sysMessageDao struct {
+	internal.SysMessageDao
+}
+
+var (
+	// SysMessage is globally public accessible object for table sys_message operations.
+	SysMessage = sysMessageDao{
+		internal.SysMessage,
+	}
+)
+
+type SysMessageDao struct {
+	internal.SysMessageDao
+}
+
+func NewSysMessageDao(tenant string) *SysMessageDao {
+	dao := internal.NewSysMessageDao(tenant)
+	return &SysMessageDao{
+		dao,
+	}
+}
+
+// Fill with you ideas below.

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

@@ -0,0 +1,94 @@
+package handler
+
+import (
+	"context"
+	"dashoo.cn/common_definition/comm_def"
+	"dashoo.cn/opms_libary/myerrors"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/util/gvalid"
+
+	"dashoo.cn/micro/app/model"
+	"dashoo.cn/micro/app/service"
+)
+
+type MessageHandler struct{}
+
+// GetList 获取列表
+func (h *MessageHandler) GetList(ctx context.Context, req *model.SysMessageSearchReq, rsp *comm_def.CommonMsg) error {
+	msgService, err := service.NewMessageService(ctx)
+	if err != nil {
+		return err
+	}
+	total, list, err := msgService.GetList(req)
+	if err != nil {
+		return err
+	}
+	rsp.Data = g.Map{"list": list, "total": total}
+	return err
+}
+
+// GetEntityById 详情
+func (o *MessageHandler) GetEntityById(ctx context.Context, req *comm_def.IdReq, rsp *comm_def.CommonMsg) error {
+	// 参数校验
+	if req.Id == 0 {
+		return myerrors.TipsError("请求参数不存在。")
+	}
+	msgService, err := service.NewMessageService(ctx)
+	if err != nil {
+		return err
+	}
+	entity, err := msgService.GetEntityById(req.Id)
+	if err != nil {
+		return err
+	}
+	rsp.Data = entity
+	return nil
+}
+
+// Create 添加
+func (h *MessageHandler) Create(ctx context.Context, req *model.CreateSysMessageReq, rsp *comm_def.CommonMsg) error {
+	// 检查请求参数
+	if err := gvalid.CheckStruct(ctx, req, nil); err != nil {
+		return err
+	}
+	msgService, err := service.NewMessageService(ctx)
+	if err != nil {
+		return err
+	}
+	err = msgService.Create(req)
+	return err
+}
+
+// UpdateById 编辑
+func (h *MessageHandler) UpdateById(ctx context.Context, req *model.UpdateSysMessageReq, rsp *comm_def.CommonMsg) error {
+	// 检查请求参数
+	if err := gvalid.CheckStruct(ctx, req, nil); err != nil {
+		return err
+	}
+	msgService, err := service.NewMessageService(ctx)
+	if err != nil {
+		return err
+	}
+	err = msgService.UpdateById(req)
+	return err
+}
+
+// DeleteByIds 删除菜单
+func (h *MessageHandler) DeleteByIds(ctx context.Context, req *comm_def.IdsReq, rsp *comm_def.CommonMsg) error {
+	msgService, err := service.NewMessageService(ctx)
+	if err != nil {
+		return err
+	}
+	err = msgService.DeleteByIds(req.Ids)
+	return err
+}
+
+// AllRead 全部已读
+func (h *MessageHandler) AllRead(ctx context.Context, null, rsp *comm_def.CommonMsg) error {
+	msgService, err := service.NewMessageService(ctx)
+	if err != nil {
+		return err
+	}
+	err = msgService.AllRead()
+	return err
+}

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

@@ -0,0 +1,30 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. DO NOT EDIT THIS FILE MANUALLY.
+// ==========================================================================
+
+package internal
+
+import (
+	"github.com/gogf/gf/os/gtime"
+)
+
+// SysMessage is the golang structure for table sys_message.
+type SysMessage struct {
+	Id          int         `orm:"id,primary"    json:"id"`          // 主键
+	MsgTitle    string      `orm:"msg_title"     json:"msgTitle"`    // 消息标题
+	MsgContent  string      `orm:"msg_content"   json:"msgContent"`  // 消息内容
+	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"` // 接收用户
+	OpnUrl      string      `orm:"opn_url"       json:"opnUrl"`      // 操作链接
+	IsRead      string      `orm:"is_read"       json:"isRead"`      // 是否已读(10否20是)
+	ReadTime    *gtime.Time `orm:"read_time"     json:"readTime"`    // 已读时间
+	Remark      string      `orm:"remark"        json:"remark"`      // 备注
+	CreatedBy   int         `orm:"created_by"    json:"createdBy"`   // 创建者
+	CreatedName string      `orm:"created_name"  json:"createdName"` // 创建人
+	CreatedTime *gtime.Time `orm:"created_time"  json:"createdTime"` // 创建时间
+	UpdatedBy   int         `orm:"updated_by"    json:"updatedBy"`   // 更新者
+	UpdatedName string      `orm:"updated_name"  json:"updatedName"` // 更新人
+	UpdatedTime *gtime.Time `orm:"updated_time"  json:"updatedTime"` // 更新时间
+	DeletedTime *gtime.Time `orm:"deleted_time"  json:"deletedTime"` // 删除时间
+}

+ 39 - 0
opms_admin/app/model/sys_message.go

@@ -0,0 +1,39 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. Fill this file as you wish.
+// ==========================================================================
+
+package model
+
+import (
+	"dashoo.cn/micro/app/model/internal"
+	"dashoo.cn/opms_libary/request"
+	"github.com/gogf/gf/os/gtime"
+)
+
+// SysMessage is the golang structure for table sys_message.
+type SysMessage internal.SysMessage
+
+// Fill with you ideas below.
+
+type SysMessageSearchReq struct {
+	MsgTitle string `json:"msgTitle"`
+	MsgType  string `json:"msgType"`
+	request.PageReq
+}
+
+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"`                                                          // 备注
+}
+
+type UpdateSysMessageReq struct {
+	Id int64 `json:"id" v:"required|min:1#主键ID不能为空|主键ID参数错误"`
+	CreateSysMessageReq
+}

+ 1 - 1
opms_admin/app/service/sys_menu.go

@@ -99,7 +99,7 @@ func (s MenuService) UpdateById(param *model.SysMenuReq) error {
 		}
 	}
 	SetUpdatedInfo(entity, s.GetCxtUserId(), s.GetCxtUserName())
-	_, err = db.FieldsEx(UpdateFieldEx).Where("Id", entity.Id).Update(entity)
+	_, err = db.FieldsEx(UpdateFieldEx...).Where("Id", entity.Id).Update(entity)
 	if err != nil {
 		return err
 	}

+ 130 - 0
opms_admin/app/service/sys_message.go

@@ -0,0 +1,130 @@
+package service
+
+import (
+	"context"
+	"dashoo.cn/micro/app/dao"
+	"dashoo.cn/micro/app/model"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/util/gconv"
+	"strings"
+)
+
+type MessageService struct {
+	*contextService
+	Dao    *dao.SysMessageDao
+	logDao *dao.SysMessageLogDao
+}
+
+func NewMessageService(ctx context.Context) (svc *MessageService, err error) {
+	svc = new(MessageService)
+	if svc.contextService, err = svc.Init(ctx); err != nil {
+		return nil, err
+	}
+	svc.Dao = dao.NewSysMessageDao(svc.Tenant)
+	svc.logDao = dao.NewSysMessageLogDao(svc.Tenant)
+	svc.Table = svc.Dao.Table
+	return svc, nil
+}
+
+func (s *MessageService) GetList(req *model.SysMessageSearchReq) (total int, list []*model.SysMessage, err error) {
+	m := s.Dao.M
+	if req != nil {
+		if req.MsgTitle != "" {
+			m = m.WhereLike(s.Dao.Columns.MsgTitle, "%"+req.MsgTitle+"%")
+		}
+		if req.MsgType != "" {
+			m = m.Where(s.Dao.Columns.MsgType, req.MsgType)
+		}
+	}
+	total, err = m.Count()
+	if err != nil {
+		return
+	}
+	err = m.Page(req.GetPage()).OrderDesc("id").Scan(&list)
+	if err != nil {
+		return
+	}
+	return
+}
+
+func (s *MessageService) GetEntityById(id int64) (msg *model.SysMessage, err error) {
+	msg, err = s.Dao.WherePri(id).One()
+	if err != nil {
+		return nil, err
+	}
+	data := g.Map{
+		s.logDao.Columns.IsRead:   "20",
+		s.logDao.Columns.ReadTime: gtime.Now(),
+	}
+	_, err = s.logDao.Where(s.logDao.Columns.MsgId, id).Where(s.logDao.Columns.UserId, s.GetCxtUserId()).Data(data).Update()
+	return
+}
+
+// Create 添加操作
+func (s *MessageService) Create(req *model.CreateSysMessageReq) (err error) {
+	data := new(model.SysMessage)
+	if err := gconv.Struct(req, data); err != nil {
+		return err
+	}
+	SetCreatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
+	lastId, err := s.Dao.InsertAndGetId(data)
+	if err != nil {
+		return err
+	}
+	data.Id = int(lastId)
+	userIds := strings.Split(data.RecvUserIds, ",")
+	for _, uid := range userIds {
+		if uid == "" {
+			continue
+		}
+		log := g.Map{
+			s.logDao.Columns.MsgId:  lastId,
+			s.logDao.Columns.UserId: uid,
+			s.logDao.Columns.IsRead: "10",
+		}
+		s.logDao.Data(log).Insert()
+		MessageNotify(uid, *data)
+	}
+	return
+}
+
+// UpdateById 修改系统参数
+func (s *MessageService) UpdateById(req *model.UpdateSysMessageReq) (err error) {
+	data := new(model.SysMessage)
+	if err := gconv.Struct(req, data); err != nil {
+		return err
+	}
+	SetUpdatedInfo(data, s.GetCxtUserId(), s.GetCxtUserName())
+	_, err = s.Dao.FieldsEx(UpdateFieldEx...).WherePri(req.Id).Data(data).Update()
+	s.logDao.Where(s.logDao.Columns.MsgId, data.Id).Delete()
+	userIds := strings.Split(data.RecvUserIds, ",")
+	for _, uid := range userIds {
+		if uid == "" {
+			continue
+		}
+		log := g.Map{
+			s.logDao.Columns.MsgId:  data.Id,
+			s.logDao.Columns.UserId: uid,
+			s.logDao.Columns.IsRead: "10",
+		}
+		s.logDao.Data(log).Insert()
+	}
+	return
+}
+
+// DeleteByIds 删除
+func (s *MessageService) DeleteByIds(ids []int64) error {
+	_, err := s.Dao.WhereIn(s.Dao.Columns.Id, ids).Delete()
+	return err
+}
+
+// 全部已读
+func (s *MessageService) AllRead() error {
+	data := g.Map{
+		s.logDao.Columns.IsRead:   "20",
+		s.logDao.Columns.ReadTime: gtime.Now(),
+	}
+	_, err := s.logDao.Where(s.logDao.Columns.UserId, s.GetCxtUserId()).Data(data).Update()
+	return err
+}

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

@@ -0,0 +1,283 @@
+package service
+
+import (
+	"dashoo.cn/micro/app/model"
+	"encoding/json"
+	"fmt"
+	"github.com/gogf/gf/net/ghttp"
+	"github.com/gogf/gf/os/glog"
+	"github.com/gorilla/websocket"
+	"log"
+	"net/http"
+	"sync"
+	"time"
+)
+
+// 管理连接
+func init() {
+	// 检查心跳
+	go func() {
+		defer func() {
+			if r := recover(); r != nil {
+				log.Println(r)
+			}
+		}()
+		heartbeat()
+	}()
+
+	// 注册注销
+	go func() {
+		defer func() {
+			if r := recover(); r != nil {
+				log.Println(r)
+			}
+		}()
+		register()
+	}()
+}
+
+// 客户端连接信息
+type Client struct {
+	ID            string           // 连接ID
+	AccountId     string           // 账号id, 一个账号可能有多个连接
+	Socket        *ghttp.WebSocket // 连接
+	HeartbeatTime int64            // 前一次心跳时间
+}
+
+// 消息类型
+const (
+	MessageTypeHeartbeat = "heartbeat" // 心跳
+	MessageTypeRegister  = "register"  // 注册
+
+	HeartbeatCheckTime = 9  // 心跳检测几秒检测一次
+	HeartbeatTime      = 20 // 心跳距离上一次的最大时间
+
+	ChanBufferRegister   = 100 // 注册chan缓冲
+	ChanBufferUnregister = 100 // 注销chan大小
+)
+
+// 客户端管理
+type ClientManager struct {
+	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),
+}
+
+var (
+	RegisterChan   = make(chan *Client, ChanBufferRegister)   // 注册
+	unregisterChan = make(chan *Client, ChanBufferUnregister) // 注销
+)
+
+// 封装回复消息
+type ServiceMessage struct {
+	Type    string                `json:"type"` // 类型
+	Content ServiceMessageContent `json:"content"`
+}
+
+// Content
+type ServiceMessageContent struct {
+	Body     interface{} `json:"body"`      // 主要数据
+	MetaData interface{} `json:"meta_data"` // 扩展数据
+}
+
+// 创建消息
+func CreateReplyMsg(t string, content ServiceMessageContent) []byte {
+	replyMsg := ServiceMessage{
+		Type:    t,
+		Content: content,
+	}
+	msg, _ := json.Marshal(replyMsg)
+	return msg
+}
+
+// 注册注销
+func register() {
+	for {
+		select {
+		case conn := <-RegisterChan: // 新注册,新连接
+			// 加入连接,进行管理
+			accountBind(conn)
+
+			// 回复消息
+			content := CreateReplyMsg(MessageTypeRegister, ServiceMessageContent{})
+			_ = conn.Socket.WriteMessage(websocket.TextMessage, content)
+
+		case conn := <-unregisterChan: // 注销,或者没有心跳
+			// 关闭连接
+			_ = conn.Socket.Close()
+
+			// 删除Client
+			accountUnBind(conn)
+		}
+	}
+}
+
+// 绑定账号
+func accountBind(c *Client) {
+	Manager.mu.Lock()
+	// 如果不存在链接,加入到连接;若已存在,则替换
+	Manager.Clients[c.ID] = c
+	Manager.mu.Unlock()
+}
+
+// 解绑账号
+func accountUnBind(c *Client) {
+	Manager.mu.Lock()
+	// 取消连接
+	if err := c.Socket.Close(); err != nil {
+		glog.Error(err)
+	}
+	delete(Manager.Clients, c.ID)
+	Manager.mu.Unlock()
+}
+
+// 维持心跳
+func heartbeat() {
+	for {
+		// 获取所有的Clients
+		Manager.mu.Lock()
+		var clients []*Client
+		for _, c := range Manager.Clients {
+			clients = append(clients, c)
+		}
+		Manager.mu.Unlock()
+		glog.Info("开始本次心跳:")
+		for _, c := range clients {
+			glog.Info(c.ID)
+			if time.Now().Unix()-c.HeartbeatTime > HeartbeatTime {
+				accountUnBind(c)
+			}
+		}
+		glog.Info("结束本次心跳:")
+
+		time.Sleep(time.Second * HeartbeatCheckTime)
+	}
+}
+
+// 根据账号获取连接
+func GetClient(uuid string) *Client {
+	Manager.mu.Lock()
+	if c, ok := Manager.Clients[uuid]; ok {
+		Manager.mu.Unlock()
+		return c
+	}
+
+	Manager.mu.Unlock()
+	return nil
+}
+
+// 读取信息,即收到消息 TODO 服务端读取客户的返回消息,并维持心跳
+func (c *Client) Read() {
+	defer func() {
+		fmt.Println("历史数据关闭")
+		_ = c.Socket.Close()
+	}()
+	for {
+		// 读取消息
+		_, body, err := c.Socket.ReadMessage()
+		if err != nil {
+			break
+		}
+
+		var msg struct {
+			Type string `json:"type"`
+		}
+		err = json.Unmarshal(body, &msg)
+		if err != nil {
+			log.Println(err)
+			continue
+		}
+
+		if msg.Type == MessageTypeHeartbeat { // 维持心跳消息
+			// 刷新连接时间
+			c.HeartbeatTime = time.Now().Unix()
+
+			// 回复心跳
+			replyMsg := CreateReplyMsg(MessageTypeHeartbeat, ServiceMessageContent{})
+			err = c.Socket.WriteMessage(websocket.TextMessage, replyMsg)
+			if err != nil {
+				log.Println(err)
+			}
+			continue
+		}
+	}
+}
+
+// 发送消息 TODO 服务端向客户端发送消息
+func Send(uuids []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)
+		}
+	}
+
+	return nil
+}
+
+func MessageNotify(uuid string, data model.SysMessage) {
+	_, ok := Manager.Clients[uuid]
+	if !ok { // 无匹配数据,直接报错
+		glog.Error(fmt.Sprintf("uuid:%v 无匹配连接", uuid))
+		return
+	}
+
+	// 发送消息
+	go func() {
+		var msg ServiceMessage
+		msg.Type = "SendMessage"
+		msg.Content = ServiceMessageContent{
+			Body: data,
+		}
+		err := Send([]string{uuid}, msg)
+		if err != nil {
+			log.Println(err)
+		}
+	}()
+}
+
+func CreateConnection(uuid string, r *ghttp.Request) {
+	// 将http升级为websocket
+	conn, err := r.WebSocket()
+	if err != nil {
+		log.Println(err)
+		http.NotFound(r.Response.Writer, r.Request)
+		return
+	}
+
+	// 创建一个实例连接
+	client := &Client{
+		ID:            uuid, // 连接id
+		AccountId:     uuid,
+		HeartbeatTime: time.Now().Unix(),
+		Socket:        conn,
+	}
+
+	// 用户注册到用户连接管理
+	RegisterChan <- client
+
+	// 发起读取心跳消息
+	go func() {
+		defer func() {
+			if r := recover(); r != nil {
+				log.Printf("MessageNotify read panic: %+v\n", r)
+			}
+		}()
+
+		client.Read()
+	}()
+}

+ 1 - 0
opms_admin/go.mod

@@ -6,6 +6,7 @@ require (
 	dashoo.cn/common_definition v0.0.0
 	dashoo.cn/opms_libary v0.0.0
 	github.com/gogf/gf v1.16.9
+	github.com/gorilla/websocket v1.5.0
 	github.com/mssola/user_agent v0.5.3
 	github.com/smallnest/rpcx v1.8.0
 	golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect

+ 20 - 0
opms_admin/main.go

@@ -3,9 +3,12 @@ package main
 import (
 	"context"
 	"dashoo.cn/micro/app/handler"
+	"dashoo.cn/micro/app/service"
 	"dashoo.cn/opms_libary/micro_srv"
 	"dashoo.cn/opms_libary/myerrors"
+	"fmt"
 	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
 	"github.com/smallnest/rpcx/protocol"
 )
 
@@ -25,6 +28,7 @@ func main() {
 	s.RegisterName("Group", new(handler.GroupHandler), "")
 	s.RegisterName("LoginLog", new(handler.LoginLogHandler), "")
 	s.RegisterName("Config", new(handler.ConfigHandler), "")
+	s.RegisterName("Message", new(handler.MessageHandler), "")
 	s.RegisterName("BaseProductAuth", new(handler.BaseProductAuthHandler), "")
 	s.RegisterName("BaseRegionAuth", new(handler.BaseRegionAuthHandler), "")
 
@@ -35,6 +39,9 @@ func main() {
 	// 错误拦截
 	s.Plugins.Add(&myerrors.HandleErrorPlugin{})
 
+	// 启动websocket服务
+	go websocketStart()
+
 	// 运行服务
 	if err := s.Serve("tcp", srvAddr); err != nil {
 		g.Log().Fatal(err)
@@ -60,3 +67,16 @@ func handleAuth(ctx context.Context, req *protocol.Message, token string) error
 	err := micro_srv.HandleAuth(ctx, req, token, AuthExcludePaths)
 	return err
 }
+
+// 启动websocket服务
+func websocketStart() {
+	s := g.Server()
+	s.BindHandler("/ws", func(r *ghttp.Request) {
+		id := r.GetString("uid")
+		fmt.Println(id)
+		service.CreateConnection(id, r)
+	})
+	// 配置文件获取参数
+	s.SetAddr(g.Config().GetString("setting.websocket-addr"))
+	s.Run()
+}

+ 4 - 4
opms_parent/app/service/proj/business.go

@@ -57,9 +57,9 @@ func (p *businessService) GetList(req *model.ProjBusinessSearchReq) (total int,
 		err = myerrors.DbError("获取总行数失败。")
 		return
 	}
-	db = db.LeftJoin(contractDao.CtrContract.Table, "contract", "proj.id=contract.nbo_id").
-		Fields("proj.*,contract.contract_amount, contract.created_time as projClosingTime").Group("id")
-	err = db.Page(req.PageNum, req.PageSize).Order("id asc").Scan(&businessList)
+	db = db.LeftJoin(contractDao.CtrContract.Table, "contract", "`proj`.id=`contract`.nbo_id").
+		Fields("`proj`.*,`contract`.contract_amount, `contract`.created_time as proj_closing_time").Group("id")
+	err = db.Page(req.PageNum, req.PageSize).OrderDesc("id").Scan(&businessList)
 	return
 }
 
@@ -273,7 +273,7 @@ func (p *businessService) UpdateById(req *model.UpdateProjBusinessReq) error {
 
 	err = p.Dao.Transaction(context.TODO(), func(ctx context.Context, tx *gdb.TX) error {
 		// 更新项目
-		_, err = p.Dao.TX(tx).FieldsEx(service.UpdateFieldEx).WherePri(projDao.ProjBusiness.Columns.Id, req.Id).Update(businessData)
+		_, err = p.Dao.TX(tx).FieldsEx(service.UpdateFieldEx...).WherePri(projDao.ProjBusiness.Columns.Id, req.Id).Update(businessData)
 		if err != nil {
 			return err
 		}