当前位置: 首页 > 工具软件 > Tokio-axum > 使用案例 >

rust axum 项目实践 deno js运行时集成

鲍飞星
2023-12-01

rust axum使用deno runtime

使用场景:
例如在创建订单的过程中,订单创建完成之后
需要根据订单的金额,和订单下单数量进行
1:增加用户积分
2:赠送优惠券
3:消息推送
。。。
对于这些需求来说 是根据营销规则定的,每次活动的规则不一样
需求不一样 只有创建订单是固定业务流程,所以说要把 这种边缘
需求进行可配置化处理
在这里想到要把执行逻辑脚本化,并放到数据库里
根据数据库的配置动态触发
这是时候就需要用到动态执行 代码的技术
那要看怎么实现了



前言

项目中我们使用到的脚本语言是JavaScript,把js存储到数据库里
根据资源的不通进行触发 多个脚本顺序执行


提示:以下是本篇文章正文内容,下面案例可供参考

一、deno 是什么?

deno 是rust 语言的 js runtime,对标的是nodejs,是这两年新兴的
js运行时,提供和nodejs一样的功能并且更强大

二、使用步骤

1.引入库

代码如下(示例):引入crate

deno_core = "0.130.0"
deno_runtime = "0.56.0"

2.使用JsRuntime

代码如下(示例):

use deno_core::op;
use deno_core::Extension;
use deno_core::JsRuntime;
use deno_core::RuntimeOptions;
use deno_core::*;
//自定义方法 这个方法会在 JavaScript进行调用
#[op]
fn op_sum(nums: Vec<f64>) -> Result<f64, deno_core::error::AnyError> {
  let sum = nums.iter().fold(0.0, |a, v| a + v);
  Ok(sum)
}

fn main() {
 //创建 Extension 并添加自定义方法op_sum 到js上下文
  let ext = Extension::builder()
    .ops(vec![
      op_sum::decl(),
    ])
    .build();
    //实例化JsRuntime 并设置 Extension
  let mut runtime = JsRuntime::new(RuntimeOptions {
    extensions: vec![ext],
    ..Default::default()
  });
  //执行 JavaScript脚本
  runtime
    .execute_script(
      "<usage>",
      r#"
          function print(value) {
            Deno.core.print(value.toString()+"\n");
          }
          const arr = [1, 2, 3];
          print("The sum of");
          print(arr);
          print("is");
          print(Deno.core.opSync('op_sum', arr));
          try {
            print(Deno.core.opSync('op_sum', 0));
          } catch(e) {
            print('Exception:');
            print(e);
          }
"#,
    )
    .unwrap();
}

该处使用的是deno_core 的JsRuntime运行时 这是一个最小版本的
js运行时 只是在v8引擎的基础上封装了高阶的api
对于日志打印来说 还是不能使用console.log()这种方式的
因为 对于 console ,http,fs …这些常规的运行时对象不在
deno_core的包含之内,所以使用的时候不能完全像使用nodejs
一样


3.使用MainWorker

代码如下(示例):

use deno_core::error::AnyError;
use deno_core::{FsModuleLoader, v8};
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
use deno_runtime::deno_web::BlobStore;
use deno_runtime::permissions::Permissions;
use deno_runtime::worker::MainWorker;
use deno_runtime::worker::WorkerOptions;
use deno_runtime::BootstrapOptions;
use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;

fn get_error_class_name(e: &AnyError) -> &'static str {
  deno_runtime::errors::get_error_class_name(e).unwrap_or("Error")
}

