使用SpringEvent解决WebUploader大文件上传解耦问题

news2024/11/16 22:23:59

目录

前言

一、SpringEvent涉及的相关组件

1、 事件(Event)

2、事件监听器

3、事件发布器

二、WebUploader大文件处理的相关事件分析

1、事件发布的时机

2、事件发布的代码

三、事件监听器及实际的业务处理

1、文件上传处理枚举

2、文件上传监听器的实现

3、文件具体处理逻辑

4、实际处理实例

 四、总结


前言

        关于Spring的Event机制,相信使用Java开发的朋友们一定非常熟悉。Spring Event是Spring框架内建的一种发布/订阅(Publish-Subscribe)模式的实现,它允许应用内部不同组件之间通过事件进行通信。当某个特定事件发生时,系统中对这类事件感兴趣的监听器可以接收到通知并执行相应操作。是不是看起来跟消息队列差不多,尤其是这种发布/订阅的模式,确实非常符合消息中间件的模式。通常来说,消息队列一般有以下几种作用。异步、解耦和削峰。不过请大家注意,之所以在这里讲解SpringEvent,在一般的中小型项目中,我们的部署节点是单个,技术的架构选型一般也是单体架构。因此我们可以在不引入复杂架构的前提下来实现一个简单版本的消息队列。通过发布订阅的模式来进行应用程序解耦,让各个功能组件更加符合实际架构的布置。从而让程序扩展起来更方便。以上是关于SpringEvent这个框架的知识,更多的关于SpringEvent的相关知识,大家感兴趣的可以登录spring的官方网站来进行查询。

        除了Spring的Event机制之外,在我们日常的项目开发过程中,肯定会遇到大文件上传的处理场景,而大文件的处理通常是需要蛮长的时间。同时,不同的场景甚至不同的业务,对于上传附件的处理是不尽相同的。比如在用户信息Excel的上传处理中,不仅需要将当前的Excel数据进行关联绑定,同时还需要解析数据后,将Excel提交的数据结果存储到数据库中。而另外一个类型,比如单据生成的任务表格,就需要根据单据信息,匹配不同的模板来生成不同的任务。由此种种需求,要求我们的应用程序在开发过程中具有较大的扩展性,可以支持将不同的应用程序快速的切入到应用中,同时能针对不同的场景灵活开发。极端的情况下,甚至需要同一种业务,根据不同的状态来定制附件的读取需求。

        本文以WebUploader大文件上传组件为例,在大文件处理的场景中使用SpringEvent的事件发布机制,灵活的扩展对文件的处理需求。本文通过代码实例的讲解,让您快速的了解如何在Spring中快速开发Event应用程序,同时使用枚举来实现动态的注册过程,实现方便灵活的注册机制。最后结合一个具体的场景详细说明在学生信息附件上传中来进行附件处理的过程,方便您掌握上述的知识点。

一、SpringEvent涉及的相关组件

        为了让不熟悉SpringEvent的朋友对Event也有一个大致的印象。这里还是对SpringEvent对象包含的方法和相关组件的应用进行简单的介绍。

1、 事件(Event)

        事件(Event)事件是应用程序中发生的某种事情,可以是用户行为、系统状态改变等。在Spring中,事件通常表示为一个Java类,它包含了与事件相关的信息。如果大家做过GUI界面的实际与实现,或者进行过Web界面的开发,相信对事件机制一定非常熟悉。比如鼠标点击事件、鼠标双击事件、鼠标拖拽事件、鼠标悬浮事件等等。事件一定是经过触发的,由某一种设备或者事务来进行触发,从而形成某种事件。在本文的场景中,文件上传后在服务器端进行合成是一种事件。

2、事件监听器

        事件监听器(Event Listener): 事件监听器是一段代码,它等待并响应事件的发生。在Spring中,事件监听器通常实现了ApplicationListener接口,该接口定义了监听事件的方法。如果对监听器模式有所了解朋友一定了解,监听器类的设计非常友好,会根据设计进行监听,而当有相应的变化进行发生时,监听器则会根据发生的情况同时相应的类或者接口,从而实现消息的动态传递。

