CentOS 7 安装条码识别工具 zbar

终端命令

安装
sudo yum install -y epel-release
sudo yum --enablerepo=epel install -y zbar
which zbarimg && zbarimg --version
卸载
sudo yum remove -y zbar

php代码案例 从图片上传过来 到识别一维码

<?php
//  /shopapi/upload.php
// 作用:上传图片 -> 读取面单条码(优先 Code128) -> 返回单号 ->(可选)入库

/* ====== CORS ====== */
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }

/* ====== 仅 POST ====== */
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
  http_response_code(405);
  header('Content-Type: application/json; charset=utf-8');
  echo json_encode(['code'=>405, 'msg'=>'Method Not Allowed'], JSON_UNESCAPED_UNICODE);
  exit;
}

/* ====== JSON 输出工具 ====== */
header('Content-Type: application/json; charset=utf-8');
function j($arr, int $code = 200){
  http_response_code($code);
  echo json_encode($arr, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
  exit;
}

/* ====== 可选:数据库配置(想入库就填) ====== */
$DB_DSN  = ''; // 例:'mysql:host=127.0.0.1;dbname=shop;charset=utf8mb4'
$DB_USER = '';
$DB_PASS = '';
$SAVE_DB = $DB_DSN !== '';

/* ====== 工具:拼接当前域名 ====== */
function current_base_url(): string {
  $isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
             (isset($_SERVER['SERVER_PORT']) && (int)$_SERVER['SERVER_PORT'] === 443) ||
             (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) === 'https');
  $scheme = $isHttps ? 'https' : 'http';
  $host   = $_SERVER['HTTP_HOST'] ?? 'localhost';
  return $scheme . '://' . $host;
}

/* ====== 条码识别函数(优先 Code128,失败再开常见制式) ====== */
function readWaybillFromImage(string $imgPath): ?string {
    $bin = '/usr/bin/zbarimg';
    if (!is_file($imgPath)) return null;

    // 1) 只开 Code128,最快最稳
    $cmd1 = sprintf('%s --raw --set "*.enable=0" --set "code128.enable=1" %s 2>/dev/null',
        $bin, escapeshellarg($imgPath));
    $out = trim(shell_exec($cmd1) ?? '');

    // 2) 读不到,再开常见的一些(部分快递会用 i25/ean13)
    if ($out === '') {
        $cmd2 = sprintf('%s --raw --set "*.enable=0" --set "code128.enable=1" --set "i25.enable=1" --set "ean13.enable=1" %s 2>/dev/null',
            $bin, escapeshellarg($imgPath));
        $out = trim(shell_exec($cmd2) ?? '');
    }

    if ($out) {
        // 取 10~18 位数字,且排除 11 位手机号
        if (preg_match_all('/(?<!\d)(\d{10,18})(?!\d)/', $out, $m)) {
            foreach ($m[1] as $n) {
                if (!preg_match('/^1[3-9]\d{9}$/', $n)) return $n;
            }
        }
    }
    return null;
}

/* ====== 接收上传 ====== */
if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
  $err = $_FILES['file']['error'] ?? -1;
  $map = [
    UPLOAD_ERR_INI_SIZE=>'文件超出服务器限制',
    UPLOAD_ERR_FORM_SIZE=>'文件超出表单限制',
    UPLOAD_ERR_PARTIAL=>'文件只上传了一部分',
    UPLOAD_ERR_NO_FILE=>'没有接收到文件',
    UPLOAD_ERR_NO_TMP_DIR=>'临时目录不存在',
    UPLOAD_ERR_CANT_WRITE=>'文件写入失败',
    UPLOAD_ERR_EXTENSION=>'扩展中断上传'
  ];
  j(['code'=>400, 'msg'=>'上传失败:'.($map[$err] ?? '未知错误')]);
}

/* ====== 基础校验 ====== */
$allowExt = ['jpg','jpeg','png','gif','webp'];
$maxSize  = 8 * 1024 * 1024; // 8MB
$origName = $_FILES['file']['name'];
$tmpPath  = $_FILES['file']['tmp_name'];
$size     = (int)$_FILES['file']['size'];

$ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowExt, true)) j(['code'=>400, 'msg'=>'不支持的图片格式']);
if ($size <= 0 || $size > $maxSize)   j(['code'=>400, 'msg'=>'图片大小不符合要求(最大8MB)']);

$finfo = function_exists('finfo_open') ? finfo_open(FILEINFO_MIME_TYPE) : false;
if ($finfo) {
  $mime = finfo_file($finfo, $tmpPath); finfo_close($finfo);
  $okM = ['image/jpeg','image/png','image/gif','image/webp'];
  if (!in_array($mime, $okM, true)) j(['code'=>400, 'msg'=>'文件类型校验失败']);
}

/* ====== 保存到 /shopapi/upload/YYYYMMDD ====== */
$rootDir = __DIR__;
$day     = date('Ymd');
$saveDir = $rootDir . '/uploaddanhao/' . $day;
$relDir  = '/shopapi/uploaddanhao/' . $day . '/';
if (!is_dir($saveDir) && !mkdir($saveDir, 0775, true)) j(['code'=>500, 'msg'=>'目录创建失败']);

$rand = bin2hex(random_bytes(8));
$saveName = $rand . '.' . $ext;
$dest = rtrim($saveDir, '/').'/'.$saveName;

if (!move_uploaded_file($tmpPath, $dest)) j(['code'=>500, 'msg'=>'保存文件失败']);
$idxFile = $rootDir . '/upload/index.html';
if (!file_exists($idxFile)) @file_put_contents($idxFile, '');

/* ====== 识别条码 -> 运单号 ====== */
$no = readWaybillFromImage($dest);
$base = current_base_url();
$url  = $base . $relDir . $saveName;
$path = $relDir . $saveName;

/* ====== 可选:入库 ====== */
/*
CREATE TABLE `waybills` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `no` VARCHAR(20) NOT NULL,
  `carrier` VARCHAR(20) DEFAULT NULL,
  `src_img` VARCHAR(255) DEFAULT NULL,
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_no` (`no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*/
if ($SAVE_DB && $no) {
    try {
        $pdo = new PDO($DB_DSN, $DB_USER, $DB_PASS, [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        ]);
        $stmt = $pdo->prepare('INSERT IGNORE INTO waybills(no, carrier, src_img) VALUES(?,?,?)');
        $stmt->execute([$no, null, $path]);
    } catch (Throwable $e) {
        // 入库失败不影响本次返回
    }
}

/* ====== 返回 ====== */
if ($no) {
  j(['code'=>0, 'msg'=>'ok', 'no'=>$no, 'url'=>$url, 'path'=>$path, 'name'=>$saveName]);
} else {
  j(['code'=>422, 'msg'=>'未读到条码,可换角度/补光再拍', 'url'=>$url, 'path'=>$path, 'name'=>$saveName]);
}

posted @ 2025-11-05 15:44  79524795  阅读(10)  评论(0)    收藏  举报