├── .gitattributes ├── .gitignore ├── COPYING ├── README.md ├── lm_test.go └── lm.go /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2020, 353474225@qq.com 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文 README](#中文) 2 | 3 | 4 | # [lm](http://github.com/simplejia/lm) (lc+redis+[mysql|http] glue) 5 | ## Original Intention 6 | > When coding with redis and mysql(maybe also add lc cache support), as following: 7 | ``` 8 | func orig(key string) (value string) { 9 | value = redis.Get(key) 10 | if value != "" { 11 | return 12 | } 13 | value = mysql.Get(key) 14 | redis.Set(key, value) 15 | return 16 | } 17 | // add lc cache support 18 | func orig(key string) (value string) { 19 | value = lc.Get(key) 20 | if value != "" { 21 | return 22 | } 23 | value = redis.Get(key) 24 | if value != "" { 25 | lc.Set(key, value) 26 | return 27 | } 28 | value = mysql.Get(key) 29 | redis.Set(key, value) 30 | lc.Set(key, value) 31 | return 32 | } 33 | ``` 34 | > Having lm, the code above will be very easy: 35 | [lm_test.go](http://github.com/simplejia/lm/tree/master/lm_test.go) 36 | ``` 37 | func tGlue(key, value string) (err error) { 38 | lmStru := &LmStru{ 39 | Input: key, 40 | Output: &value, 41 | Proc: func(p, r interface{}) error { 42 | _r := r.(*string) 43 | *_r = "test value" 44 | return nil 45 | }, 46 | Key: func(p interface{}) string { 47 | return fmt.Sprintf("tGlue:%v", p) 48 | }, 49 | Mc: &McStru{ 50 | Expire: time.Minute, 51 | Pool: pool, 52 | }, 53 | Lc: &LcStru{ 54 | Expire: time.Millisecond * 500, 55 | Safety: false, 56 | }, 57 | } 58 | err = Glue(lmStru) 59 | if err != nil { 60 | return 61 | } 62 | return 63 | } 64 | ``` 65 | ## Features 66 | * It can automatically add cache feature and support lc and redis, which will make your code more simpler and reliable and reduce large segment of redundant code. 67 | * It supports Glue[Lc|Mc] and corresponding batch operation(Glues[Lc|Mc]), for the details, please refer to the instance code of lm_test.go 68 | 69 | ## Notice 70 | * lm.LcStru.Safety parameter,When it is true, the nil value returned by the LC under concurrent state is not accepted, because when lc.Get is under concurrent state, the returned value by the same key maybe nil but ok parameter is true. When safety is set as true, all the conditions above will not be accepted. It will continue the next logic. 71 | 72 | --- 73 | 中文 74 | === 75 | 76 | # [lm](http://github.com/simplejia/lm) (lc+redis+[mysql|http] glue) 77 | ## 实现初衷 78 | > 写redis+mysql代码时(还可能加上lc),示意代码如下: 79 | ``` 80 | func orig(key string) (value string) { 81 | value = redis.Get(key) 82 | if value != "" { 83 | return 84 | } 85 | value = mysql.Get(key) 86 | redis.Set(key, value) 87 | return 88 | } 89 | // 如果再加上lc的话 90 | func orig(key string) (value string) { 91 | value = lc.Get(key) 92 | if value != "" { 93 | return 94 | } 95 | value = redis.Get(key) 96 | if value != "" { 97 | lc.Set(key, value) 98 | return 99 | } 100 | value = mysql.Get(key) 101 | redis.Set(key, value) 102 | lc.Set(key, value) 103 | return 104 | } 105 | ``` 106 | > 有了lm,再写上面的代码时,一切变的那么简单 107 | [lm_test.go](http://github.com/simplejia/lm/tree/master/lm_test.go) 108 | ``` 109 | func tGlue(key, value string) (err error) { 110 | lmStru := &LmStru{ 111 | Input: key, 112 | Output: &value, 113 | Proc: func(p, r interface{}) error { 114 | _r := r.(*string) 115 | *_r = "test value" 116 | return nil 117 | }, 118 | Key: func(p interface{}) string { 119 | return fmt.Sprintf("tGlue:%v", p) 120 | }, 121 | Mc: &McStru{ 122 | Expire: time.Minute, 123 | Pool: pool, 124 | }, 125 | Lc: &LcStru{ 126 | Expire: time.Millisecond * 500, 127 | Safety: false, 128 | }, 129 | } 130 | err = Glue(lmStru) 131 | if err != nil { 132 | return 133 | } 134 | return 135 | } 136 | ``` 137 | ## 功能 138 | * 自动添加缓存代码,支持lc, redis,减轻你的心智负担,让你的代码更加简单可靠,少了大段的冗余代码,复杂的事全交给lm自动帮你做了 139 | * 支持Glue[Lc|Mc]及相应批量操作Glues[Lc|Mc],详见lm_test.go示例代码 140 | 141 | ## 注意 142 | * lm.LcStru.Safety,当置为true时,对lc在并发状态下返回的nil值不接受,因为lc.Get在并发状态下,同一个key返回的value有可能是nil,并且ok状态为true,Safety置为true后,对以上情况不接受,会继续调用下一层逻辑 143 | 144 | ## 案例分享 145 | * 一天一个用户只容许投一次票 146 | ``` 147 | func f(uid string) (err error) { 148 | lmStru := &lm.LmStru{ 149 | Input: uid, 150 | Output: &struct{}{}, 151 | Proc: func(p, r interface{}) error { 152 | // 略掉这部分逻辑: 可以把投票入库 153 | // ... 154 | return nil 155 | }, 156 | Key: func(p interface{}) string { 157 | return fmt.Sprintf("pkg:f:%v", p) 158 | }, 159 | Mc: &lm.McStru{ 160 | Expire: time.Hour * 24, 161 | Pool: pool, 162 | }, 163 | } 164 | err = lm.GlueMc(lmStru) 165 | if err != nil { 166 | return 167 | } 168 | return 169 | } 170 | ``` 171 | 172 | -------------------------------------------------------------------------------- /lm_test.go: -------------------------------------------------------------------------------- 1 | package lm 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/simplejia/lc" 9 | 10 | "github.com/garyburd/redigo/redis" 11 | ) 12 | 13 | var pool *redis.Pool 14 | 15 | func init() { 16 | pool = &redis.Pool{ 17 | Dial: func() (c redis.Conn, err error) { 18 | c, err = redis.Dial("tcp", ":6379") 19 | if err != nil { 20 | return 21 | } 22 | return 23 | }, 24 | } 25 | 26 | lc.Init(65536) 27 | } 28 | 29 | type s1 struct { 30 | A int 31 | } 32 | 33 | func f1(ids []uint64, r *map[uint64]*s1) (err error) { 34 | m := map[uint64]*s1{ 35 | 33: &s1{ 36 | A: 9, 37 | }, 38 | 34: &s1{ 39 | A: 10, 40 | }, 41 | } 42 | for _, id := range ids { 43 | if d, ok := m[id]; ok { 44 | (*r)[id] = d 45 | } 46 | } 47 | return 48 | } 49 | 50 | func f2(id uint64, r *s1) (err error) { 51 | m := map[uint64]*s1{ 52 | 33: &s1{ 53 | A: 9, 54 | }, 55 | } 56 | if v, ok := m[id]; ok { 57 | *r = *v 58 | } 59 | return 60 | } 61 | 62 | func f3(id uint64, r **s1) (err error) { 63 | m := map[uint64]*s1{ 64 | 33: &s1{ 65 | A: 9, 66 | }, 67 | } 68 | *r = m[id] 69 | return 70 | } 71 | 72 | func f4(id, rid uint64, r *[]*s1) (err error) { 73 | m := map[string][]*s1{ 74 | "33_44": []*s1{ 75 | &s1{ 76 | A: 9, 77 | }, 78 | &s1{ 79 | A: 10, 80 | }, 81 | }, 82 | "55_66": []*s1{ 83 | &s1{ 84 | A: 9, 85 | }, 86 | &s1{ 87 | A: 10, 88 | }, 89 | }, 90 | } 91 | if v, ok := m[fmt.Sprintf("%v_%v", id, rid)]; ok { 92 | *r = v 93 | } 94 | return 95 | } 96 | 97 | func mf1(kind string, id uint64) (ret string) { 98 | ret = "lm_" + kind + strconv.FormatUint(id, 10) 99 | return 100 | } 101 | 102 | func mf2(kind string, id, rid uint64) (ret string) { 103 | ret = "lm_" + kind + strconv.FormatUint(id, 10) + "_" + strconv.FormatUint(rid, 10) 104 | return 105 | } 106 | 107 | func tGluesLc() { 108 | ids := []uint64{1, 2, 33} 109 | var r map[uint64]*s1 110 | 111 | lmStru := &LmStru{ 112 | Input: ids, 113 | Output: &r, 114 | Proc: func(ps, result interface{}) error { 115 | return f1(ps.([]uint64), result.(*map[uint64]*s1)) 116 | }, 117 | Key: func(p interface{}) string { 118 | return mf1("GluesLc", p.(uint64)) 119 | }, 120 | Lc: &LcStru{ 121 | Expire: time.Second, 122 | Safety: false, 123 | }, 124 | } 125 | err := GluesLc(lmStru) 126 | if err != nil { 127 | fmt.Println(err) 128 | return 129 | } 130 | 131 | fmt.Println(r) 132 | } 133 | 134 | func tGlueLc() { 135 | id := uint64(33) 136 | var r *s1 137 | 138 | lmStru := &LmStru{ 139 | Input: id, 140 | Output: &r, 141 | Proc: func(p, r interface{}) error { 142 | return f3(p.(uint64), r.(**s1)) 143 | }, 144 | Key: func(p interface{}) string { 145 | return mf1("GlueLc", p.(uint64)) 146 | }, 147 | Lc: &LcStru{ 148 | Expire: time.Second, 149 | Safety: false, 150 | }, 151 | } 152 | err := GlueLc(lmStru) 153 | if err != nil { 154 | fmt.Println(err) 155 | return 156 | } 157 | 158 | fmt.Println(r) 159 | } 160 | 161 | func tGlueFlatLc() { 162 | id := uint64(33) 163 | rid := uint64(44) 164 | var r []*s1 165 | 166 | lmStru := &LmStru{ 167 | Input: []interface{}{id, rid}, 168 | Output: &r, 169 | Proc: func(p, r interface{}) error { 170 | return f4(p.([]interface{})[0].(uint64), p.([]interface{})[1].(uint64), r.(*[]*s1)) 171 | }, 172 | Key: func(p interface{}) string { 173 | return mf2("GlueFlatLc", p.([]interface{})[0].(uint64), p.([]interface{})[1].(uint64)) 174 | }, 175 | Lc: &LcStru{ 176 | Expire: time.Second, 177 | Safety: false, 178 | }, 179 | } 180 | err := GlueLc(lmStru) 181 | if err != nil { 182 | fmt.Println(err) 183 | return 184 | } 185 | 186 | fmt.Println(r) 187 | } 188 | 189 | func tGluesMc() { 190 | ids := []uint64{1, 2, 33} 191 | var r map[uint64]*s1 192 | 193 | lmStru := &LmStru{ 194 | Input: ids, 195 | Output: &r, 196 | Proc: func(ps, result interface{}) error { 197 | return f1(ps.([]uint64), result.(*map[uint64]*s1)) 198 | }, 199 | Key: func(p interface{}) string { 200 | return mf1("GluesMc", p.(uint64)) 201 | }, 202 | Mc: &McStru{ 203 | Expire: time.Second, 204 | Pool: pool, 205 | }, 206 | } 207 | err := GluesMc(lmStru) 208 | if err != nil { 209 | fmt.Println(err) 210 | return 211 | } 212 | 213 | fmt.Println(r) 214 | } 215 | 216 | func tGlueMc() { 217 | id := uint64(33) 218 | var r *s1 219 | 220 | lmStru := &LmStru{ 221 | Input: id, 222 | Output: &r, 223 | Proc: func(p, r interface{}) error { 224 | return f3(p.(uint64), r.(**s1)) 225 | }, 226 | Key: func(p interface{}) string { 227 | return mf1("GlueMc", p.(uint64)) 228 | }, 229 | Mc: &McStru{ 230 | Expire: time.Second, 231 | Pool: pool, 232 | }, 233 | } 234 | err := GlueMc(lmStru) 235 | if err != nil { 236 | fmt.Println(err) 237 | return 238 | } 239 | 240 | fmt.Println(r) 241 | } 242 | 243 | func tGlueFlatMc() { 244 | id := uint64(33) 245 | rid := uint64(44) 246 | var r []*s1 247 | 248 | lmStru := &LmStru{ 249 | Input: []interface{}{id, rid}, 250 | Output: &r, 251 | Proc: func(p, r interface{}) error { 252 | return f4(p.([]interface{})[0].(uint64), p.([]interface{})[1].(uint64), r.(*[]*s1)) 253 | }, 254 | Key: func(p interface{}) string { 255 | return mf2("GlueFlatMc", p.([]interface{})[0].(uint64), p.([]interface{})[1].(uint64)) 256 | }, 257 | Mc: &McStru{ 258 | Expire: time.Second, 259 | Pool: pool, 260 | }, 261 | } 262 | err := GlueMc(lmStru) 263 | if err != nil { 264 | fmt.Println(err) 265 | return 266 | } 267 | 268 | fmt.Println(r) 269 | } 270 | 271 | func tGlues() { 272 | ids := []uint64{1, 2, 33} 273 | var r map[uint64]*s1 274 | 275 | lmStru := &LmStru{ 276 | Input: ids, 277 | Output: &r, 278 | Proc: func(ps, r interface{}) error { 279 | return f1(ps.([]uint64), r.(*map[uint64]*s1)) 280 | }, 281 | Key: func(p interface{}) string { 282 | return mf1("Glues", p.(uint64)) 283 | }, 284 | Mc: &McStru{ 285 | Expire: time.Second * 2, 286 | Pool: pool, 287 | }, 288 | Lc: &LcStru{ 289 | Expire: time.Second, 290 | Safety: false, 291 | }, 292 | } 293 | err := Glues(lmStru) 294 | if err != nil { 295 | fmt.Println(err) 296 | return 297 | } 298 | 299 | fmt.Println(r) 300 | } 301 | 302 | func tGlue() { 303 | id := uint64(33) 304 | var r *s1 305 | 306 | lmStru := &LmStru{ 307 | Input: id, 308 | Output: &r, 309 | Proc: func(p, r interface{}) error { 310 | return f3(p.(uint64), r.(**s1)) 311 | }, 312 | Key: func(p interface{}) string { 313 | return mf1("Glue", p.(uint64)) 314 | }, 315 | Mc: &McStru{ 316 | Expire: time.Second * 2, 317 | Pool: pool, 318 | }, 319 | Lc: &LcStru{ 320 | Expire: time.Second, 321 | Safety: false, 322 | }, 323 | } 324 | err := Glue(lmStru) 325 | if err != nil { 326 | fmt.Println(err) 327 | return 328 | } 329 | 330 | fmt.Println(r) 331 | } 332 | 333 | func tGlueFlat() { 334 | id := uint64(55) 335 | rid := uint64(66) 336 | var r []*s1 337 | 338 | lmStru := &LmStru{ 339 | Input: []interface{}{id, rid}, 340 | Output: &r, 341 | Proc: func(p, r interface{}) error { 342 | return f4(p.([]interface{})[0].(uint64), p.([]interface{})[1].(uint64), r.(*[]*s1)) 343 | }, 344 | Key: func(p interface{}) string { 345 | return mf2("GlueFlat", p.([]interface{})[0].(uint64), p.([]interface{})[1].(uint64)) 346 | }, 347 | Mc: &McStru{ 348 | Expire: time.Second * 2, 349 | Pool: pool, 350 | }, 351 | Lc: &LcStru{ 352 | Expire: time.Second, 353 | Safety: false, 354 | }, 355 | } 356 | err := Glue(lmStru) 357 | if err != nil { 358 | fmt.Println(err) 359 | return 360 | } 361 | 362 | fmt.Println(r) 363 | } 364 | 365 | func ExampleGlueLc() { 366 | tGlueLc() 367 | } 368 | 369 | func ExampleGlueFlatLc() { 370 | tGlueFlatLc() 371 | } 372 | 373 | func ExampleGluesLc() { 374 | tGluesLc() 375 | } 376 | 377 | func ExampleGlueMc() { 378 | tGlueMc() 379 | } 380 | 381 | func ExampleGlueFlatMc() { 382 | tGlueFlatMc() 383 | } 384 | 385 | func ExampleGluesMc() { 386 | tGluesMc() 387 | } 388 | 389 | func ExampleGlue() { 390 | tGlue() 391 | } 392 | 393 | func ExampleGlueFlat() { 394 | tGlueFlat() 395 | } 396 | 397 | func ExampleGlues() { 398 | tGlues() 399 | } 400 | -------------------------------------------------------------------------------- /lm.go: -------------------------------------------------------------------------------- 1 | // Package lm is used as wrapper for lc, redis, mysql etc... 2 | // Created by simplejia [6/2016] 3 | package lm 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | 9 | "github.com/garyburd/redigo/redis" 10 | "github.com/simplejia/lc" 11 | 12 | "reflect" 13 | "time" 14 | ) 15 | 16 | type LmStru struct { 17 | Input interface{} 18 | Output interface{} 19 | Proc ft 20 | Key mft 21 | Mc *McStru 22 | Lc *LcStru 23 | } 24 | 25 | type McStru struct { 26 | Expire time.Duration 27 | Pool *redis.Pool 28 | Safety bool // true: nil值不保存 29 | } 30 | 31 | type LcStru struct { 32 | Expire time.Duration 33 | Safety bool // true: 对lc在并发状态下返回的nil值不接受 34 | } 35 | 36 | type ft func(p, r interface{}) error 37 | type mft func(p interface{}) string 38 | 39 | func GluesMc(lmStru *LmStru) (err error) { 40 | ps, result, f, mf, stru := lmStru.Input, lmStru.Output, lmStru.Proc, lmStru.Key, lmStru.Mc 41 | expire, pool, safety := stru.Expire, stru.Pool, stru.Safety 42 | 43 | rps := reflect.ValueOf(ps) 44 | num := rps.Len() 45 | if num == 0 { 46 | return 47 | } 48 | 49 | rresult := reflect.Indirect(reflect.ValueOf(result)) 50 | if rresult.IsNil() { 51 | rresult = reflect.MakeMap(rresult.Type()) 52 | reflect.Indirect(reflect.ValueOf(result)).Set(rresult) 53 | } 54 | 55 | keys := make([]interface{}, num) 56 | for i := 0; i < num; i++ { 57 | rp := rps.Index(i) 58 | p := rp.Interface() 59 | key := mf(p) 60 | keys[i] = key 61 | } 62 | 63 | conn := pool.Get() 64 | defer conn.Close() 65 | vs, err := redis.Strings(conn.Do("MGET", keys...)) 66 | if err != nil { 67 | return 68 | } 69 | 70 | rpsNone := reflect.MakeSlice(reflect.TypeOf(ps), 0, 0) 71 | for i := 0; i < num; i++ { 72 | rp := rps.Index(i) 73 | if v := vs[i]; v != "" { 74 | var rppv reflect.Value 75 | var pvNew interface{} 76 | if re := reflect.TypeOf(result).Elem().Elem(); re.Kind() == reflect.Ptr { 77 | rppv = reflect.New(re.Elem()) 78 | pvNew = rppv.Interface() 79 | } else { 80 | rppv = reflect.New(re) 81 | pvNew = rppv.Interface() 82 | rppv = reflect.Indirect(rppv) 83 | } 84 | json.Unmarshal([]byte(v), &pvNew) 85 | if pvNew == nil { 86 | continue 87 | } 88 | rresult.SetMapIndex(rp, rppv) 89 | } else { 90 | rpsNone = reflect.Append(rpsNone, rp) 91 | continue 92 | } 93 | } 94 | 95 | numNone := rpsNone.Len() 96 | if numNone == 0 { 97 | return 98 | } 99 | 100 | rresultPtrNone := reflect.New(rresult.Type()) 101 | reflect.Indirect(rresultPtrNone).Set(reflect.MakeMap(rresult.Type())) 102 | rresultNone := reflect.Indirect(rresultPtrNone) 103 | err = f(rpsNone.Interface(), rresultPtrNone.Interface()) 104 | if err != nil { 105 | return 106 | } 107 | 108 | for i := 0; i < rpsNone.Len(); i++ { 109 | rpNone := rpsNone.Index(i) 110 | pNone := rpNone.Interface() 111 | key4mc := mf(pNone) 112 | expire4mc := int(expire / time.Second) 113 | rv := rresultNone.MapIndex(rpNone) 114 | if rv.IsValid() { 115 | rresult.SetMapIndex(rpNone, rv) 116 | v, errIgnore := json.Marshal(rv.Interface()) 117 | if errIgnore != nil { 118 | continue 119 | } 120 | conn.Do("SETEX", key4mc, expire4mc, v) 121 | } else if !safety { 122 | conn.Do("SETEX", key4mc, expire4mc, "null") 123 | } 124 | } 125 | 126 | return 127 | } 128 | 129 | func GlueMc(lmStru *LmStru) (err error) { 130 | p, result, f, mf, stru := lmStru.Input, lmStru.Output, lmStru.Proc, lmStru.Key, lmStru.Mc 131 | expire, pool, safety := stru.Expire, stru.Pool, stru.Safety 132 | 133 | key4mc := mf(p) 134 | expire4mc := int(expire / time.Second) 135 | 136 | conn := pool.Get() 137 | defer conn.Close() 138 | v, errIgnore := redis.String(conn.Do("GET", key4mc)) 139 | if errIgnore == nil { 140 | err = json.Unmarshal([]byte(v), &result) 141 | return 142 | } else if errIgnore != redis.ErrNil { 143 | err = errIgnore 144 | return 145 | } 146 | 147 | err = f(p, result) 148 | if err != nil { 149 | return 150 | } 151 | 152 | vs, err := json.Marshal(result) 153 | if err != nil { 154 | return 155 | } 156 | 157 | if safety && bytes.Compare(vs, []byte("null")) == 0 { 158 | return 159 | } 160 | 161 | _, err = conn.Do("SETEX", key4mc, expire4mc, vs) 162 | if err != nil { 163 | return 164 | } 165 | 166 | return 167 | } 168 | 169 | func GluesLc(lmStru *LmStru) (err error) { 170 | ps, result, f, mf, stru := lmStru.Input, lmStru.Output, lmStru.Proc, lmStru.Key, lmStru.Lc 171 | expire, safety := stru.Expire, stru.Safety 172 | 173 | rps := reflect.ValueOf(ps) 174 | num := rps.Len() 175 | if num == 0 { 176 | return 177 | } 178 | 179 | rresult := reflect.Indirect(reflect.ValueOf(result)) 180 | if rresult.IsNil() { 181 | rresult = reflect.MakeMap(rresult.Type()) 182 | reflect.Indirect(reflect.ValueOf(result)).Set(rresult) 183 | } 184 | 185 | keys, keysM := make([]string, num), map[string]interface{}{} 186 | for i := 0; i < num; i++ { 187 | rp := rps.Index(i) 188 | p := rp.Interface() 189 | key := mf(p) 190 | keys[i] = key 191 | keysM[key] = p 192 | } 193 | 194 | vsLc, vsAlterLc := lc.Mget(keys) 195 | for k, v := range vsLc { 196 | if v == nil { 197 | if safety { 198 | delete(vsLc, k) 199 | vsAlterLc[k] = nil 200 | } 201 | continue 202 | } 203 | p := keysM[k] 204 | rresult.SetMapIndex(reflect.ValueOf(p), reflect.ValueOf(v)) 205 | } 206 | 207 | numNone := num - len(vsLc) 208 | if numNone == 0 { 209 | return 210 | } 211 | 212 | rpsNone := reflect.MakeSlice(reflect.TypeOf(ps), 0, numNone) 213 | for i := 0; i < num; i++ { 214 | rp := rps.Index(i) 215 | p := rp.Interface() 216 | key := mf(p) 217 | if _, ok := vsAlterLc[key]; ok { 218 | rpsNone = reflect.Append(rpsNone, rp) 219 | } 220 | } 221 | 222 | rresultPtrNone := reflect.New(rresult.Type()) 223 | reflect.Indirect(rresultPtrNone).Set(reflect.MakeMap(rresult.Type())) 224 | rresultNone := reflect.Indirect(rresultPtrNone) 225 | errIgnore := f(rpsNone.Interface(), rresultPtrNone.Interface()) 226 | if errIgnore != nil { 227 | if safety { 228 | err = errIgnore 229 | return 230 | } 231 | for k, v := range vsAlterLc { 232 | if v == nil { 233 | continue 234 | } 235 | p := keysM[k] 236 | rresult.SetMapIndex(reflect.ValueOf(p), reflect.ValueOf(v)) 237 | } 238 | return 239 | } 240 | 241 | for i := 0; i < rpsNone.Len(); i++ { 242 | rpNone := rpsNone.Index(i) 243 | pNone := rpNone.Interface() 244 | key := mf(pNone) 245 | rv := rresultNone.MapIndex(rpNone) 246 | if rv.IsValid() { 247 | rresult.SetMapIndex(rpNone, rv) 248 | lc.Set(key, rv.Interface(), expire) 249 | } else { 250 | lc.Set(key, nil, expire) 251 | } 252 | } 253 | 254 | return 255 | } 256 | 257 | func GlueLc(lmStru *LmStru) (err error) { 258 | p, result, f, mf, stru := lmStru.Input, lmStru.Output, lmStru.Proc, lmStru.Key, lmStru.Lc 259 | expire, safety := stru.Expire, stru.Safety 260 | 261 | rresult := reflect.Indirect(reflect.ValueOf(result)) 262 | 263 | key := mf(p) 264 | vLc, ok := lc.Get(key) 265 | if vLc != nil { 266 | rresult.Set(reflect.Indirect(reflect.ValueOf(vLc))) 267 | } 268 | if ok { 269 | if vLc != nil || !safety { 270 | return 271 | } 272 | } 273 | 274 | rresultNone := reflect.New(rresult.Type()) 275 | errIgnore := f(p, rresultNone.Interface()) 276 | if errIgnore != nil { 277 | if safety { 278 | err = errIgnore 279 | return 280 | } 281 | return 282 | } 283 | 284 | rresult.Set(reflect.Indirect(rresultNone)) 285 | lc.Set(key, result, expire) 286 | return 287 | } 288 | 289 | func Glues(lmStru *LmStru) (err error) { 290 | ps, result, f, mf, mcStru, lcStru := lmStru.Input, lmStru.Output, lmStru.Proc, lmStru.Key, lmStru.Mc, lmStru.Lc 291 | lmStru = &LmStru{ 292 | Input: ps, 293 | Output: result, 294 | Proc: func(ps, result interface{}) error { 295 | lmStru := &LmStru{ 296 | Input: ps, 297 | Output: result, 298 | Proc: f, 299 | Key: mf, 300 | Mc: mcStru, 301 | } 302 | return GluesMc(lmStru) 303 | }, 304 | Key: mf, 305 | Lc: lcStru, 306 | } 307 | return GluesLc(lmStru) 308 | } 309 | 310 | func Glue(lmStru *LmStru) (err error) { 311 | p, result, f, mf, mcStru, lcStru := lmStru.Input, lmStru.Output, lmStru.Proc, lmStru.Key, lmStru.Mc, lmStru.Lc 312 | lmStru = &LmStru{ 313 | Input: p, 314 | Output: result, 315 | Proc: func(p, result interface{}) error { 316 | lmStru := &LmStru{ 317 | Input: p, 318 | Output: result, 319 | Proc: f, 320 | Key: mf, 321 | Mc: mcStru, 322 | } 323 | return GlueMc(lmStru) 324 | }, 325 | Key: mf, 326 | Lc: lcStru, 327 | } 328 | return GlueLc(lmStru) 329 | } 330 | --------------------------------------------------------------------------------