6
0
Quellcode durchsuchen

feature:添加新的公告管理

    新的公告管理不再和之前消息管理一起使用,是一个独立的模块,接口写在了 adapter 中
    更新后的 lims webapi adapter 前端应该一起使用新版本,不能和老版本混用
liuyaqi vor 2 Jahren
Ursprung
Commit
ec95270022

+ 4 - 4
config/config.toml

@@ -1,7 +1,7 @@
 # 应用系统设置
 [setting]
 #    logpath = "/tmp/log/admin"
-    bind-addr = "192.168.0.102:18090"
+    bind-addr = "192.168.0.119:18090"
 #    advertise-addr = "81.68.138.114:19922"
     need-advertise-addr = false
     srv-name = "dashoo.lims.adapter-0.1-jlw"
@@ -34,13 +34,13 @@
 [database]
     [[database.default]]
         Debug = true
-        link = "mysql:root:Dashoo#190801@ali@tcp(192.168.0.252:3306)/lims_test"
+        link = "mysql:root:Dashoo#190801@ali@tcp(192.168.0.252:3306)/lims_test?loc=Local"
     [[database.CU7zm9WhZm]]
         Debug = true
-        link = "mysql:root:Dashoo#190801@ali@tcp(192.168.0.252:3306)/lims_dev"
+        link = "mysql:root:Dashoo#190801@ali@tcp(192.168.0.252:3306)/lims_dev?loc=Local"
     [[database.EmGVD5szuT]]
         Debug = true
-        link = "mysql:root:Dashoo#190801@ali@tcp(192.168.0.252:3306)/lims_dev"
+        link = "mysql:root:Dashoo#190801@ali@tcp(192.168.0.252:3306)/lims_dev?loc=Local"
 
 [micro_srv]
     auth = "dashoo.labsop.auth-2.1,192.168.0.252:19930"

+ 36 - 0
dao/announcement/announcement.go

@@ -0,0 +1,36 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package dao
+
+import (
+	"lims_adapter/dao/announcement/internal"
+)
+
+// announcementDao 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 announcementDao struct {
+	internal.AnnouncementDao
+}
+
+var (
+	// Announcement is globally public accessible object for table announcement operations.
+	Announcement = announcementDao{
+		internal.Announcement,
+	}
+)
+
+type AnnouncementDao struct {
+	internal.AnnouncementDao
+}
+
+func NewAnnouncementDao(tenant string) *AnnouncementDao {
+	dao := internal.NewAnnouncementDao(tenant)
+	return &AnnouncementDao{
+		dao,
+	}
+}
+
+// Fill with you ideas below.

+ 36 - 0
dao/announcement/announcement_file.go

@@ -0,0 +1,36 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package dao
+
+import (
+	"lims_adapter/dao/announcement/internal"
+)
+
+// announcementFileDao 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 announcementFileDao struct {
+	internal.AnnouncementFileDao
+}
+
+var (
+	// AnnouncementFile is globally public accessible object for table announcement_file operations.
+	AnnouncementFile = announcementFileDao{
+		internal.AnnouncementFile,
+	}
+)
+
+type AnnouncementFileDao struct {
+	internal.AnnouncementFileDao
+}
+
+func NewAnnouncementFileDao(tenant string) *AnnouncementFileDao {
+	dao := internal.NewAnnouncementFileDao(tenant)
+	return &AnnouncementFileDao{
+		dao,
+	}
+}
+
+// Fill with you ideas below.

+ 36 - 0
dao/announcement/announcement_read_record.go

@@ -0,0 +1,36 @@
+// ============================================================================
+// This is auto-generated by gf cli tool only once. Fill this file as you wish.
+// ============================================================================
+
+package dao
+
+import (
+	"lims_adapter/dao/announcement/internal"
+)
+
+// announcementReadRecordDao 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 announcementReadRecordDao struct {
+	internal.AnnouncementReadRecordDao
+}
+
+var (
+	// AnnouncementReadRecord is globally public accessible object for table announcement_read_record operations.
+	AnnouncementReadRecord = announcementReadRecordDao{
+		internal.AnnouncementReadRecord,
+	}
+)
+
+type AnnouncementReadRecordDao struct {
+	internal.AnnouncementReadRecordDao
+}
+
+func NewAnnouncementReadRecordDao(tenant string) *AnnouncementReadRecordDao {
+	dao := internal.NewAnnouncementReadRecordDao(tenant)
+	return &AnnouncementReadRecordDao{
+		dao,
+	}
+}
+
+// Fill with you ideas below.

+ 450 - 0
dao/announcement/internal/announcement.go

