整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

python如何进行网站维护?有什么优势?

python如何进行网站维护?有什么优势?

ython 是一种强大的编程语言,可以用于网站维护和开发。下面是一些常见的 Python 工具和技术,可用于网站维护:

Web 框架:使用 Python 的 Web 框架可以更轻松地构建和维护网站。一些流行的 Python Web 框架包括 Django、Flask 和 Pyramid。这些框架提供了路由、模板引擎、数据库集成等功能,使得开发和维护网站变得更加简单。

数据库:Python 提供了许多与数据库交互的库,例如 SQLite、MySQL、PostgreSQL 等。你可以使用这些库来存储和管理网站的数据。

HTML/CSS:网站维护中经常需要对 HTML 和 CSS 进行修改和调整。Python 可以使用第三方库如 BeautifulSoup 来解析和操作 HTML,使用库如 CSSutils 来处理 CSS。

自动化任务:Python 可以用于编写脚本来自动执行一些网站维护任务,例如定期备份数据库、清理日志文件、更新网站内容等。你可以使用 Python 的标准库或第三方库来完成这些任务。

测试:在进行网站维护时,测试是非常重要的。Python 提供了多种测试框架,例如 unittest、pytest 等,可以帮助你编写和运行测试用例,确保网站的功能和性能正常。

Web 抓取:有时候需要从其他网站抓取数据来更新自己的网站。Python 提供了一些强大的库如 Requests 和 Scrapy,可以帮助你编写网络爬虫,从其他网站获取数据。

以上只是一些基本的步骤和工具,具体的网站维护任务可能因项目而异。如果有什么需要和问题请咨询我们通辽网站建设|通辽网站制作|通辽做网站公司|通辽软件开发-通辽易联通达-通辽最专业的企业营销型网站建设专家公司

众号、企业官网、个人博客等各类网站建设日益火爆,而选择一个合适的官网模板成为许多人的关注焦点。在众多的官网模板中,publicCms 官网模板凭借其独特的设计风格和丰富的功能备受瞩目。本文将对publicCms 官网模板进行评测对比,帮助您找到最适合自己需求的官网模板。

一、外观设计

publicCms 官网模板以简洁大气的设计风格为主,注重用户体验和信息传达。无论是颜色搭配还是排版布局,都力求简洁明了,同时又不失个性和创意。与其他官网模板相比,publicCms 官网模板更加注重细节处理,使得整体视觉效果更加舒适和美观。

二、响应式布局

现如今,移动设备的普及使得响应式布局成为一个必备特性。publicCms 官网模板充分考虑到了这一点,采用了响应式设计,能够自动适配各种屏幕尺寸。无论是在电脑、平板还是手机上浏览,用户都能得到良好的浏览体验,这一点在选择官网模板时非常重要。

三、丰富的功能模块

publicCms 官网模板内置了许多实用的功能模块,满足了各类网站建设的需求。例如,公众号模块可以方便地集成微信公众号,实现与用户的互动;新闻资讯模块可以方便地发布和管理新闻内容;产品展示模块可以展示企业的产品和服务等。这些功能模块的丰富性使得publicCms 官网模板具备了更大的灵活性和扩展性。

四、友好的后台管理

作为一个官网模板,易于使用和管理是至关重要的。publicCms 官网模板提供了简洁明了的后台管理界面,使得用户能够轻松进行网站的配置和内容更新。无需专业技术背景,即可快速上手操作,这对于不熟悉编程的用户来说是一个巨大的优势。

五、高效的加载速度

对于一个官网来说,加载速度直接影响用户体验和SEO优化。publicCms 官网模板采用了优化过的代码和图片压缩技术,使得页面加载速度得到了有效提升。用户不需要长时间等待,能够快速访问到所需的内容,提高了用户的满意度和留存率。

六、良好的兼容性和安全性

publicCms 官网模板经过严格测试和优化,确保在不同浏览器和操作系统下都能正常展示和运行。同时,它还采用了一系列的安全机制,保护网站免受恶意攻击和数据泄露的威胁。这对于企业和个人网站来说,是非常重要的保障。

七、优质的售后服务

选择一个官网模板除了考虑产品本身,还要考虑供应商的售后服务。publicCms 官网模板提供了专业的售后支持团队,能够及时解答用户遇到的问题,并提供技术支持和升级服务。无论是初次使用还是后期维护,都能得到及时帮助,这对于用户来说是一个很大的安心点。

综上所述,publicCms 官网模板以其独特的设计风格、丰富的功能模块、友好的后台管理以及良好的兼容性和安全性等优势,成为众多用户的首选。如果你正在寻找一款适合自己的官网模板,不妨考虑一下publicCms 官网模板,相信它会给你带来惊喜!


