根据需求我们对密码使用不可逆的hash加密,对于手机号由于我们后续要进行短信通知,所以使用可逆的对称加密;
Java⼯具类库Hutool,对⽂件、流、加密解密、转码、正则、线程、XML等JDK⽅法进⾏了封 装,本次使用hutool工具包对数据进行见密;
引入当前hutool的依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
使用hash加密时,对于秘钥的长度是有要求的(16,24,32位长度的字符串数据);
注册场景:1、管理员进行注册2、管理员创建普通用户进行的注册;
1、约定前后端交互接⼝
对于上述的请求,管理员注册和普通用户注册所需要的信息是不一样的,且密码是必填选项;同时对于响应是按照commonresult泛型类来进行返回的(状态码,返回的数据,错误信息),返回userid数据是方便后序在新建页面时需要用户信息;
@RestController
public class UserController {
@Autowired
private UserService userService;
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
//注册
@RequestMapping("/register")
public CommonResult<UserRegisterResult> userRegister(@Validated @RequestBody UserRegisterParam param){
//日志的打印
logger.info("userRegister UserRegisterParam{}:", JacksonUtil.writeValueAsString(param));
//调用service层的服务进行访问
UserRegisterDTO userRegisterDTO = userService.register(param);
return CommonResult.success(convertToUserRegisterResult(userRegisterDTO));
}
private UserRegisterResult convertToUserRegisterResult(UserRegisterDTO userRegisterDTO) {
UserRegisterResult result = new UserRegisterResult();
if(userRegisterDTO == null){
throw new ControllerException(ControllerErrorCodeConstants.REGISTER_ERROR);
};
result.setUserId(userRegisterDTO.getUserId());
return result;
}
}
在controller层对于接受到的数据要进行param类和返回的用户注册结果的封装:
对于用户注册前来的参数和返回的结果使用对象来封装
DO:data object,与数据库表结构一一对应,通过dao层向上传输数据源对象;
DTO:data transfer object,数据传输对象,service层向外传输的对象
para:数据查询对象,controller接受到的对象;
对于注册进行模拟测试后,发现根据管理员和普通用户来说,普通用户不需要密码这一数据请求;
所以使用validation这个jar包来完成一些验证性的注解,同时引入相应的依赖;
1、首先在入参对象前使用@validate这个注解;
2、其次对于UserRegisterParam进行规则设定:
@Data public class UserRegisterParam implements Serializable { @NotBlank(message = "姓名不能为空!") private String name; @NotBlank(message = "邮箱不能为空!") private String mail; @NotBlank(message = "手机号不能为空!") private String phoneNumber; private String password; @NotBlank(message = "身份信息不能为空!") private String identity; }
UserRegisterResult:
@Data public class UserRegisterResult implements Serializable { private Long userId; }
UserRegisterDTO 向controller层传输的数据封装:
@Data
public class UserRegisterDTO {
private long userId;
}
对于service接口和实现的方法使用抽象和具体分离的策略:
public interface UserService {
//注册
UserRegisterDTO register(UserRegisterParam param);
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public UserRegisterDTO register(UserRegisterParam param) {
// 校验注册信息,
checkRegisterInfo(param);
// 加密私密数据(构造dao层请求)
UserDO userDO = new UserDO();
userDO.setUserName(param.getName());
userDO.setEmail(param.getMail());
userDO.setPhoneNumber(new Encrypt(param.getPhoneNumber()));
userDO.setIdentity(param.getIdentity());
if (StringUtils.hasText(param.getPassword())) {
userDO.setPassword(DigestUtil.sha256Hex(param.getPassword()));
}
// 保存数据
userMapper.insert(userDO);
// 构造返回
UserRegisterDTO userRegisterDTO = new UserRegisterDTO();
userRegisterDTO.setUserId(userDO.getId());
return userRegisterDTO;
}
private void checkRegisterInfo(UserRegisterParam param) {
if(param == null){
throw new ServiceException(ServiceErrorCodeConstants.REGISTER_INFO_IS_EMPTY);
}
//校验邮箱格式
if(!RegexUtil.checkMail(param.getMail())){
throw new ServiceException(ServiceErrorCodeConstants.MAIL_ERROR);
}
//校验手机格式
if(!RegexUtil.checkMobile(param.getPhoneNumber())){
throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
}
//校验身份信息
//身份只有两种,管理员和普通用户,此时param里面admin和normal时字符串,要与枚举进行判断
if(UserIdentityEnum.forName(param.getIdentity()) == null){
throw new ServiceException(ServiceErrorCodeConstants.IDENTITY_ERROR);
}
//校验管理员的密码必填
if(param.getIdentity().equalsIgnoreCase(UserIdentityEnum.ADMIN.name())
&& !StringUtils.hasText(param.getPassword())){
throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_IS_EMPTY);
}
//密码的长度大于六位
if(StringUtils.hasText(param.getPassword()) && !RegexUtil.checkPassword(param.getPassword())){
throw new ServiceException(ServiceErrorCodeConstants.PASSWORD_ERROR);
}
//校验邮箱是否被使用
if (checkMailUsed(param.getMail())){
throw new ServiceException(ServiceErrorCodeConstants.MAIL_USED);
}
//校验手机是否被使用
if (checkPhoneNumberUsed(param.getPhoneNumber())){
throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_USED);
}
}
private boolean checkPhoneNumberUsed(String phoneNumber) {
int count = userMapper.countByPhone(new Encrypt(phoneNumber));
return count > 0;
}
private boolean checkMailUsed(String mail) {
int count = userMapper.countByMail(mail);
return count > 0;
}
}
当前进行测试:
在对手机号进行验证是否存在时,由于手机号是私密数据,一般在手机号进入库表之前mybatis会进行加密操作,将加密之后的手机号放入到库表中进行存储;当对手机号进行验证查重操作时,就需要将加密后的手机号进行解密操作;
上面这种频繁加密和解密的操作可以使用TypeHandler来进行解决,该类是处理指定类:将指定的类型转化为库表支持的类,即将string类型的手机号自动加密成varchar类;但是由于邮箱和手机号都是string类,所以对于string类的手机号进行encrypt封装,成为一个encrypt类,可以将encrypt类的手机号自动加密成varchar类进行存储;
encrypt封装类如下:
@Data public class Encrypt { private String value; public Encrypt(){}; public Encrypt(String value){ this.value = value; }; }typehandler如下:
@MappedTypes(Encrypt.class) @MappedJdbcTypes(JdbcType.VARCHAR) public class EncryptTypeHandler extends BaseTypeHandler<Encrypt> { private final byte[] KEY = "1234567abcdefg".getBytes(StandardCharsets.UTF_8); /** * 设置参数 * * @param ps SQL 预编译对象 * @param i 需要赋值的索引位置 * @param parameter 原本位置i需要赋的值 * @param jdbcType jdbc 类型 * @throws SQLException */ @Override public void setNonNullParameter(PreparedStatement ps, int i, Encrypt parameter, JdbcType jdbcType) throws SQLException { if (parameter == null || parameter.getValue() == null) { ps.setString(i, null); return; } System.out.println("加密的内容:" + parameter.getValue()); // 加密 AES aes = SecureUtil.aes(KEY); String str = aes.encryptHex(parameter.getValue()); ps.setString(i, str); } /** * 获取值 * * @param rs 结果集 * @param columnName 索引名 * @return * @throws SQLException */ @Override public Encrypt getNullableResult(ResultSet rs, String columnName) throws SQLException { return decrypt(rs.getString(columnName)); } /** * 获取值 * * @param rs 结果集合 * @param columnIndex 索引 * @return * @throws SQLException */ @Override public Encrypt getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return decrypt(rs.getString(columnIndex)); } /** * 获取值 * * @param cs 结果集 * @param columnIndex 索引 * @return * @throws SQLException */ @Override public Encrypt getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return decrypt(cs.getString(columnIndex)); } //统一解密 private Encrypt decrypt(String str) { if (!StringUtils.hasText(str)) { return null; } return new Encrypt(SecureUtil.aes(KEY).decryptStr(str)); } }
测试如下:
此时注册功能完成,但是防止系统运行时出现我们没有定义的异常,我们会不能正确即时的进行try-catch;我们对controller层的全局异常进行捕捉处理,代码如下:
@RestControllerAdvice // 可以捕获全局抛得异常
public class GlobalExceptionHandler {
private final static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(value = ServiceException.class)
//针对service出的错进行捕捉
public CommonResult<?> serviceException(ServiceException e) {
// 打错误日志
logger.error("serviceException:", e);
// 构造错误结果
return CommonResult.error(
GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
e.getMessage());
}
@ExceptionHandler(value = ControllerException.class)
public CommonResult<?> controllerException(ControllerException e) {
// 打错误日志
logger.error("controllerException:", e);
// 构造错误结果
return CommonResult.error(
GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
e.getMessage());
}
@ExceptionHandler(value = Exception.class)
public CommonResult<?> exception(Exception e) {
// 打错误日志
logger.error("服务异常:", e);
// 构造错误结果
return CommonResult.error(
GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode(),
e.getMessage());
}
}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>注册页面</title>
<link rel="stylesheet"
href="https://cdn.staticfile.org/-bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="./css/base.css">
<script
src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script
src="https://cdn.bootcdn.net/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
<link rel="stylesheet"
href="https://cdn.staticfile.org/-bootstrap/4.5.2/js/bootstrap.min.js">
<style>
body {
font-family: Arial, sans-serif;
background-color: #fff;
margin: 0;
padding: 0;
}
.register-container {
max-width: 600px;
margin: 50px auto;
padding: 20px;
background-color: #fff;
border-radius: 5px;
}
.register-container h2{
font-weight: 600;
font-size: 32px;
letter-spacing: 1px;
color: #000000;
line-height: 50px;
text-align: center;
margin-bottom: 60px;
}
.show-password {
float: right;
cursor: pointer;
color: #008CBA;
}
</style>
</head>
<body>
<div class="register-container">
<h2>填写注册信息</h2>
<form id="registerForm">
<div class="form-group">
<label for="name">姓名</label>
<input type="text" class="form-control" id="name"
name="name" placeholder="请输入姓名" required>
</div>
<div class="form-group">
<label for="mail">邮箱</label>
<input type="email" class="form-control" id="mail"
name="mail" placeholder="请输入邮箱" required>
</div>
<div class="form-group">
<label for="phoneNumber">手机号</label>
<input type="text" class="form-control" id="phoneNumber"
name="phoneNumber" placeholder="请输入手机号" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" class="form-control" id="password"
name="password" placeholder="请输入密码" required>
</div>
<button type="submit"
class="btn btn-primary btn-block">注册</button>
</form>
</div>
<script>
// 获取链接参数
var params = new URLSearchParams(window.location.search);
var jumpList = params.get('jumpList');
var admin = params.get('admin');
// 判断是否隐藏密码输入框
if(admin === 'false') {
// 隐藏密码输入框
$('#password').closest('div.form-group').css('display', 'none');
}
// 使用jQuery Validate插件来验证表单
$("#registerForm").validate({
rules: {
name: "required",
mail: {
required: true,
email: true
},
phoneNumber: "required",
password: {
required: true,
minlength: 6
}
},
messages: {
name: "请输入您的姓名",
mail: "请输入有效的邮箱地址",
phoneNumber: "请输入您的手机号",
password: {
required: "请输入密码",
minlength: "密码长度至少为6个字符"
}
},
submitHandler: function(form) {
var formData = {
name: $('#name').val(),
mail: $('#mail').val(),
phoneNumber: $('#phoneNumber').val(),
password: $('#password').val(),
identity: (admin == null || admin === 'true') ? "ADMIN" : "NORMAL"
};
// 表单验证通过后,发送AJAX请求
$.ajax({
url: '/register',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(formData),
success: function(result) {
if (result.code != 200) {
alert("注册失败!" + result.msg);
} else {
alert("注册成功,去登录!");
}
}
});
return false; // 阻止表单的默认提交行为
}
});
</script>
</body>
</html>
测试:
ps:谢谢观看!
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- gamedaodao.com 版权所有 湘ICP备2022005869号-6
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务