diff --git a/.cursor/worktrees.json b/.cursor/worktrees.json new file mode 100644 index 0000000..77e9744 --- /dev/null +++ b/.cursor/worktrees.json @@ -0,0 +1,5 @@ +{ + "setup-worktree": [ + "npm install" + ] +} diff --git a/server/internal/api/fields.go b/server/internal/api/fields.go new file mode 100644 index 0000000..d97fa9f --- /dev/null +++ b/server/internal/api/fields.go @@ -0,0 +1,62 @@ +package api + +import ( + "database/sql" + "net/http" + "sort" +) + +// FieldsHandler 返回指定数据源的所有表字段,包含 hidden 默认值(全部 true,可前端精确控制显示/隐藏) +func FieldsHandler(marketing, ymt *sql.DB) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ds := r.URL.Query().Get("datasource") + db := marketing + if ds == "ymt" { + db = ymt + } + + hiddenDefaults := getHiddenFieldsFromCode(ds) + + var tables []string + if ds == "ymt" { + tables = []string{"order_info", "order_cash", "order_voucher", "order_digit", "goods_voucher_batch", "goods_voucher_subject_config", "merchant", "activity"} + } else { + tables = []string{"order", "order_detail", "order_cash", "order_voucher", "plan", "key_batch", "code_batch", "voucher", "voucher_batch", "merchant_key_send"} + } + + out := []map[string]interface{}{} + for _, tbl := range tables { + cols := getColumns(db, tbl) + fields := []map[string]interface{}{} + for _, c := range cols { + tCanonical, fCanonical := canonicalField(ds, tbl, c.Name) + if tCanonical == "" || fCanonical == "" { + continue + } + label := c.Comment + if label == "" { + label = fCanonical + } + hidden := isFieldHidden(hiddenDefaults, tCanonical, fCanonical) + fields = append(fields, map[string]interface{}{ + "key": tCanonical + "." + fCanonical, + "field": fCanonical, + "label": label, + "hidden": hidden, + }) + } + tDisplay := displayTable(ds, tbl) + out = append(out, map[string]interface{}{ + "table": tDisplay, + "label": tableLabel(tDisplay), + "fields": fields, + }) + } + + sort.Slice(out, func(i, j int) bool { return out[i]["table"].(string) < out[j]["table"].(string) }) + ok(w, r, map[string]interface{}{ + "datasource": ds, + "tables": out, + }) + }) +} diff --git a/server/internal/api/metadata.go b/server/internal/api/metadata.go index e46c297..1e8e2ee 100644 --- a/server/internal/api/metadata.go +++ b/server/internal/api/metadata.go @@ -4,6 +4,9 @@ import ( "database/sql" "net/http" "sort" + "strings" + + "server/internal/schema" ) func MetadataHandler(meta, marketing, ymt *sql.DB) http.Handler { @@ -55,453 +58,26 @@ func MetadataHandler(meta, marketing, ymt *sql.DB) http.Handler { } // getHiddenFieldsFromCode 从代码中获取指定数据源的隐藏字段映射 -// 返回 map[table]map[field]bool,标记哪些字段需要隐藏 -// -// 所有表的字段列表(基于 schema/fields.go): -// -// Marketing 数据源表字段: -// - order: order_number, key, creator, out_trade_no, type, status, account, product_id, reseller_id, plan_id, -// key_batch_id, code_batch_id, pay_type, pay_status, use_coupon, deliver_status, expire_time, -// recharge_time, contract_price, num, total, pay_amount, create_time, update_time, card_code, -// official_price, merchant_name, activity_name, goods_name, pay_time, coupon_id, discount_amount, -// supplier_product_name, is_inner, icon, cost_price, success_num, is_reset, is_retry, channel, -// is_store, trace_id, out_order_no, next_retry_time, recharge_suc_time, supplier_id, -// supplier_product_id, merchant_id, goods_id, activity_id, key_batch_name -// 隐藏字段: id, order_id, key, creator_id, plan_id, product_id, reseller_id, supplier_id, -// supplier_product_id, key_batch_id, is_internal_supplier_order -// - order_detail: plan_title, order_number, reseller_name, product_name, show_url, official_price, -// cost_price, create_time, update_time -// - order_cash: order_no, trade_no, wechat_detail_id, channel, denomination, account, receive_name, -// app_id, cash_activity_id, receive_status, receive_time, success_time, cash_packet_id, -// channel_order_id, pay_fund_order_id, cash_id, amount, activity_id, goods_id, merchant_id, -// supplier_id, user_id, status, expire_time, create_time, update_time, version, is_confirm -// - order_voucher: channel, channel_activity_id, channel_voucher_id, status, receive_mode, grant_time, -// usage_time, refund_time, status_modify_time, overdue_time, refund_amount, official_price, -// out_biz_no, account_no -// - plan: id, title, status, begin_time, end_time -// - key_batch: id, batch_name, bind_object, quantity, stock, begin_time, end_time -// - code_batch: id, title, status, begin_time, end_time, quantity, usage, stock -// - voucher: channel, channel_activity_id, price, balance, used_amount, denomination -// - voucher_batch: channel_activity_id, temp_no, provider, weight -// - merchant_key_send: merchant_id, out_biz_no, key, status, usage_time, create_time -// -// YMT 数据源表字段: -// - order (order_info): order_number, key, creator, out_trade_no, type, status, account, product_id, -// reseller_id, plan_id, key_batch_id, code_batch_id, pay_type, pay_status, -// use_coupon, deliver_status, expire_time, recharge_time, contract_price, num, -// pay_amount, create_time, update_time, card_code, official_price, merchant_name, -// activity_name, goods_name, pay_time, coupon_id, discount_amount, -// supplier_product_name, is_inner, icon, cost_price, success_num, is_reset, -// is_retry, channel, is_store, trace_id, out_order_no, next_retry_time, -// recharge_suc_time, supplier_id, supplier_product_id, merchant_id, goods_id, -// activity_id, key_batch_name -// 隐藏字段: id, order_id, key, creator_id, plan_id, product_id, reseller_id, supplier_id, -// supplier_product_id, key_batch_id, is_internal_supplier_order -// - order_cash: order_no, trade_no, wechat_detail_id, channel, denomination, account, receive_name, -// app_id, cash_activity_id, receive_status, receive_time, success_time, cash_packet_id, -// channel_order_id, pay_fund_order_id, cash_id, amount, activity_id, goods_id, merchant_id, -// supplier_id, user_id, status, expire_time, create_time, update_time, version, is_confirm -// - order_voucher: channel, channel_activity_id, channel_voucher_id, status, receive_mode, grant_time, -// usage_time, refund_time, status_modify_time, overdue_time, refund_amount, official_price, -// out_biz_no, account_no -// - order_digit: order_no, card_no, account, goods_id, merchant_id, supplier_id, activity_id, user_id, -// success_time, supplier_product_no, order_type, end_time, create_time, update_time, code, -// sms_channel -// - goods_voucher_batch: channel_batch_no, voucher_subject_id, id, goods_voucher_id, supplier_id, -// temp_no, index, create_time, update_time -// - goods_voucher_subject_config: id, name, type, create_time -// - merchant: id, name, user_id, merchant_no, subject, third_party, status, balance, total_consumption, -// contact_name, contact_phone, contact_email, create_time, update_time -// - activity: id, name, user_id, merchant_id, user_name, activity_no, status, key_total_num, -// key_generate_num, key_usable_num, domain_url, theme_login_id, theme_list_id, -// theme_verify_id, settlement_type, key_expire_type, key_valid_day, key_begin_time, -// key_end_time, key_style, begin_time, end_time, is_retry, create_time, update_time, -// discard_time, delete_time, auto_charge, stock, approval_trade_no, amount, channels, -// key_begin, key_end, key_unit, key_pay_button_text, goods_pay_button_text, -// is_open_db_transaction func getHiddenFieldsFromCode(ds string) map[string]map[string]bool { - result := make(map[string]map[string]bool) + // 从字段白名单构建隐藏字段映射,所有字段默认 true(隐藏) + return buildHiddenFieldsFromWhitelist() +} - if ds == "ymt" { - // YMT 数据源的隐藏字段配置 - 所有字段默认隐藏(true) - // order 表所有字段 - result["order"] = map[string]bool{ - "order_number": true, - "key": true, - "creator": false, - "out_trade_no": true, - "type": true, - "status": true, - "account": true, - "product_id": false, - "reseller_id": false, - "plan_id": false, - "key_batch_id": false, - "code_batch_id": false, - "pay_type": true, - "pay_status": true, - "use_coupon": true, - "deliver_status": true, - "expire_time": true, - "recharge_time": true, - "contract_price": true, - "num": true, - "pay_amount": true, - "create_time": true, - "update_time": true, - "card_code": true, - "official_price": true, - "merchant_name": true, - "activity_name": true, - "goods_name": true, - "pay_time": true, - "coupon_id": true, - "discount_amount": true, - "supplier_product_name": true, - "is_inner": false, - "icon": true, - "cost_price": true, - "success_num": true, - "is_reset": false, - "is_retry": false, - "channel": true, - "is_store": false, - "trace_id": false, - "out_order_no": true, - "next_retry_time": true, - "recharge_suc_time": true, - "supplier_id": false, - "supplier_product_id": false, - "merchant_id": false, - "goods_id": false, - "activity_id": false, - "key_batch_name": true, - "id": false, - "order_id": false, - "creator_id": false, - "is_internal_supplier_order": false, +// buildHiddenFieldsFromWhitelist 读取 schema.AllWhitelist 中的所有字段,按表分组,全部标记为 true +func buildHiddenFieldsFromWhitelist() map[string]map[string]bool { + out := make(map[string]map[string]bool) + for k := range schema.AllWhitelist() { + parts := strings.SplitN(k, ".", 2) + if len(parts) != 2 { + continue } - // order_cash 表所有字段 - result["order_cash"] = map[string]bool{ - "order_no": true, - "trade_no": true, - "wechat_detail_id": false, - "channel": true, - "denomination": true, - "account": true, - "receive_name": true, - "app_id": true, - "cash_activity_id": true, - "receive_status": true, - "receive_time": true, - "success_time": true, - "cash_packet_id": false, - "channel_order_id": false, - "pay_fund_order_id": false, - "cash_id": true, - "amount": true, - "activity_id": false, - "goods_id": false, - "merchant_id": false, - "supplier_id": false, - "user_id": false, - "status": true, - "expire_time": true, - "create_time": true, - "update_time": false, - "version": false, - "is_confirm": true, - } - // order_voucher 表所有字段 - result["order_voucher"] = map[string]bool{ - "channel": true, - "channel_activity_id": true, - "channel_voucher_id": true, - "status": true, - "receive_mode": true, - "grant_time": true, - "usage_time": true, - "refund_time": true, - "status_modify_time": true, - "overdue_time": true, - "refund_amount": true, - "official_price": true, - "out_biz_no": true, - "account_no": true, - } - // order_digit 表所有字段 - result["order_digit"] = map[string]bool{ - "order_no": true, - "card_no": true, - "account": true, - "goods_id": false, - "merchant_id": false, - "supplier_id": false, - "activity_id": false, - "user_id": false, - "success_time": true, - "supplier_product_no": true, - "order_type": true, - "end_time": true, - "create_time": true, - "update_time": true, - "code": false, - "sms_channel": false, - } - // goods_voucher_batch 表所有字段 - result["goods_voucher_batch"] = map[string]bool{ - "channel_batch_no": true, - "voucher_subject_id": true, - "id": false, - "goods_voucher_id": false, - "supplier_id": false, - "temp_no": true, - "index": true, - "create_time": false, - "update_time": false, - } - // goods_voucher_subject_config 表所有字段 - result["goods_voucher_subject_config"] = map[string]bool{ - "id": true, - "name": true, - "type": true, - "create_time": true, - } - // merchant 表所有字段 - result["merchant"] = map[string]bool{ - "id": false, - "name": true, - "user_id": false, - "merchant_no": true, - "subject": true, - "third_party": false, - "status": false, - "balance": false, - "total_consumption": false, - "contact_name": true, - "contact_phone": false, - "contact_email": false, - "create_time": false, - "update_time": false, - } - // activity 表所有字段 - result["activity"] = map[string]bool{ - "id": false, - "name": true, - "user_id": false, - "merchant_id": false, - "user_name": true, - "activity_no": true, - "status": true, - "key_total_num": true, - "key_generate_num": true, - "key_usable_num": true, - "domain_url": false, - "theme_login_id": false, - "theme_list_id": false, - "theme_verify_id": false, - "settlement_type": true, - "key_expire_type": true, - "key_valid_day": true, - "key_begin_time": true, - "key_end_time": true, - "key_style": true, - "begin_time": true, - "end_time": true, - "is_retry": false, - "create_time": false, - "update_time": false, - "discard_time": false, - "delete_time": false, - "auto_charge": true, - "stock": true, - "approval_trade_no": false, - "amount": true, - "channels": true, - "key_begin": true, - "key_end": true, - "key_unit": false, - "key_pay_button_text": false, - "goods_pay_button_text": false, - "is_open_db_transaction": false, - } - } else { - // Marketing 数据源的隐藏字段配置 - 所有字段默认隐藏(true) - // order 表所有字段 - result["order"] = map[string]bool{ - "order_number": true, - "key": true, - "creator": true, - "out_trade_no": true, - "type": true, - "status": true, - "account": true, - "product_id": false, - "reseller_id": false, - "plan_id": false, - "key_batch_id": false, - "code_batch_id": false, - "pay_type": true, - "pay_status": true, - "use_coupon": true, - "deliver_status": true, - "expire_time": true, - "recharge_time": true, - "contract_price": true, - "num": true, - "total": true, - "pay_amount": true, - "create_time": true, - "update_time": true, - "card_code": true, - "official_price": true, - "merchant_name": true, - "activity_name": true, - "goods_name": true, - "pay_time": true, - "coupon_id": false, - "discount_amount": true, - "supplier_product_name": true, - "is_inner": false, - "icon": false, - "cost_price": true, - "success_num": true, - "is_reset": false, - "is_retry": false, - "channel": true, - "is_store": false, - "trace_id": false, - "out_order_no": true, - "next_retry_time": true, - "recharge_suc_time": true, - "supplier_id": true, - "supplier_product_id": false, - "merchant_id": false, - "goods_id": false, - "activity_id": false, - "key_batch_name": true, - "id": false, - "order_id": false, - "creator_id": false, - "is_internal_supplier_order": false, - } - // order_detail 表所有字段 - result["order_detail"] = map[string]bool{ - "plan_title": true, - "order_number": true, - "reseller_name": true, - "product_name": true, - "show_url": false, - "official_price": true, - "cost_price": true, - "create_time": false, - "update_time": false, - } - // order_cash 表所有字段 - result["order_cash"] = map[string]bool{ - "order_no": true, - "trade_no": true, - "wechat_detail_id": false, - "channel": true, - "denomination": true, - "account": true, - "receive_name": true, - "app_id": true, - "cash_activity_id": false, - "receive_status": true, - "receive_time": true, - "success_time": true, - "cash_packet_id": true, - "channel_order_id": false, - "pay_fund_order_id": false, - "cash_id": false, - "amount": true, - "activity_id": false, - "goods_id": false, - "merchant_id": false, - "supplier_id": false, - "user_id": false, - "status": true, - "expire_time": true, - "create_time": true, - "update_time": true, - "version": false, - "is_confirm": true, - } - // order_voucher 表所有字段 - result["order_voucher"] = map[string]bool{ - "channel": true, - "channel_activity_id": false, - "channel_voucher_id": false, - "status": true, - "receive_mode": true, - "grant_time": true, - "usage_time": true, - "refund_time": true, - "status_modify_time": true, - "overdue_time": true, - "refund_amount": true, - "official_price": true, - "out_biz_no": true, - "account_no": true, - } - // plan 表所有字段 - result["plan"] = map[string]bool{ - "id": false, - "title": true, - "status": true, - "begin_time": true, - "end_time": true, - } - // key_batch 表所有字段 - result["key_batch"] = map[string]bool{ - "id": false, - "batch_name": true, - "bind_object": true, - "quantity": true, - "stock": true, - "begin_time": true, - "end_time": true, - } - // code_batch 表所有字段 - result["code_batch"] = map[string]bool{ - "id": false, - "title": true, - "status": true, - "begin_time": true, - "end_time": true, - "quantity": true, - "usage": true, - "stock": true, - } - // voucher 表所有字段 - result["voucher"] = map[string]bool{ - "channel": true, - "channel_activity_id": false, - "price": true, - "balance": true, - "used_amount": true, - "denomination": true, - } - // voucher_batch 表所有字段 - result["voucher_batch"] = map[string]bool{ - "channel_activity_id": true, - "temp_no": true, - "provider": true, - "weight": true, - } - // merchant_key_send 表所有字段 - result["merchant_key_send"] = map[string]bool{ - "merchant_id": true, - "out_biz_no": true, - "key": true, - "status": true, - "usage_time": true, - "create_time": true, + tbl, field := parts[0], parts[1] + if _, ok := out[tbl]; !ok { + out[tbl] = make(map[string]bool) } + out[tbl][field] = true } - - return result + return out } // isFieldHidden 检查字段是否在隐藏列表中 diff --git a/server/internal/api/router.go b/server/internal/api/router.go index 5418815..ddaa5b0 100644 --- a/server/internal/api/router.go +++ b/server/internal/api/router.go @@ -13,6 +13,8 @@ func NewRouter(metaDB *sql.DB, marketingDB *sql.DB, ymtDB *sql.DB) http.Handler mux.Handle("/api/exports", withAccess(withTrace(ExportsHandler(metaDB, marketingDB, ymtDB)))) mux.Handle("/api/exports/", withAccess(withTrace(ExportsHandler(metaDB, marketingDB, ymtDB)))) mux.Handle("/api/metadata/fields", withAccess(withTrace(MetadataHandler(metaDB, marketingDB, ymtDB)))) + mux.Handle("/api/fields", withAccess(withTrace(FieldsHandler(marketingDB, ymtDB)))) + mux.Handle("/api/fields/", withAccess(withTrace(FieldsHandler(marketingDB, ymtDB)))) mux.Handle("/api/creators", withAccess(withTrace(CreatorsHandler(marketingDB)))) mux.Handle("/api/creators/", withAccess(withTrace(CreatorsHandler(marketingDB)))) mux.Handle("/api/resellers", withAccess(withTrace(ResellersHandler(marketingDB)))) diff --git a/server/server b/server/server index 648d637..548018a 100755 Binary files a/server/server and b/server/server differ