kongzy
2024-09-14 f0f00e9ba8a755e4317e029d73b69a92ad9f9df1
exam-system/src/main/java/com/gkhy/exam/system/service/impl/SysCommonServiceImpl.java
@@ -1,25 +1,44 @@
package com.gkhy.exam.system.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.gkhy.exam.common.config.FilePathConfig;
import com.gkhy.exam.common.config.MinioConfig;
import com.gkhy.exam.common.domain.entity.SysUser;
import com.gkhy.exam.common.enums.ResourceTypeEnum;
import com.gkhy.exam.common.enums.UserTypeEnum;
import com.gkhy.exam.common.excel.StudentExcelData;
import com.gkhy.exam.common.excel.StudentExcelDataListener;
import com.gkhy.exam.common.exception.ApiException;
import com.gkhy.exam.common.utils.M3u8Utils;
import com.gkhy.exam.common.utils.MinioUtils;
import com.gkhy.exam.common.utils.*;
import com.gkhy.exam.system.domain.ExResource;
import com.gkhy.exam.system.domain.ExStudent;
import com.gkhy.exam.system.domain.SysCompany;
import com.gkhy.exam.system.domain.vo.UploadObjectVO;
import com.gkhy.exam.system.mapper.ExResourceMapper;
import com.gkhy.exam.system.service.ExStudentService;
import com.gkhy.exam.system.service.SysCommonService;
import com.gkhy.exam.system.service.SysCompanyService;
import com.gkhy.exam.system.service.SysUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.time.LocalDateTime;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CountDownLatch;
@@ -27,19 +46,32 @@
@Slf4j
public class SysCommonServiceImpl implements SysCommonService {
    @Autowired
    private MinioConfig minioConfig;
    @Value("${image.upload_image}")
    private String uploadPath;
    @Autowired
    private M3u8Utils m3u8Utils;
    @Autowired
    private MinioUtils minioUtils;
    @Autowired
    private ExResourceMapper resourceMapper;
    @Resource
    private FilePathConfig filePath;
    private FilePathConfig filePathConfig;
    @Autowired
    private SysCompanyService companyService;
    @Autowired
    private ExStudentService studentService;
    @Autowired
    private SysUserService userService;
    // 使用HashSet存储允许的文件格式,提高查找效率
    private final HashSet<String> FORMATSET = new HashSet<>(Arrays.asList(".mp4",".doc", ".docx", ".ppt", ".pptx", ".pdf"));
    @Resource(name = "threadPoolTaskExecutor")
    private ThreadPoolTaskExecutor poolTaskExecutor;
    String projectUrl = System.getProperty("user.dir").replaceAll("\\\\", "/");
    @Override
    public UploadObjectVO uploadFile(MultipartFile file) {
@@ -68,7 +100,16 @@
    @Override
    public String uploadVideo2M3u8(MultipartFile file) throws Exception {
        String path=m3u8Utils.mediaFileToM3u8(file);
        return upload2M3u8(path);
        String name= upload2M3u8(path,null);
        if(StringUtils.isBlank(name)){
            throw new ApiException("文件上传失败");
        }
        return name;
    }
    public String localVideo2M3u8(String path,String mainName) throws Exception {
        String paths=m3u8Utils.localFileToM3u8(path,mainName);
        return upload2M3u8(paths,mainName);
    }
@@ -78,7 +119,7 @@
     * @return 路径
     * @throws Exception
     */
    public String upload2M3u8(String path) throws Exception {
    public synchronized String upload2M3u8(String path,String filePrex) throws Exception {
        //存储转码后文件
        String realPath = path.substring(0, path.lastIndexOf("/"));
        log.info("视频解析后的 realPath {}", realPath);
@@ -89,11 +130,10 @@
        if (null == files || files.length == 0) {
            return null;
        }
        String patch = DateUtil.format(LocalDateTime.now(), "yyyy/MM/") + name.substring(0, name.lastIndexOf(".")) + "/";
        List<File> errorFile = new ArrayList<>();
        long start = System.currentTimeMillis();
//        //替换m3u8文件中的路径
        //替换m3u8文件中的路径
//        FileUtil.replaceTextContent(path, name.substring(0, name.lastIndexOf(".")),
//                aliOssProperties.getMyHostUrl() + filePath.getProxy() + patch +
//                        name.substring(0, name.lastIndexOf(".")));
@@ -101,11 +141,15 @@
        CountDownLatch countDownLatch = new CountDownLatch(files.length);
        Arrays.stream(files).forEach(li -> poolTaskExecutor.execute(() -> {
            try (FileInputStream fileInputStream = new FileInputStream(li)) {
                minioUtils.fileUploader(patch + li.getName(), fileInputStream);
                String objectKey=li.getName();
                if(StringUtils.isNotBlank(filePrex)){
                    objectKey=filePrex+"/"+li.getName();
                }
                minioUtils.fileUploader(objectKey, fileInputStream);
                log.info("文件:{} 正在上传", li.getName());
            } catch (Exception e) {
                errorFile.add(li);
                log.error("文件:{}上传失败",li.getName());
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
@@ -114,42 +158,223 @@
        countDownLatch.await();
        long end = System.currentTimeMillis();
        log.info("解析文件上传成功,共计:{} 个文件,失败:{},共耗时: {}ms", files.length, errorFile.size(), end - start);
        //  try {
        //      minioComponent.mkBucket("m3u8");
        //  } catch (Exception e) {
        //      log.error("创建Bucket失败!");
        //  }
        //异步移除所有文件
        poolTaskExecutor.execute(() -> {
            deleteFile(projectUrl+filePath.getTempPath());
            FileUtil.del(realPath);
            log.warn("临时目录 {}已删除", realPath);
        });
        if (CollectionUtils.isEmpty(errorFile)) {
            return  filePath.getProxy() + patch + name;
            return StringUtils.isNotBlank(filePrex)? filePrex+"/"+name:name;
        }
        return "";
    }
    public static void deleteFile(String path){
        File dest = new File(path);
        if (dest.isFile() && dest.exists()) {
            dest.delete();
    @Override
    public boolean uploadSlice( String fileMd5, String chunkName, MultipartFile file) {
        String systemDir=System.getProperty("user.dir");
        String filePath=systemDir+filePathConfig.getTempPath()+"chunk/"+fileMd5;
        File dirFile=new File(filePath);
        if(!dirFile.exists()){
            dirFile.mkdirs();
        }
        filePath=filePath+File.separator+chunkName;
        try {
            file.transferTo(new File(filePath));
        } catch (FileNotFoundException e) {
            throw new ApiException("找不到文件");
        } catch (IOException e) {
            throw new ApiException("发生错误,请联系管理员");
        }
        return false;
    }
    @Override
    public UploadObjectVO uploadMerge(String fileMd5, String fileName) throws Exception {
        String subfix=fileName.substring(fileName.lastIndexOf("."),fileName.length());
        checkFileFormat(subfix);
        UploadObjectVO uploadObjectVO=mergeFile(fileMd5,fileName);
        String localPath=uploadObjectVO.getPath();
        String minioPath="";
        if(".mp4".equalsIgnoreCase(subfix)){
            String m3u8Path=m3u8Utils.localFileToM3u8(localPath, fileMd5);
            //计算mp4时长
            uploadObjectVO.setResourceLength(VideoUtils.getMp4Duration(m3u8Path));
            uploadObjectVO.setResourceType(ResourceTypeEnum.VIDEO.getCode());
            minioPath= upload2M3u8(m3u8Path,fileMd5);
        }else{
            if(".mp3".equalsIgnoreCase(subfix)){
                uploadObjectVO.setResourceType(ResourceTypeEnum.AUDIO.getCode());
                //计算mp3时长
                uploadObjectVO.setResourceLength(VideoUtils.getMp3Duration(localPath));
            }else{
                uploadObjectVO.setResourceType(ResourceTypeEnum.DOC.getCode());
                //计算文件页数
                uploadObjectVO.setDocPage(DocUtils.getDocPageCount(fileName,Files.newInputStream(Paths.get(localPath))));
            }
            minioPath=minioUtils.fileUploader(uploadObjectVO.getFilename(), Files.newInputStream(Paths.get(localPath)));
        }
        if(StringUtils.isBlank(minioPath)){
            throw new ApiException("上传文件失败");
        }
        //移除文件
        poolTaskExecutor.execute(() -> {
            FileUtil.del(new File(localPath).getParent());
            log.warn("临时目录 {}已删除", new File(localPath).getParent());
        });
        uploadObjectVO.setPath(minioPath);
        uploadObjectVO.setUrl(minioConfig.getEndpoint()+minioConfig.getBucketName()+"/"+minioPath);
        return uploadObjectVO;
    }
    private void checkFileFormat(String subfix){
        if(!FORMATSET.contains(subfix)){
            throw new ApiException(String.format("文件格式<%s>暂时不支持",subfix));
        }
    }
    @Override
    public boolean uploadSlice(HttpServletRequest request, String guid, Integer chunk, MultipartFile file) {
        return false;
    public void removeMinioFile(Long resourceId,String path) {
        String md5="";
        if(path.indexOf("/")!=-1){
            md5=path.substring(0,path.indexOf("/"));
        }else{
            md5=path.substring(0,path.lastIndexOf("."));
        }
        LambdaQueryWrapper<ExResource> lambdaQueryWrapper = Wrappers.<ExResource>lambdaQuery().
                eq(true, ExResource::getMd5, md5);
        if(resourceId!=null){
            lambdaQueryWrapper.ne(ExResource::getId,resourceId);
        }
        lambdaQueryWrapper.last(" limit 1");
        ExResource resource=resourceMapper.selectOne(lambdaQueryWrapper);
        if(resource==null){//不存在共用md5文件
            //删除minio文件
            if(path.endsWith(".m3u8")){
                minioUtils.removeFolder(md5);
            }else{
                minioUtils.removeFile(path);
            }
        }
    }
    @Override
    public String uploadVideoMerge(String guid, String fileName) {
        return null;
    @Transactional(rollbackFor = RuntimeException.class)
    public void importStudent() {
        String path="/home/java/train_exam/back/安全教育学员模板.xlsx";
      //  String path="F:/kzy/乱七八糟/安全教育学员模板.xlsx";
        List<StudentExcelData> studentExcelDataList=EasyExcel.read(path, StudentExcelData.class,new StudentExcelDataListener()).sheet().doReadSync();
        List<ExStudent> students=new ArrayList<>();
        List<StudentExcelData> errorStudents=new ArrayList<>();
        SysCompany company=companyService.getOne(Wrappers.<SysCompany>lambdaQuery().eq(SysCompany::getName,studentExcelDataList.get(0).getCompanyName())
                .eq(SysCompany::getDelFlag,0));
        if(company==null){
            throw new ApiException("公司不存在");
        }
        for(StudentExcelData studentExcelData:studentExcelDataList){
            String errorMessage=validateData(studentExcelData);
            if(StringUtils.isNotBlank(errorMessage)){
                studentExcelData.setRemark(errorMessage);
                errorStudents.add(studentExcelData);
                continue;
            }
            ExStudent dbStudent=studentService.getOne(Wrappers.<ExStudent>lambdaQuery().eq(ExStudent::getPhone,studentExcelData.getPhone()));
            if(dbStudent!=null){
                if(!dbStudent.getName().equals(studentExcelData.getName())){
                    studentExcelData.setRemark("序号"+studentExcelData.getIndex()+"学员用户已存在");
                    errorStudents.add(studentExcelData);
                }
                continue;
            }
            ExStudent student=new ExStudent();
            BeanUtils.copyProperties(studentExcelData,student,new String[]{"sex"});
            if("男".equals(studentExcelData.getSex())){
                student.setSex(0);
            }else{
                student.setSex(1);
            }
            String departName=studentExcelData.getDeptName();
            List<SysUser> users=userService.list(Wrappers.<SysUser>lambdaQuery().eq(SysUser::getName,departName).eq(SysUser::getUserType, UserTypeEnum.DEPART_USER.getCode()).eq(SysUser::getDelFlag,0).last(" limit 1"));
            if(users.isEmpty()){
                studentExcelData.setRemark("序号"+studentExcelData.getIndex()+"未找到相应的部门账号");
                errorStudents.add(studentExcelData);
                continue;
            }
            student.setPassword(SecurityUtils.encryptPassword("123456@a"));
            student.setCompanyId(company.getId());
            student.setCreateId(users.get(0).getId());
            students.add(student);
        }
        if(!errorStudents.isEmpty()){
            EasyExcel.write("error.xlsx").head(StudentExcelData.class)
                    .sheet("异常用户列表")
                    .doWrite(errorStudents);
        }
        studentService.saveBatch(students);
    }
    @Override
    public String uploadMerge(String guid, String fileName) {
        return null;
    public String validateData(StudentExcelData studentExcelData){
        if(StringUtils.isBlank(studentExcelData.getName())){
            return "序号"+studentExcelData.getIndex()+"姓名为空";
        }
        if(StringUtils.isBlank(studentExcelData.getSex())){
            return "序号"+studentExcelData.getIndex()+"性别为空";
        }
        if(StringUtils.isBlank(studentExcelData.getIdNo())||studentExcelData.getIdNo().length()!=18){
            return "序号"+studentExcelData.getIndex()+"身份证为空或者长度不正确";
        }
        if(StringUtils.isBlank(studentExcelData.getPhone())||studentExcelData.getPhone().length()!=11){
            return "序号"+studentExcelData.getIndex()+"手机号为空或者长度不正确";
        }
        if(StringUtils.isBlank(studentExcelData.getDeptName())){
            return "序号"+studentExcelData.getIndex()+"部门为空";
        }
        return "";
    }
    public UploadObjectVO mergeFile(String fileMd5,String fileName){
        String subfix = fileName.substring(fileName.lastIndexOf("."));
        String systemDir=System.getProperty("user.dir");
        String filePath=systemDir+filePathConfig.getTempPath()+"chunk/"+fileMd5;
        File parentFileDir=new File(filePath);
        if(!parentFileDir.exists()||!parentFileDir.isDirectory()){
            throw new ApiException("切片文件合并失败,文件不存在");
        }
        String destPath=systemDir+filePathConfig.getTempPath()+fileMd5;
        File destTempFile=new File(destPath,fileMd5+subfix);
        if(!destTempFile.exists()){
            destTempFile.getParentFile().mkdirs();
        }
        try{
            //destTempFile.createNewFile();
            for(int i=0;i<Objects.requireNonNull(parentFileDir.listFiles()).length;i++){
                File partFile=new File(parentFileDir,fileMd5+"-"+i);
                FileOutputStream destTempfos = new FileOutputStream(destTempFile, true);
                //遍历"所有分片文件"到"最终文件"中
                FileUtils.copyFile(partFile, destTempfos);
                destTempfos.close();
            }
            FileUtil.del(parentFileDir);
        }catch (Exception e){
            log.error("切片文件合并,失败原因e:{}", e.getMessage());
            throw new ApiException("切片文件合并失败:"+e.getMessage());
        }
        UploadObjectVO uploadObjectVO=new UploadObjectVO();
        uploadObjectVO.setSize(destTempFile.length());
        uploadObjectVO.setPath(destTempFile.getPath());
        uploadObjectVO.setOriginName(fileName);
        uploadObjectVO.setFilename(fileMd5+subfix);
        uploadObjectVO.setMd5(fileMd5);
        return uploadObjectVO;
    }
    @Override