TS项目实战三:Express实现登录注册功能后端

  使用express实现用户登录注册功能,使用ts进行代码开发,使用mysql作为数据库,实现用户登录、登录状态检测、验证码获取接口及用户注册相关接口功能的实现。
源码下载:[点击下载]
(https://download.csdn.net/download/m0_37631110/88909002)
讲解视频:

  1. 开发视频:

    TS实战项目十四:Express项目创建

  2. B站视频:

    TS实战项目十四:Express项目创建

  3. 西瓜视频:
    https://www.ixigua.com/7331627299491709477
    在这里插入图片描述

一、知识点

  1. tsc编译
  2. tsconfig.json配置项
  3. 模块定义及导入导出
  4. 类定义
  5. 参数属性
  6. 存取器
  7. 继承
  8. 抽象类
  9. 抽象方法
  10. 接口
  11. expres对接mysql数据库
  12. express-session
  13. bcryptjs
  14. express-jwt
  15. jsonwebtoken
  16. svg-captcha
  17. 枚举
  18. 静态变量
  19. 接口定义
  20. async/await

二、功能规划

  使用express框架搭建web服务器,切换js为ts方式开发,使用svg-captcha库提供图片验证码信息,使用express-jwt+jsonwebtoken实现登录状态检测及token生成。使用bcryptjs实现用户登录密码的加密及验证处理。
  实现三层分离,controller层进行接口控制,service进行业务逻辑处理,dao层进行数据库操作,使用mysql数据库。

三、项目创建

  1. 安装express生成器:npm install -g express-generator
    在这里插入图片描述

  2. 创建express项目:express tsexpress
    在这里插入图片描述

  3. 安装ts:npm install typescript --save
    在这里插入图片描述

  4. 安装ts相关依赖:
    (1) npm install @types/node --save-dev
    请添加图片描述
    (2) npm install @types/express --save-dev
    请添加图片描述

  5. 安装svg-captcha:npm install svg-captcha --save
    请添加图片描述

  6. 安装rotating-file-stream:npm install rotating-file-stream --save
    请添加图片描述

  7. 安装mysql:npm install mysql --save
    请添加图片描述
    npm install @types/mysql --save-dev
    请添加图片描述

  8. 安装morgan:npm install morgan --save
    请添加图片描述
    npm install @types/morgan --save-dev
    请添加图片描述

  9. 安装jsonwebtoken:npm install jsonwebtoken --save
    请添加图片描述
    npm install @types/jsonwebtoken --save-dev
    请添加图片描述

  10. 安装express-session:npm install express-session --save
    请添加图片描述
    npm install @types/express-session --save-dev
    请添加图片描述

  11. 安装express-jwt:npm install express-jwt --save
    请添加图片描述

  12. 安装bcryptjs:npm install bcryptjs --save
    请添加图片描述
    npm install @types/bcryptjs --save-dev
    请添加图片描述

  13. 安装cookie-parser:npm install cookie-parser --save
    请添加图片描述
    npm install @types/cookie-parser --save-dev
    请添加图片描述

  14. 使用ts-node ./bin/www启动项目,启动会报错,需要将app等文件中的代码进行调整,写成ts的写法。

  15. 项目目录结构:
    请添加图片描述

四、代码实现

  1. 项目配置文件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"
  }
}
  1. 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;
  1. 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
}
  1. 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);
    }
}
  1. 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);
    }

}
  1. 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 });
  1. 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;
  1. entity/DelFlag.ts
/**
 * 数据的删除状态标识
 */
enum DelFlag {
    //正常
    NOMAL = 1,
    //删除
    DELETE = 2
}
export default DelFlag;
  1. 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;
    }
}
  1. entity/BaseEntity.ts
export default abstract class BaseEntity {
}
  1. 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;
    }
}
  1. 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();
        })
    }
}
  1. 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();
        })
    }
}
  1. 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;
  1. 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: '处理失败'
            });
        });
    }
}
  1. 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: '处理失败'
            });
        }
    }
}
  1. controller/BaseController.ts
/**
 * 接口的基类
 */
export default abstract class BaseController<T> {
}
  1. 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;
  1. 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;
  1. 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;
  1. 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/loginPOST登录接口
2/login/captchaGET获取登录验证码接口
3/registerPOST用户注册接口
4/register/captchaGET获取用户注册验证码接口
5/getGET加载用户详情
6/savePOST保存用户信息
7/updatePOST更新用户信息
8/deleteGET删除用户信息

