diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/DeadlineOrderJob.java b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/DeadlineOrderJob.java index 4c83b97..27e6fb1 100644 --- a/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/DeadlineOrderJob.java +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/DeadlineOrderJob.java @@ -16,6 +16,8 @@ import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity; import com.agileboot.domain.qywx.user.db.QyUserService; import com.agileboot.domain.shop.order.db.ShopOrderEntity; import com.agileboot.domain.shop.order.db.ShopOrderGoodsEntity; +import com.agileboot.domain.shop.shop.db.ShopEntity; +import com.agileboot.domain.shop.shop.db.ShopService; import com.agileboot.domain.shop.order.db.ShopOrderGoodsService; import com.agileboot.domain.shop.order.db.ShopOrderService; import com.agileboot.domain.shop.paymentOperationLog.PaymentOperationLogApplicationService; @@ -41,6 +43,7 @@ public class DeadlineOrderJob { private final ShopOrderGoodsService shopOrderGoodsService; private final CabinetCellService cabinetCellService; private final SmartCabinetService smartCabinetService; + private final ShopService shopService; private final PaymentOperationLogApplicationService paymentOperationLogApplicationService; private final AuthCorpInfoApplicationService authCorpInfoApplicationService; private final AccessTokenApplicationService accessTokenApplicationService; @@ -112,12 +115,20 @@ public class DeadlineOrderJob { } Integer returnDeadlineDays = smartCabinet.getReturnDeadline(); - - // 检查归还期限是否有效(大于0天) if (returnDeadlineDays == null || returnDeadlineDays <= 0) { continue; } + ShopEntity shop = shopService.getById(smartCabinet.getShopId()); + if (shop == null) { + log.warn("智能柜[{}]关联的商店[{}]不存在,跳过处理.", smartCabinet.getCabinetId(), smartCabinet.getShopId()); + continue; + } + + if (shop.getMode() != null && shop.getMode().equals(5)) { + continue; + } + Date orderCreateTime = order.getCreateTime(); if (orderCreateTime == null) { log.warn("订单[{}]的创建时间为空,无法计算归还期限,订单商品[{}]跳过处理.", order.getOrderId(), orderGoods.getOrderGoodsId()); 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 new file mode 100644 index 0000000..1274211 --- /dev/null +++ b/agileboot-admin/src/main/java/com/agileboot/admin/customize/service/job/StorageCabinetExpiryJob.java @@ -0,0 +1,159 @@ +package com.agileboot.admin.customize.service.job; + +import cn.hutool.core.date.DateUtil; +import com.agileboot.domain.cabinet.cell.db.CabinetCellEntity; +import com.agileboot.domain.cabinet.cell.db.CabinetCellService; +import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetEntity; +import com.agileboot.domain.cabinet.smartCabinet.db.SmartCabinetService; +import com.agileboot.domain.shop.shop.db.ShopEntity; +import com.agileboot.domain.shop.shop.db.ShopService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Date; +import java.util.List; + +@RequiredArgsConstructor +@Component +@Slf4j +public class StorageCabinetExpiryJob { + + private final ShopService shopService; + private final SmartCabinetService smartCabinetService; + private final CabinetCellService cabinetCellService; + + /** + * 定时任务:处理暂存柜到期格口 + * 每天凌晨4点执行,检查暂存柜模式(shop.mode=5)下密码创建时间过期的格口并重置。 + */ + @Scheduled(cron = "0 0 4 * * *") + @Transactional + public void resetExpiredStorageCells() { + log.info("开始执行暂存柜到期格口重置任务..."); + + try { + List storageShops = getStorageShops(); + + if (CollectionUtils.isEmpty(storageShops)) { + log.info("未找到暂存柜模式商店."); + return; + } + + log.info("查找到 {} 个暂存柜模式商店.", storageShops.size()); + + int totalResetCount = 0; + + for (ShopEntity shop : storageShops) { + int resetCount = processShopStorageCabinets(shop); + totalResetCount += resetCount; + } + + log.info("暂存柜到期格口重置任务执行完毕,共重置 {} 个格口.", totalResetCount); + } catch (Exception globalException) { + log.error("执行暂存柜到期格口重置任务时发生全局错误:", globalException); + } + log.info("暂存柜到期格口重置任务执行完毕."); + } + + /** + * 获取所有暂存柜模式(mode=5)的商店 + */ + private List getStorageShops() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(ShopEntity::getMode, 5); + queryWrapper.eq(ShopEntity::getDeleted, false); + return shopService.list(queryWrapper); + } + + /** + * 处理单个商店的暂存柜格口 + */ + private int processShopStorageCabinets(ShopEntity shop) { + int resetCount = 0; + + List cabinets = getCabinetsByShopId(shop.getShopId()); + + if (CollectionUtils.isEmpty(cabinets)) { + log.debug("商店[{}]下未找到智能柜.", shop.getShopId()); + return 0; + } + + log.info("商店[{}]下查找到 {} 个智能柜.", shop.getShopId(), cabinets.size()); + + for (SmartCabinetEntity cabinet : cabinets) { + int cabinetResetCount = processStorageCabinet(cabinet); + resetCount += cabinetResetCount; + } + + return resetCount; + } + + /** + * 根据商店ID获取智能柜列表 + */ + private List getCabinetsByShopId(Long shopId) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(SmartCabinetEntity::getShopId, shopId); + queryWrapper.eq(SmartCabinetEntity::getDeleted, false); + return smartCabinetService.list(queryWrapper); + } + + /** + * 处理单个智能柜的到期格口 + */ + private int processStorageCabinet(SmartCabinetEntity cabinet) { + Integer returnDeadlineDays = cabinet.getReturnDeadline(); + + if (returnDeadlineDays == null || returnDeadlineDays <= 0) { + log.debug("智能柜[{}]归还期限无效,跳过处理.", cabinet.getCabinetId()); + return 0; + } + + List expiredCells = getExpiredCells(cabinet.getCabinetId(), returnDeadlineDays); + + if (CollectionUtils.isEmpty(expiredCells)) { + log.debug("智能柜[{}]未找到需重置的到期格口.", cabinet.getCabinetId()); + return 0; + } + + log.info("智能柜[{}]查找到 {} 个需重置的到期格口.", cabinet.getCabinetId(), expiredCells.size()); + + int resetCount = 0; + for (CabinetCellEntity cell : expiredCells) { + try { + cabinetCellService.resetCellById(cell.getCellId()); + log.info("成功重置到期格口:格口ID[{}], 格口号[{}], 柜机ID[{}].", + cell.getCellId(), cell.getCellNo(), cabinet.getCabinetId()); + resetCount++; + } catch (Exception e) { + log.error("重置格口[{}]失败", cell.getCellId(), e); + } + } + + return resetCount; + } + + /** + * 获取指定智能柜下密码创建时间过期的格口 + * 条件:password 不为空,usage_status 为 2,password_create_time 早于计算出的过期时间 + */ + private List getExpiredCells(Long cabinetId, int returnDeadlineDays) { + Date deadlineDate = DateUtil.offsetDay(new Date(), -returnDeadlineDays); + + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(CabinetCellEntity::getCabinetId, cabinetId); + queryWrapper.isNotNull(CabinetCellEntity::getPassword); + queryWrapper.ne(CabinetCellEntity::getPassword, ""); + queryWrapper.eq(CabinetCellEntity::getUsageStatus, 2); + queryWrapper.isNotNull(CabinetCellEntity::getPasswordCreateTime); + queryWrapper.le(CabinetCellEntity::getPasswordCreateTime, deadlineDate); + queryWrapper.eq(CabinetCellEntity::getDeleted, false); + + return cabinetCellService.list(queryWrapper); + } +} diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/CabinetCellApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/CabinetCellApplicationService.java index 5591301..11784c2 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/CabinetCellApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/CabinetCellApplicationService.java @@ -29,6 +29,7 @@ import com.agileboot.domain.cabinet.mainboard.model.CabinetMainboardModelFactory import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import java.util.List; +import java.util.Date; import java.util.Random; import java.util.stream.Collectors; import org.springframework.transaction.annotation.Transactional; @@ -293,10 +294,17 @@ public class CabinetCellApplicationService { // 更新密码和状态 cellModel.setPassword(password); cellModel.setUsageStatus(2); // 设置为已占用 + cellModel.setPasswordCreateTime(new Date()); cellModel.updateById(); + // 获取柜子的归还期限 + SmartCabinetModel cabinetModel = smartCabinetModelFactory.loadById(cellModel.getCabinetId()); + Integer returnDeadline = cabinetModel.getReturnDeadline(); + // 返回格口信息(包含密码) - return new CabinetCellDTO(cellModel); + CabinetCellDTO result = new CabinetCellDTO(cellModel); + result.setReturnDeadline(returnDeadline); + return result; } private String generateFourDigitPassword() { diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellEntity.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellEntity.java index 0677cb9..fd8f2df 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellEntity.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellEntity.java @@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Date; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -61,6 +63,10 @@ public class CabinetCellEntity extends BaseEntity { @TableField("password") private String password; + @ApiModelProperty("格口密码创建时间") + @TableField("password_create_time") + private Date passwordCreateTime; + @ApiModelProperty("是否已租用:0-未租用,1-已租用") @TableField("is_rented") private Integer isRented; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellServiceImpl.java index 5a66be0..df8bf45 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellServiceImpl.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/db/CabinetCellServiceImpl.java @@ -97,6 +97,7 @@ public class CabinetCellServiceImpl extends ServiceImpl updateWrapper = new UpdateWrapper<>(); updateWrapper.set("password", null) + .set("password_create_time", null) .set("usage_status", 1) // 空闲 .eq("cell_id", cellId) .eq("deleted", 0); diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/dto/CabinetCellDTO.java b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/dto/CabinetCellDTO.java index 640823b..26d87ad 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/dto/CabinetCellDTO.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/cabinet/cell/dto/CabinetCellDTO.java @@ -9,6 +9,7 @@ import com.agileboot.domain.system.user.db.SysUserEntity; import lombok.Data; import java.math.BigDecimal; +import java.util.Date; @ExcelSheet(name = "柜机格口信息列表") @Data @@ -64,6 +65,9 @@ public class CabinetCellDTO { @ExcelColumn(name = "格口密码") private String password; + @ExcelColumn(name = "格口密码创建时间") + private Date passwordCreateTime; + @ExcelColumn(name = "使用状态") private Integer usageStatus; @@ -80,6 +84,9 @@ public class CabinetCellDTO { @ExcelColumn(name = "封面图URL") private String coverImg; + @ExcelColumn(name = "归还期限(天)") + private Integer returnDeadline; + @ExcelColumn(name = "操作人") private String operator; diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/GoodsApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/GoodsApplicationService.java index 320b4e3..46b6b82 100644 --- a/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/GoodsApplicationService.java +++ b/agileboot-domain/src/main/java/com/agileboot/domain/shop/goods/GoodsApplicationService.java @@ -40,6 +40,7 @@ public class GoodsApplicationService { public void addGoods(AddGoodsCommand command) { GoodsModel model = goodsModelFactory.create(); model.loadAddGoodsCommand(command); + model.initBaseEntity(); /* model.checkGoodsNameUnique(); model.checkCategoryExist();*/ diff --git a/doc/sql/cabinet.sql b/doc/sql/cabinet.sql index 6972cca..2b8f331 100644 --- a/doc/sql/cabinet.sql +++ b/doc/sql/cabinet.sql @@ -1,8 +1,26 @@ +-- wxshop.mqtt_server definition + +CREATE TABLE `mqtt_server` ( + `mqtt_server_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `server_url` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'MQTT服务器地址', + `username` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '连接账号', + `password` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '连接密码', + `topic_filter` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '订阅主题过滤器', + `publish_topic` varchar(200) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '发布主题', + `creator_id` bigint DEFAULT '0' COMMENT '创建者ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater_id` bigint DEFAULT '0' COMMENT '更新者ID', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)', + PRIMARY KEY (`mqtt_server_id`) +) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='MQTT服务配置表'; + + -- wxshop.shop definition CREATE TABLE `shop` ( `shop_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `shop_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商店名称', + `shop_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商店名称', `corpid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '企业微信id', `belong_type` tinyint NOT NULL DEFAULT '0' COMMENT '归属类型(0-借还柜 1-固资通)', `mode` tinyint NOT NULL DEFAULT '0' COMMENT '运行模式(0-支付模式 1-审批模式 2-借还模式 3-会员模式 4-耗材模式)', @@ -14,14 +32,14 @@ CREATE TABLE `shop` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)', PRIMARY KEY (`shop_id`) -) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商店表,每个柜子属于一个商店'; +) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商店表,每个柜子属于一个商店'; -- wxshop.smart_cabinet definition CREATE TABLE `smart_cabinet` ( `cabinet_id` bigint NOT NULL AUTO_INCREMENT COMMENT '柜机唯一ID', - `cabinet_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '柜机名称', + `cabinet_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '柜机名称', `cabinet_type` tinyint NOT NULL DEFAULT '0' COMMENT '柜机类型(0主柜 1副柜)', `main_cabinet` bigint DEFAULT NULL COMMENT '归属主柜ID', `balance_enable` tinyint NOT NULL DEFAULT '1' COMMENT '借呗支付(1-正常使用 0-禁止使用)', @@ -29,7 +47,7 @@ CREATE TABLE `smart_cabinet` ( `belong_type` tinyint NOT NULL DEFAULT '0' COMMENT '归属类型(0-借还柜 1-固资通)', `shop_id` bigint DEFAULT NULL COMMENT '归属商店ID', `mqtt_server_id` bigint DEFAULT NULL COMMENT 'MQTT服务ID', - `template_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '柜机模版编号', + `template_no` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '柜机模版编号', `lock_control_no` int NOT NULL COMMENT '锁控板序号', `location` int NOT NULL COMMENT '柜机位置', `creator_id` bigint NOT NULL DEFAULT '0' COMMENT '创建者ID', @@ -38,13 +56,30 @@ CREATE TABLE `smart_cabinet` ( `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)', `return_deadline` int NOT NULL DEFAULT '0' COMMENT '归还期限(天),0表示不限制', - `corpid` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '企业微信id', + `corpid` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '企业微信id', PRIMARY KEY (`cabinet_id`), KEY `idx_template_no` (`template_no`), KEY `idx_location` (`location`) ) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='智能柜信息表'; +-- wxshop.cabinet_mainboard definition + +CREATE TABLE `cabinet_mainboard` ( + `mainboard_id` bigint NOT NULL AUTO_INCREMENT COMMENT '主板唯一ID', + `cabinet_id` bigint NOT NULL COMMENT '关联柜机ID', + `lock_control_no` int NOT NULL COMMENT '锁控板序号', + `creator_id` bigint DEFAULT '0' COMMENT '创建者ID', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater_id` bigint DEFAULT '0' COMMENT '更新者ID', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '删除标志(0存在 1删除)', + PRIMARY KEY (`mainboard_id`), + KEY `idx_cabinet` (`cabinet_id`), + CONSTRAINT `fk_mainboard_cabinet` FOREIGN KEY (`cabinet_id`) REFERENCES `smart_cabinet` (`cabinet_id`) +) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='机柜主板信息表'; + + -- wxshop.cabinet_cell definition CREATE TABLE `cabinet_cell` ( @@ -57,6 +92,7 @@ CREATE TABLE `cabinet_cell` ( `stock` int NOT NULL DEFAULT '0' COMMENT '库存数量', `cell_price` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '格口租用价格', `password` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '格口密码', + `password_create_time` datetime DEFAULT NULL COMMENT '格口密码创建时间', `is_rented` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否已租用:0-未租用,1-已租用', `cell_type` tinyint NOT NULL DEFAULT '1' COMMENT '格口类型(1小格 2中格 3大格 4超大格)', `usage_status` tinyint NOT NULL DEFAULT '1' COMMENT '使用状态(1空闲 2已占用)', diff --git a/sql/20251216_cabinet_cell.sql b/sql/20251216_cabinet_cell.sql index a5ed6d4..50ddc9a 100644 --- a/sql/20251216_cabinet_cell.sql +++ b/sql/20251216_cabinet_cell.sql @@ -1,3 +1,7 @@ ALTER TABLE `cabinet_cell` ADD COLUMN `password` VARCHAR(10) DEFAULT NULL COMMENT '格口密码' -AFTER `cell_price`; \ No newline at end of file +AFTER `cell_price`; + +ALTER TABLE `cabinet_cell` +ADD COLUMN `password_create_time` datetime DEFAULT NULL COMMENT '格口密码创建时间' +AFTER `password`; \ No newline at end of file