相关版本springBoot => 3.3.0
sa-token => 1.38.0
knife4j => 4.5.0
mybatis-plus => 3.5.6
lombok => 1.18.32
mapstruct => 1.6.0.Beta2
mysql => 8.0.34
Mysql数据库设计
数据库设计在我上一篇博客:juejin.cn/post/737498…
SpringBoot程序
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- 排除spring-boot-starter-web内自带的日志插件 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 使用 SpringBoot Validation 注解对参数进行校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<!--<version>3.3.0</version>-->
</dependency>
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.38.0</version>
</dependency>
<!-- Knife4j是基于springboot构建的一个文档生成工具,它可以让开发者为我们的应用生成API文档
=>
访问地址:项目根地址/doc.html => https://doc.xiaominfo.com/docs/quick-start -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.6</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>provided</scope>
</dependency>
<!-- mysql连接 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.4.0</version>
</dependency>
<!-- devtools 热启动 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.50</version>
</dependency>
<!-- log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
<version>3.3.0</version>
</dependency>
<!--mapstruct核心-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.6.0.Beta2</version>
</dependency>
<!--mapstruct编译-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.6.0.Beta2</version>
</dependency>
</dependencies>
Knife4j配置文件
package com.ayo.config;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Knife4jConfig {
@Bean
public OpenAPI customOpenAPI() {
//创建一个 OpenAPI 对象,用于表示整个 API 的文档信息
return new OpenAPI()
// 接口文档标题
.info(new Info().title("API接口文档")
// 接口文档简介
.description("我的第一个Knife4j")
// 接口文档版本
.version("0.0.1-SNAPSHOT")
// 开发者的联系方式,包括姓名和电子邮件地址
.contact(new Contact().name("Ayo").email("2192475085@qq.com")));
}
}
配置yaml文件
server:
# 端口
port: 8081
############## mysql配置 ##############
spring:
datasource:
# 链接数据库
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/sa_token?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
security:
user:
name: admin
password: 123456
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: true
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: false
############## mybatisPlus配置 ##############
# mybatisPlus配置
mybatis-plus:
# mapper映射地址
mapper-locations: classpath:mapper/*.xml
# 实体类扫描包路径
type-aliases-package: com.ayo.entity
# configuration:
# # sql打印
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# # 开启驼峰命名
# map-underscore-to-camel-case: true
# global-config:
# db-config:
# # 数据库表前缀
# table-prefix: m_
############## 自定义knife4j配置 ##############
# springdoc-openapi项目访问访问地址: http://127.0.0.1:8080/doc.html
springdoc:
swagger-ui:
path: /swagger-ui.html
# path: 配置swagger-ui.html/UI界面的访问路径,默认为/swagger-ui.html
tags-sorter: alpha
# tags-sorter: 接口文档中的tags排序规则,默认为alpha,可选值为alpha(按字母顺序排序)或as-is(按照在代码中定义的顺序排序)
operations-sorter: alpha
api-docs:
path: /v3/api-docs
# path: 配置api-docs的访问路径,默认为/v3/api-docs
group-configs:
# group-configs: 配置分组信息
- group: 'default'
# group: 分组名称
paths-to-match: '/**'
# paths-to-match: 配置要匹配的路径,默认为/**
packages-to-scan: com.ayo.controller
# packages-to-scan: 配置要扫描的包的路径,直接配置为Controller类所在的包名即可
# knife4j项目访问访问地址:http://127.0.0.1:8080/doc.html#/home
knife4j:
enable: true
# 设置为true以启用Knife4j增强功能,这将再应用程序中启用Knife4j UI
setting:
# language: 设置Knife4j UI的语言,默认为zh_cn,可选值为zh_cn或en
language: zh_cn
#开启生产环境屏蔽
production: false
#是否启用登录认证
basic:
enable: true
username: root # 自己设置一个
password: 123456 # 自己设置一个
注意
knife4j配置下的username和password;后续使用knife4j接口调试地址时访问doc.html地址需要输入这里的用户名和密码才运行访问
状态码枚举类
package com.ayo.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 状态码枚举
*
* @author ayo
*/
public enum StatusCodeEnum {
/**
* 操作成功
*/
SUCCESS(200, "操作成功"),
/**
* 参数错误
*/
VALID_ERROR(400, "参数错误"),
/**
* 未登录
*/
UNAUTHORIZED(402, "未登录"),
/**
* 系统异常
*/
SYSTEM_ERROR(-1, "系统异常"),
/**
* 操作失败
*/
FAIL(500, "操作失败");
/**
* 状态码
*/
private final Integer code;
/**
* 返回信息
*/
private final String msg;
public Integer getCode() {
return code;
}
public String getMsg() {
return msg;
}
StatusCodeEnum(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
sa-token全局过滤器
package com.ayo.saToken;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.alibaba.fastjson2.JSON;
import com.ayo.model.vo.Result;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static com.ayo.enums.StatusCodeEnum.UNAUTHORIZED;
/**
* [Sa-Token 权限认证] 配置类 => https://sa-token.cc/doc.html#/up/global-filter
*/
@Configuration
public class SaTokenConfigure {
// 放行路径
private final String[] EXCLUDE_PATH_PATTERNS = {
"/swagger-resources",
"/webjars/**",
"/v2/api-docs",
"/doc.html",
"/favicon.ico",
"/login",
"/oauth/*",
};
private final long timeout = 600;
/**
* 注册 [Sa-Token全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 拦截路径
.addInclude("/**")
// 放行路由
.addExclude(EXCLUDE_PATH_PATTERNS)
// 前置函数:在每次认证函数之前执行(BeforeAuth 不受 includeList 与 excludeList 的限制,所有请求都会进入)
.setBeforeAuth(r -> {
// ---------- 设置一些安全响应头 ----------
SaHolder.getResponse()
// 服务器名称
.setServer("sa-server")
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
.setHeader("X-Frame-Options", "SAMEORIGIN")
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
.setHeader("X-XSS-Protection", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff")
/* ---------------------------------------------------------- */
// 允许指定域访问跨域资源
.setHeader("Access-Control-Allow-Origin", "*")
// 允许所有请求方式
.setHeader("Access-Control-Allow-Methods", "*")
// 有效时间
.setHeader("Access-Control-Max-Age", "3600")
// 允许的header参数
.setHeader("Access-Control-Allow-Headers", "*");
;
})
// 认证函数: 每次请求执行
.setAuth(obj -> {
System.out.println("请求拦截 => ");
// 检查是否登录
SaRouter.match("/admin/**").check(r -> StpUtil.checkLogin());
// 刷新token有效期
if (StpUtil.getTokenTimeout() < timeout) {
StpUtil.renewTimeout(1800);
}
// 输出 API 请求日志,方便调试代码
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
// 设置响应头
SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");
if (e instanceof NotLoginException) {
return JSON.toJSONString(Result.fail(UNAUTHORIZED.getCode(), UNAUTHORIZED.getMsg()));
}
// TODO 服务器后端在这里无法捕获异常,仅仅将异常信息传给了前端
e.printStackTrace();
return SaResult.error(e.getMessage());
});
}
}
统一接口返回数据格式(全局vo)
package com.ayo.model.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import static com.ayo.enums.StatusCodeEnum.FAIL;
import static com.ayo.enums.StatusCodeEnum.SUCCESS;
/**
* 结果返回类
*
* @author ayo
*/
@Data
@Schema(description = "结果返回类")
public class Result<T> {
/**
* 返回状态
*/
@Schema(description = "返回状态")
private Boolean flag;
/**
* 状态码
*/
@Schema(description = "状态码")
private Integer code;
/**
* 返回信息
*/
@Schema(description = "返回信息")
private String msg;
/**
* 返回数据
*/
@Schema(description = "返回数据")
private T data;
/**
* 无参静态方法,用于创建一个表示成功结果的 Result 对象。
* 返回一个 Result 对象,其中 flag 被设置为 true,code 被设置为成功状态码,msg 被设置为成功信息,而 data 为 null
* */
public static <T> Result<T> success() {
return buildResult(true, null, SUCCESS.getCode(), SUCCESS.getMsg());
}
/**
* 接受泛型参数 data,用于创建一个包含成功数据的 Result 对象。
* 返回一个 Result 对象,其中 flag 被设置为 true,code 被设置为成功状态码,msg 被设置为成功信息,而 data 被设置为传入的数据。
* */
public static <T> Result<T> success(T data) {
return buildResult(true, data, SUCCESS.getCode(), SUCCESS.getMsg());
}
/**
* 接受一个字符串参数 message,用于创建一个表示失败结果的 Result 对象。
* 返回一个 Result 对象,其中 flag 被设置为 false,code 被设置为失败状态码,msg 被设置为传入的消息,而 data 为 null。
* */
public static <T> Result<T> fail(String message) {
return buildResult(false, null, FAIL.getCode(), message);
}
/**
* 接受一个整数参数 code 和一个字符串参数 message,用于创建一个表示失败结果的 Result 对象。
* 返回一个 Result 对象,其中 flag 被设置为 false,code 被设置为传入的状态码,msg 被设置为传入的消息,而 data 为 null。
* */
public static <T> Result<T> fail(Integer code, String message) {
return buildResult(false, null, code, message);
}
/**
* 私有静态方法,用于构建一个 Result 对象。
* 接受 flag、data、code 和 message 四个参数,并返回一个根据这些参数构建的 Result 对象。
* */
private static <T> Result<T> buildResult(Boolean flag, T data, Integer code, String message) {
Result<T> r = new Result<>();
r.setFlag(flag);
r.setData(data);
r.setCode(code);
r.setMsg(message);
return r;
}
}
封装 全局异常处理
package com.ayo.handler;
import cn.dev33.satoken.exception.DisableServiceException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import com.ayo.model.vo.Result;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Objects;
import static com.ayo.enums.StatusCodeEnum.*;
/**
* 全局异常处理
*
* @author ayo
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理SpringBoot Validation 参数验证抛出的异常
* */
//处理MethodArgumentNotValidException,该异常会在请求参数验证失败时抛出
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result<?> handleValidationException(MethodArgumentNotValidException e) {
return Result.fail(HttpStatus.BAD_REQUEST.value(),e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
}
//处理ConstraintViolationException,该异常会在方法级别的参数验证失败时抛出
@ExceptionHandler(ConstraintViolationException.class)
public Result<?> handleConstraintViolationException(ConstraintViolationException e) {
return Result.fail(HttpStatus.BAD_REQUEST.value(),e.getConstraintViolations().iterator().next().getMessage());
}
/**
* 处理Assert异常
*/
@ExceptionHandler(AssertionError.class)
public Result<?> handleAssertionError(AssertionError e) {
return Result.fail(e.getMessage());
}
/**
* 处理权限不足
*/
@ExceptionHandler(value = NotPermissionException.class)
public Result<?> handleNotPermissionException() {
return Result.fail("权限不足");
}
/**
* 处理账号封禁
*/
@ExceptionHandler(value = DisableServiceException.class)
public Result<?> handleDisableServiceExceptionException() {
return Result.fail("此账号已被禁止访问服务");
}
/**
* 处理无此角色异常
*/
@ExceptionHandler(value = NotRoleException.class)
public Result<?> handleNotRoleException() {
return Result.fail("权限不足");
}
/**
* 处理SaToken异常
*/
@ExceptionHandler(value = NotLoginException.class)
public Result<?> handlerNotLoginException(NotLoginException nle) {
// 判断场景值,定制化异常信息
String message;
if (nle.getType().equals(NotLoginException.NOT_TOKEN)) {
message = "未提供token";
} else if (nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
message = "token无效";
} else if (nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
message = "token已过期";
} else {
message = "当前会话未登录";
}
// 返回给前端
return Result.fail(UNAUTHORIZED.getCode(), message);
}
/**
* 处理系统异常
*/
@ExceptionHandler(value = Exception.class)
public Result<?> handleSystemException() {
return Result.fail(SYSTEM_ERROR.getCode(), SYSTEM_ERROR.getMsg());
}
}
定义user的modul对象(vo)
登录请求数据对象
package com.ayo.model.vo.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 登录请求传入的用户数据
*/
@Data
@Schema(description = "用户登录对象")
public class LoginReq {
@NotBlank(message = "用户名不能为空")
//@Schema(description = "用户名",required = true) // required = true弃用了
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码不能少于6位")
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED)
private String password;
}
登录成功响应个前端的数据对象
package com.ayo.model.vo.response;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.sql.Timestamp;
/**
* 登录成功后相应的数据对象
* */
@Data
@Schema(description = "用户登录成功后的数据对象")
public class LoginRes {
@Schema(description = "用户id")
private String id;
@Schema(description = "用户名")
private String username;
@Schema(description = "token")
private String token;
@Schema(description = "用户创建时间")
@TableField("`create`")
private Timestamp create;
@Schema(description = "用户更新时间")
@TableField("`update`")
private Timestamp update;
}
entity层
package com.ayo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.sql.Timestamp;
/**
* 用户表
* */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "用户对象")
@TableName("sa_user")
public class User {
@Schema(description = "用户id")
@TableId("id")
private String id;
@Schema(description = "用户名")
@TableField("username")
private String username;
@Hidden
@Schema(description = "密码")
@TableField("password")
private String password;
@Schema(description = "用户是否启用")
@TableField("enable")
private boolean enable;
@Schema(description = "用户是否删除")
@TableField("is_delete")
private boolean isDelete;
@Schema(description = "用户创建时间")
@TableField("`create`")
private Timestamp create;
@Schema(description = "用户更新时间")
@TableField("`update`")
private Timestamp update;
}
用户实体类 各种数据类型间的转换(vo <--> entity)
由于登录涉及到
vo和实体类的数据之间的转换,这里使用了mapstruct即登录的时候使用
LoginReq登录成功后使用
LoginRes具体使用教程参考:blog.csdn.net/zhouzhiweng…
package com.ayo.mapstruct;
import com.ayo.entity.User;
import com.ayo.model.vo.response.LoginRes;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* mapstruct 工具类定义步骤:
* 1、添加MapStruct jar包依赖
* 2、新增接口或抽象类,并且使用org.mapstruct.Mapper注解标签修饰。
* 3、添加自定义转换方法
*/
/**
* 用户实体类 各种数据转换
*/
//@Mapper //org.mapstruct.Mapper
@Mapper(componentModel = "spring") //org.mapstruct.Mapper
public interface UserConvert {
UserConvert INSTANCE = Mappers.getMapper( UserConvert.class );
/* 实体类 转 登录响应结果数据对象 */
LoginRes toLoginRes(User user);
/* 登录响应结果数据对象 转 实体类 */
User toUserEntity(LoginRes loginRes);
}
@Mapper为org.mapstruct.Mapper类下的,配置componentModel = "spring",让spring对其进行托管
Dao层
继承 Mybatis-plus 类
package com.ayo.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}
service层
接口
package com.ayo.service;
import com.ayo.entity.User;
import com.ayo.model.vo.request.LoginReq;
import com.ayo.model.vo.response.LoginRes;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {
/* 用户登录请求 */
LoginRes login(LoginReq login);
}
接口实现类
package com.ayo.service.Impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import com.ayo.dao.UserMapper;
import com.ayo.entity.User;
import com.ayo.mapstruct.UserConvert;
import com.ayo.model.vo.request.LoginReq;
import com.ayo.model.vo.response.LoginRes;
import com.ayo.service.UserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserConvert userConvert;
/* 用户登录请求 */
@Override
public LoginRes login(LoginReq login) {
User user = userMapper.selectOne(
new LambdaQueryWrapper<User>()
.select(User::getId,User::getUsername,User::getPassword,User::getCreate,User::getUpdate)
.eq(User::getUsername, login.getUsername())
//.eq(User::getPassword, BCrypt.checkpw(login.getPassword(User::getPassword)))
);
/**
* assert(条件语句),如果条件语句为真,继续往下执行,如果为假就会报错
* */
Assert.notNull(user, "用户名不存在");
Assert.isTrue(BCrypt.checkpw(login.getPassword(), user.getPassword()),"密码错误");
//if(!BCrypt.checkpw(login.getPassword(),user.getPassword())){
// return "密码错误";
//}
// 校验指定账号是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`
StpUtil.checkDisable(user.getId());
// 通过校验后,再进行登录
StpUtil.login(user.getId());
// entity数据转为vo响应的数据
LoginRes loginRes = userConvert.toLoginRes(user);
loginRes.setToken(StpUtil.getTokenValue());
return loginRes;
}
}
controller层
package com.ayo.controller;
import com.ayo.model.vo.Result;
import com.ayo.model.vo.request.LoginReq;
import com.ayo.service.UserService;
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.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@Tag(name = "用户登录", description = "用户登录相关")
@RestController
public class LoginController {
@Autowired
UserService userService;
@Operation(summary = "用户登录")
@PostMapping("/login")
public Result login(@Validated @RequestBody LoginReq login) {
return Result.success(userService.login(login));
}
}
测试
1.登录接口调试地址
2.登录验证
源代码地址:gitee.com/CnAyo/code-…