数据引擎-快手平台数据抽取bug修复
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/.idea/*
|
||||
/data-engine
|
||||
/resource/*
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"dataengine/common/report/extract"
|
||||
"dataengine/common/report/model"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||
)
|
||||
|
||||
@@ -174,13 +175,12 @@ func (s *ReportService) SaveBusiness(ctx context.Context, req *model.SaveBusines
|
||||
}
|
||||
|
||||
biz := &model.BusinessConfig{
|
||||
SQLBaseDO: beans.SQLBaseDO{Creator: req.Operator, Updater: req.Operator},
|
||||
BusinessCode: req.BusinessCode,
|
||||
BusinessName: req.BusinessName,
|
||||
Description: req.Description,
|
||||
Status: req.Status,
|
||||
Config: req.Config,
|
||||
Creator: req.Operator,
|
||||
Updater: req.Operator,
|
||||
}
|
||||
|
||||
if req.Status == "" {
|
||||
@@ -192,7 +192,7 @@ func (s *ReportService) SaveBusiness(ctx context.Context, req *model.SaveBusines
|
||||
|
||||
if req.ID != nil && *req.ID > 0 {
|
||||
// 更新
|
||||
biz.ID = *req.ID
|
||||
biz.Id = *req.ID
|
||||
if err := s.configLoader.UpdateBusiness(ctx, biz); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -235,6 +235,7 @@ func (s *ReportService) SaveReport(ctx context.Context, req *model.SaveReportReq
|
||||
}
|
||||
|
||||
rpt := &model.ReportConfig{
|
||||
SQLBaseDO: beans.SQLBaseDO{Creator: req.Operator, Updater: req.Operator},
|
||||
BusinessCode: req.BusinessCode,
|
||||
ReportCode: req.ReportCode,
|
||||
ReportName: req.ReportName,
|
||||
@@ -246,8 +247,6 @@ func (s *ReportService) SaveReport(ctx context.Context, req *model.SaveReportReq
|
||||
PrimaryKeys: req.PrimaryKeys,
|
||||
ConflictKeys: req.ConflictKeys,
|
||||
Config: req.Config,
|
||||
Creator: req.Operator,
|
||||
Updater: req.Operator,
|
||||
}
|
||||
|
||||
if req.Status == "" {
|
||||
@@ -267,7 +266,7 @@ func (s *ReportService) SaveReport(ctx context.Context, req *model.SaveReportReq
|
||||
}
|
||||
|
||||
if req.ID != nil && *req.ID > 0 {
|
||||
rpt.ID = *req.ID
|
||||
rpt.Id = *req.ID
|
||||
if err := s.configLoader.UpdateReport(ctx, rpt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -309,6 +308,7 @@ func (s *ReportService) SaveField(ctx context.Context, req *model.SaveFieldReq)
|
||||
}
|
||||
|
||||
field := &model.FieldConfig{
|
||||
SQLBaseDO: beans.SQLBaseDO{Creator: req.Operator, Updater: req.Operator},
|
||||
BusinessCode: req.BusinessCode,
|
||||
ReportCode: req.ReportCode,
|
||||
FieldCode: req.FieldCode,
|
||||
@@ -331,8 +331,6 @@ func (s *ReportService) SaveField(ctx context.Context, req *model.SaveFieldReq)
|
||||
SortOrder: req.SortOrder,
|
||||
GroupName: req.GroupName,
|
||||
Status: req.Status,
|
||||
Creator: req.Operator,
|
||||
Updater: req.Operator,
|
||||
}
|
||||
|
||||
if req.Status == "" {
|
||||
@@ -349,7 +347,7 @@ func (s *ReportService) SaveField(ctx context.Context, req *model.SaveFieldReq)
|
||||
}
|
||||
|
||||
if req.ID != nil && *req.ID > 0 {
|
||||
field.ID = *req.ID
|
||||
field.Id = *req.ID
|
||||
if err := s.configLoader.UpdateField(ctx, field); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -391,6 +389,7 @@ func (s *ReportService) SaveExtractConfig(ctx context.Context, req *model.SaveEx
|
||||
}
|
||||
|
||||
ec := &model.ExtractConfig{
|
||||
SQLBaseDO: beans.SQLBaseDO{Creator: req.Operator, Updater: req.Operator},
|
||||
BusinessCode: req.BusinessCode,
|
||||
ReportCode: req.ReportCode,
|
||||
ExtractCode: req.ExtractCode,
|
||||
@@ -410,8 +409,6 @@ func (s *ReportService) SaveExtractConfig(ctx context.Context, req *model.SaveEx
|
||||
TransformRules: req.TransformRules,
|
||||
BatchSize: req.BatchSize,
|
||||
Status: req.Status,
|
||||
Creator: req.Operator,
|
||||
Updater: req.Operator,
|
||||
}
|
||||
|
||||
if req.Status == "" {
|
||||
@@ -440,7 +437,7 @@ func (s *ReportService) SaveExtractConfig(ctx context.Context, req *model.SaveEx
|
||||
}
|
||||
|
||||
if req.ID != nil && *req.ID > 0 {
|
||||
ec.ID = *req.ID
|
||||
ec.Id = *req.ID
|
||||
if err := s.configLoader.UpdateExtractConfig(ctx, ec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ func (l *ConfigLoader) UpdateBusiness(ctx context.Context, biz *model.BusinessCo
|
||||
delete(m, "deleted_at")
|
||||
|
||||
_, err := gfdb.DB(ctx).Model(ctx, "report_business_config").
|
||||
Where("id", biz.ID).
|
||||
Where("id", biz.Id).
|
||||
Data(m).
|
||||
Update()
|
||||
if err != nil {
|
||||
@@ -368,8 +368,8 @@ func (l *ConfigLoader) GetBusinessByID(ctx context.Context, id int64) (*model.Bu
|
||||
if err = r.Struct(&biz); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Log().Infof(ctx, "[GetBusinessByID] id=%d, biz.ID=%d, biz.BusinessCode=%s",
|
||||
id, biz.ID, biz.BusinessCode)
|
||||
g.Log().Infof(ctx, "[GetBusinessByID] id=%d, biz.Id=%d, biz.BusinessCode=%s",
|
||||
id, biz.Id, biz.BusinessCode)
|
||||
return &biz, nil
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ func (l *ConfigLoader) UpdateReport(ctx context.Context, rpt *model.ReportConfig
|
||||
delete(m, "deleted_at")
|
||||
|
||||
_, err := gfdb.DB(ctx).Model(ctx, "report_report_config").
|
||||
Where("id", rpt.ID).
|
||||
Where("id", rpt.Id).
|
||||
Data(m).
|
||||
Update()
|
||||
if err != nil {
|
||||
@@ -486,7 +486,7 @@ func (l *ConfigLoader) UpdateField(ctx context.Context, field *model.FieldConfig
|
||||
delete(m, "deleted_at")
|
||||
|
||||
_, err := gfdb.DB(ctx).Model(ctx, "report_field_config").
|
||||
Where("id", field.ID).
|
||||
Where("id", field.Id).
|
||||
Data(m).
|
||||
Update()
|
||||
if err != nil {
|
||||
@@ -566,7 +566,7 @@ func (l *ConfigLoader) UpdateExtractConfig(ctx context.Context, ec *model.Extrac
|
||||
delete(m, "deleted_at")
|
||||
|
||||
_, err := gfdb.DB(ctx).Model(ctx, "report_extract_config").
|
||||
Where("id", ec.ID).
|
||||
Where("id", ec.Id).
|
||||
Data(m).
|
||||
Update()
|
||||
if err != nil {
|
||||
|
||||
@@ -317,7 +317,7 @@ func CRUDExample() {
|
||||
BusinessCode: "DOUYIN", BusinessName: "抖音电商",
|
||||
Operator: "admin",
|
||||
})
|
||||
businessId := result.ID
|
||||
businessId := result.Id
|
||||
|
||||
// 修改(传 ID 即修改)
|
||||
result, _ = svc.SaveBusiness(ctx, &model.SaveBusinessReq{
|
||||
@@ -348,7 +348,7 @@ func CRUDExample() {
|
||||
ConflictKeys: []string{"shop_id", "stat_date"},
|
||||
Operator: "admin",
|
||||
})
|
||||
reportId := reportResult.ID
|
||||
reportId := reportResult.Id
|
||||
|
||||
rpt, _ := svc.GetReport(ctx, reportId)
|
||||
svc.DeleteReport(ctx, reportId)
|
||||
@@ -365,7 +365,7 @@ func CRUDExample() {
|
||||
ValidAggregates: []string{"SUM", "AVG", "MAX", "MIN"},
|
||||
SortOrder: 10, GroupName: "金额", Operator: "admin",
|
||||
})
|
||||
fieldId := fieldResult.ID
|
||||
fieldId := fieldResult.Id
|
||||
|
||||
// 修改字段(传 id = 更新)
|
||||
svc.SaveField(ctx, &model.SaveFieldReq{
|
||||
@@ -395,7 +395,7 @@ func CRUDExample() {
|
||||
},
|
||||
Operator: "admin",
|
||||
})
|
||||
ecId := ecResult.ID
|
||||
ecId := ecResult.Id
|
||||
|
||||
ec, _ := svc.GetExtractConfig(ctx, ecId)
|
||||
allEc, _ := svc.GetExtractConfigs(ctx, "DOUYIN", "shop_daily_report")
|
||||
|
||||
@@ -2,6 +2,8 @@ package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// ============================================================
|
||||
@@ -10,24 +12,17 @@ import (
|
||||
|
||||
// BusinessConfig 业务配置
|
||||
type BusinessConfig struct {
|
||||
ID int64 `orm:"id" json:"id"`
|
||||
TenantId uint64 `orm:"tenant_id" json:"tenant_id"`
|
||||
BusinessCode string `orm:"business_code" json:"businessCode"`
|
||||
BusinessName string `orm:"business_name" json:"businessName"`
|
||||
Description string `orm:"description" json:"description"`
|
||||
Status string `orm:"status" json:"status"`
|
||||
Config map[string]interface{} `orm:"config" json:"config"`
|
||||
Creator string `orm:"creator" json:"creator"`
|
||||
CreatedAt *time.Time `orm:"created_at" json:"createdAt"`
|
||||
Updater string `orm:"updater" json:"updater"`
|
||||
UpdatedAt *time.Time `orm:"updated_at" json:"updatedAt"`
|
||||
DeletedAt *time.Time `orm:"deleted_at" json:"deletedAt"`
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
BusinessCode string `orm:"business_code" json:"businessCode"`
|
||||
BusinessName string `orm:"business_name" json:"businessName"`
|
||||
Description string `orm:"description" json:"description"`
|
||||
Status string `orm:"status" json:"status"`
|
||||
Config map[string]interface{} `orm:"config" json:"config"`
|
||||
}
|
||||
|
||||
// ReportConfig 报表配置
|
||||
type ReportConfig struct {
|
||||
ID int64 `orm:"id" json:"id"`
|
||||
TenantId uint64 `orm:"tenant_id" json:"tenant_id"`
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
BusinessCode string `orm:"business_code" json:"businessCode"`
|
||||
ReportCode string `orm:"report_code" json:"reportCode"`
|
||||
ReportName string `orm:"report_name" json:"reportName"`
|
||||
@@ -39,50 +34,38 @@ type ReportConfig struct {
|
||||
PrimaryKeys []string `orm:"primary_keys" json:"primaryKeys"`
|
||||
ConflictKeys []string `orm:"conflict_keys" json:"conflictKeys"`
|
||||
Config map[string]interface{} `orm:"config" json:"config"`
|
||||
Creator string `orm:"creator" json:"creator"`
|
||||
CreatedAt *time.Time `orm:"created_at" json:"createdAt"`
|
||||
Updater string `orm:"updater" json:"updater"`
|
||||
UpdatedAt *time.Time `orm:"updated_at" json:"updatedAt"`
|
||||
DeletedAt *time.Time `orm:"deleted_at" json:"deletedAt"`
|
||||
}
|
||||
|
||||
// FieldConfig 字段配置
|
||||
type FieldConfig struct {
|
||||
ID int64 `orm:"id" json:"id"`
|
||||
TenantId uint64 `orm:"tenant_id" json:"tenant_id"`
|
||||
BusinessCode string `orm:"business_code" json:"businessCode"`
|
||||
ReportCode string `orm:"report_code" json:"reportCode"`
|
||||
FieldCode string `orm:"field_code" json:"fieldCode"`
|
||||
FieldName string `orm:"field_name" json:"fieldName"`
|
||||
FieldType string `orm:"field_type" json:"fieldType"`
|
||||
DataType string `orm:"data_type" json:"dataType"`
|
||||
FieldRole string `orm:"field_role" json:"fieldRole"`
|
||||
IsAggregatable bool `orm:"is_aggregatable" json:"isAggregatable"`
|
||||
IsFilterable bool `orm:"is_filterable" json:"isFilterable"`
|
||||
IsQueryable bool `orm:"is_queryable" json:"isQueryable"`
|
||||
IsSortable bool `orm:"is_sortable" json:"isSortable"`
|
||||
DefaultAggregate string `orm:"default_aggregate" json:"defaultAggregate"`
|
||||
ValidAggregates []string `orm:"valid_aggregates" json:"validAggregates"`
|
||||
FilterOperators []string `orm:"filter_operators" json:"filterOperators"`
|
||||
Expression string `orm:"expression" json:"expression"`
|
||||
ExpressionType string `orm:"expression_type" json:"expressionType"`
|
||||
FormatPattern string `orm:"format_pattern" json:"formatPattern"`
|
||||
Unit string `orm:"unit" json:"unit"`
|
||||
DictCode string `orm:"dict_code" json:"dictCode"`
|
||||
SortOrder int `orm:"sort_order" json:"sortOrder"`
|
||||
GroupName string `orm:"group_name" json:"groupName"`
|
||||
Status string `orm:"status" json:"status"`
|
||||
Creator string `orm:"creator" json:"creator"`
|
||||
CreatedAt *time.Time `orm:"created_at" json:"createdAt"`
|
||||
Updater string `orm:"updater" json:"updater"`
|
||||
UpdatedAt *time.Time `orm:"updated_at" json:"updatedAt"`
|
||||
DeletedAt *time.Time `orm:"deleted_at" json:"deletedAt"`
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
BusinessCode string `orm:"business_code" json:"businessCode"`
|
||||
ReportCode string `orm:"report_code" json:"reportCode"`
|
||||
FieldCode string `orm:"field_code" json:"fieldCode"`
|
||||
FieldName string `orm:"field_name" json:"fieldName"`
|
||||
FieldType string `orm:"field_type" json:"fieldType"`
|
||||
DataType string `orm:"data_type" json:"dataType"`
|
||||
FieldRole string `orm:"field_role" json:"fieldRole"`
|
||||
IsAggregatable bool `orm:"is_aggregatable" json:"isAggregatable"`
|
||||
IsFilterable bool `orm:"is_filterable" json:"isFilterable"`
|
||||
IsQueryable bool `orm:"is_queryable" json:"isQueryable"`
|
||||
IsSortable bool `orm:"is_sortable" json:"isSortable"`
|
||||
DefaultAggregate string `orm:"default_aggregate" json:"defaultAggregate"`
|
||||
ValidAggregates []string `orm:"valid_aggregates" json:"validAggregates"`
|
||||
FilterOperators []string `orm:"filter_operators" json:"filterOperators"`
|
||||
Expression string `orm:"expression" json:"expression"`
|
||||
ExpressionType string `orm:"expression_type" json:"expressionType"`
|
||||
FormatPattern string `orm:"format_pattern" json:"formatPattern"`
|
||||
Unit string `orm:"unit" json:"unit"`
|
||||
DictCode string `orm:"dict_code" json:"dictCode"`
|
||||
SortOrder int `orm:"sort_order" json:"sortOrder"`
|
||||
GroupName string `orm:"group_name" json:"groupName"`
|
||||
Status string `orm:"status" json:"status"`
|
||||
}
|
||||
|
||||
// ExtractConfig 抽取配置
|
||||
type ExtractConfig struct {
|
||||
ID int64 `orm:"id" json:"id"`
|
||||
TenantId uint64 `orm:"tenant_id" json:"tenant_id"`
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
BusinessCode string `orm:"business_code" json:"businessCode"`
|
||||
ReportCode string `orm:"report_code" json:"reportCode"`
|
||||
ExtractCode string `orm:"extract_code" json:"extractCode"`
|
||||
@@ -102,16 +85,12 @@ type ExtractConfig struct {
|
||||
TransformRules []TransformRule `orm:"transform_rules" json:"transformRules"`
|
||||
BatchSize int `orm:"batch_size" json:"batchSize"`
|
||||
Status string `orm:"status" json:"status"`
|
||||
Creator string `orm:"creator" json:"creator"`
|
||||
CreatedAt *time.Time `orm:"created_at" json:"createdAt"`
|
||||
Updater string `orm:"updater" json:"updater"`
|
||||
UpdatedAt *time.Time `orm:"updated_at" json:"updatedAt"`
|
||||
DeletedAt *time.Time `orm:"deleted_at" json:"deletedAt"`
|
||||
}
|
||||
|
||||
// ExtractLog 抽取记录
|
||||
// ExtractLog 抽取记录(不含 Creator/Updater/DeletedAt,表结构不同)
|
||||
type ExtractLog struct {
|
||||
ID int64 `orm:"id" json:"id"`
|
||||
TenantId uint64 `orm:"tenant_id" json:"tenantId"`
|
||||
BusinessCode string `orm:"business_code" json:"businessCode"`
|
||||
ReportCode string `orm:"report_code" json:"reportCode"`
|
||||
ExtractCode string `orm:"extract_code" json:"extractCode"`
|
||||
|
||||
@@ -16,8 +16,10 @@ sync:
|
||||
retry_count: 3 # 最大重试次数
|
||||
sync_interval_minutes: 60 # 自动同步间隔(分钟)
|
||||
compensation_interval_seconds: 300 # 补偿调度器扫描间隔(秒)
|
||||
auto_sync_enabled: false # 是否启用自动同步
|
||||
auto_sync_enabled: true # 是否启用自动同步
|
||||
sync_timeout_minutes: 120 # 单次同步超时(分钟),全量超大表可适当调大
|
||||
default_lookback_days: 89 # 全量同步默认回溯天数(快手限制90天内,留1天余量)(单接口可通过 request_config.full_sync_start_time 覆盖)
|
||||
default_tenant_id: 1 # 自动同步使用的租户 ID(多租户部署时配置)
|
||||
|
||||
# Database.
|
||||
database:
|
||||
|
||||
@@ -254,28 +254,32 @@ async function openPlatformModal(id) {
|
||||
const title = document.getElementById('modalTitle');
|
||||
const body = document.getElementById('modalBody');
|
||||
const footer = document.getElementById('modalFooter');
|
||||
if (id) {
|
||||
const data = await api('GET', P.platform + '/getDatasourcePlatform?id=' + id);
|
||||
title.textContent = '编辑平台';
|
||||
body.innerHTML = pfHTML(data);
|
||||
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveP(' + id + ')">保存</button>';
|
||||
} else {
|
||||
title.textContent = '新建平台';
|
||||
body.innerHTML = pfHTML(null);
|
||||
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveP(0)">创建</button>';
|
||||
}
|
||||
document.getElementById('modalOverlay').classList.add('open');
|
||||
document.getElementById('modal').classList.add('open');
|
||||
try {
|
||||
if (id) {
|
||||
const data = await api('GET', P.platform + '/getDatasourcePlatform?id=' + id);
|
||||
title.textContent = '编辑平台';
|
||||
body.innerHTML = pfHTML(data);
|
||||
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveP(' + id + ')">保存</button>';
|
||||
} else {
|
||||
title.textContent = '新建平台';
|
||||
body.innerHTML = pfHTML(null);
|
||||
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveP(0)">创建</button>';
|
||||
}
|
||||
document.getElementById('modalOverlay').classList.add('open');
|
||||
document.getElementById('modal').classList.add('open');
|
||||
} catch (e) { toast('加载平台详情失败: ' + e.message, 'error'); }
|
||||
}
|
||||
function pfHTML(d) {
|
||||
d = d || {};
|
||||
const authCfg = d.authConfig ? JSON.stringify(d.authConfig, null, 2) : '{}';
|
||||
return '<div class="form-row"><div class="form-group"><label>编码 *</label><input id="f-pcode" value="' + esc(d.platformCode||'') + '"></div>' +
|
||||
'<div class="form-group"><label>名称 *</label><input id="f-pname" value="' + esc(d.platformName||'') + '"></div></div>' +
|
||||
'<div class="form-group"><label>描述</label><input id="f-pdesc" value="' + esc(d.description||'') + '"></div>' +
|
||||
'<div class="form-group"><label>API地址</label><input id="f-apiurl" value="' + esc(d.apiBaseUrl||'') + '"></div>' +
|
||||
'<div class="form-row"><div class="form-group"><label>认证类型 *</label><select id="f-auth"><option value="OAUTH2"' + (d.authType==='OAUTH2'?' selected':'') + '>OAuth2</option><option value="TOKEN"' + (d.authType==='TOKEN'?' selected':'') + '>Token</option><option value="API_KEY"' + (d.authType==='API_KEY'?' selected':'') + '>API Key</option><option value="SIGN"' + (d.authType==='SIGN'?' selected':'') + '>签名</option></select></div>' +
|
||||
'<div class="form-group"><label>状态</label><select id="f-ps"><option value="ACTIVE"' + ((d.status||'ACTIVE')==='ACTIVE'?' selected':'') + '>启用</option><option value="INACTIVE"' + (d.status==='INACTIVE'?' selected':'') + '>停用</option></select></div></div>' +
|
||||
'<div class="form-group"><label>Token / access_token</label><input id="f-tk" value="' + esc(d.token||'') + '"></div>' +
|
||||
'<div class="form-group"><label>API Key</label><input id="f-apik" value="' + esc(d.apiKey||'') + '"></div>' +
|
||||
'<div class="form-row"><div class="form-group"><label>Client ID</label><input id="f-cid" value="' + esc(d.clientId||'') + '"></div>' +
|
||||
'<div class="form-group"><label>Client Secret</label><input id="f-cs" value="' + esc(d.clientSecret||'') + '"></div></div>' +
|
||||
'<div class="form-group"><label>认证配置 JSON</label><textarea id="f-ac" rows="4">' + esc(authCfg) + '</textarea></div>' +
|
||||
@@ -284,9 +288,9 @@ function pfHTML(d) {
|
||||
}
|
||||
async function saveP(id) {
|
||||
const body = {
|
||||
platformCode: v('f-pcode'), platformName: v('f-pname'), apiBaseUrl: v('f-apiurl'),
|
||||
authType: v('f-auth'), status: v('f-ps'), token: v('f-tk'),
|
||||
clientId: v('f-cid'), clientSecret: v('f-cs'),
|
||||
platformCode: v('f-pcode'), platformName: v('f-pname'), description: v('f-pdesc'),
|
||||
apiBaseUrl: v('f-apiurl'), authType: v('f-auth'), status: v('f-ps'),
|
||||
token: v('f-tk'), apiKey: v('f-apik'), clientId: v('f-cid'), clientSecret: v('f-cs'),
|
||||
rateLimitPerMinute: parseInt(v('f-rpm'))||60, requestTimeoutMs: parseInt(v('f-to'))||30000,
|
||||
};
|
||||
try { body.authConfig = JSON.parse(v('f-ac')); } catch(e) { toast('auth_config JSON 格式错误', 'error'); return; }
|
||||
@@ -299,27 +303,30 @@ async function saveP(id) {
|
||||
|
||||
// ========== 接口表单 ==========
|
||||
async function openInterfaceModal(id) {
|
||||
const pl = await api('GET', P.platform + '/listDatasourcePlatforms?page=1&pageSize=100');
|
||||
const opts = (pl.list||[]).map(p => '<option value="' + p.id + '">' + esc(p.platformName) + '</option>').join('');
|
||||
const title = document.getElementById('modalTitle');
|
||||
const body = document.getElementById('modalBody');
|
||||
const footer = document.getElementById('modalFooter');
|
||||
if (id) {
|
||||
const data = await api('GET', P.iface + '/getApiInterface?id=' + id);
|
||||
title.textContent = '编辑接口';
|
||||
body.innerHTML = iFormHTML(data, opts);
|
||||
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveI(' + id + ')">保存</button>';
|
||||
} else {
|
||||
title.textContent = '新建接口';
|
||||
body.innerHTML = iFormHTML(null, opts);
|
||||
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveI(0)">创建</button>';
|
||||
}
|
||||
document.getElementById('modalOverlay').classList.add('open');
|
||||
document.getElementById('modal').classList.add('open');
|
||||
try {
|
||||
const pl = await api('GET', P.platform + '/listDatasourcePlatforms?page=1&pageSize=100');
|
||||
const opts = (pl.list||[]).map(p => '<option value="' + p.id + '">' + esc(p.platformName) + '</option>').join('');
|
||||
const title = document.getElementById('modalTitle');
|
||||
const body = document.getElementById('modalBody');
|
||||
const footer = document.getElementById('modalFooter');
|
||||
if (id) {
|
||||
const data = await api('GET', P.iface + '/getApiInterface?id=' + id);
|
||||
title.textContent = '编辑接口';
|
||||
body.innerHTML = iFormHTML(data, opts);
|
||||
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveI(' + id + ')">保存</button>';
|
||||
} else {
|
||||
title.textContent = '新建接口';
|
||||
body.innerHTML = iFormHTML(null, opts);
|
||||
footer.innerHTML = '<button class="btn btn-outline" onclick="closeModal()">取消</button><button class="btn btn-primary" onclick="saveI(0)">创建</button>';
|
||||
}
|
||||
document.getElementById('modalOverlay').classList.add('open');
|
||||
document.getElementById('modal').classList.add('open');
|
||||
} catch (e) { toast('加载接口详情失败: ' + e.message, 'error'); }
|
||||
}
|
||||
function iFormHTML(d, opts) {
|
||||
d = d || {};
|
||||
const sel = opts.replace('value="' + (d.platformId||'') + '"', 'value="' + (d.platformId||'') + '" selected');
|
||||
const pid = (d.platformId != null) ? d.platformId : '';
|
||||
const sel = opts.replace('value="' + pid + '"', 'value="' + pid + '" selected');
|
||||
return '<div class="form-group"><label>所属平台</label><select id="f-pid">' + sel + '</select></div>' +
|
||||
'<div class="form-row"><div class="form-group"><label>编码 *</label><input id="f-ic" value="' + esc(d.code||'') + '"></div>' +
|
||||
'<div class="form-group"><label>名称 *</label><input id="f-in" value="' + esc(d.name||'') + '"></div></div>' +
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
consts "dataengine/consts/public"
|
||||
dto "dataengine/model/dto/dict"
|
||||
entity "dataengine/model/entity/dict"
|
||||
"time"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/db/gfdb"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
@@ -18,17 +18,18 @@ var DatasourcePlatform = new(datasourcePlatformDao)
|
||||
type datasourcePlatformDao struct{}
|
||||
|
||||
// Insert 插入数据源平台
|
||||
func (d *datasourcePlatformDao) Insert(ctx context.Context, req *dto.CreateDatasourcePlatformReq, createdBy string) (ID int64, err error) {
|
||||
func (d *datasourcePlatformDao) Insert(ctx context.Context, req *dto.CreateDatasourcePlatformReq, createdBy string, tenantId uint64) (ID int64, err error) {
|
||||
var res *entity.DatasourcePlatform
|
||||
if err = gconv.Struct(req, &res); err != nil {
|
||||
return
|
||||
}
|
||||
// 设置创建人和时间(服务端生成,不依赖前端入参)
|
||||
now := time.Now()
|
||||
res.CreatedBy = createdBy
|
||||
res.CreatedAt = &now
|
||||
res.UpdatedBy = createdBy
|
||||
res.UpdatedAt = &now
|
||||
// 设置审计字段(服务端生成,不依赖前端入参)
|
||||
now := gtime.Now()
|
||||
res.TenantId = tenantId
|
||||
res.Creator = createdBy
|
||||
res.CreatedAt = now
|
||||
res.Updater = createdBy
|
||||
res.UpdatedAt = now
|
||||
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).Data(&res).Insert()
|
||||
if err != nil {
|
||||
@@ -38,16 +39,17 @@ func (d *datasourcePlatformDao) Insert(ctx context.Context, req *dto.CreateDatas
|
||||
}
|
||||
|
||||
// Update 更新数据源平台
|
||||
func (d *datasourcePlatformDao) Update(ctx context.Context, req *dto.UpdateDatasourcePlatformReq, updatedBy string) (rows int64, err error) {
|
||||
func (d *datasourcePlatformDao) Update(ctx context.Context, req *dto.UpdateDatasourcePlatformReq, updatedBy string, tenantId uint64) (rows int64, err error) {
|
||||
// 设置更新人和更新时间(服务端生成,不依赖前端入参)
|
||||
data := gconv.Map(req)
|
||||
data[entity.DatasourcePlatformCols.UpdatedBy] = updatedBy
|
||||
data[entity.DatasourcePlatformCols.UpdatedAt] = time.Now()
|
||||
data[entity.DatasourcePlatformCols.Updater] = updatedBy
|
||||
data[entity.DatasourcePlatformCols.UpdatedAt] = gtime.Now()
|
||||
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Data(data).
|
||||
OmitEmpty().
|
||||
Where(entity.DatasourcePlatformCols.ID, req.Id).
|
||||
Where(entity.DatasourcePlatformCols.Id, req.Id).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
Update()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -56,9 +58,10 @@ func (d *datasourcePlatformDao) Update(ctx context.Context, req *dto.UpdateDatas
|
||||
}
|
||||
|
||||
// Delete 删除数据源平台
|
||||
func (d *datasourcePlatformDao) Delete(ctx context.Context, req *dto.DeleteDatasourcePlatformReq) (rows int64, err error) {
|
||||
func (d *datasourcePlatformDao) Delete(ctx context.Context, req *dto.DeleteDatasourcePlatformReq, tenantId uint64) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Where(entity.DatasourcePlatformCols.ID, req.Id).
|
||||
Where(entity.DatasourcePlatformCols.Id, req.Id).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -67,9 +70,10 @@ func (d *datasourcePlatformDao) Delete(ctx context.Context, req *dto.DeleteDatas
|
||||
}
|
||||
|
||||
// GetOne 获取单个数据源平台
|
||||
func (d *datasourcePlatformDao) GetOne(ctx context.Context, req *dto.GetDatasourcePlatformReq) (res *entity.DatasourcePlatform, err error) {
|
||||
func (d *datasourcePlatformDao) GetOne(ctx context.Context, req *dto.GetDatasourcePlatformReq, tenantId uint64) (res *entity.DatasourcePlatform, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Where(entity.DatasourcePlatformCols.ID, req.Id).
|
||||
Where(entity.DatasourcePlatformCols.Id, req.Id).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
One()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -79,9 +83,10 @@ func (d *datasourcePlatformDao) GetOne(ctx context.Context, req *dto.GetDatasour
|
||||
}
|
||||
|
||||
// GetByPlatformCode 根据平台编码获取数据源平台
|
||||
func (d *datasourcePlatformDao) GetByPlatformCode(ctx context.Context, platformCode string) (res *entity.DatasourcePlatform, err error) {
|
||||
func (d *datasourcePlatformDao) GetByPlatformCode(ctx context.Context, platformCode string, tenantId uint64) (res *entity.DatasourcePlatform, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Where(entity.DatasourcePlatformCols.PlatformCode, platformCode).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
One()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -91,13 +96,13 @@ func (d *datasourcePlatformDao) GetByPlatformCode(ctx context.Context, platformC
|
||||
}
|
||||
|
||||
// Count 获取数据源平台数量
|
||||
func (d *datasourcePlatformDao) Count(ctx context.Context, req *dto.ListDatasourcePlatformReq) (count int, err error) {
|
||||
return d.buildListFilter(ctx, req).Count()
|
||||
func (d *datasourcePlatformDao) Count(ctx context.Context, req *dto.ListDatasourcePlatformReq, tenantId uint64) (count int, err error) {
|
||||
return d.buildListFilter(ctx, req, tenantId).Count()
|
||||
}
|
||||
|
||||
// List 获取数据源平台列表
|
||||
func (d *datasourcePlatformDao) List(ctx context.Context, req *dto.ListDatasourcePlatformReq) (res []entity.DatasourcePlatform, total int, err error) {
|
||||
model := d.buildListFilter(ctx, req)
|
||||
func (d *datasourcePlatformDao) List(ctx context.Context, req *dto.ListDatasourcePlatformReq, tenantId uint64) (res []entity.DatasourcePlatform, total int, err error) {
|
||||
model := d.buildListFilter(ctx, req, tenantId)
|
||||
model.OrderDesc(entity.DatasourcePlatformCols.CreatedAt)
|
||||
if req.Page != nil {
|
||||
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||
@@ -111,9 +116,12 @@ func (d *datasourcePlatformDao) List(ctx context.Context, req *dto.ListDatasourc
|
||||
}
|
||||
|
||||
// buildListFilter 构建列表查询的过滤条件
|
||||
func (d *datasourcePlatformDao) buildListFilter(ctx context.Context, req *dto.ListDatasourcePlatformReq) *gdb.Model {
|
||||
func (d *datasourcePlatformDao) buildListFilter(ctx context.Context, req *dto.ListDatasourcePlatformReq, tenantId uint64) *gdb.Model {
|
||||
model := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).Model
|
||||
|
||||
// 租户隔离
|
||||
model.Where(entity.DatasourcePlatformCols.TenantId, tenantId)
|
||||
|
||||
// 关键字搜索(平台名称或编码)
|
||||
if !g.IsEmpty(req.Keyword) {
|
||||
model.WhereLike(entity.DatasourcePlatformCols.PlatformName, "%"+req.Keyword+"%")
|
||||
@@ -140,14 +148,15 @@ func (d *datasourcePlatformDao) buildListFilter(ctx context.Context, req *dto.Li
|
||||
}
|
||||
|
||||
// UpdateStatus 更新数据源平台状态
|
||||
func (d *datasourcePlatformDao) UpdateStatus(ctx context.Context, ID int64, status string, updatedBy string) (rows int64, err error) {
|
||||
func (d *datasourcePlatformDao) UpdateStatus(ctx context.Context, ID int64, status string, updatedBy string, tenantId uint64) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Data(map[string]interface{}{
|
||||
entity.DatasourcePlatformCols.Status: status,
|
||||
entity.DatasourcePlatformCols.UpdatedBy: updatedBy,
|
||||
entity.DatasourcePlatformCols.UpdatedAt: time.Now(),
|
||||
entity.DatasourcePlatformCols.Updater: updatedBy,
|
||||
entity.DatasourcePlatformCols.UpdatedAt: gtime.Now(),
|
||||
}).
|
||||
Where(entity.DatasourcePlatformCols.ID, ID).
|
||||
Where(entity.DatasourcePlatformCols.Id, ID).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
Update()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -156,12 +165,13 @@ func (d *datasourcePlatformDao) UpdateStatus(ctx context.Context, ID int64, stat
|
||||
}
|
||||
|
||||
// ExistsByPlatformCode 检查平台编码是否存在
|
||||
func (d *datasourcePlatformDao) ExistsByPlatformCode(ctx context.Context, platformCode string, excludeId ...int64) (exists bool, err error) {
|
||||
func (d *datasourcePlatformDao) ExistsByPlatformCode(ctx context.Context, platformCode string, tenantId uint64, excludeId ...int64) (exists bool, err error) {
|
||||
model := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Where(entity.DatasourcePlatformCols.PlatformCode, platformCode)
|
||||
Where(entity.DatasourcePlatformCols.PlatformCode, platformCode).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId)
|
||||
|
||||
if len(excludeId) > 0 && excludeId[0] > 0 {
|
||||
model.WhereNot(entity.DatasourcePlatformCols.ID, excludeId[0])
|
||||
model.WhereNot(entity.DatasourcePlatformCols.Id, excludeId[0])
|
||||
}
|
||||
|
||||
count, err := model.Count()
|
||||
@@ -172,9 +182,10 @@ func (d *datasourcePlatformDao) ExistsByPlatformCode(ctx context.Context, platfo
|
||||
}
|
||||
|
||||
// ListActivePlatforms 获取所有启用的平台
|
||||
func (d *datasourcePlatformDao) ListActivePlatforms(ctx context.Context) (res []entity.DatasourcePlatform, err error) {
|
||||
func (d *datasourcePlatformDao) ListActivePlatforms(ctx context.Context, tenantId uint64) (res []entity.DatasourcePlatform, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Where(entity.DatasourcePlatformCols.Status, "ACTIVE").
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
OrderAsc(entity.DatasourcePlatformCols.PlatformName).
|
||||
All()
|
||||
if err != nil {
|
||||
@@ -185,11 +196,13 @@ func (d *datasourcePlatformDao) ListActivePlatforms(ctx context.Context) (res []
|
||||
}
|
||||
|
||||
// GetPlatformStatistics 获取平台统计信息
|
||||
func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context) (stats map[string]int64, err error) {
|
||||
func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context, tenantId uint64) (stats map[string]int64, err error) {
|
||||
stats = make(map[string]int64)
|
||||
|
||||
// 总平台数
|
||||
total, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).Count()
|
||||
total, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -198,6 +211,7 @@ func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context) (stat
|
||||
// 启用平台数
|
||||
active, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Where(entity.DatasourcePlatformCols.Status, "ACTIVE").
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -212,6 +226,7 @@ func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context) (stat
|
||||
for _, authType := range authTypes {
|
||||
count, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Where(entity.DatasourcePlatformCols.AuthType, authType).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -223,14 +238,15 @@ func (d *datasourcePlatformDao) GetPlatformStatistics(ctx context.Context) (stat
|
||||
}
|
||||
|
||||
// BatchUpdateStatus 批量更新平台状态
|
||||
func (d *datasourcePlatformDao) BatchUpdateStatus(ctx context.Context, ids []int64, status string, updatedBy string) (rows int64, err error) {
|
||||
func (d *datasourcePlatformDao) BatchUpdateStatus(ctx context.Context, ids []int64, status string, updatedBy string, tenantId uint64) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.DatasourcePlatformTable).
|
||||
Data(map[string]interface{}{
|
||||
entity.DatasourcePlatformCols.Status: status,
|
||||
entity.DatasourcePlatformCols.UpdatedBy: updatedBy,
|
||||
entity.DatasourcePlatformCols.UpdatedAt: time.Now(),
|
||||
entity.DatasourcePlatformCols.Updater: updatedBy,
|
||||
entity.DatasourcePlatformCols.UpdatedAt: gtime.Now(),
|
||||
}).
|
||||
WhereIn(entity.DatasourcePlatformCols.ID, ids).
|
||||
WhereIn(entity.DatasourcePlatformCols.Id, ids).
|
||||
Where(entity.DatasourcePlatformCols.TenantId, tenantId).
|
||||
Update()
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -18,13 +18,14 @@ var ApiInterface = new(apiInterfaceDao)
|
||||
type apiInterfaceDao struct{}
|
||||
|
||||
// Insert 插入接口
|
||||
func (d *apiInterfaceDao) Insert(ctx context.Context, req *dto.CreateApiInterfaceReq, creator string) (id int64, err error) {
|
||||
func (d *apiInterfaceDao) Insert(ctx context.Context, req *dto.CreateApiInterfaceReq, creator string, tenantId uint64) (id int64, err error) {
|
||||
var res *entity.ApiInterface
|
||||
if err = gconv.Struct(req, &res); err != nil {
|
||||
return
|
||||
}
|
||||
// 设置创建人和时间(服务端生成,不依赖前端入参)
|
||||
// 设置审计字段(服务端生成,不依赖前端入参)
|
||||
now := gtime.Now()
|
||||
res.TenantId = tenantId
|
||||
res.Creator = creator
|
||||
res.CreatedAt = now
|
||||
res.Updater = creator
|
||||
@@ -38,7 +39,7 @@ func (d *apiInterfaceDao) Insert(ctx context.Context, req *dto.CreateApiInterfac
|
||||
}
|
||||
|
||||
// Update 更新接口
|
||||
func (d *apiInterfaceDao) Update(ctx context.Context, req *dto.UpdateApiInterfaceReq, updater string) (rows int64, err error) {
|
||||
func (d *apiInterfaceDao) Update(ctx context.Context, req *dto.UpdateApiInterfaceReq, updater string, tenantId uint64) (rows int64, err error) {
|
||||
// 设置更新人和更新时间(服务端生成,不依赖前端入参)
|
||||
data := gconv.Map(req)
|
||||
data[entity.ApiInterfaceCols.Updater] = updater
|
||||
@@ -48,6 +49,7 @@ func (d *apiInterfaceDao) Update(ctx context.Context, req *dto.UpdateApiInterfac
|
||||
Data(data).
|
||||
OmitEmpty().
|
||||
Where(entity.ApiInterfaceCols.Id, req.Id).
|
||||
Where(entity.ApiInterfaceCols.TenantId, tenantId).
|
||||
Update()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -56,8 +58,11 @@ func (d *apiInterfaceDao) Update(ctx context.Context, req *dto.UpdateApiInterfac
|
||||
}
|
||||
|
||||
// Delete 删除接口
|
||||
func (d *apiInterfaceDao) Delete(ctx context.Context, req *dto.DeleteApiInterfaceReq) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).Where(entity.ApiInterfaceCols.Id, req.Id).Delete()
|
||||
func (d *apiInterfaceDao) Delete(ctx context.Context, req *dto.DeleteApiInterfaceReq, tenantId uint64) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
|
||||
Where(entity.ApiInterfaceCols.Id, req.Id).
|
||||
Where(entity.ApiInterfaceCols.TenantId, tenantId).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -65,8 +70,11 @@ func (d *apiInterfaceDao) Delete(ctx context.Context, req *dto.DeleteApiInterfac
|
||||
}
|
||||
|
||||
// GetOne 获取单个接口
|
||||
func (d *apiInterfaceDao) GetOne(ctx context.Context, req *dto.GetApiInterfaceReq) (res *entity.ApiInterface, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).Where(entity.ApiInterfaceCols.Id, req.Id).One()
|
||||
func (d *apiInterfaceDao) GetOne(ctx context.Context, req *dto.GetApiInterfaceReq, tenantId uint64) (res *entity.ApiInterface, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
|
||||
Where(entity.ApiInterfaceCols.Id, req.Id).
|
||||
Where(entity.ApiInterfaceCols.TenantId, tenantId).
|
||||
One()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -75,13 +83,13 @@ func (d *apiInterfaceDao) GetOne(ctx context.Context, req *dto.GetApiInterfaceRe
|
||||
}
|
||||
|
||||
// Count 获取接口数量
|
||||
func (d *apiInterfaceDao) Count(ctx context.Context, req *dto.ListApiInterfaceReq) (count int, err error) {
|
||||
return d.buildListFilter(ctx, req).Count()
|
||||
func (d *apiInterfaceDao) Count(ctx context.Context, req *dto.ListApiInterfaceReq, tenantId uint64) (count int, err error) {
|
||||
return d.buildListFilter(ctx, req, tenantId).Count()
|
||||
}
|
||||
|
||||
// List 获取接口列表
|
||||
func (d *apiInterfaceDao) List(ctx context.Context, req *dto.ListApiInterfaceReq) (res []entity.ApiInterface, total int, err error) {
|
||||
model := d.buildListFilter(ctx, req)
|
||||
func (d *apiInterfaceDao) List(ctx context.Context, req *dto.ListApiInterfaceReq, tenantId uint64) (res []entity.ApiInterface, total int, err error) {
|
||||
model := d.buildListFilter(ctx, req, tenantId)
|
||||
model.OrderDesc(entity.ApiInterfaceCols.CreatedAt)
|
||||
if req.Page != nil {
|
||||
model.Page(int(req.Page.PageNum), int(req.Page.PageSize))
|
||||
@@ -95,8 +103,12 @@ func (d *apiInterfaceDao) List(ctx context.Context, req *dto.ListApiInterfaceReq
|
||||
}
|
||||
|
||||
// buildListFilter 构建列表查询的过滤条件
|
||||
func (d *apiInterfaceDao) buildListFilter(ctx context.Context, req *dto.ListApiInterfaceReq) *gdb.Model {
|
||||
func (d *apiInterfaceDao) buildListFilter(ctx context.Context, req *dto.ListApiInterfaceReq, tenantId uint64) *gdb.Model {
|
||||
model := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).Model
|
||||
|
||||
// 租户隔离
|
||||
model.Where(entity.ApiInterfaceCols.TenantId, tenantId)
|
||||
|
||||
if !g.IsEmpty(req.Keyword) {
|
||||
model.WhereLike(entity.ApiInterfaceCols.Name, "%"+req.Keyword+"%")
|
||||
model.WhereOrLike(entity.ApiInterfaceCols.Code, "%"+req.Keyword+"%")
|
||||
@@ -104,16 +116,24 @@ func (d *apiInterfaceDao) buildListFilter(ctx context.Context, req *dto.ListApiI
|
||||
if req.PlatformId > 0 {
|
||||
model.Where(entity.ApiInterfaceCols.PlatformId, req.PlatformId)
|
||||
}
|
||||
model.Where(entity.ApiInterfaceCols.Name, req.Name)
|
||||
model.Where(entity.ApiInterfaceCols.Code, req.Code)
|
||||
model.Where(entity.ApiInterfaceCols.Method, req.Method)
|
||||
model.Where(entity.ApiInterfaceCols.Status, req.Status)
|
||||
if !g.IsEmpty(req.Name) {
|
||||
model.Where(entity.ApiInterfaceCols.Name, req.Name)
|
||||
}
|
||||
if !g.IsEmpty(req.Code) {
|
||||
model.Where(entity.ApiInterfaceCols.Code, req.Code)
|
||||
}
|
||||
if !g.IsEmpty(req.Method) {
|
||||
model.Where(entity.ApiInterfaceCols.Method, req.Method)
|
||||
}
|
||||
if !g.IsEmpty(req.Status) {
|
||||
model.Where(entity.ApiInterfaceCols.Status, req.Status)
|
||||
}
|
||||
model.OmitEmptyWhere()
|
||||
return model
|
||||
}
|
||||
|
||||
// UpdateStatus 更新接口状态
|
||||
func (d *apiInterfaceDao) UpdateStatus(ctx context.Context, id int64, status string, updater string) (rows int64, err error) {
|
||||
func (d *apiInterfaceDao) UpdateStatus(ctx context.Context, id int64, status string, updater string, tenantId uint64) (rows int64, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
|
||||
Data(map[string]interface{}{
|
||||
entity.ApiInterfaceCols.Status: status,
|
||||
@@ -121,6 +141,7 @@ func (d *apiInterfaceDao) UpdateStatus(ctx context.Context, id int64, status str
|
||||
entity.ApiInterfaceCols.UpdatedAt: gtime.Now(),
|
||||
}).
|
||||
Where(entity.ApiInterfaceCols.Id, id).
|
||||
Where(entity.ApiInterfaceCols.TenantId, tenantId).
|
||||
Update()
|
||||
if err != nil {
|
||||
return
|
||||
@@ -129,9 +150,10 @@ func (d *apiInterfaceDao) UpdateStatus(ctx context.Context, id int64, status str
|
||||
}
|
||||
|
||||
// GetByIds 根据ID列表获取接口列表
|
||||
func (d *apiInterfaceDao) GetByIds(ctx context.Context, ids []int64) (res []entity.ApiInterface, err error) {
|
||||
func (d *apiInterfaceDao) GetByIds(ctx context.Context, ids []int64, tenantId uint64) (res []entity.ApiInterface, err error) {
|
||||
r, err := gfdb.DB(ctx).Model(ctx, consts.ApiInterfaceTable).
|
||||
WhereIn(entity.ApiInterfaceCols.Id, ids).
|
||||
Where(entity.ApiInterfaceCols.TenantId, tenantId).
|
||||
All()
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
568
docs/CONFIG_GUIDE.md
Normal file
568
docs/CONFIG_GUIDE.md
Normal file
@@ -0,0 +1,568 @@
|
||||
# 数据引擎 — 平台与接口配置指南
|
||||
|
||||
> 适用版本:HDWL data-engine
|
||||
> 阅读对象:实施工程师、运维人员
|
||||
> 前置知识:了解 JSON 基本格式即可
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [概念说明](#1-概念说明)
|
||||
2. [平台管理 (api_datasource_platform)](#2-平台管理)
|
||||
3. [接口管理 (api_interface)](#3-接口管理)
|
||||
4. [实战:新增一个平台](#4-实战新增一个平台)
|
||||
5. [常见问题](#5-常见问题)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概念说明
|
||||
|
||||
```
|
||||
平台 (Platform) → 对接的第三方系统(如:快手电商、腾讯广告、钉钉)
|
||||
└── 接口 (Interface) → 平台提供的具体 API(如:订单列表、商品详情)
|
||||
└── 表 (Table) → 数据最终存储的数据库表
|
||||
```
|
||||
|
||||
**举例**:
|
||||
- **平台** = 快手电商
|
||||
- **接口1** = 订单列表 → 数据存入 `kuaishou_order_list` 表
|
||||
- **接口2** = 商品列表 → 数据存入 `kuaishou_item_list` 表
|
||||
- **接口3** = 售后单列表 → 数据存入 `kuaishou_refund_list` 表
|
||||
|
||||
系统会按照配置的**调度周期**,自动拉取每个接口的数据并存入对应的数据库表。
|
||||
|
||||
---
|
||||
|
||||
## 2. 平台管理
|
||||
|
||||
平台配置存储在 `api_datasource_platform` 表中,定义了一个第三方平台的基本信息和认证方式。
|
||||
|
||||
### 2.1 基础字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 | 示例 |
|
||||
|------|------|------|------|------|
|
||||
| `platform_code` | VARCHAR | ✅ | 平台唯一编码,不能重复 | `kuaishou`、`tencent` |
|
||||
| `platform_name` | VARCHAR | ✅ | 平台显示名称 | `快手电商`、`腾讯广告` |
|
||||
| `description` | VARCHAR | ❌ | 平台描述 | `快手电商开放平台数据同步` |
|
||||
| `status` | VARCHAR | ✅ | 状态,`ACTIVE`=启用,`INACTIVE`=停用 | `ACTIVE` |
|
||||
| `api_base_url` | VARCHAR | ✅ | API 域名,接口地址会拼接在此 URL 后面 | `https://openapi.kwaixiaodian.com` |
|
||||
|
||||
### 2.2 认证类型 (auth_type)
|
||||
|
||||
| 类型 | 说明 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| `TOKEN` | 简单 Token 认证,放在 Header 中 | 内部系统 |
|
||||
| `API_KEY` | API Key 认证,Token 放在请求参数中 | **快手电商**、钉钉 |
|
||||
| `OAUTH2` | OAuth 2.0 认证 | 腾讯广告 |
|
||||
| `SIGN` | 签名认证,使用 appKey + appSecret 签名 | 部分开放平台 |
|
||||
| `APP_SIGNATURE` | 应用签名认证,基于请求体 MD5 | 钉钉智能薪酬 |
|
||||
|
||||
### 2.3 认证配置 (auth_config)
|
||||
|
||||
`auth_config` 是一个 **JSON 对象**,根据不同的 `auth_type` 配置不同内容。
|
||||
|
||||
#### API_KEY 类型配置(快手电商示例)
|
||||
|
||||
```json
|
||||
{
|
||||
"sign_algorithm": "md5",
|
||||
"app_key": "ks651333099611149957",
|
||||
"app_secret": "JPUXG2CS3I7tqRWbKaLrYQ",
|
||||
"sign_secret": "7bc51baab818cf86e121a48d99ff3fe4",
|
||||
"token_in_query": true,
|
||||
"query_key": "access_token",
|
||||
"extra_query_params": {
|
||||
"timestamp": "{timestamp_ms}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `sign_algorithm` | 签名算法,可选 `md5`(小写输出)或 `md5_upper`(大写输出)或 `HMAC_SHA256` |
|
||||
| `app_key` | 平台分配的 AppKey(也叫 AppId、client_id),**快手签名必填** |
|
||||
| `app_secret` | 平台分配的 AppSecret,用于 OAuth 获取 Token |
|
||||
| `sign_secret` | 签名专用密钥(**快手特有**),平台分配,在"应用详情"中查看 |
|
||||
| `token_in_query` | `true`=Token 放在 URL 参数中;`false`=Token 放在 Header 中 |
|
||||
| `query_key` | Token 的参数名,默认 `access_token` |
|
||||
| `extra_query_params` | 额外需要拼接的参数,支持 `{timestamp}`(秒)、`{timestamp_ms}`(毫秒)、`{nonce}` 占位符 |
|
||||
|
||||
> **快手签名算法**(仅供参考,代码已实现):
|
||||
> 1. 所有参数按字母排序,拼接成 `key1=value1&key2=value2&...`
|
||||
> 2. 末尾追加 `&signSecret=你的signSecret`
|
||||
> 3. 对整个字符串取 MD5
|
||||
|
||||
#### SIGN 类型配置
|
||||
|
||||
```json
|
||||
{
|
||||
"sign_algorithm": "md5",
|
||||
"app_key": "your_app_key",
|
||||
"app_secret": "your_app_secret"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.4 限流与重试字段
|
||||
|
||||
| 字段 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `rate_limit_per_minute` | INT | 100 | 每分钟最大请求次数,防止触发平台限流 |
|
||||
| `rate_limit_per_hour` | INT | 3600 | 每小时最大请求次数 |
|
||||
| `concurrency_limit` | INT | 5 | 并发请求数 |
|
||||
| `request_timeout_ms` | INT | 30000 | 单次请求超时时间(毫秒) |
|
||||
| `max_retries` | INT | 3 | 请求失败后的最大重试次数 |
|
||||
| `retry_delay_ms` | INT | 1000 | 重试间隔(毫秒),每次翻倍 |
|
||||
|
||||
### 2.5 Token 与 API Key 字段
|
||||
|
||||
| 字段 | 说明 | 快手示例 |
|
||||
|------|------|----------|
|
||||
| `token` | 认证 Token / Refresh Token | 快手 OAuth 返回的 `refresh_token` |
|
||||
| `api_key` | API Key / Access Token | 快手 OAuth 返回的 `access_token` |
|
||||
| `client_id` | OAuth2 Client ID | 腾讯广告的 client_id |
|
||||
| `client_secret` | OAuth2 Client Secret | 腾讯广告的 client_secret |
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口管理
|
||||
|
||||
接口配置存储在 `api_interface` 表中,定义了一个平台下每个 API 的请求方式、参数、响应解析规则和存储表结构。
|
||||
|
||||
### 3.1 基础字段
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `platform_id` | INT | ✅ | 所属平台的 ID(关联 `api_datasource_platform.id`) |
|
||||
| `name` | VARCHAR | ✅ | 接口名称,如"订单列表" |
|
||||
| `code` | VARCHAR | ✅ | 接口编码,唯一标识,如 `order_list` |
|
||||
| `url` | VARCHAR | ✅ | API 路径,会拼接在平台 `api_base_url` 后面 |
|
||||
| `method` | VARCHAR | ✅ | HTTP 方法,`GET` 或 `POST` |
|
||||
| `status` | VARCHAR | ✅ | 状态,`active`=启用,`inactive`=停用 |
|
||||
| `auth_type` | VARCHAR | ✅ | 认证方式,`inherit`=继承平台配置 |
|
||||
|
||||
### 3.2 请求配置 (request_config)
|
||||
|
||||
`request_config` 是一个 **JSON 对象**,定义如何构造 API 请求参数。
|
||||
|
||||
#### 3.2.1 完整字段说明
|
||||
|
||||
```json
|
||||
{
|
||||
"page_param": "cursor",
|
||||
"page_size_param": "pageSize",
|
||||
"cursor_pagination": true,
|
||||
"pagination_mode": "offset",
|
||||
"page_size": 50,
|
||||
"initial_cursor": "",
|
||||
"method": "open.order.cursor.list",
|
||||
"version": 1,
|
||||
"signMethod": "MD5",
|
||||
"time_field": "updateTime",
|
||||
"time_field_mode": "range",
|
||||
"full_sync_start_time": 1700000000000,
|
||||
"queryType": 2,
|
||||
"parameters_location": "query",
|
||||
"body_wrapper_field": "param",
|
||||
"exclude_from_wrapper": ["method", "version", "signMethod"],
|
||||
"prefetch": { ... },
|
||||
"recursive": { ... },
|
||||
"row_inject": ["statisticsMonth"],
|
||||
"max_recursive_depth": 20
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.2 分页配置
|
||||
|
||||
| 字段 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `page_param` | 页码/游标的参数名 | `cursor`、`page`、`offset` |
|
||||
| `page_size_param` | 每页条数的参数名 | `pageSize`、`page_size` |
|
||||
| `page_size` | 每页请求条数,覆盖全局配置 | `50` |
|
||||
| `cursor_pagination` | `true`=游标分页;不配置=普通页码分页 | `true` |
|
||||
| `pagination_mode` | `offset`=偏移量分页(`offset=(page-1)*size`);不配置=页码分页 | `offset` |
|
||||
| `initial_cursor` | 游标分页时的初始游标值,不配则为空字符串 | `""` |
|
||||
|
||||
**三种分页模式对比**:
|
||||
|
||||
| 模式 | 配置方式 | 适用平台 | 翻页方式 |
|
||||
|------|----------|----------|----------|
|
||||
| 游标分页 | `cursor_pagination: true` | 快手、钉钉 | 响应返回 cursor,下次请求带上 |
|
||||
| 普通分页 | 默认 | 腾讯广告 | 响应返回 totalPages,按页码翻 |
|
||||
| hasMore 分页 | `has_more_field` 在 response_config | 钉钉 | 响应有 hasMore 字段判断是否有下一页 |
|
||||
|
||||
#### 3.2.3 时间过滤配置
|
||||
|
||||
用于增量同步——只拉取上次同步之后新增/更新的数据。
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `time_field` | 时间字段名,如 `updateTime`、`createTime` |
|
||||
| `time_field_mode` | 时间模式:`range`=快手模式(beginTime/endTime),`filtering`=腾讯模式(filtering 数组) |
|
||||
| `full_sync_start_time` | 首次全量同步的起始时间戳(**Unix 毫秒**),不配则用全局 `default_lookback_days` |
|
||||
| `queryType` | 快手专用:`1`=按创建时间查(90天内),`2`=按更新时间查(240天内) |
|
||||
|
||||
> **全局配置** `config.yml` 中 `sync.default_lookback_days` 控制默认回溯天数(默认 89 天)。
|
||||
|
||||
#### 3.2.4 业务参数
|
||||
|
||||
除了上述系统级字段外,`request_config` 中其他字段都会作为**业务参数**发送给 API。
|
||||
|
||||
**快手示例**(`orderViewStatus`、`cpsType`、`sort` 等是业务参数):
|
||||
```json
|
||||
{
|
||||
"orderViewStatus": 1,
|
||||
"cpsType": 1,
|
||||
"sort": 1,
|
||||
"pageSize": 50
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2.5 body_wrapper_field(快手专用)
|
||||
|
||||
快手 API 要求所有业务参数打包成一个 JSON 字符串,放在 `param` 字段中发送。
|
||||
|
||||
```json
|
||||
{
|
||||
"body_wrapper_field": "param",
|
||||
"exclude_from_wrapper": ["method", "version", "signMethod"]
|
||||
}
|
||||
```
|
||||
|
||||
**效果**:
|
||||
- 业务参数 `orderViewStatus=1, pageSize=50, sort=1` → 打包为 `param={"orderViewStatus":1,"pageSize":50,"sort":1}`
|
||||
- `method`、`version`、`signMethod` 保持在顶层,不被打包
|
||||
|
||||
#### 3.2.6 预取配置 (prefetch)
|
||||
|
||||
用于需要**先获取实体列表,再逐个查详情**的场景。
|
||||
|
||||
```json
|
||||
{
|
||||
"prefetch": {
|
||||
"url": "/open/order/cursor/list",
|
||||
"method": "GET",
|
||||
"response_path": "data.orderList",
|
||||
"target_param": "oid",
|
||||
"value_field": "oid"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `url` | 预取数据的 API 路径 |
|
||||
| `method` | HTTP 方法 |
|
||||
| `response_path` | 从响应中提取实体列表的 JSON 路径,如 `data.orderList` |
|
||||
| `target_param` | 将提取的值传给目标接口时使用的参数名 |
|
||||
| `value_field` | 从实体中取哪个字段的值,不配则传整个实体对象 |
|
||||
|
||||
**工作流程示例**(订单详情):
|
||||
1. 先调用 `/open/order/cursor/list` 获取所有订单 ID 列表
|
||||
2. 对每个订单 ID,调用 `/open/order/detail?oid=xxx` 获取详情
|
||||
3. 所有详情数据合并存入 `kuaishou_order_detail` 表
|
||||
|
||||
#### 3.2.7 递归遍历配置 (recursive)
|
||||
|
||||
用于树形结构数据(如钉钉部门树)。
|
||||
|
||||
```json
|
||||
{
|
||||
"recursive": {
|
||||
"key_field": "dept_id",
|
||||
"target_param": "parent_id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `key_field` | 当前节点的 ID 字段名 |
|
||||
| `target_param` | 传给下级查询的参数名 |
|
||||
| `max_recursive_depth` | 最大递归深度,默认 20 |
|
||||
|
||||
#### 3.2.8 参数位置控制
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `parameters_location` | `query`=参数放 URL 查询字符串;不配=GET 放 URL,POST 放 Body |
|
||||
|
||||
#### 3.2.9 字段注入 (row_inject)
|
||||
|
||||
如果响应中缺少请求参数中的某些字段,可以用 `row_inject` 注入到每行数据中。
|
||||
|
||||
```json
|
||||
{
|
||||
"row_inject": ["statisticsMonth"]
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 响应配置 (response_config)
|
||||
|
||||
`response_config` 是一个 **JSON 对象**,定义如何解析 API 响应,判断成功/失败,提取数据。
|
||||
|
||||
```json
|
||||
{
|
||||
"success_field": "result",
|
||||
"success_value": 1,
|
||||
"message_field": "error_msg",
|
||||
"list_path": "data.orderList",
|
||||
"cursor_field": "data.cursor",
|
||||
"cursor_end_marker": "nomore",
|
||||
"single_record": false,
|
||||
"has_more_field": "data.hasMore"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 | 默认值 |
|
||||
|------|------|--------|
|
||||
| `success_field` | 判断成功的字段名 | `code` |
|
||||
| `success_value` | 成功的值(数字) | `0` |
|
||||
| `message_field` | 错误消息的字段名 | `message` |
|
||||
| `list_path` | 数据列表的 JSON 路径,如 `data.orderList` | `data` |
|
||||
| `cursor_field` | 游标值的 JSON 路径,如 `data.cursor` | 无 |
|
||||
| `cursor_end_marker` | 游标结束标记,收到此值表示到底 | `nomore` |
|
||||
| `single_record` | `true`=响应是单条记录(详情接口),会自动包装成数组 | `false` |
|
||||
| `has_more_field` | hasMore 分页模式的判断字段路径 | 无 |
|
||||
|
||||
#### 成功判断逻辑
|
||||
|
||||
```
|
||||
读取响应的 success_field 字段 → 转成数字 → 和 success_value 比较
|
||||
相等 = 成功
|
||||
不相等 = 失败,取 message_field 作为错误消息
|
||||
```
|
||||
|
||||
**快手示例**:`result` 字段值 = `1` 表示成功
|
||||
**腾讯示例**:`code` 字段值 = `0` 表示成功
|
||||
|
||||
#### 数据提取逻辑
|
||||
|
||||
```
|
||||
1. 按 list_path 逐层进入 JSON
|
||||
2. 最后一段如果是数组 → 直接作为列表
|
||||
3. 最后一段是对象 → 找对象里的 list/orderList 字段
|
||||
4. 如果 single_record=true → 将对象包装为单元素数组
|
||||
5. 每行数据自动展平(子对象字段合并到顶层)
|
||||
6. 每行附加 raw_data 字段(原始 JSON)
|
||||
```
|
||||
|
||||
### 3.4 表结构定义 (table_definition)
|
||||
|
||||
`table_definition` 是 **JSON 对象**,定义目标表的表名、列结构和冲突处理。
|
||||
|
||||
```json
|
||||
{
|
||||
"table_name": "kuaishou_order_list",
|
||||
"columns": [
|
||||
{ "name": "oid", "type": "BIGINT", "comment": "订单ID" },
|
||||
{ "name": "status", "type": "INT", "comment": "订单状态码" },
|
||||
{ "name": "createTime", "type": "BIGINT", "comment": "创建时间" },
|
||||
{ "name": "updateTime", "type": "BIGINT", "comment": "更新时间" },
|
||||
{ "name": "totalFee", "type": "BIGINT", "comment": "总金额(分)" },
|
||||
{ "name": "buyerNick", "type": "VARCHAR(100)", "comment": "买家昵称" },
|
||||
{ "name": "itemTitle", "type": "VARCHAR(300)", "comment": "商品标题" }
|
||||
],
|
||||
"conflict_keys": ["oid"]
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `table_name` | 数据库表名,系统会自动创建 |
|
||||
| `columns` | 列定义数组 |
|
||||
| `columns[].name` | 列名,**必须与 API 响应中的字段名一致** |
|
||||
| `columns[].type` | PostgreSQL 数据类型:`BIGINT`、`INT`、`VARCHAR(n)`、`TEXT`、`BOOLEAN`、`JSONB` |
|
||||
| `columns[].comment` | 列的注释说明 |
|
||||
| `conflict_keys` | 唯一键,插入时冲突则更新(UPSERT),如 `["oid"]` |
|
||||
|
||||
> **自动建表**:系统首次同步时会自动创建表。
|
||||
> **自动过滤**:只写入 `columns` 中定义的字段,多余的忽略。
|
||||
> **raw_data**:每行会自动附带一个 `raw_data` 字段,保存完整的原始 JSON 响应。
|
||||
|
||||
---
|
||||
|
||||
## 4. 实战:新增一个平台
|
||||
|
||||
以"快手电商"为例,完整步骤:
|
||||
|
||||
### 第 1 步:创建平台配置
|
||||
|
||||
在 `api_datasource_platform` 表插入一条记录:
|
||||
|
||||
```sql
|
||||
INSERT INTO api_datasource_platform (
|
||||
tenant_id, platform_code, platform_name, description, status,
|
||||
api_base_url, auth_type, token, api_key, auth_config,
|
||||
rate_limit_per_minute, request_timeout_ms, max_retries, retry_delay_ms
|
||||
) VALUES (
|
||||
1, -- 租户 ID
|
||||
'kuaishou', -- 平台编码(唯一)
|
||||
'快手电商', -- 名<><E5908D>
|
||||
'快手电商开放平台数据同步',
|
||||
'ACTIVE', -- 状态
|
||||
'https://openapi.kwaixiaodian.com', -- API 域名
|
||||
'API_KEY', -- 认证类型
|
||||
'你的refresh_token', -- Token(OAuth 返回)
|
||||
'你的access_token', -- API Key(OAuth 返回)
|
||||
'{...}', -- auth_config(见 2.3 节)
|
||||
100, 30000, 3, 1000 -- 限流/超时/重试
|
||||
);
|
||||
```
|
||||
|
||||
### 第 2 步:创建接口配置
|
||||
|
||||
对每个需要同步的 API,在 `api_interface` 表插入一条记录:
|
||||
|
||||
```sql
|
||||
INSERT INTO api_interface (
|
||||
tenant_id, platform_id, name, code, url, method, status, auth_type,
|
||||
request_config, response_config, table_definition
|
||||
) VALUES (
|
||||
1,
|
||||
(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),
|
||||
'订单列表', -- 名称
|
||||
'order_list', -- 编码(唯一)
|
||||
'/open/order/cursor/list', -- API 路径
|
||||
'GET', -- HTTP 方法
|
||||
'active', -- 状态
|
||||
'inherit', -- 认证(继承平台)
|
||||
'{...}', -- request_config(见 3.2 节)
|
||||
'{...}', -- response_config(见 3.3 节)
|
||||
'{...}' -- table_definition(见 3.4 节)
|
||||
);
|
||||
```
|
||||
|
||||
### 第 3 步:验证
|
||||
|
||||
系统启动后会自动:
|
||||
1. 根据 `table_definition` 创建数据库表
|
||||
2. 按调度周期拉取数据
|
||||
3. 写入表中
|
||||
|
||||
---
|
||||
|
||||
## 5. 常见问题
|
||||
|
||||
### Q1:怎么判断数据有没有同步成功?
|
||||
|
||||
查看 `sync_tracker` 表,每个接口有一条记录:
|
||||
- `sync_status` = `success` 表示成功
|
||||
- `last_sync_time` = 上次同步的时间戳
|
||||
- 查看 `sync_task_log` 表可以看到每次同步的详细日志
|
||||
|
||||
### Q2:怎么修改同步频率?
|
||||
|
||||
编辑 `config.yml`:
|
||||
```yaml
|
||||
sync:
|
||||
sync_interval_minutes: 60 # 改这个,单位分钟
|
||||
```
|
||||
|
||||
### Q3:接口报"签名校验失败"怎么排查?
|
||||
|
||||
1. 确认 `app_key`、`app_secret`、`sign_secret` 和快手后台一致
|
||||
2. 确认 `access_token` 没有过期
|
||||
3. 确认 `sign_algorithm` 配置正确(快手用 `md5`)
|
||||
4. 查看日志中的 "签名原文" 和 "签名值",对比快手官方签名工具的结果
|
||||
|
||||
### Q4:想抽取历史数据怎么办?
|
||||
|
||||
在接口的 `request_config` 中设置 `full_sync_start_time`(Unix 毫秒时间戳),或修改 `config.yml` 的 `sync.default_lookback_days`。
|
||||
|
||||
### Q5:数据重复怎么办?
|
||||
|
||||
配置 `table_definition` 中的 `conflict_keys`,系统会用 **UPSERT** 模式(冲突时自动更新,不产生重复数据)。
|
||||
|
||||
### Q6:如何停用某个接口?
|
||||
|
||||
```sql
|
||||
UPDATE api_interface SET status = 'inactive' WHERE code = '接口编码';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 📅 最后更新:2026-06-16
|
||||
> 📝 如有疑问,请联系开发团队
|
||||
|
||||
---
|
||||
|
||||
## 6. 同步机制说明
|
||||
|
||||
### 6.1 同步生命周期
|
||||
|
||||
```
|
||||
第 1 次同步(全量)
|
||||
lastSyncTime = 0 → 从 default_lookback_days 天前开始拉取
|
||||
时间分片:每 7 天一个分片(如 90 天 ≈ 13 个分片循环)
|
||||
完成后:记录 lastSyncTime = 数据中最大的更新时间
|
||||
↓
|
||||
第 N 次同步(增量)
|
||||
读取 lastSyncTime → beginTime = lastSyncTime
|
||||
只拉取上次同步之后新增/更新的数据
|
||||
完成后:更新 lastSyncTime
|
||||
↓
|
||||
(每隔 sync_interval_minutes 分钟循环一次)
|
||||
```
|
||||
|
||||
### 6.2 配置项
|
||||
|
||||
编辑 `config.yml`:
|
||||
|
||||
```yaml
|
||||
sync:
|
||||
page_size: 100 # 每次分页请求条数
|
||||
concurrency: 5 # 同步并发数
|
||||
retry_count: 3 # 最大重试次数
|
||||
sync_interval_minutes: 60 # 自动同步间隔(分钟),增量频率
|
||||
compensation_interval_seconds: 300 # 失败重试扫描间隔(秒)
|
||||
auto_sync_enabled: true # 是否启用自动同步
|
||||
sync_timeout_minutes: 120 # 单次同步超时(分钟)
|
||||
default_lookback_days: 89 # 首次全量回溯天数(可按接口覆盖)
|
||||
default_tenant_id: 1 # 租户 ID
|
||||
```
|
||||
|
||||
| 配置项 | 说明 | 建议值 |
|
||||
|--------|------|--------|
|
||||
| `sync_interval_minutes` | 增量同步频率 | 60(1小时) |
|
||||
| `default_lookback_days` | 首次全量拉多少天 | 快手限制 90 天内,建议 89 |
|
||||
| `compensation_interval_seconds` | 失败后多久重试 | 300(5分钟) |
|
||||
| `auto_sync_enabled` | 是否自动跑 | 生产环境 `true` |
|
||||
|
||||
### 6.3 覆盖单个接口的回溯天数
|
||||
|
||||
在接口 `request_config` 中加入 `full_sync_start_time`(Unix **毫秒**)
|
||||
|
||||
```json
|
||||
{ "full_sync_start_time": 1769200000000 }
|
||||
```
|
||||
|
||||
### 6.4 查看同步状态
|
||||
|
||||
```sql
|
||||
-- 所有接口的同步状态
|
||||
SELECT platform_code, interface_code, sync_status,
|
||||
to_timestamp(last_sync_time) AS last_sync_at
|
||||
FROM sync_tracker ORDER BY platform_code, interface_code;
|
||||
|
||||
-- 最近失败记录
|
||||
SELECT * FROM sync_task_log WHERE status = 'failed'
|
||||
ORDER BY start_time DESC LIMIT 20;
|
||||
```
|
||||
|
||||
- `sync_status = 'success'`:上次成功
|
||||
- `sync_status = 'running'`:正在跑
|
||||
- `last_sync_time = 0`:还没完成过同步
|
||||
|
||||
### 6.5 手动重新全量
|
||||
|
||||
把 `last_sync_time` 置 0,下次自动变全量:
|
||||
|
||||
```sql
|
||||
UPDATE sync_tracker SET last_sync_time = 0
|
||||
WHERE platform_code = 'kuaishou' AND interface_code = 'order_list';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 📅 最后更新:2026-06-16
|
||||
@@ -54,6 +54,7 @@ type ListDatasourcePlatformRes struct {
|
||||
|
||||
type DatasourcePlatformItem struct {
|
||||
Id int64 `json:"id,string"`
|
||||
TenantId uint64 `json:"tenantId"`
|
||||
PlatformCode string `json:"platformCode"`
|
||||
PlatformName string `json:"platformName"`
|
||||
Description string `json:"description"`
|
||||
|
||||
@@ -1,52 +1,36 @@
|
||||
package dict
|
||||
|
||||
import (
|
||||
"time"
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// DatasourcePlatform 数据源平台配置实体
|
||||
type DatasourcePlatform struct {
|
||||
// 主键和标识字段
|
||||
ID int64 `orm:"id" json:"id" description:"主键ID,自增长"`
|
||||
PlatformCode string `orm:"platform_code" json:"platformCode" description:"平台编码,唯一标识"`
|
||||
PlatformName string `orm:"platform_name" json:"platformName" description:"平台名称"`
|
||||
Description string `orm:"description" json:"description" description:"平台描述"`
|
||||
|
||||
// 状态和基础配置
|
||||
Status string `orm:"status" json:"status" description:"状态: ACTIVE启用, INACTIVE停用"`
|
||||
ApiBaseUrl string `orm:"api_base_url" json:"apiBaseUrl" description:"API基础地址"`
|
||||
|
||||
// 认证配置
|
||||
AuthType string `orm:"auth_type" json:"authType" description:"认证类型: TOKEN/API_KEY/OAUTH2/BASIC"`
|
||||
Token string `orm:"token" json:"token" description:"认证token/密钥"`
|
||||
ApiKey string `orm:"api_key" json:"apiKey" description:"API Key"`
|
||||
ClientId string `orm:"client_id" json:"clientId" description:"OAuth2 Client ID"`
|
||||
ClientSecret string `orm:"client_secret" json:"clientSecret" description:"OAuth2 Client Secret"`
|
||||
|
||||
// 限流配置
|
||||
RateLimitPerMinute int `orm:"rate_limit_per_minute" json:"rateLimitPerMinute" description:"每分钟请求限制"`
|
||||
RateLimitPerHour int `orm:"rate_limit_per_hour" json:"rateLimitPerHour" description:"每小时请求限制"`
|
||||
ConcurrencyLimit int `orm:"concurrency_limit" json:"concurrencyLimit" description:"并发连接限制"`
|
||||
RequestTimeoutMs int `orm:"request_timeout_ms" json:"requestTimeoutMs" description:"请求超时时间(毫秒)"`
|
||||
|
||||
// 重试策略
|
||||
MaxRetries int `orm:"max_retries" json:"maxRetries" description:"最大重试次数"`
|
||||
RetryDelayMs int `orm:"retry_delay_ms" json:"retryDelayMs" description:"重试延迟(毫秒)"`
|
||||
|
||||
// 自定义认证配置 (JSONB)
|
||||
AuthConfig map[string]interface{} `orm:"auth_config" json:"authConfig" description:"自定义认证配置,支持各平台特有的认证方式"`
|
||||
|
||||
// 元数据(ORM tag 与数据库列名一致:creator/updater)
|
||||
CreatedBy string `orm:"creator" json:"createdBy" description:"创建人"`
|
||||
CreatedAt *time.Time `orm:"created_at" json:"createdAt" description:"创建时间"`
|
||||
UpdatedBy string `orm:"updater" json:"updatedBy" description:"更新人"`
|
||||
UpdatedAt *time.Time `orm:"updated_at" json:"updatedAt" description:"更新时间"`
|
||||
Version int `orm:"version" json:"version" description:"版本号(乐观锁)"`
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
PlatformCode string `orm:"platform_code" json:"platformCode" description:"平台编码,唯一标识"`
|
||||
PlatformName string `orm:"platform_name" json:"platformName" description:"平台名称"`
|
||||
Description string `orm:"description" json:"description" description:"平台描述"`
|
||||
Status string `orm:"status" json:"status" description:"状态: ACTIVE启用, INACTIVE停用"`
|
||||
ApiBaseUrl string `orm:"api_base_url" json:"apiBaseUrl" description:"API基础地址"`
|
||||
AuthType string `orm:"auth_type" json:"authType" description:"认证类型: TOKEN/API_KEY/OAUTH2/BASIC"`
|
||||
Token string `orm:"token" json:"token" description:"认证token/密钥"`
|
||||
ApiKey string `orm:"api_key" json:"apiKey" description:"API Key"`
|
||||
ClientId string `orm:"client_id" json:"clientId" description:"OAuth2 Client ID"`
|
||||
ClientSecret string `orm:"client_secret" json:"clientSecret" description:"OAuth2 Client Secret"`
|
||||
RateLimitPerMinute int `orm:"rate_limit_per_minute" json:"rateLimitPerMinute" description:"每分钟请求限制"`
|
||||
RateLimitPerHour int `orm:"rate_limit_per_hour" json:"rateLimitPerHour" description:"每小时请求限制"`
|
||||
ConcurrencyLimit int `orm:"concurrency_limit" json:"concurrencyLimit" description:"并发连接限制"`
|
||||
RequestTimeoutMs int `orm:"request_timeout_ms" json:"requestTimeoutMs" description:"请求超时时间(毫秒)"`
|
||||
MaxRetries int `orm:"max_retries" json:"maxRetries" description:"最大重试次数"`
|
||||
RetryDelayMs int `orm:"retry_delay_ms" json:"retryDelayMs" description:"重试延迟(毫秒)"`
|
||||
AuthConfig map[string]interface{} `orm:"auth_config" json:"authConfig" description:"自定义认证配置,支持各平台特有的认证方式"`
|
||||
Version int `orm:"version" json:"version" description:"版本号(乐观锁)"`
|
||||
}
|
||||
|
||||
// DatasourcePlatformCol 数据源平台配置表字段定义
|
||||
type DatasourcePlatformCol struct {
|
||||
ID string
|
||||
beans.SQLBaseCol
|
||||
PlatformCode string
|
||||
PlatformName string
|
||||
Description string
|
||||
@@ -64,16 +48,12 @@ type DatasourcePlatformCol struct {
|
||||
MaxRetries string
|
||||
RetryDelayMs string
|
||||
AuthConfig string
|
||||
CreatedBy string // 对应 DB 列 creator
|
||||
CreatedAt string
|
||||
UpdatedBy string // 对应 DB 列 updater
|
||||
UpdatedAt string
|
||||
Version string
|
||||
}
|
||||
|
||||
// DatasourcePlatformCols 数据源平台配置表字段常量
|
||||
var DatasourcePlatformCols = DatasourcePlatformCol{
|
||||
ID: "id",
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
PlatformCode: "platform_code",
|
||||
PlatformName: "platform_name",
|
||||
Description: "description",
|
||||
@@ -91,9 +71,5 @@ var DatasourcePlatformCols = DatasourcePlatformCol{
|
||||
MaxRetries: "max_retries",
|
||||
RetryDelayMs: "retry_delay_ms",
|
||||
AuthConfig: "auth_config",
|
||||
CreatedBy: "creator",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedBy: "updater",
|
||||
UpdatedAt: "updated_at",
|
||||
Version: "version",
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
dto "dataengine/model/dto/dict"
|
||||
entity "dataengine/model/entity/dict"
|
||||
"dataengine/utils"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/olekukonko/errors"
|
||||
)
|
||||
@@ -20,8 +20,10 @@ var DatasourcePlatform = new(datasourcePlatformService)
|
||||
|
||||
// Create 创建数据源平台
|
||||
func (s *datasourcePlatformService) Create(ctx context.Context, req *dto.CreateDatasourcePlatformReq) (res *dto.CreateDatasourcePlatformRes, err error) {
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
|
||||
// 检查平台编码是否重复
|
||||
exists, err := dao.DatasourcePlatform.ExistsByPlatformCode(ctx, req.PlatformCode)
|
||||
exists, err := dao.DatasourcePlatform.ExistsByPlatformCode(ctx, req.PlatformCode, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,7 +40,7 @@ func (s *datasourcePlatformService) Create(ctx context.Context, req *dto.CreateD
|
||||
currentUser := utils.GetCurrentUser(ctx)
|
||||
|
||||
// 插入数据库
|
||||
id, err := dao.DatasourcePlatform.Insert(ctx, req, currentUser)
|
||||
id, err := dao.DatasourcePlatform.Insert(ctx, req, currentUser, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -51,7 +53,8 @@ func (s *datasourcePlatformService) Create(ctx context.Context, req *dto.CreateD
|
||||
|
||||
// List 获取数据源平台列表
|
||||
func (s *datasourcePlatformService) List(ctx context.Context, req *dto.ListDatasourcePlatformReq) (res *dto.ListDatasourcePlatformRes, err error) {
|
||||
platformList, total, err := dao.DatasourcePlatform.List(ctx, req)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
platformList, total, err := dao.DatasourcePlatform.List(ctx, req, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,7 +63,8 @@ func (s *datasourcePlatformService) List(ctx context.Context, req *dto.ListDatas
|
||||
list := make([]dto.DatasourcePlatformItem, 0, len(platformList))
|
||||
for _, item := range platformList {
|
||||
list = append(list, dto.DatasourcePlatformItem{
|
||||
Id: item.ID,
|
||||
Id: item.Id,
|
||||
TenantId: item.TenantId,
|
||||
PlatformCode: item.PlatformCode,
|
||||
PlatformName: item.PlatformName,
|
||||
Description: item.Description,
|
||||
@@ -75,9 +79,9 @@ func (s *datasourcePlatformService) List(ctx context.Context, req *dto.ListDatas
|
||||
RequestTimeoutMs: item.RequestTimeoutMs,
|
||||
MaxRetries: item.MaxRetries,
|
||||
RetryDelayMs: item.RetryDelayMs,
|
||||
CreatedBy: item.CreatedBy,
|
||||
CreatedBy: item.Creator,
|
||||
CreatedAt: s.safeUnix(item.CreatedAt),
|
||||
UpdatedBy: item.UpdatedBy,
|
||||
UpdatedBy: item.Updater,
|
||||
UpdatedAt: s.safeUnix(item.UpdatedAt),
|
||||
})
|
||||
}
|
||||
@@ -91,7 +95,8 @@ func (s *datasourcePlatformService) List(ctx context.Context, req *dto.ListDatas
|
||||
|
||||
// GetOne 获取单个数据源平台
|
||||
func (s *datasourcePlatformService) GetOne(ctx context.Context, req *dto.GetDatasourcePlatformReq) (res *dto.GetDatasourcePlatformRes, err error) {
|
||||
platform, err := dao.DatasourcePlatform.GetOne(ctx, req)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
platform, err := dao.DatasourcePlatform.GetOne(ctx, req, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -104,10 +109,9 @@ func (s *datasourcePlatformService) GetOne(ctx context.Context, req *dto.GetData
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 隐藏敏感信息
|
||||
platformEntity.Token = ""
|
||||
platformEntity.ClientSecret = ""
|
||||
platformEntity.ApiKey = ""
|
||||
// 注意:编辑时需要保留 Token/ClientSecret/ApiKey,否则前端表单回填为空,
|
||||
// 用户保存时会覆盖已有凭据。敏感字段在 List 接口的 DatasourcePlatformItem 中
|
||||
// 已不返回(DTO 不含这些字段),此处保持完整。
|
||||
|
||||
return &dto.GetDatasourcePlatformRes{
|
||||
DatasourcePlatform: platformEntity,
|
||||
@@ -116,7 +120,8 @@ func (s *datasourcePlatformService) GetOne(ctx context.Context, req *dto.GetData
|
||||
|
||||
// GetByPlatformCode 根据平台编码获取数据源平台
|
||||
func (s *datasourcePlatformService) GetByPlatformCode(ctx context.Context, platformCode string) (res *entity.DatasourcePlatform, err error) {
|
||||
platform, err := dao.DatasourcePlatform.GetByPlatformCode(ctx, platformCode)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
platform, err := dao.DatasourcePlatform.GetByPlatformCode(ctx, platformCode, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -128,8 +133,10 @@ func (s *datasourcePlatformService) GetByPlatformCode(ctx context.Context, platf
|
||||
|
||||
// Update 更新数据源平台
|
||||
func (s *datasourcePlatformService) Update(ctx context.Context, req *dto.UpdateDatasourcePlatformReq) (err error) {
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
|
||||
// 检查平台是否存在
|
||||
exist, err := dao.DatasourcePlatform.GetOne(ctx, &dto.GetDatasourcePlatformReq{Id: req.Id})
|
||||
exist, err := dao.DatasourcePlatform.GetOne(ctx, &dto.GetDatasourcePlatformReq{Id: req.Id}, tenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -139,7 +146,7 @@ func (s *datasourcePlatformService) Update(ctx context.Context, req *dto.UpdateD
|
||||
|
||||
// 如果修改了平台编码,检查新编码是否重复
|
||||
if req.PlatformCode != "" && req.PlatformCode != exist.PlatformCode {
|
||||
exists, err := dao.DatasourcePlatform.ExistsByPlatformCode(ctx, req.PlatformCode, req.Id)
|
||||
exists, err := dao.DatasourcePlatform.ExistsByPlatformCode(ctx, req.PlatformCode, tenantId, req.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -164,14 +171,16 @@ func (s *datasourcePlatformService) Update(ctx context.Context, req *dto.UpdateD
|
||||
|
||||
// 从 context 中获取当前用户
|
||||
currentUser := utils.GetCurrentUser(ctx)
|
||||
_, err = dao.DatasourcePlatform.Update(ctx, req, currentUser)
|
||||
_, err = dao.DatasourcePlatform.Update(ctx, req, currentUser, tenantId)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateStatus 更新数据源平台状态
|
||||
func (s *datasourcePlatformService) UpdateStatus(ctx context.Context, req *dto.UpdateDatasourcePlatformStatusReq) (err error) {
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
|
||||
// 检查平台是否存在
|
||||
exist, err := dao.DatasourcePlatform.GetOne(ctx, &dto.GetDatasourcePlatformReq{Id: req.Id})
|
||||
exist, err := dao.DatasourcePlatform.GetOne(ctx, &dto.GetDatasourcePlatformReq{Id: req.Id}, tenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -185,14 +194,16 @@ func (s *datasourcePlatformService) UpdateStatus(ctx context.Context, req *dto.U
|
||||
}
|
||||
|
||||
currentUser := utils.GetCurrentUser(ctx)
|
||||
_, err = dao.DatasourcePlatform.UpdateStatus(ctx, req.Id, req.Status.String(), currentUser)
|
||||
_, err = dao.DatasourcePlatform.UpdateStatus(ctx, req.Id, req.Status.String(), currentUser, tenantId)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete 删除数据源平台
|
||||
func (s *datasourcePlatformService) Delete(ctx context.Context, req *dto.DeleteDatasourcePlatformReq) (err error) {
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
|
||||
// 检查平台是否存在
|
||||
exist, err := dao.DatasourcePlatform.GetOne(ctx, &dto.GetDatasourcePlatformReq{Id: req.Id})
|
||||
exist, err := dao.DatasourcePlatform.GetOne(ctx, &dto.GetDatasourcePlatformReq{Id: req.Id}, tenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -203,13 +214,14 @@ func (s *datasourcePlatformService) Delete(ctx context.Context, req *dto.DeleteD
|
||||
// TODO: 检查是否存在关联的数据,防止误删
|
||||
// 例如:检查该平台是否有关联的接口配置等
|
||||
|
||||
_, err = dao.DatasourcePlatform.Delete(ctx, req)
|
||||
_, err = dao.DatasourcePlatform.Delete(ctx, req, tenantId)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetStatistics 获取平台统计信息
|
||||
func (s *datasourcePlatformService) GetStatistics(ctx context.Context) (res *dto.GetPlatformStatisticsRes, err error) {
|
||||
stats, err := dao.DatasourcePlatform.GetPlatformStatistics(ctx)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
stats, err := dao.DatasourcePlatform.GetPlatformStatistics(ctx, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -228,7 +240,8 @@ func (s *datasourcePlatformService) GetStatistics(ctx context.Context) (res *dto
|
||||
|
||||
// ListActivePlatforms 获取所有启用的平台
|
||||
func (s *datasourcePlatformService) ListActivePlatforms(ctx context.Context) (platforms []entity.DatasourcePlatform, err error) {
|
||||
return dao.DatasourcePlatform.ListActivePlatforms(ctx)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
return dao.DatasourcePlatform.ListActivePlatforms(ctx, tenantId)
|
||||
}
|
||||
|
||||
// validateAuthFields 验证认证类型相关的必填字段
|
||||
@@ -292,8 +305,8 @@ func (s *datasourcePlatformService) getAuthTypeName(authType string) string {
|
||||
return authType
|
||||
}
|
||||
|
||||
// safeUnix 安全地从 *time.Time 获取 Unix 时间戳,nil 返回 0
|
||||
func (s *datasourcePlatformService) safeUnix(t *time.Time) int64 {
|
||||
// safeUnix 安全地从 *gtime.Time 获取 Unix 时间戳,nil 返回 0
|
||||
func (s *datasourcePlatformService) safeUnix(t *gtime.Time) int64 {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
@@ -301,11 +314,13 @@ func (s *datasourcePlatformService) safeUnix(t *time.Time) int64 {
|
||||
}
|
||||
|
||||
// BatchUpdateStatus 批量更新平台状态
|
||||
func (s *datasourcePlatformService) BatchUpdateStatus(ctx context.Context, ids []int64, status string, updatedBy string) (err error) {
|
||||
func (s *datasourcePlatformService) BatchUpdateStatus(ctx context.Context, ids []int64, status string) (err error) {
|
||||
if len(ids) == 0 {
|
||||
return errors.New("请选择要更新的平台")
|
||||
}
|
||||
|
||||
_, err = dao.DatasourcePlatform.BatchUpdateStatus(ctx, ids, status, updatedBy)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
currentUser := utils.GetCurrentUser(ctx)
|
||||
_, err = dao.DatasourcePlatform.BatchUpdateStatus(ctx, ids, status, currentUser, tenantId)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ var ApiInterface = new(apiInterfaceService)
|
||||
|
||||
// Create 创建接口
|
||||
func (s *apiInterfaceService) Create(ctx context.Context, req *dto.CreateApiInterfaceReq) (res *dto.CreateApiInterfaceRes, err error) {
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
|
||||
_, err = DatasourcePlatform.GetOne(ctx, &dto.GetDatasourcePlatformReq{Id: req.PlatformId})
|
||||
if err != nil {
|
||||
return nil, errors.New("平台不存在")
|
||||
@@ -28,7 +30,7 @@ func (s *apiInterfaceService) Create(ctx context.Context, req *dto.CreateApiInte
|
||||
interfaces, _, err := dict.ApiInterface.List(ctx, &dto.ListApiInterfaceReq{
|
||||
PlatformId: req.PlatformId,
|
||||
Code: req.Code,
|
||||
})
|
||||
}, tenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -40,7 +42,7 @@ func (s *apiInterfaceService) Create(ctx context.Context, req *dto.CreateApiInte
|
||||
currentUser := utils.GetCurrentUser(ctx)
|
||||
|
||||
// 插入数据库
|
||||
id, err := dict.ApiInterface.Insert(ctx, req, currentUser)
|
||||
id, err := dict.ApiInterface.Insert(ctx, req, currentUser, tenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -52,7 +54,8 @@ func (s *apiInterfaceService) Create(ctx context.Context, req *dto.CreateApiInte
|
||||
|
||||
// List 获取接口列表
|
||||
func (s *apiInterfaceService) List(ctx context.Context, req *dto.ListApiInterfaceReq) (res *dto.ListApiInterfaceRes, err error) {
|
||||
apiList, total, err := dict.ApiInterface.List(ctx, req)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
apiList, total, err := dict.ApiInterface.List(ctx, req, tenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -107,7 +110,8 @@ func (s *apiInterfaceService) List(ctx context.Context, req *dto.ListApiInterfac
|
||||
|
||||
// GetOne 获取单个接口
|
||||
func (s *apiInterfaceService) GetOne(ctx context.Context, req *dto.GetApiInterfaceReq) (res *dto.GetApiInterfaceRes, err error) {
|
||||
apiInterface, err := dict.ApiInterface.GetOne(ctx, req)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
apiInterface, err := dict.ApiInterface.GetOne(ctx, req, tenantId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -128,7 +132,9 @@ func (s *apiInterfaceService) GetOne(ctx context.Context, req *dto.GetApiInterfa
|
||||
|
||||
// Update 更新接口
|
||||
func (s *apiInterfaceService) Update(ctx context.Context, req *dto.UpdateApiInterfaceReq) (err error) {
|
||||
exist, err := dict.ApiInterface.GetOne(ctx, &dto.GetApiInterfaceReq{Id: req.Id})
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
|
||||
exist, err := dict.ApiInterface.GetOne(ctx, &dto.GetApiInterfaceReq{Id: req.Id}, tenantId)
|
||||
if err != nil || exist == nil {
|
||||
return errors.New("接口不存在")
|
||||
}
|
||||
@@ -148,7 +154,7 @@ func (s *apiInterfaceService) Update(ctx context.Context, req *dto.UpdateApiInte
|
||||
interfaces, _, err := dict.ApiInterface.List(ctx, &dto.ListApiInterfaceReq{
|
||||
PlatformId: platformId,
|
||||
Code: req.Code,
|
||||
})
|
||||
}, tenantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -158,26 +164,29 @@ func (s *apiInterfaceService) Update(ctx context.Context, req *dto.UpdateApiInte
|
||||
}
|
||||
|
||||
currentUser := utils.GetCurrentUser(ctx)
|
||||
_, err = dict.ApiInterface.Update(ctx, req, currentUser)
|
||||
_, err = dict.ApiInterface.Update(ctx, req, currentUser, tenantId)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus 更新接口状态
|
||||
func (s *apiInterfaceService) UpdateStatus(ctx context.Context, req *dto.UpdateApiInterfaceStatusReq) (err error) {
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
currentUser := utils.GetCurrentUser(ctx)
|
||||
_, err = dict.ApiInterface.UpdateStatus(ctx, req.Id, req.Status.String(), currentUser)
|
||||
_, err = dict.ApiInterface.UpdateStatus(ctx, req.Id, req.Status.String(), currentUser, tenantId)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除接口
|
||||
func (s *apiInterfaceService) Delete(ctx context.Context, req *dto.DeleteApiInterfaceReq) (err error) {
|
||||
_, err = dict.ApiInterface.Delete(ctx, req)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
_, err = dict.ApiInterface.Delete(ctx, req, tenantId)
|
||||
return
|
||||
}
|
||||
|
||||
// GetByIds 根据ID列表获取接口
|
||||
func (s *apiInterfaceService) GetByIds(ctx context.Context, ids []int64) (res []entity.ApiInterface, err error) {
|
||||
return dict.ApiInterface.GetByIds(ctx, ids)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
return dict.ApiInterface.GetByIds(ctx, ids, tenantId)
|
||||
}
|
||||
|
||||
// getStatusName 获取状态名称
|
||||
|
||||
@@ -152,7 +152,7 @@ func (s *publicQueryService) GetTableList(ctx context.Context) (*public.TableLis
|
||||
_ = gfdb.DB(ctx).Model(ctx, "api_datasource_platform").Scan(&platforms)
|
||||
platformMap := make(map[int64]string)
|
||||
for _, p := range platforms {
|
||||
platformMap[p.ID] = p.PlatformName
|
||||
platformMap[p.Id] = p.PlatformName
|
||||
}
|
||||
|
||||
var list []public.TableInfo
|
||||
|
||||
@@ -78,13 +78,7 @@ func (c *ApiClient) Close() {
|
||||
|
||||
// Request 通用请求方法(支持 GET/POST,支持参数在 query 或 body)
|
||||
func (c *ApiClient) Request(ctx context.Context, method, path string, params map[string]interface{}, paramsInQuery bool) (*ApiResult, error) {
|
||||
if paramsInQuery {
|
||||
return c.doRequest(ctx, method, path, params, true)
|
||||
}
|
||||
if method == "GET" {
|
||||
return c.doRequest(ctx, "GET", path, params, true)
|
||||
}
|
||||
return c.doRequest(ctx, method, path, params, false)
|
||||
return c.doRequest(ctx, method, path, params, paramsInQuery)
|
||||
}
|
||||
|
||||
func (c *ApiClient) doRequest(ctx context.Context, method, path string, body interface{}, paramsInQuery bool) (result *ApiResult, err error) {
|
||||
@@ -124,30 +118,76 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
|
||||
start := time.Now()
|
||||
fullURL := c.config.GetApiUrl(path)
|
||||
|
||||
// 先注入认证参数
|
||||
// 先注入认证参数到 URL
|
||||
fullURL = c.applyAuthURL(fullURL)
|
||||
|
||||
// 将 URL 认证参数注入 body 并清除 URL(避免重复参数)
|
||||
var reqBody io.Reader
|
||||
var reqBodyBytes []byte
|
||||
if body != nil && !paramsInQuery {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JSON序列化请求体失败: %w", err)
|
||||
if paramsMap, ok := body.(map[string]interface{}); ok {
|
||||
// 从 URL 注入认证参数到 body
|
||||
if parsed, _ := url.Parse(fullURL); parsed != nil {
|
||||
q := parsed.Query()
|
||||
for k, vs := range q {
|
||||
if len(vs) > 0 {
|
||||
if _, exists := paramsMap[k]; !exists {
|
||||
paramsMap[k] = vs[0]
|
||||
}
|
||||
q.Del(k)
|
||||
}
|
||||
}
|
||||
parsed.RawQuery = q.Encode()
|
||||
fullURL = parsed.String()
|
||||
}
|
||||
// Form body
|
||||
formStr := c.buildFormBody(paramsMap)
|
||||
reqBodyBytes = []byte(formStr)
|
||||
reqBody = strings.NewReader(formStr)
|
||||
} else {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("JSON序列化请求体失败: %w", err)
|
||||
}
|
||||
reqBodyBytes = b
|
||||
reqBody = bytes.NewBuffer(b)
|
||||
}
|
||||
reqBodyBytes = b
|
||||
reqBody = bytes.NewBuffer(b)
|
||||
}
|
||||
|
||||
// 如果参数在查询字符串中,拼接到 URL
|
||||
// GET query 模式
|
||||
if body != nil && paramsInQuery {
|
||||
if paramsMap, ok := body.(map[string]interface{}); ok {
|
||||
fullURL = c.buildQueryURL(fullURL, paramsMap)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算签名并追加(如快手 API 的 MD5 签名)
|
||||
// 计算固定签名
|
||||
fullURL = c.applySignature(fullURL, body, paramsInQuery)
|
||||
logrus.Infof("请求 URL: %s", fullURL)
|
||||
|
||||
// 将 sign 注入 body 并从 URL 清除
|
||||
if !paramsInQuery && reqBodyBytes != nil {
|
||||
if parsed, _ := url.Parse(fullURL); parsed != nil {
|
||||
if signVal := parsed.Query().Get("sign"); signVal != "" {
|
||||
reqBodyBytes = append(reqBodyBytes, []byte("&sign="+signVal)...)
|
||||
reqBody = bytes.NewReader(reqBodyBytes)
|
||||
q := parsed.Query()
|
||||
q.Del("sign")
|
||||
parsed.RawQuery = q.Encode()
|
||||
fullURL = parsed.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 打印等效 curl
|
||||
curlCmd := fmt.Sprintf("curl -X %s '%s'", method, fullURL)
|
||||
if reqBodyBytes != nil && len(reqBodyBytes) > 0 {
|
||||
for _, pair := range strings.Split(string(reqBodyBytes), "&") {
|
||||
if pair != "" {
|
||||
curlCmd += fmt.Sprintf(" --data-urlencode '%s'", pair)
|
||||
}
|
||||
}
|
||||
}
|
||||
logrus.Infof("等效curl: %s", curlCmd)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, fullURL, reqBody)
|
||||
if err != nil {
|
||||
@@ -157,7 +197,11 @@ func (c *ApiClient) execute(ctx context.Context, method, path string, body inter
|
||||
c.applyAuthHeader(req, reqBodyBytes)
|
||||
req.Header.Set("User-Agent", "data-engine/1.0")
|
||||
if body != nil && !paramsInQuery {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
if _, ok := body.(map[string]interface{}); ok {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
} else {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
@@ -224,6 +268,28 @@ func (c *ApiClient) buildQueryURL(rawURL string, params map[string]interface{})
|
||||
return parsed.String()
|
||||
}
|
||||
|
||||
// buildFormBody 将 params 编码为 application/x-www-form-urlencoded 字符串
|
||||
func (c *ApiClient) buildFormBody(params map[string]interface{}) string {
|
||||
q := make(url.Values)
|
||||
for k, v := range params {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
q.Set(k, val)
|
||||
case float64:
|
||||
if val == float64(int64(val)) {
|
||||
q.Set(k, fmt.Sprintf("%d", int64(val)))
|
||||
} else {
|
||||
q.Set(k, fmt.Sprintf("%v", val))
|
||||
}
|
||||
case int, int8, int16, int32, int64:
|
||||
q.Set(k, fmt.Sprintf("%d", val))
|
||||
default:
|
||||
q.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
return q.Encode()
|
||||
}
|
||||
|
||||
func (c *ApiClient) applyAuthURL(rawURL string) string {
|
||||
cfg := c.config.AuthConfig
|
||||
token := c.config.AccessToken
|
||||
@@ -264,6 +330,10 @@ func (c *ApiClient) applyAuthURL(rawURL string) string {
|
||||
for k, v := range extraParams {
|
||||
q.Set(k, v)
|
||||
}
|
||||
// 注入 appkey
|
||||
if appKey, ok := cfg["app_key"].(string); ok && appKey != "" {
|
||||
q.Set("appkey", appKey)
|
||||
}
|
||||
parsed.RawQuery = q.Encode()
|
||||
return parsed.String()
|
||||
}
|
||||
@@ -379,7 +449,8 @@ func generateNonce() string {
|
||||
return fmt.Sprintf("%012d%04d", nanoPart, r.Int64())
|
||||
}
|
||||
|
||||
// applySignature 计算签名并追加到 URL(支持快手等平台的 MD5 签名)
|
||||
// applySignature 计算签名并追加到 URL
|
||||
// 快手签名: 字母序拼接 key=value&...&signSecret=<secret>, 取 MD5
|
||||
func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuery bool) string {
|
||||
cfg := c.config.AuthConfig
|
||||
if cfg == nil {
|
||||
@@ -390,11 +461,16 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
|
||||
if signAlgo == "" {
|
||||
return rawURL
|
||||
}
|
||||
appSecret, _ := cfg["app_secret"].(string)
|
||||
if appSecret == "" && c.config.AppSecret != "" {
|
||||
appSecret = c.config.AppSecret
|
||||
|
||||
// 获取 signSecret(签名专用密钥)
|
||||
signSecret, _ := cfg["sign_secret"].(string)
|
||||
if signSecret == "" {
|
||||
signSecret, _ = cfg["app_secret"].(string)
|
||||
}
|
||||
if appSecret == "" {
|
||||
if signSecret == "" && c.config.AppSecret != "" {
|
||||
signSecret = c.config.AppSecret
|
||||
}
|
||||
if signSecret == "" {
|
||||
return rawURL
|
||||
}
|
||||
|
||||
@@ -405,7 +481,16 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
|
||||
}
|
||||
q := parsed.Query()
|
||||
|
||||
// 收集所有参数并按 key 排序
|
||||
// POST: 合并 body 参数
|
||||
if !paramsInQuery {
|
||||
if bodyMap, ok := body.(map[string]interface{}); ok {
|
||||
for k, v := range bodyMap {
|
||||
q.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收集参数(排除 sign),按 key 排序
|
||||
keys := make([]string, 0, len(q))
|
||||
for k := range q {
|
||||
if k == "sign" {
|
||||
@@ -415,12 +500,20 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
// 拼接: key1=value1&key2=value2&...
|
||||
var signStr string
|
||||
for _, k := range keys {
|
||||
signStr += k + "=" + q.Get(k) + "&"
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
signStr += "&"
|
||||
}
|
||||
signStr += k + "=" + q.Get(k)
|
||||
}
|
||||
signStr += "key=" + appSecret
|
||||
// 追加 signSecret
|
||||
signStr += "&signSecret=" + signSecret
|
||||
|
||||
logrus.Infof("签名原文: %s", signStr)
|
||||
|
||||
// 计算签名
|
||||
var sign string
|
||||
switch signAlgo {
|
||||
case "md5":
|
||||
@@ -432,6 +525,7 @@ func (c *ApiClient) applySignature(rawURL string, body interface{}, paramsInQuer
|
||||
default:
|
||||
return rawURL
|
||||
}
|
||||
logrus.Infof("签名值 sign=%s", sign)
|
||||
|
||||
q.Set("sign", sign)
|
||||
parsed.RawQuery = q.Encode()
|
||||
|
||||
@@ -132,13 +132,65 @@ func paramsInQuery(iface *entity.ApiInterface) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// pageResult 单页处理结果
|
||||
type pageResult struct {
|
||||
raw []byte // 原始响应体(hasMore 分页需用于判断是否还有下一页)
|
||||
rows []map[string]interface{}
|
||||
maxTime int64
|
||||
nextCursor string
|
||||
inserted int
|
||||
err error
|
||||
}
|
||||
|
||||
// processPage 处理单页数据:请求 → 解析 → 注入
|
||||
func processPage(ctx context.Context, api *ApiClient, iface *entity.ApiInterface,
|
||||
method string, inQuery bool, page, pageSize int, lastSyncTime int64,
|
||||
extraParams map[string]interface{}) pageResult {
|
||||
|
||||
body := buildReqBody(ctx, iface, page, pageSize, lastSyncTime, extraParams)
|
||||
resp, err := api.Request(ctx, method, iface.Url, body, inQuery)
|
||||
if err != nil {
|
||||
return pageResult{err: fmt.Errorf("请求失败: %w", err)}
|
||||
}
|
||||
|
||||
rows, _, maxTime, nextCursor, err := parseRespExt(resp.Body, iface.ResponseConfig)
|
||||
if err != nil {
|
||||
return pageResult{err: fmt.Errorf("解析失败: %w", err)}
|
||||
}
|
||||
|
||||
injectRowFields(rows, body, iface.RequestConfig)
|
||||
return pageResult{
|
||||
raw: resp.Body,
|
||||
rows: rows,
|
||||
maxTime: maxTime,
|
||||
nextCursor: nextCursor,
|
||||
}
|
||||
}
|
||||
|
||||
// accumPage 将单页结果累加到 SyncResult
|
||||
func accumPage(result *SyncResult, maxTime *int64, td *TableDefinition,
|
||||
ctx context.Context, pr pageResult) {
|
||||
|
||||
if len(pr.rows) == 0 {
|
||||
return
|
||||
}
|
||||
inserted, _ := savePage(ctx, td, pr.rows)
|
||||
result.InsertedRows += inserted
|
||||
result.TotalRows += len(pr.rows)
|
||||
if pr.maxTime > *maxTime {
|
||||
*maxTime = pr.maxTime
|
||||
}
|
||||
result.TotalPages++
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// syncSingleAPI 单接口分页同步
|
||||
func syncSingleAPI(ctx context.Context, api *ApiClient, platform *PlatformConfig, iface *entity.ApiInterface, td *TableDefinition, isFullSync bool, lastSyncTime int64, start time.Time) (*SyncResult, error) {
|
||||
pageSize := GetSyncPageSize(ctx)
|
||||
if ps, ok := iface.RequestConfig["page_size"].(float64); ok {
|
||||
pageSize = int(ps)
|
||||
} else if ps, ok := iface.RequestConfig["pageSize"].(float64); ok {
|
||||
pageSize = int(ps)
|
||||
if ps, ok := toInt(iface.RequestConfig["page_size"]); ok {
|
||||
pageSize = ps
|
||||
} else if ps, ok := toInt(iface.RequestConfig["pageSize"]); ok {
|
||||
pageSize = ps
|
||||
}
|
||||
|
||||
taskType := "incremental"
|
||||
@@ -149,159 +201,123 @@ func syncSingleAPI(ctx context.Context, api *ApiClient, platform *PlatformConfig
|
||||
inQuery := paramsInQuery(iface)
|
||||
method := string(iface.Method)
|
||||
|
||||
// 游标分页首次请求需要处理初始游标值
|
||||
firstExtra := map[string]interface{}{}
|
||||
if isCursorPagination(iface) {
|
||||
cp := "cursor"
|
||||
if p, ok := iface.RequestConfig["page_param"].(string); ok && p != "" {
|
||||
cp = p
|
||||
}
|
||||
// 支持 initial_cursor 配置(如钉钉HRM首次传 0)
|
||||
if icv, ok := iface.RequestConfig["initial_cursor"]; ok {
|
||||
firstExtra[cp] = icv
|
||||
} else {
|
||||
firstExtra[cp] = ""
|
||||
}
|
||||
}
|
||||
body := buildReqBody(iface, 1, pageSize, lastSyncTime, firstExtra)
|
||||
resp, err := api.Request(ctx, method, iface.Url, body, inQuery)
|
||||
if err != nil {
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, err.Error())
|
||||
return nil, fmt.Errorf("获取第一页失败: %w", err)
|
||||
// 游标参数名
|
||||
cursorParam := "cursor"
|
||||
if p, ok := iface.RequestConfig["page_param"].(string); ok && p != "" {
|
||||
cursorParam = p
|
||||
}
|
||||
cursorMode := isCursorPagination(iface)
|
||||
|
||||
rows, totalPages, maxTime, nextCursor, err := parseRespExt(resp.Body, iface.ResponseConfig)
|
||||
if err != nil {
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, fmt.Sprintf("解析第一页响应失败: %v", err))
|
||||
return nil, err
|
||||
}
|
||||
// 检测 range 模式(快手时间分片),按7天分片循环拉取
|
||||
timeMode, _ := iface.RequestConfig["time_field_mode"].(string)
|
||||
isRangeMode := timeMode == "range"
|
||||
|
||||
injectRowFields(rows, body, iface.RequestConfig)
|
||||
|
||||
result := &SyncResult{TableName: td.TableName, TotalPages: totalPages}
|
||||
inserted, _ := savePage(ctx, td, rows)
|
||||
result.InsertedRows += inserted
|
||||
result.TotalRows += len(rows)
|
||||
|
||||
// 游标分页
|
||||
if isCursorPagination(iface) {
|
||||
for nextCursor != "" && nextCursor != "nomore" {
|
||||
cp := "cursor"
|
||||
if p, ok := iface.RequestConfig["page_param"].(string); ok && p != "" {
|
||||
cp = p
|
||||
}
|
||||
body := buildReqBody(iface, 1, pageSize, lastSyncTime, map[string]interface{}{
|
||||
cp: nextCursor,
|
||||
})
|
||||
|
||||
resp, err := api.Request(ctx, method, iface.Url, body, inQuery)
|
||||
if err != nil {
|
||||
logrus.Errorf("游标 %s 请求失败: %v", nextCursor, err)
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, fmt.Sprintf("游标 %s 请求失败: %v", nextCursor, err))
|
||||
break
|
||||
}
|
||||
|
||||
rows, _, mt, nc, pe := parseRespExt(resp.Body, iface.ResponseConfig)
|
||||
if pe != nil {
|
||||
logrus.Errorf("游标 %s 解析失败: %v", nextCursor, pe)
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, fmt.Sprintf("游标 %s 解析失败: %v", nextCursor, pe))
|
||||
break
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
break
|
||||
}
|
||||
nextCursor = nc
|
||||
|
||||
injectRowFields(rows, body, iface.RequestConfig)
|
||||
inserted, _ = savePage(ctx, td, rows)
|
||||
result.InsertedRows += inserted
|
||||
result.TotalRows += len(rows)
|
||||
if mt > maxTime {
|
||||
maxTime = mt
|
||||
}
|
||||
result.TotalPages++
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// 计算时间分片(range 模式 + 全量同步)
|
||||
var timeChunks [][2]int64
|
||||
if isRangeMode && lastSyncTime <= 0 {
|
||||
chunkMs := int64(3 * 24 * 3600 * 1000) // 3天
|
||||
startMs := time.Now().Add(-time.Duration(GetDefaultLookbackDays(ctx)) * 24 * time.Hour).UnixMilli()
|
||||
if fst, ok := toFloat64(iface.RequestConfig["full_sync_start_time"]); ok && fst > 0 {
|
||||
startMs = int64(fst)
|
||||
}
|
||||
} else if iface.ResponseConfig != nil {
|
||||
// hasMore 分页(如钉钉 offset/size + hasMore)
|
||||
if hf, _ := iface.ResponseConfig["has_more_field"].(string); hf != "" {
|
||||
for page := 2; hasMoreCheck(resp.Body, hf); page++ {
|
||||
body := buildReqBody(iface, page, pageSize, lastSyncTime, nil)
|
||||
resp2, e2 := api.Request(ctx, method, iface.Url, body, inQuery)
|
||||
if e2 != nil {
|
||||
logrus.Errorf("第 %d 页请求失败: %v", page, e2)
|
||||
break
|
||||
}
|
||||
rows2, _, mt2, _, pe2 := parseRespExt(resp2.Body, iface.ResponseConfig)
|
||||
if pe2 != nil {
|
||||
logrus.Errorf("第 %d 页解析失败: %v", page, pe2)
|
||||
break
|
||||
}
|
||||
injectRowFields(rows2, body, iface.RequestConfig)
|
||||
inserted2, _ := savePage(ctx, td, rows2)
|
||||
result.InsertedRows += inserted2
|
||||
result.TotalRows += len(rows2)
|
||||
if mt2 > maxTime {
|
||||
maxTime = mt2
|
||||
}
|
||||
resp = resp2
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
} else {
|
||||
// 普通分页
|
||||
for page := 2; page <= totalPages; page++ {
|
||||
body := buildReqBody(iface, page, pageSize, lastSyncTime, nil)
|
||||
resp, err = api.Request(ctx, method, iface.Url, body, inQuery)
|
||||
if err != nil {
|
||||
logrus.Errorf("第 %d 页请求失败: %v", page, err)
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, fmt.Sprintf("第 %d 页请求失败: %v", page, err))
|
||||
continue
|
||||
}
|
||||
rows, _, mt, _, pe := parseRespExt(resp.Body, iface.ResponseConfig)
|
||||
if pe != nil {
|
||||
logrus.Errorf("第 %d 页解析失败: %v", page, pe)
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, fmt.Sprintf("第 %d 页解析失败: %v", page, pe))
|
||||
continue
|
||||
}
|
||||
injectRowFields(rows, body, iface.RequestConfig)
|
||||
inserted, _ = savePage(ctx, td, rows)
|
||||
result.InsertedRows += inserted
|
||||
result.TotalRows += len(rows)
|
||||
if mt > maxTime {
|
||||
maxTime = mt
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
endMs := time.Now().UnixMilli()
|
||||
for t := startMs; t < endMs; t += chunkMs {
|
||||
chunkEnd := t + chunkMs
|
||||
if chunkEnd > endMs {
|
||||
chunkEnd = endMs
|
||||
}
|
||||
timeChunks = append(timeChunks, [2]int64{t, chunkEnd})
|
||||
}
|
||||
logrus.Infof("时间分片模式: %d 个分片(每片7天)", len(timeChunks))
|
||||
} else {
|
||||
// 普通分页(无 response_config)
|
||||
for page := 2; page <= totalPages; page++ {
|
||||
body := buildReqBody(iface, page, pageSize, lastSyncTime, nil)
|
||||
resp, err = api.Request(ctx, method, iface.Url, body, inQuery)
|
||||
if err != nil {
|
||||
logrus.Errorf("第 %d 页请求失败: %v", page, err)
|
||||
continue
|
||||
}
|
||||
rows, _, mt, _, pe := parseRespExt(resp.Body, iface.ResponseConfig)
|
||||
if pe != nil {
|
||||
logrus.Errorf("第 %d 页解析失败: %v", page, pe)
|
||||
continue
|
||||
}
|
||||
injectRowFields(rows, body, iface.RequestConfig)
|
||||
inserted, _ = savePage(ctx, td, rows)
|
||||
result.InsertedRows += inserted
|
||||
result.TotalRows += len(rows)
|
||||
if mt > maxTime {
|
||||
maxTime = mt
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
timeChunks = [][2]int64{{0, 0}} // 单次,时间由 buildReqBody 决定
|
||||
}
|
||||
|
||||
if maxTime <= 0 {
|
||||
maxTime = time.Now().Unix()
|
||||
result := &SyncResult{TableName: td.TableName}
|
||||
globalMaxTime := int64(0)
|
||||
|
||||
for _, chunk := range timeChunks {
|
||||
if isRangeMode && chunk[0] > 0 {
|
||||
lastSyncTime = chunk[0]
|
||||
}
|
||||
|
||||
// 构建第一页的额外参数
|
||||
firstExtra := map[string]interface{}{}
|
||||
if cursorMode {
|
||||
if icv, ok := iface.RequestConfig["initial_cursor"]; ok {
|
||||
firstExtra[cursorParam] = icv
|
||||
} else {
|
||||
firstExtra[cursorParam] = ""
|
||||
}
|
||||
}
|
||||
|
||||
// 处理第一页
|
||||
pr := processPage(ctx, api, iface, method, inQuery, 1, pageSize, lastSyncTime, firstExtra)
|
||||
if pr.err != nil {
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, pr.err.Error())
|
||||
return nil, fmt.Errorf("获取第一页失败: %w", pr.err)
|
||||
}
|
||||
if len(pr.rows) == 0 {
|
||||
return &SyncResult{TableName: td.TableName, Duration: fmt.Sprintf("%.1fs", time.Since(start).Seconds())}, nil
|
||||
}
|
||||
|
||||
// result initialized above in time chunk loop
|
||||
maxTime := pr.maxTime
|
||||
accumPage(result, &maxTime, td, ctx, pr)
|
||||
|
||||
// 分页循环:三种模式仅循环控制和参数不同,内核复用 processPage + accumPage
|
||||
switch {
|
||||
case cursorMode:
|
||||
nextCursor := pr.nextCursor
|
||||
for nextCursor != "" && nextCursor != "nomore" {
|
||||
pr := processPage(ctx, api, iface, method, inQuery, 1, pageSize, lastSyncTime, map[string]interface{}{
|
||||
cursorParam: nextCursor,
|
||||
})
|
||||
if pr.err != nil {
|
||||
logrus.Errorf("游标 %s 处理失败: %v", nextCursor, pr.err)
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, fmt.Sprintf("游标 %s 失败: %v", nextCursor, pr.err))
|
||||
break
|
||||
}
|
||||
if len(pr.rows) == 0 {
|
||||
break
|
||||
}
|
||||
nextCursor = pr.nextCursor
|
||||
accumPage(result, &maxTime, td, ctx, pr)
|
||||
}
|
||||
|
||||
case iface.ResponseConfig != nil && iface.ResponseConfig["has_more_field"] != nil:
|
||||
// hasMore 分页(如钉钉 offset/size + hasMore):用上一页的 raw body 判断
|
||||
hf, _ := iface.ResponseConfig["has_more_field"].(string)
|
||||
lastRaw := pr.raw
|
||||
for page := 2; hasMoreCheck(lastRaw, hf); page++ {
|
||||
pr2 := processPage(ctx, api, iface, method, inQuery, page, pageSize, lastSyncTime, nil)
|
||||
if pr2.err != nil {
|
||||
logrus.Errorf("第 %d 页处理失败: %v", page, pr2.err)
|
||||
break
|
||||
}
|
||||
accumPage(result, &maxTime, td, ctx, pr2)
|
||||
lastRaw = pr2.raw
|
||||
}
|
||||
|
||||
default:
|
||||
// 普通分页:第一页 parseRespExt 返回 totalPages(parseRespExt 第4个返回值被忽略,需要从别处获取)
|
||||
totalPages := getTotalPages(pr.raw)
|
||||
for page := 2; page <= totalPages; page++ {
|
||||
pr := processPage(ctx, api, iface, method, inQuery, page, pageSize, lastSyncTime, nil)
|
||||
if pr.err != nil {
|
||||
logrus.Errorf("第 %d 页请求失败: %v", page, pr.err)
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, fmt.Sprintf("第 %d 页失败: %v", page, pr.err))
|
||||
continue
|
||||
}
|
||||
accumPage(result, &maxTime, td, ctx, pr)
|
||||
}
|
||||
}
|
||||
|
||||
if globalMaxTime <= 0 {
|
||||
globalMaxTime = time.Now().Unix()
|
||||
}
|
||||
}
|
||||
updateSyncTime(ctx, platform.PlatformCode, iface.Code, maxTime)
|
||||
updateSyncTime(ctx, platform.PlatformCode, iface.Code, globalMaxTime)
|
||||
|
||||
result.Duration = fmt.Sprintf("%.1fs", time.Since(start).Seconds())
|
||||
logrus.Infof("同步完成 - 表:%s, %d条, 写入%d条, 耗时%s", td.TableName, result.TotalRows, result.InsertedRows, result.Duration)
|
||||
@@ -350,7 +366,7 @@ func collectPrefetchEntities(rows []map[string]interface{}, prefetch *PrefetchCo
|
||||
if prefetch.ValueField == "" {
|
||||
*allEntities = append(*allEntities, item)
|
||||
} else if v, ok := item[prefetch.ValueField]; ok {
|
||||
if f, ok := v.(float64); ok {
|
||||
if f, ok := toFloat64(v); ok {
|
||||
*allEntities = append(*allEntities, int64(f))
|
||||
} else {
|
||||
*allEntities = append(*allEntities, v)
|
||||
@@ -392,8 +408,8 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
prefetchMethod := strings.ToUpper(prefetch.Method)
|
||||
prefetchPageSize := 100
|
||||
if prefetchIface != nil && prefetchIface.RequestConfig != nil {
|
||||
if ps, ok := prefetchIface.RequestConfig["pageSize"].(float64); ok {
|
||||
prefetchPageSize = int(ps)
|
||||
if ps, ok := toInt(prefetchIface.RequestConfig["pageSize"]); ok {
|
||||
prefetchPageSize = ps
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,6 +425,9 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
var prefetchRespCfg map[string]interface{}
|
||||
if prefetchIface != nil {
|
||||
prefetchRespCfg = prefetchIface.ResponseConfig
|
||||
logrus.Debugf("预取接口配置: code=%s, success_field=%v, success_value=%v", prefetchIface.Code, prefetchRespCfg["success_field"], prefetchRespCfg["success_value"])
|
||||
} else {
|
||||
logrus.Warnf("未找到预取接口配置: URL=%s,将使用默认配置", prefetch.URL)
|
||||
}
|
||||
|
||||
allEntities := make([]interface{}, 0)
|
||||
@@ -422,8 +441,8 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
if prefetchIface != nil && prefetchRecursiveCfg != nil {
|
||||
// ----- 递归遍历预取(如钉钉部门树)-----
|
||||
maxDepth := 20
|
||||
if md, ok := prefetchIface.RequestConfig["max_recursive_depth"].(float64); ok {
|
||||
maxDepth = int(md)
|
||||
if md, ok := toInt(prefetchIface.RequestConfig["max_recursive_depth"]); ok {
|
||||
maxDepth = md
|
||||
}
|
||||
processedKeys := make(map[string]bool)
|
||||
type rItem struct {
|
||||
@@ -449,7 +468,7 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
if item.keyVal != nil {
|
||||
extra[prefetchRecursiveCfg.TargetParam] = item.keyVal
|
||||
}
|
||||
body := buildReqBody(prefetchReqIface, 1, prefetchPageSize, 0, extra)
|
||||
body := buildReqBody(ctx, prefetchReqIface, 1, prefetchPageSize, 0, extra)
|
||||
r2, err := api.Request(ctx, prefetchMethod, prefetch.URL, body, prefetchInQuery)
|
||||
if err != nil {
|
||||
logrus.Errorf("预取递归 [depth=%d] 请求失败: %v", item.depth, err)
|
||||
@@ -465,7 +484,7 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
if prefetch.ValueField == "" {
|
||||
allEntities = append(allEntities, row)
|
||||
} else if v, ok := row[prefetch.ValueField]; ok {
|
||||
if f, ok := v.(float64); ok {
|
||||
if f, ok := toFloat64(v); ok {
|
||||
allEntities = append(allEntities, int64(f))
|
||||
} else {
|
||||
allEntities = append(allEntities, v)
|
||||
@@ -481,9 +500,15 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
// ----- 常规分页预取 -----
|
||||
firstExtra := make(map[string]interface{})
|
||||
if prefetchIsCursor {
|
||||
firstExtra[prefetchPageParam] = ""
|
||||
// 支持 initial_cursor 配置,如果没有则使用空字符串
|
||||
if icv, ok := prefetchReqIface.RequestConfig["initial_cursor"]; ok {
|
||||
firstExtra[prefetchPageParam] = icv
|
||||
} else {
|
||||
firstExtra[prefetchPageParam] = ""
|
||||
}
|
||||
}
|
||||
body := buildReqBody(prefetchReqIface, 1, prefetchPageSize, lastSyncTime, firstExtra)
|
||||
body := buildReqBody(ctx, prefetchReqIface, 1, prefetchPageSize, lastSyncTime, firstExtra)
|
||||
logrus.Debugf("预取请求 URL: %s, Method: %s, Body: %+v", prefetch.URL, prefetchMethod, body)
|
||||
resp, err := api.Request(ctx, prefetchMethod, prefetch.URL, body, prefetchInQuery)
|
||||
if err != nil {
|
||||
recordFailure(ctx, platform.PlatformCode, iface.Code, taskType, fmt.Sprintf("预取第一页请求失败: %v", err))
|
||||
@@ -499,7 +524,7 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
|
||||
if prefetchIsCursor {
|
||||
for nextCursor != "" && nextCursor != "nomore" {
|
||||
body := buildReqBody(prefetchReqIface, 1, prefetchPageSize, lastSyncTime, map[string]interface{}{
|
||||
body := buildReqBody(ctx, prefetchReqIface, 1, prefetchPageSize, lastSyncTime, map[string]interface{}{
|
||||
prefetchPageParam: nextCursor,
|
||||
})
|
||||
resp, err := api.Request(ctx, prefetchMethod, prefetch.URL, body, prefetchInQuery)
|
||||
@@ -521,7 +546,7 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
}
|
||||
} else {
|
||||
for page := 2; page <= prefetchTotalPages; page++ {
|
||||
body := buildReqBody(prefetchReqIface, page, prefetchPageSize, lastSyncTime, nil)
|
||||
body := buildReqBody(ctx, prefetchReqIface, page, prefetchPageSize, lastSyncTime, nil)
|
||||
resp, err := api.Request(ctx, prefetchMethod, prefetch.URL, body, prefetchInQuery)
|
||||
if err != nil {
|
||||
logrus.Errorf("预取第 %d 页请求失败: %v", page, err)
|
||||
@@ -558,10 +583,10 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
// 并发处理每个实体的数据
|
||||
result := &SyncResult{TableName: td.TableName}
|
||||
pageSize := GetSyncPageSize(ctx)
|
||||
if ps, ok := iface.RequestConfig["page_size"].(float64); ok {
|
||||
pageSize = int(ps)
|
||||
} else if ps, ok := iface.RequestConfig["pageSize"].(float64); ok {
|
||||
pageSize = int(ps)
|
||||
if ps, ok := toInt(iface.RequestConfig["page_size"]); ok {
|
||||
pageSize = ps
|
||||
} else if ps, ok := toInt(iface.RequestConfig["pageSize"]); ok {
|
||||
pageSize = ps
|
||||
}
|
||||
|
||||
dataMethod := string(iface.Method)
|
||||
@@ -598,7 +623,7 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
} else {
|
||||
firstExtra[cp] = ""
|
||||
}
|
||||
body := buildReqBody(iface, 1, pageSize, lastSyncTime, firstExtra)
|
||||
body := buildReqBody(ctx, iface, 1, pageSize, lastSyncTime, firstExtra)
|
||||
resp, err := api.Request(ctx, dataMethod, iface.Url, body, inQuery)
|
||||
if err != nil {
|
||||
logrus.Errorf(" 实体 %v 首次请求失败: %v", val, err)
|
||||
@@ -623,7 +648,7 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
}
|
||||
nextCursor := nc
|
||||
for nextCursor != "" && nextCursor != "nomore" {
|
||||
body := buildReqBody(iface, 1, pageSize, lastSyncTime, map[string]interface{}{
|
||||
body := buildReqBody(ctx, iface, 1, pageSize, lastSyncTime, map[string]interface{}{
|
||||
cp: nextCursor,
|
||||
prefetch.TargetParam: val,
|
||||
})
|
||||
@@ -660,7 +685,7 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
page := 1
|
||||
totalPages := 1
|
||||
for page <= totalPages {
|
||||
body := buildReqBody(iface, page, pageSize, lastSyncTime, map[string]interface{}{
|
||||
body := buildReqBody(ctx, iface, page, pageSize, lastSyncTime, map[string]interface{}{
|
||||
prefetch.TargetParam: val,
|
||||
})
|
||||
resp, err := api.Request(ctx, dataMethod, iface.Url, body, inQuery)
|
||||
@@ -721,8 +746,8 @@ func syncWithPrefetch(ctx context.Context, api *ApiClient, platform *PlatformCon
|
||||
// syncRecursive 递归遍历同步(如钉钉部门树:先查根级 → 对每个子部门递归查下级)
|
||||
func syncRecursive(ctx context.Context, api *ApiClient, platform *PlatformConfig, iface *entity.ApiInterface, td *TableDefinition, recursive *RecursiveConfig, start time.Time) (*SyncResult, error) {
|
||||
maxDepth := 20
|
||||
if md, ok := iface.RequestConfig["max_recursive_depth"].(float64); ok {
|
||||
maxDepth = int(md)
|
||||
if md, ok := toInt(iface.RequestConfig["max_recursive_depth"]); ok {
|
||||
maxDepth = md
|
||||
}
|
||||
|
||||
inQuery := paramsInQuery(iface)
|
||||
@@ -760,7 +785,7 @@ func syncRecursive(ctx context.Context, api *ApiClient, platform *PlatformConfig
|
||||
extraParams[recursive.TargetParam] = item.keyVal
|
||||
}
|
||||
|
||||
body := buildReqBody(iface, 1, 100, 0, extraParams)
|
||||
body := buildReqBody(ctx, iface, 1, 100, 0, extraParams)
|
||||
resp, err := api.Request(ctx, method, iface.Url, body, inQuery)
|
||||
if err != nil {
|
||||
logrus.Errorf("递归 [depth=%d] 请求失败: %v", item.depth, err)
|
||||
@@ -819,6 +844,12 @@ func toFloat64(v interface{}) (float64, bool) {
|
||||
return float64(val), true
|
||||
case int64:
|
||||
return float64(val), true
|
||||
case json.Number:
|
||||
f, err := val.Float64()
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return f, true
|
||||
case string:
|
||||
// 支持字符串类型的成功值(如钉钉智能薪酬返回 code: "200")
|
||||
if f, err := strconv.ParseFloat(val, 64); err == nil {
|
||||
@@ -830,6 +861,32 @@ func toFloat64(v interface{}) (float64, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// toInt 从 interface{} 安全转换为 int,支持 float64/int/int64/json.Number/string 类型
|
||||
// JSONB 字段通过 pgx 驱动扫描时可能返回 json.Number 而非 float64
|
||||
func toInt(v interface{}) (int, bool) {
|
||||
switch val := v.(type) {
|
||||
case float64:
|
||||
return int(val), true
|
||||
case int:
|
||||
return val, true
|
||||
case int64:
|
||||
return int(val), true
|
||||
case json.Number:
|
||||
i, err := val.Int64()
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return int(i), true
|
||||
case string:
|
||||
if i, err := strconv.Atoi(val); err == nil {
|
||||
return i, true
|
||||
}
|
||||
return 0, false
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// buildPrefetchParams 构建预取接口的请求参数
|
||||
func buildPrefetchParams(iface *entity.ApiInterface) map[string]interface{} {
|
||||
params := make(map[string]interface{})
|
||||
@@ -960,8 +1017,39 @@ func extractValues(raw []byte, path, valueField string) ([]interface{}, error) {
|
||||
return nil, fmt.Errorf("路径 %s 不完整", path)
|
||||
}
|
||||
|
||||
// normalizeJSONNumbers 递归将 json.Number 转换为 Go 原生数值类型
|
||||
// pgx 驱动扫描 PostgreSQL JSONB 数值字段时可能返回 json.Number(底层是 string),
|
||||
// 直接用 json.Marshal 会将其序列化为带引号的字符串(如 "0" 而非 0),
|
||||
// 导致快手等平台的 param JSON 签名校验失败
|
||||
func normalizeJSONNumbers(v interface{}) interface{} {
|
||||
switch val := v.(type) {
|
||||
case json.Number:
|
||||
if i, err := val.Int64(); err == nil {
|
||||
return i
|
||||
}
|
||||
if f, err := val.Float64(); err == nil {
|
||||
return f
|
||||
}
|
||||
return val.String()
|
||||
case map[string]interface{}:
|
||||
result := make(map[string]interface{}, len(val))
|
||||
for mk, mv := range val {
|
||||
result[mk] = normalizeJSONNumbers(mv)
|
||||
}
|
||||
return result
|
||||
case []interface{}:
|
||||
result := make([]interface{}, len(val))
|
||||
for i, item := range val {
|
||||
result[i] = normalizeJSONNumbers(item)
|
||||
}
|
||||
return result
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// buildReqBody 构建请求参数
|
||||
func buildReqBody(iface *entity.ApiInterface, page, pageSize int, lastSyncTime int64, extraParams map[string]interface{}) map[string]interface{} {
|
||||
func buildReqBody(ctx context.Context, iface *entity.ApiInterface, page, pageSize int, lastSyncTime int64, extraParams map[string]interface{}) map[string]interface{} {
|
||||
body := make(map[string]interface{})
|
||||
if iface.RequestConfig != nil {
|
||||
for k, v := range iface.RequestConfig {
|
||||
@@ -1017,15 +1105,23 @@ func buildReqBody(iface *entity.ApiInterface, page, pageSize int, lastSyncTime i
|
||||
timeMs := lastSyncTime
|
||||
if timeMs <= 0 {
|
||||
// 全量:优先使用配置的 full_sync_start_time,否则默认90天前
|
||||
if fst, ok := iface.RequestConfig["full_sync_start_time"].(float64); ok && fst > 0 {
|
||||
if fst, ok := toFloat64(iface.RequestConfig["full_sync_start_time"]); ok && fst > 0 {
|
||||
timeMs = int64(fst)
|
||||
} else {
|
||||
timeMs = time.Now().Add(-90 * 24 * time.Hour).UnixMilli()
|
||||
timeMs = time.Now().Add(-time.Duration(GetDefaultLookbackDays(ctx)) * 24 * time.Hour).UnixMilli()
|
||||
}
|
||||
}
|
||||
body["queryType"] = 2
|
||||
// 仅在配置未指定 queryType 时设默认值,尊重配置
|
||||
if _, exists := body["queryType"]; !exists {
|
||||
body["queryType"] = 2
|
||||
}
|
||||
body["beginTime"] = timeMs
|
||||
body["endTime"] = time.Now().UnixMilli()
|
||||
endTime := time.Now().UnixMilli()
|
||||
maxRangeMs := int64(3 * 24 * 3600 * 1000)
|
||||
if endTime-timeMs > maxRangeMs {
|
||||
endTime = timeMs + maxRangeMs
|
||||
}
|
||||
body["endTime"] = endTime
|
||||
} else if lastSyncTime > 0 {
|
||||
// 腾讯 filtering 模式(仅增量时)
|
||||
timeFilter := map[string]interface{}{
|
||||
@@ -1038,7 +1134,7 @@ func buildReqBody(iface *entity.ApiInterface, page, pageSize int, lastSyncTime i
|
||||
} else {
|
||||
body["filtering"] = []interface{}{timeFilter}
|
||||
}
|
||||
} else if fst, ok := iface.RequestConfig["full_sync_start_time"].(float64); ok && fst > 0 {
|
||||
} else if fst, ok := toFloat64(iface.RequestConfig["full_sync_start_time"]); ok && fst > 0 {
|
||||
// 全量 filtering 模式:指定了 full_sync_start_time,从该时间戳开始拉取
|
||||
timeFilter := map[string]interface{}{
|
||||
"field": tf,
|
||||
@@ -1076,7 +1172,11 @@ func buildReqBody(iface *entity.ApiInterface, page, pageSize int, lastSyncTime i
|
||||
delete(body, k)
|
||||
}
|
||||
}
|
||||
b, err := json.Marshal(wrapperObj)
|
||||
// 规范化 json.Number → Go 原生数值类型,避免 json.Marshal 将其序列化为字符串
|
||||
// (pgx 驱动扫描 JSONB 数值时可能返回 json.Number,其底层是 string 类型)
|
||||
normalized := normalizeJSONNumbers(wrapperObj)
|
||||
b, err := json.Marshal(normalized)
|
||||
logrus.Infof("body_wrapper param JSON (normalized): %s", string(b))
|
||||
if err != nil {
|
||||
logrus.Errorf("JSON序列化 wrapper 失败: %v", err)
|
||||
} else {
|
||||
@@ -1127,6 +1227,17 @@ func parseRespExt(raw []byte, rc map[string]interface{}) ([]map[string]interface
|
||||
actual, _ := toFloat64(v)
|
||||
if actual != successVal {
|
||||
msg, _ := respMap[msgField].(string)
|
||||
// 如果配置的消息字段为空,尝试通用的错误信息字段
|
||||
if msg == "" {
|
||||
for _, altField := range []string{"error_msg", "sub_msg", "msg"} {
|
||||
if altMsg, _ := respMap[altField].(string); altMsg != "" {
|
||||
msg = altMsg
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
respJSON, _ := json.Marshal(respMap)
|
||||
logrus.Errorf("API响应校验失败: success_field=%s, expected=%v, actual=%v, message=%s, 完整响应: %s", successField, successVal, actual, msg, string(respJSON))
|
||||
return nil, 0, 0, "", fmt.Errorf("API错误: %s=%v, %s=%s", successField, v, msgField, msg)
|
||||
}
|
||||
}
|
||||
@@ -1202,7 +1313,7 @@ func parseRespExt(raw []byte, rc map[string]interface{}) ([]map[string]interface
|
||||
j, _ := json.Marshal(m)
|
||||
flat["raw_data"] = string(j)
|
||||
for _, tf := range []string{"last_modified_time", "created_time", "update_time", "createTime", "updateTime", "lastModifiedTime"} {
|
||||
if t, ok := flat[tf].(float64); ok && int64(t) > maxTime {
|
||||
if t, ok := toFloat64(flat[tf]); ok && int64(t) > maxTime {
|
||||
maxTime = int64(t)
|
||||
}
|
||||
}
|
||||
@@ -1217,7 +1328,7 @@ func parseRespExt(raw []byte, rc map[string]interface{}) ([]map[string]interface
|
||||
if i == len(cp)-1 {
|
||||
if s, ok := cc[p].(string); ok {
|
||||
nextCursor = s
|
||||
} else if f, ok := cc[p].(float64); ok {
|
||||
} else if f, ok := toFloat64(cc[p]); ok {
|
||||
// 数字游标(如钉钉 next_cursor=10)
|
||||
nextCursor = fmt.Sprintf("%.0f", f)
|
||||
}
|
||||
@@ -1241,8 +1352,8 @@ func parseRespExt(raw []byte, rc map[string]interface{}) ([]map[string]interface
|
||||
}
|
||||
}
|
||||
if pi, ok := dataContainer["page_info"].(map[string]interface{}); ok {
|
||||
if tp, ok := pi["total_page"].(float64); ok {
|
||||
totalPages = int(tp)
|
||||
if tp, ok := toInt(pi["total_page"]); ok {
|
||||
totalPages = tp
|
||||
}
|
||||
}
|
||||
return rows, totalPages, maxTime, nextCursor, nil
|
||||
|
||||
@@ -50,3 +50,12 @@ func GetSyncTimeout(ctx context.Context) int {
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// GetDefaultLookbackDays 获取全量同步默认回溯天数(默认90)
|
||||
func GetDefaultLookbackDays(ctx context.Context) int {
|
||||
d := g.Cfg().MustGet(ctx, "sync.default_lookback_days", 90).Int()
|
||||
if d < 1 {
|
||||
return 90
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
dao "dataengine/dao/dict"
|
||||
dto "dataengine/model/dto/dict"
|
||||
entity "dataengine/model/entity/dict"
|
||||
"dataengine/utils"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -32,7 +33,8 @@ type PlatformManager struct{}
|
||||
|
||||
// GetPlatform 根据平台编码获取配置
|
||||
func (m *PlatformManager) GetPlatform(ctx context.Context, platformCode string) (*PlatformConfig, error) {
|
||||
platform, err := dao.DatasourcePlatform.GetByPlatformCode(ctx, platformCode)
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
platform, err := dao.DatasourcePlatform.GetByPlatformCode(ctx, platformCode, tenantId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("查询平台配置失败 [%s]: %w", platformCode, err)
|
||||
}
|
||||
@@ -81,10 +83,11 @@ func (m *PlatformManager) GetPlatform(ctx context.Context, platformCode string)
|
||||
|
||||
// GetInterfaces 获取平台下的活跃接口列表
|
||||
func (m *PlatformManager) GetInterfaces(ctx context.Context, platformId int64) ([]entity.ApiInterface, error) {
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
interfaces, _, err := dao.ApiInterface.List(ctx, &dto.ListApiInterfaceReq{
|
||||
PlatformId: platformId,
|
||||
Status: "active",
|
||||
})
|
||||
}, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -93,11 +96,12 @@ func (m *PlatformManager) GetInterfaces(ctx context.Context, platformId int64) (
|
||||
|
||||
// GetInterfaceByCode 根据编码获取接口定义
|
||||
func (m *PlatformManager) GetInterfaceByCode(ctx context.Context, platformId int64, code string) (*entity.ApiInterface, error) {
|
||||
tenantId := utils.GetCurrentTenantId(ctx)
|
||||
all, _, err := dao.ApiInterface.List(ctx, &dto.ListApiInterfaceReq{
|
||||
PlatformId: platformId,
|
||||
Code: code,
|
||||
Status: "active",
|
||||
})
|
||||
}, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -113,7 +117,7 @@ func (m *PlatformManager) GetPlatformWithInterfaces(ctx context.Context, platfor
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
interfaces, err := m.GetInterfaces(ctx, cfg.ID)
|
||||
interfaces, err := m.GetInterfaces(ctx, cfg.Id)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -27,13 +27,16 @@ func StartAutoSync(ctx context.Context) {
|
||||
func runAutoSync(ctx context.Context) {
|
||||
logrus.Info("=== 开始自动同步 ===")
|
||||
|
||||
// 从配置读取同步租户 ID(运维部署时配置)
|
||||
tenantId := g.Cfg().MustGet(ctx, "sync.default_tenant_id", 1).Uint64()
|
||||
|
||||
// 注入用户上下文(ORM 框架需要用于租户隔离)
|
||||
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: 1})
|
||||
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin", TenantId: tenantId})
|
||||
|
||||
// 查询所有 ACTIVE 平台
|
||||
platforms, _, err := dao.DatasourcePlatform.List(ctx, &dto.ListDatasourcePlatformReq{
|
||||
Status: "ACTIVE",
|
||||
})
|
||||
}, tenantId)
|
||||
if err != nil {
|
||||
logrus.Errorf("查询平台列表失败: %v", err)
|
||||
return
|
||||
@@ -42,9 +45,9 @@ func runAutoSync(ctx context.Context) {
|
||||
for _, p := range platforms {
|
||||
// 查询该平台下有 table_definition 的接口
|
||||
interfaces, _, err := dao.ApiInterface.List(ctx, &dto.ListApiInterfaceReq{
|
||||
PlatformId: p.ID,
|
||||
PlatformId: p.Id,
|
||||
Status: "active",
|
||||
})
|
||||
}, tenantId)
|
||||
if err != nil {
|
||||
logrus.Errorf("查询接口列表失败 [platform=%s]: %v", p.PlatformCode, err)
|
||||
continue
|
||||
|
||||
@@ -1,60 +1,67 @@
|
||||
-- =============================================
|
||||
-- 快手电商平台初始化数据(18个接口)
|
||||
-- 签名: sign = MD5(appKey + appSecret) = 固定值
|
||||
-- =============================================
|
||||
|
||||
INSERT INTO api_datasource_platform (tenant_id,creator,created_at,updater,updated_at,platform_code,platform_name,description,status,api_base_url,auth_type,auth_config,rate_limit_per_minute,rate_limit_per_hour,concurrency_limit,request_timeout_ms,max_retries,retry_delay_ms) VALUES
|
||||
(1,'admin',NOW(),'admin',NOW(),'kuaishou','快手电商','快手电商开放平台数据同步','ACTIVE','https://openapi.kwaixiaodian.com','API_KEY','{"sign_algorithm":"md5_upper","app_key":"YOUR_APP_KEY","app_secret":"YOUR_APP_SECRET","token_in_query":true,"query_key":"access_token","extra_query_params":{"timestamp":"{timestamp_ms}","signMethod":"MD5"}}'::jsonb,100,3600,5,30000,3,1000);
|
||||
INSERT INTO api_datasource_platform (tenant_id,creator,created_at,updater,updated_at,platform_code,platform_name,description,status,api_base_url,auth_type,token,api_key,auth_config,rate_limit_per_minute,rate_limit_per_hour,concurrency_limit,request_timeout_ms,max_retries,retry_delay_ms) VALUES
|
||||
(1,'admin',NOW(),'admin',NOW(),'kuaishou','快手电商','快手电商开放平台数据同步','ACTIVE','https://openapi.kwaixiaodian.com','API_KEY','ChJvYXV0aC5yZWZyZXNoVG9rZW4SsAEwBTRAuxX0JVjg_9HTLd41ZO-Hj6z2pTYsBO7vAciUFluDpQblL5ulAwyellS13XpnK3SakGI0kHGSjrsDyIhOtNC2Nk5NKUhXh_q2h2z4THu4nQ2LjWlgcMQMOGE1HOzUboUGKmXhZITG3trMnUZCynokDz_mqurmRA3b6Xblw5xB6KZATpWOxRjIP_vbxYlpOyZlMeq2I7pq7ujXYc9R5YtZINWzKymGiRM6q6bJvxoSYea4eoHshahNmq7k-U8GgXUrIiCgBe6L2AX1SF9iCt13er1lNdn970qyRFEX4uvTVV9DySgFMAE','ChFvYXV0aC5hY2Nlc3NUb2tlbhJAhQ6l9vUwsZRAMbzWpolGpSKJM_YuKAMw3E6kuWt1ZmwaP1-g_qVLwO3B0p1IpUvAdG1Xiwihv2THxij4I-S-oxoSdJTaZfTbRbe4zPS88hU0zxRHIiBlPMstkBXPj73-WGwbwTnaSzS2bxIkRcRfp3dL9qVTOCgFMAE','{"sign_algorithm":"md5","app_key":"ks651333099611149957","app_secret":"JPUXG2CS3I7tqRWbKaLrYQ","sign_secret":"7bc51baab818cf86e121a48d99ff3fe4","token_in_query":true,"query_key":"access_token","extra_query_params":{"timestamp":"{timestamp_ms}"}}'::jsonb,100,3600,5,30000,3,1000);
|
||||
|
||||
-- =============================================
|
||||
-- 接口配置
|
||||
-- 所有接口使用 body_wrapper_field=param 将业务参数打包为 JSON
|
||||
-- method/version/signMethod 保留在顶层不打包
|
||||
-- =============================================
|
||||
|
||||
-- 2. 订单列表(游标分页,增量range)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'订单列表','order_list','/open/order/cursor/list','GET','active','inherit','{"parameters_location":"query","page_param":"cursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.order.cursor.list","version":1,"signMethod":"MD5","orderViewStatus":1,"pageSize":50,"sort":1,"queryType":2,"time_field":"updateTime","time_field_mode":"range","cpsType":0,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.orderList","cursor_field":"data.cursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_order_list","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"status","type":"INT","comment":"订单状态码"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"sendTime","type":"BIGINT","comment":"发货时间"},{"name":"recvTime","type":"BIGINT","comment":"收货时间"},{"name":"refundTime","type":"BIGINT","comment":"退款时间"},{"name":"totalFee","type":"BIGINT","comment":"总金额(分)"},{"name":"expressFee","type":"BIGINT","comment":"运费(分)"},{"name":"discountFee","type":"BIGINT","comment":"优惠金额(分)"},{"name":"originalPrice","type":"BIGINT","comment":"原价(分)"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家ID"},{"name":"sellerOpenId","type":"VARCHAR(100)","comment":"卖家ID"},{"name":"buyerNick","type":"VARCHAR(100)","comment":"买家昵称"},{"name":"sellerNick","type":"VARCHAR(100)","comment":"卖家昵称"},{"name":"remark","type":"TEXT","comment":"买家留言"},{"name":"itemTitle","type":"VARCHAR(300)","comment":"商品标题"},{"name":"num","type":"INT","comment":"商品数量"},{"name":"price","type":"BIGINT","comment":"商品单价(分)"},{"name":"activityType","type":"INT","comment":"活动类型"},{"name":"cpsType","type":"INT","comment":"分销类型"},{"name":"payType","type":"INT","comment":"支付类型"},{"name":"payChannel","type":"VARCHAR(50)","comment":"支付渠道"},{"name":"channel","type":"VARCHAR(50)","comment":"分销渠道"},{"name":"commentStatus","type":"INT","comment":"评价状态"},{"name":"priorityDelivery","type":"BOOLEAN","comment":"是否优先发货"},{"name":"carrierType","type":"INT","comment":"承运商类型"},{"name":"carrierId","type":"BIGINT","comment":"承运商ID"},{"name":"province","type":"VARCHAR(100)","comment":"省份"},{"name":"city","type":"VARCHAR(100)","comment":"城市"},{"name":"district","type":"VARCHAR(100)","comment":"区县"},{"name":"provinceCode","type":"INT","comment":"省份编码"},{"name":"cityCode","type":"INT","comment":"城市编码"},{"name":"districtCode","type":"INT","comment":"区县编码"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'订单列表','order_list','/open/order/cursor/list','GET','active','inherit','{"page_param":"cursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.order.cursor.list","version":1,"signMethod":"MD5","orderViewStatus":1,"pageSize":50,"sort":1,"queryType":2,"time_field":"updateTime","time_field_mode":"range","cpsType":1,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.orderList","cursor_field":"data.cursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_order_list","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"status","type":"INT","comment":"订单状态码"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"sendTime","type":"BIGINT","comment":"发货时间"},{"name":"recvTime","type":"BIGINT","comment":"收货时间"},{"name":"refundTime","type":"BIGINT","comment":"退款时间"},{"name":"totalFee","type":"BIGINT","comment":"总金额(分)"},{"name":"expressFee","type":"BIGINT","comment":"运费(分)"},{"name":"discountFee","type":"BIGINT","comment":"优惠金额(分)"},{"name":"originalPrice","type":"BIGINT","comment":"原价(分)"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家ID"},{"name":"sellerOpenId","type":"VARCHAR(100)","comment":"卖家ID"},{"name":"buyerNick","type":"VARCHAR(100)","comment":"买家昵称"},{"name":"sellerNick","type":"VARCHAR(100)","comment":"卖家昵称"},{"name":"remark","type":"TEXT","comment":"买家留言"},{"name":"itemTitle","type":"VARCHAR(300)","comment":"商品标题"},{"name":"num","type":"INT","comment":"商品数量"},{"name":"price","type":"BIGINT","comment":"商品单价(分)"},{"name":"activityType","type":"INT","comment":"活动类型"},{"name":"cpsType","type":"INT","comment":"分销类型"},{"name":"payType","type":"INT","comment":"支付类型"},{"name":"payChannel","type":"VARCHAR(50)","comment":"支付渠道"},{"name":"channel","type":"VARCHAR(50)","comment":"分销渠道"},{"name":"commentStatus","type":"INT","comment":"评价状态"},{"name":"priorityDelivery","type":"BOOLEAN","comment":"是否优先发货"},{"name":"carrierType","type":"INT","comment":"承运商类型"},{"name":"carrierId","type":"BIGINT","comment":"承运商ID"},{"name":"province","type":"VARCHAR(100)","comment":"省份"},{"name":"city","type":"VARCHAR(100)","comment":"城市"},{"name":"district","type":"VARCHAR(100)","comment":"区县"},{"name":"provinceCode","type":"INT","comment":"省份编码"},{"name":"cityCode","type":"INT","comment":"城市编码"},{"name":"districtCode","type":"INT","comment":"区县编码"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
|
||||
-- 3. 订单详情(prefetch→order_list)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'订单详情','order_detail','/open/order/detail','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.order.detail","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"prefetch":{"url":"/open/order/cursor/list","method":"GET","response_path":"data.orderList","target_param":"oid","value_field":"oid"}}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_order_detail","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"status","type":"INT","comment":"订单状态码"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"sendTime","type":"BIGINT","comment":"发货时间"},{"name":"recvTime","type":"BIGINT","comment":"收货时间"},{"name":"refundTime","type":"BIGINT","comment":"退款时间"},{"name":"totalFee","type":"BIGINT","comment":"总金额(分)"},{"name":"expressFee","type":"BIGINT","comment":"运费(分)"},{"name":"discountFee","type":"BIGINT","comment":"优惠金额(分)"},{"name":"originalPrice","type":"BIGINT","comment":"原价(分)"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家ID"},{"name":"sellerOpenId","type":"VARCHAR(100)","comment":"卖家ID"},{"name":"buyerNick","type":"VARCHAR(100)","comment":"买家昵称"},{"name":"sellerNick","type":"VARCHAR(100)","comment":"卖家昵称"},{"name":"remark","type":"TEXT","comment":"买家留言"},{"name":"itemTitle","type":"VARCHAR(300)","comment":"商品标题"},{"name":"num","type":"INT","comment":"商品数量"},{"name":"price","type":"BIGINT","comment":"商品单价(分)"},{"name":"activityType","type":"INT","comment":"活动类型"},{"name":"cpsType","type":"INT","comment":"分销类型"},{"name":"payType","type":"INT","comment":"支付类型"},{"name":"payChannel","type":"VARCHAR(50)","comment":"支付渠道"},{"name":"channel","type":"VARCHAR(50)","comment":"分销渠道"},{"name":"commentStatus","type":"INT","comment":"评价状态"},{"name":"priorityDelivery","type":"BOOLEAN","comment":"是否优先发货"},{"name":"carrierType","type":"INT","comment":"承运商类型"},{"name":"carrierId","type":"BIGINT","comment":"承运商ID"},{"name":"province","type":"VARCHAR(100)","comment":"省份"},{"name":"city","type":"VARCHAR(100)","comment":"城市"},{"name":"district","type":"VARCHAR(100)","comment":"区县"},{"name":"provinceCode","type":"INT","comment":"省份编码"},{"name":"cityCode","type":"INT","comment":"城市编码"},{"name":"districtCode","type":"INT","comment":"区县编码"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'订单详情','order_detail','/open/order/detail','POST','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.order.detail","version":1,"signMethod":"MD5","prefetch":{"url":"/open/order/cursor/list","method":"GET","response_path":"data.orderList","target_param":"oid","value_field":"oid"},"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_order_detail","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"status","type":"INT","comment":"订单状态码"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"sendTime","type":"BIGINT","comment":"发货时间"},{"name":"recvTime","type":"BIGINT","comment":"收货时间"},{"name":"refundTime","type":"BIGINT","comment":"退款时间"},{"name":"totalFee","type":"BIGINT","comment":"总金额(分)"},{"name":"expressFee","type":"BIGINT","comment":"运费(分)"},{"name":"discountFee","type":"BIGINT","comment":"优惠金额(分)"},{"name":"originalPrice","type":"BIGINT","comment":"原价(分)"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家ID"},{"name":"sellerOpenId","type":"VARCHAR(100)","comment":"卖家ID"},{"name":"buyerNick","type":"VARCHAR(100)","comment":"买家昵称"},{"name":"sellerNick","type":"VARCHAR(100)","comment":"卖家昵称"},{"name":"remark","type":"TEXT","comment":"买家留言"},{"name":"itemTitle","type":"VARCHAR(300)","comment":"商品标题"},{"name":"num","type":"INT","comment":"商品数量"},{"name":"price","type":"BIGINT","comment":"商品单价(分)"},{"name":"activityType","type":"INT","comment":"活动类型"},{"name":"cpsType","type":"INT","comment":"分销类型"},{"name":"payType","type":"INT","comment":"支付类型"},{"name":"payChannel","type":"VARCHAR(50)","comment":"支付渠道"},{"name":"channel","type":"VARCHAR(50)","comment":"分销渠道"},{"name":"commentStatus","type":"INT","comment":"评价状态"},{"name":"priorityDelivery","type":"BOOLEAN","comment":"是否优先发货"},{"name":"carrierType","type":"INT","comment":"承运商类型"},{"name":"carrierId","type":"BIGINT","comment":"承运商ID"},{"name":"province","type":"VARCHAR(100)","comment":"省份"},{"name":"city","type":"VARCHAR(100)","comment":"城市"},{"name":"district","type":"VARCHAR(100)","comment":"区县"},{"name":"provinceCode","type":"INT","comment":"省份编码"},{"name":"cityCode","type":"INT","comment":"城市编码"},{"name":"districtCode","type":"INT","comment":"区县编码"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
|
||||
-- 4. 商品列表(普通分页,全量)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'商品列表','item_list','/open/item/list/get','GET','active','inherit','{"parameters_location":"query","pageSize":20,"page_param":"pageNumber","page_size_param":"pageSize","method":"open.item.list.get","version":1,"signMethod":"MD5","itemStatus":1,"itemType":1,"onOfflineStatus":1,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.items"}'::jsonb,'{"table_name":"kuaishou_item_list","columns":[{"name":"kwaiItemId","type":"BIGINT","comment":"商品ID"},{"name":"relItemId","type":"BIGINT","comment":"外部商品ID"},{"name":"title","type":"VARCHAR(300)","comment":"商品标题"},{"name":"details","type":"TEXT","comment":"商品详情描述"},{"name":"categoryId","type":"INT","comment":"类目ID"},{"name":"categoryName","type":"VARCHAR(200)","comment":"类目名称"},{"name":"price","type":"BIGINT","comment":"商品价格"},{"name":"volume","type":"INT","comment":"销量"},{"name":"status","type":"INT","comment":"商品状态"},{"name":"auditStatus","type":"INT","comment":"审核状态"},{"name":"auditReason","type":"VARCHAR(500)","comment":"审核原因"},{"name":"shelfStatus","type":"INT","comment":"上下架状态"},{"name":"itemType","type":"INT","comment":"商品类型"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"expressTemplateId","type":"BIGINT","comment":"运费模板ID"},{"name":"linkUrl","type":"TEXT","comment":"商品链接"}],"conflict_keys":["kwaiItemId"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'商品列表','item_list','/open/item/list/get','GET','active','inherit','{"pageSize":20,"page_param":"pageNumber","page_size_param":"pageSize","method":"open.item.list.get","version":1,"signMethod":"MD5","itemStatus":1,"itemType":1,"onOfflineStatus":1,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.items"}'::jsonb,'{"table_name":"kuaishou_item_list","columns":[{"name":"kwaiItemId","type":"BIGINT","comment":"商品ID"},{"name":"relItemId","type":"BIGINT","comment":"外部商品ID"},{"name":"title","type":"VARCHAR(300)","comment":"商品标题"},{"name":"details","type":"TEXT","comment":"商品详情描述"},{"name":"categoryId","type":"INT","comment":"类目ID"},{"name":"categoryName","type":"VARCHAR(200)","comment":"类目名称"},{"name":"price","type":"BIGINT","comment":"商品价格"},{"name":"volume","type":"INT","comment":"销量"},{"name":"status","type":"INT","comment":"商品状态"},{"name":"auditStatus","type":"INT","comment":"审核状态"},{"name":"auditReason","type":"VARCHAR(500)","comment":"审核原因"},{"name":"shelfStatus","type":"INT","comment":"上下架状态"},{"name":"itemType","type":"INT","comment":"商品类型"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"expressTemplateId","type":"BIGINT","comment":"运费模板ID"},{"name":"linkUrl","type":"TEXT","comment":"商品链接"}],"conflict_keys":["kwaiItemId"]}'::jsonb);
|
||||
|
||||
-- 5. 商品详情(prefetch→item_list)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'商品详情','item_detail','/open/item/get','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.item.get","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"prefetch":{"url":"/open/item/list/get","method":"GET","response_path":"data.items","target_param":"kwaiItemId","value_field":"kwaiItemId"}}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_item_detail","columns":[{"name":"itemId","type":"BIGINT","comment":"商品ID"},{"name":"relItemId","type":"BIGINT","comment":"关联商品ID"},{"name":"title","type":"VARCHAR(300)","comment":"商品标题"},{"name":"details","type":"TEXT","comment":"商品详情描述"},{"name":"categoryId","type":"INT","comment":"类目ID"},{"name":"categoryName","type":"VARCHAR(200)","comment":"类目名称"},{"name":"parentCategoryId","type":"INT","comment":"父类目ID"},{"name":"parentCategoryName","type":"VARCHAR(200)","comment":"父类目名称"},{"name":"rootCategoryId","type":"INT","comment":"根类目ID"},{"name":"rootCategoryName","type":"VARCHAR(200)","comment":"根类目名称"},{"name":"auditStatus","type":"INT","comment":"审核状态 2通过"},{"name":"auditReason","type":"VARCHAR(500)","comment":"审核原因"},{"name":"onOfflineStatus","type":"INT","comment":"上下架状态 1上架 0下架"},{"name":"createdTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"expressTemplateId","type":"BIGINT","comment":"运费模板ID"},{"name":"linkUrl","type":"TEXT","comment":"商品链接"},{"name":"purchaseLimit","type":"BOOLEAN","comment":"是否限购"},{"name":"limitCount","type":"INT","comment":"限购数量"},{"name":"timeOfSale","type":"BIGINT","comment":"开售时间"},{"name":"itemRemark","type":"TEXT","comment":"商品备注"},{"name":"spuId","type":"BIGINT","comment":"SPU ID"},{"name":"shortTitle","type":"VARCHAR(300)","comment":"商品短标题"},{"name":"sellingPoint","type":"TEXT","comment":"商品卖点"},{"name":"instructions","type":"TEXT","comment":"使用说明"},{"name":"duplicationStatus","type":"INT","comment":"铺货状态"},{"name":"duplicationReason","type":"VARCHAR(500)","comment":"铺货原因"},{"name":"multipleStock","type":"BOOLEAN","comment":"是否多库存"},{"name":"contractPhone","type":"BOOLEAN","comment":"是否合约机"},{"name":"offlineReason","type":"TEXT","comment":"下架原因"},{"name":"whiteBaseImageUrl","type":"TEXT","comment":"白底图URL"},{"name":"transparentImageUrl","type":"TEXT","comment":"透明图URL"}],"conflict_keys":["itemId"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'商品详情','item_detail','/open/item/get','GET','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.item.get","version":1,"signMethod":"MD5","prefetch":{"url":"/open/item/list/get","method":"GET","response_path":"data.items","target_param":"kwaiItemId","value_field":"kwaiItemId"},"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_item_detail","columns":[{"name":"itemId","type":"BIGINT","comment":"商品ID"},{"name":"relItemId","type":"BIGINT","comment":"关联商品ID"},{"name":"title","type":"VARCHAR(300)","comment":"商品标题"},{"name":"details","type":"TEXT","comment":"商品详情描述"},{"name":"categoryId","type":"INT","comment":"类目ID"},{"name":"categoryName","type":"VARCHAR(200)","comment":"类目名称"},{"name":"parentCategoryId","type":"INT","comment":"父类目ID"},{"name":"parentCategoryName","type":"VARCHAR(200)","comment":"父类目名称"},{"name":"rootCategoryId","type":"INT","comment":"根类目ID"},{"name":"rootCategoryName","type":"VARCHAR(200)","comment":"根类目名称"},{"name":"auditStatus","type":"INT","comment":"审核状态 2通过"},{"name":"auditReason","type":"VARCHAR(500)","comment":"审核原因"},{"name":"onOfflineStatus","type":"INT","comment":"上下架状态 1上架 0下架"},{"name":"createdTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"expressTemplateId","type":"BIGINT","comment":"运费模板ID"},{"name":"linkUrl","type":"TEXT","comment":"商品链接"},{"name":"purchaseLimit","type":"BOOLEAN","comment":"是否限购"},{"name":"limitCount","type":"INT","comment":"限购数量"},{"name":"timeOfSale","type":"BIGINT","comment":"开售时间"},{"name":"itemRemark","type":"TEXT","comment":"商品备注"},{"name":"spuId","type":"BIGINT","comment":"SPU ID"},{"name":"shortTitle","type":"VARCHAR(300)","comment":"商品短标题"},{"name":"sellingPoint","type":"TEXT","comment":"商品卖点"},{"name":"instructions","type":"TEXT","comment":"使用说明"},{"name":"duplicationStatus","type":"INT","comment":"铺货状态"},{"name":"duplicationReason","type":"VARCHAR(500)","comment":"铺货原因"},{"name":"multipleStock","type":"BOOLEAN","comment":"是否多库存"},{"name":"contractPhone","type":"BOOLEAN","comment":"是否合约机"},{"name":"offlineReason","type":"TEXT","comment":"下架原因"},{"name":"whiteBaseImageUrl","type":"TEXT","comment":"白底图URL"},{"name":"transparentImageUrl","type":"TEXT","comment":"透明图URL"}],"conflict_keys":["itemId"]}'::jsonb);
|
||||
|
||||
-- 6. SKU列表(prefetch→item_list)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'SKU列表','sku_list','/open/item/sku/list/get','GET','active','inherit','{"parameters_location":"query","pageSize":100,"page_param":"cursor","page_size_param":"pageSize","method":"open.item.sku.list.get","version":1,"signMethod":"MD5","skuStatus":1,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"prefetch":{"url":"/open/item/list/get","method":"GET","response_path":"data.items","target_param":"kwaiItemId","value_field":"kwaiItemId"}}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.skuList"}'::jsonb,'{"table_name":"kuaishou_sku_list","columns":[{"name":"kwaiSkuId","type":"BIGINT","comment":"SKU ID"},{"name":"relSkuId","type":"BIGINT","comment":"外部SKU ID"},{"name":"kwaiItemId","type":"BIGINT","comment":"商品ID"},{"name":"skuStock","type":"INT","comment":"库存"},{"name":"imageUrl","type":"TEXT","comment":"SKU图片URL"},{"name":"skuSalePrice","type":"BIGINT","comment":"售价(分)"},{"name":"volume","type":"INT","comment":"销量"},{"name":"isValid","type":"INT","comment":"是否有效 1有效"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"specification","type":"VARCHAR(200)","comment":"规格描述"},{"name":"appkey","type":"VARCHAR(100)","comment":"应用标识"},{"name":"skuNick","type":"VARCHAR(100)","comment":"SKU别名"},{"name":"gtinCode","type":"VARCHAR(100)","comment":"商品条形码"}],"conflict_keys":["kwaiSkuId","kwaiItemId"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'SKU列表','sku_list','/open/item/sku/list/get','GET','active','inherit','{"pageSize":100,"page_param":"cursor","page_size_param":"pageSize","method":"open.item.sku.list.get","version":1,"signMethod":"MD5","skuStatus":1,"prefetch":{"url":"/open/item/list/get","method":"GET","response_path":"data.items","target_param":"kwaiItemId","value_field":"kwaiItemId"},"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.skuList"}'::jsonb,'{"table_name":"kuaishou_sku_list","columns":[{"name":"kwaiSkuId","type":"BIGINT","comment":"SKU ID"},{"name":"relSkuId","type":"BIGINT","comment":"外部SKU ID"},{"name":"kwaiItemId","type":"BIGINT","comment":"商品ID"},{"name":"skuStock","type":"INT","comment":"库存"},{"name":"imageUrl","type":"TEXT","comment":"SKU图片URL"},{"name":"skuSalePrice","type":"BIGINT","comment":"售价(分)"},{"name":"volume","type":"INT","comment":"销量"},{"name":"isValid","type":"INT","comment":"是否有效 1有效"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"specification","type":"VARCHAR(200)","comment":"规格描述"},{"name":"appkey","type":"VARCHAR(100)","comment":"应用标识"},{"name":"skuNick","type":"VARCHAR(100)","comment":"SKU别名"},{"name":"gtinCode","type":"VARCHAR(100)","comment":"商品条形码"}],"conflict_keys":["kwaiSkuId","kwaiItemId"]}'::jsonb);
|
||||
|
||||
-- 7. 售后单列表(游标pcursor,增量range,最大1天)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'售后单列表','refund_list','/open/seller/order/refund/pcursor/list','GET','active','inherit','{"parameters_location":"query","pageSize":50,"page_param":"pcursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.seller.order.refund.pcursor.list","version":1,"signMethod":"MD5","type":9,"sort":1,"queryType":2,"currentPage":1,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"time_field":"updateTime","time_field_mode":"range"}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.refundOrderInfoList","cursor_field":"data.pcursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_refund_list","columns":[{"name":"refundId","type":"BIGINT","comment":"退款单ID"},{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"itemId","type":"BIGINT","comment":"商品ID"},{"name":"skuId","type":"BIGINT","comment":"SKU ID"},{"name":"relSkuId","type":"BIGINT","comment":"外部SKU ID"},{"name":"skuNick","type":"VARCHAR(100)","comment":"SKU别名"},{"name":"handlingWay","type":"INT","comment":"售后方式"},{"name":"negotiateStatus","type":"INT","comment":"协商状态"},{"name":"refundFee","type":"BIGINT","comment":"退款金额(分)"},{"name":"refundReason","type":"INT","comment":"退款原因码"},{"name":"refundReasonDesc","type":"VARCHAR(500)","comment":"退款原因描述"},{"name":"refundDesc","type":"TEXT","comment":"退款描述"},{"name":"refundType","type":"INT","comment":"退款类型"},{"name":"status","type":"INT","comment":"退款状态"},{"name":"receiptStatus","type":"INT","comment":"收货状态"},{"name":"buyerId","type":"BIGINT","comment":"买家ID"},{"name":"sellerId","type":"BIGINT","comment":"卖家ID"},{"name":"logisticsId","type":"BIGINT","comment":"物流ID"},{"name":"relItemId","type":"BIGINT","comment":"关联商品ID"},{"name":"submitTime","type":"BIGINT","comment":"提交时间"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"negotiateUpdateTime","type":"BIGINT","comment":"协商更新时间"},{"name":"endTime","type":"BIGINT","comment":"结束时间"},{"name":"expireTime","type":"BIGINT","comment":"过期时间"}],"conflict_keys":["refundId"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'售后单列表','refund_list','/open/seller/order/refund/pcursor/list','POST','active','inherit','{"pageSize":50,"page_param":"pcursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.seller.order.refund.pcursor.list","version":1,"signMethod":"MD5","type":9,"sort":1,"queryType":2,"currentPage":1,"time_field":"updateTime","time_field_mode":"range","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.refundOrderInfoList","cursor_field":"data.pcursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_refund_list","columns":[{"name":"refundId","type":"BIGINT","comment":"退款单ID"},{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"itemId","type":"BIGINT","comment":"商品ID"},{"name":"skuId","type":"BIGINT","comment":"SKU ID"},{"name":"relSkuId","type":"BIGINT","comment":"外部SKU ID"},{"name":"skuNick","type":"VARCHAR(100)","comment":"SKU别名"},{"name":"handlingWay","type":"INT","comment":"售后方式"},{"name":"negotiateStatus","type":"INT","comment":"协商状态"},{"name":"refundFee","type":"BIGINT","comment":"退款金额(分)"},{"name":"refundReason","type":"INT","comment":"退款原因码"},{"name":"refundReasonDesc","type":"VARCHAR(500)","comment":"退款原因描述"},{"name":"refundDesc","type":"TEXT","comment":"退款描述"},{"name":"refundType","type":"INT","comment":"退款类型"},{"name":"status","type":"INT","comment":"退款状态"},{"name":"receiptStatus","type":"INT","comment":"收货状态"},{"name":"buyerId","type":"BIGINT","comment":"买家ID"},{"name":"sellerId","type":"BIGINT","comment":"卖家ID"},{"name":"logisticsId","type":"BIGINT","comment":"物流ID"},{"name":"relItemId","type":"BIGINT","comment":"关联商品ID"},{"name":"submitTime","type":"BIGINT","comment":"提交时间"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"negotiateUpdateTime","type":"BIGINT","comment":"协商更新时间"},{"name":"endTime","type":"BIGINT","comment":"结束时间"},{"name":"expireTime","type":"BIGINT","comment":"过期时间"}],"conflict_keys":["refundId"]}'::jsonb);
|
||||
|
||||
-- 8. 售后单详情(prefetch→refund_list)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'售后单详情','refund_detail','/open/seller/order/refund/detail','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.seller.order.refund.detail","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"prefetch":{"url":"/open/seller/order/refund/pcursor/list","method":"GET","response_path":"data.refundOrderInfoList","target_param":"refundId","value_field":"refundId"}}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_refund_detail","columns":[{"name":"refundId","type":"BIGINT","comment":"退款单ID"},{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"itemId","type":"BIGINT","comment":"商品ID"},{"name":"skuId","type":"BIGINT","comment":"SKU ID"},{"name":"relSkuId","type":"BIGINT","comment":"外部SKU ID"},{"name":"relItemId","type":"BIGINT","comment":"关联商品ID"},{"name":"handlingWay","type":"INT","comment":"售后方式"},{"name":"negotiateStatus","type":"INT","comment":"协商状态"},{"name":"refundFee","type":"BIGINT","comment":"退款金额(分)"},{"name":"refundReason","type":"INT","comment":"退款原因码"},{"name":"refundReasonDesc","type":"VARCHAR(500)","comment":"退款原因描述"},{"name":"refundDesc","type":"TEXT","comment":"退款描述"},{"name":"refundType","type":"INT","comment":"退款类型"},{"name":"status","type":"INT","comment":"退款状态"},{"name":"receiptStatus","type":"INT","comment":"收货状态"},{"name":"buyerId","type":"BIGINT","comment":"买家ID"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家openId"},{"name":"sellerId","type":"BIGINT","comment":"卖家ID"},{"name":"logisticsId","type":"BIGINT","comment":"物流ID"},{"name":"productNum","type":"INT","comment":"商品数量"},{"name":"specialRefundType","type":"INT","comment":"特殊退款类型"},{"name":"sellerDisagreeReason","type":"INT","comment":"卖家拒绝原因码"},{"name":"sellerDisagreeDesc","type":"TEXT","comment":"卖家拒绝描述"},{"name":"negotiateReason","type":"VARCHAR(500)","comment":"协商原因"},{"name":"address","type":"TEXT","comment":"收货地址"},{"name":"validNegotiateBuyerModifyTimeStamp","type":"BIGINT","comment":"有效协商修改时间戳"},{"name":"timeLimitNegotiateChange","type":"INT","comment":"协商变更时限"},{"name":"submitTime","type":"BIGINT","comment":"提交时间"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"negotiateUpdateTime","type":"BIGINT","comment":"协商更新时间"},{"name":"endTime","type":"BIGINT","comment":"结束时间"},{"name":"expireTime","type":"BIGINT","comment":"过期时间"}],"conflict_keys":["refundId"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'售后单详情','refund_detail','/open/seller/order/refund/detail','POST','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.seller.order.refund.detail","version":1,"signMethod":"MD5","prefetch":{"url":"/open/seller/order/refund/pcursor/list","method":"GET","response_path":"data.refundOrderInfoList","target_param":"refundId","value_field":"refundId"},"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_refund_detail","columns":[{"name":"refundId","type":"BIGINT","comment":"退款单ID"},{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"itemId","type":"BIGINT","comment":"商品ID"},{"name":"skuId","type":"BIGINT","comment":"SKU ID"},{"name":"relSkuId","type":"BIGINT","comment":"外部SKU ID"},{"name":"relItemId","type":"BIGINT","comment":"关联商品ID"},{"name":"handlingWay","type":"INT","comment":"售后方式"},{"name":"negotiateStatus","type":"INT","comment":"协商状态"},{"name":"refundFee","type":"BIGINT","comment":"退款金额(分)"},{"name":"refundReason","type":"INT","comment":"退款原因码"},{"name":"refundReasonDesc","type":"VARCHAR(500)","comment":"退款原因描述"},{"name":"refundDesc","type":"TEXT","comment":"退款描述"},{"name":"refundType","type":"INT","comment":"退款类型"},{"name":"status","type":"INT","comment":"退款状态"},{"name":"receiptStatus","type":"INT","comment":"收货状态"},{"name":"buyerId","type":"BIGINT","comment":"买家ID"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家openId"},{"name":"sellerId","type":"BIGINT","comment":"卖家ID"},{"name":"logisticsId","type":"BIGINT","comment":"物流ID"},{"name":"productNum","type":"INT","comment":"商品数量"},{"name":"specialRefundType","type":"INT","comment":"特殊退款类型"},{"name":"sellerDisagreeReason","type":"INT","comment":"卖家拒绝原因码"},{"name":"sellerDisagreeDesc","type":"TEXT","comment":"卖家拒绝描述"},{"name":"negotiateReason","type":"VARCHAR(500)","comment":"协商原因"},{"name":"address","type":"TEXT","comment":"收货地址"},{"name":"validNegotiateBuyerModifyTimeStamp","type":"BIGINT","comment":"有效协商修改时间戳"},{"name":"timeLimitNegotiateChange","type":"INT","comment":"协商变更时限"},{"name":"submitTime","type":"BIGINT","comment":"提交时间"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"negotiateUpdateTime","type":"BIGINT","comment":"协商更新时间"},{"name":"endTime","type":"BIGINT","comment":"结束时间"},{"name":"expireTime","type":"BIGINT","comment":"过期时间"}],"conflict_keys":["refundId"]}'::jsonb);
|
||||
|
||||
-- 9. 商品类目(全量,data直接是数组)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'商品类目','category','/open/item/category','GET','active','inherit','{"parameters_location":"query","pageSize":100,"page_param":"cursor","page_size_param":"pageSize","method":"open.item.category","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data"}'::jsonb,'{"table_name":"kuaishou_category","columns":[{"name":"categoryId","type":"BIGINT","comment":"类目ID"},{"name":"categoryName","type":"VARCHAR(300)","comment":"类目名称"},{"name":"categoryPid","type":"BIGINT","comment":"父级类目ID 0为根类目"}],"conflict_keys":["categoryId"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'商品类目','category','/open/item/category','POST','active','inherit','{"pageSize":100,"page_param":"cursor","page_size_param":"pageSize","method":"open.item.category","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data"}'::jsonb,'{"table_name":"kuaishou_category","columns":[{"name":"categoryId","type":"BIGINT","comment":"类目ID"},{"name":"categoryName","type":"VARCHAR(300)","comment":"类目名称"},{"name":"categoryPid","type":"BIGINT","comment":"父级类目ID 0为根类目"}],"conflict_keys":["categoryId"]}'::jsonb);
|
||||
|
||||
-- 10. 带货口碑分(单记录,全量)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'带货口碑分','score_master','/open/score/master/get','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.score.master.get","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_score_master","columns":[{"name":"hasData","type":"BOOLEAN","comment":"是否有数据"},{"name":"showName","type":"VARCHAR(100)","comment":"分值文案描述"},{"name":"scoreStr","type":"VARCHAR(20)","comment":"分值(保留两位小数)"}],"conflict_keys":["showName"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'带货口碑分','score_master','/open/score/master/get','GET','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.score.master.get","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_score_master","columns":[{"name":"hasData","type":"BOOLEAN","comment":"是否有数据"},{"name":"showName","type":"VARCHAR(100)","comment":"分值文案描述"},{"name":"scoreStr","type":"VARCHAR(20)","comment":"分值(保留两位小数)"}],"conflict_keys":["showName"]}'::jsonb);
|
||||
|
||||
-- 11. 店铺体验分(单记录,全量)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'店铺体验分','score_shop','/open/score/shop/get','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.score.shop.get","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_score_shop","columns":[{"name":"hasData","type":"BOOLEAN","comment":"是否有数据"},{"name":"showName","type":"VARCHAR(100)","comment":"分值文案描述"},{"name":"scoreStr","type":"VARCHAR(20)","comment":"分值(保留两位小数)"}],"conflict_keys":["showName"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'店铺体验分','score_shop','/open/score/shop/get','GET','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.score.shop.get","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_score_shop","columns":[{"name":"hasData","type":"BOOLEAN","comment":"是否有数据"},{"name":"showName","type":"VARCHAR(100)","comment":"分值文案描述"},{"name":"scoreStr","type":"VARCHAR(20)","comment":"分值(保留两位小数)"}],"conflict_keys":["showName"]}'::jsonb);
|
||||
|
||||
-- 12. 店铺信息(单记录,全量)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'店铺信息','shop_info','/open/shop/info/get','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.shop.info.get","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_shop_info","columns":[{"name":"shopName","type":"VARCHAR(300)","comment":"店铺全名"},{"name":"shopType","type":"INT","comment":"店铺类型 1旗舰 2专卖 3专营"},{"name":"shopExpScoreStr","type":"VARCHAR(20)","comment":"购物体验分"},{"name":"productQualityScoreStr","type":"VARCHAR(20)","comment":"商品品质分"},{"name":"contentQualifyScoreStr","type":"VARCHAR(20)","comment":"内容质量分"},{"name":"customerServiceScoreStr","type":"VARCHAR(20)","comment":"客服服务分"},{"name":"logisticsServiceScoreStr","type":"VARCHAR(20)","comment":"物流服务分"},{"name":"afterSalesServiceScoreStr","type":"VARCHAR(20)","comment":"售后服务分"}],"conflict_keys":["shopName"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'店铺信息','shop_info','/open/shop/info/get','GET','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.shop.info.get","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_shop_info","columns":[{"name":"shopName","type":"VARCHAR(300)","comment":"店铺全名"},{"name":"shopType","type":"INT","comment":"店铺类型 1旗舰 2专卖 3专营"},{"name":"shopExpScoreStr","type":"VARCHAR(20)","comment":"购物体验分"},{"name":"productQualityScoreStr","type":"VARCHAR(20)","comment":"商品品质分"},{"name":"contentQualifyScoreStr","type":"VARCHAR(20)","comment":"内容质量分"},{"name":"customerServiceScoreStr","type":"VARCHAR(20)","comment":"客服服务分"},{"name":"logisticsServiceScoreStr","type":"VARCHAR(20)","comment":"物流服务分"},{"name":"afterSalesServiceScoreStr","type":"VARCHAR(20)","comment":"售后服务分"}],"conflict_keys":["shopName"]}'::jsonb);
|
||||
|
||||
-- 13. 门店POI详情(单记录,需outerPoiId+source)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'门店POI详情','poi_detail','/open/shop/poi/getPoiDetailByOuterPoi','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.shop.poi.getPoiDetailByOuterPoi","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_poi_detail","columns":[{"name":"outerPoiId","type":"VARCHAR(100)","comment":"图商poiId"},{"name":"source","type":"INT","comment":"图商来源 1高德 2百度 3腾讯"},{"name":"poiId","type":"VARCHAR(100)","comment":"快手poiId"},{"name":"name","type":"VARCHAR(300)","comment":"poi名称"},{"name":"longitude","type":"VARCHAR(50)","comment":"经度"},{"name":"latitude","type":"VARCHAR(50)","comment":"纬度"},{"name":"address","type":"TEXT","comment":"地址信息"},{"name":"country","type":"VARCHAR(100)","comment":"国家"},{"name":"province","type":"VARCHAR(100)","comment":"省份"},{"name":"city","type":"VARCHAR(100)","comment":"城市"},{"name":"district","type":"VARCHAR(100)","comment":"区县"},{"name":"provinceCode","type":"BIGINT","comment":"省份编码"},{"name":"cityCode","type":"BIGINT","comment":"城市编码"},{"name":"districtCode","type":"BIGINT","comment":"区县编码"}],"conflict_keys":["poiId"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'门店POI详情','poi_detail','/open/shop/poi/getPoiDetailByOuterPoi','POST','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.shop.poi.getPoiDetailByOuterPoi","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_poi_detail","columns":[{"name":"outerPoiId","type":"VARCHAR(100)","comment":"图商poiId"},{"name":"source","type":"INT","comment":"图商来源 1高德 2百度 3腾讯"},{"name":"poiId","type":"VARCHAR(100)","comment":"快手poiId"},{"name":"name","type":"VARCHAR(300)","comment":"poi名称"},{"name":"longitude","type":"VARCHAR(50)","comment":"经度"},{"name":"latitude","type":"VARCHAR(50)","comment":"纬度"},{"name":"address","type":"TEXT","comment":"地址信息"},{"name":"country","type":"VARCHAR(100)","comment":"国家"},{"name":"province","type":"VARCHAR(100)","comment":"省份"},{"name":"city","type":"VARCHAR(100)","comment":"城市"},{"name":"district","type":"VARCHAR(100)","comment":"区县"},{"name":"provinceCode","type":"BIGINT","comment":"省份编码"},{"name":"cityCode","type":"BIGINT","comment":"城市编码"},{"name":"districtCode","type":"BIGINT","comment":"区县编码"}],"conflict_keys":["poiId"]}'::jsonb);
|
||||
|
||||
-- 14. 分销订单列表(游标pcursor,增量range,最大3天)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'分销订单列表','cps_order_list','/open/seller/order/cps/list','GET','active','inherit','{"parameters_location":"query","pageSize":80,"page_param":"pcursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.seller.order.cps.list","version":1,"signMethod":"MD5","sort":1,"queryType":2,"type":1,"currentPage":1,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"time_field":"updateTime","time_field_mode":"range"}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.cpsOrderList","cursor_field":"data.pcursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_cps_order_list","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"distributorId","type":"BIGINT","comment":"分销者ID"},{"name":"distributorName","type":"VARCHAR(200)","comment":"分销者昵称"},{"name":"sellerId","type":"BIGINT","comment":"卖家ID"},{"name":"status","type":"INT","comment":"分销单状态"},{"name":"settlementTime","type":"BIGINT","comment":"结算时间"},{"name":"settlementSuccessTime","type":"BIGINT","comment":"结算成功时间"},{"name":"refundTime","type":"BIGINT","comment":"退款时间"},{"name":"payTime","type":"BIGINT","comment":"付款时间"},{"name":"expressFee","type":"BIGINT","comment":"运费(分)"},{"name":"totalFee","type":"BIGINT","comment":"实际付款金额-运费(分)"},{"name":"commissionRate","type":"BIGINT","comment":"分销率"},{"name":"estimatedIncome","type":"BIGINT","comment":"分销金额(分)"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"platformDpRate","type":"BIGINT","comment":"平台佣金比例 千分比"},{"name":"activityUserId","type":"BIGINT","comment":"团长ID"},{"name":"activityUserNickname","type":"VARCHAR(200)","comment":"团长昵称"},{"name":"investmentPromotionRate","type":"INT","comment":"团长服务费率 千分比"},{"name":"investmentPromotionAmount","type":"BIGINT","comment":"团长服务费金额(分)"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家识别ID"},{"name":"settlementBizType","type":"INT","comment":"订单业务类型 1快分销 2聚力计划"},{"name":"promoterServiceInCome","type":"BIGINT","comment":"达人接单服务收入(分)"},{"name":"promoterExcitationInCome","type":"BIGINT","comment":"达人奖励收入(分)"},{"name":"investmentServiceInCome","type":"BIGINT","comment":"团长接单收入(分)"},{"name":"investmentExcitationInCome","type":"BIGINT","comment":"团长奖励收入(分)"},{"name":"orderChannel","type":"VARCHAR(100)","comment":"出单渠道"},{"name":"activityId","type":"BIGINT","comment":"活动ID"},{"name":"itemId","type":"BIGINT","comment":"商品ID"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'分销订单列表','cps_order_list','/open/seller/order/cps/list','POST','active','inherit','{"pageSize":80,"page_param":"pcursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.seller.order.cps.list","version":1,"signMethod":"MD5","sort":1,"queryType":2,"type":1,"currentPage":1,"time_field":"updateTime","time_field_mode":"range","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.cpsOrderList","cursor_field":"data.pcursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_cps_order_list","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"distributorId","type":"BIGINT","comment":"分销者ID"},{"name":"distributorName","type":"VARCHAR(200)","comment":"分销者昵称"},{"name":"sellerId","type":"BIGINT","comment":"卖家ID"},{"name":"status","type":"INT","comment":"分销单状态"},{"name":"settlementTime","type":"BIGINT","comment":"结算时间"},{"name":"settlementSuccessTime","type":"BIGINT","comment":"结算成功时间"},{"name":"refundTime","type":"BIGINT","comment":"退款时间"},{"name":"payTime","type":"BIGINT","comment":"付款时间"},{"name":"expressFee","type":"BIGINT","comment":"运费(分)"},{"name":"totalFee","type":"BIGINT","comment":"实际付款金额-运费(分)"},{"name":"commissionRate","type":"BIGINT","comment":"分销率"},{"name":"estimatedIncome","type":"BIGINT","comment":"分销金额(分)"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"platformDpRate","type":"BIGINT","comment":"平台佣金比例 千分比"},{"name":"activityUserId","type":"BIGINT","comment":"团长ID"},{"name":"activityUserNickname","type":"VARCHAR(200)","comment":"团长昵称"},{"name":"investmentPromotionRate","type":"INT","comment":"团长服务费率 千分比"},{"name":"investmentPromotionAmount","type":"BIGINT","comment":"团长服务费金额(分)"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家识别ID"},{"name":"settlementBizType","type":"INT","comment":"订单业务类型 1快分销 2聚力计划"},{"name":"promoterServiceInCome","type":"BIGINT","comment":"达人接单服务收入(分)"},{"name":"promoterExcitationInCome","type":"BIGINT","comment":"达人奖励收入(分)"},{"name":"investmentServiceInCome","type":"BIGINT","comment":"团长接单收入(分)"},{"name":"investmentExcitationInCome","type":"BIGINT","comment":"团长奖励收入(分)"},{"name":"orderChannel","type":"VARCHAR(100)","comment":"出单渠道"},{"name":"activityId","type":"BIGINT","comment":"活动ID"},{"name":"itemId","type":"BIGINT","comment":"商品ID"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
|
||||
-- 15. 分销订单详情(prefetch→cps_order_list)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'分销订单详情','cps_order_detail','/open/seller/order/cps/detail','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.seller.order.cps.detail","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"prefetch":{"url":"/open/seller/order/cps/list","method":"GET","response_path":"data.cpsOrderList","target_param":"orderId","value_field":"oid"}}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_cps_order_detail","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"distributorId","type":"BIGINT","comment":"分销者ID"},{"name":"distributorName","type":"VARCHAR(200)","comment":"分销者昵称"},{"name":"sellerId","type":"BIGINT","comment":"卖家ID"},{"name":"status","type":"INT","comment":"分销单状态"},{"name":"settlementTime","type":"BIGINT","comment":"结算时间"},{"name":"settlementSuccessTime","type":"BIGINT","comment":"结算成功时间"},{"name":"refundTime","type":"BIGINT","comment":"退款时间"},{"name":"payTime","type":"BIGINT","comment":"付款时间"},{"name":"expressFee","type":"BIGINT","comment":"运费(分)"},{"name":"totalFee","type":"BIGINT","comment":"实际付款金额-运费(分)"},{"name":"commissionRate","type":"BIGINT","comment":"分销率"},{"name":"estimatedIncome","type":"BIGINT","comment":"分销金额(分)"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"platformDpRate","type":"BIGINT","comment":"平台佣金比例 千分比"},{"name":"activityUserId","type":"BIGINT","comment":"团长ID"},{"name":"activityUserNickname","type":"VARCHAR(200)","comment":"团长昵称"},{"name":"investmentPromotionRate","type":"INT","comment":"团长服务费率 千分比"},{"name":"investmentPromotionAmount","type":"BIGINT","comment":"团长服务费金额(分)"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家识别ID"},{"name":"settlementBizType","type":"INT","comment":"订单业务类型 1快分销 2聚力计划"},{"name":"promoterServiceInCome","type":"BIGINT","comment":"达人接单服务收入(分)"},{"name":"promoterExcitationInCome","type":"BIGINT","comment":"达人奖励收入(分)"},{"name":"investmentServiceInCome","type":"BIGINT","comment":"团长接单收入(分)"},{"name":"investmentExcitationInCome","type":"BIGINT","comment":"团长奖励收入(分)"},{"name":"orderChannel","type":"VARCHAR(100)","comment":"出单渠道"},{"name":"activityId","type":"BIGINT","comment":"活动ID"},{"name":"itemId","type":"BIGINT","comment":"商品ID"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'分销订单详情','cps_order_detail','/open/seller/order/cps/detail','POST','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.seller.order.cps.detail","version":1,"signMethod":"MD5","prefetch":{"url":"/open/seller/order/cps/list","method":"GET","response_path":"data.cpsOrderList","target_param":"orderId","value_field":"oid"},"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_cps_order_detail","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"distributorId","type":"BIGINT","comment":"分销者ID"},{"name":"distributorName","type":"VARCHAR(200)","comment":"分销者昵称"},{"name":"sellerId","type":"BIGINT","comment":"卖家ID"},{"name":"status","type":"INT","comment":"分销单状态"},{"name":"settlementTime","type":"BIGINT","comment":"结算时间"},{"name":"settlementSuccessTime","type":"BIGINT","comment":"结算成功时间"},{"name":"refundTime","type":"BIGINT","comment":"退款时间"},{"name":"payTime","type":"BIGINT","comment":"付款时间"},{"name":"expressFee","type":"BIGINT","comment":"运费(分)"},{"name":"totalFee","type":"BIGINT","comment":"实际付款金额-运费(分)"},{"name":"commissionRate","type":"BIGINT","comment":"分销率"},{"name":"estimatedIncome","type":"BIGINT","comment":"分销金额(分)"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"},{"name":"platformDpRate","type":"BIGINT","comment":"平台佣金比例 千分比"},{"name":"activityUserId","type":"BIGINT","comment":"团长ID"},{"name":"activityUserNickname","type":"VARCHAR(200)","comment":"团长昵称"},{"name":"investmentPromotionRate","type":"INT","comment":"团长服务费率 千分比"},{"name":"investmentPromotionAmount","type":"BIGINT","comment":"团长服务费金额(分)"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家识别ID"},{"name":"settlementBizType","type":"INT","comment":"订单业务类型 1快分销 2聚力计划"},{"name":"promoterServiceInCome","type":"BIGINT","comment":"达人接单服务收入(分)"},{"name":"promoterExcitationInCome","type":"BIGINT","comment":"达人奖励收入(分)"},{"name":"investmentServiceInCome","type":"BIGINT","comment":"团长接单收入(分)"},{"name":"investmentExcitationInCome","type":"BIGINT","comment":"团长奖励收入(分)"},{"name":"orderChannel","type":"VARCHAR(100)","comment":"出单渠道"},{"name":"activityId","type":"BIGINT","comment":"活动ID"},{"name":"itemId","type":"BIGINT","comment":"商品ID"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
|
||||
-- 16. 达人分销订单列表(游标pcursor,增量range,最大7天)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'达人分销订单列表','distributor_order_list','/open/distribution/cps/distributor/order/cursor/list','GET','active','inherit','{"parameters_location":"query","pageSize":100,"page_param":"pcursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.distribution.cps.distributor.order.cursor.list","version":1,"signMethod":"MD5","sortType":1,"queryType":2,"cpsOrderStatus":0,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"time_field":"updateTime","time_field_mode":"range"}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.orderView","cursor_field":"data.pcursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_distributor_order_list","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"distributorId","type":"BIGINT","comment":"分销者ID"},{"name":"cpsOrderStatus","type":"INT","comment":"分销订单状态"},{"name":"orderCreateTime","type":"BIGINT","comment":"订单创建时间"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"sendTime","type":"BIGINT","comment":"发货时间"},{"name":"sendStatus","type":"INT","comment":"发货状态"},{"name":"recvTime","type":"BIGINT","comment":"收货时间"},{"name":"orderTradeAmount","type":"BIGINT","comment":"订单交易金额(分)"},{"name":"baseAmount","type":"BIGINT","comment":"结算基数"},{"name":"shareRateStr","type":"VARCHAR(20)","comment":"分佣比例"},{"name":"settlementBizType","type":"INT","comment":"订单业务类型"},{"name":"settlementAmount","type":"BIGINT","comment":"结算金额(分)"},{"name":"settlementSuccessTime","type":"BIGINT","comment":"结算成功时间"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家识别ID"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'达人分销订单列表','distributor_order_list','/open/distribution/cps/distributor/order/cursor/list','POST','active','inherit','{"pageSize":100,"page_param":"pcursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.distribution.cps.distributor.order.cursor.list","version":1,"signMethod":"MD5","sortType":1,"queryType":2,"cpsOrderStatus":0,"time_field":"updateTime","time_field_mode":"range","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.orderView","cursor_field":"data.pcursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_distributor_order_list","columns":[{"name":"oid","type":"BIGINT","comment":"订单ID"},{"name":"distributorId","type":"BIGINT","comment":"分销者ID"},{"name":"cpsOrderStatus","type":"INT","comment":"分销订单状态"},{"name":"orderCreateTime","type":"BIGINT","comment":"订单创建时间"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"sendTime","type":"BIGINT","comment":"发货时间"},{"name":"sendStatus","type":"INT","comment":"发货状态"},{"name":"recvTime","type":"BIGINT","comment":"收货时间"},{"name":"orderTradeAmount","type":"BIGINT","comment":"订单交易金额(分)"},{"name":"baseAmount","type":"BIGINT","comment":"结算基数"},{"name":"shareRateStr","type":"VARCHAR(20)","comment":"分佣比例"},{"name":"settlementBizType","type":"INT","comment":"订单业务类型"},{"name":"settlementAmount","type":"BIGINT","comment":"结算金额(分)"},{"name":"settlementSuccessTime","type":"BIGINT","comment":"结算成功时间"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家识别ID"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"更新时间"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
|
||||
-- 17. 开票金额查询(单记录,prefetch→order_list)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'开票金额','invoice_amount','/open/invoice/amount/get','GET','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.invoice.amount.get","version":1,"signMethod":"MD5","fromType":"1","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"prefetch":{"url":"/open/order/cursor/list","method":"GET","response_path":"data.orderList","target_param":"orderId","value_field":"oid"}}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"queryInvoiceAmountResponseData","single_record":true}'::jsonb,'{"table_name":"kuaishou_invoice_amount","columns":[{"name":"toReceiverInvoiceAmount","type":"VARCHAR(50)","comment":"达人开票金额"},{"name":"toBuyerInvoiceAmount","type":"VARCHAR(50)","comment":"买家开票金额"},{"name":"toPlatformInvoiceAmount","type":"VARCHAR(50)","comment":"平台开票金额"},{"name":"orderStatus","type":"BIGINT","comment":"订单状态"},{"name":"preSaleOrder","type":"BOOLEAN","comment":"是否定金预售"},{"name":"platformAllowanceAmount","type":"VARCHAR(50)","comment":"平台补贴"},{"name":"freightWhenOrder","type":"VARCHAR(50)","comment":"运费"},{"name":"orderId","type":"BIGINT","comment":"订单ID"},{"name":"userPayAmount","type":"VARCHAR(50)","comment":"用户实付"},{"name":"receiverSubsidyAmount","type":"VARCHAR(50)","comment":"达人补贴"},{"name":"queryTime","type":"VARCHAR(50)","comment":"查询时间"}],"conflict_keys":["orderId"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'开票金额','invoice_amount','/open/invoice/amount/get','GET','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.invoice.amount.get","version":1,"signMethod":"MD5","fromType":"1","parameters_location":"query","prefetch":{"url":"/open/order/cursor/list","method":"GET","response_path":"data.orderList","target_param":"orderId","value_field":"oid"},"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"queryInvoiceAmountResponseData","single_record":true}'::jsonb,'{"table_name":"kuaishou_invoice_amount","columns":[{"name":"toReceiverInvoiceAmount","type":"VARCHAR(50)","comment":"达人开票金额"},{"name":"toBuyerInvoiceAmount","type":"VARCHAR(50)","comment":"买家开票金额"},{"name":"toPlatformInvoiceAmount","type":"VARCHAR(50)","comment":"平台开票金额"},{"name":"orderStatus","type":"BIGINT","comment":"订单状态"},{"name":"preSaleOrder","type":"BOOLEAN","comment":"是否定金预售"},{"name":"platformAllowanceAmount","type":"VARCHAR(50)","comment":"平台补贴"},{"name":"freightWhenOrder","type":"VARCHAR(50)","comment":"运费"},{"name":"orderId","type":"BIGINT","comment":"订单ID"},{"name":"userPayAmount","type":"VARCHAR(50)","comment":"用户实付"},{"name":"receiverSubsidyAmount","type":"VARCHAR(50)","comment":"达人补贴"},{"name":"queryTime","type":"VARCHAR(50)","comment":"查询时间"}],"conflict_keys":["orderId"]}'::jsonb);
|
||||
|
||||
-- 18. 代发订单列表(游标cursor,POST,全量最大30天)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'代发订单列表','dropshipping_order_list','/open/dropshipping/order/merchant/list','POST','active','inherit','{"parameters_location":"query","pageSize":50,"page_param":"cursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.dropshipping.order.merchant.list","version":1,"signMethod":"MD5","dropshippingStatus":0,"orderStatus":0,"refundStatus":0,"orderType":200,"queryType":"PAT_TIME","sort":0,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.orderList","cursor_field":"data.cursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_dropshipping_order_list","columns":[{"name":"oid","type":"BIGINT","comment":"交易订单ID"},{"name":"dropshippingOrderCode","type":"VARCHAR(100)","comment":"代发订单编码"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家开放id"},{"name":"buyerNick","type":"VARCHAR(100)","comment":"买家昵称"},{"name":"sellerOpenId","type":"VARCHAR(100)","comment":"卖家开放id"},{"name":"sellerNick","type":"VARCHAR(100)","comment":"卖家昵称"},{"name":"orderStatus","type":"INT","comment":"订单状态"},{"name":"orderStatusDesc","type":"VARCHAR(100)","comment":"订单状态描述"},{"name":"refundStatus","type":"INT","comment":"售后状态"},{"name":"refundStatusDesc","type":"VARCHAR(100)","comment":"售后状态描述"},{"name":"orderType","type":"INT","comment":"订单类型"},{"name":"orderTypeDesc","type":"VARCHAR(100)","comment":"订单类型描述"},{"name":"deliveryTime","type":"BIGINT","comment":"发货时间"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"修改时间"},{"name":"waybillCode","type":"VARCHAR(100)","comment":"运单号"},{"name":"expressCompanyCode","type":"VARCHAR(50)","comment":"物流公司编码"},{"name":"expressCompanyName","type":"VARCHAR(100)","comment":"物流公司名称"},{"name":"factoryCode","type":"VARCHAR(100)","comment":"代发厂家编码"},{"name":"factoryName","type":"VARCHAR(200)","comment":"代发厂家名称"},{"name":"allocateTime","type":"BIGINT","comment":"分配时间"},{"name":"dropshippingStatus","type":"INT","comment":"代发状态"},{"name":"dropshippingStatusDesc","type":"VARCHAR(100)","comment":"代发状态描述"},{"name":"cancelAllocateTime","type":"BIGINT","comment":"取消分配时间"},{"name":"cancelAllocateReason","type":"TEXT","comment":"取消分配原因"},{"name":"name","type":"VARCHAR(100)","comment":"收货人姓名"},{"name":"mobile","type":"VARCHAR(50)","comment":"手机号码"},{"name":"provinceName","type":"VARCHAR(100)","comment":"省份"},{"name":"cityName","type":"VARCHAR(100)","comment":"城市"},{"name":"districtName","type":"VARCHAR(100)","comment":"区县"},{"name":"detailAddress","type":"TEXT","comment":"详细地址"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'代发订单列表','dropshipping_order_list','/open/dropshipping/order/merchant/list','POST','active','inherit','{"pageSize":50,"page_param":"cursor","page_size_param":"pageSize","cursor_pagination":true,"method":"open.dropshipping.order.merchant.list","version":1,"signMethod":"MD5","dropshippingStatus":0,"orderStatus":0,"refundStatus":0,"orderType":200,"queryType":"PAT_TIME","sort":0,"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data.orderList","cursor_field":"data.cursor","cursor_end_marker":"nomore"}'::jsonb,'{"table_name":"kuaishou_dropshipping_order_list","columns":[{"name":"oid","type":"BIGINT","comment":"交易订单ID"},{"name":"dropshippingOrderCode","type":"VARCHAR(100)","comment":"代发订单编码"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家开放id"},{"name":"buyerNick","type":"VARCHAR(100)","comment":"买家昵称"},{"name":"sellerOpenId","type":"VARCHAR(100)","comment":"卖家开放id"},{"name":"sellerNick","type":"VARCHAR(100)","comment":"卖家昵称"},{"name":"orderStatus","type":"INT","comment":"订单状态"},{"name":"orderStatusDesc","type":"VARCHAR(100)","comment":"订单状态描述"},{"name":"refundStatus","type":"INT","comment":"售后状态"},{"name":"refundStatusDesc","type":"VARCHAR(100)","comment":"售后状态描述"},{"name":"orderType","type":"INT","comment":"订单类型"},{"name":"orderTypeDesc","type":"VARCHAR(100)","comment":"订单类型描述"},{"name":"deliveryTime","type":"BIGINT","comment":"发货时间"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"修改时间"},{"name":"waybillCode","type":"VARCHAR(100)","comment":"运单号"},{"name":"expressCompanyCode","type":"VARCHAR(50)","comment":"物流公司编码"},{"name":"expressCompanyName","type":"VARCHAR(100)","comment":"物流公司名称"},{"name":"factoryCode","type":"VARCHAR(100)","comment":"代发厂家编码"},{"name":"factoryName","type":"VARCHAR(200)","comment":"代发厂家名称"},{"name":"allocateTime","type":"BIGINT","comment":"分配时间"},{"name":"dropshippingStatus","type":"INT","comment":"代发状态"},{"name":"dropshippingStatusDesc","type":"VARCHAR(100)","comment":"代发状态描述"},{"name":"cancelAllocateTime","type":"BIGINT","comment":"取消分配时间"},{"name":"cancelAllocateReason","type":"TEXT","comment":"取消分配原因"},{"name":"name","type":"VARCHAR(100)","comment":"收货人姓名"},{"name":"mobile","type":"VARCHAR(50)","comment":"手机号码"},{"name":"provinceName","type":"VARCHAR(100)","comment":"省份"},{"name":"cityName","type":"VARCHAR(100)","comment":"城市"},{"name":"districtName","type":"VARCHAR(100)","comment":"区县"},{"name":"detailAddress","type":"TEXT","comment":"详细地址"}],"conflict_keys":["oid"]}'::jsonb);
|
||||
|
||||
-- 19. 代发订单详情(prefetch→dropshipping_order_list,单记录)
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'代发订单详情','dropshipping_order_detail','/open/dropshipping/order/merchant/detail','POST','active','inherit','{"parameters_location":"query","pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.dropshipping.order.merchant.detail","version":1,"signMethod":"MD5","body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"],"prefetch":{"url":"/open/dropshipping/order/merchant/list","method":"POST","response_path":"data.orderList","target_param":"dropshippingOrderCode","value_field":"dropshippingOrderCode"}}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_dropshipping_order_detail","columns":[{"name":"dropshippingOrderCode","type":"VARCHAR(100)","comment":"代发订单编码"},{"name":"oid","type":"BIGINT","comment":"交易订单ID"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家开放id"},{"name":"buyerNick","type":"VARCHAR(100)","comment":"买家昵称"},{"name":"sellerOpenId","type":"VARCHAR(100)","comment":"卖家开放id"},{"name":"sellerNick","type":"VARCHAR(100)","comment":"卖家昵称"},{"name":"orderStatus","type":"INT","comment":"订单状态"},{"name":"orderStatusDesc","type":"VARCHAR(100)","comment":"订单状态描述"},{"name":"refundStatus","type":"INT","comment":"售后状态"},{"name":"refundStatusDesc","type":"VARCHAR(100)","comment":"售后状态描述"},{"name":"orderType","type":"INT","comment":"订单类型"},{"name":"orderTypeDesc","type":"VARCHAR(100)","comment":"订单类型描述"},{"name":"deliveryTime","type":"BIGINT","comment":"发货时间"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"修改时间"},{"name":"waybillCode","type":"VARCHAR(100)","comment":"运单号"},{"name":"expressCompanyCode","type":"VARCHAR(50)","comment":"物流公司编码"},{"name":"expressCompanyName","type":"VARCHAR(100)","comment":"物流公司名称"},{"name":"factoryCode","type":"VARCHAR(100)","comment":"代发厂家编码"},{"name":"factoryName","type":"VARCHAR(200)","comment":"代发厂家名称"},{"name":"allocateTime","type":"BIGINT","comment":"分配时间"},{"name":"dropshippingStatus","type":"INT","comment":"代发状态"},{"name":"dropshippingStatusDesc","type":"VARCHAR(100)","comment":"代发状态描述"},{"name":"cancelAllocateTime","type":"BIGINT","comment":"取消分配时间"},{"name":"cancelAllocateReason","type":"TEXT","comment":"取消分配原因"},{"name":"buyerNote","type":"JSONB","comment":"买家备注列表"},{"name":"sellerNote","type":"JSONB","comment":"卖家备注列表"},{"name":"orderItemList","type":"JSONB","comment":"订单商品信息列表"},{"name":"detailAddress","type":"TEXT","comment":"详细地址"},{"name":"addressId","type":"VARCHAR(100)","comment":"地址id"},{"name":"cityCode","type":"VARCHAR(50)","comment":"城市编码"},{"name":"provinceCode","type":"VARCHAR(50)","comment":"省份编码"},{"name":"streetCode","type":"VARCHAR(50)","comment":"街道编码"},{"name":"streetName","type":"VARCHAR(100)","comment":"街道名称"},{"name":"provinceName","type":"VARCHAR(100)","comment":"省份"},{"name":"countryCode","type":"VARCHAR(50)","comment":"国家编码"},{"name":"districtName","type":"VARCHAR(100)","comment":"区县名称"},{"name":"cityName","type":"VARCHAR(100)","comment":"城市名称"},{"name":"districtCode","type":"VARCHAR(50)","comment":"区县编码"},{"name":"countryName","type":"VARCHAR(100)","comment":"国家名称"},{"name":"receiverName","type":"VARCHAR(100)","comment":"收货人姓名"},{"name":"receiverMobile","type":"VARCHAR(50)","comment":"收货人手机号码"},{"name":"receiverTelephone","type":"VARCHAR(50)","comment":"收货人电话"}],"conflict_keys":["dropshippingOrderCode"]}'::jsonb);
|
||||
INSERT INTO api_interface (tenant_id,creator,created_at,updater,updated_at,platform_id,name,code,url,method,status,auth_type,request_config,response_config,table_definition) VALUES (1,'admin',NOW(),'admin',NOW(),(SELECT id FROM api_datasource_platform WHERE platform_code='kuaishou'),'代发订单详情','dropshipping_order_detail','/open/dropshipping/order/merchant/detail','POST','active','inherit','{"pageSize":10,"page_param":"cursor","page_size_param":"pageSize","method":"open.dropshipping.order.merchant.detail","version":1,"signMethod":"MD5","prefetch":{"url":"/open/dropshipping/order/merchant/list","method":"POST","response_path":"data.orderList","target_param":"dropshippingOrderCode","value_field":"dropshippingOrderCode"},"body_wrapper_field":"param","exclude_from_wrapper":["method","version","signMethod"]}'::jsonb,'{"success_field":"result","success_value":1,"list_path":"data","single_record":true}'::jsonb,'{"table_name":"kuaishou_dropshipping_order_detail","columns":[{"name":"dropshippingOrderCode","type":"VARCHAR(100)","comment":"代发订单编码"},{"name":"oid","type":"BIGINT","comment":"交易订单ID"},{"name":"payTime","type":"BIGINT","comment":"支付时间"},{"name":"buyerOpenId","type":"VARCHAR(100)","comment":"买家开放id"},{"name":"buyerNick","type":"VARCHAR(100)","comment":"买家昵称"},{"name":"sellerOpenId","type":"VARCHAR(100)","comment":"卖家开放id"},{"name":"sellerNick","type":"VARCHAR(100)","comment":"卖家昵称"},{"name":"orderStatus","type":"INT","comment":"订单状态"},{"name":"orderStatusDesc","type":"VARCHAR(100)","comment":"订单状态描述"},{"name":"refundStatus","type":"INT","comment":"售后状态"},{"name":"refundStatusDesc","type":"VARCHAR(100)","comment":"售后状态描述"},{"name":"orderType","type":"INT","comment":"订单类型"},{"name":"orderTypeDesc","type":"VARCHAR(100)","comment":"订单类型描述"},{"name":"deliveryTime","type":"BIGINT","comment":"发货时间"},{"name":"createTime","type":"BIGINT","comment":"创建时间"},{"name":"updateTime","type":"BIGINT","comment":"修改时间"},{"name":"waybillCode","type":"VARCHAR(100)","comment":"运单号"},{"name":"expressCompanyCode","type":"VARCHAR(50)","comment":"物流公司编码"},{"name":"expressCompanyName","type":"VARCHAR(100)","comment":"物流公司名称"},{"name":"factoryCode","type":"VARCHAR(100)","comment":"代发厂家编码"},{"name":"factoryName","type":"VARCHAR(200)","comment":"代发厂家名称"},{"name":"allocateTime","type":"BIGINT","comment":"分配时间"},{"name":"dropshippingStatus","type":"INT","comment":"代发状态"},{"name":"dropshippingStatusDesc","type":"VARCHAR(100)","comment":"代发状态描述"},{"name":"cancelAllocateTime","type":"BIGINT","comment":"取消分配时间"},{"name":"cancelAllocateReason","type":"TEXT","comment":"取消分配原因"},{"name":"buyerNote","type":"JSONB","comment":"买家备注列表"},{"name":"sellerNote","type":"JSONB","comment":"卖家备注列表"},{"name":"orderItemList","type":"JSONB","comment":"订单商品信息列表"},{"name":"detailAddress","type":"TEXT","comment":"详细地址"},{"name":"addressId","type":"VARCHAR(100)","comment":"地址id"},{"name":"cityCode","type":"VARCHAR(50)","comment":"城市编码"},{"name":"provinceCode","type":"VARCHAR(50)","comment":"省份编码"},{"name":"streetCode","type":"VARCHAR(50)","comment":"街道编码"},{"name":"streetName","type":"VARCHAR(100)","comment":"街道名称"},{"name":"provinceName","type":"VARCHAR(100)","comment":"省份"},{"name":"countryCode","type":"VARCHAR(50)","comment":"国家编码"},{"name":"districtName","type":"VARCHAR(100)","comment":"区县名称"},{"name":"cityName","type":"VARCHAR(100)","comment":"城市名称"},{"name":"districtCode","type":"VARCHAR(50)","comment":"区县编码"},{"name":"countryName","type":"VARCHAR(100)","comment":"国家名称"},{"name":"receiverName","type":"VARCHAR(100)","comment":"收货人姓名"},{"name":"receiverMobile","type":"VARCHAR(50)","comment":"收货人手机号码"},{"name":"receiverTelephone","type":"VARCHAR(50)","comment":"收货人电话"}],"conflict_keys":["dropshippingOrderCode"]}'::jsonb);
|
||||
|
||||
@@ -6,11 +6,26 @@ import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// GetCurrentUser 从 context 中获取当前登录用户
|
||||
// 如果未找到用户信息,返回默认 "unknown" 用户名
|
||||
// GetCurrentUser 从 context 中获取当前登录用户名
|
||||
func GetCurrentUser(ctx context.Context) string {
|
||||
if user, ok := ctx.Value("user").(*beans.User); ok && user != nil && user.UserName != "" {
|
||||
return user.UserName
|
||||
if u := getUser(ctx); u != nil {
|
||||
return u.UserName
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
// GetCurrentTenantId 从 context 中获取当前租户 ID
|
||||
func GetCurrentTenantId(ctx context.Context) uint64 {
|
||||
if u := getUser(ctx); u != nil {
|
||||
return u.TenantId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// getUser 从 context 中获取 *beans.User
|
||||
func getUser(ctx context.Context) *beans.User {
|
||||
if user, ok := ctx.Value("user").(*beans.User); ok {
|
||||
return user
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user