Files
portal_page/docs/system_design.md
T
kevin c16a8dfbc4 feat: 门户网站初始提交
- Go + Gin + html/template 服务端渲染
- 主页:Google 风格搜索框 + 导航卡片
- 后台:卡片 CRUD、搜索引擎配置、主页背景/标题配置
- 图片上传:支持 jpg/jpeg/png/gif,自动压缩,缩略图参数 ?thumb=1
- 安全:登录日志、修改密码、IP 自动封禁、IP 白名单
- 访问统计:主页访问/卡片点击/搜索追踪、实时流量、IP 统计
- SQLite 存储(modernc.org/sqlite,纯 Go)
- 内存 Session + bcrypt 密码哈希
2026-05-28 13:54:07 +08:00

674 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Simple Portal — 系统架构设计文档
## Part A: 系统设计
---
### 1. 实现方案分析
#### 核心技术挑战
| 挑战 | 解决方案 |
|------|---------|
| 纯服务端渲染,无 SPA 路由 | Gin 路由 + html/template 模板继承(layout 嵌套子模板),表单 POST 提交后 302 重定向 |
| SQLite 并发写入 | 单文件 SQLite + WAL 模式,Go 侧使用全局 `*sql.DB` 连接池(`SetMaxOpenConns(1)` 保证串行写) |
| Session 管理(无需外部依赖) | 内存 `map[string]*Session` + `sync.RWMutex`Cookie 存储 session ID,重启失效可接受 |
| 卡片排序 | `sort` 字段整数排序,上移/下移通过交换相邻卡片的 sort 值实现 |
| 搜索引擎占位符 | settings 表存储引擎 URL 模板(`%s` 占位符),前端 JS 拼接跳转,后端验证模板合法性 |
#### 框架与库选型
| 组件 | 选型 | 理由 |
|------|------|------|
| HTTP 框架 | `github.com/gin-gonic/gin` | PRD 指定 |
| SQLite 驱动 | `modernc.org/sqlite` | 纯 Go 实现,无需 CGO,交叉编译友好 |
| 密码哈希 | `golang.org/x/crypto/bcrypt` | PRD 指定,工业标准 |
| 模板引擎 | `html/template` | PRD 指定,Go 原生 |
| CSS | 单文件 `style.css` | PRD 约束,无 CSS 框架 |
#### 架构模式
采用 **经典 MVC 分层**
```
Request → Gin Router → Middleware(auth) → Handler → Model → SQLite
Template Rendering → HTML Response
```
- **Model**:纯数据访问层,封装 SQL 操作
- **Handler**Controller):处理 HTTP 请求,调用 Model,渲染模板
- **Template**View):html/template 模板文件
- **Middleware**:鉴权拦截,未登录重定向到登录页
- **Session**:独立包,提供 Create/Get/Delete 操作
---
### 2. 文件列表
```
Portal_page/
├── main.go # 程序入口:初始化 DB、路由、启动服务
├── go.mod # Go 模块定义
├── go.sum # 依赖校验
├── database/
│ └── db.go # DB 初始化、表创建、全局 *sql.DB
├── models/
│ ├── card.go # Card CRUD + 排序操作
│ ├── setting.go # Setting 读写(搜索引擎配置等)
│ └── admin.go # Admin 用户操作 + 密码验证
├── handlers/
│ ├── home.go # 主页渲染(卡片列表 + 搜索框)
│ ├── admin.go # 登录/登出 + 后台首页
│ ├── cards.go # 卡片增删改查 + 启用/禁用 + 排序
│ └── settings.go # 搜索引擎配置页面 + 保存
├── middleware/
│ └── auth.go # 登录鉴权中间件
├── session/
│ └── session.go # 内存 Session 管理
├── templates/
│ ├── layout.html # 基础布局(head/nav/footer
│ ├── home.html # 主页模板
│ └── admin/
│ ├── login.html # 登录页
│ ├── index.html # 后台首页(仪表盘)
│ ├── cards.html # 卡片列表管理
│ ├── card_form.html # 卡片新增/编辑表单
│ └── settings.html # 搜索引擎设置
└── static/
└── style.css # 全局样式
```
#### 各文件职责说明
| 文件 | 职责 |
|------|------|
| `main.go` | 加载配置、初始化 DB、注册路由和中间件、启动 HTTP 服务 |
| `database/db.go` | `InitDB()` 打开 SQLite 连接、建表、插入默认管理员和默认搜索引擎配置 |
| `models/card.go` | `Card` struct`GetAllCards()`/`GetEnabledCards()`/`CreateCard()`/`UpdateCard()`/`DeleteCard()`/`ToggleCard()`/`MoveCardUp()`/`MoveCardDown()` |
| `models/setting.go` | `Setting` struct`GetSetting(key)`/`SetSetting(key, value)`;预定义搜索引擎常量 |
| `models/admin.go` | `Admin` struct`GetAdminByUsername()`/`CreateAdmin()`/`VerifyPassword()`/`ChangePassword()` |
| `handlers/home.go` | `HomeHandler`:查询启用卡片 + 当前搜索引擎配置,渲染主页 |
| `handlers/admin.go` | `LoginGet`/`LoginPost`/`Logout`/`AdminIndex`:登录表单、认证、登出、后台首页 |
| `handlers/cards.go` | `CardsList`/`CardCreate`/`CardEdit`/`CardDelete`/`CardToggle`/`CardMoveUp`/`CardMoveDown` |
| `handlers/settings.go` | `SettingsGet`/`SettingsPost`:展示/保存搜索引擎配置 |
| `middleware/auth.go` | `AuthRequired()`:检查 Cookie 中的 session_id,未登录则 302 到 /admin/login |
| `session/session.go` | `SessionStore`:内存 map + RWMutex`Create()/Get()/Delete()` |
| `templates/layout.html` | HTML 骨架:`{{define "layout"}}...{{template "content" .}}{{end}}` |
| `templates/home.html` | 搜索框 + 卡片网格 |
| `templates/admin/*.html` | 后台各页面 |
| `static/style.css` | 全局 CSS:卡片网格、搜索框、表单、后台布局 |
---
### 3. 数据结构与接口
```mermaid
classDiagram
direction LR
class Card {
+int ID
+string Icon
+string Title
+string Subtitle
+string URL
+int Sort
+bool Enabled
+time Time CreatedAt
}
class Setting {
+string Key
+string Value
}
class Admin {
+int ID
+string Username
+string Password
}
class SessionData {
+int AdminID
+string Username
+time Time CreatedAt
}
class SessionStore {
-map~string-SessionData~ store
-sync.RWMutex mu
+Create(adminID int, username string) string
+Get(sessionID string) *SessionData
+Delete(sessionID string)
}
class CardModel {
+GetAllCards() []Card
+GetEnabledCards() []Card
+GetCardByID(id int) *Card
+CreateCard(card *Card) error
+UpdateCard(card *Card) error
+DeleteCard(id int) error
+ToggleCard(id int) error
+MoveCardUp(id int) error
+MoveCardDown(id int) error
}
class SettingModel {
+GetSetting(key string) string
+SetSetting(key string, value string) error
+GetSearchEngines() map~string-string
+SetSearchEngine(name string, url string) error
}
class AdminModel {
+GetAdminByUsername(username string) *Admin
+CreateAdmin(username string, password string) error
+VerifyPassword(username string, password string) bool
+ChangePassword(adminID int, newPassword string) error
}
class HandlerHome {
+HomeHandler(c *gin.Context)
}
class HandlerAdmin {
+LoginGet(c *gin.Context)
+LoginPost(c *gin.Context)
+Logout(c *gin.Context)
+AdminIndex(c *gin.Context)
}
class HandlerCards {
+CardsList(c *gin.Context)
+CardCreateGet(c *gin.Context)
+CardCreatePost(c *gin.Context)
+CardEditGet(c *gin.Context)
+CardEditPost(c *gin.Context)
+CardDelete(c *gin.Context)
+CardToggle(c *gin.Context)
+CardMoveUp(c *gin.Context)
+CardMoveDown(c *gin.Context)
}
class HandlerSettings {
+SettingsGet(c *gin.Context)
+SettingsPost(c *gin.Context)
}
class AuthMiddleware {
+AuthRequired() gin.HandlerFunc
}
CardModel --> Card : returns
SettingModel --> Setting : returns
AdminModel --> Admin : returns
SessionStore --> SessionData : stores
HandlerHome --> CardModel : uses
HandlerHome --> SettingModel : uses
HandlerAdmin --> AdminModel : uses
HandlerAdmin --> SessionStore : uses
HandlerCards --> CardModel : uses
HandlerSettings --> SettingModel : uses
AuthMiddleware --> SessionStore : uses
```
#### 路由表
| 方法 | 路径 | Handler | 中间件 | 说明 |
|------|------|---------|--------|------|
| GET | `/` | HomeHandler | — | 主页 |
| GET | `/admin/login` | LoginGet | — | 登录页 |
| POST | `/admin/login` | LoginPost | — | 登录提交 |
| POST | `/admin/logout` | Logout | AuthRequired | 登出 |
| GET | `/admin` | AdminIndex | AuthRequired | 后台首页 |
| GET | `/admin/cards` | CardsList | AuthRequired | 卡片列表 |
| GET | `/admin/cards/new` | CardCreateGet | AuthRequired | 新增卡片表单 |
| POST | `/admin/cards` | CardCreatePost | AuthRequired | 新增卡片提交 |
| GET | `/admin/cards/:id/edit` | CardEditGet | AuthRequired | 编辑卡片表单 |
| POST | `/admin/cards/:id` | CardEditPost | AuthRequired | 编辑卡片提交 |
| POST | `/admin/cards/:id/delete` | CardDelete | AuthRequired | 删除卡片 |
| POST | `/admin/cards/:id/toggle` | CardToggle | AuthRequired | 启用/禁用卡片 |
| POST | `/admin/cards/:id/move-up` | CardMoveUp | AuthRequired | 卡片上移 |
| POST | `/admin/cards/:id/move-down` | CardMoveDown | AuthRequired | 卡片下移 |
| GET | `/admin/settings` | SettingsGet | AuthRequired | 搜索引擎设置页 |
| POST | `/admin/settings` | SettingsPost | AuthRequired | 保存搜索引擎设置 |
---
### 4. 程序调用流程
#### 4.1 主页加载
```mermaid
sequenceDiagram
participant U as 用户浏览器
participant G as Gin Router
participant H as HomeHandler
participant CM as CardModel
participant SM as SettingModel
participant T as html/template
U->>G: GET /
G->>H: HomeHandler(c)
H->>CM: GetEnabledCards()
CM->>CM: SELECT * FROM cards WHERE enabled=1 ORDER BY sort
CM-->>H: []Card
H->>SM: GetSetting("search_engine")
SM->>SM: SELECT value FROM settings WHERE key='search_engine'
SM-->>H: search URL template
H->>T: ExecuteTemplate("home.html", data)
T-->>U: HTML (搜索框 + 卡片网格)
```
#### 4.2 搜索跳转
```mermaid
sequenceDiagram
participant U as 用户浏览器
participant JS as 前端 JavaScript
participant SE as 搜索引擎
U->>JS: 输入关键词,按回车/点击搜索
JS->>JS: 读取隐藏域 search_url_template
JS->>JS: fmt.Sprintf(template, query)
JS->>U: window.location.href = 拼接后的URL
U->>SE: HTTP 请求到搜索引擎
```
> 注意:搜索跳转纯前端 JS 完成,不经过后端路由。
#### 4.3 后台登录
```mermaid
sequenceDiagram
participant U as 用户浏览器
participant G as Gin Router
participant H as HandlerAdmin
participant AM as AdminModel
participant SS as SessionStore
U->>G: GET /admin/login
G->>H: LoginGet(c)
H-->>U: 登录页 HTML
U->>G: POST /admin/login {username, password}
G->>H: LoginPost(c)
H->>AM: VerifyPassword(username, password)
AM->>AM: bcrypt.CompareHashAndPassword()
AM-->>H: true/false
alt 验证成功
H->>SS: Create(adminID, username)
SS-->>H: sessionID
H->>H: c.SetCookie("session_id", sessionID, 86400, "/", "", false, true)
H-->>U: 302 Redirect → /admin
else 验证失败
H-->>U: 200 + 登录页(错误提示)
end
```
#### 4.4 卡片增删改查
```mermaid
sequenceDiagram
participant U as 管理员浏览器
participant G as Gin Router
participant MW as AuthMiddleware
participant H as HandlerCards
participant CM as CardModel
Note over U,CM: 卡片列表
U->>G: GET /admin/cards
G->>MW: AuthRequired()
MW->>MW: 检查 Cookie session_id
alt 未登录
MW-->>U: 302 → /admin/login
else 已登录
MW->>H: CardsList(c)
H->>CM: GetAllCards()
CM-->>H: []Card
H-->>U: 卡片列表 HTML
end
Note over U,CM: 新增卡片
U->>G: GET /admin/cards/new
G->>H: CardCreateGet(c)
H-->>U: 空表单 HTML
U->>G: POST /admin/cards {icon, title, subtitle, url}
G->>H: CardCreatePost(c)
H->>CM: CreateCard(&Card{...})
CM->>CM: INSERT INTO cards(...)
CM-->>H: nil
H-->>U: 302 Redirect → /admin/cards
Note over U,CM: 编辑卡片
U->>G: GET /admin/cards/:id/edit
G->>H: CardEditGet(c)
H->>CM: GetCardByID(id)
CM-->>H: *Card
H-->>U: 预填表单 HTML
U->>G: POST /admin/cards/:id {icon, title, subtitle, url}
G->>H: CardEditPost(c)
H->>CM: UpdateCard(&Card{...})
CM->>CM: UPDATE cards SET ... WHERE id=?
CM-->>H: nil
H-->>U: 302 Redirect → /admin/cards
Note over U,CM: 删除卡片
U->>G: POST /admin/cards/:id/delete
G->>H: CardDelete(c)
H->>CM: DeleteCard(id)
CM->>CM: DELETE FROM cards WHERE id=?
CM-->>H: nil
H-->>U: 302 Redirect → /admin/cards
```
#### 4.5 卡片排序(上移/下移)
```mermaid
sequenceDiagram
participant U as 管理员浏览器
participant G as Gin Router
participant H as HandlerCards
participant CM as CardModel
U->>G: POST /admin/cards/:id/move-up
G->>H: CardMoveUp(c)
H->>CM: MoveCardUp(id)
CM->>CM: 获取当前卡片 sort 值
CM->>CM: 获取 sort 值仅次于当前卡片的上一张卡片
CM->>CM: 交换两者 sort 值 (UPDATE)
CM-->>H: nil
H-->>U: 302 Redirect → /admin/cards
Note over U,CM: 下移同理,方向相反
```
#### 4.6 卡片启用/禁用
```mermaid
sequenceDiagram
participant U as 管理员浏览器
participant G as Gin Router
participant H as HandlerCards
participant CM as CardModel
U->>G: POST /admin/cards/:id/toggle
G->>H: CardToggle(c)
H->>CM: ToggleCard(id)
CM->>CM: UPDATE cards SET enabled = CASE WHEN enabled=1 THEN 0 ELSE 1 END WHERE id=?
CM-->>H: nil
H-->>U: 302 Redirect → /admin/cards
```
#### 4.7 搜索引擎配置
```mermaid
sequenceDiagram
participant U as 管理员浏览器
participant G as Gin Router
participant H as HandlerSettings
participant SM as SettingModel
U->>G: GET /admin/settings
G->>H: SettingsGet(c)
H->>SM: GetSetting("search_engine")
SM-->>H: 当前搜索引擎 URL 模板
H-->>U: 设置页 HTML(预设选项 + 自定义输入框)
U->>G: POST /admin/settings {engine: "google" | "bing" | "baidu" | "custom", custom_url: "..."}
G->>H: SettingsPost(c)
H->>H: 根据选项确定 URL 模板
alt 预设引擎
H->>H: 映射到固定 URL 模板
else 自定义
H->>H: 使用 custom_url,验证含 %s
end
H->>SM: SetSetting("search_engine", urlTemplate)
SM->>SM: INSERT OR REPLACE INTO settings(key, value) VALUES(...)
SM-->>H: nil
H-->>U: 302 Redirect → /admin/settings
```
---
### 5. 不确定事项与假设
| 编号 | 问题 | 假设/默认决策 |
|------|------|--------------|
| UNC-1 | 管理员是否支持多账号? | PRD 已定:初始 admin/admin123 硬编码,暂不考虑多账号管理 UI,但数据模型支持 |
| UNC-2 | 搜索引擎配置是否需要多个? | 当前仅支持配置一个默认搜索引擎,settings 表 key=`search_engine` |
| UNC-3 | 卡片图标是否需要文件上传? | PRD 已定:仅支持 emoji 文字和 favicon URL,通过文本输入框填写 |
| UNC-4 | CSRF 防护 | 简易项目暂不实现 CSRF Token(所有写操作需登录 + Cookie HttpOnly |
| UNC-5 | HTTPS | 不在应用层处理,由部署环境(反向代理)负责 |
| UNC-6 | 端口配置 | 默认监听 `:8080`,可通过环境变量 `PORT` 覆盖 |
---
## Part B: 任务分解
---
### 6. 依赖包列表
```
github.com/gin-gonic/gin@v1.10.0 # HTTP 框架
modernc.org/sqlite@latest # 纯 Go SQLite 驱动
golang.org/x/crypto@latest # bcrypt 密码哈希
```
> 仅 3 个直接依赖,保持极简。
---
### 7. 任务列表
#### T01: 项目基础设施
**说明**:创建项目骨架,包括模块初始化、数据库初始化、会话管理、全局布局模板、静态文件服务和程序入口。
**源文件**
- `go.mod`, `go.sum`
- `main.go`
- `database/db.go`
- `session/session.go`
- `middleware/auth.go`
- `templates/layout.html`
- `static/style.css`(基础框架样式:布局、导航、表单通用样式)
**具体内容**
1. `go mod init` + 添加依赖
2. `database/db.go``InitDB()` — 打开 SQLite 文件、建表(cards/settings/admins)、插入默认管理员(admin/admin123 bcrypt hash)、插入默认搜索引擎(Google)
3. `session/session.go``SessionStore` 结构体 + `Create/Get/Delete` 方法
4. `middleware/auth.go``AuthRequired()` 中间件,从 Cookie 读取 session_id,校验失败 302 到 `/admin/login`
5. `templates/layout.html`HTML 骨架(`{{define "layout"}}` 含 head/nav/footer`{{template "content" .}}` 插槽)
6. `static/style.css`:CSS 变量定义、reset、布局容器、导航栏、表单基础样式
7. `main.go`:初始化 DB、创建 SessionStore、注册所有路由(此阶段 handler 只返回空字符串占位)、启动服务
**依赖**:无
**优先级**P0
---
#### T02: 数据模型层
**说明**:实现所有数据模型的 CRUD 操作,与 SQLite 交互。
**源文件**
- `models/card.go`
- `models/setting.go`
- `models/admin.go`
**具体内容**
1. `models/card.go`
- `Card` struct(映射 cards 表)
- `GetAllCards()` — 查询全部卡片(后台用),按 sort ASC
- `GetEnabledCards()` — 仅查询启用的卡片(主页用),按 sort ASC
- `GetCardByID(id int)` — 单卡片查询
- `CreateCard(card *Card)` — INSERTsort 自动取 MAX(sort)+1
- `UpdateCard(card *Card)` — UPDATE by ID
- `DeleteCard(id int)` — DELETE by ID
- `ToggleCard(id int)` — 切换 enabled 字段
- `MoveCardUp(id int)` — 交换 sort 值上移
- `MoveCardDown(id int)` — 交换 sort 值下移
2. `models/setting.go`
- `Setting` struct
- 预定义搜索引擎常量:`SearchEngineGoogle = "https://www.google.com/search?q=%s"`
- `GetSetting(key string) (string, error)`
- `SetSetting(key, value string) error`
3. `models/admin.go`
- `Admin` struct
- `GetAdminByUsername(username string) (*Admin, error)`
- `CreateAdmin(username, hashedPassword string) error`
- `VerifyPassword(username, password string) (bool, *Admin, error)` — bcrypt 校验
- `ChangePassword(adminID int, newHashedPassword string) error`
**依赖**T01(需要 `database/db.go` 中初始化的 `*sql.DB`
**优先级**P0
---
#### T03: 后台管理 — 登录 + 卡片管理
**说明**:实现后台核心功能:管理员登录/登出、卡片 CRUD、启用/禁用、排序。
**源文件**
- `handlers/admin.go`
- `handlers/cards.go`
- `templates/admin/login.html`
- `templates/admin/index.html`
- `templates/admin/cards.html`
- `templates/admin/card_form.html`
**具体内容**
1. `handlers/admin.go`
- `LoginGet` — 渲染登录页
- `LoginPost` — 表单验证 → `AdminModel.VerifyPassword()` → 创建 Session → 写 Cookie → 302 到 `/admin`
- `Logout` — 删除 Session → 清 Cookie → 302 到 `/admin/login`
- `AdminIndex` — 渲染后台首页
2. `handlers/cards.go`
- `CardsList` — 调用 `GetAllCards()`,渲染列表页
- `CardCreateGet` / `CardCreatePost` — 新增卡片
- `CardEditGet` / `CardEditPost` — 编辑卡片
- `CardDelete` — 删除卡片
- `CardToggle` — 切换启用/禁用
- `CardMoveUp` / `CardMoveDown` — 排序
3. 模板文件:登录页、后台首页、卡片列表(含操作按钮)、卡片表单(新增/编辑共用)
**依赖**:T01(路由和中间件)、T02(数据模型)
**优先级**P0
---
#### T04: 主页 + 搜索引擎配置
**说明**:实现面向用户的主页(搜索框 + 导航卡片)和后台搜索引擎配置。
**源文件**
- `handlers/home.go`
- `handlers/settings.go`
- `templates/home.html`
- `templates/admin/settings.html`
**具体内容**
1. `handlers/home.go`
- `HomeHandler` — 获取启用卡片 + 当前搜索引擎 URL 模板,渲染主页
- 搜索框前端 JS:读取隐藏域中的 URL 模板,拼接 `%s` 占位符,`window.location.href` 跳转
- 搜索框自动聚焦(`autofocus` 属性)
2. `handlers/settings.go`
- `SettingsGet` — 读取当前配置,渲染设置页
- `SettingsPost` — 接收引擎选择(预设/自定义),自定义 URL 需包含 `%s`,保存到 settings 表
3. `templates/home.html`:搜索框(大尺寸居中)+ 卡片网格(图标/标题/副标题,可点击跳转)
4. `templates/admin/settings.html`:预设引擎单选 + 自定义 URL 输入
**依赖**T01、T02
**优先级**P0
---
#### T05: 样式完善 + 集成调试
**说明**:完善所有页面 CSS 样式,端到端集成测试,修复问题。
**源文件**
- `static/style.css`(补充完善所有页面样式)
- `templates/layout.html`(可能微调)
- `main.go`(最终路由确认)
**具体内容**
1. 完善主页样式:卡片网格(CSS Grid/Flexbox)、hover 效果、搜索框美观
2. 完善后台样式:表单对齐、按钮样式、卡片操作按钮布局
3. 响应式基础:卡片网格自适应列数
4. 端到端手动验证:
- 首次启动 → 默认管理员 → 登录成功
- 新增/编辑/删除/排序卡片 → 主页正确展示
- 切换搜索引擎 → 主页搜索跳转正确
- 未登录访问后台 → 重定向到登录页
5. 修复发现的问题
**依赖**T03、T04
**优先级**P1
---
### 8. 跨文件共享约定
```
- 全局 *sql.DB 实例:通过 database 包导出的 DB 变量访问,models 包直接引用 database.DB
- Session Cookie 名称:"session_id"HttpOnly=trueSameSite=LaxMaxAge=8640024小时)
- Session Store 实例:main.go 中创建,通过闭包注入到 middleware 和 handlers
- 模板数据传递:所有 handler 通过 gin.H 传递,模板中统一使用 .Cards / .Card / .SearchEngine / .Error / .Message
- 模板继承:所有页面通过 {{template "layout" .}} 包裹,子页面 {{define "content"}}...{{end}}
- 错误处理:handler 层 c.HTML(500, ...) 或 302 重定向,不向用户暴露内部错误
- 表单提交:所有写操作使用 POST + 302 重定向(PRG 模式),防止重复提交
- URL 模板占位符:%sGo fmt.Sprintf 兼容)
- 静态文件路径:/static/ → ./static/gin.Static
- SQLite 文件路径:./data/portal.db(自动创建 data 目录)
- 卡片 sort 值:数字越小越靠前,新建卡片 sort=MAX(sort)+1
- 搜索引擎配置 key"search_engine"value 为完整 URL 模板
- 管理员默认账号:admin / admin123bcrypt hash 存储于 DB
- 所有日期字段使用 SQLite CURRENT_TIMESTAMPRFC 3339 格式)
- 后台路由统一前缀:/admin/
- 模板文件路径:相对于项目根目录的 templates/ 目录
- CSS 类命名:BEM 风格简化版,如 .card-grid / .card-item / .card-item__icon / .search-box
```
---
### 9. 任务依赖图
```mermaid
graph TD
T01[T01: 项目基础设施<br/>go.mod, main.go, db, session,<br/>middleware, layout, style.css]
T02[T02: 数据模型层<br/>models/card.go, setting.go, admin.go]
T03[T03: 后台管理<br/>handlers/admin.go, cards.go,<br/>templates/admin/*]
T04[T04: 主页 + 搜索引擎配置<br/>handlers/home.go, settings.go,<br/>templates/home.html, admin/settings.html]
T05[T05: 样式完善 + 集成调试<br/>style.css 补充, 全流程验证]
T01 --> T02
T01 --> T03
T01 --> T04
T02 --> T03
T02 --> T04
T03 --> T05
T04 --> T05
```
> T03 和 T04 可并行开发(均依赖 T01 + T02),最终汇聚到 T05 集成。