在Golang中将会话保存到MongoDB。

huangapple go评论103阅读模式
英文:

save session in mongodb golang

问题

我正在使用Golang作为后端,并且MongoDB是我的数据库。
我想要将Web应用程序的用户会话(在登录和注销之间)存储在MongoDB中以实现持久化。由于只有适用于MySQL而不是MongoDB的提供程序可用,因此我对其进行了编辑以支持MongoDB。但是,当我尝试使用它时,我得到了无效的内存地址或空指针解引用的错误。
以下是代码,请告诉我是否有更好的编码方式。谢谢。

type (
	SessionStore struct {
		c      *mgo.Session
		sid    string
		lock   sync.RWMutex
		values map[interface{}]interface{}
	}
)

var mgopder = &Provider{}

func (st *SessionStore) Set(key, value interface{}) error {
	st.lock.Lock()
	defer st.lock.Unlock()
	st.values[key] = value
	return nil
}

// 从MongoDB会话中获取值
func (st *SessionStore) Get(key interface{}) interface{} {
	st.lock.RLock()
	defer st.lock.RUnlock()
	if v, ok := st.values[key]; ok {
		return v
	}
	return nil
}

// 从MongoDB会话中删除值
func (st *SessionStore) Delete(key interface{}) error {
	st.lock.Lock()
	defer st.lock.Unlock()
	delete(st.values, key)
	return nil
}

// 清除MongoDB会话中的所有值
func (st *SessionStore) Flush() error {
	st.lock.Lock()
	defer st.lock.Unlock()
	st.values = make(map[interface{}]interface{})
	return nil
}

// 获取此MongoDB会话存储的会话ID
func (st *SessionStore) SessionID() string {
	return st.sid
}

// 将MongoDB会话值保存到数据库中。
// 必须调用此方法将值保存到数据库中。
func (st *SessionStore) SessionRelease(w http.ResponseWriter) {
	defer st.c.Close()
	b, err := session.EncodeGob(st.values)
	if err != nil {
		return
	}
	st.c.DB("Employee").C("Sessions").Update(nil, bson.M{"$set": bson.M{
		"session_data":   b,
		"session_expiry": time.Now().Unix(),
		"session_key":    st.sid,
	}},
	)

	/*st.c.Exec("UPDATE "+TableName+" set `session_data`=?, `session_expiry`=? where session_key=?",
	b, time.Now().Unix(), st.sid)*/
}

type Provider struct {
	maxlifetime int64
	savePath    string
	Database    string
}

// 连接到MongoDB
func (mp *Provider) connectInit() *mgo.Session {
	ds, err := mgo.Dial("Employee")
	if err != nil {
		return nil
	}
	return ds
}

// 初始化MongoDB会话。
// savepath是MongoDB的连接字符串
func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error {
	mp.maxlifetime = maxlifetime
	mp.savePath = savePath
	mp.Database = "Employee"
	return nil
}

// 通过sid获取MongoDB会话
func (mp *Provider) SessionRead(sid string) (session.Store, error) {
	var sessiondata []byte
	ds := mp.connectInit()
	defer ds.Close()
	c := ds.DB(mp.Database).C("Session")
	err := c.Find(bson.M{
		"session_key": sid,
	}).Select(bson.M{"session_data": 1}).All(&sessiondata)
	if err != nil {
		if err.Error() == "not found" {
			c.Insert(bson.M{
				"session_key":    sid,
				"session_data":   " ",
				"session_expiry": time.Now().Unix(),
			})
		}
	}

	var kv map[interface{}]interface{}
	if len(sessiondata) == 0 {
		kv = make(map[interface{}]interface{})
	} else {
		kv, err = session.DecodeGob(sessiondata)
		if err != nil {
			return nil, err
		}
	}
	rs := &SessionStore{c: ds, sid: sid, values: kv}
	return rs, nil
}

// 检查MongoDB会话是否存在
func (mp *Provider) SessionExist(sid string) bool {
	var sessiondata []byte
	ds := mp.connectInit()
	defer ds.Close()
	c := ds.DB("Employee").C("Sessions")
	err := c.Find(bson.M{
		"session_key": sid,
	}).Select(bson.M{
		"session_data": 1,
	}).One(&sessiondata)
	if err != nil {
		if err.Error() == "not found" {
			return false
		}
	}
	return true

}