3、事件发布器

        事件发布器(Event Publisher): 事件发布器负责发布事件,通知所有监听该事件的监听器。在Spring中,ApplicationEventPublisher接口表示事件发布器,可以通过Spring容器自动注入或手动获取。通常在Spring工作环境中,我们会使用applicationContext来进行事件的发布。上面三者就是SpringEvent的核心组件。事件发布器(publisher)会在事件(event)发生时进行事件的发布,事件发布后,有监听者进行事件监听,当监听到自己感兴趣的主体事件,则进行相应的事件处理。由此形成时间的发布、监听和处理的闭环操作。

        在介绍上述的重要组件之后,我们通过大文件处理的实例来具体介绍SpringEvent的详细应用。

二、WebUploader大文件处理的相关事件分析

        本节重点介绍WebUploader大文件处理组件中的后台相关事件处理。通过本节将了解何时进行相应事件的注册,具体的事件发布方法是什么?

1、事件发布的时机

        事件的发布时机是非常重要的,关于Webuploader则不再进行具体介绍。但是需要注意的是,如果在应用程序中采用了WebUploader这种后台处理机制,我们需要在后台实现数据的分片上传处理、分片的合并的操作。同时为了能兼容大文件和小文件的处理。以Webuploader为例,针对大文件,我们以5MB作为一个分片的切分逻辑,这种情况下可能有两种情况需要处理。第一种是单个文件的大小小于5MB,根据分片的策略,小于5MB的文件将不会进行分片而直接上传到后台。这时候也同样不会触发分片的合并逻辑。第二种情况是文件的大小超过5MB,比如有一个256MB的文件,就会进行分片上传。在服务端我们实现自定义的分片上传之后,还需要进行文件的合并。因此,我们在选择事件的发布时机时,就有两个点需要考虑的。需要分片的和不需要分片的文件处理时机。这两种都需要考虑,才能不漏掉相应的文件处理。

2、事件发布的代码

        在掌握了事件的发布时机后,我们就知道了在处理文件上传时的程序中如何切入事件的发布。事件的发布入口有两个地方,第一个无需分片的事件处理入口。第二个是在分片合并完成的事件入口。

        在进行事件发布前,我们需要在程序中创建一个Event的实例对象,用来进行事件信息的绑定和设置。这里我们取名位文件上传事件,关键代码如下所示:

package com.yelang.framework.event;
import org.springframework.context.ApplicationEvent;
import com.yelang.project.webupload.domain.FileEntity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class FileUploadEvent extends ApplicationEvent {
	private static final long serialVersionUID = 7396389156436678379L;
	private FileEntity fileEntity;//上传文件对象
	/**
	  *   重写构造函数
	 * @param source 事件源对象
	 * @param fileEntity 已上传的文件对象
	 */
	public FileUploadEvent(Object source,FileEntity fileEntity) {
		super(source);
		this.fileEntity = fileEntity;
	}
}

        为了方便大家可以获取上传的文件信息实体,我们将文件实体类在事件发布时一同绑定到事件上下文中。fileEntity其实就是一个文件上传的接收实体,关键代码如下:

package com.yelang.project.webupload.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.yelang.framework.web.domain.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@TableName("biz_file")
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class FileEntity extends BaseEntity {
	private static final long serialVersionUID = 1L;
	private Long id;
	@TableField(value = "f_id")
	private String fid;
	@TableField(value = "b_id")
	private String bid;
	@TableField(value = "f_type")
	private String type;
	@TableField(value = "f_name")
	private String name;
	@TableField(value = "f_desc")
	private String desc;
	@TableField(value = "f_state")
	private Integer state;
	@TableField(value = "f_size")
	private Long size;
	@TableField(value = "f_path")
	private String path;
	@TableField(value = "table_name")
	private String tablename = "temp_table";
	private String md5code;
	private String directory;
	@TableField(value = "biz_type")
	private String bizType;
	@TableField(exist = false)
	private boolean previewSign;
	@TableField(exist = false)
	private String previewType;
}

        我们在小文件(小于5MB)上传成功之后以及大文件合并完成之后就可以发布文件上传事件,在下面的代码中,我们通过applicationContext上下文对象发布了一个FilaUploadEvent的事件。大致的代码如下所示:

