feat(profit-share): 实现分账管理V2版本功能

- 新增分账规则的创建、查询、更新、启用、禁用和删除接口
- 新增分账记录的查询接口,支持按景区、订单ID等多种方式查询
- 新增手动触发分账和计算分账结果的功能接口
- 新增获取支持类型的接口,方便前端展示和选择- 集成分账服务Feign客户端,实现与zt-profitshare微服务通信
- 添加Kafka消息配置,支持分账和退款消息的发送
- 完善DTO结构定义,包括规则、记录、明细及消息相关实体类
- 实现集成服务层,封装对分账服务的操作并提供fallback机制
- 控制器层增加参数校验和异常处理逻辑,提高系统健壮性- 所有接口均遵循RESTful设计规范,并提供详细的日志记录
This commit is contained in:
2025-10-13 20:30:46 +08:00
parent be375067ce
commit bdeb41bead
20 changed files with 2053 additions and 1 deletions

View File

@@ -0,0 +1,109 @@
package com.ycwl.basic.integration.profitshare.client;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.profitshare.dto.CalculateResultVO;
import com.ycwl.basic.integration.profitshare.dto.CalculateShareRequest;
import com.ycwl.basic.integration.profitshare.dto.ManualShareRequest;
import com.ycwl.basic.integration.profitshare.dto.TypesVO;
import com.ycwl.basic.integration.profitshare.dto.record.RecordDetailVO;
import com.ycwl.basic.integration.profitshare.dto.record.RecordVO;
import com.ycwl.basic.integration.profitshare.dto.rule.CreateRuleRequest;
import com.ycwl.basic.integration.profitshare.dto.rule.RuleVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
/**
* 分账服务Feign客户端
*
* @author Claude Code
* @date 2025-01-11
*/
@FeignClient(name = "zt-profitshare", contextId = "profit-share-v2", path = "/api/profit-share/v2")
public interface ProfitShareClient {
/**
* 创建分账规则
*/
@PostMapping("/rules")
CommonResponse<RuleVO> createRule(@RequestBody CreateRuleRequest request);
/**
* 查询分账规则列表
*/
@GetMapping("/rules")
CommonResponse<PageResponse<RuleVO>> listRules(@RequestParam(value = "scenic_id", required = false) Long scenicId,
@RequestParam(value = "status", required = false) String status,
@RequestParam(value = "rule_type", required = false) String ruleType,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "page_size", defaultValue = "10") Integer pageSize);
/**
* 获取分账规则详情
*/
@GetMapping("/rules/{id}")
CommonResponse<RuleVO> getRule(@PathVariable("id") Long ruleId);
/**
* 更新分账规则
*/
@PutMapping("/rules/{id}")
CommonResponse<RuleVO> updateRule(@PathVariable("id") Long ruleId,
@RequestBody CreateRuleRequest request);
/**
* 删除分账规则
*/
@DeleteMapping("/rules/{id}")
CommonResponse<Void> deleteRule(@PathVariable("id") Long ruleId);
/**
* 启用规则
*/
@PutMapping("/rules/{id}/enable")
CommonResponse<Void> enableRule(@PathVariable("id") Long ruleId);
/**
* 禁用规则
*/
@PutMapping("/rules/{id}/disable")
CommonResponse<Void> disableRule(@PathVariable("id") Long ruleId);
/**
* 查询景区分账记录
*/
@GetMapping("/records/scenic/{scenic_id}")
CommonResponse<PageResponse<RecordVO>> getRecordsByScenic(@PathVariable("scenic_id") Long scenicId,
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "page_size", defaultValue = "10") Integer pageSize);
/**
* 查询分账记录详情
*/
@GetMapping("/records/{id}")
CommonResponse<RecordDetailVO> getRecordById(@PathVariable("id") Long recordId);
/**
* 按订单ID查询分账记录
*/
@GetMapping("/records/order/{order_id}")
CommonResponse<RecordDetailVO> getRecordByOrderId(@PathVariable("order_id") String orderId);
/**
* 手动触发分账
*/
@PostMapping("/manual")
CommonResponse<Void> manualShare(@RequestBody ManualShareRequest request);
/**
* 计算分账金额(不执行)
*/
@PostMapping("/calculate")
CommonResponse<CalculateResultVO> calculateShare(@RequestBody CalculateShareRequest request);
/**
* 获取支持的类型
*/
@GetMapping("/types")
CommonResponse<TypesVO> getSupportedTypes();
}

