【sap cap api 文档】

安装

npm install --save-dev @sap/cds-compiler swagger-ui-express express

  • 创建 openapi-config.json
bash 复制代码
{
  "output": "app/openapi.yaml",
  "format": "yaml",
  "version": "3.0.0",
  "service": "MyService",
  "options": {
    "oDataVersion": "4.0",
    "includeDraftEntities": true,
    "includeActions": true
  }
}
  • 修改 package.json 添加脚本
bash 复制代码
{
  "scripts": {
    "start": "cds-serve",
    "watch": "cds watch",
    "openapi": "cds compile srv/services.cds --to openapi --output app/openapi.yaml",
    "swagger": "node server.js",
    "test": "newman run postman_draft_feature_collection.json"
  }
}

创建 Swagger UI 服务器

bash 复制代码
const express = require('express');
const swaggerUi = require('swagger-ui-express');
const fs = require('fs');
const path = require('path');
const cds = require('@sap/cds');

const app = express();
const PORT = process.env.PORT || 4004;

// 加载 OpenAPI 规范
const openApiPath = path.join(__dirname, 'app', 'openapi.yaml');

// 自动生成 OpenAPI 文档
async function generateOpenApi() {
  try {
    const { execSync } = require('child_process');
    execSync('cds compile srv/services.cds --to openapi > app/openapi.yaml', {
      cwd: __dirname,
      stdio: 'inherit'
    });
    console.log('✅ OpenAPI 文档生成成功');
  } catch (error) {
    console.error('❌ OpenAPI 文档生成失败:', error.message);
  }
}

// 启动 CAP 服务和 Swagger UI
async function start() {
  // 生成 OpenAPI 文档
  await generateOpenApi();

  // 加载 OpenAPI 规范
  let openApiSpec;
  try {
    const yaml = require('js-yaml');
    openApiSpec = yaml.load(fs.readFileSync(openApiPath, 'utf8'));
  } catch (error) {
    console.warn('⚠️ 未找到 OpenAPI 文件,使用默认配置');
    openApiSpec = createDefaultOpenApi();
  }

  // Swagger UI 路由
  app.use('/swagger', swaggerUi.serve, swaggerUi.setup(openApiSpec, {
    explorer: true,
    customCss: '.swagger-ui .topbar { display: none }',
    customSiteTitle: 'CAP API 文档'
  }));

  // 重定向根路径到 Swagger
  app.get('/', (req, res) => {
    res.redirect('/swagger');
  });

  // 启动 CAP 服务
  await cds.connect.to('db');
  cds.emit('served', { services: cds.services });

  app.listen(PORT, () => {
    console.log(`
    ╔═══════════════════════════════════════════════════════════╗
    ║  CAP 服务已启动                                            ║
    ╠═══════════════════════════════════════════════════════════╣
    ║  📖 Swagger UI:  http://localhost:${PORT}/swagger          ║
    ║  📡 OData 服务:   http://localhost:${PORT}/odata/v4/MyService ║
    ║  📋 $metadata:     http://localhost:${PORT}/odata/v4/MyService/$metadata ║
    ╚═══════════════════════════════════════════════════════════╝
    `);
  });
}

// 创建默认 OpenAPI 规范(备用)
function createDefaultOpenApi() {
  return {
    openapi: '3.0.0',
    info: {
      title: 'MyService API',
      version: '1.0.0',
      description: 'CAP OData 服务 API 文档'
    },
    servers: [
      {
        url: `http://localhost:${PORT}`,
        description: '本地开发服务器'
      }
    ],
    paths: {},
    components: {
      schemas: {}
    }
  };
}

start().catch(console.error);

添加 CDS 注解增强文档 修改 srv/services.cds

bash 复制代码
using { my.bookshop as aa} from '../db/schema';

@requires: 'any'
@title: '订单管理服务'
@description: '提供订单、产品和订单项的 CRUD 操作及 Draft 功能'
service MyService @(impl: './service') {
    
    @title: '订单'
    @description: '订单主实体,支持 Draft 草稿功能'
    @odata.draft.enabled
    entity Orders as projection on aa.Orders;
    
    @title: '订单项'
    @description: '订单明细项目'
    entity OrderItems as projection on aa.OrderItems;
    
    @title: '产品'
    @description: '产品目录,支持版本控制和 Draft 功能'
    @odata.draft.enabled
    entity Products as projection on aa.Products;
    
    @title: '审批订单'
    @description: '将订单状态从 NEW 变更为 CONFIRMED'
    action approveOrder() returns Orders;
    
    @title: '取消审批'
    @description: '将订单状态从 CONFIRMED 变更回 NEW'
    action rejectApproval() returns Orders;
    
    @title: '提交订单'
    @description: '提交订单并扣减库存'
    action submitOrder(ID: UUID, quantity: Integer) returns Orders;
}