@SuppressWarnings("resource")
private AjaxResult mergeChunks(FileEntity db_file,String chunk_dir,String chunks,String f_path) throws IOException {
	if (db_file == null) {
		return AjaxResult.error(AjaxResult.Type.WEBUPLOADERROR.value(), "找不到数据");
	}
	if (db_file.getState() == 1) {
		//未分片文件上传成功合并成功后发布相应事件,各监听器自由监听并执行
		applicationContext.publishEvent(new FileUploadEvent(this, db_file));
		return AjaxResult.success();
    }
	if(db_file.getSize() > block_size){
		//xxx 其它业务逻辑
		db_file.setState(1);
		fileService.updateById(db_file);
		File tempFile = new File(chunk_dir);
		if (tempFile.isDirectory() && tempFile.exists()) {
			tempFile.delete();
		}
		//分片文件上传成功合并成功后发布相应事件,各监听器自由监听并执行
		applicationContext.publishEvent(new FileUploadEvent(this, db_file));
	}
	return AjaxResult.success();
}

三、事件监听器及实际的业务处理

        在上面小节中,我们介绍如何发布Spring的Event,同时以一个大文件的上传为例,具体的介绍了如何进行文件上传事件的发布。本节接着在上面的例子中,重点讲解在事件发布后,如何进行事件的监听以及具体的业务回调处理机制。通过本节可以掌握在实际业务中进行灵活的业务扩展和定制。

1、文件上传处理枚举

        在讲解事件监听器之前,首先我们对监听器中的具体回调业务类进行注册。在实际业务中,我们可以选择将具体回调业务类进行持久化处理,比如使用关系型数据库 进行处理,将具体的业务类、物理表、业务属性、回调业务实现类统一保存的数据库中。这样在执行的时候统一通过数据去获取即可。这种模式也是可以的,实现起来也比较简单。如何在不引入数据库的前提下实现呢?其实我们可以利用枚举类来轻松实现这类需求。下面分享一下这种设计,文件上传处理枚举类的业务逻辑如下所示:

package com.yelang.framework.aspectj.lang.enums;
/**
 * 文件上传监听服务注册枚举类
 * @author 夜郎king
 */
