前言

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开发者账号

  1. 访问 TikTok Developer Portal

  2. 创建应用,获得 Client KeyClient Secret

  3. 配置小程序域名(需https)

  4. 申请小程序权限:user.info.basicvideo.list(按需)

2.2 开通海外支付

推荐 Stripe(接入简单,支持全球信用卡):

  • 注册Stripe账户

  • 获取 Publishable KeySecret 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小程序前端

  1. 将前端代码压缩为zip包

  2. 登录TikTok开发者后台,进入“小程序管理”

  3. 上传代码包,填写版本号

  4. 配置合法域名:https://api.yourdomain.com

  5. 提交审核

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