Lin 's Blog - 学习
type: channel
link: https://blog.lin03.cn/category/study/
items: n8n 新手上路：从一无所知到跑出第一个自动化流程
items: Rails 中 N+1 问题的研究
items: Vibe Coding 中，如何提高开发效率
items: MySQL性能优化
items: JUC相关练习
items: 闲鱼详情页json
items: 关于浏览器传参丢失精度的问题的解决办法
items: Vue3实现瀑布流
items: 关于Mybatis-Plus框架分页不生效问题解决
items: JAVA学习笔记

n8n 新手上路：从一无所知到跑出第一个自动化流程
type: item
date: 2025-11-27T14:47:00+08:00
description: AI 自动化这几年真的越来越重要了不管是处理重复任务、整理数据，还是用 AI 做一些智能化的流程，现在这些都能被自动化接管这是我第一次接触 n8n ，也是一无所知的新手，但在简单的了解过后，我已经跑通了一个完整的自动化工作流：从 RSS 读取今日热点 → AI 自动总结 → AI 自动制图 → 生成 HTML 日报页什么是 n8n如果你没用过 n8n，可以简单把它想象成开源的 Coze不同的是它：能自托管拖几个节点就能把各种 API 接起来和 AI 配合的时候真的很爽以前做自动化要写 Python / NodeJS，配定时任务，自己处理数据结构、容错之类的。现在很多东西拖一拖就完了n8n 的强大之处不仅在于它能做传统自动化（如备份、同步、发通知）而是在 AI 时代，它可以成为「调度大脑」它自己不做什么复杂逻辑，但非常擅长“把事情串起来”例如：使用 AI 进行复杂抓取与筛选多源 RSS / API / Webhook 信息聚合使用 AI 自动判断价值度对新闻进行 分类、比对、摘要、聚合自动过滤重复内容按重要程度排序，生成清晰结构化数据我的第一个流程：RSS → AI 总结 → AI 制图 → HTML 日报这是我第一次在 n8n 里动手做出的完整流程：从 RSS 获取热点 → AI 自动总结 → AI 自动生成日报图 → 生成 HTML 页面这里推荐一个持续跟进官方的 汉化版本 ，方便学习部署docker volume create n8n_data docker run -it --name n8n -p 5678:5678 -e N8N_RELEASE_TYPE=stable -e N8N_DIAGNOSTICS_ENABLED=false -e N8N_VERSION_NOTIFICATIONS_ENABLED=false -e N8N_HIDE_USAGE_PAGE=true -e N8N_LICENSE_AUTO_RENEW_ENABLED=false -e N8N_RUNNERS_ENABLED=true -e NODE_ENV=development -e N8N_DEFAULT_LOCALE=zh-CN -e N8N_ENTERPRISE_MOCK=true -v n8n_data:/home/node/.n8n ghcr.io/deluxebear/n8n:chs随后直接访问 http://localhost:5678 即可流程展示Webhook → RSS Read → AI Summary → AI Image → Markdown → HTML → Webhook Response整个流程非常直观，基本看一眼图就能理解整个 workflow 是如何工作的通过 Webhook 触发 RSS Read, 交给 AI 进行总结内容，随后将输出结果使用 AI 制作出图片,我这里使用的是 Nano Banana Pro 返回的是 Markdown 格式的 Base64,需要转换成 HTML 才能响应 Webhook，展示在前端。预览效果如下各节点分析Webhook（流程入口）这是整个流程的触发器, 只要访问 http://localhost:5678/webhook/xxxxx 就能出发整个流程的运行RSS Read这个节点负责获取信息源作为简单的项目演示，采用了 知乎每日新闻的 RSS https://plink.anyfeeder.com/zhihu/daily 运行返回结果如下[ { "title":"为什么古代中国没有其它文明那么多大型石砌建筑？", "link":"https://daily.zhihu.com/story/9785710", "pubDate":"Invalid Date", "content":" \n \n\n \n\n\n\n \n\n \n\n\n\n\n \n \n\n \n\n \n \nBrian C，有问题请私信加微信\n查看知乎原文\n \n\n \n 为什么古代中国没有其它文明那么多大型石砌建筑？ \n 因为任何大型建筑本质上就是两门科学“物理和数学”的应用。 \n 物理学上讲， \n 中国古代人无法把向下的力转化为水平的力， ...这些内容随后被送入 AI 做内容提炼与聚合AI 总结这一节点是整个工作流的核心我这里用的不是官方 OpenAI 接口，而是一个第三方聚合平台，所以 n8n 里直接用 HTTP Request 节点 来调用 AI。看起来很高端，其实流程非常简单：就是发一个接口，把 prompt 丢进去，让 AI 返回你想要的文本或是一些其他的如图片等拿到结果后，再用 n8n 的 Code 或 Set 节点稍微处理一下，就能继续用在下一步流程（例如生成 Ma…

