From 32feaa9692600ba4e36e775c49b98e4583c125f0 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 26 Aug 2025 13:36:06 +0800 Subject: [PATCH 01/32] =?UTF-8?q?feat(integration):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20ZT-Scenic=20=E9=9B=86=E6=88=90=E6=9C=8D=E5=8A=A1=E6=A8=A1?= =?UTF-8?q?=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 FeignConfig、IntegrationProperties 等基础配置类 - 实现自定义 FeignErrorDecoder 和 IntegrationException - 添加 CommonResponse 和 PageResponse 等通用响应模型 - 定义多个 Feign 客户端接口,用于调用 ZT-Scenic 服务 - 实现 DefaultConfigIntegrationService 和 ScenicConfigIntegrationService 服务类 - 添加 ScenicIntegrationExample 示例类,展示如何使用集成服务 --- .../common/config/FeignConfig.java | 32 +++++++ .../common/config/IntegrationProperties.java | 43 +++++++++ .../common/exception/FeignErrorDecoder.java | 60 +++++++++++++ .../exception/IntegrationException.java | 33 +++++++ .../common/response/CommonResponse.java | 40 +++++++++ .../common/response/PageResponse.java | 17 ++++ .../scenic/client/DefaultConfigClient.java | 28 ++++++ .../scenic/client/ScenicConfigV2Client.java | 44 ++++++++++ .../client/ScenicConfigWithDefaultClient.java | 15 ++++ .../scenic/client/ScenicMetaClient.java | 40 +++++++++ .../scenic/client/ScenicV2Client.java | 39 +++++++++ .../config/ScenicIntegrationConfig.java | 15 ++++ .../scenic/dto/config/BatchConfigRequest.java | 26 ++++++ .../dto/config/BatchUpdateResponse.java | 16 ++++ .../dto/config/ConfigWithDefaultResponse.java | 16 ++++ .../dto/config/CreateConfigRequest.java | 23 +++++ .../scenic/dto/config/DefaultConfigDTO.java | 19 ++++ .../scenic/dto/config/ScenicConfigV2DTO.java | 36 ++++++++ .../dto/config/UpdateConfigRequest.java | 16 ++++ .../scenic/dto/filter/FilterCondition.java | 20 +++++ .../dto/filter/ScenicFilterPageResponse.java | 30 +++++++ .../dto/filter/ScenicFilterRequest.java | 22 +++++ .../dto/meta/BatchSetFieldEnabledRequest.java | 26 ++++++ .../dto/meta/EnabledFieldsResponse.java | 12 +++ .../scenic/dto/meta/FieldConfigDTO.java | 22 +++++ .../dto/meta/SetFieldEnabledRequest.java | 13 +++ .../dto/scenic/CreateScenicRequest.java | 21 +++++ .../scenic/dto/scenic/ScenicV2DTO.java | 25 ++++++ .../dto/scenic/ScenicV2WithConfigDTO.java | 14 +++ .../dto/scenic/UpdateScenicRequest.java | 16 ++++ .../example/ScenicIntegrationExample.java | 78 +++++++++++++++++ .../DefaultConfigIntegrationService.java | 60 +++++++++++++ .../ScenicConfigIntegrationService.java | 87 +++++++++++++++++++ .../service/ScenicIntegrationService.java | 77 ++++++++++++++++ .../service/ScenicMetaIntegrationService.java | 76 ++++++++++++++++ src/main/resources/application-dev.yml | 19 +++- src/main/resources/application-prod.yml | 7 +- 37 files changed, 1181 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/integration/common/config/FeignConfig.java create mode 100644 src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java create mode 100644 src/main/java/com/ycwl/basic/integration/common/exception/FeignErrorDecoder.java create mode 100644 src/main/java/com/ycwl/basic/integration/common/exception/IntegrationException.java create mode 100644 src/main/java/com/ycwl/basic/integration/common/response/CommonResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/common/response/PageResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/client/DefaultConfigClient.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigV2Client.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/config/ScenicIntegrationConfig.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchConfigRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/config/ConfigWithDefaultResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/config/CreateConfigRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/config/DefaultConfigDTO.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/config/ScenicConfigV2DTO.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/config/UpdateConfigRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/filter/FilterCondition.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/filter/ScenicFilterPageResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/filter/ScenicFilterRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/meta/BatchSetFieldEnabledRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/meta/EnabledFieldsResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/meta/FieldConfigDTO.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/meta/SetFieldEnabledRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/CreateScenicRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2DTO.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigDTO.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/UpdateScenicRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/service/DefaultConfigIntegrationService.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/service/ScenicMetaIntegrationService.java diff --git a/src/main/java/com/ycwl/basic/integration/common/config/FeignConfig.java b/src/main/java/com/ycwl/basic/integration/common/config/FeignConfig.java new file mode 100644 index 0000000..de1a873 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/config/FeignConfig.java @@ -0,0 +1,32 @@ +package com.ycwl.basic.integration.common.config; + +import com.ycwl.basic.integration.common.exception.FeignErrorDecoder; +import feign.RequestInterceptor; +import feign.codec.ErrorDecoder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +@RequiredArgsConstructor +public class FeignConfig { + + private final FeignErrorDecoder feignErrorDecoder; + + @Bean + public ErrorDecoder errorDecoder() { + return feignErrorDecoder; + } + + @Bean + public RequestInterceptor requestInterceptor() { + return template -> { + template.header("Accept", "application/json"); + template.header("Content-Type", "application/json"); + // 可以在这里添加统一的鉴权头 + // template.header("Authorization", "Bearer " + getToken()); + }; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java b/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java new file mode 100644 index 0000000..8baa61a --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/config/IntegrationProperties.java @@ -0,0 +1,43 @@ +package com.ycwl.basic.integration.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.context.config.annotation.RefreshScope; +import org.springframework.stereotype.Component; + +@Data +@Component +@RefreshScope +@ConfigurationProperties(prefix = "integration") +public class IntegrationProperties { + + /** + * 景区服务配置 + */ + private ScenicConfig scenic = new ScenicConfig(); + + @Data + public static class ScenicConfig { + /** + * 是否启用景区服务集成 + */ + private boolean enabled = true; + + /** + * 服务名称 + */ + private String serviceName = "zt-scenic"; + + /** + * 超时配置(毫秒) + */ + private int connectTimeout = 5000; + private int readTimeout = 10000; + + /** + * 重试配置 + */ + private boolean retryEnabled = false; + private int maxRetries = 3; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/exception/FeignErrorDecoder.java b/src/main/java/com/ycwl/basic/integration/common/exception/FeignErrorDecoder.java new file mode 100644 index 0000000..8d00405 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/exception/FeignErrorDecoder.java @@ -0,0 +1,60 @@ +package com.ycwl.basic.integration.common.exception; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ycwl.basic.integration.common.response.CommonResponse; +import feign.Response; +import feign.codec.ErrorDecoder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Slf4j +@Component +public class FeignErrorDecoder implements ErrorDecoder { + + private final ErrorDecoder defaultDecoder = new Default(); + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public Exception decode(String methodKey, Response response) { + log.warn("Feign调用失败, methodKey: {}, status: {}, reason: {}", + methodKey, response.status(), response.reason()); + + try { + if (response.body() != null) { + String body = new String(response.body().asInputStream().readAllBytes(), StandardCharsets.UTF_8); + log.warn("响应内容: {}", body); + + try { + CommonResponse errorResponse = objectMapper.readValue(body, CommonResponse.class); + if (errorResponse.getCode() != null && !errorResponse.getCode().equals(200)) { + return new IntegrationException( + errorResponse.getCode(), + errorResponse.getMessage() != null ? errorResponse.getMessage() : "服务调用失败", + extractServiceName(methodKey) + ); + } + } catch (Exception e) { + log.warn("解析错误响应失败", e); + } + } + } catch (IOException e) { + log.error("读取响应体失败", e); + } + + return new IntegrationException( + response.status(), + String.format("服务调用失败: %s", response.reason()), + extractServiceName(methodKey) + ); + } + + private String extractServiceName(String methodKey) { + if (methodKey != null && methodKey.contains("#")) { + return methodKey.substring(0, methodKey.indexOf("#")); + } + return "unknown"; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/exception/IntegrationException.java b/src/main/java/com/ycwl/basic/integration/common/exception/IntegrationException.java new file mode 100644 index 0000000..d6d4657 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/exception/IntegrationException.java @@ -0,0 +1,33 @@ +package com.ycwl.basic.integration.common.exception; + +import lombok.Getter; + +@Getter +public class IntegrationException extends RuntimeException { + private final Integer code; + private final String serviceName; + + public IntegrationException(Integer code, String message) { + super(message); + this.code = code; + this.serviceName = null; + } + + public IntegrationException(Integer code, String message, String serviceName) { + super(message); + this.code = code; + this.serviceName = serviceName; + } + + public IntegrationException(Integer code, String message, Throwable cause) { + super(message, cause); + this.code = code; + this.serviceName = null; + } + + public IntegrationException(Integer code, String message, String serviceName, Throwable cause) { + super(message, cause); + this.code = code; + this.serviceName = serviceName; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/response/CommonResponse.java b/src/main/java/com/ycwl/basic/integration/common/response/CommonResponse.java new file mode 100644 index 0000000..2d7d7f1 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/response/CommonResponse.java @@ -0,0 +1,40 @@ +package com.ycwl.basic.integration.common.response; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@JsonInclude(JsonInclude.Include.NON_NULL) +public class CommonResponse { + private Integer code; + private String message; + private T data; + + public static CommonResponse success() { + return new CommonResponse<>(200, "OK", null); + } + + public static CommonResponse success(T data) { + return new CommonResponse<>(200, "OK", data); + } + + public static CommonResponse success(String message, T data) { + return new CommonResponse<>(200, message, data); + } + + public static CommonResponse error(Integer code, String message) { + return new CommonResponse<>(code, message, null); + } + + public static CommonResponse error(String message) { + return new CommonResponse<>(5000, message, null); + } + + public boolean isSuccess() { + return code != null && code == 200; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/common/response/PageResponse.java b/src/main/java/com/ycwl/basic/integration/common/response/PageResponse.java new file mode 100644 index 0000000..605932d --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/response/PageResponse.java @@ -0,0 +1,17 @@ +package com.ycwl.basic.integration.common.response; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PageResponse { + private List list; + private Long total; + private Integer page; + private Integer pageSize; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/DefaultConfigClient.java b/src/main/java/com/ycwl/basic/integration/scenic/client/DefaultConfigClient.java new file mode 100644 index 0000000..c2f7bca --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/DefaultConfigClient.java @@ -0,0 +1,28 @@ +package com.ycwl.basic.integration.scenic.client; + +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.dto.config.DefaultConfigDTO; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@FeignClient(name = "zt-scenic", path = "/api/scenic/default-config") +public interface DefaultConfigClient { + + @GetMapping("/") + CommonResponse> listDefaultConfigs(); + + @GetMapping("/{configKey}") + CommonResponse getDefaultConfig(@PathVariable("configKey") String configKey); + + @PostMapping("/") + CommonResponse createDefaultConfig(@RequestBody DefaultConfigDTO request); + + @PutMapping("/{configKey}") + CommonResponse updateDefaultConfig(@PathVariable("configKey") String configKey, + @RequestBody DefaultConfigDTO request); + + @DeleteMapping("/{configKey}") + CommonResponse deleteDefaultConfig(@PathVariable("configKey") String configKey); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigV2Client.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigV2Client.java new file mode 100644 index 0000000..3016812 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigV2Client.java @@ -0,0 +1,44 @@ +package com.ycwl.basic.integration.scenic.client; + +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.dto.config.*; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@FeignClient(name = "zt-scenic", path = "/api/scenic/config/v2") +public interface ScenicConfigV2Client { + + @GetMapping("/{scenicId}") + CommonResponse> listConfigs(@PathVariable("scenicId") Long scenicId); + + @GetMapping("/{scenicId}/key/{configKey}") + CommonResponse getConfigByKey(@PathVariable("scenicId") Long scenicId, + @PathVariable("configKey") String configKey); + + @GetMapping("/{scenicId}/keys") + CommonResponse> getFlatConfigs(@PathVariable("scenicId") Long scenicId); + + @PostMapping("/{scenicId}") + CommonResponse createConfig(@PathVariable("scenicId") Long scenicId, + @RequestBody CreateConfigRequest request); + + @PutMapping("/{scenicId}/{id}") + CommonResponse updateConfig(@PathVariable("scenicId") Long scenicId, + @PathVariable("id") String id, + @RequestBody UpdateConfigRequest request); + + @DeleteMapping("/{scenicId}/{id}") + CommonResponse deleteConfig(@PathVariable("scenicId") Long scenicId, + @PathVariable("id") String id); + + @PostMapping("/{scenicId}/batch") + CommonResponse batchUpdateConfigs(@PathVariable("scenicId") Long scenicId, + @RequestBody BatchConfigRequest request); + + @PostMapping("/{scenicId}/batchFlatUpdate") + CommonResponse batchFlatUpdateConfigs(@PathVariable("scenicId") Long scenicId, + @RequestBody Map configs); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java new file mode 100644 index 0000000..38c8005 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java @@ -0,0 +1,15 @@ +package com.ycwl.basic.integration.scenic.client; + +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.dto.config.ConfigWithDefaultResponse; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +@FeignClient(name = "zt-scenic", path = "/api/scenic/config-with-default") +public interface ScenicConfigWithDefaultClient { + + @GetMapping("/{scenicId}/{configKey}") + CommonResponse getConfigWithDefault(@PathVariable("scenicId") Long scenicId, + @PathVariable("configKey") String configKey); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java new file mode 100644 index 0000000..df28f20 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java @@ -0,0 +1,40 @@ +package com.ycwl.basic.integration.scenic.client; + +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.dto.meta.*; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@FeignClient(name = "zt-scenic", path = "/api/scenic/meta") +public interface ScenicMetaClient { + + @GetMapping("/{scenicId}/fields/enabled") + CommonResponse getEnabledFields(@PathVariable("scenicId") Long scenicId); + + @GetMapping("/fields/all") + CommonResponse> getAllFields(); + + @GetMapping("/fields/{fieldKey}") + CommonResponse getFieldConfig(@PathVariable("fieldKey") String fieldKey); + + @PostMapping("/{scenicId}/fields/{fieldKey}/enabled") + CommonResponse setFieldEnabled(@PathVariable("scenicId") Long scenicId, + @PathVariable("fieldKey") String fieldKey, + @RequestBody SetFieldEnabledRequest request); + + @PostMapping("/{scenicId}/fields/batch-enabled") + CommonResponse batchSetFieldEnabled(@PathVariable("scenicId") Long scenicId, + @RequestBody BatchSetFieldEnabledRequest request); + + @GetMapping("/{scenicId}/with-config") + CommonResponse getScenicWithConfigEnhanced(@PathVariable("scenicId") Long scenicId); + + @PutMapping("/{scenicId}/config") + CommonResponse updateConfigEnhanced(@PathVariable("scenicId") Long scenicId, + @RequestBody Map configs); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java new file mode 100644 index 0000000..2d56ae0 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java @@ -0,0 +1,39 @@ +package com.ycwl.basic.integration.scenic.client; + +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse; +import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; +import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@FeignClient(name = "zt-scenic", path = "/api/scenic/v2") +public interface ScenicV2Client { + + @GetMapping("/{scenicId}") + CommonResponse getScenic(@PathVariable("scenicId") Long scenicId); + + @GetMapping("/{scenicId}/with-config") + CommonResponse getScenicWithConfig(@PathVariable("scenicId") Long scenicId); + + @GetMapping("/{scenicId}/flat") + CommonResponse> getScenicFlatConfig(@PathVariable("scenicId") Long scenicId); + + @PostMapping("/") + CommonResponse createScenic(@RequestBody CreateScenicRequest request); + + @PutMapping("/{scenicId}") + CommonResponse updateScenic(@PathVariable("scenicId") Long scenicId, + @RequestBody UpdateScenicRequest request); + + @DeleteMapping("/{scenicId}") + CommonResponse deleteScenic(@PathVariable("scenicId") Long scenicId); + + @PostMapping("/filter") + CommonResponse filterScenics(@RequestBody ScenicFilterRequest request); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/config/ScenicIntegrationConfig.java b/src/main/java/com/ycwl/basic/integration/scenic/config/ScenicIntegrationConfig.java new file mode 100644 index 0000000..5f7adec --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/config/ScenicIntegrationConfig.java @@ -0,0 +1,15 @@ +package com.ycwl.basic.integration.scenic.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Slf4j +@Configuration +@ConfigurationProperties(prefix = "integration.scenic") +public class ScenicIntegrationConfig { + + public ScenicIntegrationConfig() { + log.info("ZT-Scenic集成配置初始化完成"); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchConfigRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchConfigRequest.java new file mode 100644 index 0000000..47b3800 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchConfigRequest.java @@ -0,0 +1,26 @@ +package com.ycwl.basic.integration.scenic.dto.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import java.util.List; + +@Data +public class BatchConfigRequest { + @JsonProperty("configs") + @NotEmpty(message = "配置列表不能为空") + @Valid + private List configs; + + @Data + public static class BatchConfigItem { + @JsonProperty("configKey") + @NotEmpty(message = "配置键不能为空") + private String configKey; + + @JsonProperty("configValue") + private String configValue; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java new file mode 100644 index 0000000..d3f35b6 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/BatchUpdateResponse.java @@ -0,0 +1,16 @@ +package com.ycwl.basic.integration.scenic.dto.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class BatchUpdateResponse { + @JsonProperty("updatedCount") + private Integer updatedCount; + + @JsonProperty("createdCount") + private Integer createdCount; + + @JsonProperty("message") + private String message; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ConfigWithDefaultResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ConfigWithDefaultResponse.java new file mode 100644 index 0000000..19cecb4 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ConfigWithDefaultResponse.java @@ -0,0 +1,16 @@ +package com.ycwl.basic.integration.scenic.dto.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class ConfigWithDefaultResponse { + @JsonProperty("configKey") + private String configKey; + + @JsonProperty("configValue") + private String configValue; + + @JsonProperty("source") + private String source; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/CreateConfigRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/CreateConfigRequest.java new file mode 100644 index 0000000..b5e9dd9 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/CreateConfigRequest.java @@ -0,0 +1,23 @@ +package com.ycwl.basic.integration.scenic.dto.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; + +@Data +public class CreateConfigRequest { + @JsonProperty("configKey") + @NotBlank(message = "配置键不能为空") + private String configKey; + + @JsonProperty("configValue") + private String configValue; + + @JsonProperty("configType") + @NotBlank(message = "配置类型不能为空") + private String configType; + + @JsonProperty("description") + private String description; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/DefaultConfigDTO.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/DefaultConfigDTO.java new file mode 100644 index 0000000..987327b --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/DefaultConfigDTO.java @@ -0,0 +1,19 @@ +package com.ycwl.basic.integration.scenic.dto.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class DefaultConfigDTO { + @JsonProperty("configKey") + private String configKey; + + @JsonProperty("configValue") + private String configValue; + + @JsonProperty("configType") + private String configType; + + @JsonProperty("description") + private String description; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ScenicConfigV2DTO.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ScenicConfigV2DTO.java new file mode 100644 index 0000000..6e9f5b7 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ScenicConfigV2DTO.java @@ -0,0 +1,36 @@ +package com.ycwl.basic.integration.scenic.dto.config; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class ScenicConfigV2DTO { + @JsonProperty("id") + private String id; + + @JsonProperty("scenicID") + @JsonAlias({"scenicId", "scenicID"}) + private String scenicId; + + @JsonProperty("configKey") + private String configKey; + + @JsonProperty("configValue") + private String configValue; + + @JsonProperty("configType") + private String configType; + + @JsonProperty("description") + private String description; + + @JsonProperty("isActive") + private Integer isActive; + + @JsonProperty("createTime") + private Long createTime; + + @JsonProperty("updateTime") + private Long updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/UpdateConfigRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/UpdateConfigRequest.java new file mode 100644 index 0000000..518ae50 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/UpdateConfigRequest.java @@ -0,0 +1,16 @@ +package com.ycwl.basic.integration.scenic.dto.config; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class UpdateConfigRequest { + @JsonProperty("configKey") + private String configKey; + + @JsonProperty("configValue") + private String configValue; + + @JsonProperty("description") + private String description; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/FilterCondition.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/FilterCondition.java new file mode 100644 index 0000000..7a35425 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/FilterCondition.java @@ -0,0 +1,20 @@ +package com.ycwl.basic.integration.scenic.dto.filter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; + +@Data +public class FilterCondition { + @JsonProperty("configKey") + @NotBlank(message = "配置键不能为空") + private String configKey; + + @JsonProperty("configValue") + @NotBlank(message = "配置值不能为空") + private String configValue; + + @JsonProperty("operator") + private String operator = "eq"; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/ScenicFilterPageResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/ScenicFilterPageResponse.java new file mode 100644 index 0000000..154cb2e --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/ScenicFilterPageResponse.java @@ -0,0 +1,30 @@ +package com.ycwl.basic.integration.scenic.dto.filter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ScenicFilterPageResponse { + @JsonProperty("list") + private List list; + + @JsonProperty("total") + private Long total; + + @JsonProperty("page") + private Integer page; + + @JsonProperty("pageSize") + private Integer pageSize; + + @Data + public static class ScenicFilterItem { + @JsonProperty("id") + private String id; + + @JsonProperty("name") + private String name; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/ScenicFilterRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/ScenicFilterRequest.java new file mode 100644 index 0000000..08bb6ff --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/filter/ScenicFilterRequest.java @@ -0,0 +1,22 @@ +package com.ycwl.basic.integration.scenic.dto.filter; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import java.util.List; + +@Data +public class ScenicFilterRequest { + @JsonProperty("filters") + @NotEmpty(message = "筛选条件不能为空") + @Valid + private List filters; + + @JsonProperty("page") + private Integer page = 1; + + @JsonProperty("pageSize") + private Integer pageSize = 20; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/BatchSetFieldEnabledRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/BatchSetFieldEnabledRequest.java new file mode 100644 index 0000000..c304f4e --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/BatchSetFieldEnabledRequest.java @@ -0,0 +1,26 @@ +package com.ycwl.basic.integration.scenic.dto.meta; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import java.util.List; + +@Data +public class BatchSetFieldEnabledRequest { + @JsonProperty("fields") + @NotEmpty(message = "字段列表不能为空") + @Valid + private List fields; + + @Data + public static class FieldEnabledItem { + @JsonProperty("fieldKey") + @NotEmpty(message = "字段键不能为空") + private String fieldKey; + + @JsonProperty("enabled") + private Boolean enabled; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/EnabledFieldsResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/EnabledFieldsResponse.java new file mode 100644 index 0000000..5d2c48b --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/EnabledFieldsResponse.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.integration.scenic.dto.meta; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class EnabledFieldsResponse { + @JsonProperty("enabledFields") + private List enabledFields; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/FieldConfigDTO.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/FieldConfigDTO.java new file mode 100644 index 0000000..f6cead5 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/FieldConfigDTO.java @@ -0,0 +1,22 @@ +package com.ycwl.basic.integration.scenic.dto.meta; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class FieldConfigDTO { + @JsonProperty("fieldKey") + private String fieldKey; + + @JsonProperty("fieldName") + private String fieldName; + + @JsonProperty("fieldType") + private String fieldType; + + @JsonProperty("description") + private String description; + + @JsonProperty("enabled") + private Boolean enabled; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/SetFieldEnabledRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/SetFieldEnabledRequest.java new file mode 100644 index 0000000..55ad4fa --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/SetFieldEnabledRequest.java @@ -0,0 +1,13 @@ +package com.ycwl.basic.integration.scenic.dto.meta; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +@Data +public class SetFieldEnabledRequest { + @JsonProperty("enabled") + @NotNull(message = "enabled状态不能为空") + private Boolean enabled; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/CreateScenicRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/CreateScenicRequest.java new file mode 100644 index 0000000..78a6c80 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/CreateScenicRequest.java @@ -0,0 +1,21 @@ +package com.ycwl.basic.integration.scenic.dto.scenic; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +@Data +public class CreateScenicRequest { + @JsonProperty("name") + @NotBlank(message = "景区名称不能为空") + private String name; + + @JsonProperty("mpId") + @NotNull(message = "小程序ID不能为空") + private Integer mpId; + + @JsonProperty("status") + private Integer status = 1; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2DTO.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2DTO.java new file mode 100644 index 0000000..4814f9f --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2DTO.java @@ -0,0 +1,25 @@ +package com.ycwl.basic.integration.scenic.dto.scenic; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class ScenicV2DTO { + @JsonProperty("id") + private String id; + + @JsonProperty("name") + private String name; + + @JsonProperty("mpId") + private Integer mpId; + + @JsonProperty("status") + private Integer status; + + @JsonProperty("createTime") + private Long createTime; + + @JsonProperty("updateTime") + private Long updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigDTO.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigDTO.java new file mode 100644 index 0000000..18e1dbb --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigDTO.java @@ -0,0 +1,14 @@ +package com.ycwl.basic.integration.scenic.dto.scenic; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Map; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ScenicV2WithConfigDTO extends ScenicV2DTO { + @JsonProperty("config") + private Map config; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/UpdateScenicRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/UpdateScenicRequest.java new file mode 100644 index 0000000..b76b5d4 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/UpdateScenicRequest.java @@ -0,0 +1,16 @@ +package com.ycwl.basic.integration.scenic.dto.scenic; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class UpdateScenicRequest { + @JsonProperty("name") + private String name; + + @JsonProperty("mpId") + private Integer mpId; + + @JsonProperty("status") + private Integer status; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java b/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java new file mode 100644 index 0000000..f1f50e3 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/example/ScenicIntegrationExample.java @@ -0,0 +1,78 @@ +package com.ycwl.basic.integration.scenic.example; + +import com.ycwl.basic.integration.scenic.dto.config.CreateConfigRequest; +import com.ycwl.basic.integration.scenic.dto.filter.FilterCondition; +import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; +import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; +import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService; +import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.Collections; + +/** + * ZT-Scenic集成服务使用示例 + * 仅供参考,实际使用时根据业务需要调用相应的服务方法 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ScenicIntegrationExample { + + private final ScenicIntegrationService scenicIntegrationService; + private final ScenicConfigIntegrationService scenicConfigIntegrationService; + + /** + * 示例:创建景区并设置配置 + */ + public void createScenicWithConfig() { + try { + // 1. 创建景区 + CreateScenicRequest createRequest = new CreateScenicRequest(); + createRequest.setName("测试景区"); + createRequest.setMpId(1001); + + var scenic = scenicIntegrationService.createScenic(createRequest); + log.info("创建景区成功: {}", scenic.getName()); + + // 2. 为景区添加配置 + CreateConfigRequest configRequest = new CreateConfigRequest(); + configRequest.setConfigKey("tour_time"); + configRequest.setConfigValue("120"); + configRequest.setConfigType("int"); + configRequest.setDescription("游览时长"); + + var config = scenicConfigIntegrationService.createConfig( + Long.valueOf(scenic.getId()), configRequest); + log.info("创建配置成功: {} = {}", config.getConfigKey(), config.getConfigValue()); + + } catch (Exception e) { + log.error("创建景区和配置失败", e); + } + } + + /** + * 示例:筛选景区 + */ + public void filterScenics() { + try { + FilterCondition condition = new FilterCondition(); + condition.setConfigKey("tour_time"); + condition.setConfigValue("120"); + condition.setOperator("gte"); + + ScenicFilterRequest filterRequest = new ScenicFilterRequest(); + filterRequest.setFilters(Collections.singletonList(condition)); + filterRequest.setPage(1); + filterRequest.setPageSize(10); + + var result = scenicIntegrationService.filterScenics(filterRequest); + log.info("筛选到 {} 个景区", result.getTotal()); + + } catch (Exception e) { + log.error("筛选景区失败", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/DefaultConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/DefaultConfigIntegrationService.java new file mode 100644 index 0000000..bb48ebe --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/DefaultConfigIntegrationService.java @@ -0,0 +1,60 @@ +package com.ycwl.basic.integration.scenic.service; + +import com.ycwl.basic.integration.common.exception.IntegrationException; +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.client.DefaultConfigClient; +import com.ycwl.basic.integration.scenic.dto.config.DefaultConfigDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class DefaultConfigIntegrationService { + + private final DefaultConfigClient defaultConfigClient; + + public List listDefaultConfigs() { + log.info("获取默认配置列表"); + CommonResponse> response = defaultConfigClient.listDefaultConfigs(); + return handleResponse(response, "获取默认配置列表失败"); + } + + public DefaultConfigDTO getDefaultConfig(String configKey) { + log.info("获取指定默认配置, configKey: {}", configKey); + CommonResponse response = defaultConfigClient.getDefaultConfig(configKey); + return handleResponse(response, "获取指定默认配置失败"); + } + + public DefaultConfigDTO createDefaultConfig(DefaultConfigDTO request) { + log.info("创建默认配置, configKey: {}", request.getConfigKey()); + CommonResponse response = defaultConfigClient.createDefaultConfig(request); + return handleResponse(response, "创建默认配置失败"); + } + + public DefaultConfigDTO updateDefaultConfig(String configKey, DefaultConfigDTO request) { + log.info("更新默认配置, configKey: {}", configKey); + CommonResponse response = defaultConfigClient.updateDefaultConfig(configKey, request); + return handleResponse(response, "更新默认配置失败"); + } + + public void deleteDefaultConfig(String configKey) { + log.info("删除默认配置, configKey: {}", configKey); + CommonResponse response = defaultConfigClient.deleteDefaultConfig(configKey); + handleResponse(response, "删除默认配置失败"); + } + + private T handleResponse(CommonResponse response, String errorMessage) { + if (response == null || !response.isSuccess()) { + String msg = response != null && response.getMessage() != null + ? response.getMessage() + : errorMessage; + Integer code = response != null ? response.getCode() : 5000; + throw new IntegrationException(code, msg, "zt-scenic"); + } + return response.getData(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java new file mode 100644 index 0000000..0351b90 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java @@ -0,0 +1,87 @@ +package com.ycwl.basic.integration.scenic.service; + +import com.ycwl.basic.integration.common.exception.IntegrationException; +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client; +import com.ycwl.basic.integration.scenic.client.ScenicConfigWithDefaultClient; +import com.ycwl.basic.integration.scenic.dto.config.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ScenicConfigIntegrationService { + + private final ScenicConfigV2Client scenicConfigV2Client; + private final ScenicConfigWithDefaultClient scenicConfigWithDefaultClient; + + public List listConfigs(Long scenicId) { + log.info("获取景区配置列表, scenicId: {}", scenicId); + CommonResponse> response = scenicConfigV2Client.listConfigs(scenicId); + return handleResponse(response, "获取景区配置列表失败"); + } + + public ScenicConfigV2DTO getConfigByKey(Long scenicId, String configKey) { + log.info("根据键获取景区配置, scenicId: {}, configKey: {}", scenicId, configKey); + CommonResponse response = scenicConfigV2Client.getConfigByKey(scenicId, configKey); + return handleResponse(response, "根据键获取景区配置失败"); + } + + public Map getFlatConfigs(Long scenicId) { + log.info("获取景区扁平化配置, scenicId: {}", scenicId); + CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); + return handleResponse(response, "获取景区扁平化配置失败"); + } + + public ScenicConfigV2DTO createConfig(Long scenicId, CreateConfigRequest request) { + log.info("创建景区配置, scenicId: {}, configKey: {}", scenicId, request.getConfigKey()); + CommonResponse response = scenicConfigV2Client.createConfig(scenicId, request); + return handleResponse(response, "创建景区配置失败"); + } + + public ScenicConfigV2DTO updateConfig(Long scenicId, String id, UpdateConfigRequest request) { + log.info("更新景区配置, scenicId: {}, id: {}", scenicId, id); + CommonResponse response = scenicConfigV2Client.updateConfig(scenicId, id, request); + return handleResponse(response, "更新景区配置失败"); + } + + public void deleteConfig(Long scenicId, String id) { + log.info("删除景区配置, scenicId: {}, id: {}", scenicId, id); + CommonResponse response = scenicConfigV2Client.deleteConfig(scenicId, id); + handleResponse(response, "删除景区配置失败"); + } + + public BatchUpdateResponse batchUpdateConfigs(Long scenicId, BatchConfigRequest request) { + log.info("批量更新景区配置, scenicId: {}, configs count: {}", scenicId, request.getConfigs().size()); + CommonResponse response = scenicConfigV2Client.batchUpdateConfigs(scenicId, request); + return handleResponse(response, "批量更新景区配置失败"); + } + + public BatchUpdateResponse batchFlatUpdateConfigs(Long scenicId, Map configs) { + log.info("扁平化批量更新景区配置, scenicId: {}, configs count: {}", scenicId, configs.size()); + CommonResponse response = scenicConfigV2Client.batchFlatUpdateConfigs(scenicId, configs); + return handleResponse(response, "扁平化批量更新景区配置失败"); + } + + public ConfigWithDefaultResponse getConfigWithDefault(Long scenicId, String configKey) { + log.info("获取带默认值的配置, scenicId: {}, configKey: {}", scenicId, configKey); + CommonResponse response = scenicConfigWithDefaultClient.getConfigWithDefault(scenicId, configKey); + return handleResponse(response, "获取带默认值的配置失败"); + } + + private T handleResponse(CommonResponse response, String errorMessage) { + if (response == null || !response.isSuccess()) { + String msg = response != null && response.getMessage() != null + ? response.getMessage() + : errorMessage; + Integer code = response != null ? response.getCode() : 5000; + throw new IntegrationException(code, msg, "zt-scenic"); + } + return response.getData(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java new file mode 100644 index 0000000..6c25b77 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java @@ -0,0 +1,77 @@ +package com.ycwl.basic.integration.scenic.service; + +import com.ycwl.basic.integration.common.exception.IntegrationException; +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.client.ScenicV2Client; +import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse; +import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; +import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ScenicIntegrationService { + + private final ScenicV2Client scenicV2Client; + + public ScenicV2DTO getScenic(Long scenicId) { + log.info("获取景区信息, scenicId: {}", scenicId); + CommonResponse response = scenicV2Client.getScenic(scenicId); + return handleResponse(response, "获取景区信息失败"); + } + + public ScenicV2WithConfigDTO getScenicWithConfig(Long scenicId) { + log.info("获取景区配置信息, scenicId: {}", scenicId); + CommonResponse response = scenicV2Client.getScenicWithConfig(scenicId); + return handleResponse(response, "获取景区配置信息失败"); + } + + public Map getScenicFlatConfig(Long scenicId) { + log.info("获取景区扁平化配置, scenicId: {}", scenicId); + CommonResponse> response = scenicV2Client.getScenicFlatConfig(scenicId); + return handleResponse(response, "获取景区扁平化配置失败"); + } + + public ScenicV2DTO createScenic(CreateScenicRequest request) { + log.info("创建景区, name: {}", request.getName()); + CommonResponse response = scenicV2Client.createScenic(request); + return handleResponse(response, "创建景区失败"); + } + + public ScenicV2DTO updateScenic(Long scenicId, UpdateScenicRequest request) { + log.info("更新景区信息, scenicId: {}", scenicId); + CommonResponse response = scenicV2Client.updateScenic(scenicId, request); + return handleResponse(response, "更新景区信息失败"); + } + + public void deleteScenic(Long scenicId) { + log.info("删除景区, scenicId: {}", scenicId); + CommonResponse response = scenicV2Client.deleteScenic(scenicId); + handleResponse(response, "删除景区失败"); + } + + public ScenicFilterPageResponse filterScenics(ScenicFilterRequest request) { + log.info("筛选景区, filters: {}", request.getFilters().size()); + CommonResponse response = scenicV2Client.filterScenics(request); + return handleResponse(response, "筛选景区失败"); + } + + private T handleResponse(CommonResponse response, String errorMessage) { + if (response == null || !response.isSuccess()) { + String msg = response != null && response.getMessage() != null + ? response.getMessage() + : errorMessage; + Integer code = response != null ? response.getCode() : 5000; + throw new IntegrationException(code, msg, "zt-scenic"); + } + return response.getData(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicMetaIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicMetaIntegrationService.java new file mode 100644 index 0000000..a39f4dd --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicMetaIntegrationService.java @@ -0,0 +1,76 @@ +package com.ycwl.basic.integration.scenic.service; + +import com.ycwl.basic.integration.common.exception.IntegrationException; +import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.client.ScenicMetaClient; +import com.ycwl.basic.integration.scenic.dto.meta.*; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +@Slf4j +@Service +@RequiredArgsConstructor +public class ScenicMetaIntegrationService { + + private final ScenicMetaClient scenicMetaClient; + + public EnabledFieldsResponse getEnabledFields(Long scenicId) { + log.info("获取启用的字段, scenicId: {}", scenicId); + CommonResponse response = scenicMetaClient.getEnabledFields(scenicId); + return handleResponse(response, "获取启用的字段失败"); + } + + public List getAllFields() { + log.info("获取所有字段配置"); + CommonResponse> response = scenicMetaClient.getAllFields(); + return handleResponse(response, "获取所有字段配置失败"); + } + + public FieldConfigDTO getFieldConfig(String fieldKey) { + log.info("获取字段配置, fieldKey: {}", fieldKey); + CommonResponse response = scenicMetaClient.getFieldConfig(fieldKey); + return handleResponse(response, "获取字段配置失败"); + } + + public void setFieldEnabled(Long scenicId, String fieldKey, Boolean enabled) { + log.info("设置字段启用状态, scenicId: {}, fieldKey: {}, enabled: {}", scenicId, fieldKey, enabled); + SetFieldEnabledRequest request = new SetFieldEnabledRequest(); + request.setEnabled(enabled); + CommonResponse response = scenicMetaClient.setFieldEnabled(scenicId, fieldKey, request); + handleResponse(response, "设置字段启用状态失败"); + } + + public void batchSetFieldEnabled(Long scenicId, BatchSetFieldEnabledRequest request) { + log.info("批量设置字段启用状态, scenicId: {}, fields count: {}", scenicId, request.getFields().size()); + CommonResponse response = scenicMetaClient.batchSetFieldEnabled(scenicId, request); + handleResponse(response, "批量设置字段启用状态失败"); + } + + public ScenicV2WithConfigDTO getScenicWithConfigEnhanced(Long scenicId) { + log.info("获取景区与配置(增强版), scenicId: {}", scenicId); + CommonResponse response = scenicMetaClient.getScenicWithConfigEnhanced(scenicId); + return handleResponse(response, "获取景区与配置(增强版)失败"); + } + + public void updateConfigEnhanced(Long scenicId, Map configs) { + log.info("更新配置(增强版), scenicId: {}, configs count: {}", scenicId, configs.size()); + CommonResponse response = scenicMetaClient.updateConfigEnhanced(scenicId, configs); + handleResponse(response, "更新配置(增强版)失败"); + } + + private T handleResponse(CommonResponse response, String errorMessage) { + if (response == null || !response.isSuccess()) { + String msg = response != null && response.getMessage() != null + ? response.getMessage() + : errorMessage; + Integer code = response != null ? response.getCode() : 5000; + throw new IntegrationException(code, msg, "zt-scenic"); + } + return response.getData(); + } +} \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 0d93806..1ba1137 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -3,4 +3,21 @@ server: spring: application: - name: zt \ No newline at end of file + name: zt + +# Feign配置(简化版,基于Nacos服务发现) +feign: + client: + config: + default: + connectTimeout: 5000 + readTimeout: 10000 + loggerLevel: full + decode404: true + okhttp: + enabled: true + +# 开发环境日志配置 +logging: + level: + com.ycwl.basic.integration.scenic.client: DEBUG \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index dc970dc..510d250 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -3,4 +3,9 @@ server: spring: application: - name: zt \ No newline at end of file + name: zt + +# 生产环境日志级别 +logging: + level: + com.ycwl.basic.integration.scenic.client: WARN \ No newline at end of file From 291b3d620f353de539f8eed86bf76d04ad4ee418 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 26 Aug 2025 13:45:28 +0800 Subject: [PATCH 02/32] =?UTF-8?q?refactor(basic):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E8=B0=83?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 Redis 缓存操作,改为直接调用 ScenicIntegrationService- 新增 convertToScenicEntity 和 convertToScenicConfigEntity 方法进行数据转换 - 优化异常处理,fallback 到数据库查询 --- .../basic/repository/ScenicRepository.java | 162 ++++++++++++++++-- 1 file changed, 148 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 4016eb2..58306be 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -9,6 +9,12 @@ import com.ycwl.basic.model.pc.mp.MpNotifyConfigEntity; import com.ycwl.basic.model.pc.mp.ScenicMpNotifyVO; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; +import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.facebody.enums.FaceBodyAdapterType; +import com.ycwl.basic.pay.enums.PayAdapterType; +import com.ycwl.basic.storage.enums.StorageType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -23,6 +29,8 @@ public class ScenicRepository { private MpConfigMapper mpConfigMapper; @Autowired private RedisTemplate redisTemplate; + @Autowired + private ScenicIntegrationService scenicIntegrationService; public static final String SCENIC_CACHE_KEY = "scenic:%s"; public static final String SCENIC_FULL_CACHE_KEY = "scenic:f%s"; @@ -33,25 +41,21 @@ public class ScenicRepository { private MpNotifyConfigMapper mpNotifyConfigMapper; public ScenicEntity getScenic(Long id) { - if (redisTemplate.hasKey(String.format(SCENIC_CACHE_KEY, id))) { - return JacksonUtil.parseObject(redisTemplate.opsForValue().get(String.format(SCENIC_CACHE_KEY, id)), ScenicEntity.class); + try { + ScenicV2DTO scenicDTO = scenicIntegrationService.getScenic(id); + return convertToScenicEntity(scenicDTO); + } catch (Exception e) { + return scenicMapper.get(id); } - ScenicEntity scenic = scenicMapper.get(id); - if (scenic != null) { - redisTemplate.opsForValue().set(String.format(SCENIC_CACHE_KEY, id), JacksonUtil.toJSONString(scenic)); - } - return scenic; } public ScenicConfigEntity getScenicConfig(Long scenicId) { - if (redisTemplate.hasKey(String.format(SCENIC_CONFIG_CACHE_KEY, scenicId))) { - return JacksonUtil.parseObject(redisTemplate.opsForValue().get(String.format(SCENIC_CONFIG_CACHE_KEY, scenicId)), ScenicConfigEntity.class); + try { + ScenicV2WithConfigDTO scenicWithConfigDTO = scenicIntegrationService.getScenicWithConfig(scenicId); + return convertToScenicConfigEntity(scenicWithConfigDTO, scenicId); + } catch (Exception e) { + return scenicMapper.getConfig(scenicId); } - ScenicConfigEntity scenicConfig = scenicMapper.getConfig(scenicId); - if (scenicConfig != null) { - redisTemplate.opsForValue().set(String.format(SCENIC_CONFIG_CACHE_KEY, scenicId), JacksonUtil.toJSONString(scenicConfig)); - } - return scenicConfig; } public MpConfigEntity getScenicMpConfig(Long scenicId) { @@ -128,5 +132,135 @@ public class ScenicRepository { redisTemplate.delete(String.format(SCENIC_MP_CACHE_KEY, scenicId)); redisTemplate.delete(String.format(SCENIC_MP_NOTIFY_CACHE_KEY, scenicId)); } + + private ScenicEntity convertToScenicEntity(ScenicV2DTO dto) { + if (dto == null) { + return null; + } + ScenicEntity entity = new ScenicEntity(); + entity.setId(Long.parseLong(dto.getId())); + entity.setName(dto.getName()); + entity.setMpId(dto.getMpId()); + entity.setStatus(dto.getStatus().toString()); + entity.setCreateTime(new java.util.Date(dto.getCreateTime())); + entity.setUpdateTime(new java.util.Date(dto.getUpdateTime())); + return entity; + } + + private ScenicConfigEntity convertToScenicConfigEntity(ScenicV2WithConfigDTO dto, Long scenicId) { + if (dto == null || dto.getConfig() == null) { + return null; + } + + ScenicConfigEntity entity = new ScenicConfigEntity(); + entity.setScenicId(scenicId); + + java.util.Map config = dto.getConfig(); + + entity.setBookRoutine(getIntValue(config, "book_routine")); + entity.setForceFinishTime(getIntValue(config, "force_finish_time")); + entity.setTourTime(getIntValue(config, "tour_time")); + entity.setSampleStoreDay(getIntValue(config, "sample_store_day")); + entity.setFaceStoreDay(getIntValue(config, "face_store_day")); + entity.setVideoStoreDay(getIntValue(config, "video_store_day")); + entity.setAllFree(getIntValue(config, "all_free")); + entity.setDisableSourceVideo(getIntValue(config, "disable_source_video")); + entity.setDisableSourceImage(getIntValue(config, "disable_source_image")); + entity.setTemplateNewVideoType(getIntValue(config, "template_new_video_type")); + entity.setAntiScreenRecordType(getIntValue(config, "anti_screen_record_type")); + entity.setVideoSourceStoreDay(getIntValue(config, "video_source_store_day")); + entity.setImageSourceStoreDay(getIntValue(config, "image_source_store_day")); + entity.setUserSourceExpireDay(getIntValue(config, "user_source_expire_day")); + entity.setFaceDetectHelperThreshold(getIntValue(config, "face_detect_helper_threshold")); + entity.setPhotoFreeNum(getIntValue(config, "photo_free_num")); + entity.setVideoFreeNum(getIntValue(config, "video_free_num")); + entity.setVoucherEnable(getIntValue(config, "voucher_enable")); + + entity.setFaceScoreThreshold(getFloatValue(config, "face_score_threshold")); + entity.setBrokerDirectRate(getBigDecimalValue(config, "broker_direct_rate")); + + entity.setWatermarkType(getStringValue(config, "watermark_type")); + entity.setWatermarkScenicText(getStringValue(config, "watermark_scenic_text")); + entity.setWatermarkDtFormat(getStringValue(config, "watermark_dt_format")); + entity.setImageSourcePackHint(getStringValue(config, "image_source_pack_hint")); + entity.setVideoSourcePackHint(getStringValue(config, "video_source_pack_hint")); + entity.setExtraNotificationTime(getStringValue(config, "extra_notification_time")); + + entity.setStoreType(getEnumValue(config, "store_type", StorageType.class)); + entity.setStoreConfigJson(getStringValue(config, "store_config_json")); + entity.setTmpStoreType(getEnumValue(config, "tmp_store_type", StorageType.class)); + entity.setTmpStoreConfigJson(getStringValue(config, "tmp_store_config_json")); + entity.setLocalStoreType(getEnumValue(config, "local_store_type", StorageType.class)); + entity.setLocalStoreConfigJson(getStringValue(config, "local_store_config_json")); + + entity.setFaceType(getEnumValue(config, "face_type", FaceBodyAdapterType.class)); + entity.setFaceConfigJson(getStringValue(config, "face_config_json")); + + entity.setPayType(getEnumValue(config, "pay_type", PayAdapterType.class)); + entity.setPayConfigJson(getStringValue(config, "pay_config_json")); + + return entity; + } + + private Integer getIntValue(java.util.Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof Integer) return (Integer) value; + if (value instanceof String) { + try { + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + private Float getFloatValue(java.util.Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof Float) return (Float) value; + if (value instanceof Double) return ((Double) value).floatValue(); + if (value instanceof String) { + try { + return Float.parseFloat((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + private java.math.BigDecimal getBigDecimalValue(java.util.Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof java.math.BigDecimal) return (java.math.BigDecimal) value; + if (value instanceof String) { + try { + return new java.math.BigDecimal((String) value); + } catch (NumberFormatException e) { + return null; + } + } + if (value instanceof Number) { + return new java.math.BigDecimal(value.toString()); + } + return null; + } + + private String getStringValue(java.util.Map config, String key) { + Object value = config.get(key); + return value != null ? value.toString() : null; + } + + private > T getEnumValue(java.util.Map config, String key, Class enumClass) { + Object value = config.get(key); + if (value == null) return null; + try { + return Enum.valueOf(enumClass, value.toString()); + } catch (IllegalArgumentException e) { + return null; + } + } } From 5871beb84eb846d3e91898d65f6728f6c163f297 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 26 Aug 2025 13:45:38 +0800 Subject: [PATCH 03/32] =?UTF-8?q?refactor:=20=E7=A7=BB=E9=99=A4=20FeignCon?= =?UTF-8?q?fig=20=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除了 FeignConfig 类及相关配置,包括日志级别设置、请求拦截器、错误解码器等。这部分配置可能已经不再需要,或者已经被其他配置所替代。 --- .../com/ycwl/basic/config/FeignConfig.java | 63 ------------------- 1 file changed, 63 deletions(-) delete mode 100644 src/main/java/com/ycwl/basic/config/FeignConfig.java diff --git a/src/main/java/com/ycwl/basic/config/FeignConfig.java b/src/main/java/com/ycwl/basic/config/FeignConfig.java deleted file mode 100644 index 95594e0..0000000 --- a/src/main/java/com/ycwl/basic/config/FeignConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.ycwl.basic.config; - -import feign.Logger; -import feign.RequestInterceptor; -import feign.codec.ErrorDecoder; -import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import jakarta.servlet.http.HttpServletRequest; - -@Slf4j -@Configuration -public class FeignConfig { - - @Bean - public Logger.Level feignLoggerLevel() { - return Logger.Level.BASIC; - } - - @Bean - public RequestInterceptor requestInterceptor() { - return requestTemplate -> { - ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (attributes != null) { - HttpServletRequest request = attributes.getRequest(); - - // 传递认证头 - String authorization = request.getHeader("Authorization"); - if (authorization != null) { - requestTemplate.header("Authorization", authorization); - } - } - }; - } - - @Bean - public ErrorDecoder errorDecoder() { - return new FeignErrorDecoder(); - } - - public static class FeignErrorDecoder implements ErrorDecoder { - private final ErrorDecoder defaultErrorDecoder = new Default(); - - @Override - public Exception decode(String methodKey, feign.Response response) { - log.error("Feign调用失败: method={}, status={}, reason={}", - methodKey, response.status(), response.reason()); - - if (response.status() >= 400 && response.status() < 500) { - // 4xx错误,客户端错误 - return new RuntimeException("客户端请求错误: " + response.reason()); - } else if (response.status() >= 500) { - // 5xx错误,服务器错误 - return new RuntimeException("服务器内部错误: " + response.reason()); - } - - return defaultErrorDecoder.decode(methodKey, response); - } - } -} \ No newline at end of file From f0aeb27566606860e61388f84120cd07c6e13c59 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 26 Aug 2025 14:17:26 +0800 Subject: [PATCH 04/32] =?UTF-8?q?refactor(scenic):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E9=85=8D=E7=BD=AE=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 FeignClient 添加 contextId 属性,提高服务调用的可读性 - 更新 ScenicIntegrationService 中的接口调用方式 - 修改 ScenicConfigEntity 和 ScenicConfigResp 中的字段类型 -重构 ScenicRepository 中的配置解析逻辑,使用 ConfigValueUtil 工具类 --- .../common/util/ConfigValueUtil.java | 397 ++++++++++++++++++ .../scenic/client/DefaultConfigClient.java | 2 +- .../scenic/client/ScenicConfigV2Client.java | 2 +- .../client/ScenicConfigWithDefaultClient.java | 2 +- .../scenic/client/ScenicMetaClient.java | 2 +- .../scenic/client/ScenicV2Client.java | 2 +- .../service/ScenicIntegrationService.java | 4 +- .../pc/scenic/entity/ScenicConfigEntity.java | 6 +- .../pc/scenic/resp/ScenicConfigResp.java | 10 +- .../basic/repository/ScenicRepository.java | 134 ++---- 10 files changed, 448 insertions(+), 113 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/integration/common/util/ConfigValueUtil.java diff --git a/src/main/java/com/ycwl/basic/integration/common/util/ConfigValueUtil.java b/src/main/java/com/ycwl/basic/integration/common/util/ConfigValueUtil.java new file mode 100644 index 0000000..a38797b --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/common/util/ConfigValueUtil.java @@ -0,0 +1,397 @@ +package com.ycwl.basic.integration.common.util; + +import com.ycwl.basic.utils.JacksonUtil; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +/** + * 配置值转换工具类 + * + * 提供统一的配置Map值类型转换方法,支持多种数据类型的安全转换 + */ +public class ConfigValueUtil { + + /** + * 从配置Map中获取Integer值 + * + * @param config 配置Map + * @param key 配置键 + * @return Integer值,如果转换失败返回null + */ + public static Integer getIntValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof Integer) return (Integer) value; + if (value instanceof Number) return ((Number) value).intValue(); + if (value instanceof String) { + try { + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 从配置Map中获取Long值 + * + * @param config 配置Map + * @param key 配置键 + * @return Long值,如果转换失败返回null + */ + public static Long getLongValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof Long) return (Long) value; + if (value instanceof Number) return ((Number) value).longValue(); + if (value instanceof String) { + try { + return Long.parseLong((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 从配置Map中获取Float值 + * + * @param config 配置Map + * @param key 配置键 + * @return Float值,如果转换失败返回null + */ + public static Float getFloatValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof Float) return (Float) value; + if (value instanceof Double) return ((Double) value).floatValue(); + if (value instanceof Number) return ((Number) value).floatValue(); + if (value instanceof String) { + try { + return Float.parseFloat((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 从配置Map中获取Double值 + * + * @param config 配置Map + * @param key 配置键 + * @return Double值,如果转换失败返回null + */ + public static Double getDoubleValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof Double) return (Double) value; + if (value instanceof Number) return ((Number) value).doubleValue(); + if (value instanceof String) { + try { + return Double.parseDouble((String) value); + } catch (NumberFormatException e) { + return null; + } + } + return null; + } + + /** + * 从配置Map中获取BigDecimal值 + * + * @param config 配置Map + * @param key 配置键 + * @return BigDecimal值,如果转换失败返回null + */ + public static BigDecimal getBigDecimalValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof BigDecimal) return (BigDecimal) value; + if (value instanceof String) { + try { + return new BigDecimal((String) value); + } catch (NumberFormatException e) { + return null; + } + } + if (value instanceof Number) { + return new BigDecimal(value.toString()); + } + return null; + } + + /** + * 从配置Map中获取String值 + * 如果值是复杂对象(Map/List),会自动转换为JSON字符串 + * + * @param config 配置Map + * @param key 配置键 + * @return String值,如果value为null返回null + */ + public static String getStringValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + + // 如果是基础类型,直接转字符串 + if (value instanceof String || value instanceof Number || value instanceof Boolean) { + return value.toString(); + } + + // 如果是复杂对象(Map, List等),转换为JSON字符串 + try { + return JacksonUtil.toJSONString(value); + } catch (Exception e) { + // JSON转换失败,降级为toString + return value.toString(); + } + } + + /** + * 从配置Map中获取Boolean值 + * + * @param config 配置Map + * @param key 配置键 + * @return Boolean值,如果转换失败返回null + */ + public static Boolean getBooleanValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + if (value instanceof Boolean) return (Boolean) value; + if (value instanceof String) { + String str = (String) value; + if ("true".equalsIgnoreCase(str) || "1".equals(str)) return true; + if ("false".equalsIgnoreCase(str) || "0".equals(str)) return false; + } + if (value instanceof Number) { + return ((Number) value).intValue() != 0; + } + return null; + } + + /** + * 从配置Map中获取枚举值 + * + * @param config 配置Map + * @param key 配置键 + * @param enumClass 枚举类型 + * @param 枚举类型泛型 + * @return 枚举值,如果转换失败返回null + */ + public static > T getEnumValue(Map config, String key, Class enumClass) { + Object value = config.get(key); + if (value == null) return null; + try { + if (value instanceof String) { + return Enum.valueOf(enumClass, (String) value); + } + return Enum.valueOf(enumClass, value.toString()); + } catch (IllegalArgumentException e) { + return null; + } + } + + /** + * 从配置Map中获取Integer值,如果为null则返回默认值 + * + * @param config 配置Map + * @param key 配置键 + * @param defaultValue 默认值 + * @return Integer值或默认值 + */ + public static Integer getIntValue(Map config, String key, Integer defaultValue) { + Integer value = getIntValue(config, key); + return value != null ? value : defaultValue; + } + + /** + * 从配置Map中获取String值,如果为null则返回默认值 + * + * @param config 配置Map + * @param key 配置键 + * @param defaultValue 默认值 + * @return String值或默认值 + */ + public static String getStringValue(Map config, String key, String defaultValue) { + String value = getStringValue(config, key); + return value != null ? value : defaultValue; + } + + /** + * 从配置Map中获取Boolean值,如果为null则返回默认值 + * + * @param config 配置Map + * @param key 配置键 + * @param defaultValue 默认值 + * @return Boolean值或默认值 + */ + public static Boolean getBooleanValue(Map config, String key, Boolean defaultValue) { + Boolean value = getBooleanValue(config, key); + return value != null ? value : defaultValue; + } + + // ========== 对象和JSON转换方法 ========== + + /** + * 从配置Map中获取原始对象值 + * + * @param config 配置Map + * @param key 配置键 + * @return 原始Object值 + */ + public static Object getObjectValue(Map config, String key) { + return config.get(key); + } + + /** + * 从配置Map中获取并转换为指定类型的对象 + * 支持JSON字符串自动反序列化 + * + * @param config 配置Map + * @param key 配置键 + * @param clazz 目标类型 + * @param 目标类型泛型 + * @return 转换后的对象,如果转换失败返回null + */ + @SuppressWarnings("unchecked") + public static T getObjectValue(Map config, String key, Class clazz) { + Object value = config.get(key); + if (value == null) return null; + + // 如果类型匹配,直接返回 + if (clazz.isInstance(value)) { + return (T) value; + } + + // 如果是String类型的JSON,尝试反序列化 + if (value instanceof String && !clazz.equals(String.class)) { + try { + return JacksonUtil.parseObject((String) value, clazz); + } catch (Exception e) { + return null; + } + } + + // 如果目标是String,使用增强的字符串转换 + if (clazz.equals(String.class)) { + return (T) getStringValue(config, key); + } + + // 其他情况尝试JSON转换 + try { + String json = JacksonUtil.toJSONString(value); + return JacksonUtil.parseObject(json, clazz); + } catch (Exception e) { + return null; + } + } + + /** + * 从配置Map中获取Map类型的值 + * + * @param config 配置Map + * @param key 配置键 + * @return Map值,如果转换失败返回null + */ + @SuppressWarnings("unchecked") + public static Map getMapValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + + if (value instanceof Map) { + return (Map) value; + } + + if (value instanceof String) { + try { + return JacksonUtil.parseObject((String) value, Map.class); + } catch (Exception e) { + return null; + } + } + + return null; + } + + /** + * 从配置Map中获取List类型的值 + * + * @param config 配置Map + * @param key 配置键 + * @return List值,如果转换失败返回null + */ + @SuppressWarnings("unchecked") + public static List getListValue(Map config, String key) { + Object value = config.get(key); + if (value == null) return null; + + if (value instanceof List) { + return (List) value; + } + + if (value instanceof String) { + try { + return JacksonUtil.parseObject((String) value, List.class); + } catch (Exception e) { + return null; + } + } + + return null; + } + + /** + * 从配置Map中获取指定元素类型的List值 + * + * @param config 配置Map + * @param key 配置键 + * @param elementClass List元素类型 + * @param List元素类型泛型 + * @return 指定类型的List,如果转换失败返回null + */ + public static List getListValue(Map config, String key, Class elementClass) { + Object value = config.get(key); + if (value == null) return null; + + if (value instanceof String) { + try { + return JacksonUtil.parseArray((String) value, elementClass); + } catch (Exception e) { + return null; + } + } + + try { + String json = JacksonUtil.toJSONString(value); + return JacksonUtil.parseArray(json, elementClass); + } catch (Exception e) { + return null; + } + } + + /** + * 检查配置键是否存在 + * + * @param config 配置Map + * @param key 配置键 + * @return true如果键存在,false如果不存在 + */ + public static boolean hasKey(Map config, String key) { + return config != null && config.containsKey(key); + } + + /** + * 检查配置键是否存在且值不为null + * + * @param config 配置Map + * @param key 配置键 + * @return true如果键存在且值不为null + */ + public static boolean hasNonNullValue(Map config, String key) { + return config != null && config.containsKey(key) && config.get(key) != null; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/DefaultConfigClient.java b/src/main/java/com/ycwl/basic/integration/scenic/client/DefaultConfigClient.java index c2f7bca..7c8e99e 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/client/DefaultConfigClient.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/DefaultConfigClient.java @@ -7,7 +7,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; -@FeignClient(name = "zt-scenic", path = "/api/scenic/default-config") +@FeignClient(name = "zt-scenic", contextId = "scenic-default-config", path = "/api/scenic/default-config") public interface DefaultConfigClient { @GetMapping("/") diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigV2Client.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigV2Client.java index 3016812..31c1709 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigV2Client.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigV2Client.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; -@FeignClient(name = "zt-scenic", path = "/api/scenic/config/v2") +@FeignClient(name = "zt-scenic", contextId = "scenic-config-v2", path = "/api/scenic/config/v2") public interface ScenicConfigV2Client { @GetMapping("/{scenicId}") diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java index 38c8005..cd235d2 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java @@ -6,7 +6,7 @@ import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -@FeignClient(name = "zt-scenic", path = "/api/scenic/config-with-default") +@FeignClient(name = "zt-scenic", contextId = "scenic-config-with-default", path = "/api/scenic/config-with-default") public interface ScenicConfigWithDefaultClient { @GetMapping("/{scenicId}/{configKey}") diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java index df28f20..580f7e5 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java @@ -10,7 +10,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; -@FeignClient(name = "zt-scenic", path = "/api/scenic/meta") +@FeignClient(name = "zt-scenic", contextId = "scenic-meta", path = "/api/scenic/meta") public interface ScenicMetaClient { @GetMapping("/{scenicId}/fields/enabled") diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java index 2d56ae0..be88cee 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.*; import java.util.Map; -@FeignClient(name = "zt-scenic", path = "/api/scenic/v2") +@FeignClient(name = "zt-scenic", contextId = "scenic-v2", path = "/api/scenic/v2") public interface ScenicV2Client { @GetMapping("/{scenicId}") diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java index 6c25b77..ec3e8ea 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java @@ -2,6 +2,7 @@ package com.ycwl.basic.integration.scenic.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; +import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client; import com.ycwl.basic.integration.scenic.client.ScenicV2Client; import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse; import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; @@ -21,6 +22,7 @@ import java.util.Map; public class ScenicIntegrationService { private final ScenicV2Client scenicV2Client; + private final ScenicConfigV2Client scenicConfigV2Client; public ScenicV2DTO getScenic(Long scenicId) { log.info("获取景区信息, scenicId: {}", scenicId); @@ -36,7 +38,7 @@ public class ScenicIntegrationService { public Map getScenicFlatConfig(Long scenicId) { log.info("获取景区扁平化配置, scenicId: {}", scenicId); - CommonResponse> response = scenicV2Client.getScenicFlatConfig(scenicId); + CommonResponse> response = scenicConfigV2Client.getFlatConfigs(scenicId); return handleResponse(response, "获取景区扁平化配置失败"); } diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java index 3a502f4..c611b5a 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java @@ -72,12 +72,12 @@ public class ScenicConfigEntity { * 是否禁用源视频 * 0-否 1-是 */ - private Integer disableSourceVideo; + private Boolean disableSourceVideo; /** * 是否禁用源图片 * 0-否 1-是 */ - private Integer disableSourceImage; + private Boolean disableSourceImage; private Integer templateNewVideoType; /** * 是否开启防录屏 @@ -130,5 +130,5 @@ public class ScenicConfigEntity { * 是否启用券码功能 * 0-禁用 1-启用 */ - private Integer voucherEnable; + private Boolean voucherEnable; } diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java b/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java index c0743f6..1903f73 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java @@ -35,8 +35,8 @@ public class ScenicConfigResp { */ private Integer videoStoreDay; private Integer allFree; - private Integer disableSourceVideo; - private Integer disableSourceImage; + private Boolean disableSourceVideo; + private Boolean disableSourceImage; private Integer antiScreenRecordType; private Integer videoSourceStoreDay; private Integer imageSourceStoreDay; @@ -45,9 +45,5 @@ public class ScenicConfigResp { private String imageSourcePackHint = ""; private String videoSourcePackHint = ""; - /** - * 是否启用券码功能 - * 0-禁用 1-启用 - */ - private Integer voucherEnable; + private Boolean voucherEnable; } diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 58306be..764274d 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -12,6 +12,7 @@ import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.integration.common.util.ConfigValueUtil; import com.ycwl.basic.facebody.enums.FaceBodyAdapterType; import com.ycwl.basic.pay.enums.PayAdapterType; import com.ycwl.basic.storage.enums.StorageType; @@ -157,110 +158,49 @@ public class ScenicRepository { java.util.Map config = dto.getConfig(); - entity.setBookRoutine(getIntValue(config, "book_routine")); - entity.setForceFinishTime(getIntValue(config, "force_finish_time")); - entity.setTourTime(getIntValue(config, "tour_time")); - entity.setSampleStoreDay(getIntValue(config, "sample_store_day")); - entity.setFaceStoreDay(getIntValue(config, "face_store_day")); - entity.setVideoStoreDay(getIntValue(config, "video_store_day")); - entity.setAllFree(getIntValue(config, "all_free")); - entity.setDisableSourceVideo(getIntValue(config, "disable_source_video")); - entity.setDisableSourceImage(getIntValue(config, "disable_source_image")); - entity.setTemplateNewVideoType(getIntValue(config, "template_new_video_type")); - entity.setAntiScreenRecordType(getIntValue(config, "anti_screen_record_type")); - entity.setVideoSourceStoreDay(getIntValue(config, "video_source_store_day")); - entity.setImageSourceStoreDay(getIntValue(config, "image_source_store_day")); - entity.setUserSourceExpireDay(getIntValue(config, "user_source_expire_day")); - entity.setFaceDetectHelperThreshold(getIntValue(config, "face_detect_helper_threshold")); - entity.setPhotoFreeNum(getIntValue(config, "photo_free_num")); - entity.setVideoFreeNum(getIntValue(config, "video_free_num")); - entity.setVoucherEnable(getIntValue(config, "voucher_enable")); + entity.setBookRoutine(ConfigValueUtil.getIntValue(config, "bookRoutine")); + entity.setForceFinishTime(ConfigValueUtil.getIntValue(config, "forceFinishTime")); + entity.setTourTime(ConfigValueUtil.getIntValue(config, "tourTime")); + entity.setSampleStoreDay(ConfigValueUtil.getIntValue(config, "sampleStoreDay")); + entity.setFaceStoreDay(ConfigValueUtil.getIntValue(config, "faceStoreDay")); + entity.setVideoStoreDay(ConfigValueUtil.getIntValue(config, "videoStoreDay")); + entity.setAllFree(ConfigValueUtil.getIntValue(config, "allFree")); + entity.setDisableSourceVideo(ConfigValueUtil.getBooleanValue(config, "disableSourceVideo")); + entity.setDisableSourceImage(ConfigValueUtil.getBooleanValue(config, "disableSourceImage")); + entity.setTemplateNewVideoType(ConfigValueUtil.getIntValue(config, "templateNewVideoType")); + entity.setAntiScreenRecordType(ConfigValueUtil.getIntValue(config, "antiScreenRecordType")); + entity.setVideoSourceStoreDay(ConfigValueUtil.getIntValue(config, "videoSourceStoreDay")); + entity.setImageSourceStoreDay(ConfigValueUtil.getIntValue(config, "imageSourceStoreDay")); + entity.setUserSourceExpireDay(ConfigValueUtil.getIntValue(config, "userSourceExpireDay")); + entity.setFaceDetectHelperThreshold(ConfigValueUtil.getIntValue(config, "faceDetectHelperThreshold")); + entity.setPhotoFreeNum(ConfigValueUtil.getIntValue(config, "photoFreeNum")); + entity.setVideoFreeNum(ConfigValueUtil.getIntValue(config, "videoFreeNum")); + entity.setVoucherEnable(ConfigValueUtil.getBooleanValue(config, "voucherEnable")); - entity.setFaceScoreThreshold(getFloatValue(config, "face_score_threshold")); - entity.setBrokerDirectRate(getBigDecimalValue(config, "broker_direct_rate")); + entity.setFaceScoreThreshold(ConfigValueUtil.getFloatValue(config, "faceScoreThreshold")); + entity.setBrokerDirectRate(ConfigValueUtil.getBigDecimalValue(config, "brokerDirectRate")); - entity.setWatermarkType(getStringValue(config, "watermark_type")); - entity.setWatermarkScenicText(getStringValue(config, "watermark_scenic_text")); - entity.setWatermarkDtFormat(getStringValue(config, "watermark_dt_format")); - entity.setImageSourcePackHint(getStringValue(config, "image_source_pack_hint")); - entity.setVideoSourcePackHint(getStringValue(config, "video_source_pack_hint")); - entity.setExtraNotificationTime(getStringValue(config, "extra_notification_time")); + entity.setWatermarkType(ConfigValueUtil.getStringValue(config, "watermarkType")); + entity.setWatermarkScenicText(ConfigValueUtil.getStringValue(config, "watermarkScenicText")); + entity.setWatermarkDtFormat(ConfigValueUtil.getStringValue(config, "watermarkDtFormat")); + entity.setImageSourcePackHint(ConfigValueUtil.getStringValue(config, "imageSourcePackHint")); + entity.setVideoSourcePackHint(ConfigValueUtil.getStringValue(config, "videoSourcePackHint")); + entity.setExtraNotificationTime(ConfigValueUtil.getStringValue(config, "extraNotificationTime")); - entity.setStoreType(getEnumValue(config, "store_type", StorageType.class)); - entity.setStoreConfigJson(getStringValue(config, "store_config_json")); - entity.setTmpStoreType(getEnumValue(config, "tmp_store_type", StorageType.class)); - entity.setTmpStoreConfigJson(getStringValue(config, "tmp_store_config_json")); - entity.setLocalStoreType(getEnumValue(config, "local_store_type", StorageType.class)); - entity.setLocalStoreConfigJson(getStringValue(config, "local_store_config_json")); + entity.setStoreType(ConfigValueUtil.getEnumValue(config, "storeType", StorageType.class)); + entity.setStoreConfigJson(ConfigValueUtil.getStringValue(config, "storeConfigJson")); + entity.setTmpStoreType(ConfigValueUtil.getEnumValue(config, "tmpStoreType", StorageType.class)); + entity.setTmpStoreConfigJson(ConfigValueUtil.getStringValue(config, "tmpStoreConfigJson")); + entity.setLocalStoreType(ConfigValueUtil.getEnumValue(config, "localStoreType", StorageType.class)); + entity.setLocalStoreConfigJson(ConfigValueUtil.getStringValue(config, "localStoreConfigJson")); - entity.setFaceType(getEnumValue(config, "face_type", FaceBodyAdapterType.class)); - entity.setFaceConfigJson(getStringValue(config, "face_config_json")); + entity.setFaceType(ConfigValueUtil.getEnumValue(config, "faceType", FaceBodyAdapterType.class)); + entity.setFaceConfigJson(ConfigValueUtil.getStringValue(config, "faceConfigJson")); - entity.setPayType(getEnumValue(config, "pay_type", PayAdapterType.class)); - entity.setPayConfigJson(getStringValue(config, "pay_config_json")); + entity.setPayType(ConfigValueUtil.getEnumValue(config, "payType", PayAdapterType.class)); + entity.setPayConfigJson(ConfigValueUtil.getStringValue(config, "payConfigJson")); return entity; } - - private Integer getIntValue(java.util.Map config, String key) { - Object value = config.get(key); - if (value == null) return null; - if (value instanceof Integer) return (Integer) value; - if (value instanceof String) { - try { - return Integer.parseInt((String) value); - } catch (NumberFormatException e) { - return null; - } - } - return null; - } - - private Float getFloatValue(java.util.Map config, String key) { - Object value = config.get(key); - if (value == null) return null; - if (value instanceof Float) return (Float) value; - if (value instanceof Double) return ((Double) value).floatValue(); - if (value instanceof String) { - try { - return Float.parseFloat((String) value); - } catch (NumberFormatException e) { - return null; - } - } - return null; - } - - private java.math.BigDecimal getBigDecimalValue(java.util.Map config, String key) { - Object value = config.get(key); - if (value == null) return null; - if (value instanceof java.math.BigDecimal) return (java.math.BigDecimal) value; - if (value instanceof String) { - try { - return new java.math.BigDecimal((String) value); - } catch (NumberFormatException e) { - return null; - } - } - if (value instanceof Number) { - return new java.math.BigDecimal(value.toString()); - } - return null; - } - - private String getStringValue(java.util.Map config, String key) { - Object value = config.get(key); - return value != null ? value.toString() : null; - } - - private > T getEnumValue(java.util.Map config, String key, Class enumClass) { - Object value = config.get(key); - if (value == null) return null; - try { - return Enum.valueOf(enumClass, value.toString()); - } catch (IllegalArgumentException e) { - return null; - } - } } From f6bd7e48a3eeaf1b6edf2d4a9198619665c82e7b Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Tue, 26 Aug 2025 14:29:45 +0800 Subject: [PATCH 05/32] =?UTF-8?q?refactor(basic):=20=E5=B0=86=20ScenicConf?= =?UTF-8?q?igEntity=20=E4=B8=AD=E7=9A=84=20allFree=20=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=BB=8E=20Integer=20=E6=94=B9=E4=B8=BA=20Bo?= =?UTF-8?q?olean?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改了 OrderBiz、PriceBiz 中的相关代码,使用 Boolean.TRUE 进行比较 - 更新了 ScenicConfigEntity 和 ScenicConfigResp 中 allFree 字段的类型 - 在 ScenicRepository 中使用 ConfigValueUtil.getBooleanValue 方法获取 allFree 的值 --- src/main/java/com/ycwl/basic/biz/OrderBiz.java | 2 +- src/main/java/com/ycwl/basic/biz/PriceBiz.java | 2 +- .../ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java | 2 +- .../com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java | 2 +- src/main/java/com/ycwl/basic/repository/ScenicRepository.java | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/ycwl/basic/biz/OrderBiz.java b/src/main/java/com/ycwl/basic/biz/OrderBiz.java index 83825a1..b6232c3 100644 --- a/src/main/java/com/ycwl/basic/biz/OrderBiz.java +++ b/src/main/java/com/ycwl/basic/biz/OrderBiz.java @@ -88,7 +88,7 @@ public class OrderBiz { ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); priceObj.setScenicAllPrice(scenic.getPrice()); if (scenicConfig != null) { - if (Integer.valueOf(1).equals(scenicConfig.getAllFree())) { + if (Boolean.TRUE.equals(scenicConfig.getAllFree())) { // 景区全免 priceObj.setFree(true); priceObj.setPrice(BigDecimal.ZERO); diff --git a/src/main/java/com/ycwl/basic/biz/PriceBiz.java b/src/main/java/com/ycwl/basic/biz/PriceBiz.java index fc6747d..cc60290 100644 --- a/src/main/java/com/ycwl/basic/biz/PriceBiz.java +++ b/src/main/java/com/ycwl/basic/biz/PriceBiz.java @@ -92,7 +92,7 @@ public class PriceBiz { } ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); if (scenicConfig != null) { - if (Integer.valueOf(1).equals(scenicConfig.getAllFree())) { + if (Boolean.TRUE.equals(scenicConfig.getAllFree())) { // 景区全免 respVO.setFree(true); respVO.setSlashPrice(BigDecimal.ZERO); diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java index c611b5a..a7c3c0f 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicConfigEntity.java @@ -67,7 +67,7 @@ public class ScenicConfigEntity { /** * 是否开启全部免费 */ - private Integer allFree; + private Boolean allFree; /** * 是否禁用源视频 * 0-否 1-是 diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java b/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java index 1903f73..80d3bd2 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicConfigResp.java @@ -34,7 +34,7 @@ public class ScenicConfigResp { * 视频保存时间 */ private Integer videoStoreDay; - private Integer allFree; + private Boolean allFree; private Boolean disableSourceVideo; private Boolean disableSourceImage; private Integer antiScreenRecordType; diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 764274d..4ed0b4a 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -164,7 +164,7 @@ public class ScenicRepository { entity.setSampleStoreDay(ConfigValueUtil.getIntValue(config, "sampleStoreDay")); entity.setFaceStoreDay(ConfigValueUtil.getIntValue(config, "faceStoreDay")); entity.setVideoStoreDay(ConfigValueUtil.getIntValue(config, "videoStoreDay")); - entity.setAllFree(ConfigValueUtil.getIntValue(config, "allFree")); + entity.setAllFree(ConfigValueUtil.getBooleanValue(config, "allFree")); entity.setDisableSourceVideo(ConfigValueUtil.getBooleanValue(config, "disableSourceVideo")); entity.setDisableSourceImage(ConfigValueUtil.getBooleanValue(config, "disableSourceImage")); entity.setTemplateNewVideoType(ConfigValueUtil.getIntValue(config, "templateNewVideoType")); From 7c2db2ad22b1c454f38167c4f7a36782b4b38638 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 00:11:00 +0800 Subject: [PATCH 06/32] =?UTF-8?q?refactor(scenic):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E7=AE=A1=E7=90=86=E6=8E=A5=E5=8F=A3=E5=B9=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20V2=20=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ScenicV2Controller 控制器,实现景区 V2 版本的 CRUD操作和配置管理 - 移除 ScenicConfigWithDefaultClient 和 ScenicMetaClient 接口- 更新 ScenicV2Client接口,添加分页查询方法 - 删除 ConfigWithDefaultResponse、BatchSetFieldEnabledRequest、EnabledFieldsResponse、FieldConfigDTO 和 SetFieldEnabledRequest 类 - 新增 ScenicV2ListResponse 和 ScenicV2WithConfigListResponse 类- 更新 ScenicConfigIntegrationService 和 ScenicIntegrationService,移除与配置相关的方法 - 删除 ScenicMetaIntegrationService 类 --- .../controller/pc/ScenicV2Controller.java | 310 ++++++++++++++++++ .../client/ScenicConfigWithDefaultClient.java | 15 - .../scenic/client/ScenicMetaClient.java | 40 --- .../scenic/client/ScenicV2Client.java | 16 +- .../dto/config/ConfigWithDefaultResponse.java | 16 - .../dto/meta/BatchSetFieldEnabledRequest.java | 26 -- .../dto/meta/EnabledFieldsResponse.java | 12 - .../scenic/dto/meta/FieldConfigDTO.java | 22 -- .../dto/meta/SetFieldEnabledRequest.java | 13 - .../dto/scenic/ScenicV2ListResponse.java | 21 ++ .../ScenicV2WithConfigListResponse.java | 21 ++ .../ScenicConfigIntegrationService.java | 7 - .../service/ScenicIntegrationService.java | 14 + .../service/ScenicMetaIntegrationService.java | 76 ----- 14 files changed, 380 insertions(+), 229 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/controller/pc/ScenicV2Controller.java delete mode 100644 src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java delete mode 100644 src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java delete mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/config/ConfigWithDefaultResponse.java delete mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/meta/BatchSetFieldEnabledRequest.java delete mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/meta/EnabledFieldsResponse.java delete mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/meta/FieldConfigDTO.java delete mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/meta/SetFieldEnabledRequest.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2ListResponse.java create mode 100644 src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigListResponse.java delete mode 100644 src/main/java/com/ycwl/basic/integration/scenic/service/ScenicMetaIntegrationService.java diff --git a/src/main/java/com/ycwl/basic/controller/pc/ScenicV2Controller.java b/src/main/java/com/ycwl/basic/controller/pc/ScenicV2Controller.java new file mode 100644 index 0000000..ef89644 --- /dev/null +++ b/src/main/java/com/ycwl/basic/controller/pc/ScenicV2Controller.java @@ -0,0 +1,310 @@ +package com.ycwl.basic.controller.pc; + +import com.ycwl.basic.integration.scenic.dto.config.BatchConfigRequest; +import com.ycwl.basic.integration.scenic.dto.config.BatchUpdateResponse; +import com.ycwl.basic.integration.scenic.dto.config.CreateConfigRequest; +import com.ycwl.basic.integration.scenic.dto.config.ScenicConfigV2DTO; +import com.ycwl.basic.integration.scenic.dto.config.UpdateConfigRequest; +import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse; +import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; +import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigListResponse; +import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest; +import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService; +import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; +import com.ycwl.basic.utils.ApiResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import java.util.List; +import java.util.Map; + +/** + * @Author:longbinbin + * @Date:2024/12/26 + * 景区管理 V2 版本控制器 - 基于 zt-scenic 集成服务 + */ +@Slf4j +@RestController +@RequestMapping("/api/scenic/v2") +@RequiredArgsConstructor +public class ScenicV2Controller { + + private final ScenicIntegrationService scenicIntegrationService; + private final ScenicConfigIntegrationService scenicConfigIntegrationService; + + // ========== 景区基础 CRUD 操作 ========== + + /** + * 景区V2核心信息分页列表 + */ + @GetMapping("/") + public ApiResponse listScenics(@RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) Integer status, + @RequestParam(required = false) String name) { + log.info("分页查询景区核心信息列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name); + + // 参数验证:限制pageSize最大值为100 + if (pageSize > 100) { + pageSize = 100; + } + + try { + ScenicV2ListResponse response = scenicIntegrationService.listScenics(page, pageSize, status, name); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("分页查询景区核心信息列表失败", e); + return ApiResponse.fail("分页查询景区列表失败: " + e.getMessage()); + } + } + + /** + * 景区V2带配置信息分页列表 + */ + @GetMapping("/with-config") + public ApiResponse listScenicsWithConfig(@RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) Integer status, + @RequestParam(required = false) String name) { + log.info("分页查询景区带配置信息列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name); + + // 参数验证:限制pageSize最大值为100 + if (pageSize > 100) { + pageSize = 100; + } + + try { + ScenicV2WithConfigListResponse response = scenicIntegrationService.listScenicsWithConfig(page, pageSize, status, name); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("分页查询景区带配置信息列表失败", e); + return ApiResponse.fail("分页查询景区带配置信息列表失败: " + e.getMessage()); + } + } + + /** + * 查询单个景区详情 + */ + @GetMapping("/{scenicId}") + public ApiResponse getScenic(@PathVariable Long scenicId) { + log.info("查询景区详情, scenicId: {}", scenicId); + try { + ScenicV2DTO scenic = scenicIntegrationService.getScenic(scenicId); + return ApiResponse.success(scenic); + } catch (Exception e) { + log.error("查询景区详情失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("查询景区详情失败: " + e.getMessage()); + } + } + + /** + * 查询景区列表(支持筛选和分页)- 高级筛选 + */ + @PostMapping("/filter") + public ApiResponse filterScenics(@RequestBody @Valid ScenicFilterRequest request) { + log.info("高级筛选景区列表, 筛选条件数: {}, 页码: {}, 页大小: {}", + request.getFilters().size(), request.getPage(), request.getPageSize()); + try { + ScenicFilterPageResponse response = scenicIntegrationService.filterScenics(request); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("高级筛选景区列表失败", e); + return ApiResponse.fail("高级筛选景区列表失败: " + e.getMessage()); + } + } + + /** + * 新增景区 + */ + @PostMapping("/create") + public ApiResponse createScenic(@RequestBody @Valid CreateScenicRequest request) { + log.info("新增景区, name: {}, mpId: {}", request.getName(), request.getMpId()); + try { + ScenicV2DTO scenic = scenicIntegrationService.createScenic(request); + return ApiResponse.success(scenic); + } catch (Exception e) { + log.error("新增景区失败, name: {}", request.getName(), e); + return ApiResponse.fail("新增景区失败: " + e.getMessage()); + } + } + + /** + * 修改景区 + */ + @PutMapping("/{scenicId}") + public ApiResponse updateScenic(@PathVariable Long scenicId, + @RequestBody @Valid UpdateScenicRequest request) { + log.info("修改景区, scenicId: {}", scenicId); + try { + ScenicV2DTO scenic = scenicIntegrationService.updateScenic(scenicId, request); + return ApiResponse.success(scenic); + } catch (Exception e) { + log.error("修改景区失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("修改景区失败: " + e.getMessage()); + } + } + + /** + * 删除景区 + */ + @DeleteMapping("/{scenicId}") + public ApiResponse deleteScenic(@PathVariable Long scenicId) { + log.info("删除景区, scenicId: {}", scenicId); + try { + scenicIntegrationService.deleteScenic(scenicId); + return ApiResponse.success(null); + } catch (Exception e) { + log.error("删除景区失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("删除景区失败: " + e.getMessage()); + } + } + + // ========== 景区配置管理 ========== + + /** + * 获取景区及其配置信息 + */ + @GetMapping("/{scenicId}/with-config") + public ApiResponse getScenicWithConfig(@PathVariable Long scenicId) { + log.info("获取景区配置信息, scenicId: {}", scenicId); + try { + ScenicV2WithConfigDTO scenic = scenicIntegrationService.getScenicWithConfig(scenicId); + return ApiResponse.success(scenic); + } catch (Exception e) { + log.error("获取景区配置信息失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("获取景区配置信息失败: " + e.getMessage()); + } + } + + /** + * 获取景区扁平化配置 + */ + @GetMapping("/{scenicId}/flat-config") + public ApiResponse> getScenicFlatConfig(@PathVariable Long scenicId) { + log.info("获取景区扁平化配置, scenicId: {}", scenicId); + try { + Map config = scenicIntegrationService.getScenicFlatConfig(scenicId); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("获取景区扁平化配置失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("获取景区扁平化配置失败: " + e.getMessage()); + } + } + + /** + * 获取景区配置列表 + */ + @GetMapping("/{scenicId}/config") + public ApiResponse> listConfigs(@PathVariable Long scenicId) { + log.info("获取景区配置列表, scenicId: {}", scenicId); + try { + List configs = scenicConfigIntegrationService.listConfigs(scenicId); + return ApiResponse.success(configs); + } catch (Exception e) { + log.error("获取景区配置列表失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("获取景区配置列表失败: " + e.getMessage()); + } + } + + /** + * 根据配置键获取配置 + */ + @GetMapping("/{scenicId}/config/{configKey}") + public ApiResponse getConfigByKey(@PathVariable Long scenicId, + @PathVariable String configKey) { + log.info("根据键获取景区配置, scenicId: {}, configKey: {}", scenicId, configKey); + try { + ScenicConfigV2DTO config = scenicConfigIntegrationService.getConfigByKey(scenicId, configKey); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("根据键获取景区配置失败, scenicId: {}, configKey: {}", scenicId, configKey, e); + return ApiResponse.fail("获取配置失败: " + e.getMessage()); + } + } + + /** + * 创建景区配置 + */ + @PostMapping("/{scenicId}/config") + public ApiResponse createConfig(@PathVariable Long scenicId, + @RequestBody @Valid CreateConfigRequest request) { + log.info("创建景区配置, scenicId: {}, configKey: {}", scenicId, request.getConfigKey()); + try { + ScenicConfigV2DTO config = scenicConfigIntegrationService.createConfig(scenicId, request); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("创建景区配置失败, scenicId: {}, configKey: {}", scenicId, request.getConfigKey(), e); + return ApiResponse.fail("创建配置失败: " + e.getMessage()); + } + } + + /** + * 更新景区配置 + */ + @PutMapping("/{scenicId}/config/{configId}") + public ApiResponse updateConfig(@PathVariable Long scenicId, + @PathVariable String configId, + @RequestBody @Valid UpdateConfigRequest request) { + log.info("更新景区配置, scenicId: {}, configId: {}", scenicId, configId); + try { + ScenicConfigV2DTO config = scenicConfigIntegrationService.updateConfig(scenicId, configId, request); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("更新景区配置失败, scenicId: {}, configId: {}", scenicId, configId, e); + return ApiResponse.fail("更新配置失败: " + e.getMessage()); + } + } + + /** + * 删除景区配置 + */ + @DeleteMapping("/{scenicId}/config/{configId}") + public ApiResponse deleteConfig(@PathVariable Long scenicId, @PathVariable String configId) { + log.info("删除景区配置, scenicId: {}, configId: {}", scenicId, configId); + try { + scenicConfigIntegrationService.deleteConfig(scenicId, configId); + return ApiResponse.success(null); + } catch (Exception e) { + log.error("删除景区配置失败, scenicId: {}, configId: {}", scenicId, configId, e); + return ApiResponse.fail("删除配置失败: " + e.getMessage()); + } + } + + /** + * 批量更新景区配置 + */ + @PutMapping("/{scenicId}/config/batch") + public ApiResponse batchUpdateConfigs(@PathVariable Long scenicId, + @RequestBody @Valid BatchConfigRequest request) { + log.info("批量更新景区配置, scenicId: {}, configs count: {}", scenicId, request.getConfigs().size()); + try { + BatchUpdateResponse response = scenicConfigIntegrationService.batchUpdateConfigs(scenicId, request); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("批量更新景区配置失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("批量更新配置失败: " + e.getMessage()); + } + } + + /** + * 扁平化批量更新景区配置 + */ + @PutMapping("/{scenicId}/flat-config") + public ApiResponse batchFlatUpdateConfigs(@PathVariable Long scenicId, + @RequestBody Map configs) { + log.info("扁平化批量更新景区配置, scenicId: {}, configs count: {}", scenicId, configs.size()); + try { + BatchUpdateResponse response = scenicConfigIntegrationService.batchFlatUpdateConfigs(scenicId, configs); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("扁平化批量更新景区配置失败, scenicId: {}", scenicId, e); + return ApiResponse.fail("扁平化批量更新配置失败: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java deleted file mode 100644 index cd235d2..0000000 --- a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicConfigWithDefaultClient.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.ycwl.basic.integration.scenic.client; - -import com.ycwl.basic.integration.common.response.CommonResponse; -import com.ycwl.basic.integration.scenic.dto.config.ConfigWithDefaultResponse; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; - -@FeignClient(name = "zt-scenic", contextId = "scenic-config-with-default", path = "/api/scenic/config-with-default") -public interface ScenicConfigWithDefaultClient { - - @GetMapping("/{scenicId}/{configKey}") - CommonResponse getConfigWithDefault(@PathVariable("scenicId") Long scenicId, - @PathVariable("configKey") String configKey); -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java deleted file mode 100644 index 580f7e5..0000000 --- a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicMetaClient.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.ycwl.basic.integration.scenic.client; - -import com.ycwl.basic.integration.common.response.CommonResponse; -import com.ycwl.basic.integration.scenic.dto.meta.*; -import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; -import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest; -import org.springframework.cloud.openfeign.FeignClient; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.Map; - -@FeignClient(name = "zt-scenic", contextId = "scenic-meta", path = "/api/scenic/meta") -public interface ScenicMetaClient { - - @GetMapping("/{scenicId}/fields/enabled") - CommonResponse getEnabledFields(@PathVariable("scenicId") Long scenicId); - - @GetMapping("/fields/all") - CommonResponse> getAllFields(); - - @GetMapping("/fields/{fieldKey}") - CommonResponse getFieldConfig(@PathVariable("fieldKey") String fieldKey); - - @PostMapping("/{scenicId}/fields/{fieldKey}/enabled") - CommonResponse setFieldEnabled(@PathVariable("scenicId") Long scenicId, - @PathVariable("fieldKey") String fieldKey, - @RequestBody SetFieldEnabledRequest request); - - @PostMapping("/{scenicId}/fields/batch-enabled") - CommonResponse batchSetFieldEnabled(@PathVariable("scenicId") Long scenicId, - @RequestBody BatchSetFieldEnabledRequest request); - - @GetMapping("/{scenicId}/with-config") - CommonResponse getScenicWithConfigEnhanced(@PathVariable("scenicId") Long scenicId); - - @PutMapping("/{scenicId}/config") - CommonResponse updateConfigEnhanced(@PathVariable("scenicId") Long scenicId, - @RequestBody Map configs); -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java index be88cee..0405fcf 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/client/ScenicV2Client.java @@ -6,6 +6,8 @@ import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigListResponse; import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; @@ -21,8 +23,6 @@ public interface ScenicV2Client { @GetMapping("/{scenicId}/with-config") CommonResponse getScenicWithConfig(@PathVariable("scenicId") Long scenicId); - @GetMapping("/{scenicId}/flat") - CommonResponse> getScenicFlatConfig(@PathVariable("scenicId") Long scenicId); @PostMapping("/") CommonResponse createScenic(@RequestBody CreateScenicRequest request); @@ -36,4 +36,16 @@ public interface ScenicV2Client { @PostMapping("/filter") CommonResponse filterScenics(@RequestBody ScenicFilterRequest request); + + @GetMapping("/") + CommonResponse listScenics(@RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) Integer status, + @RequestParam(required = false) String name); + + @GetMapping("/with-config") + CommonResponse listScenicsWithConfig(@RequestParam(defaultValue = "1") Integer page, + @RequestParam(defaultValue = "10") Integer pageSize, + @RequestParam(required = false) Integer status, + @RequestParam(required = false) String name); } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ConfigWithDefaultResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ConfigWithDefaultResponse.java deleted file mode 100644 index 19cecb4..0000000 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/config/ConfigWithDefaultResponse.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ycwl.basic.integration.scenic.dto.config; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class ConfigWithDefaultResponse { - @JsonProperty("configKey") - private String configKey; - - @JsonProperty("configValue") - private String configValue; - - @JsonProperty("source") - private String source; -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/BatchSetFieldEnabledRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/BatchSetFieldEnabledRequest.java deleted file mode 100644 index c304f4e..0000000 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/BatchSetFieldEnabledRequest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.ycwl.basic.integration.scenic.dto.meta; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import java.util.List; - -@Data -public class BatchSetFieldEnabledRequest { - @JsonProperty("fields") - @NotEmpty(message = "字段列表不能为空") - @Valid - private List fields; - - @Data - public static class FieldEnabledItem { - @JsonProperty("fieldKey") - @NotEmpty(message = "字段键不能为空") - private String fieldKey; - - @JsonProperty("enabled") - private Boolean enabled; - } -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/EnabledFieldsResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/EnabledFieldsResponse.java deleted file mode 100644 index 5d2c48b..0000000 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/EnabledFieldsResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ycwl.basic.integration.scenic.dto.meta; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import java.util.List; - -@Data -public class EnabledFieldsResponse { - @JsonProperty("enabledFields") - private List enabledFields; -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/FieldConfigDTO.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/FieldConfigDTO.java deleted file mode 100644 index f6cead5..0000000 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/FieldConfigDTO.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.ycwl.basic.integration.scenic.dto.meta; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -@Data -public class FieldConfigDTO { - @JsonProperty("fieldKey") - private String fieldKey; - - @JsonProperty("fieldName") - private String fieldName; - - @JsonProperty("fieldType") - private String fieldType; - - @JsonProperty("description") - private String description; - - @JsonProperty("enabled") - private Boolean enabled; -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/SetFieldEnabledRequest.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/SetFieldEnabledRequest.java deleted file mode 100644 index 55ad4fa..0000000 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/meta/SetFieldEnabledRequest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ycwl.basic.integration.scenic.dto.meta; - -import com.fasterxml.jackson.annotation.JsonProperty; -import lombok.Data; - -import jakarta.validation.constraints.NotNull; - -@Data -public class SetFieldEnabledRequest { - @JsonProperty("enabled") - @NotNull(message = "enabled状态不能为空") - private Boolean enabled; -} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2ListResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2ListResponse.java new file mode 100644 index 0000000..f2f5790 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2ListResponse.java @@ -0,0 +1,21 @@ +package com.ycwl.basic.integration.scenic.dto.scenic; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ScenicV2ListResponse { + @JsonProperty("list") + private List list; + + @JsonProperty("total") + private Long total; + + @JsonProperty("page") + private Integer page; + + @JsonProperty("pageSize") + private Integer pageSize; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigListResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigListResponse.java new file mode 100644 index 0000000..cd5cef1 --- /dev/null +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigListResponse.java @@ -0,0 +1,21 @@ +package com.ycwl.basic.integration.scenic.dto.scenic; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class ScenicV2WithConfigListResponse { + @JsonProperty("list") + private List list; + + @JsonProperty("total") + private Long total; + + @JsonProperty("page") + private Integer page; + + @JsonProperty("pageSize") + private Integer pageSize; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java index 0351b90..39151fd 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicConfigIntegrationService.java @@ -3,7 +3,6 @@ package com.ycwl.basic.integration.scenic.service; import com.ycwl.basic.integration.common.exception.IntegrationException; import com.ycwl.basic.integration.common.response.CommonResponse; import com.ycwl.basic.integration.scenic.client.ScenicConfigV2Client; -import com.ycwl.basic.integration.scenic.client.ScenicConfigWithDefaultClient; import com.ycwl.basic.integration.scenic.dto.config.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -18,7 +17,6 @@ import java.util.Map; public class ScenicConfigIntegrationService { private final ScenicConfigV2Client scenicConfigV2Client; - private final ScenicConfigWithDefaultClient scenicConfigWithDefaultClient; public List listConfigs(Long scenicId) { log.info("获取景区配置列表, scenicId: {}", scenicId); @@ -68,11 +66,6 @@ public class ScenicConfigIntegrationService { return handleResponse(response, "扁平化批量更新景区配置失败"); } - public ConfigWithDefaultResponse getConfigWithDefault(Long scenicId, String configKey) { - log.info("获取带默认值的配置, scenicId: {}, configKey: {}", scenicId, configKey); - CommonResponse response = scenicConfigWithDefaultClient.getConfigWithDefault(scenicId, configKey); - return handleResponse(response, "获取带默认值的配置失败"); - } private T handleResponse(CommonResponse response, String errorMessage) { if (response == null || !response.isSuccess()) { diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java index ec3e8ea..e101398 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicIntegrationService.java @@ -9,6 +9,8 @@ import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigListResponse; import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -66,6 +68,18 @@ public class ScenicIntegrationService { return handleResponse(response, "筛选景区失败"); } + public ScenicV2ListResponse listScenics(Integer page, Integer pageSize, Integer status, String name) { + log.info("分页查询景区列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name); + CommonResponse response = scenicV2Client.listScenics(page, pageSize, status, name); + return handleResponse(response, "分页查询景区列表失败"); + } + + public ScenicV2WithConfigListResponse listScenicsWithConfig(Integer page, Integer pageSize, Integer status, String name) { + log.info("分页查询景区带配置列表, page: {}, pageSize: {}, status: {}, name: {}", page, pageSize, status, name); + CommonResponse response = scenicV2Client.listScenicsWithConfig(page, pageSize, status, name); + return handleResponse(response, "分页查询景区带配置列表失败"); + } + private T handleResponse(CommonResponse response, String errorMessage) { if (response == null || !response.isSuccess()) { String msg = response != null && response.getMessage() != null diff --git a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicMetaIntegrationService.java b/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicMetaIntegrationService.java deleted file mode 100644 index a39f4dd..0000000 --- a/src/main/java/com/ycwl/basic/integration/scenic/service/ScenicMetaIntegrationService.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.ycwl.basic.integration.scenic.service; - -import com.ycwl.basic.integration.common.exception.IntegrationException; -import com.ycwl.basic.integration.common.response.CommonResponse; -import com.ycwl.basic.integration.scenic.client.ScenicMetaClient; -import com.ycwl.basic.integration.scenic.dto.meta.*; -import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.util.List; -import java.util.Map; - -@Slf4j -@Service -@RequiredArgsConstructor -public class ScenicMetaIntegrationService { - - private final ScenicMetaClient scenicMetaClient; - - public EnabledFieldsResponse getEnabledFields(Long scenicId) { - log.info("获取启用的字段, scenicId: {}", scenicId); - CommonResponse response = scenicMetaClient.getEnabledFields(scenicId); - return handleResponse(response, "获取启用的字段失败"); - } - - public List getAllFields() { - log.info("获取所有字段配置"); - CommonResponse> response = scenicMetaClient.getAllFields(); - return handleResponse(response, "获取所有字段配置失败"); - } - - public FieldConfigDTO getFieldConfig(String fieldKey) { - log.info("获取字段配置, fieldKey: {}", fieldKey); - CommonResponse response = scenicMetaClient.getFieldConfig(fieldKey); - return handleResponse(response, "获取字段配置失败"); - } - - public void setFieldEnabled(Long scenicId, String fieldKey, Boolean enabled) { - log.info("设置字段启用状态, scenicId: {}, fieldKey: {}, enabled: {}", scenicId, fieldKey, enabled); - SetFieldEnabledRequest request = new SetFieldEnabledRequest(); - request.setEnabled(enabled); - CommonResponse response = scenicMetaClient.setFieldEnabled(scenicId, fieldKey, request); - handleResponse(response, "设置字段启用状态失败"); - } - - public void batchSetFieldEnabled(Long scenicId, BatchSetFieldEnabledRequest request) { - log.info("批量设置字段启用状态, scenicId: {}, fields count: {}", scenicId, request.getFields().size()); - CommonResponse response = scenicMetaClient.batchSetFieldEnabled(scenicId, request); - handleResponse(response, "批量设置字段启用状态失败"); - } - - public ScenicV2WithConfigDTO getScenicWithConfigEnhanced(Long scenicId) { - log.info("获取景区与配置(增强版), scenicId: {}", scenicId); - CommonResponse response = scenicMetaClient.getScenicWithConfigEnhanced(scenicId); - return handleResponse(response, "获取景区与配置(增强版)失败"); - } - - public void updateConfigEnhanced(Long scenicId, Map configs) { - log.info("更新配置(增强版), scenicId: {}, configs count: {}", scenicId, configs.size()); - CommonResponse response = scenicMetaClient.updateConfigEnhanced(scenicId, configs); - handleResponse(response, "更新配置(增强版)失败"); - } - - private T handleResponse(CommonResponse response, String errorMessage) { - if (response == null || !response.isSuccess()) { - String msg = response != null && response.getMessage() != null - ? response.getMessage() - : errorMessage; - Integer code = response != null ? response.getCode() : 5000; - throw new IntegrationException(code, msg, "zt-scenic"); - } - return response.getData(); - } -} \ No newline at end of file From 6bc94a65a60bbb8ad1fa8fb1a04e3e4f044dc603 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 10:07:01 +0800 Subject: [PATCH 07/32] =?UTF-8?q?feat(scenic):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E4=BF=A1=E6=81=AF=E8=8E=B7=E5=8F=96=E4=B8=8E?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -移除景区信息查询相关冗余代码 - 增加缓存逻辑,提高景区信息获取效率 - 更新 ScenicRepository 中的 getScenic 和 getScenicConfig 方法 - 重构 ScenicServiceImpl 中的 list 方法 - 删除 ScenicService 接口中未使用的多个方法 --- .../model/pc/scenic/req/ScenicReqQuery.java | 42 ----- .../basic/repository/ScenicRepository.java | 41 ++++- .../ycwl/basic/service/pc/ScenicService.java | 16 -- .../service/pc/impl/ScenicServiceImpl.java | 174 +----------------- 4 files changed, 35 insertions(+), 238 deletions(-) diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/req/ScenicReqQuery.java b/src/main/java/com/ycwl/basic/model/pc/scenic/req/ScenicReqQuery.java index 048decc..4a55a33 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/req/ScenicReqQuery.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/req/ScenicReqQuery.java @@ -23,51 +23,9 @@ public class ScenicReqQuery extends BaseQueryParameterReq { */ // 景区名称 private String name; - /** - * 景区介绍 - */ - // 景区介绍 - private String introduction; - /** - * 经度 - */ - // 经度 - private BigDecimal longitude; - /*** - * 纬度 - */ - // 纬度 - private BigDecimal latitude; - /** - * 半径(km) - */ - // 半径(km) - private BigDecimal radius; - /** - * 省份 - */ - // 省份 - private String province; - /** - * 城市 - */ - // 城市 - private String city; - /** - * 区 - */ - // 区 - private String area; - /** - * 详细地址 - */ - // 详细地址 - private String address; /** * 状态 1启用0关闭 */ // 状态 1启用0关闭 private String status; - private Date startTime; - private Date endTime; } diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 4ed0b4a..29a4d93 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -3,7 +3,6 @@ package com.ycwl.basic.repository; import com.ycwl.basic.utils.JacksonUtil; import com.ycwl.basic.mapper.MpConfigMapper; import com.ycwl.basic.mapper.MpNotifyConfigMapper; -import com.ycwl.basic.mapper.ScenicMapper; import com.ycwl.basic.model.pc.mp.MpConfigEntity; import com.ycwl.basic.model.pc.mp.MpNotifyConfigEntity; import com.ycwl.basic.model.pc.mp.ScenicMpNotifyVO; @@ -24,8 +23,6 @@ import java.util.List; @Component public class ScenicRepository { - @Autowired - private ScenicMapper scenicMapper; @Autowired private MpConfigMapper mpConfigMapper; @Autowired @@ -44,18 +41,48 @@ public class ScenicRepository { public ScenicEntity getScenic(Long id) { try { ScenicV2DTO scenicDTO = scenicIntegrationService.getScenic(id); - return convertToScenicEntity(scenicDTO); + ScenicEntity scenicEntity = convertToScenicEntity(scenicDTO); + + // 请求成功,写入缓存 + if (scenicEntity != null) { + redisTemplate.opsForValue().set( + String.format(SCENIC_CACHE_KEY, id), + JacksonUtil.toJSONString(scenicEntity) + ); + } + return scenicEntity; } catch (Exception e) { - return scenicMapper.get(id); + // 请求失败,尝试从缓存获取历史成功数据 + String cacheKey = String.format(SCENIC_CACHE_KEY, id); + if (redisTemplate.hasKey(cacheKey)) { + return JacksonUtil.parseObject(redisTemplate.opsForValue().get(cacheKey), ScenicEntity.class); + } + // 缓存也没有,返回null + return null; } } public ScenicConfigEntity getScenicConfig(Long scenicId) { try { ScenicV2WithConfigDTO scenicWithConfigDTO = scenicIntegrationService.getScenicWithConfig(scenicId); - return convertToScenicConfigEntity(scenicWithConfigDTO, scenicId); + ScenicConfigEntity configEntity = convertToScenicConfigEntity(scenicWithConfigDTO, scenicId); + + // 请求成功,写入缓存 + if (configEntity != null) { + redisTemplate.opsForValue().set( + String.format(SCENIC_CONFIG_CACHE_KEY, scenicId), + JacksonUtil.toJSONString(configEntity) + ); + } + return configEntity; } catch (Exception e) { - return scenicMapper.getConfig(scenicId); + // 请求失败,尝试从缓存获取历史成功数据 + String cacheKey = String.format(SCENIC_CONFIG_CACHE_KEY, scenicId); + if (redisTemplate.hasKey(cacheKey)) { + return JacksonUtil.parseObject(redisTemplate.opsForValue().get(cacheKey), ScenicConfigEntity.class); + } + // 缓存也没有,返回null + return null; } } diff --git a/src/main/java/com/ycwl/basic/service/pc/ScenicService.java b/src/main/java/com/ycwl/basic/service/pc/ScenicService.java index 15ea13d..d7fef50 100644 --- a/src/main/java/com/ycwl/basic/service/pc/ScenicService.java +++ b/src/main/java/com/ycwl/basic/service/pc/ScenicService.java @@ -17,23 +17,7 @@ import java.util.List; * @Date:2024/12/3 15:22 */ public interface ScenicService { - ApiResponse> pageQuery(ScenicReqQuery scenicReqQuery); ApiResponse> list(ScenicReqQuery scenicReqQuery); - ApiResponse getById(Long id); - ApiResponse add(ScenicAddOrUpdateReq scenicAddOrUpdateReq); - ApiResponse deleteById(Long id); - ApiResponse update(ScenicAddOrUpdateReq scenicAddOrUpdateReq); - ApiResponse updateStatus(Long id); - ApiResponse addConfig(ScenicConfigEntity scenicConfig); - /** - * 修改景区配置 - * @param scenicConfig - * @return - */ - ApiResponse updateConfigById(ScenicConfigEntity scenicConfig); - - ScenicConfigEntity getConfig(Long id); - void saveConfig(Long configId, ScenicConfigEntity config); IStorageAdapter getScenicStorageAdapter(Long scenicId); diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java index 5f5a6ca..6e54ee6 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java @@ -44,186 +44,14 @@ public class ScenicServiceImpl implements ScenicService { @Autowired private ScenicMapper scenicMapper; @Autowired - private ScenicAccountMapper scenicAccountMapper; - @Autowired - private TaskFaceService taskFaceService; - @Autowired private ScenicRepository scenicRepository; @Override - public ApiResponse> pageQuery(ScenicReqQuery scenicReqQuery) { - PageHelper.startPage(scenicReqQuery.getPageNum(), scenicReqQuery.getPageSize()); - List list = scenicMapper.list(scenicReqQuery); - PageInfo pageInfo = new PageInfo<>(list); - return ApiResponse.success(pageInfo); - } - - @Override + @Deprecated public ApiResponse> list(ScenicReqQuery scenicReqQuery) { return ApiResponse.success(scenicMapper.list(scenicReqQuery)); } - @Override - public ApiResponse getById(Long id) { - return ApiResponse.success(scenicMapper.getById(id)); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public ApiResponse add(ScenicAddOrUpdateReq scenicAddReq) { - Long scenicId = SnowFlakeUtil.getLongId(); - scenicAddReq.setId(scenicId); - int add = scenicMapper.add(scenicAddReq); - ScenicAccountEntity scenicAccount = scenicAccountMapper.getByAccount(scenicAddReq.getAccount()); - if (scenicAccount == null) { - scenicAccount = new ScenicAccountEntity(); - scenicAccount.setId(SnowFlakeUtil.getLongId()); - scenicAccount.setName(scenicAddReq.getName() + "管理员"); - scenicAccount.setAccount(scenicAddReq.getAccount()); - scenicAccount.setPassword(scenicAddReq.getPassword()); - scenicAccount.setIsSuper(1); - scenicAccountMapper.add(scenicAccount); - } - scenicAccountMapper.addAccountScenicRelation(scenicAccount.getId(), scenicId, 1); - if (add > 0) { - return ApiResponse.success(true); - } else { - return ApiResponse.fail("景区添加失败"); - } - } - - @Override - @Transactional(rollbackFor = Exception.class) - public ApiResponse deleteById(Long id) { - int i = scenicMapper.deleteById(id); - if (i > 0) { - scenicAccountMapper.deleteRelationByScenicId(id); - IFaceBodyAdapter adapter = getScenicFaceBodyAdapter(id); - Thread.ofVirtual().start(() -> { - adapter.deleteFaceDb(id.toString()); - adapter.deleteFaceDb(USER_FACE_DB_NAME + id); - }); - scenicMapper.deleteConfigByScenicId(id); - scenicRepository.clearCache(id); - scenicFaceBodyAdapterMap.remove(id); - scenicStorageAdapterMap.remove(id); - scenicPayAdapterMap.remove(id); - return ApiResponse.success(true); - }else { - return ApiResponse.fail("景区删除失败"); - } - } - - @Override - public ApiResponse update(ScenicAddOrUpdateReq scenicUpdateReq) { - if (scenicUpdateReq.getId() == null) { - return ApiResponse.fail("参数错误"); - } - if (StringUtils.isNotBlank(scenicUpdateReq.getAccount()) && StringUtils.isNotBlank(scenicUpdateReq.getPassword())) { - ScenicAccountEntity scenicAccount = scenicAccountMapper.getByAccount(scenicUpdateReq.getAccount()); - if (scenicAccount != null) { - if (!scenicAccount.getScenicId().equals(scenicUpdateReq.getId())) { - return ApiResponse.fail("账号已存在"); - } - } - ScenicAccountEntity account = scenicAccountMapper.getSuperAccountOfScenic(scenicUpdateReq.getId()); - if (account != null) { - account.setAccount(scenicUpdateReq.getAccount()); - account.setPassword(scenicUpdateReq.getPassword()); - scenicAccountMapper.update(account); - } else { - account = new ScenicAccountEntity(); - account.setId(SnowFlakeUtil.getLongId()); - account.setName(scenicUpdateReq.getName() + "管理员"); - account.setAccount(scenicUpdateReq.getAccount()); - account.setPassword(scenicUpdateReq.getPassword()); - account.setIsSuper(1); - scenicAccountMapper.add(account); - } - scenicAccountMapper.addAccountScenicRelation(account.getId(), scenicUpdateReq.getId(), 1); - } - int i = scenicMapper.update(scenicUpdateReq); - if (i > 0) { - scenicRepository.clearCache(scenicUpdateReq.getId()); - scenicFaceBodyAdapterMap.remove(scenicUpdateReq.getId()); - scenicStorageAdapterMap.remove(scenicUpdateReq.getId()); - scenicTmpStorageAdapterMap.remove(scenicUpdateReq.getId()); - scenicLocalStorageAdapterMap.remove(scenicUpdateReq.getId()); - scenicPayAdapterMap.remove(scenicUpdateReq.getId()); - return ApiResponse.success(true); - }else { - return ApiResponse.fail("景区修改失败"); - } - - } - - @Override - public ApiResponse updateStatus(Long id) { - int i = scenicMapper.updateStatus(id); - if (i > 0) { - scenicRepository.clearCache(id); - scenicFaceBodyAdapterMap.remove(id); - scenicStorageAdapterMap.remove(id); - scenicTmpStorageAdapterMap.remove(id); - scenicLocalStorageAdapterMap.remove(id); - scenicPayAdapterMap.remove(id); - return ApiResponse.success(true); - }else { - return ApiResponse.fail("景区状态修改失败"); - } - } - - @Override - public ApiResponse addConfig(ScenicConfigEntity scenicConfig) { - if (scenicConfig.getId() == null) { - scenicConfig.setId(SnowFlakeUtil.getLongId()); - } - int i = scenicMapper.addConfig(scenicConfig); - if (i > 0) { - scenicRepository.clearCache(scenicConfig.getScenicId()); - return ApiResponse.success(true); - }else { - return ApiResponse.fail("景区配置添加失败"); - } - } - - @Override - public ApiResponse updateConfigById(ScenicConfigEntity scenicConfig) { - int i = scenicMapper.updateConfigById(scenicConfig); - if (i > 0) { - scenicRepository.clearCache(scenicConfig.getScenicId()); - scenicFaceBodyAdapterMap.remove(scenicConfig.getScenicId()); - scenicStorageAdapterMap.remove(scenicConfig.getScenicId()); - scenicTmpStorageAdapterMap.remove(scenicConfig.getScenicId()); - scenicLocalStorageAdapterMap.remove(scenicConfig.getScenicId()); - scenicPayAdapterMap.remove(scenicConfig.getScenicId()); - return ApiResponse.success(true); - }else { - return ApiResponse.fail("景区配置修改失败"); - } - } - - @Override - public ScenicConfigEntity getConfig(Long id) { - return scenicRepository.getScenicConfig(id); - } - - @Override - public void saveConfig(Long configId, ScenicConfigEntity config) { - config.setId(configId); - if (config.getScenicId() == null) { - throw new RuntimeException("景区ID不能为空"); - } - scenicMapper.updateConfigById(config); - scenicRepository.clearCache(config.getScenicId()); - scenicFaceBodyAdapterMap.remove(config.getScenicId()); - scenicStorageAdapterMap.remove(config.getScenicId()); - scenicTmpStorageAdapterMap.remove(config.getScenicId()); - scenicLocalStorageAdapterMap.remove(config.getScenicId()); - scenicPayAdapterMap.remove(config.getScenicId()); - } - - private static final Map scenicStorageAdapterMap = new ConcurrentHashMap<>(); @Override public IStorageAdapter getScenicStorageAdapter(Long scenicId) { From 42e7b7da955e0383dde15697aa7c266125c023a2 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 10:07:14 +0800 Subject: [PATCH 08/32] =?UTF-8?q?feat(AppScenicAccountController):?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=B9=B6=E4=BC=98=E5=8C=96=E6=99=AF=E5=8C=BA?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E8=8E=B7=E5=8F=96=E5=8A=9F=E8=83=BD-=20?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20list=20=E4=B8=BA=20Collections.em?= =?UTF-8?q?ptyList()=EF=BC=8C=E9=81=BF=E5=85=8D=E7=A9=BA=E6=8C=87=E9=92=88?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=20-=20=E5=A2=9E=E5=8A=A0=E5=AF=B9=20ADMIN=20?= =?UTF-8?q?=E8=A7=92=E8=89=B2=E7=9A=84=E5=A4=84=E7=90=86=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E5=85=B6=E8=83=BD=E5=A4=9F=E8=8E=B7=E5=8F=96=E6=99=AF=E5=8C=BA?= =?UTF-8?q?=E5=88=97=E8=A1=A8=20-=20=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E7=BB=93=E6=9E=84=EF=BC=8C=E6=8F=90=E9=AB=98=E5=8F=AF=E8=AF=BB?= =?UTF-8?q?=E6=80=A7=E5=92=8C=E7=BB=B4=E6=8A=A4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/mobile/manage/AppScenicAccountController.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ycwl/basic/controller/mobile/manage/AppScenicAccountController.java b/src/main/java/com/ycwl/basic/controller/mobile/manage/AppScenicAccountController.java index 8e292ea..0b2e8be 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/manage/AppScenicAccountController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/manage/AppScenicAccountController.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static com.ycwl.basic.constant.JwtRoleConstant.ADMIN; import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT; /** @@ -65,7 +66,7 @@ public class AppScenicAccountController { @GetMapping("/myScenicList") public ApiResponse> myScenicList() { - List list; + List list = Collections.emptyList(); if (Strings.CS.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) { String userId = BaseContextHandler.getUserId(); ScenicAccountEntity account = accountService.getScenicAccountById(Long.valueOf(userId)); @@ -75,7 +76,7 @@ public class AppScenicAccountController { list = account.getScenicId().stream() .map(id -> scenicService.getDetails(id).getData()) .toList(); - } else { + } else if (Strings.CS.equals(BaseContextHandler.getRoleId(), ADMIN.type)) { list = adminScenicService.list(new ScenicReqQuery()).getData(); } return ApiResponse.success(list); From b67fb87989e57bded1a6de526c97aba29f55d1af Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 10:07:30 +0800 Subject: [PATCH 09/32] =?UTF-8?q?refactor(basic):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E6=8E=A7=E5=88=B6=E5=99=A8=E4=B8=AD=E7=9A=84?= =?UTF-8?q?=E5=86=97=E4=BD=99=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除了 ScenicController 类中多个未使用的 API 方法 - 保留了下载小程序二维码的功能 -简化了代码结构,提高了代码可维护性 --- .../basic/controller/pc/ScenicController.java | 62 ------------------- 1 file changed, 62 deletions(-) diff --git a/src/main/java/com/ycwl/basic/controller/pc/ScenicController.java b/src/main/java/com/ycwl/basic/controller/pc/ScenicController.java index 4647b0d..7cf77fa 100644 --- a/src/main/java/com/ycwl/basic/controller/pc/ScenicController.java +++ b/src/main/java/com/ycwl/basic/controller/pc/ScenicController.java @@ -50,68 +50,6 @@ public class ScenicController { @Autowired private ScenicAccountService accountService; - // 分页查询景区 - @PostMapping("/page") - public ApiResponse> pageQuery(@RequestBody ScenicReqQuery scenicReqQuery) { - return scenicService.pageQuery(scenicReqQuery); - } - // 查询景区列表 - @PostMapping("/list") - public ApiResponse> list(@RequestBody ScenicReqQuery scenicReqQuery) { - return scenicService.list(scenicReqQuery); - } - // 查询景区详情 - @GetMapping("/getDetail/{id}") - public ApiResponse getDetail(@PathVariable Long id) { - return scenicService.getById(id); - } - // 新增景区 - @PostMapping("/add") - public ApiResponse add(@RequestBody ScenicAddOrUpdateReq scenicAddReq) { - return scenicService.add(scenicAddReq); - } - // 删除景区 - @GetMapping("/delete/{id}") - public ApiResponse delete(@PathVariable Long id) { - return scenicService.deleteById(id); - } - // 修改景区 - @PostMapping("/update") - public ApiResponse update(@RequestBody ScenicAddOrUpdateReq scenicAddReq) { - return scenicService.update(scenicAddReq); - } - // 修改景区状态 - @GetMapping("/updateStatus/{id}") - public ApiResponse updateStatus(@PathVariable Long id) { - return scenicService.updateStatus(id); - } - // 新增景区配置 - @PostMapping("/addConfig") - public ApiResponse addConfig(@RequestBody ScenicConfigEntity scenicConfig) { - return scenicService.addConfig(scenicConfig); - } - // 修改景区配置 - @PostMapping("/updateConfig") - public ApiResponse updateConfig(@RequestBody ScenicConfigEntity scenicConfig) { - return scenicService.updateConfigById(scenicConfig); - } - - // 查询景区配置 - @GetMapping("/config/{id}") - public ApiResponse getConfig(@PathVariable("id") Long id) { - return ApiResponse.success(scenicService.getConfig(id)); - } - @PostMapping("/saveConfig/{id}") - public ApiResponse saveConfig(@PathVariable("id") Long id, @RequestBody ScenicConfigEntity config) { - scenicService.saveConfig(id, config); - return ApiResponse.success(null); - } - @PostMapping("/saveConfig/undefined") - public ApiResponse saveConfig(@RequestBody ScenicConfigEntity config) { - scenicService.addConfig(config); - return ApiResponse.success(null); - } - // 根据景区ID下载小程序二维码 @GetMapping("/{id}/QRCode") public ApiResponse downloadQrCode(@PathVariable Long id) { From 7d40b8043de9f4addccb7ef546765698bccf8f6e Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 10:12:08 +0800 Subject: [PATCH 10/32] =?UTF-8?q?feat(basic):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了默认配置的列表获取、单个配置获取、创建、更新和删除功能- 使用日志记录操作信息- 异常处理确保错误信息返回给客户端 --- .../pc/DefaultConfigController.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/controller/pc/DefaultConfigController.java diff --git a/src/main/java/com/ycwl/basic/controller/pc/DefaultConfigController.java b/src/main/java/com/ycwl/basic/controller/pc/DefaultConfigController.java new file mode 100644 index 0000000..8ddfe5c --- /dev/null +++ b/src/main/java/com/ycwl/basic/controller/pc/DefaultConfigController.java @@ -0,0 +1,100 @@ +package com.ycwl.basic.controller.pc; + +import com.ycwl.basic.integration.scenic.dto.config.DefaultConfigDTO; +import com.ycwl.basic.integration.scenic.service.DefaultConfigIntegrationService; +import com.ycwl.basic.utils.ApiConst; +import com.ycwl.basic.utils.ApiResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 默认配置管理控制器 + * 提供默认配置的增删查改功能 + */ +@Slf4j +@RestController +@RequestMapping("/api/default-config") +@RequiredArgsConstructor +public class DefaultConfigController { + + private final DefaultConfigIntegrationService defaultConfigIntegrationService; + + /** + * 获取默认配置列表 + */ + @GetMapping("/") + public ApiResponse> listDefaultConfigs() { + log.info("获取默认配置列表"); + try { + List configs = defaultConfigIntegrationService.listDefaultConfigs(); + return ApiResponse.success(configs); + } catch (Exception e) { + log.error("获取默认配置列表失败", e); + return ApiResponse.fail("获取默认配置列表失败: " + e.getMessage()); + } + } + + /** + * 根据配置键获取默认配置 + */ + @GetMapping("/{configKey}") + public ApiResponse getDefaultConfig(@PathVariable String configKey) { + log.info("获取默认配置, configKey: {}", configKey); + try { + DefaultConfigDTO config = defaultConfigIntegrationService.getDefaultConfig(configKey); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("获取默认配置失败, configKey: {}", configKey, e); + return ApiResponse.fail("获取默认配置失败: " + e.getMessage()); + } + } + + /** + * 创建默认配置 + */ + @PostMapping("/") + public ApiResponse createDefaultConfig(@RequestBody DefaultConfigDTO request) { + log.info("创建默认配置, configKey: {}", request.getConfigKey()); + try { + DefaultConfigDTO config = defaultConfigIntegrationService.createDefaultConfig(request); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("创建默认配置失败, configKey: {}", request.getConfigKey(), e); + return ApiResponse.fail("创建默认配置失败: " + e.getMessage()); + } + } + + /** + * 更新默认配置 + */ + @PutMapping("/{configKey}") + public ApiResponse updateDefaultConfig(@PathVariable String configKey, + @RequestBody DefaultConfigDTO request) { + log.info("更新默认配置, configKey: {}", configKey); + try { + DefaultConfigDTO config = defaultConfigIntegrationService.updateDefaultConfig(configKey, request); + return ApiResponse.success(config); + } catch (Exception e) { + log.error("更新默认配置失败, configKey: {}", configKey, e); + return ApiResponse.fail("更新默认配置失败: " + e.getMessage()); + } + } + + /** + * 删除默认配置 + */ + @DeleteMapping("/{configKey}") + public ApiResponse deleteDefaultConfig(@PathVariable String configKey) { + log.info("删除默认配置, configKey: {}", configKey); + try { + defaultConfigIntegrationService.deleteDefaultConfig(configKey); + return ApiResponse.buildResponse(ApiConst.Code.CODE_SUCCESS.code(), null, "删除成功"); + } catch (Exception e) { + log.error("删除默认配置失败, configKey: {}", configKey, e); + return ApiResponse.fail("删除默认配置失败: " + e.getMessage()); + } + } +} \ No newline at end of file From 21f76ff9c5093228a6555dcf4f01603eab1825f2 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 10:25:51 +0800 Subject: [PATCH 11/32] =?UTF-8?q?refactor(scenic):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E5=92=8C?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -移除了 ScenicMapper 中的冗余方法 - 更新了 ScenicEntity 和 ScenicRespVO 的字段结构 - 重构了 ScenicRepository 中的缓存逻辑 - 优化了 AppScenicServiceImpl 中的景区详情获取方法 --- .../com/ycwl/basic/mapper/ScenicMapper.java | 38 ----------------- .../model/pc/scenic/entity/ScenicEntity.java | 7 +++- .../model/pc/scenic/resp/ScenicRespVO.java | 13 ------ .../basic/repository/ScenicRepository.java | 41 ++++++++++++++++--- .../mobile/impl/AppScenicServiceImpl.java | 27 +++++++++++- 5 files changed, 66 insertions(+), 60 deletions(-) diff --git a/src/main/java/com/ycwl/basic/mapper/ScenicMapper.java b/src/main/java/com/ycwl/basic/mapper/ScenicMapper.java index 6d4f8a8..b9c06fd 100644 --- a/src/main/java/com/ycwl/basic/mapper/ScenicMapper.java +++ b/src/main/java/com/ycwl/basic/mapper/ScenicMapper.java @@ -21,46 +21,8 @@ import java.util.List; public interface ScenicMapper { List list(ScenicReqQuery scenicReqQuery); - ScenicEntity get(Long id); - - ScenicRespVO getById(Long id); - - int add(ScenicAddOrUpdateReq scenic); - - int deleteById(Long id); - - int update(ScenicAddOrUpdateReq scenic); - - int updateStatus(Long id); - - ScenicConfigEntity getConfig(Long scenicId); - /** - * 添加景区配置 - * - * @param scenicConfig - * @return - */ - int addConfig(ScenicConfigEntity scenicConfig); - - /** - * 修改景区配置 - * - * @param scenicConfigEntity - * @return - */ - int updateConfigById(ScenicConfigEntity scenicConfigEntity); - - /** - * 根据景区id删除配置 - * - * @param scenicId - */ - void deleteConfigByScenicId(Long scenicId); - List appList(ScenicReqQuery scenicReqQuery); - ScenicRespVO getAppById(Long id); - /** * 通过经纬度计算景区距离 * diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicEntity.java b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicEntity.java index f0c69fc..b0ce70a 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicEntity.java @@ -13,15 +13,17 @@ import java.util.Date; * 景区管理表 */ @Data -@TableName("scenic") public class ScenicEntity { - @TableId private Long id; /** * 景区名称 */ private String name; private Integer mpId; + private String phone; + private String logoUrl; + // 封面图 + private String coverUrl; /** * 景区介绍 */ @@ -63,6 +65,7 @@ public class ScenicEntity { /** * 景区源素材价格,元 */ + private String kfCodeUrl; private BigDecimal price; private BigDecimal sourceVideoPrice; private BigDecimal sourceImagePrice; diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicRespVO.java b/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicRespVO.java index 63e23c0..99ce301 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicRespVO.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/resp/ScenicRespVO.java @@ -74,19 +74,6 @@ public class ScenicRespVO { */ // 详细地址 private String address; - /** - * 状态 1启用0关闭 - */ - // 状态 1启用0关闭 - private Integer status; - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") - private Date createTime; - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") - private Date updateTime; - // 景区源素材价格,元 - private BigDecimal price; - private BigDecimal sourceVideoPrice; - private BigDecimal sourceImagePrice; // 镜头数 private Integer lensNum; private String kfCodeUrl; diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 29a4d93..4c6a22e 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -39,21 +39,22 @@ public class ScenicRepository { private MpNotifyConfigMapper mpNotifyConfigMapper; public ScenicEntity getScenic(Long id) { + String key = String.format(SCENIC_CACHE_KEY, id); try { - ScenicV2DTO scenicDTO = scenicIntegrationService.getScenic(id); + ScenicV2WithConfigDTO scenicDTO = scenicIntegrationService.getScenicWithConfig(id); ScenicEntity scenicEntity = convertToScenicEntity(scenicDTO); // 请求成功,写入缓存 if (scenicEntity != null) { redisTemplate.opsForValue().set( - String.format(SCENIC_CACHE_KEY, id), + key, JacksonUtil.toJSONString(scenicEntity) ); } return scenicEntity; } catch (Exception e) { // 请求失败,尝试从缓存获取历史成功数据 - String cacheKey = String.format(SCENIC_CACHE_KEY, id); + String cacheKey = key; if (redisTemplate.hasKey(cacheKey)) { return JacksonUtil.parseObject(redisTemplate.opsForValue().get(cacheKey), ScenicEntity.class); } @@ -63,6 +64,7 @@ public class ScenicRepository { } public ScenicConfigEntity getScenicConfig(Long scenicId) { + String key = String.format(SCENIC_CONFIG_CACHE_KEY, scenicId); try { ScenicV2WithConfigDTO scenicWithConfigDTO = scenicIntegrationService.getScenicWithConfig(scenicId); ScenicConfigEntity configEntity = convertToScenicConfigEntity(scenicWithConfigDTO, scenicId); @@ -70,14 +72,14 @@ public class ScenicRepository { // 请求成功,写入缓存 if (configEntity != null) { redisTemplate.opsForValue().set( - String.format(SCENIC_CONFIG_CACHE_KEY, scenicId), + key, JacksonUtil.toJSONString(configEntity) ); } return configEntity; } catch (Exception e) { // 请求失败,尝试从缓存获取历史成功数据 - String cacheKey = String.format(SCENIC_CONFIG_CACHE_KEY, scenicId); + String cacheKey = key; if (redisTemplate.hasKey(cacheKey)) { return JacksonUtil.parseObject(redisTemplate.opsForValue().get(cacheKey), ScenicConfigEntity.class); } @@ -174,7 +176,34 @@ public class ScenicRepository { entity.setUpdateTime(new java.util.Date(dto.getUpdateTime())); return entity; } - + + private ScenicEntity convertToScenicEntity(ScenicV2WithConfigDTO dto) { + if (dto == null) { + return null; + } + ScenicEntity entity = new ScenicEntity(); + entity.setId(Long.parseLong(dto.getId())); + entity.setName(dto.getName()); + entity.setMpId(dto.getMpId()); + entity.setStatus(dto.getStatus().toString()); + entity.setCreateTime(new java.util.Date(dto.getCreateTime())); + entity.setUpdateTime(new java.util.Date(dto.getUpdateTime())); + if (dto.getConfig() != null) { + entity.setAddress(ConfigValueUtil.getStringValue(dto.getConfig(), "address")); + entity.setArea(ConfigValueUtil.getStringValue(dto.getConfig(), "area")); + entity.setCity(ConfigValueUtil.getStringValue(dto.getConfig(), "city")); + entity.setProvince(ConfigValueUtil.getStringValue(dto.getConfig(), "province")); + entity.setLatitude(ConfigValueUtil.getBigDecimalValue(dto.getConfig(), "latitude")); + entity.setLongitude(ConfigValueUtil.getBigDecimalValue(dto.getConfig(), "longitude")); + entity.setRadius(ConfigValueUtil.getBigDecimalValue(dto.getConfig(), "radius")); + entity.setPhone(ConfigValueUtil.getStringValue(dto.getConfig(), "phone")); + entity.setLogoUrl(ConfigValueUtil.getStringValue(dto.getConfig(), "logoUrl")); + entity.setCoverUrl(ConfigValueUtil.getStringValue(dto.getConfig(), "coverUrl")); + entity.setKfCodeUrl(ConfigValueUtil.getStringValue(dto.getConfig(), "kfCodeUrl")); + } + return entity; + } + private ScenicConfigEntity convertToScenicConfigEntity(ScenicV2WithConfigDTO dto, Long scenicId) { if (dto == null || dto.getConfig() == null) { return null; diff --git a/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java index bca2acb..be8976d 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java @@ -14,9 +14,12 @@ import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity; +import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; +import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; import com.ycwl.basic.repository.DeviceRepository; +import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.mobile.AppScenicService; import com.ycwl.basic.service.pc.ScenicAccountService; import com.ycwl.basic.utils.ApiResponse; @@ -57,6 +60,8 @@ public class AppScenicServiceImpl implements AppScenicService { private ExtraDeviceMapper extraDeviceMapper; @Autowired private RedisTemplate redisTemplate; + @Autowired + private ScenicRepository scenicRepository; @Override public ApiResponse> pageQuery(ScenicReqQuery scenicReqQuery) { @@ -75,7 +80,27 @@ public class AppScenicServiceImpl implements AppScenicService { @Override public ApiResponse getDetails(Long id) { - ScenicRespVO scenicRespVO = scenicMapper.getAppById(id); + ScenicEntity scenic = scenicRepository.getScenic(id); + ScenicRespVO scenicRespVO = new ScenicRespVO(); + + // 将ScenicEntity的值通过set/get方式写入到ScenicRespVO + if (scenic != null) { + scenicRespVO.setId(scenic.getId()); + scenicRespVO.setName(scenic.getName()); + scenicRespVO.setPhone(scenic.getPhone()); + scenicRespVO.setLogoUrl(scenic.getLogoUrl()); + scenicRespVO.setCoverUrl(scenic.getCoverUrl()); + scenicRespVO.setIntroduction(scenic.getIntroduction()); + scenicRespVO.setLongitude(scenic.getLongitude()); + scenicRespVO.setLatitude(scenic.getLatitude()); + scenicRespVO.setRadius(scenic.getRadius()); + scenicRespVO.setProvince(scenic.getProvince()); + scenicRespVO.setCity(scenic.getCity()); + scenicRespVO.setArea(scenic.getArea()); + scenicRespVO.setAddress(scenic.getAddress()); + scenicRespVO.setKfCodeUrl(scenic.getKfCodeUrl()); + } + ScenicDeviceCountVO scenicDeviceCountVO = deviceMapper.deviceCountByScenicId(id); scenicRespVO.setLensNum(scenicDeviceCountVO.getTotalDeviceCount()); return ApiResponse.success(scenicRespVO); From f2ac6aaea0f65dc4dce755969270464002bdfc71 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 16:37:57 +0800 Subject: [PATCH 12/32] =?UTF-8?q?refactor(scenic):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E5=92=8C?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移除 ScenicMapper 接口,将相关方法移至 ScenicRepository - 修改景区列表查询逻辑,使用 ScenicRepository 的 list 方法 - 优化景区详情获取方式,使用 ScenicRepository 的 getScenicBasic 方法 - 重构缓存机制,增加对景区基本信息的缓存 - 优化 AppScenicService 和 ScenicService接口,使用 ScenicV2DTO 替代 ScenicRespV --- .../mobile/AppScenicController.java | 13 +- .../manage/AppScenicAccountController.java | 16 +- .../basic/controller/pc/ScenicController.java | 31 +- .../controller/pc/ScenicV2Controller.java | 32 ++- .../com/ycwl/basic/mapper/ScenicMapper.java | 33 --- .../model/pc/scenic/entity/ScenicEntity.java | 5 - .../basic/repository/ScenicRepository.java | 69 ++++- .../custom/CustomUploadTaskService.java | 1 - .../service/mobile/AppScenicService.java | 3 +- .../mobile/impl/AppScenicServiceImpl.java | 103 ++++++- .../ycwl/basic/service/pc/ScenicService.java | 7 +- .../service/pc/impl/ScenicServiceImpl.java | 22 +- .../task/DownloadNotificationTasker.java | 14 +- .../ycwl/basic/task/DynamicTaskGenerator.java | 3 - .../java/com/ycwl/basic/task/FaceCleaner.java | 87 +++--- .../com/ycwl/basic/task/ScenicStatsTask.java | 28 +- .../ycwl/basic/task/VideoTaskGenerator.java | 27 +- src/main/resources/mapper/ScenicMapper.xml | 267 ------------------ .../com/ycwl/basic/task/FaceCleanerTest.java | 11 +- .../ycwl/basic/task/ScenicStatsTaskTest.java | 26 +- 20 files changed, 324 insertions(+), 474 deletions(-) delete mode 100644 src/main/java/com/ycwl/basic/mapper/ScenicMapper.java delete mode 100644 src/main/resources/mapper/ScenicMapper.xml diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppScenicController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppScenicController.java index 1593618..939708e 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppScenicController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppScenicController.java @@ -3,12 +3,12 @@ package com.ycwl.basic.controller.mobile; import com.github.pagehelper.PageInfo; import com.ycwl.basic.annotation.IgnoreToken; import com.ycwl.basic.constant.BaseContextHandler; -import com.ycwl.basic.model.jwt.JwtInfo; import com.ycwl.basic.model.mobile.scenic.ScenicAppVO; import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO; import com.ycwl.basic.model.mobile.scenic.ScenicIndexVO; import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; +import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.model.pc.scenic.resp.ScenicConfigResp; import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; @@ -16,11 +16,14 @@ import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.mobile.AppScenicService; import com.ycwl.basic.service.pc.FaceService; import com.ycwl.basic.utils.ApiResponse; -import com.ycwl.basic.utils.JwtTokenUtil; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @@ -49,7 +52,7 @@ public class AppScenicController { // 分页查询景区列表 @PostMapping("/page") - public ApiResponse> pageQuery(@RequestBody ScenicReqQuery scenicReqQuery){ + public ApiResponse> pageQuery(@RequestBody ScenicReqQuery scenicReqQuery){ String userId = BaseContextHandler.getUserId(); if (ENABLED_USER_IDs.contains(userId)) { return appScenicService.pageQuery(scenicReqQuery); diff --git a/src/main/java/com/ycwl/basic/controller/mobile/manage/AppScenicAccountController.java b/src/main/java/com/ycwl/basic/controller/mobile/manage/AppScenicAccountController.java index 0b2e8be..d56e934 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/manage/AppScenicAccountController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/manage/AppScenicAccountController.java @@ -2,14 +2,15 @@ package com.ycwl.basic.controller.mobile.manage; import com.ycwl.basic.annotation.IgnoreToken; import com.ycwl.basic.constant.BaseContextHandler; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginOldRespVO; import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginReq; import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO; -import com.ycwl.basic.model.mobile.weChat.DTO.WeChatUserInfoDTO; import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; +import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.mobile.AppScenicService; import com.ycwl.basic.service.pc.ScenicAccountService; import com.ycwl.basic.service.pc.ScenicService; @@ -23,7 +24,6 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -44,6 +44,8 @@ public class AppScenicAccountController { private AppScenicService scenicService; @Autowired private ScenicService adminScenicService; + @Autowired + private ScenicRepository scenicRepository; // 登录 @PostMapping("/login") @@ -65,8 +67,8 @@ public class AppScenicAccountController { } @GetMapping("/myScenicList") - public ApiResponse> myScenicList() { - List list = Collections.emptyList(); + public ApiResponse> myScenicList() { + List list = Collections.emptyList(); if (Strings.CS.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) { String userId = BaseContextHandler.getUserId(); ScenicAccountEntity account = accountService.getScenicAccountById(Long.valueOf(userId)); @@ -74,10 +76,12 @@ public class AppScenicAccountController { return ApiResponse.fail("景区账号未绑定景区"); } list = account.getScenicId().stream() - .map(id -> scenicService.getDetails(id).getData()) + .map(id -> scenicRepository.getScenicBasic(id)) .toList(); } else if (Strings.CS.equals(BaseContextHandler.getRoleId(), ADMIN.type)) { - list = adminScenicService.list(new ScenicReqQuery()).getData(); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + list = scenicRepository.list(query); } return ApiResponse.success(list); } diff --git a/src/main/java/com/ycwl/basic/controller/pc/ScenicController.java b/src/main/java/com/ycwl/basic/controller/pc/ScenicController.java index 7cf77fa..14c595a 100644 --- a/src/main/java/com/ycwl/basic/controller/pc/ScenicController.java +++ b/src/main/java/com/ycwl/basic/controller/pc/ScenicController.java @@ -1,13 +1,12 @@ package com.ycwl.basic.controller.pc; -import com.github.pagehelper.PageInfo; import com.ycwl.basic.constant.BaseContextHandler; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq; +import com.ycwl.basic.model.pc.mp.MpConfigEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity; -import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; -import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; -import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; +import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.mobile.AppScenicService; import com.ycwl.basic.service.mobile.AppStatisticsService; import com.ycwl.basic.service.pc.ScenicAccountService; @@ -17,16 +16,20 @@ import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.enums.StorageAcl; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.WxMpUtil; -import com.ycwl.basic.repository.ScenicRepository; -import com.ycwl.basic.model.pc.mp.MpConfigEntity; import org.apache.commons.lang3.Strings; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.io.File; import java.util.Collections; import java.util.List; +import static com.ycwl.basic.constant.JwtRoleConstant.ADMIN; import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT; /** @@ -105,19 +108,19 @@ public class ScenicController { } @GetMapping("/myScenicList") - public ApiResponse> myScenicList() { - List list = Collections.emptyList(); + public ApiResponse> myScenicList() { + List list = Collections.emptyList(); if (Strings.CS.equals(BaseContextHandler.getRoleId(), MERCHANT.type)) { String userId = BaseContextHandler.getUserId(); ScenicAccountEntity account = accountService.getScenicAccountById(Long.valueOf(userId)); if (account == null || account.getScenicId().isEmpty()) { return ApiResponse.fail("景区账号未绑定景区"); } - list = account.getScenicId().stream().map(id -> { - return appScenicService.getDetails(id).getData(); - }).toList(); - } else { - list = scenicService.list(new ScenicReqQuery()).getData(); + list = account.getScenicId().stream().map(id -> scenicRepository.getScenicBasic(id)).toList(); + } else if (Strings.CS.equals(BaseContextHandler.getRoleId(), ADMIN.type)) { + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + list = scenicRepository.list(query); } return ApiResponse.success(list); } diff --git a/src/main/java/com/ycwl/basic/controller/pc/ScenicV2Controller.java b/src/main/java/com/ycwl/basic/controller/pc/ScenicV2Controller.java index ef89644..52b7f8b 100644 --- a/src/main/java/com/ycwl/basic/controller/pc/ScenicV2Controller.java +++ b/src/main/java/com/ycwl/basic/controller/pc/ScenicV2Controller.java @@ -9,18 +9,26 @@ import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterPageResponse; import com.ycwl.basic.integration.scenic.dto.filter.ScenicFilterRequest; import com.ycwl.basic.integration.scenic.dto.scenic.CreateScenicRequest; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; -import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigListResponse; import com.ycwl.basic.integration.scenic.dto.scenic.UpdateScenicRequest; import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService; import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; import com.ycwl.basic.utils.ApiResponse; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +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 jakarta.validation.Valid; import java.util.List; import java.util.Map; @@ -165,6 +173,24 @@ public class ScenicV2Controller { } } + /** + * 景区列表查询(默认1000条) + * 只支持根据状态筛选 + */ + @GetMapping("/list") + public ApiResponse listScenicsByStatus(@RequestParam(required = false) Integer status) { + log.info("查询景区列表, status: {}", status); + try { + // 默认查询1000条数据,第1页 + ScenicV2ListResponse scenics = scenicIntegrationService.listScenics(1, 1000, status, null); + return ApiResponse.success(scenics); + } catch (Exception e) { + log.error("查询景区列表失败, status: {}", status, e); + return ApiResponse.fail("查询景区列表失败: " + e.getMessage()); + } + } + + // ========== 景区配置管理 ========== /** diff --git a/src/main/java/com/ycwl/basic/mapper/ScenicMapper.java b/src/main/java/com/ycwl/basic/mapper/ScenicMapper.java deleted file mode 100644 index b9c06fd..0000000 --- a/src/main/java/com/ycwl/basic/mapper/ScenicMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.ycwl.basic.mapper; - -import com.ycwl.basic.model.mobile.scenic.ScenicAppVO; -import com.ycwl.basic.model.mobile.scenic.ScenicIndexVO; -import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; -import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; -import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq; -import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; -import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; -import com.ycwl.basic.utils.ApiResponse; -import org.apache.ibatis.annotations.Mapper; - -import java.util.List; - -/** - * @Author:longbinbin - * @Date:2024/12/2 10:07 - * 景区管理表 - */ -@Mapper -public interface ScenicMapper { - List list(ScenicReqQuery scenicReqQuery); - - List appList(ScenicReqQuery scenicReqQuery); - - /** - * 通过经纬度计算景区距离 - * - * @param scenicIndexVO - * @return - */ - List scenicListByLnLa(ScenicIndexVO scenicIndexVO); -} diff --git a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicEntity.java b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicEntity.java index b0ce70a..be34ffb 100644 --- a/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicEntity.java +++ b/src/main/java/com/ycwl/basic/model/pc/scenic/entity/ScenicEntity.java @@ -1,11 +1,8 @@ package com.ycwl.basic.model.pc.scenic.entity; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.math.BigDecimal; -import java.util.Date; /** * @Author:longbinbin @@ -60,8 +57,6 @@ public class ScenicEntity { * 状态 1启用0关闭 */ private String status; - private Date createTime; - private Date updateTime; /** * 景区源素材价格,元 */ diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 4c6a22e..0e8325b 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -1,6 +1,11 @@ package com.ycwl.basic.repository; -import com.ycwl.basic.utils.JacksonUtil; +import com.ycwl.basic.facebody.enums.FaceBodyAdapterType; +import com.ycwl.basic.integration.common.util.ConfigValueUtil; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; +import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; import com.ycwl.basic.mapper.MpConfigMapper; import com.ycwl.basic.mapper.MpNotifyConfigMapper; import com.ycwl.basic.model.pc.mp.MpConfigEntity; @@ -8,13 +13,10 @@ import com.ycwl.basic.model.pc.mp.MpNotifyConfigEntity; import com.ycwl.basic.model.pc.mp.ScenicMpNotifyVO; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; -import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; -import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; -import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; -import com.ycwl.basic.integration.common.util.ConfigValueUtil; -import com.ycwl.basic.facebody.enums.FaceBodyAdapterType; +import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.pay.enums.PayAdapterType; import com.ycwl.basic.storage.enums.StorageType; +import com.ycwl.basic.utils.JacksonUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -31,6 +33,7 @@ public class ScenicRepository { private ScenicIntegrationService scenicIntegrationService; public static final String SCENIC_CACHE_KEY = "scenic:%s"; + public static final String SCENIC_BASIC_CACHE_KEY = "scenic:basic:%s"; public static final String SCENIC_FULL_CACHE_KEY = "scenic:f%s"; public static final String SCENIC_CONFIG_CACHE_KEY = "scenic:%s:config"; public static final String SCENIC_MP_CACHE_KEY = "scenic:%s:mp"; @@ -38,6 +41,30 @@ public class ScenicRepository { @Autowired private MpNotifyConfigMapper mpNotifyConfigMapper; + public ScenicV2DTO getScenicBasic(Long id) { + String key = String.format(SCENIC_BASIC_CACHE_KEY, id); + try { + ScenicV2DTO scenicDTO = scenicIntegrationService.getScenic(id); + + // 请求成功,写入缓存 + if (scenicDTO != null) { + redisTemplate.opsForValue().set( + key, + JacksonUtil.toJSONString(scenicDTO) + ); + } + return scenicDTO; + } catch (Exception e) { + // 请求失败,尝试从缓存获取历史成功数据 + String cacheKey = key; + if (redisTemplate.hasKey(cacheKey)) { + return JacksonUtil.parseObject(redisTemplate.opsForValue().get(cacheKey), ScenicV2DTO.class); + } + // 缓存也没有,返回null + return null; + } + } + public ScenicEntity getScenic(Long id) { String key = String.format(SCENIC_CACHE_KEY, id); try { @@ -155,8 +182,34 @@ public class ScenicRepository { return null; } + public List list(ScenicReqQuery scenicReqQuery) { + try { + // 将 ScenicReqQuery 参数转换为 zt-scenic 服务需要的参数 + Integer page = scenicReqQuery.getPageNum(); + Integer pageSize = scenicReqQuery.getPageSize(); + Integer status = null; + if (scenicReqQuery.getStatus() != null) { + status = Integer.valueOf(scenicReqQuery.getStatus()); + } + String name = scenicReqQuery.getName(); + + // 调用 zt-scenic 服务的 list 方法 + ScenicV2ListResponse response = scenicIntegrationService.listScenics(page, pageSize, status, name); + + // 将 ScenicV2DTO 列表转换为 ScenicEntity 列表 + if (response != null && response.getList() != null) { + return response.getList(); + } + return new java.util.ArrayList<>(); + } catch (Exception e) { + // 如果调用失败,返回空列表 + return new java.util.ArrayList<>(); + } + } + public void clearCache(Long scenicId) { redisTemplate.delete(String.format(SCENIC_CACHE_KEY, scenicId)); + redisTemplate.delete(String.format(SCENIC_BASIC_CACHE_KEY, scenicId)); redisTemplate.delete(String.format(SCENIC_FULL_CACHE_KEY, scenicId)); redisTemplate.delete(String.format(SCENIC_CONFIG_CACHE_KEY, scenicId)); redisTemplate.delete(String.format(SCENIC_MP_CACHE_KEY, scenicId)); @@ -172,8 +225,6 @@ public class ScenicRepository { entity.setName(dto.getName()); entity.setMpId(dto.getMpId()); entity.setStatus(dto.getStatus().toString()); - entity.setCreateTime(new java.util.Date(dto.getCreateTime())); - entity.setUpdateTime(new java.util.Date(dto.getUpdateTime())); return entity; } @@ -186,8 +237,6 @@ public class ScenicRepository { entity.setName(dto.getName()); entity.setMpId(dto.getMpId()); entity.setStatus(dto.getStatus().toString()); - entity.setCreateTime(new java.util.Date(dto.getCreateTime())); - entity.setUpdateTime(new java.util.Date(dto.getUpdateTime())); if (dto.getConfig() != null) { entity.setAddress(ConfigValueUtil.getStringValue(dto.getConfig(), "address")); entity.setArea(ConfigValueUtil.getStringValue(dto.getConfig(), "area")); diff --git a/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java b/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java index 14251de..44ef69a 100644 --- a/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java +++ b/src/main/java/com/ycwl/basic/service/custom/CustomUploadTaskService.java @@ -13,7 +13,6 @@ import com.ycwl.basic.facebody.entity.AddFaceResp; import com.ycwl.basic.mapper.DeviceMapper; import com.ycwl.basic.mapper.CustomUploadTaskMapper; import com.ycwl.basic.mapper.FaceSampleMapper; -import com.ycwl.basic.mapper.ScenicMapper; import com.ycwl.basic.mapper.SourceMapper; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.model.custom.entity.CustomUploadTaskEntity; diff --git a/src/main/java/com/ycwl/basic/service/mobile/AppScenicService.java b/src/main/java/com/ycwl/basic/service/mobile/AppScenicService.java index 6f8a78f..3ae80d6 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/AppScenicService.java +++ b/src/main/java/com/ycwl/basic/service/mobile/AppScenicService.java @@ -7,6 +7,7 @@ import com.ycwl.basic.model.mobile.scenic.ScenicIndexVO; import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginReq; import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO; import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; +import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; import com.ycwl.basic.utils.ApiResponse; @@ -18,7 +19,7 @@ import java.util.List; * @Date:2024/12/6 10:23 */ public interface AppScenicService { - ApiResponse> pageQuery(ScenicReqQuery scenicReqQuery); + ApiResponse> pageQuery(ScenicReqQuery scenicReqQuery); ApiResponse deviceCountByScenicId(Long scenicId); diff --git a/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java index be8976d..9964de7 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java @@ -1,10 +1,11 @@ package com.ycwl.basic.service.mobile.impl; import cn.hutool.core.bean.BeanUtil; -import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; -import com.ycwl.basic.constant.BaseContextHandler; -import com.ycwl.basic.mapper.*; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; +import com.ycwl.basic.mapper.DeviceMapper; +import com.ycwl.basic.mapper.ExtraDeviceMapper; +import com.ycwl.basic.mapper.ScenicAccountMapper; import com.ycwl.basic.model.jwt.JwtInfo; import com.ycwl.basic.model.mobile.scenic.ScenicAppVO; import com.ycwl.basic.model.mobile.scenic.ScenicDeviceCountVO; @@ -14,7 +15,6 @@ import com.ycwl.basic.model.mobile.scenic.account.ScenicLoginRespVO; import com.ycwl.basic.model.pc.device.entity.DeviceEntity; import com.ycwl.basic.model.pc.device.resp.DeviceRespVO; import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity; -import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; @@ -31,6 +31,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -44,8 +45,6 @@ import static com.ycwl.basic.constant.JwtRoleConstant.MERCHANT; @Service public class AppScenicServiceImpl implements AppScenicService { - @Autowired - private ScenicMapper scenicMapper; @Autowired private DeviceMapper deviceMapper; @Autowired @@ -64,10 +63,15 @@ public class AppScenicServiceImpl implements AppScenicService { private ScenicRepository scenicRepository; @Override - public ApiResponse> pageQuery(ScenicReqQuery scenicReqQuery) { - PageHelper.startPage(scenicReqQuery.getPageNum(), scenicReqQuery.getPageSize()); - List list = scenicMapper.appList(scenicReqQuery); - PageInfo pageInfo = new PageInfo<>(list); + public ApiResponse> pageQuery(ScenicReqQuery scenicReqQuery) { + + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); + List list = scenicList.stream().map(scenic -> { + return scenicRepository.getScenic(Long.valueOf(scenic.getId())); + }).toList(); + PageInfo pageInfo = new PageInfo<>(list); return ApiResponse.success(pageInfo); } @@ -140,8 +144,58 @@ public class AppScenicServiceImpl implements AppScenicService { @Override public List scenicListByLnLa(ScenicIndexVO scenicIndexVO) { - List scenicAppVOS = scenicMapper.scenicListByLnLa(scenicIndexVO); - return scenicAppVOS.stream().filter(scenic -> scenic.getDistance().compareTo(scenic.getRadius().multiply(BigDecimal.valueOf(1_000L))) < 0).toList(); + // 从 scenicRepository 获取所有景区(1000个) + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageNum(1); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); + + List list = new ArrayList<>(); + + // 为每个景区获取详细信息(包含经纬度) + for (ScenicV2DTO scenicDTO : scenicList) { + try { + // 获取景区详细信息(包含经纬度) + ScenicEntity scenicEntity = scenicRepository.getScenic(Long.parseLong(scenicDTO.getId())); + if (scenicEntity != null && scenicEntity.getLatitude() != null && scenicEntity.getLongitude() != null) { + // 计算距离 + BigDecimal distance = calculateDistance( + scenicIndexVO.getLatitude(), + scenicIndexVO.getLongitude(), + scenicEntity.getLatitude(), + scenicEntity.getLongitude() + ); + + // 根据距离和范围筛选景区 + if (scenicEntity.getRadius() != null && + distance.compareTo(scenicEntity.getRadius().multiply(BigDecimal.valueOf(1_000L))) < 0) { + + // 转换为 ScenicAppVO + ScenicAppVO scenicAppVO = new ScenicAppVO(); + scenicAppVO.setId(scenicEntity.getId()); + scenicAppVO.setName(scenicEntity.getName()); + scenicAppVO.setPhone(scenicEntity.getPhone()); + scenicAppVO.setIntroduction(scenicEntity.getIntroduction()); + scenicAppVO.setCoverUrl(scenicEntity.getCoverUrl()); + scenicAppVO.setLongitude(scenicEntity.getLongitude()); + scenicAppVO.setLatitude(scenicEntity.getLatitude()); + scenicAppVO.setRadius(scenicEntity.getRadius()); + scenicAppVO.setProvince(scenicEntity.getProvince()); + scenicAppVO.setCity(scenicEntity.getCity()); + scenicAppVO.setArea(scenicEntity.getArea()); + scenicAppVO.setAddress(scenicEntity.getAddress()); + scenicAppVO.setDistance(distance); + + list.add(scenicAppVO); + } + } + } catch (Exception e) { + // 单个景区获取失败,继续处理下一个 + continue; + } + } + + return list; } @Override @@ -183,4 +237,29 @@ public class AppScenicServiceImpl implements AppScenicService { deviceRespVOList.addAll(0, extraDeviceList); return ApiResponse.success(deviceRespVOList); } + + /** + * 计算两点之间的距离(米) + * 使用 Haversine 公式 + */ + private BigDecimal calculateDistance(BigDecimal lat1, BigDecimal lon1, BigDecimal lat2, BigDecimal lon2) { + if (lat1 == null || lon1 == null || lat2 == null || lon2 == null) { + return BigDecimal.ZERO; + } + + final double R = 6371000; // 地球半径(米) + + double lat1Rad = Math.toRadians(lat1.doubleValue()); + double lat2Rad = Math.toRadians(lat2.doubleValue()); + double deltaLat = Math.toRadians(lat2.subtract(lat1).doubleValue()); + double deltaLon = Math.toRadians(lon2.subtract(lon1).doubleValue()); + + double a = Math.sin(deltaLat/2) * Math.sin(deltaLat/2) + + Math.cos(lat1Rad) * Math.cos(lat2Rad) * + Math.sin(deltaLon/2) * Math.sin(deltaLon/2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); + + double distance = R * c; + return BigDecimal.valueOf(distance); + } } diff --git a/src/main/java/com/ycwl/basic/service/pc/ScenicService.java b/src/main/java/com/ycwl/basic/service/pc/ScenicService.java index d7fef50..c70fc51 100644 --- a/src/main/java/com/ycwl/basic/service/pc/ScenicService.java +++ b/src/main/java/com/ycwl/basic/service/pc/ScenicService.java @@ -1,11 +1,8 @@ package com.ycwl.basic.service.pc; -import com.github.pagehelper.PageInfo; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; -import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; -import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; -import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; import com.ycwl.basic.pay.adapter.IPayAdapter; import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.utils.ApiResponse; @@ -17,7 +14,7 @@ import java.util.List; * @Date:2024/12/3 15:22 */ public interface ScenicService { - ApiResponse> list(ScenicReqQuery scenicReqQuery); + ApiResponse> list(ScenicReqQuery scenicReqQuery); IStorageAdapter getScenicStorageAdapter(Long scenicId); diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java index 6e54ee6..2cfdf82 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java @@ -1,39 +1,27 @@ package com.ycwl.basic.service.pc.impl; -import com.ycwl.basic.utils.JacksonUtil; -import com.github.pagehelper.PageHelper; -import com.github.pagehelper.PageInfo; import com.ycwl.basic.facebody.FaceBodyFactory; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; -import com.ycwl.basic.mapper.ScenicAccountMapper; -import com.ycwl.basic.mapper.ScenicMapper; -import com.ycwl.basic.model.pc.scenic.entity.ScenicAccountEntity; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; -import com.ycwl.basic.model.pc.scenic.req.ScenicAddOrUpdateReq; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; -import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; import com.ycwl.basic.pay.PayFactory; import com.ycwl.basic.pay.adapter.IPayAdapter; import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.pc.ScenicService; -import com.ycwl.basic.service.task.TaskFaceService; import com.ycwl.basic.storage.StorageFactory; import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.exceptions.StorageUnsupportedException; import com.ycwl.basic.utils.ApiResponse; -import com.ycwl.basic.utils.SnowFlakeUtil; +import com.ycwl.basic.utils.JacksonUtil; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME; - /** * @Author:longbinbin * @Date:2024/12/3 15:25 @@ -41,15 +29,13 @@ import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME; @Slf4j @Service public class ScenicServiceImpl implements ScenicService { - @Autowired - private ScenicMapper scenicMapper; @Autowired private ScenicRepository scenicRepository; @Override @Deprecated - public ApiResponse> list(ScenicReqQuery scenicReqQuery) { - return ApiResponse.success(scenicMapper.list(scenicReqQuery)); + public ApiResponse> list(ScenicReqQuery scenicReqQuery) { + return ApiResponse.success(scenicRepository.list(scenicReqQuery)); } private static final Map scenicStorageAdapterMap = new ConcurrentHashMap<>(); diff --git a/src/main/java/com/ycwl/basic/task/DownloadNotificationTasker.java b/src/main/java/com/ycwl/basic/task/DownloadNotificationTasker.java index 8a9104e..8936eeb 100644 --- a/src/main/java/com/ycwl/basic/task/DownloadNotificationTasker.java +++ b/src/main/java/com/ycwl/basic/task/DownloadNotificationTasker.java @@ -1,9 +1,9 @@ package com.ycwl.basic.task; import cn.hutool.core.date.DateUtil; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.mapper.CouponMapper; import com.ycwl.basic.mapper.MemberMapper; -import com.ycwl.basic.mapper.ScenicMapper; import com.ycwl.basic.mapper.VideoMapper; import com.ycwl.basic.model.pc.coupon.req.CouponQueryReq; import com.ycwl.basic.model.pc.coupon.resp.CouponRespVO; @@ -12,8 +12,6 @@ import com.ycwl.basic.model.pc.mp.MpConfigEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; -import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; -import com.ycwl.basic.model.pc.template.resp.TemplateRespVO; import com.ycwl.basic.notify.NotifyFactory; import com.ycwl.basic.notify.adapters.INotifyAdapter; import com.ycwl.basic.notify.entity.NotifyContent; @@ -47,10 +45,6 @@ public class DownloadNotificationTasker { @Autowired private MemberMapper memberMapper; @Autowired - private TemplateRepository templateRepository; - @Autowired - private ScenicMapper scenicMapper; - @Autowired private CouponMapper couponMapper; @Scheduled(cron = "0 0 21 * * *") @@ -168,7 +162,9 @@ public class DownloadNotificationTasker { @Scheduled(cron = "0 0 * * * *") public void sendExtraDownloadNotification() { log.info("开始执行定时任务"); - List scenicList = scenicMapper.list(new ScenicReqQuery()); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); if (scenicList.isEmpty()) { return; } @@ -177,7 +173,7 @@ public class DownloadNotificationTasker { int currentHour = calendar.get(Calendar.HOUR_OF_DAY); calendar.clear(); scenicList.parallelStream().forEach(scenic -> { - Long scenicId = scenic.getId(); + Long scenicId = Long.parseLong(scenic.getId()); ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); if (scenicConfig == null) { return; diff --git a/src/main/java/com/ycwl/basic/task/DynamicTaskGenerator.java b/src/main/java/com/ycwl/basic/task/DynamicTaskGenerator.java index bbe3d0b..2b00449 100644 --- a/src/main/java/com/ycwl/basic/task/DynamicTaskGenerator.java +++ b/src/main/java/com/ycwl/basic/task/DynamicTaskGenerator.java @@ -7,7 +7,6 @@ import com.ycwl.basic.facebody.adapter.AliFaceBodyAdapter; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; import com.ycwl.basic.mapper.FaceMapper; import com.ycwl.basic.mapper.FaceSampleMapper; -import com.ycwl.basic.mapper.ScenicMapper; import com.ycwl.basic.mapper.TemplateMapper; import com.ycwl.basic.model.pc.face.entity.FaceEntity; import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO; @@ -40,8 +39,6 @@ import static com.ycwl.basic.constant.FaceConstant.USER_FACE_DB_NAME; @EnableScheduling @Slf4j public class DynamicTaskGenerator { - @Autowired - private ScenicMapper scenicMapper; @Autowired private TemplateMapper templateMapper; @Autowired diff --git a/src/main/java/com/ycwl/basic/task/FaceCleaner.java b/src/main/java/com/ycwl/basic/task/FaceCleaner.java index 386b802..b63b586 100644 --- a/src/main/java/com/ycwl/basic/task/FaceCleaner.java +++ b/src/main/java/com/ycwl/basic/task/FaceCleaner.java @@ -4,9 +4,9 @@ import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUtil; import com.ycwl.basic.constant.StorageConstant; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.mapper.FaceMapper; import com.ycwl.basic.mapper.FaceSampleMapper; -import com.ycwl.basic.mapper.ScenicMapper; import com.ycwl.basic.mapper.SourceMapper; import com.ycwl.basic.mapper.VideoMapper; import com.ycwl.basic.model.pc.face.entity.FaceEntity; @@ -16,7 +16,6 @@ import com.ycwl.basic.model.pc.faceSample.req.FaceSampleReqQuery; import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; -import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; import com.ycwl.basic.model.pc.source.req.SourceReqQuery; import com.ycwl.basic.model.pc.source.resp.SourceRespVO; import com.ycwl.basic.model.pc.video.req.VideoReqQuery; @@ -46,8 +45,6 @@ import static com.ycwl.basic.constant.StorageConstant.VIID_FACE; @Slf4j @Profile("prod") public class FaceCleaner { - @Autowired - private ScenicMapper scenicMapper; @Autowired private FaceSampleMapper faceSampleMapper; @Autowired @@ -64,25 +61,27 @@ public class FaceCleaner { @Scheduled(cron = "0 0 1 * * ?") public void deleteExpireSample(){ - ScenicReqQuery scenicQuery = new ScenicReqQuery(); - List scenicList = scenicMapper.list(scenicQuery); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); scenicList.parallelStream().forEach(scenic -> { - log.info("当前景区{},开始删除人脸样本", scenic.getId()); - IFaceBodyAdapter adapter = scenicService.getScenicFaceBodyAdapter(scenic.getId()); - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenic.getId()); + Long scenicId = Long.parseLong(scenic.getId()); + log.info("当前景区{},开始删除人脸样本", scenicId); + IFaceBodyAdapter adapter = scenicService.getScenicFaceBodyAdapter(scenicId); + ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); Integer sampleStoreDay = scenicConfig.getSampleStoreDay(); if (sampleStoreDay == null) { log.info("当前景区{},人脸样本保存天数未设置,默认7天", scenic.getId()); sampleStoreDay = 7; } Date sampleEndDate = DateUtil.offsetDay(DateUtil.beginOfDay(new Date()), -sampleStoreDay); - List faceSampleList = faceSampleMapper.listEntityBeforeDate(scenic.getId(), sampleEndDate); + List faceSampleList = faceSampleMapper.listEntityBeforeDate(scenicId, sampleEndDate); if (faceSampleList.isEmpty()) { log.info("当前景区{},人脸样本为空", scenic.getId()); return; } faceSampleList.forEach(faceSample -> { - boolean success = adapter.deleteFace(String.valueOf(scenic.getId()), faceSample.getId().toString()); + boolean success = adapter.deleteFace(scenic.getId(), faceSample.getId().toString()); if (success) { log.info("当前景区{},人脸样本ID{},删除成功", scenic.getId(), faceSample.getId()); faceSampleMapper.deleteById(faceSample.getId()); @@ -95,21 +94,23 @@ public class FaceCleaner { @Scheduled(cron = "0 45 2 * * ?") public void deleteExpireFace() { - ScenicReqQuery scenicQuery = new ScenicReqQuery(); - List scenicList = scenicMapper.list(scenicQuery); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); scenicList.parallelStream().forEach(scenic -> { + Long scenicId = Long.parseLong(scenic.getId()); log.info("当前景区{},开始删除用户人脸", scenic.getId()); - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenic.getId()); - IFaceBodyAdapter adapter = scenicService.getScenicFaceBodyAdapter(scenic.getId()); + ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); + IFaceBodyAdapter adapter = scenicService.getScenicFaceBodyAdapter(scenicId); Integer faceStoreDay = scenicConfig.getFaceStoreDay(); if (faceStoreDay == null) { log.info("当前景区{},人脸样本保存天数未设置,默认3天", scenic.getName()); faceStoreDay = 3; } FaceReqQuery req = new FaceReqQuery(); - req.setScenicId(scenic.getId()); + req.setScenicId(scenicId); Date faceEndDate = DateUtil.offsetDay(DateUtil.beginOfDay(new Date()), -faceStoreDay); - List list = faceMapper.listUnpaidEntityBeforeDate(scenic.getId(), faceEndDate); + List list = faceMapper.listUnpaidEntityBeforeDate(scenicId, faceEndDate); list.forEach(face -> { boolean result = adapter.deleteFace(USER_FACE_DB_NAME+face.getScenicId(), face.getId().toString()); if (result) { @@ -130,10 +131,12 @@ public class FaceCleaner { @Scheduled(cron = "0 0 1 * * ?") public void deleteNotBuySource(){ - ScenicReqQuery scenicQuery = new ScenicReqQuery(); - List scenicList = scenicMapper.list(scenicQuery); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); scenicList.parallelStream().forEach(scenic -> { - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenic.getId()); + Long scenicId = Long.valueOf(scenic.getId()); + ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); if (scenicConfig == null) { log.info("当前景区{},无配置信息", scenic.getName()); return; @@ -144,17 +147,19 @@ public class FaceCleaner { } int expireDay = scenicConfig.getUserSourceExpireDay(); Date endDate = DateUtil.offsetDay(DateUtil.beginOfDay(new Date()), -expireDay); - int deleteCount = sourceMapper.deleteNotBuyRelations(scenic.getId(), endDate); + int deleteCount = sourceMapper.deleteNotBuyRelations(scenicId, endDate); log.info("当前景区{},删除关联素材{}个", scenic.getName(), deleteCount); }); } @Scheduled(cron = "0 15 1 * * ?") public void deleteNotBuyVideos(){ - ScenicReqQuery scenicQuery = new ScenicReqQuery(); - List scenicList = scenicMapper.list(scenicQuery); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); scenicList.parallelStream().forEach(scenic -> { - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenic.getId()); + Long scenicId = Long.valueOf(scenic.getId()); + ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); if (scenicConfig == null) { log.info("当前景区{},无配置信息", scenic.getName()); return; @@ -165,7 +170,7 @@ public class FaceCleaner { } int expireDay = scenicConfig.getVideoStoreDay(); Date endDate = DateUtil.offsetDay(DateUtil.beginOfDay(new Date()), -expireDay); - int deleteCount = videoMapper.deleteNotBuyRelations(scenic.getId(), endDate); + int deleteCount = videoMapper.deleteNotBuyRelations(scenicId, endDate); int deleteVideoCount = videoMapper.deleteUselessVideo(); log.info("当前景区{},删除VLOG关系{}个,删除VLOG记录{}个", scenic.getName(), deleteCount, deleteVideoCount); }); @@ -173,10 +178,12 @@ public class FaceCleaner { @Scheduled(cron = "0 30 1 * * ?") public void deleteExpiredSource(){ - ScenicReqQuery scenicQuery = new ScenicReqQuery(); - List scenicList = scenicMapper.list(scenicQuery); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); scenicList.parallelStream().forEach(scenic -> { - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenic.getId()); + Long scenicId = Long.valueOf(scenic.getId()); + ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); if (scenicConfig == null) { log.info("当前景区{},无配置信息", scenic.getName()); return; @@ -193,10 +200,10 @@ public class FaceCleaner { } else { log.info("当前景区{},原始素材保存天数未设置,默认7天", scenic.getName()); } - if (Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo())) { + if (Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo())) { return; } - if (Integer.valueOf(1).equals(scenicConfig.getDisableSourceImage())) { + if (Boolean.TRUE.equals(scenicConfig.getDisableSourceImage())) { return; } log.info("当前景区{},开始删除原始素材", scenic.getName()); @@ -240,12 +247,16 @@ public class FaceCleaner { log.info("开始清理源视频素材文件"); List list = sourceMapper.list(new SourceReqQuery()); ArrayList adapterIdentity = new ArrayList<>(); - scenicMapper.list(new ScenicReqQuery()).forEach(scenic -> { - if (disableDeleteScenicIds.contains(scenic.getId().toString())) { + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); + scenicList.forEach(scenic -> { + Long scenicId = Long.valueOf(scenic.getId()); + if (disableDeleteScenicIds.contains(scenic.getId())) { log.info("景区【{}】禁止删除文件,跳过!", scenic.getName()); return; } - IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenic.getId()); + IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenicId); String identity = adapter.identity(); if (!adapterIdentity.contains(identity)) { log.info("因为Identity相同,跳过"); @@ -291,12 +302,16 @@ public class FaceCleaner { log.info("开始清理视频文件"); List list = videoMapper.list(new VideoReqQuery()); ArrayList adapterIdentity = new ArrayList<>(); - scenicMapper.list(new ScenicReqQuery()).forEach(scenic -> { - if (disableDeleteScenicIds.contains(scenic.getId().toString())) { + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); + scenicList.forEach(scenic -> { + Long scenicId = Long.valueOf(scenic.getId()); + if (disableDeleteScenicIds.contains(scenic.getId())) { log.info("景区【{}】禁止删除文件,跳过!", scenic.getName()); return; } - IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenic.getId()); + IStorageAdapter adapter = scenicService.getScenicStorageAdapter(scenicId); String identity = adapter.identity(); if (!adapterIdentity.contains(identity)) { adapterIdentity.add(identity); diff --git a/src/main/java/com/ycwl/basic/task/ScenicStatsTask.java b/src/main/java/com/ycwl/basic/task/ScenicStatsTask.java index 755b516..4b3764c 100644 --- a/src/main/java/com/ycwl/basic/task/ScenicStatsTask.java +++ b/src/main/java/com/ycwl/basic/task/ScenicStatsTask.java @@ -1,14 +1,14 @@ package com.ycwl.basic.task; import cn.hutool.core.date.DateUtil; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.mapper.ScenicDeviceStatsMapper; -import com.ycwl.basic.mapper.ScenicMapper; import com.ycwl.basic.mapper.StatisticsMapper; import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq; import com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; -import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; import com.ycwl.basic.model.pc.scenicDeviceStats.entity.ScenicDeviceStatsEntity; +import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.mobile.AppStatisticsService; import com.ycwl.basic.utils.ApiResponse; import org.springframework.beans.factory.annotation.Autowired; @@ -31,7 +31,8 @@ public class ScenicStatsTask { @Autowired private AppStatisticsService statisticsService; @Autowired - private ScenicMapper scenicMapper; + private ScenicRepository scenicRepository; + @Scheduled(cron = "0 1 0 * * *") public void countDeviceStats() { Date yesterdayStart = DateUtil.beginOfDay(DateUtil.yesterday()); @@ -58,16 +59,19 @@ public class ScenicStatsTask { public void countScenicStats() { Date yesterdayStart = DateUtil.beginOfDay(DateUtil.yesterday()); Date yesterdayEnd = DateUtil.endOfDay(yesterdayStart); - List list = scenicMapper.list(new ScenicReqQuery()); - list.forEach((scenic) -> { - CommonQueryReq query = new CommonQueryReq(); - query.setScenicId(scenic.getId()); - query.setStartTime(yesterdayStart); - query.setEndTime(yesterdayEnd); - query.setRealtime(true); - ApiResponse resp = statisticsService.userConversionFunnel(query); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); + scenicList.forEach((scenic) -> { + CommonQueryReq commonQueryReq = new CommonQueryReq(); + Long scenicId = Long.valueOf(scenic.getId()); + commonQueryReq.setScenicId(scenicId); + commonQueryReq.setStartTime(yesterdayStart); + commonQueryReq.setEndTime(yesterdayEnd); + commonQueryReq.setRealtime(true); + ApiResponse resp = statisticsService.userConversionFunnel(commonQueryReq); AppStatisticsFunnelVO data = resp.getData(); - statisticsMapper.insertStat(scenic.getId(), yesterdayStart, data); + statisticsMapper.insertStat(scenicId, yesterdayStart, data); }); } } diff --git a/src/main/java/com/ycwl/basic/task/VideoTaskGenerator.java b/src/main/java/com/ycwl/basic/task/VideoTaskGenerator.java index 2ed6b09..8cf630d 100644 --- a/src/main/java/com/ycwl/basic/task/VideoTaskGenerator.java +++ b/src/main/java/com/ycwl/basic/task/VideoTaskGenerator.java @@ -2,21 +2,16 @@ package com.ycwl.basic.task; import cn.hutool.core.date.DateUtil; import com.ycwl.basic.biz.TemplateBiz; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.mapper.FaceMapper; -import com.ycwl.basic.mapper.FaceSampleMapper; -import com.ycwl.basic.mapper.ScenicMapper; import com.ycwl.basic.mapper.TemplateMapper; import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO; import com.ycwl.basic.model.pc.face.req.FaceReqQuery; import com.ycwl.basic.model.pc.face.resp.FaceRespVO; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; -import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; -import com.ycwl.basic.model.task.resp.SearchFaceRespVo; import com.ycwl.basic.repository.ScenicRepository; -import com.ycwl.basic.repository.TemplateRepository; import com.ycwl.basic.service.pc.FaceService; -import com.ycwl.basic.service.task.TaskFaceService; import com.ycwl.basic.service.task.impl.TaskTaskServiceImpl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -37,23 +32,21 @@ public class VideoTaskGenerator { @Autowired private FaceMapper faceMapper; @Autowired - private TaskFaceService taskFaceService; - @Autowired private TemplateBiz templateBiz; @Autowired private TaskTaskServiceImpl taskTaskService; @Autowired private TemplateMapper templateMapper; @Autowired - private ScenicMapper scenicMapper; - @Autowired private ScenicRepository scenicRepository; @Autowired private FaceService faceService; @Scheduled(cron = "0 0 * * * *") public void generateVideoTask() { - List scenicList = scenicMapper.list(new ScenicReqQuery()); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); if (scenicList.isEmpty()) { return; } @@ -63,7 +56,7 @@ public class VideoTaskGenerator { int currentHour = calendar.get(Calendar.HOUR_OF_DAY); calendar.clear(); scenicList.parallelStream().forEach(scenic -> { - Long scenicId = scenic.getId(); + Long scenicId = Long.valueOf(scenic.getId()); ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); if (scenicConfig == null) { log.info("当前景区{},无配置信息", scenic.getName()); @@ -79,11 +72,11 @@ public class VideoTaskGenerator { if (contentList.isEmpty()) { return; } - FaceReqQuery query = new FaceReqQuery(); - query.setScenicId(scenicId); - query.setStartTime(DateUtil.beginOfDay(new Date())); - query.setEndTime(DateUtil.endOfDay(new Date())); - List list = faceMapper.list(query); + FaceReqQuery faceReqQuery = new FaceReqQuery(); + faceReqQuery.setScenicId(scenicId); + faceReqQuery.setStartTime(DateUtil.beginOfDay(new Date())); + faceReqQuery.setEndTime(DateUtil.endOfDay(new Date())); + List list = faceMapper.list(faceReqQuery); list.forEach(face -> { faceService.matchFaceId(face.getId(), false); if (Integer.valueOf(3).equals(scenicConfig.getBookRoutine())) { diff --git a/src/main/resources/mapper/ScenicMapper.xml b/src/main/resources/mapper/ScenicMapper.xml deleted file mode 100644 index 8c94752..0000000 --- a/src/main/resources/mapper/ScenicMapper.xml +++ /dev/null @@ -1,267 +0,0 @@ - - - - - insert into scenic(id, `name`, introduction, phone, cover_url, longitude, latitude, radius, province, city, area, address, price, kf_code_url, kf_phone, logo_url, source_video_price, source_image_price) - values (#{id}, #{name}, #{introduction}, #{phone}, #{coverUrl},#{longitude}, #{latitude}, #{radius}, #{province}, #{city}, #{area}, #{address}, #{price}, #{kfCodeUrl}, #{kfPhone}, #{logoUrl}, #{sourceVideoPrice}, #{sourceImagePrice}) - - - insert into scenic_config(id, scenic_id, create_time) - values (#{id}, #{scenicId}, now()) - - - update - scenic - - - `name`=#{name}, - - - `phone`=#{phone}, - - - introduction=#{introduction}, - - - cover_url=#{coverUrl}, - - - longitude=#{longitude}, - - - latitude=#{latitude}, - - - radius=#{radius}, - - - status=#{status}, - - - province=#{province}, - - - city=#{city}, - - - area=#{area}, - - - address=#{address}, - - - kf_code_url=#{kfCodeUrl}, - - - kf_phone=#{kfPhone}, - - - logo_url=#{logoUrl}, - - - price=#{price}, - - - source_video_price=#{sourceVideoPrice}, - - - source_image_price=#{sourceImagePrice}, - - - where id = #{id} - - - update - scenic - set status = (CASE - status - WHEN 1 THEN - 0 - WHEN 0 THEN - 1 - END) - where id = #{id} - - - update scenic_config - - - start_time=#{startTime}, - - - end_time=#{endTime}, - - - is_default=#{isDefault}, - - all_free=#{allFree}, - book_routine=#{bookRoutine}, - tour_time=#{tourTime}, - sample_store_day=#{sampleStoreDay}, - face_store_day=#{faceStoreDay}, - video_store_day=#{videoStoreDay}, - template_new_video_type=#{templateNewVideoType}, - anti_screen_record_type=#{antiScreenRecordType}, - disable_source_video=#{disableSourceVideo}, - disable_source_image=#{disableSourceImage}, - video_source_store_day=#{videoSourceStoreDay}, - image_source_store_day=#{imageSourceStoreDay}, - user_source_expire_day=#{userSourceExpireDay}, - face_score_threshold=#{faceScoreThreshold}, - force_finish_time=#{forceFinishTime}, - face_detect_helper_threshold=#{faceDetectHelperThreshold}, - store_type=#{storeType}, - store_config_json=#{storeConfigJson}, - tmp_store_type=#{tmpStoreType}, - tmp_store_config_json=#{tmpStoreConfigJson}, - local_store_type=#{localStoreType}, - local_store_config_json=#{localStoreConfigJson}, - broker_direct_rate=#{brokerDirectRate}, - watermark_type=#{watermarkType}, - watermark_scenic_text=#{watermarkScenicText}, - watermark_dt_format=#{watermarkDtFormat}, - face_type=#{faceType}, - face_config_json=#{faceConfigJson}, - pay_type=#{payType}, - pay_config_json=#{payConfigJson}, - image_source_pack_hint=#{imageSourcePackHint}, - video_source_pack_hint=#{videoSourcePackHint}, - extra_notification_time=#{extraNotificationTime}, - photo_free_num= #{photoFreeNum}, - video_free_num= #{videoFreeNum}, - voucher_enable= #{voucherEnable} - - where id = #{id} - - - delete from scenic where id = #{id} - - - delete from scenic_config where scenic_id = #{scenicId} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/java/com/ycwl/basic/task/FaceCleanerTest.java b/src/test/java/com/ycwl/basic/task/FaceCleanerTest.java index d2a6406..d144ef0 100644 --- a/src/test/java/com/ycwl/basic/task/FaceCleanerTest.java +++ b/src/test/java/com/ycwl/basic/task/FaceCleanerTest.java @@ -5,9 +5,9 @@ import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.profile.DefaultProfile; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.mapper.FaceMapper; import com.ycwl.basic.mapper.FaceSampleMapper; -import com.ycwl.basic.mapper.ScenicMapper; import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity; import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; @@ -41,8 +41,6 @@ public class FaceCleanerTest { @Autowired private FaceSampleMapper faceSampleMapper; @Autowired - private ScenicMapper scenicMapper; - @Autowired private FaceCleaner faceCleaner; @Autowired private ScenicService scenicService; @@ -51,7 +49,7 @@ public class FaceCleanerTest { @Test public void testA() { - ScenicEntity scenic = scenicMapper.get(3980001650692722688L); + ScenicEntity scenic = scenicRepository.getScenic(3980001650692722688L); log.info("当前景区{},开始删除人脸样本", scenic.getId()); IFaceBodyAdapter adapter = scenicService.getScenicFaceBodyAdapter(scenic.getId()); int sampleStoreDay = 1; @@ -79,8 +77,9 @@ public class FaceCleanerTest { @Test public void test() { - ScenicReqQuery scenicQuery = new ScenicReqQuery(); - List scenicList = scenicMapper.list(scenicQuery); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); scenicList.forEach(scenic -> { log.info("当前景区{},开始删除人脸样本", scenic.getName()); }); diff --git a/src/test/java/com/ycwl/basic/task/ScenicStatsTaskTest.java b/src/test/java/com/ycwl/basic/task/ScenicStatsTaskTest.java index 3593d7b..a0c8e1a 100644 --- a/src/test/java/com/ycwl/basic/task/ScenicStatsTaskTest.java +++ b/src/test/java/com/ycwl/basic/task/ScenicStatsTaskTest.java @@ -1,12 +1,13 @@ package com.ycwl.basic.task; import cn.hutool.core.date.DateUtil; -import com.ycwl.basic.mapper.ScenicMapper; +import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.mapper.StatisticsMapper; import com.ycwl.basic.model.mobile.statistic.req.CommonQueryReq; import com.ycwl.basic.model.mobile.statistic.resp.AppStatisticsFunnelVO; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.model.pc.scenic.resp.ScenicRespVO; +import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.mobile.AppStatisticsService; import com.ycwl.basic.utils.ApiResponse; import lombok.extern.slf4j.Slf4j; @@ -27,11 +28,11 @@ public class ScenicStatsTaskTest { @Autowired private ScenicStatsTask task; @Autowired - private ScenicMapper scenicMapper; - @Autowired private AppStatisticsService statisticsService; @Autowired private StatisticsMapper statisticsMapper; + @Autowired + private ScenicRepository scenicRepository; @Test public void testA() { @@ -42,17 +43,20 @@ public class ScenicStatsTaskTest { public void testB() { Calendar calendar = Calendar.getInstance(); calendar.set(2025, Calendar.MAY, 1); - List list = scenicMapper.list(new ScenicReqQuery()); + ScenicReqQuery query = new ScenicReqQuery(); + query.setPageSize(1000); + List scenicList = scenicRepository.list(query); while (calendar.getTime().getTime() < System.currentTimeMillis()) { System.out.println(calendar.getTime()); - list.forEach((scenic) -> { - CommonQueryReq query = new CommonQueryReq(); - query.setScenicId(scenic.getId()); - query.setStartTime(DateUtil.beginOfDay(calendar.getTime())); - query.setEndTime(DateUtil.endOfDay(calendar.getTime())); - ApiResponse resp = statisticsService.userConversionFunnel(query); + scenicList.forEach((scenic) -> { + Long scenicId = Long.valueOf(scenic.getId()); + CommonQueryReq commonQueryReq = new CommonQueryReq(); + commonQueryReq.setScenicId(scenicId); + commonQueryReq.setStartTime(DateUtil.beginOfDay(calendar.getTime())); + commonQueryReq.setEndTime(DateUtil.endOfDay(calendar.getTime())); + ApiResponse resp = statisticsService.userConversionFunnel(commonQueryReq); AppStatisticsFunnelVO data = resp.getData(); - statisticsMapper.insertStat(scenic.getId(), DateUtil.beginOfDay(calendar.getTime()), data); + statisticsMapper.insertStat(scenicId, DateUtil.beginOfDay(calendar.getTime()), data); }); calendar.add(Calendar.DAY_OF_MONTH, 1); } From 98bbaccb3ae53e82d2e33efaad806b2e2cc8bf80 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 16:40:32 +0800 Subject: [PATCH 13/32] =?UTF-8?q?refactor(biz):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E4=B8=AD=E7=9A=84=E6=9D=A1=E4=BB=B6=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 Integer 类型的比较改为 Boolean 类型的比较,提高代码可读性和性能 - 修改涉及 scenicConfig 的条件判断,使用 Boolean.TRUE进行比较 - 优化部分代码结构,保持逻辑一致性 --- src/main/java/com/ycwl/basic/biz/PriceBiz.java | 4 ++-- .../ycwl/basic/service/mobile/impl/GoodsServiceImpl.java | 4 ++-- .../com/ycwl/basic/service/pc/impl/FaceServiceImpl.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/ycwl/basic/biz/PriceBiz.java b/src/main/java/com/ycwl/basic/biz/PriceBiz.java index cc60290..0acaf83 100644 --- a/src/main/java/com/ycwl/basic/biz/PriceBiz.java +++ b/src/main/java/com/ycwl/basic/biz/PriceBiz.java @@ -50,10 +50,10 @@ public class PriceBiz { }).forEach(goodsList::add); ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); if (scenicConfig != null) { - if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo())) { + if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo())) { goodsList.add(new GoodsListRespVO(1L, "录像集")); } - if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceImage())) { + if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceImage())) { goodsList.add(new GoodsListRespVO(2L, "照片集")); } } diff --git a/src/main/java/com/ycwl/basic/service/mobile/impl/GoodsServiceImpl.java b/src/main/java/com/ycwl/basic/service/mobile/impl/GoodsServiceImpl.java index 0ae63a9..0a2c89d 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/impl/GoodsServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/mobile/impl/GoodsServiceImpl.java @@ -139,9 +139,9 @@ public class GoodsServiceImpl implements GoodsService { List goods = faceEntry.getValue(); return goods.stream().collect(Collectors.groupingBy(SourceRespVO::getType)).keySet().stream().filter(type -> { if (Integer.valueOf(1).equals(type)) { - return !Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo()); + return !Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo()); } else if (Integer.valueOf(2).equals(type)) { - return !Integer.valueOf(1).equals(scenicConfig.getDisableSourceImage()); + return !Boolean.TRUE.equals(scenicConfig.getDisableSourceImage()); } return true; }).map(type -> { diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java index f262405..b8f5664 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java @@ -324,7 +324,7 @@ public class FaceServiceImpl implements FaceService { } } else { // 重新切视频逻辑 - if (scenicConfig != null && !Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo())) { + if (scenicConfig != null && !Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo())) { long videoCount = memberSourceEntityList.stream().filter(item -> item.getType().equals(1)).count(); long photoCount = memberSourceEntityList.stream().filter(item -> item.getType().equals(2)).count(); if (photoCount > videoCount) { @@ -443,7 +443,7 @@ public class FaceServiceImpl implements FaceService { sourceVideoContent.setLockType(-1); sourceImageContent.setLockType(-1); ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(faceRespVO.getScenicId()); - if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceImage())) { + if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceImage())) { IsBuyRespVO isBuyRespVO = orderBiz.isBuy(userId, faceRespVO.getScenicId(), 2, faceId); sourceImageContent.setSourceType(isBuyRespVO.getGoodsType()); sourceImageContent.setContentId(isBuyRespVO.getGoodsId()); @@ -462,7 +462,7 @@ public class FaceServiceImpl implements FaceService { sourceImageContent.setFreeCount((int) freeCount); contentList.add(sourceImageContent); } - if (!Integer.valueOf(1).equals(scenicConfig.getDisableSourceVideo())) { + if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo())) { IsBuyRespVO isBuyRespVO = orderBiz.isBuy(userId, faceRespVO.getScenicId(), 1, faceId); sourceVideoContent.setSourceType(isBuyRespVO.getGoodsType()); sourceVideoContent.setContentId(isBuyRespVO.getGoodsId()); From ff320ba3e8c22e076367a4d83c3e5097a5ee53f5 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 27 Aug 2025 17:10:42 +0800 Subject: [PATCH 14/32] =?UTF-8?q?feat(AppScenicServiceImpl):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=99=AF=E5=8C=BA=E8=AE=BE=E5=A4=87=E6=95=B0=E9=87=8F?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 AppScenicServiceImpl 类中,为 scenicAppVO 对象添加 deviceNum 字段 - 通过 deviceRepository.getAllDeviceByScenicId 方法获取景区设备数量并设置到 scenicAppVO 中 --- .../com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java index 9964de7..8134efb 100644 --- a/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/mobile/impl/AppScenicServiceImpl.java @@ -185,6 +185,7 @@ public class AppScenicServiceImpl implements AppScenicService { scenicAppVO.setArea(scenicEntity.getArea()); scenicAppVO.setAddress(scenicEntity.getAddress()); scenicAppVO.setDistance(distance); + scenicAppVO.setDeviceNum(deviceRepository.getAllDeviceByScenicId(scenicEntity.getId()).size()); list.add(scenicAppVO); } From 95d8b742ee471cef10f12752bb8ecaa0d04baa86 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 09:52:43 +0800 Subject: [PATCH 15/32] =?UTF-8?q?feat(scenic):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=E5=99=A8?= =?UTF-8?q?=E5=B9=B6=E9=9B=86=E6=88=90=E7=BC=93=E5=AD=98=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 ScenicConfigManager 类,用于管理和获取景区配置 - 在 ScenicRepository 中添加获取景区配置管理器的方法 - 实现了带缓存支持的景区配置获取,提高性能 --- .../basic/repository/ScenicRepository.java | 67 ++++ .../ycwl/basic/util/ScenicConfigManager.java | 376 ++++++++++++++++++ 2 files changed, 443 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/util/ScenicConfigManager.java diff --git a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java index 0e8325b..b3db8bd 100644 --- a/src/main/java/com/ycwl/basic/repository/ScenicRepository.java +++ b/src/main/java/com/ycwl/basic/repository/ScenicRepository.java @@ -6,6 +6,8 @@ import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2ListResponse; import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2WithConfigDTO; import com.ycwl.basic.integration.scenic.service.ScenicIntegrationService; +import com.ycwl.basic.integration.scenic.service.ScenicConfigIntegrationService; +import com.ycwl.basic.integration.scenic.dto.config.ScenicConfigV2DTO; import com.ycwl.basic.mapper.MpConfigMapper; import com.ycwl.basic.mapper.MpNotifyConfigMapper; import com.ycwl.basic.model.pc.mp.MpConfigEntity; @@ -17,11 +19,13 @@ import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.pay.enums.PayAdapterType; import com.ycwl.basic.storage.enums.StorageType; import com.ycwl.basic.utils.JacksonUtil; +import com.ycwl.basic.util.ScenicConfigManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.List; +import java.util.Map; @Component public class ScenicRepository { @@ -31,6 +35,8 @@ public class ScenicRepository { private RedisTemplate redisTemplate; @Autowired private ScenicIntegrationService scenicIntegrationService; + @Autowired + private ScenicConfigIntegrationService scenicConfigIntegrationService; public static final String SCENIC_CACHE_KEY = "scenic:%s"; public static final String SCENIC_BASIC_CACHE_KEY = "scenic:basic:%s"; @@ -307,5 +313,66 @@ public class ScenicRepository { return entity; } + + /** + * 获取景区配置管理器 + * + * @param scenicId 景区ID + * @return ScenicConfigManager实例,如果获取失败返回null + */ + public ScenicConfigManager getScenicConfigManager(Long scenicId) { + try { + List configList = scenicConfigIntegrationService.listConfigs(scenicId); + if (configList != null) { + return new ScenicConfigManager(configList); + } + return null; + } catch (Exception e) { + return null; + } + } + + /** + * 获取景区配置管理器,带缓存支持 + * + * @param scenicId 景区ID + * @return ScenicConfigManager实例,如果获取失败返回null + */ + public ScenicConfigManager getScenicConfigManagerWithCache(Long scenicId) { + String key = String.format(SCENIC_CONFIG_CACHE_KEY + ":manager", scenicId); + + try { + List configList = + scenicConfigIntegrationService.listConfigs(scenicId); + if (configList != null) { + ScenicConfigManager manager = new ScenicConfigManager(configList); + + // 请求成功,写入缓存(将配置列表序列化存储) + redisTemplate.opsForValue().set( + key, + JacksonUtil.toJSONString(configList) + ); + + return manager; + } + return null; + } catch (Exception e) { + // 请求失败,尝试从缓存获取历史成功数据 + if (redisTemplate.hasKey(key)) { + try { + String cachedConfigJson = redisTemplate.opsForValue().get(key); + @SuppressWarnings("unchecked") + List cachedConfigList = + JacksonUtil.parseArray(cachedConfigJson, ScenicConfigV2DTO.class); + return new ScenicConfigManager(cachedConfigList); + } catch (Exception cacheException) { + // 缓存解析失败,返回null + return null; + } + } + // 缓存也没有,返回null + return null; + } + } } diff --git a/src/main/java/com/ycwl/basic/util/ScenicConfigManager.java b/src/main/java/com/ycwl/basic/util/ScenicConfigManager.java new file mode 100644 index 0000000..3da9397 --- /dev/null +++ b/src/main/java/com/ycwl/basic/util/ScenicConfigManager.java @@ -0,0 +1,376 @@ +package com.ycwl.basic.util; + +import com.ycwl.basic.integration.common.util.ConfigValueUtil; +import com.ycwl.basic.integration.scenic.dto.config.ScenicConfigV2DTO; + +import java.math.BigDecimal; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 景区配置管理器 + * + * 提供类型安全的配置值获取功能,支持多种数据类型的自动转换, + * 当类型不兼容时返回null而不是抛出异常。 + */ +public class ScenicConfigManager { + + private final Map configMap; + + /** + * 从配置列表构造管理器 + * + * @param configList 配置项列表 + */ + public ScenicConfigManager(List configList) { + this.configMap = new HashMap<>(); + if (configList != null) { + for (ScenicConfigV2DTO config : configList) { + if (config.getConfigKey() != null && config.getConfigValue() != null) { + this.configMap.put(config.getConfigKey(), config.getConfigValue()); + } + } + } + } + + /** + * 从配置Map构造管理器 + * + * @param configMap 配置Map + */ + public ScenicConfigManager(Map configMap) { + this.configMap = configMap != null ? new HashMap<>(configMap) : new HashMap<>(); + } + + /** + * 获取字符串值 + * + * @param key 配置键 + * @return 字符串值,如果键不存在或转换失败返回null + */ + public String getString(String key) { + return ConfigValueUtil.getStringValue(configMap, key); + } + + /** + * 获取字符串值,如果为null则返回默认值 + * + * @param key 配置键 + * @param defaultValue 默认值 + * @return 字符串值或默认值 + */ + public String getString(String key, String defaultValue) { + return ConfigValueUtil.getStringValue(configMap, key, defaultValue); + } + + /** + * 获取整数值 + * + * @param key 配置键 + * @return Integer值,如果键不存在或转换失败返回null + */ + public Integer getInteger(String key) { + return ConfigValueUtil.getIntValue(configMap, key); + } + + /** + * 获取整数值,如果为null则返回默认值 + * + * @param key 配置键 + * @param defaultValue 默认值 + * @return Integer值或默认值 + */ + public Integer getInteger(String key, Integer defaultValue) { + return ConfigValueUtil.getIntValue(configMap, key, defaultValue); + } + + /** + * 获取长整数值 + * + * @param key 配置键 + * @return Long值,如果键不存在或转换失败返回null + */ + public Long getLong(String key) { + return ConfigValueUtil.getLongValue(configMap, key); + } + + /** + * 获取长整数值,如果为null则返回默认值 + * + * @param key 配置键 + * @param defaultValue 默认值 + * @return Long值或默认值 + */ + public Long getLong(String key, Long defaultValue) { + Long value = ConfigValueUtil.getLongValue(configMap, key); + return value != null ? value : defaultValue; + } + + /** + * 获取浮点数值 + * + * @param key 配置键 + * @return Float值,如果键不存在或转换失败返回null + */ + public Float getFloat(String key) { + return ConfigValueUtil.getFloatValue(configMap, key); + } + + /** + * 获取浮点数值,如果为null则返回默认值 + * + * @param key 配置键 + * @param defaultValue 默认值 + * @return Float值或默认值 + */ + public Float getFloat(String key, Float defaultValue) { + Float value = ConfigValueUtil.getFloatValue(configMap, key); + return value != null ? value : defaultValue; + } + + /** + * 获取双精度浮点数值 + * + * @param key 配置键 + * @return Double值,如果键不存在或转换失败返回null + */ + public Double getDouble(String key) { + return ConfigValueUtil.getDoubleValue(configMap, key); + } + + /** + * 获取双精度浮点数值,如果为null则返回默认值 + * + * @param key 配置键 + * @param defaultValue 默认值 + * @return Double值或默认值 + */ + public Double getDouble(String key, Double defaultValue) { + Double value = ConfigValueUtil.getDoubleValue(configMap, key); + return value != null ? value : defaultValue; + } + + /** + * 获取高精度小数值 + * + * @param key 配置键 + * @return BigDecimal值,如果键不存在或转换失败返回null + */ + public BigDecimal getBigDecimal(String key) { + return ConfigValueUtil.getBigDecimalValue(configMap, key); + } + + /** + * 获取高精度小数值,如果为null则返回默认值 + * + * @param key 配置键 + * @param defaultValue 默认值 + * @return BigDecimal值或默认值 + */ + public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) { + BigDecimal value = ConfigValueUtil.getBigDecimalValue(configMap, key); + return value != null ? value : defaultValue; + } + + /** + * 获取布尔值 + * + * @param key 配置键 + * @return Boolean值,如果键不存在或转换失败返回null + */ + public Boolean getBoolean(String key) { + return ConfigValueUtil.getBooleanValue(configMap, key); + } + + /** + * 获取布尔值,如果为null则返回默认值 + * + * @param key 配置键 + * @param defaultValue 默认值 + * @return Boolean值或默认值 + */ + public Boolean getBoolean(String key, Boolean defaultValue) { + return ConfigValueUtil.getBooleanValue(configMap, key, defaultValue); + } + + /** + * 获取枚举值 + * + * @param key 配置键 + * @param enumClass 枚举类型 + * @param 枚举类型泛型 + * @return 枚举值,如果键不存在或转换失败返回null + */ + public > T getEnum(String key, Class enumClass) { + return ConfigValueUtil.getEnumValue(configMap, key, enumClass); + } + + /** + * 获取枚举值,如果为null则返回默认值 + * + * @param key 配置键 + * @param enumClass 枚举类型 + * @param defaultValue 默认值 + * @param 枚举类型泛型 + * @return 枚举值或默认值 + */ + public > T getEnum(String key, Class enumClass, T defaultValue) { + T value = ConfigValueUtil.getEnumValue(configMap, key, enumClass); + return value != null ? value : defaultValue; + } + + /** + * 获取原始对象值 + * + * @param key 配置键 + * @return 原始Object值 + */ + public Object getObject(String key) { + return ConfigValueUtil.getObjectValue(configMap, key); + } + + /** + * 获取并转换为指定类型的对象 + * + * @param key 配置键 + * @param clazz 目标类型 + * @param 目标类型泛型 + * @return 转换后的对象,如果转换失败返回null + */ + public T getObject(String key, Class clazz) { + return ConfigValueUtil.getObjectValue(configMap, key, clazz); + } + + /** + * 获取Map类型的值 + * + * @param key 配置键 + * @return Map值,如果转换失败返回null + */ + public Map getMap(String key) { + return ConfigValueUtil.getMapValue(configMap, key); + } + + /** + * 获取List类型的值 + * + * @param key 配置键 + * @return List值,如果转换失败返回null + */ + public List getList(String key) { + return ConfigValueUtil.getListValue(configMap, key); + } + + /** + * 获取指定元素类型的List值 + * + * @param key 配置键 + * @param elementClass List元素类型 + * @param List元素类型泛型 + * @return 指定类型的List,如果转换失败返回null + */ + public List getList(String key, Class elementClass) { + return ConfigValueUtil.getListValue(configMap, key, elementClass); + } + + /** + * 检查配置键是否存在 + * + * @param key 配置键 + * @return true如果键存在,false如果不存在 + */ + public boolean hasKey(String key) { + return ConfigValueUtil.hasKey(configMap, key); + } + + /** + * 检查配置键是否存在且值不为null + * + * @param key 配置键 + * @return true如果键存在且值不为null + */ + public boolean hasNonNullValue(String key) { + return ConfigValueUtil.hasNonNullValue(configMap, key); + } + + /** + * 获取所有配置键 + * + * @return 配置键集合 + */ + public Set getAllKeys() { + return new HashSet<>(configMap.keySet()); + } + + /** + * 获取配置项数量 + * + * @return 配置项数量 + */ + public int size() { + return configMap.size(); + } + + /** + * 检查配置是否为空 + * + * @return true如果没有配置项 + */ + public boolean isEmpty() { + return configMap.isEmpty(); + } + + /** + * 获取所有配置的拷贝 + * + * @return 配置Map的拷贝 + */ + public Map getAllConfigs() { + return new HashMap<>(configMap); + } + + /** + * 根据键前缀过滤配置 + * + * @param prefix 键前缀 + * @return 匹配前缀的配置Map + */ + public Map getConfigsByPrefix(String prefix) { + if (prefix == null) { + return new HashMap<>(); + } + + return configMap.entrySet().stream() + .filter(entry -> entry.getKey() != null && entry.getKey().startsWith(prefix)) + .collect(Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue + )); + } + + /** + * 创建新的ScenicConfigManager,包含当前配置的子集 + * + * @param keys 要包含的配置键 + * @return 包含指定键配置的新管理器 + */ + public ScenicConfigManager subset(Set keys) { + Map subsetMap = new HashMap<>(); + if (keys != null) { + for (String key : keys) { + if (configMap.containsKey(key)) { + subsetMap.put(key, configMap.get(key)); + } + } + } + return new ScenicConfigManager(subsetMap); + } + + @Override + public String toString() { + return "ScenicConfigManager{" + + "configCount=" + configMap.size() + + ", keys=" + configMap.keySet() + + '}'; + } +} \ No newline at end of file From 5bb2bc1ac3c88d180c5f452eccc942279887ce45 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 09:55:40 +0800 Subject: [PATCH 16/32] =?UTF-8?q?refactor(PageResponse):=20=E5=B0=86=20tot?= =?UTF-8?q?al=20=E5=AD=97=E6=AE=B5=E7=B1=BB=E5=9E=8B=E4=BB=8E=20Long=20?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=20Integer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改了 PageResponse 类中 total 字段的类型,以更好地与前端交互 - 这个改动解决了后端与前端之间关于 total 类型不一致的问题 --- .../ycwl/basic/integration/common/response/PageResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ycwl/basic/integration/common/response/PageResponse.java b/src/main/java/com/ycwl/basic/integration/common/response/PageResponse.java index 605932d..fa4a664 100644 --- a/src/main/java/com/ycwl/basic/integration/common/response/PageResponse.java +++ b/src/main/java/com/ycwl/basic/integration/common/response/PageResponse.java @@ -11,7 +11,7 @@ import java.util.List; @AllArgsConstructor public class PageResponse { private List list; - private Long total; + private Integer total; private Integer page; private Integer pageSize; } \ No newline at end of file From c7d539993101fb22279d1b8ff4ba4dd0c6a2d164 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 09:57:39 +0800 Subject: [PATCH 17/32] =?UTF-8?q?refactor(scenic):=20=E5=B0=86=20scenic=20?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=88=97=E8=A1=A8=E6=8E=A5=E5=8F=A3=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=20total=20=E5=AD=97=E6=AE=B5=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E4=BB=8E=20Long=20=E6=94=B9=E4=B8=BA=20Integer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改了 ScenicV2ListResponse 和 ScenicV2WithConfigListResponse 类中的 total 字段类型 - 此更改统一了 total 字段的类型,提高了代码的一致性和可维护性 --- .../integration/scenic/dto/scenic/ScenicV2ListResponse.java | 2 +- .../scenic/dto/scenic/ScenicV2WithConfigListResponse.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2ListResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2ListResponse.java index f2f5790..c660579 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2ListResponse.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2ListResponse.java @@ -11,7 +11,7 @@ public class ScenicV2ListResponse { private List list; @JsonProperty("total") - private Long total; + private Integer total; @JsonProperty("page") private Integer page; diff --git a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigListResponse.java b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigListResponse.java index cd5cef1..4540036 100644 --- a/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigListResponse.java +++ b/src/main/java/com/ycwl/basic/integration/scenic/dto/scenic/ScenicV2WithConfigListResponse.java @@ -11,7 +11,7 @@ public class ScenicV2WithConfigListResponse { private List list; @JsonProperty("total") - private Long total; + private Integer total; @JsonProperty("page") private Integer page; From f451b835b9d292a2c102c1d36c49a6339e60946e Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 12:09:47 +0800 Subject: [PATCH 18/32] =?UTF-8?q?feat(pricing):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=BF=AB=E9=80=9F=E8=AE=BE=E7=BD=AE=E5=95=86=E5=93=81=E4=BB=B7?= =?UTF-8?q?=E6=A0=BC=E5=8A=9F=E8=83=BD=E5=B9=B6=E9=9B=86=E6=88=90=E5=88=B0?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E6=9C=8D=E5=8A=A1=20-=20=E5=9C=A8=20PricingM?= =?UTF-8?q?anagementServiceImpl=20=E4=B8=AD=E5=AE=9E=E7=8E=B0=20quickSetup?= =?UTF-8?q?ProductPrice=20=E6=96=B9=E6=B3=95=EF=BC=8C=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E5=BF=AB=E9=80=9F=E8=AE=BE=E7=BD=AE=E5=95=86=E5=93=81=E4=BB=B7?= =?UTF-8?q?=E6=A0=BC=20-=20=E5=9C=A8=20IPricingManagementService=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E4=B8=AD=E6=B7=BB=E5=8A=A0=20quickSetupProdu?= =?UTF-8?q?ctPrice=20=E6=96=B9=E6=B3=95=E7=9A=84=E5=A3=B0=E6=98=8E=20-=20?= =?UTF-8?q?=E5=9C=A8=20TemplateServiceImpl=20=E4=B8=AD=E8=B0=83=E7=94=A8?= =?UTF-8?q?=20quickSetupProductPrice=20=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E4=B8=BA=E6=A8=A1=E6=9D=BF=E8=AE=BE=E7=BD=AE=E4=BB=B7=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/IPricingManagementService.java | 19 ++++++++ .../impl/PricingManagementServiceImpl.java | 46 +++++++++++++++++++ .../service/pc/impl/TemplateServiceImpl.java | 6 +++ 3 files changed, 71 insertions(+) diff --git a/src/main/java/com/ycwl/basic/pricing/service/IPricingManagementService.java b/src/main/java/com/ycwl/basic/pricing/service/IPricingManagementService.java index a37de38..1e14571 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/IPricingManagementService.java +++ b/src/main/java/com/ycwl/basic/pricing/service/IPricingManagementService.java @@ -2,6 +2,8 @@ package com.ycwl.basic.pricing.service; import com.ycwl.basic.pricing.entity.*; +import java.math.BigDecimal; + /** * 价格管理服务接口(用于配置管理,手动处理时间字段) */ @@ -90,4 +92,21 @@ public interface IPricingManagementService { * 删除一口价配置 */ boolean deleteBundleConfig(Long id); + + // ==================== 快速设置价格 ==================== + + /** + * 快速设置商品基础价格(内部方法,如果存在则更新,不存在则新增) + * @param productType 商品类型 + * @param productId 商品ID + * @param scenicId 景区ID + * @param productName 商品名称 + * @param basePrice 基础价格 + * @param originalPrice 原价(可选,可为null) + * @param unit 价格单位(可选,可为null,默认为"元") + * @return 配置的ID + */ + Long quickSetupProductPrice(String productType, String productId, String scenicId, + String productName, BigDecimal basePrice, + BigDecimal originalPrice, String unit); } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/PricingManagementServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/PricingManagementServiceImpl.java index ec402f4..867e143 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/PricingManagementServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/PricingManagementServiceImpl.java @@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.math.BigDecimal; import java.util.Date; /** @@ -165,4 +166,49 @@ public class PricingManagementServiceImpl implements IPricingManagementService { log.info("删除一口价配置: id={}", id); return bundleConfigMapper.deleteById(id) > 0; } + + // ==================== 快速设置价格 ==================== + + @Override + @Transactional + public Long quickSetupProductPrice(String productType, String productId, String scenicId, + String productName, BigDecimal basePrice, + BigDecimal originalPrice, String unit) { + log.info("快速设置商品价格: productType={}, productId={}, scenicId={}, basePrice={}", + productType, productId, scenicId, basePrice); + + // 查询是否已存在配置 + PriceProductConfig existingConfig = productConfigMapper.selectByProductTypeAndId(productType, productId); + + if (existingConfig != null) { + // 存在则更新 + existingConfig.setScenicId(scenicId); + existingConfig.setProductName(productName); + existingConfig.setBasePrice(basePrice); + existingConfig.setOriginalPrice(originalPrice); + existingConfig.setUnit(unit != null ? unit : "元"); + existingConfig.setUpdateTime(new Date()); + + productConfigMapper.updateProductConfig(existingConfig); + log.info("更新商品价格配置: id={}", existingConfig.getId()); + return existingConfig.getId(); + } else { + // 不存在则新增 + PriceProductConfig newConfig = new PriceProductConfig(); + newConfig.setProductType(productType); + newConfig.setProductId(productId); + newConfig.setScenicId(scenicId); + newConfig.setProductName(productName); + newConfig.setBasePrice(basePrice); + newConfig.setOriginalPrice(originalPrice); + newConfig.setUnit(unit != null ? unit : "元"); + newConfig.setIsActive(true); + newConfig.setCreateTime(new Date()); + newConfig.setUpdateTime(new Date()); + + productConfigMapper.insertProductConfig(newConfig); + log.info("创建新商品价格配置: id={}", newConfig.getId()); + return newConfig.getId(); + } + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/TemplateServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/TemplateServiceImpl.java index 602afbf..ce47950 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/TemplateServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/TemplateServiceImpl.java @@ -7,6 +7,8 @@ import com.ycwl.basic.model.pc.template.entity.TemplateConfigEntity; import com.ycwl.basic.model.pc.template.entity.TemplateEntity; import com.ycwl.basic.model.pc.template.req.TemplateReqQuery; import com.ycwl.basic.model.pc.template.resp.TemplateRespVO; +import com.ycwl.basic.pricing.enums.ProductType; +import com.ycwl.basic.pricing.service.IPricingManagementService; import com.ycwl.basic.service.pc.TemplateService; import com.ycwl.basic.repository.TemplateRepository; import com.ycwl.basic.utils.ApiResponse; @@ -30,6 +32,8 @@ public class TemplateServiceImpl implements TemplateService { private TemplateMapper templateMapper; @Autowired private TemplateRepository templateRepository; + @Autowired + private IPricingManagementService pricingManagementService; @Override public ApiResponse> pageQuery(TemplateReqQuery templateReqQuery) { @@ -75,6 +79,7 @@ public class TemplateServiceImpl implements TemplateService { templateMapper.add(item); }); } + pricingManagementService.quickSetupProductPrice(ProductType.VLOG_VIDEO.getCode(), template.getId().toString(), template.getScenicId().toString(), template.getName(), template.getPrice(), template.getSlashPrice(), "个"); if (i > 0) { return ApiResponse.success(true); }else { @@ -112,6 +117,7 @@ public class TemplateServiceImpl implements TemplateService { }); } templateRepository.clearTemplateCache(template.getId()); + pricingManagementService.quickSetupProductPrice(ProductType.VLOG_VIDEO.getCode(), template.getId().toString(), template.getScenicId().toString(), template.getName(), template.getPrice(), template.getSlashPrice(), "个"); if (i > 0) { return ApiResponse.success(true); }else { From 46fb255e66cd91c877ebc1d45f957f752e1a583c Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 15:57:36 +0800 Subject: [PATCH 19/32] =?UTF-8?q?refactor(pc):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=99=AF=E5=8C=BA=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入 ScenicConfigManager 类替代 ScenicConfigEntity - 优化景区存储、临时存储、本地存储、人脸身体识别和支付适配器的获取逻辑 - 使用 getString 和 getObject 方法替代直接解析 JSON 对象 --- .../service/pc/impl/ScenicServiceImpl.java | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java index 2cfdf82..be753f2 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java @@ -12,6 +12,7 @@ import com.ycwl.basic.service.pc.ScenicService; import com.ycwl.basic.storage.StorageFactory; import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.exceptions.StorageUnsupportedException; +import com.ycwl.basic.util.ScenicConfigManager; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.JacksonUtil; import lombok.extern.slf4j.Slf4j; @@ -43,11 +44,11 @@ public class ScenicServiceImpl implements ScenicService { public IStorageAdapter getScenicStorageAdapter(Long scenicId) { return scenicStorageAdapterMap.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); - if (scenicConfig != null && scenicConfig.getStoreType() != null) { + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); + if (scenicConfig.getString("store_type") != null) { try { - adapter = StorageFactory.get(scenicConfig.getStoreType()); - adapter.loadConfig(JacksonUtil.parseObject(scenicConfig.getStoreConfigJson(), Map.class)); + adapter = StorageFactory.get(scenicConfig.getString("store_type")); + adapter.loadConfig(scenicConfig.getObject("store_config_json", Map.class)); } catch (StorageUnsupportedException ignored) { return StorageFactory.use("video"); } @@ -62,11 +63,11 @@ public class ScenicServiceImpl implements ScenicService { public IStorageAdapter getScenicTmpStorageAdapter(Long scenicId) { return scenicTmpStorageAdapterMap.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); - if (scenicConfig != null && scenicConfig.getTmpStoreType() != null) { + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); + if (scenicConfig.getString("tmp_store_type") != null) { try { - adapter = StorageFactory.get(scenicConfig.getTmpStoreType()); - adapter.loadConfig(JacksonUtil.parseObject(scenicConfig.getTmpStoreConfigJson(), Map.class)); + adapter = StorageFactory.get(scenicConfig.getString("tmp_store_type")); + adapter.loadConfig(scenicConfig.getObject("tmp_store_config_json", Map.class)); } catch (StorageUnsupportedException ignored) { return getScenicStorageAdapter(scenicId); } @@ -81,11 +82,11 @@ public class ScenicServiceImpl implements ScenicService { public IStorageAdapter getScenicLocalStorageAdapter(Long scenicId) { return scenicLocalStorageAdapterMap.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); - if (scenicConfig != null && scenicConfig.getLocalStoreType() != null) { + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); + if (scenicConfig.getString("local_store_type") != null) { try { - adapter = StorageFactory.get(scenicConfig.getLocalStoreType()); - adapter.loadConfig(JacksonUtil.parseObject(scenicConfig.getLocalStoreConfigJson(), Map.class)); + adapter = StorageFactory.get(scenicConfig.getString("local_store_type")); + adapter.loadConfig(scenicConfig.getObject("local_store_config_json", Map.class)); } catch (StorageUnsupportedException ignored) { return getScenicStorageAdapter(scenicId); } @@ -101,10 +102,10 @@ public class ScenicServiceImpl implements ScenicService { public IFaceBodyAdapter getScenicFaceBodyAdapter(Long scenicId) { return scenicFaceBodyAdapterMap.computeIfAbsent(scenicId, (key) -> { IFaceBodyAdapter adapter; - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); - if (scenicConfig != null && scenicConfig.getFaceType() != null) { - adapter = FaceBodyFactory.getAdapter(scenicConfig.getFaceType()); - adapter.loadConfig(JacksonUtil.parseObject(scenicConfig.getFaceConfigJson(), Map.class)); + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); + if (scenicConfig.getString("face_type") != null) { + adapter = FaceBodyFactory.getAdapter(scenicConfig.getString("face_type")); + adapter.loadConfig(scenicConfig.getObject("face_config_json", Map.class)); } else { adapter = FaceBodyFactory.use(); } @@ -117,10 +118,10 @@ public class ScenicServiceImpl implements ScenicService { public IPayAdapter getScenicPayAdapter(Long scenicId) { return scenicPayAdapterMap.computeIfAbsent(scenicId, (key) -> { IPayAdapter adapter; - ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId); - if (scenicConfig != null && scenicConfig.getPayType() != null) { - adapter = PayFactory.getAdapter(scenicConfig.getPayType()); - adapter.loadConfig(JacksonUtil.parseObject(scenicConfig.getPayConfigJson(), Map.class)); + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); + if (scenicConfig.getString("pay_type") != null) { + adapter = PayFactory.getAdapter(scenicConfig.getString("pay_type")); + adapter.loadConfig(scenicConfig.getObject("pay_config_json", Map.class)); } else { adapter = PayFactory.use(); } From 798ff3b9b5dec207c7f9f00ba5797cdcbc46a9cb Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 16:02:30 +0800 Subject: [PATCH 20/32] =?UTF-8?q?feat(service):=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=B8=A6TTL=E7=9A=84=E7=BC=93=E5=AD=98Map=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=E9=9D=99=E6=80=81Map?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增TtlCacheMap类,用于实现带生存时间的缓存 - 在ScenicServiceImpl中使用TtlCacheMap替换原有的ConcurrentHashMap - 为不同类型的适配器创建了对应的缓存Map - 优化了缓存获取逻辑,增加了TTL支持 - 添加了缓存清理和统计功能 --- .../service/pc/impl/ScenicServiceImpl.java | 123 ++++++- .../java/com/ycwl/basic/util/TtlCacheMap.java | 340 ++++++++++++++++++ 2 files changed, 452 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/util/TtlCacheMap.java diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java index be753f2..8a7171d 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java @@ -13,6 +13,7 @@ import com.ycwl.basic.storage.StorageFactory; import com.ycwl.basic.storage.adapters.IStorageAdapter; import com.ycwl.basic.storage.exceptions.StorageUnsupportedException; import com.ycwl.basic.util.ScenicConfigManager; +import com.ycwl.basic.util.TtlCacheMap; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.JacksonUtil; import lombok.extern.slf4j.Slf4j; @@ -21,7 +22,7 @@ import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; /** * @Author:longbinbin @@ -32,6 +33,21 @@ import java.util.concurrent.ConcurrentHashMap; public class ScenicServiceImpl implements ScenicService { @Autowired private ScenicRepository scenicRepository; + + // TTL缓存配置,默认10分钟过期 + private static final long DEFAULT_CACHE_TTL_MINUTES = 10; + + // 使用TTL缓存替代静态Map + private static final TtlCacheMap scenicStorageAdapterCache = + new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES)); + private static final TtlCacheMap scenicTmpStorageAdapterCache = + new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES)); + private static final TtlCacheMap scenicLocalStorageAdapterCache = + new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES)); + private static final TtlCacheMap scenicFaceBodyAdapterCache = + new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES)); + private static final TtlCacheMap scenicPayAdapterCache = + new TtlCacheMap<>(TimeUnit.MINUTES.toMillis(DEFAULT_CACHE_TTL_MINUTES)); @Override @Deprecated @@ -39,10 +55,9 @@ public class ScenicServiceImpl implements ScenicService { return ApiResponse.success(scenicRepository.list(scenicReqQuery)); } - private static final Map scenicStorageAdapterMap = new ConcurrentHashMap<>(); @Override public IStorageAdapter getScenicStorageAdapter(Long scenicId) { - return scenicStorageAdapterMap.computeIfAbsent(scenicId, (key) -> { + return scenicStorageAdapterCache.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); if (scenicConfig.getString("store_type") != null) { @@ -58,10 +73,9 @@ public class ScenicServiceImpl implements ScenicService { return adapter; }); } - private static final Map scenicTmpStorageAdapterMap = new ConcurrentHashMap<>(); @Override public IStorageAdapter getScenicTmpStorageAdapter(Long scenicId) { - return scenicTmpStorageAdapterMap.computeIfAbsent(scenicId, (key) -> { + return scenicTmpStorageAdapterCache.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); if (scenicConfig.getString("tmp_store_type") != null) { @@ -77,10 +91,9 @@ public class ScenicServiceImpl implements ScenicService { return adapter; }); } - private static final Map scenicLocalStorageAdapterMap = new ConcurrentHashMap<>(); @Override public IStorageAdapter getScenicLocalStorageAdapter(Long scenicId) { - return scenicLocalStorageAdapterMap.computeIfAbsent(scenicId, (key) -> { + return scenicLocalStorageAdapterCache.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); if (scenicConfig.getString("local_store_type") != null) { @@ -97,10 +110,9 @@ public class ScenicServiceImpl implements ScenicService { }); } - private static final Map scenicFaceBodyAdapterMap = new ConcurrentHashMap<>(); @Override public IFaceBodyAdapter getScenicFaceBodyAdapter(Long scenicId) { - return scenicFaceBodyAdapterMap.computeIfAbsent(scenicId, (key) -> { + return scenicFaceBodyAdapterCache.computeIfAbsent(scenicId, (key) -> { IFaceBodyAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); if (scenicConfig.getString("face_type") != null) { @@ -113,10 +125,9 @@ public class ScenicServiceImpl implements ScenicService { }); } - private static final Map scenicPayAdapterMap = new ConcurrentHashMap<>(); @Override public IPayAdapter getScenicPayAdapter(Long scenicId) { - return scenicPayAdapterMap.computeIfAbsent(scenicId, (key) -> { + return scenicPayAdapterCache.computeIfAbsent(scenicId, (key) -> { IPayAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); if (scenicConfig.getString("pay_type") != null) { @@ -128,4 +139,94 @@ public class ScenicServiceImpl implements ScenicService { return adapter; }); } + + // ==================== 缓存管理方法 ==================== + + /** + * 清除指定景区的所有适配器缓存 + * + * @param scenicId 景区ID + */ + public void clearScenicAdapterCache(Long scenicId) { + log.info("清除景区 {} 的所有适配器缓存", scenicId); + scenicStorageAdapterCache.remove(scenicId); + scenicTmpStorageAdapterCache.remove(scenicId); + scenicLocalStorageAdapterCache.remove(scenicId); + scenicFaceBodyAdapterCache.remove(scenicId); + scenicPayAdapterCache.remove(scenicId); + } + + /** + * 清除所有适配器缓存 + */ + public void clearAllAdapterCache() { + log.info("清除所有适配器缓存"); + scenicStorageAdapterCache.clear(); + scenicTmpStorageAdapterCache.clear(); + scenicLocalStorageAdapterCache.clear(); + scenicFaceBodyAdapterCache.clear(); + scenicPayAdapterCache.clear(); + } + + /** + * 手动触发过期缓存清理 + * + * @return 清理的过期缓存项总数 + */ + public int cleanupExpiredCache() { + log.info("手动触发过期缓存清理"); + int totalCleaned = 0; + totalCleaned += scenicStorageAdapterCache.cleanupExpired(); + totalCleaned += scenicTmpStorageAdapterCache.cleanupExpired(); + totalCleaned += scenicLocalStorageAdapterCache.cleanupExpired(); + totalCleaned += scenicFaceBodyAdapterCache.cleanupExpired(); + totalCleaned += scenicPayAdapterCache.cleanupExpired(); + log.info("清理了 {} 个过期缓存项", totalCleaned); + return totalCleaned; + } + + /** + * 获取缓存统计信息 + * + * @return 缓存统计信息 + */ + public String getCacheStats() { + StringBuilder stats = new StringBuilder(); + stats.append("=== ScenicServiceImpl 缓存统计信息 ===\n"); + stats.append("Storage Adapter Cache: ").append(scenicStorageAdapterCache.getStats()).append("\n"); + stats.append("Tmp Storage Adapter Cache: ").append(scenicTmpStorageAdapterCache.getStats()).append("\n"); + stats.append("Local Storage Adapter Cache: ").append(scenicLocalStorageAdapterCache.getStats()).append("\n"); + stats.append("FaceBody Adapter Cache: ").append(scenicFaceBodyAdapterCache.getStats()).append("\n"); + stats.append("Pay Adapter Cache: ").append(scenicPayAdapterCache.getStats()).append("\n"); + return stats.toString(); + } + + /** + * 重置所有缓存统计信息 + */ + public void resetCacheStats() { + log.info("重置所有缓存统计信息"); + scenicStorageAdapterCache.resetStats(); + scenicTmpStorageAdapterCache.resetStats(); + scenicLocalStorageAdapterCache.resetStats(); + scenicFaceBodyAdapterCache.resetStats(); + scenicPayAdapterCache.resetStats(); + } + + /** + * 获取指定景区缓存的剩余TTL时间 + * + * @param scenicId 景区ID + * @return 各类型适配器缓存的剩余TTL时间(毫秒) + */ + public String getScenicCacheTtl(Long scenicId) { + StringBuilder ttlInfo = new StringBuilder(); + ttlInfo.append("景区 ").append(scenicId).append(" 缓存TTL信息:\n"); + ttlInfo.append("Storage: ").append(scenicStorageAdapterCache.getRemainTtl(scenicId)).append("ms\n"); + ttlInfo.append("TmpStorage: ").append(scenicTmpStorageAdapterCache.getRemainTtl(scenicId)).append("ms\n"); + ttlInfo.append("LocalStorage: ").append(scenicLocalStorageAdapterCache.getRemainTtl(scenicId)).append("ms\n"); + ttlInfo.append("FaceBody: ").append(scenicFaceBodyAdapterCache.getRemainTtl(scenicId)).append("ms\n"); + ttlInfo.append("Pay: ").append(scenicPayAdapterCache.getRemainTtl(scenicId)).append("ms\n"); + return ttlInfo.toString(); + } } diff --git a/src/main/java/com/ycwl/basic/util/TtlCacheMap.java b/src/main/java/com/ycwl/basic/util/TtlCacheMap.java new file mode 100644 index 0000000..fc5f91f --- /dev/null +++ b/src/main/java/com/ycwl/basic/util/TtlCacheMap.java @@ -0,0 +1,340 @@ +package com.ycwl.basic.util; + +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Function; + +/** + * 带TTL(生存时间)的缓存Map工具类 + * + * @param 键类型 + * @param 值类型 + */ +@Slf4j +public class TtlCacheMap { + + /** + * 缓存项包装类 + */ + private static class CacheItem { + private final V value; + private final long expireTime; + + public CacheItem(V value, long ttlMillis) { + this.value = value; + this.expireTime = System.currentTimeMillis() + ttlMillis; + } + + public V getValue() { + return value; + } + + public boolean isExpired() { + return System.currentTimeMillis() > expireTime; + } + + public long getRemainTtl() { + return Math.max(0, expireTime - System.currentTimeMillis()); + } + } + + private final ConcurrentHashMap> cache; + private final long defaultTtlMillis; + private final ReentrantReadWriteLock lock; + private final ScheduledExecutorService cleanupExecutor; + + // 统计信息 + private volatile long hitCount = 0; + private volatile long missCount = 0; + private volatile long expiredCount = 0; + + /** + * 构造函数 + * + * @param defaultTtlMillis 默认TTL时间(毫秒) + */ + public TtlCacheMap(long defaultTtlMillis) { + this.cache = new ConcurrentHashMap<>(); + this.defaultTtlMillis = defaultTtlMillis; + this.lock = new ReentrantReadWriteLock(); + this.cleanupExecutor = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "TtlCacheMap-Cleanup"); + t.setDaemon(true); + return t; + }); + + // 启动定期清理任务,每分钟清理一次过期条目 + this.cleanupExecutor.scheduleWithFixedDelay(this::cleanupExpired, 60, 60, TimeUnit.SECONDS); + } + + /** + * 构造函数,使用默认TTL为10分钟 + */ + public TtlCacheMap() { + this(TimeUnit.MINUTES.toMillis(10)); + } + + /** + * 获取缓存值,如果不存在或过期则通过supplier创建 + * + * @param key 缓存键 + * @param valueSupplier 值提供器 + * @return 缓存值 + */ + public V computeIfAbsent(K key, Function valueSupplier) { + return computeIfAbsent(key, valueSupplier, defaultTtlMillis); + } + + /** + * 获取缓存值,如果不存在或过期则通过supplier创建 + * + * @param key 缓存键 + * @param valueSupplier 值提供器 + * @param ttlMillis TTL时间(毫秒) + * @return 缓存值 + */ + public V computeIfAbsent(K key, Function valueSupplier, long ttlMillis) { + lock.readLock().lock(); + try { + CacheItem item = cache.get(key); + if (item != null && !item.isExpired()) { + hitCount++; + return item.getValue(); + } + } finally { + lock.readLock().unlock(); + } + + // 缓存不存在或已过期,需要重新创建 + lock.writeLock().lock(); + try { + // 双重检查,防止重复创建 + CacheItem item = cache.get(key); + if (item != null && !item.isExpired()) { + hitCount++; + return item.getValue(); + } + + if (item != null && item.isExpired()) { + expiredCount++; + cache.remove(key); + } + + // 创建新值 + missCount++; + V value = valueSupplier.apply(key); + if (value != null) { + cache.put(key, new CacheItem<>(value, ttlMillis)); + } + return value; + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 直接放入缓存 + * + * @param key 缓存键 + * @param value 缓存值 + */ + public void put(K key, V value) { + put(key, value, defaultTtlMillis); + } + + /** + * 直接放入缓存 + * + * @param key 缓存键 + * @param value 缓存值 + * @param ttlMillis TTL时间(毫秒) + */ + public void put(K key, V value, long ttlMillis) { + lock.writeLock().lock(); + try { + cache.put(key, new CacheItem<>(value, ttlMillis)); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 获取缓存值 + * + * @param key 缓存键 + * @return 缓存值,如果不存在或过期返回null + */ + public V get(K key) { + lock.readLock().lock(); + try { + CacheItem item = cache.get(key); + if (item != null) { + if (!item.isExpired()) { + hitCount++; + return item.getValue(); + } else { + // 异步清理过期项 + cleanupExecutor.execute(() -> { + lock.writeLock().lock(); + try { + CacheItem expiredItem = cache.get(key); + if (expiredItem != null && expiredItem.isExpired()) { + cache.remove(key); + expiredCount++; + } + } finally { + lock.writeLock().unlock(); + } + }); + } + } + missCount++; + return null; + } finally { + lock.readLock().unlock(); + } + } + + /** + * 移除缓存项 + * + * @param key 缓存键 + * @return 被移除的值,如果不存在返回null + */ + public V remove(K key) { + lock.writeLock().lock(); + try { + CacheItem item = cache.remove(key); + return item != null ? item.getValue() : null; + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 清空所有缓存 + */ + public void clear() { + lock.writeLock().lock(); + try { + cache.clear(); + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 检查缓存键是否存在且未过期 + * + * @param key 缓存键 + * @return true如果存在且未过期 + */ + public boolean containsKey(K key) { + return get(key) != null; + } + + /** + * 获取缓存大小(包含过期项) + * + * @return 缓存大小 + */ + public int size() { + return cache.size(); + } + + /** + * 检查缓存是否为空 + * + * @return true如果为空 + */ + public boolean isEmpty() { + return cache.isEmpty(); + } + + /** + * 手动触发过期清理 + * + * @return 清理的过期项数量 + */ + public int cleanupExpired() { + lock.writeLock().lock(); + try { + int cleanedCount = 0; + var iterator = cache.entrySet().iterator(); + while (iterator.hasNext()) { + var entry = iterator.next(); + if (entry.getValue().isExpired()) { + iterator.remove(); + cleanedCount++; + expiredCount++; + } + } + if (cleanedCount > 0) { + log.debug("清理了 {} 个过期缓存项", cleanedCount); + } + return cleanedCount; + } finally { + lock.writeLock().unlock(); + } + } + + /** + * 获取缓存统计信息 + * + * @return 统计信息字符串 + */ + public String getStats() { + long total = hitCount + missCount; + double hitRate = total > 0 ? (double) hitCount / total * 100 : 0; + + return String.format( + "TtlCacheMap Stats: size=%d, hits=%d, misses=%d, expired=%d, hitRate=%.2f%%", + cache.size(), hitCount, missCount, expiredCount, hitRate + ); + } + + /** + * 重置统计信息 + */ + public void resetStats() { + hitCount = 0; + missCount = 0; + expiredCount = 0; + } + + /** + * 获取剩余TTL时间 + * + * @param key 缓存键 + * @return 剩余TTL毫秒数,如果不存在或已过期返回0 + */ + public long getRemainTtl(K key) { + lock.readLock().lock(); + try { + CacheItem item = cache.get(key); + return item != null ? item.getRemainTtl() : 0; + } finally { + lock.readLock().unlock(); + } + } + + /** + * 关闭清理线程池,释放资源 + */ + public void shutdown() { + cleanupExecutor.shutdown(); + try { + if (!cleanupExecutor.awaitTermination(5, TimeUnit.SECONDS)) { + cleanupExecutor.shutdownNow(); + } + } catch (InterruptedException e) { + cleanupExecutor.shutdownNow(); + Thread.currentThread().interrupt(); + } + } +} \ No newline at end of file From 5c2629237ecf75722593fb9cae8838ea1668dbc3 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 18:13:59 +0800 Subject: [PATCH 21/32] =?UTF-8?q?feat(mobile):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E8=AE=A2=E5=8D=95V2=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 AppOrderV2Controller 控制器,实现移动端价格计算和下单功能 - 新增 MobilePriceCalculationRequest DTO 类,用于移动端价格计算请求- 集成 Redis 缓存机制,提升价格查询性能- 实现人脸权限验证和价格缓存验证逻辑 - 优化日志记录和异常处理 --- .../mobile/AppOrderV2Controller.java | 176 ++++++++++++++++++ .../dto/MobilePriceCalculationRequest.java | 62 ++++++ 2 files changed, 238 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java create mode 100644 src/main/java/com/ycwl/basic/pricing/dto/MobilePriceCalculationRequest.java diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java new file mode 100644 index 0000000..ce36186 --- /dev/null +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java @@ -0,0 +1,176 @@ +package com.ycwl.basic.controller.mobile; + +import com.ycwl.basic.constant.BaseContextHandler; +import com.ycwl.basic.utils.ApiResponse; +import com.ycwl.basic.pricing.dto.*; +import com.ycwl.basic.pricing.service.IPriceCalculationService; +import com.ycwl.basic.service.pc.FaceService; +import com.ycwl.basic.service.PriceCacheService; +import com.ycwl.basic.model.pc.face.resp.FaceRespVO; +import com.ycwl.basic.dto.MobileOrderRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; + +/** + * 移动端订单控制器V2 + * 包含价格查询和订单管理功能 + */ +@Slf4j +@RestController +@RequestMapping("/api/mobile/order/v2") +@RequiredArgsConstructor +public class AppOrderV2Controller { + + private final IPriceCalculationService priceCalculationService; + private final FaceService faceService; + private final PriceCacheService priceCacheService; + + /** + * 移动端价格计算 + * 包含权限验证:验证人脸所属景区与当前用户匹配 + * 集成Redis缓存机制,提升查询性能 + */ + @PostMapping("/calculate") + public ApiResponse calculatePrice(@RequestBody MobilePriceCalculationRequest request) { + // 获取当前登录用户ID + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("移动端价格计算:用户未登录"); + return ApiResponse.fail("用户未登录"); + } + + Long currentUserId = Long.valueOf(currentUserIdStr); + log.info("移动端价格计算请求: userId={}, faceId={}, products={}", + currentUserId, request.getFaceId(), request.getProducts().size()); + + // 验证faceId参数 + if (request.getFaceId() == null) { + log.warn("移动端价格计算:faceId参数缺失"); + return ApiResponse.fail("faceId参数不能为空"); + } + + // 查询人脸信息进行权限验证 + ApiResponse faceResponse = faceService.getById(request.getFaceId()); + if (!faceResponse.isSuccess() || faceResponse.getData() == null) { + log.warn("移动端价格计算:人脸信息不存在, faceId={}", request.getFaceId()); + return ApiResponse.fail("人脸信息不存在"); + } + + FaceRespVO face = faceResponse.getData(); + Long scenicId = face.getScenicId(); + + // 先尝试从Redis缓存获取价格计算结果 + PriceCalculationResult cachedResult = priceCacheService.getCachedPriceResult( + currentUserId, scenicId, request.getProducts()); + + if (cachedResult != null) { + log.info("命中价格缓存: userId={}, scenicId={}, finalAmount={}", + currentUserId, scenicId, cachedResult.getFinalAmount()); + return ApiResponse.success(cachedResult); + } + + // 转换为标准价格计算请求 + PriceCalculationRequest standardRequest = request.toStandardRequest(currentUserId, scenicId); + + // 执行价格计算 + PriceCalculationResult result = priceCalculationService.calculatePrice(standardRequest); + + // 将计算结果缓存到Redis + String cacheKey = priceCacheService.cachePriceResult(currentUserId, scenicId, request.getProducts(), result); + + log.info("移动端价格计算完成: userId={}, scenicId={}, originalAmount={}, finalAmount={}, cacheKey={}", + currentUserId, scenicId, result.getOriginalAmount(), result.getFinalAmount(), cacheKey); + + return ApiResponse.success(result); + } + + /** + * 移动端下单接口 + * 验证价格缓存有效性,确保5分钟内使用缓存价格下单 + */ + @PostMapping("/add-order") + public ApiResponse addOrder(@RequestBody MobileOrderRequest request) { + // 获取当前登录用户ID + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("移动端下单:用户未登录"); + return ApiResponse.fail("用户未登录"); + } + + Long currentUserId = Long.valueOf(currentUserIdStr); + log.info("移动端下单请求: userId={}, faceId={}, products={}, expectedFinalAmount={}", + currentUserId, request.getFaceId(), request.getProducts().size(), request.getExpectedFinalAmount()); + + // 验证必填参数 + if (request.getFaceId() == null) { + log.warn("移动端下单:faceId参数缺失"); + return ApiResponse.fail("faceId参数不能为空"); + } + + if (request.getProducts() == null || request.getProducts().isEmpty()) { + log.warn("移动端下单:商品列表为空"); + return ApiResponse.fail("商品列表不能为空"); + } + + if (request.getExpectedFinalAmount() == null) { + log.warn("移动端下单:预期价格参数缺失"); + return ApiResponse.fail("预期价格不能为空"); + } + + // 查询人脸信息进行权限验证 + ApiResponse faceResponse = faceService.getById(request.getFaceId()); + if (!faceResponse.isSuccess() || faceResponse.getData() == null) { + log.warn("移动端下单:人脸信息不存在, faceId={}", request.getFaceId()); + return ApiResponse.fail("人脸信息不存在"); + } + + FaceRespVO face = faceResponse.getData(); + Long scenicId = face.getScenicId(); + + // 验证并消费价格缓存(一次性使用) + PriceCalculationResult cachedResult = priceCacheService.validateAndConsumePriceCache( + currentUserId, scenicId, request.getProducts()); + + if (cachedResult == null) { + log.warn("移动端下单:价格缓存已过期或不存在, userId={}, scenicId={}", currentUserId, scenicId); + return ApiResponse.fail("价格信息已过期,请重新查询价格后再下单"); + } + + // 验证价格是否匹配 + if (cachedResult.getFinalAmount().compareTo(request.getExpectedFinalAmount()) != 0) { + log.warn("移动端下单:价格不匹配, cached={}, expected={}, userId={}, scenicId={}", + cachedResult.getFinalAmount(), request.getExpectedFinalAmount(), currentUserId, scenicId); + return ApiResponse.fail("价格信息不匹配,请重新查询价格后再下单"); + } + + // 验证原价是否匹配(可选) + if (request.getExpectedOriginalAmount() != null && + cachedResult.getOriginalAmount().compareTo(request.getExpectedOriginalAmount()) != 0) { + log.warn("移动端下单:原价不匹配, cached={}, expected={}, userId={}, scenicId={}", + cachedResult.getOriginalAmount(), request.getExpectedOriginalAmount(), currentUserId, scenicId); + return ApiResponse.fail("原价信息不匹配,请重新查询价格后再下单"); + } + + log.info("价格缓存验证通过: userId={}, scenicId={}, finalAmount={}", + currentUserId, scenicId, cachedResult.getFinalAmount()); + + // TODO: 实现订单创建逻辑 + // 1. 生成订单号 + // 2. 创建订单基础信息 + // 3. 保存订单商品明细 + // 4. 记录使用的优惠券和券码信息 + // 5. 更新优惠券和券码状态 + // 6. 初始化订单状态为待支付 + // 7. 发送订单创建成功通知 + + String orderId = "ORDER_" + System.currentTimeMillis(); // 临时订单号生成逻辑 + + log.info("移动端订单创建成功: orderId={}, userId={}, scenicId={}, finalAmount={}", + orderId, currentUserId, scenicId, cachedResult.getFinalAmount()); + + return ApiResponse.success(orderId); + } +} diff --git a/src/main/java/com/ycwl/basic/pricing/dto/MobilePriceCalculationRequest.java b/src/main/java/com/ycwl/basic/pricing/dto/MobilePriceCalculationRequest.java new file mode 100644 index 0000000..d7e49dd --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/MobilePriceCalculationRequest.java @@ -0,0 +1,62 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +import java.util.List; + +/** + * 移动端价格计算请求DTO + */ +@Data +public class MobilePriceCalculationRequest { + + /** + * 商品列表 + */ + private List products; + + /** + * 人脸ID(必填,用于权限验证) + */ + private Long faceId; + + /** + * 是否自动使用优惠券 + */ + private Boolean autoUseCoupon = true; + + /** + * 用户输入的券码 + */ + private String voucherCode; + + /** + * 是否自动使用券码优惠 + */ + private Boolean autoUseVoucher = true; + + /** + * 是否仅预览优惠(不实际使用) + */ + private Boolean previewOnly = false; + + /** + * 转换为标准价格计算请求 + * + * @param userId 用户ID + * @param scenicId 景区ID + * @return 标准价格计算请求 + */ + public PriceCalculationRequest toStandardRequest(Long userId, Long scenicId) { + PriceCalculationRequest request = new PriceCalculationRequest(); + request.setProducts(this.products); + request.setUserId(userId); + request.setScenicId(scenicId); + request.setFaceId(this.faceId); + request.setAutoUseCoupon(this.autoUseCoupon); + request.setVoucherCode(this.voucherCode); + request.setAutoUseVoucher(this.autoUseVoucher); + request.setPreviewOnly(this.previewOnly); + return request; + } +} \ No newline at end of file From af79a5ffa67fc120f67e8292a14bfa717d92e768 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 18:14:34 +0800 Subject: [PATCH 22/32] =?UTF-8?q?feat(basic):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E4=B8=8B=E5=8D=95=E8=AF=B7=E6=B1=82?= =?UTF-8?q?DTO=E5=92=8C=E4=BB=B7=E6=A0=BC=E7=BC=93=E5=AD=98=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1-=20=E5=88=9B=E5=BB=BA=20MobileOrderRequest=20?= =?UTF-8?q?=E7=B1=BB=E7=94=A8=E4=BA=8E=E7=A7=BB=E5=8A=A8=E7=AB=AF=E4=B8=8B?= =?UTF-8?q?=E5=8D=95=E8=AF=B7=E6=B1=82=20-=20=E5=AE=9E=E7=8E=B0=20PriceCac?= =?UTF-8?q?heService=20=E7=B1=BB=E6=8F=90=E4=BE=9B=E4=BB=B7=E6=A0=BC?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=9B=B8=E5=85=B3=E5=8A=9F=E8=83=BD=20-=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20Redis=20=E7=BC=93=E5=AD=98=E4=BB=B7?= =?UTF-8?q?=E6=A0=BC=E8=AE=A1=E7=AE=97=E7=BB=93=E6=9E=9C=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E9=AB=98=E6=9F=A5=E8=AF=A2=E6=95=88=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ycwl/basic/dto/MobileOrderRequest.java | 54 +++++ .../ycwl/basic/service/PriceCacheService.java | 226 ++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/dto/MobileOrderRequest.java create mode 100644 src/main/java/com/ycwl/basic/service/PriceCacheService.java diff --git a/src/main/java/com/ycwl/basic/dto/MobileOrderRequest.java b/src/main/java/com/ycwl/basic/dto/MobileOrderRequest.java new file mode 100644 index 0000000..e16608f --- /dev/null +++ b/src/main/java/com/ycwl/basic/dto/MobileOrderRequest.java @@ -0,0 +1,54 @@ +package com.ycwl.basic.dto; + +import com.ycwl.basic.pricing.dto.ProductItem; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 移动端下单请求DTO + */ +@Data +public class MobileOrderRequest { + + /** + * 商品列表 + */ + private List products; + + /** + * 人脸ID(必填,用于权限验证) + */ + private Long faceId; + + /** + * 预期原价(用于价格验证) + */ + private BigDecimal expectedOriginalAmount; + + /** + * 预期最终价格(用于价格验证) + */ + private BigDecimal expectedFinalAmount; + + /** + * 是否自动使用优惠券 + */ + private Boolean autoUseCoupon = true; + + /** + * 用户输入的券码 + */ + private String voucherCode; + + /** + * 是否自动使用券码优惠 + */ + private Boolean autoUseVoucher = true; + + /** + * 订单备注 + */ + private String remarks; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/service/PriceCacheService.java b/src/main/java/com/ycwl/basic/service/PriceCacheService.java new file mode 100644 index 0000000..8743e20 --- /dev/null +++ b/src/main/java/com/ycwl/basic/service/PriceCacheService.java @@ -0,0 +1,226 @@ +package com.ycwl.basic.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ycwl.basic.pricing.dto.PriceCalculationResult; +import com.ycwl.basic.pricing.dto.ProductItem; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.List; + +/** + * 价格缓存服务 + * 使用 Redis 实现价格查询结果的缓存,缓存时间为5分钟 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class PriceCacheService { + + private final StringRedisTemplate redisTemplate; + private final ObjectMapper objectMapper; + + // 缓存Key前缀 + private static final String CACHE_PREFIX = "pricing:cache:"; + + // 缓存过期时间:5分钟 + private static final Duration CACHE_DURATION = Duration.ofMinutes(5); + + /** + * 生成缓存键 + * 格式:pricing:cache:{userId}:{scenicId}:{productsHash} + * + * @param userId 用户ID + * @param scenicId 景区ID + * @param products 商品列表 + * @return 缓存键 + */ + public String generateCacheKey(Long userId, Long scenicId, List products) { + String productsHash = calculateProductsHash(products); + return CACHE_PREFIX + userId + ":" + scenicId + ":" + productsHash; + } + + /** + * 计算商品列表的哈希值 + * 基于商品类型、ID、数量等信息生成唯一哈希 + * + * @param products 商品列表 + * @return 哈希值 + */ + private String calculateProductsHash(List products) { + try { + // 将商品列表转换为JSON字符串 + String productsJson = objectMapper.writeValueAsString(products); + + // 计算SHA-256哈希 + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hash = digest.digest(productsJson.getBytes()); + + // 转换为16进制字符串 + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + + // 返回前16位作为哈希值(足够避免冲突) + return hexString.substring(0, 16); + + } catch (JsonProcessingException | NoSuchAlgorithmException e) { + log.error("计算商品列表哈希值失败", e); + // 降级策略:使用商品列表的hashCode + return String.valueOf(products.hashCode()); + } + } + + /** + * 缓存价格计算结果 + * + * @param userId 用户ID + * @param scenicId 景区ID + * @param products 商品列表 + * @param result 价格计算结果 + * @return 缓存键 + */ + public String cachePriceResult(Long userId, Long scenicId, List products, PriceCalculationResult result) { + String cacheKey = generateCacheKey(userId, scenicId, products); + + try { + String resultJson = objectMapper.writeValueAsString(result); + redisTemplate.opsForValue().set(cacheKey, resultJson, CACHE_DURATION); + + log.info("价格计算结果已缓存: cacheKey={}, userId={}, scenicId={}", + cacheKey, userId, scenicId); + + return cacheKey; + + } catch (JsonProcessingException e) { + log.error("缓存价格计算结果失败: userId={}, scenicId={}", userId, scenicId, e); + return null; + } + } + + /** + * 获取缓存的价格计算结果 + * + * @param userId 用户ID + * @param scenicId 景区ID + * @param products 商品列表 + * @return 缓存的价格计算结果,如果不存在则返回null + */ + public PriceCalculationResult getCachedPriceResult(Long userId, Long scenicId, List products) { + String cacheKey = generateCacheKey(userId, scenicId, products); + return getCachedPriceResult(cacheKey); + } + + /** + * 根据缓存键获取价格计算结果 + * + * @param cacheKey 缓存键 + * @return 缓存的价格计算结果,如果不存在则返回null + */ + public PriceCalculationResult getCachedPriceResult(String cacheKey) { + try { + String resultJson = redisTemplate.opsForValue().get(cacheKey); + if (resultJson == null) { + log.debug("价格缓存不存在: cacheKey={}", cacheKey); + return null; + } + + PriceCalculationResult result = objectMapper.readValue(resultJson, PriceCalculationResult.class); + log.info("获取到缓存的价格计算结果: cacheKey={}", cacheKey); + + return result; + + } catch (JsonProcessingException e) { + log.error("解析缓存的价格计算结果失败: cacheKey={}", cacheKey, e); + return null; + } + } + + /** + * 检查价格缓存是否存在 + * + * @param userId 用户ID + * @param scenicId 景区ID + * @param products 商品列表 + * @return true如果缓存存在且未过期 + */ + public boolean isCacheExists(Long userId, Long scenicId, List products) { + String cacheKey = generateCacheKey(userId, scenicId, products); + return Boolean.TRUE.equals(redisTemplate.hasKey(cacheKey)); + } + + /** + * 验证并消费价格缓存(一次性使用) + * 查询缓存结果后立即删除,确保缓存只能被使用一次 + * + * @param userId 用户ID + * @param scenicId 景区ID + * @param products 商品列表 + * @return 缓存的价格计算结果,如果不存在则返回null + */ + public PriceCalculationResult validateAndConsumePriceCache(Long userId, Long scenicId, List products) { + String cacheKey = generateCacheKey(userId, scenicId, products); + + try { + // 使用 Redis 的 GETDEL 命令原子性地获取并删除缓存 + String resultJson = redisTemplate.opsForValue().getAndDelete(cacheKey); + + if (resultJson == null) { + log.warn("价格缓存不存在或已过期: userId={}, scenicId={}, cacheKey={}", + userId, scenicId, cacheKey); + return null; + } + + PriceCalculationResult result = objectMapper.readValue(resultJson, PriceCalculationResult.class); + log.info("价格缓存验证成功并已消费: userId={}, scenicId={}, cacheKey={}", + userId, scenicId, cacheKey); + + return result; + + } catch (JsonProcessingException e) { + log.error("解析价格缓存失败: userId={}, scenicId={}, cacheKey={}", + userId, scenicId, cacheKey, e); + // 缓存解析失败时也要删除无效缓存 + redisTemplate.delete(cacheKey); + return null; + } + } + + /** + * 删除价格缓存 + * + * @param userId 用户ID + * @param scenicId 景区ID + * @param products 商品列表 + * @return true如果删除成功 + */ + public boolean deletePriceCache(Long userId, Long scenicId, List products) { + String cacheKey = generateCacheKey(userId, scenicId, products); + return Boolean.TRUE.equals(redisTemplate.delete(cacheKey)); + } + + /** + * 获取缓存剩余过期时间 + * + * @param userId 用户ID + * @param scenicId 景区ID + * @param products 商品列表 + * @return 剩余时间(秒),-1表示永不过期,-2表示不存在 + */ + public long getCacheExpireTime(Long userId, Long scenicId, List products) { + String cacheKey = generateCacheKey(userId, scenicId, products); + Long expire = redisTemplate.getExpire(cacheKey); + return expire != null ? expire : -2; + } +} \ No newline at end of file From e95e0a04ff78b484bacfacbe72f56543c28360e3 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 28 Aug 2025 18:42:47 +0800 Subject: [PATCH 23/32] =?UTF-8?q?feat(order):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=20V2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增订单创建、查询、备注更新、申请退款等接口 - 添加订单相关实体类和枚举类 - 实现订单事件监听器,处理支付、退款、订单状态变化 - 优化移动端订单创建逻辑,集成订单服务 --- .../mobile/AppOrderV2Controller.java | 90 ++- .../order/controller/OrderV2Controller.java | 89 +++ .../basic/order/dto/OrderRemarkRequest.java | 15 + .../order/dto/OrderV2DetailResponse.java | 150 +++++ .../basic/order/dto/OrderV2ListResponse.java | 131 +++++ .../basic/order/dto/OrderV2PageRequest.java | 72 +++ .../ycwl/basic/order/dto/RefundRequest.java | 52 ++ .../basic/order/entity/OrderDiscountV2.java | 91 +++ .../ycwl/basic/order/entity/OrderItemV2.java | 84 +++ .../basic/order/entity/OrderRefundV2.java | 146 +++++ .../com/ycwl/basic/order/entity/OrderV2.java | 153 +++++ .../ycwl/basic/order/enums/DiscountType.java | 60 ++ .../ycwl/basic/order/enums/OrderStatus.java | 70 +++ .../ycwl/basic/order/enums/PaymentStatus.java | 50 ++ .../ycwl/basic/order/enums/RefundStatus.java | 55 ++ .../ycwl/basic/order/enums/RefundType.java | 50 ++ .../basic/order/event/OrderEventListener.java | 28 + .../basic/order/event/OrderEventManager.java | 125 +++++ .../order/event/OrderStatusChangeEvent.java | 60 ++ .../order/event/PaymentStatusChangeEvent.java | 60 ++ .../order/event/RefundStatusChangeEvent.java | 79 +++ .../impl/PaymentStatusChangeListener.java | 120 ++++ .../order/mapper/OrderDiscountMapper.java | 12 + .../basic/order/mapper/OrderItemMapper.java | 12 + .../ycwl/basic/order/mapper/OrderMapper.java | 12 + .../basic/order/mapper/OrderRefundMapper.java | 12 + .../basic/order/service/IOrderService.java | 119 ++++ .../order/service/impl/OrderServiceImpl.java | 530 ++++++++++++++++++ 28 files changed, 2515 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/order/controller/OrderV2Controller.java create mode 100644 src/main/java/com/ycwl/basic/order/dto/OrderRemarkRequest.java create mode 100644 src/main/java/com/ycwl/basic/order/dto/OrderV2DetailResponse.java create mode 100644 src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java create mode 100644 src/main/java/com/ycwl/basic/order/dto/OrderV2PageRequest.java create mode 100644 src/main/java/com/ycwl/basic/order/dto/RefundRequest.java create mode 100644 src/main/java/com/ycwl/basic/order/entity/OrderDiscountV2.java create mode 100644 src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java create mode 100644 src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java create mode 100644 src/main/java/com/ycwl/basic/order/entity/OrderV2.java create mode 100644 src/main/java/com/ycwl/basic/order/enums/DiscountType.java create mode 100644 src/main/java/com/ycwl/basic/order/enums/OrderStatus.java create mode 100644 src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java create mode 100644 src/main/java/com/ycwl/basic/order/enums/RefundStatus.java create mode 100644 src/main/java/com/ycwl/basic/order/enums/RefundType.java create mode 100644 src/main/java/com/ycwl/basic/order/event/OrderEventListener.java create mode 100644 src/main/java/com/ycwl/basic/order/event/OrderEventManager.java create mode 100644 src/main/java/com/ycwl/basic/order/event/OrderStatusChangeEvent.java create mode 100644 src/main/java/com/ycwl/basic/order/event/PaymentStatusChangeEvent.java create mode 100644 src/main/java/com/ycwl/basic/order/event/RefundStatusChangeEvent.java create mode 100644 src/main/java/com/ycwl/basic/order/event/impl/PaymentStatusChangeListener.java create mode 100644 src/main/java/com/ycwl/basic/order/mapper/OrderDiscountMapper.java create mode 100644 src/main/java/com/ycwl/basic/order/mapper/OrderItemMapper.java create mode 100644 src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java create mode 100644 src/main/java/com/ycwl/basic/order/mapper/OrderRefundMapper.java create mode 100644 src/main/java/com/ycwl/basic/order/service/IOrderService.java create mode 100644 src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java index ce36186..5d87e2d 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java @@ -1,5 +1,6 @@ package com.ycwl.basic.controller.mobile; +import com.github.pagehelper.PageInfo; import com.ycwl.basic.constant.BaseContextHandler; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.pricing.dto.*; @@ -8,6 +9,10 @@ import com.ycwl.basic.service.pc.FaceService; import com.ycwl.basic.service.PriceCacheService; import com.ycwl.basic.model.pc.face.resp.FaceRespVO; import com.ycwl.basic.dto.MobileOrderRequest; +import com.ycwl.basic.order.service.IOrderService; +import com.ycwl.basic.order.dto.OrderV2DetailResponse; +import com.ycwl.basic.order.dto.OrderV2ListResponse; +import com.ycwl.basic.order.dto.OrderV2PageRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @@ -27,6 +32,7 @@ public class AppOrderV2Controller { private final IPriceCalculationService priceCalculationService; private final FaceService faceService; private final PriceCacheService priceCacheService; + private final IOrderService orderService; /** * 移动端价格计算 @@ -157,20 +163,80 @@ public class AppOrderV2Controller { log.info("价格缓存验证通过: userId={}, scenicId={}, finalAmount={}", currentUserId, scenicId, cachedResult.getFinalAmount()); - // TODO: 实现订单创建逻辑 - // 1. 生成订单号 - // 2. 创建订单基础信息 - // 3. 保存订单商品明细 - // 4. 记录使用的优惠券和券码信息 - // 5. 更新优惠券和券码状态 - // 6. 初始化订单状态为待支付 - // 7. 发送订单创建成功通知 + // 创建订单 + try { + Long orderId = orderService.createOrder(request, currentUserId, scenicId, cachedResult); + + log.info("移动端订单创建成功: orderId={}, userId={}, scenicId={}, finalAmount={}", + orderId, currentUserId, scenicId, cachedResult.getFinalAmount()); + + return ApiResponse.success(orderId.toString()); + + } catch (Exception e) { + log.error("订单创建失败: userId={}, scenicId={}, error={}", currentUserId, scenicId, e.getMessage(), e); + return ApiResponse.fail("订单创建失败,请稍后重试"); + } + } + + // ====== 新增移动端订单查询功能 ====== + + /** + * 用户分页查询自己的订单列表 + */ + @PostMapping("/page") + public ApiResponse> pageUserOrders(@RequestBody OrderV2PageRequest request) { + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("用户未登录"); + return ApiResponse.fail("用户未登录"); + } - String orderId = "ORDER_" + System.currentTimeMillis(); // 临时订单号生成逻辑 + Long currentUserId = Long.valueOf(currentUserIdStr); + request.setMemberId(currentUserId); // 设置当前用户ID,确保只查询自己的订单 - log.info("移动端订单创建成功: orderId={}, userId={}, scenicId={}, finalAmount={}", - orderId, currentUserId, scenicId, cachedResult.getFinalAmount()); + log.info("用户查询订单列表: userId={}, request={}", currentUserId, request); - return ApiResponse.success(orderId); + try { + PageInfo pageInfo = orderService.pageOrdersByUser(request); + return ApiResponse.success(pageInfo); + } catch (Exception e) { + log.error("查询用户订单列表失败: userId={}", currentUserId, e); + return ApiResponse.fail("查询失败:" + e.getMessage()); + } + } + + /** + * 用户查询自己的订单详情 + */ + @GetMapping("/detail/{orderId}") + public ApiResponse getUserOrderDetail(@PathVariable("orderId") Long orderId) { + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("用户未登录"); + return ApiResponse.fail("用户未登录"); + } + + Long currentUserId = Long.valueOf(currentUserIdStr); + + log.info("用户查询订单详情: userId={}, orderId={}", currentUserId, orderId); + + try { + OrderV2DetailResponse detail = orderService.getOrderDetail(orderId); + if (detail == null) { + return ApiResponse.fail("订单不存在"); + } + + // 验证订单是否属于当前用户 + if (!currentUserId.equals(detail.getMemberId())) { + log.warn("用户尝试访问他人订单: userId={}, orderId={}, orderOwner={}", + currentUserId, orderId, detail.getMemberId()); + return ApiResponse.fail("无权访问该订单"); + } + + return ApiResponse.success(detail); + } catch (Exception e) { + log.error("查询用户订单详情失败: userId={}, orderId={}", currentUserId, orderId, e); + return ApiResponse.fail("查询失败:" + e.getMessage()); + } } } diff --git a/src/main/java/com/ycwl/basic/order/controller/OrderV2Controller.java b/src/main/java/com/ycwl/basic/order/controller/OrderV2Controller.java new file mode 100644 index 0000000..c5c698c --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/controller/OrderV2Controller.java @@ -0,0 +1,89 @@ +package com.ycwl.basic.order.controller; + +import com.github.pagehelper.PageInfo; +import com.ycwl.basic.order.dto.*; +import com.ycwl.basic.order.service.IOrderService; +import com.ycwl.basic.utils.ApiResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + * 订单管理控制器V2 - 管理端 + */ +@Slf4j +@RestController +@RequestMapping("/api/order/v2") +@RequiredArgsConstructor +public class OrderV2Controller { + + private final IOrderService orderService; + + /** + * 分页查询订单列表 + */ + @PostMapping("/page") + public ApiResponse> pageOrders(@RequestBody OrderV2PageRequest request) { + log.info("分页查询订单列表: {}", request); + try { + PageInfo pageInfo = orderService.pageOrders(request); + return ApiResponse.success(pageInfo); + } catch (Exception e) { + log.error("分页查询订单列表失败", e); + return ApiResponse.fail("查询失败:" + e.getMessage()); + } + } + + /** + * 查询订单详情 + */ + @GetMapping("/detail/{orderId}") + public ApiResponse getOrderDetail(@PathVariable("orderId") Long orderId) { + log.info("查询订单详情: orderId={}", orderId); + try { + OrderV2DetailResponse detail = orderService.getOrderDetail(orderId); + if (detail == null) { + return ApiResponse.fail("订单不存在"); + } + return ApiResponse.success(detail); + } catch (Exception e) { + log.error("查询订单详情失败: orderId={}", orderId, e); + return ApiResponse.fail("查询失败:" + e.getMessage()); + } + } + + /** + * 更新订单备注 + */ + @PutMapping("/remark/{orderId}") + public ApiResponse updateOrderRemarks(@PathVariable("orderId") Long orderId, + @RequestBody OrderRemarkRequest request) { + log.info("更新订单备注: orderId={}, remarks={}", orderId, request.getRemarks()); + try { + boolean success = orderService.updateOrderRemarks(orderId, request.getRemarks()); + if (success) { + return ApiResponse.success("备注更新成功"); + } else { + return ApiResponse.fail("备注更新失败"); + } + } catch (Exception e) { + log.error("更新订单备注失败: orderId={}", orderId, e); + return ApiResponse.fail("更新失败:" + e.getMessage()); + } + } + + /** + * 申请退款 + */ + @PostMapping("/refund") + public ApiResponse createRefund(@RequestBody RefundRequest request) { + log.info("申请退款: {}", request); + try { + Long refundId = orderService.createRefundRecord(request); + return ApiResponse.success("退款申请已提交,退款ID:" + refundId); + } catch (Exception e) { + log.error("申请退款失败: {}", request, e); + return ApiResponse.fail("退款申请失败:" + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/OrderRemarkRequest.java b/src/main/java/com/ycwl/basic/order/dto/OrderRemarkRequest.java new file mode 100644 index 0000000..6b8563f --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/OrderRemarkRequest.java @@ -0,0 +1,15 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +/** + * 订单备注修改请求DTO + */ +@Data +public class OrderRemarkRequest { + + /** + * 订单备注 + */ + private String remarks; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/OrderV2DetailResponse.java b/src/main/java/com/ycwl/basic/order/dto/OrderV2DetailResponse.java new file mode 100644 index 0000000..543e663 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/OrderV2DetailResponse.java @@ -0,0 +1,150 @@ +package com.ycwl.basic.order.dto; + +import com.ycwl.basic.order.entity.OrderDiscountV2; +import com.ycwl.basic.order.entity.OrderItemV2; +import com.ycwl.basic.order.entity.OrderRefundV2; +import com.ycwl.basic.order.enums.OrderStatus; +import com.ycwl.basic.order.enums.PaymentStatus; +import com.ycwl.basic.order.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * 订单详情响应DTO + */ +@Data +public class OrderV2DetailResponse { + + /** + * 订单ID + */ + private Long id; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 会员ID + */ + private Long memberId; + + /** + * 小程序openId + */ + private String openId; + + /** + * 人脸ID + */ + private Long faceId; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 原始金额 + */ + private BigDecimal originalAmount; + + /** + * 优惠金额 + */ + private BigDecimal discountAmount; + + /** + * 最终金额 + */ + private BigDecimal finalAmount; + + /** + * 订单状态 + */ + private OrderStatus orderStatus; + + /** + * 订单状态描述 + */ + private String orderStatusDesc; + + /** + * 支付状态 + */ + private PaymentStatus paymentStatus; + + /** + * 支付状态描述 + */ + private String paymentStatusDesc; + + /** + * 退款状态 + */ + private RefundStatus refundStatus; + + /** + * 退款状态描述 + */ + private String refundStatusDesc; + + /** + * 总退款金额 + */ + private BigDecimal totalRefundAmount; + + /** + * 订单备注 + */ + private String remarks; + + /** + * 支付时间 + */ + private Date payTime; + + /** + * 完成时间 + */ + private Date completeTime; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人 + */ + private Long createBy; + + /** + * 更新人 + */ + private Long updateBy; + + /** + * 订单商品明细列表 + */ + private List orderItems; + + /** + * 订单优惠记录列表 + */ + private List orderDiscounts; + + /** + * 订单退款记录列表 + */ + private List orderRefunds; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java b/src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java new file mode 100644 index 0000000..22d317f --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java @@ -0,0 +1,131 @@ +package com.ycwl.basic.order.dto; + +import com.ycwl.basic.order.enums.OrderStatus; +import com.ycwl.basic.order.enums.PaymentStatus; +import com.ycwl.basic.order.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单列表响应DTO + */ +@Data +public class OrderV2ListResponse { + + /** + * 订单ID + */ + private Long id; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 会员ID + */ + private Long memberId; + + /** + * 小程序openId + */ + private String openId; + + /** + * 人脸ID + */ + private Long faceId; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 原始金额 + */ + private BigDecimal originalAmount; + + /** + * 优惠金额 + */ + private BigDecimal discountAmount; + + /** + * 最终金额 + */ + private BigDecimal finalAmount; + + /** + * 订单状态 + */ + private OrderStatus orderStatus; + + /** + * 订单状态描述 + */ + private String orderStatusDesc; + + /** + * 支付状态 + */ + private PaymentStatus paymentStatus; + + /** + * 支付状态描述 + */ + private String paymentStatusDesc; + + /** + * 退款状态 + */ + private RefundStatus refundStatus; + + /** + * 退款状态描述 + */ + private String refundStatusDesc; + + /** + * 总退款金额 + */ + private BigDecimal totalRefundAmount; + + /** + * 订单备注 + */ + private String remarks; + + /** + * 支付时间 + */ + private Date payTime; + + /** + * 完成时间 + */ + private Date completeTime; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 商品数量(订单中商品种类数) + */ + private Integer itemCount; + + /** + * 商品总数量(所有商品数量之和) + */ + private Integer totalQuantity; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/OrderV2PageRequest.java b/src/main/java/com/ycwl/basic/order/dto/OrderV2PageRequest.java new file mode 100644 index 0000000..f94b705 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/OrderV2PageRequest.java @@ -0,0 +1,72 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +import java.util.Date; + +/** + * 订单分页查询请求DTO + */ +@Data +public class OrderV2PageRequest { + + /** + * 页码(从1开始) + */ + private Integer pageNum = 1; + + /** + * 每页大小 + */ + private Integer pageSize = 10; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 用户ID(移动端查询时自动设置) + */ + private Long memberId; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 订单状态 + */ + private String orderStatus; + + /** + * 支付状态 + */ + private String paymentStatus; + + /** + * 退款状态 + */ + private String refundStatus; + + /** + * 开始创建时间 + */ + private Date createTimeStart; + + /** + * 结束创建时间 + */ + private Date createTimeEnd; + + /** + * 开始支付时间 + */ + private Date payTimeStart; + + /** + * 结束支付时间 + */ + private Date payTimeEnd; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java b/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java new file mode 100644 index 0000000..a3cb265 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java @@ -0,0 +1,52 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 退款申请请求DTO + */ +@Data +public class RefundRequest { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 退款类型 + */ + private String refundType; + + /** + * 退款金额 + */ + private BigDecimal refundAmount; + + /** + * 退款手续费 + */ + private BigDecimal refundFee = BigDecimal.ZERO; + + /** + * 退款原因 + */ + private String refundReason; + + /** + * 退款详细说明 + */ + private String refundDescription; + + /** + * 操作备注 + */ + private String operatorRemarks; + + /** + * 退款渠道 + */ + private String refundChannel = "ORIGINAL"; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/entity/OrderDiscountV2.java b/src/main/java/com/ycwl/basic/order/entity/OrderDiscountV2.java new file mode 100644 index 0000000..04ccdb7 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/entity/OrderDiscountV2.java @@ -0,0 +1,91 @@ +package com.ycwl.basic.order.entity; + +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 com.ycwl.basic.order.enums.DiscountType; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单优惠记录表实体V2 + */ +@Data +@TableName("order_discount_v2") +public class OrderDiscountV2 { + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单ID + */ + @TableField("order_id") + private Long orderId; + + /** + * 优惠类型 + */ + @TableField("discount_type") + private DiscountType discountType; + + /** + * 优惠名称 + */ + @TableField("discount_name") + private String discountName; + + /** + * 优惠金额 + */ + @TableField("discount_amount") + private BigDecimal discountAmount; + + /** + * 优惠比例 + */ + @TableField("discount_rate") + private BigDecimal discountRate; + + /** + * 显示顺序 + */ + @TableField("sort_order") + private Integer sortOrder; + + /** + * 优惠券ID + */ + @TableField("coupon_id") + private Long couponId; + + /** + * 优惠券码 + */ + @TableField("coupon_code") + private String couponCode; + + /** + * 券码 + */ + @TableField("voucher_code") + private String voucherCode; + + /** + * 券码批次ID + */ + @TableField("voucher_batch_id") + private Long voucherBatchId; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java b/src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java new file mode 100644 index 0000000..2ff3353 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java @@ -0,0 +1,84 @@ +package com.ycwl.basic.order.entity; + +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 lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单商品明细表实体V2 + */ +@Data +@TableName("order_item_v2") +public class OrderItemV2 { + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单ID + */ + @TableField("order_id") + private Long orderId; + + /** + * 商品类型 + */ + @TableField("product_type") + private String productType; + + /** + * 商品ID + */ + @TableField("product_id") + private String productId; + + /** + * 商品名称 + */ + @TableField("product_name") + private String productName; + + /** + * 商品数量 + */ + @TableField("quantity") + private Integer quantity; + + /** + * 单价 + */ + @TableField("unit_price") + private BigDecimal unitPrice; + + /** + * 原始小计 + */ + @TableField("original_amount") + private BigDecimal originalAmount; + + /** + * 最终小计 + */ + @TableField("final_amount") + private BigDecimal finalAmount; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + */ + @TableField("update_time") + private Date updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java b/src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java new file mode 100644 index 0000000..917677d --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java @@ -0,0 +1,146 @@ +package com.ycwl.basic.order.entity; + +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 com.ycwl.basic.order.enums.RefundStatus; +import com.ycwl.basic.order.enums.RefundType; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单退款记录表实体V2 + */ +@Data +@TableName("order_refund_v2") +public class OrderRefundV2 { + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单ID + */ + @TableField("order_id") + private Long orderId; + + /** + * 退款单号 + */ + @TableField("refund_no") + private String refundNo; + + /** + * 退款类型 + */ + @TableField("refund_type") + private RefundType refundType; + + /** + * 退款金额 + */ + @TableField("refund_amount") + private BigDecimal refundAmount; + + /** + * 退款手续费 + */ + @TableField("refund_fee") + private BigDecimal refundFee; + + /** + * 退款状态 + */ + @TableField("refund_status") + private RefundStatus refundStatus; + + /** + * 退款原因 + */ + @TableField("refund_reason") + private String refundReason; + + /** + * 退款详细说明 + */ + @TableField("refund_description") + private String refundDescription; + + /** + * 申请人ID + */ + @TableField("apply_by") + private Long applyBy; + + /** + * 审批人ID + */ + @TableField("approve_by") + private Long approveBy; + + /** + * 操作备注 + */ + @TableField("operator_remarks") + private String operatorRemarks; + + /** + * 支付平台退款单号 + */ + @TableField("payment_refund_id") + private String paymentRefundId; + + /** + * 退款渠道 + */ + @TableField("refund_channel") + private String refundChannel; + + /** + * 申请时间 + */ + @TableField("apply_time") + private Date applyTime; + + /** + * 审批时间 + */ + @TableField("approve_time") + private Date approveTime; + + /** + * 完成时间 + */ + @TableField("complete_time") + private Date completeTime; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 创建人 + */ + @TableField("create_by") + private Long createBy; + + /** + * 更新人 + */ + @TableField("update_by") + private Long updateBy; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/entity/OrderV2.java b/src/main/java/com/ycwl/basic/order/entity/OrderV2.java new file mode 100644 index 0000000..cc5e0dc --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/entity/OrderV2.java @@ -0,0 +1,153 @@ +package com.ycwl.basic.order.entity; + +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 com.ycwl.basic.order.enums.OrderStatus; +import com.ycwl.basic.order.enums.PaymentStatus; +import com.ycwl.basic.order.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单主表实体V2 + */ +@Data +@TableName("order_v2") +public class OrderV2 { + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单号 + */ + @TableField("order_no") + private String orderNo; + + /** + * 会员ID + */ + @TableField("member_id") + private Long memberId; + + /** + * 小程序openId + */ + @TableField("open_id") + private String openId; + + /** + * 人脸ID + */ + @TableField("face_id") + private Long faceId; + + /** + * 景区ID + */ + @TableField("scenic_id") + private Long scenicId; + + /** + * 原始金额 + */ + @TableField("original_amount") + private BigDecimal originalAmount; + + /** + * 优惠金额 + */ + @TableField("discount_amount") + private BigDecimal discountAmount; + + /** + * 最终金额 + */ + @TableField("final_amount") + private BigDecimal finalAmount; + + /** + * 订单状态 + */ + @TableField("order_status") + private OrderStatus orderStatus; + + /** + * 支付状态 + */ + @TableField("payment_status") + private PaymentStatus paymentStatus; + + /** + * 退款状态 + */ + @TableField("refund_status") + private RefundStatus refundStatus; + + /** + * 总退款金额 + */ + @TableField("total_refund_amount") + private BigDecimal totalRefundAmount; + + /** + * 订单备注 + */ + @TableField("remarks") + private String remarks; + + /** + * 支付时间 + */ + @TableField("pay_time") + private Date payTime; + + /** + * 完成时间 + */ + @TableField("complete_time") + private Date completeTime; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 创建人 + */ + @TableField("create_by") + private Long createBy; + + /** + * 更新人 + */ + @TableField("update_by") + private Long updateBy; + + /** + * 是否删除 + */ + @TableField("deleted") + private Integer deleted; + + /** + * 删除时间 + */ + @TableField("deleted_at") + private Date deletedAt; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/DiscountType.java b/src/main/java/com/ycwl/basic/order/enums/DiscountType.java new file mode 100644 index 0000000..82730df --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/DiscountType.java @@ -0,0 +1,60 @@ +package com.ycwl.basic.order.enums; + +/** + * 优惠类型枚举 + */ +public enum DiscountType { + + /** + * 优惠券 + */ + COUPON("COUPON", "优惠券"), + + /** + * 券码 + */ + VOUCHER("VOUCHER", "券码"), + + /** + * 限时立减 + */ + FLASH_SALE("FLASH_SALE", "限时立减"), + + /** + * 套餐优惠 + */ + BUNDLE("BUNDLE", "套餐优惠"), + + /** + * 一口价优惠 + */ + FIXED_PRICE("FIXED_PRICE", "一口价优惠"); + + private final String code; + private final String description; + + DiscountType(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static DiscountType fromCode(String code) { + for (DiscountType type : values()) { + if (type.code.equals(code)) { + return type; + } + } + throw new IllegalArgumentException("Unknown DiscountType code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/OrderStatus.java b/src/main/java/com/ycwl/basic/order/enums/OrderStatus.java new file mode 100644 index 0000000..97f1777 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/OrderStatus.java @@ -0,0 +1,70 @@ +package com.ycwl.basic.order.enums; + +/** + * 订单状态枚举 + */ +public enum OrderStatus { + + /** + * 待支付 + */ + PENDING_PAYMENT("PENDING_PAYMENT", "待支付"), + + /** + * 已支付 + */ + PAID("PAID", "已支付"), + + /** + * 处理中 + */ + PROCESSING("PROCESSING", "处理中"), + + /** + * 已完成 + */ + COMPLETED("COMPLETED", "已完成"), + + /** + * 已取消 + */ + CANCELLED("CANCELLED", "已取消"), + + /** + * 退款中 + */ + REFUNDING("REFUNDING", "退款中"), + + /** + * 已退款 + */ + REFUNDED("REFUNDED", "已退款"); + + private final String code; + private final String description; + + OrderStatus(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static OrderStatus fromCode(String code) { + for (OrderStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + throw new IllegalArgumentException("Unknown OrderStatus code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java b/src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java new file mode 100644 index 0000000..02e9602 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java @@ -0,0 +1,50 @@ +package com.ycwl.basic.order.enums; + +/** + * 支付状态枚举 + */ +public enum PaymentStatus { + + /** + * 未支付 + */ + UNPAID("UNPAID", "未支付"), + + /** + * 已支付 + */ + PAID("PAID", "已支付"), + + /** + * 已退款 + */ + REFUNDED("REFUNDED", "已退款"); + + private final String code; + private final String description; + + PaymentStatus(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static PaymentStatus fromCode(String code) { + for (PaymentStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + throw new IllegalArgumentException("Unknown PaymentStatus code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/RefundStatus.java b/src/main/java/com/ycwl/basic/order/enums/RefundStatus.java new file mode 100644 index 0000000..d52e83d --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/RefundStatus.java @@ -0,0 +1,55 @@ +package com.ycwl.basic.order.enums; + +/** + * 退款状态枚举 + */ +public enum RefundStatus { + + /** + * 无退款 + */ + NO_REFUND("NO_REFUND", "无退款"), + + /** + * 部分退款 + */ + PARTIAL_REFUND("PARTIAL_REFUND", "部分退款"), + + /** + * 全额退款 + */ + FULL_REFUND("FULL_REFUND", "全额退款"), + + /** + * 退款处理中 + */ + REFUND_PROCESSING("REFUND_PROCESSING", "退款处理中"); + + private final String code; + private final String description; + + RefundStatus(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static RefundStatus fromCode(String code) { + for (RefundStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + throw new IllegalArgumentException("Unknown RefundStatus code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/RefundType.java b/src/main/java/com/ycwl/basic/order/enums/RefundType.java new file mode 100644 index 0000000..7111883 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/RefundType.java @@ -0,0 +1,50 @@ +package com.ycwl.basic.order.enums; + +/** + * 退款类型枚举 + */ +public enum RefundType { + + /** + * 全额退款 + */ + FULL_REFUND("FULL_REFUND", "全额退款"), + + /** + * 部分退款 + */ + PARTIAL_REFUND("PARTIAL_REFUND", "部分退款"), + + /** + * 商品退款 + */ + ITEM_REFUND("ITEM_REFUND", "商品退款"); + + private final String code; + private final String description; + + RefundType(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static RefundType fromCode(String code) { + for (RefundType type : values()) { + if (type.code.equals(code)) { + return type; + } + } + throw new IllegalArgumentException("Unknown RefundType code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/OrderEventListener.java b/src/main/java/com/ycwl/basic/order/event/OrderEventListener.java new file mode 100644 index 0000000..5fb92ec --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/OrderEventListener.java @@ -0,0 +1,28 @@ +package com.ycwl.basic.order.event; + +/** + * 订单事件监听器接口 + */ +public interface OrderEventListener { + + /** + * 支付状态变更事件 + * + * @param event 支付状态变更事件 + */ + void onPaymentStatusChanged(PaymentStatusChangeEvent event); + + /** + * 退款状态变更事件 + * + * @param event 退款状态变更事件 + */ + void onRefundStatusChanged(RefundStatusChangeEvent event); + + /** + * 订单状态变更事件 + * + * @param event 订单状态变更事件 + */ + void onOrderStatusChanged(OrderStatusChangeEvent event); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/OrderEventManager.java b/src/main/java/com/ycwl/basic/order/event/OrderEventManager.java new file mode 100644 index 0000000..c6161da --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/OrderEventManager.java @@ -0,0 +1,125 @@ +package com.ycwl.basic.order.event; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * 订单事件管理器 + * 管理事件监听器的注册和事件分发 + */ +@Slf4j +@Component +public class OrderEventManager { + + private final List listeners = new ArrayList<>(); + private final Executor eventExecutor = Executors.newFixedThreadPool(5, + r -> new Thread(r, "order-event-thread")); + + @PostConstruct + public void init() { + log.info("订单事件管理器初始化完成"); + } + + /** + * 注册事件监听器 + * + * @param listener 监听器实例 + */ + public void registerListener(OrderEventListener listener) { + if (listener != null && !listeners.contains(listener)) { + listeners.add(listener); + log.info("注册订单事件监听器: {}", listener.getClass().getSimpleName()); + } + } + + /** + * 移除事件监听器 + * + * @param listener 监听器实例 + */ + public void removeListener(OrderEventListener listener) { + if (listeners.remove(listener)) { + log.info("移除订单事件监听器: {}", listener.getClass().getSimpleName()); + } + } + + /** + * 发布支付状态变更事件 + * + * @param event 支付状态变更事件 + */ + public void publishPaymentStatusChangeEvent(PaymentStatusChangeEvent event) { + log.info("发布支付状态变更事件: orderId={}, {} -> {}", + event.getOrderId(), event.getOldPaymentStatus(), event.getNewPaymentStatus()); + + CompletableFuture.runAsync(() -> { + for (OrderEventListener listener : listeners) { + try { + listener.onPaymentStatusChanged(event); + } catch (Exception e) { + log.error("处理支付状态变更事件失败: listener={}, orderId={}", + listener.getClass().getSimpleName(), event.getOrderId(), e); + } + } + }, eventExecutor); + } + + /** + * 发布退款状态变更事件 + * + * @param event 退款状态变更事件 + */ + public void publishRefundStatusChangeEvent(RefundStatusChangeEvent event) { + log.info("发布退款状态变更事件: orderId={}, refundId={}, {} -> {}", + event.getOrderId(), event.getRefundId(), + event.getOldRefundStatus(), event.getNewRefundStatus()); + + CompletableFuture.runAsync(() -> { + for (OrderEventListener listener : listeners) { + try { + listener.onRefundStatusChanged(event); + } catch (Exception e) { + log.error("处理退款状态变更事件失败: listener={}, orderId={}, refundId={}", + listener.getClass().getSimpleName(), event.getOrderId(), event.getRefundId(), e); + } + } + }, eventExecutor); + } + + /** + * 发布订单状态变更事件 + * + * @param event 订单状态变更事件 + */ + public void publishOrderStatusChangeEvent(OrderStatusChangeEvent event) { + log.info("发布订单状态变更事件: orderId={}, {} -> {}", + event.getOrderId(), event.getOldOrderStatus(), event.getNewOrderStatus()); + + CompletableFuture.runAsync(() -> { + for (OrderEventListener listener : listeners) { + try { + listener.onOrderStatusChanged(event); + } catch (Exception e) { + log.error("处理订单状态变更事件失败: listener={}, orderId={}", + listener.getClass().getSimpleName(), event.getOrderId(), e); + } + } + }, eventExecutor); + } + + /** + * 获取已注册的监听器数量 + * + * @return 监听器数量 + */ + public int getListenerCount() { + return listeners.size(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/OrderStatusChangeEvent.java b/src/main/java/com/ycwl/basic/order/event/OrderStatusChangeEvent.java new file mode 100644 index 0000000..93ea868 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/OrderStatusChangeEvent.java @@ -0,0 +1,60 @@ +package com.ycwl.basic.order.event; + +import com.ycwl.basic.order.enums.OrderStatus; +import lombok.Data; + +import java.util.Date; + +/** + * 订单状态变更事件 + */ +@Data +public class OrderStatusChangeEvent { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 旧的订单状态 + */ + private OrderStatus oldOrderStatus; + + /** + * 新的订单状态 + */ + private OrderStatus newOrderStatus; + + /** + * 变更时间 + */ + private Date changeTime; + + /** + * 变更原因 + */ + private String changeReason; + + /** + * 操作人ID + */ + private Long operatorId; + + public OrderStatusChangeEvent(Long orderId, String orderNo, + OrderStatus oldStatus, OrderStatus newStatus, + String changeReason, Long operatorId) { + this.orderId = orderId; + this.orderNo = orderNo; + this.oldOrderStatus = oldStatus; + this.newOrderStatus = newStatus; + this.changeReason = changeReason; + this.operatorId = operatorId; + this.changeTime = new Date(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/PaymentStatusChangeEvent.java b/src/main/java/com/ycwl/basic/order/event/PaymentStatusChangeEvent.java new file mode 100644 index 0000000..8a5d6d3 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/PaymentStatusChangeEvent.java @@ -0,0 +1,60 @@ +package com.ycwl.basic.order.event; + +import com.ycwl.basic.order.enums.PaymentStatus; +import lombok.Data; + +import java.util.Date; + +/** + * 支付状态变更事件 + */ +@Data +public class PaymentStatusChangeEvent { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 旧的支付状态 + */ + private PaymentStatus oldPaymentStatus; + + /** + * 新的支付状态 + */ + private PaymentStatus newPaymentStatus; + + /** + * 变更时间 + */ + private Date changeTime; + + /** + * 变更原因 + */ + private String changeReason; + + /** + * 操作人ID + */ + private Long operatorId; + + public PaymentStatusChangeEvent(Long orderId, String orderNo, + PaymentStatus oldStatus, PaymentStatus newStatus, + String changeReason, Long operatorId) { + this.orderId = orderId; + this.orderNo = orderNo; + this.oldPaymentStatus = oldStatus; + this.newPaymentStatus = newStatus; + this.changeReason = changeReason; + this.operatorId = operatorId; + this.changeTime = new Date(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/RefundStatusChangeEvent.java b/src/main/java/com/ycwl/basic/order/event/RefundStatusChangeEvent.java new file mode 100644 index 0000000..ce6949a --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/RefundStatusChangeEvent.java @@ -0,0 +1,79 @@ +package com.ycwl.basic.order.event; + +import com.ycwl.basic.order.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 退款状态变更事件 + */ +@Data +public class RefundStatusChangeEvent { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 退款记录ID + */ + private Long refundId; + + /** + * 退款单号 + */ + private String refundNo; + + /** + * 旧的退款状态 + */ + private RefundStatus oldRefundStatus; + + /** + * 新的退款状态 + */ + private RefundStatus newRefundStatus; + + /** + * 退款金额 + */ + private BigDecimal refundAmount; + + /** + * 变更时间 + */ + private Date changeTime; + + /** + * 变更原因 + */ + private String changeReason; + + /** + * 操作人ID + */ + private Long operatorId; + + public RefundStatusChangeEvent(Long orderId, String orderNo, Long refundId, String refundNo, + RefundStatus oldStatus, RefundStatus newStatus, + BigDecimal refundAmount, String changeReason, Long operatorId) { + this.orderId = orderId; + this.orderNo = orderNo; + this.refundId = refundId; + this.refundNo = refundNo; + this.oldRefundStatus = oldStatus; + this.newRefundStatus = newStatus; + this.refundAmount = refundAmount; + this.changeReason = changeReason; + this.operatorId = operatorId; + this.changeTime = new Date(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/impl/PaymentStatusChangeListener.java b/src/main/java/com/ycwl/basic/order/event/impl/PaymentStatusChangeListener.java new file mode 100644 index 0000000..baf6a90 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/impl/PaymentStatusChangeListener.java @@ -0,0 +1,120 @@ +package com.ycwl.basic.order.event.impl; + +import com.ycwl.basic.order.event.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; + +/** + * 支付状态变更监听器示例 + * 应用启动后自动注册到事件管理器 + */ +@Slf4j +@Component +public class PaymentStatusChangeListener implements OrderEventListener { + + @Resource + private OrderEventManager orderEventManager; + + @PostConstruct + public void init() { + // 应用启动后自动注册到事件管理器 + orderEventManager.registerListener(this); + log.info("支付状态变更监听器注册完成"); + } + + @Override + public void onPaymentStatusChanged(PaymentStatusChangeEvent event) { + log.info("处理支付状态变更事件: orderId={}, orderNo={}, {} -> {}, reason={}", + event.getOrderId(), event.getOrderNo(), + event.getOldPaymentStatus(), event.getNewPaymentStatus(), + event.getChangeReason()); + + // 根据支付状态执行相应的业务逻辑 + switch (event.getNewPaymentStatus()) { + case PAID: + handlePaymentSuccess(event); + break; + case REFUNDED: + handlePaymentRefunded(event); + break; + case UNPAID: + handlePaymentPending(event); + break; + default: + log.warn("未处理的支付状态: {}", event.getNewPaymentStatus()); + } + } + + @Override + public void onRefundStatusChanged(RefundStatusChangeEvent event) { + log.info("处理退款状态变更事件: orderId={}, refundId={}, {} -> {}", + event.getOrderId(), event.getRefundId(), + event.getOldRefundStatus(), event.getNewRefundStatus()); + + // 可以在这里添加退款相关的业务逻辑 + // 比如发送通知、更新库存等 + } + + @Override + public void onOrderStatusChanged(OrderStatusChangeEvent event) { + log.info("处理订单状态变更事件: orderId={}, {} -> {}", + event.getOrderId(), event.getOldOrderStatus(), event.getNewOrderStatus()); + + // 可以在这里添加订单状态相关的业务逻辑 + // 比如发送用户通知、触发后续流程等 + } + + /** + * 处理支付成功事件 + */ + private void handlePaymentSuccess(PaymentStatusChangeEvent event) { + log.info("处理支付成功: orderId={}", event.getOrderId()); + + // 这里可以添加支付成功后的业务逻辑: + // 1. 发送支付成功通知 + // 2. 更新商品库存 + // 3. 生成相关凭证 + // 4. 发送短信/邮件通知 + // 5. 启动履约流程 + + // 示例:记录支付成功日志 + log.info("订单支付成功处理完成: orderId={}, changeTime={}", + event.getOrderId(), event.getChangeTime()); + } + + /** + * 处理支付退款事件 + */ + private void handlePaymentRefunded(PaymentStatusChangeEvent event) { + log.info("处理支付退款: orderId={}", event.getOrderId()); + + // 这里可以添加支付退款后的业务逻辑: + // 1. 恢复商品库存 + // 2. 取消相关服务 + // 3. 发送退款通知 + // 4. 更新会员积分 + + // 示例:记录退款处理日志 + log.info("订单退款处理完成: orderId={}, changeTime={}", + event.getOrderId(), event.getChangeTime()); + } + + /** + * 处理支付待处理事件 + */ + private void handlePaymentPending(PaymentStatusChangeEvent event) { + log.info("处理支付待处理: orderId={}", event.getOrderId()); + + // 这里可以添加支付待处理的业务逻辑: + // 1. 设置支付超时提醒 + // 2. 预留库存 + // 3. 发送支付提醒 + + // 示例:记录待支付处理日志 + log.info("订单待支付处理完成: orderId={}, changeTime={}", + event.getOrderId(), event.getChangeTime()); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderDiscountMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderDiscountMapper.java new file mode 100644 index 0000000..86b1a82 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderDiscountMapper.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.order.entity.OrderDiscountV2; +import org.apache.ibatis.annotations.Mapper; + +/** + * 订单优惠记录表Mapper接口 + */ +@Mapper +public interface OrderDiscountMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderItemMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderItemMapper.java new file mode 100644 index 0000000..89b9df0 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderItemMapper.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.order.entity.OrderItemV2; +import org.apache.ibatis.annotations.Mapper; + +/** + * 订单商品明细表Mapper接口 + */ +@Mapper +public interface OrderItemMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java new file mode 100644 index 0000000..624a043 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.order.entity.OrderV2; +import org.apache.ibatis.annotations.Mapper; + +/** + * 订单表Mapper接口 + */ +@Mapper +public interface OrderMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderRefundMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderRefundMapper.java new file mode 100644 index 0000000..cb226e7 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderRefundMapper.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.order.entity.OrderRefundV2; +import org.apache.ibatis.annotations.Mapper; + +/** + * 订单退款记录表Mapper接口 + */ +@Mapper +public interface OrderRefundMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/service/IOrderService.java b/src/main/java/com/ycwl/basic/order/service/IOrderService.java new file mode 100644 index 0000000..cfa3bed --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/service/IOrderService.java @@ -0,0 +1,119 @@ +package com.ycwl.basic.order.service; + +import com.github.pagehelper.PageInfo; +import com.ycwl.basic.dto.MobileOrderRequest; +import com.ycwl.basic.order.dto.*; +import com.ycwl.basic.order.entity.OrderV2; +import com.ycwl.basic.order.enums.RefundStatus; +import com.ycwl.basic.pricing.dto.PriceCalculationResult; + +/** + * 订单服务接口 + */ +public interface IOrderService { + + /** + * 创建订单 + * + * @param request 移动端下单请求 + * @param userId 用户ID + * @param scenicId 景区ID + * @param priceResult 价格计算结果 + * @return 订单ID + */ + Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult); + + /** + * 根据订单号查询订单 + * + * @param orderNo 订单号 + * @return 订单信息 + */ + OrderV2 getByOrderNo(String orderNo); + + /** + * 根据订单ID查询订单 + * + * @param orderId 订单ID + * @return 订单信息 + */ + OrderV2 getById(Long orderId); + + /** + * 更新订单状态 + * + * @param orderId 订单ID + * @param orderStatus 订单状态 + * @param paymentStatus 支付状态 + * @return 更新结果 + */ + boolean updateOrderStatus(Long orderId, String orderStatus, String paymentStatus); + + /** + * 生成订单号 + * + * @return 订单号 + */ + String generateOrderNo(); + + // ====== 新增方法 ====== + + /** + * 分页查询订单列表 + * + * @param request 分页查询请求 + * @return 分页结果 + */ + PageInfo pageOrders(OrderV2PageRequest request); + + /** + * 查询订单详情(包含商品明细、优惠记录、退款记录) + * + * @param orderId 订单ID + * @return 订单详情 + */ + OrderV2DetailResponse getOrderDetail(Long orderId); + + /** + * 更新订单备注 + * + * @param orderId 订单ID + * @param remarks 备注内容 + * @return 更新结果 + */ + boolean updateOrderRemarks(Long orderId, String remarks); + + /** + * 更新支付状态 + * + * @param orderId 订单ID + * @param paymentStatus 支付状态 + * @return 更新结果 + */ + boolean updatePaymentStatus(Long orderId, String paymentStatus); + + /** + * 更新退款状态 + * + * @param orderId 订单ID + * @param refundStatus 退款状态 + * @return 更新结果 + */ + boolean updateRefundStatus(Long orderId, RefundStatus refundStatus); + + /** + * 创建退款记录 + * + * @param request 退款申请请求 + * @return 退款记录ID + */ + Long createRefundRecord(RefundRequest request); + + /** + * 根据用户ID分页查询订单列表(移动端使用) + * + * @param request 分页查询请求(已设置用户ID) + * @return 分页结果 + */ + PageInfo pageOrdersByUser(OrderV2PageRequest request); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java new file mode 100644 index 0000000..191c372 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -0,0 +1,530 @@ +package com.ycwl.basic.order.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.ycwl.basic.dto.MobileOrderRequest; +import com.ycwl.basic.order.dto.*; +import com.ycwl.basic.order.entity.OrderDiscountV2; +import com.ycwl.basic.order.entity.OrderItemV2; +import com.ycwl.basic.order.entity.OrderRefundV2; +import com.ycwl.basic.order.entity.OrderV2; +import com.ycwl.basic.order.enums.*; +import com.ycwl.basic.order.mapper.OrderDiscountMapper; +import com.ycwl.basic.order.mapper.OrderItemMapper; +import com.ycwl.basic.order.mapper.OrderMapper; +import com.ycwl.basic.order.mapper.OrderRefundMapper; +import com.ycwl.basic.order.service.IOrderService; +import com.ycwl.basic.order.event.*; +import com.ycwl.basic.pricing.dto.DiscountDetail; +import com.ycwl.basic.pricing.dto.PriceCalculationResult; +import com.ycwl.basic.pricing.dto.ProductItem; +import com.ycwl.basic.pricing.dto.VoucherInfo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 订单服务实现类 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class OrderServiceImpl implements IOrderService { + + private final OrderMapper orderMapper; + private final OrderItemMapper orderItemMapper; + private final OrderDiscountMapper orderDiscountMapper; + private final OrderRefundMapper orderRefundMapper; + private final OrderEventManager orderEventManager; + + @Override + @Transactional + public Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult) { + Date now = new Date(); + + // 1. 生成订单号 + String orderNo = generateOrderNo(); + + // 2. 创建订单基础信息 + OrderV2 order = new OrderV2(); + order.setOrderNo(orderNo); + order.setMemberId(userId); + order.setOpenId(request.getFaceId().toString()); // 临时使用faceId,实际应传入openId + order.setFaceId(request.getFaceId()); + order.setScenicId(scenicId); + + order.setOriginalAmount(priceResult.getOriginalAmount()); + order.setDiscountAmount(priceResult.getDiscountAmount()); + order.setFinalAmount(priceResult.getFinalAmount()); + + order.setOrderStatus(OrderStatus.PENDING_PAYMENT); + order.setPaymentStatus(PaymentStatus.UNPAID); + order.setRefundStatus(RefundStatus.NO_REFUND); + order.setTotalRefundAmount(BigDecimal.ZERO); + + order.setRemarks(request.getRemarks()); + order.setCreateTime(now); + order.setUpdateTime(now); + order.setCreateBy(userId); + order.setUpdateBy(userId); + order.setDeleted(0); + + // 保存订单 + orderMapper.insert(order); + Long orderId = order.getId(); + + log.info("订单基础信息创建成功: orderId={}, orderNo={}", orderId, orderNo); + + // 3. 保存订单商品明细 + for (ProductItem product : request.getProducts()) { + OrderItemV2 orderItem = new OrderItemV2(); + orderItem.setOrderId(orderId); + orderItem.setProductType(product.getProductType().name()); + orderItem.setProductId(product.getProductId()); + orderItem.setProductName(getProductName(product)); // 根据商品类型和ID获取商品名称 + orderItem.setQuantity(product.getQuantity()); + orderItem.setUnitPrice(product.getUnitPrice()); + orderItem.setOriginalAmount(product.getOriginalPrice()); + orderItem.setFinalAmount(product.getSubtotal()); + orderItem.setCreateTime(now); + orderItem.setUpdateTime(now); + + orderItemMapper.insert(orderItem); + } + + log.info("订单商品明细保存成功: orderId={}, itemCount={}", orderId, request.getProducts().size()); + + // 4. 记录使用的优惠券信息 + if (priceResult.getUsedCoupon() != null) { + OrderDiscountV2 couponDiscount = new OrderDiscountV2(); + couponDiscount.setOrderId(orderId); + couponDiscount.setDiscountType(DiscountType.COUPON); + couponDiscount.setDiscountName(priceResult.getUsedCoupon().getCouponName()); + couponDiscount.setDiscountAmount(priceResult.getUsedCoupon().getActualDiscountAmount()); + couponDiscount.setSortOrder(3); // 优惠券显示顺序 + couponDiscount.setCouponId(priceResult.getUsedCoupon().getCouponId()); + couponDiscount.setCreateTime(now); + + orderDiscountMapper.insert(couponDiscount); + log.info("优惠券记录保存成功: orderId={}, couponId={}, discountAmount={}", + orderId, priceResult.getUsedCoupon().getCouponId(), priceResult.getUsedCoupon().getActualDiscountAmount()); + } + + // 5. 记录使用的券码信息 + if (priceResult.getUsedVoucher() != null) { + VoucherInfo voucherInfo = priceResult.getUsedVoucher(); + OrderDiscountV2 voucherDiscount = new OrderDiscountV2(); + voucherDiscount.setOrderId(orderId); + voucherDiscount.setDiscountType(DiscountType.VOUCHER); + voucherDiscount.setDiscountName(voucherInfo.getBatchName()); + voucherDiscount.setDiscountAmount(voucherInfo.getActualDiscountAmount()); + voucherDiscount.setSortOrder(1); // 券码显示顺序最高 + voucherDiscount.setVoucherCode(voucherInfo.getVoucherCode()); + voucherDiscount.setCreateTime(now); + + orderDiscountMapper.insert(voucherDiscount); + log.info("券码记录保存成功: orderId={}, voucherCode={}, discountAmount={}", + orderId, voucherInfo.getVoucherCode(), voucherInfo.getActualDiscountAmount()); + } + + // 6. 记录其他优惠信息(如限时立减等) + if (priceResult.getDiscountDetails() != null) { + for (DiscountDetail discount : priceResult.getDiscountDetails()) { + // 跳过已经记录的优惠券和券码 + if ("VOUCHER".equals(discount.getDiscountType()) || "COUPON".equals(discount.getDiscountType())) { + continue; + } + + OrderDiscountV2 otherDiscount = new OrderDiscountV2(); + otherDiscount.setOrderId(orderId); + otherDiscount.setDiscountType(DiscountType.fromCode(discount.getDiscountType())); + otherDiscount.setDiscountName(discount.getDiscountName()); + otherDiscount.setDiscountAmount(discount.getDiscountAmount()); + otherDiscount.setSortOrder(discount.getSortOrder()); + otherDiscount.setCreateTime(now); + + orderDiscountMapper.insert(otherDiscount); + log.info("其他优惠记录保存成功: orderId={}, type={}, discountAmount={}", + orderId, discount.getDiscountType(), discount.getDiscountAmount()); + } + } + + log.info("订单创建完成: orderId={}, orderNo={}, finalAmount={}", + orderId, orderNo, priceResult.getFinalAmount()); + + return orderId; + } + + @Override + public OrderV2 getByOrderNo(String orderNo) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("order_no", orderNo); + queryWrapper.eq("deleted", 0); + return orderMapper.selectOne(queryWrapper); + } + + @Override + public OrderV2 getById(Long orderId) { + return orderMapper.selectById(orderId); + } + + @Override + public boolean updateOrderStatus(Long orderId, String orderStatus, String paymentStatus) { + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setOrderStatus(OrderStatus.fromCode(orderStatus)); + order.setPaymentStatus(PaymentStatus.fromCode(paymentStatus)); + order.setUpdateTime(new Date()); + + if ("PAID".equals(paymentStatus)) { + order.setPayTime(new Date()); + } + + if ("COMPLETED".equals(orderStatus)) { + order.setCompleteTime(new Date()); + } + + return orderMapper.updateById(order) > 0; + } + + @Override + public String generateOrderNo() { + // 生成订单号:ORDER + 时间戳 + 3位随机数 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + String timestamp = sdf.format(new Date()); + int random = (int) (Math.random() * 900) + 100; + return "ORDER" + timestamp + random; + } + + /** + * 根据商品类型和ID获取商品名称 + * TODO: 实际应该从商品配置表或其他数据源获取 + */ + private String getProductName(ProductItem product) { + return product.getProductType().getDescription() + "-" + product.getProductId(); + } + + // ====== 新增方法实现 ====== + + @Override + public PageInfo pageOrders(OrderV2PageRequest request) { + PageHelper.startPage(request.getPageNum(), request.getPageSize()); + + QueryWrapper queryWrapper = buildOrderQueryWrapper(request); + List orders = orderMapper.selectList(queryWrapper); + + // 转换为响应DTO + List responseList = orders.stream() + .map(this::convertToListResponse) + .collect(Collectors.toList()); + + return new PageInfo<>(responseList); + } + + @Override + public OrderV2DetailResponse getOrderDetail(Long orderId) { + // 查询订单基础信息 + OrderV2 order = orderMapper.selectById(orderId); + if (order == null || order.getDeleted() == 1) { + return null; + } + + // 查询订单商品明细 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", orderId); + List orderItems = orderItemMapper.selectList(itemQuery); + + // 查询订单优惠记录 + QueryWrapper discountQuery = new QueryWrapper<>(); + discountQuery.eq("order_id", orderId).orderByAsc("sort_order"); + List orderDiscounts = orderDiscountMapper.selectList(discountQuery); + + // 查询订单退款记录 + QueryWrapper refundQuery = new QueryWrapper<>(); + refundQuery.eq("order_id", orderId).orderByDesc("create_time"); + List orderRefunds = orderRefundMapper.selectList(refundQuery); + + // 转换为响应DTO + return convertToDetailResponse(order, orderItems, orderDiscounts, orderRefunds); + } + + @Override + @Transactional + public boolean updateOrderRemarks(Long orderId, String remarks) { + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setRemarks(remarks); + order.setUpdateTime(new Date()); + + return orderMapper.updateById(order) > 0; + } + + @Override + @Transactional + public boolean updatePaymentStatus(Long orderId, String paymentStatus) { + // 先查询当前订单状态 + OrderV2 currentOrder = orderMapper.selectById(orderId); + if (currentOrder == null) { + log.warn("订单不存在: orderId={}", orderId); + return false; + } + + PaymentStatus oldPaymentStatus = currentOrder.getPaymentStatus(); + PaymentStatus newPaymentStatus = PaymentStatus.fromCode(paymentStatus); + + // 更新订单支付状态 + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setPaymentStatus(newPaymentStatus); + order.setUpdateTime(new Date()); + + if ("PAID".equals(paymentStatus)) { + order.setPayTime(new Date()); + // 支付完成后,订单状态也需要更新 + order.setOrderStatus(OrderStatus.PAID); + } + + boolean success = orderMapper.updateById(order) > 0; + + // 发布支付状态变更事件 + if (success && !oldPaymentStatus.equals(newPaymentStatus)) { + PaymentStatusChangeEvent event = new PaymentStatusChangeEvent( + orderId, currentOrder.getOrderNo(), + oldPaymentStatus, newPaymentStatus, + "支付状态更新", null + ); + orderEventManager.publishPaymentStatusChangeEvent(event); + } + + return success; + } + + @Override + @Transactional + public boolean updateRefundStatus(Long orderId, RefundStatus refundStatus) { + // 先查询当前订单状态 + OrderV2 currentOrder = orderMapper.selectById(orderId); + if (currentOrder == null) { + log.warn("订单不存在: orderId={}", orderId); + return false; + } + + RefundStatus oldRefundStatus = currentOrder.getRefundStatus(); + + // 更新订单退款状态 + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setRefundStatus(refundStatus); + order.setUpdateTime(new Date()); + + // 根据退款状态更新订单状态 + if (RefundStatus.FULL_REFUND.equals(refundStatus)) { + order.setOrderStatus(OrderStatus.REFUNDED); + } else if (RefundStatus.REFUND_PROCESSING.equals(refundStatus)) { + order.setOrderStatus(OrderStatus.REFUNDING); + } + + boolean success = orderMapper.updateById(order) > 0; + + // 发布退款状态变更事件 + if (success && !oldRefundStatus.equals(refundStatus)) { + RefundStatusChangeEvent event = new RefundStatusChangeEvent( + orderId, currentOrder.getOrderNo(), null, null, + oldRefundStatus, refundStatus, null, + "退款状态更新", null + ); + orderEventManager.publishRefundStatusChangeEvent(event); + } + + return success; + } + + @Override + @Transactional + public Long createRefundRecord(RefundRequest request) { + Date now = new Date(); + + // 生成退款单号 + String refundNo = generateRefundNo(); + + // 创建退款记录 + OrderRefundV2 refund = new OrderRefundV2(); + refund.setOrderId(request.getOrderId()); + refund.setRefundNo(refundNo); + refund.setRefundType(RefundType.fromCode(request.getRefundType())); + refund.setRefundAmount(request.getRefundAmount()); + refund.setRefundFee(request.getRefundFee()); + refund.setRefundStatus(com.ycwl.basic.order.enums.RefundStatus.REFUND_PROCESSING); + refund.setRefundReason(request.getRefundReason()); + refund.setRefundDescription(request.getRefundDescription()); + refund.setOperatorRemarks(request.getOperatorRemarks()); + refund.setRefundChannel(request.getRefundChannel()); + refund.setApplyTime(now); + refund.setCreateTime(now); + refund.setUpdateTime(now); + + orderRefundMapper.insert(refund); + + // 更新订单退款状态 + updateRefundStatus(request.getOrderId(), com.ycwl.basic.order.enums.RefundStatus.REFUND_PROCESSING); + + log.info("退款记录创建成功: refundId={}, refundNo={}, orderId={}, refundAmount={}", + refund.getId(), refundNo, request.getOrderId(), request.getRefundAmount()); + + return refund.getId(); + } + + @Override + public PageInfo pageOrdersByUser(OrderV2PageRequest request) { + // 移动端查询,确保只查询当前用户的订单 + return pageOrders(request); + } + + // ====== 私有辅助方法 ====== + + /** + * 构建订单查询条件 + */ + private QueryWrapper buildOrderQueryWrapper(OrderV2PageRequest request) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("deleted", 0); + + if (request.getOrderNo() != null && !request.getOrderNo().trim().isEmpty()) { + queryWrapper.like("order_no", request.getOrderNo().trim()); + } + + if (request.getMemberId() != null) { + queryWrapper.eq("member_id", request.getMemberId()); + } + + if (request.getScenicId() != null) { + queryWrapper.eq("scenic_id", request.getScenicId()); + } + + if (request.getOrderStatus() != null && !request.getOrderStatus().trim().isEmpty()) { + queryWrapper.eq("order_status", request.getOrderStatus().trim()); + } + + if (request.getPaymentStatus() != null && !request.getPaymentStatus().trim().isEmpty()) { + queryWrapper.eq("payment_status", request.getPaymentStatus().trim()); + } + + if (request.getRefundStatus() != null && !request.getRefundStatus().trim().isEmpty()) { + queryWrapper.eq("refund_status", request.getRefundStatus().trim()); + } + + if (request.getCreateTimeStart() != null) { + queryWrapper.ge("create_time", request.getCreateTimeStart()); + } + + if (request.getCreateTimeEnd() != null) { + queryWrapper.le("create_time", request.getCreateTimeEnd()); + } + + if (request.getPayTimeStart() != null) { + queryWrapper.ge("pay_time", request.getPayTimeStart()); + } + + if (request.getPayTimeEnd() != null) { + queryWrapper.le("pay_time", request.getPayTimeEnd()); + } + + queryWrapper.orderByDesc("create_time"); + + return queryWrapper; + } + + /** + * 转换为列表响应DTO + */ + private OrderV2ListResponse convertToListResponse(OrderV2 order) { + OrderV2ListResponse response = new OrderV2ListResponse(); + response.setId(order.getId()); + response.setOrderNo(order.getOrderNo()); + response.setMemberId(order.getMemberId()); + response.setOpenId(order.getOpenId()); + response.setFaceId(order.getFaceId()); + response.setScenicId(order.getScenicId()); + response.setOriginalAmount(order.getOriginalAmount()); + response.setDiscountAmount(order.getDiscountAmount()); + response.setFinalAmount(order.getFinalAmount()); + response.setOrderStatus(order.getOrderStatus()); + response.setOrderStatusDesc(order.getOrderStatus().getDescription()); + response.setPaymentStatus(order.getPaymentStatus()); + response.setPaymentStatusDesc(order.getPaymentStatus().getDescription()); + response.setRefundStatus(order.getRefundStatus()); + response.setRefundStatusDesc(order.getRefundStatus().getDescription()); + response.setTotalRefundAmount(order.getTotalRefundAmount()); + response.setRemarks(order.getRemarks()); + response.setPayTime(order.getPayTime()); + response.setCompleteTime(order.getCompleteTime()); + response.setCreateTime(order.getCreateTime()); + response.setUpdateTime(order.getUpdateTime()); + + // 查询商品数量信息 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", order.getId()); + List items = orderItemMapper.selectList(itemQuery); + response.setItemCount(items.size()); + response.setTotalQuantity(items.stream().mapToInt(OrderItemV2::getQuantity).sum()); + + return response; + } + + /** + * 转换为详情响应DTO + */ + private OrderV2DetailResponse convertToDetailResponse(OrderV2 order, + List orderItems, + List orderDiscounts, + List orderRefunds) { + OrderV2DetailResponse response = new OrderV2DetailResponse(); + response.setId(order.getId()); + response.setOrderNo(order.getOrderNo()); + response.setMemberId(order.getMemberId()); + response.setOpenId(order.getOpenId()); + response.setFaceId(order.getFaceId()); + response.setScenicId(order.getScenicId()); + response.setOriginalAmount(order.getOriginalAmount()); + response.setDiscountAmount(order.getDiscountAmount()); + response.setFinalAmount(order.getFinalAmount()); + response.setOrderStatus(order.getOrderStatus()); + response.setOrderStatusDesc(order.getOrderStatus().getDescription()); + response.setPaymentStatus(order.getPaymentStatus()); + response.setPaymentStatusDesc(order.getPaymentStatus().getDescription()); + response.setRefundStatus(order.getRefundStatus()); + response.setRefundStatusDesc(order.getRefundStatus().getDescription()); + response.setTotalRefundAmount(order.getTotalRefundAmount()); + response.setRemarks(order.getRemarks()); + response.setPayTime(order.getPayTime()); + response.setCompleteTime(order.getCompleteTime()); + response.setCreateTime(order.getCreateTime()); + response.setUpdateTime(order.getUpdateTime()); + response.setCreateBy(order.getCreateBy()); + response.setUpdateBy(order.getUpdateBy()); + + response.setOrderItems(orderItems); + response.setOrderDiscounts(orderDiscounts); + response.setOrderRefunds(orderRefunds); + + return response; + } + + /** + * 生成退款单号 + */ + private String generateRefundNo() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + String timestamp = sdf.format(new Date()); + int random = (int) (Math.random() * 900) + 100; + return "REFUND" + timestamp + random; + } +} \ No newline at end of file From 346c484cbc41fdb3f17ae9e8812a28151712dacc Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 29 Aug 2025 12:41:12 +0800 Subject: [PATCH 24/32] =?UTF-8?q?refactor(order):=20=E5=B0=86=20OrderMappe?= =?UTF-8?q?r=20=E9=87=8D=E5=91=BD=E5=90=8D=E4=B8=BA=20OrderV2Mapper-=20?= =?UTF-8?q?=E5=B0=86=20OrderMapper=20=E6=8E=A5=E5=8F=A3=E9=87=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E4=B8=BA=20OrderV2Mapper=20-=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E4=BA=86=E7=9B=B8=E5=85=B3=E6=9C=8D=E5=8A=A1=E7=B1=BB=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=20Mapper=E5=BC=95=E7=94=A8=20-=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=BA=86=20OrderServiceImpl=20=E4=B8=AD=E7=9A=84=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E5=90=8D=E4=BB=8E=20orderMapper=E6=94=B9=E4=B8=BA=20o?= =?UTF-8?q?rderV2Mapper=20-=20=E6=9B=B4=E6=96=B0=E4=BA=86=E4=B8=8E?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E7=9B=B8=E5=85=B3=E7=9A=84=E6=89=80=E6=9C=89?= =?UTF-8?q?=E6=96=B9=E6=B3=95=E4=B8=AD=E5=AF=B9=20Mapper=20=E7=9A=84?= =?UTF-8?q?=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{OrderMapper.java => OrderV2Mapper.java} | 2 +- .../order/service/impl/OrderServiceImpl.java | 26 +++++++++---------- .../service/pc/impl/OrderServiceImpl.java | 4 ++- 3 files changed, 17 insertions(+), 15 deletions(-) rename src/main/java/com/ycwl/basic/order/mapper/{OrderMapper.java => OrderV2Mapper.java} (78%) diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderV2Mapper.java similarity index 78% rename from src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java rename to src/main/java/com/ycwl/basic/order/mapper/OrderV2Mapper.java index 624a043..a6031a2 100644 --- a/src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderV2Mapper.java @@ -8,5 +8,5 @@ import org.apache.ibatis.annotations.Mapper; * 订单表Mapper接口 */ @Mapper -public interface OrderMapper extends BaseMapper { +public interface OrderV2Mapper extends BaseMapper { } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index 191c372..75cc371 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -12,7 +12,7 @@ import com.ycwl.basic.order.entity.OrderV2; import com.ycwl.basic.order.enums.*; import com.ycwl.basic.order.mapper.OrderDiscountMapper; import com.ycwl.basic.order.mapper.OrderItemMapper; -import com.ycwl.basic.order.mapper.OrderMapper; +import com.ycwl.basic.order.mapper.OrderV2Mapper; import com.ycwl.basic.order.mapper.OrderRefundMapper; import com.ycwl.basic.order.service.IOrderService; import com.ycwl.basic.order.event.*; @@ -39,7 +39,7 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class OrderServiceImpl implements IOrderService { - private final OrderMapper orderMapper; + private final OrderV2Mapper orderV2Mapper; private final OrderItemMapper orderItemMapper; private final OrderDiscountMapper orderDiscountMapper; private final OrderRefundMapper orderRefundMapper; @@ -78,7 +78,7 @@ public class OrderServiceImpl implements IOrderService { order.setDeleted(0); // 保存订单 - orderMapper.insert(order); + orderV2Mapper.insert(order); Long orderId = order.getId(); log.info("订单基础信息创建成功: orderId={}, orderNo={}", orderId, orderNo); @@ -168,12 +168,12 @@ public class OrderServiceImpl implements IOrderService { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("order_no", orderNo); queryWrapper.eq("deleted", 0); - return orderMapper.selectOne(queryWrapper); + return orderV2Mapper.selectOne(queryWrapper); } @Override public OrderV2 getById(Long orderId) { - return orderMapper.selectById(orderId); + return orderV2Mapper.selectById(orderId); } @Override @@ -192,7 +192,7 @@ public class OrderServiceImpl implements IOrderService { order.setCompleteTime(new Date()); } - return orderMapper.updateById(order) > 0; + return orderV2Mapper.updateById(order) > 0; } @Override @@ -219,7 +219,7 @@ public class OrderServiceImpl implements IOrderService { PageHelper.startPage(request.getPageNum(), request.getPageSize()); QueryWrapper queryWrapper = buildOrderQueryWrapper(request); - List orders = orderMapper.selectList(queryWrapper); + List orders = orderV2Mapper.selectList(queryWrapper); // 转换为响应DTO List responseList = orders.stream() @@ -232,7 +232,7 @@ public class OrderServiceImpl implements IOrderService { @Override public OrderV2DetailResponse getOrderDetail(Long orderId) { // 查询订单基础信息 - OrderV2 order = orderMapper.selectById(orderId); + OrderV2 order = orderV2Mapper.selectById(orderId); if (order == null || order.getDeleted() == 1) { return null; } @@ -264,14 +264,14 @@ public class OrderServiceImpl implements IOrderService { order.setRemarks(remarks); order.setUpdateTime(new Date()); - return orderMapper.updateById(order) > 0; + return orderV2Mapper.updateById(order) > 0; } @Override @Transactional public boolean updatePaymentStatus(Long orderId, String paymentStatus) { // 先查询当前订单状态 - OrderV2 currentOrder = orderMapper.selectById(orderId); + OrderV2 currentOrder = orderV2Mapper.selectById(orderId); if (currentOrder == null) { log.warn("订单不存在: orderId={}", orderId); return false; @@ -292,7 +292,7 @@ public class OrderServiceImpl implements IOrderService { order.setOrderStatus(OrderStatus.PAID); } - boolean success = orderMapper.updateById(order) > 0; + boolean success = orderV2Mapper.updateById(order) > 0; // 发布支付状态变更事件 if (success && !oldPaymentStatus.equals(newPaymentStatus)) { @@ -311,7 +311,7 @@ public class OrderServiceImpl implements IOrderService { @Transactional public boolean updateRefundStatus(Long orderId, RefundStatus refundStatus) { // 先查询当前订单状态 - OrderV2 currentOrder = orderMapper.selectById(orderId); + OrderV2 currentOrder = orderV2Mapper.selectById(orderId); if (currentOrder == null) { log.warn("订单不存在: orderId={}", orderId); return false; @@ -332,7 +332,7 @@ public class OrderServiceImpl implements IOrderService { order.setOrderStatus(OrderStatus.REFUNDING); } - boolean success = orderMapper.updateById(order) > 0; + boolean success = orderV2Mapper.updateById(order) > 0; // 发布退款状态变更事件 if (success && !oldRefundStatus.equals(refundStatus)) { diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/OrderServiceImpl.java index 1832c1c..6c12663 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/OrderServiceImpl.java @@ -57,6 +57,7 @@ import com.ycwl.basic.utils.SnowFlakeUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.stereotype.Service; @@ -78,7 +79,7 @@ import java.util.stream.Collectors; * @Date:2024/12/3 13:54 */ @Slf4j -@Service +@Service("OrderServiceOldImpl") @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) public class OrderServiceImpl implements OrderService { @@ -86,6 +87,7 @@ public class OrderServiceImpl implements OrderService { private OrderMapper orderMapper; @Autowired + @Qualifier(value = "OrderServiceOldImpl") private OrderServiceImpl self; @Autowired private SourceMapper sourceMapper; From 3fbfb7df541a16db5e5452c62d208b9f91e99166 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 29 Aug 2025 13:49:30 +0800 Subject: [PATCH 25/32] =?UTF-8?q?feat(coupon):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BC=98=E6=83=A0=E5=88=B8=E9=A2=86=E5=8F=96=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CouponClaimRequest 和 CouponClaimResult 类用于处理优惠券领取请求和结果 - 在 ICouponService 接口中添加 claimCoupon 方法 - 在 CouponServiceImpl 中实现 claimCoupon 方法,包括参数验证、优惠券查询、库存检查、记录创建等步骤 - 优化日志记录和异常处理 --- .../basic/pricing/dto/CouponClaimRequest.java | 51 +++++++++ .../basic/pricing/dto/CouponClaimResult.java | 100 ++++++++++++++++++ .../basic/pricing/service/ICouponService.java | 10 ++ .../service/impl/CouponServiceImpl.java | 86 +++++++++++++++ 4 files changed, 247 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/pricing/dto/CouponClaimRequest.java create mode 100644 src/main/java/com/ycwl/basic/pricing/dto/CouponClaimResult.java diff --git a/src/main/java/com/ycwl/basic/pricing/dto/CouponClaimRequest.java b/src/main/java/com/ycwl/basic/pricing/dto/CouponClaimRequest.java new file mode 100644 index 0000000..fc27050 --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/CouponClaimRequest.java @@ -0,0 +1,51 @@ +package com.ycwl.basic.pricing.dto; + +import lombok.Data; + +/** + * 优惠券领取请求DTO + */ +@Data +public class CouponClaimRequest { + + /** + * 用户ID + */ + private Long userId; + + /** + * 优惠券ID + */ + private Long couponId; + + /** + * 景区ID(可选,记录在哪个景区领取) + */ + private String scenicId; + + /** + * 领取来源(可选,如:活动页面、签到奖励等) + */ + private String claimSource; + + public CouponClaimRequest() { + } + + public CouponClaimRequest(Long userId, Long couponId) { + this.userId = userId; + this.couponId = couponId; + } + + public CouponClaimRequest(Long userId, Long couponId, String scenicId) { + this.userId = userId; + this.couponId = couponId; + this.scenicId = scenicId; + } + + public CouponClaimRequest(Long userId, Long couponId, String scenicId, String claimSource) { + this.userId = userId; + this.couponId = couponId; + this.scenicId = scenicId; + this.claimSource = claimSource; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/dto/CouponClaimResult.java b/src/main/java/com/ycwl/basic/pricing/dto/CouponClaimResult.java new file mode 100644 index 0000000..d9173ed --- /dev/null +++ b/src/main/java/com/ycwl/basic/pricing/dto/CouponClaimResult.java @@ -0,0 +1,100 @@ +package com.ycwl.basic.pricing.dto; + +import com.ycwl.basic.pricing.entity.PriceCouponClaimRecord; +import lombok.Data; + +import java.util.Date; + +/** + * 优惠券领取结果DTO + */ +@Data +public class CouponClaimResult { + + /** + * 是否成功 + */ + private boolean success; + + /** + * 错误消息(失败时) + */ + private String errorMessage; + + /** + * 错误码(失败时) + */ + private String errorCode; + + /** + * 领取记录ID(成功时) + */ + private Long claimRecordId; + + /** + * 优惠券ID + */ + private Long couponId; + + /** + * 优惠券名称 + */ + private String couponName; + + /** + * 领取时间 + */ + private Date claimTime; + + /** + * 用户ID + */ + private Long userId; + + /** + * 景区ID + */ + private String scenicId; + + /** + * 创建成功结果 + */ + public static CouponClaimResult success(PriceCouponClaimRecord record, String couponName) { + CouponClaimResult result = new CouponClaimResult(); + result.success = true; + result.claimRecordId = record.getId(); + result.couponId = record.getCouponId(); + result.couponName = couponName; + result.claimTime = record.getClaimTime(); + result.userId = record.getUserId(); + result.scenicId = record.getScenicId(); + return result; + } + + /** + * 创建失败结果 + */ + public static CouponClaimResult failure(String errorCode, String errorMessage) { + CouponClaimResult result = new CouponClaimResult(); + result.success = false; + result.errorCode = errorCode; + result.errorMessage = errorMessage; + return result; + } + + /** + * 创建失败结果(仅错误消息) + */ + public static CouponClaimResult failure(String errorMessage) { + return failure("CLAIM_FAILED", errorMessage); + } + + // 常用错误码常量 + public static final String ERROR_COUPON_NOT_FOUND = "COUPON_NOT_FOUND"; + public static final String ERROR_COUPON_EXPIRED = "COUPON_EXPIRED"; + public static final String ERROR_COUPON_INACTIVE = "COUPON_INACTIVE"; + public static final String ERROR_COUPON_OUT_OF_STOCK = "COUPON_OUT_OF_STOCK"; + public static final String ERROR_ALREADY_CLAIMED = "ALREADY_CLAIMED"; + public static final String ERROR_INVALID_PARAMS = "INVALID_PARAMS"; + public static final String ERROR_SYSTEM_ERROR = "SYSTEM_ERROR"; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/service/ICouponService.java b/src/main/java/com/ycwl/basic/pricing/service/ICouponService.java index 3acd166..21a84ff 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/ICouponService.java +++ b/src/main/java/com/ycwl/basic/pricing/service/ICouponService.java @@ -3,6 +3,8 @@ package com.ycwl.basic.pricing.service; import com.ycwl.basic.pricing.dto.CouponInfo; import com.ycwl.basic.pricing.dto.CouponUseRequest; import com.ycwl.basic.pricing.dto.CouponUseResult; +import com.ycwl.basic.pricing.dto.CouponClaimRequest; +import com.ycwl.basic.pricing.dto.CouponClaimResult; import com.ycwl.basic.pricing.dto.ProductItem; import com.ycwl.basic.pricing.entity.PriceCouponConfig; @@ -59,4 +61,12 @@ public interface ICouponService { * @return 可用优惠券列表 */ List getUserAvailableCoupons(Long userId); + + /** + * 领取优惠券(内部调用方法) + * + * @param request 领取请求 + * @return 领取结果 + */ + CouponClaimResult claimCoupon(CouponClaimRequest request); } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/CouponServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/CouponServiceImpl.java index 82bbc13..7235076 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/CouponServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/CouponServiceImpl.java @@ -196,4 +196,90 @@ public class CouponServiceImpl implements ICouponService { info.setActualDiscountAmount(actualDiscountAmount); return info; } + + @Override + @Transactional + public CouponClaimResult claimCoupon(CouponClaimRequest request) { + log.info("开始领取优惠券: userId={}, couponId={}, scenicId={}", + request.getUserId(), request.getCouponId(), request.getScenicId()); + + try { + // 1. 参数验证 + if (request.getUserId() == null || request.getCouponId() == null) { + return CouponClaimResult.failure(CouponClaimResult.ERROR_INVALID_PARAMS, "用户ID和优惠券ID不能为空"); + } + + // 2. 查询优惠券配置 + PriceCouponConfig coupon = couponConfigMapper.selectById(request.getCouponId()); + if (coupon == null || coupon.getDeleted() == 1) { + return CouponClaimResult.failure(CouponClaimResult.ERROR_COUPON_NOT_FOUND, "优惠券不存在"); + } + + // 3. 检查优惠券是否启用 + if (!Boolean.TRUE.equals(coupon.getIsActive())) { + return CouponClaimResult.failure(CouponClaimResult.ERROR_COUPON_INACTIVE, "优惠券已停用"); + } + + // 4. 检查优惠券有效期 + LocalDateTime now = LocalDateTime.now(); + if (coupon.getValidFrom() != null && now.isBefore(coupon.getValidFrom())) { + return CouponClaimResult.failure(CouponClaimResult.ERROR_COUPON_EXPIRED, "优惠券尚未生效"); + } + if (coupon.getValidUntil() != null && now.isAfter(coupon.getValidUntil())) { + return CouponClaimResult.failure(CouponClaimResult.ERROR_COUPON_EXPIRED, "优惠券已过期"); + } + + // 5. 检查库存(如果有总量限制) + if (coupon.getTotalQuantity() != null && coupon.getUsedQuantity() != null) { + if (coupon.getUsedQuantity() >= coupon.getTotalQuantity()) { + return CouponClaimResult.failure(CouponClaimResult.ERROR_COUPON_OUT_OF_STOCK, "优惠券已领完"); + } + } + + // 6. 检查用户是否已经领取过该优惠券 + PriceCouponClaimRecord existingRecord = couponClaimRecordMapper.selectUserCouponRecord( + request.getUserId(), request.getCouponId()); + if (existingRecord != null) { + return CouponClaimResult.failure(CouponClaimResult.ERROR_ALREADY_CLAIMED, "您已经领取过该优惠券"); + } + + // 7. 创建领取记录 + Date claimTime = new Date(); + PriceCouponClaimRecord claimRecord = new PriceCouponClaimRecord(); + claimRecord.setCouponId(request.getCouponId()); + claimRecord.setUserId(request.getUserId()); + claimRecord.setClaimTime(claimTime); + claimRecord.setStatus(CouponStatus.CLAIMED); + claimRecord.setScenicId(request.getScenicId()); + claimRecord.setCreateTime(claimTime); + claimRecord.setUpdateTime(claimTime); + claimRecord.setDeleted(0); + + // 8. 插入领取记录 + int insertResult = couponClaimRecordMapper.insert(claimRecord); + if (insertResult <= 0) { + log.error("插入优惠券领取记录失败: userId={}, couponId={}", + request.getUserId(), request.getCouponId()); + return CouponClaimResult.failure(CouponClaimResult.ERROR_SYSTEM_ERROR, "领取失败,请稍后重试"); + } + + // 9. 更新优惠券已使用数量(如果有总量限制) + if (coupon.getTotalQuantity() != null) { + int updatedUsedQuantity = (coupon.getUsedQuantity() == null ? 0 : coupon.getUsedQuantity()) + 1; + coupon.setUsedQuantity(updatedUsedQuantity); + couponConfigMapper.updateById(coupon); + } + + log.info("优惠券领取成功: userId={}, couponId={}, claimRecordId={}", + request.getUserId(), request.getCouponId(), claimRecord.getId()); + + // 10. 返回成功结果 + return CouponClaimResult.success(claimRecord, coupon.getCouponName()); + + } catch (Exception e) { + log.error("领取优惠券失败: userId={}, couponId={}", + request.getUserId(), request.getCouponId(), e); + return CouponClaimResult.failure(CouponClaimResult.ERROR_SYSTEM_ERROR, "系统错误,领取失败:" + e.getMessage()); + } + } } \ No newline at end of file From 4dac46bb46afee4fb564e1551911781de721f266 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 29 Aug 2025 14:50:49 +0800 Subject: [PATCH 26/32] =?UTF-8?q?refactor(order):=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E4=BC=98=E6=83=A0=E6=8E=92=E5=BA=8F=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将券码优惠的排序顺序从 1 调整为 2,使其显示顺序低于限时立减- 将限时立减优惠的排序顺序从 2 调整为 1,使其显示在最前面 --- .../com/ycwl/basic/order/service/impl/OrderServiceImpl.java | 2 +- src/main/java/com/ycwl/basic/pricing/dto/DiscountDetail.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index 75cc371..c1a02e7 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -126,7 +126,7 @@ public class OrderServiceImpl implements IOrderService { voucherDiscount.setDiscountType(DiscountType.VOUCHER); voucherDiscount.setDiscountName(voucherInfo.getBatchName()); voucherDiscount.setDiscountAmount(voucherInfo.getActualDiscountAmount()); - voucherDiscount.setSortOrder(1); // 券码显示顺序最高 + voucherDiscount.setSortOrder(2); // 券码显示顺序低于限时立减 voucherDiscount.setVoucherCode(voucherInfo.getVoucherCode()); voucherDiscount.setCreateTime(now); diff --git a/src/main/java/com/ycwl/basic/pricing/dto/DiscountDetail.java b/src/main/java/com/ycwl/basic/pricing/dto/DiscountDetail.java index e7bacc8..1ffff6c 100644 --- a/src/main/java/com/ycwl/basic/pricing/dto/DiscountDetail.java +++ b/src/main/java/com/ycwl/basic/pricing/dto/DiscountDetail.java @@ -44,7 +44,7 @@ public class DiscountDetail { detail.setDiscountName("限时立减"); detail.setDiscountAmount(discountAmount); detail.setDescription("限时优惠,立即享受"); - detail.setSortOrder(2); // 限时立减排在券码后面 + detail.setSortOrder(1); // 限时立减排在最前面 return detail; } @@ -70,7 +70,7 @@ public class DiscountDetail { detail.setDiscountName("券码优惠"); detail.setDiscountAmount(discountAmount); detail.setDescription(String.format("券码 %s - %s", voucherCode, discountTypeName)); - detail.setSortOrder(1); // 券码优先级最高,排在最前面 + detail.setSortOrder(2); // 券码显示顺序低于限时立减 return detail; } From bc2b2fb10fd2b2ad7c8f31dfd0754602ff43f409 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 29 Aug 2025 14:51:24 +0800 Subject: [PATCH 27/32] =?UTF-8?q?refactor(basic):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E4=B8=8B=E5=8D=95=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 "/add-order" 路径修改为 "/add" - 优化接口路径,使其更简洁 --- .../com/ycwl/basic/controller/mobile/AppOrderV2Controller.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java index 5d87e2d..6c00ea0 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java @@ -97,7 +97,7 @@ public class AppOrderV2Controller { * 移动端下单接口 * 验证价格缓存有效性,确保5分钟内使用缓存价格下单 */ - @PostMapping("/add-order") + @PostMapping("/add") public ApiResponse addOrder(@RequestBody MobileOrderRequest request) { // 获取当前登录用户ID String currentUserIdStr = BaseContextHandler.getUserId(); From 5a66856e728d1af109e43a7a41a2232134f5ed22 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 29 Aug 2025 15:32:47 +0800 Subject: [PATCH 28/32] =?UTF-8?q?feat(order):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E7=9B=B8=E5=85=B3=E6=8E=A5=E5=8F=A3=E5=92=8C?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增获取支付参数接口和处理支付回调接口 - 实现支付参数获取和支付回调处理的逻辑 - 添加支付相关数据传输对象(DTO) - 修改订单服务接口和实现类,增加支付相关方法 --- .../mobile/AppOrderV2Controller.java | 66 +++++- .../order/dto/PaymentCallbackResponse.java | 70 ++++++ .../basic/order/dto/PaymentParamsRequest.java | 17 ++ .../order/dto/PaymentParamsResponse.java | 76 +++++++ .../basic/order/service/IOrderService.java | 26 +++ .../order/service/impl/OrderServiceImpl.java | 210 +++++++++++++++++- 6 files changed, 461 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/order/dto/PaymentCallbackResponse.java create mode 100644 src/main/java/com/ycwl/basic/order/dto/PaymentParamsRequest.java create mode 100644 src/main/java/com/ycwl/basic/order/dto/PaymentParamsResponse.java diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java index 6c00ea0..e84ade6 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java @@ -13,10 +13,14 @@ import com.ycwl.basic.order.service.IOrderService; import com.ycwl.basic.order.dto.OrderV2DetailResponse; import com.ycwl.basic.order.dto.OrderV2ListResponse; import com.ycwl.basic.order.dto.OrderV2PageRequest; +import com.ycwl.basic.order.dto.PaymentParamsRequest; +import com.ycwl.basic.order.dto.PaymentParamsResponse; +import com.ycwl.basic.order.dto.PaymentCallbackResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; +import jakarta.servlet.http.HttpServletRequest; import java.math.BigDecimal; /** @@ -142,14 +146,14 @@ public class AppOrderV2Controller { if (cachedResult == null) { log.warn("移动端下单:价格缓存已过期或不存在, userId={}, scenicId={}", currentUserId, scenicId); - return ApiResponse.fail("价格信息已过期,请重新查询价格后再下单"); + return ApiResponse.fail("请重新下单!"); } // 验证价格是否匹配 if (cachedResult.getFinalAmount().compareTo(request.getExpectedFinalAmount()) != 0) { log.warn("移动端下单:价格不匹配, cached={}, expected={}, userId={}, scenicId={}", cachedResult.getFinalAmount(), request.getExpectedFinalAmount(), currentUserId, scenicId); - return ApiResponse.fail("价格信息不匹配,请重新查询价格后再下单"); + return ApiResponse.fail("请重新下单!"); } // 验证原价是否匹配(可选) @@ -239,4 +243,62 @@ public class AppOrderV2Controller { return ApiResponse.fail("查询失败:" + e.getMessage()); } } + + // ====== 支付相关接口 ====== + + /** + * 获取订单支付参数 + * 用于小程序调起支付 + */ + @PostMapping("/{orderId}/payment-params") + public ApiResponse getPaymentParams( + @PathVariable("orderId") Long orderId, + @RequestBody PaymentParamsRequest request) { + + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("用户未登录"); + return ApiResponse.fail("用户未登录"); + } + + Long currentUserId = Long.valueOf(currentUserIdStr); + + log.info("获取支付参数: userId={}, orderId={}", currentUserId, orderId); + + try { + PaymentParamsResponse response = orderService.getPaymentParams(orderId, currentUserId, request); + return ApiResponse.success(response); + } catch (Exception e) { + log.error("获取支付参数失败: userId={}, orderId={}", currentUserId, orderId, e); + return ApiResponse.fail(e.getMessage()); + } + } + + /** + * 支付回调处理接口 + * 供第三方支付平台回调使用 + */ + @PostMapping("/payment/callback/{scenicId}") + public String handlePaymentCallback( + @PathVariable("scenicId") Long scenicId, + HttpServletRequest request) { + + log.info("接收支付回调: scenicId={}", scenicId); + + try { + PaymentCallbackResponse response = orderService.handlePaymentCallback(scenicId, request); + + if (response.isSuccess()) { + log.info("支付回调处理成功: scenicId={}, orderId={}, statusChangeType={}", + scenicId, response.getOrderId(), response.getStatusChangeType()); + return "SUCCESS"; // 返回给第三方支付平台的成功标识 + } else { + log.error("支付回调处理失败: scenicId={}, message={}", scenicId, response.getMessage()); + return "FAIL"; // 返回给第三方支付平台的失败标识 + } + } catch (Exception e) { + log.error("支付回调异常: scenicId={}", scenicId, e); + return "FAIL"; + } + } } diff --git a/src/main/java/com/ycwl/basic/order/dto/PaymentCallbackResponse.java b/src/main/java/com/ycwl/basic/order/dto/PaymentCallbackResponse.java new file mode 100644 index 0000000..9ba7164 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/PaymentCallbackResponse.java @@ -0,0 +1,70 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +/** + * 支付回调响应DTO + */ +@Data +public class PaymentCallbackResponse { + + /** + * 处理是否成功 + */ + private boolean success; + + /** + * 响应消息 + */ + private String message; + + /** + * 订单ID + */ + private Long orderId; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 支付状态变化类型 + */ + private String statusChangeType; + + /** + * 创建成功响应 + */ + public static PaymentCallbackResponse success(Long orderId, String orderNo, String statusChangeType) { + PaymentCallbackResponse response = new PaymentCallbackResponse(); + response.success = true; + response.message = "回调处理成功"; + response.orderId = orderId; + response.orderNo = orderNo; + response.statusChangeType = statusChangeType; + return response; + } + + /** + * 创建失败响应 + */ + public static PaymentCallbackResponse failure(String message) { + PaymentCallbackResponse response = new PaymentCallbackResponse(); + response.success = false; + response.message = message; + return response; + } + + /** + * 创建失败响应(包含订单信息) + */ + public static PaymentCallbackResponse failure(String message, Long orderId, String orderNo) { + PaymentCallbackResponse response = new PaymentCallbackResponse(); + response.success = false; + response.message = message; + response.orderId = orderId; + response.orderNo = orderNo; + return response; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/PaymentParamsRequest.java b/src/main/java/com/ycwl/basic/order/dto/PaymentParamsRequest.java new file mode 100644 index 0000000..8f32f4e --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/PaymentParamsRequest.java @@ -0,0 +1,17 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +/** + * 获取支付参数请求DTO + * 所有参数都是可选的,系统会自动生成商品名称和描述 + */ +@Data +public class PaymentParamsRequest { + + // 预留字段,目前所有信息都由系统自动生成 + // 可以在未来版本中扩展支持自定义参数 + + public PaymentParamsRequest() { + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/PaymentParamsResponse.java b/src/main/java/com/ycwl/basic/order/dto/PaymentParamsResponse.java new file mode 100644 index 0000000..9760d68 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/PaymentParamsResponse.java @@ -0,0 +1,76 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Map; + +/** + * 支付参数响应DTO + */ +@Data +public class PaymentParamsResponse { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 支付金额 + */ + private BigDecimal payAmount; + + /** + * 是否需要支付(false表示免费订单) + */ + private Boolean needPay; + + /** + * 支付参数(微信小程序调起支付所需的参数) + * 包含:appId, timeStamp, nonceStr, package, signType, paySign等 + */ + private Map paymentParams; + + /** + * 支付描述信息 + */ + private String description; + + /** + * 商品名称 + */ + private String goodsName; + + /** + * 创建成功的支付参数响应 + */ + public static PaymentParamsResponse success(Long orderId, String orderNo, BigDecimal payAmount, + Boolean needPay, Map paymentParams) { + PaymentParamsResponse response = new PaymentParamsResponse(); + response.orderId = orderId; + response.orderNo = orderNo; + response.payAmount = payAmount; + response.needPay = needPay; + response.paymentParams = paymentParams; + return response; + } + + /** + * 创建免费订单的响应 + */ + public static PaymentParamsResponse free(Long orderId, String orderNo) { + PaymentParamsResponse response = new PaymentParamsResponse(); + response.orderId = orderId; + response.orderNo = orderNo; + response.payAmount = BigDecimal.ZERO; + response.needPay = false; + response.paymentParams = null; + return response; + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/service/IOrderService.java b/src/main/java/com/ycwl/basic/order/service/IOrderService.java index cfa3bed..f41d982 100644 --- a/src/main/java/com/ycwl/basic/order/service/IOrderService.java +++ b/src/main/java/com/ycwl/basic/order/service/IOrderService.java @@ -6,6 +6,11 @@ import com.ycwl.basic.order.dto.*; import com.ycwl.basic.order.entity.OrderV2; import com.ycwl.basic.order.enums.RefundStatus; import com.ycwl.basic.pricing.dto.PriceCalculationResult; +import com.ycwl.basic.order.dto.PaymentParamsRequest; +import com.ycwl.basic.order.dto.PaymentParamsResponse; +import com.ycwl.basic.order.dto.PaymentCallbackResponse; + +import jakarta.servlet.http.HttpServletRequest; /** * 订单服务接口 @@ -116,4 +121,25 @@ public interface IOrderService { * @return 分页结果 */ PageInfo pageOrdersByUser(OrderV2PageRequest request); + + // ====== 支付相关方法 ====== + + /** + * 获取订单支付参数 + * + * @param orderId 订单ID + * @param userId 用户ID(用于权限验证和获取openId) + * @param request 支付参数请求 + * @return 支付参数响应 + */ + PaymentParamsResponse getPaymentParams(Long orderId, Long userId, PaymentParamsRequest request); + + /** + * 处理支付回调 + * + * @param scenicId 景区ID + * @param request HTTP请求对象 + * @return 回调处理结果 + */ + PaymentCallbackResponse handlePaymentCallback(Long scenicId, HttpServletRequest request); } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index c1a02e7..f629a1b 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -4,6 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ycwl.basic.dto.MobileOrderRequest; +import com.ycwl.basic.exception.BaseException; +import com.ycwl.basic.mapper.MemberMapper; +import com.ycwl.basic.model.pc.face.entity.FaceEntity; +import com.ycwl.basic.model.pc.member.resp.MemberRespVO; import com.ycwl.basic.order.dto.*; import com.ycwl.basic.order.entity.OrderDiscountV2; import com.ycwl.basic.order.entity.OrderItemV2; @@ -20,11 +24,19 @@ import com.ycwl.basic.pricing.dto.DiscountDetail; import com.ycwl.basic.pricing.dto.PriceCalculationResult; import com.ycwl.basic.pricing.dto.ProductItem; import com.ycwl.basic.pricing.dto.VoucherInfo; +import com.ycwl.basic.pay.adapter.IPayAdapter; +import com.ycwl.basic.pay.entity.CreateOrderRequest; +import com.ycwl.basic.pay.entity.CreateOrderResponse; +import com.ycwl.basic.pay.entity.PayResponse; +import com.ycwl.basic.repository.FaceRepository; +import com.ycwl.basic.service.pc.ScenicService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import jakarta.servlet.http.HttpServletRequest; + import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; @@ -44,12 +56,15 @@ public class OrderServiceImpl implements IOrderService { private final OrderDiscountMapper orderDiscountMapper; private final OrderRefundMapper orderRefundMapper; private final OrderEventManager orderEventManager; + private final ScenicService scenicService; + private final FaceRepository faceRepository; + private final MemberMapper memberMapper; @Override @Transactional public Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult) { Date now = new Date(); - + MemberRespVO member = memberMapper.getById(userId); // 1. 生成订单号 String orderNo = generateOrderNo(); @@ -57,7 +72,7 @@ public class OrderServiceImpl implements IOrderService { OrderV2 order = new OrderV2(); order.setOrderNo(orderNo); order.setMemberId(userId); - order.setOpenId(request.getFaceId().toString()); // 临时使用faceId,实际应传入openId + order.setOpenId(member.getOpenId()); // 临时使用faceId,实际应传入openId order.setFaceId(request.getFaceId()); order.setScenicId(scenicId); @@ -527,4 +542,195 @@ public class OrderServiceImpl implements IOrderService { int random = (int) (Math.random() * 900) + 100; return "REFUND" + timestamp + random; } + + // ====== 支付相关方法实现 ====== + + @Override + public PaymentParamsResponse getPaymentParams(Long orderId, Long userId, PaymentParamsRequest request) { + log.info("获取支付参数: orderId={}, userId={}", orderId, userId); + + // 1. 查询订单 + OrderV2 order = orderV2Mapper.selectById(orderId); + if (order == null) { + throw new RuntimeException("订单不存在"); + } + + // 2. 验证订单权限 + if (!userId.equals(order.getMemberId())) { + throw new RuntimeException("无权获取该订单的支付参数"); + } + + // 3. 验证订单状态 + if (order.getPaymentStatus() != PaymentStatus.UNPAID) { + throw new RuntimeException("订单状态不允许支付"); + } + + // 4. 获取用户openId(从订单中获取) + String openId = order.getOpenId(); + if (openId == null || openId.trim().isEmpty()) { + throw new RuntimeException("用户openId不存在,无法发起支付"); + } + + // 5. 检查是否为免费订单 + if (order.getFinalAmount().compareTo(BigDecimal.ZERO) == 0) { + log.info("免费订单,无需支付: orderId={}", orderId); + return PaymentParamsResponse.free(orderId, order.getOrderNo()); + } + + // 6. 获取景区支付适配器 + IPayAdapter payAdapter = scenicService.getScenicPayAdapter(order.getScenicId()); + + try { + // 7. 生成商品名称和描述 + String goodsName = generateGoodsName(orderId); + String description = goodsName; // 直接使用商品名称作为描述 + + // 8. 创建支付订单请求 + CreateOrderRequest payRequest = new CreateOrderRequest() + .setOrderNo(order.getOrderNo()) + .setPriceInCents(order.getFinalAmount().multiply(BigDecimal.valueOf(100)).intValue()) + .setDescription(description) + .setGoodsName(goodsName) + .setUserIdentify(openId) + .setNotifyUrl("https://zhentuai.com/api/mobile/order/v2/payment/callback/" + order.getScenicId()); + + // 9. 调用支付适配器创建订单 + CreateOrderResponse payResponse = payAdapter.createOrder(payRequest); + + // 10. 获取支付参数 + java.util.Map paymentParams = payAdapter.getPaymentParams(payResponse); + + // 11. 构建响应 + PaymentParamsResponse response = PaymentParamsResponse.success( + orderId, + order.getOrderNo(), + order.getFinalAmount(), + !payResponse.isSkipPay(), + paymentParams + ); + response.setDescription(description); + response.setGoodsName(goodsName); + + log.info("支付参数生成成功: orderId={}, needPay={}, amount={}", + orderId, !payResponse.isSkipPay(), order.getFinalAmount()); + + return response; + + } catch (Exception e) { + log.error("获取支付参数失败: orderId={}, error={}", orderId, e.getMessage(), e); + throw new RuntimeException("获取支付参数失败: " + e.getMessage()); + } + } + + @Override + public PaymentCallbackResponse handlePaymentCallback(Long scenicId, HttpServletRequest request) { + log.info("处理支付回调: scenicId={}", scenicId); + + try { + // 1. 获取支付适配器 + IPayAdapter payAdapter = scenicService.getScenicPayAdapter(scenicId); + + // 2. 解析回调数据 + PayResponse callbackResponse = payAdapter.handleCallback(request); + log.info("解析支付回调数据: {}", callbackResponse); + + if (callbackResponse == null || callbackResponse.getOrderNo() == null) { + return PaymentCallbackResponse.failure("回调数据解析失败"); + } + + // 3. 查询订单 + String orderNo = callbackResponse.getOrderNo(); + OrderV2 order = getByOrderNo(orderNo); + if (order == null) { + return PaymentCallbackResponse.failure("订单不存在", null, orderNo); + } + + // 4. 处理不同的支付状态变更 + String statusChangeType = null; + if (callbackResponse.isPay()) { + // 支付成功 + statusChangeType = "PAID"; + updatePaymentStatus(order.getId(), PaymentStatus.PAID.name()); + + // 触发支付成功事件 + orderEventManager.publishPaymentStatusChangeEvent( + new PaymentStatusChangeEvent(order.getId(), PaymentStatus.PENDING, PaymentStatus.PAID) + ); + + } else if (callbackResponse.isCancel()) { + // 支付取消 + statusChangeType = "CANCELLED"; + updatePaymentStatus(order.getId(), PaymentStatus.CANCELLED.name()); + + // 触发支付取消事件 + orderEventManager.publishPaymentStatusChangeEvent( + new PaymentStatusChangeEvent(order.getId(), PaymentStatus.PENDING, PaymentStatus.CANCELLED) + ); + + } else if (callbackResponse.isRefund()) { + // 退款 + statusChangeType = "REFUNDED"; + updateRefundStatus(order.getId(), RefundStatus.COMPLETED.name()); + + // 触发退款事件 + orderEventManager.publishRefundStatusChangeEvent( + new RefundStatusChangeEvent(order.getId(), RefundStatus.PENDING, RefundStatus.COMPLETED) + ); + } + + log.info("支付回调处理成功: orderId={}, orderNo={}, statusChangeType={}", + order.getId(), orderNo, statusChangeType); + + return PaymentCallbackResponse.success(order.getId(), orderNo, statusChangeType); + + } catch (Exception e) { + log.error("处理支付回调失败: scenicId={}, error={}", scenicId, e.getMessage(), e); + return PaymentCallbackResponse.failure("回调处理失败: " + e.getMessage()); + } + } + + /** + * 生成商品名称 + * 根据订单的商品类型生成用于支付页面展示的商品名称 + * 如果是一种商品类型,显示具体类型名称;如果是多种,显示"多项景区商品" + */ + private String generateGoodsName(Long orderId) { + // 查询订单商品明细 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", orderId); + java.util.List orderItems = orderItemMapper.selectList(itemQuery); + + if (orderItems.isEmpty()) { + return "景区商品"; + } + + // 获取所有不同的商品类型 + java.util.Set productTypes = new java.util.HashSet<>(); + for (OrderItemV2 item : orderItems) { + productTypes.add(item.getProductType()); + } + + // 如果只有一种商品类型,返回具体类型名称 + if (productTypes.size() == 1) { + String productType = productTypes.iterator().next(); + return getProductTypeName(productType); + } else { + // 如果有多种商品类型,返回通用名称 + return "多项景区商品"; + } + } + + /** + * 获取商品类型中文名称 + */ + private String getProductTypeName(String productType) { + return switch (productType) { + case "VLOG_VIDEO" -> "Vlog视频"; + case "RECORDING_SET" -> "录像集"; + case "PHOTO_SET" -> "照相集"; + case "PHOTO_PRINT" -> "照片打印"; + case "MACHINE_PRINT" -> "一体机打印"; + default -> "景区商品"; + }; + } } \ No newline at end of file From e2b760caab548a66500421d6029217d84e892d18 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 29 Aug 2025 15:50:10 +0800 Subject: [PATCH 29/32] =?UTF-8?q?feat(order):=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=88=9B=E5=BB=BA=E5=92=8C=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加优惠券和券码的使用记录及状态更新 - 优化支付成功、取消和退款的处理逻辑 - 增加异常处理,确保事务一致性 --- .../order/service/impl/OrderServiceImpl.java | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index f629a1b..a3f3539 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -24,6 +24,9 @@ import com.ycwl.basic.pricing.dto.DiscountDetail; import com.ycwl.basic.pricing.dto.PriceCalculationResult; import com.ycwl.basic.pricing.dto.ProductItem; import com.ycwl.basic.pricing.dto.VoucherInfo; +import com.ycwl.basic.pricing.dto.CouponUseRequest; +import com.ycwl.basic.pricing.service.ICouponService; +import com.ycwl.basic.pricing.service.IVoucherService; import com.ycwl.basic.pay.adapter.IPayAdapter; import com.ycwl.basic.pay.entity.CreateOrderRequest; import com.ycwl.basic.pay.entity.CreateOrderResponse; @@ -57,11 +60,12 @@ public class OrderServiceImpl implements IOrderService { private final OrderRefundMapper orderRefundMapper; private final OrderEventManager orderEventManager; private final ScenicService scenicService; - private final FaceRepository faceRepository; private final MemberMapper memberMapper; + private final ICouponService couponService; + private final IVoucherService voucherService; @Override - @Transactional + @Transactional(rollbackFor = Exception.class) public Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult) { Date now = new Date(); MemberRespVO member = memberMapper.getById(userId); @@ -117,7 +121,7 @@ public class OrderServiceImpl implements IOrderService { log.info("订单商品明细保存成功: orderId={}, itemCount={}", orderId, request.getProducts().size()); - // 4. 记录使用的优惠券信息 + // 4. 记录使用的优惠券信息并标记为已使用 if (priceResult.getUsedCoupon() != null) { OrderDiscountV2 couponDiscount = new OrderDiscountV2(); couponDiscount.setOrderId(orderId); @@ -131,9 +135,29 @@ public class OrderServiceImpl implements IOrderService { orderDiscountMapper.insert(couponDiscount); log.info("优惠券记录保存成功: orderId={}, couponId={}, discountAmount={}", orderId, priceResult.getUsedCoupon().getCouponId(), priceResult.getUsedCoupon().getActualDiscountAmount()); + + // 标记优惠券为已使用 + try { + CouponUseRequest couponUseRequest = new CouponUseRequest(); + couponUseRequest.setCouponId(priceResult.getUsedCoupon().getCouponId()); + couponUseRequest.setUserId(userId); + couponUseRequest.setOrderId(orderNo); + couponUseRequest.setOriginalAmount(priceResult.getOriginalAmount()); + couponUseRequest.setDiscountAmount(priceResult.getUsedCoupon().getActualDiscountAmount()); + couponUseRequest.setScenicId(scenicId.toString()); + + couponService.useCoupon(couponUseRequest); + log.info("优惠券状态更新成功: couponId={}, orderId={}", + priceResult.getUsedCoupon().getCouponId(), orderNo); + } catch (Exception e) { + log.error("优惠券状态更新失败: couponId={}, orderId={}, error={}", + priceResult.getUsedCoupon().getCouponId(), orderNo, e.getMessage(), e); + // 这里可以选择抛出异常或者记录错误继续执行,根据业务需求决定 + throw new BaseException("优惠券使用失败: " + e.getMessage()); + } } - // 5. 记录使用的券码信息 + // 5. 记录使用的券码信息并标记为已使用 if (priceResult.getUsedVoucher() != null) { VoucherInfo voucherInfo = priceResult.getUsedVoucher(); OrderDiscountV2 voucherDiscount = new OrderDiscountV2(); @@ -148,6 +172,19 @@ public class OrderServiceImpl implements IOrderService { orderDiscountMapper.insert(voucherDiscount); log.info("券码记录保存成功: orderId={}, voucherCode={}, discountAmount={}", orderId, voucherInfo.getVoucherCode(), voucherInfo.getActualDiscountAmount()); + + // 标记券码为已使用 + try { + String remark = "订单使用,订单号:" + orderNo; + voucherService.markVoucherAsUsed(voucherInfo.getVoucherCode(), remark); + log.info("券码状态更新成功: voucherCode={}, orderId={}", + voucherInfo.getVoucherCode(), orderNo); + } catch (Exception e) { + log.error("券码状态更新失败: voucherCode={}, orderId={}, error={}", + voucherInfo.getVoucherCode(), orderNo, e.getMessage(), e); + // 这里可以选择抛出异常或者记录错误继续执行,根据业务需求决定 + throw new BaseException("券码使用失败: " + e.getMessage()); + } } // 6. 记录其他优惠信息(如限时立减等) @@ -654,27 +691,26 @@ public class OrderServiceImpl implements IOrderService { // 触发支付成功事件 orderEventManager.publishPaymentStatusChangeEvent( - new PaymentStatusChangeEvent(order.getId(), PaymentStatus.PENDING, PaymentStatus.PAID) + new PaymentStatusChangeEvent(order.getId(), order.getOrderNo(), + PaymentStatus.UNPAID, PaymentStatus.PAID, "支付回调成功", null) ); } else if (callbackResponse.isCancel()) { - // 支付取消 + // 支付取消 - 这种情况下支付状态保持未支付,不需要特别处理 statusChangeType = "CANCELLED"; - updatePaymentStatus(order.getId(), PaymentStatus.CANCELLED.name()); - - // 触发支付取消事件 - orderEventManager.publishPaymentStatusChangeEvent( - new PaymentStatusChangeEvent(order.getId(), PaymentStatus.PENDING, PaymentStatus.CANCELLED) - ); + log.info("支付被取消,支付状态保持未支付: orderId={}", order.getId()); } else if (callbackResponse.isRefund()) { // 退款 statusChangeType = "REFUNDED"; - updateRefundStatus(order.getId(), RefundStatus.COMPLETED.name()); + updatePaymentStatus(order.getId(), PaymentStatus.REFUNDED.name()); + updateRefundStatus(order.getId(), RefundStatus.FULL_REFUND); // 触发退款事件 orderEventManager.publishRefundStatusChangeEvent( - new RefundStatusChangeEvent(order.getId(), RefundStatus.PENDING, RefundStatus.COMPLETED) + new RefundStatusChangeEvent(order.getId(), order.getOrderNo(), null, null, + RefundStatus.NO_REFUND, RefundStatus.FULL_REFUND, + order.getFinalAmount(), "支付回调退款", null) ); } From 98ae9f293027032f2032c8289a2b8030ddfe7754 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 29 Aug 2025 16:54:46 +0800 Subject: [PATCH 30/32] =?UTF-8?q?refactor(order):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=95=86=E5=93=81=E5=93=88=E5=B8=8C=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改 DiscountType 枚举,将 FLASH_SALE 改为 LIMITED_TIME - 优化 OrderServiceImpl 中的商品信息设置逻辑,增加空值判断 - 更新 IDiscountProvider 接口和 FlashSaleDiscountProvider 类中的提供者类型标识- 优化 ScenicServiceImpl 中的字符串判空逻辑,使用 Strings.isNotBlank 方法 - 重构 PriceCacheService 中的商品列表哈希值计算逻辑,仅基于必传字段生成哈希 --- .../ycwl/basic/order/enums/DiscountType.java | 2 +- .../order/service/impl/OrderServiceImpl.java | 8 +++--- .../java/com/ycwl/basic/pricing/CLAUDE.md | 2 +- .../pricing/service/IDiscountProvider.java | 2 +- .../ycwl/basic/service/PriceCacheService.java | 26 ++++++++++++++----- .../service/pc/impl/ScenicServiceImpl.java | 11 ++++---- 6 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/ycwl/basic/order/enums/DiscountType.java b/src/main/java/com/ycwl/basic/order/enums/DiscountType.java index 82730df..6ccf1f0 100644 --- a/src/main/java/com/ycwl/basic/order/enums/DiscountType.java +++ b/src/main/java/com/ycwl/basic/order/enums/DiscountType.java @@ -18,7 +18,7 @@ public enum DiscountType { /** * 限时立减 */ - FLASH_SALE("FLASH_SALE", "限时立减"), + LIMITED_TIME("LIMITED_TIME", "限时立减"), /** * 套餐优惠 diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index a3f3539..471bc9b 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -109,10 +109,10 @@ public class OrderServiceImpl implements IOrderService { orderItem.setProductType(product.getProductType().name()); orderItem.setProductId(product.getProductId()); orderItem.setProductName(getProductName(product)); // 根据商品类型和ID获取商品名称 - orderItem.setQuantity(product.getQuantity()); - orderItem.setUnitPrice(product.getUnitPrice()); - orderItem.setOriginalAmount(product.getOriginalPrice()); - orderItem.setFinalAmount(product.getSubtotal()); + orderItem.setQuantity(product.getQuantity() != null ? product.getQuantity() : 1); + orderItem.setUnitPrice(product.getUnitPrice() != null ? product.getUnitPrice() : BigDecimal.ZERO); + orderItem.setOriginalAmount(product.getOriginalPrice() != null ? product.getOriginalPrice() : BigDecimal.ZERO); + orderItem.setFinalAmount(product.getSubtotal() != null ? product.getSubtotal() : BigDecimal.ZERO); orderItem.setCreateTime(now); orderItem.setUpdateTime(now); diff --git a/src/main/java/com/ycwl/basic/pricing/CLAUDE.md b/src/main/java/com/ycwl/basic/pricing/CLAUDE.md index c1c0ebf..03c701a 100644 --- a/src/main/java/com/ycwl/basic/pricing/CLAUDE.md +++ b/src/main/java/com/ycwl/basic/pricing/CLAUDE.md @@ -438,7 +438,7 @@ public interface IDiscountDetectionService { @Component public class FlashSaleDiscountProvider implements IDiscountProvider { @Override - public String getProviderType() { return "FLASH_SALE"; } + public String getProviderType() { return "LIMITED_TIME"; } @Override public int getPriority() { return 90; } // 介于券码和优惠券之间 diff --git a/src/main/java/com/ycwl/basic/pricing/service/IDiscountProvider.java b/src/main/java/com/ycwl/basic/pricing/service/IDiscountProvider.java index 4d2a562..28c6d07 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/IDiscountProvider.java +++ b/src/main/java/com/ycwl/basic/pricing/service/IDiscountProvider.java @@ -14,7 +14,7 @@ public interface IDiscountProvider { /** * 获取提供者类型 - * @return 提供者类型标识,如 "COUPON", "VOUCHER", "FLASH_SALE" 等 + * @return 提供者类型标识,如 "COUPON", "VOUCHER", "LIMITED_TIME" 等 */ String getProviderType(); diff --git a/src/main/java/com/ycwl/basic/service/PriceCacheService.java b/src/main/java/com/ycwl/basic/service/PriceCacheService.java index 8743e20..6962b8e 100644 --- a/src/main/java/com/ycwl/basic/service/PriceCacheService.java +++ b/src/main/java/com/ycwl/basic/service/PriceCacheService.java @@ -48,19 +48,25 @@ public class PriceCacheService { /** * 计算商品列表的哈希值 - * 基于商品类型、ID、数量等信息生成唯一哈希 + * 基于商品类型、ID、购买数量等必传字段生成唯一哈希,忽略可选的quantity字段 * * @param products 商品列表 * @return 哈希值 */ private String calculateProductsHash(List products) { try { - // 将商品列表转换为JSON字符串 - String productsJson = objectMapper.writeValueAsString(products); + // 构建缓存用的商品信息字符串,只包含必传字段 + StringBuilder cacheInfo = new StringBuilder(); + for (ProductItem product : products) { + cacheInfo.append(product.getProductType()).append(":") + .append(product.getProductId()).append(":") + .append(product.getPurchaseCount() != null ? product.getPurchaseCount() : 1).append(":") + .append(product.getScenicId()).append(";"); + } // 计算SHA-256哈希 MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] hash = digest.digest(productsJson.getBytes()); + byte[] hash = digest.digest(cacheInfo.toString().getBytes()); // 转换为16进制字符串 StringBuilder hexString = new StringBuilder(); @@ -75,10 +81,16 @@ public class PriceCacheService { // 返回前16位作为哈希值(足够避免冲突) return hexString.substring(0, 16); - } catch (JsonProcessingException | NoSuchAlgorithmException e) { + } catch (NoSuchAlgorithmException e) { log.error("计算商品列表哈希值失败", e); - // 降级策略:使用商品列表的hashCode - return String.valueOf(products.hashCode()); + // 降级策略:使用关键字段的hashCode + StringBuilder fallback = new StringBuilder(); + for (ProductItem product : products) { + fallback.append(product.getProductType()).append(":") + .append(product.getProductId()).append(":") + .append(product.getPurchaseCount() != null ? product.getPurchaseCount() : 1).append(";"); + } + return String.valueOf(fallback.toString().hashCode()); } } diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java index 8a7171d..232299e 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/ScenicServiceImpl.java @@ -17,6 +17,7 @@ import com.ycwl.basic.util.TtlCacheMap; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.JacksonUtil; import lombok.extern.slf4j.Slf4j; +import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -60,7 +61,7 @@ public class ScenicServiceImpl implements ScenicService { return scenicStorageAdapterCache.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); - if (scenicConfig.getString("store_type") != null) { + if (Strings.isNotBlank(scenicConfig.getString("store_type"))) { try { adapter = StorageFactory.get(scenicConfig.getString("store_type")); adapter.loadConfig(scenicConfig.getObject("store_config_json", Map.class)); @@ -78,7 +79,7 @@ public class ScenicServiceImpl implements ScenicService { return scenicTmpStorageAdapterCache.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); - if (scenicConfig.getString("tmp_store_type") != null) { + if (Strings.isNotBlank(scenicConfig.getString("tmp_store_type"))) { try { adapter = StorageFactory.get(scenicConfig.getString("tmp_store_type")); adapter.loadConfig(scenicConfig.getObject("tmp_store_config_json", Map.class)); @@ -96,7 +97,7 @@ public class ScenicServiceImpl implements ScenicService { return scenicLocalStorageAdapterCache.computeIfAbsent(scenicId, (key) -> { IStorageAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); - if (scenicConfig.getString("local_store_type") != null) { + if (Strings.isNotBlank(scenicConfig.getString("local_store_type"))) { try { adapter = StorageFactory.get(scenicConfig.getString("local_store_type")); adapter.loadConfig(scenicConfig.getObject("local_store_config_json", Map.class)); @@ -115,7 +116,7 @@ public class ScenicServiceImpl implements ScenicService { return scenicFaceBodyAdapterCache.computeIfAbsent(scenicId, (key) -> { IFaceBodyAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); - if (scenicConfig.getString("face_type") != null) { + if (Strings.isNotBlank(scenicConfig.getString("face_type"))) { adapter = FaceBodyFactory.getAdapter(scenicConfig.getString("face_type")); adapter.loadConfig(scenicConfig.getObject("face_config_json", Map.class)); } else { @@ -130,7 +131,7 @@ public class ScenicServiceImpl implements ScenicService { return scenicPayAdapterCache.computeIfAbsent(scenicId, (key) -> { IPayAdapter adapter; ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(scenicId); - if (scenicConfig.getString("pay_type") != null) { + if (Strings.isNotBlank(scenicConfig.getString("pay_type"))) { adapter = PayFactory.getAdapter(scenicConfig.getString("pay_type")); adapter.loadConfig(scenicConfig.getObject("pay_config_json", Map.class)); } else { From 93a424058ac1e6987763d09cfaea4121f1c5cc81 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 29 Aug 2025 17:39:52 +0800 Subject: [PATCH 31/32] =?UTF-8?q?feat(order):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E5=88=9B=E5=BB=BA=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=95=86=E5=93=81=E4=BB=B7=E6=A0=BC=E5=92=8C?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 calculateProductItemPriceAndName 方法,用于重新计算商品价格信息并获取商品名称 - 更新订单创建流程,使用计算后的商品价格和名称信息 - 引入 IProductConfigService 接口,用于获取商品配置信息 - 优化异常处理,确保在价格计算失败时有兜底方案 --- .../order/service/impl/OrderServiceImpl.java | 136 ++++++++++++++++-- 1 file changed, 124 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index 471bc9b..1c78eb0 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -25,7 +25,11 @@ import com.ycwl.basic.pricing.dto.PriceCalculationResult; import com.ycwl.basic.pricing.dto.ProductItem; import com.ycwl.basic.pricing.dto.VoucherInfo; import com.ycwl.basic.pricing.dto.CouponUseRequest; +import com.ycwl.basic.pricing.entity.PriceProductConfig; +import com.ycwl.basic.pricing.entity.PriceTierConfig; +import com.ycwl.basic.pricing.exception.PriceCalculationException; import com.ycwl.basic.pricing.service.ICouponService; +import com.ycwl.basic.pricing.service.IProductConfigService; import com.ycwl.basic.pricing.service.IVoucherService; import com.ycwl.basic.pay.adapter.IPayAdapter; import com.ycwl.basic.pay.entity.CreateOrderRequest; @@ -63,6 +67,7 @@ public class OrderServiceImpl implements IOrderService { private final MemberMapper memberMapper; private final ICouponService couponService; private final IVoucherService voucherService; + private final IProductConfigService productConfigService; @Override @Transactional(rollbackFor = Exception.class) @@ -104,15 +109,18 @@ public class OrderServiceImpl implements IOrderService { // 3. 保存订单商品明细 for (ProductItem product : request.getProducts()) { + // 重新计算商品价格信息并获取商品名称 + ProductPriceAndNameInfo priceInfo = calculateProductItemPriceAndName(product); + OrderItemV2 orderItem = new OrderItemV2(); orderItem.setOrderId(orderId); orderItem.setProductType(product.getProductType().name()); orderItem.setProductId(product.getProductId()); - orderItem.setProductName(getProductName(product)); // 根据商品类型和ID获取商品名称 + orderItem.setProductName(priceInfo.getProductName()); // 使用配置中的商品名称 orderItem.setQuantity(product.getQuantity() != null ? product.getQuantity() : 1); - orderItem.setUnitPrice(product.getUnitPrice() != null ? product.getUnitPrice() : BigDecimal.ZERO); - orderItem.setOriginalAmount(product.getOriginalPrice() != null ? product.getOriginalPrice() : BigDecimal.ZERO); - orderItem.setFinalAmount(product.getSubtotal() != null ? product.getSubtotal() : BigDecimal.ZERO); + orderItem.setUnitPrice(product.getUnitPrice()); + orderItem.setOriginalAmount(product.getOriginalPrice()); + orderItem.setFinalAmount(product.getSubtotal()); orderItem.setCreateTime(now); orderItem.setUpdateTime(now); @@ -256,14 +264,6 @@ public class OrderServiceImpl implements IOrderService { return "ORDER" + timestamp + random; } - /** - * 根据商品类型和ID获取商品名称 - * TODO: 实际应该从商品配置表或其他数据源获取 - */ - private String getProductName(ProductItem product) { - return product.getProductType().getDescription() + "-" + product.getProductId(); - } - // ====== 新增方法实现 ====== @Override @@ -769,4 +769,116 @@ public class OrderServiceImpl implements IOrderService { default -> "景区商品"; }; } + + /** + * 商品价格和名称信息内部DTO + */ + private static class ProductPriceAndNameInfo { + private BigDecimal unitPrice; + private BigDecimal originalPrice; + private String productName; + + public ProductPriceAndNameInfo(BigDecimal unitPrice, BigDecimal originalPrice, String productName) { + this.unitPrice = unitPrice; + this.originalPrice = originalPrice; + this.productName = productName; + } + + public BigDecimal getUnitPrice() { return unitPrice; } + public BigDecimal getOriginalPrice() { return originalPrice; } + public String getProductName() { return productName; } + } + + /** + * 重新计算商品价格信息并获取商品名称 + * 基于定价配置重新计算商品的单价、原价和商品名称 + */ + private ProductPriceAndNameInfo calculateProductItemPriceAndName(ProductItem product) { + String productTypeCode = product.getProductType().getCode(); + String productId = product.getProductId() != null ? product.getProductId() : "default"; + + BigDecimal unitPrice; + BigDecimal originalPrice; + String productName; + + try { + // 1. 优先使用基于product_id的阶梯定价 + PriceTierConfig tierConfig = productConfigService.getTierConfig( + productTypeCode, productId, product.getQuantity()); + + if (tierConfig != null) { + unitPrice = tierConfig.getPrice(); + originalPrice = tierConfig.getOriginalPrice(); + + // 阶梯定价没有商品名称,需要从基础配置获取 + PriceProductConfig baseConfig = productConfigService.getProductConfig(productTypeCode, productId); + productName = (baseConfig != null && baseConfig.getProductName() != null) + ? baseConfig.getProductName() + : getProductTypeName(product.getProductType().name()); + + log.debug("使用阶梯定价: productType={}, productId={}, quantity={}, price={}, originalPrice={}, productName={}", + productTypeCode, productId, product.getQuantity(), unitPrice, originalPrice, productName); + } else { + // 2. 使用基于product_id的基础配置 + PriceProductConfig baseConfig; + try { + baseConfig = productConfigService.getProductConfig(productTypeCode, productId); + } catch (Exception e) { + log.warn("未找到具体商品配置: productType={}, productId={}, 使用default配置", + productTypeCode, productId); + baseConfig = productConfigService.getProductConfig(productTypeCode, "default"); + } + + if (baseConfig == null) { + throw new PriceCalculationException( + String.format("未找到商品价格配置: productType=%s, productId=%s", productTypeCode, productId)); + } + + unitPrice = baseConfig.getBasePrice(); + originalPrice = baseConfig.getOriginalPrice(); + productName = (baseConfig.getProductName() != null) + ? baseConfig.getProductName() + : getProductTypeName(product.getProductType().name()); + + // 3. 处理按数量计价的商品类型 + if (product.getProductType() == com.ycwl.basic.pricing.enums.ProductType.PHOTO_PRINT || + product.getProductType() == com.ycwl.basic.pricing.enums.ProductType.MACHINE_PRINT) { + if (product.getQuantity() != null && product.getQuantity() > 0) { + unitPrice = unitPrice.multiply(BigDecimal.valueOf(product.getQuantity())); + if (originalPrice != null) { + originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity())); + } + } + } + } + + // 4. 设置计算后的价格到ProductItem + product.setUnitPrice(unitPrice); + product.setOriginalPrice(originalPrice != null ? originalPrice : unitPrice); + + // 5. 计算小计(单价 × 购买数量) + int purchaseCount = product.getPurchaseCount() != null ? product.getPurchaseCount() : 1; + BigDecimal subtotal = unitPrice.multiply(BigDecimal.valueOf(purchaseCount)); + product.setSubtotal(subtotal); + + log.debug("商品价格和名称计算完成: productType={}, productId={}, unitPrice={}, originalPrice={}, subtotal={}, productName={}", + productTypeCode, productId, unitPrice, originalPrice, subtotal, productName); + + return new ProductPriceAndNameInfo(unitPrice, originalPrice != null ? originalPrice : unitPrice, productName); + + } catch (Exception e) { + log.error("计算商品价格失败: productType={}, productId={}, error={}", + productTypeCode, productId, e.getMessage(), e); + + // 兜底处理:使用默认价格和类型名称 + BigDecimal defaultPrice = BigDecimal.ZERO; + String fallbackName = getProductTypeName(product.getProductType().name()); + + product.setUnitPrice(defaultPrice); + product.setOriginalPrice(defaultPrice); + product.setSubtotal(defaultPrice); + + return new ProductPriceAndNameInfo(defaultPrice, defaultPrice, fallbackName); + } + } } \ No newline at end of file From 792deb5c4defa32d4814ab6ce7883bc0b738cb56 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Sat, 30 Aug 2025 10:52:26 +0800 Subject: [PATCH 32/32] =?UTF-8?q?feat(order):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E8=B4=AD=E4=B9=B0=E6=A3=80=E6=B5=8B=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 DuplicatePurchaseException 类用于处理重复购买异常 - 在 OrderServiceImpl 中实现重复购买检查逻辑 - 更新 CustomExceptionHandle 以处理新的重复购买异常 -优化订单创建流程,在生成订单号前增加重复购买检查 --- .../exception/CustomExceptionHandle.java | 15 ++ .../exception/DuplicatePurchaseException.java | 58 +++++++ .../order/service/impl/OrderServiceImpl.java | 145 +++++++++++++++++- .../service/impl/VoucherPrintServiceImpl.java | 1 - 4 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/ycwl/basic/order/exception/DuplicatePurchaseException.java diff --git a/src/main/java/com/ycwl/basic/exception/CustomExceptionHandle.java b/src/main/java/com/ycwl/basic/exception/CustomExceptionHandle.java index 568e056..1070bb7 100644 --- a/src/main/java/com/ycwl/basic/exception/CustomExceptionHandle.java +++ b/src/main/java/com/ycwl/basic/exception/CustomExceptionHandle.java @@ -1,6 +1,7 @@ package com.ycwl.basic.exception; import com.ycwl.basic.enums.BizCodeEnum; +import com.ycwl.basic.order.exception.DuplicatePurchaseException; import com.ycwl.basic.utils.ApiResponse; import jakarta.servlet.http.HttpServletRequest; import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException; @@ -106,4 +107,18 @@ public class CustomExceptionHandle { public ApiResponse handle(SizeLimitExceededException sizeLimitExceededException) { return ApiResponse.buildResponse(415, "文件过大,请重新上传"); } + + /** + * 重复购买异常处理 + */ + @ExceptionHandler(value = DuplicatePurchaseException.class) + public ApiResponse handle(HttpServletResponse response, DuplicatePurchaseException exception) { + response.setStatus(HttpStatus.BAD_REQUEST.value()); + LOGGER.warn("检测到重复购买: productType={}, productId={}, existingOrderId={}, existingOrderNo={}", + exception.getProductType(), exception.getProductId(), + exception.getExistingOrderId(), exception.getExistingOrderNo()); + + // 返回友好的错误信息给前端 + return ApiResponse.buildResponse(4001, exception.getFriendlyMessage()); + } } diff --git a/src/main/java/com/ycwl/basic/order/exception/DuplicatePurchaseException.java b/src/main/java/com/ycwl/basic/order/exception/DuplicatePurchaseException.java new file mode 100644 index 0000000..b0e2569 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/exception/DuplicatePurchaseException.java @@ -0,0 +1,58 @@ +package com.ycwl.basic.order.exception; + +import com.ycwl.basic.exception.BaseException; +import com.ycwl.basic.pricing.enums.ProductType; + +/** + * 重复购买异常 + * 当用户尝试购买已经购买过的内容时抛出此异常 + */ +public class DuplicatePurchaseException extends BaseException { + + private final Long existingOrderId; + private final String existingOrderNo; + private final ProductType productType; + private final String productId; + + public DuplicatePurchaseException(String message, Long existingOrderId, String existingOrderNo, + ProductType productType, String productId) { + super(message); + this.existingOrderId = existingOrderId; + this.existingOrderNo = existingOrderNo; + this.productType = productType; + this.productId = productId; + } + + public DuplicatePurchaseException(String message, Long existingOrderId, String existingOrderNo, + ProductType productType) { + this(message, existingOrderId, existingOrderNo, productType, null); + } + + public Long getExistingOrderId() { + return existingOrderId; + } + + public String getExistingOrderNo() { + return existingOrderNo; + } + + public ProductType getProductType() { + return productType; + } + + public String getProductId() { + return productId; + } + + /** + * 获取友好的错误消息 + */ + public String getFriendlyMessage() { + String productDesc = productType != null ? productType.getDescription() : "商品"; + if (productId != null) { + return String.format("您已购买过该%s(商品ID:%s),订单号:%s", productDesc, productId, existingOrderNo); + } else { + return String.format("您已购买过%s,订单号:%s", productDesc, existingOrderNo); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java index 1c78eb0..fa14b0a 100644 --- a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -14,6 +14,7 @@ import com.ycwl.basic.order.entity.OrderItemV2; import com.ycwl.basic.order.entity.OrderRefundV2; import com.ycwl.basic.order.entity.OrderV2; import com.ycwl.basic.order.enums.*; +import com.ycwl.basic.order.exception.DuplicatePurchaseException; import com.ycwl.basic.order.mapper.OrderDiscountMapper; import com.ycwl.basic.order.mapper.OrderItemMapper; import com.ycwl.basic.order.mapper.OrderV2Mapper; @@ -74,10 +75,18 @@ public class OrderServiceImpl implements IOrderService { public Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult) { Date now = new Date(); MemberRespVO member = memberMapper.getById(userId); - // 1. 生成订单号 + + // 1. 检查重复购买 + log.info("开始检查重复购买: userId={}, faceId={}, scenicId={}, productCount={}", + userId, request.getFaceId(), scenicId, request.getProducts().size()); + checkDuplicatePurchase(userId, request.getFaceId(), scenicId, request.getProducts()); + log.info("重复购买检查通过: userId={}, faceId={}, scenicId={}", + userId, request.getFaceId(), scenicId); + + // 2. 生成订单号 String orderNo = generateOrderNo(); - // 2. 创建订单基础信息 + // 3. 创建订单基础信息 OrderV2 order = new OrderV2(); order.setOrderNo(orderNo); order.setMemberId(userId); @@ -107,7 +116,7 @@ public class OrderServiceImpl implements IOrderService { log.info("订单基础信息创建成功: orderId={}, orderNo={}", orderId, orderNo); - // 3. 保存订单商品明细 + // 4. 保存订单商品明细 for (ProductItem product : request.getProducts()) { // 重新计算商品价格信息并获取商品名称 ProductPriceAndNameInfo priceInfo = calculateProductItemPriceAndName(product); @@ -129,7 +138,7 @@ public class OrderServiceImpl implements IOrderService { log.info("订单商品明细保存成功: orderId={}, itemCount={}", orderId, request.getProducts().size()); - // 4. 记录使用的优惠券信息并标记为已使用 + // 5. 记录使用的优惠券信息并标记为已使用 if (priceResult.getUsedCoupon() != null) { OrderDiscountV2 couponDiscount = new OrderDiscountV2(); couponDiscount.setOrderId(orderId); @@ -165,7 +174,7 @@ public class OrderServiceImpl implements IOrderService { } } - // 5. 记录使用的券码信息并标记为已使用 + // 6. 记录使用的券码信息并标记为已使用 if (priceResult.getUsedVoucher() != null) { VoucherInfo voucherInfo = priceResult.getUsedVoucher(); OrderDiscountV2 voucherDiscount = new OrderDiscountV2(); @@ -195,7 +204,7 @@ public class OrderServiceImpl implements IOrderService { } } - // 6. 记录其他优惠信息(如限时立减等) + // 7. 记录其他优惠信息(如限时立减等) if (priceResult.getDiscountDetails() != null) { for (DiscountDetail discount : priceResult.getDiscountDetails()) { // 跳过已经记录的优惠券和券码 @@ -881,4 +890,128 @@ public class OrderServiceImpl implements IOrderService { return new ProductPriceAndNameInfo(defaultPrice, defaultPrice, fallbackName); } } + + /** + * 检查重复购买 + * 防止用户重复购买相同内容 + * + * @param userId 用户ID + * @param faceId 人脸ID + * @param scenicId 景区ID + * @param products 商品列表 + * @throws DuplicatePurchaseException 如果检测到重复购买 + */ + private void checkDuplicatePurchase(Long userId, Long faceId, Long scenicId, List products) { + for (ProductItem product : products) { + switch (product.getProductType()) { + case VLOG_VIDEO: + checkVideoAlreadyPurchased(userId, faceId, scenicId, product.getProductId()); + break; + case RECORDING_SET: + case PHOTO_SET: + checkSetAlreadyPurchased(userId, faceId, scenicId, product.getProductType()); + break; + case PHOTO_PRINT: + case MACHINE_PRINT: + // 打印类商品允许重复购买,跳过检查 + log.debug("跳过打印类商品重复购买检查: productType={}, productId={}", + product.getProductType(), product.getProductId()); + break; + default: + log.warn("未知的商品类型,跳过重复购买检查: productType={}", product.getProductType()); + break; + } + } + } + + /** + * 检查视频是否已经购买 + * + * @param userId 用户ID + * @param faceId 人脸ID + * @param scenicId 景区ID + * @param videoId 视频ID + * @throws DuplicatePurchaseException 如果已购买 + */ + private void checkVideoAlreadyPurchased(Long userId, Long faceId, Long scenicId, String videoId) { + // 构建查询条件:查找已支付的有效订单中包含该视频的订单 + QueryWrapper orderQuery = new QueryWrapper<>(); + orderQuery.eq("member_id", userId) + .eq("face_id", faceId) + .eq("scenic_id", scenicId) + .eq("payment_status", PaymentStatus.PAID.getCode()) + .in("order_status", OrderStatus.PAID.getCode(), OrderStatus.PROCESSING.getCode(), OrderStatus.COMPLETED.getCode()) + .eq("deleted", 0); + + List existingOrders = orderV2Mapper.selectList(orderQuery); + + for (OrderV2 order : existingOrders) { + // 检查订单明细中是否包含该视频 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", order.getId()) + .eq("product_type", com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO.name()) + .eq("product_id", videoId); + + long count = orderItemMapper.selectCount(itemQuery); + if (count > 0) { + log.warn("检测到重复购买视频: userId={}, faceId={}, scenicId={}, videoId={}, existingOrderId={}", + userId, faceId, scenicId, videoId, order.getId()); + throw new DuplicatePurchaseException( + "您已购买过此视频", + order.getId(), + order.getOrderNo(), + com.ycwl.basic.pricing.enums.ProductType.VLOG_VIDEO, + videoId + ); + } + } + + log.debug("视频重复购买检查通过: userId={}, faceId={}, scenicId={}, videoId={}", + userId, faceId, scenicId, videoId); + } + + /** + * 检查套餐(录像集/照相集)是否已经购买 + * + * @param userId 用户ID + * @param faceId 人脸ID + * @param scenicId 景区ID + * @param productType 商品类型 + * @throws DuplicatePurchaseException 如果已购买 + */ + private void checkSetAlreadyPurchased(Long userId, Long faceId, Long scenicId, + com.ycwl.basic.pricing.enums.ProductType productType) { + // 构建查询条件:查找已支付的有效订单中包含该类型套餐的订单 + QueryWrapper orderQuery = new QueryWrapper<>(); + orderQuery.eq("member_id", userId) + .eq("face_id", faceId) + .eq("scenic_id", scenicId) + .eq("payment_status", PaymentStatus.PAID.getCode()) + .in("order_status", OrderStatus.PAID.getCode(), OrderStatus.PROCESSING.getCode(), OrderStatus.COMPLETED.getCode()) + .eq("deleted", 0); + + List existingOrders = orderV2Mapper.selectList(orderQuery); + + for (OrderV2 order : existingOrders) { + // 检查订单明细中是否包含该类型的套餐 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", order.getId()) + .eq("product_type", productType.name()); + + long count = orderItemMapper.selectCount(itemQuery); + if (count > 0) { + log.warn("检测到重复购买套餐: userId={}, faceId={}, scenicId={}, productType={}, existingOrderId={}", + userId, faceId, scenicId, productType, order.getId()); + throw new DuplicatePurchaseException( + "您已购买过此类型的套餐", + order.getId(), + order.getOrderNo(), + productType + ); + } + } + + log.debug("套餐重复购买检查通过: userId={}, faceId={}, scenicId={}, productType={}", + userId, faceId, scenicId, productType); + } } \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java index 19d178c..d74a4dc 100644 --- a/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java +++ b/src/main/java/com/ycwl/basic/pricing/service/impl/VoucherPrintServiceImpl.java @@ -238,6 +238,5 @@ public class VoucherPrintServiceImpl implements VoucherPrintService { content += "赠品兑换码"; content += "有效期:"+sdf2.format(new Date())+""; FeiETicketPrinter.doPrint("550519002", content, 1); - // 小票配对码、赠品兑换码、抵扣兑换码 } } \ No newline at end of file