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

试描述一下网站建设的基本流程建网站做淘宝客

试描述一下网站建设的基本流程,建网站做淘宝客,企业网站备案座机号,免费广告制作软件目录 前言 拦截器 什么是拦截器? 拦截器的使用 自定义拦截器 注册并配置拦截器 拦截器详解 拦截路径 拦截器执行流程 适配器模式 统一数据返回格式 优点 统一异常处理 前言 在前面中,我们已经学习了spring中的一些常用操作,那么…

目录

前言

拦截器

什么是拦截器?

拦截器的使用

自定义拦截器

注册并配置拦截器

拦截器详解

拦截路径

 拦截器执行流程

适配器模式

统一数据返回格式

优点

统一异常处理


前言

在前面中,我们已经学习了spring中的一些常用操作,那么本篇,我们就继续往下深入学习。

我们在做一些小项目的时候,假如我们想要判断用户是否已经登录,按照我们前面的学习,我们就需要用到seesion来进行判断。设想一下,我们有几个界面,而这几个界面都需要在用户登录后才能进行查看的,就想淘宝,如果我们未登录,那么他就跳转到登录界面。

 对于这样的操作,我们的界面不止一个,那么我们对应的在每个页面调用后端的API,其中的方法每次都需要判断用户是否登录,这样会让代码冗余,所以,在Spring中,给我们提供了一种功能,能够让我们将这些重复的代码进行抽取——拦截器。

拦截器

什么是拦截器?

拦截器(Interceptor)是一种在请求处理流程中,对请求和响应进行拦截和预处理的机制。它允许开发者在请求到达目标处理器(如控制器方法)之前或之后执行统一的逻辑,从而实现诸如权限校验、日志记录、性能监控、请求过滤等功能。

拦截器的使用

拦截器的使用步骤分为两步:

  1. 定义拦截器
  2. 注册并配置拦截器 

自定义拦截器

在Spring  MVC框架中,拦截器通过实现 HandlerInterceptor  接口来定义拦截逻辑