// 为MySQL会话生成新的sid
func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
	var sessiondata []byte
	ds := mp.connectInit()
	defer ds.Close()
	c := ds.DB("Employee").C("Sessions")
	err := c.Find(bson.M{
		"session_key": oldsid,
	}).Select(bson.M{
		"session_data": 1,
	}).One(&sessiondata)
	if err != nil {
		if err.Error() == "not found" {
			c.Insert(bson.M{
				"sessoin_key":    oldsid,
				"session_data":   " ",
				"session_expiry": time.Now().Unix(),
			})
		}
	}
	/*	row := c.QueryRow("select session_data from "+TableName+" where session_key=?",
	oldsid)

	err := row.Scan(&sessiondata)

	c.Update(bson.M{"sessoin_key": oldsid}, bson.M{
		"$set": bson.M{
			"session_key": sid,
		},
	})
	/*c.Exec("update "+TableName+" set `session_key`=? where session_key=?", sid, oldsid)
	 */
	var kv map[interface{}]interface{}
	if len(sessiondata) == 0 {
		kv = make(map[interface{}]interface{})
	} else {
		kv, err = session.DecodeGob(sessiondata)
		if err != nil {
			return nil, err
		}
	}
	rs := &SessionStore{c: ds, sid: sid, values: kv}
	return rs, nil
}

// 通过sid删除MySQL会话
func (mp *Provider) SessionDestroy(sid string) error {
	ds := mp.connectInit()
	defer ds.Close()
	c := ds.DB("Employee").C("Sessions")
	c.Remove(bson.M{
		"session_key": sid,
	})
	return nil
}

// 在MySQL会话中删除过期的值
func (mp *Provider) SessionGC() {
	ds := mp.connectInit()
	defer ds.Close()
	c := ds.DB("Employee").C("Sessions")
	c.Remove(bson.M{
		"session_expiry": bson.M{
			"$lt": time.Now().Unix() - mp.maxlifetime,
		},
	})
	return
}

// 计算MySQL会话中的值总数
func (mp *Provider) SessionAll() int {
	var total int
	ds := mp.connectInit()
	defer ds.Close()
	c := ds.DB("Employee").C("Sessions")
	total, err := c.Count()

	if err != nil {
		return 0
	}
	return total
}

func init() {
	session.Register("mongodb", mgopder)
}

错误信息:

panic: runtime error: invalid memory address or nil pointer dereference
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x6db254]
goroutine 6 [running]:
panic(0xa2f560, 0xc0820080b0)
C:/Go/src/runtime/panic.go:481 +0x3f4
gopkg.in/mgo%2ev2.(*Session).Close(0x0)
C:/Projects/Go/src/gopkg.in/mgo.v2/session.go:1612 +0x144
panic(0xa2f560, 0xc0820080b0)
C:/Go/src/runtime/panic.go:443 +0x4f7
gopkg.in/mgo%2ev2.(*Session).acquireSocket(0x0, 0xc082290000, 0x0, 0x0, 0x0)
C:/Projects/Go/src/gopkg.in/mgo.v2/session.go:4409 +0x4ba
gopkg.in/mgo%2ev2.(*Collection).writeOp(0xc082279f30, 0x8feb80, 0xc082326060, 0xc082326001, 0x0, 0x0, 0x0)
C:/Projects/Go/src/gopkg.in/mgo.v2/session.go:4604 +0xe7
gopkg.in/mgo%2ev2.(*Collection).Remove(0xc082279f30, 0x9d4700, 0xc082326030, 0x0, 0x0)
C:/Projects/Go/src/gopkg.in/mgo.v2/session.go:2586 +0x15c
sample/models.(*Provider).SessionGC(0xe2f5a0)
C:/Projects/Go/src/sample/models/model.go:234 +0x3dc
github.com/astaxie/beego/session.(*Manager).GC(0xc082258b20)
C:/Projects/Go/src/github.com/astaxie/beego/session/session.go:271 +0x48
created by github.com/astaxie/beego.registerSession
C:/Projects/Go/src/github.com/astaxie/beego/hooks.go:68 +0x31d
英文:

