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

Spring Boot 统一功能处理(拦截器实现用户登录权限的统一校验、统一异常返回、统一数据格式返回)

目录

1. 用户登录权限校验

1.1 最初用户登录权限效验

1.2 Spring AOP 用户统⼀登录验证

1.3 Spring 拦截器

(1)创建自定义拦截器

(2)将自定义拦截器添加到系统配置中,并设置拦截的规则

1.4 练习:登录拦截器

(1)实现 UserController 实体类

(2)返回的登录页面:login.html

(3)实现效果

 1.5 拦截器实现原理

(1)实现原理源码分析

1.6 统一访问前缀添加

(1)在系统的配置文件中设置

 (2)在 application.properies 中配置

2. 统一的异常处理

2.1 异常的统一封装

(1)创建一个类,并在类上标识:@ControllerAdvice

 (2)添加方法 @ExceptionHandler 来订阅异常

3. 统一数据返回格式

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

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

(1)创建一个类,并添加 @ControllerAdvice

(2)实现 ResponseBodyAdvice 接口,并重写 supports 和 beforeBodyAdvice 方法

4. @ControllerAdvice 源码分析

(1) @ControllerAdvice 源码

(2)查看 initializingBean 有哪些实现类

 (3)查询 initControllerAdviceCache 方法


本节主要讲解Spring Boot 统一功能处理,同样也是 AOP 的实战环节,我们希望能够实现以下目标:

  1. 统一用户登陆权限验证
  2. 统一异常处理
  3. 统一数据格式返回

1. 用户登录权限校验

回顾一下最初用户登录验证的实现方法:

  1. 最初的用户登录校验版本:在每个方法中获取 Session 以及 Session 中的信息,对用户账号以及密码进行校验,正确则登录成功,反之则失败
  2. 第二版本:实现统一方法去校验是否登陆成功,在每个需要验证的方法中调用统一的用户登录身份效验方法来判断
  3. 第三版本:使用 Spring AOP 来进行用户统一登录校验
  4. 第四版本:使用 Spring 拦截器来实现用户的统一登录验证

1.1 最初用户登录权限效验

@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/a1")public Boolean login (HttpServletRequest request) {// 有 Session 就获取,没有就不创建HttpSession session = request.getSession(false);if (session != null && session.getAttribute("userinfo") != null) {// 说明已经登录,进行业务处理return true;} else {// 未登录return false;}}@RequestMapping("/a2")public Boolean login2 (HttpServletRequest request) {// 有 Session 就获取,没有就不创建HttpSession session = request.getSession(false);if (session != null && session.getAttribute("userinfo") != null) {// 说明已经登录,进行业务处理return true;} else {// 未登录return false;}}
}

这种方式写的代码,每个方法中都有相同的用户登录验证权限,缺点是:

  •     每个方法中都要单独写用户登录验证的方法,即使封装成公共方法,也一样要传参调用和在方法中进行判断
  •     添加控制器越多,调用用户登录验证的方法也越多,这样就增加了后期的修改成功和维护成功
  •     这些用户登录验证的方法和现在要实现的业务几乎没有任何关联,但还是要在每个方法中都要写一遍,所以提供一个公共的 AOP 方法来进行统一的用户登录权限验证是非常好的解决办法。

1.2 Spring AOP 用户统⼀登录验证

统一用户登录验证,首先想到的实现方法是使用 Spring AOP 前置通知或环绕通知来实现:

@Aspect // 当前类是一个切面
@Component
public class UserAspect {// 定义切点方法 Controller 包下、子孙包下所有类的所有方法@Pointcut("execution(* com.example.springaop.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 e) {e.printStackTrace();}System.out.println("Around 方法结束执行");return obj;}
}

但如果只在以上代码 Spring AOP 的切面中实现用户登录权限效验的功能,有这样两个问题:

  1.     没有办法得到 HttpSession 和 Request 对象
  2.     我们要对一部分方法进行拦截,而另一部分方法不拦截,比如注册方法和登录方法是不拦截的,也就是实际的拦截规则很复杂,使用简单的 aspectJ 表达式无法满足拦截的需求
     

1.3 Spring 拦截器

针对上面代码 Spring AOP 的问题,Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现有两步:

  1.     创建自定义拦截器,实现 Spring 中的 HandlerInterceptor 接口中的 preHandle方法
  2.     将自定义拦截器加入到框架的配置中,并且设置拦截规则

(1)创建自定义拦截器

//实现 HandlerInterceptor 接口
public class loginInterceptor implements HandlerInterceptor {/*** 返回 true 继续下序流程* false 表示验证失败*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 用户登录业务判断// false 表示当不存在 session 不存在时不需要创造一个会话信息HttpSession session = request.getSession(false);if (session != null && session.getAttribute("userinfo") != null){// 说明用户已经登录return true;}// 可以直接跳转到登录页面 或 返回一个 401、403 没有权限码response.sendRedirect("/login.html");
//        response.setStatus(401);return false;}
}

(2)将自定义拦截器添加到系统配置中,并设置拦截的规则

  • addPathPatterns:表示需要拦截的 URL,**表示拦截所有⽅法
  • excludePathPatterns:表示需要排除的 URL
@Configuration // 让随着spring启动而启动
public class AppConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new loginInterceptor()).addPathPatterns("/**")// 拦截所有请求.excludePathPatterns("/user/login")// 不拦截的 url 地址.excludePathPatterns("/user/reg").excludePathPatterns("/**/*.html");}
}

1.4 练习:登录拦截器

实现愿望:

  1. 登录、注册页面不拦截,其余页面都拦截
  2. 等登陆成功写入 session 后,拦截页面可访问

(1)实现 UserController 实体类


@RestController
@RequestMapping("/user")
public class UserController {@RequestMapping("/getUser")public String getuser(){System.out.println("执行了 getUser !");return "get user";}@RequestMapping("/login")public String login(){System.out.println("执行了 login !");return "get login";}@RequestMapping("/reg")public String reg(){System.out.println("执行了 reg !");return "get reg";}
}

(2)返回的登录页面:login.html

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>登录页面</title>
</head>
<body>
<h1>登录页面</h1>
</body>
</html>

(3)实现效果

 

 1.5 拦截器实现原理

 

(1)实现原理源码分析

  1. 所有的 Controller 执行都会通过一个调度器 DispatcherServlet 来实现 
  2. 而所有方法都会执行 DispatcherServlet 中的 doDispatch 调度⽅法,doDispatch 源码分析如下:

 通过源码分析,可以看出,Sping 中的拦截器也是通过动态代理和环绕通知的思想实现的

1.6 统一访问前缀添加

方法:

  1. 在系统的配置文件中设置
  2. 在 application.properies 中配置

(1)在系统的配置文件中设置

/*** 所有的接口添加 api 前缀* c 代表所有的请求(Controller)* 表示所有的地址都会加上这个前缀* @param configurer*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {configurer.addPathPrefix("api",c -> true);
}

现在我们去查看之前不被拦截的地址

 (2)在 application.properies 中配置

 

2. 统一的异常处理

  1. 给当前的类上加 @ControllerAdvice 表示控制器通知类
  2. 给方法上添加 @ExceptionHandler(xxx.class),表示异常处理器,添加异常返回的业务代码

我们先去制造些异常:

 

2.1 异常的统一封装

(1)创建一个类,并在类上标识:@ControllerAdvice

@ControllerAdvice
public class ExceptionHandler {
}

 (2)添加方法 @ExceptionHandler 来订阅异常

@ControllerAdvice
@ResponseBody// 表示当前的所有方法返回的都是数据不是页面
public class ExHandler {/*** 拦截所有的空指针异常,继续统一的数据返回*/@ExceptionHandler(NullPointerException.class)// 空指针异常public HashMap<String,Object> nullException(NullPointerException e){HashMap<String,Object> result = new HashMap<>();result.put("code","-1");result.put("msg","空指针异常:" + e.getMessage());//错误码的描述信息result.put("date",null);return result;}}