#[tokio::main]
async fn main() -> Result<(), AnyError> {
  let module_loader = Rc::new(FsModuleLoader);
  let create_web_worker_cb = Arc::new(|_| {
    todo!("Web workers are not supported in the example");
  });
  let web_worker_preload_module_cb = Arc::new(|_| {
    todo!("Web workers are not supported in the example");
  });
  let options = WorkerOptions {
    bootstrap: BootstrapOptions {
      args: vec![],
      cpu_count: 1,
      debug_flag: false,
      enable_testing_features: false,
      location: None,
      no_color: false,
      is_tty: false,
      runtime_version: "x".to_string(),
      ts_version: "x".to_string(),
      unstable: false,
    },
    extensions: vec![],
    unsafely_ignore_certificate_errors: None,
    root_cert_store: None,
    user_agent: "hello_runtime".to_string(),
    seed: None,
    source_map_getter: None,
    js_error_create_fn: None,
    web_worker_preload_module_cb,
    create_web_worker_cb,
    maybe_inspector_server: None,
    should_break_on_first_statement: false,
    module_loader,
    get_error_class_fn: Some(&get_error_class_name),
    origin_storage_dir: None,
    blob_store: BlobStore::default(),
    broadcast_channel: InMemoryBroadcastChannel::default(),
    shared_array_buffer_store: None,
    compiled_wasm_module_store: None,
  };
  let js_path =
    Path::new(env!("CARGO_MANIFEST_DIR"));
  let main_module = deno_core::resolve_path(&js_path.to_string_lossy())?;
  let permissions = Permissions::allow_all();

  let mut worker = MainWorker::bootstrap_from_options(
    main_module.clone(),
    permissions,
    options,
  );
  worker.execute_script("hello_runtime.js",r#"console.log("Hello world!")"#);
  worker.run_event_loop(false).await?;
  Ok(())
}

该处使用的是deno_runtime的MainWorker,deno_runtime是在
deno_core的基础上进行的二次封装扩展,在这里扩展了
deno_webidl,deno_console,deno_web等等

  /*初始化的时候扩展了
let mut extensions: Vec<Extension> = vec![
      // Web APIs
      deno_webidl::init(),
      deno_console::init(),
      deno_url::init(),
      deno_web::init::<Permissions>(
        options.blob_store.clone(),
        options.bootstrap.location.clone(),
      ),
     ...
    ];
  
 */
 let mut worker = MainWorker::bootstrap_from_options(
    main_module.clone(),
    permissions,
    options,
  );

项目实践

提示:这里主要是在项目中使用的代码

pub mod event_service;
pub mod ops;
use casbin::function_map::key_match2;
use cassie_domain::dto::sys_event_dto::EventConfigDTO;
use log::info;
use pharos::SharedPharos;
use serde_json::json;

use crate::{
    initialize::rules::init,
    observe::event::{CassieEvent, CustomEvent},
    service::crud_service::CrudService,
    APPLICATION_CONTEXT,
};

use self::event_service::EventConfigService;

use super::log::log_service::{LogLoginService, LogOperationService};

//事件消费
pub async fn consume(e: CassieEvent) {
    //在这里是获取不到 thread_local 的值 异步消费过来 已经不在同一个线程里了
    match e {
        //登录事件
        CassieEvent::LogLogin(dto) => {
            let mut entity = dto.into();
            let log_login_service = APPLICATION_CONTEXT.get::<LogLoginService>();
            log_login_service.save(&mut entity).await;
        }
        //操作事件
        CassieEvent::LogOperation(dto) => {
            let mut entity = dto.into();
            let log_operation_service = APPLICATION_CONTEXT.get::<LogOperationService>();
            log_operation_service.save(&mut entity).await;
        }
        //消息事件
        CassieEvent::Sms { sms_type } => todo!("待开发"),
        //自定义事件
        CassieEvent::Custom(custom) => {
            let event_config_service = APPLICATION_CONTEXT.get::<EventConfigService>();
            //获取到所有的事件配置
            let list = event_config_service.load_event().await;
            if let Ok(data) = list {
                let d = data
                    .iter()
                    .filter(|item| {
                        key_match2(&custom.path.clone().as_str(), &item.path().clone().unwrap())
                            || item.path().clone().unwrap().contains(&custom.path.clone())
                    })
                    .collect::<Vec<_>>();

                if d.len() > 0 {
                    execute_script(d, &custom);
                }
            }
        }
    }
}
//核心动态脚本执行方法
fn execute_script(data: Vec<&EventConfigDTO>, custom: &CustomEvent) {
    let init_code = format!(
        r#" var request_context=JSON.parse({});"#,
        serde_json::to_string_pretty(&serde_json::to_string_pretty(&json!(custom)).unwrap())
            .unwrap()
    );
    let mut workers = init(None);
    workers.execute_script("init_request_context", &init_code);
    //根据数据库的配置  循环执行JavaScript脚本 
    for event in data {
       match workers.js_runtime.execute_script(
            event.event_name().clone().unwrap().as_str(),
            event.event_script().clone().unwrap().as_str(),
        ){
            Ok(data) => {},
            Err(e) => {
                info!("error info {:#?}",e.to_string());
            },
        }
    }
}

//发布事件
pub async fn fire_event(e: CassieEvent) {
    let pharos = APPLICATION_CONTEXT.get::<SharedPharos<CassieEvent>>();
    pharos.notify(e).await;
}

1:增加用户积分
2:赠送优惠券
3:消息推送
ps:根据上述需求我们以下需求的 JavaScript脚本配置到数据库里,
根据router 进行配置. 当创建订单的时候发布一个自定义事件
事件消费的时候,根据router从数据里去取对应的 脚本进行排序执行,
当营销业务有所变动的时候不需要更改rust代码只需要动态
变更数据库脚本就可以了

项目源码

 类似资料: