重构小程序图标组件,替换传统 emoji 为 SVG 图标,提升视觉一致性和可维护性。更新多个页面以使用新图标组件,优化用户界面体验。同时,调整了数据加载逻辑,确保更高效的状态管理和用户交互。

This commit is contained in:
Alex-larget
2026-03-18 16:00:57 +08:00
parent 46f94a9c81
commit c55e54efbd
62 changed files with 2033 additions and 1270 deletions

View File

@@ -44,6 +44,9 @@ func KeyBookHot(limit int) string { return "soul:book:hot:" + fmt.Sprint(limit)
const KeyBookRecommended = "soul:book:recommended"
const KeyBookStats = "soul:book:stats"
const KeyConfigMiniprogram = "soul:config:miniprogram"
const KeyConfigAuditMode = "soul:config:audit-mode"
const KeyConfigCore = "soul:config:core"
const KeyConfigReadExtras = "soul:config:read-extras"
// Get 从 Redis 读取,未配置或失败返回 nil调用方回退 DB
func Get(ctx context.Context, key string, dest interface{}) bool {
@@ -156,9 +159,13 @@ func InvalidateBookCache() {
}
}
// InvalidateConfig 配置变更时调用,使小程序 config 缓存失效
// InvalidateConfig 配置变更时调用,使小程序 config 及拆分接口缓存失效
func InvalidateConfig() {
Del(context.Background(), KeyConfigMiniprogram)
ctx := context.Background()
Del(ctx, KeyConfigMiniprogram)
Del(ctx, KeyConfigAuditMode)
Del(ctx, KeyConfigCore)
Del(ctx, KeyConfigReadExtras)
}
// BookRelatedTTL 书籍相关接口 TTLhot/recommended/stats
@@ -167,6 +174,9 @@ const BookRelatedTTL = 5 * time.Minute
// ConfigTTL 配置接口 TTL
const ConfigTTL = 10 * time.Minute
// AuditModeTTL 审核模式 TTL管理端开关后需较快生效
const AuditModeTTL = 1 * time.Minute
// KeyChapterContent 章节正文缓存,格式 soul:chapter:content:{mid},存原始 HTML 字符串
func KeyChapterContent(mid int) string { return "soul:chapter:content:" + fmt.Sprint(mid) }

View File

@@ -223,6 +223,10 @@ func WarmBookPartsCache() {
}
// BookAllChapters GET /api/book/all-chapters 返回所有章节(列表,来自 chapters 表)
//
// Deprecated: 小程序已迁移至 book/parts + chapters-by-part + book/statsid↔mid 从各接口响应积累。
// 保留以兼容旧版/管理端,计划后续下线。
//
// 排序须与管理端 PUT /api/db/book action=reorder 一致:按 sort_order 升序,同序按 id
// 免费判断system_config.free_chapters / chapter_config.freeChapters 优先于 chapters.is_free
// 支持 excludeFixed=1排除序言、尾声、附录目录页固定模块不参与中间篇章
@@ -398,6 +402,29 @@ func BookChaptersByPart(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": list})
}
// getOrderedChapterList 获取按 sort_order+id 排序的章节列表(复用 all-chapters 缓存)
func getOrderedChapterList() []model.Chapter {
var list []model.Chapter
if cache.Get(context.Background(), cache.KeyAllChapters("default"), &list) && len(list) > 0 {
return list
}
allChaptersCache.mu.RLock()
if allChaptersCache.key == "default" && time.Now().Before(allChaptersCache.expires) && len(allChaptersCache.data) > 0 {
list = allChaptersCache.data
allChaptersCache.mu.RUnlock()
return list
}
allChaptersCache.mu.RUnlock()
db := database.DB()
if err := db.Model(&model.Chapter{}).Select(allChaptersSelectCols).
Order("COALESCE(sort_order, 999999) ASC, id ASC").
Find(&list).Error; err != nil || len(list) == 0 {
return nil
}
sortChaptersByNaturalID(list)
return list
}
// BookChapterByMID GET /api/book/chapter/by-mid/:mid 按自增主键 mid 查询(新链接推荐)
func BookChapterByMID(c *gin.Context) {
midStr := c.Param("mid")
@@ -618,6 +645,38 @@ func findChapterAndRespond(c *gin.Context, whereFn func(*gorm.DB) *gorm.DB) {
"sectionTitle": ch.SectionTitle,
"isFree": isFree,
}
// 文章详情内直接输出上一篇/下一篇,省去单独请求
if list := getOrderedChapterList(); len(list) > 0 {
idx := -1
for i, item := range list {
if item.ID == ch.ID {
idx = i
break
}
}
if idx >= 0 {
toItem := func(c *model.Chapter) gin.H {
if c == nil {
return nil
}
t := c.SectionTitle
if t == "" {
t = c.ChapterTitle
}
return gin.H{"id": c.ID, "mid": c.MID, "title": t}
}
if idx > 0 {
out["prev"] = toItem(&list[idx-1])
} else {
out["prev"] = nil
}
if idx < len(list)-1 {
out["next"] = toItem(&list[idx+1])
} else {
out["next"] = nil
}
}
}
if isFreeFromConfig {
out["price"] = float64(0)
} else if ch.Price != nil {

View File

@@ -26,19 +26,19 @@ func buildMiniprogramConfig() gin.H {
apiDomain = cfg.BaseURL
}
defaultMp := gin.H{
"appId": "wxb8bbb2b10dec74aa",
"apiDomain": apiDomain,
"buyerDiscount": 5,
"referralBindDays": 30,
"minWithdraw": 10,
"appId": "wxb8bbb2b10dec74aa",
"apiDomain": apiDomain,
"buyerDiscount": 5,
"referralBindDays": 30,
"minWithdraw": 10,
"withdrawSubscribeTmplId": "u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE",
"mchId": "1318592501",
"auditMode": false,
"supportWechat": true,
"mchId": "1318592501",
"auditMode": false,
"supportWechat": true,
}
out := gin.H{
"success": true,
"success": true,
"prices": defaultPrices,
"features": defaultFeatures,
"mpConfig": defaultMp,
@@ -143,6 +143,8 @@ func buildMiniprogramConfig() gin.H {
// GetPublicDBConfig GET /api/miniprogram/config 公开接口,供小程序获取完整配置(与 next-project 对齐)
// Redis 缓存 10min配置变更时失效
//
// Deprecated: 计划迁移至 /config/core + /config/audit-mode + /config/read-extras保留以兼容线上小程序
func GetPublicDBConfig(c *gin.Context) {
var cached map[string]interface{}
if cache.Get(context.Background(), cache.KeyConfigMiniprogram, &cached) && len(cached) > 0 {
@@ -154,10 +156,121 @@ func GetPublicDBConfig(c *gin.Context) {
c.JSON(http.StatusOK, out)
}
// WarmConfigCache 启动时预热 config 缓存,避免首请求冷启动
// GetAuditMode GET /api/miniprogram/config/audit-mode 审核模式独立接口,管理端开关后快速生效
func GetAuditMode(c *gin.Context) {
var cached gin.H
if cache.Get(context.Background(), cache.KeyConfigAuditMode, &cached) && len(cached) > 0 {
c.JSON(http.StatusOK, cached)
return
}
full := buildMiniprogramConfig()
auditMode := false
if mp, ok := full["mpConfig"].(gin.H); ok {
if v, ok := mp["auditMode"].(bool); ok && v {
auditMode = true
}
}
out := gin.H{"auditMode": auditMode}
cache.Set(context.Background(), cache.KeyConfigAuditMode, out, cache.AuditModeTTL)
c.JSON(http.StatusOK, out)
}
// GetCoreConfig GET /api/miniprogram/config/core 核心配置prices、features、userDiscount、mpConfig首屏/Tab 用
func GetCoreConfig(c *gin.Context) {
var cached gin.H
if cache.Get(context.Background(), cache.KeyConfigCore, &cached) && len(cached) > 0 {
c.JSON(http.StatusOK, cached)
return
}
full := buildMiniprogramConfig()
out := gin.H{
"success": true,
"prices": full["prices"],
"features": full["features"],
"userDiscount": full["userDiscount"],
"mpConfig": full["mpConfig"],
}
if out["prices"] == nil {
out["prices"] = gin.H{"section": float64(1), "fullbook": 9.9}
}
if out["features"] == nil {
out["features"] = gin.H{"matchEnabled": true, "referralEnabled": true, "searchEnabled": true}
}
if out["userDiscount"] == nil {
out["userDiscount"] = float64(5)
}
if out["mpConfig"] == nil {
out["mpConfig"] = gin.H{}
}
cache.Set(context.Background(), cache.KeyConfigCore, out, cache.ConfigTTL)
c.JSON(http.StatusOK, out)
}
// GetReadExtras GET /api/miniprogram/config/read-extras 阅读页扩展linkTags、linkedMiniprograms懒加载
func GetReadExtras(c *gin.Context) {
var cached gin.H
if cache.Get(context.Background(), cache.KeyConfigReadExtras, &cached) && len(cached) > 0 {
c.JSON(http.StatusOK, cached)
return
}
full := buildMiniprogramConfig()
out := gin.H{
"linkTags": full["linkTags"],
"linkedMiniprograms": full["linkedMiniprograms"],
}
if out["linkTags"] == nil {
out["linkTags"] = []gin.H{}
}
if out["linkedMiniprograms"] == nil {
out["linkedMiniprograms"] = []gin.H{}
}
cache.Set(context.Background(), cache.KeyConfigReadExtras, out, cache.ConfigTTL)
c.JSON(http.StatusOK, out)
}
// WarmConfigCache 启动时预热 config 及拆分接口缓存,避免首请求冷启动
func WarmConfigCache() {
out := buildMiniprogramConfig()
cache.Set(context.Background(), cache.KeyConfigMiniprogram, out, cache.ConfigTTL)
// 拆分接口预热
auditMode := false
if mp, ok := out["mpConfig"].(gin.H); ok {
if v, ok := mp["auditMode"].(bool); ok && v {
auditMode = true
}
}
cache.Set(context.Background(), cache.KeyConfigAuditMode, gin.H{"auditMode": auditMode}, cache.AuditModeTTL)
core := gin.H{
"success": true,
"prices": out["prices"],
"features": out["features"],
"userDiscount": out["userDiscount"],
"mpConfig": out["mpConfig"],
}
if core["prices"] == nil {
core["prices"] = gin.H{"section": float64(1), "fullbook": 9.9}
}
if core["features"] == nil {
core["features"] = gin.H{"matchEnabled": true, "referralEnabled": true, "searchEnabled": true}
}
if core["userDiscount"] == nil {
core["userDiscount"] = float64(5)
}
if core["mpConfig"] == nil {
core["mpConfig"] = gin.H{}
}
cache.Set(context.Background(), cache.KeyConfigCore, core, cache.ConfigTTL)
readExtras := gin.H{
"linkTags": out["linkTags"],
"linkedMiniprograms": out["linkedMiniprograms"],
}
if readExtras["linkTags"] == nil {
readExtras["linkTags"] = []gin.H{}
}
if readExtras["linkedMiniprograms"] == nil {
readExtras["linkedMiniprograms"] = []gin.H{}
}
cache.Set(context.Background(), cache.KeyConfigReadExtras, readExtras, cache.ConfigTTL)
}
// DBConfigGet GET /api/db/config管理端鉴权后同路径由 db 组处理时用)
@@ -196,13 +309,13 @@ func AdminSettingsGet(c *gin.Context) {
apiDomain = cfg.BaseURL
}
defaultMp := gin.H{
"appId": "wxb8bbb2b10dec74aa",
"apiDomain": apiDomain,
"appId": "wxb8bbb2b10dec74aa",
"apiDomain": apiDomain,
"withdrawSubscribeTmplId": "u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE",
"mchId": "1318592501",
"minWithdraw": float64(10),
"auditMode": false,
"supportWechat": true,
"mchId": "1318592501",
"minWithdraw": float64(10),
"auditMode": false,
"supportWechat": true,
}
out := gin.H{
"success": true,
@@ -313,12 +426,12 @@ func AdminReferralSettingsGet(c *gin.Context) {
db := database.DB()
defaultConfig := gin.H{
"distributorShare": float64(90),
"minWithdrawAmount": float64(10),
"bindingDays": float64(30),
"userDiscount": float64(5),
"withdrawFee": float64(5),
"enableAutoWithdraw": false,
"vipOrderShareVip": float64(20),
"minWithdrawAmount": float64(10),
"bindingDays": float64(30),
"userDiscount": float64(5),
"withdrawFee": float64(5),
"enableAutoWithdraw": false,
"vipOrderShareVip": float64(20),
"vipOrderShareNonVip": float64(10),
}
var row model.SystemConfig
@@ -361,11 +474,11 @@ func AdminReferralSettingsPost(c *gin.Context) {
val := gin.H{
"distributorShare": body.DistributorShare,
"minWithdrawAmount": body.MinWithdrawAmount,
"bindingDays": body.BindingDays,
"userDiscount": body.UserDiscount,
"withdrawFee": body.WithdrawFee,
"enableAutoWithdraw": body.EnableAutoWithdraw,
"vipOrderShareVip": vipOrderShareVip,
"bindingDays": body.BindingDays,
"userDiscount": body.UserDiscount,
"withdrawFee": body.WithdrawFee,
"enableAutoWithdraw": body.EnableAutoWithdraw,
"vipOrderShareVip": vipOrderShareVip,
"vipOrderShareNonVip": vipOrderShareNonVip,
}
valBytes, err := json.Marshal(val)
@@ -480,12 +593,12 @@ func AdminAuthorSettingsPost(c *gin.Context) {
err := db.First(&row).Error
if err != nil {
row = model.AuthorConfig{
Name: name,
Avatar: avatar,
AvatarImg: str("avatarImg"),
Title: str("title"),
Bio: str("bio"),
Stats: string(statsBytes),
Name: name,
Avatar: avatar,
AvatarImg: str("avatarImg"),
Title: str("title"),
Bio: str("bio"),
Stats: string(statsBytes),
Highlights: string(highlightsBytes),
}
err = db.Create(&row).Error
@@ -571,7 +684,7 @@ func DBUsersList(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
search := strings.TrimSpace(c.DefaultQuery("search", ""))
vipFilter := c.Query("vip") // "true" 时仅返回 VIPhasFullBook
vipFilter := c.Query("vip") // "true" 时仅返回 VIPhasFullBook
poolFilter := c.Query("pool") // "complete" 时仅返回已完善资料的用户
if page < 1 {
page = 1
@@ -744,21 +857,21 @@ func DBUsersList(c *gin.Context) {
})
}
func ptrBool(b bool) *bool { return &b }
func ptrBool(b bool) *bool { return &b }
func ptrFloat64(f float64) *float64 { v := f; return &v }
func ptrInt(n int) *int { return &n }
func ptrInt(n int) *int { return &n }
// DBUsersAction POST /api/db/users创建、PUT /api/db/users更新
func DBUsersAction(c *gin.Context) {
db := database.DB()
if c.Request.Method == http.MethodPost {
var body struct {
OpenID *string `json:"openId"`
Phone *string `json:"phone"`
Nickname *string `json:"nickname"`
WechatID *string `json:"wechatId"`
Avatar *string `json:"avatar"`
IsAdmin *bool `json:"isAdmin"`
OpenID *string `json:"openId"`
Phone *string `json:"phone"`
Nickname *string `json:"nickname"`
WechatID *string `json:"wechatId"`
Avatar *string `json:"avatar"`
IsAdmin *bool `json:"isAdmin"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请求体无效"})
@@ -788,25 +901,25 @@ func DBUsersAction(c *gin.Context) {
}
// PUT 更新(含 VIP 手动设置is_vip、vip_expire_date、vip_name、vip_avatar、vip_project、vip_contact、vip_biotags 存 ckb_tags
var body struct {
ID string `json:"id"`
Nickname *string `json:"nickname"`
Phone *string `json:"phone"`
WechatID *string `json:"wechatId"`
Avatar *string `json:"avatar"`
Tags *string `json:"tags"` // JSON 数组字符串,如 ["创业者","电商"],存 ckb_tags
HasFullBook *bool `json:"hasFullBook"`
IsAdmin *bool `json:"isAdmin"`
Earnings *float64 `json:"earnings"`
PendingEarnings *float64 `json:"pendingEarnings"`
IsVip *bool `json:"isVip"`
VipExpireDate *string `json:"vipExpireDate"` // "2026-12-31" 或 "2026-12-31 23:59:59"
VipSort *int `json:"vipSort"` // 手动排序,越小越前
VipRole *string `json:"vipRole"` // 角色:从 vip_roles 选或手动填写
VipName *string `json:"vipName"`
VipAvatar *string `json:"vipAvatar"`
VipProject *string `json:"vipProject"`
VipContact *string `json:"vipContact"`
VipBio *string `json:"vipBio"`
ID string `json:"id"`
Nickname *string `json:"nickname"`
Phone *string `json:"phone"`
WechatID *string `json:"wechatId"`
Avatar *string `json:"avatar"`
Tags *string `json:"tags"` // JSON 数组字符串,如 ["创业者","电商"],存 ckb_tags
HasFullBook *bool `json:"hasFullBook"`
IsAdmin *bool `json:"isAdmin"`
Earnings *float64 `json:"earnings"`
PendingEarnings *float64 `json:"pendingEarnings"`
IsVip *bool `json:"isVip"`
VipExpireDate *string `json:"vipExpireDate"` // "2026-12-31" 或 "2026-12-31 23:59:59"
VipSort *int `json:"vipSort"` // 手动排序,越小越前
VipRole *string `json:"vipRole"` // 角色:从 vip_roles 选或手动填写
VipName *string `json:"vipName"`
VipAvatar *string `json:"vipAvatar"`
VipProject *string `json:"vipProject"`
VipContact *string `json:"vipContact"`
VipBio *string `json:"vipBio"`
}
if err := c.ShouldBindJSON(&body); err != nil || body.ID == "" {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "用户ID不能为空"})
@@ -1001,9 +1114,9 @@ func DBUsersReferrals(c *gin.Context) {
displayStatus := bindingStatusDisplay(hasPaid, hasFullBook) // vip | paid | free供前端徽章展示
referrals = append(referrals, gin.H{
"id": b.RefereeID, "nickname": nick, "avatar": avatar, "phone": phone,
"hasFullBook": hasFullBook || status == "converted",
"hasFullBook": hasFullBook || status == "converted",
"purchasedSections": getBindingPurchaseCount(b),
"createdAt": b.BindingDate, "bindingStatus": status, "daysRemaining": daysRemaining, "commission": b.TotalCommission,
"createdAt": b.BindingDate, "bindingStatus": status, "daysRemaining": daysRemaining, "commission": b.TotalCommission,
"status": displayStatus,
})
}
@@ -1110,7 +1223,7 @@ func DBDistribution(c *gin.Context) {
if statusFilter != "" && statusFilter != "all" {
query = query.Where("status = ?", statusFilter)
}
if err := query.Offset((page-1)*pageSize).Limit(pageSize).Find(&bindings).Error; err != nil {
if err := query.Offset((page - 1) * pageSize).Limit(pageSize).Find(&bindings).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": true, "bindings": []interface{}{}, "total": 0, "page": page, "pageSize": pageSize, "totalPages": 0})
return
}

View File

@@ -111,6 +111,7 @@ func Setup(cfg *config.Config) *gin.Engine {
api.POST("/auth/reset-password", handler.AuthResetPassword)
// ----- 书籍/章节(只读,写操作由 /api/db/book 管理端路由承担) -----
// Deprecated: 小程序已迁移至 book/parts + chapters-by-part保留以兼容 next-project/管理端
api.GET("/book/all-chapters", handler.BookAllChapters)
api.GET("/book/parts", handler.BookParts)
api.GET("/book/chapters-by-part", handler.BookChaptersByPart)
@@ -134,7 +135,7 @@ func Setup(cfg *config.Config) *gin.Engine {
// ----- 配置 -----
api.GET("/config", handler.GetConfig)
// 小程序用GET /api/db/config 返回 freeChapters、prices不鉴权先于 db 组匹配)
// Deprecated: 小程序已迁移至 /miniprogram/config/core + audit-mode + read-extras保留以兼容 next-project
api.GET("/db/config", handler.GetPublicDBConfig)
// ----- 内容 -----
@@ -277,6 +278,11 @@ func Setup(cfg *config.Config) *gin.Engine {
// ----- 小程序组(所有小程序端接口统一在 /api/miniprogram 下) -----
miniprogram := api.Group("/miniprogram")
{
// config 拆分接口(优先匹配,路径更具体)
miniprogram.GET("/config/audit-mode", handler.GetAuditMode)
miniprogram.GET("/config/core", handler.GetCoreConfig)
miniprogram.GET("/config/read-extras", handler.GetReadExtras)
// Deprecated: 保留以兼容线上,计划迁移至上述拆分接口
miniprogram.GET("/config", handler.GetPublicDBConfig)
miniprogram.POST("/login", handler.MiniprogramLogin)
miniprogram.POST("/phone-login", handler.WechatPhoneLogin)
@@ -287,11 +293,12 @@ func Setup(cfg *config.Config) *gin.Engine {
miniprogram.POST("/pay/notify", handler.MiniprogramPayNotify) // 微信支付回调URL 需在商户平台配置
miniprogram.POST("/qrcode", handler.MiniprogramQrcode)
miniprogram.GET("/qrcode/image", handler.MiniprogramQrcodeImage)
// Deprecated: 小程序已迁移至 book/parts + chapters-by-part
miniprogram.GET("/book/all-chapters", handler.BookAllChapters)
miniprogram.GET("/book/parts", handler.BookParts)
miniprogram.GET("/book/chapters-by-part", handler.BookChaptersByPart)
miniprogram.GET("/book/chapter/:id", handler.BookChapterByID)
miniprogram.GET("/book/chapter/by-mid/:mid", handler.BookChapterByMID)
miniprogram.GET("/book/chapter/by-id/:id", handler.BookChapterByID)
miniprogram.GET("/book/hot", handler.BookHot)
miniprogram.GET("/book/recommended", handler.BookRecommended)
miniprogram.GET("/book/latest-chapters", handler.BookLatestChapters)

View File

@@ -0,0 +1,5 @@
-- 修复篇章标题:将 slug 形式的 part_title 更新为展示标题数据来源DB
-- 执行node .cursor/scripts/db-exec/run.js -f soul-api/scripts/fix-part-titles.sql
-- part-2026-daily 的标题应为「2026每日派对干货」
UPDATE chapters SET part_title = '2026每日派对干货' WHERE part_id = 'part-2026-daily' AND (part_title = 'part-2026-daily' OR part_title = '' OR part_title IS NULL);

View File

@@ -37453,3 +37453,7 @@
{"level":"debug","timestamp":"2026-03-18T11:51:58+08:00","caller":"kernel/baseClient.go:459","content":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 249\r\nCache-Control: no-cache, must-revalidate\r\nConnection: keep-alive\r\nContent-Language: zh-CN\r\nContent-Type: application/json; charset=utf-8\r\nDate: Wed, 18 Mar 2026 03:51:59 GMT\r\nKeep-Alive: timeout=8\r\nRequest-Id: 08DFC2E8CD06100618C39F85AB0120AEEC1C289B9804-0\r\nServer: nginx\r\nWechatpay-Nonce: 17660221f926159ccf7b6ed6f4b81a7d\r\nWechatpay-Serial: 5F2543BF58239A4EB68FA4433DF1438A88B34B16\r\nWechatpay-Signature: NoR8AwrKE2Yh6P+h0VswlALQ0Ey82PxztzQUi2egtp05gVARPBe3pPaIQBHEKAaMWJhUiOlAe8Xp5RT3Pl9a04JYlwfsGQ1hmII+DAVvqvIWJp2OISAyiwqkve6+aTlHJ4YQh8a7h8loxGSGIOMGsnCMYH3vqGSVN8Yz16munthcCspD1Alft06aD2Tyo1SIa3WV0B6SF+po8qBR8OQcy31+ubHMVKbN+Sxf2AXLvKfy1VafALUT/IDb7w7Qu4SbzAHJIubokOhrU+a8cc4kIzPsntwPN+66Wb5jybTVQrxvG7MCpffrshskCMiGYJPsDQsDwLvg1UL8yEYuxDSeUw==\r\nWechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048\r\nWechatpay-Timestamp: 1773805919\r\nX-Content-Type-Options: nosniff\r\n\r\n{\"amount\":{\"payer_currency\":\"CNY\",\"total\":100},\"appid\":\"wxb8bbb2b10dec74aa\",\"mchid\":\"1318592501\",\"out_trade_no\":\"MP20260318114741238800\",\"promotion_detail\":[],\"scene_info\":{\"device_id\":\"\"},\"trade_state\":\"NOTPAY\",\"trade_state_desc\":\"订单未支付\"}"}
{"level":"debug","timestamp":"2026-03-18T11:56:58+08:00","caller":"kernel/baseClient.go:457","content":"GET https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/MP20260318114741238800?mchid=1318592501 request header: { Content-Type:application/jsonAuthorization:WECHATPAY2-SHA256-RSA2048 mchid=\"1318592501\",nonce_str=\"kQb3R3YEPZwaIVyvHXnzmWhMxi7REUev\",timestamp=\"1773806218\",serial_no=\"4A1DB62CD5C9BE0B6FC51C30621D6F99686E75C5\",signature=\"jRP0P/vCLRRWJNZK78glwhI6mX2AabFim0k0BHfZek7g/YWel0VojRqUeAsudxMCyk5Npd51en9O7KkVC9dZ1zS+zn93MnqEUozR7mKje4FTD6cidJwuGHqaij7W2aYPTqBPGKURsAGpJOrspHvxCAlAofKXM4PfjGPaYtQaUqrq5cdolkSP6/t3ZgwWEUafDZ7kiMb+34wv6hLNCM3WfygXcED0A8n1yPN0cUKIEdDlLjhDPDQD+qlYglUCjx7NIq7w4CwFW9pDxXJRyNM7DNgjVgYNLrgRz1mo4oiYJJCkO+BKG7vmixc2ttVnzsq2VXE+yTu0qXT0m2iOA8j3VQ==\"Accept:*/*} request body:"}
{"level":"debug","timestamp":"2026-03-18T11:56:58+08:00","caller":"kernel/baseClient.go:459","content":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 249\r\nCache-Control: no-cache, must-revalidate\r\nConnection: keep-alive\r\nContent-Language: zh-CN\r\nContent-Type: application/json; charset=utf-8\r\nDate: Wed, 18 Mar 2026 03:56:59 GMT\r\nKeep-Alive: timeout=8\r\nRequest-Id: 088BC5E8CD06100F18DDB1C05520BAF3212883E701-0\r\nServer: nginx\r\nWechatpay-Nonce: 9dcbc99bd1add75dab4b137a5044c426\r\nWechatpay-Serial: 5F2543BF58239A4EB68FA4433DF1438A88B34B16\r\nWechatpay-Signature: EpRCntgRS4NDqc/nc1PzCXhdJtzh8102uDzjNdT7xHfQJ0spKF9BPg6a2/M0iWMKL57lYWC2s8v+Vo73WxIL+kKeHwziaAt7IlDj7D4BqRvWN5AbE2JG/+kq4DeI5mWj0wDJdq1jNsenvRvrsZ3rPRUXJYwg+wbGsRj2sZ5KSEqdS28ImXJGJvRdKSdNAKiorrag8v7Jzd/h/ESJ7sWslWa9/y2GNTG9c1/WTs8374FoyoTNTd+bE/+dlRjeiNVwVCyxjr2g/4A0RZlqtxAZcBpvb3/d70ZyO+vrSN9Wc2ttMjGycXX3D7XRd1ZVgpA89cF/j4WWTaX5Jwvj3kI0Ww==\r\nWechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048\r\nWechatpay-Timestamp: 1773806219\r\nX-Content-Type-Options: nosniff\r\n\r\n{\"amount\":{\"payer_currency\":\"CNY\",\"total\":100},\"appid\":\"wxb8bbb2b10dec74aa\",\"mchid\":\"1318592501\",\"out_trade_no\":\"MP20260318114741238800\",\"promotion_detail\":[],\"scene_info\":{\"device_id\":\"\"},\"trade_state\":\"NOTPAY\",\"trade_state_desc\":\"订单未支付\"}"}
{"level":"debug","timestamp":"2026-03-18T15:43:09+08:00","caller":"kernel/accessToken.go:381","content":"GET https://api.weixin.qq.com/cgi-bin/token?appid=wxb8bbb2b10dec74aa&grant_type=client_credential&neededText=&secret=3c1fb1f63e6e052222bbcead9d07fe0c request header: { Accept:*/*} "}
{"level":"debug","timestamp":"2026-03-18T15:43:09+08:00","caller":"kernel/accessToken.go:383","content":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 174\r\nConnection: keep-alive\r\nContent-Type: application/json; encoding=utf-8\r\nDate: Wed, 18 Mar 2026 07:43:09 GMT\r\n\r\n{\"access_token\":\"102_W92JqCHafxmMPmqerkx_H3KLFbch7Gn0EQESDosCLzzg3wIoVw5D6dwdO_n8t8-rC1JXBEB9niZwtll6b0CENMM8vPydT8k7-gk0pg15AMl_vTZPbq_ju0ELnJsSRLcADAGFZ\",\"expires_in\":7200}"}
{"level":"debug","timestamp":"2026-03-18T15:43:09+08:00","caller":"kernel/baseClient.go:457","content":"GET https://api.weixin.qq.com/sns/jscode2session?access_token=102_W92JqCHafxmMPmqerkx_H3KLFbch7Gn0EQESDosCLzzg3wIoVw5D6dwdO_n8t8-rC1JXBEB9niZwtll6b0CENMM8vPydT8k7-gk0pg15AMl_vTZPbq_ju0ELnJsSRLcADAGFZ&appid=wxb8bbb2b10dec74aa&grant_type=authorization_code&js_code=0d1RnJkl2eGlnh46F3ml2JJFVe0RnJks&secret=3c1fb1f63e6e052222bbcead9d07fe0c request header: { Accept:*/*} "}
{"level":"debug","timestamp":"2026-03-18T15:43:09+08:00","caller":"kernel/baseClient.go:459","content":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 82\r\nConnection: keep-alive\r\nContent-Type: text/plain\r\nDate: Wed, 18 Mar 2026 07:43:10 GMT\r\n\r\n{\"session_key\":\"mur32y6Vm+aQjbMsDdhZew==\",\"openid\":\"ogpTW5a9exdEmEwqZsYywvgSpSQg\"}"}