public enum FileUploadServiceRegisterEnum {
	UNKOWN(-1,"UNKOWN","","","未知"),
	PROJZSPRCSINFSERVIMPL(0,"biz_student","studentUploadCallbackServiceImpl","123a","项目程序管理文件上传回调处理枚举");
	private int index;//下标,编号作用
	private String tableName;//业务表名称,根据表名检索具体执行的servcie
	private String execService;//业务实际执行service
	private String bizType;//业务类型
	private String desc;//描述说明
	public int getIndex() {
		return index;
	}
	public void setIndex(int index) {
		this.index = index;
	}
	public String getTableName() {
		return tableName;
	}
	public void setTableName(String tableName) {
		this.tableName = tableName;
	}
	public String getExecService() {
		return execService;
	}
	public void setExecService(String execService) {
		this.execService = execService;
	}
	public String getDesc() {
		return desc;
	}
	public void setDesc(String desc) {
		this.desc = desc;
	}
	public String getBizType() {
		return bizType;
	}
	public void setBizType(String bizType) {
		this.bizType = bizType;
	}
	private FileUploadServiceRegisterEnum(int index, String tableName, String execService, String bizType, String desc) {
		this.index = index;
		this.tableName = tableName;
		this.execService = execService;
		this.bizType = bizType;
		this.desc = desc;
	}
	public static FileUploadServiceRegisterEnum getEnumByTableName(String tableName){
		FileUploadServiceRegisterEnum result = null;
		for (FileUploadServiceRegisterEnum enumObj : FileUploadServiceRegisterEnum.values()) {
			if(enumObj.getTableName().equals(tableName)){
				result = enumObj;
				break;
			}
		}
		return result;
	}
	public static FileUploadServiceRegisterEnum getEnumByTableNameAndBizType(String tableName,String bizType){
		FileUploadServiceRegisterEnum result = null;
		for (FileUploadServiceRegisterEnum enumObj : FileUploadServiceRegisterEnum.values()) {
			if(enumObj.getTableName().equals(tableName) && enumObj.getBizType().equals(bizType)){
				result = enumObj;
				break;
			}
		}
		return result;
	}
}

        在进行业务注册时,我们会定义具体的枚举实例,如下:PROJZSPRCSINFSERVIMPL(0,"biz_student","studentUploadCallbackServiceImpl","123a","项目程序管理文件上传回调处理枚举");0是下标索引号,biz_student是业务表,studentUploadCallbackServiceImpl是回调的具体业务实现类,123a是业务类型描述,根据需要可以用来区分同一个表的不同业务实现。最后一个是业务的描述。

2、文件上传监听器的实现

        在定义上述的枚举类之后,我们来进行文件上传监听器的实现,核心代码如下:

package com.yelang.framework.event.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import com.yelang.common.utils.StringUtils;
import com.yelang.common.utils.spring.SpringUtils;
import com.yelang.framework.aspectj.lang.enums.FileUploadServiceRegisterEnum;
import com.yelang.framework.event.FileUploadEvent;
import com.yelang.project.common.service.IFileUploadCallbackService;
import com.yelang.project.webupload.domain.FileEntity;
/**
 * 公共事件监听器组件,具体实现使用策略模式实现,统一由本类处理后进行相应转发,
 * 多种event监听均在本类中实现注册监听,使用event模式便于程序解耦,程序处理逻辑更加清晰
 * @author 夜郎king
 */
@Component
public class YelangSpringListener {
	private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");

	@EventListener
	public void fileUploadEventRegister(FileUploadEvent event){
		try {
			sys_user_logger.info("当前处理线程名称:" + Thread.currentThread().getName());
			FileEntity fileEntity = event.getFileEntity();
			if(StringUtils.isNotEmpty(fileEntity.getTablename())){
				FileUploadServiceRegisterEnum rigisterEnum = null;
				if(StringUtils.isNotBlank(fileEntity.getBizType())) {//业务类型不为空,则根据表名和业务名称来查找执行service
					rigisterEnum = FileUploadServiceRegisterEnum.getEnumByTableNameAndBizType(fileEntity.getTablename(), fileEntity.getBizType());
				}else {
					rigisterEnum = FileUploadServiceRegisterEnum.getEnumByTableName(fileEntity.getTablename());
				}
				if(null != rigisterEnum && StringUtils.isNotEmpty(rigisterEnum.getExecService())){
					String execService = rigisterEnum.getExecService();
					IFileUploadCallbackService service = SpringUtils.getBean(execService);
					service.process(fileEntity);
				}else{
					sys_user_logger.info("未注册文件上传监听回调处理器.");
				}
			}
		} catch (Exception e) {
			sys_user_logger.error("文件上传事件监听发生错误.",e);
		}
	}
}

         上面的逻辑中,重点就是找到回调的具体枚举实例,然后使用Spring的IOC机制,找到注册到Spring上下文中的IFileUploadCallbackService类,

if(null != rigisterEnum && StringUtils.isNotEmpty(rigisterEnum.getExecService())){
	String execService = rigisterEnum.getExecService();
	IFileUploadCallbackService service = SpringUtils.getBean(execService);
	service.process(fileEntity);
}

        然后调用process方法开始进行文件的处理。

3、文件具体处理逻辑

        为了让不同的业务实现不同的业务处理需要,我们将文件处理方法封装成统一的一个接口,然后通过不同的实例类来进行实现。接口的定义如下:

package com.yelang.project.common.service;
import com.yelang.project.webupload.domain.FileEntity;
public interface IFileUploadCallbackService {
	/**
	 * 文件上传事件监听器回调服务接口,封装公共服务,可以读取相关表格或修改业务表,具体实现由各实现类来完成
	 * @param fileEntity 文件实体
	 * @throws Exception
	 */
	void process(FileEntity fileEntity) throws Exception;
}

        然后定义统一的文件处理实现类,实现上述的接口,并实现具体的文件处理方法。

package com.yelang.project.common.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import com.yelang.common.utils.StringUtils;
import com.yelang.project.common.service.IFileUploadCallbackService;
import com.yelang.project.extend.student.domain.Student;
import com.yelang.project.extend.student.service.IStudentService;
import com.yelang.project.webupload.domain.FileEntity;
@Service("studentUploadCallbackServiceImpl")
public class StudentUploadCallbackServiceImpl implements IFileUploadCallbackService{
	private static final Logger logger = LoggerFactory.getLogger("sys-user");
	@Autowired
	private IStudentService studentService;
	@Override
	@Transactional(propagation=Propagation.REQUIRED,rollbackFor=Exception.class)
	public void process(FileEntity fileEntity) throws Exception {
		if(null != fileEntity && StringUtils.isNotEmpty(fileEntity.getBid())){
			String pkId = fileEntity.getBid();
			Student stu = studentService.selectStudentById(Long.valueOf(pkId));
			//System.out.println(fileEntity.getPath());
			//System.out.println(stu.getName() + "\t" + stu.getAddress());
			logger.info("开始处理........");
			Thread.sleep(35 * 1000);//休眠35秒测试
			logger.info("执行结束");
		}
	}
}

        上面的程序逻辑比较简单,我们仅演示了如何从事件发布器中获取FileEntity实体的信息,同时打印相应的信息。在实际业务中,可以实现更复杂的业务。

4、实际处理实例

        下面我们结合实际场景来看一下具体的实现及调用过程。

        我们来看一下后台的处理信息的输出,

        可以很明显的看到,在后台的控制台已经成功的输出相应的内容,表明事件的发布、监听、处理按照预定的设计运行。

23:14:53.169 [http-nio-8080-exec-37] INFO  sys-user - [fileUploadEventRegister,32] - 当前处理线程名称:http-nio-8080-exec-37
23:14:53.198 [http-nio-8080-exec-37] DEBUG c.y.p.e.s.m.S.selectById - [debug,137] - <==      Total: 1
23:14:53.199 [http-nio-8080-exec-37] INFO  sys-user - [process,32] - 开始处理........
23:15:08.200 [http-nio-8080-exec-37] INFO  sys-user - [process,34] - 执行结束

 四、总结

         以上就是本文的主要内容,本文以WebUploader大文件上传组件为例,在大文件处理的场景中使用SpringEvent的事件发布机制,灵活的扩展对文件的处理需求。本文通过代码实例的讲解,让您快速的了解如何在Spring中快速开发Event应用程序,同时使用枚举来实现动态的注册过程,实现方便灵活的注册机制。行文仓促,定有不足之处,真诚期待各位专家朋友在评论区批评指正,不甚感激。

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

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

相关文章

[红明谷CTF 2021]write_shell 1

