【Spring】SpringBoot 统一功能处理

news2024/11/18 13:54:13

在这里插入图片描述

文章目录

  • 前言
  • 1. 拦截器
    • 1.1 什么是拦截器
    • 1.2 拦截器的使用
      • 1.2.1 自定义拦截器
      • 1.2.2 注册配置拦截器
    • 1.3 拦截器详解
      • 1.3.1 拦截路径
      • 1.3.2 拦截器执行流程
      • 1.3.3 适配器模式
  • 2. 统一数据返回格式
  • 3. 统一异常处理

前言

在日常使用 Spring 框架进行开发的时候,对于一些板块来说,可能需要实现一个相同的功能,这个功能可以是验证你的登录信息,也可以是其他的,但是由于各个板块实现这个功能的代码逻辑都是相同的,如果一个板块一个板块进行添加的话,开发效率就会很低,所以 Spring 也想到了这点,为我们程序员提供了 SpringBoot 统一功能处理的方法实现,我们是可以直接使用的。这篇文章我将带大家一起学习 SpringBoot 统一功能的处理。

1. 拦截器

正常的判断用户是否登录的逻辑就是通过 session 来判断,对于一些网站来说,很多的功能都是需要用户进行登录之后才可以使用的,如果此时通过 session 判断出来用户处于未登录状态的话,咱们的服务器会强制用户进行登录,通过代码来显示就是这样的:

//检验用户是否登录
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("userInfo") == null) {
    return "您未登录,请登录后再试试该功能吧";
}

我们想要在哪个功能前判断用户的登录信息,就需要在该功能的模块中添加上面这些代码,如果需要添加的功能较少还好,如果很多,那么就需要花费很多的时间,那么有人就说了:我是否可以将这些代码封装成函数,然后哪个模块需要使用只需要调用这些函数就可以了呢?可以是可以,但是使用函数封装代码还是需要在原代码的基础上调用这个函数,并且显得也不是那么优雅。那么是否有方法既不需要更改原代码,也可以使得我们写的代码很优雅呢?SpringBoot 为我们提供了一种功能——拦截器。

1.1 什么是拦截器

在 Spring Boot 中,拦截器是一种用于在处理请求之前或之后执行特定操作的组件。拦截器通常用于实现一些通用的功能,比如权限验证、日志记录等。拦截器可以拦截通过Controller的请求,并在请求处理前后执行特定的操作。

拦截器的思想正好符合我们执行其他功能之前进行身份验证验证,并且不仅在方法执行之前拦截器可以起作用,方法执行之后。我们的拦截器也可以起到作用。

在这里插入图片描述

1.2 拦截器的使用

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

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

1.2.1 自定义拦截器

我们自定义的拦截器需要实现 HandlerInterceptor 接口,并且重写这个接口中的方法。

package com.example.springbootbook2.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("目标方法执行前执行...");
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute("userInfo") == null) {
        	response.setStatus(401);
            return false;
        }
        return true;
    }

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

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

  • preHandle() 方法是在目标方法执行之前执行的,返回 true,继续执行后面的代码;返回 false,中断后续的操作
  • postHandle() 方法是在目标方法执行之后执行的
  • afterCompletion() 方法是在视图渲染之后才执行的,它还在 postHandle() 方法之后执行,并且现在因为前后端分离,我们后端基本上接触不到视图的渲染,所以这个方法使用的较少

1.2.2 注册配置拦截器

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

package com.example.springbootbook2.config;

import com.example.springbootbook2.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;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")  //指定路径配置拦截器
                .excludePathPatterns("/user/login");  //指定路径不配置拦截器
    }
}

在添加拦截器进行身份校验之前,我们可以直接通过对应的 url 访问到指定功能页面。

在这里插入图片描述

而在我们添加拦截器之后,再看是否能直接访问某些功能。

在这里插入图片描述
在这里插入图片描述

我们启动添加了拦截器之后的代码之后,点击刷新就发现不能够直接访问到某些功能了,再搭配着前端我们就可以实现强制登录的功能了。

1.3 拦截器详解

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

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

1.3.1 拦截路径

