Authored by 毕凯

Merge branch 'feature/crawlerTaskLog' into 'master'

Feature/crawler task log



See merge request !22
@@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
3 const Router = require('koa-router'); 3 const Router = require('koa-router');
4 const _ = require('lodash'); 4 const _ = require('lodash');
5 const md5 = require('md5'); 5 const md5 = require('md5');
  6 +const moment = require('moment');
6 const pager = require('../utils/pager'); 7 const pager = require('../utils/pager');
7 const seoModel = require('../models/seoModel'); 8 const seoModel = require('../models/seoModel');
8 9
@@ -14,6 +15,18 @@ const TYPE_LIST = [ @@ -14,6 +15,18 @@ const TYPE_LIST = [
14 {name: '店铺', type: 'shop', lt: 'ShopId'}, 15 {name: '店铺', type: 'shop', lt: 'ShopId'},
15 {name: '链接', type: 'url', lt: 'URL'} 16 {name: '链接', type: 'url', lt: 'URL'}
16 ]; 17 ];
  18 +const JOB_TASK = [
  19 + {
  20 + k: 'global:yoho:task:minute',
  21 + v: '1 */10 * * * *',
  22 + info: '每10分钟执行一次'
  23 + },
  24 + {
  25 + k: 'global:yoho:task:day',
  26 + v: '30 30 1 * * *',
  27 + info: '每天的1点30分30s执行一次'
  28 + }
  29 +];
