SpringBoot从零到全栈商城(四)用户登录注册、请求类校验、自定义异常

510 阅读5分钟

章节介绍

本章主要介绍了,entity实体类和请求model类的创建,撸一个用户登录注册功能,以及创建通用的统一结果返回类和自定义异常。

项目介绍

记录SpringBoot从零到实现一整个商城后端的过程。

项目组成及技术栈

  • 接口 SpingBoot + JPA + mySql
  • 后台 vue + vue-element-admin
  • 移动端 uniapp + colorui + vuex + scss

登录注册展示

  • 手机号注册
  • 手机号登录
  • 根据token获取用户信息

entity实体类创建

创建entity目录,新建User类, 里面用到的lombox工具和审计功能在上一章节已介绍

package com.smxy.mall.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.util.Date;

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "fat_user")
@Data
public class User {
    @Id //主键Id
    @Column(name="id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @CreatedDate
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    @Column(name = "createTime",updatable = false,nullable=false)
    private Date createTime; //创建时间
    @LastModifiedDate
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date updateTime; //更新时间
    @CreatedBy
    @Column(name = "createUser",updatable = false,nullable=false)
    private String createUser; //创建者
    @LastModifiedBy
    private String updateUser; //更新者

    private String userName; //账号
    private String userPsw;  //密码
    private String phone; //手机号
    private String openId; //微信标识
    private String type; //用户类型

    private String name;  //昵称姓名
    private String img; //头像
    private String sex; //性别
    private int defaultId; //默认地址
}

请求model类

model里面加入参数校验

package com.smxy.mall.model.request.user;

import io.swagger.annotations.ApiModel;
import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

@ApiModel
@Data
public class PhoneReq {
    @NotNull(message = "~手机号不能为空")
    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^1\\d{10}$",message = "手机号格式错误")
    private String phone;
    @NotNull(message = "~密码不能为空")
    @NotBlank(message = "密码不能为空")
    private String userPsw;
}

validation 使用

  • pom依赖
<!-- validator -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.2.4.Final</version>
</dependency>
  • 常用注解

手机号登录注册

因为是学习,这里就简单的用手机号和密码进行注册登录

  • service 定义方法
package com.smxy.mall.service;

import com.smxy.mall.entity.User;

public interface UserService {
    User saveFromPhone(String phone, String psw); //手机号密码注册
    User findByPhone(String phone); //判断手机号是否存在
    User findByPhoneAndUserPsw(String phone, String userPsw); //验证手机号密码
}
  • dao
package com.smxy.mall.dao;

import com.smxy.mall.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface UserDao extends JpaRepository<User, Object>, JpaSpecificationExecutor<User> {
    User findByPhoneAndUserPsw(String phone, String userPsw); //手机号密码登录

    User findByPhone(String phone);  //手机号是否存在
}

  • service 实现类
package com.smxy.mall.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.smxy.mall.common.CustomException;
import com.smxy.mall.common.Response;
import com.smxy.mall.dao.UserDao;
import com.smxy.mall.entity.User;
import com.smxy.mall.model.Current;
import com.smxy.mall.service.UserService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserDao userDao;

    /**
     * 手机号密码注册
     * @param phone
     * @param psw
     * @return
     */
    @Override
    public User saveFromPhone(String phone, String psw) {
        User obj = userDao.findByPhone(phone); //根据手机号查询用户
        if(obj!=null){
            throw new CustomException(Response.fail("手机号"+phone+"已存在"));  //自定义异常
        }
        String md5Psw = DigestUtils.md5DigestAsHex(psw.getBytes());
        User user = new User();
        user.setPhone(phone);
        user.setUserPsw(md5Psw);
        user.setType("app");
        return userDao.save(user);
    }

    /**
     * 手机号密码登录
     * @param phone
     * @param userPsw
     * @return
     */
    @Override
    public User findByPhoneAndUserPsw(String phone, String userPsw) {
        String md5Psw = DigestUtils.md5DigestAsHex(userPsw.getBytes());
        return userDao.findByPhoneAndUserPsw(phone,md5Psw);
    }

    @Override
    public User findByPhone(String phone) {
        return userDao.findByPhone(phone);
    }
}
  • controller 加上@Valid注解, 请求model中的校验才会生效 @CurrentUser注解 解析token,用户信息可从Current类中获取
package com.smxy.mall.controller;

import com.smxy.mall.annotation.CurrentUser;
import com.smxy.mall.annotation.JwtIgnore;
import com.smxy.mall.common.Response;
import com.smxy.mall.model.Audience;
import com.smxy.mall.model.Current;
import com.smxy.mall.model.request.user.AccountReq;
import com.smxy.mall.model.request.user.PageListReq;
import com.smxy.mall.model.request.user.PhoneReq;
import com.smxy.mall.utils.JwtTokenUtil;
import com.smxy.mall.entity.User;
import com.smxy.mall.service.UserService;
import com.smxy.mall.utils.Token;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;