Rails 中 N+1 问题的研究
type: item
date: 2025-10-25T15:01:00+08:00
description: 为什么 includes 让内存暴涨，而 SQL 子查询却如此高效 —— Rails N+1 查询的真相本文基于 Akshay Khot 的文章 和本地实验结果，深入剖析 Rails 中 N+1 查询的本质、为什么 includes 会消耗更多内存，以及 SQL 子查询（Subquery）在时间与空间上的取舍。🧩 什么是 N+1 查询在 Rails 开发中，N+1 查询是常见的性能陷阱。以 User 和 Post 两个模型为例：class User < ApplicationRecord has_many :posts end class Post < ApplicationRecord belongs_to :user end控制器中简单地读取文章列表：def index @posts = Post.order(created_at: :desc) end在视图中渲染每篇文章及其作者：<% @posts.each do |post| %> <%= post.title %> User：<%= post.user.name %> <% end %>这段代码背后会执行：一条查询读取所有 posts，随后对每个 post 再单独查询对应的 user。形成了“一条主查询 + N 条子查询”的模式，被称作 N+1 查询。当数据量较大时，多次数据库往返会显著拉长响应时间。⚙️ 性能分析工具：Rack Mini Profiler为了更好地观察这些查询带来的开销，我们使用 Rack Mini Profiler。在开发环境中安装并配置后，在浏览器 URL 后加 ?pp=profile-memory 可以看到页面顶部的性能面板，显示 SQL 查询次数、执行时间和内存占用等信息。🧪 实验环境与数据生成使用 Rails 8 和 SQLite 创建简化的 users、posts 表。每个用户生成 5 000 篇文章，共两名用户，以便观察不同查询方式的差异。⚖️ 三种方案的性能对比性能对比一览模式查询数页面加载时间SQL 总耗时内存分配Ruby 对象数N+1 查询10 0015.8 s146.3 ms5.9 KB86includes2188 ms56.7 ms455.4 KB3675子查询10 0015.7 s128.8 ms7.2 KB91🚨 方案一：N+1 查询在控制器中简单地 @posts = Post.order(created_at: :desc)，然后在视图中访问 post.user.name。这种方式代码简洁，但会导致每次访问关联字段都单独执行一次查询。截图： 下图展示了 N+1 查询在 Rack Mini Profiler 中的总览。可以看到页面渲染时间超过 5.8 秒，SQL 概述中显示查询次数达到 10001 条。内存分配： 另一张图给出了 N+1 查询的内存分配情况小结：简单但最慢——因为往返次数爆炸⚙️ 方案二：includes 预加载通过 includes 让 Rails 预先加载所有关联用户，减少数据库往返：@posts = Post.includes(:user).order(created_at: :desc)Rails 会先取出所有 posts，再批量查询这些 post.user 对象：截图： Rack Mini Profiler 显示 includes 方式只发起了 2 次查询，页面响应时间从 8 秒级降至约 188 毫秒内存占用： includes 虽然快了很多，但在内存里实例化了用户对象，仍有一定开销。测试中分配的内存为 455.4 kb，生成了 3675 个对象小结：用内存换查询次数。当仅需要某个字段时，把整批关联对象都载入内存，往往不划算。🧮 方案三：SQL 子查询（Subquery）第三种方式是将关联逻辑交给数据库，通过子查询直接把 users.name 字段放在 posts 查询结果中：subquery = User .select(:name) .where("users.id = posts.user_id") .limit(1) .to_sql @posts = Post .select("posts.*, (#{subquery}) AS author_name") .order(created_at: :desc)生成的 SQL 如下：SELECT posts.*, (SELECT users.name FROM users WHERE users.id = posts.user_id LIMIT 1) AS author_name FROM posts ORDER BY created_at DESC;理论上，这能减少 Rails 与数据库的往返和对象生成。但在测试环境中，每一条 post 的…