章涵盖

  • 设计用户身份验证
  • 设置项目结构
  • 实现用户身份验证
  • 路由 HTTP 请求
  • 使用 HTTP POST 方法创建资源
  • 使用 HTTP PUT 方法更新资源
  • 使用 HTTP DELETE 方法删除资源

在上一章中,我们研究了导师的注册。您可能还记得,当用户注册为导师时,有关导师的信息存储在两个数据库中。导师的个人资料详细信息(如姓名图像专业领域)保存在后端导师 Web 服务内的数据库中。用户的注册详细信息(如用户标识密码)存储在 Web 应用程序的本地数据库中。

在本章中,我们将在上一章的代码之上进行构建。我们将学习编写一个 Rust 前端 Web 应用程序,该应用程序允许用户登录到应用程序、与本地数据库交互以及与后端 Web 服务通信

请注意,本章的重点不是为Web应用程序编写HTML/javascript用户界面(因为这不是本书的重点)。相反,我们将专注于编写在 Rust 中构成 Web 应用程序的所有其他组件,包括路由请求处理程序和数据模型,并学习如何在后端 Web 服务上调用 API。代替用户界面,我们将从命令行 HTTP 工具测试 Web 应用程序的 API。使用 Tera 模板为 Web 应用程序编写基于 HTML/javascript 的 UI 的任务主要留给读者作为练习。

让我们首先从导师登录(身份验证)功能开始。

9.1 设计用户身份验证

对于导师登录,我们将接受两个字段 - 用户名和密码,并使用它来向 Web 应用程序验证导师

图 1 显示了导师登录窗体。


图 9.1.导师登录表单

现在,让我们看一下图 2 中导师登录的工作流。请注意,图 2 中的术语 Actix Web 服务器是指前端 Web 应用程序服务器,而不是后端导师 Web 服务


图 9.2.导师登录流

  1. 用户访问着陆页网址。将显示导师登录表单
  2. 用户名和密码的基本验证是使用 HTML 功能在表单本身内执行的,而无需向 Web Actix 服务器发送请求。
  3. 如果验证中存在错误,则会向用户提供反馈。
  4. 用户提交登录表单POST 请求将发送到登录路由上的 Actix Web 服务器,然后该服务器将请求路由到相应的路由处理程序
  5. 路由处理程序函数通过从本地数据库检索用户凭据来验证用户名和密码。
  6. 如果身份验证不成功,则会向用户显示登录表单,并显示相应的错误消息。错误消息的示例包括不正确的用户名或密码。
  7. 如果用户身份验证成功,用户将被定向到导师 Web 应用程序的主页。

现在我们已经清楚了本章将要开发的内容,让我们设置项目代码结构和基本脚手架。

9.2 设置项目结构

首先克隆第 8 章中的 ezytutors 存储库。

然后,让我们将PROJECT_ROOT环境变量设置为 /path-to-folder/ezytutors/tutor-web-app-ssr。此后,我们将此文件夹称为 $PROJECT_ROOT。

让我们在项目根目录下组织代码,如下所示:

  1. 复制文件夹 $PROJECT_ROOT/src/iter5,并将其重命名为 $PROJECT_ROOT/src/iter6
  2. 复制文件夹 $PROJECT_ROOT/static/iter5,并将其重命名为 $PROJECT_ROOT/static/iter6。此文件夹将包含 html/tera 模板。
  3. 复制文件 $PROJECT_ROOT/src/bin/iter5-ssr.rs,并将其重命名为 $PROJECT_ROOT/src/bin/iter6-ssr.rs。此文件包含 main() 函数,该函数将配置和启动 Actix Web 服务器(为我们正在构建的 Web 应用程序提供服务)。在 iter6-ssr.rs 中,将所有对 iter5 的引用替换为 iter6

还要确保为 HOST_PORTDATABASE_URL环境变量正确配置 $PROJECT_ROOT 中的 .env 文件。

我们已准备好开始编码。

让我们从 $PROJECT_ROOT/src/iter6/routes.rs 中的路由定义开始。

use crate::handler::{handle_register, show_register_form, show_signin_form,
[CA]handle_signin}; #1
use actix_files as fs;
use actix_web::web;

pub fn app_config(config: &mut web::ServiceConfig) {
  config.service(
    web::scope("")
      .service(fs::Files::new("/static", "./static").show_files_listing())
      .service(web::resource("/").route(web::get().to(show_register_form)))
      .service(web::resource("/signinform").route(web::get().to(
      [CA]show_signin_form)))   #2
      .service(web::resource("/signin").route(web::post().to(
      [CA]handle_signin)))         #3
      .service(web::resource("/register").route(web::post().to(
      [CA]handle_register))),
    );
}

