文件共享存储系统是企事业单位日常办公中不可或缺的基础设施。传统的FTP服务器或局域网共享文件夹方式存在权限管理粗放、版本控制缺失、操作日志不全等痛点。基于SpringBoot的文件共享存储软件正是为解决这些问题而设计的现代化解决方案。
我在实际开发中发现,这类系统需要平衡三个核心需求:
采用SpringBoot 2.7 + MyBatis Plus组合,相比传统SSM架构:
采用混合存储策略:
java复制// 存储策略配置示例
@Configuration
public class StorageConfig {
@Value("${storage.type}")
private String storageType;
@Bean
@ConditionalOnProperty(name="storage.type", havingValue="local")
public StorageService localStorage() {
return new LocalStorageServiceImpl();
}
@Bean
@ConditionalOnProperty(name="storage.type", havingValue="oss")
public StorageService ossStorage() {
return new AliyunOssServiceImpl();
}
}
支持三种存储后端:
前端采用WebUploader实现分块,后端关键逻辑:
java复制@PostMapping("/chunk-upload")
public ResponseEntity<?> chunkUpload(
@RequestParam MultipartFile file,
@RequestParam String chunkMd5,
@RequestParam Integer chunkIndex,
@RequestParam Integer totalChunks) {
// 临时存储分块
String tempPath = "/tmp/chunks/" + chunkMd5;
file.transferTo(new File(tempPath + "_" + chunkIndex));
// 检查是否所有分块已上传
if (checkAllChunksReceived(chunkMd5, totalChunks)) {
mergeChunks(chunkMd5, totalChunks);
return ResponseEntity.ok().build();
}
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).build();
}
采用RBAC模型扩展:
sql复制CREATE TABLE `file_permission` (
`id` bigint NOT NULL AUTO_INCREMENT,
`file_id` bigint NOT NULL COMMENT '文件ID',
`role_id` int NOT NULL COMMENT '角色ID',
`can_read` tinyint DEFAULT 0 COMMENT '读取权限',
`can_download` tinyint DEFAULT 0 COMMENT '下载权限',
`can_delete` tinyint DEFAULT 0 COMMENT '删除权限',
`can_share` tinyint DEFAULT 0 COMMENT '分享权限',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_file_role` (`file_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
权限校验拦截器示例:
java复制public class PermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String uri = request.getRequestURI();
Long fileId = parseFileId(uri);
User user = getCurrentUser();
if (!permissionService.checkPermission(user, fileId, "read")) {
response.sendError(403, "无权访问该资源");
return false;
}
return true;
}
}
采用Nginx静态资源代理:
nginx复制location /download/ {
internal;
alias /data/shared-files/;
# 启用零拷贝技术
sendfile on;
tcp_nopush on;
# 限制下载速度(KB/s)
limit_rate 1024;
}
实测对比:
| 传输方式 | 100MB文件耗时 | 500MB文件耗时 |
|---|---|---|
| 传统Servlet | 12.3s | 68.5s |
| Nginx代理 | 8.7s | 42.1s |
使用Redis二级缓存:
缓存更新策略:
java复制@CacheEvict(value = "directory", key = "#userId + ':' + #pathHash")
public void updateFile(String userId, String pathHash) {
// 更新数据库操作
}
集成ClamAV反病毒引擎:
java复制public class VirusScanner {
private static final String CLAMD_HOST = "127.0.0.1";
private static final int CLAMD_PORT = 3310;
public boolean scanFile(File file) throws IOException {
try (Socket socket = new Socket(CLAMD_HOST, CLAMD_PORT);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream()) {
String command = "SCAN " + file.getAbsolutePath() + "\n";
out.write(command.getBytes());
out.flush();
byte[] buffer = new byte[1024];
int read = in.read(buffer);
String response = new String(buffer, 0, read);
return !response.contains("FOUND");
}
}
}
使用Trie树算法实现关键词过滤:
java复制public class SensitiveFilter {
private TrieNode root = new TrieNode();
public void addKeyword(String keyword) {
TrieNode node = root;
for (char c : keyword.toCharArray()) {
node = node.children.computeIfAbsent(c, k -> new TrieNode());
}
node.isEnd = true;
}
public boolean containsSensitive(String text) {
// 实现DFA算法检测
}
}
Docker Compose配置示例:
yaml复制version: '3'
services:
app:
image: file-share:1.0
ports:
- "8080:8080"
volumes:
- ./config:/config
- ./data:/data
depends_on:
- redis
- mysql
redis:
image: redis:6
ports:
- "6379:6379"
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: rootpass
volumes:
- ./mysql-data:/var/lib/mysql
采用Nginx负载均衡:
nginx复制upstream file_server {
server 192.168.1.101:8080 weight=3;
server 192.168.1.102:8080 weight=2;
server 192.168.1.103:8080 weight=1;
keepalive 32;
}
server {
listen 80;
server_name files.example.com;
location / {
proxy_pass http://file_server;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
文件锁问题
java复制try (FileChannel channel = FileChannel.open(path,
StandardOpenOption.READ,
StandardOpenOption.WRITE)) {
FileLock lock = channel.tryLock();
// 操作文件
}
内存溢出防范
properties复制spring.servlet.multipart.max-file-size=2GB
spring.servlet.multipart.max-request-size=2GB
文件名编码问题
java复制String safeName = new String(fileName.getBytes(StandardCharsets.UTF_8),
StandardCharsets.ISO_8859_1);
版本控制系统
在线预览功能
分布式文件同步
在实际部署时发现,当单个目录下文件超过5000个时,列表查询性能会明显下降。解决方案是强制分目录存储,并在数据库中添加directory表的索引:
sql复制ALTER TABLE `file_info`
ADD INDEX `idx_dir_filename` (`directory_id`, `file_name`);