修改 db/schema.cds 添加文档注解

bash 复制代码
@title: '书店数据模型'
@description: '包含订单、产品和订单项的数据模型'
namespace my.bookshop;

using {cuid, managed} from '@sap/cds/common';

@title: '书籍'
@description: '书籍基本信息'
@cds.persistence.exists: false
entity Book {
  key ID    : Integer @title: '书籍 ID';
      title : String @title: '书名';
      stock : Integer @title: '库存数量';
}

@title: '产品'
@description: '产品信息,支持版本控制'
entity Products : cuid, managed {
      ProductName : String(100) @title: '产品名称';
      stock       : Integer @title: '库存数量';
      @Core.Computed
      Version     : Integer @Core.Immutable @title: '版本号';
} @(
  OData.Draft.Enabled : true,
  OData.Draft.New : true,
  OData.Draft.Update : true,
  OData.Draft.Delete : true
)

@title: '订单'
@description: '客户订单,支持 Draft 草稿功能'
entity Orders : cuid, managed {
      OrderNumber  : String(20) @unique @title: '订单编号';
      CustomerName : String(100) @title: '客户名称';
      OrderDate    : Date @title: '订单日期';
      Status       : String(20) enum {
        New;
        Confirmed;
        Shipped;
        Delivered;
        Cancelled;
      } @title: '订单状态';
      TotalAmount  : Decimal(10, 2) @title: '订单总额';
      Currency     : String(3) @title: '货币';
      Notes        : String(500) @title: '备注';
      Items        : Composition of many OrderItems
                       on Items.parent = $self @title: '订单项';
} @(
  OData.Draft.Enabled : true,
  OData.Draft.New : true,
  OData.Draft.Update : true,
  OData.Draft.Delete : true
)

@title: '订单项'
@description: '订单中的产品明细'
entity OrderItems : cuid, managed {
      parent       : Association to Orders @title: '所属订单';
      ProductID    : String(50) @title: '产品 ID';
      ProductName  : String(100) @title: '产品名称';
      Quantity     : Integer @title: '数量';
      UnitPrice    : Decimal(10, 2) @title: '单价';
      LineTotal    : Decimal(10, 2) @title: '行总额';
      DeliveryDate : Date @title: '交付日期';
}

创建文档生成脚本 创建 scripts/generate-docs.js

bash 复制代码
 const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');

const projectRoot = path.join(__dirname, '..');

console.log('📖 开始生成 API 文档...\n');

try {
  // 确保 app 目录存在
  const appDir = path.join(projectRoot, 'app');
  if (!fs.existsSync(appDir)) {
    fs.mkdirSync(appDir, { recursive: true });
    console.log('📁 创建 app 目录...\n');
  }

  // 生成 OpenAPI YAML
  console.log('1️⃣ 生成 OpenAPI 规范 (YAML)...');
  execSync(`cds compile srv/services.cds --to openapi > ${path.join(appDir, 'openapi.yaml')}`, {
    cwd: projectRoot,
    stdio: 'inherit'
  });

  // 生成 OpenAPI JSON(使用 node 脚本转换 YAML 为 JSON)
  console.log('2️⃣ 生成 OpenAPI JSON...');
  const yaml = require('js-yaml');
  const openApiYaml = fs.readFileSync(path.join(appDir, 'openapi.yaml'), 'utf8');
  const openApiJson = JSON.stringify(yaml.load(openApiYaml), null, 2);
  fs.writeFileSync(path.join(appDir, 'openapi.json'), openApiJson);

  console.log('\n✅ 文档生成成功!');
  console.log('\n📁 输出文件:');
  console.log('   - app/openapi.yaml');
  console.log('   - app/openapi.json');
  console.log('\n🌐 访问地址:');
  console.log('   - Swagger UI: http://localhost:4004/swagger');

} catch (error) {
  console.error('❌ 文档生成失败:', error.message);
  process.exit(1);
}

创建 .gitignore 更新

bash 复制代码
# 生成的文档
app/openapi.yaml
app/openapi.json
app/api-docs.html
test-report.json
test-report.html

# 依赖
node_modules/

# 环境
.env

启动和访问

bash 复制代码
# 1. 安装依赖
npm install

# 2. 生成文档
npm run docs

# 3. 启动服务(带 Swagger UI)
npm start

访问地址

bash 复制代码
访问地址
功能	地址
📖 Swagger UI	http://localhost:4004/swagger
📡 OData 服务	http://localhost:4004/odata/v4/MyService
📋 $metadata	http://localhost:4004/odata/v4/MyService/$metadata
📄 HTML 文档	http://localhost:4004/api-docs.html
📥 OpenAPI YAML	http://localhost:4004/app/openapi.yaml

效果如上图: