Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
046b0ef674 | |||
597339b666 | |||
f57e410393 | |||
3aed750f78 | |||
afb76863de | |||
9a0dda8e77 | |||
99e53ed018 | |||
817648af09 | |||
991dd065f8 | |||
033150a442 | |||
be69a51bb2 | |||
9e8a7f5c14 | |||
ff52407ef9 | |||
5dc0ba2082 | |||
b4b41d3409 | |||
a1092c4de4 | |||
9896a560a9 | |||
c4af5e7f65 | |||
cb407365b0 |
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
# Nuxt dev/build outputs
|
||||
.output
|
||||
.output.*
|
||||
.data
|
||||
.nuxt
|
||||
.nitro
|
||||
|
53
Dockerfile
53
Dockerfile
@@ -1,32 +1,43 @@
|
||||
# ------------- 依赖缓存阶段 -------------
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
# 国内镜像
|
||||
############################################################
|
||||
# 1) 依赖层 - 只要锁文件没变,永远复用
|
||||
############################################################
|
||||
FROM node:22-alpine 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-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
# 拷贝构建产物 - Nuxt的output目录已经包含了所有运行时需要的文件
|
||||
COPY --from=builder /app/.output ./.output
|
||||
# 先复用 deps 里的 node_modules
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
# 再拷源码
|
||||
COPY . .
|
||||
|
||||
# 运行补丁
|
||||
RUN chmod +x patch-ui-pro.sh && ./patch-ui-pro.sh
|
||||
|
||||
# 注意把内存限制放在这里,避免本地开发时也被硬限制
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
RUN corepack enable && 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"]
|
||||
|
@@ -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: {
|
||||
|
@@ -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>
|
||||
|
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 可滚动的导航区域 -->
|
||||
<div class=" h-full overflow-y-auto ">
|
||||
<div class="h-full overflow-y-auto no-scrollbar">
|
||||
<!-- 导航 Section -->
|
||||
|
||||
<div>
|
||||
@@ -51,7 +51,7 @@
|
||||
<div class="mt-4 uppercase tracking-wider border-t border-gray-200 dark:border-gray-700 w-7/9 mx-5" />
|
||||
|
||||
<!-- 文档目录导航 -->
|
||||
<div class="mt-6 flex items-center justify-start pl-4 w-full pb-3">
|
||||
<div class="mt-5 flex items-center justify-start pl-2 pb-3">
|
||||
<UContentNavigation
|
||||
highlight
|
||||
:navigation="docsNavigation"
|
||||
@@ -59,6 +59,15 @@
|
||||
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>
|
||||
@@ -83,3 +92,16 @@ const docsNavigation = computed(() => {
|
||||
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>
|
||||
|
@@ -67,7 +67,9 @@ const handleLoginRegister = (type: 'login' | 'register') => {
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
<UContentSearchButton class="lg:hidden" />
|
||||
<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"
|
||||
|
@@ -5,8 +5,12 @@ const route = useRoute()
|
||||
const toast = useToast()
|
||||
const { copy, copied } = useClipboard()
|
||||
|
||||
const mdcLink = computed(() => `${window?.location?.origin}${decodeURIComponent(route.path)}`)
|
||||
const markdownLink = computed(() => `${window?.location?.origin}/raw${decodeURIComponent(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 items = [
|
||||
{
|
||||
|
108
app/components/shared/wxShare.vue
Normal file
108
app/components/shared/wxShare.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<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 {
|
||||
console.log('[WxShare] mounted with props:', { ...props })
|
||||
await loadWxSdk()
|
||||
console.log('[WxShare] wx sdk loaded:', !!(window as any).wx)
|
||||
const { appId, timestamp, nonceStr, signature } = await getWxConfig()
|
||||
console.log('[WxShare] got config:', { appId, timestamp, nonceStr, signature: signature.slice(0, 8) + '...' })
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
function loadWxSdk(): Promise<void> {
|
||||
console.log('loadWxSdk')
|
||||
if (typeof window === 'undefined') return Promise.resolve()
|
||||
if ((window as any).wx) return Promise.resolve()
|
||||
return new Promise((resolve, reject) => {
|
||||
const existing = document.getElementById('wx-jssdk') as HTMLScriptElement | null
|
||||
if (existing && (window as any).wx) 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 = (window as any).wx
|
||||
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: unknown) => {
|
||||
console.error('[WxShare] wx.error:', e)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="display: none" />
|
||||
<!-- 该组件无可视内容,仅用于初始化微信分享 -->
|
||||
</template>
|
@@ -98,12 +98,43 @@ const links = computed(() => {
|
||||
|
||||
return [...links, ...(appConfig.toc?.bottom?.links || [])].filter(Boolean)
|
||||
})
|
||||
|
||||
// ===== 微信分享(测试按钮用)=====
|
||||
const wxShareActive = ref(false)
|
||||
// const contentRoot = ref<HTMLElement | null>(null)
|
||||
|
||||
const shareLink = computed(() => 'https://lijue.me' + decodeURIComponent(path.value))
|
||||
const shareTitle = computed(() => title)
|
||||
const shareDesc = computed(() => description || title)
|
||||
const shareImg = page?.value?.img
|
||||
// const shareImg = ref<string>('/images/default-blog.jpg')
|
||||
|
||||
// onMounted(() => {
|
||||
// // 从正文中抓取第一张图片作为分享图
|
||||
// const el = contentRoot.value
|
||||
// const firstImg = el?.querySelector('img') as HTMLImageElement | null
|
||||
// if (firstImg?.src) {
|
||||
// shareImg.value = firstImg.src
|
||||
// }
|
||||
// })
|
||||
|
||||
// Toast:点击测试分享后给出指引
|
||||
const toast = useToast()
|
||||
function handleShareClick() {
|
||||
wxShareActive.value = true
|
||||
toast.add({
|
||||
title: '已获取分享接口',
|
||||
description: '点击右上角分享吧',
|
||||
duration: 3000,
|
||||
icon: 'i-lucide-share-2'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPage
|
||||
v-if="page"
|
||||
:class="['lg:mr-30 sm:mt-6', pageFontSizeClass]"
|
||||
:class="['lg:mr-30 lg:ml-5 sm:mt-6', pageFontSizeClass]"
|
||||
>
|
||||
<UPageHeader
|
||||
:title="page.title"
|
||||
@@ -147,7 +178,7 @@ const links = computed(() => {
|
||||
编辑页面
|
||||
</UButton>
|
||||
or
|
||||
<UButton
|
||||
<!-- <UButton
|
||||
variant="link"
|
||||
color="neutral"
|
||||
:to="`${appConfig.github.url}/issues/new/choose`"
|
||||
@@ -156,10 +187,29 @@ const links = computed(() => {
|
||||
:ui="{ leadingIcon: 'size-4' }"
|
||||
>
|
||||
提交问题
|
||||
</UButton> -->
|
||||
|
||||
<UButton
|
||||
variant="link"
|
||||
color="neutral"
|
||||
icon="lucide-share-2"
|
||||
:ui="{ leadingIcon: 'size-4' }"
|
||||
@click="handleShareClick()"
|
||||
>
|
||||
微信分享
|
||||
</UButton>
|
||||
</div>
|
||||
</USeparator>
|
||||
<UContentSurround :surround="surround" />
|
||||
|
||||
<!-- 激活后挂载分享组件(无可视内容) -->
|
||||
<SharedWxShare
|
||||
v-if="wxShareActive"
|
||||
:url="shareLink"
|
||||
:title="shareTitle"
|
||||
:desc="shareDesc"
|
||||
:img-url="shareImg"
|
||||
/>
|
||||
</UPageBody>
|
||||
|
||||
<template
|
||||
|
@@ -33,7 +33,7 @@ const queryPath = computed(() => {
|
||||
|
||||
const { data: page } = await useAsyncData(
|
||||
`page-${route.path}`, // 使用更具体的 key
|
||||
() => queryCollection('docs').path(decodeURIComponent(path.value)).first(),
|
||||
() => queryCollection('docs').path(queryPath.value).first(),
|
||||
{
|
||||
default: () => null // 提供默认值
|
||||
}
|
||||
@@ -43,8 +43,7 @@ if (!page.value) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: '文档不存在',
|
||||
message: `当前页面不存在,请您检查路径是否正确: ${queryPath.value}`,
|
||||
fatal: true
|
||||
message: `当前页面不存在,请您检查路径是否正确: ${queryPath.value}`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,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
|
||||
|
@@ -5,8 +5,7 @@ export default defineContentConfig({
|
||||
docs: defineCollection({
|
||||
type: 'page',
|
||||
source: {
|
||||
include: 'docs/**',
|
||||
exclude: ['/web/**']
|
||||
include: 'docs/**'
|
||||
},
|
||||
schema: z.object({
|
||||
rawbody: z.string(),
|
||||
@@ -21,8 +20,7 @@ export default defineContentConfig({
|
||||
blog: defineCollection({
|
||||
type: 'page',
|
||||
source: {
|
||||
include: 'blog/**',
|
||||
exclude: ['/web/**']
|
||||
include: 'blog/**'
|
||||
},
|
||||
schema: z.object({
|
||||
rawbody: z.string(),
|
||||
|
29
content/blog/1.技术栈/978.AI复合应用 合同审查.md
Normal file
29
content/blog/1.技术栈/978.AI复合应用 合同审查.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
title: AI复合应用 合同审查
|
||||
description: 智能合同卫士,使用AI技术,对合同进行审查,并给出审查报告.
|
||||
date: 2025-08-08
|
||||
img: https://lijue-me.oss-cn-chengdu.aliyuncs.com/20250808141452898.png
|
||||
navigation:
|
||||
icon: simple-icons:openai
|
||||
---
|
||||
|
||||
#### 这是一个 AI 复合应用
|
||||
暂起名叫 智能合同卫士 ,还是比较贴题的把?
|
||||
|
||||
#### 实现
|
||||
由 Deepseek V3 和 Kimi K2 协作完成此应用,2个好用不贵的模型
|
||||
DS 根据文件名,以及 审查主体 工作领域 与 合同要点,细化一份简要的审查要点
|
||||
主要工作由 Kimi K2 来完成:
|
||||
根据 Deepseek 细化的要点和 提取出来的文档内容,进行审查.
|
||||
输出审查后的结果
|
||||
|
||||
#### 改进
|
||||
按照初步的设想,后续还有一个模型,比如 Gemini2.5 ,或者 R1 ,Qwen3 之类的**推理模型**,进一步的审查 **Kimi K2 的审查结果**.
|
||||
不过 K2 单独使用的实际效果已经非常好了,为了节省时间和成本,暂时取掉最后一步.
|
||||
使用 AI 的话,不论是 R1 还是 K2 都是有一定的**幻觉**,在使用中必须搭配知识库来使用.
|
||||
这次测试并没有加知识库,所以 AI **虚构**了一些内容,比如当地房价涨幅 ,和虚构的 **《陕西省住房租赁管理办法》**.
|
||||
给大模型增加搜索能力,和在知识库里添加 **民法典** 等相关法律条文进去,可以有效的解决幻觉问题.
|
||||
|
||||
#### 测试结果
|
||||

|
||||
|
124
content/blog/1.技术栈/979.Kali 安装 GVM.md
Normal file
124
content/blog/1.技术栈/979.Kali 安装 GVM.md
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: Kali 安装 GVM
|
||||
description: 在 Kali 上安装 GVM
|
||||
date: 2025-08-08
|
||||
img: https://lijue-me.oss-cn-chengdu.aliyuncs.com/20250806105153532.png
|
||||
navigation:
|
||||
icon: simple-icons:kalilinux
|
||||
---
|
||||
|
||||
|
||||
### 配置安装好 Kali
|
||||

|
||||
```bash
|
||||
# 安装 gvm
|
||||
sudo apt install gvm -y
|
||||
|
||||
# 执行配置脚本
|
||||
sudo gvm-setup
|
||||
```
|
||||
|
||||
### 安装过程中 copy admin 的密码
|
||||

|
||||
|
||||
根据提示运行安装检测命令
|
||||
```bash
|
||||
sudo gvm-check-setup
|
||||
```
|
||||
|
||||
报错了.
|
||||
|
||||
```bash
|
||||
└─**$** 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
|
||||
sudo greenbone-feed-sync --type scap
|
||||
```
|
||||
|
||||
下载好后再次运行检测命令,一起无误后,访问 127.0.0.1:9293 即可
|
||||
等等...我的 Kali 是安装在其他设备里的,如何通过局域网访问?
|
||||
|
||||
```bash
|
||||
sudo nano /lib/systemd/system/greenbone-security-assistant.service
|
||||
```
|
||||
|
||||
```bash
|
||||
# 修改下面的 --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
|
||||
# 重新开始服务
|
||||
sudo gvm-start
|
||||
```
|
||||
|
||||
随后等待程序自动更新提要状态,需要时间非常久.建议释放魔法.
|
||||
如果自动更新失败,可以手动更新:
|
||||
|
||||
```bash
|
||||
sudo greenbone-feed-sync
|
||||
```
|
||||
|
||||
### END
|
||||
至此安装完毕.
|
87
content/blog/1.技术栈/980.GPT5.md
Normal file
87
content/blog/1.技术栈/980.GPT5.md
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
title: GPT-5
|
||||
description: GPT-5 上线了,Cursor 提示我可以免费试用,当然要 BP 喽。
|
||||
date: 2025-08-08
|
||||
img: https://lijue-me.oss-cn-chengdu.aliyuncs.com/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**,现在又多了一个选择了。
|
||||
|
||||
---
|
||||
|
||||

|
||||

|
||||

|
@@ -45,7 +45,7 @@ navigation:
|
||||
请一次性提供完整的HTML文件代码(包括HTML、CSS和JavaScript),代码需可直接保存为.html文件并在浏览器中运行。代码中需包含注释,体现逻辑清晰度和编程能力。
|
||||
|
||||
GROK3:
|
||||
```
|
||||
```html[GROK3.html]
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="zh-CN">
|
||||
@@ -464,7 +464,7 @@ GROK3:
|
||||
```
|
||||
|
||||
R1:
|
||||
```
|
||||
```html[R1.html]
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="zh-CN">
|
@@ -1,238 +1,190 @@
|
||||
---
|
||||
title: 记录AI
|
||||
description: 学习 AI 相关知识
|
||||
date: 2025-08-07
|
||||
title: Coolify
|
||||
description: Coolify是什么?
|
||||
date: 2025-07-14
|
||||
img: https://lijue-me.oss-cn-chengdu.aliyuncs.com/20250628122847084.png
|
||||
navigation:
|
||||
icon: lucide-house
|
||||
icon: simple-icons:chai
|
||||
---
|
||||
# 概述
|
||||
这是一份提供给AI大模型的Python代码规范与编程标准,可以有效提高Cursor等大模型对Python项目的编写能力。
|
||||
|
||||
## 博客
|
||||
---
|
||||
description: Python开发综合指南,涵盖代码组织、性能、安全性、测试等内容。这些规则旨在促进可维护、高效且安全的Python代码库。
|
||||
globs: *.py
|
||||
---
|
||||
# Python最佳实践与编码规范
|
||||
|
||||
使用过市面上的很多文档系统,但是或多或少都有一些自己不满意的功能.
|
||||
于是自己动手,丰衣足食.
|
||||
同时在 Markdown 语法的基础上增加了许多**魔法(自定义组件)**,比如:
|
||||
本文档概述了Python开发的综合最佳实践和编码标准,旨在促进编写干净、高效、可维护和安全的代码。
|
||||
|
||||
## 1. 代码组织与结构
|
||||
|
||||
::code-group
|
||||
```mdc [index.md]
|
||||
在 Markdown 中使用::card 标签,即可创建一个卡片,卡片里可以放置任何内容。比如以下内容:
|
||||
::card
|
||||
这里是卡片里的内容
|
||||
::
|
||||
```
|
||||
### 1.1 目录结构最佳实践
|
||||
|
||||
```html [Card.vue]
|
||||
<!-- Card组件: components/content/Card.vue -->
|
||||
<template>
|
||||
<div class="p-2 border bg-white dark:bg-black dark:border-gray-700 rounded">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
:::code-preview{icon="i-lucide-eye" label="Preview"}
|
||||
::::example-card
|
||||
这里是卡片里的内容
|
||||
::::
|
||||
:::
|
||||
* **扁平结构优于嵌套(但不绝对)。** 从简单结构开始,按需重构
|
||||
* **包与模块:** 使用包(包含`__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
|
||||
|
||||
:::UPageCard{icon="lucide-code" title="代码组" description="这个组件使用 自定义的 Markdown 语法,从 Markdown 中提取代码块,并展示在页面上 (而其自身亦是一个卡片组)"}
|
||||
### 1.2 文件命名规范
|
||||
|
||||
::code-tree{defaultValue="nuxt.config.ts"}
|
||||
* **模块:** 小写字母,使用下划线增强可读性(如`my_module.py`)
|
||||
* **包:** 全小写(如`my_package`),非必要不使用下划线
|
||||
* **测试文件:** 以`test_`开头(如`test_my_module.py`)
|
||||
|
||||
```css [app/assets/main.css]
|
||||
@import "tailwindcss" theme(static);
|
||||
@import "@nuxt/ui-pro";
|
||||
```
|
||||
### 1.3 模块组织最佳实践
|
||||
|
||||
```ts [app/app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'sky',
|
||||
colors: 'slate'
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
* **单一职责原则:** 每个模块应有明确定义的用途
|
||||
* **导入规范:**
|
||||
* 顺序:标准库→第三方库→本地模块
|
||||
* 优先使用绝对导入(如`from my_package.module1 import function1`)
|
||||
* 在复杂包结构中需明确相对导入时使用显式相对导入(`from . import sibling_module`)
|
||||
* **常量:** 使用全大写定义模块级常量(如`MAX_ITERATIONS = 100`)
|
||||
* **双下划线名称:** `__all__`、`__version__`等应放在模块文档字符串之后、所有导入之前(`from __future__`除外)。使用`__all__`显式声明公共API
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<UApp>
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
### 1.4 组件架构建议
|
||||
|
||||
```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"
|
||||
}
|
||||
```
|
||||
### 1.5 代码分割策略
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui-pro'],
|
||||
* **按功能拆分:** 基于不同功能划分模块(如用户管理、数据处理)
|
||||
* **按层级拆分:** 分离表现层、业务逻辑层和数据访问代码
|
||||
* **懒加载:** 使用`importlib.import_module()`实现按需加载,优化启动时间
|
||||
* **条件导入:** 根据特定条件导入模块
|
||||
|
||||
future: {
|
||||
compatibilityVersion: 4
|
||||
},
|
||||
## 2. 常见模式与反模式
|
||||
|
||||
css: ['~/assets/main.css']
|
||||
});
|
||||
```
|
||||
### 2.1 设计模式
|
||||
|
||||
````md [README.md]
|
||||
# Nuxt 3 Minimal Starter
|
||||
* **单例模式:** 限制类只能实例化一个对象
|
||||
* **工厂模式:** 创建对象时无需指定具体类
|
||||
* **观察者模式:** 建立对象间一对多依赖关系
|
||||
* **策略模式:** 定义算法族并使其可互换
|
||||
* **装饰器模式:** 动态扩展对象功能
|
||||
* **上下文管理器:** 确保资源正确清理(如自动关闭文件)
|
||||
|
||||
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
### 2.2 常见任务的推荐方案
|
||||
|
||||
## Setup
|
||||
* **数据验证:** 使用`pydantic`或`marshmallow`等库
|
||||
* **配置管理:** 使用`python-decouple`、`dynaconf`或标准库的`configparser`
|
||||
* **日志记录:** 使用`logging`模块实现结构化日志
|
||||
* **命令行接口:** 使用`argparse`、`click`或`typer`
|
||||
* **异步编程:** 使用`asyncio`处理I/O密集型任务
|
||||
|
||||
Make sure to install the dependencies:
|
||||
### 2.3 反模式与代码异味
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
* **上帝类:** 承担过多职责的类,应拆分为专注单一功能的小类
|
||||
* **霰弹式变更:** 需在多处做小修改,表明内聚性不足
|
||||
* **面条代码:** 结构混乱难以追踪,应重构为定义明确的函数/类
|
||||
* **重复代码:** 提取公共代码为可复用函数/类(遵循DRY原则)
|
||||
* **魔法数值/字符串:** 使用命名常量替代硬编码值
|
||||
* **过早优化:** 避免在没有性能瓶颈分析前提下的优化
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
### 2.4 状态管理最佳实践
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
* **无状态函数:** 尽可能使用无状态函数
|
||||
* **不可变数据:** 使用不可变数据结构防止意外修改
|
||||
* **显式状态:** 使用类或数据结构明确管理状态,避免全局变量
|
||||
* **上下文变量:** Python 3.7+可使用`contextvars`管理异步应用中的请求级状态
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
### 2.5 错误处理模式
|
||||
|
||||
## Development Server
|
||||
* **捕获特定异常:** 避免笼统捕获`Exception`或`BaseException`
|
||||
* **资源清理:** 使用`finally`确保清理代码必执行
|
||||
* **异常日志:** 记录完整堆栈信息
|
||||
* **异常消息:** 抛出包含明确错误信息的异常
|
||||
* **避免异常控制流:** 异常应用于处理意外情况而非常规流程
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
## 3. 性能优化
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
### 3.1 优化技术
|
||||
|
||||
# pnpm
|
||||
pnpm run dev
|
||||
* **性能分析:** 使用`cProfile`定位瓶颈
|
||||
* **高效数据结构:** 根据场景选择(如`set`用于成员测试、`dict`用于查找)
|
||||
* **列表推导式与生成器:** 编写简洁高效的代码
|
||||
* **NumPy向量化:** 对数值计算使用向量化操作
|
||||
* **即时编译:** 性能关键代码考虑使用Numba等JIT编译器
|
||||
* **字符串拼接:** 使用`''.join(iterable)`高效拼接字符串
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
### 3.2 内存管理
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
* **内存分析:** 使用`memory_profiler`定位内存泄漏
|
||||
* **`__slots__`:** 减少类实例的内存占用
|
||||
* **生成器:** 处理大数据集时避免全部加载到内存
|
||||
|
||||
## Production
|
||||
## 4. 安全性最佳实践
|
||||
|
||||
Build the application for production:
|
||||
### 4.1 常见漏洞防范
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
* **SQL注入:** 使用参数化查询或ORM
|
||||
* **XSS攻击:** 对用户输入消毒并转义输出
|
||||
* **CSRF防护:** 使用CSRF令牌
|
||||
* **依赖漏洞:** 定期审计和更新依赖项
|
||||
* **硬编码密钥:** 禁止在代码中硬编码密码/API密钥,使用环境变量管理
|
||||
|
||||
# pnpm
|
||||
pnpm run build
|
||||
### 4.2 API安全通信
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
* **强制HTTPS:** 所有API通信必须加密
|
||||
* **速率限制:** 防止接口滥用
|
||||
* **输入验证:** 处理前验证所有API请求
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
## 5. 测试策略
|
||||
|
||||
Locally preview production build:
|
||||
### 5.1 单元测试要点
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
* **测试粒度:** 隔离测试单个函数/类/模块
|
||||
* **边界条件:** 特别测试边界情况和异常场景
|
||||
* **测试覆盖率:** 追求高覆盖率但避免教条化
|
||||
|
||||
# pnpm
|
||||
pnpm run preview
|
||||
### 5.2 集成测试建议
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
* **聚焦关键流程:** 关注核心用户场景
|
||||
* **模拟外部服务:** 使用mock替代真实外部依赖
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
## 6. 常见陷阱
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
````
|
||||
### 6.1 高频错误
|
||||
|
||||
::
|
||||
:::
|
||||
* **可变默认参数:** 函数定义中避免使用可变对象作为默认值
|
||||
* **变量作用域:** 注意嵌套函数中的变量作用域
|
||||
* **忽略异常:** 禁止直接忽略未处理的异常
|
||||
* **虚拟环境:** 必须使用虚拟环境管理项目依赖
|
||||
|
||||
::code-group
|
||||
```md [搜索.md]
|
||||
#### 此组件打开一个模态搜索框,进行搜索
|
||||
::::example-fulltext-content-search
|
||||
::::
|
||||
## 7. 工具与环境
|
||||
|
||||
#### 此组件建立一个mini搜索框,进行搜索
|
||||
::::example-fulltext-mini-search
|
||||
::::
|
||||
```
|
||||
### 7.1 推荐工具链
|
||||
|
||||
:::code-preview{label="预览模态组件" icon="i-lucide-eye"}
|
||||
::::example-fulltext-content-search
|
||||
::::
|
||||
:::
|
||||
:::code-preview{label="预览搜索框组件" icon="i-lucide-eye"}
|
||||
::::example-fulltext-mini-search
|
||||
::::
|
||||
:::
|
||||
|
||||
::
|
||||
* **IDE:** PyCharm、VS Code(搭配Python插件)
|
||||
* **包管理:** `pip`、`poetry`
|
||||
* **格式化:** `black`、`autopep8`
|
||||
* **静态检查:** `mypy`、`pylint`
|
||||
|
||||
### 7.2 CI/CD集成
|
||||
|
||||
::tip{icon="lucide-info" color="primary" class="text-black dark:text-white"}
|
||||
阅读更多关于 [`自定义组件`](/简单文档/components/api) 的内容.
|
||||
::
|
||||
* **自动化测试:** 每次提交自动运行测试套件
|
||||
* **代码质量门禁:** 集成静态分析工具到流水线
|
||||
|
||||
|
||||
## 特性
|
||||
|
||||
- 基于 Nuxt 4 , Content v3 , Nuxt UI Pro 构建的文档系统。
|
||||
- 完美支持 Markdown 和 相关扩展。
|
||||
- 和 Vue 组件高度集成。
|
||||
- 支持搜索,由Content v3 赋能。
|
||||
- 支持多主题,使用Nuxt UI Pro。
|
||||
- 开源且免费。
|
||||
- 支持移动端。
|
||||
|
||||
## 致谢
|
||||
|
||||
- Nuxt Content:为 Vue 开发者简化内容管理。
|
||||
- Nuxt UI Pro :文档系统 UI 组件。
|
||||
- Docus:获取灵感及一些文档组件源代码。
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
遵循这些规范和最佳实践,开发者能够构建出更健壮、可维护且安全的Python应用。
|
@@ -1,238 +1,190 @@
|
||||
---
|
||||
title: 记录生活
|
||||
description: 记录生活点点滴滴
|
||||
date: 2025-08-07
|
||||
title: Coolify
|
||||
description: Coolify是什么?
|
||||
date: 2025-07-14
|
||||
img: https://lijue-me.oss-cn-chengdu.aliyuncs.com/20250628122847084.png
|
||||
navigation:
|
||||
icon: lucide-house
|
||||
icon: simple-icons:chai
|
||||
---
|
||||
# 概述
|
||||
这是一份提供给AI大模型的Python代码规范与编程标准,可以有效提高Cursor等大模型对Python项目的编写能力。
|
||||
|
||||
## 博客
|
||||
---
|
||||
description: Python开发综合指南,涵盖代码组织、性能、安全性、测试等内容。这些规则旨在促进可维护、高效且安全的Python代码库。
|
||||
globs: *.py
|
||||
---
|
||||
# Python最佳实践与编码规范
|
||||
|
||||
使用过市面上的很多文档系统,但是或多或少都有一些自己不满意的功能.
|
||||
于是自己动手,丰衣足食.
|
||||
同时在 Markdown 语法的基础上增加了许多**魔法(自定义组件)**,比如:
|
||||
本文档概述了Python开发的综合最佳实践和编码标准,旨在促进编写干净、高效、可维护和安全的代码。
|
||||
|
||||
## 1. 代码组织与结构
|
||||
|
||||
::code-group
|
||||
```mdc [index.md]
|
||||
在 Markdown 中使用::card 标签,即可创建一个卡片,卡片里可以放置任何内容。比如以下内容:
|
||||
::card
|
||||
这里是卡片里的内容
|
||||
::
|
||||
```
|
||||
### 1.1 目录结构最佳实践
|
||||
|
||||
```html [Card.vue]
|
||||
<!-- Card组件: components/content/Card.vue -->
|
||||
<template>
|
||||
<div class="p-2 border bg-white dark:bg-black dark:border-gray-700 rounded">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
:::code-preview{icon="i-lucide-eye" label="Preview"}
|
||||
::::example-card
|
||||
这里是卡片里的内容
|
||||
::::
|
||||
:::
|
||||
* **扁平结构优于嵌套(但不绝对)。** 从简单结构开始,按需重构
|
||||
* **包与模块:** 使用包(包含`__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
|
||||
|
||||
:::UPageCard{icon="lucide-code" title="代码组" description="这个组件使用 自定义的 Markdown 语法,从 Markdown 中提取代码块,并展示在页面上 (而其自身亦是一个卡片组)"}
|
||||
### 1.2 文件命名规范
|
||||
|
||||
::code-tree{defaultValue="nuxt.config.ts"}
|
||||
* **模块:** 小写字母,使用下划线增强可读性(如`my_module.py`)
|
||||
* **包:** 全小写(如`my_package`),非必要不使用下划线
|
||||
* **测试文件:** 以`test_`开头(如`test_my_module.py`)
|
||||
|
||||
```css [app/assets/main.css]
|
||||
@import "tailwindcss" theme(static);
|
||||
@import "@nuxt/ui-pro";
|
||||
```
|
||||
### 1.3 模块组织最佳实践
|
||||
|
||||
```ts [app/app.config.ts]
|
||||
export default defineAppConfig({
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'sky',
|
||||
colors: 'slate'
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
* **单一职责原则:** 每个模块应有明确定义的用途
|
||||
* **导入规范:**
|
||||
* 顺序:标准库→第三方库→本地模块
|
||||
* 优先使用绝对导入(如`from my_package.module1 import function1`)
|
||||
* 在复杂包结构中需明确相对导入时使用显式相对导入(`from . import sibling_module`)
|
||||
* **常量:** 使用全大写定义模块级常量(如`MAX_ITERATIONS = 100`)
|
||||
* **双下划线名称:** `__all__`、`__version__`等应放在模块文档字符串之后、所有导入之前(`from __future__`除外)。使用`__all__`显式声明公共API
|
||||
|
||||
```vue [app/app.vue]
|
||||
<template>
|
||||
<UApp>
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
```
|
||||
### 1.4 组件架构建议
|
||||
|
||||
```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"
|
||||
}
|
||||
```
|
||||
### 1.5 代码分割策略
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
modules: ['@nuxt/ui-pro'],
|
||||
* **按功能拆分:** 基于不同功能划分模块(如用户管理、数据处理)
|
||||
* **按层级拆分:** 分离表现层、业务逻辑层和数据访问代码
|
||||
* **懒加载:** 使用`importlib.import_module()`实现按需加载,优化启动时间
|
||||
* **条件导入:** 根据特定条件导入模块
|
||||
|
||||
future: {
|
||||
compatibilityVersion: 4
|
||||
},
|
||||
## 2. 常见模式与反模式
|
||||
|
||||
css: ['~/assets/main.css']
|
||||
});
|
||||
```
|
||||
### 2.1 设计模式
|
||||
|
||||
````md [README.md]
|
||||
# Nuxt 3 Minimal Starter
|
||||
* **单例模式:** 限制类只能实例化一个对象
|
||||
* **工厂模式:** 创建对象时无需指定具体类
|
||||
* **观察者模式:** 建立对象间一对多依赖关系
|
||||
* **策略模式:** 定义算法族并使其可互换
|
||||
* **装饰器模式:** 动态扩展对象功能
|
||||
* **上下文管理器:** 确保资源正确清理(如自动关闭文件)
|
||||
|
||||
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||
### 2.2 常见任务的推荐方案
|
||||
|
||||
## Setup
|
||||
* **数据验证:** 使用`pydantic`或`marshmallow`等库
|
||||
* **配置管理:** 使用`python-decouple`、`dynaconf`或标准库的`configparser`
|
||||
* **日志记录:** 使用`logging`模块实现结构化日志
|
||||
* **命令行接口:** 使用`argparse`、`click`或`typer`
|
||||
* **异步编程:** 使用`asyncio`处理I/O密集型任务
|
||||
|
||||
Make sure to install the dependencies:
|
||||
### 2.3 反模式与代码异味
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm install
|
||||
* **上帝类:** 承担过多职责的类,应拆分为专注单一功能的小类
|
||||
* **霰弹式变更:** 需在多处做小修改,表明内聚性不足
|
||||
* **面条代码:** 结构混乱难以追踪,应重构为定义明确的函数/类
|
||||
* **重复代码:** 提取公共代码为可复用函数/类(遵循DRY原则)
|
||||
* **魔法数值/字符串:** 使用命名常量替代硬编码值
|
||||
* **过早优化:** 避免在没有性能瓶颈分析前提下的优化
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
### 2.4 状态管理最佳实践
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
* **无状态函数:** 尽可能使用无状态函数
|
||||
* **不可变数据:** 使用不可变数据结构防止意外修改
|
||||
* **显式状态:** 使用类或数据结构明确管理状态,避免全局变量
|
||||
* **上下文变量:** Python 3.7+可使用`contextvars`管理异步应用中的请求级状态
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
### 2.5 错误处理模式
|
||||
|
||||
## Development Server
|
||||
* **捕获特定异常:** 避免笼统捕获`Exception`或`BaseException`
|
||||
* **资源清理:** 使用`finally`确保清理代码必执行
|
||||
* **异常日志:** 记录完整堆栈信息
|
||||
* **异常消息:** 抛出包含明确错误信息的异常
|
||||
* **避免异常控制流:** 异常应用于处理意外情况而非常规流程
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
## 3. 性能优化
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run dev
|
||||
### 3.1 优化技术
|
||||
|
||||
# pnpm
|
||||
pnpm run dev
|
||||
* **性能分析:** 使用`cProfile`定位瓶颈
|
||||
* **高效数据结构:** 根据场景选择(如`set`用于成员测试、`dict`用于查找)
|
||||
* **列表推导式与生成器:** 编写简洁高效的代码
|
||||
* **NumPy向量化:** 对数值计算使用向量化操作
|
||||
* **即时编译:** 性能关键代码考虑使用Numba等JIT编译器
|
||||
* **字符串拼接:** 使用`''.join(iterable)`高效拼接字符串
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
### 3.2 内存管理
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
* **内存分析:** 使用`memory_profiler`定位内存泄漏
|
||||
* **`__slots__`:** 减少类实例的内存占用
|
||||
* **生成器:** 处理大数据集时避免全部加载到内存
|
||||
|
||||
## Production
|
||||
## 4. 安全性最佳实践
|
||||
|
||||
Build the application for production:
|
||||
### 4.1 常见漏洞防范
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
* **SQL注入:** 使用参数化查询或ORM
|
||||
* **XSS攻击:** 对用户输入消毒并转义输出
|
||||
* **CSRF防护:** 使用CSRF令牌
|
||||
* **依赖漏洞:** 定期审计和更新依赖项
|
||||
* **硬编码密钥:** 禁止在代码中硬编码密码/API密钥,使用环境变量管理
|
||||
|
||||
# pnpm
|
||||
pnpm run build
|
||||
### 4.2 API安全通信
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
* **强制HTTPS:** 所有API通信必须加密
|
||||
* **速率限制:** 防止接口滥用
|
||||
* **输入验证:** 处理前验证所有API请求
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
```
|
||||
## 5. 测试策略
|
||||
|
||||
Locally preview production build:
|
||||
### 5.1 单元测试要点
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
* **测试粒度:** 隔离测试单个函数/类/模块
|
||||
* **边界条件:** 特别测试边界情况和异常场景
|
||||
* **测试覆盖率:** 追求高覆盖率但避免教条化
|
||||
|
||||
# pnpm
|
||||
pnpm run preview
|
||||
### 5.2 集成测试建议
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
* **聚焦关键流程:** 关注核心用户场景
|
||||
* **模拟外部服务:** 使用mock替代真实外部依赖
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
```
|
||||
## 6. 常见陷阱
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
````
|
||||
### 6.1 高频错误
|
||||
|
||||
::
|
||||
:::
|
||||
* **可变默认参数:** 函数定义中避免使用可变对象作为默认值
|
||||
* **变量作用域:** 注意嵌套函数中的变量作用域
|
||||
* **忽略异常:** 禁止直接忽略未处理的异常
|
||||
* **虚拟环境:** 必须使用虚拟环境管理项目依赖
|
||||
|
||||
::code-group
|
||||
```md [搜索.md]
|
||||
#### 此组件打开一个模态搜索框,进行搜索
|
||||
::::example-fulltext-content-search
|
||||
::::
|
||||
## 7. 工具与环境
|
||||
|
||||
#### 此组件建立一个mini搜索框,进行搜索
|
||||
::::example-fulltext-mini-search
|
||||
::::
|
||||
```
|
||||
### 7.1 推荐工具链
|
||||
|
||||
:::code-preview{label="预览模态组件" icon="i-lucide-eye"}
|
||||
::::example-fulltext-content-search
|
||||
::::
|
||||
:::
|
||||
:::code-preview{label="预览搜索框组件" icon="i-lucide-eye"}
|
||||
::::example-fulltext-mini-search
|
||||
::::
|
||||
:::
|
||||
|
||||
::
|
||||
* **IDE:** PyCharm、VS Code(搭配Python插件)
|
||||
* **包管理:** `pip`、`poetry`
|
||||
* **格式化:** `black`、`autopep8`
|
||||
* **静态检查:** `mypy`、`pylint`
|
||||
|
||||
### 7.2 CI/CD集成
|
||||
|
||||
::tip{icon="lucide-info" color="primary" class="text-black dark:text-white"}
|
||||
阅读更多关于 [`自定义组件`](/简单文档/components/api) 的内容.
|
||||
::
|
||||
* **自动化测试:** 每次提交自动运行测试套件
|
||||
* **代码质量门禁:** 集成静态分析工具到流水线
|
||||
|
||||
|
||||
## 特性
|
||||
|
||||
- 基于 Nuxt 4 , Content v3 , Nuxt UI Pro 构建的文档系统。
|
||||
- 完美支持 Markdown 和 相关扩展。
|
||||
- 和 Vue 组件高度集成。
|
||||
- 支持搜索,由Content v3 赋能。
|
||||
- 支持多主题,使用Nuxt UI Pro。
|
||||
- 开源且免费。
|
||||
- 支持移动端。
|
||||
|
||||
## 致谢
|
||||
|
||||
- Nuxt Content:为 Vue 开发者简化内容管理。
|
||||
- Nuxt UI Pro :文档系统 UI 组件。
|
||||
- Docus:获取灵感及一些文档组件源代码。
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
遵循这些规范和最佳实践,开发者能够构建出更健壮、可维护且安全的Python应用。
|
@@ -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) 的内容.
|
||||
::
|
||||
|
||||
|
||||
|
@@ -1,17 +0,0 @@
|
||||
[phases.setup]
|
||||
nixPkgs = [
|
||||
"nodejs_22", # Nixpacks 已帮你加,这里显性写出来安全
|
||||
"sqlite" # 预编译好的 sqlite 库
|
||||
]
|
||||
|
||||
[phases.install]
|
||||
cmds = [
|
||||
"sudo apt-get update -y",
|
||||
"sudo apt-get install -y build-essential python3", # 上 gcc/make/python
|
||||
"pnpm config set script-shell '/bin/bash'", # 避免 sh -c 失环境
|
||||
"pnpm install --frozen-lockfile"
|
||||
]
|
||||
|
||||
# 可选:编译缓存,构建更快
|
||||
[phases.build]
|
||||
cacheDirectories = [ "node_modules/.cache" ]
|
@@ -20,7 +20,7 @@ export default defineNuxtConfig({
|
||||
build: {
|
||||
markdown: {
|
||||
toc: {
|
||||
searchDepth: 1
|
||||
searchDepth: 2
|
||||
},
|
||||
highlight: {
|
||||
langs:
|
||||
@@ -42,21 +42,22 @@ export default defineNuxtConfig({
|
||||
remove: /[$*+~()'"!\-=#?:@.]/g
|
||||
}
|
||||
}
|
||||
},
|
||||
preview: {
|
||||
dev: true,
|
||||
api: 'https://api.nuxt.studio'
|
||||
}
|
||||
},
|
||||
routeRules: {
|
||||
'/': { static: true },
|
||||
'/docs/**': { ssr: false },
|
||||
'/blog/**': { ssr: false },
|
||||
'/raw/**': { ssr: false }
|
||||
},
|
||||
|
||||
compatibilityDate: '2024-07-11',
|
||||
|
||||
nitro: {
|
||||
prerender: {
|
||||
routes: [
|
||||
'/'
|
||||
],
|
||||
routes: ['/'],
|
||||
crawlLinks: true,
|
||||
failOnError: false,
|
||||
autoSubfolderIndex: false
|
||||
}
|
||||
},
|
||||
@@ -82,7 +83,7 @@ export default defineNuxtConfig({
|
||||
}
|
||||
},
|
||||
llms: {
|
||||
domain: 'https://docs.jiwei.xin',
|
||||
domain: 'https://lijue.me',
|
||||
title: 'Estel Docs',
|
||||
description: 'Estel Docs 文档系统',
|
||||
sections: [
|
||||
|
1
public/MP_verify_XJytJeqSNv67T3iY.txt
Normal file
1
public/MP_verify_XJytJeqSNv67T3iY.txt
Normal file
@@ -0,0 +1 @@
|
||||
XJytJeqSNv67T3iY
|
156
server/api/wechat/wx-config.get.ts
Normal file
156
server/api/wechat/wx-config.get.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { defineEventHandler, getQuery, setResponseHeader, createError } from 'h3'
|
||||
import { createHash, randomBytes } from 'node:crypto'
|
||||
|
||||
type AccessTokenResponse = {
|
||||
access_token: string
|
||||
expires_in: number
|
||||
errcode?: number
|
||||
errmsg?: string
|
||||
}
|
||||
|
||||
type JsapiTicketResponse = {
|
||||
errcode: number
|
||||
errmsg: string
|
||||
ticket: string
|
||||
expires_in: number
|
||||
}
|
||||
|
||||
type WxConfigPayload = {
|
||||
appId: string
|
||||
timestamp: number
|
||||
nonceStr: string
|
||||
signature: string
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/wechat/jssdk-config?url=<encoded_url>
|
||||
*
|
||||
* 仅新增本文件,不修改其他任何代码。
|
||||
* 从环境变量读取:
|
||||
* - WECHAT_APP_ID
|
||||
* - WECHAT_APP_SECRET
|
||||
*
|
||||
* 按微信官方文档生成 JS-SDK 所需签名配置:appId、timestamp、nonceStr、signature。
|
||||
* 会在内存中缓存 access_token 与 jsapi_ticket,缓存有效期比官方返回略短(预留安全缓冲)。
|
||||
*
|
||||
* 参考文档:
|
||||
* - JS-SDK 使用步骤(签名计算与 ticket 缓存):https://developers.weixin.qq.com/doc/service/guide/h5/jssdk.html#JSSDK%E4%BD%BF%E7%94%A8%E6%AD%A5%E9%AA%A4
|
||||
*/
|
||||
|
||||
const TOKEN_SAFETY_BUFFER_SECONDS = 300 // 5 分钟安全缓冲
|
||||
|
||||
let cachedAccessToken: { value: string, expiresAt: number } | null = null
|
||||
let cachedJsapiTicket: { value: string, expiresAt: number } | null = null
|
||||
|
||||
function isExpired(cache: { expiresAt: number } | null): boolean {
|
||||
if (!cache) return true
|
||||
return Date.now() >= cache.expiresAt
|
||||
}
|
||||
|
||||
async function fetchAccessToken(appId: string, appSecret: string): Promise<string> {
|
||||
if (!isExpired(cachedAccessToken)) {
|
||||
return cachedAccessToken!.value
|
||||
}
|
||||
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${encodeURIComponent(appId)}&secret=${encodeURIComponent(appSecret)}`
|
||||
const response = await $fetch<AccessTokenResponse>(url, { method: 'GET' })
|
||||
|
||||
if (!response || !response.access_token) {
|
||||
throw createError({
|
||||
statusCode: 502,
|
||||
statusMessage: `Failed to obtain access_token: ${response?.errcode ?? ''} ${response?.errmsg ?? ''}`.trim()
|
||||
})
|
||||
}
|
||||
|
||||
const expiresInSeconds = Math.max(0, (response.expires_in ?? 7200) - TOKEN_SAFETY_BUFFER_SECONDS)
|
||||
cachedAccessToken = {
|
||||
value: response.access_token,
|
||||
expiresAt: Date.now() + expiresInSeconds * 1000
|
||||
}
|
||||
|
||||
return response.access_token
|
||||
}
|
||||
|
||||
async function fetchJsapiTicket(accessToken: string): Promise<string> {
|
||||
if (!isExpired(cachedJsapiTicket)) {
|
||||
return cachedJsapiTicket!.value
|
||||
}
|
||||
|
||||
const url = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${encodeURIComponent(accessToken)}&type=jsapi`
|
||||
const response = await $fetch<JsapiTicketResponse>(url, { method: 'GET' })
|
||||
|
||||
if (!response || response.errcode !== 0 || !response.ticket) {
|
||||
throw createError({
|
||||
statusCode: 502,
|
||||
statusMessage: `Failed to obtain jsapi_ticket: ${response?.errcode ?? ''} ${response?.errmsg ?? ''}`.trim()
|
||||
})
|
||||
}
|
||||
|
||||
const expiresInSeconds = Math.max(0, (response.expires_in ?? 7200) - TOKEN_SAFETY_BUFFER_SECONDS)
|
||||
cachedJsapiTicket = {
|
||||
value: response.ticket,
|
||||
expiresAt: Date.now() + expiresInSeconds * 1000
|
||||
}
|
||||
|
||||
return response.ticket
|
||||
}
|
||||
|
||||
function generateNonceStr(bytes: number = 16): string {
|
||||
// 生成由 [a-z0-9] 组成的随机字符串
|
||||
return randomBytes(bytes).toString('hex')
|
||||
}
|
||||
|
||||
function generateTimestamp(): number {
|
||||
return Math.floor(Date.now() / 1000)
|
||||
}
|
||||
|
||||
function buildSignature(jsapiTicket: string, nonceStr: string, timestamp: number, url: string): string {
|
||||
// 注意:参数名必须为全小写,且按字典序构造字符串
|
||||
// 官方示例顺序:jsapi_ticket, noncestr, timestamp, url
|
||||
const rawString = `jsapi_ticket=${jsapiTicket}&noncestr=${nonceStr}×tamp=${timestamp}&url=${url}`
|
||||
return createHash('sha1').update(rawString).digest('hex')
|
||||
}
|
||||
|
||||
export default defineEventHandler(async (event): Promise<WxConfigPayload> => {
|
||||
// 避免代理缓存
|
||||
setResponseHeader(event, 'Cache-Control', 'no-store')
|
||||
|
||||
const appId = process.env.WECHAT_APP_ID || ''
|
||||
const appSecret = process.env.WECHAT_APP_SECRET || ''
|
||||
|
||||
if (!appId || !appSecret) {
|
||||
throw createError({ statusCode: 500, statusMessage: 'Missing WECHAT_APP_ID or WECHAT_APP_SECRET in environment.' })
|
||||
}
|
||||
|
||||
const query = getQuery(event)
|
||||
let pageUrlRaw = typeof query.url === 'string' ? query.url : ''
|
||||
// 若未传入 url,则使用当前请求完整地址(去除 hash),通常建议前端传入当前页面 URL
|
||||
if (!pageUrlRaw) {
|
||||
const requestUrl = event.node.req.headers['x-forwarded-proto'] && event.node.req.headers['x-forwarded-host']
|
||||
? `${event.node.req.headers['x-forwarded-proto']}://${event.node.req.headers['x-forwarded-host']}${event.node.req.url ?? ''}`
|
||||
: new URL(event.node.req.url ?? '/', `http://${event.node.req.headers.host}`).toString()
|
||||
pageUrlRaw = requestUrl
|
||||
}
|
||||
|
||||
const pageUrlNoHash = pageUrlRaw.includes('#')
|
||||
? pageUrlRaw.slice(0, pageUrlRaw.indexOf('#'))
|
||||
: pageUrlRaw
|
||||
const pageUrl = decodeURIComponent(pageUrlNoHash)
|
||||
if (!/^https?:\/\//i.test(pageUrl)) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Invalid url parameter. Expecting an absolute http/https URL.' })
|
||||
}
|
||||
|
||||
const accessToken = await fetchAccessToken(appId, appSecret)
|
||||
const jsapiTicket = await fetchJsapiTicket(accessToken)
|
||||
|
||||
const timestamp = generateTimestamp()
|
||||
const nonceStr = generateNonceStr(16)
|
||||
const signature = buildSignature(jsapiTicket, nonceStr, timestamp, pageUrl)
|
||||
|
||||
return {
|
||||
appId,
|
||||
timestamp,
|
||||
nonceStr,
|
||||
signature
|
||||
}
|
||||
})
|
Reference in New Issue
Block a user