【JavaEE进阶】拦截器与统一功能处理

news2024/10/7 4:29:05

文章目录

  • 一. 用户登录权限效验
    • 1. 最初用户登录验证
    • 2. Spring AOP 用户统一登录的验证
    • 3. Spring拦截器
      • 3.1 自定义拦截器
      • 3.2 将自定义拦截器设置到当前的项目中
    • 4. 拦截器实现的原理
  • 二. 统一的异常处理
  • 三. 统一数据返回格式
    • 1. 统一数据返回格式的优点
    • 2. 统一数据返回格式的实现
      • 统一数据返回(强制性)[在数据返回前进行重新修改]
  • 小结

AOP思想的体现主要分为两个方面:

  1. Spring AOP用于分离功能性需求和非功能性需求,使得开发人员可以集中处理某一个关注点,减少对业务代码的侵入,增强代码的可读性和可维护性.
  2. SpringBoot的统一功能处理模块.

一. 用户登录权限效验

1. 最初用户登录验证

我们最初对用户登录验证的实现是这样的:

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/user")
public class UserController {
    //执行的方法1
    @RequestMapping("/m1")
    public Object method1(HttpServletRequest request){
        //有 session 就创建,没有 session 就不会创建
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("userinfo") != null){
            //说明已经登录,执行业务处理
            return true;
        }else {
            //未登录
            return false;
        }
    }
    //执行的方法2
    @RequestMapping("/m1")
    public Object method2(HttpServletRequest request){
        //有 session 就创建,没有 session 就不会创建
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("userinfo") != null){
            //说明已经登录,执行业务处理
            return true;
        }else {
            //未登录
            return false;
        }
    }
    //其他需要执行的方法...
}

从上述代码可以看出,每个方法的执行都有用户登录验证权限,它的缺点如下:

  1. 每个方法中都单独写用户登录验证的方法,即使封装成公共方法,也是一样要传参调用和在方法中进行判断.
  2. 添加控制器越多,调用用户登录验证的方法就越多,这样就增加了后期的修改成本和维护成本.
  3. 这些用户登录验证的方法和下面要执行的业务代码没有什么关系,但是在每个方法中都实现了一遍.

所以接下来我们提供一个公共的AOP方法来进行统一的用户登录权限验证.

2. Spring AOP 用户统一登录的验证

使用SpringAOP的具体实现代码如下:

package com.example.demo.common;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
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. 我们要对一部分方法进行拦截,而另一部分方法不拦截,如注册方法和登录方法是不拦截的,这样的话排除方法的规则很难定义,甚至没办法定义。

那么我们应该如何解决呢?

3. Spring拦截器

针对以上问题,Spring中提供了具体的实现拦截器:HandlerInterceptor.拦截器的实现分为两个部分:

  1. 创建自定义拦截器,实现HandlerInterceptor接口的preHandle(执行具体方法之前的预处理)方法.
  2. 将自定义拦截器加入WebMvcConfigureraddInterceptors方法中.

具体实现如下:

3.1 自定义拦截器

实现一个UserInterceptor用户拦截器类,在该类中实现HandlerInterceptor接口,再重写preHandle方法
在这里插入图片描述

package com.example.demo.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Component
public class UserInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //业务方法
        //从请求中取session,如果有session,直接获取到,但是没有,这里设置为false,也不会新创建一个session。
        HttpSession session  = request.getSession(false);//这里添加false表示不会新创建session。方法中默认的是true。
        if(session!=null && session.getAttribute("userinfo")!=null){
            return true;
        }
        response.setStatus(401);//返回一个404
        return false;
    }
}

getAttribute方法是Object类中的方法,用于获取对象的指定属性值,它接受一个参数,即要获取的属性的名称,并返回该属性的值,如果对象中不存在指定名称的属性,则返回null。该方法可以用于获取对象的任意属性,包括实例变量和静态变量。

3.2 将自定义拦截器设置到当前的项目中

实现一个AppConfig类用来配置,实现WebMvcConfigurer接口,然后重写其中的addInterceptor方法.
在这里插入图片描述

package com.example.demo.config;

import com.example.demo.interceptor.UserInterceptor;
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 AppConfig implements WebMvcConfigurer {
    @Autowired
    private UserInterceptor userInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userInterceptor)
                .addPathPatterns("/**")// 表示拦截所有的请求
                .excludePathPatterns("/user/reg")//排除不拦截的url
                .excludePathPatterns("/user/login")//排除不拦截的url
        ;
    }
}

