feat: QR 码解码器 — 从零手写的完整解码流水线
新增 core/src/decoder/ 模块(9 文件,~1500 行): - bch.rs: BCH(15,5)+BCH(18,6) 查表解码(32+64 有效码字,t≤3) - format.rs: 从矩阵读取格式信息(EC+掩码)+版本信息(2 副本容错) - extract.rs: 逆向蛇形排列提取数据码字 - deinterleave.rs: 逆向 RS 数据交错 - rs_decode.rs: RS 纠错流水线(伴随式→BM→Chien→Forney) - mode_decode.rs: 逆向 4 种编码模式(数字/字母/字节/汉字 Shift JIS) - detect.rs: 定位图案检测(1:1:3:1:1 比例+交叉验证+聚类) - image.rs: 图像加载+灰度二值化(PNG/JPEG/WebP) - mod.rs: 顶层 API(decode_image + decode_matrix) 修改已有文件: - core: galois.rs 表 pub(crate), 新增 poly_eval(); reed_solomon 公开内部函数 - cli: 新增 --decode <file> 解码模式 - web: 新增 POST /api/decode(multipart file upload) 测试: 72 passed (58 原有 + 14 新增 decoder 测试)
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@ authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
qr-core = { path = "../core" }
|
||||
axum = "0.8"
|
||||
axum = { version = "0.8", features = ["multipart"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
+48
-5
@@ -1,13 +1,13 @@
|
||||
use axum::{
|
||||
extract::Query,
|
||||
extract::{Multipart, Query},
|
||||
http::{header, StatusCode},
|
||||
response::{Html, IntoResponse},
|
||||
routing::get,
|
||||
response::{Html, IntoResponse, Json},
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use qr_core::qr::{QrCode, QrConfig, VersionMode};
|
||||
use qr_core::version::EcLevel;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// GET /api/qr 查询参数
|
||||
#[derive(Deserialize)]
|
||||
@@ -78,11 +78,54 @@ async fn generate_qr(Query(params): Query<QrParams>) -> impl IntoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
/// 解码结果 JSON 响应
|
||||
#[derive(Serialize)]
|
||||
struct DecodeResponse {
|
||||
text: String,
|
||||
version: u8,
|
||||
level: String,
|
||||
mask: u8,
|
||||
errors_corrected: usize,
|
||||
}
|
||||
|
||||
/// POST /api/decode — 解码上传的 QR 码图片
|
||||
async fn decode_qr(mut multipart: Multipart) -> impl IntoResponse {
|
||||
while let Ok(Some(field)) = multipart.next_field().await {
|
||||
if field.name() == Some("file") {
|
||||
match field.bytes().await {
|
||||
Ok(data) => match qr_core::decoder::decode_image(&data) {
|
||||
Ok(result) => {
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(DecodeResponse {
|
||||
text: result.text,
|
||||
version: result.version,
|
||||
level: format!("{:?}", result.level),
|
||||
mask: result.mask,
|
||||
errors_corrected: result.errors_corrected,
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
Err(e) => {
|
||||
return (StatusCode::BAD_REQUEST, e).into_response();
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return (StatusCode::BAD_REQUEST, e.to_string()).into_response();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(StatusCode::BAD_REQUEST, "未找到上传文件(字段名: file)").into_response()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let app = Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/api/qr", get(generate_qr));
|
||||
.route("/api/qr", get(generate_qr))
|
||||
.route("/api/decode", post(decode_qr));
|
||||
|
||||
let addr = "0.0.0.0:3000";
|
||||
println!("QRGen Web → http://{}", addr);
|
||||
|
||||
Reference in New Issue
Block a user