diff --git a/src/main/java/com/example/building/controller/CategoryAttributeController.java b/src/main/java/com/example/building/controller/CategoryAttributeController.java new file mode 100644 index 0000000..d36c6a1 --- /dev/null +++ b/src/main/java/com/example/building/controller/CategoryAttributeController.java @@ -0,0 +1,37 @@ +package com.example.building.controller; + +import com.example.building.common.Result; +import com.example.building.entity.CategoryAttribute; +import com.example.building.service.CategoryAttributeService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 种类属性控制器 + */ +@RestController +@RequestMapping("/api/v1/category-attributes") +public class CategoryAttributeController { + + @Autowired + private CategoryAttributeService categoryAttributeService; + + /** + * 获取种类的属性列表 + */ + @GetMapping("/category/{categoryId}") + public Result> getByCategoryId(@PathVariable String categoryId) { + return Result.success(categoryAttributeService.getByCategoryId(categoryId)); + } + + /** + * 保存种类属性 + */ + @PostMapping("/category/{categoryId}") + public Result saveAttributes(@PathVariable String categoryId, @RequestBody List attrs) { + categoryAttributeService.saveAttributes(categoryId, attrs); + return Result.success(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/building/controller/ProductController.java b/src/main/java/com/example/building/controller/ProductController.java index b6e443d..3a36f4d 100644 --- a/src/main/java/com/example/building/controller/ProductController.java +++ b/src/main/java/com/example/building/controller/ProductController.java @@ -5,6 +5,8 @@ import com.example.building.common.Result; import com.example.building.entity.Category; import com.example.building.entity.Product; import com.example.building.service.ProductService; +import com.example.building.service.CategoryAttributeService; +import com.example.building.service.ProductAttributeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -22,6 +24,12 @@ public class ProductController { @Autowired private ProductService productService; + @Autowired + private CategoryAttributeService categoryAttributeService; + + @Autowired + private ProductAttributeService productAttributeService; + /** * 获取分类列表 */ @@ -131,4 +139,52 @@ public class ProductController { public Result>> getStockAlerts() { return Result.success(productService.getStockAlerts()); } + + // ============ 属性相关 ============ + + /** + * 获取种类的属性定义 + */ + @GetMapping("/categories/{categoryId}/attributes") + public Result getCategoryAttributes(@PathVariable String categoryId) { + return Result.success(categoryAttributeService.getByCategoryId(categoryId)); + } + + /** + * 保存种类的属性定义 + */ + @PostMapping("/categories/{categoryId}/attributes") + public Result saveCategoryAttributes( + @PathVariable String categoryId, + @RequestBody List attributes, + @RequestHeader(value = "X-User-Role", required = false) String role) { + if (!"admin".equals(role)) { + return Result.error("只有管理员可以操作"); + } + categoryAttributeService.saveAttributes(categoryId, attributes); + return Result.success(); + } + + /** + * 获取商品的属性值 + */ + @GetMapping("/{productId}/attributes") + public Result getProductAttributes(@PathVariable String productId) { + return Result.success(productAttributeService.getByProductId(productId)); + } + + /** + * 保存商品的属性值 + */ + @PostMapping("/{productId}/attributes") + public Result saveProductAttributes( + @PathVariable String productId, + @RequestBody List> attrs, + @RequestHeader(value = "X-User-Role", required = false) String role) { + if (!"admin".equals(role)) { + return Result.error("只有管理员可以操作"); + } + productAttributeService.saveProductAttributes(productId, attrs); + return Result.success(); + } } diff --git a/src/main/java/com/example/building/entity/CategoryAttribute.java b/src/main/java/com/example/building/entity/CategoryAttribute.java new file mode 100644 index 0000000..392fec2 --- /dev/null +++ b/src/main/java/com/example/building/entity/CategoryAttribute.java @@ -0,0 +1,58 @@ +package com.example.building.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 种类属性定义实体 + */ +@Data +@TableName("category_attributes") +public class CategoryAttribute { + + @TableId(type = IdType.ASSIGN_UUID) + private String attrId; + + /** + * 种类ID + */ + private String categoryId; + + /** + * 属性名称 + */ + private String name; + + /** + * 属性类型: number=数字, text=文本, formula=公式 + */ + private String attrType; + + /** + * 单位 + */ + private String unit; + + /** + * 公式表达式(如:length*width) + */ + private String formula; + + /** + * 排序 + */ + private Integer sortOrder; + + /** + * 是否必填 + */ + private Integer required; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; +} \ No newline at end of file diff --git a/src/main/java/com/example/building/entity/ProductAttribute.java b/src/main/java/com/example/building/entity/ProductAttribute.java new file mode 100644 index 0000000..1c31e82 --- /dev/null +++ b/src/main/java/com/example/building/entity/ProductAttribute.java @@ -0,0 +1,40 @@ +package com.example.building.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 商品属性值实体 + */ +@Data +@TableName("product_attributes") +public class ProductAttribute { + + @TableId(type = IdType.ASSIGN_UUID) + private String id; + + /** + * 商品ID + */ + private String productId; + + /** + * 属性定义ID + */ + private String attrId; + + /** + * 属性名称(冗余) + */ + private String attrName; + + /** + * 属性值 + */ + private String attrValue; + + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createdAt; +} \ No newline at end of file diff --git a/src/main/java/com/example/building/mapper/CategoryAttributeMapper.java b/src/main/java/com/example/building/mapper/CategoryAttributeMapper.java new file mode 100644 index 0000000..04ff2fd --- /dev/null +++ b/src/main/java/com/example/building/mapper/CategoryAttributeMapper.java @@ -0,0 +1,9 @@ +package com.example.building.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.building.entity.CategoryAttribute; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CategoryAttributeMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/example/building/mapper/ProductAttributeMapper.java b/src/main/java/com/example/building/mapper/ProductAttributeMapper.java new file mode 100644 index 0000000..bd3825f --- /dev/null +++ b/src/main/java/com/example/building/mapper/ProductAttributeMapper.java @@ -0,0 +1,9 @@ +package com.example.building.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.example.building.entity.ProductAttribute; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ProductAttributeMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/example/building/service/CategoryAttributeService.java b/src/main/java/com/example/building/service/CategoryAttributeService.java new file mode 100644 index 0000000..1f2942c --- /dev/null +++ b/src/main/java/com/example/building/service/CategoryAttributeService.java @@ -0,0 +1,26 @@ +package com.example.building.service; + +import com.example.building.entity.CategoryAttribute; +import com.baomidou.mybatisplus.extension.service.IService; +import java.util.List; + +/** + * 种类属性服务接口 + */ +public interface CategoryAttributeService extends IService { + + /** + * 获取种类的所有属性 + */ + List getByCategoryId(String categoryId); + + /** + * 保存属性 + */ + void saveAttributes(String categoryId, List attrs); + + /** + * 删除种类的所有属性 + */ + void deleteByCategoryId(String categoryId); +} \ No newline at end of file diff --git a/src/main/java/com/example/building/service/ProductAttributeService.java b/src/main/java/com/example/building/service/ProductAttributeService.java new file mode 100644 index 0000000..43a0dc7 --- /dev/null +++ b/src/main/java/com/example/building/service/ProductAttributeService.java @@ -0,0 +1,27 @@ +package com.example.building.service; + +import com.example.building.entity.ProductAttribute; +import com.baomidou.mybatisplus.extension.service.IService; +import java.util.List; +import java.util.Map; + +/** + * 商品属性服务接口 + */ +public interface ProductAttributeService extends IService { + + /** + * 获取商品的属性值 + */ + List getByProductId(String productId); + + /** + * 保存商品属性值 + */ + void saveProductAttributes(String productId, List> attrs); + + /** + * 删除商品的所有属性 + */ + void deleteByProductId(String productId); +} \ No newline at end of file diff --git a/src/main/java/com/example/building/service/impl/CategoryAttributeServiceImpl.java b/src/main/java/com/example/building/service/impl/CategoryAttributeServiceImpl.java new file mode 100644 index 0000000..ac50f99 --- /dev/null +++ b/src/main/java/com/example/building/service/impl/CategoryAttributeServiceImpl.java @@ -0,0 +1,52 @@ +package com.example.building.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.example.building.entity.CategoryAttribute; +import com.example.building.mapper.CategoryAttributeMapper; +import com.example.building.service.CategoryAttributeService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.UUID; + +/** + * 种类属性服务实现 + */ +@Service +public class CategoryAttributeServiceImpl implements CategoryAttributeService { + + @Override + public List getByCategoryId(String categoryId) { + return this.list(new LambdaQueryWrapper() + .eq(CategoryAttribute::getCategoryId, categoryId) + .orderByAsc(CategoryAttribute::getSortOrder)); + } + + @Override + @Transactional + public void saveAttributes(String categoryId, List attrs) { + // 删除旧属性 + this.remove(new LambdaQueryWrapper() + .eq(CategoryAttribute::getCategoryId, categoryId)); + + // 保存新属性 + if (attrs != null && !attrs.isEmpty()) { + for (int i = 0; i < attrs.size(); i++) { + CategoryAttribute attr = attrs.get(i); + attr.setAttrId(UUID.randomUUID().toString()); + attr.setCategoryId(categoryId); + attr.setSortOrder(i); + this.save(attr); + } + } + } + + @Override + @Transactional + public void deleteByCategoryId(String categoryId) { + this.remove(new LambdaQueryWrapper() + .eq(CategoryAttribute::getCategoryId, categoryId)); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/building/service/impl/ProductAttributeServiceImpl.java b/src/main/java/com/example/building/service/impl/ProductAttributeServiceImpl.java new file mode 100644 index 0000000..6023e5c --- /dev/null +++ b/src/main/java/com/example/building/service/impl/ProductAttributeServiceImpl.java @@ -0,0 +1,54 @@ +package com.example.building.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.example.building.entity.ProductAttribute; +import com.example.building.mapper.ProductAttributeMapper; +import com.example.building.service.ProductAttributeService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * 商品属性服务实现 + */ +@Service +public class ProductAttributeServiceImpl implements ProductAttributeService { + + @Override + public List getByProductId(String productId) { + return this.list(new LambdaQueryWrapper() + .eq(ProductAttribute::getProductId, productId)); + } + + @Override + @Transactional + public void saveProductAttributes(String productId, List> attrs) { + // 删除旧属性 + this.remove(new LambdaQueryWrapper() + .eq(ProductAttribute::getProductId, productId)); + + // 保存新属性 + if (attrs != null && !attrs.isEmpty()) { + for (Map attr : attrs) { + ProductAttribute pa = new ProductAttribute(); + pa.setId(UUID.randomUUID().toString()); + pa.setProductId(productId); + pa.setAttrId((String) attr.get("attrId")); + pa.setAttrName((String) attr.get("attrName")); + pa.setAttrValue(String.valueOf(attr.get("attrValue"))); + this.save(pa); + } + } + } + + @Override + @Transactional + public void deleteByProductId(String productId) { + this.remove(new LambdaQueryWrapper() + .eq(ProductAttribute::getProductId, productId)); + } +} \ No newline at end of file diff --git a/src/main/resources/db/migration/V7__add_category_attributes.sql b/src/main/resources/db/migration/V7__add_category_attributes.sql new file mode 100644 index 0000000..2b15473 --- /dev/null +++ b/src/main/resources/db/migration/V7__add_category_attributes.sql @@ -0,0 +1,29 @@ +-- 种类属性定义表 +CREATE TABLE IF NOT EXISTS category_attributes ( + attr_id VARCHAR(36) PRIMARY KEY, + category_id VARCHAR(36) NOT NULL, + name VARCHAR(50) NOT NULL, + attr_type VARCHAR(20) DEFAULT 'number', + unit VARCHAR(20), + formula VARCHAR(200), + sort_order INTEGER DEFAULT 0, + required INTEGER DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE CASCADE +); + +-- 商品属性值表 +CREATE TABLE IF NOT EXISTS product_attributes ( + id VARCHAR(36) PRIMARY KEY, + product_id VARCHAR(36) NOT NULL, + attr_id VARCHAR(36) NOT NULL, + attr_name VARCHAR(50), + attr_value VARCHAR(200), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (product_id) REFERENCES products(product_id) ON DELETE CASCADE, + FOREIGN KEY (attr_id) REFERENCES category_attributes(attr_id) ON DELETE CASCADE +); + +-- 商品表增加默认单位字段 +ALTER TABLE products ADD COLUMN default_unit VARCHAR(20); \ No newline at end of file