Vibe Coding 中，如何提高开发效率
type: item
date: 2025-09-27T20:20:56+08:00
description: Vibe Coding 中，如何提高开发效率随着 AI 技术的发展，越来越多的开发者开始尝试 Vibe Coding ——也就是把 AI 当作“结对编程的伙伴”，通过对话的方式完成代码编写、调试和优化一、现阶段主流 AI 工具对比目前我常用到的 AI 模型有 GPT-5、Gemini 2.5（Pro/Flash）以及 Claude 4.x。不同工具各有优势，适合的场景也不一样。工具优势适用场景GPT-5语言理解和推理能力极强，支持复杂的多轮对话和跨领域问题；适合生成计划以及修复 BUG复杂项目、跨栈开发、需要较高逻辑推理的任务Gemini 2.5 Pro上下文记忆更持久，长对话能力强，适合规划大型项目需要详细讨论和规划的长期开发Claude 4.x编码能力极强，且 UI 方面也非常不错。Plan 模式有助于结构化开发，代码逻辑清晰需要详细规划、文档和代码整合的场景补充：作为全栈开发，GPT-5 往往能提供更强的逻辑和上下文理解，利用其强大的理解能力，在配合上 Claude 的 Plan 模式。做到 先规划在执行 的场景，这样的开发会很流畅二、Vibe Coding 中常见问题在实际项目中利用 AI 开发，新手往往会遇到包括但不限于以下问题：1. Prompt 不清晰案例：我在 xxx 项目中想加一个团队管理功能，直接对 GPT-5 说“帮我做一个团队系统”，它给我生成了一个全新的 Team 模型和用户关联，但忽略了代码或者模板本身提供的如 Jumpstart 内置的组织/用户模型结构的功能。导致结果冲突很多，花了不少时间重构。重复造轮子2. 功能过于宽泛案例：我让 AI “帮我实现订阅功能”，它直接写了 Stripe 集成的控制器和视图，AI 可能会直接去帮你实现，但是确实按照了它的逻辑，如果你没有主动告诉 AI 你已经有部分实现的代码，那么大概率最终代码重复且冲突。3. 缺乏计划阶段案例：让 Claude 帮我“加一个发票模块”，Claude 写的代码看似完整，但路由、前端样式和 TailwindCSS 组件完全没有和前端模板对齐，导致的后果就是，同一个项目，像是不同的人在施工，参杂了各种冗余且复杂的代码。如果在一开始有计划的先让 AI 输出“发票模块所需的字段、数据库迁移、UI 组件和权限控制”，再进入编码，效率会更高。4. 上下文管理不当案例：在一次对话中不断添加新需求，比如“加多租户支持”“再加 API Key 管理”，但是没有维护一份需求文档。导致 GPT-5 忘记了我项目已经使用 Apartment gem 做多租户，后来又建议我用另外一种方案，冲突严重。三、解决思路与效率提升方法针对上述问题，总结出以下几条经验：1. 提升 Prompt 质量在 Vibe Coding 中，Prompt 就是需求说明书。AI 能输出什么样的结果，往往取决于你给它的输入。模糊的 Prompt 会让 AI 胡乱发挥，而一个精准的 Prompt 则能让 AI 像资深开发一样给出贴合需求的代码。Prompt 的作用明确边界：告诉 AI 该做什么、不该做什么，避免生成多余或冲突的代码。统一风格：通过 Prompt 说明项目用的框架、依赖和代码风格（例如 Rails 8 + Hotwire + TailwindCSS + Flowbite），让 AI 始终保持一致。减少返工：当 Prompt 已经涵盖关键细节，AI 生成的结果往往能直接使用，而不是需要开发者大规模重写。为什么好的 Prompt 能事半功倍降低沟通成本：把复杂需求转化成清晰的文字，AI 能一次性理解，大幅减少多轮交互。提高输出质量：明确的上下文信息，让 AI 生成的代码更可维护，避免“屎山代码”。提升团队效率：在多人协作时，标准化的 Prompt 能确保每个开发者和 AI 的交互保持一致，不会因为表达差异而导致风格混乱。优秀 Prompt 的参考资源在 GIthub 里，已经有不少优秀的 Prompt 示例可以直接拿来学习和套用：awesome-chatgpt-prompts 经典的 Prompt 收集库，涵盖开发、调试、代码审查等场景。prompt-engineering-for-developers 面向开发者的 Prompt 工程指南，详细解释了如何写高质量 Prompt，以及不同场景下的策略。高质量 Prompt 不仅告诉 AI 做什么，还明确了 如何做、用什么技术做，结果往往能直接落地，极大提升效率。2. 分解任务，逐步引导在 Vibe Coding 中，最容易犯的错误就是“一股脑把需求抛给 AI”。这样做的结果往往是：AI 给出一大段貌似完整的代码，但和你的项目框架、依赖不兼容，甚至出现“屎山代码”。解决办法有两个核心原则：任务拆解：把大需求分成小模块，逐步实现；计…

