1.功能分析
1.1. 查询列表
1.1.1. 页面效果
1.1.2. 功能要求
- 分页查询默认查询10条每页从第1页开始查询
- 附件提供查询,删除操作
- 点击上传按钮弹出上传附件页面
- 搜索条件
- 附件名称:支持模糊搜索
- 点击搜索按钮是按照录入的搜索条件进行查询数据并渲染
- 点击重置按钮的时候清空搜索条件,并重新渲染数据
1.2. 上传附件
1.2.1. 页面效果
1.2.2. 功能要求
- 存储位置必填项
- 上传文件可自行指定类型,如不指定文件类型则可上传所有类型的文件信息
- 开放本地文件上传
- 成功添加数据后列表页进行刷新
1.3. 删除文件
1.3.1. 功能要求
- 点击删除按钮需给出提示框进行二次确认,当二次确认后可进行删除操作
- 成功删除数据后列表页进行刷新
- 删除操作时需同时删除已上传的附件信息
2.功能实现
2.1. 初期准备
2.1.1. 创建数据库 zh_attachment_info
CREATE TABLE `zh_attachment_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`attachment_name` varchar(255) DEFAULT NULL COMMENT '附件名称',
`attachment_url` varchar(255) DEFAULT NULL COMMENT '附件地址',
`attachment_type` varchar(255) DEFAULT NULL COMMENT '附件类型 1.图片 2.音乐 3.视频',
`attachment_size` varchar(255) DEFAULT NULL COMMENT '长度',
`attachment_post` varchar(255) DEFAULT NULL COMMENT '附件位置 local:本地',
`original_name` varchar(255) DEFAULT NULL COMMENT '附件原始名称',
`thumbnail_url` varchar(255) DEFAULT NULL COMMENT '缩略图地址',
`content_type` varchar(255) DEFAULT NULL COMMENT '内容类型',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_user_code` varchar(255) DEFAULT NULL COMMENT '创建人编号',
`create_user_name` varchar(255) DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`update_user_code` varchar(255) DEFAULT NULL COMMENT '修改人编号',
`update_user_name` varchar(255) DEFAULT NULL COMMENT '修改人名称',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='附件表';
2.1.2. 创建控制层AttachmentInfoController
package com.zhuhuo.modual.controller.manager;
import com.zhuhuo.modual.service.AttachmentInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
@Controller
@RequestMapping(value = "/m/attachmentInfo")
public class CategoryController {
@Autowired
private AttachmentInfoService attachmentInfoService;
}
2.1.3. 创建实体映射AttachmentInfo
package com.zhuhuo.modual.entity;
import lombok.Data;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Id;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "zh_attachment_info")
public class AttachmentInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
*
*/
@Id
private Long id;
/**
* 附件名称
*/
private String attachmentName;
/**
* 附件地址
*/
private String attachmentUrl;
/**
* 附件类型 1.图片 2.音乐 3.视频
*/
private String attachmentType;
/**
* 长度
*/
private String attachmentSize;
/**
* 附件位置 local:本地 minio:minio qiniu:七牛云 oss:阿里云 cos:腾讯云 hwyun:华为云
*/
private String attachmentPost;
/**
* 附件原始名称
*/
private String originalName;
/**
* 缩略图地址
*/
private String thumbnailUrl;
/**
* 内容类型
*/
private String contentType;
/**
* 创建时间
*/
private Date createTime;
/**
* 创建人编号
*/
private String createUserCode;
/**
* 创建时间
*/
private String createUserName;
/**
* 修改时间
*/
private Date updateTime;
/**
* 修改人编号
*/
private String updateUserCode;
/**
* 修改人名称
*/
private String updateUserName;
}
2.1.4. 创建AttachmentInfoMapper, AttachmentInfoMapper.xml
package com.zhuhuo.modual.mapper;
import com.zhuhuo.core.frame.mapper.BasicsMapper;
import com.zhuhuo.modual.entity.AttachmentInfo;
public interface AttachmentInfoMapper extends BasicsMapper<AttachmentInfo>{
}
<?xml versinotallow="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.zhuhuo.modual.mapper.AttachmentInfoMapper">
<resultMap id="BaseResultMap" type="com.zhuhuo.modual.entity.AttachmentInfo">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="attachment_name" property="attachmentName" jdbcType="VARCHAR"/>
<result column="attachment_url" property="attachmentUrl" jdbcType="VARCHAR"/>
<result column="attachment_type" property="attachmentType" jdbcType="VARCHAR"/>
<result column="attachment_size" property="attachmentSize" jdbcType="VARCHAR"/>
<result column="attachment_post" property="attachmentPost" jdbcType="VARCHAR"/>
<result column="original_name" property="originalName" jdbcType="VARCHAR"/>
<result column="thumbnail_url" property="thumbnailUrl" jdbcType="VARCHAR"/>
<result column="content_type" property="contentType" jdbcType="VARCHAR"/>
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/>
<result column="create_user_code" property="createUserCode" jdbcType="VARCHAR"/>
<result column="create_user_name" property="createUserName" jdbcType="VARCHAR"/>
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP"/>
<result column="update_user_code" property="updateUserCode" jdbcType="VARCHAR"/>
<result column="update_user_name" property="updateUserName" jdbcType="VARCHAR"/>
</resultMap>
<sql id="base_column_list">
id, attachment_name, attachment_url, attachment_type, attachment_size, attachment_post, original_name,
thumbnail_url, content_type, create_time, create_user_code, create_user_name, update_time,
update_user_code, update_user_name
</sql>
</mapper>
2.1.5. 创建AttachmentInfoService ,AttachmentInfoServiceImpl
package com.zhuhuo.modual.service;
public interface AttachmentInfoService {
}
package com.zhuhuo.modual.service.impl;
import com.zhuhuo.modual.mapper.AttachmentInfoMapper;
import com.zhuhuo.modual.service.AttachmentInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service("attachmentInfoService")
public class AttachmentInfoServiceImpl implements AttachmentInfoService {
@Autowired
private AttachmentInfoMapper attachmentInfoMapper;
}
2.2. 查询附件列表
2.2.1. 静态页面
2.2.1.1. 页面布局
<!DOCTYPE html>
<html lang="en">
<head>
<div th:replace="/manager/common/common :: core-head('附件列表','','')"></div>
<div th:replace="/manager/common/common :: core-css"></div>
<div th:replace="/manager/common/common :: lib-bootstrap-table-css"></div>
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content">
<div class="panel">
<div class="panel-body">
<form role="search-form" class="form-inline" id="search-form">
<div class="form-group">
<label class="control-label">附件名称</label>
<input type="text" placeholder="请输附件名称" id="attachmentName"
name="attachmentName" class="form-control">
</div>
<a class="btn btn-primary" notallow="$.bstable.search()">
<i class="fa fa-search"></i> 搜索
</a>
<a class="btn btn-warning" notallow="$.bstable.reset()">
<i class="fa fa-refresh"></i> 重置
</a>
</form>
</div>
</div>
<div class="panel">
<div class="panel-body">
<div class="btn-group-sm" id="toolbar" role="group">
<a class="btn btn-success" notallow="$.action.addPage()">
<i class="fa fa-plus"></i> 上传
</a>
</div>
<div class="select-table table-striped">
<table id="bootstrap-table-list"></table>
</div>
</div>
</div>
</div>
<div th:replace="/manager/common/common :: core-js"></div>
<div th:replace="/manager/common/common :: lib-bootstrap-table-js"></div>
<script th:src="@{/local/js/zhuhuo.js}"></script>
</body>
</html>
2.2.1.2. 初始化表格js
var options = {
url: "/m/attachmentInfo/findAttachmentInfoList",
modualName: "附件",
columns: [
{
checkbox: true
},
{
field: 'attachmentName',
title: '附件名称'
},
{
field: 'attachmentUrl',
title: '附件地址'
},
{
field: 'attachmentType',
title: '附件类型',
formatter: function (value, item, index) {
if (item.attachmentType == '1') {
return '图片';
} else if (item.attachmentType == '2') {
return '音乐';
} else if(item.attachmentType == '3'){
return '视频';
}else {
return "未知";
}
}
},
{
field: 'attachmentPost',
title: '附件位置',
formatter: function (value, item, index) {
if (item.attachmentPost == 'local') {
return '本地';
} else {
return 'OSS';
}
}
},
{
field: 'thumbnailUrl',
title: '缩略图地址',
formatter: function (value, item, index) {
return '<img src="'+item.thumbnailUrl+'"
alt="'+item.attachmentName+'"
title="'+item.attachmentName+'"
width="80" height="80"/>';
}
},
{
field: 'contentType',
title: '内容类型'
},
{
title: '操作',
align: 'center',
width: '150px',
formatter: function actionFormatter(value, item) {
let btnArr = [];
btnArr.push('<button type="button" class="btn btn-sm btn-secondary"
data-toggle="tooltip" title="查看明细"
notallow="$.action.viewPage(' + item.id + ')">
<i class="fa fa-search"></i></button>');
btnArr.push('<button type="button" class="btn btn-sm btn-secondary"
data-toggle="tooltip" title="删除"
notallow="$.action.remove(' + item.id + ')">
<i class="fa fa-times"></i></button>');
return btnArr.join(" ");
},
}
],
}
$.bstable.init(options)
2.3.1.3. sidebar修改
<li>
<a class="zh-menu-item" th:href="@{/m/attachmentInfo/findAttachmentInfoPage}">
<i class="fa fa-file-o"></i>附件管理
</a>
</li>
2.2.2. 列表功能
2.2.2.1. 创建查询列表页面方法
@ActionLog(modual = "附件管理" ,methodDesc = "查询附件列表页面",source = LogSource.MANAGER,logtype = LogType.VIEW)
@GetMapping(value = "findAttachmentInfoPage")
public String findAttachmentInfoPage(){
return "/manager/attachmentInfo/list";
}
2.2.2.2.创建请求对象和响应对象
2.2.2.2.1. 请求对象 AttachmentInfoListBO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AttachmentInfoListBO {
/**
* 主键id
*/
private Long id;
/**
* 附件名称
*/
private String attachmentName;
/**
* 分页数量
*/
private Integer pageNum;
/**
* 分页条数
*/
private Integer pageSize;
}
2.2.2.2.2. 响应对象 AttachmentInfoListDTO
@Data
@NoArgsConstructor
@AllArgsConstructor
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AttachmentInfoListDTO {
/**
*
*/
private Long id;
/**
* 附件名称
*/
private String attachmentName;
/**
* 附件地址
*/
private String attachmentUrl;
/**
* 附件类型 1.图片 2.音乐 3.视频
*/
private String attachmentType;
/**
* 附件大小
*/
private String attachmentSize;
/**
* 附件位置 local.本地 2.OSS
*/
private String attachmentPost;
/**
* 缩略图地址
*/
private String thumbnailUrl;
/**
* 内容类型
*/
private String contentType;
}
2.2.2.3. 创建查询列表明细方法
2.2.2.3.1. controller
@ActionLog(modual = "附件管理" ,methodDesc = "查询附件列表方法",source = LogSource.MANAGER,logtype = LogType.VIEW)
@ResponseBody
@GetMapping(value = "/findAttachmentInfoList")
public RespJsonPageData<AttachmentInfoListDTO> findAttachmentInfoList(
AttachmentInfoListBO attachmentInfoListBO){
return attachmentInfoService.findAttachmentInfoList(attachmentInfoListBO);
}
2.2.2.3.2. service
RespJsonPageData<AttachmentInfoListDTO> findAttachmentInfoList(AttachmentInfoListBO attachmentInfoListBO);
2.2.2.3.3. serviceImpl
public RespJsonPageData<AttachmentInfoListDTO> findAttachmentInfoList(AttachmentInfoListBO attachmentInfoListBO) {
PageQuery query = new PageQuery(attachmentInfoListBO);
Page<Object> result = PageHelper.startPage(query.getPageNum(), query.getPageSize());
List<AttachmentInfo> attachmentInfoList =
attachmentInfoMapper.findAttachmentInfoList(MapUtils.objToMap(attachmentInfoListBO));
List<AttachmentInfoListDTO> attachmentInfoListDTOList =
JacksonUtil.transformList(JacksonUtil.transformJSONCompact(attachmentInfoList),
AttachmentInfoListDTO.class);
return RespJsonPageData.success(attachmentInfoListDTOList, result.getTotal());
}
2.2.2.3.4. mapper
List<AttachmentInfo> findAttachmentInfoList(Map<String, Object> params);
2.2.2.3.5. xml
<select id="findAttachmentInfoList" parameterType="java.util.Map" resultMap="BaseResultMap">
select
<include refid="base_column_list"/>
from zh_attachment_info
<include refid="search_list_condition"/>
</select>
<sql id="search_list_condition">
<where>
<if test="attachmentName != null and attachmentName != '' ">
and attachment_name like concat('%',#{attachmentName},'%')
</if>
</where>
</sql>
2.2.2.4. 添加本地资源映射
2.2.2.4.1. 修改WebConfiguration添加资源映射
@Autowired
private LocalProperties localProperties;
registry.addResourceHandler(localProperties.getProfile()+"/**")
.addResourceLocations("file:"+localProperties.getLocation()+"/");
2.2.2.5. 测试查询列表
2.3. 上传附件
2.3.1. 静态页面
2.3.1.1. 资源引入
1.引入文件上传插件fileinput的 css和 js文件
2.引入zhfileupload.js (针对fileinput相关方法的封装类似zhuhuo.js,具体封装内容可参考文件上传系列)
2.3.1.2. 修改common.html
<!-- fileinput css-->
<div th:fragment="lib-fileinput-css">
<link rel="stylesheet" href="../../../static/lib/fileinput/css/fileinput.min.css"
th:href="@{/lib/fileinput/css/fileinput.min.css}">
</div>
<!-- fileinput js-->
<div th:fragment="lib-fileinput-js">
<!--
piexif.min.js文件是hMatoba的Piexiffjs插件的来源。
如果使用bootstrap-fileinput插件的图像调整大小功能,则需要在fileinput.min.js之前加载
-->
<script src="../../../static/lib/fileinput/js/plugins/piexif.min.js"
th:src="@{/lib/fileinput/js/plugins/piexif.min.js}"></script>
<!--
sortable.min.js文件是ruvaxa的Sortable插件的来源。
如果希望在初始预览中对缩略图进行排序,则需要在fileinput.min.js之前加载
-->
<script src="../../../static/lib/fileinput/js/plugins/sortable.min.js"
th:src="@{/lib/fileinput/js/plugins/sortable.min.js}"></script>
<!--
purify.min.js文件是curre53的DomPurify插件的来源。
如果您想为HTML内容预览净化HTML,则需要在fileinput.min.js之前加载
-->
<script src="../../../static/lib/fileinput/js/plugins/purify.min.js"
th:src="@{/lib/fileinput/js/plugins/purify.min.js}"></script>
<!-- fileinput js实现 -->
<script src="../../../static/lib/fileinput/js/fileinput.min.js"
th:src="@{/lib/fileinput/js/fileinput.min.js}"></script>
<!-- 主题文件themes/fa/theme.js,用于字体很棒的图标样式 -->
<script src="../../../static/lib/fileinput/themes/fa/theme.min.js"
th:src="@{/lib/fileinput/themes/fa/theme.min.js}"></script>
<!-- 包含区域设置文件<xx>.js,用于翻译展示的语言 -->
<script src="../../../static/lib/fileinput/js/locales/zh.js" th:src="@{/lib/fileinput/js/locales/zh.js}"></script>
</div>
2.3.1.3. 页面布局
<!DOCTYPE html>
<html lang="en">
<head>
<div th:replace="/manager/common/common :: core-head('新增','','')"></div>
<div th:replace="/manager/common/common :: core-css"></div>
<div th:replace="/manager/common/common :: lib-bootstrap-table-css"></div>
<div th:replace="/manager/common/common :: lib-fileinput-css"></div>
</head>
<body class="gray-bg">
<div class="wrapper wrapper-content">
<div class="panel">
<div class="panel-body">
<form class="form-horizontal m" id="add-form" enctype="multipart/form-data"
style="padding-left: 20px;padding-right: 20px">
<!-- 导航名称 -->
<div class="form-group">
<div class="col-md-12">
<div class="input-group m-b">
<span class="input-group-addon">存储位置</span>
<select class="form-control m-b" name="attachmentPost" id="attachmentPost">
<option value="local">本地</option>
<option value="minio">minio</option>
<option value="qiniu">七牛云</option>
<option value="cos">腾讯云</option>
<option value="oss">阿里云</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-12">
<input id="bootstrap-file-upload" type="file" multiple>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div th:replace="/manager/common/common :: core-js"></div>
<div th:replace="/manager/common/common :: lib-bootstrap-table-js"></div>
<div th:replace="/manager/common/common :: lib-jquery-validate-js"></div>
<div th:replace="/manager/common/common :: lib-fileinput-js"></div>
<script th:src="@{/local/js/zhfileupload.js}"></script>
<script th:src="@{/local/js/zhuhuo.js}"></script>
</body>
</html>
2.3.1.4. 初始化文件上传js
let options = {
uploadUrl:'/m/attachmentInfo/addAttachmentInfo',
allowedFileExtensions : ['jpg', 'png','gif'],//接收的文件后缀,
maxFileCount: 100,
enctype: 'multipart/form-data',
showUpload: false, //是否显示上传按钮 上传操作通过layer的确认按钮进行操作
showCaption: false,//是否显示标题
showCancel: false, //是否显示文件上传取消按钮 只有在AJAX上传过程中,才会启用和显示
browseClass: "btn btn-primary", //按钮样式
previewFileIcon: "<i class='glyphicon glyphicon-king'></i>",
fileActionSettings:{
showUpload:false,//是否在缩略图中显示上传按钮
showRemove:true,//是否在缩略图中显示删除按钮
}
}
$.fileuploads.init(options)
2.3.1.5. 表单提交js
function submitHandler(index){
if($("#add-form").validate().form()) {
var files = $("#bootstrap-file-upload").prop("files")
var attachmentPost = $("#attachmentPost").val();
var formData = new FormData();
formData.append('attachmentPost',attachmentPost)
for(var i = 0 ; i< files.length;i++){
formData.append('files',files[i])
}
let url = '/m/attachmentInfo/addAttachmentInfo';
$.action.postFile(url,formData)
}
}
2.3.2. 文件上传功能
2.3.2.1. 创建上传请求对象UploadFileRequest
@Data
public class UploadFileRequest {
/**
* 上传文件参数 不同的云存储上传方式不同,有byte[],有inputStream的因此在上传文件的时候直接传递原始文件
* 在不同的上传提供方处进行转换上传
*/
private MultipartFile uploadFile;
/**
* 上传云存储空间类型
*/
private String uploadType;
/**
* 允许上传的文件类型 如果客户端未传递则默认使用系统自带的范围
*/
private List<String> allowUploadExtension;
}
2.3.2.2. 创建上传响应对象UploadFileResponse
@Data
public class UploadFileResponse {
/**
* 上传结果 true 成功 false失败
*/
private Boolean uploadResult;
/**
* 上传失败原因 只有当uploadResult为失败的时候必填
*/
private String errorMessage;
/**
* 文件名称 全称+后缀
*/
private String fileName;
/**
* 文件后缀
*/
private String fileSuffix;
/**
* 文件存储路径
*/
private String fileStorgePath;
/**
* 文件类型
*/
private String fileType;
/**
* 文件大小
*/
private String fileSize;
/**
* 文件缩略图地址
*/
private String thumbnailUrl;
}
2.3.2.3. 创建上传规则类型ObjectStorageRules
@Getter
@AllArgsConstructor
public enum ObjectStorageRules {
/**
* 本地存储
*/
STORAGE_LOCAL("local","本地"),
;
/**
* 对象存储标识
*/
private String code;
/**
* 对象存储描述
*/
private String desc;
public static ObjectStorageRules of(String code){
Objects.requireNonNull(code);
return Stream.of(values())
.filter(bean -> bean.code.equals(code))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(code +"not exists"));
}
}
2.3.2.4. 创建上传执行接口ObjectStorageRulesExecutor
public interface ObjectStorageRulesExecutor {
/**
* 规则类型标记
* @return {@link ObjectStorageRules}
*/
ObjectStorageRules ruleConfig();
/**
* 上传文件
* @return
*/
UploadFileResponse upload(UploadFileRequest uploadFile) throws IOException;
}
2.3.2.5. 创建上传抽象类AbstractObjectStorageExecutor
public abstract class AbstractObjectStorageExecutor {
public final String[] video = {".mp4",".avi",".wmv",".mpg",".mpeg",".mov",".rm",".ram",".swf",".flv"};
public final String[] images = {".png",".jpg",".jpeg",".bmp",".gif"};
public final String[] documents ={".doc",".docx",".ppt",".pptx",".xls",".txt",".vsd",".pdf",
".rtf",".wps",".wpt",".xml",".json",".html"};
private String converFileType(String suffix){
//视频格式
if(Arrays.asList(video).contains(suffix)){
return "video";
}
//图片格式
if(Arrays.asList(images).contains(suffix)){
return "images";
}
//文本格式
if(Arrays.asList(documents).contains(suffix)){
return "documents";
}
return "undefined";
}
/**
* 根据文件后缀名 获取文件类型
* @param suffix
* @return
*/
public String converContentType(String suffix){
if(".bmp".equalsIgnoreCase(suffix)) {
return "image/bmp";
}
if(".gif".equalsIgnoreCase(suffix)) {
return "image/gif";
}
if(".jpeg".equalsIgnoreCase(suffix) || ".jpg".equalsIgnoreCase(suffix)
|| ".png".equalsIgnoreCase(suffix) ) {
return "image/jpeg";
}
if(".html".equalsIgnoreCase(suffix)) {
return "text/html";
}
if(".txt".equalsIgnoreCase(suffix)) {
return "text/plain";
}
if(".vsd".equalsIgnoreCase(suffix)) {
return "application/vnd.visio";
}
if(".ppt".equalsIgnoreCase(suffix) || "pptx".equalsIgnoreCase(suffix)) {
return "application/vnd.ms-powerpoint";
}
if(".doc".equalsIgnoreCase(suffix) || "docx".equalsIgnoreCase(suffix)) {
return "application/msword";
}
if(".xml".equalsIgnoreCase(suffix)) {
return "text/xml";
}
//如果为空 依旧想展示对应的则可以在获取返回结果后自行拓展
return null;
}
/**
* 转换文件大小
* @param size
* @return
*/
public String converFileSize(long size){
String[] units = new String[]{"B","KB","MB","GB","TB","PB","EB","ZB","YB"};
int unit = 1024;
int exp = (int)(Math.log(size)/Math.log(unit));
double pre = 0 ;
if(size > 1024){
pre = size / Math.pow(unit,exp);
}else {
pre = (double)size / (double)unit;
}
return String.format(Locale.ENGLISH,"%.1f%s",pre,units[(int)exp]);
}
}
2.3.2.6. 创建上传执行管理器ObjectStorageExecuteManager
@Slf4j
@Component("objectStorageExecuteManager")
public class ObjectStorageExecuteManager implements BeanPostProcessor {
/**
* 定义规则执行器映射
*/
private Map<ObjectStorageRules, ObjectStorageRulesExecutor> executorIndex = new HashMap<>(ObjectStorageRules.values().length);
/**
* 文件上传策略
* @param uploadFileRequest 要上传的文件信息
* @return
*/
public UploadFileResponse uploadStrategy(UploadFileRequest uploadFileRequest) throws IOException {
UploadFileResponse uploadFileResponse;
ObjectStorageRules ossRuleFlag = ObjectStorageRules.of(uploadFileRequest.getUploadType());
switch (ossRuleFlag){
case STORAGE_LOCAL:
uploadFileResponse =
executorIndex.get(ObjectStorageRules.STORAGE_LOCAL).upload(uploadFileRequest);
break;
default:
uploadFileResponse =
executorIndex.get(ObjectStorageRules.STORAGE_LOCAL).upload(uploadFileRequest);
break;
}
return uploadFileResponse;
}
/**
* 在bean初始化之前进行执行(before)
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Nullable
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//如果当前bean不是ObjectStorageRulesExecutor的实现则直接返回bean
if(!(bean instanceof ObjectStorageRulesExecutor)){
return bean;
}
//把bean强转为RuleExecutor
ObjectStorageRulesExecutor objectStorageRulesExecutor = (ObjectStorageRulesExecutor) bean;
ObjectStorageRules objectStorageRules = objectStorageRulesExecutor.ruleConfig();
//如果ruleFlag已经注册了则抛出异常
if(executorIndex.containsKey(objectStorageRules)){
throw new IllegalStateException("There is already an executor fro rule flag" + objectStorageRules);
}
log.info("Load executor {} for rule flag {}",objectStorageRulesExecutor.getClass(),objectStorageRules);
executorIndex.put(objectStorageRules,objectStorageRulesExecutor);
return null;
}
/**
* 在bean初始化之后进行执行(after)
* @param bean
* @param beanName
* @return
* @throws BeansException
*/
@Nullable
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
2.3.2.7. 创建本地上传配置LocalProperties
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LocalProperties {
/**
* 前缀 cy.storage 是 文件存储通用前缀
*/
public static final String PREFIX = "cy.storage.local";
/**
* 本地存储是否启用 true 为启用 false 为禁用
*/
private Boolean enabled;
/**
* 存储类别 本地为 local
*/
private String storageType;
/**
* 域名信息 本地一般为 http://localhost:8080 如果放到生产可配置生产域名
*/
private String domain;
/**
* 文件存放地址
*/
private String location;
/**
* 资源映射路径 前缀 用于项目中使用
*/
private String profile;
}
2.3.2.8. 创建本地上传实现LocalStorage
public class LocalStorage extends AbstractObjectStorageExecutor implements ObjectStorageRulesExecutor {
private final LocalProperties localProperties;
public LocalStorage(LocalProperties localProperties) {
this.localProperties = localProperties;
}
/**
* 规则类型标记
* @return {@link ObjectStorageRules}
*/
@Override
public ObjectStorageRules ruleConfig() {
return ObjectStorageRules.STORAGE_LOCAL;
}
/**
* 文件上传操作
* @param uploadFile 要上传的文件
* @return
*/
@Override
public UploadFileResponse upload(UploadFileRequest uploadFile) throws IOException {
//获取文件后缀名
String suffix = uploadFile.getUploadFile().getOriginalFilename()
.substring(uploadFile.getUploadFile().getOriginalFilename().lastIndexOf("."));
//获取文件原始名称
String name = uploadFile.getUploadFile().getOriginalFilename()
.substring(0,uploadFile.getUploadFile().getOriginalFilename().lastIndexOf("."));
//组装文件名称
String fileName = name +suffix;
String absPath = getAbsoluteFile(fileName).getAbsolutePath();
uploadFile.getUploadFile().transferTo(Paths.get(absPath));
int dirLastIndex = localProperties.getLocation().length() + 1;
String currenDir = StringUtil.substring(absPath,dirLastIndex);
String pathFileName = localProperties.getProfile()+"/"+currenDir;
String resultUrl = localProperties.getDomain() + pathFileName;
UploadFileResponse uploadFileResponse = new UploadFileResponse();
uploadFileResponse.setFileName(fileName);
uploadFileResponse.setFileSuffix(suffix);
uploadFileResponse.setFileStorgePath(currenDir);
uploadFileResponse.setFileSize(converFileSize(uploadFile.getUploadFile().getSize()));
uploadFileResponse.setFileType(converFileType(suffix));
uploadFileResponse.setThumbnailUrl(resultUrl);
uploadFileResponse.setUploadResult(true);
return uploadFileResponse;
}
private File getAbsoluteFile(String fileName) {
String basiDir = localProperties.getLocation()+File.separator+
DateUtil.BasicDateData.stringDateRule(DateUtil.Format.YEAR_MONTH_DAY_UN);
File directory=new File(basiDir+File.separator+fileName);
if (!directory.exists()){
if(!directory.getParentFile().exists()){
directory.getParentFile().mkdirs();
}
}
return directory;
}
}
2.3.3. 新增功能
2.3.3.1. 创建上传页面方法
2.3.3.1.1. controller
@ActionLog(modual = "附件管理" ,methodDesc = "上传附件页面",source = LogSource.MANAGER,logtype = LogType.INSERT)
@GetMapping(value = "/addAttachmentInfoPage")
public String addAttachmentInfoPage(){
return "/manager/attachmentInfo/add";
}
@ActionLog(modual = "附件管理" ,methodDesc = "上传附件方法",source = LogSource.MANAGER,logtype = LogType.INSERT)
@ResponseBody
@PostMapping(value = "/addAttachmentInfo")
public RespJsonData<String> addAttachmentInfo(MultipartFile[] files,String attachmentPost) throws IOException {
return attachmentInfoService.addAttachmentInfo(files,attachmentPost);
}
2.3.3.1.2. service,serviceImpl
RespJsonData<String> addAttachmentInfo(MultipartFile[] files, String attachmentPost) throws IOException;
@Override
public RespJsonData<String> addAttachmentInfo(MultipartFile[] files, String attachmentPost) throws IOException {
//返回给客户端的
List<String> filePath = new ArrayList<String>();
for(MultipartFile file:files){
//组装要上传的文件信息
UploadFileRequest uploadFileRequest = new UploadFileRequest();
uploadFileRequest.setUploadFile(file);
uploadFileRequest.setUploadType(attachmentPost);
//获取上传文件响应结果
UploadFileResponse uploadFileResponse = objectStorageExecuteManager.uploadStrategy(uploadFileRequest);
if(uploadFileResponse.getUploadResult()){
//上传成功后保存文件信息
AttachmentInfo attachmentInfo = new AttachmentInfo();
attachmentInfo.setAttachmentPost(attachmentPost);
attachmentInfo.setAttachmentUrl(uploadFileResponse.getFileStorgePath());
attachmentInfo.setAttachmentName(uploadFileResponse.getFileName());
attachmentInfo.setAttachmentSize(uploadFileResponse.getFileSize());
attachmentInfo.setAttachmentType(uploadFileResponse.getFileType());
attachmentInfo.setContentType(uploadFileResponse.getFileType());
attachmentInfo.setThumbnailUrl(uploadFileResponse.getThumbnailUrl());
attachmentInfoMapper.insertSelective(attachmentInfo);
//上传成功后拼装显示路径信息
filePath.add(uploadFileResponse.getThumbnailUrl());
}else {
return RespJsonData.fail(uploadFileResponse.getErrorMessage(),
BaseResponseCode.FAIL.getResponseCode(),uploadFileResponse.getErrorMessage());
}
}
String resultFilePathList = String.join(",",filePath);
return RespJsonData.success(resultFilePathList);
}
2.3.3. 添加配置文件
cy:
storage:
local:
# 是否启用本地上传
enabled: true
# 本地上传存储标识
storageType: local
# 本地上传域名 如果有则填写映射 如果没有 默认获取当前服务的ip+port
domain: http://localhost:8080
# 本地上传存储位置 文件实际存储位置
location: /Users/jiuling/Documents/postmanfile/upload
#资源映射路径 前缀 用于项目中使用
profile: /profile
2.3.4. postmant测试上传
2.4. 删除文件
2.4.1. 文件删除功能
2.4.1.1. 创建删除请求对象RemoveFileRequest
@Data
public class RemoveFileRequest {
/**
* 存储类别
*/
private String storageType;
}
2.4.1.2. 创建删除响应对象RemoveFileResponse
@Data
public class RemoveFileResponse {
/**
* 移除结果
*/
private Boolean removeResult;
}
2.4.1.3. 创建删除执行接口remove
RemoveFileResponse remove(RemoveFileRequest removeFileRequest);
2.4.1.4. 在ObjectStorageExecuteManager中添加移除策略
public RemoveFileResponse removeStrategy(RemoveFileRequest removeFileRequest) throws IOException {
RemoveFileResponse removeFileResponse;
ObjectStorageRules objectStorageRules = ObjectStorageRules.of(removeFileRequest.getStorageType());
switch (objectStorageRules){
case STORAGE_LOCAL:
removeFileResponse = executorIndex.get(ObjectStorageRules.STORAGE_LOCAL).remove(removeFileRequest);
break;
default:
removeFileResponse = executorIndex.get(ObjectStorageRules.STORAGE_LOCAL).remove(removeFileRequest);
break;
}
return removeFileResponse;
}
2.4.1.5. 在LocalStorage中添加移除方法
@Override
public RemoveFileResponse remove(RemoveFileRequest removeFileRequest) {
String deleteFile = localProperties.getLocation()+File.separator+removeFileRequest.getFileUrl();
File file = new File(deleteFile);
RemoveFileResponse removeFileResponse = new RemoveFileResponse();
if(file.exists()){
if(file.delete()){
removeFileResponse.setRemoveResult(true);
}else {
removeFileResponse.setRemoveResult(false);
removeFileResponse.setErrorMessage("文件删除失败");
}
}else {
removeFileResponse.setRemoveResult(false);
removeFileResponse.setErrorMessage("文件不存在");
}
return removeFileResponse;
}
2.4.2. 删除功能
2.4.2.1. 创建删除方法
2.4.2.1.1. controller
@ActionLog(modual = "附件管理" ,methodDesc = "删除附件",source = LogSource.MANAGER,logtype = LogType.DELETE)
@ResponseBody
@PostMapping(value = "/deleteAttachmentInfo")
public RespJson deleteAttachmentInfo(@RequestBody AttachmentInfoBO attachmentInfoBO) throws IOException {
return attachmentInfoService.deleteAttachmentInfo(attachmentInfoBO);
}
2.4.2.1.2. service,serviceImpl
RespJson deleteAttachmentInfo(AttachmentInfoBO attachmentInfoBO) throws IOException;
@Override
public RespJson deleteAttachmentInfo(AttachmentInfoBO attachmentInfoBO) throws IOException {
if (ObjectUtil.isEmpty(attachmentInfoBO.getId())) {
return RespJson.fail(BizResponseCode.ATTACHMENT_ID_NOT_EXIST.getResponseCode(),
BizResponseCode.ATTACHMENT_ID_NOT_EXIST.getResponseMessage());
}
//获取附件基础信息
AttachmentInfo attachmentInfo = attachmentInfoMapper.selectByPrimaryKey(attachmentInfoBO.getId());
RemoveFileRequest removeFileRequest = new RemoveFileRequest();
removeFileRequest.setStorageType(attachmentInfo.getAttachmentPost());
removeFileRequest.setFileUrl(attachmentInfo.getAttachmentUrl());
removeFileRequest.setFileName(attachmentInfo.getAttachmentName());
RemoveFileResponse removeFileResponse = objectStorageExecuteManager.removeStrategy(removeFileRequest);
if(removeFileResponse.getRemoveResult()){
attachmentInfoMapper.delete(attachmentInfo);
//返回结果
return RespJson.success();
}else {
return RespJson.fail(BaseResponseCode.FAIL.getResponseCode(),removeFileResponse.getErrorMessage());
}
}
2.4.2.1.3. BizResponseCode
ATTACHMENT_ID_NOT_EXIST("102006","附件id不存在,请检查"),