diff --git a/cache/redis.go b/cache/redis.go new file mode 100644 index 0000000..6470dac --- /dev/null +++ b/cache/redis.go @@ -0,0 +1,75 @@ +package cache + +import ( + "context" + "github.com/redis/go-redis/v9" + "io" + "net/http" + "os" + "path/filepath" +) + +const ( + Young = "Young_Obj" + Old = "Old_Obj" + Station = 2 +) + +type Redis struct { + db *redis.Client + c chan string +} + +func Init() *Redis { + rdb := redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", + DB: 0, + }) + if rdb.Ping(context.Background()).Err() != nil { + return &Redis{} + } + c := make(chan string) + go func() { + for v := range c { + resp, _ := http.Get(v) + os.MkdirAll(filepath.Dir("cache/"+v), os.ModePerm) + fd, _ := os.Create("cache/" + v) + io.Copy(fd, resp.Body) + fd.Close() + resp.Body.Close() + rdb.HIncrBy(context.Background(), Old, v, 1) + } + }() + return &Redis{ + db: rdb, + c: c, + } +} +func (c *Redis) Nil() bool { + return c.db == nil +} +func (c *Redis) Exists(key string) bool { + if c.Nil() { + return false + } + res, _ := c.db.HExists(context.Background(), Old, key).Result() + return res +} +func (c *Redis) Incr(fd string) { + if c.Nil() { + return + } + //有序列表 增加 + val, _ := c.db.HIncrBy(context.Background(), Young, fd, 1).Result() + // upgrade 策略 + if val > Station { + c.Upgrade(fd) + } +} +func (c *Redis) Upgrade(key string) { + if c.Nil() { + return + } + c.c <- key +} diff --git a/go.mod b/go.mod index fe1c8dd..f7c1b7c 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,13 @@ module github.com/sianao/gitproxy go 1.22.5 -require github.com/gorilla/mux v1.8.1 +require ( + github.com/dustin/go-humanize v1.0.1 + github.com/gorilla/mux v1.8.1 + github.com/redis/go-redis/v9 v9.7.0 +) -require github.com/dustin/go-humanize v1.0.1 // indirect +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect +) diff --git a/go.sum b/go.sum index 94d676b..514b619 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,14 @@ +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= diff --git a/handler/url.go b/handler/url.go index 7570ec3..6dc5944 100644 --- a/handler/url.go +++ b/handler/url.go @@ -55,7 +55,7 @@ func urlProcess(w http.ResponseWriter, r *http.Request) string { } } func NewHandler(route *mux.Router) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/" || strings.HasPrefix(r.RequestURI, "/_next/") { router.ServeHTTP(w, r, route) return diff --git a/main.go b/main.go index f1a7523..5b88476 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/sianao/gitproxy/cache" "log" "net/http" "time" @@ -10,7 +11,8 @@ import ( ) func main() { - newRouter := router.NewRouter() + c := cache.Init() + newRouter := router.NewRouter(c) srv := &http.Server{ Handler: handler.NewHandler(newRouter), Addr: "0.0.0.0:8888", diff --git a/router/default.go b/router/default.go index 4da4a35..56b0945 100644 --- a/router/default.go +++ b/router/default.go @@ -2,6 +2,7 @@ package router import ( "fmt" + "github.com/sianao/gitproxy/cache" "net/http" "strings" @@ -21,7 +22,7 @@ func ServeHTTP(w http.ResponseWriter, req *http.Request, route *mux.Router) { // https://raw.githubusercontent.com/laurent22/joplin/e652db05e1ba47725249a6ff543628aeeb32fad7/.gitignore // https://raw.githubusercontent.com/laurent22/joplin/android-v3.2.2/.gitignore // 建立新的router 这里先建立好 方便后续的router -func NewRouter() *mux.Router { +func NewRouter(c *cache.Redis) *mux.Router { route := mux.NewRouter() route.HandleFunc("/git-upload-pack", func(w http.ResponseWriter, r *http.Request) { userBaisc := r.Context().Value(&moudule.B).([]string) @@ -48,14 +49,23 @@ func NewRouter() *mux.Router { vars := mux.Vars(r) userBaisc := r.Context().Value(&moudule.B).([]string) var address = fmt.Sprintf("https://github.com/%s/%s/releases/download/%s/%s", userBaisc[0], userBaisc[1], vars["version"], vars["file"]) + c.Incr(address) + if c.Exists(address) { + http.FileServer(http.Dir("./cache")).ServeHTTP(w, r) + return + } service.PacketProxy(w, r, address) }) route.HandleFunc("/archive/refs/tags/{tags}", func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - userBaisc := r.Context().Value(&moudule.B).([]string) var address = fmt.Sprintf("https://github.com/%s/%s/archive/refs/tags/%s", userBaisc[0], userBaisc[1], vars["tags"]) + c.Incr(address) + if c.Exists(address) { + http.FileServer(http.Dir("./cache")).ServeHTTP(w, r) + return + } service.PacketProxy(w, r, address) }) @@ -65,16 +75,17 @@ func NewRouter() *mux.Router { http.Error(w, "bad address", 512) return } - if userBaisc[2] != "raw" && !strings.HasPrefix(r.RequestURI, "/blob") { http.Error(w, "bad address", 512) return } r.RequestURI = strings.TrimPrefix(r.RequestURI, "/blob") - var address = fmt.Sprintf("https://raw.githubusercontent.com/%s/%s%s", userBaisc[0], userBaisc[1], r.RequestURI) - + c.Incr(address) + if c.Exists(address) { + http.FileServer(http.Dir("./cache")).ServeHTTP(w, r) + } service.PacketProxy(w, r, address) }) diff --git a/src/theme/AppAppBar.tsx b/src/theme/AppAppBar.tsx index 85a7c75..6bdaf67 100644 --- a/src/theme/AppAppBar.tsx +++ b/src/theme/AppAppBar.tsx @@ -55,20 +55,20 @@ export default function AppAppBar() { {} - - - - - - + {/**/} + {/* */} + {/* */} + {/* */} + {/* */} + {/**/} - - + {/**/} + {/**/} {/* */} @@ -112,23 +112,23 @@ export default function AppAppBar() { - Features - Testimonials - Highlights - Pricing - FAQ - Blog - - - - - - - + {/*Features*/} + {/*Testimonials*/} + {/*Highlights*/} + {/*Pricing*/} + {/*FAQ*/} + {/*Blog*/} + {/**/} + {/**/} + {/* */} + {/**/} + {/**/} + {/* */} + {/**/} diff --git a/src/theme/Features.tsx b/src/theme/Features.tsx index c3f496f..c1051f4 100644 --- a/src/theme/Features.tsx +++ b/src/theme/Features.tsx @@ -13,261 +13,258 @@ import EdgesensorHighRoundedIcon from '@mui/icons-material/EdgesensorHighRounded import ViewQuiltRoundedIcon from '@mui/icons-material/ViewQuiltRounded'; const items = [ - { - icon: , - title: 'Dashboard', - description: - 'This item could provide a snapshot of the most important metrics or data points related to the product.', - imageLight: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/dash-light.png")`, - imageDark: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/dash-dark.png")`, - }, - { - icon: , - title: 'Mobile integration', - description: - 'This item could provide information about the mobile app version of the product.', - imageLight: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/mobile-light.png")`, - imageDark: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/mobile-dark.png")`, - }, - { - icon: , - title: 'Available on all platforms', - description: - 'This item could let users know the product is available on all platforms, such as web, mobile, and desktop.', - imageLight: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/devices-light.png")`, - imageDark: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/devices-dark.png")`, - }, + { + icon: , + title: '高速', + description: + '我们使用G口网络作为代理节点 分布式部署 根据用户所在地 IP 最优化寻址', + imageLight: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/dash-light.png")`, + imageDark: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/dash-dark.png")`, + }, + { + icon: , + title: '安全 ', + description: + '我们不缓存用户任何数据 不保留用户任何敏感信息 只单纯提供流量转发服务', + imageLight: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/mobile-light.png")`, + imageDark: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/mobile-dark.png")`, + }, + { + icon: , + title: '友好', + description: + '采用React + Go 作为技术栈构建web应用 用户界面交互友好 用户体验感优', + imageLight: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/devices-light.png")`, + imageDark: `url("${process.env.TEMPLATE_IMAGE_URL || 'https://mui.com'}/static/images/templates/templates-images/devices-dark.png")`, + }, ]; interface ChipProps { - selected?: boolean; + selected?: boolean; } const Chip = styled(MuiChip)(({ theme }:{theme: any}) => ({ - variants: [ - { - props: ({ selected }) => selected, - style: { - background: - 'linear-gradient(to bottom right, hsl(210, 98%, 48%), hsl(210, 98%, 35%))', - color: 'hsl(0, 0%, 100%)', - borderColor: (theme.vars || theme).palette.primary.light, - '& .MuiChip-label': { - color: 'hsl(0, 0%, 100%)', + variants: [ + { + props: ({ selected }) => selected, + style: { + background: + 'linear-gradient(to bottom right, hsl(210, 98%, 48%), hsl(210, 98%, 35%))', + color: 'hsl(0, 0%, 100%)', + borderColor: (theme.vars || theme).palette.primary.light, + '& .MuiChip-label': { + color: 'hsl(0, 0%, 100%)', + }, + ...theme.applyStyles('dark', { + borderColor: (theme.vars || theme).palette.primary.dark, + }), + }, }, - ...theme.applyStyles('dark', { - borderColor: (theme.vars || theme).palette.primary.dark, - }), - }, - }, - ], + ], })); interface MobileLayoutProps { - selectedItemIndex: number; - handleItemClick: (index: number) => void; - selectedFeature: (typeof items)[0]; + selectedItemIndex: number; + handleItemClick: (index: number) => void; + selectedFeature: (typeof items)[0]; } export function MobileLayout({ - selectedItemIndex, - handleItemClick, - selectedFeature, -}: MobileLayoutProps) { - if (!items[selectedItemIndex]) { - return null; - } + selectedItemIndex, + handleItemClick, + selectedFeature, + }: MobileLayoutProps) { + if (!items[selectedItemIndex]) { + return null; + } - return ( - - - {items.map(({ title }, index) => ( - handleItemClick(index)} - selected={selectedItemIndex === index} - /> - ))} - - + return ( ({ - mb: 2, - backgroundSize: 'cover', - backgroundPosition: 'center', - minHeight: 280, - backgroundImage: 'var(--items-imageLight)', - ...theme.applyStyles('dark', { - backgroundImage: 'var(--items-imageDark)', - }), - })} - style={ - items[selectedItemIndex] - ? ({ - '--items-imageLight': items[selectedItemIndex].imageLight, - '--items-imageDark': items[selectedItemIndex].imageDark, - } as any) - : {} - } - /> - - - {selectedFeature.title} - - - {selectedFeature.description} - + sx={{ + display: { xs: 'flex', sm: 'none' }, + flexDirection: 'column', + gap: 2, + }} + > + + {items.map(({ title }, index) => ( + handleItemClick(index)} + selected={selectedItemIndex === index} + /> + ))} + + + ({ + mb: 2, + backgroundSize: 'cover', + backgroundPosition: 'center', + minHeight: 280, + backgroundImage: 'var(--items-imageLight)', + ...theme.applyStyles('dark', { + backgroundImage: 'var(--items-imageDark)', + }), + })} + style={ + items[selectedItemIndex] + ? ({ + '--items-imageLight': items[selectedItemIndex].imageLight, + '--items-imageDark': items[selectedItemIndex].imageDark, + } as any) + : {} + } + /> + + + {selectedFeature.title} + + + {selectedFeature.description} + + + - - - ); + ); } export default function Features() { - const [selectedItemIndex, setSelectedItemIndex] = React.useState(0); + const [selectedItemIndex, setSelectedItemIndex] = React.useState(0); - const handleItemClick = (index: number) => { - setSelectedItemIndex(index); - }; + const handleItemClick = (index: number) => { + setSelectedItemIndex(index); + }; - const selectedFeature = items[selectedItemIndex]; + const selectedFeature = items[selectedItemIndex]; - return ( - - + return ( + + {/* - Product features - Provide a brief overview of the key features of the product. For example, - you could list the number of features, their types or benefits, and - add-ons. + - - -
- - {items.map(({ icon, title, description }, index) => ( - handleItemClick(index)} - sx={[ - (theme) => ({ - p: 2, - height: '100%', - width: '100%', - '&:hover': { - backgroundColor: theme.palette.action.hover, - }, - }), - selectedItemIndex === index && { - backgroundColor: 'action.selected', - }, - ]} - > + */} + +
+ + {items.map(({ icon, title, description }, index) => ( + handleItemClick(index)} + sx={[ + (theme) => ({ + p: 2, + height: '100%', + width: '100%', + '&:hover': { + backgroundColor: theme.palette.action.hover, + }, + }), + selectedItemIndex === index && { + backgroundColor: 'action.selected', + }, + ]} + > + + {icon} + + {title} + {description} + + + ))} + + +
- {icon} - - {title} - {description} + + ({ + m: 'auto', + width: 420, + height: 500, + backgroundSize: 'contain', + backgroundImage: 'var(--items-imageLight)', + ...theme.applyStyles('dark', { + backgroundImage: 'var(--items-imageDark)', + }), + })} + style={ + items[selectedItemIndex] + ? ({ + '--items-imageLight': items[selectedItemIndex].imageLight, + '--items-imageDark': items[selectedItemIndex].imageDark, + } as any) + : {} + } + /> + -
- ))} -
- -
- - - ({ - m: 'auto', - width: 420, - height: 500, - backgroundSize: 'contain', - backgroundImage: 'var(--items-imageLight)', - ...theme.applyStyles('dark', { - backgroundImage: 'var(--items-imageDark)', - }), - })} - style={ - items[selectedItemIndex] - ? ({ - '--items-imageLight': items[selectedItemIndex].imageLight, - '--items-imageDark': items[selectedItemIndex].imageDark, - } as any) - : {} - } - /> - - -
-
- ); +
+
+ ); } diff --git a/src/theme/Footer.tsx b/src/theme/Footer.tsx index 5df5448..bf7bd7b 100644 --- a/src/theme/Footer.tsx +++ b/src/theme/Footer.tsx @@ -14,96 +14,96 @@ import TwitterIcon from '@mui/icons-material/X'; import SitemarkIcon from './SitemarkIcon'; function Copyright() { - return ( - <> - - - {'Copyright © '} - - Sitemark - -   - {new Date().getFullYear()} - + return ( + <> - - - ); + + {'Copyright © '} + + GitProxy + +   + {new Date().getFullYear()} + + + + + ); } export default function Footer() { - return ( - - - - - - + + + + {/**/} + {/* Join the newsletter Subscribe for weekly updates. No spams ever! - - Email - - - - - - - - + */} + Email + + + + + + + + {/* Product @@ -120,87 +120,87 @@ export default function Footer() { FAQs - - - - - Company - - - About us - - - Careers - - - Press - - - - - Legal - - - Terms - - - Privacy - - - Contact - - - - -
- - Privacy Policy - - -  •  - - - Terms of Service - - -
- - - - - */} +
+ + + Company + + + About us + + + Careers + + + Press + + + + + Legal + + + Terms + + + Privacy + + + Contact + + +
+ +
+ + Privacy Policy + + +  •  + + + Terms of Service + + +
+ + + + + {/* - - */} + {/* - - - -
-
- ); + */} + {/* */} + + + + ); }