feat(小程序): 添加检查小程序状态接口

refactor(测试): 移除测试中的硬编码凭证
fix(模板消息): 将小程序状态改为正式版
docs(swagger): 更新API文档并移除密码必填限制
This commit is contained in:
邹方成 2025-11-06 20:37:01 +08:00
parent 53cb31f6ce
commit 084b802b05
10 changed files with 295 additions and 13 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
MINI Executable file

Binary file not shown.

View File

@ -15,6 +15,49 @@ const docTemplate = `{
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/admin/app/check_status": {
"get": {
"security": [
{
"LoginVerifyToken": []
}
],
"description": "管理端根据 app_id 调用外部验证服务获取状态(正常/封禁/未知)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"管理端.小程序"
],
"summary": "检查小程序状态",
"parameters": [
{
"type": "string",
"description": "小程序ID",
"name": "app_id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.checkAppStatusResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/api/admin/app/create": {
"post": {
"security": [
@ -1657,7 +1700,6 @@ const docTemplate = `{
"type": "object",
"required": [
"nickname",
"password",
"username"
],
"properties": {
@ -1772,6 +1814,27 @@ const docTemplate = `{
}
}
},
"app.checkAppStatusResponse": {
"type": "object",
"properties": {
"app_id": {
"description": "小程序ID",
"type": "string"
},
"check_status_text": {
"description": "状态文字描述",
"type": "string"
},
"code": {
"description": "外部接口返回的状态码",
"type": "integer"
},
"status": {
"description": "外部接口返回的状态",
"type": "string"
}
}
},
"app.createAppRequest": {
"type": "object",
"required": [

View File

@ -7,6 +7,49 @@
},
"basePath": "/",
"paths": {
"/api/admin/app/check_status": {
"get": {
"security": [
{
"LoginVerifyToken": []
}
],
"description": "管理端根据 app_id 调用外部验证服务获取状态(正常/封禁/未知)",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"管理端.小程序"
],
"summary": "检查小程序状态",
"parameters": [
{
"type": "string",
"description": "小程序ID",
"name": "app_id",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/app.checkAppStatusResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/code.Failure"
}
}
}
}
},
"/api/admin/app/create": {
"post": {
"security": [
@ -1649,7 +1692,6 @@
"type": "object",
"required": [
"nickname",
"password",
"username"
],
"properties": {
@ -1764,6 +1806,27 @@
}
}
},
"app.checkAppStatusResponse": {
"type": "object",
"properties": {
"app_id": {
"description": "小程序ID",
"type": "string"
},
"check_status_text": {
"description": "状态文字描述",
"type": "string"
},
"code": {
"description": "外部接口返回的状态码",
"type": "integer"
},
"status": {
"description": "外部接口返回的状态",
"type": "string"
}
}
},
"app.createAppRequest": {
"type": "object",
"required": [

View File

@ -122,7 +122,6 @@ definitions:
type: string
required:
- nickname
- password
- username
type: object
admin.modifyAdminResponse:
@ -187,6 +186,21 @@ definitions:
description: 符合查询条件的总记录数
type: integer
type: object
app.checkAppStatusResponse:
properties:
app_id:
description: 小程序ID
type: string
check_status_text:
description: 状态文字描述
type: string
code:
description: 外部接口返回的状态码
type: integer
status:
description: 外部接口返回的状态
type: string
type: object
app.createAppRequest:
properties:
app_id:
@ -889,6 +903,33 @@ paths:
summary: 编辑小程序
tags:
- 管理端.小程序
/api/admin/app/check_status:
get:
consumes:
- application/json
description: 管理端根据 app_id 调用外部验证服务获取状态(正常/封禁/未知)
parameters:
- description: 小程序ID
in: query
name: app_id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/app.checkAppStatusResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/code.Failure'
security:
- LoginVerifyToken: []
summary: 检查小程序状态
tags:
- 管理端.小程序
/api/admin/app/create:
post:
consumes:

View File

@ -0,0 +1,114 @@
package app
import (
"fmt"
"net/http"
"mini-chat/internal/code"
"mini-chat/internal/pkg/core"
"mini-chat/internal/pkg/httpclient"
"mini-chat/internal/pkg/validation"
"go.uber.org/zap"
)
// checkAppStatusRequest 请求参数
// 功能描述用于检查指定小程序app_id的运行状态
// 参数说明:
// - AppID(string): 小程序ID必填通过query参数传入form:"app_id"
// 返回值该函数返回一个core.HandlerFunc处理器在HTTP请求完成后通过ctx.Payload返回JSON数据
type checkAppStatusRequest struct {
AppID string `form:"app_id" binding:"required"` // 小程序ID
}
// checkAppStatusResponse 响应数据结构
// 功能描述:返回外部验证服务的状态码与状态文本,并提供统一的文字描述
// 字段说明:
// - AppID(string): 小程序ID
// - Code(int): 外部接口返回的状态码1表示正常0表示封禁其它表示未知
// - Status(string): 外部接口返回的原始状态描述
// - CheckStatusText(string): 根据Code映射得到的中文状态文字正常/封禁/未知)
type checkAppStatusResponse struct {
AppID string `json:"app_id"` // 小程序ID
Code int `json:"code"` // 外部接口返回的状态码
CheckStatusText string `json:"check_status_text"` // 状态文字描述
}
// CheckAppStatus 检查小程序状态
// @Summary 检查小程序状态
// @Description 管理端根据 app_id 调用外部验证服务获取状态(正常/封禁/未知)
// @Tags 管理端.小程序
// @Accept json
// @Produce json
// @Param app_id query string true "小程序ID"
// @Success 200 {object} checkAppStatusResponse
// @Failure 400 {object} code.Failure
// @Router /api/admin/app/check_status [get]
// @Security LoginVerifyToken
func (h *handler) CheckAppStatus() core.HandlerFunc {
return func(ctx core.Context) {
// 绑定请求参数
req := new(checkAppStatusRequest)
if err := ctx.ShouldBindForm(req); err != nil {
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ParamBindError,
validation.Error(err)),
)
return
}
// 调用外部验证服务
// 外部接口: https://api.wxapi.work/xcx/checkxcx.php?appid=<AppID>
// 期望返回: {"code":1, "appid":"xxx", "status":"ok"}
type externalCheckResp struct {
Code int `json:"code"`
Appid string `json:"appid"`
Status string `json:"status"`
}
checkRes := new(externalCheckResp)
response, err := httpclient.GetHttpClientWithContext(ctx.RequestContext()).R().
SetQueryParams(map[string]string{
"appid": req.AppID,
}).
SetResult(checkRes).
Get("https://api.wxapi.work/xcx/checkxcx.php")
if err != nil {
// 记录请求错误
h.logger.Error("请求APP验证服务失败", zap.Error(err))
ctx.AbortWithError(core.Error(
http.StatusBadRequest,
code.ListAppError,
fmt.Sprintf("%s%s", code.Text(code.ListAppError), err.Error()),
))
return
}
// 检查响应状态码
if response.IsError() {
h.logger.Error(fmt.Sprintf("请求APP验证服务异常(%d)", response.StatusCode()))
}
// 将外部返回的code映射为中文状态
statusText := "未知"
switch checkRes.Code {
case 1:
statusText = "正常"
case 0:
statusText = "封禁"
default:
statusText = "未知"
}
// 组织响应数据并返回
res := &checkAppStatusResponse{
AppID: req.AppID,
Code: checkRes.Code,
CheckStatusText: statusText,
}
ctx.Payload(res)
}
}

View File

@ -158,7 +158,7 @@ func (h *handler) SendSubscribeMessage() core.HandlerFunc {
sendSubscribeMessageReq.Touser = req.Touser
sendSubscribeMessageReq.TemplateID = req.TemplateID
sendSubscribeMessageReq.Page = "pages/index/detail?url=1"
sendSubscribeMessageReq.MiniprogramState = "developer" // 需要改成正式版 目前是体验版 跳转小程序类型developer 为开发版trial为体验版formal为正式版;默认为正式版
sendSubscribeMessageReq.MiniprogramState = "formal" // 需要改成正式版 目前是体验版 跳转小程序类型developer 为开发版trial为体验版formal 为正式版;默认为正式版
sendSubscribeMessageReq.Lang = "zh_CN"
sendSubscribeMessageReq.Data.Thing1.Value = "留言提醒"
sendSubscribeMessageReq.Data.Time2.Value = time.Now().Format("2006-01-02 15:04:05")

View File

@ -6,7 +6,7 @@ import (
func TestGetAccessToken(t *testing.T) {
res := new(AccessTokenResponse)
err := GetAccessToken("wx26ad074017e1e63f", "026c19ce4f3bb090c56573024c59a8be", res)
err := GetAccessToken("", "", res)
if err != nil {
t.Error(err)
}

View File

@ -6,7 +6,7 @@ import (
func TestSendSubscribeMessage(t *testing.T) {
res := new(AccessTokenResponse)
err := GetAccessToken("wx26ad074017e1e63f", "026c19ce4f3bb090c56573024c59a8be", res)
err := GetAccessToken("", "", res)
if err != nil {
t.Errorf("获取 access_token 错误: %s", err.Error())
}
@ -17,9 +17,9 @@ func TestSendSubscribeMessage(t *testing.T) {
sendSubscribeMessageRequest.Page = "pages/contact/index"
sendSubscribeMessageRequest.MiniprogramState = "trial"
sendSubscribeMessageRequest.Lang = "zh_CN"
sendSubscribeMessageRequest.Data.Thing1.Value = "测试(CC)"
sendSubscribeMessageRequest.Data.Time2.Value = "2025-10-27"
sendSubscribeMessageRequest.Data.Thing4.Value = "测试(CC)"
sendSubscribeMessageRequest.Data.Thing1.Value = "测试"
sendSubscribeMessageRequest.Data.Time2.Value = "2020-1-27"
sendSubscribeMessageRequest.Data.Thing3.Value = "测试"
sendSubscribeMessageResponse := new(SendSubscribeMessageResponse)
err = SendSubscribeMessage(res.AccessToken, sendSubscribeMessageRequest, sendSubscribeMessageResponse)

View File

@ -84,6 +84,7 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo, cron cron.Server) (co
adminAuthApiRouter.POST("/app/delete", appHandler.DeleteApp()) // 删除小程序
adminAuthApiRouter.PUT("/app/:id", appHandler.ModifyApp()) // 修改小程序
adminAuthApiRouter.GET("/apps", appHandler.PageList()) // 小程序列表
adminAuthApiRouter.GET("/app/check_status", appHandler.CheckAppStatus()) // 检查小程序状态
adminAuthApiRouter.POST("/create", adminHandler.CreateAdmin()) // 新增客服
adminAuthApiRouter.POST("/delete", adminHandler.DeleteAdmin()) // 删除客服