批量处理证照图片的实战笔记:加水印、上传与批量SQL生成
引言
在一次内部项目中,我们需要对一批门店证照扫描图片进行集中处理,包括给每张图片添加水印、归档上传到对象存储,并生成对应的SQL语句将图片信息写入数据库。由于涉及的图片数量较多,手工逐一处理显然不可行,我们采用了脚本和工具链来自动化这一流程。本文作为实战笔记,将分享整个批处理过程的步骤、遇到的问题以及经验总结。
批量加水印:Shell脚本 + ImageMagick
第一步是给所有证照图片添加统一水印。例如,我们希望在每张图片的右下角加盖公司水印Logo或文字,以防止滥用。这里我们选择使用Linux下的 ImageMagick 工具结合Shell脚本来批量处理,其原因是ImageMagick提供了强大的命令行图片处理能力,而Shell脚本便于遍历批量文件。
在处理前,需确保服务器已安装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语法错误。另外,大批量插入操作建议在业务低峰期进行,并做好备份。
遇到的问题及解决方案
在实际执行上述批处理流程时,我们也遇到了一些问题,以下列举几个典型问题和解决方案:
- 脚本兼容性问题: 起初编写的Shell脚本在处理文件名包含空格或特殊符号的图片时失败。这是因为for循环分隔文件名时遇到了空格。解决方案: 在脚本中使用引号包裹文件路径变量(例如
for img in "$SRC_DIR"/*
并在后续引用时用”$img”),或改用while read
配合find -print0
处理,确保文件名作为整体被识别。 - 图片处理错误: 部分图片由于格式不符合预期(例如PNG格式或损坏文件)导致ImageMagick报错中止。解决方案: 在脚本中增加格式过滤,只处理.jpg或指定格式文件;对于损坏文件提前筛除。同时在composite命令后加上错误捕获(如上述脚本中用$?判断),出错时记录日志而不中断整个批处理。
- SCP中断: 在第一次尝试传输时,由于网络波动导致scp中途中断,需重新传输整个压缩包。正如前文所述,scp无法断点续传 ,这种情况下只能重新开始或换用其他工具。解决方案: 改用rsync替代scp来传输大文件 。rsync支持断点续传和校验,在网络不稳定情况下更可靠。我们实际采用rsync重新传输,顺利完成了大文件的续传。
- 上传超时: 批量上传过程中,某些文件由于网络原因上传耗时过长甚至超时失败。解决方案: 在Java上传工具中实现重试机制,例如对失败的上传重试2-3次。如果仍失败则跳过并在最后汇总失败文件名单。这些失败文件稍后可重新运行工具专门上传,或人工检查网络环境后再处理。
- SQL导入问题: 在将生成的SQL文件导入数据库时,发现个别记录插入失败。检查发现是由于少数字段内容超长或格式不符数据库约束(比如证照类型字段长度不够)。解决方案: 提前在生成SQL阶段校验字段长度或内容合法性,或者在数据库插入前对SQL脚本做一次审查清理。另外,超大SQL脚本执行可能比较慢,可以考虑拆分为多段执行或使用数据库批量导入工具。
优化思考
虽然此次批处理任务顺利完成,但回顾整个流程,仍有一些可以优化改进的地方:
- 提高图片处理并发度: 当前Shell脚本按顺序逐张处理图片,在图片数量非常多时效率不高。可以考虑利用GNU Parallel或Shell的后台执行 (&) 实现多线程并发处理,充分利用多核CPU。同时也可以尝试使用更高效的图像处理库或命令参数(如ImageMagick的mogrify直接批量处理目录)。
- 使用更高级语言统筹: Shell脚本虽然方便,但在复杂流程和错误处理上有局限。可以使用Python脚本替代,将加水印、上传和SQL生成整合到一个脚本中。Python的Pillow库可以方便地添加水印,结合请求库调用对象存储API,一站式完成所有步骤。此外,Python更易实现细粒度的异常处理和日志记录。
- 日志和监控: 为了方便排查问题,建议为批处理过程增加详细日志记录。可以将Shell脚本的输出重定向到日志文件,Java工具也记录每个文件的上传结果。若有条件,还可以加入简单的进度监控(例如每处理100张输出提示)或邮件通知,方便及时了解批处理进展。
- 进一步自动化和容错: 可以设计整个流程的一键脚本或工作流,减少人工介入。例如编写一个主脚本依次调用各步骤,并在出现错误时自动尝试重试或回滚。同时考虑极端情况下的容错,例如某步骤多次失败则跳过并标记处理结果,以免整批任务卡死在个别文件上。
- 性能与资源利用: 如果图片总量巨大,需要考虑批处理对系统IO和内存的影响。可以在非业务高峰时段执行,或者分批多次执行以降低单次压力。另外,对象存储的上传也可以考虑启用并发或分片上传提高速度。
经验总结
通过这次证照图片批量处理的实践,我们总结出了一些通用的经验教训:
- 批量任务做好失败回滚设计: 在大批量任务中,务必考虑中途失败的情况,设计可以重启续作或回滚的机制。例如记录已处理项,使得脚本再次运行时能跳过已完成部分,或者在数据库操作前后实现事务或检查,避免部分成功部分失败造成数据不一致。
- 小规模测试先行: 在对海量数据进行批处理之前,先拿一小部分数据进行端到端测试。通过预演流程可以及时发现脚本错误、参数不当或环境配置问题,避免大规模运行时才暴露致命问题。
- 自动化优于手工: 能用脚本解决的尽量用脚本,实现一次调试,多次运行。批处理脚本应尽量通用和可复用,同时增加必要的日志输出,方便在无人值守执行时也能事后追溯过程。
- 关注数据和安全: 处理涉及证照等重要文件时,要注意文件权限和传输安全。采用scp/rsync等通过SSH的方式传输保证了安全性,上线前也需确保对象存储的访问权限正确配置,避免敏感图片泄露。此外,加水印本身也是一种保护手段,可以在一定程度上防止未授权的文件使用。
- 跨团队协作: 这类批量处理往往涉及运维、开发等多个团队配合。例如部署环境、上传权限、数据库字段定义等都需要沟通确认。在动手之前,多和相关人员确认细节(如存储路径、命名规范、数据格式),可以避免返工。
通过以上步骤和经验,这次批量处理任务最终圆满完成:所有门店证照图片都成功添加了水印并上传至对象存储,数据库也正确记录了每张图片的位置索引。这次实战不仅达成了目标,也为日后处理类似批量任务积累了宝贵经验。
脱敏说明:本文所有出现的表名、字段名、接口地址、变量名、IP地址及示例数据等均非真实, 仅用于阐述技术思路与实现步骤,示例代码亦非公司真实代码。 示例方案亦非公司真实完整方案,仅为本人记忆总结,用于技术学习探讨。
• 文中所示任何标识符并不对应实际生产环境中的名称或编号。
• 示例 SQL、脚本、代码及数据等均为演示用途,不含真实业务数据,也不具备直接运行或复现的完整上下文。
• 读者若需在实际项目中参考本文方案,请结合自身业务场景及数据安全规范,使用符合内部命名和权限控制的配置。版权声明:本文版权归原作者所有,未经作者事先书面许可,任何单位或个人不得以任何方式复制、转载、摘编或用于商业用途。
• 若需非商业性引用或转载本文内容,请务必注明出处并保持内容完整。
• 对因商业使用、篡改或不当引用本文内容所产生的法律纠纷,作者保留追究法律责任的权利。
Copyright © 1989–Present Ge Yuxu. All Rights Reserved.