首页 > web前端 > js教程 > NgSysV.A Serious Svelte InfoSys:客户端-服务器版本

NgSysV.A Serious Svelte InfoSys:客户端-服务器版本

Linda Hamilton
发布: 2024-12-01 09:56:17
原创
699 人浏览过

该帖子系列已在 NgateSystems.com 上建立索引。您还可以在那里找到超级有用的关键字搜索工具。

最后评论:24 年 11 月

一、简介

Post 3.3 带来了一些坏消息 - 用于客户端提供有关登录用户信息的 Firestore 身份验证对象在服务器端不可用。这会产生以下后果:

  • 服务器端数据库代码必须使用 Firestore Admin API。这是因为 Firestore Client API 代码使调用遵循数据库“规则”,当身份验证不可用时,引用身份验证会失败。相比之下,管理 API 调用并不关心数据库规则。如果您放弃了规则,客户端 API 调用将在服务器端工作,但这将使您的数据库容易受到网络攻击(自从您开始使用本地 VSCode 终端以来,您一直在使用实时 Firestore 数据库 - 想想看) .

  • 使用从 auth 派生的 userName 和 userEmail 等数据项的服务器端代码必须找到另一种方式来获取此信息。

这篇文章描述了如何克服这些问题来生成一个在服务器端安全、高效运行的高性能 Web 应用程序。

2. 实践中经过身份验证的 Svelte 服务器端代码

如果您已经习惯了客户端调用签名,那么切换到 Firestore Admin API 的要求会很麻烦。但你很快就会习惯这一点,所以它不会对你造成很大的阻碍。

然而,获取用户数据是另一回事。对于许多应用程序来说,访问用户属性(例如 uId)对其设计至关重要。例如,网络应用程序可能需要确保用户只能看到他们的自己的数据。不幸的是,安排这个是相当困难的。我们开始吧:

  1. 首先,在客户端,您需要找到一种方法来创建一个“idToken”包,其中包含服务器端代码可能需要了解的有关用户的所有信息。 Google 提供了 getIdToken() 机制来根据用户的身份验证会话数据构建此机制。
  2. 然后你需要找到一种方法将这个包传递到服务器。这里使用的机制将其注册在“标头”中,该“标头”被添加到对服务器的客户端调用中。
  3. 然后您需要获取一个 Google“服务帐户”,以便您能够在 Google 服务器上验证您对 Firestore Admin API 的使用情况。定义此的密钥需要安全地嵌入到您的项目文件中(回想一下 3.3 篇文章中的 firebaseConfig.env 讨论。
  4. 最后,无论您需要使用 Firestore 数据库,您的服务器端代码都必须提供这些服务帐户密钥。

2.1 获取idToken

看看 <script> 中的以下代码: products-maintenance-sv/ page.svelte “规则友好”products-maintenance-rf 代码的“服务器”版本的部分。这使用 getIdToken() 访问用户的 Firebase 身份验证会话并构建 idToken<br> </script>

// src/routes/products-maintenance-sv/+page.svelte   
<script>
    import { auth } from "$lib/utilities/firebase-client";
    import { onMount } from "svelte";
    import { goto } from "$app/navigation";

    onMount(async () => {
        if (!auth.currentUser) {
            // Redirect to login if not authenticated, with a redirect parameter
            goto("/login?redirect=/products-maintenance-sv");
            return;
        }

        try {
            // Fetch the ID token directly
            const idToken = await auth.currentUser.getIdToken();
            window.alert("idToken:" + JSON.stringify(idToken));
        } catch (error) {
            window.alert("Error retrieving ID token:", error);
        }
    });
</script>
登录后复制
登录后复制
登录后复制
登录后复制

您之前在 products-maintenance-rf/ page.svelte 中看到了 onMount() 安排,它用于确保用户登录。现在它还用于通过调用异步身份验证来获取 idToken 变量。 currentUser.getIdToken()。

创建一个新的 src/routes/products-maintenance-sv 文件夹,并将上面列出的代码粘贴到其中的新 page.svelte 文件中。 现在尝试在开发服务器中运行它:http://localhost:5173/products-maintenance-sv。登录后(使用最后在 [Post 3.4](https://ngatelive.nw.r.appspot.com/redirect?post=3.4 中看到的 /login/ page.svelte 版本),您应该会看到显示的 idToken在警报消息中。

Firebase ID 令牌是 JSON Web 令牌 (JWT)。 JSON 位意味着它是一个使用“Javascript 对象表示法”编码为字符串的对象(如果这是您第一次看到“JSON”,您可能会发现向 chatGPT 询问背景信息很有用)。 JSON 广泛用于需要将 Javascript 对象作为字符串传递的地方。 JWT JSON 包含您可能需要了解的有关用户的所有信息。我将在本文后面向您展示如何提取这些信息 - 这并不复杂。

2.2 将idToken传递给服务器

本文中描述的机制将“IdToken”作为服务器请求附带的“请求标头”中的“cookie”发送。 “http header”是当基于客户端的 page.svelte 文件向基于服务器的 page.server.js 文件发送请求时通过 Web 传递的信息包。每次您读取或写入 Firestore 文档时都会发送此类请求。 “cookie”是一个添加到每个请求标头中的字符串。

这种安排很复杂,但被认为是安全的。 从您的角度来看,作为一名 IT 学生,它也很有趣且具有教育意义,因为它可以深入了解网页设计的内部结构。

客户端 Javascript 程序可以轻松设置包含 JWT 的 “常规” cookie,但出于安全原因,您非常不希望这样做。如果你能做到这一点,那么任何人都可以。另一方面,服务器端 page.server.js 文件可以使用 set-cookie 调用在客户端浏览器中设置 "http-only" cookie。这是一个例子:

// src/routes/products-maintenance-sv/+page.svelte   
<script>
    import { auth } from "$lib/utilities/firebase-client";
    import { onMount } from "svelte";
    import { goto } from "$app/navigation";

    onMount(async () => {
        if (!auth.currentUser) {
            // Redirect to login if not authenticated, with a redirect parameter
            goto("/login?redirect=/products-maintenance-sv");
            return;
        }

        try {
            // Fetch the ID token directly
            const idToken = await auth.currentUser.getIdToken();
            window.alert("idToken:" + JSON.stringify(idToken));
        } catch (error) {
            window.alert("Error retrieving ID token:", error);
        }
    });
</script>
登录后复制
登录后复制
登录后复制
登录后复制

上面的 httpOnly: true 设置意味着,尽管 cookie 保存在客户端,但它无法从 Javascript 访问。 这样您就可以确保您在此处设置的值不会被篡改。

您现在应该问的问题是“当服务器端 page.server.js 文件不知道 idToken 时,如何启动 Set-Cookie 命令来设置 idToken?” 。

欢迎使用 Svelte server.js 文件。这是服务器端代码,可以使用 Javascript fetch 命令从客户端代码调用。这样的服务器端代码称为“端点”。获取命令是 JavaScript 的本机方法,用于向基于 Web 的“端点”提交请求。该命令使您能够在请求中包含数据,因此这就是您在服务器上获取 idToken 值的方式。这是一个例子:

    // Set a secure, HTTP-only cookie with the `idToken` token
    const headers = {
      'Set-Cookie': cookie.serialize('idToken', idToken, {
        httpOnly: true
      })
    };

    let response = new Response('Set cookie from server', {
      status: 200,
      headers,
      body: { message: 'Cookie set successfully' }  // Optional message
    });

    return response;
登录后复制
登录后复制
登录后复制

以下是接收者 server.js 文件如何检索此内容并提取其 idToken。

// client-side +page.svelte code
         const idToken = await user.getIdToken();

            // Send token to the server to set the cookie
            fetch("/api/login", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ idToken }),
            });
登录后复制
登录后复制

您可能在想“为什么这段代码使用“fetch”命令来“发送”某些东西?”但你就在那里。 “Fetch”被设计为一个多功能 API,用于发出许多不同类型的 HTTP 请求。如果您想了解一些背景知识,请向 chatGPT 索取教程。请参阅一些示例。

现在的建议是让您的登录页面负责进行设置浏览器仅http cookie的server.js调用。设置 cookie 后,它将自动添加到浏览器发出的每个 HTTP 调用中,直到过期

要启动此操作,请为登录页面的以下 login-and-set-cookie/ page.svelte 版本及其随附的 api/set-cookie/ server.js 端点创建新文件夹和文件:

// server-side +server.js code
export async function POST({ request }) {
  const { idToken } = await request.json();
}
登录后复制

