1. 项目概述:提货卡H5前后端开源方案
去年双十一期间,我们团队为某连锁超市开发的电子提货卡系统在高峰期承载了日均20万笔交易。这个完全基于H5技术栈的前后端分离方案,最终决定将核心代码开源。不同于传统的礼品卡系统,这套方案特别针对移动端优化,用户从领取到使用全程无需下载APP,在微信、支付宝等任何浏览器环境都能流畅操作。
整套系统采用React+Node.js技术栈,前后端完全解耦。前端H5页面适配所有主流移动浏览器,后端提供RESTful API接口。特别值得一提的是,我们实现了微信环境下一键分享带封面图的功能,以及支付宝H5支付的完整对接方案。这些实战中积累的经验都会在开源代码中完整呈现。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构解析
2.1 前端技术选型
选择React作为核心框架主要基于三点考虑:
- 虚拟DOM机制能保证在低端手机上也能流畅渲染复杂的卡片动画
- 丰富的生态圈(特别是针对H5的移动端组件库)
- 与Node.js后端同属JavaScript技术栈,降低全栈开发成本
我们特别优化了这些场景:
- 微信浏览器白屏问题:通过动态polyfill解决
- IOS滑动卡顿:使用
-webkit-overflow-scrolling: touch - 安卓输入法遮挡:监听resize事件自动调整布局
javascript复制// 典型页面结构示例
import { useQRCode } from 'react-qrcode-logo'
function GiftCard() {
const qrRef = useRef(null)
const { inputValue } = useParams()
// 生成带logo的二维码
const qrCode = useQRCode(inputValue, {
logoImage: "/logo.png",
logoWidth: 24,
logoHeight: 24
})
return (
<div className="card-container">
<div ref={qrRef} dangerouslySetInnerHTML={{__html: qrCode}} />
</div>
)
}
2.2 后端服务设计
采用Express+Koa混合架构,既保留Express成熟的中间件生态,又利用Koa的洋葱模型处理高并发请求。数据库选用MongoDB,主要考虑:
- 礼品卡数据具有明显的文档型特征
- 需要处理高峰期的突发写入(如节日促销)
- 地理分布式的副本集保证99.9%的可用性
javascript复制// 典型的卡券核销接口
router.post('/redeem', async (ctx) => {
const { cardId, pin } = ctx.request.body
// 分布式锁防止重复核销
const lockKey = `lock:${cardId}`
const locked = await redis.setnx(lockKey, 1)
if (!locked) throw new Error('操作过于频繁')
try {
const card = await Card.findOneAndUpdate(
{ _id: cardId, pin: pin, status: 'active' },
{ $set: { status: 'used', usedAt: new Date() } },
{ new: true }
)
if (!card) throw new Error('卡券无效或已使用')
ctx.body = { success: true, balance: card.balance }
} finally {
await redis.del(lockKey)
}
})
3. 关键功能实现细节
3.1 微信分享优化方案
在微信环境中,我们通过接入JS-SDK实现了自定义分享功能。这里有个关键细节:微信要求分享配置必须来自后端签名,前端不能直接写死。
javascript复制// 后端签名接口
router.get('/wechat-signature', async (ctx) => {
const { url } = ctx.query
const noncestr = crypto.randomBytes(16).toString('hex')
const timestamp = Math.floor(Date.now() / 1000)
const str = `jsapi_ticket=${ticket}&noncestr=${noncestr}×tamp=${timestamp}&url=${url}`
const signature = crypto.createHash('sha1').update(str).digest('hex')
ctx.body = {
appId: config.wechat.appId,
noncestr,
timestamp,
signature
}
})
前端调用示例:
javascript复制useEffect(() => {
const fetchSignature = async () => {
const res = await axios.get(`/wechat-signature?url=${encodeURIComponent(window.location.href.split('#')[0])}`)
wx.config({
debug: false,
appId: res.data.appId,
timestamp: res.data.timestamp,
nonceStr: res.data.noncestr,
signature: res.data.signature,
jsApiList: ['updateAppMessageShareData', 'updateTimelineShareData']
})
wx.ready(() => {
wx.updateAppMessageShareData({
title: '您有一张电子提货卡待领取',
desc: '点击查看卡券详情',
link: window.location.href,
imgUrl: 'https://example.com/share.jpg'
})
})
}
if (isWechatBrowser()) fetchSignature()
}, [])
3.2 支付宝H5支付对接
支付宝H5支付需要特别注意异步通知的处理。我们实现了这些安全措施:
- 签名验证(防止伪造通知)
- 幂等性处理(防止重复入账)
- 金额校验(防止金额篡改)
javascript复制// 支付结果回调处理
router.post('/alipay/notify', async (ctx) => {
const params = ctx.request.body
const signVerified = alipaySdk.checkNotifySign(params)
if (!signVerified) {
ctx.status = 400
return
}
const { out_trade_no, trade_no, total_amount } = params
const order = await Order.findOne({ orderNo: out_trade_no })
// 双重校验
if (order && order.status === 'pending' && order.amount === parseFloat(total_amount)) {
order.status = 'paid'
order.paymentId = trade_no
await order.save()
// 发放卡券
await dispatchCard(order.userId, order.amount)
}
ctx.body = 'success'
})
4. 部署与性能优化
4.1 前端部署方案
我们采用Docker+Nginx的组合部署前端资源,关键配置包括:
- 开启HTTP/2提升加载速度
- 配置长期缓存(静态资源hash化)
- 开启Brotli压缩(比gzip提升约20%压缩率)
nginx复制server {
listen 443 ssl http2;
server_name card.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache";
}
location /static {
expires 1y;
add_header Cache-Control "public";
access_log off;
}
brotli on;
brotli_types text/plain text/css application/javascript application/json;
}
4.2 后端性能调优
针对高并发场景,我们实施了这些优化措施:
- 连接池优化:
javascript复制// MongoDB连接配置
mongoose.connect(uri, {
poolSize: 50, // 默认5
socketTimeoutMS: 30000,
connectTimeoutMS: 30000
})
- Redis缓存策略:
- 高频访问的卡券信息缓存300秒
- 使用Redis管道批量处理核销请求
- 采用Lua脚本实现原子操作
- 负载测试结果:
- 4核8G云服务器
- 模拟1000并发用户
- 平均响应时间<200ms
- 错误率<0.1%
5. 常见问题解决方案
5.1 微信浏览器缓存问题
现象:更新代码后用户仍看到旧版本
解决方案:
- 在入口HTML添加meta标签
html复制<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
- 静态资源添加hash指纹
javascript复制// webpack配置
output: {
filename: '[name].[contenthash:8].js',
}
5.2 跨域会话保持
前后端分离架构下,需要特殊处理会话状态:
javascript复制// 后端CORS配置
app.use(cors({
origin: true, // 动态匹配请求来源
credentials: true // 允许发送cookie
}))
// 前端axios配置
axios.defaults.withCredentials = true
5.3 安全防护措施
- 接口限流:
javascript复制const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 每个IP限制100次请求
})
app.use('/api/', limiter)
- XSS防护:
- 所有用户输入通过DOMPurify过滤
- 设置Content-Security-Policy头
- Cookie标记HttpOnly和Secure
- CSRF防护:
javascript复制const csrf = require('csurf')
app.use(csrf({ cookie: true }))
// 前端获取token
const getCSRFToken = async () => {
const { data } = await axios.get('/csrf-token')
axios.defaults.headers.common['X-CSRF-Token'] = data.token
}
这套提货卡系统经过618、双十一等大促活动的实战检验,峰值QPS达到2000+。开源版本保留了所有核心功能,包括卡券管理、支付对接、分销系统等模块。特别适合中小型企业快速搭建自己的电子礼品卡平台,所有接口都配备了详细的Swagger文档和Postman测试集合。
