feat(消息状态): 添加消息已读状态功能
- 新增消息已读状态表结构及模型 - 实现用户端标记消息为已读接口 - 添加管理端获取最新消息记录接口 - 实现微信小程序登录功能 - 更新相关API文档和路由配置
This commit is contained in:
parent
f1a364bae2
commit
1a285f4e23
278
docs/docs.go
278
docs/docs.go
@ -922,6 +922,60 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/messages/latest": {
|
||||||
|
"get": {
|
||||||
|
"description": "管理端根据appid获取最新消息记录,包含已读未读状态,访问时自动标记为已读",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"管理端.小程序"
|
||||||
|
],
|
||||||
|
"summary": "根据appid获取最新消息记录",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "小程序ID",
|
||||||
|
"name": "app_id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1,
|
||||||
|
"description": "当前页码",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 20,
|
||||||
|
"description": "每页返回的数据量,最多 100 条",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/app.latestMessageByAppIdResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/code.Failure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/rel_app/{id}": {
|
"/admin/rel_app/{id}": {
|
||||||
"put": {
|
"put": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -1109,6 +1163,52 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/wechat/miniprogram/login": {
|
||||||
|
"post": {
|
||||||
|
"description": "通过AppID和code获取用户的openid和session_key",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"微信"
|
||||||
|
],
|
||||||
|
"summary": "小程序登录",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "请求参数",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/wechat.miniprogramLoginRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/wechat.miniprogramLoginResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/code.Failure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/code.Failure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/wechat/qrcode": {
|
"/api/wechat/qrcode": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "根据 AppID、AppSecret 和页面路径生成微信小程序二维码",
|
"description": "根据 AppID、AppSecret 和页面路径生成微信小程序二维码",
|
||||||
@ -1216,6 +1316,46 @@ 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": {
|
"/app/send_message": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "用户发送消息",
|
"description": "用户发送消息",
|
||||||
@ -1662,6 +1802,74 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"app.latestMessageByAppIdResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/app.latestMessageData"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"description": "当前页码",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"page_size": {
|
||||||
|
"description": "每页返回的数据量",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"description": "符合查询条件的总记录数",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app.latestMessageData": {
|
||||||
|
"type": "object",
|
||||||
|
"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": {
|
||||||
|
"description": "发送时间",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sender_avatar": {
|
||||||
|
"description": "发送人头像",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sender_id": {
|
||||||
|
"description": "发送人ID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sender_name": {
|
||||||
|
"description": "发送人昵称",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"unread_count": {
|
||||||
|
"description": "未读数量",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"app.listData": {
|
"app.listData": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -2156,6 +2364,36 @@ 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": {
|
"message.userSendMessageRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -2246,6 +2484,46 @@ const docTemplate = `{
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"wechat.miniprogramLoginRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"app_id",
|
||||||
|
"js_code"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"app_id": {
|
||||||
|
"description": "小程序AppID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"js_code": {
|
||||||
|
"description": "登录时获取的code",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wechat.miniprogramLoginResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"openid": {
|
||||||
|
"description": "用户唯一标识",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"session_key": {
|
||||||
|
"description": "会话密钥",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"unionid": {
|
||||||
|
"description": "用户在开放平台的唯一标识符",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@ -914,6 +914,60 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/messages/latest": {
|
||||||
|
"get": {
|
||||||
|
"description": "管理端根据appid获取最新消息记录,包含已读未读状态,访问时自动标记为已读",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"管理端.小程序"
|
||||||
|
],
|
||||||
|
"summary": "根据appid获取最新消息记录",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "小程序ID",
|
||||||
|
"name": "app_id",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 1,
|
||||||
|
"description": "当前页码",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"default": 20,
|
||||||
|
"description": "每页返回的数据量,最多 100 条",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/app.latestMessageByAppIdResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/code.Failure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/rel_app/{id}": {
|
"/admin/rel_app/{id}": {
|
||||||
"put": {
|
"put": {
|
||||||
"security": [
|
"security": [
|
||||||
@ -1101,6 +1155,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/wechat/miniprogram/login": {
|
||||||
|
"post": {
|
||||||
|
"description": "通过AppID和code获取用户的openid和session_key",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"微信"
|
||||||
|
],
|
||||||
|
"summary": "小程序登录",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "请求参数",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/wechat.miniprogramLoginRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/wechat.miniprogramLoginResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/code.Failure"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/code.Failure"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/wechat/qrcode": {
|
"/api/wechat/qrcode": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "根据 AppID、AppSecret 和页面路径生成微信小程序二维码",
|
"description": "根据 AppID、AppSecret 和页面路径生成微信小程序二维码",
|
||||||
@ -1208,6 +1308,46 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/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": {
|
"/app/send_message": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "用户发送消息",
|
"description": "用户发送消息",
|
||||||
@ -1654,6 +1794,74 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"app.latestMessageByAppIdResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/app.latestMessageData"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"description": "当前页码",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"page_size": {
|
||||||
|
"description": "每页返回的数据量",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"description": "符合查询条件的总记录数",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"app.latestMessageData": {
|
||||||
|
"type": "object",
|
||||||
|
"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": {
|
||||||
|
"description": "发送时间",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sender_avatar": {
|
||||||
|
"description": "发送人头像",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sender_id": {
|
||||||
|
"description": "发送人ID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"sender_name": {
|
||||||
|
"description": "发送人昵称",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"unread_count": {
|
||||||
|
"description": "未读数量",
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"app.listData": {
|
"app.listData": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -2148,6 +2356,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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": {
|
"message.userSendMessageRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@ -2238,6 +2476,46 @@
|
|||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"wechat.miniprogramLoginRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"app_id",
|
||||||
|
"js_code"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"app_id": {
|
||||||
|
"description": "小程序AppID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"js_code": {
|
||||||
|
"description": "登录时获取的code",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wechat.miniprogramLoginResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"openid": {
|
||||||
|
"description": "用户唯一标识",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"session_key": {
|
||||||
|
"description": "会话密钥",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"unionid": {
|
||||||
|
"description": "用户在开放平台的唯一标识符",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
@ -257,6 +257,55 @@ definitions:
|
|||||||
description: 提示信息
|
description: 提示信息
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
app.latestMessageByAppIdResponse:
|
||||||
|
properties:
|
||||||
|
list:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/app.latestMessageData'
|
||||||
|
type: array
|
||||||
|
page:
|
||||||
|
description: 当前页码
|
||||||
|
type: integer
|
||||||
|
page_size:
|
||||||
|
description: 每页返回的数据量
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
description: 符合查询条件的总记录数
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
app.latestMessageData:
|
||||||
|
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:
|
||||||
|
description: 发送时间
|
||||||
|
type: string
|
||||||
|
sender_avatar:
|
||||||
|
description: 发送人头像
|
||||||
|
type: string
|
||||||
|
sender_id:
|
||||||
|
description: 发送人ID
|
||||||
|
type: string
|
||||||
|
sender_name:
|
||||||
|
description: 发送人昵称
|
||||||
|
type: string
|
||||||
|
unread_count:
|
||||||
|
description: 未读数量
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
app.listData:
|
app.listData:
|
||||||
properties:
|
properties:
|
||||||
app_id:
|
app_id:
|
||||||
@ -607,6 +656,27 @@ definitions:
|
|||||||
description: 发送人昵称
|
description: 发送人昵称
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
message.userSendMessageRequest:
|
||||||
properties:
|
properties:
|
||||||
app_id:
|
app_id:
|
||||||
@ -672,6 +742,34 @@ definitions:
|
|||||||
success:
|
success:
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
|
wechat.miniprogramLoginRequest:
|
||||||
|
properties:
|
||||||
|
app_id:
|
||||||
|
description: 小程序AppID
|
||||||
|
type: string
|
||||||
|
js_code:
|
||||||
|
description: 登录时获取的code
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- app_id
|
||||||
|
- js_code
|
||||||
|
type: object
|
||||||
|
wechat.miniprogramLoginResponse:
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
openid:
|
||||||
|
description: 用户唯一标识
|
||||||
|
type: string
|
||||||
|
session_key:
|
||||||
|
description: 会话密钥
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
unionid:
|
||||||
|
description: 用户在开放平台的唯一标识符
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
title: mini-chat 接口文档
|
title: mini-chat 接口文档
|
||||||
@ -1294,6 +1392,43 @@ paths:
|
|||||||
summary: 获取消息日志
|
summary: 获取消息日志
|
||||||
tags:
|
tags:
|
||||||
- 管理端.小程序
|
- 管理端.小程序
|
||||||
|
/admin/messages/latest:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 管理端根据appid获取最新消息记录,包含已读未读状态,访问时自动标记为已读
|
||||||
|
parameters:
|
||||||
|
- description: 小程序ID
|
||||||
|
in: query
|
||||||
|
name: app_id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- default: 1
|
||||||
|
description: 当前页码
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- default: 20
|
||||||
|
description: 每页返回的数据量,最多 100 条
|
||||||
|
in: query
|
||||||
|
name: page_size
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/app.latestMessageByAppIdResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/code.Failure'
|
||||||
|
summary: 根据appid获取最新消息记录
|
||||||
|
tags:
|
||||||
|
- 管理端.小程序
|
||||||
/admin/rel_app/{id}:
|
/admin/rel_app/{id}:
|
||||||
put:
|
put:
|
||||||
consumes:
|
consumes:
|
||||||
@ -1380,6 +1515,36 @@ paths:
|
|||||||
summary: 上传图片
|
summary: 上传图片
|
||||||
tags:
|
tags:
|
||||||
- 通用
|
- 通用
|
||||||
|
/api/wechat/miniprogram/login:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 通过AppID和code获取用户的openid和session_key
|
||||||
|
parameters:
|
||||||
|
- description: 请求参数
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/wechat.miniprogramLoginRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/wechat.miniprogramLoginResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/code.Failure'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/code.Failure'
|
||||||
|
summary: 小程序登录
|
||||||
|
tags:
|
||||||
|
- 微信
|
||||||
/api/wechat/qrcode:
|
/api/wechat/qrcode:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
@ -1452,6 +1617,32 @@ paths:
|
|||||||
summary: 获取消息日志
|
summary: 获取消息日志
|
||||||
tags:
|
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:
|
/app/send_message:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
|
|||||||
189
internal/api/app/app_latest_messages.go
Normal file
189
internal/api/app/app_latest_messages.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
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 {
|
||||||
|
AppID string `form:"app_id" binding:"required"` // 小程序ID
|
||||||
|
Page int `form:"page"` // 当前页码,默认1
|
||||||
|
PageSize int `form:"page_size"` // 每页返回的数据量,默认20
|
||||||
|
}
|
||||||
|
|
||||||
|
type latestMessageData struct {
|
||||||
|
MessageID int32 `json:"message_id"` // 消息ID
|
||||||
|
SendTime string `json:"send_time"` // 发送时间
|
||||||
|
SenderID string `json:"sender_id"` // 发送人ID
|
||||||
|
SenderName string `json:"sender_name"` // 发送人昵称
|
||||||
|
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"` // 未读数量
|
||||||
|
}
|
||||||
|
|
||||||
|
type latestMessageByAppIdResponse struct {
|
||||||
|
Page int `json:"page"` // 当前页码
|
||||||
|
PageSize int `json:"page_size"` // 每页返回的数据量
|
||||||
|
Total int64 `json:"total"` // 符合查询条件的总记录数
|
||||||
|
List []latestMessageData `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestMessageByAppId 根据appid获取最新消息记录
|
||||||
|
// @Summary 根据appid获取最新消息记录
|
||||||
|
// @Description 管理端根据appid获取最新消息记录,包含已读未读状态,访问时自动标记为已读
|
||||||
|
// @Tags 管理端.小程序
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param app_id query string true "小程序ID"
|
||||||
|
// @Param page query int true "当前页码" default(1)
|
||||||
|
// @Param page_size query int true "每页返回的数据量,最多 100 条" default(20)
|
||||||
|
// @Success 200 {object} latestMessageByAppIdResponse
|
||||||
|
// @Failure 400 {object} code.Failure
|
||||||
|
// @Router /admin/messages/latest [get]
|
||||||
|
func (h *handler) LatestMessageByAppId() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
req := new(latestMessageByAppIdRequest)
|
||||||
|
res := new(latestMessageByAppIdResponse)
|
||||||
|
if err := ctx.ShouldBindForm(req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
code.ParamBindError,
|
||||||
|
validation.Error(err)),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Page == 0 {
|
||||||
|
req.Page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PageSize == 0 {
|
||||||
|
req.PageSize = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PageSize > 100 {
|
||||||
|
ctx.AbortWithError(core.Error(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
code.ListMessageError,
|
||||||
|
fmt.Sprintf("%s: 一次最多只能查询 100 条", code.Text(code.ListMessageError)),
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := h.readDB.AppMessageLog.WithContext(ctx.RequestContext()).
|
||||||
|
Where(h.readDB.AppMessageLog.AppID.Eq(req.AppID))
|
||||||
|
|
||||||
|
// 查询总数
|
||||||
|
total, err := query.Count()
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
code.ListMessageError,
|
||||||
|
fmt.Sprintf("%s:%s", code.Text(code.ListMessageError), err.Error())),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询指定小程序的最新消息
|
||||||
|
resultData, err := query.
|
||||||
|
Order(h.readDB.AppMessageLog.SendTime.Desc()).
|
||||||
|
Offset((req.Page - 1) * req.PageSize).
|
||||||
|
Limit(req.PageSize).
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
code.ListMessageError,
|
||||||
|
fmt.Sprintf("%s:%s", code.Text(code.ListMessageError), err.Error())),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 自动标记该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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Page = req.Page
|
||||||
|
res.PageSize = req.PageSize
|
||||||
|
res.Total = total
|
||||||
|
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)).
|
||||||
|
Count()
|
||||||
|
|
||||||
|
res.List[k] = latestMessageData{
|
||||||
|
MessageID: v.ID,
|
||||||
|
SendTime: timeutil.FriendlyTime(v.SendTime),
|
||||||
|
SenderID: v.SenderID,
|
||||||
|
SenderName: v.SenderName,
|
||||||
|
SenderAvatar: "", // TODO: 需要从用户表获取头像信息
|
||||||
|
ReceiverID: v.ReceiverID,
|
||||||
|
Content: v.Content,
|
||||||
|
MsgType: v.MsgType,
|
||||||
|
IsRead: isRead,
|
||||||
|
UnreadCount: unreadCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Payload(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
115
internal/api/message/message_mark_read.go
Normal file
115
internal/api/message/message_mark_read.go
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
136
internal/api/wechat/miniprogram_login.go
Normal file
136
internal/api/wechat/miniprogram_login.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package wechat
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"mini-chat/internal/code"
|
||||||
|
"mini-chat/internal/pkg/core"
|
||||||
|
"mini-chat/internal/pkg/httpclient"
|
||||||
|
"mini-chat/internal/pkg/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
type miniprogramLoginRequest struct {
|
||||||
|
AppID string `json:"app_id" binding:"required"` // 小程序AppID
|
||||||
|
JSCode string `json:"js_code" binding:"required"` // 登录时获取的code
|
||||||
|
}
|
||||||
|
|
||||||
|
type miniprogramLoginResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
OpenID string `json:"openid,omitempty"` // 用户唯一标识
|
||||||
|
SessionKey string `json:"session_key,omitempty"` // 会话密钥
|
||||||
|
UnionID string `json:"unionid,omitempty"` // 用户在开放平台的唯一标识符
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code2SessionResponse 微信code2Session接口响应
|
||||||
|
type Code2SessionResponse struct {
|
||||||
|
OpenID string `json:"openid"` // 用户唯一标识
|
||||||
|
SessionKey string `json:"session_key"` // 会话密钥
|
||||||
|
UnionID string `json:"unionid"` // 用户在开放平台的唯一标识符
|
||||||
|
ErrCode int `json:"errcode"` // 错误码
|
||||||
|
ErrMsg string `json:"errmsg"` // 错误信息
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiniprogramLogin 小程序登录
|
||||||
|
// @Summary 小程序登录
|
||||||
|
// @Description 通过AppID和code获取用户的openid和session_key
|
||||||
|
// @Tags 微信
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body miniprogramLoginRequest true "请求参数"
|
||||||
|
// @Success 200 {object} miniprogramLoginResponse
|
||||||
|
// @Failure 400 {object} code.Failure
|
||||||
|
// @Failure 500 {object} code.Failure
|
||||||
|
// @Router /api/wechat/miniprogram/login [post]
|
||||||
|
func (h *handler) MiniprogramLogin() core.HandlerFunc {
|
||||||
|
return func(ctx core.Context) {
|
||||||
|
req := new(miniprogramLoginRequest)
|
||||||
|
res := new(miniprogramLoginResponse)
|
||||||
|
|
||||||
|
if err := ctx.ShouldBindJSON(req); err != nil {
|
||||||
|
ctx.AbortWithError(core.Error(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
code.ParamBindError,
|
||||||
|
validation.Error(err),
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据AppID查询小程序信息获取AppSecret
|
||||||
|
miniProgram, err := h.readDB.MiniProgram.WithContext(ctx.RequestContext()).
|
||||||
|
Where(h.readDB.MiniProgram.AppID.Eq(req.AppID)).
|
||||||
|
First()
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error(fmt.Sprintf("查询小程序信息失败: %s", err.Error()))
|
||||||
|
ctx.AbortWithError(core.Error(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
code.ServerError,
|
||||||
|
"小程序不存在或查询失败",
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用微信code2Session接口
|
||||||
|
openID, sessionKey, unionID, err := h.callCode2Session(ctx, req.AppID, miniProgram.AppSecret, req.JSCode)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error(fmt.Sprintf("调用微信code2Session接口失败: %s", err.Error()))
|
||||||
|
ctx.AbortWithError(core.Error(
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
code.ServerError,
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Success = true
|
||||||
|
res.Message = "登录成功"
|
||||||
|
res.OpenID = openID
|
||||||
|
res.SessionKey = sessionKey
|
||||||
|
res.UnionID = unionID
|
||||||
|
|
||||||
|
ctx.Payload(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// callCode2Session 调用微信code2Session接口
|
||||||
|
func (h *handler) callCode2Session(ctx core.Context, appID, appSecret, jsCode string) (string, string, string, error) {
|
||||||
|
// 构建请求URL
|
||||||
|
url := "https://api.weixin.qq.com/sns/jscode2session"
|
||||||
|
|
||||||
|
// 发送HTTP请求
|
||||||
|
client := httpclient.GetHttpClientWithContext(ctx.RequestContext())
|
||||||
|
resp, err := client.R().
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"appid": appID,
|
||||||
|
"secret": appSecret,
|
||||||
|
"js_code": jsCode,
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
}).
|
||||||
|
Get(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", fmt.Errorf("HTTP请求失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode() != http.StatusOK {
|
||||||
|
return "", "", "", fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
var result Code2SessionResponse
|
||||||
|
if err := json.Unmarshal(resp.Body(), &result); err != nil {
|
||||||
|
return "", "", "", fmt.Errorf("解析响应失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查微信API返回的错误码
|
||||||
|
if result.ErrCode != 0 {
|
||||||
|
return "", "", "", fmt.Errorf("微信API错误: errcode=%d, errmsg=%s", result.ErrCode, result.ErrMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.OpenID == "" {
|
||||||
|
return "", "", "", fmt.Errorf("获取到的openid为空")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.OpenID, result.SessionKey, result.UnionID, nil
|
||||||
|
}
|
||||||
398
internal/repository/mysql/dao/app_message_read_status.gen.go
Normal file
398
internal/repository/mysql/dao/app_message_read_status.gen.go
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@ -16,15 +16,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Q = new(Query)
|
Q = new(Query)
|
||||||
Admin *admin
|
Admin *admin
|
||||||
AppKeyword *appKeyword
|
AppKeyword *appKeyword
|
||||||
AppKeywordReply *appKeywordReply
|
AppKeywordReply *appKeywordReply
|
||||||
AppMessageLog *appMessageLog
|
AppMessageLog *appMessageLog
|
||||||
AppUser *appUser
|
AppMessageReadStatus *appMessageReadStatus
|
||||||
LogOperation *logOperation
|
AppUser *appUser
|
||||||
LogRequest *logRequest
|
LogOperation *logOperation
|
||||||
MiniProgram *miniProgram
|
LogRequest *logRequest
|
||||||
|
MiniProgram *miniProgram
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
||||||
@ -33,6 +34,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
|||||||
AppKeyword = &Q.AppKeyword
|
AppKeyword = &Q.AppKeyword
|
||||||
AppKeywordReply = &Q.AppKeywordReply
|
AppKeywordReply = &Q.AppKeywordReply
|
||||||
AppMessageLog = &Q.AppMessageLog
|
AppMessageLog = &Q.AppMessageLog
|
||||||
|
AppMessageReadStatus = &Q.AppMessageReadStatus
|
||||||
AppUser = &Q.AppUser
|
AppUser = &Q.AppUser
|
||||||
LogOperation = &Q.LogOperation
|
LogOperation = &Q.LogOperation
|
||||||
LogRequest = &Q.LogRequest
|
LogRequest = &Q.LogRequest
|
||||||
@ -41,44 +43,47 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
|
|||||||
|
|
||||||
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
|
||||||
return &Query{
|
return &Query{
|
||||||
db: db,
|
db: db,
|
||||||
Admin: newAdmin(db, opts...),
|
Admin: newAdmin(db, opts...),
|
||||||
AppKeyword: newAppKeyword(db, opts...),
|
AppKeyword: newAppKeyword(db, opts...),
|
||||||
AppKeywordReply: newAppKeywordReply(db, opts...),
|
AppKeywordReply: newAppKeywordReply(db, opts...),
|
||||||
AppMessageLog: newAppMessageLog(db, opts...),
|
AppMessageLog: newAppMessageLog(db, opts...),
|
||||||
AppUser: newAppUser(db, opts...),
|
AppMessageReadStatus: newAppMessageReadStatus(db, opts...),
|
||||||
LogOperation: newLogOperation(db, opts...),
|
AppUser: newAppUser(db, opts...),
|
||||||
LogRequest: newLogRequest(db, opts...),
|
LogOperation: newLogOperation(db, opts...),
|
||||||
MiniProgram: newMiniProgram(db, opts...),
|
LogRequest: newLogRequest(db, opts...),
|
||||||
|
MiniProgram: newMiniProgram(db, opts...),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
db *gorm.DB
|
db *gorm.DB
|
||||||
|
|
||||||
Admin admin
|
Admin admin
|
||||||
AppKeyword appKeyword
|
AppKeyword appKeyword
|
||||||
AppKeywordReply appKeywordReply
|
AppKeywordReply appKeywordReply
|
||||||
AppMessageLog appMessageLog
|
AppMessageLog appMessageLog
|
||||||
AppUser appUser
|
AppMessageReadStatus appMessageReadStatus
|
||||||
LogOperation logOperation
|
AppUser appUser
|
||||||
LogRequest logRequest
|
LogOperation logOperation
|
||||||
MiniProgram miniProgram
|
LogRequest logRequest
|
||||||
|
MiniProgram miniProgram
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) Available() bool { return q.db != nil }
|
func (q *Query) Available() bool { return q.db != nil }
|
||||||
|
|
||||||
func (q *Query) clone(db *gorm.DB) *Query {
|
func (q *Query) clone(db *gorm.DB) *Query {
|
||||||
return &Query{
|
return &Query{
|
||||||
db: db,
|
db: db,
|
||||||
Admin: q.Admin.clone(db),
|
Admin: q.Admin.clone(db),
|
||||||
AppKeyword: q.AppKeyword.clone(db),
|
AppKeyword: q.AppKeyword.clone(db),
|
||||||
AppKeywordReply: q.AppKeywordReply.clone(db),
|
AppKeywordReply: q.AppKeywordReply.clone(db),
|
||||||
AppMessageLog: q.AppMessageLog.clone(db),
|
AppMessageLog: q.AppMessageLog.clone(db),
|
||||||
AppUser: q.AppUser.clone(db),
|
AppMessageReadStatus: q.AppMessageReadStatus.clone(db),
|
||||||
LogOperation: q.LogOperation.clone(db),
|
AppUser: q.AppUser.clone(db),
|
||||||
LogRequest: q.LogRequest.clone(db),
|
LogOperation: q.LogOperation.clone(db),
|
||||||
MiniProgram: q.MiniProgram.clone(db),
|
LogRequest: q.LogRequest.clone(db),
|
||||||
|
MiniProgram: q.MiniProgram.clone(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,39 +97,42 @@ func (q *Query) WriteDB() *Query {
|
|||||||
|
|
||||||
func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
func (q *Query) ReplaceDB(db *gorm.DB) *Query {
|
||||||
return &Query{
|
return &Query{
|
||||||
db: db,
|
db: db,
|
||||||
Admin: q.Admin.replaceDB(db),
|
Admin: q.Admin.replaceDB(db),
|
||||||
AppKeyword: q.AppKeyword.replaceDB(db),
|
AppKeyword: q.AppKeyword.replaceDB(db),
|
||||||
AppKeywordReply: q.AppKeywordReply.replaceDB(db),
|
AppKeywordReply: q.AppKeywordReply.replaceDB(db),
|
||||||
AppMessageLog: q.AppMessageLog.replaceDB(db),
|
AppMessageLog: q.AppMessageLog.replaceDB(db),
|
||||||
AppUser: q.AppUser.replaceDB(db),
|
AppMessageReadStatus: q.AppMessageReadStatus.replaceDB(db),
|
||||||
LogOperation: q.LogOperation.replaceDB(db),
|
AppUser: q.AppUser.replaceDB(db),
|
||||||
LogRequest: q.LogRequest.replaceDB(db),
|
LogOperation: q.LogOperation.replaceDB(db),
|
||||||
MiniProgram: q.MiniProgram.replaceDB(db),
|
LogRequest: q.LogRequest.replaceDB(db),
|
||||||
|
MiniProgram: q.MiniProgram.replaceDB(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type queryCtx struct {
|
type queryCtx struct {
|
||||||
Admin *adminDo
|
Admin *adminDo
|
||||||
AppKeyword *appKeywordDo
|
AppKeyword *appKeywordDo
|
||||||
AppKeywordReply *appKeywordReplyDo
|
AppKeywordReply *appKeywordReplyDo
|
||||||
AppMessageLog *appMessageLogDo
|
AppMessageLog *appMessageLogDo
|
||||||
AppUser *appUserDo
|
AppMessageReadStatus *appMessageReadStatusDo
|
||||||
LogOperation *logOperationDo
|
AppUser *appUserDo
|
||||||
LogRequest *logRequestDo
|
LogOperation *logOperationDo
|
||||||
MiniProgram *miniProgramDo
|
LogRequest *logRequestDo
|
||||||
|
MiniProgram *miniProgramDo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
func (q *Query) WithContext(ctx context.Context) *queryCtx {
|
||||||
return &queryCtx{
|
return &queryCtx{
|
||||||
Admin: q.Admin.WithContext(ctx),
|
Admin: q.Admin.WithContext(ctx),
|
||||||
AppKeyword: q.AppKeyword.WithContext(ctx),
|
AppKeyword: q.AppKeyword.WithContext(ctx),
|
||||||
AppKeywordReply: q.AppKeywordReply.WithContext(ctx),
|
AppKeywordReply: q.AppKeywordReply.WithContext(ctx),
|
||||||
AppMessageLog: q.AppMessageLog.WithContext(ctx),
|
AppMessageLog: q.AppMessageLog.WithContext(ctx),
|
||||||
AppUser: q.AppUser.WithContext(ctx),
|
AppMessageReadStatus: q.AppMessageReadStatus.WithContext(ctx).(*appMessageReadStatusDo),
|
||||||
LogOperation: q.LogOperation.WithContext(ctx),
|
AppUser: q.AppUser.WithContext(ctx),
|
||||||
LogRequest: q.LogRequest.WithContext(ctx),
|
LogOperation: q.LogOperation.WithContext(ctx),
|
||||||
MiniProgram: q.MiniProgram.WithContext(ctx),
|
LogRequest: q.LogRequest.WithContext(ctx),
|
||||||
|
MiniProgram: q.MiniProgram.WithContext(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@ -63,15 +63,17 @@ 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("/messages/read", messageHandler.MarkMessageRead()) // 标记消息为已读
|
||||||
|
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()) // 小程序登录
|
||||||
}
|
}
|
||||||
|
|
||||||
// 管理端认证接口路由组
|
// 管理端认证接口路由组
|
||||||
@ -99,6 +101,7 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo, cron cron.Server) (co
|
|||||||
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获取最新消息记录
|
||||||
}
|
}
|
||||||
|
|
||||||
return mux, nil
|
return mux, nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user