跟做springboot尚品甄选项目(二)

登录功能的书写

后端接口的书写

(1)创建配置文件

粘贴这两个文件(E:\project\AllProJect\Shangpin Selection\项目材料素材\资料\资料\03-配置文件)

在spzx-manager服务的src/resources目录下创建application.yml、application-dev.yml文件,文件的内容如下所示:

复制代码
# application.yml文件内容==================================================================================
spring:
  application:
    name: service-manager
  profiles:
    active: dev		# 激活的环境文件
    
#  application-dev.yml文件内容=============================================================================
# 配置服务端口号
server:
  port: 8501

# 配置数据库连接信息
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/db_spzx?characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true
    username: root
    password: root
  # Redis的相关配置
  data:
    redis:
      host: localhost
      port: 6379
#  password: 1234

# mybatis的配置
mybatis:
  config-location: classpath:/mybatis-config.xml
  mapper-locations: classpath:/mapper/*/*.xml

导入课程资料中提供的:mybatis-config.xml和logback-spring.xml配置文件

(2)创建启动类

ManagerApplication

复制代码
package com.atguigu.spzx;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ManagerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ManagerApplication.class , args) ;
    }

}

(3)创建实体类

SysUser

创建与数据库表对应的实体类:

BaseEntity: 定义一个BaseEntity实体类,在该实体类中定义公共的属性

  • 类名: BaseEntity

  • 包路径: com.xuan.spzx.model.entity.base

  • 用途: 为其他实体类提供通用字段

  • 使用@Data注解(Lombok)自动生成getter/setter方法

  • 使用@Schema注解为Swagger API文档提供字段说明

  • 使用@JsonFormat注解格式化日期时间显示格式

  • 实现Serializable接口支持序列化

    // com.xuan.spzx.model.entity.base
    package com.xuan.spzx.model.entity.base;

    import com.fasterxml.jackson.annotation.JsonFormat;
    import io.swagger.v3.oas.annotations.media.Schema;
    import lombok.Data;

    import java.io.Serializable;
    import java.util.Date;

    @Data
    public class BaseEntity implements Serializable {

    复制代码
      @Schema(description = "唯一标识")
      private Long id;
    
      @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
      @Schema(description = "创建时间")
      private Date createTime;
    
      @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
      @Schema(description = "修改时间")
      private Date updateTime;
    
      @Schema(description = "是否删除")
      private Integer isDeleted;

    }

SysUser用户实体类定义:

复制代码
// com.xuan.spzx.model.entity.system
@Data
public class SysUser extends BaseEntity {

    private static final long serialVersionUID = 1L;
    private String userName;  // 该字段的属性名称和数据表字段不一致
    private String password;
    private String name;
    private String phone;
    private String avatar;
    private String description;
    private Integer status;

}
LoginDto

创建一个LoginDto实体类,封装登录请求参数。

复制代码
// com.xuan.spzx.model.dto.system
package com.xuan.spzx.model.dto.system;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Data
@Schema(description = "用户登录请求参数")
public class LoginDto {

    @Schema(description = "用户名")
    private String userName ;

    @Schema(description = "密码")
    private String password ;

    @Schema(description = "提交验证码")
    private String captcha ;

    @Schema(description = "验证码key")
    private String codeKey ;

}
LoginVo

创建一个LoginVo实体类,封装登录成以后响应结果数据。

复制代码
package com.xuan.spzx.model.vo.system;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

@Data
@Schema(description = "登录成功响应结果实体类")
public class LoginVo {

    @Schema(description = "令牌")
    private String token ;

    @Schema(description = "刷新令牌,可以为空")
    private String refresh_token ;

}

(4)三层书写

IndexController

表现层代码实现(初始化->引入Service业务代码->用户登录)

复制代码
package com.atguigu.spzx.controller;

import com.atguigu.spzx.model.dto.system.LoginDto;
import com.atguigu.spzx.model.vo.common.Result;
import com.atguigu.spzx.model.vo.common.ResultCodeEnum;
import com.atguigu.spzx.model.vo.system.LoginVo;
import com.atguigu.spzx.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "用户接口")
@RestController
@RequestMapping(value = "/admin/system/index")
public class IndexController {

    @Autowired
    private SysUserService sysUserService ;

    @Operation(summary = "登录接口")
    @PostMapping(value = "/login")
    public Result<LoginVo> login(@RequestBody LoginDto loginDto) {
        LoginVo loginVo = sysUserService.login(loginDto) ;
        return Result.build(loginVo , ResultCodeEnum.SUCCESS) ;
    }

}
SysUserService

业务层代码实现(初始化->引入Mapper的代码->业务代码)

实现类

impl/SysUserServiceImpl它实现了SysUserService接口。该类被@Service注解标记,表明它是一个Spring框架管理的业务服务层组件,负责处理系统用户相关的业务逻辑

复制代码
package com.xuan.spzx.manager.service.impl;
import com.alibaba.fastjson.JSON;
import com.xuan.spzx.manager.mapper.SysUserMapper;
import com.xuan.spzx.manager.service.SysUserService;
import com.xuan.spzx.model.dto.system.LoginDto;
import com.xuan.spzx.model.entity.system.SysUser;
import com.xuan.spzx.model.vo.system.LoginVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class SysUserServiceImpl implements SysUserService {
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    //用户登录
    @Override
    public LoginVo login(LoginDto loginDto) {
        //1.获取提交用户名,LoginDto获取到
        String userName = loginDto.getUserName();
        //2.根据用户名查询数据库用户表sys_user
        SysUser sysUser =sysUserMapper.selectUserInfoByUserName(userName);
        //3.如果用户名查不到对应信息,用户不存在,返回错误信息
        if (sysUser == null){
            throw new RuntimeException("用户不存在");
        }
        //4.如果用户名存在,则判断输入密码与数据库密码是否一致
        //把输入密码进行md5加密
        String database_password = sysUser.getPassword();//数据库密码
        String input_password =
                DigestUtils.md5DigestAsHex(loginDto.getPassword().getBytes());
        //比较
        if(!input_password.equals(database_password)){
            throw new RuntimeException("密码错误");
        }
        //5.如果一致,则返回登录成功信息,如果不一致登录失败
        //登录成功生成用户唯一标识token
        String token = UUID.randomUUID().toString().replaceAll("-","");
        //6.把用户信息保存到redis中
        //key: token value:用户信息
        redisTemplate.opsForValue()
                .set("user:login"+token,
                        JSON.toJSONString(sysUser),
                        7,
                        TimeUnit.DAYS
                );
        //7.返回登录成功信息,loginVo对象返回
        LoginVo loginVo = new LoginVo();
        loginVo.setToken(token);

        return loginVo;
    }
}
SysUserMapper

@Mapper注解

  • 作用: 标识这是一个MyBatis的Mapper接口
  • 功能: MyBatis会自动为该接口生成实现类,用于执行数据库操作
  • 持久层代码实现

这个Mapper接口主要用于用户登录验证:

  • 当用户输入用户名和密码进行登录时
  • 系统通过该接口查询对应用户名的用户信息
  • 获取到用户数据后,验证密码是否正确
  • 完成用户身份认证
复制代码
@Mapper
public interface SysUserMapper {

    /**
     * 根据用户名查询用户数据
     * @param userName
     * @return
     */
    public abstract SysUser selectByUserName(String userName) ;

}
SysUserMapper.xml

