使用express实现用户登录注册功能,使用ts进行代码开发,使用mysql作为数据库,实现用户登录、登录状态检测、验证码获取接口及用户注册相关接口功能的实现。
源码下载:[点击下载]
(https://download.csdn.net/download/m0_37631110/88909002)
讲解视频:
-
开发视频:
TS实战项目十四:Express项目创建
-
B站视频:
TS实战项目十四:Express项目创建
-
西瓜视频:
https://www.ixigua.com/7331627299491709477
一、知识点
- tsc编译
- tsconfig.json配置项
- 模块定义及导入导出
- 类定义
- 参数属性
- 存取器
- 继承
- 抽象类
- 抽象方法
- 接口
- expres对接mysql数据库
- express-session
- bcryptjs
- express-jwt
- jsonwebtoken
- svg-captcha
- 枚举
- 静态变量
- 接口定义
- async/await
二、功能规划
使用express框架搭建web服务器,切换js为ts方式开发,使用svg-captcha库提供图片验证码信息,使用express-jwt+jsonwebtoken实现登录状态检测及token生成。使用bcryptjs实现用户登录密码的加密及验证处理。
实现三层分离,controller层进行接口控制,service进行业务逻辑处理,dao层进行数据库操作,使用mysql数据库。
三、项目创建
-
安装express生成器:npm install -g express-generator
-
创建express项目:express tsexpress
-
安装ts:npm install typescript --save
-
安装ts相关依赖:
(1) npm install @types/node --save-dev
(2) npm install @types/express --save-dev
-
安装svg-captcha:npm install svg-captcha --save
-
安装rotating-file-stream:npm install rotating-file-stream --save
-
安装mysql:npm install mysql --save
npm install @types/mysql --save-dev
-
安装morgan:npm install morgan --save
npm install @types/morgan --save-dev
-
安装jsonwebtoken:npm install jsonwebtoken --save
npm install @types/jsonwebtoken --save-dev
-
安装express-session:npm install express-session --save
npm install @types/express-session --save-dev
-
安装express-jwt:npm install express-jwt --save
-
安装bcryptjs:npm install bcryptjs --save
npm install @types/bcryptjs --save-dev
-
安装cookie-parser:npm install cookie-parser --save
npm install @types/cookie-parser --save-dev
-
使用ts-node ./bin/www启动项目,启动会报错,需要将app等文件中的代码进行调整,写成ts的写法。
-
项目目录结构:
四、代码实现
- 项目配置文件package.json
{
"name": "demo3",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "ts-node ./bin/www"
},
"dependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/express-session": "^1.17.10",
"@types/jsonwebtoken": "^9.0.5",
"@types/mysql": "^2.15.25",
"bcryptjs": "^2.4.3",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"express": "~4.16.1",
"express-jwt": "^8.4.1",
"express-session": "^1.18.0",
"http-errors": "~1.6.3",
"jade": "~1.11.0",
"jsonwebtoken": "^9.0.2",
"morgan": "~1.9.1",
"mysql": "^2.18.1",
"rotating-file-stream": "^3.2.1",
"svg-captcha": "^1.4.0",
"typescript": "^5.3.3"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.6",
"@types/express": "^4.17.21",
"@types/morgan": "^1.9.9",
"@types/node": "^20.11.16"
}
}
- app.ts
import cookieParser from 'cookie-parser';
import express, {
NextFunction,
Request,
Response,
} from 'express';
import session from 'express-session';
import createError from 'http-errors';
import jwt from 'jsonwebtoken';
import logger from 'morgan';
import path from 'path';
import * as rfs from 'rotating-file-stream'; // version 2.x
import { SYSTEM } from './src/config/Config';
import indexRouter from './src/routes/index';
import jwtToken, { lessPaths } from './src/security/jwtToken';
const app = express();
// view engine setup
app.set('views', path.join(__dirname, './src/views'));
app.set('view engine', 'jade');
//系统日志
var accessLogStream = rfs.createStream('access.log', {
interval: '1d', // rotate daily
path: path.join(__dirname, './logs/log')
})
app.use(logger(':method :url :status :res[content-length] - :response-time ms', { stream: accessLogStream }));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(session({
secret: SYSTEM.privateKey,
resave: false,
saveUninitialized: true,
cookie: { secure: false }
}))
app.use(express.static(path.join(__dirname, 'public')));
//加登陆的拦截
app.use(/.*/, function (err: any, req: Request, res: Response, next: NextFunction) {
let less = lessPaths.find(item => {
return req.url.startsWith(item);
})
if (less) {
next();
return;
}
if (!req.headers.authorization) {
res.status(401).send({
code: 401,
msg: '请先登录!'
});
} else {
jwt.verify(req.headers.authorization.split(" ")[1], SYSTEM.privateKey, (error, decoded) => {
if (error) {
res.status(401).send({
code: 401,
msg: 'token错误或已过期'
});
} else {
req.headers.user = JSON.stringify(decoded);
next();
}
})
}
});
//是否登陆的判断
app.use(jwtToken);
//注册路由
app.use('/', indexRouter);
// catch 404 and forward to error handler
app.use(function (req: Request, res: Response, next: NextFunction) {
next(createError(404));
});
// error handler
app.use(function (err: any, req: Request, res: Response, next: NextFunction) {
if (err.name === "UnauthorizedError") {
res.status(401).send({
code: 401,
msg: '请先登录!'
});
}
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
console.log('启动成功');
module.exports = app;
- utils/DbUtils.ts
// const mysql = require('mysql')
import mysql, {
Connection,
ConnectionConfig,
} from 'mysql';
/**
* 初始化链接
* @param config 数据库的配置
*/
function init(config: ConnectionConfig): Connection {
const connection = mysql.createConnection(config)
connection.connect();
return connection;
}
// connection.query('SELECT 1 + 1 AS solution', (err, rows, fields) => {
// if (err) throw err
// console.log('The solution is: ', rows[0].solution)
// })
// connection.end()
export default {
init
}
- service/BaseService.ts
import BaseDao from '../dao/BaseDao';
import BaseEntity from '../entity/BaseEntity';
import DataEntity from '../entity/DataEntity';
import DelFlag from '../entity/DelFlag';
/**
* 业务处理的基础类
*/
export default abstract class BaseService<T extends BaseEntity> {
constructor(protected _dao: BaseDao<T>) {
}
/**
* 保存数据
* @param data 要保存的数据
*/
save(data: T): void {
if (data instanceof DataEntity) {
(<DataEntity>data).createData = new Date();
(<DataEntity>data).updateData = new Date();
(<DataEntity>data).delflag = DelFlag.NOMAL;
}
this.beforeSave(data);
this._dao.save(data).then(() => {
//TODO 记录日志
}).catch((err => {
throw err;
}));
}
/**
* 数据保存之前,进行数据默认值的填充
*/
protected abstract beforeSave(data: T): void;
/**
* 更新数据
* @param data 要保存的数据
*/
update<K extends DataEntity>(data: K): void {
if (data instanceof DataEntity) {
(<DataEntity>data).updateData = new Date();
}
this.beforeUpdate(data);
let fileds: string[] = this.getFileds();
this._dao.update(data, fileds).then(() => {
//TODO 记录日志
}).catch((err => {
throw err;
}));
}
/**
* 获取需要更新的字段
*/
protected abstract getFileds(): string[];
/**
* 数据保存之前,进行数据默认值的填充
*/
protected abstract beforeUpdate<K extends DataEntity>(data: K): void;
/**
* 删除数据
* @param data 要保存的数据
*/
deleteData(id: number): void {
this._dao.deleteData(id).then(() => {
//TODO 记录日志
}).catch((err => {
throw err;
}));
}
/**
* 删除数据
* @param data 要保存的数据
*/
async get<K extends DataEntity>(id: number): Promise<K | null> {
return this._dao.get(id);
}
}
- service/system/UserService.ts
import bcrypt from 'bcryptjs';
import UserDao from '../../dao/system/UserDao';
import DataEntity from '../../entity/DataEntity';
import User, { UserStatus } from '../../entity/system/User';
import BaseService from '../BaseService';
export default class UserService extends BaseService<User> {
protected getFileds(): string[] {
return ['_account', '_name', '_phone', '_sex', '_status', '_remork'];
}
protected beforeSave(data: User) {
data.status = UserStatus.NOMAL;
let salt = bcrypt.genSaltSync(10);
data.password = bcrypt.hashSync(data.password, salt);
}
protected beforeUpdate<K extends DataEntity>(data: K) {
}
constructor() {
super(new UserDao())
}
/**
* 通过账号查找对应的用户
* @param account 账号信息
*/
async findByAccount(account: string): Promise<User | null> {
return (<UserDao>this._dao).findByAccount(account);
}
/**
* 通过电话号码查找对应的用户
* @param account 电话号码
*/
async findByPhone(phone: string): Promise<User | null> {
return (<UserDao>this._dao).findByPhone(phone);
}
}
- security/jwtToken.ts
import express from 'express';
import { expressjwt } from 'express-jwt';
import { SYSTEM } from '../config/Config';
/**
* 不需要鉴权的地址
*/
export const lessPaths = ["/register", "/register/captcha", "/login", "/login/captcha", "/public/"];
export default expressjwt({
secret: SYSTEM.privateKey,
algorithms: ['HS256'],
getToken: (req: express.Request): string | Promise<string> | undefined => {
if (
req.headers.authorization &&
req.headers.authorization.split(" ")[0] === "Bearer"
) {
return req.headers.authorization.split(" ")[1];
} else if (req.query && req.query.token) {
return <string>req.query.token;
}
return undefined;
}
}).unless({ path: lessPaths });
- routes/index.ts
// var express = require('express');
import express from 'express';
import index from '../controller/index';
var router = express.Router();
router.use(index)
// module.exports = router;
export default router;
- entity/DelFlag.ts
/**
* 数据的删除状态标识
*/
enum DelFlag {
//正常
NOMAL = 1,
//删除
DELETE = 2
}
export default DelFlag;
- entity/DataEntity.ts
import BaseEntity from './BaseEntity';
import DelFlag from './DelFlag';
/**
* 基础实体
*/
export default abstract class DataEntity extends BaseEntity {
/**
* 主键
*/
private _id: number = 0;
/**
* 数据的创建时间
*/
private _createDate: Date = new Date();
/**
* 数据的更新时间
*/
private _updateDate: Date = new Date();
/**
* 备注
*/
private _remork: string = "";
/**
* 状体:
*/
private _delflag: DelFlag = 1;
set id(id: number) {
this._id = id;
}
get id() {
return this._id;
}
set createData(createData: Date) {
this._createDate = createData;
}
get createData() {
return this._createDate;
}
set updateData(updateData: Date) {
this._updateDate = updateData;
}
get updateData() {
return this._updateDate;
}
set remork(remork: string) {
this._remork = remork;
}
get remork() {
return this._remork;
}
set delflag(delflag: DelFlag) {
this._delflag = delflag;
}
get delflag() {
return this._delflag;
}
}
- entity/BaseEntity.ts
export default abstract class BaseEntity {
}
- entity/system/User.ts
import DataEntity from '../DataEntity';
/**
* 用户的状态
*/
export enum UserStatus {
NOMAL = 1,
LOCK
}
/**
* 用户实体
*/
export default class User extends DataEntity {
/**
* 账号
*/
private _account: string = '';
/**
* 姓名
*/
private _name: string = '';
/**
* 电话
*/
private _phone: string = '';
/**
* 性别
*/
private _sex: string = '';
/**
* 密码
*/
private _password: string = '';
/**
* 状态1正常、2锁定
*/
private _status: number = UserStatus.NOMAL;
set account(account: string) {
this._account = account;
}
get account() {
return this._account;
}
set name(name: string) {
this._name = name;
}
get name() {
return this._name;
}
set phone(phone: string) {
this._phone = phone;
}
get phone() {
return this._phone;
}
set sex(sex: string) {
this._sex = sex;
}
get sex() {
return this._sex;
}
set password(password: string) {
this._password = password;
}
get password() {
return this._password;
}
set status(status: UserStatus) {
this._status = status;
}
get status() {
return this._status;
}
}
- dao/BaseDao.ts
import {
Connection,
FieldInfo,
MysqlError,
} from 'mysql';
import { DB } from '../config/Config';
import BaseEntity from '../entity/BaseEntity';
import DataEntity from '../entity/DataEntity';
import DbUtils from '../utils/DbUtils';
/**
* dao基础类
*/
export default abstract class BaseDao<T extends BaseEntity> {
/**
* 获取数据库链接
*/
protected getConn(): Connection {
return DbUtils.init(DB);
}
/**
* 获取要操作的实体表名
*/
protected abstract getTableName(): string;
/**
* 创建新的实体
*/
protected abstract newInsance(): T;
/**
* 保存数据
* @param data 要保存的数据
*/
save(data: T): Promise<unknown> {
let conn = this.getConn();
return new Promise((resolve, reject) => {
conn.query('INSERT INTO ' + this.getTableName() + ' SET ?', data, function (err: MysqlError | null, results?: any, fields?: FieldInfo[]) {
if (err) {
throw err
};
resolve({
err, results, fields
});
});
}).finally(() => {
conn.end();
})
}
/**
* 更新数据
*/
update<K extends DataEntity>(data: K, fileds: string[]): Promise<unknown> {
let conn = this.getConn();
return new Promise((resolve, reject) => {
let sql = 'UPDATE ' + this.getTableName() + ' SET ';
let values: any[] = [];
fileds.forEach((filed, index) => {
if (index > 0) {
sql += ',';
}
sql += filed + '=?';
let value: any = null;
for (const key in data) {
if (key === filed) {
value = data[key];
}
}
values.push(value);
})
values.push(data.id);
conn.query(sql + ' where _id=?', values, function (err: MysqlError | null, results?: any, fields?: FieldInfo[]) {
if (err) {
throw err
};
resolve({
err, results, fields
});
});
}).finally(() => {
conn.end();
})
}
/**
* 删除数据
* @param data
* @returns
*/
deleteData<K extends DataEntity>(id: Number): Promise<unknown> {
let conn = this.getConn();
return new Promise((resolve, reject) => {
conn.query('DELETE FROM ' + this.getTableName() + ' where _id=?', [id], function (err: MysqlError | null, results?: any, fields?: FieldInfo[]) {
if (err) {
throw err
};
resolve({
err, results, fields
});
});
}).finally(() => {
conn.end();
})
}
/**
* 查询数据
*/
async get<K extends DataEntity>(id: Number): Promise<K | null> {
let conn = this.getConn();
return await new Promise<K | null>((resolve, reject) => {
conn.query('select * FROM ' + this.getTableName() + ' where _id=?', [id], (err: MysqlError | null, results?: any, fields?: FieldInfo[]) => {
if (err) {
throw err
};
if (!results || results === null || results.length <= 0) {
resolve(null);
} else {
resolve(Object.assign(this.newInsance(), results[0]));
}
});
}).then().finally(() => {
conn.end();
})
}
}
- dao/system/UserDao.ts
import {
FieldInfo,
MysqlError,
} from 'mysql';
import User from '../../entity/system/User';
import BaseDao from '../BaseDao';
/**
* 用户dao的处理
*/
export default class UserDao extends BaseDao<User> {
protected newInsance(): User {
return new User();
}
protected getTableName(): string {
return 'user';
}
/**
* 通过账号查找对应的用户信息
*/
async findByAccount(account: string): Promise<User | null> {
let conn = this.getConn();
return await new Promise<User | null>((resolve, reject) => {
conn.query('select * FROM ' + this.getTableName() + ' where _account=?', [account], (err: MysqlError | null, results?: any, fields?: FieldInfo[]) => {
if (err) {
throw err
};
if (!results || results === null || results.length <= 0) {
resolve(null);
} else {
resolve(Object.assign(this.newInsance(), results[0]));
}
});
}).then().finally(() => {
conn.end();
})
}
/**
* 通过电话号码查找对应的用户信息
*/
async findByPhone(phone: string): Promise<User | null> {
let conn = this.getConn();
return await new Promise<User | null>((resolve, reject) => {
conn.query('select * FROM ' + this.getTableName() + ' where _phone=?', [phone], (err: MysqlError | null, results?: any, fields?: FieldInfo[]) => {
if (err) {
throw err
};
if (!results || results === null || results.length <= 0) {
resolve(null);
} else {
resolve(Object.assign(this.newInsance(), results[0]));
}
});
}).then().finally(() => {
conn.end();
})
}
}
- controller/index.ts
// var express = require('express');
import express from 'express';
import LoginController from './login/LoginController';
import RegisterController from './register/RegisterController';
import UserController from './system/UserController';
var router = express.Router();
router.use(UserController);
router.use(LoginController);
router.use(RegisterController);
// module.exports = router;
export default router;
- controller/BaseViewController.ts
import {
NextFunction,
Request,
Response,
} from 'express';
import DataEntity from '../entity/DataEntity';
import BaseService from '../service/BaseService';
import BaseController from './BaseController';
/**
* 数据查看
*/
export default abstract class BaseViewContorller<T extends DataEntity> extends BaseController<T> {
constructor(protected service: BaseService<T>) {
super();
}
/**
* 创建实体实例
*/
protected abstract newInstance(): T;
/**
* 获取数据详情
* @param req 请求
* @param res 返回
* @param next
*/
get(req: Request, res: Response, next: NextFunction) {
let id = req.query.id;
if (!id) {
res.status(400).send({
code: 400,
msg: '参数为空'
});
return;
}
this.service.get(parseInt(<string>id)).then((value: DataEntity | null) => {
res.send({
code: 200,
msg: '',
data: value
})
}).catch((err: Error) => {
res.status(500).send({
code: 500,
msg: '处理失败'
});
});
}
}
- controller/BaseEditController.ts
import {
NextFunction,
Request,
Response,
} from 'express';
import DataEntity from '../entity/DataEntity';
import BaseService from '../service/BaseService';
import BaseViewContorller from './BaseViewController';
/**
* 数据查看
*/
export default abstract class BaseEntityContorller<T extends DataEntity> extends BaseViewContorller<T> {
constructor(service: BaseService<T>) {
super(service);
}
/**
* 校验数据的合法性
*/
protected abstract validate(data: T): boolean;
/**
* 创建实体实例
*/
protected abstract newInstance(): T;
/**
* 添加数据
* @param req 请求
* @param res 返回
* @param next
*/
save(req: Request, res: Response, next: NextFunction) {
let param = req.body;
if (!param) {
res.status(400).send({
code: 400,
msg: '参数为空'
});
return;
}
//做数据的具体校验
let entity: T = this.newInstance();
if (!this.validate(Object.assign(entity, param))) {
res.status(400).send({
code: 400,
msg: '参数不合法'
});
return;
}
//数据权限校验
try {
this.service.save(entity);
res.send({
code: 200,
msg: ''
})
} catch (error) {
console.error(error);
res.status(500).send({
code: 500,
msg: '处理失败'
});
}
}
/**
* 更新数据
* @param req 请求
* @param res 返回
* @param next
*/
update(req: Request, res: Response, next: NextFunction) {
let param = req.body;
if (!param) {
res.status(400).send({
code: 400,
msg: '参数为空'
});
return;
}
//做数据的具体校验
let entity: T = this.newInstance();
if (!this.validate(Object.assign(entity, param))) {
res.status(400).send({
code: 400,
msg: '参数不合法'
});
return;
}
//数据权限校验
try {
this.service.update(entity);
res.send({
code: 200,
msg: ''
})
} catch (error) {
console.error(error);
res.status(500).send({
code: 500,
msg: '处理失败'
});
}
}
/**
* 删除数据
* @param req
* @param res
* @param next
*/
deleteData(req: Request, res: Response, next: NextFunction) {
let id = req.query.id;
if (!id) {
res.status(400).send({
code: 400,
msg: '参数为空'
});
return;
}
try {
this.service.deleteData(parseInt(<string>id));
res.send({
code: 200,
msg: ''
})
} catch (error) {
console.error(error);
res.status(500).send({
code: 500,
msg: '处理失败'
});
}
}
}
- controller/BaseController.ts
/**
* 接口的基类
*/
export default abstract class BaseController<T> {
}
- controller/system/UserController.ts
// var express = require('express');
import express from 'express';
import User from '../../entity/system/User';
import UserService from '../../service/system/UserService';
import BaseEntityContorller from '../BaseEditController';
/**
* 用户的接口
*/
export class UserController extends BaseEntityContorller<User> {
constructor() {
super(new UserService());
}
protected validate(data: User): boolean {
if (!data.account) {
return false;
}
if (!data.name) {
return false;
}
return true;
}
protected newInstance(): User {
return new User();
}
}
var router = express.Router();
const userController = new UserController();
//获取
router.get('/get', userController.get.bind(userController));
//保存
router.post('/save', userController.save.bind(userController));
//更新
router.post('/update', userController.update.bind(userController));
//删除
router.get('/delete', userController.deleteData.bind(userController));
// module.exports = router;
export default router;
- controller/register/RegisterController.ts
// var express = require('express');
import express from 'express';
import svgCaptcha from 'svg-captcha';
import { SYSTEM } from '../../config/Config';
import User from '../../entity/system/User';
import UserService from '../../service/system/UserService';
import BaseController from '../BaseController';
/**
* 登陆接口
*/
export class RegisterController extends BaseController<null> {
/**
* 用户服务
*/
useService: UserService;
constructor() {
super();
this.useService = new UserService();
}
/**
* 生成验证码
*/
captcha(req: express.Request, res: express.Response, next: express.NextFunction) {
var captcha = svgCaptcha.create();
req.session.captcha = captcha.text;
req.session.captchaData = new Date().getTime();
res.type('svg');
res.status(200).send(captcha.data);
}
/**
* 用户注册功能
*/
async register(req: express.Request, res: express.Response, next: express.NextFunction) {
/**
* 1.检测参数是否合法,账号、密码、验证码等不能为空
* 2.校验验证码是否合法:验证码是否正确、验证码是否过期
* 3.检测账号是否重复
* 4.保存账号信息
*/
try {
//验证码校验
let captcha = req.body.captcha;
if (!captcha) {
res.status(400).send({
code: 400,
msg: '验证码不能为空'
})
return;
}
//参数校验
let user: User = new User();
user.account = req.body.account;
user.name = req.body.name;
user.password = req.body.password;
user.sex = req.body.sex;
user.phone = req.body.phone;
if (!user || !user.account || !user.name || !user.password || !user.phone) {
res.status(400).send({
code: 400,
msg: '参数不合法'
})
return;
}
//验证码校验
let systemCaptcha = req.session.captcha;
let systemCaptchaDate = req.session.captchaData;
if (!systemCaptcha || !systemCaptchaDate) {
res.status(500).send({
code: 500,
msg: '验证码不合法'
})
return;
}
if (systemCaptcha !== captcha) {
res.status(500).send({
code: 500,
msg: '验证码错误'
})
return;
}
if (new Date().getTime() - systemCaptchaDate > SYSTEM.captchaTimeOut) {
res.status(500).send({
code: 500,
msg: '验证码已失效'
})
return;
}
//账号校验
let systemUser = await this.useService.findByAccount(user.account);
if (systemUser && systemUser.id) {
res.status(500).send({
code: 500,
msg: '改账号已注册,无法重复注册'
})
return;
}
//电话号码检测
systemUser = await this.useService.findByPhone(user.phone);
if (systemUser && systemUser.id) {
res.status(500).send({
code: 500,
msg: '改账号已注册,无法重复注册'
})
return;
}
this.useService.save(user);
res.send({
code: 200,
msg: '注册成功'
});
} catch (error) {
console.error(error);
res.status(503).send({
code: 503,
msg: error
})
return;
}
}
}
var router = express.Router();
const registerController = new RegisterController();
//注册接口
router.post('/register', registerController.register.bind(registerController));
//获取验证码
router.get('/register/captcha', registerController.captcha.bind(registerController));
// module.exports = router;
export default router;
- controller/login/LoginController.ts
import bcrypt from 'bcryptjs';
// var express = require('express');
import express from 'express';
import jwt from 'jsonwebtoken';
import svgCaptcha from 'svg-captcha';
import { SYSTEM } from '../../config/Config';
import UserService from '../../service/system/UserService';
import BaseController from '../BaseController';
/**
* 登陆接口
*/
export class LoginController extends BaseController<null> {
/**
* 用户服务
*/
useService: UserService;
constructor() {
super();
this.useService = new UserService();
}
/**
* 生成验证码
*/
captcha(req: express.Request, res: express.Response, next: express.NextFunction) {
var captcha = svgCaptcha.create();
req.session.captcha = captcha.text;
req.session.captchaData = new Date().getTime();
res.type('svg');
res.status(200).send(captcha.data);
}
/**
* 登陆操作
*/
async login(req: express.Request, res: express.Response, next: express.NextFunction) {
/**
* 1.检测参数是否合法,账号、密码、验证码等不能为空
* 2.校验验证码是否合法:验证码是否正确、验证码是否过期
* 3.账号密码是否合法的校验
*/
let captcha = req.body.captcha;
let account = req.body.account;
let password = req.body.password;
if (!captcha || !account || !password) {
res.status(400).send({
code: 400,
msg: '参数为空'
})
return;
}
//验证码合法性的判断"wBxqQKlIOWlecyawht2W7dJTCI9DD_qC"
let systemCaptcha = req.session.captcha;
let systemCaptchaDate = req.session.captchaData;
if (!systemCaptcha || !systemCaptchaDate) {
res.status(500).send({
code: 500,
msg: '验证码不合法'
})
return;
}
if (systemCaptcha !== captcha) {
res.status(500).send({
code: 500,
msg: '验证码错误'
})
return;
}
if (new Date().getTime() - systemCaptchaDate > SYSTEM.captchaTimeOut) {
res.status(500).send({
code: 500,
msg: '验证码已失效'
})
return;
}
//账号密码合法性的判断
let systemUser = await this.useService.findByAccount(account).catch(error => {
console.error(error);
res.status(503).send({
code: 503,
msg: '系统错误:' + error.message
})
});
if (!systemUser) {
res.status(500).send({
code: 500,
msg: '账号错误'
})
return;
}
if (!bcrypt.compareSync(password, systemUser.password)) {//TODO 需进行加密判断
res.status(500).send({
code: 500,
msg: '密码错误'
})
return;
}
//生成token,并且返回登陆成功信息
let token = jwt.sign({
id: systemUser.id,
name: systemUser.name
}, SYSTEM.privateKey, {
expiresIn: '1d' // 1天
});
res.header("authorization", token);
res.send({
code: 200,
msg: ''
})
}
}
var router = express.Router();
const loginController = new LoginController();
//登陆接口
router.post('/login', loginController.login.bind(loginController));
//获取验证码
router.get('/login/captcha', loginController.captcha.bind(loginController));
// module.exports = router;
export default router;
- config/Config.ts
/**
* 数据库配置
*/
export class DB {
static host = 'localhost';
static port = 3306;
static user = "root";
static password = 'Root123456*';
static database = 'expressdemo';
static charset = 'utf8mb4'
}
/**
* 系统的一些配置项
*/
export class SYSTEM {
/**
* 验证码的超时时间
*/
static captchaTimeOut = 3 * 60 * 1000;
/**
* token加密的密钥
*/
static privateKey = "junjunjun006"
}
五、接口列表
序号 | 接口 | 请求方式 | 备注 |
---|---|---|---|
1 | /login | POST | 登录接口 |
2 | /login/captcha | GET | 获取登录验证码接口 |
3 | /register | POST | 用户注册接口 |
4 | /register/captcha | GET | 获取用户注册验证码接口 |
5 | /get | GET | 加载用户详情 |
6 | /save | POST | 保存用户信息 |
7 | /update | POST | 更新用户信息 |
8 | /delete | GET | 删除用户信息 |
六、问题汇总
问题一: session设置中的cookie的secure设置为true时,cookie只有在https时生效,导致postman测试接口时出现session不一致的问题。将cookie中的secure设置为false即可。
问题二: 数据库保存时id设置,可以设置为数据库自增长或者使用uuid,也可以手动查询最新id,并且进行id的更新,但这种方式不建议。
问题三: 使用async及await时,需要返回Promise类型才可以,不然ts编译器会报错。