基于微服务的云相册项目

}


 在需要用到该接口时注入feign服务,调用即可



package com.cloud.photo.api.service.impl;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.cloud.photo.api.feign.UserFeignService;
import com.cloud.photo.api.service.LoginService;
import com.cloud.photo.common.bo.UserBo;
import com.cloud.photo.common.common.CommonEnum;
import com.cloud.photo.common.common.ResultBody;
import com.cloud.photo.common.constant.CommonConstant;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.regex.Pattern;

@Service
public class LoginServiceImpl implements LoginService {

@Resource
private UserFeignService userFeignService;


@Override
//获得用户信息,先从缓存获取,没有则创建缓存
public ResultBody getUserInfo() {
    if (StpUtil.isLogin()) {
        //用户登录ID
        String loginId = StpUtil.getLoginIdAsString();
        //如果session里面有信息优先从里面拿
        SaSession session = StpUtil.getSessionByLoginId(loginId);
        if (session != null) {
            String userInfo = session.getModel(CommonConstant.USER_INFO, String.class);
            if (userInfo != null) {
                return ResultBody.success(JSON.parseObject(userInfo, UserBo.class));
            }
        }
        //否则访问user服务获取用户信息
        ResultBody userInfo = userFeignService.getUserInfo(loginId);
        if (userInfo.getCode().equals(CommonEnum.SUCCESS.getResultCode())){
            //成功拿到用户信息就存缓存里面,true是有的话就存,没有的话就新建
            SaSession saSession = StpUtil.getSessionByLoginId(loginId, true);
            saSession.set(CommonConstant.USER_INFO, JSON.toJSONString(userInfo.getData()));
        }
        return userInfo;
    }
    //未登录
    return ResultBody.error(CommonEnum.NO_LOGIN);
}

}



### (4)图片上传


此部分未项目重点,请做好笔记!!以下我将介绍整个图片上传流程


**使用步骤**


在父工程导入依赖,部分依赖上面已经导入,这里仅展示未导入的



    <!--        kafka-->
    <dependency>
        <groupId>org.springframework.kafka</groupId>
        <artifactId>spring-kafka</artifactId>
    </dependency>

    <!--        hutool工具-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>4.1.1</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.51</version>
    </dependency>

    <!--        上传图片需要用到-->
    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-core</artifactId>
        <version>1.12.487</version>
    </dependency>

    <dependency>
        <groupId>com.amazonaws</groupId>
        <artifactId>aws-java-sdk-s3</artifactId>
        <version>1.12.487</version>
    </dependency>

图片上传流程分为两步:获取上传地址和提交上传记录


#### 获取上传地址


1.这是获取上传地址接口,这里把用户id放入FileUploadBo就进入putuploadService业务层代码。前端传过来FileUploadBo 包括category图片分类,fileName文件名,fileSize图片大小,fileMd5图片的md5值。



@Autowired
PutuploadService putuploadService;


/**
 * 获取上传地址
 *
 * @return 每一张图片一个上传地址
 */
@SaCheckLogin
@PostMapping("getPutUploadUrl")
public ResultBody getPutUploadUrl(@RequestBody FileUploadBo bo) {

    //从缓存拿到用户信息
    UserBo userInfo = MyUtils.getUserInfo();
    if (userInfo == null) {
        return ResultBody.error(CommonEnum.USER_IS_NULL);
    }
    //用户ID
    String userId = userInfo.getUserId();
    //判断下上传的文件是否为空
    if (bo == null) {
        return ResultBody.error(CommonEnum.FILE_LIST_IS_NULL);
    }
    //拼接用户ID
    bo.setUserId(userId);

    // 业务实现
    return putuploadService.getPutUploadUrl(bo);
}

2.我们先看trans服务里的代码,因为putuploadService需要远程调用它。首先根据图片的md5值来查询是否有相同的图片已经上传过,这样的话就可以节省资源。如果图片已存在则称为秒传。如果图片存在的话我们直接返回storageObjectId(图片的存储id,这是数据库的主键)即可,因为图片已经上传过了,已经不需要拿他的上传地址了。如果图片不存在则需要进行图片上传,调用S3Util来进行上传,然后返回S3Util生成的返回值,重点是图片上传地址。


\*\*\*注意,秒传和非秒传返回值是不同的\*\*\*



 @Autowired
IFileMd5Service iFileService;
    
public String getPutUploadUrl(String fileName, String fileMd5, Long fileSize) {
    //文件已存在 进行秒传 直接将存储id返回
    FileMd5 fileMd5Entity = iFileService.getOne(new QueryWrapper<FileMd5>().eq("md5", fileMd5));
    if (fileMd5Entity != null) {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("storageObjectId", fileMd5Entity.getStorageObjectId());
        return jsonObject.toJSONString();
    }

    //文件不存在
    String suffixName = "";
    if (StringUtils.isNotBlank(fileName)) {//检查fileName为不为空
        suffixName = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
    }
    return S3Util.getPutUploadUrl(suffixName, fileMd5);//上传图片,返回上传信息
}

3.然后我们看看S3Util的代码。主要就是把图片上传到Minio,也可以选择服务商的桶服务存储,原理是差不多的,我选择的是Minio。原理就是先建立连接然后将文件的md5转换为base64上传到Minio存储,然后返回上传信息。这里的代码会用即可。



package com.cloud.photo.common.util;

import cn.hutool.core.codec.Base64;
import com.alibaba.fastjson.JSONObject;
import com.amazonaws.HttpMethod;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import io.micrometer.core.instrument.util.StringUtils;
import org.apache.tomcat.util.buf.HexUtils;

import java.io.;
import java.net.URL;
import java.net.URLEncoder;
import java.util.
;

public class S3Util {

private static String accessKey = "minioadmin";
private static String secretKey = "minioadmin";
private static String bucketName = "cloud-photo";
private static String serviceEndpoint = "http://127.0.0.1:9000";


private static String containerId = "10001";

/**
 * 获取上传地址
 * @param suffixName
 * @param fileMd5
 * @return
 */
public static String getPutUploadUrl(String suffixName,String fileMd5) {

    //链接过期时间
    Date expiration = new Date();
    long expTimeMillis = expiration.getTime();
    expTimeMillis += 1000 * 60 * 10;
    expiration.setTime(expTimeMillis);
    String objectId = UUID.randomUUID().toString().replaceAll("-","");
    String base64Md5 = "";
    if(StringUtils.isNotBlank(suffixName)){
        objectId = objectId +"." + suffixName;
    }
    //建立S3客户端,获取上传地址
    AmazonS3 s3Client = getAmazonS3Client();
    GeneratePresignedUrlRequest generatePresignedUrlRequest =
            new GeneratePresignedUrlRequest(bucketName, objectId)
                    .withMethod(HttpMethod.PUT)
                    .withExpiration(expiration);
    if(StringUtils.isNotBlank(fileMd5)){
        base64Md5= Base64.encode(HexUtils.fromHexString(fileMd5));
        generatePresignedUrlRequest = generatePresignedUrlRequest.withContentMd5(base64Md5);
    }
    URL url = s3Client.generatePresignedUrl(generatePresignedUrlRequest);

    JSONObject jsonObject =new JSONObject();
    jsonObject.put("objectId",objectId);
    jsonObject.put("url",url);
    jsonObject.put("containerId",containerId);
    jsonObject.put("base64Md5",base64Md5);
    return jsonObject.toJSONString();
}

}


4.这是第1中调用的putuploadService,首先需要通过Feign远程调用trans服务,2.3中已经介绍了,返回值可能有两个信息storageObjectId(秒传)和图片上传信息(非秒传)。然后根据是否为秒传设置fileUploadBo的属性, fileUploadBo的传输状态设置为传送中,然后存入redis中,后续可以查看传输进度。



@Autowired
private CloudPhotoTransService cloudPhotoTransService;

@Resource
private StringRedisTemplate stringRedisTemplate;

