Authored by yyq

get code

@@ -7,11 +7,19 @@ const zerobuyModel = require('../models/zero-buy'); @@ -7,11 +7,19 @@ const zerobuyModel = require('../models/zero-buy');
7 7
8 module.exports = { 8 module.exports = {
9 list(req, res, next) { 9 list(req, res, next) {
10 - req.ctx(zerobuyModel).getList(req.query.status) 10 + if (req.isAdmin) {
  11 + req.query.noCache = true;
  12 + }
  13 +
  14 + req.ctx(zerobuyModel).getList(req.query.status, req.query.page, req.query)
11 .then(res.json).catch(next); 15 .then(res.json).catch(next);
12 }, 16 },
13 content(req, res, next) { 17 content(req, res, next) {
14 - req.ctx(zerobuyModel).getContent(req.query.prizeId) 18 + if (req.isAdmin) {
  19 + req.query.noCache = true;
  20 + }
  21 +
  22 + req.ctx(zerobuyModel).getContent(req.query.prizeId, req.query)
15 .then(res.json).catch(next); 23 .then(res.json).catch(next);
16 }, 24 },
17 listMine(req, res, next) { 25 listMine(req, res, next) {
@@ -28,4 +36,17 @@ module.exports = { @@ -28,4 +36,17 @@ module.exports = {
28 req.ctx(zerobuyModel).getCodeMine(uid, prizeId) 36 req.ctx(zerobuyModel).getCodeMine(uid, prizeId)
29 .then(res.json).catch(next); 37 .then(res.json).catch(next);
30 }, 38 },
  39 + codeGain(req, res, next) {
  40 + let params = req.body;
  41 +
  42 + if (!params.uid || !params.actPrizeId || !params.userName || !params.userThumb) {
  43 + return res.json({
  44 + code: 400,
  45 + message: '参数非法'
  46 + });
  47 + }
  48 +
  49 + req.ctx(zerobuyModel).getPrizeCode(req.query)
  50 + .then(res.json).catch(next);
  51 + }
31 }; 52 };
@@ -5,11 +5,18 @@ @@ -5,11 +5,18 @@
5 */ 5 */
6 const _ = require('lodash'); 6 const _ = require('lodash');
7 const mysqlCli = global.yoho.utils.mysqlCli; 7 const mysqlCli = global.yoho.utils.mysqlCli;
  8 +const MemoryCache = require('../../../utils/memory-cache');
8 9
9 const TABLE_ACT_PRIZE_PRODUCT = 'act_prize_product'; 10 const TABLE_ACT_PRIZE_PRODUCT = 'act_prize_product';
10 const TABLE_ACT_PRIZE_PRODUCT_CONTENT = 'act_prize_product_content'; 11 const TABLE_ACT_PRIZE_PRODUCT_CONTENT = 'act_prize_product_content';
11 const TABLE_ACT_PRIZE_PRODUCT_USER = 'act_prize_product_user'; 12 const TABLE_ACT_PRIZE_PRODUCT_USER = 'act_prize_product_user';
12 13
  14 +const CACHE_TIMES = 60 * 5; // 缓存时间
  15 +const MAX_JOIN_TIMES = 2; // 最大活动参与次数
  16 +
  17 +const userTimesCache = new MemoryCache();
  18 +const productCache = new MemoryCache();
  19 +
13 function handelResult(result) { 20 function handelResult(result) {
14 return { 21 return {
15 code: 200, 22 code: 200,
@@ -17,6 +24,20 @@ function handelResult(result) { @@ -17,6 +24,20 @@ function handelResult(result) {
17 }; 24 };
18 } 25 }
19 26
  27 +function getActivityStatus(info = {}, num, now) {
  28 + let resData = {
  29 + isEnd: true
  30 + };
  31 +
  32 + now = now || (new Date().getTime() / 1000);
  33 +
  34 + if (!info.status || num >= info.limit || now >= info.end_time) {
  35 + return resData;
  36 + }
  37 +
  38 + return {};
  39 +}
  40 +
20 module.exports = class extends global.yoho.BaseModel { 41 module.exports = class extends global.yoho.BaseModel {
21 constructor(ctx) { 42 constructor(ctx) {
22 super(ctx); 43 super(ctx);
@@ -25,30 +46,51 @@ module.exports = class extends global.yoho.BaseModel { @@ -25,30 +46,51 @@ module.exports = class extends global.yoho.BaseModel {
25 /** 46 /**
26 * 0元购活动列表 47 * 0元购活动列表
27 * @param status 48 * @param status
28 - * @param actId 49 + * @param page
  50 + * @param extra
29 * @returns {*} 51 * @returns {*}
30 */ 52 */
31 - getList(status, actId) { 53 + getList(status, page, extra = {}) {
  54 + const pageSize = 20;
  55 + const actId = parseInt(extra.actId, 10) || 0;
  56 +
32 status = parseInt(status, 10); 57 status = parseInt(status, 10);
33 - actId = parseInt(actId, 10) || 0; 58 + page = parseInt(page, 10) || 1;
  59 +
  60 + const listCacheKey = `list_${actId}_${status}_${page}_${status}`;
  61 +
  62 + if (!extra.noCache) {
  63 + let cacheList = productCache.get(listCacheKey);
  64 +
  65 + if (!_.isUndefined(cacheList)) {
  66 + return handelResult(cacheList);
  67 + }
  68 + }
34 69
35 status = _.isNaN(status) ? '> 0' : `= ${status}`; 70 status = _.isNaN(status) ? '> 0' : `= ${status}`;
36 71
  72 + let limit = `${(page - 1) * pageSize},${page * pageSize}`;
  73 +
37 return mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT} 74 return mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT}
38 - where act_id = :actId and status ${status}`, { 75 + where act_id = :actId and status ${status} limit ${limit}`, {
39 actId 76 actId
40 - }).then(handelResult); 77 + }).then(result => {
  78 + productCache.set(listCacheKey, result, CACHE_TIMES);
  79 +
  80 + return handelResult(result);
  81 + });
41 } 82 }
42 83
43 /** 84 /**
44 * 0元购用户参与列表 85 * 0元购用户参与列表
45 * @param uid 86 * @param uid
46 - * @param actId 87 + * @param extra
47 * @returns {*} 88 * @returns {*}
48 */ 89 */
49 - getListMine(uid, actId) { 90 + getListMine(uid, extra = {}) {
  91 + const actId = parseInt(extra.actId, 10) || 0;
  92 +
50 uid = parseInt(uid, 10) || 0; 93 uid = parseInt(uid, 10) || 0;
51 - actId = parseInt(actId, 10) || 0;  
52 94
53 return mysqlCli.query(`select u.*, p.name, p.price, p.status, p.cover_img from 95 return mysqlCli.query(`select u.*, p.name, p.price, p.status, p.cover_img from
54 ${TABLE_ACT_PRIZE_PRODUCT_USER} u left join 96 ${TABLE_ACT_PRIZE_PRODUCT_USER} u left join
@@ -65,18 +107,28 @@ module.exports = class extends global.yoho.BaseModel { @@ -65,18 +107,28 @@ module.exports = class extends global.yoho.BaseModel {
65 * @param actPrizeId 107 * @param actPrizeId
66 * @returns {*} 108 * @returns {*}
67 */ 109 */
68 - getContent(actPrizeId) { 110 + getContent(actPrizeId, extra = {}) {
69 let resData = {}; 111 let resData = {};
70 112
71 if (!actPrizeId) { 113 if (!actPrizeId) {
72 return Promise.resolve(handelResult(resData)); 114 return Promise.resolve(handelResult(resData));
73 } 115 }
74 116
  117 + const contentCacheKey = `content_${actPrizeId}`;
  118 +
  119 + if (!extra.noCache) {
  120 + let cacheContent = productCache.get(contentCacheKey);
  121 +
  122 + if (!_.isUndefined(cacheContent)) {
  123 + return handelResult(cacheContent);
  124 + }
  125 + }
  126 +
75 return Promise.all([ 127 return Promise.all([
76 mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT} 128 mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT}
77 - where id = :actPrizeId limit 1`, {actPrizeId}), 129 + where id = :actPrizeId limit 1;`, {actPrizeId}),
78 mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT_CONTENT} 130 mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT_CONTENT}
79 - where act_prize_id = :actPrizeId`, {actPrizeId}) 131 + where act_prize_id = :actPrizeId;`, {actPrizeId})
80 ]).then(result => { 132 ]).then(result => {
81 let [product, content] = result; 133 let [product, content] = result;
82 134
@@ -87,6 +139,8 @@ module.exports = class extends global.yoho.BaseModel { @@ -87,6 +139,8 @@ module.exports = class extends global.yoho.BaseModel {
87 }); 139 });
88 } 140 }
89 141
  142 + productCache.set(contentCacheKey, resData, CACHE_TIMES);
  143 +
90 return resData; 144 return resData;
91 }).then(handelResult); 145 }).then(handelResult);
92 } 146 }
@@ -98,7 +152,7 @@ module.exports = class extends global.yoho.BaseModel { @@ -98,7 +152,7 @@ module.exports = class extends global.yoho.BaseModel {
98 getCodeRecent() { 152 getCodeRecent() {
99 return mysqlCli.query(`select user_name, user_thumb, create_time 153 return mysqlCli.query(`select user_name, user_thumb, create_time
100 from ${TABLE_ACT_PRIZE_PRODUCT_USER} 154 from ${TABLE_ACT_PRIZE_PRODUCT_USER}
101 - order by u.create_time desc limit 10`).then(handelResult); 155 + order by u.create_time desc limit 10;`).then(handelResult);
102 } 156 }
103 157
104 /** 158 /**
@@ -112,9 +166,166 @@ module.exports = class extends global.yoho.BaseModel { @@ -112,9 +166,166 @@ module.exports = class extends global.yoho.BaseModel {
112 actPrizeId = parseInt(actPrizeId, 10) || 0; 166 actPrizeId = parseInt(actPrizeId, 10) || 0;
113 167
114 return mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT_USER} 168 return mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT_USER}
115 - where act_prize_id = :actPrizeId and uid = :uid`, { 169 + where act_prize_id = :actPrizeId and uid = :uid;`, {
116 actPrizeId, 170 actPrizeId,
117 uid 171 uid
118 }).then(handelResult); 172 }).then(handelResult);
119 } 173 }
  174 +
  175 + /**
  176 + * 0元购获取抽奖码
  177 + * @param uid
  178 + * @param actPrizeId
  179 + * @returns {*}
  180 + */
  181 + async getPrizeCode(uid, actPrizeId, extra = {}) {
  182 + uid = parseInt(uid, 10) || 0;
  183 + actPrizeId = parseInt(actPrizeId, 10) || 0;
  184 +
  185 + let info = await Promise.all([
  186 + mysqlCli.query(`select * from ${TABLE_ACT_PRIZE_PRODUCT}
  187 + where id = :actPrizeId limit 1;`, {actPrizeId}),
  188 + mysqlCli.query(`select count(distinct uid) as join_num from ${TABLE_ACT_PRIZE_PRODUCT_USER}
  189 + where act_prize_id = :actPrizeId;`, {actPrizeId})
  190 + ]);
  191 +
  192 + const errorData = {
  193 + code: 400,
  194 + message: '活动已结束或已达人数上限'
  195 + };
  196 +
  197 + let status = getActivityStatus(_.get(info, '[0][0]'), _.get(info, '[1][0].join_num', 0));
  198 +
  199 + if (status.isEnd) {
  200 + return errorData;
  201 + }
  202 +
  203 + if (extra.shareUid) {
  204 + this.sendPrizeCode(extra.shareUid, actPrizeId, {isShareTake: true});
  205 + }
  206 +
  207 +
  208 + return this.sendPrizeCode(uid, actPrizeId, Object.assign(extra, {isShareTake: false}));
  209 + }
  210 +
  211 + /**
  212 + * 生成抽奖码
  213 + * @param length
  214 + * @param isRetry
  215 + * @returns {*}
  216 + */
  217 + async createPrizeCode(length = 8, isRetry) {
  218 + const mapStr = 'QQWERTYUIOPASDFGHJKLZXCVBNM12345678900';
  219 + let prizeCode = '';
  220 +
  221 + for (let i = 0; i < length; i++) {
  222 + prizeCode += mapStr[parseInt(Math.random() * mapStr.length, 10)];
  223 + }
  224 +
  225 + const info = await mysqlCli.query(`select id from ${TABLE_ACT_PRIZE_PRODUCT_USER}
  226 + where prize_code = :prizeCode limit 1`, {prizeCode});
  227 +
  228 + if (info && info.length && !isRetry) {
  229 + return this.createPrizeCode(length, true);
  230 + } else {
  231 + return prizeCode;
  232 + }
  233 + }
  234 +
  235 + /**
  236 + * 向单用户发送抽奖码
  237 + * @param uid
  238 + * @param actPrizeId
  239 + * @param extra
  240 + * @returns {*}
  241 + */
  242 + async sendPrizeCode(uid, actPrizeId, extra = {}) {
  243 + // 查询用户参与次数
  244 + const TimesCacheKey = `${actPrizeId}_${uid}`;
  245 +
  246 + let userJoinTimes = userTimesCache.get(TimesCacheKey);
  247 +
  248 + if (_.isUndefined(userJoinTimes)) {
  249 + userJoinTimes = await mysqlCli.query(`select count(*) from ${TABLE_ACT_PRIZE_PRODUCT_USER}
  250 + where act_prize_id = :actPrizeId and uid = :uid`, {
  251 + actPrizeId,
  252 + uid
  253 + });
  254 +
  255 + if (userJoinTimes >= MAX_JOIN_TIMES) {
  256 + userTimesCache.set(TimesCacheKey, userJoinTimes);
  257 + }
  258 + }
  259 +
  260 + let errorData = {
  261 + code: 400,
  262 + message: '领取失败,请稍后重试'
  263 + };
  264 +
  265 + // 大于最大活动参与次数
  266 + if (userJoinTimes >= MAX_JOIN_TIMES) {
  267 + errorData.message = '活动参与已达上限';
  268 +
  269 + return errorData;
  270 + }
  271 +
  272 + // 2个及以上的抽奖码仅限分享获得
  273 + if (userJoinTimes >= 1 && !extra.isShareTake) {
  274 + errorData.message = '您已获得抽奖码,分享好友获取更多抽奖码';
  275 +
  276 + return errorData;
  277 + }
  278 +
  279 + // 未获取过抽奖码需用户信息参数
  280 + if (!userJoinTimes && !extra.userName) {
  281 + errorData.message = '缺少必要参数';
  282 +
  283 + return errorData;
  284 + }
  285 +
  286 + const prizeCode = await this.createPrizeCode();
  287 +
  288 + if (!prizeCode) {
  289 + return errorData;
  290 + }
  291 +
  292 + const keyArr = ['act_prize_id', 'uid', 'user_name', 'user_thumb', 'union_id', 'prize_code', 'is_share_take', 'share_uid'];
  293 + const isShareTake = extra.isShareTake ? 1 : 0;
  294 + const shareUid = extra.shareUid || 0;
  295 + let insert;
  296 +
  297 + if (extra.userName) {
  298 + insert = await mysqlCli.insert(`insert into ${TABLE_ACT_PRIZE_PRODUCT_USER} (${keyArr.join(',')})
  299 + values (:actPrizeId, :uid, :userName, :userThumb, :unionId, :prizeCode, :isShareTake, :shareUid);`, {
  300 + actPrizeId,
  301 + uid,
  302 + userName: extra.userName,
  303 + userThumb: extra.userThumb,
  304 + unionId: extra.unionId,
  305 + prizeCode,
  306 + isShareTake,
  307 + shareUid
  308 + });
  309 + } else {
  310 + insert = await mysqlCli.insert(`insert into ${TABLE_ACT_PRIZE_PRODUCT_USER} (${keyArr.join(',')})
  311 + select :actPrizeId as act_prize_id, :uid as uid, user_name, user_thumb, union_id, :prizeCode as prize_code,
  312 + :isShareTake as is_share_take, :shareUid as share_uid from ${TABLE_ACT_PRIZE_PRODUCT_USER} where uid = :uid limit 1;`, {
  313 + actPrizeId,
  314 + uid,
  315 + prizeCode,
  316 + isShareTake,
  317 + shareUid
  318 + });
  319 + }
  320 +
  321 + if (_.isNumber(insert)) {
  322 + return {
  323 + code: 200,
  324 + data: {prizeCode},
  325 + message: '领取成功'
  326 + };
  327 + } else {
  328 + return errorData;
  329 + }
  330 + }
120 }; 331 };
@@ -22,5 +22,6 @@ router.get('/zerobuy/list/mine', zeroBuy.listMine); // 0元购用户参与列 @@ -22,5 +22,6 @@ router.get('/zerobuy/list/mine', zeroBuy.listMine); // 0元购用户参与列
22 router.get('/zerobuy/content', zeroBuy.content); // 0元购详情 22 router.get('/zerobuy/content', zeroBuy.content); // 0元购详情
23 router.get('/zerobuy/code/recent', zeroBuy.codeRecent); // 0元购抽奖码最近获取记录 23 router.get('/zerobuy/code/recent', zeroBuy.codeRecent); // 0元购抽奖码最近获取记录
24 router.get('/zerobuy/code/mine', zeroBuy.codeMine); // 0元购用户单个活动抽奖码 24 router.get('/zerobuy/code/mine', zeroBuy.codeMine); // 0元购用户单个活动抽奖码
  25 +router.post('/zerobuy/code/gain', zeroBuy.codeGain); // 0元购获取抽奖码
