add gao user remind

This commit is contained in:
Timi
2026-09-07 12:06:38 +08:00
parent 01d33a1122
commit 3a4386261c
11 changed files with 356 additions and 0 deletions
@@ -3,6 +3,7 @@ package com.imyeyu.api.modules.common.mapper;
import com.imyeyu.api.modules.common.entity.Notify;
import com.imyeyu.api.modules.common.vo.NotifyManagementItem;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.imyeyu.spring.mapper.BaseMapper;
import java.util.List;
@@ -15,6 +16,10 @@ import java.util.List;
*/
public interface NotifyMapper extends BaseMapper<Notify, String> {
/// 查询业务提醒是否已创建
@Select("SELECT EXISTS (SELECT 1 FROM `notify` WHERE `biz_type` = #{bizType} AND `biz_reminded_type` = #{bizRemindedType} AND `biz_reminded_id` = #{bizRemindedId} AND `deleted_at` IS NULL)")
boolean existsByBizReminded(@Param("bizType") String bizType, @Param("bizRemindedType") String bizRemindedType, @Param("bizRemindedId") String bizRemindedId);
/// 分页查询管理端通知
///
/// @param userId 管理员用户 ID
@@ -14,6 +14,14 @@ import com.imyeyu.spring.service.BaseService;
*/
public interface NotifyService extends BaseService<Notify, String> {
/// 查询业务提醒是否已创建
///
/// @param bizType 业务类型
/// @param bizRemindedType 被提醒业务类型
/// @param bizRemindedId 被提醒业务 ID
/// @return true 为已创建
boolean existsByBizReminded(String bizType, String bizRemindedType, String bizRemindedId);
/// 分页查询当前用户创建的通知
///
/// @param page 分页参数
@@ -46,6 +46,11 @@ public class NotifyServiceImplement extends AbstractEntityService<Notify, String
return mapper;
}
@Override
public boolean existsByBizReminded(String bizType, String bizRemindedType, String bizRemindedId) {
return mapper.existsByBizReminded(bizType, bizRemindedType, bizRemindedId);
}
/// 创建通知并级联创建通知详情
///
/// @param notify 通知
@@ -0,0 +1,27 @@
package com.imyeyu.api.modules.gao.mapper;
import com.imyeyu.api.modules.gao.vo.GaoOrderReminderCustomer;
import java.util.List;
/// GAO 订单提醒 Mapper
///
/// @author Codex
/// @since 2026-09-06
public interface GaoOrderReminderMapper {
/// 查询需要收款的订单客户
///
/// @return 订单客户列表
List<GaoOrderReminderCustomer> selectOrderReceiveCustomers();
/// 查询待核销收款客户
///
/// @return 收款客户列表
List<GaoOrderReminderCustomer> selectReceiveVerifyCustomers();
/// 查询待核销履约客户
///
/// @return 履约客户列表
List<GaoOrderReminderCustomer> selectFulfillmentVerifyCustomers();
}
@@ -15,6 +15,12 @@ import java.util.List;
/// @since 2026-07-30
public interface GaoUserMapper extends BaseMapper<GaoUser, String> {
/// 查询门店启用中的店长
///
/// @param storeId 门店 ID
/// @return 店长列表
List<GaoUser> selectEnabledStoreManagers(@Param("storeId") String storeId);
List<GaoUser> selectByUserIdList(Collection<String> userIdList);
List<GaoUser> selectRecipientPage(@Param("page") Page<GaoUser> page);
@@ -0,0 +1,14 @@
package com.imyeyu.api.modules.gao.service;
/// GAO 订单提醒服务
///
/// @author Codex
/// @since 2026-09-06
public interface GaoOrderReminderService {
/// 发送订单收款提醒
void sendOrderReceiveReminder();
/// 发送核销提醒
void sendVerifyReminder();
}
@@ -0,0 +1,174 @@
package com.imyeyu.api.modules.gao.service.implement;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imyeyu.api.modules.common.entity.Notify;
import com.imyeyu.api.modules.common.entity.NotifyDetail;
import com.imyeyu.api.modules.common.service.NotifyService;
import com.imyeyu.api.modules.gao.entity.GaoUser;
import com.imyeyu.api.modules.gao.mapper.GaoOrderReminderMapper;
import com.imyeyu.api.modules.gao.mapper.GaoUserMapper;
import com.imyeyu.api.modules.gao.service.GaoOrderReminderService;
import com.imyeyu.api.modules.gao.vo.GaoOrderReminderCustomer;
import com.imyeyu.api.modules.user.entity.User;
import com.imyeyu.api.modules.user.service.UserService;
import com.imyeyu.utils.Time;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/// GAO 订单提醒服务实现
///
/// @author Codex
/// @since 2026-09-06
@Slf4j
@Service
@RequiredArgsConstructor
public class GaoOrderReminderServiceImplement implements GaoOrderReminderService {
private static final String NOTIFY_BIZ_TYPE = "GAO_ORDER_REMINDER";
private static final String SMS_TEMPLATE_ID = "2725097";
private static final String ZONE_ID = "Asia/Shanghai";
private final GaoOrderReminderMapper mapper;
private final GaoUserMapper gaoUserMapper;
private final UserService userService;
private final NotifyService notifyService;
private final ObjectMapper jackson;
@Override
public void sendOrderReceiveReminder() {
send(mapper.selectOrderReceiveCustomers(), ReminderType.ORDER_RECEIVE);
}
@Override
public void sendVerifyReminder() {
Map<String, Map<String, GaoOrderReminderCustomer>> storeCustomerMap = new LinkedHashMap<>();
Map<String, Set<String>> storeActionMap = new LinkedHashMap<>();
mergeCustomers(storeCustomerMap, storeActionMap, mapper.selectReceiveVerifyCustomers(), "核销收款");
mergeCustomers(storeCustomerMap, storeActionMap, mapper.selectFulfillmentVerifyCustomers(), "核销履约");
for (Map.Entry<String, Map<String, GaoOrderReminderCustomer>> entry : storeCustomerMap.entrySet()) {
String action = String.join("、", storeActionMap.get(entry.getKey()));
sendStoreReminder(entry.getKey(), new ArrayList<>(entry.getValue().values()), ReminderType.VERIFY.name(), action);
}
}
private void mergeCustomers(Map<String, Map<String, GaoOrderReminderCustomer>> storeCustomerMap,
Map<String, Set<String>> storeActionMap, List<GaoOrderReminderCustomer> customerList, String action) {
if (customerList == null || customerList.isEmpty()) {
return;
}
for (GaoOrderReminderCustomer customer : customerList) {
storeCustomerMap.computeIfAbsent(customer.getStoreId(), key -> new LinkedHashMap<>())
.putIfAbsent(customer.getCustomerId(), customer);
storeActionMap.computeIfAbsent(customer.getStoreId(), key -> new LinkedHashSet<>()).add(action);
}
}
private void send(List<GaoOrderReminderCustomer> customerList, ReminderType reminderType) {
if (customerList == null || customerList.isEmpty()) {
return;
}
Map<String, List<GaoOrderReminderCustomer>> storeCustomerMap = customerList.stream()
.collect(Collectors.groupingBy(GaoOrderReminderCustomer::getStoreId, LinkedHashMap::new, Collectors.toList()));
for (Map.Entry<String, List<GaoOrderReminderCustomer>> entry : storeCustomerMap.entrySet()) {
sendStoreReminder(entry.getKey(), entry.getValue(), reminderType.name(), reminderType.label);
}
}
private void sendStoreReminder(String storeId, List<GaoOrderReminderCustomer> customerList, String reminderType,
String action) {
String customerNames = formatCustomerNames(customerList);
String content = "您的客户 %s 的订单需要 %s,请留意订单状态。".formatted(customerNames, action);
List<GaoUser> managerList = gaoUserMapper.selectEnabledStoreManagers(storeId);
if (managerList == null || managerList.isEmpty()) {
log.warn("GAO 订单提醒未找到启用中的店长: storeId[{}], reminderType[{}]", storeId, reminderType);
return;
}
List<String> userIdList = managerList.stream().map(GaoUser::getUserId).distinct().toList();
Map<String, User> userMap = userService.mapByIdList(userIdList);
for (GaoUser manager : managerList) {
try {
User user = userMap.get(manager.getUserId());
if (user == null) {
log.warn("GAO 订单提醒跳过不存在的店长用户: storeId[{}], userId[{}]", storeId, manager.getUserId());
continue;
}
String remindedId = "%s:%s:%s:%s".formatted(LocalDate.now(ZoneId.of(ZONE_ID)), storeId, manager.getUserId(), reminderType);
if (notifyService.existsByBizReminded(NOTIFY_BIZ_TYPE, reminderType, remindedId)) {
continue;
}
long sendAt = Time.now();
Notify notify = new Notify();
notify.setBizType(NOTIFY_BIZ_TYPE);
notify.setBizRemindedType(reminderType);
notify.setBizRemindedId(remindedId);
notify.setDetailList(List.of(
createInternalDetail(manager.getUserId(), content, sendAt),
createSmsDetail(user.getPhoneNo(), customerNames, action, content, sendAt)
));
notifyService.create(notify);
} catch (Exception e) {
log.error("GAO 订单提醒发送失败: storeId[{}], userId[{}], reminderType[{}]", storeId, manager.getUserId(), reminderType, e);
}
}
}
private NotifyDetail createInternalDetail(String userId, String content, long sendAt) {
NotifyDetail detail = new NotifyDetail();
detail.setMsgType(NotifyDetail.MsgType.INTERNAL);
detail.setTemplateId(SMS_TEMPLATE_ID);
detail.setSubject("订单状态提醒");
detail.setContent(content);
detail.setContentType(NotifyDetail.ContentType.TEXT);
detail.setSendTo(userId);
detail.setSendAt(sendAt);
return detail;
}
private NotifyDetail createSmsDetail(String phoneNo, String customerNames, String action, String content, long sendAt) {
NotifyDetail detail = new NotifyDetail();
detail.setMsgType(NotifyDetail.MsgType.SMS);
detail.setTemplateId(SMS_TEMPLATE_ID);
detail.setContent(content);
detail.setArgs(jackson.valueToTree(List.of(customerNames, action)));
detail.setSendTo(phoneNo);
detail.setSendAt(sendAt);
return detail;
}
private String formatCustomerNames(List<GaoOrderReminderCustomer> customerList) {
List<String> nameList = customerList.stream()
.map(GaoOrderReminderCustomer::getCustomerName)
.map(name -> name == null || name.isBlank() ? "未知客户" : name.trim())
.toList();
if (customerList.size() <= 2) {
return String.join("、", nameList);
}
return "%s、%s等 %d 位".formatted(nameList.getFirst(), nameList.get(1), customerList.size());
}
private enum ReminderType {
/// 订单收款
ORDER_RECEIVE("收款"),
/// 统一核销
VERIFY("核销收款、核销履约");
private final String label;
ReminderType(String label) {
this.label = label;
}
}
}
@@ -0,0 +1,41 @@
package com.imyeyu.api.modules.gao.task;
import com.imyeyu.api.modules.gao.service.GaoOrderReminderService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
/// GAO 订单提醒定时任务
///
/// @author Codex
/// @since 2026-09-06
@Slf4j
@Service
@RequiredArgsConstructor
public class GaoOrderReminderTask {
private static final String ZONE_ID = "Asia/Shanghai";
private final GaoOrderReminderService service;
/// 每天早上八点发送订单收款提醒
@Scheduled(cron = "0 0 8 * * ?", zone = ZONE_ID)
public void sendOrderReceiveReminder() {
execute("订单收款", service::sendOrderReceiveReminder);
}
/// 每天晚上九点发送核销提醒
@Scheduled(cron = "0 0 21 * * ?", zone = ZONE_ID)
public void sendVerifyReminder() {
execute("核销", service::sendVerifyReminder);
}
private void execute(String reminderName, Runnable action) {
try {
action.run();
} catch (Exception e) {
log.error("GAO {}提醒任务执行失败", reminderName, e);
}
}
}
@@ -0,0 +1,20 @@
package com.imyeyu.api.modules.gao.vo;
import lombok.Data;
/// GAO 订单提醒客户
///
/// @author Codex
/// @since 2026-09-06
@Data
public class GaoOrderReminderCustomer {
/// 门店 ID
private String storeId;
/// 客户 ID
private String customerId;
/// 客户名称
private String customerName;
}