wok-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/cache/cache.js +94 -0
- package/dist/cache/config.js +19 -0
- package/dist/cache/index.js +27 -0
- package/dist/cache/purge-task.js +56 -0
- package/dist/cache/stat.js +47 -0
- package/dist/config/convert.js +36 -0
- package/dist/config/exception.js +14 -0
- package/dist/config/index.js +67 -0
- package/dist/http-client/index.js +132 -0
- package/dist/i18n/ar.js +17 -0
- package/dist/i18n/de.js +17 -0
- package/dist/i18n/en-us.js +17 -0
- package/dist/i18n/es.js +17 -0
- package/dist/i18n/fr.js +17 -0
- package/dist/i18n/i18n.js +231 -0
- package/dist/i18n/index.js +52 -0
- package/dist/i18n/ja.js +17 -0
- package/dist/i18n/ko.js +17 -0
- package/dist/i18n/msg.js +2 -0
- package/dist/i18n/pt.js +17 -0
- package/dist/i18n/ru.js +17 -0
- package/dist/i18n/tag.js +18 -0
- package/dist/i18n/zh-HK.js +17 -0
- package/dist/i18n/zh-TW.js +17 -0
- package/dist/i18n/zh-cn.js +17 -0
- package/dist/index.js +13 -0
- package/dist/log/config.js +28 -0
- package/dist/log/date.js +21 -0
- package/dist/log/file.js +79 -0
- package/dist/log/index.js +109 -0
- package/dist/log/level.js +39 -0
- package/dist/log/store.js +16 -0
- package/dist/mongodb/collection.js +2 -0
- package/dist/mongodb/config.js +34 -0
- package/dist/mongodb/doc.js +2 -0
- package/dist/mongodb/exception.js +14 -0
- package/dist/mongodb/index.js +58 -0
- package/dist/mongodb/manager/base.js +563 -0
- package/dist/mongodb/manager/index.js +63 -0
- package/dist/mongodb/manager/tx-strict.js +84 -0
- package/dist/mongodb/manager/tx.js +30 -0
- package/dist/mongodb/migration.js +52 -0
- package/dist/mvc/access-log.js +31 -0
- package/dist/mvc/config.js +20 -0
- package/dist/mvc/exchange.js +113 -0
- package/dist/mvc/handler/index.js +6 -0
- package/dist/mvc/handler/json.js +33 -0
- package/dist/mvc/handler/restful.js +35 -0
- package/dist/mvc/handler/upload.js +33 -0
- package/dist/mvc/index.js +316 -0
- package/dist/mvc/interceptor.js +2 -0
- package/dist/mvc/query.js +43 -0
- package/dist/mvc/render/file.js +177 -0
- package/dist/mvc/render/html/html.js +90 -0
- package/dist/mvc/render/html/index.js +18 -0
- package/dist/mvc/render/html/style.js +2 -0
- package/dist/mvc/render/index.js +7 -0
- package/dist/mvc/render/json.js +26 -0
- package/dist/mvc/render/text.js +16 -0
- package/dist/mvc/router.js +2 -0
- package/dist/mysql/config.js +49 -0
- package/dist/mysql/exception.js +14 -0
- package/dist/mysql/index.js +85 -0
- package/dist/mysql/manager/base.js +233 -0
- package/dist/mysql/manager/index.js +107 -0
- package/dist/mysql/manager/ops/count.js +20 -0
- package/dist/mysql/manager/ops/criteria.js +326 -0
- package/dist/mysql/manager/ops/delete.js +65 -0
- package/dist/mysql/manager/ops/exist.js +26 -0
- package/dist/mysql/manager/ops/find.js +111 -0
- package/dist/mysql/manager/ops/index.js +14 -0
- package/dist/mysql/manager/ops/insert.js +101 -0
- package/dist/mysql/manager/ops/modify.js +10 -0
- package/dist/mysql/manager/ops/paginate.js +23 -0
- package/dist/mysql/manager/ops/query.js +9 -0
- package/dist/mysql/manager/ops/update.js +201 -0
- package/dist/mysql/manager/tx-strict.js +98 -0
- package/dist/mysql/manager/tx.js +30 -0
- package/dist/mysql/manager/utils.js +56 -0
- package/dist/mysql/migration.js +136 -0
- package/dist/mysql/table-info.js +8 -0
- package/dist/task/daily.js +58 -0
- package/dist/task/fixed-delay.js +33 -0
- package/dist/task/fixed-rate.js +37 -0
- package/dist/task/index.js +9 -0
- package/dist/task/task.js +39 -0
- package/dist/validation/exception.js +44 -0
- package/dist/validation/index.js +29 -0
- package/dist/validation/validator/array.js +38 -0
- package/dist/validation/validator/enum.js +28 -0
- package/dist/validation/validator/index.js +14 -0
- package/dist/validation/validator/length.js +40 -0
- package/dist/validation/validator/max-length.js +35 -0
- package/dist/validation/validator/max.js +29 -0
- package/dist/validation/validator/min-length.js +33 -0
- package/dist/validation/validator/min.js +29 -0
- package/dist/validation/validator/not-blank.js +33 -0
- package/dist/validation/validator/not-null.js +21 -0
- package/dist/validation/validator/plain-obj.js +32 -0
- package/dist/validation/validator/regexp.js +30 -0
- package/documentation/en/index.md +1 -0
- package/documentation/zh-cn/cache.md +59 -0
- package/documentation/zh-cn/config.md +68 -0
- package/documentation/zh-cn/http-client.md +33 -0
- package/documentation/zh-cn/i18n.md +154 -0
- package/documentation/zh-cn/index.md +25 -0
- package/documentation/zh-cn/log.md +40 -0
- package/documentation/zh-cn/mongodb.md +262 -0
- package/documentation/zh-cn/mvc.md +430 -0
- package/documentation/zh-cn/mysql.md +389 -0
- package/documentation/zh-cn/task.md +50 -0
- package/documentation/zh-cn/test.md +57 -0
- package/documentation/zh-cn/validate.md +125 -0
- package/package.json +46 -0
- package/types/cache/cache.d.ts +52 -0
- package/types/cache/config.d.ts +32 -0
- package/types/cache/index.d.ts +2 -0
- package/types/cache/purge-task.d.ts +11 -0
- package/types/cache/stat.d.ts +26 -0
- package/types/config/convert.d.ts +6 -0
- package/types/config/exception.d.ts +7 -0
- package/types/config/index.d.ts +15 -0
- package/types/http-client/index.d.ts +71 -0
- package/types/i18n/ar.d.ts +2 -0
- package/types/i18n/de.d.ts +2 -0
- package/types/i18n/en-us.d.ts +2 -0
- package/types/i18n/es.d.ts +2 -0
- package/types/i18n/fr.d.ts +2 -0
- package/types/i18n/i18n.d.ts +102 -0
- package/types/i18n/index.d.ts +9 -0
- package/types/i18n/ja.d.ts +2 -0
- package/types/i18n/ko.d.ts +2 -0
- package/types/i18n/msg.d.ts +50 -0
- package/types/i18n/pt.d.ts +2 -0
- package/types/i18n/ru.d.ts +2 -0
- package/types/i18n/tag.d.ts +11 -0
- package/types/i18n/zh-HK.d.ts +2 -0
- package/types/i18n/zh-TW.d.ts +2 -0
- package/types/i18n/zh-cn.d.ts +2 -0
- package/types/index.d.ts +10 -0
- package/types/log/config.d.ts +27 -0
- package/types/log/date.d.ts +2 -0
- package/types/log/file.d.ts +5 -0
- package/types/log/index.d.ts +34 -0
- package/types/log/level.d.ts +15 -0
- package/types/log/store.d.ts +12 -0
- package/types/mongodb/collection.d.ts +25 -0
- package/types/mongodb/config.d.ts +45 -0
- package/types/mongodb/doc.d.ts +11 -0
- package/types/mongodb/exception.d.ts +7 -0
- package/types/mongodb/index.d.ts +29 -0
- package/types/mongodb/manager/base.d.ts +188 -0
- package/types/mongodb/manager/index.d.ts +38 -0
- package/types/mongodb/manager/tx-strict.d.ts +41 -0
- package/types/mongodb/manager/tx.d.ts +21 -0
- package/types/mongodb/migration.d.ts +12 -0
- package/types/mvc/access-log.d.ts +7 -0
- package/types/mvc/config.d.ts +30 -0
- package/types/mvc/exchange.d.ts +72 -0
- package/types/mvc/handler/index.d.ts +3 -0
- package/types/mvc/handler/json.d.ts +23 -0
- package/types/mvc/handler/restful.d.ts +11 -0
- package/types/mvc/handler/upload.d.ts +40 -0
- package/types/mvc/index.d.ts +49 -0
- package/types/mvc/interceptor.d.ts +11 -0
- package/types/mvc/query.d.ts +13 -0
- package/types/mvc/render/file.d.ts +10 -0
- package/types/mvc/render/html/html.d.ts +98 -0
- package/types/mvc/render/html/index.d.ts +11 -0
- package/types/mvc/render/html/style.d.ts +1201 -0
- package/types/mvc/render/index.d.ts +4 -0
- package/types/mvc/render/json.d.ts +17 -0
- package/types/mvc/render/text.d.ts +10 -0
- package/types/mvc/router.d.ts +11 -0
- package/types/mysql/config.d.ts +86 -0
- package/types/mysql/exception.d.ts +7 -0
- package/types/mysql/index.d.ts +16 -0
- package/types/mysql/manager/base.d.ts +158 -0
- package/types/mysql/manager/index.d.ts +36 -0
- package/types/mysql/manager/ops/count.d.ts +13 -0
- package/types/mysql/manager/ops/criteria.d.ts +120 -0
- package/types/mysql/manager/ops/delete.d.ts +46 -0
- package/types/mysql/manager/ops/exist.d.ts +6 -0
- package/types/mysql/manager/ops/find.d.ts +66 -0
- package/types/mysql/manager/ops/index.d.ts +10 -0
- package/types/mysql/manager/ops/insert.d.ts +18 -0
- package/types/mysql/manager/ops/modify.d.ts +3 -0
- package/types/mysql/manager/ops/paginate.d.ts +36 -0
- package/types/mysql/manager/ops/query.d.ts +3 -0
- package/types/mysql/manager/ops/update.d.ts +70 -0
- package/types/mysql/manager/tx-strict.d.ts +34 -0
- package/types/mysql/manager/tx.d.ts +15 -0
- package/types/mysql/manager/utils.d.ts +17 -0
- package/types/mysql/migration.d.ts +8 -0
- package/types/mysql/table-info.d.ts +36 -0
- package/types/task/daily.d.ts +15 -0
- package/types/task/fixed-delay.d.ts +8 -0
- package/types/task/fixed-rate.d.ts +8 -0
- package/types/task/index.d.ts +4 -0
- package/types/task/task.d.ts +33 -0
- package/types/validation/exception.d.ts +43 -0
- package/types/validation/index.d.ts +32 -0
- package/types/validation/validator/array.d.ts +5 -0
- package/types/validation/validator/enum.d.ts +8 -0
- package/types/validation/validator/index.d.ts +11 -0
- package/types/validation/validator/length.d.ts +10 -0
- package/types/validation/validator/max-length.d.ts +8 -0
- package/types/validation/validator/max.d.ts +7 -0
- package/types/validation/validator/min-length.d.ts +6 -0
- package/types/validation/validator/min.d.ts +7 -0
- package/types/validation/validator/not-blank.d.ts +7 -0
- package/types/validation/validator/not-null.d.ts +6 -0
- package/types/validation/validator/plain-obj.d.ts +7 -0
- package/types/validation/validator/regexp.d.ts +8 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# mongodb
|
|
2
|
+
|
|
3
|
+
mongodb 组件基于 [mongodb 官方的驱动](https://www.npmjs.com/package/mongodb)进行封装,提供了简单的实体映射和增删改查功能,以方便操作。
|
|
4
|
+
|
|
5
|
+
## 环境变量
|
|
6
|
+
|
|
7
|
+
mongodb 组件支持多实例,默认情况下以 MONGO 为前缀。
|
|
8
|
+
|
|
9
|
+
| 变量名称 | 说明 |
|
|
10
|
+
| :-------------------------- | :-------------------------------------------------------------- |
|
|
11
|
+
| MONGO_URI | mongo 链接,示例:mongodb+srv://<user>:<password>@<cluster-url> |
|
|
12
|
+
| MONGO_MAX_POOL_SIZE | 连接小事情最大连接数,默认 10 |
|
|
13
|
+
| MONGO_MIN_POOL_SIZE | 连接小事情最小连接数,默认 1 |
|
|
14
|
+
| MONGO_MAX_CONNECTING | 连接池最大并发连接数 ,默认 10 |
|
|
15
|
+
| MONGO_MAX_IDLE_TIME_MS | 连接的最大闲置时间,单位毫秒,默认 60000 |
|
|
16
|
+
| MONGO_WAIT_QUEUE_TIMEOUT_MS | 连接的最大等待时间,单位毫秒,默认 60000 |
|
|
17
|
+
| MONGO_SLOW_QUERY_WARN | 慢查询警告,开启后会对慢查询输出警告日志,默认开启 |
|
|
18
|
+
| MONGO_SLOW_QUERY_MS | 慢查询毫秒数,默认 200 |
|
|
19
|
+
| MONGO_TRANSACTION_TIMEOUT | 事务超时时间,单位毫秒,默认 5000 |
|
|
20
|
+
| MONGO_TRANSACTION_STRICT | 事务严格模式,默认为 true ,设置为 false 关闭 |
|
|
21
|
+
|
|
22
|
+
## 使用
|
|
23
|
+
|
|
24
|
+
### 初始化
|
|
25
|
+
|
|
26
|
+
先使用函数 enableMongoDB 来启用 mongodb ,完成后才使用相关的功能。
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
await enableMongoDB()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
组件支持多实例,如果有多个库需要连接,可以指定一个新名称。
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// 启用指定名称为 md2 的实现
|
|
36
|
+
await enableMongoDB('md2')
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
默认情况下环境变量的前缀是 MONGO,指定名称的实例,将使用名称大写后的前缀,上例中指定的名称为 md2 的实例,将使用前缀 MD2。
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
# 配置默认的连接
|
|
43
|
+
MONGO_URI=mongodb://test1:t1abcd@localhost/t1
|
|
44
|
+
MONGO_MAXPOOLSIZE=10
|
|
45
|
+
# 配置 md2 名称对应的链接
|
|
46
|
+
MD2_URI=mongodb://test2:t2abcd@localhost/t2
|
|
47
|
+
MD2_MAXPOOLSIZE=10
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
使用 getMongoDBManager 函数可以获取一个管理器实例用于操作 mongodb 实例,
|
|
51
|
+
支持可选的名称来获取对应的实例。
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// 默认实例
|
|
55
|
+
const mananger = getMongoDBManager()
|
|
56
|
+
// md2 实例
|
|
57
|
+
const md2 = getMongoDBManager('md2')
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 实体映射
|
|
61
|
+
|
|
62
|
+
通过 manager 对象提供的方法可以完成基础的增删改查,但是在操作之前必须要进行配置。
|
|
63
|
+
|
|
64
|
+
配置分为两个部分,一个是集合的数据格式定义,一个是集合信息的设定,下面是一个示例。
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
/**
|
|
68
|
+
* 用户集合数据定义。
|
|
69
|
+
* 主键名称固定为 _id,不支持映射配置,实体类不要有 _id 字段。
|
|
70
|
+
*/
|
|
71
|
+
export interface User {
|
|
72
|
+
nickname: string
|
|
73
|
+
skills: string[]
|
|
74
|
+
// 创建和更新字段都可以设置让组件自动维护
|
|
75
|
+
// 因为类型约束的缘故,为了插入和更新时不填,这里设为可选
|
|
76
|
+
createAt?: Date
|
|
77
|
+
updateAt?: Date
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 用户集合信息,类型是 MongoCollection,泛型就是实体类型。
|
|
81
|
+
* 主键名称固定为 _id,不支持映射配置。
|
|
82
|
+
*/
|
|
83
|
+
export const collUser: MongoCollection<User> = {
|
|
84
|
+
/**
|
|
85
|
+
* 集合名称
|
|
86
|
+
*/
|
|
87
|
+
collectionName: 'user',
|
|
88
|
+
/**
|
|
89
|
+
* 配置创建时间字段,组件会自动管理
|
|
90
|
+
*/
|
|
91
|
+
createdDate: {
|
|
92
|
+
type: 'date',
|
|
93
|
+
field: 'createAt'
|
|
94
|
+
},
|
|
95
|
+
/**
|
|
96
|
+
* 配置更新时间字段,组件会自动管理
|
|
97
|
+
*/
|
|
98
|
+
updatedDate: {
|
|
99
|
+
type: 'date',
|
|
100
|
+
field: 'updateAt'
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 增删改查
|
|
106
|
+
|
|
107
|
+
现在就可以做增删改查了,manager 的所有操作第一个参数都是集合信息,就是前面配置的 collUser。
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const mananger = getMongoDBManager()
|
|
111
|
+
// 插入记录,在插入记录时,如果 _id 无值,则会由数据库生成 ObjectId 的主键值
|
|
112
|
+
await mananger.insert(collUser, { _id: '007', nickname: 'Spark', skills: [] })
|
|
113
|
+
// 按id 查找
|
|
114
|
+
const uer1 = await mananger.findById(collUser, '007')
|
|
115
|
+
// 更新昵称
|
|
116
|
+
user1.nickname = 'ryan'
|
|
117
|
+
await mananger.update(collUser, user1)
|
|
118
|
+
// 判定某个 id 是否存在
|
|
119
|
+
const exist = await mananger.existsById(collUser, 'xyz')
|
|
120
|
+
// 判定指定条件是否有记录存在
|
|
121
|
+
const exist2 = await mananger.existsBy(collUser, { nickname: 'acute' })
|
|
122
|
+
// 按 id 删除
|
|
123
|
+
await mananger.deleteById(collUser, '007')
|
|
124
|
+
// 按条件删除,慎用,一次性删除大量数据很可能会导致数据库高负载,引发线上事故
|
|
125
|
+
await mananger.deleteMany(collUser, { nickname: 'smith' })
|
|
126
|
+
// 查找第一条符合条件的记录
|
|
127
|
+
const jack = await mananger.findFirst(collUser, { nickname: 'jack' })
|
|
128
|
+
// 查询集合中的所有记录,谨慎使用,一次性查询大量数据可能会爆内存,需要很长时间来传输
|
|
129
|
+
await mananger.findAll(collUser)
|
|
130
|
+
// 统计数量,谨慎使用,count 操作即便能走索引,数据量大也会有性能问题
|
|
131
|
+
const count = await mananger.count(collUser, { nickname: 'Steve' })
|
|
132
|
+
// 查找有技能的用户,最多返回2条结果
|
|
133
|
+
const list = await mananger.find(
|
|
134
|
+
collUser,
|
|
135
|
+
{
|
|
136
|
+
skills: { $exists: true }
|
|
137
|
+
},
|
|
138
|
+
{ offset: 0, limit: 2 }
|
|
139
|
+
)
|
|
140
|
+
// 分页,按id排序,每页20条,查询第2页的记录
|
|
141
|
+
const page = await mananger.paginate(
|
|
142
|
+
collUser,
|
|
143
|
+
{
|
|
144
|
+
skills: { $exists: true }
|
|
145
|
+
},
|
|
146
|
+
{ pn: 2, pz: 20, orderBy: ['_id', 'asc'] }
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 几种更新方法说明
|
|
151
|
+
|
|
152
|
+
manager 提供了四种更新方法:update、partialUpdate、updateMany、updateOne。
|
|
153
|
+
|
|
154
|
+
| 方法名称 | 说明 |
|
|
155
|
+
| :------------ | :------------------------------------------------------------------------- |
|
|
156
|
+
| update | 整个文档更新,需要以传递完整的文档信息,返回更新后的文档,更新失败抛出异常 |
|
|
157
|
+
| partialUpdate | 局部更新,只需要传递 id 和要更新的字段信息,返回更新是否成功 |
|
|
158
|
+
| partialUpdate | 局部更新,只需要传递 id 和要更新的字段信息,返回更新是否成功 |
|
|
159
|
+
| updateMany | 更新所有符合条件的记录,返回被更新文档数量 |
|
|
160
|
+
| updateOne | 只更新一条符合条件的记录,只能是相等条件,不支持范围条件 |
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// update 需要有完整的文档,一般都要先查询获取文档
|
|
164
|
+
const user = await mananger.findById(collUser, '007')
|
|
165
|
+
user.nickname = 'ryan'
|
|
166
|
+
await mananger.update(collUser, user)
|
|
167
|
+
// partialUpdate 则不需要
|
|
168
|
+
// 将 id 为 001 的文档昵称更新为 lily
|
|
169
|
+
await manager.partialUpdate(collUser, '001', { $set: { nickname: 'lily' } })
|
|
170
|
+
// updateMany 和 partialUpdate 唯一的区别就是 id 参数变为条件
|
|
171
|
+
// 将所有学分小于等于10的用户学分加一
|
|
172
|
+
await manager.updateMany(collUser, { credit: { $lte: 10 } }, { $inc: { credit: 1 } })
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## 版本管理
|
|
176
|
+
|
|
177
|
+
enableMongoDB 函数支持 migration 参数,用于版本管理,完成自动迁移。
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
await enableMongoDB({
|
|
181
|
+
migration: {
|
|
182
|
+
versionList: versionList
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
其中的 versionList 是版本列表,格式是元素为 MongoMigrationVersion(`(db: Db) => Promise<void>`) 类型的数组,
|
|
188
|
+
Db 则是 mongodb 驱动提供的库,可以完成各种操作,比如创建集合和索引等。
|
|
189
|
+
版本号就是元素的下标,就是说每次程序有更新都应该增加元素,已经的元素不能做任何修改,程序不会做任何校验。
|
|
190
|
+
在启动阶段,程序会检查已经更新的版本号(版本列表的下标),然后从版本列表中寻找之后的版本,逐个更新并标记已经完成的版本号。
|
|
191
|
+
|
|
192
|
+
下面是 versionList 参数的示例:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const versionList: MongoMigrationVersion[] = [
|
|
196
|
+
// 版本一
|
|
197
|
+
async db => {
|
|
198
|
+
// 创建一个集合,插入点数据 再创建索引
|
|
199
|
+
await db.createCollection('user')
|
|
200
|
+
await db
|
|
201
|
+
.collection<User>('user')
|
|
202
|
+
.createIndex({ nickname: 1 }, { unique: true, name: 'uk_nickname' })
|
|
203
|
+
await db
|
|
204
|
+
.collection<User>('user')
|
|
205
|
+
.createIndex({ skills: 1 }, { unique: false, name: 'idx_skills' })
|
|
206
|
+
// 预置数据
|
|
207
|
+
await db.collection<User>('user').insertOne({
|
|
208
|
+
nickname: 'jack',
|
|
209
|
+
skills: ['java'],
|
|
210
|
+
createAt: new Date(),
|
|
211
|
+
updateAt: new Date()
|
|
212
|
+
})
|
|
213
|
+
},
|
|
214
|
+
// 版本二
|
|
215
|
+
async db => {
|
|
216
|
+
// 删除一个索引
|
|
217
|
+
await db.collection<User>('user').dropIndex('idx_skills')
|
|
218
|
+
// 预置数据
|
|
219
|
+
await db.collection<User>('user').insertOne({
|
|
220
|
+
nickname: 'tom',
|
|
221
|
+
skills: ['golang', 'rust'],
|
|
222
|
+
createAt: new Date(),
|
|
223
|
+
updateAt: new Date()
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## 事务
|
|
230
|
+
|
|
231
|
+
使用 mananger 对象的 tx 方法可以执行事务操作,方法接受一个函数参数,函数的参数是 session 对象,
|
|
232
|
+
**所在事务中的操作,都必须调用 session 的方法,session 的操作方法与 mananger 是一样的**。
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
await manager.tx(
|
|
236
|
+
async session => {
|
|
237
|
+
// 在事务中更新订单和帐号余额
|
|
238
|
+
// orderId 订单ID
|
|
239
|
+
// accountId 帐号ID
|
|
240
|
+
// amount 订单金额
|
|
241
|
+
await session.partialUpdate(collOrder, orderId, { $set: { status: 'finished' } })
|
|
242
|
+
await session.partialUpdate(collAccount, accountId, { $inc: { balance: -amount } })
|
|
243
|
+
},
|
|
244
|
+
// 额外的选项,可针对单个事务设置超时时间等
|
|
245
|
+
{ timeout: 1000 }
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 严格模式
|
|
250
|
+
|
|
251
|
+
事务默认是打开严格模式的,在严格模式下,事务中的很多操作都被禁止,
|
|
252
|
+
通过将环境变量 MONGO_TRANSACTION_STRICT (默认变量名称,多实例的情况下使用对应名称)
|
|
253
|
+
设置为 false 可以关闭。
|
|
254
|
+
|
|
255
|
+
严格模式下,禁止在事务中进行以下的操作:
|
|
256
|
+
|
|
257
|
+
1. 批量插入 insertMany
|
|
258
|
+
2. 批量更新 updateMany
|
|
259
|
+
3. 批量删除 deleteMany
|
|
260
|
+
4. 批量查询和计数 find、count、paginate
|
|
261
|
+
5. findByIdIn 参数超过 100 个
|
|
262
|
+
6. 调用 session 进行的任何操作累计超过 10 次
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# MVC
|
|
2
|
+
|
|
3
|
+
mvc 组件基于 Nodejs 自带的 http 模块,让搭建 http 服务,处理请求更方便。
|
|
4
|
+
|
|
5
|
+
### 环境变量
|
|
6
|
+
|
|
7
|
+
| 环境变量 | 说明 |
|
|
8
|
+
| :------------------------ | :----------------------------------------------- |
|
|
9
|
+
| SERVER_PORT | 端口号 |
|
|
10
|
+
| SERVER_TIMEOUT | 超时时间,单位毫秒,默认 30000 |
|
|
11
|
+
| SERVER_ACCESS_LOG | 是否启用访问日志,默认不启用,值为 true 表示启用 |
|
|
12
|
+
| SERVER_CORS_ALLOW_ORIGIN | 跨域允许的源域名,默认 \* |
|
|
13
|
+
| SERVER_CORS_ALLOW_HEADERS | 跨域允许的消息头,默认 \* |
|
|
14
|
+
| SERVER_CORS_ALLOW_METHODS | 跨域允许的请求方法,默认 \* |
|
|
15
|
+
|
|
16
|
+
### 开始使用
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
// 启动服务
|
|
20
|
+
await startWebServer({
|
|
21
|
+
// 路由,实际开发中页面很多的情况下,可以将每个路由处理函数单独写在一个文件中
|
|
22
|
+
routers: {
|
|
23
|
+
'/': async exchange => exchange.respondText('Hello world !')
|
|
24
|
+
},
|
|
25
|
+
// 拦截器,可以做授权校验、异常处理等
|
|
26
|
+
interceptors: [
|
|
27
|
+
async (exchange, next) => {
|
|
28
|
+
// 示例场景: http 基本认证
|
|
29
|
+
if (!validateAuth(exchange.request.headers.authorization)) {
|
|
30
|
+
// 校验失败响应信息提示浏览器用户需要登录
|
|
31
|
+
exchange.respond({
|
|
32
|
+
statusCode: 401,
|
|
33
|
+
headers: { 'www-Authenticate': 'Basic realm= "family"' }
|
|
34
|
+
})
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
// 调用 next 函数继续后面的流程
|
|
38
|
+
await next()
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
启动服务主要需要设置两个参数,一个是路由,一个是拦截器。路由用于处理具体的请求,拦截器可以在路由执行前执行,
|
|
45
|
+
对请求进行拦截,下面会细说。
|
|
46
|
+
|
|
47
|
+
在有需要的时候,也可以停止服务。
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// 停止服务
|
|
51
|
+
await stopWebServer()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 路由
|
|
55
|
+
|
|
56
|
+
路由的配置是键值对,键是路径,值是异步处理函数,函数接收一个一个参数 exchange ,类型是 ServerExchange,
|
|
57
|
+
提供了读取请求信息和一些响应常见格式的方法。
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
await startWebServer({
|
|
61
|
+
// 路由
|
|
62
|
+
routers: {
|
|
63
|
+
'/': async exchange => {
|
|
64
|
+
// 通过 exchage 获取请求信息
|
|
65
|
+
const url = exchange.request.url
|
|
66
|
+
const method = exchange.request.method
|
|
67
|
+
const referer = exchange.request.headers.referer
|
|
68
|
+
// 读取正文
|
|
69
|
+
const body = await exchange.bodyJson()
|
|
70
|
+
// 响应信息
|
|
71
|
+
exchange.respondJson({ ok: true })
|
|
72
|
+
},
|
|
73
|
+
'/users': async exchange => {
|
|
74
|
+
const list = await listUser()
|
|
75
|
+
exchange.respondJson(list)
|
|
76
|
+
},
|
|
77
|
+
'*': async exchange => {
|
|
78
|
+
exchage.respondText(`404 Not Found`, 404)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`*` 是一个特殊的路径,用于自定义 404 页面,可以处理所有匹配路径失败的请求。
|
|
85
|
+
在未设置 404 页面的情况下,默认返回一个 json 格式的错误提示。
|
|
86
|
+
|
|
87
|
+
**路由的路径目前仅支持明确地址,不支持模糊路径,不能在路径上绑定变量**,比如 `/users/:id` 这种将用户 id
|
|
88
|
+
作为路径的一部分,是无法处理的。出于程序的开销方面的考虑,没有支持,后续的版本有可能会考虑。
|
|
89
|
+
|
|
90
|
+
### 拦截器
|
|
91
|
+
|
|
92
|
+
拦截器的配置是一个列表,每个拦截器都是一个异步函数,函数接收两个参数:exchange 和 next 。
|
|
93
|
+
exchange 是 ServerExchange 类型,与上面路由中的 exchage 是一样的。
|
|
94
|
+
next 是一个无参异步函数,调用 next 执行后面的流程(下一个拦截器或路由)。
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// 启动服务
|
|
98
|
+
await startWebServer({
|
|
99
|
+
// 路由
|
|
100
|
+
routers: {
|
|
101
|
+
// 省略路由配置代码
|
|
102
|
+
},
|
|
103
|
+
// 拦截器,可以做授权校验、异常处理等
|
|
104
|
+
interceptors: [
|
|
105
|
+
async (exchange, next) => {
|
|
106
|
+
// 获取原生的请求和响应对象
|
|
107
|
+
const { request, response } = exchange
|
|
108
|
+
const url = request.url
|
|
109
|
+
const userAgent = request.headers['user-agent']
|
|
110
|
+
const ip = request.socket.remoteAddr
|
|
111
|
+
try {
|
|
112
|
+
// 调用 next 函数继续后面的流程
|
|
113
|
+
await next()
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// 演示场景:在拦截器里统一处理校验失败发生的异常
|
|
116
|
+
if (e instanceof ValidationException) {
|
|
117
|
+
// 调用 exchange 上的工具方法,快速完成一个响应
|
|
118
|
+
exchange.respondErrMsg(e.errMsg, 400)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
getLogger().error(`接口异常 ${url}`, e)
|
|
122
|
+
// 调用 exchange 上的工具方法,快速完成一个响应
|
|
123
|
+
exchange.respondErrMsg('服务器内部错误', 500)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
})
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 针对不同的方法进行处理
|
|
131
|
+
|
|
132
|
+
组件提供了 restful 函数,可以在配置路由时按请求方法进行分发。
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// 启动服务
|
|
136
|
+
await startWebServer({
|
|
137
|
+
// 路由
|
|
138
|
+
routers: {
|
|
139
|
+
'/users': restful({
|
|
140
|
+
get: async exchange => {
|
|
141
|
+
// 处理 get 请求
|
|
142
|
+
},
|
|
143
|
+
post: async exchange => {
|
|
144
|
+
// 处理 post 请求
|
|
145
|
+
},
|
|
146
|
+
delete: async exchange => {
|
|
147
|
+
// 处理 delete 请求
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
注意:路由目前并不支持 restful 风格的动态路径,前面已经有说明。
|
|
155
|
+
|
|
156
|
+
### json 请求处理
|
|
157
|
+
|
|
158
|
+
框架自带了函数 createJsonHandler 可以方便的创建一个处理 json 请求的的 RouterHandler,
|
|
159
|
+
请求和响应都是 json 格式,响应和请求都可以为空。请求为空可以填入类型 {} 来表示空对象,响应为空可以填入类型 void 。
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// 申请请求体 json 格式和响应的格式
|
|
163
|
+
interface Form {
|
|
164
|
+
name: string
|
|
165
|
+
age: number
|
|
166
|
+
}
|
|
167
|
+
interface Resp {
|
|
168
|
+
id: string
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const userCreateHandler = createJsonHandler<Form, Resp>({
|
|
172
|
+
/**
|
|
173
|
+
* 请求正文映射对象校验规则,可选.
|
|
174
|
+
*/
|
|
175
|
+
validation: {
|
|
176
|
+
name: [notBlank('名称不能为空'), length({ min: 2, max: 16, message: '名称必须是2-16个字' })],
|
|
177
|
+
age: [notNull('年龄必填'), min(2), max(16)]
|
|
178
|
+
},
|
|
179
|
+
async handle(body, exchange) {
|
|
180
|
+
// 获取授权信息
|
|
181
|
+
const { authorization } = exchange.headers
|
|
182
|
+
const currentUser = await findUserByAuth(authorization)
|
|
183
|
+
console.log(`姓名是:${body.name}`)
|
|
184
|
+
console.log(`年龄是:${body.age}`)
|
|
185
|
+
const newUser = await createUser(body)
|
|
186
|
+
return { id: newUser.id }
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
校验时会自动根据消息头 `accept-language` 切换校验器的语言,对于没有自定义错误信息的校验使用切换后的语言给予默认的提示。
|
|
192
|
+
|
|
193
|
+
### 上传文件处理
|
|
194
|
+
|
|
195
|
+
组件目前尚未实现对 multipart/form-data 类型请求的处理,如有需要可通一些第三方库来解析文件上传的请求内容。
|
|
196
|
+
|
|
197
|
+
下面是通过 formidable 这个库来解析请求的示例:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
// 启动服务
|
|
201
|
+
import formidable from 'formidable'
|
|
202
|
+
|
|
203
|
+
await startWebServer({
|
|
204
|
+
// 路由
|
|
205
|
+
routers: {
|
|
206
|
+
'/cover': async exchange => {
|
|
207
|
+
const form = formidable({})
|
|
208
|
+
// 解析请求内容
|
|
209
|
+
const [fields, files] = await form.parse(exchage.request)
|
|
210
|
+
// todo 继续业务处理
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
如果使用了其它的库来读取 request 内容,则不能再使用 exchange 中的 bodyXxx 系列方法,调用时会抛出异常。
|
|
217
|
+
同时在调用了 bodyXxx 系列方法后也不能再调用其它的库来读取内容,否则将读取不到完整内容或引发异常。
|
|
218
|
+
但是 bodyXxx 系列方法是可以重复调用的,不会产生错误,调用过 bodyText 方法后再调用 bodyJson 是可以的。
|
|
219
|
+
|
|
220
|
+
### 响应 html
|
|
221
|
+
|
|
222
|
+
ServerExchange 类型提供了 respondHtml 方法,用于渲染 html,简单的组织标签层级和进行动态的结构构建。
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// 启动服务
|
|
226
|
+
await startWebServer({
|
|
227
|
+
// 路由
|
|
228
|
+
routers: {
|
|
229
|
+
'/profile': async exchange => {
|
|
230
|
+
// 获取用户信息
|
|
231
|
+
const user = await getUser(exchange.headers.authorization)
|
|
232
|
+
// html 结构组织的逻辑可能会很长,复杂业务可以提取成为单独的函数
|
|
233
|
+
exchange.respondHtml({
|
|
234
|
+
lang: 'zh',
|
|
235
|
+
head: [
|
|
236
|
+
// 为 head 添加标签,这里添加一个 title
|
|
237
|
+
// children 中直接写字符串代表 TextNode
|
|
238
|
+
{ tag: 'title', children: ['个人中心'] },
|
|
239
|
+
{ tag: 'script', attrs: { type: 'module', src: 'main.js' } }
|
|
240
|
+
],
|
|
241
|
+
body: {
|
|
242
|
+
// 属性,属性是有类型推断的,可以对 html 的全局属性进行提示, 如 style、id、class 等
|
|
243
|
+
attrs: {
|
|
244
|
+
// style 是有类型推断的
|
|
245
|
+
style: { 'background-color': 'white' }
|
|
246
|
+
},
|
|
247
|
+
// children 也可以接收一个函数,函数的参数是一个添加子元素的函数
|
|
248
|
+
// 这样可以结合循环和分支控制来实现动态渲染效果
|
|
249
|
+
children: add => {
|
|
250
|
+
add({ tag: 'h1', children: ['个人中心'] })
|
|
251
|
+
// 在 user 有值和无值的情况下分别渲染不同的元素
|
|
252
|
+
if (user) {
|
|
253
|
+
1
|
|
254
|
+
add({ tag: 'p', children: [`用户名:${user.account}`] })
|
|
255
|
+
} else {
|
|
256
|
+
add({
|
|
257
|
+
tag: 'p',
|
|
258
|
+
children: [
|
|
259
|
+
'请登录后查看',
|
|
260
|
+
{ tag: 'a', attrs: { href: '/login' }, children: ['点击进行登录'] }
|
|
261
|
+
]
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
// 对于一些常用的组合,可以封装成一个函数,返回 HtmlTag 类型
|
|
265
|
+
// 这里的 footer 就是这样一个例子
|
|
266
|
+
add(footer())
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
footer 函数示例:
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
function footer(): HtmlTag {
|
|
279
|
+
return {
|
|
280
|
+
tag: 'div',
|
|
281
|
+
attrs: { class: 'footer' },
|
|
282
|
+
children: [
|
|
283
|
+
{ tag: 'a', attrs: { href: '/about' }, children: ['关于我们'] },
|
|
284
|
+
{ tag: 'a', attrs: { href: '/call' }, children: ['联系我们'] },
|
|
285
|
+
{ tag: 'a', attrs: { href: '/privacy' }, children: ['隐私协议'] }
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
如果不喜欢框架自带的 html 渲染模式,也可以使用一些第三方的模板渲染组件。
|
|
292
|
+
|
|
293
|
+
下面是使用库 handlebars 来渲染的的示例:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { compile } from 'handlebars'
|
|
297
|
+
|
|
298
|
+
await startWebServer({
|
|
299
|
+
// 路由
|
|
300
|
+
routers: {
|
|
301
|
+
'/html': async exchange => {
|
|
302
|
+
const source =
|
|
303
|
+
'<!DOCTYPE html>' +
|
|
304
|
+
'<html>' +
|
|
305
|
+
'<head>' +
|
|
306
|
+
'<title>Handlebars Example</title>' +
|
|
307
|
+
'</head>' +
|
|
308
|
+
'<body>' +
|
|
309
|
+
'<p>Hello, my name is {{name}}. I am from {{hometown}}. I have ' +
|
|
310
|
+
'{{kids.length}} kids:</p>' +
|
|
311
|
+
'<ul>{{#kids}}<li>{{name}} is {{age}}</li>{{/kids}}</ul>' +
|
|
312
|
+
'</body>' +
|
|
313
|
+
'</html>'
|
|
314
|
+
// 编译模板
|
|
315
|
+
const template = compile(source)
|
|
316
|
+
// 数据
|
|
317
|
+
const data = {
|
|
318
|
+
name: 'Alan',
|
|
319
|
+
hometown: 'Somewhere, TX',
|
|
320
|
+
kids: [
|
|
321
|
+
{ name: 'Jimmy', age: '12' },
|
|
322
|
+
{ name: 'Sally', age: '4' }
|
|
323
|
+
]
|
|
324
|
+
}
|
|
325
|
+
// 构建内容
|
|
326
|
+
const result = template(data)
|
|
327
|
+
// 渲染构建好的 html 字符串
|
|
328
|
+
exchange.respondHtml(result)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
下面是使用库 vue 3.x 的 ssr 来渲染 html 的示例,
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
import { createSSRApp } from 'vue'
|
|
338
|
+
import { renderToString } from 'vue/server-renderer'
|
|
339
|
+
|
|
340
|
+
await startWebServer({
|
|
341
|
+
// 路由
|
|
342
|
+
routers: {
|
|
343
|
+
'/html': async exchange => {
|
|
344
|
+
const app = createSSRApp({
|
|
345
|
+
data: () => ({ count: 1 }),
|
|
346
|
+
template: `<button>{{ count }}</button>`
|
|
347
|
+
})
|
|
348
|
+
const html = await renderToString(app)
|
|
349
|
+
// 渲染页面,将生成的 html 嵌入页面
|
|
350
|
+
exchange.respondHtml(`<!DOCTYPE html>
|
|
351
|
+
<html>
|
|
352
|
+
<head>
|
|
353
|
+
<title>Vue SSR Example</title>
|
|
354
|
+
</head>
|
|
355
|
+
<body>
|
|
356
|
+
<div id="app">${html}</div>
|
|
357
|
+
</body>
|
|
358
|
+
</html>`)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
})
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 静态文件
|
|
365
|
+
|
|
366
|
+
通过 static 参数可以设置静态文件目录映射,将一个目录映射到请求路径。
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
await startWebServer({
|
|
370
|
+
static: {
|
|
371
|
+
'/a': { dir: 'D:\\Download', cacheAge: 300 },
|
|
372
|
+
'/a/b': { dir: 'E:\\Dowload', cacheAge: 150 },
|
|
373
|
+
'/b': { dir: 'static', cacheAge: 0 }
|
|
374
|
+
},
|
|
375
|
+
routers: {}
|
|
376
|
+
})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
dir 参数是映射文件目录的地址,可以是绝对路径,也可以是相对路径,相对路径会从进程的当前目录下找。cacheAge 参数
|
|
380
|
+
是缓存时间,如果设置了大于0的值,则会根据设定生成消息头 Cache-Control。
|
|
381
|
+
|
|
382
|
+
请求路径仅支持前缀匹配,不支持通配符,比如 /a/demo.html 可以匹配 /a 路径,响应配置的文件目录下的 demo.html 文件。
|
|
383
|
+
路径配置是有优先级的,如果访问 /a/b/music.mp3 则会匹配到 /a/b 的配置,而不是 /a ,因为 /a/b 的配置更详细,优先级也更高,
|
|
384
|
+
并且如果从 /a/b 配置的目录下没有找到文件,也不会再尝试 /a 的配置。
|
|
385
|
+
|
|
386
|
+
静态文件同时也支持主页自动映射,比如访问 /a/b/c ,会匹配到 /a/b 的配置,然后在配置的文件目录下寻找文件 c ,
|
|
387
|
+
如果找不到则尝试寻找目录 c 下的 index.html 文件。
|
|
388
|
+
|
|
389
|
+
### 请求日志
|
|
390
|
+
|
|
391
|
+
通过将环境变量 SERVER_ACCESS_LOG 设置为 true 可以开启请求日志,开启后会在每次响应完成后输出请求和响应信息到日志里,默认是关闭的。
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
[2023/10/25 17:09:47.872][INFO][access-log]{"method":"GET","url":"/html?tab=%E6%9C%8D%E8%A3%85","ip":"::ffff:127.0.0.1","start":"2023/10/25 17:09:47.871","rt":1,"status":200}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
组件仅提供了简单的 json 格式信息输出,不支持配置格式。如有请求统计的需要,可考虑自定义拦截器
|
|
398
|
+
将请求信息存入消息队列或数据库再做计算,也可以分析日志提取请求信息。
|
|
399
|
+
|
|
400
|
+
### websocket
|
|
401
|
+
|
|
402
|
+
组件本身没有提供 websocket 处理的功能,但是 startWebServer 函数提供了参数 preHandler
|
|
403
|
+
可以对服务进行前置处理,在服务没有启动前完成一些额外的操作,这样可以整合其它支持原生 http 模块的库。
|
|
404
|
+
|
|
405
|
+
可通过整合 socket.io 这个库来实现对 websocket 的处理。
|
|
406
|
+
|
|
407
|
+
```ts
|
|
408
|
+
import { Server } from 'socket.io'
|
|
409
|
+
await startWebServer({
|
|
410
|
+
routers: {
|
|
411
|
+
// 路由配置省略...
|
|
412
|
+
},
|
|
413
|
+
// 前置处理,连接 socket.io
|
|
414
|
+
preHandler: async server => {
|
|
415
|
+
const io = new Server(server)
|
|
416
|
+
// 适配 /chat 路径,如果路由中也有 /chat 路径,不会冲突
|
|
417
|
+
// 客户端用 http 协议请求会被路由处理
|
|
418
|
+
// 当然,肯定不推荐这么做
|
|
419
|
+
io.of('/chat').on('connection', socket => {
|
|
420
|
+
// 建立连接后在回调中完成会话的管理和业务处理
|
|
421
|
+
socket.on('message', data => {
|
|
422
|
+
/* 处理自定义事件*/
|
|
423
|
+
})
|
|
424
|
+
socket.on('disconnect', () => {
|
|
425
|
+
/* 连接断开处理 */
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
}
|
|
429
|
+
})
|
|
430
|
+
```
|