一、现有的问题
- 现在图库和资料库直接使用腾讯云的图片压缩,这样不安全:
- 暴露用户信息
- 可以直接查看原图
?imageMogr2/crop/152x152/gravity/center/format/png/interlace/1/quality/17
- 对于tif文件的显示问题,采取的是在前端处理,导致每个页面都需要加载tif.min.js很麻烦
- 那么如果后端处理,怎么优雅一些?
- 对于头像、回复、帖子这类图片,采取前端压缩的方式。
二、流程
- 在上传图库或者资料库中,如果含有图片资源,则只在数据库中添加origin_url字段
- 向消息队列中传入process_image,width,url,table_name,id
- 由消费者(python),去补全thumb_url,以及tif的转化
三、最终效果
- 缩略图:
- 原图
四、具体代码
- 根据url下载图片
import shutil
import time
import requests
# 获取后缀名
def getSuffix(url):
return url.split(".")[-1]
# 获取整形时间戳
def getTimestamp():
return str(int(time.time()))
# 获取随机文件名
def getRandomFileName(path):
fileName = '{}.{}'.format(getTimestamp(),getSuffix(url))
return fileName
# 下载图片
def downloadImage(url):
response = requests.get(url, stream=True)
with open(getRandomFileName(url), 'wb') as out_file:
shutil.copyfileobj(response.raw, out_file)
del response
- 图像压缩处理 image-process-core.py
from PIL import Image
def compressImage(width,path):
im = Image.open(path)
(x, y) = im.size # 读取图片尺寸(像素)
height = int(y * width / x) # 计算缩小后的高度
out = im.resize((width, height), Image.Resampling.LANCZOS) # 改变尺寸,保持图片高品质
if out.mode=='RGBA':
#转化为rgb格式
out=out.convert('RGB')
fileSuffix = path.split(".")[-1]
fileName = '{}.{}'.format(getTimestamp(),fileSuffix)
out.save(fileName)
- tif转png
def tif2jpg(filePathName):
if filePathName[-3:] == "tif" or filePathName[-3:] == "bmp" or filePathName[-4:] == "tiff":
outfile = filePathName[:-3] + "jpeg"
im = Image.open(filePathName)
out = im.convert("RGB")
out.save(outfile, "JPEG", quality=100)
- 将转好的文件,上传cdn
# yyyymmdd
def getTimePrefix():
return time.strftime("%Y%m%d",time.localtime())
# return: transform/{yyyymmdd}/{业务名}/{类型}/{时间戳}.{后缀名}
# imageType: full_size,compress,thumb
# bussinessType: avatar,bbs-file,doc-file,picture-file
def genTencentCOSKey(url,imageType,bussinessType):
return "transform/{}/{}/{}/{}.{}".format(getTimePrefix(),bussinessType,imageType,getTimestamp(),getSuffix(url))
def upload2TencentCos(fileName):
response = client.upload_file(
Bucket='metal-1254798469',
Key=genTencentCOSKey(fileName,"thumb","avatar"),
LocalFilePath=fileName,
EnableMD5=False,
progress_callback=None
)
- 更新数据库
import mysql.connector
import json
from mysql.connector.pooling import MySQLConnectionPool
# 主表
DB_BASE_NAME = "metal"
DB_URL = ""
DB_PASSWORD = ""
baseMysqlPool = MySQLConnectionPool(
host = DB_URL,
user ="root",
port = 59958,
passwd = DB_PASSWORD,
database = DB_BASE_NAME,
charset ='utf8',
autocommit = True,
pool_size = 2,
auth_plugin = 'mysql_native_password'
)
def updatePictureAppendix(id,fullSizeUrl,fullSizeFileSize,thumbUrl,thumbSizeFileSize,compressUrl,compressSizeFileSize):
conn = baseMysqlPool.get_connection()
cursor = conn.cursor()
sql = "UPDATE pic_appendix SET full_size_url='%s',full_size='%s',thumb_url='%s',thumb_size='%s',compress_url='%s',compress_size='%s',has_process=1 WHERE appendix_id= '%s';"%(fullSizeUrl,fullSizeFileSize,thumbUrl,thumbSizeFileSize,compressUrl,compressSizeFileSize,id)
print(sql)
cursor.execute(sql)
conn.commit()
cursor.close()
conn.close()
- 消息队列消费者
import pika
import json
from core import transformImage
HOST = ""
EXCHANGE_NAME = "PIC_PROCESS_DIRECT_EXCHANGE"
QUEUE_NAME = "py_81"
credentials = pika.PlainCredentials('', '')
connection = pika.BlockingConnection(pika.ConnectionParameters(host=HOST,port=5672,virtual_host='/metal',credentials=credentials))
channel = connection.channel()
#
# Integer width;
# String tableName;
# Integer appendixID;
# String url;
#
def callback(ch, method, properties, body):
# ⼿动发送确认消息
ch.basic_ack(delivery_tag=method.delivery_tag)
print(body)
msg = body.decode()
dto = json.loads(msg)
transformImage(dto['appendixID'],dto['width'],dto['tableName'],dto['url'])
channel.exchange_declare(exchange=EXCHANGE_NAME, exchange_type='direct',durable=True)
result = channel.queue_declare(queue=QUEUE_NAME, exclusive=False)
channel.queue_bind(exchange=EXCHANGE_NAME, queue=QUEUE_NAME,routing_key="")
#channel.basicQos(10);
channel.basic_qos(prefetch_count=1)
channel.basic_consume(queue=QUEUE_NAME,on_message_callback=callback,auto_ack=False)
channel.start_consuming()
- 完整代码
# 做两件事
# 1. 图片压缩,生成缩略图
# 152x152
'''
实现图片压缩
1.保持图片大小比例不变
2.使用Image里面的resize进行
'''
# -*- coding=utf-8
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
from qcloud_cos.cos_exception import CosClientError, CosServiceError
import sys
import logging
import shutil
import time
from PIL import Image
import os
import requests
from db import updatePictureAppendix
# 正常情况日志级别使用INFO,需要定位时可以修改为DEBUG,此时SDK会打印和服务端的通信信息
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
secret_id = 'SECRETID'
secret_key = 'SECRETKEY'
region = 'ap-beijing'
token = None
scheme = 'https'
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme)
client = CosS3Client(config)
def getSuffix(url):
return url.split(".")[-1]
def getTimestamp():
return str(time.time())
def getRandomFileName(url):
fileName = '{}.{}'.format(getTimestamp(),getSuffix(url))
return fileName
def downloadImage(url):
response = requests.get(url, stream=True)
fileName = getRandomFileName(url)
with open(fileName, 'wb') as out_file:
shutil.copyfileobj(response.raw, out_file)
del response
return fileName
def compressImage(width,path):
im = Image.open(path)
(x, y) = im.size # 读取图片尺寸(像素)
height = int(y * width / x) # 计算缩小后的高度
out = im.resize((width, height), Image.Resampling.LANCZOS) # 改变尺寸,保持图片高品质
if out.mode=='RGBA':
#转化为rgb格式
out=out.convert('RGB')
fileSuffix = path.split(".")[-1]
fileName = '{}.{}'.format(getTimestamp(),fileSuffix)
out.save(fileName)
return fileName
# yyyymmdd
def getTimePrefix():
return time.strftime("%Y%m%d",time.localtime())
# return: transform/{yyyymmdd}/{业务名}/{类型}/{时间戳}.{后缀名}
# imageType: full_size,compress,thumb
# bussinessType: avatar,bbs-file,doc-file,picture-file
def genTencentCOSKey(url,imageType,bussinessType):
return "transform/{}/{}/{}/{}.{}".format(getTimePrefix(),bussinessType,imageType,getTimestamp(),getSuffix(url))
def upload2TencentCosAndDeleteLocalFile(fileName,imageType,bussinessType):
key = genTencentCOSKey(fileName,imageType,bussinessType)
response = client.upload_file(
Bucket='metal-1254798469',
Key=key,
LocalFilePath=fileName,
EnableMD5=False,
progress_callback=None
)
# 删除本地文件
os.remove(fileName)
return "https://cdn.jinxiangshijie.com/"+key
def updateDB(id,tableName,fullSizeUrl,fullSizeFileSize,thumbUrl,thumbSizeFileSize,compressUrl,compressSizeFileSize):
pass
def isImageFile(url):
return True
def tif2jpg(filePathName):
if filePathName[-3:] == "tif" or filePathName[-3:] == "bmp" or filePathName[-4:] == "tiff":
outfile = filePathName[:-3] + "jpeg"
im = Image.open(filePathName)
out = im.convert("RGB")
out.save(outfile, "JPEG", quality=100)
return outfile
else:
return filePathName
def getFileSize(filePath):
return os.path.getsize(filePath)
# 根据fullSizeUrl,进行下载、转换、压缩操作
def transformImage(id,width,tableName,fullSizeUrl):
if(tableName!='pic_appendix' and tableName!='document_appendix'):
return
if(tableName=='pic_appendix'):
bussinessType = 'picture-file'
else:
bussinessType = 'doc-file'
# 不做参数校验了
# todo: 根据后缀名,先判断是不是图片
if(not isImageFile(fullSizeUrl)):
return
localFileName = downloadImage(fullSizeUrl)
# 判断是不是tif,如果是,则需要转化
localFileName = tif2jpg(localFileName)
# 生成预览图
thumbImageName = compressImage(width,localFileName)
# 生成压缩图
compressImageName = compressImage(300,localFileName)
# 获取大小
fullSizeFileSize = getFileSize(localFileName)
thumbSizeFileSize = getFileSize(thumbImageName)
compressSizeFileSize = getFileSize(compressImageName)
# 上传三种图片,并删除本地文件
fullSizeCdnUrl = upload2TencentCosAndDeleteLocalFile(localFileName,"full_size",bussinessType)
thumbSizeCdnUrl = upload2TencentCosAndDeleteLocalFile(thumbImageName,"thumb",bussinessType)
compressSizeCdnUrl = upload2TencentCosAndDeleteLocalFile(compressImageName,"compress",bussinessType)
updatePictureAppendix(id,fullSizeCdnUrl,fullSizeFileSize,thumbSizeCdnUrl,thumbSizeFileSize,compressSizeCdnUrl,compressSizeFileSize)
# todo: 修改数据库
print(id,width,tableName,fullSizeCdnUrl)
# todo: 删除本地文件