Rust + Axum + MySQL + SQLx

以下是完整的执行脚本,按顺序手工操作即可:

一、创建项目并安装工具

1. 创建项目

cargo new wechat_backend --bin

cd wechat_backend

2. 安装 sqlx-cli(用于数据库迁移)

cargo install sqlx-cli --features mysql

二、配置 Cargo.toml

将 Cargo.toml 替换为以下内容:

rust 复制代码
[package]
name = "wechat_backend"
version = "0.1.0"
edition = "2021"

[dependencies]
# Web 框架
axum = { version = "0.8", features = ["macros"] }
axum-extra = { version = "0.10", features = ["typed-header"] }
tower = { version = "0.5", features = ["full"] }
tower-http = { version = "0.6", features = ["cors", "trace", "compression-gzip", "timeout"] }

# 异步运行时
tokio = { version = "1", features = ["full"] }

# 数据库
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "mysql", "macros", "chrono", "uuid"] }

# 序列化
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# 日志
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# 配置
dotenvy = "0.15"

# 错误处理
thiserror = "1"
anyhow = "1"

# 认证
jsonwebtoken = "9"
argon2 = "0.5"

# 工具
chrono = { version = "0.4", features = ["serde"] }
uuid = { version = "1", features = ["v4", "serde"] }

三、创建项目目录结构

bash

编辑

mkdir -p src/{api,domain,infrastructure,middleware}

mkdir -p migrations

touch src/{lib.rs,config.rs,error.rs}

touch src/api/{mod.rs,user.rs}

touch src/domain/{mod.rs,user_service.rs}

touch src/infrastructure/{mod.rs,db.rs,models.rs}

touch src/middleware/{mod.rs,auth.rs}

touch migrations/001_create_users.sql

四、创建环境变量文件

在项目根目录创建 .env:

bash

编辑

.env

DATABASE_URL=mysql://root:your_password@localhost:3306/wechat_db

SERVER_PORT=3000

JWT_SECRET=your_jwt_secret_key_change_me

五、创建数据库迁移文件

编辑 migrations/001_create_users.sql:

sql

编辑

