فهرست منبع

商品:增加商品浏览记录

owen 1 سال پیش
والد
کامیت
f374e778bb
16فایلهای تغییر یافته به همراه550 افزوده شده و 3 حذف شده
  1. 22 0
      sql/mysql/optinal/product_browse_history.sql
  2. 39 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/ProductBrowseHistoryController.java
  3. 33 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryPageReqVO.java
  4. 34 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java
  5. 90 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/AppProductBrowseHistoryController.java
  6. 19 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryDeleteReqVO.java
  7. 23 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryPageReqVO.java
  8. 29 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryRespVO.java
  9. 8 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java
  10. 42 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/history/ProductBrowseHistoryDO.java
  11. 52 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/history/ProductBrowseHistoryMapper.java
  12. 16 3
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java
  13. 58 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/history/ProductBrowseHistoryService.java
  14. 72 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/history/ProductBrowseHistoryServiceImpl.java
  15. 8 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java
  16. 5 0
      yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java

+ 22 - 0
sql/mysql/optinal/product_browse_history.sql

@@ -0,0 +1,22 @@
+CREATE TABLE product_browse_history
+(
+    id           bigint AUTO_INCREMENT COMMENT '记录编号'
+        PRIMARY KEY,
+    user_id      bigint                                NOT NULL COMMENT '用户编号',
+    spu_id       bigint                                NOT NULL COMMENT '商品 SPU 编号',
+    user_deleted bit         DEFAULT b'0'              NOT NULL COMMENT '用户是否删除',
+    creator      varchar(64) DEFAULT ''                NULL COMMENT '创建者',
+    create_time  datetime    DEFAULT CURRENT_TIMESTAMP NOT NULL COMMENT '创建时间',
+    updater      varchar(64) DEFAULT ''                NULL COMMENT '更新者',
+    update_time  datetime    DEFAULT CURRENT_TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    deleted      bit         DEFAULT b'0'              NOT NULL COMMENT '是否删除',
+    tenant_id    bigint      DEFAULT 0                 NOT NULL COMMENT '租户编号'
+)
+    COMMENT '商品浏览记录表';
+
+CREATE INDEX idx_spuId
+    ON product_browse_history (spu_id);
+
+CREATE INDEX idx_userId
+    ON product_browse_history (user_id);
+

+ 39 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/ProductBrowseHistoryController.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.product.controller.admin.history;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.product.controller.admin.history.vo.ProductBrowseHistoryPageReqVO;
+import cn.iocoder.yudao.module.product.controller.admin.history.vo.ProductBrowseHistoryRespVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.history.ProductBrowseHistoryDO;
+import cn.iocoder.yudao.module.product.service.history.ProductBrowseHistoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 商品浏览记录")
+@RestController
+@RequestMapping("/product/browse-history")
+@Validated
+public class ProductBrowseHistoryController {
+
+    @Resource
+    private ProductBrowseHistoryService browseHistoryService;
+
+    @GetMapping("/page")
+    @Operation(summary = "获得商品浏览记录分页")
+    @PreAuthorize("@ss.hasPermission('product:browse-history:query')")
+    public CommonResult<PageResult<ProductBrowseHistoryRespVO>> getBrowseHistoryPage(@Valid ProductBrowseHistoryPageReqVO pageReqVO) {
+        PageResult<ProductBrowseHistoryDO> pageResult = browseHistoryService.getBrowseHistoryPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, ProductBrowseHistoryRespVO.class));
+    }
+
+}

+ 33 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryPageReqVO.java

@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.product.controller.admin.history.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.SortablePageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 商品浏览记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ProductBrowseHistoryPageReqVO extends SortablePageParam {
+
+    @Schema(description = "用户编号", example = "4314")
+    private Long userId;
+
+    @Schema(description = "用户是否删除", example = "false")
+    private Boolean userDeleted;
+
+    @Schema(description = "商品 SPU 编号", example = "42")
+    private Long spuId;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 34 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/history/vo/ProductBrowseHistoryRespVO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.product.controller.admin.history.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 商品浏览记录 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class ProductBrowseHistoryRespVO {
+
+    @Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26055")
+    @ExcelProperty("记录编号")
+    private Long id;
+
+    @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "4314")
+    @ExcelProperty("用户编号")
+    private Long userId;
+
+    @Schema(description = "用户是否删除", example = "false")
+    private Boolean userDeleted;
+
+    @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "42")
+    @ExcelProperty("商品 SPU 编号")
+    private Long spuId;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 90 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/AppProductBrowseHistoryController.java

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.product.controller.app.history;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated;
+import cn.iocoder.yudao.module.product.controller.admin.history.vo.ProductBrowseHistoryPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.history.vo.AppProductBrowseHistoryDeleteReqVO;
+import cn.iocoder.yudao.module.product.controller.app.history.vo.AppProductBrowseHistoryPageReqVO;
+import cn.iocoder.yudao.module.product.controller.app.history.vo.AppProductBrowseHistoryRespVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.history.ProductBrowseHistoryDO;
+import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
+import cn.iocoder.yudao.module.product.service.history.ProductBrowseHistoryService;
+import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
+
+@Tag(name = "用户 APP - 商品浏览记录")
+@RestController
+@RequestMapping("/product/browse-history")
+public class AppProductBrowseHistoryController {
+
+    @Resource
+    private ProductBrowseHistoryService productBrowseHistoryService;
+    @Resource
+    private ProductSpuService productSpuService;
+
+    @DeleteMapping(value = "/delete")
+    @Operation(summary = "删除商品浏览记录")
+    @PreAuthenticated
+    public CommonResult<Boolean> deleteBrowseHistory(@RequestBody @Valid AppProductBrowseHistoryDeleteReqVO reqVO) {
+        productBrowseHistoryService.hideUserBrowseHistory(getLoginUserId(), reqVO.getSpuIds());
+        return success(Boolean.TRUE);
+    }
+
+    @DeleteMapping(value = "/clean")
+    @Operation(summary = "清空商品浏览记录")
+    @PreAuthenticated
+    public CommonResult<Boolean> cleanBrowseHistory() {
+        productBrowseHistoryService.hideUserBrowseHistory(getLoginUserId(), null);
+        return success(Boolean.TRUE);
+    }
+
+    @GetMapping(value = "/get-count")
+    @Operation(summary = "获得商品浏览记录数量")
+    @PreAuthenticated
+    public CommonResult<Long> getBrowseHistoryCount() {
+        return success(productBrowseHistoryService.getBrowseHistoryCount(getLoginUserId(), false));
+    }
+
+    @GetMapping(value = "/page")
+    @Operation(summary = "获得商品浏览记录分页")
+    @PreAuthenticated
+    public CommonResult<PageResult<AppProductBrowseHistoryRespVO>> getBrowseHistoryPage(AppProductBrowseHistoryPageReqVO reqVO) {
+        ProductBrowseHistoryPageReqVO pageReqVO = BeanUtils.toBean(reqVO, ProductBrowseHistoryPageReqVO.class);
+        pageReqVO.setUserId(getLoginUserId());
+        // 排除用户已删除的(隐藏的)
+        pageReqVO.setUserDeleted(false);
+        PageResult<ProductBrowseHistoryDO> pageResult = productBrowseHistoryService.getBrowseHistoryPage(pageReqVO);
+        if (CollUtil.isEmpty(pageResult.getList())) {
+            return success(PageResult.empty());
+        }
+
+        // 得到商品 spu 信息
+        Set<Long> spuIds = convertSet(pageResult.getList(), ProductBrowseHistoryDO::getSpuId);
+        Map<Long, ProductSpuDO> spuMap = convertMap(productSpuService.getSpuList(spuIds), ProductSpuDO::getId);
+
+        // 转换 VO 结果
+        PageResult<AppProductBrowseHistoryRespVO> result = BeanUtils.toBean(pageResult, AppProductBrowseHistoryRespVO.class,
+                vo -> Optional.ofNullable(spuMap.get(vo.getSpuId())).ifPresent(spu -> {
+                    vo.setSpuName(spu.getName());
+                    vo.setPicUrl(spu.getPicUrl());
+                }));
+        return success(result);
+    }
+
+}

