refactor: 重构文档结构和文件位置

docs: 添加Redis集成测试文档
docs: 添加ID生成器分析报告
docs: 添加自由布局和固定布局示例文档
test: 添加ID生成器单元测试
fix: 删除重复的前端文档文件
This commit is contained in:
2025-09-24 01:10:01 +08:00
parent adcd49a5db
commit 6ff587dc23
26 changed files with 302 additions and 6 deletions

View File

@ -1,78 +0,0 @@
# Redis集成测试
这个文件包含了Redis集成的测试说明。
## 环境配置
1. 复制环境配置文件:
```bash
cp .env.example .env
```
2. 根据需要修改 `.env` 文件中的Redis配置
```bash
REDIS_URL=redis://:123456@127.0.0.1:6379/9
```
## 启动Redis服务
确保Redis服务器正在运行
```bash
# 使用Docker运行Redis
docker run -d -p 6379:6379 --name redis-udmin redis:7-alpine --requirepass 123456
# 或者使用本地Redis服务
redis-server --requirepass 123456
```
## 测试Redis连接
启动服务器:
```bash
cargo run
```
查看日志中是否有Redis连接成功的信息
```
[INFO] Connecting to Redis at: redis://:***@127.0.0.1:6379/9
[INFO] Redis connection established successfully
```
## 测试Token存储
1. 登录获取token
```bash
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"Admin@123"}'
```
2. 检查Redis中的token
```bash
# 连接到Redis CLI
redis-cli -a 123456 -n 9
# 查看所有token键
KEYS token:*
# 查看特定用户的token
GET token:access:user:1
GET token:refresh:user:1
```
## 功能验证
Redis集成后的新功能
1. **Token存储**所有JWT token都存储在Redis中
2. **Token验证**每次API调用都会验证Redis中的token
3. **Token撤销**logout时会从Redis中删除token
4. **性能提升**:减少数据库查询,提升认证性能
5. **单点登录**:防止多重登录(可配置)
## 环境变量说明
- `REDIS_URL`: Redis连接字符串
- `REDIS_TOKEN_VALIDATION`: 是否启用Redis token验证默认true
- `JWT_ACCESS_EXP_SECS`: 访问token过期时间
- `JWT_REFRESH_EXP_SECS`: 刷新token过期时间

View File

@ -1,19 +0,0 @@
{
"name": "Branch A->COND->B/C (async)",
"code": "branch_async",
"design_json": {
"name": "Branch A->COND->B/C (async)",
"execution_mode": "async",
"nodes": [
{ "id": "A", "kind": "http", "name": "http A", "task": "http", "config": { "url": "https://httpbin.org/get", "method": "GET" } },
{ "id": "COND", "kind": "condition", "name": "cond", "task": "condition", "config": { "expression": { "left": { "type": "constant", "value": true }, "operator": "is_true", "right": { "type": "constant", "value": true } } }, "ports": { "yes": { "id": "yes" }, "no": { "id": "no" } } },
{ "id": "B", "kind": "http", "name": "http B", "task": "http", "config": { "url": "https://httpbin.org/anything/B", "method": "GET" } },
{ "id": "C", "kind": "http", "name": "http C", "task": "http", "config": { "url": "https://httpbin.org/anything/C", "method": "GET" } }
],
"edges": [
{ "from": "A", "to": "COND" },
{ "from": "COND", "to": "B", "condition": { "left": { "type": "constant", "value": true }, "operator": "is_true", "right": { "type": "constant", "value": true } } },
{ "from": "COND", "to": "C", "condition": { "left": { "type": "constant", "value": true }, "operator": "is_false", "right": { "type": "constant", "value": true } } }
]
}
}

View File

@ -1,22 +0,0 @@
{
"name": "branch-sync",
"code": "branch_sync_1",
"design_json": {
"name": "branch-sync",
"executionMode": "sync",
"nodes": [
{ "id": "A", "type": "http", "data": { "title": "A-GET-x", "api": { "method": "GET", "url": "https://httpbin.org/get?x=hello" } } },
{ "id": "COND", "type": "condition", "data": { "title": "COND", "conditions": [
{ "key": "yes", "value": { "left": { "type": "constant", "content": true }, "operator": "is_true" } },
{ "key": "no", "value": { "left": { "type": "constant", "content": false }, "operator": "is_true" } }
] } },
{ "id": "B", "type": "http", "data": { "title": "B-UUID", "api": { "method": "GET", "url": "https://httpbin.org/uuid" } } },
{ "id": "C", "type": "http", "data": { "title": "C-Delay1s", "api": { "method": "GET", "url": "https://httpbin.org/delay/1" } } }
],
"edges": [
{ "sourceNodeID": "A", "targetNodeID": "COND" },
{ "sourceNodeID": "COND", "targetNodeID": "B", "sourcePortID": "yes" },
{ "sourceNodeID": "COND", "targetNodeID": "C", "sourcePortID": "no" }
]
}
}