这里的addInterceptor方法接受一个参数,就是要添加的拦截器对象。可以通过该方法添加一个或多个拦截器。
我们可以写一个UserController1测试类看一下运行结果:

package com.example.demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/user")
public class UserController {


    @RequestMapping("/reg")
    public String reg(){
        return "do reg.";
    }
    @RequestMapping("/login")
    public String login(){
        return "do login.";
    }
    @RequestMapping("/test")
    public String test(){
        return "do Test.";
    }
}

由于我们的拦截器中拦截了所有的请求但是除了/user/reg /user/login方法,所以这两个方法可以执行成功.而我们的Test方法则被拦截,返回401状态码.
在这里插入图片描述
当我们想要排除所有的静态文件,静态文件包含图片文件,前端的JS和CSS等文件,这个时候我们不可能将每种格式的文件都手动进行排除,这样工作量也太大了(图片文件存在几十种格式),想要将这些文件排除掉我们可以将这个静态文件放入项目的static目录中,然后针对这个目录中的子目录(图片,css文件,js文件)进行排除。

excludePathPatterns("/image/**")//表示排除image目录下的所有图片

在这里插入图片描述

4. 拦截器实现的原理

没有实现拦截器的时候,用户发送的请求直接被控制层接收到,进而在相应的URL中进行登录校验,这种方式代码的可维护性较低。

但是使用拦截器,用户发送的请求首先会被拦截器接收到,拦截器进行预处理,符合条件才会进一步调用Controller层的方法。
在这里插入图片描述

二. 统一的异常处理

我们之前处理异常的方法就是使用try-catch,或者是将异常抛出去给更上一层处理,这种方式处理异常的方式通常是分散在代码的各个部分中的,当应用程序出现异常时,开发需要在每个可能抛出异常的地方编写相应的异常处理代码,这样做会导致代码冗余,可读性差,并且难以维护。
而使用统一的异常处理就可以:

  1. 集中处理异常:通过使用统一的异常处理机制,可以集中处理应用程序中的异常。这意味着无论在哪个控制器方法或服务方法中抛出异常,都可以在统一的地方进行处理,从而减少代码冗余。
  2. 统一错误响应:统一的异常处理机制可以确保应用程序返回一致的错误响应给客户端。这样做可以提高用户体验,让客户端能够更容易地理解和处理错误情况。
  3. 异常日志记录:通过统一的异常处理,可以方便地实现异常的日志记录。可以在异常处理器中添加日志记录的逻辑,记录异常的详细信息、发生时间和相关的上下文信息,以便后续的错误分析和故障排查。
  4. 异常转换和封装:统一的异常处理机制还可以进行异常的转换和封装。例如,可以将底层框架或第三方库的异常转换为应用程序定义的自定义异常,以简化异常的处理和管理。
  5. 统一的异常处理策略:通过统一的异常处理,可以定义全局的异常处理策略。可以根据不同的异常类型采取不同的处理方式,例如返回特定的错误码、跳转到指定的错误页面或执行其他自定义逻辑。

在Spring Boot中,可以使用@RestControllerAdvice注解和@ExceptionHandler注解来实现统一异常处理。这两个注解搭配使用表示的是全局异常处理,可以捕获并处理全局范围内的异常。当控制器中抛出异常时,会根据异常类型匹配对应的@ExceptionHandler方法进行处理。
Exception类是Java中所有异常类的父类。

  • @RestControllerAdvice注解用在一个类上,表示该类是一个全局的控制器增强器,可以对所有的控制器进行统一的处理。这个注解提供了一种集中管理和统一处理全局范围内操作的方式,在引用程序中起到了很好的代码复用和统一管理的作用。
  • @ExceptionHandler注解,用于定义一个方法,**该方法用于处理控制器中发生的异常。**当控制器中的方法抛出异常时,@ExceptionHandler注解标记的方法将被调用来处理该异常。这样可以集中处理控制器中的异常。
  1. 统一返回结果 首先需要定义一个类,在这个类中设置属性,用来定义一个统一返回结果格式。
package com.example.demo.common;
 
import lombok.Data;
 
@Data
public class ResultAjax {
    private int code;//状态码
    private String msg;//状态码的描述
    private Object data;//返回数据
}
  1. 创建一个全局异常处理类

