您好,欢迎来到刀刀网。
搜索
您的当前位置:首页java抽奖系统(二)

java抽奖系统(二)

来源:刀刀网

 5. ⽤户注册模块

5.1. 关于加密

        根据需求我们对密码使用不可逆的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、管理员创建普通用户进行的注册;

5.2 注册时序图

  1、约定前后端交互接⼝

        对于上述的请求,管理员注册和普通用户注册所需要的信息是不一样的,且密码是必填选项;同时对于响应是按照commonresult泛型类来进行返回的(状态码,返回的数据,错误信息),返回userid数据是方便后序在新建页面时需要用户信息;

5.3 Controller层接⼝设计:

@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;
}

5.4 service层接口设计

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());
    }
}

5.5 客户端实现 

<!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

本站由北京市万商天勤律师事务所王兴未律师提供法律服务