Unified attachment management extension based on Ruoyi and WebUploader (Part 2)

Table of contents

background

1. Database definition

1. Purpose

2. Database physical table design

Two, JAVA background service definition

1. Entity class definition

2. Data service and business layer processing

3. Definition of control layer

 3. Summary


background

       In the previous blog post, I briefly introduced how to expand Ruoyi's large attachment upload and unified management. The original address: unified attachment management extension based on Ruoyi and WebUploader (Part 1) . The previous blog posts mainly focused on the explanation of the foreground. The foreground was mainly explained around the WebUploader component, and the corresponding background processing was not explained in detail. This article, as the next part, mainly focuses on the background design and implementation of uploading large attachments. Through related UML modeling, it specifically explains how the background performs corresponding processing. Including examples of special functions such as resuming uploads from breakpoints and judging file duplication. I hope it will inspire you to use it in your project.

1. Database definition

1. Purpose

       The purpose of defining data entities is to provide common components and packaging for common use in various businesses. At the same time, in order to meet the real-time verification of whether files are uploaded repeatedly during file processing, it is necessary to use the database for storage. Of course, it is not necessary to use a relational database for data storage here, and it is also possible to use a non-relational database, such as MongoDB. Just need to achieve our purpose.

2. Database physical table design

       To design a unified attachment management module, it is necessary not only to unify the operation interface for file upload, but also to provide a unified API to retrieve attachments, so the table name of a business table must be passed to the attachment table. At the same time, in order to prevent a business table from having different attachments in different states, an additional business type field is added. For example, for a process review business, the attachments associated in the process creation stage and the attachments associated in the approval process may be different. This field can be distinguished. In order to speed up the file upload process and avoid repeated uploading of the same file to the disk, causing unnecessary waste of resources, it is very necessary to judge the weight of resources. Regarding the weight judgment of files, you can refer to the previous blog post: A brief discussion on the posture of judging the weight of java file uploads . Here is a more detailed explanation. According to the above requirements, the table structure of a database physical table can be obtained:

        This is a table structure based on the postgresql database. Similarly, it is the same under mysql or other databases, but the data type needs to be slightly modified. Here I will share the sql file corresponding to the table structure:

-- ----------------------------
-- Table structure for biz_file
-- ----------------------------
DROP TABLE IF EXISTS "biz_file";
CREATE TABLE "biz_file" (
  "id" int8 NOT NULL,
  "f_id" varchar(100)  ,
  "b_id" varchar(100)  ,
  "f_type" varchar(30)  NOT NULL,
  "f_name" varchar(512)  NOT NULL,
  "f_desc" varchar(512) ,
  "f_state" int2,
  "f_size" int8 NOT NULL,
  "f_path" varchar(1024)  NOT NULL,
  "table_name" varchar(255) ,
  "md5code" varchar(255) ,
  "directory" varchar(1024) ,
  "create_by" varchar(64) ,
  "create_time" timestamp(6),
  "update_by" varchar(64) ,
  "update_time" timestamp(6),
  "biz_type" varchar(30) 
)
;
COMMENT ON COLUMN "biz_file"."id" IS '主键';
COMMENT ON COLUMN "biz_file"."f_id" IS '文件id';
COMMENT ON COLUMN "biz_file"."b_id" IS '业务id';
COMMENT ON COLUMN "biz_file"."f_type" IS '文件类型';
COMMENT ON COLUMN "biz_file"."f_name" IS '名称';
COMMENT ON COLUMN "biz_file"."f_desc" IS '文件描述';
COMMENT ON COLUMN "biz_file"."f_state" IS '文件状态';
COMMENT ON COLUMN "biz_file"."f_size" IS '文件大小';
COMMENT ON COLUMN "biz_file"."f_path" IS '文件路径';
COMMENT ON COLUMN "biz_file"."table_name" IS '业务表名';
COMMENT ON COLUMN "biz_file"."md5code" IS 'md5code';
COMMENT ON COLUMN "biz_file"."directory" IS '文件目录';
COMMENT ON COLUMN "biz_file"."create_by" IS '创建人';
COMMENT ON COLUMN "biz_file"."create_time" IS '创建时间';
COMMENT ON COLUMN "biz_file"."update_by" IS '更新人';
COMMENT ON COLUMN "biz_file"."update_time" IS '更新时间';
COMMENT ON COLUMN "biz_file"."biz_type" IS '业务类型';
COMMENT ON TABLE "biz_file" IS '系统附件信息表,用于保存文件上传信息';

