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

目录

前言

一、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;//业务表名称,根据表名检索具体执行的servcieprivate String execService;//业务实际执行serviceprivate 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");@EventListenerpublic 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())) {//业务类型不为空,则根据表名和业务名称来查找执行servicerigisterEnum = 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");@Autowiredprivate 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.mzph.cn/news/876855.shtml

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

相关文章

pthread多线程

pthread库是linux系统的多线程库。上次写完longjmp和多线程的例子。就想用系统的真线程库改写一下读写线程的例子来和大家分享。真线程库应该比自己写的功能更完善&#xff0c;代码也更容易写吧&#xff01;… … 说到这里。我不知道接下来说什么了… …。也许是自己对多线程库…

ConstraintLayout实现原理分析

ConstraintLayout 是 Android 支持库中提供的一个非常强大的布局管理器&#xff0c;它允许开发者创建复杂的界面布局&#xff0c;并提供了比传统布局更多的灵活性。在 ConstraintLayout 中&#xff0c;视图之间可以通过约束来定义其位置关系&#xff0c;从而使得布局更加动态和…

Python+selenium web自动化测试知识点合集2

选择元素 对于百度搜索页面&#xff0c;如果我们想自动化输入“selenium”&#xff0c;怎么做呢&#xff1f; 这就是在网页中&#xff0c;操控界面元素。 web界面自动化&#xff0c;要操控元素&#xff0c;首先需要 选择 界面元素 &#xff0c;或者说 定位 界面元素 就是 先…

OD C卷 - 电脑病毒感染

电脑病毒感染 &#xff08;200&#xff09; 一个局域网内有n台电脑&#xff0c;编号为 1 -> n&#xff0c;电脑之间病毒感染时间用 t 表示&#xff1b;现在网络内已有一台电脑被病毒感染&#xff0c;求其感染所有其他电脑最少的时间&#xff0c;若最后有电脑不会被感染&…

URL重写

目录 步骤1 规则语法 Nginx URL重写规则语法 Apache URL重写规则语法 步骤2 规则配置 Apache URL重写规则配置 启用mod_rewrite模块 配置.htaccess文件 编写重写规则 测试重写规则 Nginx URL重写规则配置 配置server或location块 测试重写规则 步骤1 规则语法 Ngin…

如何使用Redis实现一个缓存策略

使用Redis实现一个缓存策略&#xff0c;主要涉及到数据的存储、读取、更新以及失效处理等方面。下面我将详细介绍如何使用Redis来设计和实现一个基本的缓存策略。 1. 确定缓存的数据结构和键命名规则 首先&#xff0c;你需要决定使用Redis中的哪种数据结构来存储缓存数据&…

解决在from pyhdf.SD import SD,SDC时No module named “hdfext“

文章内容仅用于自己知识学习和分享&#xff0c;如有侵权&#xff0c;还请联系并删除 &#xff1a;&#xff09; 1. 错误原因&#xff1a; 参考了多个博主的解决办法&#xff0c;结合自己的尝试&#xff0c;发现造成这个问题的主要原因是numpy版本太高 2. 解决方法 方法1&…

Day.31 | 1049.最后一块石头的重量II 494.目标和 474.一和零

1049.最后一块石头的重量II 要点&#xff1a;思路与分割等和子集很类似&#xff0c;把总数分成和最接近的两堆&#xff0c;然后用01背包的套路解答 class Solution { public:int lastStoneWeightII(vector<int>& stones) {int sum 0;for (int i : stones)sum i;i…

C++客户端Qt开发——界面优化(QSS)

1.QSS 如果通过QSS设置的样式和通过C代码设置的样式冲突&#xff0c;则QSS优先级更高 ①基本语法 选择器{属性名&#xff1a;属性值; } 例如&#xff1a; QPushButton {color: red; } 1>指定控件设置样式 #include "widget.h" #include "ui_widget.h&qu…

Unity Editor免登录启动 无需UnityHub

Unity Editor免登录启动项目无需UnityHub&#xff0c;命令行启动项目。需要开发Unity项目&#xff0c;就必须使用 Unity Hub来管理你的项目&#xff0c;还必须要申请一个免费许可&#xff0c;确实有点麻烦&#xff0c;官方已经提供了相关命令行&#xff0c;来直接使用Unity Edi…

qt--做一个拷贝文件器

一、项目要求 使用线程完善文件拷贝器的操作 主窗口不能假死主窗口进度条必须能动改写文件大小的单位&#xff08;自适应&#xff09; 1TB1024GB 1GB1024MB 1MB1024KB 1KB1024字节 二、所需技术 1.QFileDialog 文件对话框 QFileDialog也继承了QDialog类&#xff0c;直接使用静态…

Redis缓存数据库进阶——Redis与分布式锁(6)

分布式锁简介 1. 什么是分布式锁 分布式锁是一种在分布式系统环境下&#xff0c;通过多个节点对共享资源进行访问控制的一种同步机制。它的主要目的是防止多个节点同时操作同一份数据&#xff0c;从而避免数据的不一致性。 线程锁&#xff1a; 也被称为互斥锁&#xff08;Mu…

TypeScript学习篇-类型介绍使用、ts相关面试题

文章目录 基础知识基础类型: number, string, boolean, object, array, undefined, void(代表该函数没有返回值)enum(枚举): 定义一个可枚举的对象typeinterface联合类型: |交叉类型: &any 类型null 和 undefinednullundefined never类型 面试题及实战1. 你觉得使用ts的好处…

Robot Operating System——内部审查(Introspection)Service

大纲 introspection_service检验Parameter值和类型修改内部审查&#xff08;Introspection&#xff09;功能的状态完整代码 introspection_client完整代码 测试参考资料 在ROS 2&#xff08;Robot Operating System 2&#xff09;中&#xff0c;内部审查&#xff08;Introspect…

【中项】系统集成项目管理工程师-第7章 软硬件系统集成-7.3软件集成

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

解决WordPress文章引用的图片不显示问题

在使用WordPress发布文章时&#xff0c;有时会遇到复制发布的文档中包含的外链图片无法正常显示的问题。然而&#xff0c;当我们将图片路径复制到浏览器中单独打开时&#xff0c;图片却可以正常显示。以下是解决这一问题的方法。 问题描述 当你在WordPress文章中引用外链图片…

python 裁剪图片

情况&#xff1a; 有时候看视频&#xff0c;看到一个漂亮的妹子&#xff0c;按下 Alt PrintScreen 进行截图之后&#xff0c;会把整个屏幕都截图。 需要适当剪裁一下。 每次打开 PS &#xff0c; 也太慢了。 所以写个代码&#xff0c; 快速处理。 效果对比&#xff1a; 原始…

iOS集成Ionicons库

目录 ​​​​​​​前言 一、ionicons-iOS 二、安装 三、使用方法 1.字体 2.UILabel: 3.UIImage 四、参考文章 前言 Ionicons 是一个完全开源的图标集&#xff0c;包含 1,300 个专为 Web、iOS、Android 和桌面应用程序设计的图标。Ionicons 是为 Ionic Framework 构建…

【2025留学】德国留学真的很难毕业吗?为什么大家不来德国留学?

大家好&#xff01;我是德国Viviane&#xff0c;一句话讲自己的背景&#xff1a;本科211&#xff0c;硕士在德国读的电子信息工程。 之前网上一句热梗&#xff1a;“德国留学三年将是你人生五年中最难忘的七年。”确实&#xff0c;德国大学的宽进严出机制&#xff0c;延毕、休…

OOP知识整合----集合

目录 一、定义 1、集合: ( 不限制长度&#xff0c;存多少是多少) 2、集合框架: 二、List集合中常用的方法 1、Boolean add(Object o) 2、void add(int index,Object o) 3、Boolean remove(Object o) 4、Object remove(int index) 5、int size() 6、Boolean conta…