wok-server 0.5.0 → 0.7.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.
Files changed (380) hide show
  1. package/README.en.md +61 -0
  2. package/README.md +44 -29
  3. package/dist/cache/cache.js +98 -98
  4. package/dist/cache/config.js +19 -19
  5. package/dist/cache/index.js +27 -27
  6. package/dist/cache/purge-task.js +46 -46
  7. package/dist/cache/stat.js +47 -47
  8. package/dist/config/convert.js +36 -36
  9. package/dist/config/exception.js +14 -14
  10. package/dist/config/index.js +81 -81
  11. package/dist/http-client/index.js +136 -136
  12. package/dist/i18n/ar.js +17 -17
  13. package/dist/i18n/de.js +17 -17
  14. package/dist/i18n/en-us.js +17 -17
  15. package/dist/i18n/es.js +17 -17
  16. package/dist/i18n/fr.js +17 -17
  17. package/dist/i18n/i18n.js +231 -231
  18. package/dist/i18n/index.js +52 -52
  19. package/dist/i18n/ja.js +17 -17
  20. package/dist/i18n/ko.js +17 -17
  21. package/dist/i18n/msg.js +2 -2
  22. package/dist/i18n/pt.js +17 -17
  23. package/dist/i18n/ru.js +17 -17
  24. package/dist/i18n/tag.js +18 -18
  25. package/dist/i18n/zh-HK.js +17 -17
  26. package/dist/i18n/zh-TW.js +17 -17
  27. package/dist/i18n/zh-cn.js +17 -17
  28. package/dist/index.js +14 -14
  29. package/dist/lock/index.js +114 -114
  30. package/dist/log/config.js +35 -35
  31. package/dist/log/date.js +21 -21
  32. package/dist/log/file.js +198 -198
  33. package/dist/log/index.js +135 -135
  34. package/dist/log/level.js +33 -33
  35. package/dist/log/log.js +56 -56
  36. package/dist/log/store.js +19 -19
  37. package/dist/mongodb/collection.js +2 -2
  38. package/dist/mongodb/config.js +34 -34
  39. package/dist/mongodb/doc.js +2 -2
  40. package/dist/mongodb/exception.js +14 -14
  41. package/dist/mongodb/index.js +58 -58
  42. package/dist/mongodb/manager/base.js +563 -563
  43. package/dist/mongodb/manager/index.js +63 -63
  44. package/dist/mongodb/manager/tx-strict.js +84 -84
  45. package/dist/mongodb/manager/tx.js +30 -30
  46. package/dist/mongodb/migration.js +52 -52
  47. package/dist/mvc/access-log.js +33 -33
  48. package/dist/mvc/config.js +27 -27
  49. package/dist/mvc/exchange.js +113 -113
  50. package/dist/mvc/handler/index.js +7 -6
  51. package/dist/mvc/handler/json.js +65 -65
  52. package/dist/mvc/handler/restful.js +35 -35
  53. package/dist/mvc/handler/sse.js +65 -0
  54. package/dist/mvc/handler/upload.js +31 -31
  55. package/dist/mvc/index.js +50 -50
  56. package/dist/mvc/interceptor.js +2 -2
  57. package/dist/mvc/query.js +43 -43
  58. package/dist/mvc/render/file.js +132 -132
  59. package/dist/mvc/render/html/html.js +90 -90
  60. package/dist/mvc/render/html/index.js +18 -18
  61. package/dist/mvc/render/html/style.js +2 -2
  62. package/dist/mvc/render/index.js +7 -7
  63. package/dist/mvc/render/json.js +26 -26
  64. package/dist/mvc/render/text.js +16 -16
  65. package/dist/mvc/router.js +2 -2
  66. package/dist/mvc/server.js +272 -272
  67. package/dist/mvc/static/header.js +67 -67
  68. package/dist/mvc/static/index.js +6 -6
  69. package/dist/mvc/static/mime-type.js +84 -84
  70. package/dist/mvc/static/server-cache-config.js +66 -66
  71. package/dist/mvc/static/server-cache.js +133 -133
  72. package/dist/mvc/static/static-handler.js +372 -372
  73. package/dist/mysql/config.js +51 -51
  74. package/dist/mysql/exception.js +14 -14
  75. package/dist/mysql/index.js +87 -87
  76. package/dist/mysql/manager/base.js +278 -239
  77. package/dist/mysql/manager/index.js +107 -107
  78. package/dist/mysql/manager/ops/count.js +20 -20
  79. package/dist/mysql/manager/ops/criteria.js +381 -356
  80. package/dist/mysql/manager/ops/delete.js +59 -65
  81. package/dist/mysql/manager/ops/exist.js +26 -26
  82. package/dist/mysql/manager/ops/find.js +149 -169
  83. package/dist/mysql/manager/ops/index.js +16 -14
  84. package/dist/mysql/manager/ops/insert.js +132 -106
  85. package/dist/mysql/manager/ops/modify.js +10 -10
  86. package/dist/mysql/manager/ops/order-by.js +28 -0
  87. package/dist/mysql/manager/ops/paginate.js +48 -23
  88. package/dist/mysql/manager/ops/query.js +9 -9
  89. package/dist/mysql/manager/ops/update.js +222 -216
  90. package/dist/mysql/manager/ops/upsert.js +178 -0
  91. package/dist/mysql/manager/ops/utils.js +28 -24
  92. package/dist/mysql/manager/tx-strict.js +103 -103
  93. package/dist/mysql/manager/tx.js +30 -30
  94. package/dist/mysql/manager/utils.js +56 -56
  95. package/dist/mysql/migration.js +136 -136
  96. package/dist/mysql/table-info.js +8 -8
  97. package/dist/task/daily.js +59 -59
  98. package/dist/task/fixed-delay.js +38 -38
  99. package/dist/task/fixed-rate.js +42 -42
  100. package/dist/task/index.js +9 -9
  101. package/dist/task/task.js +56 -56
  102. package/dist/validation/exception.js +36 -36
  103. package/dist/validation/index.js +40 -40
  104. package/dist/validation/validator/array.js +34 -34
  105. package/dist/validation/validator/enum.js +28 -28
  106. package/dist/validation/validator/index.js +14 -14
  107. package/dist/validation/validator/length.js +40 -40
  108. package/dist/validation/validator/max-length.js +35 -35
  109. package/dist/validation/validator/max.js +29 -29
  110. package/dist/validation/validator/min-length.js +33 -33
  111. package/dist/validation/validator/min.js +29 -29
  112. package/dist/validation/validator/not-blank.js +33 -33
  113. package/dist/validation/validator/not-null.js +21 -21
  114. package/dist/validation/validator/plain-obj.js +32 -32
  115. package/dist/validation/validator/regexp.js +34 -34
  116. package/documentation/en/cache.md +56 -0
  117. package/documentation/en/config.md +96 -0
  118. package/documentation/en/engineering.md +256 -0
  119. package/documentation/en/http-client.md +32 -0
  120. package/documentation/en/i18n.md +143 -0
  121. package/documentation/en/index.md +24 -0
  122. package/documentation/en/lock.md +51 -0
  123. package/documentation/en/log.md +109 -0
  124. package/documentation/en/mongodb.md +256 -0
  125. package/documentation/en/mvc.md +688 -0
  126. package/documentation/en/mysql.md +682 -0
  127. package/documentation/en/task.md +45 -0
  128. package/documentation/en/test.md +56 -0
  129. package/documentation/en/validate.md +130 -0
  130. package/documentation/zh-cn/mvc.md +66 -24
  131. package/documentation/zh-cn/mysql.md +146 -17
  132. package/package.json +4 -1
  133. package/skills/wok-server-api-rules/SKILL.md +350 -0
  134. package/skills/wok-server-cache/SKILL.md +216 -0
  135. package/skills/wok-server-code-navigation/SKILL.md +153 -0
  136. package/skills/wok-server-config/SKILL.md +200 -0
  137. package/skills/wok-server-getting-started/SKILL.md +123 -0
  138. package/skills/wok-server-getting-started/references/engineering.md +169 -0
  139. package/skills/wok-server-http-client/SKILL.md +164 -0
  140. package/skills/wok-server-i18n/SKILL.md +214 -0
  141. package/skills/wok-server-lock/SKILL.md +144 -0
  142. package/skills/wok-server-log/SKILL.md +218 -0
  143. package/skills/wok-server-mongodb/SKILL.md +235 -0
  144. package/skills/wok-server-mvc/SKILL.md +251 -0
  145. package/skills/wok-server-mvc/references/respond-html.md +157 -0
  146. package/skills/wok-server-mvc/references/sse.md +121 -0
  147. package/skills/wok-server-mvc/references/static-files.md +47 -0
  148. package/skills/wok-server-mvc/references/upload.md +62 -0
  149. package/skills/wok-server-mvc/references/websocket.md +30 -0
  150. package/skills/wok-server-mysql/SKILL.md +388 -0
  151. package/skills/wok-server-mysql/references/multi-datasource.md +76 -0
  152. package/skills/wok-server-mysql/references/version-control.md +22 -0
  153. package/skills/wok-server-task/SKILL.md +158 -0
  154. package/skills/wok-server-validate/SKILL.md +167 -0
  155. package/src/cache/cache.ts +118 -0
  156. package/src/cache/config.ts +53 -0
  157. package/src/cache/index.ts +27 -0
  158. package/src/cache/purge-task.ts +53 -0
  159. package/src/cache/stat.ts +47 -0
  160. package/src/config/convert.ts +27 -0
  161. package/src/config/exception.ts +8 -0
  162. package/src/config/index.ts +92 -0
  163. package/src/http-client/index.ts +202 -0
  164. package/src/i18n/ar.ts +16 -0
  165. package/src/i18n/de.ts +16 -0
  166. package/src/i18n/en-us.ts +16 -0
  167. package/src/i18n/es.ts +16 -0
  168. package/src/i18n/fr.ts +16 -0
  169. package/src/i18n/i18n.ts +230 -0
  170. package/src/i18n/index.ts +50 -0
  171. package/src/i18n/ja.ts +16 -0
  172. package/src/i18n/ko.ts +16 -0
  173. package/src/i18n/msg.ts +50 -0
  174. package/src/i18n/pt.ts +16 -0
  175. package/src/i18n/ru.ts +16 -0
  176. package/src/i18n/tag.ts +18 -0
  177. package/src/i18n/zh-HK.ts +16 -0
  178. package/src/i18n/zh-TW.ts +16 -0
  179. package/src/i18n/zh-cn.ts +16 -0
  180. package/src/index.ts +11 -0
  181. package/src/lock/index.ts +164 -0
  182. package/src/log/config.ts +71 -0
  183. package/src/log/date.ts +19 -0
  184. package/src/log/file.ts +215 -0
  185. package/src/log/index.ts +136 -0
  186. package/src/log/level.ts +29 -0
  187. package/src/log/log.ts +77 -0
  188. package/src/log/store.ts +31 -0
  189. package/src/mongodb/collection.ts +25 -0
  190. package/src/mongodb/config.ts +69 -0
  191. package/src/mongodb/doc.ts +12 -0
  192. package/src/mongodb/exception.ts +8 -0
  193. package/src/mongodb/index.ts +71 -0
  194. package/src/mongodb/manager/base.ts +674 -0
  195. package/src/mongodb/manager/index.ts +80 -0
  196. package/src/mongodb/manager/tx-strict.ts +153 -0
  197. package/src/mongodb/manager/tx.ts +34 -0
  198. package/src/mongodb/migration.ts +66 -0
  199. package/src/mvc/access-log.ts +33 -0
  200. package/src/mvc/config.ts +70 -0
  201. package/src/mvc/exchange.ts +126 -0
  202. package/src/mvc/handler/index.ts +4 -0
  203. package/src/mvc/handler/json.ts +96 -0
  204. package/src/mvc/handler/restful.ts +39 -0
  205. package/src/mvc/handler/sse.ts +90 -0
  206. package/src/mvc/handler/upload.ts +54 -0
  207. package/src/mvc/index.ts +48 -0
  208. package/src/mvc/interceptor.ts +12 -0
  209. package/src/mvc/query.ts +36 -0
  210. package/src/mvc/render/file.ts +148 -0
  211. package/src/mvc/render/html/html.ts +187 -0
  212. package/src/mvc/render/html/index.ts +16 -0
  213. package/src/mvc/render/html/style.ts +1201 -0
  214. package/src/mvc/render/index.ts +4 -0
  215. package/src/mvc/render/json.ts +24 -0
  216. package/src/mvc/render/text.ts +14 -0
  217. package/src/mvc/router.ts +13 -0
  218. package/src/mvc/server.ts +315 -0
  219. package/src/mvc/static/header.ts +86 -0
  220. package/src/mvc/static/index.ts +3 -0
  221. package/src/mvc/static/mime-type.ts +81 -0
  222. package/src/mvc/static/server-cache-config.ts +92 -0
  223. package/src/mvc/static/server-cache.ts +171 -0
  224. package/src/mvc/static/static-handler.ts +445 -0
  225. package/src/mysql/config.ts +130 -0
  226. package/src/mysql/exception.ts +8 -0
  227. package/src/mysql/index.ts +88 -0
  228. package/src/mysql/manager/base.ts +332 -0
  229. package/src/mysql/manager/index.ts +112 -0
  230. package/src/mysql/manager/ops/count.ts +30 -0
  231. package/src/mysql/manager/ops/criteria.ts +446 -0
  232. package/src/mysql/manager/ops/delete.ts +91 -0
  233. package/src/mysql/manager/ops/exist.ts +41 -0
  234. package/src/mysql/manager/ops/find.ts +209 -0
  235. package/src/mysql/manager/ops/index.ts +13 -0
  236. package/src/mysql/manager/ops/insert.ts +158 -0
  237. package/src/mysql/manager/ops/modify.ts +14 -0
  238. package/src/mysql/manager/ops/order-by.ts +58 -0
  239. package/src/mysql/manager/ops/paginate.ts +100 -0
  240. package/src/mysql/manager/ops/query.ts +13 -0
  241. package/src/mysql/manager/ops/update.ts +318 -0
  242. package/src/mysql/manager/ops/upsert.ts +224 -0
  243. package/src/mysql/manager/ops/utils.ts +24 -0
  244. package/src/mysql/manager/tx-strict.ts +138 -0
  245. package/src/mysql/manager/tx.ts +31 -0
  246. package/src/mysql/manager/utils.ts +75 -0
  247. package/src/mysql/migration.ts +149 -0
  248. package/src/mysql/table-info.ts +41 -0
  249. package/src/task/daily.ts +70 -0
  250. package/src/task/fixed-delay.ts +45 -0
  251. package/src/task/fixed-rate.ts +49 -0
  252. package/src/task/index.ts +4 -0
  253. package/src/task/task.ts +70 -0
  254. package/src/validation/exception.ts +27 -0
  255. package/src/validation/index.ts +61 -0
  256. package/src/validation/validator/array.ts +32 -0
  257. package/src/validation/validator/enum.ts +25 -0
  258. package/src/validation/validator/index.ts +11 -0
  259. package/src/validation/validator/length.ts +42 -0
  260. package/src/validation/validator/max-length.ts +33 -0
  261. package/src/validation/validator/max.ts +26 -0
  262. package/src/validation/validator/min-length.ts +31 -0
  263. package/src/validation/validator/min.ts +26 -0
  264. package/src/validation/validator/not-blank.ts +31 -0
  265. package/src/validation/validator/not-null.ts +19 -0
  266. package/src/validation/validator/plain-obj.ts +30 -0
  267. package/src/validation/validator/regexp.ts +32 -0
  268. package/types/cache/cache.d.ts +52 -52
  269. package/types/cache/config.d.ts +32 -32
  270. package/types/cache/index.d.ts +2 -2
  271. package/types/cache/purge-task.d.ts +11 -11
  272. package/types/cache/stat.d.ts +26 -26
  273. package/types/config/convert.d.ts +6 -6
  274. package/types/config/exception.d.ts +7 -7
  275. package/types/config/index.d.ts +25 -25
  276. package/types/http-client/index.d.ts +71 -71
  277. package/types/i18n/ar.d.ts +2 -2
  278. package/types/i18n/de.d.ts +2 -2
  279. package/types/i18n/en-us.d.ts +2 -2
  280. package/types/i18n/es.d.ts +2 -2
  281. package/types/i18n/fr.d.ts +2 -2
  282. package/types/i18n/i18n.d.ts +102 -102
  283. package/types/i18n/index.d.ts +9 -9
  284. package/types/i18n/ja.d.ts +2 -2
  285. package/types/i18n/ko.d.ts +2 -2
  286. package/types/i18n/msg.d.ts +50 -50
  287. package/types/i18n/pt.d.ts +2 -2
  288. package/types/i18n/ru.d.ts +2 -2
  289. package/types/i18n/tag.d.ts +11 -11
  290. package/types/i18n/zh-HK.d.ts +2 -2
  291. package/types/i18n/zh-TW.d.ts +2 -2
  292. package/types/i18n/zh-cn.d.ts +2 -2
  293. package/types/index.d.ts +11 -11
  294. package/types/lock/index.d.ts +64 -64
  295. package/types/log/config.d.ts +35 -35
  296. package/types/log/date.d.ts +2 -2
  297. package/types/log/file.d.ts +13 -13
  298. package/types/log/index.d.ts +53 -53
  299. package/types/log/level.d.ts +14 -14
  300. package/types/log/log.d.ts +40 -40
  301. package/types/log/store.d.ts +19 -19
  302. package/types/mongodb/collection.d.ts +25 -25
  303. package/types/mongodb/config.d.ts +45 -45
  304. package/types/mongodb/doc.d.ts +11 -11
  305. package/types/mongodb/exception.d.ts +7 -7
  306. package/types/mongodb/index.d.ts +29 -29
  307. package/types/mongodb/manager/base.d.ts +188 -188
  308. package/types/mongodb/manager/index.d.ts +38 -38
  309. package/types/mongodb/manager/tx-strict.d.ts +41 -41
  310. package/types/mongodb/manager/tx.d.ts +21 -21
  311. package/types/mongodb/migration.d.ts +12 -12
  312. package/types/mvc/access-log.d.ts +7 -7
  313. package/types/mvc/config.d.ts +42 -42
  314. package/types/mvc/exchange.d.ts +72 -72
  315. package/types/mvc/handler/index.d.ts +4 -3
  316. package/types/mvc/handler/json.d.ts +44 -44
  317. package/types/mvc/handler/restful.d.ts +11 -11
  318. package/types/mvc/handler/sse.d.ts +34 -0
  319. package/types/mvc/handler/upload.d.ts +36 -36
  320. package/types/mvc/index.d.ts +22 -22
  321. package/types/mvc/interceptor.d.ts +11 -11
  322. package/types/mvc/query.d.ts +13 -13
  323. package/types/mvc/render/file.d.ts +10 -10
  324. package/types/mvc/render/html/html.d.ts +98 -98
  325. package/types/mvc/render/html/index.d.ts +11 -11
  326. package/types/mvc/render/html/style.d.ts +1201 -1201
  327. package/types/mvc/render/index.d.ts +4 -4
  328. package/types/mvc/render/json.d.ts +17 -17
  329. package/types/mvc/render/text.d.ts +10 -10
  330. package/types/mvc/router.d.ts +11 -11
  331. package/types/mvc/server.d.ts +90 -90
  332. package/types/mvc/static/header.d.ts +27 -27
  333. package/types/mvc/static/index.d.ts +3 -3
  334. package/types/mvc/static/mime-type.d.ts +2 -2
  335. package/types/mvc/static/server-cache-config.d.ts +30 -30
  336. package/types/mvc/static/server-cache.d.ts +76 -76
  337. package/types/mvc/static/static-handler.d.ts +77 -77
  338. package/types/mysql/config.d.ts +90 -90
  339. package/types/mysql/exception.d.ts +7 -7
  340. package/types/mysql/index.d.ts +16 -16
  341. package/types/mysql/manager/base.d.ts +196 -165
  342. package/types/mysql/manager/index.d.ts +36 -36
  343. package/types/mysql/manager/ops/count.d.ts +13 -13
  344. package/types/mysql/manager/ops/criteria.d.ts +144 -134
  345. package/types/mysql/manager/ops/delete.d.ts +47 -46
  346. package/types/mysql/manager/ops/exist.d.ts +6 -6
  347. package/types/mysql/manager/ops/find.d.ts +87 -86
  348. package/types/mysql/manager/ops/index.d.ts +12 -10
  349. package/types/mysql/manager/ops/insert.d.ts +32 -18
  350. package/types/mysql/manager/ops/modify.d.ts +3 -3
  351. package/types/mysql/manager/ops/order-by.d.ts +38 -0
  352. package/types/mysql/manager/ops/paginate.d.ts +53 -36
  353. package/types/mysql/manager/ops/query.d.ts +3 -3
  354. package/types/mysql/manager/ops/update.d.ts +99 -76
  355. package/types/mysql/manager/ops/upsert.d.ts +36 -0
  356. package/types/mysql/manager/ops/utils.d.ts +5 -5
  357. package/types/mysql/manager/tx-strict.d.ts +36 -36
  358. package/types/mysql/manager/tx.d.ts +15 -15
  359. package/types/mysql/manager/utils.d.ts +17 -17
  360. package/types/mysql/migration.d.ts +8 -8
  361. package/types/mysql/table-info.d.ts +36 -36
  362. package/types/task/daily.d.ts +16 -16
  363. package/types/task/fixed-delay.d.ts +9 -9
  364. package/types/task/fixed-rate.d.ts +9 -9
  365. package/types/task/index.d.ts +4 -4
  366. package/types/task/task.d.ts +34 -34
  367. package/types/validation/exception.d.ts +38 -38
  368. package/types/validation/index.d.ts +32 -32
  369. package/types/validation/validator/array.d.ts +5 -5
  370. package/types/validation/validator/enum.d.ts +8 -8
  371. package/types/validation/validator/index.d.ts +11 -11
  372. package/types/validation/validator/length.d.ts +10 -10
  373. package/types/validation/validator/max-length.d.ts +8 -8
  374. package/types/validation/validator/max.d.ts +7 -7
  375. package/types/validation/validator/min-length.d.ts +6 -6
  376. package/types/validation/validator/min.d.ts +7 -7
  377. package/types/validation/validator/not-blank.d.ts +7 -7
  378. package/types/validation/validator/not-null.d.ts +6 -6
  379. package/types/validation/validator/plain-obj.d.ts +7 -7
  380. package/types/validation/validator/regexp.d.ts +8 -8
