使用typescript和express开发后台项目

tech2023-07-15  102

一、环境的配置

1、安装依赖包

npm i express body-parser bcryptjs jsonwebtoken morgan cors validator helmet dotenv multer -S npm install mongoose npm i typescript @types/node @types/express @types/mongoose @types/bcryptjs @types/jsonwebtoken @types/morgan @types/cors @types/validator ts-node-dev nodemon @types/helmet @types/multer -D

2、生成tsconfig.json文件(根据语言来选择)

npx tsconfig.json

3、在package.json文件中配置启动命令

"scripts": { "build": "tsc", "start": "ts-node-dev --respawn src/index.ts", "dev": "nodemon --exec ts-node --files src/index.ts" },

4、在src/index.ts文件中写测试代码

import 'dotenv/config'; //这是包的作用是读取.env文件然后写入process.env.JWT_SECRET_KEY import path from 'path'; import express, { Express } from 'express'; //启服务 import mongoose from 'mongoose'; //连接数据库的 import cors from 'cors'; //用来跨域的 import morgan from 'morgan'; //这是用来输入访问日志的 import helmet from 'helmet'; //用来进行安全过滤的 const app: Express = express(); // 使用中间件 app.use(cors()); // 处理跨域请求 app.use(morgan('dev')); // 开发环境打印日志 app.use(helmet()); // 处理请求 app.use(express.json());//express.json=bodyParser.json app.use(express.urlencoded({ extended: true })); // 配置静态文件目录,也是上传的目录 app.use(express.static(path.join(__dirname, 'public'))); // 测试接口 app.get('/', (_req, res, _next) => { res.json({ code: 0, message: '成功' }); }); (async function () { await mongoose.set('useNewUrlParser', true); await mongoose.set('useUnifiedTopology', true); const MONGODB_URL = process.env.MONGODB_URL || `mongodb://localhost/express_ts`; await mongoose.connect(MONGODB_URL); const PORT = process.env.PORT || 8001; app.listen(PORT, () => { console.log(`Running on http://localhost:${PORT}`); }) })();

5、测试

npm run start # or npm run dev

二、配置错误拦截器及错误中间件

1、拦截器

class HttpException extends Error { //status HTTP错误状态码 message是错误提示信息 errors 错误对象 constructor (public status: number, public message: string, public errors: any = {}) { super(message); } } export default HttpException;

2、中间件

import { Request, Response, NextFunction } from 'express'; import HttpException from '../exceptions/HttpException'; import { INTERNAL_SERVER_ERROR } from 'http-status-codes'; const errorMiddleware = (err: HttpException, _req: Request, res: Response, _next: NextFunction) => { let result: any = { success: false, message: err.message }; if (err.errors && Object.keys(err.errors).length > 0) { result.errors = err.errors; } res.status(err.status || INTERNAL_SERVER_ERROR) .json(result); } export default errorMiddleware;

3、在src/index.ts中使用

... //如果说没有匹配到任何路由,则会创建一个自定义404错误对象并传递给错误处理中间件 app.use((_req: Request, _res: Response, next: NextFunction) => { const error: HttpException = new HttpException(404, '尚未为此路径分配路由'); next(error); }); // 使用自定义中间件 app.use(errorMiddleware); ...

三、用户模块

1、在models中创建用户的数据模型

import mongoose, { Schema, Model, Document, HookNextFunction } from 'mongoose'; import bcryptjs from 'bcryptjs'; import validator from 'validator'; // import jwt from 'jsonwebtoken'; // 定义用户的数据模型约束 export interface UserDocument extends Document { username: string; password: string; avatar: string; email: string; } // 定义mongodb数据模型 const UserSchema: Schema<UserDocument> = new Schema({ username: { type: String, required: [true, '用户名不能为空'], minlength: [6, '最小长度不能小于6位'], maxlength: [12, '最大长度不能大于12位'], trim: true, }, password: String, avatar: String, email: { type: String, // 自定义校验器 validate: { validator: validator.isEmail }, trim: true, } }, { //使用时间戳 会自动添加两个字段 createdAt updatedAt timestamps: true, // 自定义返回数据格式 toJSON: { transform: function (_doc: any, result: any) { result.id = result._id; delete result._id; delete result.__v; delete result.password; delete result.createdAt; delete result.updatedAt; return result; } } }) // 配置mongoose的钩子函数,对密码加密 UserSchema.pre<UserDocument>('save', async function (next: HookNextFunction) { if (!this.isModified('password')) { return next(); } try { this.password = await bcryptjs.hash(this.password, 10); next(); } catch (error) { next(error); } }) interface UserModel<T extends Document> extends Model<T> { } export const User: UserModel<UserDocument> = mongoose.model<UserDocument, UserModel<UserDocument>>('User', UserSchema);

2、用户注册

import { Request, Response, NextFunction, } from 'express'; import { User, UserDocument } from '../models'; import { validateRegisterInput } from '../utils'; import HttpException from '../exceptions/HttpException'; import { UNPROCESSABLE_ENTITY } from 'http-status-codes'; export const register = async (req: Request, res: Response, next: NextFunction) => { let { username, password, confirmPassword, email } = req.body; try { // 先校验用户输入的数据 let { valid, errors } = validateRegisterInput(username, password, confirmPassword, email); if (!valid) { throw new HttpException(UNPROCESSABLE_ENTITY, '用户提交的数据不正确', errors); } let oldUser: (UserDocument | null) = await User.findOne({ username }); if (oldUser) { throw new HttpException(UNPROCESSABLE_ENTITY, '用户名重复', errors); } // 创建用户 let user: UserDocument = new User({ username, password, confirmPassword, email }); await user.save(); // 返回数据 res.json({ success: true, data: user.toJSON() }); } catch (error) { next(error); }; }

3、用户登录及返回token

// models/user.ts文件 export interface UserPayload { id: string } UserSchema.methods.getAccessToken = function (this: UserDocument): string { //this.id是一个语法糖,或者说快捷方式,指向this._id let payload: UserPayload = { id: this.id };//payload是放在放在jwt token里存放的数据 return jwt.sign(payload, process.env.JWT_SECRET_KEY || 'abc', { expiresIn: '1h' }); } interface UserModel<T extends Document> extends Model<T> { login: (username: string, password: string) => UserDocument | null } // 扩展一个方法 UserSchema.static('login', async function (this: any, username: string, password: string): Promise<UserDocument | null> { const user: UserDocument | null = await this.model('User').findOne({ username }); if (user) { //判断用户输入的密码和数据库里存的密码是否能匹配 const matched = await bcryptjs.compare(password, user.password); if (matched) { return user; } else { return null; } } else { return null; } }) //controllers/user.ts控制器 // 用户登录 export const login = async (req: Request, res: Response, next: NextFunction) => { try { const { username, password } = req.body; // 传递用户名和密码登录 const user: UserDocument | null = await User.login(username, password); if (user) { let access_token = user.getAccessToken(); res.json({ success: true, data: access_token }); } else { throw new HttpException(UNAUTHORIZED, '登录失败'); } } catch (error) { next(error); } }

四、上传文件

1、安装依赖包

npm install multer

2、指定上传地址

// 在index.ts文件中 //指定上传文件的存储空间 const storage = multer.diskStorage({ //指定上传的目录 destination: path.join(__dirname, 'public', 'uploads'), filename(_req: Request, file: Express.Multer.File, callback) { // callback 第二个参数是文件名 时间戳.jpg callback(null, Date.now() + path.extname(file.originalname)); } }); // 在需要上传的地方使用这个 const upload = multer({ storage });

3、指定上传的控制器

//当服务器端接收到上传文件请求的时候,处理单文件上传。字段名avatar request.file=Express.Multer.File app.post('/user/uploadAvatar', upload.single('avatar'), userController.uploadAvatar);

4、上传文件的控制器

// 上传文件 export const uploadAvatar = async (req: Request, res: Response, next: NextFunction) => { try { const { userId } = req.body; let avatar = `${req.protocol}://${req.headers.host}/uploads/${req.file.filename}`; await User.updateOne({ _id: userId }, { avatar }); //处理上传的文件,然后更新数据库,更新此用户对应的avatar字段。然后返回真实的图片路径 res.json({ success: true, data: avatar }); } catch (error) { next(error); } }
最新回复(0)