5.2 用户注册服务

注册一个账户主要是根据用户名、密码来注册的,用户注册这个行为有可能会失败,比如用户名重复、密码太简单等等。为了能够正确的描述返回数据,我们新增一个 Result 公共(通用)模型,用于处理 API 返回值,类似之前的 Paging 一样

通用返回模型

package com.youkeda.comment.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;

/**
 * JSON 返回模型
 */
public class Result<D> implements Serializable {

    // 表示执行成功或失败
    @JsonProperty("isSuccess")
    private boolean success = false;

    // 返回消息短码,一般用于出错时,简短描述错误
    private String code;
    // 返回消息具体信息,一般用于出错时,比较详细的描述错误
    private String message;

    // 返回的具体数据
    private D data;

    // 省略 getter、setter

}

在企业级项目中,code 可以规定为一些错误枚举值。例如 “602” 表示 “用户名已存在”,在企业内部形成约定、大家都能理解就可以了。

上面的 Result 模型,我们在 success 字段上增加了一个注解@JsonProperty("isSuccess"),这个用于自定义 JSON 输出的时候的字段名称,待会看一下演示结果,感受一下

这些 JSON 的技巧请大家记住哦,比较常用的

泛型

这里哪里体现出公共、通用的含义呢?

特点就是泛型。

泛型实际上大家经常用到,例如很常见的 List 集合就支持泛型。

List<String> strings = new ArrayList<>(); 表示 strings 集合只能容纳字符串元素,而 List<User> users = new ArrayList<>(); 表示 users 集合只能容纳用户实例元素。

于是 Java 的集合是通用的,可以容纳任意对象,但通过泛型,程序明确的知道,一个集合类中容纳的元素的类型。

如果不使用泛型,集合就可以放入任意对象:List listdata表示 listdata 集合可以同时放入“字符串”和“用户实例”,容易造成混乱,此时 listdata.get(0) 你也不知道获取的具体是什么类型的对象

同理,通过 Result<D> 的声明,让返回模型支持泛型(类名后书写尖括号<D>是声明泛型的语法),此时声明的属性 D data 是不确定什么类型的。

稍微有点抽象了,大家要开动脑筋理解

但是没有关系,大家不要焦躁。在实际使用 Result 进行实例化对象时,就可以确定 data 属性的类型了:

Result<User> result = new Result<>();

尖括号中不再是抽象的 D 了,而是具体的 User ,那么就表示 result 实例对象的 data 的属性的类型就是 User 。

如果 Result 不是用于用户注册,而是用于其它项目场景,例如:

Result<String> result = new Result<>();

就表示 result 实例对象的 data 的属性的类型就是“字符串”,返回了一个简单的字符串数据。

所以 Result 返回值类是公共的、做到了通用性。

声明泛型的语法 <D> 其中字母 D 并不是硬性规定,你可以随意用其它 大写字母 ,一般不用单词(为了简短)。只是习惯上用了 data 属性的首字母 D 而已

getAll() 方法改造

我们改造一下 UserController.getAll() 方法看看

package com.youkeda.comment.control;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.youkeda.comment.dao.UserDAO;
import com.youkeda.comment.dataobject.UserDO;
import com.youkeda.comment.model.Paging;
import com.youkeda.comment.model.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.List;

@Controller
public class UserController {

    @Autowired
    private UserDAO userDAO;

    @GetMapping("/users")
    @ResponseBody
    public Result<Paging<UserDO>> getAll(@RequestParam(value = "pageNum", required = false) Integer pageNum,
                                         @RequestParam(value = "pageSize", required = false) Integer pageSize) {
        Result<Paging<UserDO>> result = new Result();

        if (pageNum == null) {
            pageNum = 1;
        }
        if (pageSize == null) {
            pageSize = 15;
        }
        // 设置当前页数为1,以及每页3条记录
        Page<UserDO> page = PageHelper.startPage(pageNum, pageSize).doSelectPage(() -> userDAO.findAll());

        result.setSuccess(true);
        result.setData(
            new Paging<>(page.getPageNum(), page.getPageSize(), page.getPages(), page.getTotal(), page.getResult()));
        return result;
    }
}

我想你应该看到了如上的数据,Result 的 success 属性输出 JSON 的时候变成了 isSuccess,符合我们的预期。

这里还有一个新的问题,code、message 字段为null,但是还是输出到 JSON 中去了,这样很浪费流量。我们需要把 null 字段的 JSON 给直接过滤掉,还有一些 JSON 的配置可以集中优化一下,打开 application.properties 文件,新增如下配置

spring.jackson.deserialization.fail-on-unknown-properties=false
spring.jackson.default-property-inclusion=non_null

上面2个配置分别的作用是:

  • 允许序列化未知的字段,可以兼容 Java 模型和 JSON 数据不一致的情况
  • 忽略 null 字段

代码演示:
style.youkeda.com/coursevideo/j6/j6-5-2-1_play.mp4

{
  "code": null,
  "message": null,
  "data": {},
  "isSuccess": true
}
  1. 作业2

完成了模型创建后,我们开始分步骤开始开发服务,首先我们完成用户注册服务

我们需要创建一个 UserService 接口,完整的路径是

com.youkeda.comment.service.UserService

注册一个账户主要是根据用户名、密码来注册的,我们创建一个 register 方法

package com.youkeda.comment.service;

import com.youkeda.comment.model.Result;
import com.youkeda.comment.model.User;

public interface UserService {

    /**
     * 注册用户
     * @param userName
     * @param pwd
     * @return
     */
    public Result<User> register(String userName, String pwd);

}

这里,我们把返回类型设置为 Result<User> 这是为了传递错误信息,如果创建失败,就可以通过 Result 模型的 isSuccess 来确定啦,而错误信息可以通过 code 和 message 属性来获取

根据前面的提示请完成 UserService 的创建

  1. 作业3
    现在需要我们完成 UserService 接口的实现类
package com.youkeda.comment.service.impl;

import com.youkeda.comment.model.Result;
import com.youkeda.comment.model.User;
import com.youkeda.comment.service.UserService;
import org.springframework.stereotype.Component;

@Component
public class UserServiceImpl implements UserService {
    @Override
    public Result<User> register(String userName, String pwd) {
        return null;
    }
}

UserServiceImpl 是一个 Spring Bean,所以需要添加 @Component 注解

任务要求

  1. 引入 UserDAO Bean
  2. 完成 register 逻辑

注册逻辑

补充说明

判断非空非 null

在企业中,我们一般会结合 commons-lang3 库来处理字符串,你需要在 pom.xml 中添加依赖

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.10</version>
</dependency>

完成配置后,可以使用 StringUtils.isEmpty(str) 来判断是否为空,StringUtils 的完整路径是

org.apache.commons.lang3.StringUtils
判断用户名是否存在

我们可以使用 UserDAO.findByUserName 查询数据是否存在,如果存在记录则说明用户名已经存在了

加密

一般来说,我们会使用 md5 算法进行加密,建议使用 commons-codec 库进行加密处理,同样需要先添加依赖

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.14</version>
</dependency>

看一下加密的例子:

// 密码加自定义盐值,确保密码安全
String saltPwd = pwd + "_ykd2050";
// 生成md5值,并转为大写字母
String md5Pwd = DigestUtils.md5Hex(saltPwd).toUpperCase();

DigestUtils 的完整包路径是

org.apache.commons.codec.digest.DigestUtils
Result 返回处理

如果当我们执行成功的时候,我们需要正确的返回 Result 对象,我们需要

Result<User> result = new Result<>();

UserDO userDO1 = new UserDO();
userDO1.setUserName(userName);
//初始昵称等于用户名
userDO1.setNickName(userName);
userDO1.setPwd(md5Pwd);
userDAO.add(userDO1);

//设置执行成功
result.setSuccess(true);

//将 UserDO 对象转化为 User 对象
User user = new User();
user.setId(userDO1.getId());
user.setUserName(userDO1.getUserName());
user.setNickName(userDO1.getNickName());

result.setData(user);

return result;

由于我们在register方法定义的返回模型是 Result<User> ,所以API层是完全感知不到 UserDO 模型的,这就需要我们手工转化一下对象类型

DO 到 Model 的互转在 Java 工程是最常见的行为,大家习惯就好,这也是 Java 语言不太优雅的地方,相比其他语言繁琐了点,有利有弊


完整的业务逻辑还是比较复杂的,大家请仔细思考完成本方法的业务逻辑哦

posted @ 2024-11-04 11:17  芝麻番茄  阅读(54)  评论(0)    收藏  举报