8. 实现业务功能--用户注册

目录

1. 顺序图

2. 参数要求

3. 接口规范

4. 创建扩展 Mapper.xml 

5. 修改 DAO 

6. 创建 Service 接口

7. 实现接口

8. 测试接口

9. 实现 Controller

9.1 密码加密处理

10. 实现前端界面


业务实现过程中主要的包和目录及主要功能:
  • model 包:实体对象
  • dao 包:数据库访问
  • services 包:业务处理相关的接口与实现,所有业务都在 Services 中实现
  • controller 包:提供 URL 映射,用来接收参数并做校验, 调用 Service 中的业务代码 ,返回执行结果
  • src/main/resources/mapper 目录:Mybaits 映射文件,配置数据库实体与类之间的映射关系
  • src/main/resources/static 目录:前端资源

那么我们再来看一下各个包之间的调用关系

  • Controller 包:主要用来接收用户的参数,并封装 Service 层需要使用的对象,最终为客户端响应结果
  • Service 包:处理业务逻辑,调用 dao 包完成数据的持久化。如果要执行多条数据库更新操作,那么就需要用事务管理

1. 顺序图

注意, 5 应该为根据用户名查询用户信息,即 selectByName(name)。

2. 参数要求

注册时,需要用户提交的参数列表:

参数名描述参数默认值条件
username用户名String必须
nickname昵称String与用户名相同必须
password密码String必须
passwordRepeat确认密码String必须,与密码相同

必须 即 需要进行非空校验,还需要进行两次密码输入校验。 

3. 接口规范

// 请求
POST /user/register HTTP/1.1

Content-Type: application/x-www-form-urlencoded
username=user222&nickname=user222&password=123456&passwordRepeat=123456
// 响应
HTTP/1.1 200
Content-Type: application/json
{"code":0,"message":"成功","data":null}

接下来,就是实现 Service 层,具体通过以下步骤:

1. 在 Mapper.xml 中编写 SQL 语句
2. 在 Mapper.java 中定义方法
3. 定义 Service 接口
4. 实现 Service 接口
5. 单元测试
6. Controller 实现方法并对外提供的 API 接口
7. 测试 API 接口
8. 实现前端逻辑,完成前后端交互

4. 创建扩展 Mapper.xml 

写入操作:已经存在(自动生成的)不需要手动编写

根据用户名查询信息:

为了避免在自动生成时重复生成相同的语句,我们在 mapper 包下,新建一个 extension 包,将 UserMapper.xml 文件重新复制一份,并重命名为:UserExtMapper

注意: 

因为,命名相同的 xml 文件最终会被解析成一个大文件,里面定义的所有标签都可以相互调用。

接下来,我们先来查看一下此时数据库中的数据:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.dao.UserMapper">
  <!--
  1. 注意 namespace 表⽰命名空间,要与 UserMapper.xml 中的 namespace 相同
  2. 统⼀⽤ com.example.forum.dao.UserMapper, 也就是UserMapper的完全限定名(包名+类名)
  3. 不同的映射⽂件指定了相同的namespace后,定义的所有⽤id或name标识的结果集映射都可以在不同的⽂件中共享
-->
  
  <!--  根据用户名查询用户信息  -->
  <select id="selectByName" parameterType="java.lang.String" resultMap="BaseResultMap">
    SELECT
    <include refid="Base_Column_List" />
    from t_user 
    where username = #{username,jdbcType=VARCHAR}
  </select>
</mapper>

可以看到我们用来代替 * 的语句中,使用的标签已经进行了全列的定义:

5. 修改 DAO 

/**
* 根据用户名查询用户信息
* @param username 用户名
* @return
*/
User selectByName(String username);

6. 创建 Service 接口

在 demo 包下创建 services 包,在 services 包下创建 IUserService 接口。

接口的命名规则: I + 实现类的名字 + Service 

public interface IUserService {
    /**
     * 根据用户名查询用户信息
     * @param username 用户名
     * @return
     */
    User selectByName(String username);

    /**
     * 创建普通用户
     * @param user 用户名
     * @return
     */
    User createNormalUser(User user);
}

7. 实现接口

在 services 包下新建 impl 包,在 impl 包下新建类:UserServiceimpl 类,并实现 IUserService 接口:

 命名规则:实体类名 + Service + Impl

接下来 Alt + 回车 生成实现方法,并加入注解:

@Slf4j // 日志
@Service // 交给 Spring 管理
public class UserServiceImpl implements IUserService {

    @Override
    public User selectByName(String username) {
        return null;
    }

    @Override
    public User createNormalUser(User user) {
        return null;
    }
}

 在实现的过程中需要对每一个必传参数做非空校验,从而保证程序的健壮性

