beego源码ITeye - 凯发娱乐

beego源码ITeye

2019-01-09 16:36:11 | 作者: 采蓝 | 标签: 装备,内容,文件 | 浏览: 2302

写在前面
beego 是一个快速开发 Go 运用的 HTTP 结构,他能够用来快速开发 API、Web 及后端效劳等各种运用,是一个 RESTful 的结构,首要规划创意来源于 tornado、sinatra 和 flask 这三个结构,可是结合了 Go 自身的一些特性(interface、struct 嵌入等)而规划的一个结构;MVC架构如下:详细内容会在后边部分给出,先从Config进口

关于config
首先要搞清楚一点Config在整个beego结构承当的使命:供给Ini、yaml、xml、json、env等办法参数装备,并供给了不同config办法的接口。
config构成
config对应beego项目的config包构成触及了env(env装备)、xml(xml装备)、yaml(yaml装备)、ini(ini装备,也是默许办法)、json(json装备办法)、fake(假造装备类)、config(装备功用接口)
2.分析
2.1 Configer接口
该接口界说怎么从装备原始数据获取或设置内容相关标准行为,而完结终究的操作是由详细完结Configer接口的详细Configer类来完结的。
IniConfigContainer:ini装备类
JSONConfigContainer:json装备类
ConfigContainer:yaml装备类
ConfigContainer:xml装备类
关于env是没有装备类, 实际上env只需求get、set操作即可: env-key:env-value方法
前面四种装备类选用的相似database/sql形式,界说两部分接口装备文件的Parse(见Config接口部分)和Operation,其详细完结装备类则依据自己的实际情况完结对应的接口(后边会有专门的源码来进行阐明)

2.2 Config接口
该接口界说对应的装备文件的Parse操作,首要经过解析装备文件获取其原始数据内容绑定到Configer,可进行Configer里边的get、set等操作
2.3 Register办法
该办法首要完结将不同装备类型及其详细装备完结类进行注册,能够在本地进行缓存,便于运用。经过map[string]Config来寄存。
2.4 NewConfig办法和NewConfigData办法
新建Config:榜首个依据filepath来新建,第二种直接依据文件内容来新建;其间榜首个参数adapterName包含:ini/json/xml/yaml
2.5 源码
接口界说
装备文件操作

type Configer interface {
       // 添加
    Set(key, val string) error   //support section::key type in given key when using ini type.
       //support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
       // 获取
    String(key string) string  
       // 可能会存在某个key有多个值
    Strings(key string) []string //get string slice
       //  依据value详细的类型进行转化
    Int(key string) (int, error)
    Int64(key string) (int64, error)
    Bool(key string) (bool, error)
    Float(key string) (float64, error)
      // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
    DefaultString(key string, defaultVal string) string     
    DefaultStrings(key string, defaultVal []string) []string //get string slice
    DefaultInt(key string, defaultVal int) int
    DefaultInt64(key string, defaultVal int64) int64
    DefaultBool(key string, defaultVal bool) bool
    DefaultFloat(key string, defaultVal float64) float64
    DIY(key string) (interface{}, error)
    GetSection(section string) (map[string]string, error)
    SaveConfigFile(filename string) error
}

解析装备文件
type Config interface {
    Parse(key string) (Configer, error)
    ParseData(data []byte) (Configer, error)
}

注册装备解析类
// 对应的Config完结类不能被注入两次或许adapter不能为nil 不然都会触发panic
func Register(name string, adapter Config) {
    if adapter == nil {
        panic( quot;config: Register adapter is nil quot;)
    }
    if _, ok := adapters[name]; ok {
        panic( quot;config: Register called twice for adapter quot; + name)
    }
    adapters[name] = adapter
}

创立Config
// adapter姓名和对应的装备文件 创立对应的Configer
func NewConfig(adapterName, filename string) (Configer, error) {
    adapter, ok := adapters[adapterName]
    if !ok {
        return nil, fmt.Errorf( quot;config: unknown adaptername %q (forgotten import?) quot;, adapterName)
    }
    return adapter.Parse(filename)
}
// 依据装备文件的内容及对应adapter来创立Configer
func NewConfigData(adapterName string, data []byte) (Configer, error) {
    adapter, ok := adapters[adapterName]
    if !ok {
        return nil, fmt.Errorf( quot;config: unknown adaptername %q (forgotten import?) quot;, adapterName)
    }
    return adapter.ParseData(data)
}

