refactor(消息): 重构消息已读状态处理

将消息已读状态从单独的表迁移到消息表,简化架构
移除标记消息已读的独立接口,改为直接更新消息表
更新相关模型、路由和文档以反映架构变更
This commit is contained in:
邹方成 2025-10-18 19:41:08 +08:00
parent 1a285f4e23
commit 4a40520a80
13 changed files with 74 additions and 846 deletions

View File

@ -1316,46 +1316,6 @@ const docTemplate = `{
}
}
},
"/app/messages/read": {
"post": {
"description": "标记指定消息为已读状态",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户端"
],
"summary": "标记消息为已读",
"parameters": [
{
"description": "请求参数",
"name": "RequestBody",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/message.markMessageReadRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/message.markMessageReadResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/app/send_message": {
"post": {
"description": "用户发送消息",
@ -2364,36 +2324,6 @@ const docTemplate = `{
}
}
},
"message.markMessageReadRequest": {
"type": "object",
"required": [
"app_id",
"message_id",
"user_id"
],
"properties": {
"app_id": {
"description": "小程序ID",
"type": "string"
},
"message_id": {
"description": "消息ID",
"type": "integer"
},
"user_id": {
"description": "用户ID",
"type": "string"
}
}
},
"message.markMessageReadResponse": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"message.userSendMessageRequest": {
"type": "object",
"required": [

View File

@ -1308,46 +1308,6 @@
}
}
},
"/app/messages/read": {
"post": {
"description": "标记指定消息为已读状态",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"用户端"
],
"summary": "标记消息为已读",
"parameters": [
{
"description": "请求参数",
"name": "RequestBody",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/message.markMessageReadRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/message.markMessageReadResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/app/send_message": {
"post": {
"description": "用户发送消息",
@ -2356,36 +2316,6 @@
}
}
},
"message.markMessageReadRequest": {
"type": "object",
"required": [
"app_id",
"message_id",
"user_id"
],
"properties": {
"app_id": {
"description": "小程序ID",
"type": "string"
},
"message_id": {
"description": "消息ID",
"type": "integer"
},
"user_id": {
"description": "用户ID",
"type": "string"
}
}
},
"message.markMessageReadResponse": {
"type": "object",
"properties": {
"message": {
"type": "string"
}
}
},
"message.userSendMessageRequest": {
"type": "object",
"required": [

View File

@ -656,27 +656,6 @@ definitions:
description: 发送人昵称
type: string
type: object
message.markMessageReadRequest:
properties:
app_id:
description: 小程序ID
type: string
message_id:
description: 消息ID
type: integer
user_id:
description: 用户ID
type: string
required:
- app_id
- message_id
- user_id
type: object
message.markMessageReadResponse:
properties:
message:
type: string
type: object
message.userSendMessageRequest:
properties:
app_id:
@ -1617,32 +1596,6 @@ paths:
summary: 获取消息日志
tags:
- 用户端
/app/messages/read:
post:
consumes:
- application/json
description: 标记指定消息为已读状态
parameters:
- description: 请求参数
in: body
name: RequestBody
required: true
schema:
$ref: '#/definitions/message.markMessageReadRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/message.markMessageReadResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
summary: 标记消息为已读
tags:
- 用户端
/app/send_message:
post:
consumes:

View File

@ -3,13 +3,11 @@ package app
import (
"fmt"
"net/http"
"time"
"mini-chat/internal/code"
"mini-chat/internal/pkg/core"
"mini-chat/internal/pkg/timeutil"
"mini-chat/internal/pkg/validation"
"mini-chat/internal/repository/mysql/model"
)
type latestMessageByAppIdRequest struct {
@ -110,38 +108,13 @@ func (h *handler) LatestMessageByAppId() core.HandlerFunc {
}
// 自动标记该appid下的所有消息为已读管理端访问时
// 这里我们为每个消息的接收者创建或更新已读状态
for _, message := range resultData {
// 检查是否已存在已读状态记录
existingStatus, _ := h.readDB.AppMessageReadStatus.WithContext(ctx.RequestContext()).
Where(h.readDB.AppMessageReadStatus.AppID.Eq(req.AppID)).
Where(h.readDB.AppMessageReadStatus.MessageID.Eq(message.ID)).
Where(h.readDB.AppMessageReadStatus.UserID.Eq(message.ReceiverID)).
First()
if existingStatus == nil {
// 如果不存在,创建新的已读状态记录
now := time.Now()
_ = h.writeDB.AppMessageReadStatus.WithContext(ctx.RequestContext()).Create(&model.AppMessageReadStatus{
AppID: req.AppID,
MessageID: message.ID,
UserID: message.ReceiverID,
IsRead: 1,
ReadTime: &now,
CreatedAt: now,
UpdatedAt: now,
})
} else if existingStatus.IsRead == 0 {
// 如果存在但未读,更新为已读
now := time.Now()
_, _ = h.writeDB.AppMessageReadStatus.WithContext(ctx.RequestContext()).
Where(h.writeDB.AppMessageReadStatus.ID.Eq(existingStatus.ID)).
Updates(map[string]interface{}{
"is_read": 1,
"read_time": &now,
"updated_at": now,
})
}
_, err = h.writeDB.AppMessageLog.WithContext(ctx.RequestContext()).
Where(h.writeDB.AppMessageLog.AppID.Eq(req.AppID)).
Where(h.writeDB.AppMessageLog.IsRead.Eq(0)).
Update(h.writeDB.AppMessageLog.IsRead, 1)
if err != nil {
// 记录错误但不影响查询结果
// TODO: 可以添加日志记录
}
res.Page = req.Page
@ -150,24 +123,11 @@ func (h *handler) LatestMessageByAppId() core.HandlerFunc {
res.List = make([]latestMessageData, len(resultData))
for k, v := range resultData {
// 查询该消息的已读状态
readStatus, _ := h.readDB.AppMessageReadStatus.WithContext(ctx.RequestContext()).
Where(h.readDB.AppMessageReadStatus.AppID.Eq(req.AppID)).
Where(h.readDB.AppMessageReadStatus.MessageID.Eq(v.ID)).
Where(h.readDB.AppMessageReadStatus.UserID.Eq(v.ReceiverID)).
First()
// 判断是否已读
isRead := int32(0)
if readStatus != nil && readStatus.IsRead == 1 {
isRead = 1
}
// 计算该用户在该应用下的未读消息总数
unreadCount, _ := h.readDB.AppMessageReadStatus.WithContext(ctx.RequestContext()).
Where(h.readDB.AppMessageReadStatus.AppID.Eq(req.AppID)).
Where(h.readDB.AppMessageReadStatus.UserID.Eq(v.ReceiverID)).
Where(h.readDB.AppMessageReadStatus.IsRead.Eq(0)).
unreadCount, _ := h.readDB.AppMessageLog.WithContext(ctx.RequestContext()).
Where(h.readDB.AppMessageLog.AppID.Eq(req.AppID)).
Where(h.readDB.AppMessageLog.ReceiverID.Eq(v.ReceiverID)).
Where(h.readDB.AppMessageLog.IsRead.Eq(0)).
Count()
res.List[k] = latestMessageData{
@ -179,7 +139,7 @@ func (h *handler) LatestMessageByAppId() core.HandlerFunc {
ReceiverID: v.ReceiverID,
Content: v.Content,
MsgType: v.MsgType,
IsRead: isRead,
IsRead: v.IsRead, // 直接使用消息表中的 is_read 字段
UnreadCount: unreadCount,
}
}

View File

@ -1,115 +0,0 @@
package message
import (
"fmt"
"net/http"
"time"
"mini-chat/internal/code"
"mini-chat/internal/pkg/core"
"mini-chat/internal/pkg/validation"
"mini-chat/internal/repository/mysql/model"
)
type markMessageReadRequest struct {
AppID string `json:"app_id" binding:"required"` // 小程序ID
UserID string `json:"user_id" binding:"required"` // 用户ID
MessageID int32 `json:"message_id" binding:"required"` // 消息ID
}
type markMessageReadResponse struct {
Message string `json:"message"`
}
// MarkMessageRead 标记消息为已读
// @Summary 标记消息为已读
// @Description 标记指定消息为已读状态
// @Tags 用户端
// @Accept json
// @Produce json
// @Param RequestBody body markMessageReadRequest true "请求参数"
// @Success 200 {object} markMessageReadResponse
// @Failure 400 {object} code.Failure
// @Router /app/messages/read [post]
func (h *handler) MarkMessageRead() core.HandlerFunc {
return func(ctx core.Context) {
req := new(markMessageReadRequest)
res := new(markMessageReadResponse)
if err := ctx.ShouldBindJSON(req); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)),
)
return
}
// 检查消息是否存在
message, err := h.readDB.AppMessageLog.WithContext(ctx.RequestContext()).
Where(h.readDB.AppMessageLog.ID.Eq(req.MessageID)).
Where(h.readDB.AppMessageLog.AppID.Eq(req.AppID)).
First()
if err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ListMessageError,
fmt.Sprintf("消息不存在:%s", err.Error())),
)
return
}
// 检查用户是否有权限标记此消息为已读(只有接收者可以标记)
if message.ReceiverID != req.UserID {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ListMessageError,
"只有消息接收者可以标记消息为已读"),
)
return
}
// 检查是否已经标记为已读
existingReadStatus, _ := h.readDB.AppMessageReadStatus.WithContext(ctx.RequestContext()).
Where(h.readDB.AppMessageReadStatus.AppID.Eq(req.AppID)).
Where(h.readDB.AppMessageReadStatus.MessageID.Eq(req.MessageID)).
Where(h.readDB.AppMessageReadStatus.UserID.Eq(req.UserID)).
First()
now := time.Now()
if existingReadStatus != nil {
// 如果已存在记录,更新为已读状态
_, err = h.writeDB.AppMessageReadStatus.WithContext(ctx.RequestContext()).
Where(h.writeDB.AppMessageReadStatus.ID.Eq(existingReadStatus.ID)).
Updates(map[string]interface{}{
"is_read": 1,
"read_time": &now,
"updated_at": now,
})
} else {
// 如果不存在记录,创建新的已读状态记录
newReadStatus := &model.AppMessageReadStatus{
AppID: req.AppID,
MessageID: req.MessageID,
UserID: req.UserID,
IsRead: 1,
ReadTime: &now,
CreatedAt: now,
UpdatedAt: now,
}
err = h.writeDB.AppMessageReadStatus.WithContext(ctx.RequestContext()).Create(newReadStatus)
}
if err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ListMessageError,
fmt.Sprintf("标记消息已读失败:%s", err.Error())),
)
return
}
res.Message = "消息已标记为已读"
ctx.Payload(res)
}
}

View File

@ -35,6 +35,7 @@ func newAppMessageLog(db *gorm.DB, opts ...gen.DOOption) appMessageLog {
_appMessageLog.ReceiverID = field.NewString(tableName, "receiver_id")
_appMessageLog.MsgType = field.NewInt32(tableName, "msg_type")
_appMessageLog.Content = field.NewString(tableName, "content")
_appMessageLog.IsRead = field.NewInt32(tableName, "is_read")
_appMessageLog.CreatedAt = field.NewTime(tableName, "created_at")
_appMessageLog.fillFieldMap()
@ -55,6 +56,7 @@ type appMessageLog struct {
ReceiverID field.String // 接收人ID
MsgType field.Int32 // 消息类型(1:文本 2:图片)
Content field.String // 消息内容
IsRead field.Int32 // 是否已读(0:未读 1:已读)
CreatedAt field.Time // 创建时间
fieldMap map[string]field.Expr
@ -97,7 +99,7 @@ func (a *appMessageLog) GetFieldByName(fieldName string) (field.OrderExpr, bool)
}
func (a *appMessageLog) fillFieldMap() {
a.fieldMap = make(map[string]field.Expr, 9)
a.fieldMap = make(map[string]field.Expr, 10)
a.fieldMap["id"] = a.ID
a.fieldMap["app_id"] = a.AppID
a.fieldMap["sender_id"] = a.SenderID
@ -106,6 +108,7 @@ func (a *appMessageLog) fillFieldMap() {
a.fieldMap["receiver_id"] = a.ReceiverID
a.fieldMap["msg_type"] = a.MsgType
a.fieldMap["content"] = a.Content
a.fieldMap["is_read"] = a.IsRead
a.fieldMap["created_at"] = a.CreatedAt
}

View File

@ -1,398 +0,0 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package dao
import (
"context"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"gorm.io/gorm/schema"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/plugin/dbresolver"
"mini-chat/internal/repository/mysql/model"
)
func newAppMessageReadStatus(db *gorm.DB, opts ...gen.DOOption) appMessageReadStatus {
_appMessageReadStatus := appMessageReadStatus{}
_appMessageReadStatus.appMessageReadStatusDo.UseDB(db, opts...)
_appMessageReadStatus.appMessageReadStatusDo.UseModel(&model.AppMessageReadStatus{})
tableName := _appMessageReadStatus.appMessageReadStatusDo.TableName()
_appMessageReadStatus.ALL = field.NewAsterisk(tableName)
_appMessageReadStatus.ID = field.NewInt32(tableName, "id")
_appMessageReadStatus.AppID = field.NewString(tableName, "app_id")
_appMessageReadStatus.MessageID = field.NewInt32(tableName, "message_id")
_appMessageReadStatus.UserID = field.NewString(tableName, "user_id")
_appMessageReadStatus.IsRead = field.NewInt32(tableName, "is_read")
_appMessageReadStatus.ReadTime = field.NewTime(tableName, "read_time")
_appMessageReadStatus.CreatedAt = field.NewTime(tableName, "created_at")
_appMessageReadStatus.UpdatedAt = field.NewTime(tableName, "updated_at")
_appMessageReadStatus.fillFieldMap()
return _appMessageReadStatus
}
// appMessageReadStatus 消息已读状态表
type appMessageReadStatus struct {
appMessageReadStatusDo
ALL field.Asterisk
ID field.Int32 // 主键ID
AppID field.String // 小程序ID
MessageID field.Int32 // 消息ID
UserID field.String // 用户ID
IsRead field.Int32 // 是否已读(0:未读 1:已读)
ReadTime field.Time // 已读时间
CreatedAt field.Time // 创建时间
UpdatedAt field.Time // 更新时间
fieldMap map[string]field.Expr
}
func (a appMessageReadStatus) Table(newTableName string) *appMessageReadStatus {
a.appMessageReadStatusDo.UseTable(newTableName)
return a.updateTableName(newTableName)
}
func (a appMessageReadStatus) As(alias string) *appMessageReadStatus {
a.appMessageReadStatusDo.DO = *(a.appMessageReadStatusDo.As(alias).(*gen.DO))
return a.updateTableName(alias)
}
func (a appMessageReadStatus) updateTableName(table string) *appMessageReadStatus {
a.ALL = field.NewAsterisk(table)
a.ID = field.NewInt32(table, "id")
a.AppID = field.NewString(table, "app_id")
a.MessageID = field.NewInt32(table, "message_id")
a.UserID = field.NewString(table, "user_id")
a.IsRead = field.NewInt32(table, "is_read")
a.ReadTime = field.NewTime(table, "read_time")
a.CreatedAt = field.NewTime(table, "created_at")
a.UpdatedAt = field.NewTime(table, "updated_at")
a.fillFieldMap()
return &a
}
func (a *appMessageReadStatus) fillFieldMap() {
a.fieldMap = make(map[string]field.Expr, 8)
a.fieldMap["id"] = a.ID
a.fieldMap["app_id"] = a.AppID
a.fieldMap["message_id"] = a.MessageID
a.fieldMap["user_id"] = a.UserID
a.fieldMap["is_read"] = a.IsRead
a.fieldMap["read_time"] = a.ReadTime
a.fieldMap["created_at"] = a.CreatedAt
a.fieldMap["updated_at"] = a.UpdatedAt
}
func (a appMessageReadStatus) clone(db *gorm.DB) appMessageReadStatus {
a.appMessageReadStatusDo.ReplaceConnPool(db.Statement.ConnPool)
return a
}
func (a appMessageReadStatus) replaceDB(db *gorm.DB) appMessageReadStatus {
a.appMessageReadStatusDo.ReplaceDB(db)
return a
}
type appMessageReadStatusDo struct{ gen.DO }
type IAppMessageReadStatusDo interface {
gen.SubQuery
Debug() IAppMessageReadStatusDo
WithContext(ctx context.Context) IAppMessageReadStatusDo
WithResult(fc func(tx gen.Dao)) gen.ResultInfo
ReplaceDB(db *gorm.DB)
ReadDB() IAppMessageReadStatusDo
WriteDB() IAppMessageReadStatusDo
As(alias string) gen.Dao
Session(config *gorm.Session) IAppMessageReadStatusDo
Columns(cols ...field.Expr) gen.Columns
Clauses(conds ...clause.Expression) IAppMessageReadStatusDo
Not(conds ...gen.Condition) IAppMessageReadStatusDo
Or(conds ...gen.Condition) IAppMessageReadStatusDo
Select(conds ...field.Expr) IAppMessageReadStatusDo
Where(conds ...gen.Condition) IAppMessageReadStatusDo
Order(conds ...field.Expr) IAppMessageReadStatusDo
Distinct(cols ...field.Expr) IAppMessageReadStatusDo
Omit(cols ...field.Expr) IAppMessageReadStatusDo
Join(table schema.Tabler, on ...field.Expr) IAppMessageReadStatusDo
LeftJoin(table schema.Tabler, on ...field.Expr) IAppMessageReadStatusDo
RightJoin(table schema.Tabler, on ...field.Expr) IAppMessageReadStatusDo
Group(cols ...field.Expr) IAppMessageReadStatusDo
Having(conds ...gen.Condition) IAppMessageReadStatusDo
Limit(limit int) IAppMessageReadStatusDo
Offset(offset int) IAppMessageReadStatusDo
Count() (count int64, err error)
Scopes(funcs ...func(gen.Dao) gen.Dao) IAppMessageReadStatusDo
Unscoped() IAppMessageReadStatusDo
Create(values ...*model.AppMessageReadStatus) error
CreateInBatches(values []*model.AppMessageReadStatus, batchSize int) error
Save(values ...*model.AppMessageReadStatus) error
First() (*model.AppMessageReadStatus, error)
Take() (*model.AppMessageReadStatus, error)
Last() (*model.AppMessageReadStatus, error)
Find() ([]*model.AppMessageReadStatus, error)
FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppMessageReadStatus, err error)
FindInBatches(result *[]*model.AppMessageReadStatus, batchSize int, fc func(tx gen.Dao, batch int) error) error
Pluck(column field.Expr, dest interface{}) error
Delete(...*model.AppMessageReadStatus) (info gen.ResultInfo, err error)
Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
Updates(value interface{}) (info gen.ResultInfo, err error)
UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)
UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)
UpdateColumns(value interface{}) (info gen.ResultInfo, err error)
UpdateFrom(q gen.SubQuery) gen.Dao
Attrs(attrs ...field.AssignExpr) IAppMessageReadStatusDo
Assign(attrs ...field.AssignExpr) IAppMessageReadStatusDo
Joins(fields ...field.RelationField) IAppMessageReadStatusDo
Preload(fields ...field.RelationField) IAppMessageReadStatusDo
FirstOrInit() (*model.AppMessageReadStatus, error)
FirstOrCreate() (*model.AppMessageReadStatus, error)
FindByPage(offset int, limit int) (result []*model.AppMessageReadStatus, count int64, err error)
ScanByPage(result interface{}, offset int, limit int) (count int64, err error)
Scan(result interface{}) (err error)
Returning(value interface{}, columns ...string) IAppMessageReadStatusDo
UnderlyingDB() *gorm.DB
schema.Tabler
}
func (a appMessageReadStatusDo) Debug() IAppMessageReadStatusDo {
return a.withDO(a.DO.Debug())
}
func (a appMessageReadStatusDo) WithContext(ctx context.Context) IAppMessageReadStatusDo {
return a.withDO(a.DO.WithContext(ctx))
}
func (a appMessageReadStatusDo) ReadDB() IAppMessageReadStatusDo {
return a.withDO(a.DO.Clauses(dbresolver.Read))
}
func (a appMessageReadStatusDo) WriteDB() IAppMessageReadStatusDo {
return a.withDO(a.DO.Clauses(dbresolver.Write))
}
func (a appMessageReadStatusDo) Session(config *gorm.Session) IAppMessageReadStatusDo {
return a.withDO(a.DO.Session(config))
}
func (a appMessageReadStatusDo) Clauses(conds ...clause.Expression) IAppMessageReadStatusDo {
return a.withDO(a.DO.Clauses(conds...))
}
func (a appMessageReadStatusDo) Returning(value interface{}, columns ...string) IAppMessageReadStatusDo {
return a.withDO(a.DO.Returning(value, columns...))
}
func (a appMessageReadStatusDo) Not(conds ...gen.Condition) IAppMessageReadStatusDo {
return a.withDO(a.DO.Not(conds...))
}
func (a appMessageReadStatusDo) Or(conds ...gen.Condition) IAppMessageReadStatusDo {
return a.withDO(a.DO.Or(conds...))
}
func (a appMessageReadStatusDo) Select(conds ...field.Expr) IAppMessageReadStatusDo {
return a.withDO(a.DO.Select(conds...))
}
func (a appMessageReadStatusDo) Where(conds ...gen.Condition) IAppMessageReadStatusDo {
return a.withDO(a.DO.Where(conds...))
}
func (a appMessageReadStatusDo) Order(conds ...field.Expr) IAppMessageReadStatusDo {
return a.withDO(a.DO.Order(conds...))
}
func (a appMessageReadStatusDo) Distinct(cols ...field.Expr) IAppMessageReadStatusDo {
return a.withDO(a.DO.Distinct(cols...))
}
func (a appMessageReadStatusDo) Omit(cols ...field.Expr) IAppMessageReadStatusDo {
return a.withDO(a.DO.Omit(cols...))
}
func (a appMessageReadStatusDo) Join(table schema.Tabler, on ...field.Expr) IAppMessageReadStatusDo {
return a.withDO(a.DO.Join(table, on...))
}
func (a appMessageReadStatusDo) LeftJoin(table schema.Tabler, on ...field.Expr) IAppMessageReadStatusDo {
return a.withDO(a.DO.LeftJoin(table, on...))
}
func (a appMessageReadStatusDo) RightJoin(table schema.Tabler, on ...field.Expr) IAppMessageReadStatusDo {
return a.withDO(a.DO.RightJoin(table, on...))
}
func (a appMessageReadStatusDo) Group(cols ...field.Expr) IAppMessageReadStatusDo {
return a.withDO(a.DO.Group(cols...))
}
func (a appMessageReadStatusDo) Having(conds ...gen.Condition) IAppMessageReadStatusDo {
return a.withDO(a.DO.Having(conds...))
}
func (a appMessageReadStatusDo) Limit(limit int) IAppMessageReadStatusDo {
return a.withDO(a.DO.Limit(limit))
}
func (a appMessageReadStatusDo) Offset(offset int) IAppMessageReadStatusDo {
return a.withDO(a.DO.Offset(offset))
}
func (a appMessageReadStatusDo) Scopes(funcs ...func(gen.Dao) gen.Dao) IAppMessageReadStatusDo {
return a.withDO(a.DO.Scopes(funcs...))
}
func (a appMessageReadStatusDo) Unscoped() IAppMessageReadStatusDo {
return a.withDO(a.DO.Unscoped())
}
func (a appMessageReadStatusDo) Create(values ...*model.AppMessageReadStatus) error {
if len(values) == 0 {
return nil
}
return a.DO.Create(values)
}
func (a appMessageReadStatusDo) CreateInBatches(values []*model.AppMessageReadStatus, batchSize int) error {
return a.DO.CreateInBatches(values, batchSize)
}
func (a appMessageReadStatusDo) Save(values ...*model.AppMessageReadStatus) error {
if len(values) == 0 {
return nil
}
return a.DO.Save(values)
}
func (a appMessageReadStatusDo) First() (*model.AppMessageReadStatus, error) {
if result, err := a.DO.First(); err != nil {
return nil, err
} else {
return result.(*model.AppMessageReadStatus), nil
}
}
func (a appMessageReadStatusDo) Take() (*model.AppMessageReadStatus, error) {
if result, err := a.DO.Take(); err != nil {
return nil, err
} else {
return result.(*model.AppMessageReadStatus), nil
}
}
func (a appMessageReadStatusDo) Last() (*model.AppMessageReadStatus, error) {
if result, err := a.DO.Last(); err != nil {
return nil, err
} else {
return result.(*model.AppMessageReadStatus), nil
}
}
func (a appMessageReadStatusDo) Find() ([]*model.AppMessageReadStatus, error) {
result, err := a.DO.Find()
return result.([]*model.AppMessageReadStatus), err
}
func (a appMessageReadStatusDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.AppMessageReadStatus, err error) {
buf := make([]*model.AppMessageReadStatus, 0, batchSize)
err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
defer func() { results = append(results, buf...) }()
return fc(tx, batch)
})
return results, err
}
func (a appMessageReadStatusDo) FindInBatches(result *[]*model.AppMessageReadStatus, batchSize int, fc func(tx gen.Dao, batch int) error) error {
return a.DO.FindInBatches(result, batchSize, fc)
}
func (a appMessageReadStatusDo) Attrs(attrs ...field.AssignExpr) IAppMessageReadStatusDo {
return a.withDO(a.DO.Attrs(attrs...))
}
func (a appMessageReadStatusDo) Assign(attrs ...field.AssignExpr) IAppMessageReadStatusDo {
return a.withDO(a.DO.Assign(attrs...))
}
func (a appMessageReadStatusDo) Joins(fields ...field.RelationField) IAppMessageReadStatusDo {
for _, _f := range fields {
a = *a.withDO(a.DO.Joins(_f))
}
return &a
}
func (a appMessageReadStatusDo) Preload(fields ...field.RelationField) IAppMessageReadStatusDo {
for _, _f := range fields {
a = *a.withDO(a.DO.Preload(_f))
}
return &a
}
func (a appMessageReadStatusDo) FirstOrInit() (*model.AppMessageReadStatus, error) {
if result, err := a.DO.FirstOrInit(); err != nil {
return nil, err
} else {
return result.(*model.AppMessageReadStatus), nil
}
}
func (a appMessageReadStatusDo) FirstOrCreate() (*model.AppMessageReadStatus, error) {
if result, err := a.DO.FirstOrCreate(); err != nil {
return nil, err
} else {
return result.(*model.AppMessageReadStatus), nil
}
}
func (a appMessageReadStatusDo) FindByPage(offset int, limit int) (result []*model.AppMessageReadStatus, count int64, err error) {
result, err = a.Offset(offset).Limit(limit).Find()
if err != nil {
return
}
if size := len(result); 0 < limit && 0 < size && size < limit {
count = int64(size + offset)
return
}
count, err = a.Offset(-1).Limit(-1).Count()
return
}
func (a appMessageReadStatusDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
count, err = a.Count()
if err != nil {
return
}
err = a.Offset(offset).Limit(limit).Scan(result)
return
}
func (a appMessageReadStatusDo) Scan(result interface{}) (err error) {
return a.DO.Scan(result)
}
func (a appMessageReadStatusDo) Delete(models ...*model.AppMessageReadStatus) (result gen.ResultInfo, err error) {
return a.DO.Delete(models)
}
func (a *appMessageReadStatusDo) withDO(do gen.Dao) *appMessageReadStatusDo {
a.DO = *do.(*gen.DO)
return a
}

View File

@ -21,7 +21,6 @@ var (
AppKeyword *appKeyword
AppKeywordReply *appKeywordReply
AppMessageLog *appMessageLog
AppMessageReadStatus *appMessageReadStatus
AppUser *appUser
LogOperation *logOperation
LogRequest *logRequest
@ -34,7 +33,6 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
AppKeyword = &Q.AppKeyword
AppKeywordReply = &Q.AppKeywordReply
AppMessageLog = &Q.AppMessageLog
AppMessageReadStatus = &Q.AppMessageReadStatus
AppUser = &Q.AppUser
LogOperation = &Q.LogOperation
LogRequest = &Q.LogRequest
@ -43,47 +41,44 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
return &Query{
db: db,
Admin: newAdmin(db, opts...),
AppKeyword: newAppKeyword(db, opts...),
AppKeywordReply: newAppKeywordReply(db, opts...),
AppMessageLog: newAppMessageLog(db, opts...),
AppMessageReadStatus: newAppMessageReadStatus(db, opts...),
AppUser: newAppUser(db, opts...),
LogOperation: newLogOperation(db, opts...),
LogRequest: newLogRequest(db, opts...),
MiniProgram: newMiniProgram(db, opts...),
db: db,
Admin: newAdmin(db, opts...),
AppKeyword: newAppKeyword(db, opts...),
AppKeywordReply: newAppKeywordReply(db, opts...),
AppMessageLog: newAppMessageLog(db, opts...),
AppUser: newAppUser(db, opts...),
LogOperation: newLogOperation(db, opts...),
LogRequest: newLogRequest(db, opts...),
MiniProgram: newMiniProgram(db, opts...),
}
}
type Query struct {
db *gorm.DB
Admin admin
AppKeyword appKeyword
AppKeywordReply appKeywordReply
AppMessageLog appMessageLog
AppMessageReadStatus appMessageReadStatus
AppUser appUser
LogOperation logOperation
LogRequest logRequest
MiniProgram miniProgram
Admin admin
AppKeyword appKeyword
AppKeywordReply appKeywordReply
AppMessageLog appMessageLog
AppUser appUser
LogOperation logOperation
LogRequest logRequest
MiniProgram miniProgram
}
func (q *Query) Available() bool { return q.db != nil }
func (q *Query) clone(db *gorm.DB) *Query {
return &Query{
db: db,
Admin: q.Admin.clone(db),
AppKeyword: q.AppKeyword.clone(db),
AppKeywordReply: q.AppKeywordReply.clone(db),
AppMessageLog: q.AppMessageLog.clone(db),
AppMessageReadStatus: q.AppMessageReadStatus.clone(db),
AppUser: q.AppUser.clone(db),
LogOperation: q.LogOperation.clone(db),
LogRequest: q.LogRequest.clone(db),
MiniProgram: q.MiniProgram.clone(db),
db: db,
Admin: q.Admin.clone(db),
AppKeyword: q.AppKeyword.clone(db),
AppKeywordReply: q.AppKeywordReply.clone(db),
AppMessageLog: q.AppMessageLog.clone(db),
AppUser: q.AppUser.clone(db),
LogOperation: q.LogOperation.clone(db),
LogRequest: q.LogRequest.clone(db),
MiniProgram: q.MiniProgram.clone(db),
}
}
@ -97,52 +92,49 @@ func (q *Query) WriteDB() *Query {
func (q *Query) ReplaceDB(db *gorm.DB) *Query {
return &Query{
db: db,
Admin: q.Admin.replaceDB(db),
AppKeyword: q.AppKeyword.replaceDB(db),
AppKeywordReply: q.AppKeywordReply.replaceDB(db),
AppMessageLog: q.AppMessageLog.replaceDB(db),
AppMessageReadStatus: q.AppMessageReadStatus.replaceDB(db),
AppUser: q.AppUser.replaceDB(db),
LogOperation: q.LogOperation.replaceDB(db),
LogRequest: q.LogRequest.replaceDB(db),
MiniProgram: q.MiniProgram.replaceDB(db),
db: db,
Admin: q.Admin.replaceDB(db),
AppKeyword: q.AppKeyword.replaceDB(db),
AppKeywordReply: q.AppKeywordReply.replaceDB(db),
AppMessageLog: q.AppMessageLog.replaceDB(db),
AppUser: q.AppUser.replaceDB(db),
LogOperation: q.LogOperation.replaceDB(db),
LogRequest: q.LogRequest.replaceDB(db),
MiniProgram: q.MiniProgram.replaceDB(db),
}
}
type queryCtx struct {
Admin *adminDo
AppKeyword *appKeywordDo
AppKeywordReply *appKeywordReplyDo
AppMessageLog *appMessageLogDo
AppMessageReadStatus *appMessageReadStatusDo
AppUser *appUserDo
LogOperation *logOperationDo
LogRequest *logRequestDo
MiniProgram *miniProgramDo
Admin *adminDo
AppKeyword *appKeywordDo
AppKeywordReply *appKeywordReplyDo
AppMessageLog *appMessageLogDo
AppUser *appUserDo
LogOperation *logOperationDo
LogRequest *logRequestDo
MiniProgram *miniProgramDo
}
func (q *Query) WithContext(ctx context.Context) *queryCtx {
return &queryCtx{
Admin: q.Admin.WithContext(ctx),
AppKeyword: q.AppKeyword.WithContext(ctx),
AppKeywordReply: q.AppKeywordReply.WithContext(ctx),
AppMessageLog: q.AppMessageLog.WithContext(ctx),
AppMessageReadStatus: q.AppMessageReadStatus.WithContext(ctx).(*appMessageReadStatusDo),
AppUser: q.AppUser.WithContext(ctx),
LogOperation: q.LogOperation.WithContext(ctx),
LogRequest: q.LogRequest.WithContext(ctx),
MiniProgram: q.MiniProgram.WithContext(ctx),
Admin: q.Admin.WithContext(ctx),
AppKeyword: q.AppKeyword.WithContext(ctx),
AppKeywordReply: q.AppKeywordReply.WithContext(ctx),
AppMessageLog: q.AppMessageLog.WithContext(ctx),
AppUser: q.AppUser.WithContext(ctx),
LogOperation: q.LogOperation.WithContext(ctx),
LogRequest: q.LogRequest.WithContext(ctx),
MiniProgram: q.MiniProgram.WithContext(ctx),
}
}
func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error {
return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...)
return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.ReplaceDB(tx)) }, opts...)
}
func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx {
tx := q.db.Begin(opts...)
return &QueryTx{Query: q.clone(tx), Error: tx.Error}
return &QueryTx{Query: q.ReplaceDB(tx), Error: tx.Error}
}
type QueryTx struct {

View File

@ -20,6 +20,7 @@ type AppMessageLog struct {
ReceiverID string `gorm:"column:receiver_id;not null;comment:接收人ID" json:"receiver_id"` // 接收人ID
MsgType int32 `gorm:"column:msg_type;not null;comment:消息类型(1:文本 2:图片)" json:"msg_type"` // 消息类型(1:文本 2:图片)
Content string `gorm:"column:content;not null;comment:消息内容" json:"content"` // 消息内容
IsRead int32 `gorm:"column:is_read;not null;default:0;comment:是否已读(0:未读 1:已读)" json:"is_read"` // 是否已读(0:未读 1:已读)
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
}

View File

@ -1,28 +0,0 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameAppMessageReadStatus = "app_message_read_status"
// AppMessageReadStatus 消息已读状态表
type AppMessageReadStatus struct {
ID int32 `gorm:"column:id;primaryKey;autoIncrement:true;comment:主键ID" json:"id"` // 主键ID
AppID string `gorm:"column:app_id;not null;comment:小程序ID" json:"app_id"` // 小程序ID
MessageID int32 `gorm:"column:message_id;not null;comment:消息ID" json:"message_id"` // 消息ID
UserID string `gorm:"column:user_id;not null;comment:用户ID" json:"user_id"` // 用户ID
IsRead int32 `gorm:"column:is_read;not null;default:0;comment:是否已读(0:未读 1:已读)" json:"is_read"` // 是否已读(0:未读 1:已读)
ReadTime *time.Time `gorm:"column:read_time;comment:已读时间" json:"read_time"` // 已读时间
CreatedAt time.Time `gorm:"column:created_at;not null;default:CURRENT_TIMESTAMP;comment:创建时间" json:"created_at"` // 创建时间
UpdatedAt time.Time `gorm:"column:updated_at;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"updated_at"` // 更新时间
}
// TableName AppMessageReadStatus's table name
func (*AppMessageReadStatus) TableName() string {
return TableNameAppMessageReadStatus
}

View File

@ -63,10 +63,9 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo, cron cron.Server) (co
appNonAuthApiRouter := mux.Group("/app")
{
appNonAuthApiRouter.POST("/user/create", appHandler.CreateAppUser()) // 新增小程序用户
appNonAuthApiRouter.GET("/messages", messageHandler.AppMessagePageList()) // 消息列表
appNonAuthApiRouter.POST("/messages/read", messageHandler.MarkMessageRead()) // 标记消息为已读
appNonAuthApiRouter.POST("/send_message", messageHandler.UserSendMessage()) // 发送消息
appNonAuthApiRouter.POST("/user/create", appHandler.CreateAppUser()) // 新增小程序用户
appNonAuthApiRouter.GET("/messages", messageHandler.AppMessagePageList()) // 消息列表
appNonAuthApiRouter.POST("/send_message", messageHandler.UserSendMessage()) // 发送消息
}
// 微信 API 路由组

View File

@ -0,0 +1 @@
{"level":"fatal","time":"2025-10-18 19:32:41","caller":"logger/logger.go:333","msg":"http server startup err","domain":"mini-chat[fat]","error":"listen tcp :9991: bind: address already in use"}

BIN
mini-chat Executable file

Binary file not shown.