init:0.4.0
This commit is contained in:
parent
c1365d1539
commit
f1f75bc466
|
|
@ -0,0 +1,20 @@
|
||||||
|
# 基于java镜像创建新镜像
|
||||||
|
FROM adoptopenjdk/openjdk8-openj9:jdk8u412-b08_openj9-0.44.0-alpine-slim
|
||||||
|
# 作者
|
||||||
|
MAINTAINER bcrjl
|
||||||
|
|
||||||
|
EXPOSE 24803
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 创建 bin 目录
|
||||||
|
RUN mkdir -p bin \
|
||||||
|
&& mkdir -p config \
|
||||||
|
&& mkdir -p lib \
|
||||||
|
&& mkdir -p log
|
||||||
|
|
||||||
|
COPY ./target/rss-reader.jar /app/rss-reader.jar
|
||||||
|
COPY ./target/lib/* /app/lib/
|
||||||
|
COPY ./config/* /app/config/
|
||||||
|
|
||||||
|
ENTRYPOINT ["java","-Dloader.path=/app/lib/", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/rss-reader.jar"]
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
[system]
|
||||||
|
## 配置订阅频率
|
||||||
|
refresh=5
|
||||||
|
## 保存微博图片
|
||||||
|
saveWeiBoImages=true
|
||||||
|
## 上传图片到AList
|
||||||
|
uploadAList=false
|
||||||
|
## AList Url
|
||||||
|
aListUrl=
|
||||||
|
## AList 账号
|
||||||
|
aListUser=
|
||||||
|
## AList 密码
|
||||||
|
aListPass=
|
||||||
|
## AList 上传路径
|
||||||
|
aListUploadPath=
|
||||||
|
[mail]
|
||||||
|
## 启用邮件推送
|
||||||
|
enable=false
|
||||||
|
## 启用邮件更新推送
|
||||||
|
sendUpdate=true
|
||||||
|
## 邮件服务器的SMTP地址,可选,默认为smtp.<发件人邮箱后缀>
|
||||||
|
host=smtp.qq.com
|
||||||
|
## 邮件服务器的SMTP端口,可选,默认25
|
||||||
|
port=465
|
||||||
|
## 发件人(必须正确,否则发送失败)
|
||||||
|
from=rss-reply<no-reply@***.com>
|
||||||
|
## 用户名,默认为发件人邮箱前缀
|
||||||
|
user=xxxxxxxxxxxxxx
|
||||||
|
## 密码(注意,某些邮箱需要为SMTP服务单独设置授权码,详情查看相关帮助)
|
||||||
|
pass=
|
||||||
|
## 接收人,多人以英文逗号分割
|
||||||
|
to=
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"rssList": {
|
||||||
|
"weibo": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"other": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
[]
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
version: "2.24.6"
|
||||||
|
services:
|
||||||
|
rss-reader:
|
||||||
|
image: bcrjl/rss-reader:latest
|
||||||
|
container_name: rss-reader
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./config:/app/config
|
||||||
|
- ./log:/app/log
|
||||||
|
- ./images:/app/images
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
environment:
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
ports:
|
||||||
|
- '24803:24803'
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.bcrjl.rss</groupId>
|
||||||
|
<artifactId>rss-reader</artifactId>
|
||||||
|
<version>0.4.0</version>
|
||||||
|
<name>RSS订阅阅读器</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>8</maven.compiler.target>
|
||||||
|
<maven.plugin.version>3.8.1</maven.plugin.version>
|
||||||
|
<maven.assembly.plugin.version>3.3.0</maven.assembly.plugin.version>
|
||||||
|
<maven.dependency.plugin.version>3.2.0</maven.dependency.plugin.version>
|
||||||
|
<maven.resources.plugin.version>3.2.0</maven.resources.plugin.version>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<!-- 统一依赖管理 -->
|
||||||
|
<spring.boot.version>2.7.18</spring.boot.version>
|
||||||
|
<sa-token.version>1.38.0</sa-token.version>
|
||||||
|
<lombok.version>1.18.34</lombok.version>
|
||||||
|
<hutool.version>5.8.29</hutool.version>
|
||||||
|
<javax.mail.version>1.6.2</javax.mail.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<!-- SpringBoot的依赖配置-->
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||||
|
<version>${sa-token.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-jwt</artifactId>
|
||||||
|
<version>${sa-token.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-jwt</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- HuTool -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>${hutool.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- mail -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>javax.mail</artifactId>
|
||||||
|
<version>${javax.mail.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<!-- 打包后的启动jar名称 -->
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<executable>true</executable>
|
||||||
|
<layout>ZIP</layout>
|
||||||
|
<!--这里是填写需要包含进去的jar,
|
||||||
|
必须项目中的某些模块,会经常变动,那么就应该将其坐标写进来
|
||||||
|
如果没有则nothing ,表示不打包依赖 -->
|
||||||
|
<includes>
|
||||||
|
<include>
|
||||||
|
<groupId>nothing</groupId>
|
||||||
|
<artifactId>nothing</artifactId>
|
||||||
|
</include>
|
||||||
|
</includes>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!--拷贝依赖到jar外面的lib目录-->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<!--指定的依赖路径-->
|
||||||
|
<outputDirectory>
|
||||||
|
${project.build.directory}/lib
|
||||||
|
</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<!-- Maven 插件配置 -->
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>nexus</id>
|
||||||
|
<name>nexus-developer</name>
|
||||||
|
<url>https://nexus.ys.bcrjl.com:41010/repository/maven-public/</url>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>nexus</id>
|
||||||
|
<url>https://nexus.ys.bcrjl.com:41010/repository/maven-public/</url>
|
||||||
|
</pluginRepository>
|
||||||
|
</pluginRepositories>
|
||||||
|
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.bcrjl.rss;
|
||||||
|
|
||||||
|
import com.bcrjl.rss.job.FileMonitor;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目启动类
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
*/
|
||||||
|
@EnableScheduling
|
||||||
|
@SpringBootApplication
|
||||||
|
public class WebApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(WebApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package com.bcrjl.rss.cache;
|
||||||
|
|
||||||
|
import cn.hutool.cache.Cache;
|
||||||
|
import cn.hutool.cache.CacheUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存配置
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class ConfigCache {
|
||||||
|
public static Cache<String, Object> fifoCache = CacheUtil.newFIFOCache(10);
|
||||||
|
|
||||||
|
public static Object getConfig(String key) {
|
||||||
|
return fifoCache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setConfig(String key, Object value) {
|
||||||
|
fifoCache.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.bcrjl.rss.common.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用常量
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
*/
|
||||||
|
public interface AppConstant {
|
||||||
|
|
||||||
|
int INIT_MAP = 16;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置路径
|
||||||
|
*/
|
||||||
|
String CONFIG_PATH = System.getProperty("user.dir") + "/config/config.setting";
|
||||||
|
/**
|
||||||
|
* RSS数据路径
|
||||||
|
*/
|
||||||
|
String RSS_DATA_PATH = System.getProperty("user.dir") + "/config/rss-data.json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSS订阅源路径
|
||||||
|
*/
|
||||||
|
String RSS_CONFIG_PATH = System.getProperty("user.dir") + "/config/data.json";
|
||||||
|
String IMAGES_PATH = System.getProperty("user.dir") + "/images/";
|
||||||
|
|
||||||
|
String SET_SYSTEM = "system";
|
||||||
|
String SET_MAIL = "mail";
|
||||||
|
/**
|
||||||
|
* 配置RSS订阅频率
|
||||||
|
*/
|
||||||
|
String REFRESH = "refresh";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存微博图片
|
||||||
|
*/
|
||||||
|
String SAVE_WEIBO_IMAGES = "saveWeiBoImages";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传图片到AList
|
||||||
|
*/
|
||||||
|
String UPLOAD_ALIST = "uploadAList";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AList Url
|
||||||
|
*/
|
||||||
|
String ALIST_URL = "aListUrl";
|
||||||
|
/**
|
||||||
|
* AList 账号
|
||||||
|
*/
|
||||||
|
String ALIST_USER = "aListUser";
|
||||||
|
/**
|
||||||
|
* AList 密码
|
||||||
|
*/
|
||||||
|
String ALIST_PASS = "aListPass";
|
||||||
|
|
||||||
|
String ALIST_UPLOAD_PATH = "aListUploadPath";
|
||||||
|
|
||||||
|
Integer MAIL_CONFIG_SIZE = 7;
|
||||||
|
|
||||||
|
String MAIL_CONFIG_ENABLE = "enable";
|
||||||
|
|
||||||
|
String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.bcrjl.rss.common.util;
|
||||||
|
|
||||||
|
import cn.hutool.cache.CacheUtil;
|
||||||
|
import cn.hutool.cache.impl.TimedCache;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.ContentType;
|
||||||
|
import cn.hutool.http.Header;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import cn.hutool.setting.Setting;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.bcrjl.rss.common.constant.AppConstant.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能描述:
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-10
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class AListUtils {
|
||||||
|
|
||||||
|
private static final String TOKEN_URL = "/api/auth/login";
|
||||||
|
|
||||||
|
private static final String UPLOAD_URL = "/api/fs/put";
|
||||||
|
|
||||||
|
private static TimedCache<String, String> timedCache = CacheUtil.newTimedCache(86400000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取AList Token
|
||||||
|
*
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
private static String getToken() {
|
||||||
|
try {
|
||||||
|
String alistToken = timedCache.get("alist_token");
|
||||||
|
if (StrUtil.isNotEmpty(alistToken)) {
|
||||||
|
return alistToken;
|
||||||
|
} else {
|
||||||
|
Setting setting = new Setting(CONFIG_PATH, CharsetUtil.CHARSET_UTF_8, true);
|
||||||
|
Setting systemSetting = setting.getSetting(SET_SYSTEM);
|
||||||
|
Map<String, String> params = new HashMap<>(INIT_MAP);
|
||||||
|
params.put("username", systemSetting.get(ALIST_USER));
|
||||||
|
params.put("password", systemSetting.get(ALIST_PASS));
|
||||||
|
String aListUrl = systemSetting.get(ALIST_URL);
|
||||||
|
String body = HttpRequest.post(aListUrl + TOKEN_URL)
|
||||||
|
.header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
|
||||||
|
.body(JSONUtil.toJsonStr(params))
|
||||||
|
.execute().body();
|
||||||
|
String token = JSONUtil.parseObj(body).getJSONObject("data").getStr("token");
|
||||||
|
timedCache.put("alist_token", token);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取AList Token异常:", e);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void uploadFile(byte[] fileByte, String fileName) {
|
||||||
|
try {
|
||||||
|
String time = DateUtil.format(new Date(), "yyyyMMddHH");
|
||||||
|
Setting setting = new Setting(CONFIG_PATH, CharsetUtil.CHARSET_UTF_8, true);
|
||||||
|
Setting systemSetting = setting.getSetting(SET_SYSTEM);
|
||||||
|
String aListUrl = systemSetting.get(ALIST_URL);
|
||||||
|
String uploadPath = systemSetting.get(ALIST_UPLOAD_PATH);
|
||||||
|
HttpResponse httpResponse = HttpRequest.put(aListUrl + UPLOAD_URL)
|
||||||
|
.header(Header.AUTHORIZATION, getToken())
|
||||||
|
.header(Header.CONTENT_TYPE, ContentType.MULTIPART.getValue())
|
||||||
|
.header("File-Path", uploadPath + "/" + time + "/" + fileName)
|
||||||
|
.body(fileByte)
|
||||||
|
.execute();
|
||||||
|
if (httpResponse.isOk()) {
|
||||||
|
String message = JSONUtil.parseObj(httpResponse.body()).getStr("message");
|
||||||
|
if ("success".equals(message)) {
|
||||||
|
//log.info("AList上传成功");
|
||||||
|
} else {
|
||||||
|
log.info("AList上传失败:{}", message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info("AList上传失败,响应状态码:{}", httpResponse.getStatus());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("AList上传文件异常:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.bcrjl.rss.common.util;
|
||||||
|
|
||||||
|
import cn.hutool.http.Header;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static com.bcrjl.rss.common.constant.AppConstant.USER_AGENT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Html 工具类
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class HtmlParseUtils {
|
||||||
|
/**
|
||||||
|
* 获取html中的图片
|
||||||
|
*
|
||||||
|
* @param htmlContent html内容
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<String> extractImageUrls(String htmlContent) {
|
||||||
|
List<String> imageUrls = new ArrayList<>();
|
||||||
|
String regex = "<img\\s+[^>]*?src\\s*=\\s*['\"]([^'\"]*?)['\"][^>]*?>";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(htmlContent);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String imageUrl = matcher.group(1);
|
||||||
|
imageUrls.add(imageUrl);
|
||||||
|
}
|
||||||
|
return imageUrls;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取微博图片流文件
|
||||||
|
*
|
||||||
|
* @param fileName 微博图片名称
|
||||||
|
* @return HttpResponse
|
||||||
|
*/
|
||||||
|
public static HttpResponse getWeiBoImagesHttpRequest(String fileName) {
|
||||||
|
try {
|
||||||
|
String url = "https://tvax3.sinaimg.cn/large/" + fileName;
|
||||||
|
HttpRequest request = HttpRequest.get(url)
|
||||||
|
.header(Header.REFERER, "https://weibo.com/")
|
||||||
|
.header(Header.USER_AGENT, USER_AGENT)
|
||||||
|
.timeout(20000);
|
||||||
|
return request.executeAsync();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取微博图片数据异常:", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.bcrjl.rss.common.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.extra.mail.MailAccount;
|
||||||
|
import cn.hutool.setting.Setting;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import static com.bcrjl.rss.common.constant.AppConstant.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 邮件工具
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class MailUtils {
|
||||||
|
public static MailAccount initMailAccount() {
|
||||||
|
Setting setting = new Setting(CONFIG_PATH, CharsetUtil.CHARSET_UTF_8, true);
|
||||||
|
Setting emailSetting = setting.getSetting(SET_MAIL);
|
||||||
|
if (emailSetting.size() >= MAIL_CONFIG_SIZE) {
|
||||||
|
MailAccount mailAccount = new MailAccount();
|
||||||
|
mailAccount.setHost(emailSetting.getWithLog("host"));
|
||||||
|
mailAccount.setPort(Integer.valueOf(emailSetting.getWithLog("port")));
|
||||||
|
mailAccount.setAuth(true);
|
||||||
|
mailAccount.setFrom(emailSetting.getWithLog("from"));
|
||||||
|
mailAccount.setUser(emailSetting.getWithLog("user"));
|
||||||
|
mailAccount.setPass(emailSetting.getWithLog("pass"));
|
||||||
|
mailAccount.setSslEnable(true);
|
||||||
|
mailAccount.setDebug(false);
|
||||||
|
return mailAccount;
|
||||||
|
} else {
|
||||||
|
log.error("请配置邮箱信息");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.bcrjl.rss.common.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import com.bcrjl.rss.model.entity.RssEntity;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSS 订阅工具类
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class RssUtils {
|
||||||
|
|
||||||
|
public static List<RssEntity> getRssList(String url) {
|
||||||
|
List<RssEntity> rssList = new ArrayList<>();
|
||||||
|
log.info("开始订阅:{}", url);
|
||||||
|
try {
|
||||||
|
//URL rssUrl = new URL(url);
|
||||||
|
HttpResponse httpResponse = HttpRequest.get(url).executeAsync();
|
||||||
|
if(httpResponse.isOk()){
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
Document document = builder.parse(httpResponse.bodyStream());
|
||||||
|
NodeList items = document.getElementsByTagName("item");
|
||||||
|
Node webTitle = document.getElementsByTagName("title").item(0);
|
||||||
|
for (int i = 0; i < items.getLength(); i++) {
|
||||||
|
Element item = (Element) items.item(i);
|
||||||
|
Element title = (Element) item.getElementsByTagName("title").item(0);
|
||||||
|
Element link = (Element) item.getElementsByTagName("link").item(0);
|
||||||
|
Element description = (Element) item.getElementsByTagName("description").item(0);
|
||||||
|
Element pubDate = (Element) item.getElementsByTagName("pubDate").item(0);
|
||||||
|
rssList.add(RssEntity.builder()
|
||||||
|
.webTitle(webTitle.getTextContent())
|
||||||
|
.url(url)
|
||||||
|
.title(title.getTextContent())
|
||||||
|
.link(link.getTextContent())
|
||||||
|
.description(description.getTextContent())
|
||||||
|
.createTime(DateUtil.parse(pubDate.getTextContent(), DatePattern.HTTP_DATETIME_FORMAT))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("地址:{}获取RSS订阅内容异常:", url, e);
|
||||||
|
}
|
||||||
|
return rssList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.bcrjl.rss.config;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.setting.Setting;
|
||||||
|
import com.bcrjl.rss.job.RssJob;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.scheduling.Trigger;
|
||||||
|
import org.springframework.scheduling.TriggerContext;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||||
|
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||||
|
import org.springframework.scheduling.support.PeriodicTrigger;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static com.bcrjl.rss.common.constant.AppConstant.REFRESH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态配置订阅
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-06
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@EnableScheduling
|
||||||
|
public class DynamicScheduleConfig implements SchedulingConfigurer {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RssJob rssJob;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认五分钟执行一次
|
||||||
|
*/
|
||||||
|
private Long timer = 5L * 1000L * 60L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
|
||||||
|
taskRegistrar.addTriggerTask(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
rssJob.subscribe();
|
||||||
|
}
|
||||||
|
}, new Trigger() {
|
||||||
|
@Override
|
||||||
|
public Date nextExecutionTime(TriggerContext triggerContext) {
|
||||||
|
log.info("当前执行速度:{}分钟", timer / 1000L / 60L);
|
||||||
|
PeriodicTrigger periodicTrigger = new PeriodicTrigger(timer);
|
||||||
|
Date nextExecutionTime = periodicTrigger.nextExecutionTime(triggerContext);
|
||||||
|
return nextExecutionTime;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.bcrjl.rss.config;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
|
||||||
|
import cn.dev33.satoken.stp.StpLogic;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sa-Token 配置
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class SaTokenConfigure {
|
||||||
|
/**
|
||||||
|
* Sa-Token 整合 jwt (Simple 简单模式)
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public StpLogic getStpLogicJwt() {
|
||||||
|
return new StpLogicJwtForSimple();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.bcrjl.rss.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-03
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebConfigurer extends WebMvcConfigurationSupport {
|
||||||
|
/**
|
||||||
|
* 跨域过滤
|
||||||
|
*
|
||||||
|
* @return FilterRegistrationBean
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public FilterRegistrationBean corsFilter() {
|
||||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", corsConfig());
|
||||||
|
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
|
||||||
|
//*****这里设置了优先级*****
|
||||||
|
bean.setOrder(1);
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CorsConfiguration corsConfig() {
|
||||||
|
CorsConfiguration corsConfiguration = new CorsConfiguration();
|
||||||
|
//corsConfiguration.addAllowedOrigin("*");
|
||||||
|
corsConfiguration.addAllowedOriginPattern("*");
|
||||||
|
corsConfiguration.addAllowedHeader("*");
|
||||||
|
corsConfiguration.addAllowedMethod("*");
|
||||||
|
corsConfiguration.setAllowCredentials(true);
|
||||||
|
corsConfiguration.setMaxAge(3600L);
|
||||||
|
return corsConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.bcrjl.rss.controller;
|
||||||
|
|
||||||
|
import com.bcrjl.rss.common.util.RssUtils;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-03
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/rss")
|
||||||
|
public class RssController {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.bcrjl.rss.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.setting.Setting;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import static com.bcrjl.rss.common.constant.AppConstant.CONFIG_PATH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-07
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system")
|
||||||
|
public class SystemController {
|
||||||
|
|
||||||
|
@GetMapping("/config")
|
||||||
|
public Object getConfig(){
|
||||||
|
String str = ResourceUtil.readUtf8Str(CONFIG_PATH);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
package com.bcrjl.rss.job;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.file.FileWriter;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import cn.hutool.setting.Setting;
|
||||||
|
import com.bcrjl.rss.cache.ConfigCache;
|
||||||
|
import com.bcrjl.rss.config.DynamicScheduleConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static com.bcrjl.rss.common.constant.AppConstant.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监控配置文件
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-06
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class FileMonitor {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DynamicScheduleConfig scheduledTask;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initConfig() {
|
||||||
|
existFile("config.setting", "[system]\n" +
|
||||||
|
"## 配置订阅频率\n" +
|
||||||
|
"refresh = 5\n" +
|
||||||
|
"## 保存微博图片\n" +
|
||||||
|
"saveWeiBoImages=false\n" +
|
||||||
|
"## 上传图片到AList\n" +
|
||||||
|
"uploadAList=false\n" +
|
||||||
|
"## AList Url\n" +
|
||||||
|
"aListUrl=\n" +
|
||||||
|
"## AList 账号\n" +
|
||||||
|
"aListUser=\n" +
|
||||||
|
"## AList 密码\n" +
|
||||||
|
"aListPass=\n" +
|
||||||
|
"## AList 上传路径\n" +
|
||||||
|
"aListUploadPath=" +
|
||||||
|
"[mail]\n" +
|
||||||
|
"## 启用邮件推送\n" +
|
||||||
|
"enable=false\n" +
|
||||||
|
"## 启用邮件更新推送\n" +
|
||||||
|
"sendUpdate=true\n" +
|
||||||
|
"## 邮件服务器的SMTP地址,可选,默认为smtp.<发件人邮箱后缀>\n" +
|
||||||
|
"host=smtp.qq.com\n" +
|
||||||
|
"## 邮件服务器的SMTP端口,可选,默认25\n" +
|
||||||
|
"port=465\n" +
|
||||||
|
"## 发件人(必须正确,否则发送失败)\n" +
|
||||||
|
"from=rss-reply<no-reply@***.com>\n" +
|
||||||
|
"## 用户名,默认为发件人邮箱前缀\n" +
|
||||||
|
"user=xxxxxxxxxxxxxx\n" +
|
||||||
|
"## 密码(注意,某些邮箱需要为SMTP服务单独设置授权码,详情查看相关帮助)\n" +
|
||||||
|
"pass=\n" +
|
||||||
|
"## 接收人,多人以英文逗号分割\n" +
|
||||||
|
"to=\n");
|
||||||
|
Map<String, Object> dataJson = new HashMap<>();
|
||||||
|
Map<String, Object> rssList = new HashMap<>();
|
||||||
|
rssList.put("weibo",new ArrayList<>());
|
||||||
|
rssList.put("other",new ArrayList<>());
|
||||||
|
dataJson.put("rssList",rssList);
|
||||||
|
existFile("data.json", JSONUtil.toJsonPrettyStr(dataJson));
|
||||||
|
existFile("rss-data.json", "[]");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void existFile(String fileName, String fileContent) {
|
||||||
|
fileName = System.getProperty("user.dir") + "/config/" + fileName;
|
||||||
|
File file = FileUtil.file(fileName);
|
||||||
|
if (!FileUtil.exist(file)) {
|
||||||
|
file = FileUtil.touch(fileName);
|
||||||
|
FileWriter dataFile = new FileWriter(file);
|
||||||
|
dataFile.write(fileContent, false);
|
||||||
|
log.info("监听配置文件不存在,系统已自动创建");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件监听
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0/10 * * * * ?")
|
||||||
|
public void configMonitor() {
|
||||||
|
Setting setting = new Setting(CONFIG_PATH, CharsetUtil.CHARSET_UTF_8, true);
|
||||||
|
Long refresh = Long.valueOf(setting.getByGroup(REFRESH, SET_SYSTEM));
|
||||||
|
Long config = (Long) ConfigCache.getConfig(REFRESH);
|
||||||
|
if (Objects.isNull(config)) {
|
||||||
|
ConfigCache.setConfig(REFRESH, refresh);
|
||||||
|
config = refresh;
|
||||||
|
}
|
||||||
|
if (!config.equals(refresh)) {
|
||||||
|
log.info("配置变动,更新频率:{}分钟", refresh);
|
||||||
|
scheduledTask.setTimer(refresh * 60 * 1000L);
|
||||||
|
ConfigCache.setConfig(REFRESH, refresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
package com.bcrjl.rss.job;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.file.FileWriter;
|
||||||
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.extra.mail.MailAccount;
|
||||||
|
import cn.hutool.extra.mail.MailUtil;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import cn.hutool.setting.Setting;
|
||||||
|
import com.bcrjl.rss.common.util.AListUtils;
|
||||||
|
import com.bcrjl.rss.common.util.HtmlParseUtils;
|
||||||
|
import com.bcrjl.rss.common.util.MailUtils;
|
||||||
|
import com.bcrjl.rss.common.util.RssUtils;
|
||||||
|
import com.bcrjl.rss.model.entity.RssEntity;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static com.bcrjl.rss.common.constant.AppConstant.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSS 任务
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class RssJob {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSS 订阅
|
||||||
|
*/
|
||||||
|
public void subscribe() {
|
||||||
|
if (!FileUtil.exist(RSS_DATA_PATH)) {
|
||||||
|
FileUtil.touch(RSS_DATA_PATH);
|
||||||
|
FileWriter dataFile = new FileWriter(RSS_DATA_PATH);
|
||||||
|
dataFile.write("[]");
|
||||||
|
}
|
||||||
|
JSONObject rssListObj = JSONUtil.parseObj(ResourceUtil.readUtf8Str(RSS_CONFIG_PATH)).getJSONObject("rssList");
|
||||||
|
if (Objects.nonNull(rssListObj)) {
|
||||||
|
List<RssEntity> dbList = JSONUtil.toList(ResourceUtil.readUtf8Str(RSS_DATA_PATH), RssEntity.class);
|
||||||
|
List<String> weiboRssList = JSONUtil.toList(rssListObj.getStr("weibo"), String.class);
|
||||||
|
List<RssEntity> saveList = new ArrayList<>();
|
||||||
|
weiboRssList.forEach(obj -> {
|
||||||
|
List<RssEntity> rssList = RssUtils.getRssList(obj);
|
||||||
|
saveList.addAll(rssList);
|
||||||
|
});
|
||||||
|
List<String> otherRssList = JSONUtil.toList(rssListObj.getStr("other"), String.class);
|
||||||
|
otherRssList.forEach(obj -> {
|
||||||
|
List<RssEntity> rssList = RssUtils.getRssList(obj);
|
||||||
|
saveList.addAll(rssList);
|
||||||
|
});
|
||||||
|
if (CollUtil.isEmpty(dbList)) {
|
||||||
|
FileWriter dataFile = new FileWriter(RSS_DATA_PATH);
|
||||||
|
dataFile.write(JSONUtil.toJsonPrettyStr(saveList));
|
||||||
|
sendEmailReply(saveList);
|
||||||
|
} else {
|
||||||
|
List<String> dbIds = dbList.stream().map(RssEntity::getLink).collect(Collectors.toList());
|
||||||
|
// 获取库中不包含的数据 则为新增数据
|
||||||
|
List<RssEntity> insertList = saveList.stream()
|
||||||
|
.filter(t -> !dbIds.contains(t.getLink())).collect(Collectors.toList());
|
||||||
|
dbList.addAll(insertList);
|
||||||
|
FileWriter dataFile = new FileWriter(RSS_DATA_PATH);
|
||||||
|
dataFile.write(JSONUtil.toJsonPrettyStr(dbList));
|
||||||
|
sendEmailReply(insertList);
|
||||||
|
log.info("本次新增了{}条推送内容", insertList.size());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.info("未配置订阅信息");
|
||||||
|
}
|
||||||
|
log.info("订阅数据结束!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送邮件通知
|
||||||
|
*
|
||||||
|
* @param list rssList
|
||||||
|
*/
|
||||||
|
private void sendEmailReply(List<RssEntity> list) {
|
||||||
|
if (CollUtil.isNotEmpty(list)) {
|
||||||
|
Setting setting = new Setting(CONFIG_PATH, CharsetUtil.CHARSET_UTF_8, true);
|
||||||
|
Setting emailSetting = setting.getSetting(SET_MAIL);
|
||||||
|
saveWeiBoImagesOrUpdateAlist(list);
|
||||||
|
if (emailSetting.getBool(MAIL_CONFIG_ENABLE) && emailSetting.getBool("sendUpdate")) {
|
||||||
|
// 如果邮箱开启且发送更新邮件开启 则推送通知
|
||||||
|
StringBuffer stringBuffer = new StringBuffer();
|
||||||
|
list.forEach(obj -> {
|
||||||
|
stringBuffer.append("<div>");
|
||||||
|
stringBuffer.append("<a href='").append(obj.getLink()).append("'>").append(obj.getWebTitle()).append("--").append(obj.getTitle()).append("</a></br>");
|
||||||
|
stringBuffer.append("</div>");
|
||||||
|
});
|
||||||
|
MailAccount mailAccount = MailUtils.initMailAccount();
|
||||||
|
List<String> toList = Arrays.asList(emailSetting.getStr("to").split(","));
|
||||||
|
if (CollUtil.isNotEmpty(toList)) {
|
||||||
|
MailUtil.send(mailAccount, toList, "RSS订阅推送", stringBuffer.toString(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存微博图片到本地且上传AList
|
||||||
|
*/
|
||||||
|
private void saveWeiBoImagesOrUpdateAlist(List<RssEntity> list) {
|
||||||
|
Setting setting = new Setting(CONFIG_PATH, CharsetUtil.CHARSET_UTF_8, true);
|
||||||
|
Setting systemSetting = setting.getSetting(SET_SYSTEM);
|
||||||
|
Boolean saveImages = Boolean.valueOf(systemSetting.get(SAVE_WEIBO_IMAGES));
|
||||||
|
Boolean uploadAList = Boolean.valueOf(systemSetting.get(UPLOAD_ALIST));
|
||||||
|
if (saveImages) {
|
||||||
|
// 保存图片
|
||||||
|
list.forEach(obj -> {
|
||||||
|
List<String> imgList = HtmlParseUtils.extractImageUrls(obj.getDescription());
|
||||||
|
imgList.forEach(imgObj -> {
|
||||||
|
if (imgObj.contains("sinaimg") && !imgObj.contains("timeline_card") && !imgObj.contains("qixi2018")) {
|
||||||
|
int lastSlashIndex = imgObj.lastIndexOf('/');
|
||||||
|
// 如果找到了斜杠,就从斜杠后面截取字符串
|
||||||
|
String fileName = imgObj.substring(lastSlashIndex + 1);
|
||||||
|
//log.info("微博图片文件名:{}", fileName);
|
||||||
|
HttpResponse weiBoImagesHttpRequest = HtmlParseUtils.getWeiBoImagesHttpRequest(fileName);
|
||||||
|
byte[] bytes = weiBoImagesHttpRequest.bodyBytes();
|
||||||
|
FileUtil.writeBytes(bytes, new File(IMAGES_PATH + fileName));
|
||||||
|
if (uploadAList) {
|
||||||
|
AListUtils.uploadFile(bytes, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.bcrjl.rss.model.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DataEntity {
|
||||||
|
/**
|
||||||
|
* rss订阅链接(必填)
|
||||||
|
*/
|
||||||
|
private RssListEntity rssList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rss订阅更新时间间隔,单位分钟(必填)
|
||||||
|
*/
|
||||||
|
private Long refresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RssListEntity {
|
||||||
|
private List<String> weibo;
|
||||||
|
private List<String> other;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.bcrjl.rss.model.entity;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-03
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class RssEntity {
|
||||||
|
/**
|
||||||
|
* 订阅地址
|
||||||
|
*/
|
||||||
|
private String url;
|
||||||
|
/**
|
||||||
|
* 源名称
|
||||||
|
*/
|
||||||
|
private String webTitle;
|
||||||
|
/**
|
||||||
|
* 标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
/**
|
||||||
|
* 链接
|
||||||
|
*/
|
||||||
|
private String link;
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
server:
|
||||||
|
# 服务端口
|
||||||
|
port: 24803
|
||||||
|
# 是否启用响应压缩,默认为 false
|
||||||
|
compression:
|
||||||
|
enabled: true
|
||||||
|
# 启用压缩的最小响应大小(字节),默认为 2048
|
||||||
|
min-response-size: 1024
|
||||||
|
tomcat:
|
||||||
|
# 最大连接数,默认为 10000
|
||||||
|
max-connections: 5000
|
||||||
|
# 最大线程数,默认为 200
|
||||||
|
max-threads: 100
|
||||||
|
# 接受队列长度,默认为 100
|
||||||
|
accept-count: 200
|
||||||
|
# 连接超时时间(毫秒),默认为 20000
|
||||||
|
connection-timeout: 30000
|
||||||
|
# 长连接的空闲超时时间(毫秒),默认为 20000
|
||||||
|
keep-alive-timeout: 30000
|
||||||
|
# 最大允许上传的文件大小(字节),默认为 2MB
|
||||||
|
max-swallow-size: 10485760
|
||||||
|
# 最大允许的单个文件大小(字节),默认为 1MB
|
||||||
|
max-file-size: 5242880
|
||||||
|
# 最大允许的请求大小(字节),默认为 10MB
|
||||||
|
max-request-size: 10485760
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: rss-backend
|
||||||
|
profiles:
|
||||||
|
active: rss
|
||||||
|
web:
|
||||||
|
resources:
|
||||||
|
static-locations: classpath:/static/
|
||||||
|
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
|
||||||
|
sa-token:
|
||||||
|
# token 名称(同时也是 cookie 名称)
|
||||||
|
token-name: Authorization
|
||||||
|
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
|
||||||
|
timeout: 2592000
|
||||||
|
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||||
|
active-timeout: -1
|
||||||
|
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||||
|
is-concurrent: false
|
||||||
|
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||||
|
is-share: true
|
||||||
|
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||||
|
token-style: uuid
|
||||||
|
# 是否输出操作日志
|
||||||
|
is-log: false
|
||||||
|
|
||||||
|
log:
|
||||||
|
path: log/
|
||||||
|
encoder: UTF-8
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
█ █ █
|
||||||
|
█ █ █
|
||||||
|
█ █ █
|
||||||
|
█▒██▒ ▒███▒ ▒███▒ █▓██ ░███░ ▓██▒ █ ▒█ ███ █▒██▒ ██▓█
|
||||||
|
██ █ █▒ ░█ █▒ ░█ █▓ ▓█ █▒ ▒█ ▓█ ▓ █ ▒█ ▓▓ ▒█ █▓ ▒█ █▓ ▓█
|
||||||
|
█ █▒░ █▒░ █ █ █ █░ █▒█ █ █ █ █ █ █
|
||||||
|
█ ░███▒ ░███▒ ███ █ █ ▒████ █ ██▓ █████ █ █ █ █
|
||||||
|
█ ▒█ ▒█ █ █ █▒ █ █░ █░█░ █ █ █ █ █
|
||||||
|
█ █░ ▒█ █░ ▒█ █▓ ▓█ █░ ▓█ ▓█ ▓ █ ░█ ▓▓ █ █ █ █▓ ▓█
|
||||||
|
█ ▒███▒ ▒███▒ █▓██ ▒██▒█ ▓██▒ █ ▒█ ███▒ █ █ ██▓█
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<property name="LOG_CONTEXT_NAME" value="log"/>
|
||||||
|
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
|
||||||
|
<property name="LOG_HOME" value="${LOG_CONTEXT_NAME}"/>
|
||||||
|
<!-- 定义日志上下文的名称 -->
|
||||||
|
<contextName>${LOG_CONTEXT_NAME}</contextName>
|
||||||
|
<!-- 控制台输出 -->
|
||||||
|
|
||||||
|
<!--<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
|
||||||
|
<!–格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符–>
|
||||||
|
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-5level) %cyan(%logger{50}:%L) - %msg%n</pattern>
|
||||||
|
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
|
||||||
|
<level>INFO</level>
|
||||||
|
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
</appender>-->
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 彩色日志依赖的渲染类 -->
|
||||||
|
|
||||||
|
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
|
||||||
|
|
||||||
|
<conversionRule conversionWord="wex"
|
||||||
|
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
|
||||||
|
|
||||||
|
<conversionRule conversionWord="wEx"
|
||||||
|
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
|
||||||
|
|
||||||
|
<!-- 彩色日志格式 -->
|
||||||
|
|
||||||
|
<property name="CONSOLE_LOG_PATTERN"
|
||||||
|
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
|
||||||
|
|
||||||
|
|
||||||
|
<!--1. 输出到控制台-->
|
||||||
|
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
|
||||||
|
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
|
||||||
|
|
||||||
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
|
|
||||||
|
<level>INFO</level>
|
||||||
|
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
<encoder>
|
||||||
|
|
||||||
|
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
|
||||||
|
|
||||||
|
<!-- 设置字符集 -->
|
||||||
|
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!--info日志统一输出到这里-->
|
||||||
|
|
||||||
|
<appender name="file.info" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
|
||||||
|
<Prudent>true</Prudent>
|
||||||
|
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
|
||||||
|
<!--日志文件输出的文件名,按小时生成-->
|
||||||
|
|
||||||
|
<FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/info/info.%d{yyyy-MM-dd-HH}.%i.log</FileNamePattern>
|
||||||
|
|
||||||
|
<!--日志文件保留天数-->
|
||||||
|
|
||||||
|
<MaxHistory>30</MaxHistory>
|
||||||
|
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
|
||||||
|
<!-- 除按日志记录之外,还配置了日志文件不能超过10M(默认),若超过10M,日志文件会以索引0开始, -->
|
||||||
|
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
|
||||||
|
</rollingPolicy>
|
||||||
|
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
|
||||||
|
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %method 方法名 %L 行数 %msg:日志消息,%n是换行符-->
|
||||||
|
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56}.%method:%L - %msg%n</pattern>
|
||||||
|
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
<!-- 此日志文件只记录info级别的 -->
|
||||||
|
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
|
||||||
|
<level>INFO</level>
|
||||||
|
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!--错误日志统一输出到这里-->
|
||||||
|
|
||||||
|
<appender name="file.error" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
|
||||||
|
<Prudent>true</Prudent>
|
||||||
|
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
|
||||||
|
<!--日志文件输出的文件名,按天生成-->
|
||||||
|
|
||||||
|
<FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/error/error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
|
||||||
|
|
||||||
|
<!--日志文件保留天数-->
|
||||||
|
|
||||||
|
<MaxHistory>30</MaxHistory>
|
||||||
|
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
|
||||||
|
<!-- 除按日志记录之外,还配置了日志文件不能超过10M(默认),若超过10M,日志文件会以索引0开始, -->
|
||||||
|
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
|
||||||
|
</rollingPolicy>
|
||||||
|
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
|
||||||
|
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %method 方法名 %L 行数 %msg:日志消息,%n是换行符-->
|
||||||
|
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56}.%method:%L - %msg%n</pattern>
|
||||||
|
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
<!-- 此日志文件只记录error级别的 -->
|
||||||
|
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
|
||||||
|
<level>ERROR</level>
|
||||||
|
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!--warn日志统一输出到这里-->
|
||||||
|
|
||||||
|
<appender name="file.warn" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
|
||||||
|
<Prudent>true</Prudent>
|
||||||
|
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
|
||||||
|
<FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/warn/warn.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
|
||||||
|
|
||||||
|
<!--日志文件保留天数-->
|
||||||
|
|
||||||
|
<MaxHistory>30</MaxHistory>
|
||||||
|
|
||||||
|
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||||
|
|
||||||
|
<!-- 除按日志记录之外,还配置了日志文件不能超过10M(默认),若超过10M,日志文件会以索引0开始, -->
|
||||||
|
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
|
||||||
|
</timeBasedFileNamingAndTriggeringPolicy>
|
||||||
|
|
||||||
|
</rollingPolicy>
|
||||||
|
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
|
||||||
|
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %method 方法名 %L 行数 %msg:日志消息,%n是换行符-->
|
||||||
|
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56}.%method:%L - %msg%n</pattern>
|
||||||
|
|
||||||
|
<charset>utf-8</charset>
|
||||||
|
|
||||||
|
</encoder>
|
||||||
|
|
||||||
|
<!-- 此日志文件只记录warn级别的 -->
|
||||||
|
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
|
||||||
|
<level>WARN</level>
|
||||||
|
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
|
||||||
|
</filter>
|
||||||
|
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- 日志输出级别 -->
|
||||||
|
|
||||||
|
<root level="DEBUG">
|
||||||
|
|
||||||
|
<appender-ref ref="STDOUT"/>
|
||||||
|
|
||||||
|
<appender-ref ref="file.error"/>
|
||||||
|
|
||||||
|
<appender-ref ref="file.info"/>
|
||||||
|
|
||||||
|
<appender-ref ref="file.warn"/>
|
||||||
|
|
||||||
|
</root>
|
||||||
|
|
||||||
|
|
||||||
|
</configuration>
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Html测试
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-10
|
||||||
|
*/
|
||||||
|
public class HtmlTest {
|
||||||
|
public static void main(String[] args) throws IOException{
|
||||||
|
String str="为什么温泉♨️水那么黄? <img style=\"\" src=\"https://tvax1.sinaimg.cn/large/00759jQJly1hsg8uwgmavj31401hcdy4.jpg\" referrerpolicy=\"no-referrer\"><br><br><img style=\"\" src=\"https://tvax4.sinaimg.cn/large/00759jQJly1hsg8uvtqz7j31401hck91.jpg\" referrerpolicy=\"no-referrer\"><br><br><img style=\"\" src=\"https://tvax2.sinaimg.cn/large/00759jQJly1hsg8ux1psqj31401z44h1.jpg\" referrerpolicy=\"no-referrer\"><br><br><img style=\"\" src=\"https://tvax3.sinaimg.cn/large/00759jQJly1hsg8uxycmvj31401hc16o.jpg\" referrerpolicy=\"no-referrer\"><br><br><img style=\"\" src=\"https://tvax2.sinaimg.cn/large/00759jQJly1hsg8uyn21kj31401hc4gl.jpg\" referrerpolicy=\"no-referrer\"><br><br><img style=\"\" src=\"https://tvax3.sinaimg.cn/large/00759jQJly1hsg8uzeqdzj31401hck84.jpg\" referrerpolicy=\"no-referrer\"><br><br><img style=\"\" src=\"https://tvax4.sinaimg.cn/large/00759jQJly1hsg8uzyy3yj3140140gz4.jpg\" referrerpolicy=\"no-referrer\"><br><br><img style=\"\" src=\"https://tvax4.sinaimg.cn/large/00759jQJly1hsg8uv9nptj31401hctma.jpg\" referrerpolicy=\"no-referrer\"><br><br><img style=\"\" src=\"https://tvax3.sinaimg.cn/large/00759jQJly1hsg8v09z04j31401404by.jpg\" referrerpolicy=\"no-referrer\"><br><br>";
|
||||||
|
List<String> strings = extractImageUrls(str);
|
||||||
|
strings.forEach(obj->{
|
||||||
|
System.out.println(obj);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String readHtmlFile(String filePath) throws IOException {
|
||||||
|
StringBuilder content = new StringBuilder();
|
||||||
|
BufferedReader reader = new BufferedReader(new FileReader(filePath));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
content.append(line);
|
||||||
|
}
|
||||||
|
reader.close();
|
||||||
|
return content.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> extractImageUrls(String htmlContent) {
|
||||||
|
List<String> imageUrls = new ArrayList<>();
|
||||||
|
String regex = "<img\\s+[^>]*?src\\s*=\\s*['\"]([^'\"]*?)['\"][^>]*?>";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(htmlContent);
|
||||||
|
while (matcher.find()) {
|
||||||
|
String imageUrl = matcher.group(1);
|
||||||
|
imageUrls.add(imageUrl);
|
||||||
|
}
|
||||||
|
return imageUrls;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import cn.hutool.core.lang.Console;
|
||||||
|
import cn.hutool.extra.mail.MailAccount;
|
||||||
|
import com.bcrjl.rss.common.util.MailUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author yanqs
|
||||||
|
* @since 2024-08-09
|
||||||
|
*/
|
||||||
|
public class MailTest {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
MailAccount mailAccount = MailUtils.initMailAccount();
|
||||||
|
Console.log(mailAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RSS获取 测试类
|
||||||
|
*
|
||||||
|
* @author yanqs
|
||||||
|
*/
|
||||||
|
public class RSSReader {
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
// RSS feed URL
|
||||||
|
// URL rssUrl = new URL("https://rsshub.ys.bcrjl.com/weibo/user/6489032761");
|
||||||
|
URL rssUrl = new URL("https://blog.yanqingshan.com/feed/");
|
||||||
|
|
||||||
|
// Create a DocumentBuilder
|
||||||
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
|
|
||||||
|
// Parse the RSS file
|
||||||
|
Document document = builder.parse(rssUrl.openStream());
|
||||||
|
|
||||||
|
// Get all items
|
||||||
|
NodeList items = document.getElementsByTagName("item");
|
||||||
|
|
||||||
|
for (int i = 0; i < items.getLength(); i++) {
|
||||||
|
Element item = (Element) items.item(i);
|
||||||
|
Element title = (Element) item.getElementsByTagName("title").item(0);
|
||||||
|
Element link = (Element) item.getElementsByTagName("link").item(0);
|
||||||
|
|
||||||
|
// Print the title
|
||||||
|
System.out.println(title.getTextContent());
|
||||||
|
System.out.println(link.getTextContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue