# 服务器运行时示例

**URL:** https://heroiclabs.com/docs/zh/nakama/server-framework/runtime-examples/
**Summary:** 使用嵌入式代码运行时编写带有Go插件、Lua模块或JavaScript包的自定义逻辑的示例

---


# 服务器运行时示例

## 服务器到服务器

与客户端调用不同，服务器到服务器的调用从不会有用户ID，因此，对于任何不希望从客户端访问的函数，如果在上下文中找到用户ID，则返回一个错误。

{{< code type="server" >}}
```go
func ServerRPC(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
	userId, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)

	if ok && userId != "" {
		logger.Error("rpc was called by a user")
		return "", runtime.NewError("rpc is only callable via server to server", 7)
	}

	// Valid server to server RPC call, continue executing the RPC...
	return "<JsonResponse>", nil
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
const serverRpc : nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) : string | void {
  if (ctx.userId != "") {
    logger.error("rpc was called by a user");
    return null;
  }
  
  // Valid server to server RPC call, continue executing the RPC...
  return "<JsonResponse>";
}
```
{{< / code >}}

{{< code type="server" >}}
```lua
local server_rpc = function(context, payload)
    if context.user_id and not context.user_id == "" then
        nk.logger_error("rpc was called by a user")
        return nil
    end

    -- Valid server to server RPC call, continue executing the RPC...
    return "<JsonResponse>"
end
```
{{< / code >}}

此外，它还有助于创建可供Web服务使用的HTTPREST处理程序，并简化与定制服务器环境的集成。 

