仙吟
仙吟
发布于 2026-08-12 / 3 阅读
0
0

如何搭建短视频平台

前言

本文内的所有内容仅供学习。

本文先讲述后端和前端镜像的构建过程,然后以构建好的镜像进行搭建。

后端

项目地址

X-Tok-backend(后端):https://github.com/GIS142857/X-Tok-backend

Docker

X-Tok-backend未提供docker搭建方式,这里我们手动做一个Dockerfile。

检出仓库

使用 git clone 命令检出仓库。

添加install.sh

install.sh内容如下所示:

#!/bin/bash

mkdir -p /app
cd /app

mkdir -p storage storage/app storage/logs
rm public/storage && ln -s /app/douyin-backend/storage public/storage

go mod tidy && go mod vendor && CGO_ENABLED=0 go build cmd/web/main.go

db_douyin.sql

仓库自带的数据库文件比较旧,可以通过向作者求取新的数据库文件。

添加entry.sh

entry.sh内容如下所示:

#!/bin/sh

# gorm_v2.yml
# MySQL
# - MYSQL_HOST
# - MYSQL_PORT
# - MYSQL_DATABSE
# - MYSQL_ROOT_PASSWORD

GORM_MYSQL_HOST=$(yq '.Gormv2.Mysql.Write.Host' /app/config/gorm_v2.yml)
GORM_MYSQL_PORT=$(yq '.Gormv2.Mysql.Write.Port' /app/config/gorm_v2.yml)
GORM_MYSQL_USER=$(yq '.Gormv2.Mysql.Write.User' /app/config/gorm_v2.yml)
GORM_MYSQL_DATABASE=$(yq '.Gormv2.Mysql.Write.DataBase' /app/config/gorm_v2.yml)
GORM_MYSQL_PASSWORD=$(yq '.Gormv2.Mysql.Write.Pass' /app/config/gorm_v2.yml)

if [ -n "${MYSQL_HOST}" ] && [ "${MYSQL_HOST}" != "${GORM_MYSQL_HOST}" ]; then
    yq -i '.Gormv2.Mysql.Write.Host="'${MYSQL_HOST}'"' /app/config/gorm_v2.yml
    yq -i '.Gormv2.Mysql.Read.Host="'${MYSQL_HOST}'"' /app/config/gorm_v2.yml
    GORM_MYSQL_HOST=${MYSQL_HOST}
fi

if [ -n "${MYSQL_PORT}" ] && [ "${MYSQL_PORT}" != "${GORM_MYSQL_PORT}" ]; then
    yq -i '.Gormv2.Mysql.Write.Port='${MYSQL_PORT}'' /app/config/gorm_v2.yml
    yq -i '.Gormv2.Mysql.Read.Port='${MYSQL_PORT}'' /app/config/gorm_v2.yml
    GORM_MYSQL_PORT=${MYSQL_PORT}
fi

if [ -n "${MYSQL_USER}" ] && [ "${MYSQL_USER}" != "${GORM_MYSQL_USER}" ]; then
    yq -i '.Gormv2.Mysql.Write.User="'${MYSQL_USER}'"' /app/config/gorm_v2.yml
    yq -i '.Gormv2.Mysql.Read.User="'${MYSQL_USER}'"' /app/config/gorm_v2.yml
    GORM_MYSQL_USER=${MYSQL_USER}
fi

if [ -n "${MYSQL_DATABASE}" ] && [ "${MYSQL_DATABASE}" != "${GORM_MYSQL_DATABASE}" ]; then
    yq -i '.Gormv2.Mysql.Write.DataBase="'${MYSQL_DATABASE}'"' /app/config/gorm_v2.yml
    yq -i '.Gormv2.Mysql.Read.DataBase="'${MYSQL_DATABASE}'"' /app/config/gorm_v2.yml
    GORM_MYSQL_DATABASE=${MYSQL_PASSWORD}
fi

if [ -n "${MYSQL_PASSWORD}" ] && [ "${MYSQL_PASSWORD}" != "${GORM_MYSQL_PASSWORD}" ]; then
    yq -i '.Gormv2.Mysql.Write.Pass="'${MYSQL_PASSWORD}'"' /app/config/gorm_v2.yml
    yq -i '.Gormv2.Mysql.Read.Pass="'${MYSQL_PASSWORD}'"' /app/config/gorm_v2.yml
    GORM_MYSQL_PASSWORD=${MYSQL_PASSWORD}
fi

