You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node <=13的ExpressJS应用中实现Microsoft Teams登录与通知

实现方案概述

完全可以通过Microsoft Graph API在ExpressJS后端实现需求,无需Teams Bots/Apps,以下是分步指引、兼容Node13的工具选择及代码示例。

一、Entra Admin中心应用注册配置

  1. 注册Web应用,重定向URI设为你的Express登录回调地址(如http://localhost:3000/auth/microsoft/callback)
  2. 添加Delegated权限并按需申请管理员同意:
    • User.Read:获取用户基本信息(登录必备)
    • Group.Read.All:读取用户加入的Teams团队(Teams对应Office 365组)
    • Channel.ReadBasic.All:读取团队下的频道列表
    • ChannelMessage.Send:向指定频道发送消息
    • 若需通过API创建Incoming Webhook,需添加ChannelSettings.ReadWrite.All
  3. 生成客户端密码(Client Secret),保存好客户端ID、租户ID、客户端密码

二、兼容Node13的依赖选择

  • 身份认证:@azure/msal-node@1.14.0(v1.x版本支持Node10+,v2+要求Node14+)
  • Graph API调用:@microsoft/microsoft-graph-client@3.0.0(v3.x支持Node10+,v4+要求Node14+)
  • Express中间件:express-session(用于存储用户会话和授权状态)

安装命令:

npm install @azure/msal-node@1.14.0 @microsoft/microsoft-graph-client@3.0.0 express-session express

三、核心流程与代码实现

1. 用户登录(OAuth2授权码流程)

const express = require('express');
const session = require('express-session');
const { ConfidentialClientApplication } = require('@azure/msal-node');

const app = express();
app.use(session({ secret: 'your-session-secret', resave: false, saveUninitialized: false }));

// MSAL配置
const msalConfig = {
  auth: {
    clientId: 'YOUR_CLIENT_ID',
    authority: 'https://login.microsoftonline.com/YOUR_TENANT_ID',
    clientSecret: 'YOUR_CLIENT_SECRET'
  }
};
const cca = new ConfidentialClientApplication(msalConfig);

// 登录路由:跳转至Microsoft授权页
app.get('/auth/microsoft', (req, res) => {
  const authCodeUrlParameters = {
    scopes: ['User.Read', 'Group.Read.All', 'Channel.ReadBasic.All', 'ChannelMessage.Send'],
    redirectUri: 'http://localhost:3000/auth/microsoft/callback'
  };

  cca.getAuthCodeUrl(authCodeUrlParameters)
    .then((response) => res.redirect(response))
    .catch((error) => res.status(500).send(error.message));
});

// 回调路由:获取授权码并交换token
app.get('/auth/microsoft/callback', (req, res) => {
  const tokenRequest = {
    code: req.query.code,
    scopes: ['User.Read', 'Group.Read.All', 'Channel.ReadBasic.All', 'ChannelMessage.Send'],
    redirectUri: 'http://localhost:3000/auth/microsoft/callback'
  };

  cca.acquireTokenByCode(tokenRequest)
    .then((response) => {
      // 存储token到会话
      req.session.accessToken = response.accessToken;
      req.session.userId = response.account.localAccountId;
      res.redirect('/select-channel');
    })
    .catch((error) => res.status(500).send(error.message));
});

2. 获取用户Teams频道列表

const { Client } = require('@microsoft/microsoft-graph-client');

// 初始化Graph客户端
function getGraphClient(accessToken) {
  const authProvider = {
    getAccessToken: async () => accessToken
  };
  return Client.initWithMiddleware({ authProvider });
}

// 获取用户加入的Teams及频道
app.get('/select-channel', async (req, res) => {
  if (!req.session.accessToken) return res.redirect('/auth/microsoft');

  const client = getGraphClient(req.session.accessToken);
  try {
    // 获取用户加入的Teams团队
    const teams = await client.api('/me/joinedTeams').get();
    // 为每个团队获取频道列表
    const teamsWithChannels = await Promise.all(teams.value.map(async (team) => {
      const channels = await client.api(`/teams/${team.id}/channels`).get();
      return {
        id: team.id,
        displayName: team.displayName,
        channels: channels.value.filter(ch => ch.membershipType === 'standard') // 过滤标准频道
      };
    }));
    // 渲染频道选择页面(这里简化为返回JSON,实际可渲染模板)
    res.json(teamsWithChannels);
  } catch (error) {
    res.status(500).send(error.message);
  }
});

3. 后端发起频道通知

方式一:通过Graph API发送消息

app.post('/send-notification', async (req, res) => {
  const { channelId, message } = req.body;
  if (!req.session.accessToken || !channelId || !message) return res.status(400).send('参数缺失');

  const client = getGraphClient(req.session.accessToken);
  try {
    await client.api(`/channels/${channelId}/messages`).post({
      body: {
        content: message
      }
    });
    res.send('通知发送成功');
  } catch (error) {
    res.status(500).send(error.message);
  }
});

方式二:使用Incoming Webhook(无需持续token)

如果不想依赖用户token有效期,可让用户选择频道后创建/提供Incoming Webhook URL:

  1. 通过API创建Webhook(需ChannelSettings.ReadWrite.All权限):
async function createIncomingWebhook(client, teamId, channelId, webhookName) {
  const webhook = await client.api(`/teams/${teamId}/channels/${channelId}/webhooks`).post({
    displayName: webhookName,
    configuration: {
      url: '' // 由Graph自动生成URL
    }
  });
  return webhook.configuration.url;
}
  1. 发送通知时直接POST到Webhook URL:
const axios = require('axios'); // axios@0.27.x支持Node13

app.post('/send-webhook-notification', async (req, res) => {
  const { webhookUrl, message } = req.body;
  try {
    await axios.post(webhookUrl, {
      text: message
    });
    res.send('通知发送成功');
  } catch (error) {
    res.status(500).send(error.message);
  }
});

四、关键注意事项

  1. Node13兼容性:所有依赖必须锁定到支持Node13的版本,避免安装最新版
  2. 权限范围:Group.Read.All和ChannelSettings.ReadWrite.All需要租户管理员同意,若无法获取管理员同意,可引导用户手动添加Incoming Webhook(在Teams频道设置中添加"Incoming Webhook"连接器,复制URL给后端)
  3. Token有效期:Delegated token有效期约1小时,需实现刷新token逻辑(msal-node可自动处理刷新)
  4. 会话存储:生产环境需用Redis等持久化存储替代默认的内存session

内容的提问来源于stack exchange,提问作者Wolf Dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 08:35:01