基于Ruoyi和WebUploader的统一附件管理扩展(下)

Posted 夜郎king

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于Ruoyi和WebUploader的统一附件管理扩展(下)相关的知识,希望对你有一定的参考价值。

目录

背景

一、数据库定义

1、目的

2、数据库物理表设计

二、JAVA后台服务定义

1、实体类定义

2、数据服务和业务层处理

3、控制层定义

 三、总结


背景

       在之前的博文中,简单介绍了如何扩展Ruoyi的大附件上传及统一管理一篇,原文地址:基于Ruoyi和WebUploader的统一附件管理扩展(上)。之前的博文主要集中在前台的讲解,前台主要是围绕WebUploader组件来说明,对应的后台处理没有仔细讲解。本文作为下篇,主要围绕大附件上传的后台设计及实现,通过相关的UML建模,具体阐述后台是如何进行对应的处理。包括断点续传,文件重复判断等特殊功能的实例讲解。希望对你在项目中的使用有所启发。

一、数据库定义

1、目的

       定义数据实体的目的是为了可以在各业务中通用,提供统一的组件及封装,同时为了可以在文件处理时满足实时对文件是否重复上传做校验,因此有必要采用数据库的方式进行存储。当然,这里的数据存储未必要使用关系型数据库,使用非关系型数据库,比如MongoDB也是可以的。只需要达到我们的目的即可。

2、数据库物理表设计

       设计统一附件管理模块,不仅要统一文件上传的操作界面,同时要提供统一的API可以对附件进行检索,因而要将一个业务表的表名传递到附件表中。同时为了,防止一张业务表在不同状态下也可以拥有不同的附件,额外增加一个业务类型的字段。比如一个流程审核的业务,在流程新建阶段关联的附件和审批中关联的附件可以是不一样的。由此字段可以区分开,为了在文件上传过程中加速,也避免减少同一份文件反复上传到磁盘中,造成不必要的资源浪费,因此非常有必要对资源进行判重。关于文件判重,可以参考之前的博文:java文件上传判重姿势浅谈,这里有比较详细的说明。根据上述的需求,可以得到一个数据库物理表的表结构:

        这是一份基于postgresql数据库的表结构,同理在mysql或者其它的数据库下,是一样的,只是数据类型需要稍微修改一下而已。这里将表结构对应的sql文件分享一下:

-- ----------------------------
-- 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");

二、JAVA后台服务定义

1、实体类定义

       实体类主要用于定义跟数据库表相互映射的对象信息,使用sql语句来操作数据库信息。示例中的代码均需要配置lombok来简化相关类的开发工作量。

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、数据服务和业务层处理

        数据服务,主要基于mybatis-plus来进行增删改查操作,而业务层包含了基础的业务封装,除了调用数据服务外,还有一些额外的业务处理操作。

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> 

         在服务层中定义了对附件对象的删除、查询、修改、保存的功能方法,以接口的形式进行定义。

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;

        其具体的实现类如下,这里主要针对在接口定义的方法进行重写,以满足新的业务需求,比较重要的方法是findByMd5Code,通过这个方法到数据库中查询是否有重复的文件,当有重复文件后,将不再重复上传,关键代码如下:

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、控制层定义

        控制层主要用于接收前端WebUploader提交过来的请求,同时调用相应的服务后进行响应。关键如下面所示:
 

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) 
					
				
			
		
	
	

其类图如下所示:

 

        以上就是完整的后端接收处理逻辑,使用java代码进行开发,这里使用了本地磁盘的方式进行存储,你可以自己扩展一下,比如可以集成分布式存储,都是可以的,这样改造后可以当成企业统一的服务。

 三、总结

        以上就是本文的主要内容,介绍统一附件管理服务的后台开发逻辑。简单介绍了后台附件表的设计,以后围绕附件管理的相关实体定义、服务层、控制层定义。通过给出相关类的类图,便于大家对整体的代码层次有一个比较全面的认识。

一周活动早知道|多云统一管理平台架构云管平台统一监控数据库智能化运维基于容器云平台的微服务改造等主题线上交流

社区每周均组织线上线下交流探讨,并邀请领域内专家分享、答疑,欢迎参与!


企业如何建立一套自主可控、安全可靠的多云统一管理平台架构设计在线交流


12月23日-1月15日


