使用 jusp app 接入已有项目
在已有应用项目中维护接入文件、上传镜像归档、生成版本包并联网调试。
jusp-cli 是独立开发工具包,安装后提供完整命令 jusp;app 是应用中心业务域子命令。它不随 jusp-platform-sdk 安装,版本也不跟随 SDK。工具面向已有项目:在项目内新增 .jusp 接入文件,不新建另一套应用工程,也不覆盖源码、容器构建文件或已有部署文件。
安装工具
准备 Python 3.12 或更高版本和 pipx。pipx 专门把 Python 命令行工具安装在隔离环境,
不要求应用项目预先创建虚拟环境,也不会把 CLI 写入应用依赖。首次使用按操作系统的软件源
安装 pipx,并执行一次 pipx ensurepath。应用开发者工作台会显示当前正式 CLI 版本和可复制命令:
export JUSP_PACKAGE_TOKEN='<临时安装凭据>'
export JUSP_CLI_VERSION='<应用开发者工作台显示的当前稳定版本>'
PIP_INDEX_URL="https://__token__:${JUSP_PACKAGE_TOKEN}@app-api.jusuanhub.com:10443/api/v1/appstore/sdk/python/simple/" \
PIP_EXTRA_INDEX_URL="https://pypi.org/simple" \
pipx install "jusp-cli==${JUSP_CLI_VERSION}"
unset JUSP_PACKAGE_TOKEN JUSP_CLI_VERSION
jusp app --help需要准备:可访问平台 HTTPS 包索引的网络、受支持的操作系统,以及 Python 3.12+ 和 pipx。CLI 会在
自己的隔离环境解析运行依赖;Python、Go、Java、Node.js 等应用都使用同一个 jusp
工具流程。只有 Python 应用调用平台运行接口时,才在应用项目中另装
jusp-platform-sdk。
从已有项目开始
cd /path/to/existing-project
jusp app init .
jusp app inspect . --jsoninit 只在不存在时创建 .jusp/app.yaml,已有文件原样保留;inspect 只读报告项目根目录的容器构建文件和接入文件。工具不会猜测业务入口、端口和数据需求,开发者必须确认这些事实。
.jusp/app.yaml 完整示例
apiVersion: apps.jusuan.io/v1alpha1
kind: AppProject
metadata:
name: my-app
spec:
displayVersion: 1.2.0
sdkContractVersion: app-center-v2
runtime:
apiVersion: app-runtime/v1
serviceName: app
servicePort: 8000
entryPath: /apps/my-app
healthPath: /healthz
authMode: app-runtime-session
protectedPaths: []
components:
- name: app
resources:
- name: primary-db
type: postgresql
- name: assets
type: object-storage
dependencies:
- appKey: coommc
apiVersion: v1
capabilities:
- integration.probe
integrations:
- apiVersion: v1
basePath: /apps/my-app/api/integrations/v1
capabilities:
- name: my-app.example.read
onlineDebug: true
roles:
- roleCode: my-app.editor
displayName: 编辑者
description: 可以编辑本应用项目
definitionVersion: 1
status: active字段含义:
| 字段 | 说明 |
|---|---|
metadata.name | 稳定 appKey,小写且发布后不变 |
displayVersion | 给开发者和用户看的版本号;只在显式新建版本时变化 |
runtime.apiVersion | 固定为 app-runtime/v1;这是公开接入合同版本,不是部署版本 |
runtime.serviceName/Port | 主 Web 组件的逻辑名和容器监听端口;serviceName 不是主机名或平台内部地址 |
entryPath | 用户打开应用的入口路径 |
healthPath | 平台判断工作负载是否就绪的路径 |
authMode | 新应用推荐 app-runtime-session |
components | 每个组件必须绑定一个导入后的镜像资产 |
resources | 只声明资源类型和用途,不写主机名、账号或密码 |
dependencies | 声明需要调用的其他应用、公开 API 版本及能力范围 |
integrations | 声明本应用对其他应用开放的 API 版本与能力;只填写应用自身路由,不填写平台内部地址 |
roles | 应用业务角色;roleCode 必须以 appKey. 开头 |
命令字典
| 命令 | 输入 | 输出/副作用 |
|---|---|---|
jusp app init [目录] | 已有项目目录 | 创建缺失的 .jusp/app.yaml 骨架;不覆盖 |
jusp app inspect [目录] [--json] | 项目目录 | 只读输出容器构建文件和接入文件事实 |
jusp app validate <文件> | AppProject、AppBundle 或 AppSubmission | 本地字段级校验;不联网 |
jusp app image upload <tar> ... | Docker/OCI 镜像归档 | 分片上传并返回 imageAssetId 和 digest |
jusp app package [目录] ... | AppProject、镜像资产、制品引用 | 生成 .jusp/dist/submission.json |
jusp app upload <submission> ... | 版本包和源文件 | 创建草稿版本;不自动上架 |
jusp app dev --local ... -- <命令> | 场景、appKey 和子进程 | 启动本地 HTTP 模拟器,给子进程注入与正式环境同名的 SDK 变量 |
jusp app dev --online ... -- <命令> | 应用 ID、候选版本 ID 和子进程 | 浏览器授权后给子进程注入绑定该候选版本的短期真实调试环境 |
本地校验
jusp app validate .jusp/app.yaml校验会拒绝:未知字段、非法端口、非绝对路径、重复组件、角色前缀错误、应用自调用、重复依赖能力和不支持的资源结构。校验通过只表示格式正确,不代表镜像或正式运行已经通过平台检查。
上传镜像 tar
应用开发者上传镜像归档,由平台完成加载和存放;开发者不接触平台制品仓库。
Docker archive:
docker save my-app:1.2.0 -o my-app-1.2.0.tar
jusp app image upload ./my-app-1.2.0.tar \
--app-id <应用ID> \
--component app \
--display-version 1.2.0 \
--idempotency-key my-app-1.2.0OCI layout:
jusp app image upload ./my-app-1.2.0.oci.tar \
--archive-type oci-archive \
--app-id <应用ID> \
--component app \
--display-version 1.2.0 \
--idempotency-key my-app-1.2.0省略 --version-id 表示先创建应用级镜像资产,这是新版本的常用流程;指定 --version-id 表示绑定已有草稿。断线后以同一幂等键重新执行,平台会复用可恢复上传事实。
上传返回的 imageAssetId 是版本包输入;镜像 digest 由平台回读,开发者不能手填另一个 digest。
生成版本包
jusp app package . \
--image-asset app=<imageAssetId>输出 .jusp/dist/submission.json,内容包括:
- 原始 AppProject;
- 每个组件对应的
imageAssetId; - 工具版本、SDK 合同版本和稳定幂等键。
它不包含镜像字节、秘密凭据、平台生成的运行名称或 verified=true。平台会独立核验制品,不能用 JSON 自证可信。
新应用不上传平台部署文件;只声明入口、组件和资源需求,平台生成受控运行配置。--artifact 是既有迁移应用核对已登记制品时的兼容参数,不属于新应用的正常接入步骤,也不应由第三方开发者自行填写。
创建草稿版本
jusp app upload .jusp/dist/submission.json \
--app-id <应用ID> \
--source .jusp/dist/submission.json--source 文件必须与待提交 AppSubmission 完全一致。平台同时保存原始文件名、大小、摘要、上传时间和解析后的入口/资源/角色/镜像信息,供开发者与管理员对照查看。
创建成功后回到应用开发者工作台:检查草稿 → 修复阻断 → 发起上架申请。CLI 不提供管理员批准或上线命令。
本地模拟
jusp app dev --local --app-key my-app \
--dependency coommc=http://127.0.0.1:9080 \
-- python local_demo.pyCLI 会在本机回环地址启动一个随进程结束的 HTTP 模拟器,并注入 Runtime 地址、测试会话、测试应用身份、appKey 和开发场景。--dependency 只能填写 .jusp/app.yaml 已声明的依赖,并且必须指向本机回环地址和明确端口;CLI 把这些本地提供方地址注入子进程。应用继续使用 RuntimeHttpClient、RuntimeClient 和提供方业务 SDK,不需要在业务代码里判断运行模式。
可用 --scenario invalid|insufficient_balance|rate_limited|dependency_error|timeout|idempotency_conflict|async_failed 覆盖错误处理。不带子进程的 jusp app dev --local 只做模拟器健康自检,不能代替应用代码验证。本地模拟不联网、不扣积分、不创建平台版本或镜像记录。
测试组织权限时,默认 success 提供组织管理员、两个分组和三名成员;organization_member 提供普通成员(目录接口返回 403),personal 不含组织(Python SDK 调用目录前抛出 ValueError,直接 HTTP 请求返回 403),expired_session 返回会话失效(401)。这些都是固定假身份,不需要平台注册测试账号。
Python 应用在仅限开发环境的入口适配层使用 RuntimeClient.resolve_dev_session() 读取 CLI 注入的会话,再把会话传给业务代码。异步客户端使用 await resolve_dev_session()。该方法在没有 JUSP_APP_DEV_MODE=local/online 或没有 CLI 会话时拒绝执行;生产入口必须继续用当前请求的 Cookie,不能把进程级调试会话当成所有用户的身份。
完整可运行示例见 Python 接入指南。这些身份场景及辅助方法要求 SDK 0.6.4、CLI 0.1.1 或更新的兼容版本;以应用开发者工作台的已发布版本为准。
联网调试
jusp app dev --online \
--app-id <应用ID> \
--version-id <当前候选版本ID> \
-- python debug_model.py流程:
- CLI 在本机监听一次性回调端口并打开应用开发者工作台确认页;
- 开发者确认应用和版本,选择“本人”或管理员已授权的测试账号,核对组织与扣费提示;
- CLI 用 PKCE 一次性交换绑定候选版本的短期会话;
- 只给子进程注入 Runtime、AppStore、应用间公网入口和短期令牌;
- 子进程结束后清空 CLI 持有的变量。
真实模型调用按所选调用账号扣积分;组织权限按该账号的实际成员关系判断。开发者不是组织管理员也没关系:请超管在应用详情的“测试账号”中加入专用测试组织的管理员账号。开发者不能自行输入任意用户,也不需要知道测试账号的密码或取走其登录会话。
平台重新签发有效期 1 小时的专用调试会话,记录开发者和测试账号两个身份。名单被移除、测试账号停用、离开组织或开发者失权后,后续受保护请求会被拒绝;已执行任务不追溯取消。调试会话不能续期成正式会话,到期重新运行命令。子进程退出不等于已签发令牌提前失效,不要保存或传播令牌。
确认后浏览器会跳转回本机 CLI。如果原页面仍然显示“调试授权已签发”,以终端是否已启动子进程为准;该提示只表示授权已签发和发起跳转,不代表本地程序已经运行。终端持续等待时,检查浏览器对本机跳转的拦截和回调端口,必要时重新运行命令,不要复制或转发授权码。
应用间调用只允许当前版本已声明的 dependencies[].apiVersion/capabilities,且目标应用正式版本明确允许 onlineDebug。调试凭据只用于运行接口,不能上传镜像、提交版本、审核、上线或管理其他应用;需要上传时单独执行上传命令完成本人授权。版本用于确定权限范围,并不创建试运行部署。
双应用本地联调
两个本地应用需要共享同一个模拟器。先下载双应用师生示例 JSON,无需访问 SDK 源码仓库。在文件中配置 apps、users、可选 groups;用户的 subject 包含 tenantId/accountId/workspaceId/userId,应用角色必须属于对应 appKey。示例 UUID 可对应应用自己的测试数据,不写真实用户信息。
# 终端一:共享模拟器
jusp app dev --local --fixture ./dual-app-local.json --serve --port 18080
# 终端二:本地可觅,命令以其开发说明为准
jusp app dev --local --attach http://127.0.0.1:18080 \
--app-key coommc --identity teacher -- python run_coommc.py
# 终端三:已声明 coommc 依赖的调用方
jusp app dev --local --attach http://127.0.0.1:18080 \
--app-key videoforge --identity teacher \
--dependency coommc=http://127.0.0.1:18011/apps/coommc -- python run_videoforge.py模拟器用真实 HTTP 校验授权、摘要与一次性启动码;只模拟平台,不替应用生成课堂或作品。重启后授权清空。
两个本地应用使用真实平台授权
两位开发者分别登录各自账号,授权时选择同一个、已获准用于两个应用的测试用户及组织。不需要知道该测试账号密码。先启动提供方:
jusp app dev --online --app-id <可觅应用ID> --version-id <可觅版本ID> \
--receive-from videoforge --api-version v1 --capability integration.probe -- \
python run_coommc.py把终端输出的 receiverRef 配对编号交给调用方,不交出密码或调试凭据。调用方显式配置本地目标与该编号:
jusp app dev --online --app-id <VideoForge应用ID> --version-id <VideoForge版本ID> \
--dependency coommc=http://127.0.0.1:18011/apps/coommc \
--receiver coommc=<receiverRef> -- python run_videoforge.pyVideoForge ── 业务请求 + 短期授权 ──→ 本地可觅
│ │
└── 自己的调试凭据 → 平台 ← 自己的调试凭据 ┘双方声明、版本、主体、能力和请求摘要必须一致。平台不连接开发机、不部署可觅,业务数据仍在应用自己的测试库。配对缺失时 CLI 拒绝本地路由,不回落线上应用。提供方退出后配对撤销;异常断电则最长到源授权的一小时期限自动失效。重新启动需重新授权和配对。
成功来源为 developer_local,区别于中转到线上应用的 developer_debug。启动交接仍由本地可觅后端交换自己的会话,最多保持至两端授权的较早到期时间;浏览器保活不延长授权。只读查看和禁止编辑属于提供方业务权限,探针成功不代替业务验收。
常见错误
| 现象 | 原因与处理 |
|---|---|
“缺少 .jusp/app.yaml” | 在项目根目录执行 jusp app init .,然后补全配置 |
| “每个项目组件都必须绑定一个镜像资产” | 为每个 components[].name 增加一个 --image-asset |
| artifact 格式错误 | 使用 kind=reference@sha256:<64hex> |
| 401 | 配对/安装凭据过期,重新从应用开发者工作台授权 |
| 404 | 应用或版本不属于当前开发者,不要猜 ID |
| 409 | 同一幂等键绑定了不同文件;换回原文件或使用新的业务版本键 |
| 上传超时 | 设置更大的 JUSP_APP_UPLOAD_TIMEOUT_SECONDS,不要关闭 TLS 校验 |