SpringBoot 统⼀功能处理

目录

[1. 拦截器](#1. 拦截器)

[1.1 拦截器快速入门](#1.1 拦截器快速入门)

[1.2 拦截器详解](#1.2 拦截器详解)

[1.2.1 拦截路径](#1.2.1 拦截路径)

[1.2.2 拦截器执行流程](#1.2.2 拦截器执行流程)

[1.3 登录校验](#1.3 登录校验)

[1.3.1 定义拦截器](#1.3.1 定义拦截器)

[1.3.2 注册配置拦截器](#1.3.2 注册配置拦截器)

[2. 统⼀数据返回格式](#2. 统⼀数据返回格式)

[2.1 快速⼊⻔](#2.1 快速⼊⻔)

[2.2 存在问题](#2.2 存在问题)

[2.3 案例代码修改](#2.3 案例代码修改)

[2.4 优点](#2.4 优点)

[3. 统⼀异常处理](#3. 统⼀异常处理)


1. 拦截器

上个章节我们完成了强制登录的功能, 后端程序根据Session来判断⽤⼾是否登录, 但是实现⽅法是⽐较⿇烦的
• 需要修改每个接⼝的处理逻辑
• 需要修改每个接⼝的返回结果
• 接⼝定义修改, 前端代码也需要跟着修改
有没有更简单的办法, 统⼀拦截所有的请求, 并进⾏Session校验呢, 这⾥我们学习⼀种新的解决办法: 拦截器

1.1 拦截器快速入门

什么是拦截器?
拦截器是Spring框架提供的核⼼功能之⼀, 主要⽤来拦截⽤⼾的请求, 在指定⽅法前后, 根据业务需要执⾏预先设定的代码.
也就是说, 允许开发⼈员提前预定义⼀些逻辑, 在⽤⼾的请求响应前后执⾏. 也可以在⽤⼾请求前阻⽌其执⾏.
在拦截器当中,开发⼈员可以在应⽤程序中做⼀些通⽤性的操作, ⽐如通过拦截器来拦截前端发来的请求, 判断Session中是否有登录⽤⼾的信息. 如果有就可以放⾏, 如果没有就进⾏拦截.


⽐如我们去银⾏办理业务, 在办理业务前后, 就可以加⼀些拦截操作
办理业务之前, 先取号, 如果带⾝份证了就取号成功
业务办理结束, 给业务办理⼈员的服务进⾏评价.
这些就是"拦截器"做的⼯作
下⾯我们先来学习下拦截器的基本使⽤.
拦截器的使⽤步骤分为两步:

  1. 定义拦截器
  2. 注册配置拦截器
    ⾃定义拦截器:实现HandlerInterceptor接⼝,并重写其所有⽅法
java 复制代码
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginInterceptor 目标方法执行前执行..");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("LoginInterceptor 目标方法执行后执行");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("LoginInterceptor 视图渲染完毕后执行,最后执行");
    }
}

• preHandle()方法:目标方法执行前执行. 返回true: 继续执行后续操作; 返回false: 中断后续操作.
• postHandle()方法:目标方法执行后执行
• afterCompletion()方法:视图渲染完毕后执行,最后执行(后端开发现在几乎不涉及视图, 暂不了
解)
**注册配置拦截器:**实现WebMvcConfigurer接⼝,并重写addInterceptors⽅法

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
 //⾃定义的拦截器对象
 @Autowired
 private LoginInterceptor loginInterceptor;
 @Override
 public void addInterceptors(InterceptorRegistry registry) {
 //注册⾃定义拦截器对象
 registry.addInterceptor(loginInterceptor)
 .addPathPatterns("/**");//设置拦截器拦截的请求路径( /** 表⽰拦截所
有请求)
 }
}

启动服务, 试试访问任意请求
preHandle ⽅法执⾏之后就放⾏了, 开始执⾏⽬标⽅法, ⽬标⽅法执⾏完成之后执⾏
postHandle和afterCompletion⽅法.
我们把拦截器中preHandle⽅法的返回值改为false,拦截器拦截了请求, 没有进⾏响应

1.2 拦截器详解

拦截器的⼊⻔程序完成之后,接下来我们来介绍拦截器的使⽤细节。拦截器的使⽤细节我们主要介绍两个部分:

  1. 拦截器的拦截路径配置
  2. 拦截器实现原理

1.2.1 拦截路径

拦截路径是指我们定义的这个拦截器, 对哪些请求⽣效.
我们在注册配置拦截器的时候, 通过 addPathPatterns() ⽅法指定要拦截哪些请求. 也可以通过
excludePathPatterns() 指定不拦截哪些请求.
上述代码中, 我们配置的是 /** , 表⽰拦截所有的请求.
⽐如⽤⼾登录校验, 我们希望可以对除了登录之外所有的路径⽣效

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    //自定义的拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/login");//设置拦截器拦截的请求路径(/** 表示拦截所有请求)
    }
}

在拦截器中除了可以设置 /** 拦截所有资源外,还有⼀些常⻅拦截路径设置:

|------------|--------------|--------------------------------------------------------|
| 拦截路径 | 含义 | 举例 |
| /* | ⼀级路径 | 能匹配/user,/book,/login,不能匹配 /user/login |
| /** | 任意级路径 | 能匹配/user,/user/login,/user/reg |
| /book/* | /book下的⼀级路径 | 能匹配/book/addBook,不能匹配/book/addBook/1,/book |
| /book/** | /book下的任意级路径 | 能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login |

以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件, JS 和 CSS 等⽂件).

1.2.2 拦截器执行流程

正常的调用顺序:

有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图

  1. 添加拦截器后, 执⾏Controller的⽅法之前, 请求会先被拦截器拦截住. 执⾏ preHandle() ⽅法,
    这个⽅法需要返回⼀个布尔类型的值. 如果返回true, 就表⽰放⾏本次操作, 继续访问controller中的
    ⽅法. 如果返回false,则不会放⾏(controller中的⽅法也不会执⾏).
  2. controller当中的⽅法执⾏完毕后,再回过来执⾏ postHandle() 这个⽅法以及 afterCompletion() ⽅法,执⾏完毕之后,最终给浏览器响应数据.

1.3 登录校验

学习拦截器的基本操作之后,接下来我们需要完成最后⼀步操作:通过拦截器来完成图书管理系统中的登录校验功能

1.3.1 定义拦截器

从session中获取⽤⼾信息, 如果session中不存在, 则返回false,并设置http状态码为401, 否则返回true.

java 复制代码
import com.example.demo.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(Constants.SESSION_USER_KEY) != null) {
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

1.3.2 注册配置拦截器

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    //自定义的拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//设置拦截器拦截的请求路径(/**表示拦截所有请求)
                .excludePathPatterns("/user/login")//设置拦截器排除拦截的路径
                .excludePathPatterns("/**/*.js") //排除前端静态资源
                .excludePathPatterns("/**/*.css")
                .excludePathPatterns("/**/*.png")
                .excludePathPatterns("/**/*.html");
    }
}

也可以改成

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Arrays;
import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    //自定义的拦截器对象
    @Autowired
    private LoginInterceptor loginInterceptor;
    private List<String> excludePaths = Arrays.asList(
            "/user/login",
            "/**/*.js",
            "/**/*.css",
            "/**/*.png",
            "/**/*.html"
    );

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")//设置拦截器拦截的请求路径(/** 表示拦截所有请求)
                .excludePathPatterns(excludePaths);//设置拦截器排除拦截的路径
    }
}

删除之前的登录校验代码

java 复制代码
@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest, HttpSession session) {
    log.info("获取图书列表, pageRequest:{}", pageRequest);
    // //判断用户是否登录
    // if (session.getAttribute(Constants.SESSION_USER_KEY)==null){
    //     return Result.unlogin();
    // }
    // UserInfo userInfo = (UserInfo) session.getAttribute(Constants.SESSION_USER_KEY);
    // if (userInfo==null || userInfo.getId()<0 || "".equals(userInfo.getUserName())){
    //     return Result.unlogin();
    // }
    //用户登录, 返回图书列表
    PageResult<BookInfo> pageResult = bookService.getBookListByPage(pageRequest);
    log.info("获取图书列表222, pageRequest:{}", pageResult);
    return Result.success(pageResult);
}

2. 统⼀数据返回格式

强制登录案例中, 我们共做了两部分⼯作

  1. 通过Session来判断⽤⼾是否登录
  2. 对后端返回数据进⾏封装, 告知前端处理的结果
    回顾
    后端统⼀返回结果
java 复制代码
@Data
public class Result<T> {
    private int status;
    private String errorMessage;
    private T data;
}

后端逻辑处理

java 复制代码
@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest) {
    log.info("获取图书列表, pageRequest:{}", pageRequest);
    //用户登录, 返回图书列表
    PageResult<BookInfo> pageResult = bookService.getBookListByPage(pageRequest);
    log.info("获取图书列表222, pageRequest:{}", pageResult);
    return Result.success(pageResult);
}

Result.success(pageResult) 就是对返回数据进行了封装
拦截器帮我们实现了第⼀个功能, 接下来看SpringBoot对第⼆个功能如何⽀持

2.1 快速⼊⻔

统⼀的数据返回格式使⽤ @ControllerAdvice 和 ResponseBodyAdvice 的⽅式实现
@ControllerAdvice 表⽰控制器通知类
添加类 ResponseAdvice , 实现 ResponseBodyAdvice 接⼝, 并在类上添加@ControllerAdvice 注解

java 复制代码
import com.example.demo.model.Result;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import
org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
 @Override
 public boolean supports(MethodParameter returnType, Class converterType) {
 return true;
 }
 
 @Override
 public Object beforeBodyWrite(Object body, MethodParameter returnType, 
MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest 
request, ServerHttpResponse response) {
 return Result.success(body);
 }
}

supports⽅法: 判断是否要执⾏beforeBodyWrite⽅法. true为执⾏, false不执⾏. 通过该⽅法可以
选择哪些类或哪些⽅法的response要进⾏处理, 其他的不进⾏处理.
从returnType获取类名和⽅法名

java 复制代码
//获取执⾏的类
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
//获取执⾏的⽅法
Method method = returnType.getMethod();

beforeBodyWrite⽅法: 对response⽅法进⾏具体操作处理

2.2 存在问题

当返回结果为String类型时会有错误发⽣
解决⽅案:

java 复制代码
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof String){
            return objectMapper.writeValueAsString(Result.success(body));
        }
        if(body instanceof Result){
            return body;
        }
        return Result.success(body);
    }
}

