乐趣区

expressmongodbreacttypescriptantd搭建管理后台系统后端前端上

express+mongodb 搭建具有用户注册、登录、验证等功能的后端

  • 安装 node

安装 node 的教程上网一大把,这里就不细说了,建议使用 nvm 安装,可以随时切换 node 版本。

  • 安装 mongodb

在这里使用 Homebrew 安装 mongodb, 首先更新 Homebrew:

brew update

mongodb 提供 3 种版本供安装:普通版、支持 TLS/SSL 版本和开发版,具体请参见 mongodb 官网。普通版本安装执行以下指令:

brew install mongodb

从终端信息得知,不管安装的是哪种版本,都会安装依赖 openssl。
各个部分默认的安装路径是:

The databases are stored in the /usr/local/var/mongodb/ directory
The mongod.conf file is here: /usr/local/etc/mongod.conf
The mongo logs can be found at /usr/local/var/log/mongodb/
The mongo binaries are here: /usr/local/Cellar/mongodb/[version]/bin

1、配置数据库:
首先创建保存数据的文件夹:

sudo mkdir -p /data/db

然后给刚创建的文件夹写入数据权限:

sudo chown -R $USER /data/db

R 是 recursive 递归的意思
如果想直接配置 mongod.conf:

nano /usr/local/etc/mongod.conf

可以看到 mongod.conf 内容如下:

systemLog:
destination: file
path: /usr/local/var/log/mongodb/mongo.log
logAppend: true
storage:
dbPath: /usr/local/var/mongodb
net:
bindIp: 127.0.0.1

注意:如果准备连接非本机环境的 mongodb 数据库,bind_ip = 0.0.0.0,并且通过 homebrew 安装的 mongodb 不需要配置环境变量了,直接运行命令就行!

2、启动 mongod 服务:

mongod

使用此命令启动 mongod 会发现输出信息如下:

2016-06-13T10:25:09.929+0800 I CONTROL [initandlisten] MongoDB starting : pid=5693 port=27017 dbpath=/data/db 64-bit host=MacBookPro
......

可以看到,会使用默认的数据保存路径 /data/db。可是 /usr/local/etc/mongod.conf 这个配置文件中的默认 dbpath 不是 /usr/local/var/mongodb 吗?难道说配置文件没有生效?
没错,以上启动命令并不会读取 /usr/local/etc/mongod.conf。这是因为不通过 $ mongod –config xxx.conf 这种形式指定配置文件 xxx.conf 时 mongodb 会使用程序内置的默认配置,该配置对应的数据保存路径为 /data/db。建议启动 mongod 时通过 –config 参数指定配置文件,更多参考官网 Configuration File Options。
所以,如果我们配置了 /usr/local/etc/mongod.conf 并希望 mongdb 使用我们的配置,可选的命令有 2 种:(1)$ mongod –config /usr/local/etc/mongod.conf 和 (2)$ mongod –config /usr/local/etc/mongod.conf –fork. 使用命令 1 启动的 mongod 服务在前台运行,可以通过 Ctrl+ C 关闭服务。命令 2 启动的 mongod 服务以守护线程的形式在后台运行
使用命令 2 启动服务端成功输出以下信息:

about to fork child process, waiting until server is ready for connections.

forked process: 6775

child process started successfully, parent exiting

如果使用命令 2 启动失败输出信息如下:

about to fork child process, waiting until server is ready for connections.

forked process: 6611

ERROR: child process failed, exited with error number 48

谷歌之后在 stackoverflow 上找到解决方案。这是因为已经有 mongodb 进程在运行,必须先找到该进程的 pid, 然后使用 kill 命令杀死该进程。

3、启动 mongo shell:
mongodb 自带 Javascript Shell, 可以运行 Javascript 程序,并可以和 mongodb 服务实例交互,启动 mongo shell 命令是:

mongo

客户端启动后会自动连接到 mongodb 服务端的 test 数据库:

MongoDB shell version: 3.2.7

connecting to: test

Welcome to the MongoDB shell.

For interactive help, type "help".

For more comprehensive documentation, see

http://docs.mongodb.org/

Questions? Try the support group

http://groups.google.com/group/mongodb-user

Server has startup warnings: 

2016-06-13T10:25:10.288+0800 I CONTROL [initandlisten] 

