客户超额消息通知
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
package com.bms.notify.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 通知消息 sys_notify_message
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
@Data
|
||||
public class NotifyMessage
|
||||
{
|
||||
/** 消息ID */
|
||||
private Long messageId;
|
||||
|
||||
/** 事件类型 */
|
||||
private String eventType;
|
||||
|
||||
/** 标题 */
|
||||
private String title;
|
||||
|
||||
/** 内容 */
|
||||
private String content;
|
||||
|
||||
/** 关联客户ID */
|
||||
private Long customerId;
|
||||
|
||||
/** 客户编号快照 */
|
||||
private String customerCode;
|
||||
|
||||
/** 客户名称快照 */
|
||||
private String customerName;
|
||||
|
||||
/** 超额金额(CNY,正数) */
|
||||
private BigDecimal overdueAmount;
|
||||
|
||||
/** 创建者(系统任务记为 system) */
|
||||
private String createBy;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/* ===== 管理页统计/明细字段 ===== */
|
||||
|
||||
/** 接收人数 */
|
||||
private Integer receiveCount;
|
||||
|
||||
/** 已读人数 */
|
||||
private Integer readCount;
|
||||
|
||||
/** 接收明细 */
|
||||
private List<NotifyReceive> receives;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.bms.notify.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 超额客户扫描结果(只读DTO,不落表)。
|
||||
*
|
||||
* <p>可用额度口径与客户资金状态保持一致:
|
||||
* 余额 + 当日有效的信用额度 + 当日有效的临时额度,< 0 即超额(欠费)。</p>
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
@Data
|
||||
public class NotifyOverdueCustomer
|
||||
{
|
||||
/** 客户ID */
|
||||
private Long customerId;
|
||||
|
||||
/** 客户编号 */
|
||||
private String customerCode;
|
||||
|
||||
/** 客户名称 */
|
||||
private String customerName;
|
||||
|
||||
/** 停用标志(0正常 1停用) */
|
||||
private String isEnable;
|
||||
|
||||
/** 账户余额 */
|
||||
private BigDecimal balance;
|
||||
|
||||
/** 当日有效额度合计后可用额度(负=超额) */
|
||||
private BigDecimal available;
|
||||
|
||||
/** 超额金额(= -available,正数) */
|
||||
private BigDecimal overdueAmount;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.bms.notify.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 通知消息接收人 sys_notify_receive
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
@Data
|
||||
public class NotifyReceive
|
||||
{
|
||||
/** 接收ID */
|
||||
private Long receiveId;
|
||||
|
||||
/** 消息ID */
|
||||
private Long messageId;
|
||||
|
||||
/** 接收用户ID */
|
||||
private Long userId;
|
||||
|
||||
/** 已读标志(0未读 1已读) */
|
||||
private String readFlag;
|
||||
|
||||
/** 阅读时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date readTime;
|
||||
|
||||
/** 登录弹屏是否已展示(N否 Y是) */
|
||||
private String popupFlag;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
|
||||
/* ===== 我的消息列表展示字段(来自 sys_notify_message / sys_user) ===== */
|
||||
|
||||
/** 标题 */
|
||||
private String title;
|
||||
|
||||
/** 内容 */
|
||||
private String content;
|
||||
|
||||
/** 事件类型 */
|
||||
private String eventType;
|
||||
|
||||
/** 客户名称 */
|
||||
private String customerName;
|
||||
|
||||
/** 客户编号 */
|
||||
private String customerCode;
|
||||
|
||||
/** 超额金额(CNY) */
|
||||
private java.math.BigDecimal overdueAmount;
|
||||
|
||||
/** 接收人昵称 */
|
||||
private String nickName;
|
||||
|
||||
/** 接收人账号 */
|
||||
private String userName;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.bms.notify.domain;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.common.core.domain.BaseEntity;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 消息通知规则 sys_notify_rule
|
||||
*
|
||||
* <p>当前仅一种事件:{@link #EVENT_CUSTOMER_OVERDUE}(客户已超额/欠费)。
|
||||
* 每条规则可配置多个通知对象(用户/角色),见 {@link NotifyRuleTarget}。</p>
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class NotifyRule extends BaseEntity
|
||||
{
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 事件类型:客户已超额(欠费) */
|
||||
public static final String EVENT_CUSTOMER_OVERDUE = "CUSTOMER_OVERDUE";
|
||||
|
||||
/** 对象类型:用户 */
|
||||
public static final String TARGET_USER = "USER";
|
||||
/** 对象类型:角色 */
|
||||
public static final String TARGET_ROLE = "ROLE";
|
||||
|
||||
/** 规则ID */
|
||||
private Long ruleId;
|
||||
|
||||
/** 规则名称 */
|
||||
private String ruleName;
|
||||
|
||||
/** 事件类型 */
|
||||
private String eventType;
|
||||
|
||||
/** 状态(0启用 1停用) */
|
||||
private String status;
|
||||
|
||||
/** 删除标志(0存在 2删除) */
|
||||
private String delFlag;
|
||||
|
||||
/** 通知对象列表(保存/回显用) */
|
||||
private List<NotifyRuleTarget> targets;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.bms.notify.domain;
|
||||
|
||||
import java.util.Date;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 通知规则对象 sys_notify_rule_target(USER 指定用户 / ROLE 指定角色)
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
@Data
|
||||
public class NotifyRuleTarget
|
||||
{
|
||||
/** 对象ID */
|
||||
private Long targetId;
|
||||
|
||||
/** 规则ID */
|
||||
private Long ruleId;
|
||||
|
||||
/** 对象类型:USER/ROLE */
|
||||
private String targetType;
|
||||
|
||||
/** 用户ID或角色ID */
|
||||
private Long refId;
|
||||
|
||||
/** 用户名/角色名快照 */
|
||||
private String refName;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.bms.notify.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import com.bms.notify.domain.NotifyMessage;
|
||||
import com.bms.notify.domain.NotifyOverdueCustomer;
|
||||
import com.bms.notify.domain.NotifyReceive;
|
||||
import com.bms.notify.domain.NotifyRule;
|
||||
import com.bms.notify.domain.NotifyRuleTarget;
|
||||
|
||||
/**
|
||||
* 消息通知 Mapper(规则/对象/消息/接收/扫描日志)
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
public interface NotifyMapper
|
||||
{
|
||||
/* ================= 规则 ================= */
|
||||
|
||||
List<NotifyRule> selectRuleList(NotifyRule query);
|
||||
|
||||
NotifyRule selectRuleById(@Param("ruleId") Long ruleId);
|
||||
|
||||
int insertRule(NotifyRule rule);
|
||||
|
||||
int updateRule(NotifyRule rule);
|
||||
|
||||
/** 软删除规则 */
|
||||
int deleteRuleByIds(@Param("ids") Long[] ids, @Param("updateBy") String updateBy);
|
||||
|
||||
int countEnabledRules(@Param("eventType") String eventType);
|
||||
|
||||
/* ================= 规则对象 ================= */
|
||||
|
||||
List<NotifyRuleTarget> selectTargetsByRuleId(@Param("ruleId") Long ruleId);
|
||||
|
||||
List<NotifyRuleTarget> selectTargetsByRuleIds(@Param("ruleIds") List<Long> ruleIds);
|
||||
|
||||
int insertTarget(NotifyRuleTarget target);
|
||||
|
||||
int deleteTargetsByRuleId(@Param("ruleId") Long ruleId);
|
||||
|
||||
/** 校验用户存在且为启用状态 */
|
||||
int countEnabledUser(@Param("userId") Long userId);
|
||||
|
||||
/** 校验角色存在且为启用状态 */
|
||||
int countEnabledRole(@Param("roleId") Long roleId);
|
||||
|
||||
/** 汇总所有启用规则的去重接收用户(直选用户 + 选中角色下用户) */
|
||||
List<Long> selectRecipientUserIds(@Param("eventType") String eventType);
|
||||
|
||||
/* ================= 消息与接收 ================= */
|
||||
|
||||
List<NotifyMessage> selectMessageList(NotifyMessage query);
|
||||
|
||||
NotifyMessage selectMessageById(@Param("messageId") Long messageId);
|
||||
|
||||
int insertMessage(NotifyMessage message);
|
||||
|
||||
int insertReceive(NotifyReceive receive);
|
||||
|
||||
List<NotifyReceive> selectReceivesByMessageId(@Param("messageId") Long messageId);
|
||||
|
||||
/* ================= 扫描日志 ================= */
|
||||
|
||||
String selectScanLogStatus(@Param("eventType") String eventType,
|
||||
@Param("customerId") Long customerId);
|
||||
|
||||
java.util.Date selectScanLogNotifyDate(@Param("eventType") String eventType,
|
||||
@Param("customerId") Long customerId);
|
||||
|
||||
int insertScanLog(@Param("eventType") String eventType,
|
||||
@Param("customerId") Long customerId,
|
||||
@Param("status") String status,
|
||||
@Param("notify") boolean notify);
|
||||
|
||||
int updateScanLog(@Param("eventType") String eventType,
|
||||
@Param("customerId") Long customerId,
|
||||
@Param("status") String status,
|
||||
@Param("notify") boolean notify);
|
||||
|
||||
/** 当前不超额但日志仍为OVERDUE的客户 -> 置NORMAL */
|
||||
int resetNormalForCustomers(@Param("eventType") String eventType,
|
||||
@Param("customerIds") List<Long> customerIds);
|
||||
|
||||
/** 查询所有当前超额客户(CNY账户,有效额度口径) */
|
||||
List<NotifyOverdueCustomer> selectOverdueCustomers();
|
||||
|
||||
/* ================= 我的消息 ================= */
|
||||
|
||||
/** 登录待弹屏消息(popup_flag=N) */
|
||||
List<NotifyReceive> selectPopupReceives(@Param("userId") Long userId);
|
||||
|
||||
/** 取弹屏列表后置 popup_flag=Y(服务器时间,一次登录只弹一次) */
|
||||
int markPopupShown(@Param("userId") Long userId);
|
||||
|
||||
/** 我的消息列表(readFlag 为空=全部) */
|
||||
List<NotifyReceive> selectMineList(@Param("userId") Long userId,
|
||||
@Param("readFlag") String readFlag);
|
||||
|
||||
int countUnread(@Param("userId") Long userId);
|
||||
|
||||
int markRead(@Param("receiveId") Long receiveId, @Param("userId") Long userId);
|
||||
|
||||
int markAllRead(@Param("userId") Long userId);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.bms.notify.service;
|
||||
|
||||
import java.util.List;
|
||||
import com.bms.notify.domain.NotifyMessage;
|
||||
import com.bms.notify.domain.NotifyReceive;
|
||||
import com.bms.notify.domain.NotifyRule;
|
||||
|
||||
/**
|
||||
* 消息通知 Service
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
public interface INotifyService
|
||||
{
|
||||
/** 规则分页列表 */
|
||||
List<NotifyRule> selectRuleList(NotifyRule query);
|
||||
|
||||
/** 规则详情(含通知对象) */
|
||||
NotifyRule selectRuleById(Long ruleId);
|
||||
|
||||
/** 新增规则(含通知对象) */
|
||||
int insertRule(NotifyRule rule);
|
||||
|
||||
/** 修改规则(全量重建通知对象) */
|
||||
int updateRule(NotifyRule rule);
|
||||
|
||||
/** 删除规则 */
|
||||
int deleteRuleByIds(Long[] ruleIds);
|
||||
|
||||
/** 通知消息分页列表 */
|
||||
List<NotifyMessage> selectMessageList(NotifyMessage query);
|
||||
|
||||
/** 消息详情(含接收人已读情况) */
|
||||
NotifyMessage selectMessageById(Long messageId);
|
||||
|
||||
/**
|
||||
* 扫描超额客户并按启用规则投递消息(定时任务/手工触发)。
|
||||
* 同一客户当天只通知一次;客户恢复正常后再次超额可立即再通知。
|
||||
* @return 本次新生成的消息条数
|
||||
*/
|
||||
int scanCustomerOverdue();
|
||||
|
||||
/** 登录后取待弹屏消息,并置 popup_flag=Y(一次登录只弹一次) */
|
||||
List<NotifyReceive> popupAndMarkShown(Long userId);
|
||||
|
||||
/** 我的消息列表(readFlag 为空=全部) */
|
||||
List<NotifyReceive> selectMineList(Long userId, String readFlag);
|
||||
|
||||
/** 我的未读数 */
|
||||
int countUnread(Long userId);
|
||||
|
||||
/** 标记单条已读 */
|
||||
int markRead(Long receiveId, Long userId);
|
||||
|
||||
/** 全部已读 */
|
||||
int markAllRead(Long userId);
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
package com.bms.notify.service.impl;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import com.bms.common.exception.ServiceException;
|
||||
import com.bms.common.utils.SecurityUtils;
|
||||
import com.bms.notify.domain.NotifyMessage;
|
||||
import com.bms.notify.domain.NotifyOverdueCustomer;
|
||||
import com.bms.notify.domain.NotifyReceive;
|
||||
import com.bms.notify.domain.NotifyRule;
|
||||
import com.bms.notify.domain.NotifyRuleTarget;
|
||||
import com.bms.notify.mapper.NotifyMapper;
|
||||
import com.bms.notify.service.INotifyService;
|
||||
|
||||
/**
|
||||
* 消息通知 Service实现
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
@Service
|
||||
public class NotifyServiceImpl implements INotifyService
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(NotifyServiceImpl.class);
|
||||
|
||||
@Autowired
|
||||
private NotifyMapper notifyMapper;
|
||||
|
||||
/* ================= 规则 ================= */
|
||||
|
||||
@Override
|
||||
public List<NotifyRule> selectRuleList(NotifyRule query)
|
||||
{
|
||||
List<NotifyRule> rules = notifyMapper.selectRuleList(query);
|
||||
if (rules.isEmpty())
|
||||
{
|
||||
return rules;
|
||||
}
|
||||
List<Long> ids = rules.stream().map(NotifyRule::getRuleId).toList();
|
||||
Map<Long, List<NotifyRuleTarget>> targetMap = loadTargets(ids);
|
||||
rules.forEach(r -> r.setTargets(targetMap.getOrDefault(r.getRuleId(), new ArrayList<>())));
|
||||
return rules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotifyRule selectRuleById(Long ruleId)
|
||||
{
|
||||
NotifyRule rule = notifyMapper.selectRuleById(ruleId);
|
||||
if (rule != null)
|
||||
{
|
||||
rule.setTargets(notifyMapper.selectTargetsByRuleId(ruleId));
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int insertRule(NotifyRule rule)
|
||||
{
|
||||
prepareRule(rule);
|
||||
String username = SecurityUtils.getUsername();
|
||||
rule.setCreateBy(username);
|
||||
notifyMapper.insertRule(rule);
|
||||
saveTargets(rule);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int updateRule(NotifyRule rule)
|
||||
{
|
||||
if (rule.getRuleId() == null || notifyMapper.selectRuleById(rule.getRuleId()) == null)
|
||||
{
|
||||
throw new ServiceException("通知规则不存在");
|
||||
}
|
||||
prepareRule(rule);
|
||||
rule.setUpdateBy(SecurityUtils.getUsername());
|
||||
int rows = notifyMapper.updateRule(rule);
|
||||
notifyMapper.deleteTargetsByRuleId(rule.getRuleId());
|
||||
saveTargets(rule);
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deleteRuleByIds(Long[] ruleIds)
|
||||
{
|
||||
if (ruleIds == null || ruleIds.length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return notifyMapper.deleteRuleByIds(ruleIds, SecurityUtils.getUsername());
|
||||
}
|
||||
|
||||
private void prepareRule(NotifyRule rule)
|
||||
{
|
||||
if (!StringUtils.hasText(rule.getRuleName()))
|
||||
{
|
||||
throw new ServiceException("规则名称不能为空");
|
||||
}
|
||||
if (!StringUtils.hasText(rule.getEventType()))
|
||||
{
|
||||
rule.setEventType(NotifyRule.EVENT_CUSTOMER_OVERDUE);
|
||||
}
|
||||
if (!NotifyRule.EVENT_CUSTOMER_OVERDUE.equals(rule.getEventType()))
|
||||
{
|
||||
throw new ServiceException("暂不支持的通知事件类型");
|
||||
}
|
||||
if (!StringUtils.hasText(rule.getStatus()))
|
||||
{
|
||||
rule.setStatus("0");
|
||||
}
|
||||
List<NotifyRuleTarget> raw = rule.getTargets();
|
||||
if (raw == null || raw.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请至少选择一个通知用户或角色");
|
||||
}
|
||||
// 去重 + 校验对象存在且启用
|
||||
Map<String, NotifyRuleTarget> uniq = new LinkedHashMap<>();
|
||||
for (NotifyRuleTarget t : raw)
|
||||
{
|
||||
if (t == null || t.getRefId() == null || !StringUtils.hasText(t.getTargetType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
String type = t.getTargetType().trim().toUpperCase();
|
||||
if (!NotifyRule.TARGET_USER.equals(type) && !NotifyRule.TARGET_ROLE.equals(type))
|
||||
{
|
||||
throw new ServiceException("通知对象类型只能是用户或角色");
|
||||
}
|
||||
String key = type + "#" + t.getRefId();
|
||||
if (uniq.containsKey(key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
int exists = NotifyRule.TARGET_USER.equals(type)
|
||||
? notifyMapper.countEnabledUser(t.getRefId())
|
||||
: notifyMapper.countEnabledRole(t.getRefId());
|
||||
if (exists == 0)
|
||||
{
|
||||
throw new ServiceException((NotifyRule.TARGET_USER.equals(type) ? "用户" : "角色")
|
||||
+ "不存在或已停用:" + (StringUtils.hasText(t.getRefName()) ? t.getRefName() : t.getRefId()));
|
||||
}
|
||||
t.setTargetType(type);
|
||||
uniq.put(key, t);
|
||||
}
|
||||
if (uniq.isEmpty())
|
||||
{
|
||||
throw new ServiceException("请至少选择一个有效的通知用户或角色");
|
||||
}
|
||||
rule.setTargets(new ArrayList<>(uniq.values()));
|
||||
}
|
||||
|
||||
private void saveTargets(NotifyRule rule)
|
||||
{
|
||||
for (NotifyRuleTarget t : rule.getTargets())
|
||||
{
|
||||
t.setTargetId(null);
|
||||
t.setRuleId(rule.getRuleId());
|
||||
notifyMapper.insertTarget(t);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, List<NotifyRuleTarget>> loadTargets(List<Long> ruleIds)
|
||||
{
|
||||
Map<Long, List<NotifyRuleTarget>> map = new LinkedHashMap<>();
|
||||
for (NotifyRuleTarget t : notifyMapper.selectTargetsByRuleIds(ruleIds))
|
||||
{
|
||||
map.computeIfAbsent(t.getRuleId(), k -> new ArrayList<>()).add(t);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/* ================= 消息 ================= */
|
||||
|
||||
@Override
|
||||
public List<NotifyMessage> selectMessageList(NotifyMessage query)
|
||||
{
|
||||
return notifyMapper.selectMessageList(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotifyMessage selectMessageById(Long messageId)
|
||||
{
|
||||
NotifyMessage message = notifyMapper.selectMessageById(messageId);
|
||||
if (message != null)
|
||||
{
|
||||
message.setReceives(notifyMapper.selectReceivesByMessageId(messageId));
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
/* ================= 超额扫描 ================= */
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int scanCustomerOverdue()
|
||||
{
|
||||
String eventType = NotifyRule.EVENT_CUSTOMER_OVERDUE;
|
||||
if (notifyMapper.countEnabledRules(eventType) == 0)
|
||||
{
|
||||
log.info("客户超额通知扫描:无启用规则,跳过");
|
||||
return 0;
|
||||
}
|
||||
List<Long> recipients = notifyMapper.selectRecipientUserIds(eventType);
|
||||
if (recipients.isEmpty())
|
||||
{
|
||||
log.info("客户超额通知扫描:启用规则未匹配到有效接收人,跳过");
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<NotifyOverdueCustomer> overdueList = notifyMapper.selectOverdueCustomers();
|
||||
List<Long> overdueIds = overdueList.stream().map(NotifyOverdueCustomer::getCustomerId).toList();
|
||||
// 曾超额但当前已恢复的客户:状态置 NORMAL,下次再超额可立即通知
|
||||
notifyMapper.resetNormalForCustomers(eventType, overdueIds);
|
||||
|
||||
LocalDate today = LocalDate.now();
|
||||
int generated = 0;
|
||||
for (NotifyOverdueCustomer c : overdueList)
|
||||
{
|
||||
String lastStatus = notifyMapper.selectScanLogStatus(eventType, c.getCustomerId());
|
||||
// 当日去重:今天已通知过且仍超额,跳过
|
||||
if ("OVERDUE".equals(lastStatus) && today.equals(lastNotifyDate(eventType, c.getCustomerId())))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NotifyMessage message = new NotifyMessage();
|
||||
message.setEventType(eventType);
|
||||
boolean disabled = "1".equals(c.getIsEnable());
|
||||
message.setTitle((disabled ? "【停用客户已超额】" : "【客户已超额】") + c.getCustomerName());
|
||||
message.setContent(buildContent(c, disabled));
|
||||
message.setCustomerId(c.getCustomerId());
|
||||
message.setCustomerCode(c.getCustomerCode());
|
||||
message.setCustomerName(c.getCustomerName());
|
||||
message.setOverdueAmount(c.getOverdueAmount());
|
||||
message.setCreateBy("system");
|
||||
notifyMapper.insertMessage(message);
|
||||
|
||||
for (Long userId : recipients)
|
||||
{
|
||||
NotifyReceive rv = new NotifyReceive();
|
||||
rv.setMessageId(message.getMessageId());
|
||||
rv.setUserId(userId);
|
||||
notifyMapper.insertReceive(rv);
|
||||
}
|
||||
|
||||
if (lastStatus == null)
|
||||
{
|
||||
notifyMapper.insertScanLog(eventType, c.getCustomerId(), "OVERDUE", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
notifyMapper.updateScanLog(eventType, c.getCustomerId(), "OVERDUE", true);
|
||||
}
|
||||
generated++;
|
||||
}
|
||||
log.info("客户超额通知扫描完成:超额客户 {} 户,新生成通知 {} 条,接收 {} 人",
|
||||
overdueList.size(), generated, recipients.size());
|
||||
return generated;
|
||||
}
|
||||
|
||||
private LocalDate lastNotifyDate(String eventType, Long customerId)
|
||||
{
|
||||
Date d = notifyMapper.selectScanLogNotifyDate(eventType, customerId);
|
||||
if (d == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return d.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
}
|
||||
|
||||
private String buildContent(NotifyOverdueCustomer c, boolean disabled)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("客户「").append(c.getCustomerName()).append("」(").append(c.getCustomerCode()).append(")");
|
||||
if (disabled)
|
||||
{
|
||||
sb.append("已停用且");
|
||||
}
|
||||
sb.append("已超额(欠费)。");
|
||||
sb.append("当前可用额度 ").append(format(c.getAvailable())).append(" CNY,");
|
||||
sb.append("超额金额 ").append(format(c.getOverdueAmount())).append(" CNY。");
|
||||
sb.append("请及时催收回款,可在【客户财务管理-收款管理】登记收款并审核。");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private String format(java.math.BigDecimal v)
|
||||
{
|
||||
return v == null ? "0.00" : v.stripTrailingZeros().toPlainString();
|
||||
}
|
||||
|
||||
/* ================= 我的消息 ================= */
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<NotifyReceive> popupAndMarkShown(Long userId)
|
||||
{
|
||||
List<NotifyReceive> list = notifyMapper.selectPopupReceives(userId);
|
||||
if (!list.isEmpty())
|
||||
{
|
||||
// 服务器端置位:一次登录只弹一次,刷新页面不再弹,未读消息可从“我的通知”再次查看
|
||||
notifyMapper.markPopupShown(userId);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NotifyReceive> selectMineList(Long userId, String readFlag)
|
||||
{
|
||||
return notifyMapper.selectMineList(userId, readFlag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countUnread(Long userId)
|
||||
{
|
||||
return notifyMapper.countUnread(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int markRead(Long receiveId, Long userId)
|
||||
{
|
||||
return notifyMapper.markRead(receiveId, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int markAllRead(Long userId)
|
||||
{
|
||||
return notifyMapper.markAllRead(userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.bms.notify.task;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.bms.notify.service.INotifyService;
|
||||
|
||||
/**
|
||||
* 客户超额通知定时任务。
|
||||
*
|
||||
* <p>纳入 Quartz(sys_job)统一管理,调用目标:
|
||||
* {@code notifyCustomerTask.scanOverdue()},cron/启停在「系统监控-定时任务」配置。</p>
|
||||
*
|
||||
* <p>本任务只做消息投递,不改动任何客户余额/额度数据。</p>
|
||||
*
|
||||
* @author bms
|
||||
* @date 2026-10-09
|
||||
*/
|
||||
@Component("notifyCustomerTask")
|
||||
public class NotifyCustomerTask
|
||||
{
|
||||
private static final Logger log = LoggerFactory.getLogger(NotifyCustomerTask.class);
|
||||
|
||||
@Autowired
|
||||
private INotifyService notifyService;
|
||||
|
||||
/**
|
||||
* 扫描已超额(欠费)客户并按通知规则投递消息(同一客户当天最多一条)。
|
||||
*/
|
||||
public void scanOverdue()
|
||||
{
|
||||
int count = notifyService.scanCustomerOverdue();
|
||||
log.info("客户超额通知任务执行完成,新生成通知 {} 条", count);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user