当前位置: 首页 > backend >正文

微服务项目->在线oj系统(Java版 - 2)

相信自己,终会成功

微服务代码:  lyyy-oj: 微服务

接口文档定义

响应数据定义:

响应数据格式:通常,HTTP API 的响应数据采用 JSON 格式

例如:成功响应(带数据)

{"code": 200,"message": "查询成功","data": {"id": 1,"name": "张三","age": 25}
}
名称内容
接口概述包括接口名称,接口功能,接口类别
接口地址接口的唯一访问地址
请求方法定义接口的请求方式,如GET(查询) POST(新增) PUT(修改) DELETE(删除)
请求参数定义请求时需要传递的参数 , 包括路径参数(Path Parameters),查询参数(Query Parameters),请求头(Headers),请求体(Body)等
响应数据定义接口返回的数据类型,包括状态码(Status Code),消息(Message),数据体(Data)等(包含接口请求出现错误时),返回的状态码和错误信息,不同接口格式统一,状态码含义相同
请求和相应示例为了更好的描述接口的使用,接口文档会提供一些具体的接口请求和响应示例,以供读者参考

状态码定义

RFC 9110: HTTP SemanticsHTTP 状态码的官方文档 :RFC 9110: HTTP Semantics

状态码分类

官方文档将状态码分为 5 类(以 RFC 9110 为准):

分类范围说明常见状态码
1xx100-199信息性响应(临时状态)100 Continue, 101 Switching Protocols
2xx200-299成功响应200 OK, 201 Created, 204 No Content
3xx300-399重定向响应301 Moved Permanently, 304 Not Modified
4xx400-499客户端错误400 Bad Request, 403 Forbidden, 404 Not Found
5xx500-599服务器错误500 Internal Server Error, 502 Bad Gateway
常见状态码详解(RFC 定义)
状态码官方描述适用场景
200 OK请求已成功完成。常规成功响应(如 GET 请求返回数据)。
301 Moved Permanently请求的资源已永久移动到新 URI。网站改版后的旧 URL 跳转。
400 Bad Request服务器无法理解请求的语法。客户端发送了无效参数或格式错误。
401 Unauthorized请求需要用户认证。未登录或 Token 过期。
403 Forbidden服务器理解请求,但拒绝执行。权限不足(如普通用户访问管理员接口)。
404 Not Found服务器找不到请求的资源。URL 路径错误或资源已删除。
500 Internal Server Error服务器遇到意外情况,无法完成请求。后端代码抛出未捕获的异常。

这些状态码不能完全支撑我们的业务,有时候我们需要更加详细的信息,另一方面处于安全考虑当服务器出错时我们不能直接暴露底层的系统错误就需要自定义状态码

创建 枚举(下放有对枚举的介绍) 类型命名为ResultCode来举例自定义状态码

Swagger 

wagger 是一套围绕 OpenAPI 规范构建的开源工具集,用于设计、构建、文档化和消费 RESTful API

