@clawhub-androidxhm-650bb4c36f
CloudCC CRM 二次开发 CLI 助手。用于需求拆解与方案选型,并通过 cloudcc-cli(cc 命令)创建/拉取/发布自定义对象、字段、菜单/应用、自定义类、定时器、触发器与 Vue 自定义组件等资产。用户提到 CloudCC、cloudcc-cli、cc 命令、对象/字段/触发器/定时器/自定义组...
---
name: cloudcc-cli-dev
description: CloudCC CRM 二次开发 CLI 助手。用于需求拆解与方案选型,并通过 cloudcc-cli(cc 命令)创建/拉取/发布自定义对象、字段、菜单/应用、自定义类、定时器、触发器与 Vue 自定义组件等资产。用户提到 CloudCC、cloudcc-cli、cc 命令、对象/字段/触发器/定时器/自定义组件 时应优先使用。
---
# CloudCC CLI Development Skill
## cloudcc-cli
- 需要检查全局是否安装了cloudcc-cli npm包,如果没有请先安装。
## 工作目录
- 在openclaw环境中,需要在agent的workspace中创建code文件夹,然后在code文件中使用cc create project xxx,创建一个模版项目。
## 使用方式(AI 必须遵循)
- 先阅读 `REQUIREMENTS_BREAKDOWN.md`,输出需求拆解与交付物清单,再进入落地步骤。
- 需要环境/密钥 配置时,阅读 `INSTALL_AND_BOOTSTRAP.md`。
- 需要建模时(对象/字段/菜单/应用),阅读 `OBJECTS_AND_FIELDS.md`。
- 需要后端逻辑时(类/定时器/触发器),阅读 `BACKEND_CODE.md`。
- 需要自定义组件时,阅读 `VUE_CUSTOM_COMPONENT.md`。
- 需要快速对照命令与参数时,阅读 `CLI_CHEATSHEET.md`。
## 强制安全边界
- 不要在输出/代码/提交中包含真实密钥(`CloudCCDev`、`safetyMark`、`secretKey`、`openSecretKey`、token
等)。
- 后端类/触发器/定时器遵守片段同步:仅在
`@SOURCE_CONTENT_START`~`@SOURCE_CONTENT_END` 内编写可发布逻辑。
- 客户端脚本遵守片段同步:仅在 `function main($CCDK, obj) { ... }`
的函数体内编写可发布逻辑。
## 快速入口
- 安装与初始化:`INSTALL_AND_BOOTSTRAP.md`
- 需求拆解与方案选择:`REQUIREMENTS_BREAKDOWN.md`
- 自定义对象与字段:`OBJECTS_AND_FIELDS.md`
- 自定义类/定时器/触发器:`BACKEND_CODE.md`
- Vue 自定义组件:`VUE_CUSTOM_COMPONENT.md`
- CLI 速查:`CLI_CHEATSHEET.md`
FILE:CLI_CHEATSHEET.md
## CLI 入口速查(本仓库已定义)
`package.json` 中定义的 bin:
- `cc`:主入口(支持 `create` / `get` / `pull` / `publish` / `doc` 等)
- `cloudccCreate`:创建模板项目(内部调用 `src/project/create.js`)
- `cloudccBuild`:组件发布/构建相关(内部调用 `src/plugin/publish.js`)
---
## 常用 CLI 命令速查(按开发流程分组)
### 项目与文档
```bash
# 版本
cc -v
# 创建模板项目
cc create project <projectName>
# 输出项目文档(纯 CLI 文档)
cc doc project overview
cc doc project key-guide
```
### 对象与字段
```bash
# 创建自定义对象
cc create object <projectPath> <label>
# 查询对象列表(输出 JSON)
# type 可选:standard / custom / both(默认:both)
cc get object <projectPath> [type]
# 查询对象字段(输出 JSON)
cc get fields <projectPath> <objprefix>
# 创建字段(字段类型决定参数个数)
cc create fields <projectPath> <fieldType> <objid> <nameLabel> [ptext|lookupObj]
```
### 菜单与应用
```bash
# 创建菜单(标签页)
cc create menu <type> <projectPath> <resourceId> <tabName> [tabStyle] [mobileimg] [cloudccservicetab]
# 创建应用
cc create application <projectPath> <appName> <appCode> [menuIds]
```
### 自定义类(classes)
```bash
cc create classes <ClassName>
cc pull classes <ClassName>
cc publish classes <ClassName>
```
### 定时类(schedule)
```bash
cc create schedule <ScheduleName>
cc pull schedule <ScheduleName>
cc publish schedule <ScheduleName>
```
### 触发器(triggers)
```bash
# 创建:参数是一个 JSON(需 encodeURI 后作为单个参数传入)
cc create triggers <encodedJson>
# 拉取/发布:namePath 形如 objectName/triggerName
cc pull triggers <namePath>
cc publish triggers <namePath>
```
### 自定义组件(plugin / plugins)
```bash
# 创建(生成到 plugins/<pluginName>/)
cc create plugin <pluginName>
# 发布(编译并上传)
cc publish plugin <pluginName>
```
FILE:BACKEND_CODE.md
## 覆盖范围
本篇聚焦 CloudCC 后端 Java 侧二开资产(通过 `cc` 命令):
- 自定义类(Class)
- 定时类(Scheduled Class / Timer)
- 触发器(Trigger)
并给出“创建 → 拉取 → 编辑 → 发布”的闭环流程。
---
## 目录结构(以本仓库约定为准)
创建后通常会生成在以下目录(项目根下):
- `classes/{ClassName}/...`
- `schedule/{ScheduleName}/...`
- `triggers/{objectName}/{triggerName}/...`
---
## 源码同步边界(必须理解)
该项目使用“片段同步”策略:
- **自定义类 / 定时类 / 触发器**:只会拉取/发布 `@SOURCE_CONTENT_START` 与 `@SOURCE_CONTENT_END` 之间的内容。
这意味着:
- 你可以在片段外保留模板、import、类结构等;真正可同步的业务逻辑写在片段内。
- 拉取会覆盖片段区域,因此片段内不要放本地专用逻辑(如临时调试代码)。
---
## 自定义类(Class)
### 创建
```bash
# 在项目根目录执行
cc create classes <ClassName>
```
### 拉取 / 发布(片段同步)
```bash
cc pull classes <ClassName>
cc publish classes <ClassName>
```
> 说明:发布/拉取只会处理 `@SOURCE_CONTENT_START` 与 `@SOURCE_CONTENT_END` 之间的代码片段。
---
## 定时类(Scheduled Class)
### 创建
```bash
# 在项目根目录执行
cc create schedule <ScheduleName>
cc pull schedule <ScheduleName>
cc publish schedule <ScheduleName>
```
---
## 触发器(Trigger)
### 创建(关键入参)
触发器的 CLI 创建参数是一个 JSON 对象(需要 `encodeURI` 后作为单参传入):
```bash
cc create triggers <encodedJson>
```
JSON 字段(示例):
- `name`: 触发器名(Java 命名规范)
- `schemetableName`: 对象表名(如从对象列表拿到)
- `targetObjectId`: 目标对象 ID
- `triggerTime`: 触发时机(beforeInsert/afterUpdate/.../approval 等)
示例(macOS/zsh,注意引号与编码):
```bash
cc create triggers "$(node -e 'console.log(encodeURI(JSON.stringify({name:\"MyTrigger\",schemetableName:\"account\",targetObjectId:\"a01...\",triggerTime:\"beforeInsert\"})))')"
```
### 拉取 / 发布
```bash
# namePath 形如:objectName/triggerName(注意 objectName 通常为小写目录)
cc pull triggers <namePath>
cc publish triggers <namePath>
```
> 说明:发布/拉取只会处理 `@SOURCE_CONTENT_START` 与 `@SOURCE_CONTENT_END` 之间的代码片段。
---
## 推荐工作流(AI 执行顺序)
当需求涉及后端逻辑时,建议 AI 按以下顺序执行:
1. 通过对象/字段工具确认数据结构(必要时先建对象字段)
2. 创建类/触发器/定时器骨架(create_*)
3. 拉取线上版本(pull_*)对齐基线(如是增量改造)
4. 编写片段内业务逻辑(注意批量与幂等)
5. 发布(publish_*)
6. 回归验证:单条、批量、权限、异常分支
FILE:REQUIREMENTS_BREAKDOWN.md
## 目标
把业务需求翻译成 CloudCC CRM 二开落地项,并选择“应该做什么组件”:自定义对象/字段/菜单/应用/自定义类/定时器/触发器/自定义组件/客户端脚本。
---
## 需求拆解模板(AI 必须输出)
拿到需求后,先输出以下结构(缺信息则显式标注“待确认”):
- **背景与目标**:要解决什么问题,成功标准是什么。
- **角色与权限**:涉及哪些用户/简档/权限集,是否需要后台权限调整。
- **对象与数据模型**:
- 需要哪些**自定义对象**(表)?
- 需要哪些**字段**(类型、必填/唯一、默认值、选项、查找/主详关系)?
- 是否涉及**记录类型**、**布局**、**验证规则**(若 CLI 不覆盖,需在 CRM 配置)?
- **业务流程**:
- 触发时机:保存前/保存后/提交审批/批处理/定时等
- 需要哪些自动化:校验、自动赋值、联动更新、异步任务、定时任务
- **界面与交互**:
- 是否需要自定义页面或嵌入式 UI?
- 是否需要列表/详情/编辑页的前端交互(客户端脚本 / 自定义组件)?
- **集成与外部依赖**:第三方接口、鉴权方式、重试/限流、日志与审计。
- **风险与回滚**:数据迁移、权限影响、性能与批量场景、灰度与回滚方案。
- **交付物清单**:最终要产出哪些 CloudCC 资产(对象/字段/菜单/应用/类/触发器/定时器/组件/脚本)。
---
## 方案选型决策表(怎么判断用什么)
### 自定义对象(Custom Object)
当出现以下需求时,优先考虑创建自定义对象:
- 需要一个新的业务实体(类似新表),有独立的字段、权限、页面与列表
- 需要与现有对象建立查找/主详关系
- 需要支持审批、报表、共享规则等平台能力(通常都围绕对象展开)
### 自定义字段(Field)
当需求是“给已有对象补充数据结构”时:
- 新增文本/数值/日期/选项/查找关系等字段
- 需要字段级校验、默认值、必填等(部分能力可能需在 CRM 配置完成)
### 触发器(Trigger)
当需求是“数据写入前后做强一致业务逻辑”时:
- 保存前校验/自动赋值/阻止不合法写入(before*)
- 保存后联动更新、写日志、创建子记录(after*)
- 批量/审批/提交后回调等特定时机(approval/batch/commitBatch 等)
> 注意:触发器要考虑**批量**、幂等、递归、性能与异常处理。
### 自定义类(Class)
当需求是“可复用的服务逻辑/复杂计算/对外接口封装”时:
- 作为触发器/定时器/页面调用的后端服务类
- 抽取通用能力(查询封装、校验器、集成适配器)
### 定时类(Scheduled Class / Timer)
当需求是“按时间周期跑任务”时:
- 夜间同步、定时报表、到期提醒、批量修复数据
- 需要可控的调度表达式与运行日志
### 自定义组件(Vue Custom Component)
当需求是“在 CloudCC UI 中提供复杂交互 UI”时:
- 自定义表单、复杂联动、可视化、批量操作 UI
- 需要前端框架能力(Vue2.x)与更强的用户体验
### 客户端脚本(Client Script)
当需求是“页面级轻量交互/字段联动/保存前校验”时:
- 列表/详情/编辑/新建页生命周期钩子
- 字段变更联动、提示、轻量校验
> 客户端脚本更轻量;交互复杂或需要组件化复用,建议升级为自定义组件。
---
## 推荐落地流程(AI 执行顺序)
一般建议按以下顺序落地(从“数据结构”到“业务逻辑”再到“界面”):
1. **对象与字段**:先把数据模型搭好(对象、字段、关系)
2. **菜单与应用**:让用户能访问(菜单/应用)
3. **后端逻辑**:自定义类 / 触发器 / 定时器
4. **前端交互**:客户端脚本 / 自定义组件
5. **联调与回归**:核心路径 + 批量场景 + 权限场景
6. **发布与回滚准备**:版本记录、灰度、回滚策略
FILE:VUE_CUSTOM_COMPONENT.md
## 覆盖范围
本篇聚焦 CloudCC Vue2.x 自定义组件(Custom Component):
- 创建组件骨架
- 编辑与最佳实践入口
- 编译与发布到服务器
---
## 创建自定义组件
### CLI 命令
```bash
# 在项目根目录执行(会生成到 plugins/<pluginName>/)
cc create plugin <pluginName>
```
创建后会生成组件主文件、子组件目录与配置文件(以项目模板为准)。
---
## 发布组件
```bash
cc publish plugin <pluginName>
```
> 发布会执行编译/打包/上传;如果组件依赖复杂,注意依赖收集与发布策略。
---
## 编辑要点(CLI 项目约束)
- 组件目录通常为 `plugins/<pluginName>/`
- 确保所有 `<style>` 标签带 `scoped`,避免全局样式污染
- 发布通过 `cc publish plugin <pluginName>` 执行编译与上传
---
## 选型建议(组件 vs 客户端脚本)
- **自定义组件**:复杂交互、强 UI、可复用组件化、需要更完整工程化能力。
- **客户端脚本**:轻量联动、页面钩子逻辑、少量 UI 提示与校验。
一般建议:
- 先用脚本满足基础联动;当逻辑与 UI 复杂度上升时,再升级为组件方案。
FILE:OBJECTS_AND_FIELDS.md
## 覆盖范围
本篇聚焦用 `cloudcc-cli` 的 `cc` 命令完成:
- 创建自定义对象(Custom Object)
- 查询对象列表
- 查询对象字段
- 为对象创建字段(支持多字段类型)
-(可选)创建菜单、创建应用(把对象挂到导航中)
---
## 自定义对象(Custom Object)
### 你需要准备什么信息
- **对象显示标签(label)**:例如“客户”“合同”“付款计划”
- **项目路径(projectPath)**:模板项目根目录
### CLI 命令(项目已有实现)
```bash
cc create object <projectPath> <label>
```
示例:
```bash
cc create object . 客户
```
---
## 查询对象列表
### CLI 命令(输出 JSON)
```bash
# type 可选:standard / custom / both(默认:both)
cc get object <projectPath> [type]
```
示例:
```bash
cc get object . both
```
用途:
- 获取对象 `objprefix` / 对象 `id`(后续创建字段、触发器会用到)
---
## 查询对象字段列表
### CLI 命令(输出 JSON)
```bash
cc get fields <projectPath> <objprefix>
```
示例:
```bash
cc get fields . a01
```
用途:
- 在创建字段前,先确认是否已存在同名/同含义字段,避免重复与冲突。
---
## 创建字段(Field)
### CLI 命令形态(项目已有实现)
字段创建命令由工具根据字段类型拼装,典型形态:
```bash
cc create fields <projectPath> <fieldType> <objid> <nameLabel> [ptext|lookupObj]
```
### 字段类型选择建议(常用)
- **S**:文本(最常用)
- **N**:数字
- **D / F**:日期 / 日期时间
- **B**:复选框
- **L / Q**:选项列表(单选/多选)
- **Y / MR / M**:关系字段(查找/查找多选/主详)
- **E / H / U**:邮箱/电话/URL
---
## 菜单与应用(让对象可访问)
### 创建菜单(CLI)
```bash
cc create menu <type> <projectPath> <resourceId> <tabName> [tabStyle] [mobileimg] [cloudccservicetab]
```
示例:
```bash
cc create menu object . <objectId> "我的对象菜单"
```
### 创建应用(CLI)
```bash
cc create application <projectPath> <appName> <appCode> [menuIds]
```
说明:
- `menuIds`:可选,多个用逗号分隔;系统会确保默认菜单 `acf000001` 被包含。
FILE:INSTALL_AND_BOOTSTRAP.md
## 目标
让开发者(或 AI)在一台新机器上,完成 `cloudcc-cli` 的安装、模板项目创建、开发密钥配置,并能通过 `cc` 命令进行开发与发布。
---
## 安装 cloudcc-cli
### 全局安装
```bash
# Windows
npm i -g cloudcc-cli
# macOS(通常需要 sudo)
sudo npm i -g cloudcc-cli
```
### 验证版本
```bash
cc -v
```
> 说明:`cc` 是该 CLI 的统一入口命令之一(见本仓库 `package.json` 的 bin 配置)。
---
## 创建 CloudCC 模板项目
在你的工作目录下执行:
```bash
cc create project demo1
cd demo1
npm i
npm run serve
```
如果能在本地启动并访问页面,说明前端开发链路可用。
---
## 配置开发者密钥与安全标记
### 在 CloudCC CRM 后台获取
你需要一个具备“代码管理/开发者权限”的账号。
- **开发者密钥(CloudCCDev)**:在 CRM 后台「连接的应用程序」中新建后获取并复制。
- **安全标记(safetyMark)**:在个人设置中重置安全标记后,通过邮箱获取。
### 写入项目的 `cloudcc-cli.config.js`
模板项目根目录通常会有 `cloudcc-cli.config.js`(或你们约定的同名配置文件),核心原则:
- **多环境**:用 `use` 字段选择当前环境(如 `dev/uat/prod`)。
- **不要提交真实密钥到 Git**:建议 `.gitignore` 忽略配置文件,或用本地私密文件方式管理。
CloudCC OpenAPI 调用技能 - 提供完整的 REST API 接口调用能力,支持对象/字段元数据查询
---
name: cloudcc-openapi-withobject
description: CloudCC OpenAPI 调用技能 - 提供完整的 REST API 接口调用能力,支持对象/字段元数据查询
version: 2.0.0
author: 鲁班
createDate: 2026-03-20
updateDate: 2026-03-20
---
## 技能信息
- **名称**: cloudcc-openapi-withobject
- **版本**: 2.0.0
- **作者**: 鲁班
- **创建日期**: 2026-03-20
- **更新日期**: 2026-03-20
- **适用范围**: CloudCC One 版本 OpenAPI 接口调用
## 安装配置
### 配置参数
| 参数 | 说明 | 默认值 | 必填 |
|------|------|--------|------|
| `orgId` | 组织 ID | - | 是 |
| `username` | 登录用户名 | - | 是 |
| `safetyMark` | 安全标记(邮箱获取) | - | 是 |
| `clientId` | 连接应用的 clientId | - | 是 |
| `secretKey` | 连接应用的 secretKey | - | 是 |
| `apiDomain` | API 网关地址(动态获取) | - | 否(自动获取) |
### 获取配置信息
1. **组织 ID (orgId)**: 系统设置 - 公司信息中查看
2. **安全标记 (safetyMark)**: 个人信息 - 重置我的安全标记(发送到邮箱)
3. **clientId/secretKey**: 管理设置 - 安全性控制 - 连接的应用程序
### 安装步骤
```bash
# 1. 创建技能目录
mkdir -p ~/.openclaw/skills/cloudcc-openapi-withobject
# 2. 创建配置文件
cat > ~/.openclaw/skills/cloudcc-openapi-withobject/config.json << 'EOF'
{
"orgId": "your-org-id",
"username": "your-username",
"safetyMark": "your-safety-mark",
"clientId": "your-client-id",
"secretKey": "your-secret-key"
}
EOF
```
## 核心功能
### 1. 认证管理
- 动态获取组织 API 网关地址
- 获取 accessToken 并自动刷新
- accessToken 有效性验证
- 认证事件日志记录
### 2. 元数据查询(新增 v2.0.0)
| 方法 | 说明 | API 端点 |
|------|------|---------|
| `getAllTabs` | 获取所有选项卡(推荐用于查找对象) | `/openApi/common` (getAllTabs) |
| `pageQuery` | 查询对象数据(不带 fields 返回所有字段) | `/openApi/common` (pageQuery) |
| `getStandardObjects` | 获取标准对象列表 | `/api/customObject/standardObjList` |
| `getCustomObjects` | 获取自定义对象列表 | `/api/customObject/list` |
| `getObjectFields` | 获取对象字段列表 | `/api/fieldSetup/queryField` |
> **注意**: 元数据查询 API (`/api/customObject/*` 和 `/api/fieldSetup/*`) 可能需要额外的权限或使用 Setup 服务域名。推荐使用 `getAllTabs` 方法查找对象 API 名称。
### 3. 数据操作
- **查询**: 普通查询、分页查询、带权限查询、SQL 查询
- **插入**: 普通插入、带权限插入
- **更新**: 普通更新、带权限更新
- **删除**: 普通删除、带权限删除
- **Upsert**: 插入或更新
### 4. 文件服务
- 图片上传(单张/多张)
- 文件上传(流/base64)
- 文件下载
- 附件管理
### 5. 消息服务
- 发送邮件
- 发送手机短信
- Chatter 微帖操作
### 6. 审批流程
- 查询待审批项目
- 批准/拒绝/重新分配
- 提交审批
### 7. 安全日志(v2.1.0 新增)
- 自动记录所有 API 调用(服务名、对象、响应码、耗时)
- 记录认证事件(token 请求、刷新、过期)
- 日志自动清理(保留最近 3 天)
- 支持日志查询、统计、导出
## 使用方法
### 快速开始
```bash
# 1. 获取 API 网关地址
curl -X GET "https://developer.apis.cloudcc.cn/oauth/apidomain?scope=cloudccCRM&orgId=YOUR_ORG_ID"
# 返回示例:
# {"result":true,"returnInfo":"","returnCode":"1","orgapi_address":"https://xxxx.apis.cloudcc.cn/lightningapi"}
# 2. 获取 accessToken
curl -X POST "https://xxxx.apis.cloudcc.cn/lightningapi/api/cauth/token" \
-H "Content-Type: application/json" \
-d '{
"username":"your-username",
"safetyMark":"your-safety-mark",
"clientId":"your-client-id",
"secretKey":"your-secret-key",
"orgId":"your-org-id",
"grant_type":"password"
}'
# 返回示例:
# {"data":{"accessToken":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."},"returnCode":"1","result":true}
```
### 日志管理(v2.1.0 新增)
```bash
# 查看最近的日志
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh view 50
# 查看统计信息
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh stats 3
# 搜索日志
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh search "productuplist"
# 导出日志
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh export
# 清理旧日志(保留最近 3 天)
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh cleanup 3
```
#### 日志格式
每条日志为 JSON 格式,包含以下字段:
```json
{
"timestamp": "2026-03-20 12:15:30",
"type": "API_REQUEST",
"service": "pageQuery",
"objectApi": "productuplist",
"responseCode": "1",
"durationMs": "125"
}
```
#### 日志类型
| 类型 | 说明 | 示例 |
|------|------|------|
| `API_REQUEST` | API 调用记录 | 记录所有 OpenAPI 调用 |
| `AUTH_EVENT` | 认证事件 | token 请求、刷新、过期 |
### 元数据查询示例(v2.0.0 新增)
#### 获取标准对象列表
```bash
curl -X POST "$API_DOMAIN/api/customObject/standardObjList" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
```
**返回示例**:
```json
{
"result": true,
"data": [
{"id": "account", "objname": "客户", "label": "Account", "objprefix": "001"},
{"id": "contact", "objname": "联系人", "label": "Contact", "objprefix": "003"},
{"id": "opportunity", "objname": "业务机会", "label": "Opportunity", "objprefix": "002"}
]
}
```
#### 获取自定义对象列表
```bash
curl -X POST "$API_DOMAIN/api/customObject/list" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":""}'
```
**返回示例**:
```json
{
"result": true,
"data": {
"objList": [
{"id": "2021A51AA0D3785lBzwh", "objLabel": "产品需求研发报备记录", "schemetable_name": "productuplist", "prefix": "b70"},
{"id": "2023D181015EDF0f7G1y", "objLabel": "需求池", "schemetable_name": "requirementpool", "prefix": "c75"}
]
}
}
```
#### 获取对象字段列表
```bash
curl -X POST "$API_DOMAIN/api/fieldSetup/queryField" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prefix":"b70"}'
```
**返回示例**:
```json
{
"result": true,
"data": {
"obj": {"id": "2021A51AA0D3785lBzwh", "label": "产品需求研发报备记录"},
"stdFields": [
{"id": "1", "labelName": "名称", "schemefieldName": "name", "schemefieldType": "S"}
],
"cusFields": [
{"id": "c1", "labelName": "产品", "schemefieldName": "product", "schemefieldType": "S"},
{"id": "c2", "labelName": "状态", "schemefieldName": "zhuangtai", "schemefieldType": "L"}
]
}
}
```
#### 通过选项卡查找对象(实用技巧)
```bash
# 搜索包含关键词的选项卡
curl -X POST "$API_DOMAIN/openApi/common" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceName":"getAllTabs"}' | jq '.data[] | select(.tab_name | test("产品 | 需求"))'
```
**返回示例**:
```json
{
"tab_name": "产品需求研发报备记录",
"objectApi": "productuplist",
"prefix": "b70",
"objId": "2021A51AA0D3785lBzwh"
}
```
### 数据查询示例
#### 普通查询 (cquery)
```json
{
"serviceName": "cquery",
"objectApiName": "Contact",
"expressions": "name='test'",
"isAddDelete": "false",
"fields": "name,createdate,createbyid"
}
```
#### 分页查询 (pageQuery)
```json
{
"serviceName": "pageQuery",
"objectApiName": "Account",
"fields": "id,name,phone",
"expressions": "industry='Technology'",
"pageNUM": 1,
"pageSize": 20
}
```
#### 插入数据 (insert)
```json
{
"serviceName": "insert",
"objectApiName": "Contact",
"data": "[{\"name\":\"张三\",\"phone\":\"13800138000\",\"email\":\"[email protected]\"}]"
}
```
#### 更新数据 (update)
```json
{
"serviceName": "update",
"objectApiName": "Contact",
"data": "[{\"id\":\"003202100BDF459HcI1R\",\"name\":\"李四\",\"phone\":\"13900139000\"}]"
}
```
#### 删除数据 (delete)
```json
{
"serviceName": "delete",
"objectApiName": "Contact",
"data": "[{\"id\":\"003202100BDF459HcI1R\"}]"
}
```
#### Upsert (插入或更新)
```json
{
"serviceName": "upsert",
"objectApiName": "Contact",
"data": "[{\"id\":\"003202100BDF459HcI1R\",\"name\":\"王五\"}]"
}
```
## API 接口列表
### 认证相关
| 接口 | 服务名 | 说明 |
|------|--------|------|
| 获取网关 | GET /oauth/apidomain | 动态获取组织 API 地址 |
| 获取 token | POST /api/cauth/token | 获取 accessToken |
| 验证 token | isValidWithBinding | 验证 accessToken 有效性 |
### 元数据查询(v2.0.0 新增)
| 接口 | 端点 | 说明 |
|------|------|------|
| 获取标准对象 | POST /api/customObject/standardObjList | 返回所有标准对象 |
| 获取自定义对象 | POST /api/customObject/list | 返回所有自定义对象 |
| 获取对象字段 | POST /api/fieldSetup/queryField | 根据 prefix 查询字段 |
| 获取选项卡 | POST /openApi/common (getAllTabs) | 返回所有选项卡配置 |
### 查询服务
| 接口 | 服务名 | 说明 |
|------|--------|------|
| 普通查询 | cquery | 基础查询 |
| 带权限查询 | cqueryWithRoleRight | 含权限控制的查询 |
| 分页查询 | pageQuery | 分页数据查询 |
| 分页带权限查询 | pageQueryWithRoleRight | 含权限的分页查询 |
| 获取查询权限 | getQueryPermisson | 获取对象查询权限 |
| SQL 查询 | cqlQueryWithLogInfo | 自定义 SQL 查询 |
| 静态查询 | cqlQueryWithStatic | 支持游标的静态查询 |
### 数据操作
| 接口 | 服务名 | 说明 |
|------|--------|------|
| 插入 | insert | 插入数据 |
| 带权限插入 | insertWithRoleRight | 含权限控制的插入 |
| 更新 | update | 更新数据 |
| 带权限更新 | updateWithRoleRight | 含权限控制的更新 |
| 删除 | delete | 删除数据 |
| 带权限删除 | deleteWithRoleRight | 含权限控制的删除 |
| Upsert | upsert | 插入或更新 |
| Upsert 带权限 | upsertWithRoleRight | 含权限的 upsert |
### 文件服务
| 接口 | 服务名 | 地址 | 说明 |
|------|--------|------|------|
| 上传图片 | uploadImg | /openApi/file | 单张图片上传 |
| 上传多图 | uploadImgMany | /openApi/file | 最多 9 张 |
| 获取图片属性 | getImgProperty | /openApi/common | 查询图片信息 |
| 上传文件 | uploadFile | /openApi/file | 文件流上传 |
| 上传附件 | uploadAttachement | /openApi/common | base64 上传 |
| 下载文件 | downloadFile | /openApi/downloadFile | GET 请求 |
| 下载附件 | downloadAttachement | /openApi/common | base64 下载 |
| 删除附件 | deleteAttachment | /openApi/common | 删除附件 |
| 删除文件 | deleteFile | /openApi/common | 删除文件 |
### 消息服务
| 接口 | 服务名 | 说明 |
|------|--------|------|
| 发送邮件 | sendEmail | 支持模板变量 |
| 发送短信 | telMessage | 手机消息服务 |
### Chatter 服务
| 接口 | 服务名 | 说明 |
|------|--------|------|
| 获取 Chatter | getChatters01 | 查询微帖内容 |
| 获取我的 Chatter | getMyChatter01 | 我追随的内容 |
| 获取追随者 | getFollowUsers | 查询追随关系 |
| 发布帖子 | addMicroPostF | 普通帖子 |
| 发布文件帖 | addMicroPostD | 带文件的帖子 |
| 发布链接帖 | addMicroPostL | 带链接的帖子 |
| 发布投票帖 | addMicroPostV | 投票类型帖子 |
| 投票 | voteMicroPost | 对帖子投票 |
| 发布评论 | addMicroComment | 普通评论 |
| 文件评论 | addMicroCommentFile | 带文件评论 |
| 点赞帖子 | praiseFeed | 喜欢/取消喜欢 |
| 点赞评论 | praiseComment | 评论点赞 |
| 收藏帖子 | favoriteFeed | 收藏/取消收藏 |
| 删除帖子 | removeMicroPost | 删除微帖 |
| 删除评论 | removeMicroComment | 删除评论 |
| 追随操作 | operateFollowRelation | 追随/取消追随 |
### 审批服务
| 接口 | 服务名 | 说明 |
|------|--------|------|
| 待审批列表 | getApprovalPaddingList | 查询待审批项目 |
| 批准 | doApproved | 批准操作 |
| 拒绝 | doRejected | 拒绝操作 |
| 重新分配 | doReassign | 重新分配审批人 |
| 调回 | reCall | 调回审批 |
| 提交审批 | submitForApproval | 提交审批流程 |
### 其他服务
| 接口 | 服务名 | 说明 |
|------|--------|------|
| 获取应用列表 | getAppList | 应用程序列表 |
| 获取选项卡 | getAllTabs | 选项卡信息 |
| 获取应用和选项卡 | getAppAndTabList | 合并查询 |
| 获取搜索设置 | getMySetupObjs | 搜索配置 |
| 获取选项列表值 | getPickListValue | 字段选项值 |
| 保存依赖关系 | saveDependency | 选项依赖配置 |
| 自助服务 | customService | MongoDB 数据操作 |
## 返回码说明
| 代码 | 说明 |
|------|------|
| 1 | 调用成功 |
| -1 | 调用成功但接口异常 |
| -2 | 调用不成功(如失效) |
| -3 | 参数输入有误 |
## 最佳实践
### 1. 查找对象的完整流程(v2.0.0)
```bash
# 步骤 1: 通过选项卡名称查找对象(最直观)
curl -X POST "$API_DOMAIN/openApi/common" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceName":"getAllTabs"}' | jq '.data[] | select(.tab_name | test("产品需求"))'
# 返回:{"objectApi":"productuplist","prefix":"b70","objId":"2021A51AA0D3785lBzwh"}
# 步骤 2: 使用 objectApi 查询数据
curl -X POST "$API_DOMAIN/openApi/common" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceName":"pageQuery","objectApiName":"productuplist","pageNUM":1,"pageSize":20}'
# 步骤 3: 使用 prefix 查询字段结构
curl -X POST "$API_DOMAIN/api/fieldSetup/queryField" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"prefix":"b70"}'
```
### 2. 网关地址动态获取
**禁止写死 API 地址**,组织网关可能根据负载漂移:
```bash
# 每次启动时获取
API_DOMAIN=$(curl -s "https://developer.apis.cloudcc.cn/oauth/apidomain?scope=cloudccCRM&orgId=$ORG_ID" | jq -r '.orgapi_address')
```
### 3. accessToken 管理
- accessToken 放入请求头:`accessToken: <value>`
- 定期验证有效性(使用 `isValidWithBinding`)
- 失效时重新获取
### 4. 批量操作
- 插入/更新/删除支持批量操作
- data 参数为 JSON 数组:`[{...}, {...}]`
- 注意 API 调用配额限制
### 5. 特殊字符处理
URL 中的特殊字符需要 URL 编码:
```java
java.net.URLEncoder.encode("%", "UTF-8")
```
### 6. 字段命名规范
- 服务名和参数名**首字母小写**
- 如文档中大写,请改为小写
## API 配额限制
| 版本 | 基础次数 | 每用户额外 | 最大限额/24h |
|------|---------|-----------|-------------|
| 专业版 | 5000 | 用户数×150 | 10000 |
| 企业版 | 50000 | 用户数×300 | 200000 |
| 旗舰版 | 50000 | 用户数×500 | 500000 |
| 超级版 | 50000 | 用户数×500 | 500000 |
> ⚠️ 禁止超负荷请求,否则 API Client 可能被停用
## 故障排查
### 常见问题
1. **accessToken 失效**: 重新调用 `/api/cauth/token`
2. **网关地址变化**: 重新获取 `/oauth/apidomain`
3. **权限不足**: 检查简档权限或使用带权限接口
4. **参数错误**: 检查字段名是否小写、JSON 格式是否正确
5. **对象不存在**: 使用 `getAllTabs` 或 `getCustomObjects` 确认对象 API 名称
### 调试技巧
```bash
# 1. 验证 token 有效性
curl -X POST "$API_DOMAIN/openApi/common" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceName":"isValidWithBinding"}'
# 2. 检查返回码
# returnCode: "1" 表示成功
# returnCode: "-1" 表示接口异常
# returnCode: "-2" 表示 token 失效
# returnCode: "-3" 表示参数错误
# 3. 查找对象 API 名称
curl -X POST "$API_DOMAIN/openApi/common" \
-H "accessToken: $TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceName":"getAllTabs"}' | jq '.data[] | select(.tab_name | test("关键词"))'
```
## 版本历史
### v2.1.0 (2026-03-20)
- ✅ 新增安全日志功能
- ✅ 支持 `logger.sh` - 日志管理工具
- ✅ 自动记录所有 API 调用(服务名、对象、响应码、耗时)
- ✅ 自动记录认证事件(token 请求、刷新、过期)
- ✅ 日志自动清理(保留最近 3 天)
- ✅ 支持日志查询、统计、导出
### v2.0.0 (2026-03-20)
- ✅ 新增元数据查询能力
- ✅ 支持 `getStandardObjects` - 获取标准对象列表
- ✅ 支持 `getCustomObjects` - 获取自定义对象列表
- ✅ 支持 `getObjectFields` - 获取对象字段列表
- ✅ 支持 `getAllTabs` - 获取选项卡信息
- ✅ 新增对象查找最佳实践文档
### v1.0.0 (2026-03-20)
- ✅ 初始版本
- ✅ 完整 API 接口文档
- ✅ 认证管理
- ✅ 数据 CRUD 操作
- ✅ 文件服务
- ✅ 消息服务
- ✅ Chatter 接口
- ✅ 审批流程
---
**维护人**: 鲁班
**参考文档**: https://help.cloudcc.cn/product03/apigai-lan/
FILE:config.json
{
"orgId": "2010000000050855WyHh",
"username": "[email protected]",
"safetyMark": "8SiiWJT49Mu4xLmVGwpgH8LRd",
"clientId": "zlMXtNhm6Uy8ZJ6fTiHZG91TF",
"secretKey": "fb5b02f4-aae9-415c-bebf-1a304ff6b1f2",
"apiDomain": "https://szyd.apis.cloudcc.cn/lightningapi",
"accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJjbG91ZGNjIiwibG9naW5OYW1lIjoiZGFxaWFvQGNsb3VkY2MuYWkiLCJiaW5kaW5nIjoiMjJlODliMmQtNjkzNy00N2ExLTljZTAtZDBhNTEyOTIzNDAxIiwiQ2xpZW50SWQiOiJ6bE1YdE5obTZVeThaSjZmVGlIWkc5MVRGIiwiZXhwIjoxNzc0MTQ4ODg4LCJvcmdJZCI6IjIwMTAwMDAwMDAwNTA4NTVXeUhoIn0.U7XotCYizWhinjhuRlpH3zFyJxOyjrAm5WgeJn-1KXc",
"tokenExpiresAt": 1773983288
}
FILE:PUBLISH.md
# 发布到 ClawHub 指南
## 前置准备
### 1. 登录 ClawHub
```bash
# 登录(会打开浏览器)
clawhub login
# 或手动访问登录链接
# https://clawhub.ai/cli/auth
```
### 2. 验证登录状态
```bash
clawhub whoami
```
---
## 发布技能
### 方法 1: 直接发布
```bash
cd ~/.openclaw/skills/cloudcc-openapi-withobject
# 发布技能
clawhub publish .
```
### 方法 2: 使用 sync 同步发布
```bash
cd ~/.openclaw/skills/
# 同步并发布所有新/更新的技能
clawhub sync
```
---
## 发布信息
| 属性 | 值 |
|------|-----|
| **技能名称** | cloudcc-openapi-withobject |
| **版本** | 2.1.0 |
| **描述** | CloudCC OpenAPI 调用技能 - 提供完整的 REST API 接口调用能力,支持对象/字段元数据查询和安全日志 |
| **作者** | 鲁班 |
| **许可证** | MIT |
| **分类** | productivity, business, development |
| **关键词** | cloudcc, crm, api, openapi, rest, metadata, logging |
---
## 发布后验证
```bash
# 搜索已发布的技能
clawhub search cloudcc-openapi-withobject
# 查看技能详情
clawhub inspect cloudcc-openapi-withobject
# 安装测试
clawhub install cloudcc-openapi-withobject
```
---
## 更新技能
```bash
# 修改技能后更新版本
# 1. 更新 package.json 中的 version
# 2. 更新 SKILL.md 中的版本历史
# 3. 重新发布
cd ~/.openclaw/skills/cloudcc-openapi-withobject
clawhub publish .
```
---
## 注意事项
1. **版本号** - 每次发布必须更新版本号(semver 格式)
2. **SKILL.md** - 必须包含完整的技能文档
3. **package.json** - 必须包含正确的元数据
4. **权限声明** - 在 package.json 中声明网络和文件权限
5. **测试** - 发布前确保所有脚本正常工作
---
## 故障排查
### 登录失败
```bash
# 清除登录状态重试
clawhub logout
clawhub login
```
### 发布失败
```bash
# 检查 package.json 格式
cat package.json | jq .
# 查看详细错误
clawhub publish . --verbose
```
### 技能已存在
```bash
# 更新现有技能(需要更高版本号)
clawhub publish . --force
```
---
## 技能包内容
```
cloudcc-openapi-withobject/
├── package.json # 技能元数据(发布必需)
├── SKILL.md # 完整技能文档
├── README.md # 快速入门指南
├── PUBLISH.md # 本文件
├── config.example.json # 配置模板
├── logs/ # 日志目录(不发布)
│ └── .gitkeep
└── scripts/
├── logger.sh # 日志管理工具
├── get-token.sh # 获取 accessToken
├── call-api.sh # 通用 API 调用
├── find-object.sh # 查找对象
├── get-objects.sh # 获取对象列表
└── get-fields.sh # 获取对象字段
```
---
## ClawHub 链接
- **技能页面**: https://clawhub.com/skills/cloudcc-openapi-withobject
- **Registry**: https://clawhub.ai
- **文档**: https://docs.openclaw.ai
FILE:README.md
# cloudcc-openapi-withobject
CloudCC OpenAPI 调用技能 - 提供完整的 REST API 接口调用能力,支持对象/字段元数据查询
**版本**: 2.1.0
**更新日期**: 2026-03-20
## 快速开始
### 1. 配置认证信息
```bash
# 复制配置模板
cp ~/.openclaw/skills/cloudcc-openapi-withobject/config.example.json \
~/.openclaw/skills/cloudcc-openapi-withobject/config.json
# 编辑配置文件,填写你的认证信息
vi ~/.openclaw/skills/cloudcc-openapi-withobject/config.json
```
### 2. 获取认证凭据
| 凭据 | 获取方式 |
|------|---------|
| `orgId` | 系统设置 - 公司信息 |
| `safetyMark` | 个人信息 - 重置我的安全标记(邮箱接收) |
| `clientId` | 管理设置 - 安全性控制 - 连接的应用程序 |
| `secretKey` | 管理设置 - 安全性控制 - 连接的应用程序 |
### 3. 获取 accessToken
```bash
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/get-token.sh
```
### 4. 调用 API
```bash
# 查询联系人
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/call-api.sh \
cquery \
'{"objectApiName":"Contact","fields":"id,name,phone","expressions":"name=\"测试\""}'
# 插入数据
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/call-api.sh \
insert \
'{"objectApiName":"Contact","data":[{"name":"张三","phone":"13800138000"}]}'
```
## 元数据查询(v2.0.0 新增)
### 查找对象
```bash
# 通过选项卡名称搜索对象
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/find-object.sh \
-k "产品需求"
# 输出:
# 选项卡名称 对象 API 前缀 对象 ID
# -------- -------- ---- --------
# 产品需求研发报备记录 productuplist b70 2021A51AA0D3785lBzwh
```
### 获取对象列表
```bash
# 获取所有对象(标准 + 自定义)
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/get-objects.sh
# 只获取自定义对象
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/get-objects.sh -c
# 搜索包含关键词的对象
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/get-objects.sh -k "需求"
```
### 获取对象字段
```bash
# 方法 1: 通过对象 API 名称查询字段(需要额外权限)
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/get-fields.sh \
-o productuplist
# 方法 2: 通过对象前缀查询字段(需要额外权限)
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/get-fields.sh \
-p b70
# 方法 3: 通过查询一条记录获取所有字段(推荐,无需额外权限)
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/call-api.sh \
cquery \
'{"objectApiName":"productuplist","expressions":"id=\"b7020254FF6E5C5PvLWi\""}' | jq '.data[0] | keys'
```
## 常用 API 示例
### 普通查询
```json
{
"serviceName": "cquery",
"objectApiName": "Account",
"fields": "id,name,phone,industry",
"expressions": "industry='Technology'"
}
```
### 分页查询
```json
{
"serviceName": "pageQuery",
"objectApiName": "Opportunity",
"fields": "id,name,amount,stage",
"expressions": "stage='Prospecting'",
"pageNUM": 1,
"pageSize": 20
}
```
### SQL 查询
```json
{
"serviceName": "cqlQueryWithLogInfo",
"objectApiName": "Account",
"expressions": "select id, name, industry from Account where createdate > '2026-01-01'"
}
```
### 发送邮件
```json
{
"serviceName": "sendEmail",
"data": {
"toAddress": "[email protected]",
"ccAddress": "[email protected]",
"subject": "测试邮件",
"content": "这是一封测试邮件",
"isText": true
}
}
```
## 目录结构
```
cloudcc-openapi-withobject/
├── SKILL.md # 完整技能文档
├── README.md # 快速入门(本文件)
├── config.example.json # 配置模板
├── config.json # 实际配置(需自行创建)
├── logs/ # 日志目录(自动创建)
│ └── api-calls.log # API 调用日志
└── scripts/
├── get-token.sh # 获取 accessToken(带日志)
├── call-api.sh # 通用 API 调用(带日志)
├── logger.sh # 日志管理工具(v2.1.0)
├── find-object.sh # 查找对象(v2.0.0)
├── get-objects.sh # 获取对象列表(v2.0.0)
└── get-fields.sh # 获取对象字段(v2.0.0)
```
## 脚本说明
| 脚本 | 说明 | 示例 |
|------|------|------|
| `get-token.sh` | 获取/刷新 accessToken(带日志) | `./get-token.sh` |
| `call-api.sh` | 调用任意 OpenAPI(带日志) | `./call-api.sh cquery '{...}'` |
| `logger.sh` | 日志管理工具(v2.1.0) | `./logger.sh view 50` |
| `find-object.sh` | 通过选项卡名称查找对象 | `./find-object.sh -k "产品"` |
| `get-objects.sh` | 获取标准/自定义对象列表 | `./get-objects.sh -c -k "需求"` |
| `get-fields.sh` | 获取对象字段结构 | `./get-fields.sh -p b70` |
### 日志管理命令(v2.1.0)
| 命令 | 说明 | 示例 |
|------|------|------|
| `view [count]` | 查看最近的日志 | `./logger.sh view 50` |
| `stats [days]` | 显示统计信息 | `./logger.sh stats 3` |
| `search <kw>` | 搜索日志 | `./logger.sh search productuplist` |
| `export` | 导出日志为 JSON | `./logger.sh export` |
| `cleanup [days]` | 清理旧日志 | `./logger.sh cleanup 3` |
## 注意事项
1. **网关地址动态获取** - 禁止写死 API 地址
2. **accessToken 有效期** - 默认 2 小时,过期自动刷新
3. **字段名小写** - 服务名和参数名首字母必须小写
4. **API 配额** - 注意每日调用限额
5. **安全日志** - 所有 API 调用自动记录到 `logs/api-calls.log`,保留最近 3 天
## 参考文档
- [CloudCC OpenAPI 概览](https://help.cloudcc.cn/product03/apigai-lan/)
- [CloudCC 系统接口说明书](https://help.cloudcc.cn/)
---
**版本**: 2.1.0
**作者**: 鲁班
**创建日期**: 2026-03-20
**更新日期**: 2026-03-20
## 安全日志(v2.1.0)
### 日志位置
```
~/.openclaw/skills/cloudcc-openapi-withobject/logs/api-calls.log
```
### 日志内容
所有 API 调用自动记录,包含:
- 时间戳
- 服务名称(cquery, pageQuery, insert 等)
- 对象 API 名称
- 响应码
- 耗时(毫秒)
### 日志管理
```bash
# 查看最近 50 条日志
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh view 50
# 查看最近 3 天统计
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh stats 3
# 搜索特定对象的调用
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh search "productuplist"
# 导出日志
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh export
# 清理 3 天前的日志
~/.openclaw/skills/cloudcc-openapi-withobject/scripts/logger.sh cleanup 3
```
### 日志保留策略
- 默认保留最近 **3 天** 的日志
- 可通过 `cleanup` 命令手动清理
- 可在 `config.json` 中配置 `logging.maxLogDays`
FILE:config.example.json
{
"orgId": "your-org-id-here",
"username": "[email protected]",
"safetyMark": "get-from-email",
"clientId": "get-from-connected-apps",
"secretKey": "get-from-connected-apps",
"apiDomain": "",
"accessToken": "",
"tokenExpiresAt": 0,
"logging": {
"enabled": true,
"logDir": "./logs",
"maxLogDays": 3,
"verbose": false
}
}
FILE:package.json
{
"name": "cloudcc-openapi-withobject",
"version": "2.1.0",
"description": "CloudCC OpenAPI 调用技能 - 提供完整的 REST API 接口调用能力,支持对象/字段元数据查询和安全日志",
"author": "鲁班",
"license": "MIT",
"keywords": [
"cloudcc",
"crm",
"api",
"openapi",
"rest",
"metadata",
"logging"
],
"categories": [
"productivity",
"business",
"development"
],
"repository": {
"type": "git",
"url": "https://github.com/openclaw/skills.git",
"directory": "cloudcc-openapi-withobject"
},
"engines": {
"openclaw": ">=0.7.0"
},
"scripts": {
"test": "echo \"No tests specified\""
},
"skill": {
"name": "cloudcc-openapi-withobject",
"version": "2.1.0",
"entry": "SKILL.md",
"readme": "README.md",
"scripts": [
"scripts/get-token.sh",
"scripts/call-api.sh",
"scripts/logger.sh",
"scripts/find-object.sh",
"scripts/get-objects.sh",
"scripts/get-fields.sh"
],
"config": {
"config.example.json": "config.example.json"
},
"capabilities": [
"http_request",
"file_read",
"file_write",
"exec"
],
"permissions": [
"network:cloudcc.cn",
"network:developer.apis.cloudcc.cn"
]
},
"bugs": {
"url": "https://github.com/openclaw/skills/issues"
},
"homepage": "https://clawhub.com/skills/cloudcc-openapi-withobject"
}
FILE:scripts/get-token.sh
#!/bin/bash
# get-token.sh - 获取 CloudCC API accessToken(带日志记录)
SCRIPT_DIR="$(cd "$(dirname "BASH_SOURCE[0]")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config.json"
LOGGER_SCRIPT="$SCRIPT_DIR/logger.sh"
# 读取配置
if [ ! -f "$CONFIG_FILE" ]; then
echo "❌ 配置文件不存在:$CONFIG_FILE"
echo "请复制 config.example.json 为 config.json 并填写配置"
exit 1
fi
ORG_ID=$(cat "$CONFIG_FILE" | jq -r '.orgId')
USERNAME=$(cat "$CONFIG_FILE" | jq -r '.username')
SAFETY_MARK=$(cat "$CONFIG_FILE" | jq -r '.safetyMark')
CLIENT_ID=$(cat "$CONFIG_FILE" | jq -r '.clientId')
SECRET_KEY=$(cat "$CONFIG_FILE" | jq -r '.secretKey')
# 检查配置
if [ "$ORG_ID" == "null" ] || [ "$ORG_ID" == "your-org-id-here" ]; then
echo "❌ 请配置 orgId"
exit 1
fi
echo "📡 正在获取 API 网关地址..."
# 记录请求开始时间(毫秒)
START_TIME=$(date +%s%3N 2>/dev/null || echo $(($(date +%s) * 1000)))
# 1. 获取 API 网关地址
API_DOMAIN_RESPONSE=$(curl -s "https://developer.apis.cloudcc.cn/oauth/apidomain?scope=cloudccCRM&orgId=$ORG_ID")
API_DOMAIN=$(echo "$API_DOMAIN_RESPONSE" | jq -r '.orgapi_address')
if [ "$API_DOMAIN" == "null" ] || [ -z "$API_DOMAIN" ]; then
echo "❌ 获取 API 网关地址失败"
echo "响应:$API_DOMAIN_RESPONSE"
# 记录错误日志
if [ -x "$LOGGER_SCRIPT" ]; then
bash "$LOGGER_SCRIPT" log_auth_event "GATEWAY_REQUEST" "FAILED" "获取网关地址失败" 2>/dev/null
fi
exit 1
fi
echo "✅ API 网关:$API_DOMAIN"
# 更新配置
jq --arg domain "$API_DOMAIN" '.apiDomain = $domain' "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
echo "🔐 正在获取 accessToken..."
# 2. 获取 accessToken
TOKEN_RESPONSE=$(curl -s -X POST "$API_DOMAIN/api/cauth/token" \
-H "Content-Type: application/json" \
-d "{
\"username\":\"$USERNAME\",
\"safetyMark\":\"$SAFETY_MARK\",
\"clientId\":\"$CLIENT_ID\",
\"secretKey\":\"$SECRET_KEY\",
\"orgId\":\"$ORG_ID\",
\"grant_type\":\"password\"
}")
RESULT=$(echo "$TOKEN_RESPONSE" | jq -r '.result')
RETURN_CODE=$(echo "$TOKEN_RESPONSE" | jq -r '.returnCode')
# 计算耗时
END_TIME=$(date +%s%3N)
DURATION_MS=$((END_TIME - START_TIME))
if [ "$RESULT" != "true" ] || [ "$RETURN_CODE" != "1" ]; then
echo "❌ 获取 accessToken 失败"
echo "响应:$TOKEN_RESPONSE"
# 记录错误日志
if [ -x "$LOGGER_SCRIPT" ]; then
bash "$LOGGER_SCRIPT" log_auth_event "TOKEN_REQUEST" "FAILED" "获取 token 失败:$RETURN_CODE" 2>/dev/null
fi
exit 1
fi
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.data.accessToken')
if [ "$ACCESS_TOKEN" == "null" ] || [ -z "$ACCESS_TOKEN" ]; then
echo "❌ accessToken 为空"
# 记录错误日志
if [ -x "$LOGGER_SCRIPT" ]; then
bash "$LOGGER_SCRIPT" log_auth_event "TOKEN_REQUEST" "FAILED" "accessToken 为空" 2>/dev/null
fi
exit 1
fi
# 计算过期时间(2 小时后)
EXPIRES_AT=$(($(date +%s) + 7200))
# 更新配置
jq --arg token "$ACCESS_TOKEN" \
--argjson expires "$EXPIRES_AT" \
'.accessToken = $token | .tokenExpiresAt = $expires' "$CONFIG_FILE" > "$CONFIG_FILE.tmp" && mv "$CONFIG_FILE.tmp" "$CONFIG_FILE"
echo "✅ accessToken 获取成功 (耗时:DURATION_MSms)"
echo "📝 Token: 0:20..."
echo "⏰ 过期时间:$(date -d "@$EXPIRES_AT" '+%Y-%m-%d %H:%M:%S')"
# 记录成功日志
if [ -x "$LOGGER_SCRIPT" ]; then
bash "$LOGGER_SCRIPT" log_auth_event "TOKEN_REQUEST" "SUCCESS" "获取 token 成功,有效期 2 小时" 2>/dev/null
fi
echo ""
echo "💡 使用方法:"
echo " curl -X POST \"$API_DOMAIN/openApi/common\" \\"
echo " -H \"accessToken: $ACCESS_TOKEN\" \\"
echo " -H \"Content-Type: application/json\" \\"
echo " -d '{\"serviceName\":\"cquery\",...}'"
FILE:scripts/find-object.sh
#!/bin/bash
# find-object.sh - 通过选项卡名称查找 CloudCC 对象
SCRIPT_DIR="$(cd "$(dirname "BASH_SOURCE[0]")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config.json"
# 读取配置
if [ ! -f "$CONFIG_FILE" ]; then
echo "❌ 配置文件不存在:$CONFIG_FILE"
exit 1
fi
API_DOMAIN=$(cat "$CONFIG_FILE" | jq -r '.apiDomain')
ACCESS_TOKEN=$(cat "$CONFIG_FILE" | jq -r '.accessToken')
TOKEN_EXPIRES=$(cat "$CONFIG_FILE" | jq -r '.tokenExpiresAt')
# 检查是否需要刷新 token
CURRENT_TIME=$(date +%s)
if [ "$ACCESS_TOKEN" == "null" ] || [ "$TOKEN_EXPIRES" == "null" ] || [ "$CURRENT_TIME" -ge "$TOKEN_EXPIRES" ]; then
echo "⚠️ token 已过期或不存在,正在刷新..."
"$SCRIPT_DIR/get-token.sh"
if [ $? -ne 0 ]; then
echo "❌ 刷新 token 失败"
exit 1
fi
API_DOMAIN=$(cat "$CONFIG_FILE" | jq -r '.apiDomain')
ACCESS_TOKEN=$(cat "$CONFIG_FILE" | jq -r '.accessToken')
fi
# 解析参数
SEARCH_KEYWORD=""
OUTPUT_JSON=false
while [[ $# -gt 0 ]]; do
case $1 in
-k|--keyword)
SEARCH_KEYWORD="$2"
shift 2
;;
-j|--json)
OUTPUT_JSON=true
shift
;;
-h|--help)
echo "用法:$0 -k <关键词> [选项]"
echo ""
echo "选项:"
echo " -k, --keyword KEY 搜索关键词(必填)"
echo " -j, --json 输出 JSON 格式"
echo " -h, --help 显示帮助"
echo ""
echo "示例:"
echo " $0 -k 产品需求 # 搜索包含'产品需求'的选项卡"
echo " $0 -k 客户 -j # 搜索'客户'并输出 JSON"
exit 0
;;
*)
echo "❌ 未知选项:$1"
exit 1
;;
esac
done
# 验证参数
if [ -z "$SEARCH_KEYWORD" ]; then
echo "❌ 必须指定搜索关键词 (-k)"
echo "使用 -h 查看帮助"
exit 1
fi
echo "🔍 搜索选项卡:'$SEARCH_KEYWORD'"
echo "================================"
echo ""
# 获取所有选项卡
TABS_RESPONSE=$(curl -s -X POST "$API_DOMAIN/openApi/common" \
-H "accessToken: $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"serviceName":"getAllTabs"}')
RESULT=$(echo "$TABS_RESPONSE" | jq -r '.result')
if [ "$RESULT" != "true" ]; then
echo "❌ 获取选项卡失败:$(echo "$TABS_RESPONSE" | jq -r '.returnInfo')"
exit 1
fi
# 过滤并显示结果
if [ "$OUTPUT_JSON" = true ]; then
echo "$TABS_RESPONSE" | jq --arg kw "$SEARCH_KEYWORD" \
'.data[] | select(.tab_name | ascii_downcase | contains($kw | ascii_downcase))'
else
COUNT=$(echo "$TABS_RESPONSE" | jq --arg kw "$SEARCH_KEYWORD" \
'[.data[] | select(.tab_name | ascii_downcase | contains($kw | ascii_downcase))] | length')
echo "找到 $COUNT 个匹配项"
echo ""
if [ "$COUNT" -gt 0 ]; then
printf "%-40s %-25s %-10s %-20s\n" "选项卡名称" "对象 API" "前缀" "对象 ID"
printf "%-40s %-25s %-10s %-20s\n" "--------" "--------" "----" "--------"
echo "$TABS_RESPONSE" | jq -r --arg kw "$SEARCH_KEYWORD" \
'.data[] | select(.tab_name | ascii_downcase | contains($kw | ascii_downcase)) |
"\(.tab_name | .[0:38])\t\(.objectApi // "N/A")\t\(.prefix // "N/A")\t\(.objId // "N/A")"' | \
while IFS=$'\t' read -r name api prefix objid; do
printf "%-40s %-25s %-10s %-20s\n" "$name" "$api" "$prefix" "$objid"
done
fi
fi
echo ""
echo "💡 提示:"
echo " 使用 -o 参数查询对象字段:./get-fields.sh -o <对象 API>"
echo " 使用 -p 参数查询对象字段:./get-fields.sh -p <前缀>"
FILE:scripts/get-fields.sh
#!/bin/bash
# get-fields.sh - 获取 CloudCC 对象字段列表
SCRIPT_DIR="$(cd "$(dirname "BASH_SOURCE[0]")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config.json"
# 读取配置
if [ ! -f "$CONFIG_FILE" ]; then
echo "❌ 配置文件不存在:$CONFIG_FILE"
exit 1
fi
API_DOMAIN=$(cat "$CONFIG_FILE" | jq -r '.apiDomain')
ACCESS_TOKEN=$(cat "$CONFIG_FILE" | jq -r '.accessToken')
TOKEN_EXPIRES=$(cat "$CONFIG_FILE" | jq -r '.tokenExpiresAt')
# 检查是否需要刷新 token
CURRENT_TIME=$(date +%s)
if [ "$ACCESS_TOKEN" == "null" ] || [ "$TOKEN_EXPIRES" == "null" ] || [ "$CURRENT_TIME" -ge "$TOKEN_EXPIRES" ]; then
echo "⚠️ token 已过期或不存在,正在刷新..."
"$SCRIPT_DIR/get-token.sh"
if [ $? -ne 0 ]; then
echo "❌ 刷新 token 失败"
exit 1
fi
API_DOMAIN=$(cat "$CONFIG_FILE" | jq -r '.apiDomain')
ACCESS_TOKEN=$(cat "$CONFIG_FILE" | jq -r '.accessToken')
fi
# 解析参数
OBJECT_PREFIX=""
OBJECT_API=""
SHOW_STANDARD=false
SHOW_CUSTOM=false
SHOW_ALL=false
while [[ $# -gt 0 ]]; do
case $1 in
-p|--prefix)
OBJECT_PREFIX="$2"
shift 2
;;
-o|--object)
OBJECT_API="$2"
shift 2
;;
-s|--standard)
SHOW_STANDARD=true
shift
;;
-c|--custom)
SHOW_CUSTOM=true
shift
;;
-a|--all)
SHOW_ALL=true
shift
;;
-h|--help)
echo "用法:$0 <-p 前缀 | -o 对象 API> [选项]"
echo ""
echo "选项:"
echo " -p, --prefix PFIX 对象前缀(如 b70, 001, 003)"
echo " -o, --object API 对象 API 名称(如 productuplist, Account)"
echo " -s, --standard 只显示标准字段"
echo " -c, --custom 只显示自定义字段"
echo " -a, --all 显示所有字段(默认)"
echo " -h, --help 显示帮助"
echo ""
echo "示例:"
echo " $0 -p b70 # 获取前缀 b70 的字段"
echo " $0 -o productuplist # 获取 productuplist 对象的字段"
echo " $0 -p 001 -s # 只获取 Account 的标准字段"
exit 0
;;
*)
echo "❌ 未知选项:$1"
exit 1
;;
esac
done
# 验证参数
if [ -z "$OBJECT_PREFIX" ] && [ -z "$OBJECT_API" ]; then
echo "❌ 必须指定对象前缀 (-p) 或对象 API 名称 (-o)"
echo "使用 -h 查看帮助"
exit 1
fi
# 如果提供的是对象 API 名称,需要先查找前缀
if [ -n "$OBJECT_API" ] && [ -z "$OBJECT_PREFIX" ]; then
echo "🔍 查找对象 '$OBJECT_API' 的前缀..."
# 从自定义对象中查找
CUSTOM_RESPONSE=$(curl -s -X POST "$API_DOMAIN/api/customObject/list" \
-H "accessToken: $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":""}')
OBJECT_PREFIX=$(echo "$CUSTOM_RESPONSE" | jq -r --arg api "$OBJECT_API" \
'.data.objList[] | select(.schemetable_name == $api) | .prefix')
if [ -z "$OBJECT_PREFIX" ] || [ "$OBJECT_PREFIX" == "null" ]; then
# 从标准对象中查找
STANDARD_RESPONSE=$(curl -s -X POST "$API_DOMAIN/api/customObject/standardObjList" \
-H "accessToken: $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{}')
OBJECT_PREFIX=$(echo "$STANDARD_RESPONSE" | jq -r --arg api "$OBJECT_API" \
'.data[] | select(.label == $api) | .objprefix')
fi
if [ -z "$OBJECT_PREFIX" ] || [ "$OBJECT_PREFIX" == "null" ]; then
echo "❌ 未找到对象 '$OBJECT_API'"
exit 1
fi
echo "✅ 找到前缀:$OBJECT_PREFIX"
fi
echo "📋 对象字段列表 (prefix: $OBJECT_PREFIX)"
echo "========================================"
echo ""
# 获取字段信息
FIELDS_RESPONSE=$(curl -s -X POST "$API_DOMAIN/api/fieldSetup/queryField" \
-H "accessToken: $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"prefix\":\"$OBJECT_PREFIX\"}")
RESULT=$(echo "$FIELDS_RESPONSE" | jq -r '.result')
if [ "$RESULT" != "true" ]; then
echo "❌ 获取字段失败:$(echo "$FIELDS_RESPONSE" | jq -r '.returnInfo')"
exit 1
fi
# 显示对象信息
OBJ_INFO=$(echo "$FIELDS_RESPONSE" | jq '.data.obj')
if [ "$OBJ_INFO" != "null" ]; then
echo "对象信息:"
echo "$FIELDS_RESPONSE" | jq '.data.obj'
echo ""
fi
# 显示标准字段
if [ "$SHOW_CUSTOM" = false ]; then
STD_COUNT=$(echo "$FIELDS_RESPONSE" | jq '.data.stdFields | length')
echo "标准字段 ($STD_COUNT 个):"
echo "-------------------"
if [ "$STD_COUNT" -gt 0 ]; then
echo "$FIELDS_RESPONSE" | jq -r '.data.stdFields[] | " \(.labelName) -> \(.schemefieldName) (\(.schemefieldType))"'
else
echo " (无标准字段或字段为空)"
fi
echo ""
fi
# 显示自定义字段
if [ "$SHOW_STANDARD" = false ]; then
CUS_COUNT=$(echo "$FIELDS_RESPONSE" | jq '.data.cusFields | length')
echo "自定义字段 ($CUS_COUNT 个):"
echo "---------------------"
if [ "$CUS_COUNT" -gt 0 ]; then
echo "$FIELDS_RESPONSE" | jq -r '.data.cusFields[] | " \(.labelName) -> \(.schemefieldName) (\(.schemefieldType))"'
else
echo " (无自定义字段或字段为空)"
fi
echo ""
fi
# 显示字段类型说明
if [ "$SHOW_ALL" = true ]; then
echo "📝 字段类型说明:"
echo " S=文本,N=数字,D=日期,F=日期/时间,L=选项列表,Q=多选列表"
echo " Y=查找关系,M=主详关系,B=复选框,E=邮件,H=电话,U=URL"
echo " J=文本区,A=富文本,IMG=图片,FL=文件,C=币种,P=百分比"
fi
FILE:scripts/logger.sh
#!/bin/bash
# logger.sh - CloudCC API 调用日志记录工具
SCRIPT_DIR="$(cd "$(dirname "BASH_SOURCE[0]")" && pwd)"
LOG_DIR="$SCRIPT_DIR/../logs"
LOG_FILE="$LOG_DIR/api-calls.log"
MAX_LOG_DAYS=3
# 创建日志目录
mkdir -p "$LOG_DIR"
# 日志级别
LOG_LEVEL_INFO="INFO"
LOG_LEVEL_WARN="WARN"
LOG_LEVEL_ERROR="ERROR"
LOG_LEVEL_DEBUG="DEBUG"
# 记录日志函数
# 用法:log_message "INFO" "service_name" "message" ["extra_data"]
log_message() {
local level="$1"
local service="$2"
local message="$3"
local extra="-"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local log_entry="{\"timestamp\":\"$timestamp\",\"level\":\"$level\",\"service\":\"$service\",\"message\":\"$message\"$extra}"
echo "$log_entry" >> "$LOG_FILE"
# 控制台输出(可选)
if [ "$LOG_VERBOSE" = "true" ]; then
echo "[$timestamp] [$level] [$service] $message"
fi
}
# 记录 API 请求日志
# 用法:log_api_request "serviceName" "objectApiName" "response_code" "duration_ms" ["extra_fields"]
log_api_request() {
local service_name="$1"
local object_api="$2"
local response_code="$3"
local duration_ms="$4"
local extra="-"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local extra_json=""
if [ -n "$extra" ]; then
extra_json=",\"extra\":$extra"
fi
local log_entry="{\"timestamp\":\"$timestamp\",\"type\":\"API_REQUEST\",\"service\":\"$service_name\",\"objectApi\":\"$object_api\",\"responseCode\":\"$response_code\",\"durationMs\":\"$duration_ms\"$extra_json}"
echo "$log_entry" >> "$LOG_FILE"
}
# 记录认证事件
# 用法:log_auth_event "event_type" "result" ["message"]
log_auth_event() {
local event_type="$1" # TOKEN_REQUEST, TOKEN_REFRESH, TOKEN_EXPIRED
local result="$2" # SUCCESS, FAILED
local message="-"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
local message_json=""
if [ -n "$message" ]; then
message_json=",\"message\":\"$message\""
fi
local log_entry="{\"timestamp\":\"$timestamp\",\"type\":\"AUTH_EVENT\",\"event\":\"$event_type\",\"result\":\"$result\"$message_json}"
echo "$log_entry" >> "$LOG_FILE"
}
# 清理旧日志(保留最近 N 天)
cleanup_old_logs() {
local days="-$MAX_LOG_DAYS"
if [ -f "$LOG_FILE" ]; then
# 创建临时文件保存最近的日志
local temp_file="$LOG_FILE.tmp"
local cutoff_date=$(date -d "$days days ago" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date -v-daysd '+%Y-%m-%d %H:%M:%S' 2>/dev/null)
if [ -n "$cutoff_date" ]; then
# 过滤保留最近的日志
while IFS= read -r line; do
log_timestamp=$(echo "$line" | jq -r '.timestamp' 2>/dev/null)
if [ -n "$log_timestamp" ] && [[ "$log_timestamp" > "$cutoff_date" ]]; then
echo "$line" >> "$temp_file"
fi
done < "$LOG_FILE"
if [ -f "$temp_file" ]; then
mv "$temp_file" "$LOG_FILE"
echo "✅ 已清理 $days 天前的日志"
else
# 如果所有日志都过期了,清空文件
> "$LOG_FILE"
echo "✅ 所有日志已过期,已清空日志文件"
fi
fi
fi
}
# 查看最近的日志
# 用法:view_logs [count] [level]
view_logs() {
local count="-20"
local level="-"
if [ ! -f "$LOG_FILE" ]; then
echo "❌ 日志文件不存在:$LOG_FILE"
return 1
fi
echo "📋 最近 $count 条日志"
echo "=================="
echo ""
if [ -n "$level" ]; then
tail -n "$count" "$LOG_FILE" | jq --arg lvl "$level" 'select(.level == $lvl or .type == $lvl)'
else
tail -n "$count" "$LOG_FILE" | jq .
fi
}
# 统计日志
# 用法:show_stats [days]
show_stats() {
local days="-$MAX_LOG_DAYS"
if [ ! -f "$LOG_FILE" ]; then
echo "❌ 日志文件不存在:$LOG_FILE"
return 1
fi
local cutoff_date=$(date -d "$days days ago" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || date -v-daysd '+%Y-%m-%d %H:%M:%S' 2>/dev/null)
echo "📊 最近 $days 天 API 调用统计"
echo "=========================="
echo ""
# 按服务名统计
echo "按服务名统计:"
tail -n 1000 "$LOG_FILE" | jq -r 'select(.type == "API_REQUEST") | .service' | sort | uniq -c | sort -rn | head -20
echo ""
echo "按响应码统计:"
tail -n 1000 "$LOG_FILE" | jq -r 'select(.type == "API_REQUEST") | .responseCode' | sort | uniq -c | sort -rn
echo ""
echo "总调用次数: $(tail -n 1000 "$LOG_FILE" | jq -r 'select(.type == "API_REQUEST")' | wc -l)"
}
# 搜索日志
# 用法:search_logs "keyword" [count]
search_logs() {
local keyword="$1"
local count="-50"
if [ ! -f "$LOG_FILE" ]; then
echo "❌ 日志文件不存在:$LOG_FILE"
return 1
fi
echo "🔍 搜索关键词:'$keyword'"
echo ""
tail -n 1000 "$LOG_FILE" | jq --arg kw "$keyword" 'select(.service | contains($kw)) or select(.objectApi | contains($kw)) or select(.message | contains($kw))' | head -n "$count"
}
# 导出日志为 JSON
# 用法:export_logs [output_file]
export_logs() {
local output="-$LOG_DIR/export-$(date +%Y%m%d-%H%M%S).json"
if [ ! -f "$LOG_FILE" ]; then
echo "❌ 日志文件不存在:$LOG_FILE"
return 1
fi
# 转换为 JSON 数组
echo "[" > "$output"
local first=true
while IFS= read -r line; do
if [ "$first" = true ]; then
first=false
else
echo "," >> "$output"
fi
echo "$line" >> "$output"
done < "$LOG_FILE"
echo "]" >> "$output"
echo "✅ 日志已导出到:$output"
}
# 显示帮助
show_help() {
echo "CloudCC API 日志工具"
echo ""
echo "用法:$0 <命令> [参数]"
echo ""
echo "命令:"
echo " view [count] [level] 查看最近的日志"
echo " stats [days] 显示统计信息"
echo " search <keyword> [count] 搜索日志"
echo " export [output_file] 导出日志为 JSON"
echo " cleanup [days] 清理旧日志"
echo " help 显示帮助"
echo ""
echo "示例:"
echo " $0 view 50 查看最近 50 条日志"
echo " $0 stats 7 显示最近 7 天统计"
echo " $0 search productuplist 搜索包含 productuplist 的日志"
echo " $0 cleanup 3 清理 3 天前的日志"
}
# 内部函数调用(供其他脚本使用)
case "-" in
log_api_request)
log_api_request "-" "-" "-" "-"
exit 0
;;
log_message)
log_message "-" "-" "-" "-"
exit 0
;;
log_auth_event)
log_auth_event "-" "-" "-"
exit 0
;;
esac
# 主程序
case "-help" in
view)
view_logs "-20" "-"
;;
stats)
show_stats "-$MAX_LOG_DAYS"
;;
search)
search_logs "-" "-50"
;;
export)
export_logs "-"
;;
cleanup)
cleanup_old_logs "-$MAX_LOG_DAYS"
;;
help|--help|-h)
show_help
;;
*)
echo "❌ 未知命令:$1"
show_help
exit 1
;;
esac
FILE:scripts/get-objects.sh
#!/bin/bash
# get-objects.sh - 获取 CloudCC 对象列表(标准对象 + 自定义对象)
SCRIPT_DIR="$(cd "$(dirname "BASH_SOURCE[0]")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config.json"
# 读取配置
if [ ! -f "$CONFIG_FILE" ]; then
echo "❌ 配置文件不存在:$CONFIG_FILE"
exit 1
fi
API_DOMAIN=$(cat "$CONFIG_FILE" | jq -r '.apiDomain')
ACCESS_TOKEN=$(cat "$CONFIG_FILE" | jq -r '.accessToken')
TOKEN_EXPIRES=$(cat "$CONFIG_FILE" | jq -r '.tokenExpiresAt')
# 检查是否需要刷新 token
CURRENT_TIME=$(date +%s)
if [ "$ACCESS_TOKEN" == "null" ] || [ "$TOKEN_EXPIRES" == "null" ] || [ "$CURRENT_TIME" -ge "$TOKEN_EXPIRES" ]; then
echo "⚠️ token 已过期或不存在,正在刷新..."
"$SCRIPT_DIR/get-token.sh"
if [ $? -ne 0 ]; then
echo "❌ 刷新 token 失败"
exit 1
fi
API_DOMAIN=$(cat "$CONFIG_FILE" | jq -r '.apiDomain')
ACCESS_TOKEN=$(cat "$CONFIG_FILE" | jq -r '.accessToken')
fi
# 解析参数
SHOW_STANDARD=false
SHOW_CUSTOM=false
SEARCH_KEYWORD=""
while [[ $# -gt 0 ]]; do
case $1 in
-s|--standard)
SHOW_STANDARD=true
shift
;;
-c|--custom)
SHOW_CUSTOM=true
shift
;;
-k|--keyword)
SEARCH_KEYWORD="$2"
shift 2
;;
-h|--help)
echo "用法:$0 [选项]"
echo ""
echo "选项:"
echo " -s, --standard 只显示标准对象"
echo " -c, --custom 只显示自定义对象"
echo " -k, --keyword KEY 搜索包含关键词的对象"
echo " -h, --help 显示帮助"
echo ""
echo "默认显示标准对象和自定义对象"
exit 0
;;
*)
echo "❌ 未知选项:$1"
exit 1
;;
esac
done
# 默认显示两者
if [ "$SHOW_STANDARD" = false ] && [ "$SHOW_CUSTOM" = false ]; then
SHOW_STANDARD=true
SHOW_CUSTOM=true
fi
echo "📋 CloudCC 对象列表"
echo "=================="
echo ""
# 获取标准对象
if [ "$SHOW_STANDARD" = true ]; then
echo "📦 标准对象:"
echo "----------"
STANDARD_RESPONSE=$(curl -s -X POST "$API_DOMAIN/api/customObject/standardObjList" \
-H "accessToken: $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{}')
RESULT=$(echo "$STANDARD_RESPONSE" | jq -r '.result')
if [ "$RESULT" != "true" ]; then
echo "❌ 获取标准对象失败:$(echo "$STANDARD_RESPONSE" | jq -r '.returnInfo')"
else
COUNT=$(echo "$STANDARD_RESPONSE" | jq '.data | length')
echo "共 $COUNT 个标准对象"
echo ""
if [ -n "$SEARCH_KEYWORD" ]; then
echo "$STANDARD_RESPONSE" | jq -r --arg kw "$SEARCH_KEYWORD" \
'.data[] | select(.objname | ascii_downcase | contains($kw | ascii_downcase)) | " \(.objname) -> \(.label) (prefix: \(.objprefix))"'
else
echo "$STANDARD_RESPONSE" | jq -r '.data[] | " \(.objname) -> \(.label) (prefix: \(.objprefix))"'
fi
fi
echo ""
fi
# 获取自定义对象
if [ "$SHOW_CUSTOM" = true ]; then
echo "🔧 自定义对象:"
echo "------------"
CUSTOM_RESPONSE=$(curl -s -X POST "$API_DOMAIN/api/customObject/list" \
-H "accessToken: $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":""}')
RESULT=$(echo "$CUSTOM_RESPONSE" | jq -r '.result')
if [ "$RESULT" != "true" ]; then
echo "❌ 获取自定义对象失败:$(echo "$CUSTOM_RESPONSE" | jq -r '.returnInfo')"
else
COUNT=$(echo "$CUSTOM_RESPONSE" | jq '.data.objList | length')
echo "共 $COUNT 个自定义对象"
echo ""
if [ -n "$SEARCH_KEYWORD" ]; then
echo "$CUSTOM_RESPONSE" | jq -r --arg kw "$SEARCH_KEYWORD" \
'.data.objList[] | select(.objLabel | ascii_downcase | contains($kw | ascii_downcase)) | " \(.objLabel) -> \(.schemetable_name) (prefix: \(.prefix))"'
else
echo "$CUSTOM_RESPONSE" | jq -r '.data.objList[] | " \(.objLabel) -> \(.schemetable_name) (prefix: \(.prefix))"'
fi
fi
echo ""
fi
FILE:scripts/call-api.sh
#!/bin/bash
# call-api.sh - 调用 CloudCC OpenAPI(带日志记录)
SCRIPT_DIR="$(cd "$(dirname "BASH_SOURCE[0]")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config.json"
LOGGER_SCRIPT="$SCRIPT_DIR/logger.sh"
# 检查配置
if [ ! -f "$CONFIG_FILE" ]; then
echo "❌ 配置文件不存在"
exit 1
fi
# 读取配置
API_DOMAIN=$(cat "$CONFIG_FILE" | jq -r '.apiDomain')
ACCESS_TOKEN=$(cat "$CONFIG_FILE" | jq -r '.accessToken')
TOKEN_EXPIRES=$(cat "$CONFIG_FILE" | jq -r '.tokenExpiresAt')
# 检查是否需要刷新 token
CURRENT_TIME=$(date +%s)
if [ "$ACCESS_TOKEN" == "null" ] || [ "$TOKEN_EXPIRES" == "null" ] || [ "$CURRENT_TIME" -ge "$TOKEN_EXPIRES" ]; then
echo "⚠️ token 已过期或不存在,正在刷新..."
"$SCRIPT_DIR/get-token.sh"
if [ $? -ne 0 ]; then
echo "❌ 刷新 token 失败"
exit 1
fi
API_DOMAIN=$(cat "$CONFIG_FILE" | jq -r '.apiDomain')
ACCESS_TOKEN=$(cat "$CONFIG_FILE" | jq -r '.accessToken')
fi
# 检查参数
if [ $# -lt 1 ]; then
echo "用法:$0 <service-name> [json-params]"
exit 1
fi
SERVICE_NAME=$1
JSON_PARAMS=-"{"}
# 记录请求开始时间(秒)
START_TIME=$(date +%s)
# 提取 objectApiName
OBJECT_API=$(echo "$JSON_PARAMS" | jq -r '.objectApiName // "N/A"' 2>/dev/null)
echo "📡 调用 API: $SERVICE_NAME"
echo "🔗 地址:$API_DOMAIN/openApi/common"
# 构建请求体
REQUEST_BODY=$(echo "$JSON_PARAMS" | jq --arg svc "$SERVICE_NAME" '.serviceName = $svc')
# 调用 API
RESPONSE=$(curl -s -X POST "$API_DOMAIN/openApi/common" \
-H "accessToken: $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d "$REQUEST_BODY")
# 计算耗时(秒)
END_TIME=$(date +%s)
DURATION_MS=$(( (END_TIME - START_TIME) * 1000 ))
# 解析响应
RETURN_CODE=$(echo "$RESPONSE" | jq -r '.returnCode // "unknown"')
RESULT=$(echo "$RESPONSE" | jq -r '.result // false')
# 输出结果
echo ""
if [ "$RETURN_CODE" == "1" ] && [ "$RESULT" == "true" ]; then
echo "✅ 调用成功 (耗时:DURATION_MSms)"
else
echo "❌ 调用失败 (returnCode: $RETURN_CODE, 耗时:DURATION_MSms)"
fi
# 记录日志
if [ -x "$LOGGER_SCRIPT" ]; then
bash "$LOGGER_SCRIPT" log_api_request "$SERVICE_NAME" "$OBJECT_API" "$RETURN_CODE" "$DURATION_MS" 2>/dev/null
fi
echo ""
echo "$RESPONSE" | jq .
exit 0