Просмотр исходного кода

营销活动: 新增文章分类管理

puhui999 1 год назад
Родитель
Сommit
a47ab32800
19 измененных файлов с 813 добавлено и 1 удалено
  1. 10 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/DictTypeConstants.java
  2. 3 0
      yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
  3. 111 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/ArticleCategoryController.java
  4. 30 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryBaseVO.java
  5. 14 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryCreateReqVO.java
  6. 39 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryExcelVO.java
  7. 25 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryExportReqVO.java
  8. 30 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryPageReqVO.java
  9. 22 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryRespVO.java
  10. 16 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategorySimpleRespVO.java
  11. 20 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryUpdateReqVO.java
  12. 35 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/articlecategory/ArticleCategoryConvert.java
  13. 49 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/articlecategory/ArticleCategoryDO.java
  14. 37 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/articlecategory/ArticleCategoryMapper.java
  15. 83 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/articlecategory/ArticleCategoryService.java
  16. 95 0
      yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/articlecategory/ArticleCategoryServiceImpl.java
  17. 175 0
      yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/articlecategory/ArticleCategoryServiceImplTest.java
  18. 2 0
      yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/clean.sql
  19. 17 1
      yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/create_tables.sql

+ 10 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/DictTypeConstants.java

@@ -0,0 +1,10 @@
+package cn.iocoder.yudao.module.promotion.enums;
+
+/**
+ * promotion 字典类型的枚举类
+ *
+ * @author HUIHUI
+ */
+public class DictTypeConstants {
+
+}

+ 3 - 0
yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java

@@ -110,4 +110,7 @@ public interface ErrorCodeConstants {
     ErrorCode BARGAIN_HELP_CREATE_FAIL_CONFLICT = new ErrorCode(1_013_014_003, "助力失败,请重试");
     ErrorCode BARGAIN_HELP_CREATE_FAIL_HELP_EXISTS = new ErrorCode(1_013_014_004, "助力失败,您已经助力过了");
 
+    // ========== 文章分类 1-013-015-000 ==========
+    ErrorCode ARTICLE_CATEGORY_NOT_EXISTS = new ErrorCode(1_013_015_000, "分类不存在");
+
 }

+ 111 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/ArticleCategoryController.java

