新增: df-im-gateway 网关骨架
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
//! 网关配置(env 加载,凭证不进 git)
|
||||
//!
|
||||
//! 配置来源:环境变量 > 默认值(gateway.toml 文件加载为后续增强,当前未实现)。
|
||||
//! u-relay token 与桌面端 df-tunnel 同源(env U_RELAY_TOKEN)。
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
/// 网关全局配置
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct GatewayConfig {
|
||||
/// u-relay WS 地址(生产 u-relay.1216.top,本地 http://127.0.0.1:9180)
|
||||
pub relay_url: String,
|
||||
/// relay 配对 token(env U_RELAY_TOKEN 同源)
|
||||
pub relay_token: String,
|
||||
/// channel 路由表:IM 侧默认走 default_channel;按前缀路由的多桌面端配置(预留项 ⑥)
|
||||
#[serde(default)]
|
||||
pub channels: Vec<ChannelRoute>,
|
||||
/// 飞书应用凭证(未配置则该平台禁用)
|
||||
#[serde(default)]
|
||||
pub feishu: Option<FeishuConfig>,
|
||||
/// TextDelta 节流窗口毫秒(默认 800,IM 频控友好)
|
||||
#[serde(default = "default_throttle_ms")]
|
||||
pub throttle_ms: u64,
|
||||
/// 用户白名单(空=放行所有,生产部署务必显式配置)
|
||||
///
|
||||
/// 注:历史注释「空=拒绝所有」与 main.rs 实现(空=放行)矛盾,走查 2026-10-04 定为
|
||||
/// 空=放行——否则首次启动零配置时飞书/Web 全部拒绝,连 banner 警告都没有,反而
|
||||
/// 更难发现配置缺失。生产风险由 main.rs 启动 banner 常驻警告 + 白名单缺失提示兜底。
|
||||
#[serde(default)]
|
||||
pub whitelist: Vec<String>,
|
||||
}
|
||||
|
||||
/// channel 路由项:IM 指令前缀 → 桌面端 channel
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ChannelRoute {
|
||||
/// 指令前缀(如 "/dev2 xxx" 路由到 chan-home);空串=默认 channel
|
||||
pub prefix: String,
|
||||
/// u-relay channel_id(桌面端 df-tunnel 配置的同一 channel)
|
||||
pub channel_id: String,
|
||||
}
|
||||
|
||||
/// 飞书企业自建应用凭证
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct FeishuConfig {
|
||||
pub app_id: String,
|
||||
pub app_secret: String,
|
||||
/// 长连接模式事件订阅(免公网回调;当前恒 true,保留显式开关供回调模式扩展)
|
||||
#[allow(dead_code)]
|
||||
pub long_connection: bool,
|
||||
}
|
||||
|
||||
fn default_throttle_ms() -> u64 {
|
||||
800
|
||||
}
|
||||
|
||||
impl GatewayConfig {
|
||||
/// 从 env + 可选 toml 文件构建。缺失关键项(relay_url/token)返回 Err。
|
||||
pub fn load() -> anyhow::Result<Self> {
|
||||
let relay_url = std::env::var("GATEWAY_RELAY_URL")
|
||||
.unwrap_or_else(|_| "ws://127.0.0.1:9180/ws/commander".to_string());
|
||||
let relay_token = std::env::var("GATEWAY_RELAY_TOKEN")
|
||||
.or_else(|_| std::env::var("U_RELAY_TOKEN"))
|
||||
.map_err(|_| anyhow::anyhow!("缺少 GATEWAY_RELAY_TOKEN / U_RELAY_TOKEN 环境变量"))?;
|
||||
|
||||
let feishu = match (
|
||||
std::env::var("FEISHU_APP_ID"),
|
||||
std::env::var("FEISHU_APP_SECRET"),
|
||||
) {
|
||||
(Ok(app_id), Ok(app_secret)) => Some(FeishuConfig {
|
||||
app_id,
|
||||
app_secret,
|
||||
long_connection: true,
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let whitelist = std::env::var("GATEWAY_WHITELIST")
|
||||
.map(|v| v.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let default_channel = std::env::var("GATEWAY_DEFAULT_CHANNEL").unwrap_or_default();
|
||||
let channels = if default_channel.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
vec![ChannelRoute { prefix: String::new(), channel_id: default_channel }]
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
relay_url,
|
||||
relay_token,
|
||||
channels,
|
||||
feishu,
|
||||
throttle_ms: default_throttle_ms(),
|
||||
whitelist,
|
||||
})
|
||||
}
|
||||
|
||||
/// 查指令应路由到的 channel(前缀匹配,空前缀=兜底默认)
|
||||
pub fn route(&self, command_text: &str) -> Option<&str> {
|
||||
// 精确前缀优先,空前缀兜底
|
||||
self.channels
|
||||
.iter()
|
||||
.find(|c| !c.prefix.is_empty() && command_text.starts_with(&c.prefix))
|
||||
.map(|c| c.channel_id.as_str())
|
||||
.or_else(|| self.channels.iter().find(|c| c.prefix.is_empty()).map(|c| c.channel_id.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn route_prefix_priority() {
|
||||
let cfg = GatewayConfig {
|
||||
relay_url: String::new(),
|
||||
relay_token: String::new(),
|
||||
channels: vec![
|
||||
ChannelRoute { prefix: "/dev2".into(), channel_id: "chan-home".into() },
|
||||
ChannelRoute { prefix: String::new(), channel_id: "chan-work".into() },
|
||||
],
|
||||
feishu: None,
|
||||
throttle_ms: 800,
|
||||
whitelist: vec![],
|
||||
};
|
||||
assert_eq!(cfg.route("/dev2 修个 bug"), Some("chan-home"));
|
||||
assert_eq!(cfg.route("普通指令"), Some("chan-work"));
|
||||
assert_eq!(cfg.route("/dev2"), Some("chan-home"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn route_empty_when_no_channels() {
|
||||
let cfg = GatewayConfig {
|
||||
relay_url: String::new(),
|
||||
relay_token: String::new(),
|
||||
channels: vec![],
|
||||
feishu: None,
|
||||
throttle_ms: 800,
|
||||
whitelist: vec![],
|
||||
};
|
||||
assert_eq!(cfg.route("任何"), None);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user