第一次提交
21
.gitignore
vendored
@ -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
21
package.json
Normal 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"
|
||||
}
|
||||
}
|
14
src/.vuepress/components/BlogHero.vue
Normal 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
@ -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
@ -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/'
|
||||
},
|
||||
]);
|
BIN
src/.vuepress/public/assets/icon/apple-icon-152.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
src/.vuepress/public/assets/icon/chrome-192.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
src/.vuepress/public/assets/icon/chrome-512.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/.vuepress/public/assets/icon/chrome-mask-192.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
src/.vuepress/public/assets/icon/chrome-mask-512.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/.vuepress/public/assets/icon/guide-maskable.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
src/.vuepress/public/assets/icon/ms-icon-144.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/.vuepress/public/assets/images/cover1.jpg
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
src/.vuepress/public/assets/images/cover2.jpg
Normal file
After Width: | Height: | Size: 128 KiB |
BIN
src/.vuepress/public/assets/images/cover3.jpg
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/.vuepress/public/bg/bgImage.jpg
Normal file
After Width: | Height: | Size: 9.7 MiB |
BIN
src/.vuepress/public/favicon.ico
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
src/.vuepress/public/logo.png
Normal file
After Width: | Height: | Size: 92 KiB |
1
src/.vuepress/public/logo.svg
Normal 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 |
BIN
src/.vuepress/public/logo/logo.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
src/.vuepress/public/logo/logoDark.png
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
src/.vuepress/public/logo/transparentLogo.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
42
src/.vuepress/sidebar.ts
Normal 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",
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
2
src/.vuepress/styles/config.scss
Normal file
@ -0,0 +1,2 @@
|
||||
// you can change config here
|
||||
$theme-color: #DC143C;
|
1
src/.vuepress/styles/index.scss
Normal file
@ -0,0 +1 @@
|
||||
// place your custom styles here
|
1
src/.vuepress/styles/palette.scss
Normal file
@ -0,0 +1 @@
|
||||
// you can change colors here
|
238
src/.vuepress/theme.ts
Normal 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
@ -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
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: 主要功能与配置演示
|
||||
index: false
|
||||
icon: laptop-code
|
||||
category:
|
||||
- 使用指南
|
||||
---
|
||||
|
||||
<Catalog />
|
42
src/demo/disable.md
Normal 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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -0,0 +1,20 @@
|
||||
---
|
||||
icon: pen-to-square
|
||||
date: 2022-01-08
|
||||
category:
|
||||
- 香蕉
|
||||
tag:
|
||||
- 黄
|
||||
- 弯曲的
|
||||
- 长
|
||||
---
|
||||
|
||||
# 香蕉 4
|
||||
|
||||
## 标题 2
|
||||
|
||||
这里是内容。
|
||||
|
||||
### 标题 3
|
||||
|
||||
这里是内容。
|
20
src/posts/cherry.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
icon: pen-to-square
|
||||
date: 2022-01-09
|
||||
category:
|
||||
- 樱桃
|
||||
tag:
|
||||
- 红
|
||||
- 小
|
||||
- 圆
|
||||
---
|
||||
|
||||
# 樱桃
|
||||
|
||||
## 标题 2
|
||||
|
||||
这里是内容。
|
||||
|
||||
### 标题 3
|
||||
|
||||
这里是内容。
|
20
src/posts/dragonfruit.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
icon: pen-to-square
|
||||
date: 2022-01-10
|
||||
category:
|
||||
- 火龙果
|
||||
- 水果
|
||||
tag:
|
||||
- 红
|
||||
- 大
|
||||
---
|
||||
|
||||
# 火龙果
|
||||
|
||||
## 标题 2
|
||||
|
||||
这里是内容。
|
||||
|
||||
### 标题 3
|
||||
|
||||
这里是内容。
|
20
src/posts/strawberry.md
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
icon: pen-to-square
|
||||
date: 2022-01-11
|
||||
category:
|
||||
- 水果
|
||||
- 草莓
|
||||
tag:
|
||||
- 红
|
||||
- 小
|
||||
---
|
||||
|
||||
# 草莓
|
||||
|
||||
## 标题 2
|
||||
|
||||
这里是内容。
|
||||
|
||||
### 标题 3
|
||||
|
||||
这里是内容。
|
22
src/posts/tomato.md
Normal 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
|
||||
|
||||
这里是内容。
|
9
src/programming/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
title: 编程
|
||||
index: false
|
||||
icon: laptop-code
|
||||
category:
|
||||
- 编程
|
||||
---
|
||||
|
||||
<Catalog />
|
3015
src/programming/java/framework/SpringSecurity.md
Normal file
766
src/programming/java/防止表单和参数重复提交.md
Normal 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;
|
||||
//一行一行的读取数据并赋值给line,line不为空
|
||||
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
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "ES2022"
|
||||
},
|
||||
"include": [
|
||||
"src/.vuepress/**/*.ts",
|
||||
"src/.vuepress/**/*.vue"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|