前言
企业内部系统常见的场景:
公司的内部系统通过企业微信给用户推送卡片通知,同时点击卡片通知可以单点登录到我们自己的系统,完成对应的操作

我今天会来做一个简单的示例,实现:
企业微信卡片消息通知 + 单点登录跳转(SSO)
以下是一个完整的可运行示例,包括:
- 推送卡片消息(企业微信 API)
- 单点登录逻辑(生成一次性登录 token)
- 用户点击卡片后自动登录进入系统
🧩 一、系统架构说明
┌──────────────────────────────┐
│      内部业务系统(Backend) │
│                              │
│ ① 业务事件触发               │
│ ② 调用企业微信推送卡片 API   │
│ ③ 生成一次性登录 token        │
│ ④ 用户点击卡片 → 跳转链接    │
│ ⑤ 验证 token → 登录成功       │
└──────────────────────────────┘
          ↓
┌──────────────────────────────┐
│ 企业微信客户端               │
│  用户点击卡片                │
│  浏览器打开目标系统 URL      │
└──────────────────────────────┘
🧠 二、技术栈建议
| 模块 | 技术 | 
|---|---|
| 后端接口 | Node.js + Express | 
| 企业微信消息推送 | 企业微信官方 API | 
| Token 管理 | JWT(或自定义一次性签名) | 
| 登录系统 | 自有系统 API | 
| 部署 | 内网服务器或云函数均可 | 
🧾 三、卡片推送 + 登录实现示例
1️⃣ 企业微信卡片消息推送
使用企业微信官方接口:
// sendCardMessage.js
import fetch from "node-fetch";
const CORP_ID = "wwxxxxxx";
const CORP_SECRET = "your_secret";
const AGENT_ID = "1000002";
async function getAccessToken() {
  const resp = await fetch(
    `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${CORP_ID}&corpsecret=${CORP_SECRET}`
  );
  const data = await resp.json();
  return data.access_token;
}
export async function sendCardMessage(userId, title, description, ssoUrl) {
  const token = await getAccessToken();
  const body = {
    touser: userId,
    msgtype: "textcard",
    agentid: AGENT_ID,
    textcard: {
      title: title,
      description: description,
      url: ssoUrl, // 单点登录跳转地址
      btntxt: "立即处理",
    },
  };
  const resp = await fetch(
    `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${token}`,
    {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    }
  );
  return resp.json();
}
2️⃣ 生成单点登录链接(一次性 Token)
// tokenGenerator.js
import jwt from "jsonwebtoken";
const SECRET = "Your_Secret_Key";
export function generateSSOToken(userId) {
  return jwt.sign({ userId, ts: Date.now() }, SECRET, { expiresIn: "2m" }); // 2分钟有效
}
export function verifySSOToken(token) {
  try {
    return jwt.verify(token, SECRET);
  } catch (err) {
    return null;
  }
}
3️⃣ Express 后端逻辑(卡片点击登录)
// server.js
import express from "express";
import { sendCardMessage } from "./sendCardMessage.js";
import { generateSSOToken, verifySSOToken } from "./tokenGenerator.js";
const app = express();
// 模拟推送通知
app.get("/notify", async (req, res) => {
  const userId = "zhangsan";
  const token = generateSSOToken(userId);
  const ssoUrl = `https://your-domain.com/sso-login?token=${token}`;
  const resp = await sendCardMessage(
    userId,
    "审批待处理",
    "<div>您有一条待审批事项,请及时处理。</div>",
    ssoUrl
  );
  res.json(resp);
});
// 用户点击卡片后跳转登录
app.get("/sso-login", (req, res) => {
  const token = req.query.token;
  const data = verifySSOToken(token);
  if (!data) return res.status(401).send("登录链接已失效或无效。");
  // 模拟写入会话
  res.cookie("user", data.userId, { httpOnly: true });
  res.redirect("/dashboard");
});
// 模拟登录后页面
app.get("/dashboard", (req, res) => {
  res.send(`<h2>欢迎 ${req.cookies?.user || "访客"} 登录系统!</h2>`);
});
app.listen(3000, () => console.log("✅ Server started on http://localhost:3000"));
📱 四、流程示意
1️⃣ 内部系统触发事件(例如审批待处理)
2️⃣ 调用 /notify → 向指定员工推送卡片消息
3️⃣ 员工在企业微信中点击“立即处理”按钮
4️⃣ 跳转到 https://your-domain.com/sso-login?token=xxx
5️⃣ 系统验证 token → 自动登录 → 进入操作界面
🔐 五、安全建议
- Token 必须设置 短期有效期(< 5分钟)
- 每个 Token 一次性使用(存 Redis 以防重放)
- 建议使用 HTTPS
- 内网系统与企业微信服务器间调用应有 IP 白名单
 
                   
                   
                   
                  
 
       
           
                 
                 
                 
                 
                 
                
               
                 
                 
                 
                 
                
               
                 
                 扫一扫
扫一扫
                     
                     
              
             
                   2295
					2295
					
 被折叠的  条评论
		 为什么被折叠?
被折叠的  条评论
		 为什么被折叠?
		 
		  到【灌水乐园】发言
到【灌水乐园】发言                                
		 
		 
    
   
    
   
             
					 
					 
					


 
            