请注意,为了使用 api/set-cookie/ server.js,您首先需要安装 npm "cookie" 库。该库有助于创建格式正确的 cookie,以便包含在 HTTP 响应标头中。

// src/routes/login-and-set-cookie/+page.svelte
<script>
    import { onMount } from "svelte";
    import { auth, app } from "$lib/utilities/firebase-client";
    import { goto } from "$app/navigation"; // SvelteKit's navigation for redirection
    import { signInWithEmailAndPassword } from "firebase/auth";

    let redirect;
    let email = "";
    let password = "";

    onMount(() => {
        // Parse the redirectTo parameter from the current URL
        const urlParams = new URLSearchParams(window.location.search);
        redirect = urlParams.get("redirect") || "/";
    });

    // this code will run after a successful login.
    auth.onAuthStateChanged(async (user) => {
        if (user) {
            const idToken = await user.getIdToken();

            console.log("In login_awith-cookie : idToken: ", idToken);

            // Send token to the server to set the cookie
            fetch("/api/set-cookie", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ idToken }),
            });

            window.alert("In with-cookie : cookie set");
            goto(redirect);
        }
    });

    async function loginWithMail() {
        try {
            const result = await signInWithEmailAndPassword(
                auth,
                email,
                password,
            );
        } catch (error) {
            window.alert("login with Mail failed" + error);
        }
    }
</script>

<div>





<pre class="brush:php;toolbar:false">// src/routes/api/set-cookie/+server.js
import admin from 'firebase-admin';
import cookie from 'cookie';

export async function POST({ request }) {
  const { idToken } = await request.json();

  try {
    // Verify the token with Firebase Admin SDK
    const decodedToken = await admin.auth().verifyIdToken(idToken);

    // Use the cookie.serialize method to create a 'Set-Cookie' header for inclusion in the POST
    // response. This will instruct the browser to create a cookie called 'idToken' with the value of idToken
    // that will be incorporated in all subsequent browser communication requests to pages on this domain.

    const headers = {
      'Set-Cookie': cookie.serialize('idToken', idToken, {
        httpOnly: true,          // Ensures the cookie is only accessible by the web server
        secure: true,            // Ensures the cookie is only sent over HTTPS
        sameSite: 'None',        // Allows the cookie to be sent in cross-site requests
        maxAge: 60 * 60,         // 1 hour (same as Firebase ID token expiry)
        path: '/'                // Ensures the cookie is sent with every request, regardless of the path.
      })
    };

    let response = new Response('Set cookie from login', {
      status: 200,
      headers,
      body: { message: 'Cookie set successfully' }  // Optional message
    });

    console.log("Cookie set")

    return response;

  } catch (err) {
    console.error("Error in login server function: ", err);

    let response = new Response('Set cookie from login', {
      status: 401,
      body: { message: 'Unauthorized' }  // Optional message
    });

    return response;
  }
};

登录后复制

不需要“注销并删除 cookie”注销页面。设置新的 cookie 将覆盖任何同名的旧版本。

2.3 在项目中设置服务帐户

项目的服务帐户是一个包含安全密钥和“所有者”信息(例如项目的projectId)的对象。当“ page.server.js”文件运行时,嵌入其中的服务帐户的副本将呈现给Google。如果两者匹配,则服务器文件通过验证。

以下程序:

  1. 在云上为您的项目创建并下载服务帐户,
  2. 将其嵌入到您的项目中,并且
  3. 在项目中安装执行比较所需的“firebase-admin”库