+ 19 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryDeleteReqVO.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.product.controller.app.history.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import lombok.Data;
+
+import java.util.List;
+
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
+
+@Schema(description = "用户 APP - 删除商品浏览记录的 Request VO")
+@Data
+public class AppProductBrowseHistoryDeleteReqVO {
+
+    @Schema(description = "商品 SPU 编号数组", requiredMode = REQUIRED, example = "29502")
+    @NotEmpty(message = "商品 SPU 编号数组不能为空")
+    private List<Long> spuIds;
+
+}

+ 23 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryPageReqVO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.module.product.controller.app.history.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "用户 APP - 商品浏览记录分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppProductBrowseHistoryPageReqVO extends PageParam {
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 29 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/history/vo/AppProductBrowseHistoryRespVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.product.controller.app.history.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
+
+@Schema(description = "用户 App - 商品浏览记录 Response VO")
+@Data
+public class AppProductBrowseHistoryRespVO {
+
+    @Schema(description = "编号", requiredMode = REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "商品 SPU 编号", requiredMode = REQUIRED, example = "29502")
+    private Long spuId;
+
+    // ========== 商品相关字段 ==========
+
+    @Schema(description = "商品 SPU 名称", example = "赵六")
+    private String spuName;
+
+    @Schema(description = "商品封面图", example = "https://domain/pic.png")
+    private String picUrl;
+
+    @Schema(description = "商品单价", example = "100")
+    private Integer price;
+
+}

+ 8 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java

@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.product.convert.spu.ProductSpuConvert;
 import cn.iocoder.yudao.module.product.dal.dataobject.sku.ProductSkuDO;
 import cn.iocoder.yudao.module.product.dal.dataobject.spu.ProductSpuDO;
 import cn.iocoder.yudao.module.product.enums.spu.ProductSpuStatusEnum;