View File

@ -1,5 +0,0 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 1756996792 refresh_token eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInVpZCI6MSwiaXNzIjoidWRtaW4iLCJleHAiOjE3NTY5OTY3OTIsInR5cCI6InJlZnJlc2gifQ.XllW1VXSni2F548WFm1tjG3gwWPou_QE1JRabz0RZTE

View File

@ -1,3 +0,0 @@
{
"yaml": "# demo flow\nnodes:\n - { id: start, kind: start, name: 开始 }\n - { id: assign, kind: custom, name: 赋值, script: ctx.x = ctx.x + 1; }\n - { id: cond, kind: custom, name: 条件 }\n - { id: end, kind: end, name: 结束 }\nedges:\n - { from: start, to: assign }\n - { from: assign, to: cond }\n - { from: cond, to: end, condition: ctx.x >= 1 }\n - { from: cond, to: end }\n"
}

View File

@ -1,15 +0,0 @@
{
"name": "linear-async",
"code": "linear_async_1",
"design_json": {
"name": "linear-async",
"executionMode": "async",
"nodes": [
{ "id": "N1", "type": "http", "data": { "title": "A-GET", "api": { "method": "GET", "url": "https://httpbin.org/delay/1" } } },
{ "id": "N2", "type": "http", "data": { "title": "B-UUID", "api": { "method": "GET", "url": "https://httpbin.org/uuid" } } }
],
"edges": [
{ "sourceNodeID": "N1", "targetNodeID": "N2" }
]
}
}

View File

@ -1,15 +0,0 @@
{
"name": "linear-sync",
"code": "linear_sync_1",
"design_json": {
"name": "linear-sync",
"executionMode": "sync",
"nodes": [
{ "id": "N1", "type": "http", "data": { "title": "A-GET", "api": { "method": "GET", "url": "https://httpbin.org/get" } } },
{ "id": "N2", "type": "http", "data": { "title": "B-UUID", "api": { "method": "GET", "url": "https://httpbin.org/uuid" } } }
],
"edges": [
{ "sourceNodeID": "N1", "targetNodeID": "N2" }
]
}
}

View File

