Authored by xuqi

Merge branch 'feature/page-cache' of http://git.yoho.cn/OPENTECH/yoho-node-ci in…

…to feature/page-cache
  1 +/**
  2 + *
  3 + * @author: chenfeng<feng.chen@yoho.cn>
  4 + * @date: 16/10/19
  5 + */
  6 +
  7 +'use strict';
  8 +import Model from './model';
  9 +import utils from 'utility';
  10 +import rp from 'request-promise';
  11 +import ws from '../../lib/ws';
  12 +
  13 +class CdnCache extends Model {
  14 + constructor() {
  15 + super('cdn_page');
  16 + }
  17 + async removeCache(queryUris, objectType) {
  18 + let self = this;
  19 + self._requestApi({
  20 + Action: 'RefreshObjectCaches',
  21 + ObjectPath: queryUris,
  22 + ObjectType: objectType
  23 + })
  24 + }
  25 + async _requestApi(params) {
  26 + let self = this;
  27 + let cdns = await self.findAll();
  28 +
  29 + if (!cdns.length) {
  30 + return;
  31 + }
  32 + params = Object.assign(params, {
  33 + Format: 'JSON',
  34 + Version: '2014-11-11',
  35 + AccessKeyId: cdns[0].keyId,
  36 + SignatureMethod: 'HMAC-SHA1',
  37 + TimeStamp: self._getUtcTime(new Date()),
  38 + SignatureVersion: '1.0',
  39 + SignatureNonce: parseInt(Math.random() * 100000, 10) + ''
  40 + });
  41 + let sign = self._sign(params, cdns[0].keySecret);
  42 + params.Signature = sign;
  43 + rp({
  44 + uri: `${cdns[0].scheme}://${cdns[0].cdnApi}`,
  45 + qs: params,
  46 + json: true
  47 + })
  48 + .then(function (res) {
  49 + self._broadcast(`清理提交成功,RefreshTaskId:${res.RefreshTaskId}`)
  50 + })
  51 + .catch(function (err) {
  52 + self._broadcast(`清理失败:${err.response.body}`)
  53 + });
  54 +
  55 + }
  56 + _getUtcTime(date) {
  57 + let year = date.getUTCFullYear();
  58 + let month = date.getUTCMonth() + 1;
  59 + let day = date.getUTCDate() < 10 ? '0' + date.getUTCDate() : date.getUTCDate();
  60 + let hour = date.getUTCHours() < 10 ? '0' + date.getUTCHours() : date.getUTCHours();
  61 + let minute = date.getUTCMinutes() < 10 ? '0' + date.getUTCMinutes() : date.getUTCMinutes();
  62 + let second = date.getUTCSeconds() < 10 ? '0' + date.getUTCSeconds() : date.getUTCSeconds();
  63 + month = month < 10 ? '0' + month : month;
  64 +
  65 + return `${year}-${month}-${day}T${hour}:${minute}:${second}Z`
  66 + }
  67 + _sign(params, keySecret) {
  68 + let data = {};
  69 + Object.keys(params).sort().forEach(k => data[k]=params[k]);
  70 + let signString = '';
  71 + Object.keys(data).forEach(k => {
  72 + data[k] = encodeURIComponent(data[k])
  73 + signString += `&${k}=${data[k]}`;
  74 + });
  75 + if (signString) {
  76 + signString = `GET&%2F&${encodeURIComponent(signString.substring(1, signString.length))}`;
  77 + let sign = utils.hmac('sha1', keySecret + '&', signString);
  78 + return sign;
  79 + }
  80 + return '';
  81 + }
  82 + _broadcast(message) {
  83 + console.log(message)
  84 + ws.broadcast(`/cdn_cache/log`, {
  85 + message: message
  86 + });
  87 + }
  88 + async init() {
  89 + let count = await this.count({});
  90 + if (count === 0) {
  91 + await this.insert({
  92 + scheme: 'https',
  93 + cdnApi: 'cdn.aliyuncs.com',
  94 + keyId: 'Mt6m3xVdWddj8bDe',
  95 + keySecret: 'ExHpmJJ7ayJEEMz2LUffKRe3ehMGDs'
  96 + })
  97 + }
  98 + }
  99 +}
  100 +
  101 +export default CdnCache;
@@ -11,6 +11,8 @@ import UserModel from './user'; @@ -11,6 +11,8 @@ import UserModel from './user';
11 import HotfixModel from './hotfix'; 11 import HotfixModel from './hotfix';
12 import OperationLoggerModel from './operation_logger'; 12 import OperationLoggerModel from './operation_logger';
13 import PageCacheModel from './page_cache'; 13 import PageCacheModel from './page_cache';
  14 +import CdnCacheModel from './cdn_cache';
  15 +import ProductCacheModel from './product_cache';
