补充实现细节
This commit is contained in:
@@ -1,23 +1,32 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.d7z.net/d7z-project/gitea-pages/pkg/utils"
|
||||
)
|
||||
|
||||
type BranchInfo struct {
|
||||
ID string `json:"id"`
|
||||
LastModified time.Time `json:"last_modified"`
|
||||
}
|
||||
|
||||
type Backend interface {
|
||||
// Repos return repo name + default branch
|
||||
Repos(owner string) (map[string]string, error)
|
||||
// Branches return branch + commit id
|
||||
Branches(owner, repo string) (map[string]string, error)
|
||||
Branches(owner, repo string) (map[string]*BranchInfo, error)
|
||||
// Open return file or error
|
||||
Open(client *http.Client, owner, repo, commit, path string, headers map[string]string) (*http.Response, error)
|
||||
Open(client *http.Client, owner, repo, commit, path string, headers http.Header) (*http.Response, error)
|
||||
}
|
||||
|
||||
type CacheBackend struct {
|
||||
@@ -60,8 +69,8 @@ func (c *CacheBackend) Repos(owner string) (map[string]string, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (c *CacheBackend) Branches(owner, repo string) (map[string]string, error) {
|
||||
ret := make(map[string]string)
|
||||
func (c *CacheBackend) Branches(owner, repo string) (map[string]*BranchInfo, error) {
|
||||
ret := make(map[string]*BranchInfo)
|
||||
key := fmt.Sprintf("branches/%s/%s", owner, repo)
|
||||
data, err := c.config.Get(key)
|
||||
if err != nil {
|
||||
@@ -90,6 +99,72 @@ func (c *CacheBackend) Branches(owner, repo string) (map[string]string, error) {
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (c *CacheBackend) Open(client *http.Client, owner, repo, commit, path string, headers map[string]string) (*http.Response, error) {
|
||||
func (c *CacheBackend) Open(client *http.Client, owner, repo, commit, path string, headers http.Header) (*http.Response, error) {
|
||||
return c.backend.Open(client, owner, repo, commit, path, headers)
|
||||
}
|
||||
|
||||
type CacheBackendBlobReader struct {
|
||||
client *http.Client
|
||||
cache utils.Cache
|
||||
base Backend
|
||||
maxSize int
|
||||
}
|
||||
|
||||
func NewCacheBackendBlobReader(client *http.Client, base Backend, cache utils.Cache, maxCacheSize int) *CacheBackendBlobReader {
|
||||
return &CacheBackendBlobReader{client: client, base: base, cache: cache, maxSize: maxCacheSize}
|
||||
}
|
||||
|
||||
func (c *CacheBackendBlobReader) Open(owner, repo, commit, path string) (io.ReadCloser, error) {
|
||||
key := fmt.Sprintf("%s/%s/%s%s", owner, repo, commit, path)
|
||||
lastCache, err := c.cache.Get(key)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
} else if lastCache == nil && err == nil {
|
||||
// 边界缓存
|
||||
return nil, os.ErrNotExist
|
||||
} else if lastCache != nil {
|
||||
return lastCache, nil
|
||||
}
|
||||
open, err := c.base.Open(c.client, owner, repo, commit, path, http.Header{})
|
||||
if err != nil {
|
||||
if open != nil {
|
||||
if open.StatusCode == http.StatusNotFound {
|
||||
// 缓存 404 路由
|
||||
_ = c.cache.Put(key, nil)
|
||||
}
|
||||
_ = open.Body.Close()
|
||||
}
|
||||
return nil, errors.Join(err, os.ErrNotExist)
|
||||
}
|
||||
|
||||
lastMod, err := time.Parse(http.TimeFormat, open.Header.Get("Last-Modified"))
|
||||
if err != nil {
|
||||
// 无时间,跳过
|
||||
return open.Body, nil
|
||||
}
|
||||
// 没法计算大小,跳过
|
||||
lengthStr := open.Header.Get("Content-Length")
|
||||
if lengthStr == "" {
|
||||
return open.Body, nil
|
||||
}
|
||||
length, err := strconv.Atoi(lengthStr)
|
||||
if err != nil || length > c.maxSize {
|
||||
// 超过最大大小,跳过
|
||||
return open.Body, err
|
||||
}
|
||||
defer open.Body.Close()
|
||||
allBytes, err := io.ReadAll(open.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = c.cache.Put(key, bytes.NewBuffer(allBytes)); err != nil {
|
||||
slog.Warn("缓存归档失败", "error", err)
|
||||
}
|
||||
return &utils.CacheContent{
|
||||
ReadSeekCloser: utils.NopCloser{
|
||||
ReadSeeker: bytes.NewReader(allBytes),
|
||||
},
|
||||
LastModified: lastMod,
|
||||
Length: length,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -14,38 +14,39 @@ import (
|
||||
)
|
||||
|
||||
type ServerMeta struct {
|
||||
client *http.Client
|
||||
Backend
|
||||
cache utils.Config
|
||||
ttl time.Duration
|
||||
|
||||
client *http.Client
|
||||
cache utils.Config
|
||||
ttl time.Duration
|
||||
|
||||
locker *utils.Locker
|
||||
}
|
||||
|
||||
type PageMeta struct {
|
||||
CommitID string `json:"id"` // 提交 COMMIT ID
|
||||
IsPage bool `json:"pg"` // 是否为 Page
|
||||
Domain string `json:"dm"` // 匹配的域名
|
||||
HistoryRouteMode bool `json:"rt"` // 路由模式
|
||||
CustomNotFound bool `json:"404"` // 注册了自定义 404 页面
|
||||
|
||||
type PageMetaContent struct {
|
||||
CommitID string `json:"id"` // 提交 COMMIT ID
|
||||
IsPage bool `json:"pg"` // 是否为 Page
|
||||
Domain string `json:"dm"` // 匹配的域名
|
||||
HistoryRouteMode bool `json:"rt"` // 路由模式
|
||||
CustomNotFound bool `json:"404"` // 注册了自定义 404 页面
|
||||
LastModified time.Time `json:"up"` // 上次更新时间
|
||||
}
|
||||
|
||||
func (m *PageMeta) From(data string) error {
|
||||
func (m *PageMetaContent) From(data string) error {
|
||||
return json.Unmarshal([]byte(data), m)
|
||||
}
|
||||
|
||||
func (m *PageMeta) String() string {
|
||||
func (m *PageMetaContent) String() string {
|
||||
marshal, _ := json.Marshal(m)
|
||||
return string(marshal)
|
||||
}
|
||||
|
||||
func NewServerMeta(client *http.Client, backend Backend, config utils.Config, ttl time.Duration) *ServerMeta {
|
||||
return &ServerMeta{client, backend, config, ttl, utils.NewLocker()}
|
||||
return &ServerMeta{backend, client, config, ttl, utils.NewLocker()}
|
||||
}
|
||||
|
||||
func (s *ServerMeta) GetMeta(owner, repo, branch string) (*PageMeta, error) {
|
||||
rel := &PageMeta{
|
||||
func (s *ServerMeta) GetMeta(owner, repo, branch string) (*PageMetaContent, error) {
|
||||
rel := &PageMetaContent{
|
||||
IsPage: false,
|
||||
}
|
||||
if repos, err := s.Repos(owner); err != nil {
|
||||
@@ -62,10 +63,12 @@ func (s *ServerMeta) GetMeta(owner, repo, branch string) (*PageMeta, error) {
|
||||
if branches, err := s.Branches(owner, repo); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
rel.CommitID = branches[branch]
|
||||
if rel.CommitID == "" {
|
||||
info := branches[branch]
|
||||
if info == nil {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
rel.CommitID = info.ID
|
||||
rel.LastModified = info.LastModified
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("meta/%s/%s/%s", owner, repo, branch)
|
||||
|
||||
@@ -1,9 +1,78 @@
|
||||
package core
|
||||
|
||||
type PageContent struct {
|
||||
meta PageMeta
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type PageDomain struct {
|
||||
*ServerMeta
|
||||
|
||||
baseDomain string
|
||||
defaultBranch string
|
||||
}
|
||||
|
||||
func (p *PageContent) GetMeta(domain, path string) (*PageMeta, error) {
|
||||
|
||||
func NewPageDomain(meta *ServerMeta, baseDomain, defaultBranch string) *PageDomain {
|
||||
return &PageDomain{
|
||||
baseDomain: baseDomain,
|
||||
defaultBranch: defaultBranch,
|
||||
ServerMeta: meta,
|
||||
}
|
||||
}
|
||||
|
||||
type PageDomainContent struct {
|
||||
*PageMetaContent
|
||||
|
||||
Owner string
|
||||
Repo string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (m *PageDomainContent) CacheKey() string {
|
||||
return fmt.Sprintf("%s/%s/%s%s", m.Owner, m.Repo, m.CommitID, m.Path)
|
||||
}
|
||||
|
||||
func (p *PageDomain) ParseDomainMeta(domain, path, branch string) (*PageDomainContent, error) {
|
||||
if branch == "" {
|
||||
branch = p.defaultBranch
|
||||
}
|
||||
|
||||
rel := &PageDomainContent{}
|
||||
if !strings.HasSuffix(domain, "."+p.baseDomain) {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
rel.Owner = strings.TrimSuffix(domain, "."+p.baseDomain)
|
||||
pathS := strings.Split(strings.TrimPrefix(path, "/"), "/")
|
||||
repo := pathS[0]
|
||||
defaultRepo := rel.Owner + "." + p.baseDomain
|
||||
if repo == "" {
|
||||
// 回退到默认仓库
|
||||
rel.Repo = defaultRepo
|
||||
}
|
||||
|
||||
meta, err := p.GetMeta(rel.Owner, rel.Repo, branch)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, err
|
||||
}
|
||||
if err == nil {
|
||||
rel.Path = "/" + strings.Join(pathS[1:], "/")
|
||||
rel.PageMetaContent = meta
|
||||
return rel, nil
|
||||
}
|
||||
if defaultRepo == rel.Repo {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
if meta, err := p.GetMeta(rel.Owner, defaultRepo, branch); err == nil {
|
||||
rel.PageMetaContent = meta
|
||||
rel.Repo = defaultRepo
|
||||
rel.Path = "/" + strings.Join(pathS, "/")
|
||||
return rel, nil
|
||||
}
|
||||
if strings.HasSuffix(path, "/") {
|
||||
rel.Path = rel.Path + "/index.html"
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user