├── .gitattributes
├── .github
└── workflows
│ └── docker-publish.yml
├── .gitignore
├── Dockerfile
├── README.md
├── docker-compose.yml
├── entrypoint.sh
├── log
└── strm.log
├── pom.xml
└── src
└── main
├── java
└── cn
│ └── jackding
│ └── aliststrm
│ ├── AlistStrmApplication.java
│ ├── ScheduledTask.java
│ ├── alist
│ └── AlistService.java
│ ├── config
│ └── Config.java
│ ├── controller
│ └── NotifyController.java
│ ├── service
│ ├── AsynService.java
│ ├── CopyAlistFileService.java
│ └── StrmService.java
│ ├── tg
│ ├── ResponseHandler.java
│ ├── StrmBot.java
│ └── TgSendMsg.java
│ └── util
│ ├── SpringContextUtil.java
│ └── Utils.java
└── resources
├── application.properties
└── log4j2.xml
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/docker-publish.yml:
--------------------------------------------------------------------------------
1 | name: Java CI with Maven docker
2 |
3 | on:
4 | push:
5 | # 分支
6 | branches: [ main ]
7 | pull_request:
8 | branches: [ main ]
9 |
10 | jobs:
11 | compile:
12 | runs-on: ubuntu-latest
13 | name: Running Java ${{ matrix.java }} compile
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: Set up JDK 1.8
17 | uses: actions/setup-java@v3
18 | with:
19 | distribution: 'temurin'
20 | java-version: '8'
21 | cache: 'maven'
22 | - name: 编译代码
23 | run: mvn compile
24 | - name: Deploy the JAR file to the remote server
25 | uses: actions/checkout@v3
26 | - name: Set up JDK 1.8
27 | uses: actions/setup-java@v3
28 | with:
29 | distribution: 'temurin'
30 | java-version: '8'
31 | cache: 'maven'
32 | - name: Generate the package
33 | run: mvn -B package --file pom.xml -Dmaven.test.skip=true
34 | - name: Set up QEMU
35 | uses: docker/setup-qemu-action@v2
36 |
37 | - name: Set up Docker Buildx
38 | uses: docker/setup-buildx-action@v2
39 |
40 | - name: Login to DockerHub
41 | uses: docker/login-action@v2
42 | with:
43 | username: ${{ secrets.DOCKER_USERNAME }}
44 | password: ${{ secrets.DOCKER_PASSWORD }}
45 | - name: Build Latest Image
46 | uses: docker/build-push-action@v3
47 | with:
48 | context: .
49 | file: Dockerfile
50 | platforms: linux/amd64,linux/arm64,linux/arm/v7
51 | push: true
52 | tags: |
53 | ${{ secrets.DOCKER_USERNAME }}/alist-strm:latest
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 | /config
35 | /log
36 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM eclipse-temurin:8u412-b08-jre-jammy
2 | LABEL title="alist-strm"
3 | LABEL description="将alist的视频文件生成媒体播放设备可播放的strm文件"
4 | LABEL authors="JackDing"
5 | RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
6 | WORKDIR /app
7 | COPY ./target/application.jar /app/aliststrm.jar
8 | COPY --chmod=755 entrypoint.sh /entrypoint.sh
9 | ENV TZ=Asia/Shanghai
10 | ENV alistServerUrl=""
11 | ENV alistServerToken=""
12 | ENV alistScanPath=""
13 | ENV isDownSub="0"
14 | ENV slowMode=""
15 | ENV encode="1"
16 | ENV tgToken=""
17 | ENV tgUserId=""
18 | ENV JAVA_OPTS="-Xms32m -Xmx512m"
19 | ENV srcDir=""
20 | ENV dstDir=""
21 | ENV replaceDir=""
22 | ENV strmAfterSync="1"
23 | ENV runAfterStartup="1"
24 | ENV minFileSize="100"
25 | ENV logLevel=""
26 | ENV maxIdleConnections="5"
27 | ENV refresh="1"
28 | ENV scheduledCron="0 0 6,18 * * ?"
29 | ENV PUID=0
30 | ENV PGID=0
31 | ENV UMASK=022
32 | ENTRYPOINT [ "/entrypoint.sh" ]
33 | VOLUME /data
34 | VOLUME /log
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # alist-strm
2 | alist生成可播放strm视频文件
3 |
4 | ## 主要功能
5 |
6 | ```
7 | 1.生成strm文件,启动即执行,定时任务执行,tg机器人执行,接口调用执行
8 | 2.复制同步alist的两个文件夹,tg机器人执行,接口调用执行
9 | 3.可支持第三方app回调,自动化处理,如qb下载完成通知,自动复制alist挂载的本地硬盘复制到云盘然后生成strm文件
10 | ```
11 |
12 | ## docker部署
13 |
14 | ```
15 | 部署前参数需要修改
16 | 必要参数
17 | alistServerUrl alist地址 如http://192.168.1.2:5244
18 | alistServerToken 可在alist后台获取
19 | alistScanPath 需要生成strm文件的目录如http://192.168.1.2:5244/阿里云分享/电影 那就填入/阿里云分享/电影
20 | 可选参数
21 | slowMode 单线程模式,防止请求网盘太快,默认0,启用填1
22 | encode 是否编码strm文件里面的链接 默认1启用 不启用填0
23 | isDownSub 是否下载目录里面的字幕文件 默认0不下载 下载填1
24 | runAfterStartup 启动是否立即执行同步任务 默认启用1,启用填0
25 | logLevel 日志级别 DEBUG INFO ERROR OFF
26 | tgToken tg机器人token,通过t.me/BotFather机器人创建bot获取token
27 | tgUserId tg用户id,通过t.me/userinfobot机器人获取userId
28 | maxIdleConnections HTTP调用线程池参数配置 默认5
29 | refresh参数 是否去读取网盘最新数据,1是实时读取网盘 0是读取alist缓存 默认1
30 | PUID 用户参数 生成strm文件的所属用户
31 | PGID 用户组参数 生成strm文件的所属用户组
32 |
33 | 复制alist不同目录的视频 源目录删除不会删除目标目录文件 只会新增
34 | srcDir 源目录
35 | dstDir 目标目录
36 | minFileSize 复制的最小文件
37 | replaceDir qb的下载根目录 使用/api/v1/notifyByDir接口时需要填
38 | strmAfterSync参数,支持上传完文件不生成strm 默认1生成strm 0不生成strm
39 | scheduledCron 定时任务cron参数,默认0 0 6,18 * * ? 每天6点和18点执行同步任务
40 |
41 | ```
42 |
43 | # 开发计划
44 |
45 | - [x] tg机器人命令生成strm文件
46 | - [ ] ...
47 |
48 | # 更新记录
49 |
50 | ```
51 | 20240610 重构代码,增加tg机器人命令strm、strmdir
52 | 20240617 增加下载目录中字幕文件的功能
53 | 20240617 增加alist目录复制的功能 使用tg机器人/sync命令执行任务
54 | 20240622 执行sync任务之后自动执行strm任务 增加定时任务每天6、18点执行sync任务
55 | 20240623 增加调用接口api/v1/notify直接执行复制sync任务 配合qb使用 监听端口是6894
56 | 20240624 增加/syncdir命令执行指定目录的同步复制,如:/sync /阿里云盘/电影#/115网盘/电影,就会将/阿里云盘/电影下的视频同步复制到/115网盘/电影
57 | 20240630 增加参数minFileSize 默认100 判断视频大小是否大于100MB,如果大于100MB才复制同步文件
58 | 20240724 增加参数配置日志级别 增加复制任务多线程执行
59 | 20240807 增加HTTP调用线程池参数配置maxIdleConnections 默认5
60 | 20240821 增加refresh参数 是否去读取网盘最新数据,1是实时读取网盘 0是读取alist缓存 默认1
61 | 20241107 增加/api/v1/notifyByDir接口和replaceDir参数,按需同步目录,防止同步文件太多,耗时过长
62 | 20241202 增加定时任务cron参数scheduledCron,默认0 0 6,18 * * ? 每天6点和18点执行同步任务
63 | 20250108 优化日志打印
64 | 20250111 复制任务完成之后立即生成strm文件,不用等待所有复制任务完成,自动重试失败的复制任务
65 | 20250115 增加strmAfterSync参数,支持上传完文件不生成strm 默认1生成strm 0不生成strm
66 | 20250424 优化性能
67 | ```
68 |
69 | # docker CLI安装
70 |
71 | ```
72 | docker run -d \
73 | --name=alist-strm \
74 | -e TZ=Asia/Shanghai \
75 | -e alistServerUrl=http://192.168.1.2:5244 \
76 | -e alistServerToken=xxx \
77 | -e alistScanPath='/阿里云分享/电影' \
78 | -e slowMode=0 \
79 | -v /volume1/docker/alist-strm/data:/data \
80 | jacksaoding/alist-strm:latest
81 | ```
82 |
83 | # docker compose安装
84 |
85 | ```
86 | version: "3"
87 | services:
88 | app:
89 | container_name: alist-strm
90 | image: 'jacksaoding/alist-strm:latest'
91 | network_mode: "host"
92 | environment:
93 | TZ: Asia/Shanghai
94 | alistServerUrl: http://192.168.1.2:5244
95 | alistServerToken: xxx
96 | alistScanPath: /阿里云分享/电影
97 | slowMode: 0
98 | volumes:
99 | - /volume1/docker/alist-strm/data:/data
100 | ```
101 |
102 | # qb脚本参考
103 |
104 | `sh /config/notify.sh "%G" "%F"`
105 |
106 | ```
107 | #!/bin/bash
108 |
109 | # 获取传递的标签
110 | TAG=$1
111 | dir=$2
112 | MOVIEPILOT="MOVIEPILOT"
113 |
114 | if [[ "$TAG" =~ "$MOVIEPILOT" ]]; then
115 | # 调用 notify 接口
116 | #curl -X POST http://192.168.31.66:6894/api/v1/notify
117 | curl -X POST -H "Content-Type: application/json" -d "{\"dir\": \"$dir\"}" http://192.168.31.66:6894/api/v1/notifyByDir &>/dev/null &
118 | fi
119 | ```
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | app:
4 | container_name: alist-strm
5 | image: 'jacksaoding/alist-strm:latest'
6 | network_mode: "host"
7 | environment:
8 | TZ: Asia/Shanghai
9 | alistServerUrl: http://192.168.1.2:5244
10 | alistServerToken: xxx
11 | alistScanPath: /阿里云分享/电影
12 | volumes:
13 | - /volume1/docker/alist-strm/data:/data
--------------------------------------------------------------------------------
/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | chown -R "${PUID}":"${PGID}" /app /log
4 | chown "${PUID}":"${PGID}" /data
5 |
6 | cd /app
7 |
8 | umask "${UMASK}"
9 |
10 | exec gosu "${PUID}":"${PGID}" java $JAVA_OPTS -XX:+UseG1GC -XX:+OptimizeStringConcat -XX:+PrintGCDetails -Xloggc:/log/gc.log -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/log -jar /app/aliststrm.jar
11 |
--------------------------------------------------------------------------------
/log/strm.log:
--------------------------------------------------------------------------------
1 | [2024-06-10 20:30:15:190] Starting AlistStrmApplication using Java 1.8.0_311 on Jacskao-Win11 with PID 100524 (D:\idea project\alist-strm\target\classes started by 90773 in D:\idea project\alist-strm)
2 | [2024-06-10 20:30:15:192] No active profile set, falling back to 1 default profile: "default"
3 | [2024-06-10 20:30:15:584] Started AlistStrmApplication in 0.658 seconds (JVM running for 1.908)
4 | [2024-06-10 20:30:16:386] No post action was detected for method with name [commands]
5 | [2024-06-10 20:30:16:388] No post action was detected for method with name [stats]
6 | [2024-06-10 20:30:16:388] No post action was detected for method with name [backup]
7 | [2024-06-10 20:30:16:394] No post action was detected for method with name [recover]
8 | [2024-06-10 20:30:16:395] No post action was detected for method with name [report]
9 | [2024-06-10 20:30:16:395] No post action was detected for method with name [ban]
10 | [2024-06-10 20:30:16:395] No post action was detected for method with name [unban]
11 | [2024-06-10 20:30:16:396] No post action was detected for method with name [promote]
12 | [2024-06-10 20:30:16:396] No post action was detected for method with name [claim]
13 | [2024-06-10 20:30:16:396] No post action was detected for method with name [demote]
14 | [2024-06-10 20:30:16:396] No post action was detected for method with name [strmdir]
15 | [2024-06-10 20:30:16:396] No post action was detected for method with name [strm]
16 | [2024-06-10 20:30:18:147] 开始执行strm任务2024-06-10T20:30:18.146
17 | [2024-06-10 20:30:18:247] 开始获取/每日更新
18 | [2024-06-10 20:30:18:272] 获取完成/每日更新
19 | [2024-06-10 20:30:19:274] 开始获取/每日更新/电影
20 | [2024-06-10 20:30:19:277] 获取完成/每日更新/电影
21 | [2024-06-10 20:30:20:292] 开始获取/每日更新/电影/老狐狸
22 | [2024-06-10 20:30:20:380] 获取完成/每日更新/电影/老狐狸
23 | [2024-06-10 20:30:21:390] 开始获取/每日更新/电视剧
24 | [2024-06-10 20:30:21:471] 获取完成/每日更新/电视剧
25 | [2024-06-10 20:30:21:471] 任务执行完成2024-06-10T20:30:21.471
26 | [2024-06-10 20:30:23:501] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
27 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
28 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?]
29 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?]
30 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?]
31 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?]
32 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?]
33 | [2024-06-10 20:30:28:100] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
34 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
35 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?]
36 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?]
37 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?]
38 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?]
39 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?]
40 | [2024-06-10 20:30:32:694] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
41 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
42 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?]
43 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?]
44 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?]
45 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?]
46 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?]
47 | [2024-06-10 20:30:37:289] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
48 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
49 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?]
50 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?]
51 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?]
52 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?]
53 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?]
54 | [2024-06-10 20:30:41:888] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
55 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
56 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?]
57 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?]
58 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?]
59 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?]
60 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?]
61 | [2024-06-10 20:30:46:494] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
62 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
63 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?]
64 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?]
65 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?]
66 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?]
67 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?]
68 | [2024-06-10 20:30:51:097] Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
69 | org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException: Error executing org.telegram.telegrambots.meta.api.methods.updates.GetUpdates query: [409] Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
70 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseInternal(PartialBotApiMethod.java:53) ~[telegrambots-meta-6.1.0.jar:?]
71 | at org.telegram.telegrambots.meta.api.methods.PartialBotApiMethod.deserializeResponseArray(PartialBotApiMethod.java:38) ~[telegrambots-meta-6.1.0.jar:?]
72 | at org.telegram.telegrambots.meta.api.methods.updates.GetUpdates.deserializeResponse(GetUpdates.java:83) ~[telegrambots-meta-6.1.0.jar:?]
73 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.getUpdatesFromServer(DefaultBotSession.java:260) ~[telegrambots-6.1.0.jar:?]
74 | at org.telegram.telegrambots.updatesreceivers.DefaultBotSession$ReaderThread.run(DefaultBotSession.java:189) ~[telegrambots-6.1.0.jar:?]
75 | [2024-06-10 20:32:50:053] Starting AlistStrmApplication using Java 1.8.0_311 on Jacskao-Win11 with PID 116800 (D:\idea project\alist-strm\target\classes started by 90773 in D:\idea project\alist-strm)
76 | [2024-06-10 20:32:50:055] No active profile set, falling back to 1 default profile: "default"
77 | [2024-06-10 20:32:50:401] Started AlistStrmApplication in 0.582 seconds (JVM running for 1.706)
78 | [2024-06-10 20:32:51:128] No post action was detected for method with name [recover]
79 | [2024-06-10 20:32:51:129] No post action was detected for method with name [stats]
80 | [2024-06-10 20:32:51:129] No post action was detected for method with name [ban]
81 | [2024-06-10 20:32:51:130] No post action was detected for method with name [unban]
82 | [2024-06-10 20:32:51:130] No post action was detected for method with name [promote]
83 | [2024-06-10 20:32:51:130] No post action was detected for method with name [claim]
84 | [2024-06-10 20:32:51:130] No post action was detected for method with name [report]
85 | [2024-06-10 20:32:51:130] No post action was detected for method with name [backup]
86 | [2024-06-10 20:32:51:131] No post action was detected for method with name [demote]
87 | [2024-06-10 20:32:51:131] No post action was detected for method with name [commands]
88 | [2024-06-10 20:32:51:131] No post action was detected for method with name [strm]
89 | [2024-06-10 20:32:51:131] No post action was detected for method with name [strmdir]
90 | [2024-06-10 20:32:52:802] 开始执行strm任务2024-06-10T20:32:52.800
91 | [2024-06-10 20:32:52:884] 开始获取/每日更新
92 | [2024-06-10 20:32:52:907] 获取完成/每日更新
93 | [2024-06-10 20:32:53:917] 开始获取/每日更新/电影
94 | [2024-06-10 20:32:53:920] 获取完成/每日更新/电影
95 | [2024-06-10 20:32:54:935] 开始获取/每日更新/电影/老狐狸
96 | [2024-06-10 20:32:54:938] 获取完成/每日更新/电影/老狐狸
97 | [2024-06-10 20:32:55:949] 开始获取/每日更新/电视剧
98 | [2024-06-10 20:32:56:161] 获取完成/每日更新/电视剧
99 | [2024-06-10 20:32:56:162] 任务执行完成2024-06-10T20:32:56.162
100 | [2024-06-10 20:33:16:490] [bot] New update [212303506] received at 2024-06-10T20:33:16.490+08:00[Asia/Shanghai]
101 | [2024-06-10 20:33:16:491] Update(updateId=212303506, message=Message(messageId=2410, from=User(id=5173469146, firstName=Jacksao, isBot=false, lastName=Ding, userName=JacksaoDing, languageCode=zh-hans, canJoinGroups=null, canReadAllGroupMessages=null, supportInlineQueries=null, isPremium=null, addedToAttachmentMenu=null), date=1718022795, chat=Chat(id=5173469146, type=private, title=null, firstName=Jacksao, lastName=Ding, userName=JacksaoDing, photo=null, description=null, inviteLink=null, pinnedMessage=null, stickerSetName=null, canSetStickerSet=null, permissions=null, slowModeDelay=null, bio=null, linkedChatId=null, location=null, messageAutoDeleteTime=null, hasPrivateForwards=null, HasProtectedContent=null, joinToSendMessages=null, joinByRequest=null), forwardFrom=null, forwardFromChat=null, forwardDate=null, text=/strm, entities=[MessageEntity(type=bot_command, offset=0, length=5, url=null, user=null, language=null, text=/strm)], captionEntities=null, audio=null, document=null, photo=null, sticker=null, video=null, contact=null, location=null, venue=null, animation=null, pinnedMessage=null, newChatMembers=[], leftChatMember=null, newChatTitle=null, newChatPhoto=null, deleteChatPhoto=null, groupchatCreated=null, replyToMessage=null, voice=null, caption=null, superGroupCreated=null, channelChatCreated=null, migrateToChatId=null, migrateFromChatId=null, editDate=null, game=null, forwardFromMessageId=null, invoice=null, successfulPayment=null, videoNote=null, authorSignature=null, forwardSignature=null, mediaGroupId=null, connectedWebsite=null, passportData=null, forwardSenderName=null, poll=null, replyMarkup=null, dice=null, viaBot=null, senderChat=null, proximityAlertTriggered=null, messageAutoDeleteTimerChanged=null, isAutomaticForward=null, hasProtectedContent=null, webAppData=null, videoChatStarted=null, videoChatEnded=null, videoChatParticipantsInvited=null, videoChatScheduled=null), inlineQuery=null, chosenInlineQuery=null, callbackQuery=null, editedMessage=null, channelPost=null, editedChannelPost=null, shippingQuery=null, preCheckoutQuery=null, poll=null, pollAnswer=null, myChatMember=null, chatMember=null, chatJoinRequest=null)
102 | [2024-06-10 20:33:17:135] 开始执行strm任务2024-06-10T20:33:17.135
103 | [2024-06-10 20:33:17:135] 开始获取/每日更新
104 | [2024-06-10 20:33:17:140] 获取完成/每日更新
105 | [2024-06-10 20:33:18:150] 开始获取/每日更新/电影
106 | [2024-06-10 20:33:18:154] 获取完成/每日更新/电影
107 | [2024-06-10 20:33:19:167] 开始获取/每日更新/电影/老狐狸
108 | [2024-06-10 20:33:19:172] 获取完成/每日更新/电影/老狐狸
109 | [2024-06-10 20:33:20:183] 开始获取/每日更新/电视剧
110 | [2024-06-10 20:33:20:251] 获取完成/每日更新/电视剧
111 | [2024-06-10 20:33:20:251] 任务执行完成2024-06-10T20:33:20.251
112 | [2024-06-10 20:33:20:258] [bot] Processing of update [212303506] ended at 2024-06-10T20:33:20.258+08:00[Asia/Shanghai]
113 | ---> Processing time: [3767 ms] <---
114 |
115 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.7.2
9 |
10 |
11 | cn.jackding
12 | alist-strm
13 | 0.0.1-SNAPSHOT
14 | alist-strm
15 | alist-strm
16 |
17 | 1.8
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-logging
27 |
28 |
29 |
30 |
31 |
32 | org.springframework.boot
33 | spring-boot-starter-web
34 |
35 |
36 | org.springframework.boot
37 | spring-boot-starter-logging
38 |
39 |
40 |
41 |
42 |
43 | org.springframework.boot
44 | spring-boot-starter-log4j2
45 |
46 |
47 |
48 |
49 | com.lmax
50 | disruptor
51 | 3.4.2
52 |
53 |
54 |
55 | com.squareup.okhttp3
56 | okhttp
57 | 4.9.3
58 |
59 |
60 |
61 | com.alibaba.fastjson2
62 | fastjson2
63 | 2.0.49
64 |
65 |
66 |
67 | org.projectlombok
68 | lombok
69 | provided
70 |
71 |
72 |
73 | org.telegram
74 | telegrambots
75 | 6.1.0
76 |
77 |
78 |
79 | org.telegram
80 | telegrambots-abilities
81 | 6.1.0
82 |
83 |
84 | eclipse-collections
85 | org.eclipse.collections
86 |
87 |
88 |
89 |
90 | org.eclipse.collections
91 | eclipse-collections
92 | 11.0.0.M1
93 |
94 |
95 |
96 |
97 |
98 |
99 | org.springframework.boot
100 | spring-boot-maven-plugin
101 |
102 |
103 | application
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/main/java/cn/jackding/aliststrm/AlistStrmApplication.java:
--------------------------------------------------------------------------------
1 | package cn.jackding.aliststrm;
2 |
3 | import cn.jackding.aliststrm.config.Config;
4 | import cn.jackding.aliststrm.service.StrmService;
5 | import cn.jackding.aliststrm.tg.StrmBot;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.apache.commons.lang3.StringUtils;
8 | import org.apache.logging.log4j.Level;
9 | import org.apache.logging.log4j.LogManager;
10 | import org.apache.logging.log4j.core.config.Configurator;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.beans.factory.annotation.Value;
13 | import org.springframework.boot.CommandLineRunner;
14 | import org.springframework.boot.SpringApplication;
15 | import org.springframework.boot.autoconfigure.SpringBootApplication;
16 | import org.springframework.scheduling.annotation.EnableAsync;
17 | import org.springframework.scheduling.annotation.EnableScheduling;
18 | import org.telegram.telegrambots.bots.DefaultBotOptions;
19 | import org.telegram.telegrambots.meta.TelegramBotsApi;
20 | import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
21 | import org.telegram.telegrambots.updatesreceivers.DefaultBotSession;
22 |
23 | @SpringBootApplication
24 | @Slf4j
25 | @EnableAsync
26 | @EnableScheduling
27 | public class AlistStrmApplication implements CommandLineRunner {
28 |
29 | @Autowired
30 | private StrmService strmService;
31 |
32 | @Value("${runAfterStartup:1}")
33 | private String runAfterStartup;
34 |
35 | @Value("${logLevel:}")
36 | private String logLevel;
37 |
38 | @Value("${slowMode:0}")
39 | private String slowMode;
40 |
41 | public static void main(String[] args) {
42 | SpringApplication.run(AlistStrmApplication.class, args);
43 | }
44 |
45 | @Override
46 | public void run(String... args) throws Exception {
47 | if (StringUtils.isNotBlank(logLevel)) {
48 | Configurator.setAllLevels(LogManager.getRootLogger().getName(), Level.valueOf(logLevel));
49 | }
50 | if (!"1".equals(slowMode)) {
51 | System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "49");
52 | }
53 | if ("1".equals(runAfterStartup)) {
54 | strmService.strm();
55 | } else {
56 | log.info("启动立即执行任务未启用,等待定时任务处理");
57 | }
58 | if (StringUtils.isBlank(Config.tgUserId) || StringUtils.isBlank(Config.tgToken)) {
59 | return;
60 | }
61 | TelegramBotsApi telegramBotsApi;
62 | try {
63 | telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
64 | } catch (TelegramApiException e) {
65 | log.error("", e);
66 | return;
67 | }
68 | DefaultBotOptions botOptions = new DefaultBotOptions();
69 | // botOptions.setProxyHost(Config.telegramBotProxyHost);
70 | // botOptions.setProxyPort(Config.telegramBotProxyPort);
71 | // botOptions.setProxyType(DefaultBotOptions.ProxyType.HTTP);
72 | //使用AbilityBot创建的事件响应机器人
73 | try {
74 | telegramBotsApi.registerBot(new StrmBot(botOptions));
75 | } catch (TelegramApiException e) {
76 | log.error("", e);
77 | }
78 | }
79 |
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/cn/jackding/aliststrm/ScheduledTask.java:
--------------------------------------------------------------------------------
1 | package cn.jackding.aliststrm;
2 |
3 | import cn.jackding.aliststrm.alist.AlistService;
4 | import cn.jackding.aliststrm.service.CopyAlistFileService;
5 | import com.alibaba.fastjson2.JSONObject;
6 | import lombok.extern.log4j.Log4j2;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.scheduling.annotation.Scheduled;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.util.CollectionUtils;
11 |
12 | import java.util.concurrent.ConcurrentHashMap;
13 |
14 | /**
15 | * @Author Jack
16 | * @Date 2024/5/24 13:32
17 | * @Version 1.0.0
18 | */
19 | @Log4j2
20 | @Service
21 | public class ScheduledTask {
22 |
23 | @Autowired
24 | private CopyAlistFileService copyAlistFileService;
25 |
26 | @Autowired
27 | private AlistService alistService;
28 |
29 | /**
30 | * 每天执行两次
31 | */
32 | @Scheduled(cron = "${scheduledCron:0 0 6,18 * * ?}")
33 | public void syncDaily() {
34 | JSONObject jsonObject = alistService.copyUndone();
35 | if (jsonObject == null || !(200 == jsonObject.getInteger("code"))) {
36 | log.warn("定时任务未执行,因为alist的task/copy/undone服务不可用");
37 | return;
38 | }
39 | if (CollectionUtils.isEmpty(jsonObject.getJSONArray("data"))) {
40 | copyAlistFileService.syncFiles("", ConcurrentHashMap.newKeySet());
41 | } else {
42 | log.warn("定时任务未执行,因为还有正在上传的文件");
43 | }
44 | }
45 |
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/cn/jackding/aliststrm/alist/AlistService.java:
--------------------------------------------------------------------------------
1 | package cn.jackding.aliststrm.alist;
2 |
3 | import cn.jackding.aliststrm.util.Utils;
4 | import com.alibaba.fastjson2.JSONObject;
5 | import lombok.extern.slf4j.Slf4j;
6 | import okhttp3.*;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.stereotype.Service;
9 |
10 | import javax.annotation.PostConstruct;
11 | import java.io.File;
12 | import java.util.List;
13 | import java.util.concurrent.TimeUnit;
14 |
15 | /**
16 | * @Author Jack
17 | * @Date 2024/6/22 18:36
18 | * @Version 1.0.0
19 | */
20 | @Service
21 | @Slf4j
22 | public class AlistService {
23 |
24 | @Value("${alistServerToken}")
25 | private String token;
26 |
27 | @Value("${alistServerUrl}")
28 | private String url;
29 |
30 | @Value("${maxIdleConnections:5}")
31 | private int maxIdleConnections;
32 |
33 | @Value("${refresh:1}")
34 | private String refresh;
35 |
36 | private OkHttpClient client;
37 |
38 | // 在 Bean 初始化时预创建 OkHttpClient
39 | @PostConstruct
40 | public void init() {
41 | client = new OkHttpClient.Builder()
42 | .connectTimeout(90, TimeUnit.SECONDS)
43 | .readTimeout(90, TimeUnit.SECONDS)
44 | .writeTimeout(90, TimeUnit.SECONDS)
45 | .connectionPool(new ConnectionPool(maxIdleConnections, 5, TimeUnit.SECONDS))
46 | .build();
47 | log.info("OkHttpClient initialized successfully.");
48 | }
49 |
50 | public JSONObject getAlist(String path) {
51 | JSONObject jsonResponse = null;
52 |
53 | // 设置请求头
54 | Headers headers = new Headers.Builder()
55 | .add("Content-Type", "application/json")
56 | .add("Accept", "application/json")
57 | .add("Authorization", token)
58 | .build();
59 |
60 | // 构建请求体数据
61 | JSONObject requestBodyJson = new JSONObject();
62 | requestBodyJson.put("path", path);
63 | requestBodyJson.put("password", "");
64 | requestBodyJson.put("page", 1);
65 | requestBodyJson.put("per_page", 0);
66 | if ("1".equals(refresh)) {
67 | requestBodyJson.put("refresh", true);
68 | } else {
69 | requestBodyJson.put("refresh", false);
70 | }
71 | String requestBodyString = requestBodyJson.toJSONString();
72 |
73 | // 构建请求
74 | Request request = new Request.Builder()
75 | .url(url + "/api/fs/list")
76 | .headers(headers)
77 | .post(RequestBody.create(MediaType.parse("application/json"), requestBodyString))
78 | .build();
79 |
80 | log.debug("开始获取alist目录{}", path);
81 | for (int i = 0; i < 3; i++) {
82 | // 发送请求并处理响应
83 | try (Response response = client.newCall(request).execute()) {
84 | if (response.isSuccessful()) {
85 | // 获取响应体
86 | String responseBody = response.body().string();
87 |
88 | // 解析 JSON 响应
89 | jsonResponse = JSONObject.parseObject(responseBody);
90 |
91 | // 处理响应数据
92 | if (200 == jsonResponse.getInteger("code")) {
93 | log.debug("获取alist目录成功{}", path);
94 | return jsonResponse;
95 | } else {
96 | log.info("Response Body: " + jsonResponse.toJSONString());
97 | log.warn("获取alist目录{}第{}次失败", path, i + 1);
98 | Utils.sleep(1);
99 | }
100 |
101 | } else {
102 | log.warn("Request failed with code: {}", response.code());
103 | log.error("Request failed with response :{}", response);
104 | return jsonResponse;
105 | }
106 | } catch (Exception e) {
107 | log.error("获取alist目录失败{}", path);
108 | log.error("", e);
109 | }
110 | }
111 | return jsonResponse;
112 | }
113 |
114 | public JSONObject getFile(String path) {
115 | JSONObject jsonResponse;
116 |
117 | // 设置请求头
118 | Headers headers = new Headers.Builder()
119 | .add("Content-Type", "application/json")
120 | .add("Accept", "application/json")
121 | .add("Authorization", token)
122 | .build();
123 |
124 | // 构建请求体数据
125 | JSONObject requestBodyJson = new JSONObject();
126 | requestBodyJson.put("path", path);
127 | requestBodyJson.put("password", "");
128 | requestBodyJson.put("page", 1);
129 | requestBodyJson.put("per_page", 0);
130 | if ("1".equals(refresh)) {
131 | requestBodyJson.put("refresh", true);
132 | } else {
133 | requestBodyJson.put("refresh", false);
134 | }
135 | String requestBodyString = requestBodyJson.toJSONString();
136 |
137 | // 构建请求
138 | Request request = new Request.Builder()
139 | .url(url + "/api/fs/get")
140 | .headers(headers)
141 | .post(RequestBody.create(MediaType.parse("application/json"), requestBodyString))
142 | .build();
143 |
144 | log.debug("开始获取alist文件{}", path);
145 |
146 | // 发送请求并处理响应
147 | try (Response response = client.newCall(request).execute()) {
148 | if (response.isSuccessful()) {
149 | // 获取响应体
150 | String responseBody = response.body().string();
151 |
152 | // 解析 JSON 响应
153 | jsonResponse = JSONObject.parseObject(responseBody);
154 |
155 |
156 | log.debug("获取alist文件成功{}", path);
157 | return jsonResponse;
158 |
159 |
160 | } else {
161 | log.warn("Request failed with code: {}", response.code());
162 | log.error("Request failed with response :{}", response);
163 | return null;
164 | }
165 | } catch (Exception e) {
166 | log.error("获取alist文件失败{}", path);
167 | log.error("", e);
168 | }
169 |
170 | return null;
171 | }
172 |
173 |
174 | public JSONObject copyAlist(String srcDir, String dstDir, List names) {
175 | JSONObject jsonResponse = null;
176 |
177 | // 设置请求头
178 | Headers headers = new Headers.Builder()
179 | .add("Content-Type", "application/json")
180 | .add("Accept", "application/json")
181 | .add("Authorization", token)
182 | .build();
183 |
184 | // 构建请求体数据
185 | JSONObject requestBodyJson = new JSONObject();
186 | requestBodyJson.put("src_dir", srcDir);
187 | requestBodyJson.put("dst_dir", dstDir);
188 | requestBodyJson.put("names", names);
189 | String requestBodyString = requestBodyJson.toJSONString();
190 |
191 | // 构建请求
192 | Request request = new Request.Builder()
193 | .url(url + "/api/fs/copy")
194 | .headers(headers)
195 | .post(RequestBody.create(MediaType.parse("application/json"), requestBodyString))
196 | .build();
197 |
198 | log.debug("开始复制[{}]=>[{}]", srcDir + File.separator + names.get(0), dstDir);
199 | for (int i = 0; i < 3; i++) {
200 | // 发送请求并处理响应
201 | try (Response response = client.newCall(request).execute()) {
202 | if (response.isSuccessful()) {
203 | // 获取响应体
204 | String responseBody = response.body().string();
205 |
206 | // 解析 JSON 响应
207 | jsonResponse = JSONObject.parseObject(responseBody);
208 |
209 | // 处理响应数据
210 | if (200 == jsonResponse.getInteger("code")) {
211 | log.debug("复制[{}]=>[{}]成功", srcDir + File.separator + names.get(0), dstDir);
212 | return jsonResponse;
213 | } else {
214 | log.warn("Response Body: " + jsonResponse.toJSONString());
215 | log.error("复制[{}]=>[{}]第{}次失败", srcDir + File.separator + names.get(0), dstDir, i + 1);
216 | Utils.sleep(1);
217 | }
218 |
219 | } else {
220 | log.warn("Request failed with code: {}", response.code());
221 | log.error("Request failed with response :{}", response);
222 | return jsonResponse;
223 | }
224 | } catch (Exception e) {
225 | log.error("复制[{}]=>[{}]失败", srcDir + File.separator + names.get(0), dstDir);
226 | log.error("", e);
227 | }
228 | }
229 | return jsonResponse;
230 | }
231 |
232 |
233 | public JSONObject mkdir(String path) {
234 | JSONObject jsonResponse;
235 |
236 | // 设置请求头
237 | Headers headers = new Headers.Builder()
238 | .add("Content-Type", "application/json")
239 | .add("Accept", "application/json")
240 | .add("Authorization", token)
241 | .build();
242 |
243 | // 构建请求体数据
244 | JSONObject requestBodyJson = new JSONObject();
245 | requestBodyJson.put("path", path);
246 | String requestBodyString = requestBodyJson.toJSONString();
247 |
248 | // 构建请求
249 | Request request = new Request.Builder()
250 | .url(url + "/api/fs/mkdir")
251 | .headers(headers)
252 | .post(RequestBody.create(MediaType.parse("application/json"), requestBodyString))
253 | .build();
254 |
255 | log.debug("开始创建alist目录{}", path);
256 |
257 | // 发送请求并处理响应
258 | try (Response response = client.newCall(request).execute()) {
259 | if (response.isSuccessful()) {
260 | // 获取响应体
261 | String responseBody = response.body().string();
262 |
263 | // 解析 JSON 响应
264 | jsonResponse = JSONObject.parseObject(responseBody);
265 |
266 |
267 | log.debug("创建alist目录完成{}", path);
268 | return jsonResponse;
269 |
270 |
271 | } else {
272 | log.warn("Request failed with code: {}", response.code());
273 | log.error("Request failed with response :{}", response);
274 | return null;
275 | }
276 | } catch (Exception e) {
277 | log.warn("创建alist目录失败{}", path);
278 | log.error("", e);
279 | }
280 |
281 | return null;
282 | }
283 |
284 | /**
285 | * 获取未完成的复制任务
286 | *
287 | * @return
288 | */
289 | public JSONObject copyUndone() {
290 | JSONObject jsonResponse;
291 |
292 | // 设置请求头
293 | Headers headers = new Headers.Builder()
294 | .add("Content-Type", "application/json")
295 | .add("Accept", "application/json")
296 | .add("Authorization", token)
297 | .build();
298 |
299 | // 构建请求
300 | Request request = new Request.Builder()
301 | .url(url + "/api/task/copy/undone")
302 | .headers(headers)
303 | .get()
304 | .build();
305 |
306 | // 发送请求并处理响应
307 | try (Response response = client.newCall(request).execute()) {
308 | if (response.isSuccessful()) {
309 | // 获取响应体
310 | String responseBody = response.body().string();
311 |
312 | // 解析 JSON 响应
313 | jsonResponse = JSONObject.parseObject(responseBody);
314 | return jsonResponse;
315 | } else {
316 | log.warn("Request failed with code: {}", response.code());
317 | log.error("Request failed with response :{}", response);
318 | return null;
319 | }
320 | } catch (Exception e) {
321 | log.error("", e);
322 | }
323 | return null;
324 | }
325 |
326 | /**
327 | * 获取复制任务的信息
328 | *
329 | * @return
330 | */
331 | public JSONObject copyInfo(String tid) {
332 | JSONObject jsonResponse;
333 |
334 | // 设置请求头
335 | Headers headers = new Headers.Builder()
336 | .add("Accept", "application/json")
337 | .add("Authorization", token)
338 | .build();
339 |
340 | // 创建请求体,传递参数
341 | RequestBody formBody = new FormBody.Builder()
342 | .add("tid", tid)
343 | .build();
344 |
345 | // 构建请求
346 | Request request = new Request.Builder()
347 | .url(url + "/api/task/copy/info" + "?tid=" + tid)
348 | .headers(headers)
349 | .post(formBody)
350 | .build();
351 |
352 | // 发送请求并处理响应
353 | try (Response response = client.newCall(request).execute()) {
354 | if (response.isSuccessful()) {
355 | // 获取响应体
356 | String responseBody = response.body().string();
357 |
358 | // 解析 JSON 响应
359 | jsonResponse = JSONObject.parseObject(responseBody);
360 | return jsonResponse;
361 | } else {
362 | log.warn("Request failed with code: {}", response.code());
363 | log.error("Request failed with response :{}", response);
364 | return null;
365 | }
366 | } catch (Exception e) {
367 | log.error("", e);
368 | }
369 | return null;
370 | }
371 |
372 | /**
373 | * 获取复制任务的信息
374 | *
375 | * @return
376 | */
377 | public JSONObject copyRetry(String tid) {
378 | JSONObject jsonResponse;
379 |
380 | // 设置请求头
381 | Headers headers = new Headers.Builder()
382 | .add("Accept", "application/json")
383 | .add("Authorization", token)
384 | .build();
385 |
386 | // 创建请求体,传递参数
387 | RequestBody formBody = new FormBody.Builder()
388 | .add("tid", tid)
389 | .build();
390 |
391 | // 构建请求
392 | Request request = new Request.Builder()
393 | .url(url + "/api/task/copy/retry" + "?tid=" + tid)
394 | .headers(headers)
395 | .post(formBody)
396 | .build();
397 |
398 | // 发送请求并处理响应
399 | try (Response response = client.newCall(request).execute()) {
400 | if (response.isSuccessful()) {
401 | // 获取响应体
402 | String responseBody = response.body().string();
403 |
404 | // 解析 JSON 响应
405 | jsonResponse = JSONObject.parseObject(responseBody);
406 | return jsonResponse;
407 | } else {
408 | log.warn("Request failed with code: {}", response.code());
409 | log.error("Request failed with response :{}", response);
410 | return null;
411 | }
412 | } catch (Exception e) {
413 | log.error("", e);
414 | }
415 | return null;
416 | }
417 |
418 | }
419 |
--------------------------------------------------------------------------------
/src/main/java/cn/jackding/aliststrm/config/Config.java:
--------------------------------------------------------------------------------
1 | package cn.jackding.aliststrm.config;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.stereotype.Component;
5 |
6 | /**
7 | * @Author Jack
8 | * @Date 2022/8/2 21:04
9 | * @Version 1.0.0
10 | */
11 | @Component
12 | public class Config {
13 |
14 | public static String tgToken;
15 |
16 | public static String tgUserId;
17 |
18 | @Value("${tgToken:}")
19 | public void setTgToken(String tgToken) {
20 | Config.tgToken = tgToken;
21 | }
22 |
23 | @Value("${tgUserId:}")
24 | public void setTgUserId(String tgUserId) {
25 | Config.tgUserId = tgUserId;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/cn/jackding/aliststrm/controller/NotifyController.java:
--------------------------------------------------------------------------------
1 | package cn.jackding.aliststrm.controller;
2 |
3 | import cn.jackding.aliststrm.service.CopyAlistFileService;
4 | import cn.jackding.aliststrm.util.Utils;
5 | import lombok.extern.slf4j.Slf4j;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.beans.factory.annotation.Value;
8 | import org.springframework.util.StringUtils;
9 | import org.springframework.web.bind.annotation.PostMapping;
10 | import org.springframework.web.bind.annotation.RequestBody;
11 | import org.springframework.web.bind.annotation.RequestMapping;
12 | import org.springframework.web.bind.annotation.RestController;
13 |
14 | import java.util.Map;
15 | import java.util.concurrent.ConcurrentHashMap;
16 |
17 | /**
18 | * @Author Jack
19 | * @Date 2024/6/23 20:34
20 | * @Version 1.0.0
21 | */
22 | @RestController
23 | @RequestMapping("api/v1")
24 | @Slf4j
25 | public class NotifyController {
26 |
27 | @Value("${replaceDir:}")
28 | private String replaceDir;
29 |
30 | @Autowired
31 | private CopyAlistFileService copyAlistFileService;
32 |
33 | @PostMapping("/notify")
34 | public void notifySync() {
35 | copyAlistFileService.syncFiles("", ConcurrentHashMap.newKeySet());
36 | }
37 |
38 | @PostMapping("/notifyByDir")
39 | public void notifyByDir(@RequestBody Map map) {
40 | log.info("map: " + map);
41 | String relativePath = "";
42 | if (StringUtils.hasText(replaceDir) && StringUtils.hasText((CharSequence) map.get("dir"))) {
43 | relativePath = map.get("dir").toString().replaceFirst(replaceDir, "");
44 | if (Utils.isVideo(relativePath)) {
45 | copyAlistFileService.syncOneFile(relativePath);
46 | } else {
47 | copyAlistFileService.syncFiles(relativePath, ConcurrentHashMap.newKeySet());
48 | }
49 | } else {
50 | copyAlistFileService.syncFiles(relativePath, ConcurrentHashMap.newKeySet());
51 | }
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/cn/jackding/aliststrm/service/AsynService.java:
--------------------------------------------------------------------------------
1 | package cn.jackding.aliststrm.service;
2 |
3 | import cn.jackding.aliststrm.alist.AlistService;
4 | import cn.jackding.aliststrm.util.Utils;
5 | import com.alibaba.fastjson2.JSONObject;
6 | import org.apache.commons.lang3.StringUtils;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.scheduling.annotation.Async;
9 | import org.springframework.stereotype.Service;
10 |
11 | import java.util.Set;
12 | import java.util.concurrent.atomic.AtomicBoolean;
13 |
14 | /**
15 | * 异步线程服务
16 | *
17 | * @Author Jack
18 | * @Date 2024/6/23 12:42
19 | * @Version 1.0.0
20 | */
21 | @Service
22 | public class AsynService {
23 |
24 | @Autowired
25 | private AlistService alistService;
26 |
27 | @Autowired
28 | private StrmService strmService;
29 |
30 | private final AtomicBoolean isRun = new AtomicBoolean(false);
31 |
32 | /**
33 | * 判断alist的复制任务是否完成 完成就执行strm任务
34 | *
35 | * @return
36 | * @Async
37 | */
38 | @Async
39 | public void isCopyDone(String dstDir, String strmDir, Set taskIdList) {
40 | if (isRun.get() && StringUtils.isBlank(strmDir)) {
41 | return;
42 | }
43 | isRun.set(true);
44 | Utils.sleep(30);
45 | while (true) {
46 | boolean allTasksCompleted = true;
47 | for (String taskId : taskIdList) {
48 | JSONObject jsonResponse = alistService.copyInfo(taskId);
49 | if (jsonResponse == null) {
50 | continue;
51 | }
52 |
53 | // 检查任务状态
54 | Integer code = jsonResponse.getInteger("code");
55 | Integer state = -1;
56 | if (jsonResponse.getJSONObject("data") != null) {
57 | state = jsonResponse.getJSONObject("data").getInteger("state");
58 | }
59 |
60 | //不是上传成功状态
61 | if (200 == code && state != 2) {
62 | //失败状态了 就重试 状态1是运行中 状态8是等待重试
63 | if (state == 7) {
64 | alistService.copyRetry(taskId);
65 | }
66 | allTasksCompleted = false;
67 | } else if (404 == code || state == 2) {
68 | taskIdList.remove(taskId);
69 | }
70 | }
71 | if (allTasksCompleted) {
72 | isRun.set(false);
73 | strmService.strmDir(dstDir + strmDir);// 生成 STRM 文件
74 | break;// 任务完成,退出循环
75 | } else {
76 | Utils.sleep(30);//继续检查
77 | }
78 | }
79 | }
80 |
81 | @Async
82 | public void isCopyDoneOneFile(String path, String taskId) {
83 | Utils.sleep(30);
84 | while (true) {
85 | JSONObject jsonResponse = alistService.copyInfo(taskId);
86 | if (jsonResponse == null) {
87 | break;
88 | }
89 | // 检查任务状态
90 | Integer code = jsonResponse.getInteger("code");
91 | Integer state = -1;
92 | if (jsonResponse.getJSONObject("data") != null) {
93 | state = jsonResponse.getJSONObject("data").getInteger("state");
94 | }
95 | //判定任务是否完成了 完成了就生成strm文件
96 | if (404 == code || state == 2) {
97 | strmService.strmOneFile(path);// 生成 STRM 文件
98 | break;// 任务完成,退出循环
99 | } else if (state == 7) {
100 | //失败就重试
101 | alistService.copyRetry(taskId);
102 | }
103 | Utils.sleep(30);//继续检查
104 | }
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/cn/jackding/aliststrm/service/CopyAlistFileService.java:
--------------------------------------------------------------------------------
1 | package cn.jackding.aliststrm.service;
2 |
3 | import cn.jackding.aliststrm.alist.AlistService;
4 | import cn.jackding.aliststrm.util.Utils;
5 | import com.alibaba.fastjson2.JSONArray;
6 | import com.alibaba.fastjson2.JSONObject;
7 | import lombok.extern.slf4j.Slf4j;
8 | import org.apache.commons.lang3.StringUtils;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.beans.factory.annotation.Value;
11 | import org.springframework.stereotype.Service;
12 |
13 | import java.util.Collections;
14 | import java.util.Set;
15 | import java.util.concurrent.ConcurrentHashMap;
16 | import java.util.concurrent.atomic.AtomicBoolean;
17 | import java.util.stream.Stream;
18 |
19 | /**
20 | * 复制alist文件
21 | *
22 | * @Author Jack
23 | * @Date 2024/6/22 17:53
24 | * @Version 1.0.0
25 | */
26 | @Service
27 | @Slf4j
28 | public class CopyAlistFileService {
29 |
30 | @Value("${srcDir:}")
31 | private String srcDir;
32 |
33 | @Value("${dstDir:}")
34 | private String dstDir;
35 |
36 | @Autowired
37 | private AlistService alistService;
38 |
39 | @Autowired
40 | private AsynService asynService;
41 |
42 | @Value("${minFileSize:100}")
43 | private String minFileSize;
44 |
45 | @Value("${slowMode:0}")
46 | private String slowMode;
47 |
48 | @Value("${strmAfterSync:1}")
49 | private String strmAfterSync;
50 |
51 | private final Set cache = ConcurrentHashMap.newKeySet();
52 |
53 | public void syncFiles(String srcDir, String dstDir, String relativePath, String strmDir, Set taskIdList) {
54 | if (StringUtils.isAnyBlank(srcDir, dstDir)) {
55 | return;
56 | }
57 | AtomicBoolean flag = new AtomicBoolean(false);
58 | //查出所有源目录
59 | JSONObject object = alistService.getAlist(srcDir + relativePath);
60 | if (object.getJSONObject("data") == null) {
61 | return;
62 | }
63 | JSONArray jsonArray = object.getJSONObject("data").getJSONArray("content");
64 | if (jsonArray == null) {
65 | return;
66 | }
67 | //判断是不是用多线程流
68 | Stream