next_web_utils/captcha/
captcha_gen.rs

1use super::captcha_error::CaptchaError;
2
3/// 验证码生成结果
4#[derive(Debug)]
5pub struct CaptchaResult {
6    /// 验证码文本
7    pub text: String,
8    /// 原始图片数据
9    pub image_data: Vec<u8>,
10    /// 图片宽度
11    pub width: u32,
12    /// 图片高度
13    pub height: u32,
14    /// 验证码长度
15    pub length: usize,
16    /// 验证码复杂度
17    pub complexity: u32,
18}
19
20// 定义 CaptchaBuilder 结构体,用于配置验证码生成器的参数
21#[derive(Debug, Default, Clone)]
22pub(crate) struct CaptchaBuilder {
23    text: Option<String>, // 自定义文本(可选)
24    length: usize,        // 验证码长度
25    complexity: u32,      // 验证码复杂度
26    width: u32,           // 图片宽度
27    height: u32,          // 图片高度
28}
29
30impl CaptchaBuilder {
31    // 创建一个新的 CaptchaBuilder 实例,并设置默认值
32    pub fn builder() -> Self {
33        Self {
34            length: 6,     // 默认长度为 6
35            complexity: 1, // 默认复杂度为 1
36            width: 150,    // 默认宽度为 150
37            height: 50,    // 默认高度为 50
38            text: None,    // 默认没有自定义文本
39        }
40    }
41
42    // 设置验证码长度
43    pub fn length(mut self, length: usize) -> Self {
44        self.length = length;
45        self
46    }
47
48    // 设置验证码复杂度
49    pub fn complexity(mut self, complexity: u32) -> Self {
50        self.complexity = complexity;
51        self
52    }
53
54    // 设置图片的宽度和高度
55    pub fn size(mut self, width: u32, height: u32) -> Self {
56        self.width = width;
57        self.height = height;
58        self
59    }
60
61    // 设置自定义文本
62    pub fn text(mut self, text: impl Into<String>) -> Self {
63        self.text = Some(text.into());
64        self
65    }
66
67    // 构建 CaptchaGen 实例
68    pub fn build(self) -> CaptchaGen {
69        CaptchaGen { config: self }
70    }
71}
72
73// 定义 CaptchaGen 结构体,用于生成验证码
74pub struct CaptchaGen {
75    config: CaptchaBuilder, // 包含 CaptchaBuilder 的配置
76}
77
78impl CaptchaGen {
79    // 生成验证码并返回原始数据
80    fn _gen(&self, base64: bool) -> Result<(String, Vec<u8>), CaptchaError> {
81        // 检查宽度是否有效
82        if self.config.width <= 0 {
83            return Err(CaptchaError::WidthNotApplicable);
84        }
85        // 检查高度是否有效
86        if self.config.height <= 0 {
87            return Err(CaptchaError::HeightNotApplicable);
88        }
89
90        // 使用 captcha_rs 库创建验证码生成器
91        let mut captcha = captcha_rs::CaptchaBuilder::new()
92            .length(self.config.length)
93            .width(self.config.width)
94            .height(self.config.height)
95            .dark_mode(false)
96            .complexity(self.config.complexity)
97            .compression(40);
98
99        // 如果设置了自定义文本,则使用该文本
100        if let Some(text) = self.config.text.as_deref() {
101            captcha = captcha.text(text.into());
102        }
103
104        // 构建验证码
105        let captcha = captcha.build();
106
107        // 获取生成的验证码文本
108        let text = if let Some(text) = self.config.text.as_ref() {
109            text.into()
110        } else {
111            captcha.text.clone()
112        };
113
114        if base64 {
115            Ok((text, captcha.to_base64().into_bytes()))
116        } else {
117            // 获取原始图片数据
118            let image_data = captcha.image.into_bytes();
119            Ok((text, image_data))
120        }
121    }
122
123    pub fn gen(self) -> Result<CaptchaResult, CaptchaError> {
124        let result = self._gen(false)?;
125        Ok(CaptchaResult {
126            text: result.0,
127            image_data: result.1,
128            width: self.config.width,
129            height: self.config.height,
130            length: self.config.length,
131            complexity: self.config.complexity,
132        })
133    }
134
135    pub fn gen_to_base64(self) -> Result<CaptchaResult, CaptchaError> {
136        let result = self._gen(true)?;
137
138        Ok(CaptchaResult {
139            text: result.0,
140            image_data: result.1,
141            width: self.config.width,
142            height: self.config.height,
143            length: self.config.length,
144            complexity: self.config.complexity,
145        })
146    }
147
148    // 提供一个静态方法来创建 CaptchaBuilder 实例
149    pub fn builder() -> CaptchaBuilder {
150        CaptchaBuilder::builder()
151    }
152}
153
154// 测试模块
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    // 测试默认配置是否正确
160    #[test]
161    fn test_default_values() {
162        let builder = CaptchaBuilder::builder();
163        assert_eq!(builder.length, 6, "Default length should be 6");
164        assert_eq!(builder.complexity, 1, "Default complexity should be 1");
165        assert_eq!(builder.width, 150, "Default width should be 150");
166        assert_eq!(builder.height, 50, "Default height should be 50");
167        assert!(builder.text.is_none(), "Default text should be None");
168    }
169
170    // 测试 length 方法是否正确更新长度
171    #[test]
172    fn test_length_config() {
173        let builder = CaptchaBuilder::builder().length(8);
174        assert_eq!(builder.length, 8, "Length should be updated to 8");
175    }
176
177    // 测试 complexity 方法是否正确更新复杂度
178    #[test]
179    fn test_complexity_config() {
180        let builder = CaptchaBuilder::builder().complexity(5);
181        assert_eq!(builder.complexity, 5, "Complexity should be updated to 5");
182    }
183
184    // 测试 size 方法是否正确更新宽度和高度
185    #[test]
186    fn test_size_config() {
187        let builder = CaptchaBuilder::builder().size(200, 100);
188        assert_eq!(builder.width, 200, "Width should be updated to 200");
189        assert_eq!(builder.height, 100, "Height should be updated to 100");
190    }
191
192    // 测试 text 方法是否正确设置自定义文本
193    #[test]
194    fn test_text_config() {
195        let builder = CaptchaBuilder::builder().text("CUSTOMTEXT");
196        assert_eq!(
197            builder.text,
198            Some("CUSTOMTEXT".to_string()),
199            "Text should be set to CUSTOMTEXT"
200        );
201    }
202
203    // 测试链式调用是否按预期工作
204    #[test]
205    fn test_chained_config() {
206        let builder = CaptchaBuilder::builder()
207            .length(8)
208            .complexity(3)
209            .size(300, 150)
210            .text("CHAINTEST");
211
212        assert_eq!(builder.length, 8, "Length should be updated to 8");
213        assert_eq!(builder.complexity, 3, "Complexity should be updated to 3");
214        assert_eq!(builder.width, 300, "Width should be updated to 300");
215        assert_eq!(builder.height, 150, "Height should be updated to 150");
216        assert_eq!(
217            builder.text,
218            Some("CHAINTEST".to_string()),
219            "Text should be set to CHAINTEST"
220        );
221    }
222
223    // 测试默认配置下生成的验证码
224    #[test]
225    fn test_captcha_gen_with_default_config() {
226        let captcha_gen = CaptchaBuilder::builder().build();
227        let result = captcha_gen.gen();
228        assert!(result.is_ok(), "Captcha generation should succeed");
229
230        let captcha_result = result.unwrap();
231        assert_eq!(
232            captcha_result.text.len(),
233            6,
234            "Generated text length should match default length of 6"
235        );
236        assert_eq!(captcha_result.width, 150);
237        assert_eq!(captcha_result.height, 50);
238        assert_eq!(captcha_result.length, 6);
239        assert_eq!(captcha_result.complexity, 1);
240        assert!(!captcha_result.image_data.is_empty());
241    }
242
243    // 测试自定义配置下生成的验证码
244    #[test]
245    fn test_captcha_gen_with_custom_config() {
246        let captcha_gen = CaptchaBuilder::builder()
247            .length(10)
248            .complexity(5)
249            .size(400, 200)
250            .text("CUSTOM")
251            .build();
252
253        let result = captcha_gen.gen();
254        assert!(result.is_ok(), "Captcha generation should succeed");
255
256        let captcha_result = result.unwrap();
257        assert_eq!(captcha_result.text, "CUSTOM");
258        assert_eq!(captcha_result.width, 400);
259        assert_eq!(captcha_result.height, 200);
260        assert_eq!(captcha_result.length, 10);
261        assert_eq!(captcha_result.complexity, 5);
262        assert!(!captcha_result.image_data.is_empty());
263    }
264
265    // 测试未设置自定义文本时生成的验证码
266    #[test]
267    fn test_captcha_gen_without_custom_text() {
268        let captcha_gen = CaptchaBuilder::builder().length(8).build();
269
270        let result = captcha_gen.gen();
271        assert!(result.is_ok(), "Captcha generation should succeed");
272
273        let captcha_result = result.unwrap();
274        assert_eq!(
275            captcha_result.text.len(),
276            8,
277            "Generated text length should match configured length of 8"
278        );
279        assert_eq!(captcha_result.width, 150);
280        assert_eq!(captcha_result.height, 50);
281        assert_eq!(captcha_result.length, 8);
282        assert_eq!(captcha_result.complexity, 1);
283        assert!(!captcha_result.image_data.is_empty());
284    }
285
286    // 测试生成验证码并保存为图片文件
287    #[test]
288    fn gen_code() -> Result<(), Box<dyn std::error::Error>> {
289        // 生成验证码
290        let result = CaptchaGen::builder()
291            .size(100, 40)
292            .text("ABCD")
293            .build()
294            .gen();
295
296        // 处理生成结果
297        result.map(|captcha_result| {
298            println!("code: {}", captcha_result.text); // 打印验证码文本
299            println!("width: {}", captcha_result.width); // 打印图片宽度
300            println!("height: {}", captcha_result.height); // 打印图片高度
301            println!("length: {}", captcha_result.length); // 打印验证码长度
302            println!("complexity: {}", captcha_result.complexity); // 打印验证码复杂度
303
304            // 将图片数据保存为文件
305            std::fs::write("test.png", &captcha_result.image_data)?;
306            Ok(())
307        })?
308    }
309
310    #[test]
311    fn test_gen() {
312        let captcha_gen = CaptchaBuilder::builder()
313            .length(6)
314            .complexity(3)
315            .size(200, 100)
316            .text("TEST123")
317            .build();
318
319        let result = captcha_gen.gen();
320        assert!(result.is_ok(), "Captcha generation should succeed");
321
322        let captcha_result = result.unwrap();
323        assert_eq!(captcha_result.text, "TEST123");
324        assert_eq!(captcha_result.width, 200);
325        assert_eq!(captcha_result.height, 100);
326        assert_eq!(captcha_result.length, 6);
327        assert_eq!(captcha_result.complexity, 3);
328        assert!(!captcha_result.image_data.is_empty());
329    }
330}