17 30
18 const tdk = { 31 const tdk = {
19 // tdk 列表 32 // tdk 列表
@@ -459,7 +472,6 @@ const category = { @@ -459,7 +472,6 @@ const category = {
459 } 472 }
460 } 473 }
461 474
462 -  
463 const friendLink = { 475 const friendLink = {
464 index: async(ctx, next) => { 476 index: async(ctx, next) => {
465 let type = ctx.query.type || 'text'; 477 let type = ctx.query.type || 'text';
@@ -624,6 +636,85 @@ const friendLink = { @@ -624,6 +636,85 @@ const friendLink = {
624 }, 636 },
625 }; 637 };
626 638
  639 +const task = {
  640 + index: async(ctx, next) => {
  641 + let tasks = await ctx.redis.multi(_.map(JOB_TASK, job => ['hvals', job.k])).execAsync().then(rdata => {
  642 + let result = [];
  643 +
  644 + _.each(rdata, (items, rk) => {
  645 + _.each(items, item => {
  646 + result.push(Object.assign(JSON.parse(item), {
  647 + job_name: JOB_TASK[rk].info,
  648 + type: rk,
  649 + }));
  650 + })
  651 + });
  652 +
  653 + return result;
  654 + });
  655 +
  656 + await ctx.render('action/seo_task', {
  657 + title: '定时任务管理',
  658 + job: JOB_TASK,
  659 + tasks: tasks
  660 + });
  661 + },
  662 + add: async(ctx, next) => {
  663 + let params = ctx.request.body || {};
  664 + params.type = parseInt(`0${params.type}`, 10);
  665 +
  666 + let task = _.get(JOB_TASK, `[${params.type}]`, {});
  667 +
  668 + if (!task.k) {
  669 + return ctx.body = {code: 400, message: 'invalid type...'};
  670 + }
  671 +
  672 + let code = md5(params.url || '');
  673 +
  674 + return ctx.redis.hsetAsync(task.k, code, JSON.stringify({
  675 + url: params.url,
  676 + time: params.time || 1000,
  677 + name: params.name,
  678 + code: code
  679 + })).then(status => {
  680 + return ctx.body = {code: 200, message: 'success...'};
  681 + });
  682 + },
  683 + del: async(ctx, next) => {
  684 + let params = ctx.request.body || {};
  685 + let data = JSON.parse(_.get(ctx.request.body, 'data', '[]'));
  686 +
  687 + ctx.redis.multi(
  688 + _.map(data, job => ['hdel', JOB_TASK[parseInt(`0${job.tpe}`)].k || '', job.code || ''])
  689 + ).execAsync().then(status => {
  690 + return ctx.body = {
  691 + code: 200,
  692 + message: 'success',
  693 + data: ''
  694 + };
  695 + });
  696 + },
  697 + log: async(ctx, next) => {
  698 + let params = ctx.request.body || {};
  699 + let key = params.code || '';
  700 + let start = params.start || 0;
  701 + let stop = params.stop || 100;
  702 +
  703 + return ctx.redis.lrangeAsync(`global:yoho:task:log:${key}`, start, stop).then(rdata => {
  704 + let data = _.map(rdata, item => {
  705 + item = JSON.parse(item || '{}');
  706 + return Object.assign(item, {time: moment(item.time).format('YYYY-MM-DD HH:mm:ss')});
  707 + });
  708 +
  709 + return ctx.body = {
  710 + code: 200,
  711 + message: 'success',
  712 + data: data
  713 + };
  714 + });
  715 + },
  716 +};
  717 +
627 r.get('/', tdk.index); 718 r.get('/', tdk.index);
628 r.get('/tdk', tdk.index); 719 r.get('/tdk', tdk.index);
629 r.post('/tdk/add', tdk.add); 720 r.post('/tdk/add', tdk.add);
@@ -650,4 +741,10 @@ r.post('/friendlink/add', friendLink.add); @@ -650,4 +741,10 @@ r.post('/friendlink/add', friendLink.add);
650 r.post('/friendlink/edit', friendLink.edit); 741 r.post('/friendlink/edit', friendLink.edit);
651 r.post('/friendlink/delete', friendLink.delete); 742 r.post('/friendlink/delete', friendLink.delete);
652 743
  744 +// 定时任务管理
  745 +r.get('/task', task.index);
  746 +r.post('/task/add', task.add);
  747 +r.post('/task/del', task.del);
  748 +r.post('/task/log', task.log);
  749 +
653 module.exports = r; 750 module.exports = r;
@@ -148,4 +148,4 @@ @@ -148,4 +148,4 @@
148 console.log('connect fail'); 148 console.log('connect fail');
149 }); 149 });
150 }); 150 });
151 -</script>  
  151 +</script>
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 <a class="btn btn-default deleteAll">删除</a> 21 <a class="btn btn-default deleteAll">删除</a>
22 <a class="btn btn-default" href="/keywords/add">增加</a> 22 <a class="btn btn-default" href="/keywords/add">增加</a>
23 <a class="btn btn-default" href="javascript:sendUrl();">推送百度</a> 23 <a class="btn btn-default" href="javascript:sendUrl();">推送百度</a>
24 - <a class="btn btn-default rand-words" href="javascript:void(0);">关键词随机关联</a> 24 + <!--<a class="btn btn-default rand-words" href="javascript:void(0);">关键词随机关联</a>-->
25 <div class="input-append pull-right"> 25 <div class="input-append pull-right">
26 <form id="query-form" action="/keywords/expand" class="query-form" method="get"> 26 <form id="query-form" action="/keywords/expand" class="query-form" method="get">
27 <!-- <div class="btn-group"> 27 <!-- <div class="btn-group">
@@ -272,4 +272,4 @@ @@ -272,4 +272,4 @@
272 alert(data.message); 272 alert(data.message);
273 }) 273 })
274 } 274 }
275 -</script>  
  275 +</script>
@@ -264,4 +264,4 @@ @@ -264,4 +264,4 @@
264 } 264 }
265 }); 265 });
266 }); 266 });
267 -</script>  
  267 +</script>
@@ -141,4 +141,4 @@ @@ -141,4 +141,4 @@
141 }); 141 });
142 142
143 </script> 143 </script>
144 -{{/if}}  
  144 +{{/if}}