拦截路径是指我们定义的这个拦截器,对哪些请求生效,我们在注册配置拦截器的时候,通过 addPathPatterns() 方法指定要拦截哪些 HTTP 请求,也可以通过 excludePathPatterns() 方法指定不拦截哪些请求,上面我们的 /** 表示拦截所有的 HTTP 请求,除了可以设置拦截所有请求外,还有一些其他的拦截设置:

拦截路径含义举例
/*⼀级路径能匹配/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 等⽂件)。

知道了如何配置拦截路径之后,我们就可以解决网页上身份验证的问题了。因为 /** 会拦截所有的请求,包括前端页面请求,所以我们需要将前端页面请求排除在拦截之外。

如果我们不讲前端页面请求在外的话机会出现这种情况:

在这里插入图片描述
然后我们将前端页面请求不添加拦截器的话,就可以实现完整的身份校验强制登录功能了。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;

    private List<String> excludePath = Arrays.asList("/user/login",
            "/css/**",
            "/js/**",
            "/pic/**",
            "/**/*.html");  // /**/*html中的/**表示所有路径下的所有以html结尾的文件 * 表示通配符
            
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(excludePath);
    }
}

当我们未登录,然后访问其他功能的话,就会强制跳转到登录页面:

在这里插入图片描述

1.3.2 拦截器执行流程

正常的调用顺序是这样的:

在这里插入图片描述
有了拦截器之后,会在调⽤ Controller 之前进⾏相应的业务处理,执⾏的流程如下图:

在这里插入图片描述

  1. 当我们添加拦截器之后,在执行 Controller 方法之前,请求会先被拦截器拦截,执行 preHandle() 方法,这个方法会返回一个布尔类型的值,如果返回的是 true,那么会继续执行后面的操作;如果返回的是 false,则不会执行后面的操作
  2. Controller 当中的方法执行完毕之后,会继续执行 postHandle() 方法以及 afterHandle() 方法,执行完毕之后,返回给浏览器响应数据。

在源码中的 DispatcherServlet 类中的 doDispatch 方法中可以看到这三个方法的执行流程:

在这里插入图片描述

1.3.3 适配器模式

在拦截器执行的过程中还使用了适配器模式:

在这里插入图片描述
适配器模式(Adapter Pattern)在计算机编程中是一种常用的设计模式,主要用于解决两个不兼容的类之间的接口匹配问题。通过将一个类的接口转换成客户端所期望的另一种接口,原本因为接口不匹配而无法一起工作的两个类现在可以一起工作。

适配器模式的优点在于它能够使原本由于接口不兼容而无法一起工作的类一起工作,提高了系统的灵活性和可扩展性。同时,它也能够减少代码的重复性,因为多个源可以共享同一个适配器。

HandlerAdapter 主要⽤于⽀持不同类型的处理器(如 Controller、HttpRequestHandler 或者
Servlet 等),让它们能够适配统⼀的请求处理流程。这样,SpringMVC可以通过⼀个统⼀的接⼝
来处理来⾃各种处理器的请求。

在这里插入图片描述
本来 target 和 adaptee 是两个无法正常对接的事物,但是通过适配器 adapter,这两者之间就可以进行对接,也就类似于下面这个情况:

在这里插入图片描述
适配器模式⻆⾊:

  • Target:⽬标接口(可以是抽象类或接口)客户希望直接⽤的接口
  • Adaptee:适配者,但是与Target不兼容
  • Adapter:适配器类,此模式的核⼼。通过继承或者引⽤适配者的对象,把适配者转为⽬标接⼝
  • client:需要使⽤适配器的对象

前⾯学习的 slf4j 就使⽤了适配器模式,slf4j 提供了⼀系列打印⽇志的 api,底层调⽤的是 log4j 或者logback 来打⽇志,我们作为调⽤者,只需要调⽤ slf4j 的 api 就⾏了。

/**
* slf4j接⼝
*/
interface Slf4jApi{
	void log(String message);
}

/**
* log4j 接⼝
*/
class Log4j{
	void log4jLog(String message){
		System.out.println("Log4j打印:"+message);
	}
}

/**
* slf4j和log4j适配器
*/
class Slf4jLog4JAdapter implements Slf4jApi{
	private Log4j log4j;
	public Slf4jLog4JAdapter(Log4j log4j) {
		this.log4j = log4j;
	}
	@Override
	public void log(String message) {
		log4j.log4jLog(message);
	}
}

/**
* 客⼾端调⽤
*/
public class Slf4jDemo {
	public static void main(String[] args) {
		Slf4jApi slf4jApi = new Slf4jLog4JAdapter(new Log4j());
		slf4jApi.log("使⽤slf4j打印⽇志");
	}
}

可以看出,我们不需要改变 log4j 的api,只需要通过适配器转换下,就可以更换⽇志框架,保障系统的平稳运行。