获取环境变量的值
条件:
// 若是value的{env} quot; , quot;{env||defaultValue} quot; , quot;defaultvalue quot;
// 比如:
//  v1 := config.ExpandValueEnv( quot;${GOPATH} quot;)         //回来环境变量GOPATH的值.
// v2 := config.ExpandValueEnv( quot;${GOAsta||/usr/local/go} quot;) // 环境变量内容为空或没有则输出默许值 quot;/usr/local/go/ quot;.
// v3 := config.ExpandValueEnv( quot;Astaxie quot;)               // 直接回来默许值 quot;Astaxie quot;.
func ExpandValueEnv(value string) (realValue string) {
    realValue = value

    vLen := len(value)
    // 3 = ${}
    if vLen 3 {   // 直接回来默许值
        return
    }
    // 需求以 quot;${ quot;开端并以 quot;} quot;完毕, 不然直接回来默许值.
    if value[0] != $ || value[1] != { || value[vLen-1] != } {
        return
    }

    key := quot; quot;
    defaultV := quot; quot;
    // value start with quot;${ quot;
    for i := 2; i vLen; i++ {  // 是否指定默许值:  key||value
        if value[i] == | amp; amp; (i+1 vLen amp; amp; value[i+1] == |) {
            key = value[2:i]                        // key
            defaultV = value[i+2 : vLen-1] // 默许值 
            break
        } else if value[i] == } {  // 没有默许值
            key = value[2:i]
            break
        }
    }

    realValue = os.Getenv(key)  // 获取对应的环境变量的值
    if realValue == quot; quot; {                // 若是对应的环境变量值为空 则回来默许值
        realValue = defaultV
    }
    return
}

接下来挑选一种装备文件格局进行解说 默许运用ini格局,就拿IniConfig来说吧
// IniConfig完结Config来解析 ini 格局文件.
type IniConfig struct {}

// 依据装备文件名创立一个新的Config并解析文件
// 详细解析操作在办法parseFile完结
func (ini *IniConfig) Parse(name string) (Configer, error) {
    return ini.parseFile(name)
}
// 读取文件内容 运用parseData来完结实在的解析操作,
// 换句话说Parse和ParseData两个办法底层操作都相同的
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
    data, err := ioutil.ReadFile(name)  // 读取文件内容
    if err != nil {
        return nil, err
    }

    return ini.parseData(filepath.Dir(name), data)
}

