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

AOP的实战(统一功能处理模块)

一、用户登录权限效验

用户登录权限的发展从之前每个方法中自己验证用户登录权限,到现在统一的用户登录验证处理,它是一个逐渐完善和逐渐优化的过程。

1.1 最初用户登录验证

我们先来回顾一下最初用户登录验证的实现方法:
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 某方法 1
*/
@RequestMapping("/m1")
public Object method(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,业务处理
return true;
} else {
// 未登录
return false;
}
}
/**
* 某方法 2
*/
@RequestMapping("/m2")
public Object method2(HttpServletRequest request) {
// 有 session 就获取,没有不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("userinfo") != null) {
// 说明已经登录,业务处理
return true;
} else {
// 未登录
return false;
}
}
// 其他方法...
}
从上述代码可以看出,每个方法中都有相同的用户登录验证权限,它的缺点是:
  1. 每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也一样要传参调用和在方法中进行判断。
  2. 添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成本和维护成本。
  3. 这些用户登录验证的方法和接下来要实现的业务几何没有任何关联,但每个方法中都要写一遍。
 所以提供一个公共的 AOP 方法来进行统一的用户登录权限验证迫在眉睫。

1.2 Spring AOP 用户统一登录验证的问题

说到统一的用户登录验证,我们想到的第一个实现方案是 Spring AOP 前置通知或环绕通知来实现,具 体实现代码如下:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class UserAspect {
// 定义切点方法 controller 包下、子孙包下所有类的所有方法
@Pointcut("execution(* com.example.demo.controller..*.*(..))")
public void pointcut(){ }
// 前置方法
@Before("pointcut()")
public void doBefore(){
}
// 环绕方法
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint){
Object obj = null;
System.out.println("Around 方法开始执行");
try {
// 执行拦截方法
obj = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("Around 方法结束执行");
return obj;
}
}
如果要在以上 Spring AOP 的切面中实现用户登录权限效验的功能,有以下两个问题:
1. 没办法获取到 HttpSession 对象。
2. 我们要对一部分方法进行拦截,而另一部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则很难定义,甚至没办法定义。
那这样如何解决呢?

1.3 Spring 拦截器

对于以上问题 Spring 中提供了具体的实现拦截器: HandlerInterceptor ,拦截器的实现分为以下两个步骤:
1. 创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle (执行具体方法之前的预处理)方法。
2. 将自定义拦截器加入 WebMvcConfigurer addInterceptors 方法中。

1.3.1 自定义拦截器

接下来使用代码来实现一个用户登录的权限效验,自定义拦截器是一个普通类,具体实现代码如下:
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
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("userinfo") != null) {
return true;
}
response.setStatus(401);
return false;
}
}

1.3.2 将自定义拦截器加入到系统配置

将上一步中的自定义拦截器加入到系统配置信息中,具体实现代码如下:
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 添加拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有接口
.excludePathPatterns("/art/param11"); // 排除接口
}
}
其中:
addPathPatterns 表示需要拦截的 URL “**” 表示拦截任意方法(也就是所有方法)。
excludePathPatterns 表示需要排除的 URL
说明:以上拦截规则可以拦截此项目中的使用 URL ,包括静态文件(图片文件、 JS CSS 等文件)。

排除所有的静态资源

// 拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有接口
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.jpg")
.excludePathPatterns("/login.html")
.excludePathPatterns("/**/login"); // 排除接口
}

1.4 拦截器实现原理

正常情况下的调用顺序:

然而有了拦截器之后,会在调用 Controller 之前进行相应的业务处理,执行的流程如下图所示:

1.4.1 实现原理源码分析

所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现,这一点可以从 Spring Boot 控制台的打印信息看出,如下图所示:

而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度方法,doDispatch 源码如下:

protected void doDispatch(HttpServletRequest request, HttpServletResponse
response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha =
this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request,
mappedHandler.getHandler());
if ((new ServletWebRequest(request,
response)).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用预处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 执行 Controller 中的业务
mv = ha.handle(processedRequest, response,
mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch
failed", var21);
}
this.processDispatchResult(processedRequest, response,
mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response,
mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response,
mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
从上述源码可以看出在开始执行 Controller 之前,会先调用 预处理方法 applyPreHandle ,而
applyPreHandle 方法的实现源码如下:
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response)
throws Exception {
for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++)
{
// 获取项目中使用的拦截器 HandlerInterceptor
HandlerInterceptor interceptor =
(HandlerInterceptor)this.interceptorList.get(i);
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
return true;
}
从上述源码可以看出,在 applyPreHandle 中会获取所有的拦截器 HandlerInterceptor 并执行拦截器中的 preHandle 方法,这样就会咱们前面定义的拦截器对应上了,如下图所示:

 

此时用户登录权限的验证方法就会执行,这就是拦截器的实现原理。

1.4.2 拦截器小结

通过上面的源码分析,我们可以看出, Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的,大体的调用流程如下:

1.5 扩展:统一访问前缀添加

所有请求地址添加 api 前缀:
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 所有的接口添加 api 前缀
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("api", c -> true);
}
}
其中第二个参数是一个表达式,设置为 true 表示启动前缀。

二、统一异常处理

统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的, @ControllerAdvice 表示控制器通知类,@ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件,具体实现代码如下:
import java.util.HashMap;
@ControllerAdvice
public class ErrorAdive {
@ExceptionHandler(Exception.class)
@ResponseBody
public Object handler(Exception e) {
HashMap<String, Object> map = new HashMap<>();
map.put("success", 0);
map.put("status", 1);
map.put("msg", e.getMessage());
return map;
}
}
PS :方法名和返回值可以自定义,其中最重要的是 @ExceptionHandler(Exception.class) 注解。
以上方法表示,如果出现了异常就返回给前端一个 HashMap 的对象,其中包含的字段如代码中定义的那样。
我们可以针对不同的异常,返回不同的结果,比以下代码所示:
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
@ControllerAdvice
@ResponseBody
public class ExceptionAdvice {
@ExceptionHandler(Exception.class)
public Object exceptionAdvice(Exception e) {
HashMap<String, Object> result = new HashMap<>();
result.put("success", -1);
result.put("message", "总的异常信息:" + e.getMessage());
result.put("data", null);
return result;
}
@ExceptionHandler(NullPointerException.class)
public Object nullPointerexceptionAdvice(NullPointerException e) {
HashMap<String, Object> result = new HashMap<>();
result.put("success", -1);
result.put("message", "空指针异常:" + e.getMessage());
result.put("data", null);
return result;
}
}
当有多个异常通知时,匹配顺序为当前类及其子类向上依次匹配,案例演示。
UserController 中设置一个空指针异常,实现代码如下:
@RestController
@RequestMapping("/u")
public class UserController {
@RequestMapping("/index")
public String index() {
Object obj = null;
int i = obj.hashCode();
return "Hello,User Index.";
}
}
以上程序的执行结果如下:

三、统一数据返回格式

3.1 为什么需要统一数据返回格式?

统一数据返回格式的优点有很多,比如以下几个:
1. 方便前端程序员更好的接收和解析后端数据接口返回的数据。
2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就行了,因为所有接口都是这样返回的。
3. 有利于项目统一数据的维护和修改。
4. 有利于后端技术部门的统一规范的标准制定,不会出现稀奇古怪的返回内容。

3.2 统一数据返回格式的实现

统一的数据返回格式可以使用 @ControllerAdvice + ResponseBodyAdvice 的方式实现,具体实现代码如下:
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;
import java.util.HashMap;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
/**
* 内容是否需要重写(通过此方法可以选择性部分控制器和方法进行重写)
* 返回 true 表示重写
*/
@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) {
// 构造统一返回对象
HashMap<String, Object> result = new HashMap<>();
result.put("success", 1);
result.put("message", "");
result.put("data", body);
return result;
}
}

3.3 @ControllerAdvice 源码分析

通过对 @ControllerAdvice 源码的分析我们可以知道上面统一异常和统一数据返回的执行流程,我们先从 @ControllerAdvice 的源码看起,点击 @ControllerAdvice 实现源码如下:
从上述源码可以看出 @ControllerAdvice 派生于 @Component 组件,而所有组件初始化都会调用
InitializingBean 接口。
所以接下来我们来看 InitializingBean 有哪些实现类?在查询的过程中我们发现了,其中 Spring MVC 中的实现子类是 RequestMappingHandlerAdapter ,它里面有一个方法 afterPropertiesSet() 方法,表示所有的参数设置完成之后执行的方法,如下图所示:

而这个方法中有一个 initControllerAdviceCache 方法,查询此方法的源码如下:

总结

本篇博客介绍了

  • 统一用户登录权限的效验使用 WebMvcConfigurer+ HandlerInterceptor来实现
  • 统一异常 处理使用 @ControllerAdvice + @ExceptionHandler 来实现
  • 统一返回值处理使用 @ControllerAdvice + ResponseBodyAdvice 来处理。

相关文章:

AOP的实战(统一功能处理模块)

一、用户登录权限效验 用户登录权限的发展从之前每个方法中自己验证用户登录权限&#xff0c;到现在统一的用户登录验证处理&#xff0c;它是一个逐渐完善和逐渐优化的过程。 1.1 最初用户登录验证 我们先来回顾一下最初用户登录验证的实现方法&#xff1a; RestController…...

时间复杂度为O(n2)的三种简单排序算法

1.冒泡排序 冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较&#xff0c;看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少少一个元素移动到它应该在的位置&#xff0c;重复n次&#xff0c;就完成了n个数据的排序工作。 /*** …...

LeetCode 热题 100 JavaScript --226. 翻转二叉树

给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 3&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 提示&#xff1a; 树中节点数目范围在 [0, 100] 内 -100 < Node.val < 100 var invertTree function(root…...

hive所有窗口函数详情总结

hive窗口函数详情总结 解释语法hive开窗函数排序开窗函数样例数据RANK()DENSE_RANK()ROW_NUMBER() 分析开窗函数样例数据&#xff1a;last_valuefirst_valuelaglead 其他窗口函数cume_distpercent_rank 解释 开窗函数用于为行定义一个窗口&#xff08;指运算将要操作的行的集合…...

Talk | 新加坡国立大学博士生施宇钧:DragDiffusion-基于扩散模型的关键点拖拽图片编辑

本期为TechBeat人工智能社区第518期线上Talk&#xff01; 北京时间8月2日(周三)20:00&#xff0c; 新加坡国立大学博士生—施宇钧的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “DragDiffusion-基于扩散模型的关键点拖拽图片编辑”&#xff0c;他…...

22 | 贝叶斯分类算法

文章目录 介绍什么是贝叶斯分类算法?贝叶斯分类算法的应用场景贝叶斯定理贝叶斯定理的基本原理贝叶斯定理的公式推导贝叶斯定理的应用举例代码介绍 什么是贝叶斯分类算法? 贝叶斯分类算法是一类基于贝叶斯定理的分类技术。在统计分类任务中,这些算法使用特定的假设来建立特…...

java.sql.SQLSyntaxErrorException: ORA-00909: 参数个数无效

问题&#xff1a; 在Select里采用Contact(%,#name,%)报错参数个数无效 原因&#xff1a; 回想以前用Mysql的时候就是这样用的&#xff0c;没有问题&#xff0c;在这里就出问题了&#xff0c;所以确定问题在oracle数据库上&#xff0c;经过查询得知&#xff0c;oracle和mysql…...

数据结构8-哈希表

数据结构8-哈希表 动态分配内存方式&#xff1a; #include <stdio.h> #include <stdlib.h>#define SIZE 20struct DataItem {int data; int key; };struct DataItem* hashArray[SIZE]; struct DataItem* dummyItem; struct DataItem* item;//获取键值 int has…...

vue3引用Font-Awesome字体图标库

环境&#xff1a;vue3tsviteelement plus 介绍&#xff1a;这里安装引用的是Font-Awesome 6.x 版本&#xff0c;有专业版&#xff08;付费&#xff09;&#xff0c;这里只介绍免费版字体使用方法 一、安装 1.使用npm安装&#xff0c;终端打开项目目录或者命令行cd到目录文件夹…...

Python: Django 服务部署可能遇到的一些问题

502 bad gateway 不要用 python3 manage.py runserver 启动服务&#xff0c; 而要用&#xff1a; daphne -b 0.0.0.0 -p <端口> <工程名>.asgi:application此外&#xff0c;在 setting.py 中&#xff0c;修改&#xff1a; import osSECRET_KEY os.environ.get(D…...

