商品甄选

商品甄选
生活像一把无情刻刀!开发环境
技术栈
后端
SpringBoot 简化Spring应用配置与开发
SpringCloud:
SpringCloud Alibaba Nacos 注册中心与配置中心
SpringCloud Alibaba Sentinel 服务熔断与降级(限流)
SpringCloud Alibaba Task 定时任务
SpringCloud GateWay 网关(访问服务要经过网关)
SpringCloud OpenFeign 微服务通信组件
SpringCloud LoadBalancer 负载均衡器
Mybatis-plus 持久层框架,依赖Mybatis
Redssion 操作redis的框架
ThreadPoolExecutor 线程池
Lombok 自动生成方法
Knife4J 接口可视化文档
Mysql 数据库
Redis 缓存
RabbitMq 消息队列
MinIO 分布式文件存储
Docker 容器化技术
前端
ECMAScript 6:也称为ECMAScript2015,是JavaScript的一个版本,引入了许多新的语法和功能。
Vue.js:一个用于构建用户界面的渐进式JavaScript框架。
Vuex:Vue.js应用程序的状态管理模式和库。
Vue Router:Vue.is官方的路由管理器,用于构建单页面应用(SPA)。
Vue CLI:Vue.jis开发的标准工具,提供了一个丰富的交互式项目脚手架。
Axiox:一个基于Promise的 HTTP客户端,用于浏览器和Node.js,常用于发送网络请求。
Element Plus:一套为 Vue.js3.0开发的桌面端u组件库,提供丰富的组件以帮助快速构建界面。
docker环境搭建
1.下载minio、mysql、nacos、rabbitmq、redis、sentinel镜像
2.通过命令或compose.yml文件启动容器
基础环境搭建
对Ruoyi-Cloud进行了修改,将Cloud版本改为了3.6.3,将jdk版本改为了17,还有一些名称的修改
项目结构图
1 | com.spzx |
spzx-monitor
使用spring boot admin健康监测组件实现
前端项目搭建
使用若依配套的前端框架,进行一些名称上的修改
业务编写
创建商品模块
创建pom.xml 配置
1 | <?xml version="1.0" encoding="UTF-8"?> |
创建bootstrap.yml 配置文件
1 | # Tomcat |
创建logback.xml 日志配置文件
1 | <?xml version="1.0" encoding="UTF-8"?> |
创建banner.txt 启动日志信息配置
1 | Spring Boot Version: ${spring-boot.version} |
创建nacos配置中心配置文件
1 | mybatis-plus: |
创建启动类
1 | package com.spzx; |
使用Mybatis-plus代码生成
在根模块下锁定版本
1 | <dependency> |
在使用的项目中添加依赖
1 | <dependency> |
创建代码生成器
1 | package com.spzx.product; |
minio文件上传
若依框架自带了文件上传接口,直接使用即可
minio存储桶权限配置
1 | { |
品牌分页查询
Controller
1 | @RestController |
Service
接口
1 | public interface IBrandService extends IService<Brand> { |
实现类
1 | @Service |
品牌增删改查
Controller
1 | package com.spzx.product.controller; |
Service
接口
1 | public interface IBrandService extends IService<Brand> { |
实现类
1 | package com.spzx.product.service.impl; |
前端整合
配置nocos
1 | # 商品服务 |
用户登录与权限
Ruoyi遵循RBAC模型开发的权限控制,基于角色的权限控制
登录:
用户登录->网关(校验信息并路由到对应的服务)->认证服务->系统服务->登录成功返回token
权限:
用户请求->网关(验证令牌是否有效,并将用户信息及token存到请求头中)->系统服务(拦截器拦截请求,获取请求头信息,存储到ThreadLocal中)->处理请求,响应结果
日志
采用AOP在操作前获取操作信息并记录到数据库/本地/文件存储服务器
商品管理-商品单位
Controller
package com.spzx.product.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.common.core.web.domain.AjaxResult;
import com.spzx.common.core.web.page.TableDataInfo;
import com.spzx.common.log.annotation.Log;
import com.spzx.common.log.enums.OperatorType;
import com.spzx.common.security.annotation.RequiresPermissions;
import com.spzx.product.domain.Brand;
import com.spzx.product.domain.ProductUnit;
import com.spzx.product.service.IBrandService;
import com.spzx.product.service.IProductUnitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.spzx.common.core.web.controller.BaseController;
import java.util.List;
/**
商品单位 前端控制器
@author atguigu
@since 2026-02-01
*/
@RestController
@RequestMapping(“/productUnit”)
public class ProductUnitController extends BaseController {@Autowired
private IProductUnitService productUnitService;@Operation(summary = “查询单位列表”)
@Log(title=”查询操作”,operatorType = OperatorType.OTHER)
@RequiresPermissions(“product:unit:list”)
@GetMapping(“/list”)
public TableDataInfo list(@Parameter(description=”名称”) String name)
{
startPage();
Listlist = productUnitService.selectProductUnitList(name);
return getDataTable(list);
}@Operation(summary = “根据id查询指定单位”)
@GetMapping(“/{id}”)
public AjaxResult byId(@PathVariable Long id)
{
ProductUnit productUnit = productUnitService.getById(id);
return AjaxResult.success(productUnit);
}@Operation(summary = “新增单位”)
@PostMapping
public AjaxResult insert(@Parameter(description = “新增”) @RequestBody ProductUnit productUnit)
{
productUnitService.save(productUnit);
return AjaxResult.success();
}@Operation(summary = “修改单位”)
@PutMapping
public AjaxResult update(@Parameter(description = “修改”) @RequestBody ProductUnit productUnit)
{
productUnitService.updateProductUnit(productUnit);
return AjaxResult.success();
}@Operation(summary = “删除单位”)
@DeleteMapping(“/{ids}”)
public AjaxResult delete(@PathVariable Listids)
{
productUnitService.removeBatchByIds(ids);
return AjaxResult.success();
}
}
Service interface
public interface IProductUnitService extends IServiceList<ProductUnit> selectProductUnitList(String name);
void updateProductUnit(ProductUnit productUnit);
}
Service implement
@Service public class ProductUnitServiceImpl extends ServiceImpl@Override
public List<ProductUnit> selectProductUnitList(String name) {
LambdaQueryWrapper<ProductUnit> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(name),ProductUnit::getName, name);
return list(wrapper);
}
@Override
public void updateProductUnit(ProductUnit productUnit) {
LambdaQueryWrapper<ProductUnit> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(ProductUnit::getId, productUnit.getId());
update(productUnit, wrapper);
}
}
商品分类
Controller
@RestController @RequestMapping("/category") public class CategoryController extends BaseController {@Autowired
private ICategoryService categoryService;
@GetMapping("/treeSelect/{parent_id}")
public AjaxResult getCategoryList(@PathVariable Long parent_id){
List<Category> list=categoryService.getList(parent_id);
return AjaxResult.success(list);
}
}
Service Interface
public interface ICategoryService extends IServiceList<Category> getList(Long parentId);
}
Service Implement
@Service public class CategoryServiceImpl extends ServiceImpl@Override
public List<Category> getList(Long parentId) {
LambdaQueryWrapper<Category> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Category::getParentId,parentId);
queryWrapper.select(Category::getId,Category::getName,Category::getParentId,Category::getStatus,Category::getImageUrl,Category::getOrderNum);
List<Category> list = this.list(queryWrapper);
if(!ObjectUtils.isEmpty(list)){
for (Category category : list) {
Long count=this.count(new LambdaQueryWrapper<Category>().eq(Category::getParentId,category.getId()));
category.setHasChildren(count>0);
}
}
return list;
}
}
分类品牌
Controller
package com.spzx.product.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.web.domain.AjaxResult;
import com.spzx.common.core.web.page.TableDataInfo;
import com.spzx.common.log.annotation.Log;
import com.spzx.common.log.enums.OperatorType;
import com.spzx.common.security.annotation.RequiresPermissions;
import com.spzx.product.domain.CategoryBrand;
import com.spzx.product.service.ICategoryBrandService;
import com.spzx.product.service.IProductService;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.spzx.common.core.web.controller.BaseController;
import java.util.List;
/**
分类品牌 前端控制器
@author atguigu
@since 2026-02-01
*/
@RestController
@RequestMapping(“/categoryBrand”)
public class CategoryBrandController extends BaseController {
@Autowired
private ICategoryBrandService categoryBrandService;@Operation(summary = “查询分类品牌列表”)
@Log(title=”查询操作”,operatorType = OperatorType.OTHER)
@RequiresPermissions(“product:categoryBrand:list”)
@GetMapping(“/list”)
public TableDataInfo list(){
startPage();
Listlist = categoryBrandService.getCategoryBrandList();
return getDataTable(list);
}@Operation(summary = “新增分类品牌”)
@Log(title=”新增操作”,operatorType = OperatorType.OTHER)
@PostMapping
public AjaxResult insertCategoryBrand(@RequestBody CategoryBrand categoryBrand){
categoryBrandService.insertCategoryBrand(categoryBrand);
return AjaxResult.success();
}@Operation(summary = “删除分类品牌”)
@Log(title=”删除操作”,operatorType = OperatorType.OTHER)
@DeleteMapping(“{ids}”)
public AjaxResult deleteCategoryBrands(@PathVariable Listids){
categoryBrandService.removeByIds(ids);
return AjaxResult.success();
}
}
Service Interface
public interface ICategoryBrandService extends IServiceList<CategoryBrand> getCategoryBrandList();
void insertCategoryBrand(CategoryBrand categoryBrand);
}
Service Implement
package com.spzx.product.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.product.domain.Brand;
import com.spzx.product.domain.Category;
import com.spzx.product.domain.CategoryBrand;
import com.spzx.product.mapper.CategoryBrandMapper;
import com.spzx.product.service.IBrandService;
import com.spzx.product.service.ICategoryBrandService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.product.service.ICategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
分类品牌 服务实现类
@author atguigu
@since 2026-02-01
*/
@Service
public class CategoryBrandServiceImpl extends ServiceImpl<CategoryBrandMapper, CategoryBrand> implements ICategoryBrandService {@Autowired
private CategoryBrandMapper categoryBrandMapper;
@Autowired
private IBrandService brandService;@Override
public ListgetCategoryBrandList() {
return categoryBrandMapper.selectCategoryBrandList();
}@Override
public void insertCategoryBrand(CategoryBrand categoryBrand) {long count = count(new LambdaQueryWrapper<CategoryBrand>() .eq(CategoryBrand::getCategoryId, categoryBrand.getCategoryId()) .eq(CategoryBrand::getBrandId, categoryBrand.getBrandId()) ); if(count>0){ throw new ServiceException("该分类已有此品牌"); } categoryBrand.setCreateBy(SecurityUtils.getUsername()); categoryBrand.setUpdateBy(SecurityUtils.getUsername()); Brand brand = brandService.getById(categoryBrand.getBrandId()); categoryBrand.setLogo(brand.getLogo()); categoryBrandMapper.insert(categoryBrand);}
}
Mapper Interface
public interface CategoryBrandMapper extends BaseMapperList<CategoryBrand> selectCategoryBrandList();
}
Mapper XML
商品规格
Controller
package com.spzx.product.controller;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.web.domain.AjaxResult;
import com.spzx.common.core.web.page.TableDataInfo;
import com.spzx.common.log.annotation.Log;
import com.spzx.common.log.enums.OperatorType;
import com.spzx.common.security.annotation.RequiresPermissions;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.product.domain.Brand;
import com.spzx.product.domain.ProductSpec;
import com.spzx.product.mapper.ProductSpecMapper;
import com.spzx.product.service.IProductSpecService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.spzx.common.core.web.controller.BaseController;
import java.util.List;
/**
商品规格 前端控制器
@author atguigu
@since 2026-02-01
*/
@RestController
@RequestMapping(“/productSpec”)
public class ProductSpecController extends BaseController {@Autowired
private IProductSpecService productSpecService;@Operation(summary = “查询商品规格列表”)
@Log(title=”查询操作”,operatorType = OperatorType.OTHER)
@RequiresPermissions(“product:productSpec:list”)
@GetMapping(“/list”)
public TableDataInfo list(){
startPage();
Listlist = productSpecService.selectProductSpecList();
return getDataTable(list);
}@Operation(summary = “添加商品规格”)
@Log(title=”添加操作”,operatorType = OperatorType.OTHER)
@PostMapping
public AjaxResult insertProductSpec(@RequestBody ProductSpec productSpec) throws JsonProcessingException {
productSpecService.saveProductSpec(productSpec);
return AjaxResult.success();
}@Operation(summary = “删除商品规格”)
@Log(title=”删除操作”,operatorType = OperatorType.OTHER)
@DeleteMapping(“{ids}”)
public AjaxResult deleteProductSpec(@PathVariable Listids){
productSpecService.removeByIds(ids);
return AjaxResult.success();
}
}
Service Interface
public interface IProductSpecService extends IServiceList<ProductSpec> selectProductSpecList();
void saveProductSpec(ProductSpec productSpec) throws JsonProcessingException;
}
Service Implement
package com.spzx.product.service.impl;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.product.domain.ProductSpec;
import com.spzx.product.mapper.ProductSpecMapper;
import com.spzx.product.service.IProductSpecService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
商品规格 服务实现类
@author atguigu
@since 2026-02-01
*/
@Service
public class ProductSpecServiceImpl extends ServiceImpl<ProductSpecMapper, ProductSpec> implements IProductSpecService {@Autowired
private ProductSpecMapper productSpecMapper;@Override
public ListselectProductSpecList() {
return productSpecMapper.selectAll();
}@Override
public void saveProductSpec(ProductSpec productSpec){
String specValue = productSpec.getSpecValue();
validateSpecJson(specValue);
productSpec.setCreateBy(SecurityUtils.getUsername());
productSpec.setUpdateBy(SecurityUtils.getUsername());
this.save(productSpec);
}private static final ObjectMapper MAPPER = new ObjectMapper();
public static void validateSpecJson(String specValueJson) {
try {
JsonNode root = MAPPER.readTree(specValueJson);
if (root == null || !root.isArray()) {
throw new ServiceException(“规格格式错误:应为 JSON 数组”);
}
// 校验 key 全局唯一
SetkeySet = new HashSet<>();
for (JsonNode item : root) {
if (item == null || !item.isObject()) {
throw new ServiceException(“规格格式错误:数组元素必须是对象”);
}
String key = item.path(“key”).asText(“”).trim();
if (key.isEmpty()) {
throw new ServiceException(“规格名称为空”);
}
if (!keySet.add(key)) {
throw new ServiceException(“规格名称重复:” + key);
}
JsonNode valueListNode = item.path(“valueList”);
if (!valueListNode.isArray() || valueListNode.size() == 0) {
throw new ServiceException(“规格值为空:” + key);
}
// 校验当前 key 的 valueList 唯一
SetvalueSet = new HashSet<>();
for (JsonNode v : valueListNode) {
String val = v.asText(“”).trim();
if (val.isEmpty()) {
throw new ServiceException(“规格值为空:” + key);
}
if (!valueSet.add(val)) {
throw new ServiceException(“规格值重复:” + key + “ = “ + val);
}
}
}
} catch (ServiceException e) {
throw e;
} catch (Exception e) {
// JSON 解析失败等
throw new ServiceException(“规格 JSON 解析失败:” + e.getMessage());
}
}
}
Mapper Interface
public interface ProductSpecMapper extends BaseMapperList<ProductSpec> selectAll();
}
Mapper XML
商品列表
Controller
@RestController @RequestMapping("/product") public class ProductController extends BaseController {@Autowired
private IProductService productService;
@Operation(summary = "查询商品列表")
@Log(title="查询操作",operatorType = OperatorType.OTHER)
@RequiresPermissions("product:product:list")
@GetMapping("/list")
public TableDataInfo list(){
//开启分页
startPage();
//查询结果
List<Product> list = productService.selectProductList();
//返回数据
return getDataTable(list);
}
}
Service Interface
public interface IProductService extends IServiceList<Product> selectProductList();
}
Service Implement
@Service public class ProductServiceImpl extends ServiceImpl@Autowired
private ProductMapper productMapper;
@Override
public List<Product> selectProductList() {
return productMapper.selectProductAll();
}
}
Mapper Interface
public interface ProductSpecMapper extends BaseMapperList<ProductSpec> selectAll();
}
Mapper XML
<select id="selectProductAll" resultType="com.spzx.product.domain.Product">
select
p.id id,
p.slider_urls sliderUrls,
p.name name,
b.name brandName,
c1.name category1Name,
c2.name category2Name,
c3.name category3Name,
p.unit_name unitName,
p.status status
from product p
inner join brand b on p.brand_id=b.id and b.del_flag=0
inner join category c1 on p.category1_id=c1.id and b.del_flag=0
inner join category c2 on p.category2_id=c2.id and b.del_flag=0
inner join category c3 on p.category3_id=c3.id and b.del_flag=0
where p.del_flag=0
</select>
开发日志
day…?
项目初始化
1.基于ruoyi框架的初始化项目,更改一些名称信息,以及结构 2.前端使用基于ruoyi框架的更改后的项目,包含了后续功能所需。 3.初始化数据库:使用提供的sql文件初始化数据库 4.前端ui菜单管理添加对应菜单与路由映射,权限映射商品管理-品牌管理
基于数据库表和若依后端结构,完成增删改,以及分页查询,按名称查询基础数据-单位管理
基于数据库表和若依后端结构,完成增删改,以及分页查询,按名称查询基础数据-商品分类
基于数据库表和若依后端结构,完成商品类别查询,子类别查询基础数据-分类品牌
基于数据库表和若依后端结构,完成增删,分页查询(多表)基础数据-商品规格
基于数据库表和若依后端结构,完成增删,分页查询(多表),以及新增时的重复校验day+1
完成商品列表的分页查询(多表)
day+2
…

