fix: 通过免费接口换算汇率,如果失效会取默认值

This commit is contained in:
old burden 2026-01-20 16:54:56 +08:00
parent a3cdc970f6
commit 1bb08855fb
5 changed files with 346 additions and 2 deletions

View File

@ -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== 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 redirectUrl: www.google.com
callbackUrl: 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

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -47,6 +47,8 @@ public class JinShaService implements IJinShaService {
private IAiUserService aiUserService; private IAiUserService aiUserService;
@Autowired @Autowired
private IAiRechargeGiftGearService aiRechargeGiftGearService; private IAiRechargeGiftGearService aiRechargeGiftGearService;
@Autowired
private IExchangeRateService exchangeRateService;
@Override @Override
public PayResVO jinShaPay(Long gearId) throws Exception { 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); throw new ServiceException("The gear position does not exist.", -1);
} }
BigDecimal amount = aiRechargeGiftGear.getRechargeAmount(); BigDecimal amount = aiRechargeGiftGear.getRechargeAmount();
amount = amount.multiply(new BigDecimal(90)); // 使用汇率服务转换金额从CNY转换为目标货币默认INR
amount = exchangeRateService.convertAmount(amount, "USD", "INR");
JinShaBodyReq req = new JinShaBodyReq(); JinShaBodyReq req = new JinShaBodyReq();
req.setAppid(appId); req.setAppid(appId);

View File

@ -54,6 +54,8 @@ public class KaDaService implements IKaDaService {
private IAiRechargeService aiRechargeService; private IAiRechargeService aiRechargeService;
@Autowired @Autowired
private IAiRechargeGiftService aiRechargeGiftService; private IAiRechargeGiftService aiRechargeGiftService;
@Autowired
private IExchangeRateService exchangeRateService;
@Override @Override
@ -65,7 +67,8 @@ public class KaDaService implements IKaDaService {
throw new ServiceException("The gear position does not exist.", -1); throw new ServiceException("The gear position does not exist.", -1);
} }
BigDecimal amount = aiRechargeGiftGear.getRechargeAmount(); 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 uuid = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date()); String dateTime = new SimpleDateFormat("yyyyMMdd").format(new Date());