但是需要考虑的一点是,如果每个异常都这样写,那么工作量是非常大的,并且还有自定义异常,所以上面这样写肯定是不好的,既然是异常直接写 Exception 就好了,它是所有异常的父类,如果遇到不是前面写的两种异常,那么就会直接匹配到 Exception

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

@ControllerAdvice
@ResponseBody// 表示当前的所有方法返回的都是数据不是页面
public class ExHandler {/*** 拦截所有的空指针异常,继续统一的数据返回*/@ExceptionHandler(NullPointerException.class)// 空指针异常public HashMap<String,Object> nullException(NullPointerException e){HashMap<String,Object> result = new HashMap<>();result.put("code","-1");result.put("msg","空指针异常:" + e.getMessage());//错误码的描述信息result.put("date",null);return result;}@ExceptionHandler(Exception.class)// 所有异常public HashMap<String,Object> AllException(NullPointerException e){HashMap<String,Object> result = new HashMap<>();result.put("code","-1");result.put("msg","异常:" + e.getMessage());//错误码的描述信息result.put("date",null);return result;}
}

 

 

3. 统一数据返回格式

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

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

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

(1)创建一个类,并添加 @ControllerAdvice

@ControllerAdvice
public class ResponseAdvice {}

(2)实现 ResponseBodyAdvice 接口,并重写 supports 和 beforeBodyAdvice 方法

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {/*** 表示是否需要重写* 返回true则执行beforeBodyWrite方法,反之则不执行*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body,MethodParameter returnType,MediaType selectedContentType,Class selectedConverterType,ServerHttpRequest request,ServerHttpResponse response) {HashMap<String,Object> hashMap = new HashMap<>();hashMap.put("code",200);// 状态码hashMap.put("msg","");// 错误的描述信息hashMap.put("date",body);return hashMap;}
}

supports方法相当于是一个开关,只有当 true 时才能执行重写 beforeBodyWrite 方法,false就不重写

当访问 getUser 时发生异常了,类型访问异常 

注意:

我们知道String既不属于基本数据类型,又不属于对象且在重写方法的时候其余类型都是用的统一的格式化工具,而String用的是它自身的格式化工具,String自身的格式化工具在执行的时候还没有加载好,就会导致 原始类型 是String的时候,在转化成HashMap的时候就会报错

所以在统一返回的时候需要对String进行单独的处理

 

jackson就是用于 json 数据转换的,json的转换工具 

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {HashMap<String,Object> hashMap = new HashMap<>();hashMap.put("code",200);// 状态码hashMap.put("msg","");// 错误的描述信息hashMap.put("date",body);if (body instanceof String){// 判断数据类型是不是 String,是String需要特殊处理,因为 String 在转换的时候会报错try {return objectMapper.writeValueAsString(hashMap);} catch (JsonProcessingException e) {e.printStackTrace();}}return hashMap;
}

4. @ControllerAdvice 源码分析

通过对 @ControllerAdvice 源码的分析我们可以知道上面统一异常和统一数据返回的执行流程

(1) @ControllerAdvice 源码

可以看到 @ControllerAdvice 派生于 @Component 组件而所有组件初始化都会调用 InitializingBean 接口

(2)查看 initializingBean 有哪些实现类

在查询过程中发现,其中 Spring MVC 中的实现子类是 RequestMappingHandlerAdapter,它里面有一个方法 afterPropertiesSet()方法,表示所有的参数设置完成之后执行的方法

 (3)查询 initControllerAdviceCache 方法

发现这个方法在执行时会查找使用所有的 @ControllerAdvice 类,发送某个事件时,调用相应的 Advice 方法,比如返回数据前调用统一数据封装,比如发生异常是调用异常的 Advice 方法实现的

相关文章:

Spring Boot 统一功能处理(拦截器实现用户登录权限的统一校验、统一异常返回、统一数据格式返回)

目录 1. 用户登录权限校验 1.1 最初用户登录权限效验 1.2 Spring AOP 用户统⼀登录验证 1.3 Spring 拦截器 &#xff08;1&#xff09;创建自定义拦截器 &#xff08;2&#xff09;将自定义拦截器添加到系统配置中&#xff0c;并设置拦截的规则 1.4 练习&#xff1a;登录…...

P4058 [Code+#1] 木材

1&#xff1a;思路&#xff1a;二分月数&#xff0c;然后贪心&#xff0c;就是说要求最小月数&#xff0c;拿每次判断是否到达s长度的时候我们就从大的开始拿。 int l-1,r1e181;while(l1<r){int midlr>>1;if(check(mid))rmid;else lmid;} 2&#xff1a;记得看数据&a…...

Python学习笔记第五十二天(Pandas 安装)

Python学习笔记第五十二天 Pandas 安装查看安装版本 安装验证结束语 Pandas 安装 安装 pandas 需要基础环境是 Python&#xff0c;开始前我们假定你已经安装了 Python 和 Pip。 使用 pip 安装 pandas: pip install pandas安装成功后&#xff0c;我们就可以导入 pandas 包使用…...

分布式搜索ElasticSearch-ES(一)

一、ElasticSearch介绍 ES是一款非常强大的开源搜索引擎&#xff0c;可以帮我们从海量的数据中快速找到我们需要的内容。 ElasticSearch结合kibana、Logstash、Beats&#xff0c;也就是elastic stack(ELK)&#xff0c;被广泛运用在日志数据分析&#xff0c;实时监控等领域。 …...

react学习笔记——3. jsx语法规则

jsx是什么&#xff1f; jsx全称&#xff1a;javaScript XML是react定义的一种类似于XML的js扩展语法&#xff0c;是jsxml。 xml早期用于存储和传输数据&#xff0c;是标签加数据的形式。只不过后来慢慢的变成了json 其本质就是React.createElement(标签,属性,内容)方法的语法糖…...

MySQL分表实现上百万上千万记录分布存储的批量查询设计模式

我们知道可以将一个海量记录的 MySQL 大表根据主键、时间字段&#xff0c;条件字段等分成若干个表甚至保存在若干服务器中。唯一的问题就是跨服务器批量查询麻烦&#xff0c;只能通过应用程序来解决。谈谈在Java中的解决思路。其他语言原理类似。这里说的分表不是 MySQL 5.1 的…...

射频入门知识-1

信号源 示波器 综合测试仪 功率计 噪声测试仪 频谱分析仪 频谱分析仪: 放大器的噪声系数测试 放大器增益测试 噪声和增益是放大器的最关键指标&#xff0c;学学怎么用频谱仪做放大器的噪声测试 那个 hbf740 输入和输出阻抗匹配具体怎么搞 《ADS2011射频电路设计与…...

基于注解函数式编程实现组件解耦设计

随着业务系统的不断发展,系统架构变得越来越复杂,多种业务交叉写在一起,不仅带来了维护层面的困难,而且新人也很难以入手修改代码,业界通常采用组件模块化开发模式,用于降低系统的复杂度,本文主要针对组件化具体实施过程中,组件层面的方法解耦进行了详细讲解。 1前言 …...

并查集、树状数组

并查集、树状数组、线段树 并查集树状数组树状数组1 (单点修改&#xff0c;区间查询)树状数组2 (单点查询&#xff0c;区间修改) 并查集 【模板】并查集 题目描述 如题&#xff0c;现在有一个并查集&#xff0c;你需要完成合并和查询操作。 输入格式 第一行包含两个整数 …...

ES6中Null判断运算符(??)正确打开方式-

读取对象属性的时候&#xff0c;如果某个属性的值是null或者undefined&#xff0c;有时候需要为它们指定默认值。常见的作法是通过||运算符指定默认值。 const headerText response.settings.headerText || Hello, world!; const animationDuration response.settings.anima…...

java的内存模型

Java内存基础 并发编程模型的两个关键问题 线程之间如何通信及线程之间如何同步 线程之间的通信机制有两种&#xff1a;共享内存和消息传递。 在共享内存的并发模型里&#xff0c;线程之间共享程序的公共状态&#xff0c;通过写-读内存中的公共状态 进行隐式通信。在消息传…...

基于 CentOS 7 构建 LVS-DR 群集 配置nginx负载均衡

环境配置&#xff1a; RHCE客户机192.168.100.146node1lvs192.168.100.145node2RS192.168.100.147node3RS192.168.100.148 配置ipvsadm httpd&#xff1a; [rootnode1 ~]# yum install ipvsadm.x86_64 [rootnode2 ~]# yum install http -y [rootnode2 ~]# systemctl …...

CSS练习

CSS练习 工具代码运行结果 工具 HBuilder X 代码 <!DOCTYPE html> <!-- 做一个表格&#xff0c;6行4列实现隔行换色&#xff08;背景色&#xff09;并且第3列文字红色第一个单元格文字大小30px。最后一个单元格文字加粗--> <html><head><meta ch…...

基于深度学习的3D城市模型增强【Mask R-CNN】

在这篇文章中&#xff0c;我们描述了一个为阿姆斯特丹 3D 城市模型自动添加门窗的系统&#xff08;可以在这里访问&#xff09;。 计算机视觉用于从城市全景图像中提取有关门窗位置的信息。 由于这种类型的街道级图像广泛可用&#xff0c;因此该方法可用于较大的地理区域。 推荐…...

LabVIEW对并行机器人结构进行建模仿真

LabVIEW对并行机器人结构进行建模仿真 为了对复杂机器人结构的数学模型进行建模、搜索、动画和验证&#xff0c;在工业机器人动态行为实验室中&#xff0c;设计并实现了具有五个自由度的单臂型机器人。在研究台上可以区分以下元素&#xff1a;带有直流电机和编码器的机器人;稳…...

【算法题】1281. 整数的各位积和之差

题目&#xff1a; 给你一个整数 n&#xff0c;请你帮忙计算并返回该整数「各位数字之积」与「各位数字之和」的差。 示例 1&#xff1a; 输入&#xff1a;n 234 输出&#xff1a;15 解释&#xff1a; 各位数之积 2 * 3 * 4 24 各位数之和 2 3 4 9 结果 24 - 9 15 示…...

(一)ES6 介绍

为什么学习ES6 ES6的版本变动内容最多&#xff0c;具有里程碑意义ES加入许多新的语法特性&#xff0c;编程实现更简单、搞笑ES6是前端发展趋势&#xff0c;就业必备技能 什么是ECMA ECMA&#xff08;European Computer Manufacturers Association&#xff09;&#xff0c;中…...

窥孔优化(Peephole Optimization)

窥孔优化&#xff08;Peephole Optimization&#xff09;是编译器中的一个技术&#xff0c;用于优化生成的中间代码或目标代码。该优化方法通过查看代码的小部分&#xff08;或称为“窥孔”&#xff09;来识别并提供更高效的代码替代方案。 1. 基本概念 定义&#xff1a;窥孔优…...

Docker安装ElasticSearch/ES 7.4.0

目录 前言安装ElasticSearch/ES安装步骤1&#xff1a;准备1. 安装docker2. 搜索可以使用的镜像。3. 也可从docker hub上搜索镜像。4. 选择合适的redis镜像。 安装步骤2&#xff1a;拉取ElasticSearch镜像1 拉取镜像2 查看已拉取的镜像 安装步骤3&#xff1a;创建容器创建容器方…...

无涯教程-Perl - readline函数

描述 此函数从EXPR引用的文件句柄中读取一行,并返回输出。如果要直接使用FILEHANDLE,则必须将其作为typeglob传递。 Simply readline function is equvivalent to <>. 语法 以下是此函数的简单语法- readline EXPR返回值 此函数在标量context中仅返回一行,而在列表…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》

引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

解读《网络安全法》最新修订,把握网络安全新趋势

《网络安全法》自2017年施行以来&#xff0c;在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂&#xff0c;网络攻击、数据泄露等事件频发&#xff0c;现行法律已难以完全适应新的风险挑战。 2025年3月28日&#xff0c;国家网信办会同相关部门起草了《网络安全…...