本文整理汇总了Golang中github.com/keybase/go-framed-msgpack-rpc.NewServer函数的典型用法代码示例。如果您正苦于以下问题:Golang NewServer函数的具体用法?Golang NewServer怎么用?Golang NewServer使用的例子?那么恭喜您, 这里精选的函数代码示例或许可以为您提供帮助。
在下文中一共展示了NewServer函数的13个代码示例,这些例子默认根据受欢迎程度排序。您可以为喜欢或者感觉有用的代码点赞,您的评价将有助于我们的系统推荐出更棒的Golang代码示例。
示例1: connect
// connect performs the actual connect() and rpc setup.
func (c *Connection) connect(ctx context.Context) error {
c.log.Debug("Connection: dialing transport")
// connect
transport, err := c.transport.Dial(ctx)
if err != nil {
c.log.Warning("Connection: error dialing transport: %v", err)
return err
}
client := rpc.NewClient(transport, c.errorUnwrapper)
server := rpc.NewServer(transport, libkb.WrapError)
// call the connect handler
err = c.handler.OnConnect(ctx, c, client, server)
if err != nil {
c.log.Warning("Connection: error calling OnConnect handler: %v", err)
return err
}
// set the client for other callers.
// we wait to do this so the handler has time to do
// any setup required, e.g. authenticate.
c.mutex.Lock()
defer c.mutex.Unlock()
c.client = client
c.server = server
c.transport.Finalize()
c.log.Debug("Connection: connected")
return nil
}
开发者ID:gozes,项目名称:kbfs-beta,代码行数:33,代码来源:connection.go
示例2: Handle
func (d *Service) Handle(c net.Conn) {
xp := rpc.NewTransport(c, libkb.NewRPCLogFactory(), libkb.WrapError)
server := rpc.NewServer(xp, libkb.WrapError)
cl := make(chan error)
server.AddCloseListener(cl)
connID := d.G().NotifyRouter.AddConnection(xp, cl)
if err := d.RegisterProtocols(server, xp, connID, d.G()); err != nil {
d.G().Log.Warning("RegisterProtocols error: %s", err)
return
}
if d.isDaemon {
baseHandler := NewBaseHandler(xp)
logUI := LogUI{sessionID: 0, cli: baseHandler.getLogUICli()}
handle := d.G().Log.AddExternalLogger(&logUI)
defer d.G().Log.RemoveExternalLogger(handle)
}
if err := server.Run(false /* bg */); err != nil {
if err != io.EOF {
d.G().Log.Warning("Run error: %s", err)
}
}
}
开发者ID:stevefox,项目名称:client,代码行数:27,代码来源:main.go
示例3: Handle
func (d *Service) Handle(c net.Conn) {
xp := rpc.NewTransport(c, libkb.NewRPCLogFactory(), libkb.WrapError)
server := rpc.NewServer(xp, libkb.WrapError)
cl := make(chan error)
server.AddCloseListener(cl)
connID := d.G().NotifyRouter.AddConnection(xp, cl)
var logReg *logRegister
if d.isDaemon {
// Create a new log register object that the Log handler can use to
// register a logger. When this function finishes, the logger
// will be removed.
logReg = newLogRegister(d.logForwarder, d.G().Log)
defer logReg.UnregisterLogger()
}
if err := d.RegisterProtocols(server, xp, connID, logReg, d.G()); err != nil {
d.G().Log.Warning("RegisterProtocols error: %s", err)
return
}
if err := server.Run(false /* bg */); err != nil {
if err != io.EOF {
d.G().Log.Warning("Run error: %s", err)
}
}
d.G().Log.Debug("Handle() complete for connection %d", connID)
}
开发者ID:mattcurrycom,项目名称:client,代码行数:31,代码来源:main.go
示例4: GetRPCServer
func GetRPCServer(g *libkb.GlobalContext) (ret *rpc.Server, xp rpc.Transporter, err error) {
if _, xp, err = g.GetSocket(false); err == nil {
ret = rpc.NewServer(xp, libkb.WrapError)
}
if err != nil {
DiagnoseSocketError(g.UI, err)
}
return
}
开发者ID:polluks,项目名称:client,代码行数:9,代码来源:rpc.go
示例5: pickFirstConnection
func (p *provisioner) pickFirstConnection() (err error) {
// This connection is auto-closed at the end of this function, so if
// you don't want it to close, then set it to nil. See the first
// case in the select below.
var conn net.Conn
var xp rpc.Transporter
defer func() {
if conn != nil {
conn.Close()
}
}()
// Only make a channel if we were provided a secret to start it with.
// If not, we'll just have to wait for a message on p.arg.SecretChannel
// and use the provisionee's channel.
if len(p.arg.Secret) != 0 {
if conn, err = NewConn(p.arg.Mr, p.arg.Secret, p.deviceID, p.arg.Timeout); err != nil {
return err
}
prot := keybase1.Kex2ProvisionerProtocol(p)
xp = rpc.NewTransport(conn, p.arg.Provisioner.GetLogFactory(), nil)
srv := rpc.NewServer(xp, nil)
if err = srv.Register(prot); err != nil {
return err
}
if err = srv.Run(true); err != nil {
return err
}
}
select {
case <-p.start:
p.conn = conn
conn = nil // so it's not closed in the defer()'ed close
p.xp = xp
case sec := <-p.arg.SecretChannel:
if len(sec) != SecretLen {
return ErrBadSecret
}
if p.conn, err = NewConn(p.arg.Mr, sec, p.deviceID, p.arg.Timeout); err != nil {
return err
}
p.xp = rpc.NewTransport(p.conn, p.arg.Provisioner.GetLogFactory(), nil)
case <-p.arg.Ctx.Done():
err = ErrCanceled
case <-time.After(p.arg.Timeout):
err = ErrTimedOut
}
return
}
开发者ID:polluks,项目名称:client,代码行数:52,代码来源:provisioner.go
示例6: Handle
func (d *Service) Handle(c net.Conn) {
xp := rpc.NewTransport(c, libkb.NewRPCLogFactory(d.G()), libkb.WrapError)
server := rpc.NewServer(xp, libkb.WrapError)
cl := make(chan error, 1)
connID := d.G().NotifyRouter.AddConnection(xp, cl)
var logReg *logRegister
if d.isDaemon {
// Create a new log register object that the Log handler can use to
// register a logger. When this function finishes, the logger
// will be removed.
logReg = newLogRegister(d.logForwarder, d.G().Log)
defer logReg.UnregisterLogger()
}
shutdowners, err := d.RegisterProtocols(server, xp, connID, logReg, d.G())
var shutdownOnce sync.Once
shutdown := func() error {
shutdownOnce.Do(func() {
for _, shutdowner := range shutdowners {
shutdowner.Shutdown()
}
})
return nil
}
// Clean up handlers when the connection closes.
defer shutdown()
// Make sure shutdown is called when service shuts down but the connection
// isn't closed yet.
d.G().PushShutdownHook(shutdown)
if err != nil {
d.G().Log.Warning("RegisterProtocols error: %s", err)
return
}
// Run the server and wait for it to finish.
<-server.Run()
// err is always non-nil.
err = server.Err()
cl <- err
if err != io.EOF {
d.G().Log.Warning("Run error: %s", err)
}
d.G().Log.Debug("Handle() complete for connection %d", connID)
}
开发者ID:qbit,项目名称:client,代码行数:51,代码来源:main.go
示例7: startServer
func (p *provisionee) startServer(s Secret) (err error) {
if p.conn, err = NewConn(p.arg.Ctx, p.arg.Mr, s, p.deviceID, p.arg.Timeout); err != nil {
return err
}
prot := keybase1.Kex2ProvisioneeProtocol(p)
p.xp = rpc.NewTransport(p.conn, p.arg.Provisionee.GetLogFactory(), nil)
srv := rpc.NewServer(p.xp, nil)
if err = srv.Register(prot); err != nil {
return err
}
p.server = srv
p.serverDoneCh = srv.Run()
return nil
}
开发者ID:qbit,项目名称:client,代码行数:15,代码来源:provisionee.go
示例8: TestTrackingNotifications
func TestTrackingNotifications(t *testing.T) {
tc := setupTest(t, "signup")
tc2 := cloneContext(tc)
tc5 := cloneContext(tc)
libkb.G.LocalDb = nil
// Hack the various portions of the service that aren't
// properly contextified.
defer tc.Cleanup()
stopCh := make(chan error)
svc := service.NewService(tc.G, false)
startCh := svc.GetStartChannel()
go func() {
err := svc.Run()
if err != nil {
t.Logf("Running the service produced an error: %v", err)
}
stopCh <- err
}()
userInfo := randomUser("sgnup")
tui := trackingUI{
signupUI: signupUI{
info: userInfo,
Contextified: libkb.NewContextified(tc2.G),
},
}
tc2.G.SetUI(&tui)
signup := client.NewCmdSignupRunner(tc2.G)
signup.SetTest()
<-startCh
if err := signup.Run(); err != nil {
t.Fatal(err)
}
tc2.G.Log.Debug("Login State: %v", tc2.G.LoginState())
nh := newTrackingNotifyHandler()
// Launch the server that will listen for tracking notifications.
launchServer := func(nh *trackingNotifyHandler) error {
cli, xp, err := client.GetRPCClientWithContext(tc5.G)
if err != nil {
return err
}
srv := rpc.NewServer(xp, nil)
if err = srv.Register(keybase1.NotifyTrackingProtocol(nh)); err != nil {
return err
}
ncli := keybase1.NotifyCtlClient{Cli: cli}
if err = ncli.SetNotifications(context.TODO(), keybase1.NotificationChannels{
Tracking: true,
}); err != nil {
return err
}
return nil
}
// Actually launch it in the background
go func() {
err := launchServer(nh)
if err != nil {
nh.errCh <- err
}
}()
// Have our test user track t_alice.
trackCmd := client.NewCmdTrackRunner(tc2.G)
trackCmd.SetUser("t_alice")
trackCmd.SetOptions(keybase1.TrackOptions{BypassConfirm: true})
err := trackCmd.Run()
if err != nil {
t.Fatal(err)
}
// Do a check for new tracking statements that should fire off a
// notification. Currently the track command above does not fetch the new
// chain link from the server, so this call is required. It's possible that
// TrackEngine (or our signature caching code) might change in the future,
// making this call unnecessary.
checkTrackingCmd := client.NewCmdCheckTrackingRunner(tc2.G)
err = checkTrackingCmd.Run()
if err != nil {
t.Fatal(err)
}
// Wait to get a notification back as we expect.
// NOTE: If this test ever starts deadlocking here, it's possible that
// we've changed how we cache signatures that we make on the local client,
// in such a way that the fetch done by CheckTracking above doesn't find
// any "isOwnNewLinkFromServer" links. If so, one way to fix this test
// would be to blow away the local db before calling CheckTracking.
tc.G.Log.Debug("Waiting for two tracking notifications.")
for i := 0; i < 2; i++ {
select {
//.........这里部分代码省略.........
开发者ID:qbit,项目名称:client,代码行数:101,代码来源:tracking_test.go
示例9: FixVersionClash
func FixVersionClash(g *libkb.GlobalContext, cl libkb.CommandLine) (err error) {
var cli keybase1.ConfigClient
var ctlCli keybase1.CtlClient
var serviceConfig keybase1.Config
var socket net.Conn
g.Log.Debug("+ FixVersionClash")
defer func() {
if socket != nil {
socket.Close()
socket = nil
}
g.Log.Debug("- FixVersionClash -> %v", err)
}()
// Make our own stack here, circumventing all of our libraries, so
// as not to introduce any incompatibilities with earlier services
// (like 1.0.8)
socket, err = g.SocketInfo.DialSocket()
if err != nil {
g.Log.Debug("| Failed to DialSocket, but ignoring error: %s\n", err)
return nil
}
xp := libkb.NewTransportFromSocket(g, socket)
srv := rpc.NewServer(xp, libkb.WrapError)
gcli := rpc.NewClient(xp, libkb.ErrorUnwrapper{})
cli = keybase1.ConfigClient{Cli: gcli}
srv.Register(NewLogUIProtocol())
serviceConfig, err = cli.GetConfig(context.TODO(), 0)
if err != nil {
return err
}
g.Log.Debug("| Contacted service; got version: %s", serviceConfig.Version)
// We'll check and restart the service if there is a new version.
var semverClient, semverService semver.Version
cliVersion := libkb.VersionString()
if g.Env.GetRunMode() == libkb.DevelRunMode {
tmp := os.Getenv("KEYBASE_SET_VERSION")
if len(tmp) > 0 {
cliVersion = tmp
}
}
semverClient, err = semver.Make(cliVersion)
if err != nil {
return err
}
semverService, err = semver.Make(serviceConfig.Version)
if err != nil {
return err
}
g.Log.Debug("| version check %s v %s", semverClient, semverService)
if semverClient.EQ(semverService) {
g.Log.Debug("| versions check out")
return nil
} else if semverClient.LT(semverService) && semverClient.Major < semverService.Major {
return fmt.Errorf("Unexpected version clash; client is at v%s, which is significantly *less than* server at v%s",
semverClient, semverService)
}
g.Log.Warning("Restarting after upgrade; service is running v%s, while v%s is available",
semverService, semverClient)
origPid, err := getPid(g)
if err != nil {
g.Log.Warning("Failed to find pid for service: %v\n", err)
}
if serviceConfig.ForkType == keybase1.ForkType_LAUNCHD {
return restartLaunchdService(g, serviceConfig.Label, g.Env.GetServiceInfoPath())
}
ctlCli = keybase1.CtlClient{Cli: gcli}
err = ctlCli.Stop(context.TODO(), keybase1.StopArg{})
if err != nil && origPid >= 0 {
// A fallback approach. I haven't seen a need for it, but it can't really hurt.
// If we fail to restart via Stop() then revert to kill techniques.
g.Log.Warning("Error in Stopping %d via RPC: %v; trying fallback (kill via pidfile)", origPid, err)
time.Sleep(time.Second)
var newPid int
newPid, err = getPid(g)
if err != nil {
g.Log.Warning("No pid; shutdown must have worked (%v)", err)
} else if newPid != origPid {
g.Log.Warning("New service found with pid=%d; assuming restart", newPid)
return nil
} else {
if err = killPid(origPid); err != nil {
g.Log.Warning("Kill via pidfile failed: %v\n", err)
return err
}
g.Log.Warning("Successful kill() on pid=%d", origPid)
}
}
//.........这里部分代码省略.........
开发者ID:qbit,项目名称:client,代码行数:101,代码来源:versionfix.go
示例10: TestSignupLogout
func TestSignupLogout(t *testing.T) {
tc := setupTest(t, "signup")
tc2 := cloneContext(tc)
tc5 := cloneContext(tc)
libkb.G.LocalDb = nil
// Hack the various portions of the service that aren't
// properly contextified.
defer tc.Cleanup()
stopCh := make(chan error)
svc := service.NewService(tc.G, false)
startCh := svc.GetStartChannel()
go func() {
err := svc.Run()
if err != nil {
t.Logf("Running the service produced an error: %v", err)
}
stopCh <- err
}()
userInfo := randomUser("sgnup")
sui := signupUI{
info: userInfo,
Contextified: libkb.NewContextified(tc2.G),
}
tc2.G.SetUI(&sui)
signup := client.NewCmdSignupRunner(tc2.G)
signup.SetTest()
logout := client.NewCmdLogoutRunner(tc2.G)
<-startCh
nh := newNotifyHandler()
// Launch the server that will listen for notifications on updates, such as logout
launchServer := func(nh *notifyHandler) error {
cli, xp, err := client.GetRPCClientWithContext(tc5.G)
if err != nil {
return err
}
srv := rpc.NewServer(xp, nil)
if err = srv.Register(keybase1.NotifySessionProtocol(nh)); err != nil {
return err
}
if err = srv.Register(keybase1.NotifyUsersProtocol(nh)); err != nil {
return err
}
ncli := keybase1.NotifyCtlClient{Cli: cli}
if err = ncli.SetNotifications(context.TODO(), keybase1.NotificationChannels{
Session: true,
Users: true,
}); err != nil {
return err
}
return nil
}
// Actually launch it in the background
go func() {
err := launchServer(nh)
if err != nil {
nh.errCh <- err
}
}()
if err := signup.Run(); err != nil {
t.Fatal(err)
}
tc2.G.Log.Debug("Login State: %v", tc2.G.LoginState())
select {
case err := <-nh.errCh:
t.Fatalf("Error before notify: %v", err)
case u := <-nh.loginCh:
if u != userInfo.username {
t.Fatalf("bad username in login notifcation: %q != %q", u, userInfo.username)
}
tc.G.Log.Debug("Got notification of login for %q", u)
}
btc := client.NewCmdBTCRunner(tc2.G)
btc.SetAddress("1HUCBSJeHnkhzrVKVjaVmWg2QtZS1mdfaz")
if err := btc.Run(); err != nil {
t.Fatal(err)
}
// Now let's be sure that we get a notification back as we expect.
select {
case err := <-nh.errCh:
t.Fatalf("Error before notify: %v", err)
case uid := <-nh.userCh:
tc.G.Log.Debug("Got notification from user changed handled (%s)", uid)
if e := libkb.CheckUIDAgainstUsername(uid, userInfo.username); e != nil {
t.Fatalf("Bad UID back: %s != %s (%s)", uid, userInfo.username, e)
}
}
//.........这里部分代码省略.........
开发者ID:qbit,项目名称:client,代码行数:101,代码来源:user_test.go
示例11: TestSecretUI
func TestSecretUI(t *testing.T) {
tc := setupTest(t, "secret_ui")
tc1 := cloneContext(tc)
tc2 := cloneContext(tc)
// Make sure we're not using G anywhere in our tests.
libkb.G.LocalDb = nil
defer tc.Cleanup()
stopCh := make(chan error)
svc := service.NewService(tc.G, false)
startCh := svc.GetStartChannel()
go func() {
err := svc.Run()
if err != nil {
t.Logf("Running the service produced an error: %v", err)
}
stopCh <- err
}()
// Wait for the server to start up
<-startCh
var err error
check := func() {
if err != nil {
t.Fatal(err)
}
}
sui := newSecretUI()
cli, xp, err := client.GetRPCClientWithContext(tc2.G)
check()
srv := rpc.NewServer(xp, nil)
err = srv.Register(keybase1.SecretUiProtocol(sui))
check()
ncli := keybase1.DelegateUiCtlClient{Cli: cli}
err = ncli.RegisterSecretUI(context.TODO())
check()
// run login command
loginCmdUI := &loginCmdUI{
Contextified: libkb.NewContextified(tc2.G),
}
tc2.G.SetUI(loginCmdUI)
cmd := client.NewCmdLoginRunner(tc2.G)
err = cmd.Run()
if err == nil {
t.Fatal("login worked, when it should have failed")
}
// check that delegate ui was called:
if !sui.getKeybasePassphrase {
t.Logf("secret ui: %+v", sui)
t.Error("delegate secret UI GetKeybasePassphrase was not called during login cmd")
}
stopper := client.NewCmdCtlStopRunner(tc1.G)
if err := stopper.Run(); err != nil {
t.Errorf("Error in stopping service: %v", err)
}
// If the server failed, it's also an error
err = <-stopCh
check()
}
开发者ID:polluks,项目名称:client,代码行数:67,代码来源:secret_ui_test.go
示例12: TestGregorForwardToElectron
func TestGregorForwardToElectron(t *testing.T) {
tc := setupTest(t, "gregor")
defer tc.Cleanup()
tc1 := cloneContext(tc)
svc := service.NewService(tc.G, false)
startCh := svc.GetStartChannel()
stopCh := make(chan error)
go func() {
tc.G.Log.Debug("+ Service.Run")
err := svc.Run()
tc.G.Log.Debug("- Service.Run")
if err != nil {
t.Logf("Running the service produced an error: %v", err)
}
stopCh <- err
}()
userInfo := randomUser("grgr")
sui := signupUI{
info: userInfo,
Contextified: libkb.NewContextified(tc.G),
}
tc.G.SetUI(&sui)
signup := client.NewCmdSignupRunner(tc.G)
signup.SetTest()
// Wait for the server to start up
<-startCh
if err := signup.Run(); err != nil {
t.Fatal(err)
}
tc.G.Log.Debug("Login State: %v", tc.G.LoginState())
var err error
check := func() {
if err != nil {
t.Fatal(err)
}
}
cli, xp, err := client.GetRPCClientWithContext(tc1.G)
srv := rpc.NewServer(xp, nil)
em := newElectronMock(tc.G)
err = srv.Register(keybase1.GregorUIProtocol(em))
check()
ncli := keybase1.DelegateUiCtlClient{Cli: cli}
// Spin until gregor comes up; it should come up after signup
var ok bool
for i := 0; !ok && i < 40; i++ {
if ok = svc.HasGregor(); !ok {
time.Sleep(50 * time.Millisecond)
} else {
tc.G.Log.Debug("spinning, waiting for gregor to come up (attempt %d)", i)
}
}
if !ok {
t.Fatal("Gregor never came up after we signed up")
}
svc.SetGregorPushStateFilter(func(m gregor.Message) bool {
cat := m.ToInBandMessage().ToStateUpdateMessage().Creation().Category()
return cat.String() != "user.identity_change" && cat.String() != "user.key_change"
})
err = ncli.RegisterGregorFirehose(context.TODO())
check()
select {
case a := <-em.stateCh:
if a.Reason != keybase1.PushReason_RECONNECTED {
t.Fatal(fmt.Sprintf("got wrong reason: %v", a.Reason))
}
if d := len(filterPubsubdItems(a.State.Items_)); d != 0 {
t.Fatal(fmt.Sprintf("Wrong number of items in state -- should have 0, but got %d", d))
}
case <-time.After(3 * time.Second):
t.Fatalf("never got a reconnect message")
}
msgID, err := svc.GregorInject("foo", []byte("bar"))
check()
err = svc.GregorInjectOutOfBandMessage("baz", []byte("bip"))
check()
checkState := func(s gregor1.State) {
items := filterPubsubdItems(s.Items_)
if n := len(items); n != 1 {
t.Errorf("Expected one item back; got %d", n)
return
}
i := items[0]
if !bytes.Equal(i.Md_.MsgID_.Bytes(), msgID.Bytes()) {
t.Error("Wrong gregor message ID received")
}
if i.Item_.Category_.String() != "foo" {
t.Error("Wrong gregor category")
}
if string(i.Item_.Body_.Bytes()) != "bar" {
t.Error("Wrong gregor body")
//.........这里部分代码省略.........
开发者ID:qbit,项目名称:client,代码行数:101,代码来源:gregor_test.go
示例13: TestDelegateUI
func TestDelegateUI(t *testing.T) {
tc := setupTest(t, "delegate_ui")
tc1 := cloneContext(tc)
tc2 := cloneContext(tc)
// Make sure we're not using G anywhere in our tests.
libkb.G.LocalDb = nil
defer tc.Cleanup()
stopCh := make(chan error)
svc := service.NewService(tc.G, false)
startCh := svc.GetStartChannel()
go func() {
err := svc.Run()
if err != nil {
t.Logf("Running the service produced an error: %v", err)
}
stopCh <- err
}()
// Wait for the server to start up
<-startCh
dui := newDelegateUI()
launchDelegateUI := func(dui *delegateUI) error {
cli, xp, err := client.GetRPCClientWithContext(tc2.G)
if err != nil {
return err
}
srv := rpc.NewServer(xp, nil)
if err = srv.Register(keybase1.IdentifyUiProtocol(dui)); err != nil {
return err
}
ncli := keybase1.DelegateUiCtlClient{Cli: cli}
if err = ncli.RegisterIdentifyUI(context.TODO()); err != nil {
return err
}
return nil
}
// Launch the delegate UI
if err := launchDelegateUI(dui); err != nil {
t.Fatal(err)
}
id := client.NewCmdIDRunner(tc1.G)
id.SetUser("t_alice")
id.UseDelegateUI()
if err := id.Run(); err != nil {
t.Errorf("Error in Run: %v", err)
}
// We should get either a 'done' or an 'error' from the delegateUI.
err, ok := <-dui.ch
if err != nil {
t.Errorf("Error with delegate UI: %v", err)
} else if ok {
t.Errorf("Delegate UI didn't close the channel properly")
} else if err = dui.checkSuccess(); err != nil {
t.Error(err)
}
stopper := client.NewCmdCtlStopRunner(tc1.G)
if err := stopper.Run(); err != nil {
t.Errorf("Error in stopping service: %v", err)
}
// If the server failed, it's also an error
if err := <-stopCh; err != nil {
t.Fatal(err)
}
}
开发者ID:mark-adams,项目名称:client,代码行数:74,代码来源:delegate_ui_test.go
注:本文中的github.com/keybase/go-framed-msgpack-rpc.NewServer函数示例整理自Github/MSDocs等源码及文档管理平台,相关代码片段筛选自各路编程大神贡献的开源项目,源码版权归原作者所有,传播和使用请参考对应项目的License;未经允许,请勿转载。 |
请发表评论