diff --git a/OAuth2-Token接口使用文档.md b/OAuth2-Token接口使用文档.md new file mode 100644 index 00000000..afc3bf58 --- /dev/null +++ b/OAuth2-Token接口使用文档.md @@ -0,0 +1,179 @@ +# OAuth2 Token 接口使用文档 + +## 接口地址 + +``` +POST http://{域名}/system/oauth2/token +Content-Type: application/x-www-form-urlencoded +``` + +## 通用规则 + +- 所有请求**必须**携带 HTTP Basic Auth 请求头:`Authorization: Basic base64(client_id:secret)` +- 响应格式:`{"code":0,"msg":"...","data":{...}}` +- `code=0` 表示成功 +- 默认客户端:`client_id=default`、`secret=admin123` + +--- + +## 支持的授权模式 + +### 1. 客户端模式 `client_credentials`(外部系统调用推荐) + +**适用场景**:机器对机器调用,无需用户登录。 + +```bash +curl -X POST "http://localhost:48080/system/oauth2/token" \ + -u "default:admin123" \ + -d "grant_type=client_credentials" +``` + +参数: + +| 参数 | 必填 | 说明 | +|------|------|------| +| `grant_type` | 是 | 固定值 `client_credentials` | +| `scope` | 否 | 授权范围,多个用空格分隔,如 `read write` | + +成功响应: + +```json +{ + "code": 0, + "data": { + "access_token": "a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "refresh_token": "r1r2r3r4-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "expires_in": 7200, + "token_type": "bearer", + "scope": "read write", + "user_id": 0 + } +} +``` + +--- + +### 2. 密码模式 `password` + +**适用场景**:用户直接用账号密码换取 token(如移动端 App 登录)。 + +```bash +curl -X POST "http://localhost:48080/system/oauth2/token" \ + -u "default:admin123" \ + -d "grant_type=password" \ + -d "username=admin" \ + -d "password=admin123" +``` + +参数: + +| 参数 | 必填 | 说明 | +|------|------|------| +| `grant_type` | 是 | 固定值 `password` | +| `username` | 是 | 用户账号 | +| `password` | 是 | 用户密码 | +| `scope` | 否 | 授权范围,多个用空格分隔 | + +--- + +### 3. 授权码模式 `authorization_code` + +**适用场景**:第三方应用需要用户授权后才能访问用户数据(如 SSO 单点登录)。 + +分两步走: + +**第一步**:用户浏览器访问授权页,拿到 `code` + +```bash +浏览器访问: +http://localhost:48080/system/oauth2/authorize?clientId=default +``` + +用户确认授权后,回调地址会带上 `code` 参数。 + +**第二步**:用 `code` 换 token + +```bash +curl -X POST "http://localhost:48080/system/oauth2/token" \ + -u "default:admin123" \ + -d "grant_type=authorization_code" \ + -d "code=xxxx" \ + -d "redirect_uri=https://回调地址" \ + -d "state=1" +``` + +参数: + +| 参数 | 必填 | 说明 | +|------|------|------| +| `grant_type` | 是 | 固定值 `authorization_code` | +| `code` | 是 | 第一步获取的授权码 | +| `redirect_uri` | 是 | 必须与第一步的回调地址一致 | +| `state` | 否 | 透传的状态值,用于防 CSRF | + +--- + +### 4. 刷新令牌 `refresh_token` + +**适用场景**:token 快过期时,用 `refresh_token` 换新 token,无需重新登录。 + +```bash +curl -X POST "http://localhost:48080/system/oauth2/token" \ + -u "default:admin123" \ + -d "grant_type=refresh_token" \ + -d "refresh_token=r1r2r3r4-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +``` + +参数: + +| 参数 | 必填 | 说明 | +|------|------|------| +| `grant_type` | 是 | 固定值 `refresh_token` | +| `refresh_token` | 是 | 之前获取的 refresh_token | + +--- + +## 其他接口 + +### 校验 Token + +```bash +curl -X POST "http://localhost:48080/system/oauth2/check-token" \ + -u "default:admin123" \ + -d "token=要校验的access_token" +``` + +### 撤销 Token(登出) + +```bash +curl -X DELETE "http://localhost:48080/system/oauth2/token?token=要撤销的access_token" \ + -u "default:admin123" +``` + +--- + +## 四种模式对比 + +| 模式 | grant_type 值 | 是否需用户参与 | 适用场景 | +|------|-------------|-------------|---------| +| 客户端模式 | `client_credentials` | 否 | 外部系统调用、定时任务、机器间通信 | +| 密码模式 | `password` | 是(提供账号密码) | 移动端 App 登录、信任的客户端 | +| 授权码模式 | `authorization_code` | 是(浏览器确认授权) | 第三方应用 SSO、开放平台 | +| 刷新令牌 | `refresh_token` | 否 | Token 续期,配合以上任意模式使用 | + +--- + +## 外部系统完整调用流程 + +``` +① 你在后台创建 OAuth2 客户端,拿到 client_id + secret + │ +② 外部系统用 client_credentials 模式换 token + curl -u "client_id:secret" -d "grant_type=client_credentials" + │ +③ 拿到 access_token,之后每个请求带上 + curl -H "Authorization: Bearer " /admin-api/xxx + │ +④ token 过期前,用 refresh_token 续期 + curl -u "client_id:secret" -d "grant_type=refresh_token" -d "refresh_token=xxx" +``` diff --git a/docs/superpowers/plans/2026-07-14-task-core-implementation.md b/docs/superpowers/plans/2026-07-14-task-core-implementation.md index 504166a1..2a4934a5 100644 --- a/docs/superpowers/plans/2026-07-14-task-core-implementation.md +++ b/docs/superpowers/plans/2026-07-14-task-core-implementation.md @@ -793,7 +793,7 @@ git commit -m "feat: 新增 TransportTaskStatusClient 同步 HTTP 回调客户 ```java package cn.code.nl.module.task.mq; -import cn.code.nl.module.task.mq.message.TaskEventMessage; +import cn.code.nl.module.task.message.TaskEventMessage; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.stereotype.Component; @@ -977,7 +977,7 @@ import cn.code.nl.module.task.client.TransportTaskStatusClient; import cn.code.nl.module.task.dal.dataobject.transporttask.TransportTaskDO; import cn.code.nl.module.task.dal.mysql.transporttask.TransportTaskMapper; import cn.code.nl.module.task.dto.AcsFeedbackReqDTO; -import cn.code.nl.module.task.mq.message.TaskEventMessage; +import cn.code.nl.module.task.message.TaskEventMessage; import cn.code.nl.module.task.dto.TaskStatusCallbackReqDTO; import cn.code.nl.module.task.dto.TaskStatusCallbackRespDTO; import cn.code.nl.module.task.enums.CallbackStatusEnum; diff --git a/docs/superpowers/plans/2026-07-15-task-operation-refactor.md b/docs/superpowers/plans/2026-07-15-task-operation-refactor.md index d2bf1e46..12cf48f7 100644 --- a/docs/superpowers/plans/2026-07-15-task-operation-refactor.md +++ b/docs/superpowers/plans/2026-07-15-task-operation-refactor.md @@ -202,7 +202,7 @@ import cn.code.nl.module.task.dto.AcsFeedbackReqDTO; import cn.code.nl.module.task.dto.TaskStatusCallbackReqDTO; import cn.code.nl.module.task.enums.TaskEventTypeEnum; import cn.code.nl.module.task.enums.TaskOperationTypeEnum; -import cn.code.nl.module.task.mq.message.TaskEventMessage; +import cn.code.nl.module.task.message.TaskEventMessage; import cn.code.nl.module.task.mq.producer.TaskEventProducer; import cn.hutool.core.util.StrUtil; import jakarta.annotation.PostConstruct; diff --git a/nl-framework/nl-spring-boot-starter-execute/src/main/java/cn/code/nl/framework/execute/core/TaskFactory.java b/nl-framework/nl-spring-boot-starter-execute/src/main/java/cn/code/nl/framework/execute/core/TaskFactory.java index 4b16f8c9..14d4e455 100644 --- a/nl-framework/nl-spring-boot-starter-execute/src/main/java/cn/code/nl/framework/execute/core/TaskFactory.java +++ b/nl-framework/nl-spring-boot-starter-execute/src/main/java/cn/code/nl/framework/execute/core/TaskFactory.java @@ -1,37 +1,31 @@ package cn.code.nl.framework.execute.core; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.stereotype.Component; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** - * * @Author: liyongde * @Date: 2026/7/15 15:32 */ @Component public class TaskFactory implements BeanPostProcessor { - private final Map taskMap; - @Autowired - public TaskFactory() { - taskMap = new HashMap<>(); - } + private final Map taskMap = new ConcurrentHashMap<>(); @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof AbstractTask) { - taskMap.put(beanName, (AbstractTask) bean); + if (bean instanceof AbstractTask task) { + taskMap.put(beanName, task); } return bean; } public AbstractTask getTask(String handleCode) { - if (handleCode == null) { + if (handleCode == null || handleCode.isBlank()) { return null; } return taskMap.get(handleCode); diff --git a/nl-module-system/nl-module-system-server/src/main/java/cn/code/nl/module/system/controller/admin/oauth2/OAuth2OpenController.http b/nl-module-system/nl-module-system-server/src/main/java/cn/code/nl/module/system/controller/admin/oauth2/OAuth2OpenController.http index cdcebbfb..11292c83 100644 --- a/nl-module-system/nl-module-system-server/src/main/java/cn/code/nl/module/system/controller/admin/oauth2/OAuth2OpenController.http +++ b/nl-module-system/nl-module-system-server/src/main/java/cn/code/nl/module/system/controller/admin/oauth2/OAuth2OpenController.http @@ -60,3 +60,24 @@ tenant-id: {{adminTenantId}} POST {{baseUrl}}/system/oauth2/check-token?token=620d307c5b4148df8a98dd6c6c547106 Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== tenant-id: {{adminTenantId}} + + + +@host = http://localhost:48080 +@clientId = huachuang-acs +@clientSecret = 1001open + +### 1、客户端模式 client_credentials(你最开始curl对应的请求) +POST http://localhost:48080/system/oauth2/token +Authorization: Bearer 32a8c752af4947d392809f954e32fadf +Content-Type: application/x-www-form-urlencoded + + +### +POST http://localhost:48080/system/oauth2/token +Authorization: Basic default admin123 +Content-Type: application/x-www-form-urlencoded + +grant_type = client_credentials + +### \ No newline at end of file diff --git a/nl-module-system/nl-module-system-server/src/main/java/cn/code/nl/module/system/framework/security/config/SecurityConfiguration.java b/nl-module-system/nl-module-system-server/src/main/java/cn/code/nl/module/system/framework/security/config/SecurityConfiguration.java index 4ac4c0b9..b767699d 100644 --- a/nl-module-system/nl-module-system-server/src/main/java/cn/code/nl/module/system/framework/security/config/SecurityConfiguration.java +++ b/nl-module-system/nl-module-system-server/src/main/java/cn/code/nl/module/system/framework/security/config/SecurityConfiguration.java @@ -32,6 +32,8 @@ public class SecurityConfiguration { .requestMatchers("/actuator/**").permitAll(); // RPC 服务的安全配置 registry.requestMatchers(ApiConstants.PREFIX + "/**").permitAll(); + // OAuth2 开放接口,无需登录即可访问 + registry.requestMatchers("/admin-api/system/oauth2/**").permitAll(); } }; diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/mq/message/TaskEventMessage.java b/nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/message/TaskEventMessage.java similarity index 67% rename from nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/mq/message/TaskEventMessage.java rename to nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/message/TaskEventMessage.java index 63ffe8a1..5d7752bc 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/mq/message/TaskEventMessage.java +++ b/nl-module-task/nl-module-task-api/src/main/java/cn/code/nl/module/task/message/TaskEventMessage.java @@ -1,4 +1,4 @@ -package cn.code.nl.module.task.mq.message; +package cn.code.nl.module.task.message; import lombok.Data; @@ -10,10 +10,15 @@ import java.util.Map; @Data public class TaskEventMessage { + /** 任务完成 */ + public static final String EVENT_TYPE_FINISHED = "TASK_FINISHED"; + /** 任务取消 */ + public static final String EVENT_TYPE_CANCELLED = "TASK_CANCELLED"; + /** 事件ID */ private String eventId; - /** 事件类型:TASK_FINISHED / TASK_CANCELLED */ + /** 事件类型,取值:{@link #EVENT_TYPE_FINISHED} / {@link #EVENT_TYPE_CANCELLED} */ private String eventType; /** 任务ID */ diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperationManager.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperationManager.java index e0c3785f..bba4c42d 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperationManager.java +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/manage/TransportTaskOperationManager.java @@ -9,7 +9,7 @@ import cn.code.nl.module.task.enums.FinishedTypeEnum; import cn.code.nl.module.task.enums.TaskEventTypeEnum; import cn.code.nl.module.task.enums.TaskOperationTypeEnum; import cn.code.nl.module.task.enums.TransportTaskStatusEnum; -import cn.code.nl.module.task.mq.message.TaskEventMessage; +import cn.code.nl.module.task.message.TaskEventMessage; import cn.code.nl.module.task.mq.producer.TaskEventProducer; import cn.hutool.core.util.StrUtil; import jakarta.annotation.PostConstruct; diff --git a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/mq/producer/TaskEventProducer.java b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/mq/producer/TaskEventProducer.java index 4168d271..2c5dae74 100644 --- a/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/mq/producer/TaskEventProducer.java +++ b/nl-module-task/nl-module-task-server/src/main/java/cn/code/nl/module/task/mq/producer/TaskEventProducer.java @@ -1,6 +1,6 @@ package cn.code.nl.module.task.mq.producer; -import cn.code.nl.module.task.mq.message.TaskEventMessage; +import cn.code.nl.module.task.message.TaskEventMessage; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.core.RocketMQTemplate; import org.springframework.stereotype.Component; diff --git a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java index e39fe317..39163471 100644 --- a/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java +++ b/nl-module-wms/nl-module-wms-api/src/main/java/cn/code/nl/module/wms/enums/ErrorCodeConstants.java @@ -8,4 +8,10 @@ public interface ErrorCodeConstants { ErrorCode SECT_ATTR_NOT_EXISTS = new ErrorCode(2, "库区属性不存在"); ErrorCode STRUC_ATTR_NOT_EXISTS = new ErrorCode(3, "仓位属性不存在"); + + + // ================ 仓储策略相关错误码 ================= + ErrorCode WAREHOUSE_STRATEGY_CONFIG_NOT_EXISTS = new ErrorCode(6_000_1, "仓储策略配置不存在"); + + ErrorCode WAREHOUSE_STRATEGY_NOT_EXISTS = new ErrorCode(6_000_2, "出入库策略不存在"); } diff --git a/nl-module-wms/nl-module-wms-server/pom.xml b/nl-module-wms/nl-module-wms-server/pom.xml index 7ed07635..ef12b750 100644 --- a/nl-module-wms/nl-module-wms-server/pom.xml +++ b/nl-module-wms/nl-module-wms-server/pom.xml @@ -32,6 +32,16 @@ nl-module-wms-api ${revision} + + cn.nl.cloud + nl-module-system-api + ${revision} + + + cn.nl.cloud + nl-module-task-api + ${revision} + diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/WarehouseStrategyController.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/WarehouseStrategyController.java new file mode 100644 index 00000000..6256d310 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/WarehouseStrategyController.java @@ -0,0 +1,106 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategy; + +import org.dromara.core.trans.anno.TransMethodResult; +import org.springframework.web.bind.annotation.*; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import jakarta.validation.constraints.*; +import jakarta.validation.*; +import jakarta.servlet.http.*; +import java.util.*; +import java.io.IOException; + +import cn.code.nl.framework.common.pojo.PageParam; +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.common.pojo.CommonResult; +import cn.code.nl.framework.common.util.object.BeanUtils; +import static cn.code.nl.framework.common.pojo.CommonResult.success; + +import cn.code.nl.framework.excel.core.util.ExcelUtils; + +import cn.code.nl.framework.apilog.core.annotation.ApiAccessLog; +import static cn.code.nl.framework.apilog.core.enums.OperateTypeEnum.*; + +import cn.code.nl.module.wms.controller.admin.warehousestrategy.vo.*; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategy.WarehouseStrategyDO; +import cn.code.nl.module.wms.service.warehousestrategy.WarehouseStrategyService; + +@Tag(name = "管理后台 - 出入库策略") +@RestController +@RequestMapping("/wms/warehouse-strategy") +@Validated +public class WarehouseStrategyController { + + @Resource + private WarehouseStrategyService warehouseStrategyService; + + @PostMapping("/create") + @Operation(summary = "创建出入库策略") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy:create')") + public CommonResult createWarehouseStrategy(@Valid @RequestBody WarehouseStrategySaveReqVO createReqVO) { + return success(warehouseStrategyService.createWarehouseStrategy(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新出入库策略") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy:update')") + public CommonResult updateWarehouseStrategy(@Valid @RequestBody WarehouseStrategySaveReqVO updateReqVO) { + warehouseStrategyService.updateWarehouseStrategy(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除出入库策略") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy:delete')") + public CommonResult deleteWarehouseStrategy(@RequestParam("id") Long id) { + warehouseStrategyService.deleteWarehouseStrategy(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Parameter(name = "ids", description = "编号", required = true) + @Operation(summary = "批量删除出入库策略") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy:delete')") + public CommonResult deleteWarehouseStrategyList(@RequestParam("ids") List ids) { + warehouseStrategyService.deleteWarehouseStrategyListByIds(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得出入库策略") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy:query')") + public CommonResult getWarehouseStrategy(@RequestParam("id") Long id) { + WarehouseStrategyDO warehouseStrategy = warehouseStrategyService.getWarehouseStrategy(id); + return success(BeanUtils.toBean(warehouseStrategy, WarehouseStrategyRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得出入库策略分页") + @TransMethodResult + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy:query')") + public CommonResult> getWarehouseStrategyPage(@Valid WarehouseStrategyPageReqVO pageReqVO) { + PageResult pageResult = warehouseStrategyService.getWarehouseStrategyPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, WarehouseStrategyRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出出入库策略 Excel") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportWarehouseStrategyExcel(@Valid WarehouseStrategyPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = warehouseStrategyService.getWarehouseStrategyPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "出入库策略.xls", "数据", WarehouseStrategyRespVO.class, + BeanUtils.toBean(list, WarehouseStrategyRespVO.class)); + } + +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategyPageReqVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategyPageReqVO.java new file mode 100644 index 00000000..516ae950 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategyPageReqVO.java @@ -0,0 +1,29 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategy.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.code.nl.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.code.nl.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 出入库策略分页 Request VO") +@Data +public class WarehouseStrategyPageReqVO extends PageParam { + + @Schema(description = "库区编码") + private String sectionCode; + + @Schema(description = "规则") + private String strategy; + + @Schema(description = "策略类型", example = "1入库2出库") + private String strategyType; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategyRespVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategyRespVO.java new file mode 100644 index 00000000..29361087 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategyRespVO.java @@ -0,0 +1,58 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategy.vo; + +import cn.code.nl.module.system.api.user.AdminUserApi; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; + +import org.dromara.core.trans.anno.Trans; +import org.dromara.core.trans.constant.TransType; +import org.dromara.core.trans.vo.VO; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import cn.idev.excel.annotation.*; + +@Schema(description = "管理后台 - 出入库策略 Response VO") +@Data +@ExcelIgnoreUnannotated +public class WarehouseStrategyRespVO implements VO { + + private Long id; + + @Schema(description = "库区编码", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("库区编码") + private String sectionCode; + + @Schema(description = "规则", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("规则") + private String strategy; + + @Schema(description = "策略类型", example = "1入库2出库") + @ExcelProperty("策略类型") + private String strategyType; + + @Schema(description = "描述") + @ExcelProperty("描述") + private String description; + + @Schema(description = "创建者") + @ExcelProperty("创建者") + @Trans(type = TransType.AUTO_TRANS, key = AdminUserApi.PREFIX, fields = "nickname", ref = "creatorName") + private String creator; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "创建者名称") + private String creatorName; + + @Schema(description = "更新者", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新者") + private String updater; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + private LocalDateTime updateTime; + +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategySaveReqVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategySaveReqVO.java new file mode 100644 index 00000000..4fe67703 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategy/vo/WarehouseStrategySaveReqVO.java @@ -0,0 +1,28 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategy.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - 出入库策略新增/修改 Request VO") +@Data +public class WarehouseStrategySaveReqVO { + + private Long id; + + @Schema(description = "库区编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "库区编码不能为空") + private String sectionCode; + + @Schema(description = "规则", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "规则不能为空") + private String strategy; + + @Schema(description = "策略类型", example = "1入库2出库") + private String strategyType; + + @Schema(description = "描述") + private String description; + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/WarehouseStrategyConfigController.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/WarehouseStrategyConfigController.java new file mode 100644 index 00000000..0a9c998e --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/WarehouseStrategyConfigController.java @@ -0,0 +1,112 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategyconfig; + +import org.springframework.web.bind.annotation.*; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import jakarta.validation.constraints.*; +import jakarta.validation.*; +import jakarta.servlet.http.*; +import java.util.*; +import java.io.IOException; + +import cn.code.nl.framework.common.pojo.PageParam; +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.common.pojo.CommonResult; +import cn.code.nl.framework.common.util.object.BeanUtils; +import static cn.code.nl.framework.common.pojo.CommonResult.success; + +import cn.code.nl.framework.excel.core.util.ExcelUtils; + +import cn.code.nl.framework.apilog.core.annotation.ApiAccessLog; +import static cn.code.nl.framework.apilog.core.enums.OperateTypeEnum.*; + +import cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo.*; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategyconfig.WarehouseStrategyConfigDO; +import cn.code.nl.module.wms.service.warehousestrategyconfig.WarehouseStrategyConfigService; + +@Tag(name = "管理后台 - 仓储策略配置") +@RestController +@RequestMapping("/wms/warehouse-strategy-config") +@Validated +public class WarehouseStrategyConfigController { + + @Resource + private WarehouseStrategyConfigService warehouseStrategyConfigService; + + @PostMapping("/create") + @Operation(summary = "创建仓储策略配置") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy-config:create')") + public CommonResult createWarehouseStrategyConfig(@Valid @RequestBody WarehouseStrategyConfigSaveReqVO createReqVO) { + return success(warehouseStrategyConfigService.createWarehouseStrategyConfig(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新仓储策略配置") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy-config:update')") + public CommonResult updateWarehouseStrategyConfig(@Valid @RequestBody WarehouseStrategyConfigSaveReqVO updateReqVO) { + warehouseStrategyConfigService.updateWarehouseStrategyConfig(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除仓储策略配置") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy-config:delete')") + public CommonResult deleteWarehouseStrategyConfig(@RequestParam("id") Long id) { + warehouseStrategyConfigService.deleteWarehouseStrategyConfig(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Parameter(name = "ids", description = "编号", required = true) + @Operation(summary = "批量删除仓储策略配置") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy-config:delete')") + public CommonResult deleteWarehouseStrategyConfigList(@RequestParam("ids") List ids) { + warehouseStrategyConfigService.deleteWarehouseStrategyConfigListByIds(ids); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得仓储策略配置") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy-config:query')") + public CommonResult getWarehouseStrategyConfig(@RequestParam("id") Long id) { + WarehouseStrategyConfigDO warehouseStrategyConfig = warehouseStrategyConfigService.getWarehouseStrategyConfig(id); + return success(BeanUtils.toBean(warehouseStrategyConfig, WarehouseStrategyConfigRespVO.class)); + } + + @GetMapping("/page") + @Operation(summary = "获得仓储策略配置分页") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy-config:query')") + public CommonResult> getWarehouseStrategyConfigPage(@Valid WarehouseStrategyConfigPageReqVO pageReqVO) { + PageResult pageResult = warehouseStrategyConfigService.getWarehouseStrategyConfigPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, WarehouseStrategyConfigRespVO.class)); + } + + @GetMapping(value = {"/list-all-simple", "/simple-list"}) + @Operation(summary = "获得仓储策略配置精简列表") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy-config:query')") + public CommonResult> getSimpleWarehouseStrategyConfigList() { + List list = warehouseStrategyConfigService.getWarehouseStrategyConfigList(); + return success(BeanUtils.toBean(list, WarehouseStrategyConfigSimpleRespVO.class)); + } + + @GetMapping("/export-excel") + @Operation(summary = "导出仓储策略配置 Excel") + @PreAuthorize("@ss.hasPermission('wms:warehouse-strategy-config:export')") + @ApiAccessLog(operateType = EXPORT) + public void exportWarehouseStrategyConfigExcel(@Valid WarehouseStrategyConfigPageReqVO pageReqVO, + HttpServletResponse response) throws IOException { + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + List list = warehouseStrategyConfigService.getWarehouseStrategyConfigPage(pageReqVO).getList(); + // 导出 Excel + ExcelUtils.write(response, "仓储策略配置.xls", "数据", WarehouseStrategyConfigRespVO.class, + BeanUtils.toBean(list, WarehouseStrategyConfigRespVO.class)); + } + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigPageReqVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigPageReqVO.java new file mode 100644 index 00000000..18cb327f --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigPageReqVO.java @@ -0,0 +1,47 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo; + +import lombok.*; +import java.util.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.code.nl.framework.common.pojo.PageParam; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; + +import static cn.code.nl.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 仓储策略配置分页 Request VO") +@Data +public class WarehouseStrategyConfigPageReqVO extends PageParam { + + @Schema(description = "策略编码") + private String strategyCode; + + @Schema(description = "策略名称", example = "赵六") + private String strategyName; + + @Schema(description = "策略类型", example = "1") + private String strategyType; + + @Schema(description = "类处理类型", example = "2") + private String classType; + + @Schema(description = "处理类") + private String param; + + @Schema(description = "描述", example = "你说的对") + private String remark; + + @Schema(description = "是否启用") + private Boolean isUsed; + + @Schema(description = "禁止操作") + private Boolean ban; + + @Schema(description = "限定参数") + private String formData; + + @Schema(description = "创建时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigRespVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigRespVO.java new file mode 100644 index 00000000..e0aa413a --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigRespVO.java @@ -0,0 +1,69 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDateTime; +import cn.idev.excel.annotation.*; + +@Schema(description = "管理后台 - 仓储策略配置 Response VO") +@Data +@ExcelIgnoreUnannotated +public class WarehouseStrategyConfigRespVO { + + private String id; + + @Schema(description = "策略编码", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("策略编码") + private String strategyCode; + + @Schema(description = "策略名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @ExcelProperty("策略名称") + private String strategyName; + + @Schema(description = "策略类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @ExcelProperty("策略类型") + private String strategyType; + + @Schema(description = "类处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @ExcelProperty("类处理类型") + private String classType; + + @Schema(description = "处理类", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("处理类") + private String param; + + @Schema(description = "描述", example = "你说的对") + @ExcelProperty("描述") + private String remark; + + @Schema(description = "是否启用") + @ExcelProperty("是否启用") + private Boolean isUsed; + + @Schema(description = "禁止操作") + @ExcelProperty("禁止操作") + private Boolean ban; + + @Schema(description = "限定参数") + @ExcelProperty("限定参数") + private String formData; + + @Schema(description = "创建者") + @ExcelProperty("创建者") + private String creator; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新者") + @ExcelProperty("更新者") + private String updater; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + private LocalDateTime updateTime; + +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigSaveReqVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigSaveReqVO.java new file mode 100644 index 00000000..ee4ef087 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigSaveReqVO.java @@ -0,0 +1,46 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; +import java.util.*; +import jakarta.validation.constraints.*; + +@Schema(description = "管理后台 - 仓储策略配置新增/修改 Request VO") +@Data +public class WarehouseStrategyConfigSaveReqVO { + + private Long id; + + @Schema(description = "策略编码", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "策略编码不能为空") + private String strategyCode; + + @Schema(description = "策略名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "赵六") + @NotEmpty(message = "策略名称不能为空") + private String strategyName; + + @Schema(description = "策略类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "策略类型不能为空") + private String strategyType; + + @Schema(description = "类处理类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotEmpty(message = "类处理类型不能为空") + private String classType; + + @Schema(description = "处理类", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "处理类不能为空") + private String param; + + @Schema(description = "描述", example = "你说的对") + private String remark; + + @Schema(description = "是否启用") + private Boolean isUsed; + + @Schema(description = "禁止操作") + private Boolean ban; + + @Schema(description = "限定参数") + private String formData; + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigSimpleRespVO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigSimpleRespVO.java new file mode 100644 index 00000000..b119205b --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/controller/admin/warehousestrategyconfig/vo/WarehouseStrategyConfigSimpleRespVO.java @@ -0,0 +1,16 @@ +package cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - 仓储策略配置精简 Response VO") +@Data +public class WarehouseStrategyConfigSimpleRespVO { + + @Schema(description = "策略编码", requiredMode = Schema.RequiredMode.REQUIRED) + private String strategyCode; + + @Schema(description = "策略名称", requiredMode = Schema.RequiredMode.REQUIRED) + private String strategyName; + +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/warehousestrategy/WarehouseStrategyDO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/warehousestrategy/WarehouseStrategyDO.java new file mode 100644 index 00000000..4aeebf02 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/warehousestrategy/WarehouseStrategyDO.java @@ -0,0 +1,48 @@ +package cn.code.nl.module.wms.dal.dataobject.warehousestrategy; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.code.nl.framework.mybatis.core.dataobject.BaseDO; + +/** + * 出入库策略 DO + * + * @author 诺力管理员 + */ +@TableName("wms_warehouse_strategy") +@KeySequence("wms_warehouse_strategy_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WarehouseStrategyDO extends BaseDO { + + /** + * id + */ + @TableId + private Long id; + /** + * 库区编码 + */ + private String sectionCode; + /** + * 规则 + */ + private String strategy; + /** + * 策略类型 + */ + private String strategyType; + /** + * 描述 + */ + private String description; + + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/warehousestrategyconfig/WarehouseStrategyConfigDO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/warehousestrategyconfig/WarehouseStrategyConfigDO.java new file mode 100644 index 00000000..057619ec --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/dataobject/warehousestrategyconfig/WarehouseStrategyConfigDO.java @@ -0,0 +1,67 @@ +package cn.code.nl.module.wms.dal.dataobject.warehousestrategyconfig; + +import lombok.*; +import java.util.*; +import java.time.LocalDateTime; +import java.time.LocalDateTime; +import com.baomidou.mybatisplus.annotation.*; +import cn.code.nl.framework.mybatis.core.dataobject.BaseDO; + +/** + * 仓储策略配置 DO + * + * @author 诺力管理员 + */ +@TableName("wms_warehouse_strategy_config") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class WarehouseStrategyConfigDO extends BaseDO { + + /** + * 策略标识 + */ + @TableId + private Long id; + /** + * 策略编码 + */ + private String strategyCode; + /** + * 策略名称 + */ + private String strategyName; + /** + * 策略类型 + */ + private String strategyType; + /** + * 类处理类型 + */ + private String classType; + /** + * 处理类 + */ + private String param; + /** + * 描述 + */ + private String remark; + /** + * 是否启用 + */ + private Boolean isUsed; + /** + * 禁止操作 + */ + private Boolean ban; + /** + * 限定参数 + */ + private String formData; + + +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/warehousestrategy/WarehouseStrategyMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/warehousestrategy/WarehouseStrategyMapper.java new file mode 100644 index 00000000..1136d163 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/warehousestrategy/WarehouseStrategyMapper.java @@ -0,0 +1,29 @@ +package cn.code.nl.module.wms.dal.mysql.warehousestrategy; + +import java.util.*; + +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategy.WarehouseStrategyDO; +import org.apache.ibatis.annotations.Mapper; +import cn.code.nl.module.wms.controller.admin.warehousestrategy.vo.*; + +/** + * 出入库策略 Mapper + * + * @author 诺力管理员 + */ +@Mapper +public interface WarehouseStrategyMapper extends BaseMapperX { + + default PageResult selectPage(WarehouseStrategyPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(WarehouseStrategyDO::getSectionCode, reqVO.getSectionCode()) + .eqIfPresent(WarehouseStrategyDO::getStrategy, reqVO.getStrategy()) + .eqIfPresent(WarehouseStrategyDO::getStrategyType, reqVO.getStrategyType()) + .betweenIfPresent(WarehouseStrategyDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(WarehouseStrategyDO::getId)); + } + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/warehousestrategyconfig/WarehouseStrategyConfigMapper.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/warehousestrategyconfig/WarehouseStrategyConfigMapper.java new file mode 100644 index 00000000..61666e49 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/dal/mysql/warehousestrategyconfig/WarehouseStrategyConfigMapper.java @@ -0,0 +1,41 @@ +package cn.code.nl.module.wms.dal.mysql.warehousestrategyconfig; + +import java.util.*; + +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.code.nl.framework.mybatis.core.mapper.BaseMapperX; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategyconfig.WarehouseStrategyConfigDO; +import org.apache.ibatis.annotations.Mapper; +import cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo.*; + +/** + * 仓储策略配置 Mapper + * + * @author 诺力管理员 + */ +@Mapper +public interface WarehouseStrategyConfigMapper extends BaseMapperX { + + default List selectList() { + return selectList(new LambdaQueryWrapperX() + .eq(WarehouseStrategyConfigDO::getIsUsed, true) + .orderByDesc(WarehouseStrategyConfigDO::getId)); + } + + default PageResult selectPage(WarehouseStrategyConfigPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(WarehouseStrategyConfigDO::getStrategyCode, reqVO.getStrategyCode()) + .likeIfPresent(WarehouseStrategyConfigDO::getStrategyName, reqVO.getStrategyName()) + .eqIfPresent(WarehouseStrategyConfigDO::getStrategyType, reqVO.getStrategyType()) + .eqIfPresent(WarehouseStrategyConfigDO::getClassType, reqVO.getClassType()) + .eqIfPresent(WarehouseStrategyConfigDO::getParam, reqVO.getParam()) + .eqIfPresent(WarehouseStrategyConfigDO::getRemark, reqVO.getRemark()) + .eqIfPresent(WarehouseStrategyConfigDO::getIsUsed, reqVO.getIsUsed()) + .eqIfPresent(WarehouseStrategyConfigDO::getBan, reqVO.getBan()) + .eqIfPresent(WarehouseStrategyConfigDO::getFormData, reqVO.getFormData()) + .betweenIfPresent(WarehouseStrategyConfigDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(WarehouseStrategyConfigDO::getId)); + } + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/StrategyTypeEnum.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/StrategyTypeEnum.java new file mode 100644 index 00000000..2bd7cd81 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/enums/StrategyTypeEnum.java @@ -0,0 +1,23 @@ +package cn.code.nl.module.wms.enums; + +import lombok.Getter; + +/** + * + * @Author: liyongde + * @Date: 2026/7/18 14:15 + */ +@Getter +public enum StrategyTypeEnum { + + INBOUND_STRATEGY("1", "入库策略"), + OUTBOUND_STRATEGY("2", "出库策略"); + + private final String code; + private final String name; + + StrategyTypeEnum(String code, String name) { + this.code = code; + this.name = name; + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/security/config/SecurityConfiguration.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/security/config/SecurityConfiguration.java index 20c30302..aa9fa217 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/security/config/SecurityConfiguration.java +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/framework/security/config/SecurityConfiguration.java @@ -11,7 +11,7 @@ import org.springframework.security.config.annotation.web.configurers.AuthorizeH * * @author zhouz */ -@Configuration(proxyBeanMethods = false, value = "baseSecurityConfiguration") +@Configuration(proxyBeanMethods = false, value = "wmsSecurityConfiguration") public class SecurityConfiguration { /* @@ -93,7 +93,7 @@ public class SecurityConfiguration { 3. 遵循了项目中每个模块定义各自安全配置的标准模式 * */ - @Bean("baseAuthorizeRequestsCustomizer") + @Bean("wmsAuthorizeRequestsCustomizer") public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() { return new AuthorizeRequestsCustomizer() { diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/DecisionManage.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/DecisionManage.java new file mode 100644 index 00000000..3b64a7dd --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/DecisionManage.java @@ -0,0 +1,72 @@ +package cn.code.nl.module.wms.manage; + +import cn.code.nl.framework.common.exception.ServiceException; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategyconfig.WarehouseStrategyConfigDO; +import cn.code.nl.module.wms.dal.mysql.warehousestrategyconfig.WarehouseStrategyConfigMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.jspecify.annotations.NonNull; +import org.springframework.beans.factory.BeanNameAware; +import org.springframework.beans.factory.InitializingBean; + +import java.util.List; + +/** + * 仓储策略决策器抽象基类 + *

