|
@@ -20,6 +20,9 @@ import com.fs.proxy.mapper.TenantTrafficPricingMapper;
|
|
|
import com.fs.proxy.model.ConsumeServiceOutcome;
|
|
import com.fs.proxy.model.ConsumeServiceOutcome;
|
|
|
import com.fs.proxy.model.UnitPricePair;
|
|
import com.fs.proxy.model.UnitPricePair;
|
|
|
import com.fs.proxy.service.BalanceService;
|
|
import com.fs.proxy.service.BalanceService;
|
|
|
|
|
+import com.fs.tenant.domain.TenantInfo;
|
|
|
|
|
+import com.fs.tenant.mapper.TenantInfoMapper;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
import org.redisson.api.RLock;
|
|
import org.redisson.api.RLock;
|
|
|
import org.redisson.api.RedissonClient;
|
|
import org.redisson.api.RedissonClient;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
@@ -39,12 +42,16 @@ import java.util.concurrent.TimeUnit;
|
|
|
* @author fs
|
|
* @author fs
|
|
|
* @date 2024-01-01
|
|
* @date 2024-01-01
|
|
|
*/
|
|
*/
|
|
|
|
|
+@Slf4j
|
|
|
@Service
|
|
@Service
|
|
|
public class BalanceServiceImpl implements BalanceService {
|
|
public class BalanceServiceImpl implements BalanceService {
|
|
|
|
|
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private TenantBalanceMapper balanceMapper;
|
|
private TenantBalanceMapper balanceMapper;
|
|
|
|
|
|
|
|
|
|
+ @Autowired
|
|
|
|
|
+ private TenantInfoMapper tenantInfoMapper;
|
|
|
|
|
+
|
|
|
@Autowired
|
|
@Autowired
|
|
|
private TenantConsumeRecordMapper recordMapper;
|
|
private TenantConsumeRecordMapper recordMapper;
|
|
|
|
|
|
|
@@ -230,6 +237,7 @@ public class BalanceServiceImpl implements BalanceService {
|
|
|
if (!isPayAsYouGo(consumeType)) {
|
|
if (!isPayAsYouGo(consumeType)) {
|
|
|
return checkBalance(tenantId, consumeType, quantity);
|
|
return checkBalance(tenantId, consumeType, quantity);
|
|
|
}
|
|
}
|
|
|
|
|
+ alignTotalBalanceWithTenantInfo(tenantId);
|
|
|
TenantBalance balance = balanceMapper.selectBalanceByTenantId(tenantId);
|
|
TenantBalance balance = balanceMapper.selectBalanceByTenantId(tenantId);
|
|
|
if (balance == null || balance.getTotalBalance() == null) {
|
|
if (balance == null || balance.getTotalBalance() == null) {
|
|
|
return false;
|
|
return false;
|
|
@@ -242,6 +250,52 @@ public class BalanceServiceImpl implements BalanceService {
|
|
|
return balance.getTotalBalance().compareTo(needed) >= 0;
|
|
return balance.getTotalBalance().compareTo(needed) >= 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @DataSource(DataSourceType.MASTER)
|
|
|
|
|
+ public String explainPayAsYouGoBalanceFailure(Long tenantId, ConsumeTypeEnum consumeType, Integer quantity) {
|
|
|
|
|
+ if (tenantId == null) {
|
|
|
|
|
+ return "租户信息缺失,无法发起外呼";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (consumeType == null || quantity == null || quantity <= 0) {
|
|
|
|
|
+ return "外呼计费参数无效";
|
|
|
|
|
+ }
|
|
|
|
|
+ if (consumeType == ConsumeTypeEnum.MANUAL_CALL) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!isPayAsYouGo(consumeType)) {
|
|
|
|
|
+ return checkBalance(tenantId, consumeType, quantity) ? null : "服务专项余额不足";
|
|
|
|
|
+ }
|
|
|
|
|
+ TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(String.valueOf(tenantId));
|
|
|
|
|
+ if (tenantInfo == null) {
|
|
|
|
|
+ return "租户不存在(tenantId=" + tenantId + ")";
|
|
|
|
|
+ }
|
|
|
|
|
+ alignTotalBalanceWithTenantInfo(tenantId);
|
|
|
|
|
+ TenantBalance balance = balanceMapper.selectBalanceByTenantId(tenantId);
|
|
|
|
|
+ BigDecimal infoBalance = tenantInfo.getBalance() != null ? tenantInfo.getBalance() : BigDecimal.ZERO;
|
|
|
|
|
+ if (balance == null || balance.getTotalBalance() == null) {
|
|
|
|
|
+ if (infoBalance.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
|
|
+ return "租户计费账户未初始化,请联系管理员同步余额";
|
|
|
|
|
+ }
|
|
|
|
|
+ return "租户总账户余额不足(当前余额 0.00 元)";
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal unitPrice = resolveUnitPrice(tenantId, consumeType);
|
|
|
|
|
+ if (unitPrice == null || unitPrice.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ return "未配置" + consumeType.getName() + "计费单价,无法发起外呼";
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal needed = unitPrice.multiply(BigDecimal.valueOf(quantity));
|
|
|
|
|
+ if (balance.getTotalBalance().compareTo(needed) >= 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return String.format("租户余额不足:当前总余额 %.2f 元,至少需要 %.2f 元(%s × %d 分钟)",
|
|
|
|
|
+ balance.getTotalBalance(), needed, consumeType.getName(), quantity);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @DataSource(DataSourceType.MASTER)
|
|
|
|
|
+ public void syncBillingBalanceFromTenantInfo(Long tenantId) {
|
|
|
|
|
+ alignTotalBalanceWithTenantInfo(tenantId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
@DataSource(DataSourceType.MASTER)
|
|
@DataSource(DataSourceType.MASTER)
|
|
|
public BigDecimal resolveUnitPrice(Long tenantId, ConsumeTypeEnum consumeType) {
|
|
public BigDecimal resolveUnitPrice(Long tenantId, ConsumeTypeEnum consumeType) {
|
|
@@ -251,6 +305,9 @@ public class BalanceServiceImpl implements BalanceService {
|
|
|
|
|
|
|
|
private ConsumeServiceOutcome doConsumeService(Long tenantId, ConsumeTypeEnum consumeType,
|
|
private ConsumeServiceOutcome doConsumeService(Long tenantId, ConsumeTypeEnum consumeType,
|
|
|
Integer quantity, String remark, String orderNo) {
|
|
Integer quantity, String remark, String orderNo) {
|
|
|
|
|
+ if (isPayAsYouGo(consumeType)) {
|
|
|
|
|
+ alignTotalBalanceWithTenantInfo(tenantId);
|
|
|
|
|
+ }
|
|
|
TenantBalance balance = balanceMapper.selectBalanceByTenantIdForUpdate(tenantId);
|
|
TenantBalance balance = balanceMapper.selectBalanceByTenantIdForUpdate(tenantId);
|
|
|
if (balance == null) {
|
|
if (balance == null) {
|
|
|
return ConsumeServiceOutcome.builder().result(ConsumeServiceResult.FAILED).orderNo(orderNo).build();
|
|
return ConsumeServiceOutcome.builder().result(ConsumeServiceResult.FAILED).orderNo(orderNo).build();
|
|
@@ -289,6 +346,7 @@ public class BalanceServiceImpl implements BalanceService {
|
|
|
if (inserted <= 0) {
|
|
if (inserted <= 0) {
|
|
|
return ConsumeServiceOutcome.builder().result(ConsumeServiceResult.FAILED).orderNo(orderNo).build();
|
|
return ConsumeServiceOutcome.builder().result(ConsumeServiceResult.FAILED).orderNo(orderNo).build();
|
|
|
}
|
|
}
|
|
|
|
|
+ syncTenantInfoBalanceAfterConsume(tenantId, totalCost);
|
|
|
return ConsumeServiceOutcome.builder()
|
|
return ConsumeServiceOutcome.builder()
|
|
|
.result(ConsumeServiceResult.SUCCESS)
|
|
.result(ConsumeServiceResult.SUCCESS)
|
|
|
.amount(totalCost)
|
|
.amount(totalCost)
|
|
@@ -440,6 +498,47 @@ public class BalanceServiceImpl implements BalanceService {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 管理后台充值写入 tenant_info.balance,外呼扣费读取 tenant_balance.total_balance。
|
|
|
|
|
+ * 校验/扣费前将 tenant_info 中更高的余额同步到 tenant_balance,避免两表不一致导致误报余额不足。
|
|
|
|
|
+ */
|
|
|
|
|
+ private void alignTotalBalanceWithTenantInfo(Long tenantId) {
|
|
|
|
|
+ if (tenantId == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ TenantInfo tenantInfo = tenantInfoMapper.selectTenantInfoById(String.valueOf(tenantId));
|
|
|
|
|
+ if (tenantInfo == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal infoBalance = tenantInfo.getBalance() != null ? tenantInfo.getBalance() : BigDecimal.ZERO;
|
|
|
|
|
+ TenantBalance balance = balanceMapper.selectBalanceByTenantId(tenantId);
|
|
|
|
|
+ if (balance == null) {
|
|
|
|
|
+ initTenantBalance(tenantId, StringUtils.defaultIfBlank(tenantInfo.getTenantName(), "租户" + tenantId));
|
|
|
|
|
+ if (infoBalance.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
|
|
+ balanceMapper.increaseTotalBalance(tenantId, infoBalance);
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal totalBalance = balance.getTotalBalance() != null ? balance.getTotalBalance() : BigDecimal.ZERO;
|
|
|
|
|
+ if (infoBalance.compareTo(totalBalance) > 0) {
|
|
|
|
|
+ balanceMapper.increaseTotalBalance(tenantId, infoBalance.subtract(totalBalance));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void syncTenantInfoBalanceAfterConsume(Long tenantId, BigDecimal amount) {
|
|
|
|
|
+ if (tenantId == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ int rows = tenantInfoMapper.updateBalance(tenantId, amount.negate());
|
|
|
|
|
+ if (rows <= 0) {
|
|
|
|
|
+ log.warn("扣费后同步 tenant_info 余额失败 tenantId={}, amount={}", tenantId, amount);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception ex) {
|
|
|
|
|
+ log.warn("扣费后同步 tenant_info 余额异常 tenantId={}, amount={}", tenantId, amount, ex);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional
|
|
@Transactional
|
|
|
public boolean transferToTotal(Long tenantId, ConsumeTypeEnum consumeType, Integer quantity) {
|
|
public boolean transferToTotal(Long tenantId, ConsumeTypeEnum consumeType, Integer quantity) {
|