2.3.1 创建服务帐号

  1. 前往 Google Cloud Console。
  2. 导航至IAM 和管理 > > 服务帐户并检查它是否指向您的 svelte-dev 项目(使用左上角的下拉菜单)。 IAM(身份和访问管理)屏幕列出了所有云权限,这些权限控制谁可以使用项目的 Google Cloud 资源执行哪些操作。这本身就值得一个“帖子”,但现在不是时候
  3. 将鼠标悬停在屏幕左侧的工具栏上并单击标有“服务帐户”的工具栏,即可退出 IAM 页面并进入服务帐户页面。您应该看到默认帐户已创建。
  4. 单击页面顶部的“创建服务帐户”按钮,然后使用唯一的“服务帐户名称”创建一个新服务帐户,例如“svelte-dev”(或任何您喜欢的名称 - 它必须在 6 到 6 之间) 30 个字符长,只能看到小写字母数字和破折号),其后缀保证在云范围内是唯一的,我建议您接受它提供的任何内容。
  5. 现在单击“创建并继续”按钮,然后转到“授予此服务帐户访问项目的权限”部分。首先打开字段上的下拉菜单。这有点复杂,因为它有两个面板。左侧面板(有一个滑块)允许您选择产品或服务。右侧列出了该服务可用的角色。使用左侧面板选择“Firebase”服务,然后从右侧面板中选择“Admin SDK 管理员服务代理”角色。单击“继续”,然后单击“完成”返回服务帐户屏幕

  6. 最后,单击您刚刚创建的“Firebase Admin SDK Service Agent”密钥条目右侧的“三点”菜单,然后选择“管理密钥”。点击“添加密钥”>创建新密钥> JSON>创建并注意一个新文件已出现在您的“下载”文件夹中。这是您的“服务帐户密钥”。您现在要做的就是将其嵌入到您的项目中。

2.3.2 将下载的服务帐户嵌入到您的项目中

  1. 在项目的根目录中创建一个 /secrets 文件夹,为服务帐户密钥提供安全位置。将下载的服务帐户文件移动到 /secrets/serviceAccount.json 文件中,并将“/secrets”文件夹及其任何编辑历史记录添加到“.gitignore”文件中:
// src/routes/products-maintenance-sv/+page.svelte   
<script>
    import { auth } from "$lib/utilities/firebase-client";
    import { onMount } from "svelte";
    import { goto } from "$app/navigation";

    onMount(async () => {
        if (!auth.currentUser) {
            // Redirect to login if not authenticated, with a redirect parameter
            goto("/login?redirect=/products-maintenance-sv");
            return;
        }

        try {
            // Fetch the ID token directly
            const idToken = await auth.currentUser.getIdToken();
            window.alert("idToken:" + JSON.stringify(idToken));
        } catch (error) {
            window.alert("Error retrieving ID token:", error);
        }
    });
</script>
登录后复制
登录后复制
登录后复制
登录后复制

这是前面在 3.3 篇文章中描述的保护机制的另一个实例,用于阻止您无意中泄露 Git 存储库中的文件。对于 Windows 用户,更安全的方法是创建 Windows GOOGLE_APPLICATION_CREDENTIAL 环境变量来提供关键引用。

2.3.3 在项目中安装“firebase-admin”库

要运行“服务器登录”过程,您的 page.server.js 代码需要访问 Firebase 管理 API。您可以通过在项目中安装“firebase-admin”来获得此信息:

    // Set a secure, HTTP-only cookie with the `idToken` token
    const headers = {
      'Set-Cookie': cookie.serialize('idToken', idToken, {
        httpOnly: true
      })
    };

    let response = new Response('Set cookie from server', {
      status: 200,
      headers,
      body: { message: 'Cookie set successfully' }  // Optional message
    });

    return response;
登录后复制
登录后复制
登录后复制

您现在可以使用以下命令在代码中创建管理员引用:

// client-side +page.svelte code
         const idToken = await user.getIdToken();

            // Send token to the server to set the cookie
            fetch("/api/login", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ idToken }),
            });
登录后复制
登录后复制

请注意,此导入的语法与您迄今为止使用的语法不同 - “admin”位周围没有大括号。虽然您使用的其他库允许您导入命名组件,但此版本要求您从 default 管理导出导入整个内容。这提供了组件作为 properties,例如父管理对象的 admin.auth()、admin.firestore() 等。该图书馆的设计者认为,在这种情况下这是一个更实用的安排。

使用默认导入时,您可以将导入的父对象称为您喜欢的任何名称(例如,您可以将其称为 myFirebaseAdmin 而不是 admin)。将此安排与您之前创建的 lib/utilities/firebase-config 文件的命名导出方法进行比较

2.4 使用 page.server.js 文件中的服务帐户和 idToken

这是您最终了解使用 Firestore 管理 API 访问 Firestore 数据库服务器端的实质内容的地方。

首先,您使用服务帐户密钥“初始化”您的应用程序,从而获得创建使用管理 API 所需的 adminDb 对象的权限(就像您需要用于客户端 API 的 db 一样)。然后,您需要从 cookie 中获取 idToken,并从中提取您在 Firestore 调用中可能需要的任何用户内容。此时,您终于可以使用 Firestore 管理 API 来编写这些调用的代码了。

