补充实现细节

This commit is contained in:
dragon
2025-01-07 17:04:39 +08:00
parent bb8966521c
commit 0172dceaa8
9 changed files with 278 additions and 49 deletions

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}