Files
database_render/requirements.md
2025-08-05 17:26:59 +08:00

471 lines
13 KiB
Markdown
Raw Permalink 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.
# 数据库动态渲染系统 - 完整需求文档
## 项目概述
构建一个可配置的数据渲染应用,通过读取 YAML 配置文件连接到指定数据库,根据配置查询不同数据表的内容,并按照预设的格式和模板进行处理,最终通过通用的列表页面向前端用户分页展示数据。
## 技术栈与框架选型
### 后端技术栈
- **HTTP 框架**: `github.com/gofiber/fiber/v3` - 高性能、零内存分配的 Go Web 框架
- **数据库 ORM**: `gorm.io/gorm` - 强大的数据库 ORM支持多种数据库
- **日志系统**: `log/slog` - Go 官方结构化日志库
- **配置管理**: `github.com/spf13/viper` - 支持动态监听配置文件修改
- **依赖注入**: `github.com/google/wire` - Google 的依赖注入工具
### 数据库支持
- **SQLite**: `github.com/mattn/go-sqlite3` - 轻量级嵌入式数据库
- **MySQL**: `github.com/go-sql-driver/mysql` - MySQL 驱动
- **PostgreSQL**: `github.com/lib/pq` - PostgreSQL 驱动
### 前端技术栈
- **HTML 模板**: Go 标准库 `html/template` - 服务端模板渲染
- **CSS 框架**: TailwindCSS - 实用优先的 CSS 框架
- **JavaScript**: 原生 JavaScript (ES6+) - 轻量级交互
- **Markdown 渲染**: `marked.js` - Markdown 转 HTML
## 核心功能需求
### 1. 配置系统 (YAML)
#### 1.1 配置文件结构
```yaml
database:
type: sqlite
path: ./data/content.db
# MySQL配置示例
# type: mysql
# host: localhost
# port: 3306
# user: root
# password: password
# dbname: testdb
tables:
- name: "articles"
alias: "技术文章"
page_size: 15
columns:
- name: "id"
alias: "ID"
render_type: "raw"
sortable: true
width: "80px"
- name: "title"
alias: "标题"
render_type: "raw"
searchable: true
max_length: 50
- name: "content"
alias: "内容"
render_type: "markdown"
is_primary_content: true # 弹窗展示全文
show_in_list: false
- name: "category"
alias: "分类"
render_type: "category"
- name: "tags"
alias: "标签"
render_type: "tag"
values:
1: { label: "Go", color: "#00ADD8" }
2: { label: "JavaScript", color: "#f7df1e" }
- name: "created_at"
alias: "发布时间"
render_type: "time"
format: "2006-01-02 15:04:05"
sortable: true
filters:
- name: "category"
type: "select"
options: ["全部", "技术", "生活"]
- name: "logs"
alias: "系统日志"
page_size: 50
columns:
- name: "id"
alias: "ID"
render_type: "raw"
- name: "level"
alias: "级别"
render_type: "tag"
values:
1: { label: "INFO", color: "#52c41a" }
2: { label: "WARN", color: "#faad14" }
3: { label: "ERROR", color: "#f5222d" }
- name: "message"
alias: "日志信息"
render_type: "raw"
is_primary_content: true
- name: "timestamp"
alias: "时间"
render_type: "time"
format: "2006-01-02 15:04:05"
```
### 2. 字段渲染类型规范
| 类型 | 描述 | 配置参数 | 示例 |
| ---------- | ------------- | ------------------ | ------------------- |
| `raw` | 原始文本 | 无 | 普通文本显示 |
| `markdown` | Markdown 渲染 | `max_length` | 文章内容 |
| `time` | 时间格式化 | `format` | 2024-01-01 12:00:00 |
| `tag` | 标签样式 | `values`, `colors` | 状态标签 |
| `category` | 分类显示 | 无 | 分类名称 |
| `html` | HTML 内容 | `sanitize` | 富文本内容 |
### 3. 后端 API 接口
#### 3.1 获取表列表
```http
GET /api/tables
```
响应:
```json
{
"tables": ["技术文章", "系统日志"]
}
```
#### 3.2 获取分页数据
```http
GET /api/data/{table_alias}?page=1&per_page=20&search=keyword&sort=created_at&order=desc
```
响应:
```json
{
"data": [
{
"id": 1,
"title": "Go语言入门",
"category": "技术",
"tags": "Go",
"created_at": "2024-01-01 12:00:00"
}
],
"total": 100,
"page": 1,
"per_page": 20,
"pages": 5
}
```
### 4. Go 模板引擎规范
#### 4.1 通用列表模板
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{.TableAlias}} - 数据管理</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
.tag {
@apply inline-block px-2 py-1 text-xs font-medium rounded-full;
}
.modal {
@apply fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden;
}
.modal-content {
@apply bg-white rounded-lg max-w-4xl w-full mx-4 max-h-[80vh] overflow-y-auto;
}
</style>
</head>
<body class="bg-gray-50">
<div class="container mx-auto px-4 py-8">
<!-- 表选择器 -->
<div class="mb-6">
<select
id="tableSelector"
class="border rounded px-3 py-2"
onchange="changeTable(this.value)"
>
{{range .Tables}}
<option value="{{.}}" {{if eq . $.CurrentTable}}selected{{end}}>
{{.}}
</option>
{{end}}
</select>
</div>
<!-- 数据表格 -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
{{range .Columns}} {{if .ShowInList}}
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{{.Alias}}
</th>
{{end}} {{end}}
<th
class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
操作
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{{range .Data}}
<tr>
{{range $.Columns}} {{if .ShowInList}}
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{{template "render_field" dict "Value" (index $.Data (printf
"%s" .Name)) "Type" .RenderType "Column" .}}
</td>
{{end}} {{end}}
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button
onclick="showDetail('{{.ID}}')"
class="text-blue-600 hover:text-blue-900"
>
查看详情
</button>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
<!-- 分页 -->
<div class="mt-4 flex justify-between items-center">
<div class="text-sm text-gray-700">
共 {{.Total}} 条记录,第 {{.Page}} / {{.Pages}} 页
</div>
<div class="flex space-x-2">
{{if gt .Page 1}}
<a
href="?page={{sub .Page 1}}&per_page={{.PerPage}}"
class="px-3 py-1 border rounded text-sm"
>上一页</a
>
{{end}} {{if lt .Page .Pages}}
<a
href="?page={{add .Page 1}}&per_page={{.PerPage}}"
class="px-3 py-1 border rounded text-sm"
>下一页</a
>
{{end}}
</div>
</div>
</div>
<!-- 详情弹窗 -->
<div id="detailModal" class="modal">
<div class="modal-content">
<div class="p-6">
<h3 class="text-lg font-medium mb-4">详情信息</h3>
<div id="detailContent"></div>
<div class="mt-4 flex justify-end">
<button
onclick="closeModal()"
class="px-4 py-2 bg-gray-300 rounded"
>
关闭
</button>
</div>
</div>
</div>
</div>
<script>
// 渲染字段模板函数
function renderField(value, type, column) {
switch(type) {
case 'time':
return new Date(value).toLocaleString('zh-CN');
case 'tag':
const tagValue = column.values[value];
return `<span class="tag" style="background-color: ${tagValue.color}; color: white;">${tagValue.label}</span>`;
case 'markdown':
return marked.parse(value || '');
default:
return value || '';
}
}
// 显示详情
function showDetail(id) {
fetch(`/api/data/{{.CurrentTable}}/detail/${id}`)
.then(response => response.json())
.then(data => {
let content = '';
{{range .Columns}}
{{if .IsPrimaryContent}}
content += `<div class="mb-4">
<label class="block text-sm font-medium text-gray-700">{{.Alias}}</label>
<div class="mt-1 text-sm text-gray-900">${renderField(data.{{.Name}}, '{{.RenderType}}', {{. | json}})}</div>
</div>`;
{{end}}
{{end}}
document.getElementById('detailContent').innerHTML = content;
document.getElementById('detailModal').classList.remove('hidden');
});
}
// 关闭弹窗
function closeModal() {
document.getElementById('detailModal').classList.add('hidden');
}
// 切换表格
function changeTable(tableAlias) {
window.location.href = `/?table=${tableAlias}&page=1`;
}
</script>
</body>
</html>
```
#### 4.2 模板辅助函数
```go
// 模板函数注册
func templateFuncs() template.FuncMap {
return template.FuncMap{
"dict": func(values ...interface{}) map[string]interface{} {
dict := make(map[string]interface{})
for i := 0; i < len(values); i += 2 {
key := values[i].(string)
dict[key] = values[i+1]
}
return dict
},
"add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"json": func(v interface{}) string {
b, _ := json.Marshal(v)
return string(b)
},
"renderField": func(value interface{}, renderType string, column interface{}) template.HTML {
switch renderType {
case "time":
if t, ok := value.(time.Time); ok {
return template.HTML(t.Format("2006-01-02 15:04:05"))
}
return template.HTML(fmt.Sprintf("%v", value))
case "tag":
if columnMap, ok := column.(map[string]interface{}); ok {
if values, ok := columnMap["values"].(map[string]interface{}); ok {
if tag, ok := values[fmt.Sprintf("%v", value)].(map[string]interface{}); ok {
color := tag["color"].(string)
label := tag["label"].(string)
return template.HTML(fmt.Sprintf(
`<span class="tag" style="background-color: %s; color: white;">%s</span>`,
color, label,
))
}
}
}
return template.HTML(fmt.Sprintf("%v", value))
case "markdown":
// 服务端渲染Markdown
return template.HTML(fmt.Sprintf("%v", value))
default:
return template.HTML(fmt.Sprintf("%v", value))
}
},
}
}
```
## 项目结构
```
database-render/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── config/
│ │ └── config.go
│ ├── database/
│ │ └── connection.go
│ ├── handler/
│ │ └── data_handler.go
│ ├── model/
│ │ └── table_config.go
│ ├── repository/
│ │ └── data_repository.go
│ ├── service/
│ │ └── data_service.go
│ └── template/
│ └── renderer.go
├── web/
│ ├── static/
│ │ ├── css/
│ │ ├── js/
│ │ └── images/
│ └── templates/
│ └── list.html
├── config/
│ └── config.yaml
├── migrations/
├── scripts/
├── tests/
├── Makefile
├── Dockerfile
├── go.mod
└── README.md
```
## 构建与部署
### 开发环境
```bash
# 安装依赖
go mod tidy
# 运行开发服务器
make dev
# 运行测试
make test
```
### 生产部署
```bash
# 构建二进制
make build
# 构建Docker镜像
docker build -t database-render .
# 运行容器
docker run -p 8080:8080 -v ./config:/app/config database-render
```
## 环境要求
- **开发环境**: Go 1.21+, Node.js 18+, SQLite3
- **生产环境**: Linux/Windows/macOS, Docker, SQLite/MySQL 5.7+/PostgreSQL 12+