// 完结装备文件的解析
// ini文件格局: 节、键、值组成
//     ; 装备文件的内容      = 注释
//     [Section1 Name]      = 节
//     KeyName1=value1      = 键、值
func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
    cfg := amp;IniConfigContainer{                                      // 初始化装备类
        data:           make(map[string]map[string]string), // 数据
        sectionComment: make(map[string]string),         //
        keyComment:     make(map[string]string),
        RWMutex:        sync.RWMutex{},
    }
    cfg.Lock()
    defer cfg.Unlock()

    var comment bytes.Buffer
    buf := bufio.NewReader(bytes.NewBuffer(data))   // 读取数据
    // check the BOM
    head, err := buf.Peek(3)  // 查看BOM
    if err == nil amp; amp; head[0] == 239 amp; amp; head[1] == 187 amp; amp; head[2] == 191 {
        for i := 1; i = 3; i++ {
            buf.ReadByte()
        }
    }
    section := defaultSection  // 节
    for {
        line, _, err := buf.ReadLine()  // 解析每行内容
        if err == io.EOF {
            break
        }
        // 直接回来反常比再做处理要好
        if _, ok := err.(*os.PathError); ok {
            return nil, err
        }
        line = bytes.TrimSpace(line)   // 去掉空白符
        if bytes.Equal(line, bEmpty) {
            continue
        }
        var bComment []byte   // 注解
        switch {
        case bytes.HasPrefix(line, bNumComment):
            bComment = bNumComment
        case bytes.HasPrefix(line, bSemComment):
            bComment = bSemComment
        }
        if bComment != nil {   // 获取注解的内容
            line = bytes.TrimLeft(line, string(bComment))
            // Need append to a new line if multi-line comments.
            if comment.Len() 0 {
                comment.WriteByte(\n)
            }
            comment.Write(line)
            continue
        }

        if bytes.HasPrefix(line, sectionStart) amp; amp; bytes.HasSuffix(line, sectionEnd) {   // 节内容
            section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
            if comment.Len() 0 {
                cfg.sectionComment[section] = comment.String()
                comment.Reset()
            }
            if _, ok := cfg.data[section]; !ok {
                cfg.data[section] = make(map[string]string)
            }
            continue
        }

        if _, ok := cfg.data[section]; !ok {
            cfg.data[section] = make(map[string]string)
        }
        keyValue := bytes.SplitN(line, bEqual, 2)

        key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
        key = strings.ToLower(key)

        // handle include quot;other.conf quot;
        if len(keyValue) == 1 amp; amp; strings.HasPrefix(key, quot;include quot;) {  // 内容

            includefiles := strings.Fields(key)
            if includefiles[0] == quot;include quot; amp; amp; len(includefiles) == 2 {  // 特别处理include

                otherfile := strings.Trim(includefiles[1], quot;\ quot; quot;)
                if !filepath.IsAbs(otherfile) {
                    otherfile = filepath.Join(dir, otherfile)
                }

                i, err := ini.parseFile(otherfile)
                if err != nil {
                    return nil, err
                }

                for sec, dt := range i.data {
                    if _, ok := cfg.data[sec]; !ok {
                        cfg.data[sec] = make(map[string]string)
                    }
                    for k, v := range dt {
                        cfg.data[sec][k] = v   // 得到ini文件数据
                    }
                }

                for sec, comm := range i.sectionComment {
                    cfg.sectionComment[sec] = comm  // 节内容
                }

                for k, comm := range i.keyComment {
                    cfg.keyComment[k] = comm   // key内容
                }

                continue
            }
        }

        if len(keyValue) != 2 {
            return nil, errors.New( quot;read the content error: \ quot; quot; + string(line) + quot;\ quot;, should key = val quot;)
        }
        val := bytes.TrimSpace(keyValue[1])
        if bytes.HasPrefix(val, bDQuote) {
            val = bytes.Trim(val, ` quot;`)
        }

        cfg.data[section][key] = ExpandValueEnv(string(val))
        if comment.Len() 0 {
            cfg.keyComment[section+ quot;. quot;+key] = comment.String()
            comment.Reset()
        }

    }
    return cfg, nil   // 回来对应的Configer
}

// ParseData parse ini the data
func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
    dir := quot;beego quot;
    currentUser, err := user.Current()
    if err == nil {
        dir = quot;beego- quot; + currentUser.Username
    }
    dir = filepath.Join(os.TempDir(), dir)
    if err = os.MkdirAll(dir, os.ModePerm); err != nil {
        return nil, err
    }

    return ini.parseData(dir, data)
}

其实上述的源码能够看到经过完结config.go文件中的Config接口办法即以为当时的struct是其详细完结类,一起也将Config接口办法按需完结并不一定需求提前来完结增强Config的可扩展性(相似Java内置的SPI机制),也将装备文件与其详细操作类相关起来了Configer(解析办法回来的成果是便是详细装备操作完结类)
接下来看下装备完结类还是以Ini装备文件为主
// Ini装备文件详细类,一般装备完结类具有data(装备文件内容)、RWMutex(确保读写goroutine安全的)即可
//  其他新增的字段多半是该装备文件专属特征
// Ini文件
// ;这是一个比如
// [Category]           
// count=5
// Default=2
// Category0=2|4|0|已下载|C:\QQDownload|0
type IniConfigContainer struct {
    data           map[string]map[string]string // section= key:val
    sectionComment map[string]string            // section : comment
    keyComment     map[string]string            // id: []{comment, key...}; id 1 is for main comment.
    sync.RWMutex
}

// 办法1、依据key 获取对应的value并将value依照其实在的类型回来
// 办法2、在办法1的基础上 添加了默许值 当没有回来成果时 则运用默许值来填充 而不是像办法1输出error

// 依据指定的key 回来一个bool值,解析过错时会触发输出一个error; 正常成果回来则err=nil
// Bool returns the boolean value for a given key.
func (c *IniConfigContainer) Bool(key string) (bool, error) {
    return ParseBool(c.getdata(key))
}

// 依据指定的key回来一个bool值,呈现error!=nil时 则运用默许值来填充
// DefaultBool returns the boolean value for a given key.
// if err != nil return defaultval
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
    v, err := c.Bool(key)
    if err != nil {
        return defaultval
    }
    return v
}

// 依据指定key回来对应的Int值, 当不能正常输出int值,则对应的给出error
// Int returns the integer value for a given key.
func (c *IniConfigContainer) Int(key string) (int, error) {
    return strconv.Atoi(c.getdata(key))
}

// 依据给定的key回来对应的值 若是处理进程呈现error 则输出默许值
// DefaultInt returns the integer value for a given key.
// if err != nil return defaultval
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
    v, err := c.Int(key)
    if err != nil {
        return defaultval
    }
    return v
}

// Int64 returns the int64 value for a given key.
func (c *IniConfigContainer) Int64(key string) (int64, error) {
    return strconv.ParseInt(c.getdata(key), 10, 64)
}

// DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaultval
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
    v, err := c.Int64(key)
    if err != nil {
        return defaultval
    }
    return v
}

// Float returns the float value for a given key.
func (c *IniConfigContainer) Float(key string) (float64, error) {
    return strconv.ParseFloat(c.getdata(key), 64)
}

// DefaultFloat returns the float64 value for a given key.
// if err != nil return defaultval
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
    v, err := c.Float(key)
    if err != nil {
        return defaultval
    }
    return v
}

// String returns the string value for a given key.
func (c *IniConfigContainer) String(key string) string {
    return c.getdata(key)
}

// DefaultString returns the string value for a given key.
// if err != nil return defaultval
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
    v := c.String(key)
    if v == quot; quot; {
        return defaultval
    }
    return v
}

// 回来指定key对应的[]string(slice) 当值不存在或为空 则成果=nil
// Strings returns the []string value for a given key.
// Return nil if config value does not exist or is empty.
func (c *IniConfigContainer) Strings(key string) []string {
    v := c.String(key)
    if v == quot; quot; {
        return nil
    }
    return strings.Split(v, quot;; quot;)  // 运用;切割value的内容
}

// // 回来指定key对应的[]string(slice) 当成果=nil 则运用指定的默许值来填充
// DefaultStrings returns the []string value for a given key.
// if err != nil return defaultval
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
    v := c.Strings(key)
    if v == nil {
        return defaultval
    }
    return v
}

// 依据给定ini中的节 获取其内容成果是一个map
// 若是对应的内容不存在 则输出nil和error(“not exist section”)
// GetSection returns map for the given section
func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
    if v, ok := c.data[section]; ok {
        return v, nil
    }
    return nil, errors.New( quot;not exist section quot;)
}

// 将装备内容寄存到文件中
// 留意:环境变量会以实在成果寄存
// SaveConfigFile save the config into file.
//
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function.
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
    // Write configuration file by filename.
    f, err := os.Create(filename)   // 创立对应的装备文件
    if err != nil {
        return err
    }
    defer f.Close()  // 确保文件保存完毕后 收回对应的资源

    // Get section or key comments. Fixed #1607 
       // 获取节和key的注释内容
    getCommentStr := func(section, key string) string {
        var (
            comment string   // 注释内容
            ok      bool
        )
        if len(key) == 0 {   // 当没key时  直接获取对应section节的注释
            comment, ok = c.sectionComment[section]
        } else {           // 当有key时 需求依据section(节).key的格局获取对应key的注释
            comment, ok = c.keyComment[section+ quot;. quot;+key]
        }

        if ok {   // 获取到对应的注释 需求对注释实在的内容进行验证:空内容、空白符
            // Empty comment 
            if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
                return string(bNumComment)
            }
            prefix := string(bNumComment)
            // Add the line head character quot;# quot;
            return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
        }
        return quot; quot;
    }
        // 运用buffer进步内容读取
    buf := bytes.NewBuffer(nil)
       // 保存默许section(节)在榜首行
    // Save default section at first place
    if dt, ok := c.data[defaultSection]; ok { // 获取默许节的内容
        for key, val := range dt {    // 遍历该section节下的内容
            if key != quot; quot; {     // 当key存在时
                // Write key comments. 
                                // 写key相关的注释
                if v := getCommentStr(defaultSection, key); len(v) 0 {
                    if _, err = buf.WriteString(v + lineBreak); err != nil {
                        return err
                    }
                }
                               // 写key和value:相似key=value
                // Write key and value.
                if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
                    return err
                }
            }
        }

                // 当该section(节)内容写完 则需求添加一个空白行 以切割
        // Put a line between sections.
        if _, err = buf.WriteString(lineBreak); err != nil {
            return err
        }
    }
        // 保存section和data
       // 将每个section(节)及其内容、注释别离写入
    // Save named sections
    for section, dt := range c.data {
        if section != defaultSection {
            // Write section comments.
            if v := getCommentStr(section, quot; quot;); len(v) 0 {
                if _, err = buf.WriteString(v + lineBreak); err != nil {
                    return err
                }
            }

            // Write section name.
            if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
                return err
            }

            for key, val := range dt {
                if key != quot; quot; {
                    // Write key comments.
                    if v := getCommentStr(section, key); len(v) 0 {
                        if _, err = buf.WriteString(v + lineBreak); err != nil {
                            return err
                        }
                    }

                    // Write key and value.
                    if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
                        return err
                    }
                }
            }
                       
            // Put a line between sections.
            if _, err = buf.WriteString(lineBreak); err != nil {
                return err
            }
        }
    }
    _, err = buf.WriteTo(f)
    return err
}

