业务流程

  • 用户注册服务
  • 用户登录服务
  • 用户发表评论
  • 用户回复评论
  • 查询文章评论

    模型

    在model包下根据下图UML类图创建模型。
    评论服务开发 - 图1
    User里有个pwd,我们知道密码是不能够泄露的,需要过滤掉这个属性。使用jackson注解

    1. @JsonSerialize(using = NullSerializer.class)
    2. private String pwd;

    API返回JSON结果时就不存在这个字段了。
    格式化时间:

    1. @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    2. private LocalDateTime gmtCreated;
    3. @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    4. private LocalDateTime gmtModified;

    用户注册服务

    注册一个账户是根据用户名,密码来注册,这个注册行为可能会失败,比如用户名重复,密码太简单等。为了能够正确描述返回数据,我们新增一个Result公共模型。用于处理API返回值,类似之前的Paging。 ```java package com.lzx.comment.model;

import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data;

import java.io.Serializable;

/**

  • @ClassName Result
  • @Author 刘正星
  • @Date 2020/7/16 13:11 **/ @Data public class Result implements Serializable { @JsonProperty(“isSuccess”) private boolean success = false;

    private String code; private String message;

    private D data; }

  1. `@JsonProperty("isSuccess")`这个注解用于自定义JSON输出的时候字段名称。效果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/316618/1594876727162-9b614bd1-6474-496c-ae22-fb247db55f74.png#align=left&display=inline&height=167&margin=%5Bobject%20Object%5D&name=image.png&originHeight=167&originWidth=518&size=6204&status=done&style=none&width=518)<br />这些JSON技巧要记住,比较常用。<br />改造getAll()
  2. ```java
  3. @GetMapping("/users")
  4. @ResponseBody
  5. private Result<Paging<UserDO>> getAll(@RequestParam(value = "pageNum" ,defaultValue = "1") Integer pageNum, @RequestParam(value = "pageSize",defaultValue = "15") Integer pageSize) {
  6. Result<Paging<UserDO>> result = new Result<>();
  7. Page<UserDO> page = PageHelper.startPage(pageNum, pageSize).doSelectPage(() -> userDAO.findAll());
  8. result.setSuccess(true);
  9. result.setData(new Paging<>(page.getPageNum(), page.getPageSize(), page.getPages(), page.getTotal(), page.getResult()));
  10. return result;
  11. }

我们发现 这里的 code,和 message 字段为 null,还是输出到 JSON 中去了,这样很浪费流量,我们需要把null的字段过滤掉,还有一些JSON的配置可以集中优化一下。
application.properties文件中:

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

上述配置作用:

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

    用户注册接口/实现

    ```java package com.lzx.comment.Service;

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

/**

  • @InterfaceName UserService
  • @Author 刘正星
  • @Date 2020/7/16 13:26 / public interface UserService { /
    • 用户注册
    • @param userName
    • @param pwd
    • @return */ public Result register(String userName,String pwd); }
返回值类型设置为 Result<User> 为了传递错误信息,如果创建失败,就可以通过Result 模型的 isSuccess来确定,而错误信息可以通过code 和message属性来获取。
<a name="Pkx72"></a>
### 实现类
注册逻辑<br />![](https://cdn.nlark.com/yuque/0/2020/svg/316618/1594877588346-5403f851-3ddc-4aba-99e2-9157becc1cae.svg#align=left&display=inline&height=586&margin=%5Bobject%20Object%5D&originHeight=586&originWidth=599&size=0&status=done&style=none&width=599)
<a name="QaUDy"></a>
#### 补充

- **判断非空非null**

企业中使用 commons-lang3库来处理字段串:
```xml
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.10</version>
</dependency>

在豆瓣APP项目中也用过~

  • 加密

使用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();

包路劲

org.apache.commons.codec.digest.DigestUtils
package com.lzx.comment.service.impl;

import com.lzx.comment.service.UserService;
import com.lzx.comment.dao.UserDAO;
import com.lzx.comment.dataobject.UserDO;
import com.lzx.comment.model.Result;
import com.lzx.comment.model.User;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @ClassName UserServiceImpl
 * @Author 刘正星
 * @Date 2020/7/16 13:32
 **/
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDAO userDAO;
    @Override
    public Result<User> register(String userName, String pwd) {
        Result<User> result = new Result<>();
        //用户名为空
        if (StringUtils.isEmpty(userName)){
            result.setCode("600");
            result.setMessage("用户名不能为空!");
        }else{
            //用户名不为空,密码为空
            if (StringUtils.isEmpty(pwd)){
                result.setCode("601");
                result.setMessage("密码不能为空");
            }else {
                //用户名不为空,密码不为空
                //判断用户是否存在
                UserDO userDO = userDAO.findByUserName(userName);
                //用户不存在
                if (userDO == null){
                    String saltPwd = pwd+"_lzx0203";
                    String md5Pwd = DigestUtils.md5Hex(saltPwd).toUpperCase();
                    userDO = new UserDO();
                    userDO.setUserName(userName);
                    userDO.setNickName(userName);
                    userDO.setPwd(md5Pwd);
                    //储存用户记录
                    userDAO.add(userDO);
                    result.setSuccess(true);
                    //将 UserDO 对象转化为 User 对象
                    User user = new User();
                    user.setId(userDO.getId());
                    user.setUserName(userDO.getUserName());
                    user.setNickName(userDO.getNickName());

                    result.setData(user);
                }else{
                    //用户名已经存在
                    result.setCode("602");
                    result.setMessage("用户名已经存在");
                }
            }
        }

        return result;
    }
}

