diff --git a/go.mod b/go.mod index 230152e..8624593 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module blog.bing89.com go 1.17 require ( + github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible github.com/coreos/go-oidc/v3 v3.1.0 github.com/gin-gonic/gin v1.8.1 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e diff --git a/go.sum b/go.sum index 3910ea5..0be41af 100644 --- a/go.sum +++ b/go.sum @@ -64,6 +64,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible h1:KXeJoM1wo9I/6xPTyt6qCxoSZnmASiAjlrr0dyTUKt8= +github.com/aliyun/aliyun-oss-go-sdk v2.2.6+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= diff --git a/go/oss_image_resize/main.go b/go/oss_image_resize/main.go new file mode 100644 index 0000000..6d13425 --- /dev/null +++ b/go/oss_image_resize/main.go @@ -0,0 +1,538 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "io" + "net/http" + "os" + "path" + "sort" + "strconv" + "strings" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +const ( + metaKeyUpdatedAt = "UpdatedAt" + metaKeyModifiedAt = "Last-Modified" + metaKeyFileSize = "Content-Length" +) + +type AliOSS struct { + bucket *oss.Bucket + client *oss.Client + endpoint string + accessKey string + secret string + bucketName string +} + +func NewAliOSS(endpoint, accessKey, secret string, bucket string) *AliOSS { + return &AliOSS{ + endpoint: endpoint, + accessKey: accessKey, + secret: secret, + bucketName: bucket, + } +} + +func (a *AliOSS) Init(opts ...oss.ClientOption) error { + client, err := oss.New(a.endpoint, a.accessKey, a.secret, opts...) + if err != nil { + return err + } + a.client = client + a.bucket, err = client.Bucket(a.bucketName) + return err +} + +// Copy 拷贝文件 +func (a *AliOSS) Copy(src, dst string, opts ...oss.Option) error { + + _, err := a.bucket.CopyObject(src, dst, opts...) + return err +} + +func (a *AliOSS) Delete(f []string, opts ...oss.Option) ([]string, error) { + res, err := a.bucket.DeleteObjects(f, opts...) + return res.DeletedObjects, err +} + +// Download 下载文件 +func (a *AliOSS) Download(obj, savePath string, opts ...oss.Option) error { + err := a.bucket.GetObjectToFile(obj, savePath, opts...) + return err +} + +// IsExist 判断文件是否存在 +func (a *AliOSS) IsExist(obj string) (bool, error) { + return a.bucket.IsObjectExist(obj) +} + +func (a *AliOSS) UploadObject(dest string, src io.Reader, opts ...oss.Option) error { + return a.bucket.PutObject(dest, src, opts...) +} + +func (a *AliOSS) UploadByte(dest string, buf []byte, opts ...oss.Option) error { + return a.UploadObject(dest, bytes.NewReader(buf), opts...) +} + +func (a *AliOSS) UploadStream(dest string, src *os.File, opts ...oss.Option) error { + // 读取本地文件。 + return a.UploadObject(dest, src, opts...) +} + +func (a *AliOSS) UploadFile(dest string, filePath string, opts ...oss.Option) error { + err := a.bucket.PutObjectFromFile(dest, filePath, opts...) + return err +} + +// set meta +func (a *AliOSS) SetObjectMeta(file string, key, val string) error { + meta := oss.Meta(key, val) + err := a.bucket.SetObjectMeta(file, meta) + return err +} + +// set metas +func (a *AliOSS) SetObjectMetaMap(file string, metaMap map[string]string) error { + metas := []oss.Option{} + for k, v := range metaMap { + metas = append(metas, oss.Meta(k, v)) + } + if len(metas) == 0 { + return nil + } + return a.bucket.SetObjectMeta(file, metas...) +} + +// get metas +func (a *AliOSS) GetObjectMeta(file string) (map[string]string, error) { + res := make(map[string]string) + metas, err := a.bucket.GetObjectMeta(file) + for k, v := range metas { + val := "" + if len(v) > 0 { + val = v[0] + } + res[k] = val + } + return res, err +} + +// set updatedAt +func (a *AliOSS) SetUpdatedAt(file string, updatedAt *time.Time) error { + if updatedAt == nil { + now := time.Now() + updatedAt = &now + } + return a.SetObjectMeta(file, metaKeyUpdatedAt, strconv.FormatInt(updatedAt.Unix(), 10)) +} + +// get updatedAt +func (a *AliOSS) GetUpdatedAt(file string) (int64, error) { + metas, err := a.GetObjectMeta(file) + if err != nil { + return 0, err + } + if u, ok := metas[metaKeyUpdatedAt]; ok { + i, err := strconv.ParseInt(u, 10, 64) + return i, err + } + return 0, nil +} + +// get updatedAt +func (a *AliOSS) GetModifiedAt(file string) (int64, error) { + metas, err := a.GetObjectMeta(file) + if err != nil { + return 0, err + } + if u, ok := metas[metaKeyModifiedAt]; ok { + //Thu, 07 Apr 2022 03:13:49 GMT + t, err := time.Parse(time.RFC1123, u) + return t.Unix(), err + } + return 0, nil +} + +// upload stream with set updated at +func (a *AliOSS) UploadObjectWithUpdatedAt(dest string, src io.Reader, opts ...oss.Option) error { + opt := oss.Meta(metaKeyUpdatedAt, strconv.FormatInt(time.Now().Unix(), 10)) + opts = append(opts, opt) + return a.UploadObject(dest, src, opts...) +} + +// upload file with set updated at +func (a *AliOSS) UploadFileWithUpdatedAt(dest string, src string, opts ...oss.Option) error { + err := a.UploadFile(dest, src, opts...) + if err != nil { + return err + } + return a.SetUpdatedAt(dest, nil) +} + +// upload bytes with set updated at +func (a *AliOSS) UploadBytesWithUpdatedAt(dest string, src []byte, opts ...oss.Option) error { + opt := oss.Meta(metaKeyUpdatedAt, strconv.FormatInt(time.Now().Unix(), 10)) + opts = append(opts, opt) + return a.UploadByte(dest, src, opts...) +} + +func (a *AliOSS) Name() string { + return a.bucketName +} + +// check file version, if not need download ,return true +func (a *AliOSS) CheckFileVersion(obj string, info os.FileInfo) (bool, error) { + metas, err := a.GetObjectMeta(obj) + if err != nil { + return true, err + } + // var modifiedAt time.Time + var modifiedAt int64 + if u, ok := metas[metaKeyModifiedAt]; ok { + //Thu, 07 Apr 2022 03:13:49 GMT + t, err := time.Parse(time.RFC1123, u) + if err == nil { + modifiedAt = t.Unix() + } + // modifiedAt, _ = strconv.ParseInt(u, 10, 64) + } + //TODO is need to check file size?? + // var length int64 = 0 + // if u, ok := metas[metaKeyFileSize]; ok { + // n, err := strconv.ParseInt(u, 10, 64) + // if err == nil { + // length = n + // } + // } + //如果本地文件时间更靠后或者本地文件更大,返回true + // return info.ModTime().Unix() >= modifiedAt || length <= info.Size(), nil + //如果本地文件时间更靠后,返回true, 防止意外退出,未成功上传 + return info.ModTime().Unix() >= modifiedAt, nil +} + + +// const host = "http://testspeed.xk.design" + +const MAX = 300 + +type file struct{ + Name string + Width int + Height int + Src string +} + +var test1 = []file{ + { + Name: "test4k.jpg", + Width: 0, + Height: 0, + Src: "test4k.jpg", + }, + { + Name: "test2k.jpg", + Width: 2590, + Height: 1619, + Src: "test4k.jpg", + }, + { + Name: "test1k.jpg", + Width: 1831, + Height: 1144, + Src: "test4k.jpg", + }, + { + Name: "test600.jpg", + Width: 647, + Height: 404, + Src: "test4k.jpg", + }, + { + Name: "test1of10.jpg", + Width: 384, + Height: 240, + Src: "test4k.jpg", + }, +} + +var test2 = []file{ + { + Name: "8ktestsrc.jpeg", + Width: 7680, + Height: 4320, + Src: "8ktestsrc.jpeg", + }, + { + Name: "8ktest3k.jpeg", + Width: 2730, + Height: 1536, + Src: "8ktestsrc.jpeg", + }, + { + Name: "8ktest1080.jpeg", + Width: 1930, + Height: 1086, + Src: "8ktestsrc.jpeg", + }, + { + Name: "8ktest682.jpeg", + Width: 682, + Height: 384, + Src: "8ktestsrc.jpeg", + }, + { + Name: "8ktest400.jpeg", + Width: 400, + Height: 225, + Src: "8ktestsrc.jpeg", + }, + { + Name: "8ktest300.jpeg", + Width: 300, + Height: 300, + Src: "8ktestsrc.jpeg", + }, +} + +func NewRequest(url string)(*http.Request, error){ + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + req.Header.Add("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9") + req.Header.Add("Accept-Encoding","gzip, deflate") + req.Header.Add("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 Edg/107.0.1418.56") + req.Header.Add("Accept-Language","zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6") + req.Header.Add("Cache-Control","no-cache") + req.Header.Add("Connection"," keep-alive") + req.Header.Add("Cookie"," Hm_lvt_2e2c80c1bd8a51afc2d7de641330a397=1669089775,1669615598,1669704782,1669875251; Hm_lpvt_2e2c80c1bd8a51afc2d7de641330a397=1669964998") + req.Header.Add("Host"," test-pic.xk.design") + // req.Header.Add("If-Modified-Since"," Fri, 02 Dec 2022 06:31:28 GMT") + // req.Header.Add("If-None-Match","\"4B8579BA5E07DD57405973606E777476\"") + // req.Header.Add("Upgrade-Insecure-Requests","1") + return req, nil +} + + +func test(f file, count int, host string){ + fmt.Printf("begin normal testing for %s\n", f.Name) + startedAt := time.Now().UnixMilli() + errCount := 0 + costMS := []int64{} + for i := 0; i < count; i ++ { + req, err := NewRequest(host + "/" + f.Name) + if err != nil { + errCount ++ + continue + } + client := &http.Client{} + begin := time.Now().UnixMilli() + resp, err := client.Do(req) + if err != nil { + errCount++ + continue + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + os.WriteFile("/dev/null", data, os.ModePerm) + if err != nil { + errCount++ + continue + } + end := time.Now().UnixMilli() + costMS = append(costMS, end-begin) + } + finishedAt := time.Now().UnixMilli() + totalCost := finishedAt - startedAt + fmt.Printf("in %d tests for file %s@%dx%d, cost %d ms, errors: %d", MAX, f.Src, f.Width, f.Height, totalCost, errCount) + statistics(costMS) +} + +func testResize(f file, count int, host string)(int64, int64, int64){ + fmt.Printf("begin resize testing for %s\n", f.Name) + startedAt := time.Now().UnixMilli() + errCount := 0 + costMS := []int64{} + for i := 0; i < count; i ++ { + query := "?x-oss-process=image/resize,m_lfit,w_"+strconv.Itoa(f.Width) + ",h_" + strconv.Itoa(f.Height) + if f.Height == 0 || f.Width == 0 { + query = "" + } + req, err := NewRequest(host + "/" + f.Src + query) + if err != nil { + errCount++ + continue + } + client := http.Client{} + begin := time.Now().UnixMilli() + resp, err := client.Do(req) + if err != nil { + errCount++ + continue + } + defer resp.Body.Close() + data, err := io.ReadAll(resp.Body) + os.WriteFile("/dev/null", data, os.ModePerm) + if err != nil { + errCount++ + continue + } + end := time.Now().UnixMilli() + costMS = append(costMS, end-begin) + // if resp != nil { + // fmt.Println(resp , resp.StatusCode, resp.Status) + // } + } + finishedAt := time.Now().UnixMilli() + totalCost := finishedAt - startedAt + fmt.Printf("in %d tests for file %s@%dx%d, cost %d ms, errors: %d", MAX, f.Src, f.Width, f.Height, totalCost, errCount) + return statistics(costMS) +} + +func filelist(dir string)([]string, error){ + fis, err := os.ReadDir(dir) + if err != nil { + return nil, err + } + fs := []string{} + for _, f := range fis { + if !f.IsDir() { + fs = append(fs, f.Name()) + } + } + return fs, nil +} + + +func case3(dir string, ak string, sk string, count int, host string)error{ + files, err := filelist(dir) + if err != nil { + return err + } + alioss := NewAliOSS("oss-cn-hangzhou.aliyuncs.com", ak,sk, "oxslmimg") + alioss.Init() + avgmap := make(map[string][]string) + p95map := make(map[string][]string) + for _, f := range files { + err = alioss.UploadFile(f, path.Join(dir,f)) + if err != nil { + fmt.Println("upload failed:", err, f, path.Join(dir,f)) + continue + } + avgmap[f] = []string{} + p95map[f] = []string{} + for _, t := range test1 { + t.Src = f + t.Name = f + avg, p95, _ := testResize(t, count, host) + avgmap[f] = append(avgmap[f], strconv.FormatInt(avg, 10)) + p95map[f] = append(p95map[f], strconv.FormatInt(p95, 10)) + } + } + fmt.Println("") + for f, v := range avgmap { + fmt.Printf("%s\t%s\n", f, strings.Join(v, "\t")) + } + for f, v := range p95map { + fmt.Printf("%s\t%s\n", f, strings.Join(v, "\t")) + } + return nil +} + +func avg(data []int64)int64{ + if len(data) == 0 { + return 0 + } + total := int64(0) + for _, i := range data { + total = total + i + } + return total/int64(len(data)) +} + +type SortInt64 []int64 + +func (a SortInt64) Len() int { return len(a) } +func (a SortInt64) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a SortInt64) Less(i, j int) bool { return a[i] < a[j] } + +func percent50(data []int64)int64{ + if len(data) == 0 { + return 0 + } + i := int(float64(len(data)) * 0.5 ) + if i == len(data) { + return 0 + } + return data[i] +} + +func percent90(data []int64)int64{ + if len(data) == 0 { + return 0 + } + i := int(float64(len(data)) * 0.9 ) + if i == len(data) { + return 0 + } + return data[i] +} + +func percent95(data []int64)int64{ + if len(data) == 0 { + return 0 + } + i := int(float64(len(data)) * 0.95 ) + if i == len(data) { + return 0 + } + return data[i] +} + +func max(data []int64)int64{ + return data[len(data)-1] +} + +func statistics(data []int64)(int64, int64, int64){ + a := avg(data) + sort.Sort(SortInt64(data)) + m := percent50(data) + p9 := percent90(data) + p95 := percent95(data) + ma := max(data) + fmt.Printf("\t avg: %d middle: %dms percent90: %dms percent95: %dms max: %dms\n", a, m, p9, p95, ma) + return a, p95, ma +} + +func main(){ + var tcase, count int + var host, ak, sk, dir string + flag.IntVar(&tcase, "case", 1, "which test case to use") + flag.IntVar(&count, "count", 100, "total test count") + flag.StringVar(&host, "host", "http://testspeed.xk.design", "access host") + flag.StringVar(&ak, "ak", "", "access key") + flag.StringVar(&sk, "sk", "", "access secret") + flag.StringVar(&dir , "dir", "", "img path") + flag.Parse() + switch tcase { + case 1: + for _, f := range test1 { + test(f, count, host) + testResize(f, count, host) + } + case 2: + for _, f := range test2 { + test(f, count, host) + testResize(f, count, host) + } + case 3: + case3(dir, ak, sk, count, host) + } +} \ No newline at end of file diff --git a/go/uiweb/index.tmpl b/go/uiweb/index.tmpl new file mode 100644 index 0000000..950e11d --- /dev/null +++ b/go/uiweb/index.tmpl @@ -0,0 +1,31 @@ + + + + + + + {{.title}} + + +
+
+
+

{{.title}}

+
+
+ {{ range .files }} +
+ +
+ {{else}} +
+

这家伙很懒,还没有发布任何信息

+
+ {{end}} +
+
+ + + + + \ No newline at end of file diff --git a/go/uiweb/main.go b/go/uiweb/main.go new file mode 100644 index 0000000..8b51622 --- /dev/null +++ b/go/uiweb/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "flag" + + "net/http" + "os" + + + "github.com/gin-gonic/gin" +) + +func run(dir, title, addr string)error{ + engine := gin.Default() + engine.LoadHTMLGlob(dir + "/*.tmpl") + engine.Static("/ui", dir) + engine.GET("/", func(ctx *gin.Context) { + dirs, _ := os.ReadDir(dir) + files := []string{} + for _, d := range dirs { + if !d.IsDir() { + continue + } + files = append(files, d.Name()) + } + ctx.HTML(http.StatusOK, "index.tmpl", gin.H{ + "title": title, + "files": files, + }) + }) + engine.GET("/favicon.ico", func(ctx *gin.Context) { + ctx.Header("Content-Type", "image/x-icon") + ctx.Writer.WriteString("data:image/x-icon;base64,AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAD/5sgA/9CaAP/SngD/1aMA/8N9Qv+dLMr/qklw/65RI/+9cgD/yowA/71yAP+1XwD/r1MA/8F5AP/AeAD/t2QA/7BVAP+wVgD/wnwA/7prAP+0XQD/rE0A/7ZjAP/AeAD/t2UA/7FYAP+pRwD/u20A//bsAP///wD///8A////AP/lxwD/z5gA/9KcAP/TnwD/yowi/5Ub//+SFP//lRzw/6E0tv+yWmj/uGYh/7diAP+xVwD/w3wA/8F5AP+4ZAD/sVUA/7FWAP/DfAD/u2sA/7VdAP+tTQD/t2MA/8F4AP+4ZQD/slgA/6pHAP+8bQD/9+0A////AP///wD///8A/+bJAP/RmwD/058A/9SgAP/SnAf/nSze/5IV//+UGP//khX//5IT//+WHPX/nS27/6Q7a/+8byP/wnsA/7ppAP+xVwD/sVYA/8N8AP+7awD/tV0A/61NAP+3YwD/wXgA/7hlAP+yWAD/qkcA/7xtAP/37QD///8A////AP///wD/5cUA/86UAP/QmAD/0JkA/9KdAP+lPbv/kRP//5UZ//+UGf//lRn//5QY//+TFv//kxX//5Yd9v+gMrz/qUZt/61OJP+yWQD/xYIA/7xuAP+1XgD/rU0A/7djAP/BeAD/uGUA/7JYAP+qRwD/vG0A//jvAP///wD///8A////AP/jwgD/zJAA/86UAP/OlAD/0ZsA/65RkP+REv//lRn//5QZ//+VGf//lRn//5UZ//+VGf//lBj//5MV//+SFf//lRr3/5wrvv+vU27/tmEl/7ZgAP+uUQD/uGUA/8F5AP+4ZQD/slgA/6pJAP+7bAD/zZIA/86VAP/MkQD/5MQA/+PDAP/MkAD/zpUA/86UAP/RmwD/uGZk/5ES//+UGf//lBn//5UZ//+VGf//lRn//5UZ//+VGf//lRn//5UZ//+UGP//kxb//5IT//+WHPj/nSu//6I2b/+zXCb/wnoA/7pqAP+zWgD/q0oA/7trAP+/cwD/vXAA/7pqAP/asAD/4b4A/8iIAP/LjQD/y40A/82SAP++czv/kxf//5QZ//+UGf//lRn//5UZ//+VGf//lRn//5UZ//+VGf//lRn//5UZ//+VGf//lRn//5QY//+TFv//kxX//5Yc+P+fMMD/qUVx/65QJ/+rSwD/vW8A/8B3AP+/dQD/vG8A/9yyAP/ivwD/yYoA/8uOAP/LjgD/zJEA/8aDGv+XIPT/kxf//5QZ//+VGf//lRn//5UZ//+VGf//lRn//5UZ//+VGf//lRn//5UZ//+VGf//lRn//5UZ//+VGf//lBj//5IV//+SFf//lRv5/5kjwf+tTmn/wnsC/8J6AP+/dAD/3bUA/+G9AP/HhwD/yosA/8qLAP/KjAD/yosD/50t1/+SFf//kxn//5QZ//+UGf//lBn//5QZ//+UGf//lBn//5QZ//+UGf//lBn//5QZ//+UGf//lBn//5QZ//+UGf//kxn//5UZ//+UGP//kRP//5wq3P/EgAH/xIAA/8F6AP/euAD/4LsA/8WBAP/HhgD/x4YA/8eGAP/KiwD/pDyx/5EU//+UGf//lRn//5UZ//+VGf//lRn//5QZ//+UGP//lBj//5QZ//+UGf//lRn//5UZ//+VGf//lRn//5UZ//+UGf//lRn//5UZ//+SFP//oje4/8WCAP/EfgD/wXgA/963AP/hvAD/xoUA/8mJAP/JiQD/yYkA/8yQAP+uUIb/kRL//5QZ//+VGf//lRn//5UZ//+UGf//lhv+/5Ub9v+SE/3/khP//5ES//+REf//kBD//5AQ//+QEP//kBD//48Q//+REf//kRH//44M//+nQaL/y40A/8iHAP/FggD/4LwA/9+6AP/EfgD/xoMA/8aDAP/GgwD/yYkA/7VfW/+SFP//lBn//5UZ//+VGf//lRn//5MX//+YIeX/vnEr/8mLJ//EgDD/umlG/7hlVv+2YWb/s1p3/7FWiP+uT5j/rEyp/6pGuP+nQsf/ojfV/7pqc//UoQD/0ZsA/8+WAP/myAD/3bQA/8N+AP/GhAD/xoMA/8aDAP/IhwD/vW8z/5QZ//+UGf//lRn//5UZ//+VGf//kxf//5cf3v/hvAf/3LNW/+nOLf///wD///8A////AP///wD///8A////AP///wD///8A////AP/+/AL//fkC//LgAP/x3gD/8N0A//ftAP/bsQD/wHcA/8aEAP/HhQD/x4UA/8eHAP/DfRP/mCLv/5MX//+VGf//lRn//5UZ//+TFv//mCHc/9enAf/x3yf/ojfc/7lnoP/u2ir///8A////AP///wD///8A////AP///wD///8A////AP///wD/8uIA//HgAP/w3wD/9+4A/9yyAP+9cQD/wXkA/8N8AP/EfwD/xH8A/8R/Af+eLc//khX//5UZ//+VGf//lRn//5MW//+aJdf/yIcD////AP/q0DX/kBH//5MX+P+9cZn/7dgp////AP///wD///8A////AP///wD///8A///+AP/26gD/9ukA//XoAP/58gD/3LIA/75zAP/AdwD/wXoA/8J8AP/DfQD/xYIA/6U9qP+SFP//lRn//5UZ//+VGf//kxb//5sn0//LjQH///8A////AP/dtFL/jw///4wJ//+WHPb/vXCY/+7ZKP///wD///8A////AP///wD/+O4A/7dkAP+yWQD/tmEA/+rRAP/csgD/vnIA/8F3AP/CegD/wnsA/8R/AP/IiQD/r1J2/5ET//+VGf//lRn//5UZ//+TFv//myjP/8uNAP///wD///8A////AP/Nk3b/jgz//5MW//+OC///lh31/75yl//v2yf///8A////AP/05QD/kxgA/40KAP+KAwD/w34A/920AP/AeAD/w30A/8SAAP/DfgD/wXkF/8F4Ff+vUm3/khX//5QZ//+VGf//lRn//5IW//+cKsr/y44A////AP///wD///8A////AP++cZv/jQr//5Ua//+TFv//jQv//5ce9f++c5X/8N0n//frAP+aJgD/lBkA/5ESAP/EgAD/1KIt/6xNZv+qSIX/pT2k/6AzwP+cKdn/mCDu/5Ub+/+UGf//lBn//5UZ//+VGf//khX//5wrxv/JigD///8A////AP///wD///8A//36Bv+vUr3/jgz//5Ua//+UGf//kxb//4oD//+xV7z//PcA/791AP+7bAD/uWgA/9quAP/KjHP/jgz//5IT//+SFP//kxX//5MW//+UF///lBj//5QZ//+VGf//lRn//5UZ//+SFf//nS3B/8qLAP///wD///8A////AP///wD///8A//brFv+iN9r/kBD//5Qa//+VGf//jg3//7tspv/+/AD/yIgA/8WAAP/DfQD/37oA/9epPP+TFv7/lBn//5UZ//+VGf//lRn//5UZ//+VGf//lBn//5UZ//+VGf//lRn//5IV//+eL7z/y44A////AP///wD///8A////AP///wD///8A/+3XK/+bKOn/khX//5UZ//+OC///x4aI///9AP/GgwD/wnwA/8B4AP/etwD/4LsW/5gh6v+TF///kxX//5MW//+UGf//lRn//5UZ//+UGf//lRn//5UZ//+VGf//khX//58xt//LjgD///8A////AP///wD///8A////AP///wD///0D/6M63P+RE///lRn//44L///Tn2z///0A/8iJAP/FgQD/w34A/9+6AP/gvAH/nSzK/5ES//+gMdH/nSvY/5MV//+REv//kxb//5MZ//+VGf//lRn//5UZ//+SFf//oDKy/8uOAP///wD///8A////AP///wD///8A////AP/cs1T/jw7//5QZ//+UGf//jw7//9+4Uf/+/AD/xYEA/8F5AP+/dgD/3bYA/+PCAP+kO6r/khb//8uPJv/DfQf/umlF/6xMlv+aJd7/khX//5ES//+TF///lBn//5IU//+hNK3/y44A///+AP///wD///8A////AP///wD///8A/7FXuv+PDv//lBn//5QY//+SFP//6c44//77AP/IhwD/xIAA/8J8AP/fuQD/58kA/6tKj/+oRLr/3LMA/8eFAP/KiwD/zpUA/71wC/+4ZUf/q0qY/5kk4P+SFf//jw7//6E0rP/LjgD///4A////AP///wD///8A////AP/u2Sr/lBn7/5MX//+UGf//kxf//5cg+f/16B3//PcA/8J7AP++cwD/vG8A/9yyAP/kxAD/tmJY/8eGQv/csgD/z5YA/8eGAP/LjgD/vnIA/8iIAP/NkQD/u2wM/7hlSf+nQqD/qEOL/86UAP///wD///8A////AP///wD///8A/8WBi/+NC///lRn//5QZ//+TF///lRz3/+3VL////wD/3rcA/9yyAP/bsAD/7NUA/+/cAP/guwf/5MQB/+XGAP/hvQD/zJAA/8qNAP++cQD/xoMA/8qLAP+8bgD/yYoA/8iJAP/NkgL/69EA//DdAP/s1QD/6M0A/+rQAP/lxQ//ni3m/5IU//+VGf//lBn//5UZ//+REv//pDzU/+zVE//x3gD/7dcA/+3VAP/16QD/9uoA/+7ZAP/u2gD/79oA/+rSAP/OlAD/yowA/75xAP/GggD/yosA/7xtAP/HhAD/xoQA/82SAP/oywD/4sAA/9qvAP/arQD/3bUA/8J6U/+PD///khP//5MW//+TGf//lRn//5Ua//+ODf//qke4/+zUBP/t1wD/6tEA//TnAP/16AD/7NUA/+3XAP/u2AD/6c8A/86UAP/KjQD/vnEA/8aCAP/KiwD/vG0A/8eFAP/GhAD/zZIA/+jMAP/ivwD/3LIA/920AP/fuQD/x4UP/6hER/+nQpj/mibg/5IW//+TFf//kxf//5QZ//+ODf//t2OW//DdAP/s1AD/9egA//XoAP/s1QD/7dcA/+7YAP/pzwD/zpQA/8qNAP++cQD/xoIA/8qLAP+8bQD/x4QA/8aDAP/NkgD/6MwA/+K/AP/cswD/3bQA/9+5AP/JigD/sVcA/8eFAP/AeAz/rlFI/6Ezmv+aJOH/kxb//5IU//+ODf//xH9y//HeAP/16AD/9egA/+zVAP/t1wD/7tgA/+nPAP/OkwD/yowA/75xAP/GggD/yooA/7xtAP/HhAD/xoMA/82RAP/ozAD/4r8A/9yyAP/dswD/37kA/8mJAP+vVAD/xH8A/8J9AP+5agD/tF4A/8N9Df+2YUj/ozmY/5cf3v+REv//y49Z//jwAP/05wD/69QA/+zWAP/t1wD/6M4A/82TAP/JjAD/vXEA/8WCAP/JigD/u20A/8aEAP/FgwD/zJEA/+fLAP/hvgD/27IA/9yzAP/euAD/yIkA/69UAP/DfwD/wXsA/7dmAP+yWgD/xoMA/8WAAP+7awD/sVcP/7JZV/+nQ57/5sgk8P////Af///wA///+AB///gAD//4AAH/+AAAP/gAAAf4AAAH/AAAD/wAAA/8AAAP/AB/z/wAH//8AQf//gGB//4DwH/4A+AfAAPgHwAD8B8AA/gfAAP4HwAD+B+AA/gfngPwH5/D8B+f++AP///gB///4Af///wD////gf////A=") + }) + return engine.Run(addr) +} + +func main(){ + var dir, title, addr string + flag.StringVar(&dir, "dir", "./", "service dir") + flag.StringVar(&title, "title", "无标题", "website title") + flag.StringVar(&addr,"addr", ":3000", "listen port") + flag.Parse() + err := run(dir, title, addr) + if err != nil { + panic(err) + } +} \ No newline at end of file