create-dp-koa 1.0.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/README.md +50 -0
- package/index.mjs +97 -0
- package/package.json +33 -0
- package/template/.env.development +9 -0
- package/template/.env.production +12 -0
- package/template/.github/workflows/ci-cd.yml +182 -0
- package/template/.trae/documents/controller_development_plan.md +386 -0
- package/template/.trae/skills/00-backend-core.skill.md +50 -0
- package/template/.trae/skills/01-backend-skill-router.skill.md +55 -0
- package/template/.trae/skills/10-backend-api.skill.md +54 -0
- package/template/.trae/skills/11-backend-controller-recipes.skill.md +107 -0
- package/template/.trae/skills/20-backend-repository.skill.md +25 -0
- package/template/.trae/skills/21-backend-service.skill.md +135 -0
- package/template/.trae/skills/25-backend-comments-and-doc.skill.md +97 -0
- package/template/.trae/skills/30-backend-validation.skill.md +320 -0
- package/template/.trae/skills/40-backend-error-logging.skill.md +21 -0
- package/template/.trae/skills/50-backend-bootstrap-lifecycle.skill.md +90 -0
- package/template/.trae/skills/60-backend-router-registration.skill.md +71 -0
- package/template/.trae/skills/70-backend-middleware.skill.md +98 -0
- package/template/.trae/skills/80-backend-utils-and-libs.skill.md +90 -0
- package/template/.trae/skills/85-backend-plugins.rule.md +64 -0
- package/template/.trae/skills/90-backend-testing.skill.md +29 -0
- package/template/.trae/skills/README.md +49 -0
- package/template/.vscode/launch.json +38 -0
- package/template/.vscode/settings.json +1 -0
- package/template/Dockerfile +36 -0
- package/template/README.md +229 -0
- package/template/docker-compose.yml +135 -0
- package/template/docs/API_DOCUMENTATION.md +837 -0
- package/template/docs/ARCHITECTURE_REFACTOR.md +109 -0
- package/template/docs/CACHE_MIGRATION_GUIDE.md +142 -0
- package/template/docs/DEPLOYMENT_GUIDE.md +1062 -0
- package/template/docs/DEVELOPMENT_GUIDE.md +1097 -0
- package/template/docs/DOCUMENTATION_CLEANUP_REPORT.md +166 -0
- package/template/docs/DOCUMENTATION_COMPLETION_REPORT.md +223 -0
- package/template/docs/DOCUMENTATION_INDEX.md +294 -0
- package/template/docs/DOCUMENTATION_STRUCTURE.md +221 -0
- package/template/docs/ENTERPRISE_ANNOTATION_SYSTEM_GUIDE.md +2069 -0
- package/template/docs/ENTERPRISE_DATABASE_ARCHITECTURE.md +318 -0
- package/template/docs/ENTERPRISE_DEPLOYMENT_GUIDE.md +547 -0
- package/template/docs/ENTERPRISE_ERROR_HANDLING_GUIDE.md +357 -0
- package/template/docs/ENTERPRISE_LOGGING_SYSTEM_GUIDE.md +494 -0
- package/template/docs/ENVIRONMENT_CONFIG_EXAMPLE.md +69 -0
- package/template/docs/FINAL_IMPLEMENTATION_SUMMARY.md +206 -0
- package/template/docs/HEALTH_CHECK_ROUTE_FIX.md +134 -0
- package/template/docs/IMPLEMENTATION_CHECKLIST.md +204 -0
- package/template/docs/INSTALLATION_GUIDE.md +611 -0
- package/template/docs/INTERCEPTOR_TESTING_REPORT.md +226 -0
- package/template/docs/INTERCEPTOR_TESTING_SCRIPTS.md +143 -0
- package/template/docs/LOGGING_OPTIMIZATION_GUIDE.md +126 -0
- package/template/docs/MEMORY_DATABASE_GUIDE.md +212 -0
- package/template/docs/NEW_ROUTER_INTEGRATION_GUIDE.md +345 -0
- package/template/docs/NEW_ROUTER_INTEGRATION_SUMMARY.md +259 -0
- package/template/docs/NEW_ROUTER_USAGE_GUIDE.md +364 -0
- package/template/docs/QUICK_START.md +268 -0
- package/template/docs/ROUTE_SLASH_COMPATIBILITY_FIX.md +191 -0
- package/template/docs/SERVICE_INTERCEPTOR_GUIDE.md +243 -0
- package/template/docs/SERVICE_LAYER_INDEX.md +205 -0
- package/template/docs/SERVICE_PATTERN_GUIDE.md +270 -0
- package/template/docs/SERVICE_RETURN_VALUE_SPECIFICATION.md +466 -0
- package/template/docs/SWAGGER_DEBUG_MODE_GUIDE.md +80 -0
- package/template/docs/SWAGGER_INTEGRATION_GUIDE.md +416 -0
- package/template/docs/TRANSACTION_MANAGER_USAGE.md +360 -0
- package/template/docs/TROUBLESHOOTING.md +869 -0
- package/template/env.production.example +62 -0
- package/template/jest.config.js +34 -0
- package/template/package-lock.json +13240 -0
- package/template/package.json +119 -0
- package/template/patches/typeorm+0.3.25.patch +22 -0
- package/template/scripts/sync-template.mjs +84 -0
- package/template/scripts/test-annotation-system.sh +48 -0
- package/template/scripts/test-core-functionality.sh +28 -0
- package/template/src/annotations/decorators/ConfigManagement.ts +9 -0
- package/template/src/annotations/decorators/DistributedTracing.ts +9 -0
- package/template/src/annotations/decorators/EnterprisePerformance.ts +9 -0
- package/template/src/annotations/decorators/PerformanceMonitor.ts +32 -0
- package/template/src/annotations/decorators/SecurityAudit.ts +9 -0
- package/template/src/annotations/index.ts +50 -0
- package/template/src/annotations/processors/ConfigManagementProcessor.ts +369 -0
- package/template/src/annotations/processors/DistributedTracingProcessor.ts +288 -0
- package/template/src/annotations/processors/EnterprisePerformanceProcessor.ts +189 -0
- package/template/src/annotations/processors/PerformanceMonitorProcessor.ts +101 -0
- package/template/src/annotations/processors/SecurityAuditProcessor.ts +345 -0
- package/template/src/annotations/processors/SwaggerProcessor.ts +612 -0
- package/template/src/annotations/processors/index.ts +10 -0
- package/template/src/app.ts +123 -0
- package/template/src/controllers/base.controller.ts +41 -0
- package/template/src/controllers/cacheManagement.controller.ts +131 -0
- package/template/src/controllers/captcha.controller.ts +57 -0
- package/template/src/controllers/demo/AnnotationDemoController.ts +118 -0
- package/template/src/controllers/example/EnterpriseExampleController.ts +297 -0
- package/template/src/controllers/example/ExampleController.ts +110 -0
- package/template/src/controllers/example/NewAnnotationExampleController.ts +159 -0
- package/template/src/controllers/example/SwaggerExampleController.ts +205 -0
- package/template/src/controllers/example/TransactionExample.controller.ts +336 -0
- package/template/src/controllers/health.controller.ts +235 -0
- package/template/src/controllers/home/register.controller.ts +58 -0
- package/template/src/controllers/home/ytGoods.controller.ts +92 -0
- package/template/src/controllers/home/ytShop.controller.ts +135 -0
- package/template/src/controllers/home/ytUser.controller.ts +89 -0
- package/template/src/controllers/logManagement.controller.ts +396 -0
- package/template/src/controllers/public/emailSend.controller.ts +65 -0
- package/template/src/controllers/public/ytUserAuth.controller.ts +174 -0
- package/template/src/controllers/testData.controller.ts +253 -0
- package/template/src/dto/controller/example/NewAnnotationExampleController.dto.ts +73 -0
- package/template/src/dto/controller/home/emailSend.controller.dto.ts +40 -0
- package/template/src/dto/controller/home/register.controller.dto.ts +45 -0
- package/template/src/dto/controller/home/ytGoods.controller.dto.ts +55 -0
- package/template/src/dto/controller/home/ytShop.controller.dto.ts +69 -0
- package/template/src/dto/controller/home/ytUser.controller.dto.ts +44 -0
- package/template/src/dto/controller/public/ytUserAuth.controller.dto.ts +63 -0
- package/template/src/dto/goods.dto.ts +212 -0
- package/template/src/dto/service/ytService.dto.ts +13 -0
- package/template/src/dto/user.dto.ts +177 -0
- package/template/src/entity/base.entity.ts +13 -0
- package/template/src/entity/columnTypes.ts +13 -0
- package/template/src/entity/goodsImagesUnlockKey.entity.ts +33 -0
- package/template/src/entity/goodsUnlocker.entity.ts +34 -0
- package/template/src/entity/index.ts +15 -0
- package/template/src/entity/shop.entity.ts +52 -0
- package/template/src/entity/shopUser.entity.ts +41 -0
- package/template/src/entity/ytGoods.entity.ts +94 -0
- package/template/src/entity/ytUser.entity.ts +96 -0
- package/template/src/examples/InterceptorExampleRunner.ts +284 -0
- package/template/src/examples/ServiceInterceptorExample.ts +214 -0
- package/template/src/examples/SwaggerProcessorExample.ts +169 -0
- package/template/src/examples/TransactionManagerDemo.ts +377 -0
- package/template/src/examples/cacheExamples.ts +155 -0
- package/template/src/framework/decorator/controller.ts +311 -0
- package/template/src/framework/decorator/processor/AnnotationDecorators.ts +100 -0
- package/template/src/framework/decorator/processor/AnnotationProcessor.ts +156 -0
- package/template/src/framework/decorator/processor/AnnotationProcessorConfig.ts +45 -0
- package/template/src/framework/decorator/processor/AnnotationRegistry.ts +117 -0
- package/template/src/framework/decorator/processor/AnnotationSystemInitializer.ts +95 -0
- package/template/src/framework/decorator/processor/ProcessorManager.ts +76 -0
- package/template/src/framework/decorator/processor/processors/CustomProcessors.ts +126 -0
- package/template/src/framework/decorator/processor/processors/DefaultProcessors.ts +207 -0
- package/template/src/framework/decorator/refactored/DecoratorFactory.ts +99 -0
- package/template/src/framework/decorator/refactored/DecoratorMetadataManager.ts +125 -0
- package/template/src/framework/decorator/refactored/DecoratorValidator.ts +128 -0
- package/template/src/framework/decorator/refactored/TypeSafeDecorators.ts +139 -0
- package/template/src/framework/decorator/refactored/index.ts +98 -0
- package/template/src/framework/decorator/swagger.ts +150 -0
- package/template/src/framework/interceptors/AdvancedServiceCallInterceptor.ts +375 -0
- package/template/src/framework/interceptors/ServiceCallInterceptor.ts +348 -0
- package/template/src/framework/interceptors/index.ts +19 -0
- package/template/src/framework/plugins/registry.ts +63 -0
- package/template/src/framework/plugins/types.ts +15 -0
- package/template/src/framework/types/ServiceResult.ts +151 -0
- package/template/src/framework/types/index.ts +16 -0
- package/template/src/framework/utils/CacheManager.ts +430 -0
- package/template/src/framework/utils/CacheService.ts +248 -0
- package/template/src/framework/utils/DtoValidator.ts +164 -0
- package/template/src/framework/utils/MigrationHelper.ts +179 -0
- package/template/src/framework/utils/MigrationManager.ts +256 -0
- package/template/src/framework/utils/NewRouter.ts +207 -0
- package/template/src/framework/utils/TransactionManager.ts +172 -0
- package/template/src/framework/utils/bootstrap.ts +445 -0
- package/template/src/framework/utils/cache.ts +269 -0
- package/template/src/framework/utils/databaseConfig.ts +148 -0
- package/template/src/framework/utils/db.ts +39 -0
- package/template/src/framework/utils/dbMonitor.ts +106 -0
- package/template/src/framework/utils/dynamicSwagger.ts +410 -0
- package/template/src/framework/utils/function.ts +61 -0
- package/template/src/framework/utils/gracefulShutdown.ts +131 -0
- package/template/src/framework/utils/logger.ts +388 -0
- package/template/src/framework/utils/metrics.ts +182 -0
- package/template/src/framework/utils/router.ts +417 -0
- package/template/src/framework/utils/swagger.ts +184 -0
- package/template/src/framework/utils/testDb.ts +19 -0
- package/template/src/framework/utils/token.ts +23 -0
- package/template/src/framework/utils/transform.ts +17 -0
- package/template/src/libs/aokEmailSender.ts +42 -0
- package/template/src/libs/captcha.ts +37 -0
- package/template/src/libs/cos.ts +45 -0
- package/template/src/libs/mCache.ts +7 -0
- package/template/src/libs/serviceValidate.ts +3 -0
- package/template/src/libs/tecentSms.ts +51 -0
- package/template/src/middlewares/a.middleware.ts +6 -0
- package/template/src/middlewares/error.middleware.ts +14 -0
- package/template/src/middlewares/logging.middleware.ts +187 -0
- package/template/src/middlewares/static.middleware.ts +79 -0
- package/template/src/middlewares/swagger.middleware.ts +70 -0
- package/template/src/middlewares/token.middleware.ts +32 -0
- package/template/src/migrations/1700000000000-InitialDatabaseStructure.ts +172 -0
- package/template/src/migrations/index.ts +6 -0
- package/template/src/plugins/weboffice/core/context.ts +47 -0
- package/template/src/plugins/weboffice/core/errors.ts +51 -0
- package/template/src/plugins/weboffice/core/types.ts +63 -0
- package/template/src/plugins/weboffice/core/utils.ts +7 -0
- package/template/src/plugins/weboffice/entities/index.ts +3 -0
- package/template/src/plugins/weboffice/entities/webofficeFile.entity.ts +28 -0
- package/template/src/plugins/weboffice/entities/webofficeFileVersion.entity.ts +29 -0
- package/template/src/plugins/weboffice/http/routes.ts +179 -0
- package/template/src/plugins/weboffice/index.ts +23 -0
- package/template/src/plugins/weboffice/services/webofficeCallback.service.ts +274 -0
- package/template/src/repository/UserRepository.ts +122 -0
- package/template/src/repository/base/BaseRepository.ts +124 -0
- package/template/src/repository/interfaces/IBaseRepository.ts +67 -0
- package/template/src/routers/index.ts +49 -0
- package/template/src/service/base.service.ts +116 -0
- package/template/src/service/paramValidateTest.service.ts +139 -0
- package/template/src/service/ytGoods.service.ts +42 -0
- package/template/src/service/ytShop.service.ts +90 -0
- package/template/src/service/ytUser.service.ts +451 -0
- package/template/src/test/swaggerParameterTest.ts +90 -0
- package/template/src/utils/testDataInitializer.ts +296 -0
- package/template/static/output.json +15203 -0
- package/template/test/controllers/controllers.test.ts +173 -0
- package/template/test/controllers/example/ExampleController.test.ts +222 -0
- package/template/test/controllers/example/NewAnnotationExampleController.test.ts +200 -0
- package/template/test/framework/TransactionManagerDemo.test.ts +363 -0
- package/template/test/framework/annotation/AnnotationDecorators.test.ts +222 -0
- package/template/test/framework/annotation/AnnotationExecutor.test.ts +246 -0
- package/template/test/framework/annotation/AnnotationProcessor.test.ts +179 -0
- package/template/test/framework/annotation/CustomProcessors.test.ts +313 -0
- package/template/test/framework/annotation/DefaultProcessors.test.ts +371 -0
- package/template/test/framework/annotation/NewRouter.test.ts +272 -0
- package/template/test/framework/annotation/ProcessorManager.test.ts +248 -0
- package/template/test/framework/annotation/setup.ts +26 -0
- package/template/test/framework/cache.test.ts +101 -0
- package/template/test/framework/databaseConfig.test.ts +142 -0
- package/template/test/integration/integration.test.ts +153 -0
- package/template/test/plugins/weboffice/http.routes.int.test.ts +61 -0
- package/template/test/service/business.test.ts +87 -0
- package/template/test/service/paramValidateTest.service.test.ts +184 -0
- package/template/test/service/ytUser.service.test.ts +566 -0
- package/template/test/setup.ts +20 -0
- package/template/test/setupAfterEnv.ts +14 -0
- package/template/test/utils/testHelpers.ts +220 -0
- package/template/test_output.txt +0 -0
- package/template/tsconfig.build.json +17 -0
- package/template/tsconfig.json +31 -0
- package/template/webpack.config.js +71 -0
- package/template/yarn.lock +7354 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type Router from "koa-router";
|
|
2
|
+
import { Provider } from "dp-ioc2";
|
|
3
|
+
import { WebofficeCallbackService } from "../services/webofficeCallback.service";
|
|
4
|
+
import { parseWebOfficeContext } from "../core/context";
|
|
5
|
+
import { OK, WebOfficeError, ErrInvalidArguments } from "../core/errors";
|
|
6
|
+
import { logger } from "@src/framework/utils/logger";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
|
|
10
|
+
const OK_CODE = OK;
|
|
11
|
+
|
|
12
|
+
function replySuccess(ctx: any, data: unknown): void {
|
|
13
|
+
ctx.status = 200;
|
|
14
|
+
ctx.body = { code: OK_CODE, data };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function replyError(ctx: any, err: unknown): void {
|
|
18
|
+
if (err instanceof WebOfficeError) {
|
|
19
|
+
ctx.status = err.statusCode;
|
|
20
|
+
ctx.body = { code: err.code, message: err.message };
|
|
21
|
+
} else {
|
|
22
|
+
logger.error("WebOffice callback error", err as Error);
|
|
23
|
+
ctx.status = 500;
|
|
24
|
+
ctx.body = { code: 50001, message: "internal error" };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function registerUnderPrefix(router: Router, basePath: string): void {
|
|
29
|
+
const prefix = basePath.replace(/\/$/, "");
|
|
30
|
+
const service = Provider(WebofficeCallbackService) as WebofficeCallbackService;
|
|
31
|
+
const base = prefix ? path.join(prefix, "v3/3rd").replace(/\\/g, "/") : "/v3/3rd";
|
|
32
|
+
|
|
33
|
+
router.post(`${base}/files`, async (ctx) => {
|
|
34
|
+
try {
|
|
35
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
36
|
+
const body = (ctx.request.body as any) || {};
|
|
37
|
+
const name = body.name || "未命名文档";
|
|
38
|
+
const projectId = body.project_id != null ? Number(body.project_id) : undefined;
|
|
39
|
+
const data = await service.createDocument(woCtx, name, projectId);
|
|
40
|
+
replySuccess(ctx, data);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
replyError(ctx, e);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
router.get(`${base}/files/:file_id`, async (ctx) => {
|
|
47
|
+
try {
|
|
48
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
49
|
+
const fileId = ctx.params.file_id;
|
|
50
|
+
const data = await service.getFile(woCtx, fileId);
|
|
51
|
+
replySuccess(ctx, data);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
replyError(ctx, e);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
router.get(`${base}/files/:file_id/download`, async (ctx) => {
|
|
58
|
+
try {
|
|
59
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
60
|
+
const fileId = ctx.params.file_id;
|
|
61
|
+
const data = await service.getFileDownload(woCtx, fileId);
|
|
62
|
+
replySuccess(ctx, data);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
replyError(ctx, e);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
router.get(`${base}/files/:file_id/permission`, async (ctx) => {
|
|
69
|
+
try {
|
|
70
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
71
|
+
const fileId = ctx.params.file_id;
|
|
72
|
+
const data = await service.getFilePermission(woCtx, fileId);
|
|
73
|
+
replySuccess(ctx, data);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
replyError(ctx, e);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
router.get(`${base}/users`, async (ctx) => {
|
|
80
|
+
try {
|
|
81
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
82
|
+
const userIds = Array.isArray(ctx.query.user_ids)
|
|
83
|
+
? (ctx.query.user_ids as string[])
|
|
84
|
+
: ctx.query.user_ids
|
|
85
|
+
? String(ctx.query.user_ids).split(",").filter(Boolean)
|
|
86
|
+
: [];
|
|
87
|
+
const data = await service.getUsers(woCtx, userIds);
|
|
88
|
+
replySuccess(ctx, data);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
replyError(ctx, e);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
router.get(`${base}/files/:file_id/watermark`, async (ctx) => {
|
|
95
|
+
try {
|
|
96
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
97
|
+
const fileId = ctx.params.file_id;
|
|
98
|
+
const data = await service.getFileWatermark(woCtx, fileId);
|
|
99
|
+
replySuccess(ctx, data);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
replyError(ctx, e);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
router.post(`${base}/files/:file_id/upload`, async (ctx) => {
|
|
106
|
+
try {
|
|
107
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
108
|
+
const fileId = ctx.params.file_id;
|
|
109
|
+
const files = (ctx.request as any).files;
|
|
110
|
+
const body = (ctx.request as any).body || {};
|
|
111
|
+
if (!files?.file) {
|
|
112
|
+
replyError(ctx, ErrInvalidArguments.withMessage("file required"));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const file = Array.isArray(files.file) ? files.file[0] : files.file;
|
|
116
|
+
const filePath = file.filepath ?? file.path;
|
|
117
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
118
|
+
replyError(ctx, ErrInvalidArguments.withMessage("file path invalid"));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const args = {
|
|
122
|
+
name: body.name ?? file.originalFilename ?? "file",
|
|
123
|
+
size: Number(body.size) || 0,
|
|
124
|
+
sha1: body.sha1 ?? "",
|
|
125
|
+
isManual: body.is_manual === "true" || body.is_manual === true,
|
|
126
|
+
filePath,
|
|
127
|
+
};
|
|
128
|
+
const data = await service.updateFile(woCtx, fileId, args);
|
|
129
|
+
replySuccess(ctx, data);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
replyError(ctx, e);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
router.get(`${base}/files/:file_id/versions`, async (ctx) => {
|
|
136
|
+
try {
|
|
137
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
138
|
+
const fileId = ctx.params.file_id;
|
|
139
|
+
const offset = Math.max(0, Number(ctx.query.offset) || 0);
|
|
140
|
+
const limit = Math.min(100, Math.max(1, Number(ctx.query.limit) || 20));
|
|
141
|
+
const data = await service.getFileVersions(woCtx, fileId, offset, limit);
|
|
142
|
+
replySuccess(ctx, data);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
replyError(ctx, e);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
router.get(`${base}/files/:file_id/versions/:version`, async (ctx) => {
|
|
149
|
+
try {
|
|
150
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
151
|
+
const fileId = ctx.params.file_id;
|
|
152
|
+
const version = Number(ctx.params.version) || 0;
|
|
153
|
+
const data = await service.getFileVersion(woCtx, fileId, version);
|
|
154
|
+
replySuccess(ctx, data);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
replyError(ctx, e);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
router.get(`${base}/files/:file_id/versions/:version/download`, async (ctx) => {
|
|
161
|
+
try {
|
|
162
|
+
const woCtx = parseWebOfficeContext(ctx);
|
|
163
|
+
const fileId = ctx.params.file_id;
|
|
164
|
+
const version = Number(ctx.params.version) || 0;
|
|
165
|
+
const data = await service.getFileVersionDownload(woCtx, fileId, version);
|
|
166
|
+
replySuccess(ctx, data);
|
|
167
|
+
} catch (e) {
|
|
168
|
+
replyError(ctx, e);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function registerWebOfficeRoutes(router: Router): void {
|
|
174
|
+
const prefixFromEnv = (process.env.WEBOFFICE_CALLBACK_PREFIX || "/weboffice").replace(/\/$/, "");
|
|
175
|
+
registerUnderPrefix(router, prefixFromEnv);
|
|
176
|
+
if (prefixFromEnv !== "") registerUnderPrefix(router, "");
|
|
177
|
+
logger.info('WebOffice routes registered (prefix: %s and /v3/3rd)', prefixFromEnv);
|
|
178
|
+
}
|
|
179
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type Router from "koa-router";
|
|
2
|
+
import type Koa from "koa";
|
|
3
|
+
import type { PluginDescriptor } from "@src/framework/plugins/types";
|
|
4
|
+
import { registerWebOfficeRoutes } from "./http/routes";
|
|
5
|
+
import { WebofficeFileEntity, WebofficeFileVersionEntity } from "./entities";
|
|
6
|
+
import { logger } from "@src/framework/utils/logger";
|
|
7
|
+
|
|
8
|
+
export const plugin: PluginDescriptor = {
|
|
9
|
+
id: "weboffice",
|
|
10
|
+
displayName: "WPS WebOffice 示例插件",
|
|
11
|
+
enabled: (env) => env.WEBOFFICE_ENABLED !== "0",
|
|
12
|
+
|
|
13
|
+
onBeforeBootstrap(_app: Koa) {
|
|
14
|
+
logger.info('Plugin "weboffice" before bootstrap');
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
registerRoutes(router: Router) {
|
|
18
|
+
registerWebOfficeRoutes(router);
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
entities: [WebofficeFileEntity, WebofficeFileVersionEntity],
|
|
22
|
+
};
|
|
23
|
+
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { Injectable } from "dp-ioc2";
|
|
2
|
+
import { BaseService } from "@src/service/base.service";
|
|
3
|
+
import { WebofficeFileEntity } from "../entities/webofficeFile.entity";
|
|
4
|
+
import { WebofficeFileVersionEntity } from "../entities/webofficeFileVersion.entity";
|
|
5
|
+
import {
|
|
6
|
+
GetFileReply,
|
|
7
|
+
GetFileDownloadReply,
|
|
8
|
+
GetFilePermissionReply,
|
|
9
|
+
WebOfficeUser,
|
|
10
|
+
GetWatermarkReply,
|
|
11
|
+
WebOfficeContext,
|
|
12
|
+
UpdateFile1PhaseArgs,
|
|
13
|
+
} from "../core/types";
|
|
14
|
+
import {
|
|
15
|
+
ErrFileNotExists,
|
|
16
|
+
ErrFileVersionNotExists,
|
|
17
|
+
ErrInvalidArguments,
|
|
18
|
+
ErrInternalError,
|
|
19
|
+
} from "../core/errors";
|
|
20
|
+
import { normalizeWpsUserId } from "../core/utils";
|
|
21
|
+
import { getFilePublicUrl, isCosConfigured, uploadToCos, webofficeCosKey } from "@src/libs/cos";
|
|
22
|
+
import { logger } from "@src/framework/utils/logger";
|
|
23
|
+
import fs from "fs";
|
|
24
|
+
import { v4 as uuidv4 } from "uuid";
|
|
25
|
+
|
|
26
|
+
@Injectable()
|
|
27
|
+
export class WebofficeCallbackService extends BaseService {
|
|
28
|
+
private generateFileId(): string {
|
|
29
|
+
return uuidv4().replace(/-/g, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async createDocument(
|
|
33
|
+
ctx: WebOfficeContext,
|
|
34
|
+
name: string,
|
|
35
|
+
projectId?: number
|
|
36
|
+
): Promise<{ file_id: string }> {
|
|
37
|
+
const fileRepo = this.getDataRepository(WebofficeFileEntity);
|
|
38
|
+
const versionRepo = this.getDataRepository(WebofficeFileVersionEntity);
|
|
39
|
+
const fileId = this.generateFileId();
|
|
40
|
+
const file = new WebofficeFileEntity();
|
|
41
|
+
file.fileId = fileId;
|
|
42
|
+
file.name = name || "未命名文档";
|
|
43
|
+
file.creatorId = ctx.token || "system";
|
|
44
|
+
file.modifierId = ctx.token || "system";
|
|
45
|
+
file.currentVersion = 1;
|
|
46
|
+
file.projectId = projectId ?? null;
|
|
47
|
+
await fileRepo.save(file);
|
|
48
|
+
const version = new WebofficeFileVersionEntity();
|
|
49
|
+
version.webofficeFileId = file.id;
|
|
50
|
+
version.version = 1;
|
|
51
|
+
version.size = 0;
|
|
52
|
+
version.digest = "";
|
|
53
|
+
version.cosKey = "";
|
|
54
|
+
version.creatorId = ctx.token || "system";
|
|
55
|
+
await versionRepo.save(version);
|
|
56
|
+
return { file_id: fileId };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async getFileAndCurrentVersion(
|
|
60
|
+
fileId: string
|
|
61
|
+
): Promise<{ file: WebofficeFileEntity; version: WebofficeFileVersionEntity } | null> {
|
|
62
|
+
const fileRepo = this.getDataRepository(WebofficeFileEntity);
|
|
63
|
+
const versionRepo = this.getDataRepository(WebofficeFileVersionEntity);
|
|
64
|
+
const file = await fileRepo.findOne({
|
|
65
|
+
where: { fileId },
|
|
66
|
+
relations: ["versions"],
|
|
67
|
+
});
|
|
68
|
+
if (!file) return null;
|
|
69
|
+
const version = await versionRepo.findOne({
|
|
70
|
+
where: { webofficeFileId: file.id, version: file.currentVersion },
|
|
71
|
+
});
|
|
72
|
+
if (!version) return null;
|
|
73
|
+
return { file, version };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private toGetFileReply(
|
|
77
|
+
file: WebofficeFileEntity,
|
|
78
|
+
version: WebofficeFileVersionEntity
|
|
79
|
+
): GetFileReply {
|
|
80
|
+
return {
|
|
81
|
+
id: file.fileId,
|
|
82
|
+
name: file.name,
|
|
83
|
+
size: Number(version.size),
|
|
84
|
+
version: file.currentVersion,
|
|
85
|
+
create_time: Math.floor((file.createDate?.getTime() ?? 0) / 1000),
|
|
86
|
+
modify_time: Math.floor((file.updateDate?.getTime() ?? 0) / 1000),
|
|
87
|
+
creator_id: normalizeWpsUserId(file.creatorId),
|
|
88
|
+
modifier_id: normalizeWpsUserId(file.modifierId),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async getFile(_ctx: WebOfficeContext, fileId: string): Promise<GetFileReply> {
|
|
93
|
+
const pair = await this.getFileAndCurrentVersion(fileId);
|
|
94
|
+
if (!pair) throw ErrFileNotExists;
|
|
95
|
+
return this.toGetFileReply(pair.file, pair.version);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getFileDownload(
|
|
99
|
+
_ctx: WebOfficeContext,
|
|
100
|
+
fileId: string
|
|
101
|
+
): Promise<GetFileDownloadReply> {
|
|
102
|
+
const pair = await this.getFileAndCurrentVersion(fileId);
|
|
103
|
+
if (!pair) throw ErrFileNotExists;
|
|
104
|
+
if (!pair.version.cosKey?.trim())
|
|
105
|
+
throw ErrFileNotExists.withMessage("file content not uploaded yet");
|
|
106
|
+
const url = getFilePublicUrl(pair.version.cosKey);
|
|
107
|
+
if (!url) throw ErrInternalError.withMessage("FILES_HOST not configured");
|
|
108
|
+
return {
|
|
109
|
+
url,
|
|
110
|
+
digest: pair.version.digest || "",
|
|
111
|
+
digest_type: "sha1",
|
|
112
|
+
headers: {},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async getFilePermission(
|
|
117
|
+
ctx: WebOfficeContext,
|
|
118
|
+
fileId: string
|
|
119
|
+
): Promise<GetFilePermissionReply> {
|
|
120
|
+
const pair = await this.getFileAndCurrentVersion(fileId);
|
|
121
|
+
if (!pair) throw ErrFileNotExists;
|
|
122
|
+
const userId = ctx.token || "anonymous";
|
|
123
|
+
const hasUser = !!ctx.token;
|
|
124
|
+
return {
|
|
125
|
+
user_id: normalizeWpsUserId(userId),
|
|
126
|
+
read: 1,
|
|
127
|
+
download: 1,
|
|
128
|
+
update: hasUser ? 1 : 0,
|
|
129
|
+
copy: 1,
|
|
130
|
+
print: 1,
|
|
131
|
+
saveas: 1,
|
|
132
|
+
comment: hasUser ? 1 : 0,
|
|
133
|
+
history: hasUser ? 1 : 0,
|
|
134
|
+
rename: 0,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 模板示例插件不依赖宿主业务用户表:对任意 user_id 返回占位用户信息。
|
|
140
|
+
* 若在真实业务中接入,请通过 plugin-api 或宿主 UserService 做映射。
|
|
141
|
+
*/
|
|
142
|
+
async getUsers(_ctx: WebOfficeContext, userIds: string[]): Promise<WebOfficeUser[]> {
|
|
143
|
+
return (userIds || []).filter(Boolean).map((id) => {
|
|
144
|
+
const norm = normalizeWpsUserId(id);
|
|
145
|
+
return {
|
|
146
|
+
id: norm,
|
|
147
|
+
name: norm,
|
|
148
|
+
avatar_url: "",
|
|
149
|
+
logined: id !== "anonymous",
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async getFileWatermark(
|
|
155
|
+
_ctx: WebOfficeContext,
|
|
156
|
+
_fileId: string
|
|
157
|
+
): Promise<GetWatermarkReply> {
|
|
158
|
+
return {
|
|
159
|
+
type: 1,
|
|
160
|
+
value: "weboffice",
|
|
161
|
+
fill_style: "rgba(192,192,192,0.6)",
|
|
162
|
+
font: "bold 20px Serif",
|
|
163
|
+
rotate: 0.5,
|
|
164
|
+
horizontal: 50,
|
|
165
|
+
vertical: 50,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async updateFile(
|
|
170
|
+
ctx: WebOfficeContext,
|
|
171
|
+
fileId: string,
|
|
172
|
+
args: UpdateFile1PhaseArgs
|
|
173
|
+
): Promise<GetFileReply> {
|
|
174
|
+
if (!isCosConfigured()) throw ErrInternalError.withMessage("COS not configured");
|
|
175
|
+
const fileRepo = this.getDataRepository(WebofficeFileEntity);
|
|
176
|
+
const versionRepo = this.getDataRepository(WebofficeFileVersionEntity);
|
|
177
|
+
const file = await fileRepo.findOne({ where: { fileId } });
|
|
178
|
+
if (!file) throw ErrFileNotExists;
|
|
179
|
+
const nextVersion = file.currentVersion + 1;
|
|
180
|
+
const cosKey = webofficeCosKey(fileId, nextVersion);
|
|
181
|
+
try {
|
|
182
|
+
await uploadToCos(args.filePath, cosKey);
|
|
183
|
+
} catch (e) {
|
|
184
|
+
logger.error("WebOffice COS upload failed", e as Error);
|
|
185
|
+
throw ErrInternalError.withMessage("Upload failed");
|
|
186
|
+
} finally {
|
|
187
|
+
try {
|
|
188
|
+
if (args.filePath && fs.existsSync(args.filePath)) {
|
|
189
|
+
fs.unlinkSync(args.filePath);
|
|
190
|
+
}
|
|
191
|
+
} catch (_) {}
|
|
192
|
+
}
|
|
193
|
+
const versionEntity = new WebofficeFileVersionEntity();
|
|
194
|
+
versionEntity.webofficeFileId = file.id;
|
|
195
|
+
versionEntity.version = nextVersion;
|
|
196
|
+
versionEntity.size = args.size;
|
|
197
|
+
versionEntity.digest = args.sha1 || "";
|
|
198
|
+
versionEntity.cosKey = cosKey;
|
|
199
|
+
versionEntity.creatorId = ctx.token || "system";
|
|
200
|
+
await versionRepo.save(versionEntity);
|
|
201
|
+
file.currentVersion = nextVersion;
|
|
202
|
+
file.modifierId = ctx.token || "system";
|
|
203
|
+
file.name = args.name || file.name;
|
|
204
|
+
file.updateDate = new Date();
|
|
205
|
+
await fileRepo.save(file);
|
|
206
|
+
const version = await versionRepo.findOne({
|
|
207
|
+
where: { webofficeFileId: file.id, version: nextVersion },
|
|
208
|
+
});
|
|
209
|
+
if (!version) throw ErrInternalError;
|
|
210
|
+
return this.toGetFileReply(file, version);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async renameFile(_ctx: WebOfficeContext, _fileId: string, _name: string): Promise<void> {
|
|
214
|
+
throw ErrInvalidArguments.withMessage("rename not implemented in template plugin");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async getFileVersions(
|
|
218
|
+
_ctx: WebOfficeContext,
|
|
219
|
+
fileId: string,
|
|
220
|
+
offset: number,
|
|
221
|
+
limit: number
|
|
222
|
+
): Promise<GetFileReply[]> {
|
|
223
|
+
const pair = await this.getFileAndCurrentVersion(fileId);
|
|
224
|
+
if (!pair) throw ErrFileNotExists;
|
|
225
|
+
const versionRepo = this.getDataRepository(WebofficeFileVersionEntity);
|
|
226
|
+
const versions = await versionRepo.find({
|
|
227
|
+
where: { webofficeFileId: pair.file.id },
|
|
228
|
+
order: { version: "DESC" },
|
|
229
|
+
skip: offset,
|
|
230
|
+
take: limit,
|
|
231
|
+
});
|
|
232
|
+
return versions.map((v) => this.toGetFileReply(pair.file, v));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async getFileVersion(
|
|
236
|
+
_ctx: WebOfficeContext,
|
|
237
|
+
fileId: string,
|
|
238
|
+
version: number
|
|
239
|
+
): Promise<GetFileReply> {
|
|
240
|
+
const fileRepo = this.getDataRepository(WebofficeFileEntity);
|
|
241
|
+
const versionRepo = this.getDataRepository(WebofficeFileVersionEntity);
|
|
242
|
+
const file = await fileRepo.findOne({ where: { fileId } });
|
|
243
|
+
if (!file) throw ErrFileNotExists;
|
|
244
|
+
const v = await versionRepo.findOne({
|
|
245
|
+
where: { webofficeFileId: file.id, version },
|
|
246
|
+
});
|
|
247
|
+
if (!v) throw ErrFileVersionNotExists;
|
|
248
|
+
return this.toGetFileReply(file, v);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async getFileVersionDownload(
|
|
252
|
+
_ctx: WebOfficeContext,
|
|
253
|
+
fileId: string,
|
|
254
|
+
version: number
|
|
255
|
+
): Promise<GetFileDownloadReply> {
|
|
256
|
+
const fileRepo = this.getDataRepository(WebofficeFileEntity);
|
|
257
|
+
const versionRepo = this.getDataRepository(WebofficeFileVersionEntity);
|
|
258
|
+
const file = await fileRepo.findOne({ where: { fileId } });
|
|
259
|
+
if (!file) throw ErrFileNotExists;
|
|
260
|
+
const v = await versionRepo.findOne({
|
|
261
|
+
where: { webofficeFileId: file.id, version },
|
|
262
|
+
});
|
|
263
|
+
if (!v) throw ErrFileVersionNotExists;
|
|
264
|
+
const url = getFilePublicUrl(v.cosKey);
|
|
265
|
+
if (!url) throw ErrInternalError.withMessage("FILES_HOST not configured");
|
|
266
|
+
return {
|
|
267
|
+
url,
|
|
268
|
+
digest: v.digest || "",
|
|
269
|
+
digest_type: "sha1",
|
|
270
|
+
headers: {},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Repository } from 'typeorm';
|
|
2
|
+
import { YtUserEntity } from '@src/entity/ytUser.entity';
|
|
3
|
+
import { BaseRepository } from '@src/repository/base/BaseRepository';
|
|
4
|
+
import { IBaseRepository } from '@src/repository/interfaces/IBaseRepository';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 用户Repository接口
|
|
8
|
+
* 定义用户相关的数据访问操作
|
|
9
|
+
*/
|
|
10
|
+
export interface IUserRepository extends IBaseRepository<YtUserEntity> {
|
|
11
|
+
findByEmail(email: string): Promise<YtUserEntity | null>;
|
|
12
|
+
findByTelNumber(telNumber: string): Promise<YtUserEntity | null>;
|
|
13
|
+
findByUserType(userType: string): Promise<YtUserEntity[]>;
|
|
14
|
+
findByStatus(status: string): Promise<YtUserEntity[]>;
|
|
15
|
+
findActiveUsers(): Promise<YtUserEntity[]>;
|
|
16
|
+
updateLastLoginTime(id: number): Promise<void>;
|
|
17
|
+
updatePassword(id: number, hashedPassword: string): Promise<void>;
|
|
18
|
+
deactivateUser(id: number): Promise<void>;
|
|
19
|
+
activateUser(id: number): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 用户Repository实现
|
|
24
|
+
*/
|
|
25
|
+
export class UserRepository extends BaseRepository<YtUserEntity> implements IUserRepository {
|
|
26
|
+
constructor(repository: Repository<YtUserEntity>) {
|
|
27
|
+
super(YtUserEntity, repository);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async findByEmail(email: string): Promise<YtUserEntity | null> {
|
|
31
|
+
try {
|
|
32
|
+
return await this.repository.findOne({
|
|
33
|
+
where: { email }
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async findByTelNumber(telNumber: string): Promise<YtUserEntity | null> {
|
|
41
|
+
try {
|
|
42
|
+
return await this.repository.findOne({
|
|
43
|
+
where: { telnumber: telNumber }
|
|
44
|
+
});
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async findByUserType(userType: string): Promise<YtUserEntity[]> {
|
|
51
|
+
try {
|
|
52
|
+
return await this.repository.find({
|
|
53
|
+
where: { userType: userType as any }
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async findByStatus(status: string): Promise<YtUserEntity[]> {
|
|
61
|
+
try {
|
|
62
|
+
return await this.repository.find({
|
|
63
|
+
where: { status: status as any }
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async findActiveUsers(): Promise<YtUserEntity[]> {
|
|
71
|
+
try {
|
|
72
|
+
return await this.repository.find({
|
|
73
|
+
where: { status: 'normal' as any }
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async updateLastLoginTime(id: number): Promise<void> {
|
|
81
|
+
try {
|
|
82
|
+
await this.repository.update(id, {
|
|
83
|
+
updateDate: new Date()
|
|
84
|
+
} as any);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async updatePassword(id: number, hashedPassword: string): Promise<void> {
|
|
91
|
+
try {
|
|
92
|
+
await this.repository.update(id, {
|
|
93
|
+
password: hashedPassword,
|
|
94
|
+
updateDate: new Date()
|
|
95
|
+
} as any);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async deactivateUser(id: number): Promise<void> {
|
|
102
|
+
try {
|
|
103
|
+
await this.repository.update(id, {
|
|
104
|
+
status: 'forbidden' as any,
|
|
105
|
+
updateDate: new Date()
|
|
106
|
+
} as any);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async activateUser(id: number): Promise<void> {
|
|
113
|
+
try {
|
|
114
|
+
await this.repository.update(id, {
|
|
115
|
+
status: 'normal' as any,
|
|
116
|
+
updateDate: new Date()
|
|
117
|
+
} as any);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|