2016-06-13T10:25:10.288+0800 I CONTROL [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000
>

关闭 mongo shell:
mongo shell 有 2 种关闭方式:
1. 运行 mongo shell 的终端输入 control+c
2. 从运行 mongo shell 的终端输入 > exit
至此,mongodb 已经完全安装完毕!

  • 构建后端项目

1、新建一个文件夹,打开该文件夹,在终端输入以下命令初始化项目:

npm init

2、安装 express 生成器,通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。express-generator 包含了 express 命令行工具。通过如下命令即可安装:

npm install express-generator -g

3、如下命令创建了一个名称为 myapp 的 Express 应用。此应用将在当前目录下的 myapp 目录中创建,并且设置为使用 Pug 模板引擎:

express --view=pug myapp

4、然后安装所有依赖包:

cd myapp
npm install

5、安装 nodemon。nodemon 将监视启动目录中的文件,如果有任何文件更改,nodemon 将自动重新启动 node 应用程序。
全局安装:

npm install -g nodemon

本地安装:

npm install --save-dev nodemon

6、启动项目

nodemon

出现如下:

~/code/lp-node-test/myapp » nodemon            
[nodemon] 1.19.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node ./bin/www`

在浏览器输入:http://localhost:3000,express 项目初步建立成功。

7、安装插件 babel-register、babel-preset-env,以便在项目中使用 es6 语法

npm install --save babel-register babel-preset-env

在根目录下新建.babelrc 文件,并输入一下内容:

.babelrc

{"presets": ["env"]
}

8、安装 mongoose、mongoose-auto-increment、consola,连接 mongodb 数据库

npm install --save mongoose mongoose-auto-increment consola

9、安装 session,用于用户登录认证

npm install --save express-session

10、在根目录下创建 config.js 全局配置文件,并输入如下:

config.js

const {argv} = require('yargs');

exports.MONGODB = {uri: `mongodb://127.0.0.1:${argv.dbport || '27017'}/node`,
    username: argv.db_username || 'DB_username',
    password: argv.db_password || 'DB_password',
};

11、在根目录下创建 core 文件夹,新建 mongodb.js 文件,并输入如下:

core/mongodb.js

const consola = require('consola')
const CONFIG = require('../app.config.js')
const mongoose = require('mongoose')
const autoIncrement = require('mongoose-auto-increment')

// remove DeprecationWarning
mongoose.set('useFindAndModify', false)


// mongoose Promise
mongoose.Promise = global.Promise

// mongoose
exports.mongoose = mongoose

// connect
exports.connect = () => {// console.log('CONFIG.MONGODB.uri :', CONFIG.MONGODB.uri)

    // 连接数据库
    mongoose.connect(CONFIG.MONGODB.uri, {
        useCreateIndex: true,
        useNewUrlParser: true,
        promiseLibrary: global.Promise
    })

    // 连接错误
    mongoose.connection.on('error', error => {consola.warn('数据库连接失败!', error)
    })

    // 连接成功
    mongoose.connection.once('open', () => {consola.ready('数据库连接成功!')
    })

    // 自增 ID 初始化
    autoIncrement.initialize(mongoose.connection)
    
    // 返回实例
    return mongoose
}

12、在根目录下创建 util 文件夹,新建 util.js 文件,用于放置公共方法,并输入如下:

util/util.js

import crypto from 'crypto';

module.exports = {md5: function(pwd) {let md5 = crypto.createHash('md5');
        return md5.update(pwd).digest('hex');
    },
    // 响应客户端
    responseClient(res, httpCode = 500, status = 3, message = '服务端异常', data = {}) {let responseData = {};
        responseData.status = status;
        responseData.message = message;
        responseData.data = data;
        res.status(httpCode).json(responseData);
    },
    // 时间 格式化成 2018-12-12 12:12:00
    timestampToTime(timestamp) {const date = new Date(timestamp);
        const Y = date.getFullYear() + '-';
        const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
        const D = date.getDate() < 10 ? '0' + date.getDate() + '' : date.getDate() +' ';
        const h = date.getHours() < 10 ? '0' + date.getHours() + ':' : date.getHours() + ':';
        const m = date.getMinutes() < 10 ? '0' + date.getMinutes() + ':' : date.getMinutes() + ':';
        const s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
        return Y + M + D + h + m + s;
    },
};

13、在根目录下创建 models 文件夹,新建建 user.js,用于构建数据库的数据模型,并输入如下:

models/user.js

const crypto = require('crypto');
const {argv} = require('yargs');
const {mongoose} = require('../core/mongodb.js');
const autoIncrement = require('mongoose-auto-increment');

const adminSchema = new mongoose.Schema({
  // 名字
  name: {type: String, required: true, default: ''},

  // 用户类型 0:管理者,1:其他用户 
  type: {type: Number, required: true, default: 1},

  // 手机
  phone: {type: Number, required: true, default: ''},

  // 邮箱
  email: {type: String,  required: true, default: ''},

  // 密码
  password: {
    type: String,
    required: true,
    default: crypto
      .createHash('md5')
      .update(argv.auth_default_password || 'root')
      .digest('hex'),
  },

  // 创建日期
  create_time: {type: Date, default: Date.now},

  // 最后修改日期
  update_time: {type: Date, default: Date.now},
});
module.exports = mongoose.model('User', adminSchema);

14、把 app.js 修改成如下:

app.js

// modules
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const session = require('express-session');

// import 等语法要用到 babel 支持
require('babel-register');

const app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false}));
app.use(express.static(path.join(__dirname, 'public')));
app.use(cookieParser('blog_node_cookie'));
app.use(
    session({
        secret: 'blog_node_cookie',
        name: 'session_id', //# 在浏览器中生成 cookie 的名称 key,默认是 connect.sid
        resave: true,
        saveUninitialized: true,
        cookie: {maxAge: 60 * 1000 * 30, httpOnly: true}, // 过期时间
    }),
);

const mongodb = require('./core/mongodb');

// data server
mongodb.connect();

// 将路由文件引入
const route = require('./routes/index');

// 初始化所有路由
route(app);

// catch 404 and forward to error handler
app.use(function(req, res, next) {next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
    // set locals, only providing error in development
    res.locals.message = err.message;
    res.locals.error = req.app.get('env') === 'development' ? err : {};

    // render the error page
    res.status(err.status || 500);
    res.render('error');
});

module.exports = app;

15、把 routes/index.js 修改如下:

routes/index.js

/*
* 所有的路由接口
*/
const users = require('./users');

module.exports = app => {app.post('/login', users.login);
    app.post('/logout', users.logout);
    app.post('/register', users.register);
    app.post('/userInfo', users.userInfo);
    app.get('/currentUser', users.currentUser);
};

16、把 routes/users.js 修改如下:

routes/users.js

const User = require('../models/user');
import {responseClient, md5} from '../util/util.js';

exports.login = (req, res) => {const { phone, password} = req.body;
  const reg =/^1[34578]\d{9}$/
  if (!phone) {responseClient(res, 200, 400, '用户邮箱不可为空');
    return;
  }else if(!reg.test(phone)){responseClient(res, 200, 400, '请输入格式正确的手机号码');
    return;
  }
  if (!password) {responseClient(res, 200, 400, '密码不可为空');
    return;
  }
  User.findOne({
    phone,
    password: md5(password),
  })
    .then(userInfo => {console.log(userInfo._id)
      if (userInfo) {
        // 登录成功后设置 session
        req.session.userInfo = userInfo;
        responseClient(res, 200, 200, '登录成功', null);
      } else {responseClient(res, 200, 402, '用户名或者密码错误');
      }
    })
    .catch(err => {responseClient(res);
    });
};

// 用户验证(获取用户信息)
exports.userInfo = (req, res) => {if (req.session.userInfo) {responseClient(res, 200, 200, '', req.session.userInfo);
  } else {responseClient(res, 200, 403, '请重新登录', req.session.userInfo);
  }
};

exports.logout = (req, res) => {console.log(req.session)
  if (req.session.userInfo) {
    req.session.userInfo = null; // 删除 session
    responseClient(res, 200, 200, '登出成功!!');
  } else {responseClient(res, 200, 402, '您还没登录!!!');
  }
};

exports.register = (req, res) => {const { name, password, phone, type,email} = req.body;
  const regPhone =/^1[34578]\d{9}$/
  const regEmail = /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/
  if (!phone) {responseClient(res, 200, 400, '用户手机号码不能为空');
    return;
  }else if(!regPhone.test(phone)){responseClient(res, 200, 400, '请输入格式正确的手机号码');
    return;
  }
  if (!name) {responseClient(res, 200, 400, '用户名不可为空');
    return;
  }
  if (!email) {responseClient(res, 200, 400, '用户邮箱不可为空');
    return;
  }else if(!regEmail.test(email)){responseClient(res, 200, 400, '请输入格式正确的邮箱');
    return;
  }
  if (!password) {responseClient(res, 200, 400, '用户密码不可为空');
    return;
  }
  // 验证用户是否已经在数据库中
  User.findOne({phone})
    .then(data => {if (data) {responseClient(res, 200, 402, '用户手机号码已存在!');
        return;
      }
      // 保存到数据库
      let user = new User({
        name,
        password: md5(password),
        phone,
        type,
        email
      });
      user.save().then(data => {responseClient(res, 200, 200, '注册成功', data);
      });
    })
    .catch(err => {responseClient(res);
      return;
    });
};

exports.delUser = (req, res) => {let { id} = req.body;
  User.deleteMany({_id: id})
    .then(result => {if (result.n === 1) {responseClient(res, 200, 0, '用户删除成功!');
      } else {responseClient(res, 200, 1, '用户不存在');
      }
    })
    .catch(err => {responseClient(res);
    });
};

好了,现在用户注册、登录、登出、用户信息、删除用户接口都有了,使用 postman 请求接口试试
注册:

登录:

其他接口可自行补充,git 地址:https://github.com/SuperMrBea…

退出移动版