# config.yml
# HttpServer
# - WEB_PORT
# Redis
# - REDIS_HOST
# - REDIS_PORT
# - REDIS_AUTH
# FileUploadSetting
# - FILE_UPLOAD_SIZE
# - SOURCE_URL_PREFIX
# - UPLOAD_ROOT_PATH

CONFIG_WEB_PORT=$(yq '.HttpServer.Web.Port' /app/config/config.yml |awk -F ':' '{print $2}')

CONFIG_REDIS_HOST=$(yq '.Redis.Host' /app/config/config.yml)
CONFIG_REDIS_PORT=$(yq '.Redis.Port' /app/config/config.yml)
CONFIG_REDIS_AUTH=$(yq '.Redis.Auth' /app/config/config.yml)

CONFIG_FILE_UPLOAD_SIZE=$(yq '.FileUploadSetting.Size' /app/config/config.yml)
CONFIG_SOURCE_URL_PREFIX=$(yq '.FileUploadSetting.SourceUrlPrefix' /app/config/config.yml)
CONFIG_UPLOAD_ROOT_PATH=$(yq '.FileUploadSetting.UploadRootPath' /app/config/config.yml)

if [ -n "${WEB_PORT}" ] && [ "${WEB_PORT}" != "${CONFIG_WEB_PORT}" ]; then
    yq -i '.HttpServer.Web.Port=":'${WEB_PORT}'"' /app/config/config.yml
fi

if [ -n "${REDIS_HOST}" ] && [ "${REDIS_HOST}" != "${CONFIG_REDIS_HOST}" ]; then
    yq -i '.Redis.Host="'${REDIS_HOST}'"' /app/config/config.yml
fi

if [ -n "${REDIS_PORT}" ] && [ "${REDIS_PORT}" != "${CONFIG_REDIS_PORT}" ]; then
    yq -i '.Redis.Port='${REDIS_PORT}'' /app/config/config.yml
fi

if [ -n "${REDIS_AUTH}" ] && [ "${REDIS_AUTH}" != "${CONFIG_REDIS_AUTH}" ]; then
    yq -i '.Redis.Auth="'${REDIS_AUTH}'"' /app/config/config.yml
fi

if [ -n "${FILE_UPLOAD_SIZE}" ] && [ "${FILE_UPLOAD_SIZE}" != "${CONFIG_FILE_UPLOAD_SIZE}" ]; then
    yq -i '.FileUploadSetting.Size='${FILE_UPLOAD_SIZE}'' /app/config/config.yml
fi

if [ -n "${SOURCE_URL_PREFIX}" ] && [ "${SOURCE_URL_PREFIX}" != "${CONFIG_SOURCE_URL_PREFIX}" ]; then
    yq -i '.FileUploadSetting.SourceUrlPrefix="'${SOURCE_URL_PREFIX}'"' /app/config/config.yml
fi

if [ -n "${UPLOAD_ROOT_PATH}" ] && [ "${UPLOAD_ROOT_PATH}" != "${CONFIG_UPLOAD_ROOT_PATH}" ]; then
    yq -i '.FileUploadSetting.UploadRootPath="'${UPLOAD_ROOT_PATH}'"' /app/config/config.yml
fi

sleep 5

is_mysql_running() {
        mysql -h $GORM_MYSQL_HOST -P $GORM_MYSQL_PORT -u $GORM_MYSQL_USER -p$GORM_MYSQL_PASSWORD -e "select version();"

    if [ $? -eq 0 ]; then
        return 0
    else
        return 1
    fi
}

is_mysql_running

while [ $? -ne 0 ]; do
    echo "Waiting for MySQL start ..."
    sleep 1

    is_mysql_running
done

result=$(mysql -h $GORM_MYSQL_HOST -P $GORM_MYSQL_PORT -u $GORM_MYSQL_USER -p$GORM_MYSQL_PASSWORD -D$GORM_MYSQL_DATABASE -e "show tables like 'tb_accounts'")
if [ -z "$result" ]; then
    mysql -h $GORM_MYSQL_HOST -P $GORM_MYSQL_PORT -u $GORM_MYSQL_USER -p$GORM_MYSQL_PASSWORD $GORM_MYSQL_DATABASE < /app/db_douyin.sql
fi

mysql -h $GORM_MYSQL_HOST -P $GORM_MYSQL_PORT -u $GORM_MYSQL_USER -p$GORM_MYSQL_PASSWORD -D$GORM_MYSQL_DATABASE -e "ALTER DATABASE ${GORM_MYSQL_DATABASE} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

