使用Postman和JMeter进行signature签名

一、前言

​ 有些接口的请求会带上sign(签名)进行请求,各接口对sign的签名内容、方式可能不一样,但一般都是从接口的入参中选择部分内容组成一个字符串,然后再进行签名操作, 将结果赋值给sign; 完整规范的接口文档都会有sign的算法描述。这里通过Postman的Pre-request Script以及JMeter的BeanShell前置处理器进行接口签名的处理。(完整代码在每部分的最后)

被测系统teachSignServer:

Gitee:https://gitee.com/z417/knowledgebroadcast/tree/master/teachSignServer-tools

直接双击运行.exe文件即可(密钥文件与conf.ini需要与exe处于同一文件夹下)

其余工具:

1.bundle.js:https://github.com/joolfe/postman-util-lib/tree/master/postman

使用方式我们在后面使用到了再进行介绍

2.json.jar: https://mvnrepository.com/artifact/org.json/json

选择适合的版本

点击bundle

将下载的jar包置于jmeter的./lib/ext下并重启jmeter

被测接口信息:

接口 URL Method Body 签名规则
v0 http://127.0.0.1:5000/api/v0/teachsign POST {
"AppKey": "z417App",
"AppVer": "1.0.0",
"Data": "{"SPhone":"18662255783","EType":0}",
"DeviceName": "web",
"DeviceType": "web",
"Lang": "CN",
"Sign": "teachsign",
"TimeStamp": 1625456804
}
appkey,timestamp,data,secret四个字段的值拼接,使用32位md5进行签名
v1 http://127.0.0.1:5000/api/v1/teachsign POST {
"appid": "wxd930u",
"mch_id": 10100,
"device_info": 100,
"body": "{"EType":0}",
"DeviceType": "",
"nonce_str": "ibuaiVc",
"sign": "CD198C36632A274C49E5F2F028FA257C",
"source": null,
"ts": 1625456804
}
1. 参与签名运算的参数选用入参里边value非空的参数
2. 参与签名运算的参数按照ASCII顺序排序
3. 组合方式:key=value通过&符连接
4. 最后加上盐key=secret(secret在conf.ini中配置,同理后面的私钥与公钥也可在其中进行配置)
5. 使用32位md5进行签名,sign的字母全大写
v2 http://127.0.0.1:5000/api/v2/teachsign POST {
"busId": "",
"busCnl": "POS",
"requJnINo": "abceefgghkjlafksdffdsf",
"reqTxnTm": "16:30:16",
"serviceCode": "chengxusong",
"bussJnIno": "Arabic - Bahrain",
"sign": "fsdfsd",
"reqTxnDt": "20210907",
"nonceStr": "Language",
"sysCnl": "OKPOS",
"ts": 1631003416
}
1. 参与签名运算的参数选用入参里边value非空的参数
2. 参与签名运算的参数按照ASCII顺序排序
3. 使用private_key签名
4. 使用SHA256withRSA进行签名

二、v0接口

1.Postman

获取请求参数并将body的参数转换为json对象

var Json = JSON.parse(pm.request.body);

获取所需变量并将新的时间戳更新到json对象中

var TimeStamp = Date.parse(new Date()) / 1000 - 10;
Json.TimeStamp = TimeStamp;
var AppKey = Json.AppKey;
var Data = Json.Data;
var secretKey = "a323f9b6-1f04-420e-adb9-b06ty67b0e63";

字符串拼接

var str = AppKey + TimeStamp + Data + secretKey;

进行md5运算并将生成的hash序列转换为字符串

var strmd5= CryptoJS.MD5(str).toString();

修改json对象中sign并将md5对象写回body中

Json.Sign = strmd5;
pm.request.body.raw = JSON.stringify(Json); // 将修改后的JSON转换回字符串格式写回到请求体中

完整代码:

/*
vo加密规则:
appkey,timestamp,data,secret四个字段的值拼接,使用32位md5加密
*/ /*
* 获取请求参数
*/
var Json = JSON.parse(pm.request.body); // 将body的参数转换为json对象 /*
* 获取所需变量
*/
var TimeStamp = Date.parse(new Date()) / 1000 - 10; // 获取时间戳
Json.TimeStamp = TimeStamp; // 修改JSON
var AppKey = Json.AppKey;
var Data = Json.Data;
var secretKey = "a323f9b6-1f04-420e-adb9-b06ty67b0e63"; /*
* 拼接字符串并加密
*/
var str = AppKey + TimeStamp + Data + secretKey;
var strmd5= CryptoJS.MD5(str).toString(); // 调用方法进行md5运算并将生成的hash序列转换为字符串
Json.Sign = strmd5; // 修改JSON
pm.request.body.raw = JSON.stringify(Json); // 将修改后的JSON写回到请求体中