25 26
26 module.exports = router; 27 module.exports = router;
@@ -34,5 +34,7 @@ module.exports = (req, res, next) => { @@ -34,5 +34,7 @@ module.exports = (req, res, next) => {
34 }); 34 });
35 } 35 }
36 36
  37 + req.isAdmin = true;
  38 +
37 next(); 39 next();
38 }; 40 };
  1 +/**
  2 + * memoryCache
  3 + * @author: yyq<yanqing.yang@yoho.cn>
  4 + * @date: 2018/07/23
  5 + */
  6 +
  7 +const _ = require('lodash');
  8 +
  9 +function _clearArray(list, key) {
  10 + _.remove(list, (n) => {
  11 + return n === key;
  12 + });
  13 +}
  14 +
  15 +function _clearObject(obj, key) {
  16 + delete obj[key];
  17 +}
  18 +
  19 +function _getNowTime() {
  20 + return new Date().getTime() / 1000;
  21 +}
  22 +
  23 +module.exports = function memoryCache(maxLength = 1000) {
  24 + let cache = {};
  25 + let mapList = [];
  26 +
  27 + // 获取缓存数据
  28 + this.get = (key) => {
  29 + if (!cache.hasOwnProperty(key)) {
  30 + return;
  31 + }
  32 +
  33 + let info = cache[key];
  34 +
  35 + // 校验过期时间
  36 + if (info.exptime && info.exptime - _getNowTime() < 0) {
  37 + this.clear(key);
  38 + return;
  39 + }
  40 +
  41 + return info.value;
  42 + };
  43 +
  44 + // 设置缓存数据
  45 + this.set = (key, value, exptime) => {
  46 + cache.hasOwnProperty(key) && _clearArray(mapList, key);
  47 +
  48 + mapList.push(key);
  49 + cache[key] = {
  50 + value,
  51 + exptime: exptime ? (exptime + _getNowTime()) : 0 // 过期时间
  52 + };
  53 +
  54 + // 清除老旧数据
  55 + if (mapList.length > maxLength) {
  56 + let len = mapList.length - maxLength;
  57 +
  58 + for (let i = 0; i < len; i++) {
  59 + _clearObject(cache, mapList.shift());
  60 + }
  61 + }
  62 + };
  63 +
  64 + // 清除单条缓存数据
  65 + this.clear = (key) => {
  66 + if (cache.hasOwnProperty(key)) {
  67 + _clearArray(mapList, key);
  68 + _clearObject(cache, key);
  69 + }
  70 + };
  71 +
  72 + // 清除所有缓存数据
  73 + this.clearAll = () => {
  74 + cache = {};
  75 + mapList = [];
  76 + };
  77 +
  78 + return this;
  79 +};