⼀般来说,适配器模式可以看作⼀种"补偿模式",⽤来补救设计上的缺陷。应⽤这种模式算是"⽆奈之举",如果在设计初期,我们就能协调规避接⼝不兼容的问题,就不需要使⽤适配器模式了。

所以适配器模式更多的应⽤场景主要是对正在运⾏的代码进⾏改造,并且希望可以复⽤原有代码实现新的功能.。⽐如版本升级等。

2. 统一数据返回格式

如果我们完成了一个项目的开发,但是这时候,我们突然觉得后端的各个功能的返回值返回的信息不够全面,我们需要补充一些返回信息,那么这时候就意味着所有方法的返回值都需要做出修改,这也是一个不小的工作量。这时 SpringBot 又为我们提供了解决方法——统一数据返回格式。

SpringBoot 统一数据返回格式会在各个方法进行 return 返回值之前插入一些代码逻辑,从而达到改变返回值的功能。

统一的数据返回格式使用 @ControllerAdviceResponseBodyAdvice 的方式实现。ControllerAdvice 表示控制器通知类。

我们先定义一个统一的返回类型:

public enum ResultCode {
    SUCCESS(0),
    FAIL(-1),
    UNLOGIN(-2);

    //0-成功  -1 失败  -2 未登录
    private int code;

    ResultCode(int code) {
        this.code = code;
    }


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}
@Data
public class Result<T> {
    /**
     * 业务状态码
     */
    private ResultCode code;  //0-成功  -1 失败  -2 未登录
    /**
     * 错误信息
     */
    private String errMsg;
    /**
     * 数据
     */
    private T data;

    public static <T> Result<T> success(T data){
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        result.setErrMsg("");
        result.setData(data);
        return result;
    }
    public static <T> Result<T> fail(String errMsg){
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setErrMsg(errMsg);
        result.setData(null);
        return result;
    }
    public static <T> Result<T> fail(String errMsg,Object data){
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }
    public static <T> Result<T> unlogin(){
        Result result = new Result();
        result.setCode(ResultCode.UNLOGIN);
        result.setErrMsg("用户未登录");
        result.setData(null);
        return result;
    }

}
package com.example.springbootbook2.config;

import com.example.springbootbook2.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);
    }
}

继承 ResponseBodyAdvice 接口后,需要实现该接口下的 supports 方法和 beforeBodyWrite 方法,supports 方法只需要更改返回值为 true 就可以了,表示是否要执行 beforeBodyWrite 方法,返回 true 表示执行,false 表示不执行,beforeBodyWrite 方法中的 body 参数就是我们原方法的返回值。

当我们关闭拦截器,访问图书列表页之后,就发现我们的返回类型发生了变化:

在这里插入图片描述
但是这个统一数据返回格式也存在问题,当我们的返回值为 String 类型的话机会出现错误:

在这里插入图片描述
在这里插入图片描述
这是为什么呢?SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter (从先后顺序排列分别为
ByteArrayHttpMessageConverter ,StringHttpMessageConverter , SourceHttpMessageConverter ,AllEncompassingFormHttpMessageConverter )
在这里插入图片描述
其中 AllEncompassingFormHttpMessageConverter 会根据项⽬依赖情况添加对应的
HttpMessageConverter

在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述
然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String,此
时t为Result类型,所以出现类型不匹配"Result cannot be cast to java.lang.String"的异常。

那么如何解决返回类型为 String 类型的问题呢?如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化。

@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));
        }else if (body instanceof Result) {
            return body;
        }else {
            return Result.success(body);
        }
    }
}
  • @SneakyThrows 的主要目的是解决 Java 的异常处理问题。当我们在代码中抛出一个异常时,如果这个异常被包裹在一个方法中,并且这个方法没有 throws 关键字来声明会抛出这个异常,那么编译器会报错。通过使用 @SneakyThrows,你可以告诉编译器:“我知道这个方法可能会抛出异常,但我保证在 catch 块中处理它。” 这样编译器就不会报错了。

通过这个处理,当返回的数据类型为 String 的时候就不会出现错误了。

在这里插入图片描述
统一数据返回格式的优点:

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

3. 统一异常处理

当我们的程序中出现异常的时候,我们需要解决这些异常,因为前面做了统一数据返回格式的处理,所以这里的异常也可以进行统一的处理。

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

package com.example.springbootbook2.config;

import com.example.springbootbook2.model.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@Slf4j
@ResponseBody  //因为返回的数据都不是视图类型,所以加上这个注解防止出现问题
public class ErrorHandler {
    @ExceptionHandler
    public Result<String> exception(Exception e) {
        log.error("发生异常:e{}",e);
        return Result.fail("内部异常");
    }