package com.example.demo.interceptor;import com.example.demo.Result.Results;
import com.example.demo.constant.Constants;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;/*** 登录拦截器,用于在请求处理前验证用户是否已登录*/
@Slf4j
@Component
public class LoginInterceptor  implements HandlerInterceptor {@Autowiredprivate ObjectMapper objectMapper;/*** 在请求处理之前进行拦截* * @param request  HttpServletRequest对象,用于获取请求信息* @param response HttpServletResponse对象,用于设置响应信息* @param handler  请求处理器,可以是HandlerMethod或RequestMappingHandler等* @return boolean 表示是否继续执行其他拦截器和目标方法。返回true表示继续执行,返回false表示中断执行。* @throws Exception 抛出异常表示拦截器处理出现错误*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("方法执行前");//进行登录校验HttpSession session= request.getSession(false);if(session!=null&&"true".equals(session.getAttribute(Constants.USER_SESSION_KEY))){// 用户已登录,继续执行请求return true;}// 用户未登录,返回未授权错误信息Results results=Results.unLogin();response.setStatus(HttpStatus.UNAUTHORIZED.value());response.getOutputStream().write(objectMapper.writeValueAsString(results).getBytes());response.setContentType("application/json;charset=UTF-8");response.getOutputStream().close();return false;}/*** 在请求处理之后,视图渲染之前进行拦截* * @param request  HttpServletRequest对象,用于获取请求信息* @param response HttpServletResponse对象,用于设置响应信息* @param handler  请求处理器,可以是HandlerMethod或RequestMappingHandler等* @param modelAndView ModelAndView对象,用于添加模型数据或修改视图* @throws Exception 抛出异常表示拦截器处理出现错误*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("方法执行后");HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}/*** 在请求完成之后执行的方法* * @param request 传入的HTTP请求对象,包含请求相关数据* @param response 传入的HTTP响应对象,包含响应相关数据* @param handler 处理请求的处理器对象,可以是任何类型的对象* @param ex 请求处理过程中发生的异常,如果没有异常,则为null* @throws Exception 根据具体实现可能会抛出的异常* * 此方法主要用于在请求处理完成后进行一些清理工作,例如关闭数据库连接、释放资源等* 它是在请求处理的最后一步调用的,确保了所有处理逻辑已经执行完毕*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}
  • preHandler():该方法是在目标方法执行前执行的。若返回true,则继续执行后续的业务逻辑,返回false,则中断后续的操作。
  • postHandler():该方法是在目标方法执行后再执行的。
  • afterCompletion():该方法是在视图渲染之后执行的,在postHandler()方法之后执行,但由于现在前后端分离,所以后端基本上接触不到视图的渲染,这个方法用的少。

注册并配置拦截器

注册配置拦截器我们需要实现 WebMvcConfiguer 接口,并实现其中的 addInterceptors 方法。

package com.example.demo.config;import com.example.demo.interceptor.LoginInterceptor;
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;/*** 配置类用于配置Web相关的设置*/
@Configuration
public class WebConfig implements WebMvcConfigurer {/*** 登录拦截器,用于拦截请求以进行登录验证*/@Autowiredprivate LoginInterceptor loginInterceptor;/*** 添加拦截器以配置请求的预处理和后处理* * @param registry 拦截器注册对象,用于注册自定义拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {//注册拦截器registry.addInterceptor(loginInterceptor)//拦截以"/book/"开头的所有请求.addPathPatterns("/book/**")//排除"/user/login"请求,使其不被拦截.excludePathPatterns("/user/login");}
}

假如此时我们未登录,调用一下查询功能:

可以看到,会对请求进行拦截。

 知道了拦截器的是如何使用的,那么就来进一步了解拦截器。

拦截器详解

拦截器的使用细节我们讲以下两个部分:

  • 拦截路径配置
  • 拦截器实现原理

拦截路径

拦截路径指的是我们定义的拦截器对哪些请求生效,我们在注册配置拦截器的时候,通过 addPathPatterns() 方法就可以来指定要拦截哪些请求,也可以通过 excludePathPatterns() 方法来指定哪些请求不需要拦截

关于拦截路径设置的规则,有以下几种:

拦截路径含义举例

/*

一级路径

能匹配/user,/book,但不能匹配/book/getList等

/**任意级路径能匹配/user,/user/login,即任意路径都可以匹配
/book/*/book下的一级路径能匹配/book/addBook,不能匹配/book/addBook/get,/book
/book/**/book下的任意级路径能匹配/book,/book/addBook,/book/addBook/2,不能匹配/user/login

上面的这些拦截规则可以拦截项目中的URL,包括静态文件(如图片文件、JS和CSS等文件).

如果我们使用下面这种拦截规则,就会将前端界面的请求也给拦截住。 

 我们可以通过设置前端界面不拦截:

package com.example.demo.config;import com.example.demo.interceptor.LoginInterceptor;
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.ArrayList;
import java.util.Arrays;
import java.util.List;/*** 配置类用于配置Web相关的设置*/
@Configuration
public class WebConfig implements WebMvcConfigurer {/*** 登录拦截器,用于拦截请求以进行登录验证*/@Autowiredprivate LoginInterceptor loginInterceptor;public List<String> excludePath= Arrays.asList("/user/*","/css/**","/js/**","/img/**","/**/*.html");//放行路径/*** 添加拦截器以配置请求的预处理和后处理** @param registry 拦截器注册对象,用于注册自定义拦截器*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {//注册拦截器registry.addInterceptor(loginInterceptor)//拦截以"/book/"开头的所有请求.addPathPatterns("/**")//排除"/user/login"请求,使其不被拦截.excludePathPatterns(excludePath);}
}

这样,就可以获取到前端界面。

 拦截器执行流程

在没有添加拦截器之前,我们调用顺序是:

在添加拦截器后:

  1. 在添加完拦截器后,执行Controller中的方法之前,请求会先被拦截器拦截住,执行preHandler() 方法,这个方法会返回一个布尔类型的值。如果返回true,说明要放行,继续访问controller中的方法;如果返回false,则不会放行(controller中的方法不会执行).
  2. controller中的方法执行完后,会继续执行 postHandler() 方法以及 afterCompletion() 方法,执行完毕后,最终给浏览器响应数据。

我们通过观察日志,可以看到当Tomcat在启动之后,会有核心的类 DispatcherServlet 来控制程序的执行顺序。

所有的请求都会先进入到DispatcherServlet 中,执行 doDispatch() 调度方法,如果有拦截器,就会先执行拦截器中 preHandle() 方法中的代码,如果 preHandle() 返回true,那么就会继续访问controller中的方法,当controller中的方法执行完毕,就会再回过来执行 postHandle() 和 afterCpmpletion() 方法,返回给DispatcherServlet,最终给浏览器响应数据。

 我们观察 DispatcherServlet 中的源码,就可以看到这三个方法的执行流程:

DispatcherServlet  源码中,我还可以看到使用了适配器模式:

适配器模式

 适配器模式也叫包装器模式。将一个类的接口,转换成客户期望的另有一个接口,适配器让原本接口不兼容的类可以合作无间。

简单来说:就是目标类不能直接使用,通过一个新类进行包装,适配调用方使用,把两个不兼容的接口通过一定的方式使之兼容

 使用适配器来使两个接口兼容:

其实在我们前面学习Spring日志的时候,其中的 slf4j 就使用到了适配器模式,我们想要打印日志的时候,只需要调用slf4j的API,而它会自动去调用底层的 log4j 或者 logback 来打印日志。

示例:

package com.example.demo.config;/*** Slf4j接口定义了打印日志的方法*/
public interface Slf4j {/*** 打印日志信息* @param message 需要打印的日志信息*/void print(String message);
}/*** Log4j类提供了具体的日志打印实现*/
class Log4j{/*** 打印日志信息* @param message 需要打印的日志信息*/void log4jPrint(String message){System.out.println("Log4j: "+message);}
}/*** Slf4jAdapter类是Slf4j接口与Log4j类之间的适配器* 它使得Log4j可以通过Slf4j接口来打印日志*/
class Slf4jAdapter  implements Slf4j{/*** log4j实例用于实际的日志打印*/private Log4j log4j;/*** 构造函数,接收一个Log4j实例* @param log4j Log4j实例,用于实际的日志打印*/public Slf4jAdapter(Log4j log4j) {this.log4j = log4j;}/*** 实现Slf4j接口的print方法,通过Log4j实例来打印日志* @param message 需要打印的日志信息*/@Overridepublic void print(String message) {log4j.log4jPrint(message);}
}/*** Demo类包含主程序,用于演示Slf4jAdapter的使用*/
class Demo{/*** 主函数,创建Log4j实例并通过Slf4jAdapter适配,然后打印日志信息* @param args 命令行参数*/public static void main(String[] args) {Log4j log4j = new Log4j();Slf4j slf4j = new Slf4jAdapter(log4j);slf4j.print("Hello World");}
}

可以看到,我们不需要修改log4j的api,只需要使用适配器,就可以更换日志框架,维护系统。

那么为什么不直接调用Log4j呢? 

 适配器模式其实可以看做一种“补偿模式”,用来补救设计上的缺陷,使用这种模式是无奈之举。如果在设计初期,能够规避接口不兼容的问题,那么就不需要使用适配器模式了。

统一数据返回格式

我们在做项目的时候,如果前端调用后端返回的数据格式都不同,后序如果修改起来,就显得有点杂乱,所以我们可以对返回的数据格式进行统一——统一数据返回格式

在SpringBoot,如果我们想要在返回数据响应之前对数据进行一些逻辑操作,那么我们就需要使用到注解 @ControllerAdvice ResponseBodyAdvice 接口的实现。

定义下存常量的类:

package com.example.demo.constant;public class Constants {public static final String USER_SESSION_KEY = "user";public static final Integer SUCCESS_CODE = 200;//成功public static final Integer FAIL_CODE = -2;//失败public static final Integer UNLOGIN_CODE = -1;//未登录}

统一返回数据格式:

package com.example.demo.Result;import com.example.demo.constant.Constants;import lombok.Data;@Data
public class Results<T> {private Integer code;//200成功,-1未登录,-2程序异常private String msg;private  T data;public static <T> Results success(T data){Results results=new Results();results.setCode(Constants.SUCCESS_CODE);results.setMsg("");results.setData(data);return results;}public static  Results unLogin(){Results results=new Results();results.setCode(Constants.UNLOGIN_CODE);results.setMsg("用户未登录");return results;}public static <T> Results fail(String msg){Results results=new Results();results.setCode(Constants.FAIL_CODE);results.setMsg(msg);return results;}public static <T> Results fail(){Results results=new Results();results.setCode(Constants.FAIL_CODE);results.setMsg("程序出现异常");return results;}
}

响应处理:

package com.example.demo.config;import com.example.demo.Result.Results;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
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;/*** 全局响应处理,统一处理所有响应* 该类实现了ResponseBodyAdvice接口,用于自定义响应体*/
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;/*** 判断是否支持当前的返回类型和转换器类型* 该方法始终返回true,表示支持所有类型的响应处理*/@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}/*** 在写入响应体前处理数据* 该方法根据返回的数据类型,进行相应的处理和封装* 如果返回类型是String,则将其作为成功消息封装进Results对象* 如果返回类型已经是Results,则直接返回* 否则,将返回值作为成功数据封装进Results对象*/@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if(body instanceof String){return  objectMapper.writeValueAsString(Results.success(body));}if(body instanceof Results){return body;}return Results.success(body);}
}

