feat(1.0):调整未读列表

This commit is contained in:
summer 2025-10-20 14:11:27 +08:00
parent a6ac558680
commit ba0630b2da
10 changed files with 65 additions and 103 deletions

View File

@ -924,6 +924,11 @@ const docTemplate = `{
}, },
"/admin/messages/latest": { "/admin/messages/latest": {
"get": { "get": {
"security": [
{
"LoginVerifyToken": []
}
],
"description": "管理端根据appid获取最新消息记录包含已读未读状态访问时自动标记为已读", "description": "管理端根据appid获取最新消息记录包含已读未读状态访问时自动标记为已读",
"consumes": [ "consumes": [
"application/json" "application/json"
@ -1788,26 +1793,6 @@ const docTemplate = `{
"app.latestMessageData": { "app.latestMessageData": {
"type": "object", "type": "object",
"properties": { "properties": {
"content": {
"description": "消息内容",
"type": "string"
},
"is_read": {
"description": "是否已读(0:未读 1:已读)",
"type": "integer"
},
"message_id": {
"description": "消息ID",
"type": "integer"
},
"msg_type": {
"description": "消息类型(1:文本 2:图片)",
"type": "integer"
},
"receiver_id": {
"description": "接收人ID",
"type": "string"
},
"send_time": { "send_time": {
"description": "发送时间", "description": "发送时间",
"type": "string" "type": "string"

View File

@ -916,6 +916,11 @@
}, },
"/admin/messages/latest": { "/admin/messages/latest": {
"get": { "get": {
"security": [
{
"LoginVerifyToken": []
}
],
"description": "管理端根据appid获取最新消息记录包含已读未读状态访问时自动标记为已读", "description": "管理端根据appid获取最新消息记录包含已读未读状态访问时自动标记为已读",
"consumes": [ "consumes": [
"application/json" "application/json"
@ -1780,26 +1785,6 @@
"app.latestMessageData": { "app.latestMessageData": {
"type": "object", "type": "object",
"properties": { "properties": {
"content": {
"description": "消息内容",
"type": "string"
},
"is_read": {
"description": "是否已读(0:未读 1:已读)",
"type": "integer"
},
"message_id": {
"description": "消息ID",
"type": "integer"
},
"msg_type": {
"description": "消息类型(1:文本 2:图片)",
"type": "integer"
},
"receiver_id": {
"description": "接收人ID",
"type": "string"
},
"send_time": { "send_time": {
"description": "发送时间", "description": "发送时间",
"type": "string" "type": "string"

View File

@ -275,21 +275,6 @@ definitions:
type: object type: object
app.latestMessageData: app.latestMessageData:
properties: properties:
content:
description: 消息内容
type: string
is_read:
description: 是否已读(0:未读 1:已读)
type: integer
message_id:
description: 消息ID
type: integer
msg_type:
description: 消息类型(1:文本 2:图片)
type: integer
receiver_id:
description: 接收人ID
type: string
send_time: send_time:
description: 发送时间 description: 发送时间
type: string type: string
@ -1414,6 +1399,8 @@ paths:
description: Bad Request description: Bad Request
schema: schema:
$ref: '#/definitions/code.Failure' $ref: '#/definitions/code.Failure'
security:
- LoginVerifyToken: []
summary: 根据appid获取最新消息记录 summary: 根据appid获取最新消息记录
tags: tags:
- 管理端.小程序 - 管理端.小程序

View File

@ -10,6 +10,7 @@ type handler struct {
logger logger.CustomLogger logger logger.CustomLogger
writeDB *dao.Query writeDB *dao.Query
readDB *dao.Query readDB *dao.Query
db mysql.Repo
} }
func New(logger logger.CustomLogger, db mysql.Repo) *handler { func New(logger logger.CustomLogger, db mysql.Repo) *handler {
@ -17,5 +18,6 @@ func New(logger logger.CustomLogger, db mysql.Repo) *handler {
logger: logger, logger: logger,
writeDB: dao.Use(db.GetDbW()), writeDB: dao.Use(db.GetDbW()),
readDB: dao.Use(db.GetDbR()), readDB: dao.Use(db.GetDbR()),
db: db,
} }
} }

View File

@ -3,6 +3,7 @@ package app
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"time"
"mini-chat/internal/code" "mini-chat/internal/code"
"mini-chat/internal/pkg/core" "mini-chat/internal/pkg/core"
@ -17,15 +18,10 @@ type latestMessageByAppIdRequest struct {
} }
type latestMessageData struct { type latestMessageData struct {
MessageID int32 `json:"message_id"` // 消息ID
SendTime string `json:"send_time"` // 发送时间 SendTime string `json:"send_time"` // 发送时间
SenderID string `json:"sender_id"` // 发送人ID SenderID string `json:"sender_id"` // 发送人ID
SenderName string `json:"sender_name"` // 发送人昵称 SenderName string `json:"sender_name"` // 发送人昵称
SenderAvatar string `json:"sender_avatar"` // 发送人头像 SenderAvatar string `json:"sender_avatar"` // 发送人头像
ReceiverID string `json:"receiver_id"` // 接收人ID
Content string `json:"content"` // 消息内容
MsgType int32 `json:"msg_type"` // 消息类型(1:文本 2:图片)
IsRead int32 `json:"is_read"` // 是否已读(0:未读 1:已读)
UnreadCount int64 `json:"unread_count"` // 未读数量 UnreadCount int64 `json:"unread_count"` // 未读数量
} }
@ -48,6 +44,7 @@ type latestMessageByAppIdResponse struct {
// @Success 200 {object} latestMessageByAppIdResponse // @Success 200 {object} latestMessageByAppIdResponse
// @Failure 400 {object} code.Failure // @Failure 400 {object} code.Failure
// @Router /admin/messages/latest [get] // @Router /admin/messages/latest [get]
// @Security LoginVerifyToken
func (h *handler) LatestMessageByAppId() core.HandlerFunc { func (h *handler) LatestMessageByAppId() core.HandlerFunc {
return func(ctx core.Context) { return func(ctx core.Context) {
req := new(latestMessageByAppIdRequest) req := new(latestMessageByAppIdRequest)
@ -78,37 +75,54 @@ func (h *handler) LatestMessageByAppId() core.HandlerFunc {
return return
} }
query := h.readDB.AppMessageLog.WithContext(ctx.RequestContext()). type unreadMessageResult struct {
Where(h.readDB.AppMessageLog.AppID.Eq(req.AppID)) SenderID string `json:"sender_id"`
SenderName string `json:"sender_name"`
SendTime time.Time `json:"send_time"`
AvatarURL string `json:"avatar_url"`
UnreadCount int64 `json:"unread_count"`
}
// 查询总数 var results []unreadMessageResult
total, err := query.Count() var total int64
if err != nil {
countErr := h.db.GetDbR().Table("app_message_log m").
Select("m.send_time, m.sender_id, m.sender_name, u.user_avatar as avatar_url, COUNT(*) as unread_count").
Joins("LEFT JOIN app_user u ON m.sender_id = u.user_id").
Where("m.app_id = ? AND m.sender_id != ? AND m.is_read = 1", req.AppID, "888888").
Group("m.sender_id").
Count(&total).
Error
if countErr != nil {
ctx.AbortWithError(core.Error( ctx.AbortWithError(core.Error(
http.StatusBadRequest, http.StatusBadRequest,
code.ListMessageError, code.ListMessageError,
fmt.Sprintf("%s%s", code.Text(code.ListMessageError), err.Error())), fmt.Sprintf("%s%s", code.Text(code.ListMessageError), countErr.Error())),
) )
return return
} }
// 分页查询指定小程序的最新消息 resultErr := h.db.GetDbR().Table("app_message_log m").
resultData, err := query. Select("max(m.send_time) as send_time, m.sender_id, max(m.sender_name) as sender_name, max(u.user_avatar) as avatar_url, COUNT(*) as unread_count").
Order(h.readDB.AppMessageLog.SendTime.Asc()). Joins("LEFT JOIN app_user u ON m.sender_id = u.user_id").
Where("m.app_id = ? AND m.sender_id != ? AND m.is_read = 1", req.AppID, "888888").
Group("m.sender_id").
Order("unread_count DESC").
Offset((req.Page - 1) * req.PageSize). Offset((req.Page - 1) * req.PageSize).
Limit(req.PageSize). Limit(req.PageSize).
Find() Find(&results).
if err != nil { Error
if resultErr != nil {
ctx.AbortWithError(core.Error( ctx.AbortWithError(core.Error(
http.StatusBadRequest, http.StatusBadRequest,
code.ListMessageError, code.ListMessageError,
fmt.Sprintf("%s%s", code.Text(code.ListMessageError), err.Error())), fmt.Sprintf("%s%s", code.Text(code.ListMessageError), resultErr.Error())),
) )
return return
} }
// 自动标记该appid下的所有消息为已读管理端访问时 // 自动标记该appid下的所有消息为已读管理端访问时
_, err = h.writeDB.AppMessageLog.WithContext(ctx.RequestContext()). _, err := h.writeDB.AppMessageLog.WithContext(ctx.RequestContext()).
Where(h.writeDB.AppMessageLog.AppID.Eq(req.AppID)). Where(h.writeDB.AppMessageLog.AppID.Eq(req.AppID)).
Where(h.writeDB.AppMessageLog.IsRead.Eq(0)). Where(h.writeDB.AppMessageLog.IsRead.Eq(0)).
Update(h.writeDB.AppMessageLog.IsRead, 1) Update(h.writeDB.AppMessageLog.IsRead, 1)
@ -120,27 +134,15 @@ func (h *handler) LatestMessageByAppId() core.HandlerFunc {
res.Page = req.Page res.Page = req.Page
res.PageSize = req.PageSize res.PageSize = req.PageSize
res.Total = total res.Total = total
res.List = make([]latestMessageData, len(resultData)) res.List = make([]latestMessageData, len(results))
for k, v := range resultData {
// 计算该用户在该应用下的未读消息总数
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()
for k, v := range results {
res.List[k] = latestMessageData{ res.List[k] = latestMessageData{
MessageID: v.ID,
SendTime: timeutil.FriendlyTime(v.SendTime), SendTime: timeutil.FriendlyTime(v.SendTime),
SenderID: v.SenderID, SenderID: v.SenderID,
SenderName: v.SenderName, SenderName: v.SenderName,
SenderAvatar: "", // TODO: 需要从用户表获取头像信息 SenderAvatar: v.AvatarURL,
ReceiverID: v.ReceiverID, UnreadCount: v.UnreadCount,
Content: v.Content,
MsgType: v.MsgType,
IsRead: v.IsRead, // 直接使用消息表中的 is_read 字段
UnreadCount: unreadCount,
} }
} }

View File

@ -8,13 +8,13 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/DanPlayer/randomname"
"mini-chat/internal/code" "mini-chat/internal/code"
"mini-chat/internal/pkg/core" "mini-chat/internal/pkg/core"
"mini-chat/internal/pkg/httpclient" "mini-chat/internal/pkg/httpclient"
"mini-chat/internal/pkg/validation" "mini-chat/internal/pkg/validation"
"mini-chat/internal/repository/mysql/model" "mini-chat/internal/repository/mysql/model"
"github.com/DanPlayer/randomname"
) )
type miniprogramLoginRequest struct { type miniprogramLoginRequest struct {

View File

@ -18,4 +18,4 @@ func New(logger logger.CustomLogger, db mysql.Repo) *handler {
writeDB: dao.Use(db.GetDbW()), writeDB: dao.Use(db.GetDbW()),
readDB: dao.Use(db.GetDbR()), readDB: dao.Use(db.GetDbR()),
} }
} }

View File

@ -114,7 +114,7 @@ func VerifySignature(rawData, signature, sessionKey string) bool {
h := sha1.New() h := sha1.New()
h.Write([]byte(rawData + sessionKey)) h.Write([]byte(rawData + sessionKey))
expectedSignature := hex.EncodeToString(h.Sum(nil)) expectedSignature := hex.EncodeToString(h.Sum(nil))
return expectedSignature == signature return expectedSignature == signature
} }
@ -126,7 +126,7 @@ func pkcs7Unpad(data []byte) ([]byte, error) {
// 获取填充长度 // 获取填充长度
padding := int(data[len(data)-1]) padding := int(data[len(data)-1])
// 验证填充长度 // 验证填充长度
if padding > len(data) || padding == 0 { if padding > len(data) || padding == 0 {
return nil, fmt.Errorf("无效的填充长度: %d", padding) return nil, fmt.Errorf("无效的填充长度: %d", padding)
@ -140,4 +140,4 @@ func pkcs7Unpad(data []byte) ([]byte, error) {
} }
return data[:len(data)-padding], nil return data[:len(data)-padding], nil
} }

View File

@ -4,11 +4,12 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"mini-chat/internal/pkg/core"
"mini-chat/internal/pkg/httpclient"
"net/http" "net/http"
"sync" "sync"
"time" "time"
"mini-chat/internal/pkg/core"
"mini-chat/internal/pkg/httpclient"
) )
// AccessTokenRequest 获取 access_token 请求参数 // AccessTokenRequest 获取 access_token 请求参数

View File

@ -63,15 +63,15 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo, cron cron.Server) (co
appNonAuthApiRouter := mux.Group("/app") appNonAuthApiRouter := mux.Group("/app")
{ {
appNonAuthApiRouter.POST("/user/create", appHandler.CreateAppUser()) // 新增小程序用户 appNonAuthApiRouter.POST("/user/create", appHandler.CreateAppUser()) // 新增小程序用户
appNonAuthApiRouter.GET("/messages", messageHandler.AppMessagePageList()) // 消息列表 appNonAuthApiRouter.GET("/messages", messageHandler.AppMessagePageList()) // 消息列表
appNonAuthApiRouter.POST("/send_message", messageHandler.UserSendMessage()) // 发送消息 appNonAuthApiRouter.POST("/send_message", messageHandler.UserSendMessage()) // 发送消息
} }
// 微信 API 路由组 // 微信 API 路由组
wechatApiRouter := mux.Group("/api/wechat") wechatApiRouter := mux.Group("/api/wechat")
{ {
wechatApiRouter.POST("/qrcode", wechatHandler.GenerateQRCode()) // 生成微信小程序二维码(返回 Base64 wechatApiRouter.POST("/qrcode", wechatHandler.GenerateQRCode()) // 生成微信小程序二维码(返回 Base64
wechatApiRouter.POST("/miniprogram/login", wechatHandler.MiniprogramLogin()) // 小程序登录 wechatApiRouter.POST("/miniprogram/login", wechatHandler.MiniprogramLogin()) // 小程序登录
} }
@ -97,9 +97,9 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo, cron cron.Server) (co
adminAuthApiRouter.PUT("/app/keyword/material/:id", keywordHandler.ModifyKeywordMaterial()) // 修改意图关键字素材 adminAuthApiRouter.PUT("/app/keyword/material/:id", keywordHandler.ModifyKeywordMaterial()) // 修改意图关键字素材
adminAuthApiRouter.GET("/app/keyword/materials", keywordHandler.KeywordMaterialPageList()) // 获取意图关键字素材列表 adminAuthApiRouter.GET("/app/keyword/materials", keywordHandler.KeywordMaterialPageList()) // 获取意图关键字素材列表
adminAuthApiRouter.GET("/app/users", appHandler.UserPageList()) // 获取小程序用户列表 adminAuthApiRouter.GET("/app/users", appHandler.UserPageList()) // 获取小程序用户列表
adminAuthApiRouter.POST("/send_message", appHandler.AdminSendMessage()) // 发送消息 adminAuthApiRouter.POST("/send_message", appHandler.AdminSendMessage()) // 发送消息
adminAuthApiRouter.GET("/messages", appHandler.AppMessagePageList()) // 获取小程序用户消息列表 adminAuthApiRouter.GET("/messages", appHandler.AppMessagePageList()) // 获取小程序用户消息列表
adminAuthApiRouter.GET("/messages/latest", appHandler.LatestMessageByAppId()) // 根据appid获取最新消息记录 adminAuthApiRouter.GET("/messages/latest", appHandler.LatestMessageByAppId()) // 根据appid获取最新消息记录
} }