MySQL性能优化
type: item
date: 2025-07-12T22:50:21+08:00
description: 本篇文章只记录 索引优化 和 SQL优化索引优化有无索引对比准备 test 表CREATE TABLE `test` ( `id` bigint NOT NULL, `name` char(4) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;插入 100w 数据import pymysql import random import string import time # 数据库配置 (根据实际情况修改) DB_CONFIG = { 'host': 'localhost', 'user': 'your_username', 'password': 'your_password', 'database': 'your_database', 'charset': 'utf8mb4' } # 生成随机4字符名称 def random_name(): return ''.join(random.choices(string.ascii_letters + string.digits, k=4)) def insert_massive_data(): try: # 连接数据库 conn = pymysql.connect(**DB_CONFIG) cursor = conn.cursor() print("开始插入数据...") start_time = time.time() # 批量插入配置 batch_size = 5000 # 每批插入量 total_records = 1_000_000 batch_count = total_records // batch_size # 循环插入批次数据 for batch in range(batch_count): # 构建批量数据 data = [] for offset in range(batch_size): id_val = batch * batch_size + offset + 1 data.append((id_val, random_name())) # 执行批量插入 sql = "INSERT INTO test (id, name) VALUES (%s, %s)" cursor.executemany(sql, data) conn.commit() # 打印进度 if (batch + 1) % 10 == 0 or batch == 0: processed = (batch + 1) * batch_size print(f"已插入: {processed:,} 条, 进度: {processed/total_records:.1%}") # 插入剩余数据（如有） remaining = total_records % batch_size if remaining: data = [] start_id = total_records - remaining + 1 for i in range(remaining): data.append((start_id + i, random_name())) cursor.executemany(sql, data) conn.commit() print(f"最后一批插入完成: {remaining} 条") # 统计结果 duration = time.time() - start_time print(f"插入完成! 总共插入 {total_records:,} 条记录") print(f"耗时: {duration:.2f} 秒") print(f"平均速度: {total_records/duration:,.0f} 条/秒") except pymysql.Error as e: print(f"数据库错误: {e}") conn.rollback() finally: if conn: conn.close() if __name__ == "__main__": insert_massive_data()有索引情况无索引情况总结性能对比分析查询类型查询语句执行时间扫描行数​​有索引情况​​SELECT * FROM test WHERE id = 7894560.00s1​​无索引情况​​SELECT * FROM test WHERE name = 'abc'0.16s1,000,000关键发现​​索引的性能优势​​：​​数量级差异​​：索引查询仅扫描 1 行，而无索引查询扫描全部 100 万行​​时间差异​​：索引查询接近 0 秒(0.…

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) …

