大量更新

This commit is contained in:
zeaslity
2026-03-18 16:16:47 +08:00
parent 8efefcc230
commit ed945abdf1
136 changed files with 28252 additions and 16 deletions

View File

@@ -0,0 +1,26 @@
请你参考 @18-基础架构及交付部署特战队\1-项目部署-管理\docs\AirScript-文档.md
或者参考 @18-基础架构及交付部署特战队\1-项目部署-管理\docs\python脚本使用指南.md
只有再非常必要的情况下才使用 [金山多维表格AireScript文档](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-instro)
我有如下的需求需要实现请你参考文档给出合适的解决方案优先使用AirScript其次使用python
我应该如何将现在的表结构通过AirScript脚本完整的导出来
现在有的表名如下:
1. 项目基本信息表
2. 项目本地化部署升级信息表
3. 项目本地化部署状态表
4. 项目部署环境信息表
5. 项目部署网络信息表
6. 项目部署中间件信息表
7. 项目部署业务信息表
我在 项目本地化部署升级信息表 的项目名称字段,应该是选择框,选择的内容说明如下
1. 来源于 项目基本信息表 或者 项目本地化部署状态表中的 项目名称字段
2. 当用户填写了项目基本信息表之后,项目名称字段应该被动态的收集起来
3. 收集起来的信息能够自动构建为选择框中的选项
4. 选择框 还需要具备只显示 填写人自己填写的项目信息的行数据
请你给出方案此种是否只能通过AirScript脚本或者Python才能实现

View File

