电商轮播图

@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>
