Lin 's Blog - JAVA
type: channel
link: https://blog.lin03.cn/tag/JAVA/
items: JUC相关练习
items: 多功能可协同云相册(开源)
items: 夸克短剧机器人
items: 关于Mybatis-Plus框架分页不生效问题解决
items: JAVA学习笔记

JUC相关练习
type: item
date: 2025-07-10T15:16:00+08:00
description: 此文章内容记录学习 JUC 时的各种问题问题虚假唤醒​​虚假唤醒​​是多线程编程中的一个典型问题，它是指线程在未被明确通知（如 signal() 或 signalAll()）的情况下从等待状态中唤醒。这可能导致程序在条件未满足时继续执行，从而引发逻辑错误为什么会发生虚假唤醒？​​底层操作系统机制​​操作系统底层的条件变量实现（如 POSIX 的 pthread_cond_wait()）允许在无显式信号时唤醒线程（通常出于性能考虑）。Java 作为跨平台语言无法完全避免这一问题。​​中断和超时机制​​当等待的线程被外部中断（interrupt()）或达到超时时间时，也可能意外唤醒。class SharedResource { private int item = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); // 生产者方法：非安全实现 public void produce() throws InterruptedException { lock.lock(); try { // 错误：使用if而非while检查条件 if (item != 0) { condition.await(); // 可能虚假唤醒后继续执行 } item++; System.out.println("生产: " + item); condition.signal(); } finally { lock.unlock(); } } // 消费者方法：非安全实现 public void consume() throws InterruptedException { lock.lock(); try { // 错误：使用if而非while检查条件 if (item == 0) { condition.await(); // 可能虚假唤醒后继续执行 } System.out.println("消费: " + item); item--; condition.signal(); } finally { lock.unlock(); } } public static void main(String[] args) { SharedResource share = new SharedResource(); for(int i=0; i<2; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { try { share.produce(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Producer" + i).start(); } for(int i=0; i<2; i++) { new Thread(() -> { for (int j = 0; j < 100; j++) { try { share.consume(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Consumer" + i).start(); } } }可以看到已经产生了虚假唤醒解决方案使用while代替ifclass SafeResource { private int value = 0; private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); // 安全的生产者 public void safeIncrement() throws InterruptedException { lock.lock(); try { // 修复：使用while循环防御虚假唤醒 while (value != 0) { condition.await(); } value++; System.out.println(Thread.currentThread().getName() + " 安全生产后: " + value); condition.signalAll(); } finally { lock.unlock(); } } // 安全的消费者 public void safeDecrement() throws InterruptedException { lock.lock(); try { // 修复：使用while循环防御虚假唤醒 while (value != 1) …

多功能可协同云相册(开源)
type: item
date: 2025-06-02T22:11:05+08:00
description: 练手项目开源链接前端 github后端github项目介绍多功能可协同云相册是一个创新的图片管理与分享平台，具有以下特色功能：多空间管理：支持公共空间、私人空间和协作工作空间协同创作：邀请他人共同管理相册内容AI增强：内置AI图像处理功能，支持图像扩展等操作智能采集：管理员可通过关键词自动采集网络图片资源技术栈后端基础框架：Java 17 / Spring Boot 2.x持久层：MySQL / MyBatis-Plus缓存方案：Redis + Caffeine 多级缓存安全框架：Sa-Token文件存储：腾讯云COSAPI文档：Knife4j前端核心框架：Vue 3 + TypeScript状态管理：Pinia路由管理：Vue RouterUI组件库：Ant Design Vue项目亮点采用 Sa-Token RBAC 进行权限控制图片文件的高效存储及压缩Redis + Caffeine 实现多级缓存,显著提高加载速度项目结合 AI大模型 进行图片的扩图多维度的可视化数据分析及搜索~ShardingSphere 动态分表~基于 WebSocket 的团队实时编辑图片网络图片采集未来计划深度整合AI，实现AI分类、打标签、审核等完善权限系统完善VIP系统新增存储桶渠道，目前计划加入CloudFlare R2加入点赞及浏览功能部分网站截图

