"为了方便 CI(持续集成/编译)、CT(持续测试)、CD(持续部署)与 Teams 群通知的联动。"
我们可以完整地定义并实现一个自动化闭环系统:
CI → CT → CD → Teams 通知
下面我帮你系统地写出完整思路与实现建议。
🚀 一、整体目标
实现以下自动化流程:
- CI 阶段:持续编译与静态分析
- CT 阶段:持续自动化测试
- CD 阶段:持续部署(到测试或生产环境)
- 通知阶段 :将每个阶段的状态(成功 / 失败 / 日志 / 报告链接)实时发送到 Microsoft Teams 群聊
🧩 二、系统架构(推荐基于 Jenkins)
┌──────────────┐
│ Developer │
│ (Git Push) │
└──────┬───────┘
│
▼
┌──────────────────┐
│ Jenkins Pipeline │
│ ─────────────────│
│ Stage 1: CI │→ Build, Lint, QAC, Unit Test
│ Stage 2: CT │→ Integration & Regression Test
│ Stage 3: CD │→ Deploy to Staging/Prod
│ Stage 4: Notify │→ Send Teams Message
└──────────────────┘
│
▼
┌────────────────────┐
│ Microsoft Teams 群 │
│ (实时通知消息) │
└────────────────────┘
⚙️ 三、各阶段核心逻辑
1️⃣ CI(持续编译 / 集成)
执行构建 + 静态检查:
groovy
stage('CI - Build') {
steps {
echo 'Building project...'
sh 'python build_project.py'
sh 'python run_qac_analysis.py'
}
}
2️⃣ CT(持续测试)
执行自动测试,生成报告:
groovy
stage('CT - Testing') {
steps {
echo 'Running automated tests...'
sh 'pytest tests/ --junitxml=report.xml'
}
}
3️⃣ CD(持续部署)
部署到测试或生产环境:
groovy
stage('CD - Deployment') {
steps {
echo 'Deploying application...'
sh 'python deploy_to_env.py --env staging'
}
}
4️⃣ Teams 群通知
在 Jenkins 或 Python 中发送消息到 Teams,有两种实现方式:
:直接用 Python + Microsoft Graph API
Python 示例:
python
import requests
import json
# Teams webhook URL(可在群中添加"传入Webhook"连接器获取)
webhook_url = "https://outlook.office.com/webhook/XXXXXX"
def send_teams_message(title, message, color="00FF00"):
payload = {
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"themeColor": color,
"summary": title,
"sections": [{
"activityTitle": title,
"text": message
}]
}
requests.post(webhook_url, headers={"Content-Type": "application/json"}, data=json.dumps(payload))
# 使用示例
send_teams_message("✅ CI/CD Pipeline 成功", "构建、测试、部署均通过 ✅")
你也可以选择使用 Microsoft Graph API + OAuth2 Token 的方式,从 Python 直接向 Teams 群聊 (非 webhook)发送消息,这种方式安全性更高且支持富文本消息。
👉 我之前帮你实现过 Graph API + Python 发送 Teams 消息的版本,可以无缝集成在这里。
📦 四、最终效果(通知示例)
Teams 群中会出现自动消息:
✅ CI/CD 通知
- 项目:
MyApp- 构建状态:✅ 成功
- 持续测试:通过 157 / 157
- 部署环境:Staging
- 报告链接:点击查看报告
时间:2025-10-24 14:03:22
🧠 五、扩展思路
- 支持 每日自动 CT 触发(例如晚上 23:00 执行回归测试)
- 支持 Teams 通知带上 Jenkins 构建日志或附件
- 支持失败自动重试与通知分级(@负责人)
- 可接入 Helix QAC / SCA / Python 自动分析结果
🔗 直接访问 Azure 应用注册页面:https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
❗️可能原因 + 解决办法
| 问题 | 原因 | 解决办法 |
|---|---|---|
| ❌ 看不到 App registrations | 你没有 Azure AD 管理员权限 或所在租户禁用了自助注册 | 联系你们 IT 管理员,请其分配 应用管理员角色 或开通权限 |
| ❌ 搜索不到已注册的 App | 可能你当前视图是"我的应用"而不是"所有应用" | 在 App registrations 页面 → 点击"所有应用"视图 |
| ✅ 页面空白 | 当前租户中没有注册任何应用 | 点击右上角"新注册"创建新的应用注册即可 |
向群里发消息需要申请这两个 ChatMessage.Read(delegated), ChatMessage.Send (delegated)
列了下面几种可以向Teams发送CI信息的方式, 咱们选择的是Microsoft Graph API方式。
| 方法 | 优点 | 缺点 | 场景 |
|---|---|---|---|
| Incoming Webhook | 简单、快速,容易集成脚本或 CI/CD | 只能发到指定频道,功能有限,不支持用户交互,不支持消息发群里 | 简单通知、自动化脚本、CI/CD 报告 |
| Microsoft Graph API | 功能强大,可发送消息、附件、Adaptive Card,可读取消息、支持机器人 | 配置复杂,需要注册 Azure AD 应用,获取 token | 企业自动化、复杂机器人、双向交互、系统集成 |
| 通过邮箱发送 | 简单,无需开发,只需频道邮箱 | 无法格式化消息,灵活性低,无法交互 | 快速通知、邮件转 Teams 方案 |
| Teams Bot(Bot Framework) | 可实现双向对话、复杂逻辑、交互式消息 | 开发和配置复杂,需要注册 Bot,部署服务 | 智能机器人、自动客服、交互式通知 |
| Power Automate / Logic Apps | 无需写代码,易于可视化自动化,支持多平台集成 | 灵活性和定制性有限,依赖 Microsoft 云服务 | 自动化流程、条件触发通知、低代码方案 |
| 直接 SharePoint / OneDrive 文件触发 | 可与 Teams 频道绑定文件操作事件,自动通知 | 需要 SharePoint/OneDrive 环境,依赖微软生态 | 文件上传/修改触发通知、文档协作场景 |
| 第三方集成工具(如 Zapier、IFTTT) | 简单易用,支持多平台数据流转 | 需要额外账号或付费,功能受限 | 跨平台通知、快速自动化、低代码 |
六、使用Microsoft Graph API方式(推荐)
🧭 操作路径:确认你进入的是对的位置
-
登录 Azure 门户:
-
左侧菜单点击:Azure Active Directory
-
在左边菜单中找到并点击:应用注册 (App registrations)
如果你没有看到这个选项,可以尝试以下操作:
- 点击左上角搜索栏,输入
App registrations - 或者从 URL 直接进入:https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade
- 点击左上角搜索栏,输入
创建 APP
点击上面链接创建一个APP,内容如下:

需要注意: 应用程序(客户端) ID 和 目录(租户) ID 这两个ID,再python创建脚本中会用到,不要泄露。
2. 向Teams IT管理员申请可以向Teams发送,读写,接收的API权限
需要联系管理员,如果是个人的话可能不需要。如下图:

3.首先要获取API Token(一般有效期三个月)
获取token时需要先在刚注册的APP中,打开 允许公共客户端流 ,如下图所示:

4. 然后使用python结合API获取当前账号Teams的群信息和一对一聊天的群消息。
下面是获取群id 和私聊id源码
python
import os
import time
import html
import msal
import requests
import subprocess
class TeamsCIReporter:
def __init__(self, client_id, tenant_id):
self.client_id = client_id
self.tenant_id = tenant_id
self.token = None
def authenticate(self):
"""使用 Device Code Flow 登录"""
app = msal.PublicClientApplication(
client_id=self.client_id,
authority=f"https://login.microsoftonline.com/{self.tenant_id}"
)
# 使用你授权的 ChatMessage.Send + ChatMessage.Read
scopes = [
"Chat.ReadWrite",
"ChatMessage.Send",
"ChatMessage.Read",
]
flow = app.initiate_device_flow(scopes=[f"https://graph.microsoft.com/{s}" for s in scopes])
if "user_code" not in flow:
raise Exception(f"启动设备码登录失败: {flow}")
print(flow["message"]) # 提示去网页输入验证码
self.token = app.acquire_token_by_device_flow(flow)
if "access_token" not in self.token:
raise Exception(f"获取Token失败: {self.token}")
print("✅ 登录成功!")
def list_chats(self):
"""获取所有群聊 ID"""
headers = {"Authorization": f"Bearer {self.token['access_token']}"}
url = "https://graph.microsoft.com/v1.0/me/chats"
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
raise Exception(f"获取群聊失败: {resp.status_code} - {resp.text}")
chats = resp.json().get("value", [])
print("=== 群聊列表 ===")
for c in chats:
print(f"ID: {c['id']} | 主题: {c.get('topic', '无主题')} | 类型: {c['chatType']}")
return chats
def list_messages(self, chat_id, top=10):
"""获取指定聊天 ID 的最新 N 条消息"""
headers = {"Authorization": f"Bearer {self.token['access_token']}"}
url = f"https://graph.microsoft.com/v1.0/chats/{chat_id}/messages?$top={top}&$orderby=createdDateTime desc"
resp = requests.get(url, headers=headers)
if resp.status_code != 200:
raise Exception(f"获取消息失败: {resp.status_code} - {resp.text}")
messages = resp.json().get("value", [])
print(f"=== 聊天 {chat_id} 的最新 {top} 条消息 ===")
for m in messages:
sender_info = m.get("from")
if sender_info and "user" in sender_info:
sender = sender_info["user"].get("displayName", "未知")
else:
sender = "系统消息/未知发送者"
content = m.get("body", {}).get("content", "")
time = m.get("createdDateTime", "")
print(f"[{time}] {sender}: {content}")
return messages
def send_message(self, chat_id, message):
"""发送消息到指定群聊"""
headers = {
"Authorization": f"Bearer {self.token['access_token']}",
"Content-Type": "application/json"
}
url = f"https://graph.microsoft.com/v1.0/chats/{chat_id}/messages"
payload = {
"body": {
"contentType": "html",
"content": message
}
}
resp = requests.post(url, headers=headers, json=payload)
if resp.status_code != 201:
raise Exception(f"发送消息失败: {resp.status_code} - {resp.text}")
print("✅ 消息发送成功!")
if __name__ == "__main__":
CLIENT_ID = "dssdvdsvsdsvd9csfsfsf62bfb" # 应用程序(客户端) ID,复制即可
TENANT_ID = "0aeffsafsffsasad10f4" # 你的租户 ID 目录(租户) ID
reporter = TeamsCIReporter(CLIENT_ID, TENANT_ID)
reporter.authenticate()
chats = reporter.list_chats() #获取群聊ID
print(chats)
# ci_message = """CI 集成第三版软件请查阅"""
# target_chat_id="19:06cb25dsssddsgdfsgshakfhsfsf@thread.v2"
# reporter.send_message(target_chat_id, ci_message)
最终获取的类似于下面这种群聊ID和 私聊ID,然后可以通过获取群聊ID 就可以向指定的群里发消息和获取消息了。
txt
=== 群聊列表 ===
ID: 19:asafsdfsdfsdgdsg9@thread.v2 | 主题(群名): XXXXXX大分队 | 类型: group
ID: 19:asafasdsfsdfd7d@thread.v2 | 主题(群名): XXXXX小分队 | 类型: group
ID: 19:08safsfffsaff4sadsfassff7@unq.gbl.spaces | 主题(群名): None | 类型: oneOnOne(私聊的id)
建议:上面这个代码运行了需要手动登录刚注册的APP 平台后获取登录token,属于半自动的,所以可以在源码中加一个逻辑,将生成的token保存到一个文件中,就后面每次手动登录了 直接使用这个保存的token,一般这个token有效期3个月。
我文章中发的这些群id的信息是错误的,请运行代码自己获取谢谢。
这样就是一个teams群通知的一个案例。希望能帮到大家。