有了这个,我们可以继续在 $PROJECT_ROOT/src/iter6/model.rs 中进行模型定义。

TutorSigninForm 数据结构添加到 model.rs

// Form to enable tutors to sign in
#[derive(Serialize, Deserialize, Debug)]
pub struct TutorSigninForm {            #1
    pub username: String,
    pub password: String,
}

通过项目设置的基本结构,我们现在可以开始编写用于登录用户的代码。

9.3 实现用户身份验证

定义路由和数据模型后,让我们在 $PROJECT_ROOT/src/iter6/handler/auth.rs 中编写用于登录用户的处理程序函数。

首先,对导入进行以下更改:

use crate::model::{TutorRegisterForm, TutorResponse, TutorSigninForm, User};  #1

将以下处理程序函数添加到同一文件。在此文件中,将对 iter5 的引用替换为 iter6

pub async fn show_signin_form(tmpl: web::Data<tera::Tera>) ->
[CA]Result<HttpResponse, Error> { #1
    let mut ctx=tera::Context::new();
    ctx.insert("error", "");
    ctx.insert("current_name", "");
    ctx.insert("current_password", "");
    let s=tmpl
        .render("signin.html", &ctx)
        .map_err(|_| EzyTutorError::TeraError(
        [CA]"Template error".to_string()))?;

    Ok(HttpResponse::Ok().content_type("text/html").body(s))
}
pub async fn handle_signin(                                     #2
    tmpl: web::Data<tera::Tera>,
    app_state: web::Data<AppState>,
    params: web::Form<TutorSigninForm>,
) -> Result<HttpResponse, Error> {

Ok(HttpResponse::Ok().finish())
}

回想一下,调用show_signin_form处理程序函数是为了响应到达路由 /signinform 的请求,如路由定义中所定义。

让我们以 html 形式设计实际的登录。当用户选择登录EzyTutor Web应用程序时,将显示此表单。在 $PROJECT_ROOT/static/iter6 下创建一个新的文件 signin.html 文件,并向其中添加以下内容。请注意,应该已经有另一个文件寄存器.html已经存在于同一个文件夹中。

清单 9.1.导师登录表单

<!doctype html>
<html>

<head>
    <meta charset=utf-8>
    <title>Tutor registration</title>

    <style>                                         #1
        .header {
            padding: 20px;
            text-align: center;
            background: #fad980;
            color: rgb(48, 40, 43);
            font-size: 30px;
        }

        .center {
            margin: auto;
            width: 20%;
            min-width: 150px;
            border: 3px solid #ad5921;
            padding: 10px;
        }

        body,
        html {
            height: 100%;
            margin: 0;
            font-kerning: normal;
        }

        h1 {
            text-align: center;
        }

        p {
            text-align: center;
        }

        div {
            text-align: center;
        }

        div {
            background-color: rgba(241, 235, 235, 0.719);
        }

        body {
            background-image: url('/static/background.jpg');
            background-repeat: no-repeat;
            background-attachment: fixed;
            background-size: cover;
            height: 500px;
        }

        #button1,
        #button2 {
            display: inline-block;
        }

        #footer {
            position: fixed;
            padding: 10px 10px 0px 10px;
            bottom: 0;
            width: 100%;
            /* Height of the footer*/
            height: 20px;

        }
    </style>
</head>

<body>
    <div class="header">
        <h1>Welcome to EzyTutor</h1>
        <p>Start your own online tutor business in a few minutes</p>
    </div>

    <div class="center">
        <h2>
            Tutor sign in
        </h2>
        <form action=/signin method=POST>       #2

            <label for="userid">Enter username</label><br>            #3
            <input type="text" name="username" autocomplete="username"
            [CA]value="{{current_name}}" minlength="6"
                maxlength="12" required><br>
            <label for="password">Enter password</label><br>        #4
            <input type="password" name="password"
            [CA]autocomplete="new-password" value="{{current_password}}"
                minlength="8" maxlength="12" required><br>
            <label for="error">                                     #5
                <p style="color:red">{{error}}</p>
            </label><br>
            <button type=submit id="button2">Sign in</button>       #6

        </form>
        <form action=/ method=GET>                                  #7
            <button type=submit id="button2">Register</button>
        </form>
    </div>
    <p>
    <div id="footer">
        (c)Photo by Author
    </div>

    </p>

</html>

