Node-web05-01博客系统构建

博客系统构建

CRUD最佳实践

  • CRUD小王子
  • 数据库封装员

增删改次难在哪里?

  • 第一: 开发效率,5年CRUD效率没有任何提升
  • 第二:代码质量
    • 屎山
    • 单元测试技术
    • 公司以结果为导向,敏捷开发
      • 而真正的敏捷开发最重要的就是——单元测试
    • 因为他们只想敏捷,不想开发。
    • 真正的敏捷开发是,用最少的时间作出质量最高的程序
      • 而不是需求想变就变,改了又改。
  • 第三:前后端联调
    • 沟通成本
  • 第四:伸缩性Scale
    • 增删改查数据量变大了咋办
    • 100万数据 变 1亿,机器扛得住?
  • 第五:高并发 C10k问题
    • 10000个连接同时进来,怎么保证服务质量
  • 第六:安全和稳定
    • 防拖库、MD5碰撞?
    • XSS、CSRF、Replay?
    • 怎么备份?怎么双活?
    • 经典案例:CSDN被拖库

一些重要的原则

  • 过早优化乃万恶之源
    • 你没办法量化性能就别尝试优化
  • 开发效率>可读性>运行效率
    • 创业公司目标是活下去
    • 纠结 ++i 和 i++的效率提升 有意义吗
  • 可用性 > 易用性 > 美观
    • 不要一开始就在易用性和美观上浪费时间
  • 永远不要删除数据
    • 软删除、删除前确认

博客系统需求分析:做毁它

功能点

  • 可登录、注销,但不可以重置密码(人工解决)
  • 重置密码联系管理员
  • 用户可以对博客CRUD
  • 用户可以对博客进行“评论”,但不能修改评论(从简)
  • 用户不可以编辑用户名、密码、姓名、头像(从简)

可用性需求

  • 手机上也能完成操作

其他要求

  • 对搜索引擎优化

思路

需求

  • 简单CRUD
  • 三个表 users/posts/comments

主要数据

  • users(id/username/password_digest)
  • posts(id/user_id/title/content)
  • comments(id/user_id/post_id/content)

其他

  • 手机适配:一开始就设计两套界面PC+mobile
  • SEO:多用 SSG或者SSR,少用BSR

代码

开始写代码

step01 创建表

代码基于这个commit

我们用的docker创建的 pgsql数据库

  • 自行安装docker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 项目根目录创建持久化数据目录
mkdir blog-data

# 启动pg
docker run -v "$PWD/blog-data":/var/lib/postgresql/data -p 5432:5432 -e POSTGRES_USER=blog -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres:12.2

# 进入 pg容器 内部
docker exec -it 容器id bash

# 登陆数据库
psql -U blog

# 查看数据库
\l

# 删除数据库
drop database 数据库名

# 创建数据库
CREATE DATABASE blog_development ENCODING 'UTF8' LC_COLLATE 'en_US.utf8' LC_CTYPE 'en_US.utf8';

# 连接数据库
\c 数据库名称
  • migration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# package.json里添加脚本
"m:create": "typeorm migration:create",

# 创建表
yarn m:create -n CreateUsers
这样会生成 src/migration/xxx-CreateUsers.ts
# 修改内容
import {MigrationInterface, QueryRunner, Table} from "typeorm";

export class CreateUsers1613578095866 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<void> {
return await queryRunner.createTable(new Table({
name:'users',
columns:[
{name:'id',isGenerated:true,type:'int',generationStrategy:'increment',isPrimary:true},
{name:'username',type:'varchar'},
{name:'password_digest',type:'varchar'},
]
}))
}

public async down(queryRunner: QueryRunner): Promise<void> {
return await queryRunner.dropTable('users');
}
}

# 然后运行 数据库表就创建好了
yarn m:run

# 同步创建 posts\comments

return await queryRunner.createTable(new Table({
name:'posts',
columns:[
{name:'id',isGenerated:true,type:'int',generationStrategy:'increment',isPrimary:true},
{name:'title',type:'varchar'},
{name:'content',type:'varchar'},
{name:'author_id',type:'int'}
]
}))

