From 365731af39830f3c78698d176a67054321788dc1 Mon Sep 17 00:00:00 2001 From: zch Date: Mon, 1 Jun 2026 13:41:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(wcs):=20=E5=AE=8C=E6=88=90WCS=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=A4=9A=E7=BB=B4=E5=BA=A6=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 统一Excel字段展示格式,简化表头并配置字典转换 - 新增实时任务明细下拉查询接口,避免分页全量问题 - 为核心业务service添加缓存注解,优化查询性能 - 完善MyBatis跨库用户表配置,支持动态schema - 优化各业务service的校验逻辑与代码注释 --- .../dromara/wcs/config/WcsMybatisConfig.java | 77 ++++++++++++++++++- .../controller/LiveTaskDetailController.java | 10 +++ .../wcs/domain/vo/BaseDeviceHostVo.java | 2 +- .../wcs/domain/vo/BaseDeviceInfoVo.java | 11 ++- .../wcs/domain/vo/BaseDeviceParamVo.java | 6 +- .../wcs/domain/vo/BaseLocationInfoVo.java | 4 +- .../wcs/domain/vo/BaseMaterialInfoVo.java | 2 +- .../wcs/domain/vo/BasePathDetailsVo.java | 2 +- .../dromara/wcs/domain/vo/BasePathInfoVo.java | 2 +- .../wcs/domain/vo/BaseStoreInfoVo.java | 2 +- .../impl/BaseDeviceHostServiceImpl.java | 16 ++++ .../impl/BaseDeviceInfoServiceImpl.java | 9 +++ .../impl/BaseDeviceParamServiceImpl.java | 9 +++ .../impl/BaseLocationInfoServiceImpl.java | 10 +++ .../impl/BaseMaterialInfoServiceImpl.java | 12 +++ .../impl/BasePathDetailsServiceImpl.java | 13 +++- .../service/impl/BasePathInfoServiceImpl.java | 38 ++++++++- .../impl/BaseStoreInfoServiceImpl.java | 16 ++++ .../impl/LiveTaskDetailServiceImpl.java | 34 +++++++- .../impl/LiveTaskQueueServiceImpl.java | 51 +++++++++++- .../mapper/wcs/BaseDeviceHostMapper.xml | 1 + 21 files changed, 309 insertions(+), 18 deletions(-) diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/config/WcsMybatisConfig.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/config/WcsMybatisConfig.java index db63b20..d3c0252 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/config/WcsMybatisConfig.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/config/WcsMybatisConfig.java @@ -8,31 +8,103 @@ import org.springframework.context.annotation.Configuration; import java.util.Properties; /** - * WCS MyBatis配置。 + * WCS MyBatis 配置类。 + * + *

背景与要解决的问题

+ *

hw-wcs 模块使用 slave 数据源(即 wcs_core 业务库),其业务表中的 create_by、update_by + * 字段存储的是 bigint 类型的用户 ID。要在列表查询时展示"创建人/更新人"的姓名, + * 必须跨库连表到 master 数据源的 sys_user 表。

+ * + *

但 Mapper XML 运行在 slave 数据源上,不能将主库表名写死在 SQL 中,原因有二: + *

    + *
  1. 生产环境主库 schema 名称可能变化(如 wcs_core_ruoyi → prod_ruoyi),写死则连表失效;
  2. + *
  3. 直接拼接有 SQL 注入风险,需在启动期做白名单校验。
  4. + *
+ *

+ * + *

解决思路

+ *

从 Spring 配置文件读取主库用户表全名(格式:schema.table 或 table), + * 经正则白名单校验后,注入为 MyBatis 全局变量 {@code ${wcsSysUserTable}}。 + * Mapper XML 中用该占位符引用,运行时由 MyBatis 自动替换为真实表名, + * 从而实现:环境隔离、配置可覆盖、防注入三重保障。

+ * + *

使用示例(Mapper XML)

+ *
{@code
+ * left join ${wcsSysUserTable} cu on t.create_by = cu.user_id
+ * left join ${wcsSysUserTable} uu on t.update_by = uu.user_id
+ * }
* * @author zch */ -@Configuration +@Configuration // 标记为 Spring 配置类,容器启动时自动扫描并处理其中的 @Bean 方法 public class WcsMybatisConfig { + /** + * MyBatis 全局变量名,Mapper XML 中通过 ${wcsSysUserTable} 引用。 + * 运行时会被替换为真实的用户表全名(如 wcs_core_ruoyi.sys_user)。 + */ private static final String SYS_USER_TABLE_VARIABLE = "wcsSysUserTable"; + + /** + * 表名白名单正则:仅允许 "table" 或 "schema.table" 格式。 + * - [A-Za-z0-9_]+ 匹配表名或 schema 名(字母、数字、下划线) + * - (\\.[A-Za-z0-9_]+)? 可选的 ".table" 部分,用于 schema.table 格式 + * + *

合法示例:sys_user、wcs_core_ruoyi.sys_user

+ *

非法示例:sys-user(含横线)、DROP TABLE sys_user(含空格/关键字)

