diff --git a/app/controller/topic.js b/app/controller/topic.js index b5a92afb..864791c3 100644 --- a/app/controller/topic.js +++ b/app/controller/topic.js @@ -2,7 +2,6 @@ const Controller = require('egg').Controller; const _ = require('lodash'); -// const validator = require('validator'); const path = require('path'); const fs = require('fs'); const awaitWriteStream = require('await-stream-ready').write; @@ -102,60 +101,48 @@ class TopicController extends Controller { async put() { const { ctx, service } = this; const { tabs } = this.config; - const title = ctx.request.body.title.trim(); - const tab = ctx.request.body.tab.trim(); - const content = ctx.request.body.t_content.trim(); + const { body } = ctx.request; // 得到所有的 tab, e.g. ['ask', 'share', ..] - const allTabs = tabs.map(function(tPair) { - return tPair[0]; - }); - - // 验证 - let editError; - if (title === '') { - editError = '标题不能是空的。'; - } else if (title.length < 5 || title.length > 100) { - editError = '标题字数太多或太少。'; - } else if (!tab || allTabs.indexOf(tab) === -1) { - editError = '必须选择一个版块。'; - } else if (content === '') { - editError = '内容不可为空。'; - } - // END 验证 - - if (editError) { - ctx.status = 422; - await ctx.render('topic/edit', { - edit_error: editError, - title, - content, - tabs, - }); - return; - } + const allTabs = tabs.map(tPair => tPair[0]); + + // 使用 egg_validate 验证 + // TODO: 此处可以优化,将所有使用egg_validate的rules集中管理,避免即时新建对象 + const RULE_CREATE = { + title: { + type: 'string', + max: 100, + min: 5, + }, + content: { + type: 'string', + }, + tab: { + type: 'enum', + values: allTabs, + }, + }; + ctx.validate(RULE_CREATE, ctx.request.body); // 储存新主题帖 const topic = await service.topic.newAndSave( - title, - content, - tab, + body.title, + body.content, + body.tab, ctx.user._id ); // 发帖用户增加积分,增加发表主题数量 await service.user.incrementScoreAndReplyCount(topic.author_id, 5, 1); - ctx.redirect('/topic/' + topic._id); - // 通知被@的用户 await service.at.sendMessageToMentionUsers( - content, + body.content, topic._id, ctx.user._id ); - await ctx.redirect('/topic/' + topic._id); + ctx.redirect('/topic/' + topic._id); } /** @@ -198,9 +185,7 @@ class TopicController extends Controller { const { ctx, service, config } = this; const topic_id = ctx.params.tid; - let title = ctx.request.body.title; - let tab = ctx.request.body.tab; - let content = ctx.request.body.t_content; + let { title, tab, content } = ctx.request.body; const { topic } = await service.topic.getTopicById(topic_id); if (!topic) { diff --git a/app/middleware/limit.js b/app/middleware/limit.js deleted file mode 100644 index 00192762..00000000 --- a/app/middleware/limit.js +++ /dev/null @@ -1,64 +0,0 @@ -// 'use strict'; -// -// const config = require('../config'); -// const cache = require('../common/cache'); -// const moment = require('moment'); -// -// const SEPARATOR = '^_^@T_T'; -// -// const makePerDayLimiter = function(identityName, identityFn) { -// return function(name, limitCount, options) { -// /* -// options.showJson = true 表示调用来自API并返回结构化数据;否则表示调用来自前段并渲染错误页面 -// */ -// return function(req, res, next) { -// const identity = identityFn(req); -// const YYYYMMDD = moment().format('YYYYMMDD'); -// const key = YYYYMMDD + -// SEPARATOR + -// identityName + -// SEPARATOR + -// name + -// SEPARATOR + -// identity; -// -// cache.get(key, function(err, count) { -// if (err) { -// return next(err); -// } -// count = count || 0; -// if (count < limitCount) { -// count += 1; -// cache.set(key, count, 60 * 60 * 24); -// res.set('X-RateLimit-Limit', limitCount); -// res.set('X-RateLimit-Remaining', limitCount - count); -// next(); -// } else { -// res.status(403); -// if (options.showJson) { -// res.send({ -// success: false, -// error_msg: '频率限制:当前操作每天可以进行 ' + limitCount + ' 次', -// }); -// } else { -// res.render('notify/notify', { -// error: '频率限制:当前操作每天可以进行 ' + limitCount + ' 次', -// }); -// } -// } -// }); -// }; -// }; -// }; -// -// exports.peruserperday = makePerDayLimiter('peruserperday', function(req) { -// return (req.user || req.session.user).loginname; -// }); -// -// exports.peripperday = makePerDayLimiter('peripperday', function(req) { -// const realIP = req.get('x-real-ip'); -// if (!realIP) { -// throw new Error('should provice `x-real-ip` header'); -// } -// return realIP; -// }); diff --git a/app/middleware/topic_per_day_limit.js b/app/middleware/topic_per_day_limit.js new file mode 100644 index 00000000..cd611244 --- /dev/null +++ b/app/middleware/topic_per_day_limit.js @@ -0,0 +1,29 @@ +'use strict'; +const moment = require('moment'); + +module.exports = ({ perDayPerUserLimitCount = 10 }) => { + + return async function(ctx, next) { + const { user, service } = ctx; + const YYYYMMDD = moment().format('YYYYMMDD'); + const key = `topics_count_${user._id}_${YYYYMMDD}`; + + let todayTopicsCount = (await service.cache.get(key)) || 0; + if (todayTopicsCount >= perDayPerUserLimitCount) { + ctx.status = 403; + await ctx.render('notify/notify', + { error: `今天的话题发布数量已达到限制(${perDayPerUserLimitCount})` }); + return; + } + + await next(); + + if (ctx.status === 302) { + // 新建话题成功 + todayTopicsCount += 1; + await service.cache.incr(key, 60 * 60 * 24); + ctx.set('X-RateLimit-Limit', perDayPerUserLimitCount); + ctx.set('X-RateLimit-Remaining', perDayPerUserLimitCount - todayTopicsCount); + } + }; +}; diff --git a/app/router.js b/app/router.js index 77140894..f500e070 100644 --- a/app/router.js +++ b/app/router.js @@ -10,6 +10,7 @@ module.exports = app => { const userRequired = middleware.userRequired(); const adminRequired = middleware.adminRequired(); + const topicPerDayLimit = middleware.topicPerDayLimit(config.topic); // home page router.get('/', site.index); @@ -78,9 +79,7 @@ module.exports = app => { router.post('/topic/:tid/delete', userRequired, topic.delete); // // 保存新建的文章 - // router.post('/topic/create', userRequired, limit.peruserperday('create_topic', config.create_post_per_day, { showJson: false }), topic.put); - - router.post('/topic/create', userRequired, topic.put); + router.post('/topic/create', userRequired, topicPerDayLimit, topic.put); router.post('/topic/:tid/edit', userRequired, topic.update); router.post('/topic/collect', userRequired, topic.collect); // 关注某话题 diff --git a/app/view/topic/edit.html b/app/view/topic/edit.html index 5ca34ae9..dbb0466c 100644 --- a/app/view/topic/edit.html +++ b/app/view/topic/edit.html @@ -53,7 +53,7 @@