登錄功能的書寫
后端接口的書寫
(1)創建配置文件
粘貼這兩個文件(E:\project\AllProJect\Shangpin Selection\項目材料素材\資料\資料\03-配置文件)
在spzx-manager服務的src/resources目錄下創建application.yml、application-dev.yml文件,文件的內容如下所示:
# application.yml文件內容==================================================================================
spring:application:name: service-managerprofiles:active: dev # 激活的環境文件# application-dev.yml文件內容=============================================================================
# 配置服務端口號
server:port: 8501# 配置數據庫連接信息
spring:datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/db_spzx?characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: root# Redis的相關配置data:redis:host: localhostport: 6379
# password: 1234# mybatis的配置
mybatis:config-location: classpath:/mybatis-config.xmlmapper-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 {@Autowiredprivate 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 {@Autowiredprivate SysUserMapper sysUserMapper;@Autowiredprivate RedisTemplate<String,String> redisTemplate;//用戶登錄@Overridepublic LoginVo login(LoginDto loginDto) {//1.獲取提交用戶名,LoginDto獲取到String userName = loginDto.getUserName();//2.根據用戶名查詢數據庫用戶表sys_userSysUser 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.如果一致,則返回登錄成功信息,如果不一致登錄失敗//登錄成功生成用戶唯一標識tokenString 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)//攔截所有異常,所有異常都用這個方法處理@ResponseBodypublic 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;@Datapublic 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)@ResponseBodypublic 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)//攔截所有異常,所有異常都用這個方法處理@ResponseBodypublic Result error(){return Result.build(null, ResultCodeEnum.SYSTEM_ERROR);}//自定義異常處理@ExceptionHandler(GuiguException.class)@ResponseBodypublic 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 {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // 添加路徑規則.allowCredentials(true) // 是否允許在跨域的情況下傳遞Cookie.allowedOriginPatterns("*") // 允許請求來源的域規則.allowedMethods("*").allowedHeaders("*") ; // 允許所有的請求頭}}
重啟后端->成功