Commit f884054d2c78ed3f01c822c06dd42c87bd17cdab

Authored by owen
1 parent e2032e7e

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

sql/mysql/member_level.sql
... ... @@ -29,8 +29,8 @@ create table member_level_log
29 29 level_id bigint default 0 not null comment '等级编号',
30 30 level int default 0 not null comment '会员等级',
31 31 discount int(4) default 100 not null comment '享受折扣',
32   - experience int(4) default 100 not null comment '升级经验',
33   - user_experience int(4) default 100 not null comment '会员此时的经验',
  32 + experience int(4) default 0 not null comment '升级经验',
  33 + user_experience int(4) default 0 not null comment '会员此时的经验',
34 34 remark varchar(255) default '' not null comment '备注',
35 35 description varchar(255) default '' not null comment '描述',
36 36 creator varchar(64) default '' null comment '创建者',
... ...
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 {
44 44  
45 45 //========== 会员等级 1004007000 ==========
46 46 ErrorCode LEVEL_NOT_EXISTS = new ErrorCode(1004007000, "会员等级不存在");
  47 + ErrorCode LEVEL_NAME_EXISTS = new ErrorCode(1004007001, "会员等级名称[{}]已被使用");
  48 + ErrorCode LEVEL_VALUE_EXISTS = new ErrorCode(1004007002, "会员等级值[{}]已被[{}]使用");
  49 + ErrorCode LEVEL_EXPERIENCE_MIN = new ErrorCode(1004007003, "升级经验必须大于上一个等级[{}]设置的升级经验[{}]");
  50 + ErrorCode LEVEL_EXPERIENCE_MAX = new ErrorCode(1004007004, "升级经验必须小于下一个等级[{}]设置的升级经验[{}]");
  51 +
47 52 ErrorCode LEVEL_LOG_NOT_EXISTS = new ErrorCode(1004007100, "会员等级记录不存在");
48 53 ErrorCode EXPERIENCE_LOG_NOT_EXISTS = new ErrorCode(1004007200, "会员经验记录不存在");
49 54 }
... ...
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 {
29 29  
30 30 @Schema(description = "等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
31 31 @NotNull(message = "等级不能为空")
  32 + @Positive(message = "等级必须大于0")
32 33 private Integer level;
33 34  
34 35 @Schema(description = "享受折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "98")
... ...
yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImpl.java
1 1 package cn.iocoder.yudao.module.member.service.level;
2 2  
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.hutool.core.util.StrUtil;
3 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 6 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelCreateReqVO;
5 7 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
7 9 import cn.iocoder.yudao.module.member.convert.level.MemberLevelConvert;
8 10 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
9 11 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
  12 +import com.google.common.annotations.VisibleForTesting;
10 13 import lombok.extern.slf4j.Slf4j;
11 14 import org.springframework.stereotype.Service;
12 15 import org.springframework.validation.annotation.Validated;
... ... @@ -16,7 +19,7 @@ import java.util.Collection;
16 19 import java.util.List;
17 20  
18 21 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
19   -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.LEVEL_NOT_EXISTS;
  22 +import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
20 23  
21 24 /**
22 25 * 会员等级 Service 实现类
... ... @@ -33,6 +36,9 @@ public class MemberLevelServiceImpl implements MemberLevelService {
33 36  
34 37 @Override
35 38 public Long createLevel(MemberLevelCreateReqVO createReqVO) {
  39 + // 校验配置是否有效
  40 + validateConfigValid(null, createReqVO.getName(), createReqVO.getLevel(), createReqVO.getExperience());
  41 +
36 42 // 插入
37 43 MemberLevelDO level = MemberLevelConvert.INSTANCE.convert(createReqVO);
38 44 levelMapper.insert(level);
... ... @@ -44,6 +50,9 @@ public class MemberLevelServiceImpl implements MemberLevelService {
44 50 public void updateLevel(MemberLevelUpdateReqVO updateReqVO) {
45 51 // 校验存在
46 52 validateLevelExists(updateReqVO.getId());
  53 + // 校验配置是否有效
  54 + validateConfigValid(updateReqVO.getId(), updateReqVO.getName(), updateReqVO.getLevel(), updateReqVO.getExperience());
  55 +
47 56 // 更新
48 57 MemberLevelDO updateObj = MemberLevelConvert.INSTANCE.convert(updateReqVO);
49 58 levelMapper.updateById(updateObj);
... ... @@ -57,10 +66,72 @@ public class MemberLevelServiceImpl implements MemberLevelService {
57 66 levelMapper.deleteById(id);
58 67 }
59 68  
60   - private void validateLevelExists(Long id) {
61   - if (levelMapper.selectById(id) == null) {
  69 + @VisibleForTesting
  70 + MemberLevelDO validateLevelExists(Long id) {
  71 + MemberLevelDO levelDO = levelMapper.selectById(id);
  72 + if (levelDO == null) {
62 73 throw exception(LEVEL_NOT_EXISTS);
63 74 }
  75 + return levelDO;
  76 + }
  77 +
  78 + @VisibleForTesting
  79 + void validateNameUnique(List<MemberLevelDO> list, Long id, String name) {
  80 + for (MemberLevelDO levelDO : list) {
  81 + if (ObjUtil.notEqual(levelDO.getName(), name)) {
  82 + continue;
  83 + }
  84 +
  85 + if (id == null || !id.equals(levelDO.getId())) {
  86 + throw exception(LEVEL_NAME_EXISTS, levelDO.getName());
  87 + }
  88 + }
  89 + }
  90 +
  91 + @VisibleForTesting
  92 + void validateLevelUnique(List<MemberLevelDO> list, Long id, Integer level) {
  93 + for (MemberLevelDO levelDO : list) {
  94 + if (ObjUtil.notEqual(levelDO.getLevel(), level)) {
  95 + continue;
  96 + }
  97 +
  98 + if (id == null || !id.equals(levelDO.getId())) {
  99 + throw exception(LEVEL_VALUE_EXISTS, levelDO.getLevel(), levelDO.getName());
  100 + }
  101 + }
  102 + }
  103 +
  104 + @VisibleForTesting
  105 + void validateExperienceOutRange(List<MemberLevelDO> list, Long id, Integer level, Integer experience) {
  106 + for (MemberLevelDO levelDO : list) {
  107 + if (levelDO.getId().equals(id)) {
  108 + continue;
  109 + }
  110 +
  111 + if (levelDO.getLevel() < level) {
  112 + // 经验大于前一个等级
  113 + if (experience <= levelDO.getExperience()) {
  114 + throw exception(LEVEL_EXPERIENCE_MIN, levelDO.getName(), levelDO.getExperience());
  115 + }
  116 + } else if (levelDO.getLevel() > level) {
  117 + //小于下一个级别
  118 + if (experience >= levelDO.getExperience()) {
  119 + throw exception(LEVEL_EXPERIENCE_MAX, levelDO.getName(), levelDO.getExperience());
  120 + }
  121 + }
  122 + }
  123 + }
  124 +
  125 + @VisibleForTesting
  126 + void validateConfigValid(Long id, String name, Integer level, Integer experience) {
  127 + List<MemberLevelDO> list = levelMapper.selectList();
  128 +
  129 + // 校验名称唯一
  130 + validateNameUnique(list, id, name);
  131 + // 校验等级唯一
  132 + validateLevelUnique(list, id, level);
  133 + // 校验升级所需经验是否有效: 大于前一个等级,小于下一个级别
  134 + validateExperienceOutRange(list, id, level, experience);
64 135 }
65 136  
66 137 @Override
... ...
yudao-module-member/yudao-module-member-biz/src/test/java/cn/iocoder/yudao/module/member/service/level/MemberLevelServiceImplTest.java
1 1 package cn.iocoder.yudao.module.member.service.level;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
4 6 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
5 7 import cn.iocoder.yudao.module.member.controller.admin.level.vo.MemberLevelCreateReqVO;
6 8 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
8 10 import cn.iocoder.yudao.module.member.dal.dataobject.level.MemberLevelDO;
9 11 import cn.iocoder.yudao.module.member.dal.mysql.level.MemberLevelMapper;
10 12 import org.junit.jupiter.api.Test;
  13 +import org.springframework.boot.test.mock.mockito.MockBean;
11 14 import org.springframework.context.annotation.Import;
12 15  
13 16 import javax.annotation.Resource;
  17 +import java.util.List;
  18 +import java.util.function.Consumer;
14 19  
  20 +import static cn.hutool.core.util.RandomUtil.randomInt;
15 21 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
16 22 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
17 23 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
18   -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
19   -import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
20   -import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.LEVEL_NOT_EXISTS;
  24 +import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
  25 +import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
21 26 import static org.junit.jupiter.api.Assertions.*;
22 27  
23 28 /**
... ... @@ -34,10 +39,19 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
34 39 @Resource
35 40 private MemberLevelMapper levelMapper;
36 41  
  42 + @MockBean
  43 + private MemberLevelLogService memberLevelLogService;
  44 + @MockBean
  45 + private MemberExperienceLogService memberExperienceLogService;
  46 +
37 47 @Test
38 48 public void testCreateLevel_success() {
39 49 // 准备参数
40   - MemberLevelCreateReqVO reqVO = randomPojo(MemberLevelCreateReqVO.class);
  50 + MemberLevelCreateReqVO reqVO = randomPojo(MemberLevelCreateReqVO.class, o -> {
  51 + o.setDiscount(randomInt());
  52 + o.setIcon(randomURL());
  53 + o.setBackgroundUrl(randomURL());
  54 + });
41 55  
42 56 // 调用
43 57 Long levelId = levelService.createLevel(reqVO);
... ... @@ -56,6 +70,14 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
56 70 // 准备参数
57 71 MemberLevelUpdateReqVO reqVO = randomPojo(MemberLevelUpdateReqVO.class, o -> {
58 72 o.setId(dbLevel.getId()); // 设置更新的 ID
  73 + //以下要保持一致
  74 + o.setName(dbLevel.getName());
  75 + o.setLevel(dbLevel.getLevel());
  76 + o.setExperience(dbLevel.getExperience());
  77 + //以下是要修改的字段
  78 + o.setDiscount(randomInt());
  79 + o.setIcon(randomURL());
  80 + o.setBackgroundUrl(randomURL());
59 81 });
60 82  
61 83 // 调用
... ... @@ -106,9 +128,9 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
106 128 });
107 129 levelMapper.insert(dbLevel);
108 130 // 测试 name 不匹配
109   - levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setName(null)));
  131 + levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setName("")));
110 132 // 测试 status 不匹配
111   - levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setStatus(null)));
  133 + levelMapper.insert(cloneIgnoreId(dbLevel, o -> o.setStatus(0)));
112 134 // 准备参数
113 135 MemberLevelPageReqVO reqVO = new MemberLevelPageReqVO();
114 136 reqVO.setName("黄金会员");
... ... @@ -122,4 +144,121 @@ public class MemberLevelServiceImplTest extends BaseDbUnitTest {
122 144 assertPojoEquals(dbLevel, pageResult.getList().get(0));
123 145 }
124 146  
  147 + @Test
  148 + public void testCreateLevel_nameUnique() {
  149 + // 准备参数
  150 + String name = randomString();
  151 +
  152 + // mock 数据
  153 + levelMapper.insert(randomLevelDO(o -> o.setName(name)));
  154 +
  155 + // 调用,校验异常
  156 + List<MemberLevelDO> list = levelMapper.selectList();
  157 + assertServiceException(() -> levelService.validateNameUnique(list, null, name), LEVEL_NAME_EXISTS, name);
  158 + }
  159 +
  160 + @Test
  161 + public void testUpdateLevel_nameUnique() {
  162 + // 准备参数
  163 + Long id = randomLongId();
  164 + String name = randomString();
  165 +
  166 + // mock 数据
  167 + levelMapper.insert(randomLevelDO(o -> o.setName(name)));
  168 +
  169 + // 调用,校验异常
  170 + List<MemberLevelDO> list = levelMapper.selectList();
  171 + assertServiceException(() -> levelService.validateNameUnique(list, id, name), LEVEL_NAME_EXISTS, name);
  172 + }
  173 +
  174 + @Test
  175 + public void testCreateLevel_levelUnique() {
  176 + // 准备参数
  177 + Integer level = randomInteger();
  178 + String name = randomString();
  179 +
  180 + // mock 数据
  181 + levelMapper.insert(randomLevelDO(o -> {
  182 + o.setLevel(level);
  183 + o.setName(name);
  184 + }));
  185 +
  186 + // 调用,校验异常
  187 + List<MemberLevelDO> list = levelMapper.selectList();
  188 + assertServiceException(() -> levelService.validateLevelUnique(list, null, level), LEVEL_VALUE_EXISTS, level, name);
  189 + }
  190 +
  191 + @Test
  192 + public void testUpdateLevel_levelUnique() {
  193 + // 准备参数
  194 + Long id = randomLongId();
  195 + Integer level = randomInteger();
  196 + String name = randomString();
  197 +
  198 + // mock 数据
  199 + levelMapper.insert(randomLevelDO(o -> {
  200 + o.setLevel(level);
  201 + o.setName(name);
  202 + }));
  203 +
  204 + // 调用,校验异常
  205 + List<MemberLevelDO> list = levelMapper.selectList();
  206 + assertServiceException(() -> levelService.validateLevelUnique(list, id, level), LEVEL_VALUE_EXISTS, level, name);
  207 + }
  208 +
  209 + @Test
  210 + public void testCreateLevel_experienceOutRange() {
  211 + // 准备参数
  212 + int level = 10;
  213 + int experience = 10;
  214 + String name = randomString();
  215 +
  216 + // mock 数据
  217 + levelMapper.insert(randomLevelDO(o -> {
  218 + o.setLevel(level);
  219 + o.setExperience(experience);
  220 + o.setName(name);
  221 + }));
  222 + List<MemberLevelDO> list = levelMapper.selectList();
  223 +
  224 + // 调用,校验异常
  225 + assertServiceException(() -> levelService.validateExperienceOutRange(list, null, level + 1, experience - 1), LEVEL_EXPERIENCE_MIN, name, level);
  226 + // 调用,校验异常
  227 + assertServiceException(() -> levelService.validateExperienceOutRange(list, null, level - 1, experience + 1), LEVEL_EXPERIENCE_MAX, name, level);
  228 + }
  229 +
  230 + @Test
  231 + public void testUpdateLevel_experienceOutRange() {
  232 + // 准备参数
  233 + int level = 10;
  234 + int experience = 10;
  235 + Long id = randomLongId();
  236 + String name = randomString();
  237 +
  238 + // mock 数据
  239 + levelMapper.insert(randomLevelDO(o -> {
  240 + o.setLevel(level);
  241 + o.setExperience(experience);
  242 + o.setName(name);
  243 + }));
  244 + List<MemberLevelDO> list = levelMapper.selectList();
  245 +
  246 + // 调用,校验异常
  247 + assertServiceException(() -> levelService.validateExperienceOutRange(list, id, level + 1, experience - 1), LEVEL_EXPERIENCE_MIN, name, level);
  248 + // 调用,校验异常
  249 + assertServiceException(() -> levelService.validateExperienceOutRange(list, id, level - 1, experience + 1), LEVEL_EXPERIENCE_MAX, name, level);
  250 + }
  251 +
  252 + // ========== 随机对象 ==========
  253 +
  254 + @SafeVarargs
  255 + private static MemberLevelDO randomLevelDO(Consumer<MemberLevelDO>... consumers) {
  256 + Consumer<MemberLevelDO> consumer = (o) -> {
  257 + o.setStatus(CommonStatusEnum.ENABLE.getStatus());
  258 + o.setDiscount(randomInt(0, 100));
  259 + o.setIcon(randomURL());
  260 + o.setBackgroundUrl(randomURL());
  261 + };
  262 + return randomPojo(MemberLevelDO.class, ArrayUtils.append(consumer, consumers));
  263 + }
125 264 }
... ...
yudao-module-member/yudao-module-member-biz/src/test/resources/sql/create_tables.sql
... ... @@ -48,19 +48,19 @@ CREATE TABLE IF NOT EXISTS &quot;member_tag&quot;
48 48  
49 49 CREATE TABLE IF NOT EXISTS "member_level"
50 50 (
51   - "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
52   - "name" varchar NOT NULL,
53   - "experience" int NOT NULL,
54   - "value" int NOT NULL,
55   - "discount" int NOT NULL,
56   - "icon" varchar NOT NULL,
57   - "bg_url" varchar NOT NULL,
58   - "creator" varchar DEFAULT '',
59   - "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
60   - "updater" varchar DEFAULT '',
61   - "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
62   - "deleted" bit NOT NULL DEFAULT FALSE,
63   - "tenant_id" bigint not null default '0',
64   - "status" int NOT NULL,
  51 + "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
  52 + "name" varchar NOT NULL,
  53 + "experience" int NOT NULL,
  54 + "level" int NOT NULL,
  55 + "discount" int NOT NULL,
  56 + "icon" varchar NOT NULL,
  57 + "background_url" varchar NOT NULL,
  58 + "creator" varchar DEFAULT '',
  59 + "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  60 + "updater" varchar DEFAULT '',
  61 + "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  62 + "deleted" bit NOT NULL DEFAULT FALSE,
  63 + "tenant_id" bigint not null default '0',
  64 + "status" int NOT NULL,
65 65 PRIMARY KEY ("id")
66 66 ) COMMENT '会员等级';
67 67 \ No newline at end of file
... ...