Files
blackbird/captcha2/src/main.cpp
T
qing 3b6a0fb002 captcha2: 迁移 captcha.ini 至 CLI args,移除 confs 依赖
- captcha2/src/main.cpp: 移除 fetch_config_from_confs/merge_ini/RpcClient,
  扩展 CLI 至 11 项(--instance/--log-level/--len/--ttl/--font-path/
  --max-store/--rate-limit),配置优先级 CLI > env > 默认值
- core/services.yaml: captcha2 全部 8 项参数迁入 CLI args
- confs/configs/captcha.ini: 已删除
- README.md: 移除 captcha.ini 表行
- _inst.sh: 新增 inst_module captcha2
2026-08-10 09:56:41 +00:00

628 lines
23 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* captcha2 — C++20 验证码服务(pymsg RPC)
*
* 基于 libmsg RpcServer + FreeType 字体渲染 + stb_image_write PNG 编码。
* 协议与 Python captcha 完全兼容,gateway 零改动。
*/
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
#include <us/Msg.hpp>
#include <us/RpcServer.hpp>
#include <us/AsyncQueue.hpp>
#include <boost/json.hpp>
#include <boost/program_options.hpp>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <deque>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <random>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include <us/LogC.hpp>
namespace json = boost::json;
namespace po = boost::program_options;
using qing::LogC;
// ═══════════════════════════════════════════════════════════════════════════
// 1. 配置结构
// ═══════════════════════════════════════════════════════════════════════════
// P3-1: 安全的 stoi 封装,异常时输出键名与原始值便于排障
static int parse_int(const std::string& key, const std::string& val) {
try {
return std::stoi(val);
} catch (const std::exception& ex) {
std::cerr << "[captcha2] 配置解析失败: " << key << " = '" << val
<< "' (" << ex.what() << ")" << std::endl;
std::exit(1);
}
}
struct CaptchaConfig {
std::string host = "127.0.0.1";
int port = 8009;
int workers = 4;
int len = 4;
int ttl = 300;
std::string font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf";
int max_store = 10000;
int rate_limit_per_min = 600; // P1-1: 全局限流仅作最后防线,主要限流由 gateway 按 IP 负责
};
// 环境变量覆盖(Docker 兼容;CLI > 环境变量 > 默认值)
static void apply_env_overrides(CaptchaConfig& cfg) {
if (auto* v = std::getenv("LISTEN_ADDR")) cfg.host = v;
if (auto* v = std::getenv("LISTEN_PORT")) cfg.port = parse_int("LISTEN_PORT", v);
if (auto* v = std::getenv("WORKERS")) cfg.workers = parse_int("WORKERS", v);
if (auto* v = std::getenv("CAPTCHA_FONT_PATH")) cfg.font_path = v;
if (auto* v = std::getenv("CAPTCHA_MAX_STORE")) cfg.max_store = parse_int("CAPTCHA_MAX_STORE", v);
// P3-3: 补齐 captcha 段环境变量覆盖
if (auto* v = std::getenv("CAPTCHA_LEN")) cfg.len = parse_int("CAPTCHA_LEN", v);
if (auto* v = std::getenv("CAPTCHA_TTL")) cfg.ttl = parse_int("CAPTCHA_TTL", v);
if (auto* v = std::getenv("CAPTCHA_RATE_LIMIT")) cfg.rate_limit_per_min = parse_int("CAPTCHA_RATE_LIMIT", v);
}
// 配置完整性校验
static void validate_config(const CaptchaConfig& cfg) {
if (cfg.host.empty()) { std::cerr << "[captcha2] 配置缺失: server.host" << std::endl; std::exit(1); }
if (cfg.port <= 0 || cfg.port > 65535) { std::cerr << "[captcha2] 配置无效: server.port" << std::endl; std::exit(1); }
if (cfg.workers < 0) { std::cerr << "[captcha2] workers 不能为负数" << std::endl; std::exit(1); }
if (cfg.len < 2 || cfg.len > 8) { std::cerr << "[captcha2] captcha.len 应在 2~8 之间" << std::endl; std::exit(1); }
if (cfg.ttl < 10) { std::cerr << "[captcha2] captcha.ttl 至少 10 秒" << std::endl; std::exit(1); }
if (cfg.max_store <= 0) { std::cerr << "[captcha2] captcha.max_store 必须为正整数" << std::endl; std::exit(1); }
}
// ═══════════════════════════════════════════════════════════════════════════
// 2. Base64 编码
// ═══════════════════════════════════════════════════════════════════════════
static const char kBase64Chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static std::string base64_encode(const std::vector<uint8_t>& data) {
std::string out;
out.reserve(((data.size() + 2) / 3) * 4);
for (size_t i = 0; i < data.size(); i += 3) {
uint32_t n = (uint32_t(data[i]) << 16);
if (i + 1 < data.size()) n |= (uint32_t(data[i + 1]) << 8);
if (i + 2 < data.size()) n |= uint32_t(data[i + 2]);
out.push_back(kBase64Chars[(n >> 18) & 0x3F]);
out.push_back(kBase64Chars[(n >> 12) & 0x3F]);
out.push_back((i + 1 < data.size()) ? kBase64Chars[(n >> 6) & 0x3F] : '=');
out.push_back((i + 2 < data.size()) ? kBase64Chars[n & 0x3F] : '=');
}
return out;
}
// ═══════════════════════════════════════════════════════════════════════════
// 3. CaptchaStore — 线程安全 Token 存储
// ═══════════════════════════════════════════════════════════════════════════
class CaptchaStore {
public:
struct Entry {
std::string answer;
std::chrono::steady_clock::time_point created;
};
std::string generate(const std::string& answer, int ttl_seconds, int max_size) {
std::lock_guard<std::mutex> lock(mtx_);
expire_locked(ttl_seconds);
if ((int)store_.size() >= max_size)
throw std::runtime_error("too many pending captchas");
// 生成 256-bit 随机 Token(64 字符 hex)
std::array<uint8_t, 32> rand_bytes;
std::random_device rd;
for (auto& b : rand_bytes) b = static_cast<uint8_t>(rd());
std::ostringstream oss;
oss << std::hex;
for (auto b : rand_bytes) oss << std::setw(2) << std::setfill('0') << (int)b;
std::string token = oss.str();
store_[token] = {answer, std::chrono::steady_clock::now()};
return token;
}
// 校验 + 一次性消费;返回 nullopt 表示 token 无效;否则返回 {answer_correct: bool}
struct VerifyResult { bool valid; bool token_found; };
VerifyResult verify(const std::string& token, const std::string& answer, int ttl_seconds) {
std::lock_guard<std::mutex> lock(mtx_);
auto it = store_.find(token);
if (it == store_.end()) return {false, false};
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now() - it->second.created).count();
if (elapsed > ttl_seconds) {
store_.erase(it);
return {false, false};
}
auto stored_answer = it->second.answer;
store_.erase(it); // 一次性消费
// 恒定时间比较
bool match = (answer.size() == stored_answer.size());
if (match) {
int diff = 0;
for (size_t i = 0; i < answer.size(); ++i)
diff |= answer[i] ^ stored_answer[i];
match = (diff == 0);
}
return {match, true};
}
size_t size() const {
std::lock_guard<std::mutex> lock(mtx_);
return store_.size();
}
private:
void expire_locked(int ttl_seconds) {
auto now = std::chrono::steady_clock::now();
for (auto it = store_.begin(); it != store_.end(); ) {
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
now - it->second.created).count();
if (elapsed > ttl_seconds)
it = store_.erase(it);
else
++it;
}
}
mutable std::mutex mtx_;
std::unordered_map<std::string, Entry> store_;
};
// ═══════════════════════════════════════════════════════════════════════════
// 4. generate_text() — 安全随机验证码文本
// ═══════════════════════════════════════════════════════════════════════════
static std::string generate_text(int len) {
static const char kChars[] =
"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
static constexpr size_t kNumChars = sizeof(kChars) - 1;
// 使用 std::random_device 逐字符生成(非确定性 CSPRNG),
// 与 Token 生成策略对齐,消除 mt19937 可预测性风险(P2-1)
std::random_device rd;
std::string text;
text.reserve(len);
for (int i = 0; i < len; ++i)
text.push_back(kChars[rd() % kNumChars]);
return text;
}
// ═══════════════════════════════════════════════════════════════════════════
// 5. 图片生成 — FreeType + 像素操作 + stb PNG 编码
// ═══════════════════════════════════════════════════════════════════════════
struct RGB {
uint8_t r, g, b;
};
static constexpr int kImgWidth = 160;
static constexpr int kImgHeight = 50;
static constexpr int kImgChannels = 3;
// 线程本地随机数生成器(用于图片生成中的各种随机元素)
static thread_local std::mt19937 t_rng(std::random_device{}());
static RGB random_color() {
std::uniform_int_distribution<int> d(0, 255);
return {static_cast<uint8_t>(d(t_rng)), static_cast<uint8_t>(d(t_rng)), static_cast<uint8_t>(d(t_rng))};
}
static inline uint8_t* pixel_at(std::vector<uint8_t>& pix, int x, int y) {
return &pix[(y * kImgWidth + x) * kImgChannels];
}
// 最近邻插值旋转灰度位图(expand 模式),返回旋转后的位图
static std::vector<uint8_t> rotate_bitmap(
const FT_Bitmap& src, double angle_deg,
int& out_w, int& out_h)
{
double rad = angle_deg * M_PI / 180.0;
double cos_a = std::cos(rad), sin_a = std::sin(rad);
int sw = static_cast<int>(src.width);
int sh = static_cast<int>(src.rows);
// expand: 计算旋转后包围盒
double corners[4][2] = {
{0, 0}, {double(sw), 0}, {double(sw), double(sh)}, {0, double(sh)}
};
double min_x = 1e9, max_x = -1e9, min_y = 1e9, max_y = -1e9;
for (auto& c : corners) {
double rx = c[0] * cos_a - c[1] * sin_a;
double ry = c[0] * sin_a + c[1] * cos_a;
if (rx < min_x) min_x = rx;
if (rx > max_x) max_x = rx;
if (ry < min_y) min_y = ry;
if (ry > max_y) max_y = ry;
}
out_w = static_cast<int>(std::ceil(max_x - min_x));
out_h = static_cast<int>(std::ceil(max_y - min_y));
std::vector<uint8_t> out(out_w * out_h, 0);
for (int dy = 0; dy < out_h; ++dy) {
for (int dx = 0; dx < out_w; ++dx) {
// 逆变换:输出坐标 → 源坐标
double sx = (dx + min_x) * cos_a + (dy + min_y) * sin_a;
double sy = -(dx + min_x) * sin_a + (dy + min_y) * cos_a;
int six = static_cast<int>(std::floor(sx));
int siy = static_cast<int>(std::floor(sy));
if (six >= 0 && six < sw && siy >= 0 && siy < sh) {
// 最近邻
out[dy * out_w + dx] = src.buffer[siy * src.pitch + six];
}
}
}
return out;
}
// Alpha 合成旋转后的字符位图到主画布(灰度值 = alpha,0=透明, 255=不透明)
static void composite_char(
std::vector<uint8_t>& pixels,
const std::vector<uint8_t>& glyph, int gw, int gh,
int dst_x, int dst_y, const RGB& color)
{
for (int gy = 0; gy < gh; ++gy) {
for (int gx = 0; gx < gw; ++gx) {
uint8_t alpha = glyph[gy * gw + gx];
if (alpha == 0) continue;
int px = dst_x + gx;
int py = dst_y + gy;
if (px < 0 || px >= kImgWidth || py < 0 || py >= kImgHeight) continue;
auto* p = pixel_at(pixels, px, py);
float a = alpha / 255.0f;
p[0] = static_cast<uint8_t>(p[0] * (1 - a) + color.r * a);
p[1] = static_cast<uint8_t>(p[1] * (1 - a) + color.g * a);
p[2] = static_cast<uint8_t>(p[2] * (1 - a) + color.b * a);
}
}
}
// Bresenham 画线
static void draw_line(std::vector<uint8_t>& pixels, int x0, int y0, int x1, int y1, const RGB& color) {
int dx = std::abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -std::abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
while (true) {
if (x0 >= 0 && x0 < kImgWidth && y0 >= 0 && y0 < kImgHeight) {
auto* p = pixel_at(pixels, x0, y0);
p[0] = color.r; p[1] = color.g; p[2] = color.b;
}
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; }
if (e2 <= dx) { err += dx; y0 += sy; }
}
}
// 设置单个像素
static void set_pixel(std::vector<uint8_t>& pixels, int x, int y, const RGB& color) {
if (x >= 0 && x < kImgWidth && y >= 0 && y < kImgHeight) {
auto* p = pixel_at(pixels, x, y);
p[0] = color.r; p[1] = color.g; p[2] = color.b;
}
}
// 主生成函数:返回 PNG 字节流
static std::vector<uint8_t> generate_image(const std::string& text, const std::string& font_path) {
// 1. 创建 RGB 画布 + 随机背景色
std::vector<uint8_t> pixels(kImgWidth * kImgHeight * kImgChannels);
auto bg = random_color();
for (int i = 0; i < kImgWidth * kImgHeight; ++i) {
pixels[i * 3 + 0] = bg.r;
pixels[i * 3 + 1] = bg.g;
pixels[i * 3 + 2] = bg.b;
}
// 2. 初始化 FreeType
FT_Library ft;
if (FT_Init_FreeType(&ft) != 0)
throw std::runtime_error("FreeType 初始化失败");
FT_Face face;
// 尝试指定字体路径,失败回退 DejaVu,再失败退出
if (FT_New_Face(ft, font_path.c_str(), 0, &face) != 0) {
std::cerr << "[captcha2] 字体 " << font_path << " 加载失败,尝试默认 DejaVu" << std::endl;
if (FT_New_Face(ft, "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 0, &face) != 0) {
FT_Done_FreeType(ft);
throw std::runtime_error("无法加载任何 TrueType 字体");
}
}
// 3. 逐字符渲染 + 旋转 + 合成
std::uniform_int_distribution<int> angle_dist(-30, 30);
std::uniform_int_distribution<int> size_dist(20, 38);
std::uniform_int_distribution<int> y_dist(2, 12);
std::uniform_int_distribution<int> x_jitter(-3, 3);
for (size_t i = 0; i < text.size(); ++i) {
int char_size = size_dist(t_rng);
FT_Set_Pixel_Sizes(face, 0, char_size);
if (FT_Load_Char(face, text[i], FT_LOAD_RENDER) != 0)
continue;
auto& bmp = face->glyph->bitmap;
if (bmp.buffer == nullptr || bmp.width == 0 || bmp.rows == 0)
continue;
auto fg = random_color();
int angle = angle_dist(t_rng);
int y = y_dist(t_rng);
int x = 15 + static_cast<int>(i) * 32 + x_jitter(t_rng);
int rw, rh;
auto rotated = rotate_bitmap(bmp, angle, rw, rh);
// 合成位置:水平居中于 x,y 为顶部留白(与 Python 版语义一致)
// 底部越界时上移钳制,保证字符完整落在画布内
int cx = x - rw / 2;
int cy = y;
if (cy + rh > kImgHeight) cy = kImgHeight - rh;
if (cy < 0) cy = 0;
composite_char(pixels, rotated, rw, rh, cx, cy, fg);
}
FT_Done_Face(face);
FT_Done_FreeType(ft);
// 4. 4 条随机干扰线
std::uniform_int_distribution<int> xd(0, kImgWidth - 1);
std::uniform_int_distribution<int> yd(0, kImgHeight - 1);
for (int i = 0; i < 4; ++i) {
draw_line(pixels, xd(t_rng), yd(t_rng), xd(t_rng), yd(t_rng), random_color());
}
// 5. 50 个随机噪点
for (int i = 0; i < 50; ++i) {
set_pixel(pixels, xd(t_rng), yd(t_rng), random_color());
}
// 6. PNG 编码
std::vector<uint8_t> png_buf;
stbi_write_png_to_func(
[](void* ctx, void* data, int size) {
auto* buf = static_cast<std::vector<uint8_t>*>(ctx);
buf->insert(buf->end(), static_cast<uint8_t*>(data), static_cast<uint8_t*>(data) + size);
},
&png_buf, kImgWidth, kImgHeight, kImgChannels, pixels.data(), kImgWidth * kImgChannels);
return png_buf;
}
// ═══════════════════════════════════════════════════════════════════════════
// 6. 滑动窗口速率限制
// ═══════════════════════════════════════════════════════════════════════════
class RateLimiter {
public:
bool allow(int max_per_minute) {
if (max_per_minute <= 0) return true; // 0 = 不限制
std::lock_guard<std::mutex> lock(mtx_);
auto now = std::chrono::steady_clock::now();
while (!timestamps_.empty() &&
std::chrono::duration_cast<std::chrono::seconds>(
now - timestamps_.front()).count() > 60) {
timestamps_.pop_front();
}
if (static_cast<int>(timestamps_.size()) >= max_per_minute)
return false;
timestamps_.push_back(now);
return true;
}
private:
std::deque<std::chrono::steady_clock::time_point> timestamps_;
std::mutex mtx_;
};
// ═══════════════════════════════════════════════════════════════════════════
// 7. main() — RpcServer 启动
// ═══════════════════════════════════════════════════════════════════════════
static CaptchaConfig g_config;
static CaptchaStore g_store;
static RateLimiter g_rate_limiter;
static auto& g_log = LogC::instance();
static const auto _log_init = (LogC::instance().set_service_name("captcha2-log"), true);
int main(int argc, char* argv[]) {
try {
// ── 命令行参数 ──
po::options_description desc("captcha2 — pymsg 验证码服务");
desc.add_options()
("help,h", "显示帮助")
("instance", po::value<std::string>()->default_value("captcha2"),
"实例名(注册中心用)")
("log-level", po::value<std::string>()->default_value("info"),
"日志级别(debug|info|warn|error)")
("addr,a", po::value<std::string>(), "监听地址")
("port,p", po::value<int>(), "监听端口")
("workers,w", po::value<int>(), "工作线程数")
("len", po::value<int>(), "验证码长度(2~8,默认 4)")
("ttl", po::value<int>(), "验证码有效期秒数(默认 300)")
("font-path", po::value<std::string>(),
"TrueType 字体路径")
("max-store", po::value<int>(),
"内存中最大待验证 token 数(默认 10000)")
("rate-limit", po::value<int>(),
"全局每分钟生成上限(默认 600)");
po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << std::endl;
return 0;
}
// ── 配置加载:CLI > 环境变量 > 默认值 ──
apply_env_overrides(g_config);
if (vm.count("addr")) g_config.host = vm["addr"].as<std::string>();
if (vm.count("port")) g_config.port = vm["port"].as<int>();
if (vm.count("workers")) g_config.workers = vm["workers"].as<int>();
if (vm.count("len")) g_config.len = vm["len"].as<int>();
if (vm.count("ttl")) g_config.ttl = vm["ttl"].as<int>();
if (vm.count("font-path")) g_config.font_path = vm["font-path"].as<std::string>();
if (vm.count("max-store")) g_config.max_store = vm["max-store"].as<int>();
if (vm.count("rate-limit")) g_config.rate_limit_per_min = vm["rate-limit"].as<int>();
validate_config(g_config);
// 日志级别
std::string log_level = vm["log-level"].as<std::string>();
std::string instance = vm["instance"].as<std::string>();
g_log.set_level(log_level);
std::cerr << "[captcha2] instance=" << instance << " log-level=" << log_level << std::endl;
std::cerr << "[captcha2] 配置来源: CLI > 环境变量 > 默认值" << std::endl;
std::cerr << "[captcha2] captcha_len=" << g_config.len
<< " ttl=" << g_config.ttl
<< " max_store=" << g_config.max_store
<< " rate_limit=" << g_config.rate_limit_per_min << "/min" << std::endl;
// 校验字体文件存在
{
FILE* ftest = std::fopen(g_config.font_path.c_str(), "rb");
if (!ftest) {
std::cerr << "[captcha2] 致命错误: 字体文件不存在 " << g_config.font_path << std::endl;
return 1;
}
std::fclose(ftest);
}
// ── 启动 RpcServer ──
g_log.info("captcha2", "启动中... 监听 tcp://" + g_config.host + ":" + std::to_string(g_config.port));
pmc::net::RpcServer rpc(g_config.host, g_config.port,
pmc::net::MsgServerConfig{.workers = g_config.workers});
// 路由: image
rpc.add_route("image", [](const json::object&) -> json::value {
// 速率限制(P3-5: 抛异常由 RpcServer 包装为 success:false,gateway 映射 429)
if (!g_rate_limiter.allow(g_config.rate_limit_per_min)) {
g_log.warning("captcha2", "速率限制触发,拒绝生成验证码");
throw std::runtime_error("rate_limit_exceeded");
}
std::string text = generate_text(g_config.len);
// 小写化存储,与 Python 版行为对齐(大小写不敏感校验)
std::string answer_lc = text;
std::transform(answer_lc.begin(), answer_lc.end(), answer_lc.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
// P3-5: generate 异常直接传播(RpcServer 自动包装为 success:false,gateway 映射 503)
std::string token = g_store.generate(answer_lc, g_config.ttl, g_config.max_store);
auto png_bytes = generate_image(text, g_config.font_path);
auto png_b64 = base64_encode(png_bytes);
g_log.info("captcha2", "验证码已生成 token=" + token.substr(0, 8) + "...");
json::object data;
data["token"] = token;
data["image"] = png_b64;
return data;
});
// 路由: verify
rpc.add_route("verify", [](const json::object& req) -> json::value {
std::string token, answer;
auto it_tok = req.find("token");
auto it_ans = req.find("answer");
if (it_tok == req.end() || it_ans == req.end() ||
!it_tok->value().is_string() || !it_ans->value().is_string()) {
json::object data;
data["valid"] = false;
data["error"] = "token and answer required";
return data;
}
token = json::value_to<std::string>(it_tok->value());
answer = json::value_to<std::string>(it_ans->value());
// 小写化(使用 unsigned char 避免 UB)
std::transform(answer.begin(), answer.end(), answer.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
auto result = g_store.verify(token, answer, g_config.ttl);
if (!result.token_found) {
g_log.warning("captcha2", "验证码校验失败-无效Token token=" + token.substr(0, std::min<size_t>(8, token.size())) + "...");
json::object data;
data["valid"] = false;
data["error"] = "invalid or expired token";
return data;
}
if (!result.valid) {
g_log.warning("captcha2", "验证码校验失败-答案错误 token=" + token.substr(0, std::min<size_t>(8, token.size())) + "...");
}
json::object data;
data["valid"] = result.valid;
return data;
});
// 路由: health
rpc.add_route("health", [](const json::object&) -> json::value {
json::object data;
data["status"] = "ok";
data["service"] = "captcha";
return data;
});
std::cerr << "[captcha2] 监听 tcp://" << g_config.host << ":" << g_config.port << std::endl;
g_log.info("captcha2", "开始监听 tcp://" + g_config.host + ":" + std::to_string(g_config.port));
rpc.start(); // 阻塞
} catch (const std::exception& e) {
std::cerr << "[captcha2] 致命错误: " << e.what() << std::endl;
g_log.error("captcha2", std::string("致命错误: ") + e.what());
return 1;
}
return 0;
}