使用@RestControllerAdvice注解来标记它。也可以使用@ControllerAdvice+@ResponseBody注解来定义这个类,@ResponseBody注解表示返回的结果为数据而不是页面。在这个@RestControllerAdvice标记的类中用@ExceptionHandler注解标记的方法是一个处理器,用来集中处理控制器中的异常.

import com.example.demo.common.ResultAjax;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
 
@RestControllerAdvice
@ResponseBody
public class ExceptionAdvice {
 
    @ExceptionHandler(Exception.class)
    public ResultAjax doNullPointerException(Exception e){
        ResultAjax resultAjax = new ResultAjax();
        resultAjax.setCode(-1);
        resultAjax.setMsg("异常信息:"+e.getMessage());
        resultAjax.setData(null);
        return resultAjax;
    }
}

这里由于异常的情况存在很多种,我们使用Exception,所有的异常都可以使用它捕获到。

三. 统一数据返回格式

1. 统一数据返回格式的优点

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

总结起来,统一数据返回格式可以提高接口的规范性、可读性和可维护性,方便异常处理,支持扩展和版本控制,并增强系统的兼容性。这些优点都有助于提高开发效率、减少错误和提升用户体验。

2. 统一数据返回格式的实现

统一的数据返回格式可以使用@ControllerAdvice+ResponseBodyAdvice的方式实现,实现步骤如下:

  1. 创建一个类,并添加@ControllerAdvice注解,让这个类对所有的控制器可以统一处理。
    在这里插入图片描述
  2. 创建异常检测的类和处理业务方法:
package com.example.demo;

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 MyExpectionAdvice {
    @ExceptionHandler(NullPointerException.class)
    public HashMap<String,Object> doNullPointerExpection(NullPointerException e){
        HashMap<String,Object> result = new HashMap<>();
        result.put("code",-1);
        result.put("msg","空指针"+e.getMessage());
        result.put("data",null);
        return result;
    }
}

写个UserController测试一下:

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/login")
    public int login() {
        //空指针异常
        Object obj = null;
        System.out.println(obj.hashCode());
        return 1;
    }
}

在这里插入图片描述
此时我们就可以告诉前端异常的类型.上述代码我们处理了空指针异常,通常情况下,我们无法预测代码会抛出什么异常.所以我们可以使用所有异常的父类Expection来处理:

    //默认的异常处理
    @ExceptionHandler(Exception.class)
    public HashMap<String, Object> doException(Exception e) {
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", -300);
        result.put("msg", "Exception:" + e.getMessage());
        result.put("data", null);
        return result;
    }

那么上述doException方法也可以处理空指针异常,当上述两个处理异常的方式同时存在时,首先采用的是doNullPointerExpection:(有子类先开始处理子类,再处理父类)
在这里插入图片描述

统一数据返回(强制性)[在数据返回前进行重新修改]

实现ResponseBodyAdvice接口.并重写其中的方法supports beforeBodyWrite方法:
值得注意的是在此类中不需要加入@ResponseBody注解,这是因为在该类中只是对返回值进行转换.

package com.example.demo.controller;

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 {

    //是否执行 beforeBodyWrite 方法,true=执行,重写返回结果
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }
    //返回数据之前进行数据重写
    //@param body :原始返回值
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // HashMap<String,Object>  ->  code,msg,data
        if (body instanceof HashMap) {
            return body;
        }
        // 重写返回结果,让其返回一个统一的数据格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", body);
        result.put("msg", "");

        return result;
    }
}

写个测试方法测试一下:

    @RequestMapping("/test")
    public int test(){
        return 666;
    }

在这里插入图片描述
可以看到,上述结果返回的是我们指定的数据格式.
统一移除处理在遇到String返回时报错问题:
但是上述返回值有一个问题,即如果返回的类型是String类型时会报错:
测试方法:

    @RequestMapping("/test1")
    public String test1(){
        return "dotest1";
    }

运行结果:
在这里插入图片描述

要注意其返回的流程:

  1. 方法返回的是String
  2. 统一数据返回之前处理-> String Convert HashMap
  3. 将 HashMap 转换成 application/json 字符串给前端(接口)