I m using golang in the back end and mongodb is my database.
I wanted to store the user session(betwen login and logout )of my web application in the mongodb for the persistence.Since there is provider available only for mysql not for mongodb,I edited it to support mongodb.But when i try to use it i m getting the invalid memory address or nil pointer dereference.
The code is as follows and please if there is any better way to code please let me know.Thanks

type (
SessionStore struct {
c      *mgo.Session
sid    string
lock   sync.RWMutex
values map[interface{}]interface{}
}
)
var mgopder = &Provider{}
func (st *SessionStore) Set(key, value interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
st.values[key] = value
return nil
}
// Get value from mongodb session
func (st *SessionStore) Get(key interface{}) interface{} {
st.lock.RLock()
defer st.lock.RUnlock()
if v, ok := st.values[key]; ok {
return v
}
return nil
}
// Delete value in mongodb session
func (st *SessionStore) Delete(key interface{}) error {
st.lock.Lock()
defer st.lock.Unlock()
delete(st.values, key)
return nil
}
// Flush clear all values in mongodb session
func (st *SessionStore) Flush() error {
st.lock.Lock()
defer st.lock.Unlock()
st.values = make(map[interface{}]interface{})
return nil
}
// SessionID get session id of this mongodb session store
func (st *SessionStore) SessionID() string {
return st.sid
}
// SessionRelease save mongodb session values to database.
// must call this method to save values to database.
func (st *SessionStore) SessionRelease(w http.ResponseWriter) {
defer st.c.Close()
b, err := session.EncodeGob(st.values)
if err != nil {
return
}
st.c.DB("Employee").C("Sessions").Update(nil, bson.M{"$set": bson.M{
"session_data":   b,
"session_expiry": time.Now().Unix(),
"session_key":    st.sid,
},
},
)
/*st.c.Exec("UPDATE "+TableName+" set `session_data`=?, `session_expiry`=? where session_key=?",
b, time.Now().Unix(), st.sid)*/
}
type Provider struct {
maxlifetime int64
savePath    string
Database    string
}
// connect to mongodb
func (mp *Provider) connectInit() *mgo.Session {
ds, err := mgo.Dial("Employee")
if err != nil {
return nil
}
return ds
}
// SessionInit init mongodb session.
// savepath is the connection string of mongodb
func (mp *Provider) SessionInit(maxlifetime int64, savePath string) error {
mp.maxlifetime = maxlifetime
mp.savePath = savePath
mp.Database = "Employee"
return nil
}
// SessionRead get mysql session by sid
func (mp *Provider) SessionRead(sid string) (session.Store, error) {
var sessiondata []byte
ds := mp.connectInit()
defer ds.Close()
c := ds.DB(mp.Database).C("Session")
err := c.Find(bson.M{
"session_key": sid,
}).Select(bson.M{"session_data": 1}).All(&sessiondata)
if err != nil {
if err.Error() == "not found" {
c.Insert(bson.M{
"session_key":    sid,
"session_data":   " ",
"session_expiry": time.Now().Unix(),
})
}
}
var kv map[interface{}]interface{}
if len(sessiondata) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(sessiondata)
if err != nil {
return nil, err
}
}
rs := &SessionStore{c: ds, sid: sid, values: kv}
return rs, nil
}
// SessionExist check mongodb session exist
func (mp *Provider) SessionExist(sid string) bool {
var sessiondata []byte
ds := mp.connectInit()
defer ds.Close()
c := ds.DB("Employee").C("Sessions")
err := c.Find(bson.M{
"session_key": sid,
}).Select(bson.M{
"session_data": 1,
}).One(&sessiondata)
if err != nil {
if err.Error() == "not found" {
return false
}
}
return true
}
// SessionRegenerate generate new sid for mysql session
func (mp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
var sessiondata []byte
ds := mp.connectInit()
defer ds.Close()
c := ds.DB("Employee").C("Sessions")
err := c.Find(bson.M{
"session_key": oldsid,
}).Select(bson.M{
"session_data": 1,
}).One(&sessiondata)
if err != nil {
if err.Error() == "not found" {
c.Insert(bson.M{
"sessoin_key":    oldsid,
"session_data":   " ",
"session_expiry": time.Now().Unix(),
})
}
}
/*	row := c.QueryRow("select session_data from "+TableName+" where session_key=?", oldsid)
err := row.Scan(&sessiondata)
c.Update(bson.M{"sessoin_key": oldsid}, bson.M{
"$set": bson.M{
"session_key": sid,
},
})
/*c.Exec("update "+TableName+" set `session_key`=? where session_key=?", sid, oldsid)
*/
var kv map[interface{}]interface{}
if len(sessiondata) == 0 {
kv = make(map[interface{}]interface{})
} else {
kv, err = session.DecodeGob(sessiondata)
if err != nil {
return nil, err
}
}
rs := &SessionStore{c: ds, sid: sid, values: kv}
return rs, nil
}
// SessionDestroy delete mysql session by sid
func (mp *Provider) SessionDestroy(sid string) error {
ds := mp.connectInit()
defer ds.Close()
c := ds.DB("Employee").C("Sessions")
c.Remove(bson.M{
"session_key": sid,
}) 
return nil
}
// SessionGC delete expired values in mysql session
func (mp *Provider) SessionGC() {
ds := mp.connectInit()
defer ds.Close()
c := ds.DB("Employee").C("Sessions")
c.Remove(bson.M{
"session_expiry": bson.M{
"$lt": time.Now().Unix() - mp.maxlifetime,
},
})
return
}
// SessionAll count values in mysql session
func (mp *Provider) SessionAll() int {
var total int
ds := mp.connectInit()
defer ds.Close()
c := ds.DB("Employee").C("Sessions")
total, err := c.Count()
if err != nil {
return 0
}
return total
}
func init() {
session.Register("mongodb", mgopder)
}