可以看到我们需要多次使用到非空校验的判断,因此我们可以将此处的语句抽取出来,放在一个公共的工具类中。在 demo 包下新建包 utils,在 utils 包下新建类 StringUtils:

public class StringUtils {
    /**
     * 校验指定的字符串是否为空
     * @param value 待校验的字符串
     * @return true 空 <br/> false 非空
     */
    public static boolean isEmpty(String value){
        if(value == null || value.isEmpty()){
            return true;
        }
        return false;
    }
}

接下来继续完善我们的实现接口部分:

@Slf4j // 日志
@Service // 交给 Spring 管理
public class UserServiceImpl implements IUserService {

    @Resource
    private UserMapper userMapper;
    
    @Override
    public User selectByName(String username) {
        // 非空校验
        if(StringUtils.isEmpty(username)){
            // 打印日志
            log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
            // 抛出异常
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }
        // 根据用户名查询用户信息
        User user = userMapper.selectByName(username);
        // 返回结果
        return user;
    }

    @Override
    public User createNormalUser(User user) {
        return null;
    }
}

接下来我们实现 createNormalUser 方法:

@Slf4j // 日志
@Service // 交给 Spring 管理
public class UserServiceImpl implements IUserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public User selectByName(String username) {
        // 非空校验
        if(StringUtils.isEmpty(username)){
            // 打印日志
            log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
            // 抛出异常
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }
        // 根据用户名查询用户信息
        User user = userMapper.selectByName(username);
        // 返回结果
        return user;
    }

    @Override
    public void createNormalUser(User user) {
        // 非空校验
        if(user == null || StringUtils.isEmpty(user.getUsername())
                || StringUtils.isEmpty(user.getNickname())
                || StringUtils.isEmpty(user.getPassword())
                || StringUtils.isEmpty(user.getSalt())){
            // 打印日志
            log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());
            // 抛出异常
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));
        }
        // 校验用户是否存在
        User existUser = selectByName(user.getUsername());
        if(existUser != null){
            // 打印日志
            log.warn(ResultCode.FAILED_USER_EXISTS.toString() + "username = " + user.getUsername());
            // 抛出异常,用户已存在
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_EXISTS));
        }
        // 为属性设置默认值
        // 性别 0女 1男 2保密
        if(user.getGender() != null){
            if(user.getGender() < 0 || user.getGender() > 2){
                user.setGender((byte)2);
            }
        }else{
            user.setGender((byte)2);
        }
        // 发帖数
        user.setArticleCount(0);
        // 是否管理员
        user.setIsAdmin((byte)0);
        // 状态
        user.setState((byte)0);
        // 时间
        Date date = new Date();
        user.setCreateTime(date); // 创建时间
        user.setUpdateTime(date); // 更新时间

        // 写入数据库
        int row = userMapper.insertSelective(user);
        if(row != 1){
            // 打印日志
            log.warn(ResultCode.FAILED_CREATE.toString() + "注册用户失败,username = " + user.getUsername());
            // 抛出异常,用户已存在
            throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));
        }
    }
}

8. 测试接口

在 UserServiceImpl 类中右键生成测试方法:

加上 @SpringBootTest 注解:

@SpringBootTest
class UserServiceImplTest {
    @Resource
    private IUserService userService;

    @Resource
    private ObjectMapper objectMapper;  // JSON 对象
    @Test
    void selectByName() throws JsonProcessingException {
        // 调用 Service 查询用户信息
        User user = userService.selectByName("bitboy");
        System.out.println(objectMapper.writeValueAsString(user));
        System.out.println("========================================");
        user= userService.selectByName("bitboy111");
        System.out.println(objectMapper.writeValueAsString(user));
        System.out.println("========================================");
        user = userService.selectByName(null);
        System.out.println(objectMapper.writeValueAsString(user));
    }

    @Test
    void createNormalUser() {
    }
}

查询结果如下:

与数据库中的数据相符合,因此测试成功。

接下来添加 createNormalUser 的测试:

@SpringBootTest
class UserServiceImplTest {
    @Resource
    private IUserService userService;

    @Resource
    private ObjectMapper objectMapper;  // JSON 对象
    @Test
    void selectByName() throws JsonProcessingException {
        // 调用 Service 查询用户信息
        User user = userService.selectByName("bitboy");
        System.out.println(objectMapper.writeValueAsString(user));
        System.out.println("========================================");
        user= userService.selectByName("bitboy111");
        System.out.println(objectMapper.writeValueAsString(user));
        System.out.println("========================================");
//        user = userService.selectByName(null);
//        System.out.println(objectMapper.writeValueAsString(user));
    }