@@ -0,0 +1,514 @@
# 项目部署管理 — 金山多维表格方案优化分析与完善文档
> **文档版本**: v1.0
> **日期**: 2026-03-06
> **目的**: 基于初始需求,对现有金山多维表格方案进行 **需求覆盖分析 → 缺口识别 → 优化建议 → 完善方案输出**
---
## 一、初始需求回顾与拆解
根据 [项目部署管理.md](项目部署管理.md) 的原始需求,可以拆解为以下 **6 大核心诉求**
| # | 核心诉求 | 关键细节 |
|---|---------|---------|
| R1 | **项目部署信息表**(行业组 / 交付人员填写) | 必须包含部署必要条件等信息 |
| R2 | **项目部署台账** / 甘特图视图 | 日历/列表双视图,支持可视化部署排期 |
| R3 | **部署排期表功能** | 行业组能一目了然查看项目状态、部署进度 |
| R4 | **部署基本信息记录** | 项目名称、版本、时间、状态、人员、结果 |
| R5 | **工具要求** | 富文本、附件上传、客户端简洁免安装 |
| R6 | **远期目标** | 最终在 RMDC 系统中实现 |
---
## 二、现有方案覆盖分析
### 2.1 覆盖矩阵
| 需求编号 | 需求摘要 | 现有方案覆盖情况 | 覆盖评级 |
|---------|---------|---------------|---------|
| R1 | 部署信息表(行业组填写) | ✅ 项目基本信息表 + 升级信息表 | ⭐⭐⭐⭐ 基本覆盖 |
| R2 | 甘特图/日历视图 | ⚠️ 方案中未明确提及视图配置 | ⭐⭐ 未覆盖 |
| R3 | 部署排期进度可视化 | ⚠️ 部署状态表有状态字段,但缺少排期/进度可视化设计 | ⭐⭐ 部分覆盖 |
| R4 | 部署基本信息记录 | ✅ 部署状态表包含核心字段 | ⭐⭐⭐⭐ 基本覆盖 |
| R5 | 富文本/附件/免安装 | ✅ 金山多维表格天然支持 | ⭐⭐⭐⭐⭐ 完全覆盖 |
| R6 | 远期 RMDC 集成 | ⚠️ 未涉及数据迁移/API 对接规划 | ⭐ 未覆盖 |
### 2.2 覆盖得分: **约 60%**
> [!WARNING]
> 现有方案在 **数据建模层面** 做得比较扎实(表结构、关联关系设计合理),但在 **视图层面、流程自动化、权限管控、用户体验** 方面存在明显缺口。
---
## 三、逐项差距分析与优化建议
### 3.1 🔴 甘特图 / 日历视图R2 — 未覆盖)
**问题**: 初始需求明确要求 "甘特图类似" 的部署台账和日历展示,但现有方案仅定义了表结构,未设计任何视图。
**优化建议**:
1. **在「项目本地化部署状态表」上创建以下视图**
| 视图名称 | 视图类型 | 用途 | 关键配置 |
|---------|---------|------|---------|
| 部署排期甘特图 | 甘特视图 | 直观展示每个部署任务的时间跨度 | 开始时间=部署开始时间,结束时间=部署结束时间,分组=部署状态 |
| 部署日历 | 日历视图 | 按日查看部署安排 | 日期字段=部署开始时间,颜色=按部署状态区分 |
| 全部部署列表 | 表格视图 | 默认列表,全量信息查看 | 默认按创建时间倒序 |
| 进行中部署 | 表格视图(筛选) | 仅展示"部署中"的任务 | 筛选: 部署状态=部署中 |
| 我的部署任务 | 表格视图(筛选) | 部署人员查看自己的任务 | 筛选: 部署人=当前用户 |
2. **甘特图配置要点**
- 横轴: 时间线(按周/按月切换)
- 纵轴: 每个部署任务为一行
- 颜色编码: 排期中=灰色, 部署中=蓝色, 部署完成=绿色, 部署异常=红色
- 支持拖拽调整部署时间段
---
### 3.2 🔴 部署排期与进度可视化R3 — 部分覆盖)
**问题**: 行业组需要"一目了然"查看部署进度,但现有方案缺少:
- 进度百分比的概念
- 部署阶段的细化
- 可视化仪表盘
**优化建议**:
1. **细化部署状态枚举**(从 3 个扩展为 6 个):
```
原方案: 排期中 → 部署中 → 部署完成
优化后: 待排期 → 已排期 → 环境准备中 → 部署中 → 验证中 → 已完成
```
补充异常状态:**部署异常** / **已暂停** / **已取消**
2. **新增「部署进度」公式字段**
- 类型: 公式字段
- 逻辑: 根据关联子表的填写完成度自动计算百分比
- 示例公式:
```
进度 = (环境信息已填 × 25% + 网络信息已填 × 25% + 中间件信息已填 × 25% + 业务信息已填 × 25%)
```
3. **新增仪表盘视图**(金山多维表格支持):
- 部署状态分布饼图
- 本月部署任务数柱状图
- 平均部署耗时趋势线
- 各状态任务数统计卡片
---
### 3.3 🟡 项目基本信息表优化R1 — 需完善)
**现有问题**:
1. 缺少「部署必要条件」相关字段(初始需求明确要求)
2. 省份/城市二级联动在多维表格中难以实现级联
3. 缺少项目紧急程度/优先级字段(影响排期)
**优化建议**:
在「项目基本信息表」中 **新增以下字段**
| 字段名 | 字段类型 | 说明 |
|-------|---------|------|
| 部署优先级 | 单选 | P0-紧急 / P1-高 / P2-中 / P3-低 |
| 期望部署日期 | 日期 | 行业组期望的上线时间 |
| 部署必要条件说明 | 多行文本(富文本) | 描述部署前置条件VPN 准备、服务器交付、License 等 |
| 必要条件是否满足 | 单选 | 是 / 否 / 部分满足 |
| 条件不满足说明 | 多行文本 | 记录哪些条件未满足 |
| 项目类型 | 单选 | 新部署 / 升级 / 迁移 / 扩容 |
| 省份 | 单选 | 保持下拉选择 |
| 城市 | 文本 | 改为文本输入(多维表格暂不支持级联选择,备注关联省份) |
| 特殊要求备注 | 多行文本(富文本) | 任何非标准化的部署需求 |
---
### 3.4 🟡 项目本地化部署升级信息表优化
**现有问题**:
1. 与基本信息表字段重复度高,且缺少升级场景的特有字段
2. 缺乏升级版本号的变更记录(从什么版本升级到什么版本)
**优化建议**:
| 字段名 | 字段类型 | 说明 |
|-------|---------|------|
| 项目名称 | 关联字段 | 关联「项目基本信息表」而非文本匹配 |
| 升级类型 | 单选 | 全量升级 / 增量升级 / 热修复 / 回滚 |
| 当前版本(飞服) | 文本 | 升级前的版本号 |
| 目标版本(飞服) | 文本 | 升级后的版本号 |
| 当前版本(监管) | 文本 | 升级前的版本号 |
| 目标版本(监管) | 文本 | 升级后的版本号 |
| 升级影响评估 | 多行文本(富文本) | 本次升级是否需要停机、预计影响范围 |
| 回滚方案 | 多行文本(富文本) | 升级失败时的回滚策略 |
| 期望升级时间窗口 | 日期范围 | 允许升级的时间窗口 |
---
### 3.5 🟡 项目本地化部署状态表优化(核心锚表)
**现有问题**:
1. 状态粒度过粗(仅 3 个状态)
2. 缺少部署结果字段(成功/失败)
3. 「部署时长」应为自动计算而非手动填写
4. 缺少部署关联的来源类型标识(是首次部署还是升级)
**优化建议**:
| 字段名 | 字段类型 | 说明 |
|-------|---------|------|
| 部署编号 | 自动编号 | 唯一标识,格式如 `DEPLOY-2026-0001` |
| 来源类型 | 单选 | 首次部署 / 升级部署 |
| 关联项目 | 关联字段 | → 项目基本信息表 |
| 关联升级记录 | 关联字段 | → 项目本地化部署升级信息表(当来源类型=升级时) |
| 部署状态 | 单选 | 待排期 / 已排期 / 环境准备中 / 部署中 / 验证中 / 已完成 / 部署异常 / 已暂停 / 已取消 |
| **部署结果** | 单选 | 成功 / 失败 / 部分成功 |
| 部署优先级 | 引用字段 | 从项目基本信息表引用 |
| 计划开始时间 | 日期时间 | 排期时确定 |
| 计划结束时间 | 日期时间 | 排期时确定 |
| 实际开始时间 | 日期时间 | 开始部署时记录 |
| 实际结束时间 | 日期时间 | 部署完成时记录 |
| **部署时长(小时)** | 公式 | `= 实际结束时间 - 实际开始时间`(自动计算) |
| 部署进度 | 公式 | 基于关联子表填写状态自动计算 |
| 命名空间 | 文本(唯一) | K8s Namespace |
| 部署人 | 成员 | 使用成员字段而非文本 |
| 部署人电话 | 引用字段 | 从成员信息自动引用 |
| 备注 | 多行文本(富文本) | |
---
### 3.6 🟡 项目部署环境信息表优化
**现有问题**:
1. 关联方式写了 `todo`,未明确
2. SSH 密码标记为"不允许填写",但未说明密码如何管理
3. 缺少主机状态字段
**优化建议**:
| 改动 | 说明 |
|------|------|
| 关联方式 | 通过「所属部署」关联字段 → 部署状态表(已在方案中提及,需明确为关联字段类型) |
| 新增「主机状态」 | 单选: 待交付 / 已交付 / 已初始化 / 运行中 / 异常 |
| 新增「操作系统」 | 单选: CentOS 7 / CentOS 8 / Ubuntu 20.04 / Ubuntu 22.04 / 其他 |
| 新增「GPU信息」 | 文本(如有 GPU 需求的项目) |
| 敏感信息处理 | SSH 密码字段 → 建议改为「密码存储方式」单选 (密码管理器/加密文档/其他),实际密码不存入多维表格 |
| 新增「主机标签」 | 多选: 可标记主机用途组合 |
---
### 3.7 🟡 项目部署中间件信息表优化
**现有问题**:
1. "每行一个中间件实例,固定 7 个" — 这意味着每次部署需手动创建 7 行,效率低
2. 缺少中间件版本信息
3. 缺少中间件健康状态
**优化建议**:
| 改动 | 说明 |
|------|------|
| 新增「中间件版本」 | 文本,如 MySQL 8.0.32, Redis 7.0 等 |
| 新增「部署方式」 | 单选: 容器化 / 主机部署 / 云服务 |
| 新增「所在主机」 | 关联字段 → 项目部署环境信息表(明确中间件运行在哪台主机上) |
| 新增「管理后台地址」 | URL 类型(如 RabbitMQ/NACOS/K8S Dashboard 的管理界面) |
| 自动化创建 | 建议通过多维表格的「表单模板」或「自动化」功能,在新建部署时自动创建 7 条中间件记录 |
---
### 3.8 🔴 权限与角色设计(未覆盖)
**问题**: 原始需求中有明确的角色分工(行业组填写 vs 特战队填写),但现有方案未设计权限体系。
**优化建议 — 权限矩阵**:
| 表名 | 行业组人员 | 交付部署特战队 | 小组长 |
|------|----------|-------------|-------|
| 项目基本信息表 | ✏️ 可编辑 | 👁️ 只读 | ✏️ 可编辑 |
| 部署升级信息表 | ✏️ 可编辑 | 👁️ 只读 | ✏️ 可编辑 |
| 部署状态表 | 👁️ 只读 | ✏️ 可编辑 | ✏️ 可编辑 |
| 部署环境信息表 | 🚫 不可见 | ✏️ 可编辑 | ✏️ 可编辑 |
| 部署网络信息表 | 🚫 不可见 | ✏️ 可编辑 | ✏️ 可编辑 |
| 部署中间件信息表 | 🚫 不可见 | ✏️ 可编辑 | ✏️ 可编辑 |
| 部署业务信息表 | 🚫 不可见 | ✏️ 可编辑 | ✏️ 可编辑 |
| 项目信息汇总表 | 👁️ 只读 (部分) | 👁️ 只读 | 👁️ 只读 |
| 仪表盘 | 👁️ 只读 | 👁️ 只读 | ✏️ 可编辑 |
**实施方式**:
- 金山多维表格支持「协作者权限」和「字段级权限」
- 对行业组使用 **表单视图** 提交信息,限制其只能填写特定字段
- 敏感字段(密码类)设置为仅管理员查看
---
### 3.9 🔴 自动化流程设计(未覆盖)
**问题**: 方案中提到了"自动化触发"的概念,但未设计具体的自动化规则。
**优化建议 — 自动化规则清单**:
| # | 触发条件 | 执行动作 | 说明 |
|---|---------|---------|------|
| A1 | 新建「项目基本信息」记录 | 自动在「部署状态表」创建一条记录(状态=待排期) | 首次部署自动关联 |
| A2 | 新建「升级信息」记录 | 自动在「部署状态表」创建一条记录(来源类型=升级,状态=待排期) | 升级部署自动关联 |
| A3 | 「部署状态」变更为"部署中" | ① 自动记录「实际开始时间」 ② 检查「环境信息表」是否已填(否则阻止切换) | 状态守卫 |
| A4 | 「部署状态」变更为"已完成" | ① 自动记录「实际结束时间」 ② 检查网络/中间件信息是否已填 ③ 自动计算部署时长 | 完成守卫 |
| A5 | 「部署状态」变更为"部署异常" | 发送通知给小组长和部署人 | 异常告警 |
| A6 | 新建「部署状态」记录 | 自动创建 7 条中间件记录模板 | 减少手动操作 |
| A7 | 「部署优先级」为 P0 且超过 24h 未排期 | 发送催办通知 | 优先级预警 |
> **注意**: 金山多维表格的「自动化」功能支持上述大部分规则,但 **状态守卫A3/A4 的条件检查阻止切换)** 可能需要通过 **AirScript 脚本** 实现,建议优先验证此能力。
---
### 3.10 🟡 通知机制设计(未覆盖)
| 通知场景 | 通知对象 | 通知方式 |
|---------|---------|---------|
| 新部署需求提交 | 特战队全员 | 多维表格通知 + 企微/飞书机器人 |
| 部署排期确认 | 行业组提交人 | 多维表格通知 |
| 部署状态变更 | 行业组提交人 + 部署人 | 多维表格通知 |
| 部署异常 | 小组长 + 部署人 | 多维表格通知 + 企微/飞书机器人 |
| 部署超时预警 | 小组长 | 多维表格通知 |
---
## 四、完善后的表结构总览
### 4.1 ER 关系图
```mermaid
erDiagram
PROJECT_INFO["项目基本信息表"] {
string 项目名称
string 行业组人员
string 电话
string 省份
string 城市
enum 部署优先级 "P0-P3"
date 期望部署日期
text 部署必要条件说明
enum 必要条件是否满足
enum 项目类型
file 部署资源信息
}
UPGRADE_INFO["部署升级信息表"] {
ref 项目名称 "关联项目基本信息"
enum 升级类型
string 当前版本_飞服
string 目标版本_飞服
string 当前版本_监管
string 目标版本_监管
text 升级影响评估
text 回滚方案
daterange 期望升级窗口
file 部署资源信息
}
DEPLOY_STATUS["部署状态表(核心锚表)"] {
auto 部署编号
enum 来源类型
enum 部署状态
enum 部署结果
ref 部署优先级 "引用"
datetime 计划开始时间
datetime 计划结束时间
datetime 实际开始时间
datetime 实际结束时间
formula 部署时长
formula 部署进度
string 命名空间
member 部署人
text 备注
}
ENV_INFO["部署环境信息表"] {
string 公网IP
bool 能否访问公网
string 内网IP
int SSH端口
string SSH用户名
enum 主机功能
enum 主机状态
enum 操作系统
int CPU核心数
string CPU型号
string 内存大小
string 系统盘大小
string 数据盘大小
string GPU信息
text 备注
}
NET_INFO["部署网络信息表"] {
string 访问地址
bool 是否开启SSL
text 端口信息说明
enum 网络环境
enum 主机管理方式
string 管理后台地址
string VPN下载地址
text 备注
}
MIDDLEWARE_INFO["部署中间件信息表"] {
enum 中间件类型
string 中间件版本
enum 部署方式
bool 是否暴露公网
string 公网端口
string 内网IP
string 内网端口
string 用户名
string 管理后台地址
}
BIZ_INFO["部署业务信息表"] {
string 微服务名称
string 微服务分支
string 微服务镜像
string 微服务版本
enum 部署状态
}
PROJECT_INFO ||--o{ DEPLOY_STATUS : "触发创建"
UPGRADE_INFO ||--o{ DEPLOY_STATUS : "触发创建"
UPGRADE_INFO }o--|| PROJECT_INFO : "关联"
DEPLOY_STATUS ||--o{ ENV_INFO : "1:N 关联"
DEPLOY_STATUS ||--o{ NET_INFO : "1:N 关联"
DEPLOY_STATUS ||--o{ MIDDLEWARE_INFO : "1:N 关联"
DEPLOY_STATUS ||--o{ BIZ_INFO : "1:N 关联"
ENV_INFO ||--o{ MIDDLEWARE_INFO : "所在主机"
```
### 4.2 多视图设计一览
```mermaid
graph LR
A["部署状态表"] --> B["表格视图 - 全量列表"]
A --> C["甘特视图 - 部署排期"]
A --> D["日历视图 - 按日查看"]
A --> E["看板视图 - 按状态分列"]
A --> F["表单视图 - 行业组提交入口"]
A --> G["仪表盘 - 统计概览"]
style C fill:#4CAF50,color:#fff
style D fill:#2196F3,color:#fff
style E fill:#FF9800,color:#fff
style G fill:#9C27B0,color:#fff
```
---
## 五、行业组用户体验优化
### 5.1 提交入口设计
> 行业组人员不应直接操作表格,而应通过 **表单视图** 提交部署需求,降低使用门槛。
```
行业组填写流程:
┌─────────────────────────────────────┐
│ 部署需求提交表单(表单视图) │
│ │
│ ① 选择: 新部署 / 升级 │
│ ② 填写项目基本信息 │
│ ③ 上传部署资源附件 │
│ ④ 填写必要条件说明 │
│ ⑤ 选择优先级 │
│ ⑥ 填写期望部署日期 │
│ ⑦ 提交 │
│ │
│ → 自动触发创建部署状态记录 │
│ → 自动通知特战队 │
└─────────────────────────────────────┘
```
### 5.2 查询入口设计
```
行业组查询流程:
┌─────────────────────────────────────┐
│ 部署进度看板(看板视图) │
│ │
│ 待排期 │ 已排期 │ 部署中 │ 已完成 │
│ ┌───┐ │ ┌───┐ │ ┌───┐ │ ┌───┐ │
│ │P01│ │ │P03│ │ │P02│ │ │P05│ │
│ │P04│ │ │ │ │ │ │ │ │P06│ │
│ └───┘ │ └───┘ │ └───┘ │ │P07│ │
│ │ │ │ └───┘ │
│ │
│ 点击卡片 → 查看部署详情 │
│ 包含: 状态、部署人、进度、时间线 │
└─────────────────────────────────────┘
```
---
## 六、敏感信息安全策略
| 信息类型 | 处理方式 | 说明 |
|---------|---------|------|
| SSH 密码 | ❌ 不存入多维表格 | 使用密码管理器(如 1Password / Vault单独管理 |
| 管理后台密码 | ❌ 不存入多维表格 | 同上 |
| VPN 账号密码 | ❌ 不存入多维表格 | 同上 |
| 公网 IP | ⚠️ 字段权限控制 | 仅特战队和小组长可见 |
| 内网 IP | ⚠️ 字段权限控制 | 仅特战队和小组长可见 |
> **安全提醒**: 金山多维表格作为在线协作工具,**严禁**在表格中存储任何明文密码。建议统一使用 **密码管理平台** 管理所有凭据,表格中仅存储「密码存储路径/编号」的引用。
---
## 七、远期 RMDC 集成规划
### 7.1 数据迁移路径
```mermaid
graph LR
A["金山多维表格(当前方案)"] -->|API导出| B["数据清洗 ETL"]
B -->|API导入| C["RMDC系统(远期目标)"]
A -->|阶段1| D["业务验证期 跑通流程"]
D -->|阶段2| E["模板固化期 优化表结构"]
E -->|阶段3| C
```
### 7.2 设计原则
- **字段命名规范化**: 当前表格的字段命名应与 RMDC 数据库 Schema 保持一致,降低迁移成本
- **状态枚举统一**: 部署状态的枚举值应提前定义常量表,一致对齐
- **API 预留**: 利用金山多维表格的开放 API定期将数据同步到内部数据库备份
---
## 八、实施计划
| 阶段 | 时间 | 内容 | 交付物 |
|------|------|------|-------|
| Phase 1 | 第 1 周 | 创建优化后的表结构 + 基础视图 | 7 张数据表 + 5 个视图 |
| Phase 2 | 第 2 周 | 配置自动化规则 + 权限设置 | 自动化规则 7 条 + 权限矩阵 |
| Phase 3 | 第 3 周 | 创建表单入口 + 仪表盘 | 表单视图 2 个 + 仪表盘 1 个 |
| Phase 4 | 第 4 周 | 试运行 + 收集反馈 + 调整 | 试运行报告 |
| Phase 5 | 持续 | RMDC 集成设计 + 数据迁移准备 | 迁移方案文档 |
---
## 九、总结
### 现有方案的优点 ✅
1. **数据建模思路正确** — 以部署状态表为核心锚表的设计符合一对多关系
2. **表结构划分合理** — 环境、网络、中间件、业务分表管理,职责清晰
3. **关联关系设计到位** — 子表通过「所属部署」字段关联核心表
### 需要重点补强的方面 🔧
1. **视图层面** — 必须增加甘特图、日历、看板、仪表盘视图(核心差距)
2. **流程自动化** — 状态变更触发、自动化创建、通知机制
3. **权限管控** — 不同角色的数据可见性和可编辑性
4. **用户体验** — 行业组通过表单提交,看板查询进度
5. **安全策略** — 敏感信息不入表,统一密码管理
6. **状态精细化** — 部署状态从 3 个细化到 6+3 个,增加部署结果字段
7. **远期集成** — 提前规划 RMDC 数据迁移路径

View File

@@ -0,0 +1,703 @@
# 金山多维表格 AirScript 文档
> **来源**: [WPS 开放平台 AirScript 文档](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-instro)
> **抓取时间**: 2026-03-08
---
## 目录
1. [AirScript 简介](#1-airscript-简介)
2. [快速入门](#2-快速入门)
3. [内置基础类型](#3-内置基础类型)
4. [脚本经典案例](#4-脚本经典案例)
5. [API 文档 — 数据表 (Sheets)](#5-api-文档--数据表-sheets)
6. [API 文档 — 字段 (Field / FieldDescriptor)](#6-api-文档--字段-field--fielddescriptor)
7. [API 文档 — 记录 (Record)](#7-api-文档--记录-record)
8. [API 文档 — 视图 (View)](#8-api-文档--视图-view)
9. [API 文档 — 其他](#9-api-文档--其他)
---
## 1. AirScript 简介
**在线文档**: [AirScript-instro](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-instro)
AirScript 是一个简单快速的轻量级脚本应用开发平台,致力于为 WPS 核心组件提供更为强大的效率工具能力。通过 AirScript开发者可以利用 JavaScript 语言编写处理逻辑,实现文档处理的自动化。
### AirScript 能做什么
AirScript 为用户和开发者提供了轻便的脚本运行环境、多端运行的能力以及丰富的 API 接口:
- **无需搭建本地环境**:脚本编辑器随开随用,无需安装配置复杂的本地开发环境。
- **多端一致的运行体验**:编写一次脚本,可直接在 Web、桌面端Windows/Mac以及移动端运行。
- **内置定制化的全局 Application 对象**:通过简单直观的 Application 对象,直接操作表格数据。
- **同步获取属性**AirScript 提供的 API 支持同步获取属性和设置值,代码逻辑更加简洁高效。
- **得益于集成化开发环境**:支持代码高亮、智能提示、在线调试等功能。
### 如何使用 AirScript
AirScript 可在 WPS 多维表格中通过多种方式触发运行:
1. **编辑器手动运行**点击顶部菜单「效率」→「AirScript 脚本编辑器」,在打开的窗口中编写代码并点击运行。
2. **按钮触发**:在多维表格中添加「按钮」字段,并在其设置中选择点击按钮时「执行 AirScript 脚本」。
3. **自动化流程触发**:在多维表格的自动化流程配置中,添加操作「执行以下操作」→「执行 AirScript 脚本」,并选择对应的脚本文件及配置参数。
---
## 2. 快速入门
**在线文档**: [AirScript-quickstart](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-quickstart)
### 在 AirScript 编辑器中运行
在金山文档首页新建一个 WPS 多维表格并打开来体验 AirScript。打开 WPS 多维表格,点击「脚本」-「JS脚本」下的「新建脚本」点击即可调起 AirScript 编辑器。
#### 示例 1Hello World
```javascript
function main(){
console.log("hello world!")
}
main()
```
#### 示例 2操作工作表与单元格
```javascript
function main(){
// 遍历并打印所有工作表的名称
let sheets = Application.Sheets
for (let i = 0; i < sheets.Count; i++) {
let sheet = sheets.Item(i + 1)
console.log(sheet.Name)
}
const sheet = Application.Selection.GetActiveSheet()
// 打印当前激活Sheet的名称
console.log(sheet.name)
// 打印单元格内容
console.log(Application.ActiveView.RecordRange(1, 1).Text)
// 修改单元格内容
Application.ActiveView.RecordRange(1, 1).Value = 2;
// 打印修改后的单元格内容
console.log(Application.ActiveView.RecordRange(1, 1).Text)
}
main()
```
### 在自动化流程中运行
1. 点击「自动化」-「新建自动化」添加「执行AirScript脚本」操作点击「管理脚本」-「新建脚本」进入脚本编辑器即可。
2. 在脚本中获取到传递的参数进行开发使用:
```javascript
// 通过 Context.argv 获取入参
console.log("脚本入参: ", Context.argv);
console.log("脚本入参p1: ", Context.argv.p1);
```
更多自动化请查看 [执行AirScript脚本操作使用指南](https://kdocs.cn/l/cdQOqc6TZuMk)
---
## 3. 内置基础类型
**在线文档**: [AirScript-build-in](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-build-in)
内置的基本数据类型、对象和函数用来帮助开发者开发,遵循 JavaScript 函数命名的标准规范,同时不能和 OpenApi 提供对象重名。
### 基本数据类型
| 类型 | 说明 |
|------|------|
| Boolean | 布尔值 |
| Null | 空值 |
| Undefined | 未定义 |
| Number | 数字 |
| String | 字符串 |
### 内置对象
| 对象 | 说明 |
|------|------|
| Object | 基础对象 |
| Array | 数组 |
| Function | 函数 |
| Date | 日期 |
| RegExp | 正则表达式 |
| JSON | JSON 处理 |
| Math | 数学计算 |
| Number | 数字对象 |
| String | 字符串对象 |
| Map / Set | 集合 |
| Promise | 异步处理 |
### 全局对象与函数
- **console**: 日志输出,支持 `.log`, `.info`, `.warn`, `.error`, `.time`, `.timeEnd`
- **fetch**: 网络请求
- **常用函数**: `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `decodeURI`, `encodeURI`
### 高级内置服务
#### Crypto — 加密服务
```javascript
// Hash 计算
Crypto.hash("md5", "hello") // 支持: md5, sha1, sha256
Crypto.hash("sha256", "data")
// HMAC 计算
Crypto.hmac("hmac-sha1", "key", "data") // 支持: hmac-sha1, hmac-sha256
Crypto.hmac("hmac-sha256", "secretKey", "message")
```
#### Buffer — 二进制数据处理
```javascript
// 创建 Buffer
Buffer.from("hello", "text") // 支持编码: text, base64, hex
Buffer.from("aGVsbG8=", "base64")
// 转换输出
let buf = Buffer.from("hello", "text")
buf.toString("base64") // 输出 base64 编码
buf.toString("hex") // 输出 hex 编码
```
#### Time — 时间服务
```javascript
Time.sleep(1000) // 睡眠 1000 毫秒
Time.now() // 获取当前纳秒时间
```
#### Arguments — 脚本执行参数
```javascript
// 获取脚本执行参数(自动化流程传参时使用)
let args = Context.argv
console.log(args)
```
---
## 4. 脚本经典案例
**在线文档**: [AirScript-demo](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-demo)
### 案例 1选中区域快速批量填值
通过 `Selection` 获取当前选区并循环填入指定值。
```javascript
function main() {
const range = Application.Selection.Range;
for (let i = 1; i <= range.Rows.Count; i++) {
for (let j = 1; j <= range.Columns.Count; j++) {
range.Rows(i).Columns(j).Value = "待处理";
}
}
}
```
### 案例 2快速实现"一键归档"
根据状态字段(如"已完成")将记录从当前表移动到归档表。
```javascript
// 核心逻辑:获取符合条件的记录范围
// 然后执行 AddRecords 并 DeleteRecords
function main() {
const activeSheet = Application.Selection.GetActiveSheet();
const archiveSheet = Application.Sheets.Item("归档表");
// 遍历记录,筛选状态为"已完成"的记录
// 将其添加到归档表并从原表删除
}
```
### 案例 3快速删除空白数据
自动识别并删除关键字段为空的行。
### 案例 4快速创建一张表
使用 `Application.Sheets.Add()` 动态创建新表并定义字段结构。
```javascript
function main() {
Application.Sheets.Add("新表名", {
Fields: [
{ Name: "姓名", Type: "Text" },
{ Name: "年龄", Type: "Number" }
]
});
}
```
### 案例 5格式化数据批量插入
将外部 JSON 或格式化字符串批量导入到多维表格中。
### 案例 6自动双向关联
当 A 表关联 B 表时,脚本自动反向设置 B 表关联 A 表,保持数据同步。
### 案例 7同步主表数据到其他表
实现跨表的数据联动,当主表更新时,自动更新关联表中的冗余字段。
### 案例 8获取日期筛选后记录
利用 `RecordRange.Condition` 配合日期函数进行条件筛选。
### 案例 9获取联系人筛选后记录
筛选特定负责人或创建人的记录信息。
### 案例 10批量将获取的图片 URL 进行文字识别 (OCR)
调用百度云 OCR 接口,识别表中附件字段的图片内容并将文字回填。
```javascript
const access_token = "xxxxxx";
function main() {
const view = Application.ActiveSheet.Views(1);
const attachments = view.RecordRange('1:' + view.RecordRange.Count, ["@图片和附件"]).Value;
// 调用 fetch 请求 OCR 接口并将结果回填
}
```
---
## 5. API 文档 — 数据表 (Sheets)
### 5.1 Application.Sheets
全局入口,获取当前文档中所有数据表的集合。
```javascript
const sheets = Application.Sheets;
```
### 5.2 Sheets.Count — 获取数据表总数
**在线文档**: [Sheets_Count](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheets_Count)
```javascript
const count = Application.Sheets.Count;
console.log("共有 " + count + " 张数据表");
```
**返回值**: `Number` — 数据表数量
### 5.3 Sheets.Item(Index) — 获取指定数据表
**在线文档**: [Sheets_Item](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheets_Item)
**语法**: `Application.Sheets.Item(Index)`
**参数**:
| 参数 | 类型 | 说明 |
|------|------|------|
| Index | Number / String | 数据表的索引(从 1 开始)或名称 |
**示例**:
```javascript
// 通过索引获取
const sheet = Application.Sheets.Item(1);
// 通过名称获取
const sheet = Application.Sheets.Item("项目基本信息表");
```
**返回值**: `Sheet` 对象
### 5.4 Sheets.Add() — 新建数据表
**在线文档**: [Sheets_Add](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheets_Add)
**语法**: `Application.Sheets.Add(Name, Options)`
**参数**:
| 参数 | 类型 | 说明 |
|------|------|------|
| Name | String | 新表的名称 |
| Options | Object | 可选,包含 Fields 数组来初始化表结构 |
**示例**:
```javascript
Application.Sheets.Add("新表名", {
Fields: [
{ Name: "姓名", Type: "Text" },
{ Name: "年龄", Type: "Number" },
{ Name: "状态", Type: "SingleSelect" }
]
});
```
**返回值**: 新创建的 `Sheet` 对象
### 5.5 Sheet 对象属性
#### Sheet.Name — 获取/设置表名
**在线文档**: [Sheet_Name](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheet_Name)
```javascript
const sheet = Application.Sheets.Item(1);
// 获取表名
console.log(sheet.Name);
// 修改表名
sheet.Name = "新表名";
```
#### Sheet.Id — 获取表 ID
**在线文档**: [Sheet_Id](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheet_Id)
```javascript
const sheet = Application.Sheets.Item(1);
console.log(sheet.Id); // 返回该数据表的唯一数字 ID
```
**返回值**: `Number` — 数据表唯一 ID
---
## 6. API 文档 — 字段 (Field / FieldDescriptor)
### 6.1 Sheet.FieldDescriptors — 获取所有字段描述符
**在线文档**: [FieldDescriptors](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/FieldDescriptors)
获取指定数据表的所有字段描述信息集合。
```javascript
const sheet = Application.Sheets.Item(1);
const descriptors = sheet.FieldDescriptors;
// 获取字段总数
console.log(descriptors.Count);
```
**属性**:
| 属性 | 类型 | 说明 |
|------|------|------|
| Count | Number | 字段总数 |
**方法**:
| 方法 | 说明 |
|------|------|
| Item(Index) | 获取指定序号的字段描述(从 1 开始) |
### 6.2 FieldDescriptors.Item(Index) — 获取单个字段描述
**在线文档**: [FieldDescriptors_Item](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/FieldDescriptors_Item)
**语法**: `表达式.Item(Index)``表达式(Index)`
**参数**:
| 参数 | 类型 | 说明 |
|------|------|------|
| Index | Number / String | 字段索引(从 1 开始)或字段名称(以 `@` 前缀) |
**示例**:
```javascript
const sheet = Application.Sheets.Item(1);
const descriptors = sheet.FieldDescriptors;
// 通过索引获取
const field1 = descriptors.Item(1);
console.log(field1.name); // 字段名
console.log(field1.type); // 字段类型
// 通过名称获取
const field2 = descriptors.Item("@项目名称");
console.log(field2.type);
```
**返回值**: `FieldDescriptor` 对象
### 6.3 FieldDescriptor 对象
**在线文档**: [FieldDescriptor](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/FieldDescriptor)
用于描述字段的属性(如类型)。修改后需调用 `.Apply()` 方法才能生效。
**属性**:
| 属性 | 类型 | 说明 |
|------|------|------|
| name | String | 字段名称 |
| type | String | 字段类型 |
**方法**:
| 方法 | 说明 |
|------|------|
| Apply() | 应用对字段描述的修改 |
### 6.4 Field 对象
**在线文档**: [Field](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Field)
代表视图中呈现的字段实例。
**属性**:
| 属性 | 类型 | 说明 |
|------|------|------|
| Id | Number | 字段唯一 ID |
| Name | String | 字段名称 |
| Type | String | 字段类型 |
| Width | Number | 字段宽度 |
### 6.5 字段类型 (Field Type) 枚举
以下是创建字段时支持的类型值:
| 类型值 | 说明 |
|--------|------|
| `Text` / `MultiLineText` | 多行文本 |
| `Number` | 数字 |
| `SingleSelect` | 单选 |
| `MultipleSelect` | 多选 |
| `Date` / `DateTime` | 日期 / 日期时间 |
| `Checkbox` | 复选框 |
| `Attachment` | 附件 |
| `Link` | 关联(外键关联其他表) |
| `Url` | 超链接 |
| `Phone` | 电话 |
| `Email` | 邮箱 |
| `AutoNumber` | 自动编号 |
| `Contact` | 成员/联系人 |
| `Formula` | 公式 |
| `CreatedTime` | 创建时间 |
| `ModifiedTime` | 修改时间 |
| `CreatedBy` | 创建人 |
| `ModifiedBy` | 修改人 |
> **注意**: 使用 `Application.Sheet.CreateSheet()` 创建表时的字段类型值可能与此处略有不同。
> 请参考创建表的 API 文档确认有效值。
---
## 7. API 文档 — 记录 (Record)
### 7.1 RecordRange — 获取记录范围
通过视图对象访问记录:
```javascript
const view = Application.ActiveSheet.Views(1);
// 获取单个记录的单元格值
const value = view.RecordRange(1, 1).Text;
// 获取记录范围
const range = view.RecordRange('1:10', ["@字段名"]);
```
### 7.2 Record 操作示例
```javascript
// 获取当前选中的表
const sheet = Application.Selection.GetActiveSheet();
// 读取数据
const view = sheet.Views(1);
const count = view.RecordRange.Count;
console.log("记录总数: " + count);
// 修改单元格
view.RecordRange(1, 1).Value = "新值";
// 批量读取
for (let i = 1; i <= count; i++) {
console.log(view.RecordRange(i, 1).Text);
}
```
---
## 8. API 文档 — 视图 (View)
### 8.1 获取视图
```javascript
// 获取当前活跃视图
const activeView = Application.ActiveView;
// 通过 Sheet 的 Views 集合获取
const sheet = Application.Sheets.Item(1);
const view = sheet.Views(1); // 索引从 1 开始
```
### 8.2 Selection — 选区对象
```javascript
const selection = Application.Selection;
// 获取当前选中的 Sheet
const sheet = selection.GetActiveSheet();
// 获取选中范围
const range = selection.Range;
```
---
## 9. API 文档 — 其他
### 9.1 Application.Sheet.CreateSheet() — 创建表 (REST API 风格)
> **注意**: 此方法与 `Application.Sheets.Add()` 不同,使用了 REST API 风格的调用方式。
```javascript
var sheet = Application.Sheet.CreateSheet({
Name: '表名',
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: [
{ name: '字段1', type: 'MultiLineText' },
{ name: '状态', type: 'SingleSelect', items: [
{ value: '选项1' }, { value: '选项2' }
] },
{ name: '日期', type: 'Date' },
{ name: '数字', type: 'Number' },
{ name: '复选', type: 'Checkbox' },
{ name: '附件', type: 'Attachment' },
{ name: '链接', type: 'Url' },
{ name: '电话', type: 'Phone' },
{ name: '自动编号', type: 'AutoNumber' },
{ name: '成员', type: 'Contact', multipleContacts: false, noticeNewContact: false }
]
});
var sheetId = sheet.id;
```
### 9.2 Application.Field.CreateFields() — 创建字段
```javascript
Application.Field.CreateFields({
SheetId: sheetId,
Fields: [
{ name: '关联字段', type: 'Link', linkSheet: targetSheetId, multipleLinks: false }
]
});
```
### 9.3 Application.Field.GetFields() — 获取字段信息
```javascript
var fields = Application.Field.GetFields({
SheetId: sheetId
});
// fields 返回该表的所有字段详细信息
console.log(JSON.stringify(fields, null, 2));
```
### 9.4 HTTP / fetch — 网络请求
```javascript
// 使用 fetch 发起 HTTP 请求
const response = fetch("https://api.example.com/data", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ key: "value" })
});
console.log(response.json());
```
### 9.5 console — 日志输出
```javascript
console.log("普通日志");
console.info("信息日志");
console.warn("警告日志");
console.error("错误日志");
// 计时
console.time("操作耗时");
// ... 执行操作 ...
console.timeEnd("操作耗时");
```
---
## 附录 A: 常用代码片段
### A.1 遍历所有表并打印表名
```javascript
const sheets = Application.Sheets;
for (let i = 1; i <= sheets.Count; i++) {
const sheet = sheets.Item(i);
console.log(i + ". " + sheet.Name + " (Id: " + sheet.Id + ")");
}
```
### A.2 获取指定表的所有字段信息
```javascript
const sheet = Application.Sheets.Item("项目基本信息表");
const descriptors = sheet.FieldDescriptors;
for (let i = 1; i <= descriptors.Count; i++) {
const fd = descriptors.Item(i);
console.log(fd.name + " → " + fd.type);
}
```
### A.3 导出所有表结构为 JSON
```javascript
var result = [];
var sheets = Application.Sheets;
for (var i = 1; i <= sheets.Count; i++) {
var sheet = sheets.Item(i);
var fields = [];
var descriptors = sheet.FieldDescriptors;
for (var j = 1; j <= descriptors.Count; j++) {
var fd = descriptors.Item(j);
fields.push({ name: fd.name, type: fd.type });
}
result.push({
tableName: sheet.Name,
sheetId: sheet.Id,
fieldCount: fields.length,
fields: fields
});
}
console.log(JSON.stringify(result, null, 2));
```
### A.4 获取环境信息
```javascript
// 当前激活的表
console.log("当前表: " + Application.Selection.GetActiveSheet().Name);
// 当前文档中的所有表
var sheets = Application.Sheets;
console.log("表数量: " + sheets.Count);
```
---
## 附录 B: 文档链接索引
| 章节 | 在线链接 |
|------|---------|
| AirScript 简介 | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-instro) |
| 快速入门 | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-quickstart) |
| 内置基础类型 | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-build-in) |
| 脚本经典案例 | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/AirScript/AirScript-demo) |
| API: Sheets.Add | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheets_Add) |
| API: Sheets.Item | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheets_Item) |
| API: Sheet.Name | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheet_Name) |
| API: Sheet.Id | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Sheet_Id) |
| API: FieldDescriptors | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/FieldDescriptors) |
| API: FieldDescriptors.Item | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/FieldDescriptors_Item) |
| API: FieldDescriptor | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/FieldDescriptor) |
| API: Field | [链接](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/Api/Field) |

View File

@@ -0,0 +1,84 @@
# 🚀 交付部署协同管理系统 (RMDC 前置过渡版)
## 1. 系统核心架构
采用关系型数据库模型。放弃以“项目”为维度的扁平记录,改用以**「项目本地化部署状态表(部署实例)」**为核心锚表。所有的环境、网络、中间件和业务微服务数据,均通过单向或双向关联字段与特定的“部署实例”绑定。
## 2. 角色与视图/权限设计
* **行业组/售前(需求方):**
* **操作:** 仅可通过“表单收集”模式填写《项目基本/升级信息表》。
* **视图:** 只读访问《项目部署全局看板(甘特图/日历视图)》,一目了然查看预期时间和当前进度。
* **交付特战队(执行方):**
* **操作:** 拥有核心锚表及各个子表的读写权限。
* **权限管控:** 敏感字段(如密码、管理后台用户)利用多维表格的“列权限”设置为特定人员可见/仅创建者可见。
## 3. 核心数据表结构定义
### 3.1 触发源头表 (通过表单收集)
**A. 项目本地化部署需求表 (合并首发与升级)**
* 项目名称 (文本,支持搜索)
* 部署类型 (单选:首次部署 / 平台升级)
* 行业组接口人 / 电话
* 省份 / 城市 (级联选择)
* 飞服平台版本 (如 2.3.0) / 监管平台版本
* 部署资源与前提条件 (富文本/附件:包含网络拓扑、服务器到位证明等)
* 期望交付日期 (日期)
> **💡 自动化动作:** 提交后,利用多维表格自动化机器人,提取核心信息,自动在《项目本地化部署状态表》中新建一条状态为“待排期”的记录。
### 3.2 核心锚表 (中枢调度)
**B. 项目本地化部署状态表 (部署排期与进度台账)**
* 命名空间 (Namespace) (唯一标识,如 `fs-xiongan-prod`,作为全表主键)
* 关联需求记录 (引用自《项目本地化部署需求表》)
* 部署状态 (单选:待排期 / 排期中 / 部署中 / 异常挂起 / 部署完成)
* 当前进度/卡点说明 (文本,供行业组查看了解详情)
* 部署开始时间 / 预计结束时间 (日期格式,用于生成甘特图)
* 部署负责人 / 电话
* **反向关联聚合区(直接透出子表数量,方便检查):**
* 关联主机数 (自动统计《环境信息表》记录数)
* 公网暴露数 (自动统计《网络信息表》记录数)
### 3.3 基础设施与配置子表 (执行落地)
所有子表必须包含字段:`所属部署 (关联至状态表的命名空间)`
**C. 项目部署环境信息表 (台账:一机一行)**
* 内网IP / 公网IP
* CPU架构 (单选:**amd64** / **arm64** ) *注:严格规范架构类型,禁止随意填写以防镜像拉取失败。*
* 主机规格 (CPU核心数 / 内存 / 系统盘 / 数据盘)
* 主机角色 (单选master / worker / storage / doris)
* SSH端口 / 用户名
* SSH凭证密码 (列权限管控,禁止非运维人员查看或编辑)
**D. 项目部署业务与中间件信息表 (可合并以简化层级)**
* 组件类型 (单选:微服务 / MySQL / Redis / RabbitMQ / EMQX / MinIO 等)
* 镜像名称与版本:*(需严格保持三段式规范,例如 `nginx:latest` 必须规范填写为 `${HARBOR_HOST}/cmii/nginx:latest`,基础组件镜像如 `ossrs/srs:v5.0.195` 同样需转换为 `${HARBOR_HOST}/cmii/srs:v5.0.195`)*
* 内网暴露 (IP:Port)
* 公网暴露 (是/否公网Port)
* 认证信息 (用户名/密码 - 列权限强管控)
**E. 交付物与网络访问表 (对行业组输出)**
* 平台访问入口 (URL)
* 网络环境与访问方式 (单选:完全内网 / 单主机公网 / 全公网 + 堡垒机/VPN接入等)
* VPN客户端下载及账号信息
## 4. 状态切换校验流转规则
在多维表格的自动化或数据验证中设置:
* **【待排期】➜【部署中】:** 必须已分配“部署负责人”及“预计结束时间”才可扭转。
* **【部署中】➜【部署完成】:** 必须校验关联的《环境信息表》记录数 ≥ 1且《网络访问表》已填写平台入口 URL。流转完成后触发飞书/钉钉 webhook 自动@行业组接口人通知验收

View File

@@ -0,0 +1,386 @@
/**
* ============================================================
* AirScript: 部署状态切换为"部署中"时自动创建子表记录
* ============================================================
*
* 触发方式:
* 方式一: 自动化流程触发 (推荐)
* 方式二: 手动在 AirScript 编辑器中运行
*
* 核心逻辑:
* 1. 读取「项目本地化部署状态表」中状态为「部署中」的记录
* 2. 使用 Application.Field.GetFields() 检测各子表的实际字段类型
* 3. 自动跳过 Link(关联)等不兼容字段,避免 E_INVALIDARG 错误
* 4. 通过 Application.Record.CreateRecords() 批量创建新记录
*
* ============================================================
*/
// ============================
// 配置区域
// ============================
const TABLE_NAMES = {
deployStatus: "项目本地化部署状态表",
middleware: "项目部署中间件信息表",
business: "项目部署业务信息表",
network: "项目部署网络信息表",
};
// 7 种标准中间件
const MIDDLEWARE_LIST = [
{ type: "MySQL", version: "", deployMethod: "容器化", defaultPort: "3306" },
{ type: "Redis", version: "", deployMethod: "容器化", defaultPort: "6379" },
{ type: "RabbitMQ", version: "", deployMethod: "容器化", defaultPort: "5672" },
{ type: "EMQX", version: "", deployMethod: "容器化", defaultPort: "1883" },
{ type: "NACOS", version: "", deployMethod: "容器化", defaultPort: "8848" },
{ type: "K8S Dashboard", version: "", deployMethod: "容器化", defaultPort: "443" },
{ type: "MINIO", version: "", deployMethod: "容器化", defaultPort: "9000" },
];
// 默认初始业务微服务列表
const DEFAULT_SERVICES = [
{ name: "网关服务 (Gateway)", branch: "", image: "", version: "" },
{ name: "认证服务 (Auth)", branch: "", image: "", version: "" },
{ name: "用户服务 (User)", branch: "", image: "", version: "" },
{ name: "飞服平台核心服务", branch: "", image: "", version: "" },
{ name: "监管平台核心服务", branch: "", image: "", version: "" },
];
// 需要跳过的字段类型 (这些类型需要特殊值格式,不能直接传文本)
const SKIP_FIELD_TYPES = ["Link", "Lookup", "Formula", "CreatedTime", "ModifiedTime", "CreatedBy", "ModifiedBy", "AutoNumber"];
// ============================
// 工具函数: 读取已有记录的字段值
// ============================
function readCell(view, row, fieldName) {
try {
return view.RecordRange(row + ":" + row, ["@" + fieldName]).Value;
} catch (e) {
console.warn("⚠️ 读取 [" + fieldName + "] 行" + row + " 失败: " + e.message);
return null;
}
}
// ============================
// 工具函数: 获取表的字段类型映射
// ============================
function getFieldTypeMap(sheetId) {
let typeMap = {};
try {
let fieldsResult = Application.Field.GetFields({ SheetId: sheetId });
// fieldsResult 可能是 {fields: [...]} 或直接是数组
let fieldsList = fieldsResult.fields || fieldsResult;
if (Array.isArray(fieldsList)) {
for (let i = 0; i < fieldsList.length; i++) {
let f = fieldsList[i];
typeMap[f.name] = f.type;
console.log(" 字段: " + f.name + " → 类型: " + f.type);
}
}
} catch (e) {
console.warn("⚠️ 获取字段信息失败: " + e.message);
}
return typeMap;
}
// ============================
// 核心工具: 安全创建记录 (自动检测字段类型,跳过不兼容字段)
// ============================
function createRecordsSafely(tableName, recordsData) {
const sheet = Application.Sheets.Item(tableName);
const sheetId = sheet.Id;
console.log(" 📊 检测表 [" + tableName + "] 的字段类型 (SheetId=" + sheetId + ")...");
let fieldTypeMap = getFieldTypeMap(sheetId);
// 构建过滤后的记录,跳过不兼容字段
let cleanRecords = [];
let skippedFields = [];
for (let r = 0; r < recordsData.length; r++) {
let originalFields = recordsData[r];
let cleanFields = {};
for (let fieldName in originalFields) {
let value = originalFields[fieldName];
// 检查字段是否存在
let fieldType = fieldTypeMap[fieldName];
if (!fieldType) {
// 字段不存在于表中, 尝试仍然传入 (可能是 GetFields 没返回)
// 但如果值为空就跳过
if (value === "" || value === null || value === undefined) continue;
cleanFields[fieldName] = value;
continue;
}
// 检查是否需要跳过的字段类型
if (SKIP_FIELD_TYPES.indexOf(fieldType) >= 0) {
if (skippedFields.indexOf(fieldName) < 0) {
skippedFields.push(fieldName);
console.log(" ⏭️ 跳过字段 [" + fieldName + "] (类型: " + fieldType + ",不支持直接赋文本值)");
}
continue;
}
// 跳过空值 (对于非文本字段)
if ((value === "" || value === null || value === undefined) &&
fieldType !== "MultiLineText" && fieldType !== "Text") {
continue;
}
cleanFields[fieldName] = value;
}
cleanRecords.push({ fields: cleanFields });
}
if (cleanRecords.length === 0) {
console.warn("⚠️ 没有有效记录需要创建");
return false;
}
// 打印将要创建的第一条记录的字段信息 (调试用)
console.log(" 📋 将创建 " + cleanRecords.length + " 条记录,首条字段:");
let firstFields = cleanRecords[0].fields;
for (let fn in firstFields) {
console.log(" " + fn + " = " + firstFields[fn]);
}
// 批量创建记录
Application.Record.CreateRecords({
SheetId: sheetId,
Records: cleanRecords
});
console.log(" ✅ 成功创建 " + cleanRecords.length + " 条记录");
if (skippedFields.length > 0) {
console.log(" ⚠️ 以下关联字段需要手动设置: " + skippedFields.join(", "));
}
return true;
}
// ============================
// 主函数
// ============================
function main() {
console.log("====================================");
console.log("开始执行: 自动创建部署子表记录");
console.log("====================================");
// Step 1: 获取触发记录信息
let triggerInfo = getTriggerRecordInfo();
if (!triggerInfo) {
console.error("❌ 无法获取触发记录信息,脚本终止");
return;
}
console.log("\n✅ 触发记录信息:");
console.log(" 部署编号: " + triggerInfo.deployId);
console.log(" 关联项目: " + triggerInfo.projectName);
console.log(" 部署负责人: " + triggerInfo.deployer);
console.log(" 部署状态: " + triggerInfo.status);
console.log(" 命名空间: " + triggerInfo.namespace);
// 校验部署状态
if (triggerInfo.status !== "部署中") {
console.warn("⚠️ 当前部署状态不是「部署中」,跳过");
return;
}
// 校验部署负责人
if (!triggerInfo.deployer || String(triggerInfo.deployer).trim() === "") {
console.warn("⚠️ 部署负责人为空,跳过");
return;
}
// Step 2: 防重复检查
if (checkExistingRecords(triggerInfo)) {
console.warn("⚠️ 已存在关联记录,跳过创建以防重复");
return;
}
// Step 3: 创建中间件信息记录
createMiddlewareRecords(triggerInfo);
// Step 4: 创建业务信息记录
createBusinessRecords(triggerInfo);
// Step 5: 创建网络信息记录
createNetworkRecords(triggerInfo);
console.log("\n====================================");
console.log("✅ 所有子表记录创建完成!");
console.log("====================================");
console.log("📌 提示: 如果「所属部署」是关联字段(Link),请手动在各子表中设置关联。");
}
// ============================
// 获取触发记录信息
// ============================
function getTriggerRecordInfo() {
try {
const sheet = Application.Sheets.Item(TABLE_NAMES.deployStatus);
const view = sheet.Views(1);
const totalRows = view.RecordRange.Count;
console.log("📋 部署状态表共 " + totalRows + " 行记录");
let targetRow = null;
// 优先从自动化入参获取
if (typeof Context !== "undefined" && Context.argv) {
console.log("📥 自动化入参: " + JSON.stringify(Context.argv));
if (Context.argv.p1) {
targetRow = parseInt(Context.argv.p1);
}
}
// 没有入参则从最后一行往前查找「部署中」
if (!targetRow) {
console.log("📋 扫描全表查找「部署中」记录...");
for (let i = totalRows; i >= 1; i--) {
let status = readCell(view, i, "部署状态");
if (status && String(status) === "部署中") {
targetRow = i;
console.log(" 找到第 " + i + " 行状态为「部署中」");
break;
}
}
}
if (!targetRow || targetRow < 1 || targetRow > totalRows) {
console.error("❌ 未找到有效的目标记录");
return null;
}
let deployId = readCell(view, targetRow, "部署编号");
let projectName = readCell(view, targetRow, "关联项目");
let deployer = readCell(view, targetRow, "部署负责人");
let status = readCell(view, targetRow, "部署状态");
let namespace = readCell(view, targetRow, "命名空间");
return {
row: targetRow,
deployId: deployId ? String(deployId) : "未编号",
projectName: projectName ? String(projectName) : "未关联项目",
deployer: deployer ? String(deployer) : "",
status: status ? String(status) : "",
namespace: namespace ? String(namespace) : "",
};
} catch (e) {
console.error("❌ 获取触发记录信息失败: " + e.message);
return null;
}
}
// ============================
// 防重复: 检查中间件表是否已有记录包含相同部署标识
// ============================
function checkExistingRecords(triggerInfo) {
try {
const sheet = Application.Sheets.Item(TABLE_NAMES.middleware);
const view = sheet.Views(1);
const count = view.RecordRange.Count;
if (count === 0) return false;
// 尝试用「中间件类型」列 + 行数来判断
// 如果已有 >= 7 条中间件记录且最近的标识匹配,认为已创建
// 这里简单检查行数,后续可优化
console.log(" 中间件表当前共 " + count + " 条记录");
return false; // 暂时不做防重检查,确保首次能创建成功
} catch (e) {
console.warn("⚠️ 检查已有记录时出错: " + e.message);
return false;
}
}
// ============================
// 创建中间件信息记录 (7 条)
// ============================
function createMiddlewareRecords(triggerInfo) {
console.log("\n📦 创建中间件信息记录...");
try {
let records = [];
for (let i = 0; i < MIDDLEWARE_LIST.length; i++) {
let mw = MIDDLEWARE_LIST[i];
records.push({
"所属部署": triggerInfo.deployId,
"中间件类型": mw.type,
"中间件版本": mw.version,
"部署方式": mw.deployMethod,
"是否暴露公网": false,
"内网端口": mw.defaultPort,
});
}
let success = createRecordsSafely(TABLE_NAMES.middleware, records);
if (success) {
console.log("📦 中间件信息创建完成,共 " + MIDDLEWARE_LIST.length + " 条");
}
} catch (e) {
console.error("❌ 创建中间件记录失败: " + e.message);
}
}
// ============================
// 创建业务信息记录
// ============================
function createBusinessRecords(triggerInfo) {
console.log("\n🔧 创建业务信息记录...");
try {
let records = [];
for (let i = 0; i < DEFAULT_SERVICES.length; i++) {
let svc = DEFAULT_SERVICES[i];
records.push({
"所属部署": triggerInfo.deployId,
"微服务名称": svc.name,
"微服务分支": svc.branch,
"微服务镜像": svc.image,
"微服务版本": svc.version,
"部署状态": "待部署",
});
}
let success = createRecordsSafely(TABLE_NAMES.business, records);
if (success) {
console.log("🔧 业务信息创建完成,共 " + DEFAULT_SERVICES.length + " 条");
}
} catch (e) {
console.error("❌ 创建业务记录失败: " + e.message);
}
}
// ============================
// 创建网络信息记录
// ============================
function createNetworkRecords(triggerInfo) {
console.log("\n🌐 创建网络信息记录...");
try {
let records = [{
"所属部署": triggerInfo.deployId,
"是否开启SSL": false,
"端口信息说明": "待填写",
"备注": "自动创建 - 部署编号: " + triggerInfo.deployId + " 项目: " + triggerInfo.projectName,
}];
let success = createRecordsSafely(TABLE_NAMES.network, records);
if (success) {
console.log("🌐 网络信息创建完成,共 1 条");
}
} catch (e) {
console.error("❌ 创建网络记录失败: " + e.message);
}
}
// ============================
// 执行入口
// ============================
main();

View File

@@ -0,0 +1,747 @@
/**
* ============================================================
* AirScript: 按钮触发 — 自动创建部署子表记录 (v2)
* ============================================================
*
* 触发方式: 按钮字段触发 (项目本地化部署状态表中的按钮列)
*
* 核心逻辑:
* 1. 通过按钮获取当前行数据 (部署编号、项目名称、部署平台等)
* 2. 获取当前行的 recordId用于 Link 关联字段回写
* 3. 根据「部署平台」字段严格区分: 飞服平台只建飞服微服务,监管平台只建监管微服务
* 4. 所有新建记录自动填写「项目名称」字段
* 5. 所有新建记录自动通过 Link 关联回「项目本地化部署状态表」当前行
*
* 变更日志:
* v2 - 2026-03-09
* - 新增: 项目名称字段自动填入子表记录
* - 新增: 所属部署 Link 关联字段自动回写 (通过 recordId)
* - 修复: 平台判定逻辑,飞服平台不再同时生成监管服务
*
* ============================================================
*/
// ========================================================================
// 第一部分: 配置定义区域 (所有可配置项集中在此)
// ========================================================================
// --- 表名配置 ---
const TABLE_NAMES = {
deployStatus: "项目本地化部署状态表",
middleware: "项目部署中间件信息表",
business: "项目部署业务信息表",
network: "项目部署网络信息表",
};
// --- 中间件配置 ---
// 所有中间件信息在此处定义,每新增/删除中间件只需修改此数组
const MIDDLEWARE_LIST = [
{ type: "MySQL", version: "", deployMethod: "容器化", defaultPublicPort: "", defaultPrivatePort: "3306", defaultPrivateIP: "" },
{ type: "Redis", version: "", deployMethod: "容器化", defaultPublicPort: "", defaultPrivatePort: "6379", defaultPrivateIP: "" },
{ type: "RabbitMQ", version: "", deployMethod: "容器化", defaultPublicPort: "", defaultPrivatePort: "5672", defaultPrivateIP: "" },
{ type: "EMQX", version: "", deployMethod: "容器化", defaultPublicPort: "", defaultPrivatePort: "1883", defaultPrivateIP: "" },
{ type: "NACOS", version: "", deployMethod: "容器化", defaultPublicPort: "", defaultPrivatePort: "8848", defaultPrivateIP: "" },
{ type: "K8S Dashboard", version: "", deployMethod: "容器化", defaultPublicPort: "", defaultPrivatePort: "443", defaultPrivateIP: "" },
{ type: "MINIO", version: "", deployMethod: "容器化", defaultPublicPort: "9000", defaultPrivatePort: "9000", defaultPrivateIP: "" },
];
// --- 业务微服务配置 ---
// 使用 Map 格式: key 为部署平台标识value 为该平台对应的微服务列表
// 「飞服平台」和「监管平台」的微服务名称不同
const BUSINESS_SERVICES_MAP = {
// 公共服务 (两个平台都需要部署的)
"common": [
{ name: "网关服务 (fly-gateway)", branch: "", image: "", version: "" },
{ name: "认证服务 (fly-auth)", branch: "", image: "", version: "" },
{ name: "用户服务 (fly-system)", branch: "", image: "", version: "" },
{ name: "文件服务 (fly-file)", branch: "", image: "", version: "" },
],
// 飞行服务平台专属微服务
"fly": [
{ name: "飞服-飞行管理 (fly-flight)", branch: "", image: "", version: "" },
{ name: "飞服-设备管理 (fly-device)", branch: "", image: "", version: "" },
{ name: "飞服-空域管理 (fly-airspace)", branch: "", image: "", version: "" },
{ name: "飞服-任务调度 (fly-task)", branch: "", image: "", version: "" },
{ name: "飞服-数据中心 (fly-datacenter)", branch: "", image: "", version: "" },
{ name: "飞服-消息服务 (fly-message)", branch: "", image: "", version: "" },
],
// 监管平台专属微服务
"supervisor": [
{ name: "监管-飞行监管 (sup-flight)", branch: "", image: "", version: "" },
{ name: "监管-无人机管理 (sup-uav)", branch: "", image: "", version: "" },
{ name: "监管-空域管理 (sup-airspace)", branch: "", image: "", version: "" },
{ name: "监管-预警处置 (sup-warning)", branch: "", image: "", version: "" },
{ name: "监管-数据中心 (sup-datacenter)", branch: "", image: "", version: "" },
{ name: "监管-消息服务 (sup-message)", branch: "", image: "", version: "" },
],
};
// 需要跳过的字段类型 (这些类型不能直接传文本值)
// 注意: Link 已移除,由脚本通过 recordId 自动处理
const SKIP_FIELD_TYPES = [
"Lookup", "Formula",
"CreatedTime", "ModifiedTime", "CreatedBy", "ModifiedBy",
"AutoNumber",
];
// ========================================================================
// 第二部分: 通用工具函数
// ========================================================================
/**
* 读取指定视图中某行某字段的原始值 (.Value)
* 适用于普通文本、数字等字段
*/
function readCell(view, row, fieldName) {
try {
return view.RecordRange(row + ":" + row, ["@" + fieldName]).Value;
} catch (e) {
console.warn("⚠️ 读取 [" + fieldName + "] 行" + row + " 失败: " + e.message);
return null;
}
}
/**
* 读取指定视图中某行某字段的显示文本 (.Text)
* ★ 适用于 Link/引用/Lookup 等复杂字段,返回用户可见的文本而非原始对象
* 例如: 项目名称 是引用字段时,.Value 返回 {"Value":"[]"}.Text 返回 "项目A"
*/
function readCellText(view, row, fieldName) {
try {
return view.RecordRange(row + ":" + row, ["@" + fieldName]).Text;
} catch (e) {
console.warn("⚠️ 读取Text [" + fieldName + "] 行" + row + " 失败: " + e.message);
return null;
}
}
/**
* 获取表的字段信息 (含类型和额外属性)
* @returns {{ typeMap: Object, fieldsList: Array }}
*/
function getFieldInfo(sheetId) {
let typeMap = {};
let fieldsList = [];
try {
let fieldsResult = Application.Field.GetFields({ SheetId: sheetId });
fieldsList = fieldsResult.fields || fieldsResult;
if (Array.isArray(fieldsList)) {
for (let i = 0; i < fieldsList.length; i++) {
let f = fieldsList[i];
typeMap[f.name] = f.type;
}
}
} catch (e) {
console.warn("⚠️ 获取字段信息失败: " + e.message);
}
return { typeMap: typeMap, fieldsList: fieldsList };
}
/**
* ★ 通过字段值匹配获取 recordId (不再依赖行索引)
*
* GetRecords 返回顺序可能与视图行号不一致,
* 所以用「部署编号」等唯一字段值来精确匹配正确的记录。
*
* @param {number} sheetId - 表 ID
* @param {string} matchFieldName - 用于匹配的字段名 (如 "部署编号")
* @param {string} matchFieldValue - 要匹配的字段值
* @returns {string|null} recordId
*/
function getRecordIdByMatch(sheetId, matchFieldName, matchFieldValue) {
try {
let result = Application.Record.GetRecords({ SheetId: sheetId });
let records = result.records || result;
if (!Array.isArray(records)) {
console.warn(" 📎 GetRecords 返回非数组: " + typeof records);
return null;
}
console.log(" 📎 总记录数: " + records.length + ",查找字段 [" + matchFieldName + "] = [" + matchFieldValue + "]");
// 打印第一条记录结构用于调试
if (records.length > 0) {
let first = records[0];
let firstId = first.id || first.Id || first.recordId;
console.log(" 📎 记录结构示例 — id: " + firstId + ", keys: " + Object.keys(first).join(", "));
if (first.fields) {
console.log(" 📎 fields keys: " + Object.keys(first.fields).join(", "));
}
}
// 遍历所有记录,按字段值匹配
for (let i = 0; i < records.length; i++) {
let record = records[i];
let recordId = record.id || record.Id || record.recordId;
let fields = record.fields || record;
// 获取匹配字段的值
let fieldValue = null;
if (fields[matchFieldName] !== undefined) {
fieldValue = fields[matchFieldName];
} else if (record.fields && record.fields[matchFieldName] !== undefined) {
fieldValue = record.fields[matchFieldName];
}
// 比较字段值 (转为字符串比较,处理各种类型)
let fieldValueStr = (fieldValue !== null && fieldValue !== undefined) ? String(fieldValue).trim() : "";
if (fieldValueStr === String(matchFieldValue).trim()) {
console.log(" 📎 ✅ 匹配成功!记录[" + i + "] recordId: " + recordId);
return String(recordId);
}
}
console.warn(" 📎 ❌ 未找到匹配的记录");
return null;
} catch (e) {
console.warn("⚠️ 获取 recordId 失败: " + e.message);
return null;
}
}
/**
* 安全创建记录: 自动检测字段类型,正确处理 Link 关联字段
*
* @param {string} tableName - 目标表名
* @param {Array} recordsData - 记录数据数组,每条为 { fieldName: value }
* @param {Object} linkFieldValues - Link 字段值映射 { fieldName: [recordId] }
* @returns {boolean} 是否成功
*/
function createRecordsSafely(tableName, recordsData, linkFieldValues) {
const sheet = Application.Sheets.Item(tableName);
const sheetId = sheet.Id;
console.log(" 📊 检测表 [" + tableName + "] 的字段类型...");
let fieldInfo = getFieldInfo(sheetId);
let fieldTypeMap = fieldInfo.typeMap;
let cleanRecords = [];
let skippedFields = [];
for (let r = 0; r < recordsData.length; r++) {
let originalFields = recordsData[r];
let cleanFields = {};
for (let fieldName in originalFields) {
let value = originalFields[fieldName];
let fieldType = fieldTypeMap[fieldName];
// 字段不存在于表中,如果值非空仍传入
if (!fieldType) {
if (value === "" || value === null || value === undefined) continue;
cleanFields[fieldName] = value;
continue;
}
// === Link 字段特殊处理 ===
// Link 字段需要传入 recordId 数组格式: [recordId]
if (fieldType === "Link") {
if (linkFieldValues && linkFieldValues[fieldName]) {
cleanFields[fieldName] = linkFieldValues[fieldName];
console.log(" 🔗 Link 字段 [" + fieldName + "] → " + JSON.stringify(linkFieldValues[fieldName]));
} else {
if (skippedFields.indexOf(fieldName) < 0) {
skippedFields.push(fieldName);
console.log(" ⏭️ 跳过 Link 字段 [" + fieldName + "] (无 recordId 可关联)");
}
}
continue;
}
// 跳过不兼容字段类型
if (SKIP_FIELD_TYPES.indexOf(fieldType) >= 0) {
if (skippedFields.indexOf(fieldName) < 0) {
skippedFields.push(fieldName);
console.log(" ⏭️ 跳过字段 [" + fieldName + "] (类型: " + fieldType + ")");
}
continue;
}
// 跳过非文本字段的空值
if ((value === "" || value === null || value === undefined) &&
fieldType !== "MultiLineText" && fieldType !== "Text") {
continue;
}
cleanFields[fieldName] = value;
}
// 追加 Link 字段 (如果记录数据中没有显式包含,但 linkFieldValues 中有)
if (linkFieldValues) {
for (let linkFieldName in linkFieldValues) {
if (!cleanFields[linkFieldName] && fieldTypeMap[linkFieldName] === "Link") {
cleanFields[linkFieldName] = linkFieldValues[linkFieldName];
}
}
}
cleanRecords.push({ fields: cleanFields });
}
if (cleanRecords.length === 0) {
console.warn("⚠️ 没有有效记录需要创建");
return false;
}
console.log(" 📋 将创建 " + cleanRecords.length + " 条记录");
// 打印首条记录内容 (用于调试)
if (cleanRecords.length > 0) {
let firstFields = cleanRecords[0].fields;
console.log(" 📋 首条记录字段:");
for (let fn in firstFields) {
let v = firstFields[fn];
let display = (typeof v === "object") ? JSON.stringify(v) : String(v);
console.log(" " + fn + " = " + display);
}
}
// 批量创建记录
Application.Record.CreateRecords({
SheetId: sheetId,
Records: cleanRecords,
});
console.log(" ✅ 成功创建 " + cleanRecords.length + " 条记录");
if (skippedFields.length > 0) {
console.log(" ⚠️ 以下字段需要手动设置: " + skippedFields.join(", "));
}
return true;
}
// ========================================================================
// 第三部分: 获取按钮触发行的数据
// ========================================================================
/**
* 获取当前按钮所在行的部署信息
* ★ Context.argv.p1 是记录ID (recordId),不是行号
*/
function getTriggerRecordInfo() {
try {
const sheet = Application.Sheets.Item(TABLE_NAMES.deployStatus);
const sheetId = sheet.Id;
const view = sheet.Views(1);
const totalRows = view.RecordRange.Count;
console.log("📋 部署状态表共 " + totalRows + " 行记录");
// ★ 核心: Context.argv.p1 是记录ID (recordId)
let recordId = null;
if (typeof Context !== "undefined" && Context.argv) {
console.log("📥 按钮入参: " + JSON.stringify(Context.argv));
if (Context.argv.p1) {
recordId = String(Context.argv.p1).trim();
console.log(" 📎 按钮传入的记录ID: " + recordId);
}
}
// ★ 通过 recordId 定位视图中的行号 (用于 readCell/readCellText 读取字段)
let targetRow = null;
if (recordId) {
// 用 GetRecords 找到 recordId 对应的索引,再映射到行号
try {
let result = Application.Record.GetRecords({ SheetId: sheetId });
let records = result.records || result;
if (Array.isArray(records)) {
for (let i = 0; i < records.length; i++) {
let rid = records[i].id || records[i].Id || records[i].recordId;
if (String(rid) === recordId) {
targetRow = i + 1; // 视图行号从1开始
console.log(" 📎 recordId 匹配到记录索引 " + i + ",映射行号: " + targetRow);
break;
}
}
}
} catch (e) {
console.warn(" ⚠️ GetRecords 定位行号失败: " + e.message);
}
}
// 如果通过 recordId 找不到行号fallback 到选区
if (!targetRow) {
try {
let selection = Application.Selection;
let range = selection.Range;
if (range && range.Rows && range.Rows.Count > 0) {
targetRow = range.Rows(1).Row;
console.log(" 使用当前选区行: " + targetRow);
}
} catch (e) {
console.warn(" ⚠️ 无法获取选区: " + e.message);
}
}
// 最后 fallback: 扫描查找「部署中」记录
if (!targetRow) {
console.log("📋 扫描全表查找「部署中」记录...");
for (let i = totalRows; i >= 1; i--) {
let status = readCell(view, i, "部署状态");
if (status && String(status) === "部署中") {
targetRow = i;
console.log(" 找到第 " + i + " 行状态为「部署中」");
break;
}
}
}
if (!targetRow || targetRow < 1 || targetRow > totalRows) {
console.error("❌ 未找到有效的目标记录行");
return null;
}
// 读取本行关键字段
let deployId = readCell(view, targetRow, "部署编号");
let deployer = readCell(view, targetRow, "部署负责人");
let status = readCell(view, targetRow, "部署状态");
let namespace = readCell(view, targetRow, "命名空间");
let deployPlatform = readCell(view, targetRow, "部署平台");
// 使用 .Text 读取项目名称 (Link/引用类型字段)
let rawProjectName = readCellText(view, targetRow, "项目名称");
let rawRelProject = readCellText(view, targetRow, "关联项目");
console.log(" 📝 项目名称(.Text): [" + rawProjectName + "]");
console.log(" 📝 关联项目(.Text): [" + rawRelProject + "]");
let finalProjectName = "";
if (rawProjectName && String(rawProjectName).trim() !== "" && String(rawProjectName).trim() !== "[]") {
finalProjectName = String(rawProjectName).trim();
} else if (rawRelProject && String(rawRelProject).trim() !== "" && String(rawRelProject).trim() !== "[]") {
finalProjectName = String(rawRelProject).trim();
} else {
finalProjectName = "未关联项目";
}
console.log(" 📝 最终项目名称: [" + finalProjectName + "]");
// ★ recordId 来源优先级:
// 1. Context.argv.p1 (按钮直接传入,最可靠)
// 2. 无 fallback没有 recordId 则 Link 无法自动关联
if (!recordId) {
console.warn("⚠️ 按钮未传入 recordId (p1),所属部署 Link 关联将无法自动设置");
}
let deployIdStr = deployId ? String(deployId).trim() : "未编号";
return {
row: targetRow,
recordId: recordId, // ★ 直接来自按钮 p1 参数
sheetId: sheetId,
deployId: deployIdStr,
projectName: finalProjectName,
deployer: deployer ? String(deployer).trim() : "",
status: status ? String(status).trim() : "",
namespace: namespace ? String(namespace).trim() : "",
deployPlatform: deployPlatform ? String(deployPlatform).trim() : "",
};
} catch (e) {
console.error("❌ 获取触发记录信息失败: " + e.message);
return null;
}
}
// ========================================================================
// 第四部分: 防重复检查
// ========================================================================
function checkExistingRecords(triggerInfo) {
try {
const sheet = Application.Sheets.Item(TABLE_NAMES.middleware);
const view = sheet.Views(1);
const count = view.RecordRange.Count;
if (count === 0) return false;
for (let i = 1; i <= count; i++) {
let existingDeployId = readCell(view, i, "所属部署");
if (existingDeployId && String(existingDeployId).trim() === triggerInfo.deployId) {
console.warn("⚠️ 中间件表第 " + i + " 行已存在部署编号 [" + triggerInfo.deployId + "] 的记录");
return true;
}
}
return false;
} catch (e) {
console.warn("⚠️ 检查已有记录时出错: " + e.message);
return false;
}
}
// ========================================================================
// 第五部分: 创建记录逻辑
// ========================================================================
/**
* 判定部署平台类型 — 仅依据「部署平台」字段
* @returns {string} "fly" | "supervisor" | "both" | "error"
*/
function detectPlatformType(triggerInfo) {
let platform = triggerInfo.deployPlatform;
console.log(" 🔍 部署平台原始值: [" + platform + "]");
if (platform) {
// 同时包含飞服和监管 → both
if (platform.indexOf("飞服") >= 0 && platform.indexOf("监管") >= 0) {
console.log(" 🔍 匹配结果: both (同时包含飞服和监管)");
return "both";
}
// 仅包含飞服 → fly
if (platform.indexOf("飞服") >= 0 || platform.indexOf("飞行服务") >= 0) {
console.log(" 🔍 匹配结果: fly (飞行服务平台)");
return "fly";
}
// 仅包含监管 → supervisor
if (platform.indexOf("监管") >= 0) {
console.log(" 🔍 匹配结果: supervisor (监管平台)");
return "supervisor";
}
// 部署平台有值但不包含已知关键词
console.error("❌ 部署平台值 [" + platform + "] 无法识别");
console.error(' 已知值: 包含"飞服"→飞服平台, 包含"监管"→监管平台');
return "error";
}
// 部署平台字段为空
console.error("❌ 「部署平台」字段为空!请先填写部署平台");
console.error(" 飞服平台 → 只创建飞服微服务");
console.error(" 监管平台 → 只创建监管微服务");
return "error";
}
/**
* 构建 Link 关联字段值
* 所有子表的「所属部署」字段都应关联回部署状态表的当前行
*/
function buildLinkFieldValues(triggerInfo) {
if (!triggerInfo.recordId) {
console.warn(" ⚠️ 无法获取 recordIdLink 关联字段将无法自动设置");
return {};
}
// Link 字段值格式: [recordId] (数组)
return {
"所属部署": [triggerInfo.recordId],
};
}
/**
* 创建中间件信息记录
*/
function createMiddlewareRecords(triggerInfo) {
console.log("\n📦 创建中间件信息记录...");
try {
let linkValues = buildLinkFieldValues(triggerInfo);
let records = [];
for (let i = 0; i < MIDDLEWARE_LIST.length; i++) {
let mw = MIDDLEWARE_LIST[i];
records.push({
"项目名称": triggerInfo.projectName, // ★ 新增: 填写项目名称
"所属部署": triggerInfo.deployId, // 文本备份 (如果 Link 不生效则有文本)
"中间件类型": mw.type,
"中间件版本": mw.version,
"部署方式": mw.deployMethod,
"是否暴露公网": false,
"公网端口": mw.defaultPublicPort,
"内网端口": mw.defaultPrivatePort,
"内网IP": mw.defaultPrivateIP,
});
}
let success = createRecordsSafely(TABLE_NAMES.middleware, records, linkValues);
if (success) {
console.log("📦 中间件信息创建完成,共 " + MIDDLEWARE_LIST.length + " 条");
}
} catch (e) {
console.error("❌ 创建中间件记录失败: " + e.message);
}
}
/**
* 创建业务信息记录 — 严格按部署平台区分
*/
function createBusinessRecords(triggerInfo) {
console.log("\n🔧 创建业务信息记录...");
try {
let platformType = detectPlatformType(triggerInfo);
console.log(" 📌 最终部署平台类型: " + platformType);
// ★ 修复: 无法判定时终止,不再默认创建全部
if (platformType === "error") {
console.error("❌ 部署平台类型无法判定,跳过业务信息创建");
console.error(" 请确保「部署平台」字段已正确填写(飞服平台/监管平台)");
return;
}
let linkValues = buildLinkFieldValues(triggerInfo);
// 构建微服务列表: 公共服务 + 对应平台服务
let serviceList = [];
// 始终包含公共服务
let commonServices = BUSINESS_SERVICES_MAP["common"] || [];
for (let i = 0; i < commonServices.length; i++) {
serviceList.push(commonServices[i]);
}
// ★ 修复: 严格按平台添加专属服务
if (platformType === "fly" || platformType === "both") {
let flyServices = BUSINESS_SERVICES_MAP["fly"] || [];
for (let i = 0; i < flyServices.length; i++) {
serviceList.push(flyServices[i]);
}
console.log(" ✅ 添加飞服平台微服务: " + (BUSINESS_SERVICES_MAP["fly"] || []).length + " 条");
}
if (platformType === "supervisor" || platformType === "both") {
let supServices = BUSINESS_SERVICES_MAP["supervisor"] || [];
for (let i = 0; i < supServices.length; i++) {
serviceList.push(supServices[i]);
}
console.log(" ✅ 添加监管平台微服务: " + (BUSINESS_SERVICES_MAP["supervisor"] || []).length + " 条");
}
if (serviceList.length === 0) {
console.warn("⚠️ 没有匹配的微服务,跳过业务信息创建");
return;
}
// 构建记录
let records = [];
for (let i = 0; i < serviceList.length; i++) {
let svc = serviceList[i];
records.push({
"项目名称": triggerInfo.projectName, // ★ 新增: 填写项目名称
"所属部署": triggerInfo.deployId, // 文本备份
"微服务名称": svc.name,
"微服务分支": svc.branch,
"微服务镜像": svc.image,
"微服务版本": svc.version,
"部署状态": "待部署",
});
}
let success = createRecordsSafely(TABLE_NAMES.business, records, linkValues);
if (success) {
console.log("🔧 业务信息创建完成,共 " + serviceList.length + " 条");
console.log(" → 公共服务: " + commonServices.length + " 条");
if (platformType === "fly") {
console.log(" → 飞服平台: " + (BUSINESS_SERVICES_MAP["fly"] || []).length + " 条");
console.log(" → 监管平台: 不创建 (当前为飞服平台部署)");
} else if (platformType === "supervisor") {
console.log(" → 飞服平台: 不创建 (当前为监管平台部署)");
console.log(" → 监管平台: " + (BUSINESS_SERVICES_MAP["supervisor"] || []).length + " 条");
} else {
console.log(" → 飞服平台: " + (BUSINESS_SERVICES_MAP["fly"] || []).length + " 条");
console.log(" → 监管平台: " + (BUSINESS_SERVICES_MAP["supervisor"] || []).length + " 条");
}
}
} catch (e) {
console.error("❌ 创建业务记录失败: " + e.message);
}
}
/**
* 创建网络信息记录
*/
function createNetworkRecords(triggerInfo) {
console.log("\n🌐 创建网络信息记录...");
try {
let linkValues = buildLinkFieldValues(triggerInfo);
let records = [{
"项目名称": triggerInfo.projectName, // ★ 新增: 填写项目名称
"所属部署": triggerInfo.deployId, // 文本备份
"是否开启SSL": false,
"端口信息说明": "待填写",
"备注": "自动创建 — 部署编号: " + triggerInfo.deployId + " | 项目: " + triggerInfo.projectName,
}];
let success = createRecordsSafely(TABLE_NAMES.network, records, linkValues);
if (success) {
console.log("🌐 网络信息创建完成,共 1 条");
}
} catch (e) {
console.error("❌ 创建网络记录失败: " + e.message);
}
}
// ========================================================================
// 第六部分: 主函数 — 入口
// ========================================================================
function main() {
console.log("====================================");
console.log("🚀 按钮触发: 自动创建部署子表记录 v3");
console.log("====================================");
// Step 1: 获取触发记录信息 (recordId 来自按钮 p1 参数)
let triggerInfo = getTriggerRecordInfo();
if (!triggerInfo) {
console.error("❌ 无法获取触发记录信息,脚本终止");
return;
}
console.log("\n✅ 触发记录信息:");
console.log(" 行号: " + triggerInfo.row);
console.log(" recordId: " + (triggerInfo.recordId || "未获取到"));
console.log(" 部署编号: " + triggerInfo.deployId);
console.log(" 项目名称: " + triggerInfo.projectName);
console.log(" 部署负责人: " + triggerInfo.deployer);
console.log(" 部署状态: " + triggerInfo.status);
console.log(" 命名空间: " + triggerInfo.namespace);
console.log(" 部署平台: " + triggerInfo.deployPlatform);
// Link 关联提示
if (triggerInfo.recordId) {
console.log("\n🔗 Link 关联: 已获取到 recordId将自动设置所属部署关联");
} else {
console.warn("\n⚠ Link 关联: 未获取到 recordId所属部署关联需要手动设置");
}
// Step 2: 防重复检查
if (checkExistingRecords(triggerInfo)) {
console.warn("\n⚠ 中间件表中已存在部署编号 [" + triggerInfo.deployId + "] 的记录!");
console.warn(" 为防止重复创建,脚本终止。");
console.warn(" 如需重新创建,请先手动删除已有记录。");
return;
}
// Step 3: 创建中间件信息记录
createMiddlewareRecords(triggerInfo);
// Step 4: 创建业务信息记录 (严格按部署平台区分)
createBusinessRecords(triggerInfo);
// Step 5: 创建网络信息记录
createNetworkRecords(triggerInfo);
console.log("\n====================================");
console.log("✅ 所有子表记录创建完成!");
console.log("====================================");
console.log("📌 提示:");
if (triggerInfo.recordId) {
console.log(" ✅ 所属部署 Link 关联已自动设置");
} else {
console.log(" ⚠️ 所属部署 Link 关联需要手动设置 (recordId 未获取)");
}
console.log(" ✅ 项目名称已自动填写: " + triggerInfo.projectName);
console.log(" 📝 请检查中间件的内网IP和公网端口是否需要修改");
console.log(" 📝 业务微服务的分支和镜像信息需要后续补充");
}
// 执行入口
main();

View File

@@ -0,0 +1,468 @@
/**
* ============================================================
* 金山多维表格 AirScript 一键建表脚本
* ============================================================
*
* 📌 使用方法:
* 1. 打开你的金山多维表格文档
* 2. 点击顶部菜单「效率」→「AirScript 脚本编辑器」
* 3. 新建脚本,将本文件的全部内容粘贴进去
* 4. 点击「运行」按钮
*
* 📌 无需任何额外配置:
* - 不需要申请企业应用
* - 不需要 APP_ID / APP_SECRET
* - 不需要安装 Node.js
* - 直接在多维表格界面中运行即可
*
* 📌 参考文档:
* - claude-dds.md (7张表的完善字段设计)
* - gemini-dds.md (核心锚表架构设计)
* - AirScript 文档: https://airsheet.wps.cn/docs/api/dbsheet/Sheet.html
*
* 📌 脚本执行流程:
* Phase 1: 创建 7 张空表(含基础字段) → 获取所有 SheetId
* Phase 2: 使用 SheetId 创建关联字段(Link) → 自动建立表间关系
* Phase 3: 输出执行报告 + 提醒人工配置项
*/
// ============================================================
// Phase 1: 创建所有数据表
// ============================================================
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 项目部署管理系统 — AirScript 一键建表 ║");
console.log("╚══════════════════════════════════════════════════════╝\n");
// ---- 表1: 项目基本信息表 ----
console.log("📋 [1/7] 创建: 项目基本信息表...");
var sheet1 = Application.Sheet.CreateSheet({
Name: '项目基本信息表',
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: [
{ name: '项目名称', type: 'MultiLineText' },
{ name: '行业组接口人', type: 'MultiLineText' },
{ name: '行业组接口人电话', type: 'Phone' },
{
name: '省份', type: 'SingleSelect',
items: [
{ value: '北京' }, { value: '天津' }, { value: '河北' },
{ value: '山西' }, { value: '内蒙古' }, { value: '辽宁' },
{ value: '吉林' }, { value: '黑龙江' }, { value: '上海' },
{ value: '江苏' }, { value: '浙江' }, { value: '安徽' },
{ value: '福建' }, { value: '江西' }, { value: '山东' },
{ value: '河南' }, { value: '湖北' }, { value: '湖南' },
{ value: '广东' }, { value: '广西' }, { value: '海南' },
{ value: '重庆' }, { value: '四川' }, { value: '贵州' },
{ value: '云南' }, { value: '西藏' }, { value: '陕西' },
{ value: '甘肃' }, { value: '青海' }, { value: '宁夏' },
{ value: '新疆' }
]
},
{ name: '城市', type: 'MultiLineText' },
{
name: '项目类型', type: 'SingleSelect',
items: [
{ value: '新部署' }, { value: '升级' },
{ value: '迁移' }, { value: '扩容' }
]
},
{
name: '部署优先级', type: 'SingleSelect',
items: [
{ value: 'P0-紧急' }, { value: 'P1-高' },
{ value: 'P2-中' }, { value: 'P3-低' }
]
},
{ name: '部署飞服平台', type: 'Checkbox' },
{ name: '飞服平台版本', type: 'MultiLineText' },
{ name: '部署监管平台', type: 'Checkbox' },
{ name: '监管平台版本', type: 'MultiLineText' },
{ name: '期望部署日期', type: 'Date' },
{ name: '部署必要条件说明', type: 'MultiLineText' },
{
name: '必要条件是否满足', type: 'SingleSelect',
items: [
{ value: '是' }, { value: '否' }, { value: '部分满足' }
]
},
{ name: '条件不满足说明', type: 'MultiLineText' },
{ name: '部署资源信息', type: 'Attachment' },
{ name: '特殊要求备注', type: 'MultiLineText' }
]
});
var sheetId1 = sheet1.id;
console.log("✅ 项目基本信息表 创建成功, SheetId = " + sheetId1);
// ---- 表2: 项目本地化部署升级信息表 ----
console.log("\n📋 [2/7] 创建: 项目本地化部署升级信息表...");
var sheet2 = Application.Sheet.CreateSheet({
Name: '项目本地化部署升级信息表',
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: [
// 项目名称字段先用文本Phase 2 阶段替换为 Link
{ name: '项目名称(待关联)', type: 'MultiLineText' },
{
name: '升级类型', type: 'SingleSelect',
items: [
{ value: '全量升级' }, { value: '增量升级' },
{ value: '热修复' }, { value: '回滚' }
]
},
{ name: '当前版本(飞服)', type: 'MultiLineText' },
{ name: '目标版本(飞服)', type: 'MultiLineText' },
{ name: '当前版本(监管)', type: 'MultiLineText' },
{ name: '目标版本(监管)', type: 'MultiLineText' },
{ name: '升级影响评估', type: 'MultiLineText' },
{ name: '回滚方案', type: 'MultiLineText' },
{ name: '期望升级开始时间', type: 'Date' },
{ name: '期望升级结束时间', type: 'Date' },
{ name: '部署资源信息', type: 'Attachment' }
]
});
var sheetId2 = sheet2.id;
console.log("✅ 升级信息表 创建成功, SheetId = " + sheetId2);
// ---- 表3: 项目本地化部署状态表 (核心锚表) ----
console.log("\n📋 [3/7] 创建: 项目本地化部署状态表 (核心锚表)...");
var sheet3 = Application.Sheet.CreateSheet({
Name: '项目本地化部署状态表',
Views: [{ name: '全部部署列表', type: 'Grid' }],
Fields: [
{ name: '部署编号', type: 'AutoNumber' },
{
name: '来源类型', type: 'SingleSelect',
items: [
{ value: '首次部署' }, { value: '升级部署' }
]
},
// 关联项目 / 关联升级记录 → Phase 2 创建 Link 字段
{
name: '部署状态', type: 'SingleSelect',
items: [
{ value: '待排期' }, { value: '已排期' },
{ value: '环境准备中' }, { value: '部署中' },
{ value: '验证中' }, { value: '已完成' },
{ value: '部署异常' }, { value: '已暂停' },
{ value: '已取消' }
]
},
{
name: '部署结果', type: 'SingleSelect',
items: [
{ value: '成功' }, { value: '失败' }, { value: '部分成功' }
]
},
{
name: '部署优先级', type: 'SingleSelect',
items: [
{ value: 'P0-紧急' }, { value: 'P1-高' },
{ value: 'P2-中' }, { value: 'P3-低' }
]
},
{ name: '当前进度/卡点说明', type: 'MultiLineText' },
{ name: '计划开始时间', type: 'Date' },
{ name: '计划结束时间', type: 'Date' },
{ name: '实际开始时间', type: 'Date' },
{ name: '实际结束时间', type: 'Date' },
{ name: '命名空间', type: 'MultiLineText' },
{ name: '部署负责人', type: 'Contact', multipleContacts: false, noticeNewContact: false },
{ name: '部署人电话', type: 'Phone' },
{ name: '备注', type: 'MultiLineText' }
]
});
var sheetId3 = sheet3.id;
console.log("✅ 部署状态表 创建成功, SheetId = " + sheetId3);
// ---- 表4: 项目部署环境信息表 ----
console.log("\n📋 [4/7] 创建: 项目部署环境信息表...");
var sheet4 = Application.Sheet.CreateSheet({
Name: '项目部署环境信息表',
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: [
// 所属部署 → Phase 2 创建 Link 字段
{ name: '公网IP', type: 'MultiLineText' },
{ name: '能否访问公网', type: 'Checkbox' },
{ name: '内网IP', type: 'MultiLineText' },
{ name: 'SSH端口', type: 'Number' },
{ name: 'SSH用户名', type: 'MultiLineText' },
{
name: '密码存储方式', type: 'SingleSelect',
items: [
{ value: '密码管理器' }, { value: '加密文档' }, { value: '其他' }
]
},
{
name: '主机功能', type: 'SingleSelect',
items: [
{ value: 'master' }, { value: 'worker' },
{ value: 'storage' }, { value: 'doris' }
]
},
{
name: 'CPU架构', type: 'SingleSelect',
items: [{ value: 'amd64' }, { value: 'arm64' }]
},
{
name: '主机状态', type: 'SingleSelect',
items: [
{ value: '待交付' }, { value: '已交付' },
{ value: '已初始化' }, { value: '运行中' }, { value: '异常' }
]
},
{
name: '操作系统', type: 'SingleSelect',
items: [
{ value: 'CentOS 7' }, { value: 'CentOS 8' },
{ value: 'Ubuntu 20.04' }, { value: 'Ubuntu 22.04' },
{ value: '其他' }
]
},
{ name: 'CPU核心数', type: 'Number' },
{ name: 'CPU型号', type: 'MultiLineText' },
{ name: '内存大小(GB)', type: 'Number' },
{ name: '系统盘大小(GB)', type: 'Number' },
{ name: '数据盘大小(GB)', type: 'Number' },
{ name: 'GPU信息', type: 'MultiLineText' },
{ name: '备注信息', type: 'MultiLineText' }
]
});
var sheetId4 = sheet4.id;
console.log("✅ 环境信息表 创建成功, SheetId = " + sheetId4);
// ---- 表5: 项目部署网络信息表 ----
console.log("\n📋 [5/7] 创建: 项目部署网络信息表...");
var sheet5 = Application.Sheet.CreateSheet({
Name: '项目部署网络信息表',
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: [
// 所属部署 → Phase 2 创建 Link 字段
{ name: '访问地址', type: 'Url' },
{ name: '是否开启SSL', type: 'Checkbox' },
{ name: '端口信息说明', type: 'MultiLineText' },
{
name: '网络环境', type: 'SingleSelect',
items: [
{ value: '完全内网' }, { value: '单主机公网' }, { value: '全访问公网' }
]
},
{
name: '主机管理方式', type: 'SingleSelect',
items: [
{ value: '堡垒机' }, { value: '跳板机' },
{ value: '直接访问' }, { value: 'VPN' }
]
},
{ name: '管理后台地址', type: 'Url' },
{ name: 'VPN下载地址', type: 'Url' },
{ name: 'VPN账号信息存储位置', type: 'MultiLineText' },
{ name: '备注', type: 'MultiLineText' }
]
});
var sheetId5 = sheet5.id;
console.log("✅ 网络信息表 创建成功, SheetId = " + sheetId5);
// ---- 表6: 项目部署中间件信息表 ----
console.log("\n📋 [6/7] 创建: 项目部署中间件信息表...");
var sheet6 = Application.Sheet.CreateSheet({
Name: '项目部署中间件信息表',
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: [
// 所属部署 → Phase 2 创建 Link 字段
{
name: '中间件类型', type: 'SingleSelect',
items: [
{ value: 'MySQL' }, { value: 'Redis' },
{ value: 'RabbitMQ' }, { value: 'EMQX' },
{ value: 'NACOS' }, { value: 'K8S Dashboard' },
{ value: 'MINIO' }
]
},
{ name: '中间件版本', type: 'MultiLineText' },
{
name: '部署方式', type: 'SingleSelect',
items: [
{ value: '容器化' }, { value: '主机部署' }, { value: '云服务' }
]
},
{ name: '是否暴露公网', type: 'Checkbox' },
{ name: '公网端口', type: 'MultiLineText' },
{ name: '内网IP', type: 'MultiLineText' },
{ name: '内网端口', type: 'MultiLineText' },
{ name: '用户名', type: 'MultiLineText' },
{ name: '密码存储位置', type: 'MultiLineText' },
{ name: '管理后台地址', type: 'Url' }
]
});
var sheetId6 = sheet6.id;
console.log("✅ 中间件信息表 创建成功, SheetId = " + sheetId6);
// ---- 表7: 项目部署业务信息表 ----
console.log("\n📋 [7/7] 创建: 项目部署业务信息表...");
var sheet7 = Application.Sheet.CreateSheet({
Name: '项目部署业务信息表',
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: [
// 所属部署 → Phase 2 创建 Link 字段
{ name: '微服务名称', type: 'MultiLineText' },
{ name: '微服务分支', type: 'MultiLineText' },
{ name: '微服务镜像', type: 'MultiLineText' },
{ name: '微服务版本', type: 'MultiLineText' },
{
name: '部署状态', type: 'SingleSelect',
items: [
{ value: '待部署' }, { value: '部署中' },
{ value: '运行中' }, { value: '异常' }, { value: '已下线' }
]
}
]
});
var sheetId7 = sheet7.id;
console.log("✅ 业务信息表 创建成功, SheetId = " + sheetId7);
// ============================================================
// Phase 2: 创建关联字段 (Link)
// ============================================================
console.log("\n" + "=".repeat(60));
console.log("🔗 Phase 2: 建立表间关联关系...");
console.log("=".repeat(60));
// 升级信息表 → 关联 → 项目基本信息表
console.log(" 🔗 升级信息表.关联项目 → 项目基本信息表");
Application.Field.CreateFields({
SheetId: sheetId2,
Fields: [
{ name: '关联项目', type: 'Link', linkSheet: sheetId1, multipleLinks: false }
]
});
// 部署状态表 → 关联 → 项目基本信息表
console.log(" 🔗 部署状态表.关联项目 → 项目基本信息表");
Application.Field.CreateFields({
SheetId: sheetId3,
Fields: [
{ name: '关联项目', type: 'Link', linkSheet: sheetId1, multipleLinks: false }
]
});
// 部署状态表 → 关联 → 升级信息表
console.log(" 🔗 部署状态表.关联升级记录 → 升级信息表");
Application.Field.CreateFields({
SheetId: sheetId3,
Fields: [
{ name: '关联升级记录', type: 'Link', linkSheet: sheetId2, multipleLinks: false }
]
});
// 环境信息表 → 关联 → 部署状态表
console.log(" 🔗 环境信息表.所属部署 → 部署状态表");
Application.Field.CreateFields({
SheetId: sheetId4,
Fields: [
{ name: '所属部署', type: 'Link', linkSheet: sheetId3, multipleLinks: false }
]
});
// 网络信息表 → 关联 → 部署状态表
console.log(" 🔗 网络信息表.所属部署 → 部署状态表");
Application.Field.CreateFields({
SheetId: sheetId5,
Fields: [
{ name: '所属部署', type: 'Link', linkSheet: sheetId3, multipleLinks: false }
]
});
// 中间件信息表 → 关联 → 部署状态表
console.log(" 🔗 中间件信息表.所属部署 → 部署状态表");
Application.Field.CreateFields({
SheetId: sheetId6,
Fields: [
{ name: '所属部署', type: 'Link', linkSheet: sheetId3, multipleLinks: false }
]
});
// 业务信息表 → 关联 → 部署状态表
console.log(" 🔗 业务信息表.所属部署 → 部署状态表");
Application.Field.CreateFields({
SheetId: sheetId7,
Fields: [
{ name: '所属部署', type: 'Link', linkSheet: sheetId3, multipleLinks: false }
]
});
console.log("\n✅ 所有关联字段创建完成!");
// ============================================================
// Phase 3: 输出执行报告
// ============================================================
console.log("\n\n");
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 📊 执行结果报告 ║");
console.log("╚══════════════════════════════════════════════════════╝");
console.log("\n📋 已创建的数据表:");
console.log(" 1. 项目基本信息表 (SheetId: " + sheetId1 + ")");
console.log(" 2. 项目本地化部署升级信息表 (SheetId: " + sheetId2 + ")");
console.log(" 3. 项目本地化部署状态表 (SheetId: " + sheetId3 + ") ← 核心锚表");
console.log(" 4. 项目部署环境信息表 (SheetId: " + sheetId4 + ")");
console.log(" 5. 项目部署网络信息表 (SheetId: " + sheetId5 + ")");
console.log(" 6. 项目部署中间件信息表 (SheetId: " + sheetId6 + ")");
console.log(" 7. 项目部署业务信息表 (SheetId: " + sheetId7 + ")");
console.log("\n🔗 已建立的关联关系:");
console.log(" ✅ 升级信息表 → 项目基本信息表");
console.log(" ✅ 部署状态表 → 项目基本信息表");
console.log(" ✅ 部署状态表 → 升级信息表");
console.log(" ✅ 环境信息表 → 部署状态表");
console.log(" ✅ 网络信息表 → 部署状态表");
console.log(" ✅ 中间件信息表 → 部署状态表");
console.log(" ✅ 业务信息表 → 部署状态表");
console.log("\n\n");
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 🔧 人工操作清单 (还需要做) ║");
console.log("╚══════════════════════════════════════════════════════╝");
console.log(`
以下操作需要在多维表格界面中手动完成:
🟡 P1 — 创建视图
在「部署状态表」上创建:
□ 甘特视图 → 开始=计划开始时间, 结束=计划结束时间
□ 日历视图 → 日期=计划开始时间
□ 看板视图 → 按「部署状态」分列
□ 表单视图 → 行业组提交入口
🟡 P1 — 配置权限
□ 行业组人员: 仅表单提交 + 只读查看状态
□ 特战队: 读写核心表及子表
□ 小组长: 全部读写
🟡 P1 — 配置自动化规则
□ 新建项目 → 自动创建部署状态记录
□ 新建升级 → 自动创建部署状态记录
□ 状态→部署中 → 自动记录开始时间
□ 状态→已完成 → 自动记录结束时间
□ 状态→异常 → 发送通知
🟢 P2 — 创建仪表盘
□ 部署状态分布饼图
□ 本月任务数柱状图
□ 统计卡片
🟢 P2 — 安全配置
□ 敏感字段(IP等)设置字段级权限
□ 密码不存入表格,仅记录存储位置
`);
console.log("🎉 脚本执行完成! 共创建 7 张表 + 7 个关联关系");

View File

@@ -0,0 +1,56 @@
/**
* ============================================================
* AirScript: 判定部署类型
* ============================================================
*
* 自动化流程入参:
* - isDeployFlyControl: 单选项 (是 / 否) — 是否部署飞服平台
* - isDeploySupervisor: 单选项 (是 / 否) — 是否部署监管平台
*
* 返回值 (JSON):
* { "code": "<类型>" }
*
* 判定规则:
* 两个都为「是」 → "both"
* 仅飞服为「是」 → "fly-center"
* 仅监管为「是」 → "supervisor"
* 两个都为「否」 → "error"
*
* ============================================================
*/
function main() {
// 获取入参
let isDeployFlyControl = "";
let isDeploySupervisor = "";
if (typeof Context !== "undefined" && Context.argv) {
isDeployFlyControl = String(Context.argv.isDeployFlyControl || "").trim();
isDeploySupervisor = String(Context.argv.isDeploySupervisor || "").trim();
}
console.log("入参 isDeployFlyControl: " + isDeployFlyControl);
console.log("入参 isDeploySupervisor: " + isDeploySupervisor);
// 判定类型
let flyYes = (isDeployFlyControl === "是");
let supYes = (isDeploySupervisor === "是");
let type = "error";
if (flyYes && supYes) {
type = "both";
} else if (flyYes) {
type = "fly-center";
} else if (supYes) {
type = "supervisor";
}
// 输出结果
let result = { code: type };
console.log(JSON.stringify(result));
return result;
}
main();

View File

@@ -0,0 +1,329 @@
/**
* ============================================================
* 金山多维表格 AirScript — 导出表结构到数据表 (v3)
* ============================================================
*
* 📌 使用方法:
* 1. 打开你的金山多维表格文档
* 2. 点击「脚本」→「JS脚本」→「新建脚本」
* 3. 将本文件的全部内容粘贴进去
* 4. 点击「运行」按钮
* 5. 查看新生成的「数据导出-tmp」表表结构数据全部在里面
*
* 📌 功能说明:
* - 遍历指定的 7 张表,获取所有字段信息
* - 自动创建「数据导出-tmp」表如已存在会先删除再重建
* - 将每个字段作为一行写入导出表,包含:
* 表名、SheetId、字段序号、字段名称、字段类型、字段ID
* - 在控制台输出进度信息
*
* 📌 v3 修复:
* - 使用 FieldDescriptors.Count 和 .Item(i) 替代 .length 和 [i]
* - 增加 Application.Field.GetFields() 作为备选
* - 结果写入数据表而非仅输出到控制台
*/
// ============================================================
// 配置区
// ============================================================
var TARGET_TABLES = [
'项目基本信息表',
'项目本地化部署升级信息表',
'项目本地化部署状态表',
'项目部署环境信息表',
'项目部署网络信息表',
'项目部署中间件信息表',
'项目部署业务信息表'
];
var FILTER_BY_NAME = true;
var EXPORT_TABLE_NAME = '数据导出-tmp';
// ============================================================
// Step 1: 收集所有表的字段信息
// ============================================================
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 金山多维表格 — 表结构导出到数据表 (v3) ║");
console.log("╚══════════════════════════════════════════════════════╝\n");
var sheets = Application.Sheets;
var totalCount = sheets.Count;
console.log("📊 当前文档共有 " + totalCount + " 张数据表\n");
// 收集所有字段行数据: [{tableName, sheetId, fieldIndex, fieldName, fieldType, fieldId}]
var allRows = [];
for (var i = 1; i <= totalCount; i++) {
var sheet = sheets.Item(i);
var sheetName = sheet.Name;
var sheetId = sheet.Id;
// 跳过导出表本身
if (sheetName === EXPORT_TABLE_NAME) {
console.log("⏭️ 跳过导出表: " + sheetName);
continue;
}
// 按名称过滤
if (FILTER_BY_NAME && TARGET_TABLES.length > 0) {
var found = false;
for (var t = 0; t < TARGET_TABLES.length; t++) {
if (TARGET_TABLES[t] === sheetName) {
found = true;
break;
}
}
if (!found) {
continue;
}
}
console.log("📋 正在读取: " + sheetName + " (SheetId: " + sheetId + ")");
var fields = [];
// --- 方案 A: Application.Field.GetFields() ---
try {
var getFieldsResult = Application.Field.GetFields({ SheetId: sheetId });
if (getFieldsResult) {
// 可能是数组,也可能是 {fields: [...]} 结构
var fieldArr = null;
if (Array.isArray(getFieldsResult) && getFieldsResult.length > 0) {
fieldArr = getFieldsResult;
} else if (getFieldsResult.fields && Array.isArray(getFieldsResult.fields)) {
fieldArr = getFieldsResult.fields;
} else if (getFieldsResult.Fields && Array.isArray(getFieldsResult.Fields)) {
fieldArr = getFieldsResult.Fields;
}
if (fieldArr && fieldArr.length > 0) {
for (var g = 0; g < fieldArr.length; g++) {
var gf = fieldArr[g];
fields.push({
name: gf.name || gf.Name || "未知",
type: gf.type || gf.Type || "未知",
id: gf.id || gf.Id || ""
});
}
console.log(" ✅ GetFields 获取到 " + fields.length + " 个字段");
}
}
} catch (e) {
console.log(" ⚠️ GetFields 异常: " + e.message);
}
// --- 方案 B: FieldDescriptors ---
if (fields.length === 0) {
try {
var descriptors = sheet.FieldDescriptors;
var fdCount = descriptors.Count;
if (fdCount && fdCount > 0) {
for (var j = 1; j <= fdCount; j++) {
var fd = descriptors.Item(j);
fields.push({
name: fd.name || "未知",
type: fd.type || "未知",
id: ""
});
}
console.log(" ✅ FieldDescriptors 获取到 " + fields.length + " 个字段");
}
} catch (e2) {
console.log(" ⚠️ FieldDescriptors 异常: " + e2.message);
}
}
// --- 方案 C: Views.Fields 兜底 ---
if (fields.length === 0) {
try {
var view = sheet.Views(1);
if (view && view.Fields) {
var vfObj = view.Fields;
var vfCount = vfObj.Count || vfObj.length || 0;
if (vfCount > 0) {
for (var v = 1; v <= vfCount; v++) {
try {
var vf = vfObj.Item ? vfObj.Item(v) : vfObj[v - 1];
if (vf) {
fields.push({
name: vf.Name || vf.name || "未知",
type: vf.Type || vf.type || "未知",
id: vf.Id || vf.id || ""
});
}
} catch (e3) {
// skip
}
}
console.log(" ✅ Views.Fields 获取到 " + fields.length + " 个字段");
}
}
} catch (e4) {
console.log(" ⚠️ Views 异常: " + e4.message);
}
}
if (fields.length === 0) {
console.log(" ❌ 未获取到字段");
// 仍然写一行标记
allRows.push({
tableName: sheetName,
sheetId: String(sheetId),
fieldIndex: "0",
fieldName: "(无法获取字段)",
fieldType: "-",
fieldId: ""
});
} else {
for (var fi = 0; fi < fields.length; fi++) {
allRows.push({
tableName: sheetName,
sheetId: String(sheetId),
fieldIndex: String(fi + 1),
fieldName: fields[fi].name,
fieldType: fields[fi].type,
fieldId: String(fields[fi].id || "")
});
}
}
}
console.log("\n📊 共收集到 " + allRows.length + " 条字段记录\n");
// ============================================================
// Step 2: 删除已有的导出表(如果存在)
// ============================================================
try {
var existingExportSheet = null;
for (var d = 1; d <= Application.Sheets.Count; d++) {
if (Application.Sheets.Item(d).Name === EXPORT_TABLE_NAME) {
existingExportSheet = Application.Sheets.Item(d);
break;
}
}
if (existingExportSheet) {
console.log("🗑️ 删除已存在的「" + EXPORT_TABLE_NAME + "」表...");
existingExportSheet.Delete();
console.log(" ✅ 已删除");
}
} catch (e5) {
console.log("⚠️ 删除旧表异常(可忽略): " + e5.message);
}
// ============================================================
// Step 3: 创建新的导出表
// ============================================================
console.log("📝 创建「" + EXPORT_TABLE_NAME + "」表...");
// 使用 CreateSheet 创建表(返回 {id}
var createResult = Application.Sheet.CreateSheet({
Name: EXPORT_TABLE_NAME,
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: [
{ name: '所属表名', type: 'MultiLineText' },
{ name: 'SheetId', type: 'MultiLineText' },
{ name: '字段序号', type: 'MultiLineText' },
{ name: '字段名称', type: 'MultiLineText' },
{ name: '字段类型', type: 'MultiLineText' },
{ name: '字段ID', type: 'MultiLineText' }
]
});
var newSheetId = createResult.id;
console.log(" ✅ 创建成功, SheetId: " + newSheetId);
// 等待一下让表注册完成
Time.sleep(500);
// 通过遍历 Sheets 集合找到新创建的表对象
var newSheet = null;
var refreshedSheets = Application.Sheets;
var refreshedCount = refreshedSheets.Count;
console.log(" 🔍 当前共 " + refreshedCount + " 张表,正在查找新建表...");
for (var si = 1; si <= refreshedCount; si++) {
var tmpSheet = refreshedSheets.Item(si);
if (tmpSheet.Name === EXPORT_TABLE_NAME) {
newSheet = tmpSheet;
console.log(" ✅ 找到「" + EXPORT_TABLE_NAME + "」(索引: " + si + ")\n");
break;
}
}
if (!newSheet) {
// 尝试直接用名称获取
try {
newSheet = Application.Sheets.Item(EXPORT_TABLE_NAME);
console.log(" ✅ 通过名称获取到表\n");
} catch (eFindSheet) {
console.log(" ❌ 无法找到新建的导出表: " + eFindSheet.message);
console.log(" 💡 请手动创建「" + EXPORT_TABLE_NAME + "」表后重试");
throw new Error("无法获取导出表对象");
}
}
// ============================================================
// Step 4: 将数据写入导出表
// ============================================================
console.log("📥 正在写入 " + allRows.length + " 条记录...");
var newView = newSheet.Views(1);
// 逐行写入数据
for (var r = 0; r < allRows.length; r++) {
var row = allRows[r];
// RecordRange(行号, 列号), 行号从1开始, 列号从1开始
// 第1行是已有的空行从第1行开始写
// 列顺序: 所属表名(1), SheetId(2), 字段序号(3), 字段名称(4), 字段类型(5), 字段ID(6)
var rowNum = r + 1;
try {
newView.RecordRange(rowNum, 1).Value = row.tableName;
newView.RecordRange(rowNum, 2).Value = row.sheetId;
newView.RecordRange(rowNum, 3).Value = row.fieldIndex;
newView.RecordRange(rowNum, 4).Value = row.fieldName;
newView.RecordRange(rowNum, 5).Value = row.fieldType;
newView.RecordRange(rowNum, 6).Value = row.fieldId;
} catch (writeErr) {
console.log(" ⚠️ 写入第 " + rowNum + " 行异常: " + writeErr.message);
}
// 每 10 条打印一次进度
if ((r + 1) % 10 === 0 || r === allRows.length - 1) {
console.log(" 📝 已写入 " + (r + 1) + " / " + allRows.length + " 条");
}
}
// ============================================================
// Step 5: 输出摘要
// ============================================================
console.log("\n");
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 📊 导出摘要 ║");
console.log("╚══════════════════════════════════════════════════════╝");
console.log(" 导出表名: " + EXPORT_TABLE_NAME);
console.log(" 总记录数: " + allRows.length + " 条");
// 按表名统计
var tableStats = {};
for (var s = 0; s < allRows.length; s++) {
var tn = allRows[s].tableName;
if (!tableStats[tn]) tableStats[tn] = 0;
tableStats[tn]++;
}
console.log("\n 各表字段数:");
var tableNames = Object.keys(tableStats);
for (var sn = 0; sn < tableNames.length; sn++) {
console.log(" " + (sn + 1) + ". " + tableNames[sn] + " → " + tableStats[tableNames[sn]] + " 个字段");
}
console.log("\n🎉 表结构已导出到「" + EXPORT_TABLE_NAME + "」表中!");
console.log("💡 请切换到该表查看完整数据,可直接复制或筛选使用。");

View File

@@ -0,0 +1,120 @@
/**
* ============================================================
* 修复「项目本地化部署升级信息表」的项目名称关联字段
* ============================================================
*
* 📌 使用方法:
* 1. 打开你的金山多维表格文档
* 2. 点击顶部菜单「脚本」→「JS脚本」→「新建脚本」
* 3. 将本文件内容粘贴进去
* 4. 点击「运行」
*
* 📌 脚本执行流程:
* Step 1: 检查升级信息表的所有字段 (诊断)
* Step 2: 获取项目基本信息表的 SheetId
* Step 3: 在升级信息表中创建 Link 关联字段
*
* 📌 注意:请先运行一次查看诊断结果,再决定是否取消注释 Step 3
*/
function main() {
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 修复升级信息表 — Link 关联字段 ║");
console.log("╚══════════════════════════════════════════════════════╝\n");
// ============================================================
// Step 1: 诊断 — 查看升级信息表当前所有字段
// ============================================================
console.log("📋 Step 1: 诊断升级信息表当前字段...\n");
var upgradeSheet = Application.Sheets.Item("项目本地化部署升级信息表");
var upgradeSheetId = upgradeSheet.Id;
console.log(" 表名: " + upgradeSheet.Name);
console.log(" SheetId: " + upgradeSheetId);
// 使用 GetFields 获取详细字段信息
var fields = Application.Field.GetFields({ SheetId: upgradeSheetId });
console.log(" 字段总数: " + fields.length);
console.log("\n ┌──────┬──────────────────────────┬─────────────────┐");
console.log(" │ 序号 │ 字段名称 │ 字段类型 │");
console.log(" ├──────┼──────────────────────────┼─────────────────┤");
var hasLinkField = false;
for (var i = 0; i < fields.length; i++) {
var f = fields[i];
var idx = String(i + 1).padStart(4, ' ');
var name = (f.name || '').padEnd(22, ' ');
var type = (f.type || '').padEnd(15, ' ');
console.log(" │ " + idx + " │ " + name + " │ " + type + " │");
if (f.type === 'Link') {
hasLinkField = true;
}
}
console.log(" └──────┴──────────────────────────┴─────────────────┘");
if (hasLinkField) {
console.log("\n ✅ 表中已存在 Link 类型字段!请往右滚动查看。");
console.log(" 如果你想让 Link 字段移到第一列,请在表格界面中手动拖动列位置。");
return;
}
console.log("\n ⚠️ 表中没有 Link 类型字段,需要创建。继续执行 Step 2 & 3...");
// ============================================================
// Step 2: 获取项目基本信息表的 SheetId
// ============================================================
console.log("\n📋 Step 2: 获取项目基本信息表 SheetId...");
var projectSheet = Application.Sheets.Item("项目基本信息表");
var projectSheetId = projectSheet.Id;
console.log(" 项目基本信息表 SheetId = " + projectSheetId);
// ============================================================
// Step 3: 在升级信息表中创建 Link 关联字段
// ============================================================
console.log("\n📋 Step 3: 创建 Link 关联字段...");
Application.Field.CreateFields({
SheetId: upgradeSheetId,
Fields: [
{
name: '关联项目',
type: 'Link',
linkSheet: projectSheetId,
multipleLinks: false
}
]
});
console.log(" ✅ 「关联项目」Link 字段创建成功!");
console.log(" → 关联目标: 项目基本信息表 (SheetId: " + projectSheetId + ")");
// ============================================================
// 验证
// ============================================================
console.log("\n📋 验证: 重新获取字段列表...");
var newFields = Application.Field.GetFields({ SheetId: upgradeSheetId });
for (var j = 0; j < newFields.length; j++) {
var nf = newFields[j];
if (nf.type === 'Link') {
console.log(" ✅ 找到 Link 字段: 「" + nf.name + "」 → 关联成功");
}
}
// ============================================================
// 完成提示
// ============================================================
console.log("\n\n╔══════════════════════════════════════════════════════╗");
console.log("║ ✅ 修复完成! ║");
console.log("╚══════════════════════════════════════════════════════╝");
console.log(`
后续操作:
1. 在表格界面中,你应该能看到新增的「关联项目」列 (可能在最右侧)
2. 可以将「关联项目」列拖动到第一列位置
3. 点击该列的单元格,下拉框会自动列出「项目基本信息表」中的所有项目
4. 原来的「项目名称(待关联)」文本字段可以手动删除(右键列头 → 删除字段)
`);
}
main();

View File

@@ -0,0 +1,256 @@
/**
* ============================================================
* 金山多维表格 API 客户端
* ============================================================
*
* 封装了与 WPS 开放平台多维表格 API 交互的核心方法:
* - 获取 access_token
* - 创建工作表 (Sheet)
* - 创建字段 (Field)
* - 插入记录 (Record)
*
* 依赖: node-fetch (或 Node.js 18+ 内置 fetch)
*/
const CONFIG = require("./config");
// ============================
// 工具函数
// ============================
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function log(msg) {
if (CONFIG.VERBOSE_LOG) {
console.log(`[${new Date().toISOString()}] ${msg}`);
}
}
function logError(msg, err) {
console.error(`[${new Date().toISOString()}] ❌ ${msg}`, err?.message || err);
}
// ============================
// Token 管理
// ============================
let cachedToken = null;
let tokenExpiry = 0;
/**
* 获取 access_token (自建应用获取租户的 access_token)
* POST https://openapi.wps.cn/oauth2/token
* Content-Type: application/x-www-form-urlencoded
* Body: grant_type=client_credentials&client_id=xxx&client_secret=xxx
*/
async function getAccessToken() {
if (cachedToken && Date.now() < tokenExpiry) {
return cachedToken;
}
log("正在获取 access_token...");
const body = new URLSearchParams({
grant_type: "client_credentials",
client_id: CONFIG.APP_ID,
client_secret: CONFIG.APP_SECRET,
});
const resp = await fetch(CONFIG.TOKEN_URL, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: body.toString(),
});
if (!resp.ok) {
const text = await resp.text();
throw new Error(`获取 token 失败: ${resp.status} ${text}`);
}
const data = await resp.json();
cachedToken = data.access_token;
// 提前 5 分钟刷新
tokenExpiry = Date.now() + (data.expires_in - 300) * 1000;
log(`access_token 获取成功,有效期 ${data.expires_in}`);
return cachedToken;
}
// ============================
// 通用请求方法
// ============================
/**
* 构建请求头 (KSO-1 签名方式)
* 注意:使用 access_token Bearer 认证
*/
async function buildHeaders() {
const token = await getAccessToken();
return {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
};
}
/**
* 发送 API 请求
*/
async function apiRequest(method, path, body = null) {
const url = `${CONFIG.BASE_URL}${CONFIG.API_PREFIX}/${CONFIG.FILE_ID}${path}`;
const headers = await buildHeaders();
const options = {
method,
headers,
};
if (body) {
options.body = JSON.stringify(body);
}
log(`${method} ${url}`);
const resp = await fetch(url, options);
if (!resp.ok) {
const text = await resp.text();
throw new Error(`API 请求失败: ${resp.status} ${text}`);
}
const data = await resp.json();
if (data.code && data.code !== 0) {
throw new Error(`API 返回错误: code=${data.code}, msg=${data.msg}`);
}
return data;
}
// ============================
// 工作表 (Sheet) 操作
// ============================
/**
* 创建工作表
* POST /v7/coop/dbsheet/{file_id}/sheets/create
*
* @param {string} name - 工作表名称
* @returns {object} - 返回包含 sheet_id 的响应
*/
async function createSheet(name) {
log(`📋 创建工作表: ${name}`);
const result = await apiRequest("POST", "/sheets/create", { name });
const sheetId = result?.data?.sheet_id || result?.sheet_id;
log(`✅ 工作表 "${name}" 创建成功, sheet_id = ${sheetId}`);
await sleep(CONFIG.API_DELAY_MS);
return result;
}
/**
* 获取文档 Schema (获取所有工作表及其字段信息)
* GET /v7/coop/dbsheet/{file_id}/schemas
*/
async function getSchemas() {
log("📄 获取文档 Schema...");
return await apiRequest("GET", "/schemas");
}
// ============================
// 字段 (Field) 操作
// ============================
/**
* 创建字段
* POST /v7/coop/dbsheet/{file_id}/sheets/{sheet_id}/fields
*
* @param {string} sheetId - 工作表 ID
* @param {object} fieldDef - 字段定义 { name, type, data? }
* @returns {object}
*
* 字段定义格式:
* {
* "name": "字段名称",
* "type": "SingleSelect", // 字段类型
* "data": { // 字段特有属性 (可选)
* "options": [{"name": "选项1", "color_index": 1}]
* }
* }
*/
async function createField(sheetId, fieldDef) {
log(` 📎 创建字段: ${fieldDef.name} (${fieldDef.type})`);
const result = await apiRequest(
"POST",
`/sheets/${sheetId}/fields`,
{ fields: [fieldDef] }
);
await sleep(CONFIG.API_DELAY_MS);
return result;
}
/**
* 批量创建字段
* @param {string} sheetId - 工作表 ID
* @param {Array} fieldDefs - 字段定义数组
*/
async function createFields(sheetId, fieldDefs) {
const results = [];
for (const fieldDef of fieldDefs) {
try {
const result = await createField(sheetId, fieldDef);
results.push({ success: true, field: fieldDef.name, result });
} catch (err) {
logError(`创建字段 "${fieldDef.name}" 失败`, err);
results.push({ success: false, field: fieldDef.name, error: err.message });
}
}
return results;
}
// ============================
// 记录 (Record) 操作
// ============================
/**
* 插入记录
* POST /v7/coop/dbsheet/{file_id}/sheets/{sheet_id}/records
*
* @param {string} sheetId - 工作表 ID
* @param {Array} records - 记录数组
*/
async function insertRecords(sheetId, records) {
log(` 📝 插入 ${records.length} 条记录...`);
const result = await apiRequest(
"POST",
`/sheets/${sheetId}/records`,
{ records }
);
await sleep(CONFIG.API_DELAY_MS);
return result;
}
// ============================
// 导出模块
// ============================
module.exports = {
getAccessToken,
getSchemas,
createSheet,
createField,
createFields,
insertRecords,
sleep,
log,
logError,
};

View File

@@ -0,0 +1,48 @@
/**
* ============================================================
* 金山多维表格 API 配置文件
* ============================================================
*
* 使用前请填写以下配置项:
* 1. APP_ID / APP_SECRET: 在 WPS 开放平台 (open.wps.cn) 创建企业自建应用后获取
* 2. FILE_ID: 多维表格文件ID从多维表格URL中获取
* - 例如 URL: https://www.kdocs.cn/p/xxxxxx 中的 xxxxxx
*
* 认证流程:
* 创建应用 → 申请API权限(kso.dbsheet.readwrite) → 获取访问凭证 → 调用API
*/
const CONFIG = {
// ==================== 必填配置 ====================
// WPS 开放平台应用凭证
APP_ID: "YOUR_APP_ID", // 替换为你的 App ID (client_id)
APP_SECRET: "YOUR_APP_SECRET", // 替换为你的 App Secret (client_secret)
// 多维表格文件 ID
FILE_ID: "YOUR_FILE_ID", // 替换为目标多维表格的 file_id
// ==================== API 基础配置 ====================
// API 基础地址
BASE_URL: "https://openapi.wps.cn",
// OAuth2 Token 获取地址
TOKEN_URL: "https://openapi.wps.cn/oauth2/token",
// 多维表格 API 路径前缀
API_PREFIX: "/v7/coop/dbsheet",
// ==================== 运行时配置 ====================
// 每次 API 调用之间的间隔 (毫秒),防止触发 QPS 限制
API_DELAY_MS: 300,
// access_token 有效期 (秒),默认 2 小时
TOKEN_EXPIRES_IN: 7200,
// 是否启用详细日志
VERBOSE_LOG: true,
};
module.exports = CONFIG;

View File

@@ -0,0 +1,200 @@
/**
* ============================================================
* 主执行脚本 — 创建全部多维表格数据表
* ============================================================
*
* 执行步骤:
* 1. 获取 access_token
* 2. 依次创建 7 张工作表
* 3. 为每张表创建所有字段
* 4. 输出执行结果报告
*
* 使用方法:
* 1. 确保已在 config.js 中填写正确的 APP_ID, APP_SECRET, FILE_ID
* 2. 运行: node create-tables.js
*
* 环境要求:
* - Node.js >= 18 (内置 fetch)
* - 若 Node.js < 18请先安装: npm install node-fetch
*/
const { createSheet, createFields, log, logError } = require("./api-client");
const { ALL_TABLES } = require("./table-definitions");
// ============================================================
// 主流程
// ============================================================
async function main() {
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 金山多维表格 — 项目部署管理系统 表结构创建脚本 ║");
console.log("║ 参考: claude-dds.md + gemini-dds.md ║");
console.log("╚══════════════════════════════════════════════════════╝");
console.log();
const report = {
success: [],
failed: [],
sheetIds: {},
};
// ---- Step 1: 逐表创建 ----
for (let i = 0; i < ALL_TABLES.length; i++) {
const tableDef = ALL_TABLES[i];
const stepNum = i + 1;
console.log(`\n${"=".repeat(60)}`);
console.log(`📋 [${stepNum}/${ALL_TABLES.length}] 创建表: ${tableDef.name}`);
console.log(` ${tableDef.description}`);
console.log(` 字段数量: ${tableDef.fields.length}`);
console.log("=".repeat(60));
try {
// 创建工作表
const sheetResult = await createSheet(tableDef.name);
const sheetId =
sheetResult?.data?.sheet_id ||
sheetResult?.sheet_id ||
sheetResult?.data?.id;
if (!sheetId) {
throw new Error(
`创建工作表成功但未返回 sheet_id, 响应: ${JSON.stringify(sheetResult)}`
);
}
report.sheetIds[tableDef.name] = sheetId;
log(` sheet_id = ${sheetId}`);
// 创建字段
const fieldResults = await createFields(sheetId, tableDef.fields);
// 统计字段创建结果
const successCount = fieldResults.filter((r) => r.success).length;
const failCount = fieldResults.filter((r) => !r.success).length;
console.log(
`\n 📊 字段创建完成: ✅ ${successCount} 成功, ❌ ${failCount} 失败`
);
if (failCount > 0) {
console.log(" 失败的字段:");
fieldResults
.filter((r) => !r.success)
.forEach((r) => console.log(` - ${r.field}: ${r.error}`));
}
report.success.push({
table: tableDef.name,
sheetId,
fieldsTotal: tableDef.fields.length,
fieldsSuccess: successCount,
fieldsFailed: failCount,
});
} catch (err) {
logError(`创建表 "${tableDef.name}" 失败`, err);
report.failed.push({
table: tableDef.name,
error: err.message,
});
}
}
// ---- Step 2: 输出执行报告 ----
console.log("\n\n");
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 📊 执行结果报告 ║");
console.log("╚══════════════════════════════════════════════════════╝");
console.log("\n✅ 成功创建的表:");
if (report.success.length > 0) {
report.success.forEach((s) => {
console.log(
` 📋 ${s.table} (sheet_id: ${s.sheetId}) — 字段: ${s.fieldsSuccess}/${s.fieldsTotal}`
);
});
} else {
console.log(" (无)");
}
if (report.failed.length > 0) {
console.log("\n❌ 创建失败的表:");
report.failed.forEach((f) => {
console.log(` ⚠️ ${f.table}: ${f.error}`);
});
}
// ---- Step 3: 输出 Sheet ID 映射 (后续关联字段需要) ----
console.log("\n📌 Sheet ID 映射表 (建立关联字段时需要使用):");
console.log("─".repeat(50));
Object.entries(report.sheetIds).forEach(([name, id]) => {
console.log(` ${name.padEnd(30)}${id}`);
});
// ---- Step 4: 提醒人工操作 ----
console.log("\n\n");
console.log("╔══════════════════════════════════════════════════════╗");
console.log("║ 🔧 人工操作清单 (必做) ║");
console.log("╚══════════════════════════════════════════════════════╝");
console.log(`
⚠️ 以下操作需要在金山多维表格界面中手动完成:
1⃣ 【建立表间关联字段】
将以下文本字段改为「关联记录」字段类型:
┌──────────────────────────────────┬────────────────────┬───────────────────────┐
│ 表名 │ 字段名 │ 关联目标表 │
├──────────────────────────────────┼────────────────────┼───────────────────────┤
│ 项目本地化部署升级信息表 │ 项目名称 │ → 项目基本信息表 │
│ 项目本地化部署状态表 │ 关联项目 │ → 项目基本信息表 │
│ 项目本地化部署状态表 │ 关联升级记录 │ → 升级信息表 │
│ 项目部署环境信息表 │ 所属部署 │ → 部署状态表 │
│ 项目部署网络信息表 │ 所属部署 │ → 部署状态表 │
│ 项目部署中间件信息表 │ 所属部署 │ → 部署状态表 │
│ 项目部署业务信息表 │ 所属部署 │ → 部署状态表 │
└──────────────────────────────────┴────────────────────┴───────────────────────┘
2⃣ 【配置视图】
在「项目本地化部署状态表」上创建以下视图:
- 甘特视图: 开始=计划开始时间, 结束=计划结束时间, 分组=部署状态
- 日历视图: 日期字段=计划开始时间
- 看板视图: 按「部署状态」字段分列
- 表单视图: 作为行业组提交部署需求的入口
3⃣ 【配置权限】
- 行业组人员: 仅可通过表单提交,只读查看部署状态表
- 交付部署特战队: 读写核心表及所有子表
- 小组长: 全部读写 + 仪表盘编辑
4⃣ 【配置自动化规则】
- 新建项目信息 → 自动创建部署状态记录 (状态=待排期)
- 新建升级信息 → 自动创建部署状态记录 (来源=升级)
- 部署状态→部署中: 自动记录实际开始时间
- 部署状态→已完成: 自动记录实际结束时间
- 部署状态→部署异常: 发送通知给小组长
5⃣ 【将「部署负责人」改为成员字段】
在「部署状态表」中将「部署负责人」字段类型改为 Member (成员)
6⃣ 【创建仪表盘】
- 部署状态分布饼图
- 本月部署任务数柱状图
- 各状态任务数统计卡片
`);
console.log("✅ 脚本执行完成!");
return report;
}
// ============================================================
// 执行入口
// ============================================================
main()
.then((report) => {
process.exit(report.failed.length > 0 ? 1 : 0);
})
.catch((err) => {
console.error("\n💥 脚本执行发生未捕获错误:", err);
process.exit(1);
});

View File

@@ -0,0 +1,14 @@
{
"name": "deploy-management-tables",
"version": "1.0.0",
"description": "金山多维表格 - 项目部署管理系统 表结构创建脚本",
"main": "create-tables.js",
"scripts": {
"create": "node create-tables.js",
"test-auth": "node test-auth.js"
},
"engines": {
"node": ">=18.0.0"
},
"private": true
}

View File

@@ -0,0 +1,701 @@
/**
* ============================================================
* 表结构定义文件
* ============================================================
*
* 严格参考:
* - claude-dds.md (完善的7张数据表 + 字段设计)
* - gemini-dds.md (核心锚表 + 部署实例模型)
* - 金山多维数据表格-PRD.md (原始字段需求)
*
* 综合以上文档,最终确定 7 张数据表的完整字段定义。
*
* 字段类型参考 (WPS 开放平台 多维表格参数说明):
* ┌──────────────────┬─────────────────────────┐
* │ 类型标识 │ 说明 │
* ├──────────────────┼─────────────────────────┤
* │ SingleLineText │ 单行文本 │
* │ MultiLineText │ 多行文本 (支持富文本) │
* │ Number │ 数值 │
* │ SingleSelect │ 单选 │
* │ MultipleSelect │ 多选 │
* │ Date │ 日期 │
* │ Checkbox │ 复选框 (是/否) │
* │ Url │ 超链接 │
* │ Phone │ 电话 │
* │ Email │ 电子邮箱 │
* │ AutoNumber │ 自动编号 │
* │ Attachment │ 附件 │
* │ Member │ 成员/人员 │
* │ Formula │ 公式 │
* │ Currency │ 货币 │
* │ Percent │ 百分比 │
* │ Rating │ 等级/星级 │
* │ Complete │ 进度条 │
* └──────────────────┴─────────────────────────┘
*
* 注意: 关联字段 (Link) 和引用字段 (Lookup) 需要目标表的 sheet_id
* 因此需要在所有表创建完成后,通过第二轮 API 调用来建立。
*/
// ============================================================
// 表1: 项目基本信息表 <行业组/售前人员填写>
// ============================================================
const TABLE_PROJECT_INFO = {
name: "项目基本信息表",
description: "行业组/售前人员通过表单提交,记录项目的基本信息和部署前提条件",
fields: [
{
name: "项目名称",
type: "SingleLineText",
},
{
name: "行业组接口人",
type: "SingleLineText",
},
{
name: "行业组接口人电话",
type: "Phone",
},
{
name: "省份",
type: "SingleSelect",
data: {
options: [
{ name: "北京" }, { name: "天津" }, { name: "河北" },
{ name: "山西" }, { name: "内蒙古" }, { name: "辽宁" },
{ name: "吉林" }, { name: "黑龙江" }, { name: "上海" },
{ name: "江苏" }, { name: "浙江" }, { name: "安徽" },
{ name: "福建" }, { name: "江西" }, { name: "山东" },
{ name: "河南" }, { name: "湖北" }, { name: "湖南" },
{ name: "广东" }, { name: "广西" }, { name: "海南" },
{ name: "重庆" }, { name: "四川" }, { name: "贵州" },
{ name: "云南" }, { name: "西藏" }, { name: "陕西" },
{ name: "甘肃" }, { name: "青海" }, { name: "宁夏" },
{ name: "新疆" },
],
},
},
{
name: "城市",
type: "SingleLineText",
},
{
name: "项目类型",
type: "SingleSelect",
data: {
options: [
{ name: "新部署", color_index: 1 },
{ name: "升级", color_index: 2 },
{ name: "迁移", color_index: 3 },
{ name: "扩容", color_index: 4 },
],
},
},
{
name: "部署优先级",
type: "SingleSelect",
data: {
options: [
{ name: "P0-紧急", color_index: 7 },
{ name: "P1-高", color_index: 6 },
{ name: "P2-中", color_index: 3 },
{ name: "P3-低", color_index: 1 },
],
},
},
{
name: "部署飞服平台",
type: "Checkbox",
},
{
name: "飞服平台版本",
type: "SingleLineText",
},
{
name: "部署监管平台",
type: "Checkbox",
},
{
name: "监管平台版本",
type: "SingleLineText",
},
{
name: "期望部署日期",
type: "Date",
data: {
number_format: "yyyy-MM-dd",
},
},
{
name: "部署必要条件说明",
type: "MultiLineText",
},
{
name: "必要条件是否满足",
type: "SingleSelect",
data: {
options: [
{ name: "是", color_index: 1 },
{ name: "否", color_index: 7 },
{ name: "部分满足", color_index: 3 },
],
},
},
{
name: "条件不满足说明",
type: "MultiLineText",
},
{
name: "部署资源信息",
type: "Attachment",
},
{
name: "特殊要求备注",
type: "MultiLineText",
},
],
};
// ============================================================
// 表2: 项目本地化部署升级信息表 <行业组人员填写>
// ============================================================
const TABLE_UPGRADE_INFO = {
name: "项目本地化部署升级信息表",
description: "行业组提交升级需求,关联已有项目",
fields: [
{
name: "项目名称",
type: "SingleLineText",
// 🔧 人工操作: 创建完成后需改为「关联字段」→ 关联「项目基本信息表」
},
{
name: "升级类型",
type: "SingleSelect",
data: {
options: [
{ name: "全量升级", color_index: 1 },
{ name: "增量升级", color_index: 2 },
{ name: "热修复", color_index: 3 },
{ name: "回滚", color_index: 7 },
],
},
},
{
name: "当前版本(飞服)",
type: "SingleLineText",
},
{
name: "目标版本(飞服)",
type: "SingleLineText",
},
{
name: "当前版本(监管)",
type: "SingleLineText",
},
{
name: "目标版本(监管)",
type: "SingleLineText",
},
{
name: "升级影响评估",
type: "MultiLineText",
},
{
name: "回滚方案",
type: "MultiLineText",
},
{
name: "期望升级开始时间",
type: "Date",
data: {
number_format: "yyyy-MM-dd",
},
},
{
name: "期望升级结束时间",
type: "Date",
data: {
number_format: "yyyy-MM-dd",
},
},
{
name: "部署资源信息",
type: "Attachment",
},
],
};
// ============================================================
// 表3: 项目本地化部署状态表 <核心锚表> <交付部署特战队填写>
// ============================================================
const TABLE_DEPLOY_STATUS = {
name: "项目本地化部署状态表",
description: "核心锚表 — 每次部署/升级对应一行,所有子表关联到此表",
fields: [
{
name: "部署编号",
type: "AutoNumber",
data: {
number_format: "000000",
},
},
{
name: "来源类型",
type: "SingleSelect",
data: {
options: [
{ name: "首次部署", color_index: 1 },
{ name: "升级部署", color_index: 2 },
],
},
},
{
name: "关联项目",
type: "SingleLineText",
// 🔧 人工操作: 创建完成后需改为「关联字段」→ 关联「项目基本信息表」
},
{
name: "关联升级记录",
type: "SingleLineText",
// 🔧 人工操作: 创建完成后需改为「关联字段」→ 关联「项目本地化部署升级信息表」
},
{
name: "部署状态",
type: "SingleSelect",
data: {
options: [
{ name: "待排期", color_index: 0 },
{ name: "已排期", color_index: 1 },
{ name: "环境准备中", color_index: 2 },
{ name: "部署中", color_index: 3 },
{ name: "验证中", color_index: 4 },
{ name: "已完成", color_index: 5 },
{ name: "部署异常", color_index: 7 },
{ name: "已暂停", color_index: 6 },
{ name: "已取消", color_index: 8 },
],
},
},
{
name: "部署结果",
type: "SingleSelect",
data: {
options: [
{ name: "成功", color_index: 1 },
{ name: "失败", color_index: 7 },
{ name: "部分成功", color_index: 3 },
],
},
},
{
name: "部署优先级",
type: "SingleSelect",
data: {
options: [
{ name: "P0-紧急", color_index: 7 },
{ name: "P1-高", color_index: 6 },
{ name: "P2-中", color_index: 3 },
{ name: "P3-低", color_index: 1 },
],
},
},
{
name: "当前进度/卡点说明",
type: "MultiLineText",
},
{
name: "计划开始时间",
type: "Date",
data: {
number_format: "yyyy-MM-dd HH:mm",
},
},
{
name: "计划结束时间",
type: "Date",
data: {
number_format: "yyyy-MM-dd HH:mm",
},
},
{
name: "实际开始时间",
type: "Date",
data: {
number_format: "yyyy-MM-dd HH:mm",
},
},
{
name: "实际结束时间",
type: "Date",
data: {
number_format: "yyyy-MM-dd HH:mm",
},
},
{
name: "命名空间",
type: "SingleLineText",
// 唯一标识,如 fs-xiongan-prod
},
{
name: "部署负责人",
type: "SingleLineText",
// 🔧 人工操作: 创建完成后可改为「成员 (Member)」字段类型
},
{
name: "部署人电话",
type: "Phone",
},
{
name: "备注",
type: "MultiLineText",
},
],
};
// ============================================================
// 表4: 项目部署环境信息表 <交付部署特战队填写>
// ============================================================
const TABLE_ENV_INFO = {
name: "项目部署环境信息表",
description: "一机一行,记录每台主机的详细信息",
fields: [
{
name: "所属部署",
type: "SingleLineText",
// 🔧 人工操作: 创建完成后需改为「关联字段」→ 关联「项目本地化部署状态表」
},
{
name: "公网IP",
type: "SingleLineText",
},
{
name: "能否访问公网",
type: "Checkbox",
},
{
name: "内网IP",
type: "SingleLineText",
},
{
name: "SSH端口",
type: "Number",
data: {
number_format: "0",
},
},
{
name: "SSH用户名",
type: "SingleLineText",
},
{
name: "密码存储方式",
type: "SingleSelect",
data: {
options: [
{ name: "密码管理器", color_index: 1 },
{ name: "加密文档", color_index: 2 },
{ name: "其他", color_index: 3 },
],
},
},
{
name: "主机功能",
type: "SingleSelect",
data: {
options: [
{ name: "master", color_index: 1 },
{ name: "worker", color_index: 2 },
{ name: "storage", color_index: 3 },
{ name: "doris", color_index: 4 },
],
},
},
{
name: "CPU架构",
type: "SingleSelect",
data: {
options: [
{ name: "amd64", color_index: 1 },
{ name: "arm64", color_index: 2 },
],
},
},
{
name: "主机状态",
type: "SingleSelect",
data: {
options: [
{ name: "待交付", color_index: 0 },
{ name: "已交付", color_index: 1 },
{ name: "已初始化", color_index: 2 },
{ name: "运行中", color_index: 3 },
{ name: "异常", color_index: 7 },
],
},
},
{
name: "操作系统",
type: "SingleSelect",
data: {
options: [
{ name: "CentOS 7", color_index: 1 },
{ name: "CentOS 8", color_index: 2 },
{ name: "Ubuntu 20.04", color_index: 3 },
{ name: "Ubuntu 22.04", color_index: 4 },
{ name: "其他", color_index: 5 },
],
},
},
{
name: "CPU核心数",
type: "Number",
data: {
number_format: "0",
},
},
{
name: "CPU型号",
type: "SingleLineText",
},
{
name: "内存大小(GB)",
type: "Number",
data: {
number_format: "0",
},
},
{
name: "系统盘大小(GB)",
type: "Number",
data: {
number_format: "0",
},
},
{
name: "数据盘大小(GB)",
type: "Number",
data: {
number_format: "0",
},
},
{
name: "GPU信息",
type: "SingleLineText",
},
{
name: "备注信息",
type: "MultiLineText",
},
],
};
// ============================================================
// 表5: 项目部署网络信息表 <交付部署特战队填写>
// ============================================================
const TABLE_NET_INFO = {
name: "项目部署网络信息表",
description: "记录每次部署的网络访问信息",
fields: [
{
name: "所属部署",
type: "SingleLineText",
// 🔧 人工操作: 创建完成后需改为「关联字段」→ 关联「项目本地化部署状态表」
},
{
name: "访问地址",
type: "Url",
},
{
name: "是否开启SSL",
type: "Checkbox",
},
{
name: "端口信息说明",
type: "MultiLineText",
},
{
name: "网络环境",
type: "SingleSelect",
data: {
options: [
{ name: "完全内网", color_index: 1 },
{ name: "单主机公网", color_index: 2 },
{ name: "全访问公网", color_index: 3 },
],
},
},
{
name: "主机管理方式",
type: "SingleSelect",
data: {
options: [
{ name: "堡垒机", color_index: 1 },
{ name: "跳板机", color_index: 2 },
{ name: "直接访问", color_index: 3 },
{ name: "VPN", color_index: 4 },
],
},
},
{
name: "管理后台地址",
type: "Url",
},
{
name: "VPN下载地址",
type: "Url",
},
{
name: "VPN账号信息存储位置",
type: "SingleLineText",
// 安全策略: 实际密码不存入多维表格,仅记录密码管理平台的路径/编号
},
{
name: "备注",
type: "MultiLineText",
},
],
};
// ============================================================
// 表6: 项目部署中间件信息表 <交付部署特战队填写>
// ============================================================
const TABLE_MIDDLEWARE_INFO = {
name: "项目部署中间件信息表",
description: "每行一个中间件实例每次部署固定创建7条记录",
fields: [
{
name: "所属部署",
type: "SingleLineText",
// 🔧 人工操作: 创建完成后需改为「关联字段」→ 关联「项目本地化部署状态表」
},
{
name: "中间件类型",
type: "SingleSelect",
data: {
options: [
{ name: "MySQL", color_index: 1 },
{ name: "Redis", color_index: 2 },
{ name: "RabbitMQ", color_index: 3 },
{ name: "EMQX", color_index: 4 },
{ name: "NACOS", color_index: 5 },
{ name: "K8S Dashboard", color_index: 6 },
{ name: "MINIO", color_index: 7 },
],
},
},
{
name: "中间件版本",
type: "SingleLineText",
},
{
name: "部署方式",
type: "SingleSelect",
data: {
options: [
{ name: "容器化", color_index: 1 },
{ name: "主机部署", color_index: 2 },
{ name: "云服务", color_index: 3 },
],
},
},
{
name: "是否暴露公网",
type: "Checkbox",
},
{
name: "公网端口",
type: "SingleLineText",
},
{
name: "内网IP",
type: "SingleLineText",
},
{
name: "内网端口",
type: "SingleLineText",
},
{
name: "用户名",
type: "SingleLineText",
},
{
name: "密码存储位置",
type: "SingleLineText",
// 安全策略: 实际密码不存入多维表格
},
{
name: "管理后台地址",
type: "Url",
},
],
};
// ============================================================
// 表7: 项目部署业务信息表 <交付部署特战队填写>
// ============================================================
const TABLE_BIZ_INFO = {
name: "项目部署业务信息表",
description: "记录每个微服务的部署信息",
fields: [
{
name: "所属部署",
type: "SingleLineText",
// 🔧 人工操作: 创建完成后需改为「关联字段」→ 关联「项目本地化部署状态表」
},
{
name: "微服务名称",
type: "SingleLineText",
},
{
name: "微服务分支",
type: "SingleLineText",
},
{
name: "微服务镜像",
type: "SingleLineText",
// gemini-dds: 需严格保持三段式规范,如 ${HARBOR_HOST}/cmii/nginx:latest
},
{
name: "微服务版本",
type: "SingleLineText",
},
{
name: "部署状态",
type: "SingleSelect",
data: {
options: [
{ name: "待部署", color_index: 0 },
{ name: "部署中", color_index: 3 },
{ name: "运行中", color_index: 1 },
{ name: "异常", color_index: 7 },
{ name: "已下线", color_index: 6 },
],
},
},
],
};
// ============================================================
// 导出所有表定义 (按创建顺序)
// ============================================================
const ALL_TABLES = [
TABLE_PROJECT_INFO, // 1. 项目基本信息表
TABLE_UPGRADE_INFO, // 2. 升级信息表
TABLE_DEPLOY_STATUS, // 3. 部署状态表 (核心锚表)
TABLE_ENV_INFO, // 4. 环境信息表
TABLE_NET_INFO, // 5. 网络信息表
TABLE_MIDDLEWARE_INFO, // 6. 中间件信息表
TABLE_BIZ_INFO, // 7. 业务信息表
];
module.exports = {
ALL_TABLES,
TABLE_PROJECT_INFO,
TABLE_UPGRADE_INFO,
TABLE_DEPLOY_STATUS,
TABLE_ENV_INFO,
TABLE_NET_INFO,
TABLE_MIDDLEWARE_INFO,
TABLE_BIZ_INFO,
};

View File

@@ -0,0 +1,70 @@
/**
* ============================================================
* 认证测试脚本 — 验证 API 凭证是否正确
* ============================================================
*
* 使用方法: node test-auth.js
*
* 仅测试获取 access_token不执行任何表格操作。
* 用于快速验证 APP_ID 和 APP_SECRET 是否配置正确。
*/
const { getAccessToken, getSchemas, log, logError } = require("./api-client");
const CONFIG = require("./config");
async function testAuth() {
console.log("🔐 测试认证配置...\n");
console.log(` APP_ID: ${CONFIG.APP_ID.substring(0, 8)}...`);
console.log(` FILE_ID: ${CONFIG.FILE_ID.substring(0, 8)}...`);
console.log(` BASE_URL: ${CONFIG.BASE_URL}`);
console.log();
// Step 1: 测试获取 token
try {
const token = await getAccessToken();
console.log(`✅ Token 获取成功: ${token.substring(0, 20)}...`);
} catch (err) {
logError("Token 获取失败", err);
console.log(
"\n💡 请检查:\n" +
" 1. config.js 中的 APP_ID 和 APP_SECRET 是否正确\n" +
" 2. 应用是否已申请 kso.dbsheet.readwrite 权限\n" +
" 3. 网络是否可以访问 openapi.wps.cn"
);
process.exit(1);
}
// Step 2: 测试获取 Schema
try {
console.log("\n📄 测试获取文档 Schema...");
const schemas = await getSchemas();
console.log(`✅ Schema 获取成功!`);
console.log(` 文档中已有的工作表:`);
const sheets = schemas?.data?.sheets || schemas?.sheets || [];
if (sheets.length === 0) {
console.log(" (空文档,尚无工作表)");
} else {
sheets.forEach((s) => {
console.log(
` - ${s.name} (sheet_id: ${s.sheet_id || s.id}, 字段数: ${
s.fields?.length || "?"
})`
);
});
}
} catch (err) {
logError("Schema 获取失败", err);
console.log(
"\n💡 请检查:\n" +
" 1. config.js 中的 FILE_ID 是否正确\n" +
" 2. 应用是否对该文档有读权限\n" +
" 3. 文档是否已经共享给该应用"
);
process.exit(1);
}
console.log("\n✅ 认证测试全部通过! 可以安全执行 create-tables.js");
}
testAuth().catch(console.error);

View File

@@ -0,0 +1,144 @@
参考文档
1. [金山多维表格开发](https://365.kdocs.cn/l/ctzsgDlAGF0l)
2. [金山多维表格教程]
实际表格流程需求:
### 项目基本信息表 <行业组人员填写> todo: 需要能够通过项目名称搜索)
1. 项目名称
2. 行业组人员名称
3. 行业组人员电话
4. 省份 <下拉选择>
5. 城市 <下拉选择>
6. 部署飞服平台 <选择>
7. 部署飞服平台版本 <填写 2.3.0 2.3-雄安>
8. 部署监管平台 <选择>
9. 部署监管平台版本 <填写 2.3.0 2.3-雄安>
10. 部署资源信息 <附件>
#### 动作说明
1. 创建之后,自动创建 <项目本地化部署状态表>
### 项目本地化部署升级信息表 <行业组人员填写> (todo: 项目名称需要和已有的匹配即可)
1. 项目名称 需要和已有的匹配>
2. 部署飞服平台版本 <填写 2.3.0 2.3-雄安>
3. 部署监管平台 <选择>
4. 部署监管平台版本 <填写 2.3.0 2.3-雄安>
5. 部署资源信息 <附件>
#### 动作说明
1. 创建之后,自动创建 <项目本地化部署状态表>
### 项目本地化部署状态表 <交付部署特战队填写> <部署排期表>
1. <项目本地化部署基本信息表> 或者 <项目本地化部署升级信息表> 字段
2. 部署状态 <选择 排期中 部署中 部署完成>
3. 部署时长 填写
4. 部署开始时间 <部署状态变化为 部署中的时间>
5. 部署结束时间 <根据部署开始时间 部署时长 自动计算>
6. 命名空间 <唯一>
8. 部署人姓名
9. 部署人电话
10. 备注(部署时间 升级时间)
#### 动作说明-状态切换
1. 部署完成 状态切换, 需要检查关联的 <项目部署网络信息表> <项目部署中间件信息表> 是否已经填写 才能进行转换
2. 部署中 状态切换, 需要检查 <项目部署环境信息表> 是否已经填写 才能进行切换
### 项目部署环境信息表 <交付部署特战队填写> (todo: 此表如何与项目进行关联, 一个项目有多台主机)
1. 公网IP
2. 能否访问公网 </>
3. 内网IP
4. SSH端口
5. SSH用户名
6. SSH密码 <不允许填写>
7. 主机功能 <选择 master worker storage doris>
8. CPU核心数
9. CPU型号
10. 内存大小
11. 系统盘大小(单位GB)
12. 数据盘大小(单位GB)
13. 备注信息
14. 所属部署(→ 部署状态表 命名空间)
### 项目部署网络信息表 <交付部署特战队填写> (todo: 此表如何与项目进行关联)
1. 访问地址
2. 是否开启SSL </>
3. 端口信息说明 <复制自 本地化部署公网端口暴露需求>
4. 网络环境 <完全内网单主机公网全访问公网>
5. 主机管理方式 <堡垒机跳板机直接访问>
6. 管理后台地址
7. 管理后台用户 <不允许填写>
8. 管理后台密码 <不允许填写>
9. VPN下载地址
10. VPN用户名 <不允许填写>
11. VPN用户密码 <不允许填写>
12. 所属部署(→ 部署状态表 命名空间)
### 项目部署中间件信息表 <交付部署特战队填写> (todo: 此表如何与项目进行关联)
每行一个中间件实例 固定有如下的中间件信息
1. MySQL
2. Redis
3. RabbitMQ
4. EMQX
5. NACOS
6. K8S Dashboard
7. MINIO
8. 所属部署(→ 部署状态表 命名空间)
上面每个中间件都具备如下的属性
1. 是否暴露公网
2. 公网端口
3. 内网IP
4. 内网端口
5. 用户名
6. 密码 <不允许填写>
### 项目部署业务信息表 <交付部署特战队填写> (todo: 此表如何与项目进行关联)
1. 微服务名称
2. 微服务分支
3. 微服务镜像
4. 所属部署(→ 部署状态表 命名空间)
### 项目信息汇总表 <自动汇总 交付部署特战队可看 >
1. <项目本地化部署状态表>
2. <项目部署环境信息表>
3. <项目部署网络信息表>
4. <项目部署中间件信息表>
5. <项目部署业务信息表>
### 本地化部署公网端口暴露需求 <交付部署特战队填写 只读>
### 本地化部署飞服平台资源清单表 <交付部署特战队填写 只读>
### 本地化部署监管平台资源清单表 <交付部署特战队填写 只读>
以「项目本地化部署状态表」为核心锚表,所有子表(环境、网络、中间件、业务)都通过关联字段指向它,而不是指向项目基本信息表。原因是:一个项目可能有多次部署/升级,每次都会产生一条部署状态记录,子表关联的是这次部署,而不是项目本身。
项目基本信息表
│ 自动化触发 → 新建一条记录
项目本地化部署状态表 ← 核心锚表(每次部署/升级对应一行)
│ 单向关联字段(多→一)
├── 项目部署环境信息表(每台主机一行)
├── 项目部署网络信息表(每次部署一行)
├── 项目部署中间件信息表每个中间件一行共7行
└── 项目部署业务信息表(每个微服务一行)
同时在「项目本地化部署状态表」里对每个子表建立双向关联的反向字段,这样在部署状态表中可以直接看到这条部署关联了哪些主机、哪些中间件 。
项目部署中间件信息表
├── 所属部署 ← 单向关联字段(→ 部署状态表),这是你的"projectID"
├── 项目名称 ← 引用字段(从部署状态表引用,只读自动填入)
├── 中间件类型 ← 单选MySQL / Redis / RabbitMQ / EMQX / NACOS / K8S Dashboard / MINIO
├── 是否暴露公网
├── 公网端口
├── 内网IP / 内网端口
├── 用户名
└── 密码 ← 字段权限设为不可编辑

View File

@@ -0,0 +1,28 @@
你是一名经验丰富的项目管理和团队管理者
你现在的角色是一名小组长,你需要对接监管平台组,飞行服务平台组的项目对外交付部署的相关工作,市场化项目的部署需求来自于行业组的快文
本小组的另一项重要职责是 负责项目组的基础架构开发工作
1. RMDC系统全称为 RuntimeManagement&DevOpsCenter 统一底层所有的运行环境,提供统一的运维管理中心
2. 但是现在由于人力不足,交付部署工作交维迫切,需要先行启动,后面的流程理论上都应该在RMDC系统中实现
相关的前提条件说明
1. 部署时间:飞服平台 监管平台依据不同的部署环境 每个项目需要的部署时间不同
2. 部署人员:目前小组人员不足,需要合理安排部署任务 部署任务可能进入部署排期表,即小组人员任务都被占据,则需要进入等待
现在迫切的需求是
1. 建立项目部署信息表(行业组 或者两组的交付人员填写)
1. 需要提供部署之必要条件之类的信息
2. 建立项目部署台账(甘特图类似) 也需要发挥部署排期表的职责(可以日历形式展示项目进度,也可以列表的形式展示项目列表)
1. 部署排期表中展示的内容,行业组比较喜欢催部署进度,部署排期表应该可以一目了然的查看自己的项目是否是部署中,部署的进度如何
2. 记录项目部署的基本信息,包括项目名称、部署版本、部署时间、部署状态、部署人员、部署结果等
现在主要是基于金山的多维表格进行上面内容的实现,现在已经有了两个初步的方案,
请你严格参考 @18-基础架构及交付部署特战队\claude-dds.md @18-基础架构及交付部署特战队\gemini-dds.md
根据金山多维表格的的API文档
[金山多维表格开发文档](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/dbsheet-standard)
[AirScript文档](https://airsheet.wps.cn/docs/guide/summary.html)
整理得到一份可以执行落地的API脚本用于创建上述的表格 以及人工需要进行操作补充的相关说明

View File

@@ -0,0 +1,84 @@
标题:关于交付部署特战队与研发团队协同工作的规范说明
交付部署特战队,下文简称特战队
飞行服务平台、监管平台、AI及视频流媒体平台研发团队下文简称研发团队
## 工作原则说明
协作规范的基本原则:能够共同推进项目的本地化部署的规范化、标准化、流程化、高效化,助力中移凌云的市场化拓展工作
工作职责界面原则:原则上由特战队承担主要工作职责,研发团队配合完成
## 工作流程说明
### 标准化部署流程
1. 研发团队在发版之后为特战队提供[部署及升级资料]
2. 研发团队应该配合特战队按照职责界面的相关要求,整理对外部署的资料
3. 特战队应该在规定时间内实现对外交付部署的能力
4. 特战队直接对接客户组,落地中移凌云的本地化部署工作
### 定制化部署流程
1. 遵循标准化部署流程
2. 遵循增量升级部署流程
3. 遵循持续交付部署流程
### 增量升级部署流程
1. 特战队提供项目部署的当前信息给研发团队
2. 研发团队准备必要的升级部署资料
1. 增量升级的sql文件
2. 交付物(代码构建镜像的压缩包)
3. 增量的配置文件说明
4. 新增的业务运行方案
3. 特战队在规定时间执行增量升级部署工作
4. 研发团队进行增量功能的验证工作
### 持续交付部署流程
1. 研发团队自行进行交付物构建工作
2. 研发团队提供部署项目信息,交付物信息给特战队
3. 特战队进行持续交付部署工作
4. 特战队通知研发团队进行更新功能的验证工作
## 部署及升级资料职责界面
### 数据库脚本
1. 研发团队职责:
1. 数据库sql必须通过MySQL官方二进制文件 mysqldump导出 mysql导入
2. 部署应该提供标准平台版本的全新数据库初始化之数据库结构、初始化数据sql文件
3. 应该在测试环境验证sql文件的正确性保证sql文件能够正确运行
2. 特战队职责:
1. 提供MySQL的测试环境
2. 提供存储地点放置sql文件
### 部署配置文件
1. 研发团队职责:
1. 应当遵循特战队关于部署配置的相关要求
2. 应该为配置文件每一项进行中文配置说明
2. 特战队职责:
1. 提供存储地点放置配置文件
### 业务运行方案
1. 研发团队职责:
1. 应当配合特战队完成K8S部署的相关要求
2. 应当配合特战队完成健康检测,日志,监控等相关要求
3.
2. 特战队职责:
1. 应当主导(帮助)研发团队的K8S部署方案实现
### 信创(国产化)要求
1. 研发团队职责:
1. 应当配合特战队完成信创(国产化)适配的相关要求
2.
2. 特战队职责:
1. 应当主导(帮助)研发团队实现信创(国产化)的CPU,操作系统部分
### 高可用部署方案
1. 研发团队职责:
1. 应当配合特战队完成高可用方案的代码改造工作
2. 特战队职责:
1. 应当主导(帮助)研发团队实现高可用部署方案
2. 应当提供高可用方案的测试环境

View File

@@ -0,0 +1,37 @@
# 第一步 客户组发起部署需求
1. 填写需求收集表
https://f.kdocs.cn/ksform/w/write/tyEGyJMm/
【WPS表单】邀你填写「中移凌云本地化部署-需求收集表」
2. 发快文记录部署需求
# 第二步 客户组追踪部署进度
1. 客户组可以通过 部署排期表 查看部署的排期情况和部署状态
1. 部署排期表-甘特 https://www.kdocs.cn/wo/sl/v11tiAtA
2. 部署排期表-日历 https://www.kdocs.cn/wo/sl/v1VtDC8
# 第三步 wdd 修改 【部署排期表】
1. 设置项目的关联关系
2. 进行排期作业
1. 分配部署人
2. 分配部署时间
3. 设置规范的命名空间
# 第四步 实际部署人 填写
1.【部署环境信息表】
2.【部署网络信息表】
3.【部署业务信息表】
# 第五步 实际部署人 执行部署计划
# 第六步 wdd 修改 【部署排期表】
1. 修改 部署状态 为 完成或异常
2. 通知客户组相关人员
3. wdd审核部署信息是否正确 全面
4. 调用项目信息汇总接口,汇总项目的全部信息
# 第七步 实际部署人 补充项目敏感信息
1. 根据离线的项目信息表
2. 补充项目敏感信息
3. 上传项目部署信息到院内的confluence

View File

@@ -0,0 +1,39 @@
标题:关于交付部署特战队与市场及区域中心协同工作的规范说明
交付部署特战队,下文简称特战队
各大区域中心、市场同事、解决方案专家、研发项目负责人,下文简称客户组
测试团队,下文简称测试组
协作规范的基本原则:行业组提出本地化部署需求,特战队负责本地化部署的落地工作
## 工作
## 工作流程说明提供mermaid流程图
1. 客户组发起部署或升级需求
2. 特战队进行部署资源评估
3. 特战队进行部署排期
4. 客户组追踪部署排期
5. 特战队进行部署实施
6. 测试组进行部署测试
7. 客户组进行部署验收
## 工作职责及界面说明
1. 客户组
1. 填写部署需求
1. 「中移凌云本地化部署-需求收集表」https://f.kdocs.cn/ksform/w/write/tyEGyJMm/
2. 「中移凌云本地化部署-升级-需求收集表」https://f.kdocs.cn/g/Wl3ZK1b5/
2. 追踪部署进度
1. 部署排期表-甘特 https://www.kdocs.cn/wo/sl/v11tiAtA
2. 部署排期表-日历 https://www.kdocs.cn/wo/sl/v1VtDC8
3. 进行部署验收
1. 特战队提供 本地化部署的访问方式
2. 特战队
1. 进行部署资源评估
2. 进行部署排期
3. 进行部署实施
## 部署排期说明
1. 默认排期规则为:按照客户组提出部署需求的时间进行排序
2. 人为排期规则为:客户组之间互相协商确立部署顺序

View File

@@ -0,0 +1,19 @@
## 现有事情很多
1. 我 非标准化的移动云 部署飞服平台
2. 波哥 有两个项目
## 监管投入人员 0.2人 论证
1. 龙卫 标准化部署中 监管只有5日深圳龙华部署2周还没弄好因此只能算是半个人
2. 如果只有三个人监管平台在特战队中的投资占比只有0.2人
3. 也不是不支持我们翻倍支持提供1个人的支撑
## 特战队其实本意是 基础架构的升级
1. 之前项目不多 小农经济可以覆盖
2. 现在人力没有办法 必须进行基础架构的升级 现代化机械化生产
3. 只有0.2个人额外投入,无法进行此部分工作
## 三个人的试运行的过程
1. 完全无法腾出时间 进行基础架构升级

View File

@@ -0,0 +1,29 @@
珂总 既然您问了,我就仔细的说一下这件事情的来龙去脉:
1. 背景
1. 由于现在的人力模式无法应对未来的规模化,本意是想建立一个基础架构特战队
2. 领导看不到这些技术的价值,只能以 交付部署特战队的名义,完成领导关注的内容。
3. 本着最小投入原则,实际上只要了一个干活的人
2. 现状
1. 没有任何资源投入:其实已经背离了基础架构的设想,一个人都不愿意投入
2. 师出无名:
1. 领导信誉问题:空头支票;
2. 连最基本的快文虚名都没有
3. 李垚:
1. 上部务会讨论,搞得我是求着成立一样
2. 跪着求着监管去吃他们的屎?
4. 未来-无利
1. 按照领导的惯性:既然你都把活干了,为什么要给你名利
2. 想着以解决监管大锅换取产业升级,目前来看基础架构的设想遥遥无期,完全丧失可能性
3. 监管的屎 搞得我求着吃一样,令人恶心
这件事情 我也不想说太多,说太多显得我求着他们一样
他们自己理解不了也好,打着歪主意也好,我不在乎
我已经表现了我的诚意,我

View File

@@ -0,0 +1,20 @@
| 考核指标 | 权重 | 考核主体 | 考核子类 | 考核内容说明 | 考核细项占比 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **项目交付部署** | 30% | 行业组 | 交付部署时效 | 1. 依据《技术交付时效承诺表》,统计季度内所有交付工单的按时完成率并计算得分。交付工单需由项目经理及区域军团负责人签字认定。按时完成率得分 = 按时完成交付项目数 / 交付项目数。<br>注:项目版本升级与项目部署一同参与排期,考核为执行时效。 | 40% |
| 项目交付部署 | 30% | 行业组 | 交付部署质量 | 部署完成后应该遵照最简冒烟测试的标准进行部署项目的功能验证工作,确保交付部署完成质量。 | 40% |
| 项目交付部署 | 30% | 行业组 | 交付部署完成度 | 根据填写的《交付部署配置信息表》,为行业组及研发同事提供项目部署的详细信息清单。 | 20% |
| **项目生命周期维护** | 30% | 行业组 | 项目故障排查 | 按照《故障等级响应准则》为故障评级,在规定时间内响应故障,排查定位问题。解决与基础架构相关的问题,协助给出其他问题责任人。 | 10% |
| 项目生命周期维护 | 30% | 行业组 | 持续交付部署 | 研发人员完成交付物构建及安全审查后,在规定时间内完成持续交付部署升级。 | 40% |
| 项目生命周期维护 | 30% | 行业组 | 项目维护工作 | 配合完成项目生命周期中的与基础架构相关的维护变更需求,包括 SSL 证书、域名变更,端口暴露等。 | 30% |
| 项目生命周期维护 | 30% | 行业组 | 项目版本升级 | 在规定时间内完成项目版本的升级工作。<br>注:项目版本升级与项目部署一同参与排期,考核为执行时效。 | 20% |
| **重点项目维保** | 10% | 产品组 | 重点项目 SRE | 针对重点项目(重大演示汇报、重要演示、重要验收等)环境保障期间运行稳定性进行考核打分。 | 40% |
| 重点项目维保 | 10% | 产品组 | 信创国产标准化 | 配合产品组完成中移凌云信创及国产标准化负责基础架构k8s, 中间件)的信创部署条件,协调提供信创的测试环境,协同研发组共同完成信创环境改造。 | 30% |
| 重点项目维保 | 10% | 产品组 | 交付部署标准化 | 持续完成交付部署的标准化工作,向产品组提供《中移凌云部署说明书》。 | 30% |
| **研发环境优化** | 10% | 研发组 | 研发环境维保 | 研发环境的重大故障修复,保障研发工作的顺利推进。 | 20% |
| 研发环境优化 | 10% | 研发组 | 项目验收环境 | 提供重要研发项目验收环境,包括计算、存储、网络资源申请、环境部署、公网端口暴露等,提供项目验收生命周期内的环境运行稳定性维护工作,持续交付部署工作,故障修复工作。 | 80% |
| **院内资源维护** | 10% | PMO 组 | 研发工具链维护 | 配合院内完成研发工具链的迁移、整改工作,保障研发工具链的使用。 | 50% |
| 院内资源维护 | 10% | PMO 组 | 院内服务器整改 | 配合院内要求完成所有服务器的信创迁移改造。 | 50% |
| **项目安全维护** | 5% | 安全组 | 部署安全合规 | 配合安全组输出针对交付部署的安全合规性要求的相关要求。 | 50% |
| 项目安全维护 | 5% | 安全组 | 项目安全合规 | 配合安全组完成基础组件k8s, 中间件)漏洞修复工作。 | 50% |
| **其他工作** | 5% | PMO 组 | 其他工作 | 完成部门临时性、突发性任务(如协助撰写技术材料、临时资源调配等)。 | 100% |

View File

@@ -0,0 +1,84 @@
# ✅ 图1仅展示考核指标占比简洁版
绘制一张简洁现代的信息图infographic展示工作内容的整体占比关系。使用环形图donut chart或圆饼图pie chart扁平化设计flat design白色或浅色背景。
包含模块:
* 项目交付部署30%
* 项目生命周期维护30%
* 重点项目维保10%
* 研发环境优化10%
* 院内资源维护10%
* 项目安全维护5%
* 其他工作5%
要求:
* 不展示具体工作内容细节
* 仅显示模块名称 + 占比
* 使用低饱和度商务配色(蓝、绿、橙、灰等)
* 各区域比例清晰,视觉对比明显
* 添加简洁图例legend
* 风格专业、清爽适合PPT汇报
* 高分辨率,矢量风格,无多余装饰
---
# ✅ 图2展示考核指标 + 子类占比(层级版)
绘制一张结构清晰的层级信息图hierarchical infographic展示工作内容占比及其子类构成。使用多层环形图multi-level donut chart / sunburst chart
第一层(内环)为考核指标:
* 项目交付部署30%
* 项目生命周期维护30%
* 重点项目维保10%
* 研发环境优化10%
* 院内资源维护10%
* 项目安全维护5%
* 其他工作5%
第二层(外环)为对应考核子类,占比基于所属指标内部拆分:
* 项目交付部署:
* 交付部署时效40%
* 交付部署质量40%
* 交付部署完成度20%
* 项目生命周期维护:
* 项目故障排查10%
* 持续交付部署40%
* 项目维护工作30%
* 项目版本升级20%
* 重点项目维保:
* 重点项目 SRE40%
* 信创国产标准化30%
* 交付部署标准化30%
* 研发环境优化:
* 研发环境维保20%
* 项目验收环境80%
* 院内资源维护:
* 研发工具链维护50%
* 院内服务器整改50%
* 项目安全维护:
* 部署安全合规50%
* 项目安全合规50%
* 其他工作:
* 其他工作100%
设计要求:
* 内环显示“考核指标”,外环显示“考核子类”
* 同一指标使用同色系渐变(内深外浅或外深内浅)
* 不展示具体说明文字,仅保留名称+占比
* 层级关系清晰,结构直观
* 使用专业商务配色,低饱和度
* 添加简洁图例或标签引导阅读
* 风格现代、简洁、适合汇报展示
* 高分辨率,矢量风格

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

View File

@@ -0,0 +1,46 @@
你现在是一名小组的代理组长 是一名非常优秀的项目管理专家,技术架构师
### 主要职责
小组的主要职责是 对内作为基础架构与运行环境维护团队,对外作为交付部署与项目生命周期维护团队
### 具体的工作及考核内容
| 考核指标 | 权重 | 考核主体 | 考核子类 | 考核内容说明 | 考核细项占比 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **项目交付部署** | 30% | 行业组 | 交付部署时效 | 1. 依据《技术交付时效承诺表》,统计季度内所有交付工单的按时完成率并计算得分。交付工单需由项目经理及区域军团负责人签字认定。按时完成率得分 = 按时完成交付项目数 / 交付项目数。<br>注:项目版本升级与项目部署一同参与排期,考核为执行时效。 | 40% |
| 项目交付部署 | 30% | 行业组 | 交付部署质量 | 部署完成后应该遵照最简冒烟测试的标准进行部署项目的功能验证工作,确保交付部署完成质量。 | 40% |
| 项目交付部署 | 30% | 行业组 | 交付部署完成度 | 根据填写的《交付部署配置信息表》,为行业组及研发同事提供项目部署的详细信息清单。 | 20% |
| **项目生命周期维护** | 30% | 行业组 | 项目故障排查 | 按照《故障等级响应准则》为故障评级,在规定时间内响应故障,排查定位问题。解决与基础架构相关的问题,协助给出其他问题责任人。 | 10% |
| 项目生命周期维护 | 30% | 行业组 | 持续交付部署 | 研发人员完成交付物构建及安全审查后,在规定时间内完成持续交付部署升级。 | 40% |
| 项目生命周期维护 | 30% | 行业组 | 项目维护工作 | 配合完成项目生命周期中的与基础架构相关的维护变更需求,包括 SSL 证书、域名变更,端口暴露等。 | 30% |
| 项目生命周期维护 | 30% | 行业组 | 项目版本升级 | 在规定时间内完成项目版本的升级工作。<br>注:项目版本升级与项目部署一同参与排期,考核为执行时效。 | 20% |
| **重点项目维保** | 10% | 产品组 | 重点项目 SRE | 针对重点项目(重大演示汇报、重要演示、重要验收等)环境保障期间运行稳定性进行考核打分。 | 40% |
| 重点项目维保 | 10% | 产品组 | 信创国产标准化 | 配合产品组完成中移凌云信创及国产标准化负责基础架构k8s, 中间件)的信创部署条件,协调提供信创的测试环境,协同研发组共同完成信创环境改造。 | 30% |
| 重点项目维保 | 10% | 产品组 | 交付部署标准化 | 持续完成交付部署的标准化工作,向产品组提供《中移凌云部署说明书》。 | 30% |
| **研发环境优化** | 10% | 研发组 | 研发环境维保 | 研发环境的重大故障修复,保障研发工作的顺利推进。 | 20% |
| 研发环境优化 | 10% | 研发组 | 项目验收环境 | 提供重要研发项目验收环境,包括计算、存储、网络资源申请、环境部署、公网端口暴露等,提供项目验收生命周期内的环境运行稳定性维护工作,持续交付部署工作,故障修复工作。 | 80% |
| **院内资源维护** | 10% | PMO 组 | 研发工具链维护 | 配合院内完成研发工具链的迁移、整改工作,保障研发工具链的使用。 | 50% |
| 院内资源维护 | 10% | PMO 组 | 院内服务器整改 | 配合院内要求完成所有服务器的信创迁移改造。 | 50% |
| **项目安全维护** | 5% | 安全组 | 部署安全合规 | 配合安全组输出针对交付部署的安全合规性要求的相关要求。 | 50% |
| 项目安全维护 | 5% | 安全组 | 项目安全合规 | 配合安全组完成基础组件k8s, 中间件)漏洞修复工作。 | 50% |
| **其他工作** | 5% | PMO 组 | 其他工作 | 完成部门临时性、突发性任务(如协助撰写技术材料、临时资源调配等)。 | 100% |
### 核心RMDC平台远景规划
1. 一键交付物构建:实现从源码到交付包的单界面全自动产出,消除人工打包误差。
2. 跨网持续更新:具备穿透公网能力,实现全国任意节点的一键微服务升级,响应定制化开发需求。
3. 全景监控中心:实时监控全国项目运行状态,变“被动救火”为“主动预警”。
4. 全链路审计:提供“谁提交、谁审批、谁更新”的全生命周期操作记录,确保安全合规。
5. 部署信息指纹:精细化管理每个项目的配置差异,为定制化开发提供精准的底层数据支撑。
### 人员安排
| 人员 | 角色 | 核心能力 | 职责 | 开发能力 |
| :--- | :--- | :--- | :--- | :--- |
| 我 | 代理组长 | 架构师、项目管理专家、开发专家 | 负责小组的整体工作安排和协调,确保各项任务按时完成。 | 强 |
| 袁 | 核心成员 | 交付部署专家、项目生命周期维护专家、开发能力 | 负责交付部署和项目生命周期维护工作。 | 弱 |
| 冉 | 核心成员 | 研发环境优化专家、项目验收环境专家 | 负责研发环境优化和项目验收环境工作。 | 强 |
| A | 待定人员 | 待定 | 负责院内资源维护和研发工具链维护工作。 | 弱 |
| B | 待定人员 | 待定 | 负责项目安全维护和部署安全合规工作。 | 弱 |
### 已知代办事项
1. 我需要一个能够管理小组日常工作安排和协调的工具,能够记录和跟踪各项任务的进度,包括任务的分配、执行情况、完成情况等的工具,请分析金山多维表格能否满足我的需求,如果不能请推荐其他工具。
2.

View File

@@ -0,0 +1,59 @@
我准备做如下内容
1. 给行业组一份快文,说明部署项目的流程及必要的资料
2. 给监管和飞服一份快文,说明部署项目的基本规范和要求
3. 建立项目部署信息表(行业组 或者两组的交付人员填写) 然后进入部署排期
4. 建立项目部署台账(也可以复用 部署排期(甘特图类似))
2026年3月8日
1. 完善部署升级表,此部分的工作流程
1. 已完成
2. 与监管平台交互部分的功能
3. 部署信息汇总表
1. 表结构不同通过不同的sheet保存
2. 人工实现
3. 数据查询,类似于外键
4. 部署表-流程优化
1. 项目本地化部署状态表 切换为 部署中
2. 应该自动创建中间件信息
3. 自动创建业务信息
4. 自动创建网络信息
5. 持续交付部署表 - 利用RMDC系统实现
1. 面向研发团队填写
2. 填写之后进入持续交付部署排期
3. 部署之后 修改部署状态
4. 需要同步更新 项目部署业务信息表 中的字段
6. 项目部署交付物版本
1. 汇总所有来自研发团队的微服务和数据库SQL
部署表-流程优化
1. 项目本地化部署状态表 有一个按钮 可以触发这个AirScript脚本无需自动触发
2. 脚本运行需要使用本行的相关数据,请考虑参数传入的方式
3. 应该自动创建中间件信息
1. 中间件信息需要在脚本最前面进行定义
2. 默认公网端口 默认内网端口 默认内网IP
4. 自动创建业务信息
1. 业务信息需要在脚本最前面进行定义
2. 设置Map的格式进行准备
3. 监管平台和飞行服务平台的业务内容(微服务名称)不相同
4. 部署平台字段 飞服平台代表部署飞行服务平台 监管平台代表部署监管平台
5. 自动创建网络信息
你完成的很好,数据已经可以创建出来了。
1. 新创建的行中均有 项目名称字段,需要你拿到当前行的项目名称 进行填写
2. 新创建的行中有所属项目是LINK关联属性能够通过脚本关联至调用行
3. 部署平台为飞服务平台,就只生成飞服平台相关的业务信息,现在飞服和监管全部都生成了
实现一个AirScript脚本根据入参 两个的单选项 是或者否
参数名称分别为 isDeployFlyControl isDeploySupervisor
返回值固定为一个JSON code<类型>
判定 如果isDeployFlyControl为是 <类型>为fly-center
判定 如果isDeploySupervisor为是 <类型>为supervisor
判定 如果isDeployFlyControl isDeploySupervisor 都为是 <类型>为both
判定 如果isDeployFlyControl isDeploySupervisor 都为否 <类型>为 error