Skip to content

Commit b6e4bdf

Browse files
committed
feat: file management
1 parent 6341fa7 commit b6e4bdf

16 files changed

Lines changed: 1034 additions & 30 deletions

File tree

blog-business-boot/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@
8080
<version>2.0.5</version>
8181
</dependency>
8282

83+
<!--用于解析HTML-->
84+
<dependency>
85+
<groupId>org.jsoup</groupId>
86+
<artifactId>jsoup</artifactId>
87+
<version>1.13.1</version>
88+
</dependency>
89+
8390
<dependency>
8491
<groupId>com.github.oshi</groupId>
8592
<artifactId>oshi-core</artifactId>

blog-business-boot/sql/blog_hackyle_com.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,18 @@ CREATE TABLE tl_article_access (
244244
is_deleted BIT DEFAULT 0 COMMENT '是否删除:0-false-未删除;1-true-已删除',
245245
PRIMARY KEY (id)
246246
) ENGINE=InnoDB COMMENT '文章访问日志';
247+
248+
# 静态资源(图片、视频、音乐、文档等)管理
249+
DROP TABLE IF EXISTS tb_file_storage;
250+
CREATE TABLE tb_file_storage (
251+
id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID:为了后续数据迁移,不使用自增主键,使用时间戳',
252+
253+
article_uri VARCHAR(256) DEFAULT '' COMMENT '文章地址,tb_article中的uri字段',
254+
file_link VARCHAR(512) NOT NULL COMMENT '文件地址',
255+
256+
create_time DATETIME DEFAULT now() COMMENT '创建时间: 年-月-日 时:分:秒',
257+
update_time DATETIME DEFAULT now() ON UPDATE now() COMMENT '更新时间',
258+
is_deleted BIT DEFAULT 0 COMMENT '是否删除:0-false-未删除;1-true-已删除',
259+
PRIMARY KEY (id)
260+
) ENGINE=InnoDB COMMENT '静态资源(图片、视频、音乐、文档等)管理';
261+

blog-business-boot/src/main/java/com/hackyle/blog/business/controller/FileStorageController.java

Lines changed: 106 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@
33
import com.alibaba.fastjson.JSON;
44
import com.alibaba.fastjson.JSONObject;
55
import com.hackyle.blog.business.common.constant.ResponseEnum;
6+
import com.hackyle.blog.business.common.pojo.ApiRequest;
67
import com.hackyle.blog.business.common.pojo.ApiResponse;
8+
import com.hackyle.blog.business.dto.FileStorageDto;
9+
import com.hackyle.blog.business.dto.PageRequestDto;
10+
import com.hackyle.blog.business.dto.PageResponseDto;
11+
import com.hackyle.blog.business.qo.FileStorageQo;
712
import com.hackyle.blog.business.service.FileStorageService;
13+
import com.hackyle.blog.business.vo.FileStorageVo;
814
import com.hackyle.blog.business.vo.FileVo;
915
import org.slf4j.Logger;
1016
import org.slf4j.LoggerFactory;
1117
import org.springframework.beans.factory.annotation.Autowired;
1218
import org.springframework.web.bind.annotation.*;
1319
import org.springframework.web.multipart.MultipartFile;
1420

21+
import javax.servlet.ServletOutputStream;
22+
import javax.servlet.http.HttpServletResponse;
23+
import java.io.FileInputStream;
24+
import java.net.URLEncoder;
25+
import java.nio.charset.StandardCharsets;
1526
import java.util.ArrayList;
1627
import java.util.List;
1728

