feat: 商品行为管理 + 属性编码 + ActionHandler机制

- ProductActionEntity: 行为管理实体(name/code/description/status)
- ProductAttributeEntity: 新增code(属性编码)和action(关联行为)字段
- ActionHandler接口: validate校验 + afterPurchase购买后处理
- StockCheckHandler: 库存校验+扣减
- DatePickHandler: 服务日期校验
- DeliveryRouteHandler: 发货方式校验
- ActionHandlerRegistry: 自动注册所有handler
This commit is contained in:
OpenCode
2026-07-28 11:55:41 +08:00
parent 24e18d9327
commit 85c9929b27
12 changed files with 254 additions and 0 deletions
@@ -0,0 +1,35 @@
package com.cool.modules.product.action;
import cn.hutool.json.JSONObject;
/**
* 商品行为处理器接口
*/
public interface ActionHandler {
/**
* 获取行为编码
*/
String getCode();
/**
* 购买时校验
*
* @param attrCode 属性编码
* @param attrValue 属性值
* @param quantity 购买数量
*/
void validate(String attrCode, String attrValue, int quantity);
/**
* 购买成功后执行(如扣减库存)
*
* @param attrCode 属性编码
* @param attrValue 属性值
* @param quantity 购买数量
* @return 更新后的属性值
*/
default String afterPurchase(String attrCode, String attrValue, int quantity) {
return attrValue;
}
}
@@ -0,0 +1,36 @@
package com.cool.modules.product.action;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 行为处理器注册中心
*/
@Component
@RequiredArgsConstructor
public class ActionHandlerRegistry {
private final List<ActionHandler> handlers;
private Map<String, ActionHandler> handlerMap;
@PostConstruct
public void init() {
handlerMap = handlers.stream()
.collect(Collectors.toMap(ActionHandler::getCode, Function.identity()));
}
public ActionHandler getHandler(String actionCode) {
return handlerMap.get(actionCode);
}
public boolean hasHandler(String actionCode) {
return handlerMap.containsKey(actionCode);
}
}
@@ -0,0 +1,22 @@
package com.cool.modules.product.action;
import org.springframework.stereotype.Component;
/**
* 日期选择处理器(服务类商品预约时间)
*/
@Component
public class DatePickHandler implements ActionHandler {
@Override
public String getCode() {
return "date_pick";
}
@Override
public void validate(String attrCode, String attrValue, int quantity) {
if (attrValue == null || attrValue.isEmpty()) {
throw new RuntimeException("请选择服务时间");
}
}
}
@@ -0,0 +1,22 @@
package com.cool.modules.product.action;
import org.springframework.stereotype.Component;
/**
* 发货路由处理器(判断物流/线上交付)
*/
@Component
public class DeliveryRouteHandler implements ActionHandler {
@Override
public String getCode() {
return "delivery_route";
}
@Override
public void validate(String attrCode, String attrValue, int quantity) {
if (attrValue == null || attrValue.isEmpty()) {
throw new RuntimeException("请选择发货方式");
}
}
}
@@ -0,0 +1,35 @@
package com.cool.modules.product.action;
import org.springframework.stereotype.Component;
/**
* 库存校验处理器
*/
@Component
public class StockCheckHandler implements ActionHandler {
@Override
public String getCode() {
return "stock_check";
}
@Override
public void validate(String attrCode, String attrValue, int quantity) {
if (attrValue == null || attrValue.isEmpty()) {
return;
}
int stock = Integer.parseInt(attrValue);
if (stock < quantity) {
throw new RuntimeException("库存不足,当前库存:" + stock);
}
}
@Override
public String afterPurchase(String attrCode, String attrValue, int quantity) {
if (attrValue == null || attrValue.isEmpty()) {
return attrValue;
}
int stock = Integer.parseInt(attrValue);
return String.valueOf(stock - quantity);
}
}
@@ -0,0 +1,21 @@
package com.cool.modules.product.controller.admin;
import cn.hutool.json.JSONObject;
import com.cool.core.annotation.CoolRestController;
import com.cool.core.base.BaseController;
import com.cool.modules.product.entity.ProductActionEntity;
import com.cool.modules.product.service.ProductActionService;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Tag(name = "商品行为管理", description = "商品行为管理")
@CoolRestController(api = {"add", "delete", "update", "page", "info"})
public class AdminProductActionController extends BaseController<ProductActionService, ProductActionEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
setPageOption(createOp());
}
}
@@ -0,0 +1,26 @@
package com.cool.modules.product.controller.app;
import cn.hutool.json.JSONObject;
import com.cool.core.annotation.CoolRestController;
import com.cool.core.request.R;
import com.cool.modules.product.entity.ProductActionEntity;
import com.cool.modules.product.service.ProductActionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
@RequiredArgsConstructor
@Tag(name = "商品行为", description = "App端商品行为")
@CoolRestController
public class AppProductActionController {
private final ProductActionService productActionService;
@Operation(summary = "行为列表")
@GetMapping("/list")
public R list(@RequestAttribute JSONObject requestParams) {
return R.ok(productActionService.list(requestParams, null));
}
}
@@ -0,0 +1,25 @@
package com.cool.modules.product.entity;
import com.cool.core.base.TenantEntity;
import com.mybatisflex.annotation.Table;
import com.cool.core.annotation.ColumnDefine;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Table(value = "product_action", comment = "商品行为表")
public class ProductActionEntity extends TenantEntity<ProductActionEntity> {
@ColumnDefine(comment = "行为名称", notNull = true)
private String name;
@ColumnDefine(comment = "行为编码", notNull = true)
private String code;
@ColumnDefine(comment = "说明", type = "text")
private String description;
@ColumnDefine(comment = "状态 0:禁用 1:启用", defaultValue = "1")
private Integer status;
}
@@ -14,12 +14,18 @@ public class ProductAttributeEntity extends TenantEntity<ProductAttributeEntity>
@ColumnDefine(comment = "属性名称", notNull = true)
private String name;
@ColumnDefine(comment = "属性编码", notNull = true)
private String code;
@ColumnDefine(comment = "值类型 0:文本 1:数字 2:选择 3:日期", defaultValue = "0")
private Integer type;
@ColumnDefine(comment = "选项值(type=2时存JSON)", type = "text")
private String options;
@ColumnDefine(comment = "关联行为编码")
private String action;
@ColumnDefine(comment = "状态 0:禁用 1:启用", defaultValue = "1")
private Integer status;
}
@@ -0,0 +1,7 @@
package com.cool.modules.product.mapper;
import com.mybatisflex.core.BaseMapper;
import com.cool.modules.product.entity.ProductActionEntity;
public interface ProductActionMapper extends BaseMapper<ProductActionEntity> {
}
@@ -0,0 +1,7 @@
package com.cool.modules.product.service;
import com.cool.core.base.BaseService;
import com.cool.modules.product.entity.ProductActionEntity;
public interface ProductActionService extends BaseService<ProductActionEntity> {
}
@@ -0,0 +1,12 @@
package com.cool.modules.product.service.impl;
import com.cool.core.base.BaseServiceImpl;
import com.cool.modules.product.entity.ProductActionEntity;
import com.cool.modules.product.mapper.ProductActionMapper;
import com.cool.modules.product.service.ProductActionService;
import org.springframework.stereotype.Service;
@Service
public class ProductActionServiceImpl extends BaseServiceImpl<ProductActionMapper, ProductActionEntity> implements
ProductActionService {
}