+ */ private static final String TABLE_NAME_PATTERN = "[A-Za-z0-9_]+(\\.[A-Za-z0-9_]+)?"; + /** + * 注册 MyBatis ConfigurationCustomizer,将用户表全名注入为全局变量。 + * + *

Spring 容器初始化时,MyBatis-Plus 会收集所有 ConfigurationCustomizer Bean, + * 在创建 Configuration 对象后依次调用,本方法即为其中之一。

+ * + * @param systemUserTable 从配置文件读取的用户表全名。 + * 格式:${wcs.system-user-table:默认值} + * - 配置键:wcs.system-user-table + * - 默认值:wcs_core_ruoyi.sys_user(开发环境) + * - 生产环境可通过环境变量 WCS_SYSTEM_USER_TABLE 覆盖 + * (见 application-dev.yml 中的 ${WCS_SYSTEM_USER_TABLE:...}) + * @return ConfigurationCustomizer Lambda,MyBatis 初始化完成后回调执行 + */ @Bean public ConfigurationCustomizer wcsSqlVariableCustomizer( @Value("${wcs.system-user-table:wcs_core_ruoyi.sys_user}") String systemUserTable) { + + // 启动期立即校验,不合法则直接抛异常阻止应用启动,防止非法表名进入 SQL String validatedSystemUserTable = validateTableName(systemUserTable); + + // 返回 Lambda:MyBatis Configuration 对象创建完毕后执行 return configuration -> { + // 获取 MyBatis 已有的全局变量集合(可能为 null) Properties variables = configuration.getVariables(); if (variables == null) { + // 首次访问时初始化,避免 NPE variables = new Properties(); configuration.setVariables(variables); } + // 将校验后的用户表全名写入 MyBatis 全局变量: + // key = "wcsSysUserTable" + // value = "wcs_core_ruoyi.sys_user"(或其他通过校验的值) + // 之后 Mapper XML 中所有 ${wcsSysUserTable} 占位符都会被替换为此值 // 用户表全名交给环境配置,避免从库XML写死主库schema后在生产库名变化时直接失效。 variables.setProperty(SYS_USER_TABLE_VARIABLE, validatedSystemUserTable); }; } + /** + * 校验表名字符串是否合法(仅允许字母、数字、下划线,可选带一个点号分隔的 schema 前缀)。 + * + *

该值最终会通过 MyBatis ${} 占位符直接拼入 SQL 语句的表名位置, + * 属于"非参数化"拼接,因此必须在启动期做严格的白名单校验, + * 杜绝 SQL 注入风险。启动时失败(fail-fast)远比运行时暴露注入面更安全。

+ * + * @param tableName 待校验的表名字符串 + * @return 校验通过的表名(原值返回) + * @throws IllegalArgumentException 若表名为 null 或不符合白名单正则 + */ private String validateTableName(String tableName) { if (tableName == null || !tableName.matches(TABLE_NAME_PATTERN)) { // 该值会进入MyBatis ${} 表名占位,启动期失败比运行期暴露SQL注入面更稳妥。 @@ -40,4 +112,5 @@ public class WcsMybatisConfig { } return tableName; } + } diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/controller/LiveTaskDetailController.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/controller/LiveTaskDetailController.java index 461361c..5d7876f 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/controller/LiveTaskDetailController.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/controller/LiveTaskDetailController.java @@ -45,6 +45,16 @@ public class LiveTaskDetailController extends BaseController { return liveTaskDetailService.queryPageList(bo, pageQuery); } + /** + * 获取实时任务明细下拉列表 + */ + @SaCheckPermission("wcs:taskDetail:query") + @GetMapping("/getTaskDetailList") + public R> getTaskDetailList(LiveTaskDetailBo bo) { + // 表单下拉使用非分页主数据,避免 pageSize 伪全量在明细数量增长后漏选。 + return R.ok(liveTaskDetailService.queryList(bo)); + } + /** * 导出实时任务明细列表 */ diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceHostVo.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceHostVo.java index dc27249..d7e8b35 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceHostVo.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceHostVo.java @@ -67,7 +67,7 @@ public class BaseDeviceHostVo implements Serializable { /** * 是否标识:1-是;0-否 */ - @ExcelProperty(value = "是否标识:1-是;0-否", converter = ExcelDictConvert.class) + @ExcelProperty(value = "是否标识", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "wcs_is_flag") private Integer isFlag; diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceInfoVo.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceInfoVo.java index 8fded07..c9c7958 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceInfoVo.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceInfoVo.java @@ -4,6 +4,8 @@ import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; import org.dromara.wcs.domain.BaseDeviceInfo; import java.io.Serial; @@ -53,19 +55,22 @@ public class BaseDeviceInfoVo implements Serializable { /** * 设备类型:0-输送线;1-AGV;2-提升机 */ - @ExcelProperty(value = "设备类型:0-输送线;1-AGV;2-提升机") + @ExcelProperty(value = "设备类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "wcs_device_type") private Integer deviceType; /** * 设备状态:0-正常;1-在忙;2-异常 */ - @ExcelProperty(value = "设备状态:0-正常;1-在忙;2-异常") + @ExcelProperty(value = "设备状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "wcs_device_status") private Integer deviceStatus; /** * 是否标识:1-是;0-否 */ - @ExcelProperty(value = "是否标识:1-是;0-否") + @ExcelProperty(value = "是否标识", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "wcs_is_flag") private Integer isFlag; /** diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceParamVo.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceParamVo.java index 4396f46..1506f4a 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceParamVo.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseDeviceParamVo.java @@ -67,7 +67,8 @@ public class BaseDeviceParamVo implements Serializable { /** * 操作类型:1-只读;2-只写;0-默认读写 */ - @ExcelProperty(value = "操作类型:1-只读;2-只写;0-默认读写") + @ExcelProperty(value = "操作类型", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "wcs_operation_type") private Integer operationType; /** @@ -80,7 +81,8 @@ public class BaseDeviceParamVo implements Serializable { /** * 是否标识:1-是;0-否 */ - @ExcelProperty(value = "是否标识:1-是;0-否") + @ExcelProperty(value = "是否标识", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "wcs_is_flag") private Integer isFlag; /** diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseLocationInfoVo.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseLocationInfoVo.java index 9c7ed8e..65625ef 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseLocationInfoVo.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseLocationInfoVo.java @@ -103,14 +103,14 @@ public class BaseLocationInfoVo implements Serializable { /** * 库位状态;0-未使用;1-已使用;2-锁库;3-异常 */ - @ExcelProperty(value = "库位状态;0-未使用;1-已使用;2-锁库;3-异常", converter = ExcelDictConvert.class) + @ExcelProperty(value = "库位状态", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "wcs_location_status") private Integer locationStatus; /** * 是否标识:1-是;0-否 */ - @ExcelProperty(value = "是否标识:1-是;0-否", converter = ExcelDictConvert.class) + @ExcelProperty(value = "是否标识", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "wcs_is_flag") private Integer isFlag; diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseMaterialInfoVo.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseMaterialInfoVo.java index 85f7499..c990413 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseMaterialInfoVo.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseMaterialInfoVo.java @@ -73,7 +73,7 @@ public class BaseMaterialInfoVo implements Serializable { /** * 是否标识:1-是;0-否 */ - @ExcelProperty(value = "是否标识:1-是;0-否", converter = ExcelDictConvert.class) + @ExcelProperty(value = "是否标识", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "wcs_is_flag") private Integer isFlag; diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BasePathDetailsVo.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BasePathDetailsVo.java index b71125c..81b6b45 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BasePathDetailsVo.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BasePathDetailsVo.java @@ -55,7 +55,7 @@ public class BasePathDetailsVo implements Serializable { /** * 是否标识:1-是;0-否 */ - @ExcelProperty(value = "是否标识:1-是;0-否", converter = ExcelDictConvert.class) + @ExcelProperty(value = "是否标识", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "wcs_is_flag") private Integer isFlag; diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BasePathInfoVo.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BasePathInfoVo.java index 1ee34a3..bbeaa42 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BasePathInfoVo.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BasePathInfoVo.java @@ -50,7 +50,7 @@ public class BasePathInfoVo implements Serializable { /** * 是否标识:1-是;0-否 */ - @ExcelProperty(value = "是否标识:1-是;0-否", converter = ExcelDictConvert.class) + @ExcelProperty(value = "是否标识", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "wcs_is_flag") private Integer isFlag; diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseStoreInfoVo.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseStoreInfoVo.java index d1b9c8b..bae46b9 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseStoreInfoVo.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/domain/vo/BaseStoreInfoVo.java @@ -49,7 +49,7 @@ public class BaseStoreInfoVo implements Serializable { /** * 是否标识:1-是;0-否 */ - @ExcelProperty(value = "是否标识:1-是;0-否", converter = ExcelDictConvert.class) + @ExcelProperty(value = "是否标识", converter = ExcelDictConvert.class) @ExcelDictFormat(dictType = "wcs_is_flag") private Integer isFlag; diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceHostServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceHostServiceImpl.java index ead399c..7f2d4a0 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceHostServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceHostServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.bo.BaseDeviceHostBo; import org.dromara.wcs.domain.vo.BaseDeviceHostVo; @@ -44,6 +46,7 @@ public class BaseDeviceHostServiceImpl implements IBaseDeviceHostService { * @return 设备主机 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public BaseDeviceHostVo queryById(Long objId){ return baseMapper.selectCustomBaseDeviceHostVoById(objId); } @@ -142,10 +145,14 @@ public class BaseDeviceHostServiceImpl implements IBaseDeviceHostService { @Override @DSTransactional public Boolean insertByBo(BaseDeviceHostBo bo) { + // 将 BO 对象转换为实体对象 BaseDeviceHost add = MapstructUtils.convert(bo, BaseDeviceHost.class); + // 保存前进行数据校验 validEntityBeforeSave(add); + // 执行插入操作 boolean flag = baseMapper.insert(add) > 0; if (flag) { + // 插入成功后将生成的主键回填到 BO 对象 bo.setObjId(add.getObjId()); } return flag; @@ -158,10 +165,14 @@ public class BaseDeviceHostServiceImpl implements IBaseDeviceHostService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(BaseDeviceHostBo bo) { + // 将 BO 对象转换为实体对象 BaseDeviceHost update = MapstructUtils.convert(bo, BaseDeviceHost.class); + // 保存前进行数据校验 validEntityBeforeSave(update); + // 执行更新操作 return baseMapper.updateById(update) > 0; } @@ -169,15 +180,19 @@ public class BaseDeviceHostServiceImpl implements IBaseDeviceHostService { * 保存前的数据校验 */ private void validEntityBeforeSave(BaseDeviceHost entity){ + // 校验实体对象和主机编号 if (entity == null || StringUtils.isBlank(entity.getHostCode())) { throw new ServiceException("主机编号不能为空"); } + // 查询相同主机编号的数量,排除当前记录(更新场景) Long sameHostCodeCount = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(BaseDeviceHost::getHostCode, entity.getHostCode()) .ne(entity.getObjId() != null, BaseDeviceHost::getObjId, entity.getObjId())); + // 校验主机编号唯一性 if (sameHostCodeCount > 0) { throw new ServiceException("主机编号已存在,请更换主机编号"); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { // 主数据新增默认启用,避免下拉选择时新建记录因空标识被业务过滤。 entity.setIsFlag(1); @@ -192,6 +207,7 @@ public class BaseDeviceHostServiceImpl implements IBaseDeviceHostService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceInfoServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceInfoServiceImpl.java index cc08639..5bacb72 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceInfoServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceInfoServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.bo.BaseDeviceInfoBo; import org.dromara.wcs.domain.vo.BaseDeviceInfoVo; @@ -44,6 +46,7 @@ public class BaseDeviceInfoServiceImpl implements IBaseDeviceInfoService { * @return 设备信息 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public BaseDeviceInfoVo queryById(Long objId){ return baseMapper.selectCustomBaseDeviceInfoVoById(objId); } @@ -158,6 +161,7 @@ public class BaseDeviceInfoServiceImpl implements IBaseDeviceInfoService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(BaseDeviceInfoBo bo) { BaseDeviceInfo update = MapstructUtils.convert(bo, BaseDeviceInfo.class); @@ -169,15 +173,19 @@ public class BaseDeviceInfoServiceImpl implements IBaseDeviceInfoService { * 保存前的数据校验 */ private void validEntityBeforeSave(BaseDeviceInfo entity){ + // 校验实体对象和设备编号 if (entity == null || StringUtils.isBlank(entity.getDeviceCode())) { throw new ServiceException("设备编号不能为空"); } + // 查询相同设备编号的数量,排除当前记录(更新场景) Long sameDeviceCodeCount = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(BaseDeviceInfo::getDeviceCode, entity.getDeviceCode()) .ne(entity.getObjId() != null, BaseDeviceInfo::getObjId, entity.getObjId())); + // 校验设备编号唯一性 if (sameDeviceCodeCount > 0) { throw new ServiceException("设备编号已存在,请更换设备编号"); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { // 设备主数据默认有效,便于调度侧只按启用设备组装可选范围。 entity.setIsFlag(1); @@ -192,6 +200,7 @@ public class BaseDeviceInfoServiceImpl implements IBaseDeviceInfoService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceParamServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceParamServiceImpl.java index f7ba85a..652de6a 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceParamServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseDeviceParamServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.bo.BaseDeviceParamBo; import org.dromara.wcs.domain.vo.BaseDeviceParamVo; @@ -44,6 +46,7 @@ public class BaseDeviceParamServiceImpl implements IBaseDeviceParamService { * @return 设备参数 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public BaseDeviceParamVo queryById(Long objId){ return baseMapper.selectCustomBaseDeviceParamVoById(objId); } @@ -160,6 +163,7 @@ public class BaseDeviceParamServiceImpl implements IBaseDeviceParamService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(BaseDeviceParamBo bo) { BaseDeviceParam update = MapstructUtils.convert(bo, BaseDeviceParam.class); @@ -171,15 +175,19 @@ public class BaseDeviceParamServiceImpl implements IBaseDeviceParamService { * 保存前的数据校验 */ private void validEntityBeforeSave(BaseDeviceParam entity){ + // 校验实体对象和参数编号 if (entity == null || StringUtils.isBlank(entity.getParamCode())) { throw new ServiceException("参数编号不能为空"); } + // 查询相同参数编号的数量,排除当前记录(更新场景) Long sameParamCodeCount = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(BaseDeviceParam::getParamCode, entity.getParamCode()) .ne(entity.getObjId() != null, BaseDeviceParam::getObjId, entity.getObjId())); + // 校验参数编号唯一性 if (sameParamCodeCount > 0) { throw new ServiceException("参数编号已存在,请更换参数编号"); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { // 参数默认启用,避免设备通讯配置保存后却不参与后续参数装载。 entity.setIsFlag(1); @@ -194,6 +202,7 @@ public class BaseDeviceParamServiceImpl implements IBaseDeviceParamService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseLocationInfoServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseLocationInfoServiceImpl.java index e3af52c..1baff10 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseLocationInfoServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseLocationInfoServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.bo.BaseLocationInfoBo; import org.dromara.wcs.domain.vo.BaseLocationInfoVo; @@ -44,6 +46,7 @@ public class BaseLocationInfoServiceImpl implements IBaseLocationInfoService { * @return 库位信息 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public BaseLocationInfoVo queryById(Long objId){ return baseMapper.selectCustomBaseLocationInfoVoById(objId); } @@ -165,6 +168,7 @@ public class BaseLocationInfoServiceImpl implements IBaseLocationInfoService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(BaseLocationInfoBo bo) { BaseLocationInfo update = MapstructUtils.convert(bo, BaseLocationInfo.class); @@ -176,19 +180,24 @@ public class BaseLocationInfoServiceImpl implements IBaseLocationInfoService { * 保存前的数据校验 */ private void validEntityBeforeSave(BaseLocationInfo entity){ + // 校验实体对象和库位编号 if (entity == null || StringUtils.isBlank(entity.getLocationCode())) { throw new ServiceException("库位编号不能为空"); } + // 查询相同库位编号的数量,排除当前记录(更新场景) Long sameLocationCodeCount = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(BaseLocationInfo::getLocationCode, entity.getLocationCode()) .ne(entity.getObjId() != null, BaseLocationInfo::getObjId, entity.getObjId())); + // 校验库位编号唯一性 if (sameLocationCodeCount > 0) { throw new ServiceException("库位编号已存在,请更换库位编号"); } + // 设置默认库位状态 if (entity.getLocationStatus() == null) { // 新库位默认未使用,避免空状态影响库位看板与分配策略判断。 entity.setLocationStatus(0); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { entity.setIsFlag(1); } @@ -202,6 +211,7 @@ public class BaseLocationInfoServiceImpl implements IBaseLocationInfoService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseMaterialInfoServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseMaterialInfoServiceImpl.java index 0cc96aa..b248855 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseMaterialInfoServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseMaterialInfoServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.bo.BaseMaterialInfoBo; import org.dromara.wcs.domain.vo.BaseMaterialInfoVo; @@ -44,6 +46,7 @@ public class BaseMaterialInfoServiceImpl implements IBaseMaterialInfoService { * @return 物料信息 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public BaseMaterialInfoVo queryById(Long objId){ return baseMapper.selectCustomBaseMaterialInfoVoById(objId); } @@ -159,10 +162,14 @@ public class BaseMaterialInfoServiceImpl implements IBaseMaterialInfoService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(BaseMaterialInfoBo bo) { + // 将 BO 对象转换为实体对象 BaseMaterialInfo update = MapstructUtils.convert(bo, BaseMaterialInfo.class); + // 保存前进行数据校验 validEntityBeforeSave(update); + // 执行更新操作 return baseMapper.updateById(update) > 0; } @@ -170,15 +177,19 @@ public class BaseMaterialInfoServiceImpl implements IBaseMaterialInfoService { * 保存前的数据校验 */ private void validEntityBeforeSave(BaseMaterialInfo entity){ + // 校验实体对象和物料编号 if (entity == null || StringUtils.isBlank(entity.getMaterialCode())) { throw new ServiceException("物料编号不能为空"); } + // 查询相同物料编号的数量,排除当前记录(更新场景) Long sameMaterialCodeCount = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(BaseMaterialInfo::getMaterialCode, entity.getMaterialCode()) .ne(entity.getObjId() != null, BaseMaterialInfo::getObjId, entity.getObjId())); + // 校验物料编号唯一性 if (sameMaterialCodeCount > 0) { throw new ServiceException("物料编号已存在,请更换物料编号"); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { // 物料主数据默认有效,确保任务和库位表单可直接引用新建物料。 entity.setIsFlag(1); @@ -193,6 +204,7 @@ public class BaseMaterialInfoServiceImpl implements IBaseMaterialInfoService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BasePathDetailsServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BasePathDetailsServiceImpl.java index d2905b0..4709eda 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BasePathDetailsServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BasePathDetailsServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.bo.BasePathDetailsBo; import org.dromara.wcs.domain.vo.BasePathDetailsVo; @@ -44,6 +46,7 @@ public class BasePathDetailsServiceImpl implements IBasePathDetailsService { * @return 路径明细 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public BasePathDetailsVo queryById(Long objId){ return baseMapper.selectCustomBasePathDetailsVoById(objId); } @@ -156,10 +159,14 @@ public class BasePathDetailsServiceImpl implements IBasePathDetailsService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(BasePathDetailsBo bo) { + // 将 BO 对象转换为实体对象 BasePathDetails update = MapstructUtils.convert(bo, BasePathDetails.class); + // 保存前进行数据校验 validEntityBeforeSave(update); + // 执行更新操作 return baseMapper.updateById(update) > 0; } @@ -167,9 +174,11 @@ public class BasePathDetailsServiceImpl implements IBasePathDetailsService { * 保存前的数据校验 */ private void validEntityBeforeSave(BasePathDetails entity){ + // 校验实体对象和路径编号 if (entity == null || StringUtils.isBlank(entity.getPathCode())) { throw new ServiceException("路径编号不能为空"); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { entity.setIsFlag(1); } @@ -183,11 +192,13 @@ public class BasePathDetailsServiceImpl implements IBasePathDetailsService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + // TODO: 做一些业务上的校验,判断是否需要校验 } + // 执行批量删除操作 return baseMapper.deleteByIds(ids) > 0; } } diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BasePathInfoServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BasePathInfoServiceImpl.java index e258926..f4496bc 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BasePathInfoServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BasePathInfoServiceImpl.java @@ -11,6 +11,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.common.core.exception.ServiceException; import org.dromara.wcs.domain.BasePathDetails; @@ -50,6 +52,7 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { * @return 路径信息 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public BasePathInfoVo queryById(Long objId){ BasePathInfoVo vo = baseMapper.selectCustomBasePathInfoVoById(objId); if (vo != null) { @@ -151,11 +154,16 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { @Override @DSTransactional public Boolean insertByBo(BasePathInfoBo bo) { + // 将 BO 对象转换为实体对象 BasePathInfo add = MapstructUtils.convert(bo, BasePathInfo.class); + // 保存前进行数据校验 validEntityBeforeSave(add); + // 执行插入操作 boolean flag = baseMapper.insert(add) > 0; if (flag) { + // 插入成功后将生成的主键回填到 BO 对象 bo.setObjId(add.getObjId()); + // 保存路径明细 saveDetails(bo); } return flag; @@ -168,18 +176,24 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(BasePathInfoBo bo) { + // 查询原记录 BasePathInfo old = baseMapper.selectById(bo.getObjId()); if (old == null) { throw new ServiceException("路径信息不存在,无法修改"); } + // 将 BO 对象转换为实体对象 BasePathInfo update = MapstructUtils.convert(bo, BasePathInfo.class); + // 保存前进行数据校验 validEntityBeforeSave(update); // 路径编号是子表外键,先按旧编号删除旧明细,避免 ON UPDATE CASCADE 后删不到旧数据。 deleteDetailsByPathCode(old.getPathCode()); + // 执行更新操作 boolean flag = baseMapper.updateById(update) > 0; if (flag) { + // 更新成功后保存路径明细 saveDetails(bo); } return flag; @@ -189,15 +203,19 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { * 保存前的数据校验 */ private void validEntityBeforeSave(BasePathInfo entity){ + // 校验实体对象和路径编号 if (entity == null || StringUtils.isBlank(entity.getPathCode())) { throw new ServiceException("路径编号不能为空"); } + // 查询相同路径编号的数量,排除当前记录(更新场景) Long samePathCodeCount = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(BasePathInfo::getPathCode, entity.getPathCode()) .ne(entity.getObjId() != null, BasePathInfo::getObjId, entity.getObjId())); + // 校验路径编号唯一性 if (samePathCodeCount > 0) { throw new ServiceException("路径编号已存在,请更换路径编号"); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { // 路径默认有效,避免新建后任务下拉因状态为空无法选择。 entity.setIsFlag(1); @@ -208,19 +226,24 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { * 保存路径明细。 */ private void saveDetails(BasePathInfoBo bo) { + // 获取路径明细列表 List details = bo.getDetails(); if (details == null || details.isEmpty()) { return; } + // 遍历明细列表并保存 for (BasePathDetailsBo detailBo : details) { + // 将明细 BO 转换为实体对象 BasePathDetails detail = MapstructUtils.convert(detailBo, BasePathDetails.class); // 主表整体保存时统一替换子表,清空明细主键避免误更新历史明细。 detail.setObjId(null); // 明细始终跟随当前主表路径编号,避免用户改编号后子表仍挂在旧路径上。 detail.setPathCode(bo.getPathCode()); + // 设置默认启用标识 if (detail.getIsFlag() == null) { detail.setIsFlag(1); } + // 插入明细记录 basePathDetailsMapper.insert(detail); } } @@ -229,9 +252,11 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { * 按路径编号删除子表明细。 */ private void deleteDetailsByPathCode(String pathCode) { + // 路径编号为空时直接返回 if (StringUtils.isBlank(pathCode)) { return; } + // 根据路径编号删除明细 basePathDetailsMapper.delete(Wrappers.lambdaQuery() .eq(BasePathDetails::getPathCode, pathCode)); } @@ -240,9 +265,11 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { * 按路径编号批量删除子表明细。 */ private void deleteDetailsByPathCodes(Collection pathCodes) { + // 路径编号集合为空时直接返回 if (pathCodes == null || pathCodes.isEmpty()) { return; } + // 根据路径编号集合批量删除明细 basePathDetailsMapper.delete(Wrappers.lambdaQuery() .in(BasePathDetails::getPathCode, pathCodes)); } @@ -251,21 +278,26 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { * 批量挂载路径明细,避免 XML 嵌套 collection 造成分页列表 N+1 查询。 */ private void attachDetails(List pathInfoList) { + // 路径信息列表为空时直接返回 if (pathInfoList == null || pathInfoList.isEmpty()) { return; } + // 提取所有路径编号并去重 List pathCodes = pathInfoList.stream() .map(BasePathInfoVo::getPathCode) .filter(StringUtils::isNotBlank) .distinct() .toList(); + // 路径编号为空时设置空明细列表 if (pathCodes.isEmpty()) { pathInfoList.forEach(item -> item.setDetails(List.of())); return; } + // 批量查询明细并按路径编号分组 Map> detailMap = basePathDetailsMapper.selectCustomBasePathDetailsVoByPathCodes(pathCodes) .stream() .collect(Collectors.groupingBy(BasePathDetailsVo::getPathCode)); + // 将明细挂载到对应的路径信息 pathInfoList.forEach(item -> item.setDetails(detailMap.getOrDefault(item.getPathCode(), List.of()))); } @@ -277,21 +309,25 @@ public class BasePathInfoServiceImpl implements IBasePathInfoService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + // 主键集合为空时直接返回 if (ids == null || ids.isEmpty()) { // 删除入口明确要求主键集合,空集合直接返回,避免拼出无意义SQL。 return false; } if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + // TODO: 做一些业务上的校验,判断是否需要校验 } + // 根据主键集合查询路径编号 List pathCodes = baseMapper.selectPathCodesByIds(ids).stream() .filter(StringUtils::isNotBlank) .distinct() .toList(); // 主子表删除放在同一个本地多数据源事务里,避免只删主表后明细残留。 deleteDetailsByPathCodes(pathCodes); + // 执行主表批量删除 return baseMapper.deleteByIds(ids) > 0; } } diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseStoreInfoServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseStoreInfoServiceImpl.java index 1ac687d..38c6744 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseStoreInfoServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/BaseStoreInfoServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.bo.BaseStoreInfoBo; import org.dromara.wcs.domain.vo.BaseStoreInfoVo; @@ -44,6 +46,7 @@ public class BaseStoreInfoServiceImpl implements IBaseStoreInfoService { * @return 仓库信息 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public BaseStoreInfoVo queryById(Long objId){ return baseMapper.selectCustomBaseStoreInfoVoById(objId); } @@ -139,10 +142,14 @@ public class BaseStoreInfoServiceImpl implements IBaseStoreInfoService { @Override @DSTransactional public Boolean insertByBo(BaseStoreInfoBo bo) { + // 将 BO 对象转换为实体对象 BaseStoreInfo add = MapstructUtils.convert(bo, BaseStoreInfo.class); + // 保存前进行数据校验 validEntityBeforeSave(add); + // 执行插入操作 boolean flag = baseMapper.insert(add) > 0; if (flag) { + // 插入成功后将生成的主键回填到 BO 对象 bo.setObjId(add.getObjId()); } return flag; @@ -155,10 +162,14 @@ public class BaseStoreInfoServiceImpl implements IBaseStoreInfoService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(BaseStoreInfoBo bo) { + // 将 BO 对象转换为实体对象 BaseStoreInfo update = MapstructUtils.convert(bo, BaseStoreInfo.class); + // 保存前进行数据校验 validEntityBeforeSave(update); + // 执行更新操作 return baseMapper.updateById(update) > 0; } @@ -166,15 +177,19 @@ public class BaseStoreInfoServiceImpl implements IBaseStoreInfoService { * 保存前的数据校验 */ private void validEntityBeforeSave(BaseStoreInfo entity){ + // 校验实体对象和仓库编号 if (entity == null || StringUtils.isBlank(entity.getStoreCode())) { throw new ServiceException("仓库编号不能为空"); } + // 查询相同仓库编号的数量,排除当前记录(更新场景) Long sameStoreCodeCount = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(BaseStoreInfo::getStoreCode, entity.getStoreCode()) .ne(entity.getObjId() != null, BaseStoreInfo::getObjId, entity.getObjId())); + // 校验仓库编号唯一性 if (sameStoreCodeCount > 0) { throw new ServiceException("仓库编号已存在,请更换仓库编号"); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { // 仓库默认启用,保证库位维护时可以立即作为所属仓库使用。 entity.setIsFlag(1); @@ -189,6 +204,7 @@ public class BaseStoreInfoServiceImpl implements IBaseStoreInfoService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { if(isValid){ diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/LiveTaskDetailServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/LiveTaskDetailServiceImpl.java index 719ce55..904bfda 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/LiveTaskDetailServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/LiveTaskDetailServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.bo.LiveTaskDetailBo; import org.dromara.wcs.domain.vo.LiveTaskDetailVo; @@ -47,6 +49,7 @@ public class LiveTaskDetailServiceImpl implements ILiveTaskDetailService { * @return 实时任务明细 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public LiveTaskDetailVo queryById(Long objId){ return baseMapper.selectCustomLiveTaskDetailVoById(objId); } @@ -153,10 +156,14 @@ public class LiveTaskDetailServiceImpl implements ILiveTaskDetailService { @Override @DSTransactional public Boolean insertByBo(LiveTaskDetailBo bo) { + // 将 BO 对象转换为实体对象 LiveTaskDetail add = MapstructUtils.convert(bo, LiveTaskDetail.class); + // 保存前进行数据校验 validEntityBeforeSave(add); + // 执行插入操作 boolean flag = baseMapper.insert(add) > 0; if (flag) { + // 插入成功后将生成的主键回填到 BO 对象 bo.setObjId(add.getObjId()); } return flag; @@ -169,17 +176,23 @@ public class LiveTaskDetailServiceImpl implements ILiveTaskDetailService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(LiveTaskDetailBo bo) { + // 将 BO 对象转换为实体对象 LiveTaskDetail update = MapstructUtils.convert(bo, LiveTaskDetail.class); + // 校验主键标识 if (update == null || update.getObjId() == null) { throw new ServiceException("主键标识不能为空"); } + // 查询原记录 LiveTaskDetail old = baseMapper.selectById(update.getObjId()); if (old == null) { throw new ServiceException("实时任务明细不存在,无法修改"); } + // 保存前进行数据校验 validEntityBeforeSave(update); + // 执行更新操作 return baseMapper.updateById(update) > 0; } @@ -187,19 +200,25 @@ public class LiveTaskDetailServiceImpl implements ILiveTaskDetailService { * 保存前的数据校验 */ private void validEntityBeforeSave(LiveTaskDetail entity){ + // 校验实体对象和任务编号 if (entity == null || StringUtils.isBlank(entity.getTaskCode())) { throw new ServiceException("任务编号不能为空"); } + // 查询任务队列主表记录 LiveTaskQueue taskQueue = liveTaskQueueMapper.selectOne(Wrappers.lambdaQuery() .eq(LiveTaskQueue::getTaskCode, entity.getTaskCode()) .last("limit 1")); + // 校验任务编号是否存在 if (taskQueue == null) { throw new ServiceException("任务编号不存在,请先维护实时任务队列"); } + // 从主表继承空字段 inheritMasterFieldsWhenBlank(entity, taskQueue); + // 设置默认校验标识 if (entity.getIsValidate() == null) { entity.setIsValidate(1); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { entity.setIsFlag(1); } @@ -209,33 +228,43 @@ public class LiveTaskDetailServiceImpl implements ILiveTaskDetailService { * 明细独立维护时从父任务继承空字段,保证手工新增的子表也与队列主表保持同一业务口径。 */ private void inheritMasterFieldsWhenBlank(LiveTaskDetail detail, LiveTaskQueue taskQueue) { + // 物料编码为空时从主表继承 if (StringUtils.isBlank(detail.getMaterialCode())) { detail.setMaterialCode(taskQueue.getMaterialCode()); } + // 托盘条码为空时从主表继承 if (StringUtils.isBlank(detail.getPalletBarcode())) { detail.setPalletBarcode(taskQueue.getPalletBarcode()); } + // 物料条码为空时从主表继承 if (StringUtils.isBlank(detail.getMaterialBarcode())) { detail.setMaterialBarcode(taskQueue.getMaterialBarcode()); } + // 物料数量为空时从主表继承 if (detail.getMaterialCount() == null) { detail.setMaterialCount(taskQueue.getMaterialCount()); } + // 任务类型为空时从主表继承 if (detail.getTaskType() == null) { detail.setTaskType(taskQueue.getTaskType()); } + // 任务分类为空时从主表继承 if (detail.getTaskCategory() == null) { detail.setTaskCategory(taskQueue.getTaskCategory()); } + // 起始点位为空时从主表继承 if (StringUtils.isBlank(detail.getStartPoint())) { detail.setStartPoint(taskQueue.getStartPoint()); } + // 目标点位为空时从主表继承 if (StringUtils.isBlank(detail.getEndPoint())) { detail.setEndPoint(taskQueue.getEndPoint()); } + // 路径编号为空时从主表继承 if (StringUtils.isBlank(detail.getPathCode())) { detail.setPathCode(taskQueue.getPathCode()); } + // 任务状态为空时从主表继承 if (detail.getTaskStatus() == null) { detail.setTaskStatus(taskQueue.getTaskStatus()); } @@ -249,15 +278,18 @@ public class LiveTaskDetailServiceImpl implements ILiveTaskDetailService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + // 主键集合为空时直接返回 if (ids == null || ids.isEmpty()) { // 删除入口明确要求主键集合,空集合直接返回,避免拼出无意义SQL。 return false; } if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + // TODO: 做一些业务上的校验,判断是否需要校验 } + // 执行批量删除操作 return baseMapper.deleteByIds(ids) > 0; } } diff --git a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/LiveTaskQueueServiceImpl.java b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/LiveTaskQueueServiceImpl.java index 900f0d5..980eaab 100644 --- a/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/LiveTaskQueueServiceImpl.java +++ b/ruoyi-modules/hw-wcs/src/main/java/org/dromara/wcs/service/impl/LiveTaskQueueServiceImpl.java @@ -12,6 +12,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.dromara.wcs.domain.LiveTaskDetail; import org.dromara.wcs.domain.bo.LiveTaskDetailBo; @@ -50,6 +52,7 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * @return 实时任务队列 */ @Override + @Cacheable(cacheNames = "wcs_cache", key = "#objId") public LiveTaskQueueVo queryById(Long objId){ LiveTaskQueueVo vo = baseMapper.selectCustomLiveTaskQueueVoById(objId); if (vo != null) { @@ -162,11 +165,16 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { @Override @DSTransactional public Boolean insertByBo(LiveTaskQueueBo bo) { + // 将 BO 对象转换为实体对象 LiveTaskQueue add = MapstructUtils.convert(bo, LiveTaskQueue.class); + // 保存前进行数据校验 validEntityBeforeSave(add); + // 执行插入操作 boolean flag = baseMapper.insert(add) > 0; if (flag) { + // 插入成功后将生成的主键回填到 BO 对象 bo.setObjId(add.getObjId()); + // 保存任务明细 saveDetails(bo); } return flag; @@ -179,18 +187,24 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * @return 是否修改成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", key = "#bo.objId") @DSTransactional public Boolean updateByBo(LiveTaskQueueBo bo) { + // 查询原记录 LiveTaskQueue old = baseMapper.selectById(bo.getObjId()); if (old == null) { throw new ServiceException("实时任务队列不存在,无法修改"); } + // 将 BO 对象转换为实体对象 LiveTaskQueue update = MapstructUtils.convert(bo, LiveTaskQueue.class); + // 保存前进行数据校验 validEntityBeforeSave(update); // 任务编号是子表外键,先按旧编号删除旧明细,避免 ON UPDATE CASCADE 后删不到旧数据。 deleteDetailsByTaskCode(old.getTaskCode()); + // 执行更新操作 boolean flag = baseMapper.updateById(update) > 0; if (flag) { + // 更新成功后保存任务明细 saveDetails(bo); } return flag; @@ -200,19 +214,24 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * 保存前的数据校验 */ private void validEntityBeforeSave(LiveTaskQueue entity){ + // 校验实体对象和任务编号 if (entity == null || StringUtils.isBlank(entity.getTaskCode())) { throw new ServiceException("任务编号不能为空"); } + // 查询相同任务编号的数量,排除当前记录(更新场景) Long sameTaskCodeCount = baseMapper.selectCount(Wrappers.lambdaQuery() .eq(LiveTaskQueue::getTaskCode, entity.getTaskCode()) .ne(entity.getObjId() != null, LiveTaskQueue::getObjId, entity.getObjId())); + // 校验任务编号唯一性 if (sameTaskCodeCount > 0) { throw new ServiceException("任务编号已存在,请更换任务编号"); } + // 设置默认任务状态 if (entity.getTaskStatus() == null) { // 新建任务默认进入待执行,避免空状态在调度看板中无法归类。 entity.setTaskStatus(1); } + // 设置默认启用标识 if (entity.getIsFlag() == null) { entity.setIsFlag(1); } @@ -222,17 +241,22 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * 保存任务明细。 */ private void saveDetails(LiveTaskQueueBo bo) { + // 获取任务明细列表 List details = bo.getDetails(); if (details == null || details.isEmpty()) { return; } + // 遍历明细列表并保存 for (LiveTaskDetailBo detailBo : details) { + // 将明细 BO 转换为实体对象 LiveTaskDetail detail = MapstructUtils.convert(detailBo, LiveTaskDetail.class); // 子表跟随主表整体替换,清空旧主键避免误把历史明细主键带入新增流程。 detail.setObjId(null); + // 设置任务编号 detail.setTaskCode(bo.getTaskCode()); // 明细常用字段默认继承主表,减少人工重复录入导致的主子表业务信息不一致。 inheritMasterFieldsWhenBlank(detail, bo); + // 插入明细记录 liveTaskDetailMapper.insert(detail); } } @@ -241,39 +265,51 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * 明细字段为空时继承主表字段。 */ private void inheritMasterFieldsWhenBlank(LiveTaskDetail detail, LiveTaskQueueBo bo) { + // 物料编码为空时从主表继承 if (StringUtils.isBlank(detail.getMaterialCode())) { detail.setMaterialCode(bo.getMaterialCode()); } + // 托盘条码为空时从主表继承 if (StringUtils.isBlank(detail.getPalletBarcode())) { detail.setPalletBarcode(bo.getPalletBarcode()); } + // 物料条码为空时从主表继承 if (StringUtils.isBlank(detail.getMaterialBarcode())) { detail.setMaterialBarcode(bo.getMaterialBarcode()); } + // 物料数量为空时从主表继承 if (detail.getMaterialCount() == null) { detail.setMaterialCount(bo.getMaterialCount()); } + // 任务类型为空时从主表继承 if (detail.getTaskType() == null) { detail.setTaskType(bo.getTaskType()); } + // 任务分类为空时从主表继承 if (detail.getTaskCategory() == null) { detail.setTaskCategory(bo.getTaskCategory()); } + // 起始点位为空时从主表继承 if (StringUtils.isBlank(detail.getStartPoint())) { detail.setStartPoint(bo.getStartPoint()); } + // 目标点位为空时从主表继承 if (StringUtils.isBlank(detail.getEndPoint())) { detail.setEndPoint(bo.getEndPoint()); } + // 路径编号为空时从主表继承 if (StringUtils.isBlank(detail.getPathCode())) { detail.setPathCode(bo.getPathCode()); } + // 任务状态为空时从主表继承 if (detail.getTaskStatus() == null) { detail.setTaskStatus(bo.getTaskStatus()); } + // 设置默认校验标识 if (detail.getIsValidate() == null) { detail.setIsValidate(1); } + // 设置默认启用标识 if (detail.getIsFlag() == null) { detail.setIsFlag(1); } @@ -283,9 +319,11 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * 按任务编号删除子表明细。 */ private void deleteDetailsByTaskCode(String taskCode) { + // 任务编号为空时直接返回 if (StringUtils.isBlank(taskCode)) { return; } + // 根据任务编号删除明细 liveTaskDetailMapper.delete(Wrappers.lambdaQuery() .eq(LiveTaskDetail::getTaskCode, taskCode)); } @@ -294,9 +332,11 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * 按任务编号批量删除子表明细。 */ private void deleteDetailsByTaskCodes(Collection taskCodes) { + // 任务编号集合为空时直接返回 if (taskCodes == null || taskCodes.isEmpty()) { return; } + // 根据任务编号集合批量删除明细 liveTaskDetailMapper.delete(Wrappers.lambdaQuery() .in(LiveTaskDetail::getTaskCode, taskCodes)); } @@ -305,21 +345,26 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * 批量挂载任务明细,避免 XML 嵌套 collection 造成分页列表 N+1 查询。 */ private void attachDetails(List taskQueueList) { + // 任务队列列表为空时直接返回 if (taskQueueList == null || taskQueueList.isEmpty()) { return; } + // 提取所有任务编号并去重 List taskCodes = taskQueueList.stream() .map(LiveTaskQueueVo::getTaskCode) .filter(StringUtils::isNotBlank) .distinct() .toList(); + // 任务编号为空时设置空明细列表 if (taskCodes.isEmpty()) { taskQueueList.forEach(item -> item.setDetails(List.of())); return; } + // 批量查询明细并按任务编号分组 Map> detailMap = liveTaskDetailMapper.selectCustomLiveTaskDetailVoByTaskCodes(taskCodes) .stream() .collect(Collectors.groupingBy(LiveTaskDetailVo::getTaskCode)); + // 将明细挂载到对应的任务队列 taskQueueList.forEach(item -> item.setDetails(detailMap.getOrDefault(item.getTaskCode(), List.of()))); } @@ -331,21 +376,25 @@ public class LiveTaskQueueServiceImpl implements ILiveTaskQueueService { * @return 是否删除成功 */ @Override + @CacheEvict(cacheNames = "wcs_cache", allEntries = true) @DSTransactional public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + // 主键集合为空时直接返回 if (ids == null || ids.isEmpty()) { // 删除入口明确要求主键集合,空集合直接返回,避免拼出无意义SQL。 return false; } if(isValid){ - //TODO 做一些业务上的校验,判断是否需要校验 + // TODO: 做一些业务上的校验,判断是否需要校验 } + // 根据主键集合查询任务编号 List taskCodes = baseMapper.selectTaskCodesByIds(ids).stream() .filter(StringUtils::isNotBlank) .distinct() .toList(); // 主子表删除放在同一个本地多数据源事务里,避免只删主表后明细残留。 deleteDetailsByTaskCodes(taskCodes); + // 执行主表批量删除 return baseMapper.deleteByIds(ids) > 0; } } diff --git a/ruoyi-modules/hw-wcs/src/main/resources/mapper/wcs/BaseDeviceHostMapper.xml b/ruoyi-modules/hw-wcs/src/main/resources/mapper/wcs/BaseDeviceHostMapper.xml index 087c993..e566697 100644 --- a/ruoyi-modules/hw-wcs/src/main/resources/mapper/wcs/BaseDeviceHostMapper.xml +++ b/ruoyi-modules/hw-wcs/src/main/resources/mapper/wcs/BaseDeviceHostMapper.xml @@ -72,4 +72,5 @@ from base_device_host t ${ew.getCustomSqlSegment} +