@Override
public ResultBody getPutUploadUrl(FileUploadBo fileUploadBo) {

    ResultBody resultBody = cloudPhotoTransService.getPutUploadUrl(fileUploadBo.getUserId(),
            fileUploadBo.getFileSize(),
            fileUploadBo.getFileMd5(),
            fileUploadBo.getFileName());

    //存进缓存的Key
    String key = fileUploadBo.getUserId() + ":" + fileUploadBo.getFileMd5();

    if (resultBody.getCode().equals(CommonEnum.SUCCESS.getResultCode())) {
        //成功
        JSONObject obj = JSONUtil.parseObj(resultBody.getData());
        if (obj.containsKey("storageObjectId")) {
            //是秒传
            fileUploadBo.setStorageObjectId(obj.getStr("storageObjectId"));
        } else {
            //不是秒传
            fileUploadBo.setContainerId(obj.getStr("containerId"));
            fileUploadBo.setObjectId(obj.getStr("objectId"));
            fileUploadBo.setUploadUrl(obj.getStr("url"));
            fileUploadBo.setBase64Md5(obj.getStr("base64Md5"));
        }

        //成功了状态是传输中
        fileUploadBo.setStatus(CommonConstant.FILE_UPLOAD_ING);
        fileUploadBo.setUploadTime(DateUtil.now());
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(fileUploadBo), 1, TimeUnit.DAYS);
    } else {
        //失败了状态是传输失败
        fileUploadBo.setStatus(CommonConstant.FILE_UPLOAD_FAIL);
        fileUploadBo.setUploadTime(DateUtil.now());
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(fileUploadBo), 1, TimeUnit.DAYS);
    }

    return ResultBody.success(fileUploadBo);
}

获取上传地址这部分流程就结束了


#### 提交上传记录


因为上一步我们只是将图片上传了,但相应记录还并未在数据库进行保存,所以这部分就是来解决这个问题的。


1.这是提交记录的接口,接受上一步中的FileUploadBo ,调用业务层commitUpload代码



/**
 * 提交
 *
 * @return 结果
 */
@SaCheckLogin
@PostMapping("commitUpload")
//将图片入数据库
public ResultBody commitUpload(@RequestBody FileUploadBo bo) {
    //从缓存拿到用户信息
    UserBo userInfo = MyUtils.getUserInfo();
    if (userInfo == null) {
        return ResultBody.error(CommonEnum.USER_IS_NULL);
    }
    //用户ID
    String userId = userInfo.getUserId();
    //判断下上传的文件是否为空
    if (ObjectUtil.isEmpty(bo)) {
        return ResultBody.error(CommonEnum.FILE_LIST_IS_NULL);
    }
    //拼接用户ID
    bo.setUserId(userId);

    // 业务实现
    return putuploadService.commitUpload(bo);
}

2.这是commitUpload代码,远程调用trans服务commit接口,然后将传输状态设置一下返回。接下来重点看trans服务commit接口实现了什么。



@Override
public ResultBody commitUpload(FileUploadBo bo) {

    //存进缓存的Key
    String key = bo.getUserId() + ":" + bo.getFileMd5();

    ResultBody commitResultBody = cloudPhotoTransService.commit(bo);

    if (commitResultBody.getCode().equals(CommonEnum.SUCCESS.getResultCode())) {
        //成功提交 - 状态更新为传输成功
        bo.setStatus(CommonConstant.FILE_UPLOAD_SUCCESS);
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(bo), 1, TimeUnit.DAYS);
    } else {
        //提及失败 - 状态更新为传输失败
        bo.setStatus(CommonConstant.FILE_UPLOAD_FAIL);
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(bo), 1, TimeUnit.DAYS);
    }

    return commitResultBody;
}

3.这是trans服务commit接口,判断是否为秒传来决定调用那个方法,因为上面介绍过了秒传的返回值FileUploadBo是有StorageObjectId的,非秒传是没有的,所以根据这点很容易判断出是否为秒传。



@RequestMapping(“/commit”)
public ResultBody commit(HttpServletRequest request, HttpServletResponse response,
@RequestBody FileUploadBo bo) {
//打印请求日志
String requestId = RequestUtil.getRequestId(request);
RequestUtil.printQequestInfo(request);

    //返回值
    CommonEnum result;

    //判断文件是否秒传,如果有StorageObjectId则说明是秒传
    if (StringUtils.isBlank(bo.getStorageObjectId())) {
        //处理非秒传
        result = putuploadService.commit(bo);
    } else {
        result = putuploadService.commitTransSecond(bo);
    }

    log.info("getPutUploadUrl() userId =" + bo.getUserId() + ",result=" + result);
    if (StringUtils.equals(result.getResultMsg(), CommonEnum.SUCCESS.getResultMsg())) {
        return ResultBody.success(CommonEnum.SUCCESS.getResultMsg(), requestId);
    } else {
        return ResultBody.error(result.getResultCode(), result.getResultMsg(), requestId);
    }

}

4.先看是非秒传调用的commit方法,这里的代码并不复杂,就是将各种信息存储到数据库里面。



/**
 * 处理非妙传的
 * @param bo
 * @return
 */
public CommonEnum commit(FileUploadBo bo) {
    //获取文件资源池储存信息
    S3ObjectSummary s3ObjectSummary = S3Util.getObjectInfo(bo.getObjectId());//非秒传的必须有ObjectId

    //文件未上传
    if (s3ObjectSummary == null) {
        return CommonEnum.FILE_NOT_UPLOADED;
    }

    //文件上传错误
    if (!bo.getFileSize().equals(s3ObjectSummary.getSize()) || !StringUtils.equalsIgnoreCase(s3ObjectSummary.getETag(), bo.getFileMd5())) {
        return CommonEnum.FILE_UPLOADED_ERROR;
    }

    //文件上传成功 - 文件存储信息入库
    StorageObject storageObject = new StorageObject("minio", bo.getContainerId(), bo.getObjectId(), bo.getFileMd5(), bo.getFileSize());
    iStorageObjectService.save(storageObject);

    //文件md5入库,秒传用
    FileMd5 fileMd5 = new FileMd5(bo.getFileMd5(), bo.getFileSize(), storageObject.getStorageObjectId());
    iFileMd5Service.save(fileMd5);

    //文件入库 - 用户文件列表 - 发送到审核、图片 kafka列表
    bo.setStorageObjectId(storageObject.getStorageObjectId());
    iUserFileService.saveAndFileDeal(bo);

    return CommonEnum.SUCCESS;
}

5.再看commitTransSecond代码,这里只需要保存一到一个表里即可,因为秒传部分数据已经会存在数据库里面了。



/**
 * 处理妙传的
 * @param bo
 * @return
 */
public CommonEnum commitTransSecond(FileUploadBo bo) {

    //检验储存的ID是否正确
    StorageObject storageObject = iStorageObjectService.getById(bo.getStorageObjectId());//秒传的必须有StorageObjectId

    if (storageObject == null) {
        return CommonEnum.FILE_UPLOADED_ERROR;
    }

    //检查秒传文件大小
    if (!storageObject.getObjectSize().equals(bo.getFileSize()) || !StringUtils.equalsIgnoreCase(bo.getFileMd5(), storageObject.getMd5())) {
        return CommonEnum.FILE_UPLOADED_ERROR;
    }

    //保存文件入库 - 用户文件列表 - 发送到审核、图片 kafka列表
    boolean result = iUserFileService.saveAndFileDeal(bo);

    if (result) {
        return CommonEnum.SUCCESS;
    } else {
        return CommonEnum.FILE_UPLOADED_ERROR;
    }
}

6.秒传和秒传都调用了saveAndFileDeal方法,我们看一下它的代码。除了将数据保存到数据库,这里还通过kafka发送了两条消息,这里下面将进行介绍。



@Override
public boolean saveAndFileDeal(FileUploadBo bo) {
    UserFile userFile = new UserFile();
    userFile.setUserId(bo.getUserId());
    userFile.setFileStatus(CommonConstant.FILE_STATUS_NORMA);//文件正常
    userFile.setCreateTime(LocalDateTime.now());
    userFile.setFileName(bo.getFileName());
    userFile.setIsFolder(CommonConstant.FILE_IS_FOLDER_NO);
    userFile.setAuditStatus(CommonConstant.FILE_AUDIT_ACCESS);
    userFile.setFileSize(bo.getFileSize());
    userFile.setModifyTime(LocalDateTime.now());
    userFile.setStorageObjectId(bo.getStorageObjectId());
    userFile.setCategory(bo.getCategory());
    Boolean result = this.save(userFile);

    //文件审核处理
    kafkaTemplate.send(CommonConstant.FILE_AUDIT_TOPIC, JSONObject.toJSONString(userFile));

    //图片处理-格式分析
    kafkaTemplate.send(CommonConstant.FILE_IMAGE_TOPIC, JSONObject.toJSONString(userFile));

    return result;
}


### (5)消息监听


上面最后通过kafka向两个topic(FILE\_AUDIT\_TOPIC和FILE\_ IMAGE\_TOPIC)各发送了一条消息,这里就介绍这里的流程。


#### FILE\_AUDIT\_TOPIC


这部分是有关文件审核处理的


AuditConsumer 用于消费file\_audit\_topic里面的消息,上面往file\_audit\_topic发送消息后,这个类监听到后获取图片的md5值,根据图片md5查询审核表之前是否有审核记录,没有审核记录的话就添加进审核列表,有审核记录并且之前已经通过的不需要再次审核,有审核记录但之前未通过的=就直接设置审核失败。



package com.cloud.photo.audit.consumer;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cloud.photo.audit.entity.FileAudit;
import com.cloud.photo.audit.service.IFileAuditService;
import com.cloud.photo.common.bo.StorageObjectBo;
import com.cloud.photo.common.bo.UserFileBo;
import com.cloud.photo.common.constant.CommonConstant;
import com.cloud.photo.common.feign.CloudPhotoTransService;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Component
public class AuditConsumer {

@Autowired
IFileAuditService iFileAuditService;
@Autowired
CloudPhotoTransService cloudPhotoTransService;

// 消费监听
@KafkaListener(topics = {"file_audit_topic"})
public void onMessage(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 fileName = jsonObject.getString("fileName");
    Integer fileSize = jsonObject.getInteger("fileSize");
    String storageObjectId = jsonObject.getString("storageObjectId");

    StorageObjectBo storageObject = cloudPhotoTransService.getStorageObjectById(storageObjectId);
    String fileMd5=storageObject.getMd5();

    //根据文件MD5查看之前是否有审核过相同的照片   读取审核状态  相同文件只需要审核一次
    FileAudit fileAudit = iFileAuditService.getOne(new QueryWrapper<FileAudit>().eq("md5", fileMd5), false);

    //之前没有审核过此照片   未人工审核  加入审核列表
    if(fileAudit == null){
        //未审核  插入审核列表
        fileAudit=new FileAudit();
        fileAudit.setAuditStatus(0);
        fileAudit.setMd5(fileMd5);
        fileAudit.setUserFileId(userFileId);
        fileAudit.setFileName(fileName);
        fileAudit.setFileSize(fileSize);
        fileAudit.setCreateTime(LocalDateTime.now());
        fileAudit.setStorageObjectId(storageObjectId);
        iFileAuditService.save(fileAudit);

    }else if(fileAudit.getAuditStatus().equals(CommonConstant.FILE_AUDIT_ACCESS)){//1
        //之前已经审核过此照片   文件审核状态默认为已通过审核  无需修改文件状态
    }else if(fileAudit.getAuditStatus().equals(CommonConstant.FILE_AUDIT_FAIL)){//2
        //之前已经审核过此照片   文件审核状态默认为未通过  审核失败  更新文件审核状态
        UserFileBo userFileBo=new UserFileBo();
        userFileBo.setUserFileId(userFileId);
        userFileBo.setAuditStatus(CommonConstant.FILE_AUDIT_FAIL);

        List<UserFileBo> userFileBoList=new ArrayList<>();
        userFileBoList.add(userFileBo);
        cloudPhotoTransService.updateUserFile(userFileBoList);
    }
}

}



#### FILE\_ IMAGE\_TOPIC


这部分是有关图片处理,格式分析的       


ImageConsumer 用于消费file\_image\_topic里面的消息,接收到消息调用iFileResizeIconService里的imageThumbnailAndMediaInfo方法。我们重点看它里面的代码。



package com.cloud.photo.image.consumer;

import com.alibaba.fastjson.JSONObject;
import com.cloud.photo.image.service.IFileResizeIconService;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@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);
}

}


这是iFileResizeIconService代码,主要用于生成图片的缩略图,这里是核心。上面先调用了里面的imageThumbnailAndMediaInfo方法,这个方法首先根据图片存储id来查询是否有200\*200和600\*600尺寸的缩略图,然后再查询这张图片是否已经分析过格式。 若都满足则可以结束,缩略图不存在的话则需要先把原图下载。调用这个类中的downloadImage方法,里面的DownloadFileUtil会先将原图下载在本地定义好的地址。然后就可以调用imageThumbnailSave方法来根据下载下来的原图生成缩略图,里面还包括了上传到Minio和数据库,其中缩略图的生成需要用到VipsUtil这个工具类。最后还要用PicUtils这个工具类来对图片格式分析后保存到数据库。



package com.cloud.photo.image.service.impl;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cloud.photo.common.bo.StorageObjectBo;
import com.cloud.photo.common.bo.UserFileBo;
import com.cloud.photo.common.common.ResultBody;
import com.cloud.photo.common.constant.CommonConstant;
import com.cloud.photo.common.feign.CloudPhotoTransService;
import com.cloud.photo.image.entity.FileResizeIcon;
import com.cloud.photo.image.entity.MediaInfo;
import com.cloud.photo.image.mapper.FileResizeIconMapper;
import com.cloud.photo.image.service.IFileResizeIconService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cloud.photo.image.service.IMediaInfoService;
import com.cloud.photo.image.util.DownloadFileUtil;
import com.cloud.photo.image.util.PicUtils;
import com.cloud.photo.image.util.UploadFileUtil;
import com.cloud.photo.image.util.VipsUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.UUID;

/**

  • 图片缩略图 服务实现类

  • @author wfc

  • @since 2023-07-13
    */
    @Service
    public class FileResizeIconServiceImpl extends ServiceImpl<FileResizeIconMapper, FileResizeIcon> implements IFileResizeIconService {

    @Autowired
    CloudPhotoTransService cloudPhotoTransService;
    @Autowired
    IMediaInfoService iMediaInfoService;

    @Override
    public String getIconUrl(String userId, String fileId, String iconCode) {

     //查询文件信息
     UserFileBo userFile=cloudPhotoTransService.getUserFileById(fileId);
     String storangeObject=userFile.getStorageObjectId();
     String fileName=userFile.getFileName();
     String suffixName=fileName.substring(fileName.lastIndexOf(".")+1,fileName.length());
    
     //获取文件存储信息
     StorageObjectBo storageObjectBo=cloudPhotoTransService.getStorageObjectById(storangeObject);
     //查询缩略图信息
     FileResizeIcon fileResizeIcon=getFileResizeIcon(userFile.getStorageObjectId(),iconCode);
    
     String objectId;
     String containerId;
     //缩略图不存在   生成缩略图
     if(fileResizeIcon==null){
         //获取原图,传递给libvips的原文件的地址
         String srcFileName=downloadImage(storageObjectBo.getContainerId(),storageObjectBo.getObjectId(),suffixName);
         if(StringUtils.isBlank(srcFileName)){
             return null;
         }
         //先生成一张缩略图,通过libvips工具,然后用http请求访问srcFileName,并上传一个文件体
         FileResizeIcon newFileResizeIcon=this.imageThumbnailSave(iconCode,suffixName,srcFileName,storangeObject,fileName);
         if(newFileResizeIcon==null){
             return null;
         }
         objectId= newFileResizeIcon.getObjectId();
         containerId= newFileResizeIcon.getContainerId();
     }else {
         objectId=fileResizeIcon.getObjectId();
         containerId=fileResizeIcon.getContainerId();
     }
    
     //生成缩略图下载地址
     ResultBody iconUrlResponse=cloudPhotoTransService.getDownloadUrl(containerId,objectId);
     return iconUrlResponse.getData().toString();
    

    }

    /**

    • 图片处理 1、生成 200_200、600_600尺寸缩略图 2、分析图片格式 宽高等信息

    • @param storageObjectId

    • @param fileName
      */
      @Override
      public void imageThumbnailAndMediaInfo(String storageObjectId, String fileName) {
      String iconCode200 = “200_200”;
      String iconCode600 = “600_600”;

      //查询尺寸200和尺寸600缩略图 是否存在 - 同一张缩略图无需重复生成
      FileResizeIcon fileResizeIcon200 = getFileResizeIcon(storageObjectId,iconCode200);
      FileResizeIcon fileResizeIcon600 = getFileResizeIcon(storageObjectId,iconCode600);

      //查询图片是否分析属性
      MediaInfo mediaInfo = iMediaInfoService.getOne(new QueryWrapper().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 = cloudPhotoTransService.getStorageObjectById(storageObjectId);
      String srcFileName = downloadImage(storageObject.getContainerId(), storageObject.getObjectId(), suffixName);

      //原图下载失败
      if(StringUtils.isBlank(srcFileName)){
      log.error(“downloadResult error!”);
      return;
      }

      //生成缩略图 保存入库
      if(fileResizeIcon200==null){
      this.imageThumbnailSave(iconCode200,suffixName,srcFileName,storageObjectId,fileName);
      }

      if(fileResizeIcon600==null){
      this.imageThumbnailSave(iconCode600,suffixName,srcFileName,storageObjectId,fileName);
      }

      //图片格式分析&入库
      MediaInfo mediaInfo1=PicUtils.analyzePicture(new File(srcFileName));
      mediaInfo1.setStorageObjectId(storageObjectId);
      if(StringUtils.isBlank(mediaInfo1.getShootingTime())){
      mediaInfo1.setShootingTime(DateUtil.now());
      }
      iMediaInfoService.save(mediaInfo1);
      }

    public FileResizeIcon getFileResizeIcon(String storageObjectId, String iconCode) {

     //1.设置查询Mapper
     QueryWrapper<FileResizeIcon> qw = new QueryWrapper<>();
     //2.组装查询条件
     HashMap<String, Object> param = new HashMap<>();
     param.put("storage_object_id",storageObjectId);
     param.put("icon_code",iconCode);
     qw.allEq(param);
     return this.getOne(qw,false);
    

    }

    private String downloadImage(String containerId, String objectId, String suffixName) {
    //获取下载地址
    String srcFileDirName = “D:\cloudiconphoto\img\”;
    ResultBody baseResponse = cloudPhotoTransService.getDownloadUrl(containerId,objectId);
    String url = baseResponse.getData().toString();

     String srcFileName  =  srcFileDirName + UUID.randomUUID().toString() +"." +suffixName;
     File dir = new File(srcFileDirName);
     if (!dir.exists()) {
         dir.mkdirs();
     }
     Boolean downloadResult = DownloadFileUtil.downloadFile(url, srcFileName);
     if(!downloadResult){
         return null;
     }
     return srcFileName;
    

    }

    //先生成缩略图到本机,再把缩略图上传到minio中
    private FileResizeIcon imageThumbnailSave(String iconCode,String suffixName,String srcFileName,
    String storageObjectId,String fileName) {

     //文件路径
     String srcFileDirName = "D:\\cloudiconphoto\\img\\";
    
     //生成缩略图
     String iconFileName  =  srcFileDirName + UUID.randomUUID().toString()+"." + suffixName;
     int width = Integer.parseInt(iconCode.split("_")[0]);
     int height = Integer.parseInt(iconCode.split("_")[1]);
     VipsUtil.thumbnail(srcFileName,iconFileName,width,height,"70");
    
     //文件为空或者截图失败
     if(StringUtils.isBlank(iconFileName) || !new File(iconFileName).exists()){
         return null;
     }
    
     //上传缩略图 & 入库,生成了一个缩略图的图片,为上传做准备
     FileResizeIcon fileResizeIcon = this.uploadIcon(null,storageObjectId ,iconCode, new File(iconFileName),fileName);
     return fileResizeIcon;
    

    }

    private FileResizeIcon uploadIcon(String userId,String storageObjectId ,String iconCode, File iconFile,String fileName) {
    //上传缩略图
    //获得了上传地址
    ResultBody uploadUrlResponse = cloudPhotoTransService.getPutUploadUrl(userId,null,null,fileName);
    JSONObject jsonObject =JSONObject.parseObject(uploadUrlResponse.getData().toString());
    String objectId = jsonObject.getString(“objectId”);
    String uploadUrl = jsonObject.getString(“url”);
    String containerId= jsonObject.getString(“containerId”);

     //上传文件到存储池
     UploadFileUtil.uploadSinglePart(iconFile,uploadUrl);
    
     //保存入库
     FileResizeIcon newFileResizeIcon = new FileResizeIcon(storageObjectId ,iconCode ,containerId,objectId);
     this.save(newFileResizeIcon);
     return newFileResizeIcon;
    

    }

    public String getAuditFailIconUrl() {

     //查询默认图是否存在存储池  不存在 上传到存储池
     String iconStorageObjectId = CommonConstant.ICON_STORAGE_OBJECT_ID;
     StorageObjectBo iconStorageObject = cloudPhotoTransService.getStorageObjectById(iconStorageObjectId);
     String containerId = "";
     String objectId = "";
     String srcFileName = "";
     if(iconStorageObject == null){
         File file = null;
         try {
             file = ResourceUtils.getFile("classpath:static/auditFail.jpg");
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         }
         FileResizeIcon newFileResizeIcon =  this.uploadIcon(null,iconStorageObjectId ,"200_200", file,"auditFail.jpg");
         containerId = newFileResizeIcon.getContainerId();
         objectId = newFileResizeIcon.getObjectId();
     }else{
         containerId = iconStorageObject.getContainerId();
         objectId = iconStorageObject.getObjectId();
     }
     //生成缩略图下载地址
     ResultBody iconUrlResponse = cloudPhotoTransService.getDownloadUrl(containerId,objectId);
     return iconUrlResponse.getData().toString();
    

    }
    }


DownloadFileUtil代码



package com.cloud.photo.image.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;

import java.io.*;

@Slf4j
public class DownloadFileUtil {

//下载原图
public static Boolean downloadFile(String url, String fileName) {
	Boolean result = false;

	log.info("downloadFile() - fileName=" + fileName + ", url=" + url);

	BufferedOutputStream bos = null;
	FileOutputStream fileOutputStream = null;
	try {
		fileOutputStream = new FileOutputStream(new File(fileName));
		bos = new BufferedOutputStream(fileOutputStream);
	} catch (Exception e) {
		log.warn("downloadFile() - fileName=" + fileName + ", url=" + url + ", FileOutputStream error. ");
		return result;
	}
	HttpClient client = getHttpClient();

最后总结

搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

image

.client.DefaultHttpRequestRetryHandler;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;

import java.io.*;

@Slf4j
public class DownloadFileUtil {

//下载原图
public static Boolean downloadFile(String url, String fileName) {
	Boolean result = false;

	log.info("downloadFile() - fileName=" + fileName + ", url=" + url);

	BufferedOutputStream bos = null;
	FileOutputStream fileOutputStream = null;
	try {
		fileOutputStream = new FileOutputStream(new File(fileName));
		bos = new BufferedOutputStream(fileOutputStream);
	} catch (Exception e) {
		log.warn("downloadFile() - fileName=" + fileName + ", url=" + url + ", FileOutputStream error. ");
		return result;
	}
	HttpClient client = getHttpClient();

最后总结

搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

[外链图片转存中…(img-IEzTmlFi-1714668684269)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值