将下面列出的代码复制到 src/routes/products-maintenance-sv 文件夹中的新 page.server.js 文件中。这是产品维护代码的“服务器版本”,首次出现在 Post 3.3 中。它用于展示尝试使用 Firestore 客户端 API 的服务器端代码在其所寻址的集合受 Firestore 数据库规则约束的情况下如何失败。这个新版本的好处是:

  • 服务帐户密钥,使其能够使用 Firestore Admin API 命令,从而忽略数据库规则
  • 一个 idToken cookie,使其能够获取经过身份验证的用户的详细信息
// src/routes/products-maintenance-sv/+page.svelte   
<script>
    import { auth } from "$lib/utilities/firebase-client";
    import { onMount } from "svelte";
    import { goto } from "$app/navigation";

    onMount(async () => {
        if (!auth.currentUser) {
            // Redirect to login if not authenticated, with a redirect parameter
            goto("/login?redirect=/products-maintenance-sv");
            return;
        }

        try {
            // Fetch the ID token directly
            const idToken = await auth.currentUser.getIdToken();
            window.alert("idToken:" + JSON.stringify(idToken));
        } catch (error) {
            window.alert("Error retrieving ID token:", error);
        }
    });
</script>
登录后复制
登录后复制
登录后复制
登录后复制

注意代码构建 userEmail 字段的奇怪方式

    // Set a secure, HTTP-only cookie with the `idToken` token
    const headers = {
      'Set-Cookie': cookie.serialize('idToken', idToken, {
        httpOnly: true
      })
    };

    let response = new Response('Set cookie from server', {
      status: 200,
      headers,
      body: { message: 'Cookie set successfully' }  // Optional message
    });

    return response;
登录后复制
登录后复制
登录后复制

verifyIdToken 方法名称可能会让您怀疑这是否正在尝试再次验证您的用户。别担心——事实并非如此。它只是对令牌的嵌入“签名”进行安全检查,以确保它没有被篡改并且没有过期。

verifyIdToken创建的decodedToken是一个简单的对象,包含经过身份验证的用户的电子邮件和用户名属性等。后续的 Firestore 代码没有使用其中任何一个,但我相信您可以轻松想象它是如何做到这一点的。

我建议您在编写管理 API 调用时再次使用“样板”方法 - 如果需要,请使用 chatGPT 转换 10.1 篇文章中记录的客户端代码。

现在用下面所示的代码替换您之前创建的 src/routes/products-maintenance-sv/ page.svelte 文件的内容。这将为 products-maintenance-sv/ page.server.js 文件提供客户端前端:

// src/routes/products-maintenance-sv/ page.svelte

    从“svelte”导入{onMount};
    从“$lib/utilities/firebase-client”导入{auth};
    从“$app/navigation”导入{goto};
    从“$lib/utilities/productNumberIsNumeric”导入{productNumberIsNumeric};

    // 将对身份验证状态更改的检查放在 onMount 内的 onAuthStateChanged 回调中。这似乎
    // 奇怪,但似乎是完成服务器端活动并使 auth.currentUser 进入稳定状态的唯一方法
    // 状态。这在客户端“规则友好”版本上不是问题,但很快就成为问题
    // 添加了带有 actions() 的 page.server.js 文件。

    onMount(异步() => {
        auth.onAuthStateChanged(异步(用户)=> {
            if (!auth.currentUser) {
                // 如果未通过身份验证,则重定向到登录。该参数告诉登录如何返回这里
                goto(“/login-and-set-cookie?redirect=/products-maintenance-sv”);
            }
        });
    });

    让产品编号;
    让产品详细信息;

    让产品编号类=“产品编号”;
    让submitButtonClass =“submitButton”;

    出口许可证表格;
</脚本>


    
        产品编号
        



<p>要在您的开发服务器中运行此程序,请首先使用 http://localhost:5173/logout 注销。然后运行http://localhost:5173/products-maintenance-sv。这将邀请您登录登录并设置 cookie 页面。 </p>

<p>成功登录后,您将看到熟悉的表单,邀请您创建新产品。 </p>

