Skip to content

Commit 082bd3c

Browse files
committed
feat: add Linuxdo authentication support and configuration
1 parent 70d8ed5 commit 082bd3c

File tree

8 files changed

+505
-299
lines changed

8 files changed

+505
-299
lines changed

auth.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package main
2+
3+
import (
4+
"html/template"
5+
"log"
6+
"net/http"
7+
"strings"
8+
)
9+
10+
// 认证中间件
11+
func AuthMiddleware(next http.Handler) http.Handler {
12+
if config.Secure != "false" {
13+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
14+
cookie, err := r.Cookie("auth")
15+
// log.Printf("请求路径: %s, Cookie状态: %+v, 错误信息: %v", r.URL.Path, cookie, err)
16+
17+
if err != nil || !verifyCookie(cookie) {
18+
// log.Printf("验证失败,跳转登录页面")
19+
http.Redirect(w, r, "/login", http.StatusFound)
20+
return
21+
}
22+
next.ServeHTTP(w, r)
23+
})
24+
}
25+
return next
26+
}
27+
28+
// 验证cookie有效性
29+
func verifyCookie(cookie *http.Cookie) bool {
30+
return cookie != nil && strings.Contains(cookie.Value, "authenticated")
31+
// return cookie != nil && cookie.Value == "authenticated"
32+
}
33+
34+
// 登录处理器
35+
func loginHandler(w http.ResponseWriter, r *http.Request) {
36+
if r.Method == http.MethodPost {
37+
// 验证密码
38+
log.Printf("输入密码:%s,正确密码:%s", r.FormValue("password"), config.Password)
39+
if r.FormValue("password") == config.Password {
40+
// 设置认证cookie(1小时有效期)
41+
http.SetCookie(w, &http.Cookie{
42+
Name: "auth",
43+
Value: "authenticated",
44+
MaxAge: 3600, // 使用秒数设置有效期(1小时)
45+
HttpOnly: true,
46+
Path: "/",
47+
Secure: true, // 开发环境可设为false,生产环境必须设为true
48+
SameSite: http.SameSiteLaxMode, // 添加SameSite属性
49+
})
50+
http.Redirect(w, r, "/", http.StatusFound)
51+
return
52+
}
53+
http.Error(w, "密码错误", http.StatusUnauthorized)
54+
return
55+
}
56+
57+
// 显示登录表单
58+
tmpl := template.Must(template.New("login").Parse(loginTemplate))
59+
tmpl.Execute(w, config)
60+
}

config.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package main
2+
3+
import "os"
4+
5+
type Config struct {
6+
ImageDir string
7+
Secure string
8+
Password string
9+
Port string
10+
Title string
11+
Icon string
12+
Url string
13+
Dynamic string
14+
Linuxdo string
15+
}
16+
17+
var config = Config{
18+
ImageDir: "./images",
19+
Password: "",
20+
Port: "8009",
21+
Title: "在线图集",
22+
Icon: "https://i.obai.cc/favicon.ico",
23+
Dynamic: "false",
24+
Url: "http://localhost:8009",
25+
Secure: "false",
26+
Linuxdo: "false",
27+
}
28+
29+
var categoryCache []Category
30+
31+
type Category struct {
32+
Name string
33+
EncodedName string
34+
CoverImage string
35+
}
36+
37+
var imageExtensions = map[string]bool{
38+
".jpg": true,
39+
".jpeg": true,
40+
".png": true,
41+
".gif": true,
42+
".webp": true,
43+
}
44+
45+
func initConfig() {
46+
envVars := map[string]*string{
47+
"SITE_DIR": &config.ImageDir,
48+
"SITE_SECURE": &config.Secure,
49+
"SITE_PASSWORD": &config.Password,
50+
"SITE_PORT": &config.Port,
51+
"SITE_TITLE": &config.Title,
52+
"SITE_ICON": &config.Icon,
53+
"SITE_DYNAMIC": &config.Dynamic,
54+
"SITE_LINUXDO": &config.Linuxdo,
55+
"SITE_URL": &config.Url,
56+
}
57+
58+
for env, conf := range envVars {
59+
if val := os.Getenv(env); val != "" {
60+
*conf = val
61+
}
62+
}
63+
}