目录和配置文件中的sql映射,位置有关系

创建映射文件并且编写sql语句: 文件位置classpath: /mapper/system/SysUserMapper.xml

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuan.spzx.manager.mapper.SysUserMapper">
    <sql id="columns">
        id,username userName ,password,name,phone,avatar,description,status,create_time,update_time,is_deleted
    </sql>
    <select id="selectUserInfoByUserName" resultType="com.xuan.spzx.model.entity.system.SysUser">
        SELECT <include refid="columns"/> FROM sys_user where userName = #{userName}
    </select>
</mapper>

(5)测试启动

启动:我启动时遇到两处报错

1.lombok的版本报错问题

java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCIm

Lombok在早期版本中使用反射访问com.sun.tools.javac.tree.JCTree$JCImport类的qualid字段,该字段在Java 21中的类型发生了变化,在Java 21及更高版本中,qualid字段的类型从JCTree变更为JCFieldAccess。这导致了Lombok无法正确访问该字段,从而抛出NoSuchFieldError异常。

在项目的依赖项中添加Lombok的最新版本

在修改项目父文件的pom.xml

<dependency>

<groupId>org.projectlombok</groupId>

<artifactId>lombok</artifactId>

<version>1.18.32</version>

</dependency>

2.yml文件缩进错误

复制代码
#spring:
#  application:
#    name: service-manager
#  profiles:
#    active:dev
# application.yml
spring:
  profiles:
    active: dev  # 这里指定默认环境

启动成功!

http://localhost:8501/doc.html#/home

添加中文提示,重启

点击测试

docker restart redis

重启

(6)异常处理

新建包

新建GlobalExceptionHandler类->全局异常

复制代码
package com.xuan.spzx.common.exception;

