提交 82ada38f 编写于 作者: cxt104926's avatar cxt104926

文件管理

上级 944c7361
......@@ -2,6 +2,7 @@
/target/
/log/*
.classpath
.project
......@@ -10,7 +11,7 @@
.idea/*
stuServer/.idea/*
stuServer/target/*
##filter databfilesln file##
##filter databfile��sln file##
*.mdb ?
......
......@@ -69,7 +69,13 @@
SET locked=0, lockgranted=null, lockedby=null
WHERE id=1
#### 6.说在最后
#### 6.开发记录
文件管理:
1、前端上传图片/文件,将图片名字改成uuid,存放到指定位置,在文件表记录一条信息,用到图片的表中记录id。
2、定时任务,在规定时间去检查文件表,已经删除的文件进行清除
#### 7.说在最后
1. 系统正在开发,想到的后面再更新
2. 正在学习使用这些技术,若有错误 不对之处欢迎大佬指正
......
......@@ -5,6 +5,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.annotation.EnableScheduling;
import tk.mybatis.spring.annotation.MapperScan;
import java.net.InetAddress;
......
......@@ -2,6 +2,8 @@ package com.stu.stusystem.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
......@@ -18,4 +20,13 @@ public class SocketConfig {
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
// 解决不能同时使用websocket和spring的定时注解
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduling = new ThreadPoolTaskScheduler();
scheduling.setPoolSize(10);
scheduling.initialize();
return scheduling;
}
}
\ No newline at end of file
package com.stu.stusystem.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.validation.Valid;
/**
* @author cxt
* @date 2020/9/9
......@@ -13,9 +16,13 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Component
public class WebConfigImpl implements WebMvcConfigurer {
@Value("${filePath}")
private String filePath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/file/**").addResourceLocations("file:"+filePath);
}
@Override
......
package com.stu.stusystem.controller.system;
import com.stu.stusystem.common.ApiException;
import com.stu.stusystem.common.ApiResult;
import com.stu.stusystem.model.system.Document;
import com.stu.stusystem.service.system.DocumentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import liquibase.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* @author: cxt
* @time: 2021/6/24
*/
@Api(tags = "文件管理")
@RestController
@EnableScheduling
@RequestMapping("/document")
public class DocumentController {
private DocumentService documentService;
@ApiOperation("formData格式")
@GetMapping("/formData")
public ApiResult<String> saveFormData(@RequestParam("file") MultipartFile file) {
return ApiResult.success(this.documentService.saveFormData(file));
}
@ApiOperation("formData格式")
@GetMapping("/files")
public ApiResult<String> saveFiles(@RequestParam("files") MultipartFile[] files) {
String[] str = new String[files.length];
int i = 0;
for (MultipartFile file : files) {
str[i++] = this.documentService.saveFormData(file);
}
String join = StringUtils.join(str, ",");
return ApiResult.success(join);
}
@ApiOperation("base64格式")
@PostMapping("/base64")
public ApiResult<String> saveBase64(@RequestParam("file") String file, @RequestParam("fileName") String fileName) {
if (file.length() == 0) {
throw new ApiException("文件数据不能为空");
}
return ApiResult.success(this.documentService.saveBase64(file, fileName));
}
@ApiOperation("根据id查询")
@GetMapping("/get/{id}")
public Document getDocument(@PathVariable("id") String id) {
return this.documentService.getDocument(id);
}
@ApiOperation("根据id查询多个文件")
@GetMapping("/get/ids")
public List<Document> getDocuments(@RequestParam("ids") String ids) {
if ("".equals(ids)) {
throw new ApiException("参数不能为空");
}
return this.documentService.getDocuments(ids);
}
@ApiOperation("删除文件")
@DeleteMapping("/remove/{id}")
public ApiResult<String> remove(@PathVariable("id") String id) {
this.documentService.remove(id);
return ApiResult.success();
}
@Autowired
public void setDocumentService(DocumentService documentService) {
this.documentService = documentService;
}
}
package com.stu.stusystem.mapper.system;
import com.stu.stusystem.common.CommonMapper;
import com.stu.stusystem.model.system.Document;
/**
* @author: cxt
* @time: 2021/6/24
*/
public interface DocumentMapper extends CommonMapper<Document> {
}
package com.stu.stusystem.model.em;
/**
* 文件类型枚取
*/
public enum FileType {
/**
* JEPG.
*/
JPEG("FFD8FF"),
/**
* PNG.
*/
PNG("89504E47"),
/**
* GIF.
*/
GIF("47494638"),
/**
* TIFF.
*/
TIFF("49492A00"),
/**
* Windows Bitmap.
*/
BMP("424D"),
/**
* CAD.
*/
DWG("41433130"),
/**
* Adobe Photoshop.
*/
PSD("38425053"),
/**
* Rich Text Format.
*/
RTF("7B5C727466"),
/**
* XML.
*/
XML("3C3F786D6C"),
/**
* HTML.
*/
HTML("68746D6C3E"),
/**
* Email [thorough only].
*/
EML("44656C69766572792D646174653A"),
/**
* Outlook Express.
*/
DBX("CFAD12FEC5FD746F"),
/**
* Outlook (pst).
*/
PST("2142444E"),
/**
* MS Word/Excel.
*/
XLS_DOC("D0CF11E0"),
/**
* MS Access.
*/
MDB("5374616E64617264204A"),
/**
* WordPerfect.
*/
WPD("FF575043"),
/**
* Postscript.
*/
EPS("252150532D41646F6265"),
/**
* Adobe Acrobat.
*/
PDF("255044462D312E"),
/**
* Quicken.
*/
QDF("AC9EBD8F"),
/**
* Windows Password.
*/
PWL("E3828596"),
/**
* ZIP Archive.
*/
ZIP("504B0304"),
/**
* RAR Archive.
*/
RAR("52617221"),
/**
* Wave.
*/
WAV("57415645"),
/**
* AVI.
*/
AVI("41564920"),
/**
* Real Audio.
*/
RAM("2E7261FD"),
/**
* Real Media.
*/
RM("2E524D46"),
/**
* MPEG (mpg).
*/
MPG("000001BA"),
/**
* Quicktime.
*/
MOV("6D6F6F76"),
/**
* Windows Media.
*/
ASF("3026B2758E66CF11"),
/**
* MIDI.
*/
MID("4D546864");
private String value = "";
private FileType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
package com.stu.stusystem.model.system;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Id;
import java.util.Date;
/**
* @author: cxt
* @time: 2021/6/24
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Document {
@Id
private String id;
// 名称
private String name;
// 类型
private String fileType;
// 后缀名
private String suffix;
// 地址
private String filePath;
// 大小
private Long fileSize;
// 创建时间
private Date creatTime;
// 是否删除
private Integer isDelete;
}
package com.stu.stusystem.service.system;
import com.stu.stusystem.common.ApiException;
import com.stu.stusystem.mapper.system.DocumentMapper;
import com.stu.stusystem.model.system.Document;
import com.stu.stusystem.util.FileTypeJudgeUtil;
import com.stu.stusystem.util.UUIDUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;
import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.weekend.WeekendSqls;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* @author: cxt
* @time: 2021/6/24
*/
@Service
@Log4j2
public class DocumentService {
@Value("${filePath}")
private String filePath;
private DocumentMapper documentMapper;
private static final Integer maxFileByteSize = 10 * 1024 * 1024;
private final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
// 特殊文件类型
private static final ArrayList<String> FILETYPE = new ArrayList<>(Arrays.asList("TXT", "MD"));
/**
* formData保存文件
*
* @param file 文件
* @return 保存后的文件id
*/
@Transactional
public String saveFormData(MultipartFile file) {
long size = file.getSize();
if (size > maxFileByteSize) {
throw new ApiException("上传文件不能大于10MB");
}
String name = file.getOriginalFilename();
if (name == null) {
throw new ApiException("文件名称为空");
}
String id = UUIDUtil.generate();
String[] split = name.split("\\.");
String fileName = id + "." + split[1];
// 文件保存到磁盘
String path = "";
try {
path = saveFile(file.getBytes(), fileName);
} catch (IOException e) {
log.error("保存文件出错");
}
// 真实的文件类型判断
String fileType = null;
String upperCase = split[1].toUpperCase();
if (FILETYPE.contains(upperCase)) {
fileType = upperCase;
} else {
byte[] b = new byte[10];
InputStream inputStream = null;
try {
inputStream = file.getInputStream();
int read = inputStream.read(b, 0, 10);
if (read != 0) {
fileType = FileTypeJudgeUtil.getTypeByByte(b);
if (fileType == null) {
throw new ApiException("系统不支持浏览您上传的文件");
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 保存到数据库
Document document = new Document(id, split[0], fileType, split[1], path, size, new Date(), 0);
return saveDocument(document);
}
/**
* 保存Base64格式
*
* @param file 文件Base64码
* @param fileName 文件名称
* @return 保存后的文件id
*/
@Transactional
public String saveBase64(String file, String fileName) {
String[] split = file.split("base64,");
// 图片格式
String fileTile = split[0].substring(10, 14);
// 实体数据
String data = split[1];
long size = data.length() * 3 / 4;
String id = UUIDUtil.generate();
String path = "";
// 保存在磁盘
BASE64Decoder decoder = new BASE64Decoder();
try {
byte[] bytes = decoder.decodeBuffer(file);
path = saveFile(bytes, id + "." + fileTile);
} catch (IOException e) {
e.printStackTrace();
}
// 保存到数据库
Document document = new Document(id, fileName, fileTile, fileTile, path, size, new Date(), 0);
return saveDocument(document);
}
/**
* 数据写入磁盘
*
* @param data 文件数据
* @param fileName 文件新名称
* @return 文件存储地址
*/
public String saveFile(byte[] data, String fileName) {
if (data == null || data.length == 0) {
throw new ApiException("文件数据为空");
}
String folder = this.format.format(new Date());
String format = filePath + "/" + folder;
File file = new File(format);
if (!file.exists()) {
if (!file.mkdirs()) {
log.error("创建文件夹失败:{}", format);
}
}
String path = format + "/" + fileName;
try {
Files.write(Paths.get(path), data);
} catch (IOException e) {
log.error("文件保存失败:{}", format);
e.printStackTrace();
}
return folder + "/" + fileName;
}
/**
* 记录到数据库
*
* @param document 文件对象
* @return 保存后的文件id
*/
public String saveDocument(Document document) {
int insert = this.documentMapper.insert(document);
if (insert == 0) {
throw new ApiException(ApiException.SAVE_FAIL);
}
return document.getId();
}
/**
* 根据id查询
*/
public Document getDocument(String id) {
return this.documentMapper.selectByPrimaryKey(id);
}
/**
* 根据id查询多个文件
*/
public List<Document> getDocuments(String ids) {
String[] split = ids.split(",");
ArrayList<String> list = new ArrayList<>(Arrays.asList(split));
Example example = new Example(Document.class);
example.createCriteria().andIn("id", list);
return this.documentMapper.selectByExample(example);
}
/**
* 删除id
*/
public void remove(String id) {
Document document = new Document();
document.setId(id);
document.setIsDelete(1);
Example example = new Example(Document.class);
example.excludeProperties().createCriteria().andEqualTo("id", id);
int i = this.documentMapper.updateByExampleSelective(document, example);
if (i == 0) {
throw new ApiException(ApiException.DELETE_FAIL);
}
}
/**
* 每天0时清除定时清除删除文件
*/
@Scheduled(cron = "0 0 0 * * ?")
public void regularRemove() {
List<Document> list = this.documentMapper.selectByExample(new Example.Builder(Document.class)
.where(WeekendSqls.<Document>custom().andEqualTo(Document::getIsDelete, 1)).build());
for (Document document : list) {
String path = filePath + document.getFilePath();
deleteFile(path);
}
this.documentMapper.deleteByExample(new Example.Builder(Document.class)
.where(WeekendSqls.<Document>custom().andEqualTo(Document::getIsDelete, 1)).build());
}
/**
* 删除文件
*/
public static void deleteFile(String fileName) {
File file = new File(fileName);
if (file.exists() && file.isFile()) {
if (file.delete()) {
log.debug("删除单个文件:" + fileName + "->成功!");
} else {
log.debug("删除单个文件:" + fileName + "->失败!");
}
}
}
@Autowired
public void setDocumentMapper(DocumentMapper documentMapper) {
this.documentMapper = documentMapper;
}
}
package com.stu.stusystem.util;
import com.stu.stusystem.common.ApiException;
import com.stu.stusystem.model.em.FileType;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author: cxt
* @time: 2021/6/25
*/
public class FileTypeJudgeUtil {
/**
* 将文件头转换成16进制字符串
*
* @param src 原生byte[]
* @return 16进制字符串
*/
private static String bytesToHexString(byte[] src) {
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
/**
* 根据文件路径获取文件头
*
* @param filePath 文件路径
* @return 文件头
*/
private static String getFileContent(String filePath) {
byte[] b = new byte[10];
InputStream inputStream = null;
try {
inputStream = new FileInputStream(filePath);
inputStream.read(b, 0, 10);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bytesToHexString(b);
}
/**
* 根据文件头字符串判断文件类型
*
* @param fileHead 文件头字符串
* @return String 文件类型
*/
private static String judgeType(String fileHead) {
FileType[] fileTypes = FileType.values();
for (FileType type : fileTypes) {
// startsWith 检测是否以指定前缀开始
String value = type.getValue();
if (fileHead.toUpperCase().startsWith(value)) {
return type.toString();
}
}
return null;
}
/**
* 根据文件路径判断文件类型
*
* @param filePath 文件路径
* @return String 文件类型
*/
public static String getTypeByPath(String filePath) {
String fileHead = getFileContent(filePath);
if (fileHead == null || fileHead.length() == 0) {
throw new ApiException("根据文件路径获取文件头失败");
}
return judgeType(fileHead);
}
/**
* 根据文件头字节数进行判断文件类型
*
* @param title 10字节文件头数据
* @return String 文件类型
*/
public static String getTypeByByte(byte[] title) {
String fileHead = bytesToHexString(title);
return judgeType(fileHead);
}
}
......@@ -7,5 +7,13 @@ spring:
username: cxt
password: cxt104926
# Liquibase 配置
liquibase:
url: jdbc:mysql://localhost:3306/stusystem?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
driver: com.mysql.jdbc.Driver
password: root
username: root
enabled: true
change-log: classpath:config/master.xml
......@@ -23,15 +23,6 @@ spring:
multipart:
max-file-size: 10MB
max-request-size: 50MB
# thymeleaf:
# # 模板缓存
# cache: false
# # 检查模板地址是否正确
# check-template-location: true
# # 启用Thymeleaf视图解析
# enabled: true
# # SpringEL编译器
# enable-spring-el-compiler: false
# 配置myBatis
mybatis:
......@@ -41,16 +32,10 @@ mybatis:
pagehelper:
helper-dialect: mysql
# Liquibase 配置
liquibase:
url: jdbc:mysql://localhost:3306/stusystem?useUnicode=true&amp;characterEncoding=utf-8
driver: com.mysql.jdbc.Driver
password: root
username: root
enabled: true
change-log: classpath:config/master.xml
# log日志配置文件
logging:
config: classpath:config/logback-boot.xml
# file文件存储地址
filePath: E:/file/
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<property name="now" value="now()" dbms="mysql"/>
<property name="autoIncrement" value="true"/>
<property name="floatType" value="float" dbms="mysql, oracle, mssql"/>
<!--
Added the entity Document
-->
<changeSet id="20210624-01" author="cxt">
<createTable tableName="document" remarks="文件">
<column name="id" type="varchar(22)">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(100)" remarks="名称">
<constraints nullable="false"/>
</column>
<column name="file_type" type="varchar(10)" remarks="类型">
<constraints nullable="false"/>
</column>
<column name="suffix" type="varchar(10)" remarks="后缀名">
<constraints nullable="false"/>
</column>
<column name="file_path" type="varchar(200)" remarks="地址">
<constraints nullable="false"/>
</column>
<column name="file_size" type="bigint(20)" remarks="大小">
<constraints nullable="false"/>
</column>
<column name="creat_time" type="datetime" remarks="创建时间">
<constraints nullable="false"/>
</column>
<column name="is_delete" type="tinyint(1)" remarks="是否删除">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>
\ No newline at end of file
<configuration>
<property name="LOG_HOME" value="D:/log" />
<property name="LOG_HOME" value="./log"/>
<!-- %m输出的信息, %p日志级别, %t线程名, %d日期, %c类的全名, %i索引 -->
<!-- ConsoleAppender 把日志输出到控制台 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
......@@ -22,7 +22,7 @@
<fileNamePattern>${LOG_HOME}/mylog/%d.%i.log</fileNamePattern>
<!-- 每产生一个日志文件,该日志文件的保存期限为30天 -->
<maxHistory>30</maxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<!-- maxFileSize:这是活动文件的大小,默认值是10MB,本篇设置为100KB -->
<maxFileSize>100KB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
......@@ -38,11 +38,11 @@
<!-- 控制台日志输出级别 -->
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="STDOUT"/>
</root>
<!-- 指定项目中某个包,当有日志操作行为时的日志记录级别,本次打印整个项目的DEBUG日志 -->
<!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="com.stu.stusystem" level="DEBUG">
<appender-ref ref="systemLog" />
<appender-ref ref="systemLog"/>
</logger>
</configuration>
\ No newline at end of file
......@@ -12,5 +12,6 @@
<include file="classpath:config/liquibase/20210331_creat_table_role_jurisdiction.xml" relativeToChangelogFile="false"/>
<include file="classpath:config/liquibase/20210615_creat_table_ChatFriends.xml" relativeToChangelogFile="false"/>
<include file="classpath:config/liquibase/20210616_creat_table_ChatMsg.xml" relativeToChangelogFile="false"/>
<include file="classpath:config/liquibase/20210624_creat_table_document.xml" relativeToChangelogFile="false"/>
</databaseChangeLog>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册