go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
module lovebai/plist
22

33
go 1.24.0
4+
5+
require (
6+
github.com/go-resty/resty/v2 v2.16.5 // indirect
7+
github.com/gorilla/securecookie v1.1.2 // indirect
8+
github.com/gorilla/sessions v1.4.0 // indirect
9+
golang.org/x/net v0.33.0 // indirect
10+
)

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
2+
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
3+
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
4+
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
5+
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
6+
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
7+
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
8+
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=

handler.go

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"html/template"
6+
"log"
7+
"net/http"
8+
"net/url"
9+
"os"
10+
"path/filepath"
11+
"strconv"
12+
"strings"
13+
"time"
14+
)
15+
16+
func loggingMiddleware(next http.Handler) http.Handler {
17+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
18+
start := time.Now()
19+
next.ServeHTTP(w, r)
20+
duration := time.Since(start)
21+
log.Printf("Method: %s, URL: %s, Duration: %s\n", r.Method, r.URL.Path, duration)
22+
})
23+
}
24+
25+
func indexHandler(w http.ResponseWriter, r *http.Request) {
26+
if config.Dynamic == "true" {
27+
tmpl := template.Must(template.New("index").Parse(indexDynamicTemplate))
28+
tmpl.Execute(w, config)
29+
} else {
30+
type Tmp struct {
31+
Category []Category
32+
Config Config
33+
}
34+
var tmp = Tmp{
35+
Category: categoryCache, // 使用缓存数据
36+
Config: config,
37+
}
38+
tmpl := template.Must(template.New("index").Parse(indexTemplate))
39+
tmpl.Execute(w, tmp)
40+
}
41+
42+
}
43+
44+
func categoryHandler(w http.ResponseWriter, r *http.Request) {
45+
// category := r.URL.Path[len("/category/"):]
46+
// category := filepath.FromSlash(r.URL.Path[len("/category/"):])
47+
encodedCategory := filepath.FromSlash(r.URL.Path[len("/category/"):])
48+
category, _ := url.PathUnescape(encodedCategory)
49+
imagePath := filepath.Join(config.ImageDir, category)
50+
cleanImageDir := filepath.Clean(config.ImageDir)
51+
absImageDir, _ := filepath.Abs(cleanImageDir)
52+
absPath, _ := filepath.Abs(imagePath)
53+
if !strings.HasPrefix(absPath, absImageDir) {
54+
http.Error(w, "无效路径", http.StatusBadRequest)
55+
return
56+
}
57+
entries, err := os.ReadDir(imagePath)
58+
if err != nil {
59+
http.Error(w, "无法读取图片目录", http.StatusInternalServerError)
60+
return
61+
}
62+
63+
type Image struct {
64+
Name string
65+
Type string
66+
}
67+
68+
var imageList []Image
69+
for _, entry := range entries {
70+
if entry.IsDir() {
71+
continue
72+
}
73+
ext := strings.ToLower(filepath.Ext(entry.Name()))
74+
if imageExtensions[ext] {
75+
imageList = append(imageList, Image{
76+
Name: entry.Name(),
77+
// 添加类型字段供模板使用(可选)
78+
Type: strings.TrimPrefix(ext, "."),
79+
})
80+
}
81+
}
82+
83+
data := struct {
84+
Category string
85+
Images []Image
86+
Config Config
87+
}{
88+
Category: category,
89+
Images: imageList,
90+
Config: config,
91+
}
92+
93+
if config.Dynamic == "true" {
94+
tmpl := template.Must(template.New("category").Parse(categoryDynamicTemplate))
95+
tmpl.Execute(w, data)
96+
} else {
97+
tmpl := template.Must(template.New("category").Parse(categoryTemplate))
98+
tmpl.Execute(w, data)
99+
100+
}
101+
}
102+
103+
func indexJson(w http.ResponseWriter, r *http.Request) {
104+
// 获取分页参数
105+
pageStr := r.URL.Query().Get("page")
106+
limitStr := r.URL.Query().Get("limit")
107+
page, err := strconv.Atoi(pageStr)
108+
if err != nil || page < 1 {
109+
page = 1
110+
}
111+
limit, err := strconv.Atoi(limitStr)
112+
if err != nil || limit < 1 {
113+
limit = 20
114+
}
115+
116+
// 使用缓存的分类信息
117+
totalCategories := len(categoryCache)
118+
totalPages := (totalCategories + limit - 1) / limit
119+
start := (page - 1) * limit
120+
end := start + limit
121+
if start > totalCategories {
122+
start = totalCategories
123+
}
124+
if end > totalCategories {
125+
end = totalCategories
126+
}
127+
currentCategories := categoryCache[start:end]
128+
129+
w.Header().Set("Content-Type", "application/json")
130+
json.NewEncoder(w).Encode(map[string]interface{}{
131+
"categories": currentCategories,
132+
"page": page,
133+
"limit": limit,
134+
"total": totalCategories,
135+
"pages": totalPages,
136+
})
137+
}
138+
139+
func categoryJson(w http.ResponseWriter, r *http.Request) {
140+
encodedCategory := filepath.FromSlash(r.URL.Path[len("/api/category/"):])
141+
category, _ := url.PathUnescape(encodedCategory)
142+
imagePath := filepath.Join(config.ImageDir, category)
143+
cleanImageDir := filepath.Clean(config.ImageDir)
144+
absImageDir, _ := filepath.Abs(cleanImageDir)
145+
absPath, _ := filepath.Abs(imagePath)
146+
if !strings.HasPrefix(absPath, absImageDir) {
147+
http.Error(w, "无效路径", http.StatusBadRequest)
148+
return
149+
}
150+
151+
// 获取分页参数
152+
pageStr := r.URL.Query().Get("page")
153+
limitStr := r.URL.Query().Get("limit")
154+
page, err := strconv.Atoi(pageStr)
155+
if err != nil || page < 1 {
156+
page = 1
157+
}
158+
limit, err := strconv.Atoi(limitStr)
159+
if err != nil || limit < 1 {
160+
limit = 20
161+
}
162+
163+
entries, err := os.ReadDir(imagePath)
164+
if err != nil {
165+
http.Error(w, "无法读取图片目录", http.StatusInternalServerError)
166+
return
167+
}
168+
169+
type Image struct {
170+
Name string
171+
Type string
172+
}
173+
174+
var imageList []Image
175+
for _, entry := range entries {
176+
if entry.IsDir() {
177+
continue
178+
}
179+
ext := strings.ToLower(filepath.Ext(entry.Name()))
180+
if imageExtensions[ext] {
181+
imageList = append(imageList, Image{
182+
Name: entry.Name(),
183+
Type: strings.TrimPrefix(ext, "."),
184+
})
185+
}
186+
}
187+
188+
totalImages := len(imageList)
189+
totalPages := (totalImages + limit - 1) / limit
190+
start := (page - 1) * limit
191+
end := start + limit
192+
if start > totalImages {
193+
start = totalImages
194+
}
195+
if end > totalImages {
196+
end = totalImages
197+
}
198+
currentImages := imageList[start:end]
199+
200+
w.Header().Set("Content-Type", "application/json")
201+
json.NewEncoder(w).Encode(map[string]interface{}{
202+
"category": category,
203+
"images": currentImages,
204+
"page": page,
205+
"limit": limit,
206+
"total": totalImages,
207+
"pages": totalPages,
208+
})
209+
}

0 commit comments

Comments
 (0)