夸克短剧机器人
type: item
date: 2025-06-02T15:51:00+08:00
description: 去年年末时候接触到了某平台的夸克推广活动，就是别人转存你的链接你能从中受益，于是变有了这个项目READMEQuarkBot⚠️ 该项目已经失效因 Gewechat 框架已停止服务，所以此项目已无法正常使用基于 Gewechat 框架二次开发一款支持影视搜索及转存分享的多功能机器人🌟 核心功能🎥 影视/短剧搜索：基于个人聚合的接口和数据库快速查找全网影视资源。🔑 夸克功能集成：检测 Cookie 有效性。检测夸克链接有效性。转存资源并生成分享链接并推送。监测转存更新。📦 资源管理：支持通过机器人口令快速转存截图项目分析菜单该项目主要利用了 Gewechat 框架接收发信息的功能,菜单功能如下// 机器人命令 String newMsg = """ {{name}} {{description}} {{link}} """; String menu = """ 欢迎使用影视资源机器人! """; String adminMenu = """ 管理员命令 ---------------------- #ping #开启/关闭提醒 #更新群组列表 #更新ck 你的COOKIE #更新文件夹id 你的文件夹id 下面请使用空格隔开 ---------------------- 如果完结为 false 的话，则自动开启更新 #提交 名字 描述 链接 (是否完结 true|false) #转存 名字 描述 链接 (是否完结 true|false) #提交并群发 名字 描述 链接 (是否完结 true|false) #转存并群发 名字 描述 链接 (是否完结 true|false) #更新 你自己的分享链接 新的分享链接(没有则写 无 ) (是否完结 true|false) ----------------------- """;因为项目的初衷是自用，所以写的简陋了些，直接将菜单项写死了搜索这个功能还是挺重要的，因为要实现搜剧，这个功能是必不可少的private CommonVO<List<Map<String, String>>> baseSearch(String name,String type) { List<Map<String, String>> data = Collections.synchronizedList(new ArrayList<>()); // 从 Redis 中获取缓存的 list 列表 List<Map<String, String>> o = (List<Map<String, String>>) redisTemplate.opsForHash().get("search-name-list", name); if (o != null && !o.isEmpty()) { log.info("命中缓存 search-name-list"); return CommonVO.OK(o); } // 从数据库中查询 List<QuarkEntity> list = quarkMapper.selectList( new QueryWrapper<QuarkEntity>() .like("name", name) .eq("valid", true) ); List<QuarkEntity> listDuanJu = new ArrayList<>(); if ("duanju".equals(type)) { List<QuarkEntity> duanjuList = duanjuMapper.selectList( new QueryWrapper<DuanjuEntity>().like("name", name) ).stream().map(duanju -> { try { return BeanConverter.convert(duanju, QuarkEntity.class); } catch (Exception e) { log.error("Bean转换失败", e); } return new QuarkEntity(); }).toList(); listDuanJu.addAll(duanjuList); } if (!listDuanJu.isEmpty()) { // 使用CountDownLatch等待异步任务完成 CountDownLatch latch = new CountDownLatch(1); threadExecutor.execute(() -> { try { List<Integer> idsToDelete = new ArrayList<>(); Iterator<QuarkEntity> iterator = listDuanJu.itera…

