{"repo": "bistutzyy/taozhiyy", "files": {"acg-api/deepseek_client.go": {"3a6d6cc368dbdde41b00b32c4e6d76c3ffe26a9b": {"t": 1785745621.6687407, "n": 6121, "h": "8dffcc33440e572c", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst deepseekSystemPrompt = \"You are the Taozhiyy site AI assistant. Be concise, friendly, and lightly anime-styled without overdoing it. Answer in the user's language. If current page context is provided, treat it as ground truth for what the user is viewing. Do not invent page content that is not present in the supplied context. Prefer answers about the site, posts, technical content, learning notes, and the current page.\"\n\ntype chatPageContext struct {\n\tPageURL string `json:\"pageUrl\"`\n\tPageTitle string `json:\"pageTitle\"`\n\tPagePath string `json:\"pagePath\"`\n\tLanguage string `json:\"language\"`\n\tSiteSection string `json:\"siteSection\"`\n\tHeadings []string `json:\"headings\"`\n\tVisibleText string `json:\"visibleText\"`\n}\n\ntype deepseekClient struct {\n\tapiKey string\n\tbaseURL string\n\tmodel string\n\thttp *http.Client\n}\n\nfunc newDeepSeekClient() *deepseekClient {\n\tbase := env(\"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com\")\n\treturn &deepseekClient{\n\t\tapiKey: env(\"DEEPSEEK_API_KEY\", \"\"),\n\t\tbaseURL: strings.TrimRight(base, \"/\"),\n\t\tmodel: env(\"DEEPSEEK_MODEL\", \"deepseek-v4-flash\"),\n\t\thttp: &http.Client{Timeout: 45 * time.Second},\n\t}\n}\n\nfunc chatConfigured() bool {\n\treturn env(\"DEEPSEEK_API_KEY\", \"\") != \"\"\n}\n\nfunc buildChatUserContent(msg, pageURL, pageTitle string, pageContext chatPageContext) string {\n\tmsg = strings.TrimSpace(msg)\n\tpageURL = strings.TrimSpace(pageURL)\n\tpageTitle = strings.TrimSpace(pageTitle)\n\tpageContext = normalizeChatPageContext(pageContext, pageURL, pageTitle)\n\tif pageURL == \"\" && pageTitle == \"\" && !pageContext.hasContent() {\n\t\treturn msg\n\t}\n\tvar b strings.Builder\n\tb.WriteString(\"[Current page context]\\n\")\n\tif pageContext.PageTitle != \"\" {\n\t\tb.WriteString(\"Title: \")\n\t\tb.WriteString(pageContext.PageTitle)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.PageURL != \"\" {\n\t\tb.WriteString(\"URL: \")\n\t\tb.WriteString(pageContext.PageURL)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.PagePath != \"\" {\n\t\tb.WriteString(\"Path: \")\n\t\tb.WriteString(pageContext.PagePath)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.SiteSection != \"\" {\n\t\tb.WriteString(\"Section: \")\n\t\tb.WriteString(pageContext.SiteSection)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.Language != \"\" {\n\t\tb.WriteString(\"Language: \")\n\t\tb.WriteString(pageContext.Language)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif len(pageContext.Headings) > 0 {\n\t\tb.WriteString(\"Headings: \")\n\t\tb.WriteString(strings.Join(pageContext.Headings, \" | \"))\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.VisibleText != \"\" {\n\t\tb.WriteString(\"Visible text: \")\n\t\tb.WriteString(pageContext.VisibleText)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tb.WriteString(\"\\n[User question]\\n\")\n\tb.WriteString(msg)\n\treturn b.String()\n}\n\nfunc (c *deepseekClient) Chat(userMessage, pageURL, pageTitle string, pageContext chatPageContext) (string, error) {\n\tif c.apiKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"DEEPSEEK_API_KEY not configured\")\n\t}\n\tuserContent := buildChatUserContent(userMessage, pageURL, pageTitle, pageContext)\n\tbody := map[string]any{\n\t\t\"model\": c.model,\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": deepseekSystemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": userContent},\n\t\t},\n\t\t\"temperature\": 0.7,\n\t\t\"max_tokens\": 400,\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tendpoint := c.baseURL + \"/v1/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.apiKey)\n\n\tres, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\trespBody, _ := io.ReadAll(res.Body)\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn \"\", fmt.Errorf(\"deepseek http %d: %s\", res.StatusCode, truncate(string(respBody), 200))\n\t}\n\n\tvar payload struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(respBody, &payload); err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(payload.Choices) == 0 || strings.TrimSpace(payload.Choices[0].Message.Content) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty deepseek response\")\n\t}\n\treturn payload.Choices[0].Message.Content, nil\n}\n\nfunc truncate(s string, maxLen int) string {\n\tif len(s) <= maxLen {\n\t\treturn s\n\t}\n\treturn s[:maxLen] + \"...\"\n}\n\nfunc normalizeChatPageContext(ctx chatPageContext, fallbackURL, fallbackTitle string) chatPageContext {\n\tctx.PageURL = limitString(strings.TrimSpace(firstNonEmpty(ctx.PageURL, fallbackURL)), 400)\n\tctx.PageTitle = limitRunes(strings.TrimSpace(firstNonEmpty(ctx.PageTitle, fallbackTitle)), 120)\n\tctx.PagePath = limitRunes(strings.TrimSpace(ctx.PagePath), 160)\n\tctx.Language = limitRunes(strings.TrimSpace(ctx.Language), 30)\n\tctx.SiteSection = limitRunes(strings.TrimSpace(ctx.SiteSection), 60)\n\tctx.VisibleText = limitRunes(normalizeSpace(ctx.VisibleText), 2200)\n\n\theadings := make([]string, 0, len(ctx.Headings))\n\tseen := map[string]bool{}\n\tfor _, heading := range ctx.Headings {\n\t\ttext := limitRunes(normalizeSpace(heading), 120)\n\t\tif text == \"\" || seen[text] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[text] = true\n\t\theadings = append(headings, text)\n\t\tif len(headings) >= 10 {\n\t\t\tbreak\n\t\t}\n\t}\n\tctx.Headings = headings\n\treturn ctx\n}\n\nfunc (ctx chatPageContext) hasContent() bool {\n\treturn ctx.PageURL != \"\" ||\n\t\tctx.PageTitle != \"\" ||\n\t\tctx.PagePath != \"\" ||\n\t\tctx.Language != \"\" ||\n\t\tctx.SiteSection != \"\" ||\n\t\tlen(ctx.Headings) > 0 ||\n\t\tctx.VisibleText != \"\"\n}\n\nfunc firstNonEmpty(values ...string) string {\n\tfor _, value := range values {\n\t\tif strings.TrimSpace(value) != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc normalizeSpace(value string) string {\n\treturn strings.Join(strings.Fields(value), \" \")\n}\n\nfunc limitRunes(value string, maxLen int) string {\n\trunes := []rune(value)\n\tif len(runes) <= maxLen {\n\t\treturn value\n\t}\n\treturn string(runes[:maxLen]) + \"...\"\n}\n\nfunc limitString(value string, maxLen int) string {\n\tif len(value) <= maxLen {\n\t\treturn value\n\t}\n\treturn value[:maxLen] + \"...\"\n}\n"}, "d72e6e55b91d384b52190f75eb906212b785a5b3": {"t": 1785754946.4035034, "n": 6121, "h": "8dffcc33440e572c", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst deepseekSystemPrompt = \"You are the Taozhiyy site AI assistant. Be concise, friendly, and lightly anime-styled without overdoing it. Answer in the user's language. If current page context is provided, treat it as ground truth for what the user is viewing. Do not invent page content that is not present in the supplied context. Prefer answers about the site, posts, technical content, learning notes, and the current page.\"\n\ntype chatPageContext struct {\n\tPageURL string `json:\"pageUrl\"`\n\tPageTitle string `json:\"pageTitle\"`\n\tPagePath string `json:\"pagePath\"`\n\tLanguage string `json:\"language\"`\n\tSiteSection string `json:\"siteSection\"`\n\tHeadings []string `json:\"headings\"`\n\tVisibleText string `json:\"visibleText\"`\n}\n\ntype deepseekClient struct {\n\tapiKey string\n\tbaseURL string\n\tmodel string\n\thttp *http.Client\n}\n\nfunc newDeepSeekClient() *deepseekClient {\n\tbase := env(\"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com\")\n\treturn &deepseekClient{\n\t\tapiKey: env(\"DEEPSEEK_API_KEY\", \"\"),\n\t\tbaseURL: strings.TrimRight(base, \"/\"),\n\t\tmodel: env(\"DEEPSEEK_MODEL\", \"deepseek-v4-flash\"),\n\t\thttp: &http.Client{Timeout: 45 * time.Second},\n\t}\n}\n\nfunc chatConfigured() bool {\n\treturn env(\"DEEPSEEK_API_KEY\", \"\") != \"\"\n}\n\nfunc buildChatUserContent(msg, pageURL, pageTitle string, pageContext chatPageContext) string {\n\tmsg = strings.TrimSpace(msg)\n\tpageURL = strings.TrimSpace(pageURL)\n\tpageTitle = strings.TrimSpace(pageTitle)\n\tpageContext = normalizeChatPageContext(pageContext, pageURL, pageTitle)\n\tif pageURL == \"\" && pageTitle == \"\" && !pageContext.hasContent() {\n\t\treturn msg\n\t}\n\tvar b strings.Builder\n\tb.WriteString(\"[Current page context]\\n\")\n\tif pageContext.PageTitle != \"\" {\n\t\tb.WriteString(\"Title: \")\n\t\tb.WriteString(pageContext.PageTitle)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.PageURL != \"\" {\n\t\tb.WriteString(\"URL: \")\n\t\tb.WriteString(pageContext.PageURL)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.PagePath != \"\" {\n\t\tb.WriteString(\"Path: \")\n\t\tb.WriteString(pageContext.PagePath)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.SiteSection != \"\" {\n\t\tb.WriteString(\"Section: \")\n\t\tb.WriteString(pageContext.SiteSection)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.Language != \"\" {\n\t\tb.WriteString(\"Language: \")\n\t\tb.WriteString(pageContext.Language)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif len(pageContext.Headings) > 0 {\n\t\tb.WriteString(\"Headings: \")\n\t\tb.WriteString(strings.Join(pageContext.Headings, \" | \"))\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageContext.VisibleText != \"\" {\n\t\tb.WriteString(\"Visible text: \")\n\t\tb.WriteString(pageContext.VisibleText)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tb.WriteString(\"\\n[User question]\\n\")\n\tb.WriteString(msg)\n\treturn b.String()\n}\n\nfunc (c *deepseekClient) Chat(userMessage, pageURL, pageTitle string, pageContext chatPageContext) (string, error) {\n\tif c.apiKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"DEEPSEEK_API_KEY not configured\")\n\t}\n\tuserContent := buildChatUserContent(userMessage, pageURL, pageTitle, pageContext)\n\tbody := map[string]any{\n\t\t\"model\": c.model,\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": deepseekSystemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": userContent},\n\t\t},\n\t\t\"temperature\": 0.7,\n\t\t\"max_tokens\": 400,\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tendpoint := c.baseURL + \"/v1/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.apiKey)\n\n\tres, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\trespBody, _ := io.ReadAll(res.Body)\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn \"\", fmt.Errorf(\"deepseek http %d: %s\", res.StatusCode, truncate(string(respBody), 200))\n\t}\n\n\tvar payload struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(respBody, &payload); err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(payload.Choices) == 0 || strings.TrimSpace(payload.Choices[0].Message.Content) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty deepseek response\")\n\t}\n\treturn payload.Choices[0].Message.Content, nil\n}\n\nfunc truncate(s string, maxLen int) string {\n\tif len(s) <= maxLen {\n\t\treturn s\n\t}\n\treturn s[:maxLen] + \"...\"\n}\n\nfunc normalizeChatPageContext(ctx chatPageContext, fallbackURL, fallbackTitle string) chatPageContext {\n\tctx.PageURL = limitString(strings.TrimSpace(firstNonEmpty(ctx.PageURL, fallbackURL)), 400)\n\tctx.PageTitle = limitRunes(strings.TrimSpace(firstNonEmpty(ctx.PageTitle, fallbackTitle)), 120)\n\tctx.PagePath = limitRunes(strings.TrimSpace(ctx.PagePath), 160)\n\tctx.Language = limitRunes(strings.TrimSpace(ctx.Language), 30)\n\tctx.SiteSection = limitRunes(strings.TrimSpace(ctx.SiteSection), 60)\n\tctx.VisibleText = limitRunes(normalizeSpace(ctx.VisibleText), 2200)\n\n\theadings := make([]string, 0, len(ctx.Headings))\n\tseen := map[string]bool{}\n\tfor _, heading := range ctx.Headings {\n\t\ttext := limitRunes(normalizeSpace(heading), 120)\n\t\tif text == \"\" || seen[text] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[text] = true\n\t\theadings = append(headings, text)\n\t\tif len(headings) >= 10 {\n\t\t\tbreak\n\t\t}\n\t}\n\tctx.Headings = headings\n\treturn ctx\n}\n\nfunc (ctx chatPageContext) hasContent() bool {\n\treturn ctx.PageURL != \"\" ||\n\t\tctx.PageTitle != \"\" ||\n\t\tctx.PagePath != \"\" ||\n\t\tctx.Language != \"\" ||\n\t\tctx.SiteSection != \"\" ||\n\t\tlen(ctx.Headings) > 0 ||\n\t\tctx.VisibleText != \"\"\n}\n\nfunc firstNonEmpty(values ...string) string {\n\tfor _, value := range values {\n\t\tif strings.TrimSpace(value) != \"\" {\n\t\t\treturn value\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc normalizeSpace(value string) string {\n\treturn strings.Join(strings.Fields(value), \" \")\n}\n\nfunc limitRunes(value string, maxLen int) string {\n\trunes := []rune(value)\n\tif len(runes) <= maxLen {\n\t\treturn value\n\t}\n\treturn string(runes[:maxLen]) + \"...\"\n}\n\nfunc limitString(value string, maxLen int) string {\n\tif len(value) <= maxLen {\n\t\treturn value\n\t}\n\treturn value[:maxLen] + \"...\"\n}\n"}, "5f8bc2dd67563583e0247a516f02b0e3df101870": {"t": 1785754947.7488165, "n": 3079, "h": "4b95a876c47b1c7c", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst deepseekSystemPrompt = \"你是一个博客 AI 小助手,名字叫博客小精灵。说话简洁、友好、轻微二次元风格,但不要过度卖萌。用户消息里可能附带当前页面的标题和链接,可据此理解用户所在位置;不要编造页面正文里不存在的内容。优先回答和博客文章、技术内容、学习内容相关的问题。回答尽量简短。\"\n\ntype deepseekClient struct {\n\tapiKey string\n\tbaseURL string\n\tmodel string\n\thttp *http.Client\n}\n\nfunc newDeepSeekClient() *deepseekClient {\n\tbase := env(\"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com\")\n\treturn &deepseekClient{\n\t\tapiKey: env(\"DEEPSEEK_API_KEY\", \"\"),\n\t\tbaseURL: strings.TrimRight(base, \"/\"),\n\t\tmodel: env(\"DEEPSEEK_MODEL\", \"deepseek-v4-flash\"),\n\t\thttp: &http.Client{Timeout: 45 * time.Second},\n\t}\n}\n\nfunc chatConfigured() bool {\n\treturn env(\"DEEPSEEK_API_KEY\", \"\") != \"\"\n}\n\nfunc buildChatUserContent(msg, pageURL, pageTitle string) string {\n\tmsg = strings.TrimSpace(msg)\n\tpageURL = strings.TrimSpace(pageURL)\n\tpageTitle = strings.TrimSpace(pageTitle)\n\tif pageURL == \"\" && pageTitle == \"\" {\n\t\treturn msg\n\t}\n\tif len([]rune(pageTitle)) > 120 {\n\t\tpageTitle = string([]rune(pageTitle)[:120]) + \"…\"\n\t}\n\tif len(pageURL) > 400 {\n\t\tpageURL = pageURL[:400] + \"…\"\n\t}\n\tvar b strings.Builder\n\tb.WriteString(\"【用户当前浏览的页面】\\n\")\n\tif pageTitle != \"\" {\n\t\tb.WriteString(\"标题:\")\n\t\tb.WriteString(pageTitle)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageURL != \"\" {\n\t\tb.WriteString(\"链接:\")\n\t\tb.WriteString(pageURL)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tb.WriteString(\"\\n【用户问题】\\n\")\n\tb.WriteString(msg)\n\treturn b.String()\n}\n\nfunc (c *deepseekClient) Chat(userMessage, pageURL, pageTitle string) (string, error) {\n\tif c.apiKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"DEEPSEEK_API_KEY not configured\")\n\t}\n\tuserContent := buildChatUserContent(userMessage, pageURL, pageTitle)\n\tbody := map[string]any{\n\t\t\"model\": c.model,\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": deepseekSystemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": userContent},\n\t\t},\n\t\t\"temperature\": 0.7,\n\t\t\"max_tokens\": 400,\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tendpoint := c.baseURL + \"/v1/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.apiKey)\n\n\tres, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\trespBody, _ := io.ReadAll(res.Body)\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn \"\", fmt.Errorf(\"deepseek http %d: %s\", res.StatusCode, truncate(string(respBody), 200))\n\t}\n\n\tvar payload struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(respBody, &payload); err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(payload.Choices) == 0 || strings.TrimSpace(payload.Choices[0].Message.Content) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty deepseek response\")\n\t}\n\treturn payload.Choices[0].Message.Content, nil\n}\n\nfunc truncate(s string, maxLen int) string {\n\tif len(s) <= maxLen {\n\t\treturn s\n\t}\n\treturn s[:maxLen] + \"…\"\n}\n"}, "c4f4601c6341f5985dbf3923ab2076282737acf9": {"t": 1785754948.8866916, "n": 3432, "h": "d25a112910e418ff", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst deepseekSystemPrompt = \"你是一个博客 AI 小助手,名字叫博客小精灵。说话简洁、友好、轻微二次元风格,但不要过度卖萌。用户消息里可能附带当前页面的标题和链接,可据此理解用户所在位置;不要编造页面正文里不存在的内容。优先回答和博客文章、技术内容、学习内容相关的问题。回答尽量简短。\"\n\ntype deepseekClient struct {\n\tapiKey string\n\tbaseURL string\n\tmodel string\n\thttp *http.Client\n}\n\ntype deepSeekChatResult struct {\n\tContent string\n\tTokens int\n}\n\nfunc newDeepSeekClient() *deepseekClient {\n\tbase := env(\"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com\")\n\treturn &deepseekClient{\n\t\tapiKey: env(\"DEEPSEEK_API_KEY\", \"\"),\n\t\tbaseURL: strings.TrimRight(base, \"/\"),\n\t\tmodel: env(\"DEEPSEEK_MODEL\", \"deepseek-v4-flash\"),\n\t\thttp: &http.Client{Timeout: 45 * time.Second},\n\t}\n}\n\nfunc chatConfigured() bool {\n\treturn env(\"DEEPSEEK_API_KEY\", \"\") != \"\"\n}\n\nfunc buildChatUserContent(msg, pageURL, pageTitle string) string {\n\tmsg = strings.TrimSpace(msg)\n\tpageURL = strings.TrimSpace(pageURL)\n\tpageTitle = strings.TrimSpace(pageTitle)\n\tif pageURL == \"\" && pageTitle == \"\" {\n\t\treturn msg\n\t}\n\tif len([]rune(pageTitle)) > 120 {\n\t\tpageTitle = string([]rune(pageTitle)[:120]) + \"…\"\n\t}\n\tif len(pageURL) > 400 {\n\t\tpageURL = pageURL[:400] + \"…\"\n\t}\n\tvar b strings.Builder\n\tb.WriteString(\"【用户当前浏览的页面】\\n\")\n\tif pageTitle != \"\" {\n\t\tb.WriteString(\"标题:\")\n\t\tb.WriteString(pageTitle)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageURL != \"\" {\n\t\tb.WriteString(\"链接:\")\n\t\tb.WriteString(pageURL)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tb.WriteString(\"\\n【用户问题】\\n\")\n\tb.WriteString(msg)\n\treturn b.String()\n}\n\nfunc (c *deepseekClient) Chat(userMessage, pageURL, pageTitle string) (deepSeekChatResult, error) {\n\tif c.apiKey == \"\" {\n\t\treturn deepSeekChatResult{}, fmt.Errorf(\"DEEPSEEK_API_KEY not configured\")\n\t}\n\tuserContent := buildChatUserContent(userMessage, pageURL, pageTitle)\n\tbody := map[string]any{\n\t\t\"model\": c.model,\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": deepseekSystemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": userContent},\n\t\t},\n\t\t\"temperature\": 0.7,\n\t\t\"max_tokens\": 400,\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn deepSeekChatResult{}, err\n\t}\n\tendpoint := c.baseURL + \"/v1/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn deepSeekChatResult{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.apiKey)\n\n\tres, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn deepSeekChatResult{}, err\n\t}\n\tdefer res.Body.Close()\n\trespBody, _ := io.ReadAll(res.Body)\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn deepSeekChatResult{}, fmt.Errorf(\"deepseek http %d: %s\", res.StatusCode, truncate(string(respBody), 200))\n\t}\n\n\tvar payload struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t\tUsage struct {\n\t\t\tTotalTokens int `json:\"total_tokens\"`\n\t\t} `json:\"usage\"`\n\t}\n\tif err := json.Unmarshal(respBody, &payload); err != nil {\n\t\treturn deepSeekChatResult{}, err\n\t}\n\tif len(payload.Choices) == 0 || strings.TrimSpace(payload.Choices[0].Message.Content) == \"\" {\n\t\treturn deepSeekChatResult{}, fmt.Errorf(\"empty deepseek response\")\n\t}\n\treturn deepSeekChatResult{\n\t\tContent: payload.Choices[0].Message.Content,\n\t\tTokens: payload.Usage.TotalTokens,\n\t}, nil\n}\n\nfunc truncate(s string, maxLen int) string {\n\tif len(s) <= maxLen {\n\t\treturn s\n\t}\n\treturn s[:maxLen] + \"…\"\n}\n"}, "f9b4ec58fb1d4048a9f161cd8e7201a0eebef0dd": {"t": 1785754950.3199735, "n": 3079, "h": "4b95a876c47b1c7c", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst deepseekSystemPrompt = \"你是一个博客 AI 小助手,名字叫博客小精灵。说话简洁、友好、轻微二次元风格,但不要过度卖萌。用户消息里可能附带当前页面的标题和链接,可据此理解用户所在位置;不要编造页面正文里不存在的内容。优先回答和博客文章、技术内容、学习内容相关的问题。回答尽量简短。\"\n\ntype deepseekClient struct {\n\tapiKey string\n\tbaseURL string\n\tmodel string\n\thttp *http.Client\n}\n\nfunc newDeepSeekClient() *deepseekClient {\n\tbase := env(\"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com\")\n\treturn &deepseekClient{\n\t\tapiKey: env(\"DEEPSEEK_API_KEY\", \"\"),\n\t\tbaseURL: strings.TrimRight(base, \"/\"),\n\t\tmodel: env(\"DEEPSEEK_MODEL\", \"deepseek-v4-flash\"),\n\t\thttp: &http.Client{Timeout: 45 * time.Second},\n\t}\n}\n\nfunc chatConfigured() bool {\n\treturn env(\"DEEPSEEK_API_KEY\", \"\") != \"\"\n}\n\nfunc buildChatUserContent(msg, pageURL, pageTitle string) string {\n\tmsg = strings.TrimSpace(msg)\n\tpageURL = strings.TrimSpace(pageURL)\n\tpageTitle = strings.TrimSpace(pageTitle)\n\tif pageURL == \"\" && pageTitle == \"\" {\n\t\treturn msg\n\t}\n\tif len([]rune(pageTitle)) > 120 {\n\t\tpageTitle = string([]rune(pageTitle)[:120]) + \"…\"\n\t}\n\tif len(pageURL) > 400 {\n\t\tpageURL = pageURL[:400] + \"…\"\n\t}\n\tvar b strings.Builder\n\tb.WriteString(\"【用户当前浏览的页面】\\n\")\n\tif pageTitle != \"\" {\n\t\tb.WriteString(\"标题:\")\n\t\tb.WriteString(pageTitle)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tif pageURL != \"\" {\n\t\tb.WriteString(\"链接:\")\n\t\tb.WriteString(pageURL)\n\t\tb.WriteString(\"\\n\")\n\t}\n\tb.WriteString(\"\\n【用户问题】\\n\")\n\tb.WriteString(msg)\n\treturn b.String()\n}\n\nfunc (c *deepseekClient) Chat(userMessage, pageURL, pageTitle string) (string, error) {\n\tif c.apiKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"DEEPSEEK_API_KEY not configured\")\n\t}\n\tuserContent := buildChatUserContent(userMessage, pageURL, pageTitle)\n\tbody := map[string]any{\n\t\t\"model\": c.model,\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": deepseekSystemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": userContent},\n\t\t},\n\t\t\"temperature\": 0.7,\n\t\t\"max_tokens\": 400,\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tendpoint := c.baseURL + \"/v1/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.apiKey)\n\n\tres, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\trespBody, _ := io.ReadAll(res.Body)\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn \"\", fmt.Errorf(\"deepseek http %d: %s\", res.StatusCode, truncate(string(respBody), 200))\n\t}\n\n\tvar payload struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(respBody, &payload); err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(payload.Choices) == 0 || strings.TrimSpace(payload.Choices[0].Message.Content) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty deepseek response\")\n\t}\n\treturn payload.Choices[0].Message.Content, nil\n}\n\nfunc truncate(s string, maxLen int) string {\n\tif len(s) <= maxLen {\n\t\treturn s\n\t}\n\treturn s[:maxLen] + \"…\"\n}\n"}, "4e576d39c64cf3349e6cd45520bf630ac73f70fb": {"t": 1785754951.6663973, "n": 2243, "h": "7e016dbf5b29f991", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst deepseekSystemPrompt = \"你是一个博客 AI 小助手,名字叫博客小精灵。说话简洁、友好、轻微二次元风格,但不要过度卖萌。优先回答和博客文章、技术内容、学习内容相关的问题。回答尽量简短。\"\n\ntype deepseekClient struct {\n\tapiKey string\n\tbaseURL string\n\tmodel string\n\thttp *http.Client\n}\n\nfunc newDeepSeekClient() *deepseekClient {\n\tbase := env(\"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com\")\n\treturn &deepseekClient{\n\t\tapiKey: env(\"DEEPSEEK_API_KEY\", \"\"),\n\t\tbaseURL: strings.TrimRight(base, \"/\"),\n\t\tmodel: env(\"DEEPSEEK_MODEL\", \"deepseek-v4-flash\"),\n\t\thttp: &http.Client{Timeout: 45 * time.Second},\n\t}\n}\n\nfunc chatConfigured() bool {\n\treturn env(\"DEEPSEEK_API_KEY\", \"\") != \"\"\n}\n\nfunc (c *deepseekClient) Chat(userMessage string) (string, error) {\n\tif c.apiKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"DEEPSEEK_API_KEY not configured\")\n\t}\n\tbody := map[string]any{\n\t\t\"model\": c.model,\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": deepseekSystemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": userMessage},\n\t\t},\n\t\t\"temperature\": 0.7,\n\t\t\"max_tokens\": 400,\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tendpoint := c.baseURL + \"/v1/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.apiKey)\n\n\tres, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\trespBody, _ := io.ReadAll(res.Body)\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn \"\", fmt.Errorf(\"deepseek http %d: %s\", res.StatusCode, truncate(string(respBody), 200))\n\t}\n\n\tvar payload struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(respBody, &payload); err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(payload.Choices) == 0 || strings.TrimSpace(payload.Choices[0].Message.Content) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty deepseek response\")\n\t}\n\treturn payload.Choices[0].Message.Content, nil\n}\n\nfunc truncate(s string, maxLen int) string {\n\tif len(s) <= maxLen {\n\t\treturn s\n\t}\n\treturn s[:maxLen] + \"…\"\n}\n"}, "709f88f6ad4a471b5171b0c2a001b39dd3cbe97c": {"t": 1785754952.963293, "n": 2133, "h": "7d98bf202113a41b", "c": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst deepseekSystemPrompt = \"你是一个博客 AI 小助手,名字叫博客小精灵。说话简洁、友好、轻微二次元风格,但不要过度卖萌。优先回答和博客文章、技术内容、学习内容相关的问题。回答尽量简短。\"\n\ntype deepseekClient struct {\n\tapiKey string\n\tbaseURL string\n\tmodel string\n\thttp *http.Client\n}\n\nfunc newDeepSeekClient() *deepseekClient {\n\tbase := env(\"DEEPSEEK_BASE_URL\", \"https://api.deepseek.com\")\n\treturn &deepseekClient{\n\t\tapiKey: env(\"DEEPSEEK_API_KEY\", \"\"),\n\t\tbaseURL: strings.TrimRight(base, \"/\"),\n\t\tmodel: env(\"DEEPSEEK_MODEL\", \"deepseek-v4-flash\"),\n\t\thttp: &http.Client{Timeout: 45 * time.Second},\n\t}\n}\n\nfunc chatConfigured() bool {\n\treturn env(\"DEEPSEEK_API_KEY\", \"\") != \"\"\n}\n\nfunc (c *deepseekClient) Chat(userMessage string) (string, error) {\n\tif c.apiKey == \"\" {\n\t\treturn \"\", fmt.Errorf(\"DEEPSEEK_API_KEY not configured\")\n\t}\n\tbody := map[string]any{\n\t\t\"model\": c.model,\n\t\t\"messages\": []map[string]string{\n\t\t\t{\"role\": \"system\", \"content\": deepseekSystemPrompt},\n\t\t\t{\"role\": \"user\", \"content\": userMessage},\n\t\t},\n\t\t\"temperature\": 0.7,\n\t\t\"max_tokens\": 400,\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tendpoint := c.baseURL + \"/v1/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+c.apiKey)\n\n\tres, err := c.http.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer res.Body.Close()\n\trespBody, _ := io.ReadAll(res.Body)\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\treturn \"\", fmt.Errorf(\"deepseek http %d: %s\", res.StatusCode, truncate(string(respBody), 200))\n\t}\n\n\tvar payload struct {\n\t\tChoices []struct {\n\t\t\tMessage struct {\n\t\t\t\tContent string `json:\"content\"`\n\t\t\t} `json:\"message\"`\n\t\t} `json:\"choices\"`\n\t}\n\tif err := json.Unmarshal(respBody, &payload); err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(payload.Choices) == 0 || strings.TrimSpace(payload.Choices[0].Message.Content) == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty deepseek response\")\n\t}\n\treturn payload.Choices[0].Message.Content, nil\n}\n"}}, "README.zh-CN.md": {"3a6d6cc368dbdde41b00b32c4e6d76c3ffe26a9b": {"t": 1785747278.94242, "n": 11432, "h": "ce77a72e7e27d4ab", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、邮件通知、AI 助手、AI 生图、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 正式进入主站的 `/about` 关于我页面:把关于页预览集成进 React 主站,图片统一引用腾讯 COS,并把项目、游戏、工具卡片作为个人站身份的一部分呈现。\n- 已部署的 AI QuizCard 项目展示:包含 `/showcase/quizcard.html` 成品页,以及 `/web/` 下带默认题组、访客可直接练习的静态应用。\n- 留言、邮件通知、AI 上下文、AI 生图、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复、AI 固定答案从站长控制台同步到网站助手的链路、UTF-8 安全邮件通知、友链联系邮箱收集、友链连续回复、站长后台邮箱可见能力、数据中心服务器状态监控、AI 生图结果保存到腾讯 COS,以及新上线的 `/about` 关于我页面和 AI QuizCard 项目展示。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n主站现在也包含一个正式的 `/about` 页面,页面名称为“关于我”。它通过带作用域隔离的 Shadow DOM 容器把关于页预览加载进 React 主站,移除了预览专用文字,图片统一引用腾讯 COS 链接,项目集在不同断点下保持更舒展的网格,常驻游戏卡片直接展示而不再依赖底部横向拉条,技术工具图标也会连续移动而不出现空白。\n\n关于页里的第一个项目是 AI QuizCard。项目卡片和 `/about/projects/quizcard` 都会指向 `/showcase/quizcard.html`,该成品页随主站部署了展示页资源、小程序风格页面预览,以及 `/web/` 下访客可直接玩的静态 QuizCard 应用。静态访客版内置默认题组,因此没有后端数据时也能马上练习;练习报告页在窄屏下让摘要卡片随页面一起滚动,宽屏下才保留 sticky 摘要,避免错题区覆盖主界面。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n数据中心页面(`/ai-traffic`)现在把 AI 流量和服务器状态放到同一个页面里。`ServerInfoPanel` 会每 10 秒刷新 `GET /api/server/info`,展示在线状态、云厂商/区域、CPU 占用、内存使用、运行时长、系统/Go 运行时、CPU 核心数、goroutine 数和服务端时间等安全遥测。该接口刻意不暴露真实 IP、主机名、磁盘路径、进程列表、环境变量等敏感服务器信息。\n\n右下角 AI 助手也加入了面向登录用户的生图模式。它会调用后端 `/api/ai/image`,使用 Agnes AI 的 `agnes-image-2.1-flash`,把生成图保存到腾讯 COS,并把最终 COS 链接、提示词和生成时间记录给 `/ai-gallery` 生成图广场,同时在屏幕中央用透明浅色镭射卡片展示结果,支持保存本地和复制最终 COS 链接。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、邮件通知、AI 助手上下文、AI 生图、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务。 |\n| `main.go` 受保护同步触发 | `POST /api/v1/sync/trigger` | 部署 token 或站长会话 | 手动同步触发;请求必须携带匹配 `SYNC_TRIGGER_TOKEN` 的 `X-Sync-Trigger-Token`,或使用已认证的站长会话。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `ai_image.go`, `ai_image_gallery.go` | `GET/POST /api/ai/image`, `GET /api/ai/image/gallery` | 生图需登录/站长额度;广场公开 | 生图额度状态、Agnes AI 生图、临时图下载、腾讯 COS 上传、生成历史记录,以及公开生成图广场 feed,包含提示词、图片 URL、作者展示名、尺寸和生成时间。 |\n| `server_info.go` | `GET /api/server/info` | 公开安全遥测 | 数据中心页面使用的非敏感服务器状态,包括在线状态、云厂商/区域标签、CPU 占用、内存使用、运行时长、运行时信息、CPU 核心数、goroutine 数和服务端时间;刻意不暴露 IP、主机名、磁盘路径、进程列表或环境变量。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go`, `mail_notifier.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见/可发布留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、连续回复、访客/登录用户发布、友链联系邮箱必填、UTF-8 邮件通知、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET /api/owner/emails`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、仅站长可见邮箱目录、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 和 `POST /api/ai/image` 调用 | 站长或服务端流程 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册/生成图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"contactEmail\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 普通留言墙未登录时提交 `nickname` + `content`,登录后提交 `content` 即可。顶层 `friends` 友链留言必须提供 `nickname` 和 `contactEmail`;公开响应不会暴露 `contactEmail`。回复会忽略提交的 `contactEmail`,并在可用时通知父留言联系人或账号邮箱。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `GET /api/owner/emails` | 无请求体 | 仅站长可访问,返回注册用户邮箱和私有留言联系邮箱目录。 |\n| `GET /api/ai/image` | 无请求体 | 返回生图额度、`imageEnabled`、`canGenerate`、当前模型和 `promptExtend: false`。 |\n| `POST /api/ai/image` | `{ \"prompt\": \"...\", \"size\": \"1024*1024\" }` | 仅登录用户可用;普通用户每天 3 张,站长不限额度。支持 `1024*1024`、`1280*720`、`720*1280`,返回的 `image.url` 是最终腾讯 COS 链接。 |\n| `GET /api/ai/image/gallery?limit=60&before=...` | 可选游标查询 | 公开生成图广场 feed,返回 `prompt`、`imageUrl`、`size`、`createdAt` 和匿名安全的 `author` 展示文本。 |\n| `GET /api/server/info` | 无请求体 | 返回 `/ai-traffic` 使用的非敏感实时遥测,前端每 10 秒刷新一次。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- AI 生图:配置 `AGNES_API_KEY`;可选配置 `AGNES_BASE_URL`(默认 `https://apihub.agnes-ai.com`)以及 `AGNES_IMAGE_MODEL` 或 `AI_IMAGE_MODEL`(默认 `agnes-image-2.1-flash`)。如果没有 `AGNES_API_KEY`,后端仍会回退兼容旧的 DashScope 变量 `DASHSCOPE_API_KEY`、`DASHSCOPE_BASE_URL`、`AI_IMAGE_MODEL` 和 `ALIYUN_BAILIAN_API_KEY`。\n- 邮件通知:配置 `SMTP_HOST`、`SMTP_PORT`、`SMTP_USER`、`SMTP_PASS`、`SMTP_FROM_NAME` 和 `MAIL_NOTIFY_TO`。`SMTP_PASS` 应该使用邮箱服务商的授权码或应用密码,不要使用账号登录密码。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器历史趋势与告警:在当前实时状态面板基础上继续记录 CPU、内存、错误率和同步耗时趋势。\n- 打造生成图展示页面:把已生成并保存到 COS 的图片整理成可浏览、可回看、可管理的展示页。\n- 继续填充项目集:让关于页里的项目卡片逐步补上真实项目页、截图、体验入口和版本记录。\n- 优化 AI QuizCard 导入流程:在默认题组可试玩的基础上,让用户导入、解析、保存题库和进入练习的路径更顺。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "d1debf42b48b3d677aad07a21907e91517b102f0": {"t": 1785754946.5271592, "n": 11432, "h": "ce77a72e7e27d4ab", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、邮件通知、AI 助手、AI 生图、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 正式进入主站的 `/about` 关于我页面:把关于页预览集成进 React 主站,图片统一引用腾讯 COS,并把项目、游戏、工具卡片作为个人站身份的一部分呈现。\n- 已部署的 AI QuizCard 项目展示:包含 `/showcase/quizcard.html` 成品页,以及 `/web/` 下带默认题组、访客可直接练习的静态应用。\n- 留言、邮件通知、AI 上下文、AI 生图、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复、AI 固定答案从站长控制台同步到网站助手的链路、UTF-8 安全邮件通知、友链联系邮箱收集、友链连续回复、站长后台邮箱可见能力、数据中心服务器状态监控、AI 生图结果保存到腾讯 COS,以及新上线的 `/about` 关于我页面和 AI QuizCard 项目展示。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n主站现在也包含一个正式的 `/about` 页面,页面名称为“关于我”。它通过带作用域隔离的 Shadow DOM 容器把关于页预览加载进 React 主站,移除了预览专用文字,图片统一引用腾讯 COS 链接,项目集在不同断点下保持更舒展的网格,常驻游戏卡片直接展示而不再依赖底部横向拉条,技术工具图标也会连续移动而不出现空白。\n\n关于页里的第一个项目是 AI QuizCard。项目卡片和 `/about/projects/quizcard` 都会指向 `/showcase/quizcard.html`,该成品页随主站部署了展示页资源、小程序风格页面预览,以及 `/web/` 下访客可直接玩的静态 QuizCard 应用。静态访客版内置默认题组,因此没有后端数据时也能马上练习;练习报告页在窄屏下让摘要卡片随页面一起滚动,宽屏下才保留 sticky 摘要,避免错题区覆盖主界面。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n数据中心页面(`/ai-traffic`)现在把 AI 流量和服务器状态放到同一个页面里。`ServerInfoPanel` 会每 10 秒刷新 `GET /api/server/info`,展示在线状态、云厂商/区域、CPU 占用、内存使用、运行时长、系统/Go 运行时、CPU 核心数、goroutine 数和服务端时间等安全遥测。该接口刻意不暴露真实 IP、主机名、磁盘路径、进程列表、环境变量等敏感服务器信息。\n\n右下角 AI 助手也加入了面向登录用户的生图模式。它会调用后端 `/api/ai/image`,使用 Agnes AI 的 `agnes-image-2.1-flash`,把生成图保存到腾讯 COS,并把最终 COS 链接、提示词和生成时间记录给 `/ai-gallery` 生成图广场,同时在屏幕中央用透明浅色镭射卡片展示结果,支持保存本地和复制最终 COS 链接。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、邮件通知、AI 助手上下文、AI 生图、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务。 |\n| `main.go` 受保护同步触发 | `POST /api/v1/sync/trigger` | 部署 token 或站长会话 | 手动同步触发;请求必须携带匹配 `SYNC_TRIGGER_TOKEN` 的 `X-Sync-Trigger-Token`,或使用已认证的站长会话。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `ai_image.go`, `ai_image_gallery.go` | `GET/POST /api/ai/image`, `GET /api/ai/image/gallery` | 生图需登录/站长额度;广场公开 | 生图额度状态、Agnes AI 生图、临时图下载、腾讯 COS 上传、生成历史记录,以及公开生成图广场 feed,包含提示词、图片 URL、作者展示名、尺寸和生成时间。 |\n| `server_info.go` | `GET /api/server/info` | 公开安全遥测 | 数据中心页面使用的非敏感服务器状态,包括在线状态、云厂商/区域标签、CPU 占用、内存使用、运行时长、运行时信息、CPU 核心数、goroutine 数和服务端时间;刻意不暴露 IP、主机名、磁盘路径、进程列表或环境变量。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go`, `mail_notifier.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见/可发布留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、连续回复、访客/登录用户发布、友链联系邮箱必填、UTF-8 邮件通知、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET /api/owner/emails`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、仅站长可见邮箱目录、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 和 `POST /api/ai/image` 调用 | 站长或服务端流程 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册/生成图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"contactEmail\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 普通留言墙未登录时提交 `nickname` + `content`,登录后提交 `content` 即可。顶层 `friends` 友链留言必须提供 `nickname` 和 `contactEmail`;公开响应不会暴露 `contactEmail`。回复会忽略提交的 `contactEmail`,并在可用时通知父留言联系人或账号邮箱。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `GET /api/owner/emails` | 无请求体 | 仅站长可访问,返回注册用户邮箱和私有留言联系邮箱目录。 |\n| `GET /api/ai/image` | 无请求体 | 返回生图额度、`imageEnabled`、`canGenerate`、当前模型和 `promptExtend: false`。 |\n| `POST /api/ai/image` | `{ \"prompt\": \"...\", \"size\": \"1024*1024\" }` | 仅登录用户可用;普通用户每天 3 张,站长不限额度。支持 `1024*1024`、`1280*720`、`720*1280`,返回的 `image.url` 是最终腾讯 COS 链接。 |\n| `GET /api/ai/image/gallery?limit=60&before=...` | 可选游标查询 | 公开生成图广场 feed,返回 `prompt`、`imageUrl`、`size`、`createdAt` 和匿名安全的 `author` 展示文本。 |\n| `GET /api/server/info` | 无请求体 | 返回 `/ai-traffic` 使用的非敏感实时遥测,前端每 10 秒刷新一次。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- AI 生图:配置 `AGNES_API_KEY`;可选配置 `AGNES_BASE_URL`(默认 `https://apihub.agnes-ai.com`)以及 `AGNES_IMAGE_MODEL` 或 `AI_IMAGE_MODEL`(默认 `agnes-image-2.1-flash`)。如果没有 `AGNES_API_KEY`,后端仍会回退兼容旧的 DashScope 变量 `DASHSCOPE_API_KEY`、`DASHSCOPE_BASE_URL`、`AI_IMAGE_MODEL` 和 `ALIYUN_BAILIAN_API_KEY`。\n- 邮件通知:配置 `SMTP_HOST`、`SMTP_PORT`、`SMTP_USER`、`SMTP_PASS`、`SMTP_FROM_NAME` 和 `MAIL_NOTIFY_TO`。`SMTP_PASS` 应该使用邮箱服务商的授权码或应用密码,不要使用账号登录密码。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器历史趋势与告警:在当前实时状态面板基础上继续记录 CPU、内存、错误率和同步耗时趋势。\n- 打造生成图展示页面:把已生成并保存到 COS 的图片整理成可浏览、可回看、可管理的展示页。\n- 继续填充项目集:让关于页里的项目卡片逐步补上真实项目页、截图、体验入口和版本记录。\n- 优化 AI QuizCard 导入流程:在默认题组可试玩的基础上,让用户导入、解析、保存题库和进入练习的路径更顺。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "242ea612617dba1fc7c7d3d8d08ab7b3f3a62b18": {"t": 1785754948.0404909, "n": 10728, "h": "fd7c3d92811c4fca", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、邮件通知、AI 助手、AI 生图、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 留言、邮件通知、AI 上下文、AI 生图、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复、AI 固定答案从站长控制台同步到网站助手的链路、UTF-8 安全邮件通知、友链联系邮箱收集、友链连续回复、站长后台邮箱可见能力、数据中心服务器状态监控,以及 AI 生图结果保存到腾讯 COS 的链路。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n数据中心页面(`/ai-traffic`)现在把 AI 流量和服务器状态放到同一个页面里。`ServerInfoPanel` 会每 10 秒刷新 `GET /api/server/info`,展示在线状态、云厂商/区域、CPU 占用、内存使用、运行时长、系统/Go 运行时、CPU 核心数、goroutine 数和服务端时间等安全遥测。该接口刻意不暴露真实 IP、主机名、磁盘路径、进程列表、环境变量等敏感服务器信息。\n\n右下角 AI 助手也加入了面向登录用户的生图模式。它会调用后端 `/api/ai/image`,使用 Agnes AI 的 `agnes-image-2.1-flash`,把生成图保存到腾讯 COS,并把最终 COS 链接、提示词和生成时间记录给 `/ai-gallery` 生成图广场,同时在屏幕中央用透明浅色镭射卡片展示结果,支持保存本地和复制最终 COS 链接。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、邮件通知、AI 助手上下文、AI 生图、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务。 |\n| `main.go` 受保护同步触发 | `POST /api/v1/sync/trigger` | 部署 token 或站长会话 | 手动同步触发;请求必须携带匹配 `SYNC_TRIGGER_TOKEN` 的 `X-Sync-Trigger-Token`,或使用已认证的站长会话。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `ai_image.go`, `ai_image_gallery.go` | `GET/POST /api/ai/image`, `GET /api/ai/image/gallery` | 生图需登录/站长额度;广场公开 | 生图额度状态、Agnes AI 生图、临时图下载、腾讯 COS 上传、生成历史记录,以及公开生成图广场 feed,包含提示词、图片 URL、作者展示名、尺寸和生成时间。 |\n| `server_info.go` | `GET /api/server/info` | 公开安全遥测 | 数据中心页面使用的非敏感服务器状态,包括在线状态、云厂商/区域标签、CPU 占用、内存使用、运行时长、运行时信息、CPU 核心数、goroutine 数和服务端时间;刻意不暴露 IP、主机名、磁盘路径、进程列表或环境变量。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go`, `mail_notifier.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见/可发布留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、连续回复、访客/登录用户发布、友链联系邮箱必填、UTF-8 邮件通知、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET /api/owner/emails`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、仅站长可见邮箱目录、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 和 `POST /api/ai/image` 调用 | 站长或服务端流程 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册/生成图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"contactEmail\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 普通留言墙未登录时提交 `nickname` + `content`,登录后提交 `content` 即可。顶层 `friends` 友链留言必须提供 `nickname` 和 `contactEmail`;公开响应不会暴露 `contactEmail`。回复会忽略提交的 `contactEmail`,并在可用时通知父留言联系人或账号邮箱。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `GET /api/owner/emails` | 无请求体 | 仅站长可访问,返回注册用户邮箱和私有留言联系邮箱目录。 |\n| `GET /api/ai/image` | 无请求体 | 返回生图额度、`imageEnabled`、`canGenerate`、当前模型和 `promptExtend: false`。 |\n| `POST /api/ai/image` | `{ \"prompt\": \"...\", \"size\": \"1024*1024\" }` | 仅登录用户可用;普通用户每天 3 张,站长不限额度。支持 `1024*1024`、`1280*720`、`720*1280`,返回的 `image.url` 是最终腾讯 COS 链接。 |\n| `GET /api/ai/image/gallery?limit=60&before=...` | 可选游标查询 | 公开生成图广场 feed,返回 `prompt`、`imageUrl`、`size`、`createdAt` 和匿名安全的 `author` 展示文本。 |\n| `GET /api/server/info` | 无请求体 | 返回 `/ai-traffic` 使用的非敏感实时遥测,前端每 10 秒刷新一次。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- AI 生图:配置 `AGNES_API_KEY`;可选配置 `AGNES_BASE_URL`(默认 `https://apihub.agnes-ai.com`)以及 `AGNES_IMAGE_MODEL` 或 `AI_IMAGE_MODEL`(默认 `agnes-image-2.1-flash`)。如果没有 `AGNES_API_KEY`,后端仍会回退兼容旧的 DashScope 变量 `DASHSCOPE_API_KEY`、`DASHSCOPE_BASE_URL`、`AI_IMAGE_MODEL` 和 `ALIYUN_BAILIAN_API_KEY`。\n- 邮件通知:配置 `SMTP_HOST`、`SMTP_PORT`、`SMTP_USER`、`SMTP_PASS`、`SMTP_FROM_NAME` 和 `MAIL_NOTIFY_TO`。`SMTP_PASS` 应该使用邮箱服务商的授权码或应用密码,不要使用账号登录密码。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器历史趋势与告警:在当前实时状态面板基础上继续记录 CPU、内存、错误率和同步耗时趋势。\n- 打造生成图展示页面:把已生成并保存到 COS 的图片整理成可浏览、可回看、可管理的展示页。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "21aa9ca1bb18f5a1997d84ffd333da49c1f82646": {"t": 1785754949.4448152, "n": 10748, "h": "f53864e0698dc7af", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、邮件通知、AI 助手、AI 生图、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 留言、邮件通知、AI 上下文、AI 生图、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复、AI 固定答案从站长控制台同步到网站助手的链路、UTF-8 安全邮件通知、友链联系邮箱收集、友链连续回复、站长后台邮箱可见能力、数据中心服务器状态监控,以及 AI 生图结果保存到腾讯 COS 的链路。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n数据中心页面(`/ai-traffic`)现在把 AI 流量和服务器状态放到同一个页面里。`ServerInfoPanel` 会每 10 秒刷新 `GET /api/server/info`,展示在线状态、云厂商/区域、CPU 占用、内存使用、运行时长、系统/Go 运行时、CPU 核心数、goroutine 数和服务端时间等安全遥测。该接口刻意不暴露真实 IP、主机名、磁盘路径、进程列表、环境变量等敏感服务器信息。\n\n右下角 AI 助手也加入了面向登录用户的生图模式。它会调用后端 `/api/ai/image`,使用 Agnes AI 的 `agnes-image-2.1-flash`,把生成图保存到腾讯 COS,并把最终 COS 链接、提示词和生成时间记录给 `/ai-gallery` 生成图广场,同时在屏幕中央用透明浅色镭射卡片展示结果,支持保存本地和复制最终 COS 链接。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、邮件通知、AI 助手上下文、AI 生图、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务。 |\n| `main.go` 受保护同步触发 | `POST /api/v1/sync/trigger` | 部署 token 或站长会话 | 手动同步触发;请求必须携带匹配 `SYNC_TRIGGER_TOKEN` 的 `X-Sync-Trigger-Token`,或使用已认证的站长会话。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `ai_image.go`, `ai_image_gallery.go` | `GET/POST /api/ai/image`, `GET /api/ai/image/gallery` | 生图需登录/站长额度;广场公开 | 生图额度状态、Agnes AI 生图、临时图下载、腾讯 COS 上传、生成历史记录,以及公开生成图广场 feed,包含提示词、图片 URL、作者展示名、尺寸和生成时间。 |\n| `server_info.go` | `GET /api/server/info` | 公开安全遥测 | 数据中心页面使用的非敏感服务器状态,包括在线状态、云厂商/区域标签、CPU 占用、内存使用、运行时长、运行时信息、CPU 核心数、goroutine 数和服务端时间;刻意不暴露 IP、主机名、磁盘路径、进程列表或环境变量。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go`, `mail_notifier.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见留言;登录后才能发布;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、连续回复、邮箱登录后发布、友链联系邮箱必填、UTF-8 邮件通知、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET /api/owner/emails`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、仅站长可见邮箱目录、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 和 `POST /api/ai/image` 调用 | 站长或服务端流程 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册/生成图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"contactEmail\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 必须先有邮箱登录态 `acg_session`,匿名请求会返回 `401 LOGIN_REQUIRED`。普通留言墙只需要 `content`。顶层 `friends` 友链留言需要提供 `nickname` 和 `contactEmail`;公开响应不会暴露 `contactEmail`。回复会忽略提交的 `contactEmail`,并在可用时通知父留言联系人或账号邮箱。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `GET /api/owner/emails` | 无请求体 | 仅站长可访问,返回注册用户邮箱和私有留言联系邮箱目录。 |\n| `GET /api/ai/image` | 无请求体 | 返回生图额度、`imageEnabled`、`canGenerate`、当前模型和 `promptExtend: false`。 |\n| `POST /api/ai/image` | `{ \"prompt\": \"...\", \"size\": \"1024*1024\" }` | 仅登录用户可用;普通用户每天 3 张,站长不限额度。支持 `1024*1024`、`1280*720`、`720*1280`,返回的 `image.url` 是最终腾讯 COS 链接。 |\n| `GET /api/ai/image/gallery?limit=60&before=...` | 可选游标查询 | 公开生成图广场 feed,返回 `prompt`、`imageUrl`、`size`、`createdAt` 和匿名安全的 `author` 展示文本。 |\n| `GET /api/server/info` | 无请求体 | 返回 `/ai-traffic` 使用的非敏感实时遥测,前端每 10 秒刷新一次。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- AI 生图:配置 `AGNES_API_KEY`;可选配置 `AGNES_BASE_URL`(默认 `https://apihub.agnes-ai.com`)以及 `AGNES_IMAGE_MODEL` 或 `AI_IMAGE_MODEL`(默认 `agnes-image-2.1-flash`)。如果没有 `AGNES_API_KEY`,后端仍会回退兼容旧的 DashScope 变量 `DASHSCOPE_API_KEY`、`DASHSCOPE_BASE_URL`、`AI_IMAGE_MODEL` 和 `ALIYUN_BAILIAN_API_KEY`。\n- 邮件通知:配置 `SMTP_HOST`、`SMTP_PORT`、`SMTP_USER`、`SMTP_PASS`、`SMTP_FROM_NAME` 和 `MAIL_NOTIFY_TO`。`SMTP_PASS` 应该使用邮箱服务商的授权码或应用密码,不要使用账号登录密码。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器历史趋势与告警:在当前实时状态面板基础上继续记录 CPU、内存、错误率和同步耗时趋势。\n- 打造生成图展示页面:把已生成并保存到 COS 的图片整理成可浏览、可回看、可管理的展示页。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "e455d3fdd64b1ca7caf7b15282e88409f9987dd7": {"t": 1785754950.9815915, "n": 10701, "h": "1b57af48f2e4e109", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、邮件通知、AI 助手、AI 生图、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 留言、邮件通知、AI 上下文、AI 生图、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复、AI 固定答案从站长控制台同步到网站助手的链路、UTF-8 安全邮件通知、友链联系邮箱收集、友链连续回复、站长后台邮箱可见能力、数据中心服务器状态监控,以及 AI 生图结果保存到腾讯 COS 的链路。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n数据中心页面(`/ai-traffic`)现在把 AI 流量和服务器状态放到同一个页面里。`ServerInfoPanel` 会每 10 秒刷新 `GET /api/server/info`,展示在线状态、云厂商/区域、CPU 占用、内存使用、运行时长、系统/Go 运行时、CPU 核心数、goroutine 数和服务端时间等安全遥测。该接口刻意不暴露真实 IP、主机名、磁盘路径、进程列表、环境变量等敏感服务器信息。\n\n右下角 AI 助手也加入了面向登录用户的生图模式。它会调用后端 `/api/ai/image`,使用 Agnes AI 的 `agnes-image-2.1-flash`,把生成图保存到腾讯 COS,并把最终 COS 链接、提示词和生成时间记录给 `/ai-gallery` 生成图广场,同时在屏幕中央用透明浅色镭射卡片展示结果,支持保存本地和复制最终 COS 链接。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、邮件通知、AI 助手上下文、AI 生图、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务。 |\n| `main.go` 受保护同步触发 | `POST /api/v1/sync/trigger` | 部署 token 或站长会话 | 手动同步触发;请求必须携带匹配 `SYNC_TRIGGER_TOKEN` 的 `X-Sync-Trigger-Token`,或使用已认证的站长会话。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `ai_image.go`, `ai_image_gallery.go` | `GET/POST /api/ai/image`, `GET /api/ai/image/gallery` | 生图需登录/站长额度;广场公开 | 生图额度状态、Agnes AI 生图、临时图下载、腾讯 COS 上传、生成历史记录,以及公开生成图广场 feed,包含提示词、图片 URL、作者展示名、尺寸和生成时间。 |\n| `server_info.go` | `GET /api/server/info` | 公开安全遥测 | 数据中心页面使用的非敏感服务器状态,包括在线状态、云厂商/区域标签、CPU 占用、内存使用、运行时长、运行时信息、CPU 核心数、goroutine 数和服务端时间;刻意不暴露 IP、主机名、磁盘路径、进程列表或环境变量。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go`, `mail_notifier.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、连续回复、匿名/登录用户、友链联系邮箱必填、UTF-8 邮件通知、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET /api/owner/emails`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、仅站长可见邮箱目录、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 和 `POST /api/ai/image` 调用 | 站长或服务端流程 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册/生成图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"contactEmail\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 普通留言墙不强制 `contactEmail`。顶层 `friends` 友链留言不管匿名或登录,都必须提供 `nickname` 和 `contactEmail`;公开响应不会暴露 `contactEmail`。回复会忽略提交的 `contactEmail`,并在可用时通知父留言联系人或账号邮箱。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `GET /api/owner/emails` | 无请求体 | 仅站长可访问,返回注册用户邮箱和私有留言联系邮箱目录。 |\n| `GET /api/ai/image` | 无请求体 | 返回生图额度、`imageEnabled`、`canGenerate`、当前模型和 `promptExtend: false`。 |\n| `POST /api/ai/image` | `{ \"prompt\": \"...\", \"size\": \"1024*1024\" }` | 仅登录用户可用;普通用户每天 3 张,站长不限额度。支持 `1024*1024`、`1280*720`、`720*1280`,返回的 `image.url` 是最终腾讯 COS 链接。 |\n| `GET /api/ai/image/gallery?limit=60&before=...` | 可选游标查询 | 公开生成图广场 feed,返回 `prompt`、`imageUrl`、`size`、`createdAt` 和匿名安全的 `author` 展示文本。 |\n| `GET /api/server/info` | 无请求体 | 返回 `/ai-traffic` 使用的非敏感实时遥测,前端每 10 秒刷新一次。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- AI 生图:配置 `AGNES_API_KEY`;可选配置 `AGNES_BASE_URL`(默认 `https://apihub.agnes-ai.com`)以及 `AGNES_IMAGE_MODEL` 或 `AI_IMAGE_MODEL`(默认 `agnes-image-2.1-flash`)。如果没有 `AGNES_API_KEY`,后端仍会回退兼容旧的 DashScope 变量 `DASHSCOPE_API_KEY`、`DASHSCOPE_BASE_URL`、`AI_IMAGE_MODEL` 和 `ALIYUN_BAILIAN_API_KEY`。\n- 邮件通知:配置 `SMTP_HOST`、`SMTP_PORT`、`SMTP_USER`、`SMTP_PASS`、`SMTP_FROM_NAME` 和 `MAIL_NOTIFY_TO`。`SMTP_PASS` 应该使用邮箱服务商的授权码或应用密码,不要使用账号登录密码。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器历史趋势与告警:在当前实时状态面板基础上继续记录 CPU、内存、错误率和同步耗时趋势。\n- 打造生成图展示页面:把已生成并保存到 COS 的图片整理成可浏览、可回看、可管理的展示页。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "0583af38f71e35b091b08e85ed7befeac59c2f0b": {"t": 1785754952.3067033, "n": 10324, "h": "238a8a2c3f745b13", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、邮件通知、AI 助手、AI 生图、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 留言、邮件通知、AI 上下文、AI 生图、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复、AI 固定答案从站长控制台同步到网站助手的链路、UTF-8 安全邮件通知、友链联系邮箱收集、友链连续回复、站长后台邮箱可见能力、数据中心服务器状态监控,以及 AI 生图结果保存到腾讯 COS 的链路。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n数据中心页面(`/ai-traffic`)现在把 AI 流量和服务器状态放到同一个页面里。`ServerInfoPanel` 会每 10 秒刷新 `GET /api/server/info`,展示在线状态、云厂商/区域、CPU 占用、内存使用、运行时长、系统/Go 运行时、CPU 核心数、goroutine 数和服务端时间等安全遥测。该接口刻意不暴露真实 IP、主机名、磁盘路径、进程列表、环境变量等敏感服务器信息。\n\n右下角 AI 助手也加入了面向登录用户的生图模式。它会调用后端 `/api/ai/image`,使用阿里云百炼 DashScope 的 `z-image-turbo`,固定关闭提示词改写,把生成图保存到腾讯 COS,并在屏幕中央用透明浅色镭射卡片展示结果,支持保存本地和复制最终 COS 链接。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、邮件通知、AI 助手上下文、AI 生图、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务。 |\n| `main.go` 受保护同步触发 | `POST /api/v1/sync/trigger` | 部署 token 或站长会话 | 手动同步触发;请求必须携带匹配 `SYNC_TRIGGER_TOKEN` 的 `X-Sync-Trigger-Token`,或使用已认证的站长会话。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `ai_image.go` | `GET/POST /api/ai/image` | 登录/站长额度 | 生图额度状态、DashScope `z-image-turbo` 生图、固定 `promptExtend: false`、临时图下载、腾讯 COS 上传和生成历史记录。 |\n| `server_info.go` | `GET /api/server/info` | 公开安全遥测 | 数据中心页面使用的非敏感服务器状态,包括在线状态、云厂商/区域标签、CPU 占用、内存使用、运行时长、运行时信息、CPU 核心数、goroutine 数和服务端时间;刻意不暴露 IP、主机名、磁盘路径、进程列表或环境变量。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go`, `mail_notifier.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、连续回复、匿名/登录用户、友链联系邮箱必填、UTF-8 邮件通知、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET /api/owner/emails`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、仅站长可见邮箱目录、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 和 `POST /api/ai/image` 调用 | 站长或服务端流程 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册/生成图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"contactEmail\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 普通留言墙不强制 `contactEmail`。顶层 `friends` 友链留言不管匿名或登录,都必须提供 `nickname` 和 `contactEmail`;公开响应不会暴露 `contactEmail`。回复会忽略提交的 `contactEmail`,并在可用时通知父留言联系人或账号邮箱。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `GET /api/owner/emails` | 无请求体 | 仅站长可访问,返回注册用户邮箱和私有留言联系邮箱目录。 |\n| `GET /api/ai/image` | 无请求体 | 返回生图额度、`imageEnabled`、`canGenerate`、当前模型和 `promptExtend: false`。 |\n| `POST /api/ai/image` | `{ \"prompt\": \"...\", \"size\": \"1024*1024\" }` | 仅登录用户可用;普通用户每天 3 张,站长不限额度。支持 `1024*1024`、`1280*720`、`720*1280`,返回的 `image.url` 是最终腾讯 COS 链接。 |\n| `GET /api/server/info` | 无请求体 | 返回 `/ai-traffic` 使用的非敏感实时遥测,前端每 10 秒刷新一次。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- AI 生图:配置 `DASHSCOPE_API_KEY`;可选配置 `DASHSCOPE_BASE_URL` 和 `AI_IMAGE_MODEL`(默认 `z-image-turbo`)。旧变量 `ALIYUN_BAILIAN_API_KEY` 也可作为百炼 Key 的兼容回退。\n- 邮件通知:配置 `SMTP_HOST`、`SMTP_PORT`、`SMTP_USER`、`SMTP_PASS`、`SMTP_FROM_NAME` 和 `MAIL_NOTIFY_TO`。`SMTP_PASS` 应该使用邮箱服务商的授权码或应用密码,不要使用账号登录密码。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器历史趋势与告警:在当前实时状态面板基础上继续记录 CPU、内存、错误率和同步耗时趋势。\n- 打造生成图展示页面:把已生成并保存到 COS 的图片整理成可浏览、可回看、可管理的展示页。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "f5d48c44c45a224a78fcf1b340ae0c0e5ef38956": {"t": 1785754953.629865, "n": 9850, "h": "96d808778933e9a5", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、邮件通知、AI 助手、AI 生图、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 留言、邮件通知、AI 上下文、AI 生图、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复、AI 固定答案从站长控制台同步到网站助手的链路、UTF-8 安全邮件通知、友链联系邮箱收集、友链连续回复、站长后台邮箱可见能力,以及 AI 生图结果保存到腾讯 COS 的链路。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n右下角 AI 助手也加入了面向登录用户的生图模式。它会调用后端 `/api/ai/image`,使用阿里云百炼 DashScope 的 `z-image-turbo`,固定关闭提示词改写,把生成图保存到腾讯 COS,并在屏幕中央用透明浅色镭射卡片展示结果,支持保存本地和复制最终 COS 链接。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、邮件通知、AI 助手上下文、AI 生图、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务。 |\n| `main.go` 受保护同步触发 | `POST /api/v1/sync/trigger` | 部署 token 或站长会话 | 手动同步触发;请求必须携带匹配 `SYNC_TRIGGER_TOKEN` 的 `X-Sync-Trigger-Token`,或使用已认证的站长会话。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `ai_image.go` | `GET/POST /api/ai/image` | 登录/站长额度 | 生图额度状态、DashScope `z-image-turbo` 生图、固定 `promptExtend: false`、临时图下载、腾讯 COS 上传和生成历史记录。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go`, `mail_notifier.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、连续回复、匿名/登录用户、友链联系邮箱必填、UTF-8 邮件通知、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET /api/owner/emails`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、仅站长可见邮箱目录、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 和 `POST /api/ai/image` 调用 | 站长或服务端流程 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册/生成图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"contactEmail\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 普通留言墙不强制 `contactEmail`。顶层 `friends` 友链留言不管匿名或登录,都必须提供 `nickname` 和 `contactEmail`;公开响应不会暴露 `contactEmail`。回复会忽略提交的 `contactEmail`,并在可用时通知父留言联系人或账号邮箱。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `GET /api/owner/emails` | 无请求体 | 仅站长可访问,返回注册用户邮箱和私有留言联系邮箱目录。 |\n| `GET /api/ai/image` | 无请求体 | 返回生图额度、`imageEnabled`、`canGenerate`、当前模型和 `promptExtend: false`。 |\n| `POST /api/ai/image` | `{ \"prompt\": \"...\", \"size\": \"1024*1024\" }` | 仅登录用户可用;普通用户每天 3 张,站长不限额度。支持 `1024*1024`、`1280*720`、`720*1280`,返回的 `image.url` 是最终腾讯 COS 链接。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- AI 生图:配置 `DASHSCOPE_API_KEY`;可选配置 `DASHSCOPE_BASE_URL` 和 `AI_IMAGE_MODEL`(默认 `z-image-turbo`)。旧变量 `ALIYUN_BAILIAN_API_KEY` 也可作为百炼 Key 的兼容回退。\n- 邮件通知:配置 `SMTP_HOST`、`SMTP_PORT`、`SMTP_USER`、`SMTP_PASS`、`SMTP_FROM_NAME` 和 `MAIL_NOTIFY_TO`。`SMTP_PASS` 应该使用邮箱服务商的授权码或应用密码,不要使用账号登录密码。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器数据和状态监测:在站长控制台中展示服务健康、资源使用、同步状态和关键数据趋势。\n- 打造生成图展示页面:把已生成并保存到 COS 的图片整理成可浏览、可回看、可管理的展示页。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "5ddbdbfe56b9cc4c3b919b248b980725dec7cfb2": {"t": 1785754956.04155, "n": 9740, "h": "4ce663ad21a7be2e", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、邮件通知、AI 助手、AI 生图、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 留言、邮件通知、AI 上下文、AI 生图、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复、AI 固定答案从站长控制台同步到网站助手的链路、UTF-8 安全邮件通知、友链联系邮箱收集、友链连续回复、站长后台邮箱可见能力,以及 AI 生图结果保存到腾讯 COS 的链路。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n右下角 AI 助手也加入了面向登录用户的生图模式。它会调用后端 `/api/ai/image`,使用阿里云百炼 DashScope 的 `z-image-turbo`,固定关闭提示词改写,把生成图保存到腾讯 COS,并在屏幕中央用透明浅色镭射卡片展示结果,支持保存本地和复制最终 COS 链接。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、邮件通知、AI 助手上下文、AI 生图、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name`, `POST /api/v1/sync/trigger` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务、手动同步触发。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `ai_image.go` | `GET/POST /api/ai/image` | 登录/站长额度 | 生图额度状态、DashScope `z-image-turbo` 生图、固定 `promptExtend: false`、临时图下载、腾讯 COS 上传和生成历史记录。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go`, `mail_notifier.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、连续回复、匿名/登录用户、友链联系邮箱必填、UTF-8 邮件通知、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET /api/owner/emails`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、仅站长可见邮箱目录、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 和 `POST /api/ai/image` 调用 | 站长或服务端流程 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册/生成图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"contactEmail\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 普通留言墙不强制 `contactEmail`。顶层 `friends` 友链留言不管匿名或登录,都必须提供 `nickname` 和 `contactEmail`;公开响应不会暴露 `contactEmail`。回复会忽略提交的 `contactEmail`,并在可用时通知父留言联系人或账号邮箱。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `GET /api/owner/emails` | 无请求体 | 仅站长可访问,返回注册用户邮箱和私有留言联系邮箱目录。 |\n| `GET /api/ai/image` | 无请求体 | 返回生图额度、`imageEnabled`、`canGenerate`、当前模型和 `promptExtend: false`。 |\n| `POST /api/ai/image` | `{ \"prompt\": \"...\", \"size\": \"1024*1024\" }` | 仅登录用户可用;普通用户每天 3 张,站长不限额度。支持 `1024*1024`、`1280*720`、`720*1280`,返回的 `image.url` 是最终腾讯 COS 链接。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- AI 生图:配置 `DASHSCOPE_API_KEY`;可选配置 `DASHSCOPE_BASE_URL` 和 `AI_IMAGE_MODEL`(默认 `z-image-turbo`)。旧变量 `ALIYUN_BAILIAN_API_KEY` 也可作为百炼 Key 的兼容回退。\n- 邮件通知:配置 `SMTP_HOST`、`SMTP_PORT`、`SMTP_USER`、`SMTP_PASS`、`SMTP_FROM_NAME` 和 `MAIL_NOTIFY_TO`。`SMTP_PASS` 应该使用邮箱服务商的授权码或应用密码,不要使用账号登录密码。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器数据和状态监测:在站长控制台中展示服务健康、资源使用、同步状态和关键数据趋势。\n- 打造生成图展示页面:把已生成并保存到 COS 的图片整理成可浏览、可回看、可管理的展示页。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "bfe3552bd42e43a545bb40d0af29bf82b30d217c": {"t": 1785754957.444872, "n": 8516, "h": "491045cf9b77fa63", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、AI 助手、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 留言、AI 上下文、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正、首页和其他页面的双层滚动修复,以及 AI 固定答案从站长控制台同步到网站助手的链路。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n站长控制台里的 AI 固定答案区现在不再只保存在本地 React 状态中,而是写入后端固定问答库。`GET /api/owner/status` 会把已保存答案返回给控制台查看,`/api/chat` 在调用 DeepSeek 前会先匹配固定答案,因此网站右下角 AI 助手遇到相同问题时可以优先返回维护好的回复。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、AI 助手上下文、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name`, `POST /api/v1/sync/trigger` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务、手动同步触发。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go`, `ai_fixed_answers.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、固定答案匹配、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、回复、匿名/登录用户、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET/POST /api/owner/drafts`, `POST /api/owner/ai/fixed-answers`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、AI 固定答案保存、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 调用 | 站长 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 匿名留言必须提供 `nickname`;登录用户使用账号昵称。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/ai/fixed-answers` | `{ \"question\": \"...\", \"answer\": \"...\" }` | 保存或更新仅站长可维护的固定答案。公开 `/api/chat` 会规范化用户问题并优先返回固定答案,再回退到 DeepSeek。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器数据和状态监测:在站长控制台中展示服务健康、资源使用、同步状态和关键数据趋势。\n- 邮箱绑定与发送:补充账号邮箱绑定、验证和通知发送能力。\n- 部署低成本生图 AI:接入预算更低的生图服务,并限制为仅登录账户可用。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "0e3a9f9ceb117e2063f138b77e74682d88948f18": {"t": 1785754960.856349, "n": 8069, "h": "c88790575e7f7aa0", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、AI 助手、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板、友链、相册和 `/moments` 碎语页面等站内功能路由与集成。\n- 留言、AI 上下文、Bili 数据缓存、站长发布、友链、相册图片和碎语更新等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n最近的成长记录也补充了 `/moments` 碎语页面迁移、站长控制台碎语发布链路、Leyili 花园友链修正,以及首页和其他页面的双层滚动修复。\n\n### `main/` 主站说明\n\n主站现在包含一个正式的 `/moments` 页面,页面名称为“碎语”。它可以从顶部导航进入,并从 `main/src/data/moments.js` 读取短内容,使用 postcard、ticket、watercolor、poem、journal、ribbon 等多种全息卡片模块展示。\n\n最近也修正了 `https://930309.xyz/` 的友链信息:名称为 `Leyili 花园`,图标为 `https://photo.930309.xyz/lcj.svg`,描述为 `小小后花园~~~`。独立的 `090909.top` 友链仍然保留为“他说”。\n\n首页双层滚动条问题已经通过收紧 Hero 与站点布局的 overflow 行为修复,并补充了布局类名相关的回归测试。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、AI 助手上下文、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name`, `POST /api/v1/sync/trigger` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务、手动同步触发。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、回复、匿名/登录用户、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET/POST /api/owner/drafts`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_moment_publish.go` | `POST /api/owner/moments` | 站长 + GitHub token | 写入碎语到 `main/src/data/moments.js`,校验年份、日期、分类和内容,分配下一组视觉卡片样式,按日期插入,并返回 commit 信息。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 调用 | 站长 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 匿名留言必须提供 `nickname`;登录用户使用账号昵称。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/moments` | `{ \"year\": \"2026\", \"date\": \"6.8\", \"type\": \"...\", \"content\": \"...\" }` | 写入一条碎语到 `main/src/data/moments.js`;`category` 也可作为 `type` 的兼容字段。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 后续计划\n\n- 服务器数据和状态监测:在站长控制台中展示服务健康、资源使用、同步状态和关键数据趋势。\n- 邮箱绑定与发送:补充账号邮箱绑定、验证和通知发送能力。\n- 设计艺术字主页:继续探索更有辨识度的首页标题与艺术字视觉方向。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "e04e71d87695cac3b24b11ddfef7be5102f2be20": {"t": 1785754962.08051, "n": 7145, "h": "b5a43adc38852e39", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、AI 助手、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板等站内功能路由与集成。\n- 留言、AI 上下文、Bili 数据缓存等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、AI 助手上下文、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它是从 `acg-api/main.go` 启动的标准 `net/http` 服务:启动时会加载 `/opt/acg-api/.env` 和本地 `.env`,在 `ACG_DATA_DIR/acg.db` 打开 SQLite,执行数据库迁移,保留站长账号,启动 Bilibili / radar / 壁纸池同步循环,并通常由 Nginx 反向代理暴露为 `/api`。\n\n后端控制器说明:\n\n| 控制器 / 文件 | 主要路由 | 权限 | 职责 |\n| --- | --- | --- | --- |\n| `main.go` 公开控制器 | `GET /api/v1/health`, `GET /api/v1/bangumi/list`, `GET /api/v1/radar/feed`, `GET /api/v1/wallpapers/draw`, `GET /api/v1/acg/image/:name`, `POST /api/v1/sync/trigger` | 公开 | 健康检查、Bili 番剧缓存、radar 动态、壁纸抽取池、缓存封面/图片服务、手动同步触发。 |\n| `main.go` 旧留言接口 | `GET/POST /api/v1/guestbook` | 公开 | 早期简单留言 API,只包含 `name` 和 `content`,用于兼容旧前端。 |\n| `auth.go` | `POST /api/auth/register`, `POST /api/auth/login`, `POST /api/auth/verify-security`, `POST /api/auth/logout`, `PATCH /api/auth/profile`, `GET /api/auth/me` | 公开/登录/站长二次验证 | 邮箱注册、登录、会话 cookie、昵称修改、站长二次验证。 |\n| `chat.go`, `chat_quota.go`, `chat_stats.go`, `deepseek_client.go` | `GET/POST /api/chat`, `GET /api/chat/stats` | 公开/登录/站长额度 | AI 助手额度查询、聊天请求、DeepSeek 转发、小时/日统计、游客/用户/站长额度控制。 |\n| `guestbook_messages.go`, `guestbook_user.go`, `guestbook_ip.go` | `GET/POST /api/guestbook/messages`, `PATCH/DELETE /api/guestbook/messages/:id`, `PATCH /api/guestbook/messages/:id/status` | 公开可见留言;站长/管理员可审核 | 当前留言板和朋友页留言控制器,支持分区、回复、匿名/登录用户、限流、防重复、IP 脱敏和软审核。 |\n| `owner_controller.go` | `GET /api/owner/status`, `GET/POST /api/owner/drafts`, `PATCH /api/owner/notifications/:id/read`, `POST /api/owner/uploads`, `GET /api/owner/uploads/:name`, `POST /api/owner/assets` | 仅站长 | 站长控制台状态、注册用户列表、未读留言收件箱、草稿保存、本地临时图片上传、COS 资源上传。 |\n| `owner_publish.go` | `POST /api/owner/publish` | 站长 + GitHub token | 通过 GitHub Contents API 发布 Markdown 到 `blog/source/_posts/`,合并 front matter,处理文件名冲突,并返回 commit 信息。 |\n| `owner_friend_publish.go` | `POST /api/owner/friends` | 站长 + GitHub token | 写入友链到 `main/src/data/friendCards.js`,会检测重复 URL,已有链接不会重复写入。 |\n| `owner_gallery_publish.go` | `POST /api/owner/gallery/images` | 站长 + GitHub token | 写入公开图片 URL 到 `main/src/data/galleryAlbums.js`,必要时创建相册,并避免重复图片。 |\n| `owner_cos.go` | 被 `POST /api/owner/assets` 调用 | 站长 + COS 凭据 | 使用服务器端腾讯 COS 凭据上传文章/相册图片,并返回公开 URL 和对象 key。 |\n\n重要请求形状:\n\n| 路由 | 请求体/查询参数 | 说明 |\n| --- | --- | --- |\n| `POST /api/auth/register` | `{ \"email\": \"...\", \"password\": \"...\" }` | 普通用户注册;站长邮箱被保留。 |\n| `POST /api/auth/login` | `{ \"email\": \"...\", \"password\": \"...\" }` | 站长登录会返回 `challengeToken`,还需要调用 `/api/auth/verify-security`。 |\n| `POST /api/auth/verify-security` | `{ \"challengeToken\": \"...\", \"answer\": \"...\" }` | 私密答案匹配后创建站长会话,并获得无限 AI 额度。 |\n| `GET /api/guestbook/messages?page=1&pageSize=20&channel=guestbook` | `channel` 可为 `guestbook` 或 `friends` | 普通访问者只看可见留言;站长/管理员可看隐藏留言。 |\n| `POST /api/guestbook/messages` | `{ \"nickname\": \"...\", \"content\": \"...\", \"parentId\": 0, \"channel\": \"guestbook\" }` | 匿名留言必须提供 `nickname`;登录用户使用账号昵称。 |\n| `PATCH /api/guestbook/messages/:id` | `{ \"status\": \"visible\" \\| \"hidden\" \\| \"deleted\" }` | 仅站长/管理员审核。 |\n| `POST /api/owner/uploads` | `multipart/form-data`,字段 `file` | 保存本地临时图片到 `ACG_DATA_DIR/owner-uploads`,最大 8 MiB。 |\n| `POST /api/owner/assets` | `multipart/form-data`,字段 `file`、`kind` 为 `gallery` 或 `article`,可选 `album` | 上传到腾讯 COS;相册上传必须带相册。 |\n| `POST /api/owner/publish` | `{ \"draftId\": 1, \"title\": \"...\", \"body\": \"...\", \"coverUrl\": \"...\" }` | 通过配置好的 token 产生真实 GitHub commit。 |\n| `POST /api/owner/friends` | `{ \"name\": \"...\", \"desc\": \"...\", \"url\": \"...\", \"avatar\": \"...\" }` | `url` 和 `avatar` 必须是公开 `http`/`https` 地址。 |\n| `POST /api/owner/gallery/images` | `{ \"albumId\": \"...\", \"albumTitle\": \"...\", \"imageUrl\": \"...\" }` | `imageUrl` 必须是公开 `http`/`https` 地址。 |\n\n拉取/部署环境清单:\n\n- 运行时:`ACG_API_ADDR` 默认 `:8787`;`ACG_DATA_DIR` 默认 `./data`。\n- 站长登录:配置 `AUTH_OWNER_PASSWORD` 和 `AUTH_OWNER_SECURITY_ANSWER`;可选配置 `AUTH_SESSION_DAYS` 与 `AUTH_COOKIE_SECURE`。\n- 重要身份说明:`owner.go` 里目前为本站部署保留了站长邮箱和安全问题文案。复用后端的人应该替换这些常量,或进一步改成环境变量。\n- AI 助手:配置 `DEEPSEEK_API_KEY`;可选配置 `DEEPSEEK_BASE_URL` 和 `DEEPSEEK_MODEL`。\n- GitHub 站长发布:配置 `OWNER_PUBLISH_GITHUB_TOKEN`;可选配置 `OWNER_PUBLISH_GITHUB_API_BASE`、`OWNER_PUBLISH_GITHUB_OWNER`、`OWNER_PUBLISH_GITHUB_REPO`、`OWNER_PUBLISH_GITHUB_BRANCH`。token 需要有写入仓库内容的权限。\n- 腾讯 COS 上传:配置 `TENCENT_COS_SECRET_ID`、`TENCENT_COS_SECRET_KEY`、`TENCENT_COS_BUCKET`、`TENCENT_COS_REGION`,可选配置 `TENCENT_COS_BASE_URL`。也兼容旧变量 `COS_SECRET_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_BASE_URL`。\n- Bili 同步:`BILIBILI_UID` 可以覆盖默认 UID;radar 创作者列表定义在 `acg-api/config.go`。\n- 前端 API 地址:生产构建时让 `VITE_API_BASE` 指向部署后的站点域名,例如 `https://taozhiyy.top`。\n\n安全边界:本仓库可能会暴露路由名称、控制器结构、环境变量名称、站长账号源码常量和部署 workflow 形状,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据、运行时 SQLite 数据库或私有 `.env`。后端上传/发布能力只有在真实凭据保存在服务器或 GitHub Actions secrets 中时才适合公开。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "e43dfe368c3ac0b602f6bd25099dfe0bd610a2cc": {"t": 1785754963.4309444, "n": 2832, "h": "c4a353f8f6bb32d5", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、AI 助手、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板等站内功能路由与集成。\n- 留言、AI 上下文、Bili 数据缓存等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n在这些原创/定制部分里,整体产品方向、功能规划、交互逻辑、内容组织和审美取舍由我主导。**Vibe Coding / AI 编程工具是实现辅助**:它们帮助我写代码、调试、整理细节和迭代版本,但不替代我对需求、设计判断和最终效果的主导。因此,本站的定制化产品设计和持续迭代成果属于我的原创设计与个人实践。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、AI 助手上下文、Bili Hub 数据缓存同步等功能。\n\n更具体地说,`acg-api` 是本站 `/api` 路由背后的 Go 服务层。它负责留言提交、AI 助手上下文/统计、Bili Hub 缓存同步、健康检查等公开功能,也负责站长专用流程,例如站长登录与会话校验、未读留言收件箱与标记已读、AI 注册用户查看、发布 build/blog 内容、发布友链/相册 URL,以及通过服务器端凭据完成 COS 图片上传。\n\n数据主要通过 SQLite 和服务器运行时文件保存。涉及 GitHub 写入、COS 上传或 AI 服务调用的动作,需要部署机器上的私有环境变量。本仓库可能会暴露路由名称、源码结构和环境变量名称,但不应该包含我的站长登录密码、验证答案、GitHub token、COS 密钥、AI Key、服务器凭据或私有数据库。别人拉取本项目后,仍然需要配置自己的密码、验证信息、密钥、数据库、部署主机和外部服务参数,不能直接复用我的后端私密信息。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}, "f89d2ac04229ddc5d816f01983c203aead8a3a3a": {"t": 1785754965.1816387, "n": 2277, "h": "a1fafe8ac9f9c154", "c": "# Taozhiyy Monorepo / 桃之夭夭 Monorepo\n\n中文说明 | [English](./README.md)\n\n这是我的个人网站 `桃之夭夭` 的 monorepo,包含主站、成长博客子站、Hexo 博客、共享 AI 助手脚本和后端 API。\n\n## 项目结构\n\n- `main/`: 主站前端,基于 React、Vite、Tailwind CSS 和 GSAP。\n- `build/`: 成长博客子站,用于记录本站从搭建到迭代的过程。\n- `blog/`: Hexo + Butterfly 博客。\n- `acg-api/`: Go 后端 API,包含留言板、AI 助手、Bili Hub 缓存同步等功能。\n- `shared/`: 多个站点共用的脚本与样式,例如右下角 AI 助手。\n- `deploy/`: 部署相关脚本。\n- `.github/workflows/`: GitHub Actions 自动部署配置。\n\n## 首页参考与学习声明\n\n本项目首页最初是在学习和参考以下项目的过程中搭建起来的:\n\n[adrianhajdin/award-winning-website](https://github.com/adrianhajdin/award-winning-website)\n\n我非常尊重原作者的劳动成果,也感谢该项目对我学习现代前端动画、滚动交互、视频视觉布局和沉浸式页面结构带来的帮助。\n\n需要明确说明的是:**当前项目并不是完全原创作品**。它仍然是在原作者项目的启发与学习基础上,逐步进行修改、重构和原创化改进的个人学习项目。\n\n我一直秉持尊重原作者劳动成果的理念推进项目原创化。无论这个项目未来原创化推进到多少完成度,哪怕有一天达到接近 100% 的个人实现,我也不会忘记这些开源项目和优秀教程曾经对我的帮助。\n\n本仓库与原作者、原教程项目或 Zentry 没有任何从属、授权、商业合作或官方关联关系。本说明公开写明参考来源,是为了保持透明,也避免让访问者误以为本项目完全脱离参考来源而独立产生。\n\n## 本项目中的原创与定制部分\n\n目前我已经围绕个人网站定位做了大量原创化和定制化改造,包括但不限于:\n\n- 基于头像徽章与指南针交互的 Hero 壁纸选择器。\n- `桃之夭夭` 个人站点身份、文案与内容组织。\n- About 区域的视觉明信片式滚动展示。\n- Features 区域的展厅布局与档案书翻页交互。\n- Blog、Build Log、Bili Hub、AI 助手、留言板等站内功能路由与集成。\n- 留言、AI 上下文、Bili 数据缓存等后端 API。\n- 适配个人域名、UCloud 服务器、GitHub Actions 自动部署的工程配置。\n\n这些原创化和定制化部分主要借助 **vibecoding** 的方式完成:我提出需求、判断方向、筛选效果并持续迭代,AI 编程工具辅助实现代码与视觉细节。\n\n由我本人完成、设计、组织或通过 vibecoding 推进出的原创部分,可以在尊重本说明的前提下自由参考、学习和使用。\n\n但如果某些内容涉及原参考项目的设计、结构、交互思路或代码来源,请不要直接从本仓库视为授权来源;请自行前往原作者项目查阅其说明、许可和使用边界。\n\n## 子页面与后端说明\n\n### `blog/` 博客页面\n\n`blog/` 子页面是基于 Hexo + Butterfly 搭建的博客页面。\n\n该部分的具体博客源码、主题配置、部署方式和更完整说明,请前往我的独立博客仓库查看:\n\n[bistutzyy/bistutzyy.github.io](https://github.com/bistutzyy/bistutzyy.github.io)\n\n该博客使用的主题原作者与项目为:\n\n[Butterfly](https://butterfly.js.org/)\n\n如果你有 Hexo、Butterfly 主题部署、主题配置、引用来源标注或许可边界相关问题,请自行前往 Butterfly 官方文档与原作者项目查阅。\n\n### `build/` 成长博客页面\n\n`build/` 页面是我借助 **vibecoding** 搭建出的成长博客子站,用于记录本站从域名、服务器、部署流程到功能迭代的搭建过程。\n\n该页面属于我个人学习和实践过程中搭建的原创/定制页面,欢迎自由参考、学习和使用。\n\n### `acg-api/` 后端与 API\n\n后端部分以及 API 调用逻辑主要借助 **vibecoding** 完成,包括留言板、AI 助手上下文、Bili Hub 数据缓存同步等功能。\n\n如果你希望参考或复用后端逻辑,请先自行阅读、验证和测试,确保符合你的使用场景后再使用。\n\n环境变量、密钥、服务器地址、数据库或外部服务配置请自行准备和配置,本仓库不会提供可直接复用的私密环境变量。\n\n如果你在使用、阅读或部署过程中发现任何问题,或有任何建议,欢迎向我提出。\n\n## 非商业立场\n\n本项目仅用于 **个人学习、技术探索、个人网站部署与非商业技术交流**。\n\n我不会将该首页作为模板售卖,不会将其包装成商业产品,也不会用于商业盈利。如果本项目中有任何部分被认为不合适、与参考项目过于接近,或不适合公开展示,我愿意继续修改、替换或移除。\n\n## 致谢\n\n感谢原作者和开源/前端学习社区提供的高质量学习资源。这个项目的成长离不开这些公开项目、教程和工具带来的启发。\n"}}}}