新增匹配次数管理功能,优化用户匹配体验。通过服务端计算用户的匹配配额,更新用户状态以反映剩余匹配次数。同时,调整匹配页面逻辑,确保在匹配次数用尽时提示用户购买更多次数。更新相关API以支持匹配记录的存储与查询,提升系统稳定性。

This commit is contained in:
乘风
2026-02-11 16:53:17 +08:00
parent ecee1bb2bb
commit a1dcf599ee
18 changed files with 422 additions and 229 deletions

View File

@@ -32,6 +32,8 @@ App({
// 购买记录
purchasedSections: [],
hasFullBook: false,
matchCount: 0,
matchQuota: null,
// 已读章节(仅统计有权限打开过的章节,用于首页「已读/待读」)
readSectionIds: [],
@@ -482,7 +484,9 @@ App({
this.globalData.isLoggedIn = false
this.globalData.purchasedSections = []
this.globalData.hasFullBook = false
this.globalData.matchCount = 0
this.globalData.matchQuota = null
wx.removeStorageSync('userInfo')
wx.removeStorageSync('token')
},

View File

@@ -38,6 +38,7 @@ Page({
todayMatchCount: 0,
totalMatchesAllowed: FREE_MATCH_LIMIT,
matchesRemaining: FREE_MATCH_LIMIT,
showQuotaExhausted: false,
needPayToMatch: false,
// 匹配状态
@@ -88,8 +89,7 @@ Page({
})
this.loadMatchConfig()
this.loadStoredContact()
this.loadTodayMatchCount()
this.initUserStatus()
this.refreshMatchCountAndStatus()
},
onShow() {
@@ -102,7 +102,7 @@ Page({
}
}
this.loadStoredContact()
this.initUserStatus()
this.refreshMatchCountAndStatus()
},
// 加载匹配配置
@@ -147,49 +147,46 @@ Page({
})
},
// 加载今日匹配次数
loadTodayMatchCount() {
try {
const today = new Date().toISOString().split('T')[0]
const stored = wx.getStorageSync('match_count_data')
if (stored) {
const data = typeof stored === 'string' ? JSON.parse(stored) : stored
if (data.date === today) {
this.setData({ todayMatchCount: data.count })
// 从服务端刷新匹配配额并初始化用户状态(前后端双向校验,服务端为权威)
async refreshMatchCountAndStatus() {
if (app.globalData.isLoggedIn && app.globalData.userInfo?.id) {
try {
const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(app.globalData.userInfo.id)}`)
if (res.success && res.data) {
app.globalData.matchCount = res.data.matchCount ?? 0
app.globalData.matchQuota = res.data.matchQuota || null
}
} catch (e) {
console.log('[Match] 拉取 matchQuota 失败:', e)
}
} catch (e) {
console.error('加载匹配次数失败:', e)
}
this.initUserStatus()
},
// 保存今日匹配次数
saveTodayMatchCount(count) {
const today = new Date().toISOString().split('T')[0]
wx.setStorageSync('match_count_data', { date: today, count })
},
// 初始化用户状态
// 初始化用户状态matchQuota 服务端纯计算:订单+match_records
initUserStatus() {
const { isLoggedIn, hasFullBook, purchasedSections } = app.globalData
// 获取额外购买的匹配次数
const extraMatches = wx.getStorageSync('extra_match_count') || 0
// 总匹配次数 = 每日免费(3) + 额外购买次数
// 全书用户无限制
const totalMatchesAllowed = hasFullBook ? 999999 : FREE_MATCH_LIMIT + extraMatches
const matchesRemaining = hasFullBook ? 999999 : Math.max(0, totalMatchesAllowed - this.data.todayMatchCount)
const needPayToMatch = !hasFullBook && matchesRemaining <= 0
const quota = app.globalData.matchQuota
// 今日剩余次数、今日已用:来自服务端 matchQuota未登录无法计算不能显示已用完
const remainToday = quota?.remainToday ?? 0
const matchesUsedToday = quota?.matchesUsedToday ?? 0
const purchasedRemain = quota?.purchasedRemain ?? 0
const totalMatchesAllowed = hasFullBook ? 999999 : (quota ? remainToday + matchesUsedToday : FREE_MATCH_LIMIT)
// 仅登录且服务端返回配额时,才判断是否已用完;未登录时显示「开始匹配」
const needPayToMatch = isLoggedIn && !hasFullBook && (quota ? remainToday <= 0 : false)
const showQuotaExhausted = isLoggedIn && !hasFullBook && (quota ? remainToday <= 0 : false)
this.setData({
isLoggedIn,
hasFullBook,
hasPurchased: true, // 所有用户都可以使用匹配功能
hasPurchased: true,
todayMatchCount: matchesUsedToday,
totalMatchesAllowed,
matchesRemaining,
matchesRemaining: hasFullBook ? 999999 : (isLoggedIn && quota ? remainToday : (isLoggedIn ? 0 : FREE_MATCH_LIMIT)),
needPayToMatch,
extraMatches
showQuotaExhausted,
extraMatches: purchasedRemain
})
},
@@ -224,7 +221,7 @@ Page({
confirmText: '去购买',
success: (res) => {
if (res.confirm) {
wx.switchTab({ url: '/pages/catalog/catalog' })
wx.switchTab({ url: '/pages/chapters/chapters' })
}
}
})
@@ -257,12 +254,12 @@ Page({
return
}
// 创业合伙类型 - 真正的匹配功能
if (this.data.needPayToMatch) {
// 创业合伙类型 - 真正的匹配功能(仅当登录且服务端确认次数用尽时,弹出购买)
if (this.data.showQuotaExhausted) {
this.setData({ showUnlockModal: true })
return
}
this.startMatch()
},
@@ -314,6 +311,7 @@ Page({
// 开始匹配 - 只匹配数据库中的真实用户
async startMatch() {
this.loadStoredContact()
this.setData({
isMatching: true,
matchAttempts: 0,
@@ -325,20 +323,28 @@ Page({
this.setData({ matchAttempts: this.data.matchAttempts + 1 })
}, 1000)
// 从数据库获取真实用户匹配
// 从数据库获取真实用户匹配(后端会校验剩余次数)
let matchedUser = null
let quotaExceeded = false
try {
const ui = app.globalData.userInfo || {}
const phone = (wx.getStorageSync('user_phone') || ui.phone || this.data.phoneNumber || '').trim()
const wechatId = (wx.getStorageSync('user_wechat') || ui.wechat || ui.wechatId || this.data.wechatId || '').trim()
const res = await app.request('/api/miniprogram/match/users', {
method: 'POST',
data: {
matchType: this.data.selectedType,
userId: app.globalData.userInfo?.id || ''
userId: app.globalData.userInfo?.id || '',
phone,
wechatId
}
})
if (res.success && res.data) {
matchedUser = res.data
console.log('[Match] 从数据库匹配到用户:', matchedUser.nickname)
} else if (res.code === 'QUOTA_EXCEEDED') {
quotaExceeded = true
}
} catch (e) {
console.log('[Match] 数据库匹配失败:', e)
@@ -348,7 +354,22 @@ Page({
const delay = Math.random() * 2000 + 2000
setTimeout(() => {
clearInterval(timer)
// 次数用尽(后端校验)
if (quotaExceeded) {
this.setData({ isMatching: false })
wx.showModal({
title: '今日匹配次数已用完',
content: '请购买更多次数继续匹配',
confirmText: '去购买',
success: (r) => {
if (r.confirm) this.setData({ showUnlockModal: true })
}
})
this.refreshMatchCountAndStatus()
return
}
// 如果没有匹配到用户,提示用户
if (!matchedUser) {
this.setData({ isMatching: false })
@@ -360,23 +381,17 @@ Page({
})
return
}
// 增加今日匹配次数
const newCount = this.data.todayMatchCount + 1
const matchesRemaining = this.data.hasFullBook ? 999999 : Math.max(0, this.data.totalMatchesAllowed - newCount)
// 匹配成功:从服务端刷新配额(后端已写入 match_records
this.setData({
isMatching: false,
currentMatch: matchedUser,
todayMatchCount: newCount,
matchesRemaining,
needPayToMatch: !this.data.hasFullBook && matchesRemaining <= 0
needPayToMatch: false
})
this.saveTodayMatchCount(newCount)
this.refreshMatchCountAndStatus()
// 上报匹配行为到存客宝
this.reportMatch(matchedUser)
}, delay)
},
@@ -665,7 +680,8 @@ Page({
try {
const result = await app.login()
if (result) {
this.initUserStatus()
// 登录成功后必须拉取 matchQuota否则无法正确显示剩余次数
await this.refreshMatchCountAndStatus()
this.setData({ showLoginModal: false, agreeProtocol: false })
wx.showToast({ title: '登录成功', icon: 'success' })
} else {
@@ -777,7 +793,24 @@ Page({
this.setData({ showUnlockModal: false })
},
// 购买匹配次数
// 支付成功后立即查询订单状态并刷新(首轮 0 延迟,之后每 800ms 重试)
async pollOrderAndRefresh(orderSn) {
const maxAttempts = 12
const interval = 800
for (let i = 0; i < maxAttempts; i++) {
try {
const r = await app.request(`/api/miniprogram/pay?orderSn=${encodeURIComponent(orderSn)}`, { method: 'GET', silent: true })
if (r?.data?.status === 'paid') {
await this.refreshMatchCountAndStatus()
return
}
} catch (_) {}
if (i < maxAttempts - 1) await new Promise(r => setTimeout(r, interval))
}
await this.refreshMatchCountAndStatus()
},
// 购买匹配次数(与购买章节逻辑一致,写入订单)
async buyMatchCount() {
this.setData({ showUnlockModal: false })
@@ -793,16 +826,17 @@ Page({
return
}
const matchPrice = this.data.matchPrice || 1
// 邀请码:与章节支付一致,写入订单便于分销归属与对账
const referralCode = wx.getStorageSync('referral_code') || ''
// 调用支付接口购买匹配次数
// 调用支付接口购买匹配次数productType: match订单类型购买匹配次数
const res = await app.request('/api/miniprogram/pay', {
method: 'POST',
data: {
openId,
productType: 'match',
productId: 'match_1',
amount: 1,
amount: matchPrice,
description: '匹配次数x1',
userId: app.globalData.userInfo?.id || '',
referralCode: referralCode || undefined
@@ -810,6 +844,7 @@ Page({
})
if (res.success && res.data?.payParams) {
const orderSn = res.data.orderSn
// 调用微信支付
await new Promise((resolve, reject) => {
wx.requestPayment({
@@ -818,13 +853,9 @@ Page({
fail: reject
})
})
// 支付成功,增加匹配次数
const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1
wx.setStorageSync('extra_match_count', extraMatches)
wx.showToast({ title: '购买成功', icon: 'success' })
this.initUserStatus()
// 轮询订单状态,确认已支付后再刷新(不依赖 PayNotify 回调时机)
this.pollOrderAndRefresh(orderSn)
} else {
throw new Error(res.error || '创建订单失败')
}
@@ -832,14 +863,13 @@ Page({
if (e.errMsg && e.errMsg.includes('cancel')) {
wx.showToast({ title: '已取消', icon: 'none' })
} else {
// 测试模式
// 测试模式(无支付环境时本地模拟)
wx.showModal({
title: '支付服务暂不可用',
content: '是否使用测试模式购买?',
success: (res) => {
if (res.confirm) {
const extraMatches = (wx.getStorageSync('extra_match_count') || 0) + 1
wx.setStorageSync('extra_match_count', extraMatches)
app.globalData.matchCount = (app.globalData.matchCount ?? 0) + 1
wx.showToast({ title: '测试购买成功', icon: 'success' })
this.initUserStatus()
}

View File

@@ -15,8 +15,8 @@
<!-- 顶部留白,让内容往下 -->
<view style="height: 30rpx;"></view>
<!-- 匹配提示条 - 简化显示 -->
<view class="match-tip-bar" wx:if="{{matchesRemaining <= 0 && !hasFullBook}}">
<!-- 匹配提示条 - 仅登录且服务端确认次数用尽时显示 -->
<view class="match-tip-bar" wx:if="{{showQuotaExhausted}}">
<text class="tip-icon">⚡</text>
<text class="tip-text">今日免费次数已用完</text>
<view class="tip-btn" bindtap="showUnlockModal">购买次数</view>

View File

@@ -110,6 +110,27 @@ Page({
}
},
// 登录后刷新购买状态(与 match/read 一致,避免其他页面用旧数据)
async refreshPurchaseStatus() {
const userId = app.globalData.userInfo?.id
if (!userId) return
try {
const res = await app.request(`/api/miniprogram/user/purchase-status?userId=${encodeURIComponent(userId)}`)
if (res.success && res.data) {
app.globalData.hasFullBook = res.data.hasFullBook || false
app.globalData.purchasedSections = res.data.purchasedSections || []
app.globalData.matchCount = res.data.matchCount ?? 0
app.globalData.matchQuota = res.data.matchQuota || null
const userInfo = app.globalData.userInfo || {}
userInfo.hasFullBook = res.data.hasFullBook
userInfo.purchasedSections = res.data.purchasedSections
wx.setStorageSync('userInfo', userInfo)
}
} catch (e) {
console.log('[My] 刷新购买状态失败:', e)
}
},
// 初始化用户状态
initUserStatus() {
const { isLoggedIn, userInfo } = app.globalData
@@ -538,6 +559,7 @@ Page({
try {
const result = await app.login()
if (result) {
await this.refreshPurchaseStatus()
this.initUserStatus()
this.setData({ showLoginModal: false, agreeProtocol: false })
wx.showToast({ title: '登录成功', icon: 'success' })
@@ -566,6 +588,7 @@ Page({
try {
const result = await app.loginWithPhone(e.detail.code)
if (result) {
await this.refreshPurchaseStatus()
this.initUserStatus()
this.setData({ showLoginModal: false })
wx.showToast({ title: '登录成功', icon: 'success' })

View File

@@ -15,6 +15,10 @@ Page({
this.loadOrders()
},
onShow() {
this.loadOrders()
},
async loadOrders() {
this.setData({ loading: true })
try {

View File

@@ -880,7 +880,9 @@ Page({
// 更新全局购买状态
app.globalData.hasFullBook = res.data.hasFullBook
app.globalData.purchasedSections = res.data.purchasedSections || []
app.globalData.matchCount = res.data.matchCount ?? 0
app.globalData.matchQuota = res.data.matchQuota || null
// 更新用户信息中的购买记录
const userInfo = app.globalData.userInfo || {}
userInfo.hasFullBook = res.data.hasFullBook
@@ -890,7 +892,8 @@ Page({
console.log('[Pay] ✅ 购买状态已刷新:', {
hasFullBook: res.data.hasFullBook,
purchasedCount: res.data.purchasedSections.length
purchasedCount: res.data.purchasedSections.length,
matchCount: res.data.matchCount
})
}
} catch (e) {

View File

@@ -59,6 +59,7 @@ Page({
showPosterModal: false,
isGeneratingPoster: false,
posterQrSrc: '',
posterQrFilePath: '',
posterReferralLink: '',
posterNickname: '',
posterNicknameInitial: '',
@@ -283,53 +284,6 @@ Page({
})
},
// 分享到朋友圈 - 1:1 迁移 Next.js 的 handleShareToWechat
shareToWechat() {
const { referralCode } = this.data
const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
// 与 Next.js 完全相同的文案
const shareText = `📖 推荐一本好书《一场SOUL的创业实验场》
这是卡若每天早上6-9点在Soul派对房分享的真实商业故事55个真实案例讲透创业的底层逻辑。
👉 点击阅读: ${referralLink}
#创业 #商业思维 #Soul派对`
wx.setClipboardData({
data: shareText,
success: () => {
wx.showModal({
title: '朋友圈文案已复制!',
content: '打开微信 → 发朋友圈 → 粘贴即可',
showCancel: false,
confirmText: '知道了'
})
}
})
},
// 更多分享方式 - 1:1 迁移 Next.js 的 handleShare
handleMoreShare() {
const { referralCode } = this.data
const referralLink = `https://soul.quwanzhi.com/?ref=${referralCode}`
// 与 Next.js 完全相同的文案
const shareText = `我正在读《一场SOUL的创业实验场》每天6-9点的真实商业故事推荐给你${referralLink}`
wx.setClipboardData({
data: shareText,
success: () => {
wx.showToast({
title: '分享文案已复制',
icon: 'success',
duration: 2000
})
}
})
},
// 生成推广海报 - 1:1 对齐 Next.js 设计
async generatePoster() {
wx.showLoading({ title: '生成中...', mask: true })
@@ -352,15 +306,18 @@ Page({
},
})
if (!res || !res.success || !res.image) {
// 接口返回 { success, image: "data:image/png;base64,...", scene }
const imageData = res?.image || res?.data?.image
if (!res || !res.success || !imageData) {
console.error('[Poster] 生成小程序码失败:', res)
throw new Error(res?.error || '生成小程序码失败')
throw new Error(res?.error || res?.message || '生成小程序码失败')
}
// 后端返回的是 data:image/png;base64,... 需要先写入本地临时文件,再作为 <image> 的 src
const base64Data = String(res.image).replace(/^data:image\/\w+;base64,/, '')
// 小程序 image 组件支持 base64 格式,直接使用;同时写入本地供预览用
const base64Str = String(imageData).trim()
const fs = wx.getFileSystemManager()
const filePath = `${wx.env.USER_DATA_PATH}/poster_qrcode_${Date.now()}.png`
const base64Data = base64Str.replace(/^data:image\/\w+;base64,/, '')
await new Promise((resolve, reject) => {
fs.writeFile({
@@ -375,10 +332,10 @@ Page({
})
})
console.log('[Poster] 小程序码已保存到本地:', filePath)
// 优先用 base64 直接显示(兼容性更好);预览时用本地路径
this.setData({
posterQrSrc: filePath,
posterQrSrc: base64Str,
posterQrFilePath: filePath,
posterReferralLink: '', // 小程序版本不再使用 H5 链接
posterNickname: nickname,
posterNicknameInitial: (nickname || '用').charAt(0),
@@ -389,7 +346,7 @@ Page({
console.error('[Poster] 生成二维码失败:', e)
wx.hideLoading()
wx.showToast({ title: '生成失败', icon: 'none' })
this.setData({ showPosterModal: false, isGeneratingPoster: false, posterQrSrc: '', posterReferralLink: '' })
this.setData({ showPosterModal: false, isGeneratingPoster: false, posterQrSrc: '', posterQrFilePath: '', posterReferralLink: '' })
}
},
@@ -570,56 +527,15 @@ Page({
// 预览二维码
previewPosterQr() {
const { posterQrSrc } = this.data
if (!posterQrSrc) return
wx.previewImage({ urls: [posterQrSrc] })
const { posterQrSrc, posterQrFilePath } = this.data
const url = posterQrFilePath || posterQrSrc
if (!url) return
wx.previewImage({ urls: [url] })
},
// 阻止冒泡
stopPropagation() {},
// 分享到朋友圈 - 随机文案
shareToMoments() {
// 10条随机文案基于书的内容
const shareTexts = [
`🔥 在派对房里听到的真实故事比虚构的小说精彩100倍\n\n电动车出租月入5万、私域一年赚1000万、一个人的公司月入10万...\n\n62个真实案例搜"Soul创业派对"小程序看全部!\n\n#创业 #私域 #商业`,
`💡 今天终于明白:会赚钱的人,都在用"流量杠杆"\n\n抖音、Soul、飞书...同一套内容,撬动不同平台的流量。\n\n《Soul创业派对》里的实战方法受用终身\n\n#流量 #副业 #创业派对`,
`📚 一个70后大健康私域一个月150万流水是怎么做到的\n\n答案在《Soul创业派对》第9章全是干货。\n\n搜小程序"Soul创业派对",我在里面等你\n\n#大健康 #私域运营 #真实案例`,
`🎯 "分钱不是分你的钱,是分不属于对方的钱"\n\n这句话改变了我对商业合作的认知。\n\n推荐《Soul创业派对》创业者必读\n\n#云阿米巴 #商业思维 #创业`,
`✨ 资源整合高手的社交方法论,在派对房里学到了\n\n"先让对方赚到钱,自己才能长久赚钱"\n\n这本《Soul创业派对》每章都是实战经验\n\n#资源整合 #社交 #创业故事`,
`🚀 AI工具推广一个隐藏的高利润赛道\n\n客单价高、复购率高、需求旺盛...\n\n《Soul创业派对》里的商业机会你发现了吗\n\n#AI #副业 #商业机会`,
`💰 美业整合:一个人的公司如何月入十万?\n\n不开店、不囤货、轻资产运营...\n\n《Soul创业派对》告诉你答案\n\n#美业 #轻创业 #月入十万`,
`🌟 3000万流水是怎么跑出来的\n\n不是靠运气,是靠系统。\n\n《Soul创业派对》里的电商底层逻辑值得反复看\n\n#电商 #创业 #商业系统`,
`📖 "人与人之间的关系,归根结底就三个东西:利益、情感、价值观"\n\n在派对房里聊出的金句都在《Soul创业派对》里\n\n#人性 #商业 #创业派对`,
`🔔 未来职业的三个方向:技术型、资源型、服务型\n\n你属于哪一种?\n\n《Soul创业派对》帮你找到答案\n\n#职业规划 #创业 #未来`
]
// 随机选择一条文案
const randomIndex = Math.floor(Math.random() * shareTexts.length)
const shareText = shareTexts[randomIndex]
wx.setClipboardData({
data: shareText,
success: () => {
wx.showModal({
title: '文案已复制',
content: '请打开微信朋友圈,粘贴分享文案,配合推广海报一起发布效果更佳!\n\n再次点击可获取新的随机文案',
showCancel: false,
confirmText: '去发朋友圈'
})
}
})
},
// 提现 - 直接到微信零钱
async handleWithdraw() {
const availableEarnings = this.data.availableEarningsNum || 0
@@ -891,16 +807,6 @@ Page({
}
},
// 分享到朋友圈
onShareTimeline() {
console.log('[Referral] 分享到朋友圈,推荐码:', this.data.referralCode)
return {
title: `Soul创业派对 - 62个真实商业案例`,
query: `ref=${this.data.referralCode}`
// 不设置 imageUrl使用小程序默认截图
}
},
goBack() {
wx.navigateBack()
},

View File

@@ -9,9 +9,6 @@
<view class="nav-btn" bindtap="showNotification">
<image class="nav-icon" src="/assets/icons/bell.svg" mode="aspectFit"></image>
</view>
<view class="nav-btn" bindtap="showSettings">
<image class="nav-icon" src="/assets/icons/settings.svg" mode="aspectFit"></image>
</view>
</view>
<text class="nav-title">分销中心</text>
<view class="nav-right-placeholder"></view>
@@ -179,28 +176,6 @@
</view>
<image class="share-arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image>
</view>
<view class="share-item" bindtap="shareToWechat">
<view class="share-icon wechat">
<image class="icon-share-btn" src="/assets/icons/message-circle.svg" mode="aspectFit"></image>
</view>
<view class="share-info">
<text class="share-title">分享到朋友圈</text>
<text class="share-desc">复制文案发朋友圈</text>
</view>
<image class="share-arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image>
</view>
<view class="share-item" bindtap="handleMoreShare">
<view class="share-icon link">
<image class="icon-share-btn" src="/assets/icons/share.svg" mode="aspectFit"></image>
</view>
<view class="share-info">
<text class="share-title">更多分享方式</text>
<text class="share-desc">使用系统分享功能</text>
</view>
<image class="share-arrow-icon" src="/assets/icons/arrow-right.svg" mode="aspectFit"></image>
</view>
</view>
<!-- 收益明细 - 增强版 -->
@@ -239,14 +214,7 @@
</view>
</view>
<!-- 空状态 - 对齐 Next.js -->
<view class="empty-earnings" wx:if="{{earningsDetails.length === 0 && activeBindings.length === 0}}">
<view class="empty-icon-wrapper">
<image class="empty-gift-icon" src="/assets/icons/gift.svg" mode="aspectFit"></image>
</view>
<text class="empty-title">暂无收益记录</text>
<text class="empty-desc">分享专属链接,好友购买即可获得 {{shareRate}}% 返利</text>
</view>
</view>
<!-- 海报生成弹窗 - 优化小程序显示 -->
@@ -315,7 +283,7 @@
</view>
<!-- 二维码 -->
<view class="poster-qr-wrap" bindtap="previewPosterQr">
<view class="poster-qr-wrap">
<image
class="poster-qr-img"
src="{{posterQrSrc}}"

View File

@@ -236,12 +236,85 @@
/* ????- ?? Next.js */
.empty-earnings { background: rgba(28, 28, 30, 0.8); backdrop-filter: blur(40rpx); border: 2rpx solid rgba(255,255,255,0.1); border-radius: 32rpx; padding: 64rpx 40rpx; text-align: center; margin-bottom: 24rpx; }
.empty-icon-wrapper { width: 128rpx; height: 128rpx; border-radius: 50%; background: rgba(28, 28, 30, 0.8); display: flex; align-items: center; justify-content: center; margin: 0 auto 32rpx; }
.empty-gift-icon { width: 64rpx; height: 64rpx; display: block; filter: brightness(0) saturate(100%) invert(60%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(95%) contrast(85%); }
.empty-title { font-size: 30rpx; font-weight: 500; color: #fff; display: block; margin-bottom: 16rpx; }
.empty-desc { font-size: 26rpx; color: rgba(255,255,255,0.6); display: block; line-height: 1.5; }
/* 空状态 - 暂无收益 */
.empty-earnings {
background: linear-gradient(160deg, rgba(28, 28, 30, 0.95) 0%, rgba(15, 33, 55, 0.9) 100%);
backdrop-filter: blur(40rpx);
border: 2rpx solid rgba(0, 206, 209, 0.15);
border-radius: 32rpx;
padding: 80rpx 48rpx;
margin-bottom: 24rpx;
text-align: center;
position: relative;
overflow: hidden;
}
.empty-earnings::before {
content: '';
position: absolute;
top: -80rpx;
left: 50%;
transform: translateX(-50%);
width: 320rpx;
height: 160rpx;
background: radial-gradient(ellipse at center, rgba(0, 206, 209, 0.08) 0%, transparent 70%);
pointer-events: none;
}
.empty-earnings-inner {
position: relative;
z-index: 1;
}
.empty-earnings-icon-wrap {
position: relative;
width: 160rpx;
height: 160rpx;
margin: 0 auto 40rpx;
display: flex;
align-items: center;
justify-content: center;
}
.empty-earnings-glow {
position: absolute;
inset: -20rpx;
background: radial-gradient(circle, rgba(0, 206, 209, 0.2) 0%, rgba(255, 215, 0, 0.06) 50%, transparent 70%);
border-radius: 50%;
}
.empty-earnings-icon {
width: 96rpx;
height: 96rpx;
position: relative;
z-index: 1;
filter: brightness(0) saturate(100%) invert(72%) sepia(54%) saturate(2933%) hue-rotate(134deg) brightness(101%) contrast(101%);
}
.empty-earnings-title {
font-size: 34rpx;
font-weight: 600;
color: #fff;
display: block;
margin-bottom: 20rpx;
letter-spacing: 1rpx;
}
.empty-earnings-desc {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.65);
display: block;
line-height: 1.6;
margin-bottom: 12rpx;
}
.empty-earnings-rate {
display: inline-block;
font-size: 52rpx;
font-weight: 800;
color: #00CED1;
letter-spacing: 4rpx;
text-shadow: 0 0 40rpx rgba(0, 206, 209, 0.4);
}
.empty-earnings-hint {
font-size: 24rpx;
color: rgba(255, 215, 0, 0.85);
display: block;
margin-top: 8rpx;
font-weight: 500;
}
/* ===== Loading 遮罩(备用) ===== */

View File

@@ -150,7 +150,9 @@ class ChapterAccessManager {
if (res.success && res.data) {
app.globalData.hasFullBook = res.data.hasFullBook || false
app.globalData.purchasedSections = res.data.purchasedSections || []
app.globalData.matchCount = res.data.matchCount ?? 0
app.globalData.matchQuota = res.data.matchQuota || null
const userInfo = app.globalData.userInfo || {}
userInfo.hasFullBook = res.data.hasFullBook
userInfo.purchasedSections = res.data.purchasedSections
@@ -158,7 +160,8 @@ class ChapterAccessManager {
console.log('[AccessManager] 购买状态已刷新:', {
hasFullBook: res.data.hasFullBook,
purchasedCount: res.data.purchasedSections.length
purchasedCount: res.data.purchasedSections.length,
matchCount: res.data.matchCount
})
}
} catch (e) {

View File

@@ -24,6 +24,9 @@ func Init(dsn string) error {
if err := db.AutoMigrate(&model.Withdrawal{}); err != nil {
log.Printf("database: withdrawals migrate warning: %v", err)
}
if err := db.AutoMigrate(&model.MatchRecord{}); err != nil {
log.Printf("database: match_records migrate warning: %v", err)
}
log.Println("database: connected")
return nil
}

View File

@@ -2,14 +2,86 @@ package handler
import (
"encoding/json"
"fmt"
"net/http"
"time"
"soul-api/internal/database"
"soul-api/internal/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
const defaultFreeMatchLimit = 3
// MatchQuota 匹配次数配额(纯计算:订单 + match_records
type MatchQuota struct {
PurchasedTotal int64 `json:"purchasedTotal"`
PurchasedUsed int64 `json:"purchasedUsed"`
MatchesUsedToday int64 `json:"matchesUsedToday"`
FreeRemainToday int64 `json:"freeRemainToday"`
PurchasedRemain int64 `json:"purchasedRemain"`
RemainToday int64 `json:"remainToday"` // 今日剩余可匹配次数
}
func getFreeMatchLimit(db *gorm.DB) int {
var cfg model.SystemConfig
if err := db.Where("config_key = ?", "match_config").First(&cfg).Error; err != nil {
return defaultFreeMatchLimit
}
var config map[string]interface{}
if err := json.Unmarshal(cfg.ConfigValue, &config); err != nil {
return defaultFreeMatchLimit
}
if v, ok := config["freeMatchLimit"].(float64); ok && v > 0 {
return int(v)
}
return defaultFreeMatchLimit
}
// GetMatchQuota 根据订单和 match_records 纯计算用户匹配配额
func GetMatchQuota(db *gorm.DB, userID string, freeLimit int) MatchQuota {
if freeLimit <= 0 {
freeLimit = defaultFreeMatchLimit
}
var purchasedTotal int64
db.Model(&model.Order{}).Where("user_id = ? AND product_type = ? AND status = ?", userID, "match", "paid").Count(&purchasedTotal)
var matchesToday int64
db.Model(&model.MatchRecord{}).Where("user_id = ? AND created_at >= CURDATE()", userID).Count(&matchesToday)
// 历史每日超出免费部分之和 = 已消耗的购买次数
var purchasedUsed int64
db.Raw(`
SELECT COALESCE(SUM(cnt - ?), 0) FROM (
SELECT DATE(created_at) AS d, COUNT(*) AS cnt
FROM match_records WHERE user_id = ?
GROUP BY DATE(created_at)
HAVING cnt > ?
) t
`, freeLimit, userID, freeLimit).Scan(&purchasedUsed)
freeUsed := matchesToday
if freeUsed > int64(freeLimit) {
freeUsed = int64(freeLimit)
}
freeRemain := int64(freeLimit) - freeUsed
if freeRemain < 0 {
freeRemain = 0
}
purchasedRemain := purchasedTotal - purchasedUsed
if purchasedRemain < 0 {
purchasedRemain = 0
}
remainToday := freeRemain + purchasedRemain
return MatchQuota{
PurchasedTotal: purchasedTotal,
PurchasedUsed: purchasedUsed,
MatchesUsedToday: matchesToday,
FreeRemainToday: freeRemain,
PurchasedRemain: purchasedRemain,
RemainToday: remainToday,
}
}
var defaultMatchTypes = []gin.H{
gin.H{"id": "partner", "label": "创业合伙", "matchLabel": "创业伙伴", "icon": "⭐", "matchFromDB": true, "showJoinAfterMatch": false, "price": 1, "enabled": true},
gin.H{"id": "investor", "label": "资源对接", "matchLabel": "资源对接", "icon": "👥", "matchFromDB": false, "showJoinAfterMatch": true, "price": 1, "enabled": true},
@@ -83,17 +155,38 @@ func MatchUsers(c *gin.Context) {
var body struct {
UserID string `json:"userId" binding:"required"`
MatchType string `json:"matchType"`
Phone string `json:"phone"`
WechatID string `json:"wechatId"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "缺少用户ID"})
return
}
db := database.DB()
// 全书用户无限制,否则校验今日剩余次数
var user model.User
skipQuota := false
if err := db.Where("id = ?", body.UserID).First(&user).Error; err == nil {
skipQuota = user.HasFullBook != nil && *user.HasFullBook
}
if !skipQuota {
freeLimit := getFreeMatchLimit(db)
quota := GetMatchQuota(db, body.UserID, freeLimit)
if quota.RemainToday <= 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "今日匹配次数已用完,请购买更多次数",
"code": "QUOTA_EXCEEDED",
})
return
}
}
// 只匹配已绑定微信或手机号的用户
var users []model.User
q := database.DB().Where("id != ?", body.UserID).
q := db.Where("id != ?", body.UserID).
Where("((wechat_id IS NOT NULL AND wechat_id != '') OR (phone IS NOT NULL AND phone != ''))")
if err := q.Order("created_at DESC").Limit(20).Find(&users).Error; err != nil || len(users) == 0 {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "暂无匹配用户", "data": nil})
c.JSON(http.StatusOK, gin.H{"success": false, "message": "暂无匹配用户", "data": nil, "code": "NO_USERS"})
return
}
// 随机选一个
@@ -124,6 +217,25 @@ func MatchUsers(c *gin.Context) {
if tag == "" {
tag = "找伙伴"
}
// 写入匹配记录(含发起者的 phone/wechat_id 便于后续联系)
rec := model.MatchRecord{
ID: fmt.Sprintf("mr_%d", time.Now().UnixNano()),
UserID: body.UserID,
MatchedUserID: r.ID,
MatchType: body.MatchType,
}
if body.MatchType == "" {
rec.MatchType = "partner"
}
if body.Phone != "" {
rec.Phone = &body.Phone
}
if body.WechatID != "" {
rec.WechatID = &body.WechatID
}
if err := db.Create(&rec).Error; err != nil {
fmt.Printf("[MatchUsers] 写入 match_records 失败: %v\n", err)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": gin.H{

View File

@@ -219,6 +219,8 @@ func miniprogramPayPost(c *gin.Context) {
if description == "" {
if req.ProductType == "fullbook" {
description = "《一场Soul的创业实验》全书"
} else if req.ProductType == "match" {
description = "购买匹配次数"
} else {
description = fmt.Sprintf("章节购买-%s", req.ProductID)
}
@@ -311,7 +313,7 @@ func miniprogramPayPost(c *gin.Context) {
})
}
// GET - 查询订单状态
// GET - 查询订单状态(并主动同步:若微信已支付但本地未标记,则更新本地订单,便于配额即时生效)
func miniprogramPayGet(c *gin.Context) {
orderSn := c.Query("orderSn")
if orderSn == "" {
@@ -336,6 +338,18 @@ func miniprogramPayGet(c *gin.Context) {
switch tradeState {
case "SUCCESS":
status = "paid"
// 若微信已支付,主动同步到本地 orders不等 PayNotify便于购买次数即时生效
db := database.DB()
var order model.Order
if err := db.Where("order_sn = ?", orderSn).First(&order).Error; err == nil && order.Status != nil && *order.Status != "paid" {
now := time.Now()
db.Model(&order).Updates(map[string]interface{}{
"status": "paid",
"transaction_id": transactionID,
"pay_time": now,
})
fmt.Printf("[PayGet] 主动同步订单已支付: %s\n", orderSn)
}
case "CLOSED", "REVOKED", "PAYERROR":
status = "failed"
case "REFUND":
@@ -429,6 +443,8 @@ func MiniprogramPayNotify(c *gin.Context) {
if attach.ProductType == "fullbook" {
db.Model(&model.User{}).Where("id = ?", buyerUserID).Update("has_full_book", true)
fmt.Printf("[PayNotify] 用户已购全书: %s\n", buyerUserID)
} else if attach.ProductType == "match" {
fmt.Printf("[PayNotify] 用户购买匹配次数: %s订单 %s\n", buyerUserID, orderSn)
} else if attach.ProductType == "section" && attach.ProductID != "" {
var count int64
db.Model(&model.Order{}).Where(

View File

@@ -276,6 +276,9 @@ func UserPurchaseStatus(c *gin.Context) {
purchasedSections = append(purchasedSections, r.ProductID)
}
}
// 匹配次数配额:纯计算(订单 + match_records
freeLimit := getFreeMatchLimit(db)
matchQuota := GetMatchQuota(db, userId, freeLimit)
earnings := 0.0
if user.Earnings != nil {
earnings = *user.Earnings
@@ -288,8 +291,17 @@ func UserPurchaseStatus(c *gin.Context) {
"hasFullBook": user.HasFullBook != nil && *user.HasFullBook,
"purchasedSections": purchasedSections,
"purchasedCount": len(purchasedSections),
"earnings": earnings,
"pendingEarnings": pendingEarnings,
"matchCount": matchQuota.PurchasedTotal,
"matchQuota": gin.H{
"purchasedTotal": matchQuota.PurchasedTotal,
"purchasedUsed": matchQuota.PurchasedUsed,
"matchesUsedToday": matchQuota.MatchesUsedToday,
"freeRemainToday": matchQuota.FreeRemainToday,
"purchasedRemain": matchQuota.PurchasedRemain,
"remainToday": matchQuota.RemainToday,
},
"earnings": earnings,
"pendingEarnings": pendingEarnings,
}})
}

View File

@@ -0,0 +1,18 @@
package model
import "time"
// MatchRecord 匹配记录,每次用户成功匹配时写入
type MatchRecord struct {
ID string `gorm:"column:id;primaryKey;size:50" json:"id"`
UserID string `gorm:"column:user_id;index;size:50;not null" json:"userId"`
MatchType string `gorm:"column:match_type;index;size:50" json:"matchType"`
Phone *string `gorm:"column:phone;size:20" json:"phone"`
WechatID *string `gorm:"column:wechat_id;size:100" json:"wechatId"`
MatchedUserID string `gorm:"column:matched_user_id;index;size:50" json:"matchedUserId"`
MatchScore *int `gorm:"column:match_score" json:"matchScore"`
Status *string `gorm:"column:status;size:20" json:"status"`
CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
}
func (MatchRecord) TableName() string { return "match_records" }

Binary file not shown.

View File

@@ -86,3 +86,21 @@
{"level":"debug","timestamp":"2026-02-11T15:36:10+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, 11 Feb 2026 07:36:11 GMT\r\n\r\n{\"access_token\":\"101_MiWs49YphSqT8fdzbbvWznN0pNKQ99O94IqbcNCL6FoMWAZG6t0v-D4xg37xDjsyGgttZm2qfQd1pg6C4za3yiJrKv8g7s8M1n_-biSvQq-zNg6WqUzq1zTv_HQVRQaACALHG\",\"expires_in\":7200}"}
{"level":"debug","timestamp":"2026-02-11T15:36:11+08:00","caller":"kernel/baseClient.go:457","content":"GET https://api.weixin.qq.com/sns/jscode2session?access_token=101_MiWs49YphSqT8fdzbbvWznN0pNKQ99O94IqbcNCL6FoMWAZG6t0v-D4xg37xDjsyGgttZm2qfQd1pg6C4za3yiJrKv8g7s8M1n_-biSvQq-zNg6WqUzq1zTv_HQVRQaACALHG&appid=wxb8bbb2b10dec74aa&grant_type=authorization_code&js_code=0a1alR000sNRQV1MJl400aJVgF3alR0q&secret=3c1fb1f63e6e052222bbcead9d07fe0c request header: { Accept:*/*} "}
{"level":"debug","timestamp":"2026-02-11T15:36:11+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, 11 Feb 2026 07:36:11 GMT\r\n\r\n{\"session_key\":\"XL+GLsPRGyvEHUMFyzfbZg==\",\"openid\":\"ogpTW5a9exdEmEwqZsYywvgSpSQg\"}"}
{"level":"debug","timestamp":"2026-02-11T16:20:46+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-02-11T16:20:46+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, 11 Feb 2026 08:20:47 GMT\r\n\r\n{\"access_token\":\"101_lk18062WH51Gpyxwf5BwvAXTDN6YbytrNdSAQ1G7-PQhmUT3FSRxP4cGbzjtYLtbJuSOenZRMn2O5-AJkY_B5nmy5Ldj5vcnTebHMdDv5iv2w3sExLP0OOe1_FkBSAbAIANEM\",\"expires_in\":7200}"}
{"level":"debug","timestamp":"2026-02-11T16:20:47+08:00","caller":"kernel/baseClient.go:457","content":"GET https://api.weixin.qq.com/sns/jscode2session?access_token=101_lk18062WH51Gpyxwf5BwvAXTDN6YbytrNdSAQ1G7-PQhmUT3FSRxP4cGbzjtYLtbJuSOenZRMn2O5-AJkY_B5nmy5Ldj5vcnTebHMdDv5iv2w3sExLP0OOe1_FkBSAbAIANEM&appid=wxb8bbb2b10dec74aa&grant_type=authorization_code&js_code=0f1M3U000LdYQV154D000I1gkx3M3U0B&secret=3c1fb1f63e6e052222bbcead9d07fe0c request header: { Accept:*/*} "}
{"level":"debug","timestamp":"2026-02-11T16:20:47+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, 11 Feb 2026 08:20:47 GMT\r\n\r\n{\"session_key\":\"i8aGTGPaJoixERiDuV/inQ==\",\"openid\":\"ogpTW5a9exdEmEwqZsYywvgSpSQg\"}"}
{"level":"debug","timestamp":"2026-02-11T16:39:49+08:00","caller":"kernel/baseClient.go:457","content":"POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi request header: { Accept:*/*Content-Type:application/jsonAuthorization:WECHATPAY2-SHA256-RSA2048 mchid=\"1318592501\",nonce_str=\"jhAt7g06fRGod5bdlsnrFiHAb2RHZ0A1\",timestamp=\"1770799189\",serial_no=\"4A1DB62CD5C9BE0B6FC51C30621D6F99686E75C5\",signature=\"KsbmL7ZKtrjOw0jeM9k0lPUNf2Tf/aeBoxByHCsap5JekU2bkLucMJT69VRhflR18wZgWEceYoZwyitzy73eCkKNGIlF6tgywEEWysPMRGtppDykcrQh32SF8X9Gml3L+auUx7GsqJc28XgByF5tu9b1NzfVrIsxH1ZPsvz2UwnkrEBX2HDE1zRi+0s5LaxG2RMucrxWBHlMwS455s3XT8kipdGMVd5IAJn7S5mLOOu5oC6vKMGW52dFjhj3+I+mvVROOfXzB8XYVjM2B9j+47rrBeSkPMv+Gwk7W0rbJaLS/88a5G8DIXYPTD13cpaPPpC2c1iQuiJvb22emZ5ctA==\"} request body:"}
{"level":"debug","timestamp":"2026-02-11T16:39:49+08:00","caller":"kernel/baseClient.go:459","content":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 52\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, 11 Feb 2026 08:39:50 GMT\r\nKeep-Alive: timeout=8\r\nRequest-Id: 08D580B1CC0610E70618D59F85AB0120F8F61428C4F202-0\r\nServer: nginx\r\nWechatpay-Nonce: 195ca7b0f0d94d96fff77025192f360a\r\nWechatpay-Serial: 5F2543BF58239A4EB68FA4433DF1438A88B34B16\r\nWechatpay-Signature: EAzms6bPJlftvkZMl7cPqplpuKY2AD5tnjppqV1ktAu5uh8e3zoSX5g2LvJmH4Bch+8Jkg/c7kN8kJGKBNEdhpA6nEW0h9CWo3Xqv7osY7xJP0VBH/or26WLoh4kgVjuKD7K+usEUIM55epJOZlrjQvHIbzaH0JzVPWLMuyLPJFJZhJv+Ha1j0NbtNmxzHN5SOD/nY0SQf6cc4kUXyP6c6XZBlNEDNvJyszrylw4Ant8yvFn6VMg88fioXZ/S7xCVLAGWwiUWQ0WAHSoixkpp+BHUYySOt2PGngkE8xrc8mcoJe7GNl/fC7lzB8H3hKuFCyJaTRno3H3OKiy2BT2Ow==\r\nWechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048\r\nWechatpay-Timestamp: 1770799190\r\nX-Content-Type-Options: nosniff\r\n\r\n{\"prepay_id\":\"wx1116395007245943fdc7aa37fd9b4c0000\"}"}
{"level":"debug","timestamp":"2026-02-11T16:44:11+08:00","caller":"kernel/baseClient.go:457","content":"POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi request header: { Content-Type:application/jsonAuthorization:WECHATPAY2-SHA256-RSA2048 mchid=\"1318592501\",nonce_str=\"NnyI9wSbe0fA4LPSVRIigUZVBDmmFZnx\",timestamp=\"1770799451\",serial_no=\"4A1DB62CD5C9BE0B6FC51C30621D6F99686E75C5\",signature=\"Tsh01GKm2uKgLh4aGpudgU0S7BfnH94OL9wMNtuwMxme6dOd84cBLX6VparCDqAlz4jeYK2zO119pAfRdYx0ENkPBroHUC8x1w52JP2uo9NijXpaYc+tvKtZxFqUzUwCQzOsU2C94PXQTyxzRjnc+e/Yz+7bxwReCW5LluOjIDF6lZPSVXahIUbwrgwTLkomhLOOmEwtVrypUtjQKUo251REnW/EgFfdxmromzQByAmi4i28fkFz8Ou0KihOHGcjzXjy/MA2CKlvQgmA3uDZyqID1G8uPWr78dZBRmIwRTVdH7NyKEIFqMibkHbm0hIig1RGg2CnwNPfMHs2F9lBQw==\"Accept:*/*} request body:"}
{"level":"debug","timestamp":"2026-02-11T16:44:11+08:00","caller":"kernel/baseClient.go:459","content":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 52\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, 11 Feb 2026 08:44:12 GMT\r\nKeep-Alive: timeout=8\r\nRequest-Id: 08DC82B1CC0610581895B2F8AF0120F8F30A28E571-0\r\nServer: nginx\r\nWechatpay-Nonce: f49bcd1b0942e2ed44f872ff8acd2e7a\r\nWechatpay-Serial: 5F2543BF58239A4EB68FA4433DF1438A88B34B16\r\nWechatpay-Signature: o1wTiMG0gMoT4Y7dxxV8XXKmERWwYlA1XFc/FU7R07SlJcCczvA+mx8sYpuxLK0Nd+fvavGI7bvyLn7QK2kbA5+kXmE8bhzvZStavPkleWoNMdnwYRSz5QlrB0//T3o4NVkLU+g8De9jYJW7FCdBrxhib7n+Tn4CIhVygkzBWiL8CfC+dXtjOyj/OzKjlxocNsSZ7DVPargq5fvANpNPXx9j2d/oXbuU1oRZum+EK1YlHdLYU8J9PfYauIUuXOBF8RShBChI3otE4IxUan2lgmMjbQNo3PMWpV2VIQI/mGzgD+5dW1dWgPEteVgZBW43jBFv4ub9h4JHda3EAs7Rlg==\r\nWechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048\r\nWechatpay-Timestamp: 1770799452\r\nX-Content-Type-Options: nosniff\r\n\r\n{\"prepay_id\":\"wx1116441226578302ef3124977d11d20001\"}"}
{"level":"debug","timestamp":"2026-02-11T16:46:30+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-02-11T16:46:30+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, 11 Feb 2026 08:46:31 GMT\r\n\r\n{\"access_token\":\"101_-JXKwbzx_ltAKNx0Nvh-kvqQWBK8PgN5fjiYuTcBD3jmDSaL23BVjOL_ISi6uEPdc2BtC7kKBHRVUZu6VVVRYRcf-G4_GoktaSxLTv-YYw0RjlZbXkVrOV7aKLsXNZiAEAAQC\",\"expires_in\":7200}"}
{"level":"debug","timestamp":"2026-02-11T16:46:31+08:00","caller":"kernel/baseClient.go:457","content":"GET https://api.weixin.qq.com/sns/jscode2session?access_token=101_-JXKwbzx_ltAKNx0Nvh-kvqQWBK8PgN5fjiYuTcBD3jmDSaL23BVjOL_ISi6uEPdc2BtC7kKBHRVUZu6VVVRYRcf-G4_GoktaSxLTv-YYw0RjlZbXkVrOV7aKLsXNZiAEAAQC&appid=wxb8bbb2b10dec74aa&grant_type=authorization_code&js_code=0f17hPll2I3A9h4hTJll2pm1lE17hPlQ&secret=3c1fb1f63e6e052222bbcead9d07fe0c request header: { Accept:*/*} "}
{"level":"debug","timestamp":"2026-02-11T16:46:31+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, 11 Feb 2026 08:46:31 GMT\r\n\r\n{\"session_key\":\"Y4B1/RLT06xbdfVXH1y00A==\",\"openid\":\"ogpTW5a9exdEmEwqZsYywvgSpSQg\"}"}
{"level":"debug","timestamp":"2026-02-11T16:50:45+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-02-11T16:50:45+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, 11 Feb 2026 08:50:46 GMT\r\n\r\n{\"access_token\":\"101_Yr_zLiETXFJ5MPdat-RxAdZxz_TaLqEmMI6COssI8tBokjwMppwVj2gzWfgvzujOnxIVWl7gFQF5R7OlqoCjUxYk_Hlak4eNxf--HGARagPByKlJnrjN6_SQ4L4PGZeACAGXQ\",\"expires_in\":7200}"}
{"level":"debug","timestamp":"2026-02-11T16:50:45+08:00","caller":"kernel/baseClient.go:457","content":"GET https://api.weixin.qq.com/sns/jscode2session?access_token=101_Yr_zLiETXFJ5MPdat-RxAdZxz_TaLqEmMI6COssI8tBokjwMppwVj2gzWfgvzujOnxIVWl7gFQF5R7OlqoCjUxYk_Hlak4eNxf--HGARagPByKlJnrjN6_SQ4L4PGZeACAGXQ&appid=wxb8bbb2b10dec74aa&grant_type=authorization_code&js_code=0d1KVG1w3BkPu634hJ3w34KXVM0KVG19&secret=3c1fb1f63e6e052222bbcead9d07fe0c request header: { Accept:*/*} "}
{"level":"debug","timestamp":"2026-02-11T16:50:45+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, 11 Feb 2026 08:50:46 GMT\r\n\r\n{\"session_key\":\"aKCaNGx074vk1YgW+4KQ5A==\",\"openid\":\"ogpTW5a9exdEmEwqZsYywvgSpSQg\"}"}
{"level":"debug","timestamp":"2026-02-11T16:52:52+08:00","caller":"kernel/baseClient.go:457","content":"POST https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi request header: { Content-Type:application/jsonAuthorization:WECHATPAY2-SHA256-RSA2048 mchid=\"1318592501\",nonce_str=\"6tRA1S0Oj3gSjC4PJN9Bw8aG0TiMl475\",timestamp=\"1770799971\",serial_no=\"4A1DB62CD5C9BE0B6FC51C30621D6F99686E75C5\",signature=\"A+VEHo9xo5LHllJAOIHW9agRd2CY9IyjsvjJTTVCBccRvuxSekj737zPyRSy9sTP82iRF9n94P0EPwHaqud8EaOMa2ErIDp6KJbqTYXlw+ZhoNO9VafIGKA3c/RASOU15ut5GuYx4KpYXmUUicuzhd7boKSlLI6vDQtSFQ/kT+0gTr5FZ8kqluEWtngHGJ6C78GLqajmK+1GbygMLWyTYuPTJNPMeb6+ifl7UI8glXXHpNronQEQViu8TrXAJ62o9Dxrc/QYd2XyrokrAAyNboNG0IZSJNogrdDFxwQ4zmHp2Yozq9zwpMUboU997O7dbg7+waYSLlNwnHjGZNJNmg==\"Accept:*/*} request body:"}
{"level":"debug","timestamp":"2026-02-11T16:52:52+08:00","caller":"kernel/baseClient.go:459","content":"------------------response content:HTTP/1.1 200 OK\r\nContent-Length: 52\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, 11 Feb 2026 08:52:52 GMT\r\nKeep-Alive: timeout=8\r\nRequest-Id: 08E486B1CC0610F80318B8CBC05520DAD417289DAA03-0\r\nServer: nginx\r\nWechatpay-Nonce: c44d544b1ecbf0d4f0ab45965b9b5979\r\nWechatpay-Serial: 5F2543BF58239A4EB68FA4433DF1438A88B34B16\r\nWechatpay-Signature: Ayy1/SGwhrYOS9JplecPKkEuS7jYKpgtxxDjVy+eGiUH1V0olSn9xzEx2HZ8LHasqGln3fhWhgkfwqddZ2rp003IJtb/R8iFn1LWoDBZw/JLFotm3Iegc9O9/sy5IxpDwOWN5Gi5KEhqxHQTta2JvSBrxnd60+GbAAd21LYzI5DHsXSc85UZZ11/MWzWwB3zvLqnpNh7PEbUyQj9+nNxeyuI59cLTDITELXa+Mrv9MpM4+raePq6VuAPqgiR2Ymf30iiggdqQjerqXWz2wIjg03Fob8XZTEnQMjYgwac6Oa4ESu7n4N/XesvopqyFsrK28chgSJqVX4SOpXIukiTnA==\r\nWechatpay-Signature-Type: WECHATPAY2-SHA256-RSA2048\r\nWechatpay-Timestamp: 1770799972\r\nX-Content-Type-Options: nosniff\r\n\r\n{\"prepay_id\":\"wx11165252691461617b158c058e434d0001\"}"}

View File

@@ -1 +1 @@
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1