关于Mybatis-Plus框架分页不生效问题解决
type: item
date: 2025-05-19T21:14:00+08:00
description: 最近在使用Mybatis-Plus的分页查询方法时候，发现并没有生效。查询的参数为{ "current": 1, "pageSize":"2" }但是实际把所有数据都返回了{ "code": 0, "data": { "records": [ { "id": 1923634783594762241, "userAccount": "admin", "createTime": "2025-05-17T07:00:46.000+00:00" }, ... ], "total": 0, "size": 2, "current": 1, "pages": 0 }, "message": "ok" }这里可以看到返回的 VO 中石油total size等字段的，但是没有生效，于是就去官网看文档,于 v3.5.9 起，PaginationInnerInterceptor 已分离出来。如需使用，则需单独引入 mybatis-plus-jsqlparser 依赖 ， 具体请查看 安装 一章。解决办法在 Maven bom标题下，可以看到使用 maven bom 管理依赖，减少版本号的冲突。因为 jsqlparser 5.0+ 版本不再支持 jdk8 针对这个问题解耦 jsqlparser 依赖。 正确打开姿势，引入 mybatis-plus-bom 模块，然后引入 ..starter 和 ..jsqlparser.. 依赖所以需要引入对应的包管理器和依赖<dependencyManagement> <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-bom</artifactId> <version>3.5.9+ 版本</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>Spring Boot3 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> </dependency>Spring Boot2 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency>另外还需根据自己JDK版本引入不同的依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser-4.9</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-jsqlparser</artifactId> </dependency>此外，还需要在 Spring Boot 项目中，通过 Java 配置来添加分页插件@Configuration @MapperScan("scan.your.mapper.package") public class MybatisPlusConfig { /** * 添加分页插件 */ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加 // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType return interceptor; } }大功告成{ "code": 0, "data": { "records": [ { "id": 1923634783594762241, "userAccount": "admin", "userRole": "admin", "createTime": "2025-05-17T07:00:46.000+00:00" }, { "id": 1924441288589975553, "use…

JAVA学习笔记
type: item
date: 2025-04-14T19:44:00+08:00
description: 这是以前刚学 JAVA 时候记录的，现在补充到博客上JAVA学习笔记变量局部变量和全局变量全局变量有默认值，而局部变量即方法中的变量无默认值，因此方法中的变量必须进行复制才能引用局部变量前面不能加修饰符（public，private，等）变量类型注意JAVA中默认的小数常量为double类型，若需要float类型，则在数字末尾添加f或者F当类型为double或是float时，0.*中的0可省略，例如 0.1 可写成 .1自动转换类型低精度到高精度可自动转换转换规则： char->int->long->float->double byte->short->int->long->float->doubleeg：int num = "a"; //a是由char类型转换为int类型，从低精度转换为高精度 char -> int double d = 80 // int -> double自由转换数据类型转为字符串只需要在数值后面加上 "" 即可eg:int a = 10; String b = a + "";String类型转其它需要引入Integer类eg：//转为Int String a = "aa"; int b = Integer.parseInt(a); //转为double double c = Double.parseDouble(a); //以此类推即可运算符+-*/不再赘述%取余a % b => a - a / b * b前++和后++作为独立语句时i++和++i作为独立语句时据等价于i = i + 1作为非独立语句时i++表示先赋值后运算++i表示先运算后赋值eg：int a = 1; int b = a++; //等价于 b = a,a = a + 1 int c = 1; int d = ++c; //等价于 d = c + 1,c = d或c = c + 1三元运算符基本语法条件表达式?表达式1:表达式2运算规则1.若条件表达式返回为True，则执行表达式22.若条件表达式返回为False，则执行表达式2int a = 1; int b = 2; int result = a > b?a++:b--; // 返回result=1 // 这里原来的a，b变量仍然不会改变Switch如下代码，表达式中返回一个结果，case中匹配常量和结果，若相同，则执行同级case下的语句，否则匹配下个case，若没有相同的常量，则默认执行default下的语句表达式的返回值必须是 byte short int srting char enumcase后的语句必须是常量或是 常量表达式，不能是变量当执行完一个case语句没有break语句时，会顺序执行到switch结尾，直到遇到break，注意，期间不管case是否匹配，都会执行下面的语句switch(表达式){ case 表达式结果等于这里的常量时: // 语句1 break; case 常量2: // 语句2 break; default: // 语句三 break; }do...while与while不同的是，while是先判断在执行循环体，do...while是先执行循环体在判断while不一定执行循环体，do...while至少执行一次循环体int count = 0; do{ count++; System.out.println(count); }while(count<=10);break细节通过lable可控制具体跳出的循环体若不指定跳出的循环体，则默认跳出离break最近的循环体lable1:{}break lable1;lable1: for(){ lable2: while(){ lable3 do{ }while(); break lable1; break lable2; break lable3; } }递归函数强烈推荐使用内存分析法下面是案例分析阶乘案例class A{ public int factorial(int num){ if n == 1{ return 1; }else{ return factorial(num - 1) * num; } } } A test = new A(); int ret = test.facaorial(5); System.out.println(ret); // 返回结果是120分析首先传入参数5，进入到第6行的factorial函数中，然后再在栈中新开辟一个内存空间假设为t1，t1的sum为4，然后再在执行factorial函数，在开辟t2，t2sum为3，....t4sum为1，所以t4返回值为1，t4空间销毁回收，返回给t3，即1sum，t3的sum为2，即1*2,返回给t2，返回值为2，然后…