+import cn.iocoder.yudao.module.product.service.history.ProductBrowseHistoryService;
 import cn.iocoder.yudao.module.product.service.sku.ProductSkuService;
 import cn.iocoder.yudao.module.product.service.spu.ProductSpuService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -48,6 +49,8 @@ public class AppProductSpuController {
     private ProductSpuService productSpuService;
     @Resource
     private ProductSkuService productSkuService;
+    @Resource
+    private ProductBrowseHistoryService productBrowseHistoryService;
 
     @Resource
     private MemberLevelApi memberLevelApi;
@@ -122,6 +125,11 @@ public class AppProductSpuController {
             throw exception(SPU_NOT_ENABLE);
         }
 
+        // 增加浏览量
+        productSpuService.updateBrowseCount(id, 1);
+        // 保存浏览记录
+        productBrowseHistoryService.createBrowseHistory(getLoginUserId(), id);
+
         // 拼接返回
         List<ProductSkuDO> skus = productSkuService.getSkuListBySpuId(spu.getId());
         AppProductSpuDetailRespVO detailVO = ProductSpuConvert.INSTANCE.convertForGetSpuDetail(spu, skus);

+ 42 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/dataobject/history/ProductBrowseHistoryDO.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.product.dal.dataobject.history;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 商品浏览记录 DO
+ *
+ * @author owen
+ */
+@TableName("product_browse_history")
+@KeySequence("product_browse_history_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ProductBrowseHistoryDO extends BaseDO {
+
+    /**
+     * 记录编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 商品 SPU 编号
+     */
+    private Long spuId;
+    /**
+     * 用户编号
+     */
+    private Long userId;
+    /**
+     * 用户是否删除
+     */
+    private Boolean userDeleted;
+
+}

+ 52 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/history/ProductBrowseHistoryMapper.java

@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.module.product.dal.mysql.history;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.product.controller.admin.history.vo.ProductBrowseHistoryPageReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.history.ProductBrowseHistoryDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+
+/**
+ * 商品浏览记录 Mapper
+ *
+ * @author owen
+ */
+@Mapper
+public interface ProductBrowseHistoryMapper extends BaseMapperX<ProductBrowseHistoryDO> {
+
+    default PageResult<ProductBrowseHistoryDO> selectPage(ProductBrowseHistoryPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ProductBrowseHistoryDO>()
+                .eqIfPresent(ProductBrowseHistoryDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(ProductBrowseHistoryDO::getUserDeleted, reqVO.getUserDeleted())
+                .eqIfPresent(ProductBrowseHistoryDO::getSpuId, reqVO.getSpuId())
+                .betweenIfPresent(ProductBrowseHistoryDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(ProductBrowseHistoryDO::getId));
+    }
+
+    default void updateUserDeletedByUserId(Long userId, Collection<Long> spuIds, Boolean userDeleted) {
+        update(new LambdaUpdateWrapper<ProductBrowseHistoryDO>()
+                .eq(ProductBrowseHistoryDO::getUserId, userId)
+                .in(CollUtil.isNotEmpty(spuIds), ProductBrowseHistoryDO::getSpuId, spuIds)
+                .set(ProductBrowseHistoryDO::getUserDeleted, userDeleted));
+    }
+
+    default Long selectCountByUserIdAndUserDeleted(Long userId, Boolean userDeleted) {
+        return selectCount(new LambdaQueryWrapperX<ProductBrowseHistoryDO>()
+                .eq(ProductBrowseHistoryDO::getUserId, userId)
+                .eqIfPresent(ProductBrowseHistoryDO::getUserDeleted, userDeleted));
+    }
+
+    default Page<ProductBrowseHistoryDO> selectPageByUserIdOrderByCreateTimeAsc(Long userId) {
+        Page<ProductBrowseHistoryDO> page = Page.of(0, 1);
+        return selectPage(page, new LambdaQueryWrapperX<ProductBrowseHistoryDO>()
+                .eqIfPresent(ProductBrowseHistoryDO::getUserId, userId)
+                .orderByAsc(ProductBrowseHistoryDO::getCreateTime));
+    }
+
+}

+ 16 - 3
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/dal/mysql/spu/ProductSpuMapper.java

@@ -71,7 +71,7 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
             query.eq(ProductSpuDO::getRecommendBenefit, true);
         } else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_BEST)) {
             query.eq(ProductSpuDO::getRecommendBest, true);
-        }  else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_NEW)) {
+        } else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_NEW)) {
             query.eq(ProductSpuDO::getRecommendNew, true);
         } else if (ObjUtil.equal(pageReqVO.getRecommendType(), AppProductSpuPageReqVO.RECOMMEND_TYPE_GOOD)) {
             query.eq(ProductSpuDO::getRecommendGood, true);
@@ -141,8 +141,8 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
     /**
      * 添加后台 Tab 选项的查询条件
      *
-     * @param tabType      标签类型
-     * @param query 查询条件
+     * @param tabType 标签类型
+     * @param query   查询条件
      */
     static void appendTabQuery(Integer tabType, LambdaQueryWrapperX<ProductSpuDO> query) {
         // 出售中商品
@@ -169,4 +169,17 @@ public interface ProductSpuMapper extends BaseMapperX<ProductSpuDO> {
         }
     }
 
+    /**
+     * 更新商品 SPU 浏览量
+     *
+     * @param id        商品 SPU 编号
+     * @param incrCount 增加的数量
+     */
+    default void updateBrowseCount(Long id, int incrCount) {
+        LambdaUpdateWrapper<ProductSpuDO> updateWrapper = new LambdaUpdateWrapper<ProductSpuDO>()
+                .setSql(" browse_count = browse_count +" + incrCount)
+                .eq(ProductSpuDO::getId, id);
+        update(null, updateWrapper);
+    }
+
 }

+ 58 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/history/ProductBrowseHistoryService.java

@@ -0,0 +1,58 @@
+package cn.iocoder.yudao.module.product.service.history;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.history.vo.ProductBrowseHistoryPageReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.history.ProductBrowseHistoryDO;
+
+import java.util.Collection;
+
+/**
+ * 商品浏览记录 Service 接口
+ *
+ * @author owen
+ */
+public interface ProductBrowseHistoryService {
+
+    /**
+     * 创建商品浏览记录
+     *
+     * @param userId 用户编号
+     * @param spuId  SPU 编号
+     * @return 编号
+     */
+    Long createBrowseHistory(Long userId, Long spuId);
+
+    /**
+     * 隐藏用户商品浏览记录
+     *
+     * @param userId 用户编号
+     * @param spuId  SPU 编号
+     */
+    void hideUserBrowseHistory(Long userId, Collection<Long> spuId);
+
+    /**
+     * 获得商品浏览记录
+     *
+     * @param id 编号
+     * @return 商品浏览记录
+     */
+    ProductBrowseHistoryDO getBrowseHistory(Long id);
+
+    /**
+     * 获取用户记录数量
+     *
+     * @param userId      用户编号
+     * @param userDeleted 用户是否删除
+     * @return 数量
+     */
+    Long getBrowseHistoryCount(Long userId, Boolean userDeleted);
+
+    /**
+     * 获得商品浏览记录分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 商品浏览记录分页
+     */
+    PageResult<ProductBrowseHistoryDO> getBrowseHistoryPage(ProductBrowseHistoryPageReqVO pageReqVO);
+
+}