@@ -101,48 +112,120 @@ public Object uploadImgTinyMCE(@RequestParam(value = "file", required = false) M
101112
/**
102113
* 文件删除
103114
*/
104-
@GetMapping("/del")
105-
public ApiResponse<String> fileDelete(@RequestParam("fileName") String fileName) {
106-
if(null == fileName || "".equals(fileName.trim())) {
115+
@DeleteMapping("/del")
116+
public ApiResponse<String> fileDelete(@RequestBody ApiRequest<FileStorageDto> apiRequest) {
117+
FileStorageDto storageDto = apiRequest.getData();
118+
LOGGER.info("文件删除-入参storageDto={}", JSON.toJSONString(storageDto));
119+
120+
if(null == storageDto.getFileLink() || "".equals(storageDto.getFileLink().trim())) {
107121
return ApiResponse.error(ResponseEnum.FRONT_END_ERROR.getCode(), ResponseEnum.FRONT_END_ERROR.getMessage());
108122
}
109123

110124
try {
111-
//boolean delFlag = minioService.fileDelete(fileName);
112-
//if(delFlag) {
113-
// return ApiResponse.success(ResponseEnum.OP_OK.getCode(), ResponseEnum.OP_OK.getMessage());
114-
//} else {
115-
// return ApiResponse.error(ResponseEnum.OP_FAIL.getCode(), ResponseEnum.OP_FAIL.getMessage());
116-
//}
117-
return ApiResponse.error(ResponseEnum.OP_FAIL.getCode(), ResponseEnum.OP_FAIL.getMessage());
125+
fileStorageService.fileDelete(storageDto);
126+
return ApiResponse.success(ResponseEnum.OP_OK.getCode(), ResponseEnum.OP_OK.getMessage());
118127

119128
}catch (Exception e) {
120129
LOGGER.error("文件删除出现异常:", e);
121-
return ApiResponse.error(ResponseEnum.EXCEPTION.getCode(), ResponseEnum.EXCEPTION.getMessage());
130+
return ApiResponse.error(ResponseEnum.EXCEPTION.getCode(), e.getMessage());
122131
}
123132
}
124133

125134

126135
/**
127-
* 获取文件详细信息
136+
* 文件修改
128137
*/
129-
@GetMapping("/fetch")
130-
public ApiResponse<String> fileDetail(@RequestParam("fileName") String fileName) {
131-
if(null == fileName || "".equals(fileName.trim())) {
138+
@PutMapping("/update")
139+
public ApiResponse<String> fileUpdate(@RequestBody ApiRequest<FileStorageDto> apiRequest) {
140+
FileStorageDto storageDto = apiRequest.getData();
141+
LOGGER.info("文件修改-入参storageDto={}", JSON.toJSONString(storageDto));
142+
143+
if(null == storageDto.getId() ||
144+
null == storageDto.getFileLink() || "".equals(storageDto.getFileLink().trim())) {
132145
return ApiResponse.error(ResponseEnum.FRONT_END_ERROR.getCode(), ResponseEnum.FRONT_END_ERROR.getMessage());
133146
}
134147

135148
try {
136-
//boolean delFlag = true; //minioService.fileDetail(fileName);
137-
//if(delFlag) {
138-
// return ApiResponse.success(ResponseEnum.OP_OK.getCode(), ResponseEnum.OP_OK.getMessage());
139-
//} else {
140-
// return ApiResponse.error(ResponseEnum.OP_FAIL.getCode(), ResponseEnum.OP_FAIL.getMessage());
141-
//}
142-
return ApiResponse.error(ResponseEnum.OP_FAIL.getCode(), ResponseEnum.OP_FAIL.getMessage());
149+
boolean delFlag = fileStorageService.fileUpdate(storageDto);
150+
if(delFlag) {
151+
return ApiResponse.success(ResponseEnum.OP_OK.getCode(), ResponseEnum.OP_OK.getMessage());
152+
} else {
153+
return ApiResponse.error(ResponseEnum.OP_FAIL.getCode(), ResponseEnum.OP_FAIL.getMessage());
154+
}
143155

144156
}catch (Exception e) {
145-
LOGGER.error("文件删除出现异常:", e);
157+
LOGGER.error("文件修改出现异常:", e);
158+
return ApiResponse.error(ResponseEnum.EXCEPTION.getCode(), ResponseEnum.EXCEPTION.getMessage());
159+
}
160+
}
161+
162+
/**
163+
* 文件下载
164+
*/
165+
@GetMapping("/download")
166+
public void download(@RequestParam("fileLink") String fileLink, HttpServletResponse response) {
167+
try {
168+
String[] split = fileLink.split("/");
169+
String fileName = split[split.length-1];
170+
FileInputStream fis = fileStorageService.download(fileLink);
171+
if(null == fis) {
172+
response.setContentType("application/json;charset=UTF-8");
173+
String respStr = JSON.toJSONString(ApiResponse.error(ResponseEnum.EXCEPTION.getCode(), ResponseEnum.EXCEPTION.getMessage(), "文件不存在"));
174+
ServletOutputStream outputStream = response.getOutputStream();
175+
outputStream.write(respStr.getBytes(StandardCharsets.UTF_8));
176+
response.flushBuffer();
177+
outputStream.close();
178+
return;
179+
}
180+
181+
//response.setHeader("Access-Control-Allow-Origin", "*");
182+
response.setContentType("application/octet-stream;charset=UTF-8");
183+
184+
//因为是跨前后端分离,默认reponse header只能取到以下:Content-Language,Content-Type,Expires,Last-Modified,Pragma
185+
//要想获取到文件名,需要采取这种方式。Reference:https://www.cnblogs.com/liuxianbin/p/13035809.html
186+
response.setHeader("filename",URLEncoder.encode(fileName, StandardCharsets.UTF_8));
187+
response.setHeader("Access-Control-Expose-Headers","filename");
188+
189+
response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName, StandardCharsets.UTF_8));
190+
ServletOutputStream outputStream = response.getOutputStream();
191+
outputStream.write(fis.readAllBytes());
192+
outputStream.flush();
193+
outputStream.close();
194+
fis.close();
195+
196+
//return ApiResponse.success(ResponseEnum.OP_OK.getCode(), ResponseEnum.OP_OK.getMessage());
197+
}catch (Exception e) {
198+
LOGGER.error("文件下载出现异常:", e);
199+
//return ApiResponse.error(ResponseEnum.EXCEPTION.getCode(), ResponseEnum.EXCEPTION.getMessage());
200+
}
201+
}
202+
203+
/**
204+
* 获取文件列表
205+
*/
206+
@PostMapping("/fetchList")
207+
public ApiResponse<PageResponseDto<FileStorageVo>> fetchList(@RequestBody ApiRequest<PageRequestDto<FileStorageQo>> apiRequest) {
208+
PageRequestDto<FileStorageQo> pageRequest = apiRequest.getData();
209+
if(pageRequest == null) {
210+
//return ApiResponse.error(ResponseEnum.PARAMETER_MISSING.getCode(), ResponseEnum.PARAMETER_MISSING.getMessage());
211+
pageRequest = new PageRequestDto<>();
212+
pageRequest.setCurrentPage(1);
213+
pageRequest.setPageSize(10);
214+
}
215+
216+
if(pageRequest.getCurrentPage() < 1) {
217+
pageRequest.setCurrentPage(1);
218+
}
219+
if(pageRequest.getPageSize() < 1) {
220+
pageRequest.setPageSize(10);
221+
}
222+
223+
try {
224+
PageResponseDto<FileStorageVo> pageResponse = fileStorageService.fetchList(pageRequest);
225+
return ApiResponse.success(ResponseEnum.OP_OK.getCode(), ResponseEnum.OP_OK.getMessage(), pageResponse);
226+
227+
}catch (Exception e) {
228+
LOGGER.error("获取文件列表出现异常:", e);
146229
return ApiResponse.error(ResponseEnum.EXCEPTION.getCode(), ResponseEnum.EXCEPTION.getMessage());
147230
}
148231
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.hackyle.blog.business.dto;
2+
3+
public class FileStorageDto {
4+
private Long id;
5+
6+
private String articleUri;
7+
8+
/** e.g. https://res.hackyle.com/blog/2023/02/xxx.png*/
9+
private String fileLink;
10+
11+
public Long getId() {
12+
return id;
13+
}
14+
15+
public void setId(Long id) {
16+
this.id = id;
17+
}
18+
19+
public String getArticleUri() {
20+
return articleUri;
21+
}
22+
23+
public void setArticleUri(String articleUri) {
24+
this.articleUri = articleUri;
25+
}
26+
27+
public String getFileLink() {
28+
return fileLink;
29+
}
30+
31+
public void setFileLink(String fileLink) {
32+
this.fileLink = fileLink;
33+
}
34+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.hackyle.blog.business.entity;
2+
3+
import com.baomidou.mybatisplus.annotation.TableField;
4+
import com.baomidou.mybatisplus.annotation.TableId;
5+
import com.baomidou.mybatisplus.annotation.TableName;
6+
7+
import java.util.Date;
8+
9+
/**
10+
* 静态资源(图片、视频、音乐、文档等)管理
11+
*/
12+
@TableName(value ="tb_file_storage")
13+
public class FileStorageEntity {
14+
/**
15+
* ID:为了后续数据迁移,不使用自增主键,使用时间戳
16+
*/
17+
@TableId
18+
private Long id;
19+
20+
private String articleUri;
21+
22+
private String fileLink;
23+
24+
/**
25+
* 创建时间: 年-月-日 时:分:秒
26+
*/
27+
private Date createTime;
28+
29+
/**
30+
* 更新时间
31+
*/
32+
private Date updateTime;
33+
34+
/**
35+
* 是否删除:0-false-未删除;1-true-已删除
36+
*/
37+
@TableField("is_deleted")
38+
private Boolean deleted;
39+
40+
//@TableField("is_released")
41+
//private Boolean released;
42+
43+
44+
public Long getId() {
45+
return id;
46+
}
47+
48+
public void setId(Long id) {
49+
this.id = id;
50+
}
51+
52+
public String getArticleUri() {
53+
return articleUri;
54+
}
55+
56+
public void setArticleUri(String articleUri) {
57+
this.articleUri = articleUri;
58+
}
59+
60+
public String getFileLink() {
61+
return fileLink;
62+
}
63+
64+
public void setFileLink(String fileLink) {
65+
this.fileLink = fileLink;
66+
}
67+
68+
public Date getCreateTime() {
69+
return createTime;
70+
}
71+
72+
public void setCreateTime(Date createTime) {
73+
this.createTime = createTime;
74+
}
75+
76+
public Date getUpdateTime() {
77+
return updateTime;
78+
}
79+
80+
public void setUpdateTime(Date updateTime) {
81+
this.updateTime = updateTime;
82+
}
83+
84+
public Boolean getDeleted() {
85+
return deleted;
86+
}
87+
88+
public void setDeleted(Boolean deleted) {
89+
this.deleted = deleted;
90+
}
91+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.hackyle.blog.business.mapper;
2+
3+
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4+
import com.hackyle.blog.business.entity.FileStorageEntity;
5+
import org.apache.ibatis.annotations.Mapper;
6+
import org.apache.ibatis.annotations.Param;
7+
8+
import java.util.List;
9+
10+
@Mapper
11+
public interface FileStorageMapper extends BaseMapper<FileStorageEntity> {
12+
Integer batchInsertImg(@Param("imgList") List<String> imgList);
13+
14+
Integer batchInsert(@Param("article_uri") String articleUri, @Param("imgList") List<String> imgList);
15+
16+
Integer updateArticleUriById(@Param("article_uri") String articleUri, @Param("idList") List<Long> idList);
17+
18+
List<FileStorageEntity> selectByArticleUri(@Param("article_uri") String articleUri);
19+
20+
List<FileStorageEntity> selectByArticleUriIsNull();
21+
22+
}

0 commit comments

Comments
 (0)