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

This commit is contained in:
owen 2023-08-21 00:23:45 +08:00
parent e2032e7e97
commit f884054d2c
6 changed files with 241 additions and 25 deletions

View File

@ -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 '升级经验', experience int(4) default 0 not null comment '升级经验',
user_experience int(4) default 100 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 '创建者',

View File

@ -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, "会员经验记录不存在");
} }

View File

@ -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")

View File

@ -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) { @VisibleForTesting
if (levelMapper.selectById(id) == null) { 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

View File

@ -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.*;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo; import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.LEVEL_NOT_EXISTS;
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));
}
} }

View File

@ -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, "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL, "name" varchar NOT NULL,
"experience" int NOT NULL, "experience" int NOT NULL,
"value" int NOT NULL, "level" int NOT NULL,
"discount" int NOT NULL, "discount" int NOT NULL,
"icon" varchar NOT NULL, "icon" varchar NOT NULL,
"bg_url" varchar NOT NULL, "background_url" varchar NOT NULL,
"creator" varchar DEFAULT '', "creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, "create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '', "updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, "update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE, "deleted" bit NOT NULL DEFAULT FALSE,
"tenant_id" bigint not null default '0', "tenant_id" bigint not null default '0',
"status" int NOT NULL, "status" int NOT NULL,
PRIMARY KEY ("id") PRIMARY KEY ("id")
) COMMENT '会员等级'; ) COMMENT '会员等级';