显然,Exception:java.util.HashMap cannot be cast to java.lang.String是第三步出现了问题.第三步在执行的时候会判断Body的类型,如果是String类型,那么执行StringHttpMessageConverter进行类型转换;如果不是String类型,那么执行HttpMessageConverter进行类型转换.问题就出在了将HashMap 转换成 application/json 字符串给前端(接口).针对以上问题,有两种解决方式:

  1. StringHttpMessageConverter去掉:
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class MyConfig implements WebMvcConfigurer {
    //移除 StringHttpMessageConverter
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.removeIf(converter -> converter instanceof StringHttpMessageConverter);
    }
}
  1. 在统一数据重写时,单独处理String 类型,让其返回一个String 字符串,而非 HashMap
 @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        // HashMap<String,Object>  ->  code,msg,data
        if (body instanceof HashMap) {
            return body;
        }
        // 重写返回结果,让其返回一个统一的数据格式
        HashMap<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", body);
        result.put("msg", "");
        if(body instanceof String){
            // 返回一个 将对象转换成 JSON String 字符串
            return objectMapper.writeValueAsString(result);
        }
        return result;
    }

小结

自定义拦截器的实现:使用HandlerInterceptor接口+WebMvcConfigurer接口实现。
统一异常的处理:使用@RestControllerAdvice注解+@ExceptionHandler注解实现。
统一数据返回格式:使用@ControllerAdvice注解+ResponseBodyAdvice接口实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/947306.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vim编辑器的基本使用复习

之前写过vim的使用&#xff0c;还不是太熟悉&#xff1b;复习一下&#xff0c; 在MSYS2下使用vim&#xff1b;先安装&#xff1b;单安装vim的话非常快&#xff1b; 输入 vim hello.c&#xff0c;进入文件hello.c的编辑&#xff1b;刚进入时是在vim的命令模式&#xff0c;不能输…

计算机视觉主要任务

计算机视觉&#xff1a;使用计算机及相关设备对生物视觉的一种模拟。 主要包含6大任务&#xff0c;图像分类&#xff0c;目标检测&#xff0c;目标跟踪&#xff0c;语义分割&#xff0c;实例分割&#xff0c;影像重构。 图像分类&#xff1a;根据图像信息中所反映的不同特征&am…

[Android 四大组件] --- Service

1 service是什么 Service是Android系统中的四大组件之一&#xff0c;它是一种长生命周期的&#xff0c;没有可视化界面&#xff0c;运行于后台的一种服务程序。 2 service分类 3 service启动方式 3.1 startService显示启动 // AndroidManifest.xml<?xml version"1…

2023第七届蓝帽杯 初赛 web LovePHP

LovePHP 直接给了源码。 network查看到&#xff0c;PHP版本是7.4.33 题目要求我们GET一个my_secret.flag参数&#xff0c;根据PHP字符串解析特性&#xff0c;PHP需要将所有参数转换为有效的变量名&#xff0c;因此在解析查询字符串时&#xff0c;它会做两件事&#xff1a; 删…

各个微服务模块之间互相依赖调用的问题

首先是模块之间不能够循环引用&#xff0c;否则会报循环依赖引入的错误。 没有了模块之间的相互依赖&#xff0c;在项目中这两个模块是相互调用的&#xff0c;分别各自定义相应的Feign接口&#xff0c;如下&#xff1a; 最开始写的运行报错的代码如下&#xff1a; FeignCli…

概念解析 | 量子机器学习:将量子力学与人工智能的奇妙融合

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:量子机器学习。 量子机器学习:将量子力学与人工智能的奇妙融合 量子增强机器学习:量子经典混合卷积神经网络 量子机器学习是量子计算和机器学习的结合,它利用量子力学的特…

Python小知识 - 一个简单的Python爬虫实例

一个简单的Python爬虫实例 这是一个简单的Python爬虫实例&#xff0c;我们将使用urllib库来下载一个网页并解析它。 首先&#xff0c;我们需要安装urllib库&#xff1a; pip install urllib接下来&#xff0c;我们来看看如何使用urllib库来下载一个网页&#xff1a; import url…

Mybatis学习笔记(三)——Mybatis的配置(Mybatis-config.xml)

Mybatis学习笔记&#xff08;三&#xff09;——Mybatis的配置&#xff08;Mybatis-config.xml&#xff09; 传送门&#xff1a;Mybatis中文网——配置 Mybatis配置文档的顶层结构&#xff1a; configuration&#xff08;配置&#xff09; properties&#xff08;属性&#…

软考A计划-网络工程师-复习背熟-网络管理和计算机基础知识

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

