├── app ├── apidoc │ ├── define │ │ ├── upload.js │ │ ├── common.js │ │ ├── user.js │ │ ├── category.js │ │ └── article.js │ └── api │ │ ├── upload.js │ │ ├── category.js │ │ ├── article.js │ │ └── user.js ├── public │ └── default │ │ └── defaultlogo.png ├── services │ ├── index.js │ ├── category.js │ ├── base.js │ ├── article.js │ └── users.js ├── middlewares │ ├── not-find.js │ ├── index.js │ ├── log.js │ ├── verify-token.js │ └── res-extend.js ├── controllers │ ├── index.js │ ├── base.js │ ├── test.js │ ├── category.js │ ├── users.js │ └── article.js ├── models │ ├── index.js │ ├── article.js │ ├── category.js │ ├── role.js │ ├── right.js │ └── users.js ├── routes │ ├── user.js │ ├── article.js │ ├── category.js │ └── index.js └── myutil │ ├── index.js │ ├── upload.js │ ├── authentication.js │ ├── params-handler.js │ ├── return.js │ ├── format.js │ ├── crypto.js │ └── response-handler.js ├── config ├── enum.js ├── development.json ├── pagesize.js ├── production.json ├── error-system.js ├── role-right.js ├── success-message.js ├── right.js ├── index.js ├── log4js.json ├── settings.js ├── settings.pro.js ├── log4js.pro.json ├── router.js └── error-message.js ├── .idea ├── misc.xml ├── vcs.xml ├── modules.xml ├── express-restfulAPI.iml └── workspace.xml ├── .vcmrc ├── .vscode └── launch.json ├── .gitignore ├── test └── http │ └── user.js ├── README.md ├── LICENSE ├── server.js ├── package.json └── CHANGELOG.md /app/apidoc/define/upload.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/enum.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | user: { 4 | male: '男' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Customer": { 3 | "settings": "settings", 4 | "log4js": "log4js" 5 | } 6 | } -------------------------------------------------------------------------------- /app/public/default/defaultlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/morehao/express-blog/HEAD/app/public/default/defaultlogo.png -------------------------------------------------------------------------------- /config/pagesize.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | users: 10, 5 | category: 10, 6 | article: 10 7 | } 8 | -------------------------------------------------------------------------------- /config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "Customer": { 3 | "settings": "settings.pro", 4 | "log4js": "log4js.pro" 5 | } 6 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /config/error-system.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | 'invalid token': 'TOKEN_IS_INVALID', 5 | 'jwt expired': 'TOKEN_HAS_EXPIRED', 6 | serverError: 'SERVER_ERROR', 7 | User: 'USERLIST_FIND_FAILED' 8 | } 9 | -------------------------------------------------------------------------------- /app/services/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const Services = {} 3 | 4 | Services.users = require('./users') 5 | Services.category = require('./category') 6 | Services.article = require('./article') 7 | 8 | module.exports = Services 9 | -------------------------------------------------------------------------------- /app/middlewares/not-find.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // 404错误处理中间件 3 | const {resHandler} = require('../myutil') 4 | 5 | module.exports = (req, res, next) => { 6 | const errorRes = resHandler.getErrorRes('NOT_FIND_ROUTE') 7 | res.sendErr(errorRes) 8 | } 9 | -------------------------------------------------------------------------------- /app/apidoc/define/common.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * @apiDefine headerParams 4 | * @apiHeader {String} token 用户登录后返回的token令牌 5 | */ 6 | /** 7 | * @apiDefine listParams 8 | * @apiParam {Number} page 可选,列表数据页码 9 | * @apiParam {Number} pagesize 可选,一页展示的数据条数 10 | */ 11 | -------------------------------------------------------------------------------- /app/controllers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const controllers = {} 3 | controllers.users = require('./users') 4 | controllers.category = require('./category') 5 | controllers.article = require('./article') 6 | controllers.test = require('./test') 7 | 8 | module.exports = controllers 9 | -------------------------------------------------------------------------------- /app/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const mdbs = {} 4 | mdbs.User = require('./users') 5 | mdbs.Role = require('./role') 6 | mdbs.Right = require('./right') 7 | mdbs.Article = require('./article') 8 | mdbs.ArticleCategory = require('./category') 9 | 10 | module.exports = mdbs 11 | -------------------------------------------------------------------------------- /config/role-right.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // 角色与权限关联关系 3 | const {right} = require('./right') 4 | module.exports = { 5 | root: [right.userAll], // 超级管理员 6 | admin: [right.userLogin, right.userView, right.userList], // 普通管理员 7 | normal: [right.userLogin, right.userEdit, right.userView] // 普通用户 8 | } 9 | -------------------------------------------------------------------------------- /app/middlewares/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const middleware = {} 3 | 4 | middleware.resExtend = require('./res-extend') 5 | middleware.verifyToken = require('./verify-token') 6 | middleware.notFind = require('./not-find') 7 | middleware.log = require('./log.js') 8 | 9 | module.exports = middleware 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/middlewares/log.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const uuidv4 = require('uuid/v4') 3 | module.exports = (req, res, next) => { 4 | const headers = req.headers 5 | if (!headers.traceId) { 6 | headers.traceId = uuidv4() 7 | } 8 | const traceId = headers.traceId 9 | logger.info(`traceId:${traceId}`) 10 | next() 11 | } 12 | -------------------------------------------------------------------------------- /config/success-message.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | USER_UPDATE_SUCCESS: '用户信息更新成功!', 4 | USER_DELETE_SUCCESS: '用户删除成功!', 5 | OPTION_SUCCESS: '操作成功!', 6 | ROUTER_DELETE_SUCCESS: '路由删除成功!', 7 | ROUTER_UPDATE_SUCCESS: '路由更新成功!', 8 | CATEGORY_UPDATE_SUCCESS: '文章类别修改成功!', 9 | ARTICLE_UPDATE_SUCCESS: '文章修改成功!' 10 | } 11 | -------------------------------------------------------------------------------- /app/routes/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Controllers = require('../controllers') 4 | 5 | module.exports = (app) => { 6 | app.route('/user') 7 | .get(Controllers.users.list) 8 | app.route('/user/:_id') 9 | .get(Controllers.users.detail) 10 | .put(Controllers.users.update) 11 | .delete(Controllers.users.destroy) 12 | } 13 | -------------------------------------------------------------------------------- /app/routes/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Controllers = require('../controllers') 4 | 5 | module.exports = (app) => { 6 | app.route('/article') 7 | .post(Controllers.article.create) 8 | .get(Controllers.article.list) 9 | app.route('/article/:_id') 10 | .get(Controllers.article.detail) 11 | .put(Controllers.article.update) 12 | } 13 | -------------------------------------------------------------------------------- /.vcmrc: -------------------------------------------------------------------------------- 1 | { 2 | "helpMessage": "\nPlease fix your commit message (and consider using https://www.npmjs.com/package/commitizen)\n", 3 | "types": [ 4 | "feat", 5 | "fix", 6 | "docs", 7 | "style", 8 | "refactor", 9 | "perf", 10 | "test", 11 | "chore", 12 | "revert" 13 | ], 14 | "warnOnFail": false, 15 | "autoFix": false 16 | } -------------------------------------------------------------------------------- /app/routes/category.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Controllers = require('../controllers') 4 | 5 | module.exports = (app) => { 6 | app.route('/category') 7 | .post(Controllers.category.create) 8 | .get(Controllers.category.list) 9 | app.route('/category/:_id') 10 | .get(Controllers.category.detail) 11 | .put(Controllers.category.update) 12 | } 13 | -------------------------------------------------------------------------------- /config/right.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // 系统权限 3 | const {users} = require('./router.js') 4 | module.exports = { 5 | userAdd: [users.add], 6 | userDelete: [users.delete], 7 | userEdit: [users.edit], 8 | userView: [users.view], 9 | userList: [users.viewList], 10 | userLogin: [users.login], 11 | userAll: [users.add, users.delete, users.edit, users.view, users.viewList, users.login] 12 | } 13 | -------------------------------------------------------------------------------- /app/controllers/base.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const mdb = require('../models') 3 | 4 | class BaseController { 5 | constructor (modelName) { 6 | this.modelName = modelName 7 | this.name = 'aaaa' 8 | } 9 | async test () { 10 | console.log('this.name', this.name) 11 | const result = await mdb[this.modelName].find() 12 | return result 13 | } 14 | } 15 | module.exports = BaseController 16 | -------------------------------------------------------------------------------- /app/myutil/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const myutil = {} 3 | 4 | myutil.crypto = require('./crypto') 5 | myutil.paramsHandler = require('./params-handler') 6 | myutil.resHandler = require('./response-handler') 7 | myutil.format = require('./format') 8 | myutil.auth = require('./authentication') 9 | myutil.validator = require('validator') 10 | myutil.upload = require('./upload') 11 | 12 | module.exports = myutil 13 | -------------------------------------------------------------------------------- /app/myutil/upload.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const formidable = require('formidable') 3 | 4 | module.exports = { 5 | getFileInfo (req) { 6 | return new Promise(function (resolve, reject) { 7 | const form = new formidable.IncomingForm() 8 | form.multiples = true 9 | form.parse(req, function (err, fields, files) { 10 | if (err) return reject(err) 11 | resolve({fileds: fields, files: files}) 12 | }) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/myutil/authentication.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const jwt = require('jsonwebtoken') 3 | const {settings} = require('../../config') 4 | 5 | module.exports = { 6 | createToken (uuid) { 7 | const token = jwt.sign( 8 | {userId: uuid}, 9 | settings.jwtSecret, 10 | {expiresIn: 3600 * 24} 11 | ) 12 | return token 13 | }, 14 | verifyToken (token) { 15 | const result = jwt.verify(token, settings.jwtSecret) 16 | return result 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.idea/express-restfulAPI.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/middlewares/verify-token.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const {auth, resHandler} = require('../myutil') 3 | 4 | module.exports = (req, res, next) => { 5 | const headers = req.headers 6 | if (!headers.token) { 7 | const errorRes = resHandler.getErrorRes('TOKEN_IS_MISSING') 8 | res.sendErr(errorRes) 9 | return 10 | } 11 | try { 12 | auth.verifyToken(headers.token) 13 | next() 14 | } catch (error) { 15 | const errorRes = resHandler.getErrorMsg(error) 16 | res.sendErr(errorRes) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/myutil/params-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | offsetFormat: (params, defaultLimt) => { 4 | let result = {} 5 | const pagesize = (params.pagesize !== undefined) ? parseInt(params.pagesize) : parseInt(defaultLimt) 6 | const currentPage = (params.page !== undefined) ? params.page : 1 7 | const sortRule = (params.sortRule !== undefined) ? parseInt(params.sortRule) : parseInt(-1) 8 | result.skipCount = (currentPage - 1) * pagesize 9 | result.pagesize = pagesize 10 | result.sortRule = sortRule 11 | return result 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "attach", 10 | <<<<<<< HEAD 11 | "name": "Attach to node", 12 | ======= 13 | "name": "Attach", 14 | "runtimeExecutable": "nodemon", 15 | >>>>>>> master 16 | "port": 9229, 17 | "restart": true, 18 | "skipFiles": [ 19 | "${workspaceFolder}/node_modules/**/*.js" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /app/controllers/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // const uuid = require('uuid/v1') 3 | const BaseController = require('./base') 4 | class TestController extends BaseController { 5 | constructor (modelName) { 6 | super(modelName) 7 | this.modelName = 'User' 8 | this.testResponse = this.testResponse.bind(this) 9 | } 10 | 11 | async testResponse (req, res) { 12 | try { 13 | const result = await super.test() 14 | res.send({data: result}) 15 | } catch (error) { 16 | res.sendErr(error) 17 | } 18 | } 19 | } 20 | module.exports = new TestController() 21 | -------------------------------------------------------------------------------- /app/apidoc/api/upload.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * @api {POST} /upload 图片上传 4 | * @apiName upload 5 | * @apiGroup Upload 6 | * @apiDescription 图片上传的接口,form-data方式 7 | * @apiUse headerParams 8 | * @apiParam {File} file 上传的图片文件 9 | * @apiSuccessExample Success-Response: 10 | { 11 | "status": 200, 12 | "errorCode": 0, 13 | "data": [ 14 | { 15 | "imageUrl": "127.0.0.1:4000/upload/0c990080-cbab-11e8-b47c-83c45848f87a.jpg", 16 | "imageName": "0c990080-cbab-11e8-b47c-83c45848f87a.jpg", 17 | "resource": "server" 18 | } 19 | ] 20 | } 21 | */ 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | # Dependency directories 23 | node_modules/ 24 | 25 | # vscode setting 26 | .vscode 27 | 28 | app/public/upload/ 29 | app/public/apidoc/ 30 | 31 | # Output of 'npm pack' 32 | *.tgz 33 | 34 | # log4js 35 | logs 36 | 37 | package-lock.json -------------------------------------------------------------------------------- /test/http/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const request = require('supertest') 4 | const assert = require('assert') 5 | const app = require('../../server') 6 | 7 | describe('test express-frame http request', () => { 8 | let server = app.listen(9000) 9 | describe('test user module', () => { 10 | let createUser = { 11 | name: 'test009', 12 | password: '123456' 13 | } 14 | it('test /users (post) create ', async () => { 15 | let response = await request(server) 16 | .post('/users') 17 | .send(createUser) 18 | .set('Accept', 'application/json') 19 | .expect(200) 20 | assert.deepEqual(0, response.body.errorCode) 21 | }) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const config = require('config') 3 | 4 | const settings = config.get('Customer.settings') 5 | const log4js = config.get('Customer.log4js') 6 | 7 | const configs = {} 8 | 9 | configs.settings = require(`./${settings}`) 10 | configs.logConfig = require(`./${log4js}`) 11 | // configs.settings = require('./settings') 12 | configs.errorMsg = require('./error-message') 13 | configs.successMsg = require('./success-message') 14 | configs.errorSystem = require('./error-system') 15 | configs.pageConfig = require('./pagesize') 16 | configs.enum = require('./enum') 17 | configs.router = require('./router.js') 18 | configs.right = require('./right') 19 | 20 | module.exports = configs 21 | -------------------------------------------------------------------------------- /config/log4js.json: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": { 3 | "request": { 4 | "type": "console", 5 | "filename": "./logs/access", 6 | "pattern": "-yyyy-MM-dd.log", 7 | "alwaysIncludePattern": true, 8 | "category": "http" 9 | }, 10 | "application": { 11 | "type": "DateFile", 12 | "filename": "./logs/application", 13 | "pattern": "-yyyy-MM-dd.log", 14 | "alwaysIncludePattern": true 15 | }, 16 | "err": { 17 | "type": "stderr", 18 | "filename": "./logs/errors", 19 | "pattern": "-yyyy-MM-dd.log", 20 | "alwaysIncludePattern": true 21 | } 22 | }, 23 | "categories": { 24 | "default": { "appenders": ["application", "request"], "level": "info" } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/myutil/return.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const configs = require('../../config') 3 | 4 | module.exports = { 5 | // 成功提示的返回 6 | returnSuccess: (successMsg) => { 7 | const result = { 8 | status: 200, 9 | errorCode: 0, 10 | successMsg: configs.successMsg[successMsg] 11 | } 12 | return result 13 | }, 14 | // 错误提示的返回 15 | returnError: (errMsg) => { 16 | const result = { 17 | status: 200, 18 | errorCode: configs.errorMsg[errMsg].errorCode, 19 | errorMsg: configs.errorMsg[errMsg].errorMsg 20 | } 21 | return result 22 | }, 23 | // 正常数据的返回 24 | returnOk: (data) => { 25 | const result = { 26 | status: 200, 27 | errorCode: 0, 28 | data: data 29 | } 30 | return result 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/myutil/format.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const enums = require('../../config/enum') 4 | const moment = require('moment') 5 | 6 | module.exports = { 7 | formatDate (date) { 8 | let result 9 | if (!date) { 10 | result = '' 11 | } else { 12 | result = moment(date).format('YYYY-MM-DD HH:mm:ss') 13 | } 14 | return result 15 | }, 16 | user (data) { 17 | if (data.sex) { 18 | data.reSex = enums.user[data.sex] 19 | } 20 | delete data.password 21 | data.createdAt = this.formatDate(data.createdAt) 22 | data.updatedAt = this.formatDate(data.updatedAt) 23 | if (data.lastLogin) { 24 | data.lastLogin = this.formatDate(data.lastLogin) 25 | } else { 26 | data.lastLogin = '暂无登录记录' 27 | } 28 | return data 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Controllers = require('../controllers') 4 | const middleware = require('../middlewares') 5 | const middlewaresArr = [middleware.verifyToken, middleware.log] 6 | 7 | module.exports = (app) => { 8 | app.use(middleware.resExtend) 9 | app.post('/users/test', Controllers.users.test) 10 | app.post('/user/login', Controllers.users.login) 11 | app.post('/user', Controllers.users.create) 12 | app.use(middlewaresArr) 13 | require('./user')(app) 14 | require('./category')(app) 15 | require('./article')(app) 16 | app.route('/upload') 17 | .post(Controllers.article.upload) 18 | app.route('/test') 19 | .post(Controllers.test.testResponse) 20 | 21 | // app.post(Controllers.test.test) 22 | 23 | app.use('/test1', Controllers.test.test) 24 | } 25 | -------------------------------------------------------------------------------- /app/myutil/crypto.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const crypto = require('crypto') 3 | 4 | module.exports = { 5 | // 密码加密 6 | encrypted (password, saltKey) { 7 | const cipher = crypto.createCipher('bf', saltKey) 8 | let newPsd = '' 9 | newPsd += cipher.update(password, 'utf8', 'hex') 10 | newPsd += cipher.final('hex') 11 | return newPsd 12 | }, 13 | // 密码解密 14 | decrypted (password, saltKey) { 15 | const decipher = crypto.createDecipher('bf', saltKey) 16 | let oldPsd = '' 17 | oldPsd += decipher.update(password, 'hex', 'utf8') 18 | oldPsd += decipher.final('utf8') 19 | return oldPsd 20 | }, 21 | // 密码对比 22 | checkPasswd (inputPasswd, userPasswd) { 23 | let result 24 | if (inputPasswd === userPasswd) { 25 | result = true 26 | } else { 27 | result = false 28 | } 29 | return result 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /config/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | // 密码盐 5 | saltKey: 'express_frame', 6 | // jwt实现token认证的secret 7 | jwtSecret: 'express_jwt', 8 | port: process.env.PORT || 4000, 9 | website: '127.0.0.1:4000', 10 | // mongodb数据库配置 11 | dbConfig: { 12 | URL: 'mongodb://127.0.0.1:27017/express-blog', 13 | DB: 'express-blog', 14 | HOST: '127.0.0.1', 15 | PORT: 27017, 16 | USERNAME: 'morehao', 17 | PASSWORD: 'morehao' 18 | }, 19 | mongooseDebug: true, 20 | adminConfig: { 21 | name: 'admin', 22 | password: '123456', 23 | role: 'ordinary users' 24 | }, 25 | qiniuConfig: { 26 | accessKey: '', 27 | secretKey: '', 28 | bucket: 'express-blog', 29 | originUrl: '' 30 | }, 31 | upload: { 32 | savePath: 'app/public/upload', 33 | showPath: '/upload/' 34 | } 35 | // redis数据库配置 36 | } 37 | -------------------------------------------------------------------------------- /config/settings.pro.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | // 密码盐 5 | saltKey: 'express_frame', 6 | // jwt实现token认证的secret 7 | jwtSecret: 'express_jwt', 8 | port: process.env.PORT || 5000, 9 | website: '127.0.0.1:4000', 10 | // mongodb数据库配置 11 | dbConfig: { 12 | URL: 'mongodb://127.0.0.1:27017/express-blog', 13 | DB: 'express-blog', 14 | HOST: '127.0.0.1', 15 | PORT: 27017, 16 | USERNAME: 'morehao', 17 | PASSWORD: 'morehao' 18 | }, 19 | mongooseDebug: false, 20 | adminConfig: { 21 | name: 'admin', 22 | password: '123456', 23 | role: 'ordinary users' 24 | }, 25 | qiniuConfig: { 26 | accessKey: '', 27 | secretKey: '', 28 | bucket: 'express-blog', 29 | originUrl: '' 30 | }, 31 | upload: { 32 | savePath: 'app/public/upload', 33 | showPath: '/upload/' 34 | } 35 | // redis数据库配置 36 | } 37 | -------------------------------------------------------------------------------- /app/myutil/response-handler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const {errorMsg, successMsg, errorSystem} = require('../../config') 4 | 5 | module.exports = { 6 | getSuccessMsg (succMsg) { 7 | const successRes = successMsg[succMsg] ? successMsg[succMsg] : successMsg['OPTION_SUCCESS'] 8 | return successRes 9 | }, 10 | getErrorMsg (error) { 11 | const errorMessage = error.message ? error.message : 'serverError' 12 | const finalError = errorSystem[errorMessage] ? errorSystem[errorMessage] : 'SERVER_ERROR' 13 | const result = errorMsg[finalError] 14 | return result 15 | }, 16 | getErrorRes (error) { 17 | const result = errorMsg[error] ? errorMsg[error] : errorMsg['SERVER_ERROR'] 18 | return result 19 | }, 20 | getModelError (model) { 21 | const result = errorSystem[model] ? errorSystem[model] : 'SERVER_ERROR' 22 | return result 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /config/log4js.pro.json: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": { 3 | "request": { 4 | "type": "DateFile", 5 | "filename": "./logs/access", 6 | "pattern": "-yyyy-MM-dd.log", 7 | "alwaysIncludePattern": true, 8 | "category": "http" 9 | }, 10 | "application": { 11 | "type": "DateFile", 12 | "filename": "./logs/application", 13 | "pattern": "-yyyy-MM-dd.log", 14 | "alwaysIncludePattern": true 15 | }, 16 | "errorFile": { 17 | "type": "DateFile", 18 | "filename": "./logs/errors", 19 | "pattern": "-yyyy-MM-dd.log", 20 | "alwaysIncludePattern": true 21 | }, 22 | "errors": { 23 | "type": "logLevelFilter", 24 | "level": "ERROR", 25 | "appender": "errorFile" 26 | } 27 | }, 28 | "categories": { 29 | "default": { "appenders": ["application", "request"], "level": "info" }, 30 | "error": { "appenders": ["errors"], "level": "error" } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # express-blog 2 | 在express框架下的学习与摸索,如果可能,最终希望形成一个博客系统
3 | 1、restfulAPI的实现
4 | 2、实现MVC模式
5 | 3、res添加属性,对返回的数据格式化为统一的标准
6 | 4、对错误进行通过中间件进行统一处理
7 | 5、用户管理的简单增删改查接口
8 | 6、用户列表查询的封装处理
9 | 7、npm run build 初始化创建管理员用户
10 | 8、读出数据的二次处理(填充)
11 | 9、学习使用vscode调试程序
12 | 10、使用JWT实现token认证
13 | 11、使用log4js管理日志
14 | 12、使用mocha框架编写测试代码
15 | 13、设置跨域访问
16 | 14、优化日志,格式化日志信息,但仍需修改
17 | 15、 后来添加的东西忘记记录了。。。。。
18 | 参考资料:
19 | [restful api](https://www.codementor.io/olatundegaruba/nodejs-restful-apis-in-10-minutes-q0sgsfhbd)
20 | [log4js](https://juejin.im/post/5b7d0e20f265da43231f00d4) 21 | 22 | 命令说明: 23 | ``` shell 24 | # 安装依赖 25 | npm install 26 | # 开发模式启动程序 27 | npm run dev 28 | # 生产模式启动程序 29 | npm run start 30 | # 生成接口文档 31 | npm run apidoc 32 | # 生成提交日志 33 | npm run changelog 34 | # 运行测试代码 35 | npm run test 36 | # 初始化构建,生成admin账号 37 | npm run build 38 | ``` 39 | 注:执行`npm run apidoc`后启动程序,开发环境下访问`localhost:4000/apidoc`即可查看接口文档。 40 | -------------------------------------------------------------------------------- /app/models/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const mongoose = require('mongoose') 3 | const Schema = mongoose.Schema 4 | const ObjectId = Schema.ObjectId 5 | const ArticleSchema = new Schema({ 6 | title: { type: String }, 7 | content: { type: String }, 8 | authorId: { type: ObjectId, ref: 'Users', index: true }, 9 | top: { type: Boolean, default: false, index: true }, // 置顶文章 10 | good: { type: Boolean, default: false }, // 精华文章 11 | createAt: { type: Date, default: Date.now, index: true }, 12 | updateAt: { type: Date, default: Date.now }, 13 | countInfo: { 14 | commentCount: {type: Number, default: 0}, 15 | visitCount: {type: Number, default: 0}, 16 | collectCount: {type: Number, default: 0}, 17 | likeCount: {type: Number, default: 0} 18 | }, 19 | contentType: { type: String, enum: ['html', 'markdown'] }, 20 | categoryId: { type: ObjectId, ref: 'Categorys' }, 21 | deleted: { type: Boolean, default: false } 22 | }) 23 | 24 | module.exports = mongoose.model('Articles', ArticleSchema) 25 | -------------------------------------------------------------------------------- /app/models/category.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const mongoose = require('mongoose') 3 | const moment = require('moment') 4 | const Schema = mongoose.Schema 5 | const ObjectId = Schema.ObjectId 6 | const CategorySchema = new Schema({ 7 | userId: {type: ObjectId, ref: 'Users'}, 8 | name: {type: String, required: 'name is required'}, 9 | createdAt: { 10 | type: Date, 11 | default: Date.now 12 | }, 13 | updatedAt: { 14 | type: Date, 15 | default: Date.now 16 | } 17 | }, { 18 | timestamps: { 19 | createdAt: 'createdAt', 20 | updatedAt: 'updatedAt' 21 | } 22 | }) 23 | 24 | CategorySchema.set('toJSON', { getters: true, virtuals: true }) 25 | CategorySchema.set('toObject', { getters: true, virtuals: true }) 26 | 27 | CategorySchema.path('createdAt').get(function (v) { 28 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 29 | }) 30 | CategorySchema.path('updatedAt').get(function (v) { 31 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 32 | }) 33 | 34 | module.exports = mongoose.model('Categorys', CategorySchema) 35 | -------------------------------------------------------------------------------- /app/middlewares/res-extend.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (req, res, next) => { 3 | res.sendOk = (data) => { 4 | const rst = { 5 | status: 200, 6 | errorCode: 0, 7 | data: data 8 | } 9 | logger.info(`traceId:${req.headers.traceId}`) 10 | logger.info(`method: [${req.method}] req-url: ${req.url}`) 11 | logger.info(`req-query:${JSON.stringify(req.query)}`) 12 | logger.info(`req-params:${JSON.stringify(req.params)}`) 13 | if (req.url !== '/user/login') { 14 | logger.info(`req-body:${JSON.stringify(req.body)}`) 15 | } 16 | logger.info(`sendOk:${JSON.stringify(data)}`) 17 | res.send(rst) 18 | } 19 | res.sendErr = (errorInfo) => { 20 | logger.info(`traceId:${req.headers.traceId}`) 21 | logger.error(`method: [${req.method}] req-url: ${req.url}`) 22 | logger.error(`req-query:${JSON.stringify(req.query)}`) 23 | logger.error(`req-params:${JSON.stringify(req.params)}`) 24 | logger.error(`req-body:${JSON.stringify(req.body)}`) 25 | logger.error(`sendErr:${JSON.stringify(errorInfo)}`) 26 | res.send(errorInfo) 27 | } 28 | next() 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 morehao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/models/role.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const mongoose = require('mongoose') 3 | const moment = require('moment') 4 | const Schema = mongoose.Schema 5 | const RoleSchema = new Schema({ 6 | roleNameEN: { type: String, required: 'name is required' }, 7 | roleNameCN: { type: String, required: 'name is required' }, 8 | description: { type: String, default: '该角色尚无描述' }, 9 | right: [ 10 | { 11 | type: String, 12 | ref: 'Rights' 13 | } 14 | ], 15 | createdAt: { 16 | type: Date, 17 | default: Date.now 18 | }, 19 | updatedAt: { 20 | type: Date, 21 | default: Date.now 22 | } 23 | }, { 24 | timestamps: { 25 | createdAt: 'createdAt', 26 | updatedAt: 'updatedAt' 27 | } 28 | }) 29 | 30 | RoleSchema.set('toJSON', { getters: true, virtuals: true }) 31 | RoleSchema.set('toObject', { getters: true, virtuals: true }) 32 | 33 | RoleSchema.path('createdAt').get(function (v) { 34 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 35 | }) 36 | RoleSchema.path('updatedAt').get(function (v) { 37 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 38 | }) 39 | 40 | module.exports = mongoose.model('Roles', RoleSchema) 41 | -------------------------------------------------------------------------------- /app/models/right.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const mongoose = require('mongoose') 3 | const moment = require('moment') 4 | const Schema = mongoose.Schema 5 | const RightSchema = new Schema({ 6 | rightNameEN: { type: String, required: 'name is required' }, 7 | rightNameCN: { type: String, required: 'name is required' }, 8 | description: { type: String, default: '该权限尚无描述' }, 9 | router: [ 10 | { 11 | type: String, 12 | ref: 'Routers' 13 | } 14 | ], 15 | createdAt: { 16 | type: Date, 17 | default: Date.now 18 | }, 19 | updatedAt: { 20 | type: Date, 21 | default: Date.now 22 | } 23 | }, { 24 | timestamps: { 25 | createdAt: 'createdAt', 26 | updatedAt: 'updatedAt' 27 | } 28 | }) 29 | 30 | RightSchema.set('toJSON', { getters: true, virtuals: true }) 31 | RightSchema.set('toObject', { getters: true, virtuals: true }) 32 | 33 | RightSchema.path('createdAt').get(function (v) { 34 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 35 | }) 36 | RightSchema.path('updatedAt').get(function (v) { 37 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 38 | }) 39 | 40 | module.exports = mongoose.model('Rights', RightSchema) 41 | -------------------------------------------------------------------------------- /config/router.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // 当请求方式为GET和DELETE时,isParams的值,1代表请求的url中有数组对象,0代表没有,默认为0 3 | // 已注册的路由 4 | module.exports = { 5 | users: { 6 | add: { 7 | routerNameCN: '创建用户', 8 | routerNmaeEN: 'add user', 9 | router: '/users', 10 | method: 'POST', 11 | isParams: 0 12 | }, 13 | delete: { 14 | routerNameCN: '删除用户', 15 | routerNmaeEN: 'delete user', 16 | router: '/users', 17 | method: 'DELETE', 18 | isParams: 1 19 | }, 20 | edit: { 21 | routerNameCN: '编辑用户', 22 | routerNmaeEN: 'edit user', 23 | router: '/users', 24 | method: 'PUT', 25 | isParams: 1 26 | }, 27 | view: { 28 | routerNameCN: '查看单个用户', 29 | routerNmaeEN: 'view user', 30 | router: '/users', 31 | method: 'GET', 32 | isParams: 1 33 | }, 34 | viewList: { 35 | routerNameCN: '查看用户列表', 36 | routerNmaeEN: 'view user list', 37 | router: '/users', 38 | method: 'GET', 39 | isParams: 0 40 | }, 41 | login: { 42 | routerNameCN: '用户登录', 43 | routerNmaeEN: 'user login', 44 | router: '/users', 45 | method: 'POST', 46 | isParams: 0 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const express = require('express') 3 | const path = require('path') 4 | const app = express() 5 | const mongoose = require('mongoose') 6 | const bodyParser = require('body-parser') 7 | const cors = require('cors') 8 | const log4js = require('log4js') 9 | 10 | const {logConfig, settings} = require('./config') 11 | // const myutil = require('./app/myutil') 12 | 13 | const routes = require('./app/routes') // 引入路由 14 | // 日志配置 15 | log4js.configure(logConfig) 16 | let logger = log4js.getLogger() 17 | global.logger = logger 18 | 19 | // 配置静态文件 20 | app.use(express.static(path.join(__dirname, 'app/public'))) 21 | // 配置apidoc 22 | app.use('/apidoc', express.static(path.join(__dirname, 'app/public/apidoc/'))) 23 | 24 | // 连接数据库 25 | // 将mongoose自身的promise替代为ES6的promise 26 | mongoose.Promise = global.Promise 27 | // MongoDB升级到4.0之后,需要加useNewUrlParser参数和useCreateIndex参数 28 | mongoose.connect(settings.dbConfig.URL, { useNewUrlParser: true, useCreateIndex: true }) 29 | mongoose.set('debug', settings.mongooseDebug) 30 | 31 | // 请求体解析中间件 32 | app.use(bodyParser.urlencoded({ extended: true })) 33 | app.use(bodyParser.json()) 34 | 35 | // // 对res的扩展 36 | // app.use(myutil.resExtend) 37 | 38 | // 跨域配置 39 | app.use(cors()) 40 | 41 | // 注册路由 42 | routes(app) 43 | app.listen(settings.port) 44 | logger.info(`start:port is ${settings.port}`) 45 | 46 | module.exports = app 47 | -------------------------------------------------------------------------------- /app/apidoc/api/category.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * @api {POST} /category 新增文章类别 4 | * @apiName create a category 5 | * @apiGroup Category 6 | * @apiDescription 新增文章类别的接口 7 | * @apiUse headerParams 8 | * @apiUse categoryParams 9 | * @apiUse categoryCreateResponse 10 | * @apiErrorExample {json} Error-Response: 11 | * { 12 | * status: 200, 13 | * errorCode: 20301, 14 | * errorMsg: '该分类已经存在' 15 | * } 16 | */ 17 | 18 | /** 19 | * @api {PUT} /category/:_id 文章类别更新 20 | * @apiName update a category 21 | * @apiGroup Category 22 | * @apiDescription 文章类别更新的接口 23 | * @apiUse headerParams 24 | * @apiParam {String} _id 文章类别的id 25 | * @apiParam {String} name 可选,文章类别的名称 26 | * @apiUse categoryResponse 27 | * @apiUse categoryNotFind 28 | */ 29 | 30 | /** 31 | * @api {GET} /category/:_id 文章类别详情 32 | * @apiName view a category 33 | * @apiGroup Category 34 | * @apiDescription 文章类别详情的接口 35 | * @apiUse headerParams 36 | * @apiParam {String} _id 文章类别的id 37 | * @apiUse categoryResponse 38 | * @apiUse categoryNotFind 39 | */ 40 | 41 | /** 42 | * @api {GET} /category/:_id 文章类别列表 43 | * @apiName get categoryList 44 | * @apiGroup Category 45 | * @apiDescription 文章类别列表的接口 46 | * @apiUse headerParams 47 | * @apiUse listParams 48 | * @apiParam {String} name 可选,文章类别的名称 49 | * @apiUse categoryListResponse 50 | * @apiUse categoryNotFind 51 | */ 52 | -------------------------------------------------------------------------------- /app/apidoc/api/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * @api {POST} /article 新增文章 4 | * @apiName create an article 5 | * @apiGroup Article 6 | * @apiDescription 新增文章的接口 7 | * @apiUse headerParams 8 | * @apiUse articleParams 9 | * @apiUse articleCreareResponse 10 | * @apiErrorExample {json} Error-Response: 11 | * { 12 | * status: 200, 13 | * errorCode: 20401, 14 | * errorMsg: '该文章已经存在' 15 | * } 16 | */ 17 | 18 | /** 19 | * @api {PUT} /article/:_id 文章更新 20 | * @apiName update an article 21 | * @apiGroup Article 22 | * @apiDescription 文章更新的接口 23 | * @apiUse headerParams 24 | * @apiParam {String} _id 文章的id 25 | * @apiUse articleParamsOptional 26 | * @apiSuccessExample Success-Response: 27 | { 28 | "status": 200, 29 | "errorCode": 0, 30 | "data": "文章修改成功!" 31 | } 32 | * @apiUse articleNotFind 33 | */ 34 | 35 | /** 36 | * @api {GET} /article/:_id 文章详情 37 | * @apiName view an article 38 | * @apiGroup Article 39 | * @apiDescription 文章详情的接口 40 | * @apiUse headerParams 41 | * @apiParam {String} _id 文章的id 42 | * @apiUse articleResponse 43 | * @apiUse articleNotFind 44 | */ 45 | 46 | /** 47 | * @api {GET} /article 文章列表 48 | * @apiName get articleList 49 | * @apiGroup Article 50 | * @apiDescription 文章列表的接口 51 | * @apiUse headerParams 52 | * @apiUse listParams 53 | * @apiUse articleParamsOptional 54 | * @apiUse articleListResponse 55 | * @apiUse articleNotFind 56 | */ 57 | -------------------------------------------------------------------------------- /app/models/users.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const mongoose = require('mongoose') 3 | const moment = require('moment') 4 | const Schema = mongoose.Schema 5 | const UserSchema = new Schema({ 6 | name: { type: String, required: 'name is required' }, 7 | nickName: { type: String }, 8 | password: { type: String }, 9 | age: {type: Number, min: 18, max: 95}, 10 | sex: { type: String, enum: ['male', 'female', 'unknow'] }, 11 | company: String, // 大学或者公司 12 | website: String, // 个人网站 13 | intruction: { type: String, default: '这个人很懒,什么都有没留下、、、' }, 14 | logo: { type: String, default: '/upload/images/defaultlogo.png' }, 15 | role: {type: String, default: 'ordinary users'}, 16 | lastLogin: Date, 17 | createdAt: { 18 | type: Date, 19 | default: Date.now 20 | }, 21 | updatedAt: { 22 | type: Date, 23 | default: Date.now 24 | } 25 | }, { 26 | timestamps: { 27 | createdAt: 'createdAt', 28 | updatedAt: 'updatedAt' 29 | } 30 | }) 31 | 32 | UserSchema.set('toJSON', { getters: true, virtuals: true }) 33 | UserSchema.set('toObject', { getters: true, virtuals: true }) 34 | 35 | UserSchema.path('lastLogin').get(function (v) { 36 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 37 | }) 38 | UserSchema.path('createdAt').get(function (v) { 39 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 40 | }) 41 | UserSchema.path('updatedAt').get(function (v) { 42 | return moment(v).format('YYYY-MM-DD HH:mm:ss') 43 | }) 44 | 45 | UserSchema.statics = { 46 | findAge: async function (age) { 47 | const findRes = await this.find({age: age}) 48 | return findRes 49 | } 50 | } 51 | 52 | module.exports = mongoose.model('Users', UserSchema) 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-blog", 3 | "version": "1.0.0", 4 | "description": "express-blog", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development nodemon --inspect server.js", 8 | "start": "cross-env NODE_ENV=production node server.js", 9 | "build": "node ./build/server.js", 10 | "test": "mocha test/**/*.js", 11 | "standard": "standard", 12 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", 13 | "apidoc": "apidoc -i app/apidoc/ -o app/public/apidoc/" 14 | }, 15 | "author": "morehao", 16 | "license": "MIT", 17 | "pre-commit": [ 18 | "standard" 19 | ], 20 | "apidoc": { 21 | "name": "express-blog接口文档", 22 | "title": "express-blog接口文档", 23 | "version": "0.1.0" 24 | }, 25 | "devDependencies": { 26 | "bluebird": "^3.5.1", 27 | "config": "^2.0.1", 28 | "cors": "^2.8.4", 29 | "cross-env": "^5.2.0", 30 | "eslint": "^4.19.1", 31 | "formidable": "^1.2.1", 32 | "mocha": "^5.2.0", 33 | "multer": "^1.3.1", 34 | "nodemon": "^1.17.3", 35 | "pre-commit": "^1.2.2", 36 | "qiniu": "^7.2.1", 37 | "standard": "^11.0.1", 38 | "supertest": "^3.1.0", 39 | "uuid": "^3.3.2", 40 | "validator": "^10.7.1" 41 | }, 42 | "standard": { 43 | "globals": [ 44 | "describe", 45 | "it", 46 | "logger" 47 | ] 48 | }, 49 | "dependencies": { 50 | "amqplib": "^0.5.2", 51 | "body-parser": "^1.18.2", 52 | "debug": "^3.1.0", 53 | "express": "^4.16.3", 54 | "jsonwebtoken": "^8.2.2", 55 | "lodash": "^4.17.10", 56 | "log4js": "^2.8.0", 57 | "moment": "^2.22.1", 58 | "mongoose": "^5.0.17" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/services/category.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const BaseService = require('./base') 3 | const mdb = require('../models') 4 | const {resHandler} = require('../myutil') 5 | 6 | class CategoryService extends BaseService { 7 | constructor (model) { 8 | super(model) 9 | this.model = 'ArticleCategory' 10 | } 11 | 12 | async addCategory (data) { 13 | try { 14 | const query = { 15 | userId: data.userId, 16 | name: data.name 17 | } 18 | const findRes = await super.findOne(query) 19 | if (findRes) { 20 | const errorMsg = 'CATEGORY_HAS_EXITS' 21 | throw errorMsg 22 | } 23 | const result = await mdb.ArticleCategory.create(data) 24 | return result 25 | } catch (error) { 26 | throw error 27 | } 28 | } 29 | 30 | async editById (id, params) { 31 | try { 32 | const findRes = await super.findById(id) 33 | if (!findRes) { 34 | const errorMsg = 'CATEGORY_NOT_EXITS' 35 | throw errorMsg 36 | } 37 | await super.updateById(id, params) 38 | const result = resHandler.getSuccessMsg('CATEGORY_UPDATE_SUCCESS') 39 | return result 40 | } catch (error) { 41 | throw error 42 | } 43 | } 44 | 45 | async getCategoryById (id) { 46 | try { 47 | const result = await mdb.ArticleCategory.findById(id) 48 | .populate([{path: 'userId', select: '-password'}]) 49 | if (!result) { 50 | const errorMsg = 'CATEGORY_NOT_EXITS' 51 | throw errorMsg 52 | } 53 | return result 54 | } catch (error) { 55 | throw error 56 | } 57 | } 58 | 59 | async getCategoryList (params) { 60 | try { 61 | const result = await super.list(params) 62 | return result 63 | } catch (error) { 64 | throw error 65 | } 66 | } 67 | } 68 | module.exports = new CategoryService() 69 | -------------------------------------------------------------------------------- /app/controllers/category.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const Services = require('../services') 3 | const {auth, resHandler, paramsHandler, validator} = require('../myutil') 4 | const {pageConfig} = require('../../config') 5 | 6 | class CategoryController { 7 | async create (req, res) { 8 | try { 9 | if (validator.isEmpty(req.body.name, {ignore_whitespace: true})) { 10 | const errorMsg = 'CATEGORYNAME_IS_EMPTY' 11 | throw errorMsg 12 | } 13 | const userInfo = auth.verifyToken(req.headers.token) 14 | req.body.userId = userInfo.userId 15 | const result = await Services.category.addCategory(req.body) 16 | res.sendOk(result) 17 | } catch (error) { 18 | const errorRes = resHandler.getErrorRes(error) 19 | res.sendErr(errorRes) 20 | } 21 | } 22 | 23 | async update (req, res) { 24 | try { 25 | const result = await Services.category.editById(req.params._id, req.body) 26 | res.sendOk(result) 27 | } catch (error) { 28 | const errorRes = resHandler.getErrorRes(error) 29 | res.sendErr(errorRes) 30 | } 31 | } 32 | 33 | async detail (req, res) { 34 | try { 35 | const result = await Services.category.getCategoryById(req.params._id) 36 | res.sendOk(result) 37 | } catch (error) { 38 | const errorRes = resHandler.getErrorRes(error) 39 | res.sendErr(errorRes) 40 | } 41 | } 42 | 43 | async list (req, res) { 44 | try { 45 | // 翻页参数处理 46 | const offset = paramsHandler.offsetFormat(req.query, pageConfig.users) 47 | const queryObj = { 48 | condition: req.query, 49 | skipCount: offset.skipCount, 50 | pagesize: offset.pagesize, 51 | sortRule: offset.sortRule, 52 | populate: [{path: 'userId', select: '-password'}] 53 | } 54 | const result = await Services.category.getCategoryList(queryObj) 55 | res.sendOk(result) 56 | } catch (error) { 57 | // const errorRes = resHandler.getErrorRes(error) 58 | // res.sendErr(errorRes) 59 | res.sendErr(error) 60 | } 61 | } 62 | } 63 | module.exports = new CategoryController() 64 | -------------------------------------------------------------------------------- /app/services/base.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const mdb = require('../models') 3 | const {resHandler} = require('../myutil') 4 | class BaseService { 5 | constructor (model) { 6 | this.model = model 7 | this.adventures = null // 相当于select,选择返回的属性 8 | } 9 | async findOne (params) { 10 | try { 11 | const result = await mdb[this.model].findOne(params, this.adventures, {lean: true}) 12 | return result 13 | } catch (error) { 14 | const modelErrorMsg = resHandler.getModelError(this.model) 15 | throw modelErrorMsg 16 | } 17 | } 18 | async findById (id) { 19 | try { 20 | const result = await mdb[this.model].findById(id, {lean: true}) 21 | return result 22 | } catch (error) { 23 | const modelErrorMsg = resHandler.getModelError(this.model) 24 | throw modelErrorMsg 25 | } 26 | } 27 | async save (params) { 28 | try { 29 | const result = await mdb[this.model].create(params) 30 | return result 31 | } catch (error) { 32 | const modelErrorMsg = resHandler.getModelError(this.model) 33 | throw modelErrorMsg 34 | } 35 | } 36 | async updateById (id, params) { 37 | try { 38 | const result = await mdb[this.model].update({_id: id}, {$set: params}) 39 | return result 40 | } catch (error) { 41 | const modelErrorMsg = resHandler.getModelError(this.model) 42 | throw modelErrorMsg 43 | } 44 | } 45 | async list (params) { 46 | try { 47 | const query = mdb[this.model] 48 | .find(params.condition, this.adventures, {lean: true}) 49 | .populate(params.populate) 50 | .skip(params.skipCount) 51 | .limit(params.pagesize) 52 | .sort({createdAt: Number(params.sortRule)}) 53 | // if (params.populate) { 54 | // query.populate = params.populate 55 | // } 56 | const dataCount = await mdb[this.model].countDocuments() 57 | const list = await query 58 | return { 59 | dataCount: dataCount, 60 | list: list 61 | } 62 | } catch (error) { 63 | // const modelErrorMsg = resHandler.getModelError(this.model) 64 | // throw modelErrorMsg 65 | throw error 66 | } 67 | } 68 | async getUserByName (name) { 69 | const query = mdb[this.model].findOne({name: name}, this.adventures, {blen: true}) 70 | if (this.model === 'User') { 71 | query.select('-password') 72 | } 73 | const result = await query 74 | return result 75 | } 76 | } 77 | 78 | module.exports = BaseService 79 | -------------------------------------------------------------------------------- /app/apidoc/define/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * @apiDefine userParams 4 | * @apiParam {String} name 用户名 5 | * @apiParam {Number} age 年龄 6 | * @apiParam {String} nickName 昵称 7 | * @apiParam {Enum} sex 性别 8 | * @apiParam {String} company 学校或者公司 9 | * @apiParam {String} website 个人网站 10 | * @apiParam {String} instruction 个人简介 11 | * @apiParam {String} role 用户角色 12 | */ 13 | 14 | /** 15 | * @apiDefine userParamsOptional 16 | * @apiParam {String} name 可选,用户名 17 | * @apiParam {Number} age 可选,年龄 18 | * @apiParam {String} nickName 可选,昵称 19 | * @apiParam {Enum} sex 可选,性别 20 | * @apiParam {String} company 可选,学校或者公司 21 | * @apiParam {String} website 可选,个人网站 22 | * @apiParam {String} instruction 可选,个人简介 23 | * @apiParam {String} role 可选,用户角色 24 | */ 25 | 26 | /** 27 | * @apiDefine userResponse 28 | * @apiSuccessExample Success-Response: 29 | { 30 | "status": 200, 31 | "errorCode": 0, 32 | "data": { 33 | "intruction": "这个人很懒,什么都有没留下、、、", 34 | "logo": "/upload/images/defaultlogo.png", 35 | "role": "ordinary users", 36 | "_id": "5bbc59366501713374220caa", 37 | "name": "morehao", 38 | "nickName": "毛浩先生", 39 | "age": 24, 40 | "sex": "male", 41 | "company": "太原科技大学", 42 | "website": "morehao.com", 43 | "createdAt": "2018-10-09 15:31:02", 44 | "updatedAt": "2018-10-09 15:31:02", 45 | "__v": 0, 46 | "lastLogin": "2018-10-09 15:31:02", 47 | "id": "5bbc59366501713374220caa", 48 | "reSex": "男" 49 | } 50 | } 51 | */ 52 | 53 | /** 54 | * @apiDefine userListResponse 55 | * @apiSuccessExample Success-Response: 56 | { 57 | "status": 200, 58 | "errorCode": 0, 59 | "data": { 60 | "dataCount": 1, 61 | "list": [ 62 | { 63 | "_id": "5bbc59366501713374220caa", 64 | "intruction": "这个人很懒,什么都有没留下、、、", 65 | "logo": "/upload/images/defaultlogo.png", 66 | "role": "ordinary users", 67 | "name": "morehao", 68 | "nickName": "毛浩先生", 69 | "age": 24, 70 | "sex": "male", 71 | "company": "太原科技大学", 72 | "website": "morehao.com", 73 | "createdAt": "2018-10-09 15:31:02", 74 | "updatedAt": "2018-10-09 15:31:02", 75 | "__v": 0, 76 | "reSex": "男", 77 | "lastLogin": "暂无登录记录" 78 | } 79 | ] 80 | } 81 | } 82 | */ 83 | /** 84 | * @apiDefine userNotFind 85 | * @apiErrorExample {json} Error-Response: 86 | * { 87 | * status: 200, 88 | * errorCode: 20101, 89 | * errorMsg: '该用户不存在!' 90 | * } 91 | */ 92 | -------------------------------------------------------------------------------- /app/apidoc/api/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * @api {POST} /user 新增用户 4 | * @apiDescription 用户新增的接口 5 | * @apiName create a user 6 | * @apiGroup User 7 | * @apiUse userParams 8 | * @apiUse userResponse 9 | * @apiErrorExample {json} Error-Response: 10 | * { 11 | * status: 200, 12 | * errorCode: 20100, 13 | * errorMsg: '该用户已经存在' 14 | * } 15 | */ 16 | 17 | /** 18 | * @api {DELETE} /user/:_id 删除用户 19 | * @apiDescription 用户删除的接口 20 | * @apiName delete a user 21 | * @apiGroup User 22 | * @apiUse headerParams 23 | * @apiParam {String} _id 用户的id 24 | * @apiSuccessExample Success-Response: 25 | * { 26 | * errorCode: 0, 27 | * status: 200, 28 | * data: "用户删除成功!" 29 | * } 30 | * @apiUse userNotFind 31 | */ 32 | 33 | /** 34 | * @api {PUT} /user/:_id 更新用户 35 | * @apiDescription 更新用户信息的接口 36 | * @apiName update a user 37 | * @apiGroup User 38 | * @apiUse headerParams 39 | * @apiParam {String} _id 用户的id 40 | * @apiUse userParamsOptional 41 | * @apiSuccessExample Success-Response: 42 | * { 43 | * errorCode: 0, 44 | * status: 200, 45 | * data: "用户信息更新成功!" 46 | * } 47 | * @apiUse userNotFind 48 | */ 49 | 50 | /** 51 | * @api {POST} /user/:_id 用户详情 52 | * @apiDescription 用户详情的接口 53 | * @apiName view a user 54 | * @apiGroup User 55 | * @apiUse headerParams 56 | * @apiParam {String} _id 用户的id 57 | * @apiUse userResponse 58 | * @apiUse userNotFind 59 | */ 60 | 61 | /** 62 | * @api {GET} /user 用户列表 63 | * @apiDescription 查看用户列表的接口 64 | * @apiName get userList 65 | * @apiGroup User 66 | * @apiUse headerParams 67 | * @apiUse listParams 68 | * @apiUse userParamsOptional 69 | * @apiUse userListResponse 70 | * @apiUse userNotFind 71 | */ 72 | 73 | /** 74 | * @api {POST} /users/login 用户登录 75 | * @apiDescription 用户登录的接口 76 | * @apiName user login 77 | * @apiGroup User 78 | * @apiParam {String} name 用户名 79 | * @apiParam {String} password 密码 80 | * @apiSuccessExample Success-Response: 81 | { 82 | "status": 200, 83 | "errorCode": 0, 84 | "data": { 85 | "_id": "5bbc59366501713374220caa", 86 | "intruction": "这个人很懒,什么都有没留下、、、", 87 | "logo": "/upload/images/defaultlogo.png", 88 | "role": "ordinary users", 89 | "name": "morehao", 90 | "nickName": "毛浩先生", 91 | "age": 24, 92 | "sex": "male", 93 | "company": "太原科技大学", 94 | "website": "morehao.com", 95 | "createdAt": "2018-10-09 15:31:02", 96 | "updatedAt": "2018-10-09 15:31:02", 97 | "__v": 0, 98 | "reSex": "男", 99 | "lastLogin": "暂无登录记录", 100 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YmJjNTkzNjY1MDE3MTMzNzQyMjBjYWEiLCJpYXQiOjE1MzkwNzAzNjksImV4cCI6MTUzOTE1Njc2OX0.NkluX_5Z7NhcE_azAa16Jtqh3YBnbzXO2MecYKD0exs" 101 | } 102 | } 103 | * @apiUse userNotFind 104 | */ 105 | -------------------------------------------------------------------------------- /app/controllers/users.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const Services = require('../services') 3 | const {auth, format, resHandler, paramsHandler} = require('../myutil') 4 | const {pageConfig} = require('../../config') 5 | 6 | class UsersController { 7 | async create (req, res) { 8 | try { 9 | const result = await Services.users.addUser(req.body) 10 | res.sendOk(result) 11 | } catch (error) { 12 | const errorRes = resHandler.getErrorRes(error) 13 | res.sendErr(errorRes) 14 | } 15 | } 16 | async destroy (req, res) { 17 | try { 18 | const result = await Services.users.destroy(req.params._id) 19 | res.sendOk(result) 20 | } catch (error) { 21 | const errorRes = resHandler.getErrorRes(error) 22 | res.sendErr(errorRes) 23 | } 24 | } 25 | async update (req, res) { 26 | try { 27 | const params = req.body 28 | params._id = req.params._id 29 | const result = await Services.users.update(req.body) 30 | res.sendOk(result) 31 | } catch (error) { 32 | const errorRes = resHandler.getErrorRes(error) 33 | res.sendErr(errorRes) 34 | } 35 | } 36 | async list (req, res) { 37 | try { 38 | // 翻页参数处理 39 | const offset = paramsHandler.offsetFormat(req.query, pageConfig.users) 40 | const queryObj = { 41 | condition: req.query, 42 | skipCount: offset.skipCount, 43 | pagesize: offset.pagesize, 44 | sortRule: offset.sortRule, 45 | populate: [] 46 | } 47 | const userList = await Services.users.getUserList(queryObj) 48 | userList.list = userList.list.map(data => { 49 | return format.user(data) 50 | }) 51 | res.sendOk(userList) 52 | } catch (error) { 53 | const errorRes = resHandler.getErrorRes(error) 54 | res.sendErr(errorRes) 55 | } 56 | // const modelObj = { 57 | // user: mdbs.User 58 | // } 59 | // const result = await modelObj[req.query.modelName].find() 60 | // const result = await mdbs.User.find({ 61 | // $or: [ 62 | // {name: {$regex: 'admin', $options: 'i'}}, 63 | // {name: {$regex: 'test001', $options: 'i'}} 64 | // ] 65 | // }) 66 | } 67 | async detail (req, res) { 68 | try { 69 | const result = await Services.users.detail(req.params._id) 70 | res.sendOk(result) 71 | } catch (error) { 72 | const errorRes = resHandler.getErrorRes(error) 73 | res.sendErr(errorRes) 74 | } 75 | } 76 | async login (req, res) { 77 | try { 78 | const result = await Services.users.login(req.body) 79 | result.token = auth.createToken(result._id) 80 | res.sendOk(result) 81 | } catch (error) { 82 | const errorRes = resHandler.getErrorRes(error) 83 | res.sendErr(errorRes) 84 | } 85 | } 86 | async test (req, res) { 87 | const result = await Services.users.test(req.body.name) 88 | res.sendOk(result) 89 | } 90 | } 91 | module.exports = new UsersController() 92 | -------------------------------------------------------------------------------- /config/error-message.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* 3 | 所有接口的status均为200; 4 | errorCode规则:当errorCode为0时表示接口访问成功并且无抛错;不为零时,errorCode共计五位, 5 | 第一位表示错误级别,1开头的为系统级别错误,2开头为系统功能模块对应的错误,其中,第二三位 6 | 表示功能模块编号,第四五位表示具体错误编号。 7 | */ 8 | module.exports = { 9 | // 系统级别错误 10 | NOT_FIND_ROUTE: { 11 | status: 200, 12 | errorCode: 10000, 13 | errorMsg: '访问的路由不存在' 14 | }, 15 | SERVER_ERROR: { 16 | status: 200, 17 | errorCode: 10001, 18 | errorMsg: '服务器开小差去了' 19 | }, 20 | LIST_QUERY_FAILDE: { 21 | status: 200, 22 | errorCode: 10002, 23 | errorMsg: '列表查询失败' 24 | }, 25 | // token认证模块错误 26 | TOKEN_IS_MISSING: { 27 | status: 200, 28 | errorCode: 20000, 29 | errorMsg: 'token缺失' 30 | }, 31 | TOKEN_IS_INVALID: { 32 | status: 200, 33 | errorCode: 20001, 34 | errorMsg: 'token无效' 35 | }, 36 | TOKEN_HAS_EXPIRED: { 37 | status: 200, 38 | errorCode: 20002, 39 | errorMsg: 'token已经过期' 40 | }, 41 | // 用户管理模块错误 42 | USER_HAS_EXITS: { 43 | status: 200, 44 | errorCode: 20100, 45 | errorMsg: '该用户已经存在' 46 | }, 47 | USER_NOT_EXITS: { 48 | status: 200, 49 | errorCode: 20101, 50 | errorMsg: '该用户不存在' 51 | }, 52 | USER_PASSWORD_WRONG: { 53 | status: 200, 54 | errorCode: 20102, 55 | errorMsg: '用户密码错误' 56 | }, 57 | USER_LOGIN_FAILED: { 58 | status: 200, 59 | errorCode: 20103, 60 | errorMsg: '用户登录失败' 61 | }, 62 | USER_ADD_FAILED: { 63 | status: 200, 64 | errorCode: 20104, 65 | errorMsg: '用户创建失败' 66 | }, 67 | USER_DELETE_FAILED: { 68 | status: 200, 69 | errorCode: 20105, 70 | errorMsg: '用户删除失败' 71 | }, 72 | USER_UPDATE_FAILED: { 73 | status: 200, 74 | errorCode: 20106, 75 | errorMsg: '用户更新失败' 76 | }, 77 | USER_QUERY_FAILED: { 78 | status: 200, 79 | errorCode: 20107, 80 | errorMsg: '用户查询失败' 81 | }, 82 | USERLIST_FIND_FAILDE: { 83 | status: 200, 84 | errorCode: 20108, 85 | errorMsg: '用户列表查询失败' 86 | }, 87 | // 路由管理模块错误 88 | ROUTER_HAS_EXITS: { 89 | status: 200, 90 | errorCode: 20200, 91 | errorMsg: '该路由已经存在' 92 | }, 93 | ROUTER_NOT_EXITS: { 94 | status: 200, 95 | errorCode: 20201, 96 | errorMsg: '该路由不存在' 97 | }, 98 | CATEGORY_HAS_EXITS: { 99 | status: 200, 100 | errorCode: 20301, 101 | errorMsg: '该分类已经存在' 102 | }, 103 | CATEGORY_NOT_EXITS: { 104 | status: 200, 105 | errorCode: 20302, 106 | errorMsg: '该分类不存在' 107 | }, 108 | CATEGORYNAME_IS_EMPTY: { 109 | status: 200, 110 | errorCode: 20303, 111 | errorMsg: '文章类别的名字不能为空' 112 | }, 113 | ARTICLE_HAS_EXITS: { 114 | status: 200, 115 | errorCode: 20401, 116 | errorMsg: '该文章已经存在' 117 | }, 118 | ARTICLE_NOT_EXITS: { 119 | status: 200, 120 | errorCode: 20402, 121 | errorMsg: '该文章不存在' 122 | }, 123 | TITLE_IS_EMPTY: { 124 | status: 200, 125 | errorCode: 20403, 126 | errorMsg: '文章标题不能为空' 127 | }, 128 | CONTENT_IS_EMPTY: { 129 | status: 200, 130 | errorCode: 20404, 131 | errorMsg: '文章内容不能为空' 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /app/apidoc/define/category.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | @apiDefine categoryParams 4 | @apiParam {String} name 文章类别名称 5 | */ 6 | 7 | /** 8 | * @apiDefine categoryCreateResponse 9 | * @apiSuccessExample Success-Response: 10 | { 11 | "status": 200, 12 | "errorCode": 0, 13 | "data": { 14 | "_id": "5bbc5fbe2e22a330ccad507a", 15 | "name": "类别", 16 | "userId": "5bbc59366501713374220caa", 17 | "createdAt": "2018-10-09 15:58:54", 18 | "updatedAt": "2018-10-09 15:58:54", 19 | "__v": 0, 20 | "id": "5bbc5fbe2e22a330ccad507a" 21 | } 22 | } 23 | */ 24 | 25 | /** 26 | * @apiDefine categoryResponse 27 | * @apiSuccessExample Success-Response: 28 | { 29 | "status": 200, 30 | "errorCode": 0, 31 | "data": { 32 | "_id": "5bbc6241dbaf531fac21b662", 33 | "name": "类别1", 34 | "userId": { 35 | "intruction": "这个人很懒,什么都有没留下、、、", 36 | "logo": "/upload/images/defaultlogo.png", 37 | "role": "ordinary users", 38 | "_id": "5bbc59366501713374220caa", 39 | "name": "morehao", 40 | "nickName": "毛浩先生", 41 | "age": 24, 42 | "sex": "male", 43 | "company": "太原科技大学", 44 | "website": "morehao.com", 45 | "createdAt": "2018-10-09 15:31:02", 46 | "updatedAt": "2018-10-09 15:31:02", 47 | "__v": 0, 48 | "lastLogin": "2018-10-09 16:56:54", 49 | "id": "5bbc59366501713374220caa" 50 | }, 51 | "createdAt": "2018-10-09 16:09:37", 52 | "updatedAt": "2018-10-09 16:09:37", 53 | "__v": 0, 54 | "id": "5bbc6241dbaf531fac21b662" 55 | } 56 | } 57 | */ 58 | 59 | /** 60 | * @apiDefine categoryListResponse 61 | * @apiSuccessExample Success-Response: 62 | { 63 | "status": 200, 64 | "errorCode": 0, 65 | "data": { 66 | "dataCount": 2, 67 | "list": [ 68 | { 69 | "_id": "5bbc5fbe2e22a330ccad507a", 70 | "name": "类别", 71 | "userId": { 72 | "_id": "5bbc59366501713374220caa", 73 | "intruction": "这个人很懒,什么都有没留下、、、", 74 | "logo": "/upload/images/defaultlogo.png", 75 | "role": "ordinary users", 76 | "name": "morehao", 77 | "nickName": "毛浩先生", 78 | "age": 24, 79 | "sex": "male", 80 | "company": "太原科技大学", 81 | "website": "morehao.com", 82 | "createdAt": "2018-10-09T07:31:02.362Z", 83 | "updatedAt": "2018-10-09T07:31:02.000Z", 84 | "__v": 0 85 | }, 86 | "createdAt": "2018-10-09T07:58:54.940Z", 87 | "updatedAt": "2018-10-09T07:58:54.000Z", 88 | "__v": 0 89 | } 90 | ] 91 | } 92 | } 93 | */ 94 | 95 | /** 96 | * @apiDefine categoryNotFind 97 | * @apiErrorExample {json} Error-Response: 98 | * { 99 | * status: 200, 100 | * errorCode: 20302, 101 | * errorMsg: '该分类不存在' 102 | * } 103 | */ 104 | -------------------------------------------------------------------------------- /app/services/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const qiniu = require('qiniu') 3 | const fs = require('fs') 4 | const BaseService = require('./base') 5 | const mdb = require('../models') 6 | const {resHandler} = require('../myutil') 7 | const {settings} = require('../../config') 8 | 9 | class ArticleService extends BaseService { 10 | constructor (model) { 11 | super(model) 12 | this.model = 'Article' 13 | } 14 | async addArticle (params) { 15 | try { 16 | const query = { 17 | title: params.title, 18 | authorId: params.authorId 19 | } 20 | const findRes = await super.findOne(query) 21 | if (findRes) { 22 | const errorMsg = 'ARTICLE_HAS_EXITS' 23 | throw errorMsg 24 | } 25 | const result = await super.save(params) 26 | return result 27 | } catch (error) { 28 | throw error 29 | } 30 | } 31 | async editById (id, params) { 32 | try { 33 | const findRes = await super.findById(id) 34 | if (!findRes) { 35 | const errorMsg = 'ARTICLE_NOT_EXITS' 36 | throw errorMsg 37 | } 38 | await super.updateById(id, params) 39 | const result = resHandler.getSuccessMsg('ARTICLE_UPDATE_SUCCESS') 40 | return result 41 | } catch (error) { 42 | throw error 43 | } 44 | } 45 | async getArticleList (params) { 46 | try { 47 | const result = await super.list(params) 48 | return result 49 | } catch (error) { 50 | throw error 51 | } 52 | } 53 | async getArticleById (id) { 54 | try { 55 | const result = await mdb.Article.findById(id) 56 | .populate([{path: 'authorId', select: '-password'}, {path: 'categoryId'}]) 57 | if (!result) { 58 | const errorMsg = 'ARTICLE_NOT_EXITS' 59 | throw errorMsg 60 | } 61 | return result 62 | } catch (error) { 63 | throw error 64 | } 65 | } 66 | async qiniuUpload (localFile, key) { 67 | try { 68 | const {accessKey, secretKey, bucket} = settings.qiniuConfig 69 | const mac = new qiniu.auth.digest.Mac(accessKey, secretKey) 70 | const putPolicy = new qiniu.rs.PutPolicy({scope: bucket}) 71 | const uploadToken = putPolicy.uploadToken(mac) 72 | const config = new qiniu.conf.Config() 73 | config.zone = qiniu.zone.Zone_z1 74 | const formUploader = new qiniu.form_up.FormUploader(config) 75 | const putExtra = new qiniu.form_up.PutExtra() 76 | return new Promise(function (resolve, reject) { 77 | formUploader.putFile(uploadToken, key, localFile, putExtra, function (respErr, respBody, respInfo) { 78 | if (respErr) return reject(respErr) 79 | if (respInfo.statusCode === 200) return resolve(respBody) 80 | reject(new Error('上传失败:statusCode !== 200')) 81 | }) 82 | }) 83 | } catch (error) { 84 | throw error 85 | } 86 | } 87 | 88 | async saveFile (filePath, target, fileName) { 89 | try { 90 | const readStream = fs.createReadStream(filePath) 91 | const writeStream = fs.createWriteStream(target) 92 | readStream.pipe(writeStream) 93 | return fileName 94 | } catch (error) { 95 | throw error 96 | } 97 | } 98 | } 99 | module.exports = new ArticleService() 100 | -------------------------------------------------------------------------------- /app/services/users.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const BaseService = require('./base') 3 | const mdb = require('../models') 4 | const {crypto, format, resHandler} = require('../myutil') 5 | const {settings} = require('../../config') 6 | class UserService extends BaseService { 7 | constructor (model) { 8 | super(model) 9 | this.model = 'User' 10 | } 11 | async addUser (data) { 12 | try { 13 | const findRes = await mdb.User.findOne({name: data.name}) 14 | if (findRes) { 15 | const errorMsg = 'USER_HAS_EXITS' 16 | throw errorMsg 17 | } 18 | data.password = crypto.encrypted(data.password, settings.saltKey) 19 | const result = await mdb.User.create(data) 20 | return format.user(result.toObject()) 21 | } catch (error) { 22 | throw error 23 | } 24 | } 25 | async getUserByName (name) { 26 | try { 27 | const user = await mdb.User.findOne({name: name}, null, {lean: true}) 28 | return user 29 | } catch (error) { 30 | const errorMsg = 'SERVER_ERROR' 31 | throw errorMsg 32 | } 33 | } 34 | async getUserById (id) { 35 | try { 36 | const user = await mdb.User.findById(id) 37 | return user 38 | } catch (error) { 39 | const errorMsg = 'SERVER_ERROR' 40 | throw errorMsg 41 | } 42 | } 43 | async destroy (params) { 44 | try { 45 | const findRes = await mdb.User.findById(params) 46 | if (!findRes) { 47 | const errorMsg = 'USER_NOT_EXITS' 48 | throw errorMsg 49 | } 50 | await mdb.User.remove({_id: params}) 51 | const result = resHandler.getSuccessMsg('USER_DELETE_SUCCESS') 52 | return result 53 | } catch (error) { 54 | throw error 55 | } 56 | } 57 | async update (params) { 58 | try { 59 | await mdb.User.findById(params._id) 60 | await mdb.User.update({_id: params._id}, {$set: params}) 61 | const result = resHandler.getSuccessMsg('USER_UPDATE_SUCCESS') 62 | return result 63 | } catch (error) { 64 | const errorMsg = 'USER_UPDATE_FAILED' 65 | throw errorMsg 66 | } 67 | } 68 | async getUserList (params) { 69 | try { 70 | const result = super.list(params) 71 | return result 72 | } catch (error) { 73 | throw error 74 | } 75 | } 76 | async detail (params) { 77 | try { 78 | const findRes = await mdb.User.findById(params) 79 | const result = format.user(findRes.toObject()) 80 | return result 81 | } catch (error) { 82 | const errorMsg = 'USER_NOT_EXITS' 83 | throw errorMsg 84 | } 85 | } 86 | async login (params) { 87 | try { 88 | const findRes = await mdb.User.findOne({name: params.name}, null, {lean: true}) 89 | if (!findRes) { 90 | const errorMsg = 'USER_NOT_EXITS' 91 | throw errorMsg 92 | } 93 | const inputPasswd = crypto.encrypted(params.password, settings.saltKey) 94 | const equal = await crypto.checkPasswd(inputPasswd, findRes.password) 95 | if (!equal) { 96 | const errorMsg = 'USER_PASSWORD_WRONG' 97 | throw errorMsg 98 | } 99 | const result = format.user(findRes) 100 | return result 101 | } catch (error) { 102 | const errorMsg = 'USER_LOGIN_FAILED' 103 | throw errorMsg 104 | } 105 | } 106 | async test (params) { 107 | this.body = 2 108 | const result = super.getUserByName(params) 109 | return result 110 | } 111 | } 112 | 113 | module.exports = new UserService() 114 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # [1.0.0](https://github.com/morehao/express-restfulApi/compare/v0.0.2...v1.0.0) (2018-09-12) 3 | 4 | 5 | ### Bug Fixes 6 | 7 | * **category:** category中controller命名修改 ([8c16c3b](https://github.com/morehao/express-restfulApi/commit/8c16c3b)) 8 | * **users:** 用户列表接口修复,populate为空数组 ([9d3ec42](https://github.com/morehao/express-restfulApi/commit/9d3ec42)) 9 | 10 | 11 | ### Features 12 | 13 | * **article:** 上传接口,获取文件的信息 ([8d2a95f](https://github.com/morehao/express-restfulApi/commit/8d2a95f)) 14 | * **article:** 文章的增、改、列表接口 ([09e8e08](https://github.com/morehao/express-restfulApi/commit/09e8e08)) 15 | * **article:** 新增upload接口,还未实现接口的内部逻辑 ([aacb78b](https://github.com/morehao/express-restfulApi/commit/aacb78b)) 16 | * **article:** 新增文章接口 ([e5b61a6](https://github.com/morehao/express-restfulApi/commit/e5b61a6)) 17 | * **article-category:** 增加文章类别的修改和列表接口 ([9ec1810](https://github.com/morehao/express-restfulApi/commit/9ec1810)) 18 | * **article-category:** 文章分类,建表和创建的接口 ([35d0b59](https://github.com/morehao/express-restfulApi/commit/35d0b59)) 19 | * **base-service:** baseservice增加findById,家、公司都可搬砖 ([89808cb](https://github.com/morehao/express-restfulApi/commit/89808cb)) 20 | * **logs:** 优化日志,格式化日志的保存与输出 ([a909324](https://github.com/morehao/express-restfulApi/commit/a909324)) 21 | * **upload:** 上传图片到七牛云 ([ea89b23](https://github.com/morehao/express-restfulApi/commit/ea89b23)) 22 | * **validator:** 添加validator校验 ([c5bda27](https://github.com/morehao/express-restfulApi/commit/c5bda27)) 23 | 24 | 25 | 26 | 27 | ## [0.0.2](https://github.com/morehao/express-restfulApi/compare/v0.0.1...v0.0.2) (2018-07-10) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * **files:** 删除一些无用的文件 ([885b012](https://github.com/morehao/express-restfulApi/commit/885b012)) 33 | * **package-lock:** 修复github提示的hoek版本过低问题 ([bc57a40](https://github.com/morehao/express-restfulApi/commit/bc57a40)) 34 | * **token:** 修复中间件检测不到token过期的问题 ([11f2657](https://github.com/morehao/express-restfulApi/commit/11f2657)) 35 | 36 | 37 | ### Features 38 | 39 | * **build:** 初始化创建管理员用户 ([497f904](https://github.com/morehao/express-restfulApi/commit/497f904)) 40 | * **changelog:** 新增commit提交日志 ([cca17a2](https://github.com/morehao/express-restfulApi/commit/cca17a2)) 41 | * **cros:** 设置跨域访问 ([744cdd7](https://github.com/morehao/express-restfulApi/commit/744cdd7)) 42 | * **data-return:** 对返回的数据进行格式化处理 ([762ef76](https://github.com/morehao/express-restfulApi/commit/762ef76)) 43 | * **error-handler:** 通过中间件对错误进行统一处理 ([96fb203](https://github.com/morehao/express-restfulApi/commit/96fb203)) 44 | * **list:** 分页处理,增加总数据数目dataCount ([f8d7c68](https://github.com/morehao/express-restfulApi/commit/f8d7c68)) 45 | * **log4js:** 请求日志console到命令行终端工具 ([db27a8f](https://github.com/morehao/express-restfulApi/commit/db27a8f)) 46 | * **log4js:** 通过log4js记录程序日志 ([d131947](https://github.com/morehao/express-restfulApi/commit/d131947)) 47 | * **mocha-test:** 利用mocha测试框架编写单元测试 ([7baa7e4](https://github.com/morehao/express-restfulApi/commit/7baa7e4)) 48 | * **package-lock:** 删除package-lock.json,重新生成该文件 ([e21aa6d](https://github.com/morehao/express-restfulApi/commit/e21aa6d)) 49 | * **role-right:** 权限管理model文件 ([91223ec](https://github.com/morehao/express-restfulApi/commit/91223ec)) 50 | * **study-async:** 新增node改写回调函数的学习 ([dfd2ccc](https://github.com/morehao/express-restfulApi/commit/dfd2ccc)) 51 | * **token:** JWT实现token机制 ([bcbefae](https://github.com/morehao/express-restfulApi/commit/bcbefae)) 52 | * **user-login:** 新增用户登录接口 ([0fb860d](https://github.com/morehao/express-restfulApi/commit/0fb860d)) 53 | * **users:** 用户管理的增删改查基本接口 ([54b534d](https://github.com/morehao/express-restfulApi/commit/54b534d)) 54 | 55 | 56 | ### Performance Improvements 57 | 58 | * **middlewares:** 优化中间件注册和使用 ([1667338](https://github.com/morehao/express-restfulApi/commit/1667338)) 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/controllers/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const uuidv1 = require('uuid/v1') 3 | const path = require('path') 4 | const Services = require('../services') 5 | const {auth, resHandler, paramsHandler, validator, upload} = require('../myutil') 6 | const {pageConfig, settings} = require('../../config') 7 | class ArticleController { 8 | async create (req, res) { 9 | try { 10 | if (validator.isEmpty(req.body.title, {ignore_whitespace: true})) { 11 | const errorMsg = 'TITLE_IS_EMPTY' 12 | throw errorMsg 13 | } 14 | if (validator.isEmpty(req.body.content, {ignore_whitespace: true})) { 15 | const errorMsg = 'CONTENT_IS_EMPTY' 16 | throw errorMsg 17 | } 18 | const userInfo = auth.verifyToken(req.headers.token) 19 | req.body.authorId = userInfo.userId 20 | const result = await Services.article.addArticle(req.body) 21 | res.sendOk(result) 22 | } catch (error) { 23 | const errorRes = resHandler.getErrorRes(error) 24 | res.sendErr(errorRes) 25 | } 26 | } 27 | async update (req, res) { 28 | try { 29 | const result = await Services.article.editById(req.params._id, req.body) 30 | res.sendOk(result) 31 | } catch (error) { 32 | res.sendErr(error) 33 | } 34 | } 35 | async upload (req, res) { 36 | try { 37 | const fileInfo = await upload.getFileInfo(req) 38 | let tasks = [] 39 | let result 40 | if (!settings.qiniuConfig.accessKey) { 41 | let saveRes = [] 42 | for (let item in fileInfo.files) { 43 | const uid = uuidv1() 44 | const filePath = fileInfo.files[item].path 45 | const fileName = uid + path.extname(fileInfo.files[item].name).toLowerCase() 46 | const target = path.join(settings.upload.savePath, fileName) 47 | saveRes.push(await Services.article.saveFile(filePath, target, fileName)) 48 | } 49 | result = saveRes.map(item => { 50 | let obj = { 51 | imageUrl: `${settings.website}${settings.upload.showPath}${item}`, 52 | imageName: item, 53 | resource: 'server' 54 | } 55 | return obj 56 | }) 57 | } else { 58 | for (let item in fileInfo.files) { 59 | const uid = uuidv1() 60 | const filePath = fileInfo.files[item].path 61 | const fileName = uid + path.extname(fileInfo.files[item].name).toLowerCase() 62 | tasks.push(Services.article.qiniuUpload(filePath, fileName)) 63 | } 64 | const qiniuRes = await Promise.all(tasks) 65 | result = qiniuRes.map(item => { 66 | let obj = { 67 | imageUrl: `${settings.qiniuConfig.originUrl}${item.key}`, 68 | imageName: item.key, 69 | resource: 'qiniu' 70 | } 71 | return obj 72 | }) 73 | } 74 | res.sendOk(result) 75 | } catch (error) { 76 | res.sendErr(error) 77 | } 78 | } 79 | async detail (req, res) { 80 | try { 81 | const result = await Services.article.getArticleById(req.params._id) 82 | res.sendOk(result) 83 | } catch (error) { 84 | const errorRes = resHandler.getErrorRes(error) 85 | res.sendErr(errorRes) 86 | } 87 | } 88 | async list (req, res) { 89 | try { 90 | // 翻页参数处理 91 | const offset = paramsHandler.offsetFormat(req.query, pageConfig.article) 92 | console.log(req.query) 93 | const queryObj = { 94 | condition: req.query, 95 | skipCount: offset.skipCount, 96 | pagesize: offset.pagesize, 97 | sortRule: offset.sortRule, 98 | populate: [{path: 'authorId', select: '-password'}, {path: 'categoryId'}] 99 | } 100 | const result = await Services.article.getArticleList(queryObj) 101 | res.sendOk(result) 102 | } catch (error) { 103 | const errorRes = resHandler.getErrorRes(error) 104 | res.sendErr(errorRes) 105 | } 106 | } 107 | } 108 | module.exports = new ArticleController() 109 | -------------------------------------------------------------------------------- /app/apidoc/define/article.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | @apiDefine articleParams 4 | @apiParam {String} title 文章标题 5 | @apiParam {String} content 文章内容 6 | @apiParam {String} type 文章格式,可选值为['markdown', 'html'] 7 | */ 8 | 9 | /** 10 | @apiDefine articleParamsOptional 11 | @apiParam {String} title 可选,文章标题 12 | @apiParam {String} type 可选,文章格式,可选值为['markdown', 'html'] 13 | */ 14 | 15 | /** 16 | * @apiDefine articleCreareResponse 17 | * @apiSuccessExample Success-Response: 18 | { 19 | "status": 200, 20 | "errorCode": 0, 21 | "data": { 22 | "countInfo": { 23 | "commentCount": 0, 24 | "visitCount": 0, 25 | "collectCount": 0, 26 | "likeCount": 0 27 | }, 28 | "top": false, 29 | "good": false, 30 | "deleted": false, 31 | "_id": "5bbc77aded51c71df03f3ba9", 32 | "title": "标题1", 33 | "content": "内容1", 34 | "categoryId": "5bbc6241dbaf531fac21b662", 35 | "authorId": "5bbc59366501713374220caa", 36 | "createAt": "2018-10-09T09:41:01.086Z", 37 | "updateAt": "2018-10-09T09:41:01.087Z", 38 | "__v": 0 39 | } 40 | } 41 | */ 42 | 43 | /** 44 | * @apiDefine articleResponse 45 | * @apiSuccessExample Success-Response: 46 | { 47 | "status": 200, 48 | "errorCode": 0, 49 | "data": { 50 | "countInfo": { 51 | "commentCount": 0, 52 | "visitCount": 0, 53 | "collectCount": 0, 54 | "likeCount": 0 55 | }, 56 | "top": false, 57 | "good": false, 58 | "deleted": false, 59 | "_id": "5bbc77aded51c71df03f3ba9", 60 | "title": "标题", 61 | "content": "内容1", 62 | "categoryId": { 63 | "_id": "5bbc6241dbaf531fac21b662", 64 | "name": "类别1", 65 | "userId": "5bbc59366501713374220caa", 66 | "createdAt": "2018-10-09 16:09:37", 67 | "updatedAt": "2018-10-09 16:09:37", 68 | "__v": 0, 69 | "id": "5bbc6241dbaf531fac21b662" 70 | }, 71 | "authorId": { 72 | "intruction": "这个人很懒,什么都有没留下、、、", 73 | "logo": "/upload/images/defaultlogo.png", 74 | "role": "ordinary users", 75 | "_id": "5bbc59366501713374220caa", 76 | "name": "morehao", 77 | "nickName": "毛浩先生", 78 | "age": 24, 79 | "sex": "male", 80 | "company": "太原科技大学", 81 | "website": "morehao.com", 82 | "createdAt": "2018-10-09 15:31:02", 83 | "updatedAt": "2018-10-09 15:31:02", 84 | "__v": 0, 85 | "lastLogin": "2018-10-09 17:48:43", 86 | "id": "5bbc59366501713374220caa" 87 | }, 88 | "createAt": "2018-10-09T09:41:01.086Z", 89 | "updateAt": "2018-10-09T09:41:01.087Z", 90 | "__v": 0 91 | } 92 | } 93 | */ 94 | 95 | /** 96 | * @apiDefine articleListResponse 97 | * @apiSuccessExample Success-Response: 98 | { 99 | "status": 200, 100 | "errorCode": 0, 101 | "data": { 102 | "dataCount": 1, 103 | "list": [ 104 | { 105 | "_id": "5bbc77aded51c71df03f3ba9", 106 | "countInfo": { 107 | "commentCount": 0, 108 | "visitCount": 0, 109 | "collectCount": 0, 110 | "likeCount": 0 111 | }, 112 | "top": false, 113 | "good": false, 114 | "deleted": false, 115 | "title": "标题", 116 | "content": "内容1", 117 | "categoryId": { 118 | "_id": "5bbc6241dbaf531fac21b662", 119 | "name": "类别1", 120 | "userId": "5bbc59366501713374220caa", 121 | "createdAt": "2018-10-09T08:09:37.986Z", 122 | "updatedAt": "2018-10-09T08:09:37.000Z", 123 | "__v": 0 124 | }, 125 | "authorId": { 126 | "_id": "5bbc59366501713374220caa", 127 | "intruction": "这个人很懒,什么都有没留下、、、", 128 | "logo": "/upload/images/defaultlogo.png", 129 | "role": "ordinary users", 130 | "name": "morehao", 131 | "nickName": "毛浩先生", 132 | "age": 24, 133 | "sex": "male", 134 | "company": "太原科技大学", 135 | "website": "morehao.com", 136 | "createdAt": "2018-10-09T07:31:02.362Z", 137 | "updatedAt": "2018-10-09T07:31:02.000Z", 138 | "__v": 0 139 | }, 140 | "createAt": "2018-10-09T09:41:01.086Z", 141 | "updateAt": "2018-10-09T09:41:01.087Z", 142 | "__v": 0 143 | } 144 | ] 145 | } 146 | } 147 | */ 148 | 149 | /** 150 | * @apiDefine articleNotFind 151 | * @apiErrorExample {json} Error-Response: 152 | * { 153 | * status: 200, 154 | * errorCode: 20402, 155 | * errorMsg: '该文章不存在' 156 | * } 157 | */ 158 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | exports 55 | 56 | 57 | 58 | 60 | 61 | 62 | 63 | 64 | 65 | true 66 | DEFINITION_ORDER 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 |