├── .babelrc ├── README.md ├── app.js ├── app ├── controllers │ ├── app.js │ └── user.js ├── dbhelper │ └── userHelper.js ├── models │ └── user.js └── service │ └── service.js ├── config └── router.js └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koa2-mongodb-server 2 | 利用koa2+mongodb搭建一套简易的nodejs后台服务,用于为客户端提供数据请求的数据api接口 3 | 4 | # 使用说明 5 | - 安装NodeJs 6 | koa2下,最好安装node7.0以上版本,不然会报错,因为低版本下Koa2部分ES7的语法会不支持 7 | - 安装MongoDB数据库 8 | 可以参考官方文档:https://docs.mongodb.com/manual/installation/ ;OS X 系统下推荐使用Homebrew进行安装。 9 | - 安装相关依赖 10 | cd到项目根目录下,执行:npm install (淘宝镜像下:cnpm install) 11 | - 终端开启nodejs服务 12 | cd到项目根目录,执行:node app 13 | - DHC测试接口 14 | 安装Chrome插件DHC,对相关接口进行测试,如: 15 | 16 | ![signup接口测试](http://upload-images.jianshu.io/upload_images/5307186-1efca5d7c8ddd2ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 17 | 18 | # 项目目录结构说明 19 | 20 | ![项目目录结构](http://upload-images.jianshu.io/upload_images/5307186-d29cb13923ae11db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 21 | 22 | - 控制器:controllers/user.js 23 | 用于接收用户模块的接口请求,如注册、更新、删除、获取用于列表、搜索用户等相关请求,以下是注册请求的举例。主要是通过koa-router实现路由转发请求到该接口,然后使用封装的dbHelper对mongodb进行操作(当然这里我直接使用了mongose的api进行数据库的操作了,比较low)。 24 | 25 | ![用户注册接口的实现](http://upload-images.jianshu.io/upload_images/5307186-bd283eeebc2a704d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 26 | 27 | - model层:表结构的定义,model/user.js 28 | mongoose的语法,先定义一个schema,再导出一个model。mongoose的文档可以参考:http://www.nodeclass.com/api/mongoose.html 。 29 | 30 | ![用户表结构](http://upload-images.jianshu.io/upload_images/5307186-a2f18ca07c580904.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 31 | 32 | - koa2的使用 33 | 还是贴一下文档吧:https://github.com/koajs/koa 34 | 35 | - koa-router的使用 36 | 再贴文档:https://github.com/alexmingoia/koa-router 37 | 38 | # 使用RAP 39 | 使用淘宝的rap来记录设计项目的API接口:http://rapapi.org/org/index.do 40 | 41 | ![更新用户信息接口的设计](http://upload-images.jianshu.io/upload_images/5307186-a628a6d42d64f4bf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const fs = require('fs') 4 | const path = require('path') 5 | const mongoose = require('mongoose') 6 | 7 | const db = 'mongodb://localhost/test' 8 | 9 | /** 10 | * mongoose连接数据库 11 | * @type {[type]} 12 | */ 13 | mongoose.Promise = require('bluebird') 14 | mongoose.connect(db) 15 | 16 | /** 17 | * 获取数据库表对应的js对象所在的路径 18 | * @type {[type]} 19 | */ 20 | const models_path = path.join(__dirname, '/app/models') 21 | 22 | 23 | /** 24 | * 已递归的形式,读取models文件夹下的js模型文件,并require 25 | * @param {[type]} modelPath [description] 26 | * @return {[type]} [description] 27 | */ 28 | var walk = function(modelPath) { 29 | fs 30 | .readdirSync(modelPath) 31 | .forEach(function(file) { 32 | var filePath = path.join(modelPath, '/' + file) 33 | var stat = fs.statSync(filePath) 34 | 35 | if (stat.isFile()) { 36 | if (/(.*)\.(js|coffee)/.test(file)) { 37 | require(filePath) 38 | } 39 | } 40 | else if (stat.isDirectory()) { 41 | walk(filePath) 42 | } 43 | }) 44 | } 45 | walk(models_path) 46 | 47 | require('babel-register') 48 | const Koa = require('koa') 49 | const logger = require('koa-logger') 50 | const session = require('koa-session') 51 | const bodyParser = require('koa-bodyparser') 52 | const app = new Koa() 53 | 54 | app.keys = ['zhangivon'] 55 | app.use(logger()) 56 | app.use(session(app)) 57 | app.use(bodyParser()) 58 | 59 | 60 | /** 61 | * 使用路由转发请求 62 | * @type {[type]} 63 | */ 64 | const router = require('./config/router')() 65 | 66 | app 67 | .use(router.routes()) 68 | .use(router.allowedMethods()); 69 | 70 | 71 | 72 | app.listen(1234) 73 | console.log('app started at port 1234...'); -------------------------------------------------------------------------------- /app/controllers/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // 用于封装controllers的公共方法 4 | 5 | var mongoose = require('mongoose') 6 | var uuid = require('uuid') 7 | var User = mongoose.model('User') 8 | 9 | exports.hasBody = async (ctx, next) => { 10 | var body = ctx.request.body || {} 11 | // console.log(this.query.phonenumber) 12 | console.log(body) 13 | 14 | if (Object.keys(body).length === 0) { 15 | ctx.body = { 16 | success: false, 17 | err: '某参数缺失' 18 | } 19 | 20 | return next 21 | } 22 | 23 | await next() 24 | } 25 | 26 | // 检验token 27 | exports.hasToken = async (ctx, next) => { 28 | var accessToken = ctx.query.accessToken 29 | 30 | if (!accessToken) { 31 | accessToken = ctx.request.body.accessToken 32 | } 33 | 34 | if (!accessToken) { 35 | ctx.body = { 36 | success: false, 37 | err: '令牌失效' 38 | } 39 | 40 | return next 41 | } 42 | 43 | var user = await User.findOne({ 44 | accessToken: accessToken 45 | }) 46 | .exec() 47 | 48 | if (!user) { 49 | ctx.body = { 50 | success: false, 51 | err: '用户没登陆' 52 | } 53 | 54 | return next 55 | } 56 | 57 | ctx.session = ctx.session || {} 58 | ctx.session.user = user 59 | 60 | await next() 61 | } -------------------------------------------------------------------------------- /app/controllers/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var xss = require('xss') 4 | var mongoose = require('mongoose') 5 | var User = mongoose.model('User') 6 | var uuid = require('uuid') 7 | // var userHelper = require('../dbhelper/userHelper') 8 | import userHelper from '../dbhelper/userHelper' 9 | 10 | /** 11 | * 注册新用户 12 | * @param {Function} next [description] 13 | * @yield {[type]} [description] 14 | */ 15 | exports.signup = async (ctx, next) => { 16 | var phoneNumber = xss(ctx.request.body.phoneNumber.trim()) 17 | var user = await User.findOne({ 18 | phoneNumber: phoneNumber 19 | }).exec() 20 | console.log(user) 21 | 22 | var verifyCode = Math.floor(Math.random()*10000+1) 23 | console.log(phoneNumber) 24 | if (!user) { 25 | var accessToken = uuid.v4() 26 | 27 | user = new User({ 28 | nickname: '测试用户', 29 | avatar: 'http://upload-images.jianshu.io/upload_images/5307186-eda1b28e54a4d48e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240', 30 | phoneNumber: xss(phoneNumber), 31 | verifyCode: verifyCode, 32 | accessToken: accessToken 33 | }) 34 | } 35 | else { 36 | user.verifyCode = verifyCode 37 | } 38 | 39 | try { 40 | user = await user.save() 41 | ctx.body = { 42 | success: true 43 | } 44 | } 45 | catch (e) { 46 | ctx.body = { 47 | success: false 48 | } 49 | 50 | return next 51 | } 52 | 53 | } 54 | 55 | /** 56 | * 更新用户信息操作 57 | * @param {[type]} ctx [description] 58 | * @param {Function} next [description] 59 | * @return {[type]} [description] 60 | */ 61 | exports.update = async (ctx, next) => { 62 | var body = ctx.request.body 63 | var user = ctx.session.user 64 | var fields = 'avatar,gender,age,nickname,breed'.split(',') 65 | 66 | fields.forEach(function(field) { 67 | if (body[field]) { 68 | user[field] = xss(body[field].trim()) 69 | } 70 | }) 71 | 72 | user = await user.save() 73 | 74 | ctx.body = { 75 | success: true, 76 | data: { 77 | nickname: user.nickname, 78 | accessToken: user.accessToken, 79 | avatar: user.avatar, 80 | age: user.age, 81 | breed: user.breed, 82 | gender: user.gender, 83 | _id: user._id 84 | } 85 | } 86 | } 87 | 88 | 89 | 90 | /** 91 | * 数据库接口测试 92 | * @param {[type]} ctx [description] 93 | * @param {Function} next [description] 94 | * @return {[type]} [description] 95 | */ 96 | exports.users = async (ctx, next) => { 97 | var data = await userHelper.findAllUsers() 98 | // var obj = await userHelper.findByPhoneNumber({phoneNumber : '13525584568'}) 99 | // console.log('obj=====================================>'+obj) 100 | 101 | ctx.body = { 102 | success: true, 103 | data 104 | } 105 | } 106 | exports.addUser = async (ctx, next) => { 107 | var user = new User({ 108 | nickname: '测试用户', 109 | avatar: 'http://ip.example.com/u/xxx.png', 110 | phoneNumber: xss('13800138000'), 111 | verifyCode: '5896', 112 | accessToken: uuid.v4() 113 | }) 114 | var user2 = await userHelper.addUser(user) 115 | if(user2){ 116 | ctx.body = { 117 | success: true, 118 | data : user2 119 | } 120 | } 121 | } 122 | exports.deleteUser = async (ctx, next) => { 123 | const phoneNumber = xss(ctx.request.body.phoneNumber.trim()) 124 | console.log(phoneNumber) 125 | var data = await userHelper.deleteUser({phoneNumber}) 126 | ctx.body = { 127 | success: true, 128 | data 129 | } 130 | } -------------------------------------------------------------------------------- /app/dbhelper/userHelper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var mongoose = require('mongoose') 4 | var User = mongoose.model('User') 5 | 6 | /** 7 | * 通过电话号码查询 8 | * @param {[type]} options.phoneNumber [description] 9 | * @return {[type]} [description] 10 | */ 11 | exports.findByPhoneNumber = async ({phoneNumber}) => { 12 | var query = User.find({phoneNumber}) 13 | var res = null 14 | await query.exec(function(err, user) { 15 | if(err) { 16 | res = {} 17 | }else { 18 | res = user 19 | } 20 | }) 21 | // console.log('res====>' + res) 22 | return res; 23 | } 24 | 25 | /** 26 | * 查找所用用户 27 | * @return {[type]} [description] 28 | */ 29 | exports.findAllUsers = async () => { 30 | var query = User.find({}); 31 | var res = [] 32 | await query.exec(function(err, users) { 33 | if(err) { 34 | res = [] 35 | }else { 36 | res = users; 37 | } 38 | }) 39 | return res 40 | } 41 | 42 | /** 43 | * 增加用户 44 | * @param {[User]} user [mongoose.model('User')] 45 | * @return {[type]} [description] 46 | */ 47 | exports.addUser = async (user) => { 48 | user = await user.save() 49 | return user 50 | } 51 | 52 | /** 53 | * 删除用户 54 | * @param {[type]} options.phoneNumber [description] 55 | * @return {[type]} [description] 56 | */ 57 | exports.deleteUser = async ({phoneNumber}) => { 58 | var flag = false 59 | console.log('flag==========>'+flag) 60 | await User.remove({phoneNumber}, function(err) { 61 | if(err) { 62 | flag = false 63 | // return false 64 | }else{ 65 | flag = true 66 | } 67 | 68 | }) 69 | console.log('flag=====await=====>'+flag) 70 | return flag 71 | } 72 | -------------------------------------------------------------------------------- /app/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var mongoose = require('mongoose') 4 | var Schema = mongoose.Schema; 5 | 6 | /** 7 | * 定义一个模式(相当于传统意义的表结构) 8 | * 每个模式映射mongoDB的一个集合, 9 | * 它定义(只是定义,不是实现)这个集合里面文档的结构,就是定义这个文档有什么字段,字段类型是什么,字段默认值是什么等。 10 | * 除了定义结构外,还定义文档的实例方法,静态模型方法,复合索引,中间件等 11 | * @type {mongoose} 12 | */ 13 | var UserSchema = new Schema({ 14 | phoneNumber: { 15 | unique: true, 16 | type: String 17 | }, 18 | areaCode: String, 19 | verifyCode: String, 20 | verified: { 21 | type: Boolean, 22 | default: false 23 | }, 24 | accessToken: String, 25 | nickname: String, 26 | gender: String, 27 | breed: String, 28 | age: String, 29 | avatar: String, 30 | meta: { 31 | createAt: { 32 | type: Date, 33 | dafault: Date.now() 34 | }, 35 | updateAt: { 36 | type: Date, 37 | dafault: Date.now() 38 | } 39 | } 40 | }) 41 | 42 | // Defines a pre hook for the document. 43 | UserSchema.pre('save', function(next) { 44 | if (this.isNew) { 45 | this.meta.createAt = this.meta.updateAt = Date.now() 46 | } 47 | else { 48 | this.meta.updateAt = Date.now() 49 | } 50 | next() 51 | }) 52 | 53 | 54 | /** 55 | * 定义模型User 56 | * 模型用来实现我们定义的模式,调用mongoose.model来编译Schema得到Model 57 | * @type {[type]} 58 | */ 59 | // 参数User 数据库中的集合名称, 不存在会创建. 60 | var User = mongoose.model('User', UserSchema) 61 | 62 | module.exports = User 63 | 64 | /** 65 | * nodejs中文社区这篇帖子对mongoose的用法总结的不错:https://cnodejs.org/topic/548e54d157fd3ae46b233502 66 | */ -------------------------------------------------------------------------------- /app/service/service.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // 定义第三方服务:比如七牛云、第三方手机短信验证码等服务 -------------------------------------------------------------------------------- /config/router.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Router = require('koa-router') 4 | const User = require('../app/controllers/user') 5 | const App = require('../app/controllers/app') 6 | 7 | module.exports = function(){ 8 | var router = new Router({ 9 | prefix: '/api' 10 | }) 11 | 12 | // user 13 | router.post('/u/signup', App.hasBody, User.signup) 14 | router.post('/u/update', App.hasBody, App.hasToken, User.update) 15 | 16 | // DB Interface test 17 | router.get('/test/user/users',User.users) 18 | router.post('/test/user/add',User.addUser) 19 | router.post('/test/user/delete',User.deleteUser) 20 | 21 | return router 22 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-mongodb-server", 3 | "version": "1.0.0", 4 | "description": "利用node+koa+mongoose搭建的后台服务,提供restful接口api", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "koa", 11 | "node", 12 | "mongodb" 13 | ], 14 | "author": "zhangivon", 15 | "license": "MIT", 16 | "dependencies": { 17 | "babel-preset-env": "^1.4.0", 18 | "babel-register": "^6.24.1", 19 | "bluebird": "^3.5.0", 20 | "cloudinary": "^1.8.0", 21 | "koa": "^2.2.0", 22 | "koa-bodyparser": "^4.2.0", 23 | "koa-logger": "^2.0.1", 24 | "koa-router": "^7.1.1", 25 | "koa-session": "^5.0.0", 26 | "lodash": "^4.17.4", 27 | "mongoose": "^4.9.5", 28 | "qiniu": "^6.1.13", 29 | "sha1": "^1.1.1", 30 | "speakeasy": "^2.0.0", 31 | "uuid": "^3.0.1", 32 | "xss": "^0.3.3" 33 | } 34 | } 35 | --------------------------------------------------------------------------------