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);
}
}