expressjs 项目实践

## 架构: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;

猜你喜欢

转载自blog.csdn.net/bbsyi/article/details/121291605