// 写入一个key-value
// 若是给指定的section(节)写入内容 则对应的key:  section::key
// 留意:一旦给定section不存在时 则会触发panic
// Set writes a new value for key.
// if write to one section, the key need be quot;section::key quot;.
// if the section is not existed, it panics.
func (c *IniConfigContainer) Set(key, value string) error {
    c.Lock()
    defer c.Unlock()
    if len(key) == 0 {
        return errors.New( quot;key is empty quot;)
    }

    var (
        section, k string
        sectionKey = strings.Split(strings.ToLower(key), quot;:: quot;)
    )

    if len(sectionKey) = 2 {
        section = sectionKey[0]
        k = sectionKey[1]
    } else {
        section = defaultSection
        k = sectionKey[0]
    }

    if _, ok := c.data[section]; !ok {
        c.data[section] = make(map[string]string)
    }
    c.data[section][k] = value
    return nil
}

// 回来指定key的原数据
// DIY returns the raw value by a given key.
func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
    if v, ok := c.data[strings.ToLower(key)]; ok {
        return v, nil
    }
    return v, errors.New( quot;key not find quot;)
}

// section.key or key
func (c *IniConfigContainer) getdata(key string) string {
    if len(key) == 0 {
        return quot; quot;
    }
    c.RLock()
    defer c.RUnlock()

    var (
        section, k string
        sectionKey = strings.Split(strings.ToLower(key), quot;:: quot;)
    )
    if len(sectionKey) = 2 {
        section = sectionKey[0]
        k = sectionKey[1]
    } else {
        section = defaultSection
        k = sectionKey[0]
    }
    if v, ok := c.data[section]; ok {
        if vv, ok := v[k]; ok {
            return vv
        }
    }
    return quot; quot;
}

其实能够看到对应的Configer关于装备文件的操作类 相关行为比较简单,在完结上也坚持和Config相同的可扩展性,当某个strcut完结了Configer接口的办法即以为是其详细的完结。
关于json、xml、yaml根本与ini装备文件完结相似,在这里就不逐个解说,关于环境变量办法相对比较简单,不过又不同于前面几种装备办法,只需求完结get、set操作即可: set key = value, get key.
运用
func main() {
    ini := new(config.IniConfig)
    cnf, err := ini.Parse(FILE_PATH) // FILE_PATH见下面的装备文件
    if err != nil{
        log.Fatal(err)
    }
    fmt.Println(cnf.String( quot;dbinfo::name quot;))
    fmt.Println(cnf.String( quot;name quot;))   // 不能获取到内容 未指定对应的section(节)
    fmt.Println(cnf.Int( quot;httpport quot;))
    fmt.Println(cnf.GetSection( quot;dbinfo quot;))  // 获取指定section(节)的内容
    fmt.Println(cnf.DIY( quot;default quot;))           // IniConfiger内部data: map[section]map[key]value 而此处的key其实便是section
}

======= 装备文件========
app=app
#comment one
#comment two
# comment three
appname=beeapi
httpport=8080

;DB Info, enable db
# DB Info
# enable db
[dbinfo]
# db type name
# suport mysql,sqlserver
name=mysql


         
       

 

版权声明
本文来源于网络,版权归原作者所有,其内容与观点不代表凯发娱乐立场。转载文章仅为传播更有价值的信息,如采编人员采编有误或者版权原因,请与我们联系,我们核实后立即修改或删除。

猜您喜欢的文章