View File

@@ -0,0 +1,21 @@
package com.ycwl.basic.integration.profitshare.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 分账服务集成配置
*
* @author Claude Code
* @date 2025-01-11
*/
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "integration.profitshare")
public class ProfitShareIntegrationConfig {
public ProfitShareIntegrationConfig() {
log.info("ZT-ProfitShare集成配置初始化完成");
}
}

View File

@@ -0,0 +1,77 @@
package com.ycwl.basic.integration.profitshare.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
* 计算分账结果VO
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CalculateResultVO {
/**
* 总金额
*/
@JsonProperty("total_amount")
private Double totalAmount;
/**
* 分账明细列表
*/
@JsonProperty("details")
private List<CalculateDetailVO> details;
/**
* 分账明细VO
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class CalculateDetailVO {
/**
* 接收人名称
*/
@JsonProperty("recipient_name")
private String recipientName;
/**
* 接收人类型
*/
@JsonProperty("recipient_type")
private String recipientType;
/**
* 分账金额
*/
@JsonProperty("share_amount")
private Double shareAmount;
/**
* 分账类型
*/
@JsonProperty("share_type")
private String shareType;
/**
* 分账值
*/
@JsonProperty("share_value")
private Double shareValue;
}
}

View File

@@ -0,0 +1,49 @@
package com.ycwl.basic.integration.profitshare.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ycwl.basic.integration.profitshare.dto.rule.CreateRecipientRequest;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 计算分账请求
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CalculateShareRequest {
/**
* 景区ID
*/
@JsonProperty("scenic_id")
private Long scenicId;
/**
* 总金额
*/
@JsonProperty("total_amount")
private Double totalAmount;
/**
* 规则类型
*/
@JsonProperty("rule_type")
private String ruleType;
/**
* 分账接收人列表
*/
@JsonProperty("recipients")
private List<CreateRecipientRequest> recipients;
}

View File

@@ -0,0 +1,28 @@
package com.ycwl.basic.integration.profitshare.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 手动分账请求
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ManualShareRequest {
/**
* 订单ID
*/
@JsonProperty("order_id")
private String orderId;
}

View File

@@ -0,0 +1,48 @@
package com.ycwl.basic.integration.profitshare.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 支持的类型VO
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class TypesVO {
/**
* 规则类型列表
*/
@JsonProperty("rule_types")
private List<String> ruleTypes;
/**
* 接收人类型列表
*/
@JsonProperty("recipient_types")
private List<String> recipientTypes;
/**
* 分账类型列表
*/
@JsonProperty("share_types")
private List<String> shareTypes;
/**
* 状态列表
*/
@JsonProperty("statuses")
private List<String> statuses;
}

View File

@@ -0,0 +1,72 @@
package com.ycwl.basic.integration.profitshare.dto.message;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 订单分账消息(发送到 zt-profitshare topic)
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class OrderMessage {
/**
* 订单ID
*/
@JsonProperty("order_id")
private String orderId;
/**
* 景区ID
*/
@JsonProperty("scenic_id")
private Long scenicId;
/**
* 总金额(单位:元)
*/
@JsonProperty("total_amount")
private Double totalAmount;
/**
* 支付系统(alipay, wechat, union)
*/
@JsonProperty("payment_system")
private String paymentSystem;
/**
* 支付订单ID
*/
@JsonProperty("payment_order_id")
private String paymentOrderId;
/**
* Unix 时间戳(秒)
*/
@JsonProperty("timestamp")
private Long timestamp;
/**
* 快速创建订单消息
*/
public static OrderMessage of(String orderId, Long scenicId, Double totalAmount, String paymentSystem, String paymentOrderId) {
return OrderMessage.builder()
.orderId(orderId)
.scenicId(scenicId)
.totalAmount(totalAmount)
.paymentSystem(paymentSystem)
.paymentOrderId(paymentOrderId)
.timestamp(System.currentTimeMillis() / 1000)
.build();
}
}

