1. 项目概述
"一番赏"是源自日本的抽奖式购物模式,玩家通过购买抽奖券获得随机商品,而"无限赏"则是其变种玩法——奖品池永不枯竭,参与者可以无限次抽取。这种模式近年来在国内潮玩市场迅速走红,据统计2022年国内一番赏市场规模已突破50亿元。
开发这样的小程序需要同时解决三个核心问题:高并发的抽奖逻辑处理、奖品库存的动态管理、以及流畅的用户体验。我们团队采用Uni-App+Spring Cloud的技术栈,实现了日均10万+抽奖请求的稳定运行。下面我将从技术选型到具体实现,完整拆解这个项目的开发要点。
2. 技术架构设计
2.1 前端技术选型
选择Uni-App主要基于三点考虑:
- 跨平台能力:一套代码可同时发布到微信、支付宝、H5等多个平台
- Vue3生态:组合式API更适合复杂交互场景的开发
- 性能优化:通过条件编译实现平台特定优化
关键配置示例(vue.config.js):
javascript复制module.exports = {
transpileDependencies: ['@dcloudio/uni-ui'],
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
maxSize: 244 * 1024
}
}
}
}
2.2 后端架构设计
采用Spring Cloud微服务架构,主要拆分为:
- 抽奖服务(lottery-service)
- 支付服务(payment-service)
- 用户服务(user-service)
- 商品服务(product-service)
服务间通信采用OpenFeign+Ribbon实现负载均衡,网关层使用Spring Cloud Gateway处理路由和限流。
3. 核心功能实现
3.1 抽奖算法实现
无限赏的核心在于概率算法设计。我们采用分段概率池技术:
java复制public class LotteryAlgorithm {
// 奖品概率配置
private static final Map<Integer, Double> PRIZE_PROBABILITY = Map.of(
1, 0.01, // 大奖1%
2, 0.09, // 二等奖9%
3, 0.9 // 普通奖90%
);
public Integer draw() {
double random = ThreadLocalRandom.current().nextDouble();
double temp = 0;
for (Map.Entry<Integer, Double> entry : PRIZE_PROBABILITY.entrySet()) {
temp += entry.getValue();
if (random <= temp) {
return entry.getKey();
}
}
return 3; // 默认返回普通奖
}
}
3.2 双Token认证方案
为保证支付和抽奖环节的安全,我们实现了双Token机制:
- AccessToken:短期有效(2小时),用于常规API调用
- RefreshToken:长期有效(7天),用于更新AccessToken
Uni-App端实现代码:
javascript复制// 请求拦截器
uni.addInterceptor('request', {
invoke(args) {
if (!args.url.startsWith('http')) {
args.url = baseURL + args.url
}
args.header = {
...args.header,
'Authorization': `Bearer ${store.getters.accessToken}`
}
},
fail(err) {
console.error('interceptor-fail', err)
}
})
// token刷新逻辑
async function refreshToken() {
const res = await uni.request({
url: '/auth/refresh',
method: 'POST',
data: {
refresh_token: store.getters.refreshToken
}
})
store.commit('SET_TOKEN', res.data)
}
4. 高并发优化方案
4.1 缓存策略设计
采用多级缓存架构:
- 本地缓存:Caffeine(有效期30秒)
- 分布式缓存:Redis(有效期5分钟)
- 数据库:MySQL+分库分表
奖品库存更新流程:
mermaid复制graph TD
A[用户抽奖请求] --> B{本地缓存是否存在}
B -->|是| C[返回缓存结果]
B -->|否| D[查询Redis]
D --> E{Redis是否存在}
E -->|是| F[返回Redis结果]
E -->|否| G[查询数据库并更新缓存]
4.2 数据库优化
- 分库分表策略:按用户ID哈希分16个库,每个库分16张表
- 索引优化:为抽奖记录表建立(user_id, create_time)联合索引
- 字段设计:所有金额字段使用DECIMAL(10,2),避免浮点精度问题
5. 运营数据分析
5.1 关键指标埋点
在小程序中埋入以下关键事件:
- 抽奖开始(lottery_start)
- 抽奖结果(lottery_result)
- 支付发起(payment_start)
- 支付完成(payment_complete)
Uni-App埋点示例:
javascript复制// 自定义埋点方法
function track(event, params = {}) {
if (process.env.NODE_ENV === 'production') {
uni.request({
url: '/log/track',
method: 'POST',
data: {
event,
...params,
timestamp: Date.now(),
deviceId: uni.getSystemInfoSync().deviceId
}
})
}
}
// 抽奖结果埋点
track('lottery_result', {
prize_id: res.id,
prize_name: res.name,
prize_level: res.level
})
5.2 实时数据分析
使用Flink构建实时计算管道:
- 日志采集:通过Logstash收集Nginx日志
- 消息队列:Kafka作为缓冲层
- 实时计算:Flink处理关键指标
- 数据存储:结果写入ClickHouse
核心指标计算SQL示例:
sql复制-- 每小时抽奖次数统计
SELECT
window_start,
window_end,
COUNT(*) AS lottery_count
FROM TABLE(
TUMBLE(TABLE lottery_events, DESCRIPTOR(event_time), INTERVAL '1' HOUR)
)
GROUP BY window_start, window_end
6. 踩坑与优化经验
6.1 微信小程序限制突破
-
分包加载优化:
- 主包控制在2MB以内
- 抽奖核心逻辑放在主包
- 奖品展示等非核心功能动态加载
-
图片加载优化:
javascript复制// 使用webp格式+CDN加速 function getImageUrl(url) { return `${cdnUrl}${url}?x-oss-process=image/format,webp/quality,Q_80` }
6.2 服务降级方案
当奖品服务不可用时,自动降级到本地缓存:
java复制@FeignClient(name = "product-service", fallback = ProductServiceFallback.class)
public interface ProductService {
@GetMapping("/api/products/{id}")
Product getProduct(@PathVariable Long id);
}
@Component
public class ProductServiceFallback implements ProductService {
@Override
public Product getProduct(Long id) {
return CacheManager.get("product:" + id);
}
}
7. 安全防护措施
7.1 防刷单机制
-
设备指纹识别:
javascript复制// 生成设备指纹 function getDeviceId() { const systemInfo = uni.getSystemInfoSync() return md5( systemInfo.brand + systemInfo.model + systemInfo.system + systemInfo.platform ) } -
抽奖频率限制:
java复制@RateLimiter(value = 10, key = "#userId") public LotteryResult doLottery(Long userId) { // 抽奖逻辑 }
7.2 数据加密方案
-
敏感字段加密:
java复制@Convert(converter = CryptoConverter.class) private String phoneNumber; public class CryptoConverter implements AttributeConverter<String, String> { public String convertToDatabaseColumn(String attribute) { return AES.encrypt(attribute); } } -
传输层加密:
nginx复制# Nginx配置 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
8. 项目部署方案
8.1 容器化部署
Docker Compose配置示例:
yaml复制version: '3'
services:
lottery-service:
image: registry.cn-hangzhou.aliyuncs.com/your-repo/lottery-service:${TAG}
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
deploy:
resources:
limits:
cpus: '2'
memory: 2G
8.2 监控告警配置
Prometheus监控指标示例:
yaml复制- job_name: 'spring'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['lottery-service:8080']
relabel_configs:
- source_labels: [__address__]
target_label: instance
regex: '(.*):\d+'
9. 项目演进方向
- 社交化功能:增加抽奖结果分享、好友助力等玩法
- AR展示:通过小程序AR能力展示3D奖品效果
- 区块链溯源:将稀有奖品的流转记录上链
AR展示实现示例:
javascript复制// 使用小程序AR能力
wx.createARCamera({
mode: '3d',
success(res) {
res.scene.addModel({
asset: 'model.glb',
position: [0, 0, -2]
})
}
})
在实际运营中,我们发现用户最活跃的时间段是晚上8-10点,这个时段的并发量能达到平时的3倍。为此我们开发了动态扩容方案,当监控到CPU使用率持续5分钟超过70%时,自动增加2个pod实例。这个优化使我们在618活动期间平稳应对了每分钟超过5000次的抽奖请求。
