## 架构:expressjs + express-jwt + sequelize + express-validate
## 数据库:mysql
## 实现功能:登录、验证码、鉴权、用户、上传、下载、错误统一处理
## api格式: restful + json
项目目录
app.js
// var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require("express-session");
var expressJwt = require('express-jwt');
var indexRouter = require('./routes/index');
var userRouter = require('./routes/user');
var uploadRouter = require('./routes/upload');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/public',express.static('public'));
app.use(expressJwt({
secret: 'secret12345', // 签名的密钥 或 PublicKey
algorithms: ['HS256'],
}).unless({
path: ['/api/user/login', '/api/user/captcha'] // 指定路径不经过 Token 解析
}))
app.use(session({
secret: 'keyboard cat',
resave: true,
saveUninitialized: true
}))
app.use('/', indexRouter);
app.use('/api/user', userRouter);
app.use('/api/upload', uploadRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
console.log(res.status)
res.json({
msg: 'Not Found',
status: 404,
data: {}
})
});
// 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 : {};
res.json({
msg: err.message,
status: err.status || 500,
data: {}
})
});
module.exports = app;
model->role.js
var db = require('../utils/db');
var {DataTypes} = require('sequelize');
var Role = db.define('role', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
name: {
type: DataTypes.STRING(50),
allowNull: false
},
pid: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
freezeTableName: true,
timestamps: false
});
module.exports = Role;
model->user.js
var db = require('../utils/db');
var {DataTypes} = require('sequelize');
var User = db.define('user', {
id: {
type: DataTypes.INTEGER,
allowNull: false,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING(20),
allowNull: false
},
password: {
type: DataTypes.STRING(50),
allowNull: false
},
roleid: {
type: DataTypes.INTEGER,
allowNull: false
}
}, {
freezeTableName: true,
timestamps: false
});
module.exports = User;
routes->index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/test', function(req, res, next) {
res.send('test')
});
module.exports = router;
routes->upload.js
var express = require('express');
var router = express.Router();
var multer = require('multer');
var upload = multer({dest: 'upload_tmp/'});
var uploadService = require('../service/upload');
// 上传文件
router.post('/', upload.any(), async function(req, res, next) {
await uploadService.uploadFile(req, res);
});
// 上传多文件
router.post('/multi', upload.array('files', 10), async function(req, res, next) {
await uploadService.uploadMultiFile(req, res);
});
module.exports = router;
routes->user.js
var express = require('express');
var router = express.Router();
var parserJSON = require('../utils/ajax');
var svgCaptcha = require('svg-captcha');
var { check, validationResult } = require('express-validator');
var userService = require('../service/user');
var jwt = require('jsonwebtoken');
// 获取验证码
router.get('/captcha', function(req, res, next) {
var captcha = svgCaptcha.create({
size: 4, //图片验证码的字符数
fontSize: 50,
ignoreChars: 'Ooli', //忽略的一些字符
width: 100,
height: 40,
noise: 2,
color: true,
background: '#ffffff',
});
req.session.captcha = captcha.text;
res.json(parserJSON.success('查询成功', 200, {svg: captcha.data, captcha: captcha.text}))
});
// 登录
router.post('/login', [
check('username').notEmpty(),
check('password').notEmpty(),
check('captcha').notEmpty(),
], async function(req, res, next) {
var errors = validationResult(req);
if(!errors.isEmpty()) {
return res.json(parserJSON.error('', 400, {errors: errors.mapped()}))
}
var data = req.body;
if(req.session.captcha === undefined){
res.json(parserJSON.error('证码失效,请重新获取', 400))
return;
}
if(req.session.captcha!==data.captcha){
res.json(parserJSON.error('请正确输入验证码', 400))
return;
}
// 判断该用户是否存在并且密码是否正确
var getUser = await userService.validUser(data.username, data.password);
if (getUser) {
var token = jwt.sign({ username: data.username },'secret12345',
{
expiresIn: 3600 * 24 * 3
});
res.json(parserJSON.success('登录成功', 200, {
token,
roleid: getUser.roleid
}))
} else {
res.json(parserJSON.error('用户名或密码错误', 400))
}
});
// 获取所以用户
router.get('/index', async function(req, res, next) {
var list = await userService.getUser(req, req.id);
res.json(parserJSON.success('查询成功', 200, list))
});
// 获取指定用户
router.get('/index/:id', async function(req, res, next) {
var list = await userService.getUser(req, req.params.id);
res.json(parserJSON.success('查询成功', 200, list))
});
// 创建用户
router.post('/create', [
check('username').notEmpty(),
check('roleid').notEmpty().isNumeric()
], async function(req, res, next) {
var errors = validationResult(req);
if(!errors.isEmpty()) {
return res.json(parserJSON.error('', 400, {errors: errors.mapped()}))
}
var { username, roleid } = req.body;
await userService.addUser(username, roleid);
res.json(parserJSON.success('新增成功', 200))
});
// 修改密码
router.put('/updatePwd/:id', [
check('password').notEmpty()
], async function(req, res, next) {
var errors = validationResult(req);
if(!errors.isEmpty()) {
return res.json(parserJSON.error('', 400, {errors: errors.mapped()}))
}
var { password } = req.body;
await userService.editPwd(req.params.id, password);
res.json(parserJSON.success('修改成功', 200))
});
// 删除用户
router.delete('/delete/:id', async function(req, res, next) {
var status = await userService.deleteUser(req.params.id);
if(status === 200){
res.json(parserJSON.success('删除成功', 200))
}else{
res.json(parserJSON.error('删除失败,没有该条记录', 400))
}
});
module.exports = router;
service->upload.js
var path = require('path');
var fs = require("fs");
var dayjs = require('dayjs');
var parserJSON = require('../utils/ajax');
var UPLOAD_PATH = "./public/upload/";
function mkdirsSync(dirname) {
if (fs.existsSync(dirname)) {
return true;
} else {
if (mkdirsSync(path.dirname(dirname))) {
fs.mkdirSync(dirname);
return true;
}
}
}
var uploadService = {
// 上传文件
uploadFile: async function(req, res){
var file = req.files[0]
console.log(file)
// 生成文件名
var filename = `${Date.now()}${Number.parseInt(
Math.random() * 1000,
)}${path.extname(file.originalname).toLocaleLowerCase()}`;
// 生成文件夹
var dirname = dayjs(Date.now()).format('YYYY/MM/DD');
mkdirsSync(path.join(UPLOAD_PATH, dirname));
// 生成写入路径
var url = `./public/upload/${dirname}/${filename}`
try {
//异步把文件流 写入
await fs.writeFileSync(url, fs.readFileSync(file.path));
res.json(parserJSON.error('上传成功', 200, {
filename: file.originalname,
name: filename,
url
}))
} catch (err) {
console.error(err)
res.json(parserJSON.error(err, 400))
}
},
// 上传多文件
uploadMultiFile: async function(req, res){
var files = req.files;
var uplist = [];
for (var i = 0; i < files.length; i++) {
var element = files[i];
// 生成文件名
var filename = `${Date.now()}${Number.parseInt(
Math.random() * 1000,
)}${path.extname(element.originalname).toLocaleLowerCase()}`;
// 生成文件夹
var dirname = dayjs(Date.now()).format('YYYY/MM/DD');
mkdirsSync(path.join(UPLOAD_PATH, dirname));
// 生成写入路径
var url = `./public/upload/${dirname}/${filename}`
try {
//异步把文件流 写入
await fs.writeFileSync(url, fs.readFileSync(element.path));
} catch (err) {
console.error(err)
}
uplist.push({
name: filename,
org_name: element.originalname,
url
})
}
res.json(parserJSON.error('上传成功', 200, {
list: uplist
}))
}
}
module.exports = uploadService;
service->user.js
var User = require('../model/user');
const crypto = require('crypto');
var Role = require('../model/role');
// 设置默认密码
const DEFAULT_PWD = '123456';
function toInt(str) {
if (typeof str === 'number') return str;
if (!str) return str;
return parseInt(str, 10) || 0;
}
var userService = {
// 查询user表,验证密码和花名
validUser: async function (username, password){
const data = await User.findAll();
const pwd = await this.getMd5Data(password);
for (const item of data) {
if (item.username === username && item.password === pwd) return item;
}
return false;
},
// 专门对数据进行md5加密的方法,输入明文返回密文
getMd5Data: async function (data) {
return await crypto.createHash('md5').update(data).digest('hex');
},
// 获取用户,不传id则查询所有
getUser: async function(req, id) {
console.log(req.query)
const query = { limit: toInt(req.query.limit), offset: toInt(req.query.offset) }
if(id){
return await User.findByPk(toInt(id));
}else{
return await User.findAll({
query,
attributes: ['username'],
include: [{
model: Role,
required: true,
as: 'role',
attributes: [ 'name', 'pid' ],
association: User.belongsTo(Role, {foreignKey: 'roleid'}) // 一对一联表查询
}],
})
}
},
// 新增人员
addUser: async function(username, roleid){
var password = await this.getMd5Data(DEFAULT_PWD);
await User.create({ username, password, roleid });
},
// 修改密码
editPwd: async function(id, password){
var user = await User.findByPk(id);
var newPwd = await this.getMd5Data(password);
await user.update({password: newPwd});
},
// 删除人员
deleteUser: async function(id){
var user = await User.findByPk(id);
if(user){
await user.destroy();
return 200;
}else{
return 400;
}
}
}
module.exports = userService;
utils->ajax.js
// 数据返回格式
var parserJSON = {
success: function(msg = '', status = 200, data = {}){
return {
msg,
status,
data
}
},
error: function(msg = '', status = 500, data = {}) {
return {
msg,
status,
data
}
}
}
module.exports = parserJSON
utils->db.js
var Sequelize = require('sequelize');
var sequelize = new Sequelize('test', 'root', '123456', {
host: '127.0.0.1',
dialect: 'mysql'
});
module.exports = sequelize;