Explorar o código

会员:完善会员等级校验、单元测试

owen hai 1 ano
pai
achega
f884054d2c

+ 2 - 2
sql/mysql/member_level.sql

@@ -29,8 +29,8 @@ create table member_level_log
     level_id        bigint       default 0                 not null comment '等级编号',
     level           int          default 0                 not null comment '会员等级',
     discount        int(4)       default 100               not null comment '享受折扣',
-    experience      int(4)       default 100               not null comment '升级经验',
-    user_experience int(4)       default 100               not null comment '会员此时的经验',
+    experience      int(4)       default 0                 not null comment '升级经验',
+    user_experience int(4)       default 0                 not null comment '会员此时的经验',
     remark          varchar(255) default ''                not null comment '备注',
     description     varchar(255) default ''                not null comment '描述',
     creator         varchar(64)  default ''                null comment '创建者',

+ 5 - 0
yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/enums/ErrorCodeConstants.java

@@ -44,6 +44,11 @@ public interface ErrorCodeConstants {
 
     //========== 会员等级 1004007000 ==========
     ErrorCode LEVEL_NOT_EXISTS = new ErrorCode(1004007000, "会员等级不存在");
+    ErrorCode LEVEL_NAME_EXISTS = new ErrorCode(1004007001, "会员等级名称[{}]已被使用");
+    ErrorCode LEVEL_VALUE_EXISTS = new ErrorCode(1004007002, "会员等级值[{}]已被[{}]使用");
+    ErrorCode LEVEL_EXPERIENCE_MIN = new ErrorCode(1004007003, "升级经验必须大于上一个等级[{}]设置的升级经验[{}]");
+    ErrorCode LEVEL_EXPERIENCE_MAX = new ErrorCode(1004007004, "升级经验必须小于下一个等级[{}]设置的升级经验[{}]");
+
     ErrorCode LEVEL_LOG_NOT_EXISTS = new ErrorCode(1004007100, "会员等级记录不存在");
     ErrorCode EXPERIENCE_LOG_NOT_EXISTS = new ErrorCode(1004007200, "会员经验记录不存在");
 }

+ 1 - 0
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/admin/level/vo/MemberLevelBaseVO.java

@@ -29,6 +29,7 @@ public class MemberLevelBaseVO {
 
     @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "等级不能为空")