将另一个文件用户.html添加到 $PROJECT_ROOT/static/iter6。这将在用户成功登录后显示。

清单 9.2.用户通知屏幕

<!DOCTYPE html>
<html>

<head>
    <meta charset=\"utf-8\" />
    <title>{{title}}</title>
</head>

<body>
    <h1>Hi, {{name}}!</h1>
    <p>{{message}}</p>
</body>

</html>

最后,让我们看看 $PROJECT_ROOT/src/bin/iter6-ssr.rs__ 中的 main() 函数。将其修改为如下所示:

以下是导入:

#[path="../iter6/mod.rs"]
mod iter6;
use actix_web::{web, App, HttpServer};
use actix_web::web::Data;
use dotenv::dotenv;
use iter6::{dbaccess, errors, handler, model, routes, state};
use routes::app_config;
use sqlx::postgres::PgPool;
use std::env;
use tera::Tera;

而且,这是main()函数:

清单 9.3.main() 函数

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenv().ok();
    //Start HTTP server
    let host_port=env::var("HOST_PORT").expect(
      [CA]"HOST:PORT address is not set in .env file");
    println!("Listening on: {}", &host_port);
    let database_url=env::var("DATABASE_URL").expect(
      [CA]"DATABASE_URL is not set in .env file");
    let db_pool=PgPool::connect(&database_url).await.unwrap();
    // Construct App State
    let shared_data=web::Data::new(state::AppState { db: db_pool });

    HttpServer::new(move || {
        let tera=Tera::new(concat!(env!("CARGO_MANIFEST_DIR"),
        [CA]"/static/iter6/**/*")).unwrap();

        App::new()
            .app_data(Data::new(tera))
            .app_data(shared_data.clone())
            .configure(app_config)
    })
    .bind(&host_port)?
    .run()
    .await
}

我们现在可以测试了。从 $PROJECT_ROOT 运行以下命令。

cargo run --bin iter6-ssr

注意:如果您收到错误:没有针对 'u32 - usize 的实现

运行以下命令:

cargo update -p lexical-core

从浏览器中,访问以下路由:

localhost:8080/signinform

您应该能够看到登录表单。您还可以通过访问显示注册表单的索引路由 / 以及使用显示的按钮切换到登录表单来调用签名表单。

完成此操作后,即可实现用于登录用户的逻辑。将以下内容添加到 $PROJECT_ROOT/src/iter6/handler.rs。不要忘记删除具有之前创建的相同名称的占位符函数。

清单 9.4.用于登录的处理程序函数

pub async fn handle_signin(
    tmpl: web::Data<tera::Tera>,
    app_state: web::Data<AppState>,
    params: web::Form<TutorSigninForm>,
) -> Result<HttpResponse, Error> {
    let mut ctx=tera::Context::new();
    let s;
    let username=params.username.clone();
    let user=get_user_record(&app_state.db, username.to_string()).await; #1
    if let Ok(user)=user {
        let does_password_match=argon2::verify_encoded(
            &user.user_password.trim(),
            params.password.clone().as_bytes(),
        )
        .unwrap();
        if !does_password_match {                                       #2
            ctx.insert("error", "Invalid login");
            ctx.insert("current_name", ?ms.username);
            ctx.insert("current_password", ?ms.password);
            s=tmpl
                .render("signin.html", &ctx)
                .map_err(|_| EzyTutorError::TeraError(
                [CA]"Template error".to_string()))?;
        } else {                                                        #3
            ctx.insert("name", ?ms.username);
            ctx.insert("title", &"Signin confirmation!".to_owned());
            ctx.insert(
                "message",
                &"You have successfully logged in to EzyTutor!".to_owned(),
            );
            s=tmpl
                .render("user.html", &ctx)
                .map_err(|_| EzyTutorError::TeraError(
                [CA]"Template error".to_string()))?;
        }
    } else {                                                            #4
        ctx.insert("error", "User id not found");
        ctx.insert("current_name", ?ms.username);
        ctx.insert("current_password", ?ms.password);
        s=tmpl
            .render("signin.html", &ctx)
            .map_err(|_| EzyTutorError::TeraError(
            [CA]"Template error".to_string()))?;
    };

    Ok(HttpResponse::Ok().content_type("text/html").body(s))
}

Let’s test the signin function now. Run the following command from $PROJECT_ROOT.

cargo run --bin iter6-ssr

从浏览器中,访问以下路由:

localhost:8080/signinform

输入正确的用户名和密码。您应该会看到确认消息。

再次加载登录表单,这次为有效用户名输入错误的密码。验证是否收到错误消息。

尝试第三次输入表单,这次使用无效的用户名。同样,您应该会看到一条错误消息。