这时,测试会正常

ObjectMapper 是 Jackson 包 里的核心类(SpringBoot 默认 JSON 序列化工具),作用: Java 对象 ↔ JSON 字符串 互相转换
原因分析:
SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (从先后顺序排列分别为
ByteArrayHttpMessageConverter ,
StringHttpMessageConverter , SourceHttpMessageConverter ,
SourceHttpMessageConverter , AllEncompassingFormHttpMessageConverter )

java 复制代码
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {

    //...
    public RequestMappingHandlerAdapter() {
        this.messageConverters = new ArrayList<>(4);
        this.messageConverters.add(new ByteArrayHttpMessageConverter());
        this.messageConverters.add(new StringHttpMessageConverter());
        if (!shouldIgnoreXml) {
            try {
                this.messageConverters.add(new SourceHttpMessageConverter<>());
            }
            catch (Error err) {
                // Ignore when no TransformerFactory implementation is available
            }
        }
        this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
    }
    //...
}

其中AllEncompassingFormHttpMessageConverter 会根据项⽬依赖情况 添加对应的
HttpMessageConverter

java 复制代码
public AllEncompassingFormHttpMessageConverter() {
    if (!shouldIgnoreXml) {
        try {
            addPartConverter(new SourceHttpMessageConverter<>());
        }
        catch (Error err) {
            // Ignore when no TransformerFactory implementation is available
        }

        if (jaxb2Present && !jackson2XmlPresent) {
            addPartConverter(new Jaxb2RootElementHttpMessageConverter());
        }
    }

    if (kotlinSerializationJsonPresent) {
        addPartConverter(new KotlinSerializationJsonHttpMessageConverter());
    }
    if (jackson2Present) {
        addPartConverter(new MappingJackson2HttpMessageConverter());
    }
    else if (gsonPresent) {
        addPartConverter(new GsonHttpMessageConverter());
    }
    else if (jsonbPresent) {
        addPartConverter(new JsonbHttpMessageConverter());
    }

    if (jackson2XmlPresent && !shouldIgnoreXml) {
        addPartConverter(new MappingJackson2XmlHttpMessageConverter());
    }

    if (jackson2SmilePresent) {
        addPartConverter(new MappingJackson2SmileHttpMessageConverter());
    }
}

在依赖中引⼊jackson包后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到
messageConverters 链的末尾.
Spring会根据返回的数据类型, 从 messageConverters 链选择合适的HttpMessageConverter .
当返回的数据是⾮字符串时, 使⽤的 MappingJackson2HttpMessageConverter 写⼊返回对象.
当返回的数据是字符串时, StringHttpMessageConverter 会先被遍历到,这时会认为
StringHttpMessageConverter 可以使⽤.

java 复制代码
public abstract class AbstractMessageConverterMethodProcessor extends
AbstractMessageConverterMethodArgumentResolver
implements HandlerMethodReturnValueHandler {
//...代码省略
protected <T> void writeWithMessageConverters(@Nullable T value,
MethodParameter returnType,
 ServletServerHttpRequest inputMessage, ServletServerHttpResponse
outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException,
HttpMessageNotWritableException {
//...代码省略
if (selectedMediaType != null) {
 selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter
instanceof GenericHttpMessageConverter ?
 (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
 ((GenericHttpMessageConverter)
converter).canWrite(targetType, valueType, selectedMediaType) :
 converter.canWrite(valueType, selectedMediaType)) {
//getAdvice().beforeBodyWrite 执⾏之后, body转换成了Result类型的
结果
 body = getAdvice().beforeBodyWrite(body, returnType,
selectedMediaType,
 (Class<? extends HttpMessageConverter<?>>)
converter.getClass(),
 inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
 LogFormatUtils.traceDebug(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody,
!traceOn) + "]");
 addContentDispositionHeader(inputMessage, outputMessage);
 if (genericConverter != null) {
 genericConverter.write(body, targetType,
selectedMediaType, outputMessage);
 }
 else {
 //此时cover为StringHttpMessageConverter
 ((HttpMessageConverter) converter).write(body,
selectedMediaType, outputMessage);
 }
 }
 else {
 if (logger.isDebugEnabled()) {
 logger.debug("Nothing to write: null body");
 }
 }
 return;
 }
 }
 }
//...代码省略
}
//...代码省略
}

在 ((HttpMessageConverter) converter).write(body, selectedMediaType,
outputMessage) 的处理中, 调⽤⽗类的write⽅法
由于 StringHttpMessageConverter 重写了addDefaultHeaders⽅法, 所以会执⾏⼦类的⽅法
然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String, 此
时t为Result类型, 所以出现类型不匹配"Result cannot be cast to java.lang.String"的异常

2.3 案例代码修改

如果⼀些⽅法返回的结果已经是Result类型了, 那就直接返回Result类型的结果即可

java 复制代码
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest
request, ServerHttpResponse response) {
    //返回结果更加灵活
    if (body instanceof Result){
        return body;
    }
    //如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化
    if (body instanceof String){
        return mapper.writeValueAsString(Result.success(body));
    }
    return Result.success(body);
}

2.4 优点

  1. ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据
  2. 降低前端程序员和后端程序员的沟通成本, 按照某个格式实现就可以了, 因为所有接⼝都是这样返回的.
  3. 有利于项⽬统⼀数据的维护和修改.
  4. 有利于后端技术部⻔的统⼀规范的标准制定, 不会出现稀奇古怪的返回内容.

3. 统⼀异常处理

统⼀异常处理使⽤的是 @ControllerAdvice + @ExceptionHandler 来实现的,
@ControllerAdvice 表⽰控制器通知类, @ExceptionHandler 是异常处理器,两个结合表
⽰当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件
具体代码如下:

java 复制代码
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@ResponseBody
public class ErrorAdvice {

    @ExceptionHandler
    public Object handler(Exception e) {
        return Result.fail(e.getMessage());
    }
}

这是全局异常处理器:

  1. @ControllerAdvice:作用于所有 Controller
  2. @ResponseBody:方法返回值直接作为响应体返回 JSON(也可以写在类上)
  3. @ExceptionHandler:捕获异常,不传参数时默认捕获 Exception,也就是所有异常
  4. 捕获到异常后,封装为失败的 Result 对象返回给前端,统一返回格式

类名, ⽅法名和返回值可以⾃定义, 重要的是注解
接⼝返回为数据时, 需要加 @ResponseBody 注解
以上代码表⽰,如果代码出现Exception异常(包括Exception的⼦类), 就返回⼀个 Result的对象, Result对象的设置参考 Result.fail(e.getMessage())

java 复制代码
public static Result fail(String msg) {
    Result result = new Result();
    result.setStatus(ResultStatus.FAIL);
    result.setErrorMessage(msg);
    result.setData("");
    return result;
}

我们可以针对不同的异常, 返回不同的结果.

java 复制代码
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ResponseBody
@ControllerAdvice
public class ErrorAdvice {

    @ExceptionHandler
    public Object handler(Exception e) {
        return Result.fail(e.getMessage());
    }
    @ExceptionHandler
    public Object handler(NullPointerException e) {
        return Result.fail("发⽣NullPointerException:"+e.getMessage());
    }

    @ExceptionHandler
    public Object handler(ArithmeticException e) {
        return Result.fail("发⽣ArithmeticException:"+e.getMessage());
    }
}

模拟制造异常:

java 复制代码
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/test")
@RestController
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        return "t1";
    }
    @RequestMapping("/t2")
    public boolean t2(){
        int a = 10/0; //抛出ArithmeticException
        return true;
    }
    @RequestMapping("/t3")
    public Integer t3(){
        String a =null;
        System.out.println(a.length()); //抛出NullPointerException
        return 200;
    }
}

当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配

相关推荐
kingx31 小时前
Bugku 3122 谷歌小恐龙题解:纯离线逆向 Vault 的 AES-GCM 加密资源(附 Python 解密器)
java·逆向·ctf·bugku·aes-gcm
DevOpenClub3 小时前
职教高考控制线 API:招生类别枚举、筛选与空分数含义
java·数据库·高考
IT_陈寒3 小时前
Redis雪崩把我坑惨了,这是5年经验的教训
前端·人工智能·后端
量化分析码农3 小时前
【Python量化多因子实战 06】从因子到持仓:多因子选股实盘工程化落地
后端
贾公子4 小时前
检测链路的容量规划——用 Little's Law 算清 QPS、并发与积压
后端·python
kyson_4 小时前
Java 集合框架:从继承体系到数据结构,再到开发选型
后端
露天赏雪4 小时前
第3篇:Sentinel熔断降级实战:大模型接口抖动时的服务兜底方案
java·开发语言·人工智能·spring boot·spring·servlet·sentinel
天天被压力4 小时前
【A股分钟级高频数据完全手册 #06】北交所分钟级数据接口:京市股票Pro版最新分时
java·人工智能·python
天天被压力4 小时前
【A股分钟级高频数据完全手册 #05】指数历史分钟数据接口:Pro版沪深指数分时回放
java·人工智能·python
想不到一个好的ID5 小时前
什么是RAG
后端