Error:

panic: runtime error: invalid memory address or nil pointer dereference
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x6db254]
goroutine 6 [running]:
panic(0xa2f560, 0xc0820080b0)
C:/Go/src/runtime/panic.go:481 +0x3f4
gopkg.in/mgo%2ev2.(*Session).Close(0x0)
C:/Projects/Go/src/gopkg.in/mgo.v2/session.go:1612 +0x144
panic(0xa2f560, 0xc0820080b0)
C:/Go/src/runtime/panic.go:443 +0x4f7
gopkg.in/mgo%2ev2.(*Session).acquireSocket(0x0, 0xc082290000, 0x0, 0x0, 0x0)
C:/Projects/Go/src/gopkg.in/mgo.v2/session.go:4409 +0x4ba
gopkg.in/mgo%2ev2.(*Collection).writeOp(0xc082279f30, 0x8feb80, 0xc082326060, 0xc082326001, 0x0, 0x0, 0x0)
C:/Projects/Go/src/gopkg.in/mgo.v2/session.go:4604 +0xe7
gopkg.in/mgo%2ev2.(*Collection).Remove(0xc082279f30, 0x9d4700, 0xc082326030, 0x0, 0x0)
C:/Projects/Go/src/gopkg.in/mgo.v2/session.go:2586 +0x15c
sample/models.(*Provider).SessionGC(0xe2f5a0)
C:/Projects/Go/src/sample/models/model.go:234 +0x3dc
github.com/astaxie/beego/session.(*Manager).GC(0xc082258b20)
C:/Projects/Go/src/github.com/astaxie/beego/session/session.go:271 +0x48
created by github.com/astaxie/beego.registerSession
C:/Projects/Go/src/github.com/astaxie/beego/hooks.go:68 +0x31d

答案1

得分: 1

这是我通常做的事情。

package mongo

import (
	"time"

	"gopkg.in/mgo.v2"
)

// DataStore 包含指向 mgo 会话的指针
type DataStore struct {
	Session *mgo.Session
}

// ConnectToTagserver 是一个连接到 pubgears 的 tagserver 数据库的辅助方法
func (ds *DataStore) ConnectToTagserver() {
	mongoDBDialInfo := &mgo.DialInfo{
		Addrs:    []string{"some IP"},
		Timeout:  60 * time.Second,
		Database: "some db",
	}
	sess, err := mgo.DialWithInfo(mongoDBDialInfo)
	if err != nil {
		panic(err)
	}
	sess.SetMode(mgo.Monotonic, true)
	ds.Session = sess
}