+ * 子类通过 {@code @Service("策略编码")} 注册为 Spring Bean, + * 启动时根据 Bean 名称自动从数据库加载对应的策略配置。 + * + * @param 返回类型:货位/库存数据类型 + * @param

入参类型:决策参数类型 + * @Author: liyongde + * @Date: 2026/7/18 11:03 + */ +@Slf4j +public abstract class DecisionManage implements InitializingBean, BeanNameAware { + + /** + * 当前策略的数据库配置,子类通过此字段读取策略参数 + */ + public WarehouseStrategyConfigDO strategyConfig; + + /** + * Spring Bean 名称,即策略编码,对应 {@link WarehouseStrategyConfigDO#getStrategyCode()} + */ + private String beanName; + + @Resource + private WarehouseStrategyConfigMapper warehouseStrategyConfigMapper; + + /** + * 策略执行入口,由子类实现具体的货位分配逻辑 + * + * @param list 候选货位/库存列表 + * @param param 决策参数(物料信息、出入库类型等) + * @return 筛选/排序后的货位/库存列表 + */ + public abstract List handler(List list, P param); + + @Override + public void setBeanName(@NonNull String name) { + this.beanName = name; + } + + /** + * Spring Bean 初始化时自动加载策略配置 + *

+ * 通过 BeanNameAware 获取策略编码,从数据库查询对应的策略配置记录。 + * 若数据库中无对应记录则抛出异常阻止启动,避免运行时才发现配置缺失。 + */ + @Override + public void afterPropertiesSet() { + this.strategyConfig = warehouseStrategyConfigMapper.selectOne( + WarehouseStrategyConfigDO::getStrategyCode, beanName); + if (this.strategyConfig == null) { + log.error("策略 [{}] 初始化失败:数据库中未找到对应的策略配置记录", beanName); + throw new ServiceException(500, "启动失败:当前策略 " + beanName + " 没有实例信息"); + } + log.info("策略 [{}] 初始化成功,策略名称:{}", beanName, strategyConfig.getStrategyName()); + } + +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/StrategyChainExecutor.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/StrategyChainExecutor.java new file mode 100644 index 00000000..fcc0d0c9 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/StrategyChainExecutor.java @@ -0,0 +1,91 @@ +package cn.code.nl.module.wms.manage; + +import cn.code.nl.framework.common.exception.ServiceException; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategy.WarehouseStrategyDO; +import cn.code.nl.module.wms.dal.mysql.warehousestrategy.WarehouseStrategyMapper; +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSON; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 策略链执行器 + *

+ * 根据库区编码和策略类型加载策略链配置,从 Spring 容器中按名称精确获取策略处理器, + * 按序执行,每个处理器的输出作为下一个处理器的输入。 + * + * @Author: liyongde + * @Date: 2026/7/18 + */ +@Slf4j +@Component +public class StrategyChainExecutor { + + @Resource + private WarehouseStrategyMapper warehouseStrategyMapper; + + @Resource + private ApplicationContext applicationContext; + + /** + * 执行策略链 + * + * @param sectionCode 库区编码 + * @param strategyType 策略类型(1=入库,2=出库) + * @param candidates 初始候选列表,入库时为可用货位列表,出库时传空列表由首个策略自行查询 + * @param param 决策参数,类型由调用方决定(入库通常传 JSONObject,出库通常传强类型实体) + * @param 货位/库存数据类型 + * @param

决策参数类型,需与策略链中所有处理器的泛型参数一致 + * @return 策略链执行后的结果列表 + */ + @SuppressWarnings("unchecked") + public List execute(String sectionCode, String strategyType, List candidates, P param) { + // 1. 查询库区策略链配置 + WarehouseStrategyDO strategy = warehouseStrategyMapper.selectOne( + WarehouseStrategyDO::getSectionCode, sectionCode, + WarehouseStrategyDO::getStrategyType, strategyType); + if (strategy == null) { + throw new ServiceException(500, "当前库区 " + sectionCode + " 未配置策略链"); + } + + // 2. 解析策略编码列表 + List strategyCodes = JSON.parseArray(strategy.getStrategy(), String.class); + if (CollUtil.isEmpty(strategyCodes)) { + throw new ServiceException(500, "当前库区 " + sectionCode + " 策略链为空"); + } + + // 3. 按序执行策略链 + List result = candidates; + for (String strategyCode : strategyCodes) { + DecisionManage handler; + try { + handler = (DecisionManage) applicationContext.getBean(strategyCode); + } catch (Exception e) { + log.error("策略 [{}] 未找到对应的处理器 Bean", strategyCode, e); + throw new ServiceException(500, "策略 " + strategyCode + " 未注册"); + } + + String strategyName = handler.strategyConfig.getStrategyName(); + int inputSize = result != null ? result.size() : 0; + log.info("执行策略 [{}]:{},输入候选数量:{}", strategyCode, strategyName, inputSize); + + long startTime = System.currentTimeMillis(); + result = handler.handler(result, param); + long cost = System.currentTimeMillis() - startTime; + + int outputSize = result != null ? result.size() : 0; + log.info("策略 [{}] 执行完成,耗时:{}ms,输出数量:{}(过滤:{})", + strategyCode, cost, outputSize, inputSize - outputSize); + + if (CollUtil.isEmpty(result)) { + throw new ServiceException(500, "策略 " + strategyName + " 执行后无可用货位"); + } + } + return result; + } + +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/dto/AlleyAveHandleDTO.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/dto/AlleyAveHandleDTO.java new file mode 100644 index 00000000..bda64595 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/dto/AlleyAveHandleDTO.java @@ -0,0 +1,13 @@ +package cn.code.nl.module.wms.manage.dto; + +import lombok.Data; + +/** + * + * @Author: liyongde + * @Date: 2026/7/18 14:12 + */ +@Data +public class AlleyAveHandleDTO { + private String id; +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/dto/AlleyAveHandleParam.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/dto/AlleyAveHandleParam.java new file mode 100644 index 00000000..ce7ae23b --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/dto/AlleyAveHandleParam.java @@ -0,0 +1,12 @@ +package cn.code.nl.module.wms.manage.dto; + +import lombok.Data; + +/** + * + * @Author: liyongde + * @Date: 2026/7/18 14:12 + */ +@Data +public class AlleyAveHandleParam { +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/handle/base/AlleyAveRuleHandler.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/handle/base/AlleyAveRuleHandler.java new file mode 100644 index 00000000..550123cd --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/handle/base/AlleyAveRuleHandler.java @@ -0,0 +1,23 @@ +package cn.code.nl.module.wms.manage.handle.base; + +import cn.code.nl.module.wms.manage.DecisionManage; +import cn.code.nl.module.wms.manage.dto.AlleyAveHandleDTO; +import cn.code.nl.module.wms.manage.dto.AlleyAveHandleParam; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * + * @Author: liyongde + * @Date: 2026/7/18 13:39 + */ +@Slf4j +@Service("alleyAve") +public class AlleyAveRuleHandler extends DecisionManage { + @Override + public List handler(List list, AlleyAveHandleParam param) { + return List.of(); + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/handle/package-info.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/handle/package-info.java new file mode 100644 index 00000000..44ddb5b7 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/manage/handle/package-info.java @@ -0,0 +1,6 @@ +/** + * 所有出入库的处理器 + * @Author: liyongde + * @Date: 2026/7/18 14:11 + */ +package cn.code.nl.module.wms.manage.handle; \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/mq/consumer/TaskStatusChangeConsumer.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/mq/consumer/TaskStatusChangeConsumer.java new file mode 100644 index 00000000..57cbb940 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/mq/consumer/TaskStatusChangeConsumer.java @@ -0,0 +1,54 @@ +package cn.code.nl.module.wms.mq.consumer; + +import cn.code.nl.framework.execute.core.AbstractTask; +import cn.code.nl.framework.execute.core.TaskFactory; +import cn.code.nl.framework.execute.core.dto.TaskExecuteDTO; +import cn.code.nl.module.task.message.TaskEventMessage; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Component; + +/** + * 监听任务状态变更,根据 handleCode 定位任务子类并执行完成/取消逻辑 + * + * @Author: liyongde + * @Date: 2026/7/20 10:28 + */ +@Slf4j +@Component +@RocketMQMessageListener( + topic = "${rocketmq.consumer.wms-task-operate.topic}", + consumerGroup = "${rocketmq.consumer.wms-task-operate.group}" +) +public class TaskStatusChangeConsumer implements RocketMQListener { + + @Resource + private TaskFactory taskFactory; + + @Override + public void onMessage(TaskEventMessage message) { + String handleCode = message.getHandleCode(); + String eventType = message.getEventType(); + log.info("收到任务状态变更消息, handleCode={}, eventType={}, taskId={}", handleCode, eventType, message.getTaskId()); + + AbstractTask task = taskFactory.getTask(handleCode); + if (task == null) { + log.warn("未找到对应任务处理器, handleCode={}", handleCode); + return; + } + + TaskExecuteDTO dto = new TaskExecuteDTO(); + dto.setTaskId(message.getTaskId()); + dto.setPayload(message.getPayload()); + + if (TaskEventMessage.EVENT_TYPE_FINISHED.equals(eventType)) { + task.doHandleFinish(dto); + } else if (TaskEventMessage.EVENT_TYPE_CANCELLED.equals(eventType)) { + task.doHandleCancel(dto); + } else { + log.warn("未知事件类型, eventType={}, handleCode={}", eventType, handleCode); + } + } +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/mq/package-info.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/mq/package-info.java new file mode 100644 index 00000000..2ed129a3 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/mq/package-info.java @@ -0,0 +1,6 @@ +/** + * 消息队列 + * @Author: liyongde + * @Date: 2026/7/20 10:26 + */ +package cn.code.nl.module.wms.mq; \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategy/WarehouseStrategyService.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategy/WarehouseStrategyService.java new file mode 100644 index 00000000..44c5461c --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategy/WarehouseStrategyService.java @@ -0,0 +1,62 @@ +package cn.code.nl.module.wms.service.warehousestrategy; + +import java.util.*; +import jakarta.validation.*; +import cn.code.nl.module.wms.controller.admin.warehousestrategy.vo.*; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategy.WarehouseStrategyDO; +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.common.pojo.PageParam; + +/** + * 出入库策略 Service 接口 + * + * @author 诺力管理员 + */ +public interface WarehouseStrategyService { + + /** + * 创建出入库策略 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createWarehouseStrategy(@Valid WarehouseStrategySaveReqVO createReqVO); + + /** + * 更新出入库策略 + * + * @param updateReqVO 更新信息 + */ + void updateWarehouseStrategy(@Valid WarehouseStrategySaveReqVO updateReqVO); + + /** + * 删除出入库策略 + * + * @param id 编号 + */ + void deleteWarehouseStrategy(Long id); + + /** + * 批量删除出入库策略 + * + * @param ids 编号 + */ + void deleteWarehouseStrategyListByIds(List ids); + + /** + * 获得出入库策略 + * + * @param id 编号 + * @return 出入库策略 + */ + WarehouseStrategyDO getWarehouseStrategy(Long id); + + /** + * 获得出入库策略分页 + * + * @param pageReqVO 分页查询 + * @return 出入库策略分页 + */ + PageResult getWarehouseStrategyPage(WarehouseStrategyPageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategy/WarehouseStrategyServiceImpl.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategy/WarehouseStrategyServiceImpl.java new file mode 100644 index 00000000..d18c8d34 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategy/WarehouseStrategyServiceImpl.java @@ -0,0 +1,85 @@ +package cn.code.nl.module.wms.service.warehousestrategy; + +import cn.hutool.core.collection.CollUtil; +import org.springframework.stereotype.Service; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import cn.code.nl.module.wms.controller.admin.warehousestrategy.vo.*; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategy.WarehouseStrategyDO; +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.common.pojo.PageParam; +import cn.code.nl.framework.common.util.object.BeanUtils; + +import cn.code.nl.module.wms.dal.mysql.warehousestrategy.WarehouseStrategyMapper; + +import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.code.nl.framework.common.util.collection.CollectionUtils.convertList; +import static cn.code.nl.framework.common.util.collection.CollectionUtils.diffList; +import static cn.code.nl.module.wms.enums.ErrorCodeConstants.*; + +/** + * 出入库策略 Service 实现类 + * + * @author 诺力管理员 + */ +@Service +@Validated +public class WarehouseStrategyServiceImpl implements WarehouseStrategyService { + + @Resource + private WarehouseStrategyMapper warehouseStrategyMapper; + + @Override + public Long createWarehouseStrategy(WarehouseStrategySaveReqVO createReqVO) { + // 插入 + WarehouseStrategyDO warehouseStrategy = BeanUtils.toBean(createReqVO, WarehouseStrategyDO.class); + warehouseStrategyMapper.insert(warehouseStrategy); + + // 返回 + return warehouseStrategy.getId(); + } + + @Override + public void updateWarehouseStrategy(WarehouseStrategySaveReqVO updateReqVO) { + // 校验存在 + validateWarehouseStrategyExists(updateReqVO.getId()); + // 更新 + WarehouseStrategyDO updateObj = BeanUtils.toBean(updateReqVO, WarehouseStrategyDO.class); + warehouseStrategyMapper.updateById(updateObj); + } + + @Override + public void deleteWarehouseStrategy(Long id) { + // 校验存在 + validateWarehouseStrategyExists(id); + // 删除 + warehouseStrategyMapper.deleteById(id); + } + + @Override + public void deleteWarehouseStrategyListByIds(List ids) { + // 删除 + warehouseStrategyMapper.deleteByIds(ids); + } + + + private void validateWarehouseStrategyExists(Long id) { + if (warehouseStrategyMapper.selectById(id) == null) { + throw exception(WAREHOUSE_STRATEGY_NOT_EXISTS); + } + } + + @Override + public WarehouseStrategyDO getWarehouseStrategy(Long id) { + return warehouseStrategyMapper.selectById(id); + } + + @Override + public PageResult getWarehouseStrategyPage(WarehouseStrategyPageReqVO pageReqVO) { + return warehouseStrategyMapper.selectPage(pageReqVO); + } + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategyconfig/WarehouseStrategyConfigService.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategyconfig/WarehouseStrategyConfigService.java new file mode 100644 index 00000000..e720d0c6 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategyconfig/WarehouseStrategyConfigService.java @@ -0,0 +1,69 @@ +package cn.code.nl.module.wms.service.warehousestrategyconfig; + +import java.util.*; +import jakarta.validation.*; +import cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo.*; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategyconfig.WarehouseStrategyConfigDO; +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.common.pojo.PageParam; + +/** + * 仓储策略配置 Service 接口 + * + * @author 诺力管理员 + */ +public interface WarehouseStrategyConfigService { + + /** + * 创建仓储策略配置 + * + * @param createReqVO 创建信息 + * @return 编号 + */ + Long createWarehouseStrategyConfig(@Valid WarehouseStrategyConfigSaveReqVO createReqVO); + + /** + * 更新仓储策略配置 + * + * @param updateReqVO 更新信息 + */ + void updateWarehouseStrategyConfig(@Valid WarehouseStrategyConfigSaveReqVO updateReqVO); + + /** + * 删除仓储策略配置 + * + * @param id 编号 + */ + void deleteWarehouseStrategyConfig(Long id); + + /** + * 批量删除仓储策略配置 + * + * @param ids 编号 + */ + void deleteWarehouseStrategyConfigListByIds(List ids); + + /** + * 获得仓储策略配置 + * + * @param id 编号 + * @return 仓储策略配置 + */ + WarehouseStrategyConfigDO getWarehouseStrategyConfig(Long id); + + /** + * 获得仓储策略配置分页 + * + * @param pageReqVO 分页查询 + * @return 仓储策略配置分页 + */ + PageResult getWarehouseStrategyConfigPage(WarehouseStrategyConfigPageReqVO pageReqVO); + + /** + * 获得仓储策略配置列表(仅启用的) + * + * @return 仓储策略配置列表 + */ + List getWarehouseStrategyConfigList(); + +} \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategyconfig/WarehouseStrategyConfigServiceImpl.java b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategyconfig/WarehouseStrategyConfigServiceImpl.java new file mode 100644 index 00000000..4d3bf1a8 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/java/cn/code/nl/module/wms/service/warehousestrategyconfig/WarehouseStrategyConfigServiceImpl.java @@ -0,0 +1,85 @@ +package cn.code.nl.module.wms.service.warehousestrategyconfig; + +import cn.code.nl.framework.common.pojo.PageResult; +import cn.code.nl.framework.common.util.object.BeanUtils; +import cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo.WarehouseStrategyConfigPageReqVO; +import cn.code.nl.module.wms.controller.admin.warehousestrategyconfig.vo.WarehouseStrategyConfigSaveReqVO; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategyconfig.WarehouseStrategyConfigDO; +import cn.code.nl.module.wms.dal.mysql.warehousestrategyconfig.WarehouseStrategyConfigMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.List; + +import static cn.code.nl.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.code.nl.module.wms.enums.ErrorCodeConstants.WAREHOUSE_STRATEGY_CONFIG_NOT_EXISTS; + +/** + * 仓储策略配置 Service 实现类 + * + * @author 诺力管理员 + */ +@Service +@Validated +public class WarehouseStrategyConfigServiceImpl implements WarehouseStrategyConfigService { + + @Resource + private WarehouseStrategyConfigMapper warehouseStrategyConfigMapper; + + @Override + public Long createWarehouseStrategyConfig(WarehouseStrategyConfigSaveReqVO createReqVO) { + // 插入 + WarehouseStrategyConfigDO warehouseStrategyConfig = BeanUtils.toBean(createReqVO, WarehouseStrategyConfigDO.class); + warehouseStrategyConfigMapper.insert(warehouseStrategyConfig); + + // 返回 + return warehouseStrategyConfig.getId(); + } + + @Override + public void updateWarehouseStrategyConfig(WarehouseStrategyConfigSaveReqVO updateReqVO) { + // 校验存在 + validateWarehouseStrategyConfigExists(updateReqVO.getId()); + // 更新 + WarehouseStrategyConfigDO updateObj = BeanUtils.toBean(updateReqVO, WarehouseStrategyConfigDO.class); + warehouseStrategyConfigMapper.updateById(updateObj); + } + + @Override + public void deleteWarehouseStrategyConfig(Long id) { + // 校验存在 + validateWarehouseStrategyConfigExists(id); + // 删除 + warehouseStrategyConfigMapper.deleteById(id); + } + + @Override + public void deleteWarehouseStrategyConfigListByIds(List ids) { + // 删除 + warehouseStrategyConfigMapper.deleteByIds(ids); + } + + + private void validateWarehouseStrategyConfigExists(Long id) { + if (warehouseStrategyConfigMapper.selectById(id) == null) { + throw exception(WAREHOUSE_STRATEGY_CONFIG_NOT_EXISTS); + } + } + + @Override + public WarehouseStrategyConfigDO getWarehouseStrategyConfig(Long id) { + return warehouseStrategyConfigMapper.selectById(id); + } + + @Override + public PageResult getWarehouseStrategyConfigPage(WarehouseStrategyConfigPageReqVO pageReqVO) { + return warehouseStrategyConfigMapper.selectPage(pageReqVO); + } + + @Override + public List getWarehouseStrategyConfigList() { + return warehouseStrategyConfigMapper.selectList(); + } + +} diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/application-dev.yaml b/nl-module-wms/nl-module-wms-server/src/main/resources/application-dev.yaml index 2de3c2e3..6de07bb1 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/resources/application-dev.yaml +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/application-dev.yaml @@ -75,11 +75,6 @@ spring: # password: 123456 # 密码,建议生产环境开启 --- #################### MQ 消息队列相关配置 #################### - -# rocketmq 配置项,对应 RocketMQProperties 配置类 -rocketmq: - name-server: 127.0.0.1:9876 # RocketMQ Namesrv - spring: # RabbitMQ 配置项,对应 RabbitProperties 配置类 rabbitmq: diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/application-mq-dev.yaml b/nl-module-wms/nl-module-wms-server/src/main/resources/application-mq-dev.yaml new file mode 100644 index 00000000..1c2b8847 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/application-mq-dev.yaml @@ -0,0 +1,8 @@ +--- #################### MQ 消息队列相关配置 #################### +# rocketmq 配置项,对应 RocketMQProperties 配置类 +rocketmq: + name-server: 127.0.0.1:9876 + consumer: + wms-task-operate: + group: wms-task-status-change-dev-group + topic: wms-task-status-change-dev-topic \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/application-mq-test.yaml b/nl-module-wms/nl-module-wms-server/src/main/resources/application-mq-test.yaml new file mode 100644 index 00000000..91e76da5 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/application-mq-test.yaml @@ -0,0 +1,8 @@ +--- #################### MQ 消息队列相关配置 #################### +# rocketmq 配置项,对应 RocketMQProperties 配置类 +rocketmq: + name-server: 127.0.0.1:9876 + consumer: + wms-task-operate: + group: wms-task-status-change-test-group + topic: wms-task-status-change-test-topic \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/application.yaml b/nl-module-wms/nl-module-wms-server/src/main/resources/application.yaml index c5fb3505..a3e0c8e7 100644 --- a/nl-module-wms/nl-module-wms-server/src/main/resources/application.yaml +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/application.yaml @@ -13,6 +13,7 @@ spring: import: - optional:classpath:application-${spring.profiles.active}.yaml # 加载【本地】配置 - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml # 加载【Nacos】的配置 + - optional:classpath:application-mq-${spring.profiles.active}.yaml # 加载 MQ 配置 # Servlet 配置 servlet: diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/warehousestrategy/WarehouseStrategyMapper.xml b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/warehousestrategy/WarehouseStrategyMapper.xml new file mode 100644 index 00000000..e0158ff1 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/warehousestrategy/WarehouseStrategyMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/warehousestrategyconfig/WarehouseStrategyConfigMapper.xml b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/warehousestrategyconfig/WarehouseStrategyConfigMapper.xml new file mode 100644 index 00000000..eb9dd4a5 --- /dev/null +++ b/nl-module-wms/nl-module-wms-server/src/main/resources/mapper/warehousestrategyconfig/WarehouseStrategyConfigMapper.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/nl-server/src/main/resources/application-dev.yaml b/nl-server/src/main/resources/application-dev.yaml index 79fa9cf0..655920a7 100644 --- a/nl-server/src/main/resources/application-dev.yaml +++ b/nl-server/src/main/resources/application-dev.yaml @@ -47,12 +47,12 @@ spring: primary: master datasource: master: - url: jdbc:mysql://192.168.81.193:3306/huachuang_lms_dev?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + url: jdbc:mysql://192.168.81.193:3307/huachuang_lms_dev?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 username: root password: root123 slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改 lazy: true # 开启懒加载,保证启动速度 - url: jdbc:mysql://192.168.81.193:3306/huachuang_lms_dev?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 + url: jdbc:mysql://192.168.81.193:3307/huachuang_lms_dev?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例 username: root password: root123 diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/warehousestrategy/index.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/warehousestrategy/index.ts new file mode 100644 index 00000000..160512d6 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/warehousestrategy/index.ts @@ -0,0 +1,58 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace WmsWarehouseStrategyApi { + /** 出入库策略信息 */ + export interface WarehouseStrategy { + id?: number; + sectionCode?: string; // 库区编码 + strategy?: string; // 规则 + strategyType: string; // 策略类型 + description: string; // 描述 + } +} + +/** 查询出入库策略分页 */ +export function getWarehouseStrategyPage(params: PageParam) { + return requestClient.get>( + '/wms/warehouse-strategy/page', + { params }, + ); +} + +/** 查询出入库策略详情 */ +export function getWarehouseStrategy(id: number) { + return requestClient.get( + `/wms/warehouse-strategy/get?id=${id}`, + ); +} + +/** 新增出入库策略 */ +export function createWarehouseStrategy(data: WmsWarehouseStrategyApi.WarehouseStrategy) { + return requestClient.post('/wms/warehouse-strategy/create', data); +} + +/** 修改出入库策略 */ +export function updateWarehouseStrategy(data: WmsWarehouseStrategyApi.WarehouseStrategy) { + return requestClient.put('/wms/warehouse-strategy/update', data); +} + +/** 删除出入库策略 */ +export function deleteWarehouseStrategy(id: number) { + return requestClient.delete(`/wms/warehouse-strategy/delete?id=${id}`); +} + +/** 批量删除出入库策略 */ +export function deleteWarehouseStrategyList(ids: number[]) { + return requestClient.delete( + `/wms/warehouse-strategy/delete-list?ids=${ids.join(',')}`, + ); +} + +/** 导出出入库策略 */ +export function exportWarehouseStrategy(params: any) { + return requestClient.download('/wms/warehouse-strategy/export-excel', { params }); +} + + diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/warehousestrategyconfig/index.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/warehousestrategyconfig/index.ts new file mode 100644 index 00000000..b278044a --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/api/wms/warehousestrategyconfig/index.ts @@ -0,0 +1,76 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace WmsWarehouseStrategyConfigApi { + /** 仓储策略配置信息 */ + export interface WarehouseStrategyConfig { + id?: string; + strategyCode?: string; // 策略编码 + strategyName?: string; // 策略名称 + strategyType?: string; // 策略类型 + classType?: string; // 类处理类型 + param?: string; // 处理类 + remark: string; // 描述 + isUsed: boolean; // 是否启用 + ban: boolean; // 禁止操作 + formData: string; // 限定参数 + } + + /** 仓储策略配置精简信息 */ + export interface WarehouseStrategyConfigSimple { + strategyCode: string; // 策略编码 + strategyName: string; // 策略名称 + } +} + +/** 查询仓储策略配置分页 */ +export function getWarehouseStrategyConfigPage(params: PageParam) { + return requestClient.get>( + '/wms/warehouse-strategy-config/page', + { params }, + ); +} + +/** 查询仓储策略配置详情 */ +export function getWarehouseStrategyConfig(id: number) { + return requestClient.get( + `/wms/warehouse-strategy-config/get?id=${id}`, + ); +} + +/** 新增仓储策略配置 */ +export function createWarehouseStrategyConfig(data: WmsWarehouseStrategyConfigApi.WarehouseStrategyConfig) { + return requestClient.post('/wms/warehouse-strategy-config/create', data); +} + +/** 修改仓储策略配置 */ +export function updateWarehouseStrategyConfig(data: WmsWarehouseStrategyConfigApi.WarehouseStrategyConfig) { + return requestClient.put('/wms/warehouse-strategy-config/update', data); +} + +/** 删除仓储策略配置 */ +export function deleteWarehouseStrategyConfig(id: number) { + return requestClient.delete(`/wms/warehouse-strategy-config/delete?id=${id}`); +} + +/** 批量删除仓储策略配置 */ +export function deleteWarehouseStrategyConfigList(ids: number[]) { + return requestClient.delete( + `/wms/warehouse-strategy-config/delete-list?ids=${ids.join(',')}`, + ); +} + +/** 导出仓储策略配置 */ +export function exportWarehouseStrategyConfig(params: any) { + return requestClient.download('/wms/warehouse-strategy-config/export-excel', { params }); +} + +/** 查询仓储策略配置精简列表 */ +export function getWarehouseStrategyConfigSimpleList() { + return requestClient.get( + '/wms/warehouse-strategy-config/simple-list', + ); +} + + diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/data.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/data.ts new file mode 100644 index 00000000..72d38c84 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/data.ts @@ -0,0 +1,161 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { WmsWarehouseStrategyApi } from '#/api/wms/warehousestrategy'; + +import { ref } from 'vue'; + +import { getRangePickerDefaultProps } from '#/utils'; +import {getDictOptions} from "@vben/hooks"; +import {DICT_TYPE} from "@vben/constants"; + +/** 策略配置选项(供表单多选和列表显示使用) */ +export const strategyConfigOptions = ref<{ label: string; value: string }[]>([]); + +/** 策略编码 -> 名称的映射 */ +export const strategyNameMap = ref>({}); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'sectionCode', + label: '库区编码', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入库区编码', + }, + }, + { + fieldName: 'strategyType', + label: '策略类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.WMS_OUT_IN_STRATEGY_TYPE), + placeholder: '请选择策略类型', + }, + }, + { + fieldName: 'strategy', + label: '规则', + rules: 'required', + component: 'Select', + componentProps: { + mode: 'multiple', + options: strategyConfigOptions, + placeholder: '请选择规则', + }, + }, + { + fieldName: 'description', + label: '描述', + component: 'TextArea', + componentProps: { + placeholder: '请输入描述', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'sectionCode', + label: '库区编码', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入库区编码', + }, + }, + { + fieldName: 'strategy', + label: '规则', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入规则', + }, + }, + { + fieldName: 'strategyType', + label: '策略类型', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_STRATEGY_TYPE), + placeholder: '请选择策略类型', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'sectionCode', + title: '库区编码', + minWidth: 120, + }, + { + field: 'strategy', + title: '规则', + minWidth: 200, + slots: { default: 'strategy' }, + }, + { + field: 'strategyType', + title: '策略类型', + minWidth: 120, + }, + { + field: 'description', + title: '描述', + minWidth: 120, + }, + { + field: 'creatorName', + title: '创建者', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/index.vue b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/index.vue new file mode 100644 index 00000000..86cc0c26 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/index.vue @@ -0,0 +1,221 @@ + + + \ No newline at end of file diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/modules/form.vue b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/modules/form.vue new file mode 100644 index 00000000..eb9e0ff1 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategy/modules/form.vue @@ -0,0 +1,122 @@ + + + \ No newline at end of file diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/data.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/data.ts new file mode 100644 index 00000000..5246a55c --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/data.ts @@ -0,0 +1,282 @@ +import {type VbenFormSchema, z} from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { WmsWarehouseStrategyConfigApi } from '#/api/wms/warehousestrategyconfig'; + +import { getDictOptions } from '@vben/hooks'; + +import { getRangePickerDefaultProps } from '#/utils'; +import { CommonTrueOrFalseEnum, DICT_TYPE} from "@vben/constants"; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + const strategyTypeOptions = getDictOptions(DICT_TYPE.WMS_STRATEGY_TYPE, 'string'); + + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'strategyCode', + label: '策略编码', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入策略编码', + }, + }, + { + fieldName: 'strategyName', + label: '策略名称', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请输入策略名称', + }, + }, + { + fieldName: 'strategyType', + label: '策略类型', + rules: 'required', + component: 'Select', + componentProps: { + options: strategyTypeOptions, + placeholder: '请选择策略类型', + }, + defaultValue: strategyTypeOptions[0]?.value, + }, + { + fieldName: 'classType', + label: '类处理类型', + rules: 'required', + component: 'Input', + componentProps: { + placeholder: '请选择类处理类型', + }, + }, + { + fieldName: 'param', + label: '参数', + component: 'Input', + componentProps: { + placeholder: '请输入参数', + }, + }, + { + fieldName: 'remark', + label: '描述', + component: 'Input', + componentProps: { + placeholder: '请输入描述', + }, + }, + { + fieldName: 'isUsed', + label: '是否启用', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_TRUE_FALSE, 'boolean'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.boolean().default(CommonTrueOrFalseEnum.TRUE), + }, + { + fieldName: 'ban', + label: '禁止操作', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_TRUE_FALSE, 'boolean'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.boolean().default(CommonTrueOrFalseEnum.FALSE), + }, + { + fieldName: 'formData', + label: '限定参数', + component: 'Input', + componentProps: { + placeholder: '请输入限定参数', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'strategyCode', + label: '策略编码', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入策略编码', + }, + }, + { + fieldName: 'strategyName', + label: '策略名称', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入策略名称', + }, + }, + { + fieldName: 'strategyType', + label: '策略类型', + component: 'Select', + componentProps: { + allowClear: true, + options: getDictOptions(DICT_TYPE.WMS_STRATEGY_TYPE), + placeholder: '请选择策略类型', + }, + }, + { + fieldName: 'classType', + label: '类处理类型', + component: 'Select', + componentProps: { + allowClear: true, + options: [], + placeholder: '请选择类处理类型', + }, + }, + { + fieldName: 'param', + label: '处理类', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入处理类', + }, + }, + { + fieldName: 'remark', + label: '描述', + component: 'Input', + componentProps: { + allowClear: true, + placeholder: '请输入描述', + }, + }, + { + fieldName: 'isUsed', + label: '是否启用', + component: 'Select', + componentProps: { + allowClear: true, + options: [], + placeholder: '请选择是否启用', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'strategyCode', + title: '策略编码', + minWidth: 120, + }, + { + field: 'strategyName', + title: '策略名称', + minWidth: 120, + }, + { + field: 'strategyType', + title: '策略类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.WMS_STRATEGY_TYPE }, + }, + }, + { + field: 'classType', + title: '类处理类型', + minWidth: 120, + }, + { + field: 'param', + title: '处理类', + minWidth: 120, + }, + { + field: 'remark', + title: '描述', + minWidth: 120, + }, + { + field: 'isUsed', + title: '是否启用', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_TRUE_FALSE }, + }, + }, + { + field: 'ban', + title: '是否禁止操作', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_TRUE_FALSE }, + }, + }, + { + field: 'formData', + title: '限定参数', + minWidth: 120, + }, + { + field: 'creator', + title: '创建者', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + field: 'updater', + title: '更新者', + minWidth: 120, + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 120, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 200, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/index.vue b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/index.vue new file mode 100644 index 00000000..2407e804 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/index.vue @@ -0,0 +1,186 @@ + + + \ No newline at end of file diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/modules/form.vue b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/modules/form.vue new file mode 100644 index 00000000..16fe21a1 --- /dev/null +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/apps/web-antdv-next/src/views/wms/warehousestrategyconfig/modules/form.vue @@ -0,0 +1,82 @@ + + + \ No newline at end of file diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/biz-system-enum.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/biz-system-enum.ts index 93fb207b..35bc769f 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/biz-system-enum.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/biz-system-enum.ts @@ -5,6 +5,12 @@ export const CommonStatusEnum = { DISABLE: 1, // 禁用 }; +// true or false +export const CommonTrueOrFalseEnum = { + TRUE: true, + FALSE: false, +}; + // 全局用户类型枚举 export const UserTypeEnum = { MEMBER: 1, // 会员 diff --git a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts index 5a190779..918e1a49 100644 --- a/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts +++ b/nl-ui/nl-ui-admin-vben/yudao-ui-admin-vben/packages/constants/src/dict-enum.ts @@ -4,7 +4,8 @@ const COMMON_DICT = { COMMON_STATUS: 'common_status', TERMINAL: 'terminal', // 终端 DATE_INTERVAL: 'date_interval', // 数据间隔 - PRODUCT_AREA: 'product_area' + PRODUCT_AREA: 'product_area', + COMMON_TRUE_FALSE: 'common_true_false', } as const; /** ========== SYSTEM - 系统模块 ========== */ @@ -279,6 +280,8 @@ const WMS_DICT = { WMS_ORDER_STATUS: 'wms_order_status', // WMS 单据状态 WMS_RECEIPT_ORDER_TYPE: 'wms_receipt_order_type', // WMS 入库单类型 WMS_SHIPMENT_ORDER_TYPE: 'wms_shipment_order_type', // WMS 出库单类型 + WMS_STRATEGY_TYPE: 'wms_strategy_type', // WMS策略类型 + WMS_OUT_IN_STRATEGY_TYPE: 'wms_out_in_strategy_type', // WMS出入库策略类型 WMS_LOCK_TYPE: 'wms_lock_type', // WMS 锁定类型 } as const; diff --git a/决策管理模块-数据库表字段分析.md b/决策管理模块-数据库表字段分析.md new file mode 100644 index 00000000..076558de --- /dev/null +++ b/决策管理模块-数据库表字段分析.md @@ -0,0 +1,451 @@ +# decision_manage(决策管理)模块 — 数据库表字段详解 + +## 概述 + +`decision_manage` 模块涉及两张核心数据库表: + +- **`st_strategy_config`**(仓储策略配置表):定义每个具体策略的元信息和运行参数 +- **`st_sect_strategy`**(库区策略关联表):定义每个库区绑定了哪些策略,形成策略链 + +两张表通过 `strategy_code`(策略编码)字段产生逻辑关联——`st_sect_strategy.strategy` 中存储的 JSON 数组元素值,对应 `st_strategy_config.strategy_code`。 + +--- + +## 一、`st_strategy_config`(仓储策略配置表) + +**作用**:存储每一个可用策略的完整定义,包括策略的基本信息、分类、运行参数和启用状态。每个策略对应一个 Spring Bean(继承 `Decisioner` 的处理器),系统启动时通过 `strategy_code` 与 Bean 名称自动关联。 + +### 字段详解 + +#### 1. `id` — 策略标识(主键) + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | +| 主键策略 | 无自增,手动赋值 | +| 生成方式 | `IdUtil.getStringId()`(雪花ID或UUID变体) | + +**作用**:策略记录的唯一标识,由后端在新增时自动生成,前端不感知。`create` 方法中通过 `IdUtil.getStringId()` 赋值。 + +--- + +#### 2. `strategy_code` — 策略编码 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR(64)` | + +**作用**:策略的编码标识,是两张表之间的**核心关联字段**。每个策略编码与 Spring 容器中一个 `Decisioner` 子类的 `@Service("xxx")` Bean 名称严格一一对应。 + +**取值示例**: +| 策略编码 | 对应处理器 | 分类 | +|----------|-----------|------| +| `fifo` | FIFORuleHandler | base | +| `nearby` | NearbyRuleHandler | base | +| `weight` | WeightRuleHandler | base | +| `cluster` | ClusterRuleHandler | base | +| `limitStorage` | LimitStorageRuleHandler | base | +| `alleyAve` | AlleyAveRuleHandler | base | +| `depthPriority` | DepthPriorityHandler | diy | +| `fifo2` | FIFO2RuleHandler | diy | +| `inventory` | InventoryRuleHandler | diy | +| `passRCL` | PassRCLHandler | diy | + +**注意**:当前 `create` 方法中硬编码为 `"000"`,这意味着新增策略时需要在数据库中手动修正 `strategy_code` 的值,或在界面上提供修改入口。这是一个应改进的点。 + +**关联关系**:被 `st_sect_strategy.strategy` JSON 数组引用,也被 `Decisioner.afterPropertiesSet()` 用于查询自身配置。 + +--- + +#### 3. `strategy_name` — 策略名称 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:策略的中文显示名称,用于后台管理界面展示,如"先进先出"、"就近放置"、"巷道均衡"等。 + +**约束**:`create` 方法中会校验名称唯一性——如果已存在相同名称的策略,抛出 `BadRequestException("已存在相同名称的策略【xxx】")`。 + +**使用场景**:在 `GET /api/strategy/decisionColumns` 接口中作为下拉选项的 `label` 字段返回给前端。 + +--- + +#### 4. `strategy_type` — 策略类型 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:标识策略适用于哪种仓储操作场景。 + +**取值约定**: + +| 值 | 含义 | 典型策略 | +|----|------|----------| +| `1` | 入库策略 | limitStorage, nearby, alleyAve, weight, cluster, depthPriority, inventory | +| `2` | 出库策略 | fifo, fifo2 | +| `3` | 通用策略(出入库均可) | alleyAve(同时支持出入) | + +**使用场景**:上层调度模块根据当前任务是入库还是出库,筛选对应类型的策略进行链式调用。 + +--- + +#### 5. `class_type` — 类处理类型 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:标识策略实现类的归属分类,用于前端分组展示或权限控制。 + +**取值约定**: + +| 值 | 含义 | 说明 | +|----|------|------| +| `base` | 基础策略 | 通用性强的标准策略,如 FIFO、就近、限位、均衡等 | +| `diy` | 自定义策略 | 为特定项目/现场定制的策略,如深位优先、双叉分配等 | + +**代码中的实际使用**:当前在 Java 代码中未发现直接使用该字段做业务逻辑判断,主要用于界面上的分类筛选和展示。 + +--- + +#### 6. `param` — 策略参数 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR`(存储 JSON 字符串) | + +**作用**:策略运行时可配置的参数,以 JSON 字符串形式存储。这是**策略配置最核心的字段**,直接影响策略的执行行为。 + +**各策略的 param 格式与含义**: + +| 策略 | param 格式 | 含义 | +|------|-----------|------| +| alleyAve (巷道均衡) | `["x","z","y"]` | 排序优先级:先按排(x),再按层(z),最后按列(y) | +| limitStorage (货位限位) | `["h","w","l","weight"]` | 需要匹配的维度:高度、宽度、深度、承重 | +| passRCL (排/列/层过滤) | `{"y":[1,2,3,104,103,102]}` | 排除列号为 1,2,3,102,103,104 的货位 | +| nearby (就近放置) | 当前未使用 param | 使用硬编码的 Top10,可扩展为从 param 读取候选数量 | +| weight (轻上重下) | 通过 handler 参数传递 | 泛型声明为 String,实际解析为 JSON | + +**注意**:同一策略类可以在 `st_strategy_config` 中存在多条记录(不同 `strategy_code`),每条记录配置不同的 `param`,实现同一策略逻辑在不同场景下的差异化复用。例如不同库区可以有不同的排序维度。 + +--- + +#### 7. `form_data` — 策略表单配置 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `JSONObject` | +| 数据库类型 | `VARCHAR`(通过 `FastjsonSortTypeHandler` 序列化) | +| 默认值 | `new JSONObject()`(空 JSON 对象) | +| TypeHandler | `FastjsonSortTypeHandler.class` | + +**作用**:存储策略在前端表单中的渲染配置,例如表单字段定义、校验规则、下拉选项等。这是一个**纯前端辅助字段**,不参与后端决策逻辑。 + +**示例**:可能包含类似以下结构的数据(具体格式取决于前端表单设计器): +```json +{ + "fields": [ + {"name": "x", "label": "排", "type": "checkbox"}, + {"name": "y", "label": "列", "type": "checkbox"}, + {"name": "z", "label": "层", "type": "checkbox"} + ] +} +``` + +**与 `param` 的区别**: +- `param`:策略的**运行参数**,后端 `handler()` 方法中解析并使用 +- `form_data`:策略的**表单配置**,前端渲染配置界面时使用 + +--- + +#### 8. `remark` — 描述/备注 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:策略的补充说明文本,用于记录策略的用途、注意事项、变更历史等。纯展示字段,不参与业务逻辑。 + +--- + +#### 9. `is_used` — 是否启用 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `Boolean` | +| 数据库类型 | `TINYINT` 或 `BIT` | +| 默认值 | `null`(未设置,建议默认为 `true` 或 `false`) | + +**作用**:控制策略的启用/禁用状态。只有启用的策略才会在策略链中被调用。提供 `PUT /api/strategy/changeActive` 接口进行切换。 + +**切换逻辑**(`changeActive` 方法):每次调用将 `is_used` 值取反,实现一键启用/禁用切换。 + +**与 `ban` 的区别**:`is_used` 是实际在使用的启用状态控制字段;`ban` 字段在代码中未被使用,疑似冗余。 + +--- + +#### 10. `ban` — 是否禁用 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `Boolean` | +| 数据库类型 | `TINYINT` 或 `BIT` | + +**作用**:从命名推断用于禁用标记,但在当前 Java 代码中**未找到任何读写该字段的逻辑**。可能是早期设计预留的字段,后来被 `is_used` 替代,或者是数据库中有但代码中未同步使用。 + +**建议**:确认是否在 SQL 脚本或前端有使用,如果确认废弃则应清理。 + +--- + +#### 11. `update_name` — 修改人名称 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:记录最后一次修改该策略配置的操作人姓名。在 `update`、`deleteAll`、`changeActive` 方法中通过 `SecurityUtils.getCurrentNickName()` 自动赋值。 + +--- + +#### 12. `update_time` — 修改时间 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:记录最后一次修改的时间戳。在 `update`、`deleteAll`、`changeActive` 方法中通过 `DateUtil.now()` 自动赋值。 + +**注意**:使用 `String` 而非 `LocalDateTime` 或 `Date`,实际存储格式取决于 `DateUtil.now()` 的返回值(通常是 `yyyy-MM-dd HH:mm:ss`)。 + +--- + +#### 13. `is_delete` — 是否删除(逻辑删除标记) + +| 属性 | 说明 | +|------|------| +| Java 类型 | `Boolean` | +| 数据库类型 | `TINYINT` 或 `BIT` | +| 查询默认值 | `Boolean.FALSE`(在 `StrategyQuery` 中设置) | + +**作用**:逻辑删除标记。删除策略时不会物理删除记录,而是将该字段设为 `"1"`(注意代码中使用字符串 `"1"` 而非布尔值 `true`,存在类型不一致)。 + +**使用方式**: +- 查询时:Mapper XML 中硬编码过滤条件 `is_delete = "0"`,只查询未删除记录 +- 删除时:`deleteAll` 方法通过 `UpdateWrapper` 设置 `is_delete = "1"` + +**注意**:`StrategyQuery` 中虽然设置了 `is_delete = Boolean.FALSE` 作为默认参数,但 Mapper XML 中直接硬编码了 `is_delete = "0"`,Query 对象中的 `is_delete` 实际上没有在 SQL 中被引用。 + +--- + +## 二、`st_sect_strategy`(库区策略关联表) + +**作用**:定义每个库区与策略链的绑定关系。一个库区可以绑定多个策略,这些策略按数组顺序组成策略链,入库/出库时依次执行。 + +### 字段详解 + +#### 1. `id` — 主键 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `Integer` | +| 数据库类型 | `INT` | +| 主键策略 | `IdType.AUTO`(数据库自增) | + +**作用**:记录的唯一标识,由数据库自动生成。 + +--- + +#### 2. `sect_code` — 库区编码 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:标识该策略链配置属于哪个库区。库区是仓库物理空间的逻辑划分,每个库区有独立的编码(如 `A01`、`B02` 等)。 + +**使用场景**: +- 调度模块接到入库/出库任务时,根据目标库区编码查询该库区绑定的策略链 +- `SectStrategyQuery` 支持按 `sect_code` 过滤分页查询 + +**关联**:库区编码对应基础数据模块中库区/仓位主数据,本表不存储库区本身的详细信息。 + +--- + +#### 3. `strategy` — 策略编码列表(策略链) + +| 属性 | 说明 | +|------|------| +| Java 类型 | `List` | +| 数据库类型 | `VARCHAR`(存储 JSON 数组字符串) | +| TypeHandler | `ListStrTypeHandler.class` | + +**作用**:这是决策模块最核心的字段,定义了该库区的**策略链**——即入库/出库时依次执行的策略编码列表,**数组顺序即为执行顺序**。 + +**存储格式**(数据库中的实际内容): +```json +["limitStorage", "nearby", "alleyAve"] +``` + +**Java 中的表示**: +```java +List strategy = ["limitStorage", "nearby", "alleyAve"] +``` + +**执行流程**:当任务到达该库区时,调度模块会按数组顺序依次调用对应的策略处理器: + +``` +可用货位列表 + → limitStorage.handler() // 过滤尺寸/重量不匹配的货位 + → nearby.handler() // 按距离排序,取前N个 + → alleyAve.handler() // 巷道均衡分配 + → 最终货位列表 +``` + +**TypeHandler 转换机制**: + +- **写入时**(Java → DB):`ListStrTypeHandler.setNonNullParameter()` 调用 `JSON.toJSONString(parameter)`,将 `List` 序列化为 JSON 数组字符串 +- **读取时**(DB → Java):`ListTypeHandler.getNullableResult()` 调用 `JSONArray.parseArray(s, String.class)`,将 JSON 数组反序列化为 `List`;若为 `NULL` 则返回空 `ArrayList` + +**策略编码的取值**:列表中的每个元素必须是 `st_strategy_config.strategy_code` 的有效值,对应一个已注册的 Spring Bean。如果填写了不存在的策略编码,运行时才会报错。 + +--- + +#### 4. `strategy_type` — 策略类型 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:标识该条策略链配置适用于哪种操作场景。 + +**取值约定**: + +| 值 | 含义 | +|----|------| +| `1` | 入库策略链 | +| `2` | 出库策略链 | + +**使用场景**:一个库区可能同时需要入库和出库的策略链配置,通过该字段区分。例如: + +``` +库区 A01: + - strategy_type=1: ["limitStorage", "nearby", "alleyAve"] ← 入库用 + - strategy_type=2: ["fifo"] ← 出库用 +``` + +调度模块根据当前任务是入库还是出库,查询对应 `strategy_type` 的策略链。 + +--- + +#### 5. `description` — 描述 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:对该库区策略链配置的说明文本,用于后台管理界面的展示。纯文本备注,不参与业务逻辑。 + +--- + +#### 6. `update_time` — 更新时间 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:记录最后一次修改的时间。在 Controller 的 `create` 和 `update` 方法中通过 `DateUtil.now()` 手动赋值。 + +--- + +#### 7. `update_name` — 更新人 + +| 属性 | 说明 | +|------|------| +| Java 类型 | `String` | +| 数据库类型 | `VARCHAR` | + +**作用**:记录最后一次修改该库区策略配置的操作人。在 Controller 中通过 `SecurityUtils.getCurrentUsername()` 手动赋值。 + +--- + +## 三、两表关联关系 + +``` +st_strategy_config (策略定义) st_sect_strategy (库区-策略绑定) +┌──────────────────────────┐ ┌──────────────────────────┐ +│ id (PK) │ │ id (PK, AUTO) │ +│ strategy_code ◄──────────┼──────────│ strategy (JSON数组) │ +│ strategy_name │ 逻辑引用 │ sect_code │ +│ strategy_type │ │ strategy_type │ +│ class_type │ │ description │ +│ param │ │ update_time │ +│ form_data │ │ update_name │ +│ remark │ └──────────────────────────┘ +│ is_used │ +│ ban │ +│ update_name │ +│ update_time │ +│ is_delete │ +└──────────────────────────┘ +``` + +**关联方式**:`st_sect_strategy.strategy` 中的 JSON 数组元素(如 `"fifo"`)逻辑上引用 `st_strategy_config.strategy_code`。 + +**当前限制**:由于使用 JSON 数组存储,数据库层面无法建立外键约束。如果策略编码被修改或删除,库区策略链中的引用不会自动更新或校验。 + +**查询链路**: +1. 调度模块根据任务中的库区编码 `sect_code` + 策略类型 `strategy_type` 查询 `st_sect_strategy` +2. 从 `strategy` 字段解析出策略编码列表 +3. 按顺序从 Spring 容器中获取对应 Bean 名称的 `Decisioner` 实例 +4. 每个 `Decisioner` 在初始化时已通过 `strategy_code` 从 `st_strategy_config` 加载了自身的配置(`param`、`form_data` 等) + +--- + +## 四、字段补充说明 + +### 缺失的常见字段 + +两张表均**没有**明确声明以下常见审计字段(但代码中有隐式引用): + +| 缺失字段 | 说明 | +|----------|------| +| `create_time` | 创建时间 — 实体中未声明,但 `StStrategyConfigServiceImpl.pageQuery` 中使用了 `ORDER BY create_time DESC`,说明数据库表中实际存在该字段,只是 Java 实体未映射 | +| `create_name` | 创建人 — 新增方法中未赋值,可能依赖数据库默认值 | + +### 类型不一致问题 + +| 问题 | 位置 | 详情 | +|------|------|------| +| `is_delete` 赋值类型 | `StStrategyConfigServiceImpl.deleteAll()` | `Boolean` 字段被赋值为字符串 `"1"` | +| `update_time` 类型 | 两张表 | 使用 `String` 存储时间,而非 `LocalDateTime` | + +--- + +## 五、设计改进建议(与字段相关) + +1. **`strategy_code` 创建时硬编码 `"000"`**:`create` 方法未提供策略编码的输入入口,需要后续手动修改。建议在创建界面提供编码输入框,并增加编码唯一性校验。 + +2. **`ban` 字段废弃**:代码中未使用,确认后可清理。 + +3. **`is_delete` 赋值类型修复**:`deleteAll` 中应将 `"1"` 改为 `true`,保持与字段声明的 `Boolean` 类型一致。 + +4. **`update_time` 使用更合适的类型**:建议从 `String` 改为 `LocalDateTime`,利用 Java 8 时间 API 的类型安全优势。 + +5. **补充 `create_time` / `create_name` 映射**:数据库存在但 Java 实体未映射,导致新增记录时无法在实体层面追踪创建信息。 + +6. **`strategy` JSON 数组的引用完整性**:无法通过数据库约束保证 `st_sect_strategy.strategy` 中的策略编码在 `st_strategy_config` 中真实存在,建议在保存时增加后端校验逻辑。 diff --git a/决策管理模块-策略链执行器架构设计.md b/决策管理模块-策略链执行器架构设计.md new file mode 100644 index 00000000..90ac363d --- /dev/null +++ b/决策管理模块-策略链执行器架构设计.md @@ -0,0 +1,393 @@ +# 决策管理模块 — 策略链执行器架构设计 + +## 一、旧代码问题分析 + +### 1.1 当前 `outBoundSectDiv` / `inBoundSectDiv` 的代码 + +```java +// StructattrServiceImpl.java (旧项目) +public List outBoundSectDiv(StrategyStructParam param) { + StSectStrategy one = iStSectStrategyService.getOne(...); + List strategy = one.getStrategy(); + Map decisionerMap = SpringContextHolder.getBeansOfType(Decisioner.class); + List list = null; + for (String decisionerType : strategy) { + Decisioner decisioner = decisionerMap.get(decisionerType); + list = decisioner.handler(list, param); + if (CollectionUtils.isEmpty(list)){ + throw new BadRequestException("..."); + } + } + return list; +} +``` + +### 1.2 逐个问题分析 + +#### 问题一:`List list = null` 作为初始输入 + +`outBoundSectDiv` 将 `null` 作为初始列表传给第一个策略处理器。 + +以 `FIFORuleHandler` 为例,它**完全忽略输入参数 `list`**,在方法内部自行查询库存: + +```java +// FIFORuleHandler.handler() — 注意 list 参数从未被使用 +public List handler(List list, StrategyStructParam param) { + // 自行查询库存,不依赖入参 list + List vechielDtos = iStructattrService.collectVechicle(...); + // ... 处理并返回 +} +``` + +这导致了**两种不一致的初始数据加载模式**: + +| 方法 | 初始数据来源 | 首个处理器行为 | +|------|------------|-------------| +| `inBoundSectDiv` | Service 层查询所有可用货位 → 传入链 | 处理器接收实际数据,进行筛选/排序 | +| `outBoundSectDiv` | 传 `null` | 首个处理器(FIFO)自行查询库存 | + +**根因**:策略链没有统一的数据加载阶段,初始数据的获取逻辑散落在 Service 层和各个处理器之间。 + +#### 问题二:`SpringContextHolder.getBeansOfType(Decisioner.class)` 全量扫描 + +```java +Map decisionerMap = SpringContextHolder.getBeansOfType(Decisioner.class); +``` + +**每次调用**都从 Spring 容器中扫描**所有** `Decisioner` 类型的 Bean(旧项目中有 10+ 个),然后从中取一个用。这相当于每次都把整个工具箱倒出来只为了拿一把螺丝刀。 + +**正确做法**:通过 Bean 名称精确查找。 + +```java +// 应该这样做 — 按名称精确获取 +DecisionManage decisioner = (DecisionManage) applicationContext.getBean("fifo"); +``` + +#### 问题三:原生类型 `List` 丢失泛型信息 + +```java +List list = null; // 原生类型 +list = decisioner.handler(list, param); // 隐式不安全转换 +return list; // 返回值也是原生类型 +``` + +`Decisioner` 是泛型类,但使用时完全退化为原生类型。编译器无法检查类型安全,FIFO 返回 `List` 但赋值给 `List`,再返回为 `List` —— 全靠程序员记忆保证不出错。 + +#### 问题四:策略链编排逻辑重复 + +`inBoundSectDiv` 和 `outBoundSectDiv` 中策略链的加载、遍历、执行、空检查逻辑**完全重复**,只有初始数据加载方式不同: + +```java +// 两段代码的重复部分 +StSectStrategy one = iStSectStrategyService.getOne(...); // 查策略链配置 +List strategy = one.getStrategy(); // 解析策略列表 +Map decisionerMap = ...; // 获取所有Bean +for (String decisionerType : strategy) { // 遍历执行 + list = decisioner.handler(list, param); + if (CollectionUtils.isEmpty(list)) { throw ...; } +} +``` + +#### 问题五:空检查时机有逻辑漏洞 + +```java +list = decisioner.handler(list, param); +if (CollectionUtils.isEmpty(list)){ + throw new BadRequestException("当前策略" + ... + "无可用货位,分配前数量:" + list.size()); +} +``` + +异常消息中的 `分配前数量` 实际是**分配后**的数量(handler 已经执行完了),且 "分配前" 语义模糊。另外,如果某个处理器确实应该返回空列表(如过滤后无符合条件的结果),这里直接抛异常阻止了整个链,没有给后续处理器降级处理的机会。 + +#### 问题六:无可观测性 + +- 无执行耗时记录(仅 `limitStorage` 和 `alleyAve` 内部有手动计时) +- 无输入/输出数量变化追踪 +- 异常时只有简单的消息,无堆栈上下文 + +--- + +## 二、新架构设计 + +### 2.1 核心思想 + +引入 **`StrategyChainExecutor`**(策略链执行器)作为独立的编排层,将策略链的加载、查找、执行、监控统一封装,与业务 Service 解耦。 + +### 2.2 架构分层 + +``` +┌─────────────────────────────────────────────────────────┐ +│ Controller 层 │ +│ WarehouseStrategyController / 调度任务入口 │ +└────────────────────────┬────────────────────────────────┘ + │ +┌────────────────────────▼────────────────────────────────┐ +│ Service 层 │ +│ XxxServiceImpl │ +│ 职责:加载初始候选数据(可用货位 / 库存记录) │ +│ 调用 StrategyChainExecutor 执行策略链 │ +└────────────────────────┬────────────────────────────────┘ + │ +┌────────────────────────▼────────────────────────────────┐ +│ StrategyChainExecutor(新增)    │ +│ 职责:① 查询库区策略链配置 │ +│ ② 按 Bean 名称精确获取策略处理器 │ +│ ③ 按序执行策略链 │ +│ ④ 统一日志、耗时、异常处理 │ +└────────────────────────┬────────────────────────────────┘ + │ +┌────────────────────────▼────────────────────────────────┐ +│ DecisionManage(策略处理器) │ +│ 职责:单一维度的货位筛选/排序逻辑 │ +└─────────────────────────────────────────────────────────┘ +``` + +### 2.3 类职责矩阵 + +| 类 | 职责 | 不该做的事 | +|----|------|-----------| +| **StrategyChainExecutor** | 编排策略链:查配置 → 找Bean → 依次执行 → 日志监控 | 不加载初始数据,不关心业务类型 | +| **DecisionManage** | 实现单一策略逻辑(筛选/排序) | 不查策略链配置,不关心其他策略 | +| **XxxServiceImpl** | 加载初始候选数据,调用执行器 | 不直接操作策略Bean,不遍历策略链 | +| **WarehouseStrategyDO** | 库区→策略链的映射关系 | — | +| **WarehouseStrategyConfigDO** | 单个策略的参数配置 | — | + +--- + +## 三、核心类设计 + +### 3.1 StrategyChainExecutor + +```java +package cn.code.nl.module.wms.manage; + +import cn.code.nl.framework.common.exception.ServiceException; +import cn.code.nl.module.wms.dal.dataobject.warehousestrategy.WarehouseStrategyDO; +import cn.code.nl.module.wms.dal.mysql.warehousestrategy.WarehouseStrategyMapper; +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 策略链执行器,负责按库区配置的策略链依次调用策略处理器 + * + * @Author: liyongde + * @Date: 2026/7/18 + */ +@Slf4j +@Component +public class StrategyChainExecutor { + + @Resource + private WarehouseStrategyMapper warehouseStrategyMapper; + + @Resource + private ApplicationContext applicationContext; + + /** + * 执行策略链 + *

+ * 根据库区编码和策略类型加载策略链配置,从 Spring 容器中按名称查找对应的策略处理器, + * 按序执行,每个处理器的输出作为下一个处理器的输入。 + * + * @param sectionCode 库区编码 + * @param strategyType 策略类型(1=入库,2=出库) + * @param candidates 初始候选列表(入库时为可用货位列表,出库时传空列表由首个策略自行查询) + * @param param 决策参数(物料信息、出入库类型等) + * @param 货位/库存数据类型 + * @return 策略链执行后的结果列表 + */ + @SuppressWarnings("unchecked") + public List execute(String sectionCode, String strategyType, + List candidates, JSONObject param) { + // 1. 查询库区策略链配置 + WarehouseStrategyDO strategy = warehouseStrategyMapper.selectOne( + WarehouseStrategyDO::getSectionCode, sectionCode, + WarehouseStrategyDO::getStrategyType, strategyType); + if (strategy == null) { + throw new ServiceException(500, "当前库区 " + sectionCode + " 未配置策略链"); + } + + // 2. 解析策略编码列表 + List strategyCodes = JSON.parseArray(strategy.getStrategy(), String.class); + if (CollUtil.isEmpty(strategyCodes)) { + throw new ServiceException(500, "当前库区 " + sectionCode + " 策略链为空"); + } + + // 3. 按序执行策略链 + List result = candidates; + for (String strategyCode : strategyCodes) { + DecisionManage handler; + try { + handler = (DecisionManage) applicationContext.getBean(strategyCode); + } catch (Exception e) { + log.error("策略 [{}] 未找到对应的处理器 Bean", strategyCode); + throw new ServiceException(500, "策略 " + strategyCode + " 未注册"); + } + + String strategyName = handler.strategyConfig.getStrategyName(); + int inputSize = result != null ? result.size() : 0; + log.info("执行策略 [{}]:{},输入候选数量:{}", strategyCode, strategyName, inputSize); + + long startTime = System.currentTimeMillis(); + result = handler.handler(result, param); + long cost = System.currentTimeMillis() - startTime; + + int outputSize = result != null ? result.size() : 0; + log.info("策略 [{}] 执行完成,耗时:{}ms,输出数量:{}(减少:{})", + strategyCode, cost, outputSize, inputSize - outputSize); + + if (CollUtil.isEmpty(result)) { + throw new ServiceException(500, + "策略 " + strategyName + " 执行后无可用货位"); + } + } + return result; + } + +} +``` + +### 3.2 调用方的使用方式 + +入库和出库的调用代码变得简洁统一: + +```java +// 入库分配 +public List inBoundSectDiv(StrategyStructParam param) { + // 1. 加载初始候选数据(可用货位) + List candidates = structattrMapper.selectList( + new LambdaQueryWrapperX() + .eq(Structattr::getIsUsed, true) + .eq(Structattr::getLockType, "未锁定") + .eq(Structattr::getSectCode, param.getSectCode())); + if (CollUtil.isEmpty(candidates)) { + throw new ServiceException(500, "库区 " + param.getSectCode() + " 无可用货位"); + } + + // 2. 执行策略链 + JSONObject params = (JSONObject) JSON.toJSON(param); + return strategyChainExecutor.execute( + param.getSectCode(), "1", candidates, params); +} + +// 出库分配 +public List outBoundSectDiv(StrategyStructParam param) { + // 出库的首个策略(FIFO)自行查询库存,传入空列表即可 + JSONObject params = (JSONObject) JSON.toJSON(param); + return strategyChainExecutor.execute( + param.getSectCode(), "2", null, params); +} +``` + +--- + +## 四、新旧代码对比 + +| 维度 | 旧代码 | 新架构 | +|------|--------|--------| +| **编排职责** | 散落在 `StructattrServiceImpl` 中,与 CRUD 逻辑混在一起 | 独立的 `StrategyChainExecutor` 组件 | +| **Bean 查找** | `getBeansOfType()` 全量扫描 | `applicationContext.getBean(name)` 精确查找 | +| **类型安全** | 原生 `List`,无泛型约束 | `@SuppressWarnings("unchecked")` 显式标注,调用方强类型 | +| **日志** | 无统一日志,部分处理器内部有 | 每个策略执行前后记录:名称、耗时、输入/输出数量 | +| **空值处理** | 错误消息写"分配前"实际是"分配后" | 明确区分输入/输出数量 | +| **代码复用** | `inBoundSectDiv` / `outBoundSectDiv` 中策略遍历逻辑重复 | 统一由 `execute()` 处理 | +| **异常处理** | `BadRequestException`,无处理器查找失败的兜底 | `ServiceException`,区分"未配置策略链"和"处理器未注册" | +| **扩展性** | 新增分配类型需复制整段遍历代码 | 只需调用 `execute()` + 准备初始数据 | + +--- + +## 五、数据流示意 + +### 入库流程 + +``` +调用方: + ① 查询库区所有可用货位(未锁定、未占用) + ② 得到 List(如 200 个) + │ + ▼ +StrategyChainExecutor.execute(sectionCode, "1", candidates, param): + ③ 查 wms_warehouse_strategy → strategy = ["limitStorage","nearby","alleyAve"] + ④ 依次执行: + ctx.getBean("limitStorage") → handler(200个, param) → 80个(过滤尺寸不匹配) + ctx.getBean("nearby") → handler(80个, param) → 10个(按距离取最近) + ctx.getBean("alleyAve") → handler(10个, param) → 10个(巷道均衡排序) + ⑤ 返回最终 10 个货位 +``` + +### 出库流程 + +``` +调用方: + ① 不预加载数据,传 null + │ + ▼ +StrategyChainExecutor.execute(sectionCode, "2", null, param): + ③ 查 wms_warehouse_strategy → strategy = ["fifo"] + ④ 依次执行: + ctx.getBean("fifo") → handler(null, param) → FIFO内部查库存 → 5个分配结果 + ⑤ 返回 5 个库存分配记录 +``` + +--- + +## 六、扩展指南 + +### 新增一个策略 + +**Step 1**:创建策略处理器 + +```java +@Service("myStrategy") // Bean 名称 = 策略编码 +@Slf4j +public class MyStrategyHandler extends DecisionManage { + + @Override + public List handler(List list, JSONObject param) { + // 从配置读取参数 + String configParam = this.strategyConfig.getParam(); + // 实现筛选/排序逻辑 + return filteredList; + } +} +``` + +**Step 2**:在数据库注册策略配置 + +```sql +INSERT INTO wms_warehouse_strategy_config +(strategy_code, strategy_name, strategy_type, class_type, param, is_used) +VALUES ('myStrategy', '我的策略', '1', 'diy', '{"threshold": 10}', 1); +``` + +**Step 3**:在库区策略链中加入新策略 + +将 `wms_warehouse_strategy.strategy` JSON 数组中追加 `"myStrategy"` 即可,无需修改任何 Java 代码。 + +### 新增一个分配场景 + +只需在 Service 层加载对应初始数据,调用 `strategyChainExecutor.execute()`: + +```java +// 例如:盘点分配 +public List inventoryDiv(String sectionCode) { + List candidates = loadInventorySlots(sectionCode); + return strategyChainExecutor.execute(sectionCode, "3", candidates, new JSONObject()); +} +``` + +--- + +## 七、实施建议 + +1. **先实现 `StrategyChainExecutor`**,它是整个架构的枢纽,不依赖任何具体策略 +2. **迁移现有策略处理器**:旧项目的 `FIFORuleHandler`、`NearbyRuleHandler` 等改为继承新的 `DecisionManage`,`handler` 方法签名基本不变 +3. **调整 Service 层调用**:将 `StructattrServiceImpl` 中的策略遍历代码替换为对 `StrategyChainExecutor.execute()` 的调用 +4. **数据库映射**:旧表 `st_sect_strategy` → 新表 `wms_warehouse_strategy`,旧表 `st_strategy_config` → 新表 `wms_warehouse_strategy_config`,字段映射见之前的字段分析文档