接下来我们将趁热打铁完成UserService
剩余的接口定义和实现。在这之前我们先完善下user
实体的设计,增加一个个性签名的字段personal_signature
。
新增用户表个性签名
显然,我们第一个要改的就是pdm模型,
然后改本地h2库,右键新增column,
然后,放开build.gradle
中注释掉的apply from: 'mbgen.gradle'
,刷新下gradle,user生成的model和mapper则更新完成。
完善UserService接口定义
下面我们一鼓作气,把剩余的用户接口都定义出来:
package com.xiaojuan.boot.service;
import ...
public interface UserService {
...
UserInfoDTO login(String username, String password) throws BusinessException;
void updatePersonalSignature(long userId, String signature);
void checkAdminRole(byte role) throws BusinessException;
}
温馨提醒
当我们的接口命名做到见名知意的话,可以不写接口的文档注释
这里我们定义了一个用来保存登录成功后的用户信息的DTO:
package com.xiaojuan.boot.dto;
import ...
@Data
public class UserInfoDTO {
/** 用户id */
private Long id;
private String userName;
/** 用户角色 1-普通用户 2-管理员 */
private Byte role;
private String personalSignature;
}
实现用户登录逻辑
在UserServiceImpl
中实现:
@Override
public UserInfoDTO login(String username, String password) throws BusinessException {
Assert.hasText(username, "用户名不能为空");
Assert.hasText(password, "密码不能为空");
// 按照用户名查询用户
Optional<User> userOptional = userMapper.selectOne(c -> c.where(UserDynamicSqlSupport.username, isEqualTo(username)));
if (!userOptional.isPresent()) {
throw new BusinessException("用户名错误");
}
User user = userOptional.get();
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BusinessException("密码错误");
}
UserInfoDTO userInfoDTO = new UserInfoDTO();
BeanUtils.copyProperties(user, userInfoDTO);
return userInfoDTO;
}
代码说明
注意这里灵活运用了mybatis3的dynamic sql的特性来查询单条记录。
密码的匹配不能直接加密后和数据库比较,要先查出来,再用spring安全框架的
PasswordEncoder
的matches
来匹配,解铃还须系铃人嘛。最后,因为
UserInfoDTO
中的字段都在User
实体类中被涵盖了,因此我们无需手动一个个赋值,直接用spring提供的工具类,进行相同属性的拷贝即可。
剩余逻辑实现
@Override
public void updatePersonalSignature(long userId, String signature) {
userMapper.update(c -> c.set(personalSignature).equalTo(signature)
.set(updateTime).equalTo(new Date())
.where(id, isEqualTo(userId)));
}
@Override
public void checkAdminRole(byte role) throws BusinessException {
if (Role.ADMIN.getValue() != role) {
throw new BusinessException("非管理员角色,不能操作!");
}
}
代码说明
同样根据用户id来更新签名信息,我们也采用mybatis dynamic sql的形式。
在检查用户角色时,我们不是直接取管理员的角色数值进行比较,而是将角色维护为一个枚举:
package com.xiaojuan.boot.enums; import ... @Getter @AllArgsConstructor public enum Role { USER("普通用户", (byte)1), ADMIN("管理员", (byte)2); private final String label; private final Byte value; }
因为在生成model时,对数据库的
tinyint
类型映射为java的byte
类型,而不是int
类型,虽然这不是我们的本意,但生成器也没提供很好的字段类型映射用户配置方式,我们就保留byte
类型,勉为其难的做一次转换吧。
常量提取
不知道大家发现没有,我们在抛出异常消息和断言异常消息时,会写重复的字符串,很显然,更好的做法是将它们提取为常量。下面是提取技巧:
右键,Refactor:
提取为常量:
全部提取出来,放到UserService
接口中
package com.xiaojuan.boot.service;
import ...
public interface UserService {
String MSG_USERNAME_REQUIRED = "用户名不能为空";
String MSG_USERNAME_EXISTS = "用户名已存在";
String MSG_USERNAME_ERROR = "用户名错误";
String MSG_PASSWORD_REQUIRED = "密码不能为空";
String MSG_PASSWORD_ERROR = "密码错误";
String MSG_ADMIN_ROLE_REQUIRED = "非管理员角色,不能操作!";
...
}
这样我们在单元测试中直接引用这些常量即可。
完善单元测试
最后将我们的service层单元测试完善下:
@Test
public void testComposition() {
// 先登录失败
assertThatExceptionOfType(BusinessException.class).isThrownBy(() -> {
userService.login("zhangsan", "123");
}).withMessage(MSG_USERNAME_ERROR);
// 注册张三用户
userService.register(new UserRegisterDTO("zhangsan", "123"));
// 密码错误
assertThatExceptionOfType(BusinessException.class).isThrownBy(() -> {
userService.login("zhangsan", "666");
}).withMessage(MSG_PASSWORD_ERROR);
// 登录成功
UserInfoDTO user = userService.login("zhangsan", "123");
// 检查没有权限
assertThatExceptionOfType(BusinessException.class).isThrownBy(() -> {
userService.checkAdminRole(user.getRole());
}).withMessage(MSG_ADMIN_ROLE_REQUIRED);
}
我们把所有的单元测试跑下:
ok!都搞定!