├── LICENSE ├── README.md ├── build ├── conf │ └── config.ini └── main.go ├── zcache.go ├── zcache_test.go ├── zcommon.go ├── zconfig.go ├── zconfig_test.go ├── zhttpd.go ├── zimage.go ├── zlog.go ├── zlog_test.go ├── zredisdb.go ├── zredisdb_test.go ├── zscale.go ├── zstorage.go ├── zstorage_base.go ├── zstorage_file.go ├── zstorage_ssdb.go ├── zutil.go └── zutil_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gimg 2 | 3 | - - - 4 | Gimg是[zimg](https://github.com/buaazp/zimg)的golang版本。 5 | 6 | 完全兼容zimg的文件目录储存格式,支持文件和类Redis协议(SSDB)储存。 7 | 8 | 环境要求: 9 | 10 | * 操作系统: ubuntu/debian/osx 11 | * golang版本: >= 1.4 12 | * ImageMagick版本: = 6.8.5-X 13 | 14 | 15 | 16 | # Install 17 | 18 | - - - 19 | ## Ubuntu/Debian 20 | 21 | - - - 22 | wget http://www.magickwand.org/download/releases/ImageMagick-6.8.5-10.tar.gz 23 | tar zxvf ImageMagick-6.8.5-10.tar.gz 24 | cd ImageMagick-6.8.5-10/ 25 | ./configure 26 | make & make install 27 | ldconfig /usr/local/lib 28 | - - - 29 | ## OSX 30 | 31 | - - - 32 | brew install ImageMagick 33 | 34 | - - - 35 | ## 安装 36 | - - - 37 | go get github.com/gographics/imagick/imagick 38 | go get code.google.com/p/gcfg 39 | go get github.com/garyburd/redigo/redis 40 | go get github.com/Leon2012/gimg 41 | cd $GOPATH/src/github.com/Leon2012/gimg/build/ 42 | go build -o gimg 43 | ./gimg --config=./conf/config.ini 44 | 45 | 46 | ## Demo 47 | --- 48 | [http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93](http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93) 49 | 50 | [http://182.92.189.64:8081/info?md5=a258607b53444f32208e864f44a06b93](http://182.92.189.64:8081/info?md5=a258607b53444f32208e864f44a06b93) 51 | 52 | [http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93?w=100&h=100&x=-1&y=-1](http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93?w=100&h=100&x=-1&y=-1) 53 | 54 | [http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93?r=45](http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93?r=45) 55 | 56 | [http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93?g=1](http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93?g=1) 57 | 58 | [http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93?f=png](http://182.92.189.64:8081/a258607b53444f32208e864f44a06b93?f=png) 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /build/conf/config.ini: -------------------------------------------------------------------------------- 1 | [system] 2 | isDaemon=0 3 | host=0.0.0.0 4 | port=4869 5 | 6 | #header config 7 | #返回时所带的HTTP header 8 | headers=Cache-Control:max-age=7776000 9 | 10 | #是否启用etag缓存 11 | etag=1 12 | 13 | #输出log格式 file|console 14 | logOutput = console 15 | #输出log级别 16 | logLevel = 6 17 | #输出log路径 18 | logName = /tmp/zimg.log 19 | 20 | #mage process config 21 | #禁用URL图片处理 22 | disableArgs = 0 23 | 24 | #ormat value: 'none' for original or other format names 25 | #默认保存新图的格式,字符串'none'表示以原有格式保存,或者是期望使用的格式名 26 | format = jpeg 27 | #quality value: 1~100(default: 75) 28 | #默认保存新图的质量 29 | quality = 75 30 | 31 | [cache] 32 | #cache config 33 | #是否启用memcached缓存 34 | cache = 1 35 | #缓存服务器IP 36 | memcacheHost = 127.0.0.1 37 | #缓存服务器端口 38 | memcachePort = 11211 39 | 40 | [storage] 41 | #torage config 42 | #zimg support 3 ways for storage images 43 | #value 1 is for local disk storage; 44 | #value 2 is for memcached protocol storage like beansdb; 45 | #value 3 is for redis protocol storage like SSDB. 46 | #存储后端类型,1为本地存储,2为memcached协议后端如beansdb,3为redis协议后端如SSDB 47 | mode = 3 48 | 49 | #save_new value: 0.don't save any 1.save all 2.only save types in lua script 50 | #新文件是否存储,0为不存储,1为全都存储,2为只存储lua脚本产生的新图 51 | saveNew = 1 52 | #上传图片大小限制,默认100MB 53 | maxSize = 104857600 #100*1024*1024 54 | #允许上传图片类型列表 55 | allowedTypes = jpeg,jpg,png,gif,webp 56 | 57 | #mode[1]: local disk mode 58 | #本地存储时的存储路径 59 | imgPath = "/Users/kentchen/Desktop/uploads" 60 | 61 | #mode[2]: beansdb mode 62 | #beansdb服务器IP 63 | beansdbHost = 127.0.0.1 64 | #beansdb服务器端口 65 | beansdbPort = 7900 66 | 67 | #mode[3]: ssdb mode 68 | #SSDB服务器IP 69 | ssdbHost = 127.0.0.1 70 | #SSDB服务器端口 71 | ssdbPort = 8888 -------------------------------------------------------------------------------- /build/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | zimg "github.com/Leon2012/gimg" 7 | _ "github.com/bradfitz/gomemcache/memcache" 8 | "net/http" 9 | "os" 10 | "runtime" 11 | // "os/signal" 12 | // "syscall" 13 | ) 14 | 15 | var cfgFile string 16 | var zContext *zimg.ZContext 17 | 18 | func main() { 19 | runtime.GOMAXPROCS(runtime.NumCPU()) 20 | 21 | configPtr := flag.String("config", "", "config file") 22 | flag.Usage = usage 23 | flag.Parse() 24 | 25 | if *configPtr == "" { 26 | *configPtr = "./conf/config.ini" 27 | } 28 | 29 | cfgFile = *configPtr 30 | 31 | isExist, _ := exists(cfgFile) 32 | if !isExist { 33 | fmt.Println("config file not exist!") 34 | os.Exit(-1) 35 | } 36 | 37 | zContext, err := zimg.NewContext(cfgFile) 38 | checkError(err) 39 | defer zContext.Release() 40 | 41 | //zContext.Logger.Info("load config.ini success!") 42 | 43 | addr := fmt.Sprintf("%s:%d", zContext.Config.System.Host, zContext.Config.System.Port) 44 | zContext.Logger.Info("server start run : %s", addr) 45 | 46 | zHttpd := zimg.NewHttpd(zContext) 47 | err = http.ListenAndServe(addr, zHttpd) 48 | if err != nil { 49 | zContext.Logger.Error("error : %s", err.Error()) 50 | } 51 | 52 | //signalHandle() 53 | } 54 | 55 | // exists returns whether the given file or directory exists or not 56 | func exists(path string) (bool, error) { 57 | _, err := os.Stat(path) 58 | if err == nil { 59 | return true, nil 60 | } 61 | if os.IsNotExist(err) { 62 | return false, nil 63 | } 64 | return false, err 65 | } 66 | 67 | func usage() { 68 | fmt.Fprintf(os.Stderr, "Usage:--config=/etc/config.ini \n") 69 | flag.PrintDefaults() 70 | os.Exit(-2) 71 | } 72 | 73 | func checkError(err error) { 74 | if err != nil { 75 | panic(err) 76 | os.Exit(-2) 77 | } 78 | } 79 | 80 | // func signalHandle() { 81 | // // Handle SIGINT and SIGTERM. 82 | // ch := make(chan os.Signal, 1) 83 | // over := make(chan bool, 1) 84 | // signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) 85 | 86 | // go func() { 87 | // sig := <-ch 88 | // //zContext.Logger.Info(sig) 89 | // over <- true 90 | // }() 91 | 92 | // //zContext.Logger.Info(<-over) 93 | 94 | // zContext.Logger.Info("server stop!!!") 95 | // } 96 | -------------------------------------------------------------------------------- /zcache.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bradfitz/gomemcache/memcache" 6 | ) 7 | 8 | type ZCache struct { 9 | host string 10 | port int 11 | client *memcache.Client 12 | } 13 | 14 | // func NewCache(c *memcache.Client) *ZCache { 15 | // return &ZCache{client: c} 16 | // } 17 | 18 | func NewCache(h string, p int) *ZCache { 19 | var c *memcache.Client 20 | cacheAddr := fmt.Sprintf("%s:%d", h, p) 21 | c = memcache.New(cacheAddr) 22 | return &ZCache{client: c, host: h, port: p} 23 | } 24 | 25 | func (z *ZCache) ReTry() { 26 | cacheAddr := fmt.Sprintf("%s:%d", z.host, z.port) 27 | c := memcache.New(cacheAddr) 28 | z.client = c 29 | } 30 | 31 | func (z *ZCache) FindCache(key string) (string, error) { 32 | it, err := z.client.Get(key) 33 | if err != nil { 34 | return "", err 35 | } 36 | return string(it.Value), nil 37 | } 38 | 39 | func (z *ZCache) SetCache(k string, v string) error { 40 | it := &memcache.Item{Key: k, Value: []byte(v)} 41 | return z.client.Set(it) 42 | } 43 | 44 | func (z *ZCache) Exist(key string) bool { 45 | _, err := z.FindCache(key) 46 | if err != nil { 47 | return false 48 | } 49 | return true 50 | } 51 | 52 | func (z *ZCache) FindCacheBin(key string) ([]byte, error) { 53 | it, err := z.client.Get(key) 54 | if err != nil { 55 | return nil, err 56 | } else { 57 | return it.Value, nil 58 | } 59 | 60 | } 61 | 62 | func (z *ZCache) SetCacheBin(k string, v []byte) error { 63 | it := &memcache.Item{Key: k, Value: v} 64 | return z.client.Set(it) 65 | } 66 | 67 | func (z *ZCache) DelCache(key string) error { 68 | return z.client.Delete(key) 69 | } 70 | -------------------------------------------------------------------------------- /zcache_test.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGet(t *testing.T) { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /zcommon.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | _ "encoding/json" 5 | "fmt" 6 | "github.com/gographics/imagick/imagick" 7 | ) 8 | 9 | const ( 10 | PROJECT_VERSION = "1.0.1" 11 | MAX_LINE = 1024 12 | CACHE_KEY_SIZE = 128 13 | RETRY_TIME_WAIT = 1000 14 | CACHE_MAX_SIZE = 1048576 //1024*1024 15 | PATH_MAX_SIZE = 512 16 | ) 17 | 18 | type ZRequest struct { 19 | Md5 string 20 | ImageType string 21 | Width int 22 | Height int 23 | Proportion int 24 | Gary int 25 | X int 26 | Y int 27 | Rotate int 28 | Format string 29 | Save int 30 | Quality int 31 | } 32 | 33 | type ZImageInfo struct { 34 | Size int `json:"size"` 35 | Width int `json:"width"` 36 | Height int `json:"height"` 37 | Quality int `json:"quality"` 38 | Format string `json:"format"` 39 | } 40 | 41 | type ZContext struct { 42 | Config AppConfig 43 | Logger *ZLogger 44 | Cache *ZCache 45 | Image *ZImage 46 | Redis *ZRedisDB 47 | } 48 | 49 | func NewContext(cfgFile string) (*ZContext, error) { 50 | imagick.Initialize() 51 | 52 | cfg, err := LoadConfig(cfgFile) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | var log *ZLogger 58 | logOutput := cfg.System.LogOutput 59 | if logOutput == "file" { 60 | log, err = NewFileLogger("gimg", 0, cfg.System.LogName) 61 | } else if logOutput == "console" { 62 | log, err = NewLogger("gimg", 0) 63 | } else { 64 | return nil, fmt.Errorf("init logger faile.") 65 | } 66 | 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | var cache *ZCache 72 | if cfg.Cache.Cache == 1 { 73 | // var client *memcache.Client 74 | // cacheAddr := fmt.Sprintf("%s:%d", cfg.Cache.MemcacheHost, cfg.Cache.MemcachePort) 75 | // client = memcache.New(cacheAddr) 76 | cache = NewCache(cfg.Cache.MemcacheHost, cfg.Cache.MemcachePort) 77 | } else { 78 | cache = nil 79 | } 80 | 81 | img := NewImage() 82 | 83 | redisDB, err := NewRedisDB(cfg.Storage.SsdbHost, cfg.Storage.SsdbPort) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | return &ZContext{Config: cfg, 89 | Logger: log, 90 | Cache: cache, 91 | Image: img, 92 | Redis: redisDB, 93 | }, nil 94 | } 95 | 96 | func (z *ZContext) Release() { 97 | z.Logger.Close() 98 | imagick.Terminate() 99 | } 100 | -------------------------------------------------------------------------------- /zconfig.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "code.google.com/p/gcfg" 5 | _ "errors" 6 | _ "strings" 7 | ) 8 | 9 | type AppConfig struct { 10 | System struct { 11 | IsDaemon int 12 | Host string 13 | Port int 14 | Headers string 15 | Etag int 16 | LogOutput string 17 | LogLevel int 18 | LogName string 19 | DisableArgs int 20 | Format string 21 | Quality int 22 | } 23 | 24 | Cache struct { 25 | Cache int 26 | MemcacheHost string 27 | MemcachePort int 28 | } 29 | 30 | Storage struct { 31 | Mode int 32 | SaveNew int 33 | MaxSize int 34 | AllowedTypes string 35 | ImgPath string 36 | 37 | BeansdbHost string 38 | BeansdbPort int 39 | 40 | SsdbHost string 41 | SsdbPort int 42 | } 43 | } 44 | 45 | // func (a *AppConfig) Types() []string { 46 | // return strings.Split(a.Storage.AllowedTypes, ",") 47 | // } 48 | 49 | func LoadConfig(cfgFile string) (AppConfig, error) { 50 | var err error 51 | var cfg AppConfig 52 | 53 | // isExists := util.IsExist(cfgFile) 54 | // if !isExists { 55 | // return cfg, errors.New("Config file is not exists!") 56 | // } 57 | 58 | err = gcfg.ReadFileInto(&cfg, cfgFile) 59 | if err != nil { 60 | //panic(err) 61 | return cfg, err 62 | } 63 | 64 | return cfg, nil 65 | } 66 | -------------------------------------------------------------------------------- /zconfig_test.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestLoadCfg(t *testing.T) { 8 | cfgFile := "/Users/kentchen/golang/src/github.com/xingskycn/go-zimg/config.ini" 9 | 10 | cfg, err := LoadConfig(cfgFile) 11 | if err != nil { 12 | t.Log(err.Error()) 13 | t.Fail() 14 | } else { 15 | t.Log(cfg.System.Host) 16 | //t.Log(cfg.System.Types()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /zhttpd.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | _ "mime/multipart" 9 | "net/http" 10 | "strconv" 11 | "strings" 12 | //"time" 13 | "io" 14 | ) 15 | 16 | type ZHttpd struct { 17 | context *ZContext 18 | storage ZStorage 19 | writer http.ResponseWriter 20 | request *http.Request 21 | contentTypes map[string]string 22 | } 23 | 24 | func NewHttpd(c *ZContext) *ZHttpd { 25 | return &ZHttpd{context: c, 26 | storage: genStorageHandler(c), 27 | contentTypes: genContentTypes()} 28 | } 29 | 30 | func genStorageHandler(c *ZContext) ZStorage { 31 | var s ZStorage = nil 32 | var i int = c.Config.Storage.Mode 33 | 34 | switch i { 35 | case 1: 36 | s = NewFileStorage(c) 37 | case 2: 38 | break 39 | case 3: 40 | s = NewSSDBStorage(c) 41 | break 42 | } 43 | return s 44 | } 45 | 46 | func genContentTypes() map[string]string { 47 | types := make(map[string]string) 48 | types["jpg"] = "image/jpeg" 49 | types["jpeg"] = "image/jpeg" 50 | types["png"] = "image/png" 51 | types["gif"] = "image/gif" 52 | types["webp"] = "image/webp" 53 | 54 | return types 55 | } 56 | 57 | func (z *ZHttpd) ServeHTTP(w http.ResponseWriter, r *http.Request) { 58 | z.writer = w 59 | z.request = r 60 | path := r.URL.Path 61 | method := r.Method 62 | 63 | if "GET" == method { 64 | if path == "/" { 65 | z.doDefault() 66 | } else if path == "/info" { 67 | z.doInfo() 68 | } else { 69 | z.context.Logger.Info("path:" + path) 70 | md5Sum := path[1:len(path)] 71 | z.context.Logger.Info("md5Sum:" + md5Sum) 72 | 73 | if is_md5(md5Sum) { 74 | z.doGet(md5Sum) 75 | } else { 76 | http.NotFound(w, r) 77 | } 78 | } 79 | 80 | } else if "POST" == method { 81 | if path == "/upload" { 82 | z.doUpload() 83 | } else { 84 | http.NotFound(w, r) 85 | } 86 | 87 | } else { 88 | http.NotFound(w, r) 89 | } 90 | 91 | return 92 | } 93 | 94 | func (z *ZHttpd) doDefault() { 95 | z.context.Logger.Info("call doDefault function........") 96 | 97 | html := ` 98 | 99 |
100 | 101 | 102 | 103 | 110 | 111 | ` 112 | fmt.Fprint(z.writer, html) 113 | 114 | } 115 | 116 | func (z *ZHttpd) doInfo() { 117 | z.context.Logger.Info("call doInfo function........") 118 | if err := z.request.ParseForm(); err != nil { 119 | z.context.Logger.Error(err.Error()) 120 | z.doError(err, http.StatusForbidden) 121 | return 122 | } 123 | 124 | md5Sum := z.request.Form.Get("md5") 125 | z.context.Logger.Info("search md5 : %s", md5Sum) 126 | 127 | imgInfo, err := z.storage.InfoImage(md5Sum) 128 | if err != nil { 129 | z.context.Logger.Error(err.Error()) 130 | z.doError(err, http.StatusForbidden) 131 | return 132 | } 133 | 134 | json, _ := json.Marshal(imgInfo) 135 | fmt.Fprint(z.writer, string(json)) 136 | 137 | } 138 | 139 | func (z *ZHttpd) doUpload() { 140 | z.context.Logger.Info("call doUpload function........") 141 | 142 | if err := z.request.ParseMultipartForm(CACHE_MAX_SIZE); err != nil { 143 | z.context.Logger.Error(err.Error()) 144 | z.doError(err, http.StatusForbidden) 145 | return 146 | } 147 | 148 | file, _, err := z.request.FormFile("upload_file") 149 | if err != nil { 150 | z.doError(err, 500) 151 | return 152 | } 153 | defer file.Close() 154 | 155 | data, err := ioutil.ReadAll(file) 156 | if err != nil { 157 | z.doError(err, 500) 158 | return 159 | } 160 | 161 | md5Sum, err := z.storage.SaveImage(data) 162 | if err != nil { 163 | z.doError(err, 500) 164 | return 165 | } 166 | 167 | fmt.Fprint(z.writer, fmt.Sprintf("upload success! md5 : %s", md5Sum)) 168 | 169 | } 170 | 171 | func (z *ZHttpd) doGet(md5Sum string) { 172 | z.context.Logger.Info("call doGet function........") 173 | if err := z.request.ParseForm(); err != nil { 174 | z.context.Logger.Error(err.Error()) 175 | z.doError(err, http.StatusForbidden) 176 | return 177 | } 178 | 179 | imgInfo, err := z.storage.InfoImage(md5Sum) 180 | if err != nil { 181 | z.context.Logger.Error(err.Error()) 182 | z.doError(err, http.StatusForbidden) 183 | return 184 | } 185 | 186 | var w, h, p, g, x, y, r, s, q int = 0, 0, 0, 0, 0, 0, 0, 0, 0 187 | var i, f string 188 | 189 | width := z.request.Form.Get("w") 190 | height := z.request.Form.Get("h") 191 | gary := z.request.Form.Get("g") 192 | xx := z.request.Form.Get("x") 193 | yy := z.request.Form.Get("y") 194 | rotate := z.request.Form.Get("r") 195 | 196 | w = str2Int(width) 197 | if w >= imgInfo.Width || w <= 0 { 198 | w = imgInfo.Width 199 | } 200 | 201 | h = str2Int(height) 202 | if h >= imgInfo.Height || h <= 0 { 203 | h = imgInfo.Height 204 | } 205 | 206 | g = str2Int(gary) 207 | if g != 1 { 208 | g = 0 209 | } 210 | 211 | x = str2Int(xx) 212 | if x < 0 { 213 | x = -1 214 | } 215 | 216 | // else if x > imgInfo.Width { 217 | // x = imgInfo.Width 218 | // } 219 | 220 | y = str2Int(yy) 221 | if y < 0 { 222 | y = -1 223 | } 224 | // else if y > imgInfo.Height { 225 | // y = imgInfo.Height 226 | // } 227 | 228 | r = str2Int(rotate) 229 | 230 | quality := z.request.Form.Get("q") 231 | q = str2Int(quality) 232 | if q <= 0 { 233 | //q = imgInfo.Quality 234 | q = z.context.Config.System.Quality //加载默认保存图片质量 235 | } else if q > 100 { 236 | q = 100 237 | } 238 | 239 | save := strings.Trim(z.request.Form.Get("s"), " ") 240 | if len(save) == 0 { 241 | s = z.context.Config.Storage.SaveNew 242 | } else { 243 | s = str2Int(save) 244 | if s != 1 { 245 | s = 0 246 | } 247 | } 248 | 249 | format := strings.Trim(z.request.Form.Get("f"), " ") 250 | if len(format) == 0 { 251 | //f = "none" 252 | //f = imgInfo.Format 253 | f = z.context.Config.System.Format //加载默认保存图片格式 254 | } else { 255 | format = strings.ToLower(format) 256 | formats := strings.Split(z.context.Config.Storage.AllowedTypes, ",") 257 | isExist := false 258 | for _, v := range formats { 259 | if format == v { 260 | isExist = true 261 | } 262 | } 263 | if !isExist { 264 | f = z.context.Config.System.Format 265 | } else { 266 | f = format 267 | } 268 | } 269 | // if f == strings.ToLower(imgInfo.Format) { 270 | // f = "none" 271 | // } 272 | 273 | request := &ZRequest{ 274 | Md5: md5Sum, 275 | Width: w, 276 | Height: h, 277 | Gary: g, 278 | X: x, 279 | Y: y, 280 | Rotate: r, 281 | Quality: q, 282 | Proportion: p, 283 | Save: s, 284 | Format: f, 285 | ImageType: i, 286 | } 287 | 288 | z.context.Logger.Debug("request params: md5 : %s, width: %d, height: %d, gary: %d, x: %d, y: %d, rotate: %d, quality: %d, proportion: %d, save: %d, format: %s, imageType: %s", request.Md5, request.Width, request.Height, request.Gary, request.X, request.Y, request.Rotate, request.Quality, request.Proportion, request.Save, request.Format, request.ImageType) 289 | 290 | data, err := z.storage.GetImage(request) 291 | 292 | if err != nil { 293 | z.doError(err, 500) 294 | return 295 | } 296 | 297 | //add etag support 298 | if z.context.Config.System.Etag == 1 { 299 | newMd5Sum := gen_md5_str(data) 300 | 301 | ifNoneMatch := z.request.Header.Get("If-None-Match") 302 | if len(ifNoneMatch) == 0 { 303 | z.writer.Header().Set("Etag", newMd5Sum) 304 | } else { 305 | if ifNoneMatch == newMd5Sum { 306 | z.context.Logger.Debug("Etag Matched Return 304 EVHTP_RES_NOTMOD.") 307 | z.doError(fmt.Errorf("Not Modified"), http.StatusNotModified) 308 | return 309 | } else { 310 | z.writer.Header().Set("Etag", newMd5Sum) 311 | } 312 | } 313 | 314 | } 315 | 316 | headers := z.context.Config.System.Headers 317 | if len(headers) > 0 { 318 | arr := strings.Split(headers, ",") 319 | for i := 0; i < len(arr); i++ { 320 | header := arr[i] 321 | kvs := strings.Split(header, ":") 322 | z.writer.Header().Set(kvs[0], kvs[1]) 323 | } 324 | } 325 | 326 | imageFormat := strings.ToLower(f) 327 | if contentType, ok := z.contentTypes[imageFormat]; ok { 328 | z.writer.Header().Set("Content-Type", contentType) 329 | z.writer.Header().Set("Accept-Ranges", "bytes") 330 | if z.writer.Header().Get("Content-Encoding") == "" { 331 | z.writer.Header().Set("Content-Length", strconv.Itoa(len(data))) 332 | } 333 | io.Copy(z.writer, bytes.NewReader(data)) 334 | 335 | } else { 336 | err = fmt.Errorf("can not found content type!!!") 337 | z.doError(err, http.StatusForbidden) 338 | return 339 | } 340 | } 341 | 342 | func (z *ZHttpd) doError(err error, statusCode int) { 343 | http.Error(z.writer, err.Error(), statusCode) 344 | return 345 | } 346 | 347 | func str2Int(str string) int { 348 | str = strings.Trim(str, " ") 349 | if len(str) > 0 { 350 | i, err := strconv.Atoi(str) 351 | if err != nil { 352 | return 0 353 | } else { 354 | return i 355 | } 356 | } else { 357 | return 0 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /zimage.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | _ "fmt" 5 | "github.com/gographics/imagick/imagick" 6 | ) 7 | 8 | type ZImage struct { 9 | MW *imagick.MagickWand 10 | } 11 | 12 | func NewImage() *ZImage { 13 | return &ZImage{MW: imagick.NewMagickWand()} 14 | } 15 | 16 | func (z *ZImage) Destroy() { 17 | z.MW.Destroy() 18 | } 19 | -------------------------------------------------------------------------------- /zlog.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "log" 8 | "os" 9 | "runtime" 10 | "sync/atomic" 11 | "time" 12 | ) 13 | 14 | var ( 15 | colors map[string]string 16 | logNo uint64 17 | ) 18 | 19 | const ( 20 | Black = (iota + 30) 21 | Red 22 | Green 23 | Yellow 24 | Blue 25 | Magenta 26 | Cyan 27 | White 28 | ) 29 | 30 | type Worker struct { 31 | Minion *log.Logger 32 | Color int 33 | LogFile *os.File 34 | } 35 | 36 | type Info struct { 37 | Id uint64 38 | Time string 39 | Module string 40 | Level string 41 | Message string 42 | format string 43 | } 44 | 45 | type ZLogger struct { 46 | Module string 47 | Worker *Worker 48 | } 49 | 50 | func (i *Info) Output() string { 51 | msg := fmt.Sprintf(i.format, i.Id, i.Time, i.Level, i.Message) 52 | return msg 53 | } 54 | 55 | func NewWorker(prefix string, flag int, color int, out io.Writer) *Worker { 56 | return &Worker{Minion: log.New(out, prefix, flag), Color: color, LogFile: nil} 57 | } 58 | 59 | func NewConsoleWorker(prefix string, flag int, color int) *Worker { 60 | return NewWorker(prefix, flag, color, os.Stdout) 61 | } 62 | 63 | func NewFileWorker(prefix string, flag int, color int, logFile *os.File) *Worker { 64 | return &Worker{Minion: log.New(logFile, prefix, flag), Color: color, LogFile: logFile} 65 | } 66 | 67 | func (w *Worker) Log(level string, calldepth int, info *Info) error { 68 | if w.Color != 0 { 69 | buf := &bytes.Buffer{} 70 | buf.Write([]byte(colors[level])) 71 | buf.Write([]byte(info.Output())) 72 | buf.Write([]byte("\033[0m")) 73 | return w.Minion.Output(calldepth+1, buf.String()) 74 | } else { 75 | return w.Minion.Output(calldepth+1, info.Output()) 76 | } 77 | } 78 | 79 | func colorString(color int) string { 80 | return fmt.Sprintf("\033[%dm", int(color)) 81 | } 82 | 83 | func initColors() { 84 | colors = map[string]string{ 85 | "CRITICAL": colorString(Magenta), 86 | "ERROR": colorString(Red), 87 | "WARNING": colorString(Yellow), 88 | "NOTICE": colorString(Green), 89 | "DEBUG": colorString(Cyan), 90 | "INFO": colorString(White), 91 | } 92 | } 93 | 94 | func NewLogger(module string, color int) (*ZLogger, error) { 95 | initColors() 96 | newWorker := NewConsoleWorker("", 0, color) 97 | return &ZLogger{Module: module, Worker: newWorker}, nil 98 | } 99 | 100 | func NewFileLogger(module string, color int, logFile string) (*ZLogger, error) { 101 | fileHandler, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777) 102 | if err != nil { 103 | return nil, err 104 | } else { 105 | initColors() 106 | newWorker := NewFileWorker("", 0, color, fileHandler) 107 | return &ZLogger{Module: module, Worker: newWorker}, nil 108 | } 109 | } 110 | 111 | func NewDailyLogger(module string, color int, logPath string) (*ZLogger, error) { 112 | 113 | var logFile string 114 | const layout = "2006-01-02" 115 | now := time.Now() 116 | fileName := now.Format(layout) 117 | if len(logPath) == 0 { 118 | logFile = "./" + fileName + ".log" 119 | } else { 120 | logFile = logPath + "/" + fileName + ".log" 121 | } 122 | fileHandler, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0777) 123 | if err != nil { 124 | return nil, err 125 | } else { 126 | initColors() 127 | newWorker := NewFileWorker("", 0, color, fileHandler) 128 | return &ZLogger{Module: module, Worker: newWorker}, nil 129 | } 130 | } 131 | 132 | func (l *ZLogger) Log(lvl string, message string) { 133 | var formatString string = "#%d %s ▶ %.3s %s" 134 | info := &Info{ 135 | Id: atomic.AddUint64(&logNo, 1), 136 | Time: time.Now().Format("2006-01-02 15:04:05"), 137 | Module: l.Module, 138 | Level: lvl, 139 | Message: message, 140 | format: formatString, 141 | } 142 | l.Worker.Log(lvl, 2, info) 143 | } 144 | 145 | func (l *ZLogger) Fatal(format string, v ...interface{}) { 146 | message := fmt.Sprintf(format, v...) 147 | l.Log("CRITICAL", message) 148 | os.Exit(1) 149 | } 150 | 151 | func (l *ZLogger) Panic(format string, v ...interface{}) { 152 | message := fmt.Sprintf(format, v...) 153 | l.Log("CRITICAL", message) 154 | panic(message) 155 | } 156 | 157 | func (l *ZLogger) Critical(format string, v ...interface{}) { 158 | message := fmt.Sprintf(format, v...) 159 | l.Log("CRITICAL", message) 160 | } 161 | 162 | func (l *ZLogger) Error(format string, v ...interface{}) { 163 | message := fmt.Sprintf(format, v...) 164 | l.Log("ERROR", message) 165 | } 166 | 167 | func (l *ZLogger) Warning(format string, v ...interface{}) { 168 | message := fmt.Sprintf(format, v...) 169 | l.Log("WARNING", message) 170 | } 171 | 172 | func (l *ZLogger) Notice(format string, v ...interface{}) { 173 | message := fmt.Sprintf(format, v...) 174 | l.Log("NOTICE", message) 175 | } 176 | 177 | func (l *ZLogger) Info(format string, v ...interface{}) { 178 | message := fmt.Sprintf(format, v...) 179 | l.Log("INFO", message) 180 | } 181 | 182 | func (l *ZLogger) Debug(format string, v ...interface{}) { 183 | message := fmt.Sprintf(format, v...) 184 | l.Log("DEBUG", message) 185 | } 186 | 187 | func (l *ZLogger) Strack(format string, v ...interface{}) { 188 | message := fmt.Sprintf(format, v...) 189 | message += "\n" 190 | buf := make([]byte, 1024*1024) 191 | n := runtime.Stack(buf, true) 192 | message += string(buf[:n]) 193 | message += "\n" 194 | l.Log("STRACK", message) 195 | } 196 | 197 | func (l *ZLogger) Close() { 198 | if l.Worker.LogFile != nil { 199 | l.Worker.LogFile.Close() 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /zlog_test.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | _ "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestLogger(t *testing.T) { 9 | logger, err := NewLogger("test", 0) 10 | if err != nil { 11 | t.Fatal() 12 | } else { 13 | //message := fmt.Sprintf("test %s, %d", "test", 200) 14 | //t.Log(message) 15 | 16 | logger.Info("test %s, %d", "test", 200) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /zredisdb.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/garyburd/redigo/redis" 7 | "time" 8 | ) 9 | 10 | type ZRedisDB struct { 11 | server string 12 | port int 13 | pool *redis.Pool 14 | isConnect bool 15 | } 16 | 17 | func NewRedisDB(s string, p int) (*ZRedisDB, error) { 18 | addr := fmt.Sprintf("%s:%d", s, p) 19 | pool := &redis.Pool{ 20 | MaxIdle: 3, 21 | IdleTimeout: 240 * time.Second, 22 | Dial: func() (redis.Conn, error) { 23 | c, err := redis.Dial("tcp", addr) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return c, err 29 | }, 30 | TestOnBorrow: func(c redis.Conn, t time.Time) error { 31 | _, err := c.Do("PING") 32 | return err 33 | }, 34 | } 35 | 36 | return &ZRedisDB{ 37 | server: s, 38 | port: p, 39 | pool: pool, 40 | isConnect: true, 41 | }, nil 42 | } 43 | 44 | func (z *ZRedisDB) getConnect() (redis.Conn, error) { 45 | if z.isConnect { 46 | conn := z.pool.Get() 47 | return conn, nil 48 | } else { 49 | return nil, errors.New("Can not connect db") 50 | } 51 | } 52 | 53 | func (z *ZRedisDB) Exist(key string) bool { 54 | conn, err := z.getConnect() 55 | if err != nil { 56 | return false 57 | } 58 | defer conn.Close() 59 | 60 | isExists, _ := redis.Bool(conn.Do("EXISTS", key)) 61 | return isExists 62 | } 63 | 64 | func (z *ZRedisDB) Get(key string) ([]byte, error) { 65 | conn, err := z.getConnect() 66 | if err != nil { 67 | return nil, errors.New("Can not connect db!") 68 | } 69 | defer conn.Close() 70 | 71 | data, err := redis.Bytes(conn.Do("GET", key)) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return data, nil 76 | } 77 | 78 | func (z *ZRedisDB) Do(commandName string, args ...interface{}) (interface{}, error) { 79 | conn, err := z.getConnect() 80 | 81 | if err != nil { 82 | return nil, errors.New("Can not connect db!") 83 | } 84 | defer conn.Close() 85 | 86 | return conn.Do(commandName, args...) 87 | } 88 | 89 | func (z *ZRedisDB) Send(commandName string, args ...interface{}) error { 90 | conn, err := z.getConnect() 91 | 92 | if err != nil { 93 | return errors.New("Can not connect db!") 94 | } 95 | defer conn.Close() 96 | 97 | return conn.Send(commandName, args...) 98 | } 99 | 100 | func (z *ZRedisDB) Flush() { 101 | if z.isConnect { 102 | conn := z.pool.Get() 103 | defer conn.Close() 104 | conn.Flush() 105 | } 106 | } 107 | 108 | func (z *ZRedisDB) Close() { 109 | if z.isConnect { 110 | z.pool.Close() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /zredisdb_test.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestConnect(t *testing.T) { 8 | s := "127.0.0.1" 9 | p := 8888 10 | 11 | db, err := NewRedisDB(s, p) 12 | if err != nil { 13 | t.Fail() 14 | } else { 15 | t.Log("connect redis db") 16 | 17 | if db.Exist("name") { 18 | t.Log("name is exists!!!") 19 | } else { 20 | t.Log("name isnot exists!!!") 21 | } 22 | 23 | } 24 | } 25 | 26 | func TestGetRedis(t *testing.T) { 27 | s := "127.0.0.1" 28 | p := 8888 29 | key := "e351d9d8c9f409ec0ff4d518d6f7551f" 30 | 31 | db, err := NewRedisDB(s, p) 32 | if err != nil { 33 | t.Fail() 34 | } else { 35 | t.Log("connect redis db") 36 | data, err := db.Get(key) 37 | if err != nil { 38 | t.Fail() 39 | } else { 40 | t.Log(data) 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /zscale.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gographics/imagick/imagick" 6 | "math" 7 | ) 8 | 9 | func crop(mw *imagick.MagickWand, x, y int, cols, rows uint) error { 10 | var result error 11 | result = nil 12 | 13 | imCols := mw.GetImageWidth() 14 | imRows := mw.GetImageHeight() 15 | 16 | if x < 0 { 17 | x = 0 18 | } 19 | if y < 0 { 20 | y = 0 21 | } 22 | 23 | if uint(x) >= imCols || uint(y) >= imRows { 24 | result = fmt.Errorf("x, y more than image cols, rows") 25 | return result 26 | } 27 | 28 | if cols == 0 || imCols < uint(x)+cols { 29 | cols = imCols - uint(x) 30 | } 31 | 32 | if rows == 0 || imRows < uint(y)+rows { 33 | rows = imRows - uint(y) 34 | } 35 | 36 | fmt.Print(fmt.Sprintf("wi_crop(im, %d, %d, %d, %d)\n", x, y, cols, rows)) 37 | 38 | result = mw.CropImage(cols, rows, x, y) 39 | 40 | return result 41 | } 42 | 43 | func proportion(mw *imagick.MagickWand, proportion int, cols uint, rows uint) error { 44 | var result error 45 | result = nil 46 | 47 | imCols := mw.GetImageWidth() 48 | imRows := mw.GetImageHeight() 49 | 50 | if proportion == 0 { 51 | fmt.Sprintf("p=0, wi_scale(im, %d, %d)\n", cols, rows) 52 | result = mw.ResizeImage(cols, rows, imagick.FILTER_UNDEFINED, 1.0) 53 | 54 | } else if proportion == 1 { 55 | 56 | if cols == 0 || rows == 0 { 57 | if cols > 0 { 58 | rows = uint(round(float64((cols / imCols) * imRows))) 59 | } else { 60 | cols = uint(round(float64((rows / imRows) * imCols))) 61 | } 62 | fmt.Sprintf("p=1, wi_scale(im, %d, %d)\n", cols, rows) 63 | result = mw.ResizeImage(cols, rows, imagick.FILTER_UNDEFINED, 1.0) 64 | } else { 65 | var x, y, sCols, sRows uint 66 | x, y = 0, 0 67 | 68 | colsRate := cols / imCols 69 | rowsRate := rows / imRows 70 | 71 | if colsRate > rowsRate { 72 | sCols = cols 73 | sRows = uint(round(float64(colsRate * imRows))) 74 | y = uint(math.Floor(float64((sRows - rows) / 2.0))) 75 | } else { 76 | sCols = uint(round(float64(rowsRate * imCols))) 77 | sRows = rows 78 | x = uint(math.Floor(float64((sCols - cols) / 2.0))) 79 | } 80 | 81 | fmt.Sprintf("p=2, wi_scale(im, %d, %d)\n", sCols, sRows) 82 | result = mw.ResizeImage(sCols, sRows, imagick.FILTER_UNDEFINED, 1.0) 83 | 84 | fmt.Sprintf("p=2, wi_crop(im, %d, %d, %d, %d)\n", x, y, cols, rows) 85 | result = mw.CropImage(cols, rows, int(x), int(y)) 86 | } 87 | 88 | } else if proportion == 2 { 89 | x := int(math.Floor(float64((imCols - cols) / 2.0))) 90 | y := int(math.Floor(float64((imRows - rows) / 2.0))) 91 | fmt.Sprintf("p=3, wi_crop(im, %d, %d, %d, %d)\n", x, y, cols, rows) 92 | result = mw.CropImage(cols, rows, x, y) 93 | 94 | } else if proportion == 3 { 95 | if cols == 0 || rows == 0 { 96 | var rate uint 97 | if cols > 0 { 98 | rate = cols 99 | } else { 100 | rate = rows 101 | } 102 | rows = uint(round(float64(imRows * rate / 100))) 103 | cols = uint(round(float64(imCols * rate / 100))) 104 | fmt.Sprintf("p=3, wi_scale(im, %d, %d)\n", cols, rows) 105 | result = mw.ResizeImage(cols, rows, imagick.FILTER_UNDEFINED, 1.0) 106 | } else { 107 | rows = uint(round(float64(imRows * rows / 100))) 108 | cols = uint(round(float64(imCols * cols / 100))) 109 | fmt.Sprintf("p=3, wi_scale(im, %d, %d)\n", cols, rows) 110 | result = mw.ResizeImage(cols, rows, imagick.FILTER_UNDEFINED, 1.0) 111 | } 112 | 113 | } else if proportion == 4 { 114 | var rate float64 115 | rate = 1.0 116 | if cols == 0 || rows == 0 { 117 | if cols > 0 { 118 | rate = float64(cols / imCols) 119 | } else { 120 | rate = float64(rows / imRows) 121 | } 122 | } else { 123 | rateCol := cols / imCols 124 | rateRow := rows / imRows 125 | if rateCol < rateRow { 126 | rate = float64(rateCol) 127 | } else { 128 | rate = float64(rateRow) 129 | } 130 | } 131 | 132 | cols = uint(round(float64(float64(imCols) * rate))) 133 | rows = uint(round(float64(float64(imRows) * rate))) 134 | fmt.Sprintf("p=4, wi_scale(im, %d, %d)\n", cols, rows) 135 | result = mw.ResizeImage(cols, rows, imagick.FILTER_UNDEFINED, 1.0) 136 | } 137 | 138 | return result 139 | 140 | } 141 | 142 | func convert(mw *imagick.MagickWand, request *ZRequest) error { 143 | 144 | fmt.Println("call convert function......") 145 | 146 | var result error 147 | result = nil 148 | mw.ResetIterator() 149 | mw.SetImageOrientation(imagick.ORIENTATION_TOP_LEFT) 150 | 151 | x := request.X 152 | y := request.Y 153 | cols := uint(request.Width) 154 | rows := uint(request.Height) 155 | 156 | fmt.Sprintf("image cols %d, rows %d \n", cols, rows) 157 | 158 | if !(cols == 0 && rows == 0) { 159 | fmt.Println("call crop&scal function......") 160 | 161 | /* crop and scale */ 162 | if x == -1 && y == -1 { 163 | fmt.Println("call crop&scal function......") 164 | 165 | fmt.Print(fmt.Sprintf("proportion(im, %d, %d, %d) \n", request.Proportion, cols, rows)) 166 | result = proportion(mw, request.Proportion, cols, rows) 167 | if result != nil { 168 | return result 169 | } 170 | } else { 171 | 172 | fmt.Print(fmt.Sprintf("crop(im, %d, %d, %d, %d) \n", x, y, cols, rows)) 173 | 174 | result = crop(mw, x, y, cols, rows) 175 | if result != nil { 176 | return result 177 | } 178 | } 179 | } 180 | 181 | /* rotate image */ 182 | if request.Rotate != 0 { 183 | fmt.Print(fmt.Sprintf("wi_rotate(im, %d) \n", request.Rotate)) 184 | 185 | background := imagick.NewPixelWand() 186 | if background == nil { 187 | result = fmt.Errorf("init new pixelwand faile.") 188 | return result 189 | } 190 | defer background.Destroy() 191 | isOk := background.SetColor("#ffffff") 192 | if !isOk { 193 | result = fmt.Errorf("set background color faile.") 194 | return result 195 | } 196 | 197 | result = mw.RotateImage(background, float64(request.Rotate)) 198 | if result != nil { 199 | return result 200 | } 201 | } 202 | 203 | /* set gray */ 204 | if request.Gary == 1 { 205 | fmt.Print(fmt.Sprintf("wi_gray(im) \n")) 206 | result = mw.SetImageType(imagick.IMAGE_TYPE_GRAYSCALE) 207 | if result != nil { 208 | return result 209 | } 210 | } 211 | 212 | /* set quality */ 213 | fmt.Print(fmt.Sprintf("wi_set_quality(im, %d) \n", request.Quality)) 214 | result = mw.SetImageCompressionQuality(uint(request.Quality)) 215 | if result != nil { 216 | return result 217 | } 218 | 219 | /* set format */ 220 | if "none" != request.Format { 221 | fmt.Print(fmt.Sprintf("wi_set_format(im, %s) \n", request.Format)) 222 | result = mw.SetImageFormat(request.Format) 223 | if result != nil { 224 | return result 225 | } 226 | } 227 | 228 | fmt.Print(fmt.Sprintf("convert(im, req) %s \n", result)) 229 | 230 | return result 231 | } 232 | -------------------------------------------------------------------------------- /zstorage.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | _ "fmt" 5 | ) 6 | 7 | type ZStorage interface { 8 | SaveImage(data []byte) (string, error) 9 | //NewImage(saveName string, data []byte) error 10 | GetImage(request *ZRequest) ([]byte, error) 11 | InfoImage(md5 string) (*ZImageInfo, error) 12 | } 13 | -------------------------------------------------------------------------------- /zstorage_base.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import () 4 | 5 | type ZBaseStorage struct { 6 | context *ZContext 7 | } 8 | -------------------------------------------------------------------------------- /zstorage_file.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gographics/imagick/imagick" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | type ZFileStorage struct { 11 | path string 12 | sync.RWMutex 13 | *ZBaseStorage 14 | } 15 | 16 | func NewFileStorage(c *ZContext) *ZFileStorage { 17 | imgPath := c.Config.Storage.ImgPath 18 | 19 | z := new(ZFileStorage) 20 | z.path = imgPath 21 | 22 | z.ZBaseStorage = new(ZBaseStorage) 23 | z.ZBaseStorage.context = c 24 | return z 25 | } 26 | 27 | func (z *ZFileStorage) SaveImage(data []byte) (string, error) { 28 | var result error 29 | result = nil 30 | 31 | md5Sum := gen_md5_str(data) 32 | var savePath string 33 | var saveName string 34 | 35 | lvl1 := str_hash(md5Sum) 36 | lvl2 := str_hash(string(md5Sum[3:])) 37 | 38 | savePath = fmt.Sprintf("%s/%d/%d/%s", z.path, lvl1, lvl2, md5Sum) 39 | z.context.Logger.Debug("save path: ", savePath) 40 | 41 | if is_dir(savePath) { 42 | //z.context.Logger.Info("Check File Exist. Needn't Save.") 43 | result = fmt.Errorf("Check File Exist. Needn't Save.") 44 | goto cache 45 | } 46 | 47 | if !mk_dir(savePath) { 48 | result = fmt.Errorf("save path[%s] Create Failed!", savePath) 49 | goto done 50 | } 51 | 52 | z.context.Logger.Debug("save path[%s] Create Finish.\n", savePath) 53 | saveName = fmt.Sprintf("%s/0*0", savePath) 54 | z.context.Logger.Debug("save name-->: %s", saveName) 55 | 56 | result = z.NewImage(saveName, data) 57 | goto done 58 | 59 | cache: 60 | if len(data) < CACHE_MAX_SIZE { 61 | z.context.Cache.SetCacheBin(md5Sum, data) 62 | z.context.Logger.Info("save " + md5Sum + " into cache") 63 | } 64 | //result = nil 65 | return md5Sum, result 66 | 67 | done: 68 | return md5Sum, result 69 | } 70 | 71 | func (z *ZFileStorage) NewImage(saveName string, data []byte) error { 72 | var result error 73 | result = nil 74 | 75 | z.context.Logger.Info("Start to Storage the New Image...") 76 | 77 | f, result := os.OpenFile(saveName, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755) 78 | if result != nil { 79 | return result 80 | } 81 | 82 | z.Lock() //文件读写锁 83 | defer z.Unlock() 84 | defer f.Close() 85 | 86 | _, err := f.Write(data) 87 | if err != nil { 88 | result = err 89 | } 90 | 91 | return result 92 | } 93 | 94 | func (z *ZFileStorage) GetImage(request *ZRequest) ([]byte, error) { 95 | var result error 96 | var data []byte 97 | var rspCachekey string 98 | result = nil 99 | data = nil 100 | toSave := true 101 | mw := imagick.NewMagickWand() 102 | defer mw.Destroy() 103 | 104 | z.context.Logger.Info("get_img() start processing zimg request...") 105 | 106 | md5Sum := request.Md5 107 | lvl1 := str_hash(md5Sum) 108 | lvl2 := str_hash(string(md5Sum[3:])) 109 | 110 | wholePath := fmt.Sprintf("%s/%d/%d/%s", z.path, lvl1, lvl2, md5Sum) 111 | z.context.Logger.Debug("whole path : ", wholePath) 112 | 113 | if !is_dir(wholePath) { 114 | result = fmt.Errorf("Image %s is not existed!", md5Sum) 115 | //goto ErrHandle 116 | return nil, result 117 | } 118 | 119 | if len(request.ImageType) > 0 { 120 | rspCachekey = fmt.Sprintf("%s:%s", md5Sum, request.ImageType) 121 | } else { 122 | if request.Proportion == 0 && request.Width == 0 && request.Height == 0 { 123 | rspCachekey = md5Sum 124 | } else { 125 | rspCachekey = gen_key(md5Sum, request.Width, request.Height, request.Proportion, request.Gary, request.X, request.Y, request.Rotate, request.Quality, request.Format) 126 | } 127 | } 128 | 129 | data, err := z.context.Cache.FindCacheBin(rspCachekey) 130 | if err == nil { 131 | z.context.Logger.Debug("Hit Cache[Key: %s].\n", rspCachekey) 132 | toSave = false 133 | 134 | //goto Done 135 | return data, nil 136 | } 137 | 138 | z.context.Logger.Info("Start to Find the Image...") 139 | origPath := fmt.Sprintf("%s/0*0", wholePath) 140 | z.context.Logger.Debug("0rig File Path: %s \n", origPath) 141 | 142 | rspPath := "" 143 | if len(request.ImageType) > 0 { 144 | rspPath = fmt.Sprintf("%s/t_%s", wholePath, request.ImageType) 145 | } else { 146 | name := fmt.Sprintf("%d*%d_p%d_g%d_%d*%d_r%d_q%d.%s", request.Width, 147 | request.Height, request.Proportion, request.Gary, request.X, request.Y, request.Rotate, request.Quality, request.Format) 148 | 149 | if request.Proportion == 0 && request.Width == 0 && request.Height == 0 { 150 | z.context.Logger.Info("Return original image.") 151 | rspPath = origPath 152 | } else { 153 | rspPath = fmt.Sprintf("%s/%s", wholePath, name) 154 | } 155 | } 156 | 157 | z.context.Logger.Debug("Got the rsp_path: %s \n", rspPath) 158 | 159 | f, err := os.OpenFile(rspPath, os.O_RDONLY, 0755) 160 | defer f.Close() 161 | 162 | if err != nil { //读取不到文件 163 | origData, err := z.context.Cache.FindCacheBin(md5Sum) 164 | if err == nil { 165 | z.context.Logger.Debug("Hit Orignal Image Cache[Key: %s].", md5Sum) 166 | err = mw.ReadImageBlob(origData) 167 | if err != nil { 168 | z.context.Logger.Error("Open Original Image From Blob Failed! Begin to Open it From Disk.") 169 | z.context.Cache.DelCache(md5Sum) 170 | 171 | // origFile, err := os.Open(origPath) 172 | // if err != nil { 173 | // result = fmt.Errorf("Open Original Image From Disk Failed!") 174 | // //goto ErrHandle 175 | // return nil, result 176 | // } 177 | 178 | //err = mw.ReadImageFile(origFile) 179 | err = mw.ReadImage(origPath) 180 | if err != nil { 181 | result = fmt.Errorf("Open Original Image From Disk Failed!") 182 | //goto ErrHandle 183 | return nil, result 184 | } else { 185 | mw.ResetIterator() 186 | newBuff := mw.GetImageBlob() 187 | if newBuff == nil { 188 | result = fmt.Errorf("Webimg Get Original Blob Failed!") 189 | //goto ErrHandle 190 | return nil, result 191 | } 192 | 193 | if len(newBuff) < CACHE_MAX_SIZE { 194 | z.context.Cache.SetCacheBin(md5Sum, newBuff) 195 | z.context.Logger.Info("save " + md5Sum + " into cache") 196 | } 197 | 198 | } 199 | 200 | } 201 | 202 | } else { 203 | z.context.Logger.Debug("Not Hit Original Image Cache. Begin to Open it.") 204 | // origFile, err := os.Open(origPath) 205 | // if err != nil { 206 | // result = fmt.Errorf("Open Original Image From Disk Failed!") 207 | // //goto ErrHandle 208 | // return nil, result 209 | // } 210 | 211 | // err = mw.ReadImageFile(origFile) 212 | err = mw.ReadImage(origPath) 213 | if err != nil { 214 | result = fmt.Errorf("Open Original Image From Disk Failed!") 215 | //goto ErrHandle 216 | return nil, result 217 | } else { 218 | mw.ResetIterator() 219 | newBuff := mw.GetImageBlob() 220 | //z.context.Logger.Debug("get image blob length1111 : %d", len(newBuff)) 221 | 222 | if newBuff == nil { 223 | result = fmt.Errorf("Webimg Get Original Blob Failed!") 224 | //goto ErrHandle 225 | return nil, result 226 | } 227 | if len(newBuff) < CACHE_MAX_SIZE { 228 | z.context.Cache.SetCacheBin(md5Sum, newBuff) 229 | z.context.Logger.Info("save " + md5Sum + " into cache") 230 | } 231 | } 232 | } 233 | 234 | // newBuff1 := mw.GetImageBlob() 235 | // z.context.Logger.Debug("get image blob length2222 : %d", len(newBuff1)) 236 | 237 | result = convert(mw, request) 238 | if result != nil { 239 | return nil, result 240 | } 241 | 242 | data = mw.GetImageBlob() 243 | z.context.Logger.Debug("get image blob length : %d", len(data)) 244 | 245 | if data == nil || len(data) == 0 { 246 | result = fmt.Errorf("Webimg Get Blob Failed!") 247 | //goto ErrHandle 248 | return nil, result 249 | } 250 | 251 | } else { 252 | toSave = false 253 | fi, err := f.Stat() 254 | flen := int(fi.Size()) 255 | if flen <= 0 { 256 | //z.context.Logger.Debug("File[%s] is Empty.", rspPath) 257 | result = fmt.Errorf("File[%s] is Empty.", rspPath) 258 | //goto ErrHandle 259 | return nil, result 260 | } 261 | 262 | data = make([]byte, flen) 263 | rlen, err := f.Read(data) 264 | if err != nil { 265 | result = err 266 | //goto ErrHandle 267 | return nil, result 268 | } 269 | 270 | if rlen < flen { 271 | result = fmt.Errorf("File[%s] Read Not Compeletly. file len : %d, read len :%d", rspPath, flen, rlen) 272 | //goto ErrHandle 273 | return nil, result 274 | } 275 | } 276 | 277 | saveNew := 0 278 | if toSave { 279 | if request.Save == 1 || z.context.Config.Storage.SaveNew == 1 { 280 | saveNew = 1 281 | } 282 | } 283 | 284 | if saveNew == 1 { 285 | z.context.Logger.Debug("Image[%s] is Not Existed. Begin to Save it.", rspPath) 286 | if err = z.NewImage(rspPath, data); err != nil { 287 | z.context.Logger.Debug("New Image[%s] Save Failed!", rspPath) 288 | z.context.Logger.Warning("fail save %s", rspPath) 289 | } 290 | } else { 291 | z.context.Logger.Debug("Image [%s] Needn't to Storage.", rspPath) 292 | } 293 | 294 | if len(data) < CACHE_MAX_SIZE { 295 | z.context.Cache.SetCacheBin(rspCachekey, data) 296 | z.context.Logger.Info("save " + rspCachekey + " into cache") 297 | } 298 | 299 | //fmt.Println("image data :", data) 300 | 301 | return data, nil 302 | 303 | // ErrHandle: 304 | // return nil, result 305 | 306 | // Done: 307 | // return data, nil 308 | } 309 | 310 | func (z *ZFileStorage) InfoImage(md5 string) (*ZImageInfo, error) { 311 | var result error 312 | result = nil 313 | z.context.Logger.Info("info_img() start processing info request...") 314 | 315 | mw := imagick.NewMagickWand() 316 | defer mw.Destroy() 317 | 318 | md5Sum := md5 319 | lvl1 := str_hash(md5Sum) 320 | lvl2 := str_hash(string(md5Sum[3:])) 321 | 322 | wholePath := fmt.Sprintf("%s/%d/%d/%s", z.path, lvl1, lvl2, md5Sum) 323 | z.context.Logger.Debug("whole_path: %s", wholePath) 324 | 325 | if !is_dir(wholePath) { 326 | result = fmt.Errorf("Image %s is not existed!", md5Sum) 327 | 328 | return nil, result 329 | } 330 | 331 | origPath := fmt.Sprintf("%s/0*0", wholePath) 332 | z.context.Logger.Debug("0rig File Path: %s", origPath) 333 | 334 | err := mw.ReadImage(origPath) 335 | 336 | // origFile, err := os.Open(origPath) 337 | // if err != nil { 338 | // result = err 339 | // return nil, result 340 | // } 341 | 342 | // err = mw.ReadImageFile(origFile) 343 | if err != nil { 344 | result = fmt.Errorf("Open Original Image From Disk Failed!") 345 | return nil, result 346 | } 347 | 348 | size := 0 349 | width := int(mw.GetImageWidth()) 350 | height := int(mw.GetImageHeight()) 351 | quality := int(mw.GetImageCompressionQuality()) 352 | format := mw.GetImageFormat() 353 | 354 | return &ZImageInfo{Size: size, 355 | Width: width, 356 | Height: height, 357 | Quality: quality, 358 | Format: format, 359 | }, nil 360 | 361 | } 362 | -------------------------------------------------------------------------------- /zstorage_ssdb.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gographics/imagick/imagick" 6 | ) 7 | 8 | type ZSSDBStorage struct { 9 | *ZBaseStorage 10 | } 11 | 12 | func NewSSDBStorage(c *ZContext) *ZSSDBStorage { 13 | z := new(ZSSDBStorage) 14 | z.ZBaseStorage = new(ZBaseStorage) 15 | z.ZBaseStorage.context = c 16 | return z 17 | } 18 | 19 | func (z *ZSSDBStorage) SaveImage(data []byte) (string, error) { 20 | var result error = nil 21 | md5Sum := gen_md5_str(data) 22 | z.context.Logger.Info("md5 : %s", md5Sum) 23 | 24 | if z.context.Redis.Exist(md5Sum) { 25 | result = fmt.Errorf("File Exist, Needn't Save.") 26 | return "", result 27 | } 28 | 29 | z.context.Logger.Debug("exist_db not found. Begin to Save File.") 30 | result = z.context.Redis.Send("SET", md5Sum, data) 31 | if result != nil { 32 | return "", result 33 | } 34 | 35 | return md5Sum, nil 36 | } 37 | 38 | func (z *ZSSDBStorage) GetImage(request *ZRequest) ([]byte, error) { 39 | var result error = nil 40 | var data []byte = nil 41 | var rspCachekey string 42 | toSave := true 43 | 44 | mw := imagick.NewMagickWand() 45 | defer mw.Destroy() 46 | 47 | z.context.Logger.Info("get_img() start processing zimg request...") 48 | 49 | md5Sum := request.Md5 50 | if !z.context.Redis.Exist(md5Sum) { 51 | result = fmt.Errorf("Image [%s] is not existed.", md5Sum) 52 | return nil, result 53 | } 54 | 55 | if len(request.ImageType) > 0 { 56 | rspCachekey = fmt.Sprintf("%s:%s", md5Sum, request.ImageType) 57 | } else { 58 | if request.Proportion == 0 && request.Width == 0 && request.Height == 0 { 59 | rspCachekey = md5Sum 60 | } else { 61 | rspCachekey = gen_key(md5Sum, request.Width, request.Height, request.Proportion, request.Gary, request.X, request.Y, request.Rotate, request.Quality, request.Format) 62 | } 63 | } 64 | 65 | data, result = z.context.Cache.FindCacheBin(rspCachekey) 66 | if result == nil { 67 | z.context.Logger.Debug("Hit Cache[Key: %s].\n", rspCachekey) 68 | toSave = false 69 | return data, nil 70 | } 71 | 72 | z.context.Logger.Debug("Start to Find the Image...") 73 | 74 | data, result = z.context.Redis.Get(rspCachekey) 75 | if result == nil { 76 | z.context.Logger.Debug("Get image [%s] from backend db succ.", rspCachekey) 77 | if len(data) < CACHE_MAX_SIZE { 78 | z.context.Cache.SetCacheBin(rspCachekey, data) 79 | } 80 | toSave = false 81 | return data, nil 82 | } 83 | 84 | data, result = z.context.Cache.FindCacheBin(md5Sum) 85 | if result != nil { 86 | data, result = z.context.Redis.Get(md5Sum) 87 | if result != nil { 88 | z.context.Logger.Debug("Get image [%s] from backend db failed.", md5Sum) 89 | return nil, result 90 | } else { 91 | if len(data) < CACHE_MAX_SIZE { 92 | z.context.Cache.SetCacheBin(md5Sum, data) 93 | } 94 | } 95 | } 96 | 97 | result = mw.ReadImageBlob(data) 98 | if result != nil { 99 | z.context.Logger.Debug("Webimg Read Blob Failed!") 100 | return nil, result 101 | } 102 | 103 | result = convert(mw, request) 104 | if result != nil { 105 | return nil, result 106 | } 107 | 108 | data = mw.GetImageBlob() 109 | z.context.Logger.Debug("get image blob length : %d", len(data)) 110 | 111 | if data == nil || len(data) == 0 { 112 | result = fmt.Errorf("Webimg Get Blob Failed!") 113 | return nil, result 114 | } 115 | 116 | if len(data) < CACHE_MAX_SIZE { 117 | z.context.Cache.SetCacheBin(rspCachekey, data) 118 | } 119 | 120 | saveNew := 0 121 | if toSave { 122 | if request.Save == 1 || z.context.Config.Storage.SaveNew == 1 { 123 | saveNew = 1 124 | } 125 | } 126 | if saveNew == 1 { 127 | z.context.Logger.Debug("Image [%s] Saved to Storage.", rspCachekey) 128 | if err := z.context.Redis.Send("SET", rspCachekey, data); err != nil { 129 | z.context.Logger.Debug("New Image[%s] Save Failed!", rspCachekey) 130 | } 131 | } else { 132 | z.context.Logger.Debug("Image [%s] Needn't to Storage.", rspCachekey) 133 | } 134 | 135 | return data, nil 136 | } 137 | 138 | func (z *ZSSDBStorage) InfoImage(md5 string) (*ZImageInfo, error) { 139 | var result error 140 | result = nil 141 | z.context.Logger.Info("info_img() start processing info request...") 142 | 143 | mw := imagick.NewMagickWand() 144 | defer mw.Destroy() 145 | 146 | md5Sum := md5 147 | data, err := z.context.Redis.Get(md5Sum) 148 | if err != nil { 149 | result = fmt.Errorf("Image [%s] is not existed.", md5Sum) 150 | return nil, result 151 | } 152 | 153 | err = mw.ReadImageBlob(data) 154 | if err != nil { 155 | result = err 156 | return nil, result 157 | } 158 | 159 | size := 0 160 | width := int(mw.GetImageWidth()) 161 | height := int(mw.GetImageHeight()) 162 | quality := int(mw.GetImageCompressionQuality()) 163 | format := mw.GetImageFormat() 164 | 165 | return &ZImageInfo{Size: size, 166 | Width: width, 167 | Height: height, 168 | Quality: quality, 169 | Format: format, 170 | }, nil 171 | } 172 | -------------------------------------------------------------------------------- /zutil.go: -------------------------------------------------------------------------------- 1 | package gimg 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "fmt" 7 | "math" 8 | "os" 9 | _ "reflect" 10 | "regexp" 11 | "runtime" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | /* 17 | #include