本文目标是让你在本地开发环境下,实现微信生态用户与抖音生态用户的行为数据采集、关联与存储,形成一个可查询的统一用户视图。整个过程不依赖任何付费第三方平台,所有代码均可直接复制使用。
我们使用Node.js作为后端服务,利用各平台的官方开放接口获取数据。数据流转路径为:微信/抖音客户端 -> 各平台服务器 -> 我们的Node.js服务 -> 本地MySQL数据库。
node -v 检查,若无则访问 https://nodejs.org 下载安装。mysql --version 检查,若无则访问 https://dev.mysql.com/downloads/mysql/ 下载安装。在终端依次执行以下命令,创建项目并安装依赖:
mkdir cross-platform-data
cd cross-platform-data
npm init -y
npm install express mysql2 axios body-parser cors
在MySQL中创建一个名为 `user_cross_platform` 的数据库,并执行以下SQL语句创建核心表。
CREATE TABLE `unified_users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`union_id` varchar(100) DEFAULT NULL COMMENT '跨平台唯一ID,由我们生成',
`wechat_openid` varchar(100) DEFAULT NULL COMMENT '微信用户在此公众号下的唯一标识',
`wechat_nickname` varchar(255) DEFAULT NULL,
`douyin_openid` varchar(100) DEFAULT NULL COMMENT '抖音用户在此应用下的唯一标识',
`douyin_nickname` varchar(255) DEFAULT NULL,
`phone_number` varchar(20) DEFAULT NULL COMMENT '通过授权后获取的手机号',
`last_wechat_active` datetime DEFAULT NULL,
`last_douyin_active` datetime DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_union_id` (`union_id`),
KEY `idx_wechat_openid` (`wechat_openid`),
KEY `idx_douyin_openid` (`douyin_openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
登录微信公众平台(mp.weixin.qq.com),进入「开发」->「基本配置」页面。
在项目根目录创建 `wechatService.js` 文件,写入以下完整代码:

const axios = require('axios');
const mysql = require('mysql2/promise');
// 配置数据库连接(替换为你的实际信息)
const dbConfig = {
host: 'localhost',
user: 'root',
password: 'your_mysql_password',
database: 'user_cross_platform',
port: 3306
};
// 你的微信配置
const wechatConfig = {
appId: '你的微信AppID',
appSecret: '你的微信AppSecret'
};
class WechatService {
constructor() {
this.pool = mysql.createPool(dbConfig);
}
// 1. 获取Access Token
async getAccessToken() {
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${wechatConfig.appId}&secret=${wechatConfig.appSecret}`;
try {
const response = await axios.get(url);
return response.data.access_token;
} catch (error) {
console.error('获取微信AccessToken失败:', error.response?.data);
return null;
}
}
// 2. 通过网页授权code获取用户OpenID和信息
async getUserInfoByCode(code) {
// 第一步:用code换access_token和openid
const tokenUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${wechatConfig.appId}&secret=${wechatConfig.appSecret}&code=${code}&grant_type=authorization_code`;
const tokenRes = await axios.get(tokenUrl);
const { openid, access_token: userAccessToken } = tokenRes.data;
// 第二步:用access_token和openid获取用户详细信息
const infoUrl = `https://api.weixin.qq.com/sns/userinfo?access_token=${userAccessToken}&openid=${openid}&lang=zh_CN`;
const infoRes = await axios.get(infoUrl);
const userInfo = infoRes.data;
// 3. 存入或更新数据库
const connection = await this.pool.getConnection();
try {
// 先检查该微信用户是否已存在
const [rows] = await connection.execute(
'SELECT union_id FROM unified_users WHERE wechat_openid = ?',
[openid]
);
let unionId;
if (rows.length > 0) {
unionId = rows[0].union_id;
// 更新信息
await connection.execute(
'UPDATE unified_users SET wechat_nickname = ?, last_wechat_active = NOW() WHERE wechat_openid = ?',
[userInfo.nickname, openid]
);
} else {
// 生成一个新的跨平台Union ID
unionId = `union_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 插入新记录
await connection.execute(
`INSERT INTO unified_users (union_id, wechat_openid, wechat_nickname, last_wechat_active) VALUES (?, ?, ?, NOW())`,
[unionId, openid, userInfo.nickname]
);
}
return { unionId, openid, nickname: userInfo.nickname };
} finally {
connection.release();
}
}
}
module.exports = new WechatService();
登录抖音开放平台(developer.open-douyin.com),进入「控制台」->「应用管理」,创建一个网页应用。
在项目根目录创建 `douyinService.js` 文件,写入以下完整代码:
const axios = require('axios');
const mysql = require('mysql2/promise');
const dbConfig = {
host: 'localhost',
user: 'root',
password: 'your_mysql_password',
database: 'user_cross_platform',
port: 3306
};
const douyinConfig = {
clientKey: '你的抖音Client Key',
clientSecret: '你的抖音Client Secret'
};
class DouyinService {
constructor() {
this.pool = mysql.createPool(dbConfig);
}
// 1. 通过授权码获取Access Token和用户信息
async getUserInfoByCode(code) {
// 获取access_token
const tokenUrl = 'https://open.douyin.com/oauth/access_token/';
const tokenParams = {
client_key: douyinConfig.clientKey,
client_secret: douyinConfig.clientSecret,
code: code,
grant_type: 'authorization_code'
};
const tokenRes = await axios.post(tokenUrl, null, { params: tokenParams });
const { access_token, open_id, expires_in } = tokenRes.data.data;
// 获取用户公开信息
const userUrl = 'https://open.douyin.com/oauth/userinfo/';
const userRes = await axios.get(userUrl, {
params: { access_token, open_id }
});
const userInfo = userRes.data.data;
// 2. 关联到数据库
const connection = await this.pool.getConnection();
try {
// 情况1:该抖音用户已存在,更新信息
const [existingUser] = await connection.execute(
'SELECT union_id FROM unified_users WHERE douyin_openid = ?',
[open_id]
);
if (existingUser.length > 0) {
await connection.execute(
'UPDATE unified_users SET douyin_nickname = ?, last_douyin_active = NOW() WHERE douyin_openid = ?',
[userInfo.nickname, open_id]
);
return { unionId: existingUser[0].union_id, openId: open_id, nickname: userInfo.nickname };
}
// 情况2:新抖音用户,尝试通过手机号匹配(假设之前微信授权已获取手机号)
// 这里演示逻辑,实际需先完成手机号授权流程
// 情况3:完全新用户,创建新记录
const unionId = `union_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
await connection.execute(
`INSERT INTO unified_users (union_id, douyin_openid, douyin_nickname, last_douyin_active) VALUES (?, ?, ?, NOW())`,
[unionId, open_id, userInfo.nickname]
);
return { unionId, openId: open_id, nickname: userInfo.nickname };
} finally {
connection.release();
}
}
// 获取用户手机号(需用户单独授权,此处给出接口示例)
async getPhoneNumber(accessToken, encryptedData) {
// 解密逻辑需使用抖音提供的SDK,此处为接口调用示例
const url = 'https://open.douyin.com/api/apps/v2/user/phone_number/';
const response = await axios.post(url, {
encrypted_data: encryptedData
}, {
headers: {
'access-token': accessToken
}
});
return response.data.data.phone_number;
}
}
module.exports = new DouyinService();
在项目根目录创建 `app.js` 文件,作为服务入口。
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const wechatService = require('./wechatService');
const douyinService = require('./douyinService');
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 1. 微信授权回调接口
app.get('/api/auth/wechat/callback', async (req, res) => {
const { code } = req.query;
if (!code) {
return res.status(400).json({ error: '缺少授权码' });
}
try {
const result = await wechatService.getUserInfoByCode(code);
// 这里通常重定向到前端页面,并携带unionId
// 为演示,直接返回JSON
res.json({
success: true,
message: '微信用户信息获取成功',
data: result
});
} catch (error) {
console.error(error);
res.status(500).json({ error: '微信授权处理失败' });
}
});
// 2. 抖音授权回调接口
app.get('/api/auth/douyin/callback', async (req, res) => {
const { code } = req.query;
if (!code) {
return res.status(400).json({ error: '缺少授权码' });
}
try {
const result = await douyinService.getUserInfoByCode(code);
res.json({
success: true,
message: '抖音用户信息获取成功',
data: result
});
} catch (error) {
console.error(error);
res.status(500).json({ error: '抖音授权处理失败' });
}
});
// 3. 核心关联查询接口:通过Union ID查询用户全平台信息
app.get('/api/user/profile', async (req, res) => {
const { union_id } = req.query;
if (!union_id) {
return res.status(400).json({ error: '缺少Union ID' });
}
const mysql = require('mysql2/promise');
const dbConfig = { host: 'localhost', user: 'root', password: 'your_mysql_password', database: 'user_cross_platform' };
const pool = mysql.createPool(dbConfig);
const connection = await pool.getConnection();
try {
const [rows] = await connection.execute(
'SELECT FROM unified_users WHERE union_id = ?',
[union_id]
);
if (rows.length === 0) {
return res.status(404).json({ error: '用户未找到' });
}
res.json({ success: true, data: rows[0] });
} catch (error) {
res.status(500).json({ error: '查询失败' });
} finally {
connection.release();
}
});
// 4. 手动关联接口(例如,在客户端让用户确认关联)
app.post('/api/user/link', async (req, res) => {
const { union_id, platform, openid } = req.body; // platform: 'wechat' or 'douyin'
if (!union_id || !platform || !openid) {
return res.status(400).json({ error: '参数不全' });
}
const mysql = require('mysql2/promise');
const dbConfig = { host: 'localhost', user: 'root', password: 'your_mysql_password', database: 'user_cross_platform' };
const pool = mysql.createPool(dbConfig);
const connection = await pool.getConnection();
try {
let updateField = '';
let timeField = '';
if (platform === 'wechat') {
updateField = 'wechat_openid';
timeField = 'last_wechat_active';
} else if (platform === 'douyin') {
updateField = 'douyin_openid';
timeField = 'last_douyin_active';
} else {
return res.status(400).json({ error: '平台参数错误' });
}
const sql = `UPDATE unified_users SET ${updateField} = ?, ${timeField} = NOW() WHERE union_id = ?`;
await connection.execute(sql, [openid, union_id]);
res.json({ success: true, message: '关联成功' });
} catch (error) {
res.status(500).json({ error: '关联失败' });
} finally {
connection.release();
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`数据联动服务运行在 http://localhost:${PORT}`);
});
在终端运行:
node app.js
如果看到“数据联动服务运行在 http://localhost:3000”的提示,说明服务启动成功。
在你的网页前端(例如 `index.html`),需要放置两个平台的授权按钮,并处理回调。
// 替换 your_appid 和 your_redirect_uri
const wechatAuthUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=你的微信AppID&redirect_uri=${encodeURIComponent('你的授权回调域名/api/auth/wechat/callback')}&response_type=code&scope=snsapi_userinfo&state=STATEwechat_redirect`;
// 将上述URL设置为一个按钮的href
// 替换 your_client_key 和 your_redirect_uri
const douyinAuthUrl = `https://open.douyin.com/platform/oauth/connect/?client_key=你的抖音ClientKey&response_type=code&scope=user_info&redirect_uri=${encodeURIComponent('你的授权回调域名/api/auth/douyin/callback')}&state=STATE`;
// 将上述URL设置为另一个按钮的href
用户先后从两个平台授权后,系统会生成或获取到两个可能的 `unionId`。你需要在前端实现以下逻辑:
易频IT社区是综合性互联网IT技术门户网站,专注分享网络技术、服务器运维、网络安全、编程开发、系统架构、云计算、大数据等行业干货,实时更新IT行业资讯、零基础教程、实战案例,为IT从业者、技术爱好者提供专业的学习交流平台。
Copyright © 2021-2026 易频IT社区. All Rights Reserved. 备案号:闽ICP备2023013482号 网站地图