+    @Positive(message = "等级必须大于0")
     private Integer level;
 
     @Schema(description = "享受折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "98")

+ 74 - 3
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java

@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.member.service.level;
 
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelCreateReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelPageReqVO;
@@ -7,6 +9,7 @@ import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelUpdat
 import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
+import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
@@ -16,7 +19,7 @@ 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.member.enums.ErrorCodeConstants.LEVEL_NOT_EXISTS;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
 
 /**
  * 会员等级 Service 实现类
@@ -33,6 +36,9 @@ public class MemberLevelServiceImpl implements MemberLevelService {
 
     @Override
     public Long createLevel(MemberLevelCreateReqVO createReqVO) {
+        // 校验配置是否有效
+        validateConfigValid(null, createReqVO.getName(), createReqVO.getLevel(), createReqVO.getExperience());
+
         // 插入
         MemberLevelDO level = MemberLevelConvert.INSTANCE.convert(createReqVO);
         levelMapper.insert(level);
@@ -44,6 +50,9 @@ public class MemberLevelServiceImpl implements MemberLevelService {
     public void updateLevel(MemberLevelUpdateReqVO updateReqVO) {
         // 校验存在
         validateLevelExists(updateReqVO.getId());
+        // 校验配置是否有效
+        validateConfigValid(updateReqVO.getId(), updateReqVO.getName(), updateReqVO.getLevel(), updateReqVO.getExperience());
+
         // 更新
         MemberLevelDO updateObj = MemberLevelConvert.INSTANCE.convert(updateReqVO);
         levelMapper.updateById(updateObj);
@@ -57,10 +66,72 @@ public class MemberLevelServiceImpl implements MemberLevelService {
         levelMapper.deleteById(id);
     }
 
-    private void validateLevelExists(Long id) {
-        if (levelMapper.selectById(id) == null) {
+    @VisibleForTesting
+    MemberLevelDO validateLevelExists(Long id) {
+        MemberLevelDO levelDO = levelMapper.selectById(id);
+        if (levelDO == null) {
             throw exception(LEVEL_NOT_EXISTS);
         }
+        return levelDO;
+    }
+
+    @VisibleForTesting
+    void validateNameUnique(List<MemberLevelDO> list, Long id, String name) {
+        for (MemberLevelDO levelDO : list) {
+            if (ObjUtil.notEqual(levelDO.getName(), name)) {
+                continue;
+            }
+
+            if (id == null || !id.equals(levelDO.getId())) {
+                throw exception(LEVEL_NAME_EXISTS, levelDO.getName());
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void validateLevelUnique(List<MemberLevelDO> list, Long id, Integer level) {
+        for (MemberLevelDO levelDO : list) {
+            if (ObjUtil.notEqual(levelDO.getLevel(), level)) {
+                continue;
+            }
+
+            if (id == null || !id.equals(levelDO.getId())) {
+                throw exception(LEVEL_VALUE_EXISTS, levelDO.getLevel(), levelDO.getName());
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void validateExperienceOutRange(List<MemberLevelDO> list, Long id, Integer level, Integer experience) {
+        for (MemberLevelDO levelDO : list) {
+            if (levelDO.getId().equals(id)) {
+                continue;
+            }
+
+            if (levelDO.getLevel() < level) {
+                // 经验大于前一个等级
+                if (experience <= levelDO.getExperience()) {
+                    throw exception(LEVEL_EXPERIENCE_MIN, levelDO.getName(), levelDO.getExperience());
+                }
+            } else if (levelDO.getLevel() > level) {
+                //小于下一个级别
+                if (experience >= levelDO.getExperience()) {
+                    throw exception(LEVEL_EXPERIENCE_MAX, levelDO.getName(), levelDO.getExperience());
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void validateConfigValid(Long id, String name, Integer level, Integer experience) {
+        List<MemberLevelDO> list = levelMapper.selectList();
+
+        // 校验名称唯一
+        validateNameUnique(list, id, name);
+        // 校验等级唯一
+        validateLevelUnique(list, id, level);
+        // 校验升级所需经验是否有效: 大于前一个等级,小于下一个级别
+        validateExperienceOutRange(list, id, level, experience);
     }
 
     @Override

+ 145 - 6
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java

@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.member.service.level;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelCreateReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelPageReqVO;
@@ -8,16 +10,19 @@ import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelUpdat
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
 import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 
 import javax.annotation.Resource;
+import java.util.List;
+import java.util.function.Consumer;
 
+import static cn.hutool.core.util.RandomUtil.randomInt;
 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.member.enums.ErrorCodeConstants.LEVEL_NOT_EXISTS;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
+import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 /**
@@ -34,10 +39,19 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
     @Resource
     private MemberLevelMapper levelMapper;
 
+    @MockBean
+    private MemberLevelLogService memberLevelLogService;
+    @MockBean
+    private MemberExperienceLogService memberExperienceLogService;
+
     @Test
     public void testCreateLevel_success() {
         // 准备参数
-        MemberLevelCreateReqVO reqVO = randomPojo(MemberLevelCreateReqVO.class);
+        MemberLevelCreateReqVO reqVO = randomPojo(MemberLevelCreateReqVO.class, o -> {
+            o.setDiscount(randomInt());
+            o.setIcon(randomURL());
+            o.setBackgroundUrl(randomURL());
+        });
 
         // 调用
         Long levelId = levelService.createLevel(reqVO);
@@ -56,6 +70,14 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
         // 准备参数
         MemberLevelUpdateReqVO reqVO = randomPojo(MemberLevelUpdateReqVO.class, o -> {
             o.setId(dbLevel.getId()); // 设置更新的 ID
+            //以下要保持一致
+            o.setName(dbLevel.getName());
+            o.setLevel(dbLevel.getLevel());
+            o.setExperience(dbLevel.getExperience());
+            //以下是要修改的字段
+            o.setDiscount(randomInt());
+            o.setIcon(randomURL());
+            o.setBackgroundUrl(randomURL());
         });
 
         // 调用
@@ -106,9 +128,9 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
         });
         levelMapper.insert(dbLevel);
         // 测试 name 不匹配
-        levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setName(null)));
+        levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setName("")));
         // 测试 status 不匹配
-        levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setStatus(null)));
+        levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setStatus(0)));
         // 准备参数
         MemberLevelPageReqVO reqVO = new MemberLevelPageReqVO();
         reqVO.setName("黄金会员");
@@ -122,4 +144,121 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(dbLevel, pageResult.getList().get(0));
     }
 
+    @Test
+    public void testCreateLevel_nameUnique() {
+        // 准备参数
+        String name = randomString();
+
+        // mock 数据
+        levelMapper.insert(randomLevelDO(o -> o.setName(name)));
+
+        // 调用,校验异常
+        List<MemberLevelDO> list = levelMapper.selectList();
+        assertServiceException(() -> levelService.validateNameUnique(list, null, name), LEVEL_NAME_EXISTS, name);
+    }
+
+    @Test
+    public void testUpdateLevel_nameUnique() {
+        // 准备参数
+        Long id = randomLongId();
+        String name = randomString();
+
+        // mock 数据
+        levelMapper.insert(randomLevelDO(o -> o.setName(name)));
+
+        // 调用,校验异常
+        List<MemberLevelDO> list = levelMapper.selectList();
+        assertServiceException(() -> levelService.validateNameUnique(list, id, name), LEVEL_NAME_EXISTS, name);
+    }
+
+    @Test
+    public void testCreateLevel_levelUnique() {
+        // 准备参数
+        Integer level = randomInteger();
+        String name = randomString();
+
+        // mock 数据
+        levelMapper.insert(randomLevelDO(o -> {
+            o.setLevel(level);
+            o.setName(name);
+        }));
+
+        // 调用,校验异常
+        List<MemberLevelDO> list = levelMapper.selectList();
+        assertServiceException(() -> levelService.validateLevelUnique(list, null, level), LEVEL_VALUE_EXISTS, level, name);
+    }
+
+    @Test
+    public void testUpdateLevel_levelUnique() {
+        // 准备参数
+        Long id = randomLongId();
+        Integer level = randomInteger();
+        String name = randomString();
+
+        // mock 数据
+        levelMapper.insert(randomLevelDO(o -> {
+            o.setLevel(level);
+            o.setName(name);
+        }));
+
+        // 调用,校验异常
+        List<MemberLevelDO> list = levelMapper.selectList();
+        assertServiceException(() -> levelService.validateLevelUnique(list, id, level), LEVEL_VALUE_EXISTS, level, name);
+    }
+
+    @Test
+    public void testCreateLevel_experienceOutRange() {
+        // 准备参数
+        int level = 10;
+        int experience = 10;
+        String name = randomString();
+
+        // mock 数据
+        levelMapper.insert(randomLevelDO(o -> {
+            o.setLevel(level);
+            o.setExperience(experience);
+            o.setName(name);
+        }));
+        List<MemberLevelDO> list = levelMapper.selectList();
+
+        // 调用,校验异常
+        assertServiceException(() -> levelService.validateExperienceOutRange(list, null, level + 1, experience - 1), LEVEL_EXPERIENCE_MIN, name, level);
+        // 调用,校验异常
+        assertServiceException(() -> levelService.validateExperienceOutRange(list, null, level - 1, experience + 1), LEVEL_EXPERIENCE_MAX, name, level);
+    }
+
+    @Test
+    public void testUpdateLevel_experienceOutRange() {
+        // 准备参数
+        int level = 10;
+        int experience = 10;
+        Long id = randomLongId();
+        String name = randomString();
+
+        // mock 数据
+        levelMapper.insert(randomLevelDO(o -> {
+            o.setLevel(level);
+            o.setExperience(experience);
+            o.setName(name);
+        }));
+        List<MemberLevelDO> list = levelMapper.selectList();
+
+        // 调用,校验异常
+        assertServiceException(() -> levelService.validateExperienceOutRange(list, id, level + 1, experience - 1), LEVEL_EXPERIENCE_MIN, name, level);
+        // 调用,校验异常
+        assertServiceException(() -> levelService.validateExperienceOutRange(list, id, level - 1, experience + 1), LEVEL_EXPERIENCE_MAX, name, level);
+    }
+
+    // ========== 随机对象 ==========
+
+    @SafeVarargs
+    private static MemberLevelDO randomLevelDO(Consumer<MemberLevelDO>... consumers) {
+        Consumer<MemberLevelDO> consumer = (o) -> {
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setDiscount(randomInt(0, 100));
+            o.setIcon(randomURL());
+            o.setBackgroundUrl(randomURL());
+        };
+        return randomPojo(MemberLevelDO.class, ArrayUtils.append(consumer, consumers));
+    }
 }

+ 14 - 14
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql

@@ -48,19 +48,19 @@ CREATE TABLE IF NOT EXISTS "member_tag"
 
 CREATE TABLE IF NOT EXISTS "member_level"
 (
-    "id"          bigint   NOT NULL GENERATED BY DEFAULT AS IDENTITY,
-    "name"        varchar  NOT NULL,
-    "experience"  int      NOT NULL,
-    "value"       int      NOT NULL,
-    "discount"    int      NOT NULL,
-    "icon"        varchar  NOT NULL,
-    "bg_url"      varchar  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 default '0',
-    "status"      int      NOT NULL,
+    "id"             bigint   NOT NULL GENERATED BY DEFAULT AS IDENTITY,
+    "name"           varchar  NOT NULL,
+    "experience"     int      NOT NULL,
+    "level"          int      NOT NULL,
+    "discount"       int      NOT NULL,
+    "icon"           varchar  NOT NULL,
+    "background_url" varchar  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 default '0',
+    "status"         int      NOT NULL,
     PRIMARY KEY ("id")
 ) COMMENT '会员等级';