Compare commits

..

19 Commits
v1.5 ... master

Author SHA1 Message Date
c5586f9621 new ui 2024-06-03 23:41:01 +08:00
3a4a22587a new ui 2024-06-03 22:34:18 +08:00
srcrs
129b3916ab fix mobile countdown show 2024-04-28 00:59:14 +08:00
srcrs
9f30853ded page add countdown and fix autoUpdatePush zero 2024-04-20 12:55:05 +08:00
srcrs
dcbeeebc20 add AutoUpdatePush 2024-04-20 12:52:42 +08:00
srcrs
3b2fb607a3 close mobile reload 2024-04-19 04:27:29 +08:00
srcrs
a9fdd31a4e Merge branch 'main' of github.com:srcrs/rss-reader 2023-10-15 19:34:36 +08:00
srcrs
8796bcc6c7 ws close to reload 2023-10-15 19:27:35 +08:00
srcrs
79ab3e511a 更改文件权限 2023-10-14 14:36:25 -04:00
srcrs
3bf1dc961b update 2023-10-15 02:04:05 +08:00
srcrs
837f187db7 add fsnotify 2023-10-15 02:03:42 +08:00
srcrs
4b968b7c81 add 2023-10-15 01:22:30 +08:00
srcrs
d0e4e09aab 兼容代码结构改变,增加夜间模式 2023-10-15 01:18:58 +08:00
srcrs
40840821cb 添加日间时间 2023-10-15 01:18:30 +08:00
srcrs
55496722ce 移动文件位置 2023-10-15 01:18:01 +08:00
srcrs
1580b9197d 增加dark-css 2023-10-15 01:16:56 +08:00
srcrs
a1d7b02b36 调整代码结构 2023-10-15 01:16:07 +08:00
srcrs
762ca0e6ec 更新说明 2023-09-23 00:01:59 +08:00
srcrs
9215669443 更新说明 2023-09-23 00:01:38 +08:00
20 changed files with 467 additions and 162 deletions

View File

@ -1,12 +1,30 @@
# 简述 # 简述
RSS将信息聚合曾寻找过一些RSS客户端但觉得都太过于复杂会需要登陆、保存历史消息、 实时展示rss订阅最新消息。
使用缓存加快响应速度但我想要看到的是打开页面看到关注网站的即时消息即可一般通过RSS订阅获取到的数据即是热点
看到有感兴趣的信息,可以跳转过去再详细的了解。 ## 特性
- 打包后镜像大小仅有约20MB通过docker实现一键部署
- 支持自定义配置页面数据自动刷新
- 响应式布局,能够兼容不同的屏幕大小
- 良好的SEO首次加载使用模版引擎快速展示页面内容
- 支持添加多个RSS订阅链接
- 简洁的页面布局,可以查看每个订阅链接最后更新时间
- 支持夜间模式
- config.json配置文件支持热更新
2023年7月28日进行了界面改版和升级 2023年7月28日进行了界面改版和升级
![](demo.png) ![](pc.png)
![](mobile.png)
# 配置文件 # 配置文件
@ -28,7 +46,9 @@ RSS将信息聚合曾寻找过一些RSS客户端但觉得都太过于复
"https://hostloc.com/forum.php?mod=rss&fid=45&auth=389ec3vtQanmEuRoghE%2FpZPWnYCPmvwWgSa7RsfjbQ%2BJpA%2F6y6eHAx%2FKqtmPOg" "https://hostloc.com/forum.php?mod=rss&fid=45&auth=389ec3vtQanmEuRoghE%2FpZPWnYCPmvwWgSa7RsfjbQ%2BJpA%2F6y6eHAx%2FKqtmPOg"
], ],
"refresh": 6, "refresh": 6,
"autoUpdatePush": 7 "autoUpdatePush": 7,
"nightStartTime": "06:30:00",
"nightEndTime": "19:30:00"
} }
``` ```
@ -37,6 +57,8 @@ RSS将信息聚合曾寻找过一些RSS客户端但觉得都太过于复
values | rss订阅链接必填 values | rss订阅链接必填
refresh | rss订阅更新时间间隔单位分钟必填 refresh | rss订阅更新时间间隔单位分钟必填
autoUpdatePush | 自动刷新间隔默认为0不开启。效果为前端每autoUpdatePush分钟自动更新页面信息单位分钟非必填 autoUpdatePush | 自动刷新间隔默认为0不开启。效果为前端每autoUpdatePush分钟自动更新页面信息单位分钟非必填
nightStartTime | 日间开始时间 ,如 06:30:00
nightEndTime | 日间结束时间,如 19:30:00
# 使用方式 # 使用方式

