钉钉机器人消息发送 npm 库:ddmessage-fruge365

从零开发一个钉钉机器人消息发送 npm 库:ddmessage-fruge365

前言

在日常开发中,我们经常需要将系统状态、错误信息、数据报告等通过钉钉机器人发送到群聊中。虽然钉钉提供了 Webhook API,但每次都要手写 HTTP 请求代码,还要处理跨域问题,非常繁琐。

于是我开发了 ddmessage-fruge365 这个 npm 库,让钉钉机器人消息发送变得简单易用。

项目背景

遇到的问题

  1. 重复代码:每个项目都要写一遍钉钉 API 调用代码
  2. 跨域限制:浏览器环境无法直接调用钉钉 API
  3. 类型安全:缺少 TypeScript 类型定义
  4. 消息格式:需要手动构造复杂的消息结构

解决方案

开发一个统一的 npm 库,提供:

  • 简洁的 API 接口
  • 完整的 TypeScript 支持
  • 跨域代理解决方案
  • 多种消息类型支持

技术选型

核心技术栈

  • TypeScript:提供类型安全和更好的开发体验
  • Axios:处理 HTTP 请求
  • Rollup:构建 ES 模块版本
  • Jest:单元测试框架

项目结构

复制代码
ddmessage-fruge365/
├── src/
│   ├── index.ts          # 主要逻辑
│   ├── types.ts          # 类型定义
│   └── utils.ts          # 工具函数
├── lib/                  # 编译输出
├── package.json
├── tsconfig.json
└── rollup.config.js

核心功能实现

1. 基础类型定义

typescript 复制代码
// src/types.ts
export interface DingTalkConfig {
  accessToken: string;    // 机器人 Access Token
  baseUrl?: string;       // API 基础地址
  timeout?: number;       // 请求超时时间
  proxyPath?: string;     // 代理路径
}

export interface TextMessage {
  msgtype: 'text';
  text: {
    content: string;
  };
  at?: {
    atMobiles?: string[];
    atUserIds?: string[];
    isAtAll?: boolean;
  };
}

export interface DingTalkResponse {
  errcode: number;
  errmsg: string;
}

2. 核心类实现

typescript 复制代码
// src/index.ts
export class DingTalkRobot {
  private client: AxiosInstance;
  private accessToken: string;
  private proxyPath?: string;

  constructor(config: DingTalkConfig) {
    this.accessToken = config.accessToken;
    this.proxyPath = config.proxyPath;
    
    // 根据是否使用代理选择不同的 baseURL
    const baseURL = this.proxyPath ? '' : (config.baseUrl || 'https://oapi.dingtalk.com');
    
    this.client = axios.create({
      baseURL,
      timeout: config.timeout || 10000,
      headers: {
        'Content-Type': 'application/json',
      },
    });
  }

  async send(message: Message): Promise<DingTalkResponse> {
    try {
      let response;
      
      if (this.proxyPath) {
        // 使用代理时,直接请求代理路径
        const url = `${this.proxyPath}?access_token=${this.accessToken}`;
        response = await axios.post(url, message, {
          timeout: 10000,
          headers: { 'Content-Type': 'application/json' }
        });
      } else {
        // 不使用代理时,使用配置的 client
        const url = `/robot/send?access_token=${this.accessToken}`;
        response = await this.client.post(url, message);
      }
      
      return response.data;
    } catch (error: any) {
      throw new Error(`发送消息失败: ${error.message}`);
    }
  }

  async sendText(content: string, at?: AtOptions): Promise<DingTalkResponse> {
    const message: TextMessage = {
      msgtype: 'text',
      text: { content },
      ...(at && { at }),
    };
    return this.send(message);
  }
}

3. 跨域问题解决

这是项目的核心难点。浏览器环境下直接调用钉钉 API 会遇到跨域问题,需要通过代理服务器转发请求。

Vite 代理配置
javascript 复制代码
// vite.config.js
export default {
  server: {
    proxy: {
      '/dd-api': {
        target: 'https://oapi.dingtalk.com/robot/send',
        changeOrigin: true,
        rewrite: (path) => path.replace('/dd-api', ''),
      },
    },
  },
}
库中的处理逻辑
typescript 复制代码
// 根据是否使用代理选择不同的请求方式
if (this.proxyPath) {
  // 浏览器环境:使用代理路径
  const url = `${this.proxyPath}?access_token=${this.accessToken}`;
  response = await axios.post(url, message);
} else {
  // Node.js 环境:直接调用钉钉 API
  const url = `/robot/send?access_token=${this.accessToken}`;
  response = await this.client.post(url, message);
}

构建配置

TypeScript 配置

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./lib",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "declaration": true,
    "sourceMap": true
  }
}

Rollup 配置(ES 模块支持)

javascript 复制代码
// rollup.config.js
const resolve = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
const typescript = require('@rollup/plugin-typescript');

module.exports = {
  input: 'src/index.ts',
  output: {
    file: 'lib/index.esm.js',
    format: 'es'
  },
  plugins: [
    resolve(),
    commonjs(),
    typescript({
      tsconfig: './tsconfig.esm.json'
    })
  ],
  external: ['axios']
};

Package.json 配置

json 复制代码
{
  "main": "lib/index.js",
  "module": "lib/index.esm.js",
  "types": "lib/index.d.ts",
  "exports": {
    ".": {
      "import": "./lib/index.esm.js",
      "require": "./lib/index.js"
    }
  }
}

使用示例

Node.js 环境

javascript 复制代码
const DingTalkRobot = require('ddmessage-fruge365');

const robot = new DingTalkRobot({
  accessToken: 'your-access-token'
});

// 发送文本消息
await robot.sendText('系统启动成功!');

// 发送 Markdown 消息
await robot.sendMarkdown('日报', `
## 今日工作总结
- 完成功能开发 ✅
- 修复 3 个 bug 🐛
- 代码审查通过 👍
`);

Vue 项目中使用

javascript 复制代码
import DingTalkRobot from 'ddmessage-fruge365';

const robot = new DingTalkRobot({
  accessToken: 'your-access-token',
  proxyPath: '/dd-api' // 使用代理
});

// 发送消息
await robot.sendText('Hello from Vue!');

遇到的技术难点

1. 模块格式兼容性

需要同时支持 CommonJS 和 ES 模块:

解决方案

  • 使用 TypeScript 编译 CommonJS 版本
  • 使用 Rollup 构建 ES 模块版本
  • 在 package.json 中配置 exports 字段

2. 跨域代理逻辑

不同环境需要不同的请求方式:

解决方案

  • 通过 proxyPath 参数判断是否使用代理
  • 代理环境直接使用 axios 请求代理路径
  • 非代理环境使用配置的 axios 实例

3. TypeScript 类型安全

确保所有 API 都有完整的类型定义:

解决方案

  • 定义完整的接口类型
  • 使用泛型约束参数类型
  • 生成 .d.ts 声明文件

项目亮点

1. 开箱即用

javascript 复制代码
// 一行代码发送消息
await robot.sendText('Hello World!');

2. 完整的 TypeScript 支持

typescript 复制代码
interface DingTalkConfig {
  accessToken: string;
  proxyPath?: string;
  timeout?: number;
}

3. 跨平台兼容

  • Node.js 服务端
  • Vue/React 前端项目
  • 原生 JavaScript

4. 多种消息类型

  • 文本消息(支持 @ 功能)
  • Markdown 消息
  • 链接消息

性能优化

1. 按需加载

javascript 复制代码
// 只导入需要的功能
import { getCurrentIP } from 'ddmessage-fruge365/lib/utils';

2. 请求优化

  • 配置合理的超时时间
  • 复用 axios 实例
  • 错误重试机制

3. 包体积优化

  • 使用 Rollup 进行 Tree Shaking
  • 外部化依赖(axios)
  • 压缩输出代码

测试与发布

单元测试

javascript 复制代码
// 测试消息发送功能
describe('DingTalkRobot', () => {
  test('should send text message', async () => {
    const robot = new DingTalkRobot({ accessToken: 'test' });
    const result = await robot.sendText('test message');
    expect(result.errcode).toBe(0);
  });
});

发布流程

bash 复制代码
# 1. 构建项目
npm run build

# 2. 运行测试
npm test

# 3. 发布到 npm
npm publish

总结

通过开发 ddmessage-fruge365 这个项目,我深入学习了:

  1. npm 包开发:从设计到发布的完整流程
  2. TypeScript 工程化:类型定义、编译配置、声明文件生成
  3. 跨域问题解决:代理配置、环境适配
  4. 模块化设计:CommonJS 和 ES 模块兼容
  5. 工具链配置:Rollup、Jest、TypeScript 集成

这个库目前已发布到 npm,欢迎大家使用和反馈!

相关链接


如果这篇文章对你有帮助,欢迎点赞收藏!有任何问题也可以在评论区交流讨论。

相关推荐
Cprsensors7 小时前
汽车“电子秤”的核心:车辆称重传感器工作原理浅析
人工智能·科技·机器人·自动化·汽车·无人机
nenchoumi31199 小时前
Nvidia Orin DK 本地 ollama 主流 20GB 级模型 gpt-oss, gemma3, qwen3 部署与测试
gpt·机器人·jetson·orin
Vizio<9 小时前
《D (R,O) Grasp:跨机械手灵巧抓取的机器人 - 物体交互统一表示》论文解读
论文阅读·人工智能·机器人
艾小码11 小时前
只会npm install?这5个隐藏技巧让你效率翻倍!
前端·npm·node.js
BFT白芙堂12 小时前
论文解读 | Franka 机器人沉浸式远程操作:高斯溅射 VR 赋能的遥操框架研发与应用
机器学习·机器人·论文解读·ai辅助·franka·遥操作框架·高斯溅射vr
AiTEN_Robotics16 小时前
自动化仓库托盘搬运减少错误和损坏的方法有哪些?实操案例解读
机器人·自动化
taxunjishu19 小时前
基于 CC-Link IE FB 转 DeviceNet 技术的三菱 PLC 与发那科机器人在汽车涂装线的精准喷涂联动
网络·人工智能·物联网·机器人·自动化·汽车·区块链
小鹿的工作手帐20 小时前
扫地日记:有鹿巡扫机器人在景区被人类“调戏”的365天
机器人
金融Tech趋势派1 天前
企业微信AI在银行落地的3个实用场景:智能机器人、搜索、文档的具体用法
人工智能·机器人·企业微信