2.JMeter

在JMeter的时间戳可以直接使用JMeter自带函数在body中获取,当然也可以在BeanShell前置处理器中使用代码获取

/1000是因为JMeter默认生成的时间戳为13位时间戳,我们只需要10位即可。

导包(org.json为第三方jar包)

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.jmeter.config.*;
import org.json.*;

获取请求传入的body,将其转化为Json对象

// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataobj = new JSONObject(arg.getValue());

获取变量并拼接

String TimeStamp = dataobj.optString("TimeStamp");
String AppKey = dataobj.optString("AppKey");
String Data = dataobj.optString("Data");
String secretKey = "a323f9b6-1f04-420e-adb9-b06ty67b0e63";
String str = AppKey + TimeStamp + Data + secretKey;

进行md5运算

sign = DigestUtils.md5Hex(str);

修改json对象的sign并转换为字符串写回body中

dataobj.put("Sign", sign);  // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并歇回request-body中

完整代码:

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.jmeter.config.*;
import org.json.*; /*
* 获取请求传入的body,将其转化为Json对象
*/
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataobj = new JSONObject(arg.getValue()); /*
* 获取变量并拼接字符串
*/
// 获取变量
String TimeStamp = dataobj.optString("TimeStamp");
String AppKey = dataobj.optString("AppKey");
String Data = dataobj.optString("Data");
String secretKey = "a323f9b6-1f04-420e-adb9-b06ty67b0e63";
// 字符串拼接
String str = AppKey + TimeStamp + Data + secretKey; /*
* 签名,更新body
*/
sign = DigestUtils.md5Hex(str); // md5运算
dataobj.put("Sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并写回request-body中

三、v1接口

1.Postman

​ 获取请求参数并将body的参数转换为json对象

var Json = JSON.parse(pm.request.body);

获取时间戳并修改json对象

var ts = Date.parse(new Date()) / 1000 - 10;
Json.ts = ts;

去除sign参数本身,然后去除值是空的参数

var keys = [];
// 循环遍历JSON
for (let k in Json ){
// 排除json中键位sign以及值为空的数据
if (k == 'sign' || !Json[k]){
continue;
}
keys.push(k); // 生成筛选后的key序列
}

排序

keys.sort();

拼接字符串

let keys_str = '';
for (let x of keys){
keys_str += `${x}=${Json[x]}&`; // 使用模版字符串进行拼接
}
keys_str = keys_str + "key=a323f9b6-1f04-420e-adb9-b06ty67b0e63";

进行md5运算并将生成的hash序列转换为字母全大写字符串

var strmd5= CryptoJS.MD5(keys_str).toString().toUpperCase();

修改json对象中sign并将md5对象写回body中

Json.sign = strmd5;
pm.request.body.raw = JSON.stringify(Json);

完整代码:

/*
v1加密规则:
1. 参与签名运算的参数选用入参里边value非空的参数
2. 参与签名运算的参数按照ASCII顺序排序
3. 组合方式:key=value通过&符连接
4. 最后加上key=secret
5. 使用32位md5签名,sign的字母全大写
*/ /*
* 获取请求参数
*/
var Json = JSON.parse(pm.request.body);
var ts = Date.parse(new Date()) / 1000 - 10; // 获取时间戳
Json.ts = ts; // 修改json /*
* 去除sign参数本身,然后去除值是空的参数
*/
var keys = []; // 定义key序列
// 循环遍历JSON
for (let k in Json ){
// 排除json中键位sign以及值为空的数据
if (k == 'sign' || !Json[k]){
continue;
}
keys.push(k); // 生成筛选后的key序列
} /*
* 对请求参数排序
*/
keys.sort(); /*
* 拼接字符串
*/
let keys_str = '';
for (let x of keys){
keys_str += `${x}=${Json[x]}&`; // 使用模版字符串进行拼接
}
keys_str = keys_str + "key=a323f9b6-1f04-420e-adb9-b06ty67b0e63"; /*
* 签名并更新body
*/
var strmd5= CryptoJS.MD5(keys_str).toString().toUpperCase(); // 调用方法进行md5运算并将生成的hash序列转换为字母全大写字符串
Json.sign = strmd5; // 修改Json
pm.request.body.raw = JSON.stringify(Json);

2.JMeter

同样在body中使用内置函数定义时间戳ts

导包

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.jmeter.config.*;
import org.json.*;

获取请求传入的body,将其转化为Json对象

// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataobj = new JSONObject(arg.getValue());

获取Json的key

// 创建list存储body中的key值
List keyArry = new ArrayList();
// 生成迭代对象
Iterator iterator = dataObj.keys();
// 循环key,将其放入list
for (String key : iterator) {
if (!key.equals("sign") && !key.equals("Sign")) {
keyArry.add(key);
}
}

对list进行排序

Collections.sort(keyArry);

字符串拼接

String str = "";
for (String s : keyArry) {
// log.error(s);
String value = dataObj.optString(s);
// 剔除值为空或值为null的参数
if (!value.equals("") && !value.equals(null)) {
str = str+s+"="+ value+"&";
}
}
str = str + "key=a323f9b6-1f04-420e-adb9-b06ty67b0e63";

进行md5运算并转换为字母全大写

String sign = DigestUtils.md5Hex(str).toUpperCase();

修改json对象的sign并转换为字符串写回body中

dataobj.put("sign", sign);  // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并歇回request-body中

完整代码:

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.jmeter.config.*;
import org.json.*; /*
* 获取请求传入的body,将其转化为Json对象
*/
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataObj = new JSONObject(arg.getValue()); /*
* 获取Json的key进行排序
*/
// 创建list存储body中的key值
List keyArry = new ArrayList();
// 生成迭代对象
Iterator iterator = dataObj.keys();
// 循环key,将其放入list
for (String key : iterator) {
if (!key.equals("sign") && !key.equals("Sign")) {
keyArry.add(key);
}
} /*
* 对list进行排序
*/
Collections.sort(keyArry); /*
* 循环list中的key,读取对应的Value组成字符串
*/
String str = "";
for (String s : keyArry) {
String value = dataObj.optString(s);
// 剔除值为空或值为null的参数
if (!value.equals("") && !value.equals(null)) {
str = str+s+"="+ value+"&";
}
}
str = str + "key=a323f9b6-1f04-420e-adb9-b06ty67b0e63"; /*
* 签名并更新body
*/
String sign = DigestUtils.md5Hex(str).toUpperCase(); // 进行md5运算并转换为字母全大写
dataobj.put("sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并写回request-body中

四、v2接口

1.Postman

将下载后的json导入到postman,进入Lib install请求

发送请求,该请求会将bundle.js写入到全局变量中

获取请求参数并将body的参数转换为json对象

var Json = JSON.parse(pm.request.body);

获取时间戳并修改json对象

var ts = Date.parse(new Date()) / 1000 - 10;
Json.ts = ts;

去除sign参数本身,然后去除值是空的参数

var keys = [];
// 循环遍历JSON
for (let k in Json ){
// 排除json中键位sign以及值为空的数据
if (k == 'sign' || !Json[k]){
continue;
}
keys.push(k); // 生成筛选后的key序列
}

排序

keys.sort();

拼接字符串

let keys_str = '';
for (let x of keys){
keys_str += `${x}=${Json[x]}&`; // 使用模版字符串进行拼接
}
keys_str = keys_str.slice(0,-1); // 删除最后一个&

导入刚才写入到全局变量的js

eval(pm.globals.get("pmlib_code"));

由于私钥过长,所以这里把私钥的内容写到环境变量中,私钥内容可在pkcs8_rsa_private_key.pem查看(私钥与公钥可自行更换,在conf.ini中进行配置即可)

获取私钥

const privatekey = pm.environment.get("privatekey");

加密

const sha256withrsa = new pmlib.rs.KJUR.crypto.Signature({"alg": "SHA256withRSA"});  // 生成签名对象并制定为SHA256withRSA类型
sha256withrsa.init(privatekey); // 初始化privatekey
sha256withrsa.updateString(keys_str); // 更新要签名的数据
const sign = pmlib.rs.hextob64(sha256withrsa.sign()); // 签名并转换为Base64字符串

修改json对象中sign并将md5对象写回body中

Json.sign = sign;
pm.request.body.raw = JSON.stringify(Json); // 将修改后的JSON转换回字符串格式写回到请求体中

完整代码:

/*
v2加密规则:
1. 参与签名运算的参数选用入参里边value非空的参数
2. 参与签名运算的参数按照ASCII顺序排序
3. 使用private_key签名
4. 使用SHA256withRSA进行签名
*/ /*
* 获取请求参数
*/
var Json = JSON.parse(pm.request.body);
var ts = Date.parse(new Date()) / 1000 - 10; // 获取时间戳
Json.ts = ts; // 修改json /*
* 去除sign参数本身,然后去除值是空的参数
*/
var keys = []; // 定义key序列
// 循环遍历JSON
for (let k in Json ){
// 排除json中键位sign以及值为空的数据
if (k == 'sign' || !Json[k]){
continue;
}
keys.push(k); // 生成筛选后的key序列
} /*
* 对请求参数排序
*/
keys.sort(); /*
* 拼接字符串
*/
let keys_str = '';
for (let x of keys){
keys_str += `${x}=${Json[x]}&`; // 使用模版字符串进行拼接
}
keys_str = keys_str.slice(0,-1); // 删除最后一个& /*
* 加密并更新body
*/
eval(pm.globals.get("pmlib_code")); // 导入写入到全局变量的js const privatekey = pm.environment.get("privatekey"); // 从环境变量获取私钥 const sha256withrsa = new pmlib.rs.KJUR.crypto.Signature({"alg": "SHA256withRSA"}); // 生成签名对象并制定为SHA256withRSA类型 sha256withrsa.init(privatekey); // 初始化privatekey
sha256withrsa.updateString(keys_str); // 更新要签名的数据 const sign = pmlib.rs.hextob64(sha256withrsa.sign()); // 签名并转换为Base64字符串 Json.sign = sign;
pm.request.body.raw = JSON.stringify(Json);

2.JMeter

同样在body中使用内置函数定义时间戳ts,同时添加用户定义的变量配置元件来存放私钥

导包

import org.apache.jmeter.config.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.json.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;

获取请求传入的body,将其转化为Json对象

// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataobj = new JSONObject(arg.getValue());

获取Json的key

// 创建list存储body中的key值
List keyArry = new ArrayList();
// 生成迭代对象
Iterator iterator = dataObj.keys();
// 循环key,将其放入list
for (String key : iterator) {
if (!key.equals("sign") && !key.equals("Sign")) {
keyArry.add(key);
}
}

对list进行排序

Collections.sort(keyArry);

字符串拼接

String str = "";
for (String s : keyArry) {
// log.error(s);
String value = dataObj.optString(s);
// 剔除值为空或值为null的参数
if (!value.equals("") && !value.equals(null)) {
str = str+s+"="+ value+"&";
}
}
//删除最后一个&
str = str.substring(0,str.length()-1);

读取私钥

​ java中读取私钥需要删除前面的“-----BEGIN PRIVATE KEY-----”和后面的“-----END PRIVATE KEY-----”,且需要key首尾连接中间无换行或空格。

String privateKeyString = vars.get("privateKey");  // 从用户定义的变量中读取私钥
privateKeyString = privateKeyString.replace(" ", ""); // 删除多余的空格
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyString); // 将Base64解码转化为字符串
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); // 根据给定的编码密钥创建一个新的 PKCS8EncodedKeySpec
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec); // 生成RSA的私钥对象。

创建 Signature 对象并初始化

Signature signature = Signature.getInstance("SHA256withRSA");  // 生成SHA256withRSA的Signature 对象
signature.initSign(privateKey); // // 初始化签署签名的私钥

更新要签名的数据

signature.update(str.getBytes("UTF-8"));  // 更新要签名或验证的字节

签名

byte[] signatureBytes = signature.sign();  // 执行签名
String sign = Base64.getEncoder().encodeToString(signatureBytes); // 将签名结果转换为 Base64 字符串

修改json对象的sign并转换为字符串写回body中

dataobj.put("sign", sign);  // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并歇回request-body中

完整代码:

import org.apache.jmeter.config.*;
import org.apache.commons.codec.digest.DigestUtils;
import org.json.*;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec; /*
* 获取请求传入的body,将其转化为Json对象
*/
// 获取请求
Arguments arguments = sampler.getArguments();
// 获取请求中的body内容
Argument arg = arguments.getArgument(0);
// 获取body的value,并将其转化为JSONObject对象
JSONObject dataObj = new JSONObject(arg.getValue()); /*
* 获取Json的key进行排序
*/
// 创建list存储body中的key值
List keyArry = new ArrayList();
// 生成迭代对象
Iterator iterator = dataObj.keys();
// 循环key,将其放入list
for (String key : iterator) {
if (!key.equals("sign") && !key.equals("Sign")) {
keyArry.add(key);
// log.error(key);
}
}
// 对list进行排序
Collections.sort(keyArry); /*
* 循环list中的key,读取对应的Value组成字符串
*/
String str = "";
for (String s : keyArry) {
// log.error(s);
String value = dataObj.optString(s);
if (!value.equals("")) {
str = str+s+"="+ value+"&";
}
}
//删除最后一个&
str = str.substring(0,str.length()-1); /*
* 读取私钥
*/
String privateKeyString = vars.get("privateKey"); // 从用户定义的变量中读取私钥
privateKeyString = privateKeyString.replace(" ", ""); // 删除多余的空格
byte[] privateKeyBytes = Base64.getDecoder().decode(privateKeyString); // 将Base64解码转化为字符串
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes); // 根据给定的编码密钥创建一个新的 PKCS8EncodedKeySpec
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(keySpec); // 生成RSA的私钥对象。 /*
* 读创建 Signature 对象并初始化
*/
Signature signature = Signature.getInstance("SHA256withRSA"); // 生成SHA256withRSA的Signature 对象
signature.initSign(privateKey); // 初始化签署签名的私钥 //
/*
* 更新要签名的数据化
*/
signature.update(str.getBytes("UTF-8")); // 更新要签名或验证的字节 /*
* 签名并更新body
*/
byte[] signatureBytes = signature.sign(); // 签署所有更新字节的签名 // 将签名结果转换为 Base64 字符串
String sign = Base64.getEncoder().encodeToString(signatureBytes); // 编码为Base64字符串 dataobj.put("sign", sign); // 修改Sign
arg.setValue(dataobj.toString()); // 转换为字符串并歇回request-body中