    @Test
    void createNormalUser() {
        // 构造用户
        User user = new User();
        user.setUsername("TestUser1");
        user.setNickname("单元测试用户1");
        user.setPassword("123456");
        user.setSalt("123456");
        // 调用 Service
        userService.createNormalUser((user));
        System.out.println("注册成功");
        System.out.println("========================");

        user.setUsername("bitboy");
        // 调用 Service
        userService.createNormalUser((user));
        System.out.println("注册成功");
    }
}

运行结果如下图所示:

测试成功。

9. 实现 Controller

在 controller 包下新建 UserController 类:

9.1 密码加密处理

由于在存储用户的密码时,不能直接存储明文,否则会泄露账户信息,因此需要通过盐进行加密处理,最终将盐和密文(明文密码+ 扰动字符串(盐))存储在数据库中。

盐 -> 随机字符

MD5(MD5(明文密码) + SALT)= 密码对应的密文

新建 UUIDUtils 类:

public class UUIDUtils {

    /**
     * 生成一个标准的36字符的UUID
     *
     * @return
     */
    public static String UUID_36() {
        return UUID.randomUUID().toString();
    }

    /**
     * 生成一个32字符的UUID
     *
     * @return
     */
    public static String UUID_32() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}

在 ForumApplicationTests 类中添加测试方法:

@Test
	void testUUID(){
		System.out.println(UUIDUtils.UUID_36());
		System.out.println(UUIDUtils.UUID_32());
	}

结果如下图所示:

在 pom.xml 中添加依赖:

<!-- 编码解码加密⼯具包-->
<dependency>
 <groupId>commons-codec</groupId>
 <artifactId>commons-codec</artifactId>
</dependency>

在 utils 包下新建 MD5Utils 类:

import org.apache.commons.codec.digest.DigestUtils;;

public class MD5Utils {
    /**
     * 普通MD5加密
     * @param str 原始字符串
     * @return ⼀次MD5加密后的密⽂
     */
    public static String md5 (String str) {
        return DigestUtils.md5Hex(str);
    }
    /**
     * 原始字符串与Key组合进⾏⼀次MD5加密
     * @param str 原始字符串
     * @param key
     * @return 组合字符串⼀次MD5加密后的密⽂
     */
    public static String md5 (String str, String key) {
        return DigestUtils.md5Hex(str + key);
    }
    /**
     * 原始字符串加密后与扰动字符串组合再进⾏⼀次MD5加密
     * @param str 原始字符串
     * @param salt 扰动字符串
     * @return 加密后的密⽂
     */
    public static String md5Salt (String str, String salt) {
        return DigestUtils.md5Hex(DigestUtils.md5Hex(str) + salt);
    }
    /**
     * 校验原⽂与盐加密后是否与传⼊的密⽂相同
     * @param original 原字符串
     * @param salt 扰动字符串
     * @param ciphertext 密⽂
     * @return true 相同, false 不同
     */
    public static boolean verifyOriginalAndCiphertext (String original, String salt, String ciphertext) {
        String md5text = md5Salt(original, salt);
        if (md5text.equalsIgnoreCase(ciphertext)) {
            return true;
        }
        return false;
    }
}

继续在 UserController 类中编写代码: 

@Api(tags = "用户接口")
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private IUserService userService;

    @ApiOperation("用户注册")
    @PostMapping("/register")
    public AppResult register(@ApiParam("用户名") @RequestParam("username") @NonNull String username,
                              @ApiParam("昵称") @RequestParam("nickname") @NonNull String nickname,
                              @ApiParam("密码") @RequestParam("password") @NonNull String password,
                              @ApiParam("确认密码") @RequestParam("passwordRepeat") @NonNull String passwordRepeat){
        // 校验密码是否一致
        if (!password.equals(passwordRepeat)) {
            // 返回错误信息
            return AppResult.failed(ResultCode.FAILED_TWO_PWD_NOT_SAME);
        }
        // 构造对象
        User user = new User();
        user.setUsername(username); // 用户名
        user.setNickname(nickname); // 昵称

        // 处理密码
        // 1.生成盐
        String salt = UUIDUtils.UUID_32();
        // 2.生成密码的密文
        String encryptPassword = MD5Utils.md5Salt(password, salt);
        // 3 设置密码和盐
        user.setPassword(encryptPassword);
        user.setSalt(salt);
        // 3. 调⽤Service
        userService.createNormalUser(user);
        // 4. 返回结果
        return AppResult.success("注册成功");
    }
}

运行启动类 ForumApplication:

打开:http://127.0.0.1:58080/swagger-ui/index.html

接下来进行密码测试:

测试成功:

继续测试用户名:

测试成功:

继续测试正确的输入:

测试成功:

查看数据库中的数据:

 可以看到数据库中显示的密码已经进行了加密处理。

10. 实现前端界面

完成表单校验工作后,需要构造要提交的数据:

  1. 通过选择器找到需要提交的标签
  2. 获取标签中的值,并封装成JS对象
    1. 前后端交互时定义的参数列表:参数名=对象的属性 参数值=标签中的值(用户输入的值)

完整的前端代码可以参考:forum: 论坛项目 - Gitee.com

接下来我们打开注册界面,输入已有的用户名,可以看到提示框弹出: 

接下来,我们在 html 文件中,自定义弹出的提示框的样式。

 $.toast({
     heading: '警告',
     text: respData.message,
     icon: 'Warning'
 });

 停止运行启动类,重新进行注册,可以看到:

接下来,我们正确的进行注册: 

可以看到通过前端界面进行注册后,数据库中正确增加了一条数据。 

并且注册成功后直接跳转至了登录界面:


 用户注册的基本功能已经实现,在下篇文章中,我们将实现用户登录功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/82297.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蛊卦-拨乱反正

目录 前言 卦辞 爻辞 总结 前言 题外话&#xff0c;今天占卜时&#xff0c;看错了&#xff0c;以为占到了蛊卦&#xff08;后续会对自己的占卦经历进行补充&#xff0c;不断完善这个易经学习的专栏&#xff09;&#xff0c;那顺便就学习一下蛊卦&#xff0c;蛊惑人心&#…

QT TLS initialization failed问题(已解决) QT基础入门【网络编程】openssl

问题: qt.network.ssl: QSslSocket::connectToHostEncrypted: TLS initialization failed 这个问题的出现主要是使用了https请求:HTTPS ≈ HTTP + SSL,即有了加密层的HTTP 所以Qt 组件库需要OpenSSL dll 文件支持HTTPS 解决: 1.加入以下两行代码获取QT是否支持opensll以…

STM32--TIM定时器(3)

文章目录 输入捕获简介频率测量输入捕获通道输入捕获基本结构PWMI的基本结构输入捕获模式测量PWM频率和占空比代码 编码器接口正交编码器工作模式接口基本结构TIM编码接口器测速代码&#xff1a; 输入捕获简介 输入捕获IC(Input Capture)&#xff0c;是处理器捕获外部输入信号…

JVM——引言+JVM内存结构

引言 什么是JVM 定义: Java VirtualMachine -java 程序的运行环境 (ava 二进制字节码的运行环境) 好处: 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收功能数组下标越界检查&#xff0c;多态 比较: jvm jre jdk 学习jvm的作用 面试理解底层实现原理中…

mybatis plus 配置自动设置创建时间和创建人id

1.新建 MyMetaObjectHandler package com.ruoyi.framework.config;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import com.ruoyi.common.bean.LocalUser; import com.ruoyi.coupon.domain.CouponUser; import org.apache.ibatis.reflection.MetaObjec…

深度学习模型训练好后使用time.time()测试处理每一帧速度(时间)和模型推理速度(时间)

在深度学习中&#xff0c;如果想测试训练好的模型处理每一帧图像的运行时间&#xff0c;可以在模型预测代码段添加时间统计的逻辑: 目录 加载预训练模型遍历测试图像汇总统计预测测试集全部图像的平均处理时间测试结果样式 加载预训练模型 model torch.load(best_model.pth) …

Datawhale Django后端开发入门 TASK03 QuerySet和Instance、APIVIew

一、QuerySet QuerySet 是 Django 中的一个查询集合&#xff0c;它是由 Model.objects 方法返回的&#xff0c;并且可以用于生成数据库中所有满足一定条件的对象的列表。 QuerySet 在 Django 中表示从数据库中获取的对象集合,它是一个可迭代的、类似列表的对象集合。主要特点…

产品经理如何提高用户画像效果?SIKT模型

产品经理做用户画像&#xff0c;最担心被业务方反馈&#xff1a;没效果。这往往是由用户画像与业务场景脱节造成的。那么我们该如何从业务场景出发&#xff0c;让用户画像更有效&#xff1f;一般来说&#xff0c;我们可以采用SIKT模型解决这个问题。 用户画像 ​ 1、SIK…

kafka安装说明以及在项目中使用

