从零部署海外TikTok短剧小程序:登录、支付、播放全流程(附商业源码)
前言
TikTok小程序正在成为短剧出海的重要入口。但很多开发者反馈:官方文档分散、支付接入复杂、播放器优化困难。
本文基于我们已商用的海外TikTok短剧小程序源码,整理出一份从0到1的部署指南。包含环境准备、核心代码实现、服务器配置及上线步骤。
说明:本文提供的代码片段来自商业源码(非开源),完整版可通过文末方式获取。
一、整体架构
用户端:TikTok App (小程序)
↓ HTTPS
后端服务:Node.js + Express (或 Java Spring Boot)
↓
数据库:MySQL 5.7+
↓
第三方:Stripe/PayPal (支付) + 云存储 (视频存放)
推荐环境
-
操作系统:Ubuntu 20.04 / 22.04
-
Node.js:16.x 或 18.x
-
MySQL:5.7+
-
Nginx:反向代理 + SSL
-
PM2:进程守护
二、准备工作
2.1 注册TikTok开发者账号
-
创建应用,获得 Client Key 和 Client Secret
-
配置小程序域名(需https)
-
申请小程序权限:
user.info.basic、video.list(按需)
2.2 开通海外支付
推荐 Stripe(接入简单,支持全球信用卡):
-
注册Stripe账户
-
获取 Publishable Key 和 Secret Key
-
设置Webhook端点(用于支付成功回调)
备用方案:PayPal REST API。
2.3 准备服务器
购买海外云服务器(DigitalOcean / Vultr / AWS),推荐配置2核4GB,系统Ubuntu 20.04。
三、后端代码实现(核心模块)
以下代码基于Node.js + Express,数据库使用MySQL。
3.1 项目初始化
mkdir tiktok-shortdrama-backend
cd tiktok-shortdrama-backend
npm init -y
npm install express mysql2 axios cors dotenv stripe @paypal/checkout-server-sdk
创建 .env 文件:
PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=yourpassword
DB_NAME=tiktok_drama
TIKTOK_CLIENT_KEY=your_client_key
TIKTOK_CLIENT_SECRET=your_client_secret
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
3.2 数据库表结构
CREATE DATABASE tiktok_drama;
USE tiktok_drama;
-- 用户表(关联TikTok open_id)
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`open_id` varchar(128) NOT NULL,
`nickname` varchar(255) DEFAULT NULL,
`avatar` varchar(512) DEFAULT NULL,
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `open_id` (`open_id`)
);
-- 短剧表
CREATE TABLE `dramas` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`cover` varchar(512) DEFAULT NULL,
`description` text,
`status` tinyint(1) DEFAULT '1',
PRIMARY KEY (`id`)
);
-- 剧集表
CREATE TABLE `episodes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`drama_id` int(11) NOT NULL,
`episode_num` int(11) NOT NULL,
`title` varchar(255) DEFAULT NULL,
`video_url` varchar(512) NOT NULL,
`price` decimal(10,2) DEFAULT '0.00',
`is_free` tinyint(1) DEFAULT '0',
`duration` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `drama_id` (`drama_id`)
);
-- 购买记录表
CREATE TABLE `purchases` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`episode_id` int(11) NOT NULL,
`order_id` varchar(128) NOT NULL,
`amount` decimal(10,2) NOT NULL,
`status` tinyint(1) DEFAULT '1',
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `order_id` (`order_id`)
);
3.3 TikTok OAuth登录
后端路由 /auth/tiktok
const axios = require('axios');
app.post('/auth/tiktok', async (req, res) => {
const { code } = req.body;
if (!code) return res.status(400).json({ error: '缺少code' });
try {
// 1. 用code换access_token
const tokenRes = await axios.post('https://open-api.tiktok.com/oauth/access_token/', {
client_key: process.env.TIKTOK_CLIENT_KEY,
client_secret: process.env.TIKTOK_CLIENT_SECRET,
code: code,
grant_type: 'authorization_code'
});
const { access_token, open_id } = tokenRes.data.data;
// 2. 获取用户信息
const userRes = await axios.get('https://open-api.tiktok.com/user/info/', {
params: { access_token, open_id }
});
const userData = userRes.data.data;
// 3. 存储或更新用户
const [user] = await db.query(
`INSERT INTO users (open_id, nickname, avatar)
VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE nickname=?, avatar=?`,
[open_id, userData.display_name, userData.avatar_url, userData.display_name, userData.avatar_url]
);
// 4. 生成你自己的session token (jwt)
const jwt = require('jsonwebtoken');
const token = jwt.sign({ userId: user.insertId }, 'your_jwt_secret', { expiresIn: '7d' });
res.json({ success: true, token, user: { open_id, nickname: userData.display_name } });
} catch (err) {
console.error(err.response?.data || err.message);
res.status(500).json({ error: '登录失败' });
}
});
3.4 播放权限校验
// 中间件:验证用户token
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: '未登录' });
try {
const decoded = jwt.verify(token, 'your_jwt_secret');
req.userId = decoded.userId;
next();
} catch (e) {
res.status(401).json({ error: 'token无效' });
}
};
// 获取剧集播放URL
app.get('/api/episode/play', authMiddleware, async (req, res) => {
const { episode_id } = req.query;
const userId = req.userId;
// 查询剧集信息
const [episodes] = await db.query('SELECT * FROM episodes WHERE id = ?', [episode_id]);
if (episodes.length === 0) return res.status(404).json({ error: '剧集不存在' });
const episode = episodes[0];
// 需要付费的,检查是否已购买
if (episode.price > 0) {
const [purchased] = await db.query(
'SELECT * FROM purchases WHERE user_id = ? AND episode_id = ? AND status = 1',
[userId, episode_id]
);
if (purchased.length === 0) {
return res.status(403).json({ need_pay: true, price: episode.price });
}
}
// 返回视频URL(可加签名防盗链,此处简化)
res.json({ video_url: episode.video_url });
});
3.5 Stripe支付集成
创建支付Session
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/api/create-payment', authMiddleware, async (req, res) => {
const { episode_id } = req.body;
const userId = req.userId;
const [episodes] = await db.query('SELECT * FROM episodes WHERE id = ?', [episode_id]);
if (episodes.length === 0) return res.status(404).json({ error: '剧集不存在' });
const episode = episodes[0];
// 创建Stripe Checkout Session
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
line_items: [{
price_data: {
currency: 'usd',
product_data: { name: `${episode.title || `第${episode.episode_num}集`}` },
unit_amount: Math.round(episode.price * 100), // 美元转美分
},
quantity: 1,
}],
mode: 'payment',
success_url: 'https://yourminiapp.com/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'https://yourminiapp.com/cancel',
metadata: { user_id: userId, episode_id }
});
res.json({ sessionId: session.id, url: session.url });
});
Webhook处理支付成功
app.post('/webhook/stripe', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
const { user_id, episode_id } = session.metadata;
const order_id = session.id;
// 记录购买
await db.query(
`INSERT INTO purchases (user_id, episode_id, order_id, amount, status)
VALUES (?, ?, ?, ?, 1)`,
[user_id, episode_id, order_id, session.amount_total / 100]
);
// 可在此触发用户解锁通知
}
res.json({ received: true });
});
四、前端小程序核心代码
TikTok小程序前端使用类似微信小程序的语法,文件扩展名为 .ttml、.ttss、.js。
4.1 登录页面
// pages/login/login.js
Page({
data: {},
onLoad() {},
handleLogin() {
tt.login({
success: (res) => {
const code = res.code;
tt.request({
url: 'https://api.yourdomain.com/auth/tiktok',
method: 'POST',
data: { code },
success: (res) => {
const { token, user } = res.data;
tt.setStorageSync('token', token);
tt.setStorageSync('userInfo', user);
tt.showToast({ title: '登录成功' });
setTimeout(() => {
tt.navigateTo({ url: '/pages/index/index' });
}, 1000);
},
fail: (err) => {
console.error(err);
tt.showToast({ title: '登录失败', icon: 'none' });
}
});
},
fail: (err) => {
console.error('tt.login fail', err);
}
});
}
});
4.2 剧集播放与解锁
// pages/play/play.js
Page({
data: {
episodeId: null,
videoUrl: '',
needPay: false,
price: 0
},
onLoad(options) {
this.setData({ episodeId: options.episode_id });
this.checkAccess();
},
checkAccess() {
const token = tt.getStorageSync('token');
tt.request({
url: `https://api.yourdomain.com/api/episode/play?episode_id=${this.data.episodeId}`,
method: 'GET',
header: { Authorization: `Bearer ${token}` },
success: (res) => {
if (res.data.need_pay) {
this.setData({ needPay: true, price: res.data.price });
} else if (res.data.video_url) {
this.setData({ videoUrl: res.data.video_url });
}
}
});
},
handlePay() {
const token = tt.getStorageSync('token');
tt.request({
url: 'https://api.yourdomain.com/api/create-payment',
method: 'POST',
header: { Authorization: `Bearer ${token}` },
data: { episode_id: this.data.episodeId },
success: (res) => {
// 打开Stripe支付页面(需用webview或跳转浏览器)
tt.navigateTo({
url: `/pages/webview/webview?url=${encodeURIComponent(res.data.url)}`
});
}
});
}
});
五、部署上线
5.1 服务器环境配置
# 更新系统
sudo apt update && sudo apt upgrade -y
# 安装Node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt install -y nodejs
# 安装MySQL
sudo apt install mysql-server -y
sudo mysql_secure_installation
# 安装PM2
sudo npm install -g pm2
# 安装Nginx
sudo apt install nginx -y
5.2 上传代码并运行
# 将后端代码上传到 /var/www/tiktok-backend
cd /var/www/tiktok-backend
npm install
# 配置.env文件
pm2 start app.js --name tiktok-api
pm2 save
pm2 startup
5.3 配置Nginx反向代理
server {
listen 80;
server_name api.yourdomain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name api.yourdomain.com;
ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
使用Certbot获取SSL证书:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d api.yourdomain.com
5.4 上传TikTok小程序前端
-
将前端代码压缩为zip包
-
登录TikTok开发者后台,进入“小程序管理”
-
上传代码包,填写版本号
-
配置合法域名:
https://api.yourdomain.com -
提交审核
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)