mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-11 18:10:15 +08:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db0ea7d6c4 | ||
|
|
1eb85003de | ||
|
|
cca170f84a | ||
|
|
c8c016caa8 | ||
|
|
45d5874026 | ||
|
|
69b1ce60ff | ||
|
|
3ff3e4b106 | ||
|
|
dc50a68b01 | ||
|
|
968cfd8654 | ||
|
|
cf28d93be6 | ||
|
|
be08d6ebb5 | ||
|
|
4bc24f3b00 | ||
|
|
15833f94cf | ||
|
|
aeb297efcf | ||
|
|
d48c6b98e8 | ||
|
|
b79ccfafed | ||
|
|
c87ba59552 | ||
|
|
91fd71c858 | ||
|
|
6f64e67538 | ||
|
|
bd7a0b072f | ||
|
|
01ca001c97 | ||
|
|
324ad2a87c | ||
|
|
d9ad2630f0 | ||
|
|
83958a4a48 | ||
|
|
f6a6efdc42 | ||
|
|
1bbe7657b9 | ||
|
|
38189753b5 | ||
|
|
5b0e658617 | ||
|
|
b6cf54d57f | ||
|
|
e8058c8813 | ||
|
|
784868048d |
59
.github/workflows/beta.yml
vendored
Normal file
59
.github/workflows/beta.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
name: MoviePilot Builder Beta
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Docker-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Build Docker Image
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Release version
|
||||||
|
id: release_version
|
||||||
|
run: |
|
||||||
|
app_version=$(cat version.py |sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp")
|
||||||
|
echo "app_version=$app_version" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Docker Meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ secrets.DOCKER_USERNAME }}/moviepilot-v2
|
||||||
|
ghcr.io/${{ github.repository }}
|
||||||
|
tags: |
|
||||||
|
type=raw,value=beta
|
||||||
|
|
||||||
|
- name: Set Up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set Up Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login DockerHub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Login GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build Image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
platforms: |
|
||||||
|
linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha, scope=${{ github.workflow }}-docker
|
||||||
|
cache-to: type=gha, scope=${{ github.workflow }}-docker
|
||||||
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -27,6 +27,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
${{ secrets.DOCKER_USERNAME }}/moviepilot-v2
|
${{ secrets.DOCKER_USERNAME }}/moviepilot-v2
|
||||||
|
${{ secrets.DOCKER_USERNAME }}/moviepilot
|
||||||
ghcr.io/${{ github.repository }}
|
ghcr.io/${{ github.repository }}
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=${{ env.app_version }}
|
type=raw,value=${{ env.app_version }}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ class SiteChain(ChainBase):
|
|||||||
indexer = siteshelper.get_indexer(domain)
|
indexer = siteshelper.get_indexer(domain)
|
||||||
# 数据库的站点信息
|
# 数据库的站点信息
|
||||||
site_info = siteoper.get_by_domain(domain)
|
site_info = siteoper.get_by_domain(domain)
|
||||||
if site_info and site_info.is_active == 1:
|
if site_info and site_info.is_active:
|
||||||
# 站点已存在,检查站点连通性
|
# 站点已存在,检查站点连通性
|
||||||
status, msg = self.test(domain)
|
status, msg = self.test(domain)
|
||||||
# 更新站点Cookie
|
# 更新站点Cookie
|
||||||
|
|||||||
@@ -42,10 +42,6 @@ class SystemConfModel(BaseModel):
|
|||||||
scheduler: int = 0
|
scheduler: int = 0
|
||||||
# 线程池大小
|
# 线程池大小
|
||||||
threadpool: int = 0
|
threadpool: int = 0
|
||||||
# 数据库连接池大小
|
|
||||||
dbpool: int = 0
|
|
||||||
# 数据库连接池溢出数量
|
|
||||||
dbpooloverflow: int = 0
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigModel(BaseModel):
|
class ConfigModel(BaseModel):
|
||||||
@@ -56,6 +52,7 @@ class ConfigModel(BaseModel):
|
|||||||
class Config:
|
class Config:
|
||||||
extra = "ignore" # 忽略未定义的配置项
|
extra = "ignore" # 忽略未定义的配置项
|
||||||
|
|
||||||
|
# ==================== 基础应用配置 ====================
|
||||||
# 项目名称
|
# 项目名称
|
||||||
PROJECT_NAME: str = "MoviePilot"
|
PROJECT_NAME: str = "MoviePilot"
|
||||||
# 域名 格式;https://movie-pilot.org
|
# 域名 格式;https://movie-pilot.org
|
||||||
@@ -64,6 +61,22 @@ class ConfigModel(BaseModel):
|
|||||||
API_V1_STR: str = "/api/v1"
|
API_V1_STR: str = "/api/v1"
|
||||||
# 前端资源路径
|
# 前端资源路径
|
||||||
FRONTEND_PATH: str = "/public"
|
FRONTEND_PATH: str = "/public"
|
||||||
|
# 时区
|
||||||
|
TZ: str = "Asia/Shanghai"
|
||||||
|
# API监听地址
|
||||||
|
HOST: str = "0.0.0.0"
|
||||||
|
# API监听端口
|
||||||
|
PORT: int = 3001
|
||||||
|
# 前端监听端口
|
||||||
|
NGINX_PORT: int = 3000
|
||||||
|
# 配置文件目录
|
||||||
|
CONFIG_DIR: Optional[str] = None
|
||||||
|
# 是否调试模式
|
||||||
|
DEBUG: bool = False
|
||||||
|
# 是否开发模式
|
||||||
|
DEV: bool = False
|
||||||
|
|
||||||
|
# ==================== 安全认证配置 ====================
|
||||||
# 密钥
|
# 密钥
|
||||||
SECRET_KEY: str = secrets.token_urlsafe(32)
|
SECRET_KEY: str = secrets.token_urlsafe(32)
|
||||||
# RESOURCE密钥
|
# RESOURCE密钥
|
||||||
@@ -74,20 +87,24 @@ class ConfigModel(BaseModel):
|
|||||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
|
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
|
||||||
# RESOURCE_TOKEN过期时间
|
# RESOURCE_TOKEN过期时间
|
||||||
RESOURCE_ACCESS_TOKEN_EXPIRE_SECONDS: int = 60 * 30
|
RESOURCE_ACCESS_TOKEN_EXPIRE_SECONDS: int = 60 * 30
|
||||||
# 时区
|
# 超级管理员
|
||||||
TZ: str = "Asia/Shanghai"
|
SUPERUSER: str = "admin"
|
||||||
# API监听地址
|
# 辅助认证,允许通过外部服务进行认证、单点登录以及自动创建用户
|
||||||
HOST: str = "0.0.0.0"
|
AUXILIARY_AUTH_ENABLE: bool = False
|
||||||
# API监听端口
|
# API密钥,需要更换
|
||||||
PORT: int = 3001
|
API_TOKEN: Optional[str] = None
|
||||||
# 前端监听端口
|
# 用户认证站点
|
||||||
NGINX_PORT: int = 3000
|
AUTH_SITE: str = ""
|
||||||
# 是否调试模式
|
|
||||||
DEBUG: bool = False
|
# ==================== 数据库配置 ====================
|
||||||
# 是否开发模式
|
# 数据库类型,支持 sqlite 和 postgresql,默认使用 sqlite
|
||||||
DEV: bool = False
|
DB_TYPE: str = "sqlite"
|
||||||
# 是否在控制台输出 SQL 语句,默认关闭
|
# 是否在控制台输出 SQL 语句,默认关闭
|
||||||
DB_ECHO: bool = False
|
DB_ECHO: bool = False
|
||||||
|
# 数据库连接超时时间(秒),默认为 60 秒
|
||||||
|
DB_TIMEOUT: int = 60
|
||||||
|
# 是否启用 WAL 模式,仅适用于SQLite,默认开启
|
||||||
|
DB_WAL_ENABLE: bool = True
|
||||||
# 数据库连接池类型,QueuePool, NullPool
|
# 数据库连接池类型,QueuePool, NullPool
|
||||||
DB_POOL_TYPE: str = "QueuePool"
|
DB_POOL_TYPE: str = "QueuePool"
|
||||||
# 是否在获取连接时进行预先 ping 操作
|
# 是否在获取连接时进行预先 ping 操作
|
||||||
@@ -96,71 +113,36 @@ class ConfigModel(BaseModel):
|
|||||||
DB_POOL_RECYCLE: int = 300
|
DB_POOL_RECYCLE: int = 300
|
||||||
# 数据库连接池获取连接的超时时间(秒)
|
# 数据库连接池获取连接的超时时间(秒)
|
||||||
DB_POOL_TIMEOUT: int = 30
|
DB_POOL_TIMEOUT: int = 30
|
||||||
# SQLite 的 busy_timeout 参数,默认为 60 秒
|
# SQLite 连接池大小
|
||||||
DB_TIMEOUT: int = 60
|
DB_SQLITE_POOL_SIZE: int = 30
|
||||||
# SQLite 是否启用 WAL 模式,默认开启
|
# SQLite 连接池溢出数量
|
||||||
DB_WAL_ENABLE: bool = True
|
DB_SQLITE_MAX_OVERFLOW: int = 50
|
||||||
|
# PostgreSQL 主机地址
|
||||||
|
DB_POSTGRESQL_HOST: str = "localhost"
|
||||||
|
# PostgreSQL 端口
|
||||||
|
DB_POSTGRESQL_PORT: int = 5432
|
||||||
|
# PostgreSQL 数据库名
|
||||||
|
DB_POSTGRESQL_DATABASE: str = "moviepilot"
|
||||||
|
# PostgreSQL 用户名
|
||||||
|
DB_POSTGRESQL_USERNAME: str = "moviepilot"
|
||||||
|
# PostgreSQL 密码
|
||||||
|
DB_POSTGRESQL_PASSWORD: str = "moviepilot"
|
||||||
|
# PostgreSQL 连接池大小
|
||||||
|
DB_POSTGRESQL_POOL_SIZE: int = 30
|
||||||
|
# PostgreSQL 连接池溢出数量
|
||||||
|
DB_POSTGRESQL_MAX_OVERFLOW: int = 50
|
||||||
|
|
||||||
|
# ==================== 缓存配置 ====================
|
||||||
# 缓存类型,支持 cachetools 和 redis,默认使用 cachetools
|
# 缓存类型,支持 cachetools 和 redis,默认使用 cachetools
|
||||||
CACHE_BACKEND_TYPE: str = "cachetools"
|
CACHE_BACKEND_TYPE: str = "cachetools"
|
||||||
# 缓存连接字符串,仅外部缓存(如 Redis、Memcached)需要
|
# 缓存连接字符串,仅外部缓存(如 Redis、Memcached)需要
|
||||||
CACHE_BACKEND_URL: Optional[str] = None
|
CACHE_BACKEND_URL: Optional[str] = None
|
||||||
# Redis 缓存最大内存限制,未配置时,如开启大内存模式时为 "1024mb",未开启时为 "256mb"
|
# Redis 缓存最大内存限制,未配置时,如开启大内存模式时为 "1024mb",未开启时为 "256mb"
|
||||||
CACHE_REDIS_MAXMEMORY: Optional[str] = None
|
CACHE_REDIS_MAXMEMORY: Optional[str] = None
|
||||||
# 配置文件目录
|
|
||||||
CONFIG_DIR: Optional[str] = None
|
# ==================== 网络代理配置 ====================
|
||||||
# 超级管理员
|
|
||||||
SUPERUSER: str = "admin"
|
|
||||||
# 辅助认证,允许通过外部服务进行认证、单点登录以及自动创建用户
|
|
||||||
AUXILIARY_AUTH_ENABLE: bool = False
|
|
||||||
# API密钥,需要更换
|
|
||||||
API_TOKEN: Optional[str] = None
|
|
||||||
# 网络代理服务器地址
|
# 网络代理服务器地址
|
||||||
PROXY_HOST: Optional[str] = None
|
PROXY_HOST: Optional[str] = None
|
||||||
# 登录页面电影海报,tmdb/bing/mediaserver
|
|
||||||
WALLPAPER: str = "tmdb"
|
|
||||||
# 自定义壁纸api地址
|
|
||||||
CUSTOMIZE_WALLPAPER_API_URL: Optional[str] = None
|
|
||||||
# 媒体搜索来源 themoviedb/douban/bangumi,多个用,分隔
|
|
||||||
SEARCH_SOURCE: str = "themoviedb,douban,bangumi"
|
|
||||||
# 媒体识别来源 themoviedb/douban
|
|
||||||
RECOGNIZE_SOURCE: str = "themoviedb"
|
|
||||||
# 刮削来源 themoviedb/douban
|
|
||||||
SCRAP_SOURCE: str = "themoviedb"
|
|
||||||
# 新增已入库媒体是否跟随TMDB信息变化
|
|
||||||
SCRAP_FOLLOW_TMDB: bool = True
|
|
||||||
# TMDB图片地址
|
|
||||||
TMDB_IMAGE_DOMAIN: str = "image.tmdb.org"
|
|
||||||
# TMDB API地址
|
|
||||||
TMDB_API_DOMAIN: str = "api.themoviedb.org"
|
|
||||||
# TMDB元数据语言
|
|
||||||
TMDB_LOCALE: str = "zh"
|
|
||||||
# 刮削使用TMDB原始语种图片
|
|
||||||
TMDB_SCRAP_ORIGINAL_IMAGE: bool = False
|
|
||||||
# TMDB API Key
|
|
||||||
TMDB_API_KEY: str = "db55323b8d3e4154498498a75642b381"
|
|
||||||
# TVDB API Key
|
|
||||||
TVDB_V4_API_KEY: str = "ed2aa66b-7899-4677-92a7-67bc9ce3d93a"
|
|
||||||
TVDB_V4_API_PIN: str = ""
|
|
||||||
# Fanart开关
|
|
||||||
FANART_ENABLE: bool = True
|
|
||||||
# Fanart语言
|
|
||||||
FANART_LANG: str = "zh,en"
|
|
||||||
# Fanart API Key
|
|
||||||
FANART_API_KEY: str = "d2d31f9ecabea050fc7d68aa3146015f"
|
|
||||||
# 115 AppId
|
|
||||||
U115_APP_ID: str = "100196807"
|
|
||||||
# Alipan AppId
|
|
||||||
ALIPAN_APP_ID: str = "ac1bf04dc9fd4d9aaabb65b4a668d403"
|
|
||||||
# 元数据识别缓存过期时间(小时)
|
|
||||||
META_CACHE_EXPIRE: int = 0
|
|
||||||
# 电视剧动漫的分类genre_ids
|
|
||||||
ANIME_GENREIDS: List[int] = Field(default=[16])
|
|
||||||
# 用户认证站点
|
|
||||||
AUTH_SITE: str = ""
|
|
||||||
# 重启自动升级
|
|
||||||
MOVIEPILOT_AUTO_UPDATE: str = 'release'
|
|
||||||
# 自动检查和更新站点资源包(站点索引、认证等)
|
|
||||||
AUTO_UPDATE_RESOURCE: bool = True
|
|
||||||
# 是否启用DOH解析域名
|
# 是否启用DOH解析域名
|
||||||
DOH_ENABLE: bool = False
|
DOH_ENABLE: bool = False
|
||||||
# 使用 DOH 解析的域名列表
|
# 使用 DOH 解析的域名列表
|
||||||
@@ -174,6 +156,65 @@ class ConfigModel(BaseModel):
|
|||||||
"api.telegram.org")
|
"api.telegram.org")
|
||||||
# DOH 解析服务器列表
|
# DOH 解析服务器列表
|
||||||
DOH_RESOLVERS: str = "1.0.0.1,1.1.1.1,9.9.9.9,149.112.112.112"
|
DOH_RESOLVERS: str = "1.0.0.1,1.1.1.1,9.9.9.9,149.112.112.112"
|
||||||
|
|
||||||
|
# ==================== 媒体元数据配置 ====================
|
||||||
|
# 媒体搜索来源 themoviedb/douban/bangumi,多个用,分隔
|
||||||
|
SEARCH_SOURCE: str = "themoviedb,douban,bangumi"
|
||||||
|
# 媒体识别来源 themoviedb/douban
|
||||||
|
RECOGNIZE_SOURCE: str = "themoviedb"
|
||||||
|
# 元数据识别缓存过期时间(小时)
|
||||||
|
META_CACHE_EXPIRE: int = 0
|
||||||
|
# 电视剧动漫的分类genre_ids
|
||||||
|
ANIME_GENREIDS: List[int] = Field(default=[16])
|
||||||
|
# 刮削来源 themoviedb/douban
|
||||||
|
SCRAP_SOURCE: str = "themoviedb"
|
||||||
|
# 新增已入库媒体是否跟随TMDB信息变化
|
||||||
|
SCRAP_FOLLOW_TMDB: bool = True
|
||||||
|
# 检查本地媒体库是否存在资源开关
|
||||||
|
LOCAL_EXISTS_SEARCH: bool = False
|
||||||
|
# 搜索多个名称
|
||||||
|
SEARCH_MULTIPLE_NAME: bool = False
|
||||||
|
# 最大搜索名称数量
|
||||||
|
MAX_SEARCH_NAME_LIMIT: int = 2
|
||||||
|
|
||||||
|
# ==================== TMDB配置 ====================
|
||||||
|
# TMDB图片地址
|
||||||
|
TMDB_IMAGE_DOMAIN: str = "image.tmdb.org"
|
||||||
|
# TMDB API地址
|
||||||
|
TMDB_API_DOMAIN: str = "api.themoviedb.org"
|
||||||
|
# TMDB元数据语言
|
||||||
|
TMDB_LOCALE: str = "zh"
|
||||||
|
# 刮削使用TMDB原始语种图片
|
||||||
|
TMDB_SCRAP_ORIGINAL_IMAGE: bool = False
|
||||||
|
# TMDB API Key
|
||||||
|
TMDB_API_KEY: str = "db55323b8d3e4154498498a75642b381"
|
||||||
|
|
||||||
|
# ==================== TVDB配置 ====================
|
||||||
|
# TVDB API Key
|
||||||
|
TVDB_V4_API_KEY: str = "ed2aa66b-7899-4677-92a7-67bc9ce3d93a"
|
||||||
|
TVDB_V4_API_PIN: str = ""
|
||||||
|
|
||||||
|
# ==================== Fanart配置 ====================
|
||||||
|
# Fanart开关
|
||||||
|
FANART_ENABLE: bool = True
|
||||||
|
# Fanart语言
|
||||||
|
FANART_LANG: str = "zh,en"
|
||||||
|
# Fanart API Key
|
||||||
|
FANART_API_KEY: str = "d2d31f9ecabea050fc7d68aa3146015f"
|
||||||
|
|
||||||
|
# ==================== 云盘配置 ====================
|
||||||
|
# 115 AppId
|
||||||
|
U115_APP_ID: str = "100196807"
|
||||||
|
# Alipan AppId
|
||||||
|
ALIPAN_APP_ID: str = "ac1bf04dc9fd4d9aaabb65b4a668d403"
|
||||||
|
|
||||||
|
# ==================== 系统升级配置 ====================
|
||||||
|
# 重启自动升级
|
||||||
|
MOVIEPILOT_AUTO_UPDATE: str = 'release'
|
||||||
|
# 自动检查和更新站点资源包(站点索引、认证等)
|
||||||
|
AUTO_UPDATE_RESOURCE: bool = True
|
||||||
|
|
||||||
|
# ==================== 媒体文件格式配置 ====================
|
||||||
# 支持的后缀格式
|
# 支持的后缀格式
|
||||||
RMT_MEDIAEXT: list = Field(
|
RMT_MEDIAEXT: list = Field(
|
||||||
default_factory=lambda: ['.mp4', '.mkv', '.ts', '.iso',
|
default_factory=lambda: ['.mp4', '.mkv', '.ts', '.iso',
|
||||||
@@ -198,8 +239,12 @@ class ConfigModel(BaseModel):
|
|||||||
)
|
)
|
||||||
# 下载器临时文件后缀
|
# 下载器临时文件后缀
|
||||||
DOWNLOAD_TMPEXT: list = Field(default_factory=lambda: ['.!qb', '.part'])
|
DOWNLOAD_TMPEXT: list = Field(default_factory=lambda: ['.!qb', '.part'])
|
||||||
|
|
||||||
|
# ==================== 媒体服务器配置 ====================
|
||||||
# 媒体服务器同步间隔(小时)
|
# 媒体服务器同步间隔(小时)
|
||||||
MEDIASERVER_SYNC_INTERVAL: int = 6
|
MEDIASERVER_SYNC_INTERVAL: int = 6
|
||||||
|
|
||||||
|
# ==================== 订阅配置 ====================
|
||||||
# 订阅模式
|
# 订阅模式
|
||||||
SUBSCRIBE_MODE: str = "spider"
|
SUBSCRIBE_MODE: str = "spider"
|
||||||
# RSS订阅模式刷新时间间隔(分钟)
|
# RSS订阅模式刷新时间间隔(分钟)
|
||||||
@@ -208,24 +253,24 @@ class ConfigModel(BaseModel):
|
|||||||
SUBSCRIBE_STATISTIC_SHARE: bool = True
|
SUBSCRIBE_STATISTIC_SHARE: bool = True
|
||||||
# 订阅搜索开关
|
# 订阅搜索开关
|
||||||
SUBSCRIBE_SEARCH: bool = False
|
SUBSCRIBE_SEARCH: bool = False
|
||||||
# 检查本地媒体库是否存在资源开关
|
|
||||||
LOCAL_EXISTS_SEARCH: bool = False
|
# ==================== 站点配置 ====================
|
||||||
# 搜索多个名称
|
|
||||||
SEARCH_MULTIPLE_NAME: bool = False
|
|
||||||
# 最大搜索名称数量
|
|
||||||
MAX_SEARCH_NAME_LIMIT: int = 2
|
|
||||||
# 站点数据刷新间隔(小时)
|
# 站点数据刷新间隔(小时)
|
||||||
SITEDATA_REFRESH_INTERVAL: int = 6
|
SITEDATA_REFRESH_INTERVAL: int = 6
|
||||||
# 读取和发送站点消息
|
# 读取和发送站点消息
|
||||||
SITE_MESSAGE: bool = True
|
SITE_MESSAGE: bool = True
|
||||||
# 不能缓存站点资源的站点域名,多个使用,分隔
|
# 不能缓存站点资源的站点域名,多个使用,分隔
|
||||||
NO_CACHE_SITE_KEY: str = "m-team"
|
NO_CACHE_SITE_KEY: str = "m-team"
|
||||||
|
|
||||||
|
# ==================== 下载配置 ====================
|
||||||
# 种子标签
|
# 种子标签
|
||||||
TORRENT_TAG: str = "MOVIEPILOT"
|
TORRENT_TAG: str = "MOVIEPILOT"
|
||||||
# 下载站点字幕
|
# 下载站点字幕
|
||||||
DOWNLOAD_SUBTITLE: bool = True
|
DOWNLOAD_SUBTITLE: bool = True
|
||||||
# 交互搜索自动下载用户ID,使用,分割
|
# 交互搜索自动下载用户ID,使用,分割
|
||||||
AUTO_DOWNLOAD_USER: Optional[str] = None
|
AUTO_DOWNLOAD_USER: Optional[str] = None
|
||||||
|
|
||||||
|
# ==================== CookieCloud配置 ====================
|
||||||
# CookieCloud是否启动本地服务
|
# CookieCloud是否启动本地服务
|
||||||
COOKIECLOUD_ENABLE_LOCAL: Optional[bool] = False
|
COOKIECLOUD_ENABLE_LOCAL: Optional[bool] = False
|
||||||
# CookieCloud服务器地址
|
# CookieCloud服务器地址
|
||||||
@@ -238,6 +283,8 @@ class ConfigModel(BaseModel):
|
|||||||
COOKIECLOUD_INTERVAL: Optional[int] = 60 * 24
|
COOKIECLOUD_INTERVAL: Optional[int] = 60 * 24
|
||||||
# CookieCloud同步黑名单,多个域名,分割
|
# CookieCloud同步黑名单,多个域名,分割
|
||||||
COOKIECLOUD_BLACKLIST: Optional[str] = None
|
COOKIECLOUD_BLACKLIST: Optional[str] = None
|
||||||
|
|
||||||
|
# ==================== 重命名配置 ====================
|
||||||
# 电影重命名格式
|
# 电影重命名格式
|
||||||
MOVIE_RENAME_FORMAT: str = "{{title}}{% if year %} ({{year}}){% endif %}" \
|
MOVIE_RENAME_FORMAT: str = "{{title}}{% if year %} ({{year}}){% endif %}" \
|
||||||
"/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}" \
|
"/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}" \
|
||||||
@@ -247,10 +294,22 @@ class ConfigModel(BaseModel):
|
|||||||
"/Season {{season}}" \
|
"/Season {{season}}" \
|
||||||
"/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}} 集{% endif %}" \
|
"/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}} 集{% endif %}" \
|
||||||
"{{fileExt}}"
|
"{{fileExt}}"
|
||||||
|
# 重命名时支持的S0别名
|
||||||
|
RENAME_FORMAT_S0_NAMES: list = Field(default=["Specials", "SPs"])
|
||||||
|
# 为指定默认字幕添加.default后缀
|
||||||
|
DEFAULT_SUB: Optional[str] = "zh-cn"
|
||||||
|
|
||||||
|
# ==================== 服务地址配置 ====================
|
||||||
# OCR服务器地址
|
# OCR服务器地址
|
||||||
OCR_HOST: str = "https://movie-pilot.org"
|
OCR_HOST: str = "https://movie-pilot.org"
|
||||||
# 服务器地址,对应 https://github.com/jxxghp/MoviePilot-Server 项目
|
# 服务器地址,对应 https://github.com/jxxghp/MoviePilot-Server 项目
|
||||||
MP_SERVER_HOST: str = "https://movie-pilot.org"
|
MP_SERVER_HOST: str = "https://movie-pilot.org"
|
||||||
|
# 登录页面电影海报,tmdb/bing/mediaserver
|
||||||
|
WALLPAPER: str = "tmdb"
|
||||||
|
# 自定义壁纸api地址
|
||||||
|
CUSTOMIZE_WALLPAPER_API_URL: Optional[str] = None
|
||||||
|
|
||||||
|
# ==================== 插件配置 ====================
|
||||||
# 插件市场仓库地址,多个地址使用,分隔,地址以/结尾
|
# 插件市场仓库地址,多个地址使用,分隔,地址以/结尾
|
||||||
PLUGIN_MARKET: str = ("https://github.com/jxxghp/MoviePilot-Plugins,"
|
PLUGIN_MARKET: str = ("https://github.com/jxxghp/MoviePilot-Plugins,"
|
||||||
"https://github.com/thsrite/MoviePilot-Plugins,"
|
"https://github.com/thsrite/MoviePilot-Plugins,"
|
||||||
@@ -271,6 +330,8 @@ class ConfigModel(BaseModel):
|
|||||||
PLUGIN_STATISTIC_SHARE: bool = True
|
PLUGIN_STATISTIC_SHARE: bool = True
|
||||||
# 是否开启插件热加载
|
# 是否开启插件热加载
|
||||||
PLUGIN_AUTO_RELOAD: bool = False
|
PLUGIN_AUTO_RELOAD: bool = False
|
||||||
|
|
||||||
|
# ==================== GitHub配置 ====================
|
||||||
# Github token,提高请求api限流阈值 ghp_****
|
# Github token,提高请求api限流阈值 ghp_****
|
||||||
GITHUB_TOKEN: Optional[str] = None
|
GITHUB_TOKEN: Optional[str] = None
|
||||||
# Github代理服务器,格式:https://mirror.ghproxy.com/
|
# Github代理服务器,格式:https://mirror.ghproxy.com/
|
||||||
@@ -279,6 +340,8 @@ class ConfigModel(BaseModel):
|
|||||||
PIP_PROXY: Optional[str] = ''
|
PIP_PROXY: Optional[str] = ''
|
||||||
# 指定的仓库Github token,多个仓库使用,分隔,格式:{user1}/{repo1}:ghp_****,{user2}/{repo2}:github_pat_****
|
# 指定的仓库Github token,多个仓库使用,分隔,格式:{user1}/{repo1}:ghp_****,{user2}/{repo2}:github_pat_****
|
||||||
REPO_GITHUB_TOKEN: Optional[str] = None
|
REPO_GITHUB_TOKEN: Optional[str] = None
|
||||||
|
|
||||||
|
# ==================== 性能配置 ====================
|
||||||
# 大内存模式
|
# 大内存模式
|
||||||
BIG_MEMORY_MODE: bool = False
|
BIG_MEMORY_MODE: bool = False
|
||||||
# FastApi性能监控
|
# FastApi性能监控
|
||||||
@@ -289,6 +352,8 @@ class ConfigModel(BaseModel):
|
|||||||
ENCODING_DETECTION_PERFORMANCE_MODE: bool = True
|
ENCODING_DETECTION_PERFORMANCE_MODE: bool = True
|
||||||
# 编码探测的最低置信度阈值
|
# 编码探测的最低置信度阈值
|
||||||
ENCODING_DETECTION_MIN_CONFIDENCE: float = 0.8
|
ENCODING_DETECTION_MIN_CONFIDENCE: float = 0.8
|
||||||
|
|
||||||
|
# ==================== 安全配置 ====================
|
||||||
# 允许的图片缓存域名
|
# 允许的图片缓存域名
|
||||||
SECURITY_IMAGE_DOMAINS: list = Field(default=[
|
SECURITY_IMAGE_DOMAINS: list = Field(default=[
|
||||||
"image.tmdb.org",
|
"image.tmdb.org",
|
||||||
@@ -308,23 +373,27 @@ class ConfigModel(BaseModel):
|
|||||||
])
|
])
|
||||||
# 允许的图片文件后缀格式
|
# 允许的图片文件后缀格式
|
||||||
SECURITY_IMAGE_SUFFIXES: list = Field(default=[".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"])
|
SECURITY_IMAGE_SUFFIXES: list = Field(default=[".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".avif"])
|
||||||
# 重命名时支持的S0别名
|
|
||||||
RENAME_FORMAT_S0_NAMES: list = Field(default=["Specials", "SPs"])
|
# ==================== 工作流配置 ====================
|
||||||
# 为指定默认字幕添加.default后缀
|
|
||||||
DEFAULT_SUB: Optional[str] = "zh-cn"
|
|
||||||
# Docker Client API地址
|
|
||||||
DOCKER_CLIENT_API: Optional[str] = "tcp://127.0.0.1:38379"
|
|
||||||
# 工作流数据共享
|
# 工作流数据共享
|
||||||
WORKFLOW_STATISTIC_SHARE: bool = True
|
WORKFLOW_STATISTIC_SHARE: bool = True
|
||||||
|
|
||||||
|
# ==================== 存储配置 ====================
|
||||||
# 对rclone进行快照对比时,是否检查文件夹的修改时间
|
# 对rclone进行快照对比时,是否检查文件夹的修改时间
|
||||||
RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME = True
|
RCLONE_SNAPSHOT_CHECK_FOLDER_MODTIME = True
|
||||||
# 对OpenList进行快照对比时,是否检查文件夹的修改时间
|
# 对OpenList进行快照对比时,是否检查文件夹的修改时间
|
||||||
OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME = True
|
OPENLIST_SNAPSHOT_CHECK_FOLDER_MODTIME = True
|
||||||
|
|
||||||
|
# ==================== 浏览器仿真配置 ====================
|
||||||
# 仿真类型:playwright 或 flaresolverr
|
# 仿真类型:playwright 或 flaresolverr
|
||||||
BROWSER_EMULATION: str = "playwright"
|
BROWSER_EMULATION: str = "playwright"
|
||||||
# FlareSolverr 服务地址,例如 http://127.0.0.1:8191
|
# FlareSolverr 服务地址,例如 http://127.0.0.1:8191
|
||||||
FLARESOLVERR_URL: Optional[str] = None
|
FLARESOLVERR_URL: Optional[str] = None
|
||||||
|
|
||||||
|
# ==================== Docker配置 ====================
|
||||||
|
# Docker Client API地址
|
||||||
|
DOCKER_CLIENT_API: Optional[str] = "tcp://127.0.0.1:38379"
|
||||||
|
|
||||||
|
|
||||||
class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
||||||
"""
|
"""
|
||||||
@@ -590,9 +659,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
fanart=512,
|
fanart=512,
|
||||||
meta=(self.META_CACHE_EXPIRE or 24) * 3600,
|
meta=(self.META_CACHE_EXPIRE or 24) * 3600,
|
||||||
scheduler=100,
|
scheduler=100,
|
||||||
threadpool=100,
|
threadpool=100
|
||||||
dbpool=100,
|
|
||||||
dbpooloverflow=50
|
|
||||||
)
|
)
|
||||||
return SystemConfModel(
|
return SystemConfModel(
|
||||||
torrents=100,
|
torrents=100,
|
||||||
@@ -603,9 +670,7 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
|
|||||||
fanart=128,
|
fanart=128,
|
||||||
meta=(self.META_CACHE_EXPIRE or 2) * 3600,
|
meta=(self.META_CACHE_EXPIRE or 2) * 3600,
|
||||||
scheduler=50,
|
scheduler=50,
|
||||||
threadpool=50,
|
threadpool=50
|
||||||
dbpool=50,
|
|
||||||
dbpooloverflow=20
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -1,19 +1,43 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any, Generator, List, Optional, Self, Tuple, AsyncGenerator, Sequence, Union
|
from typing import Any, Generator, List, Optional, Self, Tuple, AsyncGenerator, Union
|
||||||
|
|
||||||
from sqlalchemy import NullPool, QueuePool, and_, create_engine, inspect, text, select, delete
|
from sqlalchemy import NullPool, QueuePool, and_, create_engine, inspect, text, select, delete, Column, Integer, \
|
||||||
|
Sequence, Identity
|
||||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||||
from sqlalchemy.orm import Session, as_declarative, declared_attr, scoped_session, sessionmaker
|
from sqlalchemy.orm import Session, as_declarative, declared_attr, scoped_session, sessionmaker
|
||||||
|
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
|
|
||||||
|
|
||||||
|
def get_id_column():
|
||||||
|
"""
|
||||||
|
根据数据库类型返回合适的ID列定义
|
||||||
|
"""
|
||||||
|
if settings.DB_TYPE.lower() == "postgresql":
|
||||||
|
# PostgreSQL使用SERIAL类型,让数据库自动处理序列
|
||||||
|
return Column(Integer, Identity(start=1, cycle=True), primary_key=True, index=True)
|
||||||
|
else:
|
||||||
|
# SQLite使用Sequence
|
||||||
|
return Column(Integer, Sequence('id'), primary_key=True, index=True)
|
||||||
|
|
||||||
|
|
||||||
def _get_database_engine(is_async: bool = False):
|
def _get_database_engine(is_async: bool = False):
|
||||||
"""
|
"""
|
||||||
获取数据库连接参数并设置WAL模式
|
获取数据库连接参数并设置WAL模式
|
||||||
:param is_async: 是否创建异步引擎,True - 异步引擎, False - 同步引擎
|
:param is_async: 是否创建异步引擎,True - 异步引擎, False - 同步引擎
|
||||||
:return: 返回对应的数据库引擎
|
:return: 返回对应的数据库引擎
|
||||||
"""
|
"""
|
||||||
|
# 根据数据库类型选择连接方式
|
||||||
|
if settings.DB_TYPE.lower() == "postgresql":
|
||||||
|
return _get_postgresql_engine(is_async)
|
||||||
|
else:
|
||||||
|
return _get_sqlite_engine(is_async)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_sqlite_engine(is_async: bool = False):
|
||||||
|
"""
|
||||||
|
获取SQLite数据库引擎
|
||||||
|
"""
|
||||||
# 连接参数
|
# 连接参数
|
||||||
_connect_args = {
|
_connect_args = {
|
||||||
"timeout": settings.DB_TIMEOUT,
|
"timeout": settings.DB_TIMEOUT,
|
||||||
@@ -40,9 +64,9 @@ def _get_database_engine(is_async: bool = False):
|
|||||||
# 当使用 QueuePool 时,添加 QueuePool 特有的参数
|
# 当使用 QueuePool 时,添加 QueuePool 特有的参数
|
||||||
if _pool_class == QueuePool:
|
if _pool_class == QueuePool:
|
||||||
_db_kwargs.update({
|
_db_kwargs.update({
|
||||||
"pool_size": settings.CONF.dbpool,
|
"pool_size": settings.DB_SQLITE_POOL_SIZE,
|
||||||
"pool_timeout": settings.DB_POOL_TIMEOUT,
|
"pool_timeout": settings.DB_POOL_TIMEOUT,
|
||||||
"max_overflow": settings.CONF.dbpooloverflow
|
"max_overflow": settings.DB_SQLITE_MAX_OVERFLOW
|
||||||
})
|
})
|
||||||
|
|
||||||
# 创建数据库引擎
|
# 创建数据库引擎
|
||||||
@@ -52,7 +76,7 @@ def _get_database_engine(is_async: bool = False):
|
|||||||
_journal_mode = "WAL" if settings.DB_WAL_ENABLE else "DELETE"
|
_journal_mode = "WAL" if settings.DB_WAL_ENABLE else "DELETE"
|
||||||
with engine.connect() as connection:
|
with engine.connect() as connection:
|
||||||
current_mode = connection.execute(text(f"PRAGMA journal_mode={_journal_mode};")).scalar()
|
current_mode = connection.execute(text(f"PRAGMA journal_mode={_journal_mode};")).scalar()
|
||||||
print(f"Database journal mode set to: {current_mode}")
|
print(f"SQLite database journal mode set to: {current_mode}")
|
||||||
|
|
||||||
return engine
|
return engine
|
||||||
else:
|
else:
|
||||||
@@ -78,12 +102,73 @@ def _get_database_engine(is_async: bool = False):
|
|||||||
async with async_engine.connect() as _connection:
|
async with async_engine.connect() as _connection:
|
||||||
result = await _connection.execute(text(f"PRAGMA journal_mode={_journal_mode};"))
|
result = await _connection.execute(text(f"PRAGMA journal_mode={_journal_mode};"))
|
||||||
_current_mode = result.scalar()
|
_current_mode = result.scalar()
|
||||||
print(f"Async database journal mode set to: {_current_mode}")
|
print(f"Async SQLite database journal mode set to: {_current_mode}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
asyncio.run(set_async_wal_mode())
|
asyncio.run(set_async_wal_mode())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to set async WAL mode: {e}")
|
print(f"Failed to set async SQLite WAL mode: {e}")
|
||||||
|
|
||||||
|
return async_engine
|
||||||
|
|
||||||
|
|
||||||
|
def _get_postgresql_engine(is_async: bool = False):
|
||||||
|
"""
|
||||||
|
获取PostgreSQL数据库引擎
|
||||||
|
"""
|
||||||
|
# 构建PostgreSQL连接URL
|
||||||
|
if settings.DB_POSTGRESQL_PASSWORD:
|
||||||
|
db_url = f"postgresql://{settings.DB_POSTGRESQL_USERNAME}:{settings.DB_POSTGRESQL_PASSWORD}@{settings.DB_POSTGRESQL_HOST}:{settings.DB_POSTGRESQL_PORT}/{settings.DB_POSTGRESQL_DATABASE}"
|
||||||
|
else:
|
||||||
|
db_url = f"postgresql://{settings.DB_POSTGRESQL_USERNAME}@{settings.DB_POSTGRESQL_HOST}:{settings.DB_POSTGRESQL_PORT}/{settings.DB_POSTGRESQL_DATABASE}"
|
||||||
|
|
||||||
|
# PostgreSQL连接参数
|
||||||
|
_connect_args = {}
|
||||||
|
|
||||||
|
# 创建同步引擎
|
||||||
|
if not is_async:
|
||||||
|
# 根据池类型设置 poolclass 和相关参数
|
||||||
|
_pool_class = NullPool if settings.DB_POOL_TYPE == "NullPool" else QueuePool
|
||||||
|
|
||||||
|
# 数据库参数
|
||||||
|
_db_kwargs = {
|
||||||
|
"url": db_url,
|
||||||
|
"pool_pre_ping": settings.DB_POOL_PRE_PING,
|
||||||
|
"echo": settings.DB_ECHO,
|
||||||
|
"poolclass": _pool_class,
|
||||||
|
"pool_recycle": settings.DB_POOL_RECYCLE,
|
||||||
|
"connect_args": _connect_args
|
||||||
|
}
|
||||||
|
|
||||||
|
# 当使用 QueuePool 时,添加 QueuePool 特有的参数
|
||||||
|
if _pool_class == QueuePool:
|
||||||
|
_db_kwargs.update({
|
||||||
|
"pool_size": settings.DB_POSTGRESQL_POOL_SIZE,
|
||||||
|
"pool_timeout": settings.DB_POOL_TIMEOUT,
|
||||||
|
"max_overflow": settings.DB_POSTGRESQL_MAX_OVERFLOW
|
||||||
|
})
|
||||||
|
|
||||||
|
# 创建数据库引擎
|
||||||
|
engine = create_engine(**_db_kwargs)
|
||||||
|
print(f"PostgreSQL database connected to {settings.DB_POSTGRESQL_HOST}:{settings.DB_POSTGRESQL_PORT}/{settings.DB_POSTGRESQL_DATABASE}")
|
||||||
|
|
||||||
|
return engine
|
||||||
|
else:
|
||||||
|
# 构建异步PostgreSQL连接URL
|
||||||
|
async_db_url = f"postgresql+asyncpg://{settings.DB_POSTGRESQL_USERNAME}:{settings.DB_POSTGRESQL_PASSWORD}@{settings.DB_POSTGRESQL_HOST}:{settings.DB_POSTGRESQL_PORT}/{settings.DB_POSTGRESQL_DATABASE}"
|
||||||
|
|
||||||
|
# 数据库参数,只能使用 NullPool
|
||||||
|
_db_kwargs = {
|
||||||
|
"url": async_db_url,
|
||||||
|
"pool_pre_ping": settings.DB_POOL_PRE_PING,
|
||||||
|
"echo": settings.DB_ECHO,
|
||||||
|
"poolclass": NullPool,
|
||||||
|
"pool_recycle": settings.DB_POOL_RECYCLE,
|
||||||
|
"connect_args": _connect_args
|
||||||
|
}
|
||||||
|
# 创建异步数据库引擎
|
||||||
|
async_engine = create_async_engine(**_db_kwargs)
|
||||||
|
print(f"Async PostgreSQL database connected to {settings.DB_POSTGRESQL_HOST}:{settings.DB_POSTGRESQL_PORT}/{settings.DB_POSTGRESQL_DATABASE}")
|
||||||
|
|
||||||
return async_engine
|
return async_engine
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,22 @@ def update_db():
|
|||||||
"""
|
"""
|
||||||
更新数据库
|
更新数据库
|
||||||
"""
|
"""
|
||||||
db_location = settings.CONFIG_PATH / 'user.db'
|
|
||||||
script_location = settings.ROOT_PATH / 'database'
|
script_location = settings.ROOT_PATH / 'database'
|
||||||
try:
|
try:
|
||||||
alembic_cfg = Config()
|
alembic_cfg = Config()
|
||||||
alembic_cfg.set_main_option('script_location', str(script_location))
|
alembic_cfg.set_main_option('script_location', str(script_location))
|
||||||
alembic_cfg.set_main_option('sqlalchemy.url', f"sqlite:///{db_location}")
|
|
||||||
|
# 根据数据库类型设置不同的URL
|
||||||
|
if settings.DB_TYPE.lower() == "postgresql":
|
||||||
|
if settings.DB_POSTGRESQL_PASSWORD:
|
||||||
|
db_url = f"postgresql://{settings.DB_POSTGRESQL_USERNAME}:{settings.DB_POSTGRESQL_PASSWORD}@{settings.DB_POSTGRESQL_HOST}:{settings.DB_POSTGRESQL_PORT}/{settings.DB_POSTGRESQL_DATABASE}"
|
||||||
|
else:
|
||||||
|
db_url = f"postgresql://{settings.DB_POSTGRESQL_USERNAME}@{settings.DB_POSTGRESQL_HOST}:{settings.DB_POSTGRESQL_PORT}/{settings.DB_POSTGRESQL_DATABASE}"
|
||||||
|
else:
|
||||||
|
db_location = settings.CONFIG_PATH / 'user.db'
|
||||||
|
db_url = f"sqlite:///{db_location}"
|
||||||
|
|
||||||
|
alembic_cfg.set_main_option('sqlalchemy.url', db_url)
|
||||||
upgrade(alembic_cfg, 'head')
|
upgrade(alembic_cfg, 'head')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'数据库更新失败:{str(e)}')
|
logger.error(f'数据库更新失败:{str(e)}')
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, JSON, select
|
from sqlalchemy import Column, Integer, String, JSON, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, Base, async_db_query
|
from app.db import db_query, db_update, get_id_column, Base, async_db_query
|
||||||
|
|
||||||
|
|
||||||
class DownloadHistory(Base):
|
class DownloadHistory(Base):
|
||||||
"""
|
"""
|
||||||
下载历史记录
|
下载历史记录
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 保存路径
|
# 保存路径
|
||||||
path = Column(String, nullable=False, index=True)
|
path = Column(String, nullable=False, index=True)
|
||||||
# 类型 电影/电视剧
|
# 类型 电影/电视剧
|
||||||
@@ -188,7 +188,7 @@ class DownloadFiles(Base):
|
|||||||
"""
|
"""
|
||||||
下载文件记录
|
下载文件记录
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 下载器
|
# 下载器
|
||||||
downloader = Column(String)
|
downloader = Column(String)
|
||||||
# 下载任务Hash
|
# 下载任务Hash
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, JSON
|
from sqlalchemy import Column, Integer, String, JSON
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, async_db_query, Base
|
from app.db import db_query, db_update, get_id_column, async_db_query, Base
|
||||||
|
|
||||||
|
|
||||||
class MediaServerItem(Base):
|
class MediaServerItem(Base):
|
||||||
"""
|
"""
|
||||||
媒体服务器媒体条目表
|
媒体服务器媒体条目表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 服务器类型
|
# 服务器类型
|
||||||
server = Column(String)
|
server = Column(String)
|
||||||
# 媒体库ID
|
# 媒体库ID
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, JSON, select
|
from sqlalchemy import Column, Integer, String, JSON, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, Base, async_db_query
|
from app.db import db_query, Base, get_id_column, async_db_query
|
||||||
|
|
||||||
|
|
||||||
class Message(Base):
|
class Message(Base):
|
||||||
"""
|
"""
|
||||||
消息表
|
消息表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 消息渠道
|
# 消息渠道
|
||||||
channel = Column(String)
|
channel = Column(String)
|
||||||
# 消息来源
|
# 消息来源
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from sqlalchemy import Column, Integer, String, Sequence, JSON
|
from sqlalchemy import Column, String, JSON
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, Base
|
from app.db import db_query, db_update, get_id_column, Base
|
||||||
|
|
||||||
|
|
||||||
class PluginData(Base):
|
class PluginData(Base):
|
||||||
"""
|
"""
|
||||||
插件数据表
|
插件数据表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
plugin_id = Column(String, nullable=False, index=True)
|
plugin_id = Column(String, nullable=False, index=True)
|
||||||
key = Column(String, index=True, nullable=False)
|
key = Column(String, index=True, nullable=False)
|
||||||
value = Column(JSON)
|
value = Column(JSON)
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import Boolean, Column, Integer, String, Sequence, JSON, select, delete
|
from sqlalchemy import Boolean, Column, Integer, String, JSON, select, delete
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, Base, async_db_query, async_db_update
|
from app.db import db_query, db_update, Base, async_db_query, async_db_update, get_id_column
|
||||||
|
|
||||||
|
|
||||||
class Site(Base):
|
class Site(Base):
|
||||||
"""
|
"""
|
||||||
站点表
|
站点表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 站点名
|
# 站点名
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False)
|
||||||
# 域名Key
|
# 域名Key
|
||||||
@@ -69,12 +69,12 @@ class Site(Base):
|
|||||||
@classmethod
|
@classmethod
|
||||||
@db_query
|
@db_query
|
||||||
def get_actives(cls, db: Session):
|
def get_actives(cls, db: Session):
|
||||||
return db.query(cls).filter(cls.is_active == 1).all()
|
return db.query(cls).filter(cls.is_active).all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@async_db_query
|
@async_db_query
|
||||||
async def async_get_actives(cls, db: AsyncSession):
|
async def async_get_actives(cls, db: AsyncSession):
|
||||||
result = await db.execute(select(cls).where(cls.is_active == 1))
|
result = await db.execute(select(cls).where(cls.is_active))
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
from sqlalchemy import Column, Integer, String, Sequence, select
|
from sqlalchemy import Column, String, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, Base, async_db_query
|
from app.db import db_query, Base, get_id_column, async_db_query
|
||||||
|
|
||||||
|
|
||||||
class SiteIcon(Base):
|
class SiteIcon(Base):
|
||||||
"""
|
"""
|
||||||
站点图标表
|
站点图标表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 站点名称
|
# 站点名称
|
||||||
name = Column(String, nullable=False)
|
name = Column(String, nullable=False)
|
||||||
# 域名Key
|
# 域名Key
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, JSON, select
|
from sqlalchemy import Column, Integer, String, JSON, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, Base, async_db_query
|
from app.db import db_query, db_update, get_id_column, Base, async_db_query
|
||||||
|
|
||||||
|
|
||||||
class SiteStatistic(Base):
|
class SiteStatistic(Base):
|
||||||
"""
|
"""
|
||||||
站点统计表
|
站点统计表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 域名Key
|
# 域名Key
|
||||||
domain = Column(String, index=True)
|
domain = Column(String, index=True)
|
||||||
# 成功次数
|
# 成功次数
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, Float, JSON, func, or_, select
|
from sqlalchemy import Column, Integer, String, Float, JSON, func, or_, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, Base, async_db_query
|
from app.db import db_query, Base, get_id_column, async_db_query
|
||||||
|
|
||||||
|
|
||||||
class SiteUserData(Base):
|
class SiteUserData(Base):
|
||||||
"""
|
"""
|
||||||
站点数据表
|
站点数据表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 站点域名
|
# 站点域名
|
||||||
domain = Column(String, index=True)
|
domain = Column(String, index=True)
|
||||||
# 站点名称
|
# 站点名称
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, Float, JSON, select
|
from sqlalchemy import Column, Integer, String, Float, JSON, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, Base, async_db_query, async_db_update
|
from app.db import db_query, db_update, get_id_column, Base, async_db_query, async_db_update
|
||||||
|
|
||||||
|
|
||||||
class Subscribe(Base):
|
class Subscribe(Base):
|
||||||
"""
|
"""
|
||||||
订阅表
|
订阅表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 标题
|
# 标题
|
||||||
name = Column(String, nullable=False, index=True)
|
name = Column(String, nullable=False, index=True)
|
||||||
# 年份
|
# 年份
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, Float, JSON, select
|
from sqlalchemy import Column, Integer, String, Float, JSON, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, Base, async_db_query
|
from app.db import db_query, Base, get_id_column, async_db_query
|
||||||
|
|
||||||
|
|
||||||
class SubscribeHistory(Base):
|
class SubscribeHistory(Base):
|
||||||
"""
|
"""
|
||||||
订阅历史表
|
订阅历史表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 标题
|
# 标题
|
||||||
name = Column(String, nullable=False, index=True)
|
name = Column(String, nullable=False, index=True)
|
||||||
# 年份
|
# 年份
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
from sqlalchemy import Column, Integer, String, Sequence, JSON, select
|
from sqlalchemy import Column, String, JSON, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, Base, async_db_query
|
from app.db import db_query, db_update, Base, async_db_query, get_id_column
|
||||||
|
|
||||||
|
|
||||||
class SystemConfig(Base):
|
class SystemConfig(Base):
|
||||||
"""
|
"""
|
||||||
配置表
|
配置表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 主键
|
# 主键
|
||||||
key = Column(String, index=True)
|
key = Column(String, index=True)
|
||||||
# 值
|
# 值
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, String, Sequence, Boolean, func, or_, JSON, select
|
from sqlalchemy import Column, Integer, String, Boolean, func, or_, JSON, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, Base, async_db_query
|
from app.db import db_query, db_update, get_id_column, Base, async_db_query
|
||||||
|
|
||||||
|
|
||||||
class TransferHistory(Base):
|
class TransferHistory(Base):
|
||||||
"""
|
"""
|
||||||
整理记录
|
整理记录
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 源路径
|
# 源路径
|
||||||
src = Column(String, index=True)
|
src = Column(String, index=True)
|
||||||
# 源存储
|
# 源存储
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
from sqlalchemy import Boolean, Column, Integer, JSON, Sequence, String, select
|
from sqlalchemy import Boolean, Column, JSON, String, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import Base, db_query, db_update, async_db_query, async_db_update
|
from app.db import Base, db_query, db_update, async_db_query, async_db_update, get_id_column
|
||||||
|
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
@@ -10,7 +10,7 @@ class User(Base):
|
|||||||
用户表
|
用户表
|
||||||
"""
|
"""
|
||||||
# ID
|
# ID
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 用户名,唯一值
|
# 用户名,唯一值
|
||||||
name = Column(String, index=True, nullable=False)
|
name = Column(String, index=True, nullable=False)
|
||||||
# 邮箱
|
# 邮箱
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
from sqlalchemy import Column, Integer, String, Sequence, UniqueConstraint, Index, JSON
|
from sqlalchemy import Column, String, UniqueConstraint, Index, JSON
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.db import db_query, db_update, Base
|
from app.db import db_query, db_update, get_id_column, Base
|
||||||
|
|
||||||
|
|
||||||
class UserConfig(Base):
|
class UserConfig(Base):
|
||||||
"""
|
"""
|
||||||
用户配置表
|
用户配置表
|
||||||
"""
|
"""
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 用户名
|
# 用户名
|
||||||
username = Column(String, index=True)
|
username = Column(String, index=True)
|
||||||
# 配置键
|
# 配置键
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from sqlalchemy import Column, Integer, JSON, Sequence, String, and_, or_, select
|
from sqlalchemy import Column, Integer, JSON, String, and_, or_, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from app.db import Base, db_query, db_update, async_db_query, async_db_update
|
from app.db import Base, db_query, get_id_column, db_update, async_db_query, async_db_update
|
||||||
|
|
||||||
|
|
||||||
class Workflow(Base):
|
class Workflow(Base):
|
||||||
@@ -12,7 +12,7 @@ class Workflow(Base):
|
|||||||
工作流表
|
工作流表
|
||||||
"""
|
"""
|
||||||
# ID
|
# ID
|
||||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
id = get_id_column()
|
||||||
# 名称
|
# 名称
|
||||||
name = Column(String, index=True, nullable=False)
|
name = Column(String, index=True, nullable=False)
|
||||||
# 描述
|
# 描述
|
||||||
|
|||||||
@@ -497,7 +497,7 @@ class Emby:
|
|||||||
logger.info(f"影片图片链接:{res.url}")
|
logger.info(f"影片图片链接:{res.url}")
|
||||||
return res.url
|
return res.url
|
||||||
else:
|
else:
|
||||||
logger.error("Items/Id/Images 未获取到返回数据或无该影片{}图片".format(image_type))
|
logger.info("Items/Id/Images 未获取到返回数据或无该影片{}图片".format(image_type))
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"连接Items/Id/Images出错:" + str(e))
|
logger.error(f"连接Items/Id/Images出错:" + str(e))
|
||||||
|
|||||||
@@ -390,7 +390,7 @@ class Scheduler(metaclass=Singleton):
|
|||||||
if not job:
|
if not job:
|
||||||
return None
|
return None
|
||||||
if job.get("running"):
|
if job.get("running"):
|
||||||
logger.warning(f"定时任务 {job_id} - {job.get("name")} 正在运行 ...")
|
logger.warning(f"定时任务 {job_id} - {job.get('name')} 正在运行 ...")
|
||||||
return None
|
return None
|
||||||
self._jobs[job_id]["running"] = True
|
self._jobs[job_id]["running"] = True
|
||||||
return job
|
return job
|
||||||
|
|||||||
@@ -1,17 +1 @@
|
|||||||
#######################################################################################################
|
# MoviePilot V2版本,大部分设置可通过后台设置界面进行配置,仅个别配置需要通过环境变量或本配置文件配置,所有可配置项参考:https://wiki.movie-pilot.org/zh/configuration
|
||||||
# V2版本中大部分设置可通过后台设置界面进行配置,本文件仅展示界面无法配置的项, 这些项同样可以通过环境变量进行设置 #
|
|
||||||
#######################################################################################################
|
|
||||||
# 【*】API监听地址(注意不是前端访问地址)
|
|
||||||
HOST=0.0.0.0
|
|
||||||
# 【*】超级管理员,设置后一但重启将固化到数据库中,修改将无效(初始化超级管理员密码仅会生成一次,请在日志中查看并自行登录系统修改)
|
|
||||||
SUPERUSER=admin
|
|
||||||
# 开发调试模式,仅开发人员使用,打开后将停止后台服务
|
|
||||||
DEV=false
|
|
||||||
# 为指定字幕添加.default后缀设置为默认字幕,支持为'zh-cn','zh-tw','eng'添加默认字幕,未定义或设置为None则不添加
|
|
||||||
DEFAULT_SUB=zh-cn
|
|
||||||
# 是否启用内存监控,开启后将定期生成内存快照文件
|
|
||||||
MEMORY_ANALYSIS=false
|
|
||||||
# 内存快照间隔(分钟)
|
|
||||||
MEMORY_SNAPSHOT_INTERVAL=30
|
|
||||||
# 保留的内存快照文件数量
|
|
||||||
MEMORY_SNAPSHOT_KEEP_COUNT=20
|
|
||||||
@@ -40,13 +40,25 @@ def run_migrations_offline() -> None:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
url = config.get_main_option("sqlalchemy.url")
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
context.configure(
|
|
||||||
url=url,
|
# 根据数据库类型配置不同的参数
|
||||||
target_metadata=target_metadata,
|
if url and "postgresql" in url:
|
||||||
literal_binds=True,
|
# PostgreSQL配置
|
||||||
dialect_opts={"paramstyle": "named"},
|
context.configure(
|
||||||
render_as_batch=True
|
url=url,
|
||||||
)
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# SQLite配置
|
||||||
|
context.configure(
|
||||||
|
url=url,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
render_as_batch=True
|
||||||
|
)
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
@@ -66,9 +78,22 @@ def run_migrations_online() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
with connectable.connect() as connection:
|
with connectable.connect() as connection:
|
||||||
context.configure(
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
connection=connection, target_metadata=target_metadata
|
|
||||||
)
|
# 根据数据库类型配置不同的参数
|
||||||
|
if url and "postgresql" in url:
|
||||||
|
# PostgreSQL配置
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=target_metadata
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# SQLite配置
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
render_as_batch=True
|
||||||
|
)
|
||||||
|
|
||||||
with context.begin_transaction():
|
with context.begin_transaction():
|
||||||
context.run_migrations()
|
context.run_migrations()
|
||||||
|
|||||||
117
database/versions/5b3355c964bb_2_2_0.py
Normal file
117
database/versions/5b3355c964bb_2_2_0.py
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
"""2.2.0
|
||||||
|
|
||||||
|
Revision ID: 5b3355c964bb
|
||||||
|
Revises: d58298a0879f
|
||||||
|
Create Date: 2025-08-19 12:27:08.451371
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '5b3355c964bb'
|
||||||
|
down_revision = 'd58298a0879f'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
if settings.DB_TYPE.lower() == "postgresql":
|
||||||
|
# 将SQLite的Sequence转换为PostgreSQL的Identity
|
||||||
|
fix_postgresql_sequences()
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def fix_postgresql_sequences():
|
||||||
|
"""
|
||||||
|
修复PostgreSQL数据库中的序列问题
|
||||||
|
将SQLite迁移过来的Sequence转换为PostgreSQL的Identity
|
||||||
|
"""
|
||||||
|
connection = op.get_bind()
|
||||||
|
|
||||||
|
# 获取所有表名
|
||||||
|
result = connection.execute(sa.text("""
|
||||||
|
SELECT table_name
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_type = 'BASE TABLE'
|
||||||
|
"""))
|
||||||
|
tables = [row[0] for row in result.fetchall()]
|
||||||
|
|
||||||
|
print(f"发现 {len(tables)} 个表需要检查序列")
|
||||||
|
|
||||||
|
for table_name in tables:
|
||||||
|
fix_table_sequence(connection, table_name)
|
||||||
|
|
||||||
|
|
||||||
|
def fix_table_sequence(connection, table_name):
|
||||||
|
"""
|
||||||
|
修复单个表的序列
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 跳过alembic_version表,它没有id列
|
||||||
|
if table_name == 'alembic_version':
|
||||||
|
print(f"跳过表 {table_name},这是Alembic版本表")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查表是否有id列
|
||||||
|
result = connection.execute(sa.text(f"""
|
||||||
|
SELECT is_identity, column_default
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = '{table_name}'
|
||||||
|
AND column_name = 'id'
|
||||||
|
"""))
|
||||||
|
|
||||||
|
id_column = result.fetchone()
|
||||||
|
if not id_column:
|
||||||
|
print(f"表 {table_name} 没有id列,跳过")
|
||||||
|
return
|
||||||
|
|
||||||
|
is_identity, column_default = id_column
|
||||||
|
|
||||||
|
# 检查是否已经是Identity类型
|
||||||
|
if is_identity == 'YES' or (column_default and 'GENERATED BY DEFAULT AS IDENTITY' in column_default):
|
||||||
|
print(f"表 {table_name} 的id列已经是Identity类型,跳过")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查是否有序列
|
||||||
|
print(f"表 {table_name} 存在序列,需要修复")
|
||||||
|
convert_to_identity(connection, table_name)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"修复表 {table_name} 序列时出错: {e}")
|
||||||
|
# 回滚当前事务,避免影响后续操作
|
||||||
|
connection.rollback()
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_identity(connection, table_name):
|
||||||
|
"""
|
||||||
|
将序列转换为Identity,保持原有约束不变
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取当前序列的最大值
|
||||||
|
result = connection.execute(sa.text(f"""
|
||||||
|
SELECT COALESCE(MAX(id), 0) + 1 as next_value
|
||||||
|
FROM "{table_name}"
|
||||||
|
"""))
|
||||||
|
next_value = result.fetchone()[0]
|
||||||
|
|
||||||
|
# 直接修改列属性,添加Identity,保持其他约束不变
|
||||||
|
# 这种方式不会删除主键约束和索引
|
||||||
|
connection.execute(sa.text(f"""
|
||||||
|
ALTER TABLE "{table_name}"
|
||||||
|
ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (START WITH {next_value})
|
||||||
|
"""))
|
||||||
|
|
||||||
|
print(f"表 {table_name} 序列已转换为Identity,起始值为 {next_value}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"转换表 {table_name} 序列时出错: {e}")
|
||||||
|
# 如果是已经存在的Identity错误,则忽略
|
||||||
|
if "already an identity column" in str(e):
|
||||||
|
print(f"表 {table_name} 的id列已经是Identity类型,忽略此错误")
|
||||||
|
return
|
||||||
|
raise
|
||||||
21
database/versions/d58298a0879f_2_1_9.py
Normal file
21
database/versions/d58298a0879f_2_1_9.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
"""2.1.9
|
||||||
|
|
||||||
|
Revision ID: d58298a0879f
|
||||||
|
Revises: 4666ce24a443
|
||||||
|
Create Date: 2025-08-19 11:56:39.652032
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'd58298a0879f'
|
||||||
|
down_revision = '4666ce24a443'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
@@ -43,6 +43,16 @@ function load_config_from_app_env() {
|
|||||||
["GITHUB_TOKEN"]=""
|
["GITHUB_TOKEN"]=""
|
||||||
["MOVIEPILOT_AUTO_UPDATE"]="release"
|
["MOVIEPILOT_AUTO_UPDATE"]="release"
|
||||||
|
|
||||||
|
# database
|
||||||
|
["DB_TYPE"]="sqlite"
|
||||||
|
["DB_POSTGRESQL_HOST"]="localhost"
|
||||||
|
["DB_POSTGRESQL_PORT"]="5432"
|
||||||
|
["DB_POSTGRESQL_DATABASE"]="moviepilot"
|
||||||
|
["DB_POSTGRESQL_USERNAME"]="moviepilot"
|
||||||
|
["DB_POSTGRESQL_PASSWORD"]="moviepilot"
|
||||||
|
["DB_POSTGRESQL_POOL_SIZE"]="20"
|
||||||
|
["DB_POSTGRESQL_MAX_OVERFLOW"]="30"
|
||||||
|
|
||||||
# cert
|
# cert
|
||||||
["ENABLE_SSL"]="false"
|
["ENABLE_SSL"]="false"
|
||||||
["SSL_DOMAIN"]=""
|
["SSL_DOMAIN"]=""
|
||||||
@@ -195,13 +205,16 @@ fi
|
|||||||
|
|
||||||
# 使用 `envsubst` 将模板文件中的 ${NGINX_PORT} 替换为实际的环境变量值
|
# 使用 `envsubst` 将模板文件中的 ${NGINX_PORT} 替换为实际的环境变量值
|
||||||
envsubst '${NGINX_PORT}${PORT}${NGINX_CLIENT_MAX_BODY_SIZE}${ENABLE_SSL}${HTTPS_SERVER_CONF}' < /etc/nginx/nginx.template.conf > /etc/nginx/nginx.conf
|
envsubst '${NGINX_PORT}${PORT}${NGINX_CLIENT_MAX_BODY_SIZE}${ENABLE_SSL}${HTTPS_SERVER_CONF}' < /etc/nginx/nginx.template.conf > /etc/nginx/nginx.conf
|
||||||
|
|
||||||
# 自动更新
|
# 自动更新
|
||||||
cd /
|
cd /
|
||||||
source /usr/local/bin/mp_update.sh
|
source /usr/local/bin/mp_update.sh
|
||||||
cd /app || exit
|
cd /app || exit
|
||||||
|
|
||||||
# 更改 moviepilot userid 和 groupid
|
# 更改 moviepilot userid 和 groupid
|
||||||
groupmod -o -g "${PGID}" moviepilot
|
groupmod -o -g "${PGID}" moviepilot
|
||||||
usermod -o -u "${PUID}" moviepilot
|
usermod -o -u "${PUID}" moviepilot
|
||||||
|
|
||||||
# 更改文件权限
|
# 更改文件权限
|
||||||
chown -R moviepilot:moviepilot \
|
chown -R moviepilot:moviepilot \
|
||||||
"${HOME}" \
|
"${HOME}" \
|
||||||
@@ -211,17 +224,21 @@ chown -R moviepilot:moviepilot \
|
|||||||
/var/lib/nginx \
|
/var/lib/nginx \
|
||||||
/var/log/nginx
|
/var/log/nginx
|
||||||
chown moviepilot:moviepilot /etc/hosts /tmp
|
chown moviepilot:moviepilot /etc/hosts /tmp
|
||||||
|
|
||||||
# 下载浏览器内核
|
# 下载浏览器内核
|
||||||
if [[ "$HTTPS_PROXY" =~ ^https?:// ]] || [[ "$HTTPS_PROXY" =~ ^https?:// ]] || [[ "$PROXY_HOST" =~ ^https?:// ]]; then
|
if [[ "$HTTPS_PROXY" =~ ^https?:// ]] || [[ "$HTTPS_PROXY" =~ ^https?:// ]] || [[ "$PROXY_HOST" =~ ^https?:// ]]; then
|
||||||
HTTPS_PROXY="${HTTPS_PROXY:-${https_proxy:-$PROXY_HOST}}" gosu moviepilot:moviepilot playwright install chromium
|
HTTPS_PROXY="${HTTPS_PROXY:-${https_proxy:-$PROXY_HOST}}" gosu moviepilot:moviepilot playwright install chromium
|
||||||
else
|
else
|
||||||
gosu moviepilot:moviepilot playwright install chromium
|
gosu moviepilot:moviepilot playwright install chromium
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 证书管理
|
# 证书管理
|
||||||
source /app/docker/cert.sh
|
source /app/docker/cert.sh
|
||||||
|
|
||||||
# 启动前端nginx服务
|
# 启动前端nginx服务
|
||||||
INFO "→ 启动前端nginx服务..."
|
INFO "→ 启动前端nginx服务..."
|
||||||
nginx
|
nginx
|
||||||
|
|
||||||
# 启动docker http proxy nginx
|
# 启动docker http proxy nginx
|
||||||
if [ -S "/var/run/docker.sock" ]; then
|
if [ -S "/var/run/docker.sock" ]; then
|
||||||
INFO "→ 启动 Docker Proxy..."
|
INFO "→ 启动 Docker Proxy..."
|
||||||
@@ -231,6 +248,7 @@ if [ -S "/var/run/docker.sock" ]; then
|
|||||||
/var/lib/nginx \
|
/var/lib/nginx \
|
||||||
/var/log/nginx
|
/var/log/nginx
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 设置后端服务权限掩码
|
# 设置后端服务权限掩码
|
||||||
umask "${UMASK}"
|
umask "${UMASK}"
|
||||||
|
|
||||||
|
|||||||
220
docs/postgresql-setup.md
Normal file
220
docs/postgresql-setup.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# PostgreSQL 数据库配置指南
|
||||||
|
|
||||||
|
MoviePilot 现在支持 PostgreSQL 数据库,您可以根据需要选择使用 SQLite 或 PostgreSQL。
|
||||||
|
|
||||||
|
## 配置选项
|
||||||
|
|
||||||
|
### 1. 数据库类型选择
|
||||||
|
|
||||||
|
在 `config/app.env` 文件中设置:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 SQLite(默认)
|
||||||
|
DB_TYPE=sqlite
|
||||||
|
|
||||||
|
# 使用 PostgreSQL
|
||||||
|
DB_TYPE=postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. PostgreSQL 配置参数
|
||||||
|
|
||||||
|
当 `DB_TYPE=postgresql` 时,以下配置生效:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# PostgreSQL 主机地址
|
||||||
|
DB_POSTGRESQL_HOST=localhost
|
||||||
|
|
||||||
|
# PostgreSQL 端口
|
||||||
|
DB_POSTGRESQL_PORT=5432
|
||||||
|
|
||||||
|
# PostgreSQL 数据库名
|
||||||
|
DB_POSTGRESQL_DATABASE=moviepilot
|
||||||
|
|
||||||
|
# PostgreSQL 用户名
|
||||||
|
DB_POSTGRESQL_USERNAME=moviepilot
|
||||||
|
|
||||||
|
# PostgreSQL 密码
|
||||||
|
DB_POSTGRESQL_PASSWORD=moviepilot
|
||||||
|
|
||||||
|
# PostgreSQL 连接池大小
|
||||||
|
DB_POSTGRESQL_POOL_SIZE=20
|
||||||
|
|
||||||
|
# PostgreSQL 连接池溢出数量
|
||||||
|
DB_POSTGRESQL_MAX_OVERFLOW=30
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker 部署
|
||||||
|
|
||||||
|
### 使用内置 PostgreSQL
|
||||||
|
|
||||||
|
如果您使用 Docker 部署,MoviePilot 容器内置了 PostgreSQL 服务:
|
||||||
|
|
||||||
|
#### 使用 Docker Compose(推荐)
|
||||||
|
|
||||||
|
1. 创建 `docker-compose.yml` 文件:
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
moviepilot:
|
||||||
|
image: jxxghp/moviepilot:latest
|
||||||
|
container_name: moviepilot
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3000:3000" # 前端端口
|
||||||
|
- "3001:3001" # API端口
|
||||||
|
environment:
|
||||||
|
- DB_TYPE=postgresql
|
||||||
|
- DB_POSTGRESQL_HOST=localhost
|
||||||
|
- DB_POSTGRESQL_PORT=5432
|
||||||
|
- DB_POSTGRESQL_DATABASE=moviepilot
|
||||||
|
- DB_POSTGRESQL_USERNAME=moviepilot
|
||||||
|
- DB_POSTGRESQL_PASSWORD=moviepilot
|
||||||
|
volumes:
|
||||||
|
- ./config:/config
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动服务:
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 使用 Docker 命令
|
||||||
|
|
||||||
|
1. 设置环境变量:
|
||||||
|
```bash
|
||||||
|
DB_TYPE=postgresql
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动容器时,PostgreSQL 服务会自动:
|
||||||
|
- 在配置目录下创建 `postgresql/` 子目录作为数据目录
|
||||||
|
- 初始化 PostgreSQL 数据目录
|
||||||
|
- 启动 PostgreSQL 服务
|
||||||
|
- 创建数据库和用户
|
||||||
|
- 配置连接权限
|
||||||
|
|
||||||
|
3. 数据持久化:
|
||||||
|
- PostgreSQL 数据存储在 `${CONFIG_DIR}/postgresql/` 目录中
|
||||||
|
- 日志文件存储在 `${CONFIG_DIR}/postgresql/logs/` 目录中
|
||||||
|
- 这些目录会通过 Docker 卷映射持久化保存
|
||||||
|
|
||||||
|
### 使用外部 PostgreSQL
|
||||||
|
|
||||||
|
如果您想使用外部的 PostgreSQL 服务:
|
||||||
|
|
||||||
|
1. 确保外部 PostgreSQL 服务已启动并可访问
|
||||||
|
2. 设置环境变量指向外部服务:
|
||||||
|
```bash
|
||||||
|
DB_TYPE=postgresql
|
||||||
|
DB_POSTGRESQL_HOST=your-postgresql-host
|
||||||
|
DB_POSTGRESQL_PORT=5432
|
||||||
|
DB_POSTGRESQL_DATABASE=moviepilot
|
||||||
|
DB_POSTGRESQL_USERNAME=your-username
|
||||||
|
DB_POSTGRESQL_PASSWORD=your-password
|
||||||
|
```
|
||||||
|
|
||||||
|
## 数据迁移
|
||||||
|
|
||||||
|
### 从 SQLite 迁移到 PostgreSQL
|
||||||
|
|
||||||
|
1. 备份现有的 SQLite 数据库文件(`config/user.db`)
|
||||||
|
2. 修改配置为 PostgreSQL
|
||||||
|
3. 启动应用,数据库表会自动创建
|
||||||
|
4. 使用数据库迁移工具或手动导入数据
|
||||||
|
|
||||||
|
### 从 PostgreSQL 迁移到 SQLite
|
||||||
|
|
||||||
|
1. 导出 PostgreSQL 数据
|
||||||
|
2. 修改配置为 SQLite
|
||||||
|
3. 启动应用,数据库表会自动创建
|
||||||
|
4. 导入数据到 SQLite
|
||||||
|
|
||||||
|
## 数据备份
|
||||||
|
|
||||||
|
### PostgreSQL 数据备份
|
||||||
|
|
||||||
|
PostgreSQL 数据存储在 `${CONFIG_DIR}/postgresql/` 目录中,您可以通过以下方式进行备份:
|
||||||
|
|
||||||
|
#### 1. 文件级备份
|
||||||
|
```bash
|
||||||
|
# 备份整个PostgreSQL数据目录
|
||||||
|
tar -czf postgresql_backup_$(date +%Y%m%d_%H%M%S).tar.gz config/postgresql/
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 数据库级备份
|
||||||
|
```bash
|
||||||
|
# 进入容器
|
||||||
|
docker exec -it moviepilot bash
|
||||||
|
|
||||||
|
# 使用pg_dump备份
|
||||||
|
pg_dump -h localhost -U moviepilot -d moviepilot > /config/moviepilot_backup.sql
|
||||||
|
|
||||||
|
# 或使用pg_dumpall备份所有数据库
|
||||||
|
pg_dumpall -h localhost -U moviepilot > /config/all_databases_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 恢复数据
|
||||||
|
```bash
|
||||||
|
# 恢复单个数据库
|
||||||
|
psql -h localhost -U moviepilot -d moviepilot < /config/moviepilot_backup.sql
|
||||||
|
|
||||||
|
# 恢复所有数据库
|
||||||
|
psql -h localhost -U moviepilot < /config/all_databases_backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能优化
|
||||||
|
|
||||||
|
### PostgreSQL 优化建议
|
||||||
|
|
||||||
|
1. **连接池配置**:
|
||||||
|
- 根据应用负载调整 `DB_POSTGRESQL_POOL_SIZE`
|
||||||
|
- 设置合适的 `DB_POSTGRESQL_MAX_OVERFLOW`
|
||||||
|
|
||||||
|
2. **数据库配置**:
|
||||||
|
- 调整 `shared_buffers`
|
||||||
|
- 配置 `work_mem`
|
||||||
|
- 设置合适的 `maintenance_work_mem`
|
||||||
|
|
||||||
|
3. **索引优化**:
|
||||||
|
- 为常用查询字段添加索引
|
||||||
|
- 定期执行 `VACUUM` 和 `ANALYZE`
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
1. **连接失败**:
|
||||||
|
- 检查 PostgreSQL 服务是否启动
|
||||||
|
- 验证连接参数是否正确
|
||||||
|
- 确认网络连接和防火墙设置
|
||||||
|
|
||||||
|
2. **权限问题**:
|
||||||
|
- 确保用户有足够的数据库权限
|
||||||
|
- 检查 `pg_hba.conf` 配置
|
||||||
|
|
||||||
|
3. **性能问题**:
|
||||||
|
- 监控连接池使用情况
|
||||||
|
- 检查慢查询日志
|
||||||
|
- 优化数据库配置
|
||||||
|
|
||||||
|
### 日志查看
|
||||||
|
|
||||||
|
PostgreSQL 相关日志可以在以下位置查看:
|
||||||
|
|
||||||
|
- Docker 容器:`${CONFIG_DIR}/postgresql/logs/`
|
||||||
|
- 系统日志:`journalctl -u postgresql`
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **兼容性**:PostgreSQL 支持从 MoviePilot v2.0 开始
|
||||||
|
2. **备份**:建议定期备份数据库
|
||||||
|
3. **版本**:建议使用 PostgreSQL 12 或更高版本
|
||||||
|
4. **字符集**:确保使用 UTF-8 字符集
|
||||||
|
|
||||||
|
## 技术支持
|
||||||
|
|
||||||
|
如果遇到问题,请:
|
||||||
|
|
||||||
|
1. 查看应用日志
|
||||||
|
2. 检查 PostgreSQL 日志
|
||||||
|
3. 在 GitHub Issues 中报告问题
|
||||||
@@ -65,6 +65,8 @@ aiofiles~=24.1.0
|
|||||||
aiopathlib~=0.6.0
|
aiopathlib~=0.6.0
|
||||||
asynctempfile~=0.5.0
|
asynctempfile~=0.5.0
|
||||||
aiosqlite~=0.21.0
|
aiosqlite~=0.21.0
|
||||||
|
psycopg2-binary~=2.9.10
|
||||||
|
asyncpg~=0.30.0
|
||||||
jieba~=0.42.1
|
jieba~=0.42.1
|
||||||
rsa~=4.9
|
rsa~=4.9
|
||||||
redis~=6.2.0
|
redis~=6.2.0
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
APP_VERSION = 'v2.7.2'
|
APP_VERSION = 'v2.7.3'
|
||||||
FRONTEND_VERSION = 'v2.7.2'
|
FRONTEND_VERSION = 'v2.7.3'
|
||||||
|
|||||||
Reference in New Issue
Block a user