stm32 web get 参数_使用Rust、Actix-web和MongoDB构建简单博客网站-02

前言

上次留了一些问题,现在我们来解决一下。

  • 对请求时的反序列化异常进行处理
  • 带条件参数的查询接口

本文完整源码见GITHUB Repo: https://github.com/nintha/demo-myblog

对请求时的反序列化异常进行处理

问题复现:

我们构造一个这样的请求,body里面的JSON字符串比正常少了一个content字段

POST /articles
body {
    "title": "简易博客指南",
    "author": "栗子球"
}

响应内容:

Json deserialize error: trailing comma at line 5 column 1

这是普通文本,我们需要把这个错误信息捕获并转换为统一的错误格式

在 main函数里面修改

// main.rs
fn main() {
    init_logger();

    let binding_address = "0.0.0.0:8000";
    let server = HttpServer::new(|| App::new()
        // change json extractor configuration
        .data(
              web::Json::<Article>::configure(|cfg| {
                  cfg.error_handler(|err, _req| {
                      // <- create custom error response
                      log::error!("json extractor error, path={}, {}", _req.uri(), err);
                      let resp = Resp::err(10002, "argument error");
                      error::InternalError::from_response(
                          err,
                          HttpResponse::BadRequest().json(resp),
                      ).into()
                  })
              })
        )
        .service(
            web::scope("/articles")
                .route("", web::get().to(article::list_article))
                .route("", web::post().to(article::save_article))
                .route("{id}", web::put().to(article::update_article))
                .route("{id}", web::delete().to(article::remove_article))
        ))
        .bind(binding_address)
        .expect("Can not bind to port 8000");

    server.run().unwrap();
}

这里调用了.data(..)方法,对JSON反序列化的异常进行自定义处理,对于原始错误信息使用日志进行打印,对于前端返回内容,则是用我们自定义错误信息

{
    "code": 10002,
    "message": "argument error",
    "data": null
}

带条件参数的查询接口

首先我们声明一个新的结构体,用来承载查询条件参数

// article/mod.rs

#[derive(Deserialize, Serialize, Debug)]
pub struct ArticleQuery {
    #[serde(deserialize_with = "deserialize_object_id", default)]
    _id: Option<ObjectId>,
    #[serde(default)]
    keyword: String,
}

_id用于精确匹配,keyword用于模糊匹配title/author/content字段内容。

由于这些参数是可选的,所以_id用Option包一层,keyword 是字符串,可以直接用默认值进行处理。Option的默认值是None,String 的默认值是空字符串。

ObjectId默认的反序列化肯定是不好用的,我们希望用户传递一个字符串就可以了,所以需要对_id字段指定自定义反序列化函数。反序列化函数如下所示:

// article/mod.rs

pub fn deserialize_object_id<'de, D>(deserializer: D) -> Result<Option<ObjectId>, D::Error>
    where D: Deserializer<'de> {

    struct JsonOptionObjectIdVisitor;

    impl<'de> de::Visitor<'de> for JsonOptionObjectIdVisitor {
        type Value = Option<ObjectId>;

        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("an object id hash value")
        }

        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: de::Error {
            if v.is_empty() {
                return Ok(None);
            }
            Ok(ObjectId::with_string(v).ok())
        }
    }

    deserializer.deserialize_any(JsonOptionObjectIdVisitor)
}

然后就可以使用这个定义好的结构体了,把之前的查询接口函数

pub fn list_article() -> SimpleResp {
    ..
}

改成

pub fn list_article(query: web::Json<ArticleQuery>) -> SimpleResp {
    let query = query.into_inner();

    // 构造查询参数
    let mut d: Document = doc! {};
    if query._id.is_some() {
        d.insert("_id", query._id.unwrap());
    }

    if !query.keyword.is_empty() {
        d.insert("$or", bson::Bson::Array(vec![
            doc! {"title": {"$regex": &query.keyword, "$options": "i"}}.into(),
            doc! {"author": {"$regex": &query.keyword, "$options": "i"}}.into(),
            doc! {"content": {"$regex": &query.keyword, "$options": "i"}}.into(),
        ]));
    }

    let coll = collection("article");
    let cursor = coll.find(Some(d), None);
    let result = cursor.map(|mut x| x.as_vec::<Article>());
    match result {
        Ok(list) => Resp::ok(list).to_json_result(),
        Err(e) => {
            error!("list_article error, {}", e);
            return Err(BusinessError::InternalError);
        }
    }
}

这里我们先判断下每个参数是否有传递上来,对于没有的参数进行忽略。模糊匹配这里使用了大小写不敏感正则匹配,为了覆盖多个字段,则是用了mongodb 提供的$or逻辑操作符。

测试下效果,这里使用了带 body的GET请求,首先是空参数

GET /articles
body {}

响应内容:

{
    "code": 0,
    "message": "ok",
    "data": [
        {
            "_id": "5d8f70a10009c1f200be8cae",
            "title": "七天学会Rust",
            "author": "noone",
            "content": "如果七天学不会,那就再学七天"
        },
        {
            "_id": "5d8f76ed00e36d9600b7604d",
            "title": "简易博客指南",
            "author": "栗子球",
            "content": "本文介绍如何使用Actix-web和MongoDB构建简单博客网站..."
        },
        {
            "_id": "5dca98f100fe6fda00f51150",
            "title": "rust从入门到放弃",
            "author": "佚名",
            "content": "Hello World"
        }
    ]
}

带上_id参数的请求

GET /articles
body {
    "_id": "5d8f70a10009c1f200be8cae"
}

响应内容:

{
    "code": 0,
    "message": "ok",
    "data": [
        {
            "_id": "5d8f70a10009c1f200be8cae",
            "title": "七天学会Rust",
            "author": "noone",
            "content": "如果七天学不会,那就再学七天"
        }
    ]
}

带上keyword参数的请求

GET /articles
body {
    "keyword": "rust"
}

响应内容:

{
    "code": 0,
    "message": "ok",
    "data": [
        {
            "_id": "5d8f70a10009c1f200be8cae",
            "title": "七天学会Rust",
            "author": "noone",
            "content": "如果七天学不会,那就再学七天"
        },
        {
            "_id": "5dca98f100fe6fda00f51150",
            "title": "rust从入门到放弃",
            "author": "佚名",
            "content": "Hello World"
        }
    ]
}

后记

这次我们解决了上次遗留下来的一部分问题。很多时候解决一个问题会引发另一个问题,比如为了模糊查询,我们这里使用了正则匹配+$or来实现的,可能在数据量比较大的时候性能表现并不理想,到时候要考虑是否替换成全文检索进行实现。

下一篇应该快了 (咕咕)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值