六、问题汇总

问题一: session设置中的cookie的secure设置为true时,cookie只有在https时生效,导致postman测试接口时出现session不一致的问题。将cookie中的secure设置为false即可。
问题二: 数据库保存时id设置,可以设置为数据库自增长或者使用uuid,也可以手动查询最新id,并且进行id的更新,但这种方式不建议。
问题三: 使用async及await时,需要返回Promise类型才可以,不然ts编译器会报错。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/430221.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【论文阅读】《Graph Neural Prompting with Large Language Models》

文章目录 0、基本信息1、研究动机2、创新点3、准备3.1、知识图谱3.2、多项选择问答3.3、提示词工程&#xff08;prompt engineering&#xff09; 4、具体实现4.1、提示LLMs用于问答4.2、子图检索4.3、Graph Neural Prompting4.3.1、GNN Encoder4.3.2、Cross-modality Pooling4.…

UE4升级UE5 蓝图节点变更汇总(4.26/27-5.2/5.3)

一、删除部分 Ploygon Editing删除 Polygon Editing这个在4.26、4.27中的插件&#xff0c;在5.1后彻底失效。 相关的蓝图&#xff0c;如编辑器蓝图 Generate mapping UVs等&#xff0c;均失效。 如需相关功能&#xff0c;请改成Dynamic Mesh下的方法。 GetSupportedClass删…

Vue项目性能分析工具: vue-cli-plugin-webpack-bundle-analyzer

在优化项目的时候&#xff0c;每次打包后只知道包文件大&#xff0c;却不知道那个文件大&#xff0c;那个文件还有优化的空间&#xff0c;所以&#xff0c;推荐一款工具&#xff0c;只要在项目中安装配置一下&#xff0c;便可以一目了然的呈现出打包后资源所占的比例&#xff0…

企业招聘信息二维码,如何制作?其优势在哪里?

伴随着三月的春风和细雨&#xff0c;招聘和求职市场也正在回暖。招聘网站的广告攻占了各大社交平台和遍布城市的广告牌&#xff0c;求职者忙着四处打探招聘信息、投简历&#xff0c;公司也在为招聘工作、寻找合适的人才而忙得不可开交。 今天我们要分享的是&#xff0c;如何制…

基于springboot的抗疫物资管理系统论文

目 录 摘 要 1 前 言 2 第1章 概述 2 1.1 研究背景 3 1.2 研究目的 3 1.3 研究内容 4 第二章 开发技术介绍 5 2.1相关技术 5 2.2 Java技术 6 2.3 MySQL数据库 6 2.4 Tomcat介绍 7 2.5 Spring Boot框架 8 第三章 系统分析 9 3.1 可行性分析 9 3.1.1 技术可行性 9 3.1.2 经济可行…

世界最强AI大模型易主了?昨晚,Claude 3系列模型发布,GPT-4时代终结?

3 月 4 日&#xff0c;被称为 OpenAI 最强竞争对手的大模型公司 Anthropic 宣布推出 Claude3 系列模型&#xff0c;与 Gemini 类似&#xff0c;模型按照大小分为三个&#xff1a;Claude 3 Haiku、Claude 3 Sonnet 和 Claude 3 Opus。Opus 目前在官方发布的测试成绩中全方位超越…

Ajax与JQuery

一、认识Ajax 1.1 web2.0的特点 用户贡献内容内容聚合RSS更丰富的用户体验 1.2 Ajax的优势 无刷新&#xff1a;不刷新整个业务&#xff0c;只刷新局部 无刷新的好处&#xff1a; 只更新部分页面&#xff0c;有效利用带宽提供连续的用户体验提供类似C/S的交互效果&#xff…

产品推荐 - GX-SOPC-5CEFA5-M484 FPGA核心开发板

● 核心板采用8层板精心设计 ● FPGA&#xff1a;采用Intel&#xff08;ALTERA&#xff09; Cyclone V 5CEFA5&#xff0c;Les为77K&#xff0c;内嵌存储器为4460Kb&#xff0c;硬件乘法器为300个&#xff0c;最大等效门数约2300万门&#xff1b;新增DSP Block&#xff08;150…

【C语言】Leetcode 876. 链表的中间节点

