第一次提交

This commit is contained in:
liujing33 2025-05-07 17:52:05 +08:00
parent 06f0b1923a
commit 84191ee079
52 changed files with 11769 additions and 17 deletions

21
.gitignore vendored
View File

@ -1,18 +1,5 @@
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
# Other files and folders
.settings/
# Executables
*.swf
*.air
*.ipa
*.apk
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.
node_modules/
src/.vuepress/.cache/
src/.vuepress/.temp/
src/.vuepress/dist/

6696
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "mangmangblog",
"description": "我的个人博客",
"version": "2.0.0",
"license": "MIT",
"type": "module",
"scripts": {
"docs:build": "vuepress-vite build src",
"docs:clean-dev": "vuepress-vite dev src --clean-cache",
"docs:dev": "vuepress-vite dev src",
"docs:update-package": "npx vp-update"
},
"devDependencies": {
"@vuepress/bundler-vite": "2.0.0-rc.22",
"@vuepress/plugin-git": "^2.0.0-rc.99",
"sass-embedded": "^1.87.0",
"vue": "^3.5.13",
"vuepress": "2.0.0-rc.22",
"vuepress-theme-hope": "2.0.0-rc.85"
}
}

View File

@ -0,0 +1,14 @@
<script setup lang="ts">
import BlogHero from "vuepress-theme-hope/blog/components/BlogHero.js";
import HitokotoBlogHero from "vuepress-theme-hope/presets/HitokotoBlogHero.js";
import BingHeroBackground from "vuepress-theme-hope/presets/BingHeroBackground.js";
</script>
<template>
<BlogHero>
<template #info="info" #bg>
<HitokotoBlogHero v-bind="info" />
<BingHeroBackground />
</template>
</BlogHero>
</template>

26
src/.vuepress/config.ts Normal file
View File

@ -0,0 +1,26 @@
import { defineUserConfig } from "vuepress";
import { getDirname, path } from "vuepress/utils";
const __dirname = getDirname(import.meta.url);
import theme from "./theme.js";
export default defineUserConfig({
base: "/",
lang: "zh-CN",
title: "氓氓博客",
description: "氓氓博客",
theme,
alias: {
"@theme-hope/modules/blog/components/BlogHero": path.resolve(
__dirname,
"./components/BlogHero.vue",
),
},
// 和 PWA 一起启用
// shouldPrefetch: false,
});

61
src/.vuepress/navbar.ts Normal file
View File

@ -0,0 +1,61 @@
import {navbar} from "vuepress-theme-hope";
export default navbar([
"/",
{
text: "demo",
icon: "pen-to-square",
link: "/demo/",
},
{
text: "博文",
icon: "pen-to-square",
prefix: "/posts/",
children: [
{
text: "苹果",
icon: "pen-to-square",
prefix: "apple/",
children: [
{text: "苹果1", icon: "pen-to-square", link: "1"},
{text: "苹果2", icon: "pen-to-square", link: "2"},
"3",
"4",
],
},
{
text: "香蕉",
icon: "pen-to-square",
prefix: "banana/",
children: [
{
text: "香蕉 1",
icon: "pen-to-square",
link: "1",
},
{
text: "香蕉 2",
icon: "pen-to-square",
link: "2",
},
"3",
"4",
],
},
{text: "樱桃", icon: "pen-to-square", link: "cherry"},
{text: "火龙果", icon: "pen-to-square", link: "dragonfruit"},
"tomato",
"strawberry",
],
},
{
text: "V2 文档",
icon: "book",
link: "https://theme-hope.vuejs.press/zh/",
},
{
text: '编程',
icon: "mdi:laptop",
link: '/programming/'
},
]);

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" class="icon" viewBox="0 0 3280.944 2800"><path fill="#41b883" d="M1645.332 601.004h375.675L1081.82 2238.478 142.636 601.004h718.477l220.708 379.704 216.013-379.704z"/><path fill="#41b883" d="M142.636 601.004l939.185 1637.474 939.186-1637.474h-375.675l-563.51 982.484-568.208-982.484z"/><path fill="#35495e" d="M513.188 601.004l568.207 987.23 563.511-987.23h-347.498l-216.013 379.704-220.708-379.704zM1607.792 1311.83l594.678 2.293 187.353-316.325-598.662 2.292zM2198.506 1909.57C2867.436 732.7 2939.502 605.426 2937.874 603.78c-.715-.723 45.303-1.314 102.262-1.314s103.562.428 103.562.951c0 .523-208.57 367.978-463.491 816.567L2216.715 2235.6l-102.1.596-102.102.596z"/><path fill="#41b883" d="M1680.563 2233.328c0-1.34 168.208-298.145 440.375-777.048a4135645.775 4135645.775 0 00337.619-594.19l146.13-257.25 170.746-.04 170.747-.04-5.536 9.741c-3.044 5.358-43.727 77.302-90.407 159.875-85.356 150.992-337.562 595.163-656.602 1156.373l-172 302.559-170.536.588c-93.795.322-170.536.069-170.536-.567z"/><path fill="#35495e" d="M1429.783 1625.351l594.679 2.292 187.353-316.324-598.662 2.292z"/><path fill="#41b883" d="M1524.207 1464.903l608.285 6.877 173.746-320.909h-619.072z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

42
src/.vuepress/sidebar.ts Normal file
View File

