电商轮播图
@RestController @RequestMapping("/index") public class CarouselController { @Autowired private CarouselService carouselService; /** * 获取轮播图列表 * * @return */ @GetMapping("/carousel") public JsonResult carousel() { return JsonResult.ok(carouselService.queryAll(YesOrNo.YES.type)); } }@Service public class CarouselServiceImpl implements CarouselService { @Autowired private CarouselMapper carouselMapper; @Override public List<CarouselDO> queryAll(Integer isShow) { return carouselMapper.selectList(new QueryWrapper<CarouselDO>().eq("is_show", isShow).orderByDesc("sort")); } }首页分类
- 第一次刷新主页查询大分类,渲染展示到首页
- 如果鼠标移动到大分类,则加载小分类的内容
一级分类
/** * 获取商品分类(一级分类) * * @return */ @GetMapping("/cats") public JsonResult cats() { return JsonResult.ok(categoryService.queryAllRootLevelCat()); }@Service public class CategoryServiceImpl implements CategoryService { @Autowired private CategoryMapper categoryMapper; /** * 查询所有一级分类 * @return */ @Override public List<CategoryDO> queryAllRootLevelCat() { return categoryMapper.selectList(new QueryWrapper<CategoryDO>().eq("type", CategoryType.FIRST.type)); } }查询子分类
/** * 二级分类VO */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class CategoryVO { private Integer id; private String name; private Integer type; private Integer fatherId; private List<SubCategoryVO> subCatList; }/** * 三级分类VO */ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class SubCategoryVO { private Integer subId; private String subName; private Integer subType; private Integer subFatherId; }/** * 获取商品子分类 * * @param rootCatId * @return */ @GetMapping("/subCat/{rootCatId}") public JsonResult subCat(@PathVariable Integer rootCatId) { if (rootCatId == null){ return JsonResult.errorMsg("分类不存在"); } return JsonResult.ok(categoryService.querySubLevelCat(rootCatId)); }/** * 查询所有二级分类 * @param fatherId * @return */ @Override public List<CategoryVO> querySubLevelCat(Integer fatherId) { // 从数据库查询符合条件的二级分类DO列表 List<CategoryDO> categoryDOS = categoryMapper.selectList(new QueryWrapper<CategoryDO>() .eq("type", CategoryType.SECOND.type) .eq("father_id", fatherId)); List<CategoryVO> categoryVOS = categoryDOS.stream() .map(categoryDO -> { CategoryVO categoryVO = CategoryVO.builder() .id(categoryDO.getId()) .name(categoryDO.getName()) .type(categoryDO.getType()) .fatherId(categoryDO.getFatherId()) .subCatList(queryThirdLevelCat(categoryDO.getId())) .build(); return categoryVO; }) .collect(Collectors.toCollection(ArrayList::new)); return categoryVOS; } /** * 查询所有三级分类 * @param fatherId * @return */ @Override public List<SubCategoryVO> queryThirdLevelCat(Integer fatherId) { List<CategoryDO> categoryDOS = categoryMapper.selectList(new QueryWrapper<CategoryDO>() .eq("type", CategoryType.THIRD.type) .eq("father_id", fatherId)); List<SubCategoryVO> subCategoryVOS = categoryDOS.stream() .map(categoryDO -> { SubCategoryVO subCategoryVO = SubCategoryVO.builder() .subId(categoryDO.getId()) .subName(categoryDO.getName()) .subType(categoryDO.getType()) .subFatherId(categoryDO.getFatherId()) .build(); return subCategoryVO; }) .collect(Collectors.toCollection(ArrayList::new)); return subCategoryVOS; }首页推荐
分类表
商品表
商品图片表
/** * 查询每个一级分类下的最新6条商品 * @param rootCatId * @return */ @Override public List<NewItemsVO> querySixNewItems(Integer rootCatId) { List<ItemsDO> itemsDOS = itemsMapper.selectList(new QueryWrapper<ItemsDO>().eq("root_cat_id", rootCatId).eq("on_off_status", 1)); CategoryDO categoryDO = categoryMapper.selectOne(new QueryWrapper<CategoryDO>().eq("id", rootCatId)); NewItemsVO newItemsVO = NewItemsVO.builder() .rootCatId(rootCatId) .rootCatName(categoryDO.getName()) .slogan(categoryDO.getSlogan()) .catImage(categoryDO.getCatImage()) .bgColor(categoryDO.getBgColor()) .simpleItemList(itemsDOS.stream().map(itemsDO -> querySimpleItemList(itemsDO.getId(), itemsDO.getItemName())).collect(Collectors.toCollection(ArrayList::new))) .build(); return Collections.singletonList(newItemsVO); } /** * 查询商品列表 * @param id * @return */ @Override public SimpleItemVO querySimpleItemList(String id,String itemName) { ItemsImgDO itemsImgDO = itemsImgMapper.selectOne(new QueryWrapper<ItemsImgDO>().eq("item_id", id).eq("is_main", 1)); SimpleItemVO simpleItemVO = SimpleItemVO.builder() .itemId(id) .itemName(itemName) .itemUrl(itemsImgDO.getUrl()) .build(); return simpleItemVO; }/** * 查询每个一级分类下最新的6条商品 * @param rootCatId * @return */ @GetMapping("/sixNewItems/{rootCatId}") public JsonResult sixNewItems(@PathVariable Integer rootCatId) { if (rootCatId == null){ return JsonResult.errorMsg("分类不存在"); } return JsonResult.ok(categoryService.querySixNewItems(rootCatId)); }商品详情功能
商品规格表
商品参数表
@Service public class ItemsServiceImpl implements ItemsService { @Autowired private ItemsMapper itemsMapper; @Autowired private ItemsImgService itemsImgService; @Autowired private ItemsSpecMapper itemsSpecMapper; @Autowired private ItemsParamService itemsParamService; @Autowired private ItemsImgMapper itemsImgMapper; /** * 根据商品id查询商品 * * @param itemId * @return */ @Override public ItemsDO queryItemById(String itemId) { return itemsMapper.selectById(itemId); } @Override public List<ItemsImgDO> queryItemImgList(String itemId) { return itemsImgMapper.selectList(new QueryWrapper<ItemsImgDO>().eq("item_id",itemId)); } @Override public List<ItemsSpecDO> queryItemSpecList(String itemId) { return itemsSpecMapper.selectList(new QueryWrapper<ItemsSpecDO>().eq("item_id",itemId)); } @Override public ItemsParamDO queryItemParam(String itemId) { return itemsParamService.getOne(new QueryWrapper<ItemsParamDO>().eq("item_id",itemId)); } }商品评价
商品评价表
@GetMapping("/commentLevel") public JsonResult commentLevel(@RequestParam String itemId) { if (StringUtils.isBlank(itemId)) { return JsonResult.errorMsg("商品不存在"); } CommentLevelCountsVO countsVO = itemsService.queryCommentCounts(itemId); return JsonResult.ok(countsVO); }/** * 查询商品评价等级数量 * * @param itemId * @return */ @Override public CommentLevelCountsVO queryCommentCounts(String itemId) { Integer goodCounts = getCountByLevel(itemId, CommentLevel.SELLER_LEVEL_ONE.type); Integer normalCounts = getCountByLevel(itemId, CommentLevel.SELLER_LEVEL_TWO.type); Integer badCounts = getCountByLevel(itemId, CommentLevel.SELLER_LEVEL_THREE.type); CommentLevelCountsVO commentLevelCountsVO = new CommentLevelCountsVO(); commentLevelCountsVO.setTotalCounts(goodCounts + normalCounts + badCounts); commentLevelCountsVO.setGoodCounts(goodCounts); commentLevelCountsVO.setNormalCounts(normalCounts); commentLevelCountsVO.setBadCounts(badCounts); return commentLevelCountsVO; } Integer getCountByLevel(String itemId,Integer level){ ItemsComments itemsComments = new ItemsComments(); itemsComments.setItemId(itemId); if (level != null){ itemsComments.setCommentLevel(level); } return itemsCommentsMapper.selectCount(new QueryWrapper<ItemsComments>().eq("item_id",itemId).eq("comment_level",level)); }分页查询评价
分页插件
@Configuration public class MybatisPlusPageConfig { /** * 新的分页插件,一缓和二缓遵循mybatis的规则, * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }代码编写
/** * 分页查询商品评价 * * @param itemId * @param level * @param page * @param pageSize * @return */ @Override public Map<String, Object> queryPageComments(String itemId, Integer level, Integer page, Integer pageSize) { // 1. 基础参数校验(避免非法参数导致查询异常) if (page == null || page < 1) { page = 1; } if (pageSize == null || pageSize < 1 || pageSize > 100) { pageSize = 10; // 限制最大每页100条,防止查询压力过大 } if (level == null) { level = 1; // 兼容前端可能传递的空值,默认查好评 } // 2. 构建 MyBatis-Plus 分页对象,执行查询 Page<ItemsComments> pageInfo = new Page<>(page, pageSize); QueryWrapper<ItemsComments> queryWrapper = new QueryWrapper<ItemsComments>() .eq("item_id", itemId) // 按商品ID筛选 .eq("comment_level", level); // 按评论等级筛选 // 执行分页查询 Page<ItemsComments> selectPage = itemsCommentsMapper.selectPage(pageInfo, queryWrapper); // 3. 修复 ItemCommentVO 构建问题(关键:builder() 必须赋值给 VO 对象,否则返回空数据) List<ItemCommentVO> itemCommentVOS = new ArrayList<>(); for (ItemsComments itemsComments : selectPage.getRecords()) { // 正确构建 ItemCommentVO:直接用 builder() 构建并赋值,无需先 new 空对象 ItemCommentVO itemCommentVO = ItemCommentVO.builder() .commentLevel(itemsComments.getCommentLevel()) .content(itemsComments.getContent()) .specName(itemsComments.getSepcName()) // 注意:若数据库字段是 specName,此处修正笔误为 getSpecName() .createdTime(itemsComments.getCreatedTime()) .userFace(usersMapper.selectById(itemsComments.getUserId()).getFace()) .nickname(usersMapper.selectById(itemsComments.getUserId()).getNickname()) .build(); // 完成构建,字段才会有值 itemCommentVOS.add(itemCommentVO); } // 4. 构建前端需要的返回对象(字段名完全匹配前端取值逻辑) Map<String, Object> resultMap = new HashMap<>(); resultMap.put("rows", itemCommentVOS); // 对应前端 grid.rows(评论列表) resultMap.put("total", selectPage.getPages()); // 对应前端 grid.total(总页数,赋值给 maxPage) resultMap.put("records", selectPage.getTotal()); // 对应前端 grid.records(总记录数,赋值给 total) // 5. 返回该 map,供 Controller 层包装后返回给前端 return resultMap; }/** * 商品评论 * * @param itemId * @param level * @param page * @param pageSize * @return */ @GetMapping("/comments") public JsonResult comments(@RequestParam String itemId, @RequestParam(defaultValue = "1") Integer level, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize) { // 1. 校验商品ID(和前端逻辑一致,提示商品不存在) if (StringUtils.isBlank(itemId)) { return JsonResult.errorMsg("商品不存在"); } // 2. 调用 Service 层,获取适配前端的分页数据(包含 rows、total、records) Map<String, Object> commentPageData = itemsService.queryPageComments(itemId, level, page, pageSize); // 3. 直接返回该数据,前端 res.data.data 即可拿到 grid 对象 return JsonResult.ok(commentPageData); }脱敏工具类
public class DesensitizationUtil { private static final int SIZE = 6; private static final String SYMBOL = "*"; /** * 通用脱敏方法 * @param value * @return */ public static String commonDisplay(String value) { if (null == value || "".equals(value)) { return value; } int len = value.length(); int pamaone = len / 2; int pamatwo = pamaone - 1; int pamathree = len % 2; StringBuilder stringBuilder = new StringBuilder(); if (len <= 2) { if (pamathree == 1) { return SYMBOL; } stringBuilder.append(SYMBOL); stringBuilder.append(value.charAt(len - 1)); } else { if (pamatwo <= 0) { stringBuilder.append(value.substring(0, 1)); stringBuilder.append(SYMBOL); stringBuilder.append(value.substring(len - 1, len)); } else if (pamatwo >= SIZE / 2 && SIZE + 1 != len) { int pamafive = (len - SIZE) / 2; stringBuilder.append(value.substring(0, pamafive)); for (int i = 0; i < SIZE; i++) { stringBuilder.append(SYMBOL); } if ((pamathree == 0 && SIZE / 2 == 0) || (pamathree != 0 && SIZE % 2 != 0)) { stringBuilder.append(value.substring(len - pamafive, len)); } else { stringBuilder.append(value.substring(len - (pamafive + 1), len)); } } else { int pamafour = len - 2; stringBuilder.append(value.substring(0, 1)); for (int i = 0; i < pamafour; i++) { stringBuilder.append(SYMBOL); } stringBuilder.append(value.substring(len - 1, len)); } } return stringBuilder.toString(); } }商品搜索
/** * 商品搜索 * * @param keywords * @param sort * @param page * @param pageSize * @return */ @GetMapping("/search") public JsonResult search(@RequestParam String keywords, @RequestParam(defaultValue = "") String sort, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize) { // 1. 校验 if (StringUtils.isBlank(keywords)) { return JsonResult.errorMsg(null); } // 2. 调用 Service 层,获取适配前端的分页数据(包含 rows、total、records) Map<String, Object> result = itemsService.searchItems(keywords, sort, page, pageSize); return JsonResult.ok(result); }/** * 商品搜索 * * @param keywords * @param sort * @param page * @param pageSize * @return */ @Override public Map<String, Object> searchItems(String keywords, String sort, Integer page, Integer pageSize) { // 1. 分页参数校验与默认值处理(避免非法参数导致异常,和之前评论分页逻辑一致) if (page == null || page < 1) { page = 1; } if (pageSize == null || pageSize < 1 || pageSize > 100) { pageSize = 10; // 限制最大每页100条,防止查询压力过大 } // 排序参数默认值(匹配 XML 中的 otherwise,默认按商品名称升序) if (StringUtils.isBlank(sort)) { sort = "k"; } // 2. 构建 MyBatis 分页对象(注意:这里因为你用了自定义 XML SQL,需手动处理分页) // 方式:先构建分页参数,再封装查询条件 Map(和 XML 中的 parameterType="Map" 对应) Page<SearchItemsVO> pageInfo = new Page<>(page, pageSize); // 3. 封装 XML SQL 需要的查询参数(key 必须和 XML 中的 paramsMap 对应) Map<String, Object> paramsMap = new HashMap<>(); paramsMap.put("keywords", keywords); // 搜索关键词 paramsMap.put("sort", sort); // 排序条件(c: 销量降序,p: 价格升序,k: 默认名称升序) // 4. 执行分页查询(分两步:① 查询符合条件的总记录数 ② 查询当前页的商品列表) // 注意:你现有 XML 中没有总记录数查询,需先补充一个 count 方法(下面会给出补充说明),这里先按完整逻辑实现 // ① 查询总记录数(用于计算总页数) Long totalRecords = itemsMapperCustom.countSearchItems(paramsMap); // ② 查询当前页的商品列表(传递分页参数,适配自定义 XML 分页) // 给 paramsMap 补充分页参数(limit 起始位置、每页条数) paramsMap.put("start", (page - 1) * pageSize); paramsMap.put("pageSize", pageSize); List<SearchItemsVO> searchItemsVOList = itemsMapperCustom.searchItems(paramsMap); // 5. 封装分页对象(计算总页数) pageInfo.setRecords(searchItemsVOList); pageInfo.setTotal(totalRecords); long totalPages = (totalRecords + pageSize - 1) / pageSize; // 向上取整计算总页数 pageInfo.setPages(totalPages); // 6. 构建前端需要的返回 Map(和评论分页格式一致,字段名严格匹配) Map<String, Object> resultMap = new HashMap<>(); resultMap.put("rows", searchItemsVOList); // 商品列表(对应前端 grid.rows) resultMap.put("total", pageInfo.getPages()); // 总页数(对应前端 grid.total → maxPage) resultMap.put("records", pageInfo.getTotal()); // 总记录数(对应前端 grid.records → total) // 7. 返回结果 Map return resultMap; }package com.guslegend.mapper; import com.guslegend.vo.SearchItemsVO; import java.util.List; import java.util.Map; public interface ItemsMapperCustom { /** * 商品搜索 - 查询符合条件的总记录数 */ Long countSearchItems(Map<String, Object> paramsMap); /** * 商品搜索 - 分页查询商品列表(补充分页参数) */ List<SearchItemsVO> searchItems(Map<String, Object> paramsMap); }<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="com.guslegend.mapper.ItemsMapperCustom" > <!-- 商品搜索 - 查询总记录数 --> <select id="countSearchItems" parameterType="Map" resultType="java.lang.Long"> SELECT COUNT(DISTINCT i.id) FROM items i LEFT JOIN items_img ii ON i.id = ii.item_id LEFT JOIN (SELECT item_id,MIN(price_discount) as price_discount from items_spec GROUP BY item_id) tempSpec ON i.id = tempSpec.item_id WHERE ii.is_main = 1 <!-- 去掉 paramsMap.,直接用 keywords --> <if test="keywords != null and keywords != '' "> AND i.item_name like concat('%', #{keywords}, '%') </if> </select> <!-- 商品搜索 - 分页查询商品列表 --> <select id="searchItems" parameterType="Map" resultType="com.guslegend.vo.SearchItemsVO"> SELECT i.id as itemId, i.item_name as itemName, i.sell_counts as sellCounts, ii.url as imgUrl, tempSpec.price_discount as price FROM items i LEFT JOIN items_img ii on i.id = ii.item_id LEFT JOIN (SELECT item_id,MIN(price_discount) as price_discount from items_spec GROUP BY item_id) tempSpec on i.id = tempSpec.item_id WHERE ii.is_main = 1 <!-- 去掉 paramsMap.,直接用 keywords --> <if test="keywords != null and keywords != '' "> AND i.item_name like concat('%', #{keywords}, '%') </if> ORDER BY <choose> <!-- 去掉 paramsMap.,直接用 sort --> <when test="sort == "c" "> i.sell_counts desc </when> <!-- 去掉 paramsMap.,直接用 sort --> <when test="sort == "p" "> tempSpec.price_discount asc </when> <otherwise> i.item_name asc </otherwise> </choose> <!-- 去掉 paramsMap.,直接用 start 和 pageSize --> LIMIT #{start}, #{pageSize} </select> </mapper>购物车存储功能
商品添加到购物车
@RestController @RequestMapping("/orderStatusDO") public class ShopCartController { @PostMapping("/add") public JsonResult add(@RequestParam String userId, @RequestBody ShopCartBO shopCartBO, HttpServletRequest request, HttpServletResponse response){ if (StringUtils.isBlank(userId)){ return JsonResult.errorMsg("用户不存在"); } //TODO 后端同步到redis return JsonResult.ok(); } }渲染购物车
@GetMapping("/refresh") public JsonResult refresh(@RequestParam String itemSpecIds) { if (StringUtils.isBlank(itemSpecIds)) { return JsonResult.ok(Collections.emptyList()); } List<ShopCartVO> list = itemsService.queryItemsBySpecIds(itemSpecIds); return JsonResult.ok(list); }@Override public List<ShopCartVO> queryItemsBySpecIds(String itemSpecIdsStr) { // 1. 校验字符串参数 if (StringUtils.isBlank(itemSpecIdsStr)) { return Collections.emptyList(); } // 2. 拆分字符串为 List<String>(按逗号分割,过滤空值) String[] specIdArray = itemSpecIdsStr.split(","); // 优化:避免数组转 List 后无法修改,同时过滤空字符串 List<String> specIdList = new ArrayList<>(); for (String specId : specIdArray) { if (StringUtils.isNotBlank(specId)) { specIdList.add(specId.trim()); } } return itemsMapperCustom.queryItemsBySpecIds(specIdList); }List<ShopCartVO> queryItemsBySpecIds(@Param("specIdList") List<String> specIdList);<select id="queryItemsBySpecIds" parameterType="java.util.List" resultType="com.guslegend.vo.ShopCartVO"> SELECT t_items.id as itemId, t_items.item_name as itemName, t_items_img.url as itemImgUrl, t_items_spec.id as specId, t_items_spec.`name` as specName, t_items_spec.price_discount as priceDiscount, t_items_spec.price_normal as priceNormal FROM items_spec t_items_spec LEFT JOIN items t_items ON t_items.id = t_items_spec.item_id LEFT JOIN items_img t_items_img ON t_items_img.item_id = t_items.id WHERE t_items_img.is_main = 1 AND t_items_spec.id IN <foreach collection="specIdList" index="index" item="specId" open="(" separator="," close=")"> #{specId} </foreach> </select>