mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(platform): I5 callback inbox, internal ingest, and M6 catalog APIs
Made-with: Cursor
This commit is contained in:
+3
@@ -21,5 +21,8 @@ public final class AuditActions {
|
||||
public static final String LICENSE_SN_UPDATED = "LICENSE_SN_UPDATED";
|
||||
public static final String LICENSE_SN_STATUS_CHANGED = "LICENSE_SN_STATUS_CHANGED";
|
||||
|
||||
public static final String CALLBACK_INBOX_STATUS_CHANGED = "CALLBACK_INBOX_STATUS_CHANGED";
|
||||
public static final String CALLBACK_INBOX_LINK_UPDATED = "CALLBACK_INBOX_LINK_UPDATED";
|
||||
|
||||
private AuditActions() {}
|
||||
}
|
||||
|
||||
+1
@@ -5,6 +5,7 @@ public final class AuditEntityTypes {
|
||||
public static final String CONTRACT = "CONTRACT";
|
||||
public static final String DELIVERY_BATCH = "DELIVERY_BATCH";
|
||||
public static final String LICENSE_SN = "LICENSE_SN";
|
||||
public static final String CALLBACK_INBOX = "CALLBACK_INBOX";
|
||||
|
||||
private AuditEntityTypes() {}
|
||||
}
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package cn.craftlabs.platform.api.callback;
|
||||
|
||||
import cn.craftlabs.platform.api.service.CallbackInboxService;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackInboxLinkPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackInboxResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackInboxStatusPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/callback-inbox")
|
||||
@Validated
|
||||
public class CallbackInboxController {
|
||||
|
||||
private final CallbackInboxService callbackInboxService;
|
||||
|
||||
public CallbackInboxController(CallbackInboxService callbackInboxService) {
|
||||
this.callbackInboxService = callbackInboxService;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public PageResponse<CallbackInboxResponse> list(
|
||||
@RequestParam(value = "page", defaultValue = "0") @Min(0) int page,
|
||||
@RequestParam(value = "size", defaultValue = "20") @Min(1) @Max(200) int size,
|
||||
@RequestParam(value = "status", required = false) String status,
|
||||
@RequestParam(value = "eventType", required = false) String eventType,
|
||||
@RequestParam(value = "snCode", required = false) String snCode,
|
||||
@RequestParam(value = "projectId", required = false) Long projectId,
|
||||
@RequestParam(value = "productLineId", required = false) Long productLineId,
|
||||
@RequestParam(value = "environmentId", required = false) Long environmentId,
|
||||
@RequestParam(value = "receivedFrom", required = false) OffsetDateTime receivedFrom,
|
||||
@RequestParam(value = "receivedTo", required = false) OffsetDateTime receivedTo) {
|
||||
return callbackInboxService.page(
|
||||
page,
|
||||
size,
|
||||
status,
|
||||
eventType,
|
||||
snCode,
|
||||
projectId,
|
||||
productLineId,
|
||||
environmentId,
|
||||
receivedFrom,
|
||||
receivedTo);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public CallbackInboxResponse get(@PathVariable("id") long id) {
|
||||
return callbackInboxService.getById(id);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/status")
|
||||
public CallbackInboxResponse patchStatus(
|
||||
@PathVariable("id") long id, @Valid @RequestBody CallbackInboxStatusPatchRequest request) {
|
||||
return callbackInboxService.patchStatus(id, request);
|
||||
}
|
||||
|
||||
@PatchMapping("/{id}/link")
|
||||
public CallbackInboxResponse patchLink(
|
||||
@PathVariable("id") long id, @Valid @RequestBody CallbackInboxLinkPatchRequest request) {
|
||||
return callbackInboxService.patchLink(id, request);
|
||||
}
|
||||
}
|
||||
+23
-2
@@ -1,8 +1,10 @@
|
||||
package cn.craftlabs.platform.api.config;
|
||||
|
||||
import cn.craftlabs.platform.api.security.InternalTokenAuthenticationFilter;
|
||||
import cn.craftlabs.platform.api.security.JwtAuthenticationFilter;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -12,14 +14,33 @@ import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* I1:JWT(Bearer)保护业务 API;登录与健康检查、OpenAPI 文档放行。
|
||||
* I1:JWT(Bearer)保护业务 API;I5:{@code /internal/**} 使用内部共享 Token,与 JWT 分离。
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthenticationFilter jwtFilter)
|
||||
@Order(1)
|
||||
public SecurityFilterChain internalFilterChain(
|
||||
HttpSecurity http, InternalTokenAuthenticationFilter internalTokenFilter) throws Exception {
|
||||
http.securityMatcher("/internal/**")
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(
|
||||
sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
|
||||
.httpBasic(b -> b.disable())
|
||||
.exceptionHandling(
|
||||
ex ->
|
||||
ex.authenticationEntryPoint(
|
||||
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
|
||||
.addFilterBefore(internalTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(2)
|
||||
public SecurityFilterChain jwtFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtFilter)
|
||||
throws Exception {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package cn.craftlabs.platform.api.domain;
|
||||
|
||||
public enum CallbackInboxStatus {
|
||||
PENDING,
|
||||
PROCESSED,
|
||||
FAILED,
|
||||
IGNORED
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package cn.craftlabs.platform.api.integration;
|
||||
|
||||
import cn.craftlabs.platform.api.service.IntegrationCatalogService;
|
||||
import cn.craftlabs.platform.api.web.dto.IntegrationEnvironmentResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.ProductLineResponse;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/integration")
|
||||
@Validated
|
||||
public class IntegrationCatalogController {
|
||||
|
||||
private final IntegrationCatalogService integrationCatalogService;
|
||||
|
||||
public IntegrationCatalogController(IntegrationCatalogService integrationCatalogService) {
|
||||
this.integrationCatalogService = integrationCatalogService;
|
||||
}
|
||||
|
||||
@GetMapping("/product-lines")
|
||||
public PageResponse<ProductLineResponse> listProductLines(
|
||||
@RequestParam(value = "page", defaultValue = "0") @Min(0) int page,
|
||||
@RequestParam(value = "size", defaultValue = "50") @Min(1) @Max(200) int size) {
|
||||
return integrationCatalogService.pageProductLines(page, size);
|
||||
}
|
||||
|
||||
@GetMapping("/product-lines/{id}")
|
||||
public ProductLineResponse getProductLine(@PathVariable("id") long id) {
|
||||
return integrationCatalogService.getProductLine(id);
|
||||
}
|
||||
|
||||
@GetMapping("/environments")
|
||||
public PageResponse<IntegrationEnvironmentResponse> listEnvironments(
|
||||
@RequestParam(value = "page", defaultValue = "0") @Min(0) int page,
|
||||
@RequestParam(value = "size", defaultValue = "50") @Min(1) @Max(200) int size) {
|
||||
return integrationCatalogService.pageEnvironments(page, size);
|
||||
}
|
||||
|
||||
@GetMapping("/environments/{id}")
|
||||
public IntegrationEnvironmentResponse getEnvironment(@PathVariable("id") long id) {
|
||||
return integrationCatalogService.getEnvironment(id);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package cn.craftlabs.platform.api.internal;
|
||||
|
||||
import cn.craftlabs.platform.api.service.CallbackEventIngestService;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackEventIngestRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackEventIngestResponse;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Hidden
|
||||
@RestController
|
||||
@RequestMapping("/internal/v1")
|
||||
public class CallbackInternalController {
|
||||
|
||||
private final CallbackEventIngestService ingestService;
|
||||
|
||||
public CallbackInternalController(CallbackEventIngestService ingestService) {
|
||||
this.ingestService = ingestService;
|
||||
}
|
||||
|
||||
@PostMapping("/callback-events")
|
||||
public CallbackEventIngestResponse ingest(
|
||||
@Valid @RequestBody CallbackEventIngestRequest body,
|
||||
@RequestHeader(value = "Idempotency-Key", required = false) String idempotencyKey) {
|
||||
return ingestService.ingest(body, idempotencyKey);
|
||||
}
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
package cn.craftlabs.platform.api.persistence.callback;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@TableName("platform_callback_inbox")
|
||||
public class PlatformCallbackInbox {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@TableField("source_system")
|
||||
private String sourceSystem;
|
||||
|
||||
@TableField("external_message_id")
|
||||
private String externalMessageId;
|
||||
|
||||
@TableField("schema_version")
|
||||
private String schemaVersion;
|
||||
|
||||
@TableField("event_type")
|
||||
private String eventType;
|
||||
|
||||
private String status;
|
||||
|
||||
@TableField("raw_payload")
|
||||
private String rawPayload;
|
||||
|
||||
@TableField("idempotency_key")
|
||||
private String idempotencyKey;
|
||||
|
||||
@TableField("license_sn_id")
|
||||
private Long licenseSnId;
|
||||
|
||||
@TableField("project_id")
|
||||
private Long projectId;
|
||||
|
||||
@TableField("contract_id")
|
||||
private Long contractId;
|
||||
|
||||
@TableField("sn_code")
|
||||
private String snCode;
|
||||
|
||||
@TableField("product_line_id")
|
||||
private Long productLineId;
|
||||
|
||||
@TableField("integration_environment_id")
|
||||
private Long integrationEnvironmentId;
|
||||
|
||||
@TableField("received_at")
|
||||
private OffsetDateTime receivedAt;
|
||||
|
||||
@TableField("processed_at")
|
||||
private OffsetDateTime processedAt;
|
||||
|
||||
@TableField("processed_by_user_id")
|
||||
private String processedByUserId;
|
||||
|
||||
@TableField("failure_reason")
|
||||
private String failureReason;
|
||||
|
||||
@TableField("operator_note")
|
||||
private String operatorNote;
|
||||
|
||||
@TableField("webhook_receipt_id")
|
||||
private String webhookReceiptId;
|
||||
|
||||
@TableField("created_at")
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSourceSystem() {
|
||||
return sourceSystem;
|
||||
}
|
||||
|
||||
public void setSourceSystem(String sourceSystem) {
|
||||
this.sourceSystem = sourceSystem;
|
||||
}
|
||||
|
||||
public String getExternalMessageId() {
|
||||
return externalMessageId;
|
||||
}
|
||||
|
||||
public void setExternalMessageId(String externalMessageId) {
|
||||
this.externalMessageId = externalMessageId;
|
||||
}
|
||||
|
||||
public String getSchemaVersion() {
|
||||
return schemaVersion;
|
||||
}
|
||||
|
||||
public void setSchemaVersion(String schemaVersion) {
|
||||
this.schemaVersion = schemaVersion;
|
||||
}
|
||||
|
||||
public String getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public void setEventType(String eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getRawPayload() {
|
||||
return rawPayload;
|
||||
}
|
||||
|
||||
public void setRawPayload(String rawPayload) {
|
||||
this.rawPayload = rawPayload;
|
||||
}
|
||||
|
||||
public String getIdempotencyKey() {
|
||||
return idempotencyKey;
|
||||
}
|
||||
|
||||
public void setIdempotencyKey(String idempotencyKey) {
|
||||
this.idempotencyKey = idempotencyKey;
|
||||
}
|
||||
|
||||
public Long getLicenseSnId() {
|
||||
return licenseSnId;
|
||||
}
|
||||
|
||||
public void setLicenseSnId(Long licenseSnId) {
|
||||
this.licenseSnId = licenseSnId;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractId() {
|
||||
return contractId;
|
||||
}
|
||||
|
||||
public void setContractId(Long contractId) {
|
||||
this.contractId = contractId;
|
||||
}
|
||||
|
||||
public String getSnCode() {
|
||||
return snCode;
|
||||
}
|
||||
|
||||
public void setSnCode(String snCode) {
|
||||
this.snCode = snCode;
|
||||
}
|
||||
|
||||
public Long getProductLineId() {
|
||||
return productLineId;
|
||||
}
|
||||
|
||||
public void setProductLineId(Long productLineId) {
|
||||
this.productLineId = productLineId;
|
||||
}
|
||||
|
||||
public Long getIntegrationEnvironmentId() {
|
||||
return integrationEnvironmentId;
|
||||
}
|
||||
|
||||
public void setIntegrationEnvironmentId(Long integrationEnvironmentId) {
|
||||
this.integrationEnvironmentId = integrationEnvironmentId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getReceivedAt() {
|
||||
return receivedAt;
|
||||
}
|
||||
|
||||
public void setReceivedAt(OffsetDateTime receivedAt) {
|
||||
this.receivedAt = receivedAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getProcessedAt() {
|
||||
return processedAt;
|
||||
}
|
||||
|
||||
public void setProcessedAt(OffsetDateTime processedAt) {
|
||||
this.processedAt = processedAt;
|
||||
}
|
||||
|
||||
public String getProcessedByUserId() {
|
||||
return processedByUserId;
|
||||
}
|
||||
|
||||
public void setProcessedByUserId(String processedByUserId) {
|
||||
this.processedByUserId = processedByUserId;
|
||||
}
|
||||
|
||||
public String getFailureReason() {
|
||||
return failureReason;
|
||||
}
|
||||
|
||||
public void setFailureReason(String failureReason) {
|
||||
this.failureReason = failureReason;
|
||||
}
|
||||
|
||||
public String getOperatorNote() {
|
||||
return operatorNote;
|
||||
}
|
||||
|
||||
public void setOperatorNote(String operatorNote) {
|
||||
this.operatorNote = operatorNote;
|
||||
}
|
||||
|
||||
public String getWebhookReceiptId() {
|
||||
return webhookReceiptId;
|
||||
}
|
||||
|
||||
public void setWebhookReceiptId(String webhookReceiptId) {
|
||||
this.webhookReceiptId = webhookReceiptId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package cn.craftlabs.platform.api.persistence.callback;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PlatformCallbackInboxMapper extends BaseMapper<PlatformCallbackInbox> {}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package cn.craftlabs.platform.api.persistence.integration;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@TableName("platform_integration_environment")
|
||||
public class PlatformIntegrationEnvironment {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String code;
|
||||
|
||||
private String name;
|
||||
|
||||
@TableField("bitanswer_base_url")
|
||||
private String bitanswerBaseUrl;
|
||||
|
||||
private String kind;
|
||||
|
||||
@TableField("product_line_id")
|
||||
private Long productLineId;
|
||||
|
||||
@TableField("created_at")
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getBitanswerBaseUrl() {
|
||||
return bitanswerBaseUrl;
|
||||
}
|
||||
|
||||
public void setBitanswerBaseUrl(String bitanswerBaseUrl) {
|
||||
this.bitanswerBaseUrl = bitanswerBaseUrl;
|
||||
}
|
||||
|
||||
public String getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public void setKind(String kind) {
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public Long getProductLineId() {
|
||||
return productLineId;
|
||||
}
|
||||
|
||||
public void setProductLineId(Long productLineId) {
|
||||
this.productLineId = productLineId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package cn.craftlabs.platform.api.persistence.integration;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PlatformIntegrationEnvironmentMapper extends BaseMapper<PlatformIntegrationEnvironment> {}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package cn.craftlabs.platform.api.persistence.integration;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@TableName("platform_product_line")
|
||||
public class PlatformProductLine {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String code;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
private Boolean enabled;
|
||||
|
||||
@TableField("created_at")
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package cn.craftlabs.platform.api.persistence.integration;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PlatformProductLineMapper extends BaseMapper<PlatformProductLine> {}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package cn.craftlabs.platform.api.security;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 服务间内部路由:{@code X-Platform-Internal-Token},与 JWT 分离。
|
||||
*/
|
||||
@Component
|
||||
public class InternalTokenAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
public static final String HEADER_NAME = "X-Platform-Internal-Token";
|
||||
|
||||
@Value("${platform.internal.token:}")
|
||||
private String expectedToken;
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(@NonNull HttpServletRequest request) {
|
||||
String uri = request.getRequestURI();
|
||||
return uri == null || !uri.startsWith("/internal/");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
if (!StringUtils.hasText(expectedToken)) {
|
||||
response.sendError(HttpStatus.UNAUTHORIZED.value());
|
||||
return;
|
||||
}
|
||||
String presented = request.getHeader(HEADER_NAME);
|
||||
if (!constantTimeEquals(presented, expectedToken)) {
|
||||
response.sendError(HttpStatus.UNAUTHORIZED.value());
|
||||
return;
|
||||
}
|
||||
var auth =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
"platform-internal",
|
||||
null,
|
||||
List.of(new SimpleGrantedAuthority("ROLE_INTERNAL")));
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private static boolean constantTimeEquals(String a, String b) {
|
||||
if (a == null || b == null) {
|
||||
return false;
|
||||
}
|
||||
byte[] ba = a.getBytes(StandardCharsets.UTF_8);
|
||||
byte[] bb = b.getBytes(StandardCharsets.UTF_8);
|
||||
return MessageDigest.isEqual(ba, bb);
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package cn.craftlabs.platform.api.service;
|
||||
|
||||
import cn.craftlabs.platform.api.domain.CallbackInboxStatus;
|
||||
import cn.craftlabs.platform.api.persistence.callback.PlatformCallbackInbox;
|
||||
import cn.craftlabs.platform.api.persistence.callback.PlatformCallbackInboxMapper;
|
||||
import cn.craftlabs.platform.api.persistence.contract.PlatformContractLine;
|
||||
import cn.craftlabs.platform.api.persistence.contract.PlatformContractLineMapper;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSn;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSnMapper;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackEventIngestRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackEventIngestResponse;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class CallbackEventIngestService {
|
||||
|
||||
private static final int SUPPORTED_SCHEMA_MAJOR = 1;
|
||||
|
||||
private final PlatformCallbackInboxMapper inboxMapper;
|
||||
private final PlatformLicenseSnMapper licenseSnMapper;
|
||||
private final PlatformContractLineMapper contractLineMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public CallbackEventIngestService(
|
||||
PlatformCallbackInboxMapper inboxMapper,
|
||||
PlatformLicenseSnMapper licenseSnMapper,
|
||||
PlatformContractLineMapper contractLineMapper,
|
||||
ObjectMapper objectMapper) {
|
||||
this.inboxMapper = inboxMapper;
|
||||
this.licenseSnMapper = licenseSnMapper;
|
||||
this.contractLineMapper = contractLineMapper;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CallbackEventIngestResponse ingest(CallbackEventIngestRequest request, String idempotencyHeader) {
|
||||
validateSchemaMajor(request.getSchemaVersion());
|
||||
String source = request.getSourceSystem().trim();
|
||||
String ext = request.getExternalMessageId().trim();
|
||||
if (!StringUtils.hasText(source) || !StringUtils.hasText(ext)) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "sourceSystem and externalMessageId must not be blank");
|
||||
}
|
||||
|
||||
PlatformCallbackInbox existing =
|
||||
inboxMapper.selectOne(
|
||||
Wrappers.lambdaQuery(PlatformCallbackInbox.class)
|
||||
.eq(PlatformCallbackInbox::getSourceSystem, source)
|
||||
.eq(PlatformCallbackInbox::getExternalMessageId, ext));
|
||||
if (existing != null) {
|
||||
return new CallbackEventIngestResponse(existing.getId(), true);
|
||||
}
|
||||
|
||||
String rawJson;
|
||||
try {
|
||||
rawJson = objectMapper.writeValueAsString(request.getRawPayload());
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "rawPayload must be JSON-serializable");
|
||||
}
|
||||
|
||||
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
OffsetDateTime receivedAt = request.getReceivedAt() != null ? request.getReceivedAt() : now;
|
||||
|
||||
String idempotency =
|
||||
firstNonBlank(
|
||||
blankToNull(request.getIdempotencyKey()),
|
||||
blankToNull(idempotencyHeader));
|
||||
|
||||
PlatformCallbackInbox row = new PlatformCallbackInbox();
|
||||
row.setSourceSystem(source);
|
||||
row.setExternalMessageId(ext);
|
||||
row.setSchemaVersion(request.getSchemaVersion().trim());
|
||||
row.setEventType(request.getEventType().trim());
|
||||
row.setStatus(CallbackInboxStatus.PENDING.name());
|
||||
row.setRawPayload(rawJson);
|
||||
row.setIdempotencyKey(idempotency);
|
||||
row.setWebhookReceiptId(blankToNull(request.getWebhookReceiptId()));
|
||||
row.setReceivedAt(receivedAt);
|
||||
row.setCreatedAt(now);
|
||||
row.setUpdatedAt(now);
|
||||
|
||||
applySnResolution(row, request.getRawPayload());
|
||||
|
||||
try {
|
||||
inboxMapper.insert(row);
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
PlatformCallbackInbox again =
|
||||
inboxMapper.selectOne(
|
||||
Wrappers.lambdaQuery(PlatformCallbackInbox.class)
|
||||
.eq(PlatformCallbackInbox::getSourceSystem, source)
|
||||
.eq(PlatformCallbackInbox::getExternalMessageId, ext));
|
||||
if (again != null) {
|
||||
return new CallbackEventIngestResponse(again.getId(), true);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return new CallbackEventIngestResponse(row.getId(), false);
|
||||
}
|
||||
|
||||
private void validateSchemaMajor(String schemaVersion) {
|
||||
if (!StringUtils.hasText(schemaVersion)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "schemaVersion is required");
|
||||
}
|
||||
String trimmed = schemaVersion.trim();
|
||||
int dot = trimmed.indexOf('.');
|
||||
String majorPart = dot < 0 ? trimmed : trimmed.substring(0, dot);
|
||||
if (!StringUtils.hasText(majorPart)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "invalid schemaVersion");
|
||||
}
|
||||
int major;
|
||||
try {
|
||||
major = Integer.parseInt(majorPart);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "unsupported schema major version: " + majorPart);
|
||||
}
|
||||
if (major != SUPPORTED_SCHEMA_MAJOR) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST, "unsupported schema major version: " + major);
|
||||
}
|
||||
}
|
||||
|
||||
private void applySnResolution(PlatformCallbackInbox row, JsonNode payload) {
|
||||
String sn = extractSnCode(payload);
|
||||
if (!StringUtils.hasText(sn)) {
|
||||
return;
|
||||
}
|
||||
row.setSnCode(sn);
|
||||
PlatformLicenseSn license =
|
||||
licenseSnMapper.selectOne(
|
||||
Wrappers.lambdaQuery(PlatformLicenseSn.class)
|
||||
.eq(PlatformLicenseSn::getSnCode, sn));
|
||||
if (license == null) {
|
||||
return;
|
||||
}
|
||||
row.setLicenseSnId(license.getId());
|
||||
row.setProjectId(license.getProjectId());
|
||||
Long lineId = license.getContractLineId();
|
||||
if (lineId != null) {
|
||||
PlatformContractLine line = contractLineMapper.selectById(lineId);
|
||||
if (line != null) {
|
||||
row.setContractId(line.getContractId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String extractSnCode(JsonNode payload) {
|
||||
if (payload == null || !payload.isObject()) {
|
||||
return null;
|
||||
}
|
||||
for (String k : List.of("sn", "snCode", "sn_code")) {
|
||||
JsonNode n = payload.get(k);
|
||||
if (n != null && n.isTextual()) {
|
||||
String t = n.asText();
|
||||
if (StringUtils.hasText(t)) {
|
||||
return t.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String blankToNull(String s) {
|
||||
return StringUtils.hasText(s) ? s.trim() : null;
|
||||
}
|
||||
|
||||
private static String firstNonBlank(String a, String b) {
|
||||
if (StringUtils.hasText(a)) {
|
||||
return a;
|
||||
}
|
||||
if (StringUtils.hasText(b)) {
|
||||
return b;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
package cn.craftlabs.platform.api.service;
|
||||
|
||||
import cn.craftlabs.platform.api.audit.AuditActions;
|
||||
import cn.craftlabs.platform.api.audit.AuditEntityTypes;
|
||||
import cn.craftlabs.platform.api.domain.CallbackInboxStatus;
|
||||
import cn.craftlabs.platform.api.persistence.callback.PlatformCallbackInbox;
|
||||
import cn.craftlabs.platform.api.persistence.callback.PlatformCallbackInboxMapper;
|
||||
import cn.craftlabs.platform.api.persistence.contract.PlatformContractMapper;
|
||||
import cn.craftlabs.platform.api.persistence.license.PlatformLicenseSnMapper;
|
||||
import cn.craftlabs.platform.api.persistence.project.PlatformProjectMapper;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackInboxLinkPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackInboxResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.CallbackInboxStatusPatchRequest;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class CallbackInboxService {
|
||||
|
||||
private final PlatformCallbackInboxMapper inboxMapper;
|
||||
private final PlatformLicenseSnMapper licenseSnMapper;
|
||||
private final PlatformProjectMapper projectMapper;
|
||||
private final PlatformContractMapper contractMapper;
|
||||
private final AuditService auditService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public CallbackInboxService(
|
||||
PlatformCallbackInboxMapper inboxMapper,
|
||||
PlatformLicenseSnMapper licenseSnMapper,
|
||||
PlatformProjectMapper projectMapper,
|
||||
PlatformContractMapper contractMapper,
|
||||
AuditService auditService,
|
||||
ObjectMapper objectMapper) {
|
||||
this.inboxMapper = inboxMapper;
|
||||
this.licenseSnMapper = licenseSnMapper;
|
||||
this.projectMapper = projectMapper;
|
||||
this.contractMapper = contractMapper;
|
||||
this.auditService = auditService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PageResponse<CallbackInboxResponse> page(
|
||||
int page,
|
||||
int size,
|
||||
String status,
|
||||
String eventType,
|
||||
String snCode,
|
||||
Long projectId,
|
||||
Long productLineId,
|
||||
Long environmentId,
|
||||
OffsetDateTime receivedFrom,
|
||||
OffsetDateTime receivedTo) {
|
||||
String st = StringUtils.hasText(status) ? status.trim() : null;
|
||||
String et = StringUtils.hasText(eventType) ? eventType.trim() : null;
|
||||
String sn = StringUtils.hasText(snCode) ? snCode.trim() : null;
|
||||
if (st != null) {
|
||||
parseStatusOrBadRequest(st);
|
||||
}
|
||||
LambdaQueryWrapper<PlatformCallbackInbox> q =
|
||||
Wrappers.lambdaQuery(PlatformCallbackInbox.class)
|
||||
.eq(st != null, PlatformCallbackInbox::getStatus, st)
|
||||
.eq(et != null, PlatformCallbackInbox::getEventType, et)
|
||||
.like(sn != null, PlatformCallbackInbox::getSnCode, sn)
|
||||
.eq(projectId != null, PlatformCallbackInbox::getProjectId, projectId)
|
||||
.eq(
|
||||
productLineId != null,
|
||||
PlatformCallbackInbox::getProductLineId,
|
||||
productLineId)
|
||||
.eq(
|
||||
environmentId != null,
|
||||
PlatformCallbackInbox::getIntegrationEnvironmentId,
|
||||
environmentId)
|
||||
.ge(receivedFrom != null, PlatformCallbackInbox::getReceivedAt, receivedFrom)
|
||||
.le(receivedTo != null, PlatformCallbackInbox::getReceivedAt, receivedTo)
|
||||
.orderByDesc(PlatformCallbackInbox::getId);
|
||||
Page<PlatformCallbackInbox> mpPage = new Page<>(page + 1L, size);
|
||||
inboxMapper.selectPage(mpPage, q);
|
||||
List<CallbackInboxResponse> content =
|
||||
mpPage.getRecords().stream()
|
||||
.map(r -> toResponse(r, false))
|
||||
.collect(Collectors.toList());
|
||||
return new PageResponse<>(content, mpPage.getTotal(), page, size);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public CallbackInboxResponse getById(long id) {
|
||||
return toResponse(requireInbox(id), true);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CallbackInboxResponse patchStatus(long id, CallbackInboxStatusPatchRequest request) {
|
||||
PlatformCallbackInbox row = requireInbox(id);
|
||||
CallbackInboxStatus from = CallbackInboxStatus.valueOf(row.getStatus());
|
||||
CallbackInboxStatus to = parseStatusOrBadRequest(request.getStatus());
|
||||
if (from == to) {
|
||||
return toResponse(row, true);
|
||||
}
|
||||
if (from != CallbackInboxStatus.PENDING) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.CONFLICT, "illegal callback inbox status transition");
|
||||
}
|
||||
if (to != CallbackInboxStatus.PROCESSED
|
||||
&& to != CallbackInboxStatus.FAILED
|
||||
&& to != CallbackInboxStatus.IGNORED) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.CONFLICT, "illegal callback inbox status transition");
|
||||
}
|
||||
String oldJson = toJson(snapshot(row));
|
||||
OffsetDateTime now = OffsetDateTime.now(ZoneOffset.UTC);
|
||||
row.setStatus(to.name());
|
||||
row.setProcessedAt(now);
|
||||
row.setProcessedByUserId(currentActorId());
|
||||
row.setUpdatedAt(now);
|
||||
inboxMapper.updateById(row);
|
||||
auditService.record(
|
||||
AuditEntityTypes.CALLBACK_INBOX,
|
||||
id,
|
||||
AuditActions.CALLBACK_INBOX_STATUS_CHANGED,
|
||||
"status",
|
||||
oldJson,
|
||||
toJson(snapshot(row)));
|
||||
return toResponse(row, true);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public CallbackInboxResponse patchLink(long id, CallbackInboxLinkPatchRequest request) {
|
||||
PlatformCallbackInbox row = requireInbox(id);
|
||||
if (request.getLicenseSnId() == null
|
||||
&& request.getProjectId() == null
|
||||
&& request.getContractId() == null) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
"at least one of licenseSnId, projectId, contractId must be provided");
|
||||
}
|
||||
String oldJson = toJson(snapshot(row));
|
||||
if (request.getLicenseSnId() != null) {
|
||||
if (licenseSnMapper.selectById(request.getLicenseSnId()) == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "license SN not found");
|
||||
}
|
||||
row.setLicenseSnId(request.getLicenseSnId());
|
||||
}
|
||||
if (request.getProjectId() != null) {
|
||||
if (projectMapper.selectById(request.getProjectId()) == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "project not found");
|
||||
}
|
||||
row.setProjectId(request.getProjectId());
|
||||
}
|
||||
if (request.getContractId() != null) {
|
||||
if (contractMapper.selectById(request.getContractId()) == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "contract not found");
|
||||
}
|
||||
row.setContractId(request.getContractId());
|
||||
}
|
||||
row.setUpdatedAt(OffsetDateTime.now(ZoneOffset.UTC));
|
||||
inboxMapper.updateById(row);
|
||||
auditService.record(
|
||||
AuditEntityTypes.CALLBACK_INBOX,
|
||||
id,
|
||||
AuditActions.CALLBACK_INBOX_LINK_UPDATED,
|
||||
null,
|
||||
oldJson,
|
||||
toJson(snapshot(row)));
|
||||
return toResponse(row, true);
|
||||
}
|
||||
|
||||
private PlatformCallbackInbox requireInbox(long id) {
|
||||
PlatformCallbackInbox row = inboxMapper.selectById(id);
|
||||
if (row == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "callback inbox not found");
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
private static CallbackInboxStatus parseStatusOrBadRequest(String raw) {
|
||||
try {
|
||||
return CallbackInboxStatus.valueOf(raw.trim());
|
||||
} catch (Exception e) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "unknown callback inbox status: " + raw);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> snapshot(PlatformCallbackInbox row) {
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("id", row.getId());
|
||||
m.put("status", row.getStatus());
|
||||
m.put("licenseSnId", row.getLicenseSnId());
|
||||
m.put("projectId", row.getProjectId());
|
||||
m.put("contractId", row.getContractId());
|
||||
m.put("snCode", row.getSnCode());
|
||||
m.put("productLineId", row.getProductLineId());
|
||||
m.put("integrationEnvironmentId", row.getIntegrationEnvironmentId());
|
||||
return m;
|
||||
}
|
||||
|
||||
private String toJson(Object value) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(value);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private CallbackInboxResponse toResponse(PlatformCallbackInbox row, boolean includePayload) {
|
||||
CallbackInboxResponse r = new CallbackInboxResponse();
|
||||
r.setId(row.getId());
|
||||
r.setSourceSystem(row.getSourceSystem());
|
||||
r.setExternalMessageId(row.getExternalMessageId());
|
||||
r.setSchemaVersion(row.getSchemaVersion());
|
||||
r.setEventType(row.getEventType());
|
||||
r.setStatus(row.getStatus());
|
||||
r.setRawPayload(includePayload ? row.getRawPayload() : null);
|
||||
r.setIdempotencyKey(row.getIdempotencyKey());
|
||||
r.setLicenseSnId(row.getLicenseSnId());
|
||||
r.setProjectId(row.getProjectId());
|
||||
r.setContractId(row.getContractId());
|
||||
r.setSnCode(row.getSnCode());
|
||||
r.setProductLineId(row.getProductLineId());
|
||||
r.setIntegrationEnvironmentId(row.getIntegrationEnvironmentId());
|
||||
r.setReceivedAt(row.getReceivedAt());
|
||||
r.setProcessedAt(row.getProcessedAt());
|
||||
r.setProcessedByUserId(row.getProcessedByUserId());
|
||||
r.setFailureReason(row.getFailureReason());
|
||||
r.setOperatorNote(row.getOperatorNote());
|
||||
r.setWebhookReceiptId(row.getWebhookReceiptId());
|
||||
r.setCreatedAt(row.getCreatedAt());
|
||||
r.setUpdatedAt(row.getUpdatedAt());
|
||||
return r;
|
||||
}
|
||||
|
||||
private static String currentActorId() {
|
||||
var a = org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication();
|
||||
if (a == null || !a.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
return a.getName();
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package cn.craftlabs.platform.api.service;
|
||||
|
||||
import cn.craftlabs.platform.api.persistence.integration.PlatformIntegrationEnvironment;
|
||||
import cn.craftlabs.platform.api.persistence.integration.PlatformIntegrationEnvironmentMapper;
|
||||
import cn.craftlabs.platform.api.persistence.integration.PlatformProductLine;
|
||||
import cn.craftlabs.platform.api.persistence.integration.PlatformProductLineMapper;
|
||||
import cn.craftlabs.platform.api.web.dto.IntegrationEnvironmentResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.PageResponse;
|
||||
import cn.craftlabs.platform.api.web.dto.ProductLineResponse;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class IntegrationCatalogService {
|
||||
|
||||
private final PlatformProductLineMapper productLineMapper;
|
||||
private final PlatformIntegrationEnvironmentMapper environmentMapper;
|
||||
|
||||
public IntegrationCatalogService(
|
||||
PlatformProductLineMapper productLineMapper,
|
||||
PlatformIntegrationEnvironmentMapper environmentMapper) {
|
||||
this.productLineMapper = productLineMapper;
|
||||
this.environmentMapper = environmentMapper;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PageResponse<ProductLineResponse> pageProductLines(int page, int size) {
|
||||
Page<PlatformProductLine> mpPage = new Page<>(page + 1L, size);
|
||||
productLineMapper.selectPage(
|
||||
mpPage, Wrappers.lambdaQuery(PlatformProductLine.class).orderByAsc(PlatformProductLine::getId));
|
||||
List<ProductLineResponse> content =
|
||||
mpPage.getRecords().stream().map(this::toProductLine).collect(Collectors.toList());
|
||||
return new PageResponse<>(content, mpPage.getTotal(), page, size);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public ProductLineResponse getProductLine(long id) {
|
||||
PlatformProductLine row = productLineMapper.selectById(id);
|
||||
if (row == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "product line not found");
|
||||
}
|
||||
return toProductLine(row);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public PageResponse<IntegrationEnvironmentResponse> pageEnvironments(int page, int size) {
|
||||
Page<PlatformIntegrationEnvironment> mpPage = new Page<>(page + 1L, size);
|
||||
environmentMapper.selectPage(
|
||||
mpPage,
|
||||
Wrappers.lambdaQuery(PlatformIntegrationEnvironment.class)
|
||||
.orderByAsc(PlatformIntegrationEnvironment::getId));
|
||||
List<IntegrationEnvironmentResponse> content =
|
||||
mpPage.getRecords().stream().map(this::toEnvironment).collect(Collectors.toList());
|
||||
return new PageResponse<>(content, mpPage.getTotal(), page, size);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public IntegrationEnvironmentResponse getEnvironment(long id) {
|
||||
PlatformIntegrationEnvironment row = environmentMapper.selectById(id);
|
||||
if (row == null) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "integration environment not found");
|
||||
}
|
||||
return toEnvironment(row);
|
||||
}
|
||||
|
||||
private ProductLineResponse toProductLine(PlatformProductLine row) {
|
||||
ProductLineResponse r = new ProductLineResponse();
|
||||
r.setId(row.getId());
|
||||
r.setCode(row.getCode());
|
||||
r.setName(row.getName());
|
||||
r.setDescription(row.getDescription());
|
||||
r.setEnabled(row.getEnabled());
|
||||
r.setCreatedAt(row.getCreatedAt());
|
||||
r.setUpdatedAt(row.getUpdatedAt());
|
||||
return r;
|
||||
}
|
||||
|
||||
private IntegrationEnvironmentResponse toEnvironment(PlatformIntegrationEnvironment row) {
|
||||
IntegrationEnvironmentResponse r = new IntegrationEnvironmentResponse();
|
||||
r.setId(row.getId());
|
||||
r.setCode(row.getCode());
|
||||
r.setName(row.getName());
|
||||
r.setBitanswerBaseUrl(row.getBitanswerBaseUrl());
|
||||
r.setKind(row.getKind());
|
||||
r.setProductLineId(row.getProductLineId());
|
||||
r.setCreatedAt(row.getCreatedAt());
|
||||
r.setUpdatedAt(row.getUpdatedAt());
|
||||
return r;
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class CallbackEventIngestRequest {
|
||||
|
||||
@NotBlank private String schemaVersion;
|
||||
|
||||
@NotBlank private String sourceSystem;
|
||||
|
||||
@NotBlank private String externalMessageId;
|
||||
|
||||
@NotBlank private String eventType;
|
||||
|
||||
private OffsetDateTime receivedAt;
|
||||
|
||||
@NotNull private JsonNode rawPayload;
|
||||
|
||||
private String webhookReceiptId;
|
||||
|
||||
private String idempotencyKey;
|
||||
|
||||
public String getSchemaVersion() {
|
||||
return schemaVersion;
|
||||
}
|
||||
|
||||
public void setSchemaVersion(String schemaVersion) {
|
||||
this.schemaVersion = schemaVersion;
|
||||
}
|
||||
|
||||
public String getSourceSystem() {
|
||||
return sourceSystem;
|
||||
}
|
||||
|
||||
public void setSourceSystem(String sourceSystem) {
|
||||
this.sourceSystem = sourceSystem;
|
||||
}
|
||||
|
||||
public String getExternalMessageId() {
|
||||
return externalMessageId;
|
||||
}
|
||||
|
||||
public void setExternalMessageId(String externalMessageId) {
|
||||
this.externalMessageId = externalMessageId;
|
||||
}
|
||||
|
||||
public String getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public void setEventType(String eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public OffsetDateTime getReceivedAt() {
|
||||
return receivedAt;
|
||||
}
|
||||
|
||||
public void setReceivedAt(OffsetDateTime receivedAt) {
|
||||
this.receivedAt = receivedAt;
|
||||
}
|
||||
|
||||
public JsonNode getRawPayload() {
|
||||
return rawPayload;
|
||||
}
|
||||
|
||||
public void setRawPayload(JsonNode rawPayload) {
|
||||
this.rawPayload = rawPayload;
|
||||
}
|
||||
|
||||
public String getWebhookReceiptId() {
|
||||
return webhookReceiptId;
|
||||
}
|
||||
|
||||
public void setWebhookReceiptId(String webhookReceiptId) {
|
||||
this.webhookReceiptId = webhookReceiptId;
|
||||
}
|
||||
|
||||
public String getIdempotencyKey() {
|
||||
return idempotencyKey;
|
||||
}
|
||||
|
||||
public void setIdempotencyKey(String idempotencyKey) {
|
||||
this.idempotencyKey = idempotencyKey;
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
public class CallbackEventIngestResponse {
|
||||
|
||||
private long inboxId;
|
||||
private boolean duplicate;
|
||||
|
||||
public CallbackEventIngestResponse() {}
|
||||
|
||||
public CallbackEventIngestResponse(long inboxId, boolean duplicate) {
|
||||
this.inboxId = inboxId;
|
||||
this.duplicate = duplicate;
|
||||
}
|
||||
|
||||
public long getInboxId() {
|
||||
return inboxId;
|
||||
}
|
||||
|
||||
public void setInboxId(long inboxId) {
|
||||
this.inboxId = inboxId;
|
||||
}
|
||||
|
||||
public boolean isDuplicate() {
|
||||
return duplicate;
|
||||
}
|
||||
|
||||
public void setDuplicate(boolean duplicate) {
|
||||
this.duplicate = duplicate;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
public class CallbackInboxLinkPatchRequest {
|
||||
|
||||
private Long licenseSnId;
|
||||
private Long projectId;
|
||||
private Long contractId;
|
||||
|
||||
public Long getLicenseSnId() {
|
||||
return licenseSnId;
|
||||
}
|
||||
|
||||
public void setLicenseSnId(Long licenseSnId) {
|
||||
this.licenseSnId = licenseSnId;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractId() {
|
||||
return contractId;
|
||||
}
|
||||
|
||||
public void setContractId(Long contractId) {
|
||||
this.contractId = contractId;
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class CallbackInboxResponse {
|
||||
|
||||
private Long id;
|
||||
private String sourceSystem;
|
||||
private String externalMessageId;
|
||||
private String schemaVersion;
|
||||
private String eventType;
|
||||
private String status;
|
||||
/** 列表接口为 null,详情接口为 JSON 字符串 */
|
||||
private String rawPayload;
|
||||
private String idempotencyKey;
|
||||
private Long licenseSnId;
|
||||
private Long projectId;
|
||||
private Long contractId;
|
||||
private String snCode;
|
||||
private Long productLineId;
|
||||
private Long integrationEnvironmentId;
|
||||
private OffsetDateTime receivedAt;
|
||||
private OffsetDateTime processedAt;
|
||||
private String processedByUserId;
|
||||
private String failureReason;
|
||||
private String operatorNote;
|
||||
private String webhookReceiptId;
|
||||
private OffsetDateTime createdAt;
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getSourceSystem() {
|
||||
return sourceSystem;
|
||||
}
|
||||
|
||||
public void setSourceSystem(String sourceSystem) {
|
||||
this.sourceSystem = sourceSystem;
|
||||
}
|
||||
|
||||
public String getExternalMessageId() {
|
||||
return externalMessageId;
|
||||
}
|
||||
|
||||
public void setExternalMessageId(String externalMessageId) {
|
||||
this.externalMessageId = externalMessageId;
|
||||
}
|
||||
|
||||
public String getSchemaVersion() {
|
||||
return schemaVersion;
|
||||
}
|
||||
|
||||
public void setSchemaVersion(String schemaVersion) {
|
||||
this.schemaVersion = schemaVersion;
|
||||
}
|
||||
|
||||
public String getEventType() {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
public void setEventType(String eventType) {
|
||||
this.eventType = eventType;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getRawPayload() {
|
||||
return rawPayload;
|
||||
}
|
||||
|
||||
public void setRawPayload(String rawPayload) {
|
||||
this.rawPayload = rawPayload;
|
||||
}
|
||||
|
||||
public String getIdempotencyKey() {
|
||||
return idempotencyKey;
|
||||
}
|
||||
|
||||
public void setIdempotencyKey(String idempotencyKey) {
|
||||
this.idempotencyKey = idempotencyKey;
|
||||
}
|
||||
|
||||
public Long getLicenseSnId() {
|
||||
return licenseSnId;
|
||||
}
|
||||
|
||||
public void setLicenseSnId(Long licenseSnId) {
|
||||
this.licenseSnId = licenseSnId;
|
||||
}
|
||||
|
||||
public Long getProjectId() {
|
||||
return projectId;
|
||||
}
|
||||
|
||||
public void setProjectId(Long projectId) {
|
||||
this.projectId = projectId;
|
||||
}
|
||||
|
||||
public Long getContractId() {
|
||||
return contractId;
|
||||
}
|
||||
|
||||
public void setContractId(Long contractId) {
|
||||
this.contractId = contractId;
|
||||
}
|
||||
|
||||
public String getSnCode() {
|
||||
return snCode;
|
||||
}
|
||||
|
||||
public void setSnCode(String snCode) {
|
||||
this.snCode = snCode;
|
||||
}
|
||||
|
||||
public Long getProductLineId() {
|
||||
return productLineId;
|
||||
}
|
||||
|
||||
public void setProductLineId(Long productLineId) {
|
||||
this.productLineId = productLineId;
|
||||
}
|
||||
|
||||
public Long getIntegrationEnvironmentId() {
|
||||
return integrationEnvironmentId;
|
||||
}
|
||||
|
||||
public void setIntegrationEnvironmentId(Long integrationEnvironmentId) {
|
||||
this.integrationEnvironmentId = integrationEnvironmentId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getReceivedAt() {
|
||||
return receivedAt;
|
||||
}
|
||||
|
||||
public void setReceivedAt(OffsetDateTime receivedAt) {
|
||||
this.receivedAt = receivedAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getProcessedAt() {
|
||||
return processedAt;
|
||||
}
|
||||
|
||||
public void setProcessedAt(OffsetDateTime processedAt) {
|
||||
this.processedAt = processedAt;
|
||||
}
|
||||
|
||||
public String getProcessedByUserId() {
|
||||
return processedByUserId;
|
||||
}
|
||||
|
||||
public void setProcessedByUserId(String processedByUserId) {
|
||||
this.processedByUserId = processedByUserId;
|
||||
}
|
||||
|
||||
public String getFailureReason() {
|
||||
return failureReason;
|
||||
}
|
||||
|
||||
public void setFailureReason(String failureReason) {
|
||||
this.failureReason = failureReason;
|
||||
}
|
||||
|
||||
public String getOperatorNote() {
|
||||
return operatorNote;
|
||||
}
|
||||
|
||||
public void setOperatorNote(String operatorNote) {
|
||||
this.operatorNote = operatorNote;
|
||||
}
|
||||
|
||||
public String getWebhookReceiptId() {
|
||||
return webhookReceiptId;
|
||||
}
|
||||
|
||||
public void setWebhookReceiptId(String webhookReceiptId) {
|
||||
this.webhookReceiptId = webhookReceiptId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public class CallbackInboxStatusPatchRequest {
|
||||
|
||||
@NotBlank private String status;
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class IntegrationEnvironmentResponse {
|
||||
|
||||
private Long id;
|
||||
private String code;
|
||||
private String name;
|
||||
private String bitanswerBaseUrl;
|
||||
private String kind;
|
||||
private Long productLineId;
|
||||
private OffsetDateTime createdAt;
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getBitanswerBaseUrl() {
|
||||
return bitanswerBaseUrl;
|
||||
}
|
||||
|
||||
public void setBitanswerBaseUrl(String bitanswerBaseUrl) {
|
||||
this.bitanswerBaseUrl = bitanswerBaseUrl;
|
||||
}
|
||||
|
||||
public String getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public void setKind(String kind) {
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public Long getProductLineId() {
|
||||
return productLineId;
|
||||
}
|
||||
|
||||
public void setProductLineId(Long productLineId) {
|
||||
this.productLineId = productLineId;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package cn.craftlabs.platform.api.web.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class ProductLineResponse {
|
||||
|
||||
private Long id;
|
||||
private String code;
|
||||
private String name;
|
||||
private String description;
|
||||
private Boolean enabled;
|
||||
private OffsetDateTime createdAt;
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public OffsetDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(OffsetDateTime createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public OffsetDateTime getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(OffsetDateTime updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,11 @@ platform:
|
||||
jwt:
|
||||
secret: ${PLATFORM_JWT_SECRET:dev-only-unsafe-change-in-production-32chars!!}
|
||||
expiry-seconds: ${PLATFORM_JWT_EXPIRY_SECONDS:43200}
|
||||
internal:
|
||||
token: ${PLATFORM_INTERNAL_TOKEN:${CRAFTLABS_PLATFORM_INTERNAL_TOKEN:}}
|
||||
|
||||
springdoc:
|
||||
swagger-ui:
|
||||
path: /swagger-ui.html
|
||||
paths-to-exclude:
|
||||
- /internal/**
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
-- I5:Callback Inbox(M5)+ 产品线/集成环境(M6 最小只读)
|
||||
CREATE TABLE platform_product_line (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(256) NOT NULL,
|
||||
description TEXT,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_platform_product_line_code UNIQUE (code)
|
||||
);
|
||||
|
||||
CREATE TABLE platform_integration_environment (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
code VARCHAR(64) NOT NULL,
|
||||
name VARCHAR(256) NOT NULL,
|
||||
bitanswer_base_url VARCHAR(512) NOT NULL,
|
||||
kind VARCHAR(32) NOT NULL,
|
||||
product_line_id BIGINT REFERENCES platform_product_line (id),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_platform_integration_environment_code UNIQUE (code)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_platform_integration_environment_product_line
|
||||
ON platform_integration_environment (product_line_id);
|
||||
|
||||
CREATE TABLE platform_callback_inbox (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
source_system VARCHAR(64) NOT NULL,
|
||||
external_message_id VARCHAR(512) NOT NULL,
|
||||
schema_version VARCHAR(64) NOT NULL,
|
||||
event_type VARCHAR(256) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
|
||||
raw_payload TEXT NOT NULL,
|
||||
idempotency_key VARCHAR(512),
|
||||
license_sn_id BIGINT REFERENCES platform_license_sn (id),
|
||||
project_id BIGINT REFERENCES platform_project (id),
|
||||
contract_id BIGINT REFERENCES platform_contract (id),
|
||||
sn_code VARCHAR(128),
|
||||
product_line_id BIGINT REFERENCES platform_product_line (id),
|
||||
integration_environment_id BIGINT REFERENCES platform_integration_environment (id),
|
||||
received_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
processed_at TIMESTAMP WITH TIME ZONE,
|
||||
processed_by_user_id VARCHAR(256),
|
||||
failure_reason TEXT,
|
||||
operator_note TEXT,
|
||||
webhook_receipt_id VARCHAR(256),
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_platform_callback_inbox_source_message UNIQUE (source_system, external_message_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_platform_callback_inbox_status ON platform_callback_inbox (status);
|
||||
CREATE INDEX idx_platform_callback_inbox_event_type ON platform_callback_inbox (event_type);
|
||||
CREATE INDEX idx_platform_callback_inbox_sn_code ON platform_callback_inbox (sn_code);
|
||||
CREATE INDEX idx_platform_callback_inbox_project ON platform_callback_inbox (project_id);
|
||||
CREATE INDEX idx_platform_callback_inbox_product_line ON platform_callback_inbox (product_line_id);
|
||||
CREATE INDEX idx_platform_callback_inbox_environment ON platform_callback_inbox (integration_environment_id);
|
||||
CREATE INDEX idx_platform_callback_inbox_received_at ON platform_callback_inbox (received_at);
|
||||
|
||||
-- 种子:本地/联调列表筛选(单测库亦执行,数据量极小)
|
||||
INSERT INTO platform_product_line (code, name, description, enabled)
|
||||
VALUES ('default', '默认产品线', 'I5 MVP 种子', TRUE);
|
||||
|
||||
INSERT INTO platform_integration_environment (code, name, bitanswer_base_url, kind, product_line_id)
|
||||
VALUES
|
||||
('dev', '开发环境', 'https://dev.bitanswer.example', 'DEV', 1),
|
||||
('prod', '生产环境', 'https://api.bitanswer.example', 'PROD', 1);
|
||||
Reference in New Issue
Block a user