docs: add apifox document service configuration

docs: add api document
This commit is contained in:
Wzp-2008 2024-12-08 21:23:35 +08:00
parent 8e1aeb7211
commit a0c75059ba
30 changed files with 591 additions and 105 deletions

View File

@ -0,0 +1,7 @@
param.ignore=@cn.wzpmc.filemanager.annotation.Address
param.ignore=@cn.wzpmc.filemanager.annotation.AuthorizationRequired
method.additional.header[@cn.wzpmc.filemanager.annotation.AuthorizationRequired]={name: "Authorization",value: "",description: "验证Token",required:true, example="eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsImV4cCI6MTczNDA4NzM0Nn0.8EmG-u-yCLVdWtQRnfhsU5zqjIGR6vruqfI8CHba6VsBAom9gPzZz1juo1dproUItB6AXCpxMcPv1I0ggo-ZIw"}
method.additional.header[groovy:it.args().any { params -> params.hasAnn("cn.wzpmc.filemanager.annotation.AuthorizationRequired") }]={name: "Authorization",value: "",description: "验证Token",required:true, example="eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjEsImV4cCI6MTczNDA4NzM0Nn0.8EmG-u-yCLVdWtQRnfhsU5zqjIGR6vruqfI8CHba6VsBAom9gPzZz1juo1dproUItB6AXCpxMcPv1I0ggo-ZIw"}
param.required=groovy:it.hasAnn("org.springframework.web.bind.annotation.RequestParam") ? it.ann("org.springframework.web.bind.annotation.RequestParam","defaultValue").equals("\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n") : true
method.return[#response]=groovy: helper.resolveLink(it.doc("response"))
ignore=#ignore

View File

@ -5,9 +5,9 @@ import cn.wzpmc.filemanager.annotation.AuthorizationRequired;
import cn.wzpmc.filemanager.entities.PageResult;
import cn.wzpmc.filemanager.entities.Result;
import cn.wzpmc.filemanager.entities.files.FolderCreateRequest;
import cn.wzpmc.filemanager.entities.files.NamedRawFile;
import cn.wzpmc.filemanager.entities.files.RawFileObject;
import cn.wzpmc.filemanager.entities.files.FullRawFileObject;
import cn.wzpmc.filemanager.entities.files.enums.FileType;
import cn.wzpmc.filemanager.entities.files.enums.SortField;
import cn.wzpmc.filemanager.entities.vo.FileVo;
import cn.wzpmc.filemanager.entities.vo.FolderVo;
import cn.wzpmc.filemanager.entities.vo.UserVo;
@ -20,67 +20,135 @@ import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.util.Date;
/**
* 文件操作相关接口
*/
@RestController
@RequestMapping("/api/file")
@RequiredArgsConstructor
public class FileController {
private final FileService fileService;
/**
* 上传一个文件
* @param file Multipart格式的文件对象
* @return 文件详情
*/
@PutMapping("/upload")
public Result<FileVo> simpleUpload(MultipartHttpServletRequest file, @AuthorizationRequired UserVo user, @Address String address) {
return fileService.simpleUpload(file, user, address);
}
/**
* 分页获取文件
* @param page 要获取第几页的文件
* @param num 每一页的文件数量
* @param folder 要获取的文件所在的文件夹
* @param sort 文件的排序方式
* @param reverse 是否反向排序
* @return 分页后的文件列表
*/
@GetMapping("/get")
public Result<PageResult<NamedRawFile>> getFilePager(@RequestParam long page, @RequestParam int num, @RequestParam long folder) {
return fileService.getFilePager(page, num, folder);
public Result<PageResult<FullRawFileObject>> getFilePager(@RequestParam long page, @RequestParam int num, @RequestParam long folder, @RequestParam(defaultValue = "TIME") SortField sort, @RequestParam(defaultValue = "false") boolean reverse) {
return fileService.getFilePager(page, num, folder, sort, reverse);
}
/**
* 创建一个文件夹
* @param request 创建文件夹的相关参数
* @return 创建的文件夹详情
*/
@PostMapping("/mkdir")
public Result<FolderVo> mkdir(@RequestBody FolderCreateRequest request, @AuthorizationRequired UserVo user, @Address String address) {
return fileService.mkdir(request, user, address);
}
/**
* 获取一个文件的简略信息
* @param id 文件ID
* @return 文件简略信息
*/
@GetMapping("/get/file")
public Result<NamedRawFile> getFile(@RequestParam long id) {
public Result<FullRawFileObject> getFile(@RequestParam long id) {
return fileService.getFile(id);
}
/**
* 获取一个文件夹的简略信息
* @param id 文件夹ID
* @return 文件夹简略信息
*/
@GetMapping("/get/folder")
public Result<NamedRawFile> getFolder(@RequestParam long id) {
public Result<FullRawFileObject> getFolder(@RequestParam long id) {
return fileService.getFolder(id);
}
/**
* 获取文件详细信息
* @param id 文件ID
* @return 文件详细信息
*/
@GetMapping("/detail/file")
public Result<FileVo> getFileDetail(@RequestParam long id) {
return fileService.getFileDetail(id);
}
/**
* 删除一个文件/文件夹
* @param id 文件/文件夹ID
* @param type 目标为文件/文件夹
* @return 是否删除成功
*/
@DeleteMapping("/rm")
public Result<Void> delete(@RequestParam long id, @RequestParam FileType type, @AuthorizationRequired UserVo user, @Address String address) {
return fileService.delete(id, type, user, address);
}
/**
* 获取文件下载链接
* @param id 文件ID
* @return 文件下载链接ID
*/
@GetMapping("/link")
public Result<String> getFileLink(@RequestParam long id, @Address String address, HttpServletRequest request) {
return fileService.getFileLink(id, address, request);
}
/**
* 通过下载文件ID下载文件
* @param id 下载ID
* @see #getFileLink(long, String, HttpServletRequest)
*/
@GetMapping("/download/{id}")
public void downloadFile(@PathVariable String id, @RequestHeader(value = "Range", defaultValue = "null") String range, HttpServletResponse response) {
fileService.downloadFile(id, range, response);
}
/**
* 分享文件
* @deprecated
*/
@GetMapping("/share")
public Result<String> shareFile(@RequestParam Long id, @RequestParam(defaultValue = "9999-12-31") Date lastCouldDownloadTime, @RequestParam(defaultValue = "-1") int maxDownloadCount) {
return fileService.shareFile(id, lastCouldDownloadTime, maxDownloadCount);
}
/**
* 通过路径解析文件信息
* @param path 需要解析的文件路径
* @return 文件粗略信息
*/
@GetMapping("/path/resolve")
public Result<RawFileObject> resolveFileDetail(@RequestParam String path) {
public Result<FullRawFileObject> resolveFileDetail(@RequestParam String path) {
return fileService.resolveFileDetail(path);
}
/**
* 通过文件ID获取文件路径
* @param id 文件ID
* @param type 目标文件为文件/文件夹
* @return 文件的路径
*/
@GetMapping("/path/{id}")
public Result<String> findFilePathById(@PathVariable("id") long id, @RequestParam(value = "type", defaultValue = "FILE") FileType type) {
return type.equals(FileType.FILE) ? fileService.findFilePathById(id) : fileService.findFolderPathById(id);

View File

@ -9,39 +9,63 @@ import cn.wzpmc.filemanager.entities.user.enums.Auth;
import cn.wzpmc.filemanager.entities.vo.UserVo;
import cn.wzpmc.filemanager.service.UserService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 用户相关接口
*/
@Slf4j
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
/**
* 登录接口
* @param loginRequest 登录请求体
* @response {@link Result<UserVo>}
*/
@PostMapping("/login")
public void login(@RequestBody UserLoginRequest loginRequest, HttpServletResponse response, @Address String address) {
userService.login(loginRequest, response, address);
}
/**
* 注册接口
* @param registerRequest 注册请求体
* @response {@link Result<UserVo>}
*/
@PutMapping("/register")
public void register(@RequestBody UserRegisterRequest registerRequest, HttpServletResponse response, @Address String address) {
userService.register(registerRequest, response, address);
}
/**
* 管理员用户生成邀请码接口
* @return 邀请码
*/
@GetMapping("/invite")
public Result<String> invite(@AuthorizationRequired(level = Auth.admin) UserVo userVo, @Address String address) {
return userService.invite(userVo, address);
}
/**
* 获取用户信息接口
* @return 用户信息
*/
@GetMapping("/info")
public Result<UserVo> getUserInfo(@AuthorizationRequired UserVo user) {
return Result.success(user);
}
/**
* 获取指定用户信息接口
* @param id 用户ID
* @return 该ID的用户信息
*/
@GetMapping("/info/{id}")
public Result<UserVo> getUser(@PathVariable Long id) {
return userService.getUserInformation(id);

View File

@ -10,6 +10,12 @@ import java.util.List;
@AllArgsConstructor
@JSONCompiled
public class PageResult<T> {
/**
* 总行数
*/
private long total;
/**
* 当前页数据
*/
private List<T> data;
}

View File

@ -16,89 +16,121 @@ import java.nio.charset.StandardCharsets;
@Data
@JSONCompiled
public class Result<T> {
/**
* 请求的响应码
*/
private int status;
/**
* 请求的响应信息
*/
private String msg;
/**
* 请求的响应数据
*/
private T data;
/**
* 请求生成的时间戳
*/
private long timestamp;
protected Result() {
this.timestamp = System.currentTimeMillis();
}
protected Result(int status, String msg, T data) {
this();
this.status = status;
this.msg = msg;
this.data = data;
}
protected Result(HttpStatus status) {
this();
this.status = status.value();
this.msg = status.getReasonPhrase();
}
protected Result(HttpStatus status, String msg) {
this(status);
this.msg = msg;
}
protected Result(HttpStatus status, T data) {
this(status);
this.data = data;
}
protected Result(HttpStatus status, String msg, T data) {
this(status, msg);
this.data = data;
}
public static <T> Result<T> success() {
return new Result<>(HttpStatus.OK);
}
public static <T> Result<T> success(String msg) {
return new Result<>(HttpStatus.OK, msg);
}
public static <T> Result<T> success(T data) {
return new Result<>(HttpStatus.OK, data);
}
public static <T> Result<T> success(String msg, T data) {
return new Result<>(HttpStatus.OK, msg, data);
}
public static <T> Result<T> failed() {
return new Result<>(HttpStatus.FORBIDDEN);
}
public static <T> Result<T> failed(String msg) {
return new Result<>(HttpStatus.FORBIDDEN, msg);
}
public static <T> Result<T> failed(HttpStatus status) {
return new Result<>(status);
}
public static <T> Result<T> failed(HttpStatus status, String msg) {
return new Result<>(status, msg);
}
public static <T> Result<T> create() {
return new Result<>();
}
public Result<T> status(int status) {
this.status = status;
return this;
}
public Result<T> status(HttpStatus status) {
this.status = status.value();
this.msg = status.getReasonPhrase();
return this;
}
public Result<T> msg(String msg) {
this.msg = msg;
return this;
}
public Result<T> data(T data) {
this.data = data;
return this;
}
public void writeToResponse(HttpServletResponse response){
public void writeToResponse(HttpServletResponse response) {
response.addHeader("Content-Type", "application/json; charset=utf-8");
try(ServletOutputStream outputStream = response.getOutputStream()){
try (ServletOutputStream outputStream = response.getOutputStream()) {
writeToOutputStream(outputStream);
} catch (IOException e) {
log.trace("写出到流失败,", e);
}
}
public void writeToOutputStream(OutputStream stream) throws IOException {
stream.write(JSON.toJSONString(this).getBytes(StandardCharsets.UTF_8));
}

View File

@ -6,6 +6,12 @@ import lombok.Data;
@Data
@JSONCompiled
public class FolderCreateRequest {
/**
* 父文件夹ID
*/
private long parent;
/**
* 文件名
*/
private String name;
}

View File

@ -1,16 +1,25 @@
package cn.wzpmc.filemanager.entities.files;
import com.alibaba.fastjson2.annotation.JSONCompiled;
import com.mybatisflex.annotation.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true)
@Table("raw_file")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JSONCompiled
public class NamedRawFile extends RawFileObject {
public class FullRawFileObject extends RawFileObject {
/**
* 文件所有者名称
*/
private String ownerName;
/**
* 文件下载次数
*/
private long downCount;
}

View File

@ -15,13 +15,37 @@ import java.util.Date;
@NoArgsConstructor
@JSONCompiled
public class RawFileObject {
/**
* 文件ID
*/
private long id;
/**
* 文件名
*/
private String name;
/**
* 文件扩展名文件夹为null
*/
private String ext;
/**
* 文件大小文件夹为-1
*/
private long size;
/**
* 文件所有者
*/
private long owner;
/**
* 父文件夹ID
*/
private long parent;
/**
* 文件上传时间
*/
private Date time;
/**
* 文件类型
*/
private FileType type;
public static RawFileObject of(FileVo file) {

View File

@ -4,5 +4,12 @@ import com.alibaba.fastjson2.annotation.JSONCompiled;
@JSONCompiled
public enum FileType {
FILE, FOLDER
/**
* 文件
*/
FILE,
/**
* 文件夹
*/
FOLDER
}

View File

@ -0,0 +1,35 @@
package cn.wzpmc.filemanager.entities.files.enums;
import com.mybatisflex.core.query.QueryColumn;
import lombok.RequiredArgsConstructor;
import static cn.wzpmc.filemanager.entities.files.table.FullRawFileObjectTableDef.FULL_RAW_FILE_OBJECT;
@RequiredArgsConstructor
public enum SortField {
/**
* 通过ID排序默认
*/
ID(FULL_RAW_FILE_OBJECT.ID),
/**
* 通过文件夹排序
*/
NAME(FULL_RAW_FILE_OBJECT.NAME),
/**
* 通过文件扩展名排序
*/
EXT(FULL_RAW_FILE_OBJECT.EXT),
/**
* 通过文件上传时间排序
*/
TIME(FULL_RAW_FILE_OBJECT.TIME),
/**
* 通过文件上传者排序
*/
UPLOADER(FULL_RAW_FILE_OBJECT.OWNER),
/**
* 通过文件下载次数排序
*/
DOWNLOAD_COUNT(FULL_RAW_FILE_OBJECT.DOWN_COUNT);
public final QueryColumn column;
}

View File

@ -4,5 +4,36 @@ import com.alibaba.fastjson2.annotation.JSONCompiled;
@JSONCompiled
public enum Actions {
UPLOAD, DELETE, ACCESS, DOWNLOAD, SEARCH, LOGIN, INVITE, REGISTER
/**
* 文件上传事件
*/
UPLOAD,
/**
* 文件删除事件
*/
DELETE,
/**
* 访问页面事件
*/
ACCESS,
/**
* 文件下载事件
*/
DOWNLOAD,
/**
* 搜索文件事件
*/
SEARCH,
/**
* 登录事件
*/
LOGIN,
/**
* 获取邀请码事件
*/
INVITE,
/**
* 注册事件
*/
REGISTER
}

View File

@ -6,6 +6,12 @@ import lombok.Data;
@Data
@JSONCompiled
public class UserLoginRequest {
/**
* 用户名
*/
private String username;
/**
* 密码通过MD5摘要
*/
private String password;
}

View File

@ -7,8 +7,20 @@ import lombok.Data;
@Data
@JSONCompiled
public class UserRegisterRequest {
/**
* 用户名
*/
private String username;
/**
* 密码经过MD5摘要
*/
private String password;
/**
* 用户类型
*/
private Auth auth;
/**
* 邀请码当作为admin管理员注册时需填写若作为user普通用户注册时无需
*/
private String inviteCode;
}

View File

@ -7,7 +7,14 @@ import lombok.AllArgsConstructor;
@AllArgsConstructor
@JSONCompiled
public enum Auth {
admin(1, "admin"), user(0, "user");
/**
* 管理员
*/
admin(1, "admin"),
/**
* 普通用户
*/
user(0, "user");
public final int value;
@EnumValue
public final String name;

View File

@ -14,15 +14,42 @@ import java.util.Date;
@Data
@JSONCompiled
public class FileVo implements Serializable {
/**
* 文件ID
*/
@Id(keyType = KeyType.Auto)
private long id;
/**
* 文件名
*/
private String name;
/**
* 文件扩展名
*/
private String ext;
/**
* 文件的MIME类型
*/
private String mime;
/**
* 文件的Sha512哈希值
*/
private String hash;
/**
* 文件的上传者ID
*/
private long uploader;
/**
* 文件的父文件夹ID
*/
private long folder;
/**
* 文件的大小 (bytes)
*/
private long size;
/**
* 文件的上传时间
*/
@Column(onInsertValue = "now()")
private Date uploadTime;

View File

@ -13,11 +13,26 @@ import java.util.Date;
@Data
@JSONCompiled
public class FolderVo {
/**
* 文件夹ID
*/
@Id(keyType = KeyType.Auto)
private long id;
/**
* 文件夹名
*/
private String name;
/**
* 文件夹的父文件夹ID
*/
private long parent;
/**
* 文件夹的创建者
*/
private long creator;
/**
* 文件夹的创建时间
*/
@Column(onInsertValue = "now()")
private Date createTime;

View File

@ -6,6 +6,7 @@ import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.lang.Nullable;
import java.util.Date;
@ -14,13 +15,32 @@ import java.util.Date;
@NoArgsConstructor
@JSONCompiled
public class StatisticsVo {
/***
* 操作者可能为空
*/
@Nullable
private Long actor;
/**
* 具体操作
*/
private Actions action;
/**
* 操作参数一般为JSON字符串
*/
private String params;
/**
* 操作时间
*/
@Column(onInsertValue = "now()")
private Date time;
/**
* 若为下载操作时的下载文件ID
* @ignore
*/
@Column(value = "download_file_id", ignore = true)
private Integer downloadFileId;
public StatisticsVo(Long actor, Actions action, String params) {
public StatisticsVo(@Nullable Long actor, Actions action, String params) {
this.actor = actor;
this.action = action;
this.params = params;

View File

@ -14,28 +14,50 @@ import lombok.Data;
@AllArgsConstructor
@JSONCompiled
public class UserVo {
/**
* 用户ID
*/
@Id(keyType = KeyType.Auto)
private long id;
/**
* 用户名
*/
private String name;
/**
* 用户密码
* @ignore
*/
private String password;
/**
* 用户类型
*/
private Auth auth;
/**
* 用户是否被封禁
* `尽管封了你也看不到就是了`
*/
@Column(isLogicDelete = true, onInsertValue = "0")
private boolean banned;
public UserVo(String name, String password, Auth auth) {
this.name = name;
this.password = password;
this.auth = auth;
}
private UserVo(long id, String name, Auth auth){
private UserVo(long id, String name, Auth auth) {
this.id = id;
this.name = name;
this.auth = auth;
}
public UserVo(long id) {
this.id = id;
}
public void clearPassword() {
this.setPassword(null);
}
public static final UserVo CONSOLE = new UserVo(0L, "CONSOLE", Auth.admin);
}

View File

@ -0,0 +1,8 @@
package cn.wzpmc.filemanager.entities.vo.table.custom;
import com.mybatisflex.core.query.QueryColumn;
public class StatisticsVoTableDef extends cn.wzpmc.filemanager.entities.vo.table.StatisticsVoTableDef {
public static final StatisticsVoTableDef STATISTICS_VO_EXT = new StatisticsVoTableDef();
public final QueryColumn DOWNLOAD_FILE_ID = new QueryColumn(this, "download_file_id");
}

View File

@ -20,6 +20,7 @@ public class StartEventListener {
initializationMapper.createStatisticsTable();
initializationMapper.createFolderTable();
initializationMapper.createFileTable();
initializationMapper.createRawFileView();
//开启审计功能
AuditManager.setAuditEnable(true);
MessageCollector collector = new ConsoleMessageCollector();

View File

@ -1,5 +1,6 @@
package cn.wzpmc.filemanager.interfaces;
import cn.wzpmc.filemanager.entities.files.FullRawFileObject;
import cn.wzpmc.filemanager.entities.files.RawFileObject;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
@ -9,5 +10,5 @@ public interface FilePathService {
String getFilePath(@NonNull RawFileObject file);
@Nullable
RawFileObject resolveFile(@NonNull String[] path);
FullRawFileObject resolveFile(@NonNull String[] path);
}

View File

@ -1,8 +1,10 @@
package cn.wzpmc.filemanager.interfaces.impl;
import cn.wzpmc.filemanager.entities.files.RawFileObject;
import cn.wzpmc.filemanager.entities.files.FullRawFileObject;
import cn.wzpmc.filemanager.mapper.FileMapper;
import cn.wzpmc.filemanager.mapper.FolderMapper;
import cn.wzpmc.filemanager.mapper.RawFileMapper;
import com.mybatisflex.core.query.QueryCondition;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
@ -13,8 +15,7 @@ import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Date;
import static cn.wzpmc.filemanager.entities.vo.table.FileVoTableDef.FILE_VO;
import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO;
import static cn.wzpmc.filemanager.entities.files.table.FullRawFileObjectTableDef.FULL_RAW_FILE_OBJECT;
@Component
@Primary
@ -22,16 +23,18 @@ import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO;
public class ComplexResolver extends SimplePathResolver {
private final ReverseResolver reverseResolver;
private final SimpleResolver simpleResolver;
private final RawFileMapper rawFileMapper;
@Autowired
public ComplexResolver(FileMapper fileMapper, FolderMapper folderMapper, ReverseResolver reverseResolver, SimpleResolver simpleResolver) {
public ComplexResolver(FileMapper fileMapper, FolderMapper folderMapper, ReverseResolver reverseResolver, SimpleResolver simpleResolver, RawFileMapper rawFileMapper) {
super(fileMapper, folderMapper);
this.reverseResolver = reverseResolver;
this.simpleResolver = simpleResolver;
this.rawFileMapper = rawFileMapper;
}
@Override
@Nullable
public RawFileObject resolveFile(@NonNull String[] path) {
public FullRawFileObject resolveFile(@NonNull String[] path) {
String strPath = Arrays.toString(path);
String targetFileName = path[path.length - 1];
int lastDotIndex = targetFileName.lastIndexOf('.');
@ -42,17 +45,21 @@ public class ComplexResolver extends SimplePathResolver {
ext = targetFileName.substring(lastDotIndex + 1);
}
long start = new Date().getTime();
long totalRawFileCount = this.fileMapper.selectCountByCondition(FILE_VO.NAME.eq(name).and(FILE_VO.EXT.eq(ext))) + this.folderMapper.selectCountByCondition(FOLDER_VO.NAME.eq(name));
QueryCondition extCondition = FULL_RAW_FILE_OBJECT.EXT.eq(ext);
if (ext.isEmpty()) {
extCondition = extCondition.or(FULL_RAW_FILE_OBJECT.EXT.isNull());
}
long totalRawFileCount = this.rawFileMapper.selectCountByCondition(FULL_RAW_FILE_OBJECT.NAME.eq(name).and(extCondition));
if (totalRawFileCount == 0) return null;
if (totalRawFileCount > path.length) {
log.info("use simple resolver to solve path with {}", strPath);
RawFileObject rawFileObject = simpleResolver.resolveFile(path);
FullRawFileObject rawFileObject = simpleResolver.resolveFile(path);
long end = new Date().getTime();
log.info("solve path {} cost {}ms", strPath, end - start);
return rawFileObject;
}
log.info("use reverse resolver to solve path with {}", strPath);
RawFileObject rawFileObject = reverseResolver.resolveFile(path);
FullRawFileObject rawFileObject = reverseResolver.resolveFile(path);
long end = new Date().getTime();
log.info("solve path {} cost {}ms", strPath, end - start);
return rawFileObject;

View File

@ -1,29 +1,34 @@
package cn.wzpmc.filemanager.interfaces.impl;
import cn.wzpmc.filemanager.entities.files.FullRawFileObject;
import cn.wzpmc.filemanager.entities.files.RawFileObject;
import cn.wzpmc.filemanager.entities.vo.FolderVo;
import cn.wzpmc.filemanager.mapper.FileMapper;
import cn.wzpmc.filemanager.mapper.FolderMapper;
import cn.wzpmc.filemanager.mapper.RawFileMapper;
import com.mybatisflex.core.query.QueryCondition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static cn.wzpmc.filemanager.entities.vo.table.FileVoTableDef.FILE_VO;
import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO;
import static cn.wzpmc.filemanager.entities.files.table.FullRawFileObjectTableDef.FULL_RAW_FILE_OBJECT;
@Component
public class ReverseResolver extends SimplePathResolver {
public ReverseResolver(FileMapper fileMapper, FolderMapper folderMapper) {
private final RawFileMapper rawFileMapper;
@Autowired
public ReverseResolver(FileMapper fileMapper, FolderMapper folderMapper, RawFileMapper rawFileMapper) {
super(fileMapper, folderMapper);
this.rawFileMapper = rawFileMapper;
}
@Nullable
@Override
public RawFileObject resolveFile(@NonNull String[] path) {
public FullRawFileObject resolveFile(@NonNull String[] path) {
List<String> pathList = removeEmptyPath(path);
String targetFileName = pathList.get(pathList.size() - 1);
int lastDotIndex = targetFileName.lastIndexOf('.');
@ -33,13 +38,15 @@ public class ReverseResolver extends SimplePathResolver {
name = targetFileName.substring(0, lastDotIndex);
ext = targetFileName.substring(lastDotIndex + 1);
}
List<RawFileObject> rawFileObjects = new ArrayList<>();
fileMapper.selectListByCondition(FILE_VO.NAME.eq(name).and(FILE_VO.EXT.eq(ext))).stream().map(RawFileObject::of).forEach(rawFileObjects::add);
folderMapper.selectListByCondition(FOLDER_VO.NAME.eq(name)).stream().map(RawFileObject::of).forEach(rawFileObjects::add);
QueryCondition extCondition = FULL_RAW_FILE_OBJECT.EXT.eq(ext);
if (ext.isEmpty()) {
extCondition = extCondition.or(FULL_RAW_FILE_OBJECT.EXT.isNull());
}
List<FullRawFileObject> rawFileObjects = this.rawFileMapper.selectListByCondition(FULL_RAW_FILE_OBJECT.NAME.eq(name).and(extCondition));
if (rawFileObjects.isEmpty()) return null;
if (rawFileObjects.size() == 1) return rawFileObjects.get(0);
List<Long> possibleParents = rawFileObjects.stream().map(RawFileObject::getParent).toList();
Optional<RawFileObject> inRoot = rawFileObjects.stream().filter(e -> e.getParent() == -1).findFirst();
Optional<FullRawFileObject> inRoot = rawFileObjects.stream().filter(e -> e.getParent() == -1).findFirst();
if (inRoot.isPresent()) {
if (pathList.size() <= 1) {
return inRoot.get();
@ -48,7 +55,7 @@ public class ReverseResolver extends SimplePathResolver {
List<FolderVo> folderVos = folderMapper.selectListByIds(possibleParents);
FolderVo parent = reverseFindFileParent(folderVos, pathList.subList(0, pathList.size() - 1));
if (parent == null) return null;
Optional<RawFileObject> first = rawFileObjects.stream().filter(e -> e.getParent() == parent.getId()).findFirst();
Optional<FullRawFileObject> first = rawFileObjects.stream().filter(e -> e.getParent() == parent.getId()).findFirst();
return first.orElse(null);
}

View File

@ -1,10 +1,12 @@
package cn.wzpmc.filemanager.interfaces.impl;
import cn.wzpmc.filemanager.entities.files.RawFileObject;
import cn.wzpmc.filemanager.entities.vo.FileVo;
import cn.wzpmc.filemanager.entities.files.FullRawFileObject;
import cn.wzpmc.filemanager.entities.files.enums.FileType;
import cn.wzpmc.filemanager.entities.vo.FolderVo;
import cn.wzpmc.filemanager.mapper.FileMapper;
import cn.wzpmc.filemanager.mapper.FolderMapper;
import cn.wzpmc.filemanager.mapper.RawFileMapper;
import com.mybatisflex.core.query.QueryCondition;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
@ -12,23 +14,26 @@ import org.springframework.stereotype.Component;
import java.util.List;
import static cn.wzpmc.filemanager.entities.files.table.FullRawFileObjectTableDef.FULL_RAW_FILE_OBJECT;
import static cn.wzpmc.filemanager.entities.vo.table.FileVoTableDef.FILE_VO;
import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO;
@Component
public class SimpleResolver extends SimplePathResolver {
private final RawFileMapper rawFileMapper;
@Autowired
public SimpleResolver(FileMapper fileMapper, FolderMapper folderMapper) {
public SimpleResolver(FileMapper fileMapper, FolderMapper folderMapper, RawFileMapper rawFileMapper) {
super(fileMapper, folderMapper);
this.rawFileMapper = rawFileMapper;
}
@Nullable
@Override
public RawFileObject resolveFile(@NonNull String[] path) {
public FullRawFileObject resolveFile(@NonNull String[] path) {
return resolveFile(removeEmptyPath(path), -1);
}
private RawFileObject resolveFile(List<String> path, long parentId) {
private FullRawFileObject resolveFile(List<String> path, long parentId) {
String currentLayerName = path.get(0);
if (path.size() == 1) {
int lastDotIndex = currentLayerName.lastIndexOf('.');
@ -38,11 +43,19 @@ public class SimpleResolver extends SimplePathResolver {
name = currentLayerName.substring(0, lastDotIndex);
ext = currentLayerName.substring(lastDotIndex + 1);
}
FileVo file = fileMapper.selectOneByCondition(FILE_VO.NAME.eq(name).and(FILE_VO.EXT.eq(ext)).and(FILE_VO.FOLDER.eq(parentId)));
if (file != null) {
return RawFileObject.of(file);
QueryCondition extCondition = FULL_RAW_FILE_OBJECT.EXT.eq(ext);
if (ext.isEmpty()) {
extCondition = extCondition.or(FULL_RAW_FILE_OBJECT.EXT.isNull());
}
return RawFileObject.of(folderMapper.selectOneByCondition(FOLDER_VO.NAME.eq(name).and(FOLDER_VO.PARENT.eq(parentId))));
List<FullRawFileObject> files = rawFileMapper.selectListByCondition(FULL_RAW_FILE_OBJECT.NAME.eq(name).and(extCondition).and(FILE_VO.FOLDER.eq(parentId)));
int size = files.size();
if (size == 0) {
return null;
}
if (size == 1) {
return files.get(0);
}
return files.stream().filter(e -> e.getType().equals(FileType.FILE)).findFirst().orElse(null);
}
FolderVo folderVo = folderMapper.selectOneByCondition(FOLDER_VO.NAME.eq(currentLayerName).and(FOLDER_VO.PARENT.eq(parentId)));
if (folderVo == null) {

View File

@ -8,4 +8,5 @@ public interface InitializationMapper {
void createFolderTable();
void createStatisticsTable();
void createUserTable();
void createRawFileView();
}

View File

@ -0,0 +1,9 @@
package cn.wzpmc.filemanager.mapper;
import cn.wzpmc.filemanager.entities.files.FullRawFileObject;
import com.mybatisflex.core.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface RawFileMapper extends BaseMapper<FullRawFileObject> {
}

View File

@ -4,9 +4,10 @@ import cn.wzpmc.filemanager.config.FileManagerProperties;
import cn.wzpmc.filemanager.entities.PageResult;
import cn.wzpmc.filemanager.entities.Result;
import cn.wzpmc.filemanager.entities.files.FolderCreateRequest;
import cn.wzpmc.filemanager.entities.files.NamedRawFile;
import cn.wzpmc.filemanager.entities.files.FullRawFileObject;
import cn.wzpmc.filemanager.entities.files.RawFileObject;
import cn.wzpmc.filemanager.entities.files.enums.FileType;
import cn.wzpmc.filemanager.entities.files.enums.SortField;
import cn.wzpmc.filemanager.entities.statistics.enums.Actions;
import cn.wzpmc.filemanager.entities.user.enums.Auth;
import cn.wzpmc.filemanager.entities.vo.FileVo;
@ -15,6 +16,7 @@ import cn.wzpmc.filemanager.entities.vo.UserVo;
import cn.wzpmc.filemanager.interfaces.FilePathService;
import cn.wzpmc.filemanager.mapper.FileMapper;
import cn.wzpmc.filemanager.mapper.FolderMapper;
import cn.wzpmc.filemanager.mapper.RawFileMapper;
import cn.wzpmc.filemanager.utils.JwtUtils;
import cn.wzpmc.filemanager.utils.RandomUtils;
import cn.wzpmc.filemanager.utils.SizeStatisticsDigestInputStream;
@ -51,6 +53,7 @@ import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import static cn.wzpmc.filemanager.entities.files.table.FullRawFileObjectTableDef.FULL_RAW_FILE_OBJECT;
import static cn.wzpmc.filemanager.entities.vo.table.FileVoTableDef.FILE_VO;
import static cn.wzpmc.filemanager.entities.vo.table.FolderVoTableDef.FOLDER_VO;
import static cn.wzpmc.filemanager.entities.vo.table.UserVoTableDef.USER_VO;
@ -62,6 +65,7 @@ import static com.mybatisflex.core.query.QueryMethods.*;
public class FileService {
private final FileMapper fileMapper;
private final FolderMapper folderMapper;
private final RawFileMapper rawFileMapper;
private final RandomUtils randomUtils;
private final FileManagerProperties properties;
private final StatisticsService statisticsService;
@ -197,19 +201,14 @@ public class FileService {
return folderParams;
}
public Result<PageResult<NamedRawFile>> getFilePager(long page, int num, long folder) {
QueryWrapper folderQueryWrapper = queryRawFolderWithOwnerName().where(FOLDER_VO.PARENT.eq(folder));
QueryWrapper fileQueryWrapper = queryRawFileWithOwnerName().where(FILE_VO.FOLDER.eq(folder));
QueryWrapper queryWrapper = fileQueryWrapper.unionAll(folderQueryWrapper)
.orderBy(
column("time").
asc(),
column("id").
asc()
);
long totalCount = fileMapper.selectCountByCondition(FILE_VO.FOLDER.eq(folder)) + folderMapper.selectCountByCondition(FOLDER_VO.PARENT.eq(folder));
Page<NamedRawFile> paginate = fileMapper.paginateAs(page, num, totalCount, queryWrapper, NamedRawFile.class);
PageResult<NamedRawFile> result = new PageResult<>(paginate.getTotalRow(), paginate.getRecords());
public Result<PageResult<FullRawFileObject>> getFilePager(long page, int num, long folder, SortField sort, boolean reverse) {
QueryWrapper from = QueryWrapper.create().select(FULL_RAW_FILE_OBJECT.ALL_COLUMNS).from(FULL_RAW_FILE_OBJECT).where(FULL_RAW_FILE_OBJECT.PARENT.eq(folder));
if (sort != SortField.ID) {
from = from.orderBy(sort.column, reverse);
}
from = from.orderBy(FULL_RAW_FILE_OBJECT.ID, reverse);
Page<FullRawFileObject> paginate = rawFileMapper.paginate(page, num, from);
PageResult<FullRawFileObject> result = new PageResult<>(paginate.getTotalRow(), paginate.getRecords());
return Result.success(result);
}
@ -327,7 +326,7 @@ public class FileService {
FileVo fileVo = fileMapper.selectOneById(id);
long fileId = fileVo.getId();
String identify = ID_ADDR_PREFIX + fileId + address;
String link = idAddrLinkMapper.opsForValue().get(identify);
String link = null;// idAddrLinkMapper.opsForValue().get(identify);
if (link == null) {
link = randomUtils.generatorRandomFileName(8);
String authorization = request.getHeader("Authorization");
@ -353,9 +352,9 @@ public class FileService {
return Result.success(linkName);
}
public Result<RawFileObject> resolveFileDetail(String path) {
public Result<FullRawFileObject> resolveFileDetail(String path) {
String[] split = path.split(PATH_SEPARATOR);
RawFileObject fileObj = pathService.resolveFile(split);
FullRawFileObject fileObj = pathService.resolveFile(split);
if (fileObj == null) {
return Result.failed(HttpStatus.NOT_FOUND, "文件不存在!");
}
@ -378,16 +377,16 @@ public class FileService {
return Result.success("成功", pathService.getFilePath(RawFileObject.of(folderVo)));
}
public Result<NamedRawFile> getFile(long id) {
NamedRawFile fileVo = this.fileMapper.selectOneByQueryAs(queryRawFileWithOwnerName().where(FILE_VO.ID.eq(id)), NamedRawFile.class);
public Result<FullRawFileObject> getFile(long id) {
FullRawFileObject fileVo = this.rawFileMapper.selectOneByCondition(FULL_RAW_FILE_OBJECT.TYPE.eq(FileType.FILE).and(FULL_RAW_FILE_OBJECT.ID.eq(id)));
if (fileVo == null) {
return Result.failed(HttpStatus.NOT_FOUND, "未知文件");
}
return Result.success(fileVo);
}
public Result<NamedRawFile> getFolder(long id) {
NamedRawFile fileVo = this.folderMapper.selectOneByQueryAs(queryRawFolderWithOwnerName().where(FOLDER_VO.ID.eq(id)), NamedRawFile.class);
public Result<FullRawFileObject> getFolder(long id) {
FullRawFileObject fileVo = this.rawFileMapper.selectOneByCondition(FULL_RAW_FILE_OBJECT.TYPE.eq(FileType.FOLDER).and(FULL_RAW_FILE_OBJECT.ID.eq(id)));
if (fileVo == null) {
return Result.failed(HttpStatus.NOT_FOUND, "未知文件");
}

View File

@ -0,0 +1,20 @@
package cn.wzpmc.filemanager.utils;
import com.mybatisflex.core.query.FunctionQueryColumn;
import com.mybatisflex.core.query.QueryColumn;
import static com.mybatisflex.core.query.QueryMethods.string;
public class MyQueryMethods {
public static QueryColumn jsonReadColumn(QueryColumn column, String express) {
String[] dotedSplit = express.split("\\.");
StringBuilder sb = new StringBuilder("$");
for (String s : dotedSplit) {
sb.append('.');
sb.append('"');
sb.append(s);
sb.append('"');
}
return new FunctionQueryColumn("JSON_EXTRACT", column, string(sb.toString()));
}
}

View File

@ -44,18 +44,19 @@
COLLATE = utf8mb4_general_ci COMMENT ='文件夹';
</insert>
<insert id="createStatisticsTable">
CREATE TABLE IF NOT EXISTS `statistics`
(
CREATE TABLE IF NOT EXISTS `statistics` (
`actor` int DEFAULT NULL COMMENT '操作者',
`action` enum ('UPLOAD','DELETE','ACCESS','DOWNLOAD','SEARCH','LOGIN','INVITE','REGISTER') COLLATE utf8mb4_general_ci NOT NULL COMMENT '所做的操作',
`action` enum('UPLOAD','DELETE','ACCESS','DOWNLOAD','SEARCH','LOGIN','INVITE','REGISTER') COLLATE
utf8mb4_general_ci NOT NULL COMMENT '所做的操作',
`params` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作的参数在ACCESS操作中为空',
`time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作的时间',
`download_file_id` int GENERATED ALWAYS AS (if((`action` =
_utf8mb4'DOWNLOAD'),json_unquote(json_extract(`params`,_utf8mb4'$.id')),NULL)) VIRTUAL COMMENT '对于下载类型事件的文件ID',
KEY `action` (`action`) COMMENT '操作类型索引',
KEY `actor_index` (`actor`) COMMENT '操作者索引',
KEY `time` (`time`) COMMENT '时间索引'
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT ='统计信息';
KEY `time` (`time`) COMMENT '时间索引',
KEY `download_file_id_index` (`download_file_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='统计信息'
</insert>
<insert id="createUserTable">
CREATE TABLE IF NOT EXISTS `user`
@ -63,7 +64,8 @@
`id` int NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`name` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`password` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户密码MD5+SHA1',
`auth` enum ('admin','user') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'user' COMMENT '用户类型',
`auth` enum ('admin','user') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'user' COMMENT
'用户类型',
`banned` tinyint(1) NOT NULL DEFAULT '0' COMMENT '用户是否被封禁',
PRIMARY KEY (`id`) COMMENT 'ID索引',
UNIQUE KEY `name_pk` (`name`),
@ -74,4 +76,33 @@
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT ='用户表';
</insert>
<insert id="createRawFileView">
CREATE OR REPLACE VIEW `raw_file` AS
select `file`.`id` AS `id`,
`file`.`name` AS `name`,
`file`.`ext` AS `ext`,
`file`.`size` AS `size`,
`file`.`folder` AS `parent`,
`file`.`uploader` AS `owner`,
`user`.`name` AS `owner_name`,
`file`.`upload_time` AS `time`,
count(`statistics`.`time`) AS `down_count`,
'FILE' AS `type`
from ((`file` left join `user`
on (((`user`.`id` = `file`.`uploader`) and (`user`.`banned` = 0)))) left join `statistics`
on (((`statistics`.`action` = 'DOWNLOAD') and (`file`.`id` = `statistics`.`download_file_id`))))
group by `file`.`id`
union all
select `folder`.`id` AS `id`,
`folder`.`name` AS `name`,
NULL AS `ext`,
-(1) AS `size`,
`folder`.`parent` AS `parent`,
`folder`.`creator` AS `owner`,
`user`.`name` AS `owner_name`,
`folder`.`create_time` AS `time`,
0 AS `down_count`,
'FOLDER' AS `type`
from (`folder` left join `user` on (((`user`.`id` = `folder`.`creator`) and (`user`.`banned` = 0))));
</insert>
</mapper>

View File

@ -40,9 +40,13 @@ CREATE TABLE IF NOT EXISTS `statistics`
`action` enum ('UPLOAD','DELETE','ACCESS','DOWNLOAD','SEARCH','LOGIN','INVITE','REGISTER') COLLATE utf8mb4_general_ci NOT NULL COMMENT '所做的操作',
`params` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '操作的参数在ACCESS操作中为空',
`time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作的时间',
`download_file_id` int GENERATED ALWAYS AS (if((`action` = _utf8mb4'DOWNLOAD'),
json_unquote(json_extract(`params`, _utf8mb4'$.id')),
NULL)) VIRTUAL COMMENT '对于下载类型事件的文件ID',
KEY `action` (`action`) COMMENT '操作类型索引',
KEY `actor_index` (`actor`) COMMENT '操作者索引',
KEY `time` (`time`) COMMENT '时间索引'
KEY `time` (`time`) COMMENT '时间索引',
KEY `download_file_id_index` (`download_file_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT ='统计信息';
@ -61,3 +65,30 @@ CREATE TABLE IF NOT EXISTS `user`
AUTO_INCREMENT = 4
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_general_ci COMMENT ='用户表';
CREATE OR REPLACE VIEW `raw_file` AS
select `file`.`id` AS `id`,
`file`.`name` AS `name`,
`file`.`ext` AS `ext`,
`file`.`size` AS `size`,
`file`.`folder` AS `parent`,
`file`.`uploader` AS `owner`,
`user`.`name` AS `owner_name`,
`file`.`upload_time` AS `time`,
count(`statistics`.`time`) AS `down_count`,
'FILE' AS `type`
from ((`file` left join `user`
on (((`user`.`id` = `file`.`uploader`) and (`user`.`banned` = 0)))) left join `statistics`
on (((`statistics`.`action` = 'DOWNLOAD') and (`file`.`id` = `statistics`.`download_file_id`))))
group by `file`.`id`
union all
select `folder`.`id` AS `id`,
`folder`.`name` AS `name`,
NULL AS `ext`,
-(1) AS `size`,
`folder`.`parent` AS `parent`,
`folder`.`creator` AS `owner`,
`user`.`name` AS `owner_name`,
`folder`.`create_time` AS `time`,
0 AS `down_count`,
'FOLDER' AS `type`
from (`folder` left join `user` on (((`user`.`id` = `folder`.`creator`) and (`user`.`banned` = 0))));