5
config.json Normal file → Executable file
View File

@ -5,7 +5,6 @@
"http://www.ruanyifeng.com/blog/atom.xml", "http://www.ruanyifeng.com/blog/atom.xml",
"https://feeds.appinn.com/appinns/", "https://feeds.appinn.com/appinns/",
"https://v2ex.com/feed/tab/tech.xml", "https://v2ex.com/feed/tab/tech.xml",
"https://www.cmooc.com/feed",
"http://www.sciencenet.cn/xml/blog.aspx?di=30", "http://www.sciencenet.cn/xml/blog.aspx?di=30",
"https://www.douban.com/feed/review/book", "https://www.douban.com/feed/review/book",
"https://www.douban.com/feed/review/movie", "https://www.douban.com/feed/review/movie",
@ -13,5 +12,7 @@
"https://hostloc.com/forum.php?mod=rss&fid=45&auth=389ec3vtQanmEuRoghE%2FpZPWnYCPmvwWgSa7RsfjbQ%2BJpA%2F6y6eHAx%2FKqtmPOg" "https://hostloc.com/forum.php?mod=rss&fid=45&auth=389ec3vtQanmEuRoghE%2FpZPWnYCPmvwWgSa7RsfjbQ%2BJpA%2F6y6eHAx%2FKqtmPOg"
], ],
"refresh": 6, "refresh": 6,
"autoUpdatePush": 7 "autoUpdatePush": 7,
"nightStartTime": "06:30:00",
"nightEndTime": "19:30:00"
} }

BIN
demo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 KiB

39
globals/global.go Normal file
View File

