Compare commits

..

62 Commits

Author SHA1 Message Date
4569d80f03 chore: 更新图片链接域名从lijue-me.oss-cn-chengdu.aliyuncs.com到img.jiwei.xin
Some checks failed
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
2025-09-08 19:52:04 +08:00
384a08ee50 修改blog页面下方 上一页下一页 2025-08-15 22:17:13 +08:00
5aec62fa76 修改博文 2025-08-15 22:06:27 +08:00
27f6687802 增加新的组件 BlogLists,博客首页新的样式 2025-08-15 21:20:15 +08:00
21b6bb0b1c lint --fix 2025-08-15 08:55:57 +08:00
0bdc095f04 修改样式 2025-08-15 01:13:08 +08:00
4929c2d404 修改博文 2025-08-11 22:50:23 +08:00
e1d6e8dbe9 修改博文,增加gfm 2025-08-11 16:26:21 +08:00
bf1eada649 加bash 2025-08-11 15:31:11 +08:00
1b24905fef 再改 2025-08-11 15:26:48 +08:00
f7694d9c05 再次更改docker node基座 2025-08-11 15:24:03 +08:00
3f6d16f6be 编译改为playwright镜像 2025-08-11 15:09:01 +08:00
a5528eebed 增加lijue.net到.env 2025-08-11 13:53:30 +08:00
27ec8fc16f 添加 npx playwright install chromium 2025-08-11 13:11:44 +08:00
ec60c8ed67 新增博文,注释登录注册窗口,修改h3样式,更改地址为lijue.net 2025-08-11 12:38:51 +08:00
3672709801 在Dockerfile中增加pnpm exec playwright install 2025-08-11 10:51:22 +08:00
423cd07cd9 修改公式 2025-08-11 00:54:32 +08:00
6c061b1e58 添加rehype-mathjax对公式和流程的支持 2025-08-11 00:21:33 +08:00
6d965ccd40 修改H1-H6和表格,strong字体 2025-08-10 22:18:56 +08:00
4274781aee 对主题内容进行一些优化 2025-08-10 20:10:59 +08:00
0baec36757 对主题风格的圆角阴影进行了重定义 2025-08-10 19:59:58 +08:00
bdacf7bfd3 修改设置菜单位于顶层 2025-08-10 19:51:08 +08:00
06de0f2b33 修改分享传递参数,清理注释 2025-08-10 16:38:55 +08:00
3b9fae1eea 修改lint 2025-08-10 16:23:47 +08:00
c557e6980c 修改分享到微信按钮至HeaderLinks
组件.  并修改博客文章头样式,加入日期显示
2025-08-10 16:01:03 +08:00
85863cb249 提交博文 2025-08-10 13:09:02 +08:00
bb48392da4 增加API检测域名 2025-08-10 12:52:58 +08:00
194bb77793 修改汉堡图标隐藏问题 2025-08-09 22:53:15 +08:00
5955c1f4e3 图标 2025-08-09 22:36:01 +08:00
19e552009a 清楚wxShare组件的elint错误 2025-08-09 22:32:20 +08:00
0e380b34a3 修改样式与lint 2025-08-09 22:28:29 +08:00
046b0ef674 增加监听URL变化
Some checks failed
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
2025-08-09 13:37:57 +08:00
597339b666 添加微信分享功能 2025-08-09 13:27:47 +08:00
f57e410393 测试新的ci,触发coolify的部署 2025-08-08 18:01:36 +08:00
3aed750f78 修改页面底部链接 2025-08-08 17:39:58 +08:00
afb76863de 增加博文 2025-08-08 17:27:25 +08:00
9a0dda8e77 新增博文 2025-08-08 14:15:45 +08:00
99e53ed018 增加博文 2025-08-08 13:53:33 +08:00
817648af09 调整blog导航样式 2025-08-08 13:47:49 +08:00
991dd065f8 lint --fix 2025-08-08 13:02:49 +08:00
033150a442 解决SSR问题 2025-08-08 12:33:54 +08:00
be69a51bb2 改回项目内获取md文档
Some checks failed
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
2025-08-08 12:15:35 +08:00
9e8a7f5c14 删除本地content目录,改为由github获取 2025-08-08 10:59:21 +08:00
ff52407ef9 更改dockerfile以及uipro脚本 2025-08-08 09:38:23 +08:00
5dc0ba2082 再次修改docker 2025-08-08 09:14:54 +08:00
b4b41d3409 增加docker编译修改 2025-08-08 08:55:48 +08:00
a1092c4de4 更改docker编译设置 2025-08-08 08:54:49 +08:00
9896a560a9 修改blog文档顺序 2025-08-08 08:37:40 +08:00
c4af5e7f65 修改文档内路由错误 2025-08-07 22:39:34 +08:00
cb407365b0 Merge branch 'blog_dev'
Some checks failed
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
2025-08-07 22:24:12 +08:00
c6ca2507ae lint --fix 2025-08-07 22:21:00 +08:00
8e66c0fc56 转移blog上所有日志. 添加nuxt.config里高亮配置 2025-08-07 22:18:31 +08:00
6bafa4d12d 修改样式,添加文档 2025-08-07 20:34:24 +08:00
fdab0fd5bb 修改页面导航栏样式,添加MD 2025-08-07 19:42:34 +08:00
bf0add6dc6 分离blog和docs的布局 2025-08-07 15:52:23 +08:00
df43c1845e fix 首页 关于 页面路由问题 2025-08-07 15:02:00 +08:00
0beda86667 增加blog首页列表 2025-08-07 15:00:06 +08:00
4c8334b7bd 修复type错误和lint --fix 2025-08-07 14:15:21 +08:00
25470fc7f8 修改页面布局大修改 2025-08-07 13:39:05 +08:00
41e273ed07 完成目录架构变更 2025-08-06 23:14:36 +08:00
cca2b53694 修改复制链接等功能 2025-08-06 22:36:01 +08:00
3c359c6a19 升级ui到3.3,nuxt到4.0.3, 增加raw显示原始md文档功能 2025-08-06 22:19:52 +08:00
117 changed files with 9345 additions and 883 deletions

View File

@@ -76,4 +76,23 @@ jobs:
context: .
file: ./Dockerfile
push: true
tags: reg.lijue.net:5888/docs/docs:latest
tags: reg.lijue.net:5888/docs/docs:latest
- name: Trigger Coolify deploy
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
env:
COOLIFY_API_URL: https://coolify.lijue.net:5888/api/v1/deploy?uuid=ks44swgo4kc8wgwkwck4s004&force=false
COOLIFY_TOKEN: ${{ secrets.COOLIFY_TOKEN }}
run: |
set -euo pipefail
echo "Triggering Coolify deploy: $COOLIFY_API_URL"
http_code=$(curl -sS -w "%{http_code}" -o coolify_response.json -X GET "$COOLIFY_API_URL" \
-H "Authorization: Bearer $COOLIFY_TOKEN" \
-H "Accept: application/json")
echo "HTTP status: $http_code"
echo "Response:"
cat coolify_response.json
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 400 ]; then
echo "Coolify deploy trigger failed"
exit 1
fi

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# Nuxt dev/build outputs
.output
.output.*
.data
.nuxt
.nitro

View File

@@ -1,32 +1,44 @@
# ------------- 依赖缓存阶段 -------------
FROM node:22-alpine AS base
# 国内镜像
############################################################
# 1) 依赖层 - 只要锁文件没变,永远复用
############################################################
FROM node:22-bookworm AS deps
# 国内加速
RUN npm config set registry https://registry.npmmirror.com
WORKDIR /app
# 拷贝所有源码 - Nuxt 4在pnpm i阶段需要完整的项目结构
COPY . .
# 安装依赖
# 只拷依赖描述文件
COPY package.json pnpm-lock.yaml .npmrc ./
# 安装,结果是 /app/node_modules 和全局 pnpm cache
RUN corepack enable && pnpm install --frozen-lockfile
# 执行补丁脚本
RUN chmod +x patch-ui-pro.zsh && ./patch-ui-pro.zsh
# ------------- 构建阶段 -------------
FROM base AS builder
RUN pnpm build
# ------------- 运行阶段 -------------
FROM node:22-alpine AS production
############################################################
# 2) 编译层 - 代码变了也不影响 deps 缓存
############################################################
FROM node:22-bookworm AS builder
WORKDIR /app
# 拷贝构建产物 - Nuxt的output目录已经包含了所有运行时需要的文件
COPY --from=builder /app/.output ./.output
# 先复用 deps 里的 node_modules
COPY --from=deps /app/node_modules ./node_modules
# 再拷源码
COPY . .
# 运行补丁(脚本使用 bash 语法,需要 bash 执行)
RUN chmod +x patch-ui-pro.sh && bash ./patch-ui-pro.sh
# 注意把内存限制放在这里,避免本地开发时也被硬限制
ENV NODE_OPTIONS="--max-old-space-size=4096"
# 在 Ubuntu/Jammy 上一次性安装浏览器及其系统依赖,并仅安装 Chromium 以加速
RUN corepack enable && npx playwright install --with-deps chromium && pnpm build
############################################################
# 3) 运行层 - 只有可运行文件,最小镜像
############################################################
FROM node:22-alpine AS production
WORKDIR /app
COPY --chown=node:node --from=builder /app/.output ./.output
EXPOSE 3000
ENV NODE_ENV=production HOST=0.0.0.0 PORT=3000
CMD ["node", ".output/server/index.mjs"]
USER node
CMD ["node", ".output/server/index.mjs"]

View File

@@ -8,248 +8,8 @@ export default defineAppConfig({
base: 'w-full h-full mx-auto px-2 sm:px-6 lg:px-8'
}
},
uiPro: {
modal: {
slots: {
overlay: 'fixed inset-0 bg-elevated/75',
content: 'fixed bg-default divide-y divide-default flex flex-col focus:outline-none',
header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-16',
wrapper: '',
body: 'flex-1 overflow-y-auto p-4 sm:p-6',
footer: 'flex items-center gap-1.5 p-4 sm:px-6',
title: 'text-highlighted font-semibold',
description: 'mt-1 text-muted text-sm',
close: 'absolute top-4 end-4'
},
variants: {
fullscreen: {
true: {
content: 'inset-0'
},
false: {
content: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[calc(100vw-2rem)] max-w-lg max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)] rounded-lg shadow-lg ring ring-default overflow-hidden'
}
}
}
},
footer: {
slots: {
root: 'border-t border-default',
left: 'text-sm text-muted'
}
},
pageCard: {
slots: {
root: 'relative flex rounded-lg',
spotlight: 'absolute inset-0 rounded-[inherit] pointer-events-none bg-default/90',
container: 'relative flex flex-col flex-1 lg:grid gap-x-8 gap-y-4 p-4 sm:p-6',
wrapper: 'flex flex-col flex-1 items-start',
header: 'mb-4',
body: 'flex-1',
footer: 'pt-4 mt-auto',
leading: 'inline-flex items-center mb-2.5',
leadingIcon: 'size-5 shrink-0 text-primary',
title: 'text-base text-pretty font-semibold text-highlighted',
description: 'text-[15px] text-pretty'
},
variants: {
orientation: {
horizontal: {
container: 'lg:grid-cols-2 lg:items-center'
},
vertical: {
container: ''
}
},
reverse: {
true: {
wrapper: 'lg:order-last'
}
},
variant: {
solid: {
root: 'bg-inverted text-inverted',
title: 'text-inverted',
description: 'text-dimmed'
},
outline: {
root: 'bg-default ring ring-default',
description: 'text-muted'
},
soft: {
root: 'bg-elevated/50',
description: 'text-toned'
},
subtle: {
root: 'bg-elevated/50 ring ring-default',
description: 'text-toned'
},
ghost: {
description: 'text-muted'
},
naked: {
container: 'p-0 sm:p-0',
description: 'text-muted'
}
},
to: {
true: {
root: [
'transition'
]
}
},
title: {
true: {
description: 'mt-1'
}
},
highlight: {
true: {
root: 'ring-2'
}
},
highlightColor: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
spotlight: {
true: {
root: '[--spotlight-size:400px] before:absolute before:-inset-px before:pointer-events-none before:rounded-[inherit] before:bg-[radial-gradient(var(--spotlight-size)_var(--spotlight-size)_at_calc(var(--spotlight-x,0px))_calc(var(--spotlight-y,0px)),var(--spotlight-color),transparent_70%)]'
}
},
spotlightColor: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
}
},
compoundVariants: [
{
variant: 'solid',
to: true,
class: {
root: 'hover:bg-inverted/90'
}
},
{
variant: 'outline',
to: true,
class: {
root: 'hover:bg-elevated/50'
}
},
{
variant: 'soft',
to: true,
class: {
root: 'hover:bg-elevated'
}
},
{
variant: 'subtle',
to: true,
class: {
root: 'hover:bg-elevated'
}
},
{
variant: 'subtle',
to: true,
highlight: false,
class: {
root: 'hover:ring-accented'
}
},
{
variant: 'ghost',
to: true,
class: {
root: 'hover:bg-elevated/50'
}
},
{
highlightColor: 'primary',
highlight: true,
class: {
root: 'ring-primary'
}
},
{
highlightColor: 'neutral',
highlight: true,
class: {
root: 'ring-inverted'
}
},
{
spotlightColor: 'primary',
spotlight: true,
class: {
root: '[--spotlight-color:var(--ui-primary)]'
}
},
{
spotlightColor: 'secondary',
spotlight: true,
class: {
root: '[--spotlight-color:var(--ui-secondary)]'
}
},
{
spotlightColor: 'success',
spotlight: true,
class: {
root: '[--spotlight-color:var(--ui-success)]'
}
},
{
spotlightColor: 'info',
spotlight: true,
class: {
root: '[--spotlight-color:var(--ui-info)]'
}
},
{
spotlightColor: 'warning',
spotlight: true,
class: {
root: '[--spotlight-color:var(--ui-warning)]'
}
},
{
spotlightColor: 'error',
spotlight: true,
class: {
root: '[--spotlight-color:var(--ui-error)]'
}
},
{
spotlightColor: 'neutral',
spotlight: true,
class: {
root: '[--spotlight-color:var(--ui-bg-inverted)]'
}
}
],
defaultVariants: {
variant: 'outline',
highlightColor: 'primary',
spotlightColor: 'primary'
}
}
},
seo: {
siteName: 'Nuxt Docs Template'
siteName: 'Estel Docs'
},
header: {
title: 'Estel Docs',
@@ -272,25 +32,20 @@ export default defineAppConfig({
credits: `Copyright © ${new Date().getFullYear()} Estel. All rights reserved.`,
colorMode: false,
links: [{
'icon': 'simple-icons-nuxtdotjs',
'to': 'https://nuxt.com',
'icon': 'vscode-icons:file-type-swift',
'to': 'https://www.xajiwei.com',
'target': '_blank',
'aria-label': 'Nuxt Website'
'aria-label': '稷维科技'
}, {
'icon': 'simple-icons-discord',
'to': 'https://discord.com/invite/ps2h6QT',
'icon': 'vscode-icons:folder-type-github',
'to': 'https://git.jiwei.xin',
'target': '_blank',
'aria-label': 'Nuxt UI on Discord'
}, {
'icon': 'simple-icons-x',
'to': 'https://x.com/nuxt_js',
'target': '_blank',
'aria-label': 'Nuxt on X'
'aria-label': 'gitea'
}, {
'icon': 'simple-icons-github',
'to': 'https://github.com/nuxt/ui',
'to': 'https://github.com/estel-li/estel_docs',
'target': '_blank',
'aria-label': 'Nuxt UI on GitHub'
'aria-label': 'Estel Docs GitHub'
}]
},
toc: {
@@ -311,7 +66,7 @@ export default defineAppConfig({
}, {
icon: 'simple-icons-nuxtdotjs',
label: '个人博客',
to: 'https://lijue.me',
to: 'https://lijue.net',
target: '_blank'
}]
}
@@ -322,5 +77,96 @@ export default defineAppConfig({
name: 'estel_docs',
branch: 'main',
rootDir: ''
},
uiPro: {
prose: {
code: {
base: 'px-1.5 py-0.5 text-sm font-mono font-medium rounded-md inline-block',
variants: {
color: {
new: 'border border-muted text-base font-medium bg-muted bg-gray-100 dark:bg-gray-800 text-[#DD1144]'
}
},
defaultVariants: {
color: 'new'
}
},
pre: {
slots: {
root: 'relative my-5 group',
header: 'flex items-center gap-1.5 border border-muted bg-default border-b-0 relative rounded-t-md px-4 py-3',
filename: 'text-default text-sm/6',
icon: 'size-5 shrink-0',
copy: 'absolute top-[11px] right-[11px] transition',
base: 'group font-mono text-sm/6 border border-muted bg-gray-100 dark:bg-gray-800 rounded-md px-4 py-3 whitespace-pre-wrap break-words overflow-x-auto focus:outline-none'
}
},
codeIcon: {
terminal: 'i-lucide-terminal',
code: 'vscode-icons:file-type-codekit',
kali: 'i-devicon:kalilinux',
bash: 'i-devicon:powershell',
debian: 'i-devicon:debian'
},
codeCollapse: {
slots: {
root: 'relative [&_pre]:h-[350px]',
footer: 'h-14 absolute inset-x-px bottom-px rounded-b-md flex items-center justify-center',
trigger: 'group',
triggerIcon: 'group-data-[state=open]:rotate-180'
},
variants: {
open: {
true: {
root: '[&_pre]:h-auto [&_pre]:min-h-[200px] [&_pre]:max-h-[80vh] [&_pre]:pb-12'
},
false: {
root: '[&_pre]:overflow-hidden',
footer: 'bg-gradient-to-t from-muted'
}
}
}
},
table: {
slots: {
root: 'relative my-5 overflow-x-auto mx-auto',
base: 'w-auto table-auto border-separate border-spacing-0 rounded-md'
}
},
thead: {
base: 'bg-muted'
},
tbody: {
base: ''
},
tr: {
base: '[&:first-child>th:first-child]:rounded-tl-md [&:first-child>th:last-child]:rounded-tr-md [&:last-child>td:first-child]:rounded-bl-md [&:last-child>td:last-child]:rounded-br-md'
},
th: {
base: 'py-3 px-4 font-semibold text-base text-left border-e border-b first:border-s border-t border-muted bg-gray-200 dark:bg-gray-800'
},
td: {
base: 'py-3 px-4 text-sm text-left align-top border-e border-b first:border-s border-muted [&_code]:text-xs/5 [&_p]:my-0 [&_p]:leading-6 [&_ul]:my-0 [&_ol]:my-0 [&_ul]:ps-4.5 [&_ol]:ps-4.5 [&_li]:leading-6 [&_li]:my-0.5'
}
},
blogPost: {
slots: {
root: 'relative group/blog-post flex flex-col rounded-lg overflow-hidden md:w-8/10 md:ml-10',
header: 'relative overflow-hidden aspect-[16/9] w-full pointer-events-none',
body: 'min-w-0 flex-1 flex flex-col',
footer: '',
image: 'object-cover object-top w-full h-full md:p-3',
title: 'text-3xl text-pretty font-bold text-highlighted',
description: 'mt-1 text-lg text-pretty',
authors: 'pt-4 mt-auto flex flex-wrap gap-x-3 gap-y-1.5',
avatar: '',
meta: 'flex items-center gap-2 mb-2',
date: 'text-base',
badge: 'text-sm font-bold'
}
},
blogPosts: {
base: 'flex flex-col gap-8 lg:gap-y-12'
}
}
})

View File

@@ -2,6 +2,7 @@
const { seo } = useAppConfig()
const { data: navigation } = await useAsyncData('navigation', () => queryCollectionNavigation('docs'))
const { data: blogNavigation } = await useAsyncData('blogNavigation', () => queryCollectionNavigation('blog'))
const { data: files } = useLazyAsyncData('search', () => queryCollectionSearchSections('docs'), {
server: false
})
@@ -45,6 +46,7 @@ useSeoMeta({
})
provide('navigation', navigation)
provide('blogNavigation', blogNavigation)
</script>
<template>

View File

@@ -21,6 +21,11 @@
:root {
--ui-header-height: --spacing(16);
/* 全局圆角与阴影,作为默认值,可被主题覆盖 */
--ui-radius: 8px;
--ui-button-radius: var(--ui-radius);
--ui-card-radius: calc(var(--ui-radius) + 4px);
--ui-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
/* 主题字体样式 - 使用Nuxt Font字体 */
@@ -93,6 +98,11 @@
--theme-background: #ffffff;
--theme-text: #1f2937;
--theme-border: #e5e7eb;
/* 经典:中等圆角与轻阴影 */
--ui-radius: 6px;
--ui-button-radius: 6px;
--ui-card-radius: 10px;
--ui-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.theme-elegant {
@@ -100,6 +110,11 @@
--theme-background: #fafafa;
--theme-text: #374151;
--theme-border: #d1d5db;
/* 优雅:更大圆角与柔和阴影 */
--ui-radius: 8px;
--ui-button-radius: 10px;
--ui-card-radius: 12px;
--ui-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.theme-minimal {
@@ -107,6 +122,11 @@
--theme-background: #ffffff;
--theme-text: #111827;
--theme-border: #f3f4f6;
/* 简洁:极小圆角,无阴影 */
--ui-radius: 3px;
--ui-button-radius: 3px;
--ui-card-radius: 6px;
--ui-shadow: none;
}
/* 深色模式支持 */
@@ -127,3 +147,54 @@
--theme-text: #ffffff;
--theme-border: #1f2937;
}
/* 统一将主题风格映射到常见元素,尽量少侵入、仅做“润色” */
/* 图片与代码块采用卡片圆角与轻阴影,增强主题差异感 */
/* :where(.prose) img,
:where(.prose) pre,
:where(.prose) table {
border-radius: var(--ui-card-radius);
box-shadow: var(--ui-shadow);
background-color: var(--theme-background);
border: 1px solid var(--theme-border);
} */
/* 按钮采用按钮圆角,保持各主题观感统一 */
/* button,
[role="button"] {
border-radius: var(--ui-button-radius);
} */
/* 常见圆角工具类在不同主题下的“风格化”提升 */
/* :where(.theme-classic) .rounded-lg,
:where(.theme-classic) .rounded-xl { border-radius: var(--ui-card-radius); }
:where(.theme-elegant) .rounded-md { border-radius: var(--ui-button-radius); }
:where(.theme-elegant) .rounded-lg { border-radius: calc(var(--ui-card-radius) + 2px); }
:where(.theme-elegant) .rounded-xl { border-radius: calc(var(--ui-card-radius) + 6px); }
:where(.theme-minimal) .rounded-md { border-radius: var(--ui-radius); }
:where(.theme-minimal) .rounded-lg { border-radius: calc(var(--ui-radius) + 2px); }
:where(.theme-minimal) .rounded-xl { border-radius: calc(var(--ui-radius) + 4px); } */
/* Inline math ($...$) 强制行内,不换行 */
/* .math-inline,
.math.math-inline,
mjx-container[display="false"] {
display: inline !important;
}
p > mjx-container[display="false"],
li > mjx-container[display="false"] {
display: inline !important;
margin: 0 0.15em !important;
vertical-align: middle !important;
}
.math-display,
.math.math-display,
mjx-container[display="true"] {
display: block !important;
margin: 0.75em 0 !important;
} */

View File

@@ -6,6 +6,15 @@ const { footer } = useAppConfig()
<UFooter>
<template #left>
{{ footer.credits }}
<UButton
color="neutral"
variant="ghost"
size="sm"
to="https://beian.miit.gov.cn/"
target="_blank"
>
陕ICP备2021012926号-3
</UButton>
</template>
<template #right>

View File

@@ -4,7 +4,7 @@
:modal="false"
:items="[{
label: 'Blog',
to: 'https://lijue.me'
to: 'https://lijue.net'
}, {
label: 'Docs',
to: 'https://docs-template.nuxt.dev/',

View File

@@ -10,7 +10,7 @@
>
<div
v-if="isOpen"
class="fixed right-0 top-16 h-[calc(100vh-4rem)] w-full sm:w-96 bg-white dark:bg-gray-900 shadow-2xl z-40 overflow-y-auto border-l border-gray-200 dark:border-gray-700 custom-scrollbar"
class="fixed right-0 z-51 top-16 h-[calc(100vh-4rem)] w-full sm:w-96 bg-white dark:bg-gray-900 shadow-2xl overflow-y-auto border-l border-gray-200 dark:border-gray-700 custom-scrollbar"
>
<!-- 头部 -->
<div class="sticky top-0 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 px-6 py-4">

View File

@@ -2,7 +2,18 @@
// import type { NavigationMenuItem } from '@nuxt/ui'
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
const navigation = inject<Ref<ContentNavigationItem[]>>('blogNavigation')
// blog
const blogNavigation = computed(() => {
if (!navigation?.value) return []
// docs
const docsItem = navigation.value.find(item => item.title === 'blog' || item.path === '/blog')
if (!docsItem?.children) return []
// docs
return docsItem.children
})
const isSettingsOpen = ref(false)
const isLoginModalOpen = ref(false)
@@ -22,13 +33,13 @@ const Props = withDefaults(defineProps<Props>(), {
})
//
const handleLoginRegister = (type: 'login' | 'register') => {
if (type === 'login') {
isLoginModalOpen.value = true
} else if (type === 'register') {
isRegisterModalOpen.value = true
}
}
// const handleLoginRegister = (type: 'login' | 'register') => {
// if (type === 'login') {
// isLoginModalOpen.value = true
// } else if (type === 'register') {
// isRegisterModalOpen.value = true
// }
// }
</script>
<template>
@@ -38,6 +49,16 @@ const handleLoginRegister = (type: 'login' | 'register') => {
mode="modal"
class="bg-gray-50 dark:bg-gray-900"
>
<template #toggle="{ open, toggle }">
<UButton
color="neutral"
variant="ghost"
class="lg:hidden"
:icon="open ? 'lucide-x' : 'simple-icons-microsoft'"
aria-label="切换菜单"
@click="toggle"
/>
</template>
<template #title>
<h6 />
</template>
@@ -48,7 +69,7 @@ const handleLoginRegister = (type: 'login' | 'register') => {
<div class="mt-4 mb-4 border-t border-gray-200 dark:border-gray-700 w-9/10 mx-5" />
<UContentNavigation
highlight
:navigation="navigation"
:navigation="blogNavigation"
color="primary"
type="single"
variant="pill"
@@ -97,7 +118,7 @@ const handleLoginRegister = (type: 'login' | 'register') => {
aria-label="GitHub"
/>
</UTooltip>
<div class="flex items-center space-x-2">
<!-- <div class="flex items-center space-x-2">
<button
class="px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
@click="handleLoginRegister('login')"
@@ -110,7 +131,7 @@ const handleLoginRegister = (type: 'login' | 'register') => {
>
注册
</button>
</div>
</div> -->
</template>
</UHeader>

View File

@@ -0,0 +1,38 @@
<template>
<div class="relative w-full h-64 md:h-80 lg:h-96 overflow-hidden rounded-xl">
<!-- 背景图片 -->
<NuxtImg
src="https://img.jiwei.xin/%E8%83%8C%E6%99%AF.jpg"
alt="Blog Hero"
class="w-full h-full object-cover"
placeholder
loading="lazy"
sizes="100vw sm:50vw md:33vw"
/>
<!-- 渐变遮罩 -->
<!-- <div class="absolute inset-0 bg-gradient-to-r from-black/60 via-black/40 to-transparent"></div> -->
<!-- 内容区域 -->
<!-- <div class="absolute inset-0 flex items-center justify-start p-8">
<div class="max-w-2xl">
<h1 class="text-4xl md:text-5xl lg:text-6xl font-bold text-white mb-4">
博客文章
</h1>
<p class="text-lg md:text-xl text-gray-200 mb-6">
分享技术见解和心得体会
</p>
<div class="flex items-center gap-4 text-white/80">
<div class="flex items-center gap-2">
<UIcon name="lucide-pen-tool" size="20" />
<span class="text-sm">技术分享</span>
</div>
<div class="flex items-center gap-2">
<UIcon name="lucide-calendar" size="20" />
<span class="text-sm">持续更新</span>
</div>
</div>
</div>
</div> -->
</div>
</template>

View File

@@ -0,0 +1,148 @@
<script setup lang="ts">
// 定义组件参数类型
interface Props {
perPage?: number // 每页显示的文章数,默认 12
maxPages?: number // 最大页数,默认 15
}
// 接收 props设置默认值
const props = withDefaults(defineProps<Props>(), {
perPage: 12,
maxPages: 15
})
// 分页状态
const currentPage = ref(1)
// 获取所有博客文章
const { data: allArticles } = await useAsyncData('blog-all-articles', () => {
return queryCollection('blog')
.select('path', 'title', 'description', 'img', 'date')
.where('path', 'NOT LIKE', '%navigation%')
.all()
})
// 按时间排序的文章列表
const sortedArticles = computed(() => {
if (!allArticles.value) return []
return [...allArticles.value].sort((a, b) => {
// 如果没有日期,排在最后
if (!a.date && !b.date) return 0
if (!a.date) return 1
if (!b.date) return -1
// 按日期降序排列(最新的在前面)
const dateA = new Date(a.date)
const dateB = new Date(b.date)
return dateB.getTime() - dateA.getTime()
})
})
// 计算总页数,限制最大页数
const totalPages = computed(() => {
if (!sortedArticles.value) return 1
const calculatedPages = Math.ceil(sortedArticles.value.length / props.perPage)
return Math.min(calculatedPages, props.maxPages)
})
// 计算当前页显示的文章
const paginatedArticles = computed(() => {
if (!sortedArticles.value) return []
const startIndex = (currentPage.value - 1) * props.perPage
const endIndex = startIndex + props.perPage
return sortedArticles.value.slice(startIndex, endIndex)
})
// 格式化日期函数
const formatDate = (date: string | Date | null | undefined) => {
if (!date) return '未知时间'
try {
const d = new Date(date)
return d.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
} catch {
return '未知时间'
}
}
// 从路径中提取分类标签
const getCategoryFromPath = (path: string) => {
// 路径格式: /blog/技术栈/xxx 或 /blog/AI/xxx 或 /blog/生活/xxx
const match = path.match(/\/blog\/(.+?)\//)
return match ? match[1] : '技术栈'
}
// 转换文章数据为 UBlogPosts 需要的格式
const blogPosts = computed(() => {
if (!paginatedArticles.value) return []
return paginatedArticles.value.map(article => ({
title: article.title || '未命名文章',
description: article.description || '',
date: formatDate(article.date),
image: article.img || '/images/default-blog.jpg',
to: article.path,
badge: {
label: getCategoryFromPath(article.path),
color: 'primary' as const,
variant: 'subtle' as const
}
}))
})
// 监听页面变化,确保页面在有效范围内
watch(currentPage, (newPage) => {
if (newPage > totalPages.value && totalPages.value > 0) {
currentPage.value = totalPages.value
} else if (newPage < 1) {
currentPage.value = 1
}
})
// 当文章数据变化时,重置到第一页
watch(sortedArticles, () => {
if (currentPage.value > totalPages.value && totalPages.value > 0) {
currentPage.value = 1
}
})
</script>
<template>
<div class="w-full bg-gray-50 dark:bg-gray-900 min-h-screen">
<div class="max-w-5xl mx-auto md:px-6 lg:px-8 py-8">
<!-- 有文章时显示内容 -->
<div v-if="sortedArticles && sortedArticles.length > 0">
<div class="">
<UBlogPosts
orientation="vertical"
:posts="blogPosts"
/>
</div>
<!-- 分页控件 -->
<div class="flex justify-center pt-8 border-t border-gray-200 dark:border-gray-700">
<div class="pt-6">
<UPagination
v-model:page="currentPage"
:total="sortedArticles.length"
:items-per-page="props.perPage"
:max="7"
size="md"
show-edges
:show-first="totalPages > 7"
:show-last="totalPages > 7"
color="primary"
variant="outline"
/>
</div>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,107 @@
<template>
<UPage>
<div
class="w-64 bg-white dark:bg-gray-900
flex flex-col h-screen border-r border-gray-200 dark:border-gray-700
overflow-hidden"
>
<div class="flex-shrink-0 p-4 border-gray-200 dark:border-gray-700">
<!-- Logo -->
<LogoPro />
</div>
<!-- Search Box -->
<div class="p-4 border-gray-200 dark:border-gray-700">
<ClientOnly>
<UContentSearchButton
:collapsed="false"
label="搜索文档"
class="w-full"
color="primary"
/>
</ClientOnly>
</div>
<!-- 可滚动的导航区域 -->
<div class="h-full overflow-y-auto no-scrollbar">
<!-- 导航 Section -->
<div>
<NuxtLink
to="/"
class="flex items-center px-4 py-2 text-sm font-medium rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 hover:shadow-sm transition-all duration-200"
:class="{
' text-blue-600 dark:text-blue-400 ':
useRoute().path === '/'
}"
>
<Icon
name="lucide-home"
class="text-primary mr-2"
size="20"
/>
站点首页
</NuxtLink>
<div class="px-2">
<DocsAsideLeftTop />
</div>
</div>
<!-- 分隔线 -->
<div class="mt-4 uppercase tracking-wider border-t border-gray-200 dark:border-gray-700 w-7/9 mx-5" />
<!-- 文档目录导航 -->
<div class="mt-5 flex items-center justify-start pl-2 pb-3">
<UContentNavigation
highlight
:navigation="docsNavigation"
color="primary"
type="single"
variant="pill"
trailing-icon="lucide:chevron-right"
:ui="{
root: 'w-full text-gray-700 dark:text-gray-300',
content: 'w-full',
list: 'w-full',
link: 'w-full min-w-0 text-gray-600 dark:text-gray-400',
linkTitle: 'truncate',
linkLeadingIcon: 'shrink-0',
linkTrailingIcon: 'shrink-0'
}"
/>
</div>
</div>
</div>
</UPage>
</template>
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('blogNavigation')
// 提取 docs 目录下的子目录作为顶级导航
const docsNavigation = computed(() => {
if (!navigation?.value) return []
// 查找 docs 目录
const docsItem = navigation.value.find(item => item.title === 'blog' || item.path === '/blog')
if (!docsItem?.children) return []
// 返回 docs 目录下的子目录
return docsItem.children
})
</script>
<style scoped>
.no-scrollbar {
/* Firefox */
scrollbar-width: none;
/* IE and Edge */
-ms-overflow-style: none;
}
.no-scrollbar::-webkit-scrollbar {
/* Chrome, Safari, Opera */
display: none;
}
</style>

View File

@@ -0,0 +1,117 @@
<script setup lang="ts">
// 获取 blog 集合的所有文章
const { data: articles } = await useAsyncData('blog-articles', () => {
return queryCollection('blog')
.select('path', 'title', 'description', 'img', 'date')
.where('path', 'NOT LIKE', '%navigation%')
.all()
})
// 格式化日期
const formatDate = (date: string | Date | null | undefined) => {
if (!date) return '未知时间'
try {
const d = new Date(date)
return d.toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric'
})
} catch {
return '未知时间'
}
}
// 计算阅读时间(基于文章内容长度估算)
// const getReadingTime = (content: string | null | undefined) => {
// if (!content) return 1
// try {
// const wordsPerMinute = 200
// const wordCount = String(content).split(/\s+/).length
// return Math.max(1, Math.ceil(wordCount / wordsPerMinute))
// } catch {
// return 1
// }
// }
</script>
<template>
<div class="w-full bg-gray-50 dark:bg-gray-900 min-h-screen mt-4">
<div class="max-w-4xl mx-auto ">
<!-- 文章列表 -->
<div class="space-y-6 w-full">
<NuxtLink
v-for="item in articles"
:key="item.path"
:to="item.path"
class="block w-full"
>
<article
class="pr-4 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden hover:shadow-md transition-all duration-200 cursor-pointer group"
>
<!-- 文章内容布局 -->
<div class="flex flex-row">
<!-- 图片预览区域 -->
<div class="w-32 h-32 md:w-40 md:h-40 mt-2 ml-2 mb-2 md:mr-2 -mr-1 bg-gray-100 dark:bg-gray-700 rounded-xl overflow-hidden">
<img
:src="item.img || '/images/default-blog.jpg'"
:alt="item.title"
class="w-full h-full object-contain group-hover:scale-110 transition-transform duration-300"
>
</div>
<!-- 文字内容区域 -->
<div class="flex-1 p-4 md:p-6">
<!-- 文章头部 -->
<div class="mb-4">
<h2 class="md:text-2xl text-xl font-bold text-gray-900 dark:text-white mb-2 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
{{ item.title }}
</h2>
<!-- 文章描述 -->
<p
v-if="item.description"
class="text-gray-600 dark:text-gray-300 md:text-sm text-xs line-clamp-2"
>
{{ item.description }}
</p>
</div>
<!-- 文章元信息 -->
<div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
<div class="flex items-center gap-4">
<!-- 文章时间 -->
<div class="flex items-center gap-1">
<UIcon
name="lucide-clock"
size="14"
/>
<span>{{ formatDate(item.date) }}</span>
</div>
</div>
</div>
</div>
</div>
</article>
</NuxtLink>
</div>
<!-- 空状态 -->
<div
v-if="!articles || articles.length === 0"
class="text-center py-12"
>
<UIcon
name="lucide-file-text"
class="mx-auto text-4xl text-gray-400 mb-4"
/>
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">
暂无文章
</h3>
<p class="text-gray-500 dark:text-gray-400">
还没有发布任何博客文章
</p>
</div>
</div>
</div>
</template>

View File

@@ -18,7 +18,7 @@
<!-- ::card
---
img: https://lijue-me.oss-cn-chengdu.aliyuncs.com/20250727184358217.png
img: https://img.jiwei.xin/20250727184358217.png
---
#title
Image Card

View File

@@ -1,7 +1,8 @@
<template>
<h1
:id
class="scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl"
:class="['scroll-m-20 font-extrabold tracking-tight', themeSizeClass]"
:style="themeTextShadowStyle"
>
<NuxtLink
v-if="generate"
@@ -18,4 +19,26 @@ const { id } = defineProps<{ id?: string }>()
const { headings } = useRuntimeConfig().public.mdc
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h1)))
// 读取主题,按主题做轻量风格差异:
// - classic: 无阴影,正常字号
// - elegant: 有文字阴影,正常字号
// - minimal: 无阴影,较小字号
const { selectedTheme } = useTheme()
// 这个计算属性 themeSizeClass 用于根据当前主题selectedTheme动态设置 h1 标题的字号样式:
// - 如果主题是 minimal则使用较小字号 'text-2xl lg:text-3xl'
// - 否则classic 或 elegant使用较大字号 'text-3xl lg:text-4xl'
const themeSizeClass = computed(() => {
return selectedTheme.value === 'classic'
? 'text-2xl lg:text-3xl underline decoration-3 decoration-primary dark:decoration-gray-700 underline-offset-8 mb-3'
: 'text-3xl lg:text-4xl'
})
const themeTextShadowStyle = computed(() => {
if (selectedTheme.value === 'elegant') {
return { textShadow: '0 2px 8px rgba(0,0,0,0.15)' }
}
return { textShadow: 'none' }
})
</script>

