From dbf408c51ff7a91b5eda9c1b326c850c20a7b09a Mon Sep 17 00:00:00 2001 From: cst61 Date: Sat, 30 May 2026 08:26:47 +0800 Subject: [PATCH] =?UTF-8?q?goods=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/LbThirdIntegrationConstants.java | 23 + .../java/com/rj/config/MybatisPlusConfig.java | 1 + .../com/rj/controller/LbGoodsController.java | 5 +- .../rj/controller/LbOrderRowController.java | 15 +- .../LbThirdIntegrationConfigController.java | 138 +++++ ...irdIntegrationCredentialUpdateRequest.java | 46 ++ .../java/com/rj/entity/LbAssessmentApply.java | 4 + src/main/java/com/rj/entity/LbOrderRow.java | 7 +- .../rj/entity/LbThirdIntegrationConfig.java | 212 +++++++ .../LbThirdIntegrationConfigMapper.java | 12 + .../java/com/rj/service/ILbGoodsService.java | 3 +- .../com/rj/service/ILbOrderRowService.java | 5 +- .../ILbThirdIntegrationConfigService.java | 38 ++ .../impl/LbAssessmentApplyServiceImpl.java | 27 +- .../rj/service/impl/LbGoodsServiceImpl.java | 18 +- .../service/impl/LbOrderRowServiceImpl.java | 98 +++- .../LbThirdIntegrationConfigServiceImpl.java | 543 ++++++++++++++++++ .../resources/mapper/LbOrderRowMapper.xml | 2 +- ..._assessment_apply_alter_add_sorted_num.sql | 8 + src/main/sql/lb_third_integration_config.sql | 60 ++ 20 files changed, 1233 insertions(+), 32 deletions(-) create mode 100644 src/main/java/com/rj/common/LbThirdIntegrationConstants.java create mode 100644 src/main/java/com/rj/controller/LbThirdIntegrationConfigController.java create mode 100644 src/main/java/com/rj/dto/LbThirdIntegrationCredentialUpdateRequest.java create mode 100644 src/main/java/com/rj/entity/LbThirdIntegrationConfig.java create mode 100644 src/main/java/com/rj/mapper/LbThirdIntegrationConfigMapper.java create mode 100644 src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java create mode 100644 src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java create mode 100644 src/main/sql/lb_assessment_apply_alter_add_sorted_num.sql create mode 100644 src/main/sql/lb_third_integration_config.sql diff --git a/src/main/java/com/rj/common/LbThirdIntegrationConstants.java b/src/main/java/com/rj/common/LbThirdIntegrationConstants.java new file mode 100644 index 0000000..654a621 --- /dev/null +++ b/src/main/java/com/rj/common/LbThirdIntegrationConstants.java @@ -0,0 +1,23 @@ +package com.rj.common; + +/** + * 租户第三方集成配置常量 + */ +public final class LbThirdIntegrationConstants { + + private LbThirdIntegrationConstants() { + } + + public static final String PROVIDER_HXR_ADMIN = "HXR_ADMIN"; + public static final String AUTH_COOKIE_PHPSID = "COOKIE_PHPSID"; + + public static final String DEFAULT_ORDER_SELECT_PATH = "/app/admin/order/select"; + public static final String DEFAULT_USER_SELECT_PATH = "/app/admin/user/select"; + public static final String DEFAULT_USER_UPDATE_PATH = "/app/admin/user/update"; + public static final String DEFAULT_GOODS_API_PATH = "/api/order/goods"; + public static final String DEFAULT_BUY_API_PATH = "/api/order/buy"; + + public static final int DEFAULT_ORDER_PAGE_LIMIT = 90; + public static final int DEFAULT_USER_PAGE_LIMIT = 90; + public static final int DEFAULT_GOODS_PAGE_LIMIT = 20; +} diff --git a/src/main/java/com/rj/config/MybatisPlusConfig.java b/src/main/java/com/rj/config/MybatisPlusConfig.java index e5d313f..3ed8300 100644 --- a/src/main/java/com/rj/config/MybatisPlusConfig.java +++ b/src/main/java/com/rj/config/MybatisPlusConfig.java @@ -60,6 +60,7 @@ public class MybatisPlusConfig { ignoreTables.add("industry_tags"); // 行业标签(示例:如认为是公共字典) ignoreTables.add("menu"); // 菜单表(不需要租户隔离) ignoreTables.add("ai_prompts"); // AI 提示词全局配置,表无 tenant_id 字段 + ignoreTables.add("lb_third_integration_config"); // 每租户一条,按 tenant_id 显式查询,不走插件自动拼接 return new TenantLineHandler() { diff --git a/src/main/java/com/rj/controller/LbGoodsController.java b/src/main/java/com/rj/controller/LbGoodsController.java index 04ed0e9..54fa971 100644 --- a/src/main/java/com/rj/controller/LbGoodsController.java +++ b/src/main/java/com/rj/controller/LbGoodsController.java @@ -136,8 +136,9 @@ public class LbGoodsController { @Operation( summary = "抢购货品", description = - "从 lb_goods 按 total_money 从大到小选取货品,调用 hxrd POST /api/order/buy(body: id、seller_id);" - + "成功笔数达到 maxBuyCount 后停止;token 可选,未传时使用 hxr.admin.goods-api-token") + "从 lb_goods 按 total_money 从大到小选取金额小于39000的货品,调用 hxrd POST /api/order/buy(body: id、seller_id);" + + "成功笔数达到 maxBuyCount 后停止,且循环抢购总次数不超过 maxBuyCount 的5倍;" + + "token 可选,未传时使用 hxr.admin.goods-api-token") public ResponseEntity> rushBuy( @Parameter(description = "maxBuyCount 必填,token 可选", required = true) @RequestBody LbGoodsRushBuyRequest request) { diff --git a/src/main/java/com/rj/controller/LbOrderRowController.java b/src/main/java/com/rj/controller/LbOrderRowController.java index 05575e4..3f81052 100644 --- a/src/main/java/com/rj/controller/LbOrderRowController.java +++ b/src/main/java/com/rj/controller/LbOrderRowController.java @@ -24,7 +24,9 @@ public class LbOrderRowController { @PostMapping("/add") @Operation(summary = "新增") public ResponseEntity> add( - @Parameter(description = "实体(id 为订单 id,需调用方指定)", required = true) @RequestBody LbOrderRow entity) { + @Parameter(description = "实体(复合主键 tenantId + id,需调用方指定)", required = true) + @RequestBody + LbOrderRow entity) { Map result = lbOrderRowService.add(entity); Boolean success = (Boolean) result.get("success"); if (success != null && success) { @@ -36,7 +38,7 @@ public class LbOrderRowController { @PutMapping("/update") @Operation(summary = "编辑") public ResponseEntity> update( - @Parameter(description = "实体", required = true) @RequestBody LbOrderRow entity) { + @Parameter(description = "实体(须含 tenantId 与 id)", required = true) @RequestBody LbOrderRow entity) { Map result = lbOrderRowService.update(entity); Boolean success = (Boolean) result.get("success"); if (success != null && success) { @@ -48,8 +50,11 @@ public class LbOrderRowController { @DeleteMapping("/delete/{id}") @Operation(summary = "删除") public ResponseEntity> delete( - @Parameter(description = "主键(订单 id)", required = true) @PathVariable Long id) { - Map result = lbOrderRowService.deleteById(id); + @Parameter(description = "订单 id(复合主键之一)", required = true) @PathVariable Long id, + @Parameter(description = "租户 id(复合主键之一;未传时使用 X-Tenant-Id / Token 租户)") + @RequestParam(required = false) + String tenantId) { + Map result = lbOrderRowService.deleteById(id, tenantId); Boolean success = (Boolean) result.get("success"); if (success != null && success) { return ResponseEntity.ok(result); @@ -92,7 +97,7 @@ public class LbOrderRowController { @PostMapping("/sync-order-from-third") @Operation( summary = "从 外部系统 同步订单", - description = "按购买时间区间、租户 id、转卖状态调用后台 order/select,将结果写入 lb_order_row(按订单 id upsert)") + description = "按购买时间区间、租户 id、转卖状态调用后台 order/select,将结果写入 lb_order_row(按 tenant_id + 订单 id upsert)") public ResponseEntity> syncFromHxr( @Parameter(description = "同步条件", required = true) @RequestBody LbOrderRowSyncFromHxrRequest request) { Map result = diff --git a/src/main/java/com/rj/controller/LbThirdIntegrationConfigController.java b/src/main/java/com/rj/controller/LbThirdIntegrationConfigController.java new file mode 100644 index 0000000..9f3d5d8 --- /dev/null +++ b/src/main/java/com/rj/controller/LbThirdIntegrationConfigController.java @@ -0,0 +1,138 @@ +package com.rj.controller; + +import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest; +import com.rj.entity.LbThirdIntegrationConfig; +import com.rj.service.ILbThirdIntegrationConfigService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * 租户第三方集成配置控制器 + */ +@Slf4j +@RestController +@RequestMapping("/api/lbThirdIntegrationConfig") +@Tag(name = "租户第三方集成配置", description = "lb_third_integration_config 增删改查与分页") +public class LbThirdIntegrationConfigController { + + @Autowired + private ILbThirdIntegrationConfigService lbThirdIntegrationConfigService; + + @PostMapping("/add") + @Operation(summary = "新增") + public ResponseEntity> add( + @Parameter(description = "配置实体(凭证请传 *Plain 明文字段)", required = true) + @RequestBody LbThirdIntegrationConfig entity) { + Map result = lbThirdIntegrationConfigService.add(entity); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + return ResponseEntity.badRequest().body(result); + } + + @PutMapping("/update") + @Operation(summary = "更新") + public ResponseEntity> update( + @Parameter(description = "配置实体(仅传需修改字段;凭证 *Plain 非空时才轮换)", required = true) + @RequestBody LbThirdIntegrationConfig entity) { + Map result = lbThirdIntegrationConfigService.update(entity); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + return ResponseEntity.badRequest().body(result); + } + + @DeleteMapping("/delete/{id}") + @Operation(summary = "删除") + public ResponseEntity> delete( + @Parameter(description = "主键ID", required = true) @PathVariable String id) { + Map result = lbThirdIntegrationConfigService.deleteById(id); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + return ResponseEntity.badRequest().body(result); + } + + @GetMapping("/list") + @Operation(summary = "分页查询") + public ResponseEntity> list( + @RequestParam(defaultValue = "1") Integer current, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(required = false) String tenantId, + @RequestParam(required = false) String providerCode, + @RequestParam(required = false) Integer enabled) { + Map result = lbThirdIntegrationConfigService.pageQuery( + current, size, tenantId, providerCode, enabled); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + return ResponseEntity.internalServerError().body(result); + } + + @GetMapping("/get/{id}") + @Operation(summary = "按ID查询") + public ResponseEntity> getById( + @Parameter(description = "主键ID", required = true) @PathVariable String id) { + Map result = lbThirdIntegrationConfigService.getDetailById(id); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + if ("记录不存在".equals(result.get("message"))) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.badRequest().body(result); + } + + @GetMapping("/credential/{tenantId}") + @Operation( + summary = "查询租户凭证(含明文)", + description = "按 tenantId 解密返回 cookie、phpsid、goodsApiToken、goodsApiAppStr 明文,便于管理查看") + public ResponseEntity> getCredentialStatus( + @Parameter(description = "租户 id", required = true) @PathVariable String tenantId) { + Map result = lbThirdIntegrationConfigService.getCredentialStatusByTenantId(tenantId); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + if ("该租户未配置第三方集成".equals(result.get("message"))) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.badRequest().body(result); + } + + @PostMapping("/credential/{tenantId}") + @Operation( + summary = "更新租户凭证", + description = + "仅更新 cookie/phpsid/goodsApiToken/goodsApiAppStr 凭证列;" + + "传明文则加密入库并 credential_version+1;" + + "传 clear* 为 true 则清除对应密文;至少操作一项") + public ResponseEntity> updateCredential( + @Parameter(description = "租户 id", required = true) @PathVariable String tenantId, + @Parameter(description = "凭证更新请求", required = true) + @RequestBody + LbThirdIntegrationCredentialUpdateRequest request) { + Map result = + lbThirdIntegrationConfigService.updateCredentialByTenantId(tenantId, request); + Boolean success = (Boolean) result.get("success"); + if (success != null && success) { + return ResponseEntity.ok(result); + } + if ("该租户未配置第三方集成,请先新增配置".equals(result.get("message"))) { + return ResponseEntity.notFound().build(); + } + return ResponseEntity.badRequest().body(result); + } +} diff --git a/src/main/java/com/rj/dto/LbThirdIntegrationCredentialUpdateRequest.java b/src/main/java/com/rj/dto/LbThirdIntegrationCredentialUpdateRequest.java new file mode 100644 index 0000000..97fc8e7 --- /dev/null +++ b/src/main/java/com/rj/dto/LbThirdIntegrationCredentialUpdateRequest.java @@ -0,0 +1,46 @@ +package com.rj.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 更新 {@code lb_third_integration_config} 凭证列的请求体(明文仅用于写入,响应不回显)。 + */ +@Data +@Schema(description = "租户第三方集成凭证更新请求") +public class LbThirdIntegrationCredentialUpdateRequest { + + @Schema(description = "完整 Cookie 明文,如 PHPSID=xxxx;与 clearCookie 互斥") + private String cookie; + + @Schema(description = "PHPSID 值明文(不含 PHPSID= 前缀);与 clearPhpsid 互斥") + private String phpsid; + + @Schema(description = "货品/抢购 token 明文;与 clearGoodsApiToken 互斥") + private String goodsApiToken; + + @Schema(description = "签名密钥 appStr 明文;与 clearGoodsApiAppStr 互斥") + private String goodsApiAppStr; + + @Schema(description = "凭证预计过期时间(可选)") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime credentialExpireTime; + + @Schema(description = "操作人(可选,写入 updated_by)") + private String updatedBy; + + @Schema(description = "为 true 时清除 cookie_cipher") + private Boolean clearCookie; + + @Schema(description = "为 true 时清除 phpsid_cipher") + private Boolean clearPhpsid; + + @Schema(description = "为 true 时清除 goods_api_token_cipher") + private Boolean clearGoodsApiToken; + + @Schema(description = "为 true 时清除 goods_api_app_str_cipher") + private Boolean clearGoodsApiAppStr; +} diff --git a/src/main/java/com/rj/entity/LbAssessmentApply.java b/src/main/java/com/rj/entity/LbAssessmentApply.java index 949c8d0..773813c 100644 --- a/src/main/java/com/rj/entity/LbAssessmentApply.java +++ b/src/main/java/com/rj/entity/LbAssessmentApply.java @@ -79,6 +79,10 @@ public class LbAssessmentApply implements Serializable { @JsonFlexibleLocalDateTime private LocalDateTime applicationDatetime; + @TableField("sorted_num") + @Schema(description = "排序序号") + private Integer sortedNum; + @TableField("created_at") @Schema(description = "创建时间") @JsonFlexibleLocalDateTime diff --git a/src/main/java/com/rj/entity/LbOrderRow.java b/src/main/java/com/rj/entity/LbOrderRow.java index 417c738..4e0cfc9 100644 --- a/src/main/java/com/rj/entity/LbOrderRow.java +++ b/src/main/java/com/rj/entity/LbOrderRow.java @@ -19,9 +19,12 @@ public class LbOrderRow implements Serializable { private static final long serialVersionUID = 1L; - /** 业务主键为订单 id,由调用方传入,非数据库自增 */ + /** + * 订单 id(与 {@link #tenantId} 组成复合主键 {@code (tenant_id, id)}),由调用方传入,非数据库自增。 + * MyBatis-Plus 仅标注 {@code id} 为 {@link TableId},按主键定位记录时需同时使用租户 id。 + */ @TableId(value = "id", type = IdType.INPUT) - @Schema(description = "订单 id") + @Schema(description = "订单 id(复合主键之一,须与 tenantId 一起使用)") private Long id; @TableField("old_id") diff --git a/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java b/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java new file mode 100644 index 0000000..ba390b4 --- /dev/null +++ b/src/main/java/com/rj/entity/LbThirdIntegrationConfig.java @@ -0,0 +1,212 @@ +package com.rj.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 租户第三方集成配置(表 lb_third_integration_config) + */ +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("lb_third_integration_config") +@Schema(description = "租户第三方集成配置") +@JsonIgnoreProperties(ignoreUnknown = true) +public class LbThirdIntegrationConfig implements Serializable { + + private static final long serialVersionUID = 1L; + + @TableId("id") + @Schema(description = "主键 UUID") + private String id; + + @TableField("tenant_id") + @Schema(description = "租户ID") + private String tenantId; + + @TableField("provider_code") + @Schema(description = "集成类型,如 HXR_ADMIN") + private String providerCode; + + @TableField("enabled") + @Schema(description = "总开关:1启用 0禁用") + private Integer enabled; + + @TableField("admin_base_url") + @Schema(description = "后台管理域名") + private String adminBaseUrl; + + @TableField("web_base_url") + @Schema(description = "前端 Web 域名") + private String webBaseUrl; + + @TableField("order_select_path") + @Schema(description = "订单列表 API 路径") + private String orderSelectPath; + + @TableField("user_select_path") + @Schema(description = "用户列表 API 路径") + private String userSelectPath; + + @TableField("user_update_path") + @Schema(description = "用户更新 API 路径") + private String userUpdatePath; + + @TableField("goods_api_path") + @Schema(description = "货品列表 API 路径") + private String goodsApiPath; + + @TableField("buy_api_path") + @Schema(description = "抢购 API 路径") + private String buyApiPath; + + @TableField("order_page_limit") + @Schema(description = "订单分页 limit") + private Integer orderPageLimit; + + @TableField("user_page_limit") + @Schema(description = "用户分页 limit") + private Integer userPageLimit; + + @TableField("goods_page_limit") + @Schema(description = "货品分页 limit") + private Integer goodsPageLimit; + + @TableField("order_referer") + @Schema(description = "订单 Referer") + private String orderReferer; + + @TableField("user_referer") + @Schema(description = "用户 Referer") + private String userReferer; + + @TableField("goods_api_origin") + @Schema(description = "货品 Origin") + private String goodsApiOrigin; + + @TableField("goods_api_referer") + @Schema(description = "货品 Referer") + private String goodsApiReferer; + + @TableField("auth_type") + @Schema(description = "Admin 鉴权类型") + private String authType; + + @TableField("cookie_cipher") + @Schema(description = "完整 Cookie 密文(存储字段,接口不回显)") + private byte[] cookieCipher; + + @TableField("phpsid_cipher") + @Schema(description = "PHPSID 密文(存储字段,接口不回显)") + private byte[] phpsidCipher; + + @TableField("goods_api_token_cipher") + @Schema(description = "货品/抢购 token 密文(存储字段,接口不回显)") + private byte[] goodsApiTokenCipher; + + @TableField("goods_api_app_str_cipher") + @Schema(description = "签名密钥 appStr 密文(存储字段,接口不回显)") + private byte[] goodsApiAppStrCipher; + + @TableField("credential_version") + @Schema(description = "凭证版本号") + private Integer credentialVersion; + + @TableField("credential_expire_time") + @Schema(description = "凭证预计过期时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime credentialExpireTime; + + @TableField("sync_order_resell_enabled") + @Schema(description = "定时同步未寄卖订单:1启用 0禁用") + private Integer syncOrderResellEnabled; + + @TableField("sync_order_unpaid_enabled") + @Schema(description = "定时同步未支付订单:1启用 0禁用") + private Integer syncOrderUnpaidEnabled; + + @TableField("sync_order_paid_enabled") + @Schema(description = "定时同步已支付订单:1启用 0禁用") + private Integer syncOrderPaidEnabled; + + @TableField("sync_user_enabled") + @Schema(description = "定时同步用户:1启用 0禁用") + private Integer syncUserEnabled; + + @TableField("extra_config") + @Schema(description = "扩展 JSON") + private String extraConfig; + + @TableField("last_verified_time") + @Schema(description = "最近连通性探测时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime lastVerifiedTime; + + @TableField("last_verified_ok") + @Schema(description = "最近探测结果:1成功 0失败") + private Integer lastVerifiedOk; + + @TableField("remark") + @Schema(description = "备注") + private String remark; + + @TableField("created_by") + @Schema(description = "创建人") + private String createdBy; + + @TableField("updated_by") + @Schema(description = "更新人") + private String updatedBy; + + @TableField("create_time") + @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime createTime; + + @TableField("update_time") + @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime updateTime; + + // ---------- 明文凭证(仅请求入参,不落库)---------- + + @TableField(exist = false) + @Schema(description = "完整 Cookie 明文(保存时加密入库)") + private String cookiePlain; + + @TableField(exist = false) + @Schema(description = "PHPSID 明文(保存时加密入库)") + private String phpsidPlain; + + @TableField(exist = false) + @Schema(description = "货品/抢购 token 明文(保存时加密入库)") + private String goodsApiTokenPlain; + + @TableField(exist = false) + @Schema(description = "签名密钥 appStr 明文(保存时加密入库)") + private String goodsApiAppStrPlain; + + @TableField(exist = false) + @Schema(description = "是否已配置 Cookie 密文") + private Boolean cookieConfigured; + + @TableField(exist = false) + @Schema(description = "是否已配置 PHPSID 密文") + private Boolean phpsidConfigured; + + @TableField(exist = false) + @Schema(description = "是否已配置货品 token 密文") + private Boolean goodsApiTokenConfigured; + + @TableField(exist = false) + @Schema(description = "是否已配置 appStr 密文") + private Boolean goodsApiAppStrConfigured; +} diff --git a/src/main/java/com/rj/mapper/LbThirdIntegrationConfigMapper.java b/src/main/java/com/rj/mapper/LbThirdIntegrationConfigMapper.java new file mode 100644 index 0000000..2acdc2e --- /dev/null +++ b/src/main/java/com/rj/mapper/LbThirdIntegrationConfigMapper.java @@ -0,0 +1,12 @@ +package com.rj.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.rj.entity.LbThirdIntegrationConfig; +import org.apache.ibatis.annotations.Mapper; + +/** + * 租户第三方集成配置 Mapper + */ +@Mapper +public interface LbThirdIntegrationConfigMapper extends BaseMapper { +} diff --git a/src/main/java/com/rj/service/ILbGoodsService.java b/src/main/java/com/rj/service/ILbGoodsService.java index 19b4681..5c09b20 100644 --- a/src/main/java/com/rj/service/ILbGoodsService.java +++ b/src/main/java/com/rj/service/ILbGoodsService.java @@ -36,7 +36,8 @@ public interface ILbGoodsService extends IService { Map syncFromHxrGoods(String tenantId, String token); /** - * 按 {@code total_money} 从大到小选取 {@code lb_goods},调用 hxrd {@code /api/order/buy} 抢购。 + * 按 {@code total_money} 从大到小选取金额小于39000的 {@code lb_goods},调用 hxrd {@code /api/order/buy} 抢购; + * 成功笔数达到 {@code maxBuyCount} 后停止,且循环抢购总次数不超过 {@code maxBuyCount} 的5倍。 */ Map rushBuy(String token, Integer maxBuyCount); } diff --git a/src/main/java/com/rj/service/ILbOrderRowService.java b/src/main/java/com/rj/service/ILbOrderRowService.java index a3f73b6..7ef4fd0 100644 --- a/src/main/java/com/rj/service/ILbOrderRowService.java +++ b/src/main/java/com/rj/service/ILbOrderRowService.java @@ -11,7 +11,10 @@ public interface ILbOrderRowService extends IService { Map update(LbOrderRow entity); - Map deleteById(Long id); + /** + * 按复合主键 {@code (tenant_id, id)} 删除;{@code tenantId} 为空时使用当前租户上下文。 + */ + Map deleteById(Long id, String tenantId); Map pageQuery( Integer current, diff --git a/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java b/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java new file mode 100644 index 0000000..bf48b7b --- /dev/null +++ b/src/main/java/com/rj/service/ILbThirdIntegrationConfigService.java @@ -0,0 +1,38 @@ +package com.rj.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest; +import com.rj.entity.LbThirdIntegrationConfig; + +import java.util.Map; + +/** + * 租户第三方集成配置服务 + */ +public interface ILbThirdIntegrationConfigService extends IService { + + Map add(LbThirdIntegrationConfig entity); + + Map update(LbThirdIntegrationConfig entity); + + Map deleteById(String id); + + Map pageQuery(Integer current, + Integer size, + String tenantId, + String providerCode, + Integer enabled); + + Map getDetailById(String id); + + /** + * 按租户 id 查询凭证配置(解密后返回明文,便于管理查看)。 + */ + Map getCredentialStatusByTenantId(String tenantId); + + /** + * 按租户 id 更新凭证列;至少需更新或清除一项凭证。 + */ + Map updateCredentialByTenantId( + String tenantId, LbThirdIntegrationCredentialUpdateRequest request); +} diff --git a/src/main/java/com/rj/service/impl/LbAssessmentApplyServiceImpl.java b/src/main/java/com/rj/service/impl/LbAssessmentApplyServiceImpl.java index 359ad4d..99fffee 100644 --- a/src/main/java/com/rj/service/impl/LbAssessmentApplyServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbAssessmentApplyServiceImpl.java @@ -71,7 +71,7 @@ public class LbAssessmentApplyServiceImpl moderatorName 主持人姓名; meetingNumber 会议号; applicationDatetime 申请日期时间,优先 ISO-8601(如 2026-05-15T14:30:00),也可 yyyy-MM-dd HH:mm:ss;若仅有日期可设为当天 00:00:00。 - 不要输出 id、tenantId、createdAt、updatedAt、notificationText。 + 不要输出 id、tenantId、createdAt、updatedAt、notificationText、sortedNum。 """; @Override @@ -194,6 +194,9 @@ public class LbAssessmentApplyServiceImpl entity.setCreatedAt(now); } entity.setUpdatedAt(now); + if (entity.getSortedNum() == null) { + entity.setSortedNum(resolveNextSortedNum(entity.getTenantId())); + } refreshNotificationText(entity); boolean ok = this.save(entity); @@ -512,6 +515,7 @@ public class LbAssessmentApplyServiceImpl queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim()) .ge(LbAssessmentApply::getApplicationDatetime, start) .le(LbAssessmentApply::getApplicationDatetime, end) + .orderByAsc(LbAssessmentApply::getSortedNum) .orderByDesc(LbAssessmentApply::getApplicationDatetime); List rows = this.list(queryWrapper); @@ -554,7 +558,7 @@ public class LbAssessmentApplyServiceImpl for (LbAssessmentApply item : rows) { Row row = sheet.createRow(rowIndex); row.setHeightInPoints(20); - createCell(row, 0, String.valueOf(rowIndex), dataStyle); + createCell(row, 0, formatSortedNum(item.getSortedNum(), rowIndex), dataStyle); createCell(row, 1, nullToEmpty(item.getApplicantName()), dataStyle); createCell(row, 2, nullToEmpty(item.getApplicantPhone()), dataStyle); createCell(row, 3, nullToEmpty(item.getColleagueName()), dataStyle); @@ -633,11 +637,28 @@ public class LbAssessmentApplyServiceImpl if (meetingNumber != null && !meetingNumber.trim().isEmpty()) { queryWrapper.like(LbAssessmentApply::getMeetingNumber, meetingNumber.trim()); } - queryWrapper.orderByDesc(LbAssessmentApply::getUpdatedAt) + queryWrapper.orderByAsc(LbAssessmentApply::getSortedNum) + .orderByDesc(LbAssessmentApply::getUpdatedAt) .orderByDesc(LbAssessmentApply::getCreatedAt); return queryWrapper; } + private Integer resolveNextSortedNum(String tenantId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(LbAssessmentApply::getTenantId, tenantId.trim()) + .orderByDesc(LbAssessmentApply::getSortedNum) + .last("LIMIT 1"); + LbAssessmentApply latest = this.getOne(queryWrapper); + if (latest == null || latest.getSortedNum() == null) { + return 1; + } + return latest.getSortedNum() + 1; + } + + private static String formatSortedNum(Integer sortedNum, int fallback) { + return sortedNum != null ? String.valueOf(sortedNum) : String.valueOf(fallback); + } + private static String nullToEmpty(String value) { return value == null ? "" : value; } diff --git a/src/main/java/com/rj/service/impl/LbGoodsServiceImpl.java b/src/main/java/com/rj/service/impl/LbGoodsServiceImpl.java index 519e3ee..58977a2 100644 --- a/src/main/java/com/rj/service/impl/LbGoodsServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbGoodsServiceImpl.java @@ -32,6 +32,8 @@ import java.util.Optional; @RequiredArgsConstructor public class LbGoodsServiceImpl extends ServiceImpl implements ILbGoodsService { + private static final BigDecimal RUSH_BUY_MAX_TOTAL_MONEY = new BigDecimal("39000"); + private static final DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final HxrAdminGoodsService hxrAdminGoodsService; @@ -387,37 +389,46 @@ public class LbGoodsServiceImpl extends ServiceImpl impl LambdaQueryWrapper w = new LambdaQueryWrapper<>(); w.isNotNull(LbGoods::getSellerId); + w.lt(LbGoods::getTotalMoney, RUSH_BUY_MAX_TOTAL_MONEY); w.orderByDesc(LbGoods::getTotalMoney).orderByDesc(LbGoods::getId); List goodsList = this.list(w); if (goodsList.isEmpty()) { result.put("success", false); - result.put("message", "lb_goods 中无可抢购货品"); + result.put("message", "lb_goods 中无可抢购货品(金额需小于39000)"); result.put("successCount", 0); result.put("failCount", 0); result.put("details", List.of()); return result; } + int maxAttempts = maxBuyCount * 10; List> details = new ArrayList<>(); int successCount = 0; int failCount = 0; + int attemptCount = 0; boolean stoppedByMax = false; + boolean stoppedByMaxAttempts = false; for (LbGoods goods : goodsList) { if (successCount >= maxBuyCount) { stoppedByMax = true; break; } + if (attemptCount >= maxAttempts) { + stoppedByMaxAttempts = true; + break; + } if (goods.getId() == null) { continue; } + attemptCount++; Map item = new LinkedHashMap<>(); item.put("id", goods.getId()); item.put("sellerId", goods.getSellerId()); item.put("title", goods.getTitle()); item.put("totalMoney", goods.getTotalMoney()); - + log.info("正在抢购货品是:{}",item.toString()); HxrAdminBuyService.BuyApiResult buyResult = hxrAdminBuyService.buy(goods.getId(), goods.getSellerId(), token); item.put("apiCode", buyResult.apiCode()); @@ -438,7 +449,10 @@ public class LbGoodsServiceImpl extends ServiceImpl impl : "抢购未成功"); result.put("successCount", successCount); result.put("failCount", failCount); + result.put("attemptCount", attemptCount); + result.put("maxAttempts", maxAttempts); result.put("stoppedByMax", stoppedByMax); + result.put("stoppedByMaxAttempts", stoppedByMaxAttempts); result.put("details", details); return result; } catch (Exception e) { diff --git a/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java b/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java index 900ad78..24120be 100644 --- a/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java +++ b/src/main/java/com/rj/service/impl/LbOrderRowServiceImpl.java @@ -99,13 +99,12 @@ public class LbOrderRowServiceImpl extends ServiceImpl() + .eq(LbOrderRow::getTenantId, tenantId) + .eq(LbOrderRow::getId, entity.getId())); result.put("success", ok); result.put("message", ok ? "编辑成功" : "编辑失败"); if (ok) { - result.put("data", this.getById(entity.getId())); + result.put("data", getByTenantAndId(tenantId, entity.getId())); } return result; } catch (Exception e) { @@ -160,10 +171,30 @@ public class LbOrderRowServiceImpl extends ServiceImpl deleteById(Long id) { + public Map deleteById(Long id, String tenantId) { Map result = new HashMap<>(); try { - boolean ok = this.removeById(id); + if (id == null) { + result.put("success", false); + result.put("message", "id不能为空"); + return result; + } + String tid = resolveTenantId(tenantId); + if (tid == null) { + result.put("success", false); + result.put("message", "tenantId不能为空"); + return result; + } + if (getByTenantAndId(tid, id) == null) { + result.put("success", false); + result.put("message", "记录不存在"); + return result; + } + boolean ok = + this.remove( + new LambdaQueryWrapper() + .eq(LbOrderRow::getTenantId, tid) + .eq(LbOrderRow::getId, id)); result.put("success", ok); result.put("message", ok ? "删除成功" : "删除失败"); return result; @@ -615,19 +646,25 @@ public class LbOrderRowServiceImpl extends ServiceImpl entities) { if (entities == null || entities.isEmpty()) { return 0; } - Map deduped = new LinkedHashMap<>(); + Map deduped = new LinkedHashMap<>(); for (LbOrderRow entity : entities) { - if (entity.getId() != null) { - deduped.put(entity.getId(), entity); + if (entity.getId() == null) { + continue; } + String tenantId = trimToNull(entity.getTenantId()); + if (tenantId == null) { + continue; + } + entity.setTenantId(tenantId); + deduped.put(compositeKey(tenantId, entity.getId()), entity); } if (deduped.isEmpty()) { return 0; @@ -764,4 +801,35 @@ public class LbOrderRowServiceImpl extends ServiceImpl() + .eq(LbOrderRow::getTenantId, tenantId.trim()) + .eq(LbOrderRow::getId, id), + false); + } + + private String resolveTenantId(String tenantIdFromParam) { + String current = trimToNull(TenantContextHolder.getTenantId()); + if (current != null) { + return current; + } + return trimToNull(tenantIdFromParam); + } + + private static String compositeKey(String tenantId, Long id) { + return tenantId + ":" + id; + } + + private static String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } } diff --git a/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java b/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java new file mode 100644 index 0000000..70c9cc5 --- /dev/null +++ b/src/main/java/com/rj/service/impl/LbThirdIntegrationConfigServiceImpl.java @@ -0,0 +1,543 @@ +package com.rj.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.rj.common.LbThirdIntegrationConstants; +import com.rj.common.PasswordUtil; +import com.rj.dto.LbThirdIntegrationCredentialUpdateRequest; +import com.rj.entity.LbThirdIntegrationConfig; +import com.rj.mapper.LbThirdIntegrationConfigMapper; +import com.rj.service.ILbThirdIntegrationConfigService; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * 租户第三方集成配置服务实现 + */ +@Service +public class LbThirdIntegrationConfigServiceImpl + extends ServiceImpl + implements ILbThirdIntegrationConfigService { + + @Override + public Map add(LbThirdIntegrationConfig entity) { + Map result = new HashMap<>(); + try { + String validationError = validateRequiredForAdd(entity); + if (validationError != null) { + result.put("success", false); + result.put("message", validationError); + return result; + } + + String tenantId = entity.getTenantId().trim(); + if (this.count(new LambdaQueryWrapper() + .eq(LbThirdIntegrationConfig::getTenantId, tenantId)) > 0) { + result.put("success", false); + result.put("message", "该租户已存在集成配置,不能重复添加"); + return result; + } + + applyDefaults(entity); + entity.setTenantId(tenantId); + entity.setId(UUID.randomUUID().toString()); + LocalDateTime now = LocalDateTime.now(); + entity.setCreateTime(now); + entity.setUpdateTime(now); + + boolean credentialUpdated = applyCredentialEncryption(entity, null); + if (credentialUpdated && entity.getCredentialVersion() == null) { + entity.setCredentialVersion(1); + } else if (entity.getCredentialVersion() == null) { + entity.setCredentialVersion(1); + } + + boolean ok = this.save(entity); + result.put("success", ok); + result.put("message", ok ? "添加成功" : "添加失败"); + if (ok) { + result.put("data", maskForResponse(this.getById(entity.getId()))); + } + return result; + } catch (Exception e) { + result.put("success", false); + result.put("message", "添加异常:" + e.getMessage()); + return result; + } + } + + @Override + public Map update(LbThirdIntegrationConfig entity) { + Map result = new HashMap<>(); + try { + if (!StringUtils.hasText(entity.getId())) { + result.put("success", false); + result.put("message", "ID不能为空"); + return result; + } + + LbThirdIntegrationConfig existing = this.getById(entity.getId().trim()); + if (existing == null) { + result.put("success", false); + result.put("message", "记录不存在"); + return result; + } + + if (StringUtils.hasText(entity.getTenantId()) + && !entity.getTenantId().trim().equals(existing.getTenantId())) { + String newTenantId = entity.getTenantId().trim(); + long dup = this.count(new LambdaQueryWrapper() + .eq(LbThirdIntegrationConfig::getTenantId, newTenantId) + .ne(LbThirdIntegrationConfig::getId, existing.getId())); + if (dup > 0) { + result.put("success", false); + result.put("message", "目标租户已存在集成配置"); + return result; + } + } + + boolean credentialUpdated = applyCredentialEncryption(entity, existing); + if (credentialUpdated) { + int version = existing.getCredentialVersion() == null ? 1 : existing.getCredentialVersion(); + entity.setCredentialVersion(version + 1); + } + + entity.setUpdateTime(LocalDateTime.now()); + boolean ok = this.updateById(entity); + result.put("success", ok); + result.put("message", ok ? "更新成功" : "更新失败"); + if (ok) { + result.put("data", maskForResponse(this.getById(entity.getId()))); + } + return result; + } catch (Exception e) { + result.put("success", false); + result.put("message", "更新异常:" + e.getMessage()); + return result; + } + } + + @Override + public Map deleteById(String id) { + Map result = new HashMap<>(); + try { + if (!StringUtils.hasText(id)) { + result.put("success", false); + result.put("message", "ID不能为空"); + return result; + } + boolean ok = this.removeById(id.trim()); + result.put("success", ok); + result.put("message", ok ? "删除成功" : "删除失败"); + return result; + } catch (Exception e) { + result.put("success", false); + result.put("message", "删除异常:" + e.getMessage()); + return result; + } + } + + @Override + public Map pageQuery(Integer current, + Integer size, + String tenantId, + String providerCode, + Integer enabled) { + Map result = new HashMap<>(); + try { + if (current == null || current < 1) { + current = 1; + } + if (size == null || size < 1) { + size = 10; + } + + LambdaQueryWrapper q = new LambdaQueryWrapper<>(); + if (StringUtils.hasText(tenantId)) { + q.eq(LbThirdIntegrationConfig::getTenantId, tenantId.trim()); + } + if (StringUtils.hasText(providerCode)) { + q.eq(LbThirdIntegrationConfig::getProviderCode, providerCode.trim()); + } + if (enabled != null) { + q.eq(LbThirdIntegrationConfig::getEnabled, enabled); + } + q.orderByDesc(LbThirdIntegrationConfig::getUpdateTime) + .orderByDesc(LbThirdIntegrationConfig::getCreateTime); + + Page page = this.page(new Page<>(current, size), q); + List records = page.getRecords().stream() + .map(this::maskForResponse) + .toList(); + + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", records); + result.put("total", page.getTotal()); + result.put("current", page.getCurrent()); + result.put("size", page.getSize()); + result.put("pages", page.getPages()); + return result; + } catch (Exception e) { + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return result; + } + } + + @Override + public Map getDetailById(String id) { + Map result = new HashMap<>(); + try { + if (!StringUtils.hasText(id)) { + result.put("success", false); + result.put("message", "ID不能为空"); + return result; + } + LbThirdIntegrationConfig data = this.getById(id.trim()); + if (data == null) { + result.put("success", false); + result.put("message", "记录不存在"); + return result; + } + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", maskForResponse(data)); + return result; + } catch (Exception e) { + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return result; + } + } + + @Override + public Map getCredentialStatusByTenantId(String tenantId) { + Map result = new HashMap<>(); + try { + if (!StringUtils.hasText(tenantId)) { + result.put("success", false); + result.put("message", "tenantId不能为空"); + return result; + } + LbThirdIntegrationConfig config = getByTenantIdOrNull(tenantId.trim()); + if (config == null) { + result.put("success", false); + result.put("message", "该租户未配置第三方集成"); + return result; + } + result.put("success", true); + result.put("message", "查询成功"); + result.put("data", buildCredentialStatus(config, true)); + return result; + } catch (Exception e) { + result.put("success", false); + result.put("message", "查询异常:" + e.getMessage()); + return result; + } + } + + @Override + public Map updateCredentialByTenantId( + String tenantId, LbThirdIntegrationCredentialUpdateRequest request) { + Map result = new HashMap<>(); + try { + if (!StringUtils.hasText(tenantId)) { + result.put("success", false); + result.put("message", "tenantId不能为空"); + return result; + } + if (request == null) { + result.put("success", false); + result.put("message", "请求体不能为空"); + return result; + } + + LbThirdIntegrationConfig existing = getByTenantIdOrNull(tenantId.trim()); + if (existing == null) { + result.put("success", false); + result.put("message", "该租户未配置第三方集成,请先新增配置"); + return result; + } + + String validationError = validateCredentialUpdateRequest(request); + if (validationError != null) { + result.put("success", false); + result.put("message", validationError); + return result; + } + + LambdaUpdateWrapper uw = new LambdaUpdateWrapper<>(); + uw.eq(LbThirdIntegrationConfig::getId, existing.getId()); + applyCredentialUpdateWrapper(uw, request); + + if (request.getCredentialExpireTime() != null) { + uw.set(LbThirdIntegrationConfig::getCredentialExpireTime, request.getCredentialExpireTime()); + } + if (StringUtils.hasText(request.getUpdatedBy())) { + uw.set(LbThirdIntegrationConfig::getUpdatedBy, request.getUpdatedBy().trim()); + } + + int version = existing.getCredentialVersion() == null ? 1 : existing.getCredentialVersion(); + uw.set(LbThirdIntegrationConfig::getCredentialVersion, version + 1); + uw.set(LbThirdIntegrationConfig::getUpdateTime, LocalDateTime.now()); + + boolean ok = this.update(uw); + result.put("success", ok); + result.put("message", ok ? "凭证更新成功" : "凭证更新失败"); + if (ok) { + LbThirdIntegrationConfig latest = this.getById(existing.getId()); + result.put("data", buildCredentialStatus(latest, true)); + } + return result; + } catch (Exception e) { + result.put("success", false); + result.put("message", "凭证更新异常:" + e.getMessage()); + return result; + } + } + + private LbThirdIntegrationConfig getByTenantIdOrNull(String tenantId) { + return this.getOne(new LambdaQueryWrapper() + .eq(LbThirdIntegrationConfig::getTenantId, tenantId) + .last("LIMIT 1")); + } + + private static String validateCredentialUpdateRequest(LbThirdIntegrationCredentialUpdateRequest request) { + boolean hasUpdate = StringUtils.hasText(request.getCookie()) + || StringUtils.hasText(request.getPhpsid()) + || StringUtils.hasText(request.getGoodsApiToken()) + || StringUtils.hasText(request.getGoodsApiAppStr()); + boolean hasClear = Boolean.TRUE.equals(request.getClearCookie()) + || Boolean.TRUE.equals(request.getClearPhpsid()) + || Boolean.TRUE.equals(request.getClearGoodsApiToken()) + || Boolean.TRUE.equals(request.getClearGoodsApiAppStr()); + if (!hasUpdate && !hasClear) { + return "请至少提供一项凭证明文,或指定一项 clear* 清除操作"; + } + if (Boolean.TRUE.equals(request.getClearCookie()) && StringUtils.hasText(request.getCookie())) { + return "clearCookie 与 cookie 不能同时传"; + } + if (Boolean.TRUE.equals(request.getClearPhpsid()) && StringUtils.hasText(request.getPhpsid())) { + return "clearPhpsid 与 phpsid 不能同时传"; + } + if (Boolean.TRUE.equals(request.getClearGoodsApiToken()) && StringUtils.hasText(request.getGoodsApiToken())) { + return "clearGoodsApiToken 与 goodsApiToken 不能同时传"; + } + if (Boolean.TRUE.equals(request.getClearGoodsApiAppStr()) && StringUtils.hasText(request.getGoodsApiAppStr())) { + return "clearGoodsApiAppStr 与 goodsApiAppStr 不能同时传"; + } + return null; + } + + private void applyCredentialUpdateWrapper( + LambdaUpdateWrapper uw, + LbThirdIntegrationCredentialUpdateRequest request) { + if (Boolean.TRUE.equals(request.getClearCookie())) { + uw.set(LbThirdIntegrationConfig::getCookieCipher, null); + } else if (StringUtils.hasText(request.getCookie())) { + uw.set(LbThirdIntegrationConfig::getCookieCipher, encryptToBytes(request.getCookie())); + } + + if (Boolean.TRUE.equals(request.getClearPhpsid())) { + uw.set(LbThirdIntegrationConfig::getPhpsidCipher, null); + } else if (StringUtils.hasText(request.getPhpsid())) { + uw.set(LbThirdIntegrationConfig::getPhpsidCipher, encryptToBytes(request.getPhpsid())); + } + + if (Boolean.TRUE.equals(request.getClearGoodsApiToken())) { + uw.set(LbThirdIntegrationConfig::getGoodsApiTokenCipher, null); + } else if (StringUtils.hasText(request.getGoodsApiToken())) { + uw.set(LbThirdIntegrationConfig::getGoodsApiTokenCipher, encryptToBytes(request.getGoodsApiToken())); + } + + if (Boolean.TRUE.equals(request.getClearGoodsApiAppStr())) { + uw.set(LbThirdIntegrationConfig::getGoodsApiAppStrCipher, null); + } else if (StringUtils.hasText(request.getGoodsApiAppStr())) { + uw.set(LbThirdIntegrationConfig::getGoodsApiAppStrCipher, encryptToBytes(request.getGoodsApiAppStr())); + } + } + + private Map buildCredentialStatus(LbThirdIntegrationConfig config, boolean includePlaintext) { + Map data = new HashMap<>(); + data.put("id", config.getId()); + data.put("tenantId", config.getTenantId()); + data.put("providerCode", config.getProviderCode()); + data.put("authType", config.getAuthType()); + data.put("cookieConfigured", hasCipher(config.getCookieCipher())); + data.put("phpsidConfigured", hasCipher(config.getPhpsidCipher())); + data.put("goodsApiTokenConfigured", hasCipher(config.getGoodsApiTokenCipher())); + data.put("goodsApiAppStrConfigured", hasCipher(config.getGoodsApiAppStrCipher())); + data.put("credentialVersion", config.getCredentialVersion()); + data.put("credentialExpireTime", config.getCredentialExpireTime()); + data.put("lastVerifiedTime", config.getLastVerifiedTime()); + data.put("lastVerifiedOk", config.getLastVerifiedOk()); + data.put("updateTime", config.getUpdateTime()); + if (includePlaintext) { + data.put("cookie", decryptFromBytes(config.getCookieCipher())); + data.put("phpsid", decryptFromBytes(config.getPhpsidCipher())); + data.put("goodsApiToken", decryptFromBytes(config.getGoodsApiTokenCipher())); + data.put("goodsApiAppStr", decryptFromBytes(config.getGoodsApiAppStrCipher())); + } + return data; + } + + private String validateRequiredForAdd(LbThirdIntegrationConfig entity) { + if (entity == null) { + return "请求体不能为空"; + } + if (!StringUtils.hasText(entity.getTenantId())) { + return "tenantId不能为空"; + } + if (!StringUtils.hasText(entity.getAdminBaseUrl())) { + return "adminBaseUrl不能为空"; + } + if (!StringUtils.hasText(entity.getWebBaseUrl())) { + return "webBaseUrl不能为空"; + } + return null; + } + + private void applyDefaults(LbThirdIntegrationConfig entity) { + if (!StringUtils.hasText(entity.getProviderCode())) { + entity.setProviderCode(LbThirdIntegrationConstants.PROVIDER_HXR_ADMIN); + } + if (entity.getEnabled() == null) { + entity.setEnabled(1); + } + if (!StringUtils.hasText(entity.getOrderSelectPath())) { + entity.setOrderSelectPath(LbThirdIntegrationConstants.DEFAULT_ORDER_SELECT_PATH); + } + if (!StringUtils.hasText(entity.getUserSelectPath())) { + entity.setUserSelectPath(LbThirdIntegrationConstants.DEFAULT_USER_SELECT_PATH); + } + if (!StringUtils.hasText(entity.getUserUpdatePath())) { + entity.setUserUpdatePath(LbThirdIntegrationConstants.DEFAULT_USER_UPDATE_PATH); + } + if (!StringUtils.hasText(entity.getGoodsApiPath())) { + entity.setGoodsApiPath(LbThirdIntegrationConstants.DEFAULT_GOODS_API_PATH); + } + if (!StringUtils.hasText(entity.getBuyApiPath())) { + entity.setBuyApiPath(LbThirdIntegrationConstants.DEFAULT_BUY_API_PATH); + } + if (entity.getOrderPageLimit() == null) { + entity.setOrderPageLimit(LbThirdIntegrationConstants.DEFAULT_ORDER_PAGE_LIMIT); + } + if (entity.getUserPageLimit() == null) { + entity.setUserPageLimit(LbThirdIntegrationConstants.DEFAULT_USER_PAGE_LIMIT); + } + if (entity.getGoodsPageLimit() == null) { + entity.setGoodsPageLimit(LbThirdIntegrationConstants.DEFAULT_GOODS_PAGE_LIMIT); + } + if (!StringUtils.hasText(entity.getAuthType())) { + entity.setAuthType(LbThirdIntegrationConstants.AUTH_COOKIE_PHPSID); + } + if (entity.getSyncOrderResellEnabled() == null) { + entity.setSyncOrderResellEnabled(0); + } + if (entity.getSyncOrderUnpaidEnabled() == null) { + entity.setSyncOrderUnpaidEnabled(0); + } + if (entity.getSyncOrderPaidEnabled() == null) { + entity.setSyncOrderPaidEnabled(0); + } + if (entity.getSyncUserEnabled() == null) { + entity.setSyncUserEnabled(0); + } + if (entity.getCredentialVersion() == null) { + entity.setCredentialVersion(1); + } + } + + /** + * 将明文凭证加密为 VARBINARY;未传明文则保留已有密文(更新场景)。 + * + * @return 是否有任一凭证字段被更新 + */ + private boolean applyCredentialEncryption(LbThirdIntegrationConfig entity, LbThirdIntegrationConfig existing) { + boolean updated = false; + + if (StringUtils.hasText(entity.getCookiePlain())) { + entity.setCookieCipher(encryptToBytes(entity.getCookiePlain())); + updated = true; + } else if (existing != null) { + entity.setCookieCipher(existing.getCookieCipher()); + } + + if (StringUtils.hasText(entity.getPhpsidPlain())) { + entity.setPhpsidCipher(encryptToBytes(entity.getPhpsidPlain())); + updated = true; + } else if (existing != null) { + entity.setPhpsidCipher(existing.getPhpsidCipher()); + } + + if (StringUtils.hasText(entity.getGoodsApiTokenPlain())) { + entity.setGoodsApiTokenCipher(encryptToBytes(entity.getGoodsApiTokenPlain())); + updated = true; + } else if (existing != null) { + entity.setGoodsApiTokenCipher(existing.getGoodsApiTokenCipher()); + } + + if (StringUtils.hasText(entity.getGoodsApiAppStrPlain())) { + entity.setGoodsApiAppStrCipher(encryptToBytes(entity.getGoodsApiAppStrPlain())); + updated = true; + } else if (existing != null) { + entity.setGoodsApiAppStrCipher(existing.getGoodsApiAppStrCipher()); + } + + entity.setCookiePlain(null); + entity.setPhpsidPlain(null); + entity.setGoodsApiTokenPlain(null); + entity.setGoodsApiAppStrPlain(null); + return updated; + } + + private static byte[] encryptToBytes(String plainText) { + String encrypted = PasswordUtil.encryptReversibleWithDefaultSalt(plainText); + return encrypted.getBytes(StandardCharsets.UTF_8); + } + + /** + * 供业务层读取凭证明文(内部使用) + */ + public static String decryptFromBytes(byte[] cipherBytes) { + if (cipherBytes == null || cipherBytes.length == 0) { + return null; + } + String stored = new String(cipherBytes, StandardCharsets.UTF_8); + return PasswordUtil.decryptReversibleWithDefaultSalt(stored); + } + + private LbThirdIntegrationConfig maskForResponse(LbThirdIntegrationConfig entity) { + if (entity == null) { + return null; + } + entity.setCookieConfigured(hasCipher(entity.getCookieCipher())); + entity.setPhpsidConfigured(hasCipher(entity.getPhpsidCipher())); + entity.setGoodsApiTokenConfigured(hasCipher(entity.getGoodsApiTokenCipher())); + entity.setGoodsApiAppStrConfigured(hasCipher(entity.getGoodsApiAppStrCipher())); + + entity.setCookieCipher(null); + entity.setPhpsidCipher(null); + entity.setGoodsApiTokenCipher(null); + entity.setGoodsApiAppStrCipher(null); + entity.setCookiePlain(null); + entity.setPhpsidPlain(null); + entity.setGoodsApiTokenPlain(null); + entity.setGoodsApiAppStrPlain(null); + return entity; + } + + private static boolean hasCipher(byte[] cipher) { + return cipher != null && cipher.length > 0; + } +} diff --git a/src/main/resources/mapper/LbOrderRowMapper.xml b/src/main/resources/mapper/LbOrderRowMapper.xml index 26e5a06..3e1c35b 100644 --- a/src/main/resources/mapper/LbOrderRowMapper.xml +++ b/src/main/resources/mapper/LbOrderRowMapper.xml @@ -2,7 +2,7 @@ - + INSERT INTO lb_order_row ( id, old_id, tenant_id, data_type, diff --git a/src/main/sql/lb_assessment_apply_alter_add_sorted_num.sql b/src/main/sql/lb_assessment_apply_alter_add_sorted_num.sql new file mode 100644 index 0000000..2d8f867 --- /dev/null +++ b/src/main/sql/lb_assessment_apply_alter_add_sorted_num.sql @@ -0,0 +1,8 @@ +-- ============================================================================= +-- 升级脚本:为 lb_assessment_apply 表增加 sorted_num 列 +-- ============================================================================= + +SET NAMES utf8mb4; + +ALTER TABLE `lb_assessment_apply` + ADD COLUMN `sorted_num` int NULL DEFAULT NULL COMMENT '排序序号' AFTER `application_datetime`; diff --git a/src/main/sql/lb_third_integration_config.sql b/src/main/sql/lb_third_integration_config.sql new file mode 100644 index 0000000..1b25f4a --- /dev/null +++ b/src/main/sql/lb_third_integration_config.sql @@ -0,0 +1,60 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +DROP TABLE IF EXISTS `lb_third_integration_config`; +CREATE TABLE `lb_third_integration_config` ( + `id` VARCHAR(36) NOT NULL COMMENT '主键 UUID', + `tenant_id` VARCHAR(36) NOT NULL COMMENT '租户ID,关联 tenant.id,每租户唯一', + `provider_code` VARCHAR(32) NOT NULL DEFAULT 'HXR_ADMIN' COMMENT '集成类型:HXR_ADMIN 等', + `enabled` TINYINT NOT NULL DEFAULT 1 COMMENT '总开关:1启用 0禁用', + + `admin_base_url` VARCHAR(256) NOT NULL COMMENT '后台管理域名,如 https://hxrdhoutai.hxrdsm.cn', + `web_base_url` VARCHAR(256) NOT NULL COMMENT '前端 Web 域名,如 https://hxrdweb.hxrdsm.cn', + + `order_select_path` VARCHAR(128) NOT NULL DEFAULT '/app/admin/order/select' COMMENT '订单列表 API 路径', + `user_select_path` VARCHAR(128) NOT NULL DEFAULT '/app/admin/user/select' COMMENT '用户列表 API 路径', + `user_update_path` VARCHAR(128) NOT NULL DEFAULT '/app/admin/user/update' COMMENT '用户更新 API 路径', + `goods_api_path` VARCHAR(128) NOT NULL DEFAULT '/api/order/goods' COMMENT '货品列表 API 路径', + `buy_api_path` VARCHAR(128) NOT NULL DEFAULT '/api/order/buy' COMMENT '抢购 API 路径', + + `order_page_limit` INT NOT NULL DEFAULT 90 COMMENT '订单分页 limit', + `user_page_limit` INT NOT NULL DEFAULT 90 COMMENT '用户分页 limit', + `goods_page_limit` INT NOT NULL DEFAULT 20 COMMENT '货品分页 limit', + `order_referer` VARCHAR(512) DEFAULT NULL COMMENT '订单 Referer;空则 {admin_base_url}/app/admin/order/index', + `user_referer` VARCHAR(512) DEFAULT NULL COMMENT '用户 Referer;空则 {admin_base_url}/app/admin/user/index', + `goods_api_origin` VARCHAR(256) DEFAULT NULL COMMENT '货品 Origin;空则 web_base_url', + `goods_api_referer` VARCHAR(256) DEFAULT NULL COMMENT '货品 Referer;空则 web_base_url/', + + `auth_type` VARCHAR(32) NOT NULL DEFAULT 'COOKIE_PHPSID' COMMENT 'Admin 鉴权类型', + `cookie_cipher` VARBINARY(1024) DEFAULT NULL COMMENT '完整 Cookie 密文(优先使用)', + `phpsid_cipher` VARBINARY(512) DEFAULT NULL COMMENT 'PHPSID 值密文', + `goods_api_token_cipher` VARBINARY(512) DEFAULT NULL COMMENT '货品/抢购 token 密文', + `goods_api_app_str_cipher` VARBINARY(512) DEFAULT NULL COMMENT '签名密钥 appStr 密文', + `credential_version` INT NOT NULL DEFAULT 1 COMMENT '凭证版本号,轮换时递增', + `credential_expire_time` DATETIME DEFAULT NULL COMMENT '凭证预计过期时间', + + `sync_order_resell_enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '定时同步未寄卖订单:1启用 0禁用', + `sync_order_unpaid_enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '定时同步未支付订单:1启用 0禁用', + `sync_order_paid_enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '定时同步已支付订单:1启用 0禁用', + `sync_user_enabled` TINYINT NOT NULL DEFAULT 0 COMMENT '定时同步用户:1启用 0禁用', + + `extra_config` JSON DEFAULT NULL COMMENT '扩展 JSON(超时、status 值等)', + `last_verified_time` DATETIME DEFAULT NULL COMMENT '最近连通性探测时间', + `last_verified_ok` TINYINT DEFAULT NULL COMMENT '最近探测结果:1成功 0失败', + `remark` VARCHAR(512) DEFAULT NULL COMMENT '备注', + `created_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人', + `updated_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `uk_ltic_tenant` (`tenant_id`) USING BTREE, + KEY `idx_ltic_enabled` (`enabled`) USING BTREE, + KEY `idx_ltic_sync` (`sync_order_resell_enabled`, `sync_order_unpaid_enabled`, `sync_order_paid_enabled`) USING BTREE +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_0900_ai_ci + COMMENT = '租户第三方集成配置(单表)' + ROW_FORMAT = DYNAMIC; + +SET FOREIGN_KEY_CHECKS = 1;