#while true
#do
#    sleep 1
#done

./main

MySQL与Redis使用第三方镜像。

添加Dockerfile

Dockerfile内容如下所示:

FROM golang:latest AS builder

WORKDIR /app
COPY ./ ./

RUN mkdir -p storage storage/app storage/logs && rm public/storage && ln -s /app/storage public/storage
RUN go mod tidy && go mod vendor && CGO_ENABLED=0 go build cmd/web/main.go

FROM alpine:latest

WORKDIR /app
COPY --from=builder /app/main /app/entry.sh /app/db_douyin.sql /app
RUN mkdir -p /app/upload /app/config /app/storage /app/storage/app /app/storage/logs /app/public
COPY --from=builder /app/config /app/config
VOLUME ["/app/upload"]

RUN apk update && apk add mysql-client yq ffmpeg && chmod a+x /app/entry.sh
ENTRYPOINT ["/app/entry.sh"]

文件结构

如图所示:

构建镜像

使用如下命令镜像构建:

docker build -t 镜像名:标签 .

例如:

docker build -t x-tok-backend:1.0.0

前端

项目地址

X-Tok(前端):https://github.com/GIS142857/X-Tok

Docker

这里我们需要稍微修改一下Dockerfile。

检出仓库

使用 git clone 命令检出仓库。

添加entry.sh

entry.sh内容如下所示:

#!/bin/sh

cd /app

#cp nginx-x-tok.conf /etc/nginx/http.d

# nginx
# BACKEND_SERVER_HOST
# BACKEND_SERVER_PORT
# SERVER_NAME
# CLIENT_MAX_BODY_SIZE

NGINX_BACKEND_SERVER_HOST=$(cat /etc/nginx/http.d/nginx-x-tok.conf |grep '后端服务地址' |awk -F ' ' '{print $2}' |awk -F ':' '{print $1}')
NGINX_BACKEND_SERVER_PORT=$(cat /etc/nginx/http.d/nginx-x-tok.conf |grep '后端服务地址' |awk -F ' ' '{print $2}' |awk -F ':' '{print $2}' |awk -F ';' '{print $1}')
NGINX_SERVER_NAME=$(cat /etc/nginx/http.d/nginx-x-tok.conf |grep 'server_name' |awk -F' ' '{print $2}' |awk -F';' '{print $1}')
NGINX_LISTEN_PORT=$(cat /etc/nginx/http.d/nginx-x-tok.conf |grep 'listen' |awk -F ' ' '{print $2}' |awk -F ';' '{print $1}')
NGINX_CLIENT_MAX_BODY_SIZE=$(cat /etc/nginx/nginx.conf |grep 'client_max_body_size' |awk -F ' ' '{print $2}' |awk -F 'm' '{print $1}')

#echo "$NGINX_BACKEND_SERVER_HOST, $BACKEND_SERVER_HOST"
#echo "$NGINX_BACKEND_SERVER_PORT, $BACKEND_SERVER_PORT"
#echo "$NGINX_SERVER_NAME, $SERVER_NAME"
#echo "$NGINX_LISTEN_PORT,$LISTEN_PORT"
#echo "NGINX_CLIENT_MAX_BODY_SIZE, $CLIENT_MAX_BODY_SIZE"

if [ -n "$BACKEND_SERVER_HOST" ] || [-n "$BACKEND_SERVER_PORT" ]; then
    if [ "$BACKEND_SERVER_HOST" != "$NGINX_BACKEND_SERVER_HOST" ] || [ "$BACKEND_SERVER_PORT" != "$NGINX_BACKEND_SERVER_PORT" ]; then
        sed -i "s|server $NGINX_BACKEND_SERVER_HOST:$NGINX_BACKEND_SERVER_PORT|server $BACKEND_SERVER_HOST:$BACKEND_SERVER_PORT|g" /etc/nginx/http.d/nginx-x-tok.conf
    fi
fi

if [ -n "$SERVER_NAME" ] && [ "$SERVER_NAME" != "$NGINX_SERVER_NAME" ]; then
    sed -i "s/server_name $NGINX_SERVER_NAME/server_name $SERVER_NAME/g" /etc/nginx/http.d/nginx-x-tok.conf
fi

if [ -n "$LISTEN_PORT" ] && [ "$LISTEN_PORT" != "$NGINX_LISTEN_PORT" ]; then
    sed -i "s/listen $NGINX_LISTEN_PORT/listen $LISTEN_PORT/g" /etc/nginx/http.d/nginx-x-tok.conf