return await queryRunner.createTable(new Table({
name:'comments',
columns:[
{name:'id',isGenerated:true,type:'int',generationStrategy:'increment',isPrimary:true},
{name:'user_id',type:'int'},
{name:'post_id',type:'int'},
{name:'content',type:'text'}
]
}))

# 我们的表需要有时间列, 那样你就得冲头修改表的结构
# 我们再次创建一个 migration
yarn m:create -n AddCreateAtAndUpdatedAt

注意 await
注意 await
注意 await

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumns('users', [
new TableColumn({name: 'createdAt', type: 'time', isNullable: false, default: 'now()'}),
new TableColumn({name: 'updatedAt', type: 'time', isNullable: false, default: 'now()'})
]);
await queryRunner.addColumns('posts', [
new TableColumn({name: 'createdAt', type: 'time', isNullable: false, default: 'now()'}),
new TableColumn({name: 'updatedAt', type: 'time', isNullable: false, default: 'now()'})
]);
await queryRunner.addColumns('comments', [
new TableColumn({name: 'createdAt', type: 'time', isNullable: false, default: 'now()'}),
new TableColumn({name: 'updatedAt', type: 'time', isNullable: false, default: 'now()'})
]);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn('users', 'createdAt');
await queryRunner.dropColumn('users', 'updatedAt');
await queryRunner.dropColumn('posts', 'createdAt');
await queryRunner.dropColumn('posts', 'updatedAt');
await queryRunner.dropColumn('comments', 'createdAt');
await queryRunner.dropColumn('comments', 'updatedAt');
}

统一命名风格:规范列名

  • 驼峰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
yarn m:create -n RenameColumns

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.renameColumn('users', 'password_digest', 'passwordDigest');
await queryRunner.renameColumn('posts', 'author_id', 'authorId');
await queryRunner.renameColumn('comments', 'user_id', 'userId');
await queryRunner.renameColumn('comments', 'post_id', 'postId');
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.renameColumn('users', 'passwordDigest', 'password_digest');
await queryRunner.renameColumn('posts', 'authorId', 'author_id');
await queryRunner.renameColumn('comments', 'userId', 'user_id');
await queryRunner.renameColumn('comments', 'postId', 'post_id');
}

# migration
yarn m:run

# 查看表结构
\d 表名

step02 创建关联

1
2
3
4
5
6
7
8
9
10
11
users
one user has many posts
one has many comments

posts
a post to one user
a post has many comments

comments
a comments to one user
a comments to one post
  • 修改package.json 脚本 "e:create": "typeorm entity:create"
  • 创建三个实体
    1
    2
    3
    yarn e:create -n User
    yarn e:create -n Post
    yarn e:create -n Comment

step03 填充数据

  • seed尝试录入数据,修改 src/seed.ts
1
2
3
4
5
6
7
8
9
10
11
12
import "reflect-metadata";
import {createConnection} from "typeorm";
import {User} from "./entity/User";

createConnection().then(async connection => {
const {manager} = connection;
const u1 = new User();
u1.username = 'aaa';
u1.passwordDigest = 'xxx';
await manager.save(u1);
console.log(u1.id);
}).catch(error => console.log(error));
  • 运行node dist/seed.js 报错
1
Error: Column createdAt of Entity Comment does not support length property.
  • 点击 src/entity/User 里的 @CreateDateColumn
  • 会跳到对应的ts源码
  • 但是还看不懂 于是 webstorm 项目目录 有个 jump to source 点击 定位到位置
  • 然后 node_modules/typeorm 目录点击 findinpath 搜索 “createdAt” 发现 psql 对应会创建 timestamp

step04 创建页面

step05 创建API

  • /api/v1/sign_up
  • /api/v1/sign_in

step06 约定前后端接口

  • RESTful
  • 约定错误码
  • 约定资源格式

step07 单元测试