From e81e7f25281b78b0232d2fbb3c7e6a3ea53c76a6 Mon Sep 17 00:00:00 2001 From: bunny <1319900154@qq.com> Date: Sat, 14 Jun 2025 17:02:42 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20vue2=E4=B8=AD=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E5=92=8C=20=E6=A8=A1=E6=9D=BF=E8=AF=AD?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Vim/Vim相关命令.md | 173 + Vim/vim1.md | 60 - Vim/vim2.md | 109 - cloud-demo/gateway/pom.xml | 1 - .../src/main/resources/application-route.yaml | 2 +- .../service/feign/ProductFeignClient.java | 1 - interview/Base.md | 1104 ++ mq-demo/pom.xml | 15 +- .../mq/listener/MessageListenerOrder.java | 26 +- vue2-tutorials/import-script/1-模板语法.html | 34 + vue2-tutorials/import-script/2-数据绑定.html | 64 + vue2-tutorials/import-script/js/vue@2.7.16.js | 13223 ++++++++++++++++ 12 files changed, 14614 insertions(+), 198 deletions(-) create mode 100644 Vim/Vim相关命令.md delete mode 100644 Vim/vim1.md delete mode 100644 Vim/vim2.md create mode 100644 interview/Base.md create mode 100644 vue2-tutorials/import-script/1-模板语法.html create mode 100644 vue2-tutorials/import-script/2-数据绑定.html create mode 100644 vue2-tutorials/import-script/js/vue@2.7.16.js diff --git a/Vim/Vim相关命令.md b/Vim/Vim相关命令.md new file mode 100644 index 0000000..a2c22de --- /dev/null +++ b/Vim/Vim相关命令.md @@ -0,0 +1,173 @@ +# Vim相关命令 + +## 基本移动命令 + +| 命令 | 描述 | +| -------- | -------------------------------- | +| `h` | 向左移动 | +| `j` | 向下移动 | +| `k` | 向上移动 | +| `l` | 向右移动 | +| `w` | 移动到下一个单词开头 | +| `W` | 移动到下一个单词开头(忽略符号) | +| `b` | 移动到上一个单词开头 | +| `B` | 移动到上一个单词开头(忽略符号) | +| `e` | 移动到当前单词末尾 | +| `E` | 移动到当前单词末尾(忽略符号) | +| `0` | 移动到行首 | +| `^` | 移动到行首第一个非空白字符 | +| `$` | 移动到行尾 | +| `gg` | 移动到文件开头 | +| `G` | 移动到文件末尾 | +| `:[n]` | 移动到第 n 行 | +| `Ctrl+f` | 向下翻页 | +| `Ctrl+b` | 向上翻页 | +| `H` | 移动到屏幕顶部 | +| `M` | 移动到屏幕中间 | +| `L` | 移动到屏幕底部 | + +--- + +## 插入模式 + +| 命令 | 描述 | +| -------- | -------------------------- | +| `i` | 在光标前插入 | +| `a` | 在光标后插入 | +| `I` | 在行首插入 | +| `A` | 在行尾插入 | +| `o` | 在当前行下方新建一行并插入 | +| `O` | 在当前行上方新建一行并插入 | +| `R` | 进入替换模式(覆盖字符) | +| `Ctrl+h` | 删除前一个字符(退格键) | +| `Ctrl+w` | 删除前一个单词 | +| `Ctrl+u` | 删除到行首 | + +--- + +## 编辑命令 + +| 命令 | 描述 | +| -------- | -------------------- | +| `x` | 删除当前字符 | +| `X` | 删除前一个字符 | +| `dd` | 删除当前行 | +| `dw` | 删除到下一个单词开头 | +| `db` | 删除到上一个单词开头 | +| `d$` | 删除到行尾 | +| `d^` | 删除到行首 | +| `dG` | 删除到文件末尾 | +| `dgg` | 删除到文件开头 | +| `yy` | 复制当前行 | +| `yw` | 复制到下一个单词开头 | +| `y$` | 复制到行尾 | +| `p` | 粘贴到光标后 | +| `P` | 粘贴到光标前 | +| `u` | 撤销 | +| `Ctrl+r` | 重做 | +| `.` | 重复上一次修改 | +| `J` | 合并下一行到当前行 | +| `>>` | 向右缩进 | +| `<<` | 向左缩进 | +| `==` | 自动缩进当前行 | + +--- + +## 搜索与替换 + +| 命令 | 描述 | +| ---------------- | ------------------------ | +| `/pattern` | 向前搜索 pattern | +| `?pattern` | 向后搜索 pattern | +| `n` | 下一个匹配项 | +| `N` | 上一个匹配项 | +| `:%s/old/new/g` | 全局替换 old 为 new | +| `:%s/old/new/gc` | 全局替换,每次确认 | +| `:s/old/new/g` | 当前行替换 | +| `*` | 向前搜索当前光标下的单词 | +| `#` | 向后搜索当前光标下的单词 | +| `:noh` | 临时取消搜索高亮 | + +--- + +## 可视模式(选择文本) + +| 命令 | 描述 | +| ---------------- | ------------------------------ | +| `v` | 进入字符可视模式(按字符选择) | +| `V` | 进入行可视模式(按行选择) | +| `Ctrl+v` | 进入块可视模式(矩形选择) | +| `ggVG` 或 `ggvG` | **全选**整个文件 | +| `o` | 切换选择的光标端 | +| `y` | 复制选中内容 | +| `d` | 删除(剪切)选中内容 | +| `c` | 修改(删除并进入插入模式) | +| `~` | 切换选中内容的大小写 | + +--- + +## 窗口管理 + +| 命令 | 描述 | +| ------------- | -------------- | +| `:sp [file]` | 水平分割窗口 | +| `:vsp [file]` | 垂直分割窗口 | +| `Ctrl+w h` | 移动到左侧窗口 | +| `Ctrl+w j` | 移动到下方窗口 | +| `Ctrl+w k` | 移动到上方窗口 | +| `Ctrl+w l` | 移动到右侧窗口 | +| `Ctrl+w w` | 循环切换窗口 | +| `Ctrl+w c` | 关闭当前窗口 | +| `Ctrl+w o` | 只保留当前窗口 | +| `Ctrl+w +` | 增加窗口高度 | +| `Ctrl+w -` | 减少窗口高度 | +| `Ctrl+w >` | 增加窗口宽度 | +| `Ctrl+w <` | 减少窗口宽度 | +| `Ctrl+w =` | 均衡窗口尺寸 | + +--- + +## 标签页 + +| 命令 | 描述 | +| ---------------- | --------------------- | +| `:tabnew [file]` | 新建标签页 | +| `gt` | 下一个标签页 | +| `gT` | 上一个标签页 | +| `:tabclose` | 关闭当前标签页 | +| `:tabonly` | 关闭其他标签页 | +| `:tabmove [n]` | 移动标签页到第 n 位置 | + +--- + +## 文件操作 + +| 命令 | 描述 | +| -------------- | -------------------------- | +| `:e file` | 打开文件 | +| `:w` | 保存文件 | +| `:w file` | 另存为 file | +| `:q` | 退出 | +| `:q!` | 强制退出不保存 | +| `:wq` | 保存并退出 | +| `:x` | 保存并退出(仅当有修改时) | +| `:saveas file` | 另存为 file | +| `:r file` | 插入文件内容到当前位置 | +| `:r !command` | 插入命令输出到当前位置 | + +--- + +## 其他实用命令 + +| 命令 | 描述 | +| ----------------- | --------------------------------------- | +| `:set nu` | 显示行号 | +| `:set nonu` | 隐藏行号 | +| `:set hlsearch` | 高亮搜索结果 | +| `:set nohlsearch` | 取消高亮搜索结果 | +| `:set paste` | 进入粘贴模式(防止自动缩进) | +| `:set nopaste` | 退出粘贴模式 | +| `:!command` | 执行外部命令 | +| `:help` | 打开帮助文档 | +| `Ctrl+g` | 显示当前文件信息 | +| `:%!command` | 用命令处理整个文件(如 `:%!sort` 排序) | diff --git a/Vim/vim1.md b/Vim/vim1.md deleted file mode 100644 index 0d47084..0000000 --- a/Vim/vim1.md +++ /dev/null @@ -1,60 +0,0 @@ -## **1. 基础移动(Navigation)** -- **字符移动**: - - `h` ← / `j` ↓ / `k` ↑ / `l` → - - 方向键也能用,但 Vim 提倡用 `hjkl` 保持手不离键盘。 -- **单词移动**: - - `w` → 跳到下一个单词开头 - - `b` ← 跳到上一个单词开头 - - `e` → 跳到当前单词末尾 -- **行内跳转**: - - `0` 跳到行首,`^` 跳到第一个非空字符 - - `$` 跳到行尾 - - `f` 向后搜索字符(如 `fa` 跳到下一个 `a`) -- **屏幕移动**: - - `Ctrl + u` 上翻半屏,`Ctrl + d` 下翻半屏 - - `gg` 跳到文件开头,`G` 跳到文件末尾 - - `:<行号>` 跳转到指定行(如 `:42` 跳到第 42 行) - ---- - -## **2. 编辑文本(Editing)** -- **插入模式**: - - `i` 在光标前插入 - - `a` 在光标后插入 - - `I` 在行首插入,`A` 在行尾插入 - - `o` 在下一行插入,`O` 在上一行插入 -- **删除**: - - `x` 删除当前字符 - - `dw` 删除一个单词 - - `dd` 删除整行 - - `D` 删除从光标到行尾 -- **复制/粘贴**: - - `yy` 复制当前行 - - `p` 粘贴到光标后,`P` 粘贴到光标前 - - `yiw` 复制当前单词 -- **撤销/重做**: - - `u` 撤销 - - `Ctrl + r` 重做 - ---- - -## **3. 选择与可视化模式(Visual Mode)** -- `v` 进入字符选择模式 -- `V` 进入行选择模式 -- `Ctrl + v` 进入块选择模式(适合多行编辑) -- 选中后可以用 `d`(删除)、`y`(复制)、`>`(缩进)等操作 - ---- - -## **4. 搜索与替换(Search & Replace)** -- `/` + 关键词 → 搜索(`n` 下一个,`N` 上一个) -- `:%s/old/new/g` → 全局替换(`g` 表示所有匹配) -- `*` 快速搜索当前光标下的单词 - ---- - -## **5. 组合命令(Power Moves)** -- `di"` → 删除引号内的内容 -- `ciw` → 修改当前单词 -- `dt` → 删除直到某个字符(如 `dt)` 删除到 `)`) -- `.` → 重复上一次操作 diff --git a/Vim/vim2.md b/Vim/vim2.md deleted file mode 100644 index 587670a..0000000 --- a/Vim/vim2.md +++ /dev/null @@ -1,109 +0,0 @@ -## 基本移动命令 - -| 命令 | 描述 | -| ------ | -------------------------- | -| `h` | 向左移动 | -| `j` | 向下移动 | -| `k` | 向上移动 | -| `l` | 向右移动 | -| `w` | 移动到下一个单词开头 | -| `b` | 移动到上一个单词开头 | -| `e` | 移动到当前单词末尾 | -| `0` | 移动到行首 | -| `^` | 移动到行首第一个非空白字符 | -| `$` | 移动到行尾 | -| `gg` | 移动到文件开头 | -| `G` | 移动到文件末尾 | -| `:[n]` | 移动到第 n 行 | - -## 插入模式 - -| 命令 | 描述 | -| ---- | -------------------------- | -| `i` | 在光标前插入 | -| `a` | 在光标后插入 | -| `I` | 在行首插入 | -| `A` | 在行尾插入 | -| `o` | 在当前行下方新建一行并插入 | -| `O` | 在当前行上方新建一行并插入 | - -## 编辑命令 - -| 命令 | 描述 | -| -------- | -------------------- | -| `x` | 删除当前字符 | -| `dd` | 删除当前行 | -| `dw` | 删除到下一个单词开头 | -| `d$` | 删除到行尾 | -| `d^` | 删除到行首 | -| `yy` | 复制当前行 | -| `p` | 粘贴 | -| `u` | 撤销 | -| `Ctrl+r` | 重做 | -| `.` | 重复上一次修改 | - -## 搜索与替换 - -| 命令 | 描述 | -| ---------------- | ------------------- | -| `/pattern` | 向前搜索 pattern | -| `?pattern` | 向后搜索 pattern | -| `n` | 下一个匹配项 | -| `N` | 上一个匹配项 | -| `:%s/old/new/g` | 全局替换 old 为 new | -| `:%s/old/new/gc` | 全局替换,每次确认 | - -## 可视模式 - -| 命令 | 描述 | -| -------- | ---------------- | -| `v` | 进入字符可视模式 | -| `V` | 进入行可视模式 | -| `Ctrl+v` | 进入块可视模式 | - -## 窗口管理 - -| 命令 | 描述 | -| ------------- | -------------- | -| `:sp [file]` | 水平分割窗口 | -| `:vsp [file]` | 垂直分割窗口 | -| `Ctrl+w h` | 移动到左侧窗口 | -| `Ctrl+w j` | 移动到下方窗口 | -| `Ctrl+w k` | 移动到上方窗口 | -| `Ctrl+w l` | 移动到右侧窗口 | -| `Ctrl+w c` | 关闭当前窗口 | -| `Ctrl+w o` | 只保留当前窗口 | - -## 标签页 - -| 命令 | 描述 | -| ---------------- | -------------- | -| `:tabnew [file]` | 新建标签页 | -| `gt` | 下一个标签页 | -| `gT` | 上一个标签页 | -| `:tabclose` | 关闭当前标签页 | - -## 文件操作 - -| 命令 | 描述 | -| --------- | -------------- | -| `:e file` | 打开文件 | -| `:w` | 保存文件 | -| `:w file` | 另存为 file | -| `:q` | 退出 | -| `:q!` | 强制退出不保存 | -| `:wq` | 保存并退出 | -| `:x` | 保存并退出 | - -## 其他实用命令 - -| 命令 | 描述 | -| ----------------- | --------------------------------- | -| `*` | 搜索当前光标下的单词 | -| `#` | 反向搜索当前光标下的单词 | -| `:%!command` | 将当前缓冲区内容通过 command 过滤 | -| `:r!command` | 将 command 的输出插入到当前位置 | -| `:set nu` | 显示行号 | -| `:set nonu` | 隐藏行号 | -| `:set hlsearch` | 高亮搜索结果 | -| `:set nohlsearch` | 取消高亮搜索结果 | diff --git a/cloud-demo/gateway/pom.xml b/cloud-demo/gateway/pom.xml index 6da9863..cf1144f 100644 --- a/cloud-demo/gateway/pom.xml +++ b/cloud-demo/gateway/pom.xml @@ -69,6 +69,5 @@ org.springframework.cloud spring-cloud-starter-loadbalancer - diff --git a/cloud-demo/gateway/src/main/resources/application-route.yaml b/cloud-demo/gateway/src/main/resources/application-route.yaml index 355efeb..ce31cd9 100644 --- a/cloud-demo/gateway/src/main/resources/application-route.yaml +++ b/cloud-demo/gateway/src/main/resources/application-route.yaml @@ -49,4 +49,4 @@ spring: param: user value: bunny # filters: - # - RedirectTo=/api/order/?(?.*), /$\{segment} + # - RedirectTo=/api/order/?(?.*), /$\{segment} \ No newline at end of file diff --git a/cloud-demo/services/service-order/src/main/java/cn/bunny/service/feign/ProductFeignClient.java b/cloud-demo/services/service-order/src/main/java/cn/bunny/service/feign/ProductFeignClient.java index 1374f7c..676ddc2 100644 --- a/cloud-demo/services/service-order/src/main/java/cn/bunny/service/feign/ProductFeignClient.java +++ b/cloud-demo/services/service-order/src/main/java/cn/bunny/service/feign/ProductFeignClient.java @@ -7,7 +7,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; // Feign 客户端 -// @FeignClient(value = "service-product", path = "/api/product", fallback = ProductFeignClientFallback.class) @FeignClient(value = "gateway", path = "/api/product", fallback = ProductFeignClientFallback.class) public interface ProductFeignClient { diff --git a/interview/Base.md b/interview/Base.md new file mode 100644 index 0000000..74c0654 --- /dev/null +++ b/interview/Base.md @@ -0,0 +1,1104 @@ +# Java面试 + +## 高频面试题 + +### Bean生命周期 + +#### Spring Bean生命周期主要阶段 + +1. **实例化(Instantiation)**: + - 通过构造函数或工厂方法创建Bean实例 + - 对应`new`关键字或`FactoryBean`的`getObject()`方法 + +2. **属性赋值(Populate properties)**: + - Spring容器注入Bean的依赖项(通过setter、构造函数或自动装配) + +3. **BeanNameAware的setBeanName()**: + - 如果Bean实现了`BeanNameAware`接口,会调用`setBeanName()`方法 + +4. **BeanFactoryAware的setBeanFactory()**: + - 如果Bean实现了`BeanFactoryAware`接口,会调用`setBeanFactory()`方法 + +5. **ApplicationContextAware的setApplicationContext()**: + - 如果Bean实现了`ApplicationContextAware`接口,会调用`setApplicationContext()`方法 + +6. **预初始化(BeanPostProcessor的postProcessBeforeInitialization())**: + - 任何实现了`BeanPostProcessor`接口的Bean会在此处调用`postProcessBeforeInitialization()`方法 + +7. **初始化(Initialization)**: + - 如果Bean实现了`InitializingBean`接口,调用`afterPropertiesSet()`方法 + - 如果配置了自定义的init方法(如`init-method`或`@PostConstruct`),调用该方法 + +8. **后初始化(BeanPostProcessor的postProcessAfterInitialization())**: + - 任何实现了`BeanPostProcessor`接口的Bean会在此处调用`postProcessAfterInitialization()`方法 + +9. **使用中(Ready for use)**: + - Bean已经完全初始化,可以被应用程序使用 + +10. **销毁(Destruction)**: + - 如果Bean实现了`DisposableBean`接口,调用`destroy()`方法 + - 如果配置了自定义的destroy方法(如`destroy-method`或`@PreDestroy`),调用该方法 + +#### 常用生命周期回调方式 + +1. **注解方式**: + - `@PostConstruct`:初始化方法 + - `@PreDestroy`:销毁方法 + +2. **接口方式**: + - `InitializingBean`和`afterPropertiesSet()` + - `DisposableBean`和`destroy()` + +3. **XML配置方式**: + - `init-method`和`destroy-method`属性 + +#### 示例代码 + +```java +public class ExampleBean implements BeanNameAware, InitializingBean, DisposableBean { + + private String name; + + // 1. 构造函数 + public ExampleBean() { + System.out.println("1. 构造函数调用 - Bean实例化"); + } + + // 2. 属性注入 + public void setName(String name) { + System.out.println("2. 属性注入 - setName()"); + this.name = name; + } + + // 3. BeanNameAware + @Override + public void setBeanName(String name) { + System.out.println("3. BeanNameAware.setBeanName()"); + } + + // 4. @PostConstruct + @PostConstruct + public void postConstruct() { + System.out.println("4. @PostConstruct方法"); + } + + // 5. InitializingBean + @Override + public void afterPropertiesSet() { + System.out.println("5. InitializingBean.afterPropertiesSet()"); + } + + // 6. 自定义init方法 + public void initMethod() { + System.out.println("6. 自定义init方法"); + } + + // 7. @PreDestroy + @PreDestroy + public void preDestroy() { + System.out.println("7. @PreDestroy方法"); + } + + // 8. DisposableBean + @Override + public void destroy() { + System.out.println("8. DisposableBean.destroy()"); + } + + // 9. 自定义destroy方法 + public void destroyMethod() { + System.out.println("9. 自定义destroy方法"); + } +} +``` + +#### 面试回答技巧 + +1. 从简单到详细:先概述主要阶段,再深入细节 +2. 结合实际经验:可以提到你在项目中如何使用生命周期回调 +3. 区分容器管理和普通Java对象:强调Spring容器对生命周期的管理 +4. 提及相关扩展点:如`BeanPostProcessor`、`BeanFactoryPostProcessor`等 + +记住,不同版本的Spring框架可能会有细微差别,但核心生命周期阶段基本保持一致。 + +### AOP(面向切面编程) + +#### AOP核心概念 + +1. **切面(Aspect)**:横切关注点的模块化,包含通知和切点 +2. **连接点(Join Point)**:程序执行过程中的特定点,如方法调用或异常抛出 +3. **通知(Advice)**:在切面的某个连接点上执行的动作 +4. **切点(Pointcut)**:匹配连接点的谓词,确定通知应该应用到哪些连接点 +5. **引入(Introduction)**:向现有类添加新方法或属性 +6. **目标对象(Target Object)**:被一个或多个切面通知的对象 +7. **AOP代理(AOP Proxy)**:由AOP框架创建的对象,用于实现切面契约 + +#### AOP实现原理 + +##### 1. 代理模式 + +AOP的核心实现基于**代理模式**,主要有两种实现方式: + +(1) JDK动态代理 + +- **适用场景**:目标对象实现了至少一个接口 +- **实现方式**: + - 通过`java.lang.reflect.Proxy`类创建代理对象 + - 实现`InvocationHandler`接口处理代理逻辑 +- **特点**: + - 基于接口代理 + - Java原生支持,无需额外依赖 + - 性能较好 + +(2) CGLIB代理 + +- **适用场景**:目标对象没有实现接口 +- **实现方式**: + - 通过继承目标类生成子类 + - 重写父类方法实现代理逻辑 +- **特点**: + - 基于类代理 + - 需要引入CGLIB库 + - 不能代理final类和方法 + +##### 2. Spring AOP实现机制 + +Spring AOP默认使用以下策略: +- 如果目标对象实现了接口,使用JDK动态代理 +- 如果目标对象没有实现接口,使用CGLIB代理 +- 可以通过配置强制使用CGLIB + +##### 3. 通知类型 + +1. **前置通知(Before advice)**:在连接点之前执行 +2. **后置通知(After returning advice)**:连接点正常完成后执行 +3. **异常通知(After throwing advice)**:连接点抛出异常后执行 +4. **最终通知(After (finally) advice)**:连接点完成后执行(无论正常或异常) +5. **环绕通知(Around advice)**:包围连接点,可以控制是否执行连接点 + +#### AOP工作流程 + +1. **解析切面配置**:读取@Aspect注解或XML配置 +2. **创建代理对象**: + - 根据目标对象选择代理方式(JDK或CGLIB) + - 生成代理类字节码 + - 实例化代理对象 +3. **拦截方法调用**: + - 当调用代理对象方法时 + - 检查该方法是否匹配切点表达式 + - 如果匹配,按顺序执行相关通知 +4. **执行目标方法**: + - 在环绕通知中通过`ProceedingJoinPoint.proceed()`调用目标方法 + - 或由代理直接调用目标方法 + +#### Spring AOP与AspectJ比较 + +| 特性 | Spring AOP | AspectJ | +| -------- | -------------------- | -------------------------------- | +| 实现方式 | 运行时代理 | 编译时/加载时编织 | +| 性能 | 较慢 | 更快 | +| 功能 | 仅支持方法级别的切点 | 支持字段、构造器、方法等更多切点 | +| 依赖 | Spring容器 | 需要AspectJ编译器或织入器 | +| 学习曲线 | 较简单 | 较复杂 | +| 适用场景 | 大多数Spring应用 | 需要更强大AOP功能的场景 | + +#### 示例代码 + +```java +// 定义切面 +@Aspect +@Component +public class LoggingAspect { + + // 定义切点 + @Pointcut("execution(* com.example.service.*.*(..))") + private void serviceLayer() {} + + // 前置通知 + @Before("serviceLayer()") + public void beforeAdvice(JoinPoint joinPoint) { + System.out.println("Before method: " + joinPoint.getSignature().getName()); + } + + // 环绕通知 + @Around("serviceLayer()") + public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { + System.out.println("Around before: " + joinPoint.getSignature().getName()); + Object result = joinPoint.proceed(); + System.out.println("Around after: " + joinPoint.getSignature().getName()); + return result; + } +} +``` + +#### 面试回答技巧 + +1. **分层回答**:先讲核心概念,再深入实现原理 +2. **结合实际**:举例说明你在项目中如何使用AOP解决实际问题 +3. **对比分析**:比较不同实现方式的优缺点 +4. **扩展知识**:可以提到AOP的适用场景(日志、事务、安全等) +5. **性能考量**:讨论代理方式的选择对性能的影响 + +记住,AOP是Spring框架的核心功能之一,理解其原理对于掌握Spring至关重要。 + +### 事务传播机制详解 + +事务传播机制是Spring事务管理中的重要概念,它定义了在多个事务方法相互调用时,事务应该如何传播。 + +#### 七种传播行为 + +Spring定义了7种事务传播行为,通过`Propagation`枚举表示: + +1. **REQUIRED(默认)**: + - 如果当前存在事务,则加入该事务 + - 如果当前没有事务,则创建一个新事务 + - **适用场景**:大多数业务方法使用此级别 + +2. **SUPPORTS**: + - 如果当前存在事务,则加入该事务 + - 如果当前没有事务,则以非事务方式执行 + - **适用场景**:查询方法,可有可无事务 + +3. **MANDATORY**: + - 如果当前存在事务,则加入该事务 + - 如果当前没有事务,则抛出异常 + - **适用场景**:必须运行在事务中的方法 + +4. **REQUIRES_NEW**: + - 总是创建一个新事务 + - 如果当前存在事务,则挂起当前事务 + - **适用场景**:需要独立事务的方法,如日志记录 + +5. **NOT_SUPPORTED**: + - 以非事务方式执行 + - 如果当前存在事务,则挂起当前事务 + - **适用场景**:不需要事务支持的方法 + +6. **NEVER**: + - 以非事务方式执行 + - 如果当前存在事务,则抛出异常 + - **适用场景**:不允许在事务中运行的方法 + +7. **NESTED**: + - 如果当前存在事务,则在嵌套事务内执行 + - 如果当前没有事务,则创建一个新事务 + - **特点**: + - 嵌套事务是外部事务的子事务 + - 子事务回滚不会影响外部事务 + - 外部事务回滚会导致子事务回滚 + - **适用场景**:需要部分回滚的场景 + - **注意**:需要JDBC 3.0以上驱动支持 + +#### 传播行为对比表 + +| 传播行为 | 当前有事务 | 当前无事务 | 特点说明 | +| ------------- | ---------- | ---------- | ------------------------ | +| REQUIRED | 加入 | 创建新事务 | 默认设置,最常用 | +| SUPPORTS | 加入 | 非事务执行 | 可有可无的事务 | +| MANDATORY | 加入 | 抛出异常 | 强制要求存在事务 | +| REQUIRES_NEW | 挂起并新建 | 创建新事务 | 完全独立的新事务 | +| NOT_SUPPORTED | 挂起 | 非事务执行 | 强制非事务执行 | +| NEVER | 抛出异常 | 非事务执行 | 强制要求不能有事务 | +| NESTED | 嵌套事务 | 创建新事务 | 嵌套在现有事务中的子事务 | + +#### 典型应用场景 + +1. **REQUIRED**: + ```java + @Transactional(propagation = Propagation.REQUIRED) + public void placeOrder(Order order) { + // 订单处理逻辑 + } + ``` + +2. **REQUIRES_NEW**(日志记录): + ```java + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void logActivity(Activity activity) { + // 活动日志记录,即使主事务回滚也要记录 + } + ``` + +3. **NESTED**(部分回滚): + ```java + @Transactional(propagation = Propagation.NESTED) + public void updateInventory(Order order) { + // 库存更新,可以独立回滚而不影响整个订单 + } + ``` + +#### 实现原理 + +Spring事务传播机制的实现基于以下技术: + +1. **事务管理器**(PlatformTransactionManager) +2. **事务定义**(TransactionDefinition) +3. **事务状态**(TransactionStatus) +4. **线程绑定**(ThreadLocal保存当前事务状态) + +当方法调用发生时: +1. 检查当前线程是否存在事务 +2. 根据传播行为决定是加入、创建新事务还是挂起当前事务 +3. 通过TransactionSynchronizationManager管理事务资源 + +#### 常见面试问题示例 + +**Q: REQUIRED和REQUIRES_NEW有什么区别?** + +A: +- REQUIRED会加入当前事务(如果存在),而REQUIRES_NEW总是创建新事务并挂起当前事务(如果存在) +- REQUIRED的事务是同一个物理事务,REQUIRES_NEW创建的是完全独立的新事务 +- REQUIRED中内部方法回滚会导致外部事务回滚,而REQUIRES_NEW的内部方法回滚不会影响外部事务 + +**Q: NESTED和REQUIRES_NEW有什么区别?** + +A: +- NESTED是嵌套事务,REQUIRES_NEW是独立新事务 +- NESTED事务的回滚不会影响外部事务,但外部事务回滚会导致NESTED事务回滚 +- REQUIRES_NEW事务完全独立,内外事务互不影响 +- NESTED事务会使用保存点(Savepoint)实现部分回滚 + +#### 最佳实践建议 + +1. 大多数业务方法使用默认的REQUIRED传播行为 +2. 需要独立事务的操作(如日志记录)使用REQUIRES_NEW +3. 需要部分回滚能力的场景考虑使用NESTED +4. 查询方法可以使用SUPPORTS以减少事务开销 +5. 明确不需要事务的方法使用NOT_SUPPORTED +6. 避免滥用REQUIRES_NEW,因为它会带来更多数据库连接开销 + +### 分布式锁详解 + +分布式锁是分布式系统中用于协调多个节点对共享资源访问的重要机制。以下是关于分布式锁的全面解析: + +#### 为什么需要分布式锁? + +在分布式系统中,当多个服务实例需要访问/修改共享资源时,需要一种跨JVM的互斥机制来保证: +- 数据一致性 +- 避免重复处理 +- 防止资源竞争 + +#### 分布式锁的核心特性 + +1. **互斥性**:同一时刻只有一个客户端能持有锁 +2. **可重入性**:同一客户端可多次获取同一把锁 +3. **锁超时**:防止死锁,自动释放机制 +4. **高可用**:锁服务需要高可用 +5. **非阻塞**:获取锁失败应快速返回而非阻塞 +6. **公平性**(可选):按申请顺序获取锁 + +#### 常见实现方案 + +##### 1. 基于数据库 + +**实现方式**: + +- 唯一索引:利用唯一键冲突实现 +- 乐观锁:版本号机制 +- 悲观锁:`select for update` + +**特点**: +- 实现简单 +- 性能较差(高并发下数据库压力大) +- 无自动过期机制(需自行实现) + +##### 2. 基于Redis + +**常用命令**: +```bash +SET key value [EX seconds] [PX milliseconds] [NX|XX] +``` + +**Redlock算法**(Redis官方推荐的分布式锁算法): +1. 获取当前时间 +2. 依次尝试从N个Redis节点获取锁 +3. 计算获取锁耗时,只有耗时小于锁超时时间且从多数节点获取成功才算获取成功 +4. 锁的实际有效时间 = 初始有效时间 - 获取锁耗时 +5. 如果获取失败,向所有节点发送释放锁请求 + +**特点**: +- 性能好 +- 需要处理锁续期问题(看门狗机制) +- 需考虑Redis集群故障转移时的安全性 + +##### 3. 基于Zookeeper + +**实现原理**: +- 创建临时顺序节点 +- 判断自己是否是最小节点,是则获取锁 +- 否则监听前一个节点的删除事件 + +**特点**: +- 可靠性高(CP系统) +- 性能比Redis略差 +- 原生支持锁释放(临时节点特性) +- 天然支持公平锁 + +##### 4. 基于Etcd + +**实现原理**: +- 利用租约(Lease)机制 +- 事务比较并交换(CAS)操作 +- 前缀查询和监听 + +**特点**: +- 强一致性 +- 高可用 +- 支持TTL自动过期 + +#### 方案对比 + +| 特性 | 数据库 | Redis | Zookeeper | Etcd | +| ---------- | -------------- | ---------- | ------------ | ---------- | +| 性能 | 低 | 高 | 中 | 中高 | +| 实现复杂度 | 简单 | 中等 | 复杂 | 中等 | +| 可靠性 | 一般 | 依赖配置 | 高 | 高 | +| 自动过期 | 不支持 | 支持 | 支持 | 支持 | +| 公平锁 | 不支持 | 可实现 | 原生支持 | 可实现 | +| 适用场景 | 低并发简单场景 | 高并发场景 | 高可靠性场景 | 云原生环境 | + +#### 最佳实践 + +1. **锁粒度**:尽量细化,避免大范围锁 +2. **超时设置**:设置合理的锁超时时间 +3. **幂等性**:即使锁失效,业务逻辑也应保持幂等 +4. **锁续期**:对于长任务实现锁续期机制 +5. **容错处理**:考虑锁服务不可用时的降级方案 +6. **监控**:实现锁获取/释放的监控 + +#### Redis分布式锁示例(Redisson实现) + +```java +// 获取锁 +RLock lock = redisson.getLock("myLock"); +try { + // 尝试加锁,最多等待100秒,上锁后30秒自动解锁 + boolean res = lock.tryLock(100, 30, TimeUnit.SECONDS); + if (res) { + // 业务逻辑 + } +} catch (InterruptedException e) { + Thread.currentThread().interrupt(); +} finally { + // 释放锁 + lock.unlock(); +} +``` + +#### 常见问题解决方案 + +**问题1:锁提前过期** +- 解决方案:实现锁续期(看门狗机制) + +**问题2:误释放其他客户端锁** +- 解决方案:锁值使用唯一客户端标识 + +**问题3:Redis主从切换导致锁失效** +- 解决方案:使用Redlock算法或集群模式 + +#### 面试回答技巧 + +1. **分层回答**:先讲为什么需要分布式锁,再讲实现方案 +2. **对比分析**:比较不同实现方案的优缺点 +3. **结合实际**:分享你在项目中如何使用分布式锁 +4. **深入细节**:对熟悉的方案可以深入实现原理 +5. **问题意识**:讨论分布式锁的常见问题及解决方案 + +分布式锁是分布式系统设计中的重要组件,理解其原理和实现方式对于构建可靠的分布式系统至关重要。 + +### Redis + RabbitMQ 高并发场景解决方案 + +#### Redis 核心问题 + +#### 缓存穿透 +**问题**:大量请求查询不存在的数据,绕过缓存直接访问数据库 + +**解决方案**: +1. **缓存空对象**:对查询结果为null的数据也进行缓存,设置较短过期时间 + ```java + // 伪代码示例 + Object value = redis.get(key); + if (value == null) { + value = db.get(key); + if (value == null) { + redis.setex(key, 300, "NULL"); // 缓存空值5分钟 + } + } + ``` +2. **布隆过滤器**:在缓存前加布隆过滤器判断key是否存在 + ```java + if (!bloomFilter.mightContain(key)) { + return null; // 肯定不存在 + } + ``` + +#### 缓存雪崩 +**问题**:大量缓存同时失效,导致请求直接打到数据库 + +**解决方案**: +1. **差异化过期时间**:为缓存设置随机的过期时间 + ```java + redis.setex(key, 3600 + Random.nextInt(600), value); // 基础1小时+随机10分钟 + ``` +2. **多级缓存**:构建本地缓存+Redis缓存的层级结构 +3. **热点数据永不过期**:配合定期异步更新策略 + +#### 缓存击穿 +**问题**:热点key突然失效,大量请求直接访问数据库 + +**解决方案**: +1. **互斥锁**:使用Redis的SETNX实现分布式锁 + ```java + if (redis.setnx("lock:"+key, 1, 30, TimeUnit.SECONDS)) { + try { + // 查询数据库并重建缓存 + } finally { + redis.del("lock:"+key); + } + } else { + Thread.sleep(50); // 稍后重试 + } + ``` +2. **逻辑过期**:缓存值中包含过期时间字段,异步刷新 + +#### 持久化 +1. **RDB**: + - 定时快照 + - 优点:恢复速度快,适合备份 + - 缺点:可能丢失最后一次快照后的数据 + +2. **AOF**: + - 记录所有写操作命令 + - 优点:数据安全性高(可配置fsync策略) + - 缺点:文件体积大,恢复速度慢 + +**生产建议**: +- 同时开启RDB和AOF +- AOF使用everysec配置 +- 定期执行BGREWRITEAOF压缩AOF文件 + +#### 集群模式 +1. **主从复制**: + - 读写分离 + - 一主多从架构 + - 故障需手动切换 + +2. **哨兵模式**: + - 监控主节点状态 + - 自动故障转移 + - 配置至少3个哨兵节点 + +3. **Cluster模式**: + - 数据分片(16384个slot) + - 每个节点保存部分数据 + - 节点间通过Gossip协议通信 + - 至少需要3主3从 + +**选择建议**: +- 小规模:哨兵模式 +- 大规模:Cluster模式 +- 超大规模:Cluster分片+代理层 + +### RabbitMQ 核心问题 + +#### 保证消息不丢失 + +**完整方案**: +1. **生产者确认**: + ```java + // 开启确认模式 + channel.confirmSelect(); + // 异步确认回调 + channel.addConfirmListener((sequenceNumber, multiple) -> { + // 处理成功确认 + }, (sequenceNumber, multiple) -> { + // 处理失败确认 + }); + ``` + +2. **消息持久化**: + ```java + // 声明持久化队列 + channel.queueDeclare(QUEUE_NAME, true, false, false, null); + // 发送持久化消息 + channel.basicPublish("", QUEUE_NAME, + MessageProperties.PERSISTENT_TEXT_PLAIN, + message.getBytes()); + ``` + +3. **消费者手动ACK**: + ```java + // 关闭自动ACK + channel.basicConsume(QUEUE_NAME, false, consumer); + // 处理完成后手动ACK + channel.basicAck(deliveryTag, false); + ``` + +4. **镜像队列**(高可用): + ```bash + # 设置镜像队列策略 + rabbitmqctl set_policy ha-all "^ha." '{"ha-mode":"all"}' + ``` + +#### 延迟队列实现 + +**方案1:TTL+死信队列** +1. 创建普通队列A并设置死信交换器 +2. 发送消息到A并设置TTL +3. 消息过期后转入死信队列B +4. 消费者监听B队列 + +**方案2:rabbitmq-delayed-message-exchange插件** +1. 安装插件 + ```bash + rabbitmq-plugins enable rabbitmq_delayed_message_exchange + ``` +2. 声明延迟交换器 + ```java + Map args = new HashMap<>(); + args.put("x-delayed-type", "direct"); + channel.exchangeDeclare("delayed.exchange", "x-delayed-message", true, false, args); + ``` +3. 发送延迟消息 + ```java + AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder(); + props.headers(new HashMap<>()).headers().put("x-delay", 5000); + channel.basicPublish("delayed.exchange", "", props.build(), message.getBytes()); + ``` + +#### 集群搭建 + +**普通集群模式**: +1. 同步.erlang.cookie文件到所有节点 +2. 节点加入集群 + ```bash + # 在slave节点执行 + rabbitmqctl stop_app + rabbitmqctl join_cluster rabbit@master + rabbitmqctl start_app + ``` +3. 查看集群状态 + ```bash + rabbitmqctl cluster_status + ``` + +**镜像队列集群**: +1. 先搭建普通集群 +2. 设置镜像策略 + ```bash + rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}' + ``` + +**集群节点类型**: +- **磁盘节点**:保存元数据到磁盘(至少需要一个) +- **内存节点**:只保存元数据到内存(性能更好) + +**最佳实践**: +- 3-5个节点组成集群 +- 混合部署磁盘节点和内存节点 +- 使用HAProxy/Nginx做负载均衡 + +#### Redis + RabbitMQ 组合实践 + +**典型高并发场景架构**: +``` +客户端 → Nginx → 应用集群 → Redis缓存 → RabbitMQ → 数据库 +``` + +**流量削峰示例**: +1. 请求先查Redis缓存 +2. 缓存未命中则发送MQ消息 + ```java + // 生成唯一消息ID + String msgId = UUID.randomUUID().toString(); + // 存储到Redis做状态跟踪 + redis.setex("msg:"+msgId, 3600, "processing"); + // 发送消息 + channel.basicPublish("", "order_queue", + new AMQP.BasicProperties.Builder() + .messageId(msgId) + .build(), + message.getBytes()); + ``` +3. 异步消费者处理并更新结果 + ```java + // 处理成功 + redis.set("msg:"+msgId, "completed"); + redis.set("data:"+orderId, result); + // 处理失败 + redis.set("msg:"+msgId, "failed"); + ``` + +### Spring Security 6 过滤器链面试题解析 + +在 Spring Security 6 面试中,关于过滤器链的问题是高频考点。以下是针对这个主题的全面解析: + +#### 核心概念:SecurityFilterChain + +Spring Security 6 的核心安全功能是通过一系列过滤器实现的,这些过滤器组成了 **过滤器链(Filter Chain)**。在 Spring Security 6 中,`SecurityFilterChain` 是配置安全规则的主要方式。 + +#### 常见面试问题形式 + +1. **基础概念类**: + - "请解释 Spring Security 的过滤器链工作原理" + - "Spring Security 如何处理 HTTP 请求?" + +2. **具体实现类**: + - "Spring Security 6 中默认包含哪些重要过滤器?" + - "如何自定义一个 Security 过滤器?" + +3. **配置相关类**: + - "在 Spring Security 6 中如何配置多个 SecurityFilterChain?" + - "WebSecurity 和 HttpSecurity 有什么区别?" + +4. **深度原理类**: + - "过滤器链中的过滤器顺序为什么很重要?" + - "Spring Security 6 相比之前版本在过滤器方面有什么变化?" + +#### 关键过滤器及其顺序 + +Spring Security 6 的默认过滤器链包含以下重要过滤器(按执行顺序): + +1. **ForceEagerSessionCreationFilter** (新增 in 6.x) + - 强制提前创建会话 + +2. **WebAsyncManagerIntegrationFilter** + - 集成 WebAsyncManager 与 SecurityContext + +3. **SecurityContextPersistenceFilter** + - 在请求间持久化 SecurityContext + +4. **HeaderWriterFilter** + - 添加安全相关的头部信息 + +5. **CorsFilter** + - 处理 CORS 跨域请求 + +6. **CsrfFilter** + - CSRF 防护 + +7. **LogoutFilter** + - 处理注销请求 + +8. **OAuth2AuthorizationRequestRedirectFilter** (OAuth2 相关) + - OAuth2 授权请求重定向 + +9. **Saml2WebSsoAuthenticationRequestFilter** (SAML 相关) + - SAML 认证请求处理 + +10. **X509AuthenticationFilter** + - X509 证书认证 + +11. **AbstractPreAuthenticatedProcessingFilter** + - 预认证处理 + +12. **CasAuthenticationFilter** (CAS 相关) + - CAS 认证处理 + +13. **UsernamePasswordAuthenticationFilter** + - 表单登录认证处理 + +14. **OpenIDAuthenticationFilter** (OpenID 相关) + - OpenID 认证处理 + +15. **DefaultLoginPageGeneratingFilter** + - 默认登录页生成 + +16. **DefaultLogoutPageGeneratingFilter** + - 默认注销页生成 + +17. **BasicAuthenticationFilter** + - HTTP 基本认证处理 + +18. **RequestCacheAwareFilter** + - 请求缓存处理 + +19. **SecurityContextHolderAwareRequestFilter** + - 使 HttpServletRequest 感知 SecurityContext + +20. **JaasApiIntegrationFilter** (JAAS 相关) + - JAAS 集成 + +21. **RememberMeAuthenticationFilter** + - 记住我功能处理 + +22. **AnonymousAuthenticationFilter** + - 匿名用户认证处理 + +23. **OAuth2LoginAuthenticationFilter** (OAuth2 相关) + - OAuth2 登录认证 + +24. **Saml2WebSsoAuthenticationFilter** (SAML 相关) + - SAML Web SSO 认证 + +25. **ExceptionTranslationFilter** + - 安全异常转换 + +26. **FilterSecurityInterceptor** + - 最终访问决策 + +#### 常见面试问题及回答示例 + +#### Q1: 请解释 Spring Security 的过滤器链工作原理 + +**回答要点**: +1. Spring Security 基于 Servlet 过滤器链实现 +2. 每个过滤器负责特定安全功能 +3. 请求按顺序通过所有过滤器 +4. 过滤器可以决定是否中断链式调用 +5. 最后一个过滤器 (`FilterSecurityInterceptor`) 做最终访问控制决策 + +**示例回答**: +"Spring Security 的安全功能是通过一系列过滤器组成的链条实现的。当 HTTP 请求到达时,它会依次通过这些过滤器,每个过滤器处理特定的安全关注点。比如早期的过滤器可能处理 CSRF 防护,后续的过滤器处理认证逻辑,最后的过滤器做授权决策。如果任何过滤器判定请求不安全,它可以中断处理流程并返回错误响应。" + +#### Q2: 如何在 Spring Security 6 中自定义过滤器? + +**回答要点**: +1. 实现 `Filter` 接口或继承 `GenericFilterBean` +2. 通过 `HttpSecurity` 配置添加 +3. 可以指定过滤器的位置 +4. 示例代码: + +```java +@Bean +SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) + .addFilterAfter(new AnotherFilter(), BasicAuthenticationFilter.class); + return http.build(); +} +``` + +#### Q3: Spring Security 6 在过滤器方面有哪些重要变化? + +**回答要点**: +1. 移除了 `WebSecurityConfigurerAdapter`,改用 `SecurityFilterChain` bean +2. 默认情况下不再自动创建会话 +3. 新增 `ForceEagerSessionCreationFilter` +4. 更清晰的过滤器配置 API +5. 更强调函数式配置风格 + +#### Q4: 为什么过滤器顺序很重要? + +**回答要点**: +1. 某些过滤器依赖前置过滤器的处理结果 +2. 例如认证过滤器需要在授权过滤器之前 +3. 错误的顺序可能导致安全漏洞 +4. CSRF 防护通常需要在早期执行 +5. 异常处理过滤器需要在可能抛出异常的过滤器之后 + +#### 配置多个 SecurityFilterChain + +Spring Security 6 支持基于请求路径配置多个过滤器链: + +```java +@Bean +@Order(1) +public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { + http + .securityMatcher("/api/**") + .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) + .httpBasic(Customizer.withDefaults()); + return http.build(); +} + +@Bean +@Order(2) +public SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()); + return http.build(); +} +``` + +#### 面试技巧 + +1. **结合实践**:分享你在项目中如何定制过滤器链 +2. **对比版本**:说明 Spring Security 5 和 6 在过滤器配置上的区别 +3. **强调安全**:解释为什么默认过滤器链的顺序是安全的 +4. **准备示例**:准备一个自定义过滤器的代码示例 +5. **理解原理**:能解释过滤器链与 Servlet 容器的关系 + +理解 Spring Security 的过滤器链机制是掌握其工作原理的关键,也是面试中的高频考察点。 + +### MySQL索引优化 + +#### 1. 索引基础概念 + +索引是帮助MySQL高效获取数据的数据结构,类似于书籍的目录,可以加快查询速度。 + +#### 2. 索引类型 + +##### (1) 按数据结构分类: +- **B+Tree索引**:最常用,适合范围查询 +- **Hash索引**:适合等值查询,不支持范围查询 +- **Full-text索引**:全文检索 +- **R-Tree索引**:空间数据索引 + +##### (2) 按逻辑分类: +- **普通索引**:最基本的索引,无限制 +- **唯一索引**:列值必须唯一,允许NULL +- **主键索引**:特殊的唯一索引,不允许NULL +- **复合索引**:多列组合的索引 +- **覆盖索引**:查询的列都在索引中 + +#### 3. 索引优化原则 + +1. **最左前缀原则**: + - 复合索引(a,b,c),有效查询条件: + - a + - a,b + - a,b,c + - 无效查询条件: + - b + - b,c + - c + +2. **避免索引失效场景**: + - 使用`!=`或`<>`操作符 + - 使用`OR`连接条件(除非所有OR条件都有索引) + - 对索引列进行运算或函数操作 + - 使用`LIKE`以通配符开头('%abc') + - 类型转换(如字符串列用数字查询) + +3. **选择合适的索引列**: + - 高选择性的列(区分度高) + - 常用于WHERE、ORDER BY、GROUP BY、JOIN的列 + - 避免对频繁更新的列建过多索引 + +4. **控制索引数量**: + - 不是越多越好,每个索引占用存储空间 + - 增删改操作需要维护索引,影响性能 + +#### 4. 索引优化实战技巧 + +1. **使用覆盖索引**: + ```sql + -- 假设有索引(username, age) + SELECT username, age FROM users WHERE username = 'john'; + ``` + +2. **索引列顺序优化**: + - 将选择性高的列放在前面 + - 考虑查询频率和排序需求 + +3. **使用索引提示**: + ```sql + SELECT * FROM users USE INDEX(index_name) WHERE ... + SELECT * FROM users FORCE INDEX(index_name) WHERE ... + ``` + +4. **前缀索引**: + ```sql + ALTER TABLE users ADD INDEX idx_name(name(10)); -- 只索引前10个字符 + ``` + +### Explain执行计划详解 + +#### 1. Explain基本用法 + +```sql +EXPLAIN SELECT * FROM users WHERE id = 1; +``` + +#### 2. 关键字段解析 + +| 字段 | 说明 | +| ------------- | ------------------------------------------------------------ | +| id | 查询标识符,相同id表示同一查询块 | +| select_type | 查询类型(SIMPLE, PRIMARY, SUBQUERY, DERIVED等) | +| table | 访问的表名 | +| partitions | 匹配的分区 | +| type | 访问类型(从好到差: system > const > eq_ref > ref > range > index > ALL) | +| possible_keys | 可能使用的索引 | +| key | 实际使用的索引 | +| key_len | 使用的索引长度 | +| ref | 与索引比较的列或常量 | +| rows | 预估需要读取的行数 | +| filtered | 返回结果的行数占读取行数的百分比 | +| Extra | 额外信息(Using index, Using where, Using temporary, Using filesort等) | + +#### 3. type字段详解 + +1. **system**:表只有一行记录 +2. **const**:通过主键或唯一索引一次就找到 + ```sql + EXPLAIN SELECT * FROM users WHERE id = 1; + ``` +3. **eq_ref**:联表查询时使用主键或唯一索引 + ```sql + EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id; + ``` +4. **ref**:使用非唯一索引查找 + ```sql + EXPLAIN SELECT * FROM users WHERE username = 'john'; + ``` +5. **range**:索引范围扫描 + ```sql + EXPLAIN SELECT * FROM users WHERE id > 10 AND id < 100; + ``` +6. **index**:全索引扫描 +7. **ALL**:全表扫描 + +#### 4. Extra字段常见值 + +1. **Using index**:使用覆盖索引 +2. **Using where**:服务器在存储引擎检索行后再过滤 +3. **Using temporary**:使用临时表 +4. **Using filesort**:需要额外排序 +5. **Using join buffer**:使用连接缓存 +6. **Impossible WHERE**:WHERE条件永远为false + +#### 5. 执行计划优化案例 + +**案例1:索引失效** +```sql +EXPLAIN SELECT * FROM users WHERE DATE(create_time) = '2023-01-01'; +-- 优化后 +EXPLAIN SELECT * FROM users WHERE create_time BETWEEN '2023-01-01 00:00:00' AND '2023-01-01 23:59:59'; +``` + +**案例2:避免filesort** +```sql +-- 有索引(a,b) +EXPLAIN SELECT * FROM table ORDER BY a, b; -- 不会filesort +EXPLAIN SELECT * FROM table ORDER BY b, a; -- 会filesort +``` + +**案例3:联表优化** +```sql +-- 不好的写法 +EXPLAIN SELECT * FROM a, b WHERE a.id = b.a_id; +-- 优化写法 +EXPLAIN SELECT * FROM a JOIN b ON a.id = b.a_id; +``` + +#### 三、综合优化建议 + +1. **定期分析慢查询**: + ```sql + -- 开启慢查询日志 + SET GLOBAL slow_query_log = 'ON'; + SET GLOBAL long_query_time = 1; + ``` + +2. **使用PROFILE分析**: + ```sql + SET profiling = 1; + SELECT * FROM users WHERE ...; + SHOW PROFILE FOR QUERY 1; + ``` + +3. **监控索引使用情况**: + ```sql + SELECT * FROM sys.schema_unused_indexes; + ``` + +4. **定期优化表**: + ```sql + ANALYZE TABLE users; + OPTIMIZE TABLE users; + ``` + +5. **合理使用索引合并**: + - 当多个单列索引条件用AND连接时,MySQL可能使用Index Merge优化 diff --git a/mq-demo/pom.xml b/mq-demo/pom.xml index 7b891be..58d5ba9 100644 --- a/mq-demo/pom.xml +++ b/mq-demo/pom.xml @@ -13,20 +13,7 @@ 0.0.1-SNAPSHOT mq-demo mq-demo - - - - - - - - - - - - - - + 17 1.3.5 diff --git a/mq-demo/src/main/java/cn/bunny/mq/mqdemo/mq/listener/MessageListenerOrder.java b/mq-demo/src/main/java/cn/bunny/mq/mqdemo/mq/listener/MessageListenerOrder.java index 1fe524f..5b67ae2 100644 --- a/mq-demo/src/main/java/cn/bunny/mq/mqdemo/mq/listener/MessageListenerOrder.java +++ b/mq-demo/src/main/java/cn/bunny/mq/mqdemo/mq/listener/MessageListenerOrder.java @@ -3,26 +3,28 @@ package cn.bunny.mq.mqdemo.mq.listener; import com.rabbitmq.client.Channel; import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Message; -import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.annotation.*; import org.springframework.stereotype.Component; import java.io.IOException; +import static cn.bunny.mq.mqdemo.domain.RabbitMQMessageListenerConstants.*; + @Component @Slf4j public class MessageListenerOrder { - // /* 测试这个,需要注释下main那个 */ - // @RabbitListener(bindings = @QueueBinding( - // exchange = @Exchange(value = EXCHANGE_DIRECT), - // value = @Queue(value = QUEUE_NAME, durable = "true"), - // key = ROUTING_KEY_DIRECT, - // arguments = @Argument(name = "alternate-exchange", value = ALTERNATE_EXCHANGE_BACKUP) - // ) - // ) - // public void processMessage(String dataString, Message message, Channel channel) { - // System.out.println("消费端接受消息:" + dataString); - // } + /* 测试这个,需要注释下main那个 */ + @RabbitListener(bindings = @QueueBinding( + exchange = @Exchange(value = EXCHANGE_DIRECT), + value = @Queue(value = QUEUE_NAME, durable = "true"), + key = ROUTING_KEY_DIRECT, + arguments = @Argument(name = "alternate-exchange", value = ALTERNATE_EXCHANGE_BACKUP) + ) + ) + public void processMessage(String dataString, Message message, Channel channel) { + System.out.println("消费端接受消息:" + dataString); + } // /* 如果测试这个需要注释上面那个 */ // @RabbitListener(queues = {QUEUE_NAME}) diff --git a/vue2-tutorials/import-script/1-模板语法.html b/vue2-tutorials/import-script/1-模板语法.html new file mode 100644 index 0000000..b51dd83 --- /dev/null +++ b/vue2-tutorials/import-script/1-模板语法.html @@ -0,0 +1,34 @@ + + + + + + + + 模板语法 + + + +
+

