feat: 添加 axum 框架支持和 SQLite 数据库功能

- 添加 axum 框架支持并更新依赖版本
- 添加 axum、chrono、rand、serde 和 serde_json 依赖,更新 sqlx 和 tokio 配置,移除 warp 依赖。
- 添加授权数据数据库文件
- 添加数据库模块,实现 SQLite 连接、授权表创建及授权数据的增查功能。
- 添加随机Token生成器结构体及其实现,支持自定义字符集生成随机字符串。
- 重构项目以支持跨平台数据库路径配置并集成Token生成与验证功能
This commit is contained in:
2025-08-15 18:49:04 +08:00
parent 479d699d24
commit 2466d3db5d
6 changed files with 419 additions and 182 deletions

Binary file not shown.

111
src/db.rs Normal file
View File

@@ -0,0 +1,111 @@
use serde::Serialize;
use serde_json;
use std::{fs, path::Path};
use welds::{connections::sqlite::SqliteClient, prelude::*};
#[derive(WeldsModel, Clone, Serialize)]
#[welds(table = "authorize")]
pub struct Authorize {
#[welds(primary_key)]
pub id: i32,
pub project: String,
pub key: String,
pub device_id: String,
pub expire: String,
pub insert_time: String,
}
/// 包装类,内部持有 SQLite 连接
pub struct Db {
client: SqliteClient,
}
impl Db {
/// 初始化:建目录 -> 建文件 -> 建表 -> 返回 Self
pub async fn new(
db_path: impl AsRef<str>,
) -> Result<Self, Box<dyn std::error::Error>> {
let db_path = db_path.as_ref();
let parent = Path::new(db_path).parent().unwrap();
if !parent.exists() {
fs::create_dir_all(parent)?;
}
if !Path::new(db_path).exists() {
fs::File::create(db_path)?;
}
let conn_str = format!("sqlite://{db_path}");
let client = welds::connections::sqlite::connect(&conn_str).await?;
client
.execute(
r#"
CREATE TABLE IF NOT EXISTS authorize (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project TEXT NOT NULL,
key TEXT NOT NULL,
device_id TEXT NOT NULL,
expire TEXT NOT NULL,
insert_time TEXT NOT NULL
)
"#,
&[],
)
.await?;
Ok(Db { client })
}
/// 判断 token 是否存在
pub async fn verify_token(
&self,
token: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
let rows = Authorize::where_col(|p| p.key.equal(token))
.limit(1)
.run(&self.client)
.await?;
Ok(!rows.is_empty())
}
/// 查询 token 的详细信息,返回 Option<Authorize> 如果存在
pub async fn get_token_info(
&self,
token: &str,
) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
let rows: Vec<DbState<Authorize>> =
Authorize::where_col(|p| p.key.equal(token))
.limit(1)
.run(&self.client)
.await?;
if let Some(auth) = rows.into_inners().into_iter().next() {
// 转 JSON
let json_str = serde_json::to_string(&auth)?;
Ok(Some(json_str))
} else {
Ok(None)
}
}
/// 插入新的授权数据
pub async fn insert_authorize(
&self,
project: &str,
key: &str,
device_id: &str,
expire: &str,
insert_time: &str,
) -> Result<i32, Box<dyn std::error::Error>> {
let mut auth = Authorize::new();
auth.project = project.to_string();
auth.key = key.to_string();
auth.device_id = device_id.to_string();
auth.expire = expire.to_string();
auth.insert_time = insert_time.to_string();
let _created = auth.save(&self.client).await?;
Ok(auth.id)
}
}

85
src/generate.rs Normal file
View File

