Compare commits

..
4 Commits
Author SHA1 Message Date
zhaoxi 45700a8cd3 feat(k8s): 启用 postgres 每日备份到 S3(已实测上传成功) 2026-07-19 17:30:42 +08:00
zhaoxi 693e08d67b fix(editor): 修复草稿被默认文本覆盖的数据丢失 bug 与编辑器无法滚动
- 编辑器初始化等待文章和草稿两个查询都返回,此前草稿接口先返回
  空内容时会用默认文本初始化并被自动保存覆盖真实草稿
- CodeMirror 容器加 min-h-0(flex 子项默认 min-height:auto 会被
  内容撑开),并约束 .cm-editor 高度为 100% 使内部滚动生效
- 修复规则必须放在 @layer 外,否则被 Tailwind 当作未使用样式裁剪
2026-07-19 14:56:47 +08:00
zhaoxi 3e22d3b12e fix(k8s): 移除 kustomize 的 registry 镜像改写,使用本地导入镜像 2026-07-18 22:32:54 +08:00
zhaoxi 0efb2bffbd chore(k8s): 适配生产部署
- ingress 只暴露 /api(前端静态文件由外部 Caddy 托管)
- deployment 填入正式域名 blog.zhaoxi.net.cn
- 真实 secret 移出 git 跟踪,改用 secret.example.yaml 模板
2026-07-18 21:45:11 +08:00
11 changed files with 44 additions and 36 deletions
+4
View File
@@ -8,3 +8,7 @@
picture/ picture/
images/ images/
# k8s 真实密钥文件不入库,仓库里只放 secret.example.yaml 模板
k8s/backend/secret.yaml
k8s/postgres/secret.yaml
+6
View File
@@ -285,3 +285,9 @@
@apply shadow-[0_0_0_3px_rgba(129,140,248,0.2)]; @apply shadow-[0_0_0_3px_rgba(129,140,248,0.2)];
} }
} }
/* CodeMirror 编辑器内部滚动(必须是无层级的普通 CSS,
放在 @layer 里会被 Tailwind 当作未使用样式裁剪掉) */
.cm-editor {
height: 100%;
}
@@ -234,7 +234,7 @@ export function AdminProfilePage() {
theme={theme === 'dark' ? oneDark : 'light'} theme={theme === 'dark' ? oneDark : 'light'}
extensions={[markdown()]} extensions={[markdown()]}
onChange={(value) => setContent(value)} onChange={(value) => setContent(value)}
className="flex-1 text-sm" className="flex-1 min-h-0 text-sm"
basicSetup={{ lineNumbers: true, highlightActiveLineGutter: true, highlightActiveLine: true, foldGutter: false }} basicSetup={{ lineNumbers: true, highlightActiveLineGutter: true, highlightActiveLine: true, foldGutter: false }}
/> />
</div> </div>
+6 -4
View File
@@ -53,14 +53,16 @@ export function EditorPage() {
enabled: isEdit, enabled: isEdit,
}); });
const { data: draft } = useQuery({ const { data: draft, isLoading: draftLoading } = useQuery({
queryKey: ['admin-draft', id], queryKey: ['admin-draft', id],
queryFn: () => api.getDraft(id!), queryFn: () => api.getDraft(id!),
enabled: isEdit, enabled: isEdit,
}); });
// 必须等文章和草稿两个查询都返回后再初始化,
// 否则会先用默认文本渲染,自动保存随即覆盖掉真实草稿(数据丢失)
useEffect(() => { useEffect(() => {
if (!initialized && (post || draft)) { if (!initialized && !isLoading && !draftLoading && (post || draft)) {
setTitle(post?.title || ''); setTitle(post?.title || '');
setSlug(post?.slug || ''); setSlug(post?.slug || '');
setSummary(post?.summary || ''); setSummary(post?.summary || '');
@@ -70,7 +72,7 @@ export function EditorPage() {
setContentMd(md); setContentMd(md);
setInitialized(true); setInitialized(true);
} }
}, [post, draft, initialized]); }, [post, draft, isLoading, draftLoading, initialized]);
// 自动保存草稿 // 自动保存草稿
useEffect(() => { useEffect(() => {
@@ -356,7 +358,7 @@ export function EditorPage() {
theme={theme === 'dark' ? oneDark : 'light'} theme={theme === 'dark' ? oneDark : 'light'}
extensions={[markdown()]} extensions={[markdown()]}
onChange={(value) => setContentMd(value)} onChange={(value) => setContentMd(value)}
className="flex-1 text-sm" className="flex-1 min-h-0 text-sm"
basicSetup={{ basicSetup={{
lineNumbers: true, lineNumbers: true,
highlightActiveLineGutter: true, highlightActiveLineGutter: true,
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/App.tsx","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/api/client.ts","./src/api/index.ts","./src/components/AuthInit.tsx","./src/components/Header.tsx","./src/components/PostCard.tsx","./src/components/StarBackground.tsx","./src/pages/HomePage.tsx","./src/pages/OAuthCallback.tsx","./src/pages/PostPage.tsx","./src/pages/RssPage.tsx","./src/pages/TagsPage.tsx","./src/pages/admin/AdminLayout.tsx","./src/pages/admin/AdminPage.tsx","./src/pages/admin/EditorPage.tsx","./src/store/auth.ts","./src/store/theme.ts"],"version":"6.0.3"} {"root":["./src/App.tsx","./src/main.tsx","./src/types.ts","./src/vite-env.d.ts","./src/api/client.ts","./src/api/index.ts","./src/components/AuthInit.tsx","./src/components/ErrorBoundary.tsx","./src/components/Header.tsx","./src/components/Markdown.tsx","./src/components/PostCard.tsx","./src/components/StarBackground.tsx","./src/config/hero.ts","./src/config/profile.ts","./src/lib/tree.ts","./src/pages/HomePage.tsx","./src/pages/LibraryPage.tsx","./src/pages/OAuthCallback.tsx","./src/pages/PostPage.tsx","./src/pages/ProfilePage.tsx","./src/pages/RssPage.tsx","./src/pages/admin/AdminHeroPage.tsx","./src/pages/admin/AdminLayout.tsx","./src/pages/admin/AdminPage.tsx","./src/pages/admin/AdminProfilePage.tsx","./src/pages/admin/EditorPage.tsx","./src/store/auth.ts","./src/store/theme.ts"],"version":"6.0.3"}
+2 -2
View File
@@ -57,9 +57,9 @@ spec:
name: backend-secret name: backend-secret
key: github-admin-id key: github-admin-id
- name: APP_URL - name: APP_URL
value: "https://your-domain.com" value: "https://blog.zhaoxi.net.cn"
- name: FRONTEND_URL - name: FRONTEND_URL
value: "https://your-domain.com" value: "https://blog.zhaoxi.net.cn"
# MOCK_OAUTH 生产环境绝不开启,缺省即关闭 # MOCK_OAUTH 生产环境绝不开启,缺省即关闭
- name: S3_ENDPOINT - name: S3_ENDPOINT
valueFrom: valueFrom:
+18
View File
@@ -0,0 +1,18 @@
apiVersion: v1
kind: Secret
metadata:
name: backend-secret
namespace: blog
type: Opaque
stringData:
database-url: "postgres://blog:CHANGE_ME@postgres:5432/blog"
jwt-secret: "CHANGE_ME_random_base64"
github-client-id: ""
github-client-secret: ""
github-admin-id: ""
s3-endpoint: ""
s3-region: "us-east-1"
s3-bucket: ""
s3-access-key: ""
s3-secret-key: ""
s3-public-url: ""
-19
View File
@@ -1,19 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: backend-secret
namespace: blog
type: Opaque
stringData:
database-url: "postgres://blog:change-me-in-production@postgres:5432/blog"
jwt-secret: "change-me-in-production"
github-client-id: "your-github-client-id"
github-client-secret: "your-github-client-secret"
github-admin-id: "your-github-user-id"
# S3 兼容对象存储,留空表示关闭上传功能
s3-endpoint: ""
s3-region: "us-east-1"
s3-bucket: ""
s3-access-key: ""
s3-secret-key: ""
s3-public-url: ""
+1 -1
View File
@@ -7,7 +7,7 @@ metadata:
traefik.ingress.kubernetes.io/router.entrypoints: web traefik.ingress.kubernetes.io/router.entrypoints: web
spec: spec:
rules: rules:
- host: your-domain.com - host: blog.zhaoxi.net.cn
http: http:
paths: paths:
# 前端静态文件由外部 Caddy 直接托管,集群内只暴露 API # 前端静态文件由外部 Caddy 直接托管,集群内只暴露 API
+3 -6
View File
@@ -8,17 +8,14 @@ resources:
- postgres/secret.yaml - postgres/secret.yaml
- postgres/statefulset.yaml - postgres/statefulset.yaml
- postgres/service.yaml - postgres/service.yaml
# 可选:PostgreSQL 每日备份到 S3,填好 s3-* 后取消注释 # PostgreSQL 每日备份到 S3
# - postgres/backup-cronjob.yaml - postgres/backup-cronjob.yaml
- backend/secret.yaml - backend/secret.yaml
- backend/deployment.yaml - backend/deployment.yaml
- backend/service.yaml - backend/service.yaml
- ingress.yaml - ingress.yaml
images: # 镜像通过 k3s ctr images import 本地导入,不走 registry
- name: blog-backend
newName: your-registry/blog-backend
newTag: latest
# 可选:统一修改域名 # 可选:统一修改域名
# replacements: # replacements:
@@ -5,4 +5,4 @@ metadata:
namespace: blog namespace: blog
type: Opaque type: Opaque
stringData: stringData:
password: "change-me-in-production" password: "CHANGE_ME"