「遇见周大侠(网名:程序猿),长沙奥科领航信息技术公司的资深软件开发工程师。他的软件开发系列课件和项目,凝聚了多年的专业积累与热情。每套课件都包含丰富的案例和深入的知识点解析,每一讲都配备了精心设计的课后作业,旨在为热爱编程的你提供最佳编程实践。是教师备课的得力助手,是学生学习的贴心伴侣,亦是毕业设计的灵感源泉。现在,请搜索微信小程序「奥科领航信息技术 」,让我们一起深入代码的世界,探索无限可能!」
一、后台项目
1.1技术方案
Node.js + TypeORM + Express
1.2 API设计
1.2.1 POST: /users/login
(1)请求动作:POST
(2)功能说明:用户登录
(3)请求参数
通过请求体发送数据,格式如下:
{
"account": "xxx",
"password": "xxx"
}
(4)登录失败的响应报文
{
statusCode: 401,
data:
{
errMsg: "登录失败"
}
}
(5)登录成功的响应报文
{
statusCode: 200,
data:
{
"token": "xxx"
}
}
1.2.2 POST: /articles
(1)请求动作:POST
(2)功能说明:发表文章
(3)请求参数
通过请求体发送数据,格式如下:
{
"title": "文章标题",
"content": "文章内容",
"userId": 用户ID
}
(4)响应报文
{
}
1.2.3 GET: /articles
(1)请求动作:GET
(2)功能说明:分页获取文章信息
(3)请求参数
通过查询字符串发送数据,格式如下:
{
"pageIndex": 1,
"pageSize": 10
}
(4)响应报文
{
statusCode: 200,
data:
{
total: 20,
rows: [{
id: 1,
title: "文章标题",
content: "文章内容",
comments: [{
id: 1,
content: "评论内容2"
},{
id: 2,
content: "评论内容2"
}]
}]
}
}
1.3 创建项目
typeorm init --name typeorm_server --database mysql --express
项目创建好之后,切换到typeorm_server目录,安装以下的依赖包:
cnpm i jsonwebtoken --save
cnpm i @types/express --save-dev
cnpm i @types/jsonwebtoken --save-dev
最终package.json文件的内容大体如下:
{
"name": "typeorm_server",
"version": "0.0.1",
"description": "Awesome project developed with TypeORM.",
"type": "commonjs",
"devDependencies": {
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^16.11.10",
"ts-node": "10.9.1",
"typescript": "4.5.2"
},
"dependencies": {
"body-parser": "^1.19.1",
"express": "^4.17.2",
"jsonwebtoken": "^9.0.2",
"mysql": "^2.14.1",
"reflect-metadata": "^0.1.13",
"typeorm": "0.3.19"
},
"scripts": {
"start": "ts-node src/index.ts",
"typeorm": "typeorm-ts-node-commonjs"
}
}
1.4 配置数据源
src/data-source.ts
import "reflect-metadata"
import { DataSource } from "typeorm"
export const AppDataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "123456",
database: "typeorm_demo",
synchronize: true,
logging: false,
entities: [__dirname + "/entity/*.ts"],
})
请确保在MySQL服务器中已经创建了一个名为typeorm_demo的数据库。
1.5 创建实体类
1.5.1 用户实体类
src/entity/User.ts
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity({ name: "users" })
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ type: "varchar", length: 20, nullable: false })
account: string
@Column({ type: "varchar", length: 20, nullable: false })
password: string
@Column({ type: "varchar", length: 50, nullable: true })
name: string
}
1.5.2 文章实体类
src/entity/Article.ts
import { Entity, Column, PrimaryGeneratedColumn, OneToMany, ManyToOne, CreateDateColumn } from "typeorm";
import { Comment } from "./Comment";
import { User } from "./User";
@Entity({ name: "articles" })
export class Article {
@PrimaryGeneratedColumn()
id: number
@Column({ type: "varchar", length: 100, nullable: false })
title: string
@Column({ type: "text", nullable: false })
content: string
@CreateDateColumn()
createTime: Date
@OneToMany(() => Comment, comment => comment.article, { cascade: true })
comments: Comment[]
@ManyToOne(() => User)
user: User
}
1.5.3 评论实体类
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, ManyToOne } from "typeorm";
import { User } from "./User";
import { Article } from "./Article";
@Entity({ name: "comments" })
export class Comment {
@PrimaryGeneratedColumn()
id: number
@Column({ length: 200 })
content: string
@CreateDateColumn()
createTime: Date
@ManyToOne(() => User)
user: User
@ManyToOne(() => Article, article => article.comments, { onDelete: "CASCADE" })
article: Article
}
1.5 入口程序
src/index.ts
import * as express from "express"
import * as bodyParser from "body-parser"
import { Request, Response } from "express"
import { AppDataSource } from "./data-source"
AppDataSource.initialize().then(async () => {
const app = express()
app.use(bodyParser.json())
app.listen(3000)
console.log("Express server has started on port 3000")
}).catch(error => console.log(error))
1.6 启动项目
nodemon src/index.ts
1.7 数据表结构
自动生成的表结构如下:
mysql> use typeorm_demo
Database changed
mysql> desc `users`;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| account | varchar(20) | NO | | NULL | |
| password | varchar(20) | NO | | NULL | |
| name | varchar(50) | YES | | NULL | |
+----------+-------------+------+-----+---------+----------------+
4 rows in set (0.02 sec)
mysql> desc `articles`;
+------------+--------------+------+-----+----------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+----------------------+-------------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| title | varchar(100) | NO | | NULL | |
| content | text | NO | | NULL | |
| userId | int(11) | YES | MUL | NULL | |
| createTime | datetime(6) | NO | | CURRENT_TIMESTAMP(6) | DEFAULT_GENERATED |
+------------+--------------+------+-----+----------------------+-------------------+
5 rows in set (0.00 sec)
mysql> desc `comments`;
+------------+--------------+------+-----+----------------------+-------------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+----------------------+-------------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| content | varchar(200) | NO | | NULL | |
| createTime | datetime(6) | NO | | CURRENT_TIMESTAMP(6) | DEFAULT_GENERATED |
| userId | int(11) | YES | MUL | NULL | |
| articleId | int(11) | YES | MUL | NULL | |
+------------+--------------+------+-----+----------------------+-------------------+
5 rows in set (0.03 sec)
注意:数据表结构创建好之后,记得将数据源配置文件中的代码同步功能给关了:
synchronize: false,
1.8 创建控制器
1. JWT身份验证
1.9.1 辅助工具
src/utils/jwt.ts
// 导入依赖包
import * as jwt from "jsonwebtoken";
import { User } from "../entity/User";
// 密钥
const key: string = "obtk";
// 过期时间,单位为秒。这里设置为2个小时
const expires: number = 60 * 60 * 2;
//生成token
const generateToken = (playload: User) => {
let token = jwt.sign({
account: playload.account,
name: playload.name
}, key, { expiresIn: expires });
return token;
}
//验证token
const verityToken = (token: string) => {
try {
let tokenKey: any = jwt.verify(token, key);
return {
errMsg: "ok",
tokenKey
}
} catch {
return {
errMsg: "验证失败",
token
}
}
}
export { generateToken, verityToken };
1.9.2 拦截器
src/utils/interceptor.ts
import { NextFunction, Request, Response } from 'express';
import * as jwt from './jwt';
export function preHandler(req: Request, res: Response, next: NextFunction) {
// 登录API不予验证
if (req.url != "/users/login") {
// 取出附加在headers中的token
let token: string = req.headers.token as string;
if (token) {
// 验证token是否有效
let result = jwt.verityToken(token);
if (result.errMsg == "ok") {
next();
} else {
res.status(401).send({
errMsg: "登录已过期,请重新登录"
});
}
} else {
res.status(403).send({
errMsg: "无访问权限,请先登录",
});
}
} else {
next();
}
}
1.10 数据访问层
1.10.1 用户数据访问
src/dao/UserDAO.ts
import { AppDataSource } from "../data-source"
import { User } from "../entity/User"
export class UserDAO {
private userRepository = AppDataSource.getRepository(User)
public async login(user: User) {
let { account, password } = user;
let item = await this.userRepository.findOne({
where: {
account,
password
}
});
return item;
}
}
1.11 服务层
1.11.1 用户服务
src/service/UserService.ts
import { UserDAO } from "../dao/UserDAO";
import { User } from "../entity/User";
export class UserService {
private userDAO: UserDAO;
constructor() {
this.userDAO = new UserDAO();
}
public async login(user: User) {
let item = await this.userDAO.login(user);
return item;
}
}
1.12 控制器
1.12.1 用户控制器
src/controller/UserController.ts
import { NextFunction, Request, Response } from "express"
import { User } from "../entity/User"
import * as jwt from "../utils/jwt";
import { UserService } from "../service/UserService";
export class UserController {
private userService: UserService;
constructor() {
this.userService = new UserService();
}
public async login(req: Request, res: Response, next: NextFunction) {
let { account, password } = req.body;
let user = new User();
user.account = account;
user.password = password;
let item = await this.userService.login(user);
if (item) {
return res.status(200).send({
data: {
token: jwt.generateToken(user)
}
})
} else {
return res.status(401).send({
data: {
errMsg: "登录失败"
}
})
}
}
}
1.13 路由
1.13.1 用户路由
src/route/UserRoute.ts
import { UserController } from "../controller/UserController";
export const userRoutes = [{
method: "post",
route: "/users/login",
controller: UserController,
action: "login"
}]
1.13.2 路由集合
src/route/routes.ts
import { userRoutes } from "./UserRoute";
let routes = [...userRoutes];
export { routes }
1.14 挂载路由
在入口文件src/index.ts中挂载所有路由(第12到24行代码)
import * as express from "express";
import { Request, Response, NextFunction } from "express"
import * as bodyParser from "body-parser"
import { AppDataSource } from "./data-source"
import { routes } from "./routes/routes";
AppDataSource.initialize().then(async () => {
const app = express()
app.use(bodyParser.json())
// 遍历Routes数组中的每个路由配置
routes.forEach(route => {
// 为每个路由配置的方法和路径添加对应的处理函数。形式:app.get("/users", (req, res, nxt) => {})
(app as any)[route.method](route.route, (req: Request, res: Response, next: NextFunction) => {
// 实例化对应的控制器,并调用相应的方法处理请求。控制器是一个类,需要创建类的实例来调用类中定义的方法
(new (route.controller as any))[route.action](req, res, next).catch((err: Error) => {
res.status(500).send({
data: {
errMsg: err.message
}
});
})
})
})
app.listen(3000)
console.log("Express server has started on port 3000")
}).catch(error => console.log(error))
或者:
import * as express from "express";
import { Request, Response, NextFunction } from "express"
import * as bodyParser from "body-parser"
import { AppDataSource } from "./data-source"
import { routes } from "./routes/routes";
AppDataSource.initialize().then(async () => { }).catch(error => console.log(error))
const app = express()
app.use(bodyParser.json())
// 遍历Routes数组中的每个路由配置
routes.forEach(route => {
// 为每个路由配置的方法和路径添加对应的处理函数。形式:app.get("/users", (req, res, nxt) => {})
(app as any)[route.method](route.route, (req: Request, res: Response, next: NextFunction) => {
// 实例化对应的控制器,并调用相应的方法处理请求。控制器是一个类,需要创建类的实例来调用类中定义的方法
(new (route.controller as any))[route.action](req, res, next).catch((err: Error) => {
res.status(500).send({
data: {
errMsg: err.message
}
});
})
})
})
app.listen(3000)
console.log("Express server has started on port 3000")
1.5 测试
(1)用户登录成功
(2)用户登录失败
(3)其他异常
比如将数据表的表名修改为"users2",让TypeORM找不到正确的数据表。
二、前端项目
2.1 技术方案
Vue3.X + Vite + TypeScript
2.2 创建项目
npm init vite@latest
依次输入或者选择:
- Project name:typeorm_client
- Vue
- TypeScript
项目创建好之后,切换到typeorm_client目录,安装依赖包:
cd typeorm_client
cnpm i
安装好之后,继续安装以下依赖包:
cnpm i element-plus --save
cnpm i @element-plus/icons-vue --save
cnpm i axios --save
cnpm i vue-router --save
cnpm i pinia --save
本文暂时没有评论,来添加一个吧(●'◡'●)