fi

if [ -n "$CLIENT_MAX_BODY_SIZE" ] && [ "$CLIENT_MAX_BODY_SIZE" != "$NGINX_CLIENT_MAX_BODY_SIZE" ]; then
    sed -i "s/client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}m/client_max_body_size ${CLIENT_MAX_BODY_SIZE}m/g" /etc/nginx/http.d/nginx-x-tok.conf
    sed -i "s/client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}m/client_max_body_size ${CLIENT_MAX_BODY_SIZE}m/g" /etc/nginx/nginx.conf
fi

rebuild=false

CONFIG_BASE_URL=$(cat /app/src/config/index.ts |grep 'baseUrl: ' |awk -F ": " '{print $2}' |awk -F "'" '{print $2}')
CONFIG_FILE_PREVIEW=$(cat /app/src/config/index.ts |grep 'filePreview: ' |awk -F ": " '{print $2}' |awk -F "'" '{print $2}' |awk -F "/s" '{print $1}')
CONFIG_UNI=$(cat /app/src/config/index.ts |grep 'UNI: ' |awk -F ": " '{print $2}' |awk -F "'" '{print $2}')

#echo "$CONFIG_BASE_URL, $BASE_URL"
#echo "$CONFIG_FILE_PREVIEW, $FILE_PREVIEW"
#echo "$CONFIG_UNI, $UNI"

if [ -n "$BASE_URL" ] && [ "$BASE_URL" != "$CONFIG_BASE_URL" ]; then
    sed -i "s|baseUrl: '$CONFIG_BASE_URL'|baseUrl: '$BASE_URL'|g" /app/src/config/index.ts
    rebuild=true
fi

if [ -n "$FILE_PREVIEW" ] && [ "$FILE_PREVIEW" != "$CONFIG_FILE_PREVIEW" ]; then
    sed -i "s|filePreview: '$CONFIG_FILE_PREVIEW/static/uploads/'|filePreview: '$FILE_PREVIEW/static/uploads/'|g" /app/src/config/index.ts
    rebuild=true
fi

if [ -n "$UNI" ] && [ "$UNI" != "$CONFIG_UNI" ]; then
    sed -i "s|UNI: '$CONFIG_UNI'|UNI: '$UNI'|g" /app/src/config/index.ts
    rebuild=true
fi

if $rebuild; then
    npm run build
fi

nginx -g "daemon off;"

添加nginx-x-tok.conf

nginx-x-tok.conf文件内容如下:

upstream backend_server_list {
    ip_hash;
    server localhost:22001;  # 后端服务地址(IP_Addr替换为自己的IP地址)
}

server {
    listen 3000; # managed by Certbot
    server_name localhost;  # 修改为自己的域名

    # Vue 项目根目录
    root /app/dist;
    index index.html;
    charset utf-8;

    # 静态资源缓存配置
    #location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
    #    expires 30d;
    #}  
    location /videos/source/ {
        root /app; # 根目录
        autoindex on;       # 开启目录索引(可选)
        autoindex_exact_size off; # 显示文件大小(可选)
        autoindex_localtime on;   # 显示本地时间(可选)
    }
    location /videos/cover/ {
        root /app; # 根目录
        autoindex on;       # 开启目录索引(可选)
        autoindex_exact_size off; # 显示文件大小(可选)
        autoindex_localtime on;   # 显示本地时间(可选)
    }
    location /images/avatar_small/ {
        root /app; # 根目录
        autoindex on;       # 开启目录索引(可选)
        autoindex_exact_size off; # 显示文件大小(可选)
        autoindex_localtime on;   # 显示本地时间(可选)
    }
    location /images/avatar_large/ {
        root /app; # 根目录
        autoindex on;       # 开启目录索引(可选)
        autoindex_exact_size off; # 显示文件大小(可选)
        autoindex_localtime on;   # 显示本地时间(可选)
    }
    location /images/cover/ {
        root /app; # 根目录
        autoindex on;       # 开启目录索引(可选)
        autoindex_exact_size off; # 显示文件大小(可选)
        autoindex_localtime on;   # 显示本地时间(可选)
    }
    location /images/white_cover/ {
        root /app; # 根目录
        autoindex on;       # 开启目录索引(可选)
        autoindex_exact_size off; # 显示文件大小(可选)
        autoindex_localtime on;   # 显示本地时间(可选)
    }

    location ~ .*\.(js|css)?$ {
        expires 12h;
    }

    location ~ /\. {
        deny all;
    }

    location / {
        #auth_basic "Login";
        #auth_basic_user_file /etc/nginx/conf.d/passwd;

        try_files $uri $uri/ /index.html;
        add_header Access-Control-Allow-Origin $http_origin;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
        add_header Access-Control-Allow-Headers "Content-Type, Authorization, Token";
        add_header Access-Control-Allow-Credentials true;
        if ($request_method = OPTIONS) {
            add_header Content-Length 0;
            add_header Content-Type text/plain;
            return 204;
        }
    }

    # API 代理配置 (合并 /message/, /base/, /user/)
    location ~ ^/(message|base|user|post|shop|video|jwt|upload)/ {
        client_max_body_size 1m;  # 允许最大请求体为 1MB
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 跨域支持
        if ($request_method = 'OPTIONS') {
            add_header 'Content-Type' 'text/plain, charset=utf-8';
            add_header 'Access-Control-Allow-Credentials' 'true';
            add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With';
            add_header 'Access-Control-Allow-Methods' 'GET,POST,DELETE,PUT,OPTIONS';
            add_header 'Access-Control-Allow-Origin' '*';
            return 204;
        }

        # 代理到后端服务
        proxy_pass http://backend_server_list;
    }
}

