feat: init project
This commit is contained in:
470
requirements.md
Normal file
470
requirements.md
Normal file
@@ -0,0 +1,470 @@
|
||||
# 数据库动态渲染系统 - 完整需求文档
|
||||
|
||||
## 项目概述
|
||||
|
||||
构建一个可配置的数据渲染应用,通过读取 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+
|
||||
Reference in New Issue
Block a user