一、window 安装 1.1、下载安装包 下载kafka 地址&#xff0c;其中官方版内置zk&#xff0c; kafka_2.12-3.4.0.tgz其中这个名称的意思是 kafka3.4.0 版本 &#xff0c;所用语言 scala 版本为 2.12 1.2、安装配置 1、解压刚刚下载的配置文件&#xff0c;解压后如下&#x…

通过安全日志读取WFP防火墙放行日志

前言 之前的文档中&#xff0c;描写了如何对WFP防火墙进行操作以及如何在防火墙日志中读取被防火墙拦截网络通讯的日志。这边文档&#xff0c;着重描述如何读取操作系统中所有被放行的网络通信行为。 读取系统中放行的网络通信行为日志&#xff0c;在win10之后的操作系统上&am…

Monitor.Analog烧机室|高温老化箱软件概要设计

Monitor.Analog产品老化试验软件概要设计&#xff1a; 1. 引言&#xff1a; 模拟量采集软件的目标是实现对模拟量信号的采集、处理和展示。该软件旨在提供一个用户友好的界面&#xff0c;允许用户配置采集参数、实时监测模拟量信号&#xff0c;并提供数据分析和导出功能。 2. 功…

LVS-DR集群(一台LVS,一台CIP,两台web,一台NFS)的构建以及LVS-DR模式工作原理和特点

一.LVS-DR工作模式原理和特点 1.工作模式 2.模式特点 二.构建环境 1.五台关闭防火墙&#xff0c;关闭selinux&#xff0c;拥有固定IP&#xff0c;部署有http服务的虚拟机&#xff0c;LVS设备下载ipvsadm工具&#xff0c;NFS 设备需要下载rpcbind和nfs-utils 2.实现功能 3…

【简单认识Docker基本管理】

文章目录 一、Docker概述1、定义2.容器化流行的原因3.Docker和虚拟机的区别4.Docker核心概念 二、安装docker三、镜像管理1.搜索镜像2.下载&#xff08;拉取&#xff09;镜像3.查看已下载镜像4.查看镜像详细信息5.修改镜像标签6.删除镜像7.导出镜像文件和拉取本地镜像文件8.上传…

ruoyi-vue-pro yudao 项目报表设计器 积木报表模块启用及相关SQL脚本

目前ruoyi-vue-pro 项目虽然开源&#xff0c;但是report模块被屏蔽了&#xff0c;查看文档却要收费 199元&#xff08;知识星球&#xff09;&#xff0c;价格有点太高了吧。 分享下如何启用 report 模块&#xff0c;顺便贴上sql相关脚本。 一、启用模块 修改根目录 pom.xml …

《安富莱嵌入式周报》第320期:键盘敲击声解码, 军工级boot设计,开源CNC运动控制器,C语言设计笔记,开源GPS车辆跟踪器,一键生成RTOS任务链表

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV1Cr4y1d7Mp/ 《安富莱嵌入式周报》第320期&#xff1a;键盘敲击…

空洞卷积网络实现

代码中涉及的图片实验数据下载地址&#xff1a;https://download.csdn.net/download/m0_37567738/88235543?spm1001.2014.3001.5501 代码&#xff1a; import torch.nn as nn import numpy as npfrom matplotlib import pyplot as plt import time #from utils import get_ac…

【深入探究人工智能】:常见机器学习算法总结

文章目录 1、前言1.1 机器学习算法的两步骤1.2 机器学习算法分类 2、逻辑回归算法2.1 逻辑函数2.2 逻辑回归可以用于多类分类2.3 逻辑回归中的系数 3、线性回归算法3.1 线性回归的假设3.2 确定线性回归模型的拟合优度3.3线性回归中的异常值处理 4、支持向量机&#xff08;SVM&a…

基于ArcGis提取道路中心线

基于ArcGis提取道路中心线 文章目录 基于ArcGis提取道路中心线前言一、生成缓冲区二、导出栅格数据三、导入栅格数据四、新建中心线要素五、生成中心线总结 前言 最近遇到一个问题&#xff0c;根据道路SHP数据生成模型的时候由于下载的道路数据杂项数据很多&#xff0c;所以导…

链表之第三回

欢迎来到我的&#xff1a;世界 该文章收入栏目&#xff1a;链表 希望作者的文章对你有所帮助&#xff0c;有不足的地方还请指正&#xff0c;大家一起学习交流 ! 目录 前言第一题&#xff1a;判断是否为环形链表第二题&#xff1a;找到两条链表的相交点第三题&#xff1a;返回…

webSocket 开发

1 认识webSocket WebSocket_ohana&#xff01;的博客-CSDN博客 一&#xff0c;什么是websocket WebSocket是HTML5下一种新的协议&#xff08;websocket协议本质上是一个基于tcp的协议&#xff09;它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和带宽…