至此,我们结束本节。到目前为止,我们已经了解了如何使用Tera模板库定义模板来生成动态网页,以及如何向用户显示注册登录表单。我们还实现了用于注册登录用户以及处理用户输入中的错误的代码。我们还定义了自定义错误类型来统一错误处理。

现在让我们继续管理课程详细信息。

9.4 路由 HTTP 请求

在本节中,我们将添加导师维护课程的功能。

我们目前将所有处理程序函数都放在一个文件中。我们现在还必须添加处理程序以进行课程维护。因此,让我们首先将处理程序函数组织到它自己的模块中,以便能够跨多个源文件拆分处理程序函数。

首先在 $PROJECT_ROOT/src/iter6 下创建一个新的处理程序文件夹。

将$PROJECT_ROOT/src/iter6/handler.rs移动到$PROJECT_ROOT/src/iter6/handler中,并将其重命名为 auth.rs,因为这涉及注册和登录功能。(即 mv $PROJECT_ROOT/src/iter6/handler.rs $PROJECT_ROOT/src/iter6/handler/auth.rs in linux)。

$PROJECT_ROOT/src/iter6/handler 文件夹下 course.rs 和 mod.rs 创建新文件。在 mod.rs 中添加以下代码来构建处理程序文件夹中的文件,并将它们导出为 Rust 模块。

pub mod auth;   #1
pub mod course; #2

修改 $PROJECT_ROOT/src/iter6/routes.rs,如下所示:

清单 9.5.添加路线维护路线

use crate::handler::auth::{handle_register, handle_signin,
[CA]show_register_form, show_signin_form}; #1
use crate::handler::course::{handle_delete_course, handle_insert_course,
[CA]handle_update_course}; #2

use actix_files as fs;
use actix_web::web;

pub fn app_config(config: &mut web::ServiceConfig) {            #3
  config.service(
    web::scope("")
      .service(fs::Files::new("/static", "./static").show_files_listing())
      .service(web::resource("/").route(web::get().to(show_register_form)))
      .service(web::resource("/signinform").route(web::get().to(
      [CA]show_signin_form)))
      .service(web::resource("/signin").route(web::post().to(
      [CA]handle_signin)))
      .service(web::resource("/register").route(web::post().to(
      [CA]handle_register))),
    );
}

pub fn course_config(config: &mut web::ServiceConfig) {             #4
  config.service(
    web::scope("/courses")                                      #5
      .service(web::resource("new/{tutor_id}").route(web::post().to(
      [CA]handle_insert_course)))   #6
      .service(                                               #7
        web::resource("{tutor_id}/{course_id}").route(web::put().to(
        [CA]handle_update_course)),
      )
      .service(                                               #8
        web::resource("delete/{tutor_id}/{course_id}")
          .route(web::delete().to(handle_delete_course)),
      ),
  );
}

请注意,在我们指定 {tutor_id} 和 {course_id} 作为路径参数的地方,可以在 Actix Web 框架提供的提取器的帮助下从请求的路径中提取它们。

另外,请确保在 $PROJECT_ROOT/bin/iter6-ssr.rs 中添加新的课程维护路由,如下所示:

对导入语句:应用名称进行以下更改:

use routes::{app_config, course_config};

main() 函数中,进行更改以添加course_config路由。

    HttpServer::new(move || {
        let tera=Tera::new(concat!(env!("CARGO_MANIFEST_DIR"),
        [CA]"/static/iter6/**/*")).unwrap();

        App::new()
            .app_data(Data::new(tera))
            .app_data(shared_data.clone())
            .configure(course_config)      #1
            .configure(app_config)   #2
    })
    .bind(&host_port)?
    .run()
    .await

接下来,让我们现在在 $PROJECT_ROOT/src/iter6/handler/course.rs 中添加用于课程维护的占位符处理程序函数。稍后我们将编写实际逻辑来调用后端 Web 服务。

清单 9.6.课程维护处理程序函数的占位符

use actix_web::{web, Error, HttpResponse, Result};
use crate::state::AppState;

pub async fn handle_insert_course(
    _tmpl: web::Data<tera::Tera>,
    _app_state: web::Data<AppState>,
) -> Result<HttpResponse, Error> {
    println!("Got insert request");
    Ok(HttpResponse::Ok().body("Got insert request"))
}

pub async fn handle_update_course(
    _tmpl: web::Data<tera::Tera>,
    _app_state: web::Data<AppState>,
) -> Result<HttpResponse, Error> {
    Ok(HttpResponse::Ok().body("Got update request"))
}