View File

@@ -0,0 +1,72 @@
package com.ycwl.basic.integration.profitshare.dto.message;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 退款消息(发送到 zt-refund topic)
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RefundMessage {
/**
* 订单ID
*/
@JsonProperty("order_id")
private String orderId;
/**
* 景区ID
*/
@JsonProperty("scenic_id")
private Long scenicId;
/**
* 退款金额(单位:元)
*/
@JsonProperty("refund_amount")
private Double refundAmount;
/**
* 支付系统(alipay, wechat, union)
*/
@JsonProperty("payment_system")
private String paymentSystem;
/**
* 退款订单ID
*/
@JsonProperty("refund_order_id")
private String refundOrderId;
/**
* Unix 时间戳(秒)
*/
@JsonProperty("timestamp")
private Long timestamp;
/**
* 快速创建退款消息
*/
public static RefundMessage of(String orderId, Long scenicId, Double refundAmount, String paymentSystem, String refundOrderId) {
return RefundMessage.builder()
.orderId(orderId)
.scenicId(scenicId)
.refundAmount(refundAmount)
.paymentSystem(paymentSystem)
.refundOrderId(refundOrderId)
.timestamp(System.currentTimeMillis() / 1000)
.build();
}
}

View File

@@ -0,0 +1,96 @@
package com.ycwl.basic.integration.profitshare.dto.record;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 分账记录详情VO
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RecordDetailVO {
/**
* 记录ID
*/
@JsonProperty("id")
private Long id;
/**
* 订单ID
*/
@JsonProperty("order_id")
private String orderId;
/**
* 景区ID
*/
@JsonProperty("scenic_id")
private Long scenicId;
/**
* 规则ID
*/
@JsonProperty("rule_id")
private Long ruleId;
/**
* 总金额
*/
@JsonProperty("total_amount")
private Double totalAmount;
/**
* 支付系统
*/
@JsonProperty("payment_system")
private String paymentSystem;
/**
* 支付订单ID
*/
@JsonProperty("payment_order_id")
private String paymentOrderId;
/**
* 状态
*/
@JsonProperty("status")
private String status;
/**
* 错误信息
*/
@JsonProperty("error_message")
private String errorMessage;
/**
* 分账明细列表
*/
@JsonProperty("details")
private List<ShareDetailVO> details;
/**
* 创建时间
*/
@JsonProperty("created_at")
private String createdAt;
/**
* 更新时间
*/
@JsonProperty("updated_at")
private String updatedAt;
}

View File

@@ -0,0 +1,88 @@
package com.ycwl.basic.integration.profitshare.dto.record;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 分账记录VO
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RecordVO {
/**
* 记录ID
*/
@JsonProperty("id")
private Long id;
/**
* 订单ID
*/
@JsonProperty("order_id")
private String orderId;
/**
* 景区ID
*/
@JsonProperty("scenic_id")
private Long scenicId;
/**
* 规则ID
*/
@JsonProperty("rule_id")
private Long ruleId;
/**
* 总金额
*/
@JsonProperty("total_amount")
private Double totalAmount;
/**
* 支付系统
*/
@JsonProperty("payment_system")
private String paymentSystem;
/**
* 支付订单ID
*/
@JsonProperty("payment_order_id")
private String paymentOrderId;
/**
* 状态(pending, processing, success, failed)
*/
@JsonProperty("status")
private String status;
/**
* 错误信息
*/
@JsonProperty("error_message")
private String errorMessage;
/**
* 创建时间
*/
@JsonProperty("created_at")
private String createdAt;
/**
* 更新时间
*/
@JsonProperty("updated_at")
private String updatedAt;
}

View File

@@ -0,0 +1,64 @@
package com.ycwl.basic.integration.profitshare.dto.record;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 分账明细VO
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ShareDetailVO {
/**
* 明细ID
*/
@JsonProperty("id")
private Long id;
/**
* 接收人名称
*/
@JsonProperty("recipient_name")
private String recipientName;
/**
* 接收人类型
*/
@JsonProperty("recipient_type")
private String recipientType;
/**
* 账户信息
*/
@JsonProperty("account_info")
private String accountInfo;
/**
* 分账金额
*/
@JsonProperty("share_amount")
private Double shareAmount;
/**
* 状态
*/
@JsonProperty("status")
private String status;
/**
* 错误信息
*/
@JsonProperty("error_message")
private String errorMessage;
}

View File

@@ -0,0 +1,78 @@
package com.ycwl.basic.integration.profitshare.dto.rule;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* 分账接收人请求
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateRecipientRequest {
/**
* 接收人名称
*/
@JsonProperty("recipient_name")
private String recipientName;
/**
* 接收人类型(platform, merchant, agent)
*/
@JsonProperty("recipient_type")
private String recipientType;
/**
* 账户信息
*/
@JsonProperty("account_info")
private String accountInfo;
/**
* 分账类型(percentage, fixed_amount)
*/
@JsonProperty("share_type")
private String shareType;
/**
* 分账值(百分比或固定金额)
*/
@JsonProperty("share_value")
private Double shareValue;
/**
* 最小分账金额
*/
@JsonProperty("min_amount")
private Double minAmount;
/**
* 最大分账金额
*/
@JsonProperty("max_amount")
private Double maxAmount;
/**
* 优先级
*/
@JsonProperty("priority")
private Integer priority;
/**
* 扩展配置
*/
@JsonProperty("ext_config")
private Map<String, Object> extConfig;
}

View File

@@ -0,0 +1,54 @@
package com.ycwl.basic.integration.profitshare.dto.rule;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 创建分账规则请求
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateRuleRequest {
/**
* 景区ID
*/
@JsonProperty("scenic_id")
private Long scenicId;
/**
* 规则名称
*/
@JsonProperty("rule_name")
private String ruleName;
/**
* 规则类型(percentage, fixed_amount, scaled_amount)
*/
@JsonProperty("rule_type")
private String ruleType;
/**
* 规则描述
*/
@JsonProperty("description")
private String description;
/**
* 分账接收人列表
*/
@JsonProperty("recipients")
private List<CreateRecipientRequest> recipients;
}

View File

@@ -0,0 +1,90 @@
package com.ycwl.basic.integration.profitshare.dto.rule;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Map;
/**
* 分账接收人VO
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RecipientVO {
/**
* 接收人ID
*/
@JsonProperty("id")
private Long id;
/**
* 接收人名称
*/
@JsonProperty("recipient_name")
private String recipientName;
/**
* 接收人类型
*/
@JsonProperty("recipient_type")
private String recipientType;
/**
* 账户信息
*/
@JsonProperty("account_info")
private String accountInfo;
/**
* 分账类型
*/
@JsonProperty("share_type")
private String shareType;
/**
* 分账值
*/
@JsonProperty("share_value")
private Double shareValue;
/**
* 最小分账金额
*/
@JsonProperty("min_amount")
private Double minAmount;
/**
* 最大分账金额
*/
@JsonProperty("max_amount")
private Double maxAmount;
/**
* 优先级
*/
@JsonProperty("priority")
private Integer priority;
/**
* 扩展配置
*/
@JsonProperty("ext_config")
private Map<String, Object> extConfig;
/**
* 状态
*/
@JsonProperty("status")
private String status;
}

View File

@@ -0,0 +1,78 @@
package com.ycwl.basic.integration.profitshare.dto.rule;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 分账规则VO
*
* @author Claude Code
* @date 2025-01-11
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RuleVO {
/**
* 规则ID
*/
@JsonProperty("id")
private Long id;
/**
* 景区ID
*/
@JsonProperty("scenic_id")
private Long scenicId;
/**
* 规则名称
*/
@JsonProperty("rule_name")
private String ruleName;
/**
* 规则类型
*/
@JsonProperty("rule_type")
private String ruleType;
/**
* 规则描述
*/
@JsonProperty("description")
private String description;
/**
* 状态(active, inactive)
*/
@JsonProperty("status")
private Integer status;
/**
* 分账接收人列表
*/
@JsonProperty("recipients")
private List<RecipientVO> recipients;
/**
* 创建时间
*/
@JsonProperty("created_at")
private String createdAt;
/**
* 更新时间
*/
@JsonProperty("updated_at")
private String updatedAt;
}

