本文主要实现的是CRUD请求 实现用户表的增删改查
前端
- React17
- Vite
- Tailwind
- Antd ProComponent
后端
- Express
- Joi
- Sequelize
- Log4js
- mysql2
-
Express的基础部分可以参考作者的 juejin.cn/post/694249…
-
本文只记录一些后端的实现 前端代码参考 github.com/LuckyChou71… 这个仓库
-
此外 你还需要有mysql的环境
搭建项目
- 安装依赖
// 初始化项目
yarn init -y
// 服务框架
yarn add express
// 参数校验
yarn add joi
// 数据库
yarn add mysql2
yarn add sequelize
// 日志
yarn add log4js
- 生成项目目录
├─ src
│ ├─ app.ts // 入口文件
│ ├─ dao // 数据库相关操作
│ │ ├─ config.ts
│ │ ├─ connect.ts
│ │ └─ user.ts
│ ├─ log // 日志相关操作
│ │ └─ index.ts
│ ├─ routes // 路由
│ │ └─ user.ts
│ ├─ utils // 工具方法
│ │ └─ index.ts
│ └─ validator // 参数验证
│ ├─ index.ts
│ └─ user.ts
ok 接下来我们就来完善上面的每一个文件
Log
用 log4js 可以很方便的记录log 看一下项目运行时记录的一些log
数据库相关
api相关
log4js 配置如下
import log4js from 'log4js';
type LogCate = 'user' | 'error';
log4js.configure({
appenders: {
cheese: { type: 'dateFile', filename: 'log/cheese.log' },
userLog: { type: 'dateFile', filename: 'log/user.log' },
errLog: { type: 'dateFile', filename: 'log/error.log' },
},
categories: {
user: { appenders: ['userLog'], level: 'debug' },
error: { appenders: ['errLog'], level: 'debug' },
default: { appenders: ['cheese'], level: 'debug' },
},
});
export const logger = (category: LogCate) => log4js.getLogger(category);
说明一下
appenders 可以理解为配置每一个规则项
categories 为具体的每一个配置 其中的 appenders 为一个数组 它可以启用多条规则
然后在需要使用的地方 logger([categories中的配置项]).debug([debug信息])即可
数据库
数据库的相关操作放在 dao/ 目录下 表结构如下
+-----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| nickname | varchar(255) | NO | | NULL | |
| password | varchar(255) | NO | | NULL | |
| status | tinyint(1) | YES | | 0 | |
| address | varchar(255) | YES | | NULL | |
| tel | varchar(255) | YES | | NULL | |
| createdAt | datetime | NO | | NULL | |
| updatedAt | datetime | NO | | NULL | |
+-----------+--------------+------+-----+---------+----------------+
这边我们使用 orm 框架 sequelize 来操作数据库 相关介绍作者写在另一篇文章里
- 新建一个
user.ts文件 在里面创建我们的 user modal
import { DataTypes } from 'sequelize';
import sequelize from './connect';
const UserInfo = sequelize.define('user_info', {
// 在这里定义模型属性
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
nickname: {
type: DataTypes.STRING,
allowNull: false,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
status: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
address: {
type: DataTypes.STRING,
},
tel: {
type: DataTypes.STRING,
},
});
UserInfo.sync();
export default UserInfo;
- connect
新建一个 connect.ts 文件 用作数据库的连接
这里需要说明的是
- 在创建 Sequelize 实例的时候 要指定 timezone: '+08:00' 这样记录的时间才是北京时间
- 指定 logQueryParameters 为 true 不然记录的sql语句都是
INSERT INTO ... VALUES (DEFAULT,?,?,?,?,?)的形式 - sequelize.authenticate 用来测试是否建立连接
import { Sequelize } from 'sequelize';
import figlet from 'figlet';
import chalk from 'chalk';
import { logger } from '../log';
import Config from './config';
const sequelize = new Sequelize('demo', Config.username, Config.password, {
host: 'localhost',
dialect: 'mysql',
timezone: '+08:00',
logging: (msg) => logger('user').debug(msg),
logQueryParameters: true,
});
(async () => {
try {
await sequelize.authenticate();
figlet.text(
'Connect',
{
font: 'Larry 3D',
horizontalLayout: 'default',
verticalLayout: 'default',
width: 80,
whitespaceBreak: true,
},
(err, data) => {
if (!err) console.log(chalk.yellow(data));
}
);
} catch (error) {
console.error(chalk.red('Unable to connect to the database:', error));
}
})();
export default sequelize;
参数校验
对于每一个request的参数 其实都有属于它的数据结构 借助 joi 这个库 我们可以快速的进行参数的校验
下面以 update 这个接口需要的参数举例
const update = Joi.object({
// id是number类型 必传
id: Joi.number().required(),
// nickname是string 可选
nickname: Joi.string(),
password: Joi.string(),
status: Joi.boolean(),
// address可以是string或者null 可选
address: Joi.allow(string, null),
tel: Joi.allow(string, null),
});
// 需要验证的地方
update.validateAsync(params)
当参数验证失败时 就会返回一个error 例如 id is required
路由
提前说明一下 util 文件
主要用于 请求参数的校验 / 错误请求的log记录 / 成功状态的返回
import { logger } from '../log';
const validateParams = async (req: any, res: any, statement: Promise<any>) => {
try {
await statement;
} catch (error: any) {
// 记录错误的api请求
logger('error').error({
api: req.originalUrl,
param: req.body,
error: error.message,
});
res.status(400);
res.json({
message: error.message,
status: false,
});
}
};
const success = (res: any) =>
res.json({
message: 'success',
status: true,
});
export const ReqUtils = {
validateParams,
};
export const ResUtils = {
success,
};
下面就是 增删改查的实现
主要流程就是 request --> 校验参数 --> 操作数据库 --> response
| 功能 | method | path | 参数位置 | |
|---|---|---|---|---|
| 增 | post | /add | req.body | |
| 删 | delete | /delete/:id | req.params | |
| 改 | put | /update | req.body | |
| 查 | get | /read | req.query |
import express from 'express';
import UserInfo from '../dao/user';
import Validator from '../validator/user';
import { ReqUtils, ResUtils } from '../utils';
const { add, read, del, update } = Validator;
const { validateParams } = ReqUtils;
const router = express.Router();
router.post('/add', async (req, res) => {
await validateParams(req, res, add.validateAsync(req.body));
await UserInfo.create(req.body);
ResUtils.success(res);
});
router.delete('/delete/:id', async (req, res) => {
await validateParams(req, res, del.validateAsync(req.params));
await UserInfo.destroy({ where: req.params });
ResUtils.success(res);
});
router.put('/update', async (req, res) => {
await validateParams(req, res, update.validateAsync(req.body));
await UserInfo.update(req.body, { where: { id: req.body?.id } });
ResUtils.success(res);
});
router.get('/read', async (req, res) => {
await validateParams(req, res, read.validateAsync(req.query));
const result = await UserInfo.findAll({ where: req.query as any });
res.json(result);
});
export default router;
app.ts
import express from 'express';
import userRouter from './routes/user';
const app = express();
// 解析post请求的body
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
// 配置路由
app.use('/user', userRouter);
app.listen('8080');