Coze源码分析-API授权-编辑令牌-前端源码

发布于:2025-08-30 ⋅ 阅读:(13) ⋅ 点赞:(0)

概述

本文深入分析Coze Studio中用户API授权编辑令牌功能的前端实现。该功能允许用户安全地编辑已创建的个人访问令牌(Personal Access Token,简称PAT),实现令牌名称的自定义修改,确保API访问的灵活性和令牌管理的便捷性。通过对源码的详细解析,我们将了解其架构设计、组件实现、状态管理和用户体验优化等核心技术要点。

功能特性

核心功能

  • 令牌编辑:支持修改已创建令牌的名称信息
  • 编辑确认:提供模态框形式的编辑界面,确保操作明确性
  • 状态感知:基于令牌状态和用户权限控制编辑操作的可用性
  • 即时反馈:编辑操作完成后提供即时的成功反馈
  • 列表刷新:编辑成功后自动刷新令牌列表

用户体验特性

  • 权限控制:只有令牌创建者且令牌未过期时才能编辑
  • 操作指引:通过Tooltip提供清晰的操作说明
  • 表单验证:实时验证令牌名称的合法性
  • 国际化支持:多语言界面适配

技术架构

整体架构设计

┌─────────────────────────────────────────────────────────────┐
│                        PAT编辑模块                          │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │  DataTable  │  │ ColumnOpBody│  │   PermissionModal   │  │
│  │  (令牌列表)  │  │  (操作列)   │  │   (编辑表单弹窗)     │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │               CommonFormParams                          │  │
│  │               (编辑表单组件)                             │  │
│  └─────────────────────────────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                      状态管理层                             │
│  ┌─────────────────┐  ┌─────────────────────────────────┐  │
│  │ usePatOperation │  │         API Hooks               │  │
│  │   (操作逻辑)     │  │  usePatForm / useUpdatePAT      │  │
│  └─────────────────┘  └─────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                       API服务层                            │
│  ┌─────────────────────────────────────────────────────────┐  │
│  │              PAT Permission API                         │  │
│  │   UpdatePersonalAccessTokenAndPermission               │  │
│  └─────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

核心模块结构

frontend/packages/studio/open-platform/open-auth/
├── src/
│   ├── components/
│   │   └── pat/                    # PAT管理核心组件模块
│   │       ├── index.tsx          # 主组件(PatBody)- 整合所有子组件
│   │       ├── data-table/        # 令牌列表展示模块
│   │       │   ├── index.tsx      # 数据表格主组件
│   │       │   └── table-column/  # 表格列配置模块
│   │       │       ├── index.tsx           # 列配置主文件
│   │       │       └── column-op.tsx       # 操作列(编辑/删除)
│   │       └── permission-modal/  # 编辑弹窗模块
│   │           ├── index.tsx      # 权限弹窗主组件
│   │           └── common-form-params/ # 通用表单参数
│   │               └── index.tsx  # 令牌名称编辑表单
│   ├── hooks/
│   │   └── pat/                   # PAT状态管理模块
│   │       ├── use-token.ts       # API调用Hooks
│   │       │                      # - useUpdatePAT(更新令牌)
│   │       │                      # - usePATPermission(获取详情)
│   │       └── action/
│   │           ├── use-pat-operation.ts # 操作状态管理Hook
│   │           │                  # - 编辑操作处理
│   │           │                  # - 成功反馈管理
│   │           └── use-pat-form.ts # 表单状态管理Hook
│   │                              # - 表单验证
│   │                              # - 数据提交
│   └── utils/
│       └── time.ts                # 时间处理工具模块
│                                  # - 令牌状态判断
│                                  # - 过期时间显示
└── API服务层/
    └── pat_permission_api/        # PAT权限API接口模块
        ├── UpdatePersonalAccessTokenAndPermission  # 更新令牌接口
        └── GetPersonalAccessTokenAndPermission     # 获取令牌详情接口

用户编辑令牌流程概述

用户点击编辑按钮(编辑图标)
        ↓
  editHandle() 触发
        ↓
  PermissionModal 编辑弹窗显示
        ↓
  usePATPermission() 获取令牌详情
        ↓
  表单预填充令牌名称
        ↓
  用户修改令牌名称
        ↓
  validateName() 实时验证名称
        ↓
  用户点击"确认"按钮
        ↓
  onSubmit() 触发
        ↓
  runUpdate() 调用
        ↓
  patPermissionApi.UpdatePersonalAccessTokenAndPermission()
        ↓
  后端更新指定令牌信息
        ↓
  onSuccess() 处理成功响应
        ↓
  Toast.success() 显示成功提示
        ↓
  onRefresh() 刷新PAT列表

该流程包含多层安全保障:

  1. 权限验证:只有令牌创建者且令牌未过期时才显示编辑按钮
  2. 表单验证:通过 validateParams() 进行实时名称验证
  3. API调用:使用 UpdatePersonalAccessTokenAndPermission API 更新令牌
  4. 成功处理:通过 Toast 提示用户操作成功
  5. 数据刷新:自动刷新PAT列表显示最新状态

整个流程确保了令牌编辑的安全性和用户体验的流畅性。

核心组件实现

