From f2136bd8ae3100c5d44383f65481cfd0763fcbd6 Mon Sep 17 00:00:00 2001 From: camilesing Date: Fri, 18 Sep 2026 23:29:02 +0800 Subject: [PATCH 1/3] fix(datatask): tolerate string wire formats for array parameters Array-declared parameters (isArray=true) now accept three wire shapes besides a native JSON array: a serialized JSON array string (["a","b"]), a comma-separated string (a,b) and a single scalar, which becomes a one-element list. The same tolerance applies to arrays declared on OBJECT children. Elements are still coerced one by one to the declared type, so the loosening does not weaken value validation; a JSON-looking string that fails to parse falls back to comma splitting. Adds binder tests for the new formats (including elements with embedded commas and type coercion) and documents the accepted submission forms in docs/{en,zh}/data-task.md. --- .../cs/core/datatask/DataTaskParamBinder.java | 63 ++++++++++--- .../datatask/DataTaskParamBinderTest.java | 90 +++++++++++++++++++ docs/en/data-task.md | 3 + docs/zh/data-task.md | 2 + 4 files changed, 145 insertions(+), 13 deletions(-) diff --git a/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskParamBinder.java b/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskParamBinder.java index 83bc4b4..184084d 100644 --- a/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskParamBinder.java +++ b/datapoly-core/src/main/java/com/cs/core/datatask/DataTaskParamBinder.java @@ -5,6 +5,7 @@ import com.cs.common.dto.ItemParam; import com.cs.common.exception.CommonException; import com.cs.common.exception.ResponseErrorCode; +import com.cs.persistence.util.JsonUtils; import org.apache.commons.lang3.StringUtils; import java.util.*; @@ -46,19 +47,60 @@ private static Object bindScalarRoot(ItemParam decl, Map body, b if (!isArray) { return coerceOrNull(decl, raw, decl.getName(), Boolean.TRUE.equals(decl.getRequired())); } - List values = raw instanceof List ? (List) raw : null; + List values = arrayElements(decl, raw, decl.getName(), true); if (null == values || values.isEmpty()) { return requireOrNothing(decl, raw, decl.getName(), () -> defaultList(decl)); } - List out = new ArrayList<>(values.size()); - for (int i = 0; i < values.size(); i++) { - out.add(coerce(decl.getType(), values.get(i), - String.format("%s[%d]", decl.getName(), i), true)); + return values; + } + + /** + * Element list of an array-declared parameter. Besides a native JSON array, two + * string wire formats that frontends commonly emit are tolerated: a pre-serialized + * JSON array literal (["a","b"]) and a comma-separated list ("a,b"). Every element + * is still coerced to the declared type, so shape loosening does not weaken value + * validation; a String that carries nothing usable yields null for the caller. + */ + private static List arrayElements(BaseParam decl, Object raw, String path, boolean enforce) { + List rawElements; + if (raw instanceof List) { + rawElements = (List) raw; + } else if (raw instanceof String && StringUtils.isNotBlank((String) raw)) { + rawElements = parseArrayString(StringUtils.trim((String) raw)); + } else { + return null; + } + List out = new ArrayList<>(rawElements.size()); + for (int i = 0; i < rawElements.size(); i++) { + out.add(coerce(decl.getType(), rawElements.get(i), + String.format("%s[%d]", path, i), enforce)); } return out; } + private static List parseArrayString(String value) { + if (value.startsWith("[") && value.endsWith("]")) { + try { + return JsonUtils.toBeanList(value, Object.class); + } catch (RuntimeException ignore) { + // not a JSON array after all; fall back to comma splitting + } + } + String[] parts = value.split(","); + List values = new ArrayList<>(parts.length); + for (String part : parts) { + String item = part.trim(); + if (item.length() >= 2 && item.startsWith("\"") && item.endsWith("\"")) { + item = item.substring(1, item.length() - 1); + } + if (!item.isEmpty()) { + values.add(item); + } + } + return values; + } + /** * OBJECT parameters accept either a nested map under the parameter name or flat * {@code name.child} keys on the request body; the nested form wins on conflicts. @@ -92,19 +134,14 @@ private static Object bindObjectChildren(ItemParam decl, Map bod for (BaseParam child : children) { String path = root + "." + child.getName(); if (Boolean.TRUE.equals(child.getIsArray())) { - List raws = container.get(child.getName()) instanceof List - ? (List) container.get(child.getName()) : null; - if (null == raws || raws.isEmpty()) { + List items = arrayElements(child, container.get(child.getName()), path, + Boolean.TRUE.equals(child.getRequired())); + if (null == items || items.isEmpty()) { if (Boolean.TRUE.equals(child.getRequired())) { throw missing(path); } continue; } - List items = new ArrayList<>(raws.size()); - for (int i = 0; i < raws.size(); i++) { - items.add(coerce(child.getType(), raws.get(i), String.format("%s[%d]", path, i), - Boolean.TRUE.equals(child.getRequired()))); - } out.put(child.getName(), items); } else { Object value = coerceOrNull(child, container.get(child.getName()), path, diff --git a/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskParamBinderTest.java b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskParamBinderTest.java index 0d4f1ba..b680523 100644 --- a/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskParamBinderTest.java +++ b/datapoly-test/src/test/java/com/cs/core/datatask/DataTaskParamBinderTest.java @@ -119,6 +119,96 @@ public void scalarArraysConvertElementwise() { Assert.assertEquals(Arrays.asList(1L, 2L), bound.get("ids")); } + @Test + public void arrayDeclaredParamAcceptsJsonArrayString() { + Map body = new HashMap<>(); + body.put("names", "[\"燕文\",\"顺友\"]"); + + Map bound = DataTaskParamBinder.bind( + Collections.singletonList(simple("names", ParamTypeEnum.STRING, true, false, null)), body); + Assert.assertEquals(Arrays.asList("燕文", "顺友"), bound.get("names")); + } + + @Test + public void rawWireJsonWithStringifiedArrayBindsToList() { + String wire = "{\"params\":{\"logisticsProviderNameList\":\"[\\\"燕文\\\",\\\"顺友\\\"]\"}}"; + Map body = com.cs.persistence.util.JsonUtils.toBeanType(wire, + new com.fasterxml.jackson.core.type.TypeReference>() { + }); + + @SuppressWarnings("unchecked") + Map params = (Map) body.get("params"); + Map bound = DataTaskParamBinder.bind( + Collections.singletonList(simple("logisticsProviderNameList", ParamTypeEnum.STRING, true, false, null)), + params); + Assert.assertEquals(Arrays.asList("燕文", "顺友"), bound.get("logisticsProviderNameList")); + } + + @Test + public void arrayDeclaredParamAcceptsCommaSeparatedString() { + Map body = new HashMap<>(); + body.put("names", " 燕文 , 顺友 "); + + Map bound = DataTaskParamBinder.bind( + Collections.singletonList(simple("names", ParamTypeEnum.STRING, true, false, null)), body); + Assert.assertEquals(Arrays.asList("燕文", "顺友"), bound.get("names")); + } + + @Test + public void arrayDeclaredParamAcceptsSingleScalarValue() { + Map body = new HashMap<>(); + body.put("names", "燕文"); + + Map bound = DataTaskParamBinder.bind( + Collections.singletonList(simple("names", ParamTypeEnum.STRING, true, false, null)), body); + Assert.assertEquals(Collections.singletonList("燕文"), bound.get("names")); + } + + @Test + public void jsonArrayStringElementsKeepEmbeddedCommas() { + Map body = new HashMap<>(); + body.put("names", "[\"a,b\",\"c\"]"); + + Map bound = DataTaskParamBinder.bind( + Collections.singletonList(simple("names", ParamTypeEnum.STRING, true, false, null)), body); + Assert.assertEquals(Arrays.asList("a,b", "c"), bound.get("names")); + } + + @Test + public void arrayStringElementsCoerceToDeclaredType() { + Map body = new HashMap<>(); + body.put("ids", "[1,2,3]"); + + Map bound = DataTaskParamBinder.bind( + Collections.singletonList(simple("ids", ParamTypeEnum.LONG, true, false, null)), body); + Assert.assertEquals(Arrays.asList(1L, 2L, 3L), bound.get("ids")); + } + + @Test + public void objectChildArrayAcceptsJsonArrayString() { + ItemParam decl = new ItemParam(); + decl.setName("obj"); + decl.setType(ParamTypeEnum.OBJECT); + decl.setIsArray(false); + decl.setRequired(false); + BaseParam child = new BaseParam(); + child.setName("tags"); + child.setType(ParamTypeEnum.STRING); + child.setIsArray(true); + child.setRequired(false); + decl.setChildren(new ArrayList<>(Collections.singletonList(child))); + + Map body = new HashMap<>(); + Map inner = new HashMap<>(); + inner.put("tags", "[\"a\",\"b\"]"); + body.put("obj", inner); + + Map bound = DataTaskParamBinder.bind(Collections.singletonList(decl), body); + @SuppressWarnings("unchecked") + Map obj = (Map) bound.get("obj"); + Assert.assertEquals(Arrays.asList("a", "b"), obj.get("tags")); + } + @Test public void objectParamsAcceptNestedAndDottedForms() { ItemParam decl = simple("obj", ParamTypeEnum.OBJECT, false, false, null); diff --git a/docs/en/data-task.md b/docs/en/data-task.md index ae4fab6..bb702cc 100644 --- a/docs/en/data-task.md +++ b/docs/en/data-task.md @@ -131,6 +131,9 @@ Notes: `type` is one of `LONG/DOUBLE/STRING/DATE/TIME/BOOLEAN/OBJECT` (OBJECT requires `children`; submit accepts nested maps or flat `parent.sub` keys). Submission enforces required checks and type conversion; undeclared extra parameters are ignored. +- Array parameters (`isArray=true`) accept a JSON array, a pre-serialized JSON array string (e.g. + `"[\"YANWEN\",\"SHUNYOU\"]"`), or a comma-separated string (e.g. `"YANWEN,SHUNYOU"`); elements are still + converted one by one to the declared type. - Reshaping order is **naming strategy → alias → column order**: `columnAlias` keys match column names **after** the naming strategy is applied; `columnOrder` fixes the output order and subset — unlisted columns are dropped. - In `formatMap`, date/time types take a pattern string and `BIG_DECIMAL` takes a scale (HALF_UP, default 6); with diff --git a/docs/zh/data-task.md b/docs/zh/data-task.md index 0ead322..a4b178c 100644 --- a/docs/zh/data-task.md +++ b/docs/zh/data-task.md @@ -121,6 +121,8 @@ curl -s -X POST $BASE/create -H "$AUTH" -H 'Content-Type: application/json' -d ' - 入参声明字段与 API 配置一致:`name/type/location/isArray/required/defaultValue/remark`,`type` 取 `LONG/DOUBLE/STRING/DATE/TIME/BOOLEAN/OBJECT`(OBJECT 需声明 `children`,支持嵌套 Map 或 `parent.sub` 扁平键提交);提交时按声明做必填校验与类型转换,未声明的多余入参被忽略。 +- 数组入参(`isArray=true`)接受 JSON 数组、序列化后的 JSON 数组字符串(如 `"[\"燕文\",\"顺友\"]"`)以及逗号 + 分隔字符串(如 `"燕文,顺友"`)三种提交形式,元素仍逐项按声明类型转换。 - 整形顺序为**命名策略 → 别名 → 列顺序**:`columnAlias` 的 key 匹配命名策略转换**之后**的列名; `columnOrder` 给出输出列的顺序与子集,未列出的列被丢弃。 - `formatMap` 中日期/时间类型给格式串,`BIG_DECIMAL` 给小数位数(HALF_UP,默认 6); From 8b44ee70469c16d1b71710df38c88337cecc2da2 Mon Sep 17 00:00:00 2001 From: camilesing Date: Fri, 18 Sep 2026 23:29:16 +0800 Subject: [PATCH 2/3] feat(login): add a user role column, a login-page extension hook and Feishu config injection Squashed port of two commits (user role foundation + Feishu login config plumbing). Backend: DATAPOLY_SYSTEM_USER gains a user_role column through the v1.4.0 Liquibase migration in both dialects (new rows default to USER, existing rows are backfilled to ADMIN so nobody locks themselves out on upgrade). AccessToken carries the role, SystemUserService.login fills it from the row, SystemUserEntity maps user_role, and SystemUserDao.insert() lets account providers such as the Feishu extension create their users. Login page: src/views/login/index.vue renders an extension.loginExtras component list below the password button (the fifth compile-time extension hook) and stores the role in sessionStorage; the in-repo stub contributes an empty array, so a plain build renders nothing new. Deployment: the Feishu OAuth settings (DATAPOLY_FEISHU_ENABLED / APP_ID / APP_SECRET / REDIRECT_URI) are wired through docker-compose (values come from the ignored install/.env), conf/config.ini and datapolyctl.sh - a key is exported only when it has a value, since an empty string breaks relaxed binding of the boolean switch. The Feishu OAuth backend and the login button live in the separate datapoly-extension repository. --- AGENTS.md | 5 ++-- build-docker/install/docker-compose.yml | 6 +++++ .../java/com/cs/common/dto/AccessToken.java | 3 +++ .../cs/core/service/SystemUserService.java | 2 +- .../src/main/assembly/bin/datapolyctl.sh | 14 +++++++++++ .../src/main/assembly/conf/config.ini | 12 ++++++++- .../src/extension-stub/index.js | 5 +++- datapoly-manager-ui/src/views/login/index.vue | 25 +++++++++++++++++++ .../db/changelog/db.changelog-master.yaml | 2 ++ .../resources/db/changelog/log-v1.4.0.yaml | 9 +++++++ .../db/migration/V1_4_0__user-role-ddl.sql | 8 ++++++ .../pg/changelog/db.changelog-master.yaml | 2 ++ .../resources/pg/changelog/log-v1.4.0.yaml | 9 +++++++ .../pg/migration/V1_4_0__user-role-ddl.sql | 8 ++++++ .../com/cs/persistence/dao/SystemUserDao.java | 9 +++++++ .../persistence/entity/SystemUserEntity.java | 4 +++ .../core/service/SystemUserServiceTest.java | 3 +++ 17 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 datapoly-manager/src/main/resources/db/changelog/log-v1.4.0.yaml create mode 100644 datapoly-manager/src/main/resources/db/migration/V1_4_0__user-role-ddl.sql create mode 100644 datapoly-manager/src/main/resources/pg/changelog/log-v1.4.0.yaml create mode 100644 datapoly-manager/src/main/resources/pg/migration/V1_4_0__user-role-ddl.sql diff --git a/AGENTS.md b/AGENTS.md index 283fa1b..6a3497b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,7 +27,7 @@ Groovy 沙箱默认启用但不是 JVM 隔离:勿把脚本编写权开放给 ## 三、凭据外部化 -真实值一律环境变量注入,禁止写回仓库 yaml:`DATAPOLY_ADMIN_PASSWORD`、`DATAPOLY_REDIS_PASSWORD`、`DATAPOLY_DS_AES_KEY`(轮换前须用旧密钥导出重录)、`DATAPOLY_CORS_ALLOWED_ORIGINS`、compose 的 `MYSQL_ROOT_PASSWORD`/`MYSQL_PASSWORD`(演示默认 123456)。演示凭据 admin/123456、test/test 正式部署必须修改;actuator 已收窄为 health,info。 +真实值一律环境变量注入,禁止写回仓库 yaml:`DATAPOLY_ADMIN_PASSWORD`、`DATAPOLY_REDIS_PASSWORD`、`DATAPOLY_DS_AES_KEY`(轮换前须用旧密钥导出重录)、`DATAPOLY_CORS_ALLOWED_ORIGINS`、compose 的 `MYSQL_ROOT_PASSWORD`/`MYSQL_PASSWORD`(演示默认 123456)。演示凭据 admin/123456、test/test 正式部署必须修改;actuator 已收窄为 health,info。飞书登录凭证 `DATAPOLY_FEISHU_APP_ID`/`DATAPOLY_FEISHU_APP_SECRET` 同样只从环境变量注入:compose 取被忽略的 `install/.env`,发行版 `conf/config.ini` 留空即关闭(`datapolyctl.sh` 只在键有值时导出,空串会让布尔属性宽松绑定失败)。 ## 四、端点防护 @@ -37,6 +37,7 @@ Groovy 沙箱默认启用但不是 JVM 隔离:勿把脚本编写权开放给 - 新增 JDBC 代码资源必须 try-with-resources;firewall 规则行被删时网关按"全拒绝"处理(fail-closed,属预期)。 - DataTask 投递 Sink 仓库内置零实现,外部以 Spring Bean / `META-INF/services` 注册(SPI `com.cs.common.datatask.DataTaskSink`);宿主可自行维护本地扩展:在顶层 `datapoly-extension/`(已被 .gitignore 排除,独立 git 仓库)下用 `backend/` 放 Maven 扩展模块(依赖钉版在模块自身 pom、不进根 reactor,由入库脚本 build-extension.sh 在宿主机 JDK 25 构建后投放 lib-extra/,随发行版装配进各服务 classpath)、`front/` 放扩展 UI;API 扩展点 `ApiAssignmentPostProcessor` 注册方式相同、须同步执行且保持轻量。详见 docs/*/data-task.md。 -- 默认前端扩展目录 `datapoly-extension/front`(同被 .gitignore 排除)经 datapoly-manager-ui 编译期装配:webpack `@extension` 别名自动探测该目录 `src/index.js`(见 build/webpack.base.conf.js)、`src/extension-stub` 为缺省回退、扩展路由与 i18n 词条在 manager-ui 入口深合并——这四处钩子文件(build/webpack.base.conf.js、src/extension-stub、src/router、src/main.js)勿移除或改名;目录不存在时 CI 与普通构建不受影响。front 自带 `package.json` 可直接启动(`npm run dev`,复用宿主 webpack 链,前置为宿主 node_modules 已安装、Node 24——dev server 与生产构建均已实测)。 +- 默认前端扩展目录 `datapoly-extension/front`(同被 .gitignore 排除)经 datapoly-manager-ui 编译期装配:webpack `@extension` 别名自动探测该目录 `src/index.js`(见 build/webpack.base.conf.js)、`src/extension-stub` 为缺省回退、扩展路由与 i18n 词条在 manager-ui 入口深合并、登录页扩展区渲染 `@extension` 的 `loginExtras` 组件数组(`src/views/login/index.vue`,stub 为空数组)——这五处钩子(build/webpack.base.conf.js、src/extension-stub、src/router、src/main.js、src/views/login/index.vue 的 loginExtras 挂载点)勿移除或改名;目录不存在时 CI 与普通构建不受影响。front 自带 `package.json` 可直接启动(`npm run dev`,复用宿主 webpack 链,前置为宿主 node_modules 已安装、Node 24——dev server 与生产构建均已实测)。 +- 用户角色见 `DATAPOLY_SYSTEM_USER.user_role`(`ADMIN`/`USER`,v1.4.0 迁移:新行默认 `USER`、存量行回填 `ADMIN`),登录响应 `AccessToken.role` 带回该值;角色只决定界面可见范围,鉴权仍以 token 为准,按角色的端点拦截须同时校验数据库中的角色而非前端传来的值。 - 宿主扩展 jar 经根目录 `lib-extra/` 投放点进入发行版 `lib/common/`(`package.xml` 打包该目录 `*.jar`;目录只占位入库,jar 永不入库)。扩展为独立 git 仓库(内部 GitLab,front+backend 一体):`build-extension.sh` 按环境变量 `DATAPOLY_EXTENSION_GIT_URL`(真实地址不入库,CI 注入)+ `DATAPOLY_EXTENSION_GIT_REF`(默认 master)浅克隆到 `datapoly-extension/`(仍被 .gitignore 排除),目录已存在则按本地工作区构建(宿主机 JDK 25 优先、低于 25 不可用)、`DATAPOLY_EXTENSION_FORCE_SYNC=1` 强制覆盖本地改动;`build.sh`/`docker-maven-build.sh` 会先调用该脚本,未配置且目录不存在时无操作(纯开源构建零影响)。本地环境变量注入(env.sh)与防误提交钩子集中在被忽略的 `dev-local/`。 - 一次性 token 在校验时即消费(含查库兜底路径;2026-09 修复兜底不消费导致的重放);并发首用竞态下多 executor 仍可能各放行一次(无分布式锁,已知限制)。 diff --git a/build-docker/install/docker-compose.yml b/build-docker/install/docker-compose.yml index af7033f..2275be7 100644 --- a/build-docker/install/docker-compose.yml +++ b/build-docker/install/docker-compose.yml @@ -44,6 +44,12 @@ services: DATAPOLY_DS_ENCRYPT: false # 部署后请务必设置:覆盖种子 admin 口令(S5) DATAPOLY_ADMIN_PASSWORD: ${DATAPOLY_ADMIN_PASSWORD:-} + # 飞书登录(datapoly-extension-feishu,relaxed binding 映射为 datapoly.feishu.*): + # 真实值由 install/.env 注入,勿写回本文件;三项凭证缺一或开关关闭时登录页不显示飞书按钮 + DATAPOLY_FEISHU_ENABLED: ${DATAPOLY_FEISHU_ENABLED:-false} + DATAPOLY_FEISHU_APP_ID: ${DATAPOLY_FEISHU_APP_ID:-} + DATAPOLY_FEISHU_APP_SECRET: ${DATAPOLY_FEISHU_APP_SECRET:-} + DATAPOLY_FEISHU_REDIRECT_URI: ${DATAPOLY_FEISHU_REDIRECT_URI:-} # DATAPOLY_MANAGER_URL: http://www.example.com:8090 # DATAPOLY_GATEWAY_URL: http://www.example.com:8091 depends_on: diff --git a/datapoly-common/src/main/java/com/cs/common/dto/AccessToken.java b/datapoly-common/src/main/java/com/cs/common/dto/AccessToken.java index 5e3031d..da3be45 100644 --- a/datapoly-common/src/main/java/com/cs/common/dto/AccessToken.java +++ b/datapoly-common/src/main/java/com/cs/common/dto/AccessToken.java @@ -30,4 +30,7 @@ public class AccessToken implements Serializable { @Schema(description = "有效期(时间段,单位:秒)") private Long expireSeconds; + + @Schema(description = "角色(ADMIN/USER)") + private String role; } diff --git a/datapoly-core/src/main/java/com/cs/core/service/SystemUserService.java b/datapoly-core/src/main/java/com/cs/core/service/SystemUserService.java index dcbbc7a..5e2e3d3 100644 --- a/datapoly-core/src/main/java/com/cs/core/service/SystemUserService.java +++ b/datapoly-core/src/main/java/com/cs/core/service/SystemUserService.java @@ -46,7 +46,7 @@ public AccessToken login(String username, String password) { String token = TokenUtils.generateValue(); CacheUtils.put(token, user); AccessToken accessTokenWrapper = new AccessToken(user.getRealName(), user.getUsername(), token, - System.currentTimeMillis() / 1000, CacheUtils.CACHE_DURATION_SECONDS); + System.currentTimeMillis() / 1000, CacheUtils.CACHE_DURATION_SECONDS, user.getRole()); return accessTokenWrapper; } diff --git a/datapoly-dist/src/main/assembly/bin/datapolyctl.sh b/datapoly-dist/src/main/assembly/bin/datapolyctl.sh index 938d745..9176db1 100644 --- a/datapoly-dist/src/main/assembly/bin/datapolyctl.sh +++ b/datapoly-dist/src/main/assembly/bin/datapolyctl.sh @@ -55,6 +55,20 @@ export JSON_TIMEZONE=$(get_config_value "JSON_TIMEZONE" "${APP_CONF_PATH}/config export DATAPOLY_MANAGER_URL=$(get_config_value "DATAPOLY_MANAGER_URL" "${APP_CONF_PATH}/config.ini") export DATAPOLY_GATEWAY_URL=$(get_config_value "DATAPOLY_GATEWAY_URL" "${APP_CONF_PATH}/config.ini") +# 飞书登录(datapoly-extension-feishu 扩展模块):config.ini 里留空即关闭。仅在确实 +# 配置了值时才导出——否则会把 enabled 这类布尔属性导出成空串,宽松绑定会因无法转换而启动失败 +export_feishu_config() { + local key=$1 + local value=$(get_config_value "$key" "${APP_CONF_PATH}/config.ini") + if [ -n "$value" ]; then + export "$key=$value" + fi +} +export_feishu_config "DATAPOLY_FEISHU_ENABLED" +export_feishu_config "DATAPOLY_FEISHU_APP_ID" +export_feishu_config "DATAPOLY_FEISHU_APP_SECRET" +export_feishu_config "DATAPOLY_FEISHU_REDIRECT_URI" + # JVM参数可以在这里设置 # 堆 4G、年轻代/老年代 1:3:长驻对象(Hazelcast token/API 响应缓存、Eureka、Spring 框架 # 对象)占堆内大头,老年代空间优先;年轻代 1G 足以容纳数据任务流式批次与 ≤200 行的 diff --git a/datapoly-dist/src/main/assembly/conf/config.ini b/datapoly-dist/src/main/assembly/conf/config.ini index d2a4885..a09f0be 100644 --- a/datapoly-dist/src/main/assembly/conf/config.ini +++ b/datapoly-dist/src/main/assembly/conf/config.ini @@ -45,4 +45,14 @@ DATAPOLY_DS_ENCRYPT=false # 外部配置化网关/管理地址,默认为空 # DATAPOLY_MANAGER_URL=http://www.example.com:8090 -# DATAPOLY_GATEWAY_URL=http://www.example.com:8091 \ No newline at end of file +# DATAPOLY_GATEWAY_URL=http://www.example.com:8091 + +# 飞书登录(datapoly-extension-feishu 扩展模块;未装配该扩展时全部留空即可) +# App ID / App Secret 取自飞书开发者后台「凭证与基础信息」;真实值请只填在部署现场, +# 不要写回仓库 +# DATAPOLY_FEISHU_ENABLED=true +# DATAPOLY_FEISHU_APP_ID= +# DATAPOLY_FEISHU_APP_SECRET= +# 回调地址必须与开发者后台「安全设置 → 重定向 URL」逐字一致,取对外可达的网关地址; +# 未配置或与登记值不符时飞书会直接跳失败页,登录页则不显示飞书按钮 +# DATAPOLY_FEISHU_REDIRECT_URI=https://www.example.com:8091/user/feishu/callback \ No newline at end of file diff --git a/datapoly-manager-ui/src/extension-stub/index.js b/datapoly-manager-ui/src/extension-stub/index.js index 9672539..702e89e 100644 --- a/datapoly-manager-ui/src/extension-stub/index.js +++ b/datapoly-manager-ui/src/extension-stub/index.js @@ -1,8 +1,11 @@ // Use of this source code is governed by a BSD-style license // Compile-time extension fallback: the webpack '@extension' alias resolves here when -// ../../datapoly-extension/front/src is absent, keeping router and i18n assembly no-ops. +// ../../datapoly-extension/front/src is absent, keeping router, i18n and login-page +// assembly no-ops. export default { routes: [], + // Components rendered below the password login button by src/views/login/index.vue. + loginExtras: [], i18n: { 'zh-CN': {}, 'en-US': {} diff --git a/datapoly-manager-ui/src/views/login/index.vue b/datapoly-manager-ui/src/views/login/index.vue index 4da167d..6938a3f 100644 --- a/datapoly-manager-ui/src/views/login/index.vue +++ b/datapoly-manager-ui/src/views/login/index.vue @@ -43,6 +43,16 @@ + + + @@ -51,6 +61,8 @@