View File

@@ -0,0 +1,224 @@
package com.ycwl.basic.integration.profitshare.service;
import com.ycwl.basic.integration.common.exception.IntegrationException;
import com.ycwl.basic.integration.common.response.CommonResponse;
import com.ycwl.basic.integration.common.response.PageResponse;
import com.ycwl.basic.integration.common.service.IntegrationFallbackService;
import com.ycwl.basic.integration.profitshare.client.ProfitShareClient;
import com.ycwl.basic.integration.profitshare.dto.CalculateResultVO;
import com.ycwl.basic.integration.profitshare.dto.CalculateShareRequest;
import com.ycwl.basic.integration.profitshare.dto.ManualShareRequest;
import com.ycwl.basic.integration.profitshare.dto.TypesVO;
import com.ycwl.basic.integration.profitshare.dto.record.RecordDetailVO;
import com.ycwl.basic.integration.profitshare.dto.record.RecordVO;
import com.ycwl.basic.integration.profitshare.dto.rule.CreateRuleRequest;
import com.ycwl.basic.integration.profitshare.dto.rule.RuleVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 分账集成服务
*
* @author Claude Code
* @date 2025-01-11
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ProfitShareIntegrationService {
private final ProfitShareClient profitShareClient;
private final IntegrationFallbackService fallbackService;
private static final String SERVICE_NAME = "zt-profitshare";
// ==================== 规则管理 ====================
/**
* 创建分账规则(直接操作,无fallback)
*/
public RuleVO createRule(CreateRuleRequest request) {
log.debug("创建分账规则, scenicId: {}, ruleName: {}", request.getScenicId(), request.getRuleName());
CommonResponse<RuleVO> response = profitShareClient.createRule(request);
return handleResponse(response, "创建分账规则失败");
}
/**
* 查询分账规则列表(带fallback)
*/
public PageResponse<RuleVO> listRules(Long scenicId, String status, String ruleType, Integer page, Integer pageSize) {
log.debug("查询分账规则列表, scenicId: {}, status: {}, ruleType: {}, page: {}, pageSize: {}",
scenicId, status, ruleType, page, pageSize);
return fallbackService.executeWithFallback(
SERVICE_NAME,
String.format("rules:list:%s:%s:%s:%d:%d", scenicId, status, ruleType, page, pageSize),
() -> {
CommonResponse<PageResponse<RuleVO>> response = profitShareClient.listRules(scenicId, status, ruleType, page, pageSize);
return handleResponse(response, "查询分账规则列表失败");
},
PageResponse.class
);
}
/**
* 获取分账规则详情(带fallback)
*/
public RuleVO getRule(Long ruleId) {
log.debug("获取分账规则详情, ruleId: {}", ruleId);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"rule:" + ruleId,
() -> {
CommonResponse<RuleVO> response = profitShareClient.getRule(ruleId);
return handleResponse(response, "获取分账规则详情失败");
},
RuleVO.class
);
}
/**
* 更新分账规则(直接操作,无fallback)
*/
public RuleVO updateRule(Long ruleId, CreateRuleRequest request) {
log.debug("更新分账规则, ruleId: {}", ruleId);
CommonResponse<RuleVO> response = profitShareClient.updateRule(ruleId, request);
return handleResponse(response, "更新分账规则失败");
}
/**
* 删除分账规则(直接操作,无fallback)
*/
public void deleteRule(Long ruleId) {
log.debug("删除分账规则, ruleId: {}", ruleId);
CommonResponse<Void> response = profitShareClient.deleteRule(ruleId);
handleResponse(response, "删除分账规则失败");
}
/**
* 启用规则(直接操作,无fallback)
*/
public void enableRule(Long ruleId) {
log.debug("启用分账规则, ruleId: {}", ruleId);
CommonResponse<Void> response = profitShareClient.enableRule(ruleId);
handleResponse(response, "启用分账规则失败");
}
/**
* 禁用规则(直接操作,无fallback)
*/
public void disableRule(Long ruleId) {
log.debug("禁用分账规则, ruleId: {}", ruleId);
CommonResponse<Void> response = profitShareClient.disableRule(ruleId);
handleResponse(response, "禁用分账规则失败");
}
// ==================== 分账记录查询 ====================
/**
* 查询景区分账记录(带fallback)
*/
public PageResponse<RecordVO> getRecordsByScenic(Long scenicId, Integer page, Integer pageSize) {
log.debug("查询景区分账记录, scenicId: {}, page: {}, pageSize: {}", scenicId, page, pageSize);
return fallbackService.executeWithFallback(
SERVICE_NAME,
String.format("records:scenic:%d:%d:%d", scenicId, page, pageSize),
() -> {
CommonResponse<PageResponse<RecordVO>> response = profitShareClient.getRecordsByScenic(scenicId, page, pageSize);
return handleResponse(response, "查询景区分账记录失败");
},
PageResponse.class
);
}
/**
* 查询分账记录详情(带fallback)
*/
public RecordDetailVO getRecordById(Long recordId) {
log.debug("查询分账记录详情, recordId: {}", recordId);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"record:" + recordId,
() -> {
CommonResponse<RecordDetailVO> response = profitShareClient.getRecordById(recordId);
return handleResponse(response, "查询分账记录详情失败");
},
RecordDetailVO.class
);
}
/**
* 按订单ID查询分账记录(带fallback)
*/
public RecordDetailVO getRecordByOrderId(String orderId) {
log.debug("按订单ID查询分账记录, orderId: {}", orderId);
return fallbackService.executeWithFallback(
SERVICE_NAME,
"record:order:" + orderId,
() -> {
CommonResponse<RecordDetailVO> response = profitShareClient.getRecordByOrderId(orderId);
return handleResponse(response, "按订单ID查询分账记录失败");
},
RecordDetailVO.class
);
}
// ==================== 分账操作 ====================
/**
* 手动触发分账(直接操作,无fallback)
*/
public void manualShare(String orderId) {
log.debug("手动触发分账, orderId: {}", orderId);
ManualShareRequest request = ManualShareRequest.builder()
.orderId(orderId)
.build();
CommonResponse<Void> response = profitShareClient.manualShare(request);
handleResponse(response, "手动触发分账失败");
}
/**
* 计算分账金额(不执行)(带fallback)
*/
public CalculateResultVO calculateShare(CalculateShareRequest request) {
log.debug("计算分账金额, scenicId: {}, totalAmount: {}", request.getScenicId(), request.getTotalAmount());
return fallbackService.executeWithFallback(
SERVICE_NAME,
String.format("calculate:%d:%.2f", request.getScenicId(), request.getTotalAmount()),
() -> {
CommonResponse<CalculateResultVO> response = profitShareClient.calculateShare(request);
return handleResponse(response, "计算分账金额失败");
},
CalculateResultVO.class
);
}
/**
* 获取支持的类型(带fallback)
*/
public TypesVO getSupportedTypes() {
log.debug("获取支持的类型");
return fallbackService.executeWithFallback(
SERVICE_NAME,
"types",
() -> {
CommonResponse<TypesVO> response = profitShareClient.getSupportedTypes();
return handleResponse(response, "获取支持的类型失败");
},
TypesVO.class
);
}
// ==================== 私有方法 ====================
private <T> T handleResponse(CommonResponse<T> 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, SERVICE_NAME);
}
return response.getData();
}
}