@ -0,0 +1,42 @@
import {sidebar} from "vuepress-theme-hope";
export default sidebar({
"/demo/": [
{
text: "如何使用",
icon: "laptop-code",
link: "demo/",
children: "structure",
},
{
text: "幻灯片",
icon: "person-chalkboard",
link: "https://ecosystem.vuejs.press/zh/plugins/markdown/revealjs/demo.html",
},
],
"/posts/": [
{
text: "文章",
icon: "book",
prefix: "posts/",
children: "structure",
},
],
"/programming/": [
{
text: "Java",
collapsible: true,
icon: "mdi:language-java",
prefix: "java/",
children: [
{
text: "Framework",
icon: "simple-icons:framework",
collapsible: true,
prefix: "framework/",
children: "structure",
}
]
}
]
});

View File

@ -0,0 +1,2 @@
// you can change config here
$theme-color: #DC143C;

View File

@ -0,0 +1 @@
// place your custom styles here

View File

@ -0,0 +1 @@
// you can change colors here

238
src/.vuepress/theme.ts Normal file
View File

@ -0,0 +1,238 @@
import {hopeTheme} from "vuepress-theme-hope";
import navbar from "./navbar.js";
import sidebar from "./sidebar.js";
export default hopeTheme(
{
hostname: "http://10.6.212.39:8080",
author: {
name: "LiuMangMang",
email: "362165265@qq.com"
},
license: "CC 4.0",
// 导航栏
navbar,
// 侧边栏
sidebar,
logo: "logo/transparentLogo.png",
logoDark: "logo/transparentLogo.png",
repo: "vuepress-theme-hope/vuepress-theme-hope",
docsDir: "src",
// 页脚
footer: "Powered by VuePress | Theme by Hope",
displayFooter: true,
lastUpdated: false,
// 博客相关
blog: {
description: "一个后端开发者",
intro: "/intro.html"
/* medias: {
Baidu: "https://example.com",
BiliBili: "https://example.com",
Bitbucket: "https://example.com",
Dingding: "https://example.com",
Discord: "https://example.com",
Dribbble: "https://example.com",
Email: "mailto:info@example.com",
Evernote: "https://example.com",
Facebook: "https://example.com",
Flipboard: "https://example.com",
Gitee: "https://example.com",
GitHub: "https://example.com",
Gitlab: "https://example.com",
Gmail: "mailto:info@example.com",
Instagram: "https://example.com",
Lark: "https://example.com",
Lines: "https://example.com",
Linkedin: "https://example.com",
Pinterest: "https://example.com",
Pocket: "https://example.com",
QQ: "https://example.com",
Qzone: "https://example.com",
Reddit: "https://example.com",
Rss: "https://example.com",
Steam: "https://example.com",
Twitter: "https://example.com",
Wechat: "https://example.com",
Weibo: "https://example.com",
Whatsapp: "https://example.com",
Youtube: "https://example.com",
Zhihu: "https://example.com",
VuePressThemeHope: {
icon: "https://theme-hope-assets.vuejs.press/logo.svg",
link: "https://theme-hope.vuejs.press",
},
},*/
},
// 加密配置
encrypt: {
config: {
"/demo/encrypt.html": {
hint: "Password: 1234",
password: "1234",
},
},
},
// 多语言配置
metaLocales: {
editLink: "在 GitHub 上编辑此页",
},
// 如果想要实时查看任何改变,启用它。注: 这对更新性能有很大负面影响
// hotReload: true,
// 此处开启了很多功能用于演示,你应仅保留用到的功能。
markdown: {
align: true,
attrs: true,
codeTabs: true,
component: true,
demo: true,
figure: true,
gfm: true,
imgLazyload: true,
imgSize: true,
include: true,
mark: true,
plantuml: true,
spoiler: true,
stylize: [
{
matcher: "Recommended",
replacer: ({tag}) => {
if (tag === "em")
return {
tag: "Badge",
attrs: {type: "tip"},
content: "Recommended",
};
},
},
],
sub: true,
sup: true,
tabs: true,
tasklist: true,
vPre: true,
// 取消注释它们如果你需要 TeX 支持
// math: {
// // 启用前安装 katex
// type: "katex",
// // 或者安装 mathjax-full
// type: "mathjax",
// },
// 如果你需要幻灯片,安装 @vuepress/plugin-revealjs 并取消下方注释
// revealjs: {
// plugins: ["highlight", "math", "search", "notes", "zoom"],
// },
// 在启用之前安装 chart.js
// chartjs: true,
// insert component easily
// 在启用之前安装 echarts
// echarts: true,
// 在启用之前安装 flowchart.ts
// flowchart: true,
// 在启用之前安装 mermaid
// mermaid: true,
// playground: {
// presets: ["ts", "vue"],
// },
// 在启用之前安装 @vue/repl
// vuePlayground: true,
// 在启用之前安装 sandpack-vue3
// sandpack: true,
},
// 在这里配置主题提供的插件
plugins: {
blog: true,
// 启用之前需安装 @waline/client
// 警告: 这是一个仅供演示的测试服务,在生产环境中请自行部署并使用自己的服务!
// comment: {
// provider: "Waline",
// serverURL: "https://waline-comment.vuejs.press",
// },
components: {
components: ["Badge", "VPCard"],
},
icon: {
prefix: "fa6-solid:",
},
// 如果你需要 PWA。安装 @vuepress/plugin-pwa 并取消下方注释
// pwa: {
// favicon: "/favicon.ico",
// cacheHTML: true,
// cacheImage: true,
// appendBase: true,
// apple: {
// icon: "/assets/icon/apple-icon-152.png",
// statusBarColor: "black",
// },
// msTile: {
// image: "/assets/icon/ms-icon-144.png",
// color: "#ffffff",
// },
// manifest: {
// icons: [
// {
// src: "/assets/icon/chrome-mask-512.png",
// sizes: "512x512",
// purpose: "maskable",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-mask-192.png",
// sizes: "192x192",
// purpose: "maskable",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-512.png",
// sizes: "512x512",
// type: "image/png",
// },
// {
// src: "/assets/icon/chrome-192.png",
// sizes: "192x192",
// type: "image/png",
// },
// ],
// shortcuts: [
// {
// name: "Demo",
// short_name: "Demo",
// url: "/demo/",
// icons: [
// {
// src: "/assets/icon/guide-maskable.png",
// sizes: "192x192",
// purpose: "maskable",
// type: "image/png",
// },
// ],
// },
// ],
// },
// },
},
},
{
custom: true // ✅ 添加这一行
});

47
src/README.md Normal file
View File

@ -0,0 +1,47 @@
---
home: true
layout: BlogHome
hero: true
title: 博客主页
heroText: ''
tagline: ''
#heroImage: logo/transparentLogo.png
#heroImageDark: logo/transparentLogo.png
#heroAlt: 可爱小熊猫
#bgImage: bg/bgImage.jpg
heroFullScreen: true
icon: house
#projects:
# - icon: folder-open
# name: 项目名称
# desc: 项目详细描述
# link: https://你的项目链接
#
# - icon: link
# name: 链接名称
# desc: 链接详细描述
# link: https://链接地址
#
# - icon: book
# name: 书籍名称
# desc: 书籍详细描述
# link: https://你的书籍链接
#
# - icon: newspaper
# name: 文章名称
# desc: 文章详细描述
# link: https://你的文章链接
#
# - icon: user-group
# name: 伙伴名称
# desc: 伙伴详细介绍
# link: https://你的伙伴链接
#
# - icon: https://theme-hope-assets.vuejs.press/logo.svg
# name: 自定义项目
# desc: 自定义详细介绍
# link: https://你的自定义链接
#footer: Powered by VuePress | Theme by Hope
---

9
src/demo/README.md Normal file
View File

@ -0,0 +1,9 @@
---
title: 主要功能与配置演示
index: false
icon: laptop-code
category:
- 使用指南
---
<Catalog />

42
src/demo/disable.md Normal file
View File

@ -0,0 +1,42 @@
---
title: 布局与功能禁用
icon: gears
order: 4
category:
- 使用指南
tag:
- 禁用
navbar: false
sidebar: false
breadcrumb: false
pageInfo: false
contributors: false
editLink: false
lastUpdated: false
prev: false
next: false
comment: false
footer: false
backtotop: false
---
你可以通过设置页面的 Frontmatter在页面禁用功能与布局。
<!-- more -->
本页面就是一个示例,禁用了如下功能:
- 导航栏
- 侧边栏
- 路径导航
- 页面信息
- 贡献者
- 编辑此页链接
- 更新时间
- 上一篇/下一篇 链接
- 评论
- 页脚
- 返回顶部按钮

15
src/demo/encrypt.md Normal file
View File

@ -0,0 +1,15 @@
---
icon: lock
category:
- 使用指南
tag:
- 加密
---
# 密码加密的文章
实际的文章内容。
段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字段落 1 文字。
段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字段落 2 文字。

31
src/demo/layout.md Normal file
View File

