3 Commits

Author SHA1 Message Date
fa554c3930 feat: 注册 C 端微信 JSSDK 配置接口路由及文档生成
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m35s
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-16 18:38:43 +08:00
1125701329 feat: 新增 C 端微信 JSSDK 签名配置接口 Handler
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-16 18:38:20 +08:00
ed1facc1b8 feat: 公众号服务新增 GetJSSDKConfig 方法及 JSSDK 配置 DTO
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-04-16 18:37:59 +08:00
8 changed files with 147 additions and 0 deletions

View File

@@ -132,5 +132,6 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
WechatConfig: admin.NewWechatConfigHandler(svc.WechatConfig),
AgentRecharge: admin.NewAgentRechargeHandler(svc.AgentRecharge, validate),
Refund: admin.NewRefundHandler(svc.Refund),
ClientWechat: app.NewClientWechatHandler(svc.WechatConfig, deps.Redis, deps.Logger),
}
}

View File

@@ -61,6 +61,7 @@ type Handlers struct {
WechatConfig *admin.WechatConfigHandler
AgentRecharge *admin.AgentRechargeHandler
Refund *admin.RefundHandler
ClientWechat *app.ClientWechatHandler
}
// Middlewares 封装所有中间件

View File

@@ -0,0 +1,78 @@
package app
import (
"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
wechatConfigSvc "github.com/break/junhong_cmp_fiber/internal/service/wechat_config"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/logger"
"github.com/break/junhong_cmp_fiber/pkg/response"
"github.com/break/junhong_cmp_fiber/pkg/wechat"
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
)
var clientWechatValidator = validator.New()
// ClientWechatHandler C 端微信相关处理器
type ClientWechatHandler struct {
wechatConfigService *wechatConfigSvc.Service
wechatCache kernel.CacheInterface
logger *zap.Logger
}
// NewClientWechatHandler 创建 C 端微信处理器
func NewClientWechatHandler(
wechatConfigService *wechatConfigSvc.Service,
rdb *redis.Client,
log *zap.Logger,
) *ClientWechatHandler {
var cache kernel.CacheInterface
if rdb != nil {
cache = wechat.NewRedisCache(rdb)
}
return &ClientWechatHandler{
wechatConfigService: wechatConfigService,
wechatCache: cache,
logger: log,
}
}
// GetJSSDKConfig 获取微信 JSSDK 签名配置
// GET /api/c/v1/wechat/jssdk-config
func (h *ClientWechatHandler) GetJSSDKConfig(c *fiber.Ctx) error {
var req dto.JSSDKConfigRequest
if err := c.QueryParser(&req); err != nil {
return errors.New(errors.CodeInvalidParam)
}
if err := clientWechatValidator.Struct(&req); err != nil {
logger.GetAppLogger().Warn("获取 JSSDK 配置参数校验失败", zap.Error(err))
return errors.New(errors.CodeInvalidParam)
}
wechatConfig, err := h.wechatConfigService.GetActiveConfig(c.UserContext())
if err != nil {
return err
}
oaApp, err := wechat.NewOfficialAccountAppFromConfig(wechatConfig, h.wechatCache, h.logger)
if err != nil {
h.logger.Error("初始化微信公众号应用失败", zap.Error(err))
return errors.New(errors.CodeWechatConfigUnavailable)
}
oaService := wechat.NewOfficialAccountService(oaApp, h.logger)
jssdkConfig, err := oaService.GetJSSDKConfig(c.UserContext(), req.URL)
if err != nil {
return err
}
return response.Success(c, &dto.JSSDKConfigResponse{
AppID: jssdkConfig.AppID,
Timestamp: jssdkConfig.Timestamp,
NonceStr: jssdkConfig.NonceStr,
Signature: jssdkConfig.Signature,
})
}

View File

@@ -54,3 +54,17 @@ type WechatPayH5Params struct {
ID uint `path:"id" description:"订单ID" required:"true"`
WechatPayH5Request
}
// JSSDKConfigRequest 获取 JSSDK 签名配置请求
type JSSDKConfigRequest struct {
URL string `query:"url" validate:"required" required:"true" description:"当前页面完整 URL含协议和路径不含 # 及其后内容)"`
}
// JSSDKConfigResponse JSSDK 签名配置响应
// 前端使用这些字段调用 wx.config() 初始化微信 JS-SDK
type JSSDKConfigResponse struct {
AppID string `json:"app_id" description:"公众号 AppID"`
Timestamp int64 `json:"timestamp" description:"时间戳(秒)"`
NonceStr string `json:"nonce_str" description:"随机字符串"`
Signature string `json:"signature" description:"签名SHA1"`
}

