@youzan-cloud/zan-saas-cli 0.0.0-stage → 0.1.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPENDENCIES.json +50 -0
- package/INSTALL.md +79 -0
- package/LICENSE +21 -0
- package/NOTICE +1 -0
- package/README.md +151 -2
- package/THIRD_PARTY_NOTICES.txt +162 -0
- package/bin/zan-saas-cli.js +28 -0
- package/internal/registry/index.js +123 -0
- package/internal/registry/manifest.json +424 -0
- package/npm-shrinkwrap.json +86 -0
- package/package.json +33 -4
- package/skills/README.md +30 -0
- package/skills/zan-saas/SKILL.md +54 -0
- package/skills/zan-saas/references/host-entry.md +25 -0
- package/skills/zan-saas-access/SKILL.md +37 -0
- package/skills/zan-saas-access/references/role-list.md +11 -0
- package/skills/zan-saas-access/references/staff-perms-get.md +11 -0
- package/skills/zan-saas-aftersale/SKILL.md +31 -0
- package/skills/zan-saas-aftersale/references/case-list.md +13 -0
- package/skills/zan-saas-aftersale/references/case-scan.md +31 -0
- package/skills/zan-saas-benefit/SKILL.md +46 -0
- package/skills/zan-saas-benefit/references/card-get.md +7 -0
- package/skills/zan-saas-benefit/references/card-grant-list.md +11 -0
- package/skills/zan-saas-benefit/references/card-list.md +7 -0
- package/skills/zan-saas-benefit/references/customer-card-get.md +7 -0
- package/skills/zan-saas-benefit/references/customer-card-list.md +7 -0
- package/skills/zan-saas-coupon/SKILL.md +41 -0
- package/skills/zan-saas-coupon/references/activity-get.md +11 -0
- package/skills/zan-saas-coupon/references/activity-list.md +15 -0
- package/skills/zan-saas-coupon/references/voucher-get.md +13 -0
- package/skills/zan-saas-coupon/references/voucher-list.md +25 -0
- package/skills/zan-saas-customer/SKILL.md +73 -0
- package/skills/zan-saas-customer/references/customer-scan.md +11 -0
- package/skills/zan-saas-customer/references/segment-list.md +22 -0
- package/skills/zan-saas-fulfillment/SKILL.md +36 -0
- package/skills/zan-saas-fulfillment/references/delivery-get.md +11 -0
- package/skills/zan-saas-fulfillment/references/shipment-list.md +11 -0
- package/skills/zan-saas-fulfillment/references/waybill-get.md +9 -0
- package/skills/zan-saas-interaction/SKILL.md +37 -0
- package/skills/zan-saas-interaction/references/lottery-get.md +11 -0
- package/skills/zan-saas-interaction/references/lottery-list.md +19 -0
- package/skills/zan-saas-item/SKILL.md +61 -0
- package/skills/zan-saas-item/references/attribute-list.md +11 -0
- package/skills/zan-saas-item/references/brand-list.md +7 -0
- package/skills/zan-saas-item/references/classification-get.md +7 -0
- package/skills/zan-saas-item/references/classification-list.md +7 -0
- package/skills/zan-saas-item/references/group-list.md +9 -0
- package/skills/zan-saas-item/references/item-content.md +9 -0
- package/skills/zan-saas-item/references/item-get.md +9 -0
- package/skills/zan-saas-item/references/item-search.md +23 -0
- package/skills/zan-saas-item/references/product-identity.md +11 -0
- package/skills/zan-saas-member/SKILL.md +41 -0
- package/skills/zan-saas-member/references/customer-level-get.md +7 -0
- package/skills/zan-saas-member/references/growth-log-list.md +17 -0
- package/skills/zan-saas-member/references/membership-get.md +7 -0
- package/skills/zan-saas-order/SKILL.md +56 -0
- package/skills/zan-saas-order/references/order-get.md +26 -0
- package/skills/zan-saas-order/references/order-list.md +25 -0
- package/skills/zan-saas-order/references/order-scan.md +35 -0
- package/skills/zan-saas-org/SKILL.md +37 -0
- package/skills/zan-saas-org/references/staff-get.md +15 -0
- package/skills/zan-saas-org/references/staff-role-get.md +11 -0
- package/skills/zan-saas-org/references/unit-get.md +11 -0
- package/skills/zan-saas-org/references/unit-list.md +15 -0
- package/skills/zan-saas-points/SKILL.md +42 -0
- package/skills/zan-saas-points/references/account-get.md +7 -0
- package/skills/zan-saas-points/references/log-list.md +17 -0
- package/skills/zan-saas-points/references/rule-list.md +2 -0
- package/skills/zan-saas-privacy/SKILL.md +30 -0
- package/skills/zan-saas-privacy/references/field-decrypt.md +19 -0
- package/skills/zan-saas-shared/SKILL.md +94 -0
- package/skills/zan-saas-shared/references/authentication.md +52 -0
- package/skills/zan-saas-shared/references/value-processing.md +27 -0
- package/skills/zan-saas-stock/SKILL.md +28 -0
- package/skills/zan-saas-stock/references/log-list.md +11 -0
- package/skills/zan-saas-valuecard/SKILL.md +37 -0
- package/skills/zan-saas-valuecard/references/card-get.md +13 -0
- package/skills/zan-saas-valuecard/references/card-list.md +11 -0
- package/src/auth/browser.js +28 -0
- package/src/auth/callback-server.js +99 -0
- package/src/auth/config.js +53 -0
- package/src/auth/credential-store.js +109 -0
- package/src/auth/oauth-client.js +105 -0
- package/src/auth/pkce.js +28 -0
- package/src/auth/session.js +178 -0
- package/src/auth/tool.js +25 -0
- package/src/cli/command-paths.js +126 -0
- package/src/cli/context-help.js +102 -0
- package/src/cli/diagnostic-command.js +57 -0
- package/src/cli/domain-catalog.js +42 -0
- package/src/cli/help-text.js +32 -0
- package/src/cli/native-loader.js +120 -0
- package/src/cli/native-path.js +22 -0
- package/src/cli/parser-policy.js +141 -0
- package/src/cli/program.js +134 -0
- package/src/cli/skill-catalog.js +241 -0
- package/src/commands/native/access/commands.index.json +24 -0
- package/src/commands/native/access/domain.json +3 -0
- package/src/commands/native/access/role/list/command.js +141 -0
- package/src/commands/native/access/role/list/schema.json +393 -0
- package/src/commands/native/access/staff-perms/get/command.js +119 -0
- package/src/commands/native/access/staff-perms/get/schema.json +703 -0
- package/src/commands/native/aftersale/case/get/command.js +104 -0
- package/src/commands/native/aftersale/case/get/exact-json.js +52 -0
- package/src/commands/native/aftersale/case/get/money.js +28 -0
- package/src/commands/native/aftersale/case/get/projection.js +120 -0
- package/src/commands/native/aftersale/case/get/query.js +72 -0
- package/src/commands/native/aftersale/case/get/schema.json +134 -0
- package/src/commands/native/aftersale/case/list/command.js +135 -0
- package/src/commands/native/aftersale/case/list/schema.json +1095 -0
- package/src/commands/native/aftersale/case/scan/command.js +110 -0
- package/src/commands/native/aftersale/case/scan/query.js +62 -0
- package/src/commands/native/aftersale/case/scan/scan.js +78 -0
- package/src/commands/native/aftersale/case/scan/schema.json +964 -0
- package/src/commands/native/aftersale/case/scan/search.js +108 -0
- package/src/commands/native/aftersale/commands.index.json +42 -0
- package/src/commands/native/aftersale/domain.json +3 -0
- package/src/commands/native/aftersale/refund/list/command.js +97 -0
- package/src/commands/native/aftersale/refund/list/money.js +25 -0
- package/src/commands/native/aftersale/refund/list/projection.js +50 -0
- package/src/commands/native/aftersale/refund/list/query.js +73 -0
- package/src/commands/native/aftersale/refund/list/schema.json +130 -0
- package/src/commands/native/aftersale/shared/query.js +58 -0
- package/src/commands/native/aftersale/shared/search.js +128 -0
- package/src/commands/native/benefit/card-definition/get/command.js +110 -0
- package/src/commands/native/benefit/card-definition/get/schema.json +545 -0
- package/src/commands/native/benefit/card-definition/list/command.js +106 -0
- package/src/commands/native/benefit/card-definition/list/schema.json +690 -0
- package/src/commands/native/benefit/card-grant/list/command.js +135 -0
- package/src/commands/native/benefit/card-grant/list/schema.json +758 -0
- package/src/commands/native/benefit/commands.index.json +51 -0
- package/src/commands/native/benefit/customer-card/get/command.js +110 -0
- package/src/commands/native/benefit/customer-card/get/schema.json +340 -0
- package/src/commands/native/benefit/customer-card/list/command.js +103 -0
- package/src/commands/native/benefit/customer-card/list/schema.json +731 -0
- package/src/commands/native/benefit/domain.json +3 -0
- package/src/commands/native/coupon/activity/get/command.js +183 -0
- package/src/commands/native/coupon/activity/get/schema.json +624 -0
- package/src/commands/native/coupon/activity/list/command.js +169 -0
- package/src/commands/native/coupon/activity/list/schema.json +845 -0
- package/src/commands/native/coupon/commands.index.json +42 -0
- package/src/commands/native/coupon/domain.json +3 -0
- package/src/commands/native/coupon/shared/query.js +15 -0
- package/src/commands/native/coupon/voucher/get/command.js +113 -0
- package/src/commands/native/coupon/voucher/get/schema.json +147 -0
- package/src/commands/native/coupon/voucher/list/command.js +155 -0
- package/src/commands/native/coupon/voucher/list/schema.json +772 -0
- package/src/commands/native/customer/commands.index.json +96 -0
- package/src/commands/native/customer/customer/get/adapter.js +82 -0
- package/src/commands/native/customer/customer/get/command.js +106 -0
- package/src/commands/native/customer/customer/get/schema.json +958 -0
- package/src/commands/native/customer/customer/list/command.js +78 -0
- package/src/commands/native/customer/customer/list/input.js +145 -0
- package/src/commands/native/customer/customer/list/query.js +103 -0
- package/src/commands/native/customer/customer/list/schema.json +969 -0
- package/src/commands/native/customer/customer/scan/command.js +80 -0
- package/src/commands/native/customer/customer/scan/scan.js +92 -0
- package/src/commands/native/customer/customer/scan/schema.json +211 -0
- package/src/commands/native/customer/customer-tag/get/command.js +166 -0
- package/src/commands/native/customer/customer-tag/get/schema.json +317 -0
- package/src/commands/native/customer/customer-tag/list/command.js +143 -0
- package/src/commands/native/customer/customer-tag/list/schema.json +259 -0
- package/src/commands/native/customer/domain.json +3 -0
- package/src/commands/native/customer/segment/list/command.js +96 -0
- package/src/commands/native/customer/segment/list/query.js +98 -0
- package/src/commands/native/customer/segment/list/schema.json +135 -0
- package/src/commands/native/customer/segment-customer/list/command.js +171 -0
- package/src/commands/native/customer/segment-customer/list/schema.json +123 -0
- package/src/commands/native/customer/shared/search-input.js +179 -0
- package/src/commands/native/customer/shared/search-page.js +54 -0
- package/src/commands/native/customer/tag/get/command.js +73 -0
- package/src/commands/native/customer/tag/get/schema.json +339 -0
- package/src/commands/native/customer/tagcategory/get/command.js +80 -0
- package/src/commands/native/customer/tagcategory/get/query.js +98 -0
- package/src/commands/native/customer/tagcategory/get/schema.json +339 -0
- package/src/commands/native/customer/tagcategory/list/command.js +127 -0
- package/src/commands/native/customer/tagcategory/list/schema.json +394 -0
- package/src/commands/native/fulfillment/commands.index.json +33 -0
- package/src/commands/native/fulfillment/delivery/get/command.js +107 -0
- package/src/commands/native/fulfillment/delivery/get/projection.js +142 -0
- package/src/commands/native/fulfillment/delivery/get/schema.json +173 -0
- package/src/commands/native/fulfillment/domain.json +3 -0
- package/src/commands/native/fulfillment/shipment/list/command.js +83 -0
- package/src/commands/native/fulfillment/shipment/list/projection.js +130 -0
- package/src/commands/native/fulfillment/shipment/list/schema.json +752 -0
- package/src/commands/native/fulfillment/waybill/get/command.js +193 -0
- package/src/commands/native/fulfillment/waybill/get/response.js +68 -0
- package/src/commands/native/fulfillment/waybill/get/schema.json +190 -0
- package/src/commands/native/interaction/commands.index.json +24 -0
- package/src/commands/native/interaction/domain.json +3 -0
- package/src/commands/native/interaction/lottery/get/command.js +61 -0
- package/src/commands/native/interaction/lottery/get/query.js +152 -0
- package/src/commands/native/interaction/lottery/get/schema.json +1004 -0
- package/src/commands/native/interaction/lottery/list/command.js +176 -0
- package/src/commands/native/interaction/lottery/list/schema.json +898 -0
- package/src/commands/native/item/attribute/list/command.js +122 -0
- package/src/commands/native/item/attribute/list/schema.json +727 -0
- package/src/commands/native/item/brand/list/command.js +114 -0
- package/src/commands/native/item/brand/list/schema.json +725 -0
- package/src/commands/native/item/classification/get/command.js +87 -0
- package/src/commands/native/item/classification/get/schema.json +596 -0
- package/src/commands/native/item/classification/list/command.js +105 -0
- package/src/commands/native/item/classification/list/schema.json +676 -0
- package/src/commands/native/item/commands.index.json +87 -0
- package/src/commands/native/item/domain.json +1 -0
- package/src/commands/native/item/group/list/command.js +122 -0
- package/src/commands/native/item/group/list/schema.json +809 -0
- package/src/commands/native/item/item/get/command.js +122 -0
- package/src/commands/native/item/item/get/schema.json +3799 -0
- package/src/commands/native/item/item/list/command.js +7 -0
- package/src/commands/native/item/item/list/schema.json +1118 -0
- package/src/commands/native/item/item/scan/command.js +7 -0
- package/src/commands/native/item/item/scan/schema.json +1046 -0
- package/src/commands/native/item/item-content/get/command.js +54 -0
- package/src/commands/native/item/item-content/get/schema.json +677 -0
- package/src/commands/native/item/shared/api.js +31 -0
- package/src/commands/native/item/shared/content.js +20 -0
- package/src/commands/native/item/shared/diagnostics.js +61 -0
- package/src/commands/native/item/shared/h5.js +63 -0
- package/src/commands/native/item/shared/identity.js +62 -0
- package/src/commands/native/item/shared/normalize.js +49 -0
- package/src/commands/native/item/shared/scope.js +22 -0
- package/src/commands/native/item/shared/search-command.js +61 -0
- package/src/commands/native/item/shared/search-options.js +150 -0
- package/src/commands/native/item/shared/search.js +158 -0
- package/src/commands/native/member/commands.index.json +60 -0
- package/src/commands/native/member/customer-level/get/command.js +122 -0
- package/src/commands/native/member/customer-level/get/schema.json +300 -0
- package/src/commands/native/member/domain.json +3 -0
- package/src/commands/native/member/growth/get/command.js +64 -0
- package/src/commands/native/member/growth/get/schema.json +283 -0
- package/src/commands/native/member/growth-log/list/command.js +203 -0
- package/src/commands/native/member/growth-log/list/schema.json +695 -0
- package/src/commands/native/member/level/get/command.js +70 -0
- package/src/commands/native/member/level/get/query.js +125 -0
- package/src/commands/native/member/level/get/schema.json +339 -0
- package/src/commands/native/member/level/list/command.js +50 -0
- package/src/commands/native/member/level/list/query.js +109 -0
- package/src/commands/native/member/level/list/schema.json +104 -0
- package/src/commands/native/member/membership/get/command.js +78 -0
- package/src/commands/native/member/membership/get/schema.json +283 -0
- package/src/commands/native/order/commands.index.json +33 -0
- package/src/commands/native/order/domain.json +3 -0
- package/src/commands/native/order/order/get/command.js +155 -0
- package/src/commands/native/order/order/get/schema.json +1167 -0
- package/src/commands/native/order/order/list/command.js +230 -0
- package/src/commands/native/order/order/list/schema.json +1363 -0
- package/src/commands/native/order/order/scan/command.js +148 -0
- package/src/commands/native/order/order/scan/scan.js +89 -0
- package/src/commands/native/order/order/scan/schema.json +1445 -0
- package/src/commands/native/order/shared/projection.js +183 -0
- package/src/commands/native/order/shared/query.js +84 -0
- package/src/commands/native/order/shared/search.js +112 -0
- package/src/commands/native/org/commands.index.json +42 -0
- package/src/commands/native/org/domain.json +3 -0
- package/src/commands/native/org/shared/single-staff.js +115 -0
- package/src/commands/native/org/staff/get/command.js +148 -0
- package/src/commands/native/org/staff/get/schema.json +114 -0
- package/src/commands/native/org/staff-role/get/command.js +45 -0
- package/src/commands/native/org/staff-role/get/schema.json +93 -0
- package/src/commands/native/org/unit/get/command.js +131 -0
- package/src/commands/native/org/unit/get/schema.json +478 -0
- package/src/commands/native/org/unit/list/command.js +187 -0
- package/src/commands/native/org/unit/list/schema.json +828 -0
- package/src/commands/native/points/account/get/command.js +65 -0
- package/src/commands/native/points/account/get/schema.json +303 -0
- package/src/commands/native/points/commands.index.json +33 -0
- package/src/commands/native/points/domain.json +3 -0
- package/src/commands/native/points/log/list/command.js +141 -0
- package/src/commands/native/points/log/list/schema.json +761 -0
- package/src/commands/native/points/rule/list/command.js +82 -0
- package/src/commands/native/points/rule/list/schema.json +294 -0
- package/src/commands/native/privacy/commands.index.json +15 -0
- package/src/commands/native/privacy/domain.json +3 -0
- package/src/commands/native/privacy/field/decrypt/command.js +185 -0
- package/src/commands/native/privacy/field/decrypt/schema.json +656 -0
- package/src/commands/native/privacy/shared/order-source.js +33 -0
- package/src/commands/native/stock/commands.index.json +15 -0
- package/src/commands/native/stock/domain.json +3 -0
- package/src/commands/native/stock/log/list/command.js +207 -0
- package/src/commands/native/stock/log/list/schema.json +922 -0
- package/src/commands/native/valuecard/card/get/command.js +107 -0
- package/src/commands/native/valuecard/card/get/schema.json +683 -0
- package/src/commands/native/valuecard/card/list/command.js +136 -0
- package/src/commands/native/valuecard/card/list/pagination.js +23 -0
- package/src/commands/native/valuecard/card/list/schema.json +780 -0
- package/src/commands/native/valuecard/commands.index.json +24 -0
- package/src/commands/native/valuecard/domain.json +3 -0
- package/src/commands/native/valuecard/shared/projection.js +57 -0
- package/src/commands/system/api.js +47 -0
- package/src/commands/system/auth.js +118 -0
- package/src/commands/system/contracts.js +373 -0
- package/src/commands/system/doctor.js +24 -0
- package/src/commands/system/install.js +41 -0
- package/src/commands/system/printer.js +16 -0
- package/src/commands/system/schema.js +162 -0
- package/src/commands/system/shop-guidance.js +9 -0
- package/src/commands/system/shop.js +237 -0
- package/src/commands/system/skills.js +49 -0
- package/src/execution/beijing-date-time.js +29 -0
- package/src/execution/beijing-time.js +21 -0
- package/src/execution/bounded-input.js +42 -0
- package/src/execution/command-io.js +137 -0
- package/src/execution/command-policy.js +41 -0
- package/src/execution/command-schema.js +131 -0
- package/src/execution/datetime.js +83 -0
- package/src/execution/error-details.js +399 -0
- package/src/execution/error-model.js +82 -0
- package/src/execution/exact-json.js +80 -0
- package/src/execution/list-total.js +10 -0
- package/src/execution/money.js +59 -0
- package/src/execution/parser-error.js +66 -0
- package/src/execution/privacy.js +197 -0
- package/src/execution/public-schema.js +52 -0
- package/src/execution/query-api.js +101 -0
- package/src/execution/range.js +84 -0
- package/src/execution/request-state.js +55 -0
- package/src/execution/schema-completeness.js +116 -0
- package/src/execution/shop-query.js +54 -0
- package/src/execution/target-shop-default.js +23 -0
- package/src/execution/target-shop.js +49 -0
- package/src/identity/context-store.js +54 -0
- package/src/identity/context.js +38 -0
- package/src/installation/install.js +158 -0
- package/src/installation/npm-error.js +93 -0
- package/src/installation/skill.js +113 -0
- package/src/transport/account-api.js +152 -0
- package/src/transport/api.js +199 -0
- package/src/transport/shop-app.js +63 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { withTargetShopDefault } = require('../../../../../execution/target-shop-default');
|
|
4
|
+
|
|
5
|
+
const { Command, Option } = require('commander');
|
|
6
|
+
const { validateCommandValue } = require('../../../../../execution/command-schema');
|
|
7
|
+
const { buildInput, reject } = require('../../shared/search-input');
|
|
8
|
+
const { preview, scan, serialize } = require('./scan');
|
|
9
|
+
const schema = require('./schema.json');
|
|
10
|
+
|
|
11
|
+
function createCommand({ context } = {}) {
|
|
12
|
+
const instance = new Command('scan')
|
|
13
|
+
.description('在显式成为客户时间及数量、请求预算内串行跨页观察客户')
|
|
14
|
+
.allowExcessArguments(false)
|
|
15
|
+
.exitOverride(error => {
|
|
16
|
+
if (['commander.helpDisplayed', 'commander.version'].includes(error.code)) throw error;
|
|
17
|
+
throw reject('validation', 'input');
|
|
18
|
+
})
|
|
19
|
+
.option('--kdt-id <id>', '目标店铺正安全整数ID文本;省略时使用当前上下文店铺')
|
|
20
|
+
.requiredOption('--created <start..end>', '必填:成为客户的北京时间完整范围,严格开始早于结束,最多366天')
|
|
21
|
+
.option('--keyword <text>', '姓名、手机号或昵称模糊搜索,非空且最多200字符,与mobile互斥')
|
|
22
|
+
.option('--mobile <text>', '单个精确手机号,可选+及5至20位数字,保留前导零;不接受CSV')
|
|
23
|
+
.option('--is-member <boolean>', 'true=会员,false=非会员;省略不筛选')
|
|
24
|
+
.option('--tag-ids <ids>', '1至50项标签定义ID,CSV或JSON字符串数组,固定全部满足')
|
|
25
|
+
.option('--member-created <start..end>', '成为会员的北京时间范围,可单边,不证明当前仍是会员')
|
|
26
|
+
.option('--last-trade <start..end>', '最后购买的北京时间范围,可单边,不是任意窗口购买或未下单')
|
|
27
|
+
.option('--trade-count <range>', '历史购次非负32位整数范围,不保证全渠道口径')
|
|
28
|
+
.option('--trade-amount <range>', '历史累计消费人民币元文本范围,最多两位小数,精确转分,不是净收入')
|
|
29
|
+
.option('--points <range>', '搜索积分非负安全整数范围,不读取流水')
|
|
30
|
+
.option('--stored-balance <range>', '搜索储值余额人民币元范围,不等于逐卡可支付余额或全部资产')
|
|
31
|
+
.option('--level-ids <ids>', '会员等级定义level_id,1至50项,不能用alias或等级值,不推定多值AND/OR')
|
|
32
|
+
.option('--has-level <boolean>', 'true=有等级,false=无等级;false与level-ids冲突')
|
|
33
|
+
.option('--benefit-card-ids <ids>', '权益卡定义cardId,1至50项,不能用cardAlias或cardNo,不推定多值AND/OR')
|
|
34
|
+
.option('--has-benefit-card <boolean>', 'true=有本搜索API会员卡,false=无卡;false与卡ID冲突,不证明卡有效')
|
|
35
|
+
.option('--max-items <n>', '去重客户预算1至2000,默认200,可裁切末页', '200')
|
|
36
|
+
.option('--max-requests <n>', '业务请求预算2至200,默认20,含绑定、窗口观察和翻页', '20')
|
|
37
|
+
.addOption(new Option('--format <format>', 'json或pretty,只改变缩进').choices(['json', 'pretty']).default('json'))
|
|
38
|
+
.option('--dry-run', '零认证零请求本地预览,mobile与keyword遮盖为***')
|
|
39
|
+
.addHelpText('after', `
|
|
40
|
+
范围语法 LOW..HIGH、LOW..、..HIGH;数值裸值传相同双边,时间必须含..。
|
|
41
|
+
时间输入北京时间 YYYY-MM-DD HH:mm:ss[.SSS],输出固定三位毫秒;秒接口只接受零毫秒。
|
|
42
|
+
时间限Unix秒0至4102444800。范围不补当前时间或0,空范围、空白、负数、指数均拒绝。
|
|
43
|
+
金额元文本精确转分,分值及积分上限9007199254740991。ID均为正安全整数文本。
|
|
44
|
+
所有标量选项不得重复。标签固定AND;has=false加相应定义ID零请求拒绝,不自动补true。
|
|
45
|
+
固定服务端创建时间降序、每页50、并发1,不承诺跨窗口全局排序。
|
|
46
|
+
按yzOpenId保留首次记录,不按手机号合并;缺失明细省略,null或错型整体失败。
|
|
47
|
+
摘要包括身份、展示名、手机、性别、会员标记、购次、积分及创建时间,不读取详情代验筛选。
|
|
48
|
+
createdAt源0无效;memberCreatedAt允许数字0,不能据此推断会员身份。
|
|
49
|
+
pagination包含traversalCompleted、stopReason、requestCount、duplicatesRemoved。
|
|
50
|
+
pages_exhausted仅表示本轮叶窗遍历结束,不是快照、无遗漏或全店完整历史导出证明。
|
|
51
|
+
其它停止原因max_items、max_requests、window_limit、total_unknown、inconsistent_page均不完整。
|
|
52
|
+
父窗越10000时严格缩小重叠1秒子窗,新窗先读;预算包含父窗观察,绝不发超窗口页。
|
|
53
|
+
每请求10秒,累计业务响应10MiB,最终含换行输出5MiB;任何后页失败整体失败,不输出前批。
|
|
54
|
+
无恢复游标、后台导出、来源渠道、phones/lookup、include或sort,不自动重试、初始化应用或换身份。
|
|
55
|
+
真实调用需有效四字段上下文与公共认证;非总部显式目标须等于shopId,总部仍须同店绑定及服务端授权。
|
|
56
|
+
缺上下文仅可显式目标本地预览,预览不证明授权。应用测试身份不证明店铺员工权限隔离。
|
|
57
|
+
完整闭合输入、摘要及错误模型见 zan-saas schema customer.customer.scan。
|
|
58
|
+
|
|
59
|
+
示例:
|
|
60
|
+
zan-saas customer customer scan --kdt-id 63077 --created "2026-10-01 00:00:00..2026-10-02 00:00:00" --points "100..500" --max-requests 2 --dry-run
|
|
61
|
+
zan-saas customer customer scan --kdt-id 63077 --created "2026-10-01 00:00:00..2026-10-02 00:00:00" --mobile +8613800000000 --has-level false
|
|
62
|
+
`);
|
|
63
|
+
const supplied = new Set();
|
|
64
|
+
for (const option of instance.options) {
|
|
65
|
+
instance.on(`option:${option.name()}`, () => {
|
|
66
|
+
if (supplied.has(option.name())) throw reject('validation', 'input');
|
|
67
|
+
supplied.add(option.name());
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return instance.action(async options => {
|
|
71
|
+
const input = buildInput(options, context);
|
|
72
|
+
validateCommandValue(schema, 'input', input);
|
|
73
|
+
const result = options.dryRun ? preview(input) : await scan(input);
|
|
74
|
+
try { validateCommandValue(schema, options.dryRun ? 'preview' : 'output', result); }
|
|
75
|
+
catch { throw reject('protocol', 'output', { kdtId: input.kdtId }, options.dryRun ? 'not_sent' : 'sent'); }
|
|
76
|
+
process.stdout.write(serialize(result, options.format === 'pretty', { kdtId: input.kdtId }, options.dryRun ? 'not_sent' : 'sent'));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { kind: 'atomic', risk: 'read', at: 'all', createCommand: withTargetShopDefault(createCommand) };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createBoundShopQuery, bindingPath } = require('../../../../../execution/shop-query');
|
|
4
|
+
const { parseDateTime } = require('../../../../../execution/datetime');
|
|
5
|
+
const { bodyFor, reject, adaptError, searchPath, command } = require('../../shared/search-input');
|
|
6
|
+
const { projectPage } = require('../../shared/search-page');
|
|
7
|
+
|
|
8
|
+
const responseLimit = 10 * 1024 * 1024;
|
|
9
|
+
const outputLimit = 5 * 1024 * 1024;
|
|
10
|
+
|
|
11
|
+
function preview(input) {
|
|
12
|
+
const body = bodyFor(input.filters);
|
|
13
|
+
for (const key of ['mobile', 'keyword']) if (Object.hasOwn(body, key)) body[key] = '***';
|
|
14
|
+
return {
|
|
15
|
+
requests: [{ method: 'POST', path: bindingPath, body: {} }, { method: 'POST', path: searchPath, body }],
|
|
16
|
+
strategy: { kind: 'bounded-created-time-scan', ...input.limits,
|
|
17
|
+
pageSize: 50, concurrency: 1, dynamicRequests: true }
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function scan(input) {
|
|
22
|
+
const scope = { kdtId: input.kdtId };
|
|
23
|
+
let requestCount = 0, responseBytes = 0, duplicatesRemoved = 0;
|
|
24
|
+
const customers = [], seenIds = new Set();
|
|
25
|
+
const originalWindow = { start: parseDateTime(input.filters.created.start) / 1000,
|
|
26
|
+
end: parseDateTime(input.filters.created.end) / 1000 };
|
|
27
|
+
const pendingWindows = [{ ...originalWindow }];
|
|
28
|
+
const finish = stopReason => ({ data: { customers, pagination: {
|
|
29
|
+
traversalCompleted: stopReason === 'pages_exhausted', stopReason, requestCount, duplicatesRemoved
|
|
30
|
+
} } });
|
|
31
|
+
let read;
|
|
32
|
+
try {
|
|
33
|
+
({ read } = await createBoundShopQuery({ kdtId: Number(input.kdtId) }, command, 'shop-binding', {
|
|
34
|
+
onResponse(response) { responseBytes += response.responseBytes; },
|
|
35
|
+
transportOptions: { shopApp: false, timeoutMs: 10000,
|
|
36
|
+
maxResponseBytes: () => responseLimit - responseBytes,
|
|
37
|
+
onRequest() {
|
|
38
|
+
if (requestCount >= input.limits.maxRequests) throw reject('limit', 'scan', scope);
|
|
39
|
+
requestCount += 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}));
|
|
43
|
+
} catch (error) { throw adaptError(error, scope); }
|
|
44
|
+
while (pendingWindows.length) {
|
|
45
|
+
const window = pendingWindows.pop();
|
|
46
|
+
let pageNo = 1, previousIds;
|
|
47
|
+
while (true) {
|
|
48
|
+
if (requestCount >= input.limits.maxRequests) return finish('max_requests');
|
|
49
|
+
let response;
|
|
50
|
+
try { response = await read(searchPath, bodyFor(input.filters, window, pageNo), 'search'); }
|
|
51
|
+
catch (error) { throw adaptError(error, scope); }
|
|
52
|
+
const page = projectPage(response.data, pageNo, originalWindow, scope, response.facts);
|
|
53
|
+
let undelivered = false;
|
|
54
|
+
for (const customer of page.customers) {
|
|
55
|
+
if (seenIds.has(customer.yzOpenId)) duplicatesRemoved += 1;
|
|
56
|
+
else if (customers.length === input.limits.maxItems) undelivered = true;
|
|
57
|
+
else { seenIds.add(customer.yzOpenId); customers.push(customer); }
|
|
58
|
+
}
|
|
59
|
+
const ids = page.customers.map(customer => customer.yzOpenId);
|
|
60
|
+
const exceedsWindow = page.total > 10000;
|
|
61
|
+
const leafDone = page.total !== undefined && !exceedsWindow && pageNo * 50 >= page.total;
|
|
62
|
+
const shortPage = page.customers.length < 50;
|
|
63
|
+
if (!exceedsWindow && page.total !== undefined && shortPage &&
|
|
64
|
+
(pageNo - 1) * 50 + page.customers.length < page.total) return finish('inconsistent_page');
|
|
65
|
+
if (!exceedsWindow && !leafDone && previousIds && JSON.stringify(ids) === JSON.stringify(previousIds)) {
|
|
66
|
+
return finish('inconsistent_page');
|
|
67
|
+
}
|
|
68
|
+
if (page.total === undefined && shortPage) return finish('total_unknown');
|
|
69
|
+
if (undelivered) return finish('max_items');
|
|
70
|
+
if (leafDone && pendingWindows.length === 0) return finish('pages_exhausted');
|
|
71
|
+
if (customers.length === input.limits.maxItems) return finish('max_items');
|
|
72
|
+
if (leafDone) break;
|
|
73
|
+
if (exceedsWindow || (pageNo + 1) * 50 > 10000) {
|
|
74
|
+
if (window.end - window.start < 3) return finish('window_limit');
|
|
75
|
+
const middle = Math.floor((window.start + window.end) / 2);
|
|
76
|
+
pendingWindows.push({ start: window.start, end: middle + 1 }, { start: middle, end: window.end });
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
previousIds = ids;
|
|
80
|
+
pageNo += 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return finish('pages_exhausted');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function serialize(output, pretty, scope, state = 'sent') {
|
|
87
|
+
const json = JSON.stringify(output, null, pretty ? 2 : 0);
|
|
88
|
+
if (Buffer.byteLength(`${json}\n`) > outputLimit) throw reject('limit', 'output', scope, state);
|
|
89
|
+
return `${json}\n`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { preview, scan, serialize };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"input": {
|
|
4
|
+
"type": "object", "additionalProperties": false, "required": ["kdtId", "filters", "limits"],
|
|
5
|
+
"description": "规范化业务输入,不是argv;范围表达式在CLI层解析,不把表达式原文传给接口。",
|
|
6
|
+
"properties": {
|
|
7
|
+
"kdtId": { "$ref": "#/definitions/id", "description": "目标查询店铺;省略 --kdt-id 时取当前上下文 shopId,显式非法值不回退;非总部须等于上下文 shopId,总部不自动遍历分店。" },
|
|
8
|
+
"filters": {
|
|
9
|
+
"type": "object", "additionalProperties": false, "required": ["created"],
|
|
10
|
+
"properties": {
|
|
11
|
+
"keyword": { "type": "string", "minLength": 1, "maxLength": 200, "pattern": "^\\S(?:[\\s\\S]*\\S)?$", "description": "姓名、手机号或昵称模糊搜索,与mobile互斥。" },
|
|
12
|
+
"mobile": { "type": "string", "pattern": "^\\+?\\d{5,20}$", "description": "单值精确手机号文本,保留前导零和加号,不支持CSV或数组。" },
|
|
13
|
+
"isMember": { "type": "boolean", "description": "true=会员,false=非会员;省略不筛选,不从卡或等级推算。" },
|
|
14
|
+
"tagIds": { "$ref": "#/definitions/ids", "description": "标签定义ID,固定全部满足AND,首现去重。" },
|
|
15
|
+
"created": { "$ref": "#/definitions/createdRange" },
|
|
16
|
+
"memberCreated": { "$ref": "#/definitions/timeRange", "description": "成为会员时间范围,不证明当前仍是会员。" },
|
|
17
|
+
"lastTrade": { "$ref": "#/definitions/timeRange", "description": "最后购买时间,不是任意窗口内购买或未下单。" },
|
|
18
|
+
"tradeCount": { "$ref": "#/definitions/countRange", "description": "搜索服务历史购次,不保证全渠道口径。" },
|
|
19
|
+
"tradeAmount": { "$ref": "#/definitions/moneyRange", "description": "历史累计消费人民币元文本,不是净收入。" },
|
|
20
|
+
"points": { "$ref": "#/definitions/integerRange", "description": "搜索积分非负范围,不读取流水。" },
|
|
21
|
+
"storedBalance": { "$ref": "#/definitions/moneyRange", "description": "搜索储值余额人民币元文本,不等于逐卡可支付余额或全部资产。" },
|
|
22
|
+
"levelIds": { "$ref": "#/definitions/ids", "description": "会员等级定义level_id,不接受alias、等级值或客户ID;不推定多值AND/OR。" },
|
|
23
|
+
"hasLevel": { "type": "boolean", "description": "true=有等级,false=无等级;false与非空levelIds冲突,不自动补true。" },
|
|
24
|
+
"benefitCardIds": { "$ref": "#/definitions/ids", "description": "权益卡定义cardId,可由benefit card-definition list取得,不接受cardAlias、cardNo或储值卡号;不推定多值AND/OR。" },
|
|
25
|
+
"hasBenefitCard": { "type": "boolean", "description": "true=有搜索API会员卡,false=无卡;false与卡定义ID冲突,不证明卡当前有效。" }
|
|
26
|
+
},
|
|
27
|
+
"allOf": [
|
|
28
|
+
{ "not": { "properties": { "keyword": { "type": "string" }, "mobile": { "type": "string" } }, "required": ["keyword", "mobile"] } },
|
|
29
|
+
{ "not": { "required": ["hasLevel", "levelIds"], "properties": { "hasLevel": { "const": false }, "levelIds": { "$ref": "#/definitions/ids" } } } },
|
|
30
|
+
{ "not": { "required": ["hasBenefitCard", "benefitCardIds"], "properties": { "hasBenefitCard": { "const": false }, "benefitCardIds": { "$ref": "#/definitions/ids" } } } }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"limits": {
|
|
34
|
+
"type": "object", "additionalProperties": false, "required": ["maxItems", "maxRequests"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"maxItems": { "type": "integer", "minimum": 1, "maximum": 2000, "default": 200, "description": "去重客户预算,可截取已完整校验的末页;未交付新客户必须标记max_items。" },
|
|
37
|
+
"maxRequests": { "type": "integer", "minimum": 2, "maximum": 200, "default": 20, "description": "总业务请求预算,含绑定、窗口观察及翻页,不含公共认证。" }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"output": {
|
|
43
|
+
"type": "object", "additionalProperties": false, "required": ["data"],
|
|
44
|
+
"properties": {
|
|
45
|
+
"data": {
|
|
46
|
+
"type": "object", "additionalProperties": false, "required": ["customers", "pagination"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"customers": { "type": "array", "maxItems": 2000, "items": { "$ref": "#/definitions/customer" }, "description": "按实际请求观察顺序保留首次yzOpenId,不按手机号合并,不承诺跨窗口全局排序。" },
|
|
49
|
+
"pagination": {
|
|
50
|
+
"type": "object", "additionalProperties": false,
|
|
51
|
+
"required": ["traversalCompleted", "stopReason", "requestCount", "duplicatesRemoved"],
|
|
52
|
+
"properties": {
|
|
53
|
+
"traversalCompleted": { "type": "boolean", "description": "仅pages_exhausted为true,只表示本轮计划叶窗遍历结束,不证明快照、无遗漏或完整历史导出。" },
|
|
54
|
+
"stopReason": {
|
|
55
|
+
"type": "string", "enum": ["pages_exhausted", "max_items", "max_requests", "window_limit", "total_unknown", "inconsistent_page"],
|
|
56
|
+
"description": "pages_exhausted=当次有效总数下所有叶窗读完;max_items=仍有工作或本页未交付新客户而达到数量预算;max_requests=下一业务请求超过预算;window_limit=最小时间桶仍需拆窗但无法严格缩小;total_unknown=未知总数的短页或空页;inconsistent_page=非越窗页的已知总数前提前短页,或同叶连续页主键序列相同且仍有工作。非越窗页指total已知且total<=10000;越窗页指total>10000。越窗观察页即使短页也不因此判为inconsistent_page,保留合法客户,预算允许且时间桶可严格缩小时继续拆窗。协议失败优先且整体失败;非越窗提前短页、同叶停滞及未知总数短页优先于预算。越窗观察后的优先级为max_items、window_limit、max_requests,分别对应数量预算已满或本页仍有未交付新客户、无法严格缩小的必要拆窗、下一业务请求超预算。仅所有叶窗完成且没有未交付新客户时,pages_exhausted优先于恰好耗尽的预算。"
|
|
57
|
+
},
|
|
58
|
+
"requestCount": { "type": "integer", "minimum": 2, "maximum": 200, "description": "实际已发送业务API请求数,含绑定与父窗观察。" },
|
|
59
|
+
"duplicatesRemoved": { "type": "integer", "minimum": 0, "maximum": 10000, "description": "已交付yzOpenId后续重复记录数,不覆盖首条或暗示内容相同。" }
|
|
60
|
+
},
|
|
61
|
+
"allOf": [{ "if": { "properties": { "stopReason": { "const": "pages_exhausted" } } }, "then": { "properties": { "traversalCompleted": { "const": true } } }, "else": { "properties": { "traversalCompleted": { "const": false } } } }]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"preview": {
|
|
68
|
+
"type": "object", "additionalProperties": false, "required": ["requests", "strategy"],
|
|
69
|
+
"properties": {
|
|
70
|
+
"requests": {
|
|
71
|
+
"type": "array", "minItems": 2, "maxItems": 2,
|
|
72
|
+
"items": { "oneOf": [{ "$ref": "#/definitions/bindingRequest" }, { "$ref": "#/definitions/searchRequest" }] },
|
|
73
|
+
"description": "依次为同店绑定和原始创建窗口第一页;后续窗口由真实响应决定,不探测总数。"
|
|
74
|
+
},
|
|
75
|
+
"strategy": {
|
|
76
|
+
"type": "object", "additionalProperties": false,
|
|
77
|
+
"required": ["kind", "maxItems", "maxRequests", "pageSize", "concurrency", "dynamicRequests"],
|
|
78
|
+
"properties": {
|
|
79
|
+
"kind": { "type": "string", "const": "bounded-created-time-scan", "description": "创建时间有界扫描。" },
|
|
80
|
+
"maxItems": { "type": "integer", "minimum": 1, "maximum": 2000, "description": "本次用户结果预算。" },
|
|
81
|
+
"maxRequests": { "type": "integer", "minimum": 2, "maximum": 200, "description": "本次用户业务请求预算。" },
|
|
82
|
+
"pageSize": { "type": "integer", "const": 50, "description": "固定页容量。" },
|
|
83
|
+
"concurrency": { "type": "integer", "const": 1, "description": "固定串行读取。" },
|
|
84
|
+
"dynamicRequests": { "type": "boolean", "const": true, "description": "后续请求需看真实响应。" }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"error": {
|
|
90
|
+
"type": "object", "additionalProperties": false, "required": ["success", "code", "message", "details"],
|
|
91
|
+
"properties": {
|
|
92
|
+
"success": { "type": "boolean", "const": false },
|
|
93
|
+
"code": { "type": "integer", "const": -10001 },
|
|
94
|
+
"message": { "type": "string", "minLength": 1, "description": "受控说明,不回显客户条件、token或上游自由消息。" },
|
|
95
|
+
"details": {
|
|
96
|
+
"type": "object", "additionalProperties": false,
|
|
97
|
+
"required": ["category", "stage", "command", "targetScope", "requestState", "nextStep"],
|
|
98
|
+
"properties": {
|
|
99
|
+
"category": { "type": "string", "enum": ["validation", "configuration", "authentication", "permission", "scope_unresolved", "http", "business_rejected", "transport", "protocol", "limit"], "description": "validation=本地输入非法;configuration=公共配置不可用;authentication=认证失败;permission=明确权限拒绝;scope_unresolved=范围未确认;http=HTTP拒绝;business_rejected=业务拒绝;transport=传输失败;protocol=模型不符;limit=字节或固定资源超限。" },
|
|
100
|
+
"stage": { "type": "string", "enum": ["input", "context", "configuration", "shop-binding", "search", "scan", "output"], "description": "input=输入;context=上下文;configuration=公共环境;shop-binding=店铺绑定;search=搜索;scan=遍历;output=输出组装。" },
|
|
101
|
+
"command": { "type": "string", "const": "customer.customer.scan" },
|
|
102
|
+
"targetScope": { "type": "object", "additionalProperties": false, "properties": { "kdtId": { "$ref": "#/definitions/id" } }, "description": "仅保留已本地校验的目标店铺,否则空对象;不回显客户条件。" },
|
|
103
|
+
"requestState": { "type": "string", "enum": ["not_sent", "sent", "unknown"], "description": "not_sent=相关请求未发送;sent=收到响应但不代表业务成功;unknown=已发送但结果不明。" },
|
|
104
|
+
"nextStep": { "type": "string", "minLength": 1, "description": "核对原目标、输入及授权,不自动重试或变更查询模式,前批结果不构成成功。" },
|
|
105
|
+
"api": { "type": "string", "enum": ["youzan.shop.basic.get/3.0.0", "youzan.scrm.customer.list/1.0.0"], "description": "实际失败API。" },
|
|
106
|
+
"httpStatus": { "type": "integer", "minimum": 100, "maximum": 599 },
|
|
107
|
+
"businessCode": { "$ref": "#/definitions/diagnosticCode" },
|
|
108
|
+
"gatewayCode": { "$ref": "#/definitions/diagnosticCode" },
|
|
109
|
+
"trace_id": { "$ref": "#/definitions/trace" },
|
|
110
|
+
"request_id": { "$ref": "#/definitions/trace" }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"definitions": {
|
|
116
|
+
"id": { "type": "string", "pattern": "^[1-9]\\d*$", "maxLength": 16, "description": "正十进制安全整数ID文本,最大9007199254740991,不接受首尾空白或别名。" },
|
|
117
|
+
"ids": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "$ref": "#/definitions/id" }, "description": "原始CSV或JSON字符串数组1至50项,逐项验证后首现去重。" },
|
|
118
|
+
"safeNonnegative": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
|
|
119
|
+
"safeInteger": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 },
|
|
120
|
+
"count": { "type": "integer", "minimum": 0, "maximum": 2147483647 },
|
|
121
|
+
"money": { "type": "string", "pattern": "^(0|[1-9]\\d*)\\.\\d{2}$", "description": "人民币元固定两位文本,精确分值不超过9007199254740991。" },
|
|
122
|
+
"time": { "type": "string", "format": "beijing-date-time-ms", "pattern": "\\.000$", "description": "北京时间YYYY-MM-DD HH:mm:ss.SSS,零毫秒,输入Unix秒0至4102444800。" },
|
|
123
|
+
"outputTime": { "type": "string", "format": "beijing-date-time-ms", "description": "按源Unix秒转换的北京时间YYYY-MM-DD HH:mm:ss.SSS,正安全整数源时间且在0001至9999年内。" },
|
|
124
|
+
"createdRange": {
|
|
125
|
+
"type": "object", "additionalProperties": false, "required": ["start", "end"],
|
|
126
|
+
"properties": { "start": { "$ref": "#/definitions/time" }, "end": { "$ref": "#/definitions/time" } },
|
|
127
|
+
"description": "成为客户时间,argv必须start..end,严格start<end且跨度至多366天,规范化原范围不被拆窗改写。"
|
|
128
|
+
},
|
|
129
|
+
"timeRange": {
|
|
130
|
+
"type": "object", "additionalProperties": false, "minProperties": 1,
|
|
131
|
+
"properties": { "start": { "$ref": "#/definitions/time" }, "end": { "$ref": "#/definitions/time" } },
|
|
132
|
+
"description": "时间必须含..,允许单边,不补缺省,双边start≤end,拒绝非零毫秒、非法日历、Unix数字、T分隔或时区后缀。"
|
|
133
|
+
},
|
|
134
|
+
"integerRange": {
|
|
135
|
+
"type": "object", "additionalProperties": false, "minProperties": 1,
|
|
136
|
+
"properties": { "min": { "$ref": "#/definitions/safeNonnegative" }, "max": { "$ref": "#/definitions/safeNonnegative" } },
|
|
137
|
+
"description": "LOW..HIGH、LOW..、..HIGH;裸值传相同双边,不补0或缺省上限,双边min≤max,拒绝空白、负数及指数。"
|
|
138
|
+
},
|
|
139
|
+
"countRange": {
|
|
140
|
+
"type": "object", "additionalProperties": false, "minProperties": 1,
|
|
141
|
+
"properties": { "min": { "$ref": "#/definitions/count" }, "max": { "$ref": "#/definitions/count" } },
|
|
142
|
+
"description": "购次整数范围,语法同数值范围,双边min≤max。"
|
|
143
|
+
},
|
|
144
|
+
"moneyRange": {
|
|
145
|
+
"type": "object", "additionalProperties": false, "minProperties": 1,
|
|
146
|
+
"properties": { "min": { "$ref": "#/definitions/money" }, "max": { "$ref": "#/definitions/money" } },
|
|
147
|
+
"description": "金额数值范围,输入最多两位小数,规范化固定两位,按精确分值比较min≤max,拒绝负数和指数。"
|
|
148
|
+
},
|
|
149
|
+
"customer": {
|
|
150
|
+
"type": "object", "additionalProperties": false, "required": ["yzOpenId"],
|
|
151
|
+
"properties": {
|
|
152
|
+
"yzOpenId": { "type": "string", "minLength": 1, "pattern": "^\\S(?:[\\s\\S]*\\S)?$", "description": "稳定开放客户标识,不裁空白、不数值化,不是手机号或内部Long用户ID。" },
|
|
153
|
+
"name": { "type": "string", "description": "客户姓名,合法空串保留。" },
|
|
154
|
+
"showName": { "type": "string", "description": "服务端推荐展示名,不重新排优先级。" },
|
|
155
|
+
"mobile": { "type": "string", "description": "手机号,允许空串,缺失不推断无手机号,不代替稳定标识。" },
|
|
156
|
+
"gender": { "$ref": "#/definitions/safeInteger", "description": "0=其他、1=男、2=女;未来安全整数原值保留,不贴新标签。" },
|
|
157
|
+
"isMember": { "type": "boolean", "description": "源整数0=非会员、1=会员;其它编码失败,不由等级、卡或时间推算。" },
|
|
158
|
+
"tradeCount": { "$ref": "#/definitions/count", "description": "搜索服务购次,不保证全渠道口径。" },
|
|
159
|
+
"points": { "$ref": "#/definitions/safeInteger", "description": "搜索积分,允许负安全整数原值,不由筛选补值。" },
|
|
160
|
+
"createdAt": { "$ref": "#/definitions/outputTime", "description": "成为客户时间,源正Unix秒,0或null失败;若返回须处于原始查询总窗口。" },
|
|
161
|
+
"memberCreatedAt": { "anyOf": [{ "$ref": "#/definitions/outputTime" }, { "type": "integer", "const": 0 }], "description": "成为会员时间,源正Unix秒转换;数字0原样保留,不独立表示非会员。" },
|
|
162
|
+
"isMobileAuthorized": { "type": "boolean", "description": "是否授权手机号,不称为已验证。" }
|
|
163
|
+
},
|
|
164
|
+
"description": "有限摘要,所选可选明细缺失省略,null或错型失败;未知源扩展忽略。"
|
|
165
|
+
},
|
|
166
|
+
"diagnosticCode": { "anyOf": [{ "$ref": "#/definitions/safeInteger" }, { "type": "string", "pattern": "^-?\\d{1,12}$" }], "description": "白名单诊断码,不含任意自由消息。" },
|
|
167
|
+
"trace": { "type": "string", "pattern": "^[a-zA-Z0-9_-]{1,128}$", "description": "安全追踪号,不拼入自由文本。" },
|
|
168
|
+
"bindingRequest": {
|
|
169
|
+
"type": "object", "additionalProperties": false, "required": ["method", "path", "body"],
|
|
170
|
+
"properties": { "method": { "type": "string", "const": "POST" }, "path": { "type": "string", "const": "youzan.shop.basic.get/3.0.0" }, "body": { "type": "object", "additionalProperties": false, "maxProperties": 0 } }
|
|
171
|
+
},
|
|
172
|
+
"searchRequest": {
|
|
173
|
+
"type": "object", "additionalProperties": false, "required": ["method", "path", "body"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"method": { "type": "string", "const": "POST" }, "path": { "type": "string", "const": "youzan.scrm.customer.list/1.0.0" },
|
|
176
|
+
"body": {
|
|
177
|
+
"type": "object", "additionalProperties": false, "required": ["page_no", "page_size", "created_at_start", "created_at_end"],
|
|
178
|
+
"properties": {
|
|
179
|
+
"page_no": { "type": "integer", "const": 1 }, "page_size": { "type": "integer", "const": 50 },
|
|
180
|
+
"keyword": { "type": "string", "const": "***" }, "mobile": { "type": "string", "const": "***" },
|
|
181
|
+
"is_member": { "type": "integer", "enum": [0, 1], "description": "0=非会员,1=会员。" },
|
|
182
|
+
"has_level": { "type": "boolean", "description": "true=有等级,false=无等级。" }, "has_card": { "type": "boolean", "description": "true=有卡,false=无卡。" },
|
|
183
|
+
"all_match_tag_ids": { "$ref": "#/definitions/requestIds", "description": "标签定义安全整数JSON数组,全部满足。" },
|
|
184
|
+
"level_ids": { "$ref": "#/definitions/requestIds", "description": "等级定义安全整数JSON数组。" }, "card_ids": { "$ref": "#/definitions/requestIds", "description": "卡定义安全整数JSON数组。" },
|
|
185
|
+
"created_at_start": { "$ref": "#/definitions/seconds" }, "created_at_end": { "$ref": "#/definitions/seconds" },
|
|
186
|
+
"created_member_at_start": { "$ref": "#/definitions/seconds" }, "created_member_at_end": { "$ref": "#/definitions/seconds" },
|
|
187
|
+
"last_trade_time_start": { "$ref": "#/definitions/seconds" }, "last_trade_time_end": { "$ref": "#/definitions/seconds" },
|
|
188
|
+
"total_trade_count_min": { "$ref": "#/definitions/count" }, "total_trade_count_max": { "$ref": "#/definitions/count" },
|
|
189
|
+
"points_min": { "$ref": "#/definitions/safeNonnegative" }, "points_max": { "$ref": "#/definitions/safeNonnegative" },
|
|
190
|
+
"total_trade_amount_min": { "$ref": "#/definitions/safeNonnegative" }, "total_trade_amount_max": { "$ref": "#/definitions/safeNonnegative" },
|
|
191
|
+
"stored_balance_min": { "$ref": "#/definitions/safeNonnegative" }, "stored_balance_max": { "$ref": "#/definitions/safeNonnegative" }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"seconds": { "type": "integer", "minimum": 0, "maximum": 4102444800, "description": "北京时间适配后的Unix秒整数。" },
|
|
197
|
+
"requestIds": { "type": "array", "minItems": 1, "maxItems": 50, "uniqueItems": true, "items": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 }, "description": "HTTP JSON body原生安全整数数组,不是JSON数组文本。" }
|
|
198
|
+
},
|
|
199
|
+
"api": { "method": "POST", "path": "youzan.scrm.customer.list/1.0.0" },
|
|
200
|
+
"success": { "description": "同店绑定与全部实际请求成功且视图满足模型后输出;有界预算停止可返回有限结果,其余失败无部分成功。" },
|
|
201
|
+
"errors": { "description": "失败stdout为空,stderr一份脱敏错误JSON,退出码非零;不自动重试、初始化应用、换身份或切店。" },
|
|
202
|
+
"notes": [
|
|
203
|
+
"--kdt-id 省略时使用本次有效上下文 shopId;显式提供的非法值不回退。两者都缺失时拒绝;目标范围、同店核验和服务端权限仍按本命令校验。",
|
|
204
|
+
"公开选项为15项业务筛选和5项控制;format与dryRun不进入规范化业务input。",
|
|
205
|
+
"固定每页50、并发1、服务端创建时间降序;越10000窗口时按秒严格缩小并重叠1秒,先遍历较新窗口。重叠不是无遗漏证明。",
|
|
206
|
+
"每页重新使用当次total,不冻结首次总数;未知总数短页与同叶停滞不会报告完整;父子重叠不触发同叶停滞。",
|
|
207
|
+
"每请求10秒、累计流式业务响应10MiB、最终含换行UTF-8输出5MiB,任一后页失败整体失败。",
|
|
208
|
+
"无恢复游标,重复执行是新观察;不提供phones/lookup、来源渠道、sort、任意字段、后台导出或逐客户详情校验。",
|
|
209
|
+
"真实执行需有效四字段上下文及公共认证,同店绑定sid须等于显式目标;at=all或预览成功不证明授权,应用身份不证明员工权限隔离。"
|
|
210
|
+
]
|
|
211
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { withTargetShopDefault } = require('../../../../../execution/target-shop-default');
|
|
4
|
+
|
|
5
|
+
const { Command } = require('commander');
|
|
6
|
+
const { requestApi, snapshotApiEnvironment, ApiConfigurationError } = require('../../../../../transport/api');
|
|
7
|
+
const { validateCommandValue } = require('../../../../../execution/command-schema');
|
|
8
|
+
const schema = require('./schema.json');
|
|
9
|
+
|
|
10
|
+
const commandPath = 'customer.customer-tag.get';
|
|
11
|
+
const MAX_RESPONSE_BYTES = 10 * 1024 * 1024;
|
|
12
|
+
const MAX_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
13
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
14
|
+
const safeCode = value => Number.isSafeInteger(value) ||
|
|
15
|
+
(typeof value === 'string' && /^-?\d{1,12}$/.test(value));
|
|
16
|
+
|
|
17
|
+
function failure(category, stage, scope = {}, requestState = 'not_sent', facts = {}) {
|
|
18
|
+
const error = new Error(`客户标签关系查询失败:${category}(${stage})`);
|
|
19
|
+
error.cliDetails = {
|
|
20
|
+
category, stage, command: commandPath, targetScope: scope, requestState,
|
|
21
|
+
nextStep: '核对本命令 help/schema、目标店铺和客户标识;服务端拒绝时停止核对授权,不自动重试、切换编码、身份、店铺或接口。',
|
|
22
|
+
...facts
|
|
23
|
+
};
|
|
24
|
+
return error;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function buildRequest(options, context) {
|
|
28
|
+
let input;
|
|
29
|
+
try {
|
|
30
|
+
const target = Object.hasOwn(options, 'kdtId') ? options.kdtId : context?.shopId;
|
|
31
|
+
if (typeof target !== 'string' || !/^[1-9]\d*$/.test(target) || !['json', 'pretty'].includes(options.format)) {
|
|
32
|
+
throw new Error('invalid input');
|
|
33
|
+
}
|
|
34
|
+
input = { kdtId: Number(target), yzOpenId: options.yzOpenId.trim() };
|
|
35
|
+
validateCommandValue(schema, 'input', input);
|
|
36
|
+
} catch {
|
|
37
|
+
throw failure('parameter', 'input');
|
|
38
|
+
}
|
|
39
|
+
const scope = { kdtId: input.kdtId };
|
|
40
|
+
if (context && context.orgType !== 'hq' && String(input.kdtId) !== context.shopId) {
|
|
41
|
+
throw failure('permission', 'scope', scope);
|
|
42
|
+
}
|
|
43
|
+
const preview = {
|
|
44
|
+
targetScope: scope,
|
|
45
|
+
request: {
|
|
46
|
+
method: schema.api.method,
|
|
47
|
+
path: schema.api.path,
|
|
48
|
+
body: {
|
|
49
|
+
account: { account_type: 5, account_id: input.yzOpenId },
|
|
50
|
+
filter_invalid_tag: false,
|
|
51
|
+
filter_category_tag: false,
|
|
52
|
+
display_type: 2
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
validateCommandValue(schema, 'preview', preview);
|
|
57
|
+
return { input, preview };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function diagnostics(result, httpStatus) {
|
|
61
|
+
const facts = { api: schema.api.path, httpStatus };
|
|
62
|
+
if (safeCode(result?.code)) facts.businessCode = result.code;
|
|
63
|
+
if (object(result?.gw_err_resp) && safeCode(result.gw_err_resp.err_code)) {
|
|
64
|
+
facts.gatewayCode = result.gw_err_resp.err_code;
|
|
65
|
+
}
|
|
66
|
+
for (const field of ['trace_id', 'request_id']) {
|
|
67
|
+
const value = result?.[field] ?? result?.gw_err_resp?.[field];
|
|
68
|
+
if (typeof value === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(value)) {
|
|
69
|
+
facts[field] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return facts;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function execute({ input, preview }) {
|
|
76
|
+
const scope = preview.targetScope;
|
|
77
|
+
let response;
|
|
78
|
+
try {
|
|
79
|
+
const env = await snapshotApiEnvironment(undefined, input.kdtId);
|
|
80
|
+
response = await requestApi(preview.request, env);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (error instanceof ApiConfigurationError) throw failure('configuration', 'configuration', scope);
|
|
83
|
+
throw failure('transport', 'request', scope, 'unknown');
|
|
84
|
+
}
|
|
85
|
+
const fail = (category, stage, facts) => failure(category, stage, scope, 'sent', facts);
|
|
86
|
+
if (Buffer.byteLength(response.body) > MAX_RESPONSE_BYTES) {
|
|
87
|
+
throw fail('protocol', 'response-budget', { api: schema.api.path, httpStatus: response.httpStatus });
|
|
88
|
+
}
|
|
89
|
+
let result;
|
|
90
|
+
try { result = JSON.parse(response.body); }
|
|
91
|
+
catch { result = null; }
|
|
92
|
+
const facts = diagnostics(result, response.httpStatus);
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
const category = response.httpStatus === 401 ? 'authentication' :
|
|
95
|
+
response.httpStatus === 403 ? 'permission' : 'http';
|
|
96
|
+
throw fail(category, 'response', facts);
|
|
97
|
+
}
|
|
98
|
+
if (!object(result)) throw fail('protocol', 'response', facts);
|
|
99
|
+
if (object(result.gw_err_resp) && safeCode(result.gw_err_resp.err_code)) {
|
|
100
|
+
throw fail(result.success === true || result.code === 200 ? 'protocol' : 'business_rejected', 'response', facts);
|
|
101
|
+
}
|
|
102
|
+
if (result.success === false && safeCode(result.code) && Number(result.code) !== 200) {
|
|
103
|
+
throw fail('business_rejected', 'response', facts);
|
|
104
|
+
}
|
|
105
|
+
if (result.success !== true || result.code !== 200) throw fail('protocol', 'success', facts);
|
|
106
|
+
const data = result.data;
|
|
107
|
+
if (!object(data) || data.yz_open_id !== input.yzOpenId || !Array.isArray(data.tags)) {
|
|
108
|
+
throw fail('protocol', 'customer-tag-model', facts);
|
|
109
|
+
}
|
|
110
|
+
const tags = data.tags.map(tag => {
|
|
111
|
+
if (!object(tag)) throw fail('protocol', 'customer-tag-model', facts);
|
|
112
|
+
const mapped = { tag_id: tag.tag_id, tag_name: tag.name };
|
|
113
|
+
if (Object.hasOwn(tag, 'category_id')) mapped.tag_category_id = tag.category_id;
|
|
114
|
+
if (Object.hasOwn(tag, 'category_name')) mapped.tag_category_name = tag.category_name;
|
|
115
|
+
return mapped;
|
|
116
|
+
});
|
|
117
|
+
const output = { data: { customer_tag: { yz_open_id: data.yz_open_id, tags } } };
|
|
118
|
+
try { validateCommandValue(schema, 'output', output); }
|
|
119
|
+
catch { throw fail('protocol', 'customer-tag-model', facts); }
|
|
120
|
+
return { output, facts };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function createCommand({ context } = {}) {
|
|
124
|
+
return new Command('get')
|
|
125
|
+
.description('获取单个客户本次返回的完整标签集合及已取得的分组信息')
|
|
126
|
+
.allowExcessArguments(false)
|
|
127
|
+
.exitOverride(error => {
|
|
128
|
+
if (['commander.helpDisplayed', 'commander.version'].includes(error.code)) throw error;
|
|
129
|
+
throw failure('parameter', 'input');
|
|
130
|
+
})
|
|
131
|
+
.option('--kdt-id <id>', '目标店铺 ID,省略时使用当前选中店铺')
|
|
132
|
+
.requiredOption('--yz-open-id <id>', '必填:目标客户 yzOpenId,不接受手机号替代')
|
|
133
|
+
.option('--format <format>', 'json 或 pretty(仅改变缩进)', 'json')
|
|
134
|
+
.option('--dry-run', '仅本地预览,不读凭证、不联网')
|
|
135
|
+
.addHelpText('after', `
|
|
136
|
+
返回 data.customer_tag;tags 完整保留本次响应中的该客户标签集合;受授权可见范围限制,不承诺历史全集或跨时点快照。
|
|
137
|
+
请求隐藏和展示标签,不主动排除禁用或组内标签;仍受服务端授权和可见范围限制。
|
|
138
|
+
空 tags 数组可以成功;缺失数组、客户不匹配、非安全整数或成功信号冲突整体失败。
|
|
139
|
+
保留接口顺序、重复项和分类 ID 0;缺少分类名称时省略,不补 null。
|
|
140
|
+
不提供 --tag-id、分页、--include 或客户详情降级;现行客户详情 --include tags 不变。
|
|
141
|
+
获取单个客户标签集合推荐使用本命令;已有 customer-tag list 保留原契约供现有调用方使用。
|
|
142
|
+
list 的 data.yzOpenId/data.tags 与本命令结构不同,不互作别名或失败降级,不据此区分时效或历史范围。
|
|
143
|
+
执行需要有效的用户、组织与店铺上下文,可通过登录后选店或 ZAN_SAAS_CONTEXT 提供;目标范围和服务端授权按本命令契约校验,公共调用前置见 zan-saas skills read zan-saas-shared。
|
|
144
|
+
店铺上下文中目标必须等于 shopId;at: all 不是任意跨店授权。
|
|
145
|
+
接口不含店铺字段,目标店铺由网关上下文传递;省略 --kdt-id 时使用当前选中店铺。
|
|
146
|
+
一次最多一个请求;响应读取后检查10MiB、最终stdout检查5MiB,超额整体失败,不截断。
|
|
147
|
+
失败 stdout 为空、stderr 为错误 JSON;不自动重试、切编码、换身份、店铺或接口。
|
|
148
|
+
|
|
149
|
+
示例:
|
|
150
|
+
zan-saas customer customer-tag get --yz-open-id customer-1
|
|
151
|
+
zan-saas customer customer-tag get --kdt-id 63077 --yz-open-id customer-1 --format pretty
|
|
152
|
+
zan-saas customer customer-tag get --kdt-id 63077 --yz-open-id customer-1 --dry-run
|
|
153
|
+
`)
|
|
154
|
+
.action(async options => {
|
|
155
|
+
const request = buildRequest(options, context);
|
|
156
|
+
const { output, facts } = options.dryRun ? { output: request.preview } : await execute(request);
|
|
157
|
+
const text = `${JSON.stringify(output, null, options.format === 'pretty' ? 2 : 0)}\n`;
|
|
158
|
+
if (Buffer.byteLength(text) > MAX_OUTPUT_BYTES) {
|
|
159
|
+
throw failure('protocol', 'output-budget', request.preview.targetScope,
|
|
160
|
+
options.dryRun ? 'not_sent' : 'sent', facts);
|
|
161
|
+
}
|
|
162
|
+
process.stdout.write(text);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = { kind: 'atomic', risk: 'read', at: 'all', createCommand: withTargetShopDefault(createCommand) };
|