【Postman&JMeter】使用Postman和JMeter进行signature签名的更多相关文章

  1. Jmeter学习一:Jmeter性能测试环境搭建(Windows下)

    最近刚开始接触Jmeter性能测试,现总结环境搭建如下: 一.windows安装JDK步骤与环境变量配置: 1.先将下载的JDK安装到其默认目录:C:\Program Files\Java\jdk1. ...

  2. JMeter入门(1):JMeter总体介绍及组件介绍

    一.JMeter概述 JMeter就是一个测试工具,相比于LoadRunner等测试工具,此工具免费,且比较好用,但是前提当然是安装Java环境: JMeter可以做 (1)压力测试及性能测试: (2 ...

  3. JMeter接口测试系列:Jmeter+jenkins+ant 的自动化构建

    在JMeter接口测试不断深入的过程中,发现可以和jenkins和ant一起搭配进行自动化的构建.下面是jmeter自动化构建的整理笔记. 准备环境 需要本机上确定安装了jmeter.ant和jenk ...

  4. JMeter学习笔记——认识JMeter(1)

    拿到一个自动化测试工具,我们第一步就应该了解它能提供我们哪方面的功能(最直接的方法就是从官网获取),接下来就是简单的对这个工具进行“功能测试”了,当然这里的功能测试不是让你找它存在的bug,而是让自己 ...

  5. 【JMeter】如何用JMeter进行压力测试

    [JMeter]如何用JMeter进行压力测试(调试脚本已再猪猪微信的收藏中进行了收藏哦,名字叫exam-wow.jmx) 一.用badboy录制压测过程形成脚本另存为jmeter格式文档. 二.JM ...

  6. Jmeter接口测试自动化(jmeter+ant+jenkins持续集成)

    Jmeter是压力测试.接口测试工具,Ant是基于Java的构建工具,具有跨平台的作用,jenkins是持续集成工具.将这三者结合起来可以搭建一套webservice接口测试的持续构建环境.   1. ...

  7. Jmeter学习前提:Jmeter安装

    一.Jmeter下载 1. 前提:已经安装 jdk8+ 1.1 JDK下载 a. 进入jdk8+下载地址:https://www.oracle.com/technetwork/java/javase/ ...

  8. Postman学习之Postman简介

    前言:对于测试人员来说,接口测试是必须掌握的一个技能:在工作中掌握了接口自动化测试无疑是如虎添翼,那么怎么开展接口测试呢?下面将介绍一款接口测试的神器——postman 1.postman背景介绍 p ...

  9. 『动善时』JMeter基础 — 2、JMeter的安装和启动

    1.安装Java环境 由于JMeter是纯Java的桌面应用程序,因此它的运行环境需要Java环境,即需要安装JDK或JRE.(也就是安装JDK环境) 步骤简要说明: 下载并安装JDK 配置环境变量 ...

  10. 『动善时』JMeter基础 — 7、jmeter.properties文件常用配置

    目录 1.默认语言设置 2.配置默认编码格式 3.GUI图标放大比例设置 4.功能区工具栏图标大小设置 5.视图区目录树图标大小设置 6.内容区编辑字体设置 7.添加JMeter元素快捷键设置 8.捕 ...

随机推荐

  1. python 运行环境变为 pytest in (for) xxx.py原因

    因为本人的自定义函数名称开头为test,在.py文件内我用了unittest框架,所以环境随着变化了. 修改回去很简单,只要不使用test开头或者换个文件夹.

  2. 在 Visual Studio 2022 中使用文件对比

    在最新版本的 Visual Studio 2022 中,加入了新的功能特性--"文件对比". 在开发过程中,开发人员有时会需要比对文件差异,特别是代码文件,之前很多时候是借助版本控 ...

  3. Dami 本地过程调用框架(主打解耦),v0.24 发布

    Dami,专为本地多模块之间通讯解耦而设计(尤其是未知模块.隔离模块.领域模块).零依赖,特适合 DDD. 特点 结合 Bus 与 RPC 的概念,可作事件分发,可作接口调用,可作异步响应. 支持事务 ...

  4. 【uniapp】【微信小程序】wxml-to-canvas

    真是搞吐了,研究了整整两天,困死我了 本来使用生成二维码插件好好的,插件页也支持导出二维码图片,可是领导说要带上文件的名称,那就涉及html转图片了,当然也可以改二维码插件的源码,不过源码做了混淆,看 ...

  5. 产品代码都给你看了,可别再说不会DDD(六):聚合根与资源库

    这是一个讲解DDD落地的文章系列,作者是<实现领域驱动设计>的译者滕云.本文章系列以一个真实的并已成功上线的软件项目--码如云(https://www.mryqr.com)为例,系统性地讲 ...

  6. xmind文件数据解析重构成mindmap可识别数据

    [需求背景] 测试平台开发中,需要引入前端mindmap模块,进行在线xmind实时解析并前端展示 [卡点难点] 选取什么库进行xmind解析 如何转换成mindmap可以识别的数据 [xmind解析 ...

  7. Solution -「CCPC Winter Camp Day 6 A」Convolution

    Description Link. 给定一个数列 \(\sf a_1,a_2,....a_n\),请求出下面这个结果在模 \(\sf 998244353\) 下的答案. \[\sum_{i=1}^{n ...

  8. C/C++基础——引用与指针有什么区别?C++中输入输出加速

    文章目录 1 引用与指针有什么区别? 2 C++中输入输出加速 tie sync_with_stdio 应用 1 引用与指针有什么区别? 指针和引用都是地址的概念,指针指向一块内存,它的内容是所指内存 ...

  9. 19c上ADG主库sys密码修改会影响备库同步吗?

    一套Oracle 19c的ADG集群要修改sys密码,由于之前遇见过11g上sys密码修改导致同步问题的情况,所以改之前特意查了下文档,发现其实12cR2开始,在主库修改密码就会自动同步到备库了,以下 ...

  10. 算法修养--A*寻路算法

    A*寻路算法 广度优先算法 广度优先算法搜索以广度做未优先级进行搜索. 从起点开始,首先遍历起点周围邻近的点,然后再遍历已经遍历过的点邻近的点,逐步的向外扩散,直到找到终点. 这种算法就像洪水(Flo ...