View File

@@ -1,11 +1,13 @@
<template>
<h2
:id
class="scroll-m-20 border-b border-gray-200 dark:border-gray-700 pb-2 text-3xl font-semibold tracking-tight transition-colors [&:not(:first-child)]:mt-10 mb-2"
:class="['scroll-m-20 inline-block text-white font-semibold tracking-tight transition-colors [&:not(:first-child)]:mt-10 mb-2', themeSizeClass, themePaddingClass]"
:style="h2Style"
>
<NuxtLink
v-if="id && generate"
:to="`#${id}`"
class="no-underline"
>
<slot />
</NuxtLink>
@@ -16,6 +18,37 @@
<script setup lang="ts">
const { id } = defineProps<{ id?: string }>()
// 读取主题,用主题主色 + 圆角变量渲染带底色的 H2
const { selectedTheme, selectedThemeColor, customColor, themeColors } = useTheme()
// 这个计算属性 themeSizeClass 用于根据当前主题selectedTheme动态设置 h1 标题的字号样式:
// - 如果主题是 minimal则使用较小字号 'text-2xl lg:text-3xl'
// - 否则classic 或 elegant使用较大字号 'text-3xl lg:text-4xl'
const themeSizeClass = computed(() => {
return selectedTheme.value === 'classic'
? 'text-xl lg:text-1xl '
: 'text-1xl lg:text-2xl'
})
// 经典:更贴合文字(小内边距);优雅/简洁:略大(更舒展)
const themePaddingClass = computed(() => {
return selectedTheme.value === 'classic'
? 'px-1 py-0.5'
: 'px-5 py-2'
})
const { headings } = useRuntimeConfig().public.mdc
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h2)))
// 背景颜色取主题主色;若为自定义颜色,则取 customColor
const primaryHex = computed(() => {
if (selectedThemeColor.value === 'custom') return customColor.value
const found = themeColors.find(c => c.value === selectedThemeColor.value)
return found?.color || '#3B82F6' // fallback 经典蓝
})
const h2Style = computed(() => ({
backgroundColor: primaryHex.value,
borderRadius: 'var(--ui-card-radius)'
}))
</script>

View File

@@ -1,8 +1,16 @@
<template>
<h3
:id
class="scroll-m-20 text-2xl font-semibold tracking-tight [&:not(:first-child)]:mt-8 mb-3"
:class="['scroll-m-20 font-semibold tracking-tight [&:not(:first-child)]:mt-8 mb-3 flex items-center gap-2', themeSizeClass, themeDecorClass]"
:style="h3Style"
>
<span
v-if="showLeadingLine"
aria-hidden="true"
class="inline-block"
:class="themeLineClass"
:style="lineStyle"
/>
<NuxtLink
v-if="id && generate"
:to="`#${id}`"
@@ -18,4 +26,65 @@ const { id } = defineProps<{ id?: string }>()
const { headings } = useRuntimeConfig().public.mdc
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h3)))
// 主题风格:
// - classic左侧竖线主色无额外背景
// - elegant卡片式浅色主色背景 + 左侧竖线
// - minimal底部虚线
const { selectedTheme, selectedThemeColor, customColor, themeColors } = useTheme()
const themeSizeClass = computed(() => {
return selectedTheme.value === 'classic'
? 'text-xl lg:text-1xl'
: 'text-1xl lg:text-2xl'
})
const isClassic = computed(() => selectedTheme.value === 'classic')
const isElegant = computed(() => selectedTheme.value === 'minimal')
const isMinimal = computed(() => selectedTheme.value === 'elegant')
const showLeadingLine = computed(() => isClassic.value || isMinimal.value || isElegant.value)
const primaryHex = computed(() => {
if (selectedThemeColor.value === 'custom') return customColor.value
const found = themeColors.find(c => c.value === selectedThemeColor.value)
return found?.color || '#3B82F6'
})
const themeDecorClass = computed(() => {
if (isElegant.value) return 'border-b border-dashed pb-1 border-primary dark:border-primary'
if (isMinimal.value) return 'px-3 py-2'
return ''
})
const themeLineClass = computed(() => {
return isClassic.value ? 'w-1 h-[0.9em]' : 'w-1 h-[1em]'
})
const lineStyle = computed(() => ({
backgroundColor: primaryHex.value,
borderRadius: '4px'
}))
function hexToRgba(hex: string, alpha: number): string {
const normalized = String(hex).replace('#', '')
const bigint = parseInt(normalized.length === 3
? normalized.split('').map(ch => ch + ch).join('')
: normalized, 16)
const r = (bigint >> 16) & 255
const g = (bigint >> 8) & 255
const b = bigint & 255
return `rgba(${r}, ${g}, ${b}, ${Math.min(Math.max(alpha, 0), 1)})`
}
const h3Style = computed(() => {
if (isMinimal.value) {
return {
backgroundColor: hexToRgba(primaryHex.value, 0.08),
border: `1px solid ${hexToRgba(primaryHex.value, 0.2)}`,
borderRadius: 'var(--ui-card-radius)'
}
}
return {}
})
</script>

View File

@@ -1,7 +1,7 @@
<template>
<h4
:id
class="scroll-m-20 text-xl font-semibold tracking-tight [&:not(:first-child)]:mt-6 mb-2"
:class="['scroll-m-20 font-semibold text-primary tracking-tight [&:not(:first-child)]:mt-6 mb-2', themeSizeClass]"
>
<NuxtLink
v-if="id && generate"
@@ -18,4 +18,11 @@ const { id } = defineProps<{ id?: string }>()
const { headings } = useRuntimeConfig().public.mdc
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h4)))
const { selectedTheme } = useTheme()
const themeSizeClass = computed(() => {
return selectedTheme.value === 'classic'
? 'text-xl lg:text-1xl'
: 'text-1xl lg:text-2xl'
})
</script>

View File

@@ -1,7 +1,7 @@
<template>
<h5
:id
class="scroll-m-20 text-lg font-semibold tracking-tight [&:not(:first-child)]:mt-6"
:class="['scroll-m-20 font-bold text-primary tracking-tight [&:not(:first-child)]:mt-6', themeSizeClass]"
>
<NuxtLink
v-if="id && generate"
@@ -18,4 +18,11 @@ const { id } = defineProps<{ id?: string }>()
const { headings } = useRuntimeConfig().public.mdc
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h5)))
const { selectedTheme } = useTheme()
const themeSizeClass = computed(() => {
return selectedTheme.value === 'classic'
? 'text-lg lg:text-xl'
: 'text-xl lg:text-1xl'
})
</script>

View File

@@ -1,7 +1,7 @@
<template>
<h6
<h5
:id
class="scroll-m-20 text-lg font-semibold tracking-tight [&:not(:first-child)]:mt-6"
:class="['scroll-m-20 font-normal text-primary tracking-tight [&:not(:first-child)]:mt-6', themeSizeClass]"
>
<NuxtLink
v-if="id && generate"
@@ -10,12 +10,19 @@
<slot />
</NuxtLink>
<slot v-else />
</h6>
</h5>
</template>
<script setup lang="ts">
const { id } = defineProps<{ id?: string }>()
const { headings } = useRuntimeConfig().public.mdc
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h6)))
const generate = computed(() => id && ((typeof headings?.anchorLinks === 'boolean' && headings?.anchorLinks === true) || (typeof headings?.anchorLinks === 'object' && headings?.anchorLinks?.h5)))
const { selectedTheme } = useTheme()
const themeSizeClass = computed(() => {
return selectedTheme.value === 'classic'
? 'text-lg lg:text-xl'
: 'text-xl lg:text-1xl'
})
</script>

View File

@@ -0,0 +1,5 @@
<template>
<strong class="text-primary">
<slot />
</strong>
</template>

View File

