diff --git a/.trae/rules/project_rules.md b/.trae/rules/project_rules.md index e75d9e3..858fe7e 100644 --- a/.trae/rules/project_rules.md +++ b/.trae/rules/project_rules.md @@ -2,14 +2,14 @@ ## 1. 项目概述 -- 技术栈:后端使用 `Go`,前端使用 `HTML + CSS + JS`,前后端代码同仓管理。 +- 技术栈:后端使用 `Go`,前端使用 `Vue 3 + Element Plus`(通过 CDN 引入,无打包);前后端代码同仓管理。 - 目标:为“营销系统”和“易码通系统”提供统一的高性能数据导出能力,支持模板化 SQL 构建与权限控制,生成 `CSV` 与 `Excel` 文件。 - 数据源:至少包含两个独立库(`marketing_db`、`ymt_db`),通过环境变量配置连接与凭据。 ## 2. 目录结构与命名约定 - `server/`:Go 服务端代码(API、模板校验、导出执行、权限校验、日志与监控)。 -- `web/`:前端页面与静态资源(模板管理、导出发起、进度查看、历史下载)。 +- `web/`:前端页面与静态资源(模板管理、导出发起、进度查看、历史下载),采用 `Vue 3 + Element Plus` 组件搭建。 - `config/`:非敏感配置(字段白名单、场景定义、关联关系元数据)。敏感信息使用环境变量注入。 - `scripts/`:开发与运维脚本(如生成索引建议、批量校验 EXPLAIN)。 - 命名规范:统一使用下划线或小驼峰,文件名见名知意;避免缩写导致歧义。 @@ -93,8 +93,9 @@ - `GET /api/exports/{id}` 进度与指标;`GET /api/exports/{id}/download` 下载文件。 - `POST /api/exports/{id}/cancel` 取消任务。 - 前端约定: + - 使用 `Vue 3 + Element Plus`,通过 CDN 加载:`vue@3` 与 `element-plus`;页面在 `web/index.html` 中挂载。 - 统一通过权限范围参数(如 `user_id IN (...)`)传递查询边界;由后端注入到 SQL。 - - 所有下拉/选择项来自白名单与元数据;禁止自由输入列名与表名。 + - 所有下拉/选择项来自白名单与元数据;禁止自由输入列名与表名;表单与日期选择使用 Element Plus 组件。 ## 10. 性能与稳定性 diff --git a/server/internal/api/exports.go b/server/internal/api/exports.go index ec06914..ddd7a97 100644 --- a/server/internal/api/exports.go +++ b/server/internal/api/exports.go @@ -5,8 +5,6 @@ import ( "encoding/json" "io" "net/http" - "os" - "path/filepath" "strconv" "strings" "time" @@ -92,7 +90,7 @@ func (a *ExportsAPI) create(w http.ResponseWriter, r *http.Request) { w.Write([]byte(err.Error())) return } - _, _ = exporter.RunExplain(a.marketing, q, args) + _, _, _ = exporter.RunExplain(a.marketing, q, args) res, err := a.meta.Exec("INSERT INTO export_jobs (template_id, status, requested_by, permission_scope_json, options_json, file_format, created_at) VALUES (?,?,?,?,?,?,?)", p.TemplateID, "queued", p.RequestedBy, toJSON(p.Permission), toJSON(p.Options), p.FileFormat, time.Now()) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -197,15 +195,25 @@ func (a *ExportsAPI) runJob(id uint64, q string, args []interface{}, cols []stri func (a *ExportsAPI) get(w http.ResponseWriter, r *http.Request, id string) { row := a.meta.QueryRow("SELECT id, template_id, status, requested_by, total_rows, file_format, started_at, finished_at, created_at, updated_at FROM export_jobs WHERE id=?", id) var m = map[string]interface{}{} + var jid uint64 + var templateID uint64 + var status string + var requestedBy uint64 var totalRows sql.NullInt64 + var fileFormat string var startedAt, finishedAt sql.NullTime var createdAt, updatedAt time.Time - err := row.Scan(&m["id"], &m["template_id"], &m["status"], &m["requested_by"], &totalRows, &m["file_format"], &startedAt, &finishedAt, &createdAt, &updatedAt) + err := row.Scan(&jid, &templateID, &status, &requestedBy, &totalRows, &fileFormat, &startedAt, &finishedAt, &createdAt, &updatedAt) if err != nil { w.WriteHeader(http.StatusNotFound) w.Write([]byte("not found")) return } + m["id"] = jid + m["template_id"] = templateID + m["status"] = status + m["requested_by"] = requestedBy + m["file_format"] = fileFormat m["total_rows"] = totalRows.Int64 m["started_at"] = startedAt.Time m["finished_at"] = finishedAt.Time @@ -259,4 +267,3 @@ func toString(v interface{}) string { return "" } } - diff --git a/server/internal/api/templates.go b/server/internal/api/templates.go index 608bbbf..1e5201c 100644 --- a/server/internal/api/templates.go +++ b/server/internal/api/templates.go @@ -136,17 +136,27 @@ func (a *TemplatesAPI) listTemplates(w http.ResponseWriter, r *http.Request) { func (a *TemplatesAPI) getTemplate(w http.ResponseWriter, r *http.Request, id string) { row := a.meta.QueryRow("SELECT id,name,datasource,main_table,fields_json,filters_json,file_format,visibility,owner_id,enabled,explain_score,last_validated_at,created_at,updated_at FROM export_templates WHERE id=?", id) var m = map[string]interface{}{} + var tid uint64 + var name, datasource, mainTable, fileFormat, visibility string + var ownerID uint64 var enabled int var explainScore sql.NullInt64 var lastValidatedAt sql.NullTime var createdAt, updatedAt time.Time var fields, filters []byte - err := row.Scan(&m["id"], &m["name"], &m["datasource"], &m["main_table"], &fields, &filters, &m["file_format"], &m["visibility"], &m["owner_id"], &enabled, &explainScore, &lastValidatedAt, &createdAt, &updatedAt) + err := row.Scan(&tid, &name, &datasource, &mainTable, &fields, &filters, &fileFormat, &visibility, &ownerID, &enabled, &explainScore, &lastValidatedAt, &createdAt, &updatedAt) if err != nil { w.WriteHeader(http.StatusNotFound) w.Write([]byte("not found")) return } + m["id"] = tid + m["name"] = name + m["datasource"] = datasource + m["main_table"] = mainTable + m["file_format"] = fileFormat + m["visibility"] = visibility + m["owner_id"] = ownerID m["enabled"] = enabled == 1 m["explain_score"] = explainScore.Int64 m["last_validated_at"] = lastValidatedAt.Time diff --git a/server/server b/server/server new file mode 100755 index 0000000..e1c1391 Binary files /dev/null and b/server/server differ diff --git a/web/index.html b/web/index.html index ea2abdc..b46a2d6 100644 --- a/web/index.html +++ b/web/index.html @@ -4,87 +4,93 @@