基于原有的私有云建设和运维模式,由于没有统一的私有云架构规划,企业各部门为了满足自身的信息化建设需求,各自采购服务器、存储、安全等硬件设备组建自己的私有云环境,包括基于PowerVM、KVM、Vmware、HyperV等虚拟化计算资源池建设的私有云环境,而且由于自身的应用和硬件提前量的储备等原因,造成各个云环境的资源留有很大余地,而且不同的云环境重复投资造成了极大的资源浪费。各应用系统的虚拟化资源不能被合理、有效利用。

大部分基于X86服务器的KVM、Vmware、HyperV等云环境负载较轻,各个云环境的资源浪费非常严重,由于基于原有的硬件部署方式无法及时对资源进行共享和调配,做到资源合理高效的利用,造成了的设备资源浪费,能耗高,占用机房面积大,不同的云管理平台切换管理繁杂。

1、资源浪费严重;

2、部署响应缓慢:跨不同云环境部署业务经常需要面对多个部门复杂的审批流程,而且还需要面临多个云环境可能出现不同故障的不确定因素。

3、应用和数据缺乏安全保障;

4、运维成本高

企业基于多种云产品形式、基于成本的考虑,会有多云和混合云的使用。为了数据安全,防止厂商锁定等内部要求,多个云平台出现在企业IT内部,又形成了新的烟囱式系统,应用在多个平台上的分布出现了问题,导致使用云所带来的效率提升无法从整体体现。因此企业十分迫切需要解决跨多云平台统一管理问题。

所以,多云平台带来的日益增长的操作和安全上复杂性以及代价,作为企业的云平台架构师们必须要去解决,寻求一种最优的架构方案,为了能更好的帮助架构师们解决这方面的困难,社区也特别邀请了一些金融行业实践过的架构师专家参与线上的交流探讨。

互动嘉宾:

邓晓华 某金融机构 云管平台研发负责人

擅长数据中心各系统运营、运维、开源产品及相关工具链和技术栈集成、治理。IT从业20+年,有10+年云计算经验,有跨国项目经历,行业经验丰富,有国内大小金融机构和通信省公司的私有云相关规划建设经验。

李高峰  晋城银行 系统架构师

2006年开始涉入IT行业,目前主要负责系统集成实施团队、数据中心架构规划和技术方案审核。

http://www.talkwithtrend.com/activity/?id=1517


如何实现云管理平台的统一监控及监控工具的选型?在线技术交流


12月23日-12月31日

一周活动早知道|多云统一管理平台架构、云管平台统一监控、数据库智能化运维、基于容器云平台的微服务改造等主题线上交流


随着云计算技术的发展,越来越多的云平台和服务类型出现,如VM、KVM、PAAS等。各大企业都在纷纷建设自己私有云平台包括IAAS、PAAS,同时IAAS也有自己的云管理平台如Openstack,另外PAAS也有自己的云管理平台如Kubernetes。一个好的云管理平台是贴合企业实际需求的,它应该在提升 IT 资源利用率的基础上,实现资源的统一视图管理,并且实现与企业内部流程的融合和交互,最终实现云平台的自服务。云管理平台实现了对传统资源交付方式的变革, 然而云管理平台的实施也遇到了诸如技术线路的选择、异构资源(X86 物理机、小型机、 多种虚拟化的虚拟机)的统一纳管、管控流程的标准化和个性化之间的差异等问题。

但如何有效的监控VM、KVM、Openstack、Kubernetes、PAAS、微服务等?不同的平台应该监控哪些指标?应该如何针对不同的平台选择合适的监控工具?如何有效的将云管理平台监控集成到现有的集中监控平台?等问题都大量存在云管理平台的建设和运维中。

在本次主题为“如何实现云管理平台的统一监控及监控工具的选型?”的在线交流活动中,我们邀请到了昆仑银行的云平台监控专家许中华老师,针对如上提到的问题,在线帮助大家答疑解惑。您可以根据企业自身的云管理平台建设现状,以及在实际应用中遇到的具体问题来在线提问交流。

http://www.talkwithtrend.com/activity/?id=1521


传统数据库系统如何实现智能化运维?在线技术交流


12月17日-12月30日

一周活动早知道|多云统一管理平台架构、云管平台统一监控、数据库智能化运维、基于容器云平台的微服务改造等主题线上交流


当前主流数据库运维模式还是基于“专家规则”的经验运维加上自动化运维的方法。然而面对越来越多的系统产生的海量运维数据,传统数据库运维遇到一些痛点:

(1) 基于“专家规则”的经验运维场景覆盖面小。一方面人的经验有限,只能定义有限场景。另一方面每套数据库都有其独特性,统一规则运维不能体现差异性,单独运维成本又太高。

