From 849058ff8a4f41ae859399f9270c6c060b5db929 Mon Sep 17 00:00:00 2001 From: Minho Date: Tue, 27 Feb 2018 17:20:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=96=87=E6=A1=A3=E7=BC=93?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cache/cache.go | 46 ++++++++++++++++ cache/cache_null.go | 39 ++++++++++++++ commands/command.go | 113 ++++++++++++++++++++++++++++++++++++++-- conf/app.conf.example | 24 ++++++++- controllers/document.go | 34 ++++++------ models/document.go | 105 +++++++++++++++++++++++++++++++------ 6 files changed, 323 insertions(+), 38 deletions(-) create mode 100644 cache/cache.go create mode 100644 cache/cache_null.go diff --git a/cache/cache.go b/cache/cache.go new file mode 100644 index 00000000..968a8932 --- /dev/null +++ b/cache/cache.go @@ -0,0 +1,46 @@ +package cache + +import ( + "github.com/astaxie/beego/cache" + "time" +) + +var bm cache.Cache + +func Get(key string) interface{} { + + return bm.Get(key) +} + +func GetMulti(keys []string) []interface{} { + + return bm.GetMulti(keys) +} + +func Put(key string, val interface{}, timeout time.Duration) error { + + return bm.Put(key, val, timeout) +} +func Delete(key string) error { + return bm.Delete(key) +} +func Incr(key string) error { + return bm.Incr(key) +} +func Decr(key string) error { + return bm.Decr(key) +} +func IsExist(key string) bool { + return bm.IsExist(key) +} +func ClearAll() error{ + return bm.ClearAll() +} + +func StartAndGC(config string) error { + return bm.StartAndGC(config) +} +//初始化缓存 +func Init(c cache.Cache) { + bm = c +} \ No newline at end of file diff --git a/cache/cache_null.go b/cache/cache_null.go new file mode 100644 index 00000000..7abf55f0 --- /dev/null +++ b/cache/cache_null.go @@ -0,0 +1,39 @@ +package cache + +import "time" + +type NullCache struct { + +} + + +func (bm *NullCache)Get(key string) interface{} { + return nil +} + +func (bm *NullCache)GetMulti(keys []string) []interface{} { + return nil +} + +func (bm *NullCache)Put(key string, val interface{}, timeout time.Duration) error { + return nil +} +func (bm *NullCache)Delete(key string) error { + return nil +} +func (bm *NullCache)Incr(key string) error { + return nil +} +func (bm *NullCache)Decr(key string) error { + return nil +} +func (bm *NullCache)IsExist(key string) bool { + return false +} +func (bm *NullCache)ClearAll() error{ + return nil +} + +func (bm *NullCache)StartAndGC(config string) error { + return nil +} diff --git a/commands/command.go b/commands/command.go index 2fcbc4b8..f3bc772c 100644 --- a/commands/command.go +++ b/commands/command.go @@ -20,10 +20,15 @@ import ( "github.com/lifei6671/mindoc/conf" "github.com/lifei6671/mindoc/models" "github.com/lifei6671/mindoc/utils" + "github.com/lifei6671/mindoc/cache" + beegoCache "github.com/astaxie/beego/cache" + _ "github.com/astaxie/beego/cache/memcache" + _ "github.com/astaxie/beego/cache/redis" ) // RegisterDataBase 注册数据库 func RegisterDataBase() { + beego.Info("正在初始化数据库配置.") adapter := beego.AppConfig.String("db_adapter") if adapter == "mysql" { @@ -37,13 +42,18 @@ func RegisterDataBase() { dataSource := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true&loc=%s", username, password, host, port, database, url.QueryEscape(timezone)) - orm.RegisterDataBase("default", "mysql", dataSource) + err := orm.RegisterDataBase("default", "mysql", dataSource) + if err != nil { + beego.Error("注册默认数据库失败:",err) + os.Exit(1) + } location, err := time.LoadLocation(timezone) if err == nil { orm.DefaultTimeLoc = location } else { - log.Fatalln(err) + beego.Error("加载时区配置信息失败,请检查是否存在ZONEINFO环境变量:",err) + os.Exit(1) } } else if adapter == "sqlite3" { database := beego.AppConfig.String("db_database") @@ -54,8 +64,17 @@ func RegisterDataBase() { dbPath := filepath.Dir(database) os.MkdirAll(dbPath, 0777) - orm.RegisterDataBase("default", "sqlite3", database) + err := orm.RegisterDataBase("default", "sqlite3", database) + + if err != nil { + beego.Error("注册默认数据库失败:",err) + os.Exit(1) + } + }else{ + beego.Error("不支持的数据库类型.") + os.Exit(1) } + beego.Info("数据库初始化完成.") } // RegisterModel 注册Model @@ -120,7 +139,7 @@ func RegisterCommand() { migrate.RunMigration() } } - +//注册模板函数 func RegisterFunction() { beego.AddFuncMap("config", models.GetOptionValue) @@ -179,6 +198,7 @@ func RegisterFunction() { }) } +//解析命令 func ResolveCommand(args []string) { flagSet := flag.NewFlagSet("MinDoc command: ", flag.ExitOnError) flagSet.StringVar(&conf.ConfigurationFile, "config", "", "MinDoc configuration file.") @@ -226,10 +246,95 @@ func ResolveCommand(args []string) { gocaptcha.ReadFonts(filepath.Join(conf.WorkingDirectory, "static", "fonts"), ".ttf") RegisterDataBase() + RegisterCache() RegisterModel() RegisterLogger(conf.LogFile) } +//注册缓存管道 +func RegisterCache() { + isOpenCache := beego.AppConfig.DefaultBool("cache",false) + if !isOpenCache { + cache.Init(&cache.NullCache{}) + } + beego.Info("正常初始化缓存配置.") + cacheProvider := beego.AppConfig.String("cache_provider") + if cacheProvider == "file" { + cacheFilePath := beego.AppConfig.DefaultString("cache_file_path","./runtime/") + if strings.HasPrefix(cacheFilePath, "./") { + cacheFilePath = filepath.Join(conf.WorkingDirectory, string(cacheFilePath[1:])) + } + fileCache := beegoCache.NewFileCache() + beegoCache.FileCachePath = cacheFilePath + beegoCache.FileCacheDirectoryLevel = beego.AppConfig.DefaultInt("cache_file_dir_level",2) + beegoCache.FileCacheEmbedExpiry = time.Duration(beego.AppConfig.DefaultInt64("cache_file_expiry",120)) + beegoCache.FileCacheFileSuffix = beego.AppConfig.DefaultString("cache_file_suffix",".bin") + fileCache.StartAndGC("") + + cache.Init(fileCache) + }else if cacheProvider == "memory" { + cacheInterval := beego.AppConfig.DefaultInt("cache_memory_interval",60) + memory := beegoCache.NewMemoryCache() + beegoCache.DefaultEvery = cacheInterval + cache.Init(memory) + }else if cacheProvider == "redis"{ + var redisConfig struct{ + Conn string `json:"conn"` + Password string `json:"password"` + DbNum int `json:"dbNum"` + } + redisConfig.DbNum = 0 + redisConfig.Conn = beego.AppConfig.DefaultString("cache_redis_host","") + if pwd := beego.AppConfig.DefaultString("cache_redis_password","");pwd != "" { + redisConfig.Password = pwd + } + if dbNum := beego.AppConfig.DefaultInt("cache_redis_db",0); dbNum > 0 { + redisConfig.DbNum = dbNum + } + + bc,err := json.Marshal(&redisConfig) + if err != nil { + beego.Error("初始化Redis缓存失败:",err) + os.Exit(1) + } + beego.Info(string(bc)) + redisCache,err := beegoCache.NewCache("redis",string(bc)) + + if err != nil { + beego.Error("初始化Redis缓存失败:",err) + os.Exit(1) + } + + cache.Init(redisCache) + }else if cacheProvider == "memcache" { + + var memcacheConfig struct{ + Conn string `json:"conn"` + } + memcacheConfig.Conn = beego.AppConfig.DefaultString("cache_memcache_host","") + + bc,err := json.Marshal(&memcacheConfig) + if err != nil { + beego.Error("初始化Redis缓存失败:",err) + os.Exit(1) + } + memcache,err := beegoCache.NewCache("memcache",string(bc)) + + if err != nil { + beego.Error("初始化Memcache缓存失败:",err) + os.Exit(1) + } + + cache.Init(memcache) + + }else { + cache.Init(&cache.NullCache{}) + beego.Warn("不支持的缓存管道,缓存将禁用.") + return + } + beego.Info("缓存初始化完成.") +} + func init() { if configPath ,err := filepath.Abs(conf.ConfigurationFile); err == nil { diff --git a/conf/app.conf.example b/conf/app.conf.example index 0f9626cd..94d95567 100644 --- a/conf/app.conf.example +++ b/conf/app.conf.example @@ -105,7 +105,29 @@ ldap_user_role=2 ldap_filter=objectClass=posixAccount - +######################缓存配置############################### +#是否开启缓存,true 开启/false 不开启 +cache=false +#缓存方式:memory/memcache/redis/file +cache_provider=memory +#当配置缓存方式为memory时,内存回收时间,单位是秒 +cache_memory_interval=120 +#当缓存方式配置为file时,缓存的储存目录 +cache_file_path=./runtime/ +#缓存文件后缀 +cache_file_suffix= +#文件缓存目录层级 +cache_file_dir_level=2 +#文件缓存的默认过期时间 +cache_file_expiry=3600 +#memcache缓存服务器地址 +cache_memcache_host=127.0.0.1:11211 +#redis服务器地址 +cache_redis_host=127.0.0.1:6379 +#redis数据库索引 +cache_redis_db=0 +#redis服务器密码 +cache_redis_password= diff --git a/controllers/document.go b/controllers/document.go index 08988a1c..a8cfdd96 100644 --- a/controllers/document.go +++ b/controllers/document.go @@ -834,23 +834,23 @@ func (c *DocumentController) Content() { c.JsonResult(0, "ok", doc) } - -func (c *DocumentController) GetDocumentById(id string) (doc *models.Document, err error) { - doc = models.NewDocument() - if doc_id, err := strconv.Atoi(id); err == nil { - doc, err = doc.Find(doc_id) - if err != nil { - return nil, err - } - } else { - doc, err = doc.FindByFieldFirst("identify", id) - if err != nil { - return nil, err - } - } - - return doc, nil -} +// +//func (c *DocumentController) GetDocumentById(id string) (doc *models.Document, err error) { +// doc = models.NewDocument() +// if doc_id, err := strconv.Atoi(id); err == nil { +// doc, err = doc.Find(doc_id) +// if err != nil { +// return nil, err +// } +// } else { +// doc, err = doc.FindByFieldFirst("identify", id) +// if err != nil { +// return nil, err +// } +// } +// +// return doc, nil +//} // 导出 func (c *DocumentController) Export() { diff --git a/models/document.go b/models/document.go index 2b3f035f..1f351d09 100644 --- a/models/document.go +++ b/models/document.go @@ -2,7 +2,6 @@ package models import ( "time" - "bytes" "fmt" "github.com/astaxie/beego" @@ -13,6 +12,9 @@ import ( "path/filepath" "strconv" "github.com/PuerkitoBio/goquery" + "github.com/lifei6671/mindoc/cache" + "encoding/json" + "qiniupkg.com/x/errors.v7" ) // Document struct. @@ -63,6 +65,9 @@ func (m *Document) Find(id int) (*Document, error) { if id <= 0 { return m, ErrInvalidParameter } + if m,err := m.FromCacheById(id); err == nil { + return m,nil + } o := orm.NewOrm() err := o.QueryTable(m.TableNameWithPrefix()).Filter("document_id", id).One(m) @@ -70,55 +75,85 @@ func (m *Document) Find(id int) (*Document, error) { if err == orm.ErrNoRows { return m, ErrDataNotExist } + m.PutToCache() return m, nil } //插入和更新文档. func (m *Document) InsertOrUpdate(cols ...string) error { o := orm.NewOrm() - + var err error if m.DocumentId > 0 { - _, err := o.Update(m) - return err + _, err = o.Update(m) } else { - _, err := o.Insert(m) + _, err = o.Insert(m) NewBook().ResetDocumentNumber(m.BookId) + } + if err != nil { return err } + + m.PutToCache() + + return nil } //根据指定字段查询一条文档. func (m *Document) FindByFieldFirst(field string, v interface{}) (*Document, error) { + + if field == "identify" { + if identify,ok := v.(string);ok { + if m,err := m.FromCacheByIdentify(identify);err == nil{ + return m,nil + } + } + }else if field == "document_id" { + if id,ok := v.(int); ok && id > 0 { + if m,err := m.FromCacheById(id);err == nil{ + return m,nil + } + } + } + o := orm.NewOrm() err := o.QueryTable(m.TableNameWithPrefix()).Filter(field, v).One(m) + if err == nil { + m.PutToCache() + } + return m, err } //递归删除一个文档. -func (m *Document) RecursiveDocument(doc_id int) error { +func (m *Document) RecursiveDocument(docId int) error { o := orm.NewOrm() - if doc, err := m.Find(doc_id); err == nil { + if doc, err := m.Find(docId); err == nil { o.Delete(doc) NewDocumentHistory().Clear(doc.DocumentId) } + // + //var docs []*Document + // + //_, err := o.QueryTable(m.TableNameWithPrefix()).Filter("parent_id", doc_id).All(&docs) - var docs []*Document - - _, err := o.QueryTable(m.TableNameWithPrefix()).Filter("parent_id", doc_id).All(&docs) + var maps []orm.Params + _, err := o.Raw("SELECT document_id FROM " + m.TableNameWithPrefix() + " WHERE parent_id=" + strconv.Itoa(docId)).Values(&maps) if err != nil { beego.Error("RecursiveDocument => ", err) return err } - for _, item := range docs { - doc_id := item.DocumentId - o.QueryTable(m.TableNameWithPrefix()).Filter("document_id", doc_id).Delete() - m.RecursiveDocument(doc_id) + for _, item := range maps { + if docId,ok := item["document_id"].(string); ok{ + id,_ := strconv.Atoi(docId) + o.QueryTable(m.TableNameWithPrefix()).Filter("document_id", id).Delete() + m.RecursiveDocument(id) + } } return nil @@ -178,16 +213,54 @@ func (m *Document) ReleaseContent(bookId int) { if err != nil { beego.Error(fmt.Sprintf("发布失败 => %+v", item), err) }else { + //当文档发布后,需要清除已缓存的转换文档和文档缓存 + item.PutToCache() os.RemoveAll(filepath.Join(conf.WorkingDirectory,"uploads","books",strconv.Itoa(bookId))) } } } +//将文档写入缓存 +func (m *Document) PutToCache(){ + if v,err := json.Marshal(m);err == nil { + if m.Identify == "" { + if err := cache.Put("Document.Id."+strconv.Itoa(m.DocumentId), v, time.Second*3600); err != nil { + beego.Info("文档缓存失败:", m) + } + }else{ + if err := cache.Put("Document.Identify."+ m.Identify, v, time.Second*3600); err != nil { + beego.Info("文档缓存失败:", m) + } + } + } +} +//从缓存获取 +func (m *Document) FromCacheById(id int) (*Document,error) { + b := cache.Get("Document.Id." + strconv.Itoa(id)) + if v,ok := b.([]byte); ok { + beego.Info("从缓存中获取文档信息成功") + if err := json.Unmarshal(v,m);err == nil{ + return m,nil + } + } + return nil,errors.New("Cache not exists") +} +func (m *Document) FromCacheByIdentify(identify string) (*Document,error) { + b := cache.Get("Document.Identify." + identify) + if v,ok := b.([]byte); ok { + beego.Info("从缓存中获取文档信息成功") + if err := json.Unmarshal(v,m);err == nil{ + return m,nil + } + } + return nil,errors.New("Cache not exists") +} + //根据项目ID查询文档列表. -func (m *Document) FindListByBookId(book_id int) (docs []*Document, err error) { +func (m *Document) FindListByBookId(bookId int) (docs []*Document, err error) { o := orm.NewOrm() - _, err = o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", book_id).OrderBy("order_sort").All(&docs) + _, err = o.QueryTable(m.TableNameWithPrefix()).Filter("book_id", bookId).OrderBy("order_sort").All(&docs) return }