修改Dockerfile

修改后的Dockerfile,如下所示:

# syntax = docker/dockerfile:experimental
FROM node:alpine3.21

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN apk update && apk add nginx && npm i -g corepack@latest && corepack enable

WORKDIR /app
COPY ./ ./

# RUN两次方便观察install和build, 也可以用pnpm cache and locked
RUN pnpm install && npm run build && chmod +x /app/entry.sh && cp nginx-x-tok.conf /etc/nginx/http.d/
ENTRYPOINT ["/app/entry.sh"] 

#FROM --platform=${BUILDPLATFORM:-linux/amd64,linux/arm64} ghcr.io/rookie-luochao/nginx-runner:latest

#COPY --from=builder /x-tok/dist /app
#COPY --from=builder /x-tok/nginx-x-tok.conf /etc/nginx/conf.d/conf-base.template

文件结构

如图所示:

构建镜像

使用如下命令镜像构建:

docker build -t 镜像名:标签 .

例如:

docker build -t x-tok:1.0.0

Docker Compose

使用docker-compose.yml的方式启动镜像。

如下所示:

services:
  x-tok:
    image: x-tok:1.0.0
    restart: always
    container_name: x-tok
    networks:
      - ubuntu-network
    ports:
      - 3000:3000
    environment:
      - BACKEND_SERVER_HOST=x-tok-backend
      - BACKEND_SERVER_PORT=22001
      - SERVER_NAME=douyin.example.com
      - LISTEN_PORT=3000
      - CLIENT_MAX_BODY_SIZE=1024 #unit: MB
      - BASE_URL=https://douyin.example.com
      - FILE_PREVIEW=https://douyin.example.com
      - UNI=https://douyin.example.com

  x-tok-backend:
    image: x-tok-backend:1.0.0
    restart: always
    container_name: x-tok-backend 
    networks:
      - ubuntu-network
    ports:
      - 22001:22001
    environment:
      - MYSQL_HOST=x-tok-mysql
      - MYSQL_PORT=3306
      - MYSQL_USER=db_douyin
      - MYSQL_DATABASE=db_douyin
      - MYSQL_PASSWORD=db_douyin
      - REDIS_HOST=x-tok-redis
      - REDIS_PORT=6379
      - REDIS_AUTH=db_douyin
      - FILE_UPLOAD_SIZE=1024 # unit: MB
      - SOURCE_URL_PREFIX=https://douyin.example.com
      - UPLOAD_ROOT_PATH=/app/upload

  x-tok-mysql:
    image: mariadb:latest
    restart: always
    container_name: x-tok-mysql
    environment:
      - MYSQL_ROOT_PASSWORD=db_douyin
      - MYSQL_PASSWORD=db_douyin
      - MYSQL_DATABASE=db_douyin
      - MYSQL_USER=db_douyin
    networks:
      - ubuntu-network
    volumes:
      - ./mysql/data:/var/lib/mysql
    expose:
      - 3306

  x-tok-redis:
    image: redis:latest
    restart: always
    container_name: x-tok-redis
    command: redis-server --requirepass 'db_douyin' 
    volumes:
      - ./redis/data:/data
    networks:
      - ubuntu-network
    expose:
      - 6379

networks:
  ubuntu-network:
    name: ubuntu-network
    external: true


评论