Authored by 沈志敏

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

# Conflicts:
#	apps/models/index.js
... ... @@ -22,6 +22,17 @@ class ApiCache {
});
}
flushAll() {
this.memcached.flush(err => {
if (err) {
this._log(`flush all fail.` + err.toString())
} else {
this._log(`flush all success`)
}
this.memcached.end();
});
}
clean(key) {
let begin = new Date();
... ... @@ -58,7 +69,7 @@ class ApiCache {
if (_.isArray(response)) {
_.each(response, keyObj => {
count ++ ;
if (keyObj.key && (keyObj.key.indexOf(key1) >= 0 || keyObj.key.indexOf(key2) >= 0 )) {
if (keyObj.key && (keyObj.key.indexOf(key1) >= 0 || keyObj.key.indexOf(key2) >= 0 || keyObj.key.indexOf(key) >= 0)) {
this.delKey(keyObj.key);
} else {
// this._log(`skip ${keyObj.key}`)
... ... @@ -66,7 +77,7 @@ class ApiCache {
});
} else {
count ++;
if (response.key && (response.key.indexOf(key1) >= 0 || response.key.indexOf(key2) >= 0 )) {
if (response.key && (response.key.indexOf(key1) >= 0 || response.key.indexOf(key2) >= 0 || response.key.indexOf(key) >= 0)) {
this.delKey(response.key);
} else {
// this._log(`skip ${response.key}`)
... ...
'use strict';
import Model from './model';
import _ from 'lodash';
const defaultDegrades = [
{
path: '/pc/common/disMyYohoHover',
name: '【禁止】公共头部>>> MY有货鼠标移入显示账户信息'
},
{
path: '/pc/common/removeCartCount',
name: '【移除】公共头部>>> 购物车图标显示的购物车商品数目'
},
{
path: '/pc/common/disSearchAssociation',
name: '【禁止】公共头部>>> 搜索框执行搜索联想'
},
{
path: '/pc/common/disCartHover',
name: '【禁止】公共头部>>> 购物车图标鼠标移入请求且不显示购物车商品列表'
},
{
path: '/pc/brands/disBrandNameHover',
name: '【禁止】品牌一览>>> 品牌名移入显示品牌简介Tip'
},
{
path: '/pc/product/removeRecentView',
name: '【移除】商品列表/商品详情>>> 最近浏览'
},
{
path: '/pc/guang/removeHotTag',
name: '【移除】逛>>> 热门标签'
},
{
path: '/pc/guang/removeAd',
name: '【移除】逛>>> 广告banner'
},
{
path: '/pc/guang/removeItemComment',
name: '【移除】逛>>> 详情页评论'
},
//wap
{
path: '/wap/plustar/removeCollect',
name: '【移除】Plustar>>> 品牌收藏'
},
{
path: '/wap/plustar/removeRelatedPost',
name: '【移除】Plustar>>> 相关资讯'
},
{
path: '/wap/search/removeHotSearch',
name: '【移除】搜索>>> 热门搜索'
},
{
path: '/wap/ucenter/removePrefer',
name: '【移除】个人中心>>> 为您优选'
},
{
path: '/wap/common/removeCartCount',
name: '【移除】公共>>> 购物车icon商品数目'
},
{
path: '/wap/cart/removePrefer',
name: '【移除】购物车>>> 为您优选新品'
}
];
class Degrade extends Model {
constructor() {
super('degrade');
}
async init() {
for (let i of defaultDegrades) {
let count = await this.count({
path: i.path
});
if (count === 0) {
await this.insert(i);
}
}
}
}
export default Degrade;
\ No newline at end of file
... ...
'use strict';
import Model from './model';
class DegradeServer extends Model {
constructor() {
super('degrade_server');
}
}
export default DegradeServer;
\ No newline at end of file
... ...
... ... @@ -12,6 +12,8 @@ import HotfixModel from './hotfix';
import OperationLoggerModel from './operation_logger';
import PageCacheModel from './page_cache';
import MemcachedHostModel from './memcached_host';
import DegradeModel from './degrade';
import DegradeServerModel from './degrade_server';
shelljs.mkdir('-p', config.dbDir);
... ... @@ -26,10 +28,14 @@ const Hotfix = new HotfixModel();
const OperationLogger = new OperationLoggerModel();
const PageCache = new PageCacheModel();
const MemcachedHost = new MemcachedHostModel();
const Degrade = new DegradeModel();
const DegradeServer = new DegradeServerModel();
User.init();
PageCache.init();
Degrade.init();
export {
Server,
Building,
... ... @@ -41,5 +47,7 @@ export {
PageCache,
MemcachedHost,
RestartInfo,
DeleteRestartInfo
DeleteRestartInfo,
Degrade,
DegradeServer
};
\ No newline at end of file
... ...
... ... @@ -51,6 +51,18 @@ const api_cache = {
return ctx.body = {
code: 200
};
},
async flushAll(ctx) {
let hosts = await MemcachedHost.findAll();
_.each(hosts, (h) => {
(new ApiCache(h.host)).flushAll();
});
return ctx.body = {
code: 200
};
}
};
... ... @@ -58,5 +70,6 @@ r.get('/', api_cache.index);
r.post('/host/add', api_cache.addHost);
r.post('/host/del', api_cache.delHost);
r.post('/clean', api_cache.cleanKey);
r.post('/flush', api_cache.flushAll);
export default r;
... ...
'use strict';
import Router from 'koa-router';
import moment from 'moment';
import _ from 'lodash';
import {Degrade, DegradeServer} from '../../models';
import getter from '../../zookeeper/getter';
import setter from '../../zookeeper/setter';
const router = new Router();
const ctl = {
async getServer() {
let server = await DegradeServer.findAll({});
server = _.last(server);
if (server) {
return `${server.ip}:${server.port}`;
} else {
return 'localhost:2181';
}
},
async index (ctx) {
let count = await DegradeServer.count({});
let render;
if (count) {
let serverPath = await ctl.getServer();
let degrades = await Degrade.findAll();
for (let i of degrades) {
i.checked = await getter(serverPath, i.path);
}
let pc = _.filter(degrades, o => _.startsWith(o.path, '/pc'));
let wap = _.filter(degrades, o => _.startsWith(o.path, '/wap'));
let serverSplit = serverPath.split(':');
render = {
ip: serverSplit[0],
port: serverSplit[1],
render: {
pc: pc,
wap: wap
}
}
}
await ctx.render('action/degrade', render);
},
async server(ctx) {
let ip = ctx.request.body.ip;
let port = ctx.request.body.port;
let serverCount = await DegradeServer.count({});
// keep one server
if (serverCount) {
let serverConfig = await DegradeServer.findAll({});
let id = _.last(serverConfig)._id; // get the latest item
await DegradeServer.update({
_id: id
}, {
$set: {
ip: ip,
port: port
}
});
} else {
await DegradeServer.insert({
ip: ip,
port: port
});
}
ctx.body = {
code: 200,
message: `${serverCount ? 'update' : 'new'} server success`
};
},
async setter(ctx) {
let {checked, id} = ctx.query;
let theDegrade = await Degrade.findById(id);
let path = theDegrade.path;
let serverPath = await ctl.getServer();
await setter(serverPath, path, checked.toString());
ctx.body = {
code: 200,
message: 'update success'
};
}
};
router.get('/', ctl.index);
router.post('/server', ctl.server);
router.get('/setter', ctl.setter);
export default router;
\ No newline at end of file
... ...
... ... @@ -11,6 +11,7 @@ import hotfix from './actions/hotfix';
import operationLog from './actions/operation_log';
import pageCahe from './actions/page_cache';
import apiCache from './actions/api_cache';
import degrade from './actions/degrade';
const noAuth = new Router();
const base = new Router();
... ... @@ -39,6 +40,8 @@ export default function (app) {
base.use('/page_cache', pageCahe.routes(), pageCahe.allowedMethods());
base.use('/api_cache', apiCache.routes(), apiCache.allowedMethods());
base.use('/degrade', degrade.routes(), degrade.allowedMethods());
base.use('', index.routes(), index.allowedMethods());
app.use(base.routes(), base.allowedMethods());
... ...
... ... @@ -19,6 +19,9 @@
<div class="row">
<div class="col-sm-4 col-md-3">
<div>
<button class="btn btn-danger" id="flush-btn">Flush All</button>
</div>
<h4 class="md-title mb5">memcached列表</h4>
<div class="host-list">
... ... @@ -29,7 +32,9 @@
<div class="mb20"></div>
<button class="btn btn-success" id="add-btn">添加</button>
<div>
<button class="btn btn-success" id="add-btn">添加</button>
</div>
<br>
... ... @@ -102,6 +107,18 @@
});
});
$('#flush-btn').click(function(){
var i = layer.confirm('确定清楚所有缓存吗?', {
btn: ['确定', '取消']
}, function() {
$.post('/api_cache/flush', function(ret) {
if (ret.code === 200) {
layer.close(i);
}
});
});
});
function layoutResize() {
$('.yoho-log-dark').height($('body').height() - 340);
}
... ...
<style>
.degrade-page {
padding: 20px;
}
.degrade-tab li {
cursor: pointer;
}
.pc-degrade,
.wap-degrade {
list-style: none;
padding: 20px;
}
.err-tip {
color: #a94442;
}
</style>
<div class="degrade-page">
<div class="panel panel-info">
<div class="panel-heading">zookeeper server</div>
<div class="panel-body">
<div class="form-inline">
<div class="form-group">
<label for="server-ip">Server IP</label>
<input id="server-ip" class="form-control" type="text" placeholder="input server ip" value="{{ip}}">
</div>
<div class="form-group">
<label for="server-port">Server Port</label>
<input id="server-port" class="form-control" type="text" placeholder="input server port" value="{{port}}">
</div>
<span id="server-sure" class="btn btn-default">确定</span>
<span id="err-tip" class="err-tip hide">
<i class="glyphicon glyphicon-remove-sign"></i>
小哥,填错了吧
</span>
</div>
</div>
</div>
<div class="panel panel-danger">
<div class="panel-heading">degrade point</div>
<div id="pjax-container" class="panel-body">
{{#render}}
<ul id="degrade-tab" class="nav nav-tabs degrade-tab" role="tablist">
<li role="presentation" class="active">
<a>PC</a>
</li>
<li role="presentation">
<a>WAP</a>
</li>
</ul>
<ul class="pc-degrade degrade-content">
{{#each pc}}
<li data-id="{{_id}}">
<div class="checkbox">
<label>
<input type="checkbox"{{#if checked}} checked{{/if}}>
{{name}}
</label>
</div>
</li>
{{/each}}
</ul>
<ul class="wap-degrade degrade-content hide">
{{#each wap}}
<li data-id="{{_id}}">
<div class="checkbox">
<label>
<input type="checkbox"{{#if checked}} checked{{/if}}>
{{name}}
</label>
</div>
</li>
{{/each}}
</ul>
{{^}}
Waitting for connecting to server...
{{/render}}
</div>
</div>
</div>
<script>
$(function() {
var $ip = $('#server-ip');
var $port = $('#server-port');
var $tip = $('#err-tip');
function validateIP(ip) {
if (ip === 'localhost' ||
/^(0|[1-9]?|1\d\d?|2[0-4]\d|25[0-5])\.(0|[1-9]?|1\d\d?|2[0-4]\d|25[0-5])\.(0|[1-9]?|1\d\d?|2[0-4]\d|25[0-5])\.(0|[1-9]?|1\d\d?|2[0-4]\d|25[0-5])$/.test(ip))
{
$ip.closest('.form-group').removeClass('has-error');
return true;
}
$ip.closest('.form-group').addClass('has-error');
return false;
}
function validatePort(port) {
if (/^[1-9]\d*$/.test(port) && +port >= 1 && +port <= 65535) {
$port.closest('.form-group').removeClass('has-error');
return true;
}
$port.closest('.form-group').addClass('has-error');
return false;
}
// server
$('#server-sure').click(function() {
var ip = $.trim($ip.val());
var port = $.trim($port.val());
if (!validateIP(ip) || !validatePort(port)) {
$tip.removeClass('hide');
return;
}
$tip.addClass('hide');
var serverPrompt = confirm('小伙砸,你确认修改zookeeper的server到:' + ip + ':' + port + '吗?')
if (!serverPrompt) {
return;
}
$.ajax({
type: 'POST',
url: '/degrade/server',
data: {
ip: ip,
port: port
}
}).then(function(data) {
if (data.code === 200) {
$.pjax.reload('#pjax-container');
}
});
});
$('#degrade-tab').on('click', 'li', function() {
var $this = $(this);
if ($this.hasClass('active')) {
return;
}
$('li', $('#degrade-tab')).toggleClass('active');
var index = $this.index();
if (index === 0) {
//PC active
$('.pc-degrade').removeClass('hide');
$('.wap-degrade').addClass('hide');
} else {
// wap active
$('.wap-degrade').removeClass('hide');
$('.pc-degrade').addClass('hide');
}
});
// change
$('.degrade-content input[type="checkbox"]').change(function() {
var $checkbox = $(this),
$li = $checkbox.closest('li');
var checked = $checkbox.prop('checked');
var id = $li.data('id');
$.ajax({
url: '/degrade/setter',
data: {
checked: checked,
id: id
}
});
});
})
</script>
\ No newline at end of file
... ...
... ... @@ -41,6 +41,7 @@
</ul>
</li>
{{/if}}
<li><a href="/degrade"><i class="fa fa-hand-o-down"></i> <span>降级配置</span></a></li>
</ul>
</div>
\ No newline at end of file
... ...
'use strict';
import zookeeper from 'node-zookeeper-client';
module.exports = (server, path) => {
const client = zookeeper.createClient(server);
client.once('connected', () => {
client.mkdirp(path, new Buffer('false'), (err, path) => {
if (err) {
console.log('Node %s create err', path, err.stack);
} else {
console.log('Node %s is created', path);
}
client.close();
});
});
client.connect();
};
\ No newline at end of file
... ...
'usu strict';
import _ from 'lodash';
import zookeeper from 'node-zookeeper-client';
import creator from './creator';
const getter = (server, client, path, resolve, reject) => {
client.exists(path, (err, stat) => {
if (err) {
console.log('path %s exits error', path, err.stack);
resolve(false);
return;
}
if (stat) {
client.getData(
path,
(err, data, stat) => {
if (err) {
console.log('Got path %s data error', path, err.stack);
}
client.close();
resolve((data && data.toString('utf8') === 'true') ? true : false);
}
)
} else {
// 不存在的路径
console.log('no path %s, we will create it with value "false" automatic', path);
client.close();
// create path
creator(server, path);
resolve(false);
}
});
};
module.exports = (server, path) => new Promise((resolve, reject) => {
const client = zookeeper.createClient(server);
client.once('connected', () => {
getter(server, client, path, resolve, reject);
});
client.connect();
});
... ...
'usu strict';
import _ from 'lodash';
import zookeeper from 'node-zookeeper-client';
module.exports = (server, path, val) => new Promise((resolve, reject) => {
const client = zookeeper.createClient(server);
client.once('connected', function () {
client.setData(path, new Buffer(val.toString()), function(err, data, stat) {
console.log('path %s data change to', path, val);
resolve();
client.close();
});
});
client.connect();
});
... ...
... ... @@ -55,6 +55,7 @@
"moment": "^2.13.0",
"nedb": "^1.8.0",
"nedb-promise": "^2.0.0",
"node-zookeeper-client": "^0.2.2",
"qn": "^1.3.0",
"qs": "^6.2.0",
"request-promise": "^4.1.1",
... ...