@@ -0,0 +1,450 @@
+// ==========================================================================
+// 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"
+	"time"
+
+	model "lims_adapter/model/announcement"
+)
+
+// AnnouncementDao is the manager for logic model data accessing
+// and custom defined data operations functions management.
+type AnnouncementDao struct {
+	gmvc.M
+	DB      gdb.DB
+	Table   string
+	Columns announcementColumns
+}
+
+// AnnouncementColumns defines and stores column names for table announcement.
+type announcementColumns struct {
+	Title          string // 标题
+	Content        string // 内容
+	Sender         string // 发送者
+	SenderId       string // 发送者ID
+	Status         string // 状态
+	DeptId         string // 部门Id
+	DeptName       string // 部门名称
+	PublishTime    string // 发布时间
+	Id             string // 主键ID
+	CreatedBy      string // 创建人
+	CreatedAt      string // 创建时间
+	UpdatedBy      string // 更新人
+	UpdatedAt      string // 更新时间
+	DeletedAt      string // 删除时间
+	CreatedById    string // 创建人ID
+	UpdatedById    string // 更新人ID
+	ReceiverDevice string // 接收设备的终端编码
+	Link           string // 链接
+	IsTop          string // 是否置顶
+}
+
+var (
+	// Announcement is globally public accessible object for table announcement operations.
+	Announcement = AnnouncementDao{
+		M:     g.DB("default").Model("announcement").Safe(),
+		DB:    g.DB("default"),
+		Table: "announcement",
+		Columns: announcementColumns{
+			Title:          "Title",
+			Content:        "Content",
+			Sender:         "Sender",
+			SenderId:       "SenderId",
+			Status:         "Status",
+			DeptId:         "DeptId",
+			DeptName:       "DeptName",
+			PublishTime:    "PublishTime",
+			Id:             "Id",
+			CreatedBy:      "CreatedBy",
+			CreatedAt:      "CreatedAt",
+			UpdatedBy:      "UpdatedBy",
+			UpdatedAt:      "UpdatedAt",
+			DeletedAt:      "DeletedAt",
+			CreatedById:    "CreatedById",
+			UpdatedById:    "UpdatedById",
+			ReceiverDevice: "ReceiverDevice",
+			Link:           "Link",
+			IsTop:          "IsTop",
+		},
+	}
+)
+
+func NewAnnouncementDao(tenant string) AnnouncementDao {
+	var dao AnnouncementDao
+	dao = AnnouncementDao{
+		M:     g.DB(tenant).Model("announcement").Safe(),
+		DB:    g.DB(tenant),
+		Table: "announcement",
+		Columns: announcementColumns{
+			Title:          "Title",
+			Content:        "Content",
+			Sender:         "Sender",
+			SenderId:       "SenderId",
+			Status:         "Status",
+			DeptId:         "DeptId",
+			DeptName:       "DeptName",
+			PublishTime:    "PublishTime",
+			Id:             "Id",
+			CreatedBy:      "CreatedBy",
+			CreatedAt:      "CreatedAt",
+			UpdatedBy:      "UpdatedBy",
+			UpdatedAt:      "UpdatedAt",
+			DeletedAt:      "DeletedAt",
+			CreatedById:    "CreatedById",
+			UpdatedById:    "UpdatedById",
+			ReceiverDevice: "ReceiverDevice",
+			Link:           "Link",
+			IsTop:          "IsTop",
+		},
+	}
+	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 *AnnouncementDao) Ctx(ctx context.Context) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.Ctx(ctx)}
+}
+
+// As sets an alias name for current table.
+func (d *AnnouncementDao) As(as string) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (d *AnnouncementDao) TX(tx *gdb.TX) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (d *AnnouncementDao) Master() *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) Slave() *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.Slave()}
+}
+
+// Args sets custom arguments for model operation.
+func (d *AnnouncementDao) Args(args ...interface{}) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.Args(args...)}
+}
+
+// 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 *AnnouncementDao) LeftJoin(table ...string) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) RightJoin(table ...string) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) InnerJoin(table ...string) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) Fields(fieldNamesOrMapStruct ...interface{}) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) FieldsEx(fieldNamesOrMapStruct ...interface{}) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.FieldsEx(fieldNamesOrMapStruct...)}
+}
+
+// Option sets the extra operation option for the model.
+func (d *AnnouncementDao) Option(option int) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) OmitEmpty() *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (d *AnnouncementDao) Filter() *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) Where(where interface{}, args ...interface{}) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) WherePri(where interface{}, args ...interface{}) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.WherePri(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (d *AnnouncementDao) And(where interface{}, args ...interface{}) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (d *AnnouncementDao) Or(where interface{}, args ...interface{}) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (d *AnnouncementDao) Group(groupBy string) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (d *AnnouncementDao) Order(orderBy ...string) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) Limit(limit ...int) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) Offset(offset int) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.Offset(offset)}
+}
+
+// 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 *AnnouncementDao) Page(page, limit int) *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (d *AnnouncementDao) Batch(batch int) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) Cache(duration time.Duration, name ...string) *AnnouncementDao {
+	return &AnnouncementDao{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 *AnnouncementDao) Data(data ...interface{}) *AnnouncementDao {
+	return &AnnouncementDao{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.Announcement.
+// 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 *AnnouncementDao) All(where ...interface{}) ([]*model.Announcement, error) {
+	all, err := d.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*model.Announcement
+	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.Announcement.
+// 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 *AnnouncementDao) One(where ...interface{}) (*model.Announcement, error) {
+	one, err := d.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *model.Announcement
+	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 *AnnouncementDao) FindOne(where ...interface{}) (*model.Announcement, error) {
+	one, err := d.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *model.Announcement
+	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 *AnnouncementDao) FindAll(where ...interface{}) ([]*model.Announcement, error) {
+	all, err := d.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*model.Announcement
+	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 *AnnouncementDao) 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 *AnnouncementDao) 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 *AnnouncementDao) Scan(pointer interface{}, where ...interface{}) error {
+	return d.M.Scan(pointer, where...)
+}
+
+// Chunk iterates the table with given size and callback function.
+func (d *AnnouncementDao) Chunk(limit int, callback func(entities []*model.Announcement, err error) bool) {
+	d.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*model.Announcement
+		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 *AnnouncementDao) LockUpdate() *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (d *AnnouncementDao) LockShared() *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (d *AnnouncementDao) Unscoped() *AnnouncementDao {
+	return &AnnouncementDao{M: d.M.Unscoped()}
+}

+ 429 - 0
dao/announcement/internal/announcement_file.go

@@ -0,0 +1,429 @@
+// ==========================================================================
+// 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"
+	"time"
+
+	model "lims_adapter/model/announcement"
+)
+
+// AnnouncementFileDao is the manager for logic model data accessing
+// and custom defined data operations functions management.
+type AnnouncementFileDao struct {
+	gmvc.M
+	DB      gdb.DB
+	Table   string
+	Columns announcementFileColumns
+}
+
+// AnnouncementFileColumns defines and stores column names for table announcement_file.
+type announcementFileColumns struct {
+	Id         string // 主键ID
+	MessageId  string // 消息id
+	FileName   string // 资料名称
+	FileUrl    string // 资料url
+	FileId     string // 文件服务ID
+	FileSize   string // 大小
+	FileExtend string // 扩展名
+	CreatedBy  string // 创建人
+	CreatedAt  string // 创建时间
+	UpdatedBy  string // 更新人
+	UpdatedAt  string // 更新时间
+	DeletedAt  string // 删除时间
+}
+
+var (
+	// AnnouncementFile is globally public accessible object for table announcement_file operations.
+	AnnouncementFile = AnnouncementFileDao{
+		M:     g.DB("default").Model("announcement_file").Safe(),
+		DB:    g.DB("default"),
+		Table: "announcement_file",
+		Columns: announcementFileColumns{
+			Id:         "Id",
+			MessageId:  "MessageId",
+			FileName:   "FileName",
+			FileUrl:    "FileUrl",
+			FileId:     "FileId",
+			FileSize:   "FileSize",
+			FileExtend: "FileExtend",
+			CreatedBy:  "CreatedBy",
+			CreatedAt:  "CreatedAt",
+			UpdatedBy:  "UpdatedBy",
+			UpdatedAt:  "UpdatedAt",
+			DeletedAt:  "DeletedAt",
+		},
+	}
+)
+
+func NewAnnouncementFileDao(tenant string) AnnouncementFileDao {
+	var dao AnnouncementFileDao
+	dao = AnnouncementFileDao{
+		M:     g.DB(tenant).Model("announcement_file").Safe(),
+		DB:    g.DB(tenant),
+		Table: "announcement_file",
+		Columns: announcementFileColumns{
+			Id:         "Id",
+			MessageId:  "MessageId",
+			FileName:   "FileName",
+			FileUrl:    "FileUrl",
+			FileId:     "FileId",
+			FileSize:   "FileSize",
+			FileExtend: "FileExtend",
+			CreatedBy:  "CreatedBy",
+			CreatedAt:  "CreatedAt",
+			UpdatedBy:  "UpdatedBy",
+			UpdatedAt:  "UpdatedAt",
+			DeletedAt:  "DeletedAt",
+		},
+	}
+	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 *AnnouncementFileDao) Ctx(ctx context.Context) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.Ctx(ctx)}
+}
+
+// As sets an alias name for current table.
+func (d *AnnouncementFileDao) As(as string) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (d *AnnouncementFileDao) TX(tx *gdb.TX) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (d *AnnouncementFileDao) Master() *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) Slave() *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.Slave()}
+}
+
+// Args sets custom arguments for model operation.
+func (d *AnnouncementFileDao) Args(args ...interface{}) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.Args(args...)}
+}
+
+// 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 *AnnouncementFileDao) LeftJoin(table ...string) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) RightJoin(table ...string) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) InnerJoin(table ...string) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) Fields(fieldNamesOrMapStruct ...interface{}) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) FieldsEx(fieldNamesOrMapStruct ...interface{}) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.FieldsEx(fieldNamesOrMapStruct...)}
+}
+
+// Option sets the extra operation option for the model.
+func (d *AnnouncementFileDao) Option(option int) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) OmitEmpty() *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (d *AnnouncementFileDao) Filter() *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) Where(where interface{}, args ...interface{}) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) WherePri(where interface{}, args ...interface{}) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.WherePri(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (d *AnnouncementFileDao) And(where interface{}, args ...interface{}) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (d *AnnouncementFileDao) Or(where interface{}, args ...interface{}) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (d *AnnouncementFileDao) Group(groupBy string) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (d *AnnouncementFileDao) Order(orderBy ...string) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) Limit(limit ...int) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) Offset(offset int) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.Offset(offset)}
+}
+
+// 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 *AnnouncementFileDao) Page(page, limit int) *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (d *AnnouncementFileDao) Batch(batch int) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) Cache(duration time.Duration, name ...string) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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 *AnnouncementFileDao) Data(data ...interface{}) *AnnouncementFileDao {
+	return &AnnouncementFileDao{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.AnnouncementFile.
+// 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 *AnnouncementFileDao) All(where ...interface{}) ([]*model.AnnouncementFile, error) {
+	all, err := d.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*model.AnnouncementFile
+	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.AnnouncementFile.
+// 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 *AnnouncementFileDao) One(where ...interface{}) (*model.AnnouncementFile, error) {
+	one, err := d.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *model.AnnouncementFile
+	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 *AnnouncementFileDao) FindOne(where ...interface{}) (*model.AnnouncementFile, error) {
+	one, err := d.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *model.AnnouncementFile
+	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 *AnnouncementFileDao) FindAll(where ...interface{}) ([]*model.AnnouncementFile, error) {
+	all, err := d.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*model.AnnouncementFile
+	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 *AnnouncementFileDao) 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 *AnnouncementFileDao) 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 *AnnouncementFileDao) Scan(pointer interface{}, where ...interface{}) error {
+	return d.M.Scan(pointer, where...)
+}
+
+// Chunk iterates the table with given size and callback function.
+func (d *AnnouncementFileDao) Chunk(limit int, callback func(entities []*model.AnnouncementFile, err error) bool) {
+	d.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*model.AnnouncementFile
+		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 *AnnouncementFileDao) LockUpdate() *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (d *AnnouncementFileDao) LockShared() *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (d *AnnouncementFileDao) Unscoped() *AnnouncementFileDao {
+	return &AnnouncementFileDao{M: d.M.Unscoped()}
+}

+ 423 - 0
dao/announcement/internal/announcement_read_record.go

@@ -0,0 +1,423 @@
+// ==========================================================================
+// 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"
+	"time"
+
+	model "lims_adapter/model/announcement"
+)
+
+// AnnouncementReadRecordDao is the manager for logic model data accessing
+// and custom defined data operations functions management.
+type AnnouncementReadRecordDao struct {
+	gmvc.M
+	DB      gdb.DB
+	Table   string
+	Columns announcementReadRecordColumns
+}
+
+// AnnouncementReadRecordColumns defines and stores column names for table announcement_read_record.
+type announcementReadRecordColumns struct {
+	Id          string // 主键ID
+	MessageId   string // 消息id
+	UserId      string // 消息id
+	CreatedBy   string // 创建人
+	CreatedAt   string // 创建时间
+	UpdatedBy   string // 更新人
+	UpdatedAt   string // 更新时间
+	DeletedAt   string // 删除时间
+	CreatedById string // 创建人ID
+	UpdatedById string // 更新人ID
+}
+
+var (
+	// AnnouncementReadRecord is globally public accessible object for table announcement_read_record operations.
+	AnnouncementReadRecord = AnnouncementReadRecordDao{
+		M:     g.DB("default").Model("announcement_read_record").Safe(),
+		DB:    g.DB("default"),
+		Table: "announcement_read_record",
+		Columns: announcementReadRecordColumns{
+			Id:          "Id",
+			MessageId:   "MessageId",
+			UserId:      "UserId",
+			CreatedBy:   "CreatedBy",
+			CreatedAt:   "CreatedAt",
+			UpdatedBy:   "UpdatedBy",
+			UpdatedAt:   "UpdatedAt",
+			DeletedAt:   "DeletedAt",
+			CreatedById: "CreatedById",
+			UpdatedById: "UpdatedById",
+		},
+	}
+)
+
+func NewAnnouncementReadRecordDao(tenant string) AnnouncementReadRecordDao {
+	var dao AnnouncementReadRecordDao
+	dao = AnnouncementReadRecordDao{
+		M:     g.DB(tenant).Model("announcement_read_record").Safe(),
+		DB:    g.DB(tenant),
+		Table: "announcement_read_record",
+		Columns: announcementReadRecordColumns{
+			Id:          "Id",
+			MessageId:   "MessageId",
+			UserId:      "UserId",
+			CreatedBy:   "CreatedBy",
+			CreatedAt:   "CreatedAt",
+			UpdatedBy:   "UpdatedBy",
+			UpdatedAt:   "UpdatedAt",
+			DeletedAt:   "DeletedAt",
+			CreatedById: "CreatedById",
+			UpdatedById: "UpdatedById",
+		},
+	}
+	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 *AnnouncementReadRecordDao) Ctx(ctx context.Context) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.Ctx(ctx)}
+}
+
+// As sets an alias name for current table.
+func (d *AnnouncementReadRecordDao) As(as string) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.As(as)}
+}
+
+// TX sets the transaction for current operation.
+func (d *AnnouncementReadRecordDao) TX(tx *gdb.TX) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.TX(tx)}
+}
+
+// Master marks the following operation on master node.
+func (d *AnnouncementReadRecordDao) Master() *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) Slave() *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.Slave()}
+}
+
+// Args sets custom arguments for model operation.
+func (d *AnnouncementReadRecordDao) Args(args ...interface{}) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.Args(args...)}
+}
+
+// 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 *AnnouncementReadRecordDao) LeftJoin(table ...string) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) RightJoin(table ...string) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) InnerJoin(table ...string) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) Fields(fieldNamesOrMapStruct ...interface{}) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) FieldsEx(fieldNamesOrMapStruct ...interface{}) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.FieldsEx(fieldNamesOrMapStruct...)}
+}
+
+// Option sets the extra operation option for the model.
+func (d *AnnouncementReadRecordDao) Option(option int) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) OmitEmpty() *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.OmitEmpty()}
+}
+
+// Filter marks filtering the fields which does not exist in the fields of the operated table.
+func (d *AnnouncementReadRecordDao) Filter() *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) Where(where interface{}, args ...interface{}) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) WherePri(where interface{}, args ...interface{}) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.WherePri(where, args...)}
+}
+
+// And adds "AND" condition to the where statement.
+func (d *AnnouncementReadRecordDao) And(where interface{}, args ...interface{}) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.And(where, args...)}
+}
+
+// Or adds "OR" condition to the where statement.
+func (d *AnnouncementReadRecordDao) Or(where interface{}, args ...interface{}) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.Or(where, args...)}
+}
+
+// Group sets the "GROUP BY" statement for the model.
+func (d *AnnouncementReadRecordDao) Group(groupBy string) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.Group(groupBy)}
+}
+
+// Order sets the "ORDER BY" statement for the model.
+func (d *AnnouncementReadRecordDao) Order(orderBy ...string) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) Limit(limit ...int) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) Offset(offset int) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.Offset(offset)}
+}
+
+// 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 *AnnouncementReadRecordDao) Page(page, limit int) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.Page(page, limit)}
+}
+
+// Batch sets the batch operation number for the model.
+func (d *AnnouncementReadRecordDao) Batch(batch int) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) Cache(duration time.Duration, name ...string) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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 *AnnouncementReadRecordDao) Data(data ...interface{}) *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{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.AnnouncementReadRecord.
+// 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 *AnnouncementReadRecordDao) All(where ...interface{}) ([]*model.AnnouncementReadRecord, error) {
+	all, err := d.M.All(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*model.AnnouncementReadRecord
+	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.AnnouncementReadRecord.
+// 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 *AnnouncementReadRecordDao) One(where ...interface{}) (*model.AnnouncementReadRecord, error) {
+	one, err := d.M.One(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *model.AnnouncementReadRecord
+	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 *AnnouncementReadRecordDao) FindOne(where ...interface{}) (*model.AnnouncementReadRecord, error) {
+	one, err := d.M.FindOne(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entity *model.AnnouncementReadRecord
+	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 *AnnouncementReadRecordDao) FindAll(where ...interface{}) ([]*model.AnnouncementReadRecord, error) {
+	all, err := d.M.FindAll(where...)
+	if err != nil {
+		return nil, err
+	}
+	var entities []*model.AnnouncementReadRecord
+	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 *AnnouncementReadRecordDao) 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 *AnnouncementReadRecordDao) 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 *AnnouncementReadRecordDao) Scan(pointer interface{}, where ...interface{}) error {
+	return d.M.Scan(pointer, where...)
+}
+
+// Chunk iterates the table with given size and callback function.
+func (d *AnnouncementReadRecordDao) Chunk(limit int, callback func(entities []*model.AnnouncementReadRecord, err error) bool) {
+	d.M.Chunk(limit, func(result gdb.Result, err error) bool {
+		var entities []*model.AnnouncementReadRecord
+		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 *AnnouncementReadRecordDao) LockUpdate() *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.LockUpdate()}
+}
+
+// LockShared sets the lock in share mode for current operation.
+func (d *AnnouncementReadRecordDao) LockShared() *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.LockShared()}
+}
+
+// Unscoped enables/disables the soft deleting feature.
+func (d *AnnouncementReadRecordDao) Unscoped() *AnnouncementReadRecordDao {
+	return &AnnouncementReadRecordDao{M: d.M.Unscoped()}
+}

