This commit is contained in:
2025-08-05 17:46:15 +08:00
parent e43d78ddf0
commit 4409bea9fc
106 changed files with 18564 additions and 7 deletions
@@ -0,0 +1,15 @@
package api
// ApiGroup 微信集成API组
type ApiGroup struct {
WechatMiniApi WechatMiniApi // 微信小程序API
MpUserApi MpUserApi // 公众号用户API
MpMessageApi MpMessageApi // 公众号消息API
MpAutoReplyApi MpAutoReplyApi // 公众号自动回复API
MpMenuApi MpMenuApi // 公众号菜单API
MpMaterialApi MpMaterialApi // 公众号素材API
WechatWebhookApi WechatWebhookApi // 微信Webhook API
}
// ApiGroupApp API组实例
var ApiGroupApp = new(ApiGroup)
@@ -0,0 +1,132 @@
package api
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MiniStatisticsApi struct{}
var miniStatisticsService = service.ServiceGroupApp.MiniStatisticsService
// GetMiniStatistics 获取小程序基础统计数据
// @Tags MiniStatistics
// @Summary 获取小程序基础统计数据
// @Description 获取小程序基础统计数据
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=response.MiniStatisticsResponse} "获取成功"
// @Router /wechat/mini/statistics [get]
func (m *MiniStatisticsApi) GetMiniStatistics(c *gin.Context) {
statistics, err := miniStatisticsService.GetMiniStatistics()
if err != nil {
global.GVA_LOG.Error("获取小程序基础统计数据失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(statistics, c)
}
// GetVisitTrendData 获取访问趋势数据
// @Tags MiniStatistics
// @Summary 获取访问趋势数据
// @Description 获取小程序访问趋势数据
// @Accept json
// @Produce json
// @Param startDate query string false "开始日期"
// @Param endDate query string false "结束日期"
// @Success 200 {object} response.Response{data=[]response.VisitTrendData} "获取成功"
// @Router /wechat/mini/statistics/visit-trend [get]
func (m *MiniStatisticsApi) GetVisitTrendData(c *gin.Context) {
startDate := c.Query("startDate")
endDate := c.Query("endDate")
trendData, err := miniStatisticsService.GetVisitTrendData(startDate, endDate)
if err != nil {
global.GVA_LOG.Error("获取访问趋势数据失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(trendData, c)
}
// GetSourceData 获取用户来源分布数据
// @Tags MiniStatistics
// @Summary 获取用户来源分布数据
// @Description 获取小程序用户来源分布数据
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=[]response.SourceData} "获取成功"
// @Router /wechat/mini/statistics/source [get]
func (m *MiniStatisticsApi) GetSourceData(c *gin.Context) {
sourceData, err := miniStatisticsService.GetSourceData()
if err != nil {
global.GVA_LOG.Error("获取用户来源分布数据失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(sourceData, c)
}
// GetPageData 获取页面访问数据
// @Tags MiniStatistics
// @Summary 获取页面访问数据
// @Description 获取小程序页面访问数据
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=[]response.PageData} "获取成功"
// @Router /wechat/mini/statistics/page [get]
func (m *MiniStatisticsApi) GetPageData(c *gin.Context) {
pageData, err := miniStatisticsService.GetPageData()
if err != nil {
global.GVA_LOG.Error("获取页面访问数据失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(pageData, c)
}
// GetRetentionData 获取用户留存数据
// @Tags MiniStatistics
// @Summary 获取用户留存数据
// @Description 获取小程序用户留存数据
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=[]response.RetentionData} "获取成功"
// @Router /wechat/mini/statistics/retention [get]
func (m *MiniStatisticsApi) GetRetentionData(c *gin.Context) {
retentionData, err := miniStatisticsService.GetRetentionData()
if err != nil {
global.GVA_LOG.Error("获取用户留存数据失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(retentionData, c)
}
// GetDeviceData 获取设备分布数据
// @Tags MiniStatistics
// @Summary 获取设备分布数据
// @Description 获取小程序设备分布数据
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=[]response.DeviceData} "获取成功"
// @Router /wechat/mini/statistics/device [get]
func (m *MiniStatisticsApi) GetDeviceData(c *gin.Context) {
deviceData, err := miniStatisticsService.GetDeviceData()
if err != nil {
global.GVA_LOG.Error("获取设备分布数据失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(deviceData, c)
}
@@ -0,0 +1,294 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/request"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpAutoReplyApi struct{}
var mpAutoReplyService = service.ServiceGroupApp.MpAutoReplyService
// GetAutoReplyList 获取自动回复列表
// @Tags MpAutoReply
// @Summary 获取自动回复列表
// @Description 获取公众号自动回复规则列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param replyType query int false "回复类型"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mp/auto-reply [get]
func (m *MpAutoReplyApi) GetAutoReplyList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
var replyType *int
if rt := c.Query("replyType"); rt != "" {
if t, err := strconv.Atoi(rt); err == nil {
replyType = &t
}
}
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
replies, total, err := mpAutoReplyService.GetAutoReplyList(page, pageSize, replyType)
if err != nil {
global.GVA_LOG.Error("获取自动回复列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(wechatResponse.PageResult{
List: replies,
Total: total,
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// GetAutoReplyByID 获取自动回复详情
// @Tags MpAutoReply
// @Summary 获取自动回复详情
// @Description 根据ID获取自动回复详细信息
// @Accept json
// @Produce json
// @Param id path int true "自动回复ID"
// @Success 200 {object} response.Response{data=model.MpAutoReply} "获取成功"
// @Router /wechat/mp/auto-reply/{id} [get]
func (m *MpAutoReplyApi) GetAutoReplyByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的自动回复ID", c)
return
}
reply, err := mpAutoReplyService.GetAutoReplyByID(uint(id))
if err != nil {
global.GVA_LOG.Error("获取自动回复详情失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(reply, c)
}
// CreateAutoReply 创建自动回复
// @Tags MpAutoReply
// @Summary 创建自动回复
// @Description 创建新的自动回复规则
// @Accept json
// @Produce json
// @Param data body request.CreateAutoReplyRequest true "创建参数"
// @Success 200 {object} response.Response{data=model.MpAutoReply} "创建成功"
// @Router /wechat/mp/auto-reply [post]
func (m *MpAutoReplyApi) CreateAutoReply(c *gin.Context) {
var req request.CreateAutoReplyRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 验证必填字段
if req.Type == 0 || req.ResponseMessageType == "" || req.ResponseContent == "" {
response.FailWithMessage("回复类型、响应消息类型和响应内容不能为空", c)
return
}
// 构建自动回复对象
autoReply := &model.MpAutoReply{
Type: req.Type,
ResponseMessageType: req.ResponseMessageType,
ResponseContent: &req.ResponseContent,
}
// 根据类型设置特定字段
if req.Type == model.AutoReplyTypeKeyword {
if req.RequestKeyword == "" {
response.FailWithMessage("关键字回复必须设置关键字", c)
return
}
autoReply.RequestKeyword = &req.RequestKeyword
if req.RequestMatch != 0 {
autoReply.RequestMatch = &req.RequestMatch
}
} else if req.Type == model.AutoReplyTypeMessage {
if req.RequestMessageType == "" {
response.FailWithMessage("消息回复必须设置请求消息类型", c)
return
}
autoReply.RequestMessageType = &req.RequestMessageType
}
err = mpAutoReplyService.CreateAutoReply(autoReply)
if err != nil {
global.GVA_LOG.Error("创建自动回复失败!", zap.Error(err))
response.FailWithMessage("创建失败: "+err.Error(), c)
return
}
response.OkWithDetailed(autoReply, "创建成功", c)
}
// UpdateAutoReply 更新自动回复
// @Tags MpAutoReply
// @Summary 更新自动回复
// @Description 更新自动回复规则
// @Accept json
// @Produce json
// @Param data body request.UpdateAutoReplyRequest true "更新参数"
// @Success 200 {object} response.Response "更新成功"
// @Router /wechat/mp/auto-reply [put]
func (m *MpAutoReplyApi) UpdateAutoReply(c *gin.Context) {
var req request.UpdateAutoReplyRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.ID == 0 {
response.FailWithMessage("ID不能为空", c)
return
}
// 获取现有记录
existingReply, err := mpAutoReplyService.GetAutoReplyByID(req.ID)
if err != nil {
global.GVA_LOG.Error("获取自动回复失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
// 更新字段
if req.ResponseContent != "" {
existingReply.ResponseContent = &req.ResponseContent
}
if req.RequestKeyword != "" {
existingReply.RequestKeyword = &req.RequestKeyword
}
if req.RequestMatch != 0 {
existingReply.RequestMatch = &req.RequestMatch
}
err = mpAutoReplyService.UpdateAutoReply(existingReply)
if err != nil {
global.GVA_LOG.Error("更新自动回复失败!", zap.Error(err))
response.FailWithMessage("更新失败: "+err.Error(), c)
return
}
response.OkWithMessage("更新成功", c)
}
// DeleteAutoReply 删除自动回复
// @Tags MpAutoReply
// @Summary 删除自动回复
// @Description 删除指定的自动回复规则
// @Accept json
// @Produce json
// @Param id path int true "自动回复ID"
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/auto-reply/{id} [delete]
func (m *MpAutoReplyApi) DeleteAutoReply(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的自动回复ID", c)
return
}
err = mpAutoReplyService.DeleteAutoReply(uint(id))
if err != nil {
global.GVA_LOG.Error("删除自动回复失败!", zap.Error(err))
response.FailWithMessage("删除失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// TestAutoReply 测试自动回复
// @Tags MpAutoReply
// @Summary 测试自动回复
// @Description 测试关键字是否能触发自动回复
// @Accept json
// @Produce json
// @Param data body request.TestAutoReplyRequest true "测试参数"
// @Success 200 {object} response.Response{data=model.MpAutoReply} "测试成功"
// @Router /wechat/mp/auto-reply/test [post]
func (m *MpAutoReplyApi) TestAutoReply(c *gin.Context) {
var req request.TestAutoReplyRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.Keyword == "" {
response.FailWithMessage("测试关键字不能为空", c)
return
}
reply, err := mpAutoReplyService.TestAutoReply(req.Keyword)
if err != nil {
global.GVA_LOG.Error("测试自动回复失败!", zap.Error(err))
response.FailWithMessage("测试失败: "+err.Error(), c)
return
}
if reply == nil {
response.OkWithDetailed(nil, "没有匹配的自动回复", c)
return
}
response.OkWithDetailed(reply, "找到匹配的自动回复", c)
}
// SetSubscribeReply 设置关注回复
// @Tags MpAutoReply
// @Summary 设置关注回复
// @Description 设置用户关注时的自动回复
// @Accept json
// @Produce json
// @Param data body request.SetSubscribeReplyRequest true "设置参数"
// @Success 200 {object} response.Response "设置成功"
// @Router /wechat/mp/auto-reply/subscribe [post]
func (m *MpAutoReplyApi) SetSubscribeReply(c *gin.Context) {
var req request.SetSubscribeReplyRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.Content == "" {
response.FailWithMessage("回复内容不能为空", c)
return
}
err = mpAutoReplyService.SetSubscribeReply(req.Content)
if err != nil {
global.GVA_LOG.Error("设置关注回复失败!", zap.Error(err))
response.FailWithMessage("设置失败: "+err.Error(), c)
return
}
response.OkWithMessage("设置成功", c)
}
@@ -0,0 +1,369 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/request"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpConfigApi struct{}
var mpConfigService = service.ServiceGroupApp.MpConfigService
// GetWechatConfig 获取微信配置
// @Tags MpConfig
// @Summary 获取微信配置
// @Description 获取微信配置信息
// @Accept json
// @Produce json
// @Param type query string false "配置类型 mp/mini"
// @Success 200 {object} response.Response{data=model.MpConfig} "获取成功"
// @Router /wechat/mp/config [get]
func (m *MpConfigApi) GetWechatConfig(c *gin.Context) {
configType := c.DefaultQuery("type", "mp")
config, err := mpConfigService.GetWechatConfig(configType)
if err != nil {
global.GVA_LOG.Error("获取微信配置失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(config, c)
}
// SaveWechatConfig 保存微信配置
// @Tags MpConfig
// @Summary 保存微信配置
// @Description 保存微信配置信息
// @Accept json
// @Produce json
// @Param data body request.SaveMpConfigRequest true "配置信息"
// @Success 200 {object} response.Response "保存成功"
// @Router /wechat/mp/config [post]
func (m *MpConfigApi) SaveWechatConfig(c *gin.Context) {
var req request.SaveMpConfigRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 将DTO转换为数据库实体
config := &model.MpConfig{
ConfigType: req.ConfigType,
AppID: req.AppID,
AppSecret: req.AppSecret,
Token: req.Token,
EncodingAESKey: req.EncodingAESKey,
ServerURL: req.ServerURL,
WebhookURL: req.WebhookURL,
MiniAppName: req.MiniAppName,
MiniAppDesc: req.MiniAppDesc,
Status: req.Status,
ExtraConfig: req.ExtraConfig,
Remark: req.Remark,
}
// 验证配置
err = mpConfigService.ValidateConfig(config)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = mpConfigService.SaveWechatConfig(config)
if err != nil {
global.GVA_LOG.Error("保存微信配置失败!", zap.Error(err))
response.FailWithMessage("保存失败: "+err.Error(), c)
return
}
response.OkWithData(config, c)
}
// TestWechatConfig 测试微信配置
// @Tags MpConfig
// @Summary 测试微信配置
// @Description 测试微信配置是否有效
// @Accept json
// @Produce json
// @Param data body request.TestMpConfigRequest true "配置信息"
// @Success 200 {object} response.Response "测试成功"
// @Router /wechat/mp/config/test [post]
func (m *MpConfigApi) TestWechatConfig(c *gin.Context) {
var req request.TestMpConfigRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 将DTO转换为数据库实体
config := &model.MpConfig{
ConfigType: req.ConfigType,
AppID: req.AppID,
AppSecret: req.AppSecret,
Token: req.Token,
EncodingAESKey: req.EncodingAESKey,
ServerURL: req.ServerURL,
WebhookURL: req.WebhookURL,
MiniAppName: req.MiniAppName,
MiniAppDesc: req.MiniAppDesc,
}
err = mpConfigService.TestWechatConfig(config)
if err != nil {
global.GVA_LOG.Error("测试微信配置失败!", zap.Error(err))
response.FailWithMessage("测试失败: "+err.Error(), c)
return
}
response.OkWithMessage("测试成功", c)
}
// GetConfigList 获取配置列表
// @Tags MpConfig
// @Summary 获取配置列表
// @Description 获取所有微信配置列表
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=[]model.MpConfig} "获取成功"
// @Router /wechat/mp/config/list [get]
func (m *MpConfigApi) GetConfigList(c *gin.Context) {
configs, err := mpConfigService.GetConfigList()
if err != nil {
global.GVA_LOG.Error("获取配置列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(configs, c)
}
// DeleteConfig 删除配置
// @Tags MpConfig
// @Summary 删除配置
// @Description 删除微信配置
// @Accept json
// @Produce json
// @Param id path int true "配置ID"
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/config/{id} [delete]
func (m *MpConfigApi) DeleteConfig(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的配置ID", c)
return
}
err = mpConfigService.DeleteConfig(uint(id))
if err != nil {
global.GVA_LOG.Error("删除配置失败!", zap.Error(err))
response.FailWithMessage("删除失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// GetWebhookLogs 获取Webhook日志
// @Tags MpConfig
// @Summary 获取Webhook日志
// @Description 获取微信Webhook日志列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param configId query int false "配置ID"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mp/webhook/logs [get]
func (m *MpConfigApi) GetWebhookLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
var configID *uint
if configIDStr := c.Query("configId"); configIDStr != "" {
if id, err := strconv.ParseUint(configIDStr, 10, 32); err == nil {
configIDUint := uint(id)
configID = &configIDUint
}
}
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
logs, total, err := mpConfigService.GetWebhookLogs(page, pageSize, configID)
if err != nil {
global.GVA_LOG.Error("获取Webhook日志失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(wechatResponse.PageResult{
List: logs,
Total: total,
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// GetWebhookLogDetail 获取Webhook日志详情
// @Tags MpConfig
// @Summary 获取Webhook日志详情
// @Description 获取微信Webhook日志详情
// @Accept json
// @Produce json
// @Param id path int true "日志ID"
// @Success 200 {object} response.Response{data=model.MpWebhookLog} "获取成功"
// @Router /wechat/mp/webhook/logs/{id} [get]
func (m *MpConfigApi) GetWebhookLogDetail(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的日志ID", c)
return
}
log, err := mpConfigService.GetWebhookLogByID(uint(id))
if err != nil {
global.GVA_LOG.Error("获取Webhook日志详情失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(log, c)
}
// ClearWebhookLogs 清空Webhook日志
// @Tags MpConfig
// @Summary 清空Webhook日志
// @Description 清空微信Webhook日志
// @Accept json
// @Produce json
// @Param configId query int false "配置ID"
// @Success 200 {object} response.Response "清空成功"
// @Router /wechat/mp/webhook/logs/clear [post]
func (m *MpConfigApi) ClearWebhookLogs(c *gin.Context) {
var configID *uint
if configIDStr := c.Query("configId"); configIDStr != "" {
if id, err := strconv.ParseUint(configIDStr, 10, 32); err == nil {
configIDUint := uint(id)
configID = &configIDUint
}
}
err := mpConfigService.ClearWebhookLogs(configID)
if err != nil {
global.GVA_LOG.Error("清空Webhook日志失败!", zap.Error(err))
response.FailWithMessage("清空失败: "+err.Error(), c)
return
}
response.OkWithMessage("清空成功", c)
}
// ValidateConfig 验证微信配置
// @Tags MpConfig
// @Summary 验证微信配置
// @Description 验证微信配置是否有效
// @Accept json
// @Produce json
// @Success 200 {object} response.Response "验证成功"
// @Router /wechat/mp/config/validate [post]
func (m *MpConfigApi) ValidateConfig(c *gin.Context) {
// 获取当前配置
config, err := mpConfigService.GetWechatConfig("mp")
if err != nil {
global.GVA_LOG.Error("获取微信配置失败!", zap.Error(err))
response.FailWithMessage("获取配置失败: "+err.Error(), c)
return
}
// 验证配置
err = mpConfigService.ValidateConfig(config)
if err != nil {
global.GVA_LOG.Error("验证微信配置失败!", zap.Error(err))
response.FailWithMessage("验证失败: "+err.Error(), c)
return
}
// 使用微信公众号服务验证配置
wechatMpService := service.ServiceGroupApp.WechatMpService
err = wechatMpService.ValidateConfig(config)
if err != nil {
global.GVA_LOG.Error("配置连通性测试失败!", zap.Error(err))
response.FailWithMessage("配置连通性测试失败: "+err.Error(), c)
return
}
response.OkWithMessage("验证成功", c)
}
// GenerateQrCode 生成公众号二维码
// @Tags MpConfig
// @Summary 生成公众号二维码
// @Description 生成公众号二维码
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=map[string]string} "生成成功"
// @Router /wechat/mp/qrcode/generate [post]
func (m *MpConfigApi) GenerateQrCode(c *gin.Context) {
// 获取当前配置
config, err := mpConfigService.GetWechatConfig("mp")
if err != nil {
global.GVA_LOG.Error("获取微信配置失败!", zap.Error(err))
response.FailWithMessage("获取配置失败: "+err.Error(), c)
return
}
// 生成二维码
qrCodeUrl, err := mpConfigService.GenerateQrCode(config)
if err != nil {
global.GVA_LOG.Error("生成二维码失败!", zap.Error(err))
response.FailWithMessage("生成失败: "+err.Error(), c)
return
}
response.OkWithData(map[string]string{"qrCodeUrl": qrCodeUrl}, c)
}
// ClearQuota 清空API配额
// @Tags MpConfig
// @Summary 清空API配额
// @Description 清空微信公众号API配额
// @Accept json
// @Produce json
// @Success 200 {object} response.Response "清空成功"
// @Router /wechat/mp/quota/clear [post]
func (m *MpConfigApi) ClearQuota(c *gin.Context) {
// 获取当前配置
config, err := mpConfigService.GetWechatConfig("mp")
if err != nil {
global.GVA_LOG.Error("获取微信配置失败!", zap.Error(err))
response.FailWithMessage("获取配置失败: "+err.Error(), c)
return
}
// 清空配额
err = mpConfigService.ClearQuota(config)
if err != nil {
global.GVA_LOG.Error("清空API配额失败!", zap.Error(err))
response.FailWithMessage("清空失败: "+err.Error(), c)
return
}
response.OkWithMessage("清空成功", c)
}
@@ -0,0 +1,226 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpDraftApi struct{}
var mpDraftService = service.ServiceGroupApp.MpDraftService
// GetDraftList 获取草稿列表
// @Tags MpDraft
// @Summary 获取草稿列表
// @Description 获取公众号草稿列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param title query string false "标题"
// @Param author query string false "作者"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mp/draft [get]
func (m *MpDraftApi) GetDraftList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
title := c.Query("title")
author := c.Query("author")
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
draftResponse, err := mpDraftService.GetDraftList(page, pageSize, title, author)
if err != nil {
global.GVA_LOG.Error("获取草稿列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(wechatResponse.PageResult{
List: draftResponse.Items,
Total: int64(draftResponse.TotalCount),
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// GetDraftInfo 获取草稿详情
// @Tags MpDraft
// @Summary 获取草稿详情
// @Description 获取公众号草稿详情
// @Accept json
// @Produce json
// @Param id path string true "草稿MediaID"
// @Success 200 {object} response.Response{data=service.DraftItem} "获取成功"
// @Router /wechat/mp/draft/{id} [get]
func (m *MpDraftApi) GetDraftInfo(c *gin.Context) {
mediaID := c.Param("id")
if mediaID == "" {
response.FailWithMessage("草稿MediaID不能为空", c)
return
}
draft, err := mpDraftService.GetDraftInfo(mediaID)
if err != nil {
global.GVA_LOG.Error("获取草稿详情失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(draft, c)
}
// CreateDraft 创建草稿
// @Tags MpDraft
// @Summary 创建草稿
// @Description 创建公众号草稿
// @Accept json
// @Produce json
// @Param data body service.CreateDraftRequest true "草稿信息"
// @Success 200 {object} response.Response "创建成功"
// @Router /wechat/mp/draft [post]
func (m *MpDraftApi) CreateDraft(c *gin.Context) {
var req service.CreateDraftRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
mediaID, err := mpDraftService.CreateDraft(&req)
if err != nil {
global.GVA_LOG.Error("创建草稿失败!", zap.Error(err))
response.FailWithMessage("创建失败: "+err.Error(), c)
return
}
response.OkWithData(map[string]string{"mediaId": mediaID}, c)
}
// UpdateDraft 更新草稿
// @Tags MpDraft
// @Summary 更新草稿
// @Description 更新公众号草稿
// @Accept json
// @Produce json
// @Param data body service.UpdateDraftRequest true "草稿信息"
// @Success 200 {object} response.Response "更新成功"
// @Router /wechat/mp/draft [put]
func (m *MpDraftApi) UpdateDraft(c *gin.Context) {
var req service.UpdateDraftRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
err = mpDraftService.UpdateDraft(&req)
if err != nil {
global.GVA_LOG.Error("更新草稿失败!", zap.Error(err))
response.FailWithMessage("更新失败: "+err.Error(), c)
return
}
response.OkWithMessage("更新成功", c)
}
// DeleteDraft 删除草稿
// @Tags MpDraft
// @Summary 删除草稿
// @Description 删除公众号草稿
// @Accept json
// @Produce json
// @Param id path string true "草稿MediaID"
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/draft/{id} [delete]
func (m *MpDraftApi) DeleteDraft(c *gin.Context) {
mediaID := c.Param("id")
if mediaID == "" {
response.FailWithMessage("草稿MediaID不能为空", c)
return
}
err := mpDraftService.DeleteDraft(mediaID)
if err != nil {
global.GVA_LOG.Error("删除草稿失败!", zap.Error(err))
response.FailWithMessage("删除失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// PublishDraft 发布草稿
// @Tags MpDraft
// @Summary 发布草稿
// @Description 发布公众号草稿
// @Accept json
// @Produce json
// @Param id path string true "草稿MediaID"
// @Success 200 {object} response.Response "发布成功"
// @Router /wechat/mp/draft/{id}/publish [post]
func (m *MpDraftApi) PublishDraft(c *gin.Context) {
mediaID := c.Param("id")
if mediaID == "" {
response.FailWithMessage("草稿MediaID不能为空", c)
return
}
err := mpDraftService.PublishDraft(mediaID)
if err != nil {
global.GVA_LOG.Error("发布草稿失败!", zap.Error(err))
response.FailWithMessage("发布失败: "+err.Error(), c)
return
}
response.OkWithMessage("发布成功", c)
}
// SyncDrafts 同步草稿
// @Tags MpDraft
// @Summary 同步草稿
// @Description 同步公众号草稿
// @Accept json
// @Produce json
// @Success 200 {object} response.Response "同步成功"
// @Router /wechat/mp/draft/sync [post]
func (m *MpDraftApi) SyncDrafts(c *gin.Context) {
err := mpDraftService.SyncDrafts()
if err != nil {
global.GVA_LOG.Error("同步草稿失败!", zap.Error(err))
response.FailWithMessage("同步失败: "+err.Error(), c)
return
}
response.OkWithMessage("同步成功", c)
}
// GetDraftCount 获取草稿数量统计
// @Tags MpDraft
// @Summary 获取草稿数量统计
// @Description 获取草稿数量统计信息
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=map[string]int64} "获取成功"
// @Router /wechat/mp/draft/count [get]
func (m *MpDraftApi) GetDraftCount(c *gin.Context) {
count, err := mpDraftService.GetDraftCount()
if err != nil {
global.GVA_LOG.Error("获取草稿数量统计失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(count, c)
}
@@ -0,0 +1,293 @@
package api
import (
"io"
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpMaterialApi struct{}
var mpMaterialService = service.ServiceGroupApp.MpMaterialService
// GetMaterialList 获取素材列表
// @Tags MpMaterial
// @Summary 获取素材列表
// @Description 获取公众号素材列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param materialType query string false "素材类型"
// @Param permanent query bool false "是否永久素材"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mp/material [get]
func (m *MpMaterialApi) GetMaterialList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
materialType := c.Query("materialType")
var permanent *bool
if p := c.Query("permanent"); p != "" {
if perm, err := strconv.ParseBool(p); err == nil {
permanent = &perm
}
}
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
materials, total, err := mpMaterialService.GetMaterialList(page, pageSize, materialType, permanent)
if err != nil {
global.GVA_LOG.Error("获取素材列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(wechatResponse.PageResult{
List: materials,
Total: total,
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// GetMaterialByID 获取素材详情
// @Tags MpMaterial
// @Summary 获取素材详情
// @Description 根据ID获取素材详细信息
// @Accept json
// @Produce json
// @Param id path int true "素材ID"
// @Success 200 {object} response.Response{data=model.MpMaterial} "获取成功"
// @Router /wechat/mp/material/{id} [get]
func (m *MpMaterialApi) GetMaterialByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的素材ID", c)
return
}
material, err := mpMaterialService.GetMaterialByID(uint(id))
if err != nil {
global.GVA_LOG.Error("获取素材详情失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(material, c)
}
// UploadTempMaterial 上传临时素材
// @Tags MpMaterial
// @Summary 上传临时素材
// @Description 上传临时素材到微信服务器
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "素材文件"
// @Param type formData string true "素材类型:image、voice、video、thumb"
// @Success 200 {object} response.Response{data=model.MpMaterial} "上传成功"
// @Router /wechat/mp/material/temp [post]
func (m *MpMaterialApi) UploadTempMaterial(c *gin.Context) {
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
response.FailWithMessage("获取文件失败: "+err.Error(), c)
return
}
defer file.Close()
materialType := c.PostForm("type")
if materialType == "" {
response.FailWithMessage("素材类型不能为空", c)
return
}
// 验证素材类型
validTypes := map[string]bool{
"image": true,
"voice": true,
"video": true,
"thumb": true,
}
if !validTypes[materialType] {
response.FailWithMessage("无效的素材类型", c)
return
}
material, err := mpMaterialService.UploadTempMaterial(file, header.Filename, materialType)
if err != nil {
global.GVA_LOG.Error("上传临时素材失败!", zap.Error(err))
response.FailWithMessage("上传失败: "+err.Error(), c)
return
}
response.OkWithDetailed(material, "上传成功", c)
}
// UploadPermanentMaterial 上传永久素材
// @Tags MpMaterial
// @Summary 上传永久素材
// @Description 上传永久素材到微信服务器
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "素材文件"
// @Param type formData string true "素材类型:image、voice、video、thumb"
// @Success 200 {object} response.Response{data=model.MpMaterial} "上传成功"
// @Router /wechat/mp/material/permanent [post]
func (m *MpMaterialApi) UploadPermanentMaterial(c *gin.Context) {
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
response.FailWithMessage("获取文件失败: "+err.Error(), c)
return
}
defer file.Close()
materialType := c.PostForm("type")
if materialType == "" {
response.FailWithMessage("素材类型不能为空", c)
return
}
// 验证素材类型
validTypes := map[string]bool{
"image": true,
"voice": true,
"video": true,
"thumb": true,
}
if !validTypes[materialType] {
response.FailWithMessage("无效的素材类型", c)
return
}
material, err := mpMaterialService.UploadPermanentMaterial(file, header.Filename, materialType)
if err != nil {
global.GVA_LOG.Error("上传永久素材失败!", zap.Error(err))
response.FailWithMessage("上传失败: "+err.Error(), c)
return
}
response.OkWithDetailed(material, "上传成功", c)
}
// DeleteMaterial 删除素材
// @Tags MpMaterial
// @Summary 删除素材
// @Description 删除指定的素材
// @Accept json
// @Produce json
// @Param id path int true "素材ID"
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/material/{id} [delete]
func (m *MpMaterialApi) DeleteMaterial(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的素材ID", c)
return
}
err = mpMaterialService.DeleteMaterial(uint(id))
if err != nil {
global.GVA_LOG.Error("删除素材失败!", zap.Error(err))
response.FailWithMessage("删除失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// GetMaterialByMediaID 根据MediaID获取素材
// @Tags MpMaterial
// @Summary 根据MediaID获取素材
// @Description 根据微信MediaID获取素材信息
// @Accept json
// @Produce json
// @Param mediaId query string true "微信MediaID"
// @Success 200 {object} response.Response{data=model.MpMaterial} "获取成功"
// @Router /wechat/mp/material/media [get]
func (m *MpMaterialApi) GetMaterialByMediaID(c *gin.Context) {
mediaID := c.Query("mediaId")
if mediaID == "" {
response.FailWithMessage("MediaID不能为空", c)
return
}
material, err := mpMaterialService.GetMaterialByMediaID(mediaID)
if err != nil {
global.GVA_LOG.Error("获取素材失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(material, c)
}
// DownloadMaterial 下载素材
// @Tags MpMaterial
// @Summary 下载素材
// @Description 从微信服务器下载素材
// @Accept json
// @Produce json
// @Param mediaId query string true "素材MediaID"
// @Success 200 {object} response.Response "下载成功"
// @Router /wechat/mp/material/download [get]
func (m *MpMaterialApi) DownloadMaterial(c *gin.Context) {
mediaID := c.Query("mediaId")
if mediaID == "" {
response.FailWithMessage("MediaID不能为空", c)
return
}
reader, filename, err := mpMaterialService.DownloadMaterial(mediaID)
if err != nil {
global.GVA_LOG.Error("下载素材失败!", zap.Error(err))
response.FailWithMessage("下载失败: "+err.Error(), c)
return
}
defer reader.Close()
// 设置响应头
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Header("Content-Type", "application/octet-stream")
// 将文件内容写入响应
_, err = io.Copy(c.Writer, reader)
if err != nil {
global.GVA_LOG.Error("写入响应失败!", zap.Error(err))
response.FailWithMessage("下载失败", c)
return
}
}
// SyncMaterials 同步素材
// @Tags MpMaterial
// @Summary 同步素材
// @Description 从微信服务器同步素材列表
// @Accept json
// @Produce json
// @Success 200 {object} response.Response "同步成功"
// @Router /wechat/mp/material/sync [post]
func (m *MpMaterialApi) SyncMaterials(c *gin.Context) {
err := mpMaterialService.SyncMaterialsFromWechat()
if err != nil {
global.GVA_LOG.Error("同步素材失败!", zap.Error(err))
response.FailWithMessage("同步失败: "+err.Error(), c)
return
}
response.OkWithMessage("同步成功", c)
}
@@ -0,0 +1,247 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/request"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpMenuApi struct{}
var mpMenuService = service.ServiceGroupApp.MpMenuService
// GetMenuList 获取菜单列表
// @Tags MpMenu
// @Summary 获取菜单列表
// @Description 获取公众号菜单配置列表
// @Accept json
// @Produce json
// @Success 200 {object} response.Response{data=[]model.MpMenu} "获取成功"
// @Router /wechat/mp/menu [get]
func (m *MpMenuApi) GetMenuList(c *gin.Context) {
menus, err := mpMenuService.GetMenuList()
if err != nil {
global.GVA_LOG.Error("获取菜单列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(menus, "获取成功", c)
}
// GetMenuByID 获取菜单详情
// @Tags MpMenu
// @Summary 获取菜单详情
// @Description 根据ID获取菜单详细信息
// @Accept json
// @Produce json
// @Param id path int true "菜单ID"
// @Success 200 {object} response.Response{data=model.MpMenu} "获取成功"
// @Router /wechat/mp/menu/{id} [get]
func (m *MpMenuApi) GetMenuByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的菜单ID", c)
return
}
menu, err := mpMenuService.GetMenuByID(uint(id))
if err != nil {
global.GVA_LOG.Error("获取菜单详情失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(menu, c)
}
// CreateMenu 创建菜单
// @Tags MpMenu
// @Summary 创建菜单
// @Description 创建新的菜单项
// @Accept json
// @Produce json
// @Param data body request.CreateMenuRequest true "创建参数"
// @Success 200 {object} response.Response{data=model.MpMenu} "创建成功"
// @Router /wechat/mp/menu [post]
func (m *MpMenuApi) CreateMenu(c *gin.Context) {
var req request.CreateMenuRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 验证必填字段
if req.Name == "" || req.Type == "" {
response.FailWithMessage("菜单名称和类型不能为空", c)
return
}
// 构建菜单对象
menu := &model.MpMenu{
Name: &req.Name,
Type: req.Type,
}
// 设置可选字段
if req.ParentID != "" {
menu.ParentID = &req.ParentID
}
if req.MenuKey != "" {
menu.MenuKey = &req.MenuKey
}
if req.URL != "" {
menu.URL = &req.URL
}
if req.MiniProgramAppID != "" {
menu.MiniProgramAppID = &req.MiniProgramAppID
}
if req.MiniProgramPagePath != "" {
menu.MiniProgramPagePath = &req.MiniProgramPagePath
}
if req.ReplyMessageType != "" {
menu.ReplyMessageType = &req.ReplyMessageType
}
if req.ReplyContent != "" {
menu.ReplyContent = &req.ReplyContent
}
err = mpMenuService.CreateMenu(menu)
if err != nil {
global.GVA_LOG.Error("创建菜单失败!", zap.Error(err))
response.FailWithMessage("创建失败: "+err.Error(), c)
return
}
response.OkWithDetailed(menu, "创建成功", c)
}
// UpdateMenu 更新菜单
// @Tags MpMenu
// @Summary 更新菜单
// @Description 更新菜单项
// @Accept json
// @Produce json
// @Param data body request.UpdateMenuRequest true "更新参数"
// @Success 200 {object} response.Response "更新成功"
// @Router /wechat/mp/menu [put]
func (m *MpMenuApi) UpdateMenu(c *gin.Context) {
var req request.UpdateMenuRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.ID == 0 {
response.FailWithMessage("ID不能为空", c)
return
}
// 获取现有记录
existingMenu, err := mpMenuService.GetMenuByID(req.ID)
if err != nil {
global.GVA_LOG.Error("获取菜单失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
// 更新字段
if req.Name != "" {
existingMenu.Name = &req.Name
}
if req.Type != "" {
existingMenu.Type = req.Type
}
if req.MenuKey != "" {
existingMenu.MenuKey = &req.MenuKey
}
if req.URL != "" {
existingMenu.URL = &req.URL
}
if req.ReplyContent != "" {
existingMenu.ReplyContent = &req.ReplyContent
}
err = mpMenuService.UpdateMenu(existingMenu)
if err != nil {
global.GVA_LOG.Error("更新菜单失败!", zap.Error(err))
response.FailWithMessage("更新失败: "+err.Error(), c)
return
}
response.OkWithMessage("更新成功", c)
}
// DeleteMenu 删除菜单
// @Tags MpMenu
// @Summary 删除菜单
// @Description 删除指定的菜单项
// @Accept json
// @Produce json
// @Param id path int true "菜单ID"
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/menu/{id} [delete]
func (m *MpMenuApi) DeleteMenu(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的菜单ID", c)
return
}
err = mpMenuService.DeleteMenu(uint(id))
if err != nil {
global.GVA_LOG.Error("删除菜单失败!", zap.Error(err))
response.FailWithMessage("删除失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// PublishMenu 发布菜单
// @Tags MpMenu
// @Summary 发布菜单
// @Description 将菜单配置发布到微信服务器
// @Accept json
// @Produce json
// @Success 200 {object} response.Response "发布成功"
// @Router /wechat/mp/menu/publish [post]
func (m *MpMenuApi) PublishMenu(c *gin.Context) {
err := mpMenuService.PublishMenu()
if err != nil {
global.GVA_LOG.Error("发布菜单失败!", zap.Error(err))
response.FailWithMessage("发布失败: "+err.Error(), c)
return
}
response.OkWithMessage("发布成功", c)
}
// DeleteWechatMenu 删除微信菜单
// @Tags MpMenu
// @Summary 删除微信菜单
// @Description 删除微信服务器上的菜单
// @Accept json
// @Produce json
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/menu/wechat [delete]
func (m *MpMenuApi) DeleteWechatMenu(c *gin.Context) {
err := mpMenuService.DeleteWechatMenu()
if err != nil {
global.GVA_LOG.Error("删除微信菜单失败!", zap.Error(err))
response.FailWithMessage("删除失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
@@ -0,0 +1,175 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/request"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpMessageApi struct{}
var mpMessageService = service.ServiceGroupApp.MpMessageService
// GetMessageList 获取消息列表
// @Tags MpMessage
// @Summary 获取消息列表
// @Description 获取公众号消息记录列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param msgType query string false "消息类型"
// @Param openid query string false "用户openid"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mp/messages [get]
func (m *MpMessageApi) GetMessageList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
msgType := c.Query("msgType")
openid := c.Query("openid")
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
messages, total, err := mpMessageService.GetMessageList(page, pageSize, msgType, openid)
if err != nil {
global.GVA_LOG.Error("获取消息列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(wechatResponse.PageResult{
List: messages,
Total: total,
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// SendTextMessage 发送文本消息
// @Tags MpMessage
// @Summary 发送文本消息
// @Description 向指定用户发送文本消息
// @Accept json
// @Produce json
// @Param data body request.SendTextMessageRequest true "发送参数"
// @Success 200 {object} response.Response "发送成功"
// @Router /wechat/mp/message/text [post]
func (m *MpMessageApi) SendTextMessage(c *gin.Context) {
var req request.SendTextMessageRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.OpenID == "" || req.Content == "" {
response.FailWithMessage("openid和消息内容不能为空", c)
return
}
err = mpMessageService.SendTextMessage(req.OpenID, req.Content)
if err != nil {
global.GVA_LOG.Error("发送文本消息失败!", zap.Error(err))
response.FailWithMessage("发送失败: "+err.Error(), c)
return
}
response.OkWithMessage("发送成功", c)
}
// SendImageMessage 发送图片消息
// @Tags MpMessage
// @Summary 发送图片消息
// @Description 向指定用户发送图片消息
// @Accept json
// @Produce json
// @Param data body request.SendImageMessageRequest true "发送参数"
// @Success 200 {object} response.Response "发送成功"
// @Router /wechat/mp/message/image [post]
func (m *MpMessageApi) SendImageMessage(c *gin.Context) {
var req request.SendImageMessageRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.OpenID == "" || req.MediaID == "" {
response.FailWithMessage("openid和媒体ID不能为空", c)
return
}
err = mpMessageService.SendImageMessage(req.OpenID, req.MediaID)
if err != nil {
global.GVA_LOG.Error("发送图片消息失败!", zap.Error(err))
response.FailWithMessage("发送失败: "+err.Error(), c)
return
}
response.OkWithMessage("发送成功", c)
}
// GetMessageByID 获取消息详情
// @Tags MpMessage
// @Summary 获取消息详情
// @Description 根据ID获取消息详细信息
// @Accept json
// @Produce json
// @Param id path int true "消息ID"
// @Success 200 {object} response.Response{data=model.MpMessage} "获取成功"
// @Router /wechat/mp/message/{id} [get]
func (m *MpMessageApi) GetMessageByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的消息ID", c)
return
}
message, err := mpMessageService.GetMessageByID(uint(id))
if err != nil {
global.GVA_LOG.Error("获取消息详情失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(message, c)
}
// DeleteMessage 删除消息
// @Tags MpMessage
// @Summary 删除消息
// @Description 删除指定的消息记录
// @Accept json
// @Produce json
// @Param id path int true "消息ID"
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/message/{id} [delete]
func (m *MpMessageApi) DeleteMessage(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的消息ID", c)
return
}
err = mpMessageService.DeleteMessage(uint(id))
if err != nil {
global.GVA_LOG.Error("删除消息失败!", zap.Error(err))
response.FailWithMessage("删除失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
@@ -0,0 +1,259 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/request"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpNewsApi struct{}
var mpNewsService = service.ServiceGroupApp.MpNewsService
// GetNewsList 获取图文发表记录列表
// @Tags MpNews
// @Summary 获取图文发表记录列表
// @Description 获取公众号图文发表记录列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param status query string false "发布状态"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mp/news [get]
func (m *MpNewsApi) GetNewsList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
status := c.Query("status")
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
newsList, total, err := mpNewsService.GetNewsList(page, pageSize, status)
if err != nil {
global.GVA_LOG.Error("获取图文发表记录列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(wechatResponse.PageResult{
List: newsList,
Total: total,
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// GetNewsInfo 获取图文详情
// @Tags MpNews
// @Summary 获取图文详情
// @Description 获取公众号图文详情
// @Accept json
// @Produce json
// @Param id path int true "图文ID"
// @Success 200 {object} response.Response{data=model.MpNews} "获取成功"
// @Router /wechat/mp/news/{id} [get]
func (m *MpNewsApi) GetNewsInfo(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的图文ID", c)
return
}
news, err := mpNewsService.GetNewsInfo(uint(id))
if err != nil {
global.GVA_LOG.Error("获取图文详情失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(news, c)
}
// CreateNews 创建图文记录
// @Tags MpNews
// @Summary 创建图文记录
// @Description 创建公众号图文记录
// @Accept json
// @Produce json
// @Param data body request.CreateMpNewsRequest true "图文信息"
// @Success 200 {object} response.Response "创建成功"
// @Router /wechat/mp/news [post]
func (m *MpNewsApi) CreateNews(c *gin.Context) {
var req request.CreateMpNewsRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// 将DTO转换为数据库实体
news := &model.MpNews{
MediaID: req.MediaID,
Title: req.Title,
Author: req.Author,
Digest: req.Digest,
Content: req.Content,
ContentURL: req.ContentURL,
SourceURL: req.SourceURL,
ThumbMediaID: req.ThumbMediaID,
ThumbURL: req.ThumbURL,
ShowCover: req.ShowCover,
NeedOpenComment: req.NeedOpenComment,
OnlyFansCanComment: req.OnlyFansCanComment,
PublishStatus: req.PublishStatus,
WechatURL: req.WechatURL,
WechatMsgID: req.WechatMsgID,
}
err = mpNewsService.CreateNews(news)
if err != nil {
global.GVA_LOG.Error("创建图文记录失败!", zap.Error(err))
response.FailWithMessage("创建失败: "+err.Error(), c)
return
}
response.OkWithData(news, c)
}
// UpdateNews 更新图文记录
// @Tags MpNews
// @Summary 更新图文记录
// @Description 更新公众号图文记录
// @Accept json
// @Produce json
// @Param data body request.UpdateMpNewsRequest true "图文信息"
// @Success 200 {object} response.Response "更新成功"
// @Router /wechat/mp/news [put]
func (m *MpNewsApi) UpdateNews(c *gin.Context) {
var req request.UpdateMpNewsRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.ID == 0 {
response.FailWithMessage("ID不能为空", c)
return
}
// 获取现有记录以保留时间字段
existingNews, err := mpNewsService.GetNewsInfo(req.ID)
if err != nil {
global.GVA_LOG.Error("获取图文记录失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
// 更新字段,保留原有的时间字段
existingNews.MediaID = req.MediaID
existingNews.Title = req.Title
existingNews.Author = req.Author
existingNews.Digest = req.Digest
existingNews.Content = req.Content
existingNews.ContentURL = req.ContentURL
existingNews.SourceURL = req.SourceURL
existingNews.ThumbMediaID = req.ThumbMediaID
existingNews.ThumbURL = req.ThumbURL
existingNews.ShowCover = req.ShowCover
existingNews.NeedOpenComment = req.NeedOpenComment
existingNews.OnlyFansCanComment = req.OnlyFansCanComment
existingNews.PublishStatus = req.PublishStatus
existingNews.WechatURL = req.WechatURL
existingNews.WechatMsgID = req.WechatMsgID
err = mpNewsService.UpdateNews(existingNews)
if err != nil {
global.GVA_LOG.Error("更新图文记录失败!", zap.Error(err))
response.FailWithMessage("更新失败: "+err.Error(), c)
return
}
response.OkWithMessage("更新成功", c)
}
// DeleteNews 删除图文记录
// @Tags MpNews
// @Summary 删除图文记录
// @Description 删除公众号图文记录
// @Accept json
// @Produce json
// @Param id path int true "图文ID"
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/news/{id} [delete]
func (m *MpNewsApi) DeleteNews(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的图文ID", c)
return
}
err = mpNewsService.DeleteNews(uint(id))
if err != nil {
global.GVA_LOG.Error("删除图文记录失败!", zap.Error(err))
response.FailWithMessage("删除失败: "+err.Error(), c)
return
}
response.OkWithMessage("删除成功", c)
}
// GetNewsStatistics 获取图文数据统计
// @Tags MpNews
// @Summary 获取图文数据统计
// @Description 获取公众号图文数据统计
// @Accept json
// @Produce json
// @Param id path int true "图文ID"
// @Success 200 {object} response.Response{data=map[string]interface{}} "获取成功"
// @Router /wechat/mp/news/{id}/statistics [get]
func (m *MpNewsApi) GetNewsStatistics(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的图文ID", c)
return
}
statistics, err := mpNewsService.GetNewsStatistics(uint(id))
if err != nil {
global.GVA_LOG.Error("获取图文数据统计失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(statistics, c)
}
// SyncNews 同步图文发表记录
// @Tags MpNews
// @Summary 同步图文发表记录
// @Description 同步公众号图文发表记录
// @Accept json
// @Produce json
// @Success 200 {object} response.Response "同步成功"
// @Router /wechat/mp/news/sync [post]
func (m *MpNewsApi) SyncNews(c *gin.Context) {
err := mpNewsService.SyncNews()
if err != nil {
global.GVA_LOG.Error("同步图文发表记录失败!", zap.Error(err))
response.FailWithMessage("同步失败: "+err.Error(), c)
return
}
response.OkWithMessage("同步成功", c)
}
@@ -0,0 +1,130 @@
package api
import (
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpStatisticsApi struct{}
// GetUserSummary 获得粉丝增减数据
// @Tags MpStatistics
// @Summary 获得粉丝增减数据
// @Description 获得粉丝增减数据
// @Accept json
// @Produce json
// @Param accountId query int true "公众号账号ID"
// @Param date query string true "日期"
// @Success 200 {object} response.Response "获取成功"
// @Router /wechat/mp/statistics/user-summary [get]
func (m *MpStatisticsApi) GetUserSummary(c *gin.Context) {
accountId := c.Query("accountId")
date := c.Query("date")
// TODO: 实现粉丝增减数据获取逻辑
global.GVA_LOG.Info("获取粉丝增减数据", zap.String("accountId", accountId), zap.String("date", date))
// 模拟数据
data := []map[string]interface{}{
{
"refDate": "2025-08-04",
"userSource": 0,
"newUser": 12,
"cancelUser": 3,
"cumulateUser": 1350,
},
}
response.OkWithData(data, c)
}
// GetUserCumulate 获得粉丝累计数据
// @Tags MpStatistics
// @Summary 获得粉丝累计数据
// @Description 获得粉丝累计数据
// @Accept json
// @Produce json
// @Param accountId query int true "公众号账号ID"
// @Param date query string true "日期"
// @Success 200 {object} response.Response "获取成功"
// @Router /wechat/mp/statistics/user-cumulate [get]
func (m *MpStatisticsApi) GetUserCumulate(c *gin.Context) {
accountId := c.Query("accountId")
date := c.Query("date")
// TODO: 实现粉丝累计数据获取逻辑
global.GVA_LOG.Info("获取粉丝累计数据", zap.String("accountId", accountId), zap.String("date", date))
// 模拟数据
data := []map[string]interface{}{
{
"refDate": "2025-08-04",
"cumulateUser": 1350,
},
}
response.OkWithData(data, c)
}
// GetUpstreamMessage 获取消息发送概况数据
// @Tags MpStatistics
// @Summary 获取消息发送概况数据
// @Description 获取消息发送概况数据
// @Accept json
// @Produce json
// @Param accountId query int true "公众号账号ID"
// @Param date query string true "日期"
// @Success 200 {object} response.Response "获取成功"
// @Router /wechat/mp/statistics/upstream-message [get]
func (m *MpStatisticsApi) GetUpstreamMessage(c *gin.Context) {
accountId := c.Query("accountId")
date := c.Query("date")
// TODO: 实现消息发送概况数据获取逻辑
global.GVA_LOG.Info("获取消息发送概况数据", zap.String("accountId", accountId), zap.String("date", date))
// 模拟数据
data := []map[string]interface{}{
{
"refDate": "2025-08-04",
"msgType": 1,
"msgUser": 45,
"msgCount": 128,
},
}
response.OkWithData(data, c)
}
// GetInterfaceSummary 获取接口调用概况数据
// @Tags MpStatistics
// @Summary 获取接口调用概况数据
// @Description 获取接口调用概况数据
// @Accept json
// @Produce json
// @Param accountId query int true "公众号账号ID"
// @Param date query string true "日期"
// @Success 200 {object} response.Response "获取成功"
// @Router /wechat/mp/statistics/interface-summary [get]
func (m *MpStatisticsApi) GetInterfaceSummary(c *gin.Context) {
accountId := c.Query("accountId")
date := c.Query("date")
// TODO: 实现接口调用概况数据获取逻辑
global.GVA_LOG.Info("获取接口调用概况数据", zap.String("accountId", accountId), zap.String("date", date))
// 模拟数据
data := []map[string]interface{}{
{
"refDate": "2025-08-04",
"callbackCount": 256,
"failCount": 2,
"totalTimeCost": 1280,
"maxTimeCost": 45,
},
}
response.OkWithData(data, c)
}
@@ -0,0 +1,187 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpTagApi struct{}
// CreateTag 创建公众号标签
// @Tags MpTag
// @Summary 创建公众号标签
// @Description 创建公众号标签
// @Accept json
// @Produce json
// @Param data body map[string]interface{} true "标签信息"
// @Success 200 {object} response.Response "创建成功"
// @Router /wechat/mp/tag/create [post]
func (m *MpTagApi) CreateTag(c *gin.Context) {
var req map[string]interface{}
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// TODO: 实现标签创建逻辑
global.GVA_LOG.Info("创建标签请求", zap.Any("data", req))
response.OkWithMessage("标签创建功能待实现", c)
}
// UpdateTag 更新公众号标签
// @Tags MpTag
// @Summary 更新公众号标签
// @Description 更新公众号标签
// @Accept json
// @Produce json
// @Param data body map[string]interface{} true "标签信息"
// @Success 200 {object} response.Response "更新成功"
// @Router /wechat/mp/tag/update [put]
func (m *MpTagApi) UpdateTag(c *gin.Context) {
var req map[string]interface{}
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
// TODO: 实现标签更新逻辑
global.GVA_LOG.Info("更新标签请求", zap.Any("data", req))
response.OkWithMessage("标签更新功能待实现", c)
}
// DeleteTag 删除公众号标签
// @Tags MpTag
// @Summary 删除公众号标签
// @Description 删除公众号标签
// @Accept json
// @Produce json
// @Param id query int true "标签ID"
// @Success 200 {object} response.Response "删除成功"
// @Router /wechat/mp/tag/delete [delete]
func (m *MpTagApi) DeleteTag(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的标签ID", c)
return
}
// TODO: 实现标签删除逻辑
global.GVA_LOG.Info("删除标签请求", zap.Uint64("id", id))
response.OkWithMessage("标签删除功能待实现", c)
}
// GetTag 获取公众号标签详情
// @Tags MpTag
// @Summary 获取公众号标签详情
// @Description 获取公众号标签详情
// @Accept json
// @Produce json
// @Param id query int true "标签ID"
// @Success 200 {object} response.Response "获取成功"
// @Router /wechat/mp/tag/get [get]
func (m *MpTagApi) GetTag(c *gin.Context) {
idStr := c.Query("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的标签ID", c)
return
}
// TODO: 实现获取标签详情逻辑
global.GVA_LOG.Info("获取标签详情请求", zap.Uint64("id", id))
response.OkWithMessage("获取标签详情功能待实现", c)
}
// GetTagPage 获取公众号标签分页
// @Tags MpTag
// @Summary 获取公众号标签分页
// @Description 获取公众号标签分页
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mp/tag/page [get]
func (m *MpTagApi) GetTagPage(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
// 模拟数据
tags := []map[string]interface{}{
{
"id": 1,
"name": "VIP用户",
"count": 150,
},
{
"id": 2,
"name": "普通用户",
"count": 1200,
},
}
response.OkWithDetailed(wechatResponse.PageResult{
List: tags,
Total: int64(len(tags)),
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// GetSimpleTags 获取公众号标签精简信息列表
// @Tags MpTag
// @Summary 获取公众号标签精简信息列表
// @Description 获取公众号标签精简信息列表
// @Accept json
// @Produce json
// @Success 200 {object} response.Response "获取成功"
// @Router /wechat/mp/tag/list-all-simple [get]
func (m *MpTagApi) GetSimpleTags(c *gin.Context) {
// TODO: 实现获取简单标签列表逻辑
global.GVA_LOG.Info("获取简单标签列表请求")
// 模拟数据
tags := []map[string]interface{}{
{"id": 1, "name": "VIP用户"},
{"id": 2, "name": "普通用户"},
}
response.OkWithData(tags, c)
}
// SyncTag 同步公众号标签
// @Tags MpTag
// @Summary 同步公众号标签
// @Description 同步公众号标签
// @Accept json
// @Produce json
// @Param accountId query int true "公众号账号ID"
// @Success 200 {object} response.Response "同步成功"
// @Router /wechat/mp/tag/sync [post]
func (m *MpTagApi) SyncTag(c *gin.Context) {
accountIdStr := c.Query("accountId")
accountId, err := strconv.ParseUint(accountIdStr, 10, 32)
if err != nil {
response.FailWithMessage("无效的账号ID", c)
return
}
// TODO: 实现标签同步逻辑
global.GVA_LOG.Info("同步标签请求", zap.Uint64("accountId", accountId))
response.OkWithMessage("标签同步功能待实现", c)
}
@@ -0,0 +1,151 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/request"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type MpUserApi struct{}
var mpUserService = service.ServiceGroupApp.MpUserService
// GetUserList 获取公众号用户列表
// @Tags MpUser
// @Summary 获取公众号用户列表
// @Description 获取微信公众号粉丝列表
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Param subscribeStatus query int false "关注状态"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mp/users [get]
func (m *MpUserApi) GetUserList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
var subscribeStatus *int
if status := c.Query("subscribeStatus"); status != "" {
if s, err := strconv.Atoi(status); err == nil {
subscribeStatus = &s
}
}
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
users, total, err := mpUserService.GetUserList(page, pageSize, subscribeStatus)
if err != nil {
global.GVA_LOG.Error("获取公众号用户列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(wechatResponse.PageResult{
List: users,
Total: total,
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// GetUserInfo 获取公众号用户信息
// @Tags MpUser
// @Summary 获取公众号用户信息
// @Description 根据openid获取公众号用户详细信息
// @Accept json
// @Produce json
// @Param openid query string true "用户openid"
// @Success 200 {object} response.Response{data=model.MpUser} "获取成功"
// @Router /wechat/mp/user [get]
func (m *MpUserApi) GetUserInfo(c *gin.Context) {
openid := c.Query("openid")
if openid == "" {
response.FailWithMessage("openid不能为空", c)
return
}
user, err := mpUserService.GetUserByOpenID(openid)
if err != nil {
global.GVA_LOG.Error("获取公众号用户信息失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(user, c)
}
// SyncUser 同步用户信息
// @Tags MpUser
// @Summary 同步用户信息
// @Description 从微信服务器同步用户信息
// @Accept json
// @Produce json
// @Param data body request.SyncUserRequest true "同步参数"
// @Success 200 {object} response.Response{data=model.MpUser} "同步成功"
// @Router /wechat/mp/user/sync [post]
func (m *MpUserApi) SyncUser(c *gin.Context) {
var req request.SyncUserRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.OpenID == "" {
response.FailWithMessage("openid不能为空", c)
return
}
user, err := mpUserService.SyncUser(req.OpenID)
if err != nil {
global.GVA_LOG.Error("同步用户信息失败!", zap.Error(err))
response.FailWithMessage("同步失败: "+err.Error(), c)
return
}
response.OkWithDetailed(user, "同步成功", c)
}
// UpdateUserRemark 更新用户备注
// @Tags MpUser
// @Summary 更新用户备注
// @Description 更新公众号用户备注
// @Accept json
// @Produce json
// @Param data body request.UpdateRemarkRequest true "更新参数"
// @Success 200 {object} response.Response "更新成功"
// @Router /wechat/mp/user/remark [put]
func (m *MpUserApi) UpdateUserRemark(c *gin.Context) {
var req request.UpdateRemarkRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.OpenID == "" {
response.FailWithMessage("openid不能为空", c)
return
}
err = mpUserService.UpdateUserRemark(req.OpenID, req.Remark)
if err != nil {
global.GVA_LOG.Error("更新用户备注失败!", zap.Error(err))
response.FailWithMessage("更新失败: "+err.Error(), c)
return
}
response.OkWithMessage("更新成功", c)
}
@@ -0,0 +1,212 @@
package api
import (
"strconv"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/request"
wechatResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model/response"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type WechatMiniApi struct{}
var wechatMiniService = service.ServiceGroupApp.WechatMiniService
// Login 小程序登录
// @Tags WechatMini
// @Summary 小程序登录
// @Description 微信小程序登录接口
// @Accept json
// @Produce json
// @Param data body request.MiniLoginRequest true "登录参数"
// @Success 200 {object} response.Response{data=response.MiniLoginResponse} "登录成功"
// @Router /wechat/mini/login [post]
func (w *WechatMiniApi) Login(c *gin.Context) {
var req request.MiniLoginRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.Code == "" {
response.FailWithMessage("code不能为空", c)
return
}
user, err := wechatMiniService.Code2Session(req.Code)
if err != nil {
global.GVA_LOG.Error("小程序登录失败!", zap.Error(err))
response.FailWithMessage("登录失败: "+err.Error(), c)
return
}
// 这里可以生成JWT token或其他认证信息
resp := wechatResponse.MiniLoginResponse{
OpenID: user.OpenID,
UnionID: user.UnionID,
UserID: user.UserID,
}
response.OkWithDetailed(resp, "登录成功", c)
}
// GetUserInfo 获取用户信息
// @Tags WechatMini
// @Summary 获取用户信息
// @Description 获取小程序用户信息
// @Accept json
// @Produce json
// @Param openid query string true "用户openid"
// @Success 200 {object} response.Response{data=model.WechatMiniUser} "获取成功"
// @Router /wechat/mini/userinfo [get]
func (w *WechatMiniApi) GetUserInfo(c *gin.Context) {
openid := c.Query("openid")
if openid == "" {
response.FailWithMessage("openid不能为空", c)
return
}
user, err := wechatMiniService.GetUserInfo(openid)
if err != nil {
global.GVA_LOG.Error("获取用户信息失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithData(user, c)
}
// UpdateUserInfo 更新用户信息
// @Tags WechatMini
// @Summary 更新用户信息
// @Description 更新小程序用户信息
// @Accept json
// @Produce json
// @Param data body request.UpdateUserInfoRequest true "用户信息"
// @Success 200 {object} response.Response "更新成功"
// @Router /wechat/mini/userinfo [put]
func (w *WechatMiniApi) UpdateUserInfo(c *gin.Context) {
var req request.UpdateUserInfoRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.OpenID == "" {
response.FailWithMessage("openid不能为空", c)
return
}
err = wechatMiniService.UpdateUserInfo(req.OpenID, req.UserInfo)
if err != nil {
global.GVA_LOG.Error("更新用户信息失败!", zap.Error(err))
response.FailWithMessage("更新失败: "+err.Error(), c)
return
}
response.OkWithMessage("更新成功", c)
}
// BindPhone 绑定手机号
// @Tags WechatMini
// @Summary 绑定手机号
// @Description 绑定手机号并关联系统用户
// @Accept json
// @Produce json
// @Param data body request.BindPhoneRequest true "绑定参数"
// @Success 200 {object} response.Response{data=model.WechatMiniUser} "绑定成功"
// @Router /wechat/mini/bind-phone [post]
func (w *WechatMiniApi) BindPhone(c *gin.Context) {
var req request.BindPhoneRequest
err := c.ShouldBindJSON(&req)
if err != nil {
response.FailWithMessage(err.Error(), c)
return
}
if req.OpenID == "" || req.Phone == "" {
response.FailWithMessage("openid和手机号不能为空", c)
return
}
user, err := wechatMiniService.BindPhoneAndLinkUser(req.OpenID, req.Phone)
if err != nil {
global.GVA_LOG.Error("绑定手机号失败!", zap.Error(err))
response.FailWithMessage("绑定失败: "+err.Error(), c)
return
}
response.OkWithDetailed(user, "绑定成功", c)
}
// GetUserList 获取用户列表
// @Tags WechatMini
// @Summary 获取用户列表
// @Description 获取小程序用户列表(管理员接口)
// @Accept json
// @Produce json
// @Param page query int false "页码"
// @Param pageSize query int false "每页数量"
// @Success 200 {object} response.Response{data=response.PageResult} "获取成功"
// @Router /wechat/mini/users [get]
func (w *WechatMiniApi) GetUserList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
if page <= 0 {
page = 1
}
if pageSize <= 0 || pageSize > 100 {
pageSize = 10
}
users, total, err := wechatMiniService.GetUserList(page, pageSize)
if err != nil {
global.GVA_LOG.Error("获取用户列表失败!", zap.Error(err))
response.FailWithMessage("获取失败: "+err.Error(), c)
return
}
response.OkWithDetailed(wechatResponse.PageResult{
List: users,
Total: total,
Page: page,
PageSize: pageSize,
}, "获取成功", c)
}
// CheckUnionID 检查UnionID是否存在(为APP登录预留)
// @Tags WechatMini
// @Summary 检查UnionID
// @Description 检查UnionID是否已存在用户
// @Accept json
// @Produce json
// @Param unionid query string true "UnionID"
// @Success 200 {object} response.Response{data=model.WechatMiniUser} "检查成功"
// @Router /wechat/mini/check-unionid [get]
func (w *WechatMiniApi) CheckUnionID(c *gin.Context) {
unionid := c.Query("unionid")
if unionid == "" {
response.FailWithMessage("unionid不能为空", c)
return
}
user, err := wechatMiniService.CheckUnionIDExists(unionid)
if err != nil {
global.GVA_LOG.Error("检查UnionID失败!", zap.Error(err))
response.FailWithMessage("检查失败: "+err.Error(), c)
return
}
if user == nil {
response.OkWithDetailed(nil, "UnionID不存在", c)
} else {
response.OkWithDetailed(user, "UnionID已存在", c)
}
}
@@ -0,0 +1,89 @@
package api
import (
"crypto/sha1"
"fmt"
"io"
"sort"
"strings"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/service"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
type WechatWebhookApi struct{}
var webhookMessageService = service.ServiceGroupApp.MpMessageService
// OfficialAccountWebhook 公众号消息接收
// @Tags WechatWebhook
// @Summary 公众号消息接收
// @Description 接收微信公众号推送的消息和事件
// @Accept xml
// @Produce xml
// @Param signature query string true "微信加密签名"
// @Param timestamp query string true "时间戳"
// @Param nonce query string true "随机数"
// @Param echostr query string false "随机字符串"
// @Success 200 {string} string "success"
// @Router /wechat/official/webhook [get,post]
func (w *WechatWebhookApi) OfficialAccountWebhook(c *gin.Context) {
// 获取参数
signature := c.Query("signature")
timestamp := c.Query("timestamp")
nonce := c.Query("nonce")
echostr := c.Query("echostr")
// 验证签名
if !w.verifySignature(signature, timestamp, nonce) {
global.GVA_LOG.Error("微信签名验证失败")
c.String(403, "签名验证失败")
return
}
// GET请求用于验证服务器配置
if c.Request.Method == "GET" {
c.String(200, echostr)
return
}
// POST请求处理消息
body, err := io.ReadAll(c.Request.Body)
if err != nil {
global.GVA_LOG.Error("读取请求体失败", zap.Error(err))
c.String(400, "读取请求体失败")
return
}
// 简化消息处理 - 暂时只记录日志
global.GVA_LOG.Info("收到微信消息", zap.String("body", string(body)))
// TODO: 实现完整的消息解析和处理
// 由于微信SDK API变化,暂时简化处理
c.String(200, "success")
}
// verifySignature 验证微信签名
func (w *WechatWebhookApi) verifySignature(signature, timestamp, nonce string) bool {
token := global.GVA_CONFIG.Wechat.OfficialToken
if token == "" {
global.GVA_LOG.Error("微信公众号Token未配置")
return false
}
// 将token、timestamp、nonce三个参数进行字典序排序
strs := []string{token, timestamp, nonce}
sort.Strings(strs)
// 将三个参数字符串拼接成一个字符串进行sha1加密
str := strings.Join(strs, "")
h := sha1.New()
h.Write([]byte(str))
encrypted := fmt.Sprintf("%x", h.Sum(nil))
// 将加密后的字符串与signature对比
return encrypted == signature
}