create-dp-koa 1.0.0 → 1.0.2

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 (82) hide show
  1. package/package.json +2 -2
  2. package/template/.cursor/commands/cheatsheet-backend-controller.md +45 -0
  3. package/template/.cursor/commands/implement-backend-api-controller.md +60 -0
  4. package/template/.cursor/commands/plan-backend.md +97 -0
  5. package/template/.cursor/rules/00-backend-core.skill.md +61 -0
  6. package/template/.cursor/rules/01-backend-skill-router.skill.md +57 -0
  7. package/template/.cursor/rules/10-backend-api.skill.md +55 -0
  8. package/template/.cursor/rules/11-backend-controller-recipes.skill.md +188 -0
  9. package/template/.cursor/rules/20-backend-repository.skill.md +25 -0
  10. package/template/.cursor/rules/21-backend-service.skill.md +137 -0
  11. package/template/.cursor/rules/25-backend-comments-and-doc.skill.md +98 -0
  12. package/template/.cursor/rules/30-backend-validation.skill.md +342 -0
  13. package/template/.cursor/rules/40-backend-error-logging.skill.md +21 -0
  14. package/template/.cursor/rules/50-backend-bootstrap-lifecycle.skill.md +105 -0
  15. package/template/.cursor/rules/60-backend-router-registration.skill.md +73 -0
  16. package/template/.cursor/rules/70-backend-middleware.skill.md +100 -0
  17. package/template/.cursor/rules/80-backend-utils-and-libs.skill.md +108 -0
  18. package/template/.cursor/rules/85-backend-plugins.rule.md +65 -0
  19. package/template/.cursor/rules/90-backend-testing.skill.md +29 -0
  20. package/template/.cursor/rules/README.md +49 -0
  21. package/template/.trae/skills/11-backend-controller-recipes.skill.md +91 -10
  22. package/template/scripts/sync-template.mjs +0 -1
  23. package/template/src/controllers/example/ExampleController.ts +14 -0
  24. package/template/src/entity/index.ts +1 -15
  25. package/template/src/framework/decorator/processor/AnnotationProcessor.ts +5 -1
  26. package/template/src/routers/index.ts +0 -35
  27. package/template/src/utils/testDataInitializer.ts +2 -269
  28. package/template/test/controllers/example/ExampleController.test.ts +29 -31
  29. package/template/test/framework/annotation/AnnotationDecorators.test.ts +15 -15
  30. package/template/test/framework/annotation/AnnotationExecutor.test.ts +27 -32
  31. package/template/test/framework/annotation/AnnotationProcessor.test.ts +25 -24
  32. package/template/test/framework/annotation/CustomProcessors.test.ts +15 -25
  33. package/template/test/framework/annotation/NewRouter.test.ts +9 -7
  34. package/template/test/framework/annotation/ProcessorManager.test.ts +14 -27
  35. package/template/test/framework/databaseConfig.test.ts +2 -2
  36. package/template/test/integration/integration.test.ts +15 -72
  37. package/template/src/controllers/cacheManagement.controller.ts +0 -131
  38. package/template/src/controllers/captcha.controller.ts +0 -57
  39. package/template/src/controllers/example/NewAnnotationExampleController.ts +0 -159
  40. package/template/src/controllers/example/SwaggerExampleController.ts +0 -205
  41. package/template/src/controllers/example/TransactionExample.controller.ts +0 -336
  42. package/template/src/controllers/health.controller.ts +0 -235
  43. package/template/src/controllers/home/register.controller.ts +0 -58
  44. package/template/src/controllers/home/ytGoods.controller.ts +0 -92
  45. package/template/src/controllers/home/ytShop.controller.ts +0 -135
  46. package/template/src/controllers/home/ytUser.controller.ts +0 -89
  47. package/template/src/controllers/logManagement.controller.ts +0 -396
  48. package/template/src/controllers/public/emailSend.controller.ts +0 -65
  49. package/template/src/controllers/public/ytUserAuth.controller.ts +0 -174
  50. package/template/src/controllers/testData.controller.ts +0 -253
  51. package/template/src/dto/controller/example/NewAnnotationExampleController.dto.ts +0 -73
  52. package/template/src/dto/controller/home/emailSend.controller.dto.ts +0 -40
  53. package/template/src/dto/controller/home/register.controller.dto.ts +0 -45
  54. package/template/src/dto/controller/home/ytGoods.controller.dto.ts +0 -55
  55. package/template/src/dto/controller/home/ytShop.controller.dto.ts +0 -69
  56. package/template/src/dto/controller/home/ytUser.controller.dto.ts +0 -44
  57. package/template/src/dto/controller/public/ytUserAuth.controller.dto.ts +0 -63
  58. package/template/src/dto/goods.dto.ts +0 -212
  59. package/template/src/dto/service/ytService.dto.ts +0 -13
  60. package/template/src/dto/user.dto.ts +0 -177
  61. package/template/src/entity/columnTypes.ts +0 -13
  62. package/template/src/entity/goodsImagesUnlockKey.entity.ts +0 -33
  63. package/template/src/entity/goodsUnlocker.entity.ts +0 -34
  64. package/template/src/entity/shop.entity.ts +0 -52
  65. package/template/src/entity/shopUser.entity.ts +0 -41
  66. package/template/src/entity/ytGoods.entity.ts +0 -94
  67. package/template/src/entity/ytUser.entity.ts +0 -96
  68. package/template/src/examples/SwaggerProcessorExample.ts +0 -169
  69. package/template/src/examples/TransactionManagerDemo.ts +0 -377
  70. package/template/src/framework/utils/dynamicSwagger.ts +0 -410
  71. package/template/src/repository/UserRepository.ts +0 -122
  72. package/template/src/service/paramValidateTest.service.ts +0 -139
  73. package/template/src/service/ytGoods.service.ts +0 -42
  74. package/template/src/service/ytShop.service.ts +0 -90
  75. package/template/src/service/ytUser.service.ts +0 -451
  76. package/template/src/test/swaggerParameterTest.ts +0 -90
  77. package/template/test/controllers/controllers.test.ts +0 -173
  78. package/template/test/controllers/example/NewAnnotationExampleController.test.ts +0 -200
  79. package/template/test/framework/TransactionManagerDemo.test.ts +0 -363
  80. package/template/test/service/business.test.ts +0 -87
  81. package/template/test/service/paramValidateTest.service.test.ts +0 -184
  82. package/template/test/service/ytUser.service.test.ts +0 -566
@@ -1,92 +0,0 @@
1
- import { Body, ControllerCache, Get, Post, Query, State } from "@src/framework/decorator/controller";
2
- import { BaseController, ControllerResponse } from "@src/controllers/base.controller";
3
- import { GetGoodsInfoDto, GetGoodsInfoResultDto, GetGoodsUnlockInfoDto, UnlockGoodsImageDto } from "@src/dto/controller/home/ytGoods.controller.dto";
4
- import { Inject } from "dp-ioc2";
5
- import { YtGoodsService } from "@src/service/ytGoods.service";
6
- import { YtGoodsEntity } from "@src/entity/ytGoods.entity";
7
- import { CommonServiceResultCode } from "@src/framework/types/ServiceResult";
8
- import { isDebug } from "@src/framework/utils/function";
9
- import { YtGoodsUnlockerEntity } from "@src/entity/goodsUnlocker.entity";
10
-
11
- export class YtGoodsController extends BaseController {
12
-
13
- @Inject(YtGoodsService)
14
- goodsService: YtGoodsService;
15
-
16
- /**
17
- * 解锁商品图片
18
- * @param body
19
- */
20
- @Post()
21
- async unlockGoodsImage(
22
- @Body(UnlockGoodsImageDto) body: UnlockGoodsImageDto,
23
- @State() state: any
24
- ): Promise<ControllerResponse<boolean>> {
25
-
26
- const result = await this.goodsService.unlockGoodsImage(
27
- body.goodsId,
28
- state.user.userId,
29
- body.key
30
- );
31
-
32
- if (result.code !== CommonServiceResultCode.SUCCESS) {
33
- return this.fail(-1, result.message || "解锁失败");
34
- }
35
-
36
- return this.success(true);
37
- }
38
-
39
- @ControllerCache((query: GetGoodsInfoDto) => `YtGoodsController-getGoodsInfo-${query.id}`, {
40
- ttl: {
41
- max: 60 * 5,
42
- min: 30,
43
- },
44
- enable: !isDebug()
45
- })
46
- @Get()
47
- async getGoodsInfo(
48
- @Query(GetGoodsInfoDto) query: GetGoodsInfoDto
49
- ): Promise<ControllerResponse<GetGoodsInfoResultDto>> {
50
-
51
- const goods = await this.goodsService.getById(YtGoodsEntity, query.id);
52
- if (!goods) {
53
- return this.fail(-1, "查询商品失败");
54
- }
55
-
56
- return this.success({
57
- id: goods.id,
58
- name: goods.name,
59
- albums: goods.albums,
60
- tags: goods.tags,
61
- price: goods.price,
62
- description: goods.description,
63
- content: goods.content,
64
- imagesContent: goods.imagesContent
65
- })
66
- }
67
-
68
- // @ControllerCache((query: GetGoodsUnlockInfoDto) => `YtGoodsController-getGoodsUnlockInfo-${query.goodsId}`, {
69
- // ttl: {
70
- // max: 60 * 2,
71
- // min: 30,
72
- // },
73
- // enable: !isDebug()
74
- // })
75
- @Get()
76
- async getGoodsUnlockInfo(
77
- @Query(GetGoodsUnlockInfoDto) query: GetGoodsUnlockInfoDto,
78
- @State() state: any,
79
- ): Promise<ControllerResponse<boolean | null>> {
80
-
81
- const result = await this.goodsService.getDataRepository(YtGoodsUnlockerEntity).findOne({
82
- where: {
83
- ytUserId: state.user.userId,
84
- goodsId: query.goodsId,
85
- }
86
- });
87
- if (result) {
88
- return this.success(true);
89
- }
90
- return this.success(false);
91
- }
92
- }
@@ -1,135 +0,0 @@
1
- import { Inject } from "dp-ioc2"
2
- import { BaseController, ControllerResponse } from "@src/controllers/base.controller"
3
- import { YtShopService } from "@src/service/ytShop.service"
4
- import { Get, Query, ResponseValidateIf, State } from "@src/framework/decorator/controller";
5
- import { GetShopGoodsListQueryDto, GetShopGoodsListResultDto, GetShopInfoByIdQueryDto, GetShopInfoByIdResultDto, GetShopUserInfoDto } from "@src/dto/controller/home/ytShop.controller.dto";
6
- import { ShopEntity, ShopStatusEnum } from "@src/entity/shop.entity";
7
- import { CommonServiceResultCode } from "@src/framework/types/ServiceResult";
8
- import { getUserInfoByIdDto, GetUserInfoResponseDto } from "@src/dto/controller/home/ytUser.controller.dto";
9
- import { createCache, CacheType } from "@src/framework/utils/cache";
10
- import { YtUserService } from "@src/service/ytUser.service";
11
- import { YtUserEntity } from "@src/entity/ytUser.entity";
12
-
13
- // 创建店铺用户缓存实例
14
- const shopUserCache = createCache('yt-shop-user-controller', CacheType.USER, {
15
- stdTTL: 1800, // 30分钟过期
16
- maxKeys: 500
17
- });
18
-
19
-
20
- export class YtShopController extends BaseController {
21
-
22
- @Inject(YtShopService)
23
- ytShopService: YtShopService;
24
-
25
- @Inject(YtUserService)
26
- ytUserService: YtUserService
27
-
28
-
29
- /**
30
- * 读取店铺的基本信息
31
- * @param state
32
- * @returns
33
- */
34
- @ResponseValidateIf(GetShopInfoByIdResultDto, (data) => data && data.data)
35
- @Get()
36
- async getShopInfoById(
37
- @Query(GetShopInfoByIdQueryDto) query: GetShopInfoByIdQueryDto
38
- ): Promise<ControllerResponse<GetShopInfoByIdResultDto | null>> {
39
-
40
- const shop = await this.ytShopService.getById(ShopEntity, query.id);
41
- if (!shop) {
42
- return this.fail(-1, "店铺不存在");
43
- }
44
-
45
- if (shop.status == ShopStatusEnum.FORBIDDEN) {
46
- return this.fail(-1, "店铺已被禁用");
47
- }
48
-
49
- // 读取 店铺用户的基本信息
50
- const shopUserResult = await this.ytShopService.getShopUserInfo(shop.id);
51
- if (shopUserResult.code != CommonServiceResultCode.SUCCESS) {
52
- return this.fail(-1, "店铺用户信息加载失败");
53
- }
54
-
55
- return this.success({
56
- id: shop.id,
57
- name: shop.name,
58
- description: shop.description,
59
- userId: shopUserResult.data?.id ?? 0,
60
- // avatar: shopUserResult.data?.avatar ?? "",
61
- status: shop.status,
62
- });
63
- }
64
-
65
- /**
66
- * 获取店铺的商品列表
67
- * @param query
68
- */
69
- @Get()
70
- async getShopGoodsList(
71
- @Query(GetShopGoodsListQueryDto) query: GetShopGoodsListQueryDto
72
- ): Promise<ControllerResponse<[GetShopGoodsListResultDto[], number]>> {
73
- const { id, page = 1, pageSize = 10 } = query;
74
- const result = await this.ytShopService.getGoodsListByShopId(id, page, pageSize);
75
- if (result.code != CommonServiceResultCode.SUCCESS) {
76
- return this.fail(-1, result.message);
77
- }
78
-
79
- // 过滤出精简信息
80
- const responseData = result.data?.[0].map(item => {
81
- return {
82
- id: item.id,
83
- name: item.name,
84
- description: item.description,
85
- price: item.price,
86
- albums: item.albums,
87
- }
88
- }) ?? []
89
- return this.success([responseData, result.data?.[1] ?? 0]);
90
- }
91
-
92
- @ResponseValidateIf(GetUserInfoResponseDto, (data) => data && data.data)
93
- @Get()
94
- async getShopUserInfo(
95
- @Query(GetShopUserInfoDto) query: GetShopUserInfoDto
96
- )
97
- : Promise<ControllerResponse<null | GetUserInfoResponseDto>> {
98
-
99
- const cacheKey = `shop_user_${query.shopId}`;
100
- const user = shopUserCache.get(cacheKey);
101
- if (user) {
102
- return this.success({
103
- id: user.id,
104
- nickName: user.nickName,
105
- status: user.status,
106
- email: user.email,
107
- type: user.userType,
108
- avatar: user.avatar,
109
- gender: user.gender,
110
- age: user.age,
111
- constellation: user.constellation,
112
- })
113
- }
114
-
115
- const getUserResult = await this.ytShopService.getShopUserInfo(query.shopId);
116
- if (getUserResult.code != CommonServiceResultCode.SUCCESS) {
117
- return this.fail(-1, getUserResult.message)
118
- }
119
- const _user = getUserResult.data as YtUserEntity;
120
- shopUserCache.set(cacheKey, _user);
121
-
122
- return this.success({
123
- id: _user.id,
124
- nickName: _user.nickName,
125
- status: _user.status,
126
- email: _user.email,
127
- type: _user.userType,
128
- avatar: _user.avatar,
129
- gender: _user.gender,
130
- age: _user.age,
131
- constellation: _user.constellation,
132
- })
133
- }
134
-
135
- }
@@ -1,89 +0,0 @@
1
- import { Get, Query, ResponseValidateIf, ResponseValidator, State } from "@src/framework/decorator/controller";
2
- import { BaseController, ControllerResponse } from "@src/controllers/base.controller";
3
- import { logger } from "@src/framework/utils/logger";
4
- import { Inject } from "dp-ioc2";
5
- import { YtUserService } from "@src/service/ytUser.service";
6
- import { YtUserEntity } from "@src/entity/ytUser.entity";
7
- import { createCache, CacheType } from "@src/framework/utils/cache";
8
- import { CommonServiceResultCode } from "@src/framework/types/ServiceResult";
9
- import { getUserInfoByIdDto, GetUserInfoResponseDto } from "@src/dto/controller/home/ytUser.controller.dto";
10
- import { YtShopService } from "@src/service/ytShop.service";
11
-
12
- // 创建用户缓存实例
13
- const userCache = createCache('yt-user-controller', CacheType.USER, {
14
- stdTTL: 1800, // 30分钟过期
15
- maxKeys: 500
16
- });
17
-
18
-
19
-
20
- export class YtUserController extends BaseController {
21
-
22
-
23
- @Inject(YtUserService)
24
- ytUserService: YtUserService;
25
-
26
- @Inject(YtShopService)
27
- ytShopService: YtShopService;
28
-
29
-
30
- @ResponseValidateIf(GetUserInfoResponseDto, (data) => data && data.data)
31
- @Get()
32
- async getUserInfo(@State() state: any)
33
- : Promise<ControllerResponse<null | GetUserInfoResponseDto>> {
34
-
35
- const cacheKey = `user_${state.user.userId}`;
36
- const user = userCache.get(cacheKey);
37
- if (user) {
38
- return this.success({
39
- id: user.id,
40
- nickName: user.nickName,
41
- status: user.status,
42
- email: user.email,
43
- type: user.userType,
44
- avatar: user.avatar,
45
- gender: user.gender,
46
- age: user.age,
47
- constellation: user.constellation,
48
- })
49
- }
50
-
51
- const _user = await this.ytUserService.getById(YtUserEntity, state.user.userId);
52
- if (!_user) {
53
- return this.fail(-1, '用户不存在')
54
- }
55
-
56
- userCache.set(cacheKey, _user);
57
-
58
- return this.success({
59
- id: _user.id,
60
- nickName: _user.nickName,
61
- status: _user.status,
62
- email: _user.email,
63
- type: _user.userType,
64
- avatar: _user.avatar,
65
- gender: _user.gender,
66
- age: _user.age,
67
- constellation: _user.constellation,
68
- })
69
- }
70
-
71
-
72
- /**
73
- * 检查当前用户是否有店铺
74
- * @param state
75
- * @returns
76
- */
77
- @Get()
78
- async hasShop(
79
- @State() state: any
80
- ): Promise<ControllerResponse<number | null>> {
81
-
82
- const result = await this.ytShopService.checkHasShop(state.user.userId);
83
- if (result.code != CommonServiceResultCode.SUCCESS) {
84
- return this.fail(-1, result.message);
85
- }
86
- return this.success(result.data);
87
- }
88
-
89
- }
@@ -1,396 +0,0 @@
1
- import { Context } from 'koa'
2
- import { logger } from '@src/framework/utils/logger'
3
- import { BaseController } from '@src/controllers/base.controller'
4
- import fs from 'fs'
5
- import path from 'path'
6
-
7
- /**
8
- * 日志管理控制器
9
- * 提供日志查询、下载、清理等功能
10
- */
11
- export class LogManagementController extends BaseController {
12
-
13
- /**
14
- * 获取日志统计信息
15
- */
16
- async getLogStats(ctx: Context) {
17
- try {
18
- const logsDir = path.join(process.cwd(), 'logs')
19
- const stats = {
20
- totalFiles: 0,
21
- totalSize: 0,
22
- fileTypes: {} as Record<string, { count: number, size: number }>,
23
- lastModified: null as Date | null
24
- }
25
-
26
- if (fs.existsSync(logsDir)) {
27
- const files = fs.readdirSync(logsDir)
28
-
29
- for (const file of files) {
30
- const filePath = path.join(logsDir, file)
31
- const fileStat = fs.statSync(filePath)
32
-
33
- if (fileStat.isFile()) {
34
- stats.totalFiles++
35
- stats.totalSize += fileStat.size
36
-
37
- const ext = path.extname(file) || 'log'
38
- if (!stats.fileTypes[ext]) {
39
- stats.fileTypes[ext] = { count: 0, size: 0 }
40
- }
41
- stats.fileTypes[ext].count++
42
- stats.fileTypes[ext].size += fileStat.size
43
-
44
- if (!stats.lastModified || fileStat.mtime > stats.lastModified) {
45
- stats.lastModified = fileStat.mtime
46
- }
47
- }
48
- }
49
- }
50
-
51
- logger.info('Log stats retrieved', { stats })
52
-
53
- ctx.body = {
54
- code: 0,
55
- data: stats,
56
- message: '日志统计信息获取成功'
57
- }
58
- } catch (error) {
59
- logger.error('Failed to get log stats', error as Error)
60
- ctx.status = 500
61
- ctx.body = {
62
- code: 500,
63
- message: '获取日志统计信息失败'
64
- }
65
- }
66
- }
67
-
68
- /**
69
- * 获取日志文件列表
70
- */
71
- async getLogFiles(ctx: Context) {
72
- try {
73
- const logsDir = path.join(process.cwd(), 'logs')
74
- const files: any[] = []
75
-
76
- if (fs.existsSync(logsDir)) {
77
- const fileList = fs.readdirSync(logsDir)
78
-
79
- for (const file of fileList) {
80
- const filePath = path.join(logsDir, file)
81
- const fileStat = fs.statSync(filePath)
82
-
83
- if (fileStat.isFile()) {
84
- files.push({
85
- name: file,
86
- size: fileStat.size,
87
- modified: fileStat.mtime,
88
- created: fileStat.birthtime,
89
- type: path.extname(file) || 'log'
90
- })
91
- }
92
- }
93
- }
94
-
95
- // 按修改时间排序
96
- files.sort((a, b) => b.modified.getTime() - a.modified.getTime())
97
-
98
- logger.info('Log files list retrieved', { fileCount: files.length })
99
-
100
- ctx.body = {
101
- code: 0,
102
- data: files,
103
- message: '日志文件列表获取成功'
104
- }
105
- } catch (error) {
106
- logger.error('Failed to get log files', error as Error)
107
- ctx.status = 500
108
- ctx.body = {
109
- code: 500,
110
- message: '获取日志文件列表失败'
111
- }
112
- }
113
- }
114
-
115
- /**
116
- * 下载日志文件
117
- */
118
- async downloadLogFile(ctx: Context) {
119
- try {
120
- const { filename } = ctx.params
121
-
122
- if (!filename || !filename.match(/^[a-zA-Z0-9._-]+$/)) {
123
- ctx.status = 400
124
- ctx.body = {
125
- code: 400,
126
- message: '无效的文件名'
127
- }
128
- return
129
- }
130
-
131
- const filePath = path.join(process.cwd(), 'logs', filename)
132
-
133
- if (!fs.existsSync(filePath)) {
134
- ctx.status = 404
135
- ctx.body = {
136
- code: 404,
137
- message: '日志文件不存在'
138
- }
139
- return
140
- }
141
-
142
- // 审计日志
143
- logger.audit('LOG_DOWNLOAD', filename, {
144
- userId: ctx.state.user?.id,
145
- ip: ctx.ip,
146
- userAgent: ctx.get('User-Agent')
147
- })
148
-
149
- ctx.set('Content-Type', 'application/octet-stream')
150
- ctx.set('Content-Disposition', `attachment; filename="${filename}"`)
151
- ctx.body = fs.createReadStream(filePath)
152
-
153
- } catch (error) {
154
- logger.error('Failed to download log file', error as Error, {
155
- filename: ctx.params.filename
156
- })
157
- ctx.status = 500
158
- ctx.body = {
159
- code: 500,
160
- message: '下载日志文件失败'
161
- }
162
- }
163
- }
164
-
165
- /**
166
- * 查看日志内容
167
- */
168
- async viewLogContent(ctx: Context) {
169
- try {
170
- const { filename } = ctx.params
171
- const { lines = 100, offset = 0 } = ctx.query
172
-
173
- if (!filename || !filename.match(/^[a-zA-Z0-9._-]+$/)) {
174
- ctx.status = 400
175
- ctx.body = {
176
- code: 400,
177
- message: '无效的文件名'
178
- }
179
- return
180
- }
181
-
182
- const filePath = path.join(process.cwd(), 'logs', filename)
183
-
184
- if (!fs.existsSync(filePath)) {
185
- ctx.status = 404
186
- ctx.body = {
187
- code: 404,
188
- message: '日志文件不存在'
189
- }
190
- return
191
- }
192
-
193
- const content = fs.readFileSync(filePath, 'utf8')
194
- const linesArray = content.split('\n')
195
- const startLine = Math.max(0, Number(offset))
196
- const endLine = Math.min(linesArray.length, startLine + Number(lines))
197
-
198
- const selectedLines = linesArray.slice(startLine, endLine)
199
-
200
- // 审计日志
201
- logger.audit('LOG_VIEW', filename, {
202
- userId: ctx.state.user?.id,
203
- lines: Number(lines),
204
- offset: Number(offset),
205
- ip: ctx.ip
206
- })
207
-
208
- ctx.body = {
209
- code: 0,
210
- data: {
211
- filename,
212
- totalLines: linesArray.length,
213
- selectedLines: selectedLines,
214
- startLine,
215
- endLine,
216
- hasMore: endLine < linesArray.length
217
- },
218
- message: '日志内容获取成功'
219
- }
220
-
221
- } catch (error) {
222
- logger.error('Failed to view log content', error as Error, {
223
- filename: ctx.params.filename
224
- })
225
- ctx.status = 500
226
- ctx.body = {
227
- code: 500,
228
- message: '查看日志内容失败'
229
- }
230
- }
231
- }
232
-
233
- /**
234
- * 清理过期日志
235
- */
236
- async cleanExpiredLogs(ctx: Context) {
237
- try {
238
- const { days = 30 } = ctx.request.body
239
- const logsDir = path.join(process.cwd(), 'logs')
240
- const cutoffDate = new Date()
241
- cutoffDate.setDate(cutoffDate.getDate() - Number(days))
242
-
243
- let deletedCount = 0
244
- let deletedSize = 0
245
-
246
- if (fs.existsSync(logsDir)) {
247
- const files = fs.readdirSync(logsDir)
248
-
249
- for (const file of files) {
250
- const filePath = path.join(logsDir, file)
251
- const fileStat = fs.statSync(filePath)
252
-
253
- if (fileStat.isFile() && fileStat.mtime < cutoffDate) {
254
- deletedSize += fileStat.size
255
- fs.unlinkSync(filePath)
256
- deletedCount++
257
- }
258
- }
259
- }
260
-
261
- // 审计日志
262
- logger.audit('LOG_CLEANUP', 'expired_logs', {
263
- userId: ctx.state.user?.id,
264
- days: Number(days),
265
- deletedCount,
266
- deletedSize,
267
- ip: ctx.ip
268
- })
269
-
270
- ctx.body = {
271
- code: 0,
272
- data: {
273
- deletedCount,
274
- deletedSize,
275
- cutoffDate: cutoffDate.toISOString()
276
- },
277
- message: `成功清理 ${deletedCount} 个过期日志文件`
278
- }
279
-
280
- } catch (error) {
281
- logger.error('Failed to clean expired logs', error as Error)
282
- ctx.status = 500
283
- ctx.body = {
284
- code: 500,
285
- message: '清理过期日志失败'
286
- }
287
- }
288
- }
289
-
290
- /**
291
- * 设置日志级别
292
- */
293
- async setLogLevel(ctx: Context) {
294
- try {
295
- const { level, category = 'default' } = ctx.request.body
296
-
297
- const validLevels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']
298
- if (!validLevels.includes(level)) {
299
- ctx.status = 400
300
- ctx.body = {
301
- code: 400,
302
- message: '无效的日志级别'
303
- }
304
- return
305
- }
306
-
307
- // 这里需要实现动态设置日志级别的逻辑
308
- // 由于log4js的限制,可能需要重启应用才能生效
309
-
310
- // 审计日志
311
- logger.audit('LOG_LEVEL_CHANGE', category, {
312
- userId: ctx.state.user?.id,
313
- oldLevel: 'unknown',
314
- newLevel: level,
315
- ip: ctx.ip
316
- })
317
-
318
- ctx.body = {
319
- code: 0,
320
- data: {
321
- category,
322
- level,
323
- message: '日志级别设置成功,重启应用后生效'
324
- },
325
- message: '日志级别设置成功'
326
- }
327
-
328
- } catch (error) {
329
- logger.error('Failed to set log level', error as Error)
330
- ctx.status = 500
331
- ctx.body = {
332
- code: 500,
333
- message: '设置日志级别失败'
334
- }
335
- }
336
- }
337
-
338
- /**
339
- * 获取实时日志流
340
- */
341
- async getLogStream(ctx: Context) {
342
- try {
343
- const { filename } = ctx.params
344
-
345
- if (!filename || !filename.match(/^[a-zA-Z0-9._-]+$/)) {
346
- ctx.status = 400
347
- ctx.body = {
348
- code: 400,
349
- message: '无效的文件名'
350
- }
351
- return
352
- }
353
-
354
- const filePath = path.join(process.cwd(), 'logs', filename)
355
-
356
- if (!fs.existsSync(filePath)) {
357
- ctx.status = 404
358
- ctx.body = {
359
- code: 404,
360
- message: '日志文件不存在'
361
- }
362
- return
363
- }
364
-
365
- // 设置SSE响应头
366
- ctx.set({
367
- 'Content-Type': 'text/event-stream',
368
- 'Cache-Control': 'no-cache',
369
- 'Connection': 'keep-alive',
370
- 'Access-Control-Allow-Origin': '*',
371
- 'Access-Control-Allow-Headers': 'Cache-Control'
372
- })
373
-
374
- // 审计日志
375
- logger.audit('LOG_STREAM_START', filename, {
376
- userId: ctx.state.user?.id,
377
- ip: ctx.ip
378
- })
379
-
380
- // 创建可读流
381
- const stream = fs.createReadStream(filePath, { encoding: 'utf8' })
382
-
383
- ctx.body = stream
384
-
385
- } catch (error) {
386
- logger.error('Failed to get log stream', error as Error, {
387
- filename: ctx.params.filename
388
- })
389
- ctx.status = 500
390
- ctx.body = {
391
- code: 500,
392
- message: '获取日志流失败'
393
- }
394
- }
395
- }
396
- }