+ 72 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/history/ProductBrowseHistoryServiceImpl.java

@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.product.service.history;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.product.controller.admin.history.vo.ProductBrowseHistoryPageReqVO;
+import cn.iocoder.yudao.module.product.dal.dataobject.history.ProductBrowseHistoryDO;
+import cn.iocoder.yudao.module.product.dal.mysql.history.ProductBrowseHistoryMapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+
+/**
+ * 商品浏览记录 Service 实现类
+ *
+ * @author owen
+ */
+@Service
+@Validated
+public class ProductBrowseHistoryServiceImpl implements ProductBrowseHistoryService {
+    private static final int USER_STORE_MAXIMUM = 100;
+
+    @Resource
+    private ProductBrowseHistoryMapper browseHistoryMapper;
+
+    @Override
+    public Long createBrowseHistory(Long userId, Long spuId) {
+        // 情况一:同一个商品,只保留最新的一条记录
+        ProductBrowseHistoryDO historyDO = browseHistoryMapper.selectOne(ProductBrowseHistoryDO::getUserId, userId, ProductBrowseHistoryDO::getSpuId, spuId);
+        if (historyDO != null) {
+            browseHistoryMapper.deleteById(historyDO);
+        } else {
+            // 情况二:限制每个用户的浏览记录的条数
+            Page<ProductBrowseHistoryDO> pageResult = browseHistoryMapper.selectPageByUserIdOrderByCreateTimeAsc(userId);
+            if (pageResult.getTotal() >= USER_STORE_MAXIMUM) {
+                // 删除最早的一条
+                browseHistoryMapper.deleteById(CollUtil.getFirst(pageResult.getRecords()));
+            }
+        }
+
+        // 插入
+        ProductBrowseHistoryDO browseHistory = new ProductBrowseHistoryDO()
+                .setUserId(userId)
+                .setSpuId(spuId);
+        browseHistoryMapper.insert(browseHistory);
+        // 返回
+        return browseHistory.getId();
+    }
+
+    @Override
+    public void hideUserBrowseHistory(Long userId, Collection<Long> spuIds) {
+        browseHistoryMapper.updateUserDeletedByUserId(userId, spuIds, true);
+    }
+
+    @Override
+    public ProductBrowseHistoryDO getBrowseHistory(Long id) {
+        return browseHistoryMapper.selectById(id);
+    }
+
+    @Override
+    public Long getBrowseHistoryCount(Long userId, Boolean userDeleted) {
+        return browseHistoryMapper.selectCountByUserIdAndUserDeleted(userId, userDeleted);
+    }
+
+    @Override
+    public PageResult<ProductBrowseHistoryDO> getBrowseHistoryPage(ProductBrowseHistoryPageReqVO pageReqVO) {
+        return browseHistoryMapper.selectPage(pageReqVO);
+    }
+
+}

+ 8 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuService.java

@@ -148,4 +148,12 @@ public interface ProductSpuService {
      */
     List<ProductSpuDO> validateSpuList(Collection<Long> ids);
 
+    /**
+     * 更新商品 SPU 浏览量
+     *
+     * @param id        商品 SPU 编号
+     * @param incrCount 增加的数量
+     */
+    void updateBrowseCount(Long id, int incrCount);
+
 }

+ 5 - 0
yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/service/spu/ProductSpuServiceImpl.java

@@ -157,6 +157,11 @@ public class ProductSpuServiceImpl implements ProductSpuService {
         return list;
     }
 
+    @Override
+    public void updateBrowseCount(Long id, int incrCount) {
+        productSpuMapper.updateBrowseCount(id , incrCount);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void deleteSpu(Long id) {