Ruby's NoteBook - java
type: channel
link: https://slugyao.top/index.php/tag/java/
items: 文件上传实现断点续传(单线程)
items: 排序算法
items: AOP编程
items: SpringBoot与Mybatis整合时出现bug解决
items: Spring笔记

文件上传实现断点续传(单线程)
type: item
date: 2025-11-28T16:03:00+08:00
description: 在说断点续传之前，先说一下文件上传的原理，因为断点续传的应用场景就是文件上传。文件上传客户端发送上传文件请求服务器收到请求之后解析请求头和请求体，获取上传的数据并进行验证（已经上传的文件不重复上传）服务器端将分片上传的文件存在临时目录中，所有分片上传后再进行合并和重命名最后将文件存储在指定的目录断点续传文件传输过程中，如果出现传输中断或者失败的情况，可以从中断的地方继续传输，不用重新开始传输。断点续传有很多种实现方式，这里推荐b站一个用Java实现多线程断点续传的视频<4.java多线程-实现断点续传_哔哩哔哩_bilibili>在实际应用时我选择用Redis实现断点续传大致的思路：前端计算上传文件的MD5值，如果Redis中由对应的MD5值则说明秒传成功后端通过Redis记录文件分片的情况（Redis中使用Set记录哪些分片已经上传，已经上传过的分片会被跳过），前端开始上传之后，每次上传成功都要记录在Redis中，续传之后前端重新计算MD5值，重新开始上传Redis中记录缺失的分片。当所有分片都上传完成并且合并成文件之后，删除Redis中对应的分片记录。以上思路只适用于单线程，如果要使用多线程方式实现，需要考虑更多的问题~代码实现 @Transactional(rollbackFor = Exception.class) public boolean uploadFile(MultipartFile file, String fileName, String fileMD5, String fileId, HttpServletRequest request, String redisKey) { try { // 参数校验 if (file == null || file.isEmpty()) { throw new IllegalArgumentException("文件不能为空"); } // 如果fileId为空，则生成一个随机的fileId if (StringUtils.isEmpty(fileId)) { fileId = RandomStringUtils.random(10); } // 从请求头中获取分片信息 int chunkNumber = 0; int totalChunks = 1; long chunkSize = file.getSize(); long totalSize = file.getSize(); String chunkMD5 = null; try { chunkNumber = Integer.parseInt(request.getHeader("Chunk-Number")); totalChunks = Integer.parseInt(request.getHeader("Total-Chunks")); chunkSize = Long.parseLong(request.getHeader("Chunk-Size")); totalSize = Long.parseLong(request.getHeader("Total-Size")); chunkMD5 = request.getHeader("Chunk-MD5"); } catch (Exception e) { log.info("未获取到分片信息"); } // 创建临时目录 String tempDir = System.getProperty("java.io.tmpdir") + "/upload/" + fileMD5 + "/"; File tempFileDir = new File(tempDir); if (!tempFileDir.exists()) { tempFileDir.mkdirs(); } // 秒传检查 File finalFile = new File("/final/path/" + fileId + "_" + fileName); if (finalFile.exists() && finalFile.length() == totalSize) { log.info("文件已存在，秒传成功"); return true; } // 处理分片上传 File chunkFile = new File(tempDir + "chunk_" + chunkNumber); // 断点续传检查 - 结合Redis Boolean chunkExistsInRedis = redisTemplate.opsForSet().isMember( String.format(CHUNK_UPLOAD_KEY, fileMD5), String.valueOf(chunkNum…

排序算法
type: item
date: 2025-08-27T09:57:49+08:00
description: 最近写题目的时候发现一些排序算法我不太熟悉，简单记录一下。下面所有的算法都会把数组元素按照升序排序。插入排序插入排序的核心思想是：遍历未排序的序列，对未排序序列的元素插入到已排序的序列中。插入排序的步骤：从第一个元素开始，该元素可以认为已经被排序。取出下一个元素，在已经排序的元素序列中从后向前扫描。如果该元素（已排序）大于新元素，将该元素移到下一位置。重复步骤3，直到找到已排序的元素小于或者等于新元素的位置。将新元素插入到该位置后。重复步骤2~5。public void insertionSort(int[] arr) { if (arr == null || arr.length <= 1) { return; // 数组为空或只有一个元素时无需排序 } for (int i = 1; i < arr.length; i++) { int key = arr[i]; // key是当前需要插入的元素 int j = i - 1; // 将比key大的元素向后移动 while (j >= 0 && arr[j] > key) { arr[j + 1] = arr[j]; // 元素后移 j--; } arr[j + 1] = key; // 把key插入到正确的位置 } }快速排序快速排序使用了“分治”的思想，每次选择一个元素，以这个元素为基准，将数组分为小于等于基准和大于基准的部分。再递归地对两边进行排序（也就是对左右两个区间再此选择基准进行划分，如此重复）。快速排序的步骤：选择基准：从数组中选择一个元素作为基准。基准元素的选择可以是随意的。分区：重新排列数组，使得所有小于基准的元素放在基准前面，大于基准的放在后面。分区结束后，基准处于数组的中间位置。递归：递归地将小于基准的子数组和大于基准的子数组排序。public void quickSort(int[] array) { if (array == null || array.length <= 1) return; sort(array, 0, array.length - 1); } private static void sort(int[] array, int low, int high) { if (low >= high) return; int pivot = array[high]; // 选择最后一个元素作为基准 int i = low - 1; // 较小元素的边界指针 // 分区操作：将小于基准的元素移到左侧 for (int j = low; j < high; j++) { if (array[j] <= pivot) { i++; swap(array, i, j); } } swap(array, i + 1, high); int pivotIndex = i + 1; // 递归排序两边子数组 sort(array, low, pivotIndex - 1); sort(array, pivotIndex + 1, high); } // 交换两个元素的方法 private static void swap(int[] array, int i, int j) { int temp = array[i]; array[i] = array[j]; array[j] = temp; }归并排序归并排序也用到了“分治”的思想，先将序列分为若干个有序的小区间，最后将这些区间逐步合并。归并排序的步骤：分解：将数组从中间分成两部分。解决：递归地对左右两部分进行归并排序。合并：将两个有序的子数组合并成一个有序数组。 public void mergeSort(int[] array) { if (array == null || array.length <= 1) return; int n = array.length; int[] temp = new int[n]; for (int size = 1; size < n; size *= 2) { // 内层循环处理每个子数组 for (int left = 0; left < n; left += 2 * size) { int mid = Math.min(left + size - 1, n - 1); int right = Math.min(left + 2 * size - 1, n - 1); // 合并操作 int i = left; int j = mid + 1; int k = left; // 比较并合并两个子数组 while (i <= mid && j <= right) { if (array[i] <= array[j]) { temp[k++] = arr…

AOP编程
type: item
date: 2025-06-03T16:26:00+08:00
description: 什么是AOP？AOP就是面向切面编程，与OOP(面向对象编程)一样，是一种编程思想。AOP的思想运用在编程中可以在不改动原有设计的基础上进行功能的增强。AOP中的主要概念通知：简单来说就是抽取出的共性功能。切面：描述通知与切入点的关系，通俗来说就是哪些方法要追加哪些功能。连接点：就是方法，因为共性功能是要追加到方法里面的，因此连接点就是方法。切点：要抽取共性功能的方法称为切点，切点是连接点的子集。织入：将要添加的共性功能添加到的目标对象中的过程。AOP的原理其实对于初学者来说，不需要太清楚AOP的实现原理，但我还是决定写一下。知道它底层实现的原理也许能在未来解决一些bug。代理模式要讲AOP的底层原理，首先要讲什么是“代理”。假设有这样一个场景：你想要买一双鞋，但是一般情况下你是从商店里面买到这双鞋，而不是从这双鞋生产的地方买的。商店就在这一过程中充当了“代理”的角色。在买鞋的时候，你是很难直接接触到工厂的，而商店，这个“代理”者，能帮我们从工厂那里拿到鞋子。那么在购买这双鞋的过程中，商店就充当了“代理”的角色。示例这里我们使用aspectj来实现AOP项目背景：在一个项目中，调用接口时打印日志首先梳理一下大致的思路导入坐标到pom.xml文件中一般来说添加以下这两个依赖即可 org.springframework.boot spring-boot-starter-aop test org.aspectj aspectjweaver 首先创建一个打印日志的注解MyLogpackage com.blog.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD)//运用在方法上 @Retention(RetentionPolicy.RUNTIME)//在运行时执行 public @interface MyLog { String title() default ""; }然后制作共性功能这里的共性功能就是打印日志，我们就简单写一下在aspect包下创建一个切面类@AfterReturning注解在方法返回后进入切面的通知方法中package com.blog.aspect; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import java.util.Date; @Aspect @Component public class LogAspect { @AfterReturning(pointcut = "@annotation(com.blog.annotation.MyLog)") public void log(JoinPoint joinPoint) { System.out.println("log:"); handler(joinPoint); } protected void handler(JoinPoint joinPoint) { Date date = new Date(); String className = joinPoint.getTarget().getClass().getName(); System.out.println(className); System.out.println(date.getTime()); } } 这里面的pointcut指定为添加了MyLog注解的方法，也就是添加了@MyLog注解的方法才会进入AOP的逻辑中打印日志。

SpringBoot与Mybatis整合时出现bug解决
type: item
date: 2025-03-03T20:22:00+08:00
description: 今天在Spring Boot与MySQL数据库进行连接的时候出现了以下报错(直接眼前一黑😢)，在网上进行搜索之后发现是maven依赖中的MyBatis与Spring Boot的版本不匹配解决方法如下：将MyBatis依赖的version改为2.2.2版本（与SpringBoot不发生冲突的版本）即可小小的感悟：出现爆红时先不要慌，记得复制报错信息搜索一下，说不定就是个很简单的BUG。以及maven依赖的报错真是数不胜数，简直是噩梦。 org.mybatis.spring.boot mybatis-spring-boot-starter 2.2.2 图片报错信息如下

Spring笔记
type: item
date: 2024-11-05T15:58:00+08:00
description: 最近在学一些后端的技术，零零散散记了一些相关的知识点，有关Spring的内容基本就集中在这一篇。看的教程网址：教程链接Spring核心概念IoC(Inversion of Control)控制反转使用对象时，由主动的new来产生对象转变为外部提供对象，此过程中对象创建控制权由程序转移到外部Spring提供一个容器IoC容器来充当“外部"这个容器复杂对象的创建、初始化等一系列工作，被创建或被管理的对象在IoC容器中统称为Bean目标：充分解耦使用IoC管理Bean(IoC)在IoC容器中将有依赖关系的bean进行关系绑定(DI)IoC思路分析IoC管理Service和Dao用配置将被管理的对象告知IoC容器用接口获取IoC容器用接口方法从容器中获取Bean1）导入spring坐标2）定义Spring管理的类(接口)3）创建Spring配置文件，配置对应的类作为Spring管理的Bean 注意：bean定义时id属性在同一个上下文中不能重复DI入门案例依赖注入，全称 Dependency Injection，简称 DI依赖关系的维护就称为依赖注入基于IoC管理BeanService中使用new形式创建的对象不能保留Service中需要的Dao对象通过方法进入Service中1）删除使用new形式创建对象的代码2）提供依赖对象对应的set方法3）配置service和dao之间的关系 BeanBean基础配置标签id:bean的id，使用容器可以通过id值获取对应的bean，在一个容器中id值唯一class:bean的类型，即配置的bean的店路径名name:bean的别名，可以使用空格分隔多个别名scope:作用范围，可以选择单例或者多例，默认单例适合交给容器进行管理的Bean表现层对象业务层对象数据层对象工具对象Controller:控制层，接受前端发送的请求，对请求进行处理，并响应数据Service:业务逻辑层，处理具体的业务逻辑Dao:数据访问层，复杂数据访问操作，包括数据的增删改查Bean实例化实例化Bean的方式用构造方法(无参)创建对象，如果没有无参构造方法将抛出异常BeanCreationException静态工厂实例化Bean， 实例工厂初始化Bean 可以使用接口FactoryBean<>来代替public class UseDaoFactoryBean implements FactoryBean<UserDao>{ public UserDao getObject() throws Exception{ return new UserDaoImpl(); } public Class<> getObjectType(){ reuturn UserDao.class; } }配置 Bean的生命周期生命周期：从创建到销毁的完整过程初始化容器创建对象执行构造方法执行属性注入(set)执行bean初始化方法使用bean关闭/销毁容器执行bean销毁方法bean的生命周期控制：在bean创建后到销毁前做一些事情加载properties文件开启context命名空间<? xml version="1.0" encoding="UTF-8"?> < beans xmlns=" http://www.springframework.org/schema/bean s" xmlns: xsi=" http://www.w3.org/2001/XMLSchema-instance " xmlns: context=" http://www.springframework.org/schema/context " xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> </ beans>使用contex命名空间<context:property-placeholder locaion=" "/>使用${}读取加载的属性值 不加载系统属性<context:property-placeholder location="" system-properties-mode="NEVER"/>加载多个properties文件<context:property-placeholder location="jdbc.propert…