目录 代码审计check()$_GET["action"] ?? "" 解题 代码审计 <?php error_reporting(0); highlight_file(__FILE__); function check($input){if(preg_match("/| |_|php|;|~|\\^|\\|eval|{|}/i",$input)){// if(preg_match("/| |_||p…

科普文:万字梳理31个Kafka问题

1、 kafka 是什么,有什么作用 2、Kafka为什么这么快 3、Kafka架构及名词解释 4、Kafka中的AR、ISR、OSR代表什么 5、HW、LEO代表什么 6、ISR收缩性 7、kafka follower如何与leader同步数据 8、Zookeeper 在 Kafka 中的作用&#xff08;早期&#xff09; 9、Kafka如何快…

软件测试的实质

一、软件缺陷定义 软件未实现产品说明书要求的功能软件出现了产品说明书指明不应该出现的错误软件实现了产品说明书未提到的功能&#xff1b;如罕见未实现产品说明书虽未明确提及但应该实现的目标软件难以理解、不易使用、运行速度慢&#xff0c;或者软件测试员认为最终用户会…

java——集合介绍【汇总】

一、集合的理解和好处 1.1、数组的不足 1、长度开始时必须指定&#xff0c;而且一旦指定&#xff0c;不能更改 2、保存的必须为同一类型的元素 3、使用数组进行增删&#xff0c;比较麻烦 1.2、集合 1、可以动态保存任意多个对象&#xff0c;使用比较方便! 2、提供了一系…

【前端 16】使用Ajax发送异步请求

Ajax 基础入门&#xff1a;实现异步请求 Ajax&#xff08;Asynchronous JavaScript and XML&#xff09;是一种在无需重新加载整个网页的情况下&#xff0c;能够更新部分网页的技术。通过使用 Ajax&#xff0c;可以在后台与服务器交换数据&#xff0c;这意味着可以在不影响用户…

解决hook汇编代码时空间不足的一种方法

思路&#xff1a;如下图&#xff0c;使用两条jmp指令。原内存地址使用一条jmp指令跳转到新开辟的内存空间(VirtualAlloc或者VirtualAllocEx函数&#xff09;&#xff0c;在新开辟的内存空间完成处理之后再使用jmp指令跳转到原内存地址合适的位置&#xff08;通常是原内存处被ho…

华为诺亚发布无限上下文大模型,超越SoTA 4.3%

你的大语言模型是不是也患上了"长文健忘症"&#xff1f;当使用大模型遇到长上下文时总是会出现词不达意&#xff1f;别担心&#xff0c;LLM界的"记忆大师"来啦&#xff01;华为诺亚方舟实验室最新推出的EM-LLM模型&#xff0c;就像是给大模型装上了"超…

光伏混合储能直流微网直流母线电压下垂控制MATLAB仿真

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 模型简介 此模型以混合储能系统为研究对象&#xff0c;采用基于关联参数SOC的改进下垂控制策略&#xff0c;将初始下垂系数与储能单元SOC的n次幂的比值作为现行下垂系数&#xff0c;通过改变n值&#xff0c;…

2.5 C#视觉程序开发实例2----图片内存管理

2.5 C#视觉程序开发实例2----图片内存管理 1 目标效果视频 mat-buffer 2 Mat 数组的定义 3 图片内存使用场合说明 3.1 程序加载或者切换程序时 3.2 设定时&#xff0c;注册图片 例如注册一个线速的图片 注册流程说明 3.3 外部触发时采集最新图片或者按钮点击时触发拍照 …

计算机毕业设计碾压导师Python+Django农产品推荐系统 农产品爬虫 农产品商城 农产品大数据 农产品数据分析可视化 PySpark Hadoop

基于Spark的农产品个性推荐系统 相关技术介绍: 1. Python Python是一种高级编程语言&#xff0c;具有简洁、易读、易学的特点&#xff0c;被广泛应用于Web开发、数据分析、人工智能等领域。 在此系统中&#xff0c;我们使用Python进行后端开发&#xff0c;利用其强大的语法…

图形引擎实战:Unity性能分析工具原理介绍

最近在维护一个Unity性能分析工具&#xff0c;类似UPR&#xff0c;客户端采集信息&#xff0c;WEB端显示数据。下面简单介绍下原理。 数据来源 Profiler数据 熟悉Unity的同学对Profiler一定不会陌生&#xff0c;我们的性能数据主要来源于它&#xff0c;主要包含函数耗时&…

Linux基础操作(下)

软件安装&#xff0c;CentOS系统和Ubuntu是使用不同的包管理器 CentOS使用yum管理器&#xff0c;Ubuntu使用apt管理器 在CentOS系统中&#xff0c;使用yum命令联网管理软件安装 yum语法: yum [-y] [install | remove | search ] 软件名称 在Ubuntu系统中&#xff0c;使用apt命…

如何跨越 LangChain 应用研发的最后一公里

说 [LangChain] 是现在最流行的 AI 应用开发框架&#xff0c;应该没有人出来反对吧。LangChain 的出现极大地简化了基于大型语言模型&#xff08;LLM&#xff09;的 AI 应用构建难度&#xff0c;如果把 AI 应用比作一个人的话&#xff0c;那么 LLM 相当于这个人的“大脑”&…

FRP配置内网穿透52版本以上适用

简述 适用frp配置内网穿透来说我们需要进行简单的区分&#xff0c;具有公网IP的服务器我们简称为服务端&#xff0c;内网的服务器我们可以简称为客户端&#xff0c;frp需要针对不同的服务器配置不同的文件 下载安装包 Linux下载地址 https://github.com/fatedier/frp/relea…

数据丢失不用愁!这四款数据恢复大师免费版助你找回珍贵回忆

我们在办公或者是生活中常常会遇到不小心将手机设备或者计算机当中的重要数据误删除/格式化/或其他不小心丢失的情况&#xff0c;但是不用紧张&#xff0c;这篇文章就是给大家分享如何恢复他们&#xff0c;以下带来除易我数据恢复外的其他好用的数据恢复软件&#xff1a; 第一…

后端笔记(2)--JDBC

1.JDBC简介 *JDBC(Java DataBase Connectivity)就是使用java语言操作关系型数据库的一套API *JDBC本质&#xff1a;&#xff08;可以使用同一套代码&#xff0c;操作不同的关系型数据库&#xff09; ​ *官方定义的一套操作所有关系型数据库的规则&#xff0c;即接口 ​ *各…

2024年巴黎奥运会奖牌榜数据源:各国选手为荣誉而战!

奥运会是全球瞩目的盛会&#xff0c;每四年举办一次&#xff0c;汇集了来自超过200个国家的优秀运动员参与夏季和冬季的400多场比赛。这是一项真正的全球综合性运动会&#xff0c;各个国家选手为了荣誉和国家的面子而激烈竞争。2024年的巴黎奥运会将是一场令人期待的盛宴&#…

C语言——选择结构

C语言——选择结构 关系运算符及关系表达式关系运算符关系表达式 逻辑运算符和逻辑表达式逻辑运算符逻辑表达式 选择语句if语句条件运算符switch case语句 关系运算符及关系表达式 关系运算符 关系运算实际上是比较运算&#xff0c;C语言提供了六种关系运算符分别为&#xff…

Go语言教程(一看就会)

全篇文章 7000 字左右&#xff0c; 建议阅读时长 1h 以上。 Go语言是一门开源的编程语言&#xff0c;目的在于降低构建简单、可靠、高效软件的门槛。Go平衡了底层系统语言的能力&#xff0c;以及在现代语言中所见到的高级特性。它是快速的、静态类型编译语言。 第一个GO程序…

一篇文章带你入门爬虫并编写自己的第一个爬虫程序

一、引言 目前我们处在一个信息快速迭代更新的时代&#xff0c;海量的数据以大爆炸的形式出现在网络之中&#xff0c;相比起过去那个通过广播无线电、书籍报刊等传统媒介获取信息的方式&#xff0c;我们现在通过网络使用搜索引擎几乎可以获得任何我们需要的信息资源。 但与此同…