1. 编辑操作列(ColumnOpBody)

文件位置:frontend/packages/studio/open-platform/open-auth/src/components/pat/data-table/table-column/column-op.tsx

负责渲染编辑按钮和权限控制:

export const ColumnOpBody: FC<{
  record: PersonalAccessToken;
  isCurrentUser?: boolean;
  onEdit: (v: PersonalAccessToken) => void;
  onDelete: (id: string) => void;
  afterConfirmDelete?: () => void;
  afterCancelDelete?: () => void;
}> = ({
  record,
  isCurrentUser,
  onEdit,
  onDelete,
  afterConfirmDelete,
  afterCancelDelete,
}) => {
  const isActive = getStatus(record?.expire_at as number);

  return (
    <Space align="center" spacing={17}>
      {/* 编辑按钮 */}
      <Tooltip
        content={
          isCurrentUser
            ? I18n.t(isActive ? 'Edit' : 'not_support_edit_1')
            : I18n.t('org_api_pat_edit_reminder')
        }
      >
        <UIButton
          onClick={() => onEdit(record)}
          className={classNames(styles['btn-frame'], {
            [styles['btn-frame-disabled']]: !isActive,
          })}
          theme="borderless"
          icon={<IconCozEdit className={styles.icon} />}
          disabled={!isActive || !isCurrentUser}
        ></UIButton>
      </Tooltip>
      {/* 删除按钮 */}
      <Popconfirm
        style={{ width: 400 }}
        okType="danger"
        trigger="click"
        onConfirm={() => {
          onDelete(`${record?.id}`);
          afterConfirmDelete?.();
        }}
        onCancel={() => {
          afterCancelDelete?.();
        }}
        content={I18n.t('remove_token_1')}
        title={I18n.t('remove_token_reminder_1')}
      >
        <div>
          <Tooltip content={I18n.t('Remove')}>
            <UIButton
              className={styles['btn-frame']}
              theme="borderless"
              icon={<IconCozMinusCircle className={styles.icon} />}
            ></UIButton>
          </Tooltip>
        </div>
      </Popconfirm>
    </Space>
  );
};

设计亮点

  • 权限控制:基于isCurrentUserisActive双重验证
  • 状态响应:根据令牌状态动态调整按钮样式
  • 友好提示:通过Tooltip提供操作说明和限制原因

2. 权限编辑模态框(PermissionModal)

文件位置:frontend/packages/studio/open-platform/open-auth/src/components/pat/permission-modal/index.tsx

提供编辑令牌的模态框界面:

export const PermissionModal = forwardRef(function PermissionModal({
  editInfo,
  isCreate,
  onRefresh,
  onCreateSuccess,
  onCancel,
  children,
  onPatPermissionChange,
  onCustomFormValueChange,
  validateCustomParams,
  getCustomParams,
  afterSubmit,
  isReady = true,
  isShowAuthMigrateNotice = false,
}, ref) {
  const formApi = useRef<FormApi<FormApiInfo>>();
  
  const {
    isFailToValid,
    ready,
    loading,
    onSubmit,
    onFormValueChange,
    patPermission,
    successData,
    updateSuccessData,
    validateParams,
  } = usePatForm({
    editInfo,
    isCreate,
    formApi,
    validateCustomParams,
    getCustomParams,
    afterSubmit,
    isShowAuthMigrateNotice,
  });
  
  const modalReady = isReady && ready;

  // 创建成功处理
  useEffect(() => {
    if (successData) {
      Toast.success({ content: I18n.t('Create_success'), showClose: false });
      onCreateSuccess(successData);
      onRefresh();
    }
  }, [successData]);

  // 编辑成功处理
  useEffect(() => {
    if (updateSuccessData) {
      Toast.success({ content: I18n.t('Edit_success'), showClose: false });
      onRefresh();
    }
  }, [updateSuccessData]);

  return (
    <Modal
      title={isCreate ? I18n.t('add_new_pat_1') : I18n.t('edit_pat_1')}
      visible={true}
      width={480}
      centered
      maskClosable={false}
      onCancel={onCancel}
      onOk={onSubmit}
      okButtonProps={{
        disabled: isFailToValid || !modalReady,
        loading,
      }}
      cancelText={I18n.t('cancel')}
      okText={I18n.t('confirm')}
    >
      <Spin spinning={!modalReady}>
        <div className={styles['permission-form-content']}>
          <Form<FormApiInfo>
            showValidateIcon={false}
            getFormApi={api => (formApi.current = api)}
            onValueChange={(values, changedValue) => {
              if (onCustomFormValueChange) {
                onCustomFormValueChange(values, changedValue);
              } else {
                onFormValueChange(values, changedValue as FormApiInfo);
              }
            }}
          >
            <CommonFormParams
              isCreate={isCreate}
              patPermission={patPermission}
            />
            {children}
          </Form>
        </div>
      </Spin>
    </Modal>
  );
});

设计亮点

  • 模式适配:通过isCreate参数区分创建和编辑模式
  • 表单管理:使用usePatForm进行表单状态管理
  • 成功反馈:编辑成功后自动刷新列表

3. 表单参数组件(CommonFormParams)

