This commit is contained in:
j 2024-10-09 09:39:28 +08:00
commit 854504eae9
303 changed files with 12823 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
.github/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,16 @@
---
name: 功能新增请求
about: 请求添加一个新的功能
title: ''
labels: enhancement
assignees: ''
---
**问题描述**
简要描述新的功能要解决的问题
**功能描述**
简要描述你想要新增的功能

View File

@ -0,0 +1,20 @@
---
name: 规则问题反馈
about: 剧集规则或电影规则问题反馈
title: ''
labels: enhancement
assignees: ''
---
**原始标题**
```text
[Lilith-Raws] 鬼滅之刃 刀匠村篇 / Kimetsu no Yaiba - Katanakaji no Sato-Hen - 01 [Baha][WEB-DL][1080p][AVC AAC][CHT][MP4]
```
> 建议附上原始种子页面的链接
**问题描述**
简要描述有什么问题

View File

@ -0,0 +1,27 @@
---
name: 项目问题反馈
about: 反馈项目相关的问题
title: ''
labels: bug
assignees: ''
---
**部署环境windows / docker**
docker
**问题描述**
简要描述复现问题的操作,以及出现的具体问题
**相关日志**
docker 请执行 `docker logs -f jproxy` 并重新执行复现问题的操作截取重新操作后的日志
windows 在 logs 目录下
```text
请以代码的形式附上
```
**相关截图**

13
.github/config vendored Normal file
View File

@ -0,0 +1,13 @@
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = https://github.com/LuckyPuppy514/jproxy.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
remote = origin
merge = refs/heads/main

55
.github/workflows/docker-build-test.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: docker-build-test
on:
push:
branches:
- main
env:
DOCKERHUB_REPO: luckypuppy514/jproxy
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache local Maven repository
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Maven
run: mvn clean package -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -B -V -P prod && java -Djarmode=layertools -jar target/jproxy.jar extract
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERHUB_REPO }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./docker/Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKERHUB_REPO }}:test
labels: ${{ steps.meta.outputs.labels }}

56
.github/workflows/docker-build.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: docker-build
on:
push:
tags:
- v*
env:
DOCKERHUB_REPO: luckypuppy514/jproxy
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Cache local Maven repository
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build with Maven
run: mvn clean package -Dmaven.test.skip=true -Dmaven.javadoc.skip=true -B -V -P prod && java -Djarmode=layertools -jar target/jproxy.jar extract
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKERHUB_REPO }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./docker/Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKERHUB_REPO }}:latest
${{ env.DOCKERHUB_REPO }}:${{ steps.meta.outputs.version }}
labels: ${{ steps.meta.outputs.labels }}

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.settings
logs
target
.classpath
.factorypath
.project

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

18
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar

21
LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 LuckyPuppy514
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

169
README.en_US.md Normal file
View File

