diff --git a/README.md b/README.md index 21f7a0a..361d15c 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,8 @@ chmod +x /home/tms/scripts/tms.sh Open: - Swagger UI: http://localhost:8080/swagger-ui.html +- Internal API docs JSON: http://localhost:8080/v3/api-docs/internal-api +- Runtime-generated Swagger/OpenAPI docs are the source of truth for `/api/**`. - Internal health API: `GET /api/v1/device/status` - Internal sign preview API: `POST /api/v1/sign/preview` - External sign API: `POST /openapi/v1/sign/signature` diff --git a/pom.xml b/pom.xml index 9bd5405..0d27edd 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,22 @@ spring-boot-starter-test test + + + + org.bouncycastle + bcprov-jdk18on + 1.83 + + + + + + org.bouncycastle + bcpkix-jdk18on + 1.83 + + diff --git a/src/main/java/com/cisd/tms/common/config/OpenApiConfig.java b/src/main/java/com/cisd/tms/common/config/OpenApiConfig.java index d6f9fb0..bed22f5 100644 --- a/src/main/java/com/cisd/tms/common/config/OpenApiConfig.java +++ b/src/main/java/com/cisd/tms/common/config/OpenApiConfig.java @@ -16,7 +16,7 @@ public class OpenApiConfig { return new OpenAPI() .info(new Info() .title("CISD TMS API") - .description("CISD Terminal Management System API") + .description("CISD TMS 内部业务接口文档") .version("v0.0.1") .license(new License().name("Internal Use"))) .externalDocs(new ExternalDocumentation() diff --git a/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java b/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java index c8f1405..1f8a71a 100644 --- a/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java +++ b/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java @@ -5,6 +5,9 @@ import com.cisd.tms.modules.auth.dto.CurrentUserResponse; import com.cisd.tms.modules.auth.dto.LoginRequest; import com.cisd.tms.modules.auth.dto.LoginResponse; import com.cisd.tms.modules.auth.service.AuthService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -15,6 +18,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/auth") +@Tag(name = "认证鉴权", description = "内部用户登录与当前用户信息接口") public class AuthController { private final AuthService authService; @@ -24,12 +28,17 @@ public class AuthController { } @PostMapping("/login") + @Operation(summary = "内部用户登录", description = "校验用户名和密码,返回访问令牌及过期时间。") public ApiResponse login(@Valid @RequestBody LoginRequest request) { return ApiResponse.success(authService.login(request)); } @GetMapping("/me") - public ApiResponse me(@RequestHeader(value = "X-User", required = false) String user) { + @Operation(summary = "查询当前用户", description = "根据请求头中的用户标识返回当前登录用户信息。") + public ApiResponse me( + @Parameter(description = "当前用户标识,来自内部鉴权链路透传的 X-User 请求头") + @RequestHeader(value = "X-User", required = false) String user + ) { return ApiResponse.success(authService.me(user)); } } diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/CurrentUserResponse.java b/src/main/java/com/cisd/tms/modules/auth/dto/CurrentUserResponse.java index 976a6bf..3fa14e4 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/CurrentUserResponse.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/CurrentUserResponse.java @@ -1,9 +1,15 @@ package com.cisd.tms.modules.auth.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "当前登录用户信息") public class CurrentUserResponse { + @Schema(description = "用户名", example = "admin") private String username; + @Schema(description = "展示名称", example = "系统管理员") private String displayName; + @Schema(description = "角色标识", example = "ADMIN") private String role; public String getUsername() { diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/LoginRequest.java b/src/main/java/com/cisd/tms/modules/auth/dto/LoginRequest.java index da97d99..fcd0b4e 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/LoginRequest.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/LoginRequest.java @@ -1,13 +1,17 @@ package com.cisd.tms.modules.auth.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +@Schema(description = "内部登录请求") public class LoginRequest { @NotBlank(message = "username is required") + @Schema(description = "登录用户名", example = "admin") private String username; @NotBlank(message = "password is required") + @Schema(description = "登录密码", example = "P@ssw0rd") private String password; public String getUsername() { diff --git a/src/main/java/com/cisd/tms/modules/auth/dto/LoginResponse.java b/src/main/java/com/cisd/tms/modules/auth/dto/LoginResponse.java index 1341d1f..b5873f0 100644 --- a/src/main/java/com/cisd/tms/modules/auth/dto/LoginResponse.java +++ b/src/main/java/com/cisd/tms/modules/auth/dto/LoginResponse.java @@ -1,9 +1,15 @@ package com.cisd.tms.modules.auth.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "内部登录响应") public class LoginResponse { + @Schema(description = "登录用户名", example = "admin") private String username; + @Schema(description = "访问令牌") private String token; + @Schema(description = "令牌过期时间", example = "2026-03-11T18:00:00+08:00") private String expiresAt; public String getUsername() { diff --git a/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java b/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java index 42d5bf7..afde97a 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java @@ -25,6 +25,9 @@ import com.cisd.tms.integration.crypto.pcie.model.SymmetricCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.SymmetricKekCryptoRequest; import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult; import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotBlank; @@ -42,6 +45,7 @@ import org.springframework.web.bind.annotation.RestController; @Validated @RestController @RequestMapping("/api/v1/device/crypto") +@Tag(name = "密码卡调试", description = "密码卡设备调试、密钥操作和文件管理接口") public class CryptoCardController { private final PcieCryptoService pcieCryptoService; @@ -51,22 +55,29 @@ public class CryptoCardController { } @GetMapping("/device-count") + @Operation(summary = "查询密码卡数量", description = "返回当前环境可见的密码卡设备数量。") public ApiResponse getDeviceCount() { return ApiResponse.success(pcieCryptoService.getDeviceCount()); } @GetMapping("/device-conf") + @Operation(summary = "查询密码卡配置", description = "返回密码卡底层驱动和配置快照。") public ApiResponse getDeviceConf() { return ApiResponse.success(pcieCryptoService.getDeviceConf()); } @GetMapping("/device-info") + @Operation(summary = "查询密码卡信息", description = "返回密码卡设备详细信息。") public ApiResponse getDeviceInfo() { return ApiResponse.success(pcieCryptoService.getDeviceInfo()); } @GetMapping("/random") - public ApiResponse> random(@RequestParam(defaultValue = "16") @Min(1) int length) { + @Operation(summary = "生成随机数", description = "使用密码卡生成指定长度的随机数并以 Base64 返回。") + public ApiResponse> random( + @Parameter(description = "随机数字节长度,默认 16") + @RequestParam(defaultValue = "16") @Min(1) int length + ) { byte[] random = pcieCryptoService.generateRandom(length); return ApiResponse.success(Map.of( "length", random.length, @@ -75,12 +86,14 @@ public class CryptoCardController { } @PostMapping("/self-test") + @Operation(summary = "执行密码卡自检", description = "触发密码卡自检,自检通过时返回 passed=true。") public ApiResponse> selfTest() { pcieCryptoService.deviceSelfTest(); return ApiResponse.success(Map.of("passed", true)); } @PostMapping("/hmac") + @Operation(summary = "计算 HMAC", description = "使用给定算法、密钥和数据在密码卡内完成 HMAC 计算。") public ApiResponse> hmac(@Valid @RequestBody CryptoHmacInternalRequest request) { HmacRequest hmacRequest = new HmacRequest(); hmacRequest.setAlgId(request.getAlgId()); @@ -95,6 +108,7 @@ public class CryptoCardController { } @PostMapping("/digest") + @Operation(summary = "计算摘要", description = "使用密码卡对输入数据执行摘要运算。") public ApiResponse> digest(@Valid @RequestBody CryptoDigestInternalRequest request) { DigestRequest digestRequest = new DigestRequest(); digestRequest.setAlgId(request.getAlgId()); @@ -108,6 +122,7 @@ public class CryptoCardController { } @PostMapping("/encrypt/plain") + @Operation(summary = "明文密钥加密", description = "使用明文对称密钥在密码卡内完成加密。") public ApiResponse> encryptPlain(@Valid @RequestBody CryptoSymmetricInternalRequest request) { SymmetricCryptoRequest cryptoRequest = toSymmetricCryptoRequest(request); byte[] data = pcieCryptoService.encryptWithPlainKey(cryptoRequest).getData(); @@ -118,6 +133,7 @@ public class CryptoCardController { } @PostMapping("/decrypt/plain") + @Operation(summary = "明文密钥解密", description = "使用明文对称密钥在密码卡内完成解密。") public ApiResponse> decryptPlain(@Valid @RequestBody CryptoSymmetricInternalRequest request) { SymmetricCryptoRequest cryptoRequest = toSymmetricCryptoRequest(request); byte[] data = pcieCryptoService.decryptWithPlainKey(cryptoRequest).getData(); @@ -128,6 +144,7 @@ public class CryptoCardController { } @PostMapping("/encrypt/kek") + @Operation(summary = "KEK 包裹密钥加密", description = "使用 KEK 包裹密钥在密码卡内完成加密。") public ApiResponse> encryptKek(@Valid @RequestBody CryptoSymmetricKekInternalRequest request) { SymmetricKekCryptoRequest cryptoRequest = toSymmetricKekCryptoRequest(request); byte[] data = pcieCryptoService.encryptWithKekWrappedKey(cryptoRequest).getData(); @@ -138,6 +155,7 @@ public class CryptoCardController { } @PostMapping("/decrypt/kek") + @Operation(summary = "KEK 包裹密钥解密", description = "使用 KEK 包裹密钥在密码卡内完成解密。") public ApiResponse> decryptKek(@Valid @RequestBody CryptoSymmetricKekInternalRequest request) { SymmetricKekCryptoRequest cryptoRequest = toSymmetricKekCryptoRequest(request); byte[] data = pcieCryptoService.decryptWithKekWrappedKey(cryptoRequest).getData(); @@ -148,6 +166,7 @@ public class CryptoCardController { } @PostMapping("/mac/plain") + @Operation(summary = "计算 MAC", description = "使用明文对称密钥在密码卡内完成 MAC 计算。") public ApiResponse> macPlain(@Valid @RequestBody CryptoMacInternalRequest request) { MacCalcRequest macRequest = new MacCalcRequest(); macRequest.setAlgId(request.getAlgId()); @@ -163,6 +182,7 @@ public class CryptoCardController { } @PostMapping("/file/create") + @Operation(summary = "创建密码卡文件", description = "在密码卡内创建指定名称和容量的文件。") public ApiResponse createFile(@Valid @RequestBody CryptoFileCreateInternalRequest request) { FileCreateRequest fileCreateRequest = new FileCreateRequest(); fileCreateRequest.setFileName(request.getFileName()); @@ -172,6 +192,7 @@ public class CryptoCardController { } @PostMapping("/file/write") + @Operation(summary = "写入密码卡文件", description = "向密码卡文件的指定偏移位置写入 Base64 数据。") public ApiResponse writeFile(@Valid @RequestBody CryptoFileWriteInternalRequest request) { FileWriteRequest fileWriteRequest = new FileWriteRequest(); fileWriteRequest.setFileName(request.getFileName()); @@ -182,6 +203,7 @@ public class CryptoCardController { } @PostMapping("/file/read") + @Operation(summary = "读取密码卡文件", description = "从密码卡文件指定偏移位置读取指定长度的数据。") public ApiResponse> readFile(@Valid @RequestBody CryptoFileReadInternalRequest request) { FileReadRequest fileReadRequest = new FileReadRequest(); fileReadRequest.setFileName(request.getFileName()); @@ -195,12 +217,14 @@ public class CryptoCardController { } @PostMapping("/file/delete") + @Operation(summary = "删除密码卡文件", description = "删除密码卡内指定文件。") public ApiResponse deleteFile(@Valid @RequestBody CryptoFileDeleteInternalRequest request) { pcieCryptoService.deleteFile(request.getFileName()); return ApiResponse.success(); } @PostMapping("/kek/generate") + @Operation(summary = "生成 KEK", description = "在密码卡指定索引处生成 KEK 密钥。") public ApiResponse generateKek(@Valid @RequestBody CryptoGenerateKekInternalRequest request) { GenerateKekRequest generateKekRequest = new GenerateKekRequest(); generateKekRequest.setKeyBits(request.getKeyBits()); @@ -210,11 +234,16 @@ public class CryptoCardController { } @GetMapping("/kek/status") - public ApiResponse kekStatus(@RequestParam @Min(0) int keyIndex) { + @Operation(summary = "查询 KEK 状态", description = "返回指定 KEK 索引位置的密钥状态。") + public ApiResponse kekStatus( + @Parameter(description = "KEK 索引,从 0 开始") + @RequestParam @Min(0) int keyIndex + ) { return ApiResponse.success(pcieCryptoService.statusKek(keyIndex)); } @GetMapping("/lmk/seed-mac") + @Operation(summary = "导出 LMK Seed MAC", description = "导出当前 LMK Seed 的 MAC 值。") public ApiResponse> exportLmkSeedMac() { byte[] mac = pcieCryptoService.exportLmkSeedMac(); return ApiResponse.success(Map.of( @@ -224,11 +253,13 @@ public class CryptoCardController { } @GetMapping("/lmk/check") + @Operation(summary = "校验 LMK", description = "检查密码卡中的 LMK 是否有效。") public ApiResponse checkLmk() { return ApiResponse.success(pcieCryptoService.checkLmk()); } @PostMapping("/std/keypair/rsa") + @Operation(summary = "生成标准 RSA 密钥对", description = "在密码卡内生成标准 RSA 密钥对并返回公私钥。") public ApiResponse> generateStandardRsaKeyPair( @Valid @RequestBody CryptoGenerateStandardRsaKeyPairInternalRequest request ) { @@ -246,6 +277,7 @@ public class CryptoCardController { } @PostMapping("/std/keypair/ecc") + @Operation(summary = "生成标准 ECC 密钥对", description = "在密码卡内生成标准 ECC 密钥对并返回公私钥。") public ApiResponse> generateStandardEccKeyPair( @Valid @RequestBody CryptoGenerateStandardEccKeyPairInternalRequest request ) { @@ -264,6 +296,7 @@ public class CryptoCardController { } @PostMapping("/envelope/exchange/rsa") + @Operation(summary = "RSA 数字信封转换", description = "基于 RSA 公钥对输入数字信封执行转换。") public ApiResponse> exchangeEnvelopeRsa( @Valid @RequestBody CryptoExchangeEnvelopeRsaInternalRequest request ) { @@ -280,6 +313,7 @@ public class CryptoCardController { } @PostMapping("/envelope/exchange/ecc") + @Operation(summary = "ECC 数字信封转换", description = "基于 ECC 公钥对输入数字信封执行转换。") public ApiResponse> exchangeEnvelopeEcc( @Valid @RequestBody CryptoExchangeEnvelopeEccInternalRequest request ) { @@ -297,6 +331,7 @@ public class CryptoCardController { } @PostMapping("/agreement/data-key/ecc") + @Operation(summary = "生成 ECC 协商数据", description = "生成 ECC 协商过程中的响应方公钥和临时公钥数据。") public ApiResponse> generateAgreementDataAndKeyEcc( @Valid @RequestBody CryptoGenerateAgreementDataAndKeyEccInternalRequest request ) { @@ -320,6 +355,7 @@ public class CryptoCardController { } @PostMapping("/agreement/session-key/ecc") + @Operation(summary = "生成 ECC 会话密钥", description = "根据协商双方参数生成 ECC 会话密钥及赞助方公钥数据。") public ApiResponse> generateSessionKeyEcc( @Valid @RequestBody CryptoGenerateSessionKeyEccInternalRequest request ) { diff --git a/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java b/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java index 6e4f03a..03ad44e 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java @@ -4,6 +4,9 @@ import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.modules.device.dto.DeviceActionResponse; import com.cisd.tms.modules.device.dto.DeviceInfoResponse; import com.cisd.tms.modules.device.service.DeviceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; @@ -12,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/device") +@Tag(name = "设备概览", description = "设备基础信息、状态和重启接口") public class DeviceController { private final DeviceService deviceService; @@ -21,17 +25,23 @@ public class DeviceController { } @GetMapping("/info") + @Operation(summary = "查询设备信息", description = "返回设备标识、版本和预置产品类型等基础信息。") public ApiResponse info() { return ApiResponse.success(deviceService.info()); } @GetMapping("/status") + @Operation(summary = "查询设备状态", description = "返回当前设备运行状态与版本快照。") public ApiResponse status() { return ApiResponse.success(deviceService.info()); } @PostMapping("/restart") - public ApiResponse restart(@RequestHeader(value = "X-User", required = false) String operator) { + @Operation(summary = "重启设备服务", description = "受理设备重启请求,并记录触发操作的内部用户。") + public ApiResponse restart( + @Parameter(description = "触发重启的操作人,来自内部鉴权链路透传的 X-User 请求头") + @RequestHeader(value = "X-User", required = false) String operator + ) { return ApiResponse.success(deviceService.restart(operator)); } } diff --git a/src/main/java/com/cisd/tms/modules/device/dto/DeviceActionResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/DeviceActionResponse.java index 7a9ec26..e0aeb37 100644 --- a/src/main/java/com/cisd/tms/modules/device/dto/DeviceActionResponse.java +++ b/src/main/java/com/cisd/tms/modules/device/dto/DeviceActionResponse.java @@ -1,10 +1,17 @@ package com.cisd.tms.modules.device.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "设备动作受理响应") public class DeviceActionResponse { + @Schema(description = "受理动作名称", example = "restart") private String action; + @Schema(description = "是否已受理", example = "true") private boolean accepted; + @Schema(description = "操作人", example = "admin") private String operator; + @Schema(description = "受理时间", example = "2026-03-11T18:00:00+08:00") private String acceptedAt; public String getAction() { diff --git a/src/main/java/com/cisd/tms/modules/device/dto/DeviceInfoResponse.java b/src/main/java/com/cisd/tms/modules/device/dto/DeviceInfoResponse.java index bcb5a9f..4067158 100644 --- a/src/main/java/com/cisd/tms/modules/device/dto/DeviceInfoResponse.java +++ b/src/main/java/com/cisd/tms/modules/device/dto/DeviceInfoResponse.java @@ -1,12 +1,21 @@ package com.cisd.tms.modules.device.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "设备信息响应") public class DeviceInfoResponse { + @Schema(description = "设备唯一标识", example = "TMS-DEVICE-01") private String deviceId; + @Schema(description = "设备状态", example = "UP") private String status; + @Schema(description = "设备版本", example = "1.0.0") private String version; + @Schema(description = "预置产品类型", example = "STANDARD") private String presetProductType; + @Schema(description = "预置版本", example = "6.6.4") private String presetVersion; + @Schema(description = "预置来源", example = "application.yml") private String presetSource; public String getDeviceId() { diff --git a/src/main/java/com/cisd/tms/modules/file/controller/FileController.java b/src/main/java/com/cisd/tms/modules/file/controller/FileController.java index d3162ff..7ee866e 100644 --- a/src/main/java/com/cisd/tms/modules/file/controller/FileController.java +++ b/src/main/java/com/cisd/tms/modules/file/controller/FileController.java @@ -4,6 +4,9 @@ import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.modules.file.dto.FileDetailResponse; import com.cisd.tms.modules.file.dto.FileUploadResponse; import com.cisd.tms.modules.file.service.FileService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -14,6 +17,7 @@ import org.springframework.web.multipart.MultipartFile; @RestController @RequestMapping("/api/v1/files") +@Tag(name = "文件管理", description = "初始化文件上传与文件元数据查询接口") public class FileController { private final FileService fileService; @@ -24,13 +28,21 @@ public class FileController { // 内部上传入口,前端先拿到 fileId,再把 fileId 传给初始化相关接口。 @PostMapping("/upload") - public ApiResponse upload(@RequestParam("file") MultipartFile file) { + @Operation(summary = "上传初始化文件", description = "上传许可证、配置包等初始化依赖文件,返回 fileId 供后续任务引用。") + public ApiResponse upload( + @Parameter(description = "待上传的初始化文件") + @RequestParam("file") MultipartFile file + ) { return ApiResponse.success(fileService.upload(file)); } // 返回已存储的文件元数据,便于调用方确认 fileId 到实际文件的映射关系。 @GetMapping("/{fileId}") - public ApiResponse detail(@PathVariable String fileId) { + @Operation(summary = "查询文件详情", description = "根据 fileId 返回已存储文件的元数据和当前状态。") + public ApiResponse detail( + @Parameter(description = "上传接口返回的文件唯一标识") + @PathVariable String fileId + ) { return ApiResponse.success(fileService.getDetail(fileId)); } } diff --git a/src/main/java/com/cisd/tms/modules/file/dto/FileDetailResponse.java b/src/main/java/com/cisd/tms/modules/file/dto/FileDetailResponse.java index 31bd680..d9a0f76 100644 --- a/src/main/java/com/cisd/tms/modules/file/dto/FileDetailResponse.java +++ b/src/main/java/com/cisd/tms/modules/file/dto/FileDetailResponse.java @@ -1,11 +1,19 @@ package com.cisd.tms.modules.file.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "文件详情响应") public class FileDetailResponse { + @Schema(description = "文件唯一标识", example = "FILE-20260310-000001") private String fileId; + @Schema(description = "原始文件名", example = "license.dat") private String originalFilename; + @Schema(description = "文件实际存储路径", example = "/home/tms/uploads/FILE-20260310-000001/license.dat") private String storagePath; + @Schema(description = "文件状态", example = "ACTIVE") private String status; + @Schema(description = "当前文件大小,单位字节", example = "12") private long size; public String getFileId() { diff --git a/src/main/java/com/cisd/tms/modules/file/dto/FileUploadResponse.java b/src/main/java/com/cisd/tms/modules/file/dto/FileUploadResponse.java index 4b67dbf..63ef032 100644 --- a/src/main/java/com/cisd/tms/modules/file/dto/FileUploadResponse.java +++ b/src/main/java/com/cisd/tms/modules/file/dto/FileUploadResponse.java @@ -1,9 +1,15 @@ package com.cisd.tms.modules.file.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "文件上传响应") public class FileUploadResponse { + @Schema(description = "文件唯一标识", example = "FILE-20260310-000001") private String fileId; + @Schema(description = "文件大小,单位字节", example = "12") private long size; + @Schema(description = "文件实际存储路径", example = "/home/tms/uploads/FILE-20260310-000001/license.dat") private String storagePath; public String getFileId() { diff --git a/src/main/java/com/cisd/tms/modules/init/controller/InitController.java b/src/main/java/com/cisd/tms/modules/init/controller/InitController.java index f318a81..25959c8 100644 --- a/src/main/java/com/cisd/tms/modules/init/controller/InitController.java +++ b/src/main/java/com/cisd/tms/modules/init/controller/InitController.java @@ -10,6 +10,9 @@ import com.cisd.tms.modules.init.dto.InitTaskExecuteResponse; import com.cisd.tms.modules.init.dto.InitTaskStepLogResponse; import com.cisd.tms.modules.init.dto.InitTaskStepResponse; import com.cisd.tms.modules.init.service.InitService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.util.List; import org.springframework.web.bind.annotation.GetMapping; @@ -22,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/init") +@Tag(name = "CISD初始化任务", description = "初始化模板、预检、任务创建、执行与日志查询接口") public class InitController { private final InitService initService; @@ -35,7 +39,11 @@ public class InitController { * 前端据此识别后端预制版本,并渲染对应版本的初始化页面。 */ @GetMapping("/template") - public ApiResponse template(@RequestParam(required = false) String version) { + @Operation(summary = "查询初始化模板", description = "返回初始化模板版本、预制产品类型和模板路径等元信息。") + public ApiResponse template( + @Parameter(description = "模板版本,不传时返回默认预制版本") + @RequestParam(required = false) String version + ) { return ApiResponse.success(initService.loadTemplate(version)); } @@ -43,6 +51,7 @@ public class InitController { * 初始化预检:仅做参数校验和步骤数量预估,不落库。 */ @PostMapping("/preview") + @Operation(summary = "预检初始化请求", description = "校验初始化参数并返回解析后的产品类型与预估步骤数量,不落库。") public ApiResponse preview(@Valid @RequestBody InitPreviewRequest request) { return ApiResponse.success(initService.preview(request)); } @@ -51,17 +60,26 @@ public class InitController { * 创建初始化任务:写入任务主表和步骤表,状态初始化为 PENDING。 */ @PostMapping("/tasks") + @Operation(summary = "创建初始化任务", description = "根据初始化请求生成任务和步骤记录,任务初始状态为 PENDING。") public ApiResponse createTask(@Valid @RequestBody InitPreviewRequest request) { return ApiResponse.success(initService.createTask(request)); } @GetMapping("/tasks/{taskId}") - public ApiResponse taskDetail(@PathVariable String taskId) { + @Operation(summary = "查询任务详情", description = "返回初始化任务的主状态、产品类型和原始计划快照。") + public ApiResponse taskDetail( + @Parameter(description = "初始化任务唯一标识") + @PathVariable String taskId + ) { return ApiResponse.success(initService.getTask(taskId)); } @GetMapping("/tasks/{taskId}/steps") - public ApiResponse> taskSteps(@PathVariable String taskId) { + @Operation(summary = "查询任务步骤", description = "返回初始化任务下全部步骤的执行状态和命令信息。") + public ApiResponse> taskSteps( + @Parameter(description = "初始化任务唯一标识") + @PathVariable String taskId + ) { return ApiResponse.success(initService.getTaskSteps(taskId)); } @@ -69,7 +87,13 @@ public class InitController { * 下载单步骤执行日志,用于页面定位失败原因。 */ @GetMapping("/tasks/{taskId}/steps/{stepNo}/log") - public ApiResponse taskStepLog(@PathVariable String taskId, @PathVariable int stepNo) { + @Operation(summary = "查询步骤日志", description = "返回指定任务步骤的日志路径和日志内容,用于定位失败原因。") + public ApiResponse taskStepLog( + @Parameter(description = "初始化任务唯一标识") + @PathVariable String taskId, + @Parameter(description = "任务步骤序号,从 1 开始") + @PathVariable int stepNo + ) { return ApiResponse.success(initService.getTaskStepLog(taskId, stepNo)); } @@ -78,7 +102,11 @@ public class InitController { * 接口只负责启动后台执行并立即返回,前端需继续轮询任务详情、步骤状态和步骤日志。 */ @PostMapping("/tasks/{taskId}/execute") - public ApiResponse executeTask(@PathVariable String taskId) { + @Operation(summary = "执行初始化任务", description = "异步受理初始化任务执行请求,立即返回当前任务快照,实际步骤在后台继续运行。") + public ApiResponse executeTask( + @Parameter(description = "初始化任务唯一标识") + @PathVariable String taskId + ) { return ApiResponse.success(initService.executeTask(taskId)); } } diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitCreateTaskResponse.java b/src/main/java/com/cisd/tms/modules/init/dto/InitCreateTaskResponse.java index 44856ee..86c2ebb 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitCreateTaskResponse.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitCreateTaskResponse.java @@ -1,10 +1,17 @@ package com.cisd.tms.modules.init.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "初始化任务创建响应") public class InitCreateTaskResponse { + @Schema(description = "初始化任务唯一标识", example = "INIT-20260310-000001") private String taskId; + @Schema(description = "任务状态", example = "PENDING") private String status; + @Schema(description = "解析后的产品类型", example = "STANDARD") private String resolvedProductType; + @Schema(description = "命中的预制版本", example = "6.6.4") private String presetVersion; public String getTaskId() { diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitPlanTemplateResponse.java b/src/main/java/com/cisd/tms/modules/init/dto/InitPlanTemplateResponse.java index 3af5612..06debb8 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitPlanTemplateResponse.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitPlanTemplateResponse.java @@ -1,12 +1,18 @@ package com.cisd.tms.modules.init.dto; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; +@Schema(description = "初始化模板元信息") public class InitPlanTemplateResponse { + @Schema(description = "预制产品类型", example = "STANDARD") private String presetProductType; + @Schema(description = "预制模板版本", example = "6.6.4") private String presetVersion; + @Schema(description = "支持的模板版本列表") private List supportedVersions; + @Schema(description = "模板文件路径", example = "classpath:initplan/standard-6.6.4.json") private String templatePath; public String getPresetProductType() { diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java b/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java index 3c5a4f6..d5e1597 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewRequest.java @@ -1,5 +1,6 @@ package com.cisd.tms.modules.init.dto; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; @@ -8,6 +9,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; +@Schema(description = "初始化预检与任务创建请求") public class InitPreviewRequest { private static final String IPV4_REGEX = @@ -16,39 +18,49 @@ public class InitPreviewRequest { @NotBlank(message = "orgCodeType is required") @Pattern(regexp = "BIC|CIPSID|LEI", message = "orgCodeType must be BIC/CIPSID/LEI") + @Schema(description = "机构代码类型", example = "BIC") private String orgCodeType; @NotBlank(message = "orgCode is required") @Size(max = 32, message = "orgCode length must be <= 32") + @Schema(description = "机构代码", example = "BKCHCNBJXXX") private String orgCode; @NotBlank(message = "orgNameCn is required") @Size(max = 128, message = "orgNameCn length must be <= 128") + @Schema(description = "机构中文名称", example = "中国银行") private String orgNameCn; @Size(max = 256, message = "orgNameEn length must be <= 256") + @Schema(description = "机构英文名称", example = "Bank of China") private String orgNameEn; @NotBlank(message = "deployMode is required") @Pattern(regexp = "SINGLE|DUAL|QUAD", message = "deployMode must be SINGLE/DUAL/QUAD") + @Schema(description = "部署模式", example = "DUAL") private String deployMode; @Valid @NotNull(message = "nodes is required") + @Schema(description = "节点 IP 配置") private Nodes nodes; @Valid @NotNull(message = "mq is required") + @Schema(description = "消息中间件配置") private Mq mq; @Valid @NotNull(message = "licenses is required") + @Schema(description = "初始化许可证与配置包引用") private Licenses licenses; @Valid + @Schema(description = "签名服务配置,直参版按需填写") private SignServer signServer; @Valid + @Schema(description = "直连 TLQ 配置,直参版按需填写") private DirectTlqConfig directTlq; public String getOrgCodeType() { @@ -131,19 +143,24 @@ public class InitPreviewRequest { this.directTlq = directTlq; } + @Schema(description = "节点 IP 配置") public static class Nodes { @NotBlank(message = "nodes.node01Ip is required") @Pattern(regexp = IPV4_REGEX, message = "nodes.node01Ip must be a valid IPv4") + @Schema(description = "节点 1 IP", example = "192.168.1.11") private String node01Ip; @Pattern(regexp = IPV4_REGEX, message = "nodes.node02Ip must be a valid IPv4") + @Schema(description = "节点 2 IP", example = "192.168.1.12") private String node02Ip; @Pattern(regexp = IPV4_REGEX, message = "nodes.node03Ip must be a valid IPv4") + @Schema(description = "节点 3 IP", example = "192.168.1.13") private String node03Ip; @Pattern(regexp = IPV4_REGEX, message = "nodes.node04Ip must be a valid IPv4") + @Schema(description = "节点 4 IP", example = "192.168.1.14") private String node04Ip; public String getNode01Ip() { @@ -179,25 +196,31 @@ public class InitPreviewRequest { } } + @Schema(description = "消息中间件配置") public static class Mq { @NotBlank(message = "mq.mqType is required") + @Schema(description = "消息中间件类型", example = "RABBITMQ") private String mqType; @NotBlank(message = "mq.channelUsername is required") @Size(max = 32, message = "mq.channelUsername length must be <= 32") + @Schema(description = "通道用户名", example = "tms") private String channelUsername; @NotBlank(message = "mq.channelPassword is required") @Size(max = 128, message = "mq.channelPassword length must be <= 128") + @Schema(description = "通道密码") private String channelPassword; @Size(max = 128, message = "mq.tlqLicenseFileId length must be <= 128") @Pattern(regexp = FILE_ID_REGEX, message = "mq.tlqLicenseFileId must be a fileId") + @Schema(description = "TLQ 许可证文件 fileId", example = "FILE-20260310-000001") private String tlqLicenseFileId; @Size(max = 128, message = "mq.cfmqConfigFileId length must be <= 128") @Pattern(regexp = FILE_ID_REGEX, message = "mq.cfmqConfigFileId must be a fileId") + @Schema(description = "CFMQ 配置文件 fileId", example = "FILE-20260310-000002") private String cfmqConfigFileId; public String getMqType() { @@ -241,15 +264,18 @@ public class InitPreviewRequest { } } + @Schema(description = "许可证和配置包引用") public static class Licenses { @NotBlank(message = "licenses.receiverLicenseFileId is required") @Size(max = 128, message = "licenses.receiverLicenseFileId length must be <= 128") @Pattern(regexp = FILE_ID_REGEX, message = "licenses.receiverLicenseFileId must be a fileId") + @Schema(description = "接收端许可证文件 fileId", example = "FILE-20260310-000003") private String receiverLicenseFileId; @Size(max = 128, message = "licenses.cfgZipFileId length must be <= 128") @Pattern(regexp = FILE_ID_REGEX, message = "licenses.cfgZipFileId must be a fileId") + @Schema(description = "标准版配置包 zip 文件 fileId", example = "FILE-20260310-000004") private String cfgZipFileId; public String getReceiverLicenseFileId() { @@ -269,16 +295,20 @@ public class InitPreviewRequest { } } + @Schema(description = "签名服务配置") public static class SignServer { @Size(max = 64, message = "signServer.signHost length must be <= 64") + @Schema(description = "签名服务地址", example = "10.0.0.20") private String signHost; @NotNull(message = "signServer.signPort must not be null") @Max(value = 65535, message = "signServer.signPort must be <= 65535") + @Schema(description = "签名服务端口", example = "8088") private Integer signPort; @Pattern(regexp = "Infosec|CFCA", message = "signServer.signType must be Infosec/CFCA") + @Schema(description = "签名服务厂商类型", example = "CFCA") private String signType; @Size(max = 512, message = "signServer.signCertDn length must be <= 512") @@ -339,16 +369,20 @@ public class InitPreviewRequest { } } + @Schema(description = "直连 TLQ 配置") public static class DirectTlqConfig { @Pattern(regexp = "A|B", message = "directTlq.localNodeSeq must be A/B") + @Schema(description = "本节点序号", example = "A") private String localNodeSeq; @Size(max = 256, message = "directTlq.upstreamInstitutions length must be <= 256") + @Schema(description = "上游机构列表", example = "BANKA,BANKB") private String upstreamInstitutions; @Min(value = 1, message = "directTlq.upstreamCocCount must be >= 1") @Max(value = 2, message = "directTlq.upstreamCocCount must be <= 2") + @Schema(description = "上游 COC 数量", example = "2") private Integer upstreamCocCount; public String getLocalNodeSeq() { diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewResponse.java b/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewResponse.java index 5809383..589db75 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewResponse.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitPreviewResponse.java @@ -1,10 +1,17 @@ package com.cisd.tms.modules.init.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "初始化预检响应") public class InitPreviewResponse { + @Schema(description = "解析后的产品类型", example = "STANDARD") private String resolvedProductType; + @Schema(description = "命中的预制版本", example = "6.6.4") private String presetVersion; + @Schema(description = "预计执行步骤数", example = "18") private int stepCount; + @Schema(description = "预检摘要说明") private String summary; public String getResolvedProductType() { diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitTaskDetailResponse.java b/src/main/java/com/cisd/tms/modules/init/dto/InitTaskDetailResponse.java index 913df5e..e672467 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitTaskDetailResponse.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitTaskDetailResponse.java @@ -1,14 +1,25 @@ package com.cisd.tms.modules.init.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "初始化任务详情") public class InitTaskDetailResponse { + @Schema(description = "初始化任务唯一标识", example = "INIT-20260310-000001") private String taskId; + @Schema(description = "任务状态", example = "RUNNING") private String status; + @Schema(description = "解析后的产品类型", example = "STANDARD") private String resolvedProductType; + @Schema(description = "命中的预制版本", example = "6.6.4") private String presetVersion; + @Schema(description = "机构代码", example = "BKCHCNBJXXX") private String orgCode; + @Schema(description = "消息中间件类型", example = "RABBITMQ") private String mqType; + @Schema(description = "部署模式", example = "DUAL") private String deployMode; + @Schema(description = "初始化计划原始 JSON 快照") private String initPlanJson; public String getTaskId() { diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitTaskExecuteResponse.java b/src/main/java/com/cisd/tms/modules/init/dto/InitTaskExecuteResponse.java index e249ba5..eb5a703 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitTaskExecuteResponse.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitTaskExecuteResponse.java @@ -1,10 +1,17 @@ package com.cisd.tms.modules.init.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "初始化任务执行受理响应") public class InitTaskExecuteResponse { + @Schema(description = "初始化任务唯一标识", example = "INIT-20260310-000001") private String taskId; + @Schema(description = "任务当前状态", example = "RUNNING") private String status; + @Schema(description = "总步骤数", example = "18") private int totalSteps; + @Schema(description = "已成功步骤数", example = "6") private int successSteps; public String getTaskId() { diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitTaskStepLogResponse.java b/src/main/java/com/cisd/tms/modules/init/dto/InitTaskStepLogResponse.java index 4a80da5..8d209a1 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitTaskStepLogResponse.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitTaskStepLogResponse.java @@ -1,11 +1,19 @@ package com.cisd.tms.modules.init.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "初始化步骤日志详情") public class InitTaskStepLogResponse { + @Schema(description = "初始化任务唯一标识", example = "INIT-20260310-000001") private String taskId; + @Schema(description = "步骤序号", example = "3") private int stepNo; + @Schema(description = "步骤编码", example = "DB_APPLY") private String stepCode; + @Schema(description = "日志文件路径") private String logPath; + @Schema(description = "日志内容") private String content; public String getTaskId() { diff --git a/src/main/java/com/cisd/tms/modules/init/dto/InitTaskStepResponse.java b/src/main/java/com/cisd/tms/modules/init/dto/InitTaskStepResponse.java index a32290c..0adff18 100644 --- a/src/main/java/com/cisd/tms/modules/init/dto/InitTaskStepResponse.java +++ b/src/main/java/com/cisd/tms/modules/init/dto/InitTaskStepResponse.java @@ -1,13 +1,23 @@ package com.cisd.tms.modules.init.dto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "初始化任务步骤状态") public class InitTaskStepResponse { + @Schema(description = "步骤序号", example = "1") private int stepNo; + @Schema(description = "步骤编码", example = "DB_APPLY") private String stepCode; + @Schema(description = "步骤状态", example = "SUCCESS") private String status; + @Schema(description = "步骤执行摘要") private String message; + @Schema(description = "执行命令行") private String commandLine; + @Schema(description = "步骤日志路径") private String logPath; + @Schema(description = "命令退出码", example = "0") private Integer exitCode; public int getStepNo() { diff --git a/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java b/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java index d890e53..f489868 100644 --- a/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java +++ b/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java @@ -11,6 +11,8 @@ import com.cisd.tms.modules.mk.enums.MasterKeyStatus; import com.cisd.tms.modules.mk.service.LmkService; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -30,6 +32,7 @@ import java.util.List; */ @RestController @RequestMapping("/api/v1") +@Tag(name = "主密钥管理", description = "LMK 生成、恢复、状态查询和销毁接口") public class LmkController { @Resource @@ -42,6 +45,7 @@ public class LmkController { * @return */ @PostMapping("/masterkey/generate") + @Operation(summary = "生成主密钥", description = "生成新的主密钥并返回密钥信息。") public ApiResponse generateMasterKey() { LMK lmk = lmkService.generateLMK(); return ApiResponse.success(lmk); @@ -53,6 +57,7 @@ public class LmkController { * @return */ @PostMapping("/masterkey/uKeySignAndKeyComponent") + @Operation(summary = "UKey 签名并获取密钥分量", description = "根据 UKey 签名结果导出内部密钥公钥并返回主密钥分量。") public ApiResponse signAndKeyComponent(@RequestBody UKeySignDTO uKeySignDTO) { String iPubKey = lmkService.exportIkPublicKeyHex(); String payload; @@ -68,6 +73,7 @@ public class LmkController { } @GetMapping("/masterKeyStatus") + @Operation(summary = "查询主密钥状态", description = "返回主密钥当前是否存在以及相关状态详情。") public ApiResponse getMasterKeyStatus() { return ApiResponse.success(lmkService.getMasterKeyStatus()); } @@ -80,6 +86,7 @@ public class LmkController { * @throws ClassNotFoundException */ @PostMapping("/recoveryMasterKey") + @Operation(summary = "恢复主密钥", description = "根据上传的分量或序列化载荷恢复主密钥,并在需要时重建内部密钥。") public ApiResponse recoveryMasterKey(@RequestBody MasterKeyRestoreDTO masterKeyRestoreDTO) throws IOException, ClassNotFoundException { RecoveryResult result; @@ -103,6 +110,7 @@ public class LmkController { * @return */ @PostMapping("/masterKeyRecovery") + @Operation(summary = "分量合成恢复主密钥", description = "根据主密钥分量直接合成并恢复主密钥。") public ApiResponse masterKeyRecovery(@RequestBody MasterKeyRestoreDTO masterKeyRestoreDTO) { String masterKey = lmkService.masterKeyCompose(masterKeyRestoreDTO.getComponents()); LMK lmk = lmkService.recoveryLMK1(LMK.getInstance(masterKey, masterKeyRestoreDTO)); @@ -110,6 +118,7 @@ public class LmkController { } @GetMapping("/destroyLMKAndIK") + @Operation(summary = "销毁主密钥和内部密钥", description = "销毁当前主密钥以及相关内部密钥材料。") public ApiResponse destroyLMKAndIK() { lmkService.destroyLMKAndIK(); return ApiResponse.success("主密钥和内部密钥销毁成功!"); diff --git a/src/main/java/com/cisd/tms/modules/sign/controller/SignController.java b/src/main/java/com/cisd/tms/modules/sign/controller/SignController.java index 43d6feb..7ce61dc 100644 --- a/src/main/java/com/cisd/tms/modules/sign/controller/SignController.java +++ b/src/main/java/com/cisd/tms/modules/sign/controller/SignController.java @@ -6,6 +6,8 @@ import com.cisd.tms.modules.sign.dto.internal.InternalSignPreviewResponse; import com.cisd.tms.modules.sign.service.SignService; import com.cisd.tms.modules.sign.support.SignCommand; import com.cisd.tms.modules.sign.support.SignResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -14,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/sign") +@Tag(name = "签名预览", description = "内部签名预览与摘要计算接口") public class SignController { private final SignService signService; @@ -23,6 +26,7 @@ public class SignController { } @PostMapping("/preview") + @Operation(summary = "预览签名摘要", description = "根据明文和算法生成摘要预览结果,不落库。") public ApiResponse preview(@Valid @RequestBody InternalSignPreviewRequest request) { SignCommand command = new SignCommand(); command.setAlgorithm(request.getAlgorithm()); diff --git a/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewRequest.java b/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewRequest.java index a4f2065..0c9edf3 100644 --- a/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewRequest.java +++ b/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewRequest.java @@ -1,12 +1,16 @@ package com.cisd.tms.modules.sign.dto.internal; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +@Schema(description = "内部签名预览请求") public class InternalSignPreviewRequest { @NotBlank(message = "plainText is required") + @Schema(description = "待签名明文", example = "hello-tms") private String plainText; + @Schema(description = "签名算法,不传时使用默认算法", example = "SHA256withRSA") private String algorithm; public String getPlainText() { diff --git a/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewResponse.java b/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewResponse.java index 4c932b8..7231103 100644 --- a/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewResponse.java +++ b/src/main/java/com/cisd/tms/modules/sign/dto/internal/InternalSignPreviewResponse.java @@ -1,8 +1,13 @@ package com.cisd.tms.modules.sign.dto.internal; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(description = "内部签名预览响应") public class InternalSignPreviewResponse { + @Schema(description = "实际使用的签名算法", example = "SHA256withRSA") private String algorithm; + @Schema(description = "摘要十六进制串") private String digestHex; public String getAlgorithm() { diff --git a/src/main/java/com/cisd/tms/modules/system/controller/HealthController.java b/src/main/java/com/cisd/tms/modules/system/controller/HealthController.java index c98fdfb..30d742b 100644 --- a/src/main/java/com/cisd/tms/modules/system/controller/HealthController.java +++ b/src/main/java/com/cisd/tms/modules/system/controller/HealthController.java @@ -1,6 +1,8 @@ package com.cisd.tms.modules.system.controller; import com.cisd.tms.common.api.ApiResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Map; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -8,9 +10,11 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/api/v1/system") +@Tag(name = "系统状态", description = "服务健康检查接口") public class HealthController { @GetMapping("/health") + @Operation(summary = "查询服务健康状态", description = "返回当前服务是否可用以及服务名称等基础健康信息。") public ApiResponse> status() { return ApiResponse.success(Map.of( "status", "UP", diff --git a/src/test/java/com/cisd/tms/TmsApplicationTests.java b/src/test/java/com/cisd/tms/TmsApplicationTests.java index 0760680..bd20ed4 100644 --- a/src/test/java/com/cisd/tms/TmsApplicationTests.java +++ b/src/test/java/com/cisd/tms/TmsApplicationTests.java @@ -1,12 +1,36 @@ package com.cisd.tms; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest +@AutoConfigureMockMvc class TmsApplicationTests { + @Autowired + private MockMvc mockMvc; + @Test void contextLoads() { } + + @Test + void shouldGenerateAnnotatedInternalApiDocs() throws Exception { + mockMvc.perform(get("/v3/api-docs/internal-api")) + .andExpect(status().isOk()) + .andExpect(content().string(org.hamcrest.Matchers.containsString("\"description\":\"CISD TMS 内部业务接口文档\""))) + .andExpect(content().string(org.hamcrest.Matchers.containsString("\"/api/v1/init/template\""))) + .andExpect(content().string(org.hamcrest.Matchers.containsString("\"summary\":\"查询初始化模板\""))) + .andExpect(content().string(org.hamcrest.Matchers.containsString("\"/api/v1/files/upload\""))) + .andExpect(content().string(org.hamcrest.Matchers.containsString("\"summary\":\"上传初始化文件\""))) + .andExpect(content().string(org.hamcrest.Matchers.containsString("\"/api/v1/auth/login\""))) + .andExpect(content().string(org.hamcrest.Matchers.containsString("\"summary\":\"内部用户登录\""))); + } }