Browse Source

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

owen 1 year ago
parent
commit
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_id        bigint       default 0                 not null comment '等级编号',
     level           int          default 0                 not null comment '会员等级',
     level           int          default 0                 not null comment '会员等级',
     discount        int(4)       default 100               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 '备注',
     remark          varchar(255) default ''                not null comment '备注',
     description     varchar(255) default ''                not null comment '描述',
     description     varchar(255) default ''                not null comment '描述',
     creator         varchar(64)  default ''                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 ==========
     //========== 会员等级 1004007000 ==========
     ErrorCode LEVEL_NOT_EXISTS = new ErrorCode(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 LEVEL_LOG_NOT_EXISTS = new ErrorCode(1004007100, "会员等级记录不存在");
     ErrorCode EXPERIENCE_LOG_NOT_EXISTS = new ErrorCode(1004007200, "会员经验记录不存在");
     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")
     @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "等级不能为空")
     @NotNull(message = "等级不能为空")
+    @Positive(message = "等级必须大于0")
     private Integer level;
     private Integer level;
 
 
     @Schema(description = "享受折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "98")
     @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;
 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.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.MemberLevelCreateReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelPageReqVO;
 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.convert.level.MemberLevelConvert;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
+import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.validation.annotation.Validated;
@@ -16,7 +19,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.List;
 
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 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 实现类
  * 会员等级 Service 实现类
@@ -33,6 +36,9 @@ public class MemberLevelServiceImpl implements MemberLevelService {
 
 
     @Override
     @Override
     public Long createLevel(MemberLevelCreateReqVO createReqVO) {
     public Long createLevel(MemberLevelCreateReqVO createReqVO) {
+        // 校验配置是否有效
+        validateConfigValid(null, createReqVO.getName(), createReqVO.getLevel(), createReqVO.getExperience());
+
         // 插入
         // 插入
         MemberLevelDO level = MemberLevelConvert.INSTANCE.convert(createReqVO);
         MemberLevelDO level = MemberLevelConvert.INSTANCE.convert(createReqVO);
         levelMapper.insert(level);
         levelMapper.insert(level);
@@ -44,6 +50,9 @@ public class MemberLevelServiceImpl implements MemberLevelService {
     public void updateLevel(MemberLevelUpdateReqVO updateReqVO) {
     public void updateLevel(MemberLevelUpdateReqVO updateReqVO) {
         // 校验存在
         // 校验存在
         validateLevelExists(updateReqVO.getId());
         validateLevelExists(updateReqVO.getId());
+        // 校验配置是否有效
+        validateConfigValid(updateReqVO.getId(), updateReqVO.getName(), updateReqVO.getLevel(), updateReqVO.getExperience());
+
         // 更新
         // 更新
         MemberLevelDO updateObj = MemberLevelConvert.INSTANCE.convert(updateReqVO);
         MemberLevelDO updateObj = MemberLevelConvert.INSTANCE.convert(updateReqVO);
         levelMapper.updateById(updateObj);
         levelMapper.updateById(updateObj);
@@ -57,10 +66,72 @@ public class MemberLevelServiceImpl implements MemberLevelService {
         levelMapper.deleteById(id);
         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);
             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
     @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;
 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.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 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.MemberLevelCreateReqVO;
 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelPageReqVO;
 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.dataobject.level.MemberLevelDO;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.Import;
 
 
 import javax.annotation.Resource;
 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.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.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
 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.*;
 import static org.junit.jupiter.api.Assertions.*;
 
 
 /**
 /**
@@ -34,10 +39,19 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
     @Resource
     @Resource
     private MemberLevelMapper levelMapper;
     private MemberLevelMapper levelMapper;
 
 
+    @MockBean
+    private MemberLevelLogService memberLevelLogService;
+    @MockBean
+    private MemberExperienceLogService memberExperienceLogService;
+
     @Test
     @Test
     public void testCreateLevel_success() {
     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);
         Long levelId = levelService.createLevel(reqVO);
@@ -56,6 +70,14 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
         // 准备参数
         // 准备参数
         MemberLevelUpdateReqVO reqVO = randomPojo(MemberLevelUpdateReqVO.class, o -> {
         MemberLevelUpdateReqVO reqVO = randomPojo(MemberLevelUpdateReqVO.class, o -> {
             o.setId(dbLevel.getId()); // 设置更新的 ID
             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);
         levelMapper.insert(dbLevel);
         // 测试 name 不匹配
         // 测试 name 不匹配
-        levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setName(null)));
+        levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setName("")));
         // 测试 status 不匹配
         // 测试 status 不匹配
-        levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setStatus(null)));
+        levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setStatus(0)));
         // 准备参数
         // 准备参数
         MemberLevelPageReqVO reqVO = new MemberLevelPageReqVO();
         MemberLevelPageReqVO reqVO = new MemberLevelPageReqVO();
         reqVO.setName("黄金会员");
         reqVO.setName("黄金会员");
@@ -122,4 +144,121 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
         assertPojoEquals(dbLevel, pageResult.getList().get(0));
         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"
 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")
     PRIMARY KEY ("id")
 ) COMMENT '会员等级';
 ) COMMENT '会员等级';