-- ----------------------------
-- Primary Key structure for table biz_file
-- ----------------------------
ALTER TABLE "biz_file" ADD CONSTRAINT "pk_biz_file" PRIMARY KEY ("id");

Two, JAVA background service definition

1. Entity class definition

       Entity classes are mainly used to define object information that is mapped to database tables, and use SQL statements to manipulate database information. The codes in the examples all need to configure lombok to simplify the development workload of related classes.

package com.hngtghy.project.webupload.domain;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.hngtghy.framework.web.domain.BaseEntity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@TableName("biz_file")
@NoArgsConstructor
@AllArgsConstructor
@Setter
@Getter
@ToString
public class FileEntity extends BaseEntity {

	private static final long serialVersionUID = 1L;

	private Long id;

	@TableField(value = "f_id")
	private String fid;
	
	@TableField(value = "b_id")
	private String bid;
	
	@TableField(value = "f_type")
	private String type;
	
	@TableField(value = "f_name")
	private String name;
	
	@TableField(value = "f_desc")
	private String desc;
	
	@TableField(value = "f_state")
	private Integer state;
	
	@TableField(value = "f_size")
	private Long size;
	
	@TableField(value = "f_path")
	private String path;
	
	@TableField(value = "table_name")
	private String tablename = "temp_table";

	private String md5code;
	
	private String directory;
	
	@TableField(value = "biz_type")
	private String bizType;

}

2. Data service and business layer processing

        Data services are mainly based on mybatis-plus to perform addition, deletion, modification and query operations, while the business layer includes basic business encapsulation. In addition to calling data services, there are some additional business processing operations.

package com.hngtghy.project.webupload.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hngtghy.project.webupload.domain.FileEntity;

public interface FileMapper extends BaseMapper<FileEntity> {

}

         Functional methods for deleting, querying, modifying, and saving attachment objects are defined in the service layer, and are defined in the form of interfaces.

package com.hngtghy.project.webupload.service;

import java.util.List;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hngtghy.project.system.user.domain.User;
import com.hngtghy.project.webupload.domain.FileEntity;

public interface IFileService extends IService<FileEntity>{

	int saveEntity(FileEntity entity) throws Exception;
	
	int updateEntity(FileEntity entity) throws Exception;
	
	int removeByIds(Long [] ids) throws Exception;
	
	int removeByBids(List<String> bids) throws Exception;
	
	FileEntity getOneByFid(String fid) throws Exception;
	
	int removeByFid(String fid) throws Exception;
	
	void deleteErrorFile(User user);
	
    Long findFileSizeByWrapper(QueryWrapper<FileEntity> queryWrapper);
    
    List<FileEntity> findListByQueryWrapper(QueryWrapper<FileEntity> queryWrapper);
    
    FileEntity findByMd5Code(String md5Code) throws Exception;
}

        Its specific implementation class is as follows. Here, it mainly rewrites the methods defined in the interface to meet new business requirements. The more important method is findByMd5Code. This method is used to query the database for duplicate files. When there are duplicate files After that, the upload will not be repeated. The key code is as follows:

package com.hngtghy.project.webupload.service;