(2) 数据库运维产生的海量数据只要很少部分被管理起来,大量数据价值有待挖掘。这些运维数据潜在的规律和关系特征,是高效运维的基础。

而通过建设机器学习问题分析平台,实现基于机器学习模型的异常检测 , 根因定位和问题管理 。这是对“ 专家规则”模式的补充,解决海量数据运维痛点,提升现有运维能力。

在本次主题为“传统数据库系统如何实现智能化运维?”活动中,我们邀请了民生银行的数据库专家孔再华老师,在线帮助大家解答在规划和实施数据库系统智能化运维的相关疑问。

答疑嘉宾:

孔再华,IBM认证高级DBA,SAP认证BASIS,具有丰富的数据库环境问题诊断和性能调优的经验。在数据库同城双活,集群,多分区,分布式等项目实施上具有丰富的经验。现任职于中国民生银行科技部,工作致力于数据库同城双活架构建设,数据库分布式架构建设和数据库智能运维(AIOps)方向。对于如何将AI技术运用在运维领域具有浓厚的兴趣和创新热情。

参考资料:

次数据库问题智能分析实践

银行行业数据库智能运维平台实践

http://www.talkwithtrend.com/activity/?id=1519


企业如何基于OpenShift容器云平台实现微服务改造的分布式应用集成和流程自动化?


12月24日

一周活动早知道|多云统一管理平台架构、云管平台统一监控、数据库智能化运维、基于容器云平台的微服务改造等主题线上交流


在企业级软件应用开发中,长期以来,API、服务、数据以及系统的集成都是最具挑战性同时也是最基本的需求。 在过去,我们会将这些独立的应用以点对点的方式进行集成,这种方式随后被企业服务总线(enterprise service bus,ESB)和面向服务架构(service-oriented architecture,SOA)所替代。 但是,在现代微服务和云原生架构中,我们很少再去讨论应用集成了。 但这并不意味着这种现代架构已经解决了企业应用集成的所有挑战。 应用集成的挑战几乎没有什么变化,但是我们解决它们的方式却发生了变化。
“微服务”的概念兴起于四五年前,近几年尤其火热,各大企业都在进行微服务化改造和微服务建设。 说起微服务,不得不提来自Martin Flower的《Microservices》文中将发展多年的微服务架构进行了重要性总结,推动了微服务的流行。 经过微服务架构改造的系统会变得更加复杂,这种复杂性体现在一个软件的全生命周期的所有环节,业界大师Martin Fowler建议微服务要基于一个基础平台最好是一个PaaS平台。 其实不仅是PaaS,这个平台需要涵盖DevOps、容器云、微服务框架三个领域,DevOps平台用于支撑微服务的全生命周期数字化协作,容器以其不可变性和自给自足的特点成为微服务部署与运维的最佳选择,微服务之间通过分布式调用框架所提供的服务注册发现机制、服务路由、集群容错等特性以去中心化的方式进行协同。 在微服务化之后,业务模块拆更细,流程变碎,有效地实现分布式应用集成和流程自动化是企业生产环境容器平台建设的挑战。
红帽OpenShift是一个企业就绪型 Kubernetes 容器平台,可以实现全堆栈自动化运营,以管理混合云和多云部署。 红帽 OpenShift企业版已对分布式应用集成和流程自动化进行了企业级应用优化。
12月24日下午2点到4点,社区将邀请到红帽容器资深技术专家王洪涛和魏新宇,为大家分享基于OpenShift的分布式应用集成和流程自动化解决方案及行业实践,同时在线为大家答疑解惑。
核心交流议题:
1 如何实现容器基础镜像的安全和合规性管理?
2 容器和虚机混合部署的应用,如何进行快速部署? 有没有什么好的实践?
3 如何划分流程自动化覆盖范围界限? 微服务化的流程全连路图所需自动化的自动化标准工具技术选型?
4 OpenShift的流程自动化如何和现有的ITLE,工单系统对接?
5 如何在OpenShift上实现微服务系统发布的流程自动化?
6 使用OpenShift+Fuse对比通过Camel、Servicemix 实现分布式应用集成,有什么不同?
支持企业:
一周活动早知道|多云统一管理平台架构、云管平台统一监控、数据库智能化运维、基于容器云平台的微服务改造等主题线上交流
专家简介:
王洪涛,红帽资深解决方案架构师, 15年IT从业经验,超过10年中间件及J2EE项目工作经验。 目前专注于开源领域、JBoss产品系列以及PaaS、DevOps解决方案的售前支持工作。
魏新宇,红帽资深解决方案架构师。 专注开源云计算、容器及自动化运维在金融行业的推广; 拥有 MBA、ITIL V3、Cobit5、C-STAR、TOGAF9.1(鉴定级)等管理认证。 拥有红帽 RHCE/RHCA Level V、VMware VCP-DCV、VCP-DT、VCP-Network、VCP-Cloud、AIX、HPUX 等技术认证。
活动礼品:
一周活动早知道|多云统一管理平台架构、云管平台统一监控、数据库智能化运维、基于容器云平台的微服务改造等主题线上交流
本次活动的奖品是书籍《OpenShift在企业中的实践: PaaS DevOps 微服务》,领取方式请联系社区运营及本场活动负责人: 赵法威(微信号zhaofawei26)
活动资料:
红帽云原生runtime助力容器云平台实现CICD
基于云原生的敏捷集成和流程自动化
Redhat-基于云原生的敏捷集成和流程自动化应用(视频)
Redhat-红帽云原生runtime助力容器云平台实现CICD(视频)