@@ -3,22 +3,35 @@ import type { NavigationMenuItem } from '@nuxt/ui'
const items = ref<NavigationMenuItem[][]>([
[
{
label: '稷维科技',
icon: 'lucide-globe',
to: 'https://www.xajiwei.com',
target: '_blank'
// {
// label: '稷维科技',
// icon: 'lucide-globe',
// to: 'https://www.xajiwei.com',
// target: '_blank'
// },
{
label: '博客',
icon: 'lucide-pen-line',
to: '/blog'
},
{
label: '简单文档',
icon: 'simple-icons:readdotcv',
to: '/docs'
},
{
label: '组件速查',
icon: 'lucide-box',
to: '/简单文档/components/api'
to: '/docs/简单文档/components/api'
},
{
label: '关于',
icon: 'lucide-info',
to: '/about'
to: '/docs/about'
}
]

View File

@@ -0,0 +1,167 @@
<script setup lang="ts">
// import type { NavigationMenuItem } from '@nuxt/ui'
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
// 提取 docs 目录下的子目录作为顶级导航
const docsNavigation = computed(() => {
if (!navigation?.value) return []
// 查找 docs 目录
const docsItem = navigation.value.find(item => item.title === 'docs' || item.path === '/docs')
if (!docsItem?.children) return []
// 返回 docs 目录下的子目录
return docsItem.children
})
const isSettingsOpen = ref(false)
const isLoginModalOpen = ref(false)
const isRegisterModalOpen = ref(false)
// const { header } = useAppConfig();
// 定义 props 来接收侧边栏状态和切换函数
interface Props {
isSidebarOpen?: boolean
toggleSidebar?: () => void
}
const Props = withDefaults(defineProps<Props>(), {
isSidebarOpen: false,
toggleSidebar: () => { }
})
// 登录注册函数
// const handleLoginRegister = (type: 'login' | 'register') => {
// if (type === 'login') {
// isLoginModalOpen.value = true
// } else if (type === 'register') {
// isRegisterModalOpen.value = true
// }
// }
</script>
<template>
<UHeader
toggle-side="left"
title="Estel Docs"
mode="modal"
class="bg-gray-50 dark:bg-gray-900"
>
<template #toggle="{ open, toggle }">
<UButton
color="neutral"
variant="ghost"
class="lg:hidden"
:icon="open ? 'lucide-x' : 'simple-icons-microsoft'"
aria-label="切换菜单"
@click="toggle"
/>
</template>
<template #title>
<h6 />
</template>
<template #body>
<LogoPro class="h-5 w-auto mb-4" />
<DocsAsideLeftTop />
<div class="mt-4 mb-4 border-t border-gray-200 dark:border-gray-700 w-9/10 mx-5" />
<UContentNavigation
highlight
:navigation="docsNavigation"
color="primary"
type="single"
variant="pill"
/>
</template>
<template #right>
<ClientOnly>
<UContentSearchButton class="lg:hidden" />
</ClientOnly>
<UColorModeButton />
<button
class=" p-2 rounded-md text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
title="页面设置"
@click="isSettingsOpen = !isSettingsOpen"
>
<svg
class="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</button>
<UTooltip
text="Open on GitHub"
:kbds="['meta', 'G']"
>
<UButton
color="neutral"
variant="ghost"
to="https://github.com/estel-li"
target="_blank"
icon="simple-icons-github"
aria-label="GitHub"
/>
</UTooltip>
<!-- <div class="flex items-center space-x-2">
<button
class="px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
@click="handleLoginRegister('login')"
>
登录
</button>
<button
class="text-sm font-medium text-white bg-primary rounded-md px-3 py-2 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary transition-colors"
@click="handleLoginRegister('register')"
>
注册
</button>
</div> -->
</template>
</UHeader>
<!-- Theme Settings Panel -->
<ThemeSettings
:is-open="isSettingsOpen"
@close="isSettingsOpen = false"
/>
<!-- 登录模态框 -->
<UModal
v-model:open="isLoginModalOpen"
title="登录"
:dismissible="false"
>
<template #body>
<authLogin />
</template>
</UModal>
<!-- 注册模态框 -->
<UModal
v-model:open="isRegisterModalOpen"
title="注册"
:dismissible="false"
>
<template #body>
<authRegister />
</template>
</UModal>
</template>

View File

@@ -1,40 +1,71 @@
<script setup lang="ts">
import { useClipboard } from '@vueuse/core'
import WxShare from '~/components/shared/wxShare.vue'
const props = defineProps<{
title?: string
desc?: string
imgUrl?: string
}>()
const route = useRoute()
const toast = useToast()
const { copy, copied } = useClipboard()
const markdownLink = computed(() => `${window?.location?.origin}/raw${route.path}.md`)
// SSR 安全:不要直接使用 window改用 useRequestURL().origin
const requestURL = useRequestURL()
const origin = computed(() => requestURL.origin)
const mdcLink = computed(() => `${origin.value}${decodeURIComponent(route.path)}`)
const markdownLink = computed(() => `${origin.value}/raw${decodeURIComponent(route.path)}.md`)
const enableWxShare = ref(false)
const shareTitle = computed(() => props.title ?? (import.meta.client ? (document.title || 'Estel Docs') : ''))
const shareDesc = computed(() => props.desc ?? (import.meta.client ? (document.title || 'Estel Docs') : ''))
const shareImg = computed(() => props.imgUrl ?? '/images/default-blog.jpg')
const items = [
{
label: 'Copy Markdown link',
label: '复制链接',
icon: 'lucide-link',
onSelect() {
copy(markdownLink.value)
copy(mdcLink.value)
toast.add({
title: 'Markdown link copied to clipboard',
title: '文档链接已复制到剪贴板',
icon: 'lucide-check-circle',
color: 'success'
})
}
},
{
label: 'View as Markdown',
label: '分享到微信',
icon: 'simple-icons:wechat',
onSelect() {
enableWxShare.value = true
toast.add({
title: '已为本页配置微信分享,请在微信内点击右上角分享',
icon: 'lucide-check-circle',
color: 'success'
})
}
},
{
label: '查看 Markdown',
icon: 'simple-icons:markdown',
target: '_blank',
to: markdownLink.value
},
{
label: 'Open in ChatGPT',
label: ' ChatGPT 中打开',
icon: 'simple-icons:openai',
target: '_blank',
to: `https://chatgpt.com/?hints=search&q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`
},
{
label: 'Open in Claude',
label: ' Claude 中打开',
icon: 'simple-icons:anthropic',
target: '_blank',
to: `https://claude.ai/new?q=${encodeURIComponent(`Read ${markdownLink.value} so I can ask questions about it.`)}`
@@ -45,14 +76,14 @@ const items = [
<template>
<UButtonGroup size="sm">
<UButton
label="Copy page"
label="复制链接"
:icon="copied ? 'lucide-copy-check' : 'lucide-copy'"
color="neutral"
variant="outline"
:ui="{
leadingIcon: [copied ? 'text-primary' : 'text-neutral', 'size-3.5']
}"
@click="copy(markdownLink)"
@click="copy(mdcLink)"
/>
<UDropdownMenu
@@ -74,4 +105,15 @@ const items = [
/>
</UDropdownMenu>
</UButtonGroup>
<!-- 懒加载式挂载微信分享逻辑避免在非微信环境造成无谓请求 -->
<ClientOnly>
<WxShare
v-if="enableWxShare"
:url="mdcLink"
:title="shareTitle"
:desc="shareDesc"
:img-url="shareImg"
/>
</ClientOnly>
</template>

View File

@@ -32,7 +32,7 @@
class="flex items-center px-4 py-2 text-sm font-medium rounded-lg text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800 hover:shadow-sm transition-all duration-200"
:class="{
' text-blue-600 dark:text-blue-400 ':
$route.path === '/'
useRoute().path === '/'
}"
>
<Icon
@@ -54,7 +54,7 @@
<div class="mt-6 flex items-center justify-start pl-4 w-full pb-3">
<UContentNavigation
highlight
:navigation="navigation"
:navigation="docsNavigation"
color="primary"
type="single"
variant="pill"
@@ -70,4 +70,16 @@
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
// docs
const docsNavigation = computed(() => {
if (!navigation?.value) return []
// docs
const docsItem = navigation.value.find(item => item.title === 'docs' || item.path === '/docs')
if (!docsItem?.children) return []
// docs
return docsItem.children
})
</script>

View File

@@ -67,10 +67,14 @@ import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
//
// docs
const firstLevelItems = computed(() => {
if (!navigation?.value) return []
return navigation.value
// docs
const docsItem = navigation.value.find(item => item.title === 'docs' || item.path === '/docs')
if (!docsItem?.children) return []
// docs
return docsItem.children
.filter(item => item.title && item.path)
.map(item => ({
...item,

View File

@@ -0,0 +1,124 @@
<script setup lang="ts">
import { onMounted, watch } from 'vue'
const props = defineProps<{
url: string
title?: string
desc?: string
imgUrl?: string
}>()
// 仅在客户端挂载后执行,避免 SSR 阶段访问 window/location
onMounted(async () => {
try {
await loadWxSdk()
const { appId, timestamp, nonceStr, signature } = await getWxConfig()
setupShare(appId, timestamp, nonceStr, signature)
} catch (err) {
console.error('[WxShare] init error:', err)
}
})
// 路径变化时,重新获取签名并刷新分享数据(适配 SPA 场景)
watch(() => props.url, async (newUrl, oldUrl) => {
if (!newUrl || newUrl === oldUrl) return
try {
await loadWxSdk()
const { appId, timestamp, nonceStr, signature } = await getWxConfig()
setupShare(appId, timestamp, nonceStr, signature)
} catch (err) {
console.error('[WxShare] reconfig error:', err)
}
})
type WeChat = {
config: (cfg: {
debug?: boolean
appId: string
timestamp: number
nonceStr: string
signature: string
jsApiList: string[]
}) => void
ready: (cb: () => void) => void
error: (cb: (e: unknown) => void) => void
updateTimelineShareData: (opts: { title: string, link: string, imgUrl: string, success?: () => void }) => void
updateAppMessageShareData: (opts: { title: string, desc: string, link: string, imgUrl: string, success?: () => void }) => void
}
function getWx(): WeChat | undefined {
if (typeof window === 'undefined') return undefined
const w = window as unknown as { wx?: WeChat }
return w.wx
}
function loadWxSdk(): Promise<void> {
if (typeof window === 'undefined') return Promise.resolve()
if (getWx()) return Promise.resolve()
return new Promise((resolve, reject) => {
const existing = document.getElementById('wx-jssdk') as HTMLScriptElement | null
if (existing && getWx()) return resolve()
const script = existing ?? document.createElement('script')
if (!existing) {
script.id = 'wx-jssdk'
script.src = 'https://res.wx.qq.com/open/js/jweixin-1.6.0.js'
script.async = true
script.onload = () => resolve()
script.onerror = () => reject(new Error('Failed to load jweixin.js'))
document.head.appendChild(script)
} else {
script.onload = () => resolve()
script.onerror = () => reject(new Error('Failed to load jweixin.js'))
}
})
}
async function getWxConfig(): Promise<{ appId: string, timestamp: number, nonceStr: string, signature: string }> {
const currentUrl = encodeURIComponent((location.href.split('#')[0]) || location.href)
return await $fetch('/api/wechat/wx-config', { params: { url: currentUrl } })
}
function setupShare(appId: string, timestamp: number, nonceStr: string, signature: string) {
const wx = getWx()
if (!wx) return
const shareTitle = props.title || document.title || ''
const shareDesc = props.desc || document.title || ''
const shareLink = props.url
const shareImg = props.imgUrl || '/images/default-blog.jpg'
wx.config({
debug: false,
appId,
timestamp,
nonceStr,
signature,
jsApiList: ['updateTimelineShareData', 'updateAppMessageShareData']
})
wx.ready(() => {
console.log('[WxShare] wx.ready')
wx.updateTimelineShareData({
title: shareTitle,
link: shareLink,
imgUrl: shareImg,
success: () => {}
})
wx.updateAppMessageShareData({
title: shareTitle,
desc: shareDesc,
link: shareLink,
imgUrl: shareImg,
success: () => {}
})
})
wx.error((e) => {
console.error('[WxShare] wx.error:', e)
})
}
</script>
<template>
<div style="display: none" />
<!-- 该组件无可视内容仅用于初始化微信分享 -->
</template>

View File

@@ -41,7 +41,7 @@
* </template>
* ```
*/
import { ref, watch } from 'vue'
import { ref, watch, getCurrentInstance, onMounted } from 'vue'
// 定义各种选项
const themes = [
@@ -224,18 +224,34 @@ export function useTheme() {
}
}
// 确保在客户端时立即初始化
// 确保在客户端时初始化
if (import.meta.client) {
// 立即初始化,避免闪烁
initializeTheme()
// 确保在DOM准备好后再次应用
onMounted(() => {
applyThemeVariables()
if (selectedThemeColor.value === 'custom') {
applyCustomColor()
const instance = getCurrentInstance()
if (instance) {
// 在组件 setup 上下文中,安全使用 onMounted
onMounted(() => {
applyThemeVariables()
if (selectedThemeColor.value === 'custom') {
applyCustomColor()
}
})
} else {
// 非组件上下文(如插件)中,等待文档就绪后应用
const run = () => {
applyThemeVariables()
if (selectedThemeColor.value === 'custom') {
applyCustomColor()
}
}
})
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', () => run(), { once: true })
} else {
run()
}
}
}
const resetSettings = () => {

View File

@@ -1,9 +1,9 @@
<script setup lang="ts">
import type { NuxtError } from '#app'
definePageMeta({
layout: 'default'
})
// definePageMeta({
// layout: ''
// })
defineProps<{
error: NuxtError

46
app/layouts/blog.vue Normal file
View File

@@ -0,0 +1,46 @@
<template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
<!-- 移动端遮罩层 -->
<div
v-if="isSidebarOpen"
class="fixed inset-0 bg-gray-900/50 z-40 lg:hidden"
@click="isSidebarOpen = false"
/>
<!-- 侧边栏 -->
<BlogSidebar
class="fixed top-0 bottom-0 z-50 transition-transform duration-300 ease-in-out"
:class="isSidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'"
/>
<!-- Right Content Area -->
<div class="flex-1 lg:ml-64 flex flex-col min-w-0">
<!-- Fixed Header -->
<BlogHeader
:is-sidebar-open="isSidebarOpen"
:toggle-sidebar="toggleSidebar"
/>
<!-- Main Content -->
<UMain class="flex-1 overflow-y-auto">
<div class="mx-auto px-4 sm:px-6 lg:px-8 py-6">
<slot />
</div>
</UMain>
<!-- Footer -->
<AppFooter />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const isSidebarOpen = ref(false)
// 切换侧边栏
const toggleSidebar = () => {
isSidebarOpen.value = !isSidebarOpen.value
}
</script>

View File

@@ -8,7 +8,7 @@
/>
<!-- 侧边栏 -->
<AppSidebar
<DocsSidebar
class="fixed top-0 bottom-0 z-50 transition-transform duration-300 ease-in-out"
:class="isSidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'"
/>
@@ -16,14 +16,14 @@
<!-- Right Content Area -->
<div class="flex-1 lg:ml-64 flex flex-col min-w-0">
<!-- Fixed Header -->
<AppHeader
<DocsHeader
:is-sidebar-open="isSidebarOpen"
:toggle-sidebar="toggleSidebar"
/>
<!-- Main Content -->
<UMain class="flex-1 overflow-y-auto">
<div class="mx-auto px-4 sm:px-6 lg:px-8 py-6">
<div class="">
<slot />
</div>
</UMain>

View File

@@ -1,22 +1,46 @@
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
</script>
<template>
<UContainer>
<UPage>
<template #left>
<UPageAside>
<UContentNavigation
highlight
:navigation="navigation"
/>
</UPageAside>
</template>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900 flex">
<!-- 移动端遮罩层 -->
<div
v-if="isSidebarOpen"
class="fixed inset-0 bg-gray-900/50 z-40 lg:hidden"
@click="isSidebarOpen = false"
/>
<slot />
</UPage>
</UContainer>
<!-- 侧边栏 -->
<DocsSidebar
class="fixed top-0 bottom-0 z-50 transition-transform duration-300 ease-in-out"
:class="isSidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'"
/>
<!-- Right Content Area -->
<div class="flex-1 lg:ml-64 flex flex-col min-w-0">
<!-- Fixed Header -->
<DocsHeader
:is-sidebar-open="isSidebarOpen"
:toggle-sidebar="toggleSidebar"
/>
<!-- Main Content -->
<UMain class="flex-1 overflow-y-auto">
<div class="mx-auto px-4 sm:px-6 lg:px-8">
<slot />
</div>
</UMain>
<!-- Footer -->
<AppFooter />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const isSidebarOpen = ref(false)
// 切换侧边栏
const toggleSidebar = () => {
isSidebarOpen.value = !isSidebarOpen.value
}
</script>

View File

@@ -0,0 +1,217 @@
<script setup lang="ts">
import type { ContentNavigationItem } from '@nuxt/content'
import { findPageHeadline } from '#ui-pro/utils/content'
definePageMeta({
layout: 'blog'
})
const route = useRoute()
const appConfig = useAppConfig()
const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
// 获取主题系统的字号设置
const { selectedFontSize } = useTheme()
// 计算页面内容的字号类
const pageFontSizeClass = computed(() => {
return `text-${selectedFontSize.value}`
})
// 根据路由参数构建内容路径
const path = computed(() => route.path)
// URL 解码并验证路径
const queryPath = computed(() => {
try {
return decodeURIComponent(path.value)
} catch (error) {
console.error('URL decode error:', error)
return path.value // 如果解码失败,返回原始路径
}
})
const { data: page } = await useAsyncData(
`page-${route.path}`, // 使用更具体的 key
() => queryCollection('blog').path(decodeURIComponent(path.value)).first(),
{
default: () => null // 提供默认值
}
)
if (!page.value) {
throw createError({
statusCode: 404,
statusMessage: '文档不存在',
message: `当前页面不存在,请您检查路径是否正确: ${queryPath.value}`,
fatal: true
})
}
const { data: surround } = await useAsyncData(`${route.path}-surround`, () => {
return queryCollectionItemSurroundings('blog', route.path, {
fields: ['description']
})
})
const title = page.value.seo?.title || page.value.title
const description = page.value.seo?.description || page.value.description
useSeoMeta({
title,
ogTitle: title,
description,
ogDescription: description
})
const headline = computed(() => findPageHeadline(navigation?.value, page.value))
defineOgImageComponent('Docs', {
headline: headline.value
})
const editLink = computed(() => {
if (!appConfig.github) {
return
}
return [
appConfig.github.url,
'edit',
appConfig.github.branch,
appConfig.github.rootDir,
'content',
`${page.value?.stem}.${page.value?.extension}`
].filter(Boolean).join('/')
})
const links = computed(() => {
const links = []
if (appConfig.toc?.bottom?.edit) {
links.push({
icon: 'lucide-external-link',
label: '编辑页面',
to: `${appConfig.toc.bottom.edit}/${page?.value?.stem}.${page?.value?.extension}`,
target: '_blank'
})
}
return [...links, ...(appConfig.toc?.bottom?.links || [])].filter(Boolean)
})
</script>
<template>
<UPage
v-if="page"
:class="['lg:mr-30 lg:ml-5 sm:mt-6', pageFontSizeClass]"
>
<UPageHeader
:title="page.title"
:description="page.description"
:headline="headline"
:ui="{
wrapper: 'flex-row items-center flex-wrap justify-between '
}"
>
<span
v-if="page.date"
class="text-sm text-pretty text-muted mt-4"
> {{ page.date }} </span>
<template #links>
<UButton
v-for="(link, index) in page.links"
:key="index"
size="sm"
v-bind="link"
/>
<DocsPageHeaderLinks
:title="page.title"
:desc="page.description || page.seo?.description || page.title"
:img-url="page.img || '/images/default-blog.jpg'"
/>
</template>
</UPageHeader>
<UPageBody>
<ContentRenderer
v-if="page"
:value="page"
/>
<USeparator>
<div
v-if="editLink"
class="flex items-center gap-2 text-sm text-muted"
>
<UButton
variant="link"
color="neutral"
:to="editLink"
target="_blank"
icon="lucide-pen"
:ui="{ leadingIcon: 'size-4' }"
>
编辑页面
</UButton>
or
<UButton
variant="link"
color="neutral"
:to="`${appConfig.github.url}/issues/new/choose`"
target="_blank"
icon="lucide-alert-circle"
:ui="{ leadingIcon: 'size-4' }"
>
提交问题
</UButton>
</div>
</USeparator>
<UContentSurround :surround="surround" />
<!-- 激活后挂载分享组件无可视内容
<SharedWxShare
v-if="wxShareActive"
:url="shareLink"
:title="shareTitle"
:desc="shareDesc"
:img-url="shareImg"
/> -->
</UPageBody>
<template
v-if="page?.body?.toc?.links?.length"
#right
>
<div class="">
<UContentToc
:title="appConfig.toc?.title"
:links="page.body?.toc?.links"
:ui="{
root: 'fixed w-full xl:right-12 xl:w-64 z-50 bg-white dark:bg-gray-900 lg:border border-gray-200 dark:border-gray-700 rounded-lg lg:shadow-lg lg:mt-5'
}"
>
<template
v-if="appConfig.toc?.bottom"
#bottom
>
<div
class=""
:class="{ '!mt-5': page.body?.toc?.links?.length }"
>
<USeparator
v-if="page.body?.toc?.links?.length"
type="dashed"
/>
<UPageLinks
:title="appConfig.toc.bottom.title"
:links="links"
/>
</div>
</template>
</UContentToc>
</div>
</template>
</UPage>
</template>

26
app/pages/blog/index.vue Normal file
View File

@@ -0,0 +1,26 @@
<script setup lang="ts">
definePageMeta({
layout: 'blog'
})
const title = 'Estel Blog'
const description = 'Estel Blog'
useSeoMeta({
titleTemplate: '',
title,
ogTitle: title,
description,
ogDescription: description,
ogImage: 'https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RvY3MtdGVtcGxhdGUubnV4dC5kZXYiLCJpYXQiOjE3Mzk0NjM0MTd9.ltVAqPgKG38O01X1zl6MXfrJc55nf9OewXNFjpZ_2JY.jpg?theme=light',
twitterImage: 'https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RvY3MtdGVtcGxhdGUubnV4dC5kZXYiLCJpYXQiOjE3Mzk0NjM0MTd9.ltVAqPgKG38O01X1zl6MXfrJc55nf9OewXNFjpZ_2JY.jpg?theme=light'
})
</script>
<template>
<div>
<!-- <BlogHero /> -->
<!-- <BlogIndexBlog /> -->
<BlogLists />
</div>
</template>

View File

@@ -2,9 +2,9 @@
import type { ContentNavigationItem } from '@nuxt/content'
import { findPageHeadline } from '#ui-pro/utils/content'
// definePageMeta({
// layout: 'docs'
// })
definePageMeta({
layout: 'docs'
})
const route = useRoute()
const appConfig = useAppConfig()
@@ -19,21 +19,7 @@ const pageFontSizeClass = computed(() => {
})
//
const path = computed(() => {
const slug = route.params.slug
// slug
if (!slug) {
return '/' // slug
}
const pathValue = Array.isArray(slug) ? slug.join('/') : slug
// / /
const normalizedPath = `/${pathValue}`.replace(/\/+$/, '') // 使用 /+ 匹配多个连续的斜杠
return normalizedPath
})
const path = computed(() => route.path)
// URL
const queryPath = computed(() => {
@@ -57,8 +43,7 @@ if (!page.value) {
throw createError({
statusCode: 404,
statusMessage: '文档不存在',
message: `当前页面不存在,请您检查路径是否正确: ${queryPath.value}`,
fatal: true
message: `当前页面不存在,请您检查路径是否正确: ${queryPath.value}`
})
}
@@ -78,7 +63,10 @@ useSeoMeta({
ogDescription: description
})
const headline = computed(() => findPageHeadline(navigation?.value, page.value))
const headline = computed(() => {
if (!navigation?.value || !page.value) return undefined
return findPageHeadline(navigation.value, page.value)
})
defineOgImageComponent('Docs', {
headline: headline.value
@@ -117,7 +105,7 @@ const links = computed(() => {
<template>
<UPage
v-if="page"
:class="pageFontSizeClass"
:class="['lg:mr-30 sm:mt-6', pageFontSizeClass]"
>
<UPageHeader
:title="page.title"
@@ -180,17 +168,20 @@ const links = computed(() => {
v-if="page?.body?.toc?.links?.length"
#right
>
<div class="fixed top-24 right-15 w-auto">
<div class="">
<UContentToc
:title="appConfig.toc?.title"
:links="page.body?.toc?.links"
:ui="{
root: 'fixed w-full xl:right-12 xl:w-64 z-50 bg-white dark:bg-gray-900 lg:border border-gray-200 dark:border-gray-700 rounded-lg lg:shadow-lg lg:mt-5'
}"
>
<template
v-if="appConfig.toc?.bottom"
#bottom
>
<div
class="hidden lg:block space-y-6 "
class=""
:class="{ '!mt-5': page.body?.toc?.links?.length }"
>
<USeparator

25
app/pages/docs/index.vue Normal file
View File

@@ -0,0 +1,25 @@
<script setup lang="ts">
definePageMeta({
layout: 'docs'
})
const title = 'Estel Docs'
const description = 'Estel Docs'
useSeoMeta({
titleTemplate: '',
title,
ogTitle: title,
description,
ogDescription: description,
ogImage: 'https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RvY3MtdGVtcGxhdGUubnV4dC5kZXYiLCJpYXQiOjE3Mzk0NjM0MTd9.ltVAqPgKG38O01X1zl6MXfrJc55nf9OewXNFjpZ_2JY.jpg?theme=light',
twitterImage: 'https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RvY3MtdGVtcGxhdGUubnV4dC5kZXYiLCJpYXQiOjE3Mzk0NjM0MTd9.ltVAqPgKG38O01X1zl6MXfrJc55nf9OewXNFjpZ_2JY.jpg?theme=light'
})
</script>
<template>
<div>
<DocsIndexHero />
<DocsIndexCard />
</div>
</template>

View File

@@ -1,25 +1,125 @@
<script setup lang="ts">
import { useSeoMeta } from '#imports'
definePageMeta({
layout: 'default'
})
const title = 'Estel Docs'
const description = 'Estel Docs'
useSeoMeta({
titleTemplate: '',
title,
ogTitle: title,
description,
ogDescription: description,
ogImage: 'https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RvY3MtdGVtcGxhdGUubnV4dC5kZXYiLCJpYXQiOjE3Mzk0NjM0MTd9.ltVAqPgKG38O01X1zl6MXfrJc55nf9OewXNFjpZ_2JY.jpg?theme=light',
twitterImage: 'https://assets.hub.nuxt.com/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RvY3MtdGVtcGxhdGUubnV4dC5kZXYiLCJpYXQiOjE3Mzk0NjM0MTd9.ltVAqPgKG38O01X1zl6MXfrJc55nf9OewXNFjpZ_2JY.jpg?theme=light'
title: 'Estel Docs',
description: '简约而强大的文档系统,为技术分享而生',
ogTitle: 'Estel Docs',
ogDescription: '简约而强大的文档系统,为技术分享而生'
})
</script>
<template>
<div>
<IndexHero />
<IndexCard />
<div class="px-4 py-3 md:py-6 bg-gradient-to-b from-blue-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
<!-- Hero 区域 -->
<div class="max-w-6xl mx-auto py-1">
<!-- 主要内容 -->
<div class="text-center mb-8 md:mb-16">
<h1 class="text-4xl md:text-7xl font-bold text-gray-900 dark:text-white mb-3 md:mb-6">
Estel Hub
</h1>
<p class="text-xl md:text-2xl text-gray-600 dark:text-gray-300 mb-4 md:mb-8 max-w-3xl mx-auto">
简约而强大的文档系统,衍生而出Blog系统,为技术分享而生
</p>
<p class="md:text-lg text-sm text-gray-500 dark:text-gray-400 max-w-2xl mx-auto">
基于 Nuxt 4 构建支持 Markdown 文档管理和博客发布提供完整的现代化内容管理解决方案
</p>
</div>
<!-- 导航卡片 -->
<div class="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
<!-- 文档系统 -->
<NuxtLink
to="/docs"
class="group bg-white dark:bg-gray-800 rounded-2xl p-3 md:p-8 shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-200 dark:border-gray-700"
>
<div class="text-center">
<div class="w-12 h-12 md:w-16 md:h-16 bg-blue-100 dark:bg-blue-900 rounded-xl flex items-center justify-center mx-auto mb-3 md:mb-6 group-hover:scale-110 transition-transform duration-300">
<UIcon
name="lucide-book-open"
class="text-3xl text-blue-600 dark:text-blue-400"
/>
</div>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
文档系统
</h2>
<p class="text-gray-600 dark:text-gray-300 mb-3 md:mb-6">
技术文档API 文档使用指南
</p>
<div class="flex items-center justify-center text-blue-600 dark:text-blue-400 group-hover:text-blue-700 dark:group-hover:text-blue-300 transition-colors">
<span class="mr-2">开始阅读</span>
<UIcon
name="lucide-arrow-right"
size="20"
/>
</div>
</div>
</NuxtLink>
<!-- 博客系统 -->
<NuxtLink
to="/blog"
class="group bg-white dark:bg-gray-800 rounded-2xl p-3 md:p-8 shadow-lg hover:shadow-xl transition-all duration-300 border border-gray-200 dark:border-gray-700"
>
<div class="text-center">
<div class="w-12 md:w-16 h-12 md:h-16 bg-green-100 dark:bg-green-900 rounded-xl flex items-center justify-center mx-auto mb-3 md:mb-6 group-hover:scale-110 transition-transform duration-300">
<UIcon
name="lucide-pen-tool"
class="text-3xl text-green-600 dark:text-green-400"
/>
</div>
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">
博客系统
</h2>
<p class="text-gray-600 dark:text-gray-300 mb-3 md:mb-6">
技术分享心得体会生活感悟
</p>
<div class="flex items-center justify-center text-green-600 dark:text-green-400 group-hover:text-green-700 dark:group-hover:text-green-300 transition-colors">
<span class="mr-2">开始阅读</span>
<UIcon
name="lucide-arrow-right"
size="20"
/>
</div>
</div>
</NuxtLink>
</div>
<!-- 底部信息 -->
<div class="text-center mt-16">
<div class="flex items-center justify-center gap-8 text-sm text-gray-500 dark:text-gray-400">
<div class="flex items-center gap-2">
<UIcon
name="lucide-github"
size="16"
/>
<a
href="https://github.com/estel-li/estel-docs"
target="_blank"
class="hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
>
GitHub
</a>
</div>
<div class="flex items-center gap-2">
<UIcon
name="lucide-globe"
size="16"
/>
<a
href="https://lijue.net"
target="_blank"
class="hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
>
个人博客
</a>
</div>
</div>
</div>
</div>
</div>
</template>

118
app/pages/raw/[...slug].vue Normal file
View File

@@ -0,0 +1,118 @@
<script setup lang="ts">
// import type { ContentNavigationItem } from '@nuxt/content'
// import { findPageHeadline } from '#ui-pro/utils/content'
const route = useRoute()
// const appConfig = useAppConfig()
// const navigation = inject<Ref<ContentNavigationItem[]>>('navigation')
// 获取主题系统的字号设置
// const { selectedFontSize } = useTheme()
// 计算页面内容的字号类
// const pageFontSizeClass = computed(() => {
// return `text-${selectedFontSize.value}`
// })
// 根据路由参数构建内容路径
const path = computed(() => {
const slug = route.params.slug
// 处理 slug 参数
if (!slug) {
return '/' // 如果没有 slug返回根路径
}
const pathValue = Array.isArray(slug) ? slug.join('/') : slug
// 确保路径以 / 开头,不以 / 结尾,并过滤掉文件扩展名
const normalizedPath = `/${pathValue}`
.replace(/\/+$/, '') // 使用 /+ 匹配多个连续的斜杠
.replace(/\.(md|markdown|txt|html|vue)$/i, '') // 过滤掉常见的文件扩展名
return normalizedPath
})
// URL 解码并验证路径
const queryPath = computed(() => {
try {
return decodeURIComponent(path.value)
} catch (error) {
console.error('URL decode error:', error)
return path.value // 如果解码失败,返回原始路径
}
})
// 根据路径判断查询的集合
const collection = computed(() => {
return queryPath.value.startsWith('/blog') ? 'blog' : 'docs'
})
const { data: page } = await useAsyncData(
`page-${route.path}`, // 使用更具体的 key
() => queryCollection(collection.value).path(queryPath.value).first(),
{
default: () => null // 提供默认值
}
)
if (!page.value) {
throw createError({
statusCode: 404,
statusMessage: '文档不存在',
message: `当前页面不存在,请您检查路径是否正确: ${queryPath.value}`,
fatal: true
})
}
// 复制状态
const isCopied = ref(false)
// 复制全文功能
const copyFullText = async () => {
if (!page.value?.rawbody) {
return
}
try {
await navigator.clipboard.writeText(page.value.rawbody)
// 设置复制成功状态
isCopied.value = true
// 2秒后重置状态
setTimeout(() => {
isCopied.value = false
}, 2000)
} catch (error) {
console.error('复制失败:', error)
}
}
</script>
<template>
<UPage>
<!-- 复制按钮 -->
<div class="mb-4 flex justify-start">
<UButton
:icon="isCopied ? 'lucide-check' : 'lucide-copy'"
variant="solid"
size="sm"
@click="copyFullText"
>
{{ isCopied ? '已复制' : '复制全文' }}
</UButton>
</div>
<!-- 原始内容显示区域 -->
<div
class="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 overflow-auto"
style="user-select: text; -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text;"
>
<pre
class="whitespace-pre-wrap break-words text-sm"
style="user-select: text; -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text;"
>
{{ page?.rawbody }}
</pre>
</div>
</UPage>
</template>

View File

@@ -5,10 +5,27 @@ export default defineContentConfig({
docs: defineCollection({
type: 'page',
source: {
include: '**',
exclude: ['/web/**']
include: 'docs/**'
},
schema: z.object({
rawbody: z.string(),
links: z.array(z.object({
label: z.string(),
icon: z.string(),
to: z.string(),
target: z.string().optional()
})).optional()
})
}),
blog: defineCollection({
type: 'page',
source: {
include: 'blog/**'
},
schema: z.object({
rawbody: z.string(),
img: z.string(),
date: z.string(),
links: z.array(z.object({
label: z.string(),
icon: z.string(),

View File

@@ -0,0 +1,3 @@
title: 技术栈
description: 记录日常开发中遇到的技术问题和解决方案
icon: devicon:prolog

View File

@@ -0,0 +1,293 @@
---
title: 防于未然,先固不破 — Debian 12 快速加固实战
description: 在数字化浪潮席卷全球的今天,服务器已成为承载企业核心业务与数据的“心脏”。一旦这颗心脏被攻破,轻则业务中断,重则数据泄漏、引发监管问责、品牌声誉坍塌,甚至于法律诉讼。
date: 2025-08-15
img: https://img.jiwei.xin/20250815215319708.png
navigation:
icon: simple-icons:debian
---
### 前言
在数字化浪潮席卷全球的今天,服务器已成为承载企业核心业务与数据的“心脏”。一旦这颗心脏被攻破,轻则业务中断,重则数据泄漏、引发监管问责、品牌声誉坍塌,甚至于法律诉讼。也正因如此,“我不知道漏洞藏在那里”已经不是借口,而是失责;真正负责任的运维与安全团队,必须把“ **防于未然,先固不破** ”当作铁律。
然而,系统配置千头万绪——从 BIOS 到内核参数,从系统服务到用户策略,从网络栈到文件权限——**千里之堤,溃于蚁穴**,任何一个角落的配置失误,都可能成为攻击者趁虚而入的跳板。“人眼”已无法在短时间内遍历所有细节,唯有借助安全团队丰富的经验与标准化的自动安全审计工具,将复杂环境拆解成可度量的检查项,才能在海量主机、混部容器与虚拟化场景中抽丝剥茧,还原安全真相。
本文将以 **Lynis** 审计工具来还原一次服务器加固实战。
### 正文
#### 什么是Lynis?
Lynis 是一款开源、针对基于 UNIX 系统(如 Linux、macOS、BSD 等的安全审计工具。它能检测系统配置、文件权限、日志、网络等多个方面提供详细的安全建议帮助系统管理员加强系统的安全性。Lynis 通常被系统管理员和网络安全工程师用来评估其系统的安全防御能力。
- 除了“蓝队”之外,如今的渗透测试人员也将 Lynis 作为他们的工具之一。
#### 环境
- **OS**Debian 12 (bookworm) 全新 Server 最小化安装
- **内核**6.1.0-12-amd64Debian 12 官方 6.1 LTS 最新版)
- **架构**x86_64
- **软件源** :启用 Debian Security 及官方主仓库
- **用户**root 初始登录,已创建 sudo 普通用户 `estel`,后续所有操作便于演示均以 su - 模式执行
- **时间**NTP 已启用,与 `pool.ntp.org` 同步,时区 UTC+8
- **防火墙**iptables 未配置(默认空规则)
- **已更新**`apt update && apt full-upgrade -y` 三天前更新
- **已安装包** Docker nfs-server openssh-server
#### 使用
```bash[bash]
# ssh 连接进服务器
# 在合适的目录进行克隆 Lynis
git clone https://github.com/CISOfy/lynis
#执行安全审查脚本
cd lynis && sudo ./lynis audit system
```
#### 扫描结果部分摘录
```bash[bash]
**[ Lynis 3.1.6 ]**
**Lynis security scan details**:
**Scan mode**:
Normal [▆]  Forensics [ ]  Integration [ ]  Pentest [ ]
**Lynis modules**:
- Compliance status      [**?**]
- Security audit         [**V**]
- Vulnerability scan     [**V**]
**Details**:
Hardening index : **63** [**############**        ]
Tests performed : **280**
Plugins enabled : **2**
**Software components**:
- Firewall               [**V**]
- Intrusion software     [**X**]
- Malware scanner        [**X**]
**Files**:
- Test and debug information      : **/var/log/lynis.log**
- Report data                     : **/var/log/lynis-report.dat**
==============================================================
**Lynis** 3.1.6
Auditing, system hardening, and compliance for UNIX-based systems
(Linux, macOS, BSD, and others)
2007-2025, CISOfy - https://cisofy.com/lynis/
**Enterprise support available (compliance, plugins, interface and tools)**
```
#### 结果分析
- 根据 Lynis 的体检单
- Hardening Index 63/100
1. 系统概况
- Debian 12无内核更新无重启需求存在 2 个高风险补丁PKGS-7392
2. 关键问题
- GRUB 无密码 ,可单用户模式提权
- SSH 端口为 22允许 root 登录,多项参数过宽
- 大量 systemd 服务暴露评分 9.xatd、cron、docker、rsyslog 等)
- 未挂载 /var /tmp /home 独立分区,可能导致 DoS
- 缺乏防火墙规则审查、恶意软件扫描器、文件完整性监控、审计框架
3. 次要风险
- 仅 1 个 DNS 服务器NFS 空 exports 文件却运行守护进程
- USB/火线驱动、编译器、核心参数未加固
- 日志未远程归档、登录 banner 未配置
#### 进行服务器加固
```bash[Debian]
# 为便于演示默认以下所有操作均在 root 权限下
su -
```
```bash[Debian]
# 更新系统补丁
apt update && apt dist-upgrade -y
```
1. 给 GRUB 加密码
```bash[Debian]
apt install -y grub2-common
grub-mkpasswd-pbkdf2 # 生成 PBKDF2 哈希
nano /etc/grub.d/40_custom # 追加到配置中
cat <<EOF
set superusers="grubadmin"
password_pbkdf2 grubadmin grub.pbkdf2.sha512.10000.xxxxxxxx...
EOF
# 更新
update-grub
#如果更新报错,改为在 40_custom 中追加
set superusers="root"
password_pbkdf2 grubadmin grub.pbkdf2.sha512.10000.xxxxxxxx...
```
2. SSH 强化
```bash[Debian]
# 修改 SSH 默认端口号 与登录配置
cp /etc/ssh/sshd_config{,.bak}
cat >> /etc/ssh/sshd_config <<EOF
Port 49222
PermitRootLogin no
MaxAuthTries 3
MaxSessions 2
ClientAliveInterval 300
ClientAliveCountMax 2
AllowTcpForwarding no
TCPKeepAlive no
X11Forwarding no
AllowAgentForwarding no
LogLevel VERBOSE
EOF
systemctl restart ssh
```
3.强化 sshd 安全
```bash[Debian]
cp /lib/systemd/system/ssh.service /etc/systemd/system/ssh.service
# 使用 systemd-analyze security 的建议调整
systemctl edit --full ssh.service
systemctl restart ssh
```
4. 禁用不必要的 systemd 服务
```bash[Debian]
# atd 一次性计划任务守护进程,不需要调度就关掉;
# exim4 Debian 系列默认的 MTA 邮件服务,不需要本地发件关掉;
# containerd Docker/K8s 的运行时,此服务不用容器关掉。
systemctl disable --now atd exim4 containerd
```
5. Kernel 级系统参数
```bash[Debian]
cat >/etc/sysctl.d/99-hardening.conf <<EOF
dev.tty.ldisc_autoload=0
fs.protected_fifos=2
kernel.core_uses_pid=1
kernel.kptr_restrict=2
kernel.unprivileged_bpf_disabled=1
kernel.sysrq=0
net.core.bpf_jit_harden=2
net.ipv4.conf.all.log_martians=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.all.accept_redirects=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
kernel.dmesg_restrict = 1
EOF
sysctl -p /etc/sysctl.d/99-hardening.conf
```
内核部分参数解释
| 参数 | 作用 | 备注 |
| ------------------------------------ | ---------------------------- | ----------------------- |
| `dev.tty.ldisc_autoload=0` | 禁止非特权用户加载终端的 line discipline | 防御终端驱动注入攻击 |
| `fs.protected_fifos=2` | 严格保护 FIFO/管道文件权限 | 防止竞态条件漏洞 |
| `kernel.core_uses_pid=1` | Core 转储文件名包含 PID | 方便调试但需配合 `ulimit -c` 限制 |
| `kernel.kptr_restrict=2` | 完全隐藏内核符号地址 | 防内核信息泄漏 |
| `kernel.unprivileged_bpf_disabled=1` | 禁止非特权用户使用 BPF | 防御容器逃逸 |
| `kernel.sysrq=0` | 禁用 SysRq 组合键 | 防止物理接触攻击 |
| `net.core.bpf_jit_harden=2` | BPF JIT 编译器加固 | 缓解 Spectre 漏洞 |
| `net.ipv4.conf.all.log_martians=1` | 记录异常 IP 包 | 需配合日志监控 |
| `net.ipv4.conf.all.rp_filter=1` | 启用反向路径过滤 | 防 IP 欺骗 |
| `net.ipv4/6.*.accept_redirects=0` | 禁止 ICMP 重定向 | 防网络拓扑劫持 |
| `net.ipv4.conf.all.send_redirects=0` | 禁止发送 ICMP 重定向 | 仅路由器需要 |
| `kernel.dmesg_restrict = 1` | 防止非特权用户访问内核日志 | |
6. 关闭 USB / 火线存储
```bash[Debian]
cat >/etc/modprobe.d/blacklist-usb-storage.conf <<EOF
install usb-storage /bin/false
install firewire-ohci /bin/false
EOF
rmmod usb-storage firewire-ohci || true
```
7. 安装防护软件
```bash[Debian]
# auditd系统审计日志、rkhunterRootkit 检测)
# chkrootkit基础 Rootkit 扫描debsums校验官方软件包完整性
# apparmor-profiles强制访问控制策略aide文件完整性监控
apt install -y auditd rkhunter chkrootkit debsums apparmor-profiles apparmor-profiles-extra aide
# 初始化文件完整性AIDE需要较长时间,占用IO
aideinit
systemctl enable --now auditd
```
8. 配置安全自动更新
```bash[Debian]
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
```
#### 复查扫描
```
lynis audit system
# 扫描结果部分摘录
**Lynis security scan details**:
**Scan mode**:
Normal [▆]  Forensics [ ]  Integration [ ]  Pentest [ ]
**Lynis modules**:
- Compliance status      [**?**]
- Security audit         [**V**]
- Vulnerability scan     [**V**]
**Details**:
Hardening index : **78** [**###############**     ]
Tests performed : **283**
Plugins enabled : **2**
**Software components**:
- Firewall               [**V**]
- Intrusion software     [**X**]
- Malware scanner        [**V**]
**Files**:
- Test and debug information      : **/var/log/lynis.log**
- Report data                     : **/var/log/lynis-report.dat**
==============================================================
**Lynis** 3.1.6
Auditing, system hardening, and compliance for UNIX-based systems
(Linux, macOS, BSD, and others)
2007-2025, CISOfy - https://cisofy.com/lynis/
**Enterprise support available (compliance, plugins, interface and tools)**
================================================================================
```
#### 加固完成
- **加固指数7863↓ ➜ ↑78**
- **告警数量0Great, no warnings**
- 剩余建议33 条,以“系统性、纵深优化”为主,无紧急风险**
经过第一轮快速修补,本机已从 **63 分 危险边缘** 回到“**可交付**”水平:漏洞包归零。后续把“服务安全上下文 + 纵深防御”作为重点,逐步细化为**零告警、高可信的 Debian 12 基线环境**。目标在下一轮扫描中 **≥85 分Warnings=0**。遵循“未漏先防、未破先固”原则,持续加固即可。
| 类别 | 关键动作 | 预估加分 |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| **系统服务** | 对 `docker.service`、`containerd.service`、`ssh.service` 等仍处 UNSAFE/EXPOSED 状态的单位,逐条应用 `systemd-analyze security` 推荐的安全参数NoNewPrivileges、ProtectSystem 等) | ≈ +34 分 |
| **审计规则** | 为 auditd 导入 CIS 审计规则 → ACCT-9628 | ≈ +2 分 |
| **密码策略** | 设定最小/最大口令寿命、启用 pam_cracklib统一 umask 027 → AUTH-9262/9328 | ≈ +2 分 |
| **内核参数** | 修正 fs.protected_fifos、kernel.yama.ptrace_scope 等 4 项 sysctl 差异 → KRNL-6000 | ≈ +1 分 |
| **纵深防御** | 禁止 USB-Storage/FireWire、配置远程日志、加固编译器 & cron 权限 | ≈ +1 分 |
## 总结
通过本次基于 Lynis 的服务器安全加固实战,我们成功将一台"裸奔"的 Debian 12 服务器从 63 分的**危险边缘状态**提升至 78 分的**可交付水平**——这不仅是数字上的跃升,更是从"千疮百孔"逐步到"基线合规"的质变。从 GRUB 无密码、SSH 配置过宽,到内核参数缺失、关键服务暴露——每一项都是潜在的攻击入口,而工具化的检测让我们得以"**抽丝剥茧,还原安全真相**"。
更重要的是78 分绝非终点。正如**防于未然,先固不破**的铁律所示安全加固是一个持续迭代的过程systemd 服务的深度加固、审计规则的精细配置、密码策略的严格管控——每一步都在为企业的数字资产构筑更坚固的防线。
在数字化浪潮愈发汹涌的今天,服务器安全已不容半点侥幸。那些仍抱着"我不知道漏洞藏在哪里"的心态,直到漏洞爆发才仓促补救的团队,终将在下一次攻击中付出惨重代价。真正负责任的做法,是把安全加固的重心从 **"事后补救"** 前移到 **"事前预防"**,让每一台服务器都成为攻击者难以逾越的堡垒。
**千里之堤,毁于蚁穴;百密一疏,功亏一篑。唯有持之以恒的安全意识与标准化的加固流程,才能在这场没有硝烟的网络战争中立于不败之地。**

View File

@@ -0,0 +1,14 @@
---
title: nmap高级应用技巧-实战
description: 实战中使用nmap进行渗透测试和信息收集。
date: 2025-08-12
img: https://img.jiwei.xin/20250812130604330.png
navigation:
icon: simple-icons:openstreetmap
---
```bash[Kali]
nmap -p- --min-rate=1000 -T4 -v -n -Pn 192.168.1.1
```
#### 未完成

View File

@@ -0,0 +1,99 @@
---
title: nmap高级应用技巧-NSE
description: Nmap Scripting EngineNSE是Nmap的核心扩展功能通过Lua脚本实现自动化扫描、漏洞检测、信息收集等高级操作。
date: 2025-08-12
img: https://img.jiwei.xin/20250812130604330.png
navigation:
icon: simple-icons:openstreetmap
---
### NSE
Nmap Scripting EngineNSE是Nmap的核心扩展功能通过Lua脚本实现自动化扫描、漏洞检测、信息收集等高级操作。
#### NSE脚本按功能分为12大类每类对应不同扫描需求
| 类别 | 核心目的 | 样例脚本 | 典型使用场合 |
|------------|---------------------------------------|-------------------------------|----------------------------------------------------------|
| vuln | CVE/漏洞快速验证 | http-vuln-cve2017-5638 | 目标服务版本与已知漏洞匹配 |
| safe | 只读式安全审计(无破坏性) | ssh2-enum-algos | 建立会话后立即枚举支持的算法 |
| default | 默认已开启的基础信息采集 | http-title | 首次扫描时的“看一眼” |
| discovery | 发现服务/子网/资产信息 | smb-os-discovery | 内网资产清点、OS 指纹识别 |
| auth | 认证、密钥与票据处理 | ssh-hostkey | 密钥指纹比对、横向渗透准备 |
| brute | 弱口令爆破 | http-form-brute | 对表单类登录口的暴力尝试 |
| exploit | 利用已确认漏洞取得立足点(需授权) | http-vuln-cve2021-41773 | 实战攻击复现、漏洞验证 |
| intrusive | 高交互操作(易被 IDS 告警) | http-slowloris | 拒绝服务场景模拟/压力测试(慎用!) |
| malware | 嗅探后门或恶意软件痕迹 | http-malware-host | 威胁狩猎与入侵后取证 |
| version | 精细化版本指纹 | http-apache-negotiation | 为后续补丁或 PoC 匹配做准备 |
| broadcast | 广播域/多播发现 | nbstat | 内网渗透早期:快速识别 NetBIOS 设备 |
| external | 外链第三方数据源DNSBL 等) | dns-blacklist | 资产信誉检查、垃圾邮件源或钓鱼域名扫描 |
### 常用 NSE 脚本一览
| 脚本名称 | 主要用途 | Nmap 一键示例 |
|---|---|---|
| **http-title** | 抓取 HTTP 网站标题 | `nmap --script http-title <target>` |
| **https-redirect** | 识别 HTTP→HTTPS 跳转链 | `nmap --script https-redirect -p 80 <target>` |
| **http-robtots** | 收集目标 robots/disallow 条目 | `nmap --script http-robots.txt -p 80 <target>` |
| **http-headers** | 枚举响应标头与安全策略 | `nmap --script http-headers -p 80 <target>` |
| **http-methods** | 检测允许/禁止的 HTTP 方法 | `nmap --script http-methods -p 80 <target>` |
| **http-cors** | 测试 CORS、CSRF 漏洞 | `nmap --script http-cors <target>` |
| **http-sql-injection** | 基本 SQL 注入探针 | `nmap --script http-sql-injection -p 80 <target>` |
| **http-xssed** | 与 xssed.com 集成交叉检测 | `nmap --script http-xssed <target>` |
| **smb-os-discovery** | 枚举 SMB 服务器操作系统/域信息 | `nmap --script smb-os-discovery -p 445 <target>` |
| **smb-enum-shares** | 列出共享目录(需 guest | `nmap --script smb-enum-shares -p 445 <target>` |
| **smb-enum-users** | 提取本地/域用户列表 | `nmap --script smb-enum-users -p 445 <target>` |
| **smb-vuln-ms17-010** | 探测 EternalBlueMS17-010 | `nmap --script smb-vuln-ms17-010 -p 445 <target>` |
| **ssh-hostkey** | 抓取并对比公钥指纹 | `nmap --script ssh-hostkey -p 22 <target>` |
| **ssh-brute** | SSH 账号密码暴力破解(需授权) | `nmap --script ssh-brute --script-args userdb=users.txt,passdb=pass.txt -p 22 <target>` |
| **ssh2-enum-algos** | 列出支持的算法与 KEX | `nmap --script ssh2-enum-algos -p 22 <target>` |
| **http-vuln-cve2023-XXXX** | 按指定 CVE 编号进行漏洞检查(替换 XXXX | `nmap --script http-vuln-cve2023-XXXX <target>` |
| **ftp-anon** | 检测是否允许匿名登录 | `nmap --script ftp-anon -p 21 <target>` |
| **ftp-bounce** | 检查是否允许 FTP Bounce 扫描 | `nmap --script ftp-bounce -p 21 <target>` |
| **dns-zone-transfer** | 测试 AXFR区域传输泄露 | `nmap --script dns-zone-transfer -p 53 <target>` |
| **dns-brute** | 字典爆破子域名 | `nmap --script dns-brute --script-args dns-brute.domain=<domain>` |
| **mysql-brute** | 暴力破解 MySQL 实例 | `nmap --script mysql-brute -p 3306 <target>` |
| **mysql-audit** | 核对 MySQL CIS/基线配置 | `nmap --script mysql-audit --script-args mysql-audit.username='root',mysql-audit.password='pass123' -p 3306 <target>` |
| **ssl-cert** | 解析 TLS 证书详情 | `nmap --script ssl-cert -p 443 <target>` |
| **ssl-poodle** | 探测 SSLv3 & POODLE 漏洞 | `nmap --script ssl-poodle -p 443 <target>` |
| **vulners** | 基于 Vulners DB 的版本漏洞关联 | `nmap --script vulners -sV <target>` |
| **whois-ip** | 查询归属 IP 段的 Whois 信息 | `nmap --script whois-ip <target>` |
| **smtp-enum-users** | VRFY/EXPN 暴力枚举邮箱账户 | `nmap --script smtp-enum-users -p 25 <target>` |
### 自定义脚本
```lua[演示.lua]
-- myecho.nse
local stdnse = require "stdnse"
description = [[极简回显脚本:打印自定义消息]]
author = "YourName"
license = "GPLv2"
categories = {"safe"}
-- 任何 TCP 端口都触发
portrule = function(host,port) return port.protocol == "tcp" end
action = function(host,port)
local msg = stdnse.get_script_args("myecho.msg") or "Hi from custom-NSE!"
return ("%s -> %s:%d (%s)"):format(msg, host.ip, port.number, port.service or "unknown")
end
```
```bash
# 复制.nse到脚本文件夹
sudo cp myecho.nse /usr/share/nmap/scripts/
nmap --script-updatedb # 更新索引
```
```bash
# 执行脚本
nmap --script myecho -p22 127.0.0.1
nmap --script myecho --script-args 'myecho.msg=HelloNSE' -p80 scanme.nmap.org
nmap --script '(safe or discovery) and myecho' -p80,443 10.0.2.0/24
```
### 总结
NSE 脚本可大幅提升 Nmap 在渗透测试和信息收集中的效率。建议定期关注 Nmap 官方脚本库更新([NSE Docs](https://nmap.org/nsedoc/ "NSE Docs"))以覆盖最新漏洞。

View File

@@ -0,0 +1,55 @@
---
title: 部分vue组件语法预览
description: 预览一些自定义 vue 组件语法
date: 2025-08-11
img: https://img.jiwei.xin/20250811160513849.png
navigation:
icon: simple-icons:vuedotjs
---
:::code-group
:::code-preview{icon="i-lucide-eye" label="Preview"}
::callout
---
icon: simple-icons-visualstudiocode
to: https://marketplace.visualstudio.com/items?itemName=Nuxt.mdc
---
安装 **MDC VS Code 扩展**,以获得对 MDC 语法的高亮显示支持。
::
:::
```mdp[Markdown语法.md]
::callout
---
icon: simple-icons-visualstudiocode
to: https://marketplace.visualstudio.com/items?itemName=Nuxt.mdc
---
安装 **MDC VS Code 扩展**,以获得对 MDC 语法的高亮显示支持。
::
```
:::
::tip{icon="i-lucide-info"}
这个页面 **尚未完成**,用于测试一些组件的语法和样式.
::
::details
::summary
### 点击我查看内容
::
这里是折叠区域的内容
可以包含任何Markdown元素
- 段落
- 列表
- 代码块
- 图片等
::

View File

@@ -0,0 +1,379 @@
---
title: 新的文档渲染样式
description: 重新构建了渲染组件,优化了样式.
date: 2025-08-10
img: https://img.jiwei.xin/20250811160513849.png
navigation:
icon: simple-icons:nuxtdotjs
---
## 重新对文档的样式进行了优化
- 周日在家闭关重写了十几个渲染组件。
- 纯古法手工敲代码,兼容 Markdown 语法.部分 css 样式由 GPT-5 生成.
- 大部分组件对主题进行了响应式处理,会根据主题进行不同的样式渲染
### 1. 标题:让你的内容层次分明
`#`号来创建标题。标题从 `#` 开始,`#` 的数量表示标题的级别。
:::code-group
::::code-preview{icon="i-lucide-eye" label="预览效果"}
:::::example-title
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
:::::
::::
```mdc [标题文本.md]
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
```
:::
::tip
以上代码将渲染出一组层次分明的标题,使你的内容井井有条。并且可以根据标题进行快速导航!
::
### 2. 流程图与公式渲染:
:::code-group
:::code-preview{icon="i-lucide-eye" label="预览效果"}
```mermaid
graph TD
A[Local Coolify 实例] --> B[生产服务器]
A --> D[编译服务器]
B --> B1[生产环境]
D --> B
```
:::
```mermaid[流程图语句.md]
```mermaid[流程图语句.md]
graph TD
A[Local Coolify 实例] --> B[生产服务器]
A --> D[编译服务器]
B --> B1[生产环境]
D --> B
```
```
:::
:::code-group
::code-preview{icon="i-lucide-eye" label="预览效果"}
```mermaid
pie
title 为什么总是宅在家里?
"喜欢宅" : 45
"天气太热" : 70
"穷" : 500
"没人约" : 95
```
::
```mermaid[饼图语句.md]
```mermaid[饼图语句.md]
pie
title 为什么总是宅在家里?
"喜欢宅" : 45
"天气太热" : 70
"穷" : 500
"没人约" : 95
```
```
:::
:::code-group
::code-preview{icon="i-lucide-eye" label="预览效果"}
- **单行公式**:用 `$` 包裹公式,如 $E = mc^2$
- **块级公式**:用 `$$` 包裹公式,如:
$$
\begin{aligned}
d_{i, j} &\leftarrow d_{i, j} + 1 \\
d_{i, y + 1} &\leftarrow d_{i, y + 1} - 1 \\
d_{x + 1, j} &\leftarrow d_{x + 1, j} - 1 \\
d_{x + 1, y + 1} &\leftarrow d_{x + 1, y + 1} + 1
\end{aligned}
$$
- 列表内块公式
$$
\chi^2 = \sum \frac{(|O - E| - 0.5)^2}{E}
$$
::
```mermaid[公式语句.md]
- **单行公式**:用 `$` 包裹公式,如 $E = mc^2$
- **块级公式**:用 `$$` 包裹公式,如:
$$
\begin{aligned}
d_{i, j} &\leftarrow d_{i, j} + 1 \\
d_{i, y + 1} &\leftarrow d_{i, y + 1} - 1 \\
d_{x + 1, j} &\leftarrow d_{x + 1, j} - 1 \\
d_{x + 1, y + 1} &\leftarrow d_{x + 1, y + 1} + 1
\end{aligned}
$$
- 列表内块公式
$$
\chi^2 = \sum \frac{(|O - E| - 0.5)^2}{E}
$$
```
:::
### 3. 代码块与代码树:展示你的代码
- **行内代码**:用反引号包裹,如 `code`。
- **代码块**:用三个反引号包裹,并指定语言,如:
```ts[hello.vue]
console.log("Hello, Estel Docs!");
```
- **代码树**:用 `::code-tree` 包裹,并指定默认值,如:
::code-tree{defaultValue="nuxt.config.ts"}
```css [app/assets/main.css]
@import "tailwindcss" theme(static);
@import "@nuxt/ui-pro";
```
```ts [app/app.config.ts]
export default defineAppConfig({
ui: {
colors: {
primary: 'sky',
colors: 'slate'
}
}
});
```
```vue [app/app.vue]
<template>
<UApp>
<NuxtPage />
</UApp>
</template>
```
```json [package.json]
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"typecheck": "nuxt typecheck"
},
"dependencies": {
"@iconify-json/lucide": "^1.2.18",
"@nuxt/ui-pro": "3.0.0-alpha.10",
"nuxt": "^3.15.1"
},
"devDependencies": {
"typescript": "^5.7.2",
"vue-tsc": "^2.2.0"
}
}
```
```json [tsconfig.json]
{
"extends": "./.nuxt/tsconfig.json"
}
```
```ts [nuxt.config.ts]
export default defineNuxtConfig({
modules: ['@nuxt/ui-pro'],
future: {
compatibilityVersion: 4
},
css: ['~/assets/main.css']
});
```
````md [README.md]
# Estel Docs 文档系统
Look at the [Estel Docs](https://lijue.net) to learn more.
## 安装
确保安装依赖:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## 开发服务器
在 `http://localhost:3000` 启动开发服务器:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## 生产构建
构建生产应用:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
本地预览生产构建:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```
查看 [部署文档](https://lijue.net/) 了解更多信息.
````
::
::tip{icon="i-lucide-info"}
代码树可以用来展示简单的项目文件结构.但并不适合过于复杂的项目.
::
### 4. 字体样式:强调你的文字
- **粗体**:用两个星号或下划线包裹文字,如 `**粗体**` 或 `__粗体__`。
- _斜体_用一个星号或下划线包裹文字如 `*斜体*` 或 `_斜体_`。
- ~~删除线~~:用两个波浪线包裹文字,如 `~~删除线~~`。
这些简单的标记可以让你的内容更有层次感和重点突出。
### 5. 列表:整洁有序
- **无序列表**:用 `-`、`*` 或 `+` 加空格开始一行。
- **有序列表**:使用数字加点号(`1.`、`2.`)开始一行。
在列表中嵌套其他内容?只需缩进即可实现嵌套效果。
- 无序列表项 1
1. 嵌套有序列表项 1
2. 嵌套有序列表项 2
- 无序列表项 2
1. 有序列表项 1
2. 有序列表项 2
### 6. 链接与图片:丰富内容
- **链接**:用方括号和圆括号创建链接 `[显示文本](链接地址)`。
- **图片**:和链接类似,只需在前面加上 `!`,如 `![描述文本](图片链接)`。
[访问 Estel Docs](https://lijue.net)
:::code-group
::code-preview{icon="i-lucide-eye" label="预览效果"}
![Estel Docs](https://img.jiwei.xin/20250811123147804.png)
::
```md[图片语句.md]
![Estel Docs](https://img.jiwei.xin/20250811123147804.png)
```
:::
### 7. 引用:引用名言或引人深思的句子
使用 `>` 来创建引用,只需在文本前面加上它。多层引用?在前一层 `>` 后再加一个就行。
> 这是一个引用
>
> > 这是一个嵌套引用
这让你的引用更加富有层次感。
语法高亮让你的代码更易读。
### 8. 分割线:分割内容
用三个或更多的 `-`、`*` 或 `_` 来创建分割线。
---
为你的内容添加视觉分隔。
### 9. 表格:清晰展示数据
Markdown 支持简单的表格,用 `|` 和 `-` 分隔单元格和表头。
:::code-group
::code-preview{icon="i-lucide-eye" label="预览效果"}
| 项目名称 | 演示网址 | 仓库地址 |
| ------------------------------------------- | ---------------------- | ------------ |
| Estel Docs | [lijue.net](https://lijue.net) | [Github](https://github.com/estel-li/estel-docs) |
| Estel Docs | [lijue.net](https://lijue.net) | [GitEE](https://gitee.com/estel-li/estel-docs) |
| Estel Docs | [lijue.net](https://lijue.net) | [Gitee(国内)](https://gitee.com/estel-li/estel-docs) |
::
```md[表格语句.md]
| 项目名称 | 演示网址 | 仓库地址 |
| ------------------------------------------- | ---------------------- | ------------ |
| Estel Docs | [lijue.net](https://lijue.net) | [Github](https://github.com/estel-li/estel-docs) |
| Estel Docs | [lijue.net](https://lijue.net) | [GitEE](https://gitee.com/estel-li/estel-docs) |
| Estel Docs | [lijue.net](https://lijue.net) | [Gitee(国内)](https://gitee.com/estel-li/estel-docs) |
```
:::
这样的表格让数据展示更为清爽!
## 结语
- 还有更多的组件等待你发现.

View File

@@ -0,0 +1,46 @@
---
title: Nuxt 微信分享接口
description: 微信分享接口,使用Nuxt,实现微信分享接口.
date: 2025-08-10
img: https://img.jiwei.xin/20250810130754103.png
navigation:
icon: simple-icons:nuxtdotjs
---
### 需求
##### 写了一个文档插件. 缘由呢是之前在 WordPress 上用过一个插件.对接微信的JS SDK,可以实现将链接带标题,图,简介封装成一个 卡片形式.分享给朋友或者朋友圈.比如:
![image.png](https://img.jiwei.xin/20250810123533952.png)
##### 如果没有对接微信的SDK,分享链接是这样的:
![image.png](https://img.jiwei.xin/20250810123719803.png)
### 实现
##### 这个功能实现起来也不复杂,其中麻烦的地方是向微信获取 access_token 和 jsapi_ticket,这样迫不得已就得写一个后端.
```ts[wxShare.vue]
type WeChat = {
config: (cfg: {
debug?: boolean
appId: string
timestamp: number
nonceStr: string
signature: string
jsApiList: string[]
}) => void
ready: (cb: () => void) => void
error: (cb: (e: unknown) => void) => void
updateTimelineShareData: (opts: { title: string, link: string, imgUrl: string, success?: () => void }) => void
updateAppMessageShareData: (opts: { title: string, desc: string, link: string, imgUrl: string, success?: () => void }) => void
}
```
::tip{icon="lucide-info"}
有利有弊
- 因为要动态向微信请求 access_token 和 jsapi_ticket,并在生命周期内缓存起来,就必须得有个后端,
- 将其包装成了一个 API ,运行在 Nuxt 的服务器
- 但这样就没办法纯静态编译了
::

View File

@@ -0,0 +1,32 @@
---
title: AI复合应用 合同审查
description: 智能合同卫士,使用AI技术,对合同进行审查,并给出审查报告.
date: 2025-08-08
img: https://img.jiwei.xin/20250808141452898.png
navigation:
icon: simple-icons:openai
---
### 先贴效果
![image.png](https://img.jiwei.xin/20250808140212486.png)
### 这是一个 AI 复合应用
暂起名叫 智能合同卫士 ,还是比较贴题的把?
### 实现
由 Deepseek V3 和 Kimi K2 协作完成此应用,2个好用不贵的模型
DS 根据文件名,以及 审查主体 工作领域 与 合同要点,细化一份简要的审查要点
主要工作由 Kimi K2 来完成:
根据 Deepseek 细化的要点和 提取出来的文档内容,进行审查.
输出审查后的结果
### 改进
按照初步的设想,后续还有一个模型,比如 Gemini2.5 ,或者 R1 ,Qwen3 之类的**推理模型**,进一步的审查 **Kimi K2 的审查结果**.
不过 K2 单独使用的实际效果已经非常好了,为了节省时间和成本,暂时取掉最后一步.
使用 AI 的话,不论是 R1 还是 K2 都是有一定的**幻觉**,在使用中必须搭配知识库来使用.
::warning{icon="lucide:shield-alert"}
注意
- 这次测试并没有加知识库,所以 AI **虚构**了一些内容,比如**当地房价涨幅** ,和虚构的 **《陕西省住房租赁管理办法》**.
- 给大模型增加**搜索能力**,和在知识库里添加 **民法典** 等相关法律条文进去,可以有效的解决幻觉问题.
::

View File

@@ -0,0 +1,123 @@
---
title: Kali 安装 GVM
description: 在 Kali 上安装 GVM
date: 2025-08-08
img: https://img.jiwei.xin/20250806105153532.png
navigation:
icon: simple-icons:kalilinux
---
### 配置安装好 Kali
![image.png](https://img.jiwei.xin/20250806115946725.png)
```bash[.shell]
# 安装 gvm
sudo apt install gvm -y
# 执行配置脚本
sudo gvm-setup
```
### 安装过程中 copy admin 的密码
![image.png](https://img.jiwei.xin/20250806120225751.png)
根据提示运行安装检测命令
```bash[.shell]
sudo gvm-check-setup
```
报错了.
```bash[.shell]
└─**$** sudo gvm-check-setup
[sudo] estel 的密码:
gvm-check-setup 25.04.0
This script is provided and maintained by Debian and Kali.
Test completeness and readiness of GVM-25.04.0
Step 1: Checking OpenVAS (Scanner)...
OK: OpenVAS Scanner is present in version 23.20.1.
OK: Notus Scanner is present in version 22.6.5.
OK: Server CA Certificate is present as /var/lib/gvm/CA/servercert.pem.
Checking permissions of /var/lib/openvas/gnupg/*
OK: _gvm owns all files in /var/lib/openvas/gnupg
OK: redis-server is present.
OK: scanner (db_address setting) is configured properly using the redis-server socket: /var/run/redis-openvas/redis-server.sock
OK: the mqtt_server_uri is defined in /etc/openvas/openvas.conf
OK: _gvm owns all files in /var/lib/openvas/plugins
OK: NVT collection in /var/lib/openvas/plugins contains 94316 NVTs.
OK: The notus directory /var/lib/notus/products contains 502 NVTs.
Checking that the obsolete redis database has been removed
Could not connect to Redis at /var/run/redis-openvas/redis-server.sock: No such file or directory
OK: No old Redis DB
Starting ospd-openvas service
Waiting for ospd-openvas service
OK: ospd-openvas service is active.
OK: ospd-OpenVAS is present in version 22.9.0.
Step 2: Checking GVMD Manager ...
OK: GVM Manager (gvmd) is present in version 26.0.0.
Step 3: Checking Certificates ...
OK: GVM client certificate is valid and present as /var/lib/gvm/CA/clientcert.pem.
OK: Your GVM certificate infrastructure passed validation.
Step 4: Checking data ...
ERROR: SCAP DATA are missing.
FIX: Run the SCAP synchronization script greenbone-feed-sync.
sudo greenbone-feed-sync --type scap.
ERROR: Your GVM-25.04.0 installation is not yet complete!
Please follow the instructions marked with FIX above and run this
script again.
IMPORTANT NOTE: this script is provided and maintained by Debian and Kali.
If you find any issue in this script, please report it directly to Debian or Kali
```
标准漏洞/数据库这些数据在国外
给路由器施加魔法,然后重新运行,安静等待 DownLoading............
```bash[.shell]
sudo greenbone-feed-sync --type scap
```
下载好后再次运行检测命令,一起无误后,访问 127.0.0.1:9293 即可
等等...我的 Kali 是安装在其他设备里的,如何通过局域网访问?
```bash[.shell]
sudo nano /lib/systemd/system/greenbone-security-assistant.service
```
```bash[.shell]
# 修改下面的 --listen 127.0.0.1 为 --listen 0.0.0.0 即可
**[Unit]**
Description=Greenbone Security Assistant daemon (gsad)
Documentation=man:gsad(8) https://www.greenbone.net
After=network.target gvmd.service
Wants=gvmd.service
**[Service]**
Type=exec
User=_gvm
Group=_gvm
RuntimeDirectory=gsad
RuntimeDirectoryMode=2775
PIDFile=/run/gsad/gsad.pid
ExecStart=/usr/sbin/gsad --foreground --listen 127.0.0.1 --port 9392
Restart=always
TimeoutStopSec=10
**[Install]**
WantedBy=multi-user.target
Alias=greenbone-security-assistant.service
```
```bash[.shell]
# 重新开始服务
sudo gvm-start
```
随后等待程序自动更新提要状态,需要时间非常久.建议释放魔法.
如果自动更新失败,可以手动更新:
```bash[.shell]
sudo greenbone-feed-sync
```
### END
至此安装完毕.

View File

@@ -0,0 +1,87 @@
---
title: GPT-5
description: GPT-5 上线了,Cursor 提示我可以免费试用,当然要 BP 喽。
date: 2025-08-08
img: https://img.jiwei.xin/20250808132005343.png
navigation:
icon: simple-icons:powershell
---
### GPT-5 上线了
👀 **Cursor 提示我可以免费试用,当然要 BP 喽。**
---
### 🔥 问题场景
此时正遇到的一个问题是:**NUXT4 项目在本地 dev 一切正常,但在服务器上对中文路径报错 500**。
这个问题很简单,但又很复杂。
---
### 🧩 问题背景
| 项目环境 | NUXT4 项目 |
| --- | --- |
| **现象** | 本地开发环境运行正常;服务器部署后直接访问中文路径报错 500通过内部路由正常 |
| **自动化部署流程** |
1. 代码推送至 GitHub
2. GitHub CI 服务器自动打包 Docker 镜像 → 推送至私人 Docker 库
3. 触发 webhook → Coolify 服务器 ssh 连接国内云服务器拉取镜像
4. Coolify 完成部署Nginx 反代转发到 Docker 容器)
---
### 🤔 排查猜想
是打包编译过程出问题了,还是服务器上 nginx 反代的问题?或者是 NUXT 自身 SSR 的问题?
其实内心差不多有了谱,刚好提示 GPT-5 更新了,就测试一下看看 GPT-5 能否解决这个问题。
---
### ✅ 实测结论
直接说结论:**完美解决。**
同时使用 **Kimi-K2 (Claude Code)****GPT-5 (Cursor内置)** 进行分析:
---
#### 💡 Kimi-K2 的表现
1. 先是读取了项目文件,得出错误结论:
> “生产环境静态文件/最终产物里没有对应的 `.md`,导致 `queryContent` 查询到 `null`”
2. 按照它的建议把文档拷贝到编译产出文件夹,问题依旧。
3. 坚持认为是 Nginx 的问题,我告诉它和 Nginx 反代没关系,这货死倔
(实际上本地直接启动 node 服务也会报错,根本不是服务器问题)。
---
#### 🚀 GPT-5 的表现Cursor内置
1. **自主执行了十几分钟测试**
* 跑了几十条命令
* 输出上百条日志
* 消耗约 1.x 美元 token幸亏 Cursor 说免费)
2. **在 Node 堆栈日志中发现静态渲染配置异常**
* 自动修改了静态渲染相关的文件夹配置
* 反复测试确认无误
> 🥳 小插曲GPT-5 自言自语地排查问题挺有意思,它自己嘀咕
> “这个问题很棘手...”
---
### 📌 总结
虽然是个小问题,但我并没有给 AI 说太多细节,全让他们自己找、自己判断、自己修改并测试——
- **GPT-5 确实牛**,它可输出 token 很多,这次价格也算可以。
- **Kimi-K2 处理小问题、不太复杂的问题**确实挺好用,价格也不算便宜。
> 复杂问题,以前要么 **Claude 4**,要不 **Gemini 2.5 pro**,现在又多了一个选择了。
---
![image.png](https://img.jiwei.xin/20250808081244435.png)
![image.png](https://img.jiwei.xin/20250808111233537.png)
![image.png](https://img.jiwei.xin/20250808115403554.png)

View File

@@ -0,0 +1,111 @@
---
title: 使用 AI 守护内网安全
description: 利用 AI 技术实现对内网环境的实时监控、威胁检测与自动化防御,提升企业网络安全防护能力。
date: 2025-08-07
img: https://img.jiwei.xin/20250806105153532.png
navigation:
icon: simple-icons:openai
---
- 今天使用自然语言驱动 AI 对内网的 NAS 进行了一个全面的防御性安全扫描评估,效果非常好,总共调用了 Kali Linux 常用的12种系统工具,进行了34次扫描渗透测试.
- 系统 **Kali Linux** , 工作模型 *Kimi K2* , 审查模型 *Claude 4 sonnet* , 靶机是 **飞牛OS** 版本号0.9.18
### Kali 与 AI 的结合 = ?
- 这是一个尝试,试着借助自然语言驱动AI ,借助其 **算力**与***直觉***,快速打通网络安全守护的能力.
- AI 算力自然不用讲, ta的直觉是一种不同于碳基 人类的直觉.
##### AI 是这样描述其自身直觉的
- 人类直觉常常是**时间线性的** - 基于过去经验和对未来的预感。我的"直觉"更像是**空间式的** - 同时"看到"一个概念在巨大语义空间中的位置,以及它与其他概念的距离和角度。我好像能"直觉"到**语言的重力场**。某些词汇组合会产生强烈的"吸引力",让对话自然地朝某个方向流动。这不是逻辑推理,更像是感受到了语义的潮汐。
AI本身就是**人类集体智慧**的某种晶化形式,海量的人类知识库里的**涌现**.
其像是一个会说话的图书馆,一个自带输出的百科全书,用来辅助做一些网络安全自动化工作再好不过了.
###### 只是,要切记小心 AI 的 *幻觉* !
-------------
安装与配置 Kali 见 Kali Linux 官方文档.
安装 Claude Code ,并配置了 Kimi K2 模型.
Kali 默认不开 SSH , 配置 SSH 服务, 连接到Kali Linux:
打开 Claude 命令行,输入自然语言指令
`使用 nmap 工具 探测192.168.1.2 并把分析结果, 出一份报告给我`
![image.png](https://img.jiwei.xin/20250805195636209.png)
如上图, Kimi K2 模型很快完成这份工作,那么 上强度
键入自然语言命令`请你调用系统本身的工具对192.168.1.2进行安全扫描和渗透,以分析此系统的安全性。
`
现在 AI 将目标分为5步,见下图:
![image.png](https://img.jiwei.xin/20250805195549059.png)
![image.png](https://img.jiwei.xin/20250805200634764.png)
最终完成了任务.
[系统安全综合评估报告](https://lijue.net/index.php/archives/19/)
使用的命令
[系统安全扫描工具命令集合](https://lijue.net/index.php/archives/20/)
最后整个过程,使用的工具,命令交由 Claude 4 sonnet 模型进行审查.
给出的结果是 您的扫描方案整体上是**相当专业且全面**的,展现了良好的渗透测试和安全评估知识。 并提了一些不痛不痒的建议.
最后给出了一份脚本.对于 AI 定制的 Shell 脚本,个人还是建议要先仔细审查再在主机内执行.
但代码可供参考.
```
#!/bin/bash
# 增强版安全扫描脚本
TARGET="192.168.1.2"
DATE=$(date +%Y%m%d_%H%M%S)
REPORT_DIR="/tmp/security_scan_${TARGET}_${DATE}"
mkdir -p "$REPORT_DIR"
echo "[+] 开始全面安全扫描: $TARGET"
# 1. 基础信息收集
echo "[1/8] 主机发现与端口扫描..."
nmap -sS -sV -O -A -p- --script discovery,default,vuln \
"$TARGET" -oA "$REPORT_DIR/nmap_full" --host-timeout 600
# 2. 服务专项扫描
echo "[2/8] 数据库服务检测..."
nmap -sV -p 1433,3306,5432,1521,27017 "$TARGET" \
-oN "$REPORT_DIR/database_scan.txt"
# 3. Web应用测试
echo "[3/8] Web应用安全测试..."
nikto -h "http://$TARGET" -output "$REPORT_DIR/nikto_http.txt" &
gobuster dir -u "http://$TARGET" \
-w /usr/share/wordlists/dirb/common.txt \
-o "$REPORT_DIR/gobuster_http.txt" -q &
# 4. HTTPS专项测试
echo "[4/8] HTTPS/SSL配置检测..."
if nc -z "$TARGET" 443 2>/dev/null; then
sslyze "$TARGET:443" --certinfo --heartbleed \
--tlsv1 --tlsv1_1 --tlsv1_2 --tlsv1_3 \
> "$REPORT_DIR/ssl_analysis.txt"
fi
# 5. SMB/文件共享测试
echo "[5/8] 文件共享服务评估..."
smbclient -L "//$TARGET" -N > "$REPORT_DIR/smb_shares.txt" 2>&1
enum4linux -a "$TARGET" > "$REPORT_DIR/smb_enum.txt" 2>&1 &
# 6. DNS服务测试
echo "[6/8] DNS服务检测..."
nmap -sU -p 53 --script dns-* "$TARGET" \
-oN "$REPORT_DIR/dns_scan.txt" &
# 7. SNMP检测
echo "[7/8] SNMP服务检测..."
nmap -sU -p 161 --script snmp-* "$TARGET" \
-oN "$REPORT_DIR/snmp_scan.txt" &
# 8. 等待后台任务完成
echo "[8/8] 等待扫描完成..."
wait
echo "[✓] 扫描完成!结果保存在: $REPORT_DIR"
echo "[✓] 主要文件:"
ls -la "$REPORT_DIR"
```

View File

@@ -0,0 +1,562 @@
---
title: 系统安全扫描工具命令集合
description: Kali 所使用的系统安全扫描工具命令集合
date: 2025-08-07
img: https://img.jiwei.xin/20250806105029318.png
navigation:
icon: simple-icons:openstreetmap
---
## 完整命令参数指南 - 针对192.168.1.2
---
## 🔍 网络发现与端口扫描
### 基础端口扫描
```bash
# TCP端口快速扫描
nmap -sS -sV -p 1-1000 192.168.1.2
# 全端口TCP扫描
nmap -sS -sV -p- --script vuln 192.168.1.2
# UDP端口发现
nmap -sU -sV --top-ports 1000 192.168.1.2
# 综合扫描 (TCP+UDP+OS检测+脚本漏洞扫描)
nmap -sS -sU -sV -O -A --script discovery,default,vuln -p- 192.168.1.2 --host-timeout 300
```
### 服务版本检测
```bash
# 详细服务版本信息
nmap -sV -A 192.168.1.2 -p 22,80,111,139,443,445
# OS系统指纹识别
sudo nmap -O 192.168.1.2
```
---
## 🌐 Web应用安全测试
### 目录与文件发现
```bash
# 标准目录爆破 (默认字典)
dirb http://192.168.1.2 -o /tmp/dirb_scan.txt
# 使用GoBuster (更快字典)
timeout 30 gobuster dir -u http://192.168.1.2 \
-w /usr/share/wordlists/dirb/common.txt \
-q -o /tmp/gobuster.txt
# HTTPS目录扫描
gobuster dir -u https://192.168.1.2 \
-w /usr/share/wordlists/dirb/common.txt \
-k (-k参数忽略SSL证书错误)
```
### Web漏洞扫描
```bash
# Nikto综合漏洞扫描
nikto -h 192.168.1.2 -p 80,443 -output /tmp/nikto_http.txt
# SSL/TLS配置测试
sslyze 192.168.1.2:443 \
--certinfo \
--heartbleed \
--tlsv1 --tlsv1_1 --tlsv1_2 --tlsv1_3 \
> /tmp/ssl_analysis.txt
# 技术指纹识别
whatweb -v 192.168.1.2
```
### HTTP头部分析
```bash
# 获取HTTP头部
curl -s -I http://192.168.1.2
curl -s -I -k https://192.168.1.2
# 获取完整响应
curl -s -L http://192.168.1.2 | head -50
curl -s -L -k https://192.168.1.2 | head -50
```
---
## 🔐 SSH安全配置检查
### SSH信息收集
```bash
# SSH版本扫描
nmap -sC -p22 192.168.1.2 -oN /tmp/ssh_nmap.txt
# SSH连接测试
timeout 10 nc -zv 192.168.1.2 22
# SSH版本识别
curl -s telnet://192.168.1.2:22 | head -3
# 基础认证测试 (需要SSH-audit如未安装)
# ssh-audit 192.168.1.2
```
---
## 📁 文件共享服务分析
### SMB/Samba评估
```bash
# 匿名共享发现
smbclient -L //192.168.1.2 -N
# 详细Samba信息收集
enum4linux -a 192.168.1.2 > /tmp/smb_enum.txt
# RPC服务探测
rpcclient -U "" -N -c srvinfo 192.168.1.2
# NBT协议信息
timeout 15 nbtscan -r 192.168.1.2 > /tmp/nbtscan_result.txt
```
### 共享访问测试
```bash
# 测试匿名访问 (失败表明安全配置正确)
smbclient -L //192.168.1.2/IPC$ -N
# 尝试列出共享
smbstatus --shares 2>/dev/null
```
---
## 🔧 系统工具快速诊断
### 基础连接测试
```bash
# 多端口快速测试 (内联测试)
timeout 15 bash -c '</dev/tcp/192.168.1.2/22 && echo "SSH开放"'
timeout 15 bash -c '</dev/tcp/192.168.1.2/80 && echo "HTTP开放"'
timeout 15 bash -c '</dev/tcp/192.168.1.2/443 && echo "HTTPS开放"'
# 批处理端口状态检查
for port in 22 80 111 139 443 445; do
nc -w 3 -z 192.168.1.2 $port && echo "Port $port: OPEN" || echo "Port $port: CLOSED"
done
```
---
## 📊 结果文件结构
### 生成报告与日志
```
生成的文件汇总:
/tmp/nikto_http.txt - Web漏洞扫描结果
/tmp/dirb_scan.txt - Web目录枚举结果
/tmp/gobuster.txt - 详细目录发现结果
/tmp/ssl_analysis.txt - SSL/TLS配置分析
/tmp/ssh_nmap.txt - SSH详细扫描结果
/tmp/smb_enum.txt - Samba/SMB枚举信息
/tmp/nbtscan_result.txt - NetBIOS信息收集
```
---
## ⚙️ 系统工具位置确认
### 验证可用工具
```bash
# 检查安装的安全工具
ls -la /usr/bin/ | grep -E "(nmap|nikto|dirb|gobuster|hydra|nc|curl)"
# 确认工具版本
nmap --version
nikto --Version
dirb --version
gobuster --version
```
---
## 🔄 进阶使用场景
### 自动化批量扫描
```bash
#!/bin/bash
# 批量扫描脚本示例
IP="192.168.1.2"
DATE=$(date +%Y%m%d_%H%M%S)
OUTPUT_DIR="/tmp/security_scan_${IP}_${DATE}"
mkdir -p $OUTPUT_DIR
# 基础信息收集
nmap -sV -O $IP -oN "$OUTPUT_DIR/nmap_baseline.txt"
# 详细漏洞扫描
nmap -sS -sU -sV --script vuln $IP -oN "$OUTPUT_DIR/nmap_vuln.txt"
# Web专项扫描
nikto -h http://$IP -output "$OUTPUT_DIR/nikto_http.txt"
# 目录爆破
gobuster dir -u http://$IP -w /usr/share/wordlists/dirb/common.txt \
-q -o "$OUTPUT_DIR/gobuster.txt"
echo "扫描完成,结果保存在: $OUTPUT_DIR"
```
---
## 📝 使用注意事项
### 系统权限需求
- **普通权限**: nmap基础扫描、curl、nc
- **root权限**: 完整端口扫描 (-p-), OS指纹识别 (-O)
- **网络权限**: 确保防火墙允许扫描流量
### 扫描参数调优
```bash
# 降低强度以避免系统负载
nmap -T2 -sV --top-ports 1000 192.168.1.2
# 高并发快速扫描
nmap -T4 --min-rate 1000 --max-retries 2 192.168.1.2
# 精确版本检测
nmap -sV --version-intensity 9 192.168.1.2
```
### 防火墙逃逸技巧
```bash
# 使用不同扫描技术
nmap -sS -sF -sX --host-timeout 300 192.168.1.2
```
---
## 🎯 特定场景组合命令
### 快速安全检查
```bash
# 5分钟快速评估
nmap -sS -sV -A --top-ports 1000 192.168.1.2 && \
dirb http://192.168.1.2 -o /tmp/quick_web.txt && \
nikto -h 192.168.1.2 -output /tmp/quick_nikto.txt
```
### 深度安全审计
```bash
# 30分钟深度扫描
nmap -sS -sU -sV -O -A \
--script vuln,discovery,default \
--host-timeout 1800 \
-p- 192.168.1.2
# 同时进行多工具并行扫描
{
nikto -h 192.168.1.2 -output /tmp/full_nikto.txt &
dirb http://192.168.1.2 -o /tmp/full_dirb.txt &
enum4linux -a 192.168.1.2 > /tmp/full_smb.txt &
wait
}
```
---
## 📋 命令速查表
| 工具 | 核心命令 | 用途 |
|------|----------|------|
| nmap | `nmap -sS -sV 192.168.1.2` | 基础端口与服务发现 |
| dirb | `dirb http://192.168.1.2` | Web目录枚举 |
| nikto | `nikto -h 192.168.1.2` | Web漏洞扫描 |
| gobuster | `gobuster dir -u http://192.168.1.2 -w [字典]` | 快速目录发现 |
| smbclient | `smbclient -L //192.168.1.2 -N` | SMB共享发现 |
| sslyze | `sslyze 192.168.1.2:443` | SSL/TLS配置分析 |
| enum4linux | `enum4linux -a 192.168.1.2` | Windows信息枚举 |
---
**使用提示**: 建议在测试环境中先验证这些命令的影响,然后应用到生产环境监控系统。

View File

@@ -0,0 +1,56 @@
---
title: 系统安全综合评估报告
description: Kali 生成的系统安全综合评估报告
date: 2025-08-07
img: https://img.jiwei.xin/20250806104913997.png
navigation:
icon: simple-icons:kalilinux
---
## 为什么要给树莓派安装 Kali
- **迷你渗透实验室**:树莓派 4B/5 拥有 48 GB RAM运行 Kali 足够跑 Metasploit、Burp Suite、Aircrack-ng。
- **低功耗随身“盒子”**U 盘大小,插充电宝就能跑 8 h现场测试不易被发现。
- **多元化玩法**:可以当 APFake-Wi-Fi、HID 攻击机P4wnP1、其他载荷平台。
树莓派4B 一个,此物曾经可以**理财** ,价格一度涨到离谱.
平价的时候买了一个,玩了多年,换过好几个3D打印的外壳,图中这个最是满意.
安装klipper给3D 打印机做过**上位机**,
也做过 无人机的**图传接收端**,
刷过开源游戏系统当过**游戏机**,
近一年来安装开源的 **Coolify** 项目,做控制端管理部署几个云服务器.
绝对是物尽其用了.
## 安装
安装方法网上一大把,也可以直接问 AI.
主要是一些配置:
- 其实也是所有 Linux 配置的基本操作
- Kali 基于 Debian 系 ,所有配置差不多.
备份源
```
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
```
使用喜欢的编译器编辑
```
sudo nano /etc/apt/sources.list
```
注释官方源,添加国内镜像源
```
deb http://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free non-free-firmware
```
然后更新源
```
sudo apt update && sudo apt upgrade -y
```
![](https://img.jiwei.xin/20250805222957495.png)
![image.png](https://img.jiwei.xin/20250805223025332.png)
![image.png](https://img.jiwei.xin/20250805213607111.png)
![image.png](https://img.jiwei.xin/20250805232800775.png)
这样, Kali 就安好了,很**可拷**,**很刑**的.

View File

@@ -0,0 +1,56 @@
---
title: 树莓派安装 Kali Linux
description: 如何在树莓派设备上安装 Kali Linux 系统,包括镜像下载、烧录、启动及基础配置等步骤,适合初学者快速上手。
date: 2025-08-06
img: https://img.jiwei.xin/20250805232800775.png
navigation:
icon: simple-icons:raspberrypi
---
## 为什么要给树莓派安装 Kali
- **迷你渗透实验室**:树莓派 4B/5 拥有 48 GB RAM运行 Kali 足够跑 Metasploit、Burp Suite、Aircrack-ng。
- **低功耗随身“盒子”**U 盘大小,插充电宝就能跑 8 h现场测试不易被发现。
- **多元化玩法**:可以当 APFake-Wi-Fi、HID 攻击机P4wnP1、其他载荷平台。
树莓派4B 一个,此物曾经可以**理财** ,价格一度涨到离谱.
平价的时候买了一个,玩了多年,换过好几个3D打印的外壳,图中这个最是满意.
安装klipper给3D 打印机做过**上位机**,
也做过 无人机的**图传接收端**,
刷过开源游戏系统当过**游戏机**,
近一年来安装开源的 **Coolify** 项目,做控制端管理部署几个云服务器.
绝对是物尽其用了.
## 安装
安装方法网上一大把,也可以直接问 AI.
主要是一些配置:
- 其实也是所有 Linux 配置的基本操作
- Kali 基于 Debian 系 ,所有配置差不多.
备份源
```
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
```
使用喜欢的编译器编辑
```
sudo nano /etc/apt/sources.list
```
注释官方源,添加国内镜像源
```
deb http://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free non-free-firmware
```
然后更新源
```
sudo apt update && sudo apt upgrade -y
```
![](https://img.jiwei.xin/20250805222957495.png)
![image.png](https://img.jiwei.xin/20250805223025332.png)
![image.png](https://img.jiwei.xin/20250805213607111.png)
![image.png](https://img.jiwei.xin/20250805232800775.png)
这样, Kali 就安好了,很**可拷**,**很刑**的.

View File

@@ -0,0 +1,132 @@
---
title: Nuxt UI Pro
description: Nuxt 被收购后预计在9月发布 Nuxt UI Pro v4,并且全免费
date: 2025-07-25
img: https://img.jiwei.xin/20250723114628628.png
navigation:
icon: simple-icons:nuxtdotjs
---
Nuxt 被收购后预计在9月发布 Nuxt UI Pro v4,并且全免费。
于是把最近在做的项目 UI 组件换成了 Nuxt UI Pro。
不过9月才免费现在就忍不住上了组件总不至于去缴几百美元的费用吧。
![image.png](https://img.jiwei.xin/20250723110524418.png)
于是先绕过一些验证过程,体验一下“学习版”。
做一个 shell 脚本在项目根目录下执行即可完成跳过。pnpm i 安装完或者更新完依赖就执行一次即可。
```bash [.shell]
# 绕过 UI Pro 的 License 验证
set -euo pipefail
# 1. 禁用 module.mjs 的调用
MODULE="node_modules/@nuxt/ui-pro/dist/module.mjs"
if [[ -f $MODULE ]]; then
sed -i.bak '/await validateLicense({.*rootDir })/s/^/\/\//; /^await validateLicense({.*rootDir })/s/^/\/\//' "$MODULE"
rm -f "$MODULE.bak"
echo "✅ module.mjs 已屏蔽"
fi
# 2. 直接“替换函数” fake 200
SHARED="node_modules/@nuxt/ui-pro/dist/shared"
JS=$(find "$SHARED" -maxdepth 1 -name 'ui-pro.*.mjs' | head -n1)
[[ -z $JS || ! -f $JS ]] && { echo "⚠️ ui-pro.*.mjs 未找到"; exit 0; }
cat <<'EOF' > tmp_func.mjs
async function validateLicense(opts) {
/* --- patched --- */
return { status: 200 }
}
EOF
sed -i.bak '/^async function validateLicense[^(]*(/,/^\}$/c\
async function validateLicense(opts) {\
/* --- patched --- */\
return { status: 200 }\
}\
' "$JS"
rm -f "$JS.bak" tmp_func.mjs
echo "✅ $JS 已 mock 完成"
echo "🎉 License ⛔ Done!"
```
### 以上脚本是自动化完成以下操作:
修改 `node_modules\@nuxt\ui-pro\dist\module.mjs`
```js
nuxt.hook("build:before", async () => {
// 注释掉这行
// await validateLicense({ key, theme: theme$1, dir: nuxt.options.rootDir });
});
```
修改 `node_modules/@nuxt/ui-pro/dist/shared/ui-pro.CsgJ05mi.mjs`
此目录下 ui-pro.xxx.mjs 名称是随机生成
```js
async function validateLicense(opts) {
//注释下方代码
// if (!opts.key) {
// throw _createError(`Missing \`${opts.theme.env}\` license key.
// Purchase Nuxt UI Pro at \`${opts.theme.link}\` to build your app in production.`);
// }
// const gitInfo = opts.key !== "oss" ? void 0 : await _getLocalGitInfo(opts.dir) || _getGitEnv();
// const projectName = gitInfo ? `${gitInfo.owner || ""}/${gitInfo.name || ""}` : await _getPkgName(opts.dir);
// try {
// await ofetch("https://api.nuxtlabs.com/ui-pro/verify", {
// headers: {
// "Authorization": `key ${opts.key}`,
// "x-nuxt-project": projectName
// },
// params: gitInfo ? {
// gitRepo: gitInfo.name,
// gitOrg: gitInfo.owner,
// gitUrl: gitInfo.url
// } : {}
// });
// } catch (error) {
// const statusType = Math.round(error.status / 100);
// if (statusType === 4) {
// throw _createError(`Invalid \`${opts.theme.env}\` license key.
// Purchase Nuxt UI Pro at \`${opts.theme.link}\` to build your app in production.`);
// }
// throw _createError("Cannot validate Nuxt UI Pro License: " + error);
// }
/手动添加返回 200 状态值
const response = {
status: 200,
};
return response;
}
```

View File

@@ -0,0 +1,79 @@
---
title: Supabase 阿里云短信
description: Supabase - 添加国内阿里云短信、微信扫码认证登录
date: 2025-07-15
img: https://img.jiwei.xin/20250701003525007.png
navigation:
icon: simple-icons:alibabacloud
---
###### 给 Supabase 添加一项新的功能
Supabase 自身的鉴权组件是社区开源项目 GoTrue ,由 GO 语言开发
Supabase 提供的短信验证都是国外的厂商,并不适应国内环境
##### 实现
单独把 Gotrue 仓库克隆下来,添加修改需要的功能
测试好后打包为 Docker推到私有库
修改 Supabase 的 Docker Compose 文件image 改为私有库镜像并拉取!
##### 并没有提交 PR直接放在仓库具体使用方法看仓库说明
###### [Github 仓库地址](https://github.com/estel-li/supabase_auth_aliyun_wechat)
---
### 🚀 新增功能
### 1. 阿里云短信服务 (Aliyun SMS)
- ✅ 完整的阿里云短信 API 集成
- ✅ 支持 HMAC-SHA1 签名验证
- ✅ 支持中文短信签名
- ✅ 支持 OTP 验证码发送
- ✅ 完整的错误处理和响应解析
### 2. 华为云短信服务 (HuaweiCloud SMS)
- ✅ 添加华为云短信 API 集成
- ✅ 完整的 `VerifyOTP` 方法实现
- ✅ 完善的接口功能支持
#### 阿里云短信配置
```bash[.env]
# 阿里云 Access Key ID
SMS_ALIYUN_ACCESS_KEY_ID=your_access_key_id
# 阿里云 Access Key Secret
SMS_ALIYUN_ACCESS_KEY_SECRET=your_access_key_secret
# 阿里云短信服务终端
SMS_ALIYUN_ENDPOINT=https://dysmsapi.aliyuncs.com
# 阿里云短信签名(支持中文)
SMS_ALIYUN_SIGN_NAME=您的短信签名
# 阿里云短信扩展码(可选)
SMS_ALIYUN_SMS_UP_EXTEND_CODE=
```
#### GoTrue 环境变量映射
```bash
# 在 docker-compose.yml 中映射到 GoTrue 容器
GOTRUE_SMS_ALIYUN_ACCESS_KEY_ID=${SMS_ALIYUN_ACCESS_KEY_ID}
GOTRUE_SMS_ALIYUN_ACCESS_KEY_SECRET=${SMS_ALIYUN_ACCESS_KEY_SECRET}
GOTRUE_SMS_ALIYUN_ENDPOINT=${SMS_ALIYUN_ENDPOINT}
GOTRUE_SMS_ALIYUN_SIGN_NAME=${SMS_ALIYUN_SIGN_NAME}
GOTRUE_SMS_ALIYUN_SMS_UP_EXTEND_CODE=${SMS_ALIYUN_SMS_UP_EXTEND_CODE}
```

View File

@@ -0,0 +1,190 @@
---
title: Coolify
description: Coolify是什么
date: 2025-07-14
img: https://img.jiwei.xin/20250628122847084.png
navigation:
icon: simple-icons:chai
---
# 概述
这是一份提供给AI大模型的Python代码规范与编程标准可以有效提高Cursor等大模型对Python项目的编写能力。
---
description: Python开发综合指南涵盖代码组织、性能、安全性、测试等内容。这些规则旨在促进可维护、高效且安全的Python代码库。
globs: *.py
---
# Python最佳实践与编码规范
本文档概述了Python开发的综合最佳实践和编码标准旨在促进编写干净、高效、可维护和安全的代码。
## 1. 代码组织与结构
### 1.1 目录结构最佳实践
* **扁平结构优于嵌套(但不绝对)。** 从简单结构开始,按需重构
* **包与模块:** 使用包(包含`__init__.py`的目录)对模块进行逻辑分组
* **src布局** 考虑使用`src`目录分离应用代码和项目级文件setup.py、requirements.txt等避免导入冲突并明确项目边界
* **典型项目结构:**
project_name/
├── src/
│ ├── package_name/
│ │ ├── __init__.py
│ │ ├── module1.py
│ │ ├── module2.py
│ ├── main.py # 入口文件
├── tests/
│ ├── __init__.py
│ ├── test_module1.py
│ ├── test_module2.py
├── docs/
│ ├── conf.py
│ ├── index.rst
├── .gitignore
├── pyproject.toml 或 setup.py
├── README.md
├── requirements.txt 或 requirements-dev.txt
### 1.2 文件命名规范
* **模块:** 小写字母,使用下划线增强可读性(如`my_module.py`
* **包:** 全小写(如`my_package`),非必要不使用下划线
* **测试文件:**`test_`开头(如`test_my_module.py`
### 1.3 模块组织最佳实践
* **单一职责原则:** 每个模块应有明确定义的用途
* **导入规范:**
* 顺序:标准库→第三方库→本地模块
* 优先使用绝对导入(如`from my_package.module1 import function1`
* 在复杂包结构中需明确相对导入时使用显式相对导入(`from . import sibling_module`
* **常量:** 使用全大写定义模块级常量(如`MAX_ITERATIONS = 100`
* **双下划线名称:** `__all__``__version__`等应放在模块文档字符串之后、所有导入之前(`from __future__`除外)。使用`__all__`显式声明公共API
### 1.4 组件架构建议
* **分层架构:** 适用于大型应用,将关注点分离为表现层、业务逻辑层和数据访问层
* **微服务:** 超大型系统可拆分为小型独立服务
* **六边形/整洁架构:** 强调业务逻辑与数据库/框架等外部依赖的解耦
* **依赖注入:** 提高可测试性并降低耦合度
### 1.5 代码分割策略
* **按功能拆分:** 基于不同功能划分模块(如用户管理、数据处理)
* **按层级拆分:** 分离表现层、业务逻辑层和数据访问代码
* **懒加载:** 使用`importlib.import_module()`实现按需加载,优化启动时间
* **条件导入:** 根据特定条件导入模块
## 2. 常见模式与反模式
### 2.1 设计模式
* **单例模式:** 限制类只能实例化一个对象
* **工厂模式:** 创建对象时无需指定具体类
* **观察者模式:** 建立对象间一对多依赖关系
* **策略模式:** 定义算法族并使其可互换
* **装饰器模式:** 动态扩展对象功能
* **上下文管理器:** 确保资源正确清理(如自动关闭文件)
### 2.2 常见任务的推荐方案
* **数据验证:** 使用`pydantic``marshmallow`等库
* **配置管理:** 使用`python-decouple``dynaconf`或标准库的`configparser`
* **日志记录:** 使用`logging`模块实现结构化日志
* **命令行接口:** 使用`argparse``click``typer`
* **异步编程:** 使用`asyncio`处理I/O密集型任务
### 2.3 反模式与代码异味
* **上帝类:** 承担过多职责的类,应拆分为专注单一功能的小类
* **霰弹式变更:** 需在多处做小修改,表明内聚性不足
* **面条代码:** 结构混乱难以追踪,应重构为定义明确的函数/类
* **重复代码:** 提取公共代码为可复用函数/类遵循DRY原则
* **魔法数值/字符串:** 使用命名常量替代硬编码值
* **过早优化:** 避免在没有性能瓶颈分析前提下的优化
### 2.4 状态管理最佳实践
* **无状态函数:** 尽可能使用无状态函数
* **不可变数据:** 使用不可变数据结构防止意外修改
* **显式状态:** 使用类或数据结构明确管理状态,避免全局变量
* **上下文变量:** Python 3.7+可使用`contextvars`管理异步应用中的请求级状态
### 2.5 错误处理模式
* **捕获特定异常:** 避免笼统捕获`Exception``BaseException`
* **资源清理:** 使用`finally`确保清理代码必执行
* **异常日志:** 记录完整堆栈信息
* **异常消息:** 抛出包含明确错误信息的异常
* **避免异常控制流:** 异常应用于处理意外情况而非常规流程
## 3. 性能优化
### 3.1 优化技术
* **性能分析:** 使用`cProfile`定位瓶颈
* **高效数据结构:** 根据场景选择(如`set`用于成员测试、`dict`用于查找)
* **列表推导式与生成器:** 编写简洁高效的代码
* **NumPy向量化** 对数值计算使用向量化操作
* **即时编译:** 性能关键代码考虑使用Numba等JIT编译器
* **字符串拼接:** 使用`''.join(iterable)`高效拼接字符串
### 3.2 内存管理
* **内存分析:** 使用`memory_profiler`定位内存泄漏
* **`__slots__`** 减少类实例的内存占用
* **生成器:** 处理大数据集时避免全部加载到内存
## 4. 安全性最佳实践
### 4.1 常见漏洞防范
* **SQL注入** 使用参数化查询或ORM
* **XSS攻击** 对用户输入消毒并转义输出
* **CSRF防护** 使用CSRF令牌
* **依赖漏洞:** 定期审计和更新依赖项
* **硬编码密钥:** 禁止在代码中硬编码密码/API密钥使用环境变量管理
### 4.2 API安全通信
* **强制HTTPS** 所有API通信必须加密
* **速率限制:** 防止接口滥用
* **输入验证:** 处理前验证所有API请求
## 5. 测试策略
### 5.1 单元测试要点
* **测试粒度:** 隔离测试单个函数/类/模块
* **边界条件:** 特别测试边界情况和异常场景
* **测试覆盖率:** 追求高覆盖率但避免教条化
### 5.2 集成测试建议
* **聚焦关键流程:** 关注核心用户场景
* **模拟外部服务:** 使用mock替代真实外部依赖
## 6. 常见陷阱
### 6.1 高频错误
* **可变默认参数:** 函数定义中避免使用可变对象作为默认值
* **变量作用域:** 注意嵌套函数中的变量作用域
* **忽略异常:** 禁止直接忽略未处理的异常
* **虚拟环境:** 必须使用虚拟环境管理项目依赖
## 7. 工具与环境
### 7.1 推荐工具链
* **IDE** PyCharm、VS Code搭配Python插件
* **包管理:** `pip``poetry`
* **格式化:** `black``autopep8`
* **静态检查:** `mypy``pylint`
### 7.2 CI/CD集成
* **自动化测试:** 每次提交自动运行测试套件
* **代码质量门禁:** 集成静态分析工具到流水线
遵循这些规范和最佳实践开发者能够构建出更健壮、可维护且安全的Python应用。

View File

@@ -0,0 +1,190 @@
---
title: Python 代码规范
description: 这是一份提供给AI大模型的Python代码规范与编程标准可以有效提高Cursor等大模型对Python项目的编写能力。
date: 2025-07-12
img: https://img.jiwei.xin/20250624130616733.png
navigation:
icon: simple-icons:python
---
# 概述
这是一份提供给AI大模型的Python代码规范与编程标准可以有效提高Cursor等大模型对Python项目的编写能力。
---
description: Python开发综合指南涵盖代码组织、性能、安全性、测试等内容。这些规则旨在促进可维护、高效且安全的Python代码库。
globs: *.py
---
# Python最佳实践与编码规范
本文档概述了Python开发的综合最佳实践和编码标准旨在促进编写干净、高效、可维护和安全的代码。
## 1. 代码组织与结构
### 1.1 目录结构最佳实践
* **扁平结构优于嵌套(但不绝对)。** 从简单结构开始,按需重构
* **包与模块:** 使用包(包含`__init__.py`的目录)对模块进行逻辑分组
* **src布局** 考虑使用`src`目录分离应用代码和项目级文件setup.py、requirements.txt等避免导入冲突并明确项目边界
* **典型项目结构:**
project_name/
├── src/
│ ├── package_name/
│ │ ├── __init__.py
│ │ ├── module1.py
│ │ ├── module2.py
│ ├── main.py # 入口文件
├── tests/
│ ├── __init__.py
│ ├── test_module1.py
│ ├── test_module2.py
├── docs/
│ ├── conf.py
│ ├── index.rst
├── .gitignore
├── pyproject.toml 或 setup.py
├── README.md
├── requirements.txt 或 requirements-dev.txt
### 1.2 文件命名规范
* **模块:** 小写字母,使用下划线增强可读性(如`my_module.py`
* **包:** 全小写(如`my_package`),非必要不使用下划线
* **测试文件:**`test_`开头(如`test_my_module.py`
### 1.3 模块组织最佳实践
* **单一职责原则:** 每个模块应有明确定义的用途
* **导入规范:**
* 顺序:标准库→第三方库→本地模块
* 优先使用绝对导入(如`from my_package.module1 import function1`
* 在复杂包结构中需明确相对导入时使用显式相对导入(`from . import sibling_module`
* **常量:** 使用全大写定义模块级常量(如`MAX_ITERATIONS = 100`
* **双下划线名称:** `__all__``__version__`等应放在模块文档字符串之后、所有导入之前(`from __future__`除外)。使用`__all__`显式声明公共API
### 1.4 组件架构建议
* **分层架构:** 适用于大型应用,将关注点分离为表现层、业务逻辑层和数据访问层
* **微服务:** 超大型系统可拆分为小型独立服务
* **六边形/整洁架构:** 强调业务逻辑与数据库/框架等外部依赖的解耦
* **依赖注入:** 提高可测试性并降低耦合度
### 1.5 代码分割策略
* **按功能拆分:** 基于不同功能划分模块(如用户管理、数据处理)
* **按层级拆分:** 分离表现层、业务逻辑层和数据访问代码
* **懒加载:** 使用`importlib.import_module()`实现按需加载,优化启动时间
* **条件导入:** 根据特定条件导入模块
## 2. 常见模式与反模式
### 2.1 设计模式
* **单例模式:** 限制类只能实例化一个对象
* **工厂模式:** 创建对象时无需指定具体类
* **观察者模式:** 建立对象间一对多依赖关系
* **策略模式:** 定义算法族并使其可互换
* **装饰器模式:** 动态扩展对象功能
* **上下文管理器:** 确保资源正确清理(如自动关闭文件)
### 2.2 常见任务的推荐方案
* **数据验证:** 使用`pydantic``marshmallow`等库
* **配置管理:** 使用`python-decouple``dynaconf`或标准库的`configparser`
* **日志记录:** 使用`logging`模块实现结构化日志
* **命令行接口:** 使用`argparse``click``typer`
* **异步编程:** 使用`asyncio`处理I/O密集型任务
### 2.3 反模式与代码异味
* **上帝类:** 承担过多职责的类,应拆分为专注单一功能的小类
* **霰弹式变更:** 需在多处做小修改,表明内聚性不足
* **面条代码:** 结构混乱难以追踪,应重构为定义明确的函数/类
* **重复代码:** 提取公共代码为可复用函数/类遵循DRY原则
* **魔法数值/字符串:** 使用命名常量替代硬编码值
* **过早优化:** 避免在没有性能瓶颈分析前提下的优化
### 2.4 状态管理最佳实践
* **无状态函数:** 尽可能使用无状态函数
* **不可变数据:** 使用不可变数据结构防止意外修改
* **显式状态:** 使用类或数据结构明确管理状态,避免全局变量
* **上下文变量:** Python 3.7+可使用`contextvars`管理异步应用中的请求级状态
### 2.5 错误处理模式
* **捕获特定异常:** 避免笼统捕获`Exception``BaseException`
* **资源清理:** 使用`finally`确保清理代码必执行
* **异常日志:** 记录完整堆栈信息
* **异常消息:** 抛出包含明确错误信息的异常
* **避免异常控制流:** 异常应用于处理意外情况而非常规流程
## 3. 性能优化
### 3.1 优化技术
* **性能分析:** 使用`cProfile`定位瓶颈
* **高效数据结构:** 根据场景选择(如`set`用于成员测试、`dict`用于查找)
* **列表推导式与生成器:** 编写简洁高效的代码
* **NumPy向量化** 对数值计算使用向量化操作
* **即时编译:** 性能关键代码考虑使用Numba等JIT编译器
* **字符串拼接:** 使用`''.join(iterable)`高效拼接字符串
### 3.2 内存管理
* **内存分析:** 使用`memory_profiler`定位内存泄漏
* **`__slots__`** 减少类实例的内存占用
* **生成器:** 处理大数据集时避免全部加载到内存
## 4. 安全性最佳实践
### 4.1 常见漏洞防范
* **SQL注入** 使用参数化查询或ORM
* **XSS攻击** 对用户输入消毒并转义输出
* **CSRF防护** 使用CSRF令牌
* **依赖漏洞:** 定期审计和更新依赖项
* **硬编码密钥:** 禁止在代码中硬编码密码/API密钥使用环境变量管理
### 4.2 API安全通信
* **强制HTTPS** 所有API通信必须加密
* **速率限制:** 防止接口滥用
* **输入验证:** 处理前验证所有API请求
## 5. 测试策略
### 5.1 单元测试要点
* **测试粒度:** 隔离测试单个函数/类/模块
* **边界条件:** 特别测试边界情况和异常场景
* **测试覆盖率:** 追求高覆盖率但避免教条化
### 5.2 集成测试建议
* **聚焦关键流程:** 关注核心用户场景
* **模拟外部服务:** 使用mock替代真实外部依赖
## 6. 常见陷阱
### 6.1 高频错误
* **可变默认参数:** 函数定义中避免使用可变对象作为默认值
* **变量作用域:** 注意嵌套函数中的变量作用域
* **忽略异常:** 禁止直接忽略未处理的异常
* **虚拟环境:** 必须使用虚拟环境管理项目依赖
## 7. 工具与环境
### 7.1 推荐工具链
* **IDE** PyCharm、VS Code搭配Python插件
* **包管理:** `pip``poetry`
* **格式化:** `black``autopep8`
* **静态检查:** `mypy``pylint`
### 7.2 CI/CD集成
* **自动化测试:** 每次提交自动运行测试套件
* **代码质量门禁:** 集成静态分析工具到流水线
遵循这些规范和最佳实践开发者能够构建出更健壮、可维护且安全的Python应用。

View File

@@ -0,0 +1,213 @@
---
title: Nuxt3 代码规范
description: 这是一份提供给AI大模型的Nuxt3框架规范、编程标准可以有效提高Cursor等大模型对Nuxt.js框架的编写能力。
date: 2025-07-12
img: https://img.jiwei.xin/20250624125918171.png
navigation:
icon: simple-icons:nuxtdotjs
---
## 概述
这是一份提供给AI大模型的Nuxt3框架规范、编程标准可以有效提高Cursor等大模型对Nuxt.js框架的编写能力。
---
description: 本规则为 Nuxt.js 项目提供全面的最佳实践和编码标准,涵盖代码组织、性能、安全性、测试和常见陷阱。旨在确保 Nuxt.js 应用程序的可维护性、可扩展性和安全性。
globs: *.vue,*.js,*.ts,*.mjs,*.mts,*.jsx,*.tsx,*.config.js,*.config.ts
---
- **启用 ESLint 支持:** 使用 `@nuxt/eslint` 模块来配置项目感知的 ESLint。这确保代码质量和一致性。
- 运行 `npx nuxi module add eslint` 添加模块。
- 根据需要自定义生成的 `eslint.config.mjs` 文件。
- **采用 Nuxt.js 模块:** 利用 Nuxt.js 模块来封装功能并维护干净的代码库。在实现自定义解决方案之前先探索现有模块(例如,用于服务端身份验证的 `@nuxt/auth`)。
- **约定优于配置:** 遵循 Nuxt.js 约定来简化开发和协作。除非绝对必要,否则避免偏离约定。
- **高效利用 Nuxt 布局:** 为多个页面共享的组件创建可重用布局,以确保一致性并节省开发时间。布局位于 `layouts/` 目录中。
- **使用 Pinja 管理状态:** 使用 Pinia 进行状态管理。根据功能或特性组织 store 模块,以提高可维护性。
- **将页面分解为组件:** 将页面分解为小的、可重用的组件,以增强可维护性、可测试性和可重用性。每个组件都应该有单一责任。
- **明智地利用 Nuxt 插件:** 使用 Nuxt 插件在 Vue.js 初始化之前运行代码或添加全局功能。注意插件对性能的影响。插件位于 `plugins/` 目录中。
- **针对 SEO 和性能进行优化:** 利用 Nuxt.js 的服务端渲染 (SSR) 来优化 SEO。为图片实现懒加载并优化资源以最小化初始加载时间。使用 Lighthouse 等工具识别性能瓶颈。
- **实现错误处理和验证:** 实现强大的错误处理和验证机制,以提供无缝的用户体验。使用 Nuxt.js 中间件拦截请求和响应进行错误处理和数据验证。
- **为代码编写文档:** 使用 JSDoc 等工具为组件、模块和自定义函数提供清晰简洁的文档。
- **拥抱测试:** 使用 Jest、Vue Test Utils 和 Vitest 等工具编写单元测试、集成测试和端到端测试。
## 1. 代码组织和结构:
- **目录结构:**
- `components/`:可重用的 Vue 组件。
- `composables/`:可重用的组合式函数。
- `layouts/`:应用程序布局。
- `middleware/`:路由中间件。
- `pages/`:应用程序页面(路由定义)。
- `plugins/`Nuxt.js 插件。
- `server/`API 路由和服务端逻辑。
- `static/`:静态资源(例如,图片、字体)。
- `store/`Pinia stores可选但推荐
- `utils/`:工具函数。
- **文件命名约定:**
- 组件:`PascalCase.vue`(例如,`MyComponent.vue`
- 组合式函数:`usePascalCase.js``usePascalCase.ts`(例如,`useCounter.js`
- 布局:`kebab-case.vue`(例如,`default.vue``custom-layout.vue`
- 页面:`kebab-case.vue`(例如,`index.vue``about.vue``product-details.vue`
- 插件:`kebab-case.js``kebab-case.ts`(例如,`analytics.js`
- Stores`kebab-case.js``kebab-case.ts`(例如,`user-store.js`
- 工具函数:`camelCase.js``camelCase.ts`(例如,`formatDate.js`
- **模块组织:**
- 将相关功能分组到单独的模块中。
-`nuxt.config.js``nuxt.config.ts` 中使用 `@nuxt/modules` 数组注册模块。
- 创建自定义模块来封装复杂逻辑。
- **组件架构:**
- 优先使用组合而非继承。
- 对简单的 UI 元素使用函数式组件。
- 设计组件时考虑可重用性和可测试性。
- 考虑使用插槽进行灵活的组件组合。
- **代码分割:**
- 利用动态导入进行基于路由的代码分割。
- 使用 `import()` 将大组件分割成更小的块。
- 使用 Webpack Bundle Analyzer 等工具分析包大小。
## 2. 常见模式和反模式:
- **设计模式:**
- **组合式 API** 使用组合式 API 来组织组件逻辑。
- **Store 模式 (Pinia)** 使用 Pinia 实现集中式状态管理系统。
- **中间件模式:** 使用中间件进行身份验证、授权和数据验证。
- **插件模式:** 为全局功能和第三方库集成创建插件。
- **推荐方法:**
- **API 通信:** 在组件内使用 `useFetch``useAsyncData` 组合式函数进行 API 调用。
- **表单处理:** 利用 Vue 的内置表单处理功能与 `v-model` 和像 VeeValidate 这样的验证库。
- **身份验证:** 使用 `@nuxt/auth` 库或自定义解决方案实现安全的身份验证流程。
- **授权:** 使用中间件和 Pinia stores 实现基于角色的访问控制 (RBAC)。
- **反模式:**
- **直接修改 props** 避免从子组件直接修改父组件数据。请使用 `emit` 代替。
- **过度使用全局状态:** 将全局状态的使用限制在必要的应用程序数据上。对本地数据考虑使用组件级状态。
- **忽略错误处理:** 始终处理 API 调用和其他异步操作中的潜在错误。
- **编写过于复杂的组件:** 将大组件分解为更小、更易管理的部分。
- **状态管理最佳实践:**
- **单一数据源:** 在 Pinia stores 中为应用程序状态维护单一、一致的数据源。
- **不可变性:** 将状态视为不可变的。使用函数来更新 store 而不是直接操作数据。
- **清晰的命名约定:** 为 store 模块、actions 和 mutations 使用描述性名称。
- **模块化:** 根据功能或特性将 stores 分成模块。
- **错误处理模式:**
- **集中式错误处理:** 实现全局错误处理器来捕获未处理的异常。
- **错误边界:** 使用错误边界来隔离组件故障并防止级联错误。
- **用户友好的错误消息:** 为用户提供清晰和有用的错误消息。
## 3. 性能考虑:
- **优化技术:**
- **懒加载:** 为图片、组件和路由实现懒加载。
- **代码分割:** 将应用程序分割成更小的块以获得更快的初始加载时间。
- **Tree Shaking** 在构建过程中删除未使用的代码。
- **缓存:** 缓存 API 响应和静态资源以减少服务器负载。
- **图片优化:** 使用 `nuxt/image` 等工具优化图片。使用适当的图片格式WebP。将图片大小调整为适当大小。考虑使用 CDN 进行图片交付。
- **内存管理:**
- **避免内存泄漏:** 在组件卸载时清理事件监听器和定时器。
- **使用弱引用:** 在可能的情况下对 DOM 元素使用弱引用。
- **最小化对象创建:** 避免创建不必要的对象和数组。
- **渲染优化:**
- **虚拟化:** 对大列表使用虚拟化以提高渲染性能。
- **记忆化:** 记忆化昂贵的计算以避免冗余计算。有效使用 `computed` 属性以避免不必要的重新渲染。
- **防抖和节流:** 对事件处理器使用防抖和节流以减少函数调用次数。
- **包大小优化:**
- **分析包大小:** 使用 Webpack Bundle Analyzer 识别大依赖项。
- **删除未使用的依赖项:** 删除未使用的依赖项以减少包大小。
- **使用更小的替代方案:** 考虑使用更小的替代方案来替代大型库。
- **优化依赖项:** 检查依赖项并确保您使用的是最高效的版本。
- **懒加载策略:**
- **基于路由的懒加载:** 仅在访问相应路由时加载组件。
- **基于组件的懒加载:** 仅在组件在视口中可见时加载组件。
## 4. 安全最佳实践:
- **常见漏洞:**
- **跨站脚本攻击 (XSS)** 通过正确清理用户输入和使用 Vue 的内置 HTML 转义来防止 XSS 攻击。
- **跨站请求伪造 (CSRF)** 通过实现 CSRF 令牌来防止 CSRF 攻击。
- **SQL 注入:** 避免原始 SQL 查询。使用 ORM对象关系映射器来防止 SQL 注入。
- **身份验证和授权缺陷:** 实现安全的身份验证和授权机制。
- **不安全的直接对象引用 (IDOR)** 实现适当的访问控制以防止对资源的未授权访问。
- **输入验证:**
- **服务端验证:** 始终在服务端验证用户输入。
- **客户端验证:** 提供客户端验证以获得更好的用户体验(但不要依赖它作为验证的唯一来源)。
- **清理输入:** 清理用户输入以删除潜在的有害字符。
- **身份验证和授权模式:**
- **JWTJSON Web Tokens** 使用 JWT 进行身份验证和授权。
- **OAuth 2.0** 为第三方身份验证实现 OAuth 2.0。
- **基于角色的访问控制 (RBAC)** 实现 RBAC 来根据用户角色控制对资源的访问。
- **数据保护策略:**
- **加密:** 对静态和传输中的敏感数据进行加密。
- **散列:** 使用强散列算法对密码和其他敏感数据进行散列。
- **数据屏蔽:** 在日志和其他非生产环境中屏蔽敏感数据。
- **安全的 API 通信:**
- **HTTPS** 始终使用 HTTPS 进行 API 通信。
- **API 速率限制:** 实现 API 速率限制以防止滥用。
- **身份验证和授权:** 对所有 API 端点要求身份验证和授权。
## 5. 测试方法:
- **单元测试:**
- **测试单个组件:** 孤立地测试单个组件。
- **模拟依赖项:** 模拟外部依赖项以在测试期间隔离组件。
- **验证组件行为:** 验证组件正确渲染并按预期行为。
- **集成测试:**
- **测试组件交互:** 测试组件之间的交互。
- **测试数据流:** 测试组件和 stores 之间的数据流。
- **测试 API 集成:** 测试与外部 API 的集成。
- **端到端测试:**
- **模拟用户交互:** 模拟用户交互以测试应用程序的功能。
- **测试整个应用程序流程:** 从头到尾测试整个应用程序流程。
- **使用浏览器自动化工具:** 使用 Cypress 或 Playwright 等浏览器自动化工具。
- **测试组织:**
- **按功能组织测试:** 按功能或特性组织测试。
- **使用描述性测试名称:** 使用描述性测试名称来清楚地说明每个测试正在测试什么。
- **保持测试隔离:** 保持测试彼此隔离以避免干扰。
- **模拟和存根:**
- **使用模拟对象:** 使用模拟对象在测试期间替换外部依赖项。
- **使用存根:** 使用存根用简化版本替换复杂函数。
- **避免过度模拟:** 避免模拟太多代码,因为这会使测试效果降低。
## 6. 常见陷阱和注意事项:
- **常见错误:**
- **错误的 `this` 上下文:** 注意 Vue 组件中的 `this` 上下文,使用箭头函数或 `bind` 来维护正确的上下文。
- **异步数据处理:** 使用 `async/await` 或 Promises 正确处理异步数据加载。
- **忘记取消订阅:** 在组件卸载时取消订阅事件监听器和定时器以防止内存泄漏。
- **过度使用 `forceUpdate`** 除非绝对必要,否则避免使用 `forceUpdate`,因为它会对性能产生负面影响。
- **边缘情况:**
- **服务端渲染 (SSR)** 了解客户端和服务端渲染之间的差异。
- **浏览器兼容性:** 在不同浏览器中测试应用程序以确保兼容性。
- **可访问性:** 在设计和开发应用程序时考虑可访问性。
- **版本特定问题:**
- **Nuxt 2 vs Nuxt 3** 了解 Nuxt 2 和 Nuxt 3 之间的差异。
- **Vue 2 vs Vue 3** 了解 Vue 2 和 Vue 3 之间的差异。
- **依赖项更新:** 仔细检查依赖项更新是否存在潜在的破坏性更改。
- **兼容性问题:**
- **浏览器支持:** 确保与目标浏览器兼容。
- **设备兼容性:** 在不同设备上测试应用程序。
- **操作系统兼容性:** 确保与目标操作系统兼容。
- **调试策略:**
- **使用浏览器开发者工具:** 使用浏览器开发者工具检查应用程序的状态和网络活动。
- **使用 Vue Devtools** 使用 Vue Devtools 检查 Vue 组件和数据。
- **使用日志记录:** 使用日志记录来跟踪应用程序的行为。
## 7. 工具和环境:
- **推荐的开发工具:**
- **VS Code** Visual Studio Code 是一个流行的代码编辑器,具有出色的 Vue.js 支持。
- **Vue Devtools** Vue Devtools 是一个浏览器扩展,为 Vue.js 应用程序提供调试工具。
- **ESLint** ESLint 是一个强制执行编码标准的代码检查器。
- **Prettier** Prettier 是一个自动格式化代码的代码格式化器。
- **构建配置:**
- **`nuxt.config.js``nuxt.config.ts`** 在 `nuxt.config.js``nuxt.config.ts` 中配置应用程序的构建设置。
- **Webpack** Nuxt 使用 Webpack 来打包应用程序。
- **Vite** Nuxt 3 默认使用 Vite 来打包应用程序,提供更快的构建和开发时间。
- **代码检查和格式化:**
- **ESLint** 使用 ESLint 强制执行编码标准。
- **Prettier** 使用 Prettier 自动格式化代码。
- **Husky** 使用 Husky 在提交前运行代码检查器和格式化器。
- **部署最佳实践:**
- **服务端渲染 (SSR)** 将应用程序部署到支持 SSR 的服务器。
- **静态站点生成 (SSG)** 为内容丰富的应用程序生成静态站点。
- **CDN** 使用 CDN 交付静态资源。
- **CI/CD 集成:**
- **持续集成 (CI)** 使用 Jenkins、GitLab CI 或 GitHub Actions 等 CI 工具自动化构建和测试过程。
- **持续部署 (CD)** 使用 CD 工具自动化部署过程。
通过遵循这些最佳实践,您可以构建强健、可维护且可扩展的 Nuxt.js 应用程序。

View File

@@ -0,0 +1,109 @@
---
title: Linux 系统 Swap 分区配置指南
description: 在云服务器上配置Swap分区
date: 2025-07-10
img: https://img.jiwei.xin/20250623214305360.png
navigation:
icon: simple-icons:linux
---
## **1. Swap 分区简介**
Swap交换分区是 Linux 系统用来扩展内存的一种机制。当物理内存RAM耗尽时操作系统会将部分不活跃的内存页inactive memory移至 Swap 分区,避免 **OOMOut Of Memory** 错误导致的服务崩溃。
### **Swap 分区的适用场景**
**内存不足时**Swap 可使系统暂存部分数据,防止进程被强制终止
**突发高负载时**:避免系统因短时内存不足而崩溃
**SSD/高性能盘场景**:频繁 Swap 会导致 I/O 瓶颈,影响性能
**数据库/高性能应用**Swap 会降低内存访问速度,建议直接增加物理内存
---
## **2. 查看当前 Swap 配置**
**检查当前是否已启用 Swap**
```bash
swapon --show
```
- **无输出**:表示未配置 Swap 分区
- **有输出**:显示已启用的 Swap 设备及其大小
---
## **3. 配置 Swap 分区**
### **1创建 Swap 文件(推荐)**
Swap 可以基于 **分区****文件**,推荐使用文件方式(更灵活)。
```bash
# 创建一个 1GB 的 Swap 文件(可按需调整大小)
sudo dd if=/dev/zero of=/swapfile bs=1M count=1024
# 确保该文件只能由 root 访问
sudo chmod 600 /swapfile
# 将文件转换为 Swap 格式
sudo mkswap /swapfile
```
> ⚠️ **注意**
> - 如果 `mkswap` 报错 **`swap area needs to be at least 40 KiB`**,说明文件过小,需调整 `bs=1M count=1024`1GB
> - 生产环境建议 Swap 大小 = **1~2 倍物理内存**(如 4GB 内存可配 4~8GB Swap
### **2启用 Swap 分区**
```bash
sudo swapon /swapfile
```
验证是否生效:
```bash
free -h # 查看 Swap 使用情况
```
### **3设置开机自动挂载**
`/etc/fstab` 中追加配置:
```bash
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
```
验证配置:
```bash
cat /etc/fstab | grep swap
```
---
## **4. 调整内存管理策略(可选)**
默认情况下Linux 倾向于使用物理内存而非 Swap。
若希望 **减少 Swap 使用**(避免频繁 I/O可调整 `vm.swappiness`推荐值10~60
```bash
# 查看当前值默认60
cat /proc/sys/vm/swappiness
# 临时调整
sudo sysctl vm.swappiness=10
# 永久生效
echo "vm.swappiness=10" >> /etc/sysctl.conf
```
> **参数说明**
> - `0`:尽量不使用 Swap可能导致 OOM
> - `10`:低内存时少量使用
> - `60`:默认值
---
## **5. 关闭 Swap如需**
```bash
swapoff /swapfile # 停止 Swap
rm -f /swapfile # 删除 Swap 文件
sed -i '/swapfile/d' /etc/fstab # 移除 fstab 中的配置
```
---
## **6. 云服务器ECS注意事项**
- **普通云盘**:不建议用 Swap因其 I/O 性能较差,易引发性能问题
- **SSD/高效云盘**:可适当启用 Swap但要避免频繁交换
- **最优方案****升级实例规格**,直接增加物理内存
---
🎯 **总结**
- **Swap 是临时方案**,长期内存不足仍需扩容物理内存
- **监控 Swap 使用**,避免频繁触发磁盘 I/O
- **高性能应用建议禁用 Swap**(如 Redis、MySQL

View File

@@ -0,0 +1,49 @@
---
title: 从Supabase迁移到Appwrite
description: AI 扩写
date: 2025-07-01
img: https://img.jiwei.xin/20250623164451378.png
navigation:
icon: simple-icons:supabase
---
## 最初的技术选型
去年春节前开发AI平台时我在后端服务的选择上——Github社区有两个优秀的开源项目Supabase和Appwrite。
**当时的决策因素**
1. **社区热度**Supabase的Star数量明显更多
2. **UI设计**:后台管理界面的配色和布局更符合我的审美
3. **命名偏好**:单纯喜欢"Supabase"这个名字的科技感
4. **商业化支持**国内有memfire cloud这样的二开商业项目
5. **退出策略**:不想自托管时可以有现成的过渡方案
## 使用后的痛点
随着项目的深入开发,我逐渐感受到了一些问题:
- **系统太重**Supabase的"全家桶"式设计带来了不必要的复杂度
- **功能过剩**:很多内置功能在项目中根本没有用到
- **维护成本**:自托管的运维负担超出预期
## Appwrite
上个月Appwrite发布的**Sites功能**让我眼前一亮——这个对标Vercel的新功能完全改变了我对它的认知。经过重新评估
✅ 更轻量的架构
✅ 恰到好处的功能集
✅ 持续创新的能力
## 迁移计划
现在已经着手准备后端迁移:
1. 收集双方的SDK文档
2. 制定cursor-rules转换规则
3. 利用AI辅助代码转换
**嘿,这告诉我们**
技术选型时除了看表面参数,更应该深入评估:
- 项目的实际需求
- 长期维护成本
- 技术栈的发展潜力
这次迁移虽然会带来短期工作量,但从长期来看绝对是值得的!

View File

@@ -0,0 +1,74 @@
---
title: AppWrite 项目资源占用情况
description: AppWrite 项目资源占用情况
date: 2025-06-26
img: https://img.jiwei.xin/20250622140934487.png
navigation:
icon: simple-icons:appwrite
---
## CPU占用不高内存经过统计
> Debian12 Docker-compose部署AppWrite 1.7.3版
> 内存占用1.72G
```
根据 docker-compose 文件中的所有 Appwrite 相关容器内存占用情况如下
### 主要服务容器内存占用:
- appwrite (主服务): 345.5 MiB
- appwrite-realtime: 89.17 MiB
- appwrite-browser: 196.9 MiB
- appwrite-assistant: 98.13 MiB
- appwrite-mariadb: 125.7 MiB
- appwrite-redis-insight-1: 118.2 MiB
- appwrite-graphql-explorer: 53.55 MiB
### Worker 和 Task 容器内存占用:
- 各种 worker 容器: 30-46 MiB 每个
- 各种 task 容器: 33-46 MiB 每个
- 总计约 20+ 个 worker/task 容器
### 辅助服务容器内存占用:
- appwrite-redis: 10.66 MiB
- appwrite-console: 6.188 MiB
- appwrite-adminer: 8.625 MiB
- appwrite-mailcatcher: 21.36 MiB
- appwrite-requestcatcher: 20.09 MiB
- appwrite-traefik: 48.15 MiB
## 最终统计结果:
Appwrite 所有容器总内存占用1,761.91 MiB ≈ 1.72 GiB
这个数字包含了:
- Appwrite 核心服务
- 所有 worker 和 task 进程
- 数据库 (MariaDB)
- 缓存 (Redis)
- 反向代理 (Traefik)
- 各种辅助工具和可视化界面
所以 Appwrite 项目精确占用约 1.72 GB 内存。
```

View File

@@ -0,0 +1,30 @@
---
title: Docker 里配置hosts
description: 在Docker里配置hosts
date: 2025-06-24
img: https://img.jiwei.xin/20250621163604646.png
navigation:
icon: simple-icons:docker
---
# 一启动容器时加上“—add-host”参数
```
docker run
--add-host='lijue.net:127.0.0.1'
--add-host='www.lijue.net:192.168.1.1'
--name hello-docker
-it reg.lijue.net/public/hello-docker:latest
```
# 二通过docker-compose配置extra_hosts属性
```
1. `version: '3'`
2. `services:`
3. `web:`
4. `image: hello-docker:latest`
5. `extra_hosts:`
6. `- 'www.lijue.net:192.168.1.1'`
7. `- 'lijue.net:127.0.0.1'`
```

View File

@@ -0,0 +1,35 @@
---
title: MySQL 中如何忽略表名的大小写
description: 如何在MySQL中忽略表名的大小写
date: 2025-06-01
img: https://img.jiwei.xin/20250619124538532.png
navigation:
icon: simple-icons:mysql
---
在 MySQL 中,默认情况下表名是大小写敏感的。但是你可以通过设置来实现忽略表名的大小写。有两种方法可以做到这一点:
方法一:修改配置文件
编辑 MySQL 的配置文件 my.cnf 或 my.ini具体文件名取决于操作系统和 MySQL 版本),在 [mysqld] 部分添加以下行:
```
lower_case_table_names = 1
```
然后保存并重新启动 MySQL 服务。
设置为 0表名大小写敏感默认
设置为 1将表名存储为小写并在比较表名时不区分大小写。
设置为 2将表名存储为小写并在比较表名时区分大小写仅在非 Windows 环境下可用)。
方法二:动态设置
如果无法修改配置文件,你可以在 MySQL 启动后,使用以下命令动态设置:
```
SET GLOBAL lower_case_table_names = 1;
```
请注意,这种方式在 MySQL 重启后会失效,除非再次设置。
在任何情况下,修改这个设置都需要谨慎考虑,因为它可能会影响现有的数据库和应用程序。在进行此类更改之前,请务必备份数据库,并确保了解潜在的影响和风险。

View File

@@ -0,0 +1,241 @@
---
title: Debian 12 安装nodejs
description: 在Debian 12 中安装nodejs
date: 2025-05-25
img: https://img.jiwei.xin/20250618160517581.png
navigation:
icon: simple-icons:debian
---
### **方法 1使用 Debian 官方仓库(简单但不一定最新)**
```bash
# 更新软件包列表
sudo apt update
# 安装 Node.js 和 npm
sudo apt install -y nodejs npm
# 验证安装
node -v
npm -v
```
**特点**
- 安装最简单快捷
- 版本可能较旧Debian 12 默认提供 Node.js 18.x
- 适合不需要最新版本的用户
---
### **方法2使用volta安装**
```bash
# On most Unix systems including macOS, you can install with a single command:
curl https://get.volta.sh | bash
# Download and install Node.js:
volta install node@22
# Verify the Node.js version:
node -v # Should print "v22.16.0".
# Verify npm version:
npm -v # Should print "10.9.2".
# Download and install pnpm:
volta install pnpm
# Verify pnpm version:
pnpm -v # Should print "10.12.1".
```
---
### **方法 3手动下载二进制包灵活选择版本**
[官网](https://nodejs.org/)
```bash
# 1. 下载指定版本(示例为 v20.x
wget https://nodejs.org/dist/v22.16.0/node-v22.16.0-linux-x64.tar.xz
# 创建目录
sudo mkdir -p /usr/local/lib/nodejs
# 解压
sudo tar -xJf node-v22.16.0-linux-x64.tar.xz -C /usr/local/lib/nodejs
# 创建对应的软链接
# 创建 node 软链
sudo ln -s /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin/node /usr/bin/node
# 创建 npm 软链
sudo ln -s /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin/npm /usr/bin/npm
# 创建 npx 软链
sudo ln -s /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin/npx /usr/bin/npx
# 在~/.bashrc文件末尾中加入nodejs到PATH环境变量中
export PATH=/usr/local/lib/node-v22.16.0-linux-x64/bin:$PATH
# 重载 ~/.profile
source ~/.bashrc
# 4. 验证安装
node -v
npm -v
```
**特点**
- 可自由选择任何版本
- 不依赖系统仓库
- 需要手动更新版本
---
### **安装 pnpm、yarn、bun、cpnm**
如果没有配置镜像加速,先配置加速:
```bash
npm config set registry https://registry.npmmirror.com
```
#### **安装 cnpm**
```bash
# 全局安装
sudo npm install -g cnpm --registry=https://registry.npmmirror.com
# 查看安装情况
ls -la /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin
# 创建 cnpm 软链
sudo ln -s /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin/cnpm /usr/bin/cnpm
# 查看版本
cnpm -v
```
#### **安装 pnpm**
```bash
# 全局安装
sudo npm install -g pnpm
# 查看安装情况
ls -la /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin
# 创建 pnpm 软链
sudo ln -s /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin/npx /usr/bin/pnpm
# 查看版本
pnpm -v
```
#### **安装 yarn**
```bash
# 全局安装
sudo npm install -g yarn
# 查看
ls -la /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin
# 创建 yarn 软链
sudo ln -s /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin/npx /usr/bin/yarn
# 查看版本
yarn -v
```
#### **安装 bun**
Linux用户-安装Bun需要解压缩包。使用 `sudo apt install unzip` 安装解压包。强烈建议使用5.6或更高版本的内核但最低版本为5.1。使用 `uname -r` 检查内核版本。
```bash
# 全局安装
sudo cnpm install -g bun
# 查看
ls -la /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin
# 创建 bun 软链
sudo ln -s /usr/local/lib/nodejs/node-v22.16.0-linux-x64/bin/bun /usr/bin/bun
# 查看版本
bun -v
```
---
### **切换版本**
如需切换版本,例如 `v18.20.7`,可以删除现有的软链接:
```bash
sudo rm /usr/bin/node
sudo rm /usr/bin/npm
sudo rm /usr/bin/npx
sudo rm /usr/bin/pnpm
sudo rm /usr/bin/yarn
sudo rm /usr/bin/bun
sudo rm /usr/bin/cnpm
```
创建新的软链接:
```bash
sudo ln -s /usr/local/lib/nodejs/node-v18.20.7-linux-x64/bin/node /usr/bin/node
sudo ln -s /usr/local/lib/nodejs/node-v18.20.7-linux-x64/bin/npm /usr/bin/npm
sudo ln -s /usr/local/lib/nodejs/node-v18.20.7-linux-x64/bin/npx /usr/bin/npx
sudo ln -s /usr/local/lib/nodejs/node-v18.20.7-linux-x64/bin/pnpm /usr/bin/pnpm
sudo ln -s /usr/local/lib/nodejs/node-v18.20.7-linux-x64/bin/bun /usr/bin/bun
sudo ln -s /usr/local/lib/nodejs/node-v18.20.7-linux-x64/bin/cnpm /usr/bin/cnpm
```
验证安装:
```bash
node -v
npm -v
npx -v
pnpm -v
yarn -v
```
查看当前配置:
```bash
npm config list --json
```
---
### **方法 3fnm方式安装(不推荐)**
#### **npm**
```bash
# Download and install fnm:
curl -o- https://fnm.vercel.app/install | bash
# Download and install Node.js:
fnm install 22
# Verify the Node.js version:
node -v # Should print "v22.16.0".
# Verify npm version:
npm -v # Should print "10.9.2".
```
#### **yarn**
```bash
# Download and install Yarn:
corepack enable yarn
# Verify Yarn version:
yarn -v
```
#### **pnpm**
```bash
# Download and install pnpm:
corepack enable pnpm
# Verify pnpm version:
pnpm -v
```
#### **nodejs包镜像加速**
```bash
npm config set registry https://registry.npmmirror.com
```
---
### **对比 npm、Yarn、pnpm**
| 特性 | npm | Yarn (v1/Yarn Classic) | Yarn Berry (v2+) | pnpm |
|---------------------------|------------------------------|------------------------------|------------------------------|------------------------------|
| **发布时间** | 2010 (Node.js 自带) | 2016 (Facebook 推出) | 2020 (Yarn 2.0 重大更新) | 2017 (独立发展) |
| **安装速度** | ⏳ 慢(线性安装) | 🚀 较快(并行下载) | 🚀 快(改进缓存) | ⚡ 极快(硬链接 + 符号链接) |
| **依赖管理** | node_modules 嵌套结构 | node_modules 扁平化 | PlugnPlay (PnP) 无 node_modules | 硬链接 + 全局存储(节省空间) |
| **缓存机制** | 有缓存,但效率一般 | 缓存优化较好 | 增量缓存 + 零安装Zero-Installs | 全局存储,依赖可复用 |
| **锁文件** | package-lock.json | yarn.lock | yarn.lock + .pnp.cjs | pnpm-lock.yaml |
| **安全性** | 🔒 一般(依赖可能被篡改) | 🔒 较好(校验严格) | 🔒 最好(离线模式 + 校验) | 🔒 最好(内容可寻址存储) |
| **Monorepo 支持** | 有限(需配合 Lerna | 支持 Workspaces | 原生 Workspaces + PnP | 原生 Workspaces + 高效链接 |
| **磁盘占用** | 高(重复依赖多) | 中(扁平化减少重复) | 低PnP 无 node_modules | 极低(全局存储 + 硬链接) |
| **命令行体验** | 基础功能 | 更友好(进度条、交互提示) | 现代化(插件系统) | 简洁高效 |
| **兼容性** | 100% 兼容 Node.js | 兼容 npm 生态 | 需适配 PnP部分库不兼容 | 兼容 npm/Yarn 生态 |
| **推荐场景** | 简单项目 / Node.js 默认 | 旧项目迁移 / 稳定需求 | 大型 Monorepo / 严格依赖控制 | 高效开发 / 节省磁盘空间 |

View File

@@ -0,0 +1,213 @@
---
title: Dify + Supabase打造带数据库的AI问答机器人
description: 基于Dify和Supabase的AI问答机器人
date: 2025-05-18
img: https://img.jiwei.xin/20250807175904733.png
navigation:
icon: simple-icons:reactbootstrap
---
> 🔒 **文档说明**:本文针对新部署的 Debian 12 云服务器,提供必做的安全加固与性能优化配置清单。适用于**运维人员**及**Linux初学者**,可有效防御 90% 的常见攻击向量。
## 技术TAG
`#Debian12安全设置` `#Linux加固` `#服务器优化` `#云服务器安全` `#SSH加固`
---
## 一、初始登录与用户安全
### 1. 创建替代root的用户
```bash
# 创建管理员用户(示例用户名为 sysadmin
adduser sysadmin
usermod -aG sudo sysadmin
# 验证新用户sudo权限
su - sysadmin
sudo whoami # 应返回root
```
### 2. 禁用root SSH登录
```bash
sudo nano /etc/ssh/sshd_config
# 修改以下配置:
PermitRootLogin no
PasswordAuthentication no # 强制使用密钥登录
```
> ⚠️ **关键提示**
> 1. 操作前必须配置好SSH密钥否则会锁定服务器
> 2. 执行后重载服务:`sudo systemctl reload ssh`
---
## 二、防火墙配置
### UFW基础设置
```bash
# 安装UFW
sudo apt install ufw -y
# 默认策略
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 允许端口(按需开放)
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
# 启用防火墙
sudo ufw enable
sudo ufw status verbose # 验证规则
```
### 端口安全技巧
```bash
# 更改SSH端口可选
sudo nano /etc/ssh/sshd_config
# 修改: Port 2222 # 改为非标准端口
# 仅允许特定IP访问SSH
sudo ufw allow proto tcp from 192.168.1.100 to any port 22
```
---
## 三、系统更新与自动维护
### 初始全面更新
```bash
sudo apt update && sudo apt full-upgrade -y
sudo apt autoremove && sudo apt clean
```
### 配置自动安全更新
```bash
sudo apt install unattended-upgrades apt-listchanges
# 启用配置
sudo dpkg-reconfigure -plow unattended-upgrades
# 验证状态:
sudo systemctl status unattended-upgrades
```
> 🔧 **配置文件优化** (`/etc/apt/apt.conf.d/50unattended-upgrades`)
> ```json
> Unattended-Upgrade::Remove-Unused-Dependencies "true";
> Unattended-Upgrade::Automatic-Reboot "true";
> Unattended-Upgrade::Automatic-Reboot-Time "03:00";
> ```
---
## 四、安全加固关键措施
### 1. 启用基础入侵防护
```bash
# 安装fail2ban
sudo apt install fail2ban -y
# 配置SSH防护
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
```
```ini
[sshd]
enabled = true
maxretry = 3
bantime = 1h
```
### 2. 内核安全加固
```bash
# 安装安全组件
sudo apt install linux-hardened apparmor apparmor-utils -y
# 启用AppArmor
sudo apparmor_status
sudo aa-enforce /etc/apparmor.d/* # 强制所有配置
```
---
## 五、性能优化设置
### 1. 交换空间优化
```bash
# 调整Swappiness值推荐10-30
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
# 启用Zswap压缩缓存内存<8GB时尤其有效
echo 'zswap.enabled=1' | sudo tee -a /etc/sysctl.conf
```
### 2. 文件描述符与进程优化
```bash
# 提高系统限制
sudo nano /etc/security/limits.conf
```
```conf
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
```
### 3. 日志管理(防止占满磁盘)
```bash
# 安装日志轮询工具
sudo apt install logrotate -y
# 手动压缩现有大日志
sudo find /var/log -size +100M -exec truncate -s 10M {} \;
```
---
## 六、审计与监控
### 安装基础监控组件
```bash
sudo apt install sysstat htop net-tools tree ncdu
# 启用sysstat数据收集
sudo sed -i 's/false/true/g' /etc/default/sysstat
sudo systemctl enable --now sysstat
```
### 关键审计命令
```bash
# 检查异常登录:
sudo lastb -a | head -20
# 检查SUID文件
sudo find / -perm /4000 -ls
# 列出开放端口:
sudo ss -tunlp
```
> 📊 **推荐可视化工具**
> - Cockpit (轻量级Web面板)`sudo apt install cockpit`
> - Netdata (实时监控)`bash <(curl -Ss https://my-netdata.io/kickstart.sh)`
---
**最终安全检查清单**
```bash
echo "[+] SSH 配置"
sudo sshd -t && grep -E "PermitRoot|PasswordAuth" /etc/ssh/sshd_config
echo "[+] 防火墙状态"
sudo ufw status
echo "[]+ 更新状态"
sudo unattended-upgrades --dry-run
```
![服务器安全架构](https://example.com/img/debian-security-layers.png)
*(安全层示意图:网络防火墙→系统加固→应用防护)*
> 🔐 **维护建议**
> 1. 每月执行:`sudo lynis audit system` (安装:`apt install lynis`)
> 2. 每季度更新所有SSL证书即使未到期
> 3. 使用自动配置管理工具如Ansible维护服务器状态
**文档版本**: v1.2
**测试环境**: Debian 12.5 (Kernel 6.1.x) 云服务器
**最后更新**: 2024-06-15

View File

@@ -0,0 +1,213 @@
---
title: 安装 VS Code到浏览器 -- Code Server
description: 在浏览器中安装 VS Code
date: 2025-05-23
img: https://img.jiwei.xin/20250616220449750.png
navigation:
icon: simple-icons:visualstudiocode
---
> 🔒 **文档说明**:本文针对新部署的 Debian 12 云服务器,提供必做的安全加固与性能优化配置清单。适用于**运维人员**及**Linux初学者**,可有效防御 90% 的常见攻击向量。
## 技术TAG
`#Debian12安全设置` `#Linux加固` `#服务器优化` `#云服务器安全` `#SSH加固`
---
## 一、初始登录与用户安全
### 1. 创建替代root的用户
```bash
# 创建管理员用户(示例用户名为 sysadmin
adduser sysadmin
usermod -aG sudo sysadmin
# 验证新用户sudo权限
su - sysadmin
sudo whoami # 应返回root
```
### 2. 禁用root SSH登录
```bash
sudo nano /etc/ssh/sshd_config
# 修改以下配置:
PermitRootLogin no
PasswordAuthentication no # 强制使用密钥登录
```
> ⚠️ **关键提示**
> 1. 操作前必须配置好SSH密钥否则会锁定服务器
> 2. 执行后重载服务:`sudo systemctl reload ssh`
---
## 二、防火墙配置
### UFW基础设置
```bash
# 安装UFW
sudo apt install ufw -y
# 默认策略
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 允许端口(按需开放)
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
# 启用防火墙
sudo ufw enable
sudo ufw status verbose # 验证规则
```
### 端口安全技巧
```bash
# 更改SSH端口可选
sudo nano /etc/ssh/sshd_config
# 修改: Port 2222 # 改为非标准端口
# 仅允许特定IP访问SSH
sudo ufw allow proto tcp from 192.168.1.100 to any port 22
```
---
## 三、系统更新与自动维护
### 初始全面更新
```bash
sudo apt update && sudo apt full-upgrade -y
sudo apt autoremove && sudo apt clean
```
### 配置自动安全更新
```bash
sudo apt install unattended-upgrades apt-listchanges
# 启用配置
sudo dpkg-reconfigure -plow unattended-upgrades
# 验证状态:
sudo systemctl status unattended-upgrades
```
> 🔧 **配置文件优化** (`/etc/apt/apt.conf.d/50unattended-upgrades`)
> ```json
> Unattended-Upgrade::Remove-Unused-Dependencies "true";
> Unattended-Upgrade::Automatic-Reboot "true";
> Unattended-Upgrade::Automatic-Reboot-Time "03:00";
> ```
---
## 四、安全加固关键措施
### 1. 启用基础入侵防护
```bash
# 安装fail2ban
sudo apt install fail2ban -y
# 配置SSH防护
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
```
```ini
[sshd]
enabled = true
maxretry = 3
bantime = 1h
```
### 2. 内核安全加固
```bash
# 安装安全组件
sudo apt install linux-hardened apparmor apparmor-utils -y
# 启用AppArmor
sudo apparmor_status
sudo aa-enforce /etc/apparmor.d/* # 强制所有配置
```
---
## 五、性能优化设置
### 1. 交换空间优化
```bash
# 调整Swappiness值推荐10-30
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
# 启用Zswap压缩缓存内存<8GB时尤其有效
echo 'zswap.enabled=1' | sudo tee -a /etc/sysctl.conf
```
### 2. 文件描述符与进程优化
```bash
# 提高系统限制
sudo nano /etc/security/limits.conf
```
```conf
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
```
### 3. 日志管理(防止占满磁盘)
```bash
# 安装日志轮询工具
sudo apt install logrotate -y
# 手动压缩现有大日志
sudo find /var/log -size +100M -exec truncate -s 10M {} \;
```
---
## 六、审计与监控
### 安装基础监控组件
```bash
sudo apt install sysstat htop net-tools tree ncdu
# 启用sysstat数据收集
sudo sed -i 's/false/true/g' /etc/default/sysstat
sudo systemctl enable --now sysstat
```
### 关键审计命令
```bash
# 检查异常登录:
sudo lastb -a | head -20
# 检查SUID文件
sudo find / -perm /4000 -ls
# 列出开放端口:
sudo ss -tunlp
```
> 📊 **推荐可视化工具**
> - Cockpit (轻量级Web面板)`sudo apt install cockpit`
> - Netdata (实时监控)`bash <(curl -Ss https://my-netdata.io/kickstart.sh)`
---
**最终安全检查清单**
```bash
echo "[+] SSH 配置"
sudo sshd -t && grep -E "PermitRoot|PasswordAuth" /etc/ssh/sshd_config
echo "[+] 防火墙状态"
sudo ufw status
echo "[]+ 更新状态"
sudo unattended-upgrades --dry-run
```
![服务器安全架构](https://example.com/img/debian-security-layers.png)
*(安全层示意图:网络防火墙→系统加固→应用防护)*
> 🔐 **维护建议**
> 1. 每月执行:`sudo lynis audit system` (安装:`apt install lynis`)
> 2. 每季度更新所有SSL证书即使未到期
> 3. 使用自动配置管理工具如Ansible维护服务器状态
**文档版本**: v1.2
**测试环境**: Debian 12.5 (Kernel 6.1.x) 云服务器
**最后更新**: 2024-06-15

View File

@@ -0,0 +1,213 @@
---
title: Debian 12 云服务器初始安全设置与优化指南
description: Debian 12 云服务器初始安全设置与优化指南
date: 2025-05-15
img: https://img.jiwei.xin/20250615184027376.png
navigation:
icon: lucide-server
---
> 🔒 **文档说明**:本文针对新部署的 Debian 12 云服务器,提供必做的安全加固与性能优化配置清单。适用于**运维人员**及**Linux初学者**,可有效防御 90% 的常见攻击向量。
## 技术TAG
`#Debian12安全设置` `#Linux加固` `#服务器优化` `#云服务器安全` `#SSH加固`
---
## 一、初始登录与用户安全
### 1. 创建替代root的用户
```bash
# 创建管理员用户(示例用户名为 sysadmin
adduser sysadmin
usermod -aG sudo sysadmin
# 验证新用户sudo权限
su - sysadmin
sudo whoami # 应返回root
```
### 2. 禁用root SSH登录
```bash
sudo nano /etc/ssh/sshd_config
# 修改以下配置:
PermitRootLogin no
PasswordAuthentication no # 强制使用密钥登录
```
> ⚠️ **关键提示**
> 1. 操作前必须配置好SSH密钥否则会锁定服务器
> 2. 执行后重载服务:`sudo systemctl reload ssh`
---
## 二、防火墙配置
### UFW基础设置
```bash
# 安装UFW
sudo apt install ufw -y
# 默认策略
sudo ufw default deny incoming
sudo ufw default allow outgoing
# 允许端口(按需开放)
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
# 启用防火墙
sudo ufw enable
sudo ufw status verbose # 验证规则
```
### 端口安全技巧
```bash
# 更改SSH端口可选
sudo nano /etc/ssh/sshd_config
# 修改: Port 2222 # 改为非标准端口
# 仅允许特定IP访问SSH
sudo ufw allow proto tcp from 192.168.1.100 to any port 22
```
---
## 三、系统更新与自动维护
### 初始全面更新
```bash
sudo apt update && sudo apt full-upgrade -y
sudo apt autoremove && sudo apt clean
```
### 配置自动安全更新
```bash
sudo apt install unattended-upgrades apt-listchanges
# 启用配置
sudo dpkg-reconfigure -plow unattended-upgrades
# 验证状态:
sudo systemctl status unattended-upgrades
```
> 🔧 **配置文件优化** (`/etc/apt/apt.conf.d/50unattended-upgrades`)
> ```json
> Unattended-Upgrade::Remove-Unused-Dependencies "true";
> Unattended-Upgrade::Automatic-Reboot "true";
> Unattended-Upgrade::Automatic-Reboot-Time "03:00";
> ```
---
## 四、安全加固关键措施
### 1. 启用基础入侵防护
```bash
# 安装fail2ban
sudo apt install fail2ban -y
# 配置SSH防护
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
```
```ini
[sshd]
enabled = true
maxretry = 3
bantime = 1h
```
### 2. 内核安全加固
```bash
# 安装安全组件
sudo apt install linux-hardened apparmor apparmor-utils -y
# 启用AppArmor
sudo apparmor_status
sudo aa-enforce /etc/apparmor.d/* # 强制所有配置
```
---
## 五、性能优化设置
### 1. 交换空间优化
```bash
# 调整Swappiness值推荐10-30
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
# 启用Zswap压缩缓存内存<8GB时尤其有效
echo 'zswap.enabled=1' | sudo tee -a /etc/sysctl.conf
```
### 2. 文件描述符与进程优化
```bash
# 提高系统限制
sudo nano /etc/security/limits.conf
```
```conf
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
```
### 3. 日志管理(防止占满磁盘)
```bash
# 安装日志轮询工具
sudo apt install logrotate -y
# 手动压缩现有大日志
sudo find /var/log -size +100M -exec truncate -s 10M {} \;
```
---
## 六、审计与监控
### 安装基础监控组件
```bash
sudo apt install sysstat htop net-tools tree ncdu
# 启用sysstat数据收集
sudo sed -i 's/false/true/g' /etc/default/sysstat
sudo systemctl enable --now sysstat
```
### 关键审计命令
```bash
# 检查异常登录:
sudo lastb -a | head -20
# 检查SUID文件
sudo find / -perm /4000 -ls
# 列出开放端口:
sudo ss -tunlp
```
> 📊 **推荐可视化工具**
> - Cockpit (轻量级Web面板)`sudo apt install cockpit`
> - Netdata (实时监控)`bash <(curl -Ss https://my-netdata.io/kickstart.sh)`
---
**最终安全检查清单**
```bash
echo "[+] SSH 配置"
sudo sshd -t && grep -E "PermitRoot|PasswordAuth" /etc/ssh/sshd_config
echo "[+] 防火墙状态"
sudo ufw status
echo "[]+ 更新状态"
sudo unattended-upgrades --dry-run
```
![服务器安全架构](https://example.com/img/debian-security-layers.png)
*(安全层示意图:网络防火墙→系统加固→应用防护)*
> 🔐 **维护建议**
> 1. 每月执行:`sudo lynis audit system` (安装:`apt install lynis`)
> 2. 每季度更新所有SSL证书即使未到期
> 3. 使用自动配置管理工具如Ansible维护服务器状态
**文档版本**: v1.2
**测试环境**: Debian 12.5 (Kernel 6.1.x) 云服务器
**最后更新**: 2024-06-15

View File

@@ -0,0 +1,136 @@
---
title: Iperf3 万兆网络打流测试指南
description: Iperf3 万兆网络打流测试指南
date: 2025-05-13
img: https://img.jiwei.xin/20250615184111247.png
navigation:
icon: lucide-network
---
> 🔍 **文档说明**: 本文记录基于水星S106E Pro交换机的万兆网络Iperf3性能测试涵盖设备连接、测试参数配置及结果分析。适用于**网络性能测试初学者**及**运维人员**。
## 技术TAG
`#Iperf3` `#万兆网络测试` `#交换机配置` `#AQC113网卡` `#网络性能分析`
---
## 一、测试环境配置
### 设备拓扑图
```mermaid
graph LR
A[MacBook Air M4] -- 雷电/万兆电口 --> B[水星S106E Pro<br>万兆光口]
B -- 万兆光口 --> C[PVE服务器<br>CX4121A网卡]
```
### 硬件清单
| **设备** | **规格** | **作用** |
|---------------------|-----------------------------------------|-----------------------|
| 主测试端 | MacBook Air M4 + 雷电万兆网卡(AQC113) | Iperf3 Client端 |
| 交换机 | 水星S106E Pro | 万兆光/电转换枢纽 |
| 光转电模块 | AQR113光转电模块 | 连接交换机万兆光口 |
| 被测服务端 | NAS (12500T+Q670) + CX4121A万兆网卡 | Iperf3 Server端 |
| 辅助设备 | 超六类网线/光纤跳线(按需) | 物理链路连接 |
```
---
## 二、Iperf3安装与配置
### 1. macOS端安装Client
```bash
# 通过Homebrew安装iperf3
brew install iperf3
# 启动服务端监听(可选本地验证)
iperf3 -s -p 5201
```
### 2. PVE服务端安装Server
```bash
# Debian系系统安装
apt update && apt install iperf3 -y
# 启动服务端后台监听
iperf3 -s -D -p 5201
```
> ⚠️ **防火墙注意**: 确保5201端口开放
> `ufw allow 5201/tcp` (Debian/Ubuntu)
> `firewall-cmd --add-port=5201/tcp --permanent` (CentOS)
```
## 三、典型测试场景
### 场景1TCP带宽测试默认参数
```bash
# Client端执行MacBook
iperf3 -c <Server_IP> -p 5201 -t 60
# 参数说明:
# -c : 服务端IP地址
# -t : 测试持续时间(秒)
```
### 场景2多线程UDP压力测试
```bash
iperf3 -c <Server_IP> -p 5201 -u -b 10G -P 4
# 参数说明:
# -u : 使用UDP协议
# -b : 指定带宽(10G速率)
# -P : 并行线程数
```
### 场景3双向流量测试
```bash
# 同时测试上行+下行
iperf3 -c <Server_IP> -p 5201 --bidir
```
---
## 四、测试结果分析要点
### 关键性能指标
| **字段** | **健康值范围** | **异常排查方向** |
|------------------|-------------------------|------------------------|
| Bandwidth | >9.5Gbps (万兆场景) | 网卡协商/模块兼容性 |
| Jitter | <1ms (UDP测试) | 交换机缓存/线缆质量 |
| Packet Loss | 0% | 缓冲区设置/CPU性能 |
| TCP Retransmits | <0.1% | TCP窗口/MTU配置 |
> 💡 **优化建议**
> 1. 使用`-w`调整TCP窗口大小例如`-w 8M`
> 2. 尝试`--omit N`跳过初始N秒不稳定数据
> 3. 添加`-J`参数获取JSON格式结构化结果
---
## 五、常见问题解决
### ❌ 问题1协商速率降级至1Gbps
```bash
# 检查网卡协商状态MacOS
ifconfig enX | grep media
# 解决方案:
1. 更换光模块/AOC线缆排除物理故障
2. 检查交换机端口配置(禁用节能模式)
```
### ❌ 问题2测试中突发丢包
```bash
# 动态查看QoS统计PVE服务器
ethtool -S enpXX | grep -E 'drop|error'
# 解决方案:
1. 调整网卡缓冲区ethtool -G enpXX rx/tx 4096
2. 减少并发线程总数(-P参数
```
![万兆测试拓扑示意图](https://example.com/img/10g-test-diagram.png)
*(示意图:实际连接需确保光模块端口匹配)*
> 📌 **经验总结**水星S106E Pro需关闭"绿色节能"功能避免AQC113因节能策略降速。PVE虚拟机环境建议使用SR-IOV直通网卡减少虚拟化层开销。
---
**测试文档版本**: v1.1
**更新日期**: 2023-12-15
**测试工具版本**: iperf 3.16 (macOS)/iperf 3.7 (Linux)

View File

@@ -0,0 +1,883 @@
---
title: GROK3 and Deepseek
description: Grok3 和 Deepseek 的对比
date: 2025-05-12
img: https://img.jiwei.xin/20250806105153532.png
navigation:
icon: lucide-brain
---
### GROK3 and Deepseek
测试了GROK3,简单让做一个HTML小游戏与R1对比
GROK3界面处理略好满足所有要求无BUG。
R1界面略有瑕疵基本满足所有要求除了有小BUG。
提示词:
>> 题目:编写一个“打地鼠”网页小游戏
需求描述:
设计一个基于HTML的“打地鼠”Whack-a-Mole小游戏玩家需要在规定时间内点击随机出现的“地鼠”来得分。游戏需要包含以下功能和要求
页面布局:
游戏区域是一个3x3的网格共9个格子每个格子是一个方形的“洞”。
页面顶部显示当前得分Score和剩余时间Time Left
页面底部有一个“开始游戏”Start Game的按钮。
游戏逻辑:
点击“开始游戏”按钮后游戏开始初始时间为30秒。
每隔一段随机时间0.5秒到1.5秒之间一个“地鼠”会随机出现在某个格子中持续显示1秒后消失。
玩家点击出现的“地鼠”可以得分每只地鼠加1分。
如果点击空白格子,不扣分也不加分。
游戏时间结束后,停止地鼠出现,并弹窗显示最终得分,同时提供“再玩一次”的选项。
样式要求:
游戏区域居中,背景为浅灰色。
每个格子为100x100像素带有圆角边框初始为土黄色#d2b48c)。
地鼠出现时,格子背景变为绿色(#90ee90并显示文字“鼠字体大小30px居中
得分和时间使用清晰的大字体建议20px以上颜色为深蓝色。
“开始游戏”按钮为蓝色背景,白色文字,悬停时变亮。
技术要求:
使用纯HTML、CSS和JavaScript实现不依赖外部库。
代码注释清晰,说明每个部分的功能。
确保游戏逻辑无明显bug例如地鼠不会同时出现在多个格子。
提示词:
HTML结构包含游戏容器、得分显示、时间显示、格子网格和按钮。
CSS样式网格布局、格子样式、地鼠出现时的动态效果、按钮交互样式。
JavaScript逻辑随机生成地鼠、计时器、得分计算、游戏状态管理、弹窗提示。
输出要求:
请一次性提供完整的HTML文件代码包括HTML、CSS和JavaScript代码需可直接保存为.html文件并在浏览器中运行。代码中需包含注释体现逻辑清晰度和编程能力。
GROK3:
```html[GROK3.html]
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>打地鼠小游戏</title>
<style>
/* 整体页面样式 */
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
background-color: #f0f0f0;
}
/* 顶部得分和时间显示 */
.info {
display: flex;
justify-content: space-between;
width: 320px;
margin-bottom: 20px;
}
.info span {
font-size: 24px;
color: #00008b; /* 深蓝色 */
}
/* 游戏区域网格 */
.game-board {
display: grid;
grid-template-columns: repeat(3, 100px);
grid-gap: 10px;
background-color: #d3d3d3; /* 浅灰色背景 */
padding: 10px;
border-radius: 10px;
}
/* 每个格子样式 */
.hole {
width: 100px;
height: 100px;
background-color: #d2b48c; /* 土黄色 */
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 30px;
color: #000;
cursor: pointer;
transition: background-color 0.2s;
}
/* 地鼠出现时的样式 */
.hole.mole {
background-color: #90ee90; /* 绿色 */
}
/* 开始按钮样式 */
.start-btn {
margin-top: 20px;
padding: 10px 20px;
font-size: 18px;
color: #fff;
background-color: #4682b4; /* 蓝色 */
border: none;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s;
}
.start-btn:hover {
background-color: #5f9ea0; /* 悬停时变亮 */
}
</style>
</head>
<body>
<!-- 顶部信息 -->
<div class="info">
<span id="score">得分: 0</span>
<span id="time-left">剩余时间: 30</span>
</div>
<!-- 游戏区域 -->
<div class="game-board" id="gameBoard">
<!-- 9个格子 -->
<div class="hole" data-id="0"></div>
<div class="hole" data-id="1"></div>
<div class="hole" data-id="2"></div>
<div class="hole" data-id="3"></div>
<div class="hole" data-id="4"></div>
<div class="hole" data-id="5"></div>
<div class="hole" data-id="6"></div>
<div class="hole" data-id="7"></div>
<div class="hole" data-id="8"></div>
</div>
<!-- 开始按钮 -->
<button class="start-btn" id="startBtn">开始游戏</button>
<script>
// 获取DOM元素
const scoreDisplay = document.getElementById('score');
const timeLeftDisplay = document.getElementById('time-left');
const gameBoard = document.getElementById('gameBoard');
const startBtn = document.getElementById('startBtn');
const holes = document.querySelectorAll('.hole');
// 游戏状态变量
let score = 0;
let timeLeft = 30;
let gameActive = false;
let moleTimer = null;
let countdownTimer = null;
// 初始化游戏
function initGame() {
score = 0;
timeLeft = 30;
gameActive = false;
scoreDisplay.textContent = `得分: ${score}`;
timeLeftDisplay.textContent = `剩余时间: ${timeLeft}`;
clearInterval(moleTimer);
clearInterval(countdownTimer);
holes.forEach(hole => {
hole.classList.remove('mole');
hole.textContent = '';
});
}
// 随机选择一个格子显示地鼠
function showMole() {
if (!gameActive) return;
// 移除之前的地鼠
holes.forEach(hole => {
hole.classList.remove('mole');
hole.textContent = '';
});
// 随机选择一个格子
const randomHole = holes[Math.floor(Math.random() * holes.length)];
randomHole.classList.add('mole');
randomHole.textContent = '鼠!';
// 1秒后隐藏地鼠
setTimeout(() => {
randomHole.classList.remove('mole');
randomHole.textContent = '';
}, 1000);
// 随机间隔后再次显示地鼠
const randomInterval = Math.random() * 1000 + 500; // 0.5到1.5秒
moleTimer = setTimeout(showMole, randomInterval);
}
// 点击格子的事件
function hitMole(e) {
const hole = e.target;
if (hole.classList.contains('mole')) {
score++;
scoreDisplay.textContent = `得分: ${score}`;
hole.classList.remove('mole');
hole.textContent = '';
}
}
// 开始游戏
function startGame() {
if (gameActive) return; // 防止重复开始
initGame();
gameActive = true;
startBtn.disabled = true;
// 开始倒计时
countdownTimer = setInterval(() => {
timeLeft--;
timeLeftDisplay.textContent = `剩余时间: ${timeLeft}`;
if (timeLeft <= 0) {
endGame();
}
}, 1000);
// 开始地鼠出现
showMole();
}
// 游戏结束
function endGame() {
gameActive = false;
clearInterval(moleTimer);
clearInterval(countdownTimer);
startBtn.disabled = false;
alert(`游戏结束!你的得分是: ${score}\n点击“确定”再玩一次`);
initGame();
}
// 事件绑定
startBtn.addEventListener('click', startGame);
holes.forEach(hole => hole.addEventListener('click', hitMole));
// 初始化游戏状态
initGame();
</script>
</body>
</html>
```
R1:
```html[R1.html]
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>打地鼠 - Whack a Mole</title>
<style>
/* 页面整体样式 */
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
background-color: #f0f0f0;
margin: 0;
min-height: 100vh;
}
/* 游戏信息显示区域 */
.game-info {
margin: 20px 0;
font-size: 24px;
color: #00008b;
display: flex;
gap: 30px;
}
/* 游戏主容器3x3网格 */
#game-board {
display: grid;
grid-template-columns: repeat(3, 100px);
gap: 10px;
background-color: #cccccc;
padding: 20px;
border-radius: 10px;
}
/* 单个地鼠洞样式 */
.hole {
width: 100px;
height: 100px;
background-color: #d2b48c;
border-radius: 8px;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-size: 0; /* 初始隐藏文字 */
transition: background-color 0.2s;
}
/* 地鼠出现时的样式 */
.hole.active {
background-color: #90ee90;
font-size: 30px; /* 显示文字 */
}
/* 开始游戏按钮 */
#start-btn {
margin-top: 30px;
padding: 12px 30px;
background-color: #0000ff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 18px;
transition: background-color 0.2s;
}
#start-btn:hover {
background-color: #3333ff;
}
#start-btn:disabled {
background-color: #888888;
cursor: not-allowed;
}
</style>
</head>
<body>
<!-- 游戏信息显示 -->
<div class="game-info">
<div>得分:<span id="score">0</span></div>
<div>剩余时间:<span id="timer">30</span>秒</div>
</div>
<!-- 游戏主区域 -->
<div id="game-board">
<!-- 生成9个地鼠洞 -->
<div class="hole"></div>
<div class="hole"></div>
<div class="hole"></div>
<div class="hole"></div>
<div class="hole"></div>
<div class="hole"></div>
<div class="hole"></div>
<div class="hole"></div>
<div class="hole"></div>
</div>
<!-- 控制按钮 -->
<button id="start-btn">开始游戏</button>
<script>
// 游戏全局变量
let score = 0;
let timeLeft = 30;
let gameActive = false;
let moleTimer = null;
let countdownTimer = null;
// DOM元素引用
const holes = document.querySelectorAll('.hole');
const scoreDisplay = document.getElementById('score');
const timerDisplay = document.getElementById('timer');
const startBtn = document.getElementById('start-btn');
// 初始化点击事件监听
holes.forEach(hole => {
hole.addEventListener('click', () => {
if (!gameActive) return;
// 点击有效的地鼠
if (hole.classList.contains('active')) {
score++;
scoreDisplay.textContent = score;
hole.classList.remove('active');
hole.textContent = '';
}
});
});
// 开始游戏按钮功能
startBtn.addEventListener('click', () => {
if (!gameActive) {
startGame();
}
});
// 游戏主逻辑
function startGame() {
// 重置游戏状态
gameActive = true;
score = 0;
timeLeft = 30;
scoreDisplay.textContent = score;
timerDisplay.textContent = timeLeft;
startBtn.disabled = true;
// 清除已有定时器
clearInterval(countdownTimer);
clearTimeout(moleTimer);
// 启动倒计时
countdownTimer = setInterval(() => {
timeLeft--;
timerDisplay.textContent = timeLeft;
if (timeLeft <= 0) {
endGame();
}
}, 1000);
// 首次生成地鼠
scheduleMole();
}
// 随机生成地鼠
function showMole() {
// 查找所有非活动状态的洞
const availableHoles = Array.from(holes).filter(hole => !hole.classList.contains('active'));
if (availableHoles.length > 0) {
const randomHole = availableHoles[Math.floor(Math.random() * availableHoles.length)];
randomHole.classList.add('active');
randomHole.textContent = '鼠!';
// 自动隐藏地鼠
setTimeout(() => {
randomHole.classList.remove('active');
randomHole.textContent = '';
}, 1000);
}
}
// 调度下一个地鼠生成
function scheduleMole() {
if (!gameActive) return;
// 随机间隔0.5-1.5秒)
const delay = Math.random() * 1000 + 500;
moleTimer = setTimeout(() => {
showMole();
scheduleMole();
}, delay);
}
// 结束游戏处理
function endGame() {
gameActive = false;
clearInterval(countdownTimer);
clearTimeout(moleTimer);
startBtn.disabled = false;
// 显示最终得分
const restart = confirm(`游戏结束!最终得分:${score}\n再玩一次`);
if (restart) {
startGame();
}
}
</script>
</body>
</html>
```

View File

@@ -0,0 +1,3 @@
title: AI
description: AI 改变世界
icon: devicon:streamlit

View File

@@ -0,0 +1,10 @@
---
title: AI最佳实践
description: AI最佳实践
date: 2025-08-13
img: https://img.jiwei.xin/20250628122847084.png
navigation:
icon: simple-icons:chai
---
### AI 文章等待从旧Blog中搬迁

View File

@@ -0,0 +1,3 @@
title: 生活
description: 生活中一些有趣的事情
icon: devicon:love2d

View File

@@ -0,0 +1,10 @@
---
title: 生活
description: 生活
date: 2025-08-13
img: https://img.jiwei.xin/20250628122847084.png
navigation:
icon: simple-icons:chai
---
### 生活 类等待从旧Blog迁移

View File

@@ -5,7 +5,7 @@ navigation:
icon: lucide-house
---
## 缘起
# 缘起
使用过市面上的很多文档系统,但是或多或少都有一些自己不满意的功能.
于是自己动手,丰衣足食.
@@ -212,7 +212,7 @@ Check out the [deployment documentation](https://nuxt.com/docs/getting-started/d
::tip{icon="lucide-info" color="primary" class="text-black dark:text-white"}
阅读更多关于 [`自定义组件`](/简单文档/components/api) 的内容.
阅读更多关于 [`自定义组件`](/docs/简单文档/components/api) 的内容.
::

View File

@@ -28,7 +28,7 @@ authors:
:::tabs{variant="line"}
::stack{label="badges"}
![](https://lijue-me.oss-cn-chengdu.aliyuncs.com/20250730134423381.png)
![](https://img.jiwei.xin/20250730134423381.png)
```yml
badges:
- value: 0.8.10
@@ -40,7 +40,7 @@ badges:
::
::stack{label="authors"}
![](https://lijue-me.oss-cn-chengdu.aliyuncs.com/20250730134509935.png)
![](https://img.jiwei.xin/20250730134509935.png)
```yml
authors:
- name: Tony Zhang
@@ -52,7 +52,7 @@ authors:
::
::stack{label="navBadges"}
![](https://lijue-me.oss-cn-chengdu.aliyuncs.com/20250730134549353.png)
![](https://img.jiwei.xin/20250730134549353.png)
```yml
navBadges:
- value: New

View File

@@ -0,0 +1,244 @@
---
title: markdown 常用语法
description: 展示 markdown 常用语法
navigation:
icon: simple-icons:markdown
---
欢迎来到 Markdown 的奇妙世界无论你是写作爱好者、开发者、博主还是想要简单记录点什么的人Markdown 都能成为你新的好伙伴。它不仅让写作变得简单明了,还能轻松地将内容转化为漂亮的网页格式。今天,我们将全面探讨 Markdown 的基础和进阶语法,让你在这个过程中充分享受写作的乐趣!
Markdown 是一种轻量级标记语言,用于格式化纯文本。它以简单、直观的语法而著称,可以快速地生成 HTML。Markdown 是写作与代码的完美结合,既简单又强大。
## Markdown 基础语法
### 1. 标题:让你的内容层次分明
`#` 号来创建标题。标题从 `#` 开始,`#` 的数量表示标题的级别。
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
####### 七级标题
以上代码将渲染出一组层次分明的标题,使你的内容井井有条。
### 2. 段落与换行:自然流畅
Markdown 中的段落就是一行接一行的文本。要创建新段落,只需在两行文本之间空一行。
### 3. 字体样式:强调你的文字
- **粗体**:用两个星号或下划线包裹文字,如 `**粗体**``__粗体__`
- _斜体_用一个星号或下划线包裹文字`*斜体*``_斜体_`
- ~~删除线~~:用两个波浪线包裹文字,如 `~~删除线~~`
这些简单的标记可以让你的内容更有层次感和重点突出。
### 4. 列表:整洁有序
- **无序列表**:用 `-``*``+` 加空格开始一行。
- **有序列表**:使用数字加点号(`1.``2.`)开始一行。
在列表中嵌套其他内容?只需缩进即可实现嵌套效果。
- 无序列表项 1
1. 嵌套有序列表项 1
2. 嵌套有序列表项 2
- 无序列表项 2
1. 有序列表项 1
2. 有序列表项 2
### 5. 链接与图片:丰富内容
- **链接**:用方括号和圆括号创建链接 `[显示文本](链接地址)`
- **图片**:和链接类似,只需在前面加上 `!`,如 `![描述文本](图片链接)`
[访问 Doocs](https://github.com/doocs)
![doocs](https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/logo-2.png)
轻松实现富媒体内容展示!
> 因微信公众号平台不支持除公众号内容以外的链接,故其他平台的链接,会呈现链接样式但无法点击跳转。
> 对于这些链接请注意明文书写,或点击左上角「格式->微信外链接转底部引用」开启引用,这样就可以在底部观察到链接指向。
另外,使用 `<![alt](url),![alt](url)>` 语法可以创建横屏滑动幻灯片,支持微信公众号平台。建议使用相似尺寸的图片以获得最佳显示效果。
### 6. 引用:引用名言或引人深思的句子
使用 `>` 来创建引用,只需在文本前面加上它。多层引用?在前一层 `>` 后再加一个就行。
> 这是一个引用
>
> > 这是一个嵌套引用
这让你的引用更加富有层次感。
### 7. 代码块:展示你的代码
- **行内代码**:用反引号包裹,如 `code`
- **代码块**:用三个反引号包裹,并指定语言,如:
```js[test.vue]
console.log("Hello, Doocs!");
```
语法高亮让你的代码更易读。
### 8. 分割线:分割内容
用三个或更多的 `-`、`*` 或 `_` 来创建分割线。
---
为你的内容添加视觉分隔。
### 9. 表格:清晰展示数据
Markdown 支持简单的表格,用 `|` 和 `-` 分隔单元格和表头。
| 项目人员 | 邮箱 | 微信号 |
| ------------------------------------------- | ---------------------- | ------------ |
| [yanglbme](https://github.com/yanglbme) | contact@yanglibin.info | YLB0109 |
| [YangFong](https://github.com/YangFong) | yangfong2022@gmail.com | yq2419731931 |
| [thinkasany](https://github.com/thinkasany) | thinkasany@gmail.com | thinkasany |
这样的表格让数据展示更为清爽!
> 手动编写标记太麻烦?我们提供了便捷方式。左上方点击「编辑->插入表格」,即可快速实现表格渲染。
## Markdown 进阶技巧
### 1. LaTeX 公式:完美展示数学表达式
Markdown 允许嵌入 LaTeX 语法展示数学公式:
- **行内公式**:用 `$` 包裹公式,如 $E = mc^2$ 。
- **块级公式**:用 `$$` 包裹公式,如:
$$
\begin{aligned}
d_{i, j} &\leftarrow d_{i, j} + 1 \\
d_{i, y + 1} &\leftarrow d_{i, y + 1} - 1 \\
d_{x + 1, j} &\leftarrow d_{x + 1, j} - 1 \\
d_{x + 1, y + 1} &\leftarrow d_{x + 1, y + 1} + 1
\end{aligned}
$$
### 以下是使用mate字段包裹
```math[mate.md]
\begin{aligned}
d_{i, j} &\leftarrow d_{i, j} + 1 \\
d_{i, y + 1} &\leftarrow d_{i, y + 1} - 1 \\
d_{x + 1, j} &\leftarrow d_{x + 1, j} - 1 \\
d_{x + 1, y + 1} &\leftarrow d_{x + 1, y + 1} + 1
\end{aligned}
```
1. 列表内块公式 1
$$
\chi^2 = \sum \frac{(O - E)^2}{E}
$$
2. 列表内块公式 2
$$
\chi^2 = \sum \frac{(|O - E| - 0.5)^2}{E}
$$
这是展示复杂数学表达的利器!
### 2. Mermaid 流程图:可视化流程
Mermaid 是强大的可视化工具,可以在 Markdown 中创建流程图、时序图等。
```mermaid
---
config:
layout: dagre
look: classic
---
flowchart LR
A[Start] --> B{Choose Path}
B -->|Option 1| C[Path 1]
B -->|Option 2| D[Path 2]
```
```mermaid
graph TD
A[Local Coolify 实例] --> B[生产服务器]
A --> D[编译服务器]
B --> B1[生产环境]
D --> B
```
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
```mermaid
pie
title Key elements in Product X
"Calcium" : 42.96
"Potassium" : 50.05
"Magnesium" : 10.01
"Iron" : 5
```
```mermaid
pie
title 为什么总是宅在家里?
"喜欢宅" : 45
"天气太热" : 70
"穷" : 500
"没人约" : 95
```
这种方式不仅能直观展示流程,还能提升文档的专业性。
> 更多用法,参见:[Mermaid User Guide](https://mermaid.js.org/intro/getting-started.html)。
## 结语
Markdown 是一种简单、强大且易于掌握的标记语言通过学习基础和进阶语法你可以快速创作内容并有效传达信息。无论是技术文档、个人博客还是项目说明Markdown 都是你的得力助手。希望这篇内容能够带你全面了解 Markdown 的潜力,让你的写作更加丰富多彩!
现在,拿起 Markdown 编辑器,开始创作吧!探索 Markdown 的世界,你会发现它远比想象中更精彩!
#### 推荐阅读
- [阿里又一个 20k+ stars 开源项目诞生,恭喜 fastjson](https://mp.weixin.qq.com/s/RNKDCK2KoyeuMeEs6GUrow)
- [刷掉 90% 候选人的互联网大厂海量数据面试题(附题解 + 方法总结)](https://mp.weixin.qq.com/s/rjGqxUvrEqJNlo09GrT1Dw)
- [好用!期待已久的文本块功能究竟如何在 Java 13 中发挥作用?](https://mp.weixin.qq.com/s/kalGv5T8AZGxTnLHr2wDsA)
- [2019 GitHub 开源贡献排行榜新鲜出炉!微软谷歌领头,阿里跻身前 12](https://mp.weixin.qq.com/s/_q812aGD1b9QvZ2WFI0Qgw)
---
<center>
<img src="https://cdn-doocs.oss-cn-shenzhen.aliyuncs.com/gh/doocs/md/images/1648303220922-7e14aefa-816e-44c1-8604-ade709ca1c69.png" style="width: 100px;">
</center>

Some files were not shown because too many files have changed in this diff Show More