---
title: 编写插件权限
sidebar:
  order: 11
i18nReady: true
---

import { Steps } from '@astrojs/starlight/components';
import ShowSolution from '@components/ShowSolution.astro'
import Cta from '@fragments/cta.mdx';

本练习的目标是更好地理解在编写自己的插件时如何创建插件权限。

在最后，你将能够为插件创建简单的权限。你将获得一个 
Tauri 插件示例，其中权限部分是自动生成的，部分是手动设置的。

<Steps>

1. ### 创建 Tauri 插件

    在我们的示例中，我们将使用 Tauri [`cli`](/reference/cli/) 引导生成一个 Tauri 插件源代码结构。
    请确保已安装所有必备 [组件](/start/prerequisites/) ，并通过运行 
    `cargo tauri info` 验证你的 Tauri CLI 版本是否正确。

    输出应表明 `tauri-cli` 版本为 `2.x` 。
    我们将使用 `pnpm` 进行分步说明，但你可以选择其他包管理器，并在命令中相应地替换它。

    安装最新版本后，你可以继续使用 Tauri CLI 创建插件。

    <ShowSolution>
    ```sh
    mkdir -p tauri-learning
    cd tauri-learning
    cargo tauri plugin new test
    cd tauri-plugin-test
    pnpm install
    pnpm build
    cargo build
    ```
    </ShowSolution>

2. ### 创建一个新命令

    为了展示一些实用而简单的东西，让我们假设我们的命令将用户输入的内容
    写入临时文件夹内的文件中，同时向文件添加一些自定义标题。

    我们将命令命名为 `write_custom_file` ，并在 `src/commands.rs` 
    中实现它 并将其添加到我们的插件构建器中以暴露给前端。

    Tauri 的核心程序将自动生成此命令的 `allow` 和 `deny` 权限，因此我们不需要关心这一点。

    <ShowSolution>

    命令实现：

    ```rust title="src/commands.rs" ins={15-22} ins=", Manager"
    use tauri::{AppHandle, command, Runtime};

    use crate::models::*;
    use crate::Result;
    use crate::TestExt;

    #[command]
    pub(crate) async fn ping<R: Runtime>(
        app: AppHandle<R>,
        payload: PingRequest,
    ) -> Result<PingResponse> {
        app.test1().ping(payload)
    }

    #[command]
    pub(crate) async fn write_custom_file<R: Runtime>(
        user_input: String,
        app: AppHandle<R>,
    ) -> Result<String> {
        std::fs::write(app.path().temp_dir().unwrap(), user_input)?;
        Ok("success".to_string())
    }
   
    ```

    为你的新命令自动生成内置权限：

    ```rust title="src/build.rs" ins="\"write_custom_file\""
    const COMMANDS: &[&str] = &["ping", "write_custom_file"];
    ```

    这些内置权限将由 Tauri 构建系统自动生成，并显示在 `permissions/autogenerated/commands` 
    文件夹中。默认情况下，将创建 `enable-<command>` 和 `deny-<command>` 权限。

    </ShowSolution>

3. ### 公开新命令

    上一步是编写实际的命令实现。接下来，我们希望将其暴露给前端，以便其可以被使用。

    <ShowSolution>

    配置 Tauri 构建器以生成调用处理程序，将前端 IPC 请求传递给新实现的命令：

    ```rust title="src/lib.rs"  ins="commands::write_custom_file,"
    pub fn init<R: Runtime>() -> TauriPlugin<R> {
        Builder::new("test")
            .invoke_handler(tauri::generate_handler![
                commands::ping,
                commands::write_custom_file,
            ])
            .setup(|app, api| {
                #[cfg(mobile)]
                let test = mobile::init(app, api)?;
                #[cfg(desktop)]
                let test = desktop::init(app, api)?;
                app.manage(test);

                // 管理状态，以便命令访问
                app.manage(MyState::default());
                Ok(())
            })
            .build()
    }
    ```

    在前端模块中公开新命令。

    这一步对于示例应用程序成功导入前端模块至关重要。这是为了方便起见，
    不会对安全性产生影响，因为命令处理程序已经生成，该命令也可以从前端手动调用。

    ```ts title="guest-js/index.ts" ins={11-13}
    import { invoke } from '@tauri-apps/api/core'

    export async function ping(value: string): Promise<string | null> {
      return await invoke<{value?: string}>('plugin:test|ping', {
        payload: {
          value,
        },
      }).then((r) => (r.value ? r.value : null));
    }

    export async function writeCustomFile(user_input: string): Promise<string> {
      return await invoke('plugin:test|write_custom_file',{userInput: user_input});
    }
    ```

    :::tip 
    调用参数需要采用驼峰命名法。本例中，它是 `userInput` 而不是 `user_input` 。
    :::

    确保你的包已经构建：

    ```
    pnpm build
    ```

    </ShowSolution>

4. ### 定义默认插件权限

    由于我们的插件应该公开 `write_custom_file` 命令，
    因此我们应该将其添加到我们的 `default.toml` 权限中。

    <ShowSolution>

    将其添加到我们的默认权限集以允许我们刚刚公开的新命令正常运行。

    ```toml title="permissions/default.toml" ins=", \"allow-write-custom-file\""
    "$schema" = "schemas/schema.json"
    [default]
    description = "Default permissions for the plugin"
    permissions = ["allow-ping", "allow-write-custom-file"]
    ```
    </ShowSolution>

5. ### 从示例应用程序调用测试命令
    
    创建的插件目录结构包含一个 `examples/tauri-app` 文件夹，
    其中有一个可供使用的 Tauri 应用程序来测试该插件。

    由于我们添加了新命令，因此我们需要稍微修改前端来调用我们的新命令。

    <ShowSolution>
    ```svelte title="src/App.svelte" del={11-13,42-45} ins={14-16,45-49}
    <script>
      import Greet from './lib/Greet.svelte'
      import { ping, writeCustomFile } from 'tauri-plugin-test-api'

      let response = ''

      function updateResponse(returnValue) {
        response += `[${new Date().toLocaleTimeString()}]` + (typeof returnValue === 'string' ? returnValue : JSON.stringify(returnValue)) + '<br>'
      }

      function _ping() {
        ping("Pong!").then(updateResponse).catch(updateResponse)
      }
      function _writeCustomFile() {
        writeCustomFile("HELLO FROM TAURI PLUGIN").then(updateResponse).catch(updateResponse)
      }
    </script>

    <main class="container">
      <h1>Welcome to Tauri!</h1>

      <div class="row">
        <a href="https://vitejs.dev" target="_blank">
          <img src="/vite.svg" class="logo vite" alt="Vite Logo" />
        </a>
        <a href="https://tauri.app" target="_blank">
          <img src="/tauri.svg" class="logo tauri" alt="Tauri Logo" />
        </a>
        <a href="https://svelte.dev" target="_blank">
          <img src="/svelte.svg" class="logo svelte" alt="Svelte Logo" />
        </a>
      </div>

      <p>
        Click on the Tauri, Vite, and Svelte logos to learn more.
      </p>

      <div class="row">
        <Greet />
      </div>

      <div>
        <button on:click="{_ping}">Ping</button>
        <div>{@html response}</div>
      </div>
      <div>
        <button on:click="{_writeCustomFile}">Write</button>
        <div>{@html response}</div>
      </div>


    </main>

    <style>
      .logo.vite:hover {
        filter: drop-shadow(0 0 2em #747bff);
      }

      .logo.svelte:hover {
        filter: drop-shadow(0 0 2em #ff3e00);
      }
    </style>
    ```

    运行此程序并按下“Write”按钮，你将看到以下内容：

    ```
    success
    ```

    你应该在临时文件夹中找到一个 `test.txt` 文件，其中包含来自我们新实现的插件命令的消息。🥳

    </ShowSolution>

 </Steps>   