@@ -0,0 +1,111 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.*;
+import cn.iocoder.yudao.module.promotion.convert.articlecategory.ArticleCategoryConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.articlecategory.ArticleCategoryDO;
+import cn.iocoder.yudao.module.promotion.service.articlecategory.ArticleCategoryService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
+
+@Tag(name = "管理后台 - 文章分类")
+@RestController
+@RequestMapping("/promotion/article-category")
+@Validated
+public class ArticleCategoryController {
+
+    @Resource
+    private ArticleCategoryService articleCategoryService;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建文章分类")
+    @PreAuthorize("@ss.hasPermission('promotion:article-category:create')")
+    public CommonResult<Long> createArticleCategory(@Valid @RequestBody ArticleCategoryCreateReqVO createReqVO) {
+        return success(articleCategoryService.createArticleCategory(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新文章分类")
+    @PreAuthorize("@ss.hasPermission('promotion:article-category:update')")
+    public CommonResult<Boolean> updateArticleCategory(@Valid @RequestBody ArticleCategoryUpdateReqVO updateReqVO) {
+        articleCategoryService.updateArticleCategory(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除文章分类")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('promotion:article-category:delete')")
+    public CommonResult<Boolean> deleteArticleCategory(@RequestParam("id") Long id) {
+        articleCategoryService.deleteArticleCategory(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得文章分类")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('promotion:article-category:query')")
+    public CommonResult<ArticleCategoryRespVO> getArticleCategory(@RequestParam("id") Long id) {
+        ArticleCategoryDO articleCategory = articleCategoryService.getArticleCategory(id);
+        return success(ArticleCategoryConvert.INSTANCE.convert(articleCategory));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "获得文章分类列表")
+    @Parameter(name = "ids", description = "编号列表", required = true, example = "1024,2048")
+    @PreAuthorize("@ss.hasPermission('promotion:article-category:query')")
+    public CommonResult<List<ArticleCategoryRespVO>> getArticleCategoryList(@RequestParam("ids") Collection<Long> ids) {
+        List<ArticleCategoryDO> list = articleCategoryService.getArticleCategoryList(ids);
+        return success(ArticleCategoryConvert.INSTANCE.convertList(list));
+    }
+
+    @GetMapping("/list-all-simple")
+    @Operation(summary = "获取文章分类精简信息列表", description = "只包含被开启的文章分类,主要用于前端的下拉选项")
+    public CommonResult<List<ArticleCategorySimpleRespVO>> getSimpleDeptList() {
+        // 获得部门列表,只要开启状态的
+        List<ArticleCategoryDO> list = articleCategoryService.getArticleCategoryListByStatus(CommonStatusEnum.ENABLE.getStatus());
+        // 降序排序后,返回给前端
+        list.sort(Comparator.comparing(ArticleCategoryDO::getSort).reversed());
+        return success(ArticleCategoryConvert.INSTANCE.convertList03(list));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得文章分类分页")
+    @PreAuthorize("@ss.hasPermission('promotion:article-category:query')")
+    public CommonResult<PageResult<ArticleCategoryRespVO>> getArticleCategoryPage(@Valid ArticleCategoryPageReqVO pageVO) {
+        PageResult<ArticleCategoryDO> pageResult = articleCategoryService.getArticleCategoryPage(pageVO);
+        return success(ArticleCategoryConvert.INSTANCE.convertPage(pageResult));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出文章分类 Excel")
+    @PreAuthorize("@ss.hasPermission('promotion:article-category:export')")
+    @OperateLog(type = EXPORT)
+    public void exportArticleCategoryExcel(@Valid ArticleCategoryExportReqVO exportReqVO,
+                                           HttpServletResponse response) throws IOException {
+        List<ArticleCategoryDO> list = articleCategoryService.getArticleCategoryList(exportReqVO);
+        // 导出 Excel
+        List<ArticleCategoryExcelVO> datas = ArticleCategoryConvert.INSTANCE.convertList02(list);
+        ExcelUtils.write(response, "文章分类.xls", "数据", ArticleCategoryExcelVO.class, datas);
+    }
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryBaseVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+/**
+ * 文章分类 Base VO,提供给添加、修改、详细的子 VO 使用
+ * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
+ */
+@Data
+public class ArticleCategoryBaseVO {
+
+    @Schema(description = "文章分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "秒杀")
+    @NotNull(message = "文章分类名称不能为空")
+    private String name;
+
+    @Schema(description = "图标地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
+    private String picUrl;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "状态不能为空")
+    private Integer status;
+
+    @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "排序不能为空")
+    private Integer sort;
+
+}

+ 14 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryCreateReqVO.java

@@ -0,0 +1,14 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 文章分类创建 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ArticleCategoryCreateReqVO extends ArticleCategoryBaseVO {
+
+}

+ 39 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryExcelVO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+
+/**
+ * 文章分类 Excel VO
+ *
+ * @author HUIHUI
+ */
+@Data
+public class ArticleCategoryExcelVO {
+
+    @ExcelProperty("文章分类编号")
+    private Long id;
+
+    @ExcelProperty("文章分类名称")
+    private String name;
+
+    @ExcelProperty("图标地址")
+    private String picUrl;
+
+    @ExcelProperty(value = "状态", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.COMMON_STATUS)
+    private Integer status;
+
+    @ExcelProperty("排序")
+    private Integer sort;
+
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+}

+ 25 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryExportReqVO.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+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 = "管理后台 - 文章分类 Excel 导出 Request VO,参数和 ArticleCategoryPageReqVO 是一致的")
+@Data
+public class ArticleCategoryExportReqVO {
+
+    @Schema(description = "文章分类名称", example = "秒杀")
+    private String name;
+
+    @Schema(description = "状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 30 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryPageReqVO.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.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 = "管理后台 - 文章分类分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ArticleCategoryPageReqVO extends PageParam {
+
+    @Schema(description = "文章分类名称", example = "秒杀")
+    private String name;
+
+    @Schema(description = "状态", example = "1")
+    private Integer status;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}

+ 22 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryRespVO.java

@@ -0,0 +1,22 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - 文章分类 Response VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ArticleCategoryRespVO extends ArticleCategoryBaseVO {
+
+    @Schema(description = "文章分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19490")
+    private Long id;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}

+ 16 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategorySimpleRespVO.java

@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 文章分类精简信息 Response VO")
+@Data
+public class ArticleCategorySimpleRespVO {
+
+    @Schema(description = "文章分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19490")
+    private Long id;
+
+    @Schema(description = "文章分类名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "秒杀")
+    private String name;
+
+}

+ 20 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/articlecategory/vo/ArticleCategoryUpdateReqVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 文章分类更新 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class ArticleCategoryUpdateReqVO extends ArticleCategoryBaseVO {
+
+    @Schema(description = "文章分类编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "19490")
+    @NotNull(message = "文章分类编号不能为空")
+    private Long id;
+
+}

+ 35 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/convert/articlecategory/ArticleCategoryConvert.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.promotion.convert.articlecategory;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.*;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.articlecategory.ArticleCategoryDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+import java.util.List;
+
+/**
+ * 文章分类 Convert
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface ArticleCategoryConvert {
+
+    ArticleCategoryConvert INSTANCE = Mappers.getMapper(ArticleCategoryConvert.class);
+
+    ArticleCategoryDO convert(ArticleCategoryCreateReqVO bean);
+
+    ArticleCategoryDO convert(ArticleCategoryUpdateReqVO bean);
+
+    ArticleCategoryRespVO convert(ArticleCategoryDO bean);
+
+    List<ArticleCategoryRespVO> convertList(List<ArticleCategoryDO> list);
+
+    PageResult<ArticleCategoryRespVO> convertPage(PageResult<ArticleCategoryDO> page);
+
+    List<ArticleCategoryExcelVO> convertList02(List<ArticleCategoryDO> list);
+
+    List<ArticleCategorySimpleRespVO> convertList03(List<ArticleCategoryDO> list);
+
+}

+ 49 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/articlecategory/ArticleCategoryDO.java

@@ -0,0 +1,49 @@
+package cn.iocoder.yudao.module.promotion.dal.dataobject.articlecategory;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+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 HUIHUI
+ */
+@TableName("promotion_article_category")
+@KeySequence("promotion_article_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ArticleCategoryDO extends BaseDO {
+
+    /**
+     * 文章分类编号
+     */
+    @TableId
+    private Long id;
+    /**
+     * 文章分类名称
+     */
+    private String name;
+    /**
+     * 图标地址
+     */
+    private String picUrl;
+    /**
+     * 状态
+     *
+     * 枚举 {@link CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 排序
+     */
+    private Integer sort;
+
+}

+ 37 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/articlecategory/ArticleCategoryMapper.java

@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.articlecategory;
+
+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.promotion.controller.admin.articlecategory.vo.ArticleCategoryExportReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryPageReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.articlecategory.ArticleCategoryDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * 文章分类 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface ArticleCategoryMapper extends BaseMapperX<ArticleCategoryDO> {
+
+    default PageResult<ArticleCategoryDO> selectPage(ArticleCategoryPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<ArticleCategoryDO>()
+                .likeIfPresent(ArticleCategoryDO::getName, reqVO.getName())
+                .eqIfPresent(ArticleCategoryDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(ArticleCategoryDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(ArticleCategoryDO::getSort));
+    }
+
+    default List<ArticleCategoryDO> selectList(ArticleCategoryExportReqVO reqVO) {
+        return selectList(new LambdaQueryWrapperX<ArticleCategoryDO>()
+                .likeIfPresent(ArticleCategoryDO::getName, reqVO.getName())
+                .eqIfPresent(ArticleCategoryDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(ArticleCategoryDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(ArticleCategoryDO::getSort));
+    }
+
+}

+ 83 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/articlecategory/ArticleCategoryService.java

@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.promotion.service.articlecategory;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryExportReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.articlecategory.ArticleCategoryDO;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 文章分类 Service 接口
+ *
+ * @author HUIHUI
+ */
+public interface ArticleCategoryService {
+
+    /**
+     * 创建文章分类
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createArticleCategory(@Valid ArticleCategoryCreateReqVO createReqVO);
+
+    /**
+     * 更新文章分类
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateArticleCategory(@Valid ArticleCategoryUpdateReqVO updateReqVO);
+
+    /**
+     * 删除文章分类
+     *
+     * @param id 编号
+     */
+    void deleteArticleCategory(Long id);
+
+    /**
+     * 获得文章分类
+     *
+     * @param id 编号
+     * @return 文章分类
+     */
+    ArticleCategoryDO getArticleCategory(Long id);
+
+    /**
+     * 获得文章分类列表
+     *
+     * @param ids 编号
+     * @return 文章分类列表
+     */
+    List<ArticleCategoryDO> getArticleCategoryList(Collection<Long> ids);
+
+    /**
+     * 获得文章分类分页
+     *
+     * @param pageReqVO 分页查询
+     * @return 文章分类分页
+     */
+    PageResult<ArticleCategoryDO> getArticleCategoryPage(ArticleCategoryPageReqVO pageReqVO);
+
+    /**
+     * 获得文章分类列表, 用于 Excel 导出
+     *
+     * @param exportReqVO 查询条件
+     * @return 文章分类列表
+     */
+    List<ArticleCategoryDO> getArticleCategoryList(ArticleCategoryExportReqVO exportReqVO);
+
+    /**
+     * 获得指定状态的文章分类列表
+     *
+     * @param status 状态
+     * @return 文章分类列表
+     */
+    List<ArticleCategoryDO> getArticleCategoryListByStatus(Integer status);
+
+}

+ 95 - 0
yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/articlecategory/ArticleCategoryServiceImpl.java

@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.promotion.service.articlecategory;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.ListUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryExportReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.convert.articlecategory.ArticleCategoryConvert;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.articlecategory.ArticleCategoryDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.articlecategory.ArticleCategoryMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.ARTICLE_CATEGORY_NOT_EXISTS;
+
+/**
+ * 文章分类 Service 实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+@Validated
+public class ArticleCategoryServiceImpl implements ArticleCategoryService {
+
+    @Resource
+    private ArticleCategoryMapper articleCategoryMapper;
+
+    @Override
+    public Long createArticleCategory(ArticleCategoryCreateReqVO createReqVO) {
+        // 插入
+        ArticleCategoryDO articleCategory = ArticleCategoryConvert.INSTANCE.convert(createReqVO);
+        articleCategoryMapper.insert(articleCategory);
+        // 返回
+        return articleCategory.getId();
+    }
+
+    @Override
+    public void updateArticleCategory(ArticleCategoryUpdateReqVO updateReqVO) {
+        // 校验存在
+        validateArticleCategoryExists(updateReqVO.getId());
+        // 更新
+        ArticleCategoryDO updateObj = ArticleCategoryConvert.INSTANCE.convert(updateReqVO);
+        articleCategoryMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteArticleCategory(Long id) {
+        // 校验存在
+        validateArticleCategoryExists(id);
+        // 删除
+        articleCategoryMapper.deleteById(id);
+    }
+
+    private void validateArticleCategoryExists(Long id) {
+        if (articleCategoryMapper.selectById(id) == null) {
+            throw exception(ARTICLE_CATEGORY_NOT_EXISTS);
+        }
+    }
+
+    @Override
+    public ArticleCategoryDO getArticleCategory(Long id) {
+        return articleCategoryMapper.selectById(id);
+    }
+
+    @Override
+    public List<ArticleCategoryDO> getArticleCategoryList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return ListUtil.empty();
+        }
+        return articleCategoryMapper.selectBatchIds(ids);
+    }
+
+    @Override
+    public PageResult<ArticleCategoryDO> getArticleCategoryPage(ArticleCategoryPageReqVO pageReqVO) {
+        return articleCategoryMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public List<ArticleCategoryDO> getArticleCategoryList(ArticleCategoryExportReqVO exportReqVO) {
+        return articleCategoryMapper.selectList(exportReqVO);
+    }
+
+    @Override
+    public List<ArticleCategoryDO> getArticleCategoryListByStatus(Integer status) {
+        return articleCategoryMapper.selectList(ArticleCategoryDO::getStatus, status);
+    }
+
+}

+ 175 - 0
yudao-module-mall/yudao-module-promotion-biz/src/test/java/cn/iocoder/yudao/module/promotion/service/articlecategory/ArticleCategoryServiceImplTest.java

@@ -0,0 +1,175 @@
+package cn.iocoder.yudao.module.promotion.service.articlecategory;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryCreateReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryExportReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.articlecategory.vo.ArticleCategoryUpdateReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.articlecategory.ArticleCategoryDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.articlecategory.ArticleCategoryMapper;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.ARTICLE_CATEGORY_NOT_EXISTS;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * {@link ArticleCategoryServiceImpl} 的单元测试类
+ *
+ * @author HUIHUI
+ */
+@Import(ArticleCategoryServiceImpl.class)
+public class ArticleCategoryServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private ArticleCategoryServiceImpl articleCategoryService;
+
+    @Resource
+    private ArticleCategoryMapper articleCategoryMapper;
+
+    @Test
+    public void testCreateArticleCategory_success() {
+        // 准备参数
+        ArticleCategoryCreateReqVO reqVO = randomPojo(ArticleCategoryCreateReqVO.class);
+
+        // 调用
+        Long articleCategoryId = articleCategoryService.createArticleCategory(reqVO);
+        // 断言
+        assertNotNull(articleCategoryId);
+        // 校验记录的属性是否正确
+        ArticleCategoryDO articleCategory = articleCategoryMapper.selectById(articleCategoryId);
+        assertPojoEquals(reqVO, articleCategory);
+    }
+
+    @Test
+    public void testUpdateArticleCategory_success() {
+        // mock 数据
+        ArticleCategoryDO dbArticleCategory = randomPojo(ArticleCategoryDO.class);
+        articleCategoryMapper.insert(dbArticleCategory);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        ArticleCategoryUpdateReqVO reqVO = randomPojo(ArticleCategoryUpdateReqVO.class, o -> {
+            o.setId(dbArticleCategory.getId()); // 设置更新的 ID
+        });
+
+        // 调用
+        articleCategoryService.updateArticleCategory(reqVO);
+        // 校验是否更新正确
+        ArticleCategoryDO articleCategory = articleCategoryMapper.selectById(reqVO.getId()); // 获取最新的
+        assertPojoEquals(reqVO, articleCategory);
+    }
+
+    @Test
+    public void testUpdateArticleCategory_notExists() {
+        // 准备参数
+        ArticleCategoryUpdateReqVO reqVO = randomPojo(ArticleCategoryUpdateReqVO.class);
+
+        // 调用, 并断言异常
+        assertServiceException(() -> articleCategoryService.updateArticleCategory(reqVO), ARTICLE_CATEGORY_NOT_EXISTS);
+    }
+
+    @Test
+    public void testDeleteArticleCategory_success() {
+        // mock 数据
+        ArticleCategoryDO dbArticleCategory = randomPojo(ArticleCategoryDO.class);
+        articleCategoryMapper.insert(dbArticleCategory);// @Sql: 先插入出一条存在的数据
+        // 准备参数
+        Long id = dbArticleCategory.getId();
+
+        // 调用
+        articleCategoryService.deleteArticleCategory(id);
+        // 校验数据不存在了
+        assertNull(articleCategoryMapper.selectById(id));
+    }
+
+    @Test
+    public void testDeleteArticleCategory_notExists() {
+        // 准备参数
+        Long id = randomLongId();
+
+        // 调用, 并断言异常
+        assertServiceException(() -> articleCategoryService.deleteArticleCategory(id), ARTICLE_CATEGORY_NOT_EXISTS);
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetArticleCategoryPage() {
+        // mock 数据
+        ArticleCategoryDO dbArticleCategory = randomPojo(ArticleCategoryDO.class, o -> { // 等会查询到
+            o.setName(null);
+            o.setPicUrl(null);
+            o.setStatus(null);
+            o.setSort(null);
+            o.setCreateTime(null);
+        });
+        articleCategoryMapper.insert(dbArticleCategory);
+        // 测试 name 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setName(null)));
+        // 测试 picUrl 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setPicUrl(null)));
+        // 测试 status 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setStatus(null)));
+        // 测试 sort 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setSort(null)));
+        // 测试 createTime 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setCreateTime(null)));
+        // 准备参数
+        ArticleCategoryPageReqVO reqVO = new ArticleCategoryPageReqVO();
+        reqVO.setName(null);
+        reqVO.setStatus(null);
+        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+        // 调用
+        PageResult<ArticleCategoryDO> pageResult = articleCategoryService.getArticleCategoryPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbArticleCategory, pageResult.getList().get(0));
+    }
+
+    @Test
+    @Disabled  // TODO 请修改 null 为需要的值,然后删除 @Disabled 注解
+    public void testGetArticleCategoryList() {
+        // mock 数据
+        ArticleCategoryDO dbArticleCategory = randomPojo(ArticleCategoryDO.class, o -> { // 等会查询到
+            o.setName(null);
+            o.setPicUrl(null);
+            o.setStatus(null);
+            o.setSort(null);
+            o.setCreateTime(null);
+        });
+        articleCategoryMapper.insert(dbArticleCategory);
+        // 测试 name 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setName(null)));
+        // 测试 picUrl 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setPicUrl(null)));
+        // 测试 status 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setStatus(null)));
+        // 测试 sort 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setSort(null)));
+        // 测试 createTime 不匹配
+        articleCategoryMapper.insert(cloneIgnoreId(dbArticleCategory, o -> o.setCreateTime(null)));
+        // 准备参数
+        ArticleCategoryExportReqVO reqVO = new ArticleCategoryExportReqVO();
+        reqVO.setName(null);
+        reqVO.setStatus(null);
+        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
+
+        // 调用
+        List<ArticleCategoryDO> list = articleCategoryService.getArticleCategoryList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbArticleCategory, list.get(0));
+    }
+
+}

+ 2 - 0
yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/clean.sql

@@ -6,3 +6,5 @@ DELETE FROM "promotion_discount_activity";
 DELETE FROM "promotion_discount_product";
 DELETE FROM "promotion_seckill_config";
 DELETE FROM "promotion_combination_activity";
+DELETE
+FROM "promotion_article_category";

+ 17 - 1
yudao-module-mall/yudao-module-promotion-biz/src/test/resources/sql/create_tables.sql

@@ -180,4 +180,20 @@ CREATE TABLE IF NOT EXISTS "promotion_combination_activity"
     "deleted"            bit      NOT NULL DEFAULT FALSE,
     "tenant_id"          bigint   NOT NULL,
     PRIMARY KEY ("id")
-) COMMENT '拼团活动';
+) COMMENT '拼团活动';
+
+CREATE TABLE IF NOT EXISTS "promotion_article_category"
+(
+    "id"          bigint   NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "name"        varchar  NOT NULL,
+    "pic_url"     varchar,
+    "status"      int      NOT NULL,
+    "sort"        int      NOT NULL,
+    "creator"     varchar           DEFAULT '',
+    "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    "updater"     varchar           DEFAULT '',
+    "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    "deleted"     bit      NOT NULL DEFAULT FALSE,
+    "tenant_id"   bigint   NOT NULL,
+    PRIMARY KEY ("id")
+) COMMENT '文章分类表';