@@ -0,0 +1,171 @@
1
+ export interface CacheVal {
2
+ /**
3
+ * 文件内容
4
+ */
5
+ buffer: Buffer
6
+ /**
7
+ * 媒体类型
8
+ */
9
+ mimeType: string
10
+ /**
11
+ * 修改时间
12
+ */
13
+ mtime: Date
14
+ /**
15
+ * 客户端缓存时间
16
+ */
17
+ cacheAge?: number
18
+ }
19
+
20
+ /**
21
+ * 缓存内容
22
+ */
23
+ interface CacheContent extends CacheVal {
24
+ /**
25
+ * 过期时间
26
+ */
27
+ expireAt: number
28
+ }
29
+
30
+ /**
31
+ * 文件的服务器缓存
32
+ */
33
+ export class ServerCache {
34
+ /**
35
+ * 当前的缓存内容大小,单位字节
36
+ */
37
+ private size = 0
38
+ /**
39
+ * 缓存内容
40
+ */
41
+ private readonly cacheMap = new Map<string, CacheContent>()
42
+
43
+ /**
44
+ * promise 表,作用是处理异步并发问题,如果有相同的 key 同时请求,保证异步的 provider 只执行一次
45
+ */
46
+ private promiseMap = new Map<string, Promise<CacheVal | null>>()
47
+
48
+ constructor(
49
+ private readonly opts: {
50
+ /**
51
+ * 最大缓存大小,超出后将出触发清除缓存操作
52
+ */
53
+ maxSize: number
54
+ /**
55
+ * 缓存时长,单位秒
56
+ */
57
+ maxAge: number
58
+ }
59
+ ) {}
60
+ /**
61
+ * 获取缓存
62
+ */
63
+ get(key: string): CacheVal | null {
64
+ const data = this.cacheMap.get(key)
65
+ if (data) {
66
+ if (data.expireAt < Date.now()) {
67
+ this.cacheMap.delete(key)
68
+ return null
69
+ }
70
+ return data
71
+ }
72
+ return null
73
+ }
74
+ /**
75
+ * 设置缓存内容,如果缓存内容超过最大缓存大小,则触发清除缓存操作
76
+ * @param key
77
+ * @param value
78
+ * @returns
79
+ */
80
+ set(key: string, value: CacheVal) {
81
+ const expireAt = Date.now() + this.opts.maxAge * 1000
82
+ const content = value as CacheContent
83
+ content.expireAt = expireAt
84
+ this.cacheMap.set(key, content)
85
+ this.size += content.buffer.length
86
+ if (this.size > this.opts.maxSize) {
87
+ setTimeout(() => this.clean(), 0)
88
+ }
89
+ }
90
+ /**
91
+ * 删除缓存
92
+ */
93
+ remove(key: string) {
94
+ const data = this.cacheMap.get(key)
95
+ if (data) {
96
+ this.cacheMap.delete(key)
97
+ this.size -= data.buffer.length
98
+ }
99
+ }
100
+ /**
101
+ * 如果缓存不存在,则计算缓存内容并放入缓存
102
+ * @param key
103
+ * @param provider 计算函数,返回值将放入缓存,如果返回 null 则表示未能计算出缓存内容,不进行缓存
104
+ */
105
+ async computeIfAbsent(
106
+ key: string,
107
+ provider: () => Promise<CacheVal | null>
108
+ ): Promise<CacheVal | null> {
109
+ const data = this.get(key)
110
+ if (data) {
111
+ return data
112
+ }
113
+ // 如果已经在处理中,则直接返回 promise
114
+ const ep = this.promiseMap.get(key)
115
+ if (ep) {
116
+ return ep
117
+ }
118
+ const promise = Promise.resolve().then(async () => {
119
+ try {
120
+ const res = await provider()
121
+ if (res) {
122
+ this.set(key, res)
123
+ }
124
+ return res
125
+ } finally {
126
+ this.promiseMap.delete(key)
127
+ }
128
+ })
129
+ this.promiseMap.set(key, promise)
130
+ return promise
131
+ }
132
+ /**
133
+ * 清理无用的缓存内容
134
+ */
135
+ private clean() {
136
+ // 先清理掉过期的
137
+ const keys = Array.from(this.cacheMap.keys())
138
+ for (const key of keys) {
139
+ const data = this.cacheMap.get(key)
140
+ if (data) {
141
+ if (data.expireAt < Date.now()) {
142
+ this.cacheMap.delete(key)
143
+ this.size -= data.buffer.length
144
+ }
145
+ }
146
+ }
147
+ if (this.size < this.opts.maxSize) {
148
+ return
149
+ }
150
+ const keys2 = Array.from(this.cacheMap.keys())
151
+ // 再逐个清理,直到空间不会超出最大缓存大小
152
+ for (const key of keys2) {
153
+ if (this.size < this.opts.maxSize * 0.8) {
154
+ break
155
+ }
156
+ const data = this.cacheMap.get(key)
157
+ if (data) {
158
+ this.cacheMap.delete(key)
159
+ this.size -= data.buffer.length
160
+ }
161
+ }
162
+ }
163
+
164
+ /**
165
+ * 清除缓存内容
166
+ */
167
+ clear() {
168
+ this.cacheMap.clear()
169
+ this.size = 0
170
+ }
171
+ }
@@ -0,0 +1,445 @@
1
+ import { Stats, createReadStream, existsSync, statSync } from 'fs'
2
+ import { readFile, stat } from 'fs/promises'
3
+ import { IncomingMessage, ServerResponse } from 'http'
4
+ import { isAbsolute, resolve } from 'path'
5
+ import { createGzip, gzip } from 'zlib'
6
+ import { renderError } from '../render'
7
+ import { StaticHeaders, parseHeaders } from './header'
8
+ import { decideContentType } from './mime-type'
9
+ import { ServerCache } from './server-cache'
10
+ import { getConfig, parseSize } from './server-cache-config'
11
+
12
+ /**
13
+ * 静态资源的映射规则
14
+ */
15
+ export type ServerStaticRules = Record<
16
+ /**
17
+ * 要映射的路径
18
+ */
19
+ string,
20
+ {
21
+ /**
22
+ * 文件目录
23
+ */
24
+ dir: string
25
+ /**
26
+ * 客户端缓存时长,单位秒,根据设定生成消息头 Cache-Control: max-age=时长,设置为小于等于0可关闭
27
+ */
28
+ cacheAge?: number
29
+ }
30
+ >
31
+
32
+ /**
33
+ * 处理后的静态资源规则,最终按优先级排列.
34
+ */
35
+ interface FlatServerStaticRules {
36
+ /**
37
+ * 匹配路径,仅支持前缀匹配
38
+ */
39
+ path: string
40
+ /**
41
+ * 目录
42
+ */
43
+ dir: string
44
+ /**
45
+ * 缓存时长,单位秒,根据设定生成消息头 Cache-Control: max-age=时长,设置为小于等于0可关闭
46
+ */
47
+ cacheAge: number
48
+ }
49
+ /**
50
+ * 本地文件抽象
51
+ */
52
+ interface LocalFile {
53
+ filePath: string
54
+ stats: Stats
55
+ maxAge?: number
56
+ }
57
+ /**
58
+ * 响应文件抽象
59
+ */
60
+ interface ResponseFile {
61
+ /**
62
+ * 最大生存时间
63
+ */
64
+ maxAge?: number
65
+ /**
66
+ * 修改时间
67
+ */
68
+ mtime: Date
69
+ /**
70
+ * 文件大小
71
+ */
72
+ size: number
73
+ mimeType: string
74
+ /**
75
+ * 内容或路径,两者有一个必须存在
76
+ */
77
+ bufferOrPath: Buffer | string
78
+ }
79
+
80
+ export class StaticHandler {
81
+ private readonly DEFAULT_CONTENT_TYPE = 'application/octet-stream'
82
+ private readonly maxFileSize: number
83
+ private cache?: ServerCache
84
+ private rules: FlatServerStaticRules[]
85
+ /**
86
+ * 静态处理器。规则说明:
87
+ * 请求路径仅支持前缀匹配,不支持通配符,比如 /a/demo.html 可以匹配 /a 路径,响应配置的文件目录下的 demo.html 文件。
88
+ * 路径配置是有优先级的,如果访问 /a/b/music.mp3 则会匹配到 /a/b 的配置,而不是 /a ,因为 /a/b 的配置更详细,优先级也更高,
89
+ * 并且如果从 /a/b 配置的目录下没有找到文件,也不会再尝试 /a 的配置。
90
+ *
91
+ * 静态文件同时也支持主页自动映射,比如访问 /a/b/c ,会匹配到 /a/b 的配置,然后在配置的文件目录下寻找文件 c ,
92
+ * 如果找不到则尝试寻找目录 c 下的 index.html 文件。
93
+ * @param rules 规则设置
94
+ */
95
+ constructor(rules: ServerStaticRules) {
96
+ const config = getConfig()
97
+ this.maxFileSize = parseSize(config.maxFileSize)
98
+ if (config.enable) {
99
+ this.cache = new ServerCache({ maxSize: parseSize(config.maxSize), maxAge: config.maxAge })
100
+ }
101
+ this.rules = []
102
+ // 规则解析,路径判定,设置的目录必须存在或可以被创建
103
+ // 重复记录表 ,作用是为了路径去重判定,可以提示哪些路径是重复的
104
+ const duplicateMap = new Map<string, string>()
105
+ for (const entry of Object.entries(rules)) {
106
+ const [path, setting] = entry
107
+ const dir = isAbsolute(setting.dir) ? setting.dir : resolve(process.cwd(), setting.dir)
108
+ if (!existsSync(dir)) {
109
+ throw new Error(
110
+ `Static file configuration error,path ${dir} does not exist,config dir:${setting.dir}`
111
+ )
112
+ }
113
+ const statRes = statSync(dir)
114
+ if (!statRes.isDirectory()) {
115
+ throw new Error(
116
+ `Static file configuration error,path ${dir} is not a directory,config dir:${setting.dir}`
117
+ )
118
+ }
119
+ let finalPath = path.startsWith('/') ? path : '/' + path
120
+ // 保持以 / 结尾,为了匹配方便
121
+ if (!finalPath.endsWith('/')) {
122
+ finalPath += '/'
123
+ }
124
+ if (duplicateMap.has(finalPath)) {
125
+ throw new Error(`Static path duplicated: ${duplicateMap.get(finalPath)} and ${path}`)
126
+ }
127
+ duplicateMap.set(finalPath, path)
128
+ this.rules.push({ path: finalPath, dir, cacheAge: setting.cacheAge || 0 })
129
+ }
130
+ // 优先级排序
131
+ this.rules.sort((o1, o2) => {
132
+ let priority1 = o1.path === '/' ? -1 : o1.path.split('/').length
133
+ let priority2 = o2.path === '/' ? -1 : o2.path.split('/').length
134
+ // 如果 o1 优先级高,就应该排前面,返回小于0的值,反之亦然\
135
+ // 前面的优先级值是值越大优先级越高,反过来减
136
+ return priority2 - priority1
137
+ })
138
+ }
139
+ /**
140
+ * 处理静态文件 get 请求
141
+ * @param request
142
+ * @param response
143
+ * @param path
144
+ * @returns 是否能够处理,如果因为找不到文件或其它原因无法处理则返回 false ,由后续流程继续处理
145
+ */
146
+ async handleGet(
147
+ request: IncomingMessage,
148
+ response: ServerResponse,
149
+ path: string
150
+ ): Promise<boolean> {
151
+ // 解析消息头
152
+ const headersInfo = parseHeaders(request.headers)
153
+ // 构建文件信息
154
+ let file = await this.buildRespFile(path, headersInfo)
155
+ if (!file) {
156
+ return false
157
+ }
158
+
159
+ // content-type
160
+ response.setHeader('Content-Type', file.mimeType)
161
+ // client cache
162
+ if (file.maxAge === 0) {
163
+ response.setHeader('Cache-Control', 'no-store')
164
+ } else if (file.maxAge) {
165
+ response.setHeader('Cache-Control', `max-age=${file.maxAge}`)
166
+ }
167
+ response.setHeader('Last-Modified', file.mtime.toUTCString())
168
+
169
+ // 开始响应,先判定 if-modified-since
170
+ if (headersInfo.ifModifiedSince) {
171
+ if (headersInfo.ifModifiedSince >= file.mtime) {
172
+ response.statusCode = 304
173
+ response.end()
174
+ return true
175
+ }
176
+ }
177
+
178
+ if (headersInfo.range) {
179
+ const { start } = headersInfo.range
180
+ if (isNaN(start) || start < 0) {
181
+ renderError(response, `Range not satisfiable,start is ${start}.`, 416)
182
+ return true
183
+ }
184
+ const maxEnd = file.size - 1
185
+ let end = headersInfo.range.end ? headersInfo.range.end : maxEnd
186
+ // 校验 end
187
+ if (end <= start) {
188
+ renderError(response, `Range not satisfiable,end must be greater than start.`, 416)
189
+ return true
190
+ }
191
+ if (end > maxEnd) {
192
+ renderError(response, `Range not satisfiable,end must not be greater than ${maxEnd}.`, 416)
193
+ return true
194
+ }
195
+ // range 是前后都包含的,详细可以参考规范:
196
+ // https://www.rfc-editor.org/rfc/rfc9110#field.range
197
+ // 但是 buffer.subarray 中包含前不包含后,createReadStream 是前后都包含的,需要注意
198
+ // 还需要注意 gzip 编码
199
+ response.setHeader('Content-Range', `bytes ${start}-${end}/${file.size}`)
200
+ response.statusCode = 206
201
+ if (file.bufferOrPath instanceof Buffer) {
202
+ let buffer = file.bufferOrPath.subarray(start, end + 1)
203
+ if (headersInfo.gzip) {
204
+ buffer = await new Promise<Buffer>((resolve, reject) => {
205
+ gzip(buffer, (err, res) => {
206
+ if (err) {
207
+ reject(err)
208
+ return
209
+ }
210
+ resolve(res)
211
+ })
212
+ })
213
+ response.setHeader('Content-Encoding', 'gzip')
214
+ }
215
+ await this.endRespWithBuffer(response, buffer)
216
+ } else {
217
+ const filePath = file.bufferOrPath
218
+ // 文件处理
219
+ await new Promise<void>((resolve, reject) => {
220
+ response.setHeader('Content-Encoding', 'gzip')
221
+ response.once('finish', resolve).once('error', reject)
222
+ if (headersInfo.gzip) {
223
+ createReadStream(filePath, { start, end }).pipe(createGzip()).pipe(response)
224
+ } else {
225
+ createReadStream(filePath, { start, end }).pipe(response)
226
+ }
227
+ })
228
+ }
229
+ return true
230
+ }
231
+ // gzip
232
+ if (headersInfo.gzip) {
233
+ response.setHeader('Content-Encoding', 'gzip')
234
+ if (file.bufferOrPath instanceof Buffer) {
235
+ // buffer 是缓存的文件,gzip 编码缓存的文件即是已经压缩后的文件
236
+ await this.endRespWithBuffer(response, file.bufferOrPath)
237
+ } else {
238
+ const path = file.bufferOrPath
239
+ await new Promise<void>((resolve, reject) => {
240
+ response.once('finish', resolve).once('error', reject)
241
+ createReadStream(path).pipe(createGzip()).pipe(response)
242
+ })
243
+ }
244
+ return true
245
+ }
246
+ // 普通的整个文件请求处理
247
+ if (file.bufferOrPath instanceof Buffer) {
248
+ await this.endRespWithBuffer(response, file.bufferOrPath)
249
+ } else {
250
+ const filePath = file.bufferOrPath
251
+ await new Promise<void>((resolve, reject) => {
252
+ response.once('finish', resolve).once('error', reject)
253
+ createReadStream(filePath).pipe(response)
254
+ })
255
+ }
256
+ return true
257
+ }
258
+
259
+ /**
260
+ * 处理 head 请求,不响应正文内容,如果文件可以被找到,仅仅响应以下的消息头:
261
+ * Content-Length
262
+ * Content-Type
263
+ * Last-Modified
264
+ * Cache-Control
265
+ *
266
+ * @param path 请求路径
267
+ * @param response
268
+ * @returns 是否成功处理
269
+ */
270
+ async handleHead(path: string, response: ServerResponse): Promise<boolean> {
271
+ // 构建文件信息
272
+ let file = await this.buildRespFile(path, { gzip: false })
273
+ if (!file) {
274
+ return false
275
+ }
276
+ response.setHeader('Content-Length', file.size)
277
+ response.setHeader('Content-Type', file.mimeType)
278
+ response.setHeader('Last-Modified', file.mtime.toUTCString())
279
+ if (file.maxAge === 0) {
280
+ response.setHeader('Cache-Control', 'no-store')
281
+ } else if (file.maxAge) {
282
+ response.setHeader('Cache-Control', `max-age=${file.maxAge}`)
283
+ }
284
+ response.end()
285
+ return true
286
+ }
287
+
288
+ async endRespWithBuffer(response: ServerResponse, buffer: Buffer) {
289
+ if (response.writableEnded || response.destroyed) {
290
+ return
291
+ }
292
+ await new Promise<void>((resolve, reject) => {
293
+ response.write(buffer, err => {
294
+ if (err) {
295
+ reject(err)
296
+ } else {
297
+ resolve()
298
+ }
299
+ })
300
+ })
301
+ await new Promise<void>((resolve, reject) => {
302
+ response.end(resolve)
303
+ })
304
+ }
305
+
306
+ /**
307
+ * 构建响应文件
308
+ *
309
+ * @param path
310
+ * @param headers
311
+ * @returns
312
+ */
313
+ private async buildRespFile(path: string, headers: StaticHeaders): Promise<ResponseFile | null> {
314
+ if (!this.cache) {
315
+ const file = await this.findFile(path)
316
+ if (!file) {
317
+ return null
318
+ }
319
+ let { mtime } = file.stats
320
+ mtime.setMilliseconds(0)
321
+ return {
322
+ maxAge: file.maxAge,
323
+ mtime: mtime,
324
+ size: file.stats.size,
325
+ bufferOrPath: file.filePath,
326
+ mimeType: decideContentType(file.filePath) || this.DEFAULT_CONTENT_TYPE
327
+ }
328
+ }
329
+ const cacheingGzip = headers.gzip && !headers.range
330
+ const key = cacheingGzip ? `gzip-${path}` : path
331
+ let file: { filePath: string; stats: Stats; maxAge?: number } | undefined
332
+ const cachedVal = await this.cache.computeIfAbsent(key, async () => {
333
+ file = (await this.findFile(path)) || undefined
334
+ if (!file) {
335
+ return null
336
+ }
337
+ // 文件太大不能被缓存
338
+ if (file.stats.size > this.maxFileSize) {
339
+ return null
340
+ }
341
+ let buffer = await readFile(file.filePath)
342
+ // gzip 存储压缩后的文件
343
+ if (cacheingGzip) {
344
+ buffer = await new Promise<Buffer>((resolve, reject) => {
345
+ gzip(buffer, (err, res) => {
346
+ if (err) {
347
+ reject(err)
348
+ } else {
349
+ resolve(res)
350
+ }
351
+ })
352
+ })
353
+ }
354
+ let { mtime } = file.stats
355
+ mtime.setMilliseconds(0)
356
+ return {
357
+ buffer,
358
+ mtime: mtime,
359
+ cacheAge: file.maxAge,
360
+ mimeType: decideContentType(file.filePath) || this.DEFAULT_CONTENT_TYPE
361
+ }
362
+ })
363
+ if (cachedVal) {
364
+ return {
365
+ maxAge: cachedVal.cacheAge,
366
+ mtime: cachedVal.mtime,
367
+ size: cachedVal.buffer.length,
368
+ bufferOrPath: cachedVal.buffer,
369
+ mimeType: cachedVal.mimeType
370
+ }
371
+ }
372
+
373
+ // 判定文件大小
374
+ if (file) {
375
+ let { mtime } = file.stats
376
+ mtime.setMilliseconds(0)
377
+ return {
378
+ maxAge: file.maxAge,
379
+ mtime: mtime,
380
+ size: file.stats.size,
381
+ bufferOrPath: file.filePath,
382
+ mimeType: decideContentType(file.filePath) || this.DEFAULT_CONTENT_TYPE
383
+ }
384
+ }
385
+ return null
386
+ }
387
+ /**
388
+ * 根据路径查找文件
389
+ * @param path 访问路径
390
+ * @returns 返回被查询到的文件信息,如果找不到则返回 null
391
+ */
392
+ private async findFile(path: string): Promise<LocalFile | null> {
393
+ let matchedRule: FlatServerStaticRules | undefined
394
+ for (const rule of this.rules) {
395
+ if (rule.path === '/') {
396
+ matchedRule = rule
397
+ break
398
+ }
399
+ if (path.startsWith(rule.path)) {
400
+ matchedRule = rule
401
+ break
402
+ }
403
+ }
404
+ // 匹配失败
405
+ if (!matchedRule) {
406
+ return null
407
+ }
408
+ // 构建地址
409
+ let finalPath = matchedRule.path === '/' ? path : path.substring(matchedRule.path.length)
410
+ if (finalPath.startsWith('/')) {
411
+ finalPath = finalPath.substring(1)
412
+ }
413
+ let filePath = resolve(matchedRule.dir, finalPath)
414
+ if (!existsSync(filePath)) {
415
+ return null
416
+ }
417
+ const fileStat = await stat(filePath)
418
+ // 如果是目录,尝试查找 index.html
419
+ if (fileStat.isDirectory()) {
420
+ const indexPath = resolve(filePath, 'index.html')
421
+ if (!existsSync(indexPath)) {
422
+ return null
423
+ }
424
+ const indexStat = await stat(indexPath)
425
+ if (!indexStat.isFile()) {
426
+ return null
427
+ }
428
+ return { filePath: indexPath, stats: indexStat }
429
+ }
430
+ if (!fileStat.isFile()) {
431
+ return null
432
+ }
433
+ return { filePath, stats: fileStat, maxAge: matchedRule.cacheAge }
434
+ }
435
+ /**
436
+ * 删除服务器端静态资源缓存
437
+ * @param path
438
+ */
439
+ removeServerCache(path: string) {
440
+ if (this.cache) {
441
+ this.cache.remove(path)
442
+ this.cache.remove(`gzip-${path}`)
443
+ }
444
+ }
445
+ }