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;
|
|
/**
|
* <p>
|
* 公司余额预警扫描服务实现:
|
* 遍历预警配置,公司总账户余额/子账户余额低于预警线时发送提醒短信(按天去重)。
|
* </p>
|
*
|
* @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<TCompanyBalanceWarning> warnings = companyBalanceWarningMapper.selectList(
|
Wrappers.<TCompanyBalanceWarning>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<String> 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<TVinManagement> fetchVins(Integer companyId) {
|
try {
|
R<List<TVinManagement>> 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<TChargingCard> fetchCards(Integer companyId) {
|
try {
|
R<List<TChargingCard>> 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<TAppUserWhitelist> fetchWhitelists(Integer companyId) {
|
try {
|
R<List<TAppUserWhitelist>> 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();
|
}
|
|
}
|