View File

@@ -0,0 +1,131 @@
package com.ycwl.basic.integration.profitshare.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ycwl.basic.integration.kafka.config.KafkaIntegrationProperties;
import com.ycwl.basic.integration.profitshare.dto.message.OrderMessage;
import com.ycwl.basic.integration.profitshare.dto.message.RefundMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
/**
* 分账Kafka消息生产者
*
* @author Claude Code
* @date 2025-01-11
*/
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnProperty(name = "kafka.enabled", havingValue = "true")
public class ProfitShareKafkaProducer {
public static final String DEFAULT_PROFITSHARE_TOPIC = "zt-profitshare";
public static final String DEFAULT_REFUND_TOPIC = "zt-refund";
private final KafkaTemplate<String, String> kafkaTemplate;
private final ObjectMapper objectMapper;
private final KafkaIntegrationProperties kafkaProps;
/**
* 发送分账消息(订单支付成功后调用)
*/
public void sendProfitShareMessage(OrderMessage message) {
validate(message);
String topic = kafkaProps != null && StringUtils.isNotBlank(kafkaProps.getProfitShareTopic())
? kafkaProps.getProfitShareTopic()
: DEFAULT_PROFITSHARE_TOPIC;
String key = message.getOrderId();
String payload = toJson(message);
log.info("[PROFIT-SHARE] producing to topic={}, key={}, orderId={}, scenicId={}, amount={}",
topic, key, message.getOrderId(), message.getScenicId(), message.getTotalAmount());
kafkaTemplate.send(topic, key, payload).whenComplete((metadata, ex) -> {
if (ex != null) {
log.error("[PROFIT-SHARE] produce failed: orderId={}, error={}", message.getOrderId(), ex.getMessage(), ex);
} else if (metadata != null) {
log.info("[PROFIT-SHARE] produced: orderId={}, partition={}, offset={}",
message.getOrderId(), metadata.getRecordMetadata().partition(), metadata.getRecordMetadata().offset());
}
});
}
/**
* 发送退款消息(订单退款成功后调用)
*/
public void sendRefundMessage(RefundMessage message) {
validateRefund(message);
String topic = kafkaProps != null && StringUtils.isNotBlank(kafkaProps.getRefundTopic())
? kafkaProps.getRefundTopic()
: DEFAULT_REFUND_TOPIC;
String key = message.getOrderId();
String payload = toJson(message);
log.info("[REFUND] producing to topic={}, key={}, orderId={}, scenicId={}, amount={}",
topic, key, message.getOrderId(), message.getScenicId(), message.getRefundAmount());
kafkaTemplate.send(topic, key, payload).whenComplete((metadata, ex) -> {
if (ex != null) {
log.error("[REFUND] produce failed: orderId={}, error={}", message.getOrderId(), ex.getMessage(), ex);
} else if (metadata != null) {
log.info("[REFUND] produced: orderId={}, partition={}, offset={}",
message.getOrderId(), metadata.getRecordMetadata().partition(), metadata.getRecordMetadata().offset());
}
});
}
private void validate(OrderMessage msg) {
if (msg == null) {
throw new IllegalArgumentException("OrderMessage is null");
}
if (StringUtils.isBlank(msg.getOrderId())) {
throw new IllegalArgumentException("orderId is required");
}
if (msg.getScenicId() == null || msg.getScenicId() <= 0) {
throw new IllegalArgumentException("scenicId is required and must be positive");
}
if (msg.getTotalAmount() == null || msg.getTotalAmount() <= 0) {
throw new IllegalArgumentException("totalAmount is required and must be positive");
}
if (StringUtils.isBlank(msg.getPaymentSystem())) {
throw new IllegalArgumentException("paymentSystem is required");
}
if (StringUtils.isBlank(msg.getPaymentOrderId())) {
throw new IllegalArgumentException("paymentOrderId is required");
}
}
private void validateRefund(RefundMessage msg) {
if (msg == null) {
throw new IllegalArgumentException("RefundMessage is null");
}
if (StringUtils.isBlank(msg.getOrderId())) {
throw new IllegalArgumentException("orderId is required");
}
if (msg.getScenicId() == null || msg.getScenicId() <= 0) {
throw new IllegalArgumentException("scenicId is required and must be positive");
}
if (msg.getRefundAmount() == null || msg.getRefundAmount() <= 0) {
throw new IllegalArgumentException("refundAmount is required and must be positive");
}
if (StringUtils.isBlank(msg.getPaymentSystem())) {
throw new IllegalArgumentException("paymentSystem is required");
}
if (StringUtils.isBlank(msg.getRefundOrderId())) {
throw new IllegalArgumentException("refundOrderId is required");
}
}
private String toJson(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("failed to serialize message", e);
}
}
}