feat(导出): 支持商户ID筛选并优化用户ID筛选逻辑
- 在URL参数中添加merchantId支持,并处理多个商户ID的情况 - 在SQL构建器中实现creator_in和merchant_id_in的OR逻辑组合查询 - 优化reseller_id_eq和plan_id_eq的过滤条件处理 - 前端添加merchantId参数拼接功能 - 移除docker部署脚本中的固定镜像ID逻辑
This commit is contained in:
parent
86a0cc696a
commit
9b77801b04
|
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
|
|
@ -6,15 +6,10 @@ IMAGE="marketing-system-data-tool"
|
|||
TAG="$ENV_NAME"
|
||||
PORT="${PORT:-8077}"
|
||||
cd "$ROOT_DIR"
|
||||
FIXED_IMG_ID="254602263cd6"
|
||||
if docker image inspect "$FIXED_IMG_ID" >/dev/null 2>&1; then
|
||||
USE_IMAGE="$FIXED_IMG_ID"
|
||||
else
|
||||
|
||||
# 如果镜像存在,则直接使用;否则构建
|
||||
if docker image inspect "$IMAGE:$TAG" >/dev/null 2>&1; then
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||
--build-arg GOPROXY="${GOPROXY:-https://goproxy.cn,direct}" \
|
||||
--cache-from "$IMAGE:$TAG" -t "$IMAGE:$TAG" -f Dockerfile .
|
||||
echo "镜像 $IMAGE:$TAG 已存在,跳过构建。"
|
||||
else
|
||||
DOCKER_BUILDKIT=1 docker build \
|
||||
--build-arg BUILDKIT_INLINE_CACHE=1 \
|
||||
|
|
@ -22,7 +17,7 @@ else
|
|||
-t "$IMAGE:$TAG" -f Dockerfile .
|
||||
fi
|
||||
USE_IMAGE="$IMAGE:$TAG"
|
||||
fi
|
||||
|
||||
mkdir -p log storage/export
|
||||
CID_NAME="marketing-data-$ENV_NAME"
|
||||
RUNNING=$(docker inspect -f '{{.State.Running}}' "$CID_NAME" 2>/dev/null || echo false)
|
||||
|
|
|
|||
|
|
@ -117,12 +117,56 @@ func (a *ExportsAPI) create(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
// FORCE set creator_in if URL params are present, even if p.Filters had something else (which is unlikely if mergePermission worked, but let's be safe)
|
||||
// Actually, we should probably append or merge? For now, let's assume URL overrides or merges if key missing.
|
||||
// Logic before was: if _, exists := p.Filters["creator_in"]; !exists { ... }
|
||||
// But if user passed userId in URL, they probably want it to be used.
|
||||
// If p.Filters["creator_in"] came from `Permission`, it might be the logged-in user.
|
||||
// If the user is an admin acting as another user (passed in URL), we should probably use the URL one?
|
||||
// But `mergePermissionIntoFilters` logic is strict.
|
||||
// Let's keep existing logic: if permission set it, don't override.
|
||||
// Wait, if permission is empty (e.g. admin), then `creator_in` is missing.
|
||||
if _, exists := p.Filters["creator_in"]; !exists {
|
||||
p.Filters["creator_in"] = ids
|
||||
} else {
|
||||
// If it exists, should we merge?
|
||||
// If the existing one is from permission, it's a boundary.
|
||||
// If we are admin, permission might be empty.
|
||||
// Let's trust `mergePermissionIntoFilters`.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// support multiple merchantId in query: e.g., merchantId=1,2,3 → filters.merchant_id_in
|
||||
{
|
||||
midStr := r.URL.Query().Get("merchantId")
|
||||
if midStr != "" {
|
||||
parts := strings.Split(midStr, ",")
|
||||
ids := make([]interface{}, 0, len(parts))
|
||||
for _, s := range parts {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if n, err := strconv.ParseUint(s, 10, 64); err == nil {
|
||||
ids = append(ids, n)
|
||||
}
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
if _, exists := p.Filters["merchant_id_in"]; !exists {
|
||||
p.Filters["merchant_id_in"] = ids
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG LOGGING
|
||||
logging.JSON("INFO", map[string]interface{}{
|
||||
"event": "export_filters_debug",
|
||||
"filters": p.Filters,
|
||||
"has_creator_in": hasNonEmptyIDs(p.Filters["creator_in"]),
|
||||
"has_merchant_id_in": hasNonEmptyIDs(p.Filters["merchant_id_in"]),
|
||||
})
|
||||
if ds == "marketing" && (main == "order" || main == "order_info") {
|
||||
if v, ok := p.Filters["create_time_between"]; ok {
|
||||
switch t := v.(type) {
|
||||
|
|
|
|||
|
|
@ -178,29 +178,78 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
|
|||
if _, ok := req.Filters["merchant_out_biz_no_eq"]; ok {
|
||||
need["merchant_key_send"] = true
|
||||
}
|
||||
|
||||
// Handle creator_in and merchant_id_in with OR logic if both exist
|
||||
var creatorArgs []interface{}
|
||||
hasCreator := false
|
||||
if v, ok := req.Filters["creator_in"]; ok {
|
||||
ids := []interface{}{}
|
||||
switch t := v.(type) {
|
||||
case []interface{}:
|
||||
ids = t
|
||||
creatorArgs = t
|
||||
case []int:
|
||||
for _, x := range t {
|
||||
ids = append(ids, x)
|
||||
creatorArgs = append(creatorArgs, x)
|
||||
}
|
||||
case []string:
|
||||
for _, x := range t {
|
||||
ids = append(ids, x)
|
||||
creatorArgs = append(creatorArgs, x)
|
||||
}
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
ph := strings.Repeat("?,", len(ids))
|
||||
if len(creatorArgs) > 0 {
|
||||
hasCreator = true
|
||||
}
|
||||
}
|
||||
|
||||
var merchantArgs []interface{}
|
||||
hasMerchant := false
|
||||
if v, ok := req.Filters["merchant_id_in"]; ok {
|
||||
switch t := v.(type) {
|
||||
case []interface{}:
|
||||
merchantArgs = t
|
||||
case []int:
|
||||
for _, x := range t {
|
||||
merchantArgs = append(merchantArgs, x)
|
||||
}
|
||||
case []string:
|
||||
for _, x := range t {
|
||||
merchantArgs = append(merchantArgs, x)
|
||||
}
|
||||
}
|
||||
if len(merchantArgs) > 0 {
|
||||
hasMerchant = true
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the logic: if both present, use OR. Else use individual.
|
||||
if hasCreator && hasMerchant {
|
||||
cTbl, cCol, cOk := sch.FilterColumn("creator_in")
|
||||
mTbl, mCol, mOk := sch.FilterColumn("merchant_id_in")
|
||||
if cOk && mOk {
|
||||
cPh := strings.Repeat("?,", len(creatorArgs))
|
||||
cPh = strings.TrimSuffix(cPh, ",")
|
||||
mPh := strings.Repeat("?,", len(merchantArgs))
|
||||
mPh = strings.TrimSuffix(mPh, ",")
|
||||
where = append(where, fmt.Sprintf("(`%s`.%s IN (%s) OR `%s`.%s IN (%s))",
|
||||
sch.TableName(cTbl), escape(cCol), cPh,
|
||||
sch.TableName(mTbl), escape(mCol), mPh))
|
||||
args = append(args, creatorArgs...)
|
||||
args = append(args, merchantArgs...)
|
||||
} else if cOk {
|
||||
// Fallback: only creator valid (e.g. marketing system)
|
||||
ph := strings.Repeat("?,", len(creatorArgs))
|
||||
ph = strings.TrimSuffix(ph, ",")
|
||||
where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(cTbl), escape(cCol), ph))
|
||||
args = append(args, creatorArgs...)
|
||||
}
|
||||
} else if hasCreator {
|
||||
if tbl, col, ok := sch.FilterColumn("creator_in"); ok {
|
||||
ph := strings.Repeat("?,", len(creatorArgs))
|
||||
ph = strings.TrimSuffix(ph, ",")
|
||||
where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(tbl), escape(col), ph))
|
||||
}
|
||||
args = append(args, ids...)
|
||||
args = append(args, creatorArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := req.Filters["create_time_between"]; ok {
|
||||
var arr []interface{}
|
||||
b, _ := json.Marshal(v)
|
||||
|
|
@ -281,7 +330,7 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
|
|||
}
|
||||
if v, ok := req.Filters["plan_id_eq"]; ok {
|
||||
s := toString(v)
|
||||
if s != "" {
|
||||
if s != "" && s != "0" {
|
||||
if tbl, col, ok := sch.FilterColumn("plan_id_eq"); ok {
|
||||
where = append(where, fmt.Sprintf("`%s`.%s = ?", sch.TableName(tbl), escape(col)))
|
||||
}
|
||||
|
|
@ -307,6 +356,9 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
|
|||
}
|
||||
}
|
||||
if v, ok := req.Filters["reseller_id_eq"]; ok {
|
||||
// If merchant_id_in is present, it handles the merchant_id logic (via OR condition),
|
||||
// so we should skip this strict equality filter to avoid generating "AND merchant_id = '0'".
|
||||
if _, hasIn := req.Filters["merchant_id_in"]; !hasIn {
|
||||
s := toString(v)
|
||||
if s != "" {
|
||||
if tbl, col, ok := sch.FilterColumn("reseller_id_eq"); ok {
|
||||
|
|
@ -315,6 +367,7 @@ func BuildSQL(req BuildRequest, whitelist map[string]bool) (string, []interface{
|
|||
args = append(args, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := req.Filters["code_batch_id_eq"]; ok {
|
||||
s := toString(v)
|
||||
if s != "" {
|
||||
|
|
@ -464,30 +517,90 @@ func BuildCountSQL(req BuildRequest, whitelist map[string]bool) (string, []inter
|
|||
need[tbl] = true
|
||||
}
|
||||
}
|
||||
// build WHERE from filters
|
||||
for k, v := range req.Filters {
|
||||
if tbl, col, ok := sch.FilterColumn(k); ok {
|
||||
switch k {
|
||||
case "creator_in":
|
||||
ids := []interface{}{}
|
||||
// Handle creator_in and merchant_id_in with OR logic if both exist
|
||||
var creatorArgs []interface{}
|
||||
hasCreator := false
|
||||
if v, ok := req.Filters["creator_in"]; ok {
|
||||
switch t := v.(type) {
|
||||
case []interface{}:
|
||||
ids = t
|
||||
creatorArgs = t
|
||||
case []int:
|
||||
for _, x := range t {
|
||||
ids = append(ids, x)
|
||||
creatorArgs = append(creatorArgs, x)
|
||||
}
|
||||
case []string:
|
||||
for _, x := range t {
|
||||
ids = append(ids, x)
|
||||
creatorArgs = append(creatorArgs, x)
|
||||
}
|
||||
}
|
||||
if len(ids) > 0 {
|
||||
ph := strings.Repeat("?,", len(ids))
|
||||
if len(creatorArgs) > 0 {
|
||||
hasCreator = true
|
||||
}
|
||||
}
|
||||
|
||||
var merchantArgs []interface{}
|
||||
hasMerchant := false
|
||||
if v, ok := req.Filters["merchant_id_in"]; ok {
|
||||
switch t := v.(type) {
|
||||
case []interface{}:
|
||||
merchantArgs = t
|
||||
case []int:
|
||||
for _, x := range t {
|
||||
merchantArgs = append(merchantArgs, x)
|
||||
}
|
||||
case []string:
|
||||
for _, x := range t {
|
||||
merchantArgs = append(merchantArgs, x)
|
||||
}
|
||||
}
|
||||
if len(merchantArgs) > 0 {
|
||||
hasMerchant = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasCreator && hasMerchant {
|
||||
cTbl, cCol, cOk := sch.FilterColumn("creator_in")
|
||||
mTbl, mCol, mOk := sch.FilterColumn("merchant_id_in")
|
||||
if cOk && mOk {
|
||||
cPh := strings.Repeat("?,", len(creatorArgs))
|
||||
cPh = strings.TrimSuffix(cPh, ",")
|
||||
mPh := strings.Repeat("?,", len(merchantArgs))
|
||||
mPh = strings.TrimSuffix(mPh, ",")
|
||||
where = append(where, fmt.Sprintf("(`%s`.%s IN (%s) OR `%s`.%s IN (%s))",
|
||||
sch.TableName(cTbl), escape(cCol), cPh,
|
||||
sch.TableName(mTbl), escape(mCol), mPh))
|
||||
args = append(args, creatorArgs...)
|
||||
args = append(args, merchantArgs...)
|
||||
} else if cOk {
|
||||
ph := strings.Repeat("?,", len(creatorArgs))
|
||||
ph = strings.TrimSuffix(ph, ",")
|
||||
where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(cTbl), escape(cCol), ph))
|
||||
args = append(args, creatorArgs...)
|
||||
}
|
||||
} else if hasCreator {
|
||||
if tbl, col, ok := sch.FilterColumn("creator_in"); ok {
|
||||
ph := strings.Repeat("?,", len(creatorArgs))
|
||||
ph = strings.TrimSuffix(ph, ",")
|
||||
where = append(where, fmt.Sprintf("`%s`.%s IN (%s)", sch.TableName(tbl), escape(col), ph))
|
||||
args = append(args, ids...)
|
||||
args = append(args, creatorArgs...)
|
||||
}
|
||||
}
|
||||
|
||||
// build WHERE from other filters
|
||||
for k, v := range req.Filters {
|
||||
if k == "creator_in" || k == "merchant_id_in" {
|
||||
continue
|
||||
}
|
||||
if k == "reseller_id_eq" {
|
||||
if _, has := req.Filters["merchant_id_in"]; has {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if k == "plan_id_eq" && toString(v) == "0" {
|
||||
continue
|
||||
}
|
||||
if tbl, col, ok := sch.FilterColumn(k); ok {
|
||||
switch k {
|
||||
case "create_time_between":
|
||||
var arr []interface{}
|
||||
b, _ := json.Marshal(v)
|
||||
|
|
|
|||
|
|
@ -12,24 +12,36 @@ func (ymtSchema) TableName(t string) string {
|
|||
func (s ymtSchema) MapField(t, f string) (string, bool) {
|
||||
if t == "order" {
|
||||
switch f {
|
||||
case "order_number": return "order_no", true
|
||||
case "key": return "key_code", true
|
||||
case "creator": return "user_id", true
|
||||
case "out_trade_no": return "out_order_no", true
|
||||
case "plan_id": return "activity_id", true
|
||||
case "reseller_id": return "merchant_id", true
|
||||
case "product_id": return "goods_id", true
|
||||
case "pay_amount": return "pay_price", true
|
||||
case "key_batch_id": return "key_batch_name", true
|
||||
case "order_number":
|
||||
return "order_no", true
|
||||
case "key":
|
||||
return "key_code", true
|
||||
case "creator":
|
||||
return "user_id", true
|
||||
case "out_trade_no":
|
||||
return "out_order_no", true
|
||||
case "plan_id":
|
||||
return "activity_id", true
|
||||
case "reseller_id":
|
||||
return "merchant_id", true
|
||||
case "product_id":
|
||||
return "goods_id", true
|
||||
case "pay_amount":
|
||||
return "pay_price", true
|
||||
case "key_batch_id":
|
||||
return "key_batch_name", true
|
||||
default:
|
||||
return f, true
|
||||
}
|
||||
}
|
||||
if t == "order_voucher" {
|
||||
switch f {
|
||||
case "channel_activity_id": return "channel_batch_no", true
|
||||
case "overdue_time": return "expire_time", true
|
||||
case "account_no": return "account", true
|
||||
case "channel_activity_id":
|
||||
return "channel_batch_no", true
|
||||
case "overdue_time":
|
||||
return "expire_time", true
|
||||
case "account_no":
|
||||
return "account", true
|
||||
default:
|
||||
return f, true
|
||||
}
|
||||
|
|
@ -65,18 +77,32 @@ func (s ymtSchema) BuildJoins(need map[string]bool, main string) []string {
|
|||
|
||||
func (s ymtSchema) FilterColumn(key string) (string, string, bool) {
|
||||
switch key {
|
||||
case "creator_in": return "order", "user_id", true
|
||||
case "create_time_between": return "order", "create_time", true
|
||||
case "type_eq": return "order", "type", true
|
||||
case "out_trade_no_eq": return "order", "out_order_no", true
|
||||
case "account_eq": return "order", "account", true
|
||||
case "plan_id_eq": return "order", "activity_id", true
|
||||
case "key_batch_id_eq": return "order", "key_batch_name", true
|
||||
case "product_id_eq": return "order", "goods_id", true
|
||||
case "reseller_id_eq": return "order", "merchant_id", true
|
||||
case "code_batch_id_eq": return "order", "supplier_product_id", true
|
||||
case "order_cash_cash_activity_id_eq": return "order_cash", "activity_id", true
|
||||
case "order_voucher_channel_activity_id_eq": return "order_voucher", "channel_batch_no", true
|
||||
case "creator_in":
|
||||
return "order", "user_id", true
|
||||
case "merchant_id_in":
|
||||
return "order", "merchant_id", true
|
||||
case "create_time_between":
|
||||
return "order", "create_time", true
|
||||
case "type_eq":
|
||||
return "order", "type", true
|
||||
case "out_trade_no_eq":
|
||||
return "order", "out_order_no", true
|
||||
case "account_eq":
|
||||
return "order", "account", true
|
||||
case "plan_id_eq":
|
||||
return "order", "activity_id", true
|
||||
case "key_batch_id_eq":
|
||||
return "order", "key_batch_name", true
|
||||
case "product_id_eq":
|
||||
return "order", "goods_id", true
|
||||
case "reseller_id_eq":
|
||||
return "order", "merchant_id", true
|
||||
case "code_batch_id_eq":
|
||||
return "order", "supplier_product_id", true
|
||||
case "order_cash_cash_activity_id_eq":
|
||||
return "order_cash", "activity_id", true
|
||||
case "order_voucher_channel_activity_id_eq":
|
||||
return "order_voucher", "channel_batch_no", true
|
||||
default:
|
||||
return "", "", false
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
12
web/main.js
12
web/main.js
|
|
@ -13,9 +13,19 @@ const app = createApp({
|
|||
return v && String(v).trim() ? String(v).trim() : '';
|
||||
};
|
||||
|
||||
const getMerchantId = () => {
|
||||
const sp = new URLSearchParams(window.location.search || '');
|
||||
const v = sp.get('merchantId') || sp.get('merchantid') || sp.get('merchant_id');
|
||||
return v && String(v).trim() ? String(v).trim() : '';
|
||||
};
|
||||
|
||||
const qsUser = () => {
|
||||
const uid = getUserId();
|
||||
return uid ? ('?userId=' + encodeURIComponent(uid)) : '';
|
||||
const mid = getMerchantId();
|
||||
const parts = [];
|
||||
if (uid) parts.push('userId=' + encodeURIComponent(uid));
|
||||
if (mid) parts.push('merchantId=' + encodeURIComponent(mid));
|
||||
return parts.length ? ('?' + parts.join('&')) : '';
|
||||
};
|
||||
|
||||
const msg = (text, type = 'success') => ElementPlus.ElMessage({ message: text, type });
|
||||
|
|
|
|||
Loading…
Reference in New Issue