文件位置:frontend/packages/studio/open-platform/open-auth/src/components/pat/permission-modal/common-form-params/index.tsx

提供令牌编辑的表单字段:

export const CommonFormParams: FC<{
  isCreate?: boolean;
  patPermission?: GetPersonalAccessTokenAndPermissionResponseData;
}> = ({ isCreate, patPermission }) => {
  const [durationDay, setDurationDay] = useState<ExpirationDate>();
  const dataOptionsList = getExpirationOptions();

  return (
    <>
      {/* 令牌名称输入框 */}
      <Form.Input
        trigger={['blur', 'change']}
        field="name"
        label={{
          text: I18n.t('coze_api_list1'),
          required: true,
        }}
        placeholder={''}
        maxLength={20}
        rules={[{ required: true, message: '' }]}
      />
      
      {/* 过期时间显示 */}
      <Form.Slot
        label={{
          text: I18n.t('expire_time_1'),
          required: true,
          extra: <Tips tips={I18n.t('expired_time_forbidden_1')} />,
        }}
      >
        {isCreate ? (
          <>
            <div className={styles['expiration-select']}>
              <Form.Select
                noLabel={true}
                field="duration_day"
                style={{ width: '100%' }}
                disabled={!isCreate}
                optionList={dataOptionsList}
                onChange={v => setDurationDay(v as ExpirationDate)}
                rules={[{ required: true, message: '' }]}
                placeholder={I18n.t('select_expired_time_1')}
              />

              {durationDay === ExpirationDate.CUSTOMIZE && (
                <Form.DatePicker
                  noLabel={true}
                  field="expire_at"
                  style={{ width: '100%' }}
                  disabled={!isCreate}
                  disabledDate={disabledDate}
                  position="bottomRight"
                />
              )}
            </div>
          </>
        ) : (
          // 编辑模式:只读显示过期时间
          <Input
            disabled
            value={
              patPermission?.personal_access_token?.expire_at
                ? getExpirationTime(
                    patPermission?.personal_access_token?.expire_at as number,
                  )
                : ''
            }
          />
        )}
      </Form.Slot>
    </>
  );
};

设计亮点

  • 模式区分:创建模式允许设置过期时间,编辑模式只显示
  • 表单验证:令牌名称必填,最大20字符

编辑令牌服务逻辑

1. usePatOperation - 操作管理Hook

文件位置frontend/packages/studio/open-platform/open-auth/src/hooks/pat/action/use-pat-operation.ts

功能职责

  • 编辑状态管理
  • 模态框控制
  • 操作流程协调
  • 数据获取和删除管理

核心代码

export const usePatOperation = ({
  fetchCustomPatList,
  afterCancelPermissionModal,
}: {
  fetchCustomPatList?: FetchCustomPatList;
  afterCancelPermissionModal?: (isCreate: boolean) => void;
}) => {
  const { loading, dataSource, fetchData } = useGetPATList({
    fetchCustomPatList,
  });
  const { runDelete } = useDeletePAT({
    successHandle: () => {
      Toast.success({ content: I18n.t('Delete_success'), showClose: false });
      fetchData();
    },
  });
  
  const [showDataForm, setShowDataForm] = useState(false);
  const [showResult, setShowResult] = useState(false);
  const [isCreate, setIsCreate] = useState(true);
  const [editInfo, setEditInfo] = useState<PersonalAccessToken>();
  const [successData, setSuccessData] =
    useState<CreatePersonalAccessTokenAndPermissionResponseData>();

  const onAddClick = () => {
    setIsCreate(true);
    setShowDataForm(true);
  };
  
  const editHandle = (v: PersonalAccessToken) => {
    setEditInfo(v);
    setIsCreate(false);
    setShowDataForm(true);
  };
  
  const onCancel = () => {
    setShowDataForm(false);
    setEditInfo(undefined);
    afterCancelPermissionModal?.(isCreate);
  };

  const refreshHandle = () => {
    fetchData();
    setShowDataForm(false);
    setEditInfo(undefined);
  };
  
  return {
    dataSource,
    loading,
    showDataForm,
    isCreate,
    editInfo,
    successData,
    onAddClick,
    editHandle,
    runDelete,
    onCancel,
    refreshHandle,
    fetchData,
    // ... 其他返回值
  };
};

2. usePatForm - 表单管理Hook

文件位置frontend/packages/studio/open-platform/open-auth/src/hooks/pat/action/use-pat-form.ts

功能职责

  • 表单状态管理
  • 提交逻辑处理
  • API调用集成
  • 表单验证管理

接口定义

export interface FormApiInfo {
  name: string;
  duration_day: ExpirationDate;
  expire_at: Date;
}

interface PatFormProps {
  editInfo?: PersonalAccessToken;
  isCreate: boolean;
  isShowAuthMigrateNotice?: boolean;
  formApi: React.MutableRefObject<FormApi<FormApiInfo> | undefined>;
  validateCustomParams?: () => boolean;
  getCustomParams?: () => Record<string, unknown>;
  afterSubmit?: (params: Record<string, unknown>) => void;
}

核心代码

export const usePatForm = ({
  editInfo,
  isCreate,
  formApi,
  getCustomParams,
  validateCustomParams,
  afterSubmit,
  isShowAuthMigrateNotice,
}: PatFormProps) => {
  const { patPermission } = usePATPermission({
    patId: editInfo?.id,
  });

  const { loading: createLoading, runCreate, successData } = useCreatePAT();
  const {
    loading: updateLoading,
    runUpdate,
    updateSuccessData,
  } = useUpdatePAT();

  const [isFailToValid, setIsFailToValid] = useState(true);

  const onSubmit = () => {
    const {
      name = '',
      duration_day,
      expire_at,
    } = formApi.current?.getValues() || {};

    const params = {
      name,
      ...(getCustomParams?.() || {}),
    };
    
    if (isCreate) {
      runCreate({
        ...params,
        ...getDurationData(duration_day as ExpirationDate, expire_at as Date),
      });
    } else {
      runUpdate({ ...params, id: editInfo?.id ?? '' });
    }
    afterSubmit?.({ ...params, duration_day, expire_at });
  };

  const validateParams = () => {
    const { name, duration_day, expire_at } =
      formApi.current?.getValues() || {};

    const nameValid = validateName(name);
    const isCustomParamsValid = validateCustomParams?.() !== false;
    const durationValid = isCreate
      ? validateDuration(duration_day, expire_at)
      : true;
    setIsFailToValid(!(nameValid && isCustomParamsValid && durationValid));
  };

  const onFormValueChange = (
    _values: FormApiInfo,
    _changedValue: FormApiInfo,
  ) => {
    validateParams();
  };

  // 编辑模式下预填充表单
  useEffect(() => {
    if (isCreate) {
      formApi.current?.setValue('name', 'Secret token');
    } else if (patPermission && patPermission?.personal_access_token?.name) {
      formApi.current?.setValue(
        'name',
        patPermission?.personal_access_token?.name,
      );
    }
  }, [patPermission]);

  const ready = isCreate ? true : !!patPermission;
  
  return {
    isFailToValid,
    ready,
    loading: updateLoading || createLoading,
    onSubmit,
    onFormValueChange,
    patPermission,
    validateParams,
    successData,
    updateSuccessData,
  };
};

3. useUpdatePAT - 更新API Hook

文件位置frontend/packages/studio/open-platform/open-auth/src/hooks/pat/use-token.ts

功能职责

  • API调用封装
  • 错误处理
  • 数据刷新
  • 事件上报管理

核心代码

import { patPermissionApi } from '@coze-arch/bot-api';
import { useRequest } from 'ahooks';

export const useUpdatePAT = (
  handle: {
    successHandle?: () => void;
  } = {},
) => {
  const {
    loading,
    run: runUpdate,
    data: updateSuccessData,
  } = useRequest(
    (info: UpdatePersonalAccessTokenAndPermissionRequest) =>
      patPermissionApi.UpdatePersonalAccessTokenAndPermission(info),
    {
      manual: true,
      onSuccess: () => {
        handle?.successHandle?.();
        reporter.event({
          eventName: REPORT_EVENTS.openPatAction,
          meta: {
            level: 'success',
            action: 'UpdatePersonalAccessTokenAndPermission',
          },
        });
      },
      onError: error => {
        reporter.errorEvent({
          eventName: REPORT_EVENTS.openPatAction,
          error,
          meta: {
            action: 'UpdatePersonalAccessTokenAndPermission',
          },
        });
      },
    },
  );
  
  return {
    runUpdate,
    loading,
    updateSuccessData,
  };
};
bot-api/package.json

文件位置:frontend/packages/arch/bot-api/package.json
核心代码:

{
  "name": "@coze-arch/bot-api",
  "version": "0.0.1",
  "description": "RPC wrapper for bot studio application",
  "author": "fanwenjie.fe@bytedance.com",
  "exports": {
    ".": "./src/index.ts",
    
  },
}

代码作用:

  • 1.包定义 :定义了一个名为 @coze-arch/bot-api 的 npm 包,版本为 0.0.1,这是一个用于 bot studio 应用的 RPC 包装器。
  • 2.通过主入口文件 :
    frontend\packages\arch\bot-api\src\index.ts 中, patPermissionApi 被导出:
export { patPermissionApi } from './pat-permission-api';

这允许通过 @coze-arch/bot-api 直接导入 patPermissionApi 。

  • 3.patPermissionApi 实现 :在 src/pat-permission-api.ts 中, patPermissionApi 是一个配置好的服务实例,它使用了 PATPermissionService 和 axios 请求配置。
src/pat-permission-api.ts

文件位置:frontend\packages\arch\bot-api\src\pat-permission-api.ts

核心代码:

import PATPermissionService from './idl/pat_permission_api';
import { axiosInstance, type BotAPIRequestConfig } from './axios';

export const patPermissionApi = new PATPermissionService<BotAPIRequestConfig>({
  request: (params, config = {}) =>
    axiosInstance.request({ ...params, ...config }),
});
代码含义详解

这段代码是创建一个 PATPermissionService 实例的构造函数调用,具体含义如下:

