Compare commits
No commits in common. "master" and "v1.5" have entirely different histories.
32
README.md
32
README.md
@ -1,30 +1,12 @@
|
||||
# 简述
|
||||
|
||||
实时展示rss订阅最新消息。
|
||||
|
||||
## 特性
|
||||
|
||||
- 打包后镜像大小仅有约20MB,通过docker实现一键部署
|
||||
|
||||
- 支持自定义配置页面数据自动刷新
|
||||
|
||||
- 响应式布局,能够兼容不同的屏幕大小
|
||||
|
||||
- 良好的SEO,首次加载使用模版引擎快速展示页面内容
|
||||
|
||||
- 支持添加多个RSS订阅链接
|
||||
|
||||
- 简洁的页面布局,可以查看每个订阅链接最后更新时间
|
||||
|
||||
- 支持夜间模式
|
||||
|
||||
- config.json配置文件支持热更新
|
||||
RSS将信息聚合,曾寻找过一些RSS客户端,但觉得都太过于复杂,会需要登陆、保存历史消息、
|
||||
使用缓存加快响应速度,但我想要看到的是,打开页面看到关注网站的即时消息即可(一般通过RSS订阅获取到的数据即是热点),
|
||||
看到有感兴趣的信息,可以跳转过去再详细的了解。
|
||||
|
||||
2023年7月28日,进行了界面改版和升级
|
||||
|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
# 配置文件
|
||||
|
||||
@ -46,9 +28,7 @@
|
||||
"https://hostloc.com/forum.php?mod=rss&fid=45&auth=389ec3vtQanmEuRoghE%2FpZPWnYCPmvwWgSa7RsfjbQ%2BJpA%2F6y6eHAx%2FKqtmPOg"
|
||||
],
|
||||
"refresh": 6,
|
||||
"autoUpdatePush": 7,
|
||||
"nightStartTime": "06:30:00",
|
||||
"nightEndTime": "19:30:00"
|
||||
"autoUpdatePush": 7
|
||||
}
|
||||
```
|
||||
|
||||
@ -57,8 +37,6 @@
|
||||
values | rss订阅链接(必填)
|
||||
refresh | rss订阅更新时间间隔,单位分钟(必填)
|
||||
autoUpdatePush | 自动刷新间隔,默认为0,不开启。效果为前端每autoUpdatePush分钟自动更新页面信息,单位分钟(非必填)
|
||||
nightStartTime | 日间开始时间 ,如 06:30:00
|
||||
nightEndTime | 日间结束时间,如 19:30:00
|
||||
|
||||
# 使用方式
|
||||
|
||||
|
||||
5
config.json
Executable file → Normal file
5
config.json
Executable file → Normal file
@ -5,6 +5,7 @@
|
||||
"http://www.ruanyifeng.com/blog/atom.xml",
|
||||
"https://feeds.appinn.com/appinns/",
|
||||
"https://v2ex.com/feed/tab/tech.xml",
|
||||
"https://www.cmooc.com/feed",
|
||||
"http://www.sciencenet.cn/xml/blog.aspx?di=30",
|
||||
"https://www.douban.com/feed/review/book",
|
||||
"https://www.douban.com/feed/review/movie",
|
||||
@ -12,7 +13,5 @@
|
||||
"https://hostloc.com/forum.php?mod=rss&fid=45&auth=389ec3vtQanmEuRoghE%2FpZPWnYCPmvwWgSa7RsfjbQ%2BJpA%2F6y6eHAx%2FKqtmPOg"
|
||||
],
|
||||
"refresh": 6,
|
||||
"autoUpdatePush": 7,
|
||||
"nightStartTime": "06:30:00",
|
||||
"nightEndTime": "19:30:00"
|
||||
"autoUpdatePush": 7
|
||||
}
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
package models
|
||||
package main
|
||||
|
||||
type Feed struct {
|
||||
type feed struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Link string `json:"link"`
|
||||
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"`
|
||||
Link string `json:"link"`
|
||||
Description string `json:"description"`
|
||||
@ -1,39 +0,0 @@
|
||||
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
File diff suppressed because one or more lines are too long
2
go.mod
2
go.mod
@ -3,7 +3,6 @@ module rss-reader
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/mmcdole/gofeed v1.2.1
|
||||
)
|
||||
@ -16,6 +15,5 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // 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
|
||||
)
|
||||
|
||||
5
go.sum
5
go.sum
@ -5,8 +5,6 @@ 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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
@ -31,9 +29,6 @@ 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/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-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/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
|
||||
@ -8,10 +8,7 @@
|
||||
<meta name="keywords" content="<< .Keywords >>">
|
||||
<meta name="anthor" content="srcrs">
|
||||
<!-- <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> -->
|
||||
<link rel="stylesheet" href="static/index.css">
|
||||
<< if .DarkMode >>
|
||||
<!-- <link rel="stylesheet" href="static/dark-mode.css">-->
|
||||
<< end >>
|
||||
<link rel="stylesheet" href="static/index.min.css">
|
||||
<link rel="icon" href="static/favicon.svg" type="image/x-icon">
|
||||
<style>
|
||||
body {
|
||||
@ -24,18 +21,11 @@
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 15px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
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 {
|
||||
@ -46,32 +36,32 @@
|
||||
}
|
||||
|
||||
.list-item-title {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
/* white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-overflow: ellipsis; */
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
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 {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.feed-col {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -79,12 +69,7 @@
|
||||
<div id="app">
|
||||
<el-container>
|
||||
<el-header>
|
||||
<h1>
|
||||
RSS Reader
|
||||
<< if gt .AutoUpdatePush 0 >>
|
||||
<span v-show="isPc"><br/>{{ countdown }} s</span>
|
||||
<< end >>
|
||||
</h1>
|
||||
<h1>RSS Reader</h1>
|
||||
</el-header>
|
||||
<el-main v-loading.fullscreen.lock="fullscreenLoading" element-loading-text="拼命加载中">
|
||||
<el-row :gutter="20">
|
||||
@ -96,7 +81,7 @@
|
||||
<< $feed.Title >>
|
||||
</span>
|
||||
</div>
|
||||
<el-scrollbar style="height: 580px;">
|
||||
<el-scrollbar style="height: 300px;">
|
||||
<< range $i, $item :=$feed.Items >>
|
||||
<el-list key="<< $i >>">
|
||||
<el-list-item>
|
||||
@ -125,7 +110,7 @@
|
||||
<div slot="header" class="card-header">
|
||||
<span>{{ feed.title }}</span>
|
||||
</div>
|
||||
<el-scrollbar style="height: 580px;">
|
||||
<el-scrollbar style="height: 300px;">
|
||||
<el-list v-for="(item, i) in feed.items" :key="i">
|
||||
<el-list-item>
|
||||
<div class="list-item-title">
|
||||
@ -135,9 +120,9 @@
|
||||
</el-list-item>
|
||||
</el-list>
|
||||
</el-scrollbar>
|
||||
<!-- <div slot="footer" class="card-footer" style="height: 10px;">-->
|
||||
<!-- <time class="time">{{ feed.custom.lastupdate }}</time>-->
|
||||
<!-- </div>-->
|
||||
<div slot="footer" class="card-footer" style="height: 10px;">
|
||||
<time class="time">{{ feed.custom.lastupdate }}</time>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@ -162,15 +147,10 @@
|
||||
feeds: [],
|
||||
showSEOFlag: true,
|
||||
fullscreenLoading: true,
|
||||
countdown: 60,
|
||||
isPc: true,
|
||||
autoUpdatePush: << .AutoUpdatePush >>,
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.fullscreenLoading = false;
|
||||
// 使用媒体查询判断设备类型
|
||||
this.isPc = !window.matchMedia('(max-width: 767px)').matches;
|
||||
},
|
||||
async mounted() {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
@ -186,39 +166,17 @@
|
||||
}
|
||||
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 => {
|
||||
if (this.isPc && this.autoUpdatePush > 0) {
|
||||
console.log("WebSocket closed. Reconnecting...");
|
||||
setInterval(reloadHtml, 3000);
|
||||
}
|
||||
console.log("WebSocket closed. Reconnecting...");
|
||||
setTimeout(connect, 300000);
|
||||
};
|
||||
// Send heartbeat message every 60 seconds
|
||||
// Send heartbeat message every 120 seconds
|
||||
const sendHeartbeat = () => {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.send("heartbeat");
|
||||
} else if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) {
|
||||
reloadHtml()
|
||||
}
|
||||
};
|
||||
if (this.isPc && this.autoUpdatePush > 0) {
|
||||
setInterval(sendHeartbeat, 60000);
|
||||
setInterval(() => {
|
||||
if (this.countdown > 0) {
|
||||
this.countdown--;
|
||||
} else {
|
||||
this.countdown = 60;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
setInterval(sendHeartbeat, 120000);
|
||||
};
|
||||
connect();
|
||||
},
|
||||
162
main.go
162
main.go
@ -1,40 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"rss-reader/globals"
|
||||
"rss-reader/models"
|
||||
|
||||
"rss-reader/utils"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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() {
|
||||
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() {
|
||||
go utils.UpdateFeeds()
|
||||
go utils.WatchConfigFileChanges("config.json")
|
||||
go updateFeeds()
|
||||
http.HandleFunc("/feeds", getFeedsHandler)
|
||||
http.HandleFunc("/ws", wsHandler)
|
||||
// http.HandleFunc("/", serveHome)
|
||||
http.HandleFunc("/", tplHandler)
|
||||
|
||||
//加载静态文件
|
||||
fs := http.FileServer(http.FS(globals.DirStatic))
|
||||
fs := http.FileServer(http.FS(dirStatic))
|
||||
http.Handle("/static/", fs)
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
func serveHome(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "text/html; charset=utf-8")
|
||||
w.Write(globals.HtmlContent)
|
||||
w.Write(htmlContent)
|
||||
}
|
||||
|
||||
func tplHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@ -47,32 +82,19 @@ func tplHandler(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
}
|
||||
// 加载模板文件
|
||||
tmpl, err := tmplInstance.Funcs(funcMap).ParseFS(globals.DirStatic, "static/index.html")
|
||||
tmpl, err := tmplInstance.Funcs(funcMap).ParseFS(fileIndex, "index.html")
|
||||
if err != nil {
|
||||
log.Println("模板加载错误:", err)
|
||||
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 {
|
||||
Keywords string
|
||||
RssDataList []models.Feed
|
||||
DarkMode bool
|
||||
AutoUpdatePush int
|
||||
Keywords string
|
||||
RssDataList []feed
|
||||
}{
|
||||
Keywords: getKeywords(),
|
||||
RssDataList: utils.GetFeeds(),
|
||||
DarkMode: darkMode,
|
||||
AutoUpdatePush: globals.RssUrls.AutoUpdatePush,
|
||||
Keywords: getKeywords(),
|
||||
RssDataList: getFeeds(),
|
||||
}
|
||||
|
||||
// 渲染模板并将结果写入响应
|
||||
@ -83,7 +105,7 @@ func tplHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := globals.Upgrader.Upgrade(w, r, nil)
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Printf("Upgrade failed: %v", err)
|
||||
return
|
||||
@ -91,10 +113,10 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
defer conn.Close()
|
||||
for {
|
||||
for _, url := range globals.RssUrls.Values {
|
||||
globals.Lock.RLock()
|
||||
cache, ok := globals.DbMap[url]
|
||||
globals.Lock.RUnlock()
|
||||
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
|
||||
@ -113,21 +135,83 @@ func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
//如果未配置则不自动更新
|
||||
if globals.RssUrls.AutoUpdatePush == 0 {
|
||||
if rssUrls.AutoUpdatePush == 0 {
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Duration(globals.RssUrls.AutoUpdatePush) * time.Minute)
|
||||
time.Sleep(time.Duration(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
|
||||
//获取feeds列表
|
||||
func getKeywords() string {
|
||||
words := ""
|
||||
for _, url := range globals.RssUrls.Values {
|
||||
globals.Lock.RLock()
|
||||
cache, ok := globals.DbMap[url]
|
||||
globals.Lock.RUnlock()
|
||||
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
|
||||
@ -140,7 +224,7 @@ func getKeywords() string {
|
||||
}
|
||||
|
||||
func getFeedsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
feeds := utils.GetFeeds()
|
||||
feeds := getFeeds()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(feeds)
|
||||
|
||||
BIN
mobile.png
BIN
mobile.png
Binary file not shown.
|
Before Width: | Height: | Size: 122 KiB |
@ -1,45 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
Before Width: | Height: | Size: 781 B After Width: | Height: | Size: 781 B |
File diff suppressed because one or more lines are too long
117
utils/feed.go
117
utils/feed.go
@ -1,117 +0,0 @@
|
||||
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 {}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user