+ 196 - 0
handler/announcement.go

@@ -0,0 +1,196 @@
+package handler
+
+import (
+	"context"
+	"lims_adapter/model/announcement"
+	announcementSrv "lims_adapter/service/announcement"
+
+	"dashoo.cn/common_definition/comm_def"
+	"dashoo.cn/micro_libary/myerrors"
+	"github.com/gogf/gf/frame/g"
+)
+
+type Announcement struct{}
+
+func (c *Announcement) List(ctx context.Context, req *model.AnnouncementListReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.List request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	total, ent, err := s.List(ctx, req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	if ent == nil {
+		ent = []*model.Announcement{}
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	rsp.Data = map[string]interface{}{
+		"total": total,
+		"list":  ent,
+	}
+	return nil
+}
+
+func (c *Announcement) Announcement(ctx context.Context, req *model.AnnouncementAnnouncementReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.Announcement request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	total, ent, err := s.Announcement(ctx, req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	if ent == nil {
+		ent = []*model.AnnouncementWithNew{}
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	rsp.Data = map[string]interface{}{
+		"total": total,
+		"list":  ent,
+	}
+	return nil
+}
+
+func (c *Announcement) Receipt(ctx context.Context, req *model.IdReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.Receipt request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	err = s.Receipt(ctx, req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	return nil
+}
+
+func (c *Announcement) Get(ctx context.Context, req *model.IdReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.Get request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	ent, err := s.Get(ctx, req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	rsp.Data = ent
+	return nil
+}
+
+func (c *Announcement) AddDraft(ctx context.Context, req *model.AnnouncementAddReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.AddDraft request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+
+	req.Status = 1
+	id, err := s.Add(ctx, req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	rsp.Data = id
+	return nil
+}
+
+func (c *Announcement) AddPublish(ctx context.Context, req *model.AnnouncementAddReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.AddPublish request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	req.Status = 2
+	id, err := s.Add(ctx, req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	rsp.Data = id
+	return nil
+}
+
+func (c *Announcement) Update(ctx context.Context, req *model.AnnouncementUpdateReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.Update request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	err = s.Update(ctx, req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	return nil
+}
+
+func (c *Announcement) SetTop(ctx context.Context, req *model.AnnouncementSetTopReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.SetTop request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	err = s.Update(ctx, &model.AnnouncementUpdateReq{
+		Id:    req.Id,
+		IsTop: &req.IsTop,
+	})
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	return nil
+}
+
+func (c *Announcement) Publish(ctx context.Context, req *model.IdReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.Publish request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	err = s.Publish(ctx, req)
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	return nil
+}
+
+func (c *Announcement) Deldraft(ctx context.Context, req *model.IdReq, rsp *comm_def.CommonMsg) error {
+	g.Log().Infof("Announcement.Delete request %#v ", *req)
+	s, err := announcementSrv.NewAnnouncementService(ctx)
+	if err != nil {
+		return err
+	}
+	err = s.Delete(ctx, []int{req.Id})
+	_, err, code, msg := myerrors.CheckError(err)
+	if err != nil {
+		return err
+	}
+	rsp.Code = code
+	rsp.Msg = msg
+	return nil
+}

+ 2 - 0
main.go

@@ -77,6 +77,8 @@ func main() {
 		new((feedback.Feedback)), "")
 	s.RegisterName("Repair",
 		new((repair.Repair)), "")
+	s.RegisterName("Announcement",
+		new((handler.Announcement)), "")
 
 	// 注册auth处理
 	s.AuthFunc = handleAuth

+ 82 - 0
model/announcement/announcement.go

@@ -0,0 +1,82 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. Fill this file as you wish.
+// ==========================================================================
+
+package model
+
+import (
+	"lims_adapter/model/announcement/internal"
+
+	"github.com/gogf/gf/os/gtime"
+)
+
+// Announcement is the golang structure for table announcement.
+type Announcement internal.Announcement
+
+// Fill with you ideas below.
+type AnnouncementListReq struct {
+	Title            string      `json:"title"`
+	Sender           string      `json:"sender"`
+	Status           int         `json:"status"`
+	PublishTimeEnd   *gtime.Time `json:"publish_time_end"`
+	PublishTimeStart *gtime.Time `json:"publish_time_start"`
+	Current          int         `json:"current"`
+	Size             int         `json:"size"`
+}
+
+type AnnouncementAnnouncementReq struct {
+	Current int `json:"current"`
+	Size    int `json:"size"`
+}
+
+type AnnouncementAddReq struct {
+	Title          string     `json:"title" v:"required#请输入标题"` // 标题
+	Content        string     `json:"content" v:"required#请输入内容"`
+	IsTop          bool       `json:"isTop"`          // 是否置顶
+	Link           string     `json:"link"`           // 链接
+	File           []FileInfo `json:"file"`           // 文件
+	DeptId         int        `json:"deptId"`         // 标题
+	ReceiverDevice string     `json:"receiverDevice"` // 接收设备
+	Status         int        `json:"-"`
+}
+
+type AnnouncementUpdateReq struct {
+	Id             int        `json:"id" v:"required#请输入Id"`
+	Title          string     `json:"title"` // 标题
+	Content        string     `json:"content"`
+	IsTop          *bool      `json:"isTop"`          // 是否置顶
+	Link           string     `json:"link"`           // 链接
+	File           []FileInfo `json:"file"`           // 文件
+	DeptId         int        `json:"deptId"`         // 标题
+	ReceiverDevice *string    `json:"receiverDevice"` // 接收设备
+	Status         int        `json:"-"`
+	FileDelete     []int      `json:"file_delete"`
+}
+
+type AnnouncementSetTopReq struct {
+	Id    int  `json:"id" v:"required#请输入Id"`
+	IsTop bool `json:"isTop"` // 是否置顶
+}
+
+type IdReq struct {
+	Id int `json:"id" v:"required#请输入Id"`
+}
+
+type AnnouncementGetResp struct {
+	Announcement
+	ReceiverDeviceName string     `json:"receiverDeviceName"`
+	File               []FileInfo `json:"file"` // 文件
+}
+
+type FileInfo struct {
+	Id         int    `json:"id"`
+	FileExtend string `json:"file_extend"`
+	FileName   string `json:"file_name"`
+	FileSize   int    `json:"file_size"`
+	FileUrl    string `json:"file_url"`
+}
+
+type AnnouncementWithNew struct {
+	Announcement
+	IsNew bool `json:"isNew"`
+}

+ 14 - 0
model/announcement/announcement_file.go

@@ -0,0 +1,14 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. Fill this file as you wish.
+// ==========================================================================
+
+package model
+
+import (
+	"lims_adapter/model/announcement/internal"
+)
+
+// AnnouncementFile is the golang structure for table announcement_file.
+type AnnouncementFile internal.AnnouncementFile
+
+// Fill with you ideas below.

+ 14 - 0
model/announcement/announcement_read_record.go

@@ -0,0 +1,14 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. Fill this file as you wish.
+// ==========================================================================
+
+package model
+
+import (
+	"lims_adapter/model/announcement/internal"
+)
+
+// AnnouncementReadRecord is the golang structure for table announcement_read_record.
+type AnnouncementReadRecord internal.AnnouncementReadRecord
+
+// Fill with you ideas below.

+ 32 - 0
model/announcement/internal/announcement.go

@@ -0,0 +1,32 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. DO NOT EDIT THIS FILE MANUALLY.
+// ==========================================================================
+
+package internal
+
+import (
+	"github.com/gogf/gf/os/gtime"
+)
+
+// Announcement is the golang structure for table announcement.
+type Announcement struct {
+	Title          string      `orm:"Title"          json:"title"`          // 标题
+	Content        string      `orm:"Content"        json:"content"`        // 内容
+	Sender         string      `orm:"Sender"         json:"sender"`         // 发送者
+	SenderId       int         `orm:"SenderId"       json:"senderId"`       // 发送者ID
+	Status         int         `orm:"Status"         json:"status"`         // 状态
+	DeptId         int         `orm:"DeptId"         json:"deptId"`         // 部门Id
+	DeptName       string      `orm:"DeptName"       json:"deptName"`       // 部门名称
+	PublishTime    *gtime.Time `orm:"PublishTime"    json:"publishTime"`    // 发布时间
+	Id             int         `orm:"Id,primary"     json:"id"`             // 主键ID
+	CreatedBy      string      `orm:"CreatedBy"      json:"createdBy"`      // 创建人
+	CreatedAt      *gtime.Time `orm:"CreatedAt"      json:"createdAt"`      // 创建时间
+	UpdatedBy      string      `orm:"UpdatedBy"      json:"updatedBy"`      // 更新人
+	UpdatedAt      *gtime.Time `orm:"UpdatedAt"      json:"updatedAt"`      // 更新时间
+	DeletedAt      *gtime.Time `orm:"DeletedAt"      json:"deletedAt"`      // 删除时间
+	CreatedById    int         `orm:"CreatedById"    json:"createdById"`    // 创建人ID
+	UpdatedById    int         `orm:"UpdatedById"    json:"updatedById"`    // 更新人ID
+	ReceiverDevice string      `orm:"ReceiverDevice" json:"receiverDevice"` // 接收设备的终端编码
+	Link           string      `orm:"Link"           json:"link"`           // 链接
+	IsTop          int         `orm:"IsTop"          json:"isTop"`          // 是否置顶
+}

+ 25 - 0
model/announcement/internal/announcement_file.go

@@ -0,0 +1,25 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. DO NOT EDIT THIS FILE MANUALLY.
+// ==========================================================================
+
+package internal
+
+import (
+	"github.com/gogf/gf/os/gtime"
+)
+
+// AnnouncementFile is the golang structure for table announcement_file.
+type AnnouncementFile struct {
+	Id         int         `orm:"Id,primary" json:"id"`         // 主键ID
+	MessageId  int         `orm:"MessageId"  json:"messageId"`  // 消息id
+	FileName   string      `orm:"FileName"   json:"fileName"`   // 资料名称
+	FileUrl    string      `orm:"FileUrl"    json:"fileUrl"`    // 资料url
+	FileId     string      `orm:"FileId"     json:"fileId"`     // 文件服务ID
+	FileSize   string      `orm:"FileSize"   json:"fileSize"`   // 大小
+	FileExtend string      `orm:"FileExtend" json:"fileExtend"` // 扩展名
+	CreatedBy  string      `orm:"CreatedBy"  json:"createdBy"`  // 创建人
+	CreatedAt  *gtime.Time `orm:"CreatedAt"  json:"createdAt"`  // 创建时间
+	UpdatedBy  string      `orm:"UpdatedBy"  json:"updatedBy"`  // 更新人
+	UpdatedAt  *gtime.Time `orm:"UpdatedAt"  json:"updatedAt"`  // 更新时间
+	DeletedAt  *gtime.Time `orm:"DeletedAt"  json:"deletedAt"`  // 删除时间
+}

+ 23 - 0
model/announcement/internal/announcement_read_record.go

@@ -0,0 +1,23 @@
+// ==========================================================================
+// This is auto-generated by gf cli tool. DO NOT EDIT THIS FILE MANUALLY.
+// ==========================================================================
+
+package internal
+
+import (
+	"github.com/gogf/gf/os/gtime"
+)
+
+// AnnouncementReadRecord is the golang structure for table announcement_read_record.
+type AnnouncementReadRecord struct {
+	Id          int         `orm:"Id,primary"  json:"id"`          // 主键ID
+	MessageId   int         `orm:"MessageId"   json:"messageId"`   // 消息id
+	UserId      int         `orm:"UserId"      json:"userId"`      // 消息id
+	CreatedBy   string      `orm:"CreatedBy"   json:"createdBy"`   // 创建人
+	CreatedAt   *gtime.Time `orm:"CreatedAt"   json:"createdAt"`   // 创建时间
+	UpdatedBy   string      `orm:"UpdatedBy"   json:"updatedBy"`   // 更新人
+	UpdatedAt   *gtime.Time `orm:"UpdatedAt"   json:"updatedAt"`   // 更新时间
+	DeletedAt   *gtime.Time `orm:"DeletedAt"   json:"deletedAt"`   // 删除时间
+	CreatedById int         `orm:"CreatedById" json:"createdById"` // 创建人ID
+	UpdatedById int         `orm:"UpdatedById" json:"updatedById"` // 更新人ID
+}

+ 459 - 0
service/announcement/announcement.go

@@ -0,0 +1,459 @@
+package announcement
+
+import (
+	"context"
+	"database/sql"
+	"fmt"
+	"lims_adapter/dao/announcement"
+	"lims_adapter/model/announcement"
+	"strconv"
+	"strings"
+
+	"dashoo.cn/micro_libary/micro_srv"
+	"dashoo.cn/micro_libary/myerrors"
+	"dashoo.cn/micro_libary/request"
+	"github.com/gogf/gf/database/gdb"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/gtime"
+	"github.com/gogf/gf/util/gvalid"
+)
+
+type AnnouncementService struct {
+	Tenant        string
+	userInfo      request.UserInfo
+	DB            gdb.DB
+	Dao           *dao.AnnouncementDao
+	FileDao       *dao.AnnouncementFileDao
+	ReadRecordDao *dao.AnnouncementReadRecordDao
+}
+
+func NewAnnouncementService(ctx context.Context) (*AnnouncementService, error) {
+	tenant, err := micro_srv.GetTenant(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("获取组合码异常:%s", err.Error())
+	}
+	// 获取用户信息
+	userInfo, err := micro_srv.GetUserInfo(ctx)
+	if err != nil {
+		return nil, fmt.Errorf("获取用户信息异常:%s", err.Error())
+	}
+	return &AnnouncementService{
+		Tenant:        tenant,
+		userInfo:      userInfo,
+		DB:            g.DB(tenant),
+		Dao:           dao.NewAnnouncementDao(tenant),
+		FileDao:       dao.NewAnnouncementFileDao(tenant),
+		ReadRecordDao: dao.NewAnnouncementReadRecordDao(tenant),
+	}, nil
+}
+
+func (s AnnouncementService) Get(ctx context.Context, req *model.IdReq) (*model.AnnouncementGetResp, error) {
+	ent, err := s.Dao.Where("Id = ?", req.Id).One()
+	if err != nil {
+		return nil, err
+	}
+	if ent == nil {
+		return nil, myerrors.NewMsgError(nil, "反馈不存在")
+	}
+	f, err := s.FileDao.Where("MessageId = ?", req.Id).All()
+	if err != nil {
+		return nil, err
+	}
+	file := []model.FileInfo{}
+	for _, i := range f {
+		filesize, _ := strconv.Atoi(i.FileSize)
+		file = append(file, model.FileInfo{
+			Id:         i.Id,
+			FileExtend: i.FileExtend,
+			FileName:   i.FileName,
+			FileSize:   filesize,
+			FileUrl:    i.FileUrl,
+		})
+	}
+
+	names := []string{}
+	if ent.ReceiverDevice != "" {
+		names, err = ColumnString(s.DB.Table("instrument").Where("Terminal in (?)", strings.Split(ent.ReceiverDevice, ",")), "Name")
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return &model.AnnouncementGetResp{
+		Announcement:       *ent,
+		ReceiverDeviceName: strings.Join(names, ", "),
+		File:               file,
+	}, nil
+}
+
+func (s AnnouncementService) List(ctx context.Context, req *model.AnnouncementListReq) (int, []*model.Announcement, error) {
+	dao := &s.Dao.AnnouncementDao
+	if req.Title != "" {
+		dao = dao.Where("Title LIKE ?", fmt.Sprintf("%%%s%%", req.Title))
+	}
+	if req.Sender != "" {
+		dao = dao.Where("Sender LIKE ?", fmt.Sprintf("%%%s%%", req.Sender))
+	}
+	if req.Status != 0 {
+		dao = dao.Where("Status = ?", req.Status)
+	}
+	if req.PublishTimeStart != nil {
+		dao = dao.Where("PublishTime > ?", req.PublishTimeStart)
+	}
+	if req.PublishTimeEnd != nil {
+		dao = dao.Where("PublishTime < ?", req.PublishTimeEnd)
+	}
+
+	total, err := dao.Count()
+	if err != nil {
+		return 0, nil, err
+	}
+
+	if req.Current == 0 {
+		req.Current = 1
+	}
+	if req.Size == 0 {
+		req.Size = 10
+	}
+	dao = dao.Page(req.Current, req.Size)
+	dao = dao.Order("IsTop desc,CreatedAt desc")
+	ent, err := dao.All()
+	return total, ent, err
+}
+
+func (s AnnouncementService) Announcement(ctx context.Context, req *model.AnnouncementAnnouncementReq) (int, []*model.AnnouncementWithNew, error) {
+	dept := organization{}
+	err := s.DB.Table("base_organize").
+		Where("Enabled = ?", 1).
+		Where("Id = ?", s.userInfo.DeptId).
+		Struct(&dept)
+	if err != nil {
+		return 0, nil, fmt.Errorf("查询用户部门错误 %s", err)
+	}
+	ret := []*model.AnnouncementWithNew{}
+	err = s.DB.Table("announcement a").
+		LeftJoin(fmt.Sprintf("(select `Id`,`MessageId`,`UserId` from announcement_read_record where `UserId`=%d) b", s.userInfo.Id), "a.Id=b.MessageId").
+		Where("a.Status = 2").
+		Where("a.DeptId in (?)", strings.Split(dept.Paths, "/")).
+		Fields("a.*, b.Id is null as IsNew").Structs(&ret)
+	return 0, ret, err
+}
+
+func (s AnnouncementService) Receipt(ctx context.Context, req *model.IdReq) error {
+	validErr := gvalid.CheckStruct(ctx, req, nil)
+	if validErr != nil {
+		return myerrors.NewMsgError(nil, validErr.Current().Error())
+	}
+
+	ent, err := s.Dao.Where("id = ?", req.Id).One()
+	if err != nil {
+		return err
+	}
+	if ent == nil {
+		return myerrors.NewMsgError(nil, fmt.Sprintf("公告不存在: %d", req.Id))
+	}
+	count, err := s.ReadRecordDao.Where("MessageId = ?", req.Id).Where("UserId = ?", s.userInfo.Id).Count()
+	if err != nil {
+		return err
+	}
+	if count == 0 {
+		_, err := s.ReadRecordDao.Insert(model.AnnouncementReadRecord{
+			MessageId:   req.Id,
+			UserId:      int(s.userInfo.Id),
+			CreatedBy:   s.userInfo.RealName,
+			CreatedAt:   gtime.Now(),
+			UpdatedBy:   s.userInfo.RealName,
+			UpdatedAt:   gtime.Now(),
+			CreatedById: int(s.userInfo.Id),
+			UpdatedById: int(s.userInfo.Id),
+		})
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s AnnouncementService) BindFile(tx *gdb.TX, msgId int, fileinfo []model.FileInfo, fileDelete []int) error {
+	if len(fileDelete) != 0 {
+		_, err := tx.Delete("announcement_file", "id in (?)", fileDelete)
+		if err != nil {
+			return err
+		}
+	}
+	file := []model.AnnouncementFile{}
+	for _, i := range fileinfo {
+		file = append(file, model.AnnouncementFile{
+			MessageId:  msgId,
+			FileName:   i.FileName,
+			FileUrl:    i.FileUrl,
+			FileId:     "",
+			FileSize:   strconv.Itoa(i.FileSize),
+			FileExtend: i.FileExtend,
+			CreatedBy:  s.userInfo.RealName,
+			CreatedAt:  gtime.Now(),
+			UpdatedBy:  s.userInfo.RealName,
+			UpdatedAt:  gtime.Now(),
+		})
+	}
+	if len(file) != 0 {
+		_, err := tx.Insert("announcement_file", file)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+type organize struct {
+	Id       int
+	FullName string
+}
+
+func (s AnnouncementService) Add(ctx context.Context, req *model.AnnouncementAddReq) (int, error) {
+	validErr := gvalid.CheckStruct(ctx, req, nil)
+	if validErr != nil {
+		return 0, myerrors.NewMsgError(nil, validErr.Current().Error())
+	}
+	if req.DeptId == 0 && len(req.ReceiverDevice) == 0 {
+		return 0, myerrors.NewMsgError(nil, fmt.Sprintf("请输入通知对象"))
+	}
+
+	dept := &organize{}
+	err := s.DB.Table("base_organize").Where("Id = ?", req.DeptId).Struct(dept)
+	if err == sql.ErrNoRows {
+		return 0, myerrors.NewMsgError(err, fmt.Sprintf("所选部门不存在"))
+	}
+	if err != nil {
+		return 0, err
+	}
+
+	var msgId int
+	txerr := s.Dao.DB.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
+		publishTime := gtime.Now()
+		if req.Status == 1 {
+			publishTime = nil
+		}
+		istop := 0
+		if req.IsTop {
+			istop = 1
+		}
+		id, err := tx.InsertAndGetId("announcement", model.Announcement{
+			Title:          req.Title,
+			Content:        req.Content,
+			Sender:         s.userInfo.RealName,
+			SenderId:       int(s.userInfo.Id),
+			Status:         req.Status,
+			DeptId:         req.DeptId,
+			DeptName:       dept.FullName,
+			PublishTime:    publishTime,
+			CreatedBy:      s.userInfo.RealName,
+			CreatedAt:      gtime.Now(),
+			UpdatedBy:      s.userInfo.RealName,
+			UpdatedAt:      gtime.Now(),
+			CreatedById:    int(s.userInfo.Id),
+			UpdatedById:    int(s.userInfo.Id),
+			ReceiverDevice: req.ReceiverDevice,
+			Link:           req.Link,
+			IsTop:          istop,
+		})
+		if err != nil {
+			return err
+		}
+		err = s.BindFile(tx, int(id), req.File, nil)
+		if err != nil {
+			return err
+		}
+		msgId = int(id)
+		return nil
+	})
+	return msgId, txerr
+}
+
+func (s AnnouncementService) Update(ctx context.Context, req *model.AnnouncementUpdateReq) error {
+	validErr := gvalid.CheckStruct(ctx, req, nil)
+	if validErr != nil {
+		return myerrors.NewMsgError(nil, validErr.Current().Error())
+	}
+
+	ent, err := s.Dao.Where("id = ?", req.Id).One()
+	if err != nil {
+		return err
+	}
+	if ent == nil {
+		return myerrors.NewMsgError(nil, fmt.Sprintf("公告不存在: %d", req.Id))
+	}
+
+	dept := &organize{}
+	if req.DeptId != 0 {
+		err := s.DB.Table("base_organize").Where("Id = ?", req.DeptId).Struct(dept)
+		if err == sql.ErrNoRows {
+			return myerrors.NewMsgError(err, fmt.Sprintf("所选部门不存在"))
+		}
+		if err != nil {
+			return err
+		}
+	}
+
+	toupdate := map[string]interface{}{}
+	if req.Title != "" {
+		toupdate["Title"] = req.Title
+	}
+	if req.Content != "" {
+		toupdate["Content"] = req.Content
+	}
+	if req.IsTop != nil {
+		toupdate["IsTop"] = *req.IsTop
+	}
+	if req.Link != "" {
+		toupdate["Link"] = req.Link
+	}
+	if req.DeptId != 0 {
+		toupdate["DeptId"] = req.DeptId
+		toupdate["DeptName"] = dept.FullName
+	}
+	if req.ReceiverDevice != nil {
+		toupdate["ReceiverDevice"] = req.ReceiverDevice
+	}
+
+	txerr := s.Dao.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
+		if len(toupdate) != 0 {
+			toupdate["UpdatedAt"] = gtime.Now()
+			toupdate["UpdatedBy"] = s.userInfo.RealName
+			toupdate["UpdatedById"] = int(s.userInfo.Id)
+			_, err := tx.Update("announcement", toupdate, "Id = ?", req.Id)
+			if err != nil {
+				return err
+			}
+		}
+		err = s.BindFile(tx, req.Id, req.File, req.FileDelete)
+		if err != nil {
+			return err
+		}
+		return nil
+	})
+	return txerr
+}
+
+func (s AnnouncementService) Publish(ctx context.Context, req *model.IdReq) error {
+	validErr := gvalid.CheckStruct(ctx, req, nil)
+	if validErr != nil {
+		return myerrors.NewMsgError(nil, validErr.Current().Error())
+	}
+
+	ent, err := s.Dao.Where("id = ?", req.Id).One()
+	if err != nil {
+		return err
+	}
+	if ent == nil {
+		return myerrors.NewMsgError(nil, fmt.Sprintf("公告不存在: %d", req.Id))
+	}
+	if ent.Status != 1 {
+		return myerrors.NewMsgError(nil, fmt.Sprintf("当前公告不可发布"))
+	}
+	_, err = s.Dao.Data(map[string]interface{}{
+		"Sender":      s.userInfo.RealName,
+		"SenderId":    int(s.userInfo.Id),
+		"Status":      2,
+		"PublishTime": gtime.Now(),
+	}).Where("Id = ?", req.Id).Update()
+	return err
+}
+
+func (s AnnouncementService) Delete(ctx context.Context, id []int) error {
+	if len(id) == 0 {
+		return nil
+	}
+	_, err := s.Dao.Where("Id IN (?)", id).Delete()
+	if err != nil {
+		return err
+	}
+	_, err = s.FileDao.Where("MessageId IN (?)", id).Delete()
+	if err != nil {
+		return err
+	}
+	_, err = s.ReadRecordDao.Where("MessageId IN (?)", id).Delete()
+	return err
+}
+
+func ColumnString(m *gdb.Model, name string) ([]string, error) {
+	v, err := m.Fields(name).Array()
+	if err != nil {
+		return nil, err
+	}
+	res := []string{}
+	for _, i := range v {
+		res = append(res, i.String())
+	}
+	return res, nil
+}
+
+type organization struct {
+	Id       int    `orm:"Id,primary"       json:"id"`       //
+	ParentId int    `orm:"ParentId"         json:"parentId"` // 上级组织id
+	FullName string `orm:"FullName"         json:"fullName"` // 名称
+	Paths    string `orm:"Paths"            json:"paths"`    // 路径索引
+}
+
+type DeptNode struct {
+	Id      int
+	Name    string
+	SubDept []*DeptNode
+}
+
+func (n DeptNode) String() string {
+	Subdept := "{"
+	for _, i := range n.SubDept {
+		Subdept = Subdept + i.String() + ","
+	}
+	Subdept = Subdept + "}"
+	return fmt.Sprintf("DeptNode{id=%d, name=%s, SubDept=%s}", n.Id, n.Name, Subdept)
+}
+
+func DeptTree(tenant string, id int) (*DeptNode, error) {
+	deptlist := []organization{}
+	err := g.DB(tenant).Table("base_organize").
+		Where("Enabled = ?", 1).Structs(&deptlist)
+	if err == sql.ErrNoRows {
+		err = nil
+	}
+	if err != nil {
+		return nil, err
+	}
+	deptMap := map[int]*DeptNode{}
+	for _, d := range deptlist {
+		deptMap[d.Id] = &DeptNode{
+			Id:   d.Id,
+			Name: d.FullName,
+		}
+	}
+	for _, d := range deptlist {
+		if d.ParentId == 0 {
+			continue
+		}
+		deptMap[d.ParentId].SubDept = append(deptMap[d.ParentId].SubDept, deptMap[d.Id])
+	}
+	for _, d := range deptMap {
+		if d.Id == id {
+			return d, nil
+		}
+	}
+	return nil, nil
+}
+
+func RangeDeptTree(node *DeptNode) []*DeptNode {
+	if node == nil {
+		return nil
+	}
+	allnode := []*DeptNode{}
+	nodes := []*DeptNode{node}
+	for len(nodes) != 0 {
+		newnode := []*DeptNode{}
+		for _, n := range nodes {
+			allnode = append(allnode, n)
+			newnode = append(newnode, n.SubDept...)
+		}
+		nodes = newnode
+	}
+	return allnode
+}

+ 2 - 0
service/equipment/equipment.go

@@ -423,6 +423,8 @@ func (s Service) AppointTimeInfo(req *equipment2.AppointTimeInfoReq, userinfo re
 
 	return map[string]interface{}{
 		"time_split":  timesplit,
+		"begin_at":    instr.BeginAt.Time.Format("15:04:05"),
+		"ent_at":      instr.EndAt.Time.Format("15:04:05"),
 		"unavailable": unavailable,
 		"appoint":     appointInfo,
 	}, nil

+ 2 - 0
service/reservation/reservation.go

@@ -302,6 +302,8 @@ func (s Service) AppointTimeInfo(req *meeting3.AppointTimeInfoReq, userinfo requ
 	}
 
 	return map[string]interface{}{
+		"begin_at":    m.BeginAt.Time.Format("15:04:05"),
+		"ent_at":      m.EndAt.Time.Format("15:04:05"),
 		"time_split":  timesplit,
 		"unavailable": unavailable,
 		"appoint":     appointInfo,

+ 56 - 0
sql/tmp.sql

@@ -1,3 +1,59 @@
 -- 2023-04-03
 alter table meeting add `BeginAt` datetime DEFAULT NULL COMMENT '预约开始时段' after Scale;
 alter table meeting add `EndAt` datetime DEFAULT NULL COMMENT '预约结束时段' after BeginAt;
+
+CREATE TABLE `announcement` (
+  `Title` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '标题',
+  `Content` text COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '内容',
+  `Sender` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '发送者',
+  `SenderId` int(11) DEFAULT NULL COMMENT '发送者ID',
+  `Status` int(11) DEFAULT NULL COMMENT '状态',
+  `DeptId` int(11) NOT NULL COMMENT '部门Id',
+  `DeptName` varchar(255) NOT NULL COMMENT '部门名称',
+  `PublishTime` datetime DEFAULT NULL COMMENT '发布时间',
+  `Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `CreatedBy` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '创建人',
+  `CreatedAt` datetime NOT NULL COMMENT '创建时间',
+  `UpdatedBy` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '更新人',
+  `UpdatedAt` datetime NOT NULL COMMENT '更新时间',
+  `DeletedAt` datetime DEFAULT NULL COMMENT '删除时间',
+  `CreatedById` int(11) NOT NULL COMMENT '创建人ID',
+  `UpdatedById` int(11) NOT NULL COMMENT '更新人ID',
+  `ReceiverDevice` text COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '接收设备的终端编码',
+  `Link` text COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '链接',
+  `IsTop` tinyint(1) DEFAULT NULL COMMENT '是否置顶',
+  PRIMARY KEY (`Id`) USING BTREE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC COMMENT = '公告';
+
+CREATE TABLE `announcement_file` (
+  `Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `MessageId` int(11) DEFAULT NULL COMMENT '消息id',
+  `FileName` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '资料名称',
+  `FileUrl` varchar(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '资料url',
+  `FileId` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件服务ID',
+  `FileSize` varchar(25) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '大小',
+  `FileExtend` varchar(25) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '扩展名',
+  `CreatedBy` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '创建人',
+  `CreatedAt` datetime DEFAULT NULL COMMENT '创建时间',
+  `UpdatedBy` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '更新人',
+  `UpdatedAt` datetime DEFAULT NULL COMMENT '更新时间',
+  `DeletedAt` datetime DEFAULT NULL COMMENT '删除时间',
+  KEY (`MessageId`),
+  PRIMARY KEY (`Id`) USING BTREE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC COMMENT = '公告附件信息表';
+
+CREATE TABLE `announcement_read_record` (
+  `Id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `MessageId` int(11) DEFAULT NULL COMMENT '消息id',
+  `UserId` int(11) DEFAULT NULL COMMENT '消息id',
+  `CreatedBy` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '创建人',
+  `CreatedAt` datetime NOT NULL COMMENT '创建时间',
+  `UpdatedBy` varchar(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '更新人',
+  `UpdatedAt` datetime NOT NULL COMMENT '更新时间',
+  `DeletedAt` datetime DEFAULT NULL COMMENT '删除时间',
+  `CreatedById` int(11) NOT NULL COMMENT '创建人ID',
+  `UpdatedById` int(11) NOT NULL COMMENT '更新人ID',
+  KEY (`MessageId`),
+  KEY (`UserId`),
+  PRIMARY KEY (`Id`) USING BTREE
+) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC COMMENT = '公告已读记录';