import java.io.File;
import java.io.FileFilter;
import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hngtghy.common.utils.StringUtils;
import com.hngtghy.common.utils.security.ShiroUtils;
import com.hngtghy.framework.aspectj.lang.annotation.DataSource;
import com.hngtghy.framework.config.HngtghyConfig;
import com.hngtghy.project.system.user.domain.User;
import com.hngtghy.project.webupload.domain.FileEntity;
import com.hngtghy.project.webupload.mapper.FileMapper;

@Service
public class FileServiceImpl extends ServiceImpl<FileMapper, FileEntity> implements IFileService {
	
	
	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public int saveEntity(FileEntity entity) throws Exception {
		entity.setCreateBy(ShiroUtils.getLoginName());
		entity.setCreateTime(new Date());
		int result = this.save(entity) ? 1 : 0;
		return result;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public int updateEntity(FileEntity entity) throws Exception {
		int result = this.updateById(entity) ? 1 : 0;
		return result;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public int removeByIds(Long[] ids) throws Exception {
		List<Long> removeList = Arrays.asList(ids);
		for (Long id : ids) {
			deleteFileOnDiskById(id);
		}
		int result = this.removeByIds(removeList) ? 1 : 0;
		return result;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
	public FileEntity getOneByFid(String fid) throws Exception {
		QueryWrapper<FileEntity> queryWrapper = new QueryWrapper<FileEntity>();
		queryWrapper.eq("f_id", fid);
		FileEntity file = this.getOne(queryWrapper);
		return file;
	}

	@Override
	public int removeByFid(String fid) throws Exception {
		QueryWrapper<FileEntity> queryWrapper = new QueryWrapper<FileEntity>();
		queryWrapper.eq("f_id", fid);
		deleteFileOnDiskByFid(fid);
		int result = this.remove(queryWrapper) ? 1 : 0;
		return result;
	}

	private void deleteFileOnDiskByFid(String fid) throws Exception {
		FileEntity file = this.getOneByFid(fid);
		String file_path = file.getPath();
		if(!sharedFile(file_path)){
			this.deleteFileOnDisk(file_path);
		}
	}

	private void deleteFileOnDiskById(Long id) throws Exception {
		FileEntity file = this.getById(id);
		String file_path = file.getPath();
		if(!sharedFile(file_path)){
			this.deleteFileOnDisk(file.getPath());
		}
	}
	
	private boolean sharedFile(String path){
		QueryWrapper<FileEntity> queryWrapper = new QueryWrapper<FileEntity>();
		queryWrapper.eq("f_path", path);
		queryWrapper.eq("f_state", 1);
		List<FileEntity> files = this.list(queryWrapper);
		if (files != null && files.size() > 1) {
			return  true;
		}
		return false;
	}

	private void deleteFileOnDisk(String path) throws Exception {
		File file = new File(HngtghyConfig.getProfile() + "/" + path);
		file.deleteOnExit();
		if (file.isFile() && file.exists()) {
			file.delete();
		}
	}
	
	/**
	 * 删除失败文件、未绑定文件
	 * @author 
	 * @date 
	 */
	public void deleteErrorFile(User user){
		try{
			String path = HngtghyConfig.getProfile() + "/";
			QueryWrapper<FileEntity> queryWrapper = new QueryWrapper<FileEntity>();
			queryWrapper.eq("f_state", 0);
			queryWrapper.lt("create_time", "now() - INTERVAL '3 days'");//三天前的失败文件
			List<FileEntity> files = this.list(queryWrapper);
			if (files != null && files.size() > 0) {
				this.remove(queryWrapper);
				for(FileEntity file:files){
					deleteFileOnDiskById(file.getId()); 
				}
			}
			queryWrapper = new QueryWrapper<FileEntity>();
			queryWrapper.eq("linked", 0);
			queryWrapper.lt("create_time", "now() - INTERVAL '3 days'");
			files = this.list(queryWrapper);
			if (files != null && files.size() > 0) {
				this.remove(queryWrapper);
				for(FileEntity file:files){
					deleteFileOnDiskById(file.getId()); 
				}
			}
			
			//三天前的分片临时目录
			String save_path = path + (user != null ? user.getUserId() : "unkown");
			File directory = new File(save_path);
			File[] fileArray = directory.listFiles(new FileFilter() {
				@Override
				public boolean accept(File pathname) {
					long last = pathname.lastModified();
					long diff = System.currentTimeMillis() - last;
					boolean del = (diff - 24 * 60 * 60 * 1000) > 0;
					if (pathname.isDirectory() && del) {
						return true;
					}
					return false;
				}
			});
			for(File dir : fileArray){
				dir.delete();
			}
		}catch(Exception e){
			//无需处理该异常
		}
	}

	@Override
	public Long findFileSizeByWrapper(QueryWrapper<FileEntity> queryWrapper) {
		Long result = this.count(queryWrapper);
		return result;
	}

	@Override
	public List<FileEntity> findListByQueryWrapper(QueryWrapper<FileEntity> queryWrapper) {
		List<FileEntity> result = this.list(queryWrapper);
		return result;
	}

	/**
	 * @Title: removeByBids
	 * @Description: 根据bid删除附件
	 * @param bids
	 * @return
	 * @throws Exception 
	 */
	@Override
	public int removeByBids(List<String> bids) throws Exception {
		QueryWrapper<FileEntity> paramWrapper = new QueryWrapper<>();
		paramWrapper.in("b_id", bids);
		List<FileEntity> files = this.list(paramWrapper);
		int ret = this.remove(paramWrapper)?1:0;
		if(files == null || files.size() == 0)
			return 0;
		for(FileEntity file:files){
			String file_path = file.getPath();
			if(!sharedFile(file_path)){
				this.deleteFileOnDisk(file_path);
			}
		}
		return ret;
	}

	@Override
	public FileEntity findByMd5Code(String md5Code) throws Exception {
		QueryWrapper<FileEntity> queryWrapper = new QueryWrapper<FileEntity>();
		queryWrapper.eq("md5code", md5Code);
		List<FileEntity> list = this.list(queryWrapper);
		return StringUtils.isEmpty(list) ? null : list.get(0);
	}
	
}

3. Definition of control layer

        The control layer is mainly used to receive the request submitted by the front-end WebUploader, and at the same time call the corresponding service to respond. The key is as follows:
 

package com.hngtghy.project.webupload.controller;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hngtghy.common.utils.StringUtils;
import com.hngtghy.common.utils.file.FileTypeUtils;
import com.hngtghy.framework.config.HngtghyConfig;
import com.hngtghy.framework.event.FileUploadEvent;
import com.hngtghy.framework.web.controller.BaseController;
import com.hngtghy.framework.web.domain.AjaxResult;
import com.hngtghy.framework.web.page.LayerTableDataInfo;
import com.hngtghy.project.system.user.domain.User;
import com.hngtghy.project.webupload.domain.FileEntity;
import com.hngtghy.project.webupload.service.IFileService;
import com.github.pagehelper.util.StringUtil;

/**
 * 文件上传相关
 * @author wuzuhu
 */
@Controller
@RequestMapping("/uploadfile")
public class UploadFileController extends BaseController {
	
	private String prefix = "upload/";

	@Autowired
	private IFileService fileService;

	private final int block_size = 5*1024*1024;
	
	@Autowired
	private ApplicationContext applicationContext;
	
	/**
	 * 文件列表页面
	 */
	@GetMapping("/main")
	public String main(ModelMap mmap,String bid,String tablename,String bizType,String multipleMode) {
		mmap.put("bid", bid);//业务表id
		mmap.put("temp_b_id", UUID.randomUUID().toString());
		mmap.put("tablename", tablename);//业务表名
		mmap.put("bizType", bizType);//业务类型
		mmap.put("multipleMode", multipleMode);//文件多选模式,默认为多选,为空即可。单选需要设置为:single
		return prefix + "fileTablePage";
	}
	
	/**
	 * 文件上传进度页面
	 */
	@GetMapping("/process")
	public String upload(ModelMap mmap) {
		return prefix + "uploadProcessModal";
	}
	
	/**
	 * 文件上传进度页面
	 */
	@GetMapping("/view")
	public String view(ModelMap mmap) {
		return prefix + "viewFile";
	}
	
	@ResponseBody
	@PostMapping("/bigUploader")
	public AjaxResult bigUploader(String chunk,String chunks,String fid,
			@RequestParam("file") MultipartFile multipartFile) {
		String path = HngtghyConfig.getProfile() + "/";
		try {
			FileEntity db_file = fileService.getOneByFid(fid);
			if (db_file == null) {
				return AjaxResult.error(AjaxResult.Type.WEBUPLOADERROR.value(),"没找到数据库记录");
			}
		    if(db_file.getState() == 1){
		    	return AjaxResult.success();
            }
		    User user = getSysUser();
			String save_dir = path + (user != null ? user.getUserId() : "unkown") ;
			String chunk_dir = save_dir + "/" + fid ;
			boolean sign = chunks != null && Integer.parseInt(chunks) > 0;
			String tempmlDir = sign ? chunk_dir : save_dir;
			String chunkFilePath = sign ? chunk_dir + "/" + chunk : chunk_dir + "." + db_file.getType();
			if(!sign) {
				db_file.setState(1);
				fileService.updateById(db_file);
			}
			File tempml = new File(tempmlDir);
			if (!tempml.exists()) {
				tempml.mkdirs();
			}
			File chunkFile = new File(chunkFilePath);
			multipartFile.transferTo(chunkFile);
			return AjaxResult.success();
		} catch (Exception e) {
			return AjaxResult.error(AjaxResult.Type.WEBUPLOADERROR.value(),"服务端异常");
		}
	}

	@ResponseBody
	@PostMapping("/merge")
	public AjaxResult merge(String action,String chunks,String chunk,String chunkSize,String temp_b_id,FileEntity fileObj) {
		try {
			User user = getSysUser();
			String fid = fileObj.getFid();
			//公共目录
			String commonDir = user != null ? String.valueOf(user.getUserId()) : "unkown";
			FileEntity db_file = fileService.getOneByFid(fid);
			String type = FileTypeUtils.getFileType(fileObj.getName());
			
			String path = HngtghyConfig.getProfile() + "/";
			String chunk_dir = path + commonDir + "/" + fid;
			
			//数据库保存目录考虑可迁移 add by wuzuhu on 2022-07-18
			String dbPath = commonDir + "/" + fid + "." + type;
			
			if (action.equals("mergeChunks")) {
				return mergeChunks(db_file,chunk_dir,chunks,path + dbPath);
			} 
			if (action.equals("checkChunk")) {
				String chunkfile_dir = chunk_dir + "/" + chunk;
				return checkChunk(chunkfile_dir,chunkSize);
			}
			if (action.equals("exSendFile")) {
				FileEntity md5File = fileService.findByMd5Code(fileObj.getMd5code());
				if(null != md5File) {
					dbPath = md5File.getPath();
					fileObj.setName(md5File.getName());
				}
				fileObj.setPath(dbPath);
				fileObj.setType(type);
				fileObj.setBid(temp_b_id);
				fileObj.setState(0);
				return exSendFile(db_file,path + dbPath,fileObj);
			}
		} catch (Exception e) {
			return AjaxResult.error();
		}
		return AjaxResult.success();
	}
	
	private AjaxResult checkChunk(String chunkfile_dir,String chunkSize) {
		File checkFile = new File(chunkfile_dir);
		if (checkFile.exists() && checkFile.length() == Integer.parseInt(chunkSize)) {
			return AjaxResult.error(2,"文件已存在");
		} else {
			return AjaxResult.success();
		}
	}
	
	@SuppressWarnings("resource")
	private AjaxResult mergeChunks(FileEntity db_file,String chunk_dir,String chunks,String f_path) throws IOException {
		if (db_file == null) {
			return AjaxResult.error(AjaxResult.Type.WEBUPLOADERROR.value(), "找不到数据");
		}
		if (db_file.getState() == 1) {
			//未分片文件上传成功合并成功后发布相应事件,各监听器自由监听并执行
			applicationContext.publishEvent(new FileUploadEvent(this, db_file));
			return AjaxResult.success();
        }
		if(db_file.getSize() > block_size){
			File f = new File(chunk_dir);
			File[] fileArray = f.listFiles(new FileFilter() {
				@Override
				public boolean accept(File pathname) {
					if (pathname.isDirectory()) {
						return false;
					}
					return true;
				}
			});
			if (fileArray == null || fileArray.length == 0) {
				return AjaxResult.error(AjaxResult.Type.WEBUPLOADERROR.value(), "找不到分片文件");
			}
			if (StringUtil.isNotEmpty(chunks) && fileArray.length != Integer.parseInt(chunks)) {
				return AjaxResult.error(AjaxResult.Type.WEBUPLOADERROR.value(), "分片文件数量错误");
			}
			List<File> fileList = new ArrayList<File>(Arrays.asList(fileArray));
			Collections.sort(fileList, new Comparator<File>() {
				@Override
				public int compare(File o1, File o2) {
					if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())) {
						return -1;
					}
					return 1;
				}
			});
			File outputFile = new File(f_path);
			outputFile.createNewFile();
			FileChannel outChnnel = new FileOutputStream(outputFile).getChannel();
			FileChannel inChannel;
			for (File file : fileList) {
				inChannel = new FileInputStream(file).getChannel();
				inChannel.transferTo(0, inChannel.size(), outChnnel);
				inChannel.close();
				file.delete();
			}
			outChnnel.close();
			db_file.setState(1);
			fileService.updateById(db_file);
			File tempFile = new File(chunk_dir);
			if (tempFile.isDirectory() && tempFile.exists()) {
				tempFile.delete();
			}
			//分片文件上传成功合并成功后发布相应事件,各监听器自由监听并执行
			applicationContext.publishEvent(new FileUploadEvent(this, db_file));
		}
		return AjaxResult.success();
	}
	
	private AjaxResult exSendFile(FileEntity db_file,String f_path,FileEntity fileObj) throws Exception {
		if (db_file != null) {
			return AjaxResult.success();
		}
		System.out.println("md5code==>" + fileObj.getMd5code() + "\t f_path=="+ f_path);
		System.out.println("fileObj id ==>" + fileObj.getId() + "\t fid===>" + fileObj.getFid());
		
		fileObj.setState(0);
		fileService.saveEntity(fileObj);
		//执行插入
		File file_indisk = new File(f_path);
		if (file_indisk.exists() && file_indisk.length() == fileObj.getSize()) {
			fileObj.setState(1);
			fileService.updateById(fileObj);
			//已上传文件上传成功合并成功后发布相应事件,各监听器自由监听并执行
			applicationContext.publishEvent(new FileUploadEvent(this, fileObj));
			return AjaxResult.error(2,"文件已存在");
		} else {
			return AjaxResult.success();
		}
	}

	/**
	 * 删除文件
	 * @param fid
	 */
	@ResponseBody
	@RequestMapping("/delete")
	public AjaxResult delete(@RequestParam("ids[]") Long[] ids) throws Exception {
		fileService.deleteErrorFile(getSysUser());
		int result = fileService.removeByIds(ids);
		return result > 0 ? AjaxResult.success() : AjaxResult.error();
	}

	/**
	 * 删除文件
	 * @param fid
	 */
	@ResponseBody
	@RequestMapping("/deleteByFid")
	public AjaxResult deleteByFid(String fid) throws Exception{
		fileService.deleteErrorFile(getSysUser());
		int result = fileService.removeByFid(fid);
		return result > 0 ? AjaxResult.success() : AjaxResult.error();
	}


	/**
	 * 查询文件列表
	 * @author zlz
	 * @date 2019年3月19日 下午3:16:32
	 */
	@ResponseBody
	@RequestMapping("/list")
	public LayerTableDataInfo list(String b_id, String b_ids, String f_id,String name,String tablename,String bizType) throws Exception{
		startLayerPage();
		QueryWrapper<FileEntity> queryWrapper = new QueryWrapper<FileEntity>();
		queryWrapper.eq("f_state", 1);
        if(StringUtils.isNotBlank(b_id)){
        	queryWrapper.eq("b_id", b_id);
        }
        if(StringUtils.isNotBlank(b_ids)){
        	queryWrapper.in("b_id", splitIds(b_ids));
        }
        if(StringUtils.isNotBlank(f_id)){
        	queryWrapper.eq("f_id", f_id);
        }
        if(StringUtils.isNotBlank(name)){
        	queryWrapper.like("f_name", name);
        }
        if(StringUtils.isNotBlank(tablename)) {
        	queryWrapper.eq("table_name", tablename);
        }
        if(StringUtils.isNotBlank(bizType)){
        	queryWrapper.eq("biz_type", bizType);
        }
        List<FileEntity> list = fileService.findListByQueryWrapper(queryWrapper);
		return getLayerDataTable(list);
	}
	
	/**
	 * 将 *,*,*,*,*, 样的ID放入Set中
	 * @return 包含对应各id的数值的列表, 不包含重复id
	 */
	private static final Set<String> splitIds(String ids){
		Set<String> idsSet = new HashSet<String>();
		if(ids != null && !ids.isEmpty()){
			String[] data = ids.split(",");
			if(data != null){
				for(String d : data){
					idsSet.add(d);
				}
			}
		}
		return idsSet;
	}

	/**
	 * 下载文件
	 * @param fid
	 * @param response
	 */
	@RequestMapping("/download")
	public void download(String fid, HttpServletResponse response) throws Exception{
		FileEntity file = fileService.getOneByFid(fid);
		if (file == null) {
			return;
		}
		OutputStream to = null;
		try {
			String filename = file.getName();
			response.setContentType("text/html");
			response.setHeader("Content-Disposition","attachment;filename=\"" + new String(filename.getBytes(), "ISO-8859-1") + "\"");
			to = response.getOutputStream();
			this.getFileInfo(to, file);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (to != null) {
				try {
					to.flush();
					to.close();
				} catch (IOException e) {
				}
			}
		}
	}
	
	private void getFileInfo(OutputStream to,FileEntity file) throws Exception{
		InputStream in = null;
		try{
			File file_download = new File(HngtghyConfig.getProfile() + "/" + file.getPath());
			in = new FileInputStream(file_download);
			byte[] buffer = new byte[1024];
			int got = -1;
			while ((got = in.read(buffer)) != -1){
				to.write(buffer,0,got);
			}
		}catch(Exception e){
			throw e;
		}finally{
			if(in != null){
				try {
					in.close();
				} catch (IOException e) {
					
				}
			}
		}
	}
	
}

Its class diagram is as follows:

 

        The above is the complete back-end receiving and processing logic, which is developed using java code. Here, the local disk is used for storage. You can expand it yourself, such as integrating distributed storage, which is all possible. After this transformation, it can be used as an enterprise. Unified service.

 3. Summary

        The above is the main content of this article, introducing the background development logic of the unified attachment management service. Briefly introduce the design of the background attachment table, and then define the related entity definition, service layer and control layer around the attachment management. By giving the class diagram of related classes, it is convenient for everyone to have a more comprehensive understanding of the overall code level.

Guess you like

Origin blog.csdn.net/yelangkingwuzuhu/article/details/127361371