调用一下看看:

可以看到,这样的话,如果前端想要获取到我们后端的数据,以及后续修改操作等,就更加清晰容易操作了。

我们再来看一处地方:

这里为什么要这样写呢?

 SpringMVC默认会注册一些自导的HttpMessageConverter(从先后顺序排序分别为ByteArrayHttpMessageConverter、StringHttpMessageConverter、SourceHttpMessageConverte、SourceHttpMessageConverterr、AllEncompassingFormHttpMessageConverter)

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

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

可以看到子类StringHttpMessageConverter中的addDefaultHeaders()方法接收的参数为String,但我们需要返回的类型为Results类型,所以这里我们就需要利用SpringBoot内置提供的Jackson来实现信息的序列化。即:

  • @SneakyThrows 是 Lombok 提供的一个注解,用于简化 Java 中的异常处理。它允许开发者在方法中抛出受检异常(checked exceptions),而无需在方法签名中显式声明 throws,也不需要使用 try-catch 块。

优点

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

统一异常处理

 当我们的程序出现异常时,我们也可以对这些异常进行统一处理。

统一异常处理使用的是 @ControllerAdvice@ExceptionHandler 来实现的。

  • @ControllerAdvice 表⽰控制器通知类
  • @ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执⾏某个⽅法事件
package com.example.demo.config;import cn.hutool.core.io.resource.NoResourceException;
import com.example.demo.Result.Results;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.resource.NoResourceFoundException;/*** 全局异常处理类* 用于统一处理项目中的异常,提高代码的健壮性和用户体验*/
@ControllerAdvice
@Slf4j
@ResponseBody
public class ErrorAdviceHandler {/*** 处理通用异常* @param e 异常对象* @return 返回处理结果*/@ExceptionHandlerpublic Object handler(Exception e){log.error("异常信息:{}",e.getMessage());return Results.fail(e.getMessage());}/*** 处理数组越界异常* @param e 异常对象* @return 返回处理结果*/@ExceptionHandlerpublic Object handler(ArrayIndexOutOfBoundsException e){log.error("异常信息:{}",e.getMessage());return Results.fail(e.getMessage());}/*** 处理空指针异常* @param e 异常对象* @return 返回处理结果*/@ResponseStatus@ExceptionHandlerpublic Object handler(NullPointerException e){log.error("异常信息:{}",e.getMessage());return Results.fail(e.getMessage());}/*** 处理算数异常* @param e 异常对象* @return 返回处理结果*/@ExceptionHandlerpublic Object handler(ArithmeticException e){log.error("异常信息:{}",e.getMessage());return Results.fail(e.getMessage());}/*** 处理资源未找到异常* @param e 异常对象* @return 返回处理结果*/@ResponseStatus(HttpStatus.NOT_FOUND)@ExceptionHandlerpublic Object handler(NoResourceFoundException e){log.error("异常信息:{}  path:{}",e.getDetailMessageCode(),e.getResourcePath());return Results.fail(e.getMessage());}
}

 测试案例:

package com.example.demo.Controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** Demo控制器类,用于处理演示项目的HTTP请求*/
@RestController
@RequestMapping("/demo")
public class DemoController {/*** 处理/t1请求的方法* 该方法演示了未处理的除零异常,用于教学或测试目的* * @return 成功信息字符串,但在执行中将引发除零异常*/@RequestMapping("/t1")public String t1(){// 该行代码将产生除零异常int a=1/0;return "success";}/*** 处理/t2请求的方法* 该方法演示了未处理的空指针异常,用于教学或测试目的* * @return 成功信息字符串,但在执行中将引发空指针异常*/@RequestMapping("/t2")public String t2(){// 该行代码将产生空指针异常String str=null;str.length();return "success";}/*** 处理/t3请求的方法* 该方法演示了未处理的数组越界异常,用于教学或测试目的* * @return 成功信息字符串,但在执行中将引发数组越界异常*/@RequestMapping("/t3")public String t3(){// 初始化一个包含三个元素的数组int[] arr={1,2,3};// 该行代码将产生数组越界异常arr[3]=4;return "success";}
}

此外,如果发生空指针异常或者其他异常,异常处理中有Exception和NullPointerException的话,优先执行具体的异常,即执行NullPointerException异常。

具体异常优先于通用异常


以上就是本篇所有内容~

若有不足,欢迎指正~ 


文章转载自:

http://TNgnXK7Y.xmwdt.cn
http://1ICFa0of.xmwdt.cn
http://D5KvoGpk.xmwdt.cn
http://xAeBQwCd.xmwdt.cn
http://GISEY0pu.xmwdt.cn
http://BHb8ax8Q.xmwdt.cn
http://4d0OqLB6.xmwdt.cn
http://22FgRbNS.xmwdt.cn
http://fsMP14M0.xmwdt.cn
http://6Vvbebhd.xmwdt.cn
http://ToTgueA7.xmwdt.cn
http://AOuRQ8ZD.xmwdt.cn
http://KVXk1fMc.xmwdt.cn
http://eFnvjBCU.xmwdt.cn
http://YpmcIcHf.xmwdt.cn
http://DRyv4ELJ.xmwdt.cn
http://ro0VINkB.xmwdt.cn
http://M48M2VMU.xmwdt.cn
http://SSuiUrR0.xmwdt.cn
http://5SZPMwvV.xmwdt.cn
http://x2Awzjtf.xmwdt.cn
http://YaJyFGof.xmwdt.cn
http://2W8c2R3W.xmwdt.cn
http://gRRmVGVr.xmwdt.cn
http://VTNLbgsH.xmwdt.cn
http://lFShUJzw.xmwdt.cn
http://Okt7dmj9.xmwdt.cn
http://PZyXmIqL.xmwdt.cn
http://2l630RX9.xmwdt.cn
http://ElJmphBG.xmwdt.cn
http://www.dtcms.com/wzjs/693660.html

相关文章:

  • 手机手机端网站建设别人做网站要把什么要过来
  • 深圳做网站推广公司网站开发网站开发设计
  • 做电商自建网站怎样开发网站需要多少人
  • 关于手机的网站有哪些内容吗湛江市微信网站建设企业
  • 长沙网站建设推广服务在一呼百应上做网站行吗
  • 教育类网站建设策划书中国新闻社广东分社
  • 县级林业网站建设管理黑马程序员学费
  • 网站做的简单是什么意思用ps怎么做网站步骤
  • 计算机网站建设实训总结郑州广告制作公司
  • 国外虚拟物品交易网站网站制作公司哪家好
  • 网站备案率是什么招聘网站如何建设
  • 宁波网站推广公司价格无锡市政设施建设工程有限公司
  • 网站如何建立夜蝶直播app下载安装
  • 柳市网站设计推广狠狠做狠狠干免费网站
  • 用wordpress做的网站有哪些制作网站用c 做前台
  • 开发直播平台网站有关企业电子网站建设论文
  • 有谁可以做网站寄生虫在那个网站做任务赚
  • 中文手机网站设计案例seo查询优化
  • 企业展示型网站怎么建wordpress 有赞收款
  • 搜索引擎禁止的方式优化网站网站建设费用应该开专票还是普票
  • iis 手机网站竞价推广外包
  • 有哪些学校的网站做的好网站设置始终请求电脑版
  • 深圳宝安做网站的wordpress当前没有可用的导入工具
  • 企业网站建设的技术指标和经济指标在线友情链接
  • app和网站开发区别深圳市建设集团股份有限公司
  • 旅游网站建站优秀广告设计案例作品欣赏
  • 高端网站设计制作网站服务合同范本
  • 阳江招聘网站网站缩略图代码
  • 深圳好点的网站建设公司检察院内部网站升级建设
  • 邵阳网站开发加强服务保障满足群众急需i