# @fefeding/common

[![npm version](https://img.shields.io/npm/v/@fefeding/common.svg)](https://www.npmjs.com/package/@fefeding/common)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

通用工具库，提供数据模型、HTTP 请求封装、API Token 生成、装饰器、远程日志、腾讯云 COS 操作、随机字符串等常用功能。

## 目录

- [安装](#安装)
- [使用方式](#使用方式)
- [工具类 (utils)](#工具类-utils)
  - [HTTP 请求 (axios)](#http-请求-axios)
  - [装饰器 (decorator)](#装饰器-decorator)
  - [API Token (api)](#api-token-api)
  - [远程日志 (logger)](#远程日志-logger)
  - [随机字符串 (rand)](#随机字符串-rand)
  - [对象存储 (s3，S3 兼容)](#对象存储-s3s3-兼容)
  - [PDF 处理 (pdf)](#pdf-处理-pdf)
- [数据模型 (models)](#数据模型-models)
  - [基础模型 (base)](#基础模型-base)
  - [账户模型 (account)](#账户模型-account)
  - [AI 模型 (ai)](#ai-模型-ai)
- [开发](#开发)
- [License](#license)

## 安装

```bash
npm install @fefeding/common
# 或
pnpm add @fefeding/common
```

## 使用方式

本库通过 **默认导出** 暴露一个按源码目录结构组织的命名空间对象，所有 API 都挂在 `Common.utils.*` 与 `Common.models.*` 下：

```js
// ESM
import Common from '@fefeding/common';

// CommonJS
const Common = require('@fefeding/common');
```

**访问规则：**

- 以 `export function` / `export const` / `export class` 形式导出的成员，可直接从命名空间解构。
- 以 `export default` 形式导出的模块（如 `api`、`decorator`、`logger`、各默认导出模型），需通过 `.default` 获取。

```js
// utils —— 命名导出直接解构
const { requestApi, requestServer } = Common.utils.axios;
const { randString, stringToNumber } = Common.utils.rand;
const {
  createClient, uploadFile, putObject, getFile, getObjectUrl, checkExists
} = Common.utils.s3;

// utils —— 默认导出模块取 .default
const decorators      = Common.utils.decorator.default;
const api             = Common.utils.api.default;
const RemotePinoLogger = Common.utils.logger.default;

// models —— 默认导出取 .default，命名导出直接取
const Account          = Common.models.account.account.default;
const { User, EGender } = Common.models.account.user;
const { Session, LoginByCodeReq } = Common.models.account.session;
```

## 工具类 (utils)

### HTTP 请求 (axios)

对 Axios 的封装，支持直接传入请求 URL，或传入带有 `@api` 装饰器的请求模型（自动解析装饰器中定义的 URL）。

| 导出 | 说明 |
|------|------|
| `requestServer(url, option?)` | 发送请求，返回完整的 Axios 响应对象（`AxiosResponse`）。`url` 可为字符串或请求模型对象。 |
| `requestApi(url, option?)` | 等价于 `requestServer`，但只返回响应体 `res.data`（无数据时返回 `null`）。 |
| `default` | 原始 `axios` 实例，可直接使用。 |

```js
import Common from '@fefeding/common';
const { requestApi, requestServer } = Common.utils.axios;

// 直接传入 URL
const data = await requestApi('/api/user/info', { method: 'GET' });

// 传入带 @api 装饰器的请求模型，自动解析 URL
const res = await requestApi(
  new Common.models.account.user.QuerUserReq({ name: 'Tom' })
);
```

### 装饰器 (decorator)

基于 `reflect-metadata` 的装饰器，用于 API 路由映射与权限标记。

| 导出 | 说明 |
|------|------|
| `api(options)` | 类装饰器，标记类的 API 路由配置（如 `{ url }`）。 |
| `getApi(target)` | 获取类/实例的 API 配置。 |
| `checkApiToken(isCheck = true)` | 方法装饰器，标记接口需要 Token 校验。 |
| `getApiToken(target, key)` | 获取方法是否设置了 Token 校验。 |
| `checkApiLogin(isCheck = true)` | 方法装饰器，标记接口需要登录态校验。 |
| `getApiLogin(target, key)` | 获取方法是否设置了登录态校验。 |
| `req(options)` | 类装饰器，标记类为请求对象实例。 |
| `getReq(target)` | 获取类是否标记为请求对象实例。 |

```js
import Common from '@fefeding/common';
const decorators = Common.utils.decorator.default;
const Request = Common.models.base.request.default;

// 标记 API 路由
@decorators.api({ url: '/api/user/save' })
export class SaveUserReq extends Request {
  data: any;
}

// 权限校验标记
@decorators.checkApiToken()
async saveUser() { /* ... */ }

@decorators.checkApiLogin()
async getUserInfo() { /* ... */ }

// 运行时读取元数据
decorators.getApi(SaveUserReq);          // => { url: '/api/user/save' }
decorators.getApiToken(target, 'saveUser');
decorators.getApiLogin(target, 'getUserInfo');
```

### API Token (api)

基于时间戳与 MD5 的 API 校验 Token 生成工具，用于服务端接口安全校验。

| 导出 | 说明 |
|------|------|
| `default` | 模块对象，包含 `createApiToken`。 |
| `createApiToken(accessKey, timestamp?)` | 返回 `{ sign: string, timestamp: string }`。`timestamp` 默认当前时间戳。 |

```js
import Common from '@fefeding/common';
const api = Common.utils.api.default;

const token = api.createApiToken('your-access-key');
// => { sign: 'md5签名', timestamp: '1719465600000' }
```

### 远程日志 (logger)

基于 [pino](https://github.com/pinojs/pino) 的日志记录器，支持本地日志输出与远程日志服务同步。

| 导出 | 说明 |
|------|------|
| `default` | `RemotePinoLogger` 类。 |
| `LoggerOption` | 类型：`remoteUrl`、`serviceName`、`logLevel`、`requestId`、`loginId`、`userId`、`clientIP`、`serverIP`、`apiKey`、`url`、`ext`。 |

方法：`info(message, data?)`、`warn(...)`、`error(...)`、`debug(...)`（均为异步发送、无需 await）、`setOptions(options)`。

```js
import Common from '@fefeding/common';
const RemotePinoLogger = Common.utils.logger.default;

const logger = new RemotePinoLogger({
  remoteUrl: 'https://log-server.com/api/log',
  serviceName: 'my-service',
  logLevel: 'info',
  apiKey: 'your-api-key'
});

logger.info('操作成功', { userId: 123 });
logger.error('发生错误', { error: err.message });
```

> 配置了 `remoteUrl` 且设置 `apiKey` 时，会以 `x-api-token` / `x-api-timestamp` 头携带签名发送到远程。

### 随机字符串 (rand)

| 导出 | 说明 |
|------|------|
| `randString(id = 0, len = 0)` | 基于 ID 与随机时间戳生成唯一短码（36 进制拼接）。`len > 0` 时截断长度，`id = 0` 时不关联 ID。 |
| `stringToNumber(str)` | 将字符串按字符编码求和转换为数字，可用于哈希计算。 |

```js
import Common from '@fefeding/common';
const { randString, stringToNumber } = Common.utils.rand;

const code = randString(123, 8);   // 基于 ID 生成唯一短码
const num  = stringToNumber('abc'); // 字符编码求和
```

### 对象存储 (s3，S3 兼容)

基于 AWS SDK v3（`@aws-sdk/client-s3`、`@aws-sdk/lib-storage`、`@aws-sdk/s3-request-presigner`）实现的 S3 兼容对象存储封装。**通过自定义 `endpoint` 即可对接任意 S3 兼容服务**，不再局限于腾讯云 COS：

| 服务 | `endpoint` | `forcePathStyle` |
|------|-----------|------------------|
| AWS S3（官方） | 省略（默认 `https://s3.amazonaws.com`） | `false` |
| 腾讯云 COS（兼容模式） | `https://cos.<region>.myqcloud.com` | `true` |
| 阿里云 OSS（兼容模式） | `https://oss-<region>.aliyuncs.com` | `true` |
| MinIO | `http://localhost:9000` | `true` |

每个方法均接受 `params` 与 `cos`（`S3Client` 实例或连接配置对象；传配置对象时会自动 `createClient`）。

| 导出 | 说明 |
|------|------|
| `createClient(option)` | 创建 S3 客户端。`option`: `accessKeyId`、`secretAccessKey`、`region?`、`endpoint?`、`forcePathStyle?`、`bucket?`。 |
| `uploadFile(params, cos)` | 分片上传（适合大文件），内部自动处理 multipart。 |
| `putObject(params, cos)` | 简单上传（适合小文件）。 |
| `getFile(params, cos)` | 获取对象，返回 S3 响应（`Body` 为可读流）。 |
| `getObjectUrl(params, cos)` | 获取对象 URL。`Sign: true` 时返回带签名的预签名 URL；否则按 `endpoint` 拼接公开 URL。 |
| `checkExists(params, cos)` | 检查对象是否存在（404 / 403 或异常时返回 `false`）。 |

```js
import Common from '@fefeding/common';
const { createClient, uploadFile, putObject, getFile, getObjectUrl, checkExists } = Common.utils.s3;

// 对接 MinIO（同理可对接 腾讯云 COS / 阿里云 OSS / AWS S3）
const s3 = createClient({
  accessKeyId: 'xxx',
  secretAccessKey: 'xxx',
  endpoint: 'https://cos.ap-guangzhou.myqcloud.com',
  forcePathStyle: true
});

await uploadFile({ Bucket, Key, Body: buffer }, s3);
await putObject({ Bucket, Key, Body: buffer }, s3);

const url    = await getObjectUrl({ Bucket, Key, Sign: true }, s3);
const exists = await checkExists({ Bucket, Key }, s3);
```

> **向后兼容 `txCos`**：`Common.utils.txCos` 保留腾讯云 COS 的**原始调用接口**（`createClient({ SecretId, SecretKey, Region })`、`getObjectUrl({ Region, Sign })` 等），内部自动映射到本 S3 实现（`SecretId→accessKeyId`、`SecretKey→secretAccessKey`、按 `Region` 拼接 `https://cos.<region>.myqcloud.com`、公开 URL 采用虚拟主机风格 `https://<bucket>.cos.<region>.myqcloud.com/<key>` 与原 SDK 一致）。**旧项目无需改动即可继续工作**；新项目建议直接使用 `Common.utils.s3`。

### PDF 处理 (pdf)

> ⚠️ 当前 **已禁用**：实现代码已注释，该模块暂不导出任何运行时 API，保留以用于后续功能扩展。

## 数据模型 (models)

所有模型默认继承 `Model`（支持 `fromJSON` 复制初始化、`fromArray` 数组转换）。TypeORM 实体类继承 `BaseORM` / `ORMBaseFields`。

### 基础模型 (base)

| 模块 | 导出 | 说明 |
|------|------|------|
| `model` | `default` `Model` / `ORMBaseFields` | 基础模型类（JSON 复制 + 数组转换）；`ORMBaseFields` 含 `valid`、`creator`、`updater`、`createTime`、`modifyTime`。 |
| `baseORM` | `default` `BaseORM` | TypeORM 实体基类，映射字段 `Fvalid`、`Fcreator`、`Fupdater`、`Fcreate_time`、`Fmodify_time`。 |
| `request` | `default` `Request` | API 请求基类：`api_token`、`timestamp`、`request_id`。 |
| `response` | `default` `Response<T>` | API 响应基类：`ret`(0=OK)、`msg`、`data`。 |
| `pagination` | `PageRequest<T>`、`PageResponse<T>` | 分页请求（`query`、`page=1`、`size=20`）；分页响应（`data[]`、`page`、`total`）。 |
| `enumType` | `EValid`、`EStatus` | `EValid`: `Valid=1`、`Unvalid=0`；`EStatus`: `ACTIVED=1`、`DISABLED=2`、`OFFLINE=3`、`INACTIVATED=4`。 |
| `cos` | `TextAuditingReq`、`TextAuditingRes` | 腾讯云文本审核请求/响应（`@api '/api/cos/textAuditing'`）及结果接口 `ITextAuditingResult`。 |

### 账户模型 (account)

| 模块 | 导出 | 说明 |
|------|------|------|
| `account` | `default` `Account` | 登录账号：`loginId`、`userId`、`appId`、`openId`、`account`、`unionId`、`password`、`user`。`fromJSON` 会清空 `password`，避免外泄。 |
| `user` | `default` `User` / `EGender`、`EEnable` 及各类 Req/Res | 用户信息：`id`、`name`、`mobile`、`gender(EGender)`、`email`、`avatar`、`telephone`、`enable(EEnable)`、`status(EStatus)`、`alias`、`address`、`ext`。API 模型：`QuerUserReq/Res`、`SaveUserReq/Res`、`DeleteUserReq/Res`、`GetUserByIdReq/Res`。 |
| `session` | `Session`、`AuthMap`、`EAuthMapStatus` 及登录 Req/Res | 会话管理。枚举 `EAuthMapStatus`: `ACTIVED=1`、`DISABLED=2`。登录接口：`LoginByCode`、`LoginByPhone`、`CheckSession`、`CreateSession`、`Logout`、`GetLoginSession`、`GetSession`、`SetStatus`、`LoginByWeWork`、`LoginByUserName`、`GetCodeBySessionToken`、`LoginByWx`、`LoginByAccount`。 |
| `message` | `default` `Message` / `EMsgStatus` | 消息：`title`、`content`、`status(EMsgStatus)`、`appId`、`url`、`toUser`。`EMsgStatus`: `SUCCESS=0`、`FAIL=1`、`TRANS=2`、`OTHER=3`。 |
| `app` | `App`、`EAppType` 及 API Req/Res | 应用配置：`id`、`name`、`type(EAppType)`、`appId`、`appKey`、`secret`、`remark`、`ext`。`EAppType`: `Own=0`、`WxGzh=1`、`WxMiniApp=2`、`WxWeb=3`、`QQ=4`、`BaiduOCR=5`、`WkWeb=6`、`WkMiniApp=7`、`BaiduAccount=8`。API 模型：`QuerApp`、`SaveApp`、`DeleteApp`、`GetApp`、`GetCompanyBaseApp`、`GetLoginApp`。 |
| `verificationCode` | `VerificationCode`、`EStatus`、`ECodeType` 及 API Req/Res | 验证码：`id`、`targetId`、`receiver`、`status`、`codeType`、`code`。`EStatus`(本模块): `Active=0`、`Fail=1`、`Success=2`；`ECodeType`: `Image=0`、`Phone=1`、`Mail=2`。API 模型：`VerificationCodeCreate`、`Validate`、`SendSMSCode`。 |
| `wx` | 各类 Req/Res | 微信相关接口：`GetAccessTokenByAppId`、`GetApiTicketByAppId`、`GetAppTicketByAppId`、`GetJSSDKParams`。 |

### AI 模型 (ai)

| 模块 | 导出 | 说明 |
|------|------|------|
| `message` | `Message` (interface) | AI 对话消息体：`index`、`role`、`content`。 |

## 开发

### 环境要求

- Node.js >= 16
- pnpm（推荐）

### 构建

```bash
pnpm install   # 安装依赖
pnpm build     # 构建（生成 dist/）
pnpm clean     # 清理构建产物
```

### 测试

```bash
pnpm test          # 运行测试
pnpm test:watch    # 监听模式
pnpm test:coverage # 测试覆盖率
```

### 项目结构

```
├── src/
│   ├── models/                  # 数据模型
│   │   ├── base/                # 基础模型（model / baseORM / request / response / pagination / enumType / cos）
│   │   ├── account/             # 账户模型（account / user / session / message / app / verificationCode / wx）
│   │   └── ai/                  # AI 模型（message）
│   └── utils/                   # 工具类
│       ├── api.ts               # API Token 生成
│       ├── axios.ts             # HTTP 请求封装
│       ├── decorator.ts         # 装饰器（api / checkApiToken / checkApiLogin 等）
│       ├── logger.ts            # 远程日志记录器
│       ├── pdf.ts               # PDF 处理（当前已禁用）
│       ├── rand.ts              # 随机字符串生成
│       ├── s3.ts                # 对象存储（S3 兼容：AWS / 腾讯云 COS / 阿里云 OSS / MinIO 等）
│       └── txCos.ts             # 腾讯云 COS 向后兼容适配层（保留原接口，内部映射至 s3）
├── test/                        # 测试文件
├── dist/                        # 构建产物（默认导出命名空间对象）
├── build.js                     # 构建脚本
├── gulpfile.js                  # Gulp 构建配置
└── tsconfig.json                # TypeScript 配置
```

## License

MIT © fefeding
