zoukankan      html  css  js  c++  java
  • 前后端分离Controller层开发 笔记

    前后端分离Controller层开发 笔记

    如要开发如下用户接口:

    1.登录

    POST /user/login

    request

    Content-Type: application/json

    {
    	"username":"admin",
    	"password":"admin",
    }
    

    response

    fail

    {
        "status": 1,
        "msg": "密码错误"
    }
    

    success

    {
        "status": 0,
        "data": {
            "id": 12,
            "username": "aaa",
            "email": "aaa@163.com",
            "phone": null,
            "role": 0,
            "createTime": 1479048325000,
            "updateTime": 1479048325000
        }
    }
    

    2.注册

    POST /user/register

    request

    {
    	"username":"admin",
    	"password":"admin",
    	"email":"admin@qq.com"
    }
    

    response

    success

    {
        "status": 0,
        "msg": "成功"
    }
    

    fail

    {
        "status": 2,
        "msg": "用户已存在"
    }
    

    3.获取登录用户信息

    GET /user

    request

    无参数
    

    response

    success

    {
        "status": 0,
        "data": {
            "id": 12,
            "username": "aaa",
            "email": "aaa@163.com",
            "phone": null,
            "role": 0,
            "createTime": 1479048325000,
            "updateTime": 1479048325000
        }
    }
    

    fail

    {
        "status": 10,
        "msg": "用户未登录,无法获取当前用户信息"
    }
    
    

    4.退出登录

    **POST /user/logout

    request

    response

    success

    {
        "status": 0,
        "msg": "退出成功"
    }
    

    fail

    {
        "status": -1,
        "msg": "服务端异常"
    }
    

    分析接口:

    由于项目是前后端分离的项目,故所有controller返回值都是json格式,可在controller层添加@ResponseBody实现。

    由于前段发送http数据采用Content-Type: application/json 格式,故需要 在接受参数时使用@RequestBody 接受参数(如果前端采用:x-www-form-urlencoded ,则controller接口方法需要使用@RequestParam(value= "") 注解 接受参数)

    分析所有用户接口,返回值有 三个属性 status,msg, data,为便于处理返回结果值,可新建返回值对象ResponseVo, 其中 data有时需要返回,有时不需要返回,可在ResponseVo对象添加@JsonInclude(value = JsonInclude.Include.NON_NULL)注解使其在序列化时排除属性为null的值。同时考虑到data中的数据为User对象,后期也可能会是其他对象,故ResponseVo对象的data属性需要用泛型

    ResponseVo对象设计如下:

    package cn.blogsx.mimall.vo;
    
    import cn.blogsx.mimall.enums.ResponseEnum;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import lombok.Data;
    import org.springframework.validation.BindingResult;
    
    import java.util.Objects;
    
    /**
     * @author Alex
     * @create 2020-03-26 20:08
     **/
    @Data
    @JsonInclude(value = JsonInclude.Include.NON_NULL)
    public class ResponseVo<T> {
        private Integer status;
    
        private String msg;
    
        private T data;
    
        private ResponseVo(Integer status, String msg) {
            this.status = status;
            this.msg = msg;
        }
        private ResponseVo(Integer status, T data) {
            this.status = status;
            this.data=data;
        }
    
    }
    
    

    根据需要添加构造方法ResponseVo(Integer status, String msg)、ResponseVo(Integer status, T data)

    不同的错误需要不同的方法去返回相应,故可添加如下静态方法:

     public static <T> ResponseVo<T> successByMsg(String msg) {
            return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(),msg);
        }
        public static <T> ResponseVo<T> success(T data) {
            return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(),data);
        }
        public static <T> ResponseVo<T> success() {
            return new ResponseVo<>(ResponseEnum.SUCCESS.getCode(),ResponseEnum.SUCCESS.getDesc());
        }
    
    
        public static <T> ResponseVo<T> error(ResponseEnum responseEnum) {
            return new ResponseVo<>(responseEnum.getCode(),responseEnum.getDesc());
        }
        public static <T> ResponseVo<T> error(ResponseEnum responseEnum,String msg) {
            return new ResponseVo<>(responseEnum.getCode(),msg);
        }
        public static <T> ResponseVo<T> error(ResponseEnum responseEnum, BindingResult bindingResult) {
            return new ResponseVo<>(responseEnum.getCode(),
                    Objects.requireNonNull(bindingResult.getFieldError()).getField()+" "+
                            bindingResult.getFieldError().getDefaultMessage());
        }
    

    分析属性:

    status属性的值为 整数,故使用Integer类型,为避免硬编码,status应使用 枚举对值做约束。

    msg属性分几种情况:0对应的 成功、2对应的 用户已存在、10对应的 用户未登录、-1对应的服务端错误(泛类型错误)

    status和msg存在对应关系,可用枚举类ResponseEnum做对应

    package cn.blogsx.mimall.enums;
    
    import lombok.Getter;
    
    @Getter
    public enum ResponseEnum {
        ERROR(-1,"服务端错误"),
    
        SUCCESS(0,"成功"),
    
        PASSWORD_ERROR(1,"密码错误"),
    
        USERNNAME_EXIST(2,"用户名已存在"),
    
        PARAM_ERROR(3,"参数错误"),
    
        EMAIL_EXIST(4,"邮箱已存在"),
    
        NEED_LOGIN(10,"用户未登录,请先登录"),
    
        USERNAME_OR_PASSWORD_ERROR(11,"用户名或密码错误"),
        ;
    
        Integer code;
    
        String desc;
    
        ResponseEnum(Integer code, String desc) {
            this.code = code;
            this.desc = desc;
        }
    }
    
    

    功能分析:

    由于用户登录 只需要传递username和password而不需要传递其他参数,并且需要做参数校验,所以为最好还是专门新建一个类专门用作传参。注册 同理

    UserLoginForm类传传参封装使用:

    package cn.blogsx.mimall.form;
    
    import lombok.Data;
    
    import javax.validation.constraints.NotBlank;
    
    /**
     * @author Alex
     * @create 2020-03-26 20:50
     **/
    @Data
    public class UserLoginForm {
    
        @NotBlank
        private String username;
    
        @NotBlank
        private String password;
    }
    
    

    UserRegisterForm类传传参封装使用:

    package cn.blogsx.mimall.form;
    
    import lombok.Data;
    
    import javax.validation.constraints.NotBlank;
    
    @Data
    public class UserRegisterForm {
    
        @NotBlank
        private String username;
    
        @NotBlank
        private String password;
    
        @NotBlank
        private String email;
    }
    
    

    在注册时需要将UserRegisterForm对象的属性拷贝成user对象才能插入数据库中,可使用Spring中提供的BeanUtils.copyProperties(userRegisterForm, user);方法实现。

    关于javax.validation.constraints相关注解说明:

    @NotBlank //用于String 判断空格
    @NotEmpty //用于集合
    @NotNull  //判断null
    

    使用如上注解校验参数时只需在Controller层的接口方法上添加@Valid 以及 BindingResult bindingResult 形式参数,并在方法体中使用如下判断并返回前端参数异常信息:

    if (bindingResult.hasErrors()) {
        
                return ResponseVo.error(PARAM_ERROR, bindingResult);
      }
    

    以上注解还可以定义参数异常原因:只需在使用注解时添加(message = "用户名不能为空") 即可使用如下方法得到并返回给前端:

    log.error("注册提交参数有误,{} {}",
                        Objects.requireNonNull(bindingResult.getFieldError()).getField(),
                        bindingResult.getFieldError().getDefaultMessage());
    

    Objects.requireNonNull(bindingResult.getFieldError()).getField()是得到有误从参数字段,bindingResult.getFieldError().getDefaultMessage()是得到我们在@NotBlank(message = "用户名不能为空")写入的message值,如果不写,则默认为 “不能为空” 四个字。

    具体实现接口的Controller方法:

    package cn.blogsx.mimall.controller;
    
    import cn.blogsx.mimall.consts.MiMallConst;
    import cn.blogsx.mimall.form.UserLoginForm;
    import cn.blogsx.mimall.form.UserRegisterForm;
    import cn.blogsx.mimall.pojo.User;
    import cn.blogsx.mimall.service.IUserService;
    import cn.blogsx.mimall.vo.ResponseVo;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.servlet.http.HttpSession;
    import javax.validation.Valid;
    import java.util.Objects;
    
    import static cn.blogsx.mimall.enums.ResponseEnum.PARAM_ERROR;
    
    /**
     * @author Alex
     * @create 2020-03-26 19:43
     **/
    @RestController
    @Slf4j
    public class UserController {
    
        @Autowired
        private IUserService userService;
    
        @PostMapping("/user/register")
        public ResponseVo<User> register(@Valid @RequestBody UserRegisterForm userRegisterForm,
                                         BindingResult bindingResult) {
            if (bindingResult.hasErrors()) {
                log.error("注册提交参数有误,{} {}",
                        Objects.requireNonNull(bindingResult.getFieldError()).getField(),
                        bindingResult.getFieldError().getDefaultMessage());
                return ResponseVo.error(PARAM_ERROR, bindingResult);
            }
            User user = new User();
            BeanUtils.copyProperties(userRegisterForm, user);//使用Spring中拷贝对象之间的方法
            return userService.register(user);
        }
    
        @PostMapping("/user/login")
        public ResponseVo<User> login(@Valid @RequestBody UserLoginForm userLoginForm
                , BindingResult bindingResult, HttpSession session) {
            if (bindingResult.hasErrors()) {
                return ResponseVo.error(PARAM_ERROR, bindingResult);
            }
            ResponseVo<User> userResponseVo = userService.login(userLoginForm.getUsername(), userLoginForm.getPassword());
    
            //设置session
            session.setAttribute(MiMallConst.CURRENT_USER, userResponseVo.getData());
            log.info("login sessionId={}",session.getId());
            return userResponseVo;
    
        }
    
        //session保存在内存里改进版:token+redis
        @GetMapping("/user")
        public ResponseVo<User> userInfo(HttpSession httpSession) {
            User user = (User) httpSession.getAttribute(MiMallConst.CURRENT_USER);
            return ResponseVo.success(user);
        }
    
        @PostMapping("/user/logout")
        public ResponseVo logout(HttpSession httpSession) {
            log.info("/user sessionId={}",httpSession.getId());
            httpSession.removeAttribute(MiMallConst.CURRENT_USER);
            return ResponseVo.success();
        }
    }
    
    

    登录有保存信息到session中,为降低耦合性,也可在专门新建常量MiMallConst类存储session键常量:

    package cn.blogsx.mimall.consts;
    
    /**
     * @author Alex
     * @create 2020-03-26 22:33
     **/
    public class MiMallConst {
        public static final String CURRENT_USER = "currentUser";
    }
    

    session默认有效时间为30分钟。也可在application文件中做如下配置:

    server:
      servlet:
        session:
          timeout: 120 #以秒为单位    两分钟
    

    为方便统一管理判断用户是否登录,可使用拦截器做统一拦截:

    package cn.blogsx.mimall;
    
    import cn.blogsx.mimall.consts.MiMallConst;
    import cn.blogsx.mimall.exception.UserloginException;
    import cn.blogsx.mimall.pojo.User;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.servlet.HandlerInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * @Dscription: 登录拦截器
     * @author Alex
     * @create 2020-03-27 10:13
     **/
    @Slf4j
    public class UserLoginInterceptor implements HandlerInterceptor {
        /**
         * true 表示继续流程,false 表示中断
         * @param request
         * @param response
         * @param handler
         * @return
         * @throws Exception
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            log.info("preHandle...");
    
            User user = (User) request.getSession().getAttribute(MiMallConst.CURRENT_USER);
            if (user == null) {
                log.info("user=null");
                throw new UserloginException();
    
    //            return ResponseVo.error(ResponseEnum.NEED_LOGIN);
    //            return false;
            }
            return true;
        }
    }
    
    

    此时拦截器还未生效,需要在配置拦截路径,新建InterceptorConfig配置类:

    package cn.blogsx.mimall;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * @author Alex
     * @create 2020-03-27 10:21
     **/
    @Configuration //使用该注解才能在Spring启动后起作用
    public class InterceptorConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new UserLoginInterceptor())
                    .addPathPatterns("/**")
                    .excludePathPatterns("/user/login","/user/register");//登录注册接口无需拦截
        }
    }
    

    Service接口及方法:

    package cn.blogsx.mimall.service;
    
    
    import cn.blogsx.mimall.pojo.User;
    import cn.blogsx.mimall.vo.ResponseVo;
    
    public interface IUserService {
        /**
         * 注册
         */
        ResponseVo<User> register(User user);
    
        /**
         * 登录
         */
        ResponseVo<User> login(String username,String password);
    
    }
    

    实现方法:

    package cn.blogsx.mimall.service.impl;
    
    import cn.blogsx.mimall.dao.UserMapper;
    import cn.blogsx.mimall.enums.RoleEnum;
    import cn.blogsx.mimall.pojo.User;
    import cn.blogsx.mimall.service.IUserService;
    import cn.blogsx.mimall.vo.ResponseVo;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.DigestUtils;
    
    import java.nio.charset.StandardCharsets;
    
    import static cn.blogsx.mimall.enums.ResponseEnum.*;
    
    /**
     * @author Alex
     * @create 2020-03-26 17:34
     **/
    @Service
    public class UserServiceImpl implements IUserService {
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public ResponseVo<User> register(User user) {
    
             //username不能重复
            int countByUsername = userMapper.countByUsername(user.getUsername());
            if(countByUsername>0) {
                return ResponseVo.error(USERNNAME_EXIST);
            }
    
            //email不能重复
            int countByEmail = userMapper.countByEmail(user.getEmail());
            if(countByEmail>0) {
                return ResponseVo.error(EMAIL_EXIST);
            }
            user.setRole(RoleEnum.CUSTOMER.getCode());
    
            //MD5摘要算法(Spring自带)
            user.setPassword( DigestUtils.md5DigestAsHex(user.getPassword()
                    .getBytes(StandardCharsets.UTF_8)));
    
            //写入数据库
            int resultCount = userMapper.insertSelective(user);
            if(resultCount==0) {
                return ResponseVo.error(ERROR);
            }
    
            return ResponseVo.success();
    
        }
    
        @Override
        public ResponseVo<User> login(String username, String password) {
            User user = userMapper.selectByUsername(username);//登录查询推荐只使用username查询即可
            if(user == null) {
                //用户不存在,返回 用户名或密码错误
                return ResponseVo.error(USERNAME_OR_PASSWORD_ERROR);
            }
            if(!user.getPassword().equalsIgnoreCase(
                    DigestUtils.md5DigestAsHex(
                    password.getBytes(StandardCharsets.UTF_8)))) {
                //密码错误,返回 用户名或密码错误
                return ResponseVo.error(USERNAME_OR_PASSWORD_ERROR);
    
            }
            user.setPassword("");
            return ResponseVo.success(user);
        }
    }
    
    

    使用Srping框架无需再单独写Utils方法做MD5加密,Spring中自带MD5加密方法: DigestUtils.md5DigestAsHex

    如果服务端发生其他异常,例如为500的异常也需要返回json格式的提示,此时可以新建RuntimeExcetion异常处理类:

    package cn.blogsx.mimall.exception;
    
    import cn.blogsx.mimall.enums.ResponseEnum;
    import cn.blogsx.mimall.vo.ResponseVo;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import static cn.blogsx.mimall.enums.ResponseEnum.ERROR;
    
    /**
     * @author Alex
     * @create 2020-03-26 21:43
     **/
    @ControllerAdvice
    public class RuntimeExceptionHandler {
    
        @ExceptionHandler(RuntimeException.class)
        @ResponseBody
    //    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)//可修改http协议状态码
        public ResponseVo handler(RuntimeException e) {
            return ResponseVo.error(ERROR,e.getMessage());
        }
    
        @ExceptionHandler(UserloginException.class)
        @ResponseBody
        public ResponseVo userLoginHandler() {
            return ResponseVo.error(ResponseEnum.NEED_LOGIN);
        }
    }
    
    

    由于用户账号和密码的错误采用HandlerInterceptor的实现方法拦截,且其preHandle方法返回值只能是true或false以用作判断是否继续流程(true 表示继续流程,false 表示中断)所以为了返回给前端 可新建UserloginException异常做异常处理

    public class UserloginException extends RuntimeException {
    }
    

    只需继承RuntimeException即可,只要有该类名即可,真正处理异常的地方在 RuntimeExceptionHandler中。

    关于测试:一般来说 开发人员单测只需测试service即可,controller层为测试人员测试。

    idea中可在serviceimpl 类中 右键空白处-> Go To ->test 即可在mavne test目录下自动生成测试类。

    Maven 打包是执行单测:

    mvn clean package
    

    Mavnen打包时跳过单测:

    mvn clean package -Dmaven.test.skip=true
    
  • 相关阅读:
    idhttp post 上传或下载时显示进度条(对接idhttp1.OnWork事件)
    DBGridEh列宽自动适应内容的简单方法
    好的python链接
    Git安装和使用
    Git及码云学习总结
    创建编写博客园写博客
    Git和Github的使用
    sql sever 两数据表差异比较EXCEPT、INTERSECT
    Vue学习(七)capture模式
    Vue学习(六)计算属性和侦听属性的区别以及应用场景
  • 原文地址:https://www.cnblogs.com/sxblog/p/12580741.html
Copyright © 2011-2022 走看看