chore(infra): add gRPC layer, update protobufs, remove immediate module

- Add gRPC service modules: auth, channel, channel settings, member,
  permission
- Update protobuf definitions and generated code
- Remove immediate/ real-time module (superseded by IM service)
- Update etcd discovery and registration
- Update cache, error, config, and build infrastructure
- Add ADR documentation
- Update OpenAPI spec
This commit is contained in:
zhenyi
2026-06-10 18:49:42 +08:00
parent 9eb77ab98b
commit 1000f8a80d
57 changed files with 22524 additions and 2703 deletions
+53 -68
View File
@@ -1,15 +1,13 @@
use crate::config::AppConfig;
use crate::error::AppError;
use crate::error::AppResult;
use r2d2::Pool;
use crate::error::{AppError, AppResult};
use futures_util::future::BoxFuture;
use redis::cluster::ClusterClient;
use redis::{Client, ConnectionLike, RedisError};
use std::time::Duration;
use redis::{Client, FromRedisValue};
#[derive(Clone)]
enum RedisBackend {
Single(Pool<Client>),
Cluster(Pool<ClusterClient>),
Single(redis::aio::ConnectionManager),
Cluster(redis::cluster_async::ClusterConnection),
}
#[derive(Clone)]
@@ -18,100 +16,87 @@ pub struct AppRedis {
}
impl AppRedis {
pub fn from_config(config: &AppConfig) -> AppResult<Self> {
pub async fn from_config(config: &AppConfig) -> AppResult<Self> {
let backend = if config.redis_cluster_enabled()? {
let nodes = config.redis_cluster_nodes()?;
let cluster_client =
ClusterClient::new(nodes.iter().map(|s| s.as_str()).collect::<Vec<_>>())?;
let pool = Self::build_pool(config, cluster_client)?;
RedisBackend::Cluster(pool)
let conn = cluster_client.get_async_connection().await?;
RedisBackend::Cluster(conn)
} else {
let url = config
.redis_url()?
.ok_or_else(|| AppError::Config("APP_REDIS_URL is not set".into()))?;
let client = Client::open(url.as_str())?;
let pool = Self::build_pool(config, client)?;
RedisBackend::Single(pool)
let conn = client.get_connection_manager().await?;
RedisBackend::Single(conn)
};
Ok(Self { backend })
}
fn build_pool<M: r2d2::ManageConnection>(config: &AppConfig, manager: M) -> AppResult<Pool<M>> {
let max_conn = config.redis_max_connections()?;
let min_conn = config.redis_min_connections()?;
let idle_timeout = config.redis_idle_timeout()?;
let conn_timeout = config.redis_connection_timeout()?;
Ok(r2d2::Builder::new()
.max_size(max_conn)
.min_idle(Some(min_conn))
.idle_timeout(Some(Duration::from_secs(idle_timeout)))
.connection_timeout(Duration::from_secs(conn_timeout))
.build(manager)?)
}
pub fn get_connection(&self) -> Result<PooledRedisConnection, r2d2::Error> {
pub fn get_connection(&self) -> RedisConnection {
match &self.backend {
RedisBackend::Single(pool) => pool.get().map(PooledRedisConnection::Single),
RedisBackend::Cluster(pool) => pool.get().map(PooledRedisConnection::Cluster),
RedisBackend::Single(cm) => RedisConnection::Single(cm.clone()),
RedisBackend::Cluster(cc) => RedisConnection::Cluster(cc.clone()),
}
}
}
#[allow(clippy::large_enum_variant)]
pub enum PooledRedisConnection {
Single(r2d2::PooledConnection<Client>),
Cluster(r2d2::PooledConnection<ClusterClient>),
pub enum RedisConnection {
Single(redis::aio::ConnectionManager),
Cluster(redis::cluster_async::ClusterConnection),
}
impl PooledRedisConnection {
pub fn inner_mut(&mut self) -> &mut dyn ConnectionLike {
impl redis::aio::ConnectionLike for RedisConnection {
fn req_packed_command<'a>(
&'a mut self,
cmd: &'a redis::Cmd,
) -> BoxFuture<'a, redis::RedisResult<redis::Value>> {
match self {
PooledRedisConnection::Single(conn) => conn,
PooledRedisConnection::Cluster(conn) => conn,
}
}
}
impl ConnectionLike for PooledRedisConnection {
fn req_packed_command(&mut self, cmd: &[u8]) -> Result<redis::Value, RedisError> {
match self {
PooledRedisConnection::Single(conn) => conn.req_packed_command(cmd),
PooledRedisConnection::Cluster(conn) => conn.req_packed_command(cmd),
Self::Single(c) => Box::pin(c.req_packed_command(cmd)),
Self::Cluster(c) => Box::pin(c.req_packed_command(cmd)),
}
}
fn req_packed_commands(
&mut self,
cmd: &[u8],
fn req_packed_commands<'a>(
&'a mut self,
cmd: &'a redis::Pipeline,
offset: usize,
count: usize,
) -> Result<Vec<redis::Value>, RedisError> {
) -> BoxFuture<'a, redis::RedisResult<Vec<redis::Value>>> {
match self {
PooledRedisConnection::Single(conn) => conn.req_packed_commands(cmd, offset, count),
PooledRedisConnection::Cluster(conn) => conn.req_packed_commands(cmd, offset, count),
Self::Single(c) => Box::pin(c.req_packed_commands(cmd, offset, count)),
Self::Cluster(c) => Box::pin(c.req_packed_commands(cmd, offset, count)),
}
}
fn get_db(&self) -> i64 {
match self {
PooledRedisConnection::Single(conn) => conn.get_db(),
PooledRedisConnection::Cluster(conn) => conn.get_db(),
}
}
fn check_connection(&mut self) -> bool {
match self {
PooledRedisConnection::Single(conn) => conn.check_connection(),
PooledRedisConnection::Cluster(conn) => conn.check_connection(),
}
}
fn is_open(&self) -> bool {
match self {
PooledRedisConnection::Single(conn) => conn.is_open(),
PooledRedisConnection::Cluster(conn) => conn.is_open(),
Self::Single(c) => c.get_db(),
Self::Cluster(c) => c.get_db(),
}
}
}
impl RedisConnection {
pub async fn query_async<T: FromRedisValue>(
&mut self,
cmd: &mut redis::Cmd,
) -> redis::RedisResult<T> {
match self {
Self::Single(c) => cmd.query_async(c).await,
Self::Cluster(c) => cmd.query_async(c).await,
}
}
pub async fn query_pipeline_async<T: FromRedisValue>(
&mut self,
pipe: &mut redis::Pipeline,
) -> redis::RedisResult<T> {
match self {
Self::Single(c) => pipe.query_async(c).await,
Self::Cluster(c) => pipe.query_async(c).await,
}
}
}