    @ExceptionHandler
    public Result<String> exception(NullPointerException e) {
        log.error("发生异常:e{}",e);
        return Result.fail("NullPointerException异常,请联系管理员");
    }

    @ExceptionHandler
    public Result<String> exception(ArithmeticException e) {
        log.error("发生异常:e{}",e);
        return Result.fail("ArithmeticException异常,请联系管理员");
    }
}

@Controller
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public Integer test1() {
        return 10/0;
    }
}

在这里插入图片描述

这个 Exception 和 ArithmeticException 的先后顺序可以不考虑,这个不会因为 Exception 在前面捕获就报的 Exception 异常,而是会根据自己出现的最接近的异常来捕获。

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

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

相关文章

flutter给组件设置背景图的操作

可以设置背景图的组件只有一个&#xff0c;那就是Container容器&#xff0c;要想设置背景图&#xff0c;可以使用网路图片&#xff0c;也可以使用本地图片&#xff0c;要是使用本地图片&#xff0c;需要在本地添加一个资源路径&#xff0c;用来管理这些文件&#xff0c;在本地项…

若依框架实现排序【升序或降序】很简单

前端实现 1. 在表格上加监听函数sort-change。如下红框所示&#xff1a; 2. 在表行上加排序字:sort-orders&#xff0c;可排序字sortable。如下红框所示&#xff1a; 3. 添加监听函数实现。代码如下&#xff1a; handleSortChange(column) {this.queryParams.orderByColumn …

【深入理解 ByteBuf 之三 接口类拆解】2. Recycler 接口设计真正的回收机制

Recycler 回收器接口设计 本节接着 ObjectPool 的设计脉络&#xff0c;具体看看其具体实现 RecyclerObjectPool 中引用的 Recycler 究竟是怎么实现的 这一张图基本已经说明白了&#xff0c;我再做个总结&#xff0c;对细节感兴趣的可以看看我下面带源码的注释。 对于 Recycle…

debian 11 arm64 aarch64 源码变异winehq arm64 笔记

安装华为毕昇编译器 sudo apt install libc1-13 编译tools cd tools su root export PATH/opt/bisheng-compiler-1.3.3-aarch64-linux/bin:$PATH rootdebian:/home/yeqiang/下载/src/wine/tools# ../configure CC/opt/bisheng-compiler-1.3.3-aarch64-linux/bin/clang C…

职场硬货:刚入职面对陌生的被测系统, 没有需求文档如何快速熟悉?

各位小伙伴大家好, 今天为大家分析一下在企业中, 如果快速的上手被测系统/软件, 了解产品目标业务需求, 做到尽快尽职完成测试工作。 找到所有可能的相关文档 尽可能找到项目开发计划书, 项目签订的合同, 一般合同中会包含项目研发背景, 与产品及功能点概述, 这样可以先了解项…

一文速学-selenium高阶性能优化技巧

一文速学-selenium高阶性能优化技巧 前言 最近写的挺多自动化办公的selenium程序没有做优化&#xff0c;执行效率不高&#xff0c;启动浏览器又慢但是又可能出现其他不可控的因素&#xff0c;总结来说虽然放心运行但是又没那么好用&#xff0c;项目是写完了最后还是需要优化结…

Hive分区表实战 - 多分区字段

文章目录 一、实战概述二、实战步骤&#xff08;一&#xff09;创建学校数据库&#xff08;二&#xff09;创建省市分区的大学表&#xff08;三&#xff09;在本地创建数据文件1、创建四川成都学校数据文件2、创建四川泸州学校数据文件3、创建江苏南京学校数据文件4、创建江苏苏…

在线项目实习|2024寒假项目实战火热报名中!

一、在线实习项目分类 二、在线实习项目流程 三、在线实习项目优惠及项目特色 1、师傅带练教学模式&#xff0c;手把手教你掌握 采用“师带徒”的教学模式&#xff0c;课程以“项目前置知识学习 师傅带练 项目实战”贯穿&#xff0c;强调动手实操&#xff0c;内容以代码落地为…

反向代理的本质是什么?

反向代理是一种网络架构模式&#xff0c;通常用于提供静态内容、处理安全、负载均衡和缓存等任务。在这种架构中&#xff0c;客户端发送的请求首先到达反向代理服务器&#xff0c;然后由反向代理服务器将请求转发给后端的实际服务器。反向代理服务器可以处理和修改请求和响应&a…

