上传控件使用了FastDfs作为文件存储服务器来进行存储,在使用上传控件时需对上传空间进行配置。
以下介绍几个概念
下面是FastDfs简单配置流程说明。
FastDFS的分组信息与服务端信息可在数据库中进行配置维护,或直接使用代码将配置信息写入,下面介绍使用数据库配置方式来进行配置。
现有以下二张表:
FAST_DFS_GROUP与TRACKER_SERVER
内容如下:
FAST_DFS_GROUP:
列名 | 字段说明 |
---|---|
id | 主键 |
group_name | 分组名称 |
connect_timeout | 连接超时时间 |
network_timout | 网络超时时间 |
charset | 编码 |
tracker_http_address | 通过nginx访问的IP地址 |
anti_streal_token | 暂时不需要配置 |
tracker_http_port | 通过nginx访问的端口号 |
secret_key | 密钥 |
TRACKER_SERVER
列名 | 字段说明 |
---|---|
id | 主键 |
group_id | 分组id,一个分组里可能会有多个tracker |
tracker_server | FastDFS服务器地址 |
文件服务器的信息应在服务器启动时,自动载入。以下给出一个简单载入实例:
1.在Spring中注入文件组容器
<bean name="clientGroupContainer" class="com.gillion.fdfs.group.ClientGroupContainer"></bean
2.编写初始化类FastDfsGroupService与FastDfsGroupServiceImpl
FastDfsGroupService:
package com.gillion.sebusiness.demo.service.upload;
import com.gfa4j.mybatis.service.BaseService;
/**
* Created by wengms on 2015/7/30.
*/
public interface FastDfsGroupService extends BaseService{
}
FastDfsGroupServiceImpl
package com.gillion.sebusiness.demo.service.upload.impl;
import com.gfa4j.mybatis.mapper.BaseMapper;
import com.gfa4j.mybatis.service.impl.BaseServiceImpl;
import com.gillion.fdfs.client.GroupFastDFSClient;
import com.gillion.fdfs.group.ClientGroup;
import com.gillion.fdfs.group.ClientGroupContainer;
import com.gillion.sebusiness.demo.service.upload.FastDfsGroupService;
import com.gillion.sebusiness.upload.mapper.FastDfsGroupMapper;
import com.gillion.sebusiness.upload.mapper.TrackerServerMapper;
import com.gillion.sebusiness.upload.model.FastDfsGroup;
import com.gillion.sebusiness.upload.model.TrackerServer;
import com.gillion.sebusiness.upload.model.TrackerServerExample;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Nullable;
import java.awt.*;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Collectors;
/**
* Created by wengms on 2015/7/30.
*/
@Service
public class FastDfsGroupServiceImpl extends BaseServiceImpl implements FastDfsGroupService,InitializingBean{
@Autowired
private FastDfsGroupMapper fastDfsGroupMapper;
@Autowired
private TrackerServerMapper trackerServerMapper;
@Autowired
private ClientGroupContainer clientGroupContainer;
@Override
public BaseMapper getMapper() {
return fastDfsGroupMapper;
}
@Override
public void afterPropertiesSet() throws Exception {
List<FastDfsGroup> groups = this.selectByExample(null);
groups.stream().forEach(input -> {
ClientGroup clientGroup = new ClientGroup();
clientGroup.setAntiStrealToken(input.getAntiStrealToken());
clientGroup.setCharset(input.getCharset());
clientGroup.setConnectTimeout(input.getConnectTimeout());
clientGroup.setGroupName(input.getGroupName());
clientGroup.setNetworkTimeout(input.getNetworkTimeout());
clientGroup.setSecretKey(input.getSecretKey());
clientGroup.setTrackerHttpAddress(input.getTrackerHttpAddress());
clientGroup.setTrackerHttpPort(input.getTrackerHttpPort());
TrackerServerExample trackerServerExample = new TrackerServerExample();
trackerServerExample.createCriteria().andGroupIdEqualTo(input.getId());
List<TrackerServer> trackerServers = trackerServerMapper.selectByExample(trackerServerExample);
List<String> trackerAddress = trackerServers.stream().map(trackerServer->trackerServer.getTrackerServer()).collect(Collectors.toList());
clientGroup.setTrackerServerAddresses(trackerAddress);
GroupFastDFSClient client = new GroupFastDFSClient();
client.initGroup(clientGroup);
clientGroupContainer.addGroup(clientGroup);
});
}
}
按照以上的配置,就完成了FastDFS组的初始化。
上传控件的使用流程是通过发送一个key到服务器端获取上传控件的类型限制,大小限制等配置信息等,主要包括以下功能配置:
下面给个示例表:
UPLOAD_INFO
字段名 | 说明 |
---|---|
id | 主键,也作为key,在前端配置时使用 |
upload_type | 上传类型 |
limit_type | 限制类型 |
limit_count | 限制数量 |
limit_size | 限制大小 |
dfs_group | 文件服务器分组名 |
compress_strategy | 压缩策略 |
delete_strategy | 删除策略 |
delete_sexpression | 删除引用表达式 |
现在给出示例配置信息如下所示:
{
"id":"1",
"upload_type":"1",
"limit_type":"jpg,gif,jpef,png,xls,xlsx",
"limit_count":"3",
"limit_size":"1000",
"dfs_group":"word",
"compress_strategy":{
"largeThumbnail":{
"width":"500",
"height":"500"
},
"smallThumbnail":{
"width":"150",
"height":"150"
}
},
"delete_strategy":"",
"delete_expression":"@testBusinessService.delete([fileInfo][id],[id])"
}
除配置表外,还需要两张表配置文件的上传信息以及文件的FastDFS路径信息,示例表如下:
DFS_FILE:(此处命名有问题,请酌情根据自己需求命名)
用于保存文件的基本信息,如文件名,文件在nginx上的直接访问路径等
字段名 | 说明 |
---|---|
id | 主键 |
file_name | 上传文件名 |
orginal_path | 源文件在FastDFS上的直接访问路径 |
large_thumb_path | 大缩略图在FastDFS上的访问路径 |
small_thumb_path | 小缩略图在FastDFS上的访问路径 |
FAST_DFS_FILE:
用于保存文件在FastDFS上的实际路径信息
字段名 | 说明 |
---|---|
id | 主键 |
group_server_name | 文件服务器组名 |
file_name | 文件名称 |
file_id | 文件ID,对应DFS_FILE的主键,如果是图片,一个file_id可能对于多个Fast_DFS_FILE记录 |
group_name | 文件服务器分配的组名称 |
path | 文件存储路径,下载文件时,需与group_name结合获取文件内容 |
文件控件后台提供一个访问接口,以供前台进行获取文件上传配置信息,文件上传操作,删除操作等。
其中需要在Spring里将UploadController注入
同时注入删除器Deleter
<bean name="deleter" class="com.gillion.upload.strategy.delete.Deleter"/>
在dispatcher-servlet.xml中引入上传文件配置
<import resource="classpath*:spring-upload.xml"/>
各个业务系统需根据自己的需求,实现UploadService接口,接口内容主要包括:
以下给个示例实现,请根据各自不同的表设计进行修改:
package com.gillion.sebusiness.demo.service.upload.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gfa4j.mybatis.mapper.BaseMapper;
import com.gfa4j.mybatis.service.impl.BaseServiceImpl;
import com.gillion.fdfs.entity.DFSFileInfo;
import com.gillion.sebusiness.demo.service.upload.DfsFileService;
import com.gillion.sebusiness.upload.mapper.DfsFileMapper;
import com.gillion.sebusiness.upload.mapper.FastDfsFileMapper;
import com.gillion.sebusiness.upload.model.DfsFile;
import com.gillion.sebusiness.upload.model.FastDfsFile;
import com.gillion.sebusiness.upload.model.FastDfsFileExample;
import com.gillion.upload.entity.*;
import com.gillion.upload.service.UploadService;
import com.gillion.upload.strategy.compress.CompressStrategy;
import com.gillion.upload.strategy.delete.DeleteStrategy;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Created by wengms on 2015/7/29.
*/
@Service
public class UploadServiceImpl extends BaseServiceImpl implements UploadService, DfsFileService, InitializingBean {
@Autowired
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
@Autowired
private DfsFileMapper dfsFileMapper;
@Autowired
private FastDfsFileMapper fastDfsFileMapper;
@Override
public UploadBaseInfo getUploadBaseInfo(String key) {
return jdbcTemplate.queryForObject("SELECT DFS_GROUP,UPLOAD_TYPE,LIMIT_TYPE,LIMIT_COUNT,LIMIT_SIZE FROM UPLOAD_INFO WHERE ID = ?", new RowMapper<UploadBaseInfo>() {
@Override
public UploadBaseInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
UploadBaseInfo uploadBaseInfo = new UploadBaseInfo();
uploadBaseInfo.setDfsGroup(rs.getString(1));
uploadBaseInfo.setUploadType(rs.getInt(2));
String limitTypeStr = rs.getString(3);
if (!StringUtils.isEmpty(limitTypeStr)) {
uploadBaseInfo.setLimitType(Arrays.asList(limitTypeStr.split(",")));
}
uploadBaseInfo.setLimitCount(rs.getInt(4));
uploadBaseInfo.setLimitSize(rs.getBigDecimal(5));
return uploadBaseInfo;
}
}, key);
}
@Override
public BaseMapper getMapper() {
return dfsFileMapper;
}
@Override
public Object saveUploadFile(UploadedFile uploadedFile,DFSFileInfo dfsFileInfo) {
DfsFile dfsFile = new DfsFile();
dfsFile.setFileName(uploadedFile.getFileName());
dfsFile.setOrginalPath(uploadedFile.getOriginalPath());
dfsFile = this.save(dfsFile);
saveFile(dfsFile.getId(),dfsFileInfo);
return dfsFile;
}
@Override
public Object saveUploadImage(UploadedImageFile uploadedImageFile,ImageDFSFileInfo imageDFSFileInfos) {
DfsFile dfsFile = new DfsFile();
dfsFile.setFileName(uploadedImageFile.getFileName());
dfsFile.setLargeThumbPath(uploadedImageFile.getLargeThumbnailPath());
dfsFile.setOrginalPath(uploadedImageFile.getOriginalPath());
dfsFile.setSmallThumbPath(uploadedImageFile.getSmallThumbnailPath());
dfsFile = this.save(dfsFile);
/*对FastDFS信息进行存储*/
//1.存储原图
saveFile(dfsFile.getId(), imageDFSFileInfos.getOriginalImageFileInfo());
//2.存储大缩略图
saveFile(dfsFile.getId(), imageDFSFileInfos.getLargeThumbnailFileInfo());
//3.存储小缩略图
saveFile(dfsFile.getId(), imageDFSFileInfos.getSmallThumbnailFileInfo());
return dfsFile;
}
private void saveFile(String fileId,DFSFileInfo dfsFileInfo){
if (dfsFileInfo != null){
FastDfsFile file = new FastDfsFile();
file.setFileid(fileId);
file.setGroupName(dfsFileInfo.getGroup());
file.setPath(dfsFileInfo.getPath());
file.setGroupServerName(dfsFileInfo.getGroupServer());
file.setFileName(dfsFileInfo.getFileName());
file.setId(UUID.randomUUID().toString());
fastDfsFileMapper.insert(file);
}
}
@Override
public UploadInfo getUploadInfo(String key) {
return jdbcTemplate.queryForObject("SELECT ID,DFS_GROUP,UPLOAD_TYPE,LIMIT_TYPE,LIMIT_COUNT,LIMIT_SIZE,DELETE_STRATEGY,DELETE_EXPRESSION,COMPRESS_STRATEGY FROM UPLOAD_INFO WHERE ID = ?", new RowMapper<UploadInfo>() {
@Override
public UploadInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
UploadInfo uploadInfo = null;
if (StringUtils.equals("0",rs.getString(3))){
uploadInfo = new UploadInfo();
}else{
uploadInfo = new ImageUploadInfo();
}
uploadInfo.setKey(rs.getString(1));
uploadInfo.setDfsGroup(rs.getString(2));
uploadInfo.setUploadType(rs.getInt(3));
String limitTypeStr = rs.getString(4);
if (!StringUtils.isEmpty(limitTypeStr)) {
uploadInfo.setLimitType(Arrays.asList(limitTypeStr.split(",")));
}
uploadInfo.setLimitCount(rs.getInt(5));
uploadInfo.setLimitSize(rs.getBigDecimal(6));
String deleteStrategy = rs.getString(7);
if (StringUtils.equals("1",deleteStrategy)){
uploadInfo.setDeleteStrategy(DeleteStrategy.PHYSIC_DELETE);
}else{
uploadInfo.setDeleteStrategy(DeleteStrategy.LOGIC_DELETE);
}
uploadInfo.setDeleteExpression(rs.getString(8));
if (StringUtils.equals("0",rs.getString(3))){
return uploadInfo;
}else{
ImageUploadInfo imageUploadInfo = (ImageUploadInfo) uploadInfo;
//解析压缩策略
ObjectMapper objectMapper = new ObjectMapper();
CompressStrategy compressStrategy =null;
try {
compressStrategy = objectMapper.readValue(rs.getString(9),objectMapper.constructType(CompressStrategy.class));
} catch (IOException e) {
e.printStackTrace();
}
imageUploadInfo.setCompressStrategy(compressStrategy);
return imageUploadInfo;
}
}
}, key);
}
@Override
public boolean deleteById(String fileId) {
//删除文件ID下所有相关文件数据
FastDfsFileExample example = new FastDfsFileExample();
example.createCriteria().andFileidEqualTo(fileId);
fastDfsFileMapper.deleteByExample(example);
//删除文件数据
dfsFileMapper.deleteByPrimaryKey(fileId);
return true;
}
@Override
public List<DFSFileInfo> getDfsFileInfo(String fileId) {
FastDfsFileExample example = new FastDfsFileExample();
example.createCriteria().andFileidEqualTo(fileId);
List<FastDfsFile> fastDfsFiles = fastDfsFileMapper.selectByExample(example);
return fastDfsFiles.stream().map(fastDfsFile->{
DFSFileInfo dfsFileInfo = new DFSFileInfo();
dfsFileInfo.setGroup(fastDfsFile.getGroupName());
dfsFileInfo.setPath(fastDfsFile.getPath());
dfsFileInfo.setFileName(fastDfsFile.getFileName());
dfsFileInfo.setGroupServer(fastDfsFile.getGroupServerName());
return dfsFileInfo;
}).collect(Collectors.toList());
}
@Override
public void afterPropertiesSet() throws Exception {
this.jdbcTemplate = new JdbcTemplate(this.dataSource);
}
}
现有示例业务表如下:
TEST_BUSINESS:
列名 | 含义 |
---|---|
id | 主键 |
business_name | 业务名称 |
TEST_BUSINESS_FILE (业务与文件关系表):
列名 | 含义 |
---|---|
id | 主键 |
BUSINESS_ID | 业务ID |
FILE_ID | 文件存储ID,对应DFS_FILE表的主键 |
编写业务操作Service与Controller,此处略去接口编写,自己根据业务需要添加
TestBusinessServiceImpl.java
package com.gillion.sebusiness.demo.service.upload.impl;
import com.gfa4j.mybatis.mapper.BaseMapper;
import com.gfa4j.mybatis.service.impl.BaseServiceImpl;
import com.gfa4j.utils.ResultUtils;
import com.gillion.fdfs.group.ClientGroupContainer;
import com.gillion.sebusiness.demo.service.upload.TestBusinessService;
import com.gillion.sebusiness.demo.vo.upload.TestBusinessVO;
import com.gillion.sebusiness.upload.mapper.*;
import com.gillion.sebusiness.upload.model.DfsFile;
import com.gillion.sebusiness.upload.model.TestBusiness;
import com.gillion.sebusiness.upload.model.TestBusinessFile;
import com.gillion.sebusiness.upload.model.TestBusinessFileExample;
import com.gillion.upload.service.UploadService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Created by wengms on 2015/8/6.
*/
@Service("testBusinessService")
public class TestBusinessServiceImpl extends BaseServiceImpl implements TestBusinessService {
@Autowired
private TestBusinessMapper testBusinessMapper;
@Autowired
private TestBusinessFileMapper testBusinessFileMapper;
@Autowired
private DfsFileMapper dfsFileMapper;
@Autowired
private UploadService uploadService;
@Autowired
private UploadInfoMapper uploadInfoMapper;
@Autowired
private FastDfsFileMapper fastDfsFileMapper;
@Autowired
private ClientGroupContainer clientGroupContainer;
@Override
public BaseMapper getMapper() {
return testBusinessMapper;
}
@Override
public TestBusinessVO save(TestBusinessVO testBusinessVO) {
TestBusiness testBusiness = new TestBusiness();
BeanUtils.copyProperties(testBusinessVO, testBusiness);
List<DfsFile> dfsFiles = testBusinessVO.getDfsFiles();
List<String> fileIds = dfsFiles.stream().map((dfsFile) -> dfsFile.getId()).collect(Collectors.toList());
testBusiness = this.save(testBusiness);
final String businessId = testBusiness.getId();
fileIds.forEach(fileId -> {
TestBusinessFile testBusinessFile = new TestBusinessFile();
testBusinessFile.setFileId(fileId);
testBusinessFile.setBusinessId(businessId);
testBusinessFile.setId(UUID.randomUUID().toString());
this.testBusinessFileMapper.insert(testBusinessFile);
});
return testBusinessVO;
}
@Override
public TestBusinessVO selectById(String id) {
TestBusiness testBusiness = this.selectByPrimaryKey(id);
TestBusinessFileExample example = new TestBusinessFileExample();
example.createCriteria().andBusinessIdEqualTo(id);
List<TestBusinessFile> testBusinessFiles = this.testBusinessFileMapper.selectByExample(example);
List<DfsFile> dfsFiles = testBusinessFiles.stream().map((file) -> dfsFileMapper.selectByPrimaryKey(file.getFileId())).collect(Collectors.toList());
TestBusinessVO testBusinessVO = new TestBusinessVO();
BeanUtils.copyProperties(testBusiness, testBusinessVO);
testBusinessVO.setDfsFiles(dfsFiles);
return testBusinessVO;
}
@Override
public Object delete(String fileId, String bussinessId) {
TestBusinessFileExample testBusinessFileExample = new TestBusinessFileExample();
testBusinessFileExample.createCriteria().andBusinessIdEqualTo(bussinessId).andFileIdEqualTo(fileId);
testBusinessFileMapper.deleteByExample(testBusinessFileExample);
uploadInfoMapper.deleteByPrimaryKey(fileId);
return ResultUtils.getSuccessResultData();
}
}
在数据库表UPLOAD_INFO里配置删除引用表达式为:@testBusinessService.delete([fileInfo][id],[id]),即在删除文件时,调用此方法,删除业务表与文件表的引用关系
TestBusinessVO:
package com.gillion.sebusiness.demo.vo.upload;
import com.gillion.sebusiness.upload.model.DfsFile;
import java.util.List;
/**
* Created by wengms on 2015/8/6.
*/
public class TestBusinessVO {
private String id;
private String businessName;
private List<DfsFile> dfsFiles;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBusinessName() {
return businessName;
}
public void setBusinessName(String businessName) {
this.businessName = businessName;
}
public List<DfsFile> getDfsFiles() {
return dfsFiles;
}
public void setDfsFiles(List<DfsFile> dfsFiles) {
this.dfsFiles = dfsFiles;
}
}
TestBusinessController:
package com.gillion.sebusiness.demo.controller.upload;
import com.gillion.sebusiness.demo.service.datasource.StudentService;
import com.gillion.sebusiness.demo.service.upload.TestBusinessService;
import com.gillion.sebusiness.demo.vo.upload.TestBusinessVO;
import com.gillion.sebusiness.upload.model.DfsFile;
import com.gillion.sebusiness.upload.model.TestBusiness;
import com.gillion.sebusiness.upload.model.TestBusinessFile;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* Created by wengms on 2015/8/6.
*/
@RequestMapping("/upload/test")
@Controller
public class TestBusinessController {
@Autowired
private TestBusinessService testBusinessService;
@ResponseBody
@RequestMapping(method = RequestMethod.POST)
public TestBusinessVO save(@RequestBody TestBusinessVO testBusinessVO){
return this.testBusinessService.save(testBusinessVO);
}
@RequestMapping(method = RequestMethod.GET)
@ResponseBody
public TestBusinessVO get(@RequestParam String id){
return this.testBusinessService.selectById(id);
}
}
至此,后端配置已完成。
前端配置属性:
参数名 | 含义 | 是否必须 | 说明 |
---|---|---|---|
key | 用于前端获取前端配置的key | Y | |
upload-info-url | 获取配置信息的访问地址 | Y | |
upload-url | 文件上传地址 | N | 若没填写,默认与upload-info-url相同 |
delete-url | 点击删除时,访问的接口地址 | Y | |
delete-params | 在删除时,需要携带的信息,如:业务ID等 | N | 为字面量配置:{“businessId”:”321321321”} |
initial-show-count | 初始化时显示的数量 | N | 默认为1 |
image-show-url-prop | 如果控件显示是从已有的ngModel中取值进行显示,则ngModel必须提供一个控件访问地址,同时还需要配置此属性去接收那个地址,用于控件进行显示 | N | 默认为smallThumbnail |
ng-model | 双向绑定 | N | 若有设值,则为一个数组,默认每个文件信息里必须包含:id,url用于在页面中文件的显示与点击删除按钮时,文件信息的传递 |
示例配置:
<g-upload-group
key="1"
upload-info-url="/sebusiness/upload"
upload-url="/sebusiness/upload"
delete-url="/sebusiness/upload/delete"
delete-params="deleteParams"
initial-show-count="1"
image-show-url-prop="smallThumbPath"
ng-model="business.dfsFiles"
>
</g-upload-group>
js示例代码:
define(["angular", "framework/upload/UploadGroupModule"], function(angular) {
var TestUploadModule;
TestUploadModule = angular.module("TestUploadModule", ["UploadGroupModule"]);
TestUploadModule.controller("UploadController", function($scope, Resource) {
var Business;
Business = Resource("/sebusiness/upload/test");
Business.get({
id: "e782f3a3-1988-47b6-a768-1e1267dd78cc"
}, function(result) {
$scope.business = result;
$scope.deleteParams = {
id: "e782f3a3-1988-47b6-a768-1e1267dd78cc"
};
});
/*保存订单 */
$scope.save = function() {
$scope.business.id = void 0;
Business.save($scope.business, function(result) {
console.info("保存成功!!");
});
};
});
});