package com.ruoyi.other.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.account.api.dto.SendMessageDTO;
import com.ruoyi.account.api.feignClient.AppUserClient;
import com.ruoyi.account.api.feignClient.AppUserWhitelistClient;
import com.ruoyi.account.api.feignClient.ChargingCardClient;
import com.ruoyi.account.api.feignClient.VinManagementClient;
import com.ruoyi.account.api.model.TAppUserWhitelist;
import com.ruoyi.account.api.model.TChargingCard;
import com.ruoyi.account.api.model.TVinManagement;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.other.api.domain.TBalanceWarningRecord;
import com.ruoyi.other.api.domain.TCompanyBalanceWarning;
import com.ruoyi.other.api.domain.TGroup;
import com.ruoyi.other.api.domain.TGroupCompany;
import com.ruoyi.other.mapper.TCompanyBalanceWarningMapper;
import com.ruoyi.other.mapper.TGroupCompanyMapper;
import com.ruoyi.other.mapper.TGroupMapper;
import com.ruoyi.other.service.TBalanceWarningRecordService;
import com.ruoyi.other.service.TBalanceWarningScanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
/**
*
* 公司余额预警扫描服务实现:
* 遍历预警配置,公司总账户余额/子账户余额低于预警线时发送提醒短信(按天去重)。
*
*
* @author mxcd
* @since 2026-10-10
*/
@Slf4j
@Service
public class TBalanceWarningScanServiceImpl implements TBalanceWarningScanService {
/** 预警范围:公司账户 */
private static final int WARN_SCOPE_COMPANY = 1;
/** 预警范围:VIN码 */
private static final int WARN_SCOPE_VIN = 2;
/** 预警范围:刷卡卡 */
private static final int WARN_SCOPE_CARD = 3;
/** 预警范围:白名单用户 */
private static final int WARN_SCOPE_WHITELIST = 4;
/** 通知对象:集团联系人 */
private static final int NOTIFY_OBJECT_GROUP_CONTACT = 1;
/** 通知对象:公司联系人 */
private static final int NOTIFY_OBJECT_COMPANY_CONTACT = 2;
/** 通知方式:短信 */
private static final int NOTIFY_TYPE_SMS = 1;
/** 账户模式:分配模式 */
private static final int ACCOUNT_MODE_ALLOCATION = 2;
/** 公司状态:启用 */
private static final int COMPANY_STATUS_ENABLED = 1;
/** 短信类型:公司总账户余额不足 */
private static final int SMS_TYPE_COMPANY_BALANCE = 5;
/** 短信类型:子账号余额不足 */
private static final int SMS_TYPE_SUB_BALANCE = 6;
@Resource
private TCompanyBalanceWarningMapper companyBalanceWarningMapper;
@Resource
private TGroupCompanyMapper groupCompanyMapper;
@Resource
private TGroupMapper groupMapper;
@Resource
private TBalanceWarningRecordService balanceWarningRecordService;
@Resource
private VinManagementClient vinManagementClient;
@Resource
private ChargingCardClient chargingCardClient;
@Resource
private AppUserWhitelistClient appUserWhitelistClient;
@Resource
private AppUserClient appUserClient;
@Override
public void scan() {
List warnings = companyBalanceWarningMapper.selectList(
Wrappers.lambdaQuery());
for (TCompanyBalanceWarning warning : warnings) {
try {
handleWarning(warning);
} catch (Exception e) {
log.error("余额预警扫描处理失败, companyId={}", warning.getCompanyId(), e);
}
}
}
/**
* 处理单条预警配置
*/
private void handleWarning(TCompanyBalanceWarning warning) {
if (warning.getCompanyId() == null) {
return;
}
TGroupCompany company = groupCompanyMapper.selectById(warning.getCompanyId());
// 公司不存在或已停用,跳过
if (company == null || !Integer.valueOf(COMPANY_STATUS_ENABLED).equals(company.getStatus())) {
return;
}
// 仅支持短信通知
if (warning.getNotifyType() != null && warning.getNotifyType() != NOTIFY_TYPE_SMS) {
return;
}
// 联系人手机号为空时跳过(记日志)
String phone = resolveNotifyPhone(warning.getNotifyObject(), company);
if (isBlank(phone)) {
log.info("余额预警联系人手机号为空,跳过发送, companyId={}, notifyObject={}",
warning.getCompanyId(), warning.getNotifyObject());
return;
}
// 公司总账户余额预警
if (warning.getCompanyWarnAmount() != null && company.getAvailableBalance() != null
&& company.getAvailableBalance().compareTo(warning.getCompanyWarnAmount()) <= 0) {
sendCompanyWarn(warning, company, phone);
}
// 子账户余额预警(仅分配模式)
if (Integer.valueOf(ACCOUNT_MODE_ALLOCATION).equals(company.getAccountMode())
&& warning.getSubWarnAmount() != null) {
warnSubAccounts(warning, company, phone);
}
}
/**
* 解析通知对象手机号(1=集团联系人,2=公司联系人;默认集团联系人)
*/
private String resolveNotifyPhone(Integer notifyObject, TGroupCompany company) {
int target = notifyObject == null ? NOTIFY_OBJECT_GROUP_CONTACT : notifyObject;
if (target == NOTIFY_OBJECT_COMPANY_CONTACT) {
return company.getContactPhone();
}
if (company.getGroupId() == null) {
return null;
}
TGroup group = groupMapper.selectById(company.getGroupId());
return group == null ? null : group.getContactPhone();
}
/**
* 发送公司总账户余额不足提醒(模板1:公司名称/当前余额/预警线)
*/
private void sendCompanyWarn(TCompanyBalanceWarning warning, TGroupCompany company, String phone) {
if (balanceWarningRecordService.isNotifiedToday(warning.getCompanyId(), WARN_SCOPE_COMPANY, "")) {
return;
}
SendMessageDTO dto = new SendMessageDTO();
dto.setType(SMS_TYPE_COMPANY_BALANCE);
dto.setPhone(phone);
dto.setCompanyName(company.getName());
dto.setBalance(formatAmount(company.getAvailableBalance()));
dto.setWarnAmount(formatAmount(warning.getCompanyWarnAmount()));
sendSms(dto, warning.getCompanyId(), WARN_SCOPE_COMPANY, "", phone);
}
/**
* 扫描三类子账户(VIN/卡/白名单)余额,低于子账户预警线时发送提醒(模板2)
*/
private void warnSubAccounts(TCompanyBalanceWarning warning, TGroupCompany company, String phone) {
Integer companyId = warning.getCompanyId();
BigDecimal subWarn = warning.getSubWarnAmount();
String companyName = company.getName();
for (TVinManagement vin : fetchVins(companyId)) {
if (vin.getBalance() == null || vin.getBalance().compareTo(subWarn) > 0) {
continue;
}
String identifier = vin.getVin();
if (isBlank(identifier)) {
continue;
}
if (balanceWarningRecordService.isNotifiedToday(companyId, WARN_SCOPE_VIN, identifier)) {
continue;
}
String userInfo = isBlank(vin.getPlateNumber()) ? identifier : vin.getPlateNumber();
sendSms(buildSubWarnDto(phone, companyName, userInfo, vin.getBalance(), subWarn),
companyId, WARN_SCOPE_VIN, identifier, phone);
}
for (TChargingCard card : fetchCards(companyId)) {
if (card.getBalance() == null || card.getBalance().compareTo(subWarn) > 0) {
continue;
}
String identifier = card.getCardNo();
if (isBlank(identifier)) {
continue;
}
if (balanceWarningRecordService.isNotifiedToday(companyId, WARN_SCOPE_CARD, identifier)) {
continue;
}
sendSms(buildSubWarnDto(phone, companyName, identifier, card.getBalance(), subWarn),
companyId, WARN_SCOPE_CARD, identifier, phone);
}
for (TAppUserWhitelist whitelist : fetchWhitelists(companyId)) {
if (whitelist.getBalance() == null || whitelist.getBalance().compareTo(subWarn) > 0) {
continue;
}
String identifier = whitelist.getPhone();
if (isBlank(identifier)) {
continue;
}
if (balanceWarningRecordService.isNotifiedToday(companyId, WARN_SCOPE_WHITELIST, identifier)) {
continue;
}
String userInfo = isBlank(whitelist.getName()) ? identifier : whitelist.getName();
sendSms(buildSubWarnDto(phone, companyName, userInfo, whitelist.getBalance(), subWarn),
companyId, WARN_SCOPE_WHITELIST, identifier, phone);
}
}
/**
* 构建子账号余额不足短信 DTO
*/
private SendMessageDTO buildSubWarnDto(String phone, String companyName, String userInfo,
BigDecimal balance, BigDecimal subWarn) {
SendMessageDTO dto = new SendMessageDTO();
dto.setType(SMS_TYPE_SUB_BALANCE);
dto.setPhone(phone);
dto.setCompanyName(companyName);
dto.setUserInfo(userInfo);
dto.setBalance(formatAmount(balance));
dto.setWarnAmount(formatAmount(subWarn));
return dto;
}
/**
* 发送短信;成功后写提醒记录(按天去重),失败不写以便下次扫描重试
*/
private void sendSms(SendMessageDTO dto, Integer companyId, int warnScope, String subIdentifier, String phone) {
try {
R result = appUserClient.sendMessage(dto);
if (R.isSuccess(result)) {
saveNotifyRecord(companyId, warnScope, subIdentifier, phone);
log.info("余额预警短信发送成功, companyId={}, scope={}, identifier={}, phone={}",
companyId, warnScope, subIdentifier, phone);
} else {
log.warn("余额预警短信发送失败, companyId={}, scope={}, identifier={}, result={}",
companyId, warnScope, subIdentifier, result);
}
} catch (Exception e) {
log.error("余额预警短信发送异常, companyId={}, scope={}, identifier={}",
companyId, warnScope, subIdentifier, e);
}
}
/**
* 保存提醒记录(唯一键兜底,重复插入忽略)
*/
private void saveNotifyRecord(Integer companyId, int warnScope, String subIdentifier, String phone) {
try {
TBalanceWarningRecord record = new TBalanceWarningRecord();
record.setCompanyId(companyId);
record.setWarnScope(warnScope);
record.setSubIdentifier(subIdentifier == null ? "" : subIdentifier);
record.setNotifyPhone(phone);
record.setWarnDate(LocalDate.now());
balanceWarningRecordService.save(record);
} catch (Exception e) {
log.warn("保存余额预警提醒记录失败(可能唯一键冲突), companyId={}, scope={}, identifier={}",
companyId, warnScope, subIdentifier, e);
}
}
/**
* 金额格式化:保留2位小数;超过模板变量长度(10)时退回整数部分,仍超则截断
*/
private String formatAmount(BigDecimal amount) {
if (amount == null) {
return "0";
}
String value = amount.setScale(2, RoundingMode.HALF_UP).toPlainString();
if (value.length() > 10) {
value = amount.setScale(0, RoundingMode.DOWN).toPlainString();
}
return value.length() > 10 ? value.substring(0, 10) : value;
}
/**
* 远程查询公司VIN码列表(降级返回空列表,保证扫描主流程不受影响)
*/
private List fetchVins(Integer companyId) {
try {
R> response = vinManagementClient.listByCompany(companyId);
return (response != null && R.isSuccess(response) && response.getData() != null)
? response.getData() : new ArrayList<>();
} catch (Exception e) {
return new ArrayList<>();
}
}
/**
* 远程查询公司卡片列表(降级返回空列表)
*/
private List fetchCards(Integer companyId) {
try {
R> response = chargingCardClient.listByCompany(companyId);
return (response != null && R.isSuccess(response) && response.getData() != null)
? response.getData() : new ArrayList<>();
} catch (Exception e) {
return new ArrayList<>();
}
}
/**
* 远程查询公司白名单列表(降级返回空列表)
*/
private List fetchWhitelists(Integer companyId) {
try {
R> response = appUserWhitelistClient.listByCompany(companyId);
return (response != null && R.isSuccess(response) && response.getData() != null)
? response.getData() : new ArrayList<>();
} catch (Exception e) {
return new ArrayList<>();
}
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
}