@@ -196,7 +196,7 @@ @@ -196,7 +196,7 @@
196 {{/each}} 196 {{/each}}
197 {{#unless linkList}} 197 {{#unless linkList}}
198 <tr> 198 <tr>
199 - <td class="text-center" colspan="6">暂无数据</td> 199 + <td class="text-center" colspan="5">暂无数据</td>
200 </tr> 200 </tr>
201 {{/unless}} 201 {{/unless}}
202 </tbody> 202 </tbody>
  1 +<style type="text/css">
  2 + .task-add {
  3 + padding-top: 20px;
  4 + }
  5 + .task-add ul {
  6 + list-style: none;
  7 + }
  8 + .task-add ul li {
  9 + height: 40px;
  10 + }
  11 + .task-add select,
  12 + .task-add input {
  13 + width: 70%;
  14 + height: 30px;
  15 + padding: 5px;
  16 + }
  17 + .task-add .job-select {
  18 + margin-left: -3px;
  19 + }
  20 +
  21 + .task-log {
  22 + padding: 20px;
  23 + }
  24 + .task-log table, .task-log table tr th,.task-log table tr td {
  25 + border: none;
  26 + }
  27 + .task-log tbody tr {
  28 + height: 40px;
  29 + }
  30 + .task-log table {
  31 + border-collapse: separate;
  32 + border-spacing: 0 4px;
  33 + }
  34 + .task-log tbody tr:nth-child(odd) td:first-child {
  35 + border-left: 2px solid #00C656;
  36 + }
  37 + .task-log tbody tr:nth-child(even) td:first-child {
  38 + border-left: 2px solid #4F95E8;
  39 + }
  40 + .task-log tbody tr.empty td {
  41 + border: none !important;
  42 + }
  43 +</style>
  44 +<div class="pageheader">
  45 + <div class="media">
  46 + <div class="pageicon pull-left">
  47 + <i class="fa fa-th-list"></i>
  48 + </div>
  49 + <div class="media-body">
  50 + <ul class="breadcrumb">
  51 + <li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
  52 + <li>{{title}}</li>
  53 + </ul>
  54 + <h4>{{title}}</h4>
  55 + </div>
  56 + </div>
  57 + <!-- media -->
  58 +</div>
  59 +<!-- pageheader -->
  60 +
  61 +<div class="contentpanel task-panel" style="padding-bottom:0;">
  62 + <div class="panel panel-default">
  63 + <div class="panel-body">
  64 + <button class="btn btn-default btn-add" >增加</button>
  65 + <button class="btn btn-default delete-all" type="submit">删除</button>
  66 + </div>
  67 + </div><!--/panel-default-->
  68 +
  69 + <div class="panel panel-default">
  70 + <div class="panel-body">
  71 + <table class="table table-striped table-bordered tab-task">
  72 + <thead>
  73 + <tr>
  74 + <th width="10%">&nbsp;&nbsp;&nbsp;
  75 + <label><input type="checkbox" style="vertical-align: text-bottom;" onclick="javascript:$('.tab-task input[type=checkbox]').attr('checked', this.checked);" />&nbsp;全选</label>
  76 + </th>
  77 + <th class="text-center" width="20%">任务名称</th>
  78 + <th class="text-center" width="30%">链接</th>
  79 + <th class="text-center" width="10%">延迟时间</th>
  80 + <th class="text-center" width="15%">执行频率</th>
  81 + <th class="text-center" width="15%">操作</th>
  82 + </tr>
  83 + </thead>
  84 +
  85 + <tbody>
  86 + {{#each tasks}}
  87 + <tr data-code="{{code}}" data-type="{{type}}">
  88 + <td>&nbsp;&nbsp;&nbsp;&nbsp;<input type="checkbox" /></td>
  89 + <td class="text-center td-name" >{{name}}</td>
  90 + <td class="text-center" >{{url}}</td>
  91 + <td class="text-center" >{{time}}</td>
  92 + <td class="text-center" >{{job_name}}</td>
  93 + <td class="text-center">
  94 + <a href="javascript:;" class="log-btn">执行日志</a>
  95 + <a href="javascript:;" class="del-btn">删除</a>
  96 + </td>
  97 + </tr>
  98 + {{/each}}
  99 + {{#unless tasks}}
  100 + <tr>
  101 + <td class="text-center" colspan="6">暂无数据</td>
  102 + </tr>
  103 + {{/unless}}
  104 + </tbody>
  105 + </table>
  106 + {{# pager}}
  107 + <div class="text-right">
  108 + {{#if pages}}
  109 + <ul class="pagination">
  110 + {{# prePage}}
  111 + <li><a href="{{url}}">上一页</a></li>
  112 + {{/ prePage}}
  113 + {{# pages}}
  114 + <li class="{{#unless url}}disabled {{/unless}}{{#if cur}}active{{/if}}"><a {{#if url}}href="{{url}}"{{^}}href="javascript:;"{{/if}}>{{num}}</a></li>
  115 + {{/ pages}}
  116 + {{# nextPage}}
  117 + <li><a href="{{url}}">下一页</a></li>
  118 + {{/ nextPage}}
  119 + </ul>
  120 + {{/if}}
  121 + </div>
  122 + {{/ pager}}
  123 + </div>
  124 + </div><!--/panel-default-->
  125 +</div><!--/contentpanel-->
  126 +
  127 +<script id="task-add-tpl" type="text/tmpl">
  128 +<div class="task-add">
  129 + <ul>
  130 + <li>
  131 + 执行频率:
  132 + <select class="job-select" data-placeholder="请选择..." >
  133 + <option value="-1">请选择...</option>
  134 + {{#each job}}
  135 + <option value="{{@index}}">{{info}}</option>
  136 + {{/each}}
  137 + </select>
  138 + </li>
  139 + <li>任务名称:<input type="text" class="job-name" placeholder='请输入任务名称...'></li>
  140 + <li>执行链接:<input type="text" class="job-link" placeholder='请输入执行链接...'></li>
  141 + <li>延迟时间:<input type="text" class="job-time" placeholder='请输入延迟时间...'></li>
  142 + </ul>
  143 + <p><span class="err-tip"></span></p>
  144 +</div>
  145 +</script>
  146 +
  147 +<script id="task-log-tpl" type="text/tmpl">
  148 +<div class="task-log">
  149 + <table class="table table-striped table-bordered">
  150 + <thead>
  151 + <tr>
  152 + <th class="text-center" width="20%">执行时间</th>
  153 + <th class="text-center" width="20%">状态码</th>
  154 + <th class="text-center" width="50%">执行结果</th>
  155 + <th class="text-right" width="10%">
  156 + <a href="javascript:;" class="btn btn-default btn-refresh-log">刷新</a>
  157 + </th>
  158 + </tr>
  159 + </thead>
  160 + <tbody></tbody>
  161 + </table>
  162 +</div>
  163 +</script>
  164 +
  165 +<script>
  166 + var tasks = {
  167 + init: function() {
  168 + var that = this;
  169 + var taskData = {};
  170 +
  171 + this.$base = $('.contentpanel');
  172 +
  173 + this.$base.on('click', '.btn-add', function() {
  174 + that.add();
  175 + });
  176 +
  177 + // 单个删除
  178 + this.$base.on('click', '.del-btn', function() {
  179 + let $tr = $(this).closest('tr');
  180 + that.del([$tr.data()], () => $tr.remove());
  181 + });
  182 +
  183 + // 日志
  184 + this.$base.on('click', '.log-btn', function() {
  185 + let $tr = $(this).closest('tr');
  186 +
  187 + taskData = $tr.data();
  188 +
  189 + return that.getTaskLog(taskData).then(rdata => {
  190 + layer.open({
  191 + type: 1,
  192 + area: ['800px', '500px'],
  193 + title: '《' + $tr.find('.td-name').text() + '》执行明细',
  194 + content: that.taskLogTpl
  195 + });
  196 +
  197 + that.rendLog(rdata.data);
  198 + });
  199 + });
  200 +
  201 + // 日志刷新
  202 + $('body').on('click', '.btn-refresh-log', function() {
  203 + var layerIndex = layer.load(1);
  204 +
  205 + return that.getTaskLog(taskData).then(rdata => {
  206 + setTimeout(function() {
  207 + layer.close(layerIndex);
  208 + that.rendLog(rdata.data);
  209 + }, 2000);
  210 + });
  211 + });
  212 +
  213 + // 删除全部
  214 + this.$base.on('click', '.delete-all', function() {
  215 + let arr = [];
  216 +
  217 + $('.tab-task input[type=checkbox]:checked').each(function(){
  218 + arr.push($(this).closest('tr').data());
  219 + });
  220 +
  221 + that.del(arr, () => history.go(0));
  222 + });
  223 +
  224 + this.taskAddTpl = $('#task-add-tpl').html();
  225 + this.taskLogTpl = $('#task-log-tpl').html();
  226 + },
  227 + add: function() {
  228 + var that = this;
  229 + var layerIndex = layer.open({
  230 + type: 1,
  231 + area: ['450px'],
  232 + title: '任务添加',
  233 + content: that.taskAddTpl,
  234 + btn: '添加',
  235 + yes: function() {
  236 + var data = {
  237 + type: parseInt($('.job-select').val(), 10),
  238 + name: $('.job-name').val(),
  239 + url: $('.job-link').val(),
  240 + time: parseInt($('.job-time').val() || 0),
  241 + };
  242 +
  243 + if (data.type < 0) {
  244 + return that.tips('请选择执行频率...', '.job-select');
  245 + } else if (data.name === '') {
  246 + return that.tips('任务名称必填...', '.job-name');
  247 + } else if (data.url === '' || data.url.indexOf('http') === -1) {
  248 + return that.tips('请输入正确的执行链接...', '.job-link');
  249 + }
  250 +
  251 + return that.ajax({
  252 + url: '/seo/task/add',
  253 + type: 'POST',
  254 + data: data,
  255 + }).then(rdata => {
  256 + if (rdata.code !== 200) {
  257 + return alert(rdata.message);
  258 + }
  259 +
  260 + layer.close(layerIndex);
  261 +
  262 + history.go(0)
  263 + });
  264 + }
  265 + });
  266 + },
  267 + del: function(data, callback) {
  268 + if (data.length <= 0) {
  269 + return false;
  270 + }
  271 +
  272 + var that = this;
  273 + var layerIndex = layer.confirm('您确定要删除吗!!!', {
  274 + btn: ['确定', '取消']
  275 + }, function() {
  276 + return that.ajax({
  277 + url: '/seo/task/del',
  278 + type: 'POST',
  279 + data: {data: JSON.stringify(data)},
  280 + }).then(res => {
  281 + callback();
  282 + layer.close(layerIndex);
  283 + return res;
  284 + });
  285 + });
  286 + },
  287 + rendLog: function(rdata) {
  288 + var $tbody = $('.task-log tbody');
  289 + $tbody.empty();
  290 +
  291 + if (rdata.length <= 0) {
  292 + $tbody.append(`<tr class="empty">
  293 + <td class="text-center" colspan="4">暂无数据</td>
  294 + </tr>`);
  295 + } else {
  296 + $.each(rdata, (key, item)=> {
  297 + $tbody.append(`<tr>
  298 + <td class="text-center" >${item.time}</td>
  299 + <td class="text-center" >${item.code}</td>
  300 + <td class="text-center" colspan="2">${item.message}</td>
  301 + </tr>`);
  302 + });
  303 + }
  304 + },
  305 + getTaskLog: function(data) {
  306 + return this.ajax({
  307 + url: '/seo/task/log',
  308 + type: 'POST',
  309 + data: data,
  310 + });
  311 + },
  312 + ajax: function(options) {
  313 + return Promise.resolve($.ajax(options));
  314 + },
  315 + tips: function(tip, dom) {
  316 + return layer.tips(tip, dom, {
  317 + tips: [1, '#000'],
  318 + time: 2000
  319 + });
  320 + }
  321 + };
  322 +
  323 +$(function() {
  324 + tasks.init();
  325 +});
  326 +</script>
@@ -77,6 +77,7 @@ @@ -77,6 +77,7 @@
77 <li><a href="/seo/tdk"><span>TDK管理</span></a></li> 77 <li><a href="/seo/tdk"><span>TDK管理</span></a></li>
78 <li><a href="/seo/category"><span>品类描述管理</span></a></li> 78 <li><a href="/seo/category"><span>品类描述管理</span></a></li>
79 <li><a href="/seo/friendlink"><span>友链管理</span></a></li> 79 <li><a href="/seo/friendlink"><span>友链管理</span></a></li>
  80 + <li><a href="/seo/task"><span>定时任务管理</span></a></li>
80 </ul> 81 </ul>
81 </li> 82 </li>
82 83