@ -0,0 +1,169 @@
<p align="center">
<a href="https://github.com/LuckyPuppy514/jproxy">
<img alt="JProxy Logo" width="200" src="https://raw.githubusercontent.com/LuckyPuppy514/image/main/2023/2023-04-02/logo.png">
</a>
</p>
<p align="center">
<a href="https://github.com/LuckyPuppy514/jproxy"><img alt="stars" src="https://badgen.net/github/stars/LuckyPuppy514/jproxy"/></a>
<a href="https://github.com/LuckyPuppy514/jproxy"><img alt="forks" src="https://badgen.net/github/forks/LuckyPuppy514/jproxy"/></a>
<a href="https://hub.docker.com/r/luckypuppy514/jproxy"><img alt="docker pulls" src="https://img.shields.io/docker/pulls/luckypuppy514/jproxy.svg"/></a>
<a href="https://github.com/LuckyPuppy514/jproxy/blob/main/LICENSE.txt"><img alt="MIT License" src="https://badgen.net/github/license/LuckyPuppy514/jproxy"/></a>
</p>
<div align="center">
<a href="https://github.com/LuckyPuppy514/jproxy/blob/main/README.md">简体中文</a> | English
</div>
- [🌟 Introduce](#-introduce)
- [🧱 Install](#-install)
- [Docker](#docker)
- [Windows](#windows)
- [☃️ Basic Configuration](#-basic-configuration)
- [😘 Contributing](#-contributing)
- [👏 Related Efforts](#-related-efforts)
- [🃏 License](#-license)
## 🌟 Introduce
A proxy between `Sonarr / Radarr` and `Jackett / Prowlarr`, mainly used to optimize search and improve recognition rate
```mermaid
graph LR
1[Sonarr / Radarr] == request Jackett / Prowlarr Torznab interface ==> 2(JProxy) == proxy Sonarr / Radarr request ==> 3(Jackett / Prowlarr)
3(Jackett / Prowlarr) == return raw result ==> 2(JProxy) == return formatted result ==> 1(Sonarr / Radarr)
2(JProxy) == optimize search keywords ==> 2(JProxy)
2(JProxy) == format search result ==> 2(JProxy)
```
![20230405044128](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-05/20230405044128.webp)
![20230406181845](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-06/20230406181845.webp)
![20230414101425](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-14/20230414101425.webp)
## 🧱 Install
### Docker
```text
version: '3.0'
services:
jproxy:
image: luckypuppy514/jproxy:latest
container_name: jproxy
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- JAVA_OPTS=-Xms512m -Xmx512m
ports:
- 8117:8117
volumes:
- /docker/jproxy/database:/app/database
```
If you want deploy via `docker run` see [docker-run.sh](https://github.com/LuckyPuppy514/jproxy/blob/main/docker/docker-run.sh)
| Parameter | Default | Description |
| :--------------------------: | :---------------: | :----------------------------------------------------------------------------------------------------------------------------: |
| PUID | 0 | User ID |
| PGID | 0 | Group ID |
| TZ | Asia/Shanghai | Timezone |
| JAVA_OPTS | -Xms512m -Xmx512m | JVM parameters |
| CACHE_EXPIRES | 4320 | Cache expiration time (minutes) |
| TOKEN_EXPIRES | 10080 | Login expiration time (minutes) |
| SYNC_INTERVAL | 3 | Synchronization interval (minutes) |
| RENAME_FILE | true | File rename switch (true/false) |
| MIN_COUNT | 8 | Append title of primary language (without season and episode number) to search while current result count less than this value |
| INDEXER_RESULT_CACHE_EXPIRES | 15 | Indexer result cache expiration time (minutes) |
If you need to set a proxy, you can append the corresponding proxy parameters in `JAVA_OPTS`
- HTTP Proxy
`-Xms512m -Xmx512m -Dhttp.proxyHost=192.168.6.2 -Dhttp.proxyPort=12345`
- SOCKS Proxy
`-Xms512m -Xmx512m -DsocksProxyHost=192.168.6.2 -DsocksProxyPort=54321`
### Windows
1. [Download jdk17](https://kutt.lckp.top/yrnerc), install and configure environment variables
2. [Download windows.zip](https://github.com/LuckyPuppy514/jproxy/releases) unzip to the installation directory
| Filename | Explanation | Remark |
| :----------------: | :-----------------------: | :-------------------------------------: |
| startup.bat | starup script | - |
| shutdown.bat | shutdown script | - |
| startup-daemon.bat | startup background script | hidden window running in the background |
| database | database | keep it while upgrade |
| config | configuration files | - |
| jproxy.jar | Runnable jar package | - |
## ☃️ Basic Configuration
- URL: `http://127.0.0.1:8117/login`
- User: `jproxy`
- Password: `jproxy@2023`
![20230406181038](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-06/20230406181038.webp)
① Fill in `Sonarr Server Url`, `API KEY`, and `Indexer Address` in `System - Configure` (Jackett or Prowlarr)
![20230404182207](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-04/20230404182207.webp)
![20230414101841](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-14/20230414101841.webp)
💡 After saving, it should normally be as shown in the picture below ✅, otherwise please check the input and network connectivity
![20230414101757](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-14/20230414101757.webp)
② For the first use, it is recommended to manually synchronize `Series Title` and `Series Rule` once (it will be automatically synchronized later)
![20230406182240](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-06/20230406182240.webp)
![20230406182304](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-06/20230406182304.webp)
③ Modify the `IP` and `Port` of the indexer to the `IP` and `Port` of JProxy, and append the path
Jackett
`http://192.168.6.15:9117/api/v2.0/......` ➡️ `http://192.168.6.14:8117/sonarr/jackett/api/v2.0/......`
![20230404172541](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-04/20230404172541.webp)
Prowlarr
`http://192.168.6.15:9696` ➡️ `http://192.168.6.14:8117/sonarr/prowlarr`
![20230806210024](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-08-06/20230806210024.webp)
And change `Authentication Required` to `Disabled for Local Addresses`
![20230806211107](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-08-06/20230806211107.webp)
[🎗️ For advanced configuration and usage, see wiki](https://github.com/LuckyPuppy514/jproxy/wiki)
## 😘 Contributing
Feel free to dive in[Open an issue](https://github.com/LuckyPuppy514/jproxy/issues/new/choose) or submit PRs.
- [arco-design-pro-vue](https://github.com/arco-design/arco-design-pro-vue)
- [spring-boot](https://github.com/spring-projects/spring-boot)
- [sqlite](https://github.com/sqlite/sqlite)
- [liquibase](https://github.com/liquibase/liquibase)
- [mybatis](https://github.com/mybatis/mybatis-3)
- [mybatis-plus](https://github.com/baomidou/mybatis-plus)
- [caffeine](https://github.com/ben-manes/caffeine)
- [knife4j](https://github.com/xiaoymin/knife4j)
- [charon](https://github.com/mkopylec/charon-spring-boot-starter)
- [jib](https://github.com/GoogleContainerTools/jib)
## 👏 Related Efforts
- [Sonarr](https://github.com/Sonarr/Sonarr)
- [Radarr](https://github.com/radarr/radarr)
- [Jackett](https://github.com/Jackett/Jackett)
- [Prowlarr](https://github.com/Prowlarr/Prowlarr)
- [qBittorrent](https://github.com/qbittorrent/qBittorrent)
## 🃏 License
[MIT](https://github.com/LuckyPuppy514/jproxy/blob/main/LICENSE) © LuckyPuppy514

169
README.md Normal file
View File

@ -0,0 +1,169 @@
<p align="center">
<a href="https://github.com/LuckyPuppy514/jproxy">
<img alt="JProxy Logo" width="200" src="https://raw.githubusercontent.com/LuckyPuppy514/image/main/2023/2023-04-02/logo.png">
</a>
</p>
<p align="center">
<a href="https://github.com/LuckyPuppy514/jproxy"><img alt="stars" src="https://badgen.net/github/stars/LuckyPuppy514/jproxy"/></a>
<a href="https://github.com/LuckyPuppy514/jproxy"><img alt="forks" src="https://badgen.net/github/forks/LuckyPuppy514/jproxy"/></a>
<a href="https://hub.docker.com/r/luckypuppy514/jproxy"><img alt="docker pulls" src="https://img.shields.io/docker/pulls/luckypuppy514/jproxy.svg"/></a>
<a href="https://github.com/LuckyPuppy514/jproxy/blob/main/LICENSE.txt"><img alt="MIT License" src="https://badgen.net/github/license/LuckyPuppy514/jproxy"/></a>
</p>
<div align="center">
简体中文 | <a href="https://github.com/LuckyPuppy514/jproxy/blob/main/README.en_US.md">English</a>
</div>
- [🌟 项目简介](#-项目简介)
- [🧱 项目安装](#-项目安装)
- [Docker](#docker)
- [Windows](#windows)
- [☃️ 基础配置](#-基础配置)
- [😘 如何贡献](#-如何贡献)
- [👏 相关仓库](#-相关仓库)
- [🃏 使用许可](#-使用许可)
## 🌟 项目简介
介于 `Sonarr / Radarr``Jackett / Prowlarr` 之间的代理,主要用于优化查询和提升识别率
```mermaid
graph LR
1[Sonarr / Radarr] == 请求 Jackett / Prowlarr Torznab 接口 ==> 2(JProxy) == 代理 Sonarr / Radarr 请求 ==> 3(Jackett / Prowlarr)
3(Jackett / Prowlarr) == 返回原始结果 ==> 2(JProxy) == 返回格式化结果 ==> 1(Sonarr / Radarr)
2(JProxy) == 优化查询关键字 ==> 2(JProxy)
2(JProxy) == 格式化查询结果 ==> 2(JProxy)
```
![20230405044128](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-05/20230405044128.webp)
![20230405044054](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-05/20230405044054.webp)
![20230414101403](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-14/20230414101403.webp)
## 🧱 项目安装
### Docker
```text
version: '3.0'
services:
jproxy:
image: luckypuppy514/jproxy:latest
container_name: jproxy
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- JAVA_OPTS=-Xms512m -Xmx512m
ports:
- 8117:8117
volumes:
- /docker/jproxy/database:/app/database
```
如需使用 `docker run` 进行部署,请参考 [docker-run.sh](https://github.com/LuckyPuppy514/jproxy/blob/main/docker/docker-run.sh)
| 参数名 | 默认值 | 说明 |
| :--------------------------: | :---------------: | :----------------------------------------------------------: |
| PUID | 0 | 用户 ID |
| PGID | 0 | 组 ID |
| TZ | Asia/Shanghai | 时区 |
| JAVA_OPTS | -Xms512m -Xmx512m | JVM 运行参数 |
| CACHE_EXPIRES | 4320 | 缓存过期时间(分钟) |
| TOKEN_EXPIRES | 10080 | 登录过期时间(分钟) |
| SYNC_INTERVAL | 3 | 同步间隔(分钟) |
| RENAME_FILE | true | 文件重命名开关true/false |
| MIN_COUNT | 6 | 当结果数量少于该值时,会追加主语言标题(去除季数和集数)搜索 |
| INDEXER_RESULT_CACHE_EXPIRES | 15 | 索引器结果缓存过期时间(分钟) |
如需设置代理,可在 `JAVA_OPTS` 添加对应的代理参数
- HTTP 代理
`-Xms512m -Xmx512m -Dhttp.proxyHost=192.168.6.2 -Dhttp.proxyPort=12345`
- SOCKS 代理
`-Xms512m -Xmx512m -DsocksProxyHost=192.168.6.2 -DsocksProxyPort=54321`
### Windows
1. [下载 jdk17](https://kutt.lckp.top/yrnerc),安装并配置好环境变量
2. [下载 windows.zip](https://github.com/LuckyPuppy514/jproxy/releases) ,解压到安装目录
| 文件名 | 说明 | 备注 |
| :----------------: | :-----------: | :--------------: |
| startup.bat | 启动脚本 | - |
| shutdown.bat | 关闭脚本 | - |
| startup-daemon.bat | 后台启动脚本 | 隐藏窗口后台运行 |
| database | 数据库 | 升级请保留数据库 |
| config | 配置文件 | - |
| jproxy.jar | 可执行 jar 包 | - |
## ☃️ 基础配置
- 地址:`http://127.0.0.1:8117/login`
- 用户:`jproxy`
- 密码:`jproxy@2023`
![20230405202207](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-05/20230405202207.webp)
① 在 `系统配置 - 基础配置` 中填写 `Sonarr 服务地址``API 密钥`,以及 `索引器地址`Jackett / Prowlarr 二选一即可)
![20230404182207](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-04/20230404182207.webp)
![20230414101622](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-14/20230414101622.webp)
💡 保存后,正常应如下图所示 ✅ ,否则请检查输入和网络连通性
![20230414101718](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-14/20230414101718.webp)
② 首次使用,建议手动同步一次 `剧集标题``剧集规则`(后续会自动同步)
![20230404172313](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-04/20230404172313.webp)
![20230404172225](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-04/20230404172225.webp)
③ 修改索引器地址的 `IP``端口号` 为 JProxy 的 `IP``端口号`,并追加相应路径
Jackett
`http://192.168.6.15:9117/api/v2.0/......` ➡️ `http://192.168.6.14:8117/sonarr/jackett/api/v2.0/......`
![20230404172541](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-04-04/20230404172541.webp)
Prowlarr
`http://192.168.6.15:9696` ➡️ `http://192.168.6.14:8117/sonarr/prowlarr`
![20230806204236](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-08-06/20230806204236.webp)
并关闭本地安全认证
![20230806210826](https://github.com/LuckyPuppy514/image/raw/main/2023/2023-08-06/20230806210826.webp)
[🎗️ 进阶配置和使用说明请查看 Wiki](https://github.com/LuckyPuppy514/jproxy/wiki)
## 😘 如何贡献
非常欢迎你的加入![提一个 Issue](https://github.com/LuckyPuppy514/jproxy/issues/new/choose) 或者提交一个 Pull Request
- [arco-design-pro-vue](https://github.com/arco-design/arco-design-pro-vue)
- [spring-boot](https://github.com/spring-projects/spring-boot)
- [sqlite](https://github.com/sqlite/sqlite)
- [liquibase](https://github.com/liquibase/liquibase)
- [mybatis](https://github.com/mybatis/mybatis-3)
- [mybatis-plus](https://github.com/baomidou/mybatis-plus)
- [caffeine](https://github.com/ben-manes/caffeine)
- [knife4j](https://github.com/xiaoymin/knife4j)
- [charon](https://github.com/mkopylec/charon-spring-boot-starter)
- [jib](https://github.com/GoogleContainerTools/jib)
## 👏 相关仓库
- [Sonarr](https://github.com/Sonarr/Sonarr)
- [Radarr](https://github.com/radarr/radarr)
- [Jackett](https://github.com/Jackett/Jackett)
- [Prowlarr](https://github.com/Prowlarr/Prowlarr)
- [qBittorrent](https://github.com/qbittorrent/qBittorrent)
## 🃏 使用许可
[MIT](https://github.com/LuckyPuppy514/jproxy/blob/main/LICENSE) © LuckyPuppy514

244
changelog.en_US.md Normal file
View File

@ -0,0 +1,244 @@
[中文](https://github.com/LuckyPuppy514/jproxy/blob/main/changelog.md) | English
# Change Logs
## v3.4.5 2024-06-18
👻 Fix the issue of NullPointerException
## v3.4.4 2024-06-04
👻 Fix the issue of nested path while renaming
## v3.4.3 2024-02-20
🚀 Optimize radarr title matching logic
## v3.4.2 2024-02-19
👻 Fix bug of rename
## v3.4.1 2023-10-13
🚀 Append other info while rename file
## v3.4.0 2023-08-09
👻 Fix the problem that appending the main language title actually appends the alternate language title
## v3.3.9 2023-08-08
🆕 Caching indexer query result
## v3.3.8 2023-08-06
👻 Fix bug of pagination
## v3.3.7 2023-08-06
🚀 Optimize search logic
## v3.3.6 2023-08-06
🚀 Optimize matching logic
## v3.3.5 2023-08-06
🚀 Optimize language matching logic
## v3.3.4 2023-07-27
🆕 New parameter `min-count`append primary title (without season and episode number) to search while current result count less than this value
## v3.3.3 2023-07-17
🚀 Optimize title matching logic
## v3.3.2 2023-07-14
🚀 Optimize search logic
## v3.3.1 2023-07-08
🚀 Optimize import logic
## v3.3.0 2023-06-27
🚀 Optimize file rename logic
## v3.2.9 2023-06-22
🚀 Optimize file rename logic
## v3.2.8 2023-06-13
🚀 Optimize clean title logic
## v3.2.7 2023-06-04
👻 Fix bug of clean title
👻 Fix bug about TMDB sync
## v3.2.6 2023-06-03
🚀 Optimize title matching logic
## v3.2.5 2023-06-03
🚀 Optimize title matching logic
## v3.2.4 2023-05-29
👏 Merge pull request [#41](https://github.com/LuckyPuppy514/jproxy/pull/41) from DDS-Derek/main
## v3.2.3 2023-05-28
🚀 Optimize downloader file rename logic
## v3.2.2 2023-05-23
🚀 Optimize search logic
## v3.2.1 2023-04-30
🆕 Add title at TMDB Menu
🚀 Optimize search logic
## v3.2.0 2023-04-30
🚀 Optimize clean title logic
## v3.1.9 2023-04-29
👻 Fix downloader rename bug
## v3.1.8 2023-04-17
🆕 Added version display and upgrade prompt
## v3.1.7 2023-04-16
🆕 Add file rename switch
## v3.1.6 2023-04-14
👻 Fix downloader rename bug
## v3.1.4 2023-04-14
🚀 Optimize downloader rename
## v3.1.3 2023-04-14
🚀 Optimize qBittorrent rename
🆕 Added Transmission rename
🚀 Optimize search of series without absolute number
## v3.1.2 2023-04-12
👏 Merge pull request [#27](https://github.com/LuckyPuppy514/jproxy/pull/27)
## v3.1.1 2023-04-12
🚀 Optimize qBittorrent rename
## v3.1.0 2023-04-11
🚮 Remove the proxy of qBittorrent (if you set the proxy in Sonarr according to the old version, please restore the settings)
🆕 Add qBittorrent rename function, support Sonarr and Radarr
## v3.0.9 2023-04-10
🆕 Switch cache database: Redis => Caffeine
🚀 Optimize title matching logic
## v3.0.8 2023-04-09
🆕 Add liquibase config
## v3.0.7 2023-04-09
🆕 Add package way
## v3.0.6 2023-04-08
🆕 Added the function to modify the TMDB title
## v3.0.5 2023-04-08
👻 Fix the downloaded bug
## v3.0.4 2023-04-08
👻 Fix BUG of append TMDB title to search
## v3.0.3 2023-04-07
🚀 Optimize title matching logic
## v3.0.2 2023-04-07
🚀 Optimize Radarr title matching logic
## v3.0.1 2023-04-07
🆕 Add a backup address for the rule to avoid the inability to synchronize the rules due to the inability to access github
🚀 Docker basic image changes to support ARM architecture
## v3.0.0 2023-04-06
🚨 Code refactoring, not compatible with v2 version
🆕 Separation of front and back ends, new WebUI
🆕 Logical reconstruction, the matching method is separated from the original single rule into multiple marking rules, which is more free and has a higher recognition rate
🆕 Automatically append the selected language title to optimize Sonarr search via TMDB
🆕 Add support for Radarr, automatically add the main language title to optimize the search, adapt to the new logic, format the results, and improve the recognition rate
🆕 The rule sharing is changed to Pull Request, and the rule download is changed to directly synchronize the rules under the `src/main/resources/rule` directory of this project
## v2.6.5 2023-01-17
🚀 Improve: torrent name format(reduce import error)
## v2.6.4 2022-09-21
👻 Fixed: Market-Search, sort bug of download count
🆕 New Function: enable modify username while change password
## v2.6.3 2022-09-02
👻 Fixed: replace WebClient with RestTemplate to solve request error sometime
## v2.6.2 2022-08-10
👻 Fixed: can not format season and ep or date in search key while series type is Standard or Daily
## v2.6.1 2022-08-07
👻 Fixed: part of BT/PT indexers can not download while use qBittorrent proxy
## v2.6.0 2022-08-05
🆕 New Function: qBittorrent Proxy
🆕 New Function: add search condition: remark
👻 Fixed: import wrong season while sonarr unrecognize the title of torrent
## v2.5.2 2022-08-01
🆕 New Function: Reachalbe test first when save proxy config
## v2.5.1 2022-07-31
🆕 Add README.md
👻 Fixed: docker build error at aarch64 by changing sqlite-jdbc version to: 3.39.2-SNAPSHOT
👻 Fixed: sync error
👻 Fixed: prowlarr error
## v2.5.0 2022-07-30
🆕 Web UI: Chinese or English
🆕 Proxy Config: Jackett / Prowlarr ip, port setting
🆕 Add Rule: Include search and result rule
🆕 Rule Manage: Search, edit, delete, share and import or export
🆕 Rule Market: Search rules shared by others and download
🆕 Rule Test: Add title list and check the result after format

244
changelog.md Normal file
View File

@ -0,0 +1,244 @@
简体中文 | [English](https://github.com/LuckyPuppy514/jproxy/blob/main/changelog.en_US.md)
# 变更日志
## v3.4.5 2024-06-18
👻 修复空指针问题
## v3.4.4 2024-06-04
👻 修复重命名时路径嵌套的问题
## v3.4.3 2024-02-20
🚀 优化 Radarr 标题匹配逻辑
## v3.4.2 2024-02-19
👻 修复重命名问题
## v3.4.1 2023-10-13
🚀 文件重命名追加其他信息
## v3.4.0 2023-08-09
👻 修复追加主语言标题实际上追加的是备用语言标题的问题
## v3.3.9 2023-08-08
🆕 缓存索引器查询结果
## v3.3.8 2023-08-06
👻 处理分页异常问题
## v3.3.7 2023-08-06
🚀 优化查询逻辑
## v3.3.6 2023-08-06
🚀 优化匹配逻辑
## v3.3.5 2023-08-06
🚀 优化语言匹配逻辑
## v3.3.4 2023-07-27
🆕 新增配置参数 `min-count`:当结果数量少于该值时,会追加主标题(去除季数和集数)搜索
## v3.3.3 2023-07-17
🚀 优化标题匹配逻辑
## v3.3.2 2023-07-14
🚀 优化查询逻辑
## v3.3.1 2023-07-08
🚀 优化导入逻辑
## v3.3.0 2023-06-27
🚀 优化文件重命名逻辑
## v3.2.9 2023-06-22
🚀 优化文件重命名逻辑
## v3.2.8 2023-06-13
🚀 优化净标题逻辑
## v3.2.7 2023-06-04
👻 修复净标题 BUG
👻 修复 TMDB 同步 BUG
## v3.2.6 2023-06-03
🚀 优化标题匹配逻辑
## v3.2.5 2023-06-03
🚀 优化标题匹配逻辑
## v3.2.4 2023-05-29
👏 合并来自 DDS-Derek/main 的 PR [#41](https://github.com/LuckyPuppy514/jproxy/pull/41)
## v3.2.3 2023-05-28
🚀 优化下载器文件重命名逻辑
## v3.2.2 2023-05-23
🚀 优化查询逻辑
## v3.2.1 2023-04-30
🆕 TMDB 新增标题新增功能
🚀 优化查询逻辑
## v3.2.0 2023-04-30
🚀 优化净标题逻辑
## v3.1.9 2023-04-29
👻 修复下载器重命名BUG
## v3.1.8 2023-04-17
🆕 新增版本号展示及版本升级提示
## v3.1.7 2023-04-16
🆕 新增文件重命名开关
## v3.1.6 2023-04-14
👻 修复下载器重命名BUG
## v3.1.4 2023-04-14
🚀 优化下载器重命名
## v3.1.3 2023-04-14
🚀 优化 qBittorrent 重命名
🆕 新增 Transmission 重命名
🚀 优化无绝对集数剧集查询
## v3.1.2 2023-04-12
👏 合并 PR [#27](https://github.com/LuckyPuppy514/jproxy/pull/27)
## v3.1.1 2023-04-12
🚀 优化 qBittorrent 重命名
## v3.1.0 2023-04-11
🚮 移除 qBittorrent 代理(如按旧版在 Sonarr 设置了代理,请还原设置)
🆕 新增 qBittorrent 重命名,支持 Sonarr 和 Radarr
## v3.0.9 2023-04-10
🆕 切换缓存数据库Redis => Caffeine
🚀 优化标题匹配逻辑
## v3.0.8 2023-04-09
🆕 更新 liquibase 配置
## v3.0.7 2023-04-09
🆕 打包方式更新
## v3.0.6 2023-04-08
🆕 新增修改 TMDB 标题的功能
## v3.0.5 2023-04-08
👻 修复部分数据无法下载的问题
## v3.0.4 2023-04-08
👻 修复追加 TMDB 标题查询 BUG
## v3.0.3 2023-04-07
🚀 优化标题匹配逻辑
## v3.0.2 2023-04-07
🚀 优化 Radarr 标题匹配逻辑
## v3.0.1 2023-04-07
🆕 新增规则备用地址,避免因无法访问 github 导致无法同步规则
2. Docker 底层镜像变更,以支持 ARM 架构
## v3.0.0 2023-04-06
🚨 代码重构,不兼容 v2 版本
🆕 前后端分离,全新 WebUI
🆕 格式化逻辑重构,匹配方式由原来的单一规则,分离成多个标记规则,更自由且识别率更高
🆕 引入 TMDB自动追加所选语言标题优化 Sonarr 查询
🆕 添加对 Radarr 的支持,自动追加主语言标题优化查询,同时适配新逻辑,格式化结果,提升识别率
🆕 规则分享改成 Pull Request 形式,规则下载改为直接同步本仓库 `src/main/resources/rule` 目录下规则
## v2.6.5 2023-01-17
🚀 提升: 种子名称格式化(减少因为种子名称不规范导致不自动导入或导入错误季的问题)
## v2.6.4 2022-09-21
👻 修复:市场-查询,下载次数排序问题
🆕 新增:修改密码时可修改用户名
## v2.6.3 2022-09-02
👻 修复:使用 RestTemplate 代替 WebClient 以解决偶尔请求异常的问题
## v2.6.2 2022-08-10
👻 修复当系列类型为Standard, Daily 时,查询关键字季集,日期等参数无法格式化的问题
## v2.6.1 2022-08-07
👻 修复:部分 BT/PT 站当使用 qBittorrent 代理时无法下载的问题
## v2.6.0 2022-08-05
🆕 新增 qBittorrent 代理
🆕 新增搜索条件:备注
👻 修复:当 sonarr 无法识别种子标题时,导入错误季的问题
## v2.5.2 2022-08-01
🆕 新功能:保存代理配置时,先进行连通性测试
## v2.5.1 2022-07-30
🆕 更新 README.md
👻 变更sqlite-jdbc 版本到3.39.2-SNAPSHOT用于修复在 aarch64 机器上 docker build 出错的问题
👻 修复:同步出错的问题
👻 修复prowlarr 错误
## v2.5.0 2022-07-30
🆕 简单界面:支持中文和英文
🆕 代理配置:配置 Jackett / Prowlarr 的地址,端口等信息
🆕 新增规则:包括查询规则和结果规则
🆕 规则管理:查询,编辑,删除,分享,以及导入导出等
🆕 规则市场:可以查询大家分享的规则,并下载
🆕 用例测试:可以批量添加标题进行测试,查看格式化后的效果

25
docker/Dockerfile Normal file
View File

@ -0,0 +1,25 @@
FROM eclipse-temurin:17-jre
ENV TERM="xterm" \
TZ=Asia/Shanghai \
PUID=0 \
PGID=0 \
UMASK=022 \
JAVA_OPTS="-Xms512m -Xmx512m"
RUN set -ex && \
export DEBIAN_FRONTEND=noninteractive && \
apt update -y && \
apt install -y gosu dumb-init && \
apt autoremove -y && \
apt clean && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY ./dependencies/ ./
COPY ./spring-boot-loader/ ./
COPY ./snapshot-dependencies/ ./
COPY ./application/ ./
COPY --chmod=755 ./docker/entrypoint.sh ./entrypoint.sh
ENTRYPOINT [ "/app/entrypoint.sh" ]

15
docker/docker-compose.yml Normal file
View File

@ -0,0 +1,15 @@
version: '3.0'
services:
jproxy:
image: luckypuppy514/jproxy:latest
container_name: jproxy
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Shanghai
- JAVA_OPTS=-Xms512m -Xmx512m
ports:
- 8117:8117
volumes:
- /docker/jproxy/database:/app/database

10
docker/docker-run.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/bash
docker run --name jproxy \
--restart unless-stopped \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Asia/Shanghai \
-e JAVA_OPTS="-Xms512m -Xmx512m" \
-p 8117:8117 \
-v /docker/jproxy/database:/app/database \
-d luckypuppy514/jproxy:latest

23
docker/entrypoint.sh Normal file
View File

@ -0,0 +1,23 @@
#!/bin/bash
# 初始化持久化目录
CONFIG_PATH=/app/config
DATABASE_PATH=/app/database
if [ ! -d "${CONFIG_PATH}" ]; then
mkdir -p ${CONFIG_PATH}
fi
if [ ! -d "${DATABASE_PATH}" ]; then
mkdir -p ${DATABASE_PATH}
fi
# 初始化持久化配置
cp -n /app/BOOT-INF/classes/application.yml ${CONFIG_PATH}/application.yml
cp -n /app/BOOT-INF/classes/application-prod.yml ${CONFIG_PATH}/application-prod.yml
cp -n /app/BOOT-INF/classes/database/jproxy.db ${DATABASE_PATH}/jproxy.db
# 设置权限
chown -R ${PUID}:${PGID} /app/
umask ${UMASK}
# 启动应用
exec gosu ${PUID}:${PGID} dumb-init java ${JAVA_OPTS} -Dfile.encoding=utf-8 -Dspring.config.location=${CONFIG_PATH}/ org.springframework.boot.loader.JarLauncher

316
mvnw vendored Normal file
View File

@ -0,0 +1,316 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

188
mvnw.cmd vendored Normal file
View File

@ -0,0 +1,188 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

187
pom.xml Normal file
View File

@ -0,0 +1,187 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>
<!-- 项目信息 -->
<groupId>com.lckp</groupId>
<artifactId>jproxy</artifactId>
<version>3.4.5</version>
<name>JProxy</name>
<description>介于 Sonarr/Radarr 和 Jackett/Prowlarr 之间的代理,主要用于优化查询和提升识别率</description>
<!-- 依赖版本 -->
<properties>
<java.version>17</java.version>
<java-jwt.version>4.3.0</java-jwt.version>
<knife4j.version>4.0.0</knife4j.version>
<fastjson2.version>2.0.24</fastjson2.version>
<mybatis-plus.version>3.5.3</mybatis-plus.version>
<mybatis-plus-generator.version>3.5.3</mybatis-plus-generator.version>
<dom4j.version>2.1.4</dom4j.version>
</properties>
<!-- 依赖 -->
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- token -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt.version}</version>
</dependency>
<!-- 接口文档 -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-generator.version}</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!-- sqlite 数据库驱动 -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
</dependency>
<!-- 数据库版本控制 -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
<!-- 缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<!-- xml 处理 -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>${dom4j.version}</version>
</dependency>
</dependencies>
<build>
<!-- 打包文件名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.lckp.jproxy.Application</mainClass>
<layers>
<enabled>true</enabled>
</layers>
<!-- 去除生产不需要的依赖 -->
<excludeDevtools>true</excludeDevtools>
<excludes>
<exclude>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-ui</artifactId>
</exclude>
<exclude>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
</exclude>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
<exclude>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</exclude>
<exclude>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<!-- 配置 -->
<profiles>
<!-- 开发环境 -->
<profile>
<id>dev</id>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<!-- 生产环境 -->
<profile>
<id>prod</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</profiles>
</project>

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,29 @@
package com.lckp.jproxy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
*
* <p>
* 主类
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-04
*/
@SpringBootApplication
@MapperScan("com.lckp.jproxy.mapper")
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@ServletComponentScan
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,59 @@
package com.lckp.jproxy;
import java.util.Collections;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
/**
*
* <p>
* 代码生成器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-04
*/
public class CodeGenerator {
public static void main(String[] args) {
// 数据库
String tableName = "system_user";
String dbUsername = "";
String dbPassword = "";
String dbUrl = "jdbc:sqlite:target\\classes\\database\\jproxy.db";
// 作者
String author = "LuckyPuppy514";
// 包名
String packageName = CodeGenerator.class.getPackageName();
// 输出目录
String outputDir = System.getProperty("user.dir") + "/src/main/java/";
String xmlOutputDir = System.getProperty("user.dir") + "/src/main/resources/mapper";
FastAutoGenerator.create(dbUrl, dbUsername, dbPassword)
.globalConfig(builder -> builder.author(author)
.disableOpenDir()
.enableSpringdoc()
.outputDir(outputDir)
)
.packageConfig(builder -> builder.parent(packageName)
.pathInfo(Collections.singletonMap(OutputFile.xml, xmlOutputDir))
)
.strategyConfig(builder -> builder.addInclude(tableName)
.controllerBuilder().enableRestStyle()
.entityBuilder().enableLombok()
)
.templateEngine(new FreemarkerTemplateEngine())
.templateConfig(builder -> builder.disable()
.entity("/templates/entity.java")
.service("/templates/service.java")
.serviceImpl("/templates/serviceImpl.java")
.mapper("/templates/mapper.java")
.xml("/templates/mapper.xml")
.controller("/templates/controller.java")
)
.execute();
}
}

View File

@ -0,0 +1,66 @@
package com.lckp.jproxy.aspect;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* sqlite 读写锁
* </p>
*
* @author LuckyPuppy514
* @date 2023-04-02
*/
@Slf4j
@Aspect
@Component
public class SqliteAspect {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
@Pointcut("execution(* com.baomidou.mybatisplus.extension.service..*.*(..))"
+ " || execution(* com.lckp.jproxy.mapper..*.*(..))")
public void pointCut() {
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
String name = pjp.getSignature().getName();
log.trace("切入点:{}", pjp.getTarget().getClass().getName());
if (name.contains("save") || name.contains("update") || name.contains("remove")) {
log.trace("{} 开始获取写锁", name);
readWriteLock.writeLock().lock();
try {
log.trace("{} 获得了写锁", name);
return pjp.proceed();
} catch (Exception e) {
log.error("{} 写保护出错:{}", name, e.getMessage());
throw e;
} finally {
readWriteLock.writeLock().unlock();
log.trace("{} 释放了写锁", name);
}
} else {
log.trace("{} 开始获取读锁", name);
readWriteLock.readLock().lock();
try {
log.trace("{} 获得了读锁", name);
return pjp.proceed();
} catch (Exception e) {
log.error("{} 读保护出错:{}", name, e.getMessage());
throw e;
} finally {
readWriteLock.readLock().unlock();
log.trace("{} 释放了读锁", name);
}
}
}
}

View File

@ -0,0 +1,76 @@
package com.lckp.jproxy.config;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
/**
* <p>
* 缓存配置
* </p>
*
* @author LuckyPuppy514
* @date 2023-04-10
*/
@Configuration
public class CacheConfig {
@Value("${time.cache-expires}")
private long cacheExpires;
@Value("${time.sync-interval}")
private long syncInterval;
@Value("${time.indexer-result-cache-expires}")
private long indexerResultCacheExpires;
@Bean(name = "syncIntervalCache")
Cache<String, Integer> syncIntervalCache() {
return Caffeine.newBuilder().expireAfter(new Expiry<String, Object>() {
public long expireAfterCreate(String key, Object graph, long currentTime) {
return TimeUnit.MINUTES.toNanos(syncInterval);
}
public long expireAfterUpdate(String key, Object graph, long currentTime, long currentDuration) {
return currentDuration;
}
public long expireAfterRead(String key, Object graph, long currentTime, long currentDuration) {
return currentDuration;
}
}).initialCapacity(3).maximumSize(10).build();
}
@Bean(name = "indexerResultCache")
Cache<String, String> indexerResultCache() {
return Caffeine.newBuilder().expireAfter(new Expiry<String, Object>() {
public long expireAfterCreate(String key, Object graph, long currentTime) {
return TimeUnit.MINUTES.toNanos(indexerResultCacheExpires);
}
public long expireAfterUpdate(String key, Object graph, long currentTime, long currentDuration) {
return currentDuration;
}
public long expireAfterRead(String key, Object graph, long currentTime, long currentDuration) {
return currentDuration;
}
}).initialCapacity(100).maximumSize(1000).build();
}
@Bean
CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(Caffeine.newBuilder().expireAfterAccess(cacheExpires, TimeUnit.MINUTES)
.initialCapacity(100).maximumSize(1000));
return cacheManager;
}
}

View File

@ -0,0 +1,29 @@
package com.lckp.jproxy.config;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
/**
* <p>
* 错误页面配置
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-30
*/
@Configuration
public class ErrorPageConfig implements ErrorPageRegistrar {
/**
* @param registry
* @see org.springframework.boot.web.server.ErrorPageRegistrar#registerErrorPages(org.springframework.boot.web.server.ErrorPageRegistry)
*/
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/index.html"));
}
}

View File

@ -0,0 +1,74 @@
package com.lckp.jproxy.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.lckp.jproxy.constant.Common;
import com.lckp.jproxy.filter.RadarrJackettFilter;
import com.lckp.jproxy.filter.RadarrProwlarrFilter;
import com.lckp.jproxy.filter.SonarrJackettFilter;
import com.lckp.jproxy.filter.SonarrProwlarrFilter;
import com.lckp.jproxy.service.IRadarrJackettService;
import com.lckp.jproxy.service.IRadarrProwlarrService;
import com.lckp.jproxy.service.ISonarrJackettService;
import com.lckp.jproxy.service.ISonarrProwlarrService;
import lombok.RequiredArgsConstructor;
/**
* <p>
* 过滤器配置
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-07
*/
@Configuration
@RequiredArgsConstructor
public class FilterConfig {
private final ISonarrJackettService sonarrJackettService;
private final ISonarrProwlarrService sonarrProwlarrService;
private final IRadarrJackettService radarrJackettService;
private final IRadarrProwlarrService radarrProwlarrService;
@Bean
FilterRegistrationBean<SonarrJackettFilter> sonarrJackettFilter() {
FilterRegistrationBean<SonarrJackettFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new SonarrJackettFilter(sonarrJackettService));
bean.setOrder(Integer.MIN_VALUE);
bean.addUrlPatterns(Common.FILTER_SONARR_JACKETT_PATH);
return bean;
}
@Bean
FilterRegistrationBean<SonarrProwlarrFilter> sonarrProwlarrFilter() {
FilterRegistrationBean<SonarrProwlarrFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new SonarrProwlarrFilter(sonarrProwlarrService));
bean.setOrder(Integer.MIN_VALUE);
bean.addUrlPatterns(Common.FILTER_SONARR_PROWLARR_PATH);
return bean;
}
@Bean
FilterRegistrationBean<RadarrJackettFilter> radarrJackettFilter() {
FilterRegistrationBean<RadarrJackettFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new RadarrJackettFilter(radarrJackettService));
bean.setOrder(Integer.MIN_VALUE);
bean.addUrlPatterns(Common.FILTER_RADARR_JACKETT_PATH);
return bean;
}
@Bean
FilterRegistrationBean<RadarrProwlarrFilter> radarrProwlarrFilter() {
FilterRegistrationBean<RadarrProwlarrFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new RadarrProwlarrFilter(radarrProwlarrService));
bean.setOrder(Integer.MIN_VALUE);
bean.addUrlPatterns(Common.FILTER_RADARR_PROWLARR_PATH);
return bean;
}
}

View File

@ -0,0 +1,40 @@
package com.lckp.jproxy.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.lckp.jproxy.constant.Common;
import com.lckp.jproxy.interceptor.LoginInterceptor;
import com.lckp.jproxy.service.ISystemUserService;
import lombok.RequiredArgsConstructor;
/**
* <p>
* 拦截器配置
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-23
*/
@Configuration
@RequiredArgsConstructor
public class InterceptorConfig implements WebMvcConfigurer {
private final ISystemUserService systemUserService;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor(systemUserService))
.addPathPatterns(Common.INTERCEPTOR_ALL_PATH)
.excludePathPatterns(Common.INTERCEPTOR_SONARR_API_PATH)
.excludePathPatterns(Common.INTERCEPTOR_RADARR_API_PATH)
.excludePathPatterns(Common.INTERCEPTOR_LOGIN_API_PATH)
.excludePathPatterns(Common.INTERCEPTOR_LOGIN_PAGE_PATH)
.excludePathPatterns(Common.INTERCEPTOR_INDEX_PAGE_PATH)
.excludePathPatterns(Common.INTERCEPTOR_STATIC_PATH)
.excludePathPatterns(Common.INTERCEPTOR_KNFIE4J_PATHS)
.excludePathPatterns(Common.INTERCEPTOR_OTHER_PATHS);
}
}

View File

@ -0,0 +1,39 @@
package com.lckp.jproxy.config;
import java.util.Locale;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.LocaleResolver;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
/**
* <p>
* 本地化配置
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-25
*/
@Configuration
public class LocaleResolverConfig implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
Locale locale = httpServletRequest.getLocale();
locale = locale == null ? Locale.getDefault() : locale;
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
// do nothing
}
@Bean
LocaleResolver localeResolver() {
return new LocaleResolverConfig();
}
}

View File

@ -0,0 +1,60 @@
package com.lckp.jproxy.config;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.lckp.jproxy.constant.TableField;
/**
*
* <p>
* Mybatis-Plus 配置类
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-04
*/
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfig implements MetaObjectHandler {
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Bean
MybatisPlusInterceptor mybatisPlusInterceptor() {
PaginationInnerInterceptor pagination = new PaginationInnerInterceptor(DbType.SQLITE);
pagination.setOverflow(true);
pagination.setMaxLimit(1000L);
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(pagination);
return interceptor;
}
/**
* @param metaObject
* @see com.baomidou.mybatisplus.core.handlers.MetaObjectHandler#insertFill(org.apache.ibatis.reflection.MetaObject)
*/
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName(TableField.CREATE_TIME_CAMEL, dateFormat.format(new Date()), metaObject);
this.setFieldValByName(TableField.UPDATE_TIME_CAMEL, dateFormat.format(new Date()), metaObject);
}
/**
* @param metaObject
* @see com.baomidou.mybatisplus.core.handlers.MetaObjectHandler#updateFill(org.apache.ibatis.reflection.MetaObject)
*/
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName(TableField.UPDATE_TIME_CAMEL, dateFormat.format(new Date()), metaObject);
}
}

View File

@ -0,0 +1,48 @@
package com.lckp.jproxy.config;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.ExtractingResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
/**
* <p>
* RestTemplate 配置
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-25
*/
@Configuration
public class RestTemplateConfig {
@Bean
RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL));
messageConverters.add(converter);
return restTemplateBuilder.setReadTimeout(Duration.ofMinutes(15))
.setConnectTimeout(Duration.ofMinutes(15))
.additionalMessageConverters(messageConverters)
.errorHandler(new ExtractingResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return !response.getStatusCode().is2xxSuccessful()
&& !HttpStatusCode.valueOf(409).equals(response.getStatusCode());
}
}).build();
}
}

View File

@ -0,0 +1,36 @@
package com.lckp.jproxy.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
/**
*
* <p>
* Swagger 配置类
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-04
*/
@Configuration
public class SwaggerConfig {
@Value("${project.name}")
private String projectName;
@Value("${project.version}")
private String projectVersion;
@Value("${project.description}")
private String projectDescription;
@Bean
OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info().title(projectName).version(projectVersion).description(projectDescription));
}
}

View File

@ -0,0 +1,168 @@
package com.lckp.jproxy.constant;
/**
* <p>
* 接口字段
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-18
*/
public class ApiField {
ApiField() {
}
public static final String INDEXER_SEARCH_KEY = "q";
public static final String INDEXER_SEARCH_TYPE = "t";
public static final String INDEXER_SEASON_NUMBER = "season";
public static final String INDEXER_EPISODE_NUMBER = "ep";
public static final String INDEXER_LIMIT = "limit";
public static final String INDEXER_OFFSET = "offset";
public static final String INDEXER_CHANNEL = "channel";
public static final String INDEXER_ITEM = "item";
public static final String INDEXER_TITLE = "title";
public static final String INDEXER_DESCRIPTION = "description";
public static final String INDEXER_LINK = "link";
public static final String SONARR_APIKEY = "apikey";
public static final String SONARR_ID = "id";
public static final String SONARR_SERIES_ID = "seriesId";
public static final String SONARR_TVDB_ID = "tvdbId";
public static final String SONARR_TITLE = "title";
public static final String SONARR_TITLE_SLUG = "titleSlug";
public static final String SONARR_SERIES_TYPE = "seriesType";
public static final String SONARR_STATUS = "status";
public static final String SONARR_MONITORED = "monitored";
public static final String SONARR_ALTERNATE_TITLES = "alternateTitles";
public static final String SONARR_SCENE_SEASON_NUMBER = "sceneSeasonNumber";
public static final String SONARR_DOWNLOAD_ID = "downloadId";
public static final String SONARR_EPISODE = "episode";
public static final String SONARR_SEASON_NUMBER = "seasonNumber";
public static final String SONARR_EPISODE_NUMBER = "episodeNumber";
public static final String SONARR_LANGUAGE = "language";
public static final String SONARR_QUALITY = "quality";
public static final String SONARR_NAME = "name";
public static final String SONARR_RECORDS = "records";
public static final String SONARR_SOURCES_TITLE = "sourceTitle";
public static final String SONARR_DATA = "data";
public static final String SONARR_DATE = "date";
public static final String SONARR_TZ = "UTC";
public static final String SONARR_TORRENT_INFO_HASH = "torrentInfoHash";
public static final String SONARR_DOWNLOAD_CLIENT = "downloadClient";
public static final String RADARR_APIKEY = "apikey";
public static final String RADARR_TMDB_ID = "tmdbId";
public static final String RADARR_ID = "id";
public static final String RADARR_MOVIE_ID = "movieId";
public static final String RADARR_DOWNLOAD_ID = "downloadId";
public static final String RADARR_LANGUAGES = "languages";
public static final String RADARR_QUALITY = "quality";
public static final String RADARR_NAME = "name";
public static final String RADARR_RECORDS = "records";
public static final String RADARR_SOURCES_TITLE = "sourceTitle";
public static final String RADARR_DATA = "data";
public static final String RADARR_DATE = "date";
public static final String RADARR_TZ = "UTC";
public static final String RADARR_TORRENT_INFO_HASH = "torrentInfoHash";
public static final String RADARR_TITLE = "title";
public static final String RADARR_CLEAN_TITLE = "cleanTitle";
public static final String RADARR_PATH = "path";
public static final String RADARR_ORIGINAL_TITLE = "originalTitle";
public static final String RADARR_ALTERNATE_TITLES = "alternateTitles";
public static final String RADARR_MONITORED = "monitored";
public static final String RADARR_YEAR = "year";
public static final String RADARR_DOWNLOAD_CLIENT = "downloadClient";
public static final String TMDB_API_KEY = "api_key";
public static final String TMDB_TVDB_ID = "tvdb_id";
public static final String TMDB_ID = "id";
public static final String TMDB_LANGUAGE = "language";
public static final String TMDB_TV_RESULTS = "tv_results";
public static final String TMDB_EXTERNAL_SOURCE = "external_source";
public static final String TMDB_NAME = "name";
public static final String QBITTORRENT_HASH = "hash";
public static final String QBITTORRENT_NAME = "name";
public static final String QBITTORRENT_USERNAME = "username";
public static final String QBITTORRENT_PASSWORD = "password";
public static final String QBITTORRENT_OLD_PATH = "oldPath";
public static final String QBITTORRENT_NEW_PATH = "newPath";
public static final String TRANSMISSION_METHOD = "method";
public static final String TRANSMISSION_ARGUMENTS = "arguments";
public static final String TRANSMISSION_SESSION_ID = "X-Transmission-Session-Id";
public static final String TRANSMISSION_TORRENTS = "torrents";
public static final String TRANSMISSION_NAME = "name";
public static final String GITHUB_TAG_NAME = "tag_name";
}

View File

@ -0,0 +1,32 @@
package com.lckp.jproxy.constant;
/**
* <p>
* 缓存名称
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-16
*/
public class CacheName {
CacheName() {
}
// 注解
public static final String SYSTEM_CONFIG = "system_config";
public static final String SONARR_SEARCH_TITLE = "sonarr_search_title";
public static final String INDEXER_SEARCH_OFFSET = "indexer_search_offset";
public static final String SONARR_RULE = "sonarr_rule";
public static final String SONARR_RESULT_TITLE = "sonarr_result_title";
public static final String RADARR_SEARCH_TITLE = "radarr_search_title";
public static final String RADARR_RULE = "radarr_rule";
public static final String RADARR_RESULT_TITLE = "radarr_result_title";
// Bean
public static final String SONARR_TITLE_SYNC_INTERVAL = "sonarr_title_sync_interval";
public static final String TMDB_TITLE_SYNC_INTERVAL = "tmdb_title_sync_interval";
public static final String RADARR_TITLE_SYNC_INTERVAL = "radarr_title_sync_interval";
public static final String TOKEN_SECRET = "token_secret";
public static final String TOKEN_BLACK_LIST = "token_black_list";
public static final String INDEXER_RESULT = "indexer_result";
}

View File

@ -0,0 +1,55 @@
package com.lckp.jproxy.constant;
/**
* <p>
* 通用常量
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
public class Common {
Common() {
}
// 登陆拦截器路径
public static final String INTERCEPTOR_ALL_PATH = "/**";
public static final String INTERCEPTOR_SONARR_API_PATH = "/sonarr/**";
public static final String INTERCEPTOR_RADARR_API_PATH = "/radarr/**";
public static final String INTERCEPTOR_LOGIN_API_PATH = "/api/system/user/login";
public static final String INTERCEPTOR_LOGIN_PAGE_PATH = "/login";
public static final String INTERCEPTOR_INDEX_PAGE_PATH = "/index.html";
public static final String INTERCEPTOR_STATIC_PATH = "/assets/**";
public static final String[] INTERCEPTOR_KNFIE4J_PATHS = { "/doc.html", "/webjars/**",
"/v3/api-docs/**" };
public static final String[] INTERCEPTOR_OTHER_PATHS = { "/error", "/*.json", "/", "/favicon.ico",
"/system/**" };
// Charon
public static final String CHARON_ALL_PATH = "/.*";
public static final String CHARON_IN_PATH = "/(?<path>.*)";
public static final String CHARON_OUT_PATH = "/<path>";
public static final String CHARON_JACKETT_PATH = "/(sonarr|radarr)/jackett";
public static final String CHARON_PROWLARR_PATH = "/(sonarr|radarr)/prowlarr";
public static final String CHARON_QBITTORRENT_PATH = "/(sonarr|radarr)/qbittorrent";
public static final String CHARON_TRANSMISSION_PATH = "/(sonarr|radarr)/transmission";
// Filter
public static final String FILTER_SONARR_JACKETT_PATH = "/sonarr/jackett/*";
public static final String FILTER_SONARR_PROWLARR_PATH = "/sonarr/prowlarr/*";
public static final String FILTER_RADARR_JACKETT_PATH = "/radarr/jackett/*";
public static final String FILTER_RADARR_PROWLARR_PATH = "/radarr/prowlarr/*";
public static final String FILTER_SONARR_QBITTORRENT_PATH = "/sonarr/qbittorrent/api/v2/torrents/info";
public static final String FILTER_SONARR_TRANSMISSION_PATH = "/sonarr/transmission/*";
// 批量保存大小
public static final int BATCH_SIZE = 200;
// 规则同步作者
public static final String RULE_SYNC_AUTHORS_ALL = "ALL";
// 标题主规则 ID
public static final String MOST_IMPORTANT_TITLE_RULE_ID = "00000000000000000000000000000000";
// 视频文件扩展名正则表达式
public static final String VIDEO_AND_SUBTITLE_EXTENSION_REGEX = "(\\.(mp4|avi|wmv|flv|mov|mkv|webm|mpg|mpeg|3gp|iso|ts|ass|srt|ssa|idx|sub))$";
// 字幕文件扩展名正则表达式
public static final String SUBTITLE_EXTENSION_REGEX = "(\\.(ass|srt|ssa|idx|sub))$";
// 时间格式
public static final String DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
}

View File

@ -0,0 +1,23 @@
package com.lckp.jproxy.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* <p>
* 下载器
* </p>
*
* @author LuckyPuppy514
* @date 2023-04-13
*/
@Getter
@AllArgsConstructor
public enum Downloader {
QBITTORRENT("qBittorrent"),
TRANSMISSION("Transmission");
private final String name;
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.constant;
/**
* <p>
* 信息
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-25
*/
public class Messages {
Messages() {
}
public static final String LOGIN_WRONG_TOO_MANY_TIMES = "login.wrong.too.many.times";
public static final String LOGIN_WRONG_USER = "login.wrong.user";
public static final String TITLE_SYNC_TOO_OFTEN = "title.sync.too.often";
public static final String RULE_MODIFY_PRIMARY_FORBIDDEN = "rule.modify.primary.forbidden";
public static final String EXAMPLE_MATCH_TITLE_FAIL = "example.match.title.fail";
public static final String SYSTEM_CONFIG_INVALID_PREFIX = "system.config.invalid.";
public static final String DATABASE_BUSY = "database.busy";
}

View File

@ -0,0 +1,41 @@
package com.lckp.jproxy.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* <p>
* 监控状态
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-06
*/
@Getter
@AllArgsConstructor
public enum Monitored {
CONTINUING(0, false, "未监控"),
ENDED(1, true, "监控中");
private final Integer code;
private final boolean flag;
private final String description;
/**
*
* 根据 flag 获取对应的 Monitored
*
* @param flag
* @return Monitored
*/
public static Monitored getByFlag(boolean flag) {
if (flag) {
return ENDED;
}
return CONTINUING;
}
}

View File

@ -0,0 +1,42 @@
package com.lckp.jproxy.constant;
/**
* <p>
* 系统配置 Key
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
public class SystemConfigKey {
SystemConfigKey() {
}
// Sonarr
public static final String SONARR_URL = "sonarrUrl";
public static final String SONARR_APIKEY = "sonarrApikey";
public static final String SONARR_INDEXER_FORMAT = "sonarrIndexerFormat";
public static final String SONARR_LANGUAGE_1 = "sonarrLanguage1";
public static final String SONARR_LANGUAGE_2 = "sonarrLanguage2";
// Radarr
public static final String RADARR_URL = "radarrUrl";
public static final String RADARR_APIKEY = "radarrApikey";
public static final String RADARR_INDEXER_FORMAT = "radarrIndexerFormat";
// 索引器
public static final String JACKETT_URL = "jackettUrl";
public static final String PROWLARR_URL = "prowlarrUrl";
// 下载器
public static final String QBITTORRENT_URL = "qbittorrentUrl";
public static final String QBITTORRENT_USERNAME = "qbittorrentUsername";
public static final String QBITTORRENT_PASSWORD = "qbittorrentPassword";
public static final String TRANSMISSION_URL = "transmissionUrl";
public static final String TRANSMISSION_USERNAME = "transmissionUsername";
public static final String TRANSMISSION_PASSWORD = "transmissionPassword";
// TMDB
public static final String TMDB_URL = "tmdbUrl";
public static final String TMDB_APIKEY = "tmdbApikey";
// 净标题排除字符正则
public static final String CLEAN_TITLE_REGEX = "cleanTitleRegex";
// 规则同步作者
public static final String RULE_SYNC_AUTHORS = "ruleSyncAuthors";
}

View File

@ -0,0 +1,72 @@
package com.lckp.jproxy.constant;
/**
* <p>
* 表字段名
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-16
*/
public class TableField {
TableField() {
}
public static final String VALID_STATUS = "valid_status";
public static final String TMDB_ID = "tmdb_id";
public static final String TITLE = "title";
public static final String CLEAN_TITLE = "clean_title";
public static final String TVDB_ID = "tvdb_id";
public static final String SERIES_ID = "series_id";
public static final String MOVIE_ID = "movie_id";
public static final String PRIORITY = "priority";
public static final String KEY = "key";
public static final String TOKEN = "token";
public static final String OFFSET = "offset";
public static final String EXAMPLE = "example";
public static final String AUTHOR = "author";
public static final String REMARK = "remark";
public static final String MONITORED = "monitored";
public static final String SNO = "sno";
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
public static final String ID = "id";
public static final String ROLE = "role";
public static final String YEAR = "year";
public static final String ORIGINAL_TEXT = "original_text";
public static final String FORMAT_TEXT = "format_text";
public static final String REGEX = "regex";
public static final String REPLACEMENT = "replacement";
public static final String CREATE_TIME = "create_time";
public static final String UPDATE_TIME = "update_time";
public static final String CREATE_TIME_CAMEL = "createTime";
public static final String UPDATE_TIME_CAMEL = "updateTime";
}

View File

@ -0,0 +1,34 @@
package com.lckp.jproxy.constant;
/**
* <p>
* 规则标记
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-16
*/
public class Token {
Token() {
}
public static final String REGEX = "\\{([^}]+)\\}";
public static final String TITLE = "title";
public static final String SEASON = "season";
public static final String EPISODE = "episode";
public static final String LANGUAGE = "language";
public static final String RESOLUTION = "resolution";
public static final String QUALITY = "quality";
public static final String GROUP = "group";
public static final String CLEAN_TITLE = "cleanTitle";
public static final String YEAR = "year";
}

View File

@ -0,0 +1,54 @@
package com.lckp.jproxy.constant;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* <p>
* 有效状态
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-06
*/
@Getter
@AllArgsConstructor
public enum ValidStatus {
VALID(1, "有效"),
INVALID(0, "无效");
private final Integer code;
private final String description;
/**
*
* 根据 code 获取对应的 ValidStatus
*
* @param code
* @return ValidStatus
*/
public static ValidStatus getByCode(Integer code) {
for (ValidStatus validStatus : ValidStatus.values()) {
if (validStatus.code.equals(code)) {
return validStatus;
}
}
return INVALID;
}
/**
*
* 根据 boolean 值获取 code
*
* @param flag
* @return Integer
*/
public static Integer getCode(boolean flag) {
if (flag) {
return VALID.getCode();
}
return INVALID.getCode();
}
}

View File

@ -0,0 +1,71 @@
package com.lckp.jproxy.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lckp.jproxy.entity.RadarrExample;
import com.lckp.jproxy.model.request.RadarrExampleQueryRequest;
import com.lckp.jproxy.model.request.RadarrExampleSaveRequest;
import com.lckp.jproxy.model.response.PageResponse;
import com.lckp.jproxy.service.IRadarrExampleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import liquibase.util.MD5Util;
import lombok.RequiredArgsConstructor;
/**
* <p>
* 电影范例
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-30
*/
@Tag(name = "电影范例")
@RequestMapping("/api/radarr/example")
@RestController
@RequiredArgsConstructor
public class RadarrExampleController {
private final IRadarrExampleService radarrExampleService;
@Operation(summary = "保存")
@PostMapping("/save")
public ResponseEntity<Void> save(@Validated @RequestBody RadarrExampleSaveRequest request) {
String[] originalTexts = request.getOriginalText().split("\\n");
List<RadarrExample> radarrExampleList = new ArrayList<>(originalTexts.length);
for (String originalText : originalTexts) {
RadarrExample example = new RadarrExample();
example.setOriginalText(originalText);
example.setHash(MD5Util.computeMD5(originalText).toUpperCase());
radarrExampleList.add(example);
}
radarrExampleService.saveOrUpdateBatch(radarrExampleList);
return ResponseEntity.ok().build();
}
@Operation(summary = "查询")
@GetMapping("/query")
public ResponseEntity<PageResponse<RadarrExample>> query(
@ParameterObject RadarrExampleQueryRequest request, Locale locale) {
return ResponseEntity.ok(new PageResponse<>(radarrExampleService.query(request, locale)));
}
@Operation(summary = "删除")
@PostMapping("/remove")
public ResponseEntity<Void> remove(@RequestBody List<String> idList) {
radarrExampleService.removeBatchByIds(idList);
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,179 @@
package com.lckp.jproxy.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.lckp.jproxy.constant.CacheName;
import com.lckp.jproxy.constant.Common;
import com.lckp.jproxy.constant.Messages;
import com.lckp.jproxy.constant.TableField;
import com.lckp.jproxy.constant.ValidStatus;
import com.lckp.jproxy.entity.RadarrRule;
import com.lckp.jproxy.model.request.RadarrRuleQueryRequest;
import com.lckp.jproxy.model.response.PageResponse;
import com.lckp.jproxy.service.IRadarrRuleService;
import com.lckp.jproxy.util.FileUtil;
import com.lckp.jproxy.util.Generator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* 电影规则
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-26
*/
@Slf4j
@Tag(name = "电影规则")
@RequestMapping("/api/radarr/rule")
@RestController
@RequiredArgsConstructor
public class RadarrRuleController {
private final IRadarrRuleService radarrRuleService;
private final MessageSource messageSource;
@Operation(summary = "同步")
@PostMapping("/sync")
public ResponseEntity<String> sync(Locale locale) {
if (radarrRuleService.sync()) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.TITLE_SYNC_TOO_OFTEN, null, locale));
}
}
@Operation(summary = "查询")
@GetMapping("/query")
public ResponseEntity<PageResponse<RadarrRule>> query(@ParameterObject RadarrRuleQueryRequest request) {
return ResponseEntity.ok(new PageResponse<>(radarrRuleService.query(request)));
}
@Operation(summary = "保存")
@PostMapping("/save")
@CacheEvict(cacheNames = CacheName.RADARR_RULE, allEntries = true)
public ResponseEntity<String> save(@Validated @RequestBody RadarrRule request, Locale locale) {
try {
Pattern.compile(request.getRegex()).matcher(request.getRegex())
.replaceAll(request.getReplacement());
} catch (Exception e) {
return ResponseEntity.internalServerError().body(e.getMessage());
}
if (StringUtils.isBlank(request.getId())) {
request.setId(Generator.generateUUID());
} else if (Common.MOST_IMPORTANT_TITLE_RULE_ID.equals(request.getId())) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.RULE_MODIFY_PRIMARY_FORBIDDEN, null, locale));
}
radarrRuleService.saveOrUpdate(request);
return ResponseEntity.ok().build();
}
@Operation(summary = "删除")
@PostMapping("/remove")
@CacheEvict(cacheNames = CacheName.RADARR_RULE, allEntries = true)
public ResponseEntity<String> remove(@RequestBody List<String> idList, Locale locale) {
for (String id : idList) {
if (Common.MOST_IMPORTANT_TITLE_RULE_ID.equals(id)) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.RULE_MODIFY_PRIMARY_FORBIDDEN, null, locale));
}
}
radarrRuleService.removeBatchByIds(idList);
return ResponseEntity.ok().build();
}
@Operation(summary = "启用")
@PostMapping("/enable")
public ResponseEntity<Void> enable(@RequestBody List<String> idList) {
radarrRuleService.switchValidStatus(idList, ValidStatus.VALID);
return ResponseEntity.ok().build();
}
@Operation(summary = "禁用")
@PostMapping("/disable")
public ResponseEntity<String> disable(@RequestBody List<String> idList, Locale locale) {
for (String id : idList) {
if (Common.MOST_IMPORTANT_TITLE_RULE_ID.equals(id)) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.RULE_MODIFY_PRIMARY_FORBIDDEN, null, locale));
}
}
radarrRuleService.switchValidStatus(idList, ValidStatus.INVALID);
return ResponseEntity.ok().build();
}
@Operation(summary = "导出")
@PostMapping("/export")
public ResponseEntity<List<RadarrRule>> export(@RequestBody List<String> idList) {
if (idList == null || idList.isEmpty()) {
return ResponseEntity.ok(radarrRuleService.query()
.select(TableField.ID, TableField.TOKEN, TableField.PRIORITY, TableField.REGEX,
TableField.REPLACEMENT, TableField.OFFSET, TableField.EXAMPLE, TableField.REMARK,
TableField.AUTHOR)
.list());
}
return ResponseEntity.ok(radarrRuleService.listByIds(idList));
}
@Operation(summary = "导入")
@PostMapping("/import")
@CacheEvict(cacheNames = CacheName.RADARR_RULE, allEntries = true)
public ResponseEntity<String> importRadarrRule(MultipartFile file) {
try {
String content = FileUtil.read(file);
if (content == null) {
return ResponseEntity.badRequest().build();
}
List<RadarrRule> radarrRuleList = JSON.parseObject(content,
new TypeReference<List<RadarrRule>>() {
});
radarrRuleList.forEach(radarrRule -> {
if (ValidStatus.VALID.getCode().equals(radarrRule.getValidStatus())) {
radarrRule.setValidStatus(null);
}
});
radarrRuleService.saveOrUpdateBatch(radarrRuleList);
return ResponseEntity.ok().build();
} catch (Exception e) {
log.error("导入出错:", e);
return ResponseEntity.internalServerError().body(e.getMessage());
}
}
@Operation(summary = "查询 token 列表")
@GetMapping("/token/list")
public ResponseEntity<List<String>> listToken() {
List<RadarrRule> radarrRuleList = radarrRuleService.query().select(TableField.TOKEN)
.groupBy(TableField.TOKEN).list();
List<String> tokenList = new ArrayList<>(radarrRuleList.size());
for (RadarrRule radarrRule : radarrRuleList) {
tokenList.add(radarrRule.getToken());
}
return ResponseEntity.ok(tokenList);
}
}

View File

@ -0,0 +1,75 @@
package com.lckp.jproxy.controller;
import java.util.List;
import java.util.Locale;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lckp.jproxy.constant.CacheName;
import com.lckp.jproxy.constant.Messages;
import com.lckp.jproxy.entity.RadarrTitle;
import com.lckp.jproxy.model.request.RadarrTitleQueryRequest;
import com.lckp.jproxy.model.response.PageResponse;
import com.lckp.jproxy.service.IRadarrTitleService;
import com.lckp.jproxy.service.ISystemCacheService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
/**
* <p>
* 电影标题
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-26
*/
@Tag(name = "电影标题")
@RequestMapping("/api/radarr/title")
@RestController
@RequiredArgsConstructor
public class RadarrTitleController {
private final IRadarrTitleService radarrTitleService;
private final ISystemCacheService systemCacheService;
private final MessageSource messageSource;
@Operation(summary = "同步")
@PostMapping("/sync")
public ResponseEntity<String> sync(Locale locale) {
try {
if (radarrTitleService.sync()) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.TITLE_SYNC_TOO_OFTEN, null, locale));
}
} catch (Exception e) {
systemCacheService.clear(CacheName.RADARR_TITLE_SYNC_INTERVAL);
throw e;
}
}
@Operation(summary = "查询")
@GetMapping("/query")
public ResponseEntity<PageResponse<RadarrTitle>> query(@ParameterObject RadarrTitleQueryRequest request) {
return ResponseEntity.ok(new PageResponse<>(radarrTitleService.query(request)));
}
@Operation(summary = "删除")
@PostMapping("/remove")
public ResponseEntity<Void> remove(@RequestBody List<Integer> idList) {
radarrTitleService.removeBatchByIds(idList);
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,41 @@
package com.lckp.jproxy.controller;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lckp.jproxy.model.request.RuleTestRequest;
import com.lckp.jproxy.util.FormatUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
/**
* <p>
* 规则
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-29
*/
@Tag(name = "规则")
@RequestMapping("/api/rule")
@RestController
public class RuleController {
@Operation(summary = "测试")
@GetMapping("/test")
public ResponseEntity<String> test(@Validated @ParameterObject RuleTestRequest request) {
String[] examples = request.getExample().split("\\n");
StringBuilder builder = new StringBuilder();
for (String example : examples) {
String value = example.replaceAll(request.getRegex(), request.getReplacement());
value = FormatUtil.executeOffset(value, request.getOffset());
builder.append(value + "\n");
}
return ResponseEntity.ok(builder.toString());
}
}

View File

@ -0,0 +1,71 @@
package com.lckp.jproxy.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lckp.jproxy.entity.SonarrExample;
import com.lckp.jproxy.model.request.SonarrExampleQueryRequest;
import com.lckp.jproxy.model.request.SonarrExampleSaveRequest;
import com.lckp.jproxy.model.response.PageResponse;
import com.lckp.jproxy.service.ISonarrExampleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import liquibase.util.MD5Util;
import lombok.RequiredArgsConstructor;
/**
* <p>
* 剧集范例
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-30
*/
@Tag(name = "剧集范例")
@RequestMapping("/api/sonarr/example")
@RestController
@RequiredArgsConstructor
public class SonarrExampleController {
private final ISonarrExampleService sonarrExampleService;
@Operation(summary = "保存")
@PostMapping("/save")
public ResponseEntity<Void> save(@Validated @RequestBody SonarrExampleSaveRequest request) {
String[] originalTexts = request.getOriginalText().split("\\n");
List<SonarrExample> sonarrExampleList = new ArrayList<>(originalTexts.length);
for (String originalText : originalTexts) {
SonarrExample example = new SonarrExample();
example.setOriginalText(originalText);
example.setHash(MD5Util.computeMD5(originalText).toUpperCase());
sonarrExampleList.add(example);
}
sonarrExampleService.saveOrUpdateBatch(sonarrExampleList);
return ResponseEntity.ok().build();
}
@Operation(summary = "查询")
@GetMapping("/query")
public ResponseEntity<PageResponse<SonarrExample>> query(
@ParameterObject SonarrExampleQueryRequest request, Locale locale) {
return ResponseEntity.ok(new PageResponse<>(sonarrExampleService.query(request, locale)));
}
@Operation(summary = "删除")
@PostMapping("/remove")
public ResponseEntity<Void> remove(@RequestBody List<String> idList) {
sonarrExampleService.removeBatchByIds(idList);
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,179 @@
package com.lckp.jproxy.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.lckp.jproxy.constant.CacheName;
import com.lckp.jproxy.constant.Common;
import com.lckp.jproxy.constant.Messages;
import com.lckp.jproxy.constant.TableField;
import com.lckp.jproxy.constant.ValidStatus;
import com.lckp.jproxy.entity.SonarrRule;
import com.lckp.jproxy.model.request.SonarrRuleQueryRequest;
import com.lckp.jproxy.model.response.PageResponse;
import com.lckp.jproxy.service.ISonarrRuleService;
import com.lckp.jproxy.util.FileUtil;
import com.lckp.jproxy.util.Generator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* 剧集规则
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-26
*/
@Slf4j
@Tag(name = "剧集规则")
@RequestMapping("/api/sonarr/rule")
@RestController
@RequiredArgsConstructor
public class SonarrRuleController {
private final ISonarrRuleService sonarrRuleService;
private final MessageSource messageSource;
@Operation(summary = "同步")
@PostMapping("/sync")
public ResponseEntity<String> sync(Locale locale) {
if (sonarrRuleService.sync()) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.TITLE_SYNC_TOO_OFTEN, null, locale));
}
}
@Operation(summary = "查询")
@GetMapping("/query")
public ResponseEntity<PageResponse<SonarrRule>> query(@ParameterObject SonarrRuleQueryRequest request) {
return ResponseEntity.ok(new PageResponse<>(sonarrRuleService.query(request)));
}
@Operation(summary = "保存")
@PostMapping("/save")
@CacheEvict(cacheNames = CacheName.SONARR_RULE, allEntries = true)
public ResponseEntity<String> save(@Validated @RequestBody SonarrRule request, Locale locale) {
try {
Pattern.compile(request.getRegex()).matcher(request.getRegex())
.replaceAll(request.getReplacement());
} catch (Exception e) {
return ResponseEntity.internalServerError().body(e.getMessage());
}
if (StringUtils.isBlank(request.getId())) {
request.setId(Generator.generateUUID());
} else if (Common.MOST_IMPORTANT_TITLE_RULE_ID.equals(request.getId())) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.RULE_MODIFY_PRIMARY_FORBIDDEN, null, locale));
}
sonarrRuleService.saveOrUpdate(request);
return ResponseEntity.ok().build();
}
@Operation(summary = "删除")
@PostMapping("/remove")
@CacheEvict(cacheNames = CacheName.SONARR_RULE, allEntries = true)
public ResponseEntity<String> remove(@RequestBody List<String> idList, Locale locale) {
for (String id : idList) {
if (Common.MOST_IMPORTANT_TITLE_RULE_ID.equals(id)) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.RULE_MODIFY_PRIMARY_FORBIDDEN, null, locale));
}
}
sonarrRuleService.removeBatchByIds(idList);
return ResponseEntity.ok().build();
}
@Operation(summary = "启用")
@PostMapping("/enable")
public ResponseEntity<Void> enable(@RequestBody List<String> idList) {
sonarrRuleService.switchValidStatus(idList, ValidStatus.VALID);
return ResponseEntity.ok().build();
}
@Operation(summary = "禁用")
@PostMapping("/disable")
public ResponseEntity<String> disable(@RequestBody List<String> idList, Locale locale) {
for (String id : idList) {
if (Common.MOST_IMPORTANT_TITLE_RULE_ID.equals(id)) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.RULE_MODIFY_PRIMARY_FORBIDDEN, null, locale));
}
}
sonarrRuleService.switchValidStatus(idList, ValidStatus.INVALID);
return ResponseEntity.ok().build();
}
@Operation(summary = "导出")
@PostMapping("/export")
public ResponseEntity<List<SonarrRule>> export(@RequestBody List<String> idList) {
if (idList == null || idList.isEmpty()) {
return ResponseEntity.ok(sonarrRuleService.query()
.select(TableField.ID, TableField.TOKEN, TableField.PRIORITY, TableField.REGEX,
TableField.REPLACEMENT, TableField.OFFSET, TableField.EXAMPLE, TableField.REMARK,
TableField.AUTHOR)
.list());
}
return ResponseEntity.ok(sonarrRuleService.listByIds(idList));
}
@Operation(summary = "导入")
@PostMapping("/import")
@CacheEvict(cacheNames = CacheName.SONARR_RULE, allEntries = true)
public ResponseEntity<String> importSonarrRule(MultipartFile file) {
try {
String content = FileUtil.read(file);
if (content == null) {
return ResponseEntity.badRequest().build();
}
List<SonarrRule> sonarrRuleList = JSON.parseObject(content,
new TypeReference<List<SonarrRule>>() {
});
sonarrRuleList.forEach(sonarrRule -> {
if (ValidStatus.VALID.getCode().equals(sonarrRule.getValidStatus())) {
sonarrRule.setValidStatus(null);
}
});
sonarrRuleService.saveOrUpdateBatch(sonarrRuleList);
return ResponseEntity.ok().build();
} catch (Exception e) {
log.error("导入出错:", e);
return ResponseEntity.internalServerError().body(e.getMessage());
}
}
@Operation(summary = "查询 token 列表")
@GetMapping("/token/list")
public ResponseEntity<List<String>> listToken() {
List<SonarrRule> sonarrRuleList = sonarrRuleService.query().select(TableField.TOKEN)
.groupBy(TableField.TOKEN).list();
List<String> tokenList = new ArrayList<>(sonarrRuleList.size());
for (SonarrRule sonarrRule : sonarrRuleList) {
tokenList.add(sonarrRule.getToken());
}
return ResponseEntity.ok(tokenList);
}
}

View File

@ -0,0 +1,75 @@
package com.lckp.jproxy.controller;
import java.util.List;
import java.util.Locale;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lckp.jproxy.constant.CacheName;
import com.lckp.jproxy.constant.Messages;
import com.lckp.jproxy.entity.SonarrTitle;
import com.lckp.jproxy.model.request.SonarrTitleQueryRequest;
import com.lckp.jproxy.model.response.PageResponse;
import com.lckp.jproxy.service.ISonarrTitleService;
import com.lckp.jproxy.service.ISystemCacheService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
/**
* <p>
* 剧集标题
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-26
*/
@Tag(name = "剧集标题")
@RequestMapping("/api/sonarr/title")
@RestController
@RequiredArgsConstructor
public class SonarrTitleController {
private final ISonarrTitleService sonarrTitleService;
private final ISystemCacheService systemCacheService;
private final MessageSource messageSource;
@Operation(summary = "同步")
@PostMapping("/sync")
public ResponseEntity<String> sync(Locale locale) {
try {
if (sonarrTitleService.sync()) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.TITLE_SYNC_TOO_OFTEN, null, locale));
}
} catch (Exception e) {
systemCacheService.clear(CacheName.SONARR_TITLE_SYNC_INTERVAL);
throw e;
}
}
@Operation(summary = "查询")
@GetMapping("/query")
public ResponseEntity<PageResponse<SonarrTitle>> query(@ParameterObject SonarrTitleQueryRequest request) {
return ResponseEntity.ok(new PageResponse<>(sonarrTitleService.query(request)));
}
@Operation(summary = "删除")
@PostMapping("/remove")
public ResponseEntity<Void> remove(@RequestBody List<Integer> idList) {
sonarrTitleService.removeBatchByIds(idList);
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,46 @@
package com.lckp.jproxy.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lckp.jproxy.model.request.SystemCacheClearRequest;
import com.lckp.jproxy.service.ISystemCacheService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
/**
* <p>
* 系统缓存
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-26
*/
@Tag(name = "系统缓存")
@RequestMapping("/api/system/cache")
@RestController
@RequiredArgsConstructor
public class SystemCacheController {
private final ISystemCacheService systemCacheService;
@Operation(summary = "清除所有")
@PostMapping("/clearAll")
public ResponseEntity<Void> clearAll() {
systemCacheService.clearAll();
return ResponseEntity.ok().build();
}
@Operation(summary = "清除")
@PostMapping("/clear")
public ResponseEntity<Void> clear(@Validated @RequestBody SystemCacheClearRequest request) {
systemCacheService.clear(request.getCacheName());
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,128 @@
package com.lckp.jproxy.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.lckp.jproxy.constant.ApiField;
import com.lckp.jproxy.constant.CacheName;
import com.lckp.jproxy.entity.SystemConfig;
import com.lckp.jproxy.service.ISystemCacheService;
import com.lckp.jproxy.service.ISystemConfigService;
import com.lckp.jproxy.task.SonarrRenameTask;
import com.lckp.jproxy.util.Generator;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* 系统配置
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-12
*/
@Slf4j
@Tag(name = "系统配置")
@RequestMapping("/api/system/config")
@RestController
@RequiredArgsConstructor
public class SystemConfigController implements CommandLineRunner {
private final ISystemConfigService systemConfigService;
private final ISystemCacheService systemCacheService;
private final SonarrRenameTask sonarrRenameTask;
private final RestTemplate restTemplate;
@Value("${project.version}")
private String projectVersion;
@Value("${rule.location}")
private String ruleLocation;
@Value("${rule.location-backup}")
private String ruleLocationBackup;
@Operation(summary = "版本号")
@GetMapping("/version")
public ResponseEntity<String> version() {
String latestVersion = null;
try {
latestVersion = JSON.parseObject(restTemplate.getForObject(
"https://api.github.com/repos/LuckyPuppy514/jproxy/releases/latest", String.class))
.getString(ApiField.GITHUB_TAG_NAME);
if (StringUtils.isNotBlank(latestVersion)) {
latestVersion = latestVersion.replace("v", "");
if (!projectVersion.equals(latestVersion)) {
return ResponseEntity.ok(projectVersion + " 🚨");
}
}
} catch (Exception e) {
log.debug("获取最新版本号出错:{}", e.getMessage());
}
return ResponseEntity.ok(projectVersion);
}
@Operation(summary = "查询")
@GetMapping("/query")
public ResponseEntity<List<SystemConfig>> query() {
sonarrRenameTask.run();
return ResponseEntity.ok(systemConfigService.query().list());
}
@Operation(summary = "更新")
@PostMapping("/update")
public ResponseEntity<Void> update(@RequestBody List<SystemConfig> systemConfigList) {
systemConfigService.updateSystemConfig(systemConfigList);
systemCacheService.clear(CacheName.SONARR_TITLE_SYNC_INTERVAL);
systemCacheService.clear(CacheName.TMDB_TITLE_SYNC_INTERVAL);
systemCacheService.clear(CacheName.RADARR_TITLE_SYNC_INTERVAL);
return ResponseEntity.ok().build();
}
@Operation(summary = "查询作者列表")
@GetMapping("/author/list")
public ResponseEntity<String[]> listAuthor() {
String authorUrl = Generator.generateAuthorUrl();
String[] authorList = { "LuckyPuppy514" };
try {
authorList = restTemplate.getForEntity(authorUrl, String[].class).getBody();
} catch (Exception e) {
log.error("获取作者列表出错:{}", e.getMessage());
}
return ResponseEntity.ok(authorList);
}
/**
* @param args
* @throws Exception
* @see org.springframework.boot.CommandLineRunner#run(java.lang.String[])
*/
@Override
public void run(String... args) throws Exception {
try {
Generator.setRuleLocation(ruleLocation);
restTemplate.getForEntity(Generator.generateAuthorUrl(), String[].class).getBody();
} catch (Exception e) {
log.info("无法访问规则地址:{} - {}", ruleLocation, e.getMessage());
Generator.setRuleLocation(ruleLocationBackup);
log.info("已切换到备用地址:{}", ruleLocationBackup);
}
}
}

View File

@ -0,0 +1,96 @@
package com.lckp.jproxy.controller;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.MessageSource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lckp.jproxy.constant.Messages;
import com.lckp.jproxy.entity.SystemUser;
import com.lckp.jproxy.model.request.SystemUserLoginRequest;
import com.lckp.jproxy.service.ISystemUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
/**
* <p>
* 系统用户
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-12
*/
@Tag(name = "系统用户")
@RequestMapping("/api/system/user")
@RestController
@RequiredArgsConstructor
public class SystemUserController {
private final ISystemUserService systemUserService;
private final MessageSource messageSource;
private int loginWrongCount = 0;
@Operation(summary = "登陆")
@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody @Validated SystemUserLoginRequest request,
Locale locale) {
if (loginWrongCount > 10) {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.LOGIN_WRONG_TOO_MANY_TIMES, null, locale));
}
SystemUser systemUser = new SystemUser();
systemUser.setUsername(request.getUsername());
systemUser.setPassword(request.getPassword());
if (systemUserService.check(systemUser)) {
return ResponseEntity.ok(systemUserService.sign(systemUser));
}
loginWrongCount++;
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.LOGIN_WRONG_USER, null, locale));
}
@Operation(summary = "信息")
@GetMapping("/info")
public ResponseEntity<SystemUser> info(HttpServletRequest servletRequest) {
String token = servletRequest.getHeader(HttpHeaders.AUTHORIZATION);
SystemUser systemUser = systemUserService.getSystemUser(token);
systemUser.setPassword("******");
return ResponseEntity.ok(systemUser);
}
@Operation(summary = "更新")
@PostMapping("/update")
public ResponseEntity<Void> update(@RequestBody SystemUser systemUser,
HttpServletRequest servletRequest) {
String token = servletRequest.getHeader(HttpHeaders.AUTHORIZATION);
SystemUser currentSystemUser = systemUserService.getSystemUser(token);
if (StringUtils.isNotBlank(systemUser.getUsername())) {
currentSystemUser.setUsername(systemUser.getUsername());
}
if (StringUtils.isNotBlank(systemUser.getPassword())) {
currentSystemUser.setPassword(systemUser.getPassword());
}
systemUserService.update(currentSystemUser);
return ResponseEntity.ok().build();
}
@Operation(summary = "注销")
@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletRequest servletRequest) {
systemUserService.logout(servletRequest.getHeader(HttpHeaders.AUTHORIZATION));
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,107 @@
package com.lckp.jproxy.controller;
import java.util.List;
import java.util.Locale;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lckp.jproxy.constant.CacheName;
import com.lckp.jproxy.constant.Messages;
import com.lckp.jproxy.constant.SystemConfigKey;
import com.lckp.jproxy.constant.TableField;
import com.lckp.jproxy.entity.TmdbTitle;
import com.lckp.jproxy.model.request.TmdbTitleQueryRequest;
import com.lckp.jproxy.model.response.PageResponse;
import com.lckp.jproxy.service.ISonarrTitleService;
import com.lckp.jproxy.service.ISystemCacheService;
import com.lckp.jproxy.service.ISystemConfigService;
import com.lckp.jproxy.service.ITmdbTitleService;
import com.lckp.jproxy.util.FormatUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
/**
* <p>
* TMDB 标题
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-26
*/
@Tag(name = "TMDB 标题")
@RequestMapping("/api/tmdb/title")
@RestController
@RequiredArgsConstructor
public class TmdbTitleController {
private final ITmdbTitleService tmdbTitleService;
private final ISonarrTitleService sonarrTitleService;
private final ISystemConfigService systemConfigService;
private final ISystemCacheService systemCacheService;
private final MessageSource messageSource;
@Operation(summary = "同步")
@PostMapping("/sync")
public ResponseEntity<String> sync(Locale locale) {
try {
if (tmdbTitleService.sync(sonarrTitleService.queryNeedSyncTmdbTitle())) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.badRequest()
.body(messageSource.getMessage(Messages.TITLE_SYNC_TOO_OFTEN, null, locale));
}
} catch (Exception e) {
systemCacheService.clear(CacheName.TMDB_TITLE_SYNC_INTERVAL);
throw e;
}
}
@Operation(summary = "查询")
@GetMapping("/query")
public ResponseEntity<PageResponse<TmdbTitle>> query(@ParameterObject TmdbTitleQueryRequest request) {
PageResponse<TmdbTitle> response = new PageResponse<>(tmdbTitleService.query(request));
String cleanTitleRegex = systemConfigService.queryValueByKey(SystemConfigKey.CLEAN_TITLE_REGEX);
List<TmdbTitle> tmdbTitleList = response.getList();
for (TmdbTitle tmdbTitle : tmdbTitleList) {
tmdbTitle.setCleanTitle(FormatUtil.cleanTitle(tmdbTitle.getTitle(), cleanTitleRegex));
}
return ResponseEntity.ok(response);
}
@Operation(summary = "删除")
@PostMapping("/remove")
public ResponseEntity<Void> remove(@RequestBody List<Integer> idList) {
tmdbTitleService.removeBatchByIds(idList);
return ResponseEntity.ok().build();
}
@Operation(summary = "保存")
@PostMapping("/save")
@CacheEvict(cacheNames = { CacheName.SONARR_SEARCH_TITLE, CacheName.INDEXER_SEARCH_OFFSET,
CacheName.SONARR_RESULT_TITLE }, allEntries = true)
public ResponseEntity<Void> save(@RequestBody TmdbTitle tmdbTitle) {
if (tmdbTitle.getTmdbId() == null) {
List<TmdbTitle> tmdbTitleList = tmdbTitleService.query()
.eq(TableField.TVDB_ID, tmdbTitle.getTvdbId()).list();
if (!tmdbTitleList.isEmpty()) {
tmdbTitle.setTmdbId(tmdbTitleList.get(0).getTmdbId());
}
}
tmdbTitleService.saveOrUpdate(tmdbTitle);
return ResponseEntity.ok().build();
}
}

View File

@ -0,0 +1,44 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* RadarrExample
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-30
*/
@Getter
@Setter
@TableName("radarr_example")
@Schema(name = "RadarrExample", description = "RadarrExample")
public class RadarrExample implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private String hash;
private String originalText;
private String formatText;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,65 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* RadarrRule
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-20
*/
@Getter
@Setter
@TableName("radarr_rule")
@Schema(name = "RadarrRule", description = "RadarrRule")
public class RadarrRule implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private String id;
@NotBlank
private String token;
@NotNull
private Integer priority;
@NotBlank
private String regex;
private String replacement;
@NotNull
private Integer offset;
@NotBlank
private String example;
@NotBlank
private String remark;
@NotBlank
private String author;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,56 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* RadarrTitle
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-20
*/
@Getter
@Setter
@TableName("radarr_title")
@Schema(name = "RadarrTitle", description = "RadarrTitle")
public class RadarrTitle implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Integer id;
private Integer movieId;
private Integer tmdbId;
private Integer sno;
private String mainTitle;
private String title;
private String cleanTitle;
private Integer year;
private Integer monitored;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,44 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* SonarrExample
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-30
*/
@Getter
@Setter
@TableName("sonarr_example")
@Schema(name = "SonarrExample", description = "SonarrExample")
public class SonarrExample implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private String hash;
private String originalText;
private String formatText;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,65 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* SonarrRule
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-20
*/
@Getter
@Setter
@TableName("sonarr_rule")
@Schema(name = "SonarrRule", description = "SonarrRule")
public class SonarrRule implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private String id;
@NotBlank
private String token;
@NotNull
private Integer priority;
@NotBlank
private String regex;
private String replacement;
@NotNull
private Integer offset;
@NotBlank
private String example;
@NotBlank
private String remark;
@NotBlank
private String author;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,56 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* SonarrTitle
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
@Getter
@Setter
@TableName("sonarr_title")
@Schema(name = "SonarrTitle", description = "SonarrTitle")
public class SonarrTitle implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Integer id;
private Integer seriesId;
private Integer tvdbId;
private Integer sno;
private String mainTitle;
private String title;
private String cleanTitle;
private Integer seasonNumber;
private Integer monitored;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,44 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* SystemConfig
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
@Getter
@Setter
@TableName("system_config")
@Schema(name = "SystemConfig", description = "SystemConfig")
public class SystemConfig implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Integer id;
private String key;
private String value;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,46 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* SystemUser
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-30
*/
@Getter
@Setter
@TableName("system_user")
@Schema(name = "SystemUser", description = "SystemUser")
public class SystemUser implements Serializable {
private static final long serialVersionUID = 1L;
@TableId
private Integer id;
private String username;
private String password;
private String role;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,52 @@
package com.lckp.jproxy.entity;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* TmdbTitle
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
@Getter
@Setter
@TableName("tmdb_title")
@Schema(name = "TmdbTitle", description = "TmdbTitle")
public class TmdbTitle implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Integer tvdbId;
private Integer tmdbId;
private String language;
private String title;
@TableField(exist = false)
private String cleanTitle;
private Integer validStatus;
@TableField(fill = FieldFill.INSERT)
private String createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateTime;
}

View File

@ -0,0 +1,128 @@
package com.lckp.jproxy.exception;
import org.springframework.context.MessageSource;
import org.springframework.http.ResponseEntity;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.HttpClientErrorException;
import com.lckp.jproxy.constant.Messages;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* 全局异常处理
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-23
*/
@Slf4j
@ControllerAdvice
@RequiredArgsConstructor
public class GlobalExceptionHandler {
private final MessageSource messageSource;
/**
*
* 处理请求参数异常
*
* @param e
* @param request
* @return ResponseEntity<Object>
*/
@ResponseBody
@ExceptionHandler(BindException.class)
public ResponseEntity<Object> dealBindException(BindException e, HttpServletRequest request) {
StringBuilder builder = new StringBuilder();
BindingResult bindingResult = e.getBindingResult();
for (ObjectError objectError : bindingResult.getAllErrors()) {
FieldError fieldError = (FieldError) objectError;
builder.append("\n" + fieldError.getField() + ": " + fieldError.getDefaultMessage());
}
String message = builder.toString();
if (message.length() > 0) {
message = message.substring(1);
}
log.error("请求参数异常:{}", message);
return ResponseEntity.badRequest().body(message);
}
/**
*
* 处理系统配置异常
*
* @param e
* @param request
* @return ResponseEntity<Object>
*/
@ResponseBody
@ExceptionHandler(SystemConfigException.class)
public ResponseEntity<Object> dealSystemConfigException(SystemConfigException e,
HttpServletRequest request) {
String message = messageSource.getMessage(e.getMessage(), null, request.getLocale());
log.error("系统配置异常:{}", message);
return ResponseEntity.internalServerError().body(message);
}
/**
*
* 处理 Http 请求异常
*
* @param e
* @param request
* @return ResponseEntity<Object>
*/
@ResponseBody
@ExceptionHandler(HttpClientErrorException.class)
public ResponseEntity<Object> dealHttpClientErrorException(HttpClientErrorException e,
HttpServletRequest request) {
log.error("Http 请求异常:{}", e.getMessage());
return ResponseEntity.internalServerError().body(e.getMessage());
}
/**
*
* 处理数据库异常
*
* @param e
* @param request
* @return ResponseEntity<Object>
*/
@ResponseBody
@ExceptionHandler(UncategorizedSQLException.class)
public ResponseEntity<Object> dealUncategorizedSQLException(UncategorizedSQLException e,
HttpServletRequest request) {
String message = e.getMessage();
if (message.contains("SQLITE_BUSY")) {
message = messageSource.getMessage(Messages.DATABASE_BUSY, null, request.getLocale());
}
log.error("数据库异常:{}", message);
return ResponseEntity.internalServerError().body(message);
}
/**
*
* 处理其他异常
*
* @param e
* @param request
* @return ResponseEntity<Object>
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public ResponseEntity<Object> dealException(Exception e, HttpServletRequest request) {
log.error("其他异常:{}", e.getMessage(), e);
return ResponseEntity.internalServerError().body(e.getMessage());
}
}

View File

@ -0,0 +1,17 @@
package com.lckp.jproxy.exception;
/**
* <p>
* 系统配置异常
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-31
*/
public class SystemConfigException extends RuntimeException {
private static final long serialVersionUID = 1L;
public SystemConfigException(String message) {
super(message);
}
}

View File

@ -0,0 +1,66 @@
package com.lckp.jproxy.filter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
/**
* <p>
* 基础过滤器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
public abstract class BaseFilter implements Filter {
/**
* @param request
* @param response
* @param chain
* @throws IOException
* @throws ServletException
* @see jakarta.servlet.Filter#doFilter(jakarta.servlet.ServletRequest,
* jakarta.servlet.ServletResponse, jakarta.servlet.FilterChain)
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
/**
*
* 把结果写入 response
*
* @param content
* @param response
* @throws IOException void
*/
public void writeToResponse(byte[] content, ServletResponse response) throws IOException {
ServletOutputStream out = response.getOutputStream();
response.setContentLength(content.length);
response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
out.write(content);
out.flush();
}
/**
*
* 把结果写入 response
*
* @param content
* @param response
* @throws IOException void
*/
public void writeToResponse(String content, ServletResponse response) throws IOException {
content = content == null ? "" : content;
writeToResponse(content.getBytes(StandardCharsets.UTF_8), response);
}
}

View File

@ -0,0 +1,175 @@
package com.lckp.jproxy.filter;
import java.io.IOException;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.github.benmanes.caffeine.cache.Cache;
import com.lckp.jproxy.constant.ApiField;
import com.lckp.jproxy.filter.wrapper.RequestWrapper;
import com.lckp.jproxy.model.request.IndexerRequest;
import com.lckp.jproxy.service.IIndexerService;
import com.lckp.jproxy.util.ApplicationContextHolder;
import com.lckp.jproxy.util.FormatUtil;
import com.lckp.jproxy.util.XmlUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* 索引器过滤器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-20
*/
@Slf4j
@RequiredArgsConstructor
public abstract class IndexerFilter extends BaseFilter {
private final IIndexerService indexerService;
private Cache<String, String> indexerResultCache;
@Override
@SuppressWarnings("unchecked")
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
RequestWrapper requestWrapper = new RequestWrapper((HttpServletRequest) request);
// 读取缓存
if (indexerResultCache == null) {
indexerResultCache = (Cache<String, String>) ApplicationContextHolder
.getBean("indexerResultCache");
}
String cacheKey = indexerService.generateCacheKey(requestWrapper);
String xml = indexerResultCache.asMap().get(cacheKey);
if (xml != null) {
log.debug("From cache: {}", cacheKey);
log.trace("{}", xml);
writeToResponse(xml, response);
return;
}
// 处理查询
IndexerRequest indexerRequest = getIndexerRequest(requestWrapper);
String searchKey = indexerRequest.getSearchKey();
if (StringUtils.isNotBlank(searchKey)) {
// 无绝对集数去除 00
searchKey = searchKey.replaceAll(" 00$", "");
indexerRequest.setSearchKey(searchKey);
// 获取所有待查询标题
String title = indexerService.getTitle(indexerRequest.getSearchKey());
List<String> searchTitleList = indexerService.getSearchTitle(title);
// 计算 offset
int offset = indexerRequest.getOffset();
int size = searchTitleList.size();
String offsetKey = indexerService.generateOffsetKey(requestWrapper);
List<Integer> offsetList = indexerService.getOffsetList(offsetKey, size);
// 计算当前标题下标
int index = indexerService.calculateCurrentIndex(offset, offsetList);
int count = 0;
int minCount = indexerService.getMinCount();
do {
if (size > 2 && index == size - 1) {
// 已查询到的结果数量少于 minCount 则去除季集信息尝试查询
if (minCount > 0 && offsetList.get(index - 1) < minCount) {
if (StringUtils.isNotBlank(indexerRequest.getSeasonNumber())) {
indexerRequest.setSeasonNumber("");
indexerRequest.setEpisodeNumber("");
} else {
indexerRequest.setSearchKey(
FormatUtil.removeSeasonEpisode(indexerRequest.getSearchKey()));
}
}
}
// 请求
indexerService.updateIndexerRequest(index, searchTitleList, offsetList, indexerRequest);
updateRequestWrapper(indexerRequest, requestWrapper);
String newXml = indexerService.executeNewRequest(requestWrapper);
count = XmlUtil.count(newXml);
// 处理 Prowlarr 分页异常
if (count > indexerRequest.getLimit()) {
count = indexerRequest.getLimit();
newXml = XmlUtil.remove(newXml, count);
offset = offset - count;
}
if (count > 0 || xml == null) {
xml = XmlUtil.merger(xml, newXml);
}
// 更新参数
offset = offset + count;
indexerRequest.setOffset(offset);
offsetList.set(index, offset);
indexerService.updateOffsetList(offsetKey, offsetList);
indexerRequest.setLimit(indexerRequest.getLimit() - count);
} while (++index < size && indexerRequest.getLimit() > 0);
} else {
xml = indexerService.executeNewRequest(requestWrapper);
}
if (StringUtils.isNotBlank(xml) && xml.contains("<" + ApiField.INDEXER_CHANNEL + ">")) {
// 执行格式化规则
xml = indexerService.executeFormatRule(xml);
// 缓存结果
indexerResultCache.asMap().put(cacheKey, xml);
}
log.debug("From request: {}", cacheKey);
log.trace("{}", xml);
writeToResponse(xml, response);
}
/**
*
* 获取索引器参数
*
* @param requestWrapper
* @return IndexerRequest
*/
public IndexerRequest getIndexerRequest(RequestWrapper requestWrapper) {
IndexerRequest indexerRequest = new IndexerRequest();
indexerRequest.setSearchKey(requestWrapper.getParameter(ApiField.INDEXER_SEARCH_KEY));
indexerRequest.setSearchType(requestWrapper.getParameter(ApiField.INDEXER_SEARCH_TYPE));
String seasonNumber = requestWrapper.getParameter(ApiField.INDEXER_SEASON_NUMBER);
indexerRequest.setSeasonNumber(seasonNumber);
String episodeNumber = requestWrapper.getParameter(ApiField.INDEXER_EPISODE_NUMBER);
indexerRequest.setEpisodeNumber(episodeNumber);
String offset = requestWrapper.getParameter(ApiField.INDEXER_OFFSET);
indexerRequest.setOffset(offset == null ? null : Integer.valueOf(offset));
String limit = requestWrapper.getParameter(ApiField.INDEXER_LIMIT);
indexerRequest.setLimit(limit == null ? null : Integer.valueOf(limit));
return indexerRequest;
}
/**
*
* 更新请求参数
*
* @param indexerRequest
* @param requestWrapper void
*/
public void updateRequestWrapper(IndexerRequest indexerRequest, RequestWrapper requestWrapper) {
if (StringUtils.isNotBlank(indexerRequest.getSearchKey())) {
requestWrapper.setParameter(ApiField.INDEXER_SEARCH_KEY, indexerRequest.getSearchKey());
}
if (StringUtils.isNotBlank(indexerRequest.getSearchType())) {
requestWrapper.setParameter(ApiField.INDEXER_SEARCH_TYPE, indexerRequest.getSearchType());
}
if (indexerRequest.getSeasonNumber() != null) {
requestWrapper.setParameter(ApiField.INDEXER_SEASON_NUMBER, indexerRequest.getSeasonNumber());
}
if (indexerRequest.getEpisodeNumber() != null) {
requestWrapper.setParameter(ApiField.INDEXER_EPISODE_NUMBER, indexerRequest.getEpisodeNumber());
}
if (indexerRequest.getOffset() != null) {
requestWrapper.setParameter(ApiField.INDEXER_OFFSET, String.valueOf(indexerRequest.getOffset()));
}
if (indexerRequest.getLimit() != null) {
requestWrapper.setParameter(ApiField.INDEXER_LIMIT, String.valueOf(indexerRequest.getLimit()));
}
}
}

View File

@ -0,0 +1,22 @@
package com.lckp.jproxy.filter;
import com.lckp.jproxy.service.IIndexerService;
/**
* <p>
* Radarr 索引器过滤器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-20
*/
public abstract class RadarrIndexerFilter extends IndexerFilter {
/**
* @param indexerService
*/
protected RadarrIndexerFilter(IIndexerService indexerService) {
super(indexerService);
}
}

View File

@ -0,0 +1,22 @@
package com.lckp.jproxy.filter;
import com.lckp.jproxy.service.IRadarrIndexerService;
/**
* <p>
* Radarr Jackett 过滤器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
public class RadarrJackettFilter extends RadarrIndexerFilter {
/**
* @param radarrIndexerService
*/
public RadarrJackettFilter(IRadarrIndexerService radarrIndexerService) {
super(radarrIndexerService);
}
}

View File

@ -0,0 +1,22 @@
package com.lckp.jproxy.filter;
import com.lckp.jproxy.service.IRadarrIndexerService;
/**
* <p>
* Radarr Prowlarr 过滤器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
public class RadarrProwlarrFilter extends RadarrIndexerFilter {
/**
* @param radarrIndexerService
*/
public RadarrProwlarrFilter(IRadarrIndexerService radarrIndexerService) {
super(radarrIndexerService);
}
}

View File

@ -0,0 +1,22 @@
package com.lckp.jproxy.filter;
import com.lckp.jproxy.service.IIndexerService;
/**
* <p>
* Sonarr 索引器过滤器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-20
*/
public abstract class SonarrIndexerFilter extends IndexerFilter {
/**
* @param indexerService
*/
protected SonarrIndexerFilter(IIndexerService indexerService) {
super(indexerService);
}
}

View File

@ -0,0 +1,21 @@
package com.lckp.jproxy.filter;
import com.lckp.jproxy.service.ISonarrIndexerService;
/**
* <p>
* Sonarr Jackett 过滤器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
public class SonarrJackettFilter extends SonarrIndexerFilter {
/**
* @param sonarrIndexerService
*/
public SonarrJackettFilter(ISonarrIndexerService sonarrIndexerService) {
super(sonarrIndexerService);
}
}

View File

@ -0,0 +1,22 @@
package com.lckp.jproxy.filter;
import com.lckp.jproxy.service.ISonarrIndexerService;
/**
* <p>
* Sonarr Prowlarr 过滤器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-19
*/
public class SonarrProwlarrFilter extends SonarrIndexerFilter {
/**
* @param sonarrIndexerService
*/
public SonarrProwlarrFilter(ISonarrIndexerService sonarrIndexerService) {
super(sonarrIndexerService);
}
}

View File

@ -0,0 +1,82 @@
package com.lckp.jproxy.filter.wrapper;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
/**
* <p>
* 重写 HttpServletRequestWrapper 以复用
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-04
*/
public class RequestWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> parameterMap;
private Charset charset;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
parameterMap = new HashMap<>(request.getParameterMap());
String encode = getCharacterEncoding();
try {
charset = Charset.forName(encode);
} catch (Exception e) {
charset = Charset.forName(StandardCharsets.UTF_8.name());
}
}
public void setParameter(String name, String value) {
parameterMap.put(name, new String[] { value });
}
@Override
public Map<String, String[]> getParameterMap() {
return parameterMap;
}
@Override
public Enumeration<String> getParameterNames() {
return new Vector<String>(parameterMap.keySet()).elements();
}
@Override
public String[] getParameterValues(String name) {
return parameterMap.get(name);
}
@Override
public String getParameter(String name) {
String[] values = parameterMap.get(name);
if (values != null && values.length > 0) {
return values[0];
}
return null;
}
@Override
public String getQueryString() {
StringBuilder builder = new StringBuilder();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
builder.append("&");
builder.append(URLEncoder.encode(entry.getKey(), charset));
builder.append("=");
builder.append(URLEncoder.encode(entry.getValue()[0], charset));
}
if (builder.length() > 0) {
builder.replace(0, 1, "");
}
return builder.toString();
}
}

View File

@ -0,0 +1,72 @@
package com.lckp.jproxy.filter.wrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
/**
* <p>
* 重写 HttpServletResponseWrapper 以复用
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-04
*/
public class ResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream buffer;
private ServletOutputStream out;
/**
* @param response
*/
public ResponseWrapper(HttpServletResponse response) {
super(response);
buffer = new ByteArrayOutputStream();
out = new WrapperOutputStream(buffer);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return out;
}
@Override
public void flushBuffer() throws IOException {
if (out != null) {
out.flush();
}
}
public byte[] getContent() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
class WrapperOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos;
public WrapperOutputStream(ByteArrayOutputStream bos) {
this.bos = bos;
}
@Override
public void write(int b) throws IOException {
bos.write(b);
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setWriteListener(WriteListener arg0) {
// do nothing
}
}
}

View File

@ -0,0 +1,40 @@
package com.lckp.jproxy.interceptor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;
import com.lckp.jproxy.service.ISystemUserService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
* 登陆拦截器
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-23
*/
@Slf4j
@RequiredArgsConstructor
public class LoginInterceptor implements HandlerInterceptor {
private final ISystemUserService systemUserService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.debug("登陆拦截器: {}", request.getServletPath());
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
if (systemUserService.verify(token)) {
log.debug("token: {}", token.substring(0, 12) + "******" + token.substring(token.length() - 5));
return true;
}
response.sendError(HttpStatus.FORBIDDEN.value());
return false;
}
}

View File

@ -0,0 +1,16 @@
package com.lckp.jproxy.mapper;
import com.lckp.jproxy.entity.RadarrExample;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* RadarrExample Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-30
*/
public interface RadarrExampleMapper extends BaseMapper<RadarrExample> {
}

View File

@ -0,0 +1,16 @@
package com.lckp.jproxy.mapper;
import com.lckp.jproxy.entity.RadarrRule;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* RadarrRule Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-20
*/
public interface RadarrRuleMapper extends BaseMapper<RadarrRule> {
}

View File

@ -0,0 +1,16 @@
package com.lckp.jproxy.mapper;
import com.lckp.jproxy.entity.RadarrTitle;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* RadarrTitle Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-20
*/
public interface RadarrTitleMapper extends BaseMapper<RadarrTitle> {
}

View File

@ -0,0 +1,16 @@
package com.lckp.jproxy.mapper;
import com.lckp.jproxy.entity.SonarrExample;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* SonarrExample Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-30
*/
public interface SonarrExampleMapper extends BaseMapper<SonarrExample> {
}

View File

@ -0,0 +1,16 @@
package com.lckp.jproxy.mapper;
import com.lckp.jproxy.entity.SonarrRule;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* SonarrRule Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-20
*/
public interface SonarrRuleMapper extends BaseMapper<SonarrRule> {
}

View File

@ -0,0 +1,32 @@
package com.lckp.jproxy.mapper;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lckp.jproxy.entity.SonarrTitle;
/**
* <p>
* SonarrTitle Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-19
*/
public interface SonarrTitleMapper extends BaseMapper<SonarrTitle> {
/**
*
* 查询需要同步 TMBD 标题的 tvdbId
*
* @return List<Integer>
*/
public List<Integer> selectNeedSyncTmdbTitle();
/**
*
* 查询 Sonarr 标题和 TMDB 标题
*
* @return List<SonarrTitle>
*/
public List<SonarrTitle> selectSonarrTitleAndTmdbTitle();
}

View File

@ -0,0 +1,16 @@
package com.lckp.jproxy.mapper;
import com.lckp.jproxy.entity.SystemConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* SystemConfig Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-12
*/
public interface SystemConfigMapper extends BaseMapper<SystemConfig> {
}

View File

@ -0,0 +1,16 @@
package com.lckp.jproxy.mapper;
import com.lckp.jproxy.entity.SystemUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* SystemUser Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-24
*/
public interface SystemUserMapper extends BaseMapper<SystemUser> {
}

View File

@ -0,0 +1,16 @@
package com.lckp.jproxy.mapper;
import com.lckp.jproxy.entity.TmdbTitle;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* TmdbTitle Mapper 接口
* </p>
*
* @author LuckyPuppy514
* @since 2023-03-19
*/
public interface TmdbTitleMapper extends BaseMapper<TmdbTitle> {
}

View File

@ -0,0 +1,40 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 索引器请求参数
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-12
*/
@Getter
@Setter
@Schema(description = "索引器请求参数")
public class IndexerRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "查询关键字")
private String searchKey;
@Schema(description = "查询类型")
private String searchType;
@Schema(description = "季数")
private String seasonNumber;
@Schema(description = "集数")
private String episodeNumber;
@Schema(description = "偏移量")
private Integer offset;
@Schema(description = "数量限制")
private Integer limit;
}

View File

@ -0,0 +1,42 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
*
* <p>
* 分页查询入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-04
*/
@Getter
@Setter
@Schema(description = "分页查询入参")
public class PageRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "页码")
protected long current = 1;
@Schema(description = "页长")
protected long pageSize = 10;
/**
*
* 获取 Mybatis-Plus 分页参数
*
* @param <T>
* @return Page<T>
*/
public <T> Page<T> mybatisPlusPage() {
return new Page<T>().setCurrent(this.current).setSize(this.pageSize);
}
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 电影范例查询入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-27
*/
@Getter
@Setter
@Schema(description = "电影范例查询入参")
public class RadarrExampleQueryRequest extends PageRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "原始文本")
private String originalText;
@Schema(description = "状态")
private Integer validStatus;
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 保存电影范例入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-30
*/
@Getter
@Setter
@Schema(description = "保存电影范例入参")
public class RadarrExampleSaveRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "原始文本")
@NotBlank
private String originalText;
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* Radarr 规则查询入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-27
*/
@Getter
@Setter
@Schema(description = "Radarr 规则查询入参")
public class RadarrRuleQueryRequest extends PageRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "标记")
private String token;
@Schema(description = "备注")
private String remark;
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* Radarr 标题查询入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-25
*/
@Getter
@Setter
@Schema(description = "Radarr 标题查询入参")
public class RadarrTitleQueryRequest extends PageRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "标题")
private String title;
@Schema(description = "TMDB ID")
private Integer tmdbId;
}

View File

@ -0,0 +1,38 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 规则测试入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-29
*/
@Getter
@Setter
@Schema(description = "规则测试入参")
public class RuleTestRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "匹配正则")
@NotBlank
private String regex;
@Schema(description = "替换内容")
@NotBlank
private String replacement;
@Schema(description = "偏移量")
private Integer offset = 0;
@Schema(description = "例子")
@NotBlank
private String example;
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 剧集范例查询入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-27
*/
@Getter
@Setter
@Schema(description = "剧集范例查询入参")
public class SonarrExampleQueryRequest extends PageRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "原始文本")
private String originalText;
@Schema(description = "状态")
private Integer validStatus;
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 保存剧集范例入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-30
*/
@Getter
@Setter
@Schema(description = "保存剧集范例入参")
public class SonarrExampleSaveRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "原始文本")
@NotBlank
private String originalText;
}