CREATE TABLE IF NOT EXISTS users (

id VARCHAR(36) PRIMARY KEY,

openid VARCHAR(128) NOT NULL UNIQUE,

nickname VARCHAR(64) DEFAULT '',

avatar_url VARCHAR(255) DEFAULT '',

phone VARCHAR(20) DEFAULT NULL,

created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,

updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

INDEX idx_openid (openid)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

六、编写核心代码

  1. src/infrastructure/db.rs --- 数据库连接池

rust

编辑

use sqlx::mysql::MySqlPool;

pub async fn create_pool(database_url: &str) -> Result<MySqlPool, anyhow::Error> {

let pool = MySqlPool::connect(database_url).await?;

Ok(pool)

}

pub async fn run_migrations(pool: &MySqlPool) -> Result<(), anyhow::Error> {

sqlx::migrate!("./migrations").run(pool).await?;

Ok(())

}

  1. src/infrastructure/models.rs --- 数据模型

rust

编辑

use serde::{Deserialize, Serialize};

use sqlx::FromRow;

use uuid::Uuid;

use chrono::{DateTime, Utc};

#derive(Debug, Clone, Serialize, Deserialize, FromRow)

pub struct User {

pub id: String,

pub openid: String,

pub nickname: String,

pub avatar_url: String,

pub phone: Option<String>,

pub created_at: DateTime<Utc>,

pub updated_at: DateTime<Utc>,

}

#derive(Debug, Deserialize)

pub struct WxLoginRequest {

pub code: String,

}

#derive(Debug, Serialize)

pub struct WxLoginResponse {

pub token: String,

pub user: User,

}

  1. src/infrastructure/mod.rs

rust

编辑

pub mod db;

pub mod models;

  1. src/domain/user_service.rs --- 业务逻辑

rust

编辑

use sqlx::MySqlPool;

use uuid::Uuid;

use crate::infrastructure::models::{User, WxLoginRequest, WxLoginResponse};

use crate::error::AppError;

pub struct UserService {

pool: MySqlPool,

}

impl UserService {

pub fn new(pool: MySqlPool) -> Self {

Self { pool }

}

/// 微信小程序登录:code → openid → 生成/查询用户 → 返回 JWT

pub async fn wx_login(&self, req: WxLoginRequest) -> Result<WxLoginResponse, AppError> {

// TODO: 调用微信接口 https://api.weixin.qq.com/sns/jscode2session

// let openid = fetch_openid_from_wechat(&req.code).await?;

// 这里先用 mock openid 演示流程

let openid = format!("mock_openid_{}", req.code);

// 查询用户是否存在

let user = sqlx::query_as::<_, User>(

"SELECT * FROM users WHERE openid = ?"

)

.bind(&openid)

.fetch_optional(&self.pool)

.await?;

let user = match user {

Some(u) => u,

None => {

// 新用户,插入记录

let id = Uuid::new_v4().to_string();

sqlx::query(

"INSERT INTO users (id, openid) VALUES (?, ?)"

)

.bind(&id)

.bind(&openid)

.execute(&self.pool)

.await?;

sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?")

.bind(&id)

.fetch_one(&self.pool)

.await?

}

};

// TODO: 生成 JWT token

let token = format!("mock_token_for_{}", user.id);

Ok(WxLoginResponse { token, user })

}

/// 根据 openid 查询用户

pub async fn get_user_by_openid(&self, openid: &str) -> Result<Option<User>, AppError> {

let user = sqlx::query_as::<_, User>(

"SELECT * FROM users WHERE openid = ?"

)

.bind(openid)

.fetch_optional(&self.pool)

.await?;

Ok(user)

}

/// 更新用户信息

pub async fn update_user_info(

&self,

openid: &str,

nickname: &str,

avatar_url: &str,

) -> Result<User, AppError> {

sqlx::query(

"UPDATE users SET nickname = ?, avatar_url = ? WHERE openid = ?"

)

.bind(nickname)

.bind(avatar_url)

.bind(openid)

.execute(&self.pool)

.await?;

let user = sqlx::query_as::<_, User>(

"SELECT * FROM users WHERE openid = ?"

)

.bind(openid)

.fetch_one(&self.pool)

.await?;

Ok(user)

}

}

  1. src/domain/mod.rs

rust

编辑

pub mod user_service;

  1. src/api/user.rs --- API 路由处理器

rust

编辑

use axum::{

extract::State,

routing::{get, post},

Json, Router,

};

use serde_json::json;

use crate::domain::user_service::UserService;

use crate::infrastructure::models::{WxLoginRequest, WxLoginResponse};

use crate::error::AppError;

pub fn router(user_service: UserService) -> Router {

Router::new()

.route("/wx/login", post(wx_login))

.route("/user/info", get(get_user_info))

.with_state(user_service)

}

/// POST /api/wx/login

async fn wx_login(

State(service): State<UserService>,

Json(req): Json<WxLoginRequest>,

) -> Result<Json<WxLoginResponse>, AppError> {

let resp = service.wx_login(req).await?;

Ok(Json(resp))

}

/// GET /api/user/info?openid=xxx

async fn get_user_info(

State(service): State<UserService>,

axum::extract::Query(params): axum::extract::Query<std::collections::HashMap<String, String>>,

) -> Result<Json<serde_json::Value>, AppError> {

let openid = params.get("openid").ok_or(AppError::BadRequest("openid is required".into()))?;

let user = service.get_user_by_openid(openid).await?;

match user {

Some(u) => Ok(Json(json!(u))),

None => Err(AppError::NotFound("用户不存在".into())),

}

}

  1. src/api/mod.rs

rust

编辑

pub mod user;

  1. src/error.rs --- 统一错误处理

rust

编辑

use axum::{

http::StatusCode,

response::{IntoResponse, Response},

Json,

};

use serde_json::json;

use thiserror::Error;

#derive(Error, Debug)

pub enum AppError {

#error("数据库错误: {0}")

Db(#from sqlx::Error),

#error("资源不存在: {0}")

NotFound(String),

#error("参数错误: {0}")

BadRequest(String),

#error("内部错误: {0}")

Internal(String),

}

impl IntoResponse for AppError {

fn into_response(self) -> Response {

let (status, message) = match self {

AppError::Db(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("数据库错误: {}", e)),

AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),

AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),

AppError::Internal(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),

};

(status, Json(json!({ "error": message }))).into_response()

}

}

  1. src/config.rs --- 配置读取

rust

编辑

use dotenvy::dotenv;

use std::env;