主页&#xff1a;17_Kevin-CSDN博客 专栏&#xff1a;《Leetcode》 题目 通过题目的要求可以判断出有两种示例要解决&#xff0c;一种是偶数节点的链表&#xff0c;一种是奇数节点的链表&#xff0c;应对这两种情况我们需要使程序对二者都可以兼容。 解决思路 struct ListNode…

UBOOT和LINUX 调试等级设置

比较好的网页 UBOOT LINUX 设置相关 方法1&#xff1a; echo 5 > /proc/sys/kernel/printk 缺点&#xff1a;方法1无法修改在内核启动时的输出信息 方法2&#xff1a; 通过uboot的环境变量bootargs传递打印级别的参数 set bootargs root/dev/nfs init/linuxrc nfsroot19…

pandas数据分析42——读取和写入stata和spss的数据格式

python就是胶水语言&#xff0c;啥文件基本都能读取&#xff0c;而且pandas作为数据分析最好用的包&#xff0c;其功能自然也很多&#xff0c;可以读取各种数据文件。 本次就来演示一下怎么读取stata文件&#xff0c;和spss文件&#xff0c;他们不仅储存了数据和变量&#xff…

用红黑树封装实现map和set

map和set的实现原理 为了方便实现我们的map和set&#xff0c;我们肯定是要养成看源码的习惯的&#xff0c;看了源码之后你才会感受到大佬的强大&#xff01; 在源码里面&#xff0c;对于map和set的实现&#xff0c;底层是用同一棵红黑树封装出来的&#xff0c;并不是用了两棵…

Spring基础——Bean定义的继承(Bean配置中的parent属性)

Bean的继承 当一个Bean中定义了很多配置信息&#xff0c;可以将一部分固定信息抽象成父Bean&#xff0c;子Bean从父Bean继承配置数据&#xff0c;并根据需要可以覆盖或添加其他数据&#xff0c;这样可以使开发变的更加高效。 父Bean的定义 父Bean所定义的配置属性子Bean必须…

JWT的是什么

session共享 什么是session共享 Session共享是指在分布式系统中&#xff0c;在多个服务器之间共享同一个用户的会话数据。在传统的Web应用中&#xff0c;用户的会话信息通常存储在服务器端的Session中&#xff0c;而每个用户的请求在同一个服务器上处理&#xff0c;因此可以轻…

智奇科技工业 Linux 屏更新开机logo

智奇科技工业 Linux 屏更新开机logo 简介制作logo.img文件1、转换格式得到logo.bmp2、使用Linux命令生成img文件 制作rootfs.img文件替换rootfs.img中的logo 生成update.img固件附件 简介 智奇科技的 Linux 屏刷开机logo必须刷img镜像文件&#xff0c;比较复杂。 制作logo.i…

深度学习-Pytorch实现经典AlexNet网络:山高我为峰

深度学习-Pytorch实现经典AlexNet网络之山高我为峰 深度学习中&#xff0c;经典网络引领一波又一波的技术革命&#xff0c;从LetNet到当前最火的GPT所用的Transformer&#xff0c;它们把AI技术不断推向高潮。2012年AlexNet大放异彩&#xff0c;它把深度学习技术引领第一个高峰…

AI论文速读 | 大语言模型作为城市居民——利用LLM智能体框架生成人类移动轨迹

题目&#xff1a;Large Language Models as Urban Residents: An LLM Agent Framework for Personal Mobility Generation 作者&#xff1a;Jiawei Wang (王家伟), Renhe Jiang&#xff08;姜仁河&#xff09;, Chuang Yang&#xff08;杨闯&#xff09;, Zengqing Wu&#xf…

JeeSite Vue3:前端开发的未来之路

JeeSite Vue3&#xff1a;前端开发的未来之路 随着技术的飞速发展&#xff0c;前端开发技术日新月异。在这个背景下&#xff0c;JeeSite Vue3 作为一个基于 Vue3、Vite、Ant-Design-Vue、TypeScript 和 Vue Vben Admin 的前端框架&#xff0c;引起了广泛关注。它凭借其先进的技…

mapbox加载全球3D建筑

本案例使用Mapbox GL JavaScript库进行加载全球3D建筑。 文章目录 1. 引入 CDN 链接2. 创建地图3. 监听地图加载完成事件3.1. 获取地图的样式中的图层3.2. 查找图层3.3. 添加三维建筑图层 4. 演示效果5. 代码实现 1. 引入 CDN 链接 <!-- 1.引入CDN链接 --> <script sr…