http://www.talkwithtrend.com/activity/?id=1513


电信行业BSS/OSS应用如何基于OpenShift实现容器化改造在线探讨


12月26日

一周活动早知道|多云统一管理平台架构、云管平台统一监控、数据库智能化运维、基于容器云平台的微服务改造等主题线上交流


为满足电信行业大并发及海量数据的数据处理和实时计算需要,需要引入分布式计算、分布式存储、微服务技术、各种开源技术以减少业务支撑成本,但系统复杂性、可靠性、灵活性、资源利用率受到一定影响,因此需要引入一种技术实现资源的弹性伸缩提高资源利用率、故障的自动隔离保障系统稳定性,提升应用标准化发布提高需求上线效率,进而保障系统的稳定性和敏捷性。

电信行业最核心的业务系统BOSS系统,即业务运营支撑系统,包括BSS(营业受理、计费、账务、结算等核心业务系统),OSS(服务开通、激活、资源等核心系统),目前电信行业已经开始了云化改造探索,在传统IOE架构里,开始了去O的进程,同时对业务做了扁平化处理,大量采取了容器的技术,实现了业务的高可用的同时,提升了主机资源的的利用率。

容器是一种轻量级的虚拟化技术,将应用和操作系统封装在一起,它具备资源隔离、性能隔离、故障隔离和网络隔离能力;同时具备快速发布、弹性伸缩和故障自动化恢复的能力,使得复杂的业务支撑微服务架构得以落地生根,进而实现速度快、成本低、资源利用率高、系统稳定等IT支撑目标。

容器化实施难度主要在于三个方面:

1、容器化和微服务化后应用数量增加系统管理难度增大,需要积累和储备技术经验做运维开发保障;

2、电信行业业务连续性要求高,现网系统复杂需要对现网功能和架构充分梳理才能做业务迁移;

3、需要考虑硬件、操作系统、中间件、数据库间的兼容性。

本场活动,社区邀请了红帽的容器专家沈卫忠,为大家分享电信行业BSS/OSS应用基于OpenShift实现容器化改造的解决方案,同时在线为大家答疑解惑。欢迎各位电信行业的朋友报名参与本场活动,可以下载资料参考学习,还可以在线提出您的问题,与专家在线探讨。

支持企业:

专家简介:

沈卫忠   高级方案架构师  

社区ID: WilliamShen

红帽资深解决方案架构师。专注云计算、容器技术、Kurbernetes、微服务和自动化运维技术在电信行业的推广,对Linux操作系统、数据库、中间件、分布式计算和电信行业BSS/OSS应用拥有超过15年的架构设计、项目实施和技术咨询经验,曾在多个知名外企担任项目经理、首席顾问、首席架构师等职。

http://www.talkwithtrend.com/activity/?id=1515


点击阅读原文,查询和参与最新的社区活动

长按二维码关注公众号

以上是关于基于Ruoyi和WebUploader的统一附件管理扩展(下)的主要内容,如果未能解决你的问题,请参考以下文章

上传控件 WebUploader 判断有没有上传成功

Ruoyi微服务开发管理框架

Ruoyi微服务开发管理框架

基于云原生的电信云管理平台的实践与思考

ruoyi前后端分离版学习001--初见

地下综合管廊