这可以通过使用RPC挂钩和[运行时 HTTP 密钥](../../getting-started/configuration/#runtime.http_key)在服务器上进行身份验证来实现。

{{< code type="server" >}}
```lua
local nk = require("nakama")

local function http_handler(context, payload)
  local message = nk.json_decode(payload)
  nk.logger_info(string.format("Message: %q", message))
  return nk.json_encode({["context"] = context})
end

nk.register_rpc(http_handler, "http_handler_path")
```
{{< / code >}}

{{< code type="server" >}}
```go
func HttpHandler(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
		var message interface{}
		if err := json.Unmarshal([]byte(payload), &message); err != nil {
				return "", err
		}

		logger.Info("Message: %v", message)

		response, err := json.Marshal(map[string]interface{}{"message": message})
		if err != nil {
				return "", err
		}

		return string(response), nil
}

// Register as an RPC function, this call should be in InitModule.
if err := initializer.RegisterRpc("http_handler_path", HttpHandler); err != nil {
  logger.Error("Unable to register: %v", err)
  return err
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let customFuncRpc: nkruntime.RpcFunction =
function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) {
	logger.info('payload: %q', payload);

	if (ctx.userId) {
		// Reject non server-to-server call
		throw Error('Cannot invoke this function from user session');
	}

	let message = JSON.parse(payload);
	logger.info('Message: %q', message);

	return JSON.stringify({ message: message });
}

// Register as an after hook for the appropriate feature, this call should be in InitModule.
initializer.registerRpc("custom_rpc_func_id", customFuncRpc);
```
{{< / code >}}

注册的RPC函数可以用您选择的任何HTTP客户端调用。例如，您可以使用cURL在服务器上执行函数，如下所示。

```sh
curl "http://127.0.0.1:7350/v2/rpc/http_handler_path?http_key=defaulthttpkey" \
	-d '"{\"some\": \"data\"}"' \
	-H 'Content-Type: application/json' \
	-H 'Accept: application/json'
```

请注意，JSON有效负载被转义并包装在字符串中。这是设计的结果，因为在设计RPC API时，gRPC没有在Protobuf类型和JSON对象之间进行映射的类型。之后gRPC中增加了对SON的支持，但我们一直保持这种方式，以免违反API契约并确保兼容性。

`unwrap`查询参数受支持，使您能使用有负载中的原始JSON数据调用RPC函数：

```sh
curl "http://127.0.0.1:7350/v2/rpc/http_handler_path?http_key=defaulthttpkey&unwrap" \
	-d '{"some": "data"}' \
	-H 'Content-Type: application/json' \
	-H 'Accept: application/json'
```

## 初始化用户

使用[register挂钩](../introduction/#hooks)在用户完成注册后写入新用户的记录。

`"register_after"钩子`可以与一种"authenticaterequest_*"消息类型一起使用，指示服务器在处理这个消息后运行函数。需要注意的是，服务器不区分注册和登录消息，因此我们使用[有条件写入](../../concepts/storage/collections/#conditional-writes)来存储记录。

{{< code type="server" >}}
```lua
local function initialize_user(context, payload)
  if payload.created then
    -- Only run this logic if the account that has authenticated is new.
    local changeset = {
      coins = 10,   -- Add 10 coins to the user's wallet.
      gems = 5      -- Add 5 gems to the user's wallet.
      artifacts = 0 -- No artifacts to start with.
    }
    local metadata = {}
    nk.wallet_update(context.user_id, changeset, metadata, true)
  end
end

-- change to whatever message name matches your authentication type.
nk.register_req_after(initialize_user, "AuthenticateDevice")
```
{{< / code >}}

{{< code type="server" >}}
```go
func InitializeUser(ctx context.Context, logger Logger, db *sql.DB, nk NakamaModule, out *api.Session, in *api.AuthenticateDeviceRequest) error {
  if out.Created {
    // Only run this logic if the account that has authenticated is new.
    userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
    if !ok {
      return "", errors.New("Invalid context")
    }
    changeset := map[string]interface{}{
      "coins": 10,    // Add 10 coins to the user's wallet.
      "gems":  5,     // Add 5 gems to the user's wallet.
      "artifacts": 0, // No artifacts to start with.
    }
    var metadata map[string]interface{}
    if err := nk.WalletUpdate(ctx, userID, changeset, metadata, true); err != nil {
      // Handle error.
    }
  }
}

// Register as after hook, this call should be in InitModule.
if err := initializer.RegisterAfterAuthenticateDevice(InitializeUser); err != nil {
  logger.Error("Unable to register: %v", err)
  return err
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let initializeUser : nkruntime.AfterHookFunction<nkruntime.Session, nkruntime.AuthenticateDeviceRequest> = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, out: nkruntime.Session, data: nkruntime.AuthenticateDeviceRequest) : nkruntime.Session {
  const changeset = {
    "coins": 10, // Add 10 coins to the user's wallet
    "gems": 5, // Add 5 gems to the user's wallet
    "artifacts": 0 // No artifacts to start with
  };
  
  nk.walletUpdate(ctx.userId, changeset, null, true);
  return out;
};

// Register as after hook, this call should be in InitModule.
initializer.registerAfterAuthenticateDevice(initializeUser);
```
{{< / code >}}

## 存储

### 写入存储

{{< code type="server" >}}
```go
func AuthoritativeWriteRPC(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
	userID, _ := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)

	data := map[string]interface{}{
		"achievementPoints": 100,
		"unlockedAchievements": []string{"max-level", "defeat-boss-2", "equip-rare-gear"},
	}

	bytes, err := json.Marshal(data)
	if err != nil {
		return "", runtime.NewError("error marshaling data", 13)
	}

	write := &runtime.StorageWrite{
		Collection:      "Unlocks",
		Key:             "Achievements",
		UserID:          userID,
		Value:           string(bytes),
		PermissionRead:  1, // Only the server and owner can read
		PermissionWrite: 0, // Only the server can write
	}

	_, err = nk.StorageWrite(ctx, []*runtime.StorageWrite{write})
	if err != nil {
		return "", runtime.NewError("error saving data", 13)
	}
	
	return "<JsonResponse>", nil
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let authoritativeWriteRpc : nkruntime.RpcFunction = function (ctx: nkruntime.Context, logger: nkruntime.Logger, nk: nkruntime.Nakama, payload: string) : string | void {
  const data = {
    achievementPoints: 100,
    unlockedAchievements: ['max-level', 'defeat-boss-2', 'equip-rare-gear']
  };

  const write : StorageWriteRequest = {
    collection: 'Unlocks',
    key: 'Achievements',
    userId: ctx.userId,
    value: data,
    permissionRead: 1, // Only the server and owner can read
    permissionWrite: 0 // Only the server can write
  };

  nk.storageWrite([write]);

  return "<JsonResponse>";
};
```
{{< / code >}}

{{< code type="server" >}}
```lua
local authoritative_write_rpc = function(context, payload)
    local data = {
        ["achievementPoints"] = 100,
        ["unlockedAchievements"] = { "max-level", "defeat-boss-2", "equip-rare-gear" }
    }

    local write = {
        collection = "Unlocks",
        key = "Achievements",
        user_id = context.user_id,
        value = data,
        permission_read = 1,
        permission_write = 0
    }
    
    nk.storage_write({ write })

    return "<JsonResponse>"
end
```
{{< / code >}}