mirror of
https://gitee.com/johng/gf
synced 2026-06-30 19:17:30 +08:00
增加gfcache包,改进gcfg/gview包
This commit is contained in:
3
TODO
3
TODO
@ -19,6 +19,7 @@ ghttp.Server的Cookie及Session锁机制优化(去掉map锁机制);
|
||||
ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps);
|
||||
ghttp路由功能增加分组路由特性;
|
||||
解决glog串日志情况;
|
||||
ghttp增加返回数据压缩机制;
|
||||
|
||||
|
||||
DONE:
|
||||
@ -53,4 +54,4 @@ DONE:
|
||||
29. gdb增加查询缓存特性;
|
||||
30. gpage分页增加对自定义后缀的支持,如:2.html, 2.php等等;
|
||||
31. gvalid包增加struct tag的校验规则、自定义错误提示信息绑定的支持特性;
|
||||
|
||||
32. 增加文件缓存包,可根据fsnotify机制进行缓存更新;
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"net/http"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/encoding/gparser"
|
||||
@ -17,46 +16,29 @@ import (
|
||||
"gitee.com/johng/gf/g/frame/gins"
|
||||
)
|
||||
|
||||
// 服务端请求返回对象
|
||||
// 服务端请求返回对象。
|
||||
// 注意该对象并没有实现http.ResponseWriter接口,而是依靠ghttp.ResponseWriter实现。
|
||||
type Response struct {
|
||||
ResponseWriter
|
||||
Server *Server
|
||||
Writer *ResponseWriter // io.Writer
|
||||
mu sync.RWMutex // 缓冲区互斥锁
|
||||
buffer []byte // 每个请求的返回数据缓冲区
|
||||
Writer *ResponseWriter // ResponseWriter的别名
|
||||
request *Request // 关联的Request请求对象
|
||||
}
|
||||
|
||||
// 自定义的ResponseWriter,用于写入流的控制
|
||||
type ResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
Status int // http status
|
||||
Length int // response length
|
||||
}
|
||||
|
||||
// 创建一个ghttp.Response对象指针
|
||||
func newResponse(s *Server, w http.ResponseWriter) *Response {
|
||||
r := &Response {
|
||||
Server : s,
|
||||
ResponseWriter : ResponseWriter{w, http.StatusOK, 0},
|
||||
ResponseWriter : ResponseWriter{
|
||||
ResponseWriter : w,
|
||||
Status : http.StatusOK,
|
||||
buffer : make([]byte, 0),
|
||||
},
|
||||
}
|
||||
r.Writer = &r.ResponseWriter
|
||||
return r
|
||||
}
|
||||
|
||||
// 覆盖父级的WriteHeader方法
|
||||
func (w *ResponseWriter) Write(buffer []byte) (int, error) {
|
||||
n, e := w.ResponseWriter.Write(buffer)
|
||||
w.Length += n
|
||||
return n, e
|
||||
}
|
||||
|
||||
// 覆盖父级的WriteHeader方法
|
||||
func (w *ResponseWriter) WriteHeader(code int) {
|
||||
w.Status = code
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// 返回信息,任何变量自动转换为bytes
|
||||
func (r *Response) Write(content ... interface{}) {
|
||||
if len(content) == 0 {
|
||||
@ -225,11 +207,5 @@ func (r *Response) ClearBuffer() {
|
||||
// 输出缓冲区数据到客户端
|
||||
func (r *Response) OutputBuffer() {
|
||||
r.Header().Set("Server", r.Server.config.ServerAgent)
|
||||
if len(r.buffer) > 0 {
|
||||
r.mu.Lock()
|
||||
r.ResponseWriter.Write(r.buffer)
|
||||
r.buffer = make([]byte, 0)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
r.Writer.OutputBuffer()
|
||||
}
|
||||
|
||||
45
g/net/ghttp/ghttp_response_gzip.go
Normal file
45
g/net/ghttp/ghttp_response_gzip.go
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package ghttp
|
||||
|
||||
// 默认的gzip压缩文件类型
|
||||
var defaultGzipContentTypes = []string{
|
||||
"application/atom+xml",
|
||||
"application/font-sfnt",
|
||||
"application/javascript",
|
||||
"application/json",
|
||||
"application/ld+json",
|
||||
"application/manifest+json",
|
||||
"application/rdf+xml",
|
||||
"application/rss+xml",
|
||||
"application/schema+json",
|
||||
"application/vnd.geo+json",
|
||||
"application/vnd.ms-fontobject",
|
||||
"application/x-font-ttf",
|
||||
"application/x-javascript",
|
||||
"application/x-web-app-manifest+json",
|
||||
"application/xhtml+xml",
|
||||
"application/xml",
|
||||
"font/eot",
|
||||
"font/opentype",
|
||||
"image/bmp",
|
||||
"image/svg+xml",
|
||||
"image/vnd.microsoft.icon",
|
||||
"image/x-icon",
|
||||
"text/cache-manifest",
|
||||
"text/css",
|
||||
"text/html",
|
||||
"text/javascript",
|
||||
"text/plain",
|
||||
"text/vcard",
|
||||
"text/vnd.rim.location.xloc",
|
||||
"text/vtt",
|
||||
"text/x-component",
|
||||
"text/x-cross-domain-policy",
|
||||
"text/xml",
|
||||
}
|
||||
43
g/net/ghttp/ghttp_response_writer.go
Normal file
43
g/net/ghttp/ghttp_response_writer.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 自定义的ResponseWriter,用于写入流的控制
|
||||
type ResponseWriter struct {
|
||||
http.ResponseWriter
|
||||
mu sync.RWMutex // 缓冲区互斥锁
|
||||
Status int // http status
|
||||
buffer []byte // 缓冲区内容
|
||||
}
|
||||
|
||||
// 覆盖父级的WriteHeader方法
|
||||
func (w *ResponseWriter) Write(buffer []byte) (int, error) {
|
||||
w.buffer = append(w.buffer, buffer...)
|
||||
return len(buffer), nil
|
||||
}
|
||||
|
||||
// 覆盖父级的WriteHeader方法
|
||||
func (w *ResponseWriter) WriteHeader(code int) {
|
||||
w.Status = code
|
||||
w.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
// 输出buffer数据到客户端
|
||||
func (w *ResponseWriter) OutputBuffer() {
|
||||
if len(w.buffer) > 0 {
|
||||
w.mu.Lock()
|
||||
w.ResponseWriter.Write(w.buffer)
|
||||
w.buffer = make([]byte, 0)
|
||||
w.mu.Unlock()
|
||||
}
|
||||
}
|
||||
@ -59,27 +59,31 @@ type ServerConfig struct {
|
||||
AllowIps []string // 仅允许访问的ip列表,支持ip前缀过滤,如: 10 将仅允许10开头的ip访问
|
||||
// 路由访问控制
|
||||
DenyRoutes []string // 不允许访问的路由规则列表
|
||||
// Gzip压缩文件类型
|
||||
GzipContentTypes []string // 允许进行gzip压缩的文件类型
|
||||
}
|
||||
|
||||
// 默认HTTP Server
|
||||
var defaultServerConfig = ServerConfig {
|
||||
Addr : "",
|
||||
HTTPSAddr : "",
|
||||
Handler : nil,
|
||||
ReadTimeout : 60 * time.Second,
|
||||
WriteTimeout : 60 * time.Second,
|
||||
IdleTimeout : 60 * time.Second,
|
||||
MaxHeaderBytes : 1024,
|
||||
IndexFiles : []string{"index.html", "index.htm"},
|
||||
IndexFolder : false,
|
||||
ServerAgent : "gf",
|
||||
ServerRoot : "",
|
||||
Addr : "",
|
||||
HTTPSAddr : "",
|
||||
Handler : nil,
|
||||
ReadTimeout : 60 * time.Second,
|
||||
WriteTimeout : 60 * time.Second,
|
||||
IdleTimeout : 60 * time.Second,
|
||||
MaxHeaderBytes : 1024,
|
||||
IndexFiles : []string{"index.html", "index.htm"},
|
||||
IndexFolder : false,
|
||||
ServerAgent : "gf",
|
||||
ServerRoot : "",
|
||||
|
||||
CookieMaxAge : gDEFAULT_COOKIE_MAX_AGE,
|
||||
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
|
||||
SessionIdName : gDEFAULT_SESSION_ID_NAME,
|
||||
CookieMaxAge : gDEFAULT_COOKIE_MAX_AGE,
|
||||
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
|
||||
SessionIdName : gDEFAULT_SESSION_ID_NAME,
|
||||
|
||||
ErrorLogEnabled : true,
|
||||
ErrorLogEnabled : true,
|
||||
|
||||
GzipContentTypes : defaultGzipContentTypes,
|
||||
}
|
||||
|
||||
// 获取默认的http server设置
|
||||
|
||||
@ -97,6 +97,12 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
request.Response.OutputBuffer()
|
||||
// 事件 - AfterOutput
|
||||
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
|
||||
|
||||
// gzip压缩处理
|
||||
encoding := request.Header.Get("Accept-Encoding")
|
||||
if encoding != "" {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化控制器
|
||||
|
||||
@ -26,7 +26,7 @@ func (s *Server) handleAccessLog(r *Request) {
|
||||
content := fmt.Sprintf(`"%s %s %s %s" %s %s`,
|
||||
r.Method, r.Host, r.URL.String(), r.Proto,
|
||||
gconv.String(r.Response.Status),
|
||||
gconv.String(r.Response.Length),
|
||||
gconv.String(r.Response.BufferLength()),
|
||||
)
|
||||
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
|
||||
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())
|
||||
|
||||
@ -25,7 +25,6 @@ type Config struct {
|
||||
name *gtype.String // 默认配置文件名称
|
||||
paths *gspath.SPath // 搜索目录路径
|
||||
jsons *gmap.StringInterfaceMap // 配置文件对象
|
||||
closed *gtype.Bool // 是否已经被close
|
||||
vc *gtype.Bool // 层级检索是否执行分隔符冲突检测(默认为false,检测会比较影响检索效率)
|
||||
}
|
||||
|
||||
@ -41,7 +40,6 @@ func New(path string, file...string) *Config {
|
||||
name : gtype.NewString(name),
|
||||
paths : s,
|
||||
jsons : gmap.NewStringInterfaceMap(),
|
||||
closed : gtype.NewBool(),
|
||||
vc : gtype.NewBool(),
|
||||
}
|
||||
}
|
||||
@ -251,17 +249,14 @@ func (c *Config) Reload() {
|
||||
c.jsons.Clear()
|
||||
}
|
||||
|
||||
// 关闭Config对象,自动关闭异步协程
|
||||
func (c *Config) Close() {
|
||||
c.closed.Set(true)
|
||||
}
|
||||
|
||||
// 添加文件监控
|
||||
func (c *Config) addMonitor(path string) {
|
||||
if c.jsons.Get(path) == nil {
|
||||
gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
// 删除文件内容缓存,下一次查询会自动更新
|
||||
c.jsons.Remove(event.Path)
|
||||
})
|
||||
// 防止多goroutine同时调用
|
||||
if c.jsons.Get(path) != nil {
|
||||
return
|
||||
}
|
||||
gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
// 删除文件内容缓存,下一次查询会自动更新
|
||||
c.jsons.Remove(event.Path)
|
||||
})
|
||||
}
|
||||
|
||||
61
g/os/gfcache/gfcache.go
Normal file
61
g/os/gfcache/gfcache.go
Normal file
@ -0,0 +1,61 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 文件缓存.
|
||||
package gfcache
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/os/gcache"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/os/gfsnotify"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
cap *gtype.Int // 缓存容量(byte),设置为0表示不限制
|
||||
size *gtype.Int // 缓存大小(Byte)
|
||||
cache *gcache.Cache // 缓存对象
|
||||
notify *gfsnotify.Watcher // 文件监控管理对象
|
||||
}
|
||||
|
||||
const (
|
||||
// 默认的缓存容量(不限制)
|
||||
gDEFAULT_CACHE_CAP = 0
|
||||
)
|
||||
|
||||
var (
|
||||
// 默认的文件缓存对象
|
||||
cache = New()
|
||||
)
|
||||
|
||||
func New(cap ... int) *Cache {
|
||||
c := gDEFAULT_CACHE_CAP
|
||||
if len(cap) > 0 {
|
||||
c = cap[0]
|
||||
}
|
||||
notify, _ := gfsnotify.New()
|
||||
return &Cache {
|
||||
cap : gtype.NewInt(c),
|
||||
size : gtype.NewInt(),
|
||||
cache : gcache.New(),
|
||||
notify : notify,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 获得已缓存的文件大小(byte)
|
||||
func GetSize() int {
|
||||
return cache.GetSize()
|
||||
}
|
||||
|
||||
// 获得文件内容 string
|
||||
func GetContents(path string) string {
|
||||
return cache.GetContents(path)
|
||||
}
|
||||
|
||||
// 获得文件内容 []byte
|
||||
func GetBinContents(path string) []byte {
|
||||
return cache.GetBinContents(path)
|
||||
}
|
||||
73
g/os/gfcache/gfcache_cache.go
Normal file
73
g/os/gfcache/gfcache_cache.go
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 文件缓存.
|
||||
package gfcache
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/gfsnotify"
|
||||
)
|
||||
|
||||
// 设置容量大小(MB)
|
||||
func (c *Cache) SetCap(cap int) {
|
||||
c.cap.Set(cap)
|
||||
}
|
||||
|
||||
// 获得缓存容量大小(byte)
|
||||
func (c *Cache) GetCap() int {
|
||||
return c.cap.Val()
|
||||
}
|
||||
|
||||
// 获得已缓存的文件大小(byte)
|
||||
func (c *Cache) GetSize() int {
|
||||
return c.size.Val()
|
||||
}
|
||||
|
||||
// 获得文件内容 string
|
||||
func (c *Cache) GetContents(path string) string {
|
||||
return string(c.GetBinContents(path))
|
||||
}
|
||||
|
||||
// 获得文件内容 []byte
|
||||
func (c *Cache) GetBinContents(path string) []byte {
|
||||
v := c.cache.Get(path)
|
||||
if v != nil {
|
||||
return v.([]byte)
|
||||
}
|
||||
b := gfile.GetBinContents(path)
|
||||
if b != nil && (c.cap.Val() == 0 || c.size.Val() < c.cap.Val()) {
|
||||
c.addMonitor(path)
|
||||
c.cache.Set(path, b, 0)
|
||||
c.size.Add(len(b))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// 添加文件监控
|
||||
func (c *Cache) addMonitor(path string) {
|
||||
// 防止多goroutine同时调用
|
||||
if c.cache.Get(path) != nil {
|
||||
return
|
||||
}
|
||||
c.notify.Add(path, func(event *gfsnotify.Event) {
|
||||
r := c.cache.Get(path).([]byte)
|
||||
// 是否删除
|
||||
if event.IsRemove() {
|
||||
c.cache.Remove(path)
|
||||
c.size.Add(-len(r))
|
||||
}
|
||||
// 更新缓存内容
|
||||
if c.cap.Val() == 0 || c.size.Val() < c.cap.Val() {
|
||||
b := gfile.GetBinContents(path)
|
||||
if b != nil {
|
||||
dif := len(b) - len(r)
|
||||
c.cache.Set(path, b, 0)
|
||||
c.size.Add(dif)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -28,16 +28,19 @@ import (
|
||||
|
||||
// 封装了常用的文件操作方法,如需更详细的文件控制,请查看官方os包
|
||||
|
||||
|
||||
// 文件分隔符
|
||||
const (
|
||||
Separator = string(filepath.Separator)
|
||||
)
|
||||
|
||||
// 源码的main包所在目录,仅仅会设置一次
|
||||
var mainPkgPath = gtype.NewString()
|
||||
var (
|
||||
// 源码的main包所在目录,仅仅会设置一次
|
||||
mainPkgPath = gtype.NewString()
|
||||
|
||||
// 编译时的 GOROOT 数值
|
||||
var goRootOfBuild = gtype.NewString()
|
||||
// 编译时的 GOROOT 数值
|
||||
goRootOfBuild = gtype.NewString()
|
||||
)
|
||||
|
||||
// 给定文件的绝对路径创建文件
|
||||
func Mkdir(path string) error {
|
||||
|
||||
@ -70,12 +70,14 @@ func (p *Pool) File() (*File, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭指针池
|
||||
func (p *Pool) Close() {
|
||||
// 关闭指针池(返回error是标准库io.ReadWriteCloser接口实现)
|
||||
func (p *Pool) Close() error {
|
||||
p.pool.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获得底层文件指针
|
||||
func (f *File) Close() {
|
||||
// 获得底层文件指针(返回error是标准库io.ReadWriteCloser接口实现)
|
||||
func (f *File) Close() error {
|
||||
f.pool.pool.Put(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -12,12 +12,11 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"html/template"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/encoding/ghash"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/os/gfsnotify"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
"gitee.com/johng/gf/g/os/gfcache"
|
||||
)
|
||||
|
||||
// 视图对象
|
||||
@ -25,7 +24,6 @@ type View struct {
|
||||
mu sync.RWMutex
|
||||
paths *gspath.SPath // 模板查找目录(绝对路径)
|
||||
funcmap map[string]interface{} // FuncMap
|
||||
contents *gmap.StringStringMap // 已解析的模板文件内容
|
||||
delimiters []string // 模板变量分隔符号
|
||||
}
|
||||
|
||||
@ -62,7 +60,6 @@ func New(path string) *View {
|
||||
view := &View {
|
||||
paths : s,
|
||||
funcmap : make(map[string]interface{}),
|
||||
contents : gmap.NewStringStringMap(),
|
||||
delimiters : make([]string, 2),
|
||||
}
|
||||
view.SetDelimiters("{{", "}}")
|
||||
@ -82,18 +79,12 @@ func (view *View) AddPath(path string) error {
|
||||
|
||||
// 解析模板,返回解析后的内容
|
||||
func (view *View) Parse(file string, params...map[string]interface{}) ([]byte, error) {
|
||||
path := view.paths.Search(file)
|
||||
content := view.contents.Get(path)
|
||||
if content == "" {
|
||||
content = gfile.GetContents(path)
|
||||
if content != "" {
|
||||
view.addMonitor(path)
|
||||
view.contents.Set(path, content)
|
||||
}
|
||||
}
|
||||
if content == "" {
|
||||
path := view.paths.Search(file)
|
||||
if path == "" {
|
||||
return nil, errors.New("tpl \"" + file + "\" not found")
|
||||
}
|
||||
content := gfcache.GetContents(path)
|
||||
|
||||
// 模板参数
|
||||
data := (map[string]interface{})(nil)
|
||||
if len(params) > 0 {
|
||||
@ -154,13 +145,3 @@ func (view *View) funcInclude(file string, data...map[string]interface{}) templa
|
||||
}
|
||||
return template.HTML(content)
|
||||
}
|
||||
|
||||
// 添加模板文件监控
|
||||
func (view *View) addMonitor(path string) {
|
||||
if view.contents.Get(path) == "" {
|
||||
gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
// 删除文件内容缓存,下一次查询会自动更新
|
||||
view.contents.Remove(event.Path)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
28
geg/os/gfcache/gfcache.go
Normal file
28
geg/os/gfcache/gfcache.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/os/gfcache"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := 0
|
||||
r := ""
|
||||
path := gfile.TempDir() + gfile.Separator + "temp"
|
||||
gfile.PutContents(path, "hello")
|
||||
|
||||
s = gfcache.GetSize()
|
||||
r = gfcache.GetContents(path)
|
||||
fmt.Println(s, r)
|
||||
|
||||
gfile.PutContentsAppend(path, " john")
|
||||
|
||||
// 等待1秒以便gfsnotify回调能处理完成
|
||||
time.Sleep(time.Second)
|
||||
|
||||
s = gfcache.GetSize()
|
||||
r = gfcache.GetContents(path)
|
||||
fmt.Println(s, r)
|
||||
}
|
||||
@ -1,11 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
type A struct {
|
||||
S string
|
||||
}
|
||||
|
||||
type B struct {
|
||||
A
|
||||
}
|
||||
|
||||
|
||||
func (a *A) editA () {
|
||||
a.S += "a"
|
||||
}
|
||||
|
||||
func (b *B) editB () {
|
||||
b.S += "b"
|
||||
}
|
||||
|
||||
func main() {
|
||||
methodMap := make(map[string]bool)
|
||||
methodMap["t"] = true
|
||||
fmt.Println(methodMap["t"])
|
||||
b := new(B)
|
||||
b.editA()
|
||||
b.editB()
|
||||
b.A.editA()
|
||||
fmt.Println(b.S)
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "0.99 beta"
|
||||
const VERSION = "0.99.682 beta"
|
||||
const AUTHORS = "john<john@johng.cn>"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user