diff --git a/app/api/referral/visit/route.ts b/app/api/referral/visit/route.ts new file mode 100644 index 0000000..2f56790 --- /dev/null +++ b/app/api/referral/visit/route.ts @@ -0,0 +1,100 @@ +/** + * 推荐访问记录API + * 用于统计「通过链接进的人数」 + * 不需要用户登录即可记录 + */ + +import { NextRequest, NextResponse } from 'next/server' +import { query } from '@/lib/db' + +/** + * POST - 记录推荐访问 + */ +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { referralCode, visitorOpenId, visitorId, source, page } = body + + if (!referralCode) { + return NextResponse.json({ + success: false, + error: '推荐码不能为空' + }, { status: 400 }) + } + + // 查找推荐人 + const referrers = await query( + 'SELECT id FROM users WHERE referral_code = ?', + [referralCode] + ) as any[] + + if (referrers.length === 0) { + return NextResponse.json({ + success: false, + error: '推荐码无效' + }, { status: 400 }) + } + + const referrerId = referrers[0].id + + // 记录访问(允许重复访问记录,用于统计总访问次数) + try { + await query(` + INSERT INTO referral_visits ( + referrer_id, visitor_id, visitor_openid, source, page, created_at + ) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + `, [ + referrerId, + visitorId || null, + visitorOpenId || null, + source || 'miniprogram', + page || '' + ]) + + console.log(`[Referral Visit] 记录访问: 推荐人=${referrerId}, 访客openId=${visitorOpenId?.slice(0,10) || 'unknown'}`) + } catch (insertError) { + // 表可能不存在,尝试创建 + console.log('[Referral Visit] 插入失败,尝试创建表...') + await query(` + CREATE TABLE IF NOT EXISTS referral_visits ( + id INT AUTO_INCREMENT PRIMARY KEY, + referrer_id VARCHAR(50) NOT NULL COMMENT '推广者ID', + visitor_id VARCHAR(50) COMMENT '访客ID(可能为空)', + visitor_openid VARCHAR(100) COMMENT '访客openId', + source VARCHAR(50) DEFAULT 'miniprogram' COMMENT '来源:miniprogram/web/share', + page VARCHAR(200) COMMENT '落地页路径', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_referrer_id (referrer_id), + INDEX idx_visitor_id (visitor_id), + INDEX idx_created_at (created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + `) + + // 重试插入 + await query(` + INSERT INTO referral_visits ( + referrer_id, visitor_id, visitor_openid, source, page, created_at + ) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP) + `, [ + referrerId, + visitorId || null, + visitorOpenId || null, + source || 'miniprogram', + page || '' + ]) + } + + return NextResponse.json({ + success: true, + message: '访问已记录' + }) + + } catch (error) { + console.error('[Referral Visit] 错误:', error) + // 即使出错也返回成功,不影响用户体验 + return NextResponse.json({ + success: true, + message: '已处理' + }) + } +} diff --git a/miniprogram/app.js b/miniprogram/app.js index 358818b..e0c3901 100644 --- a/miniprogram/app.js +++ b/miniprogram/app.js @@ -78,21 +78,48 @@ App({ if (refCode) { console.log('[App] 检测到推荐码:', refCode) + // 立即记录访问(不需要登录,用于统计"通过链接进的人数") + this.recordReferralVisit(refCode) + // 检查是否已经绑定过 const boundRef = wx.getStorageSync('boundReferralCode') if (boundRef && boundRef !== refCode) { - console.log('[App] 已绑定过其他推荐码,跳过') - return + console.log('[App] 已绑定过其他推荐码,不更换绑定关系') + // 但仍然记录访问,不return + } else { + // 保存待绑定的推荐码 + this.globalData.pendingReferralCode = refCode + wx.setStorageSync('pendingReferralCode', refCode) + + // 如果已登录,立即绑定 + if (this.globalData.isLoggedIn && this.globalData.userInfo) { + this.bindReferralCode(refCode) + } } + } + }, + + // 记录推荐访问(不需要登录,用于统计) + async recordReferralVisit(refCode) { + try { + // 获取openId(如果有) + const openId = this.globalData.openId || wx.getStorageSync('openId') || '' + const userId = this.globalData.userInfo?.id || '' - // 保存待绑定的推荐码 - this.globalData.pendingReferralCode = refCode - wx.setStorageSync('pendingReferralCode', refCode) - - // 如果已登录,立即绑定 - if (this.globalData.isLoggedIn && this.globalData.userInfo) { - this.bindReferralCode(refCode) - } + await this.request('/api/referral/visit', { + method: 'POST', + data: { + referralCode: refCode, + visitorOpenId: openId, + visitorId: userId, + source: 'miniprogram', + page: getCurrentPages()[getCurrentPages().length - 1]?.route || '' + } + }) + console.log('[App] 记录推荐访问成功') + } catch (e) { + console.log('[App] 记录推荐访问失败:', e.message) + // 忽略错误,不影响用户体验 } }, @@ -286,20 +313,29 @@ App({ wx.setStorageSync('userInfo', res.data.user) wx.setStorageSync('token', res.data.token || '') + + // 登录成功后,检查待绑定的推荐码并执行绑定 + const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode + if (pendingRef) { + console.log('[App] 登录后自动绑定推荐码:', pendingRef) + this.bindReferralCode(pendingRef) + } } return res.data } } catch (apiError) { - console.log('[App] API登录失败,使用模拟登录:', apiError.message) + console.log('[App] API登录失败:', apiError.message) + // 不使用模拟登录,提示用户网络问题 + wx.showToast({ title: '网络异常,请重试', icon: 'none' }) + return null } - // API不可用时使用模拟登录 - return this.mockLogin() + return null } catch (e) { console.error('[App] 登录失败:', e) - // 最后尝试模拟登录 - return this.mockLogin() + wx.showToast({ title: '登录失败,请重试', icon: 'none' }) + return null } }, @@ -335,36 +371,11 @@ App({ return null }, - // 模拟登录(后端不可用时使用) + // 模拟登录已废弃 - 不再使用 + // 现在必须使用真实的微信登录获取openId作为唯一标识 mockLogin() { - const mockUser = { - id: 'user_' + Date.now(), - nickname: '访客用户', - phone: '', - avatar: '', - referralCode: 'SOUL' + Date.now().toString(36).toUpperCase().slice(-6), - purchasedSections: [], - hasFullBook: false, - earnings: 0, - pendingEarnings: 0, - referralCount: 0, - createdAt: new Date().toISOString() - } - - const mockToken = 'mock_token_' + Date.now() - - // 保存用户信息 - this.globalData.userInfo = mockUser - this.globalData.isLoggedIn = true - this.globalData.purchasedSections = mockUser.purchasedSections - this.globalData.hasFullBook = mockUser.hasFullBook - - wx.setStorageSync('userInfo', mockUser) - wx.setStorageSync('token', mockToken) - - console.log('模拟登录成功:', mockUser) - - return { user: mockUser, token: mockToken } + console.warn('[App] mockLogin已废弃,请使用真实登录') + return null }, // 手机号登录 @@ -385,14 +396,21 @@ App({ wx.setStorageSync('userInfo', res.data.user) wx.setStorageSync('token', res.data.token) + // 登录成功后绑定推荐码 + const pendingRef = wx.getStorageSync('pendingReferralCode') || this.globalData.pendingReferralCode + if (pendingRef) { + console.log('[App] 手机号登录后自动绑定推荐码:', pendingRef) + this.bindReferralCode(pendingRef) + } + return res.data } } catch (e) { - console.log('手机号API登录失败,使用模拟登录:', e) + console.log('[App] 手机号登录失败:', e) + wx.showToast({ title: '登录失败,请重试', icon: 'none' }) } - // 回退到模拟登录 - return this.mockLogin() + return null }, // 退出登录 diff --git a/miniprogram/pages/read/read.js b/miniprogram/pages/read/read.js index 8de44cf..13f9b1b 100644 --- a/miniprogram/pages/read/read.js +++ b/miniprogram/pages/read/read.js @@ -550,6 +550,12 @@ Page({ let paymentData = null try { + // 获取章节完整名称用于支付描述 + const sectionTitle = this.data.section?.title || sectionId + const description = type === 'fullbook' + ? '《一场Soul的创业实验》全书' + : `章节${sectionId}-${sectionTitle.length > 20 ? sectionTitle.slice(0, 20) + '...' : sectionTitle}` + const res = await app.request('/api/miniprogram/pay', { method: 'POST', data: { @@ -557,7 +563,7 @@ Page({ productType: type, productId: sectionId, amount, - description: type === 'fullbook' ? '《一场Soul的创业实验》全书' : `章节-${sectionId}`, + description, userId: app.globalData.userInfo?.id || '' } })