文章只提供了部分代码,每一天对应着分支工具类等完整代码都可以去github上获取,地址:https://github.com/kkoneone11/cloud-photo/
生成缩略图功能
流程图
预览功能
文件上传功能
流程解释(粗体的为大致要实现的接口):首先加载图片列表会先需要请求获得缩略图,服务器收到该信息之后先判断有没有该缩略图,如果
- 没有的话则需要通过请求照片的信息去资源池下载原图,再根据原图生成生成缩略图的地址,然后下载下来再显示缩略图。最后将缩略图上传到资源池,再将缩略图进行入库
- 是存在缩略图的话则直接获得缩略图地址然后进行返回结果
数据库
图片缩略图表:tb_file_resize_icon 图片格式分析表:tb_media_info
都是通过存储id(StorageObjectId)去对应,所以通过存储id去映射着两个表。且StorageObjectId对应着一张原图、一张200x200的缩略图和一张600x600的缩略图
分析:
开发过程:
1.基础配置
1.1创建Image子项目,配置配置类,改端口为9007,改application的name
1.2pom中引入common项目,导入以下pom坐标
<dependencies> <dependency> <groupId>org.example</groupId> <artifactId>cloud-photo-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.drewnoakes</groupId> <artifactId>metadata-extractor</artifactId> <version>2.18.0</version> </dependency> </dependencies>
1.3启动类
- @ComponentScan注解用于指定要扫描的包或类的位置。它会自动扫描指定包及其子包下的所有类,并将其注册为Spring Bean。这个注解通常用于配置类上,以便Spring Boot可以自动扫描并注册所有需要的组件
1.4在common类的utils导入工具类
- 要先下载原图,所以有一个下载工具类S3Util 里面也要记得修改到自己具体的minio的配置
- 获得图片宽高等工具
- 生成缩略图后上传到存储池上传工具
- vips工具生成缩略图 记得要修改里面的下载好的工具类的地址
1.5导入代码生成工具类,根据要操作的数据库"tb_file_resize_icon","tb_media_info"
生成对应的mvc,实体类里的主键记得添加上@TableId(type = IdType.ASSIGN_UUID)
图片预览功能流程图:
2.接口开发
2.1查看缩略图信息getFileResizeIcon()
2.1.1参数分析:首先肯定需要先查询是否存储在数据库里(storageObjectId )和哪一种缩略图(iconCode)。返回值为FileResizeIcon,返回的是缩略图的信息
2.1.2这个方法只需要在service里实现,是getIconUrl()会调用到。需要通过storageObjectId和iconCode在file_resize_icon表里进行唯一查询
getOne方法的时候注意后面要加个false,不然存入多个相同的照片的时候会出错
2.2下载原图downloadImage()
2.2.1参数分析:String containerId , String objectId , String suffixName。返回原图的下载地址
2.2.2这个方法需要找到资源池里的原图然后获取其下载地址并下载到源文件目录里。填写地址的时候记得后面还有一个斜杠。
2.2.3是通过trans里的方法进行下载原图,而项目直接的方法调用就得用到fegin,因此在common类里面我们写一个Cloud2TransService,用来作为中间对象方便调用Trans里的方法。通过fegin调用trans服务,fegin类放在common包里方便管理。同时记得在启动类上加上
@EnableFeignClients(basePackages = {"com.cloud.photo.common.fegin"})扫描到对应包
package com.cloud.photo.common.fegin; import com.cloud.photo.common.bo.AlbumPageBo; import com.cloud.photo.common.bo.FileUploadBo; import com.cloud.photo.common.bo.StorageObjectBo; import com.cloud.photo.common.bo.UserFileBo; import com.cloud.photo.common.common.ResultBody; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; /** * @Author:kkoneone11 * @name:Image2TransService * @Date:2023/7/15 13:20 */ @Service @FeignClient("cloud-photo-trans") public interface Cloud2TransService { /** * 获得图片下载地址 * @param containerId * @param objectId * @return */ @GetMapping("/trans/getDownloadUrl") ResultBody getDownloadUrl(@RequestParam(value = "containerId") String containerId, @RequestParam(value = "objectId") String objectId); /** * 上传文件 * @param userId * @param fileName * @param fileMd5 * @param fileSize * @return */ @GetMapping("/trans/getPutUploadUrl") ResultBody getPutUploadUrl(@RequestParam(value = "userId",required = false) String userId, @RequestParam(value = "fileName") String fileName, @RequestParam(value = "fileMd5") String fileMd5, @RequestParam(value = "fileSize") Long fileSize); /** * 更新文件审核状态 * @param userFileList * @return */ @RequestMapping("/trans/updateUserFile") Boolean updateUserFile(@RequestBody List<UserFileBo> userFileList); /** * 提交上传 * @param bo * @return */ @RequestMapping("/trans/commit") ResultBody commit(@RequestBody FileUploadBo bo); @RequestMapping("/trans/userFilelist") ResultBody userFilelist(@RequestBody AlbumPageBo pageBo); @RequestMapping("/trans/getUserFileById") UserFileBo getUserFileById(@RequestParam("fileId") String fileId); @RequestMapping("/trans/getStorageObjectById") StorageObjectBo getStorageObjectById(@RequestParam("storageObjectId") String storageObjectId); }
2.2.4而在trans项目里的DownloadController我们应该再写一个含containerId,objectId两个参数的getDownloadUrl()方法。然后在service里直接调用S3工具里的getDownloadUrl()方法即可。
/** *从资源池下载原图 * @param containerId * @param objectId * @param suffixName 文件后缀名 * @return */ @Override public String downloadImage(String containerId, String objectId, String suffixName) { //将文件下载到该地址 String srcFileDirName = "D:/cloudImage/"; //获取原图的下载地址 ResultBody url = cloud2TransService.getDownloadUrl(containerId, objectId); //组装文件名 String srcFileName = srcFileDirName + UUID.randomUUID().toString() + "." + suffixName; //根据文件路径创建一个file类 File dir = new File(srcFileDirName); //判断该路径下的文件存不存在,不存在则创建 if(!dir.exists()){ dir.mkdir(); } //将文件下载到该文件路径 Boolean result = DownloadFileUtil.downloadFile(url.getData().toString(), srcFileName); if(!result){ return null; } //返回文件名 return srcFileName; }
2.3.原图生成缩略图并入库imageThumbnailSave()
2.3.1参数分析:根据原图然后生成缩略图(suffixName,srcFileName)、图相关属性因为会有200和600的尺寸(iconCode)、获得上传地址(fileName)、唯一识别缩略图连接两个表且需要当做信息存入到图片缩略图表(storageObjectId)
Vips工具生成缩略图的方法参数
2.3.2要先找到原图位置,然后构造一下缩略图的源文件名,将文件的信息分析出来,然后再用vips生成缩略图,然后将调用uploadIcon()方法缩略图上传到(存储池)资源池。
2.4上传缩略图uploadIcon()
2.4.1参数分析:要入库到缩略图的数据库里,要有的参数有userId,storageObjectId, ,fileName,
同时还需要调用getPutUploadUrl()获得上传缩略图的地址,所以需要fileName,useId
然后通过调用UploadFileUtil里的文件上传方法将文件上传到地址所以需要iconFile
2.4.2通过fegin调用传输服务的上传地址方法去上传图片。看源码就知道不需要md5,传null也能生成一个地址。而缩略图不需要上传提交,因为提交是上传到文件列表,而缩略图不需要上传到文件列表 ,直接保存入库即可。如果报错返回空即可
** * 上传缩略图并入库 * @param userId * @param storageObjectId * @param iconCode * @param iconFile * @param fileName * @return */ @Override public FileResizeIcon uploadIcon(String userId,String storageObjectId ,String iconCode, File iconFile,String fileName){ //获得上传地址 ResultBody putUploadUrl = cloud2TransService.getPutUploadUrl(userId, fileName, null, null); //转化成JSON格式并把参数提取出来用作上传到资源池和入库 JSONObject jsonObject = JSONObject.parseObject(putUploadUrl.getData().toString()); String objectId = jsonObject.getString("objectId"); String uploadUrl = jsonObject.getString("url"); String containerId= jsonObject.getString("containerId"); //上传到资源池 UploadFileUtil.uploadSinglePart(iconFile,uploadUrl); //入库 file_resize_iocn表 FileResizeIcon fileResizeIcon = new FileResizeIcon(storageObjectId,iconCode,containerId,objectId); iFileResizeIconService.save(fileResizeIcon); return fileResizeIcon; }
FileResizeIcon()的时候记得要创建一个无参的构造函数,因为有了一个有参的构造函数
2.5. 接受消息处理缩略图imageThumbnailAndMediaInfo()
2.5.1参数分析:String storageObjectId, String fileName
2.5.2因为已经之前上传完原图已经上传了一条消息到kfaka,所以现在要写一个类去消费kafka的消息。并且生成 200_200、600_600尺寸缩略图 再分析图片格式 宽高等信息并信息入库。在image下创建一个consumer包,然后创建一个ImageConsumer类,记得在类上备注@Component,然后监听消费file_image_topic的信息
@Component public class ImageConsumer { @Autowired IFileResizeIconService iFileResizeIconService; // 消费监听 @KafkaListener(topics = {"file_image_topic"}) public void onMessage1(ConsumerRecord<String, Object> record){ // 消费的哪个topic、partition的消息,打印出消息内容 System.out.println("消费:"+record.topic()+"-"+record.partition()+"-"+record.value()); Object value = record.value(); JSONObject jsonObject = JSONObject.parseObject(value.toString()); String userFileId = jsonObject.getString("userFileId"); String storageObjectId = jsonObject.getString("storageObjectId"); String fileName = jsonObject.getString("fileName"); iFileResizeIconService.imageThumbnailAndMediaInfo(storageObjectId,fileName); } }
2.5.3先用getFileResizeIcon()方法看一下缩略图是否已经生成,都已经生成且存在数据库里了则不用再生成,否则通过downloadImage()方法下载原图然后再分别生成两张缩略图
再在image创建一个consumer的包,包里创建一个类,并在类名上注释@Component,把kafka里的消息读出来然后进行处理,处理的内容主要是生成缩略图,一张200x200,一张600x600的图和然后处理格式分析。先到数据库查是否有对应的尺寸,分别查,然后再查是否有格式分析属性,如果没生成图片和没有格式分析再处理。
/** * 调用imageThumbnailSave方法分别生成200和600的缩略图并将缩略图信息入库medi_info表 * @param storageObjectId * @param fileName */ @Override public void imageThumbnailAndMediaInfo(String storageObjectId, String fileName){ String iconCode200 = "200_200"; String iconCode600 = "600_600"; //查询一下200和600的缩略图是否存在 FileResizeIcon fileResizeIcon200 = iFileResizeIconService.getFileResizeIcon(storageObjectId, iconCode200); FileResizeIcon fileResizeIcon600 = iFileResizeIconService.getFileResizeIcon(storageObjectId, iconCode600); MediaInfo mediaInfo = iMediaInfoService.getOne(new QueryWrapper<MediaInfo>().eq("storage_Object_Id", storageObjectId) ,false); //如果都不为空则已经存在缩略图 if(fileResizeIcon200!=null&&fileResizeIcon600!=null&&mediaInfo!=null){ return ; } //缩略图存在的话则下载一下 String suffixName = fileName.substring(fileName.lastIndexOf(".")+1,fileName.length()); StorageObjectBo storageObject = cloud2TransService.getStorageObjectById(storageObjectId); String srcFileName = iFileResizeIconService.downloadImage(storageObject.getContainerId(), storageObject.getObjectId(), suffixName); if(StringUtils.isBlank(srcFileName)){ log.error("downloadImage error!"); return; } //生成缩略图并保存入库 if(fileResizeIcon200 == null){ iFileResizeIconService.imageThumbnailSave(iconCode200,suffixName,srcFileName,storageObjectId,fileName); } if(fileResizeIcon600 == null){ iFileResizeIconService.imageThumbnailSave(iconCode600,suffixName,srcFileName,storageObjectId,fileName); } //使用分析图片并入mediaInfo库 MediaInfo newmediaInfo = PicUtils.analyzePicture(new File(srcFileName)); //组装一下mediaInfo newmediaInfo.setStorageObjectId(storageObjectId); if(StringUtils.isBlank(newmediaInfo.getShootingTime())){ newmediaInfo.setShootingTime(DateUtil.now()); } iMediaInfoService.save(newmediaInfo); }