@@ -0,0 +1,85 @@
use rand::Rng;
/// 随机Token生成器结构体
pub struct TokenGenerator {
uppercase: bool,
lowercase: bool,
numbers: bool,
}
impl TokenGenerator {
/// 创建新的TokenGenerator实例
pub fn new() -> Self {
Self {
uppercase: true,
lowercase: true,
numbers: true,
}
}
/// 设置是否包含大写字母
pub fn with_uppercase(mut self, include: bool) -> Self {
self.uppercase = include;
self
}
/// 设置是否包含小写字母
pub fn with_lowercase(mut self, include: bool) -> Self {
self.lowercase = include;
self
}
/// 设置是否包含数字
pub fn with_numbers(mut self, include: bool) -> Self {
self.numbers = include;
self
}
/// 生成随机token
pub fn generate(&self, length: usize) -> String {
let charset = self.build_charset();
if charset.is_empty() {
// 如果没有选择任何字符类型,使用默认字符集
return self.generate_with_default_charset(length);
}
let mut rng = rand::rng();
(0..length)
.map(|_| {
let idx = rng.random_range(0..charset.len());
charset[idx] as char
})
.collect()
}
/// 构建字符集
fn build_charset(&self) -> Vec<u8> {
let mut charset = Vec::new();
if self.uppercase {
charset.extend_from_slice(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}
if self.lowercase {
charset.extend_from_slice(b"abcdefghijklmnopqrstuvwxyz");
}
if self.numbers {
charset.extend_from_slice(b"0123456789");
}
charset
}
/// 使用默认字符集生成token
fn generate_with_default_charset(&self, length: usize) -> String {
let charset =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let mut rng = rand::rng();
(0..length)
.map(|_| {
let idx = rng.random_range(0..charset.len());
charset[idx] as char
})
.collect()
}
}

View File

@@ -1,8 +1,13 @@
use std::{fs, path::Path};
use chrono::{prelude::*, Duration, ParseError};
use std::env;
use token_generator::TokenGenerator;
use warp::Filter;
use axum::{routing::get, Router};
use welds::prelude::*;
mod db;
mod generate;
#[derive(WeldsModel)]
#[welds(table = "authorize")]
pub struct Authorize {
@@ -17,53 +22,50 @@ pub struct Authorize {
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let db_path = "C:/Users/admin/Desktop/database.db";
// 根据操作系统选择数据库路径
let windows_path: String = env::current_dir()
.expect("无法获取当前目录")
.join("authorize_data/database.db")
.to_string_lossy()
.into_owned();
if let Some(parent_dir) = Path::new(db_path).parent() {
if !parent_dir.exists() {
fs::create_dir_all(parent_dir)?;
println!("已创建目录: {:?}", parent_dir);
}
}
// 现在可以取切片
let db_path: &str = if cfg!(target_os = "windows") {
&windows_path
} else if cfg!(target_os = "linux") {
"/usr/local/etc/authorize_data/database.db"
} else {
panic!("不支持的操作系统");
};
if !Path::new(db_path).exists() {
fs::File::create(db_path)?;
println!("已创建空数据库文件: {}", db_path);
}
let db = db::Db::new(db_path).await?;
let connection_string = format!("sqlite://{db_path}");
let client = welds::connections::connect(connection_string).await?;
// // build our application with a single route
// let app = Router::new().route("/", get(|| async { "Hello, World!" }));
// 先执行建表 SQL如果表不存在
client
.execute(
r#"
CREATE TABLE IF NOT EXISTS authorize (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project TEXT NOT NULL,
key TEXT NOT NULL,
device_id TEXT NOT NULL,
expire TEXT NOT NULL,
insert_time TEXT NOT NULL
)
"#,
&[],
)
.await?;
// // run our app with hyper, listening globally on port 3000
// let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
// axum::serve(listener, app).await.unwrap();
let mut authorize = Authorize::new();
authorize.id = 0;
authorize.project = "TestProject1".to_string();
authorize.key = "TestKey1".to_string();
authorize.device_id = "Device001".to_string();
authorize.expire = "2099-12-31".to_string();
authorize.insert_time = "2025-08-14 12:00:00".to_string();
authorize.save(client.as_ref()).await?;
let generator = TokenGenerator::new()
.with_uppercase(true)
.with_lowercase(true)
.with_numbers(true);
// GET /hello/warp => 200 OK with body "Hello, warp!"
let hello =
warp::path!("hello" / String).map(|name| format!("Hello, {}!", name));
let token = generator.generate(16);
println!("token: {}", token);
warp::serve(hello).run(([127, 0, 0, 1], 3030)).await;
let result = db.get_token_info("TestKey1").await.unwrap();
println!("{:?}", result);
Ok(())
}
fn get_current_datetime() -> String {
Local::now().format("%Y-%m-%d %H:%M:%S").to_string()
}
fn add_day(t: &String, days: i64) -> Result<String, ParseError> {
let date_time = DateTime::parse_from_str(t, "%Y-%m-%d %H:%M:%S")?;
let new_time = date_time + Duration::days(days);
Ok(new_time.to_string())
}