Python爬虫时遇到连接超时解决方案

在进行Python爬虫任务时&#xff0c;经常会遇到连接超时&#xff08;TimeoutError&#xff09;错误。连接超时意味着爬虫无法在规定的时间内建立与目标服务器的连接&#xff0c;导致请求失败。为了帮助您解决这个常见的问题&#xff0c;本文将提供一些解决办法&#xff0c;并提…...

这所国字头双一流,根本招不满,学硕都没人报!

一、学校及专业介绍 中国民航大学&#xff0c;位于天津市&#xff0c;是民航局、天津市、教育部共建高校&#xff0c;是天津市“双一流”建设高校和高水平特色大学建设高校。 1.1 招生情况 2023年中国民航大学电子信息与自动化学院&#xff0c;初试考806信号与系统的一共有两…...

macos 查询端口占用 命令

在 macOS 上查询端口占用的命令是通过使用lsof&#xff08;list open files&#xff09;工具来实现的。 lsof可以显示当前系统中打开的文件&#xff08;包括网络连接和端口&#xff09;的相关信息。 打开终端应用程序&#xff08;Terminal&#xff09;&#xff0c;然后输入以下…...

无代码开发:打破传统开发模式,引领数字化转型新方向

随着数字化转型的加速&#xff0c;企业对于高效、便捷的软件开发需求愈发旺盛。无代码开发作为一种新兴的软件开发模式&#xff0c;以其可视化、模块化的开发方式&#xff0c;为数字化转型提供了新的方向。本文将从无代码开发的优势、应用场景、如何实现等方面进行详细解读&…...

go-zero超强工具goctl的常用命令api,rpc,model及其构建的服务解析

goctl api 详情移步&#xff1a; go-zero的路由机制解析 基于go-zero的api服务刨析并对比与gin的区别 goctl rpc goctl支持多种rpc&#xff0c;较为流行的是google开源的grpc&#xff0c;这里主要介绍goctl rpc protoc的代码生成与使用。 protoc是grpc的命令&#xff0c;作用…...

手机python编程软件怎么用,手机python编程软件下载

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;手机python编程软件保存的代码在哪里&#xff0c;手机python编程软件怎么运行&#xff0c;现在让我们一起来看看吧&#xff01; 原标题&#xff1a;盘点几个在手机上可以用来学习编程的软件 前天在悟空问答的时候&#…...

【使用 DSP 滤波器加速速度和位移】使用信号处理算法过滤加速度数据并将其转换为速度和位移研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

家居行业解决方案 | 君子签电子签约助力家居企业减负增效

过去&#xff0c;家居行业因供需两端碎片化、服务链条较长等因素&#xff0c;导致线上发展较为缓慢&#xff0c;近年来&#xff0c;互联网的发展推动直播电商、兴趣电商兴起&#xff0c;促使家居行业数字化建设需求越来越为迫切。 合同管理作为家居行业企业经营的一项重要管理…...

Nodejs 第五章(Npm run 原理)

npm run xxx 发生了什么 按照下面的例子npm run dev 举例过程中发生了什么 读取package json 的scripts 对应的脚本命令(dev:vite),vite是个可执行脚本&#xff0c;他的查找规则是&#xff1a; 先从当前项目的node_modules/.bin去查找可执行命令vite如果没找到就去全局的node…...

150. 逆波兰表达式求值

给你一个字符串数组 tokens &#xff0c;表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意&#xff1a; 有效的算符为 、-、* 和 / 。 每个操作数&#xff08;运算对象&#xff09;都可以是一个整数或者另一个表达式。 两个…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

IP如何挑?2025年海外专线IP如何购买?

你花了时间和预算买了IP&#xff0c;结果IP质量不佳&#xff0c;项目效率低下不说&#xff0c;还可能带来莫名的网络问题&#xff0c;是不是太闹心了&#xff1f;尤其是在面对海外专线IP时&#xff0c;到底怎么才能买到适合自己的呢&#xff1f;所以&#xff0c;挑IP绝对是个技…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式

今天是关于AI如何在教学中增强学生的学习体验&#xff0c;我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育&#xff0c;这并非炒作&#xff0c;而是已经发生的巨大变革。教育机构和教育者不能忽视它&#xff0c;试图简单地禁止学生使…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...