使用场景:
例如在创建订单的过程中,订单创建完成之后
需要根据订单的金额,和订单下单数量进行
1:增加用户积分
2:赠送优惠券
3:消息推送
。。。
对于这些需求来说 是根据营销规则定的,每次活动的规则不一样
需求不一样 只有创建订单是固定业务流程,所以说要把 这种边缘
需求进行可配置化处理
在这里想到要把执行逻辑脚本化,并放到数据库里
根据数据库的配置动态触发
这是时候就需要用到动态执行 代码的技术
那要看怎么实现了
项目中我们使用到的脚本语言是JavaScript,把js存储到数据库里
根据资源的不通进行触发 多个脚本顺序执行
提示:以下是本篇文章正文内容,下面案例可供参考
deno 是rust 语言的 js runtime,对标的是nodejs,是这两年新兴的
js运行时,提供和nodejs一样的功能并且更强大
代码如下(示例):引入crate
deno_core = "0.130.0"
deno_runtime = "0.56.0"
代码如下(示例):
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(
"" ,
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
一样
代码如下(示例):
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 = vec![
// Web APIs
deno_webidl::init(),
deno_console::init(),
deno_url::init(),
deno_web::init::(
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代码只需要动态
变更数据库脚本就可以了
项目源码