Commit 41e4a56d by zhangxingmin

push

parent 1f199e88
package com.yd.base.api.controller;
import com.yd.base.api.service.ApiExchangeRateService;
import com.yd.base.feign.client.exchangerate.ApiExchangeRateFeignClient;
import com.yd.base.feign.request.exchangerate.ApiExchangeRateConvertRequest;
import com.yd.base.feign.response.exchangerate.ApiExchangeRateConvertResponse;
import com.yd.common.result.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.List;
/**
* 汇率信息
*
* @author zxm
* @since 2025-12-29
*/
@RestController
@RequestMapping("/exchangeRate")
@Validated
public class ApiExchangeRateController implements ApiExchangeRateFeignClient {
@Autowired
private ApiExchangeRateService apiExchangeRateService;
/**
* 货币转换
* @param request
* @return
*/
@Override
public Result<ApiExchangeRateConvertResponse> convert(ApiExchangeRateConvertRequest request) {
return apiExchangeRateService.convert(request);
}
/**
* 获取汇率
* @param baseCurrency 基础币种
* @param targetCurrency 目标币种
* @param date
* @return
*/
@Override
public Result<BigDecimal> getExchangeRate(String baseCurrency, String targetCurrency, String date) {
return apiExchangeRateService.getExchangeRate(baseCurrency,targetCurrency,date);
}
/**
* 批量转换
* @param requests
* @return
*/
@Override
public Result<List<ApiExchangeRateConvertResponse>> batchConvert(List<ApiExchangeRateConvertRequest> requests) {
return apiExchangeRateService.batchConvert(requests);
}
/**
* 验证币种是否支持
* @param currency
* @return
*/
@Override
public Result<Boolean> isCurrencySupportedResult(String currency) {
return apiExchangeRateService.isCurrencySupportedResult(currency);
}
/**
* 获取支持的币种列表
* @return
*/
@Override
public Result<List<String>> getSupportedCurrencies() {
return apiExchangeRateService.getSupportedCurrencies();
}
}
package com.yd.base.api.service;
import com.yd.base.feign.request.exchangerate.ApiExchangeRateConvertRequest;
import com.yd.base.feign.response.exchangerate.ApiExchangeRateConvertResponse;
import com.yd.common.result.Result;
import java.math.BigDecimal;
import java.util.List;
public interface ApiExchangeRateService {
Result<List<String>> getSupportedCurrencies();
Result<ApiExchangeRateConvertResponse> convert(ApiExchangeRateConvertRequest request);
Result<BigDecimal> getExchangeRate(String baseCurrency, String targetCurrency, String date);
Result<List<ApiExchangeRateConvertResponse>> batchConvert(List<ApiExchangeRateConvertRequest> requests);
Result<Boolean> isCurrencySupportedResult(String currency);
}
package com.yd.base.api.service.impl;
import com.yd.base.api.service.ApiExchangeRateService;
import com.yd.base.feign.request.exchangerate.ApiExchangeRateConvertRequest;
import com.yd.base.feign.response.exchangerate.ApiExchangeRateConvertResponse;
import com.yd.base.service.model.ExchangeRate;
import com.yd.base.service.service.IExchangeRateService;
import com.yd.common.exception.BusinessException;
import com.yd.common.result.Result;
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.util.CollectionUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
@Slf4j
@Service
public class ApiExchangeRateServiceImpl implements ApiExchangeRateService {
@Autowired
private IExchangeRateService iExchangeRateService;
private static final String DEFAULT_BASE_CURRENCY = "USD";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
/**
* 货币转换
* @param request
* @return
*/
@Override
public Result<ApiExchangeRateConvertResponse> convert(ApiExchangeRateConvertRequest request) {
try {
log.info("货币转换请求: {}", request);
// 参数验证
if (request.getAmount() == null) {
return Result.fail("金额不能为空");
}
if (StringUtils.isBlank(request.getFromCurrency())) {
return Result.fail("源币种不能为空");
}
if (StringUtils.isBlank(request.getToCurrency())) {
return Result.fail("目标币种不能为空");
}
// 验证币种是否支持
if (!isCurrencySupported(request.getFromCurrency())) {
return Result.fail("源币种不支持: " + request.getFromCurrency());
}
if (!isCurrencySupported(request.getToCurrency())) {
return Result.fail("目标币种不支持: " + request.getToCurrency());
}
// 获取转换日期
LocalDate effectiveDate = getEffectiveDate(request.getTransactionDate(), request.getDate());
// 执行转换
BigDecimal convertedAmount = convertAmount(
request.getAmount(),
request.getFromCurrency(),
request.getToCurrency(),
effectiveDate
);
// 获取汇率
BigDecimal exchangeRate = getExchangeRate(
request.getFromCurrency(),
request.getToCurrency(),
effectiveDate
);
// 构建响应
ApiExchangeRateConvertResponse response = new ApiExchangeRateConvertResponse();
response.setOriginalAmount(request.getAmount());
response.setOriginalCurrency(request.getFromCurrency());
response.setConvertedAmount(convertedAmount);
response.setTargetCurrency(request.getToCurrency());
response.setExchangeRate(exchangeRate);
response.setTransactionDate(request.getTransactionDate());
log.info("货币转换结果: {} {} = {} {}, 汇率: {}",
request.getAmount(), request.getFromCurrency(),
convertedAmount, request.getToCurrency(), exchangeRate);
return Result.success(response);
} catch (BusinessException e) {
log.error("货币转换业务异常", e);
return Result.fail(e.getMessage());
} catch (Exception e) {
log.error("货币转换系统异常", e);
return Result.fail("货币转换失败: " + e.getMessage());
}
}
/**
* 获取汇率
* @param baseCurrency 基础币种
* @param targetCurrency 目标币种
* @param date
* @return
*/
@Override
public Result<BigDecimal> getExchangeRate(String baseCurrency, String targetCurrency, String date) {
try {
log.info("获取汇率: {} -> {}, date: {}", baseCurrency, targetCurrency, date);
if (StringUtils.isBlank(baseCurrency)) {
return Result.fail("基础币种不能为空");
}
if (StringUtils.isBlank(targetCurrency)) {
return Result.fail("目标币种不能为空");
}
// 解析日期
LocalDate effectiveDate = parseDate(date);
BigDecimal rate = getExchangeRate(baseCurrency, targetCurrency, effectiveDate);
if (rate == null) {
return Result.fail("汇率不存在");
}
return Result.success(rate);
} catch (BusinessException e) {
log.error("获取汇率业务异常", e);
return Result.fail(e.getMessage());
} catch (Exception e) {
log.error("获取汇率系统异常", e);
return Result.fail("获取汇率失败: " + e.getMessage());
}
}
/**
* 批量转换
* @param requests
* @return
*/
@Override
public Result<List<ApiExchangeRateConvertResponse>> batchConvert(List<ApiExchangeRateConvertRequest> requests) {
try {
log.info("批量货币转换: {}条记录", requests == null ? 0 : requests.size());
if (CollectionUtils.isEmpty(requests)) {
return Result.success(new ArrayList<>());
}
List<ApiExchangeRateConvertResponse> responses = new ArrayList<>();
for (ApiExchangeRateConvertRequest request : requests) {
try {
// 参数验证
if (request.getAmount() == null ||
StringUtils.isBlank(request.getFromCurrency()) ||
StringUtils.isBlank(request.getToCurrency())) {
log.warn("批量转换中跳过无效请求: {}", request);
continue;
}
// 获取转换日期
LocalDate effectiveDate = getEffectiveDate(request.getTransactionDate(), request.getDate());
// 执行转换
BigDecimal convertedAmount = convertAmount(
request.getAmount(),
request.getFromCurrency(),
request.getToCurrency(),
effectiveDate
);
// 获取汇率
BigDecimal exchangeRate = getExchangeRate(
request.getFromCurrency(),
request.getToCurrency(),
effectiveDate
);
// 构建响应
ApiExchangeRateConvertResponse response = new ApiExchangeRateConvertResponse();
response.setOriginalAmount(request.getAmount());
response.setOriginalCurrency(request.getFromCurrency());
response.setConvertedAmount(convertedAmount);
response.setTargetCurrency(request.getToCurrency());
response.setExchangeRate(exchangeRate);
response.setTransactionDate(request.getTransactionDate());
responses.add(response);
} catch (Exception e) {
log.error("批量转换中单条转换失败: {}", request, e);
// 跳过失败记录
}
}
return Result.success(responses);
} catch (Exception e) {
log.error("批量货币转换失败", e);
return Result.fail("批量货币转换失败: " + e.getMessage());
}
}
/**
* 验证币种是否支持
* @param currency
* @return
*/
@Override
public Result<Boolean> isCurrencySupportedResult(String currency) {
try {
log.info("验证币种是否支持: {}", currency);
if (StringUtils.isBlank(currency)) {
return Result.success(false);
}
boolean isSupported = isCurrencySupported(currency);
return Result.success(isSupported);
} catch (Exception e) {
log.error("验证币种支持失败", e);
return Result.fail("验证币种支持失败: " + e.getMessage());
}
}
/**
* 获取支持的币种列表
* @return
*/
@Override
public Result<List<String>> getSupportedCurrencies() {
try {
log.info("获取支持的币种列表");
List<String> currencies = getSupportedCurrenciesList();
return Result.success(currencies);
} catch (Exception e) {
log.error("获取支持币种列表失败", e);
return Result.fail("获取支持币种列表失败: " + e.getMessage());
}
}
// ==================== 业务逻辑方法 ====================
/**
* 货币转换(业务逻辑)
*/
private BigDecimal convertAmount(BigDecimal amount, String fromCurrency, String toCurrency, LocalDate effectiveDate) {
if (amount == null) {
return BigDecimal.ZERO;
}
if (StringUtils.isBlank(fromCurrency) || StringUtils.isBlank(toCurrency)) {
throw new BusinessException("币种代码不能为空");
}
// 如果币种相同,直接返回
if (fromCurrency.equalsIgnoreCase(toCurrency)) {
return amount;
}
// 获取汇率
BigDecimal exchangeRate = getExchangeRate(fromCurrency, toCurrency, effectiveDate);
// 计算转换后的金额
BigDecimal convertedAmount = amount.multiply(exchangeRate);
// 设置精度(保留2位小数,银行家舍入法)
return convertedAmount.setScale(2, RoundingMode.HALF_EVEN);
}
/**
* 获取汇率(业务逻辑)
*/
private BigDecimal getExchangeRate(String baseCurrency, String targetCurrency, LocalDate effectiveDate) {
if (StringUtils.isBlank(baseCurrency) || StringUtils.isBlank(targetCurrency)) {
throw new BusinessException("币种代码不能为空");
}
// 如果币种相同,返回1
if (baseCurrency.equalsIgnoreCase(targetCurrency)) {
return BigDecimal.ONE;
}
// 1. 尝试获取直接汇率
BigDecimal directRate = getDirectExchangeRate(baseCurrency, targetCurrency, effectiveDate);
if (directRate != null) {
return directRate;
}
// 2. 尝试获取反向汇率
BigDecimal reverseRate = getDirectExchangeRate(targetCurrency, baseCurrency, effectiveDate);
if (reverseRate != null) {
// 计算反向汇率的倒数
return BigDecimal.ONE.divide(reverseRate, 8, RoundingMode.HALF_UP);
}
// 3. 尝试通过中间货币计算交叉汇率
return calculateCrossRate(baseCurrency, targetCurrency, effectiveDate);
}
/**
* 获取直接汇率
*/
private BigDecimal getDirectExchangeRate(String baseCurrency, String targetCurrency, LocalDate effectiveDate) {
ExchangeRate exchangeRate;
if (effectiveDate != null) {
// 查询指定日期的汇率
exchangeRate = iExchangeRateService.getByCurrencyAndDate(baseCurrency, targetCurrency, effectiveDate);
} else {
// 查询最新汇率
exchangeRate = iExchangeRateService.getLatestByCurrency(baseCurrency, targetCurrency);
}
return exchangeRate != null ? exchangeRate.getExchangeRate() : null;
}
/**
* 计算交叉汇率
*/
private BigDecimal calculateCrossRate(String baseCurrency, String targetCurrency, LocalDate effectiveDate) {
log.debug("尝试计算交叉汇率: {} -> {}", baseCurrency, targetCurrency);
try {
// 通过默认基础货币(如USD)计算交叉汇率
// 获取 baseCurrency -> USD 的汇率
BigDecimal baseToUsdRate;
if (baseCurrency.equals(DEFAULT_BASE_CURRENCY)) {
baseToUsdRate = BigDecimal.ONE;
} else {
BigDecimal rate = getDirectExchangeRate(baseCurrency, DEFAULT_BASE_CURRENCY, effectiveDate);
if (rate != null) {
baseToUsdRate = rate;
} else {
// 尝试反向
BigDecimal reverseRate = getDirectExchangeRate(DEFAULT_BASE_CURRENCY, baseCurrency, effectiveDate);
if (reverseRate != null) {
baseToUsdRate = BigDecimal.ONE.divide(reverseRate, 8, RoundingMode.HALF_UP);
} else {
throw new BusinessException("无法获取 " + baseCurrency + " 到 " + DEFAULT_BASE_CURRENCY + " 的汇率");
}
}
}
// 获取 USD -> targetCurrency 的汇率
BigDecimal usdToTargetRate;
if (targetCurrency.equals(DEFAULT_BASE_CURRENCY)) {
usdToTargetRate = BigDecimal.ONE;
} else {
BigDecimal rate = getDirectExchangeRate(DEFAULT_BASE_CURRENCY, targetCurrency, effectiveDate);
if (rate != null) {
usdToTargetRate = rate;
} else {
// 尝试反向
BigDecimal reverseRate = getDirectExchangeRate(targetCurrency, DEFAULT_BASE_CURRENCY, effectiveDate);
if (reverseRate != null) {
usdToTargetRate = BigDecimal.ONE.divide(reverseRate, 8, RoundingMode.HALF_UP);
} else {
throw new BusinessException("无法获取 " + DEFAULT_BASE_CURRENCY + " 到 " + targetCurrency + " 的汇率");
}
}
}
// 计算交叉汇率
BigDecimal crossRate = baseToUsdRate.multiply(usdToTargetRate);
log.debug("交叉汇率计算结果: {} -> {} = {}", baseCurrency, targetCurrency, crossRate);
return crossRate;
} catch (Exception e) {
log.error("计算交叉汇率失败: {} -> {}", baseCurrency, targetCurrency, e);
throw new BusinessException("无法获取汇率: " + baseCurrency + " -> " + targetCurrency);
}
}
/**
* 验证币种是否支持(业务逻辑)
*/
private boolean isCurrencySupported(String currency) {
if (StringUtils.isBlank(currency)) {
return false;
}
// 查询该币种是否在汇率表中存在(作为基础币种或目标币种)
List<ExchangeRate> baseCurrencyRates = iExchangeRateService.getByBaseCurrency(currency);
List<ExchangeRate> targetCurrencyRates = iExchangeRateService.getByTargetCurrency(currency);
return !CollectionUtils.isEmpty(baseCurrencyRates) || !CollectionUtils.isEmpty(targetCurrencyRates);
}
/**
* 获取支持的币种列表(业务逻辑)
*/
private List<String> getSupportedCurrenciesList() {
// 查询所有有效的汇率记录
List<ExchangeRate> exchangeRates = iExchangeRateService.getAllActiveRates();
Set<String> currencies = new HashSet<>();
for (ExchangeRate rate : exchangeRates) {
currencies.add(rate.getBaseCurrency());
currencies.add(rate.getTargetCurrency());
}
return new ArrayList<>(currencies);
}
/**
* 获取生效日期
*/
private LocalDate getEffectiveDate(Date transactionDate, String dateStr) {
if (transactionDate != null) {
try {
String dateString = SIMPLE_DATE_FORMAT.format(transactionDate);
return LocalDate.parse(dateString, DATE_FORMATTER);
} catch (Exception e) {
log.warn("交易日期格式转换失败: {}, 使用当前日期", transactionDate);
}
}
if (StringUtils.isNotBlank(dateStr)) {
try {
return LocalDate.parse(dateStr, DATE_FORMATTER);
} catch (Exception e) {
log.warn("日期字符串格式错误: {}, 使用当前日期", dateStr);
}
}
// 默认返回null,查询最新汇率
return null;
}
/**
* 解析日期字符串
*/
private LocalDate parseDate(String dateStr) {
if (StringUtils.isBlank(dateStr)) {
return null;
}
try {
return LocalDate.parse(dateStr, DATE_FORMATTER);
} catch (DateTimeParseException e) {
log.warn("日期格式错误: {}, 使用最新汇率", dateStr);
return null;
}
}
}
\ No newline at end of file
......@@ -104,10 +104,10 @@ public class ApiRelFieldValueServiceImpl implements ApiRelFieldValueService {
return Result.success(resultList);
}
// 1. 根据字段ID列表查询fvq_setting配置
// 根据字段ID列表查询fvq_setting配置
Map<String, FvqSetting> fvqSettingMap = getFvqSettingsByFieldIds(request.getFieldBizIdList());
// 2. 分离有配置和无配置的字段ID
// 分离有配置和无配置的字段ID
List<String> configuredFieldIds = new ArrayList<>();
List<String> unconfiguredFieldIds = new ArrayList<>();
......@@ -120,13 +120,13 @@ public class ApiRelFieldValueServiceImpl implements ApiRelFieldValueService {
}
}
// 3. 处理有配置的字段(通过接口获取)
// 处理有配置的字段(通过接口获取)
Map<String, GetFieldListResponse> configuredFieldsMap = processConfiguredFields(configuredFieldIds, fvqSettingMap);
// 4. 处理无配置的字段(默认查询方式)
// 处理无配置的字段(默认查询方式)
Map<String, GetFieldListResponse> unconfiguredFieldsMap = processUnconfiguredFields(unconfiguredFieldIds);
// 5. 按照请求字段ID的顺序合并结果
// 按照请求字段ID的顺序合并结果
for (String fieldBizId : request.getFieldBizIdList()) {
if (configuredFieldsMap.containsKey(fieldBizId)) {
resultList.add(configuredFieldsMap.get(fieldBizId));
......
package com.yd.base.feign.client.exchangerate;
import com.yd.base.feign.fallback.exchangerate.ApiExchangeRateFeignFallbackFactory;
import com.yd.base.feign.request.exchangerate.ApiExchangeRateConvertRequest;
import com.yd.base.feign.response.exchangerate.ApiExchangeRateConvertResponse;
import com.yd.common.result.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
import java.util.List;
/**
* 基础数据服务-汇率信息Feign客户端
*/
@FeignClient(name = "yd-base-api",path = "/base/api/exchangeRate",fallbackFactory = ApiExchangeRateFeignFallbackFactory.class)
public interface ApiExchangeRateFeignClient {
/**
* 货币转换
* @param request
* @return
*/
@PostMapping("/convert")
Result<ApiExchangeRateConvertResponse> convert(@RequestBody ApiExchangeRateConvertRequest request);
/**
* 获取汇率
* @param baseCurrency 基础币种
* @param targetCurrency 目标币种
* @param date
* @return
*/
@GetMapping("/rate")
Result<BigDecimal> getExchangeRate(
@RequestParam("baseCurrency") String baseCurrency,
@RequestParam("targetCurrency") String targetCurrency,
@RequestParam(value = "date", required = false) String date
);
/**
* 批量转换
* @param requests
* @return
*/
@PostMapping("/batch/convert")
Result<List<ApiExchangeRateConvertResponse>> batchConvert(@RequestBody List<ApiExchangeRateConvertRequest> requests);
/**
* 验证币种是否支持
* @param currency
* @return
*/
@GetMapping("/currency/supported")
Result<Boolean> isCurrencySupportedResult(@RequestParam("currency") String currency);
/**
* 获取支持的币种列表
* @return
*/
@GetMapping("/currencies")
Result<List<String>> getSupportedCurrencies();
}
package com.yd.base.feign.fallback.exchangerate;
import com.yd.base.feign.client.exchangerate.ApiExchangeRateFeignClient;
import com.yd.base.feign.request.exchangerate.ApiExchangeRateConvertRequest;
import com.yd.base.feign.response.exchangerate.ApiExchangeRateConvertResponse;
import com.yd.common.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.List;
/**
* 基础数据服务-汇率信息Feign降级处理
*/
@Slf4j
@Component
public class ApiExchangeRateFeignFallbackFactory implements FallbackFactory<ApiExchangeRateFeignClient> {
@Override
public ApiExchangeRateFeignClient create(Throwable cause) {
return new ApiExchangeRateFeignClient() {
@Override
public Result<ApiExchangeRateConvertResponse> convert(ApiExchangeRateConvertRequest request) {
return null;
}
@Override
public Result<BigDecimal> getExchangeRate(String baseCurrency, String targetCurrency, String date) {
return null;
}
@Override
public Result<List<ApiExchangeRateConvertResponse>> batchConvert(List<ApiExchangeRateConvertRequest> requests) {
return null;
}
@Override
public Result<Boolean> isCurrencySupportedResult(String currency) {
return null;
}
@Override
public Result<List<String>> getSupportedCurrencies() {
return null;
}
};
}
}
package com.yd.base.feign.request.exchangerate;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Date;
/**
* 汇率转换请求DTO
* @author zxm
* @since 2025-12-29
*/
@Data
public class ApiExchangeRateConvertRequest {
/**
* 金额
*/
@NotNull(message = "金额不能为空")
private BigDecimal amount;
/**
* 源币种代码
*/
@NotBlank(message = "源币种不能为空")
private String fromCurrency;
/**
* 目标币种代码
*/
@NotBlank(message = "目标币种不能为空")
private String toCurrency;
/**
* 交易日期(用于历史汇率查询)
*/
private Date transactionDate;
/**
* 日期字符串(格式:yyyy-MM-dd),优先级低于transactionDate
*/
private String date;
/**
* 是否需要返回汇率详情
*/
private Boolean needDetail = false;
/**
* 业务场景标识
*/
private String businessScene;
/**
* 业务ID
*/
private String businessId;
/**
* 请求来源
*/
private String source;
/**
* 请求ID(用于幂等性控制)
*/
private String requestId;
/**
* 备注
*/
private String remark;
/**
* 扩展字段(JSON格式)
*/
private String extInfo;
}
\ No newline at end of file
package com.yd.base.feign.response.exchangerate;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 汇率转换响应DTO
* @author zxm
* @since 2025-12-29
*/
@Data
public class ApiExchangeRateConvertResponse {
/**
* 原始金额
*/
private BigDecimal originalAmount;
/**
* 原始币种代码
*/
private String originalCurrency;
/**
* 转换后金额
*/
private BigDecimal convertedAmount;
/**
* 目标币种代码
*/
private String targetCurrency;
/**
* 汇率(原始币种 -> 目标币种)
*/
private BigDecimal exchangeRate;
/**
* 反向汇率(目标币种 -> 原始币种)
*/
private BigDecimal reverseExchangeRate;
/**
* 汇率类型
*/
private String rateType;
/**
* 生效日期
*/
private Date effectiveDate;
/**
* 交易日期
*/
private Date transactionDate;
/**
* 汇率来源
*/
private String rateSource;
/**
* 转换时间
*/
private Date convertTime;
/**
* 是否使用交叉汇率计算
*/
private Boolean isCrossRate = false;
/**
* 交叉汇率中间币种
*/
private String crossRateCurrency;
/**
* 转换状态
*/
private String convertStatus;
/**
* 错误码
*/
private String errorCode;
/**
* 错误信息
*/
private String errorMessage;
/**
* 汇率记录ID
*/
private Long exchangeRateId;
/**
* 基础币种名称
*/
private String baseCurrencyName;
/**
* 目标币种名称
*/
private String targetCurrencyName;
/**
* 业务场景标识
*/
private String businessScene;
/**
* 业务ID
*/
private String businessId;
/**
* 请求ID
*/
private String requestId;
/**
* 扩展字段(JSON格式)
*/
private String extInfo;
/**
* 转换详情
*/
@Data
public static class ConvertDetail {
/**
* 直接汇率是否存在
*/
private Boolean directRateExists;
/**
* 是否使用反向汇率
*/
private Boolean useReverseRate;
/**
* 汇率计算路径
*/
private String ratePath;
/**
* 汇率更新时间
*/
private Date rateUpdateTime;
/**
* 汇率有效期
*/
private Date rateExpireTime;
}
/**
* 转换详情(当needDetail=true时返回)
*/
private ConvertDetail convertDetail;
/**
* 创建成功响应
*/
public static ApiExchangeRateConvertResponse success(
BigDecimal originalAmount,
String originalCurrency,
BigDecimal convertedAmount,
String targetCurrency,
BigDecimal exchangeRate) {
ApiExchangeRateConvertResponse response = new ApiExchangeRateConvertResponse();
response.setOriginalAmount(originalAmount);
response.setOriginalCurrency(originalCurrency);
response.setConvertedAmount(convertedAmount);
response.setTargetCurrency(targetCurrency);
response.setExchangeRate(exchangeRate);
response.setConvertTime(new Date());
response.setConvertStatus("SUCCESS");
// 计算反向汇率
if (exchangeRate != null && exchangeRate.compareTo(BigDecimal.ZERO) != 0) {
try {
BigDecimal reverseRate = BigDecimal.ONE.divide(
exchangeRate, 8, BigDecimal.ROUND_HALF_UP);
response.setReverseExchangeRate(reverseRate);
} catch (Exception e) {
// 忽略计算错误
}
}
return response;
}
/**
* 创建失败响应
*/
public static ApiExchangeRateConvertResponse error(String errorCode, String errorMessage) {
ApiExchangeRateConvertResponse response = new ApiExchangeRateConvertResponse();
response.setConvertTime(new Date());
response.setConvertStatus("FAILED");
response.setErrorCode(errorCode);
response.setErrorMessage(errorMessage);
return response;
}
}
\ No newline at end of file
package com.yd.base.service.dao;
import com.yd.base.service.model.ExchangeRate;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 汇率表 Mapper 接口
* </p>
*
* @author zxm
* @since 2025-12-29
*/
public interface ExchangeRateMapper extends BaseMapper<ExchangeRate> {
}
package com.yd.base.service.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 汇率表
* </p>
*
* @author zxm
* @since 2025-12-29
*/
@Getter
@Setter
@TableName("exchange_rate")
public class ExchangeRate implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 基础币种代码(ISO 4217标准)
*/
@TableField("base_currency")
private String baseCurrency;
/**
* 基础币种名称
*/
@TableField("base_currency_name")
private String baseCurrencyName;
/**
* 目标币种代码(ISO 4217标准)
*/
@TableField("target_currency")
private String targetCurrency;
/**
* 目标币种名称
*/
@TableField("target_currency_name")
private String targetCurrencyName;
/**
* 汇率(1单位基础币种可兑换的目标币种数量)
*/
@TableField("exchange_rate")
private BigDecimal exchangeRate;
/**
* 生效日期
*/
@TableField("effective_date")
private LocalDate effectiveDate;
/**
* 失效日期(NULL表示永久有效)
*/
@TableField("expiration_date")
private LocalDate expirationDate;
/**
* 汇率类型: SPOT-即期, FORWARD-远期, HISTORICAL-历史
*/
@TableField("rate_type")
private String rateType;
/**
* 数据来源: SYSTEM-系统设置, API-外部接口, MANUAL-手动录入
*/
@TableField("source")
private String source;
/**
* 是否有效: 0-无效, 1-有效
*/
@TableField("is_active")
private Integer isActive;
/**
* 通用备注
*/
@TableField("remark")
private String remark;
/**
* 删除标识:0-正常,1-删除
*/
@TableField("is_deleted")
private Integer isDeleted;
/**
* 创建人ID
*/
@TableField("creator_id")
private String creatorId;
/**
* 更新人ID
*/
@TableField("updater_id")
private String updaterId;
/**
* 创建时间
*/
@TableField("create_time")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField("update_time")
private LocalDateTime updateTime;
}
package com.yd.base.service.service;
import com.yd.base.service.model.ExchangeRate;
import com.baomidou.mybatisplus.extension.service.IService;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
/**
* <p>
* 汇率表 服务类
* </p>
*
* @author zxm
* @since 2025-12-29
*/
public interface IExchangeRateService extends IService<ExchangeRate> {
/**
* 根据币种对和日期查询汇率
* @param baseCurrency 基础币种
* @param targetCurrency 目标币种
* @param effectiveDate 生效日期
* @return 汇率记录
*/
ExchangeRate getByCurrencyAndDate(String baseCurrency, String targetCurrency, LocalDate effectiveDate);
/**
* 根据币种对查询最新汇率
* @param baseCurrency 基础币种
* @param targetCurrency 目标币种
* @return 最新汇率记录
*/
ExchangeRate getLatestByCurrency(String baseCurrency, String targetCurrency);
/**
* 根据生效日期查询汇率列表
* @param effectiveDate 生效日期
* @return 汇率列表
*/
List<ExchangeRate> getByEffectiveDate(LocalDate effectiveDate);
/**
* 查询所有有效的汇率记录
* @return 汇率列表
*/
List<ExchangeRate> getAllActiveRates();
/**
* 根据基础币种查询汇率
* @param baseCurrency 基础币种
* @return 汇率列表
*/
List<ExchangeRate> getByBaseCurrency(String baseCurrency);
/**
* 根据目标币种查询汇率
* @param targetCurrency 目标币种
* @return 汇率列表
*/
List<ExchangeRate> getByTargetCurrency(String targetCurrency);
}
\ No newline at end of file
package com.yd.base.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yd.base.service.model.ExchangeRate;
import com.yd.base.service.dao.ExchangeRateMapper;
import com.yd.base.service.service.IExchangeRateService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
/**
* <p>
* 汇率表 服务实现类
* </p>
*
* @author zxm
* @since 2025-12-29
*/
@Service
public class ExchangeRateServiceImpl extends ServiceImpl<ExchangeRateMapper, ExchangeRate> implements IExchangeRateService {
@Override
public ExchangeRate getByCurrencyAndDate(String baseCurrency, String targetCurrency, LocalDate effectiveDate) {
LambdaQueryWrapper<ExchangeRate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ExchangeRate::getBaseCurrency, baseCurrency)
.eq(ExchangeRate::getTargetCurrency, targetCurrency)
.eq(ExchangeRate::getEffectiveDate, effectiveDate)
.eq(ExchangeRate::getIsActive, 1)
.eq(ExchangeRate::getIsDeleted, 0)
.last("LIMIT 1");
return this.getOne(queryWrapper);
}
@Override
public ExchangeRate getLatestByCurrency(String baseCurrency, String targetCurrency) {
LambdaQueryWrapper<ExchangeRate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ExchangeRate::getBaseCurrency, baseCurrency)
.eq(ExchangeRate::getTargetCurrency, targetCurrency)
.eq(ExchangeRate::getIsActive, 1)
.eq(ExchangeRate::getIsDeleted, 0)
.orderByDesc(ExchangeRate::getEffectiveDate)
.last("LIMIT 1");
return this.getOne(queryWrapper);
}
@Override
public List<ExchangeRate> getByEffectiveDate(LocalDate effectiveDate) {
LambdaQueryWrapper<ExchangeRate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ExchangeRate::getEffectiveDate, effectiveDate)
.eq(ExchangeRate::getIsActive, 1)
.eq(ExchangeRate::getIsDeleted, 0);
return this.list(queryWrapper);
}
@Override
public List<ExchangeRate> getAllActiveRates() {
LambdaQueryWrapper<ExchangeRate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ExchangeRate::getIsActive, 1)
.eq(ExchangeRate::getIsDeleted, 0)
.orderByAsc(ExchangeRate::getBaseCurrency)
.orderByAsc(ExchangeRate::getTargetCurrency);
return this.list(queryWrapper);
}
@Override
public List<ExchangeRate> getByBaseCurrency(String baseCurrency) {
LambdaQueryWrapper<ExchangeRate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ExchangeRate::getBaseCurrency, baseCurrency)
.eq(ExchangeRate::getIsActive, 1)
.eq(ExchangeRate::getIsDeleted, 0)
.orderByAsc(ExchangeRate::getTargetCurrency);
return this.list(queryWrapper);
}
@Override
public List<ExchangeRate> getByTargetCurrency(String targetCurrency) {
LambdaQueryWrapper<ExchangeRate> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(ExchangeRate::getTargetCurrency, targetCurrency)
.eq(ExchangeRate::getIsActive, 1)
.eq(ExchangeRate::getIsDeleted, 0)
.orderByAsc(ExchangeRate::getBaseCurrency);
return this.list(queryWrapper);
}
}
\ No newline at end of file
......@@ -21,7 +21,7 @@ public class MyBatisPlusCodeGenerator {
})
.strategyConfig(builder -> {
builder.addInclude(
"fvq_setting"
"exchange_rate"
)
.entityBuilder()
.enableLombok()
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yd.base.service.dao.ExchangeRateMapper">
</mapper>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment