feat:补充接口注释

This commit is contained in:
waner 2026-03-11 15:26:33 +08:00
parent a00991f49d
commit bf8ec7054c
29 changed files with 311 additions and 12 deletions

View File

@ -63,6 +63,8 @@ chmod +x /home/tms/scripts/tms.sh
Open: Open:
- Swagger UI: http://localhost:8080/swagger-ui.html - 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 health API: `GET /api/v1/device/status`
- Internal sign preview API: `POST /api/v1/sign/preview` - Internal sign preview API: `POST /api/v1/sign/preview`
- External sign API: `POST /openapi/v1/sign/signature` - External sign API: `POST /openapi/v1/sign/signature`

16
pom.xml
View File

@ -79,6 +79,22 @@
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- Source: https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk18on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.83</version>
<!-- <scope>compile</scope>-->
</dependency>
<!-- Source: https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.83</version>
<!-- <scope>compile</scope>-->
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -16,7 +16,7 @@ public class OpenApiConfig {
return new OpenAPI() return new OpenAPI()
.info(new Info() .info(new Info()
.title("CISD TMS API") .title("CISD TMS API")
.description("CISD Terminal Management System API") .description("CISD TMS 内部业务接口文档")
.version("v0.0.1") .version("v0.0.1")
.license(new License().name("Internal Use"))) .license(new License().name("Internal Use")))
.externalDocs(new ExternalDocumentation() .externalDocs(new ExternalDocumentation()

View File

@ -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.LoginRequest;
import com.cisd.tms.modules.auth.dto.LoginResponse; import com.cisd.tms.modules.auth.dto.LoginResponse;
import com.cisd.tms.modules.auth.service.AuthService; 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 jakarta.validation.Valid;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -15,6 +18,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/api/v1/auth") @RequestMapping("/api/v1/auth")
@Tag(name = "认证鉴权", description = "内部用户登录与当前用户信息接口")
public class AuthController { public class AuthController {
private final AuthService authService; private final AuthService authService;
@ -24,12 +28,17 @@ public class AuthController {
} }
@PostMapping("/login") @PostMapping("/login")
@Operation(summary = "内部用户登录", description = "校验用户名和密码,返回访问令牌及过期时间。")
public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) { public ApiResponse<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
return ApiResponse.success(authService.login(request)); return ApiResponse.success(authService.login(request));
} }
@GetMapping("/me") @GetMapping("/me")
public ApiResponse<CurrentUserResponse> me(@RequestHeader(value = "X-User", required = false) String user) { @Operation(summary = "查询当前用户", description = "根据请求头中的用户标识返回当前登录用户信息。")
public ApiResponse<CurrentUserResponse> me(
@Parameter(description = "当前用户标识,来自内部鉴权链路透传的 X-User 请求头")
@RequestHeader(value = "X-User", required = false) String user
) {
return ApiResponse.success(authService.me(user)); return ApiResponse.success(authService.me(user));
} }
} }

View File

@ -1,9 +1,15 @@
package com.cisd.tms.modules.auth.dto; package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "当前登录用户信息")
public class CurrentUserResponse { public class CurrentUserResponse {
@Schema(description = "用户名", example = "admin")
private String username; private String username;
@Schema(description = "展示名称", example = "系统管理员")
private String displayName; private String displayName;
@Schema(description = "角色标识", example = "ADMIN")
private String role; private String role;
public String getUsername() { public String getUsername() {

View File

@ -1,13 +1,17 @@
package com.cisd.tms.modules.auth.dto; package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@Schema(description = "内部登录请求")
public class LoginRequest { public class LoginRequest {
@NotBlank(message = "username is required") @NotBlank(message = "username is required")
@Schema(description = "登录用户名", example = "admin")
private String username; private String username;
@NotBlank(message = "password is required") @NotBlank(message = "password is required")
@Schema(description = "登录密码", example = "P@ssw0rd")
private String password; private String password;
public String getUsername() { public String getUsername() {

View File

@ -1,9 +1,15 @@
package com.cisd.tms.modules.auth.dto; package com.cisd.tms.modules.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "内部登录响应")
public class LoginResponse { public class LoginResponse {
@Schema(description = "登录用户名", example = "admin")
private String username; private String username;
@Schema(description = "访问令牌")
private String token; private String token;
@Schema(description = "令牌过期时间", example = "2026-03-11T18:00:00+08:00")
private String expiresAt; private String expiresAt;
public String getUsername() { public String getUsername() {

View File

@ -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.SymmetricKekCryptoRequest;
import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult; import com.cisd.tms.integration.crypto.pcie.model.StandardKeyPairResult;
import com.cisd.tms.integration.crypto.pcie.service.PcieCryptoService; 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.Valid;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@ -42,6 +45,7 @@ import org.springframework.web.bind.annotation.RestController;
@Validated @Validated
@RestController @RestController
@RequestMapping("/api/v1/device/crypto") @RequestMapping("/api/v1/device/crypto")
@Tag(name = "密码卡调试", description = "密码卡设备调试、密钥操作和文件管理接口")
public class CryptoCardController { public class CryptoCardController {
private final PcieCryptoService pcieCryptoService; private final PcieCryptoService pcieCryptoService;
@ -51,22 +55,29 @@ public class CryptoCardController {
} }
@GetMapping("/device-count") @GetMapping("/device-count")
@Operation(summary = "查询密码卡数量", description = "返回当前环境可见的密码卡设备数量。")
public ApiResponse<DeviceCountResult> getDeviceCount() { public ApiResponse<DeviceCountResult> getDeviceCount() {
return ApiResponse.success(pcieCryptoService.getDeviceCount()); return ApiResponse.success(pcieCryptoService.getDeviceCount());
} }
@GetMapping("/device-conf") @GetMapping("/device-conf")
@Operation(summary = "查询密码卡配置", description = "返回密码卡底层驱动和配置快照。")
public ApiResponse<DeviceConfResult> getDeviceConf() { public ApiResponse<DeviceConfResult> getDeviceConf() {
return ApiResponse.success(pcieCryptoService.getDeviceConf()); return ApiResponse.success(pcieCryptoService.getDeviceConf());
} }
@GetMapping("/device-info") @GetMapping("/device-info")
@Operation(summary = "查询密码卡信息", description = "返回密码卡设备详细信息。")
public ApiResponse<DeviceInfoResult> getDeviceInfo() { public ApiResponse<DeviceInfoResult> getDeviceInfo() {
return ApiResponse.success(pcieCryptoService.getDeviceInfo()); return ApiResponse.success(pcieCryptoService.getDeviceInfo());
} }
@GetMapping("/random") @GetMapping("/random")
public ApiResponse<Map<String, Object>> random(@RequestParam(defaultValue = "16") @Min(1) int length) { @Operation(summary = "生成随机数", description = "使用密码卡生成指定长度的随机数并以 Base64 返回。")
public ApiResponse<Map<String, Object>> random(
@Parameter(description = "随机数字节长度,默认 16")
@RequestParam(defaultValue = "16") @Min(1) int length
) {
byte[] random = pcieCryptoService.generateRandom(length); byte[] random = pcieCryptoService.generateRandom(length);
return ApiResponse.success(Map.of( return ApiResponse.success(Map.of(
"length", random.length, "length", random.length,
@ -75,12 +86,14 @@ public class CryptoCardController {
} }
@PostMapping("/self-test") @PostMapping("/self-test")
@Operation(summary = "执行密码卡自检", description = "触发密码卡自检,自检通过时返回 passed=true。")
public ApiResponse<Map<String, Object>> selfTest() { public ApiResponse<Map<String, Object>> selfTest() {
pcieCryptoService.deviceSelfTest(); pcieCryptoService.deviceSelfTest();
return ApiResponse.success(Map.of("passed", true)); return ApiResponse.success(Map.of("passed", true));
} }
@PostMapping("/hmac") @PostMapping("/hmac")
@Operation(summary = "计算 HMAC", description = "使用给定算法、密钥和数据在密码卡内完成 HMAC 计算。")
public ApiResponse<Map<String, Object>> hmac(@Valid @RequestBody CryptoHmacInternalRequest request) { public ApiResponse<Map<String, Object>> hmac(@Valid @RequestBody CryptoHmacInternalRequest request) {
HmacRequest hmacRequest = new HmacRequest(); HmacRequest hmacRequest = new HmacRequest();
hmacRequest.setAlgId(request.getAlgId()); hmacRequest.setAlgId(request.getAlgId());
@ -95,6 +108,7 @@ public class CryptoCardController {
} }
@PostMapping("/digest") @PostMapping("/digest")
@Operation(summary = "计算摘要", description = "使用密码卡对输入数据执行摘要运算。")
public ApiResponse<Map<String, Object>> digest(@Valid @RequestBody CryptoDigestInternalRequest request) { public ApiResponse<Map<String, Object>> digest(@Valid @RequestBody CryptoDigestInternalRequest request) {
DigestRequest digestRequest = new DigestRequest(); DigestRequest digestRequest = new DigestRequest();
digestRequest.setAlgId(request.getAlgId()); digestRequest.setAlgId(request.getAlgId());
@ -108,6 +122,7 @@ public class CryptoCardController {
} }
@PostMapping("/encrypt/plain") @PostMapping("/encrypt/plain")
@Operation(summary = "明文密钥加密", description = "使用明文对称密钥在密码卡内完成加密。")
public ApiResponse<Map<String, Object>> encryptPlain(@Valid @RequestBody CryptoSymmetricInternalRequest request) { public ApiResponse<Map<String, Object>> encryptPlain(@Valid @RequestBody CryptoSymmetricInternalRequest request) {
SymmetricCryptoRequest cryptoRequest = toSymmetricCryptoRequest(request); SymmetricCryptoRequest cryptoRequest = toSymmetricCryptoRequest(request);
byte[] data = pcieCryptoService.encryptWithPlainKey(cryptoRequest).getData(); byte[] data = pcieCryptoService.encryptWithPlainKey(cryptoRequest).getData();
@ -118,6 +133,7 @@ public class CryptoCardController {
} }
@PostMapping("/decrypt/plain") @PostMapping("/decrypt/plain")
@Operation(summary = "明文密钥解密", description = "使用明文对称密钥在密码卡内完成解密。")
public ApiResponse<Map<String, Object>> decryptPlain(@Valid @RequestBody CryptoSymmetricInternalRequest request) { public ApiResponse<Map<String, Object>> decryptPlain(@Valid @RequestBody CryptoSymmetricInternalRequest request) {
SymmetricCryptoRequest cryptoRequest = toSymmetricCryptoRequest(request); SymmetricCryptoRequest cryptoRequest = toSymmetricCryptoRequest(request);
byte[] data = pcieCryptoService.decryptWithPlainKey(cryptoRequest).getData(); byte[] data = pcieCryptoService.decryptWithPlainKey(cryptoRequest).getData();
@ -128,6 +144,7 @@ public class CryptoCardController {
} }
@PostMapping("/encrypt/kek") @PostMapping("/encrypt/kek")
@Operation(summary = "KEK 包裹密钥加密", description = "使用 KEK 包裹密钥在密码卡内完成加密。")
public ApiResponse<Map<String, Object>> encryptKek(@Valid @RequestBody CryptoSymmetricKekInternalRequest request) { public ApiResponse<Map<String, Object>> encryptKek(@Valid @RequestBody CryptoSymmetricKekInternalRequest request) {
SymmetricKekCryptoRequest cryptoRequest = toSymmetricKekCryptoRequest(request); SymmetricKekCryptoRequest cryptoRequest = toSymmetricKekCryptoRequest(request);
byte[] data = pcieCryptoService.encryptWithKekWrappedKey(cryptoRequest).getData(); byte[] data = pcieCryptoService.encryptWithKekWrappedKey(cryptoRequest).getData();
@ -138,6 +155,7 @@ public class CryptoCardController {
} }
@PostMapping("/decrypt/kek") @PostMapping("/decrypt/kek")
@Operation(summary = "KEK 包裹密钥解密", description = "使用 KEK 包裹密钥在密码卡内完成解密。")
public ApiResponse<Map<String, Object>> decryptKek(@Valid @RequestBody CryptoSymmetricKekInternalRequest request) { public ApiResponse<Map<String, Object>> decryptKek(@Valid @RequestBody CryptoSymmetricKekInternalRequest request) {
SymmetricKekCryptoRequest cryptoRequest = toSymmetricKekCryptoRequest(request); SymmetricKekCryptoRequest cryptoRequest = toSymmetricKekCryptoRequest(request);
byte[] data = pcieCryptoService.decryptWithKekWrappedKey(cryptoRequest).getData(); byte[] data = pcieCryptoService.decryptWithKekWrappedKey(cryptoRequest).getData();
@ -148,6 +166,7 @@ public class CryptoCardController {
} }
@PostMapping("/mac/plain") @PostMapping("/mac/plain")
@Operation(summary = "计算 MAC", description = "使用明文对称密钥在密码卡内完成 MAC 计算。")
public ApiResponse<Map<String, Object>> macPlain(@Valid @RequestBody CryptoMacInternalRequest request) { public ApiResponse<Map<String, Object>> macPlain(@Valid @RequestBody CryptoMacInternalRequest request) {
MacCalcRequest macRequest = new MacCalcRequest(); MacCalcRequest macRequest = new MacCalcRequest();
macRequest.setAlgId(request.getAlgId()); macRequest.setAlgId(request.getAlgId());
@ -163,6 +182,7 @@ public class CryptoCardController {
} }
@PostMapping("/file/create") @PostMapping("/file/create")
@Operation(summary = "创建密码卡文件", description = "在密码卡内创建指定名称和容量的文件。")
public ApiResponse<Void> createFile(@Valid @RequestBody CryptoFileCreateInternalRequest request) { public ApiResponse<Void> createFile(@Valid @RequestBody CryptoFileCreateInternalRequest request) {
FileCreateRequest fileCreateRequest = new FileCreateRequest(); FileCreateRequest fileCreateRequest = new FileCreateRequest();
fileCreateRequest.setFileName(request.getFileName()); fileCreateRequest.setFileName(request.getFileName());
@ -172,6 +192,7 @@ public class CryptoCardController {
} }
@PostMapping("/file/write") @PostMapping("/file/write")
@Operation(summary = "写入密码卡文件", description = "向密码卡文件的指定偏移位置写入 Base64 数据。")
public ApiResponse<Void> writeFile(@Valid @RequestBody CryptoFileWriteInternalRequest request) { public ApiResponse<Void> writeFile(@Valid @RequestBody CryptoFileWriteInternalRequest request) {
FileWriteRequest fileWriteRequest = new FileWriteRequest(); FileWriteRequest fileWriteRequest = new FileWriteRequest();
fileWriteRequest.setFileName(request.getFileName()); fileWriteRequest.setFileName(request.getFileName());
@ -182,6 +203,7 @@ public class CryptoCardController {
} }
@PostMapping("/file/read") @PostMapping("/file/read")
@Operation(summary = "读取密码卡文件", description = "从密码卡文件指定偏移位置读取指定长度的数据。")
public ApiResponse<Map<String, Object>> readFile(@Valid @RequestBody CryptoFileReadInternalRequest request) { public ApiResponse<Map<String, Object>> readFile(@Valid @RequestBody CryptoFileReadInternalRequest request) {
FileReadRequest fileReadRequest = new FileReadRequest(); FileReadRequest fileReadRequest = new FileReadRequest();
fileReadRequest.setFileName(request.getFileName()); fileReadRequest.setFileName(request.getFileName());
@ -195,12 +217,14 @@ public class CryptoCardController {
} }
@PostMapping("/file/delete") @PostMapping("/file/delete")
@Operation(summary = "删除密码卡文件", description = "删除密码卡内指定文件。")
public ApiResponse<Void> deleteFile(@Valid @RequestBody CryptoFileDeleteInternalRequest request) { public ApiResponse<Void> deleteFile(@Valid @RequestBody CryptoFileDeleteInternalRequest request) {
pcieCryptoService.deleteFile(request.getFileName()); pcieCryptoService.deleteFile(request.getFileName());
return ApiResponse.success(); return ApiResponse.success();
} }
@PostMapping("/kek/generate") @PostMapping("/kek/generate")
@Operation(summary = "生成 KEK", description = "在密码卡指定索引处生成 KEK 密钥。")
public ApiResponse<Void> generateKek(@Valid @RequestBody CryptoGenerateKekInternalRequest request) { public ApiResponse<Void> generateKek(@Valid @RequestBody CryptoGenerateKekInternalRequest request) {
GenerateKekRequest generateKekRequest = new GenerateKekRequest(); GenerateKekRequest generateKekRequest = new GenerateKekRequest();
generateKekRequest.setKeyBits(request.getKeyBits()); generateKekRequest.setKeyBits(request.getKeyBits());
@ -210,11 +234,16 @@ public class CryptoCardController {
} }
@GetMapping("/kek/status") @GetMapping("/kek/status")
public ApiResponse<KeyStatusResult> kekStatus(@RequestParam @Min(0) int keyIndex) { @Operation(summary = "查询 KEK 状态", description = "返回指定 KEK 索引位置的密钥状态。")
public ApiResponse<KeyStatusResult> kekStatus(
@Parameter(description = "KEK 索引,从 0 开始")
@RequestParam @Min(0) int keyIndex
) {
return ApiResponse.success(pcieCryptoService.statusKek(keyIndex)); return ApiResponse.success(pcieCryptoService.statusKek(keyIndex));
} }
@GetMapping("/lmk/seed-mac") @GetMapping("/lmk/seed-mac")
@Operation(summary = "导出 LMK Seed MAC", description = "导出当前 LMK Seed 的 MAC 值。")
public ApiResponse<Map<String, Object>> exportLmkSeedMac() { public ApiResponse<Map<String, Object>> exportLmkSeedMac() {
byte[] mac = pcieCryptoService.exportLmkSeedMac(); byte[] mac = pcieCryptoService.exportLmkSeedMac();
return ApiResponse.success(Map.of( return ApiResponse.success(Map.of(
@ -224,11 +253,13 @@ public class CryptoCardController {
} }
@GetMapping("/lmk/check") @GetMapping("/lmk/check")
@Operation(summary = "校验 LMK", description = "检查密码卡中的 LMK 是否有效。")
public ApiResponse<Boolean> checkLmk() { public ApiResponse<Boolean> checkLmk() {
return ApiResponse.success(pcieCryptoService.checkLmk()); return ApiResponse.success(pcieCryptoService.checkLmk());
} }
@PostMapping("/std/keypair/rsa") @PostMapping("/std/keypair/rsa")
@Operation(summary = "生成标准 RSA 密钥对", description = "在密码卡内生成标准 RSA 密钥对并返回公私钥。")
public ApiResponse<Map<String, Object>> generateStandardRsaKeyPair( public ApiResponse<Map<String, Object>> generateStandardRsaKeyPair(
@Valid @RequestBody CryptoGenerateStandardRsaKeyPairInternalRequest request @Valid @RequestBody CryptoGenerateStandardRsaKeyPairInternalRequest request
) { ) {
@ -246,6 +277,7 @@ public class CryptoCardController {
} }
@PostMapping("/std/keypair/ecc") @PostMapping("/std/keypair/ecc")
@Operation(summary = "生成标准 ECC 密钥对", description = "在密码卡内生成标准 ECC 密钥对并返回公私钥。")
public ApiResponse<Map<String, Object>> generateStandardEccKeyPair( public ApiResponse<Map<String, Object>> generateStandardEccKeyPair(
@Valid @RequestBody CryptoGenerateStandardEccKeyPairInternalRequest request @Valid @RequestBody CryptoGenerateStandardEccKeyPairInternalRequest request
) { ) {
@ -264,6 +296,7 @@ public class CryptoCardController {
} }
@PostMapping("/envelope/exchange/rsa") @PostMapping("/envelope/exchange/rsa")
@Operation(summary = "RSA 数字信封转换", description = "基于 RSA 公钥对输入数字信封执行转换。")
public ApiResponse<Map<String, Object>> exchangeEnvelopeRsa( public ApiResponse<Map<String, Object>> exchangeEnvelopeRsa(
@Valid @RequestBody CryptoExchangeEnvelopeRsaInternalRequest request @Valid @RequestBody CryptoExchangeEnvelopeRsaInternalRequest request
) { ) {
@ -280,6 +313,7 @@ public class CryptoCardController {
} }
@PostMapping("/envelope/exchange/ecc") @PostMapping("/envelope/exchange/ecc")
@Operation(summary = "ECC 数字信封转换", description = "基于 ECC 公钥对输入数字信封执行转换。")
public ApiResponse<Map<String, Object>> exchangeEnvelopeEcc( public ApiResponse<Map<String, Object>> exchangeEnvelopeEcc(
@Valid @RequestBody CryptoExchangeEnvelopeEccInternalRequest request @Valid @RequestBody CryptoExchangeEnvelopeEccInternalRequest request
) { ) {
@ -297,6 +331,7 @@ public class CryptoCardController {
} }
@PostMapping("/agreement/data-key/ecc") @PostMapping("/agreement/data-key/ecc")
@Operation(summary = "生成 ECC 协商数据", description = "生成 ECC 协商过程中的响应方公钥和临时公钥数据。")
public ApiResponse<Map<String, Object>> generateAgreementDataAndKeyEcc( public ApiResponse<Map<String, Object>> generateAgreementDataAndKeyEcc(
@Valid @RequestBody CryptoGenerateAgreementDataAndKeyEccInternalRequest request @Valid @RequestBody CryptoGenerateAgreementDataAndKeyEccInternalRequest request
) { ) {
@ -320,6 +355,7 @@ public class CryptoCardController {
} }
@PostMapping("/agreement/session-key/ecc") @PostMapping("/agreement/session-key/ecc")
@Operation(summary = "生成 ECC 会话密钥", description = "根据协商双方参数生成 ECC 会话密钥及赞助方公钥数据。")
public ApiResponse<Map<String, Object>> generateSessionKeyEcc( public ApiResponse<Map<String, Object>> generateSessionKeyEcc(
@Valid @RequestBody CryptoGenerateSessionKeyEccInternalRequest request @Valid @RequestBody CryptoGenerateSessionKeyEccInternalRequest request
) { ) {

View File

@ -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.DeviceActionResponse;
import com.cisd.tms.modules.device.dto.DeviceInfoResponse; import com.cisd.tms.modules.device.dto.DeviceInfoResponse;
import com.cisd.tms.modules.device.service.DeviceService; 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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestHeader;
@ -12,6 +15,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/api/v1/device") @RequestMapping("/api/v1/device")
@Tag(name = "设备概览", description = "设备基础信息、状态和重启接口")
public class DeviceController { public class DeviceController {
private final DeviceService deviceService; private final DeviceService deviceService;
@ -21,17 +25,23 @@ public class DeviceController {
} }
@GetMapping("/info") @GetMapping("/info")
@Operation(summary = "查询设备信息", description = "返回设备标识、版本和预置产品类型等基础信息。")
public ApiResponse<DeviceInfoResponse> info() { public ApiResponse<DeviceInfoResponse> info() {
return ApiResponse.success(deviceService.info()); return ApiResponse.success(deviceService.info());
} }
@GetMapping("/status") @GetMapping("/status")
@Operation(summary = "查询设备状态", description = "返回当前设备运行状态与版本快照。")
public ApiResponse<DeviceInfoResponse> status() { public ApiResponse<DeviceInfoResponse> status() {
return ApiResponse.success(deviceService.info()); return ApiResponse.success(deviceService.info());
} }
@PostMapping("/restart") @PostMapping("/restart")
public ApiResponse<DeviceActionResponse> restart(@RequestHeader(value = "X-User", required = false) String operator) { @Operation(summary = "重启设备服务", description = "受理设备重启请求,并记录触发操作的内部用户。")
public ApiResponse<DeviceActionResponse> restart(
@Parameter(description = "触发重启的操作人,来自内部鉴权链路透传的 X-User 请求头")
@RequestHeader(value = "X-User", required = false) String operator
) {
return ApiResponse.success(deviceService.restart(operator)); return ApiResponse.success(deviceService.restart(operator));
} }
} }

View File

@ -1,10 +1,17 @@
package com.cisd.tms.modules.device.dto; package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "设备动作受理响应")
public class DeviceActionResponse { public class DeviceActionResponse {
@Schema(description = "受理动作名称", example = "restart")
private String action; private String action;
@Schema(description = "是否已受理", example = "true")
private boolean accepted; private boolean accepted;
@Schema(description = "操作人", example = "admin")
private String operator; private String operator;
@Schema(description = "受理时间", example = "2026-03-11T18:00:00+08:00")
private String acceptedAt; private String acceptedAt;
public String getAction() { public String getAction() {

View File

@ -1,12 +1,21 @@
package com.cisd.tms.modules.device.dto; package com.cisd.tms.modules.device.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "设备信息响应")
public class DeviceInfoResponse { public class DeviceInfoResponse {
@Schema(description = "设备唯一标识", example = "TMS-DEVICE-01")
private String deviceId; private String deviceId;
@Schema(description = "设备状态", example = "UP")
private String status; private String status;
@Schema(description = "设备版本", example = "1.0.0")
private String version; private String version;
@Schema(description = "预置产品类型", example = "STANDARD")
private String presetProductType; private String presetProductType;
@Schema(description = "预置版本", example = "6.6.4")
private String presetVersion; private String presetVersion;
@Schema(description = "预置来源", example = "application.yml")
private String presetSource; private String presetSource;
public String getDeviceId() { public String getDeviceId() {

View File

@ -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.FileDetailResponse;
import com.cisd.tms.modules.file.dto.FileUploadResponse; import com.cisd.tms.modules.file.dto.FileUploadResponse;
import com.cisd.tms.modules.file.service.FileService; 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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -14,6 +17,7 @@ import org.springframework.web.multipart.MultipartFile;
@RestController @RestController
@RequestMapping("/api/v1/files") @RequestMapping("/api/v1/files")
@Tag(name = "文件管理", description = "初始化文件上传与文件元数据查询接口")
public class FileController { public class FileController {
private final FileService fileService; private final FileService fileService;
@ -24,13 +28,21 @@ public class FileController {
// 内部上传入口前端先拿到 fileId再把 fileId 传给初始化相关接口 // 内部上传入口前端先拿到 fileId再把 fileId 传给初始化相关接口
@PostMapping("/upload") @PostMapping("/upload")
public ApiResponse<FileUploadResponse> upload(@RequestParam("file") MultipartFile file) { @Operation(summary = "上传初始化文件", description = "上传许可证、配置包等初始化依赖文件,返回 fileId 供后续任务引用。")
public ApiResponse<FileUploadResponse> upload(
@Parameter(description = "待上传的初始化文件")
@RequestParam("file") MultipartFile file
) {
return ApiResponse.success(fileService.upload(file)); return ApiResponse.success(fileService.upload(file));
} }
// 返回已存储的文件元数据便于调用方确认 fileId 到实际文件的映射关系 // 返回已存储的文件元数据便于调用方确认 fileId 到实际文件的映射关系
@GetMapping("/{fileId}") @GetMapping("/{fileId}")
public ApiResponse<FileDetailResponse> detail(@PathVariable String fileId) { @Operation(summary = "查询文件详情", description = "根据 fileId 返回已存储文件的元数据和当前状态。")
public ApiResponse<FileDetailResponse> detail(
@Parameter(description = "上传接口返回的文件唯一标识")
@PathVariable String fileId
) {
return ApiResponse.success(fileService.getDetail(fileId)); return ApiResponse.success(fileService.getDetail(fileId));
} }
} }

View File

@ -1,11 +1,19 @@
package com.cisd.tms.modules.file.dto; package com.cisd.tms.modules.file.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "文件详情响应")
public class FileDetailResponse { public class FileDetailResponse {
@Schema(description = "文件唯一标识", example = "FILE-20260310-000001")
private String fileId; private String fileId;
@Schema(description = "原始文件名", example = "license.dat")
private String originalFilename; private String originalFilename;
@Schema(description = "文件实际存储路径", example = "/home/tms/uploads/FILE-20260310-000001/license.dat")
private String storagePath; private String storagePath;
@Schema(description = "文件状态", example = "ACTIVE")
private String status; private String status;
@Schema(description = "当前文件大小,单位字节", example = "12")
private long size; private long size;
public String getFileId() { public String getFileId() {

View File

@ -1,9 +1,15 @@
package com.cisd.tms.modules.file.dto; package com.cisd.tms.modules.file.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "文件上传响应")
public class FileUploadResponse { public class FileUploadResponse {
@Schema(description = "文件唯一标识", example = "FILE-20260310-000001")
private String fileId; private String fileId;
@Schema(description = "文件大小,单位字节", example = "12")
private long size; private long size;
@Schema(description = "文件实际存储路径", example = "/home/tms/uploads/FILE-20260310-000001/license.dat")
private String storagePath; private String storagePath;
public String getFileId() { public String getFileId() {

View File

@ -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.InitTaskStepLogResponse;
import com.cisd.tms.modules.init.dto.InitTaskStepResponse; import com.cisd.tms.modules.init.dto.InitTaskStepResponse;
import com.cisd.tms.modules.init.service.InitService; 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 jakarta.validation.Valid;
import java.util.List; import java.util.List;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -22,6 +25,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/api/v1/init") @RequestMapping("/api/v1/init")
@Tag(name = "CISD初始化任务", description = "初始化模板、预检、任务创建、执行与日志查询接口")
public class InitController { public class InitController {
private final InitService initService; private final InitService initService;
@ -35,7 +39,11 @@ public class InitController {
* 前端据此识别后端预制版本并渲染对应版本的初始化页面 * 前端据此识别后端预制版本并渲染对应版本的初始化页面
*/ */
@GetMapping("/template") @GetMapping("/template")
public ApiResponse<InitPlanTemplateResponse> template(@RequestParam(required = false) String version) { @Operation(summary = "查询初始化模板", description = "返回初始化模板版本、预制产品类型和模板路径等元信息。")
public ApiResponse<InitPlanTemplateResponse> template(
@Parameter(description = "模板版本,不传时返回默认预制版本")
@RequestParam(required = false) String version
) {
return ApiResponse.success(initService.loadTemplate(version)); return ApiResponse.success(initService.loadTemplate(version));
} }
@ -43,6 +51,7 @@ public class InitController {
* 初始化预检仅做参数校验和步骤数量预估不落库 * 初始化预检仅做参数校验和步骤数量预估不落库
*/ */
@PostMapping("/preview") @PostMapping("/preview")
@Operation(summary = "预检初始化请求", description = "校验初始化参数并返回解析后的产品类型与预估步骤数量,不落库。")
public ApiResponse<InitPreviewResponse> preview(@Valid @RequestBody InitPreviewRequest request) { public ApiResponse<InitPreviewResponse> preview(@Valid @RequestBody InitPreviewRequest request) {
return ApiResponse.success(initService.preview(request)); return ApiResponse.success(initService.preview(request));
} }
@ -51,17 +60,26 @@ public class InitController {
* 创建初始化任务写入任务主表和步骤表状态初始化为 PENDING * 创建初始化任务写入任务主表和步骤表状态初始化为 PENDING
*/ */
@PostMapping("/tasks") @PostMapping("/tasks")
@Operation(summary = "创建初始化任务", description = "根据初始化请求生成任务和步骤记录,任务初始状态为 PENDING。")
public ApiResponse<InitCreateTaskResponse> createTask(@Valid @RequestBody InitPreviewRequest request) { public ApiResponse<InitCreateTaskResponse> createTask(@Valid @RequestBody InitPreviewRequest request) {
return ApiResponse.success(initService.createTask(request)); return ApiResponse.success(initService.createTask(request));
} }
@GetMapping("/tasks/{taskId}") @GetMapping("/tasks/{taskId}")
public ApiResponse<InitTaskDetailResponse> taskDetail(@PathVariable String taskId) { @Operation(summary = "查询任务详情", description = "返回初始化任务的主状态、产品类型和原始计划快照。")
public ApiResponse<InitTaskDetailResponse> taskDetail(
@Parameter(description = "初始化任务唯一标识")
@PathVariable String taskId
) {
return ApiResponse.success(initService.getTask(taskId)); return ApiResponse.success(initService.getTask(taskId));
} }
@GetMapping("/tasks/{taskId}/steps") @GetMapping("/tasks/{taskId}/steps")
public ApiResponse<List<InitTaskStepResponse>> taskSteps(@PathVariable String taskId) { @Operation(summary = "查询任务步骤", description = "返回初始化任务下全部步骤的执行状态和命令信息。")
public ApiResponse<List<InitTaskStepResponse>> taskSteps(
@Parameter(description = "初始化任务唯一标识")
@PathVariable String taskId
) {
return ApiResponse.success(initService.getTaskSteps(taskId)); return ApiResponse.success(initService.getTaskSteps(taskId));
} }
@ -69,7 +87,13 @@ public class InitController {
* 下载单步骤执行日志用于页面定位失败原因 * 下载单步骤执行日志用于页面定位失败原因
*/ */
@GetMapping("/tasks/{taskId}/steps/{stepNo}/log") @GetMapping("/tasks/{taskId}/steps/{stepNo}/log")
public ApiResponse<InitTaskStepLogResponse> taskStepLog(@PathVariable String taskId, @PathVariable int stepNo) { @Operation(summary = "查询步骤日志", description = "返回指定任务步骤的日志路径和日志内容,用于定位失败原因。")
public ApiResponse<InitTaskStepLogResponse> taskStepLog(
@Parameter(description = "初始化任务唯一标识")
@PathVariable String taskId,
@Parameter(description = "任务步骤序号,从 1 开始")
@PathVariable int stepNo
) {
return ApiResponse.success(initService.getTaskStepLog(taskId, stepNo)); return ApiResponse.success(initService.getTaskStepLog(taskId, stepNo));
} }
@ -78,7 +102,11 @@ public class InitController {
* 接口只负责启动后台执行并立即返回前端需继续轮询任务详情步骤状态和步骤日志 * 接口只负责启动后台执行并立即返回前端需继续轮询任务详情步骤状态和步骤日志
*/ */
@PostMapping("/tasks/{taskId}/execute") @PostMapping("/tasks/{taskId}/execute")
public ApiResponse<InitTaskExecuteResponse> executeTask(@PathVariable String taskId) { @Operation(summary = "执行初始化任务", description = "异步受理初始化任务执行请求,立即返回当前任务快照,实际步骤在后台继续运行。")
public ApiResponse<InitTaskExecuteResponse> executeTask(
@Parameter(description = "初始化任务唯一标识")
@PathVariable String taskId
) {
return ApiResponse.success(initService.executeTask(taskId)); return ApiResponse.success(initService.executeTask(taskId));
} }
} }

View File

@ -1,10 +1,17 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "初始化任务创建响应")
public class InitCreateTaskResponse { public class InitCreateTaskResponse {
@Schema(description = "初始化任务唯一标识", example = "INIT-20260310-000001")
private String taskId; private String taskId;
@Schema(description = "任务状态", example = "PENDING")
private String status; private String status;
@Schema(description = "解析后的产品类型", example = "STANDARD")
private String resolvedProductType; private String resolvedProductType;
@Schema(description = "命中的预制版本", example = "6.6.4")
private String presetVersion; private String presetVersion;
public String getTaskId() { public String getTaskId() {

View File

@ -1,12 +1,18 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List; import java.util.List;
@Schema(description = "初始化模板元信息")
public class InitPlanTemplateResponse { public class InitPlanTemplateResponse {
@Schema(description = "预制产品类型", example = "STANDARD")
private String presetProductType; private String presetProductType;
@Schema(description = "预制模板版本", example = "6.6.4")
private String presetVersion; private String presetVersion;
@Schema(description = "支持的模板版本列表")
private List<String> supportedVersions; private List<String> supportedVersions;
@Schema(description = "模板文件路径", example = "classpath:initplan/standard-6.6.4.json")
private String templatePath; private String templatePath;
public String getPresetProductType() { public String getPresetProductType() {

View File

@ -1,5 +1,6 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
@ -8,6 +9,7 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
@Schema(description = "初始化预检与任务创建请求")
public class InitPreviewRequest { public class InitPreviewRequest {
private static final String IPV4_REGEX = private static final String IPV4_REGEX =
@ -16,39 +18,49 @@ public class InitPreviewRequest {
@NotBlank(message = "orgCodeType is required") @NotBlank(message = "orgCodeType is required")
@Pattern(regexp = "BIC|CIPSID|LEI", message = "orgCodeType must be BIC/CIPSID/LEI") @Pattern(regexp = "BIC|CIPSID|LEI", message = "orgCodeType must be BIC/CIPSID/LEI")
@Schema(description = "机构代码类型", example = "BIC")
private String orgCodeType; private String orgCodeType;
@NotBlank(message = "orgCode is required") @NotBlank(message = "orgCode is required")
@Size(max = 32, message = "orgCode length must be <= 32") @Size(max = 32, message = "orgCode length must be <= 32")
@Schema(description = "机构代码", example = "BKCHCNBJXXX")
private String orgCode; private String orgCode;
@NotBlank(message = "orgNameCn is required") @NotBlank(message = "orgNameCn is required")
@Size(max = 128, message = "orgNameCn length must be <= 128") @Size(max = 128, message = "orgNameCn length must be <= 128")
@Schema(description = "机构中文名称", example = "中国银行")
private String orgNameCn; private String orgNameCn;
@Size(max = 256, message = "orgNameEn length must be <= 256") @Size(max = 256, message = "orgNameEn length must be <= 256")
@Schema(description = "机构英文名称", example = "Bank of China")
private String orgNameEn; private String orgNameEn;
@NotBlank(message = "deployMode is required") @NotBlank(message = "deployMode is required")
@Pattern(regexp = "SINGLE|DUAL|QUAD", message = "deployMode must be SINGLE/DUAL/QUAD") @Pattern(regexp = "SINGLE|DUAL|QUAD", message = "deployMode must be SINGLE/DUAL/QUAD")
@Schema(description = "部署模式", example = "DUAL")
private String deployMode; private String deployMode;
@Valid @Valid
@NotNull(message = "nodes is required") @NotNull(message = "nodes is required")
@Schema(description = "节点 IP 配置")
private Nodes nodes; private Nodes nodes;
@Valid @Valid
@NotNull(message = "mq is required") @NotNull(message = "mq is required")
@Schema(description = "消息中间件配置")
private Mq mq; private Mq mq;
@Valid @Valid
@NotNull(message = "licenses is required") @NotNull(message = "licenses is required")
@Schema(description = "初始化许可证与配置包引用")
private Licenses licenses; private Licenses licenses;
@Valid @Valid
@Schema(description = "签名服务配置,直参版按需填写")
private SignServer signServer; private SignServer signServer;
@Valid @Valid
@Schema(description = "直连 TLQ 配置,直参版按需填写")
private DirectTlqConfig directTlq; private DirectTlqConfig directTlq;
public String getOrgCodeType() { public String getOrgCodeType() {
@ -131,19 +143,24 @@ public class InitPreviewRequest {
this.directTlq = directTlq; this.directTlq = directTlq;
} }
@Schema(description = "节点 IP 配置")
public static class Nodes { public static class Nodes {
@NotBlank(message = "nodes.node01Ip is required") @NotBlank(message = "nodes.node01Ip is required")
@Pattern(regexp = IPV4_REGEX, message = "nodes.node01Ip must be a valid IPv4") @Pattern(regexp = IPV4_REGEX, message = "nodes.node01Ip must be a valid IPv4")
@Schema(description = "节点 1 IP", example = "192.168.1.11")
private String node01Ip; private String node01Ip;
@Pattern(regexp = IPV4_REGEX, message = "nodes.node02Ip must be a valid IPv4") @Pattern(regexp = IPV4_REGEX, message = "nodes.node02Ip must be a valid IPv4")
@Schema(description = "节点 2 IP", example = "192.168.1.12")
private String node02Ip; private String node02Ip;
@Pattern(regexp = IPV4_REGEX, message = "nodes.node03Ip must be a valid IPv4") @Pattern(regexp = IPV4_REGEX, message = "nodes.node03Ip must be a valid IPv4")
@Schema(description = "节点 3 IP", example = "192.168.1.13")
private String node03Ip; private String node03Ip;
@Pattern(regexp = IPV4_REGEX, message = "nodes.node04Ip must be a valid IPv4") @Pattern(regexp = IPV4_REGEX, message = "nodes.node04Ip must be a valid IPv4")
@Schema(description = "节点 4 IP", example = "192.168.1.14")
private String node04Ip; private String node04Ip;
public String getNode01Ip() { public String getNode01Ip() {
@ -179,25 +196,31 @@ public class InitPreviewRequest {
} }
} }
@Schema(description = "消息中间件配置")
public static class Mq { public static class Mq {
@NotBlank(message = "mq.mqType is required") @NotBlank(message = "mq.mqType is required")
@Schema(description = "消息中间件类型", example = "RABBITMQ")
private String mqType; private String mqType;
@NotBlank(message = "mq.channelUsername is required") @NotBlank(message = "mq.channelUsername is required")
@Size(max = 32, message = "mq.channelUsername length must be <= 32") @Size(max = 32, message = "mq.channelUsername length must be <= 32")
@Schema(description = "通道用户名", example = "tms")
private String channelUsername; private String channelUsername;
@NotBlank(message = "mq.channelPassword is required") @NotBlank(message = "mq.channelPassword is required")
@Size(max = 128, message = "mq.channelPassword length must be <= 128") @Size(max = 128, message = "mq.channelPassword length must be <= 128")
@Schema(description = "通道密码")
private String channelPassword; private String channelPassword;
@Size(max = 128, message = "mq.tlqLicenseFileId length must be <= 128") @Size(max = 128, message = "mq.tlqLicenseFileId length must be <= 128")
@Pattern(regexp = FILE_ID_REGEX, message = "mq.tlqLicenseFileId must be a fileId") @Pattern(regexp = FILE_ID_REGEX, message = "mq.tlqLicenseFileId must be a fileId")
@Schema(description = "TLQ 许可证文件 fileId", example = "FILE-20260310-000001")
private String tlqLicenseFileId; private String tlqLicenseFileId;
@Size(max = 128, message = "mq.cfmqConfigFileId length must be <= 128") @Size(max = 128, message = "mq.cfmqConfigFileId length must be <= 128")
@Pattern(regexp = FILE_ID_REGEX, message = "mq.cfmqConfigFileId must be a fileId") @Pattern(regexp = FILE_ID_REGEX, message = "mq.cfmqConfigFileId must be a fileId")
@Schema(description = "CFMQ 配置文件 fileId", example = "FILE-20260310-000002")
private String cfmqConfigFileId; private String cfmqConfigFileId;
public String getMqType() { public String getMqType() {
@ -241,15 +264,18 @@ public class InitPreviewRequest {
} }
} }
@Schema(description = "许可证和配置包引用")
public static class Licenses { public static class Licenses {
@NotBlank(message = "licenses.receiverLicenseFileId is required") @NotBlank(message = "licenses.receiverLicenseFileId is required")
@Size(max = 128, message = "licenses.receiverLicenseFileId length must be <= 128") @Size(max = 128, message = "licenses.receiverLicenseFileId length must be <= 128")
@Pattern(regexp = FILE_ID_REGEX, message = "licenses.receiverLicenseFileId must be a fileId") @Pattern(regexp = FILE_ID_REGEX, message = "licenses.receiverLicenseFileId must be a fileId")
@Schema(description = "接收端许可证文件 fileId", example = "FILE-20260310-000003")
private String receiverLicenseFileId; private String receiverLicenseFileId;
@Size(max = 128, message = "licenses.cfgZipFileId length must be <= 128") @Size(max = 128, message = "licenses.cfgZipFileId length must be <= 128")
@Pattern(regexp = FILE_ID_REGEX, message = "licenses.cfgZipFileId must be a fileId") @Pattern(regexp = FILE_ID_REGEX, message = "licenses.cfgZipFileId must be a fileId")
@Schema(description = "标准版配置包 zip 文件 fileId", example = "FILE-20260310-000004")
private String cfgZipFileId; private String cfgZipFileId;
public String getReceiverLicenseFileId() { public String getReceiverLicenseFileId() {
@ -269,16 +295,20 @@ public class InitPreviewRequest {
} }
} }
@Schema(description = "签名服务配置")
public static class SignServer { public static class SignServer {
@Size(max = 64, message = "signServer.signHost length must be <= 64") @Size(max = 64, message = "signServer.signHost length must be <= 64")
@Schema(description = "签名服务地址", example = "10.0.0.20")
private String signHost; private String signHost;
@NotNull(message = "signServer.signPort must not be null") @NotNull(message = "signServer.signPort must not be null")
@Max(value = 65535, message = "signServer.signPort must be <= 65535") @Max(value = 65535, message = "signServer.signPort must be <= 65535")
@Schema(description = "签名服务端口", example = "8088")
private Integer signPort; private Integer signPort;
@Pattern(regexp = "Infosec|CFCA", message = "signServer.signType must be Infosec/CFCA") @Pattern(regexp = "Infosec|CFCA", message = "signServer.signType must be Infosec/CFCA")
@Schema(description = "签名服务厂商类型", example = "CFCA")
private String signType; private String signType;
@Size(max = 512, message = "signServer.signCertDn length must be <= 512") @Size(max = 512, message = "signServer.signCertDn length must be <= 512")
@ -339,16 +369,20 @@ public class InitPreviewRequest {
} }
} }
@Schema(description = "直连 TLQ 配置")
public static class DirectTlqConfig { public static class DirectTlqConfig {
@Pattern(regexp = "A|B", message = "directTlq.localNodeSeq must be A/B") @Pattern(regexp = "A|B", message = "directTlq.localNodeSeq must be A/B")
@Schema(description = "本节点序号", example = "A")
private String localNodeSeq; private String localNodeSeq;
@Size(max = 256, message = "directTlq.upstreamInstitutions length must be <= 256") @Size(max = 256, message = "directTlq.upstreamInstitutions length must be <= 256")
@Schema(description = "上游机构列表", example = "BANKA,BANKB")
private String upstreamInstitutions; private String upstreamInstitutions;
@Min(value = 1, message = "directTlq.upstreamCocCount must be >= 1") @Min(value = 1, message = "directTlq.upstreamCocCount must be >= 1")
@Max(value = 2, message = "directTlq.upstreamCocCount must be <= 2") @Max(value = 2, message = "directTlq.upstreamCocCount must be <= 2")
@Schema(description = "上游 COC 数量", example = "2")
private Integer upstreamCocCount; private Integer upstreamCocCount;
public String getLocalNodeSeq() { public String getLocalNodeSeq() {

View File

@ -1,10 +1,17 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "初始化预检响应")
public class InitPreviewResponse { public class InitPreviewResponse {
@Schema(description = "解析后的产品类型", example = "STANDARD")
private String resolvedProductType; private String resolvedProductType;
@Schema(description = "命中的预制版本", example = "6.6.4")
private String presetVersion; private String presetVersion;
@Schema(description = "预计执行步骤数", example = "18")
private int stepCount; private int stepCount;
@Schema(description = "预检摘要说明")
private String summary; private String summary;
public String getResolvedProductType() { public String getResolvedProductType() {

View File

@ -1,14 +1,25 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "初始化任务详情")
public class InitTaskDetailResponse { public class InitTaskDetailResponse {
@Schema(description = "初始化任务唯一标识", example = "INIT-20260310-000001")
private String taskId; private String taskId;
@Schema(description = "任务状态", example = "RUNNING")
private String status; private String status;
@Schema(description = "解析后的产品类型", example = "STANDARD")
private String resolvedProductType; private String resolvedProductType;
@Schema(description = "命中的预制版本", example = "6.6.4")
private String presetVersion; private String presetVersion;
@Schema(description = "机构代码", example = "BKCHCNBJXXX")
private String orgCode; private String orgCode;
@Schema(description = "消息中间件类型", example = "RABBITMQ")
private String mqType; private String mqType;
@Schema(description = "部署模式", example = "DUAL")
private String deployMode; private String deployMode;
@Schema(description = "初始化计划原始 JSON 快照")
private String initPlanJson; private String initPlanJson;
public String getTaskId() { public String getTaskId() {

View File

@ -1,10 +1,17 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "初始化任务执行受理响应")
public class InitTaskExecuteResponse { public class InitTaskExecuteResponse {
@Schema(description = "初始化任务唯一标识", example = "INIT-20260310-000001")
private String taskId; private String taskId;
@Schema(description = "任务当前状态", example = "RUNNING")
private String status; private String status;
@Schema(description = "总步骤数", example = "18")
private int totalSteps; private int totalSteps;
@Schema(description = "已成功步骤数", example = "6")
private int successSteps; private int successSteps;
public String getTaskId() { public String getTaskId() {

View File

@ -1,11 +1,19 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "初始化步骤日志详情")
public class InitTaskStepLogResponse { public class InitTaskStepLogResponse {
@Schema(description = "初始化任务唯一标识", example = "INIT-20260310-000001")
private String taskId; private String taskId;
@Schema(description = "步骤序号", example = "3")
private int stepNo; private int stepNo;
@Schema(description = "步骤编码", example = "DB_APPLY")
private String stepCode; private String stepCode;
@Schema(description = "日志文件路径")
private String logPath; private String logPath;
@Schema(description = "日志内容")
private String content; private String content;
public String getTaskId() { public String getTaskId() {

View File

@ -1,13 +1,23 @@
package com.cisd.tms.modules.init.dto; package com.cisd.tms.modules.init.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "初始化任务步骤状态")
public class InitTaskStepResponse { public class InitTaskStepResponse {
@Schema(description = "步骤序号", example = "1")
private int stepNo; private int stepNo;
@Schema(description = "步骤编码", example = "DB_APPLY")
private String stepCode; private String stepCode;
@Schema(description = "步骤状态", example = "SUCCESS")
private String status; private String status;
@Schema(description = "步骤执行摘要")
private String message; private String message;
@Schema(description = "执行命令行")
private String commandLine; private String commandLine;
@Schema(description = "步骤日志路径")
private String logPath; private String logPath;
@Schema(description = "命令退出码", example = "0")
private Integer exitCode; private Integer exitCode;
public int getStepNo() { public int getStepNo() {

View File

@ -11,6 +11,8 @@ import com.cisd.tms.modules.mk.enums.MasterKeyStatus;
import com.cisd.tms.modules.mk.service.LmkService; import com.cisd.tms.modules.mk.service.LmkService;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; 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 jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -30,6 +32,7 @@ import java.util.List;
*/ */
@RestController @RestController
@RequestMapping("/api/v1") @RequestMapping("/api/v1")
@Tag(name = "主密钥管理", description = "LMK 生成、恢复、状态查询和销毁接口")
public class LmkController { public class LmkController {
@Resource @Resource
@ -42,6 +45,7 @@ public class LmkController {
* @return * @return
*/ */
@PostMapping("/masterkey/generate") @PostMapping("/masterkey/generate")
@Operation(summary = "生成主密钥", description = "生成新的主密钥并返回密钥信息。")
public ApiResponse<LMK> generateMasterKey() { public ApiResponse<LMK> generateMasterKey() {
LMK lmk = lmkService.generateLMK(); LMK lmk = lmkService.generateLMK();
return ApiResponse.success(lmk); return ApiResponse.success(lmk);
@ -53,6 +57,7 @@ public class LmkController {
* @return * @return
*/ */
@PostMapping("/masterkey/uKeySignAndKeyComponent") @PostMapping("/masterkey/uKeySignAndKeyComponent")
@Operation(summary = "UKey 签名并获取密钥分量", description = "根据 UKey 签名结果导出内部密钥公钥并返回主密钥分量。")
public ApiResponse<UKeySignResult> signAndKeyComponent(@RequestBody UKeySignDTO uKeySignDTO) { public ApiResponse<UKeySignResult> signAndKeyComponent(@RequestBody UKeySignDTO uKeySignDTO) {
String iPubKey = lmkService.exportIkPublicKeyHex(); String iPubKey = lmkService.exportIkPublicKeyHex();
String payload; String payload;
@ -68,6 +73,7 @@ public class LmkController {
} }
@GetMapping("/masterKeyStatus") @GetMapping("/masterKeyStatus")
@Operation(summary = "查询主密钥状态", description = "返回主密钥当前是否存在以及相关状态详情。")
public ApiResponse<MasterKeyStatus.StatusDetail> getMasterKeyStatus() { public ApiResponse<MasterKeyStatus.StatusDetail> getMasterKeyStatus() {
return ApiResponse.success(lmkService.getMasterKeyStatus()); return ApiResponse.success(lmkService.getMasterKeyStatus());
} }
@ -80,6 +86,7 @@ public class LmkController {
* @throws ClassNotFoundException * @throws ClassNotFoundException
*/ */
@PostMapping("/recoveryMasterKey") @PostMapping("/recoveryMasterKey")
@Operation(summary = "恢复主密钥", description = "根据上传的分量或序列化载荷恢复主密钥,并在需要时重建内部密钥。")
public ApiResponse<RecoveryResult> recoveryMasterKey(@RequestBody MasterKeyRestoreDTO masterKeyRestoreDTO) public ApiResponse<RecoveryResult> recoveryMasterKey(@RequestBody MasterKeyRestoreDTO masterKeyRestoreDTO)
throws IOException, ClassNotFoundException { throws IOException, ClassNotFoundException {
RecoveryResult result; RecoveryResult result;
@ -103,6 +110,7 @@ public class LmkController {
* @return * @return
*/ */
@PostMapping("/masterKeyRecovery") @PostMapping("/masterKeyRecovery")
@Operation(summary = "分量合成恢复主密钥", description = "根据主密钥分量直接合成并恢复主密钥。")
public ApiResponse<LMK> masterKeyRecovery(@RequestBody MasterKeyRestoreDTO masterKeyRestoreDTO) { public ApiResponse<LMK> masterKeyRecovery(@RequestBody MasterKeyRestoreDTO masterKeyRestoreDTO) {
String masterKey = lmkService.masterKeyCompose(masterKeyRestoreDTO.getComponents()); String masterKey = lmkService.masterKeyCompose(masterKeyRestoreDTO.getComponents());
LMK lmk = lmkService.recoveryLMK1(LMK.getInstance(masterKey, masterKeyRestoreDTO)); LMK lmk = lmkService.recoveryLMK1(LMK.getInstance(masterKey, masterKeyRestoreDTO));
@ -110,6 +118,7 @@ public class LmkController {
} }
@GetMapping("/destroyLMKAndIK") @GetMapping("/destroyLMKAndIK")
@Operation(summary = "销毁主密钥和内部密钥", description = "销毁当前主密钥以及相关内部密钥材料。")
public ApiResponse<String> destroyLMKAndIK() { public ApiResponse<String> destroyLMKAndIK() {
lmkService.destroyLMKAndIK(); lmkService.destroyLMKAndIK();
return ApiResponse.success("主密钥和内部密钥销毁成功!"); return ApiResponse.success("主密钥和内部密钥销毁成功!");

View File

@ -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.service.SignService;
import com.cisd.tms.modules.sign.support.SignCommand; import com.cisd.tms.modules.sign.support.SignCommand;
import com.cisd.tms.modules.sign.support.SignResult; 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 jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -14,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/api/v1/sign") @RequestMapping("/api/v1/sign")
@Tag(name = "签名预览", description = "内部签名预览与摘要计算接口")
public class SignController { public class SignController {
private final SignService signService; private final SignService signService;
@ -23,6 +26,7 @@ public class SignController {
} }
@PostMapping("/preview") @PostMapping("/preview")
@Operation(summary = "预览签名摘要", description = "根据明文和算法生成摘要预览结果,不落库。")
public ApiResponse<InternalSignPreviewResponse> preview(@Valid @RequestBody InternalSignPreviewRequest request) { public ApiResponse<InternalSignPreviewResponse> preview(@Valid @RequestBody InternalSignPreviewRequest request) {
SignCommand command = new SignCommand(); SignCommand command = new SignCommand();
command.setAlgorithm(request.getAlgorithm()); command.setAlgorithm(request.getAlgorithm());

View File

@ -1,12 +1,16 @@
package com.cisd.tms.modules.sign.dto.internal; package com.cisd.tms.modules.sign.dto.internal;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
@Schema(description = "内部签名预览请求")
public class InternalSignPreviewRequest { public class InternalSignPreviewRequest {
@NotBlank(message = "plainText is required") @NotBlank(message = "plainText is required")
@Schema(description = "待签名明文", example = "hello-tms")
private String plainText; private String plainText;
@Schema(description = "签名算法,不传时使用默认算法", example = "SHA256withRSA")
private String algorithm; private String algorithm;
public String getPlainText() { public String getPlainText() {

View File

@ -1,8 +1,13 @@
package com.cisd.tms.modules.sign.dto.internal; package com.cisd.tms.modules.sign.dto.internal;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(description = "内部签名预览响应")
public class InternalSignPreviewResponse { public class InternalSignPreviewResponse {
@Schema(description = "实际使用的签名算法", example = "SHA256withRSA")
private String algorithm; private String algorithm;
@Schema(description = "摘要十六进制串")
private String digestHex; private String digestHex;
public String getAlgorithm() { public String getAlgorithm() {

View File

@ -1,6 +1,8 @@
package com.cisd.tms.modules.system.controller; package com.cisd.tms.modules.system.controller;
import com.cisd.tms.common.api.ApiResponse; 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 java.util.Map;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -8,9 +10,11 @@ import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@RequestMapping("/api/v1/system") @RequestMapping("/api/v1/system")
@Tag(name = "系统状态", description = "服务健康检查接口")
public class HealthController { public class HealthController {
@GetMapping("/health") @GetMapping("/health")
@Operation(summary = "查询服务健康状态", description = "返回当前服务是否可用以及服务名称等基础健康信息。")
public ApiResponse<Map<String, Object>> status() { public ApiResponse<Map<String, Object>> status() {
return ApiResponse.success(Map.of( return ApiResponse.success(Map.of(
"status", "UP", "status", "UP",

View File

@ -1,12 +1,36 @@
package com.cisd.tms; package com.cisd.tms;
import org.junit.jupiter.api.Test; 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.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 @SpringBootTest
@AutoConfigureMockMvc
class TmsApplicationTests { class TmsApplicationTests {
@Autowired
private MockMvc mockMvc;
@Test @Test
void contextLoads() { 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\":\"内部用户登录\"")));
}
} }