@ -0,0 +1,31 @@
---
title: 布局
icon: object-group
order: 2
category:
- 指南
tag:
- 布局
---
布局包括:
- [导航栏](https://theme-hope.vuejs.press/zh/guide/layout/navbar.html)
- [侧边栏](https://theme-hope.vuejs.press/zh/guide/layout/sidebar.html)
- [页脚](https://theme-hope.vuejs.press/zh/guide/layout/footer.html)
同时每个页面包含:
- [路径导航](https://theme-hope.vuejs.press/zh/guide/layout/breadcrumb.html)
- [标题和页面信息](https://theme-hope.vuejs.press/zh/guide/feature/page-info.html)
- [TOC (文章标题列表)](https://theme-hope.vuejs.press/zh/guide/layout/page.html#标题列表)
- [贡献者、更新时间等页面元信息](https://theme-hope.vuejs.press/guide/feature/meta.html)
- [评论](https://theme-hope.vuejs.press/zh/guide/feature/comment.html)
主题也带有以下元素:
- [夜间模式按钮](https://theme-hope.vuejs.press/zh/guide/interface/darkmode.html)
- [返回顶部按钮](https://theme-hope.vuejs.press/guide/interface/others.html#返回顶部按钮)
- [打印按钮](https://theme-hope.vuejs.press/guide/interface/others.html#打印按钮)
你可以在主题选项和页面的 frontmatter 中自定义它们。

323
src/demo/markdown.md Normal file
View File

@ -0,0 +1,323 @@
---
title: Markdown 展示
icon: fa6-brands:markdown
order: 2
category:
- 使用指南
tag:
- Markdown
---
VuePress 主要从 Markdown 文件生成页面。因此,你可以使用它轻松生成文档或博客站点。
你需要创建并编写 Markdown以便 VuePress 可以根据文件结构将它们转换为不同的页面。
<!-- more -->
## Markdown 介绍
如果你是一个新手,还不会编写 Markdown请先阅读 [Markdown 介绍](https://theme-hope.vuejs.press/zh/cookbook/markdown/) 和 [Markdown 演示](https://theme-hope.vuejs.press/zh/cookbook/markdown/demo.html)。
## Markdown 配置
VuePress 通过 Frontmatter 为每个 Markdown 页面引入配置。
::: important Frontmatter
Frontmatter 是 VuePress 中很重要的一个概念,请阅读 [Frontmatter 介绍](https://theme-hope.vuejs.press/zh/cookbook/vuepress/page.html#front-matter) 了解详情。
:::
## Markdown 扩展
VuePress 会使用 [markdown-it](https://github.com/markdown-it/markdown-it) 来解析 Markdown 内容,因此可以借助于 markdown-it 插件来实现 [语法扩展](https://github.com/markdown-it/markdown-it#syntax-extensions) 。
### VuePress 扩展
为了丰富文档写作VuePress 对 Markdown 语法进行了扩展。
关于这些扩展,请阅读 [VuePress 中的 Markdown 扩展](https://theme-hope.vuejs.press/zh/cookbook/vuepress/markdown.html)。
### 主题扩展
通过 VuePress 插件,主题扩展了更多 Markdown 语法,提供更加丰富的写作功能。
#### 选项卡
::: tabs#fruit
@tab apple
Apple
@tab banana
Banana
@tab orange
Orange
:::
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/content/tabs.html)
#### 脚注
此文字有脚注[^first].
[^first]: 这是脚注内容
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/content/footnote.html)
#### 导入文件
<!-- @include: ./README.md{11-17} -->
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/content/include.html)
#### TeX 语法
$$
\frac {\partial^r} {\partial \omega^r} \left(\frac {y^{\omega}} {\omega}\right)
= \left(\frac {y^{\omega}} {\omega}\right) \left\{(\log y)^r + \sum_{i=1}^r \frac {(-1)^i r \cdots (r-i+1) (\log y)^{r-i}} {\omega^i} \right\}
$$
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/grammar/math.html)
#### 任务列表
- [x] 计划 1
- [ ] 计划 2
[查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/grammar/tasklist.html)
### 图片增强
支持为图片设置颜色模式和大小。
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/grammar/image.html)
#### 上下角标
19^th^ H~2~O
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/stylize/sup-sub.html)
#### 组件
```component VPCard
title: Mr.Hope
desc: Where there is light, there is hope
logo: https://mister-hope.com/logo.svg
link: https://mister-hope.com
background: rgba(253, 230, 138, 0.15)
```
- [查看详情](https://theme-hope.vuejs.press/zh/guide/component/grammar.html)
#### 提示容器
::: v-pre
安全的在 Markdown 中使用 {{ variable }}。
:::
::: info 自定义标题
信息容器,包含 `代码` 与 [链接](#提示容器)。
```js
const a = 1;
```
:::
::: tip 自定义标题
提示容器
:::
::: warning 自定义标题
警告容器
:::
::: caution 自定义标题
危险容器
:::
::: details 自定义标题
详情容器
:::
- [GitHub 警示](https://theme-hope.vuejs.press/zh/guide/markdown/stylize/alert.html)
- [提示框](https://theme-hope.vuejs.press/zh/guide/markdown/stylize/hint.html)
#### 自定义对齐
::: center
我是居中的
:::
::: right
我在右对齐
:::
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/stylize/align.html)
#### 属性支持
一个拥有 ID 的 **单词**{#word}。
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/stylize/attrs.html)
#### 标记
你可以标记 ==重要的内容== 。
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/stylize/mark.html)
#### 剧透
VuePress Theme Hope !!十分强大!!.
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/stylize/spoiler.html)
#### 样式化
向 Mr.Hope 捐赠一杯咖啡。 _Recommended_
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/stylize/stylize.html)
#### 图表
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/chartjs.html" width="100%" height="450"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/chart/chartjs.html)
#### ECharts
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/echarts.html" width="100%" height="800"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/chart/echarts.html)
#### 流程图
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/flowchart.html" width="100%" height="450"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/chart/flowchart.html)
#### MarkMap
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/markmap.html" width="100%" height="380"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/chart/markmap.html)
#### Mermaid
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/mermaid.html" width="100%" height="620"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/chart/mermaid.html)
#### PlantUML
@startuml
Alice -> Bob: 认证请求
alt 成功情况
Bob -> Alice: 认证接受
else 某种失败情况
Bob -> Alice: 认证失败
group 我自己的标签
Alice -> Log : 开始记录攻击日志
loop 1000次
Alice -> Bob: DNS 攻击
end
Alice -> Log : 结束记录攻击日志
end
else 另一种失败
Bob -> Alice: 请重复
end
@enduml
- [View Detail](https://theme-hope.vuejs.press/zh/guide/markdown/chart/plantuml.html)
#### 代码块
::: code-tabs
@tab pnpm
```bash
pnpm add -D vuepress-theme-hope
```
@tab yarn
```bash
yarn add -D vuepress-theme-hope
```
@tab:active npm
```bash
npm i -D vuepress-theme-hope
```
:::
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/code/code-tabs.html)
#### 代码演示
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/code-demo.html" width="100%" height="450"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/code/demo.html)
#### 交互演示
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/playground.html" width="100%" height="480"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/code/playground.html)
#### Kotlin 交互演示
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/kotlin-playground.html" width="100%" height="220"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/code/kotlin-playground.html)
#### Sandpack 交互演示
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/sandpack.html" width="100%" height="380"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/code/sandpack.html)
#### Vue 交互演示
<iframe src="https://plugin-md-enhance-demo.vuejs.press/snippet/vue-playground.html" width="100%" height="380"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/code/vue-playground.html)
#### 幻灯片
<iframe src="https://ecosystem.vuejs.press/zh/plugins/markdown/revealjs/demo.html" width="100%" height="420"/>
- [查看详情](https://theme-hope.vuejs.press/zh/guide/markdown/content/revealjs.html)

125
src/demo/page.md Normal file
View File

@ -0,0 +1,125 @@
---
# 这是文章的标题
title: 页面配置
# 你可以自定义封面图片
cover: /assets/images/cover1.jpg
# 这是页面的图标
icon: file
# 这是侧边栏的顺序
order: 3
# 设置作者
author: Ms.Hope
# 设置写作时间
date: 2020-01-01
# 一个页面可以有多个分类
category:
- 使用指南
# 一个页面可以有多个标签
tag:
- 页面配置
- 使用指南
# 此页面会在文章列表置顶
#sticky: true
# 此页面会出现在星标文章中
star: true
# 你可以自定义页脚
footer: 这是测试显示的页脚
# 你可以自定义版权信息
copyright: 无版权
---
`more` 注释之前的内容被视为文章摘要。
<!-- more -->
## 页面标题
The first H1 title in Markdown will be regarded as page title.
Markdown 中的第一个 H1 标题会被视为页面标题。
你可以在 Markdown 的 Frontmatter 中设置页面标题。
```md
---
title: 页面标题
---
```
## 页面信息
你可以在 Markdown 的 Frontmatter 中设置页面信息。
- 作者设置为 Ms.Hope。
- 写作日期为 2020 年 1 月 1 日
- 分类为 “使用指南”
- 标签为 “页面配置” 和 “使用指南”
## 页面内容
你可以自由在这里书写你的 Markdown。
::: tip 图片引入
- 你可以将图片和 Markdown 文件放置在一起使用相对路径进行引用。
- 对于 `.vuepress/public` 文件夹的图片,请使用绝对链接 `/` 进行引用。
:::
## 组件
每个 Markdown 页面都会被转换为一个 Vue 组件,这意味着你可以在 Markdown 中使用 Vue 语法:
{{ 1 + 1 }}
<!-- markdownlint-disable MD033 -->
<ul>
<li v-for="i in 3">{{ i }}</li>
</ul>
<!-- markdownlint-enable MD033 -->
你也可以创建并引入你自己的组件。
<MyComponent />
<script setup>
import { defineComponent, h, ref } from 'vue';
const MyComponent = defineComponent({
setup() {
const input = ref('Hello world!');
const onInput = (e) => {
input.value = e.target.value;
};
return () => [
h('p', [
h('span','输入: '),
h('input', {
value: input.value,
onInput,
}),
]),
h('p', [h('span','输出: '), input.value]),
];
},
});
</script>
---
主题包含一些有用的组件。这里是一些例子:
- 文字结尾应该有深蓝色的 徽章文字 徽章。 <Badge text="徽章文字" color="#242378" />
- 一个卡片:
```component VPCard
title: Mr.Hope
desc: Where there is light, there is hope
logo: https://mister-hope.com/logo.svg
link: https://mister-hope.com
background: rgba(253, 230, 138, 0.15)
```

12
src/intro.md Normal file
View File

@ -0,0 +1,12 @@
---
icon: circle-info
cover: /assets/images/cover3.jpg
date: 2025-05-06
---
# 介绍页
## 自我介绍
> 你好哇哈哈哈哈的武器恶趣味大苏打撒旦eqweqwesadasdwewcasdqweqwfc1231233dsczxcxzc

20
src/posts/apple/1.md Normal file
View File

@ -0,0 +1,20 @@
---
icon: pen-to-square
date: 2022-01-01
category:
- 苹果
tag:
- 红
- 大
- 圆
---
# 苹果 1
## 标题 2
这里是内容。
### 标题 3
这里是内容。

24
src/posts/apple/2.md Normal file
View File

@ -0,0 +1,24 @@
---
icon: pen-to-square
date: 2022-01-02
category:
- 苹果
tag:
- 红
- 大
- 圆
---
# 苹果 2
一个被星标了的苹果文章。
<!-- more -->
## 标题 2
这里是内容。
### 标题 3
这里是内容。

21
src/posts/apple/3.md Normal file
View File

@ -0,0 +1,21 @@
---
icon: pen-to-square
date: 2022-01-03
category:
- 苹果
- 水果
tag:
- 红
- 大
- 圆
---
# 苹果 3
## 标题 2
这里是内容。
### 标题 3
这里是内容。

21
src/posts/apple/4.md Normal file
View File

@ -0,0 +1,21 @@
---
icon: pen-to-square
date: 2022-01-04
category:
- 苹果
- 水果
tag:
- 红
- 大
- 圆
---
# 苹果 4
## 标题 2
这里是内容。
### 标题 3
这里是内容。

21
src/posts/banana/1.md Normal file
View File

@ -0,0 +1,21 @@
---
icon: pen-to-square
date: 2022-01-05
category:
- 香蕉
- 水果
tag:
- 黄
- 弯曲的
- 长
---
# 香蕉 1
## 标题 2
这里是内容。
### 标题 3
这里是内容。

25
src/posts/banana/2.md Normal file
View File

@ -0,0 +1,25 @@
---
icon: pen-to-square
date: 2022-01-06
category:
- 香蕉
- 水果
tag:
- 黄
- 弯曲的
- 长
---
# 香蕉 2
一个被数字 `10` 星标了的香蕉文章。
<!-- more -->
## 标题 2
这里是内容。
### 标题 3
这里是内容。

20
src/posts/banana/3.md Normal file
View File

@ -0,0 +1,20 @@
---
icon: pen-to-square
date: 2022-01-07
category:
- 香蕉
tag:
- 黄
- 弯曲的
- 长
---
# 香蕉 3
## 标题 2
这里是内容。
### 标题 3
这里是内容。

20
src/posts/banana/4.md Normal file
View File

@ -0,0 +1,20 @@
---
icon: pen-to-square
date: 2022-01-08
category:
- 香蕉
tag:
- 黄
- 弯曲的
- 长
---
# 香蕉 4
## 标题 2
这里是内容。
### 标题 3
这里是内容。

20
src/posts/cherry.md Normal file
View File

@ -0,0 +1,20 @@
---
icon: pen-to-square
date: 2022-01-09
category:
- 樱桃
tag:
- 红
- 小
- 圆
---
# 樱桃
## 标题 2
这里是内容。
### 标题 3
这里是内容。

20
src/posts/dragonfruit.md Normal file
View File

@ -0,0 +1,20 @@
---
icon: pen-to-square
date: 2022-01-10
category:
- 火龙果
- 水果
tag:
- 红
- 大
---
# 火龙果
## 标题 2
这里是内容。
### 标题 3
这里是内容。

20
src/posts/strawberry.md Normal file
View File

@ -0,0 +1,20 @@
---
icon: pen-to-square
date: 2022-01-11
category:
- 水果
- 草莓
tag:
- 红
- 小
---
# 草莓
## 标题 2
这里是内容。
### 标题 3
这里是内容。

22
src/posts/tomato.md Normal file
View File

@ -0,0 +1,22 @@
---
cover: /assets/images/cover2.jpg
icon: pen-to-square
date: 2022-01-12
category:
- 蔬菜
tag:
- 红
- 圆
star: true
#sticky: true
---
# 番茄
## 标题 2
这里是内容。
### 标题 3
这里是内容。

View File

@ -0,0 +1,9 @@
---
title: 编程
index: false
icon: laptop-code
category:
- 编程
---
<Catalog />

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,766 @@
---
date: 2025-05-07
category:
- JAVA
tag:
- 表单
- 重复提交
- 防重
# 此页面会在文章列表置顶
sticky: true
---
防止表单和参数重复提交案例
<!-- more -->
# 防止表单和参数重复提交
## 1. 编写注解
```java
/**
* 防止表单重复提交
*
* @author 氓氓编程
* @Date: 2021-06-08-16:35
* @Inherited @interface 自定义注解时自动继承了java.lang.annotation.Annotation接口由编译程序自动完成其他细节
* @Target 用于描述注解的使用范围(作用于方法上)
* @Retention 被描述的注解在什么范围内有效 (在运行时有效,即运行时保留)
*/
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
}
```
## 2. 自定义拦截器
```java
/**
* @author 茫茫编程
* @Date: 2021-06-08-16:29
*/
@Slf4j
@Component
public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter {
/**
* @param request 是指经过spring封装的请求对象, 包含请求地址, 头, 参数, body(流)等信息.
* @param response 是指经过spring封装的响应对象, 包含输入流, 响应body类型等信息.
* @param handler 是指controller的@Controller注解下的"完整"方法名, 是包含exception等字段信息的.
* @return 是否放行
* @throws Exception 异常
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//instanceof是Java中的二元运算符左边是对象右边是类当对象是右边类或子类所创建对象时返回true否则返回false。
if (handler instanceof HandlerMethod) {
log.info("进来了");
//把handler强转为HandlerMethod
HandlerMethod handlerMethod = (HandlerMethod) handler;
//获取当前请求的方法
Method method = handlerMethod.getMethod();
//获取当前去请求方法上是否有注解@RepeatSubmit
RepeatSubmit repeatSubmit = method.getAnnotation(RepeatSubmit.class);
//如果方法有@RepeatSubmit注解进入if
if (repeatSubmit != null) {
//判断是否是重复提交,重复提交进入
if (isRepeatSubmit(request, response)) {
//返回消息实体类
R message = R.error().message("不允许重复提交");
//把消息响应给客户端
ServletUtils.renderString(response, JSONUtil.toJsonStr(message));
//拦截
return false;
}
}
//注解和不重复直接放行
return true;
} else {
//如果handler不是HandlerMethod或子类放行
return super.preHandle(request, response, handler);
}
}
/**
* 弗雷调用该方法会使用子类的实现
* 验证是否重复提交由子类实现具体的防重复提交的规则
*
* @param request 请求
* @return 是否是重复提交
*/
public abstract boolean isRepeatSubmit(HttpServletRequest request,HttpServletResponse response);
}
```
## 3. 自定义拦截器子类
```java
/**
* 判断请求url和数据是否和上一次相同
* 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
*
* @author 氓氓编程
* @Date: 2021-06-08-17:27
*/
@Slf4j
@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
/**
* 重复参数
*/
public final String REPEAT_PARAMS = "repeatParams";
/**
* 重复时间
*/
public final String REPEAT_TIME = "repeatTime";
/**
* 间隔时间单位秒
* 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
*/
private int intervalTime = 120;
public SameUrlDataInterceptor(JwtUtil jwtUtil, RedisCache redisCache) {
this.jwtUtil = jwtUtil;
this.redisCache = redisCache;
}
public void setIntervalTime(int intervalTime) {
this.intervalTime = intervalTime;
}
private final JwtUtil jwtUtil;
/**
* 注入redis
*/
private final RedisCache redisCache;
/**
* 重写父类判断是否重复的抽象方法
*
* @param request 请求
* @return true=重复提交 false=未重复
*/
@SneakyThrows
@SuppressWarnings("unchecked")
@Override
public boolean isRepeatSubmit(HttpServletRequest request, HttpServletResponse response) {
//1.声明当前参数
String nowParams = null;
//判断请求是否为空
if (request != null) {
//把request 转为可重复获取流的RepeatedlyRequestWrapper
RepeatedlyRequestWrapper repeatedlyRequest = new RepeatedlyRequestWrapper(request, response);
//获取body参数
nowParams = RepeatedlyRequestWrapper.getBodyString(repeatedlyRequest);
}
//如果请求体body参数为空获取Parameter的参数
if (StringUtils.isEmpty(nowParams)) {
assert request != null;
nowParams = JSONUtil.toJsonStr(request.getParameterMap());
log.info("body=={}", nowParams);
}
//把数据存储起来
Map<String, Object> nowMap = new HashMap<>(80);
nowMap.put(REPEAT_PARAMS, nowParams);
nowMap.put(REPEAT_TIME, System.currentTimeMillis());
log.info("nowMap=={}", nowMap);
// 请求地址作为存放cache的key值
String url = request.getRequestURI();
//唯一标识-获取请求头的token值
String submitKey = request.getHeader(jwtUtil.getHeader());
//如果token为空,使用请求地址作为key
if (StringUtils.isEmpty(submitKey)) {
submitKey = url;
}
//唯一标识指定key+消息头)
String cacheRepeatKey = "repeat_submit:" + submitKey;
log.info("repeat_submit=={}", cacheRepeatKey);
//缓存中获取上次请求数据
Object cacheObject = redisCache.getCacheObject(cacheRepeatKey);
log.info("cacheObject=={}", cacheObject);
//如果缓存中没有数据,则存放
if (cacheObject == null) {
Map<String, Object> cacheMap = new HashMap<>(109);
cacheMap.put(url, nowMap);
redisCache.setCacheObject(cacheRepeatKey, cacheMap, intervalTime, TimeUnit.SECONDS);
} else {
//强转为map
Map<String, Object> preDataMap = (Map<String, Object>) cacheObject;
//判断该map是否有url作为的键
if (preDataMap.containsKey(url)) {
//根据map中的键url 获取对应的参数
Map<String, Object> preMap = (Map<String, Object>) preDataMap.get(url);
return compareParams(nowMap, preMap) && compareTime(nowMap, preMap);
}
}
return false;
}
/**
* 比较两次请求参数是否相同
*
* @param nowMap 现在的数据
* @param preMap 之前的数据
* @return true=相同 false=不相同
*/
private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
String now = (String) nowMap.get(REPEAT_PARAMS);
String pre = (String) preMap.get(REPEAT_PARAMS);
return now.equals(pre);
}
/**
* 比较两次请求时间间隔
*
* @param nowMap 现在的数据
* @param preMap 之前的数据
* @return true=相同 false=不相同
*/
private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap) {
long now = (Long) nowMap.get(REPEAT_TIME);
long pre = (Long) preMap.get(REPEAT_TIME);
//如果两次间隔时间小于10秒
return (now - pre) < this.intervalTime * 1000L;
}
}
```
## 4. 配置拦截器到web中
```java
/**
* @author 氓氓编程
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
private final RepeatSubmitInterceptor repeatSubmitInterceptor;
//构造方法注入
public CorsConfig(RepeatSubmitInterceptor repeatSubmitInterceptor) {
this.repeatSubmitInterceptor = repeatSubmitInterceptor;
}
/**
* 解决跨域
* @return CorsFilter
*/
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration configuration = new CorsConfiguration();
// 设置访问源地址(允许那些地址访问服务器)
configuration.addAllowedOrigin("*");
// 设置访问源请求方法(方法)
configuration.addAllowedMethod("*");
// 设置访问源请求头(头部信息)
configuration.addAllowedHeader("*");
// 跨域需要暴露的请求头(因为跨域访问默认不能获取全部头部信息)
configuration.addExposedHeader("token");
// 注册配置
source.registerCorsConfiguration("/**", configuration);
return new CorsFilter(source);
}
/**
* 添加拦截器
* @param registry registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(repeatSubmitInterceptor);
}
}
```
## 5. redis
### 5.1 配置
```java
**
* @author 氓氓编程
*
*/
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//key序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
//value序列化
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
```
> 以下是需要用到的工具类
### 5.2 redis 工具类
```java
/**
* Redis 工具类
*
* @author 氓氓编程
* @Date: 2021-06-08-17:35
*/
@Component
public class RedisCache {
private final RedisTemplate<Object, Object> redisTemplate;
public RedisCache(RedisTemplate<Object, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 缓存基本对象Integer、String、实体类等
*
* @param key 键
* @param value 值
*/
public void setCacheObject(final String key, final Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 带有效时间缓存基本对象Integer、String、实体类等
*
* @param key 键
* @param value 值
* @param timeout 有效时间
* @param timeUnit 有效时间单位
*/
public void setCacheObject(final String key, final Object value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 给某个键设置有效时间
*
* @param key 需要设置有效时间的键
* @param timeout 设置的时间 默认是秒
* @return true=设置成功 false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 给某个键设置有效时间
*
* @param key 需要设置有效时间的键
* @param timeout 设置的时间
* @param timeUnit 有效时间单位
* @return true=设置成功 false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit timeUnit) {
Boolean isSuccess = redisTemplate.expire(key, timeout, timeUnit);
if (isSuccess != null) {
return isSuccess;
}
return false;
}
/**
* 根据键获取某个缓存的值
*
* @param key 键
* @return Object
*/
public Object getCacheObject(final String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 根据建删除缓存
*
* @param key 键
* @return true=成功 false=失败
*/
public boolean deleteObject(final String key) {
Boolean isDelete = redisTemplate.delete(key);
return isDelete != null && isDelete;
}
/**
* 根据键批量删除
*
* @param collection 装有键的集合
* @return 删除成功的数量
*/
public long deleteObject(final Collection<Object> collection) {
Long count = redisTemplate.delete(collection);
return count == null ? 0 : count;
}
/**
* 缓存List集合数据
*
* @param key 键
* @param dataList 待缓存的List数据
* @return 缓存成功的数量
*/
public long setCacheList(final String key, final List<Object> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获取List缓存数据
*
* @param key 键
* @return 缓存键值对应的数据
*/
public List<Object> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 键
* @param dataSet 待缓存的Set数据
* @return 缓存数据的对象
*/
public BoundSetOperations<Object, Object> setCacheSet(final String key, final Set<Object> dataSet) {
BoundSetOperations<Object, Object> setOperations = redisTemplate.boundSetOps(key);
Iterator<Object> it = dataSet.iterator();
if (it.hasNext()) {
setOperations.add(it.next());
}
return setOperations;
}
/**
* 获取Set缓存数据
*
* @param key 键
* @return 缓存键值对应的数据
*/
public Set<Object> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* @param key 键
* @param dataMap 缓存的map数据
*/
public void setCacheMap(final String key, final Map<String, Object> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key 键
* @return 获得缓存的数据
*/
public Map<Object, Object> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public void setCacheMapValue(final String key, final String hKey, final Object value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public Object getCacheMapValue(final String key, final String hKey) {
return redisTemplate.opsForHash().get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public List<Object> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<Object> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
```
## 6. Servlet参数发送的工具类
```java
/**
* 客户端工具类
*
* @author 氓氓编程
* @Date: 2021-06-08-16:50
*/
public class ServletUtils {
/**
* 获取String参数
*/
public static String getParameter(String name) {
return getRequest().getParameter(name);
}
/**
* 获取String参数,如墨没有设置一个默认值
*/
public static String getParameter(String name, String defaultValue) {
return Convert.toStr(getRequest().getParameter(name), defaultValue);
}
/**
* 获取session
*/
public static HttpSession getSession() {
return getRequest().getSession();
}
/**
* 获取Integer参数
*/
public static Integer getParameterToInt(String name) {
return Convert.toInt(getRequest().getParameter(name));
}
/**
* 获取response
*/
public static HttpServletResponse getResponse() {
return getRequestAttributes().getResponse();
}
/**
* 获取request
*/
public static HttpServletRequest getRequest() {
return getRequestAttributes().getRequest();
}
/**
* 获取到当前的HttpServletRequest
*
* @return ServletRequestAttributes
*/
public static ServletRequestAttributes getRequestAttributes() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
return (ServletRequestAttributes) attributes;
}
/**
* @param response 当前请求的响应
* @param string 传输的文字
*/
public static void renderString(HttpServletResponse response, String string) {
try {
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().print(string);
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
## 7. 可重复读取流的RepeatedlyRequestWrapper
```java
/**
* 构建可重复读取inputStream的请求request
*
* @author 氓氓编程
* @Date: 2021-06-09-8:47
*/
@Slf4j
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {
/**
* 存放请求体中的数据
*/
private final byte[] body;
public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws UnsupportedEncodingException {
super(request);
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
this.body = getBodyString(request).getBytes(StandardCharsets.UTF_8);
}
@Override
public BufferedReader getReader() {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream(){
//1.创建一个字节数组流存放请求体
final ByteArrayInputStream bodyInputStream = new ByteArrayInputStream(body);
//2.返回获取的body中的数据流
return new ServletInputStream() {
@Override
public int read(){
return bodyInputStream.read();
}
@Override
public int available(){
return body.length;
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
/**
* 获取请求体
*
* @param request 请求
* @return 字符串
*/
public static String getBodyString(ServletRequest request) {
//1.创建一个StringBuilder
StringBuilder sb = new StringBuilder();
//2.声明一个读缓存的流
BufferedReader reader = null;
//3.获取请求中的流
try (InputStream inputStream = request.getInputStream()) {
//把请求中的流读取出来给reader
reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
//声明line存放每一行的数据
String line;
//一行一行的读取数据并赋值给lineline不为空
while ((line = reader.readLine()) != null) {
//追加写入
sb.append(line);
}
} catch (IOException e) {
log.warn("获取请求体中数据出现问题");
} finally {
//如果reader不为空关闭流
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
log.error(ExceptionUtil.getMessage(e));
}
}
}
return sb.toString();
}
}
```
## 8. 让可重复读的流生效
```java
/**
* 使用重写后的RepeatedlyRequestWrapper
* <p>
* Repeatable 过滤器
*/
public class RepeatableFilter implements Filter {
/**
* startsWithIgnoreCase 判断开始部分是否与二参数相同。不区分大小写
*
* @param request 请求
* @param response 响应
* @param chain 放行
* @throws IOException io异常
* @throws ServletException 异常
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
//声明一个ServletRequest
ServletRequest requestWrapper = null;
//判断request是HttpServletRequest或子类并且request.getContentType()开头包含application/json
if (request instanceof HttpServletRequest && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
//创建一个可重复获取流的RepeatedlyRequestWrapper赋值给ServletRequest
requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response);
}
//为空,直接放行
if (null == requestWrapper) {
chain.doFilter(request, response);
} else {
//赋值完毕可重复读流继续向下传
chain.doFilter(requestWrapper, response);
}
}
}
```
```java
@SuppressWarnings({ "rawtypes", "unchecked" })
@Bean
public FilterRegistrationBean someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new RepeatableFilter());
registration.addUrlPatterns("/*");
registration.setName("repeatableFilter");
registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE);
return registration;
}
```

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022"
},
"include": [
"src/.vuepress/**/*.ts",
"src/.vuepress/**/*.vue"
],
"exclude": [
"node_modules"
]
}