思考:注册逻辑答案只用 if 去判断,相比于自己用的 if - else两者有什么区别?

  • if-else 多层嵌套可阅读性较差。
  • 只用if,如果有一个if满足,直接return,下面的代码不会在执行。

    开发UserAPI

    企业开发模式

    设计领域模型——开发基础DAO——开发Service——开发API
package com.lzx.comment.api;

import com.lzx.comment.model.Result;
import com.lzx.comment.model.User;
import com.lzx.comment.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @ClassName UserAPI
 * @Author 刘正星
 * @Date 2020/7/16 14:31
 **/
@Controller
public class UserAPI {
    @Autowired
    private UserService userService;
    @PostMapping("/api/user/reg")
    @ResponseBody
    public Result<User> register(@RequestParam("userName")String userName,@RequestParam("pwd")String pwd){

        return userService.register(userName,pwd);
    }
}

用户登录服务

用户可能很多,所以一般我们把userId(user表的id值)存储在session里,竟可能节省session空间。
接口:

  /**
     * 用户登录
     * @param userName
     * @param pwd
     * @return
     */
    public Result<User> login(String userName,String pwd);

登录逻辑

评论服务开发 - 图2
实现类:

 @Override
    public Result<User> login(String userName, String pwd) {

        Result<User> result = new Result<>();
        if (StringUtils.isEmpty(userName)){
            result.setSuccess(false);
            result.setCode("600");
            result.setMessage("用户名不能为空");
            return result;
        }
        if (StringUtils.isEmpty(pwd)){
            result.setCode("601");
            result.setSuccess(false);
            result.setMessage("密码不能为空");
            return  result;
        }

        UserDO userDO = userDAO.findByUserName(userName);
        if (userDO == null){
            result.setCode("602");
            result.setMessage("用户名不存在");
            return result;

        }
        String saltPwd = pwd +"_lzx0203";
        String md5Pwd = DigestUtils.md5Hex(saltPwd).toUpperCase();
        if (!md5Pwd.equals(userDO.getPwd())){
            result.setCode("603");
            result.setMessage("密码不对");
            return result;
        }
        User user = userDO.toModel();

        result.setSuccess(true);
        result.setData(user);
        return result;
    }

API:注意保存userId到session中。

  @PostMapping("/api/user/login")
    @ResponseBody
    public Result<User> login(@RequestParam("userName")String userName, @RequestParam("pwd")String pwd, HttpServletRequest request, HttpServletResponse response){
        Result<User> result = userService.login(userName,pwd);

        if (result.isSuccess()){

            request.getSession().setAttribute("userId",result.getData().getId());
        }
        return result;
    }

用户退出

API

  @GetMapping ("/api/user/logout")
    @ResponseBody
    public Result<User> logout(HttpServletRequest request){
        request.getSession().removeAttribute("userId");
        Result<User> result = new Result<>();
        result.setMessage("退出成功");
        result.setSuccess(true);
        return result;
    }

优化DO和Model互转

// 将 UserDO 对象转化为 User 对象
User user = new User();
user.setId(userDO.getId());
user.setUserName(userDO.getUserName());
user.setNickName(userDO.getNickName());
user.setAvatar(userDO.getAvatar());
user.setGmtCreated(userDO.getGmtCreated());
user.setGmtModified(userDO.getGmtModified());

我们一般吧转换代码抽象成公共方法,放在DO对象中,命名为toModel。

import com.youkeda.comment.model.User;
import java.time.LocalDateTime;

public class UserDO {

    /**
     * DO 转换为 Model
     *
     * @return
     */
    public User toModel() {
        User user = new User();
        user.setId(getId());
        user.setUserName(getUserName());
        user.setNickName(getNickName());
        user.setAvatar(getAvatar());
        user.setGmtCreated(getGmtCreated());
        user.setGmtModified(getGmtModified());
        return user;
    }
}