当前位置:网站首页 >  攻略

从零搭建带连续签到专属券触发的轻量级会员锁客技术实操指南

时间:2026年06月07日 05:49:37 来源:易频IT社区

前置准备:10分钟完成环境与依赖配置

本次实操依赖三个核心工具,均为开源免费,配置步骤如下:

完成工具安装后,在桌面新建`member-lock`文件夹,终端进入后执行依赖初始化:

```bash npm init -y npm install express mysql2 redis dayjs jsonwebtoken cors --save ```

第一步:创建MySQL数据库与锁客核心表

打开MySQL Workbench或终端(终端命令`mysql -uroot -proot123456`),执行以下完整SQL脚本:

```sql CREATE DATABASE IF NOT EXISTS member_lock DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; USE member_lock; -- 用户基础表(含会员等级映射) CREATE TABLE IF NOT EXISTS `users` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID', `phone` VARCHAR(11) NOT NULL UNIQUE COMMENT '手机号', `level` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '会员等级:1普通 2白银 3黄金 4钻石', `total_points` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '总积分', `used_points` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '已用积分', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), INDEX `idx_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 连续签到记录表(用Redis替代大部分,但留一张月签到备份历史用) CREATE TABLE IF NOT EXISTS `sign_history` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '历史记录ID', `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID', `sign_date` DATE NOT NULL COMMENT '签到日期', `reward_points` INT UNSIGNED NOT NULL COMMENT '本次签到积分', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '签到时间', PRIMARY KEY (`id`), UNIQUE KEY `idx_user_date` (`user_id`, `sign_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 专属优惠券表 CREATE TABLE IF NOT EXISTS `coupons` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '券ID', `user_id` BIGINT UNSIGNED NOT NULL COMMENT '领取用户ID', `type` TINYINT UNSIGNED NOT NULL COMMENT '券类型:1连续7天专属满减券', `threshold` INT UNSIGNED NOT NULL COMMENT '满减门槛(分)', `discount` INT UNSIGNED NOT NULL COMMENT '折扣金额(分)', `expire_time` DATETIME NOT NULL COMMENT '过期时间', `status` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态:1未使用 2已使用 3已过期', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发放时间', PRIMARY KEY (`id`), INDEX `idx_user_status` (`user_id`, `status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ```

第二步:编写轻量级会员锁客后端核心代码

在`member-lock`文件夹下创建三个文件:`db.js`(数据库连接)、`redis.js`(Redis连接)、`app.js`(主业务逻辑)。

文件1:db.js

从零搭建带连续签到专属券触发的轻量级会员锁客技术实操指南

可直接复制的完整代码:

```javascript const mysql = require('mysql2/promise'); const pool = mysql.createPool({ host: 'localhost', user: 'root', password: 'root123456', database: 'member_lock', waitForConnections: true, connectionLimit: 10, queueLimit: 0 }); module.exports = pool; ```

文件2:redis.js

可直接复制的完整代码:

```javascript const redis = require('redis'); const client = redis.createClient({ socket: { port: 6379, host: 'localhost' } }); client.on('error', (err) => console.log('Redis连接错误', err)); client.connect(); module.exports = client; ```

文件3:app.js

可直接复制的完整代码(已包含注册、登录、签到、查连续签到天数、查专属券功能):

```javascript const express = require('express'); const cors = require('cors'); const dayjs = require('dayjs'); const pool = require('./db'); const redisClient = require('./redis'); const app = express(); const PORT = 3000; app.use(cors()); app.use(express.json()); // 生成简易JWT(仅用于演示,生产环境需加secret和过期时间) const generateToken = (userId) => `token_${userId}`; // 注册接口(仅需手机号) app.post('/api/register', async (req, res) => { const { phone } = req.body; if (!/^1[3-9]\d{9}$/.test(phone)) return res.json({ code: 400, msg: '手机号格式错误' }); try { const [result] = await pool.execute('INSERT INTO users (phone) VALUES (?)', [phone]); const token = generateToken(result.insertId); res.json({ code: 200, msg: '注册成功', data: { userId: result.insertId, token } }); } catch (err) { if (err.code === 'ER_DUP_ENTRY') return res.json({ code: 400, msg: '手机号已注册' }); res.json({ code: 500, msg: '服务器错误' }); } }); // 登录接口(复用注册逻辑) app.post('/api/login', async (req, res) => { const { phone } = req.body; if (!/^1[3-9]\d{9}$/.test(phone)) return res.json({ code: 400, msg: '手机号格式错误' }); try { const [rows] = await pool.execute('SELECT id FROM users WHERE phone = ?', [phone]); if (rows.length === 0) return res.json({ code: 404, msg: '用户未注册' }); const token = generateToken(rows[0].id); res.json({ code: 200, msg: '登录成功', data: { userId: rows[0].id, token } }); } catch (err) { res.json({ code: 500, msg: '服务器错误' }); } }); // 验证token中间件(演示版,直接解析userId) const auth = (req, res, next) => { const token = req.headers.authorization?.split(' ')[1]; if (!token?.startsWith('token_')) return res.json({ code: 401, msg: '未登录' }); req.userId = parseInt(token.split('_')[1]); next(); }; // 核心锁客接口:签到 app.post('/api/sign', auth, async (req, res) => { const userId = req.userId; const today = dayjs().format('YYYY-MM-DD'); const yesterday = dayjs().subtract(1, 'day').format('YYYY-MM-DD'); const signKey = `sign:${userId}`; const streakKey = `streak:${userId}`; try { // 检查是否已签到 const isSigned = await redisClient.hGet(signKey, today); if (isSigned) return res.json({ code: 400, msg: '今日已签到' }); // 获取连续签到天数 let streak = 0; const lastStreakDay = await redisClient.hGet(signKey, yesterday); if (lastStreakDay) { streak = parseInt(await redisClient.get(streakKey) || 0); } streak += 1; if (streak > 7) streak = 1; // 计算积分:普通日10,连续第3天20,连续第7天50 let rewardPoints = 10; if (streak === 3) rewardPoints = 20; if (streak === 7) rewardPoints = 50; // 开启事务 const connection = await pool.getConnection(); await connection.beginTransaction(); try { // 更新用户积分 await connection.execute('UPDATE users SET total_points = total_points + ? WHERE id = ?', [rewardPoints, userId]); // 写入月签到历史 await connection.execute('INSERT INTO sign_history (user_id, sign_date, reward_points) VALUES (?, ?, ?)', [userId, today, rewardPoints]); // 连续7天发放专属满减券:满1000分(10元)减200分(2元),7天后过期 if (streak === 7) { const expireTime = dayjs().add(7, 'day').format('YYYY-MM-DD HH:mm:ss'); await connection.execute('INSERT INTO coupons (user_id, type, threshold, discount, expire_time) VALUES (?, 1, 1000, 200, ?)', [userId, expireTime]); } await connection.commit(); } catch (err) { await connection.rollback(); throw err; } finally { connection.release(); } // 更新Redis await redisClient.hSet(signKey, today, streak); await redisClient.expire(signKey, 86400 32); // 保留32天月签到记录 await redisClient.set(streakKey, streak); await redisClient.expire(streakKey, 86400 2); // 连续签到超时时间设为2天(防止漏签后1天还能续) res.json({ code: 200, msg: '签到成功', data: { streak, rewardPoints, is7DayCoupon: streak === 7 } }); } catch (err) { res.json({ code: 500, msg: '服务器错误' }); } }); // 查询接口:当前连续签到天数 app.get('/api/streak', auth, async (req, res) => { const userId = req.userId; const streak = parseInt(await redisClient.get(`streak:${userId}`) || 0); res.json({ code: 200, msg: '查询成功', data: { streak } }); }); // 查询接口:专属未过期券 app.get('/api/coupons', auth, async (req, res) => { const userId = req.userId; const [rows] = await pool.execute('SELECT id, threshold, discount, expire_time FROM coupons WHERE user_id = ? AND status = 1 AND expire_time > NOW()', [userId]); res.json({ code: 200, msg: '查询成功', data: { coupons: rows } }); }); app.listen(PORT, () => console.log(`服务已启动:http://localhost:${PORT}`)); ```

第三步:1分钟启动服务并测试

终端进入`member-lock`文件夹,执行以下命令启动服务:

```bash node app.js ```

服务启动成功后,用Postman或Apifox(推荐,下载地址 https://www.apifox.cn/download)测试接口,测试流程按顺序:

  • 注册:POST请求`http://localhost:3000/api/register`,Body选JSON,输入`{"phone":"13800138000"}`,保存返回的userId和token
  • 签到:POST请求`http://localhost:3000/api/sign`,Headers添加`Authorization: Bearer token_返回的userId`,连续执行7次模拟连续签到(注意Apifox可手动修改Redis中的streak值快速验证第7天券发放)
  • 查券:GET请求`http://localhost:3000/api/coupons`,Headers添加同样的token,查看是否有满减券

第四步:生产环境快速优化建议

演示版为了零门槛省略了安全和性能细节,生产环境可按以下操作10分钟优化:

  • JWT替换为带强secret和2小时过期时间的标准jsonwebtoken
  • 签到Redis事务用`MULTI/EXEC`保证原子性
  • 添加定时任务每日凌晨检查coupons表更新过期券状态(可使用`node-cron`)

相关推荐

最新

热门

推荐

精选

标签

易频IT社区是综合性互联网IT技术门户网站,专注分享网络技术、服务器运维、网络安全、编程开发、系统架构、云计算、大数据等行业干货,实时更新IT行业资讯、零基础教程、实战案例,为IT从业者、技术爱好者提供专业的学习交流平台。

Copyright © 2021-2026 易频IT社区. All Rights Reserved. 备案号:闽ICP备2023013482号 网站地图