@ -78,4 +78,155 @@ const REQUEST_LOG_SUB_ID: u8 = 1;
pub fn generate_request_log_id() -> i64 {
generate_biz_id(BizIdConfig::new(REQUEST_LOG_MAIN_ID, REQUEST_LOG_SUB_ID))
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
use std::time::Duration;
use std::collections::HashSet;
#[test]
fn test_id_sequential_generation() {
// 测试1: 连续生成ID验证递增性
let mut prev_id = 0i64;
for i in 1..=10 {
let current_id = generate_id();
println!("ID {}: {}", i, current_id);
if i > 1 {
assert!(current_id > prev_id,
"ID {} ({}) 应该大于前一个ID {} ({})", i, current_id, i-1, prev_id);
}
prev_id = current_id;
}
}
#[test]
fn test_id_time_interval_generation() {
// 测试2: 间隔时间生成ID验证时间戳影响
let mut time_based_ids = Vec::new();
for i in 1..=5 {
let id = generate_id();
time_based_ids.push(id);
println!("时间间隔ID {}: {}", i, id);
thread::sleep(Duration::from_millis(10)); // 减少等待时间以加快测试
}
// 验证时间间隔ID的递增性
for i in 1..time_based_ids.len() {
assert!(time_based_ids[i] > time_based_ids[i-1],
"时间间隔ID {} ({}) 应该大于前一个ID ({})",
i+1, time_based_ids[i], time_based_ids[i-1]);
}
}
#[test]
fn test_different_business_id_types() {
// 测试3: 不同业务类型ID的递增性
let flow_id1 = generate_biz_id(BizIdConfig::new(1, 1));
thread::sleep(Duration::from_millis(1));
let flow_id2 = generate_biz_id(BizIdConfig::new(1, 1));
thread::sleep(Duration::from_millis(1));
let log_id1 = generate_biz_id(BizIdConfig::new(2, 1));
thread::sleep(Duration::from_millis(1));
let log_id2 = generate_biz_id(BizIdConfig::new(2, 1));
println!("Flow ID 1: {}", flow_id1);
println!("Flow ID 2: {}", flow_id2);
println!("Log ID 1: {}", log_id1);
println!("Log ID 2: {}", log_id2);
// 验证同类型业务ID递增
assert!(flow_id2 > flow_id1, "Flow ID 2 应该大于 Flow ID 1");
assert!(log_id2 > log_id1, "Log ID 2 应该大于 Log ID 1");
}
#[test]
fn test_concurrent_id_generation() {
// 测试4: 多线程并发生成ID测试
let handles: Vec<_> = (0..3).map(|thread_id| {
thread::spawn(move || {
let mut thread_ids = Vec::new();
for _ in 0..5 {
thread_ids.push(generate_id());
thread::sleep(Duration::from_millis(1));
}
(thread_id, thread_ids)
})
}).collect();
let mut all_ids = Vec::new();
for handle in handles {
let (thread_id, ids) = handle.join().unwrap();
println!("线程 {} 生成的ID: {:?}", thread_id, ids);
all_ids.extend(ids);
}
// 验证所有ID的唯一性
let unique_ids: HashSet<_> = all_ids.iter().collect();
assert_eq!(all_ids.len(), unique_ids.len(),
"发现重复ID总数: {}, 唯一数: {}", all_ids.len(), unique_ids.len());
}
#[test]
fn test_id_timestamp_parsing() {
// 测试5: 解析ID验证时间戳部分
let id1 = generate_id();
thread::sleep(Duration::from_millis(10));
let id2 = generate_id();
// 提取时间戳部分低39位中的时间戳
let timestamp1 = id1 & ((1i64 << 39) - 1);
let timestamp2 = id2 & ((1i64 << 39) - 1);
println!("ID1: {}, 时间戳部分: {}", id1, timestamp1);
println!("ID2: {}, 时间戳部分: {}", id2, timestamp2);
// 注意在同一毫秒内生成的ID时间戳部分可能相同但序列号会递增
// 所以这里只验证ID2不小于ID1
assert!(id2 > id1, "ID2 应该大于 ID1");
}
#[test]
fn test_biz_id_parsing() {
// 测试6: 业务ID解析功能
let config = BizIdConfig::new(123, 45);
let id = generate_biz_id(config);
let (main_id, sub_id, base_id) = parse_biz_id(id);
assert_eq!(main_id, 123, "解析的main_id应该等于123");
assert_eq!(sub_id, 45, "解析的sub_id应该等于45");
assert!(base_id > 0, "解析的base_id应该大于0");
println!("原始ID: {}, 解析结果: main_id={}, sub_id={}, base_id={}",
id, main_id, sub_id, base_id);
}
#[test]
fn test_specific_id_generators() {
// 测试7: 特定业务ID生成器
let flow_log_id1 = generate_flow_run_log_id();
let flow_log_id2 = generate_flow_run_log_id();
let request_log_id1 = generate_request_log_id();
let request_log_id2 = generate_request_log_id();
// 验证递增性
assert!(flow_log_id2 > flow_log_id1, "流程日志ID应该递增");
assert!(request_log_id2 > request_log_id1, "请求日志ID应该递增");
// 验证业务类型解析
let (main_id, sub_id, _) = parse_biz_id(flow_log_id1);
assert_eq!(main_id, FLOW_RUN_LOG_MAIN_ID, "流程日志ID的main_id应该正确");
assert_eq!(sub_id, FLOW_RUN_LOG_SUB_ID, "流程日志ID的sub_id应该正确");
let (main_id, sub_id, _) = parse_biz_id(request_log_id1);
assert_eq!(main_id, REQUEST_LOG_MAIN_ID, "请求日志ID的main_id应该正确");
assert_eq!(sub_id, REQUEST_LOG_SUB_ID, "请求日志ID的sub_id应该正确");
println!("流程日志ID: {}, {}", flow_log_id1, flow_log_id2);
println!("请求日志ID: {}, {}", request_log_id1, request_log_id2);
}
}

View File

@ -1 +0,0 @@
use argon2::{Argon2, PasswordHasher}; use argon2::password_hash::{SaltString, rand_core::OsRng}; fn main() { let argon2 = Argon2::default(); let salt = SaltString::generate(&mut OsRng); let password_hash = argon2.hash_password(b"123456", &salt).unwrap().to_string(); println!("{}", password_hash); }

View File

@ -1,5 +0,0 @@
{
"name": "测试流程",
"code": "test-flow",
"yaml": "# demo flow\nnodes:\n - { id: start, kind: start, name: 开始 }\n - { id: assign, kind: custom, name: 赋值, script: ctx.x = ctx.input.name || 'default'; }\n - { id: end, kind: end, name: 结束 }\nedges:\n - { from: start, to: assign }\n - { from: assign, to: end }\n"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.