1、新增首页合计详情页;
2、新增索引和缓存;
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
responseCacheTTL = 5 * time.Minute
|
||||
responseCacheMaxBody = 4 << 20
|
||||
responseCacheMaxKeys = 512
|
||||
)
|
||||
|
||||
type responseCacheEntry struct {
|
||||
status int
|
||||
contentType string
|
||||
body []byte
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
type responseCacheStore struct {
|
||||
mu sync.RWMutex
|
||||
items map[string]responseCacheEntry
|
||||
}
|
||||
|
||||
func newResponseCacheStore() *responseCacheStore {
|
||||
return &responseCacheStore{items: make(map[string]responseCacheEntry)}
|
||||
}
|
||||
|
||||
func (s *responseCacheStore) get(key string) (responseCacheEntry, bool) {
|
||||
now := time.Now()
|
||||
|
||||
s.mu.RLock()
|
||||
entry, ok := s.items[key]
|
||||
s.mu.RUnlock()
|
||||
if !ok {
|
||||
return responseCacheEntry{}, false
|
||||
}
|
||||
if now.After(entry.expiresAt) {
|
||||
s.mu.Lock()
|
||||
if current, exists := s.items[key]; exists && now.After(current.expiresAt) {
|
||||
delete(s.items, key)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return responseCacheEntry{}, false
|
||||
}
|
||||
return entry, true
|
||||
}
|
||||
|
||||
func (s *responseCacheStore) set(key string, entry responseCacheEntry) {
|
||||
s.mu.Lock()
|
||||
if len(s.items) >= responseCacheMaxKeys {
|
||||
now := time.Now()
|
||||
for cacheKey, current := range s.items {
|
||||
if now.After(current.expiresAt) || len(s.items) >= responseCacheMaxKeys {
|
||||
delete(s.items, cacheKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
s.items[key] = entry
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
type cachedResponseWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func (w cachedResponseWriter) Write(data []byte) (int, error) {
|
||||
w.body.Write(data)
|
||||
return w.ResponseWriter.Write(data)
|
||||
}
|
||||
|
||||
func cacheableGETResponseMiddleware(store *responseCacheStore) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.Request.Method != http.MethodGet || strings.HasPrefix(c.Request.URL.Path, "/api/v1/download/") {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
key := c.Request.URL.RequestURI()
|
||||
if entry, ok := store.get(key); ok {
|
||||
c.Header("Content-Type", entry.contentType)
|
||||
c.Data(entry.status, entry.contentType, entry.body)
|
||||
return
|
||||
}
|
||||
|
||||
body := bytes.NewBuffer(nil)
|
||||
writer := cachedResponseWriter{ResponseWriter: c.Writer, body: body}
|
||||
c.Writer = writer
|
||||
c.Next()
|
||||
|
||||
status := c.Writer.Status()
|
||||
if status != http.StatusOK || body.Len() == 0 || body.Len() > responseCacheMaxBody {
|
||||
return
|
||||
}
|
||||
|
||||
contentType := c.Writer.Header().Get("Content-Type")
|
||||
if !strings.Contains(contentType, "application/json") {
|
||||
return
|
||||
}
|
||||
|
||||
store.set(key, responseCacheEntry{
|
||||
status: status,
|
||||
contentType: contentType,
|
||||
body: append([]byte(nil), body.Bytes()...),
|
||||
expiresAt: time.Now().Add(responseCacheTTL),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ import (
|
||||
//
|
||||
// 函数集中注册跨域中间件和 v1 版本接口,调用方只需要注入各业务 Handler。
|
||||
func NewRouter(commonApi *api.CommonHandler, stockApi *api.StockHandler, limitBoardApi *api.LimitBoardHandler) *gin.Engine {
|
||||
r := gin.Default()
|
||||
r := gin.New()
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
// 跨域中间件允许前端从任意来源访问接口,并暴露 CSV 下载需要的 Content-Disposition。
|
||||
r.Use(func(c *gin.Context) {
|
||||
@@ -23,6 +24,7 @@ func NewRouter(commonApi *api.CommonHandler, stockApi *api.StockHandler, limitBo
|
||||
}
|
||||
c.Next()
|
||||
})
|
||||
r.Use(cacheableGETResponseMiddleware(newResponseCacheStore()))
|
||||
|
||||
v1 := r.Group("/api/v1")
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user