openxiangda 1.0.119 → 1.0.120
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/lib/cli.js +57 -0
- package/openxiangda-skills/references/component-guide.md +2 -0
- package/openxiangda-skills/references/forms/component-registry.md +1 -1
- package/openxiangda-skills/references/openxiangda-api.md +68 -0
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +11 -1
- package/package.json +1 -1
package/lib/cli.js
CHANGED
|
@@ -5804,6 +5804,23 @@ function validateResourceItem(kind, item, errors, warnings) {
|
|
|
5804
5804
|
if (!credentials.accessKeyId || !credentials.accessKeySecret) {
|
|
5805
5805
|
warnings.push(`${label}: 未声明凭据;publish 只适用于更新并保留平台已有密文`);
|
|
5806
5806
|
}
|
|
5807
|
+
const cors = config.cors || item.cors;
|
|
5808
|
+
if (cors?.managed === true) {
|
|
5809
|
+
const allowedOrigins = normalizeManifestStringArray(
|
|
5810
|
+
cors.allowedOrigins || cors.allowedOrigin || cors.origins || cors.origin
|
|
5811
|
+
);
|
|
5812
|
+
if (allowedOrigins.length === 0) {
|
|
5813
|
+
warnings.push(`${label}.cors.allowedOrigins: 未声明时后端将尝试使用当前平台域名`);
|
|
5814
|
+
}
|
|
5815
|
+
const allowedMethods = normalizeManifestStringArray(
|
|
5816
|
+
cors.allowedMethods || cors.allowedMethod || ['PUT', 'GET', 'HEAD']
|
|
5817
|
+
);
|
|
5818
|
+
if (allowedMethods.length === 0) errors.push(`${label}.cors.allowedMethods: 不能为空`);
|
|
5819
|
+
const maxAgeSeconds = Number(cors.maxAgeSeconds || 600);
|
|
5820
|
+
if (!Number.isFinite(maxAgeSeconds) || maxAgeSeconds < 0 || maxAgeSeconds > 86400) {
|
|
5821
|
+
errors.push(`${label}.cors.maxAgeSeconds: 必须在 0 到 86400 秒之间`);
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5807
5824
|
}
|
|
5808
5825
|
if (kind === 'authConfigs') {
|
|
5809
5826
|
const config = item.configJson || item.config || item;
|
|
@@ -7353,6 +7370,7 @@ async function publishStorageConfigResources(config, target, storageConfigs, res
|
|
|
7353
7370
|
code,
|
|
7354
7371
|
action: existing ? 'update' : 'create',
|
|
7355
7372
|
id: data?.id,
|
|
7373
|
+
corsStatus: data?.corsStatus,
|
|
7356
7374
|
});
|
|
7357
7375
|
}
|
|
7358
7376
|
}
|
|
@@ -8844,6 +8862,7 @@ function normalizeStorageConfigManifest(storageConfig) {
|
|
|
8844
8862
|
storageConfig.config ||
|
|
8845
8863
|
withoutStorageConfigManifestMeta(storageConfig)
|
|
8846
8864
|
);
|
|
8865
|
+
const cors = normalizeStorageCorsForPublish(configJson.cors || storageConfig.cors);
|
|
8847
8866
|
const credentials = normalizeStorageCredentialsForPublish(storageConfig);
|
|
8848
8867
|
return stripUndefinedValues({
|
|
8849
8868
|
code,
|
|
@@ -8863,11 +8882,38 @@ function normalizeStorageConfigManifest(storageConfig) {
|
|
|
8863
8882
|
maxFileSizeMb: configJson.maxFileSizeMb,
|
|
8864
8883
|
allowedExtensions: configJson.allowedExtensions,
|
|
8865
8884
|
uploadUrlExpiresSeconds: configJson.uploadUrlExpiresSeconds,
|
|
8885
|
+
cors,
|
|
8866
8886
|
}),
|
|
8867
8887
|
...(credentials ? { credentials } : {}),
|
|
8868
8888
|
});
|
|
8869
8889
|
}
|
|
8870
8890
|
|
|
8891
|
+
function normalizeStorageCorsForPublish(cors) {
|
|
8892
|
+
if (cors === undefined || cors === null || cors === '') return undefined;
|
|
8893
|
+
const managed =
|
|
8894
|
+
cors === true ||
|
|
8895
|
+
cors.managed === true ||
|
|
8896
|
+
String(cors.managed || '').toLowerCase() === 'true';
|
|
8897
|
+
if (!managed) return { managed: false };
|
|
8898
|
+
return stripUndefinedValues({
|
|
8899
|
+
managed: true,
|
|
8900
|
+
allowedOrigins: normalizeManifestStringArray(
|
|
8901
|
+
cors.allowedOrigins || cors.allowedOrigin || cors.origins || cors.origin
|
|
8902
|
+
),
|
|
8903
|
+
allowedMethods: normalizeManifestStringArray(
|
|
8904
|
+
cors.allowedMethods || cors.allowedMethod || ['PUT', 'GET', 'HEAD'],
|
|
8905
|
+
{ uppercase: true }
|
|
8906
|
+
),
|
|
8907
|
+
allowedHeaders: normalizeManifestStringArray(
|
|
8908
|
+
cors.allowedHeaders || cors.allowedHeader || ['content-type', 'x-oss-*']
|
|
8909
|
+
),
|
|
8910
|
+
exposeHeaders: normalizeManifestStringArray(
|
|
8911
|
+
cors.exposeHeaders || cors.exposeHeader || ['ETag', 'x-oss-request-id']
|
|
8912
|
+
),
|
|
8913
|
+
maxAgeSeconds: Number(cors.maxAgeSeconds || 600),
|
|
8914
|
+
});
|
|
8915
|
+
}
|
|
8916
|
+
|
|
8871
8917
|
function withoutStorageConfigManifestMeta(storageConfig) {
|
|
8872
8918
|
const next = withoutResourceMeta(storageConfig);
|
|
8873
8919
|
delete next.code;
|
|
@@ -8876,6 +8922,7 @@ function withoutStorageConfigManifestMeta(storageConfig) {
|
|
|
8876
8922
|
delete next.description;
|
|
8877
8923
|
delete next.provider;
|
|
8878
8924
|
delete next.status;
|
|
8925
|
+
delete next.cors;
|
|
8879
8926
|
delete next.config;
|
|
8880
8927
|
delete next.configJson;
|
|
8881
8928
|
delete next.credentials;
|
|
@@ -9214,6 +9261,16 @@ function normalizePermissionCodeArray(value) {
|
|
|
9214
9261
|
);
|
|
9215
9262
|
}
|
|
9216
9263
|
|
|
9264
|
+
function normalizeManifestStringArray(value, options = {}) {
|
|
9265
|
+
const source = Array.isArray(value) ? value : value === undefined || value === null ? [] : [value];
|
|
9266
|
+
const items = source
|
|
9267
|
+
.flatMap(item => String(item || '').split(','))
|
|
9268
|
+
.map(item => item.trim())
|
|
9269
|
+
.filter(Boolean)
|
|
9270
|
+
.map(item => options.uppercase ? item.toUpperCase() : item);
|
|
9271
|
+
return unique(items);
|
|
9272
|
+
}
|
|
9273
|
+
|
|
9217
9274
|
function saveResourceEntry(target, bucket, code, extra = {}) {
|
|
9218
9275
|
saveStateResource(target, bucket, code, pickStateFields(extra, ['formUuid']), [
|
|
9219
9276
|
'formUuid',
|
|
@@ -81,6 +81,8 @@ import {
|
|
|
81
81
|
<AttachmentField name="ossFiles" label="OSS 附件" uploadProvider="oss" storageCode="evaluate_oss" />
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
OSS 浏览器直传所需的 CORS 规则在 `src/resources/storage/<code>.json` 的 `configJson.cors.managed` 中声明,不要把 bucket 配置写进组件 props。
|
|
85
|
+
|
|
84
86
|
> ✅ 一句话原则:**这些场景看到了,直接抄上表,不要自己写。**
|
|
85
87
|
|
|
86
88
|
---
|
|
@@ -75,7 +75,7 @@ Rules:
|
|
|
75
75
|
- For `CascadeDateField`, store a date range and document whether the value is inclusive.
|
|
76
76
|
- For `UserSelectField` and `DepartmentSelectField`, do not invent user or department IDs. Use runtime/platform selection.
|
|
77
77
|
- For `AttachmentField` and `ImageField`, do not hardcode OSS URLs in defaults unless the file has already been uploaded by platform tooling.
|
|
78
|
-
- For custom OSS attachment upload, first declare a `src/resources/storage/<code>.json` resource, then set `uploadProvider: "oss"` and `storageCode: "<code>"` on `AttachmentField`. Never put OSS AK/SK values in schema, page code, or committed resource files.
|
|
78
|
+
- For custom OSS attachment upload, first declare a `src/resources/storage/<code>.json` resource, optionally enable `configJson.cors.managed` for browser direct-upload CORS, then set `uploadProvider: "oss"` and `storageCode: "<code>"` on `AttachmentField`. Never put OSS AK/SK values in schema, page code, or committed resource files.
|
|
79
79
|
- For `SubFormField`, use `columns` for child fields and keep child `fieldId` values stable within the subform.
|
|
80
80
|
- `TextAreaField` is accepted by workspace tools and normalized to runtime `TextareaField`; prefer `TextAreaField` in OpenXiangda docs for readability.
|
|
81
81
|
|
|
@@ -643,3 +643,71 @@ Requires Bearer token. Returns runtime bootstrap information for an existing cod
|
|
|
643
643
|
### GET `/apps/:appType/snapshot`
|
|
644
644
|
|
|
645
645
|
Requires Bearer token. Returns app metadata, forms, menus, code page definitions, code page releases, and permission hints.
|
|
646
|
+
|
|
647
|
+
## Storage Configs
|
|
648
|
+
|
|
649
|
+
Storage config APIs manage application-level custom upload targets. The first provider is Aliyun OSS.
|
|
650
|
+
|
|
651
|
+
### GET `/apps/:appType/storage-configs`
|
|
652
|
+
|
|
653
|
+
Requires Bearer token and storage-config manage permission. Lists storage configs.
|
|
654
|
+
|
|
655
|
+
### POST `/apps/:appType/storage-configs`
|
|
656
|
+
|
|
657
|
+
Requires Bearer token and storage-config manage permission. Creates or upserts a storage config.
|
|
658
|
+
|
|
659
|
+
Body:
|
|
660
|
+
|
|
661
|
+
```json
|
|
662
|
+
{
|
|
663
|
+
"code": "evaluate_oss",
|
|
664
|
+
"name": "Evaluate OSS",
|
|
665
|
+
"provider": "oss",
|
|
666
|
+
"status": "active",
|
|
667
|
+
"configJson": {
|
|
668
|
+
"region": "oss-cn-hangzhou",
|
|
669
|
+
"bucket": "evaluate-oss",
|
|
670
|
+
"publicBaseUrl": "https://evaluate-oss.oss-cn-hangzhou.aliyuncs.com",
|
|
671
|
+
"pathPrefix": "openxiangda/{{appType}}/{{yyyy}}/{{MM}}/{{dd}}",
|
|
672
|
+
"allowedExtensions": ["txt", "pdf", "png"],
|
|
673
|
+
"cors": {
|
|
674
|
+
"managed": true,
|
|
675
|
+
"allowedOrigins": ["https://yida.wisejob.cn"],
|
|
676
|
+
"allowedMethods": ["PUT", "GET", "HEAD"],
|
|
677
|
+
"allowedHeaders": ["content-type", "x-oss-*"],
|
|
678
|
+
"exposeHeaders": ["ETag", "x-oss-request-id"],
|
|
679
|
+
"maxAgeSeconds": 600
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
"credentials": {
|
|
683
|
+
"accessKeyId": "resolved at publish time",
|
|
684
|
+
"accessKeySecret": "resolved at publish time"
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
When `configJson.cors.managed` is true, the backend attempts to merge the required OSS bucket CORS rule after saving the storage config. The response may include `corsStatus`; `applied: false` means the storage config was saved but the OSS credentials could not manage bucket CORS.
|
|
690
|
+
|
|
691
|
+
### GET `/apps/:appType/storage-configs/:code`
|
|
692
|
+
|
|
693
|
+
Requires Bearer token and storage-config manage permission. Returns the storage config with credential status only, never plaintext AK/SK.
|
|
694
|
+
|
|
695
|
+
### PUT `/apps/:appType/storage-configs/:code`
|
|
696
|
+
|
|
697
|
+
Requires Bearer token and storage-config manage permission. Updates the storage config. Omit `credentials` to keep the existing encrypted credentials.
|
|
698
|
+
|
|
699
|
+
### DELETE `/apps/:appType/storage-configs/:code`
|
|
700
|
+
|
|
701
|
+
Requires Bearer token and storage-config manage permission. Deletes the storage config metadata; it does not delete existing OSS objects.
|
|
702
|
+
|
|
703
|
+
### POST `/apps/:appType/storage-configs/:code/cors/apply`
|
|
704
|
+
|
|
705
|
+
Requires Bearer token and storage-config manage permission. Re-applies the declared managed CORS rule for an existing config.
|
|
706
|
+
|
|
707
|
+
### POST `/apps/:appType/storage-configs/:code/uploads/initiate`
|
|
708
|
+
|
|
709
|
+
Requires Bearer token. Creates a short-lived signed OSS upload URL for browser direct upload.
|
|
710
|
+
|
|
711
|
+
### POST `/apps/:appType/storage-configs/:code/objects/delete`
|
|
712
|
+
|
|
713
|
+
Requires Bearer token. Deletes an OSS object under the configured `pathPrefix`.
|
|
@@ -155,7 +155,15 @@ await auth.phoneCodeLogin({ phone, code, challengeId: sent.challengeId });
|
|
|
155
155
|
"publicBaseUrl": "https://evaluate-oss.oss-cn-hangzhou.aliyuncs.com",
|
|
156
156
|
"pathPrefix": "openxiangda/{{appType}}/{{yyyy}}/{{MM}}/{{dd}}",
|
|
157
157
|
"maxFileSizeMb": 20,
|
|
158
|
-
"allowedExtensions": ["txt", "pdf", "png"]
|
|
158
|
+
"allowedExtensions": ["txt", "pdf", "png"],
|
|
159
|
+
"cors": {
|
|
160
|
+
"managed": true,
|
|
161
|
+
"allowedOrigins": ["https://yida.wisejob.cn"],
|
|
162
|
+
"allowedMethods": ["PUT", "GET", "HEAD"],
|
|
163
|
+
"allowedHeaders": ["content-type", "x-oss-*"],
|
|
164
|
+
"exposeHeaders": ["ETag", "x-oss-request-id"],
|
|
165
|
+
"maxAgeSeconds": 600
|
|
166
|
+
}
|
|
159
167
|
},
|
|
160
168
|
"credentials": {
|
|
161
169
|
"accessKeyId": "${APP_OSS_ACCESS_KEY_ID}",
|
|
@@ -177,6 +185,8 @@ await auth.phoneCodeLogin({ phone, code, challengeId: sent.challengeId });
|
|
|
177
185
|
|
|
178
186
|
默认附件不配置 `uploadProvider`/`storageCode` 时仍走平台上传。OSS 附件保存真实 OSS URL;预览、下载、删除对象都通过 storage 配置对应的运行时接口处理。
|
|
179
187
|
|
|
188
|
+
`configJson.cors.managed: true` 会在发布 storage 配置时顺便设置 OSS bucket CORS。后端会读取现有规则并合并,不覆盖其他系统规则;该操作需要 OSS 凭据具备 bucket CORS 管理权限。未声明 `cors.managed` 时不会修改 bucket 级配置。
|
|
189
|
+
|
|
180
190
|
## 1. Public Route — `src/resources/routes/<code>.json`
|
|
181
191
|
|
|
182
192
|
新 React SPA 公开页使用 `/view/:appType/public/*`,不要再使用旧 `?publicAccess=guest`。
|