fix: 通过免费接口换算汇率,如果失效会取默认值
This commit is contained in:
parent
a3cdc970f6
commit
1bb08855fb
|
|
@ -243,3 +243,16 @@ yuzhou:
|
|||
secretKey: MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAL7U/f7yb9Z9j56dauCUb0B/I0ONAcZDK+TtOnAgLEjV4qrirYHYuCumxYbPFvt6qYggjbBpphFhihbWzf8IPS7iD6VXoSX1T1iAlWFL1ZaBscqQSvPxFGtgpTBiRFS3KkZV70WswcCL770OCkY8+DJpdyk9OkD6vBa0TYU1wxJFAgMBAAECgYBpF+t5iBJHUYbSl2bQn25VWq8U+IbNpRh7TposPculoQTfj052f9+NSp7liw7hF8Bdk2/0g3pNgCYIRevUU7k9MEIKqHCiOWkyavtsfqGYI37PZ4/0uMzB5eibTqKTEkcyskSJ9GxrL4uGKgTGNc213i3VOcZZ4xEfvuDQCHF8gQJBAPATuhQeFNNAIE9TkGiESHFGChSZZgzp1xfGrAt8BwidBSe+r9duAcGJSeNJatxneeu0w6NuwQ6iq9ztnqtoG90CQQDLfSHrOTloHSa+DdBc2SFQWa/P4K2Tznb8Y5ng8L/t+a9sYvGjWOln0R3Bq9TrImm0AjWnq/saaMg2nYD1wr2JAkA36XA5xTO2a0XbE6wbG0u/zb8FQyCIO2GTsPpahl0g/Wi48+kB9CXGjBHANFYF1LeJVIUHqACgRvRdtJ1ycAGlAkEAqASVWiTw2p+fWrQLRG7gS/kR6uIIUI/cvT78UrhWsYdFqof0Hz0N0/PdzwkzkEbk4oYkiWK+viqgjj/0uHfoiQJAQhPYVVLHD7xiJApc/Aga6g0OFF5O7zy8KTsq+KTXqRlJREBH5nirSponHwYalEbUvtQrVs+Z4BCBEGCU8m2GEw==
|
||||
redirectUrl: www.google.com
|
||||
callbackUrl: www.google.com
|
||||
|
||||
# 汇率服务配置
|
||||
exchange-rate:
|
||||
enabled: true
|
||||
apikey: 0bed27315c87475f8dd6a0792a632cc5
|
||||
api-url: https://api.currencyfreaks.com/v2.0/rates/latest
|
||||
# base 货币(API返回的基准货币,通常为USD)
|
||||
base-currency: USD
|
||||
# 备用汇率(当API调用失败时使用)
|
||||
fallback-rate-jinsha: 90
|
||||
fallback-rate-kada: 60
|
||||
# PHP比索的备用汇率(当API调用失败时使用,默认使用JinSha的汇率)
|
||||
fallback-rate-php: 90
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.ruoyi.ai.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 汇率服务接口
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
public interface IExchangeRateService {
|
||||
|
||||
/**
|
||||
* 获取汇率
|
||||
*
|
||||
* @param fromCurrency 源货币代码(如:CNY)
|
||||
* @param toCurrency 目标货币代码(如:PHP)
|
||||
* @return 汇率值
|
||||
* @throws Exception 获取汇率失败时抛出异常
|
||||
*/
|
||||
BigDecimal getExchangeRate(String fromCurrency, String toCurrency) throws Exception;
|
||||
|
||||
/**
|
||||
* 转换金额
|
||||
*
|
||||
* @param amount 原始金额
|
||||
* @param fromCurrency 源货币代码(如:CNY)
|
||||
* @param toCurrency 目标货币代码(如:PHP)
|
||||
* @return 转换后的金额
|
||||
* @throws Exception 获取汇率失败时抛出异常
|
||||
*/
|
||||
BigDecimal convertAmount(BigDecimal amount, String fromCurrency, String toCurrency) throws Exception;
|
||||
}
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
package com.ruoyi.ai.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.ruoyi.ai.service.IExchangeRateService;
|
||||
import com.ruoyi.common.utils.http.OkHttpUtils;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
/**
|
||||
* 汇率服务实现类
|
||||
*
|
||||
* @author system
|
||||
* @date 2025-01-XX
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class ExchangeRateServiceImpl implements IExchangeRateService {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Value("${exchange-rate.api-url:https://api.currencyfreaks.com/v2.0/rates/latest}")
|
||||
private String apiUrl;
|
||||
|
||||
@Value("${exchange-rate.apikey:}")
|
||||
private String apikey;
|
||||
|
||||
@Value("${exchange-rate.base-currency:USD}")
|
||||
private String baseCurrency;
|
||||
|
||||
@Value("${exchange-rate.enabled:true}")
|
||||
private boolean enabled;
|
||||
|
||||
@Value("${exchange-rate.fallback-rate-jinsha:90}")
|
||||
private BigDecimal fallbackRateJinSha;
|
||||
|
||||
@Value("${exchange-rate.fallback-rate-kada:60}")
|
||||
private BigDecimal fallbackRateKaDa;
|
||||
|
||||
@Value("${exchange-rate.fallback-rate-php:90}")
|
||||
private BigDecimal fallbackRatePHP;
|
||||
|
||||
@Override
|
||||
public BigDecimal getExchangeRate(String fromCurrency, String toCurrency) throws Exception {
|
||||
if (!enabled) {
|
||||
log.warn("汇率服务未启用,使用默认汇率");
|
||||
return getFallbackRate(toCurrency);
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求URL - 使用 CurrencyFreaks Rates Latest Endpoint
|
||||
String url = buildApiUrl();
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build();
|
||||
|
||||
// 发送请求
|
||||
Response response = OkHttpUtils.newCall(request).execute();
|
||||
ResponseBody body = response.body();
|
||||
|
||||
if (response.isSuccessful() && body != null) {
|
||||
String responseBody = body.string();
|
||||
log.debug("汇率API响应: {}", responseBody);
|
||||
|
||||
// 解析响应 - CurrencyFreaks Rates Latest Endpoint 响应格式
|
||||
RatesResponse ratesResponse = objectMapper.readValue(responseBody, RatesResponse.class);
|
||||
|
||||
if (ratesResponse != null && ratesResponse.getRates() != null) {
|
||||
BigDecimal rate = calculateRate(ratesResponse, fromCurrency, toCurrency);
|
||||
if (rate != null && rate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
log.info("成功获取汇率 {} -> {}: {}", fromCurrency, toCurrency, rate);
|
||||
return rate;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("汇率API请求失败,状态码: {}", response.code());
|
||||
if (body != null) {
|
||||
log.warn("响应内容: {}", body.string());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取汇率失败,使用备用汇率: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 如果API调用失败,使用备用汇率
|
||||
return getFallbackRate(toCurrency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal convertAmount(BigDecimal amount, String fromCurrency, String toCurrency) throws Exception {
|
||||
if (amount == null) {
|
||||
throw new IllegalArgumentException("金额不能为空");
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
log.warn("汇率服务未启用,使用默认汇率计算");
|
||||
BigDecimal rate = getFallbackRate(toCurrency);
|
||||
// 返回完整精度,不限制小数位
|
||||
return amount.multiply(rate);
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求URL - 使用 CurrencyFreaks Rates Latest Endpoint
|
||||
String url = buildApiUrl();
|
||||
|
||||
// 构建请求
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.build();
|
||||
|
||||
// 发送请求
|
||||
Response response = OkHttpUtils.newCall(request).execute();
|
||||
ResponseBody body = response.body();
|
||||
|
||||
if (response.isSuccessful() && body != null) {
|
||||
String responseBody = body.string();
|
||||
log.debug("汇率API响应: {}", responseBody);
|
||||
|
||||
// 解析响应 - CurrencyFreaks Rates Latest Endpoint 响应格式
|
||||
RatesResponse ratesResponse = objectMapper.readValue(responseBody, RatesResponse.class);
|
||||
|
||||
if (ratesResponse != null && ratesResponse.getRates() != null) {
|
||||
BigDecimal rate = calculateRate(ratesResponse, fromCurrency, toCurrency);
|
||||
if (rate != null && rate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal convertedAmount = amount.multiply(rate);
|
||||
log.info("成功转换金额 {} {} -> {} {}: {}",
|
||||
amount, fromCurrency, convertedAmount, toCurrency, rate);
|
||||
// 返回完整精度,不限制小数位
|
||||
return convertedAmount;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn("汇率API请求失败,状态码: {}", response.code());
|
||||
if (body != null) {
|
||||
log.warn("响应内容: {}", body.string());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("转换金额失败,使用备用汇率计算: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 如果API调用失败,使用备用汇率计算(不限制小数位)
|
||||
BigDecimal rate = getFallbackRate(toCurrency);
|
||||
return amount.multiply(rate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建API URL - 使用 CurrencyFreaks Rates Latest Endpoint
|
||||
*/
|
||||
private String buildApiUrl() {
|
||||
if (apiUrl.contains("currencyfreaks.com") && apiUrl.contains("/rates/latest")) {
|
||||
// 使用 CurrencyFreaks Rates Latest Endpoint 格式
|
||||
// https://api.currencyfreaks.com/v2.0/rates/latest?apikey=YOUR_APIKEY&base=USD
|
||||
StringBuilder urlBuilder = new StringBuilder(apiUrl);
|
||||
urlBuilder.append("?apikey=").append(apikey);
|
||||
urlBuilder.append("&base=").append(baseCurrency.toUpperCase());
|
||||
return urlBuilder.toString();
|
||||
} else {
|
||||
// 兼容其他API格式(向后兼容)
|
||||
return apiUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据汇率响应计算 fromCurrency 到 toCurrency 的汇率
|
||||
*/
|
||||
private BigDecimal calculateRate(RatesResponse ratesResponse, String fromCurrency, String toCurrency) {
|
||||
String base = ratesResponse.getBase();
|
||||
java.util.Map<String, String> rates = ratesResponse.getRates();
|
||||
|
||||
if (rates == null || rates.isEmpty()) {
|
||||
log.warn("汇率响应中 rates 为空");
|
||||
return null;
|
||||
}
|
||||
|
||||
String fromUpper = fromCurrency.toUpperCase();
|
||||
String toUpper = toCurrency.toUpperCase();
|
||||
String baseUpper = base.toUpperCase();
|
||||
|
||||
// 如果源货币就是基准货币
|
||||
if (baseUpper.equals(fromUpper)) {
|
||||
String toRateStr = rates.get(toUpper);
|
||||
if (toRateStr != null) {
|
||||
try {
|
||||
return new BigDecimal(toRateStr);
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("无法解析目标货币 {} 的汇率: {}", toUpper, toRateStr);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
log.warn("未找到目标货币 {} 的汇率", toUpper);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果目标货币就是基准货币
|
||||
if (baseUpper.equals(toUpper)) {
|
||||
String fromRateStr = rates.get(fromUpper);
|
||||
if (fromRateStr != null) {
|
||||
try {
|
||||
// 如果目标货币是基准货币,汇率是 1 / fromRate
|
||||
BigDecimal fromRate = new BigDecimal(fromRateStr);
|
||||
if (fromRate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
return BigDecimal.ONE.divide(fromRate, 10, RoundingMode.HALF_UP);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("无法解析源货币 {} 的汇率: {}", fromUpper, fromRateStr);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
log.warn("未找到源货币 {} 的汇率", fromUpper);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 两个货币都不是基准货币,需要计算
|
||||
String fromRateStr = rates.get(fromUpper);
|
||||
String toRateStr = rates.get(toUpper);
|
||||
|
||||
if (fromRateStr == null) {
|
||||
log.warn("未找到源货币 {} 的汇率", fromUpper);
|
||||
return null;
|
||||
}
|
||||
if (toRateStr == null) {
|
||||
log.warn("未找到目标货币 {} 的汇率", toUpper);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
BigDecimal fromRate = new BigDecimal(fromRateStr);
|
||||
BigDecimal toRate = new BigDecimal(toRateStr);
|
||||
|
||||
if (fromRate.compareTo(BigDecimal.ZERO) > 0) {
|
||||
// 汇率 = toRate / fromRate
|
||||
// 例如:base=USD, from=CNY, to=PHP
|
||||
// USD->CNY = fromRate, USD->PHP = toRate
|
||||
// CNY->PHP = toRate / fromRate
|
||||
return toRate.divide(fromRate, 10, RoundingMode.HALF_UP);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("无法解析汇率: from={}, to={}", fromRateStr, toRateStr);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取备用汇率(当API调用失败时使用)
|
||||
*/
|
||||
private BigDecimal getFallbackRate(String toCurrency) {
|
||||
// 根据目标货币返回不同的备用汇率
|
||||
if ("PHP".equalsIgnoreCase(toCurrency)) {
|
||||
// PHP比索,使用配置的PHP备用汇率
|
||||
log.warn("使用PHP备用汇率: {}", fallbackRatePHP);
|
||||
return fallbackRatePHP;
|
||||
}
|
||||
// 其他货币可以扩展,默认使用PHP的备用汇率
|
||||
log.warn("未配置货币 {} 的备用汇率,使用PHP默认汇率 {}", toCurrency, fallbackRatePHP);
|
||||
return fallbackRatePHP;
|
||||
}
|
||||
|
||||
/**
|
||||
* CurrencyFreaks Rates Latest Endpoint 响应对象
|
||||
*/
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static class RatesResponse {
|
||||
@JsonProperty("date")
|
||||
private String date;
|
||||
|
||||
@JsonProperty("base")
|
||||
private String base;
|
||||
|
||||
@JsonProperty("rates")
|
||||
private java.util.Map<String, String> rates;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,6 +47,8 @@ public class JinShaService implements IJinShaService {
|
|||
private IAiUserService aiUserService;
|
||||
@Autowired
|
||||
private IAiRechargeGiftGearService aiRechargeGiftGearService;
|
||||
@Autowired
|
||||
private IExchangeRateService exchangeRateService;
|
||||
|
||||
@Override
|
||||
public PayResVO jinShaPay(Long gearId) throws Exception {
|
||||
|
|
@ -57,7 +59,8 @@ public class JinShaService implements IJinShaService {
|
|||
throw new ServiceException("The gear position does not exist.", -1);
|
||||
}
|
||||
BigDecimal amount = aiRechargeGiftGear.getRechargeAmount();
|
||||
amount = amount.multiply(new BigDecimal(90));
|
||||
// 使用汇率服务转换金额(从CNY转换为目标货币,默认INR)
|
||||
amount = exchangeRateService.convertAmount(amount, "USD", "INR");
|
||||
|
||||
JinShaBodyReq req = new JinShaBodyReq();
|
||||
req.setAppid(appId);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ public class KaDaService implements IKaDaService {
|
|||
private IAiRechargeService aiRechargeService;
|
||||
@Autowired
|
||||
private IAiRechargeGiftService aiRechargeGiftService;
|
||||
@Autowired
|
||||
private IExchangeRateService exchangeRateService;
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -65,7 +67,8 @@ public class KaDaService implements IKaDaService {
|
|||
throw new ServiceException("The gear position does not exist.", -1);
|
||||
}
|
||||
BigDecimal amount = aiRechargeGiftGear.getRechargeAmount();
|
||||
amount = amount.multiply(new BigDecimal(60.00));
|
||||
// 使用汇率服务转换金额(从CNY转换为目标货币,默认PHP)
|
||||
amount = exchangeRateService.convertAmount(amount, "USD", "PHP");
|
||||
|
||||
String uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
|
||||
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());
|
||||
|
|
|
|||
Loading…
Reference in New Issue