@ -0,0 +1,39 @@
package globals
import (
"embed"
"rss-reader/models"
"sync"
"github.com/gorilla/websocket"
"github.com/mmcdole/gofeed"
)
var (
DbMap map[string]models.Feed
RssUrls models.Config
Upgrader = websocket.Upgrader{}
Lock sync.RWMutex
//go:embed static
DirStatic embed.FS
HtmlContent []byte
Fp = gofeed.NewParser()
)
func Init() {
conf, err := models.ParseConf()
if err != nil {
panic(err)
}
RssUrls = conf
// 读取 index.html 内容
HtmlContent, err = DirStatic.ReadFile("static/index.html")
if err != nil {
panic(err)
}
DbMap = make(map[string]models.Feed)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 781 B

After

Width:  |  Height:  |  Size: 781 B

114
globals/static/index-1.html Normal file

File diff suppressed because one or more lines are too long

1
globals/static/index.css Normal file

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,10 @@
<meta name="keywords" content="<< .Keywords >>"> <meta name="keywords" content="<< .Keywords >>">
<meta name="anthor" content="srcrs"> <meta name="anthor" content="srcrs">
<!-- <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> --> <!-- <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> -->
<link rel="stylesheet" href="static/index.min.css"> <link rel="stylesheet" href="static/index.css">
<< if .DarkMode >>
<!-- <link rel="stylesheet" href="static/dark-mode.css">-->
<< end >>
<link rel="icon" href="static/favicon.svg" type="image/x-icon"> <link rel="icon" href="static/favicon.svg" type="image/x-icon">
<style> <style>
body { body {
@ -21,11 +24,18 @@
} }
.card-header { .card-header {
display: flex; font-size: 15px;
justify-content: center;
align-items: center;
font-size: 18px;
font-weight: bold; font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
margin-bottom: 10px;
background: linear-gradient(to bottom, #007f80, #007070);
color: white;
line-height: 2em;
border-radius: 6px;
padding: 0 0.5em;
} }
.list-item { .list-item {
@ -36,32 +46,32 @@
} }
.list-item-title { .list-item-title {
display: flex; display: block;
/* white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; */ text-overflow: ellipsis;
flex-grow: 1; flex-grow: 1;
text-align: left; text-align: left;
width: 100%; width: 100%;
margin-bottom: 10px; margin-bottom: 10px;
font-size: 15px;
align-items: center; /* 确保内容垂直居中 */
}
.title-link {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
} }
a { a {
color: black; color: black;
text-decoration: none; text-decoration: none;
} }
a:hover {
text-decoration: underline;
}
.feed-col { .feed-col {
margin-bottom: 20px; margin-bottom: 20px;
} }
.time {
font-size: 12px;
color: #999;
}
</style> </style>
</head> </head>
@ -69,7 +79,12 @@
<div id="app"> <div id="app">
<el-container> <el-container>
<el-header> <el-header>
<h1>RSS Reader</h1> <h1>
RSS Reader
<< if gt .AutoUpdatePush 0 >>
<span v-show="isPc"><br/>{{ countdown }} s</span>
<< end >>
</h1>
</el-header> </el-header>
<el-main v-loading.fullscreen.lock="fullscreenLoading" element-loading-text="拼命加载中"> <el-main v-loading.fullscreen.lock="fullscreenLoading" element-loading-text="拼命加载中">
<el-row :gutter="20"> <el-row :gutter="20">
@ -81,7 +96,7 @@
<< $feed.Title >> << $feed.Title >>
</span> </span>
</div> </div>
<el-scrollbar style="height: 300px;"> <el-scrollbar style="height: 580px;">
<< range $i, $item :=$feed.Items >> << range $i, $item :=$feed.Items >>
<el-list key="<< $i >>"> <el-list key="<< $i >>">
<el-list-item> <el-list-item>
@ -110,7 +125,7 @@
<div slot="header" class="card-header"> <div slot="header" class="card-header">
<span>{{ feed.title }}</span> <span>{{ feed.title }}</span>
</div> </div>
<el-scrollbar style="height: 300px;"> <el-scrollbar style="height: 580px;">
<el-list v-for="(item, i) in feed.items" :key="i"> <el-list v-for="(item, i) in feed.items" :key="i">
<el-list-item> <el-list-item>
<div class="list-item-title"> <div class="list-item-title">
@ -120,9 +135,9 @@
</el-list-item> </el-list-item>
</el-list> </el-list>
</el-scrollbar> </el-scrollbar>
<div slot="footer" class="card-footer" style="height: 10px;"> <!-- <div slot="footer" class="card-footer" style="height: 10px;">-->
<time class="time">{{ feed.custom.lastupdate }}</time> <!-- <time class="time">{{ feed.custom.lastupdate }}</time>-->
</div> <!-- </div>-->
</el-card> </el-card>
</el-col> </el-col>
</el-row> </el-row>
@ -147,10 +162,15 @@
feeds: [], feeds: [],
showSEOFlag: true, showSEOFlag: true,
fullscreenLoading: true, fullscreenLoading: true,
countdown: 60,
isPc: true,
autoUpdatePush: << .AutoUpdatePush >>,
}; };
}, },
async created() { async created() {
this.fullscreenLoading = false; this.fullscreenLoading = false;
// 使用媒体查询判断设备类型
this.isPc = !window.matchMedia('(max-width: 767px)').matches;
}, },
async mounted() { async mounted() {
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
@ -166,17 +186,39 @@
} }
this.showSEOFlag = false; this.showSEOFlag = false;
}; };
const reloadHtml = () => {
if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) {
if (document.visibilityState === 'visible') {
// 刷新网页
console.log("reload...")
location.reload();
}
}
}
socket.onclose = event => { socket.onclose = event => {
console.log("WebSocket closed. Reconnecting..."); if (this.isPc && this.autoUpdatePush > 0) {
setTimeout(connect, 300000); console.log("WebSocket closed. Reconnecting...");
setInterval(reloadHtml, 3000);
}
}; };
// Send heartbeat message every 120 seconds // Send heartbeat message every 60 seconds
const sendHeartbeat = () => { const sendHeartbeat = () => {
if (socket.readyState === WebSocket.OPEN) { if (socket.readyState === WebSocket.OPEN) {
socket.send("heartbeat"); socket.send("heartbeat");
} else if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) {
reloadHtml()
} }
}; };
setInterval(sendHeartbeat, 120000); if (this.isPc && this.autoUpdatePush > 0) {
setInterval(sendHeartbeat, 60000);
setInterval(() => {
if (this.countdown > 0) {
this.countdown--;
} else {
this.countdown = 60;
}
}, 1000);
}
}; };
connect(); connect();
}, },

2
go.mod
View File

@ -3,6 +3,7 @@ module rss-reader
go 1.18 go 1.18
require ( require (
github.com/fsnotify/fsnotify v1.6.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/mmcdole/gofeed v1.2.1 github.com/mmcdole/gofeed v1.2.1
) )
@ -15,5 +16,6 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
golang.org/x/net v0.4.0 // indirect golang.org/x/net v0.4.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.5.0 // indirect golang.org/x/text v0.5.0 // indirect
) )

5
go.sum
View File

@ -5,6 +5,8 @@ github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEq
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -29,6 +31,9 @@ golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=

162
main.go
View File

@ -1,75 +1,40 @@
package main package main
import ( import (
"embed"
"encoding/json" "encoding/json"
"html/template" "html/template"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"sync" "rss-reader/globals"
"rss-reader/models"
"rss-reader/utils"
"time" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/mmcdole/gofeed"
)
type Config struct {
Values []string `json:"values"`
ReFresh int `json:"refresh"`
AutoUpdatePush int `json:"autoUpdatePush"`
}
var (
dbMap map[string]feed
rssUrls Config
upgrader = websocket.Upgrader{}
lock sync.RWMutex
//go:embed static
dirStatic embed.FS
//go:embed index.html
fileIndex embed.FS
htmlContent []byte
) )
func init() { func init() {
// 读取配置文件 globals.Init()
data, err := ioutil.ReadFile("config.json")
if err != nil {
panic(err)
}
// 解析JSON数据到Config结构体
err = json.Unmarshal(data, &rssUrls)
if err != nil {
panic(err)
}
// 读取 index.html 内容
htmlContent, err = fileIndex.ReadFile("index.html")
if err != nil {
panic(err)
}
dbMap = make(map[string]feed)
} }
func main() { func main() {
go updateFeeds() go utils.UpdateFeeds()
go utils.WatchConfigFileChanges("config.json")
http.HandleFunc("/feeds", getFeedsHandler) http.HandleFunc("/feeds", getFeedsHandler)
http.HandleFunc("/ws", wsHandler) http.HandleFunc("/ws", wsHandler)
// http.HandleFunc("/", serveHome) // http.HandleFunc("/", serveHome)
http.HandleFunc("/", tplHandler) http.HandleFunc("/", tplHandler)
//加载静态文件 //加载静态文件
fs := http.FileServer(http.FS(dirStatic)) fs := http.FileServer(http.FS(globals.DirStatic))
http.Handle("/static/", fs) http.Handle("/static/", fs)
log.Fatal(http.ListenAndServe(":8080", nil)) log.Fatal(http.ListenAndServe(":8080", nil))
} }
func serveHome(w http.ResponseWriter, r *http.Request) { func serveHome(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "text/html; charset=utf-8") w.Header().Add("Content-Type", "text/html; charset=utf-8")
w.Write(htmlContent) w.Write(globals.HtmlContent)
} }
func tplHandler(w http.ResponseWriter, r *http.Request) { func tplHandler(w http.ResponseWriter, r *http.Request) {
@ -82,19 +47,32 @@ func tplHandler(w http.ResponseWriter, r *http.Request) {
}, },
} }
// 加载模板文件 // 加载模板文件
tmpl, err := tmplInstance.Funcs(funcMap).ParseFS(fileIndex, "index.html") tmpl, err := tmplInstance.Funcs(funcMap).ParseFS(globals.DirStatic, "static/index.html")
if err != nil { if err != nil {
log.Println("模板加载错误:", err) log.Println("模板加载错误:", err)
return return
} }
//判断现在是否是夜间
formattedTime := time.Now().Format("15:04:05")
darkMode := false
if globals.RssUrls.NightStartTime != "" && globals.RssUrls.NightEndTime != "" {
if globals.RssUrls.NightStartTime > formattedTime || formattedTime > globals.RssUrls.NightEndTime {
darkMode = true
}
}
// 定义一个数据对象 // 定义一个数据对象
data := struct { data := struct {
Keywords string Keywords string
RssDataList []feed RssDataList []models.Feed
DarkMode bool
AutoUpdatePush int
}{ }{
Keywords: getKeywords(), Keywords: getKeywords(),
RssDataList: getFeeds(), RssDataList: utils.GetFeeds(),
DarkMode: darkMode,
AutoUpdatePush: globals.RssUrls.AutoUpdatePush,
} }
// 渲染模板并将结果写入响应 // 渲染模板并将结果写入响应
@ -105,7 +83,7 @@ func tplHandler(w http.ResponseWriter, r *http.Request) {
} }
func wsHandler(w http.ResponseWriter, r *http.Request) { func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil) conn, err := globals.Upgrader.Upgrade(w, r, nil)
if err != nil { if err != nil {
log.Printf("Upgrade failed: %v", err) log.Printf("Upgrade failed: %v", err)
return return
@ -113,10 +91,10 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
defer conn.Close() defer conn.Close()
for { for {
for _, url := range rssUrls.Values { for _, url := range globals.RssUrls.Values {
lock.RLock() globals.Lock.RLock()
cache, ok := dbMap[url] cache, ok := globals.DbMap[url]
lock.RUnlock() globals.Lock.RUnlock()
if !ok { if !ok {
log.Printf("Error getting feed from db is null %v", url) log.Printf("Error getting feed from db is null %v", url)
continue continue
@ -135,83 +113,21 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
//如果未配置则不自动更新 //如果未配置则不自动更新
if rssUrls.AutoUpdatePush == 0 { if globals.RssUrls.AutoUpdatePush == 0 {
return return
} }
time.Sleep(time.Duration(rssUrls.AutoUpdatePush) * time.Minute) time.Sleep(time.Duration(globals.RssUrls.AutoUpdatePush) * time.Minute)
} }
} }
func updateFeeds() {
var (
tick = time.Tick(time.Duration(rssUrls.ReFresh) * time.Minute)
fp = gofeed.NewParser()
)
for {
formattedTime := time.Now().Format("2006-01-02 15:04:05")
for _, url := range rssUrls.Values {
go updateFeed(fp, url, formattedTime)
}
<-tick
}
}
func updateFeed(fp *gofeed.Parser, url, formattedTime string) {
result, err := fp.ParseURL(url)
if err != nil {
log.Printf("Error fetching feed: %v | %v", url, err)
return
}
//feed内容无更新时无需更新缓存
if cache, ok := dbMap[url]; ok &&
len(result.Items) > 0 &&
len(cache.Items) > 0 &&
result.Items[0].Link == cache.Items[0].Link {
return
}
customFeed := feed{
Title: result.Title,
Link: result.Link,
Custom: map[string]string{"lastupdate": formattedTime},
Items: make([]item, 0, len(result.Items)),
}
for _, v := range result.Items {
customFeed.Items = append(customFeed.Items, item{
Link: v.Link,
Title: v.Title,
Description: v.Description,
})
}
lock.Lock()
defer lock.Unlock()
dbMap[url] = customFeed
}
//获取feeds列表
func getFeeds() []feed {
feeds := make([]feed, 0, len(rssUrls.Values))
for _, url := range rssUrls.Values {
lock.RLock()
cache, ok := dbMap[url]
lock.RUnlock()
if !ok {
log.Printf("Error getting feed from db is null %v", url)
continue
}
feeds = append(feeds, cache)
}
return feeds
}
//获取关键词也就是title //获取关键词也就是title
//获取feeds列表 //获取feeds列表
func getKeywords() string { func getKeywords() string {
words := "" words := ""
for _, url := range rssUrls.Values { for _, url := range globals.RssUrls.Values {
lock.RLock() globals.Lock.RLock()
cache, ok := dbMap[url] cache, ok := globals.DbMap[url]
lock.RUnlock() globals.Lock.RUnlock()
if !ok { if !ok {
log.Printf("Error getting feed from db is null %v", url) log.Printf("Error getting feed from db is null %v", url)
continue continue
@ -224,7 +140,7 @@ func getKeywords() string {
} }
func getFeedsHandler(w http.ResponseWriter, r *http.Request) { func getFeedsHandler(w http.ResponseWriter, r *http.Request) {
feeds := getFeeds() feeds := utils.GetFeeds()
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(feeds) json.NewEncoder(w).Encode(feeds)

BIN
mobile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

45
models/config.go Normal file
View File

@ -0,0 +1,45 @@
package models
import (
"encoding/json"
"os"
)
func ParseConf() (Config, error) {
var conf Config
data, err := os.ReadFile("config.json")
if err != nil {
return conf, err
}
// 解析JSON数据到Config结构体
err = json.Unmarshal(data, &conf)
return conf, err
}
type Config struct {
Values []string `json:"values"`
ReFresh int `json:"refresh"`
AutoUpdatePush int `json:"autoUpdatePush"`
NightStartTime string `json:"nightStartTime"`
NightEndTime string `json:"nightEndTime"`
}
func (older Config) GetIncrement(newer Config) []string {
var (
urlMap = make(map[string]struct{})
increment = make([]string, 0, len(newer.Values))
)
for _, item := range older.Values {
urlMap[item] = struct{}{}
}
for _, item := range newer.Values {
if _, ok := urlMap[item]; ok {
continue
}
increment = append(increment, item)
}
return increment
}

View File

@ -1,13 +1,13 @@
package main package models
type feed struct { type Feed struct {
Title string `json:"title,omitempty"` Title string `json:"title,omitempty"`
Link string `json:"link"` Link string `json:"link"`
Custom map[string]string `json:"custom,omitempty"` Custom map[string]string `json:"custom,omitempty"`
Items []item `json:"items,omitempty"` Items []Item `json:"items,omitempty"`
} }
type item struct { type Item struct {
Title string `json:"title"` Title string `json:"title"`
Link string `json:"link"` Link string `json:"link"`
Description string `json:"description"` Description string `json:"description"`

BIN
pc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

117
utils/feed.go Normal file
View File

@ -0,0 +1,117 @@
package utils
import (
"log"
"rss-reader/globals"
"rss-reader/models"
"time"
"github.com/fsnotify/fsnotify"
)
func UpdateFeeds() {
var (
tick = time.Tick(time.Duration(globals.RssUrls.ReFresh) * time.Minute)
)
for {
formattedTime := time.Now().Format("2006-01-02 15:04:05")
for _, url := range globals.RssUrls.Values {
go UpdateFeed(url, formattedTime)
}
<-tick
}
}
func UpdateFeed(url, formattedTime string) {
result, err := globals.Fp.ParseURL(url)
if err != nil {
log.Printf("Error fetching feed: %v | %v", url, err)
return
}
//feed内容无更新时无需更新缓存
if cache, ok := globals.DbMap[url]; ok &&
len(result.Items) > 0 &&
len(cache.Items) > 0 &&
result.Items[0].Link == cache.Items[0].Link {
return
}
customFeed := models.Feed{
Title: result.Title,
Link: result.Link,
Custom: map[string]string{"lastupdate": formattedTime},
Items: make([]models.Item, 0, len(result.Items)),
}
for _, v := range result.Items {
customFeed.Items = append(customFeed.Items, models.Item{
Link: v.Link,
Title: v.Title,
Description: v.Description,
})
}
globals.Lock.Lock()
defer globals.Lock.Unlock()
globals.DbMap[url] = customFeed
}
//获取feeds列表
func GetFeeds() []models.Feed {
feeds := make([]models.Feed, 0, len(globals.RssUrls.Values))
for _, url := range globals.RssUrls.Values {
globals.Lock.RLock()
cache, ok := globals.DbMap[url]
globals.Lock.RUnlock()
if !ok {
log.Printf("Error getting feed from db is null %v", url)
continue
}
feeds = append(feeds, cache)
}
return feeds
}
func WatchConfigFileChanges(filePath string) {
// 创建一个新的监控器
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
// 添加要监控的文件
err = watcher.Add(filePath)
if err != nil {
log.Fatal(err)
}
// 启动一个 goroutine 来处理文件变化事件
go func() {
for {
time.Sleep(7 * time.Second)
select {
case event, ok := <-watcher.Events:
if !ok {
log.Println("通道关闭1")
return
}
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("文件已修改")
globals.Init()
formattedTime := time.Now().Format("2006-01-02 15:04:05")
for _, url := range globals.RssUrls.Values {
go UpdateFeed(url, formattedTime)
}
}
case err, ok := <-watcher.Errors:
if !ok {
log.Println("通道关闭2")
return
}
log.Println("错误:", err)
return
}
}
}()
select {}
}