Files
soul/app/api/miniprogram/qrcode/route.ts

115 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// app/api/miniprogram/qrcode/route.ts
// 生成带参数的小程序码 - 绑定推荐人ID和章节ID
import { NextRequest, NextResponse } from 'next/server'
const APPID = process.env.WECHAT_APPID || 'wxb8bbb2b10dec74aa'
const APPSECRET = process.env.WECHAT_APPSECRET || '3c1fb1f63e6e052222bbcead9d07fe0c'
// 简单的内存缓存
let cachedToken: { token: string; expireAt: number } | null = null
// 获取access_token带缓存
async function getAccessToken() {
// 检查缓存
if (cachedToken && cachedToken.expireAt > Date.now()) {
return cachedToken.token
}
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`
const res = await fetch(url)
const data = await res.json()
if (data.access_token) {
// 缓存token提前5分钟过期
cachedToken = {
token: data.access_token,
expireAt: Date.now() + (data.expires_in - 300) * 1000
}
return data.access_token
}
throw new Error(data.errmsg || '获取access_token失败')
}
export async function POST(req: NextRequest) {
try {
const body = await req.json()
const { scene, page, width = 280, chapterId, userId } = body
// 构建scene参数
// 格式ref=用户ID&ch=章节ID用于分享海报
let finalScene = scene
if (!finalScene) {
const parts = []
if (userId) parts.push(`ref=${userId.slice(0, 15)}`)
if (chapterId) parts.push(`ch=${chapterId}`)
finalScene = parts.join('&') || 'soul'
}
console.log('[QRCode] 生成小程序码, scene:', finalScene)
// 获取access_token
const accessToken = await getAccessToken()
// 生成小程序码(使用无限制生成接口)
const qrcodeUrl = `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${accessToken}`
const qrcodeRes = await fetch(qrcodeUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
scene: finalScene.slice(0, 32), // 最多32个字符
page: page || 'pages/index/index',
width: Math.min(width, 430), // 最大430
auto_color: false,
line_color: { r: 0, g: 206, b: 209 }, // 品牌色
is_hyaline: false,
env_version: 'trial' // 体验版,正式发布后改为 release
})
})
// 检查响应类型
const contentType = qrcodeRes.headers.get('content-type')
if (contentType?.includes('application/json')) {
// 返回了错误信息
const errorData = await qrcodeRes.json()
console.error('[QRCode] 生成失败:', errorData)
return NextResponse.json({
success: false,
error: errorData.errmsg || '生成小程序码失败',
errcode: errorData.errcode
}, { status: 200 }) // 返回200但success为false
}
// 返回图片
const imageBuffer = await qrcodeRes.arrayBuffer()
if (imageBuffer.byteLength < 1000) {
// 图片太小,可能是错误
console.error('[QRCode] 返回的图片太小:', imageBuffer.byteLength)
return NextResponse.json({
success: false,
error: '生成的小程序码无效'
}, { status: 200 })
}
const base64 = Buffer.from(imageBuffer).toString('base64')
console.log('[QRCode] 生成成功,图片大小:', base64.length, '字符')
return NextResponse.json({
success: true,
image: `data:image/png;base64,${base64}`,
scene: finalScene
})
} catch (error) {
console.error('[QRCode] Error:', error)
return NextResponse.json({
success: false,
error: '生成小程序码失败: ' + String(error)
}, { status: 200 })
}
}