pub struct AppConfig {

pub database_url: String,

pub server_port: u16,

pub jwt_secret: String,

}

impl AppConfig {

pub fn from_env() -> Self {

dotenv().ok();

Self {

database_url: env::var("DATABASE_URL").expect("DATABASE_URL must be set"),

server_port: env::var("SERVER_PORT")

.unwrap_or_else(|_| "3000".to_string())

.parse()

.unwrap_or(3000),

jwt_secret: env::var("JWT_SECRET").unwrap_or_else(|_| "dev_secret".to_string()),

}

}

}

  1. src/lib.rs

rust

编辑

pub mod api;

pub mod config;

pub mod domain;

pub mod error;

pub mod infrastructure;

pub mod middleware;

  1. src/main.rs --- 应用入口

rust

编辑

mod api;

mod config;

mod domain;

mod error;

mod infrastructure;

mod middleware;

use axum::{Router, routing::get};

use tower_http::{

cors::CorsLayer,

trace::TraceLayer,

compression::CompressionLayer,

timeout::TimeoutLayer,

};

use std::time::Duration;

use tracing_subscriber;

#tokio::main

async fn main() -> anyhow::Result<()> {

// 初始化日志

tracing_subscriber::fmt::init();

// 加载配置

let config = config::AppConfig::from_env();

// 创建数据库连接池

let pool = infrastructure::db::create_pool(&config.database_url).await?;

tracing::info!("数据库连接成功");

// 执行数据库迁移

infrastructure::db::run_migrations(&pool).await?;

tracing::info!("数据库迁移完成");

// 创建服务层

let user_service = domain::user_service::UserService::new(pool);

// 组装路由

let app = Router::new()

.route("/health", get(|| async { "OK" }))

.nest("/api", api::user::router(user_service))

.layer(TraceLayer::new_for_http())

.layer(CompressionLayer::new())

.layer(CorsLayer::permissive())

.layer(TimeoutLayer::new(Duration::from_secs(30)));

let addr = format!("0.0.0.0:{}", config.server_port);

let listener = tokio::net::TcpListener::bind(&addr).await?;

tracing::info!("服务启动在 http://{}", addr);

axum::serve(listener, app).await?;

Ok(())

}

七、启动 MySQL 并执行迁移

bash

编辑

1. 确保 MySQL 已启动,创建数据库

mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS wechat_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

2. 执行数据库迁移

sqlx migrate run

3. 编译并启动服务

cargo run

八、测试接口

bash

编辑

健康检查

curl http://localhost:3000/health

微信小程序登录(mock)

curl -X POST http://localhost:3000/api/wx/login \

-H "Content-Type: application/json" \

-d '{"code": "test_code_123"}'

查询用户

curl "http://localhost:3000/api/user/info?openid=mock_openid_test_code_123"

九、后续需要手工补齐的部分

表格

模块 说明

微信 code2session 在 user_service.rs 中用 reqwest 调用 https://api.weixin.qq.com/sns/jscode2session,换取真实 openid

JWT 认证 用 jsonwebtoken 库生成/验证 token,在 middleware/auth.rs 中实现认证中间件

密码/签名校验 微信小程序通信需要校验签名,防止请求伪造

业务表 根据实际需求在 migrations/ 中添加订单、商品、消息等业务表

执行完以上脚本,你就有了一个可运行的微信小程序后端骨架,包含用户登录、数据库迁移、分层架构和统一错误处理。

相关推荐
mikuyyds18 小时前
geo-toolbox 插件算法解析:RUSLE 与 MUSLE 的工程化落地
算法·rust·gis
qq_4523962319 小时前
第十四篇:《系统编程实战:用 Rust 编写高性能 HTTP 服务器》
rust
Amos_Web1 天前
Rspack 源码解析(十一):资产 Hook 与增量构建
前端·rust·源码阅读
孙启超1 天前
【AI开发之Rust】第 12 课:并发模型与同步原语
开发语言·后端·rust
柯南46681 天前
【AI开发之Rust】第 13 课:async/await 与 tokio 异步运行时
rust·编程语言
传奇开心果编程1 天前
【Rust入门练中学】第4课:函数与所有权入门
开发语言·学习·rust
右耳朵猫AI1 天前
Rust周刊2026W38 | mold重写Rust、认证级Rust裸机、Slint 1.18发布、lint提速3133倍
后端·rust·系统编程