Batch License Photo Processing: Watermarking, Upload, and SQL Generation
Adding Watermarks with ImageMagick
First, ensure your server has ImageMagick installed (on Ubuntu: apt-get install imagemagick). Write a simple shell script, batch_watermark.sh, to iterate through all images in a target directory and apply watermarks to each.
#!/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
The script invokes ImageMagick's composite command once per image to overlay the watermark. The -gravity southeast flag positions the watermark in the bottom-right corner; -dissolve 80 sets opacity to 80 (lower values make the watermark more transparent). Processed images go to a new directory, preserving originals. The script logs each file's result, so you can monitor progress.
Tip: Before processing at scale, test the script on a few sample images. Check watermark position and clarity. If you need to adjust size or position, modify the ImageMagick parameters and re-test before running the full batch.
Offline Compression and SCP Transfer
After adding watermarks, you have a batch of marked images. The next step is uploading them to a remote object storage server. Direct file-by-file transfer is inefficient, so compress everything locally first, then transfer once with SCP. Compression also reduces size, cutting upload time.
Use tar to bundle the directory and gzip to compress:
# 将加水印后的图片目录打包压缩
tar -czvf cert_images_watermarked.tar.gz ./cert_images_watermarked
This produces cert_images_watermarked.tar.gz. Next, transfer it to the target server (assume the domain is yourserver.example.com):
# 将压缩包通过scp传输到远程服务器
scp cert_images_watermarked.tar.gz [email protected]:/tmp/
Ensure network connectivity and adequate bandwidth before SCP. For large files, add the -C flag to enable compression during transfer. Once transfer completes, decompress on the server with tar -xzvf to prepare for the upload step.
Note: SCP does not support resumable transfer. Network interruption forces you to restart from the beginning. For files of dozens of gigabytes, use rsync instead—it supports resumable transfer and is more reliable on unstable networks, saving time if the connection breaks mid-way.
Uploading Images to Object Storage (Java Tool)
Once images reach the server and are decompressed, upload them in bulk to object storage (such as an internal cloud service). We wrote a simple Java utility for this task. We chose Java because the project's existing object storage SDK and access controls are implemented in Java; using the existing API ensures safe and reliable uploads.
Upload workflow: The Java tool reads all images from a target directory and uploads each to object storage via API. The storage service returns an access URL or key for each file. We associate these URLs with the corresponding image and store information, preparing to generate database SQL. Here is the pseudocode flow:
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);
}
In the actual implementation, objectStorageClient.upload wraps the HTTP upload to object storage and returns the public access URL for each file. To simplify the workflow, we use the filename as the storage key. Typically, the URL can be constructed by concatenating a fixed domain with the filename. For example, if the object storage domain is https://files.example.com/ and an image is named store001_license.jpg, its access path would likely be https://files.example.com/store001_license.jpg. This naming strategy means we don't need extra lookups to construct SQL, reducing overhead.
For bulk uploads, implement retry logic. For instance, retry each file a few times on failure, or log failed files for manual or scripted re-upload later, ensuring all files eventually reach object storage.
Generating SQL in Bulk
The final task is generating SQL INSERT statements from the uploaded images to record them in the database. You need one INSERT per license photo, including fields like store ID, certificate type, and image URL. Manual SQL writing is tedious and error-prone at scale, so script this too.
Suppose the database table is store_certificates(store_id, cert_type, image_url, upload_date), with one row per store's certificate image. Write a shell script to read the filename–URL mappings, then concatenate SQL. Here is a sample generate_sql.sh snippet:
#!/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"
The script assumes filenames encode store ID and certificate type (e.g., 001_yingye.jpg = store 001's business license). It extracts the needed fields via string slicing and concatenates INSERT statements, writing all to insert_pics.sql. The last line prints the statement count for verification. After generating, execute the SQL file on the target database to insert all image records in bulk.
Note: When generating SQL, escape and quote strings properly. String values need quotes to prevent syntax errors if filenames contain special characters. Also, large INSERT operations should run during off-peak hours with backups in place.
Encountered Problems and Solutions
During actual execution of the batch workflow, we faced several typical issues:
- Script compatibility: Initial shell scripts failed on images with spaces or special characters in filenames because
forloops split on spaces. Solution: Quote variable paths (e.g.,for img in "$SRC_DIR"/*and use"$img"when referencing), or usewhile readwithfind -print0to treat filenames as complete units. - Image processing errors: Some images failed ImageMagick processing due to unexpected formats (e.g., PNG) or corruption. Solution: Add format filters to process only
.jpgor specific formats, and exclude corrupted files upfront. Capture errors aftercomposite(check$?as shown in the script) to log failures without halting the entire batch. - SCP interruption: Network fluctuations interrupted the first transfer attempt, requiring a full restart of the compressed package. As noted, SCP cannot resume. Solution: Use
rsyncinstead for large files. It supports resumable transfer and is more reliable on unstable networks. We successfully completed the large transfer usingrsync. - Upload timeout: Some files timed out during bulk upload due to network delays. Solution: Implement retry in the Java tool—retry failed uploads 2–3 times. If still unsuccessful, skip and summarize failed files at the end. Re-run the tool later to upload these files, or inspect the network and retry.
- SQL import failure: Some records failed to insert into the database. Investigation revealed that a few fields exceeded length limits or violated constraints (e.g., certificate type field was too short). Solution: Validate field lengths and content legality when generating SQL, or review the SQL file before import. Large SQL files may execute slowly; consider splitting into segments or using a database bulk import tool.
Optimization Opportunities
Though the batch task completed successfully, reviewing the workflow reveals several areas for improvement:
- Parallelize image processing: The current shell script processes images sequentially. With thousands of images, this is slow. Use GNU Parallel or shell background execution (
&) to leverage multi-core CPUs. Also explore faster image libraries or ImageMagick options likemogrifyfor direct batch processing. - Use a higher-level language: Shell scripts are convenient but limited for complex workflows and error handling. Consider Python: Pillow handles watermarking, requests calls the object storage API, and a single script manages all steps. Python also makes fine-grained exception handling and logging easier.
- Logging and monitoring: Add detailed logging to troubleshoot issues. Redirect shell script output to log files; log every upload result in the Java tool. If possible, add simple progress tracking (e.g., a prompt every 100 images) or email notifications so you know when the batch completes.
- Further automation and fault tolerance: Design a one-click script or workflow orchestration to reduce manual steps. A master script calls each step in sequence and auto-retries or rolls back on error. Handle edge cases—if one step fails repeatedly, skip and mark it rather than stalling the entire batch on a single file.
- Performance and resource utilization: For enormous image counts, consider system I/O and memory impact. Run during off-peak hours or split into multiple smaller batches to reduce single-run pressure. Object storage uploads could use concurrency or chunked upload for higher throughput.
Lessons Learned
This practical experience batch-processing license photos yielded several general principles:
- Design for failure recovery: In large-scale batches, plan for mid-process failure. Build mechanisms to resume work or roll back—track completed items so re-runs skip them, or implement transactions before and after database operations to prevent partial success / partial failure inconsistency.
- Small-scale rehearsal first: Before processing huge datasets, test end-to-end with a small subset. Dry runs catch script errors, bad parameters, or environment issues before large-scale failure.
- Automate over manual work: Use scripts wherever possible. One debug cycle, many runs. Batch scripts should be generic and reusable, with sufficient logging so you can reconstruct what happened even if no one was watching.
- Prioritize data security: License photos are sensitive. Use SSH-based methods like SCP or
rsyncfor safe transfer, and ensure object storage access permissions are correct before going live to prevent unintended leaks. Watermarking itself is also a protective measure, deterring unauthorized file use. - Cross-team coordination: Batch tasks often span operations, development, and other groups. Deployment, upload permissions, database schema—all need discussion beforehand. Confirm details (storage paths, naming conventions, data formats) to avoid rework.
Through these steps and lessons, the batch task succeeded: all store license photos received watermarks, uploaded to object storage, and the database correctly indexed each image location. This hands-on experience not only achieved the goal but also built a foundation for tackling similar batch work in the future.