pakage.json
"scripts": {
"build": "tsc",
"start": "ts-node-dev --respawn src/index.ts",
"dev": "nodemon --exec ts-node --files src/index.ts"
},
.env
JWT_SECRET_KEY=zhuzhu
MONGODB_URL=mongodb:
index.ts
- 要想使用ts声明 要使用import 不能使用require
import express , { Express, Request, Response, NextFunction }from 'express';
import mongoose from 'mongoose';
import cors from 'cors';
import morgan from 'morgan';
import helmet from 'helmet';
import multer from 'multer';
import "dotenv/config";
import path from 'path';
import { Slider, Lesson } from './models/index';
const storage = multer.diskStorage({
destination: path.join(__dirname, 'public', 'uploads'),
filename(_req:Request, _file: Express.Multer.File, callback) {
callback(null, Date.now() + path.extname(_file.originalname))
}
});
const upload = multer({ storage });
import errorMiddleware from './middlewares/errorMiddleware';
import HttpException from './exception/httpException';
import * as userController from './controllers/user';
import * as sliderController from './controllers/slider';
import * as lessonController from './controllers/lesson';
console.log(userController, 'user')
const app: Express = express();
app.use(cors());
app.use(morgan('dev'));
app.use(helmet());
app.use(express.static(path.join(__dirname, "public")));
app.use(express.json());
app.use(express.urlencoded({extended: true }));
app.get("/", (_req,res,_next) => {
res.json({success: true, data: "hello zhuzhu"});
});
app.post("/user/register", userController.register);
app.post("/user/login", userController.login);
app.get("/user/validate", userController.validate);
app.post("/user/uploadAvatar", upload.single('avatar'), userController.uploadAvatar);
app.get("/slider/list", sliderController.list);
app.get("/lesson/list", lessonController.list);
app.get("/lesson/:id", lessonController.getLesson);
app.use((_req:Request, _res:Response, next: NextFunction) => {
const error: HttpException = new HttpException(404, "尚未为此路径分配路由");
next(error);
})
app.use(errorMiddleware);
(async function() {
await mongoose.set('useNewUrlParser', true);
await mongoose.set('useUnifiedTopology', true);
const MONGODB_URL = process.env.MONGODB_URL || 'mongodb://localhost/zfketangapp';
await mongoose.connect(MONGODB_URL);
await createInitialSliders();
await createInitialLessons();
const PORT = process.env.PORT || 8001;
app.listen(PORT, () => {
console.log(`running on port ${PORT}`);
})
})()
async function createInitialSliders() {
const sliders = await Slider.find();
if(sliders.length == 0) {
const sliders = [
{url:"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.52souluo.com%2Fwp-content%2Fuploads%2F2014%2F11%2Fgongqijun08.jpg&refer=http%3A%2F%2Fwww.52souluo.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1620137772&t=c6f7f415e591d19c56e42a0157b0500f"},
{ url: "https://c-ssl.duitang.com/uploads/item/201502/11/20150211150925_mKQMa.jpeg"},
{ url: "https://c-ssl.duitang.com/uploads/blog/201508/17/20150817185916_YnyvC.jpeg"},
{ url: "https://c-ssl.duitang.com/uploads/item/201510/02/20151002133212_Cmvys.jpeg"},
{ url: "https://c-ssl.duitang.com/uploads/blog/202012/18/20201218100630_8ec8f.thumb.1000_0.jpg"}
];
await Slider.create(sliders);
}
}
async function createInitialLessons() {
const lessons = await Lesson.find();
if(lessons.length == 0) {
await Lesson.create(lessonarr);
}
}
const lessonarr = [
{
order: 1,
title: "1.react全栈架构",
video: "http://img.zhufengpeixun.cn/gee2.mp4",
poster: "http://www.zhufengpeixun.cn/react/img/react.jpg",
url: "http://www.zhufengpeixun.cn/themes/jianmo2/images/react.png",
price: "¥100.00元",
category: "vue"
},
{
order: 2,
title: "2.vue从入门到项目实战",
video: "http://img.zhufengpeixun.cn/gee2.mp4",
poster: "http://www.zhufengpeixun.cn/react/img/react.jpg",
url: "http://www.zhufengpeixun.cn/themes/jianmo2/images/react.png",
price: "¥200.00元",
category: "vue"
},
{
order: 3,
title: "3.vue从入门到项目实战",
video: "http://img.zhufengpeixun.cn/gee2.mp4",
poster: "http://www.zhufengpeixun.cn/react/img/react.jpg",
url: "http://www.zhufengpeixun.cn/themes/jianmo2/images/react.png",
price: "¥300.00元",
category: "vue"
},
]
错误中间件
import { Request, Response, NextFunction} from 'express';
import HttpException from "../exception/HttpException";
import { StatusCodes } from 'http-status-codes';
const errorMiddleware = (err:HttpException, _req:Request, res:Response, _next: NextFunction) => {
console.log("返回错误")
res.status(err.status || StatusCodes.INTERNAL_SERVER_ERROR)
.json({
success: false,
message: err.message,
errors: err.errors
})
};
export default errorMiddleware;
HttpException 错误类型
class HttpException extends Error {
constructor(public status:number, public message:string, public errors?:any){
super(message);
}
}
export default HttpException;
mongodb
arc/models文件夹
index.ts
export * from './user';
export * from './slider';
export * from './lession';
lession.ts
import mongoose, {Schema, Document, Model} from 'mongoose';
export interface LessonDocument extends Document {
order: number;
title: string;
video: string;
poster: string;
url: string;
price: string;
category: string;
}
const LessonSchema:Schema<LessonDocument>= new Schema({
order: Number,
title: String,
video: String,
poster: String,
url: String,
price: String,
category: String,
}, {
timestamps: true,
toJSON: {
transform: function(_doc:any, result:any) {
result.id = result._id;
delete result._id;
delete result.__v;
delete result.createdAt;
delete result.updatedAt;
return result;
}
}
});
export const Lesson:Model<LessonDocument> = mongoose.model("Lesson", LessonSchema);
slider.ts
import mongoose, {Schema,Document, Model} from 'mongoose';
export interface SliderDocument extends Document {
url: string;
}
const SliderSchema: Schema<SliderDocument> = new Schema({
url: String,
}, { timestamps: true });
export const Slider:Model<SliderDocument> = mongoose.model("Slider", SliderSchema);
user.ts
import mongoose, { Schema, Model, Document, HookNextFunction} from 'mongoose';
import validator from 'validator';
import bcryptjs from 'bcryptjs';
import jwt from 'jsonwebtoken';
import {UserPayload} from '../typings/payload';
export interface UserDocument extends Document {
username: string;
password: string;
avatar: string;
email: string;
getAccessToken: () => string;
}
const UserSchema: Schema<UserDocument> = new Schema({
username: {
type: String,
required: [true, "用户名不能为空"],
minlength: [6, '最小长度不能小于6位'],
maxlength: [12, '最大长度不得大于12位']
},
password: String,
avatar: String,
email: {
type: String,
trim: true,
validate: {
validator: validator.isEmail
}
}
}, {
timestamps: true,
toJSON: {
transform: function(_doc, result) {
result.id = result._id;
delete result._id;
delete result.__v;
delete result.password;
return result;
}
}
});
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(err) {
next(err);
}
})
UserSchema.static('login', async function(this:any,username:string, password:string):Promise<UserDocument | null> {
let 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;
}
}
return null;
})
UserSchema.methods.getAccessToken = function(this: UserDocument) :string {
let payload: UserPayload = {id: this._id};
return jwt.sign(payload, process.env.JWT_SECRET_KEY || 'zhuzhu', { expiresIn: '1h'});
}
interface UserModel<T extends Document> extends Model<T> {
login : (username:string, password:string) => UserDocument | null
}
export const User:UserModel<UserDocument> = mongoose.model<UserDocument, UserModel<UserDocument>>('User', UserSchema);
src/ controllers 分类路由
lession.ts
import {Lesson, LessonDocument} from '../models/index';
import { Response, Request} from 'express';
export const list = async (req:any, res:Response) => {
let {category='all', offset, limit} = req.query;
offset = isNaN(offset)? 0 : Number(offset);
limit = isNaN(limit)? 5 : Number(limit);
let query:any = {};
if(category && category !== 'all') {
query.category = category;
}
let total: number = await Lesson.count(query);
let lessons: LessonDocument[] = await Lesson.find(query)
.sort({order:1}).skip(offset).limit(limit)
setTimeout(() => {
res.json({
success: true,
data: {
list: lessons,
hasMore: total > offset + limit
}
})
}, 2000);
};
export const getLesson = async (req:Request, res:Response) => {
let id = req.params.id;
let lesson = await Lesson.findById(id);
setTimeout(() => {
res.json({
success: true,
data: lesson
})
}, 2000);
};
slider.ts
import { Request, Response, NextFunction } from 'express';
import { Slider, SliderDocument } from '../models/index';
export const list = async (_req:Request, res:Response, _next:NextFunction) => {
let sliders: SliderDocument[] = await Slider.find();
res.json({
success: true,
data: sliders
})
}
user.ts
import { Request, Response, NextFunction } from 'express';
import HttpException from '../exception/httpException';
import { User, UserDocument } from '../models/index';
import { validateRegisterInput } from '../utils/validator';
import { StatusCodes } from 'http-status-codes';
import jwt from 'jsonwebtoken';
import { UserPayload} from '../typings/payload';
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(StatusCodes.UNPROCESSABLE_ENTITY, '参数校验失败', errors);
}
let exituser: UserDocument | null = await User.findOne({username});
if(exituser) {
throw new HttpException(StatusCodes.UNPROCESSABLE_ENTITY, '用户名已存在', errors);
}
let user: UserDocument = new User({ username, password, confirmPassword, email });
await user.save();
res.json({
success: true,
data: user.toJSON()
})
}catch(err) {
next(err);
}
}
export const login = async (req:Request, res:Response, next: NextFunction) => {
try{
let { password, username } = req.body;
let 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(StatusCodes.UNAUTHORIZED, '登录失败')
}
}catch(err) {
next(err);
}
}
export const validate = async (req:Request, res:Response, next:NextFunction) => {
const authorization = req.headers.authorization;
if(authorization) {
const access_token = authorization.split(' ')[1];
console.log(access_token, "111111111111111")
if(access_token) {
try{
const payload: UserPayload = jwt.verify(access_token, process.env.JWT_SECRET_KEY || "zhuzhu") as UserPayload;
const user = await User.findById(payload.id);
if(user) {
res.json({
success: true,
data: user.toJSON()
})
}else{
next(new HttpException(StatusCodes.UNAUTHORIZED, '用户未找到'))
}
}catch(err) {
next(new HttpException(StatusCodes.UNAUTHORIZED, 'accesstoken不合法'));
}
}else{
next(new HttpException(StatusCodes.UNAUTHORIZED, 'authorization未提供'));
}
}else {
next(new HttpException(StatusCodes.UNAUTHORIZED, 'authorization未提供'));
}
}
export const uploadAvatar = async(req:Request, res:Response, next: NextFunction) => {
try{
let { userId } = req.body;
let avatar = `${req.protocol}://${req.headers.host}/uploads/${req.file.filename}`;
await User.updateOne({_id: userId}, {avatar});
res.json({
success: true,
data: avatar
})
}catch(err) {
next(err);
}
}
utils/validator.ts
import validator from 'validator';
import { UserDocument } from '../models/index';
export interface RegisterInput extends Partial<UserDocument> {
confirmPassword? : string
}
export interface RegisterInputValidateResult {
errors: RegisterInput,
valid: boolean
}
export const validateRegisterInput = (
username: string,
password:string,
confirmPassword: string,
email: string
):RegisterInputValidateResult => {
let errors: RegisterInput = {};
if(username === undefined || username.length == 0) {
errors.username = '用户名不能为空';
}
if(password === undefined || password.length == 0) {
errors.password = '密码不能为空';
}
if(confirmPassword === undefined || confirmPassword.length == 0) {
errors.confirmPassword = '确认密码不能为空';
}
if(email === undefined || email.length == 0) {
errors.email = '邮箱不能为空';
}
if(password !== confirmPassword) {
errors.email = '密码和确认密码不相等';
}
if(!validator.isEmail(email)) {
errors.email = '邮箱格式不正确';
}
return {
valid: Object.keys(errors).length == 0,
errors
}
}