插值语法

+

你好:{{name}}

+
+ +

指令语法

+ v2.cn.vuejs.org + + + v2.cn.vuejs.org +
+ + + + \ No newline at end of file diff --git a/vue2-tutorials/import-script/2-数据绑定.html b/vue2-tutorials/import-script/2-数据绑定.html new file mode 100644 index 0000000..26c611d --- /dev/null +++ b/vue2-tutorials/import-script/2-数据绑定.html @@ -0,0 +1,64 @@ + + + + + + + + 数据绑定 + + + +
+ 单项数据绑定: + 双向数据绑定: +
+ +
+ 2秒后绑定到这个上 + 单项数据绑定: + 双向数据绑定: +
+ + + + \ No newline at end of file diff --git a/vue2-tutorials/import-script/js/vue@2.7.16.js b/vue2-tutorials/import-script/js/vue@2.7.16.js new file mode 100644 index 0000000..2f071d1 --- /dev/null +++ b/vue2-tutorials/import-script/js/vue@2.7.16.js @@ -0,0 +1,13223 @@ +/*! + * Vue.js v2.7.16 + * (c) 2014-2023 Evan You + * Released under the MIT License. + */ +(function (global, factory) { + typeof exports === "object" && typeof module !== "undefined" + ? (module.exports = factory()) + : typeof define === "function" && define.amd + ? define(factory) + : ((global = + typeof globalThis !== "undefined" ? globalThis : global || self), + (global.Vue = factory())); +})(this, function () { + "use strict"; + + var emptyObject = Object.freeze({}); + var isArray = Array.isArray; + // These helpers produce better VM code in JS engines due to their + // explicitness and function inlining. + function isUndef(v) { + return v === undefined || v === null; + } + function isDef(v) { + return v !== undefined && v !== null; + } + function isTrue(v) { + return v === true; + } + function isFalse(v) { + return v === false; + } + /** + * Check if value is primitive. + */ + function isPrimitive(value) { + return ( + typeof value === "string" || + typeof value === "number" || + // $flow-disable-line + typeof value === "symbol" || + typeof value === "boolean" + ); + } + function isFunction(value) { + return typeof value === "function"; + } + /** + * Quick object check - this is primarily used to tell + * objects from primitive values when we know the value + * is a JSON-compliant type. + */ + function isObject(obj) { + return obj !== null && typeof obj === "object"; + } + /** + * Get the raw type string of a value, e.g., [object Object]. + */ + var _toString = Object.prototype.toString; + function toRawType(value) { + return _toString.call(value).slice(8, -1); + } + /** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ + function isPlainObject(obj) { + return _toString.call(obj) === "[object Object]"; + } + function isRegExp(v) { + return _toString.call(v) === "[object RegExp]"; + } + /** + * Check if val is a valid array index. + */ + function isValidArrayIndex(val) { + var n = parseFloat(String(val)); + return n >= 0 && Math.floor(n) === n && isFinite(val); + } + function isPromise(val) { + return ( + isDef(val) && + typeof val.then === "function" && + typeof val.catch === "function" + ); + } + /** + * Convert a value to a string that is actually rendered. + */ + function toString(val) { + return val == null + ? "" + : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) + ? JSON.stringify(val, replacer, 2) + : String(val); + } + function replacer(_key, val) { + // avoid circular deps from v3 + if (val && val.__v_isRef) { + return val.value; + } + return val; + } + /** + * Convert an input value to a number for persistence. + * If the conversion fails, return original string. + */ + function toNumber(val) { + var n = parseFloat(val); + return isNaN(n) ? val : n; + } + /** + * Make a map and return a function for checking if a key + * is in that map. + */ + function makeMap(str, expectsLowerCase) { + var map = Object.create(null); + var list = str.split(","); + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + return expectsLowerCase + ? function (val) { + return map[val.toLowerCase()]; + } + : function (val) { + return map[val]; + }; + } + /** + * Check if a tag is a built-in tag. + */ + var isBuiltInTag = makeMap("slot,component", true); + /** + * Check if an attribute is a reserved attribute. + */ + var isReservedAttribute = makeMap("key,ref,slot,slot-scope,is"); + /** + * Remove an item from an array. + */ + function remove$2(arr, item) { + var len = arr.length; + if (len) { + // fast path for the only / last item + if (item === arr[len - 1]) { + arr.length = len - 1; + return; + } + var index = arr.indexOf(item); + if (index > -1) { + return arr.splice(index, 1); + } + } + } + /** + * Check whether an object has the property. + */ + var hasOwnProperty = Object.prototype.hasOwnProperty; + function hasOwn(obj, key) { + return hasOwnProperty.call(obj, key); + } + /** + * Create a cached version of a pure function. + */ + function cached(fn) { + var cache = Object.create(null); + return function cachedFn(str) { + var hit = cache[str]; + return hit || (cache[str] = fn(str)); + }; + } + /** + * Camelize a hyphen-delimited string. + */ + var camelizeRE = /-(\w)/g; + var camelize = cached(function (str) { + return str.replace(camelizeRE, function (_, c) { + return c ? c.toUpperCase() : ""; + }); + }); + /** + * Capitalize a string. + */ + var capitalize = cached(function (str) { + return str.charAt(0).toUpperCase() + str.slice(1); + }); + /** + * Hyphenate a camelCase string. + */ + var hyphenateRE = /\B([A-Z])/g; + var hyphenate = cached(function (str) { + return str.replace(hyphenateRE, "-$1").toLowerCase(); + }); + /** + * Simple bind polyfill for environments that do not support it, + * e.g., PhantomJS 1.x. Technically, we don't need this anymore + * since native bind is now performant enough in most browsers. + * But removing it would mean breaking code that was able to run in + * PhantomJS 1.x, so this must be kept for backward compatibility. + */ + /* istanbul ignore next */ + function polyfillBind(fn, ctx) { + function boundFn(a) { + var l = arguments.length; + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx); + } + boundFn._length = fn.length; + return boundFn; + } + function nativeBind(fn, ctx) { + return fn.bind(ctx); + } + // @ts-expect-error bind cannot be `undefined` + var bind$1 = Function.prototype.bind ? nativeBind : polyfillBind; + /** + * Convert an Array-like object to a real Array. + */ + function toArray(list, start) { + start = start || 0; + var i = list.length - start; + var ret = new Array(i); + while (i--) { + ret[i] = list[i + start]; + } + return ret; + } + /** + * Mix properties into target object. + */ + function extend(to, _from) { + for (var key in _from) { + to[key] = _from[key]; + } + return to; + } + /** + * Merge an Array of Objects into a single Object. + */ + function toObject(arr) { + var res = {}; + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res; + } + /* eslint-disable no-unused-vars */ + /** + * Perform no operation. + * Stubbing args to make Flow happy without leaving useless transpiled code + * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). + */ + function noop(a, b, c) {} + /** + * Always return false. + */ + var no = function (a, b, c) { + return false; + }; + /* eslint-enable no-unused-vars */ + /** + * Return the same value. + */ + var identity = function (_) { + return _; + }; + /** + * Generate a string containing static keys from compiler modules. + */ + function genStaticKeys$1(modules) { + return modules + .reduce(function (keys, m) { + return keys.concat(m.staticKeys || []); + }, []) + .join(","); + } + /** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ + function looseEqual(a, b) { + if (a === b) return true; + var isObjectA = isObject(a); + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + var isArrayA = Array.isArray(a); + var isArrayB = Array.isArray(b); + if (isArrayA && isArrayB) { + return ( + a.length === b.length && + a.every(function (e, i) { + return looseEqual(e, b[i]); + }) + ); + } else if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime(); + } else if (!isArrayA && !isArrayB) { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + return ( + keysA.length === keysB.length && + keysA.every(function (key) { + return looseEqual(a[key], b[key]); + }) + ); + } else { + /* istanbul ignore next */ + return false; + } + } catch (e) { + /* istanbul ignore next */ + return false; + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b); + } else { + return false; + } + } + /** + * Return the first index at which a loosely equal value can be + * found in the array (if value is a plain object, the array must + * contain an object of the same shape), or -1 if it is not present. + */ + function looseIndexOf(arr, val) { + for (var i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) return i; + } + return -1; + } + /** + * Ensure a function is called only once. + */ + function once(fn) { + var called = false; + return function () { + if (!called) { + called = true; + fn.apply(this, arguments); + } + }; + } + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#polyfill + function hasChanged(x, y) { + if (x === y) { + return x === 0 && 1 / x !== 1 / y; + } else { + return x === x || y === y; + } + } + + var SSR_ATTR = "data-server-rendered"; + var ASSET_TYPES = ["component", "directive", "filter"]; + var LIFECYCLE_HOOKS = [ + "beforeCreate", + "created", + "beforeMount", + "mounted", + "beforeUpdate", + "updated", + "beforeDestroy", + "destroyed", + "activated", + "deactivated", + "errorCaptured", + "serverPrefetch", + "renderTracked", + "renderTriggered", + ]; + + var config = { + /** + * Option merge strategies (used in core/util/options) + */ + // $flow-disable-line + optionMergeStrategies: Object.create(null), + /** + * Whether to suppress warnings. + */ + silent: false, + /** + * Show production mode tip message on boot? + */ + productionTip: true, + /** + * Whether to enable devtools + */ + devtools: true, + /** + * Whether to record perf + */ + performance: false, + /** + * Error handler for watcher errors + */ + errorHandler: null, + /** + * Warn handler for watcher warns + */ + warnHandler: null, + /** + * Ignore certain custom elements + */ + ignoredElements: [], + /** + * Custom user key aliases for v-on + */ + // $flow-disable-line + keyCodes: Object.create(null), + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + /** + * Check if an attribute is reserved so that it cannot be used as a component + * prop. This is platform-dependent and may be overwritten. + */ + isReservedAttr: no, + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + /** + * Perform updates asynchronously. Intended to be used by Vue Test Utils + * This will significantly reduce performance if set to false. + */ + async: true, + /** + * Exposed for legacy reasons + */ + _lifecycleHooks: LIFECYCLE_HOOKS, + }; + + /** + * unicode letters used for parsing html tags, component names and property paths. + * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname + * skipping \u10000-\uEFFFF due to it freezing up PhantomJS + */ + var unicodeRegExp = + /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; + /** + * Check if a string starts with $ or _ + */ + function isReserved(str) { + var c = (str + "").charCodeAt(0); + return c === 0x24 || c === 0x5f; + } + /** + * Define a property. + */ + function def(obj, key, val, enumerable) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true, + }); + } + /** + * Parse simple path. + */ + var bailRE = new RegExp("[^".concat(unicodeRegExp.source, ".$_\\d]")); + function parsePath(path) { + if (bailRE.test(path)) { + return; + } + var segments = path.split("."); + return function (obj) { + for (var i = 0; i < segments.length; i++) { + if (!obj) return; + obj = obj[segments[i]]; + } + return obj; + }; + } + + // can we use __proto__? + var hasProto = "__proto__" in {}; + // Browser environment sniffing + var inBrowser = typeof window !== "undefined"; + var UA = inBrowser && window.navigator.userAgent.toLowerCase(); + var isIE = UA && /msie|trident/.test(UA); + var isIE9 = UA && UA.indexOf("msie 9.0") > 0; + var isEdge = UA && UA.indexOf("edge/") > 0; + UA && UA.indexOf("android") > 0; + var isIOS = UA && /iphone|ipad|ipod|ios/.test(UA); + UA && /chrome\/\d+/.test(UA) && !isEdge; + UA && /phantomjs/.test(UA); + var isFF = UA && UA.match(/firefox\/(\d+)/); + // Firefox has a "watch" function on Object.prototype... + // @ts-expect-error firebox support + var nativeWatch = {}.watch; + var supportsPassive = false; + if (inBrowser) { + try { + var opts = {}; + Object.defineProperty(opts, "passive", { + get: function () { + /* istanbul ignore next */ + supportsPassive = true; + }, + }); // https://github.com/facebook/flow/issues/285 + window.addEventListener("test-passive", null, opts); + } catch (e) {} + } + // this needs to be lazy-evaled because vue may be required before + // vue-server-renderer can set VUE_ENV + var _isServer; + var isServerRendering = function () { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && typeof global !== "undefined") { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = + global["process"] && global["process"].env.VUE_ENV === "server"; + } else { + _isServer = false; + } + } + return _isServer; + }; + // detect devtools + var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + /* istanbul ignore next */ + function isNative(Ctor) { + return typeof Ctor === "function" && /native code/.test(Ctor.toString()); + } + var hasSymbol = + typeof Symbol !== "undefined" && + isNative(Symbol) && + typeof Reflect !== "undefined" && + isNative(Reflect.ownKeys); + var _Set; // $flow-disable-line + /* istanbul ignore if */ if (typeof Set !== "undefined" && isNative(Set)) { + // use native Set when available. + _Set = Set; + } else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = /** @class */ (function () { + function Set() { + this.set = Object.create(null); + } + Set.prototype.has = function (key) { + return this.set[key] === true; + }; + Set.prototype.add = function (key) { + this.set[key] = true; + }; + Set.prototype.clear = function () { + this.set = Object.create(null); + }; + return Set; + })(); + } + + var currentInstance = null; + /** + * This is exposed for compatibility with v3 (e.g. some functions in VueUse + * relies on it). Do not use this internally, just use `currentInstance`. + * + * @internal this function needs manual type declaration because it relies + * on previously manually authored types from Vue 2 + */ + function getCurrentInstance() { + return currentInstance && { proxy: currentInstance }; + } + /** + * @internal + */ + function setCurrentInstance(vm) { + if (vm === void 0) { + vm = null; + } + if (!vm) currentInstance && currentInstance._scope.off(); + currentInstance = vm; + vm && vm._scope.on(); + } + + /** + * @internal + */ + var VNode = /** @class */ (function () { + function VNode( + tag, + data, + children, + text, + elm, + context, + componentOptions, + asyncFactory + ) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = undefined; + this.context = context; + this.fnContext = undefined; + this.fnOptions = undefined; + this.fnScopeId = undefined; + this.key = data && data.key; + this.componentOptions = componentOptions; + this.componentInstance = undefined; + this.parent = undefined; + this.raw = false; + this.isStatic = false; + this.isRootInsert = true; + this.isComment = false; + this.isCloned = false; + this.isOnce = false; + this.asyncFactory = asyncFactory; + this.asyncMeta = undefined; + this.isAsyncPlaceholder = false; + } + Object.defineProperty(VNode.prototype, "child", { + // DEPRECATED: alias for componentInstance for backwards compat. + /* istanbul ignore next */ + get: function () { + return this.componentInstance; + }, + enumerable: false, + configurable: true, + }); + return VNode; + })(); + var createEmptyVNode = function (text) { + if (text === void 0) { + text = ""; + } + var node = new VNode(); + node.text = text; + node.isComment = true; + return node; + }; + function createTextVNode(val) { + return new VNode(undefined, undefined, undefined, String(val)); + } + // optimized shallow clone + // used for static nodes and slot nodes because they may be reused across + // multiple renders, cloning them avoids errors when DOM manipulations rely + // on their elm reference. + function cloneVNode(vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + // #7975 + // clone children array to avoid mutating original in case of cloning + // a child. + vnode.children && vnode.children.slice(), + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ); + cloned.ns = vnode.ns; + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isComment = vnode.isComment; + cloned.fnContext = vnode.fnContext; + cloned.fnOptions = vnode.fnOptions; + cloned.fnScopeId = vnode.fnScopeId; + cloned.asyncMeta = vnode.asyncMeta; + cloned.isCloned = true; + return cloned; + } + + /* not type checking this file because flow doesn't play well with Proxy */ + var initProxy; + { + var allowedGlobals_1 = makeMap( + "Infinity,undefined,NaN,isFinite,isNaN," + + "parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent," + + "Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt," + + "require" // for Webpack/Browserify + ); + var warnNonPresent_1 = function (target, key) { + warn$2( + 'Property or method "'.concat( + key, + '" is not defined on the instance but ' + ) + + "referenced during render. Make sure that this property is reactive, " + + "either in the data option, or for class-based components, by " + + "initializing the property. " + + "See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.", + target + ); + }; + var warnReservedPrefix_1 = function (target, key) { + warn$2( + 'Property "' + .concat(key, '" must be accessed with "$data.') + .concat(key, '" because ') + + 'properties starting with "$" or "_" are not proxied in the Vue instance to ' + + "prevent conflicts with Vue internals. " + + "See: https://v2.vuejs.org/v2/api/#data", + target + ); + }; + var hasProxy_1 = typeof Proxy !== "undefined" && isNative(Proxy); + if (hasProxy_1) { + var isBuiltInModifier_1 = makeMap( + "stop,prevent,self,ctrl,shift,alt,meta,exact" + ); + config.keyCodes = new Proxy(config.keyCodes, { + set: function (target, key, value) { + if (isBuiltInModifier_1(key)) { + warn$2( + "Avoid overwriting built-in modifier in config.keyCodes: .".concat( + key + ) + ); + return false; + } else { + target[key] = value; + return true; + } + }, + }); + } + var hasHandler_1 = { + has: function (target, key) { + var has = key in target; + var isAllowed = + allowedGlobals_1(key) || + (typeof key === "string" && + key.charAt(0) === "_" && + !(key in target.$data)); + if (!has && !isAllowed) { + if (key in target.$data) warnReservedPrefix_1(target, key); + else warnNonPresent_1(target, key); + } + return has || !isAllowed; + }, + }; + var getHandler_1 = { + get: function (target, key) { + if (typeof key === "string" && !(key in target)) { + if (key in target.$data) warnReservedPrefix_1(target, key); + else warnNonPresent_1(target, key); + } + return target[key]; + }, + }; + initProxy = function initProxy(vm) { + if (hasProxy_1) { + // determine which proxy handler to use + var options = vm.$options; + var handlers = + options.render && options.render._withStripped + ? getHandler_1 + : hasHandler_1; + vm._renderProxy = new Proxy(vm, handlers); + } else { + vm._renderProxy = vm; + } + }; + } + + /****************************************************************************** + Copyright (c) Microsoft Corporation. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + PERFORMANCE OF THIS SOFTWARE. + ***************************************************************************** */ + + var __assign = function () { + __assign = + Object.assign || + function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) + if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); + }; + + typeof SuppressedError === "function" + ? SuppressedError + : function (error, suppressed, message) { + var e = new Error(message); + return ( + (e.name = "SuppressedError"), + (e.error = error), + (e.suppressed = suppressed), + e + ); + }; + + var uid$2 = 0; + var pendingCleanupDeps = []; + var cleanupDeps = function () { + for (var i = 0; i < pendingCleanupDeps.length; i++) { + var dep = pendingCleanupDeps[i]; + dep.subs = dep.subs.filter(function (s) { + return s; + }); + dep._pending = false; + } + pendingCleanupDeps.length = 0; + }; + /** + * A dep is an observable that can have multiple + * directives subscribing to it. + * @internal + */ + var Dep = /** @class */ (function () { + function Dep() { + // pending subs cleanup + this._pending = false; + this.id = uid$2++; + this.subs = []; + } + Dep.prototype.addSub = function (sub) { + this.subs.push(sub); + }; + Dep.prototype.removeSub = function (sub) { + // #12696 deps with massive amount of subscribers are extremely slow to + // clean up in Chromium + // to workaround this, we unset the sub for now, and clear them on + // next scheduler flush. + this.subs[this.subs.indexOf(sub)] = null; + if (!this._pending) { + this._pending = true; + pendingCleanupDeps.push(this); + } + }; + Dep.prototype.depend = function (info) { + if (Dep.target) { + Dep.target.addDep(this); + if (info && Dep.target.onTrack) { + Dep.target.onTrack(__assign({ effect: Dep.target }, info)); + } + } + }; + Dep.prototype.notify = function (info) { + // stabilize the subscriber list first + var subs = this.subs.filter(function (s) { + return s; + }); + if (!config.async) { + // subs aren't sorted in scheduler if not running async + // we need to sort them now to make sure they fire in correct + // order + subs.sort(function (a, b) { + return a.id - b.id; + }); + } + for (var i = 0, l = subs.length; i < l; i++) { + var sub = subs[i]; + if (info) { + sub.onTrigger && sub.onTrigger(__assign({ effect: subs[i] }, info)); + } + sub.update(); + } + }; + return Dep; + })(); + // The current target watcher being evaluated. + // This is globally unique because only one watcher + // can be evaluated at a time. + Dep.target = null; + var targetStack = []; + function pushTarget(target) { + targetStack.push(target); + Dep.target = target; + } + function popTarget() { + targetStack.pop(); + Dep.target = targetStack[targetStack.length - 1]; + } + + /* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + var arrayProto = Array.prototype; + var arrayMethods = Object.create(arrayProto); + var methodsToPatch = [ + "push", + "pop", + "shift", + "unshift", + "splice", + "sort", + "reverse", + ]; + /** + * Intercept mutating methods and emit events + */ + methodsToPatch.forEach(function (method) { + // cache original method + var original = arrayProto[method]; + def(arrayMethods, method, function mutator() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var result = original.apply(this, args); + var ob = this.__ob__; + var inserted; + switch (method) { + case "push": + case "unshift": + inserted = args; + break; + case "splice": + inserted = args.slice(2); + break; + } + if (inserted) ob.observeArray(inserted); + // notify change + { + ob.dep.notify({ + type: "array mutation" /* TriggerOpTypes.ARRAY_MUTATION */, + target: this, + key: method, + }); + } + return result; + }); + }); + + var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + var NO_INITIAL_VALUE = {}; + /** + * In some cases we may want to disable observation inside a component's + * update computation. + */ + var shouldObserve = true; + function toggleObserving(value) { + shouldObserve = value; + } + // ssr mock dep + var mockDep = { + notify: noop, + depend: noop, + addSub: noop, + removeSub: noop, + }; + /** + * Observer class that is attached to each observed + * object. Once attached, the observer converts the target + * object's property keys into getter/setters that + * collect dependencies and dispatch updates. + */ + var Observer = /** @class */ (function () { + function Observer(value, shallow, mock) { + if (shallow === void 0) { + shallow = false; + } + if (mock === void 0) { + mock = false; + } + this.value = value; + this.shallow = shallow; + this.mock = mock; + // this.value = value + this.dep = mock ? mockDep : new Dep(); + this.vmCount = 0; + def(value, "__ob__", this); + if (isArray(value)) { + if (!mock) { + if (hasProto) { + value.__proto__ = arrayMethods; + /* eslint-enable no-proto */ + } else { + for (var i = 0, l = arrayKeys.length; i < l; i++) { + var key = arrayKeys[i]; + def(value, key, arrayMethods[key]); + } + } + } + if (!shallow) { + this.observeArray(value); + } + } else { + /** + * Walk through all properties and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ + var keys = Object.keys(value); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + defineReactive( + value, + key, + NO_INITIAL_VALUE, + undefined, + shallow, + mock + ); + } + } + } + /** + * Observe a list of Array items. + */ + Observer.prototype.observeArray = function (value) { + for (var i = 0, l = value.length; i < l; i++) { + observe(value[i], false, this.mock); + } + }; + return Observer; + })(); + // helpers + /** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ + function observe(value, shallow, ssrMockReactivity) { + if (value && hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) { + return value.__ob__; + } + if ( + shouldObserve && + (ssrMockReactivity || !isServerRendering()) && + (isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value.__v_skip /* ReactiveFlags.SKIP */ && + !isRef(value) && + !(value instanceof VNode) + ) { + return new Observer(value, shallow, ssrMockReactivity); + } + } + /** + * Define a reactive property on an Object. + */ + function defineReactive( + obj, + key, + val, + customSetter, + shallow, + mock, + observeEvenIfShallow + ) { + if (observeEvenIfShallow === void 0) { + observeEvenIfShallow = false; + } + var dep = new Dep(); + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return; + } + // cater for pre-defined getter/setters + var getter = property && property.get; + var setter = property && property.set; + if ( + (!getter || setter) && + (val === NO_INITIAL_VALUE || arguments.length === 2) + ) { + val = obj[key]; + } + var childOb = shallow ? val && val.__ob__ : observe(val, false, mock); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter() { + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + { + dep.depend({ + target: obj, + type: "get" /* TrackOpTypes.GET */, + key: key, + }); + } + if (childOb) { + childOb.dep.depend(); + if (isArray(value)) { + dependArray(value); + } + } + } + return isRef(value) && !shallow ? value.value : value; + }, + set: function reactiveSetter(newVal) { + var value = getter ? getter.call(obj) : val; + if (!hasChanged(value, newVal)) { + return; + } + if (customSetter) { + customSetter(); + } + if (setter) { + setter.call(obj, newVal); + } else if (getter) { + // #7981: for accessor properties without setter + return; + } else if (!shallow && isRef(value) && !isRef(newVal)) { + value.value = newVal; + return; + } else { + val = newVal; + } + childOb = shallow + ? newVal && newVal.__ob__ + : observe(newVal, false, mock); + { + dep.notify({ + type: "set" /* TriggerOpTypes.SET */, + target: obj, + key: key, + newValue: newVal, + oldValue: value, + }); + } + }, + }); + return dep; + } + function set(target, key, val) { + if (isUndef(target) || isPrimitive(target)) { + warn$2( + "Cannot set reactive property on undefined, null, or primitive value: ".concat( + target + ) + ); + } + if (isReadonly(target)) { + warn$2( + 'Set operation on key "'.concat(key, '" failed: target is readonly.') + ); + return; + } + var ob = target.__ob__; + if (isArray(target) && isValidArrayIndex(key)) { + target.length = Math.max(target.length, key); + target.splice(key, 1, val); + // when mocking for SSR, array methods are not hijacked + if (ob && !ob.shallow && ob.mock) { + observe(val, false, true); + } + return val; + } + if (key in target && !(key in Object.prototype)) { + target[key] = val; + return val; + } + if (target._isVue || (ob && ob.vmCount)) { + warn$2( + "Avoid adding reactive properties to a Vue instance or its root $data " + + "at runtime - declare it upfront in the data option." + ); + return val; + } + if (!ob) { + target[key] = val; + return val; + } + defineReactive(ob.value, key, val, undefined, ob.shallow, ob.mock); + { + ob.dep.notify({ + type: "add" /* TriggerOpTypes.ADD */, + target: target, + key: key, + newValue: val, + oldValue: undefined, + }); + } + return val; + } + function del(target, key) { + if (isUndef(target) || isPrimitive(target)) { + warn$2( + "Cannot delete reactive property on undefined, null, or primitive value: ".concat( + target + ) + ); + } + if (isArray(target) && isValidArrayIndex(key)) { + target.splice(key, 1); + return; + } + var ob = target.__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn$2( + "Avoid deleting properties on a Vue instance or its root $data " + + "- just set it to null." + ); + return; + } + if (isReadonly(target)) { + warn$2( + 'Delete operation on key "'.concat(key, '" failed: target is readonly.') + ); + return; + } + if (!hasOwn(target, key)) { + return; + } + delete target[key]; + if (!ob) { + return; + } + { + ob.dep.notify({ + type: "delete" /* TriggerOpTypes.DELETE */, + target: target, + key: key, + }); + } + } + /** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ + function dependArray(value) { + for (var e = void 0, i = 0, l = value.length; i < l; i++) { + e = value[i]; + if (e && e.__ob__) { + e.__ob__.dep.depend(); + } + if (isArray(e)) { + dependArray(e); + } + } + } + + function reactive(target) { + makeReactive(target, false); + return target; + } + /** + * Return a shallowly-reactive copy of the original object, where only the root + * level properties are reactive. It also does not auto-unwrap refs (even at the + * root level). + */ + function shallowReactive(target) { + makeReactive(target, true); + def(target, "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */, true); + return target; + } + function makeReactive(target, shallow) { + // if trying to observe a readonly proxy, return the readonly version. + if (!isReadonly(target)) { + { + if (isArray(target)) { + warn$2( + "Avoid using Array as root value for " + .concat( + shallow ? "shallowReactive()" : "reactive()", + " as it cannot be tracked in watch() or watchEffect(). Use " + ) + .concat( + shallow ? "shallowRef()" : "ref()", + " instead. This is a Vue-2-only limitation." + ) + ); + } + var existingOb = target && target.__ob__; + if (existingOb && existingOb.shallow !== shallow) { + warn$2( + "Target is already a " + .concat( + existingOb.shallow ? "" : "non-", + "shallow reactive object, and cannot be converted to " + ) + .concat(shallow ? "" : "non-", "shallow.") + ); + } + } + var ob = observe( + target, + shallow, + isServerRendering() /* ssr mock reactivity */ + ); + if (!ob) { + if (target == null || isPrimitive(target)) { + warn$2("value cannot be made reactive: ".concat(String(target))); + } + if (isCollectionType(target)) { + warn$2( + "Vue 2 does not support reactive collection types such as Map or Set." + ); + } + } + } + } + function isReactive(value) { + if (isReadonly(value)) { + return isReactive(value["__v_raw" /* ReactiveFlags.RAW */]); + } + return !!(value && value.__ob__); + } + function isShallow(value) { + return !!(value && value.__v_isShallow); + } + function isReadonly(value) { + return !!(value && value.__v_isReadonly); + } + function isProxy(value) { + return isReactive(value) || isReadonly(value); + } + function toRaw(observed) { + var raw = observed && observed["__v_raw" /* ReactiveFlags.RAW */]; + return raw ? toRaw(raw) : observed; + } + function markRaw(value) { + // non-extensible objects won't be observed anyway + if (Object.isExtensible(value)) { + def(value, "__v_skip" /* ReactiveFlags.SKIP */, true); + } + return value; + } + /** + * @internal + */ + function isCollectionType(value) { + var type = toRawType(value); + return ( + type === "Map" || + type === "WeakMap" || + type === "Set" || + type === "WeakSet" + ); + } + + /** + * @internal + */ + var RefFlag = "__v_isRef"; + function isRef(r) { + return !!(r && r.__v_isRef === true); + } + function ref$1(value) { + return createRef(value, false); + } + function shallowRef(value) { + return createRef(value, true); + } + function createRef(rawValue, shallow) { + if (isRef(rawValue)) { + return rawValue; + } + var ref = {}; + def(ref, RefFlag, true); + def(ref, "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */, shallow); + def( + ref, + "dep", + defineReactive(ref, "value", rawValue, null, shallow, isServerRendering()) + ); + return ref; + } + function triggerRef(ref) { + if (!ref.dep) { + warn$2("received object is not a triggerable ref."); + } + { + ref.dep && + ref.dep.notify({ + type: "set" /* TriggerOpTypes.SET */, + target: ref, + key: "value", + }); + } + } + function unref(ref) { + return isRef(ref) ? ref.value : ref; + } + function proxyRefs(objectWithRefs) { + if (isReactive(objectWithRefs)) { + return objectWithRefs; + } + var proxy = {}; + var keys = Object.keys(objectWithRefs); + for (var i = 0; i < keys.length; i++) { + proxyWithRefUnwrap(proxy, objectWithRefs, keys[i]); + } + return proxy; + } + function proxyWithRefUnwrap(target, source, key) { + Object.defineProperty(target, key, { + enumerable: true, + configurable: true, + get: function () { + var val = source[key]; + if (isRef(val)) { + return val.value; + } else { + var ob = val && val.__ob__; + if (ob) ob.dep.depend(); + return val; + } + }, + set: function (value) { + var oldValue = source[key]; + if (isRef(oldValue) && !isRef(value)) { + oldValue.value = value; + } else { + source[key] = value; + } + }, + }); + } + function customRef(factory) { + var dep = new Dep(); + var _a = factory( + function () { + { + dep.depend({ + target: ref, + type: "get" /* TrackOpTypes.GET */, + key: "value", + }); + } + }, + function () { + { + dep.notify({ + target: ref, + type: "set" /* TriggerOpTypes.SET */, + key: "value", + }); + } + } + ), + get = _a.get, + set = _a.set; + var ref = { + get value() { + return get(); + }, + set value(newVal) { + set(newVal); + }, + }; + def(ref, RefFlag, true); + return ref; + } + function toRefs(object) { + if (!isReactive(object)) { + warn$2("toRefs() expects a reactive object but received a plain one."); + } + var ret = isArray(object) ? new Array(object.length) : {}; + for (var key in object) { + ret[key] = toRef(object, key); + } + return ret; + } + function toRef(object, key, defaultValue) { + var val = object[key]; + if (isRef(val)) { + return val; + } + var ref = { + get value() { + var val = object[key]; + return val === undefined ? defaultValue : val; + }, + set value(newVal) { + object[key] = newVal; + }, + }; + def(ref, RefFlag, true); + return ref; + } + + var rawToReadonlyFlag = "__v_rawToReadonly"; + var rawToShallowReadonlyFlag = "__v_rawToShallowReadonly"; + function readonly(target) { + return createReadonly(target, false); + } + function createReadonly(target, shallow) { + if (!isPlainObject(target)) { + { + if (isArray(target)) { + warn$2("Vue 2 does not support readonly arrays."); + } else if (isCollectionType(target)) { + warn$2( + "Vue 2 does not support readonly collection types such as Map or Set." + ); + } else { + warn$2("value cannot be made readonly: ".concat(typeof target)); + } + } + return target; + } + if (!Object.isExtensible(target)) { + warn$2( + "Vue 2 does not support creating readonly proxy for non-extensible object." + ); + } + // already a readonly object + if (isReadonly(target)) { + return target; + } + // already has a readonly proxy + var existingFlag = shallow ? rawToShallowReadonlyFlag : rawToReadonlyFlag; + var existingProxy = target[existingFlag]; + if (existingProxy) { + return existingProxy; + } + var proxy = Object.create(Object.getPrototypeOf(target)); + def(target, existingFlag, proxy); + def(proxy, "__v_isReadonly" /* ReactiveFlags.IS_READONLY */, true); + def(proxy, "__v_raw" /* ReactiveFlags.RAW */, target); + if (isRef(target)) { + def(proxy, RefFlag, true); + } + if (shallow || isShallow(target)) { + def(proxy, "__v_isShallow" /* ReactiveFlags.IS_SHALLOW */, true); + } + var keys = Object.keys(target); + for (var i = 0; i < keys.length; i++) { + defineReadonlyProperty(proxy, target, keys[i], shallow); + } + return proxy; + } + function defineReadonlyProperty(proxy, target, key, shallow) { + Object.defineProperty(proxy, key, { + enumerable: true, + configurable: true, + get: function () { + var val = target[key]; + return shallow || !isPlainObject(val) ? val : readonly(val); + }, + set: function () { + warn$2( + 'Set operation on key "'.concat(key, '" failed: target is readonly.') + ); + }, + }); + } + /** + * Returns a reactive-copy of the original object, where only the root level + * properties are readonly, and does NOT unwrap refs nor recursively convert + * returned properties. + * This is used for creating the props proxy object for stateful components. + */ + function shallowReadonly(target) { + return createReadonly(target, true); + } + + function computed(getterOrOptions, debugOptions) { + var getter; + var setter; + var onlyGetter = isFunction(getterOrOptions); + if (onlyGetter) { + getter = getterOrOptions; + setter = function () { + warn$2("Write operation failed: computed value is readonly"); + }; + } else { + getter = getterOrOptions.get; + setter = getterOrOptions.set; + } + var watcher = isServerRendering() + ? null + : new Watcher(currentInstance, getter, noop, { lazy: true }); + if (watcher && debugOptions) { + watcher.onTrack = debugOptions.onTrack; + watcher.onTrigger = debugOptions.onTrigger; + } + var ref = { + // some libs rely on the presence effect for checking computed refs + // from normal refs, but the implementation doesn't matter + effect: watcher, + get value() { + if (watcher) { + if (watcher.dirty) { + watcher.evaluate(); + } + if (Dep.target) { + if (Dep.target.onTrack) { + Dep.target.onTrack({ + effect: Dep.target, + target: ref, + type: "get" /* TrackOpTypes.GET */, + key: "value", + }); + } + watcher.depend(); + } + return watcher.value; + } else { + return getter(); + } + }, + set value(newVal) { + setter(newVal); + }, + }; + def(ref, RefFlag, true); + def(ref, "__v_isReadonly" /* ReactiveFlags.IS_READONLY */, onlyGetter); + return ref; + } + + var mark; + var measure; + { + var perf_1 = inBrowser && window.performance; + /* istanbul ignore if */ + if ( + perf_1 && + // @ts-ignore + perf_1.mark && + // @ts-ignore + perf_1.measure && + // @ts-ignore + perf_1.clearMarks && + // @ts-ignore + perf_1.clearMeasures + ) { + mark = function (tag) { + return perf_1.mark(tag); + }; + measure = function (name, startTag, endTag) { + perf_1.measure(name, startTag, endTag); + perf_1.clearMarks(startTag); + perf_1.clearMarks(endTag); + // perf.clearMeasures(name) + }; + } + } + + var normalizeEvent = cached(function (name) { + var passive = name.charAt(0) === "&"; + name = passive ? name.slice(1) : name; + var once = name.charAt(0) === "~"; // Prefixed last, checked first + name = once ? name.slice(1) : name; + var capture = name.charAt(0) === "!"; + name = capture ? name.slice(1) : name; + return { + name: name, + once: once, + capture: capture, + passive: passive, + }; + }); + function createFnInvoker(fns, vm) { + function invoker() { + var fns = invoker.fns; + if (isArray(fns)) { + var cloned = fns.slice(); + for (var i = 0; i < cloned.length; i++) { + invokeWithErrorHandling( + cloned[i], + null, + arguments, + vm, + "v-on handler" + ); + } + } else { + // return handler return value for single handlers + return invokeWithErrorHandling( + fns, + null, + arguments, + vm, + "v-on handler" + ); + } + } + invoker.fns = fns; + return invoker; + } + function updateListeners(on, oldOn, add, remove, createOnceHandler, vm) { + var name, cur, old, event; + for (name in on) { + cur = on[name]; + old = oldOn[name]; + event = normalizeEvent(name); + if (isUndef(cur)) { + warn$2( + 'Invalid handler for event "'.concat(event.name, '": got ') + + String(cur), + vm + ); + } else if (isUndef(old)) { + if (isUndef(cur.fns)) { + cur = on[name] = createFnInvoker(cur, vm); + } + if (isTrue(event.once)) { + cur = on[name] = createOnceHandler(event.name, cur, event.capture); + } + add(event.name, cur, event.capture, event.passive, event.params); + } else if (cur !== old) { + old.fns = cur; + on[name] = old; + } + } + for (name in oldOn) { + if (isUndef(on[name])) { + event = normalizeEvent(name); + remove(event.name, oldOn[name], event.capture); + } + } + } + + function mergeVNodeHook(def, hookKey, hook) { + if (def instanceof VNode) { + def = def.data.hook || (def.data.hook = {}); + } + var invoker; + var oldHook = def[hookKey]; + function wrappedHook() { + hook.apply(this, arguments); + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove$2(invoker.fns, wrappedHook); + } + if (isUndef(oldHook)) { + // no existing hook + invoker = createFnInvoker([wrappedHook]); + } else { + /* istanbul ignore if */ + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // already a merged invoker + invoker = oldHook; + invoker.fns.push(wrappedHook); + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]); + } + } + invoker.merged = true; + def[hookKey] = invoker; + } + + function extractPropsFromVNodeData(data, Ctor, tag) { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + var propOptions = Ctor.options.props; + if (isUndef(propOptions)) { + return; + } + var res = {}; + var attrs = data.attrs, + props = data.props; + if (isDef(attrs) || isDef(props)) { + for (var key in propOptions) { + var altKey = hyphenate(key); + { + var keyInLowerCase = key.toLowerCase(); + if ( + key !== keyInLowerCase && + attrs && + hasOwn(attrs, keyInLowerCase) + ) { + tip( + 'Prop "'.concat(keyInLowerCase, '" is passed to component ') + + "".concat( + formatComponentName( + // @ts-expect-error tag is string + tag || Ctor + ), + ", but the declared prop name is" + ) + + ' "'.concat(key, '". ') + + "Note that HTML attributes are case-insensitive and camelCased " + + "props need to use their kebab-case equivalents when using in-DOM " + + 'templates. You should probably use "' + .concat(altKey, '" instead of "') + .concat(key, '".') + ); + } + } + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey, false); + } + } + return res; + } + function checkProp(res, hash, key, altKey, preserve) { + if (isDef(hash)) { + if (hasOwn(hash, key)) { + res[key] = hash[key]; + if (!preserve) { + delete hash[key]; + } + return true; + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey]; + if (!preserve) { + delete hash[altKey]; + } + return true; + } + } + return false; + } + + // The template compiler attempts to minimize the need for normalization by + // statically analyzing the template at compile time. + // + // For plain HTML markup, normalization can be completely skipped because the + // generated render function is guaranteed to return Array. There are + // two cases where extra normalization is needed: + // 1. When the children contains components - because a functional component + // may return an Array instead of a single root. In this case, just a simple + // normalization is needed - if any child is an Array, we flatten the whole + // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep + // because functional components already normalize their own children. + function simpleNormalizeChildren(children) { + for (var i = 0; i < children.length; i++) { + if (isArray(children[i])) { + return Array.prototype.concat.apply([], children); + } + } + return children; + } + // 2. When the children contains constructs that always generated nested Arrays, + // e.g.