View File

@ -0,0 +1,31 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* Sonarr 格式化入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-18
*/
@Getter
@Setter
@Schema(description = "格式化入参")
public class SonarrFormatRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "文本", example = "[ANi] By the Grace of the Gods - 众神眷顾的男人 2 - 10 [1080P][Baha][WEB-DL][AAC AVC][CHT][MP4]")
@NotBlank
private String text;
@Schema(description = "格式", example = "{title} {season}{episode} {language}{resolution}")
@NotBlank
private String format;
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* Sonarr 规则查询入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-27
*/
@Getter
@Setter
@Schema(description = "Sonarr 规则查询入参")
public class SonarrRuleQueryRequest extends PageRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "标记")
private String token;
@Schema(description = "备注")
private String remark;
}

View File

@ -0,0 +1,28 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* Sonarr 标题查询入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-25
*/
@Getter
@Setter
@Schema(description = "Sonarr 标题查询入参")
public class SonarrTitleQueryRequest extends PageRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "标题")
private String title;
@Schema(description = "TVDB 编号")
private Integer tvdbId;
}

View File

@ -0,0 +1,27 @@
package com.lckp.jproxy.model.request;
import java.io.Serializable;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 清除系统缓存入参
* </p>
*
* @author LuckyPuppy514
* @date 2023-03-27
*/
@Getter
@Setter
@Schema(description = "清除系统缓存入参")
public class SystemCacheClearRequest implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "缓存名称", example = "sonarr_rule")
@NotBlank
private String cacheName;
}

Some files were not shown because too many files have changed in this diff Show More