文章 · 2021-01-25

批量处理证照图片的实战笔记:加水印、上传与批量SQL生成

在处理前,需确保服务器已安装ImageMagick(如Ubuntu下执行apt-get install imagemagick)。接下来,我们编写一个简单的Shell脚本batch_watermark.sh,遍历目标目录下的所有图片文件,并对每个文件执行加水印操作。示例脚本代码如下:

#!/bin/bash
WATERMARK="watermark.png"               # 预先准备好的水印图片路径
SRC_DIR="./cert_images_original"        # 原始证照图片目录
DST_DIR="./cert_images_watermarked"     # 加水印后的输出目录

mkdir -p "$DST_DIR"
for img in "$SRC_DIR"/*.jpg; do
  filename=$(basename "$img")
  # 使用ImageMagick composite命令叠加水印
  composite -gravity southeast -dissolve 80 "$WATERMARK" "$img" "$DST_DIR/$filename"
  if [ $? -eq 0 ]; then
    echo "$filename 处理完成"
  else
    echo "$filename 处理出错" >&2
  fi
done

上述脚本对每张图片调用了一次ImageMagick的composite命令进行水印叠加:-gravity southeast指定将水印放置在原图的右下角,-dissolve 80表示水印的不透明度为80(数值越小表示水印越透明)。我们将处理后的图片输出到新的目录以保留原始文件。脚本执行时会输出每个文件的处理结果,方便观察进度。

经验提示:批量处理前,先用几张样例图片测试脚本效果,检查水印位置和清晰度是否符合预期。如果需要调整水印大小或位置,可修改ImageMagick命令参数后再批量运行。

离线压缩与SCP传输

完成水印添加后,我们得到了一批已加水印的图片文件。下一步准备将这些文件上传到远程的对象存储服务器。考虑到直接逐文件传输效率较低,我们选择先在本地将所有图片打包压缩,然后通过scp一次性传输。在压缩过程中还顺便进一步减小了文件体积,减少上传耗时。

具体做法是使用tar命令将目录打包并使用gzip压缩:

# 将加水印后的图片目录打包压缩
tar -czvf cert_images_watermarked.tar.gz ./cert_images_watermarked

压缩完成后,会生成一个cert_images_watermarked.tar.gz文件。接着,使用scp将该文件传输到目标服务器(假设目标服务器域名为yourserver.example.com):

# 将压缩包通过scp传输到远程服务器
scp cert_images_watermarked.tar.gz [email protected]:/tmp/

scp传输前请确保网络畅通、带宽充足。如果文件较大,添加-C参数还能在传输过程中启用压缩。传输完成后,在服务器上使用tar -xzvf解压出图片文件,以准备后续的上传入库步骤。

注意:scp本身不支持断点续传,如果网络中断传输会从头开始。因此对于几十GB的大文件,推荐使用支持断点续传的rsync工具进行传输,在网络不稳定情况下更可靠,以避免中途中断造成重复耗时。

图片上传至对象存储(Java 工具)

图片文件抵达服务器并解压后,需要批量上传到对象存储(如公司内部的云存储服务)。在本次实践中,我们编写了一个简单的Java小工具来完成批量上传。之所以使用Java,是因为项目现有的对象存储SDK和权限控制封装在Java环境中,利用现有接口可以确保上传过程安全可靠。

上传流程概述:Java工具会读取指定目录下的所有图片文件,逐一调用对象存储的API上传。对于每个上传的文件,存储服务会返回一个访问URL或键值。我们将这些URL与对应的图片、门店信息关联起来,为生成数据库SQL做准备。伪代码流程如下:

List<File> files = listFiles("/tmp/cert_images_watermarked");
for (File file : files) {
    String key = file.getName();  // 使用文件名作为对象存储中的键
    // 调用对象存储SDK上传文件并获取URL
    String url = objectStorageClient.upload(file, key);
    // 记录映射关系,后续用于生成SQL,例如保存在列表或写入文件
    recordMapping(file.getName(), url);
}

实际实现中,objectStorageClient.upload封装了对象存储服务的HTTP上传过程,并返回文件的公网访问URL。为简化流程,我们直接用文件名作为存储键,这样URL往往可以由固定的域名加上文件名拼接得到。例如,如果对象存储的访问域名为https://files.example.com/,某张图片文件store001_license.jpg上传后的访问路径可能为https://files.example.com/store001_license.jpg。这种命名策略使我们在生成SQL时无需额外查询映射,减少了工作量。

批量上传时要考虑失败重试机制。例如可以对每个文件上传设置重试次数,或将失败的文件记录下来以便稍后手工或脚本重新上传,确保所有文件最终都成功进入对象存储。

批量生成SQL脚本

最后一项任务是根据上传后的图片信息生成对应的SQL插入语句,以将图片关联数据写入数据库中。我们需要针对每张证照图片生成一条INSERT语句,包含诸如门店ID、证照类型、图片URL等字段。由于图片数量多,手工编写SQL既繁琐又容易出错,因此再度借助脚本来批量生成。

假设我们的数据库表结构为:store_certificates(store_id, cert_type, image_url, upload_date),每条记录存储一个门店的一张证照图片及其类型。我们可以编写Shell脚本读取之前记录的文件名和URL映射关系,然后拼接SQL语句。以下是一个示例脚本generate_sql.sh片段:

#!/bin/bash
OUTPUT_FILE="insert_pics.sql"
BASE_URL="https://files.example.com/"  # 对象存储访问域名

> $OUTPUT_FILE  # 清空输出文件
for file_path in ./cert_images_watermarked/*.jpg; do
  filename=$(basename "$file_path")
  # 假设文件名格式为 storeId_certCode.jpg,例如 "001_yingye.jpg"
  store_id=$(echo "$filename" | cut -d'_' -f1)
  cert_code=$(echo "$filename" | cut -d'_' -f2 | sed 's/\..*//')
  image_url="${BASE_URL}${filename}"
  echo "INSERT INTO store_certificates (store_id, cert_type, image_url, upload_date) VALUES ('$store_id', '$cert_code', '$image_url', NOW());" >> $OUTPUT_FILE
done
echo "生成完毕:`wc -l < $OUTPUT_FILE` 条SQL语句已写入$OUTPUT_FILE"

这个脚本假定文件名中包含门店ID和证照类型编码(如001_yingye.jpg表示门店001的营业执照)。脚本通过字符串截取得到所需字段,并拼接生成INSERT语句,最终将所有语句输出到insert_pics.sql文件中。最后一行还简单打印了生成的SQL数量,便于核对。生成完成后,只需将该SQL脚本在目标数据库上执行,即可批量插入所有图片的记录。

注意:在生成SQL时,确保正确转义字符和引用字符串。例如,字符串值要用引号括起来,避免文件名中如果含特殊字符导致SQL语法错误。另外,大批量插入操作建议在业务低峰期进行,并做好备份。

遇到的问题及解决方案

在实际执行上述批处理流程时,我们也遇到了一些问题,以下列举几个典型问题和解决方案:

优化思考

虽然此次批处理任务顺利完成,但回顾整个流程,仍有一些可以优化改进的地方:

经验总结

通过这次证照图片批量处理的实践,我们总结出了一些通用的经验教训:

通过以上步骤和经验,这次批量处理任务最终圆满完成:所有门店证照图片都成功添加了水印并上传至对象存储,数据库也正确记录了每张图片的位置索引。这次实战不仅达成了目标,也为日后处理类似批量任务积累了宝贵经验。

© 2026 Yuxu Ge ·