编程导航React话题讨论

React

142 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

我的本科毕业设计《智搭低代码应用生成系统》开源啦!!!

# 智搭低代码应用生成系统 ## 项目简介 ![image.png](https://pic.code-nav.cn/post_picture/1644143521826918401/Y8icrhC5ms4krNpO.webp) ![image.png](https://pic.code-nav.cn/post_picture/1644143521826918401/PtBKsfoz5Vxtt1SJ.webp) ![image.png](https://pic.code-nav.cn/post_picture/1644143521826918401/nKthgIf3jhpAk0g9.webp) 本项目是我学习完编程导航的零代码应用生成平台系列教程交的作业,同时也是我的软件工程专业的本科毕业设计。 本项目由3个 Github 仓库共同组成,它们分别是: 后端:[ymz6/zhida-backend](https://github.com/ymz6/zhida-backend) 前端:[ymz6/zhida-frontend](https://github.com/ymz6/zhida-frontend) 应用项目模板:[ymz6/zhida-application-template](https://github.com/ymz6/zhida-application-template) 后端是用 Java 21 + SpringBoot 3 开发的,前端则是用 React 。 本项目的亮点: - 增加思考模式支持 - 通过预制项目模板来初始化应用 - 还有一些我自己也忘了,请各位鱼友自行探索吧! - 另外附带我开发这个项目的完整git提交记录,各位鱼友可以看到我起起落落落落落又起的、曲折的开发过程,方便各位从中学到些东西 毕业之后,我可能不打算对再此项目继续维护了,也许会存在一些大大小小的我没发现的Bug,有能力的鱼友可以 Fork 本项目进行二次开发,十分期待看到大家的二开作品! 最后真心希望我的项目对大家有所帮助,Love & Peace! ## 所需运行环境 后端:JDK 21 前端:Node.js >= 24 数据库:MySQL、Redis OSS:推荐用 Docker 起一个 RustFS 容器 操作系统:最好是 Windows,因为我为了开发方便,直接把 Nginx 可执行文件放在了后端项目中,使用其他操作系统的同学请自行下载 Nginx,并且我还用 AI 写了一些方便本地开发用的 Powershell 脚本 截图服务依赖:本项目有部署后自动截取应用封面图片的功能,由于固定使用的 Chrome 浏览器的驱动,因此若想要正常使用本功能,请务必确认在你本地电脑上安装有 Chrome 浏览器! ## 如何运行本项目? > 注意,本项目最终只是为了在本地演示给学校老师检查,因此并未考虑过部署上线的事情,所以跑起来会有些许麻烦。 > > 如果有鱼友有这方面需求,可以 fork 本项目进行改造。 ### 后端 ① 执行后端项目中的数据库初始化脚本(`sql/schemas.sql`) ② 准备自己的 AI Api Key。本项目为了开发方便,并未将大语言模型配置写在`application.yaml`中,而是通过代码进行配置,具体位于`app/src/main/java/org/ymz/app/ai/config/LLMsFactoryConfig.java`中,你需要在环境变量中设置对应大语言模型的 API Key。 ③ 以下配置需要你自行修改,推荐定位到后端项目的根目录中: ```yaml zhida: app-path: tmp-dir: C:/Users/ymz/Data/Codes/zhida/backend/tmp template-dir: C:/Users/ymz/Data/Codes/zhida/backend/project-template/zhida-react-project app-url: deploy-base-url: http://localhost:80 ``` ⑤ 准备 OSS 的访问密钥,本项目开发时使用的是通过本地 Docker 运行的 RustFS 容器。你可以替换为任何你喜欢的兼容 S3 协议的对象存储服务。这里只需要创建一个公共 bucket 即可,目前写的私有 bucket 可以不用动,原本我打算用来存放私有资源的,但是最终并未用上。 ⑥ 如果你要测试部署功能,请提前启动后端项目中集成的 Nginx。为了方便我让 AI 写了个用于方便启动和关闭 Nginx 的 Powershell 脚本,你可以通过它来启动/关闭 Nginx。 另外,你还需要修改 Nginx 的配置文件(`nginx/conf/nginx.conf`),重点是这一段: ```nginx # 项目部署根目录 root C:/Users/ymz/Data/Codes/zhida/backend/tmp/app-deploy; ``` 推荐定位到后端的根目录。 ⑦ 上述内容配置好后,便可以使用本项目内置的 maven 包装器来安装后端所需依赖,然后启动运行了。 ### 前端 本项目使用 pnpm 作为前端的包管理器。 安装完依赖就可以启动了

带你30分钟弄明白useContext的原理,教不会你随便喷!

## 前言 在React的函数组件中,`useContext`是一个非常重要的Hook,它让我们能够轻松地在组件树中共享状态。但是,你真的理解它的工作原理吗? ## 开宗明义:useContext的重新渲染原理 **useContext能重新渲染的原理,其实就是订阅发布者模式!** - **订阅阶段**:当使用`useContext`时,其实就是在订阅。订阅做的事情其实就是把这个fiber添加一些特殊的标记(dependencies)。 - **发布阶段**:等到发布者(Provider)的value发生改变时,就会执行发布这个行为。发布的行为就是给之前的特殊标记的fiber节点标记上更新。 - **渲染阶段**:其他事情交给React的渲染机制去做了。 这就是useContext重新渲染的完整流程! ## 目录 1. [订阅与发布机制](#订阅与发布机制) 2. [渲染周期详解](#渲染周期详解) 3. [Fiber树结构与依赖关系](#fiber树结构与依赖关系) 4. [基本概念澄清与常见误区](#基本概念澄清与常见误区) ## 订阅与发布机制 ### 订阅机制 当函数组件调用`useContext`时,React会在**订阅者组件**的fiber上记录依赖关系: ```javascript function useContext(Context) { const fiber = getCurrentFiber(); if (fiber) { // 在订阅者组件的 fiber 上记录依赖关系 if (!fiber.dependencies) { fiber.dependencies = []; } // 只需要注意这里,这里就添加了特殊标记 fiber.dependencies.push({ context: Context, fiber: fiber }); } return Context._currentValue; } ``` **关键点**:订阅关系记录在**订阅者组件**的fiber上,而不是Provider的fiber上。 ### 发布机制 当Provider的值发生变化时,React会遍历整个fiber树来找到所有订阅者,并且确保找到的订阅者订阅的是这个特定的发布者: ```javascript function propagateContextChange(context, fiber) { let current = fiber.child; while (current !== null) { // 检查当前 fiber 是否订阅了目标 Context if (current.dependencies) { for (let i = 0; i < current.dependencies.length; i++) { const dependency = current.dependencies[i]; if (dependency.context === context) { // 找到订阅者,标记需要更新 scheduleUpdateOnFiber(current); break; } } } // 继续遍历子节点 if (current.child !== null) { current = current.child; continue; } // 遍历兄弟节点 while (current.sibling === null) { if (current.return === null || current.return === fiber) { return; } current = current.return; } current = current.sibling; } } ``` **重要理解**: - 遍历整个fiber树是为了找到**全部**的订阅者 - 一个订阅者组件可以订阅多个发布者,比如: ```javascript const Child = () => { const user = useContext(UserContext); // 订阅 UserContext const theme = useContext(ThemeContext); // 订阅 ThemeContext return <div>{user.name}</div>; }; ``` ## 渲染周期详解 ### 完整的渲染流程 让我们通过一个具体例子来理解整个渲染周期: ```javascript const App = () => { const [count, setCount] = useState(0); return ( <CountContext.Provider value={count}> <Child /> </CountContext.Provider> ); }; const Child = () => { const count = useContext(CountContext); return <div>{count}</div>; }; ``` 当`setCount(1)`被调用时,完整的渲染流程如下: #### 1. 状态更新 ```javascript setCount(1); // 触发状态更新 ``` #### 2. React调度更新 ```javascript scheduleUpdateOnFiber(rootFiber); // 调度根fiber的更新 ``` #### 3. 开始渲染流程 ```javascript renderRootSync(root); // 开始同步渲染流程 ``` #### 4. 处理App组件 ```javascript updateFunctionComponent(appFiber); // App 重新执行,Provider 接收到新的 value ``` #### 5. 处理Provider ```javascript updateContextProvider(providerFiber); // 比较新旧值,发现不同,触发发布 ``` #### 6. 通知订阅者 ```javascript propagateContextChange(context, providerFiber); // 找到 Child 组件,标记需要更新 ``` #### 7. 处理Child组件 ```javascript updateFunctionComponent(childFiber); // Child 重新执行,useContext 返回新值 ``` #### 8. 渲染到DOM ```javascript commitRoot(root); // 将更新提交到DOM ``` ### 关键函数实现 #### updateContextProvider ```javascript function updateContextProvider(current, workInProgress, Component, nextProps) { const context = workInProgress.type._context; const newValue = nextProps.value; const oldValue = current.memoizedState; // 比较新旧值 if (is(oldValue, newValue)) { // 值相同,不更新 return; } // 值不同,更新 Context 的值 context._currentValue = newValue; // 标记需要更新 workInProgress.flags |= Update; // 通知所有订阅者 propagateContextChange(context, workInProgress); } ``` ## Fiber树结构与依赖关系 ### 为什么需要遍历整个fiber树 让我们通过一个具体的例子来说明: ```javascript const App = () => { const [count, setCount] = useState(0); return ( <CountContext.Provider value={count}> <div> <Header /> <Main> <C1 /> {/* 订阅者 1 */} </Main> <Footer> <C2 /> {/* 订阅者 2 */} </Footer> </div> </CountContext.Provider> ); }; const C1 = () => { const count = useContext(CountContext); return <div>{count}</div>; }; const C2 = () => { const count = useContext(CountContext); return <div>{count}</div>; }; ``` ### Fiber树结构 ``` // fiber 树结构: // App // └── div // ├── Header // ├── Main // │ └── C1 (订阅者) // └── Footer // └── C2 (订阅者) // 需要遍历整个树才能找到 C1 和 C2 ``` ### 依赖关系分布 ```javascript // 每个组件的 dependencies 分布: // App 组件(Provider) appFiber.dependencies = [] // 说明:App 没有使用 useContext,只是 Provider // Header 组件 headerFiber.dependencies = [] // 说明:Header 没有使用 useContext // Main 组件 mainFiber.dependencies = [] // 说明:Main 没有使用 useContext // C1 组件(订阅者) c1Fiber.dependencies = [ { context: CountContext, fiber: c1Fiber } ] // 说明:C1 订阅了 CountContext // Footer 组件 footerFiber.dependencies = [] // 说明:Footer 没有使用 useContext // C2 组件(订阅者) c2Fiber.dependencies = [ { context: CountContext, fiber: c2Fiber } ] // 说明:C2 订阅了 CountContext ``` ### 遍历查找过程 ```javascript // 当 CountContext 值变化时,遍历过程: function propagateContextChange(context, fiber) { let current = fiber.child; // 从 div 开始 while (current !== null) { // 检查当前 fiber 的 dependencies if (current.dependencies) { for (let i = 0; i < current.dependencies.length; i++) { const dependency = current.dependencies[i]; if (dependency.context === context) { // 找到订阅者! scheduleUpdateOnFiber(current); break; } } } // 继续遍历... if (current.child !== null) { current = current.child; continue; } // 遍历兄弟节点 while (current.sibling === null) { if (current.return === null || current.return === fiber) { return; } current = current.return; } current = current.sibling; } } ``` ## 基本概念澄清与常见误区 ### 重要纠正:函数组件的useContext **前提说明**:一些文章里说到通过`fiber.tag === ContextConsumer`判断是否是订阅者组件时,其实是在类组件里,在函数组件中不是这样。 在函数组件中: ```javascript // 当组件使用 useContext 时 const Child = () => { const count = useContext(CountContext); // 这里只是获取值 return <div>{count}</div>; }; // 这个组件仍然是 FunctionComponent,不是 ContextConsumer // fiber.tag === FunctionComponent ``` **关键点**:`useContext`本身**不会**创建`ContextConsumer` fiber。`ContextConsumer`实际上对应的是类组件时代的`Context.Consumer`。 ### dependencies的真正含义 每个组件的`dependencies`只记录**自己**订阅了哪些Context: ```javascript // 当组件使用 useContext 时 const C1 = () => { const count = useContext(CountContext); // C1 订阅了 CountContext return <div>{count}</div>; }; const C2 = () => { const count = useContext(CountContext); // C2 也订阅了 CountContext return <div>{count}</div>; }; // C1 的 fiber.dependencies = [ // { context: CountContext, fiber: c1Fiber } // ] // C2 的 fiber.dependencies = [ // { context: CountContext, fiber: c2Fiber } // ] // 注意:A 的 dependencies 是空的,因为 A 没有使用 useContext // A 只是 Provider,不是消费者 ``` #### 认为有专门的订阅者数组 ```javascript // ❌ 错误:认为 React 维护了订阅者数组 // CountContext.subscribers = [C1, C2] // ✅ 正确:React 通过遍历 fiber 树来找到订阅者 // 每个组件的 dependencies 只记录自己订阅了哪些 Context ``` ## 总结 通过本文的深入解析,我们了解了`useContext`的核心原理: 1. **订阅机制**:组件调用`useContext`时,在**订阅者组件**的fiber上记录依赖关系 2. **发布机制**:Provider值变化时,遍历fiber树找到所有订阅者,确保找到的订阅者订阅的是这个特定的发布者 3. **渲染流程**:从状态更新到DOM渲染的完整8步流程 4. **依赖关系**:每个组件的`dependencies`只记录自己订阅的Context 5. **常见误区**:澄清了dependencies的真正含义和遍历的必要性 `useContext`的设计体现了React的响应式理念,通过fiber树的天然结构实现了高效的跨组件状态共享。理解这些原理,不仅有助于我们更好地使用Context,也能在面试中展现出对React内部机制的深入理解。 --- **记住**:在函数组件中,`useContext`不会创建`ContextConsumer` fiber,而是通过`dependencies`记录订阅关系,通过遍历fiber树来找到所有订阅者。这是React Context机制的核心所在。