14 import MemcachedHostModel from './memcached_host'; 16 import MemcachedHostModel from './memcached_host';
15 import DegradeModel from './degrade'; 17 import DegradeModel from './degrade';
16 import DegradeServerModel from './degrade_server'; 18 import DegradeServerModel from './degrade_server';
@@ -27,12 +29,16 @@ const User = new UserModel(); @@ -27,12 +29,16 @@ const User = new UserModel();
27 const Hotfix = new HotfixModel(); 29 const Hotfix = new HotfixModel();
28 const OperationLogger = new OperationLoggerModel(); 30 const OperationLogger = new OperationLoggerModel();
29 const PageCache = new PageCacheModel(); 31 const PageCache = new PageCacheModel();
  32 +const CdnCache = new CdnCacheModel();
  33 +const ProductCache = new ProductCacheModel();
30 const MemcachedHost = new MemcachedHostModel(); 34 const MemcachedHost = new MemcachedHostModel();
31 const Degrade = new DegradeModel(); 35 const Degrade = new DegradeModel();
32 const DegradeServer = new DegradeServerModel(); 36 const DegradeServer = new DegradeServerModel();
33 37
34 User.init(); 38 User.init();
35 PageCache.init(); 39 PageCache.init();
  40 +CdnCache.init();
  41 +ProductCache.init();
36 42
37 Degrade.init(); 43 Degrade.init();
38 44
@@ -45,6 +51,8 @@ export { @@ -45,6 +51,8 @@ export {
45 Hotfix, 51 Hotfix,
46 OperationLogger, 52 OperationLogger,
47 PageCache, 53 PageCache,
  54 + CdnCache,
  55 + ProductCache,
48 MemcachedHost, 56 MemcachedHost,
49 RestartInfo, 57 RestartInfo,
50 DeleteRestartInfo, 58 DeleteRestartInfo,
@@ -28,7 +28,7 @@ class PageCahe extends Model { @@ -28,7 +28,7 @@ class PageCahe extends Model {
28 * @param {[array]} keys [key列表] 28 * @param {[array]} keys [key列表]
29 * @param {[string]} storeTableName [pc和wap的标识] 29 * @param {[string]} storeTableName [pc和wap的标识]
30 */ 30 */
31 - async removeCache(queryUris, storeTableName) { 31 + async removeCache(queryUris, storeTableName, serverType) {
32 let self = this; 32 let self = this;
33 let queryUriList = queryUris.split('\n'); 33 let queryUriList = queryUris.split('\n');
34 34
@@ -49,8 +49,7 @@ class PageCahe extends Model { @@ -49,8 +49,7 @@ class PageCahe extends Model {
49 // } 49 // }
50 50
51 } 51 }
52 - let servers = await self.findAll();  
53 - 52 + let servers = await self.find({server: { $in: serverType }});
54 self._broadcast(`共${servers.length}nginx准备清理..`) 53 self._broadcast(`共${servers.length}nginx准备清理..`)
55 for (let i = 0; i < servers.length; i++) { 54 for (let i = 0; i < servers.length; i++) {
56 try { 55 try {
@@ -67,9 +66,9 @@ class PageCahe extends Model { @@ -67,9 +66,9 @@ class PageCahe extends Model {
67 * [全量清理缓存] 66 * [全量清理缓存]
68 * @param {[string]} storeTableName [pc和wap的标识] 67 * @param {[string]} storeTableName [pc和wap的标识]
69 */ 68 */
70 - async removeAllCache(storeTableName) { 69 + async removeAllCache(storeTableName, serverType) {
71 let self = this; 70 let self = this;
72 - let servers = await self.findAll(); 71 + let servers = await self.find({server: { $in: serverType }});
73 72
74 self._broadcast(`共${servers.length}nginx准备清理`) 73 self._broadcast(`共${servers.length}nginx准备清理`)
75 for (let i = 0; i < servers.length; i++) { 74 for (let i = 0; i < servers.length; i++) {
@@ -222,15 +221,26 @@ class PageCahe extends Model { @@ -222,15 +221,26 @@ class PageCahe extends Model {
222 // password: '1', 221 // password: '1',
223 // port: 22, 222 // port: 22,
224 // tag: 'nginx2', 223 // tag: 'nginx2',
225 - // cachepath: '/usr/local/nginx' 224 + // cachepath: '/usr/local/nginx',
  225 + // server: 'qCloud'
  226 + // });
  227 + // await this.insert({
  228 + // host: '127.0.0.1',
  229 + // username: 'chenfeng',
  230 + // password: '1',
  231 + // port: 22,
  232 + // tag: 'nginx2',
  233 + // cachepath: '/usr/local/nginx',
  234 + // server: 'AWS'
226 // }); 235 // });
227 // await this.insert({ 236 // await this.insert({
228 // host: '10.66.1.2', 237 // host: '10.66.1.2',
229 - // username: 'node', 238 + // username: 'www',
230 // password: 'yoho9646', 239 // password: 'yoho9646',
231 // port: 22, 240 // port: 22,
232 // tag: 'nginx2', 241 // tag: 'nginx2',
233 - // cachepath: '/usr/local/openresty/nginx' 242 + // cachepath: '/usr/local/openresty/nginx',
  243 + // server: 'qCloud'
234 // }); 244 // });
235 await this.insert({ 245 await this.insert({
236 host: '10.66.1.3', 246 host: '10.66.1.3',
@@ -238,32 +248,54 @@ class PageCahe extends Model { @@ -238,32 +248,54 @@ class PageCahe extends Model {
238 password: 'yoho9646', 248 password: 'yoho9646',
239 port: 22, 249 port: 22,
240 tag: 'nginx3', 250 tag: 'nginx3',
241 - cachepath: '/usr/local/openresty/nginx' 251 + cachepath: '/usr/local/openresty/nginx',
  252 + server: 'qCloud'
242 }); 253 });
243 // await this.insert({ 254 // await this.insert({
244 // host: '10.66.1.15', 255 // host: '10.66.1.15',
245 - // username: 'node', 256 + // username: 'www',
246 // password: 'yoho9646', 257 // password: 'yoho9646',
247 // port: 22, 258 // port: 22,
248 // tag: 'nginx15', 259 // tag: 'nginx15',
249 - // cachepath: '/usr/local/openresty/nginx' 260 + // cachepath: '/usr/local/openresty/nginx',
  261 + // server: 'qCloud'
250 // }); 262 // });
251 // await this.insert({ 263 // await this.insert({
252 // host: '10.66.1.84', 264 // host: '10.66.1.84',
253 - // username: 'node', 265 + // username: 'www',
254 // password: 'yoho9646', 266 // password: 'yoho9646',
255 // port: 22, 267 // port: 22,
256 // tag: 'nginx84', 268 // tag: 'nginx84',
257 - // cachepath: '/usr/local/openresty/nginx' 269 + // cachepath: '/usr/local/openresty/nginx',
  270 + // server: 'qCloud'
258 // }); 271 // });
259 // await this.insert({ 272 // await this.insert({
260 // host: '10.66.1.97', 273 // host: '10.66.1.97',
261 - // username: 'node', 274 + // username: 'www',
262 // password: 'yoho9646', 275 // password: 'yoho9646',
263 // port: 22, 276 // port: 22,
264 // tag: 'nginx97', 277 // tag: 'nginx97',
265 - // cachepath: '/usr/local/openresty/nginx' 278 + // cachepath: '/usr/local/openresty/nginx',
  279 + // server: 'qCloud'
266 // }); 280 // });
  281 + await this.insert({
  282 + host: '172.31.23.111',
  283 + username: 'www',
  284 + password: 'yoho9646',
  285 + port: 22,
  286 + tag: 'aws1',
  287 + cachepath: '/usr/local/openresty/nginx',
  288 + server: 'AWS'
  289 + });
  290 + await this.insert({
  291 + host: '172.31.21.139',
  292 + username: 'www',
  293 + password: 'yoho9646',
  294 + port: 22,
  295 + tag: 'aws2',
  296 + cachepath: '/usr/local/openresty/nginx',
  297 + server: 'AWS'
  298 + });
267 } 299 }
268 } 300 }
269 } 301 }
  1 +/**
  2 + *
  3 + * @author: chenfeng<feng.chen@yoho.cn>
  4 + * @date: 16/10/20
  5 + */
  6 +
  7 +import Model from './model';
  8 +import rp from 'request-promise';
  9 +import ws from '../../lib/ws';
  10 +
  11 +class ProductCache extends Model{
  12 + constructor() {
  13 + super('product_cache');
  14 + }
  15 + async removePriceCache(list) {
  16 + let self = this;
  17 + let apis = await self.findAll();
  18 +
  19 + if (!apis.length) {
  20 + return;
  21 + }
  22 + let url = apis[0].priceCacheApi;
  23 + await self._batchPost(list, url);
  24 + }
  25 + async removeProductCache(list) {
  26 + let self = this;
  27 + let apis = await self.findAll();
  28 +
  29 + if (!apis.length) {
  30 + return;
  31 + }
  32 + let url = apis[0].productCacheApi;
  33 + await self._batchPost(list, url);
  34 + }
  35 + async _batchPost(list, url) {
  36 + let self = this;
  37 + let interval = 2;
  38 + return new Promise(async (resolve, reject) => {
  39 + let tick = parseInt(list.length / interval, 10) + (list.length % interval > 0 ? 1 : 0);
  40 + let post = async (i, len) => {
  41 + if (i < len) {
  42 + let limit = list.length > (i + 1) * interval ? (i + 1) * interval : list.length;
  43 + let datas = list.slice(i * interval, limit);
  44 + try {
  45 + await self._postApi(datas, url);
  46 + self._broadcast(`进度:${limit}/${list.length}`)
  47 + } catch(err) {
  48 + self._broadcast(`错误:${err.message}`)
  49 + }
  50 + setTimeout(async () => {
  51 + await post(++i, tick);
  52 + }, 500);
  53 + } else {
  54 + resolve();
  55 + }
  56 + }
  57 + await post(0, tick);
  58 + });
  59 + }
  60 + async _postApi(data, url) {
  61 + return new Promise((resolve, reject) => {
  62 + rp({
  63 + method: 'POST',
  64 + uri: url,
  65 + body: data,
  66 + json: true
  67 + })
  68 + .then(function (res) {
  69 + res.code === 200 ? resolve(res) : reject(res);
  70 + })
  71 + .catch(function (err) {
  72 + console.log(err.response.body);
  73 + reject(err.response.body)
  74 + });
  75 + })
  76 +
  77 + }
  78 + _broadcast(message) {
  79 + console.log(message)
  80 + ws.broadcast(`/product_cache/log`, {
  81 + message: message
  82 + });
  83 + }
  84 + async init() {
  85 + let count = await this.count({});
  86 +
  87 + if (count === 0) {
  88 + await this.insert({
  89 + priceCacheApi: 'http://service-test3.yohops.com:9999/erp/clear/batch/productPriceCache',
  90 + productCacheApi: 'http://service-test3.yohops.com:9999/erp/clear/batch/productCache'
  91 + });
  92 + }
  93 + }
  94 +}
  95 +export default ProductCache;
  1 +/**
  2 + *
  3 + * @author: chenfeng<feng.chen@yoho.cn>
  4 + * @date: 16/10/19
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +import Router from 'koa-router';
  10 +import {
  11 + CdnCache
  12 +} from '../../models'
  13 +
  14 +const r = new Router();
  15 +
  16 +const cdnCache = {
  17 + async query(ctx) {
  18 + let date = {
  19 + typeList: [{
  20 + name: '文件',
  21 + typeName: 'File',
  22 + }, {
  23 + name: '目录',
  24 + typeName: 'Directory',
  25 + }]
  26 + }
  27 + await ctx.render('action/cdn_cache', date);
  28 + },
  29 + async clear(ctx) {
  30 + let queryUris = ctx.request.body.query_uri;
  31 + let objectType = ctx.request.body.object_type;
  32 + if (queryUris.trim() && objectType) {
  33 + CdnCache.removeCache(queryUris, objectType);
  34 + }
  35 + return ctx.body = {
  36 + code: 200
  37 + };
  38 + }
  39 +}
  40 +r.get('/query', cdnCache.query);
  41 +r.post('/clear', cdnCache.clear);
  42 +
  43 +export default r;
@@ -24,6 +24,11 @@ const pageCahe = { @@ -24,6 +24,11 @@ const pageCahe = {
24 tableName: 'ngx_cache_wap', 24 tableName: 'ngx_cache_wap',
25 name: 'Wap' 25 name: 'Wap'
26 }], 26 }],
  27 + serverList: [{
  28 + name: 'AWS'
  29 + }, {
  30 + name: 'qCloud'
  31 + }],
27 count: 0 32 count: 0
28 }; 33 };
29 await ctx.render('action/page_cache', data); 34 await ctx.render('action/page_cache', data);
@@ -31,16 +36,25 @@ const pageCahe = { @@ -31,16 +36,25 @@ const pageCahe = {
31 async clear(ctx) { 36 async clear(ctx) {
32 let queryUris = ctx.request.body.query_uri; 37 let queryUris = ctx.request.body.query_uri;
33 let storeTableName = ctx.request.body.table_name; 38 let storeTableName = ctx.request.body.table_name;
34 - if (queryUris && storeTableName) {  
35 - await PageCache.removeCache(queryUris, storeTableName); 39 + let serverType = ctx.request.body.server;
  40 + let servers = serverType.split(',').filter(server => server);
  41 + if (queryUris && storeTableName && servers.length) {
  42 + PageCache.removeCache(queryUris, storeTableName, servers);
36 } 43 }
37 - 44 + return ctx.body = {
  45 + code: 200
  46 + };
38 }, 47 },
39 async clearAll(ctx) { 48 async clearAll(ctx) {
40 let storeTableName = ctx.request.body.table_name; 49 let storeTableName = ctx.request.body.table_name;
41 - if (storeTableName) {  
42 - await PageCache.removeAllCache(storeTableName); 50 + let serverType = ctx.request.body.server;
  51 + let servers = serverType.split(',').filter(server => server);
  52 + if (storeTableName && servers.length) {
  53 + PageCache.removeAllCache(storeTableName, servers);
43 } 54 }
  55 + return ctx.body = {
  56 + code: 200
  57 + };
44 } 58 }
45 } 59 }
46 r.get('/query', pageCahe.query); 60 r.get('/query', pageCahe.query);
  1 +/**
  2 + *
  3 + * @author: chenfeng<feng.chen@yoho.cn>
  4 + * @date: 16/10/19
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +import Router from 'koa-router';
  10 +import {
  11 + ProductCache
  12 +} from '../../models'
  13 +
  14 +const r = new Router();
  15 +
  16 +const productCache = {
  17 + async query(ctx) {
  18 + await ctx.render('action/product_cache');
  19 + },
  20 + async clear(ctx) {
  21 + let type = ctx.request.body.type;
  22 + if (type) {
  23 + ProductCache.removePriceCache([51068877, 51068893, 51117073])
  24 + }
  25 + return ctx.body = {
  26 + code: 200
  27 + };
  28 + }
  29 +}
  30 +
  31 +r.get('/query', productCache.query);
  32 +r.post('/clear', productCache.clear);
  33 +
  34 +export default r;
@@ -9,7 +9,9 @@ import monitor from './actions/monitor'; @@ -9,7 +9,9 @@ import monitor from './actions/monitor';
9 import users from './actions/users'; 9 import users from './actions/users';
10 import hotfix from './actions/hotfix'; 10 import hotfix from './actions/hotfix';
11 import operationLog from './actions/operation_log'; 11 import operationLog from './actions/operation_log';
12 -import pageCahe from './actions/page_cache'; 12 +import pageCache from './actions/page_cache';
  13 +import cdnCache from './actions/cdn_cache';
  14 +import productCache from './actions/product_cache';
13 import apiCache from './actions/api_cache'; 15 import apiCache from './actions/api_cache';
14 import degrade from './actions/degrade'; 16 import degrade from './actions/degrade';
15 17
@@ -37,9 +39,10 @@ export default function (app) { @@ -37,9 +39,10 @@ export default function (app) {
37 base.use('/users', users.routes(), users.allowedMethods()); 39 base.use('/users', users.routes(), users.allowedMethods());
38 base.use('/hotfix', hotfix.routes(), hotfix.allowedMethods()); 40 base.use('/hotfix', hotfix.routes(), hotfix.allowedMethods());
39 base.use('/operation', operationLog.routes(), operationLog.allowedMethods()); 41 base.use('/operation', operationLog.routes(), operationLog.allowedMethods());
40 - base.use('/page_cache', pageCahe.routes(), pageCahe.allowedMethods()); 42 + base.use('/page_cache', pageCache.routes(), pageCache.allowedMethods());
  43 + base.use('/cdn_cache', cdnCache.routes(), cdnCache.allowedMethods());
  44 + base.use('/product_cache', productCache.routes(), productCache.allowedMethods());
41 base.use('/api_cache', apiCache.routes(), apiCache.allowedMethods()); 45 base.use('/api_cache', apiCache.routes(), apiCache.allowedMethods());
42 -  
43 base.use('/degrade', degrade.routes(), degrade.allowedMethods()); 46 base.use('/degrade', degrade.routes(), degrade.allowedMethods());
44 47
45 base.use('', index.routes(), index.allowedMethods()); 48 base.use('', index.routes(), index.allowedMethods());
  1 +<div class="pageheader">
  2 + <div class="media">
  3 + <div class="pageicon pull-left">
  4 + <i class="fa fa-th-list"></i>
  5 + </div>
  6 + <div class="media-body">
  7 + <ul class="breadcrumb">
  8 + <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
  9 + <li><a href="/servers">缓存管理</a></li>
  10 + </ul>
  11 + <h4>清理CdnCahe缓存</h4>
  12 + </div>
  13 + </div>
  14 + <!-- media -->
  15 +</div>
  16 +
  17 +<div class="contentpanel">
  18 + <div class="row cache-panel">
  19 + <div class="panel panel-default">
  20 + <div class="panel-body">
  21 + <div class="col-sm-6">
  22 + <div class="operations mb20">
  23 +
  24 + </div>
  25 + <div class="query">
  26 + <textarea name="" id="uri" cols="30" rows="20" class="form-control" placeholder="支持多条换行输入"></textarea>
  27 + </div>
  28 + </div>
  29 + <div class="col-sm-6">
  30 + <div class="panel">
  31 + <div class="panel-heading">
  32 + <h4 class="panel-title">日志</h4>
  33 + </div><!-- panel-heading -->
  34 + <div class="panel-body yoho-log-dark">
  35 + <div class="results-list ">
  36 +
  37 + </div><!-- results-list -->
  38 + </div><!-- panel-body -->
  39 + </div><!-- panel -->
  40 + </div>
  41 + </div>
  42 + <div class="panel-footer">
  43 + <select id="objectType" class="form-control input-sm selcet-auto pull-left mr20">
  44 + {{#each typeList}}
  45 + <option value="{{typeName}}">{{name}}</option>
  46 + {{/each}}
  47 + </select>
  48 + <button class="btn btn-warning btn-clear pull-left">清除</button>
  49 + <div class="result pull-left ml20"></div>
  50 + </div>
  51 + </div>
  52 + </div>
  53 +</div>
  54 +
  55 +<script>
  56 + var posing = false;
  57 + $(document).on('ready pjax:success', function() {
  58 + $('.btn-clear').click(function() {
  59 + var typeName = $('#objectType').val();
  60 + var uri = $('#uri').val();
  61 + if (uri && typeName) {
  62 + $logs.empty();
  63 + $.post('/cdn_cache/clear', {
  64 + query_uri: uri,
  65 + object_type: typeName
  66 + }, function(res) {
  67 + });
  68 + }
  69 + })
  70 +
  71 + function layoutResize() {
  72 + $('.yoho-log-dark').height($('body').height() - 450);
  73 + }
  74 +
  75 + $(window).resize(function() {
  76 + layoutResize();
  77 + });
  78 +
  79 + layoutResize();
  80 + var $logs = $('.yoho-log-dark .results-list');
  81 + var $dark = $('.yoho-log-dark');
  82 + function appendLog(message) {
  83 + var html = '<p><span class="message">- ' + message + '</span></p>';
  84 + $logs.append(html);
  85 + $dark.scrollTop($logs[0].scrollHeight);
  86 + }
  87 +
  88 + var ws = io();
  89 + ws.on('connect', function() {
  90 + ws.on('/cdn_cache/log', function(data) {
  91 + appendLog(data.message)
  92 + });
  93 + });
  94 +
  95 + });
  96 +</script>
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
8 <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li> 8 <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
9 <li><a href="/servers">缓存管理</a></li> 9 <li><a href="/servers">缓存管理</a></li>
10 </ul> 10 </ul>
11 - <h4>清理缓存</h4> 11 + <h4>清理PageCahe缓存</h4>
12 </div> 12 </div>
13 </div> 13 </div>
14 <!-- media --> 14 <!-- media -->
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 23
24 </div> 24 </div>
25 <div class="query"> 25 <div class="query">
26 - <textarea name="" id="uri" cols="30" rows="20" class="form-control" placeholder="支持多条换行输入">http://m.yohobuy.com</textarea> 26 + <textarea name="" id="uri" cols="30" rows="20" class="form-control" placeholder="支持多条换行输入"></textarea>
27 </div> 27 </div>
28 </div> 28 </div>
29 <div class="col-sm-6"> 29 <div class="col-sm-6">
@@ -39,7 +39,17 @@ @@ -39,7 +39,17 @@
39 </div><!-- panel --> 39 </div><!-- panel -->
40 </div> 40 </div>
41 </div> 41 </div>
42 - <div class="panel-footer"> 42 + <div class="panel-footer ">
  43 + <div class="ckbox ckbox-primary pull-left mr20">
  44 + <input type="checkbox" id="chk_aws" value="AWS" name="server" checked="checked">
  45 + <label for="chk_aws">AWS</label>
  46 + </div>
  47 +
  48 + <div class="ckbox ckbox-primary pull-left mr20">
  49 + <input type="checkbox" value="qCloud" id="chk_qcloud" name="server" checked="checked">
  50 + <label for="chk_qcloud">qCloud</label>
  51 + </div>
  52 +
43 <select id="stores" class="form-control input-sm selcet-auto pull-left mr20"> 53 <select id="stores" class="form-control input-sm selcet-auto pull-left mr20">
44 {{#each storeList}} 54 {{#each storeList}}
45 <option value="{{tableName}}">{{name}}</option> 55 <option value="{{tableName}}">{{name}}</option>
@@ -56,49 +66,34 @@ @@ -56,49 +66,34 @@
56 <script> 66 <script>
57 var posing = false; 67 var posing = false;
58 $(document).on('ready pjax:success', function() { 68 $(document).on('ready pjax:success', function() {
59 - $('.btn-success').click(function() {  
60 - if (posing) {  
61 - return;  
62 - }  
63 - posing = true;  
64 - $('.page-count>span').text('查询中..');  
65 - $('.btn-search').addClass('disabled');  
66 - var tableName = $('#stores').val();  
67 - var uri = $('#uri').val();  
68 - if (uri && tableName) {  
69 - $.post('/page_cache/search', {  
70 - query_uri: uri,  
71 - table_name: tableName  
72 - }, function (res) {  
73 - posing = false;  
74 - $('.btn-search').removeClass('disabled');  
75 - if (res.code === 200) {  
76 - $('.page-count>span').text(res.data);  
77 - } else {  
78 - $('.page-count>span').text('查询失败');  
79 - }  
80 -  
81 - })  
82 - }  
83 - });  
84 $('.btn-clear').click(function() { 69 $('.btn-clear').click(function() {
  70 + var servers = "";
  71 + $(':checkbox[name="server"]:checked').each(function() {
  72 + servers += $(this).val() + ',';
  73 + })
85 var tableName = $('#stores').val(); 74 var tableName = $('#stores').val();
86 var uri = $('#uri').val(); 75 var uri = $('#uri').val();
87 - if (uri && tableName) { 76 + if (uri && tableName && servers) {
88 $logs.empty(); 77 $logs.empty();
89 $.post('/page_cache/clear', { 78 $.post('/page_cache/clear', {
90 query_uri: uri, 79 query_uri: uri,
91 - table_name: tableName 80 + table_name: tableName,
  81 + server: servers
92 }, function(res) { 82 }, function(res) {
93 }); 83 });
94 } 84 }
95 }) 85 })
96 $('.btn-all-clear').click(function() { 86 $('.btn-all-clear').click(function() {
  87 + var servers = "";
  88 + $(':checkbox[name="server"]:checked').each(function() {
  89 + servers += $(this).val() + ',';
  90 + })
97 var tableName = $('#stores').val(); 91 var tableName = $('#stores').val();
98 - if (tableName) { 92 + if (tableName && servers) {
99 $logs.empty(); 93 $logs.empty();
100 $.post('/page_cache/clear/all', { 94 $.post('/page_cache/clear/all', {
101 - table_name: tableName 95 + table_name: tableName,
  96 + server: servers
102 }, function(res) { 97 }, function(res) {
103 }); 98 });
104 } 99 }
  1 +<div class="pageheader">
  2 + <div class="media">
  3 + <div class="pageicon pull-left">
  4 + <i class="fa fa-th-list"></i>
  5 + </div>
  6 + <div class="media-body">
  7 + <ul class="breadcrumb">
  8 + <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
  9 + <li><a href="/servers">缓存管理</a></li>
  10 + </ul>
  11 + <h4>清理商品缓存</h4>
  12 + </div>
  13 + </div>
  14 + <!-- media -->
  15 +</div>
  16 +
  17 +<div class="contentpanel">
  18 + <div class="row cache-panel">
  19 + <div class="panel panel-default">
  20 + <div class="panel-body">
  21 + <div class="col-sm-6">
  22 + <div class="operations mb20">
  23 +
  24 + </div>
  25 + <div class="query">
  26 + <textarea name="" id="uri" cols="30" rows="20" class="form-control" placeholder="支持多条换行输入"></textarea>
  27 + </div>
  28 + </div>
  29 + <div class="col-sm-6">
  30 + <div class="panel">
  31 + <div class="panel-heading">
  32 + <h4 class="panel-title">日志</h4>
  33 + </div><!-- panel-heading -->
  34 + <div class="panel-body yoho-log-dark">
  35 + <div class="results-list ">
  36 +
  37 + </div><!-- results-list -->
  38 + </div><!-- panel-body -->
  39 + </div><!-- panel -->
  40 + </div>
  41 + </div>
  42 + <div class="panel-footer">
  43 + <select id="selectType" class="form-control input-sm selcet-auto pull-left mr20">
  44 + <option value="1">批量变价</option>
  45 + <option value="2">其它批量</option>
  46 + </select>
  47 + <button class="btn btn-warning btn-clear pull-left">清除</button>
  48 + <div class="result pull-left ml20"></div>
  49 + </div>
  50 + </div>
  51 + </div>
  52 +</div>
  53 +
  54 +<script>
  55 + var posing = false;
  56 + $(document).on('ready pjax:success', function() {
  57 + $('.btn-clear').click(function() {
  58 + var selectType = $('#selectType').val();
  59 + if (selectType) {
  60 + $logs.empty();
  61 + $.post('/product_cache/clear', {
  62 + type: selectType
  63 + }, function(res) {
  64 + });
  65 + }
  66 + })
  67 +
  68 + function layoutResize() {
  69 + $('.yoho-log-dark').height($('body').height() - 450);
  70 + }
  71 +
  72 + $(window).resize(function() {
  73 + layoutResize();
  74 + });
  75 +
  76 + layoutResize();
  77 + var $logs = $('.yoho-log-dark .results-list');
  78 + var $dark = $('.yoho-log-dark');
  79 + function appendLog(message) {
  80 + var html = '<p><span class="message">- ' + message + '</span></p>';
  81 + $logs.append(html);
  82 + $dark.scrollTop($logs[0].scrollHeight);
  83 + }
  84 +
  85 + var ws = io();
  86 + ws.on('connect', function() {
  87 + ws.on('/product_cache/log', function(data) {
  88 + appendLog(data.message)
  89 + });
  90 + });
  91 +
  92 + });
  93 +</script>
@@ -75,14 +75,20 @@ @@ -75,14 +75,20 @@
75 <span class="label label-serstatus"></span> 75 <span class="label label-serstatus"></span>
76 </div> 76 </div>
77 <div class="clearfix mt5"> 77 <div class="clearfix mt5">
78 - <div class="col-xs-3 project-env" data-env="test"> 78 + <div class="col-xs-4 project-env" data-env="test">
79 <h5 class="md-title mt10">当前状态</h5> 79 <h5 class="md-title mt10">当前状态</h5>
  80 + </div>
  81 + <div class="col-xs-4">
  82 + <h5 class="md-title mt10">进程状态</h5>
  83 + </div>
  84 + </div>
  85 + <div class="clearfix mt5">
  86 + <div class="col-xs-4 project-env" data-env="test">
80 <span class="label label-success deploy-log-btn" data-host="{{host}}"><i 87 <span class="label label-success deploy-log-btn" data-host="{{host}}"><i
81 class="fa fa-spinner fa-spin fa-fw margin-bottom"></i> <b>{{#if info}}{{info.state}}{{^}} 88 class="fa fa-spinner fa-spin fa-fw margin-bottom"></i> <b>{{#if info}}{{info.state}}{{^}}
82 未知部署{{/if}}</b></span> 89 未知部署{{/if}}</b></span>
83 </div> 90 </div>
84 - <div class="col-xs-3">  
85 - <h5 class="md-title mt10">进程状态</h5> 91 + <div class="col-xs-4">
86 <span class="label label-status"></span> 92 <span class="label label-status"></span>
87 </div> 93 </div>
88 </div> 94 </div>
@@ -28,8 +28,9 @@ @@ -28,8 +28,9 @@
28 <li class="parent"><a href=""><i class="fa fa-history"></i> <span>缓存管理</span></a> 28 <li class="parent"><a href=""><i class="fa fa-history"></i> <span>缓存管理</span></a>
29 <ul class="children"> 29 <ul class="children">
30 <li><a href="/page_cache/query">PageCahe清理</a></li> 30 <li><a href="/page_cache/query">PageCahe清理</a></li>
  31 + {{!-- <li><a href="/cdn_cache/query">CDN清理</a></li>
  32 + <li><a href="/product_cache/query">商品清理</a></li> --}}
31 <li><a href="/api_cache">ApiCahe清理</a></li> 33 <li><a href="/api_cache">ApiCahe清理</a></li>
32 - {{!-- <li><a href="/cdn_cache/query">CDN清理</a></li> --}}  
33 </ul> 34 </ul>
34 </li> 35 </li>
35 {{#if is_master}} 36 {{#if is_master}}
@@ -62,7 +62,8 @@ @@ -62,7 +62,8 @@
62 "shelljs": "^0.7.0", 62 "shelljs": "^0.7.0",
63 "socket.io": "^1.4.6", 63 "socket.io": "^1.4.6",
64 "ssh2": "^0.5.0", 64 "ssh2": "^0.5.0",
65 - "tar": "^2.2.1" 65 + "tar": "^2.2.1",
  66 + "utility": "^1.8.0"
66 }, 67 },
67 "devDependencies": { 68 "devDependencies": {
68 "ava": "^0.14.0", 69 "ava": "^0.14.0",