<!-- Maven 依赖 -->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version>
</dependency>
<!-- springdoc-openapi(Swagger UI 的现代替代方案)-->
<dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.2.0</version>
</dependency>
注解作用示例
@Tag接口分组(替代 @Api@Tag(name = "用户管理")
@Operation接口描述(替代 @ApiOperation@Operation(summary = "创建用户")
@Parameter参数说明@Parameter(name = "id", required = true)
@Schema模型/字段说明(替代 @ApiModelProperty@Schema(description = "用户名"

JWT介绍 

JWT (JSON Web Token) 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为JSON对象。

一个JWT由三部分组成,用点(.)分隔:

  • Header (头部):包含令牌的类型和使用的算法

  • Payload (负载):包含用户信息和其他元数据

  • Signature (签名):用于验证令牌的完整性和真实性

库名称特点
jjwt简单易用,API友好,维护良好
java-jwtAuth0提供,功能全面
nimbus-jose-jwt功能最全,支持所有JWT/JWS/JWE规范,但API较复杂

身份认证流程:

  1. 客户端使用用户名跟密码请求登录。

  2. 服务端收到请求,去验证用户名与密码。

  3. 验证成功后,服务端会签发一个Token,再把这个Token发送给客户端。(token上述的jwt串)

  4. 客户端收到Token以后可以把它存储起来,比如放在Cookie里或者Local Storage里。

  5. 客户端每次向服务端请求资源的时候需要带着服务端签发的Token。

  6. 服务端收到请求,然后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据。

身份认证仅仅使用JWT机制就可以吗?

  1. JWT中payload存储用户相关信息,采用Base64编码,没有加密,因此JWT中不能存储敏感数据。但部分业务逻辑需要获取当前登录用户的敏感信息参与业务处理。

  2. JWT是无状态的,修改内容必须重新签发新Token。用户修改个人信息后需要重新登录。

  3. 无法延长JWT的过期时间。用户正在操作时可能突然身份认证失效。

所以使用redis+jwt的结构完成身份认证.jwt中进存储用户的唯一标识信息,使用redis作为第三方存储机制,存储用于用户身份认证的信息,并通过redis控制 jwt 的过期时间 

存储信息redis中数据结构keyvalue(JSON结构)缓存有效时间缓存刷新时机
登录用户信息string类型login_tokens:用户token- token(用户唯一标识)
- userId(用户名id)
- nickName(用户昵称)
- identity(用户身份)
720分钟(用户长时间不操作自动下线,防止盗用)1. 用户访问页面时若缓存即将失效则更新有效期
2. 用户重新登录时重新录入缓存
3. 通过拦截器在业务处理前刷新


管理端介绍 :

管理员登录:账号密码  不提供管理员注册  不对外开放新增管理员用户接口

下图是B端大致流程

全局异常处理

/*** 全局异常处理器*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {/*** 请求方式不支持*/@ExceptionHandler(HttpRequestMethodNotSupportedException.class)public R<?> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,HttpServletRequest request) {String requestURI = request.getRequestURI();log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());return R.fail(ResultCode.ERROR);}@ExceptionHandler(ServiceException.class)public R<?> handleServiceException(ServiceException e, HttpServletRequest request) {String requestURI = request.getRequestURI();ResultCode resultCode = e.getResultCode();log.error("请求地址'{}',发生业务异常: {}", requestURI, resultCode.getMsg(), e);return R.fail(resultCode);}@ExceptionHandler(BindException.class)public R<Void> handleBindException(BindException e) {log.error(e.getMessage());String message = join(e.getAllErrors(),DefaultMessageSourceResolvable::getDefaultMessage, ", ");return R.fail(ResultCode.FAILED_PARAMS_VALIDATE.getCode(), message);}private <E> String join(Collection<E> collection, Function<E, String>function, CharSequence delimiter) {if (CollUtil.isEmpty(collection)) {return StrUtil.EMPTY;}return collection.stream().map(function).filter(Objects::nonNull).collect(Collectors.joining(delimiter));}/*** 拦截运行时异常*/@ExceptionHandler(RuntimeException.class)public R<?> handleRuntimeException(RuntimeException e, HttpServletRequest request) {String requestURI = request.getRequestURI();log.error("请求地址'{}',发生运行时异常.", requestURI, e);return R.fail(ResultCode.ERROR);}/*** 系统异常*/@ExceptionHandler(Exception.class)public R<?> handleException(Exception e, HttpServletRequest request) {String requestURI = request.getRequestURI();log.error("请求地址'{}',发生异常.", requestURI, e);return R.fail(ResultCode.ERROR);}
}

@RestControllerAdvice:

抛出异常时,@RestControllerAdvice标注的类将被自动调用,并根据异常类型和处理程序的注解来决定如何处理该异常,这使得开发者可以在整个应用程序范围内统一处理异常

@ExceptionHandler:

@ExceptionHandler一般与 @RestControllerAdvice配合使用,使用其来捕获和处理不同类型的异常

日志输出格式:

Chapter 6: Layouts

占位符说明示例输出
%d日期时间2023-08-15 14:30:45.123
%thread线程名main
%level日志级别INFO
%loggerLogger名称com.example.MyClass
%msg日志消息User login successfully
%n换行符-
%c类名缩写MyClass
%M方法名doSomething
%L行号42
%XMDC变量{key:value}

 日期输出格式

%d{yyyy-MM-dd HH:mm:ss.SSS}
%d{ISO8601}
%d{UNIX}

Entity、DTO、VO  

类型英文全称作用域核心职责生命周期
EntityDomain Entity数据持久层与数据库表结构映射,承载业务实体从数据库查询到内存操作
DTOData Transfer Object服务层-表现层跨进程/服务数据传输,优化网络效率远程调用过程
VOValue Object/View Object表现层前端展示数据定制,适配界面需求请求响应周期

 

  1. 问题:Entity直接作为API返回值

    • 风险:暴露敏感字段、产生循环引用

    • 解决:严格通过DTO转换

  2. 问题:DTO与VO混用

    • 现象:前端需求变更导致服务层频繁修改

    • 解决:VO应独立演化,通过Adapter模式转换DTO

  3. 问题:过度转换造成性能损耗

    • 优化

      • 使用MapStruct编译期生成转换代码

      • 对只读场景采用投影查询(如JPA的Interface Projection)


枚举 

枚举(Enum)是Java 5引入的一种特殊数据类型,它允许开发者定义一组命名的常量,使代码更加清晰、安全且易于维护

举例:


public enum UserStatus {Normal(1),Black(0);}

特点

  • 类型安全:编译时检查

  • 不可实例化:枚举构造器默认private

  • 不可继承:所有枚举都隐式继承java.lang.Enum

  • 线程安全:枚举实例在类加载时创建

枚举进阶特性 


@Getter
public enum UserStatus {Normal(1),Black(0);private Integer value;UserStatus(Integer value) {this.value = value;}
}//使用方法 //user.setStatus(UserStatus.Normal.getValue());

枚举最佳实践

  1. 优先使用枚举替代常量

    • 替代public static final int常量

    • 替代字符串常量

  2. 考虑性能影响

    • values()方法每次返回新数组

    • 可缓存values()结果

  3. 合理设计枚举方法

    • 避免过于复杂的业务逻辑

    • 保持单一职责

  4. 序列化考虑

    • 默认序列化机制安全

    • 自定义属性需要特殊处理

枚举限制

  1. 不能继承其他类

  2. 不能显式声明为final

  3. 不能创建枚举实例(new操作)

  4. 不能扩展枚举常量(每个常量都是final)


 final(由枚举延申的知识点)

final 是 Java 中的一个重要关键字,可以用来修饰类、方法和变量,具有不同的语义和作用

1.final 修饰变量

基本特点

  • 不可变性:一旦赋值,值(基本类型)或引用(对象类型)不可更改

  • 必须初始化:必须在声明时、构造方法中或静态代码块(static final)中初始化。

变量类型特点示例
局部变量方法内使用,初始化后不可修改final int x = 10;
成员变量必须在声明时或构造方法中初始化final String name = "Java";
静态变量必须在声明时或静态代码块中初始化,通常作为常量static final double PI = 3.14;

引用类型变量final 仅保证引用不变,但对象内部属性仍可修改(除非对象本身不可变,如 String)。

2. final 修饰方法

核心特点

  • 不可重写:子类不能重写 final 方法(防止继承破坏父类逻辑)。

  • 早期绑定:编译时确定调用目标,可能提高性能(JVM 可能内联优化)

  适用场景

  • 关键方法:如核心算法、安全性相关方法。

  • 模板方法模式:防止子类修改流程骨架。

3. final 修饰类

核心特点

  • 不可继承final 类不能被其他类继承(如 StringInteger)。

  • 隐式 final 方法:类中所有方法自动成为 final 方法(不可重写)

适用场景

  • 不可变类:如 String、基本类型包装类(Integer 等)。

  • 安全性要求高的类:防止恶意子类化破坏行为。

 总结

  • 变量:值/引用不可变,必须初始化。

  • 方法:不可重写,可能优化性能。

  • :不可继承,适合不可变设计。

  • 线程安全final 字段天然线程安全。

  • 平衡使用:在需要限制修改或保证安全时使用,但避免过度。

LambdaQueryWrapper 详解 (Mybatis-Plus)

LambdaQueryWrapper 是 MyBatis-Plus 提供的一个强大的查询条件构造器,它通过 Lambda 表达式的方式引用实体属性,避免了字段名的硬编码,提高了代码的安全性和可维护性。

只查询必要字段

基本特点
  1. 类型安全:使用 Lambda 表达式引用实体属性,避免字段名拼写错误

  2. 链式调用:支持流畅的链式编程风格

  3. 防止SQL注入:自动处理参数化查询

 常用方法对照表

方法名

说明

示例

eq

等于

wrapper.eq(User::getName, "张三")

ne

不等于

wrapper.ne(User::getAge, 18)

gt

大于

wrapper.gt(User::getAge, 18)

ge

大于等于

wrapper.ge(User::getAge, 18)

lt

小于

wrapper.lt(User::getAge, 30)

le

小于等于

wrapper.le(User::getAge, 30)

like

模糊查询

wrapper.like(User::getName, "张")

in

IN 查询

wrapper.in(User::getId, Arrays.asList(1, 2, 3))

orderByAsc

升序排序

wrapper.orderByAsc(User::getAge)

orderByDesc

降序排序

wrapper.orderByDesc(User::getCreateTime)

select

指定查询字段

wrapper.select(User::getId, User::getName)

重写和重载 

特性方法重写 (Override)方法重载 (Overload)
定义子类重新定义父类中已有的方法同一个类中定义多个同名但参数不同的方法
英文名OverridingOverloading
目的实现多态,改变父类方法的行为提供处理不同类型数据的多种方
场景选择
需要改变继承方法的行为使用重写
需要以不同方式处理不同类型/数量的参数使用重载
实现多态特性必须使用重写
提供多种构造对象的方式使用构造方法重载
  1. 能否根据返回类型区分重载方法?

    不能,仅返回类型不同会导致编译错误
  2. 重写方法是否可以修改参数列表?

    不可以,修改参数列表会变成重载而非重写
  3. 为什么重写不能抛出更宽泛的异常?

    子类方法不应破坏父类方法的约定
  4. 重载方法在继承体系中如何工作?

    子类会继承父类的所有重载版本,并可以添加新的重载

为什么要封装service? 

第三方组件封装的核心价值

  1. 抽象与解耦 : 提供高级抽象层,隔离具体实现 , 更换组件时只需修改封装层,避免全局代码改动

  2. 统一接口 : 标准化不同第三方工具的API差异 , 开发者无需关注底层工具实现细节

  3. 扩展性增强 : 便于添加项目特定功能 , 灵活扩展第三方工具原生能力

  4. 异常管理 : 统一转换第三方工具原生异常 , 提供业务语义明确的错误信息

  5. 可维护性提升 : 通过语义化接口提高代码可读性 , 完善的封装层文档降低新人学习成本

http://www.xdnf.cn/news/7054.html

相关文章:

  • c++编写中遇见的错误
  • 【AWS入门】Amazon SageMaker简介
  • 4:OpenCV—保存图像
  • 解决 Tailwind CSS 代码冗余问题
  • 机器学习(12)——LGBM(1)
  • Python爬虫基础
  • 选择合适的AI模型:解析Trae编辑器中的多款模型及其应用场景
  • Go 语言中的一等公民(First-Class Citizens)
  • Flutter与Kotlin Multiplatform(KMP)深度对比及鸿蒙生态适配解析
  • STM32单片机开发环境搭建 keil/proteus仿真/STM32CubeMX
  • 【OpenGL学习】(三)元素缓冲对象(EBO)的使用
  • Limesurvay系统“48核心92GB服务器”优化方案
  • uniapp的适配方式
  • PDF批量合并拆分+加水印转换 编辑 加密 OCR 识别
  • 软件架构之-论软件系统架构评估以及应用
  • Zookeeper入门(三)
  • 《Vite 报错》ReferenceError: module is not defined in ES module scope
  • 影刀处理 Excel:智能工具带来的高效变革
  • 广域网学习
  • 数据结构与算法——栈和队列
  • Python字符串格式化(一):三种经典格式化方法
  • 从零开始实现大语言模型(十六):加载开源大语言模型参数
  • 《Python星球日记》 第87天:什么是大语言模型 LLM?
  • 1_Spring 【IOC容器的创建】
  • 深入了解linux系统—— 基础IO(下)
  • 【QGIS二次开发】地图编辑-08
  • tauri2项目使用sidcar嵌入可执行文件并使用命令行调用
  • 实战设计模式之状态模式
  • 互联网大厂Java面试场景:从Spring Boot到分布式缓存技术的探讨
  • 十一、STM32入门学习之FREERTOS移植