命名空间 “Eigen“ 没有成员 “SelfAdjointEigenSolver“

代码中用到SelfAdjointEigenSolver 结果报错&#xff1a;报错实在windows10条件下发生的。 查找资料&#xff0c;最后还是要定位到官方文档。 计算自伴随矩阵的特征值和特征向量。 这是在特征值模块中定义的。 添加如下引用即可解决&#xff0c;请点赞关注。 #include <…

2023 年公链发展报告

作者&#xff1a;stellafootprint.network 2023 年&#xff0c;公链领域展现出强大的韧性和持续的创新力。这一年&#xff0c;比特币的强势回归、以太坊的稳步增长以及 Solana 的惊人崛起&#xff0c;共同绘制出一幅市场复苏的生动画面。在这一背景下&#xff0c;公链加密货币…

华媒舍:高效率的新闻资讯新闻媒体宣发套餐内容推广计划方案

怎样让自己的新闻资讯可以被大众孰知&#xff0c;变成了每一个新闻媒体宣发者一同存在的困难。下面我们就给大家介绍一套高效率的新闻资讯新闻媒体宣发套餐内容推广计划方案&#xff0c;致力于帮助新闻媒体宣发者提升宣发高效率&#xff0c;提高新闻资讯的传播性。 1.新闻媒体宣…

2023年全国电子签章政策汇总,契约锁提供具有法律效力的电子签章

2023年&#xff0c;国务院及各地政府办公厅、市监局、住建委等机关部门&#xff0c;持续推动电子签章、电子合同等功能在“政府采购、工程项目审批、企业开办等”领域深化应用&#xff0c;加快实现电子签章互信互认&#xff0c;不断简化办事流程&#xff0c;让越来越多高频常办…

【mars3d】 graphic.bindPopup(inthtml).openPopup()无需单击小车,即可在地图上自动激活弹窗的效果。

实现效果&#xff1a;new mars3d.graphic.FixedRoute({无需单击小车&#xff0c;即可在地图上实现默认打开弹窗的激活效果。↓↓↓↓↓↓↓↓ 相关链接说明&#xff1a; 1.popup的示例完全开源&#xff0c;可参考&#xff1a;功能示例(Vue版) | Mars3D三维可视化平台 | 火星科…

QT 原生布局和QML的区别

一、QML 与 Qt Quick的区别 1.1 从概念上区分 为了更精确地对两者进行说明&#xff0c;先看助手对 QML 的描述&#xff1a; QML is a user interface specification and programming language. QML 是一种用户界面规范和标记语言&#xff0c;允许开发人员和设计师创建高性能、流…

央视推荐的护眼灯是哪款?教育部认可护眼灯品牌

许多家长一般都会给孩子买上许多学习用品&#xff0c;比如现在一些学习桌椅、读写笔灯等等&#xff0c;配有蛮多的学习用具&#xff0c;但对孩子学习时用的护眼台灯很忽略&#xff0c;没有给孩子选好真正合格好用的护眼台灯&#xff0c;就容易让孩子的视觉形成偏差&#xff0c;…

外汇天眼:分析K线背后的力量,别让自己只停留在画线的阶段!

不确定性是市场的本质&#xff0c;也是它魅力所在。 大部分学习技术分析的新手以及亏货老手所能理解的技术分析就是在已经走完的图表上画线&#xff0c;或者研究K线形态&#xff0c;组合形态&#xff0c;等等...... 然后根据画的线和形态来预测未来走势&#xff0c;并依据这个…

深入理解 go RWMutex

在上一篇文章《深入理解 go Mutex》中&#xff0c; 我们已经对 go Mutex 的实现原理有了一个大致的了解&#xff0c;也知道了 Mutex 可以实现并发读写的安全。 今天&#xff0c;我们再来看看另外一种锁&#xff0c;RWMutex&#xff0c;有时候&#xff0c;其实我们读数据的频率要…

深度探讨 Golang 中并发发送 HTTP 请求的最佳技术

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 在 Golang 领域&#xff0c;并发发送 HTTP 请求…

浊度水质分析仪的功能特性,及其在环境监测中的重要作用

在环境保护和水资源管理领域&#xff0c;对水质的精准监测是确保水体健康、保障公众用水安全的重要环节。其中&#xff0c;浊度作为衡量水体中悬浮物含量的关键参数&#xff0c;其精确测量对于环境评价和治理至关重要。浊度水质分析仪正是这样一款专门针对浊度进行实时在线监测…