// Close 是一个确保会话正确终止的辅助方法
func (ds *DataStore) Close() {
	ds.Session.Close()
}

然后在我的 models 包中,我会这样做:

package models

import (
	"./mongo"
	"gopkg.in/mgo.v2/bson"
)

// AdSize 表示通过查询 mongo 的 account 集合返回的数据对象结构
type AdSize struct {
	ID        bson.ObjectId `bson:"_id,omitempty"`
	Providers []string      `bson:"providers"`
	Size      string        `bson:"size"`
}

// GetAllAdsizes 是一个辅助函数,用于检索 adsize 集合中的所有对象
func GetAllAdsizes() ([]AdSize, error) {
	ds := mongo.DataStore{}
	ds.ConnectToTagserver()
	defer ds.Close()
	adSizes := []AdSize{}
	adSizeCollection := ds.Session.DB("some database").C("some collection")
	err := adSizeCollection.Find(bson.M{}).Sort("name").All(&adSizes)
	return adSizes, err
}

所以我在 mongo 文件中创建了一个会话包装器,然后在 models 文件中创建了一个会话对象,最后在某个路由文件中调用 GetAllAdsizes() 方法,该方法处理了我的 mongo 会话。会话会一直保持活动状态,直到 GetAllAdsizes() 方法结束,因为它在 defer 中关闭了。但是,类似这样的代码可以进行修改,您可以在处理完所有用户操作后关闭会话,例如用户注销时。另请参阅这里的链接:https://stackoverflow.com/questions/26574594/best-practice-to-maintain-a-mgo-session,您可以在其中看到类似类型的逻辑。

英文:

This is what i normally do.

package mongo
import (
"time"
"gopkg.in/mgo.v2"
)
// DataStore containing a pointer to a mgo session
type DataStore struct {
Session *mgo.Session
}
// ConnectToTagserver is a helper method that connections to pubgears' tagserver
// database
func (ds *DataStore) ConnectToTagserver() {
mongoDBDialInfo := &mgo.DialInfo{
Addrs:    []string{"some IP"},
Timeout:  60 * time.Second,
Database: "some db",
}
sess, err := mgo.DialWithInfo(mongoDBDialInfo)
if err != nil {
panic(err)
}
sess.SetMode(mgo.Monotonic, true)
ds.Session = sess
}
// Close is a helper method that ensures the session is properly terminated
func (ds *DataStore) Close() {
ds.Session.Close()
}

Then in my models package I do something like this

package models
import (
"./mongo"
"gopkg.in/mgo.v2/bson"
)
// AdSize represents the data object stucture that is returned by querying
// mongo's account collection
type AdSize struct {
ID        bson.ObjectId `bson:"_id,omitempty"`
Providers []string      `bson:"providers"`
Size      string        `bson:"size"`
}
// GetAllAdsizes is a helper function designed to retrieve all the objects in the
// adsize collection
func GetAllAdsizes() ([]AdSize, error) {
ds := mongo.DataStore{}
ds.ConnectToTagserver()
defer ds.Close()
adSizes := []AdSize{}
adSizeCollection := ds.Session.DB("some database").C("some collection")
err := adSizeCollection.Find(bson.M{}).Sort("name").All(&adSizes)
return adSizes, err
}

So I created a session wrapper in the mongo file, then create a session object in the models file, then lastly in some route file i call the method GetAllAdsizes(), which handled my mongo session. The session is keep alive until the the end of the GetAllAdsizes() method, as it was closed on defer. However something like this can be modified, where you handle all the user stuff and then close the session if user logs out. Also take a look here https://stackoverflow.com/questions/26574594/best-practice-to-maintain-a-mgo-session, where you can see a similar type of logic.

答案2

得分: 0

不确定你的代码,这里是一个创建并使用Go中的MongoDB会话的包文件示例。

package mydb

import (
   "fmt"
   "os"

   "gopkg.in/mgo.v2"
   "gopkg.in/mgo.v2/bson"
)

var Db *mgo.Session

type DbLink struct {
     DbName string
}

func NewDbSession(url string) {
    var e error
  
    Db, e = mgo.Dial(url)
  
    if e != nil {
        panic(e)
        os.Exit(-1)
    }
}

然后在你的主包中:

package main

import (
    "fmt"
    "os"

    "mydb"
)

func main() {
    // <user> 是访问数据库的用户名
    // <pwd> 是数据库用户的密码
    // <host> 是数据库服务器的主机名或IP地址(例如localhost)
    // <port> 是数据库服务器监听的端口(通常是27016)。
    mydb.NewDbSession("mongodb://<user>:<pwd>@<host>:<port>/?authSource=<db_name>")

    session := mydb.Db.Copy()
    defer session.Close()
 
    col := session.DB("my_db_schema").C("my_collection_name")

    // row 是一个依赖于集合的结构体列表
    var row []interface{}
    err := col.Find(bson.M{"field_name": "value"}).All(&row)
    if err != nil {
        fmt.Printf("%s\n", err)
    }
}

这是一个非常简单的示例,应该能帮助你入门。

请注意,MongoDB与MySQL非常不同,它是一个NoSQL无模式的数据库管理系统。

请参考mgo包的文档以获取详细信息。

你的用户会话结构体存在一些问题:

type SessionStore struct {
   c      *mgo.Session
   sid    string
   lock   sync.RWMutex
   values map[interface{}]interface{}
}

首先,你需要使用大写字母来导出键。

例如,不要使用c,而应该使用C,以此类推,SidLockValues也是如此。

type SessionStore struct {
   C      *mgo.Session                `json:"C" bson:"c"`
   Sid    string                      `json:"Sid" bson:"sid,omitempty"`
   Lock   sync.RWMutex                `json:"Lock" bson:"lock,omitempty"`
   Values map[interface{}]interface{} `json:"Values" bson:"values,omitempty"`
}

我不认为这会与指针一起工作。

英文:

Not sure about your code, here is a sample of a package file that create a session to mongodb in go and used it.

package mydb
import (
&quot;fmt&quot;
&quot;os&quot;
&quot;gopkg.in/mgo.v2&quot;
&quot;gopkg.in/mgo.v2/bson&quot;
)
var Db *mgo.Session
type DbLink struct {
DbName string
}
func NewDbSession(url string) {
var e error
Db, e = mgo.Dial(url)
if e != nil {
panic(e)
os.Exit(-1)
}
}

Then in your main package:

package main
import (
&quot;fmt&quot;
&quot;os&quot;
&quot;mydb&quot;
)
func main() {
// &lt;user&gt; the user to access the db
// &lt;pwd&gt; the database user password
// &lt;host&gt; hostname or ip address of the database server (localhost i.e.)
// &lt;port&gt; the port where the database server is listening (usually 27016).
mydb.NewDbSession(&quot;mongodb://&lt;user&gt;:&lt;pwd&gt;@&lt;host&gt;:&lt;port&gt;/?authSource=&lt;db_name&gt;&quot;)
session := mydb.Db.Copy()
defer session.Close()
col := session.DB(&quot;my_db_schema&quot;).C(&quot;my_collection_name&quot;)
// row is a list of struct depends on collection
err := col.Find(bson.M{&quot;field_name&quot;: &quot;value&quot;}).All(&amp;row)
if err != nil {
fmt.Printf(&quot;%s\n&quot;, err)
}
}

This is a very trivial example that should help you to start.

Consider that MongoDB it's very different from MySQL, as it is a NoSQL and schema less database management system.

Please refer to the documentation of the mgo package for details.

Your user session struct have some issues:

type SessionStore struct {
c      *mgo.Session
sid    string
lock   sync.RWMutex
values map[interface{}]interface{}
}

First you have to use capital letters to export the key.

For example instead of c you should use C, and so on with Sid, Lock and Values.

type SessionStore struct {
C      *mgo.Session                `json:&quot;C&quot; bson:&quot;c&quot;`
Sid    string                      `json:&quot;Sid&quot; bson:&quot;sid,omitempty&quot;`
Lock   sync.RWMutex                `json:&quot;Lock&quot; bson:&quot;lock,omitempty&quot;`
Values map[interface{}]interface{} `json:&quot;Values&quot; bson:&quot;values,omitempty&quot;`
}

I don't think that is going to work with pointers.

huangapple
  • 本文由 发表于 2016年9月22日 17:00:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/39634610.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定