Authored by 陈峰

商品缓存清理增加根据skn导出商品url功能

No preview for this file type
No preview for this file type
... ... @@ -13,6 +13,7 @@ import OperationLoggerModel from './operation_logger';
import PageCacheModel from './page_cache';
import CdnCacheModel from './cdn_cache';
import ProductCacheModel from './product_cache';
import TempProductImportModel from './temp_product_import';
import MemcachedHostModel from './memcached_host';
import DegradeModel from './degrade';
import DegradeServerModel from './degrade_server';
... ... @@ -31,6 +32,7 @@ const OperationLogger = new OperationLoggerModel();
const PageCache = new PageCacheModel();
const CdnCache = new CdnCacheModel();
const ProductCache = new ProductCacheModel();
const TempProductImport = new TempProductImportModel();
const MemcachedHost = new MemcachedHostModel();
const Degrade = new DegradeModel();
const DegradeServer = new DegradeServerModel();
... ... @@ -53,6 +55,7 @@ export {
PageCache,
CdnCache,
ProductCache,
TempProductImport,
MemcachedHost,
RestartInfo,
DeleteRestartInfo,
... ...
... ... @@ -7,6 +7,8 @@
import Model from './model';
import rp from 'request-promise';
import ws from '../../lib/ws';
import sign from '../../lib/sign'
import _ from 'lodash';
class ProductCache extends Model{
constructor() {
... ... @@ -34,6 +36,78 @@ class ProductCache extends Model{
self._broadcast(`开始提交:其它批量`);
await self._batchPost(list, url);
}
async generateProductUrl(list) {
let self = this;
let interval = 50;
let tick = parseInt(list.length / interval, 10) + (list.length % interval > 0 ? 1 : 0);
let result = [];
self._broadcast(`获取商品信息中:`);
for (var tickIndex = 0; tickIndex < tick; tickIndex++) {
let datas = list.slice(tickIndex * interval, (tickIndex + 1) * interval > list.length ? list.length : (tickIndex + 1) * interval);
try {
let sknList = [];
_.forEach(datas, data => {
sknList.push(data.skn)
})
let skns = sknList.join(',');
self._broadcast(`进度:${tickIndex+1}/${tick}`)
let res = await self._batchProductInfo(skns);
_.forEach(res.data.product_list, (product) => {
result.push(self._joinProductUrl(product));
});
} catch(exp) {
}
}
self._broadcast(`获取完成,正在生成文件`);
return result;
}
async _batchProductInfo(skns) {
let params = {
productSkn: skns,
method: 'h5.product.batch'
}
params = sign.apiSign(params, 'h5', '5.1.0', '');
return new Promise((resolve, reject) => {
rp({
url: 'http://api.yoho.cn/',
qs: params,
json: true,
timeout: 3000,
gzip: true
})
.then(function (res) {
res.code === 200 ? resolve(res) : reject(res);
})
.catch(function (err) {
if (err.response) {
console.log(err.response.body);
reject(err.response.body)
}
});
})
}
_joinProductUrl(product) {
if (product && product.goods_list && product.goods_list.length) {
return `/product/pro_${product.product_id}_${product.goods_list[0].goods_id}/${product.cn_alphabet}.html`;
}
return '';
}
rendExcel(sheets) {
if (sheets.length) {
let rows = sheets[0].data;
if (rows.length > 1) {
let data = [];
for (var i = 1; i < rows.length; i++) {
var row = rows[i];
if (row.length) {
data.push({skn: row[0]});
}
}
return data;
}
}
return [];
}
async _batchPost(list, url) {
let self = this;
let interval = 50;
... ... @@ -44,7 +118,11 @@ class ProductCache extends Model{
let limit = list.length > (i + 1) * interval ? (i + 1) * interval : list.length;
let datas = list.slice(i * interval, limit);
try {
await self._postApi(datas, url);
let skns = [];
_.forEach(datas, data => {
skns.push(data.skn)
})
await self._postApi(skns, url);
self._broadcast(`进度:${limit}/${list.length}`)
} catch(err) {
self._broadcast(`错误:${err.message}`)
... ... @@ -72,8 +150,10 @@ class ProductCache extends Model{
res.code === 200 ? resolve(res) : reject(res);
})
.catch(function (err) {
console.log(err.response.body);
reject(err.response.body)
if (err.response) {
console.log(err.response.body);
reject(err.response.body)
}
});
})
... ...
/**
*
* @author: chenfeng<feng.chen@yoho.cn>
* @date: 16/11/01
*/
'use strict';
import Model from './model';
class TempProductImport extends Model {
constructor() {
super('temp_product_import');
}
}
export default TempProductImport;
\ No newline at end of file
... ...
No preview for this file type
... ... @@ -9,47 +9,94 @@
import Router from 'koa-router';
import xlsx from 'node-xlsx';
import fs from 'fs';
import _ from 'lodash';
import {
ProductCache
ProductCache,
TempProductImport
} from '../../models'
const r = new Router();
const productCache = {
async query(ctx) {
if (await TempProductImport.count({}) > 0) {
await TempProductImport.remove({});
}
await ctx.render('action/product_cache');
},
async clear(ctx) {
async import(ctx) {
if (await TempProductImport.count({}) > 0) {
await TempProductImport.remove({});
}
let excelFile = ctx.request.body._files && ctx.request.body._files.excelFile;
let type = ctx.request.body.type;
if (excelFile && type) {
if (excelFile) {
let sheets = xlsx.parse(fs.readFileSync(excelFile.path));
if (sheets.length) {
let rows = sheets[0].data;
if (rows.length > 1) {
let data = [];
for (var i = 1; i < rows.length; i++) {
var row = rows[i];
if (row.length) {
data.push(row[0]);
}
}
if (type === '1') {
ProductCache.removePriceCache(data)
} else if (type === '2') {
ProductCache.removeProductCache(data)
}
let data = ProductCache.rendExcel(sheets);
await TempProductImport.insert(data);
let count = await TempProductImport.count({});
ctx.body = {
code: 200,
count
}
return;
}
ctx.body = {
code: 400
}
},
async clear(ctx) {
let type = ctx.request.body.type;
if (await TempProductImport.count({}) > 0) {
let data = await TempProductImport.findAll();
if (type) {
if (type === '1') {
ProductCache.removePriceCache(data)
} else if (type === '2') {
ProductCache.removeProductCache(data)
}
ctx.body = {
code: 200
}
return;
}
} else {
ProductCache._broadcast('请导入商品');
}
ctx.body = {
code: 400
}
},
async downloadProductUrl(ctx) {
if (await TempProductImport.count({}) > 0) {
let data = await TempProductImport.findAll();
let productUrls = await ProductCache.generateProductUrl(data);
let domains = {'PC': 'http://www.yohobuy.com', 'WAP': 'https://m.yohobuy.com'};
let resultData = [];
_.forEach(domains, (value, key) => {
let rows = [['URL']];
_.forEach(productUrls, url => {
rows.push([value + url]);
});
resultData.push({name: key, data: rows});
});
var buffer = xlsx.build(resultData);
ctx.status = 200;
ctx.set('Content-disposition', 'attachment; filename=output.xlsx');
ctx.set('Content-type', 'application/vnd.ms-excel');
ctx.body = buffer;
return;
} else {
ProductCache._broadcast('请导入商品');
}
ctx.body = {
code: 200
code: 400
}
}
}
r.get('/query', productCache.query);
r.post('/import', productCache.import);
r.post('/clear', productCache.clear);
r.get('/product_url', productCache.downloadProductUrl);
export default r;
\ No newline at end of file
... ...
... ... @@ -22,6 +22,7 @@
<div class="operations mb20">
<label class="control-label">导入文件:</label>
<div class="file-upload"></div>
<p class="skn-count"></p>
<select id="selectType" class="form-control input-sm selcet-auto pull-left mr20">
<option value="1">批量变价</option>
<option value="2">其它批量</option>
... ... @@ -31,6 +32,8 @@
</div>
<div class="download">
<a href="/template/product_cache_template.xlsx" class="block">模板文件下载</a>
&nbsp;
<a href="javascript:;" class="block product-url">生成商品url</a>
</div>
</div>
<div class="col-sm-6">
... ... @@ -59,9 +62,8 @@
$(document).on('ready pjax:success', function() {
var fileUpload = $('.file-upload').uploadFile({
url: '/product_cache/clear',
url: '/product_cache/import',
method: 'POST',
autoSubmit: false,
uploadStr: '浏览',
multiple: false,
maxFileCount: 1,
... ... @@ -72,6 +74,9 @@
},
onSuccess: function(files, data, xhr, pd) {
console.log('success')
if(data.code === 200) {
$('.skn-count').text('导入数量:' + data.count).data('count', data.count);
}
fileUpload.reset();
},
onError: function(files, data, xhr, pd) {
... ... @@ -80,16 +85,19 @@
}
})
$('.btn-clear').click(function() {
// var selectType = $('#selectType').val();
// if (selectType) {
// $logs.empty();
// // $.post('/product_cache/clear', {
// // type: selectType
// // }, function(res) {
// // });
// }
fileUpload.startUpload();
var count = $('.skn-count').data('count');
var selectType = $('#selectType').val();
if(count > 0 && selectType) {
$.post('/product_cache/clear', {
type: selectType,
});
}
})
$('.product-url').click(function() {
var count = $('.skn-count').data('count');
if(count > 0) {
window.location.href = '/product_cache/product_url';
}
})
function layoutResize() {
... ...
No preview for this file type
/**
* 签名
* @author: bikai
* @date: 2016/5/6
*/
'use strict';
const _ = require('lodash');
const md5 = require('md5');
const privateKey = {
android: 'fd4ad5fcfa0de589ef238c0e7331b585',
iphone: 'a85bb0674e08986c6b115d5e3a4884fa',
ipad: 'ad9fcda2e679cf9229e37feae2cdcf80',
web: '0ed29744ed318fd28d2c07985d3ba633',
yoho: 'fd4ad5fcsa0de589af23234ks1923ks',
h5: 'fd4ad5fcfa0de589ef238c0e7331b585'
};
/**
* 排序参数
* @param {Object} argument 需要排序的参数对象
* @return {Object} 排序之后的参数对象
*/
const packageSort = argument => {
const newObj = {};
for (const k of Object.keys(argument).sort()) {
newObj[k] = argument[k];
}
return newObj;
};
/**
* 生成签名
* @param {Object} argument 需要签名的数据
* @return {string} 生成的签名字符串
*/
const makeSign = argument => {
const qs = [];
_.forEach(argument, (value, key) => {
value = _.trim(value);
qs.push(`${key}=${value}`);
argument[key] = value;
});
return md5(qs.join('&')).toLowerCase();
};
// 生成API签名,调用后端接口的时候有私钥校验
exports.apiSign = (params, app, appVersion, signExtend) => {
const clientType = params.client_type || app;
appVersion = appVersion || '4.6.0';
signExtend = signExtend || {};
/* eslint-disable */
let sign = packageSort(Object.assign({
client_type: clientType,
private_key: privateKey[clientType],
app_version: appVersion,
os_version: `yohobuy:${app}`,
screen_size: '720x1280',
v: '7'
}, signExtend, params));
/* eslint-enable */
sign = Object.assign(sign, {
client_secret: makeSign(sign) // eslint-disable-line camelcase
});
delete sign.private_key;
return sign;
};
// 检查签名,APP 访问 H5 页面的时候需要检查
exports.checkSign = (params) => {
const clientSecret = params.client_secret; // eslint-disable-line camelcase
let sortedParams;
// 忽略部分参数
delete params.client_secret;
delete params.q;
delete params.debug_data;
delete params['/api'];
params.private_key = privateKey[params.client_type]; // eslint-disable-line camelcase
sortedParams = packageSort(params);
return clientSecret === makeSign(sortedParams);
};
// 检查签名,APP 访问 H5 页面的时候需要检查, 有可能不同于上边的签名方式
exports.webSign = (params) => {
const webPrivateKey = 'yohobuyapp';
return params.key === md5(md5(webPrivateKey) + params.uid);
};
// 生成 token
exports.makeToken = (string) => {
return md5(md5(string + '#@!@#'));
};
// 校验 token
exports.verifyToken = (string, token) => {
return exports.makeToken(string) === token;
};
... ...
0 info it worked if it ends with ok
1 verbose cli [ '/usr/local/bin/node',
1 verbose cli '/Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/.bin/npm',
1 verbose cli 'run',
1 verbose cli 'babel',
1 verbose cli 'app.js' ]
2 info using npm@3.10.9
3 info using node@v6.2.0
4 verbose run-script [ 'prebabel', 'babel', 'postbabel' ]
5 info lifecycle yoho-node-ci@0.0.1~prebabel: yoho-node-ci@0.0.1
6 silly lifecycle yoho-node-ci@0.0.1~prebabel: no script for prebabel, continuing
7 info lifecycle yoho-node-ci@0.0.1~babel: yoho-node-ci@0.0.1
8 verbose lifecycle yoho-node-ci@0.0.1~babel: unsafe-perm in lifecycle true
9 verbose lifecycle yoho-node-ci@0.0.1~babel: PATH: /Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/npm/bin/node-gyp-bin:/Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/.bin:/usr/local/lib/node_modules/npm/bin/node-gyp-bin:/Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/.bin:/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/share/dotnet:/Users/chenfeng/Documents/tools/android-sdk-macosx/platform-tools:/Users/chenfeng/Documents/tools/android-sdk-macosx/tools:/usr/local/nginx/sbin
10 verbose lifecycle yoho-node-ci@0.0.1~babel: CWD: /Users/chenfeng/Documents/source/yoho/yoho-node-ci
11 silly lifecycle yoho-node-ci@0.0.1~babel: Args: [ '-c', 'babel-node "app.js"' ]
12 silly lifecycle yoho-node-ci@0.0.1~babel: Returned: code: 1 signal: null
13 info lifecycle yoho-node-ci@0.0.1~babel: Failed to exec babel script
14 verbose stack Error: yoho-node-ci@0.0.1 babel: `babel-node "app.js"`
14 verbose stack Exit status 1
14 verbose stack at EventEmitter.<anonymous> (/Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/npm/lib/utils/lifecycle.js:255:16)
14 verbose stack at emitTwo (events.js:106:13)
14 verbose stack at EventEmitter.emit (events.js:191:7)
14 verbose stack at ChildProcess.<anonymous> (/Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/npm/lib/utils/spawn.js:40:14)
14 verbose stack at emitTwo (events.js:106:13)
14 verbose stack at ChildProcess.emit (events.js:191:7)
14 verbose stack at maybeClose (internal/child_process.js:850:16)
14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:215:5)
15 verbose pkgid yoho-node-ci@0.0.1
16 verbose cwd /Users/chenfeng/Documents/source/yoho/yoho-node-ci
17 error Darwin 16.1.0
18 error argv "/usr/local/bin/node" "/Users/chenfeng/Documents/source/yoho/yoho-node-ci/node_modules/.bin/npm" "run" "babel" "app.js"
19 error node v6.2.0
20 error npm v3.10.9
21 error code ELIFECYCLE
22 error yoho-node-ci@0.0.1 babel: `babel-node "app.js"`
22 error Exit status 1
23 error Failed at the yoho-node-ci@0.0.1 babel script 'babel-node "app.js"'.
23 error Make sure you have the latest version of node.js and npm installed.
23 error If you do, this is most likely a problem with the yoho-node-ci package,
23 error not with npm itself.
23 error Tell the author that this fails on your system:
23 error babel-node "app.js"
23 error You can get information on how to open an issue for this project with:
23 error npm bugs yoho-node-ci
23 error Or if that isn't available, you can get their info via:
23 error npm owner ls yoho-node-ci
23 error There is likely additional logging output above.
24 verbose exit [ 1, true ]
... ...
No preview for this file type