7.1 KiB
name, description
| name | description |
|---|---|
| export-datasource | Project-specific guide for implementing, modifying, or reviewing export data sources in junhong_cmp_fiber. Use when Codex needs to add a new export scene, extend filters or dynamic columns, register an export scene, change export task query behavior, or explain/debug the DataSource-based export system. |
Export Datasource
Use this skill to work on this project's DataSource-based export system. It covers developer-facing export scene implementation, not one-off manual export operations.
First Reads
Before editing export code, read the relevant current files:
internal/exporter/datasource.gointernal/exporter/query_params.gointernal/exporter/filter_helpers.gointernal/exporter/registry.go- Existing scene closest to the new scene:
internal/exporter/device_scene.gointernal/exporter/iot_card_scene.go
- For request/scene validation:
internal/model/dto/export_task_dto.gopkg/constants/constants.go
- For behavior across worker stages:
internal/task/export_dispatch.gointernal/task/export_shard.gointernal/task/export_finalize.go
Read references/export-scene-template.md when adding a new scene or when a concrete code skeleton is useful.
Mental Model
The framework owns async execution, sharding, file generation, OSS upload, and download URLs. A scene implementation owns only data semantics:
type DataSource interface {
Scene() string
Count(ctx context.Context, params ExportParams) (int, error)
Headers(ctx context.Context, params ExportParams) ([]string, error)
Fetch(ctx context.Context, params ExportParams, offset, limit int) ([][]string, error)
}
Execution flow:
- Admin API creates
tb_export_taskand enqueuesexport:dispatch. - Dispatch parses
query_json.filtersplus permission snapshot intoExportParams. - Dispatch calls
Headersonce and storesquery_json.resolved_headers. - Dispatch calls
Countand createstb_export_shard_taskrows withshard_offsetandshard_limit. - Shard calls
Fetch, writes headerless CSV shard files, and uploads them. - Finalize downloads shard CSV files in shard order, writes one header row, uploads final CSV or converts CSV to XLSX.
Do not reintroduce keyset cursor logic. New scenes must use offset/limit through Fetch.
Implementation Workflow
- Add a scene constant in
pkg/constants/constants.go. - Update
internal/model/dto/export_task_dto.govalidation and descriptions forscene. - Implement
internal/exporter/<scene>_scene.gowithDataSource. - Register the source in
NewDefaultRegistry. - Update
IsSupportedSceneif it is used by the current code path. - Add or update migrations only if the exported domain needs schema/index changes. Do not change export task tables unless the framework contract changes.
- Build and manually verify. This repository forbids automated tests unless the user explicitly requests them.
DataSource Rules
Scenemust return the constant, not a string literal.CountandFetchmust apply the same filters and permission scope.Headersdefines the exact column contract for the whole task. Dispatch stores it once; shards and finalize reuse it.Fetchmust return rows aligned toHeaders; the framework pads/truncates as a fallback, but the source should be correct.Fetchmust use stable ordering, normallyORDER BY id ASC.- Return string values only. Format time as
2006-01-02 15:04:05unless the surrounding scene establishes another convention. - Use GORM only. Do not use
database/sql. - Keep SQL parameters bound through GORM placeholders. Do not concatenate user-controlled values into SQL.
- Keep comments, logs, errors, and documentation in Chinese per project rules.
Filters And Permissions
Incoming task query shape:
{
"filters": {
"status": 1,
"shop_id": 1
}
}
ParseExportParams exposes:
Filters:query_json.filtersScopeShopIDs: shop permission snapshot captured at task creationUserType: creator user type snapshot
Apply permission scope inside each DataSource:
query = applyExportShopScope(query, params, "shop_id")
For aliased queries:
query = applyExportShopScope(query, params, "o.shop_id")
Use helpers from filter_helpers.go:
filterIntfilterUintfilterStringfilterBoolfilterTimeformatOptionalUintformatOptionalTime
Do not read live permissions from context inside a DataSource. Export tasks must use the permission snapshot stored at creation time.
Dynamic Columns
Use dynamic headers only when the task parameters or data require it. If Headers changes based on filters, Fetch must produce the same shape for every shard under the same ExportParams.
Example pattern:
headers := []string{"ID", "ICCID", "状态"}
if filterBool(params.Filters, "with_package") {
headers = append(headers, "套餐名称", "套餐状态")
}
return headers, nil
Join Queries
JOIN-based exports are allowed. Keep these constraints:
- Preserve one output row per intended exported entity unless the scene explicitly exports detail rows.
- If a JOIN can multiply rows, make
Countmatch the exported row semantics exactly. - Use table aliases consistently in filters and scope columns.
- Prefer explicit
Selectinto a local row struct for multi-table exports. - Keep
Order,Limit, andOffseton the final query used byFetch.
User-Facing API Notes
Current API group:
POST /api/admin/export-tasksGET /api/admin/export-tasksGET /api/admin/export-tasks/:idPOST /api/admin/export-tasks/:id/cancel
Creation request:
{
"scene": "iot_card",
"format": "csv",
"query": {
"filters": {
"shop_id": 1,
"with_package": true
}
}
}
Formats are csv and xlsx. Final download URLs are returned from task detail after completion.
Verification
This project forbids automated tests and _test.go files unless the user explicitly asks for tests. Use manual verification:
go build ./internal/exporter/...
go build ./internal/task/...
go build ./...
Then create an export task through the API and inspect:
tb_export_task.query_jsoncontains originalfiltersand generatedresolved_headers.tb_export_task.total_rowsmatches the filtered query.tb_export_shard_task.shard_offsetandshard_limitare filled.- Shards reach success and final task reaches completed status.
- Downloaded file has one header row, expected row count, correct filtering, and valid CSV/XLSX format.
Use PostgreSQL MCP/manual SQL for data validation when needed.
Common Mistakes
- Updating only
Fetchand forgettingCount, causing wrong shard planning. - Returning dynamic rows whose column count does not match
Headers. - Filtering by requested
shop_idwithout also applyingScopeShopIDs. - Using context/user middleware inside worker DataSource logic.
- Registering the source but forgetting DTO
oneof, so API rejects the new scene. - Writing XLSX shard files. Shards should be CSV; finalize handles final format.
- Adding automated tests despite the repository ban.