一、安装依赖
基础
npm i express
const app = express();
解析post请求主题
npm i body-parser
const bodyParser = require('body-parser');
app.use(bodyParser.json());
解析cookie
npm i cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());
使用session
npm i express-session
const session = require('express-session');
app.use(session({
secret: 'my secret',
resave: false, // 重新保存
saveUninitialized: true // 自动保存
}));
token加密解析
npm i jsonwebtoken
const jwt = require('jsonwebtoken')
日志
npm i express-winston
const {globalLogger}=require('./winstonLogger.js')
app.use(expressWinston.logger({
winstonInstance: globalLogger,
meta: true, // 如果设置为 true,则会在日志中添加一些元数据,例如请求的 URL、方法、HTTP 版本等
msg: "HTTP {{req.method}} {{req.url}}", // 日志消息的格式。你可以使用双大括号 {{}} 来引用请求或响应的属性。
expressFormat: true, // 如果设置为 true,则日志消息的格式会类似于 Express 的默认日志格式 :method :url :status :response-time ms - :res[content-length]。
colorize: true, // 如果设置为 true,则日志消息会被着色
requestWhitelist: ['url', 'headers', 'method', 'httpVersion', 'originalUrl', 'query','body','ip'],// 一个数组,用于指定哪些请求属性应该被记录
ignoreRoute: function (req, res) { return false; } //
}));
// winstonLogger.js
const winston = require("winston");
const DailyRotateFile =require("winston-daily-rotate-file");
const customFormat = winston.format.combine(
winston.format.timestamp({ format: "MMM-DD-YYYY HH:mm:ss" }),
winston.format.align(),
winston.format.printf(({level,timestamp,message,meta}) =>{
return `${level}:
Time:${[timestamp]}
Url:${meta.req.url}
Method:${meta.req.method}
headers:${JSON.stringify(meta.req.headers)}
httpVersion:${meta.req.httpVersion}
originalUrl:${meta.req.originalUrl}
query:${JSON.stringify(meta.req.query)}
Message:${message}
body:${JSON.stringify(meta.req.body)}
ip:${meta.req.ip}`
})
);
const defaultOptions = {
format: customFormat,
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
};
const globalLogger = winston.createLogger({
format: customFormat,
transports: [
new DailyRotateFile({
filename: "logs/info-%DATE%.log",
level: "info",
...defaultOptions,
}),
new DailyRotateFile({
filename: "logs/error-%DATE%.log",
level: "error",
...defaultOptions,
}),
],
});
const authLogger = winston.createLogger({
transports: [
new DailyRotateFile({
filename: "logs/authLog-%DATE%.log",
...defaultOptions,
}),
],
});
module.exports = {
globalLogger: globalLogger,
authLogger: authLogger,
};
密码加密(单向)
npm i crypto
// util.js
const crypto = require('crypto');
const getHashValue = (value, salt = '') => {
// 生成一个随机的盐值
if (!salt) {
salt = crypto.randomBytes(16).toString('hex');
}
// 把盐值添加到密码中,然后进行哈希
const hashValue = crypto.pbkdf2Sync(value, salt, 1000, 64, 'sha512').toString('hex');
const saltedHash = salt + ":" + hashValue;
return saltedHash
}
验证函数
npm i express-validator
const { body, validationResult,check } = require('express-validator');
// 封装验证函数
const validateFields = (fields) => {
const validators = [];
fields.forEach(({ field, noEmpty, validType, errorMessage }) => {
let validator = body(field);
if (noEmpty) {
validator = validator.notEmpty().withMessage(errorMessage || `${field} must not be empty`);
}
if(!noEmpty&&validator.isEmpty()){
validator = check('fieldName')
.custom(value => {
if (value === null || value === undefined || value === '') {
return true; // 验证通过
}
return true; // 如果字段不为空,也返回 true
})
.withMessage('The field cannot be empty');
}
else if (validType === 'string') {
validator = validator.isString().withMessage(errorMessage || `${field} must be a string`);
} else if (validType === 'int') {
validator = validator.isInt().withMessage(errorMessage || `${field} must be an integer`);
} else if (validType === 'boolean') {
validator = validator.isBoolean().withMessage(errorMessage || `${field} must be a boolean`);
} else if (validType === 'email') {
validator = validator.isEmail().withMessage(errorMessage || `${field} must be a valid email`);
}
validators.push(validator);
});
validators.push((req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
});
return validators;
}
数据库
npm i mysql
// mysql.js
const mysql = require('mysql');
// 数据库连接配置
const pool = mysql.createPool({
connectionLimit : 10,
host : 'localhost',
user : 'root',
password : '123456',
database : 'mydb',
multipleStatements:true,
});
pool.on('enqueue', function (sequence) {
if ('Query' === sequence.constructor.name) {
console.log('enqueue',sequence.sql);
}
});
pool.on('connection', function (conn) {
conn.on('enqueue', function(sequence) {
if ('Query' === sequence.constructor.name) {
console.log('connection',sequence.sql);
}
});
});
module.exports=pool
二、监听
app.listen(3000, () => {
console.log('Server started on port 3000');
});
三、获取token并拦截
使用中间件的方式
const noNeedToken=['/Admin/Login']
app.use((req, res, next) => {
if(noNeedToken.includes(req.originalUrl)){
next()
}else{
const token = req.headers['authorization']||req.headers['token'];
if (!token) {
return res.status(401).send(errorMsg('无法获取token'));
}
try {
const decoded = jwt.verify(token, security_key,{maxAge:'7d',complete:true});
// security_key为自己定义的密钥
next(); // 继续执行后续中间件或路由处理器
} catch(err) {
if(err.name === 'TokenExpiredError') {
return res.status(401).send(errorMsg('token已过期'));
} else {
return res.status(401).send(errorMsg('不合法的token'));
}
}
}
});
四、常量
将数据库的下划线字段转为驼峰
npm i humps
const humps = require('humps');
/**
* @param {any | undefined} [data]
*/
function successMsg(data) {
return {
code: 0,
data: humps.camelizeKeys(data),
msg: '成功'
}
}
/**
* @param {any | undefined} [data]
*/
function errorMsg(data) {
return {
code: -1,
data: humps.camelizeKeys(data),
msg: '失败'
}
}
function page({data,total,pageIndex}){
return {
records:data,
total,
pageIndex
}
}
const security_key='test'
module.exports = {
successMsg,
errorMsg,
page,
security_key
}
五、开始配置接口
以登录接口为例
安装multer
Multer 是一个 node.js 中间件,用于处理 multipart/form-data 类型的表单数据
npm i multer
const pool = require('../mysql/mysql.js');
const express = require('express');
const router = express.Router();
const multer = require('multer');
const upload = multer();
const jwt = require('jsonwebtoken')
const { getHashValue, validateFields } = require('../public/util.js')
const { successMsg, errorMsg,page, security_key } = require('../public/constant.js')
router.post('/Login', upload.none(), validateFields([
{ field: 'userName', noEmpty: true, validType: 'string' },
{ field: 'password', noEmpty: true, validType: 'string' }
]), (req, res, next) => {
const { userName, password } = req.body;
// 参数1: 生成到token中的信息
// 参数2: 密钥
// 参数3: token的有效时间: 60, "2 days", "10h", "7d"
const sql = `SELECT * FROM ${tableName} WHERE user_name=? and is_delete=0`
pool.query(sql, [userName], (err, result) => {
if (err){
return res.status(500).send(errorMsg('数据库错误'));
}
// 将解密后的password(buffer)转为字符串
var r = [...result]
if (r.length) {
const [storedSalt, storedHash] = r[0].password.split(":");
const hashedPassword = getHashValue(password, storedSalt)
if (hashedPassword == r[0].password) {
const token = jwt.sign({ userName }, security_key, { expiresIn: '7d' })
res.status(200).send(successMsg(token));
} else {
res.status(200).send(errorMsg('密码错误'))
}
} else {
res.status(200).send(errorMsg('没有该用户'))
}
});
});
// 最后导出
module.exports = router;
六、使用接口
在监听之前
const adminRouter = require('./api/admin.js');
app.use('/Admin', adminRouter);
// 接下来便可调用/Admin/Login进行测试了
下篇为swagger的使用,期待!!!