@Api(tags = "用户模块")
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
    @Autowired
    private Audience audience;

    @ApiOperation("手机号注册")
    @JwtIgnore  //忽然jwt拦截器
    @PostMapping("/register/phone")
    public Object phoneReg(@Valid PhoneReq phoneReq){
        return Response.success(userService.saveFromPhone(phoneReq.getPhone(),phoneReq.getUserPsw()));
    }

    @ApiOperation("手机号登录")
    @JwtIgnore
    @PostMapping("/login/phone")
    public Object phoneLogin(@Valid PhoneReq phoneReq) {
        User obj = userService.findByPhoneAndUserPsw(phoneReq.getPhone(),phoneReq.getUserPsw());
        if(StringUtils.isEmpty(obj)){
            return Response.fail("手机号或密码错误");
        }else{
            String token = JwtTokenUtil.createJWT(obj,audience); //创建token
            Map<String,Object> map = new HashMap();
            map.put("token",token);
            map.put("userInfo",obj);
            //返回token和用户信息
            return Response.success(map);
        }
    }

    @ApiOperation("用户个人信息")
    @GetMapping("/userInfo")
    public Object userInfo(@CurrentUser Current current) {
        return Response.success(userService.findById(current.getUserId()));
    }
}

Response 统一结果返回类

package com.smxy.mall.common;

import lombok.Data;

@Data
public class Response<T> {
    private static final String successCode = "0";
    private static final String errorCode = "1";
    private static final String successMsg = "请求正常";
    private static final String errorMsg = "请求失败";
    private String code;
    private String msg;
    private T data;

    public Response() {}

    public Response(String code, String msg){
        this.code = code;
        this.msg = msg;
    }

    public Response(String code,T data,String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    /**
     * 有返回数据
     * 返回固定msg code
     * @param data
     * @return
     */
    public static Response success(Object data) {
        return new Response(successCode,data,successMsg);
    }

    /**
     * 返回固定code  自定义msg
     * @param info
     * @return
     */
    public static Response fail(String info) {
        return new Response(errorCode,info);
    }

    /**
     * 有返回数据
     * 返回固定code  自定义msg
     * @param data
     * @param info
     * @return
     */
    public static Response success(Object data, String info) {
        return new Response(successCode,data,info);
    }

    /**
     * 无返回数据
     * 返回固定msg code
     * @return
     */
    public static Response success() {
        return new Response(successCode,successMsg);
    }

    /**
     * 无返回数据
     * 返回固定code  msg自定义
     * @param info
     * @return
     */
    public static Response success(String info) {
        return new Response(successCode,info);
    }

    /**
     * 返回固定code msg
     * @return
     */
    public static Response fail() {
        return new Response(errorCode,errorMsg);
    }


    /**
     * 返回自定义code msg
     * @param code
     * @param info
     * @return
     */
    public static Response fail(String code, String info) {
        return new Response(code,info);
    }

    @Override
    public String toString() {
        return "Response{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

自定义异常

  • 在common目录新建CustomException类
package com.smxy.mall.common;

import lombok.Getter;

@Getter
public class CustomException extends RuntimeException {
    private String code;
    private String message;
    
    public CustomException(Response response) {
        this.code = response.getCode();
        this.message = response.getMsg();
    }
}
  • 在common目录新建全局异常类 GlobalException
package com.smxy.mall.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.List;

/**
 * 全局异常处理器
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理自定义异常
     */
    @ExceptionHandler(CustomException.class)
    public Object handleException(CustomException e) {
        // 打印异常信息
        log.error("### 异常信息:{} ###", e.getMessage());
        return Response.fail(e.getCode(),e.getMessage());
    }

    /**
     * 参数错误异常
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    public Object handleException(Exception e) {

        if (e instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
            BindingResult result = validException.getBindingResult();
            StringBuffer errorMsg = new StringBuffer();
            if (result.hasErrors()) {
                List<ObjectError> errors = result.getAllErrors();
                errors.forEach(p ->{
                    FieldError fieldError = (FieldError) p;
                    errorMsg.append(fieldError.getDefaultMessage()).append(",");
                    log.error("### 1请求参数错误:{"+fieldError.getObjectName()+"},field{"+fieldError.getField()+ "},errorMessage{"+fieldError.getDefaultMessage()+"}"); });
            }
            return Response.fail("参数无效"+e.getMessage());
        } else if (e instanceof BindException) {
            BindException bindException = (BindException)e;
            if (bindException.hasErrors()) {
                log.error("### 2请求参数错误: {}", bindException.getAllErrors());
            }
            return Response.fail(bindException.getBindingResult().getFieldError().getDefaultMessage());
        }else{
            return Response.fail("参数无效");
        }
    }

//    @ExceptionHandler(NullPointerException.class)
//    public Object handleException(NullPointerException e){
//        System.out.println(e);
//        log.error("### 空指针异常:{} ###", e.getMessage());
//        return Response.fail("空指针异常"+e.getMessage());
//    }

    /**
     * 处理所有不可知的异常
     */
    @ExceptionHandler(Exception.class)
    public Object handleOtherException(Exception e){
        //打印异常堆栈信息
        e.printStackTrace();
        // 打印异常信息
        log.error("### 不可知的异常:{} ###", e.getMessage());
        return Response.fail("系统繁忙,请稍后重试"+e.getMessage());
    }

}

在service层中,直接throw new CustomException(Response.fail("错误信息")),就可以将统一格式的错误信息返回给前端

写在最后

有写的不对的地方,欢迎大家指出来,我好及时改正。一起成长~