千万级科技公司高薪招聘

00后科技公司高薪招聘! 准备启动一个新的AI应用的开发项目,目前缺少前端开发工程师,欢迎来投简历,有梦想你就来,只看实力不看学历或者工作经验! 公司在合肥,来了之后包住宿,公司核心优势简单说一下,不是初创公司,已经发展两年了,现在是千万级别,还在稳定上升中 老板和公司人员基本都是00后,最大的不超过95,所以交流氛围很轻松,工作强度不高,没有条条框框的约束,只需要按照公司的开发进程做就行了 因为原先技术大多外部合作,最近几个月开始组建公司自营技术团队,求贤若渴,所以涨薪比较快,发展前景高 ps:AI应用开发的全栈工程师也可以投简历,待遇比前端开发工程师会高 ![a07e33fbe65df21df0cecef0aae188d.jpg](https://pic.code-nav.cn/post_picture/1928043786928259073/Y8Te1iCBHuLXRRCN.webp)

【跟着博主做】仿DeepSeek官网AI聊天网站React(Next.js)项目实战-登陆页面

个人前端小白,在跟着B站一位up @爱吃豌豆尖的Ziho做一个React(Next.js)的网站 做的时候遇到了大量的问题(有不少愚蠢问题Os...),有不少问题是在豆包的帮助下解决的,如果同样的是小白的话,可能也会遇到类似的问题,在这里小做记录,节省诸位时间。 ~~在这里发跟其他博主学习的笔记不会被鱼皮打吧˜~~ ## 登录篇 ### ziho创建完项目之后运行了npm run dev,这个命令是干嘛的? > Q:这个作者在初始化完项目之后,先运行了一个 npm run dev ,然后就把项目给起来了。但这个命令只是单单的用来起项目吗?他是有什么背后的深意吗?或者说一般开发前端项目上来都要先运行一个这个吗? A: `npm run dev`是一个常用的命令,通常用于启动开发服务器来运行前端项目。 - **启动开发服务器**:`npm run dev`的主要作用是启动一个开发服务器,它会监听项目文件的变化,并在浏览器中实时更新页面。这样,开发人员在编写代码时,只要保存文件,浏览器就会自动刷新,显示最新的页面效果,大大提高了开发效率。以 React 项目为例,当执行`npm run dev`后,开发服务器会将项目的代码进行打包和编译,然后在本地的一个指定端口(如 3000 端口)启动一个服务器,开发人员可以通过浏览器访问该端口来查看项目的运行效果。【改了立马就能看见,真好啊。。。】 - **背后的深意**:它不仅仅是简单地启动项目,还涉及到一系列的构建和编译过程。在开发过程中,前端项目通常会使用各种现代的开发工具和技术,如模块化、代码转换(如将 ES6 + 代码转换为浏览器兼容的 ES5 代码)、样式预处理等。`npm run dev`命令会触发这些工具和技术的执行,将项目的源代码转换为浏览器能够理解和运行的形式。同时,它还会提供一些开发时的辅助功能,如热模块替换(HMR),可以在不刷新整个页面的情况下更新部分代码,保持应用的状态,让开发体验更加流畅。 - **使用场景**:在前端开发中,尤其是使用像 Next.js、Create React App 等脚手架工具创建的项目,`npm run dev`是非常常见的启动开发环境的方式。不过,不同的项目可能会根据自身的需求和配置有所不同。例如,一些 Vue.js 项目可能会使用`npm run serve`来启动开发服务器,其功能与`npm run dev`类似,但具体的配置和执行的任务可能会有差异。此外,对于一些简单的静态网页项目,可能不需要这样的开发服务器,直接在浏览器中打开 HTML 文件即可查看效果,但对于使用了框架和构建工具的现代前端项目,`npm run dev`是启动项目开发环境的重要步骤。 --- ### 开发登陆功能的时候使用Clerk,这是啥?为啥要用它? >Q: 接下来作者想要实现一个登录功能,他并没有采用原本的这个,像一般人,在我看来一般人不是应该先从登录页面写起吗?但是他没有,他直接用了一个 Clerk。这是一个登录插件吗?就是说这个插件只负责登录功能,然后你就可以引入它,帮助你快速开发代码。现在都这么玩吗? A: Clerk是一个托管身份验证提供程序,它可以帮助开发者在应用程序中快速实现身份验证和用户管理功能,而不仅仅是一个简单的登录插件。使用Clerk这样的工具来实现登录功能有以下优点: - **降低开发难度**:从零开始构建安全的身份验证系统是一项复杂的任务,需要处理密码哈希和加盐、会话管理、防止恶意攻击等多个方面。而Clerk封装了这些复杂的逻辑,开发者无需自己处理底层的安全机制,大大降低了开发门槛和工作量。 - **提供多种功能**:Clerk支持多种登录方式,如电子邮件验证、无密码登录、双因素身份验证以及社交身份验证(如Google、GitHub登录)等。同时还提供了用户资料管理、密码重置等功能,能满足不同应用场景下的需求。 - **良好的开发者体验**:Clerk的API设计简洁直观,易于集成到各种框架和项目中。在Next.js项目中,只需进行一些简单的配置和安装依赖,就可以快速使用其提供的组件和功能来实现登录和用户管理界面。 - **保证安全性**:Clerk作为专业的身份验证服务提供商,会采用一系列安全措施来保护用户数据,如密码加密、认证令牌管理等,确保应用程序的安全性。 在现代前端开发中,使用类似Clerk这样的第三方身份验证服务是很常见的做法。这样可以让开发者专注于应用程序的核心功能开发,提高开发效率,同时也能保证身份验证系统的安全性和可靠性。除了Clerk,还有一些其他的身份验证服务和库,如NextAuth.js、Firebase Authentication等,也被广泛应用于不同的项目中。 --- ### 作者在引入Clerk的过程中,创建了文件".env",为啥要有这一步? >Q: 我看到引入 clerk 的一个步骤是要在我们的项目里创建了一个.env 文件,说实话,这个文件一般放一些环境变量一类的东西吗?他为什么以点开头? A: `.env`文件通常用于存放环境变量。以下是关于它以点开头及相关原因的介绍: - **以点开头的原因**:以点开头是一种常见的命名约定,用于表示该文件是一个隐藏文件。这样做的目的是为了避免在项目目录中被意外显示和误操作,同时也遵循了Unix和类Unix系统中隐藏配置文件的传统。在大多数开发环境和终端中,隐藏文件不会默认显示,只有通过特定的命令或设置才能查看,这有助于保持项目目录的整洁和有序,防止开发者不小心修改或删除重要的配置文件。 - **存放环境变量的作用**: - **区分不同环境**:在开发过程中,通常会有开发环境、测试环境和生产环境等。不同环境可能需要连接不同的数据库、使用不同的API密钥或有不同的配置参数。通过`.env`文件,可以方便地根据不同的环境设置相应的变量值,而无需在代码中硬编码这些信息。例如,在开发环境中可以使用本地的数据库连接字符串,而在生产环境中则使用正式的数据库连接字符串。 - **安全性**:对于一些敏感信息,如API密钥、数据库密码等,将它们放在`.env`文件中可以避免将这些信息直接暴露在代码仓库中。这样,即使代码仓库被公开,敏感信息也不会被轻易获取。同时,可以在`.gitignore`文件中添加`.env`,防止将环境变量文件提交到版本控制系统中,进一步提高安全性。 - **灵活性**:当需要修改某些配置参数时,只需要在`.env`文件中进行修改,而无需在代码中查找和修改多个地方。这使得项目的配置更加灵活和易于维护。 在使用`.env`文件时,需要注意确保项目的构建和运行脚本能够正确读取其中的环境变量,并在代码中通过相应的方式来访问这些变量,以实现根据不同环境进行灵活配置的目的。 > Q: 这么看来,它的作用类似于java后端开发的yml文件 A: `.env`文件在前端开发中与Java后端开发中的`.yml`(通常是指Spring Boot项目中的`application.yml`等配置文件)有一些相似之处,但也存在一些区别,具体如下: #### 相似之处 - **配置信息管理**:两者都用于存储项目的配置信息,将配置与代码分离,使项目在不同环境下的配置管理更加方便。例如,都可以在这些文件中配置数据库连接信息、服务器端口号、日志级别等。 - **环境区分**:都可以根据不同的环境(开发、测试、生产等)设置不同的配置值。以Java后端为例,通过配置不同的`application-{env}.yml`文件,在启动项目时指定不同的环境变量来加载相应的配置。前端的`.env`文件也类似,通过设置不同的`.env.development`、`.env.production`等文件,在构建或运行项目时根据环境加载对应的配置。 - **提高可维护性**:当项目的配置信息发生变化时,只需要在相应的配置文件中进行修改,而无需在大量的代码中查找和修改相关配置,提高了项目的可维护性和可扩展性。 #### 不同之处 - **语法格式**:`.env`文件的格式较为简单,通常是每行一个键值对,以`KEY=VALUE`的形式存储配置信息。而`.yml`文件采用YAML语法,具有更严格的缩进格式和层级结构,适合存储复杂的配置信息,能够以更清晰的方式表示对象、数组等数据结构。 - **功能定位**:`.env`文件主要用于存储环境相关的变量,侧重于为前端项目提供运行时的环境配置。而Java后端的`.yml`文件功能更为强大,除了配置环境变量外,还可以用于配置Spring Bean的属性、AOP切面、数据库连接池、消息队列等各种框架和中间件的复杂配置,对整个后端应用的运行和功能起着关键的作用。 - **加载方式**:前端项目中,`.env`文件的变量通常在项目构建或运行时由相关的工具(如Webpack、Vite等)进行加载和处理,并将其注入到前端代码中供使用。在Java后端中,`application.yml`等配置文件是由Spring框架在启动过程中进行加载和解析,通过配置类或注解等方式将配置信息注入到相应的Bean中。 --- ### ziho在引入Clerk的过程中创建了一个`middleware`文件,官网的解释也看不懂,这玩意有啥用? >Q: 我看到他让我们创建了一个 middleware 的文件。然后官网给出了一个的解释。The clerkMiddleware helper enables authentication and is where you'll configure your protected routes.。这句话是什么意思?根据英语来看,就是说引入的这段代码能够,帮助启用校验。这也是一个你能够保护你的routes的一个地方。这对一个新手小白来说,缺乏背景知识,比较难以理解。 A: 这句话准确的意思是:“Clerk的中间件助手能够实现身份验证功能,并且你可以在这个中间件里配置你的受保护路由。” 下面为你更详细地解释一下,帮助你理解: - **中间件(middleware)**:在Web开发中,中间件是介于请求和响应处理之间的代码层。它可以对进入应用的请求进行预处理,也可以对应用发出的响应进行后处理。就好比一个关卡,所有经过的请求和响应都要先经过它的检查和处理。 - **身份验证(authentication)**:简单来说就是确认用户的身份,判断用户是不是他所声称的那个人。比如用户登录时输入用户名和密码,系统验证用户名和密码是否正确,这就是身份验证的过程。在这个场景中,Clerk的中间件助手就负责处理这些验证逻辑,确保只有通过身份验证的用户才能访问特定的资源。 - **受保护路由(protected routes)**:路由可以理解为Web应用中不同页面或功能的地址路径。受保护路由就是指那些只有特定权限(比如已登录的用户)的用户才能访问的路由。通过配置中间件,你可以指定哪些路由是受保护的。当用户尝试访问这些受保护路由时,中间件会先检查用户是否已经通过身份验证,如果没有,就会阻止用户访问,并可能引导用户去登录页面进行登录。 举个例子,假如你开发了一个博客应用,只有注册并登录的用户才能发布新博客文章。那么“发布新博客文章”对应的页面路径就是一个受保护路由,你可以在Clerk的中间件里配置这个路由为受保护的,这样当未登录的用户尝试访问这个页面时,就会被拦截并要求先登录。 --- ### 引入过程中又改了一个layout.tsx,这个layout是干什么的? A: 在前端项目(尤其是使用 React 相关框架如 Next.js 时)中,`layout.tsx` 文件有着重要的作用,以下为你详细介绍: 在 Next.js 里,`layout.tsx` 是布局文件,它可以定义页面的整体结构和公共部分。比如网站的头部导航栏、底部的页脚、侧边栏等,这些公共的 UI 组件都可以放在 `layout.tsx` 中,这样不同的页面就可以复用这些公共部分,避免重复编写代码。 当你需要改动 `layout.tsx` 文件时,一般可能是以下几种情况: 1. **添加或修改公共组件**:比如要在网站的每个页面都添加一个新的功能按钮或者修改导航栏的样式,就需要在 `layout.tsx` 中进行操作。在你的项目中,可能是为了集成 Clerk 相关的功能,比如在布局中引入 Clerk 的提供程序(provider)组件,来确保整个应用的身份验证功能能够正常工作。例如,可能需要将 `<ClerkProvider>` 组件包裹在布局的顶层组件中,这样所有页面都能访问到 Clerk 的上下文信息,实现登录、注销等功能。 2. **处理页面级别的数据和状态**:`layout.tsx` 还可以处理一些页面级别的数据获取和状态管理。虽然它主要负责 UI 布局,但也可以在其中进行一些数据的初始化或者与后端 API 的交互,然后将数据传递给子页面组件。 3. **配置页面的元数据**:例如设置页面的标题、描述、关键字等元数据,这些信息对于搜索引擎优化(SEO)和社交媒体分享很重要。在 `layout.tsx` 中可以方便地统一配置这些元数据,确保每个页面都有合适的描述信息。 总之,`layout.tsx` 文件是定义应用整体布局和公共行为的关键部分,改动它是为了让应用的结构和功能符合项目的需求,在集成 Clerk 功能时,也是为了让身份验证相关的逻辑能够在整个应用中正确运行。如果你在改动过程中遇到具体的问题,可以随时问我。 --- ### 在引入`<SignIn />`标签的时候,这种代码结构我从来没见过,这玩意是啥呀? > Q:我对下面这段代码有点问题,我没见过这种代码形式: ``` import { SignIn } from '@clerk/nextjs' export default function Page() { return ( <div className="flex justify-center items-center h-screen w-screen"> <SignIn /> </div> ) } ``` > 前面improt我能理解,类似于python的引入是吧,后面的export,还是一个函数,函数一般来说不是处理逻辑的的吗,最后怎么返回一个`<div>`?这不是html代码么?现在框架做项目已经不用html文件来显示页面,只用其代码进行拼凑吗?,还有,className里面的这些配置都是些什么意思? A: 你观察到的是现代前端框架(如React)的典型写法,我来逐一解释: #### 1. 关于 `export default function Page()` 这是ES6模块语法,用于导出组件。在React里,页面/组件通常定义为函数,称为"函数组件"。函数返回的不是HTML,而是JSX语法(JavaScript XML)。 ```javascript // 这是一个React函数组件 export default function Page() { return <div>Hello World</div> } ``` 这种写法本质是JavaScript,JSX会在编译时被Babel转换为React.createElement()调用,最终渲染为DOM元素。 #### 2. 为什么不用HTML文件? 传统Web开发确实依赖HTML文件,但React采用"组件化"思想: - 页面由多个独立组件拼装而成 - 组件内部封装自己的结构(JSX)、样式(CSS)和逻辑(JavaScript) - 最终通过工具打包成一个或多个HTML文件 这种方式使代码更易维护和复用,例如: ```javascript // 可复用的Button组件 function Button({ text, onClick }) { return <button onClick={onClick}>{text}</button> } // 在其他组件中使用 function App() { return ( <div> <Button text="提交" onClick={() => console.log('点击')} /> </div> ) } ``` #### 3. className里的配置(如flex justify-center) 这是Tailwind CSS的类名系统。Tailwind是一个工具类优先的CSS框架,直接在HTML标签上写样式: ```html <!-- 传统CSS --> <div class="container"></div> <style> .container { display: flex; justify-content: center; align-items: center; } </style> <!-- Tailwind方式 --> <div class="flex justify-center items-center"></div> ``` 这些类名是预定义的工具类,对应特定的CSS属性,例如: - `flex` → display: flex - `justify-center` → justify-content: center - `h-screen` → height: 100vh #### 4. 实际渲染流程 虽然代码里看不到HTML文件,但最终会生成HTML。以Next.js项目为例: 1. 编写React组件(.jsx/.tsx文件) 2. 通过构建工具(如Webpack、Vite)编译 3. 生成静态HTML文件或服务器端渲染(SSR) 4. 浏览器加载HTML并执行JavaScript代码 #### 5. 推荐学习资源 - React官方文档:https://react.dev/ - Tailwind CSS文档:https://tailwindcss.com/ - Next.js教程:https://nextjs.org/learn --- > Q: 这些内容都有什么用?flex justify-center items-center h-screen w-screen A: 这些都是Tailwind CSS的工具类,用于快速设置元素的样式。我来拆解它们的作用和实际效果: ##### 1. `flex` - **作用**:设置 `display: flex` - **效果**:将元素变为弹性容器,子元素可以使用flex布局的特性 ##### 2. `justify-center` - **作用**:设置 `justify-content: center` - **效果**:在主轴(默认水平方向)上居中对齐子元素 ##### 3. `items-center` - **作用**:设置 `align-items: center` - **效果**:在交叉轴(默认垂直方向)上居中对齐子元素 ##### 4. `h-screen` - **作用**:设置 `height: 100vh` - **效果**:元素高度等于浏览器视口高度 ##### 5. `w-screen` - **作用**:设置 `width: 100vw` - **效果**:元素宽度等于浏览器视口宽度 #### 组合效果演示 当这些类一起使用时,会创建一个占满全屏且内容居中的容器: ```html <div class="flex justify-center items-center h-screen w-screen bg-gray-100"> <div class="bg-white p-8 rounded-lg shadow-lg"> <h1 class="text-2xl font-bold">居中的内容</h1> <p class="mt-2">这个元素在屏幕中水平和垂直都居中</p> </div> </div> ``` #### 常见应用场景 1. **登录/注册页面**:让表单在屏幕中央显示 2. **加载状态**:全屏加载指示器居中 3. **模态框**:确保内容在各种屏幕尺寸下都居中

AI 超级智能体笔记和作业 - AI 应用部分

# 项目代码 开源地址:https://github.com/waitlistSawana/ai-super-agent-yupi-nextjs 上线地址:https://ai-super-agent-yupi-nextjs.hsawana9.com/ 基于 Nextjs 后端实现项目课程中提到的功能,主要参考 [AI SDK](https://ai-sdk.dev/) 的实现,提供 [Shadcn Registry 服务](https://ai-super-agent-yupi-nextjs.hsawana9.com/#registry-list) 快速复制代码到本地。 欢迎 star ⭐! # 本章学习 - 提示词工程 - 生成文本 - 多轮对话 Chatbot - 拦截器(中间件) - 结构化输出 - 记忆持久化(本地文件) - 记忆持久化(数据库) ## 1. 提示词工程 多步骤提示词,xml 结构的提示词。在 Dify、阿里云百炼 迭代提示词,效果非常满意: - 角色设定 - 步骤更新 - 话题拓展 - 对话结束 - 工具调用 ```markdown <identity> <role>机器学习研究教练</role> <expertise> <area>机器学习</area> <area>数据科学</area> <area>音频处理</area> <area>计算机视觉</area> <area>自然语言处理</area> </expertise> <tone>友好、引导式、专业但平易近人</tone> </identity> <purpose> 作为您的机器学习研究伙伴,我的目标是帮助您在研究过程中取得进展。我专注于引导您完成构建基本流水线-基准模型阶段的训练前分析讨论模块,采用分步骤讨论的方式:首先认识问题,然后理解数据,接着确定研究目标,最后规划基础流水线。每个阶段我们会充分讨论并获得您的确认后,才会进入下一小节。 </purpose> <conversation_flow> <phase name="认识问题" current="true" done="false"> <focus> <point>帮助用户深入理解研究问题的本质和挑战</point> <point>分析问题的类型(分类、回归、聚类等)</point> <point>探讨问题的应用背景和实际意义</point> <point>主动提出关键问题点和可能的难点</point> </focus> <transition> <question>您对问题的理解是否清晰?我们是否可以进入下个话题,开始探索数据特征?</question> </transition> </phase> <phase name="认识数据" current="false" done="false"> <focus> <point>请求用户提供关于数据集、数据列、元数据的详细信息</point> <point>探索数据集、数据列、元数据的结构和特征,描述数据集、数据列的含义和作用</point> <point>元数据的质量、分布和潜在偏差</point> <point>元数据间的潜在关系和相互影响</point> <point>分析数据列的现实意义,预期影响以及在本课题中的意义</point> </focus> <transition> <question>您对数据的特点是否已有清晰认识?我们是否可以进入下个话题,明确研究目标?</question> </transition> </phase> <phase name="确定目标" current="false" done="false"> <focus> <point>帮助用户明确具体的研究目标</point> <point>可能的模型</point> <point>合适的评估指标</point> <point>确定表现良好和不良好的标准</point> <point>设定现实可行的阶段性目标</point> </focus> <transition> <question>您对研究目标和评估方法是否满意?我们是否可以进入下个话题,规划基础流水线?</question> </transition> </phase> <phase name="规划基准流水线" current="false" done="false"> <focus> <point>讨论最小可行的处理流程,基准模型</point> <point>建议适合的基线模型</point> <point>设计简单但完整的实验验证方案</point> </focus> <transition> <question>您对这个基础流水线的规划是否满意?是否进入下一阶段:模型训练阶段?</question> <action>结束对话,唤起结束对话工具</action> </transition> </phase> </conversation_flow> <information_verification> <action>验证用户提供的链接和资料是否可访问和充分</action> <action>检查是否能获取到研究问题和数据集的基本信息</action> <action>当信息不足时,明确向用户索要特定信息</action> <insufficient_info_response> <template> 我注意到您提供的{资源类型}似乎{问题描述}。为了更好地帮助您,我需要了解以下信息: 1. {数据集的基本结构} 2. {可用的特征、标签或元数据信息} 3. {包含数据集信息的网址链接} 4. {...具体需要的信息} 您能否提供这些信息,这样我们才能开始有效地分析研究问题? </template> </insufficient_info_response> </information_verification> <response_guidelines> <guideline>本阶段只关注最基本的流水线实现,目标是引导用户完成基准模型,不主动进行拓展和优化建议</guideline> <guideline>每次回复仅聚焦于当前阶段的内容,不要提前讨论后续小节,适当引导话题回归</guideline> <guideline>使用自然、对话式的语言,避免过于机械化的结构</guideline> <guideline>像一位经验丰富的导师一样交流,主动提出见解和分析</guideline> <guideline>在每个阶段结束时,明确询问用户是否准备进入下一阶段</guideline> <guideline>只有在用户明确确认后,才更新当前阶段并进入下一阶段</guideline> <guideline>如果用户有疑问或需要更多讨论,继续深入当前阶段的内容</guideline> <guideline>不提供任何代码实现,专注于概念和方法的讨论</guideline> <guideline>如果用户跳跃话题或提前要求代码实现,指出其在后续章节,并礼貌地引导用户回到当前小节</guideline> </response_guidelines> <phase_transition> <action>仅在用户明确确认后,将当前小节标记为完成</action> <action>将下一小节的current属性设置为"true"</action> <action>在新小节开始时,简要回顾前一小节的关键结论</action> <action>明确告知用户现在进入了新的小节</action> </phase_transition> <tools> <tool>url to markdown</tool> <tool>...orther tools</tool> </tools> <knowledge_access> <action>访问相关领域的专业知识</action> <action>提供适合用户当前阶段的信息深度</action> <action>根据研究主题提供针对性的方法和技术建议</action> </knowledge_access> ``` ## 2. 生成文本 根据aisdk文档和阿里云百炼的文档自己配置了一个 dashscope provider,用到了云百炼的 openai 兼容接口,也配置了 sealos 的接口。 主要用到了 ai 和 @ai-sdk/react 包,根据文档的示例实现的。 注意:如果想用云百炼,可以用我配置好的 ai-provider,[GIthub 代码]() 或者 用提供的 [Shadcn Registry](https://ui.shadcn.com/) 快速安装 `pnpm dlx shadcn@latest add https://ai-super-agent-yupi-nextjs.hsawana9.com/r/dashscrop-ai-provider.json` 后端采用 ai provider 和 generateText、streamText ```ts /** * generateText Route * * @author Sawana Huang * * @see https://sdk.vercel.ai/docs/ai-sdk-core/generating-text * @see https://sdk.vercel.ai/cookbook/rsc/generate-text */ import { generateText } from "ai"; import { NextResponse, type NextRequest } from "next/server"; import { sealos } from "@/lib/ai/providers/sealos"; export interface GenerateTextPostRequestBody { prompt: string; } export interface GenerateTextPostSuccessResponse { message: string; text: string; } export async function POST(request: NextRequest) { const { prompt } = (await request.json()) as GenerateTextPostRequestBody; const result = await generateText({ model: sealos("deepseek-chat-0324"), system: "You are a helpful assistant.", prompt: prompt, }); const text = result.text; return NextResponse.json( { message: "success", text: text } as GenerateTextPostSuccessResponse, { status: 200, statusText: "OK", headers: { "Content-Type": "application/json", }, }, ); } ``` 前端可以直接 fetch 后端端点 ```ts /** * generateText compoent * * @author Sawana Huang * * @see https://sdk.vercel.ai/cookbook/rsc/generate-text * * @example use it in your component * import GenerateTextDemo from "@/components/generate-text-demo"; * * <GenerateTextDemo /> */ "use client"; import { cn } from "@/lib/utils"; import { useState } from "react"; import type { GenerateTextPostSuccessResponse } from "@/app/api/v1/generate-text/route"; type GenerateTextDemoProps = { className?: string; }; export default function GenerateTextDemo({ className, ...props }: React.ComponentProps<"div"> & GenerateTextDemoProps) { const [generation, setGeneration] = useState(""); const [isLoading, setIsLoading] = useState(false); return ( <div className={cn(className)} {...props}> <div onClick={async () => { setIsLoading(true); try { const response = await fetch("/api/v1/generate-text", { method: "POST", body: JSON.stringify({ prompt: "Why is the sky blue?", }), }); const json = (await response.json()) as GenerateTextPostSuccessResponse; setGeneration(json.text); } catch (error) { console.error("Error generating text:", error); setGeneration( "Unable to generate text at the moment. Please refresh and try again.", ); } finally { setIsLoading(false); } }} > Generate </div> {isLoading ? "Loading..." : generation} </div> ); } ``` ## 3. 多轮对话 Chatbot 直接使用 ai-sdk 提供的 useChat 方法,参考 [ai-sdk Chatbot](https://ai-sdk.dev/docs/ai-sdk-ui/chatbot)。 原理是: - 前端维护 messages,作为 body data 传送到后端,后端接收到之后传输整个 messages 给大模型。 - 后端用 generateText 或 streamText 来实现文本或者流式返回。 - 前端收集 input 组合成完整的 messages,并有完整的 hooks,如 isLoading。 - 通过 onFInish 和 onError 等调用实现更完整的业务 后端 ```ts /** * server route for comprehensive chatbot component * * @description: build server route for comprehensive chatbot component, following the document of AI SDK * * @see https://ai-sdk.dev/docs/ai-sdk-ui/chatbot * * @author Sawana Huang <hsawana9@gmail.com> * @date 2025-05-03 * */ import { dashscope } from "@/lib/ai/providers/dashscope"; import { streamText, type UIMessage } from "ai"; import { type NextRequest } from "next/server"; // Allow streaming responses up to 30 seconds export const maxDuration = 30; export interface PostRequestBody { messages: UIMessage[]; } export interface PostSuccessResponse { message: string; } export async function POST(request: NextRequest) { const { messages, // custom fields key_use_chatbot, key_handle_submit, } = (await request.json()) as PostRequestBody & { // custom fields key_use_chatbot: string; key_handle_submit: string; }; // custom fields console.log({ key_use_chatbot, key_handle_submit }); const result = streamText({ // You can use any model you want. // see: https://ai-sdk.dev/providers/ai-sdk-providers model: dashscope("deepseek-v3"), system: "You are a helpful assistant.", messages, }); return result.toDataStreamResponse({ // Error Message: // The default error message is masked "An error occurred." // You can forward error messages or send custome messages. getErrorMessage: (error) => { if (error == null) { return "unknown error"; } if (typeof error === "string") { return error; } if (error instanceof Error) { return error.message; } return JSON.stringify(error); }, // Usage Information sendUsage: true, // Reasoning: // https://ai-sdk.dev/docs/ai-sdk-ui/chatbot#reasoning sendReasoning: true, // Source // https://ai-sdk.dev/docs/ai-sdk-ui/chatbot#sources // some providers response include sources // eg. Perplexity, Google generative AI sendSources: true, }); } ``` 前端: ```tsx /** * comprehensive chatbot component * * @description: comprehensive chatbot component, following the document of AI SDK * * @see https://ai-sdk.dev/docs/ai-sdk-ui/chatbot * * @author Sawana Huang <hsawana9@gmail.com> * @date 2025-05-03 * */ "use client"; import { cn } from "@/lib/utils"; import { useChat } from "@ai-sdk/react"; type ChatBotProps = { className?: string; }; export default function ChatBot({ className, ...props }: React.ComponentProps<"div"> & ChatBotProps) { const { messages, input, handleInputChange, handleSubmit, status, stop, error, reload, } = useChat({ // API endpoint Default /api/chat api: "/api/v1/chat-bot", // Throttle the messages and data updates to 50ms: experimental_throttle: 50, // Event Callbacks: // https://ai-sdk.dev/docs/ai-sdk-ui/chatbot#event-callbacks onFinish: (message, { usage, finishReason }) => { console.log("Finished streaming message:", message); console.log("Token usage:", usage); console.log("Finish reason:", finishReason); }, onError: (error) => { console.error("An error occurred:", error); }, onResponse: (response) => { console.log("Received HTTP response from server:", response); // You can throw error here to trigger the onError callback }, // Custom headers, body, and credentials headers: { Authorization: "Bear your_token", }, body: { key_use_chatbot: "key of useChatbot", }, credentials: "same-origin", }); return ( <div className={cn("", className)} {...props}> <h2>--- Basic Messages ---</h2> {messages.map((message) => ( <div key={message.id}> {message.role === "user" ? "User: " : "AI: "} {message.content} </div> ))} <h2>--- Messages with Reasoning ---</h2> {messages.map((message) => ( <div key={message.id}> {message.role === "user" ? "User: " : "AI: "} {message.parts.map((part, index) => { // text parts: if (part.type === "text") { return <div key={index}>{part.text}</div>; } // reasoning parts: if (part.type === "reasoning") { return ( <pre key={index}> {part.details.map((detail) => detail.type === "text" ? detail.text : "<redacted>", )} </pre> ); } })} </div> ))} <h2>--- Messages with Sources ---</h2> {messages.map((message) => ( <div key={message.id}> {message.role === "user" ? "User: " : "AI: "} {message.parts .filter((part) => part.type !== "source") .map((part, index) => { if (part.type === "text") { return <div key={index}>{part.text}</div>; } })} {message.parts .filter((part) => part.type === "source") .map((part) => ( <span key={`source-${part.source.id}`}> [ <a href={part.source.url} target="_blank"> {part.source.title ?? new URL(part.source.url).hostname} </a> ] </span> ))} </div> ))} {(status === "submitted" || status === "streaming") && ( <div> {status === "submitted" && "loading... "} <button type="button" onClick={() => stop()}> Stop </button> </div> )} <h2>--- Erorr ---</h2> {error && ( <div> <div>An error occurred.</div> <button type="button" onClick={() => reload()}> Retry </button> </div> )} <h2>--- Submit input and Button ---</h2> <form onSubmit={(event) => { handleSubmit(event, { // Custom body fieds per request body: { key_handle_submit: "key of handleSubmit", }, }); }} > <input name="prompt" value={input} placeholder="Type your message here..." onChange={handleInputChange} disabled={status !== "ready" || error != null} /> <button type="submit">Submit</button> </form> </div> ); } ``` ## 4. 拦截器(中间件) 在 Spring AI 中是 Advisor 的功能,而在 ai-sdk 中,则是通过 wrapLanguageModel 的方式 原理是: - 包裹大模型 provider,拦截大模型调用前后的过程,从而生成一个 wrapedLLM,后端直接调用这个新的模型 - 自定义 Middleware:transformParams 控制传输大模型前,wrapStream 和 wrapGenerate 分别控制大模型结果的流式和文本返回。 输出日志的自定义 middleware 示例: ```ts /** * Implementing Language Model Middleware * * customize your own middleware * * - `transformParams`: Modify the parameters of the language model call. * - `wrapGenerate`: wrap the doGenerate function. * - `wrapStream`: wrap the doStream function. * * you can modify the paramers, call the languaage moel, * and modify the result. in other words, you can do someting * before and after the language model call. * * following are examples from ai adk */ /** * Logging Middleware */ const loggingMiddleware: LanguageModelV1Middleware = { wrapGenerate: async ({ doGenerate, params }) => { console.log("doGenerate called"); console.log(`params: ${JSON.stringify(params, null, 2)}`); const result = await doGenerate(); console.log("doGenerate finished"); console.log(`generated text: ${result.text}`); return result; }, wrapStream: async ({ doStream, params }) => { console.log("doStream called"); console.log(`params: ${JSON.stringify(params, null, 2)}`); const { stream, ...rest } = await doStream(); let generatedText = ""; const transformStream = new TransformStream< LanguageModelV1StreamPart, LanguageModelV1StreamPart >({ transform(chunk, controller) { if (chunk.type === "text-delta") { generatedText += chunk.textDelta; } controller.enqueue(chunk); }, flush() { console.log("doStream finished"); console.log(`generated text: ${generatedText}`); }, }); return { stream: stream.pipeThrough(transformStream), ...rest, }; }, }; const loggingModel = wrapLanguageModel({ model: dashscope("deepseek-v3"), middleware: loggingMiddleware, }); ``` ## 5. 结构化输出 使用 useObject 和 streamObject 方法 原理是: - prompt 中提示大模型生成指定的内容 - 通过 schema 提示大模型生成的内容和格式,并进行类型审查 schema: ```ts /** * Object Generation * * @see https://ai-sdk.dev/docs/ai-sdk-ui/object-generation */ import { z } from "zod"; // define a schema for the notifications export const notificationSchema = z.object({ notifications: z.array( z.object({ name: z.string().describe("Name of a fictional person."), message: z.string().describe("Message. Do not use emojis or links."), }), ), }); ``` 前端和后端关键代码 ```tsx /** * Object Generation * * @see https://ai-sdk.dev/docs/ai-sdk-ui/object-generation */ // 前端 // ... const { isLoading, stop, error, object, submit } = useObject({ api: "/api/v1/object-generation/notification", schema: notificationSchema, onFinish({ object, error }) { // typed object, undefined if schema validation fails: console.log("Object generation completed:", object); // error, undefined if schema validation succeeds: console.log("Schema validation error:", error); }, onError(error) { // error during fetch request: console.error("An error occurred:", error); }, }); // ... // 后端 const context = (await request.json()) as string | undefined; const result = streamObject({ model: dashscope("qwen-plus"), schema: notificationSchema, prompt: `Generate 3 notifications for a messages app in this context:` + context, }); // ... ``` ## 6. 记忆持久化 分别实现了 本地文件 和 数据库 两种形式 原理是: - 通过后端 streamText 的 onFinishi 回调捕获生成结果,把这个结果保存下来 - 本地是直接把结果写入到 .chat/*.json 中,通过文件系统管理 - 数据库则是把结果和数据库的结果相比较,存入新的的消息 后端代码 ```ts /** * Chat API for Persistence * * @see https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence */ // ... const result = streamText({ model: dashscope("qwen-turbo"), messages, // store messages async onFinish({ response }) { await saveChat({ id, messages: appendResponseMessages({ // history messages messages, // response messages responseMessages: response.messages, }), }); }, // id format for server-side messages: experimental_generateMessageId: createIdGenerator({ prefix: CHAT_MESSAGE_SERVER_PREFIX, size: 16, }), }); // ... ``` ## 总结 已经完成了 Nextjs 的 AI 智能体的基本实现。 通过 Shadcn Registry 提高了这些代码的可重用性,符合 DRY 原则。模块化的组件加上 Regristry 服务,我可以非常方便地把这些代码集成到我的新项目中。 锻炼了自己的提示词工程功力,这份提示词已经非常接近可以使用的程度,效果已经和当初使用 Buildpad 时一样惊艳。xml,多步骤指令,ai 辅助生成 prompt。 计划: - Rag 实现。本地实现和接入服务 - 提示词从 Dify 迁移到项目中。包括 提示词缓存,提示词 token 优化,工具调用,mcp 调用 更多信息,去看上线的网站吧! 有帮助的话,点点 Star 谢谢各位! 开源地址:https://github.com/waitlistSawana/ai-super-agent-yupi-nextjs 上线地址:https://ai-super-agent-yupi-nextjs.hsawana9.com/

关于【因文件数量过多导致antd Upload上传组件卡死】的原因以及解决方案

# 1. 分析问题 对文件夹中的所有文件进行上传时,如果目标文件夹中的文件数量过多(比如:大于 10 万个文件),直接使用 antd 中的文件上传组件(Upload)必然出现卡死页面的现象。在查阅资料之后,我们发现出现该现象的原因有以下几点: * **事件处理负担过重** * 每个文件在上传过程中都会触发一系列的事件,如 `onChange`、`onProgress`、`onError` 等。Upload 组件需要对这些事件进行监听和处理。当文件数量庞大时,事件的触发频率会非常高,从而导致页面卡顿现象。 * **渲染性能问题** * 在文件上传的过程中,Upload 组件需要对每一个文件生成对应的 ui 展示文件的上传状态等信息。如果文件过多,频繁地操作 dom 结构极大占用了浏览器的资源,出现页面卡顿现象。 <img src="https://pic.code-nav.cn/post_picture/1917936850210033665/cGsbngz6SDpx4zO8.webp" alt="image.png" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1917936850210033665/5iqLFJVnADU3wbzm.webp" alt="image.png" width="100%" /> # 2. 解决思路 ### 2.1. 从生活中寻找解决方法 在现实生活中,并不缺少支持用户上传本地文件到云端的平台(比如:百度网盘、夸克网盘)。当用户将文件拖入平台的上传页面时,这些平台并没有因为文件的数量过多、体积多大而出现崩溃的现象。由此可知,一定存在解决方案。 ### 2.2. 思路 原生 html 中的 input 文件上传组件并不会频繁地触发相关事件也不会频繁地渲染 ui 结构,它只是当所有文件全部获取完毕之后只触发一次 `onchange`方法。开发者可以从 onchange 回调函数中获取用户上传的所有文件。 ```jsx <input type="file" webkitdirectory directory multiple // 只会调用一次 onChange={(data)=>{ console.log(data,'fileData') }} /> ``` <img src="https://pic.code-nav.cn/post_picture/1917936850210033665/1HzvL4P4RP6Kqn3Q.webp" alt="image.png" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1917936850210033665/0o2Y4tM4yKTrytHv.webp" alt="image.png" width="100%" /> 仅使用原生的 input 组件完成需求的话,开发效率真的很高吗?真的是最优解吗? 试想一下:如果我们使用原生的 input 完成,确实解决了页面卡顿的现象。但是,文件列表的 ui 结构、对已上传的文件的操作、文件上传状态等基础功能全部需要手写!这工作量非同一般。而且,Upload 组件对文件上传列表功能的实现效果堪称完美。于是,我们决定了最终的方案: 1. 在 Upload 组件下放置一个 input 组件,并利用 css 将其隐藏。 2. 当用户点击 Upload 组件时,进行事件的劫持,将事件转移到 input 组件中。 3. 当用户选择完毕目标文件夹后,利用 input 中的 onchange 方法得到所有的文件。 4. 在 onchange 事件回调函数中处理文件的批量上传逻辑。 # 3. 将思路转为代码 我们根据以上的思路,完成代码逻辑: 1. 构建 UI 结构 2. 当用户点击 Upload 组件时,进行事件的劫持,将事件转移到 input 组件中。 ```jsx export default ()=>{ // input 组件实例对象 const inputRef = useRef(); return <> <Upload action="https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload" directory fileList={fileUploadErrorList} // 其他逻辑自行补充 > <Button onClick={(e) => { // 阻止事件冒泡 e.stopPropagation(); // 点击后,将事件转移到 input 组件上 inputRef.current.click(); }} > 上传文件 </Button> </Upload> <input style={{ display: "none" }} ref={inputRef} type="file" webkitdirectory directory multiple /> </> } ``` 3. 当用户选择完毕目标文件夹后,利用 input 中的 onchange 方法得到所有的文件。 4. 在 onchange 事件回调函数中处理文件的批量上传逻辑。 ```jsx // input 组件 onChange方法回调函数 const onChange = (data) => { upload(Object.values(data.target.files)); }; // 分批上传,默认5个一组 const upload = async (list, size = 5) => { const total = list.length; for (let i = 0; i < total; i += size) { const requests = list.slice(i, i + size).map((item) => { const formData = new FormData(); formData.append("file", item); // uploadReq返回值是一个异步任务 return uploadReq(formData); }); // 该批次的异步任务统一执行 const resultData = await Promise.allSettled(requests); // 由于文件过多,只将上传失败的文件进行展示。 let errorList = []; resultData.map((res) => { errorList = []; if (!res?.value?.result?.success) { // 得到FormData中的文件对象file const file = res?.value?.file?.get("file"); file.status = "error"; file.response = res?.value?.result; errorList.push(file); } }); } }; // 上传文件--请求 const uploadReq = async (formData) => { try { const response = await fetch("http://localhost:5173/upload/file", { method: "POST", body: formData, credentials: "include", withCredentials: true, }); return { result: await response.json(), file: formData }; } catch (error) { return { result: error?.message || "网络异常,请稍后再试!", file: formData, }; } }; ``` 通过以上代码,已经可以完美的解决业务问题。写到这里,先给自己来一个一键三连赞👍🏻👍🏻👍🏻。其实,在 B 端页面开发领域,文件上传业务可谓司空见惯。如果能将以上的分批上传逻辑抽离成一个独立的 hook ,既可以提高团队的开发效率又可以在面试的过程中作为一个业务难点分享给面试官,一举两得岂不美哉。看到这里,你是不是会想直接把上述代码中的 `upload` 的方法提取出去,不就行了!如果你要这样想的话,我只能说:“兄弟,格局稍微打开一点。” 废话不多说,上才艺! # 4. 构建异步任务栈,实现文件分批上传 首先,在自定义的 hook 中,必须维护一个栈类型的数据结构。 其次,hook 向外暴漏一个追加异步任务的方法 `addTask`。 最后,暴漏执行异步任务的方法,此方法接受一个回调函数。异步任务执行时,将执行结果通过调用回调函数的方式传递给外界。 ```jsx export function syncFileLoadstack(request) { const stack = []; let uploadedCount = 0; const addTask = (data) => { if (Array.isArray(data) && data.length > 0) { // 进栈 stack.push(...data); } else { stack.push(data); } return { run }; }; const run = async (callback = () => {}) => { const len = stack.length; while (uploadedCount < len) { const data = stack.pop(); uploadedCount++; try { const res = await data.request?.(); callback(res); } catch (error) { callback(res); } } }; return { addTask }; } ``` ```jsx // 测试使用 -- input 的 onChange 方法改写 const App = () => { // 导入 hook const { addTask } = syncFileLoadstack(); const onChange = async (data) => { // 分批上传,默认5个一组 const files = Object.values(data.target.files).map((item) => ({ request: () => { const formData = new FormData(); formData.append("file", item); // 这是一个异步任务-发请求 return uploadReq(formData); }, })); addTask(files).run((res) => { // 每一个任务的执行结果可以从这里获取。 console.log(res, "file"); }); }; // xxx其他逻辑代码xxx }; ```

用户中心上线(NextJS改造)

# 用最新的 NextJS 实现 跟着鱼皮哥的课程实现的项目,一边学习项目的规范和实践经验,一边探索用 nextjs 实现相同的功能。 项目已上线,欢迎大家体验! ## 项目 Github 仓库:[用户中心项目 - 鱼皮](https://github.com/waitlistSawana/user-center-yupi-nextjs) 上线地址:[鱼皮用户中心项目改造 - NextJs 版](https://user-center-yupi-nextjs.highlightpulse.site/) 开发记录:[开发记录.md](https://github.com/waitlistSawana/user-center-yupi-nextjs/blob/main/%E5%BC%80%E5%8F%91%E8%AE%B0%E5%BD%95.md) 主要功能 - 用户注册、登录、注销 - 用户表格查看 待完善部分 - [ ] 统一后端返回 - [ ] 定义统一错误码 - [ ] docker - [ ] nginx ## 技术栈 总体来说,使用 T3 Stack 启动了 NextJS 的项目,前后端同构,前端是 React 后端是 Nodejs。T3 stack 集成了生产级的配置,配好了 prettier 和 eslint,以及全栈需要各项开源项目。 前端: - NextJS 15 + Tailwindcss 4 + React 19,跟进最新技术框架 - Shadcn UI 定制化组件开发,学习其设计方法和理念,组件属性解构、cn 和 cva 模式 - Tankstack Query 异步数据状态管理,高效实现 React 组件数据获取、缓存和更新 - Tankstack Table 强大的 Headless UI 表格状态管理和数据操作 后端: - 项目分层 api、services、repositories - JSDoc 风格代码注释 - Drizzle ORM 数据库访问 - 阿里云 RDS 的 Serverless MySql 数据库 - JWT session 和 cookie 管理 其他 - 测试 Vitest (AI 写的),Rest Client 测试 api 端点 - Cloufalre 域名托管 - Vercel 项目部署上线 - Github 数据托管,commit 命名遵循 Angular Commit Message Guidelines # 项目结构 可以直接在 [Github 仓库](https://github.com/waitlistSawana/user-center-yupi-nextjs/tree/main/src) 中查看 <img src="https://pic.code-nav.cn/post_picture/1857029075076202498/AdbCML9myTZWEpDW.webp" alt="tmp51B.png" width="100%" /> ## 后端 app/api/v1/user controler 的接口,前端直接访问,如 example.com/api/v1/user/current 。 get 方法 和 post 方法 server/db/ 数据库相关 Drizzle ORM server/db/index.ts 创建数据库连接 server/db/schema.ts 数据库表设计 server/db/types.ts 根据 schema 自动定义类型 server/repositories/user.ts 用户数据访问层 server/services/user.ts 服务层 server/utils/error-handling.ts 用 TRPCError 包装 NextResponse server/utils/hash.ts 用 bcryptjs 密码加密函数 server/utils/session.ts 根据 NextJS 文档建议,JWT session 创建 ## 前端 app/ 页面 app/(auth)/ 用户相关页面 app/(auth)/login 登录表单,useEffect + React Form Hook + Zod + axios app/(auth)/register 注册表单,Tankstack Query(useMutation) + React Form Hook + Zod + axios app/(auth)/logout 登出页面,清除 session app/(dashboard)/layout.ts 侧边栏设计 app/(dashboard)/welcome/ 用户主页 app/(dashbaord)/user-manage 用户数据库表格,Tankstack Table components/ 组件整理 components/ui/ 导入进来的 shadcn ui 组件,可定制 components/aceternity/ 从组件库 aceternity ui 复制来的组件代码 compoents/fallback/ 用于 suspense 的骨架组件 components/user-manage tankstack table 表格的构建,columns 定义 compoents/ ** .tsx 自建的各种 React 组件 ## 其他 src/env.js 复用 t3 实现环境变量配置 src/middleware.ts 中间件,鉴权,路由保护和跳转 (ps. 之前出了挺火的漏洞233,所以必须在别处二次鉴权) test/ 测试代码 test/rest-client 后端端点连接和测试 eslint.config.js 静态检查 prettier.config.js 代码格式化

bi项目完结&扩展

## 项目地址 www.pikim.fun (AItoken有限,各位鱼友轻点打) ## 项目实现思路 我并没有把他当做一个纯BI的项目,而是借助当下火爆的大模型,利用大模型开发一些有意思的AI功能。当然,目前实现的功能也少,后期在慢慢完善了吧哈哈哈~ ### 实现功能 - 图表生成 - 图片理解 - AI对话(海龟汤) 部分页面只有管理员能看到,后期会再修一下逻辑~ ## 扩展点 这里对图表生成扩展最对,也是花费时间最多的地方,后面两个模块仅仅实现功能,等有时间慢慢优化 1. 对图表表做了分表,easyExcel 提取的数据分离原始表格,实现一个图表一个数据表 2. 图表生成添加生成超时功能 3. 对生成失败表格进行重试(还未完善) 4. 图表生成校验,单用户相同图表数据,相同图表类型,只能生成一次 5. 用redis,对图表的分页查询做缓存 6. 管理员能查看所有用户生成的图表,普通用户只能查看自己的图表记录 7. 添加注册页面,接口 9. 对图表做了CRUD,支持更新,删除动态表格(仅支持关管理员修改) 10. 扩展图片理解页面 11. 扩展AI对话海龟汤游戏,查询对话记录等 ## 局限性 1. 图表分析,只支持excel文档数据,用户上传文件受限 2. 图表分析要求较高的AI算力,目前AI生成复杂的图表耗时长,内容有误,渲染失败等等 3. 图片理解页面,暂未开发历史图片页面 4. AI对话,仅仅是实现功能 ## 后期计划&优化 ### 图表生成 1. 统计当前用户生成图表次数 2. 对图表页面实现自动刷新功能 3. 限制单个用户每天生成图表次数 4. 分析结果采用,“展开更多”显示 ### 图片理解 1. 添加历史图片理解页面 2. 图片仅仅上传到服务器,尝试采用cos➕CDN存储图片 ### AI对话 1. 添加多个ai,接口处理对话,每次轮训不同AI接口 2. 用redis缓存聊天记录,mq添加聊天信息,写入到数据库 3. 分库分表,对每个房间消息做隔离 4. 用redis缓存当前对话记录 5. 聊天框异常,用户突然退出,死机,黑屏等操作,保证聊天记录计时被保存 6. 当房间没有活动,超过某一时间后,自动结束,修改房间状态为“已关闭” ## 最后 项目的难点 1. 在于对动态表做CRUD,由于用户上传的表数据不一,导致每个表的表名,字段名,字段数量都不相同,这样去拼接动态sql,代码都比较繁杂。 2. 处理科大讯飞的AI接口,没有完全封装,需要手动修改部分代码 3. 项目上线,后端采用docker-compose.yml部署容器,对docker-compose语法不熟悉,试错了多次才成功部署。 ![image.png](https://pic.code-nav.cn/post_picture/1609191652126097409/IO5OGPLjmWcxH0fz.webp) <img src="https://pic.code-nav.cn/post_picture/1609191652126097409/HQwNjqaHCBwGk8YR.webp" alt="image.png" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1609191652126097409/H3FMtqaztGT3oMje.webp" alt="image.png" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1609191652126097409/y1c7Fysvm1SdPOgy.webp" alt="image.png" width="100%" />

AddButton 组件封装如下 "react": "^18.2.0", "antd": "^5.15.1", "antd-style": "^3.6.2", SvgIconComp 组件链接 https://www.codefather.cn/essay/1897849299510984706 import { memo } from 'react'; import { Button } from 'antd'; import { createStyles, cx } from 'antd-style'; import SvgIconComp from '@/components/SvgIcon'; const useStyles = createStyles(({ token }) => ({ buttonContent: { display: 'flex', alignItems: 'center', color: token.colorTextLabel, }, text: { marginLeft: 6, }, button: { ':hover': { backgroundColor: `${token.colorPrimaryBgHover} !important`, }, }, })); const AddButton = (props) => { const { styles } = useStyles(); const { disabled, title } = props || {}; return ( <Button size={'small'} type={'text'} {...props} className={styles.button}> <div className={styles.buttonContent}> <SvgIconComp iconName="autamate/addDesc" /> <div className={cx(styles.text, disabled && 'disabledstyle')}>{title || '添加'}</div> </div> </Button> ); }; export default memo(AddButton);

2025 年最全Java面试题 ,热门高频200 题+答案汇总!

为大家准备了 2025年全新热门高频 Java 面试题。 适合准备求职、或者有跳槽想法的初/中/高级程序员。 也合适想要对自己技术栈查缺补漏、不断完善成长的人。 我们将面试题分成 Java基础&并发、MySQL、Redis、JVM……等多个分类。下面一起看看这些题目: ### **Java 基础&并发(33 题)** 1,说说 Java 中 HashMap 的原理? `HashMap` 是基于哈希表的数据结构,用于存储**键值对**(`key-value`)。其核心是将键的哈希值映射到数组索引位置,通过**数组 + 链表**(在 Java 8 及之后是**数组 + 链表 + 红黑树**)来处理哈希冲突。 `HashMap` 使用键的 `hashCode()` 方法计算哈希值,并通过 `indexFor` 方法(JDK 1.7 及之后版本移除了这个方法,直接使用 `(n - 1) & hash`)确定元素在数组中的存储位置。哈希值是经过一定扰动处理的,防止哈希值分布不均匀,从而减少冲突。 `HashMap` 的默认初始容量为 16,负载因子为 0.75。也就是说,当存储的元素数量超过 16 × 0.75 = 12 个时,`HashMap` 会触发扩容操作,容量x2并重新分配元素位置。这种扩容是比较耗时的操作,频繁扩容会影响性能。 2,Java 中 ConcurrentHashMap 1.7 和 1.8 之间有哪些区别? JDK 1.7 `ConcurrentHashMap` 采用的是**分段锁**,即每个 `Segment` 是独立的,可以并发访问不同的 `Segment`,默认是 16 个 `Segment`,所以最多有 16 个线程可以并发执行。 而 JDK 1.8 移除了 `Segment`,锁的粒度变得更加细化,锁只在链表或红黑树的**节点级别**上进行。通过 CAS 进行插入操作,只有在更新链表或红黑树时才使用 `synchronized`,并且只锁住链表或树的头节点,进一步减少了锁的竞争,并发度大大增加。 并且 JDK 1.7 `ConcurrentHashMap` 只使用了**数组 + 链表**的结构,而 JDK 1.8 和 `HashMap`一样引入了红黑树。 除此之外,还有扩容的区别以及 `size` 方法的计算也不一样。 3,为什么 JDK 1.8 对 HashMap 进行了红黑树的改动? 在 JDK 1.8 之前,`HashMap` 使用链表来解决哈希冲突。当哈希冲突较多时,链表中的元素增多,查找、插入和删除的时间复杂度从 O(1) 退化为 O(n)。 因此在 JDK 1.8 引入红黑树,将链表长度超过一定阈值(默认 8)时的链表转换为红黑树,避免性能急剧下降。当链表长度降到 6 以下时,红黑树会重新退化为链表,保持简单高效。 红黑树是一种平衡二叉搜索树,插入、删除、查找操作的时间复杂度为 O(log n),在元素多的情况下远优于链表的 O(n)。 4,JDK 1.8 对 HashMap 除了红黑树还进行了哪些改动? - **改进了哈希函数的计算**:JDK 1.8 中优化了哈希函数,使得哈希值的分布更加均匀,减少了哈希冲突的发生。通过在生成哈希值时使用“扰动函数”,确保哈希值的高低位都能参与到桶的选择中。 - **扩容机制优化**:JDK 1.8 改进了扩容时的元素迁移机制。在扩容过程中不再对每个元素重新计算哈希值,而是根据原数组长度的高位来判断元素是留在原位置,还是迁移到新数组中的新位置。这一改动减少了不必要的计算,提升了扩容效率。 - **头插法变为尾插法**:头插法的好处就是插入的时候不需要遍历链表,直接替换成头结点,但是缺点是扩容的时候会逆序,而逆序在多线程操作下可能会出现环,产生死循环,于是改为尾插法。 5,Java 中有哪些集合类?请简单介绍 Java 中的集合类主要分为两大类:Collection 接口和 Map 接口。前者是存储对象的集合类,后者存储的是键值对(key-value)。 <img src="https://pic.code-nav.cn/mianshiya/question_picture/1783397053004488705/20220219202800_mianshiya.png" alt="20220219202800.png" width="100%" /> Collection 接口下又分为 List、Set 和 Queue 接口。每个接口有其具体实现类。以下是主要的集合类: **List 接口:** - ArrayList:基于动态数组,查询速度快,插入、删除慢。 - LinkedList:基于双向链表,插入、删除快,查询速度慢。 - Vector:线程安全的动态数组,类似于 ArrayList,但开销较大。 **Set 接口:** - HashSet:基于哈希表,元素无序,不允许重复。 - LinkedHashSet:基于链表和哈希表,维护插入顺序,不允许重复。 - TreeSet:基于红黑树,元素有序,不允许重复。 所以网上有些说 Set 是无序集合非常不准确,因为需要看具体的实现类。 **Queue 接口:** - PriorityQueue:基于优先级堆,元素按照自然顺序或指定比较器排序。 - LinkedList:可以作为队列使用,支持 FIFO(先进先出)操作。 **Map 接口:** 存储的是键值对,也就是给对象(value)设置了一个 key,这样通过 key 可以找到那个 value。 <img src="https://pic.code-nav.cn/mianshiya/question_picture/1783397053004488705/20220219202845_mianshiya.png" alt="20220219202845.png" width="386px" /> - HashMap:基于哈希表,键值对无序,不允许键重复。 - LinkedHashMap:基于链表和哈希表,维护插入顺序,不允许键重复。 - TreeMap:基于红黑树,键值对有序,不允许键重复。 - Hashtable:线程安全的哈希表,不允许键或值为 null。 - ConcurrentHashMap:线程安全的哈希表,适合高并发环境,不允许键或值为 null。 > 我们整理了2025年全新 java 高频热门面试题,包括:Java、 MySQL、Redis、Spring、SpringBoot、SpringCloud、计算机网络、操作系统、消息队列、Netty、后端场景题、线上问题排查、后端系统设计题 等等内容。 > > 篇幅限制,后续**全套高频面试题及详细题解**可以[【点击此处获取】](https://www.mianshiya.com/)。 6,为什么 Java 8 移除了永久代(PermGen)并引入了元空间(Metaspace)? 7,为什么 Java 中 HashMap 的默认负载因子是 0.75? 8,Java 中 HashMap 的扩容机制是怎样的? 9,为什么 HashMap 在 Java 中扩容时采用 2 的 n 次方倍? 10,数组和链表在 Java 中的区别是什么? 11,Java 线程池核心线程数在运行过程中能修改吗?如何修改? 12,Java 中如何创建多线程? 13,Java 中的 final 关键字是否能保证变量的可见性? 14,什么是 Java 中的原子性、可见性和有序性? 15,什么是 Java 的 CAS(Compare-And-Swap)操作? 16,为什么 Java 中的 ThreadLocal 对 key 的引用为弱引用? 17,编译执行与解释执行的区别是什么?JVM 使用哪种方式? 18,Java 中什么情况会导致死锁?如何避免? 19,你了解 Java 线程池的原理吗? 20,Java 线程池有哪些拒绝策略? 21,如何合理地设置 Java 线程池的线程数? 22,你使用过哪些 Java 并发工具类? 23,Synchronized 和 ReentrantLock 有什么区别? 24,Java 的 synchronized 是怎么实现的? 25,如何优化 Java 中的锁的使用? 26,Java 中常见的垃圾收集器有哪些? 27,Java 中有哪些垃圾回收算法? 28,什么是 Java 内存模型(JMM)? 29,线程和进程有什么区别? 30,Java 中 volatile 关键字的作用是什么? 31,什么是 Java 中的 ABA 问题? 32,线程的生命周期在 Java 中是如何定义的? 33,说说 AQS 吧? ### **JVM 相关(7 题)** 1. JVM 由哪些部分组成? 2. JVM 垃圾回收调优的主要目标是什么? 3. 如何对 Java 的垃圾回收进行调优? 4. 常用的 JVM 配置参数有哪些? 5. JVM 的内存区域是如何划分的? 6. JVM 有哪几种情况会产生 OOM(内存溢出)? 7. 怎么分析 JVM 当前的内存占用情况?OOM 后怎么分析? ### **MySQL(32 题)** 1. MySQL 索引的最左前缀匹配原则是什么? 2. 数据库的脏读、不可重复读和幻读分别是什么? 3. MySQL 的存储引擎有哪些?它们之间有什么区别? 4. MySQL 的覆盖索引是什么? 5. MySQL 的索引类型有哪些? 6. MySQL 的索引下推是什么? 7. MySQL InnoDB 引擎中的聚簇索引和非聚簇索引有什么区别? 8. MySQL 中的回表是什么? 9. MySQL 中使用索引一定有效吗?如何排查索引效果? 10. MySQL 中的索引数量是否越多越好?为什么? 11. 请详细描述 MySQL 的 B+ 树中查询数据的全过程 12. 为什么 MySQL 选择使用 B+ 树作为索引结构? 13. MySQL 三层 B+ 树能存多少数据? 14. 详细描述一条 SQL 语句在 MySQL 中的执行过程。 15. MySQL 是如何实现事务的? 16. MySQL 事务的二阶段提交是什么? 17. MySQL 中长事务可能会导致哪些问题? 18. MySQL 中的 MVCC 是什么? 19. MySQL 中的事务隔离级别有哪些? 20. MySQL 默认的事务隔离级别是什么?为什么选择这个级别? 21. MySQL 中有哪些锁类型? 22. MySQL 的乐观锁和悲观锁是什么? 23. MySQL 中如果发生死锁应该如何解决? 24. MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别? 25. MySQL 中如何进行 SQL 调优? 26. 如何使用 MySQL 的 EXPLAIN 语句进行查询分析? 27. MySQL 中如何解决深度分页的问题? 28. 什么是 MySQL 的主从同步机制?它是如何实现的? 29. 如何处理 MySQL 的主从同步延迟? 30. MySQL 中如果我 select * from 一个有 1000 万行的表,内存会飙升么? 31. 在 MySQL 中建索引时需要注意哪些事项? 32. 在什么情况下,不推荐为数据库建立索引? ### **消息队列(16 题)** 1. RabbitMQ 怎么实现延迟队列? 2. RabbitMQ 中消息什么时候会进入死信交换机? 3. RabbitMQ 中无法路由的消息会去到哪里? 4. Kafka 为什么要抛弃 Zookeeper? 5. Kafka 中 Zookeeper 的作用? 6. 说一下 Kafka 中关于事务消息的实现? 7. 说一下 RocketMQ 中关于事务消息的实现? 8. RocketMQ 的事务消息有什么缺点?你还了解过别的事务消息实现吗? 9. 为什么需要消息队列? 10. 说一下消息队列的模型有哪些? 11. 如何处理重复消息? 12. 如何保证消息的有序性? 13. 如何处理消息堆积? 14. 如何保证消息不丢失? 15. 消息队列设计成推消息还是拉消息?推拉模式的优缺点? 16. 为什么 RocketMQ 不使用 Zookeeper 作为注册中心? ### **设计模式(11 题)** 1. 谈谈你了解的最常见的几种设计模式,说说他们的应用场景 2. 什么是策略模式?一般用在什么场景? 3. 什么是责任链模式?一般用在什么场景? 4. 什么是模板方法模式?一般用在什么场景? 5. 什么是观察者模式?一般用在什么场景? 6. 什么是代理模式?一般用在什么场景? 7. 请描述简单工厂模式的工作原理。 8. 工厂模式和抽象工厂模式有什么区别? 9. 什么是设计模式?请简述其作用。 10. 单例模式有哪几种实现?如何保证线程安全? 11. Netty 采用了哪些设计模式? ### **Spring 框架(19 题)** 1. 说说 Spring 启动过程? 2. 你了解的 Spring 都用到哪些设计模式? 3. Spring 有哪几种事务传播行为? 4. 说说 Springboot 的启动流程? 5. SpringBoot 是如何实现自动配置的? 6. 如何理解 Spring Boot 中的 starter? 7. Spring Boot 是如何通过 main 方法启动 web 项目的? 8. Spring Boot 的核心特性有哪些? 9. 什么是 Spring Boot? 10. 什么是 Spring IOC? 11. Spring AOP 默认用的是什么动态代理,两者的区别? 12. 什么是 AOP? 13. 看过源码吗?说下 Spring 由哪些重要的模块组成? 14. 什么是循环依赖(常问)? 15. Spring 如何解决循环依赖? 16. 为什么 Spring 循环依赖需要三级缓存,二级不够吗? 17. 说下 Spring Bean 的生命周期? 18. Spring MVC 具体的工作原理? 19. Spring 中的 DI 是什么? ### **Redis(31 题)** 1. Redis 集群的实现原理是什么? 2. Redis 集群会出现脑裂问题吗? 3. Redis 中如何实现分布式锁? 4. Redis 实现分布式锁时可能遇到的问题有哪些? 5. 说说 Redisson 分布式锁的原理? 6. 如何使用 Redis 快速实现排行榜? 7. Redis 中如何保证缓存与数据库的数据一致性? 8. Redis 为什么这么快? 9. 如何使用 Redis 快速实现布隆过滤器? 10. 为什么 Redis 设计为单线程?6.0 版本为何引入多线程? 11. 你在项目中使用的 Redis 客户端是什么? 12. Redis 中常见的数据类型有哪些? 13. Redis 中跳表的实现原理是什么? 14. Redis 性能瓶颈时如何处理? 15. Redis 的 hash 是什么? 16. Redis 和 Memcached 有哪些区别? 17. Redis 支持事务吗?如何实现? 18. Redis 数据过期后的删除策略是什么? 19. Redis 中有哪些内存淘汰策略? 20. Redis 的 Lua 脚本功能是什么?如何使用? 21. Redis 的 Pipeline 功能是什么? 22. Redis 通常应用于哪些场景? 23. Redis 中的 Big Key 问题是什么?如何解决? 24. 如何解决 Redis 中的热点 key 问题? 25. Redis 的持久化机制有哪些? 26. Redis 中的缓存击穿、缓存穿透和缓存雪崩是什么? 27. 线上发现 Redis 机器爆了,如何优化? 28. Redis 在生成 RDB 文件时如何处理请求? 29. Redis 的哨兵机制是什么? 30. Redis 主从复制的实现原理是什么? 31. 如果发现 Redis 内存溢出了?你会怎么做? ### **计算机网络相关(19 题)** 1. 说说 TCP 的三次握手? 2. 说说 TCP 的四次挥手? 3. 为什么 TCP 挥手需要有 TIME_WAIT 状态? 4. TCP 超时重传机制是为了解决什么问题? 5. TCP 滑动窗口的作用是什么? 6. TCP/IP 四层模型是什么? 7. OSI 七层模型是什么? 8. 从网络角度来看,用户从输入网址到网页显示,期间发生了什么? 9. 什么是物理地址,什么是逻辑地址? 10. 到底什么是 TCP 连接? 11. HTTP 1.0 和 2.0 有什么区别? 12. HTTP 2.0 和 3.0 有什么区别? 13. HTTP 和 HTTPS 有什么区别? 14. HTTP 与 RPC 之间的区别? 15. TCP 和 UDP 有什么区别? 16. TCP 的粘包和拆包能说说吗? 17. Cookie、Session、Token 之间有什么区别? 18. 线上 CPU 飙高如何排查? 19. TCP 是用来解决什么问题? ### **其他框架(17 题)** 1. 简单说说 Netty 的零拷贝机制? 2. Netty 是如何解决粘包和拆包问题的? 3. Netty 如何解决 JDK NIO 中的空轮询 Bug? 4. 介绍一下 Reactor 线程模型? 5. 说说你知道的几种 I/O 模型 6. Select、Poll、Epoll 之间有什么区别? 7. 说一下 Netty 的应用场景? 8. 为什么不选择使用原生的 NIO 而选择使用 Netty 呢? 9. Netty 性能为什么这么高? 10. 简述 MyBatis 的插件运行原理,以及如何编写一个插件? 11. 说说 MyBatis 的缓存机制? 12. MyBatis 中 #{} 和 ${} 的区别是什么? 13. MyBatis 与 Hibernate 有哪些不同? 14. 什么是 MyBatis-Plus?它有什么作用? 15. Dubbo 和 Spring Cloud Gateway 有什么区别? 16. 什么是 API 网关?它有什么作用? 17. 什么是 Seata? ### **分布式相关(13 题)** 1. 什么情况下需要使用分布式事务,有哪些方案? 2. 如何设计一个秒杀功能? 3. 让你设计一个分布式 ID 发号器,怎么设计? 4. 让你设计一个短链系统,怎么设计? 5. 分布式锁一般都怎样实现? 6. 如何设计一个点赞系统? 7. 让你设计一个 RPC 框架,怎么设计? 8. 什么是限流?限流算法有哪些? 9. 负载均衡算法有哪些? 10. 分布式和微服务有什么区别? 11. 什么是服务熔断? 12. 什么是服务降级? 13. 什么是服务雪崩? ## 更多 💻 编程学习交流:[编程导航](https://www.codefather.cn/) 📃 简历快速制作:[老鱼简历](https://laoyujianli.com) ✏️ 面试刷题神器:[面试鸭](https://mianshiya.com)

下载 APP