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 目录:前端资源

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

8. 实现业务功能--用户注册_第1张图片

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

1. 顺序图

8. 实现业务功能--用户注册_第2张图片

注意, 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 

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

8. 实现业务功能--用户注册_第3张图片

根据用户名查询信息:

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

8. 实现业务功能--用户注册_第4张图片

注意: 

8. 实现业务功能--用户注册_第5张图片

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

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

8. 实现业务功能--用户注册_第6张图片




  
  
  
  

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

5. 修改 DAO 

8. 实现业务功能--用户注册_第7张图片

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

8. 实现业务功能--用户注册_第8张图片

6. 创建 Service 接口

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

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

8. 实现业务功能--用户注册_第9张图片

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

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

7. 实现接口

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

8. 实现业务功能--用户注册_第10张图片

 命名规则:实体类名 + 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;
    }
}

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

8. 实现业务功能--用户注册_第11张图片

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

8. 实现业务功能--用户注册_第12张图片

public class StringUtils {
    /**
     * 校验指定的字符串是否为空
     * @param value 待校验的字符串
     * @return true 空 
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 类中右键生成测试方法:

8. 实现业务功能--用户注册_第13张图片

加上 @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() {
    }
}

查询结果如下:

8. 实现业务功能--用户注册_第14张图片

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

接下来添加 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 类:

8. 实现业务功能--用户注册_第15张图片

8. 实现业务功能--用户注册_第16张图片

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

结果如下图所示:

8. 实现业务功能--用户注册_第17张图片

在 pom.xml 中添加依赖:



 commons-codec
 commons-codec

在 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

8. 实现业务功能--用户注册_第18张图片

接下来进行密码测试:

8. 实现业务功能--用户注册_第19张图片

测试成功:

8. 实现业务功能--用户注册_第20张图片

继续测试用户名:

8. 实现业务功能--用户注册_第21张图片

测试成功:

8. 实现业务功能--用户注册_第22张图片

继续测试正确的输入:

8. 实现业务功能--用户注册_第23张图片

测试成功:

8. 实现业务功能--用户注册_第24张图片

查看数据库中的数据:

8. 实现业务功能--用户注册_第25张图片

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

10. 实现前端界面

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

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

8. 实现业务功能--用户注册_第26张图片

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

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

8. 实现业务功能--用户注册_第27张图片

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

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

8. 实现业务功能--用户注册_第28张图片

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

8. 实现业务功能--用户注册_第29张图片

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

8. 实现业务功能--用户注册_第30张图片

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

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

8. 实现业务功能--用户注册_第31张图片


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

你可能感兴趣的:(项目,java,spring,boot)