Skip to content

Commit 9aa2e8f

Browse files
committed
Merge branch 'dev-media' of https://github.com/OpenListTeam/OpenList into dev-media
# Conflicts: # internal/db/db.go resolved by dev-media version
2 parents 6808807 + 0308504 commit 9aa2e8f

12 files changed

Lines changed: 601 additions & 12 deletions

File tree

go.mod

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ require (
4141
github.com/foxxorcat/weiyun-sdk-go v0.1.4
4242
github.com/gin-contrib/cors v1.7.6
4343
github.com/gin-gonic/gin v1.10.1
44+
github.com/glebarez/sqlite v1.11.0
4445
github.com/go-resty/resty/v2 v2.16.5
4546
github.com/go-webauthn/webauthn v0.13.4
4647
github.com/golang-jwt/jwt/v4 v4.5.2
@@ -85,13 +86,13 @@ require (
8586
gopkg.in/ldap.v3 v3.1.0
8687
gorm.io/driver/mysql v1.5.7
8788
gorm.io/driver/postgres v1.5.9
88-
gorm.io/driver/sqlite v1.5.6
89-
gorm.io/gorm v1.25.11
89+
gorm.io/driver/sqlite v1.6.0
90+
gorm.io/gorm v1.30.0
9091
)
9192

9293
require (
93-
cloud.google.com/go/compute/metadata v0.9.0 // indirect
9494
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
95+
github.com/BurntSushi/toml v1.6.0 // indirect
9596
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect
9697
github.com/ProtonMail/gluon v0.17.1-0.20230724134000-308be39be96e // indirect
9798
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
@@ -105,6 +106,7 @@ require (
105106
github.com/cloudsoda/sddl v0.0.0-20250224235906-926454e91efc // indirect
106107
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
107108
github.com/cronokirby/saferith v0.33.0 // indirect
109+
github.com/dustin/go-humanize v1.0.1 // indirect
108110
github.com/ebitengine/purego v0.8.4 // indirect
109111
github.com/emersion/go-message v0.18.2 // indirect
110112
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff // indirect
@@ -118,10 +120,12 @@ require (
118120
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
119121
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
120122
github.com/lanrat/extsort v1.0.2 // indirect
123+
github.com/mattn/go-sqlite3 v1.14.22 // indirect
121124
github.com/mikelolasagasti/xz v1.0.1 // indirect
122125
github.com/minio/minlz v1.0.0 // indirect
123126
github.com/minio/xxml v0.0.3 // indirect
124127
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
128+
github.com/ncruces/go-strftime v0.1.9 // indirect
125129
github.com/quic-go/qpack v0.5.1 // indirect
126130
github.com/relvacode/iso8601 v1.6.0 // indirect
127131
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
@@ -204,6 +208,7 @@ require (
204208
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
205209
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
206210
github.com/gin-contrib/sse v1.1.0 // indirect
211+
github.com/glebarez/go-sqlite v1.22.0 // indirect
207212
github.com/go-chi/chi/v5 v5.2.2 // indirect
208213
github.com/go-ole/go-ole v1.3.0 // indirect
209214
github.com/go-playground/locales v0.14.1 // indirect
@@ -244,7 +249,6 @@ require (
244249
github.com/mattn/go-isatty v0.0.20 // indirect
245250
github.com/mattn/go-localereader v0.0.1 // indirect
246251
github.com/mattn/go-runewidth v0.0.16 // indirect
247-
github.com/mattn/go-sqlite3 v1.14.22 // indirect
248252
github.com/minio/sha256-simd v1.0.1 // indirect
249253
github.com/mitchellh/go-homedir v1.1.0 // indirect
250254
github.com/mitchellh/mapstructure v1.5.0 // indirect
@@ -272,6 +276,7 @@ require (
272276
github.com/prometheus/client_model v0.6.2 // indirect
273277
github.com/prometheus/common v0.64.0 // indirect
274278
github.com/prometheus/procfs v0.16.1 // indirect
279+
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
275280
github.com/rfjakob/eme v1.1.2 // indirect
276281
github.com/rivo/uniseg v0.4.7 // indirect
277282
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
@@ -300,6 +305,10 @@ require (
300305
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
301306
gopkg.in/yaml.v3 v3.0.1 // indirect
302307
lukechampine.com/blake3 v1.1.7 // indirect
308+
modernc.org/libc v1.55.3 // indirect
309+
modernc.org/mathutil v1.6.0 // indirect
310+
modernc.org/memory v1.8.0 // indirect
311+
modernc.org/sqlite v1.33.1 // indirect
303312
)
304313

305314
replace github.com/ProtonMail/go-proton-api => github.com/henrybear327/go-proton-api v1.0.0

internal/bootstrap/db.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import (
1010
"github.com/OpenListTeam/OpenList/v4/internal/conf"
1111
"github.com/OpenListTeam/OpenList/v4/internal/db"
1212
log "github.com/sirupsen/logrus"
13+
"github.com/glebarez/sqlite"
1314
"gorm.io/driver/mysql"
1415
"gorm.io/driver/postgres"
15-
"gorm.io/driver/sqlite"
1616
"gorm.io/gorm"
1717
"gorm.io/gorm/logger"
1818
"gorm.io/gorm/schema"

internal/conf/const.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,6 @@ const (
198198
PathKey
199199
SharingIDKey
200200
SkipHookKey
201+
VirtualHostKey
202+
VhostPrefixKey
201203
)

internal/db/virtual_host.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package db
2+
3+
import (
4+
"github.com/OpenListTeam/OpenList/v4/internal/model"
5+
"github.com/pkg/errors"
6+
)
7+
8+
func GetVirtualHostByDomain(domain string) (*model.VirtualHost, error) {
9+
var v model.VirtualHost
10+
if err := db.Where("domain = ?", domain).First(&v).Error; err != nil {
11+
return nil, errors.Wrapf(err, "failed select virtual host")
12+
}
13+
return &v, nil
14+
}
15+
16+
func GetVirtualHostById(id uint) (*model.VirtualHost, error) {
17+
var v model.VirtualHost
18+
if err := db.First(&v, id).Error; err != nil {
19+
return nil, errors.Wrapf(err, "failed get virtual host")
20+
}
21+
return &v, nil
22+
}
23+
24+
func CreateVirtualHost(v *model.VirtualHost) error {
25+
return errors.WithStack(db.Create(v).Error)
26+
}
27+
28+
func UpdateVirtualHost(v *model.VirtualHost) error {
29+
return errors.WithStack(db.Save(v).Error)
30+
}
31+
32+
func GetVirtualHosts(pageIndex, pageSize int) (vhosts []model.VirtualHost, count int64, err error) {
33+
vhostDB := db.Model(&model.VirtualHost{})
34+
if err = vhostDB.Count(&count).Error; err != nil {
35+
return nil, 0, errors.Wrapf(err, "failed get virtual hosts count")
36+
}
37+
if err = vhostDB.Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&vhosts).Error; err != nil {
38+
return nil, 0, errors.Wrapf(err, "failed find virtual hosts")
39+
}
40+
return vhosts, count, nil
41+
}
42+
43+
func DeleteVirtualHostById(id uint) error {
44+
return errors.WithStack(db.Delete(&model.VirtualHost{}, id).Error)
45+
}

internal/model/virtual_host.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package model
2+
3+
type VirtualHost struct {
4+
ID uint `json:"id" gorm:"primaryKey"`
5+
Enabled bool `json:"enabled"`
6+
Domain string `json:"domain" gorm:"unique" binding:"required"`
7+
Path string `json:"path" binding:"required"`
8+
WebHosting bool `json:"web_hosting"`
9+
}

internal/op/virtual_host.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package op
2+
3+
import (
4+
"time"
5+
6+
"github.com/OpenListTeam/OpenList/v4/internal/db"
7+
"github.com/OpenListTeam/OpenList/v4/internal/model"
8+
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
9+
"github.com/OpenListTeam/go-cache"
10+
"github.com/pkg/errors"
11+
"gorm.io/gorm"
12+
)
13+
14+
var vhostCache = cache.NewMemCache(cache.WithShards[*model.VirtualHost](2))
15+
16+
// GetVirtualHostByDomain 根据域名获取虚拟主机配置(带缓存)
17+
func GetVirtualHostByDomain(domain string) (*model.VirtualHost, error) {
18+
if v, ok := vhostCache.Get(domain); ok {
19+
if v == nil {
20+
utils.Log.Infof("[VirtualHost] cache hit (nil) for domain=%q", domain)
21+
return nil, errors.New("virtual host not found")
22+
}
23+
utils.Log.Infof("[VirtualHost] cache hit for domain=%q id=%d", domain, v.ID)
24+
return v, nil
25+
}
26+
utils.Log.Infof("[VirtualHost] cache miss for domain=%q, querying db...", domain)
27+
v, err := db.GetVirtualHostByDomain(domain)
28+
if err != nil {
29+
if errors.Is(errors.Cause(err), gorm.ErrRecordNotFound) {
30+
utils.Log.Infof("[VirtualHost] domain=%q not found in db, caching nil", domain)
31+
vhostCache.Set(domain, nil, cache.WithEx[*model.VirtualHost](time.Minute*5))
32+
return nil, errors.New("virtual host not found")
33+
}
34+
utils.Log.Errorf("[VirtualHost] db error for domain=%q: %v", domain, err)
35+
return nil, err
36+
}
37+
utils.Log.Infof("[VirtualHost] db found domain=%q id=%d enabled=%v web_hosting=%v", domain, v.ID, v.Enabled, v.WebHosting)
38+
vhostCache.Set(domain, v, cache.WithEx[*model.VirtualHost](time.Hour))
39+
return v, nil
40+
}
41+
42+
func GetVirtualHostById(id uint) (*model.VirtualHost, error) {
43+
return db.GetVirtualHostById(id)
44+
}
45+
46+
func CreateVirtualHost(v *model.VirtualHost) error {
47+
v.Path = utils.FixAndCleanPath(v.Path)
48+
vhostCache.Del(v.Domain)
49+
return db.CreateVirtualHost(v)
50+
}
51+
52+
func UpdateVirtualHost(v *model.VirtualHost) error {
53+
v.Path = utils.FixAndCleanPath(v.Path)
54+
old, err := db.GetVirtualHostById(v.ID)
55+
if err != nil {
56+
return err
57+
}
58+
// 如果域名变更,清除旧域名缓存
59+
vhostCache.Del(old.Domain)
60+
vhostCache.Del(v.Domain)
61+
return db.UpdateVirtualHost(v)
62+
}
63+
64+
func DeleteVirtualHostById(id uint) error {
65+
old, err := db.GetVirtualHostById(id)
66+
if err != nil {
67+
return err
68+
}
69+
vhostCache.Del(old.Domain)
70+
return db.DeleteVirtualHostById(id)
71+
}
72+
73+
func GetVirtualHosts(pageIndex, pageSize int) ([]model.VirtualHost, int64, error) {
74+
return db.GetVirtualHosts(pageIndex, pageSize)
75+
}

server/handles/fsread.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ func FsListSplit(c *gin.Context) {
6868
SharingList(c, &req)
6969
return
7070
}
71+
// 虚拟主机路径重映射:根据 Host 头匹配虚拟主机规则,将请求路径映射到实际路径
72+
req.Path = applyVhostPathMapping(c, req.Path)
7173
user := c.Request.Context().Value(conf.UserKey).(*model.User)
7274
if user.IsGuest() && user.Disabled {
7375
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
@@ -273,6 +275,11 @@ func FsGetSplit(c *gin.Context) {
273275
SharingGet(c, &req)
274276
return
275277
}
278+
// 虚拟主机路径重映射:根据 Host 头匹配虚拟主机规则,将请求路径映射到实际路径
279+
// 同时将 vhost.Path 前缀存入 context,供 FsGet 生成 /p/ 链接时去掉前缀
280+
var vhostPrefix string
281+
req.Path, vhostPrefix = applyVhostPathMappingWithPrefix(c, req.Path)
282+
common.GinWithValue(c, conf.VhostPrefixKey, vhostPrefix)
276283
user := c.Request.Context().Value(conf.UserKey).(*model.User)
277284
if user.IsGuest() && user.Disabled {
278285
common.ErrorStrResp(c, "Guest user is disabled, login please", 401)
@@ -322,12 +329,14 @@ func FsGet(c *gin.Context, req *FsGetReq, user *model.User) {
322329
rawURL = common.GenerateDownProxyURL(storage.GetStorage(), reqPath)
323330
if rawURL == "" {
324331
query := ""
332+
// 生成 /p/ 链接时,去掉 vhost 路径前缀,保持前端看到的路径一致
333+
downPath := stripVhostPrefix(c, reqPath)
325334
if isEncrypt(meta, reqPath) || setting.GetBool(conf.SignAll) {
326335
query = "?sign=" + sign.Sign(reqPath)
327336
}
328337
rawURL = fmt.Sprintf("%s/p%s%s",
329338
common.GetApiUrl(c),
330-
utils.EncodePath(reqPath, true),
339+
utils.EncodePath(downPath, true),
331340
query)
332341
}
333342
} else {
@@ -432,3 +441,60 @@ func FsOther(c *gin.Context) {
432441
}
433442
common.SuccessResp(c, res)
434443
}
444+
445+
// applyVhostPathMapping 根据请求的 Host 头匹配虚拟主机规则,将请求路径映射到实际路径。
446+
func applyVhostPathMapping(c *gin.Context, reqPath string) string {
447+
mapped, _ := applyVhostPathMappingWithPrefix(c, reqPath)
448+
return mapped
449+
}
450+
451+
// applyVhostPathMappingWithPrefix 根据请求的 Host 头匹配虚拟主机规则,
452+
// 将请求路径映射到虚拟主机配置的实际路径,同时返回 vhost.Path 前缀(用于生成下载链接时去掉前缀)。
453+
// 例如:vhost.Path="/123pan/Downloads",reqPath="/",则返回 ("/123pan/Downloads", "/123pan/Downloads")
454+
// 例如:vhost.Path="/123pan/Downloads",reqPath="/subdir",则返回 ("/123pan/Downloads/subdir", "/123pan/Downloads")
455+
// 如果没有匹配的虚拟主机规则,则返回 (原始路径, "")
456+
func applyVhostPathMappingWithPrefix(c *gin.Context, reqPath string) (string, string) {
457+
rawHost := c.Request.Host
458+
domain := stripHostPortForVhost(rawHost)
459+
if domain == "" {
460+
return reqPath, ""
461+
}
462+
vhost, err := op.GetVirtualHostByDomain(domain)
463+
if err != nil || vhost == nil {
464+
return reqPath, ""
465+
}
466+
if !vhost.Enabled || vhost.WebHosting {
467+
// 未启用,或者是 Web 托管模式(Web 托管不做路径重映射)
468+
return reqPath, ""
469+
}
470+
// 路径重映射:将 reqPath 拼接到 vhost.Path 后面
471+
mapped := stdpath.Join(vhost.Path, reqPath)
472+
utils.Log.Debugf("[VirtualHost] API path remapping: domain=%q reqPath=%q -> mappedPath=%q", domain, reqPath, mapped)
473+
return mapped, vhost.Path
474+
}
475+
476+
// stripVhostPrefix 从 gin context 中取出 vhost 路径前缀,并从 path 中去掉该前缀。
477+
// 用于生成 /p/ 下载链接时,将真实路径还原为前端看到的路径。
478+
func stripVhostPrefix(c *gin.Context, path string) string {
479+
prefix, ok := c.Request.Context().Value(conf.VhostPrefixKey).(string)
480+
if !ok || prefix == "" {
481+
return path
482+
}
483+
if strings.HasPrefix(path, prefix+"/") {
484+
return path[len(prefix):]
485+
}
486+
if path == prefix {
487+
return "/"
488+
}
489+
return path
490+
}
491+
492+
// stripHostPortForVhost 去掉 host 中的端口号,返回纯域名
493+
func stripHostPortForVhost(host string) string {
494+
if idx := strings.LastIndex(host, ":"); idx != -1 {
495+
if !strings.Contains(host, "[") {
496+
return host[:idx]
497+
}
498+
}
499+
return host
500+
}

0 commit comments

Comments
 (0)