pub async fn handle_delete_course(
    _tmpl: web::Data<tera::Tera>,
    _app_state: web::Data<AppState>,
) -> Result<HttpResponse, Error> {
    Ok(HttpResponse::Ok().body("Got delete request"))
}

正如您将注意到的,处理程序函数现在除了返回消息外什么都不做。我们将在本章后面实现预期的处理程序功能。

请注意在变量名称前使用下划线 (_)。这是因为,我们还没有在处理程序函数的主体中使用这些参数,因此在变量名称之前添加下划线将防止编译器警告。

让我们对这四条路线进行快速测试:

使用以下命令运行服务器:

cargo run --bin iter6-ssr

要测试 POSTPUTDELETE 请求,请从命令行尝试以下操作:

curl -H "Content-Type: application/json" -X POST -d '{}'
[CA]localhost:8080/courses/new/1
curl -H "Content-Type: application/json" -X PUT -d '{}'
[CA]localhost:8080/courses/1/2
curl -H "Content-Type: application/json" -X DELETE -d '{}'
[CA]localhost:8080/courses/delete/1/2

您应该看到从服务器返回的以下消息,对应于上面显示的三个 HTTP 请求:

Got insert request
Got update request
Got delete request

现在,我们已验证路由是否已正确建立,并且 HTTP 请求正在路由到正确的处理程序函数。在下一节中,让我们实现在处理程序函数中为导师添加课程的实际逻辑。

9.5 使用 HTTP POST 方法创建资源

在本部分中,我们将通过向后端导师 Web 服务发送 API 请求,为给定导师添加新课程。

转到第 6 章的代码存储库(即 /path-to-chapter4-folder/ezytutors/tutor-db ),并使用以下命令启动 tutor Web 服务

cargo run --bin iter5

导师 Web 服务现在应该已准备好接收来自导师 Web 应用程序的请求。现在让我们在 Web 应用程序中编写课程处理程序的代码,在 $PROJECT_ROOT/src/iter6/handler/course.rs 中。

修改 $PROJECT_ROOT/src/iter6/model.rs 以添加以下内容:

清单 9.7.课程维护的数据模型更改

#[derive(Deserialize, Debug, Clone)]
pub struct NewCourse {                      #1
    pub course_name: String,
    pub course_description: String,
    pub course_format: String,
    pub course_duration: String,
    pub course_structure: Option<String>,
    pub course_price: Option<i32>,
    pub course_language: Option<String>,
    pub course_level: Option<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct NewCourseResponse {                  #2
    pub course_id: i32,
    pub tutor_id: i32,
    pub course_name: String,
    pub course_description: String,
    pub course_format: String,
    pub course_structure: Option<String>,
    pub course_duration: String,
    pub course_price: Option<i32>,
    pub course_language: Option<String>,
    pub course_level: Option<String>,
    pub posted_time: String,
}

impl From<web::Json<NewCourseResponse>> for NewCourseResponse {     #3
    fn from(new_course: web::Json<NewCourseResponse>) -> Self {
        NewCourseResponse {
            tutor_id: new_course.tutor_id,
            course_id: new_course.course_id,
            course_name: new_course.course_name.clone(),
            course_description: new_course.course_description.clone(),
            course_format: new_course.course_format.clone(),
            course_structure: new_course.course_structure.clone(),
            course_duration: new_course.course_duration.clone(),
            course_price: new_course.course_price,
            course_language: new_course.course_language.clone(),
            course_level: new_course.course_level.clone(),
            posted_time: new_course.posted_time.clone(),
        }
    }
}

此外,请确保添加以下模块导入,这是 From 特征实现所必需的。

use actix_web::web;

接下来,让我们重写处理程序函数以创建新课程。在 $PROJECT_ROOT/src/iter6/handler/course.rs 中,添加以下模块导入:

use actix_web::{web, Error, HttpResponse, Result};  #1
use crate::state::AppState;
use crate::model::{NewCourse, NewCourseResponse, UpdateCourse, UpdateCourseResponse}; #2
use serde_json::json;                               #3

use crate::state::AppState;

然后修改handle_insert_course处理程序函数,如下所示:

清单 9.8.用于插入新课程的处理程序函数

pub async fn handle_insert_course(                  #1
    _tmpl: web::Data<tera::Tera>,                   #2
    _app_state: web::Data<AppState>,                #3
    path: web::Path<i32>,
    params: web::Json<NewCourse>,                   #4
) -> Result<HttpResponse, Error> {
    let tutor_id=path.into_inner();               #5
    let new_course=json!({                        #6
        "tutor_id": tutor_id,
        "course_name": ?ms.course_name,
        "course_description": ?ms.course_description,
        "course_format": ?ms.course_format,
        "course_structure": ?ms.course_structure,
        "course_duration": ?ms.course_duration,
        "course_price": ?ms.course_price,
        "course_language": ?ms.course_language,
        "course_level": ?ms.course_level

    });
    let awc_client=awc::Client::default();        #7
    let res=awc_client                            #8
        .post("http://localhost:3000/courses/")
        .send_json(&new_course)
        .await
        .unwrap()
        .body()
        .await?;
    println!("Finished call: {:?}", res);
    let course_response: NewCourseResponse=serde_json::from_str(
    [CA]&std::str::from_utf8(&res)?)?;    #9
    Ok(HttpResponse::Ok().json(course_response))                #10
}

从 $PROJECT_ROOT 构建并运行 Web ssr 客户端,如下所示:

cargo run --bin iter6-ssr

让我们使用 curl 请求测试新课程的创建。确保导师 Web 服务正在运行。从另一个终端,运行以下命令:

curl -X POST localhost:8080/courses/new/1 -d '{"course_name":"Rust web
[CA]development", "course_description":"Teaches how to write web apps in
[CA]Rust", "course_format":"Video", "course_duration":"3 hours",
[CA]"course_price":100}' -H "Content-Type: application/json"

通过在导师 Web 服务上运行 GET 请求来验证是否已添加新课程:

curl localhost:3000/courses/1

您应该在检索到 tutor-id=1 的课程列表中看到新课程。

在下一节中,我们将编写处理程序函数来更新课程。

9.6 使用 HTTP PUT 方法更新资源

让我们在 $PROJECT_ROOT/src/iter6/model.rs 文件中编写用于更新课程的数据结构。

清单 9.9.更新课程的数据模型更改

// Update course
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct UpdateCourse {                           #1
    pub course_name: Option<String>,
    pub course_description: Option<String>,
    pub course_format: Option<String>,
    pub course_duration: Option<String>,
    pub course_structure: Option<String>,
    pub course_price: Option<i32>,
    pub course_language: Option<String>,
    pub course_level: Option<String>,
}

#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct UpdateCourseResponse {                   #2
    pub course_id: i32,
    pub tutor_id: i32,
    pub course_name: String,
    pub course_description: String,
    pub course_format: String,
    pub course_structure: String,
    pub course_duration: String,
    pub course_price: i32,
    pub course_language: String,
    pub course_level: String,
    pub posted_time: String,
}

impl From<web::Json<UpdateCourseResponse>> for UpdateCourseResponse {   #3
    fn from(new_course: web::Json<UpdateCourseResponse>) -> Self {
        UpdateCourseResponse {
            tutor_id: new_course.tutor_id,
            course_id: new_course.course_id,
            course_name: new_course.course_name.clone(),
            course_description: new_course.course_description.clone(),
            course_format: new_course.course_format.clone(),
            course_structure: new_course.course_structure.clone(),
            course_duration: new_course.course_duration.clone(),
            course_price: new_course.course_price,
            course_language: new_course.course_language.clone(),
            course_level: new_course.course_level.clone(),
            posted_time: new_course.posted_time.clone(),
        }
    }
}

您还会注意到,我们已经定义了类似的数据结构,用于创建课程(NewCourse,NewCourseResponse)和更新课程(UpdateCourseUpdateCourseResponse)。 是否可以通过在创建和更新操作中重用相同的结构来优化?在实际项目场景中,可能会进行一些优化。但是,为了编写此示例代码,我们假设创建新课程所需的必填字段集与更新课程所需的必填字段集不同(其中没有必填字段)。此外,分离用于创建和更新操作的数据结构可以在学习时更容易理解。

接下来,让我们重写处理程序函数以更新 $PROJECT_ROOT/src/iter6/handler/course.rs 中的课程详细信息。

清单 9.10.用于更新课程的处理程序函数

pub async fn handle_update_course(
    _tmpl: web::Data<tera::Tera>,
    _app_state: web::Data<AppState>,
    web::Path((tutor_id, course_id)): web::Path<(i32, i32)>,
    params: web::Json<UpdateCourse>,
) -> Result<HttpResponse, Error> {
    let update_course=json!({                 #1
        "course_name": ?ms.course_name,
        "course_description": ?ms.course_description,
        "course_format": ?ms.course_format,
        "course_duration": ?ms.course_duration,
       "course_structure": ?ms.course_structure,
        "course_price": ?ms.course_price,
        "course_language": ?ms.course_language,
        "course_level": ?ms.course_level,

    });
    let awc_client=awc::Client::default();    #2
    let update_url=format!("http://localhost:3000/courses/{}/{}",
    [CA]tutor_id, course_id);   #3
    let res=awc_client                #4
        .put(update_url)
        .send_json(&update_course)
        .await
        .unwrap()
        .body()
        .await?;
    let course_response: UpdateCourseResponse=serde_json::from_str(
    [CA]&std::str::from_utf8(&res)?)?; #5

    Ok(HttpResponse::Ok().json(course_response))

}

确保导入与更新相关的结构,如下所示:

use crate::model::{NewCourse, NewCourseResponse, UpdateCourse, UpdateCourseResponse};

从 $PROJECT_ROOT 构建并运行 Web ssr 客户端,如下所示:

cargo run --bin iter6-ssr

让我们使用 curl 请求进行测试,以更新我们之前创建的课程。确保导师 Web 服务正在运行。在新终端中,运行以下命令。将导师 ID 和课程 ID 替换为之前创建的新课程的导师 ID 和课程 ID

curl  -X PUT -d '{"course_name":"Rust advanced web development",
[CA]"course_description":"Teaches how to write advanced web apps in Rust",
[CA]"course_format":"Video", "course_duration":"4 hours",
[CA]"course_price":100}' localhost:8080/courses/1/27 -H
[CA]"Content-Type: application/json"

通过在导师 Web 服务上运行 GET 请求来验证课程详细信息是否已更新:

curl localhost:3000/courses/1

注意:将 course_id:1 替换为您更新课程的tutor_id的正确值。

您应该会看到更新的课程详细信息已反映。

让我们继续删除课程。

9.7 使用 HTTP DELETE 方法删除资源

让我们更新处理程序函数以删除 $PROJECT_ROOT/src/iter6/handler/course.rs 中的课程。

示例 9.11.用于删除课程的处理程序函数

pub async fn handle_delete_course(
    _tmpl: web::Data<tera::Tera>,
    _app_state: web::Data<AppState>,
    path: web::Path<(i32, i32)>,    #1
) -> Result<HttpResponse, Error> {
    let (tutor_id, course_id)=path.into_inner();
    let awc_client=awc::Client::default();                    #2
    let delete_url=format!("http://localhost:3000/courses/{}/{}",
    [CA]tutor_id, course_id);   #3
    let _res=awc_client.delete(delete_url).send().await.unwrap(); #4
    Ok(HttpResponse::Ok().body("Course deleted"))                   #5
}

从 $PROJECT_ROOT 生成并运行导师 Web 应用,如下所示:

cargo run --bin iter6-ssr

运行删除请求,如下所示:

curl -X DELETE localhost:8080/courses/delete/1/19

tutor_idcourse_id替换为您自己的。

通过在导师 Web 服务上运行查询来验证课程是否已被删除。

curl localhost:3000/courses/1

tutor_id替换为您自己的。您应该看到该课程已在_tutor Web 服务中删除。

有了这个,我们已经看到了如何从用 Rust 编写的 Web 客户端前端添加、更新和删除课程。

作为练习,读者可以执行以下附加任务:

  1. 实现新路由以检索导师的课程列表
  2. 创建用于创建、更新和删除课程的 HTML/Tera 模板
  3. 为具有无效用户输入的情况添加其他错误处理。

9.8 小结

  • 在本章中,我们学习了如何在 Rust 中构建和编写一个与后端 Web 服务通信的 Web 应用程序项目。
  • 我们设计并实现了用户身份验证功能,允许用户在 HTML 表单中输入凭据,然后将其存储在本地数据库中。还介绍了用户输入中错误的处理。
  • 我们讨论了如何构建项目并模块化 Web 前端应用程序的代码,其中包括 HTTP 请求处理程序数据库交互逻辑数据模型Web UI/Html 模板
  • 我们编写了代码来创建、更新删除数据库中的特定数据,以响应 HTTP POSTPUTDELETE 方法请求。我们还了解如何提取作为 HTTP 请求的一部分发送的参数。
  • 我们学习了如何构造 HTTP 请求以在后端 Web 服务上调用 API,以及如何解释收到的响应,包括数据序列化和反序列化。
  • 总之,您已经学习了如何在 Rust 中构建一个 Web 应用程序,该应用程序可以与后端 Web 服务通信,与本地数据库交互,并对数据执行基本的创建、更新和删除操作以响应传入的 HTTP 请求。

至此,我们得出本章的结论,以及关于 Rust Web 应用程序开发的这一部分。

在下一章中,我们将介绍与 Rust 中的异步服务器相关的高级主题。

下一章见。