<p>此时,登录并设置 cookie 页面应该已在您的浏览器中安全地设置了 idToken cookie。当您输入数据并提交表单时,控制权将传递到 products-maintenance-sv/page.server.js 中的服务器端代码。它通过呈现项目中内置的服务代码来进行自身身份验证,然后从 Sveltekit 请求中的表单对象中获取标头中的 idToken 及其输入数据。该代码不会对 idToken 中可用的用户数据执行任何有用的操作,但会在 VSCode 终端中显示一条显示 userEmail 值的日志消息。最后,Firestore 管理代码会将新产品添加到产品数据库集合中。</p>

<p>您可以通过运行旧的 http://localhost:5173/products-display-rf 页面来确认更新已成功应用。</p>

<p>请注意,提交表单后,它会显示一条确认消息并清除其输入字段。 “表单刷新”是表单提交后 Javascript 的默认操作。 </p><p>您可能想知道当 http://localhost:5173/products-display-rf 页面仍在运行 Firestore <strong>客户端</strong> API 代码服务器端并在产品集合上设置 Firestore 身份验证规则时,它是如何工作的。不同之处在于这些规则仅适用于写入。 products-display-rfcode 只是读取文档。 </p>

<p>在实践中,我认为如果您担心避免混淆并决定创建 products-display-sv 的 products-display-sv 版本,您会希望在整个过程中使用 Firestore <strong>Admin</strong> API 调用。但请记住,您需要首先提供您的服务帐户凭据来初始化应用程序。</p>

<h3>
  
  
  三、总结
</h3>

<p>这是一篇很长的文章,将会使您的 Javascript 达到极限。如果此时你仍然和我在一起——干得好。真的,干得好——你表现出了非凡的毅力! </p>

<p>上一篇文章介绍的“客户端”技术使用起来很愉快,但我希望您会欣赏服务器端安排的安全性和速度优势。凭借经验,服务器端代码开发将变得像客户端工作一样简单自然。</p>

<p>但是还有一件事要做。尽管您现在已经开发了一个包含许多页面的 Web 应用程序,这些页面在您的开发服务器中运行得很好,但这些页面在 Web 上尚不可见。</p>

<p>下一篇文章将告诉您如何“构建”和部署”您的 Web 应用程序到 Google AppEngine 上,从而将其发布给热切的公众。这将是一个重要的时刻!</p>

<p>我希望您仍有精力继续阅读并找出您需要做什么。不是太难。</p>

<h3>
  
  
  后记:当出现问题时 - 在检查器中查看标题
</h3>

<p>实际上,在本节中可能出现问题的地方可能接近无限。尽量不要过于恐慌,并密切关注项目的文件结构。很容易将正确的代码放入错误的文件中,或者将正确的文件放入错误的文件夹中。此外,您可能会发现通过终止并重新启动终端会话定期“清理地面”很有帮助。至少,这可以让您在寻找错误序列的最初原因时获得一张干净的纸。</p>

<p>但是,由于您现在正在使用标头和 cookie,您还会发现了解浏览器的检查器工具可以让您直观地了解这些内容很有用。检查器可以<strong>向您显示</strong>嵌入在页面请求标头中的cookie。</p><p>要查看此功能的运行情况,首先请确保您已使用 https://myLiveUrl/logout 在实时系统上注销(其中 myLiveUrl 是您部署的 Web 应用程序的地址)。然后运行 ​​products-maintenance=sv 页面(网址为 https://https://myLiveUrl/products-maintenance-sv)。登录后,打开“输入新产品”表单上的检查器,然后单击“网络”选项卡。现在显示页面发出的网络请求的列表。</p>

<p>现在使用网络应用程序插入新产品并注意“请求”列表是如何刷新的。这些是执行简单更新所需的网络请求 - 一个令人惊讶的长列表!向上滚动到此列表的顶部,您应该会找到 products-maintenance-sv 页面的条目。如果单击此按钮,请求列表右侧的面板应显示事务的响应和请求标头的完整详细信息。下面的屏幕截图显示了嵌入在请求标头中的 cookie。</p>

<p><img src="https://img.php.cn/upload/article/000/000/000/173301818045981.jpg" alt="NgSysV.A Serious Svelte InfoSys: A Client-Server Version"></p>


          

            
        
登录后复制

以上是NgSysV.A Serious Svelte InfoSys:客户端-服务器版本的详细内容。更多信息请关注PHP中文网其他相关文章!

来源:dev.to
本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板