PATPermissionService<BotAPIRequestConfig>({
  request: (params, config = {}) => 
    axiosInstance.request({ ...params, ...config }),
})
  1. 泛型参数
  • PATPermissionService<BotAPIRequestConfig>:这是一个泛型类,BotAPIRequestConfig 是类型参数
  • BotAPIRequestConfig 定义了业务层的自定义 axios 配置类型,包含 __disableErrorToast 等业务特定字段
  1. 构造函数参数

传入一个配置对象,包含 request 函数:

{
  request: (params, config = {}) => 
    axiosInstance.request({ ...params, ...config })
}
  1. request 函数解析

这是一个依赖注入的设计模式:

  • 参数说明

    • params:包含 HTTP 请求的基本参数(url、method、data、headers 等)
    • config:可选的额外配置,默认为空对象
  • 函数体

    • { ...params, ...config }:使用展开运算符合并参数
    • axiosInstance.request():调用 axios 实例的 request 方法发送 HTTP 请求
  1. 依赖注入模式
// IDL 生成的服务类不直接依赖具体的 HTTP 库
class PATPermissionService<T> {
  private request: any;
  
  constructor(options?: { request?: Function }) {
    this.request = options?.request || this.request;
  }
}
  1. 适配器模式
// 将 axiosInstance.request 适配为 IDL 服务所需的接口
request: (params, config) => axiosInstance.request({ ...params, ...config })
  1. 数据流转过程

  2. 业务调用patPermissionApi.UpdatePersonalAccessTokenAndPermission(params)

  3. 参数组装:IDL 生成的方法将业务参数转换为标准 HTTP 参数

  4. 请求发送:调用注入的 request 函数

  5. HTTP 请求:最终通过 axiosInstance.request 发送请求

  6. 优势

  • 解耦:IDL 生成的代码不直接依赖 axios,便于测试和替换
  • 类型安全:通过泛型确保配置类型的一致性
  • 可扩展:可以在 request 函数中添加业务逻辑(如错误处理、认证等)
  • 统一性:所有 API 调用都通过相同的 request 函数,便于统一管理
  1. 实际效果

当调用 UpdatePersonalAccessTokenAndPermission 时:

// 1. IDL 生成的方法
UpdatePersonalAccessTokenAndPermission(req, options) {
  const params = { url: '/api/...', method: 'POST', data: {...} };
  return this.request(params, options); // 调用注入的 request 函数
}

// 2. 注入的 request 函数
(params, config) => {
  // params = { url: '/api/...', method: 'POST', data: {...} }
  // config = options (可能包含 __disableErrorToast 等)
  return axiosInstance.request({ ...params, ...config });
}

这种设计确保了代码的模块化、可测试性和可维护性。

axiosInstance说明

1.axiosInstance 在整个项目中是全局共享的
2.bot-api 包中的导入 ( frontend/packages/arch/bot-api/src/axios.ts )
是直接从 @coze-arch/bot-http 包导入了 axiosInstance 。

import {
  axiosInstance,
  isApiError,
  type AxiosRequestConfig,
} from '@coze-arch/bot-http';

3.bot-http 包中的定义 ( frontend/packages/arch/bot-http/src/axios.ts ):

export const axiosInstance = axios.create();

这里创建了一个全局的 axios 实例,与用户名修改保存请求的 axios 实例是同一个。

PATPermissionService说明

1.bot-api包中的导入路径:
import PATPermissionService from ‘./idl/pat_permission_api’;
实际指向
frontend/packages/arch/bot-api/src/idl/pat_permission_api.ts
文件内容重新导出了 @coze-arch/idl/pat_permission_api 包的所有内容,包括默认导出

export * from '@coze-arch/idl/pat_permission_api';
export { default as default } from '@coze-arch/idl/pat_permission_api';

2.idl包的模块映射
文件位置:frontend/packages/arch/idl/package.json
核心代码:

"name": "@coze-arch/idl",
  "version": "0.0.1",
  "description": "IDL files for bot studio application",
  "author": "fanwenjie.fe@bytedance.com",
  "exports": {
    "./pat_permission_api": "./src/auto-generated/pat_permission_api/index.ts",

代码作用:将 @coze-arch/idl/pat_permission_api 映射到实际文件路径frontend/packages/arch/idl/src/auto-generated/pat_permission_api/index.ts
这个文件说明后续见 PAT权限编辑令牌-API接口实现 这个章节。

API接口定义

IDL基础类型定义(base.thrift)

文件位置:idl/base.thrift
核心代码:

namespace py base
namespace go base
namespace java com.bytedance.thrift.base

struct TrafficEnv {
    1: bool   Open = false,
    2: string Env  = ""   ,
}

struct Base {
    1:          string             LogID      = "",
    2:          string             Caller     = "",
    3:          string             Addr       = "",
    4:          string             Client     = "",
    5: optional TrafficEnv         TrafficEnv     ,
    6: optional map<string,string> Extra          ,
}

struct BaseResp {
    1:          string             StatusMessage = "",
    2:          i32                StatusCode    = 0 ,
    3: optional map<string,string> Extra             ,
}

struct EmptyReq {
}

struct EmptyData {}

struct EmptyResp {
    1: i64       code,
    2: string    msg ,
    3: EmptyData data,
}

struct EmptyRpcReq {
    255: optional Base Base,
}

struct EmptyRpcResp {
    255: optional BaseResp BaseResp,
}

文件作用:
定义了项目中所有接口的基础数据结构,作为其他IDL文件的依赖基础。

IDL接口定义(openapiauth.thrift)

文件位置:idl/permission/openapiauth.thrift

定义编辑令牌的数据结构:

include "../base.thrift"

namespace go permission.openapiauth


// 更新请求结构
struct UpdatePersonalAccessTokenAndPermissionRequest {
    1: required i64 id (api.js_conv="true") // PAT Id
    2: string name // PAT name
}

// 更新响应结构
struct UpdatePersonalAccessTokenAndPermissionResponse {
    1: required i32 code
    2: required string msg
}

// 个人访问令牌数据结构
struct PersonalAccessToken {
    1: required i64 id (api.js_conv="true")
    2: required string name
    3: required i64 created_at
    4: required i64 updated_at
    5: required i64 last_used_at // -1 means unused
    6: required i64 expire_at // -1 means indefinite
}

设计亮点

  • 简洁设计:只需要传递令牌ID和新名称即可完成编辑
  • 类型安全:ID字段使用string类型确保兼容性
  • 标准响应:统一的响应格式包含状态码和消息

服务接口定义

文件位置:idl/permission/openapiauth_service.thrift

定义更新令牌的服务接口:

include "../base.thrift"
include "./openapiauth.thrift"

namespace go permission.openapiauth

service OpenAPIAuthService {
    openapiauth.UpdatePersonalAccessTokenAndPermissionResponse UpdatePersonalAccessTokenAndPermission (
        1: openapiauth.UpdatePersonalAccessTokenAndPermissionRequest req
    ) (api.post="/api/permission_api/pat/update_personal_access_token_and_permission")
}

PAT权限编辑令牌-TypeScript接口生成

通过IDL代码生成工具,自动生成对应的TypeScript接口:

文件位置:frontend/packages/arch/idl/src/auto-generated/pat_permission_api/namespaces/openapi.ts

// API调用接口
interface UpdatePersonalAccessTokenAndPermissionRequest {
  id: string; // 令牌ID
  name?: string; // 令牌名称
  workspace_permission?: unknown; // 工作区权限
  account_permission?: unknown; // 账户权限
  workspace_permission_v2?: unknown; // 工作区v2权限
  enterprise_permission?: unknown; // 企业权限
}

interface UpdatePersonalAccessTokenAndPermissionResponse {
  code: number;
  msg: string;
}

// 令牌数据结构
interface PersonalAccessToken {
  id: string;
  name: string;
  created_at: number;
  updated_at: number;
  last_used_at: number; // -1表示未使用
  expire_at: number; // -1表示无限期
}

设计亮点

  • 接口版本化:提供两个版本的请求接口以支持不同的使用场景
  • 类型安全:使用string类型确保ID的正确传递
  • 标准响应:统一的响应格式便于错误处理和状态管理

PAT权限编辑令牌-服务类生成

文件位置:frontend/packages/arch/idl/src/auto-generated/pat_permission_api/index.ts

自动生成的API服务类实现:

export default class PatPermissionApiService<T> {
  private request: any = () => {
    throw new Error('PatPermissionApiService.request is undefined');
  };
  private baseURL: string | ((path: string) => string) = '';

  constructor(options?: {
    baseURL?: string | ((path: string) => string);
    request?<R>(
      params: {
        url: string;
        method: 'GET' | 'DELETE' | 'POST' | 'PUT' | 'PATCH';
        data?: any;
        params?: any;
        headers?: any;
      },
      options?: T,
    ): Promise<R>;
  }) {
    this.request = options?.request || this.request;
    this.baseURL = options?.baseURL || '';
  }

  private genBaseURL(path: string) {
    return typeof this.baseURL === 'string'
      ? this.baseURL + path
      : this.baseURL(path);
  }

  /**
   * POST /api/permission_api/pat/update_personal_access_token_and_permission
   *
   * update pat with permission updated
   *
   * update pat with permission updated
   */
  UpdatePersonalAccessTokenAndPermission(
    req: UpdatePersonalAccessTokenAndPermissionRequest2,
    options?: T,
  ): Promise<UpdatePersonalAccessTokenAndPermissionResponse> {
    const _req = req;
    const url = this.genBaseURL(
      '/api/permission_api/pat/update_personal_access_token_and_permission',
    );
    const method = 'POST';
    const data = {
      workspace_permission: _req['workspace_permission'],
      account_permission: _req['account_permission'],
      workspace_permission_v2: _req['workspace_permission_v2'],
      enterprise_permission: _req['enterprise_permission'],
      id: _req['id'],
      name: _req['name'],
    };
    const headers = { 'x-tt-env': _req['x-tt-env'] };
    return this.request({ url, method, data, headers }, options);
  }

  // ... 其他方法
}

代码作用

  • PatPermissionApiService 类的 UpdatePersonalAccessTokenAndPermission 方法用于更新PAT令牌和相关权限
  • 该方法使用POST请求,向后端发送更新指令
  • 此文件是基于 openapiauth.thrift 自动生成的,开发者无需手动修改

文件依赖关系

以下是编辑令牌功能相关文件的依赖关系图:

┌─────────────────────────────────────────────────────────────────────────────┐
│                              文件依赖关系图                                 │
└─────────────────────────────────────────────────────────────────────────────┘

    IDL定义层                    代码生成工具                  生成的TypeScript代码
  ┌─────────────┐                ┌─────────────┐              ┌─────────────────────┐
  │             │                │             │              │                     │
  │ openapiauth │────────────────→│   cli.js    │──────────────→│     openapi.ts      │
  │   .thrift   │                │             │              │   (类型定义文件)    │
  │             │                │ IDL转换工具 │              │                     │
  └─────────────┘                │             │              └─────────────────────┘
        │                        └─────────────┘                        │
        │                                                               │
        ▼                                                               ▼
  ┌─────────────┐                                                ┌─────────────────────┐
  │             │                                                │                     │
  │openapiauth_ │                                                │     index.ts        │
  │service.thrift│────────────────────────────────────────────────→│  (服务实现文件)     │
  │             │                                                │                     │
  │(服务接口定义)│                                                │                     │
  └─────────────┘                                                └─────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│                                文件内容说明                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│ 1. openapiauth.thrift                                                       │
│    ├─ UpdatePersonalAccessTokenAndPermissionRequest                         │
│    └─ UpdatePersonalAccessTokenAndPermissionResponse                        │
│                                                                             │
│ 2. openapiauth_service.thrift                                               │
│    └─ UpdatePersonalAccessTokenAndPermission 服务方法定义                  │
│                                                                             │
│ 3. cli.js (IDL转换工具)                                                     │
│    └─ @coze-arch/idl2ts-cli 工具入口                                       │
│                                                                             │
│ 4. openapi.ts (类型定义)                                                    │
│    ├─ UpdatePersonalAccessTokenAndPermissionRequest                         │
│    ├─ UpdatePersonalAccessTokenAndPermissionResponse                        │
│    └─ PersonalAccessToken                        │
│                                                                             │
│ 5. index.ts (服务实现)                                                      │
│    ├─ PATPermissionService 类                                               │
│    └─ UpdatePersonalAccessTokenAndPermission 方法实现                      │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

IDL文件解析器分析结论

通过深入分析Coze Studio项目的IDL架构,我可以确认**openapiauth_service.thriftpassport.thrift使用相同的Thrift Parser**。

关键发现
  1. 统一的IDL工具链:项目使用@coze-arch/idl2ts-cli作为统一的IDL到TypeScript转换工具,该工具支持处理所有Thrift文件。

  2. 共享基础结构

    • 两个文件都位于统一的coze-studio\idl目录下
    • 两个文件都引用了共享的base.thrift文件
    • 使用相同的namespace和结构体定义规范
  3. 统一的代码生成流程

    • frontend\packages\arch\api-schema\api.config.js配置了passport.thrift的生成
    • frontend\packages\arch\idl\package.json包含了openapiauth_service.thrift的自动生成代码
    • 两者都使用相同的idl2ts工具链进行代码生成
  4. 相同的输出格式:生成的TypeScript代码都遵循相同的结构和命名约定,包含相同的注释头和类型定义格式。

结论

openapiauth_service.thriftpassport.thrift确实使用相同的Thrift Parser(@coze-arch/idl2ts-cli),它们共享相同的解析规则、代码生成逻辑和输出格式。这确保了整个项目中IDL文件处理的一致性和兼容性。

@coze-arch/idl2ts-cli 工具详细信息

工具名称

@coze-arch/idl2ts-cli

详细地址

项目路径frontend/infra/idl/idl2ts-cli/

工具详细信息

版本:0.1.7

描述:IDL(Interface Definition Language)到TypeScript的转换工具

主要功能

  1. gen命令:从Thrift或Protocol Buffer文件生成API代码
  2. filter命令:生成过滤后的API类型定义

可执行文件idl2ts(位于 ./src/cli.js
最终调用的是frontend/infra/idl/idl2ts-cli/src/cli.ts 这个文件

核心依赖

  • @coze-arch/idl2ts-generator:代码生成器
  • @coze-arch/idl2ts-helper:辅助工具
  • @coze-arch/idl2ts-plugin:插件系统
  • commander:命令行界面
  • prettier:代码格式化

使用方式

# 生成API代码
idl2ts gen <projectRoot> [-f --format-config <formatConfig>]

# 生成过滤类型
idl2ts filter <projectRoot> [-f --format-config <formatConfig>]

许可证:Apache-2.0

作者:fanwenjie.fe@bytedance.com

这个工具是Coze Studio项目中统一处理所有IDL文件(包括openapiauth_service.thrift和其他相关文件)的核心工具,确保了整个项目中API代码生成的一致性。

状态管理

编辑状态流转

初始状态 → 点击编辑 → 编辑模式 → 提交更新 → 成功状态 → 初始状态
   ↓           ↓         ↓         ↓         ↓         ↓
 editInfo:   editInfo:  editInfo:  loading:  editInfo:  editInfo:
   undefined   token      token      true      undefined  undefined
 isCreate:   isCreate:  isCreate:  isCreate: isCreate:  isCreate:
   true        false      false      false     true       true
 showDataForm: showDataForm: showDataForm: showDataForm: showDataForm: showDataForm:
   false       true       true       true      false      false

usePatOperation Hook集成

编辑令牌功能通过 usePatOperation Hook 实现状态管理:

  • 编辑状态editInfo 存储当前编辑的令牌信息
  • 表单显示isShowForm 控制编辑模态框的显示
  • 数据刷新refreshHandle 在编辑成功后刷新列表

时间处理工具

编辑功能中的时间处理逻辑:

// 检查令牌状态
const isActive = getStatus(record?.expire_at as number);

// 格式化过期时间显示
const formattedTime = getExpirationTime(
  patPermission?.personal_access_token?.expire_at as number,
);

编辑令牌功能的安全性设计

权限控制机制

  1. 用户权限验证:只有令牌创建者可以编辑
  2. 状态检查:过期令牌不允许编辑
  3. 表单验证:确保输入数据的有效性

错误处理

  1. API错误捕获:捕获并处理API调用异常
  2. 用户反馈:提供清晰的错误提示信息
  3. 状态恢复:错误后恢复到正常状态

设计亮点

异常捕获

  • API层异常:通过 useAsyncFn 统一处理API异常
  • 表单验证异常:实时验证并提示用户
  • 组件异常:使用错误边界保护组件稳定性

错误上报

  • 成功事件:编辑成功时上报 UpdatePersonalAccessTokenAndPermission.success
  • 失败事件:编辑失败时上报 UpdatePersonalAccessTokenAndPermission.fail
  • 数据分析:为产品优化提供数据支持

用户反馈

  • 即时提示:编辑成功后立即显示成功Toast
  • 状态指示:按钮loading状态提示操作进行中
  • 禁用状态:过期令牌编辑按钮置灰并提示

用户体验设计

即时反馈

  • 操作确认:编辑成功后显示成功提示
  • 状态更新:实时更新令牌列表数据
  • 视觉反馈:按钮状态变化提供操作反馈

交互优化

  • 权限提示:清晰的权限说明和操作指引
  • 表单预填:编辑时自动填充现有数据
  • 模态框设计:居中显示,防止误操作

国际化支持

  • 多语言界面:支持中英文等多种语言
  • 动态文案:根据语言设置动态切换
  • 文案统一:使用 I18n.t() 统一管理文案

性能优化

组件渲染

  • 条件渲染:根据权限和状态条件渲染组件
  • 状态缓存:使用 useCallback 缓存事件处理函数
  • 组件拆分:合理拆分组件减少不必要的重渲染

API调用

  • 请求去重:防止重复提交编辑请求
  • 错误重试:API失败时提供重试机制
  • 状态管理:统一管理API调用状态

状态管理

  • Hook封装:使用自定义Hook封装业务逻辑
  • 状态分离:分离UI状态和业务状态
  • 依赖优化:合理设置Hook依赖减少重复执行

技术对比分析

与添加令牌功能对比

特性 添加令牌 编辑令牌
表单字段 名称+过期时间+权限 名称+权限(过期时间只读)
API接口 CreatePersonalAccessTokenAndPermission UpdatePersonalAccessTokenAndPermission
权限控制 所有用户可创建 仅创建者可编辑
状态检查 无需检查 需检查令牌是否过期

与其他令牌管理功能对比

  • 删除功能:提供二次确认,编辑功能直接提交
  • 查看功能:只读展示,编辑功能可修改
  • 权限管理:编辑功能集成权限配置界面

架构设计最佳实践

单一职责原则

  • 组件职责:每个组件专注单一功能
  • Hook职责:业务逻辑和UI逻辑分离
  • API职责:纯粹的数据交互层

依赖注入模式

  • 服务注入:通过依赖注入获取API服务
  • 配置注入:通过props传递配置参数
  • 回调注入:通过回调函数实现组件通信

错误边界处理

  • 组件边界:使用错误边界保护组件树
  • API边界:统一处理API调用异常
  • 用户边界:提供友好的错误提示

类型安全保障

  • TypeScript:全面使用TypeScript提供类型检查
  • IDL生成:通过IDL自动生成类型定义
  • 接口约束:严格的接口类型约束

总结

Coze Studio的API授权编辑令牌功能展现了现代前端开发的多个技术亮点:

技术亮点

  1. 架构设计:清晰的分层架构和组件设计
  2. 状态管理:完善的Hook封装和状态管理
  3. 类型安全:全面的TypeScript类型保障
  4. 错误处理:完善的异常捕获和用户反馈机制

学习价值

  1. 组件设计:学习如何设计可复用的业务组件
  2. Hook封装:掌握自定义Hook的最佳实践
  3. API设计:了解IDL驱动的API设计模式
  4. 用户体验:学习如何设计友好的用户交互

最佳实践

  1. 权限控制:细粒度的权限验证和状态检查
  2. 表单处理:完善的表单验证和数据处理
  3. 国际化:统一的多语言支持方案
  4. 性能优化:合理的组件拆分和状态管理

这个编辑令牌功能不仅在功能实现上考虑周全,在工程化、用户体验、安全性等方面也体现了高水准的前端开发实践,为类似功能的开发提供了优秀的参考范例.