编辑 | blame | 历史 | 原始文档
/**
 * Node.js + Express 企业微信 OAuth 后端示例
 * 
 * 安装依赖:
 * npm install express axios cors dotenv morgan
 * 
 * 启动服务:
 * node server.js
 */

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const axios = require('axios');
const path = require('path');

const app = express();

// 中间件
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(express.static(path.join(__dirname, '../dist')));

// 企业微信配置
const WECOM_CONFIG = {
    corpId: process.env.WECOM_CORP_ID,
    appSecret: process.env.WECOM_APP_SECRET,
    agentId: process.env.WECOM_AGENT_ID
};

// 缓存 access_token
let accessTokenCache = {
    token: null,
    expireTime: 0
};

/**
 * 获取企业微信访问令牌
 */
async function getAccessToken() {
    const now = Date.now();
    
    // 如果 token 未过期,直接返回缓存
    if (accessTokenCache.token && accessTokenCache.expireTime > now) {
        return accessTokenCache.token;
    }

    try {
        const response = await axios.get('https://qyapi.weixin.qq.com/cgi-bin/gettoken', {
            params: {
                corpid: WECOM_CONFIG.corpId,
                corpsecret: WECOM_CONFIG.appSecret
            }
        });

        if (response.data.errcode === 0) {
            const token = response.data.access_token;
            const expiresIn = response.data.expires_in || 7200;
            
            accessTokenCache = {
                token: token,
                expireTime: now + (expiresIn - 300) * 1000 // 提前5分钟刷新
            };

            return token;
        } else {
            throw new Error(`获取 token 失败: ${response.data.errmsg}`);
        }
    } catch (error) {
        console.error('获取 access_token 错误:', error.message);
        throw error;
    }
}

/**
 * API: 使用授权码获取用户信息
 */
app.post('/api/wecom/getUserInfo', async (req, res) => {
    const { code } = req.body;

    if (!code) {
        return res.status(400).json({
            code: -1,
            message: '缺少授权码'
        });
    }

    try {
        const accessToken = await getAccessToken();

        // 获取用户信息
        const response = await axios.get('https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail', {
            params: {
                access_token: accessToken,
                code: code
            }
        });

        if (response.data.errcode === 0) {
            const userData = {
                userid: response.data.userid,
                name: response.data.name,
                mobile: response.data.mobile || '',
                email: response.data.email || '',
                department: (response.data.department && response.data.department[0]) || '',
                position: response.data.position || '',
                gender: response.data.gender || 0,
                avatar: response.data.avatar || '',
                extattr: response.data.extattr || {}
            };

            // 可选: 将用户信息存储到数据库
            // await saveUserToDatabase(userData);

            res.json({
                code: 0,
                message: 'success',
                data: userData
            });
        } else {
            res.status(401).json({
                code: response.data.errcode,
                message: response.data.errmsg || '获取用户信息失败'
            });
        }
    } catch (error) {
        console.error('获取用户信息错误:', error.message);
        res.status(500).json({
            code: -1,
            message: '获取用户信息失败: ' + error.message
        });
    }
});

/**
 * API: 获取用户详情
 */
app.get('/api/wecom/userDetail/:userId', async (req, res) => {
    const { userId } = req.params;

    try {
        const accessToken = await getAccessToken();

        const response = await axios.get('https://qyapi.weixin.qq.com/cgi-bin/user/get', {
            params: {
                access_token: accessToken,
                userid: userId
            }
        });

        if (response.data.errcode === 0) {
            res.json({
                code: 0,
                message: 'success',
                data: response.data
            });
        } else {
            res.status(404).json({
                code: response.data.errcode,
                message: response.data.errmsg
            });
        }
    } catch (error) {
        console.error('获取用户详情错误:', error.message);
        res.status(500).json({
            code: -1,
            message: '获取用户详情失败'
        });
    }
});

/**
 * API: 验证用户权限
 */
app.get('/api/wecom/verify/:userId', async (req, res) => {
    const { userId } = req.params;

    try {
        // 实现自己的权限验证逻辑
        // 例如: 检查用户是否在允许的部门列表中
        const authorized = await checkUserPermission(userId);

        res.json({
            code: 0,
            message: 'success',
            data: {
                authorized: authorized
            }
        });
    } catch (error) {
        console.error('验证用户权限错误:', error.message);
        res.status(500).json({
            code: -1,
            message: '验证失败'
        });
    }
});

/**
 * API: 同步用户到本地系统
 */
app.post('/api/user/sync', async (req, res) => {
    const { userid, name, mobile } = req.body;

    try {
        // 实现自己的用户同步逻辑
        // 例如: 保存到数据库、更新缓存等

        console.log(`同步用户: ${userid}, ${name}, ${mobile}`);

        res.json({
            code: 0,
            message: 'success',
            data: {
                synced: true
            }
        });
    } catch (error) {
        console.error('同步用户错误:', error.message);
        res.status(500).json({
            code: -1,
            message: '同步用户失败'
        });
    }
});

/**
 * 健康检查
 */
app.get('/api/health', (req, res) => {
    res.json({
        code: 0,
        message: 'OK',
        timestamp: new Date().toISOString()
    });
});

/**
 * SPA 路由处理
 */
app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, '../dist/index.html'));
});

/**
 * 错误处理中间件
 */
app.use((error, req, res, next) => {
    console.error('未捕获的错误:', error);
    res.status(500).json({
        code: -1,
        message: '服务器内部错误'
    });
});

/**
 * 权限检查辅助函数 (示例)
 */
async function checkUserPermission(userId) {
    // 实现你的权限验证逻辑
    // 例如:
    // 1. 检查用户是否在允许列表中
    // 2. 检查用户的部门是否被授权
    // 3. 查询数据库的权限配置

    // 这里返回 true 作为示例
    return true;
}

/**
 * 数据库保存函数 (示例)
 */
async function saveUserToDatabase(userData) {
    // 实现保存到数据库的逻辑
    // 例如使用 MongoDB:
    // const user = new User(userData);
    // await user.save();
    console.log('保存用户到数据库:', userData);
}

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
    console.log(`服务器运行在 http://localhost:${PORT}`);
    console.log(`企业微信配置:`);
    console.log(`  CorpID: ${WECOM_CONFIG.corpId}`);
    console.log(`  AgentID: ${WECOM_CONFIG.agentId}`);
});

module.exports = app;