View File

@@ -22,6 +22,15 @@ func RegisterPersonalCustomerRoutes(router fiber.Router, doc *openapi.Generator,
authBasePath := "/auth"
// === 公开路由(无需认证)===
Register(router, doc, basePath, "GET", "/wechat/jssdk-config", handlers.ClientWechat.GetJSSDKConfig, RouteSpec{
Summary: "获取微信 JSSDK 签名配置",
Description: "前端调用 wx.config() 初始化微信 JS-SDK 时所需的签名参数,需传入当前页面完整 URL",
Tags: []string{"个人客户 - 微信"},
Auth: false,
Input: &dto.JSSDKConfigRequest{},
Output: &dto.JSSDKConfigResponse{},
})
Register(router, doc, basePath, "POST", authBasePath+"/verify-asset", handlers.ClientAuth.VerifyAsset, RouteSpec{
Summary: "资产验证",
Tags: []string{"个人客户 - 认证"},

View File

@@ -62,5 +62,6 @@ func BuildDocHandlers() *bootstrap.Handlers {
WechatConfig: admin.NewWechatConfigHandler(nil),
AgentRecharge: admin.NewAgentRechargeHandler(nil, nil),
Refund: admin.NewRefundHandler(nil),
ClientWechat: app.NewClientWechatHandler(nil, nil, nil),
}
}

View File

@@ -8,6 +8,37 @@ import (
"go.uber.org/zap"
)
// GetJSSDKConfig 获取 JSSDK 签名配置
// url 为当前页面完整 URL含协议和路径不含 # 及其后内容)
func (s *OfficialAccountService) GetJSSDKConfig(ctx context.Context, url string) (*JSSDKConfig, error) {
if url == "" {
return nil, errors.New(errors.CodeInvalidParam, "页面 URL 不能为空")
}
signMap, err := s.app.JSSDK.ConfigSignature(ctx, url, "", 0)
if err != nil {
s.logger.Error("获取 JSSDK 签名失败", zap.String("url", url), zap.Error(err))
return nil, errors.Wrap(errors.CodeInternalError, err, "获取 JSSDK 签名失败")
}
appID, _ := (*signMap)["appId"].(string)
nonceStr, _ := (*signMap)["nonceStr"].(string)
timestamp, _ := (*signMap)["timestamp"].(int64)
signature, _ := (*signMap)["signature"].(string)
s.logger.Debug("获取 JSSDK 签名配置成功",
zap.String("app_id", appID),
zap.String("url", url),
)
return &JSSDKConfig{
AppID: appID,
Timestamp: timestamp,
NonceStr: nonceStr,
Signature: signature,
}, nil
}
// OfficialAccountService 微信公众号服务实现
type OfficialAccountService struct {
app *officialAccount.OfficialAccount

View File

@@ -10,11 +10,23 @@ type Service interface {
GetUserInfo(ctx context.Context, code string) (openID, unionID string, err error)
}
// JSSDKConfig JSSDK 签名配置
// 用于前端调用 wx.config() 初始化微信 JS-SDK
type JSSDKConfig struct {
AppID string // 公众号 AppID
Timestamp int64 // 时间戳(秒)
NonceStr string // 随机字符串
Signature string // 签名SHA1
}
// OfficialAccountServiceInterface 微信公众号服务接口
type OfficialAccountServiceInterface interface {
Service
GetUserInfoDetailed(ctx context.Context, code string) (*UserInfo, error)
GetUserInfoByToken(ctx context.Context, accessToken, openID string) (*UserInfo, error)
// GetJSSDKConfig 获取 JSSDK 签名配置,用于前端调用 wx.config() 初始化
// url 为当前页面完整 URL含协议和路径不含 # 及其后内容)
GetJSSDKConfig(ctx context.Context, url string) (*JSSDKConfig, error)
}
// PaymentServiceInterface 微信支付服务接口