基于SpringBoot+Vue的旅游系统

摘 要 随着旅游业的发展&#xff0c;越来越多的人选择旅游作为自己的出行方式。在旅游规划过程中&#xff0c;旅游景点选择是至关重要的环节。本文提出了一种基于协同过滤推荐算法的旅游平台系统。该系统采用前后端分离的设计&#xff0c;主要使用了SpringBoot、Vue等技术&…

springmvc没有绿标,怎么配置tomcat插件运行?

一、添加插件后&#xff0c;刷新&#xff0c;自动从maven仓库下载tomcat插件 二、写好项目后&#xff0c;添加tomcat配置 三、即可点击绿标运行

2024王道408数据结构P144 T17

2024王道408数据结构P144 T17 思考过程 先看题目&#xff0c;让我们判断两棵二叉树是否相似&#xff0c;相似指的是以下三个方面&#xff1a; T1和T2都是空的二叉树或T1和T2都只有一个结点T1的左子树和T2的左子树是相似的&#xff0c;且T1的右子树和T2的右子树是相似的。 题…

61.linux系统上c程序的编译与调试

目录 1.检查GCC是否已经安装&#xff1a;​编辑 2.使用包管理器来安装gcc: 3.c程序执行需要经过四个步骤 4.make和makefile 5.gdb调试 基础调试命令 一些示例 对于在Linux系统上编译和调试C程序&#xff0c;首先&#xff0c;需要确保已经安装了合适的编译器。在大多数…

均匀性校准积分球光源

随着LED半导体照明技术的发展和LED半导体照明产业的不断壮大&#xff0c;合理有效的LED 灯具或芯片的光度、色度检测方法是支撑半导体照明产业发展的重要技术基础&#xff0c;同时也 为积分球内部照明产品的提升提供了重要的技术保障。 在物理世界中&#xff0c;存在着各种各样…

elementui tree 层级过多时,高亮状态无法选满整行

问题&#xff1a; 如上图所示&#xff0c;官方的tree组件&#xff0c;在层级很多时 elementui -tree 的高亮状态并没有选中整行。 &#xff08;衍生库 vue-easy-tree 也会出现此问题&#xff09; 原因&#xff1a; &#xff08;没有查看源码&#xff0c;只是根据dom简单定位…

“亚马逊云科技创业加速器”首期聚焦AI,促进入营企业业务发展

生成式AI技术飞速发展&#xff0c;颠覆着人们的生活&#xff0c;正在掀起新一轮的科技革命。在生成式AI的浪潮中&#xff0c;亚马逊云科技旨在为中国的优秀初创企业提供全方位支持&#xff0c;助其抢占先机。 在6月底举办的亚马逊云科技中国峰会上&#xff0c;亚马逊云科技联合…

SingleCellExperiment and SummarizedExperiment

这里的两个是不一样的 http://home.cc.umanitoba.ca/~psgendb/birchhomedir/R/x86_64-redhat-linux-gnu-library/3.4/SummarizedExperiment/html/SummarizedExperiment-class.html创建SummarizedExperiment nrows <- 200; ncols <- 6 counts <- matrix(runif(nrows …

什么是模块化编程?如何在JavaScript中实现模块化?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 模块化编程⭐ CommonJS 模块导出模块导入模块 ⭐ ES6 模块导出模块导入模块 ⭐ AMD 和 RequireJSAMD 模块 ⭐ UMD&#xff08;Universal Module Definition&#xff09;⭐ 小结⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开…

万物流动 万物永驻 ——C++ Core Guidelines的流动哲学

众所周知&#xff0c;C 是一门自由的语言&#xff0c;语言的设计哲学之一就是赋予程序员极大的自由度和灵活性&#xff0c;因此&#xff0c;使用C 完成一个任务时&#xff0c;不同的程序员往往会有不同的实现方法&#xff0c;这真正阐释了什么叫条条大路通罗马。不过&#xff0…

【SaaS】你知道什么是SaaS吗?

文章目录 前言一、云服务架构的三个概念1.1 PaaS1.2 IaaS1.3 SaaS 二、SaaS系统的两大特征三、SaaS服务与传统服务、互联网服务的区别3.1 SaaS服务3.2 传统软件3.3 互联网应用供应商 四、B2B2C五、SaaS系统的分类5.1 业务型SaaS5.2 效率型SaaS5.3 混合型SaaS 六、如何SaaS化七、…