import com.xuan.spzx.model.vo.common.Result;
import com.xuan.spzx.model.vo.common.ResultCodeEnum;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {
    //全局异常处理
    @ExceptionHandler(Exception.class)//拦截所有异常,所有异常都用这个方法处理
    @ResponseBody
    public Result error(){
        return Result.build(null, ResultCodeEnum.SYSTEM_ERROR);
    }
}

新建GuiguException类->自定义

复制代码
package com.xuan.spzx.common.exception;

import com.xuan.spzx.model.vo.common.ResultCodeEnum;
import lombok.Data;

@Data

public class GuiguException extends RuntimeException {
    private Integer code;
    private String message;
    private ResultCodeEnum resultCodeEnum;
    public GuiguException(ResultCodeEnum resultCodeEnum) {
        this.code = resultCodeEnum.getCode();
        this.message = resultCodeEnum.getMessage();
        this.resultCodeEnum = resultCodeEnum;
    }

}

在第一个里面引入自定义异常

复制代码
    //自定义异常处理
    @ExceptionHandler(GuiguException.class)
    @ResponseBody
    public Result error(GuiguException e){
        return Result.build(null, e.getResultCodeEnum());
    }

package com.xuan.spzx.common.exception;

import com.xuan.spzx.model.vo.common.Result;
import com.xuan.spzx.model.vo.common.ResultCodeEnum;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalExceptionHandler {
    //全局异常处理
    @ExceptionHandler(Exception.class)//拦截所有异常,所有异常都用这个方法处理
    @ResponseBody
    public Result error(){
        return Result.build(null, ResultCodeEnum.SYSTEM_ERROR);
    }
    //自定义异常处理
    @ExceptionHandler(GuiguException.class)
    @ResponseBody
    public Result error(GuiguException e){
        return Result.build(null, e.getResultCodeEnum());
    }
}

修改登录使用异常

复制代码
  if (sysUser == null){
//            throw new RuntimeException("用户不存在");
            throw new GuiguException(ResultCodeEnum.LOGIN_ERROR);
        }
  if(!input_password.equals(database_password)){
//            throw new RuntimeException("密码错误");
            throw new GuiguException(ResultCodeEnum.LOGIN_ERROR);
        }

重启成功

前端接入登录

当后端接口开发好了以后就可以让前端去请求该登录接口完成登录操作。

修改前端代码

修改src/utils/request.js更改基础请求路径

复制代码
const service = axios.create({
  baseURL: 'http://localhost:8501',    // 后端服务的ip地址和端口号
  timeout: 10000,
  withCredentials: true,
})

修改src/api/login.js更改登录接口地址

复制代码
// 登录接口
export const Login = data => {
  return request({
    url: '/admin/system/index/login',
    method: 'post',
    data,
  })
}

发送登录请求,那么此时会报一个错误:

报错的原因是因为此时的请求是一个跨域的请求。

跨域请求

跨域请求简介

跨域请求:通过一个域的JavaScript脚本和另外一个域的内容进行交互

域的信息:协议、域名、端口号

同域:当两个域的协议、域名、端口号均相同

如下所示:

同源【域】策略:在浏览器中存在一种安全策略就是同源策略,同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。

CORS概述

官网地址:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

  • CORS的全称为Cross-origin Resource Sharing,中文含义是跨域资源共享
  • CORS 是跨域的一种解决方案,CORS 给了web服务器一种权限:服务器可以选择是否允许跨域请求访问到它们的资源。

CORS解决跨域

后端服务器开启跨域支持:

方案一:在IndexController上添加**@CrossOrigin**注解

复制代码
@RestController
@RequestMapping(value = "/admin/system/index")
@CrossOrigin(allowCredentials = "true" , originPatterns = "*" , allowedHeaders = "*") 
public class IndexController {

}

弊端:每一个controller类上都来添加这样的一个接口影响开发效率、维护性较差

方案二:添加一个配置类配置跨域请求(用这个,全局)

复制代码
// com.atguigu.spzx.manager.config
@Component
public class WebMvcConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")      // 添加路径规则
                .allowCredentials(true)               // 是否允许在跨域的情况下传递Cookie
                .allowedOriginPatterns("*")           // 允许请求来源的域规则
                .allowedMethods("*")
                .allowedHeaders("*") ;                // 允许所有的请求头
    }
    
}

重启后端->成功

相关推荐
葫芦和十三6 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp6 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑7 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯8 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan10 小时前
多Agent之间的区别
后端
青石路11 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充12 小时前
1.面向对象设计思想
后端
IT_陈寒12 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro13 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗13 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端