闲鱼详情页json
type: item
date: 2025-06-11T12:42:00+08:00
description: 最近看到某论坛上有关闲鱼获取详细的帖子，便有了写个脚本的想法闲鱼h5的研究可以看到咸鱼的这个这个接口便是对应的数据url: https://h5api.m.goofish.com/h5/mtop.taobao.idle.pc.detail/1.0/param: jsv=2.7.2&appKey=34839810&t=1749660271468&sign=d99215598a87fc030a45a608269f6906&v=1.0&type=originaljson&accountSite=xianyu&dataType=json&timeout=20000&api=mtop.taobao.idle.pc.detail&sessionOption=AutoLoginOnly&spm_cnt=a21ybx.item.0.0data: {"itemId":"859199211604"}cookie: ..._m_h5_tk=xxx; _m_h5_tk_enc=xxx经过我多次尝试，只有t,sign,_m_h5_tk,_m_h5_tk_enc是变化的，现在目标就很明确了编码实现t为13位时间戳，而经过查阅资料可以分析出，token: _m_h5_tk sign：token&t&appKey&data,然后取MD5_m_h5_tk会有过期时间，通过开发者工具，这个地址https://h5api.m.goofish.com/h5/mtop.gaia.nodejs.gaia.idle.data.gw.v2.index.get/1.0/在首次访问时候，在header里面的set-cookie能找到_m_h5_tk,_m_h5_tk_enc，而首次访问所携带的token必为空，也就是说，计算sign只需要t和data即可代码实现def _get_m_h5_tk(self) -> list: """获取API认证所需的_m_h5_tk令牌""" # 构建一个简单的请求数据和参数 data = '{"piUrl":"https://h5.m.goofish.com/wow/moyu/moyu-project/xy-site/pages/announcement"}' # 生成时间戳 t = str(round(time.time() * 1000)) token = "" appKey = "34839810" # token + & + t + & + appKey + & + data_str sign_string = f"{token}&{t}&{appKey}&{data}" sign = md5(sign_string.encode()).hexdigest() params = { 'jsv': "2.7.2", 'appKey': "34839810", "t": t, "sign": sign, 'api': 'mtop.gaia.nodejs.gaia.idle.data.gw.v2.index.get', 'v': '1.0', "type": "originaljson", "accountSite": "xianyu", "dataType": "json", "timeout": "20000", "sessionOption": "AutoLoginOnly", "spm_cnt": "a21ybx.item.0.0", } # API请求地址 api = 'https://h5api.m.goofish.com/h5/mtop.gaia.nodejs.gaia.idle.data.gw.v2.index.get/1.0/' response = requests.get(api, params=params) # 从响应cookies中提取_m_h5_tk cookies = response.cookies _m_h5_tk = cookies.get('_m_h5_tk', '') _m_h5_tk_enc = cookies.get('_m_h5_tk_enc', '') return [_m_h5_tk, _m_h5_tk_enc]['08c31977d09c9c8c610114029b8e17a5_1749669468863', 'b8d02be3c6b0ee2a0f5a21d8c7023b93']对应的获取商品详细也能写出来了def get_info(self, url: str): """ 获取闲鱼商品信息 """ itemId = self._get_itemId(url) if itemId is None: logging.error("获取商品ID失败") return None cookies…

关于浏览器传参丢失精度的问题的解决办法
type: item
date: 2025-05-27T20:37:00+08:00
description: 今天在写项目时候，需要前端传一个 number 类型的参数，可能由于类型过长，url接收时候直接丢失了精度，导致了一系列的错误前端源码const doEdit = () => { console.log(p.value.id) router.push({ path: '/add', query: { id: p.value.id }, }) }这里用控制台打印了 p.value.id 来确认是否是后端传参前端接收时丢失了精度，但是控制台打印是正常的结果 1927331787680264194, 但是看浏览器重重定向的地址为 http://localhost:8081/api/get/vo?id=1927331787680264200 明显是这里出现了问题问题出在 JavaScript 的数值精度限制上。JavaScript 使用 IEEE 754 双精度浮点数表示数字，最大可精确表示的整数是 2^53 - 1（约 16 位数字或 ±9007199254740991）所以出现了精度丢失的问题解决方案解决问题很简单 String(p.value.id) 只需把高精度的数字转换成 string 类型传入即可

Vue3实现瀑布流
type: item
date: 2025-05-22T20:04:00+08:00
description: vue使用 vue-waterfall-plugin-next 实现瀑布流Vue3瀑布流插件支持PC和移动端，包含Animate的各种动画效果以及图片懒加载功能。根据文档，可以先写出这样的一个例子 <Waterfall ref="waterfall" :list="list" :breakpoints="breakpoints"> <LazyImg class="card_img" :url="url" /> </Waterfall> 跨域问题文档提供的是本地图片，再用远程图片加载时候，不出意外地会出现跨域问题Access to image at 'https://emample.com/1.png' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 仔细研究文档，会看到 Props参数 里面有个跨域的参数参数名类型默认值描述crossOriginBooleantrue图片加载是否开启跨域把默认值修改成 false 即可，即 <Waterfall :crossOrigin="false">

关于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，然后…