diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/StorageCabinetExpiryJob.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/StorageCabinetExpiryJob.java index 1274211..620e459 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/StorageCabinetExpiryJob.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/StorageCabinetExpiryJob.java @@ -15,6 +15,15 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import com.agileboot.domain.qywx.accessToken.AccessTokenApplicationService; +import com.agileboot.domain.qywx.accessToken.db.QyAccessTokenEntity; +import com.agileboot.domain.qywx.api.QywxApiUtil; +import com.agileboot.domain.qywx.api.response.NewsArticle; +import com.agileboot.domain.qywx.authCorpInfo.AuthCorpInfoApplicationService; +import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity; +import com.agileboot.domain.qywx.user.db.QyUserService; + +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -26,6 +35,9 @@ public class StorageCabinetExpiryJob { private final ShopService shopService; private final SmartCabinetService smartCabinetService; private final CabinetCellService cabinetCellService; + private final AuthCorpInfoApplicationService authCorpInfoApplicationService; + private final AccessTokenApplicationService accessTokenApplicationService; + private final QyUserService qyUserService; /** * 定时任务:处理暂存柜到期格口 @@ -86,7 +98,7 @@ public class StorageCabinetExpiryJob { log.info("商店[{}]下查找到 {} 个智能柜.", shop.getShopId(), cabinets.size()); for (SmartCabinetEntity cabinet : cabinets) { - int cabinetResetCount = processStorageCabinet(cabinet); + int cabinetResetCount = processStorageCabinet(shop, cabinet); resetCount += cabinetResetCount; } @@ -106,7 +118,7 @@ public class StorageCabinetExpiryJob { /** * 处理单个智能柜的到期格口 */ - private int processStorageCabinet(SmartCabinetEntity cabinet) { + private int processStorageCabinet(ShopEntity shop, SmartCabinetEntity cabinet) { Integer returnDeadlineDays = cabinet.getReturnDeadline(); if (returnDeadlineDays == null || returnDeadlineDays <= 0) { @@ -124,12 +136,14 @@ public class StorageCabinetExpiryJob { log.info("智能柜[{}]查找到 {} 个需重置的到期格口.", cabinet.getCabinetId(), expiredCells.size()); int resetCount = 0; + Date deadlineDate = DateUtil.offsetDay(new Date(), -returnDeadlineDays); for (CabinetCellEntity cell : expiredCells) { try { cabinetCellService.resetCellById(cell.getCellId()); log.info("成功重置到期格口:格口ID[{}], 格口号[{}], 柜机ID[{}].", cell.getCellId(), cell.getCellNo(), cabinet.getCabinetId()); resetCount++; + sendStorageExpiryNotification(shop, cabinet, cell, deadlineDate); } catch (Exception e) { log.error("重置格口[{}]失败", cell.getCellId(), e); } @@ -156,4 +170,42 @@ public class StorageCabinetExpiryJob { return cabinetCellService.list(queryWrapper); } + + private void sendStorageExpiryNotification(ShopEntity shop, SmartCabinetEntity cabinet, CabinetCellEntity cell, Date expiredDate) { + try { + List adminUserIds = qyUserService.selectAdminUserIds(shop.getCorpid()); + if (CollectionUtils.isEmpty(adminUserIds)) { + log.warn("商店[{}]未找到管理员用户,无法发送通知.", shop.getShopId()); + return; + } + + String toUser = String.join("|", adminUserIds); + String toparty = ""; + String totag = ""; + List articles = new ArrayList<>(); + NewsArticle article = new NewsArticle(); + article.setTitle("暂存柜格口逾期自动重置通知"); + article.setDescription(String.format("商店名称: %s, 柜机名称: %s, 格口号: %s, 逾期时间: %s", + shop.getShopName(), cabinet.getCabinetName(), cell.getCellNo(), DateUtil.formatDateTime(expiredDate))); + + QyAuthCorpInfoEntity authCorpInfo = authCorpInfoApplicationService.selectByCorpid(shop.getCorpid()); + if (authCorpInfo == null) { + log.warn("商店[{}]未找到企业微信配置信息.", shop.getShopId()); + return; + } + + QyAccessTokenEntity accessToken = accessTokenApplicationService.getByAppid(authCorpInfo.getAppid(), authCorpInfo.getCorpid()); + if (accessToken == null) { + log.warn("商店[{}]未获取到企业微信accessToken.", shop.getShopId()); + return; + } + + articles.add(article); + QywxApiUtil.sendNewsMessage(accessToken.getAccessToken(), Integer.valueOf(authCorpInfo.getAgentid()), + toUser, toparty, totag, articles); + log.info("成功发送暂存柜格口逾期通知给商店[{}]的管理员.", shop.getShopId()); + } catch (Exception e) { + log.error("发送暂存柜格口逾期通知失败", e); + } + } } diff --git a/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java b/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java index d45a5ef..3ca624a 100644 --- a/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java +++ b/agileboot-api/src/main/java/com/agileboot/api/controller/CabinetCellController.java @@ -240,12 +240,12 @@ public class CabinetCellController { String cacheKey = "storeItem:" + command.getShopId() + ":" + command.getCellType(); // 重复请求过滤 - Boolean cached = caffeineCacheService.apiCache.get(cacheKey); + Boolean cached = caffeineCacheService.fastApiCache.get(cacheKey); if (cached != null && cached) { - return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "重复请求,请稍后重试")); + return ResponseDTO.fail(new ApiException(ErrorCode.Internal.INTERNAL_ERROR, "请求过于频繁,请稍后重试")); } // 放入缓存,标记请求已处理 - caffeineCacheService.apiCache.put(cacheKey, Boolean.TRUE); + caffeineCacheService.fastApiCache.put(cacheKey, Boolean.TRUE); try { CabinetCellDTO result = cabinetCellApplicationService.storeItemToCell(command); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java b/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java index 50d7ee8..4f3fc7d 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CacheCenter.java @@ -44,6 +44,8 @@ public class CacheCenter { public static AbstractCaffeineCacheTemplate apiCache; + public static AbstractCaffeineCacheTemplate fastApiCache; + @PostConstruct public void init() { GuavaCacheService guavaCache = SpringUtil.getBean(GuavaCacheService.class); @@ -62,6 +64,7 @@ public class CacheCenter { accessTokenCache = caffeineCache.accessTokenCache; corpidCache = caffeineCache.corpidCache; apiCache = caffeineCache.apiCache; + fastApiCache = caffeineCache.fastApiCache; } } diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java b/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java index f990352..b68a875 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/common/cache/CaffeineCacheService.java @@ -139,6 +139,17 @@ public class CaffeineCacheService { } }; + // 快速API缓存:3秒过期和刷新(API调用参数) + public AbstractCaffeineCacheTemplate fastApiCache = new AbstractCaffeineCacheTemplate( + 3, TimeUnit.SECONDS, + 3, TimeUnit.SECONDS) { + @Override + public Boolean getObjectFromDb(Object id) { + // API缓存通常不需要从数据库获取,这里返回null + return null; + } + }; + /** * 获取缓存统计信息 * @return 统计信息字符串 @@ -156,6 +167,7 @@ public class CaffeineCacheService { stats.append("Access Token Cache: ").append(accessTokenCache.getStats()).append("\n"); stats.append("Corpid Cache: ").append(corpidCache.getStats()).append("\n"); stats.append("API Cache: ").append(apiCache.getStats()).append("\n"); + stats.append("Fast API Cache: ").append(fastApiCache.getStats()).append("\n"); return stats.toString(); } @@ -186,6 +198,8 @@ public class CaffeineCacheService { return corpidCache; case "apiCache": return apiCache; + case "fastApiCache": + return fastApiCache; default: return null; } @@ -198,7 +212,7 @@ public class CaffeineCacheService { public String[] getAllCacheNames() { return new String[]{ "captchaCache", "loginUserCache", "userCache", - "roleCache", "postCache", "qyUseridCache", "dynamicCodeCache", "accessTokenCache", "corpidCache", "apiCache" + "roleCache", "postCache", "qyUseridCache", "dynamicCodeCache", "accessTokenCache", "corpidCache", "apiCache", "fastApiCache" }; } } \ No newline at end of file