commit
78a6dfa230
76 changed files with 7707 additions and 0 deletions
@ -0,0 +1,38 @@ |
|||
target/ |
|||
!.mvn/wrapper/maven-wrapper.jar |
|||
!**/src/main/**/target/ |
|||
!**/src/test/**/target/ |
|||
|
|||
### IntelliJ IDEA ### |
|||
.idea/modules.xml |
|||
.idea/jarRepositories.xml |
|||
.idea/compiler.xml |
|||
.idea/libraries/ |
|||
*.iws |
|||
*.iml |
|||
*.ipr |
|||
|
|||
### Eclipse ### |
|||
.apt_generated |
|||
.classpath |
|||
.factorypath |
|||
.project |
|||
.settings |
|||
.springBeans |
|||
.sts4-cache |
|||
|
|||
### NetBeans ### |
|||
/nbproject/private/ |
|||
/nbbuild/ |
|||
/dist/ |
|||
/nbdist/ |
|||
/.nb-gradle/ |
|||
build/ |
|||
!**/src/main/**/build/ |
|||
!**/src/test/**/build/ |
|||
|
|||
### VS Code ### |
|||
.vscode/ |
|||
|
|||
### Mac OS ### |
|||
.DS_Store |
File diff suppressed because it is too large
@ -0,0 +1,256 @@ |
|||
SET NAMES utf8mb4; |
|||
SET FOREIGN_KEY_CHECKS = 0; |
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for account |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `account`; |
|||
CREATE TABLE `account` ( |
|||
`id` bigint NOT NULL COMMENT 'ID', |
|||
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', |
|||
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', |
|||
`avatar_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '用户头像', |
|||
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '手机号', |
|||
`role` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'COMMON' COMMENT '用户角色 COMMON, ADMIN', |
|||
`del` tinyint DEFAULT '0' COMMENT '逻辑删除(1删除 0未删除)', |
|||
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
|||
PRIMARY KEY (`id`) USING BTREE |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='用户信息表'; |
|||
|
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for account_file |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `account_file`; |
|||
CREATE TABLE `account_file` ( |
|||
`id` bigint NOT NULL COMMENT 'id', |
|||
`account_id` bigint DEFAULT NULL COMMENT '用户ID', |
|||
`is_dir` int NOT NULL COMMENT '状态 0不是文件夹,1是文件夹', |
|||
`parent_id` bigint DEFAULT NULL COMMENT '上层文件夹ID,顶层文件夹为0', |
|||
`file_id` bigint DEFAULT NULL COMMENT '文件ID,真正存储的文件', |
|||
`file_name` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件名称', |
|||
`file_type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件类型:普通文件common 、压缩文件compress 、 excel 、 word 、 pdf 、 txt 、 图片img 、音频audio 、视频video 、ppt 、源码文件code 、 csv', |
|||
`file_suffix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件的后缀拓展名', |
|||
`file_size` bigint DEFAULT NULL COMMENT '文件大小,字节', |
|||
`del` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除(0未删除,1已删除)', |
|||
`del_time` datetime DEFAULT NULL COMMENT '删除日期', |
|||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
|||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
PRIMARY KEY (`id`) USING BTREE |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='用户文件表'; |
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for file |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `file`; |
|||
CREATE TABLE `file` ( |
|||
`id` bigint NOT NULL COMMENT '文件id', |
|||
`account_id` bigint DEFAULT NULL COMMENT '用户id,是哪个用户初次上传的', |
|||
`file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件名称,秒传需要用到,冗余存储', |
|||
`file_suffix` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件的后缀拓展名,冗余存储', |
|||
`file_size` bigint DEFAULT NULL COMMENT '文件大小,字节,冗余存储', |
|||
`object_key` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文件的key, 格式 日期/md5.拓展名,比如 2024-11-13/921674fd-cdaf-459a-be7b-109469e7050d.png', |
|||
`identifier` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '唯一标识,文件MD5', |
|||
`del` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除(0未删除,1已删除)', |
|||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
|||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
PRIMARY KEY (`id`) USING BTREE |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='用户文件表'; |
|||
|
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for file_chunk |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `file_chunk`; |
|||
CREATE TABLE `file_chunk` ( |
|||
`id` bigint NOT NULL, |
|||
`identifier` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件唯一标识(md5)', |
|||
`upload_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '分片上传ID', |
|||
`file_name` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件名', |
|||
`bucket_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所属桶名', |
|||
`object_key` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件的key', |
|||
`total_size` bigint NOT NULL COMMENT '总文件大小(byte)', |
|||
`chunk_size` bigint NOT NULL COMMENT '每个分片大小(byte)', |
|||
`chunk_num` int NOT NULL COMMENT '分片数量', |
|||
`account_id` bigint NOT NULL COMMENT '用户ID', |
|||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP, |
|||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
|||
PRIMARY KEY (`id`) USING BTREE, |
|||
UNIQUE KEY `uq_file_identifier` (`identifier`) USING BTREE |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='文件分片信息表'; |
|||
|
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for file_suffix |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `file_suffix`; |
|||
CREATE TABLE `file_suffix` ( |
|||
`id` int NOT NULL AUTO_INCREMENT, |
|||
`file_suffix` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '文件扩展名', |
|||
`file_type_id` int NOT NULL COMMENT '文件类型ID', |
|||
PRIMARY KEY (`id`) USING BTREE, |
|||
KEY `fk_file_type_id` (`file_type_id`) USING BTREE, |
|||
CONSTRAINT `fk_file_type_id` FOREIGN KEY (`file_type_id`) REFERENCES `file_type` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT |
|||
) ENGINE=InnoDB AUTO_INCREMENT=80 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='文件分类表'; |
|||
|
|||
-- ---------------------------- |
|||
-- Records of file_suffix |
|||
-- ---------------------------- |
|||
BEGIN; |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (1, 'jpg', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (2, 'jpeg', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (3, 'png', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (4, 'gif', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (5, 'bmp', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (6, 'tiff', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (7, 'svg', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (8, 'ico', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (9, 'webp', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (10, 'heic', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (11, 'psd', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (12, 'ai', 1); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (13, 'mp4', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (14, 'avi', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (15, 'mkv', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (16, 'flv', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (17, 'mov', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (18, 'wmv', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (19, 'mpeg', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (20, 'rmvb', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (21, '3gp', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (22, 'webm', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (23, 'm4v', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (24, 'ts', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (25, 'vob', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (26, 'm2ts', 2); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (27, 'mp3', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (28, 'wav', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (29, 'flac', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (30, 'aac', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (31, 'ogg', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (32, 'wma', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (33, 'm4a', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (34, 'mid', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (35, 'aiff', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (36, 'alac', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (37, 'pcm', 3); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (38, 'doc', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (39, 'docx', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (40, 'pdf', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (41, 'txt', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (42, 'ppt', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (43, 'pptx', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (44, 'xls', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (45, 'xlsx', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (46, 'odt', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (47, 'rtf', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (48, 'csv', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (49, 'md', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (50, 'epub', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (51, 'mobi', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (52, 'tex', 4); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (53, 'zip', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (54, 'rar', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (55, '7z', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (56, 'tar', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (57, 'gz', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (58, 'bz2', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (59, 'xz', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (60, 'iso', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (61, 'z', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (62, 'tgz', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (63, 'dmg', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (64, 'cbr', 5); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (65, 'exe', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (66, 'bat', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (67, 'sh', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (68, 'apk', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (69, 'iso', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (70, 'bin', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (71, 'torrent', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (72, 'bak', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (73, 'dll', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (74, 'deb', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (75, 'rpm', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (76, 'msi', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (77, 'vmdk', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (78, 'vdi', 6); |
|||
INSERT INTO `file_suffix` (`id`, `file_suffix`, `file_type_id`) VALUES (79, 'qcow2', 6); |
|||
COMMIT; |
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for file_type |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `file_type`; |
|||
CREATE TABLE `file_type` ( |
|||
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', |
|||
`file_type_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '文件类型名', |
|||
PRIMARY KEY (`id`) USING BTREE |
|||
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='文件类型表'; |
|||
|
|||
-- ---------------------------- |
|||
-- Records of file_type |
|||
-- ---------------------------- |
|||
BEGIN; |
|||
INSERT INTO `file_type` (`id`, `file_type_name`) VALUES (1, '图片'); |
|||
INSERT INTO `file_type` (`id`, `file_type_name`) VALUES (2, '视频'); |
|||
INSERT INTO `file_type` (`id`, `file_type_name`) VALUES (3, '音频'); |
|||
INSERT INTO `file_type` (`id`, `file_type_name`) VALUES (4, '文档'); |
|||
INSERT INTO `file_type` (`id`, `file_type_name`) VALUES (5, '压缩'); |
|||
INSERT INTO `file_type` (`id`, `file_type_name`) VALUES (6, '其他'); |
|||
INSERT INTO `file_type` (`id`, `file_type_name`) VALUES (7, '全部'); |
|||
COMMIT; |
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for share |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `share`; |
|||
CREATE TABLE `share` ( |
|||
`id` bigint NOT NULL COMMENT '分享id', |
|||
`share_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '分享名称', |
|||
`share_type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '0' COMMENT '分享类型(no_code没有提取码 ,need_code有提取码)', |
|||
`share_day_type` int NOT NULL DEFAULT '0' COMMENT '分享类型(0 永久有效;1: 7天有效;2: 30天有效)', |
|||
`share_day` int NOT NULL DEFAULT '0' COMMENT '分享有效天数(永久有效为0)', |
|||
`share_end_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '分享结束时间', |
|||
`share_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '分享链接地址', |
|||
`share_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '分享提取码', |
|||
`share_status` varchar(255) COLLATE utf8mb4_bin NOT NULL DEFAULT '0' COMMENT '分享状态 used正常, expired已失效, cancled取消', |
|||
`account_id` bigint NOT NULL COMMENT '分享创建人', |
|||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
|||
PRIMARY KEY (`id`) USING BTREE, |
|||
UNIQUE KEY `uk_create_user_time` (`account_id`,`gmt_create`) USING BTREE COMMENT '创建人、创建时间唯一索引' |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='用户分享表'; |
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for share_file |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `share_file`; |
|||
CREATE TABLE `share_file` ( |
|||
`id` bigint NOT NULL COMMENT '主键ID', |
|||
`share_id` bigint NOT NULL COMMENT '分享id', |
|||
`account_file_id` bigint NOT NULL COMMENT '用户文件的ID', |
|||
`account_id` bigint NOT NULL COMMENT '创建者id', |
|||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '分享时间', |
|||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
|||
PRIMARY KEY (`id`) USING BTREE |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='文件分享表'; |
|||
|
|||
-- ---------------------------- |
|||
-- Table structure for storage |
|||
-- ---------------------------- |
|||
DROP TABLE IF EXISTS `storage`; |
|||
CREATE TABLE `storage` ( |
|||
`id` bigint NOT NULL, |
|||
`account_id` bigint DEFAULT NULL COMMENT '所属用户', |
|||
`used_size` bigint DEFAULT NULL COMMENT '占用存储大小', |
|||
`total_size` bigint DEFAULT NULL COMMENT '总容量大小,字节存储', |
|||
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |
|||
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', |
|||
PRIMARY KEY (`id`) USING BTREE, |
|||
UNIQUE KEY `userid_index` (`account_id`) USING BTREE |
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='存储信息表'; |
|||
|
|||
|
|||
SET FOREIGN_KEY_CHECKS = 1; |
@ -0,0 +1,149 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" |
|||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
<parent> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-parent</artifactId> |
|||
<version>3.2.4</version> |
|||
</parent> |
|||
|
|||
<groupId>org.ycloud.aipan</groupId> |
|||
<artifactId>ycloud-aipan</artifactId> |
|||
<version>1.0-SNAPSHOT</version> |
|||
<description>智能云盘</description> |
|||
|
|||
<properties> |
|||
<java.version>21</java.version> |
|||
<aws-java-sdk-s3.version>1.12.730</aws-java-sdk-s3.version> |
|||
<mybatisplus.version>3.5.6</mybatisplus.version> |
|||
<hutool-all.version>5.8.27</hutool-all.version> |
|||
<common-io.version>2.8.0</common-io.version> |
|||
<fastjson.version>2.0.42</fastjson.version> |
|||
<mysql.version>8.0.27</mysql.version> |
|||
</properties> |
|||
<dependencies> |
|||
<!-- Spring Boot Web依赖 --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-web</artifactId> |
|||
</dependency> |
|||
|
|||
<!-- 切面 --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-aop</artifactId> |
|||
</dependency> |
|||
|
|||
<!-- 数据库连接 --> |
|||
<dependency> |
|||
<groupId>mysql</groupId> |
|||
<artifactId>mysql-connector-java</artifactId> |
|||
<version>${mysql.version}</version> |
|||
</dependency> |
|||
|
|||
<!-- Lombok依赖 --> |
|||
<dependency> |
|||
<groupId>org.projectlombok</groupId> |
|||
<artifactId>lombok</artifactId> |
|||
<version>1.18.30</version> |
|||
</dependency> |
|||
|
|||
<!-- 测试依赖 --> |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-test</artifactId> |
|||
<scope>test</scope> |
|||
</dependency> |
|||
|
|||
<!-- AWS S3 SDK --> |
|||
<dependency> |
|||
<groupId>com.amazonaws</groupId> |
|||
<artifactId>aws-java-sdk-s3</artifactId> |
|||
<version>${aws-java-sdk-s3.version}</version> |
|||
</dependency> |
|||
|
|||
<!-- JWT支持 --> |
|||
<dependency> |
|||
<groupId>io.jsonwebtoken</groupId> |
|||
<artifactId>jjwt</artifactId> |
|||
<version>0.12.3</version> |
|||
</dependency> |
|||
|
|||
<!-- MyBatis-Plus依赖 --> |
|||
<dependency> |
|||
<groupId>com.baomidou</groupId> |
|||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId> |
|||
<version>${mybatisplus.version}</version> |
|||
</dependency> |
|||
<!-- 代码自动生成依赖 begin --> |
|||
<dependency> |
|||
<groupId>com.baomidou</groupId> |
|||
<artifactId>mybatis-plus-generator</artifactId> |
|||
<version>${mybatisplus.version}</version> |
|||
</dependency> |
|||
<!-- velocity --> |
|||
<dependency> |
|||
<groupId>org.apache.velocity</groupId> |
|||
<artifactId>velocity-engine-core</artifactId> |
|||
<version>2.0</version> |
|||
</dependency> |
|||
<!-- 代码自动生成依赖 end--> |
|||
|
|||
<!-- Hutool依赖 --> |
|||
<dependency> |
|||
<groupId>cn.hutool</groupId> |
|||
<artifactId>hutool-all</artifactId> |
|||
<version>${hutool-all.version}</version> |
|||
</dependency> |
|||
|
|||
<!-- Fastjson依赖 --> |
|||
<dependency> |
|||
<groupId>com.alibaba</groupId> |
|||
<artifactId>fastjson</artifactId> |
|||
<version>${fastjson.version}</version> |
|||
</dependency> |
|||
|
|||
<!-- knife4j 依赖,接口文档工具 --> |
|||
<dependency> |
|||
<groupId>com.github.xiaoymin</groupId> |
|||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> |
|||
<version>4.4.0</version> |
|||
</dependency> |
|||
|
|||
<!-- AWS S3 SDK --> |
|||
<dependency> |
|||
<groupId>com.amazonaws</groupId> |
|||
<artifactId>aws-java-sdk-s3</artifactId> |
|||
<version>${aws-java-sdk-s3.version}</version> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-maven-plugin</artifactId> |
|||
</plugin> |
|||
|
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-compiler-plugin</artifactId> |
|||
<version>3.1</version> |
|||
<configuration> |
|||
<source>${java.version}</source> |
|||
<target>${java.version}</target> |
|||
</configuration> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-surefire-plugin</artifactId> |
|||
<version>2.19.1</version> |
|||
<configuration> |
|||
<skipTests>true</skipTests> |
|||
</configuration> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
</project> |
@ -0,0 +1,36 @@ |
|||
package org.ycloud.aipan; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.mybatis.spring.annotation.MapperScan; |
|||
import org.springframework.boot.SpringApplication; |
|||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
|||
import org.springframework.context.ConfigurableApplicationContext; |
|||
import org.springframework.core.env.Environment; |
|||
import org.springframework.transaction.annotation.EnableTransactionManagement; |
|||
|
|||
import java.net.InetAddress; |
|||
|
|||
|
|||
@Slf4j |
|||
@SpringBootApplication |
|||
@EnableTransactionManagement |
|||
@MapperScan("org.ycloud.aipan.mapper") |
|||
public class CloudApplication { |
|||
|
|||
public static void main(String[] args) throws Exception { |
|||
ConfigurableApplicationContext application = SpringApplication.run(CloudApplication.class, args); |
|||
Environment env = application.getEnvironment(); |
|||
log.info("\n----------------------------------------------------------\n\t" + |
|||
"Application '{}' is running! Access URLs:\n\t" + |
|||
"Local: \t\thttp://localhost:{}\n\t" + |
|||
"External: \thttp://{}:{}\n\t" + |
|||
"API文档: \thttp://{}:{}/doc.html\n" + |
|||
"----------------------------------------------------------", |
|||
env.getProperty("spring.application.name"), |
|||
env.getProperty("server.port"), |
|||
InetAddress.getLocalHost().getHostAddress(), |
|||
env.getProperty("server.port"), |
|||
InetAddress.getLocalHost().getHostAddress(), |
|||
env.getProperty("server.port")); |
|||
} |
|||
} |
@ -0,0 +1,68 @@ |
|||
package org.ycloud.aipan.component; |
|||
|
|||
import com.amazonaws.services.s3.model.Bucket; |
|||
import com.amazonaws.services.s3.model.S3ObjectSummary; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
//@Component
|
|||
public class LocalFileStoreEngine implements StoreEngine{ |
|||
@Override |
|||
public boolean bucketExists(String bucketName) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean removeBucket(String bucketName) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public void createBucket(String bucketName) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public List<Bucket> getAllBucket() { |
|||
return List.of(); |
|||
} |
|||
|
|||
@Override |
|||
public List<S3ObjectSummary> listObjects(String bucketName) { |
|||
return List.of(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean doesObjectExist(String bucketName, String objectKey) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean upload(String bucketName, String objectKey, String localFileName) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean upload(String bucketName, String objectKey, MultipartFile file) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean delete(String bucketName, String objectKey) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public String getDownloadUrl(String bucketName, String remoteFileName, long timeout, TimeUnit unit) { |
|||
return ""; |
|||
} |
|||
|
|||
@Override |
|||
public void download2Response(String bucketName, String objectKey, HttpServletResponse response) { |
|||
|
|||
} |
|||
} |
@ -0,0 +1,226 @@ |
|||
package org.ycloud.aipan.component; |
|||
|
|||
import com.amazonaws.services.s3.AmazonS3Client; |
|||
import com.amazonaws.services.s3.model.Bucket; |
|||
import com.amazonaws.services.s3.model.ObjectMetadata; |
|||
import com.amazonaws.services.s3.model.S3Object; |
|||
import com.amazonaws.services.s3.model.S3ObjectSummary; |
|||
import jakarta.annotation.Resource; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.tomcat.util.http.fileupload.IOUtils; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
import java.util.Date; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.concurrent.ConcurrentHashMap; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
@Slf4j |
|||
@Component |
|||
public class MinIOFileStoreEngine implements StoreEngine { |
|||
|
|||
@Resource |
|||
private AmazonS3Client amazonS3Client; |
|||
|
|||
// 缓存 bucket 存在性
|
|||
private final Map<String, Boolean> bucketCache = new ConcurrentHashMap<>(); |
|||
|
|||
/** |
|||
* 检查 bucket 是否存在 |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @return 如果 bucket 存在返回 true,否则返回 false |
|||
*/ |
|||
@Override |
|||
public boolean bucketExists(String bucketName) { |
|||
return bucketCache.computeIfAbsent(bucketName, key -> amazonS3Client.doesBucketExistV2(key)); |
|||
} |
|||
|
|||
/** |
|||
* 删除 bucket |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @return 如果删除成功返回 true,否则返回 false |
|||
*/ |
|||
@Override |
|||
public boolean removeBucket(String bucketName) { |
|||
if (bucketExists(bucketName)) { |
|||
try { |
|||
amazonS3Client.deleteBucket(bucketName); |
|||
bucketCache.remove(bucketName); |
|||
return true; |
|||
} catch (Exception e) { |
|||
log.error("删除 bucket {} 失败: {}", bucketName, e.getMessage(), e); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 创建 bucket |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
*/ |
|||
@Override |
|||
public void createBucket(String bucketName) { |
|||
log.info("创建 bucket: {}", bucketName); |
|||
if (!bucketExists(bucketName)) { |
|||
try { |
|||
amazonS3Client.createBucket(bucketName); |
|||
bucketCache.put(bucketName, true); |
|||
} catch (Exception e) { |
|||
log.error("创建 bucket {} 失败: {}", bucketName, e.getMessage(), e); |
|||
} |
|||
} else { |
|||
log.info("bucket 已存在"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取所有 bucket 列表 |
|||
* |
|||
* @return bucket 列表 |
|||
*/ |
|||
@Override |
|||
public List<Bucket> getAllBucket() { |
|||
return amazonS3Client.listBuckets(); |
|||
} |
|||
|
|||
/** |
|||
* 列出 bucket 中的所有对象 |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @return 对象列表 |
|||
*/ |
|||
@Override |
|||
public List<S3ObjectSummary> listObjects(String bucketName) { |
|||
if (bucketExists(bucketName)) { |
|||
return amazonS3Client.listObjects(bucketName).getObjectSummaries(); |
|||
} |
|||
return List.of(); |
|||
} |
|||
|
|||
/** |
|||
* 检查对象是否存在 |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @param objectKey 对象键 |
|||
* @return 如果对象存在返回 true,否则返回 false |
|||
*/ |
|||
@Override |
|||
public boolean doesObjectExist(String bucketName, String objectKey) { |
|||
if (bucketExists(bucketName)) { |
|||
return amazonS3Client.doesObjectExist(bucketName, objectKey); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 上传本地文件到 bucket |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @param objectKey 对象键 |
|||
* @param localFileName 本地文件路径 |
|||
* @return 如果上传成功返回 true,否则返回 false |
|||
*/ |
|||
@Override |
|||
public boolean upload(String bucketName, String objectKey, String localFileName) { |
|||
if (bucketExists(bucketName)) { |
|||
try { |
|||
amazonS3Client.putObject(bucketName, objectKey, new File(localFileName)); |
|||
return true; |
|||
} catch (Exception e) { |
|||
log.error("上传文件到 bucket {} 失败: {}", bucketName, e.getMessage(), e); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 上传 MultipartFile 到 bucket |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @param objectKey 对象键 |
|||
* @param file MultipartFile 对象 |
|||
* @return 如果上传成功返回 true,否则返回 false |
|||
*/ |
|||
@Override |
|||
public boolean upload(String bucketName, String objectKey, MultipartFile file) { |
|||
if (bucketExists(bucketName)) { |
|||
try { |
|||
ObjectMetadata objectMetadata = new ObjectMetadata(); |
|||
objectMetadata.setContentType(file.getContentType()); |
|||
objectMetadata.setContentLength(file.getSize()); |
|||
amazonS3Client.putObject(bucketName, objectKey, file.getInputStream(), objectMetadata); |
|||
return true; |
|||
} catch (Exception e) { |
|||
log.error("上传文件到 bucket {} 失败: {}", bucketName, e.getMessage(), e); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 删除 bucket 中的对象 |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @param objectKey 对象键 |
|||
* @return 如果删除成功返回 true,否则返回 false |
|||
*/ |
|||
@Override |
|||
public boolean delete(String bucketName, String objectKey) { |
|||
if (bucketExists(bucketName)) { |
|||
try { |
|||
amazonS3Client.deleteObject(bucketName, objectKey); |
|||
return true; |
|||
} catch (Exception e) { |
|||
log.error("删除 bucket {} 中的对象 {} 失败: {}", bucketName, objectKey, e.getMessage(), e); |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
/** |
|||
* 获取对象的下载 URL |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @param objectKey 对象键 |
|||
* @param timeout 过期时间 |
|||
* @param unit 时间单位 |
|||
* @return 下载 URL 字符串 |
|||
*/ |
|||
@Override |
|||
public String getDownloadUrl(String bucketName, String objectKey, long timeout, TimeUnit unit) { |
|||
try { |
|||
Date expiration = new Date(System.currentTimeMillis() + unit.toMillis(timeout)); |
|||
return amazonS3Client.generatePresignedUrl(bucketName, objectKey, expiration).toString(); |
|||
} catch (Exception e) { |
|||
log.error("生成 bucket {} 中对象 {} 的下载 URL 失败: {}", bucketName, objectKey, e.getMessage(), e); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 下载对象到 HttpServletResponse |
|||
* |
|||
* @param bucketName bucket 名称 |
|||
* @param objectKey 对象键 |
|||
* @param response HttpServletResponse 对象 |
|||
*/ |
|||
@Override |
|||
public void download2Response(String bucketName, String objectKey, HttpServletResponse response) { |
|||
try (S3Object s3Object = amazonS3Client.getObject(bucketName, objectKey)) { |
|||
response.setHeader("Content-Disposition", "attachment;filename=" + objectKey.substring(objectKey.lastIndexOf("/") + 1)); |
|||
response.setContentType("application/force-download"); |
|||
response.setCharacterEncoding("UTF-8"); |
|||
IOUtils.copy(s3Object.getObjectContent(), response.getOutputStream()); |
|||
} catch (IOException e) { |
|||
log.error("下载 bucket {} 中对象 {} 失败: {}", bucketName, objectKey, e.getMessage(), e); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,68 @@ |
|||
package org.ycloud.aipan.component; |
|||
|
|||
import com.amazonaws.services.s3.model.Bucket; |
|||
import com.amazonaws.services.s3.model.S3ObjectSummary; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
|
|||
//@Component
|
|||
public class OSSFileStoreEngine implements StoreEngine{ |
|||
@Override |
|||
public boolean bucketExists(String bucketName) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean removeBucket(String bucketName) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public void createBucket(String bucketName) { |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public List<Bucket> getAllBucket() { |
|||
return List.of(); |
|||
} |
|||
|
|||
@Override |
|||
public List<S3ObjectSummary> listObjects(String bucketName) { |
|||
return List.of(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean doesObjectExist(String bucketName, String objectKey) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean upload(String bucketName, String objectKey, String localFileName) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean upload(String bucketName, String objectKey, MultipartFile file) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean delete(String bucketName, String objectKey) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public String getDownloadUrl(String bucketName, String remoteFileName, long timeout, TimeUnit unit) { |
|||
return ""; |
|||
} |
|||
|
|||
@Override |
|||
public void download2Response(String bucketName, String objectKey, HttpServletResponse response) { |
|||
|
|||
} |
|||
} |
@ -0,0 +1,110 @@ |
|||
package org.ycloud.aipan.component; |
|||
|
|||
import com.amazonaws.services.s3.model.Bucket; |
|||
import com.amazonaws.services.s3.model.S3ObjectSummary; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
import java.util.List; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
public interface StoreEngine { |
|||
|
|||
/*=====================Bucket相关===========================*/ |
|||
|
|||
/** |
|||
* 检查指定的存储桶是否存在于当前的存储系统中 |
|||
* |
|||
* @param bucketName 存储桶的名称 |
|||
* @return 如果存储桶存在,则返回true;否则返回false |
|||
*/ |
|||
boolean bucketExists(String bucketName); |
|||
|
|||
/** |
|||
* 删除指定名称的存储桶 |
|||
* |
|||
* @param bucketName 存储桶的名称 |
|||
* @return 如果存储桶删除成功,则返回true;否则返回false |
|||
*/ |
|||
boolean removeBucket(String bucketName); |
|||
|
|||
/** |
|||
* 创建一个新的存储桶 |
|||
* |
|||
* @param bucketName 新存储桶的名称 |
|||
*/ |
|||
void createBucket(String bucketName); |
|||
|
|||
/** |
|||
* 获取当前存储系统中的所有存储桶列表 |
|||
* |
|||
* @return 包含所有存储桶的列表 |
|||
*/ |
|||
List<Bucket> getAllBucket(); |
|||
|
|||
/*===================文件处理相关=============================*/ |
|||
|
|||
/** |
|||
* 列出指定桶中的所有对象 |
|||
* |
|||
* @param bucketName 桶名称 |
|||
* @return 包含桶中所有对象摘要的列表 |
|||
*/ |
|||
List<S3ObjectSummary> listObjects(String bucketName); |
|||
|
|||
|
|||
/** |
|||
* 判断文件是否存在 |
|||
*/ |
|||
boolean doesObjectExist(String bucketName, String objectKey); |
|||
|
|||
/** |
|||
* 将本地文件上传到指定桶 |
|||
* |
|||
* @param bucketName 桶名称 |
|||
* @param objectKey 上传后对象的名称 |
|||
* @param localFileName 本地文件的路径 |
|||
* @return 上传是否成功 |
|||
*/ |
|||
boolean upload(String bucketName, String objectKey, String localFileName); |
|||
|
|||
/** |
|||
* 将multipart文件上传到指定桶 |
|||
* |
|||
* @param bucketName 桶名称 |
|||
* @param objectKey 上传后对象的名称 |
|||
* @param file 要上传的multipart文件 |
|||
* @return 上传是否成功 |
|||
*/ |
|||
boolean upload(String bucketName, String objectKey, MultipartFile file); |
|||
|
|||
/** |
|||
* 从指定桶中删除对象 |
|||
* |
|||
* @param bucketName 桶名称 |
|||
* @param objectKey 要删除的对象的名称 |
|||
* @return 删除是否成功 |
|||
*/ |
|||
boolean delete(String bucketName, String objectKey); |
|||
|
|||
/*===================下载相关=============================*/ |
|||
/** |
|||
* 获取指定对象的下载URL |
|||
* |
|||
* @param bucketName 桶名称 |
|||
* @param remoteFileName 对象的名称 |
|||
* @param timeout URL的有效时长 |
|||
* @param unit URL有效时长的时间单位 |
|||
* @return 对象的下载URL |
|||
*/ |
|||
String getDownloadUrl(String bucketName, String remoteFileName, long timeout, TimeUnit unit); |
|||
|
|||
/** |
|||
* 将指定对象下载到HTTP响应中 |
|||
* |
|||
* @param bucketName 桶名称 |
|||
* @param objectKey 对象的名称 |
|||
* @param response HTTP响应对象,用于输出下载的对象 |
|||
*/ |
|||
void download2Response(String bucketName, String objectKey, HttpServletResponse response); |
|||
} |
@ -0,0 +1,25 @@ |
|||
package org.ycloud.aipan.config; |
|||
|
|||
|
|||
public class AccountConfig { |
|||
|
|||
/** |
|||
* 账号密码加密的盐 |
|||
*/ |
|||
public static final String ACCOUNT_SALT = "cn.yuan"; |
|||
|
|||
/** |
|||
* 默认存储空间大小 100MB |
|||
*/ |
|||
public static final Long DEFAULT_STORAGE_SIZE = 1024 * 1024 * 100L; |
|||
|
|||
/** |
|||
* 根文件夹名称 |
|||
*/ |
|||
public static final String ROOT_FOLDER_NAME = "全部文件夹"; |
|||
|
|||
/** |
|||
* 根文件夹的父ID |
|||
*/ |
|||
public static final Long ROOT_PARENT_ID = 0L; |
|||
} |
@ -0,0 +1,56 @@ |
|||
package org.ycloud.aipan.config; |
|||
|
|||
import com.amazonaws.ClientConfiguration; |
|||
import com.amazonaws.Protocol; |
|||
import com.amazonaws.auth.AWSCredentials; |
|||
import com.amazonaws.auth.AWSStaticCredentialsProvider; |
|||
import com.amazonaws.auth.BasicAWSCredentials; |
|||
import com.amazonaws.client.builder.AwsClientBuilder; |
|||
import com.amazonaws.regions.Regions; |
|||
import com.amazonaws.services.s3.AmazonS3; |
|||
import com.amazonaws.services.s3.AmazonS3ClientBuilder; |
|||
import jakarta.annotation.Resource; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
|
|||
/** |
|||
* 配置类,用于定义Bean并配置Amazon S3客户端 |
|||
*/ |
|||
@Configuration |
|||
public class AmazonS3Config { |
|||
|
|||
// 注入Minio配置类,用于获取访问密钥和Endpoint等信息
|
|||
@Resource |
|||
private MinioConfig minioConfig; |
|||
|
|||
/** |
|||
* 创建并配置Amazon S3客户端 |
|||
* |
|||
* @return AmazonS3 实例,用于与Amazon S3服务进行交互 |
|||
*/ |
|||
@Bean(name = "amazonS3Client") |
|||
public AmazonS3 amazonS3Client() { |
|||
// 设置连接时的参数
|
|||
ClientConfiguration config = new ClientConfiguration(); |
|||
// 设置连接方式为HTTP,可选参数为HTTP和HTTPS
|
|||
config.setProtocol(Protocol.HTTP); |
|||
// 设置网络访问超时时间
|
|||
config.setConnectionTimeout(5000); |
|||
config.setUseExpectContinue(true); |
|||
|
|||
// 使用Minio配置中的访问密钥和秘密密钥创建AWS凭证
|
|||
AWSCredentials credentials = new BasicAWSCredentials(minioConfig.getAccessKey(), minioConfig.getAccessSecret()); |
|||
|
|||
// 设置Endpoint
|
|||
AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder |
|||
.EndpointConfiguration(minioConfig.getEndpoint(), Regions.US_EAST_1.name()); |
|||
|
|||
// 使用以上配置创建并返回Amazon S3客户端实例
|
|||
return AmazonS3ClientBuilder.standard() |
|||
.withClientConfiguration(config) |
|||
.withCredentials(new AWSStaticCredentialsProvider(credentials)) |
|||
.withEndpointConfiguration(endpointConfiguration) |
|||
.withPathStyleAccessEnabled(true).build(); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,29 @@ |
|||
package org.ycloud.aipan.config; |
|||
|
|||
import jakarta.annotation.Resource; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.context.annotation.Configuration; |
|||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
|||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
|||
import org.ycloud.aipan.interceptor.LoginInterceptor; |
|||
|
|||
|
|||
@Configuration |
|||
@Slf4j |
|||
public class InterceptorConfig implements WebMvcConfigurer { |
|||
|
|||
@Resource |
|||
private LoginInterceptor loginInterceptor; |
|||
|
|||
@Override |
|||
public void addInterceptors(InterceptorRegistry registry) { |
|||
registry.addInterceptor(loginInterceptor) |
|||
//添加拦截的路径
|
|||
.addPathPatterns("/api/account/*/**","/api/file/*/**","/api/share/*/**") |
|||
|
|||
//排除不拦截
|
|||
.excludePathPatterns("/api/account/*/register","/api/account/*/login","/api/account/*/upload_avatar", |
|||
"/api/share/*/check_share_code","/api/share/*/visit","/api/share/*/detail_no_code","/api/share/*/detail_with_code"); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,38 @@ |
|||
package org.ycloud.aipan.config; |
|||
|
|||
import io.swagger.v3.oas.models.OpenAPI; |
|||
import io.swagger.v3.oas.models.info.Contact; |
|||
import io.swagger.v3.oas.models.info.Info; |
|||
import io.swagger.v3.oas.models.info.License; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.context.annotation.Bean; |
|||
import org.springframework.context.annotation.Configuration; |
|||
|
|||
/** |
|||
* Knife4j配置 ,默认是下面 |
|||
* <p> |
|||
* knife4j 访问地址:http://localhost:8080/doc.html
|
|||
* Swagger2.0访问地址:http://localhost:8080/swagger-ui.html
|
|||
* Swagger3.0访问地址:http://localhost:8080/swagger-ui/index.html
|
|||
*/ |
|||
@Slf4j |
|||
@Configuration |
|||
public class Knife4jConfig { |
|||
@Bean |
|||
public OpenAPI customOpenAPI() { |
|||
return new OpenAPI() |
|||
.info(new Info() |
|||
.title("AI智能云盘系统 API") |
|||
.version("1.0-SNAPSHOT") |
|||
.description("AI智能云盘系统") |
|||
.termsOfService("https://www.xxx.net") |
|||
.license(new License().name("Apache 2.0").url("https://www.xxx.net")) |
|||
// 添加作者信息
|
|||
.contact(new Contact() |
|||
.name("anonymity") // 替换为作者的名字
|
|||
.email("anonymity@qq.com") // 替换为作者的电子邮件
|
|||
.url("https://www.xxx.net") // 替换为作者的网站或个人资料链接
|
|||
) |
|||
); |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
package org.ycloud.aipan.config; |
|||
|
|||
import lombok.Data; |
|||
import org.springframework.beans.factory.annotation.Value; |
|||
import org.springframework.boot.context.properties.ConfigurationProperties; |
|||
import org.springframework.stereotype.Component; |
|||
|
|||
@Data |
|||
@Component |
|||
@ConfigurationProperties(prefix = "minio") |
|||
public class MinioConfig { |
|||
|
|||
@Value("endpoint") |
|||
private String endpoint; |
|||
|
|||
@Value("access-key") |
|||
private String accessKey; |
|||
|
|||
@Value("access-secret") |
|||
private String accessSecret; |
|||
|
|||
@Value("bucket-name") |
|||
private String bucketName; |
|||
|
|||
@Value("avatar-bucket-name") |
|||
private String avatarBucketName; |
|||
|
|||
// 预签名url过期时间(ms)
|
|||
private Long PRE_SIGN_URL_EXPIRE = 60 * 10 * 1000L; |
|||
} |
@ -0,0 +1,66 @@ |
|||
package org.ycloud.aipan.controller; |
|||
|
|||
import io.swagger.v3.oas.annotations.Operation; |
|||
import io.swagger.v3.oas.annotations.Parameter; |
|||
import io.swagger.v3.oas.annotations.tags.Tag; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
import org.ycloud.aipan.controller.req.AccountLoginReq; |
|||
import org.ycloud.aipan.controller.req.AccountRegisterReq; |
|||
import org.ycloud.aipan.dto.AccountDTO; |
|||
import org.ycloud.aipan.interceptor.LoginInterceptor; |
|||
import org.ycloud.aipan.service.AccountService; |
|||
import org.ycloud.aipan.util.JsonData; |
|||
import org.ycloud.aipan.util.JwtUtil; |
|||
|
|||
@RestController |
|||
@RequestMapping("/api/account/v1") |
|||
@Tag(name = "账户管理接口", description = "账户管理相关的接口") |
|||
public class AccountController { |
|||
|
|||
@Autowired |
|||
private AccountService accountService; |
|||
|
|||
/** |
|||
* 注册接口 |
|||
*/ |
|||
@PostMapping("register") |
|||
@Operation(summary = "用户注册", description = "用户注册接口") |
|||
public JsonData register(@RequestBody @Parameter(description = "注册请求对象", required = true) AccountRegisterReq req) { |
|||
accountService.register(req); |
|||
return JsonData.buildSuccess(); |
|||
} |
|||
|
|||
/** |
|||
* 头像上传接口 |
|||
*/ |
|||
@PostMapping("upload_avatar") |
|||
@Operation(summary = "上传头像", description = "上传头像接口") |
|||
public JsonData uploadAvatar(@RequestParam("file") @Parameter(description = "上传的头像文件", required = true) MultipartFile file) { |
|||
String url = accountService.uploadAvatar(file); |
|||
return JsonData.buildSuccess(url); |
|||
} |
|||
|
|||
/** |
|||
* 登录模块 |
|||
*/ |
|||
@PostMapping("login") |
|||
@Operation(summary = "用户登录", description = "用户登录接口") |
|||
public JsonData login(@RequestBody @Parameter(description = "登录请求对象", required = true) AccountLoginReq req) { |
|||
AccountDTO accountDTO = accountService.login(req); |
|||
//生成token jwt ssm 一般前端存储在localStorage里面,或 sessionStorage里面
|
|||
String token = JwtUtil.geneLoginJWT(accountDTO); |
|||
return JsonData.buildSuccess(token); |
|||
} |
|||
|
|||
/** |
|||
* 获取用户详情接口 |
|||
*/ |
|||
@GetMapping("detail") |
|||
@Operation(summary = "获取用户详情", description = "获取当前登录用户详情接口") |
|||
public JsonData detail() { |
|||
AccountDTO accountDTO = accountService.queryDetail(LoginInterceptor.threadLocal.get().getId()); |
|||
return JsonData.buildSuccess(accountDTO); |
|||
} |
|||
} |
@ -0,0 +1,97 @@ |
|||
package org.ycloud.aipan.controller; |
|||
|
|||
import io.swagger.v3.oas.annotations.Operation; |
|||
import io.swagger.v3.oas.annotations.Parameter; |
|||
import io.swagger.v3.oas.annotations.tags.Tag; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.web.bind.annotation.*; |
|||
import org.ycloud.aipan.controller.req.FileBatchReq; |
|||
import org.ycloud.aipan.controller.req.FileUpdateReq; |
|||
import org.ycloud.aipan.controller.req.FileUploadReq; |
|||
import org.ycloud.aipan.dto.AccountFileDTO; |
|||
import org.ycloud.aipan.dto.FolderTreeNodeDTO; |
|||
import org.ycloud.aipan.interceptor.LoginInterceptor; |
|||
import org.ycloud.aipan.service.AccountFileService; |
|||
import org.ycloud.aipan.controller.req.FolderCreateReq; |
|||
import org.ycloud.aipan.util.JsonData; |
|||
|
|||
import java.util.List; |
|||
|
|||
@RestController |
|||
@RequestMapping("/api/file/v1") |
|||
@Tag(name = "文件管理", description = "文件和文件夹操作相关的接口") |
|||
public class FileController { |
|||
|
|||
@Autowired |
|||
private AccountFileService accountFileService; |
|||
|
|||
/** |
|||
* 查询文件列表接口 |
|||
*/ |
|||
@GetMapping("list") |
|||
@Operation(summary = "查询文件列表", description = "根据父文件夹ID查询文件列表") |
|||
public JsonData list(@RequestParam(value = "parent_id") Long parentId) { |
|||
Long accountId = LoginInterceptor.threadLocal.get().getId(); |
|||
List<AccountFileDTO> list = accountFileService.listFile(accountId, parentId); |
|||
return JsonData.buildSuccess(list); |
|||
} |
|||
|
|||
/** |
|||
* 创建文件夹 |
|||
*/ |
|||
@PostMapping("create_folder") |
|||
@Operation(summary = "创建文件夹", description = "创建一个新的文件夹") |
|||
public JsonData createFolder(@RequestBody FolderCreateReq req) { |
|||
Long accountId = LoginInterceptor.threadLocal.get().getId(); |
|||
req.setAccountId(accountId); |
|||
return JsonData.buildSuccess(accountFileService.createFolder(req)); |
|||
} |
|||
|
|||
/** |
|||
* 文件重命名 |
|||
*/ |
|||
@PostMapping("rename_file") |
|||
@Operation(summary = "文件重命名", description = "重命名指定的文件") |
|||
public JsonData renameFile(@RequestBody FileUpdateReq req) { |
|||
Long accountId = LoginInterceptor.threadLocal.get().getId(); |
|||
req.setAccountId(accountId); |
|||
accountFileService.renameFile(req); |
|||
return JsonData.buildSuccess(); |
|||
} |
|||
|
|||
/** |
|||
* 文件树接口 |
|||
*/ |
|||
@GetMapping("/folder/tree") |
|||
@Operation(summary = "获取文件树", description = "获取文件和文件夹的树形结构") |
|||
public JsonData folderTree() { |
|||
Long accountId = LoginInterceptor.threadLocal.get().getId(); |
|||
List<FolderTreeNodeDTO> list = accountFileService.folderTree(accountId); |
|||
return JsonData.buildSuccess(list); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 普通小文件上传接口 |
|||
*/ |
|||
@PostMapping("upload") |
|||
@Operation(summary = "普通小文件上传", description = "普通小文件上传") |
|||
public JsonData upload(FileUploadReq req) { |
|||
req.setAccountId(LoginInterceptor.threadLocal.get().getId()); |
|||
accountFileService.fileUpload(req); |
|||
return JsonData.buildSuccess(); |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 文件批量移动 |
|||
*/ |
|||
@PostMapping("move_batch") |
|||
@Operation(summary = "文件批量移动", description = "文件批量移动") |
|||
public JsonData moveBatch( |
|||
@Parameter(description = "文件批量移动请求对象", required = true) @RequestBody FileBatchReq req) { |
|||
req.setAccountId(LoginInterceptor.threadLocal.get().getId()); |
|||
accountFileService.moveBatch(req); |
|||
return JsonData.buildSuccess(); |
|||
} |
|||
} |
@ -0,0 +1,27 @@ |
|||
package org.ycloud.aipan.controller.req; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
@Builder |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Schema(description = "用户登录请求对象", requiredProperties = {"password", "phone"}) |
|||
public class AccountLoginReq { |
|||
|
|||
/** |
|||
* 密码 |
|||
*/ |
|||
@Schema(description = "密码", example = "123456") |
|||
private String password; |
|||
|
|||
/** |
|||
* 手机号 |
|||
*/ |
|||
@Schema(description = "手机号", example = "15600000000") |
|||
private String phone; |
|||
} |
@ -0,0 +1,35 @@ |
|||
package org.ycloud.aipan.controller.req; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@Builder |
|||
@Schema(description = "用户注册请求对象", requiredProperties = {"username", "password", "phone"}) |
|||
public class AccountRegisterReq { |
|||
|
|||
/** |
|||
* 用户名 |
|||
*/ |
|||
@Schema(description = "用户名", example = "exampleUser") |
|||
private String username; |
|||
|
|||
/** |
|||
* 密码 |
|||
*/ |
|||
@Schema(description = "密码", example = "password123") |
|||
private String password; |
|||
|
|||
/** |
|||
* 手机号 |
|||
*/ |
|||
@Schema(description = "手机号", example = "13800138000") |
|||
private String phone; |
|||
|
|||
/** |
|||
* 头像 |
|||
*/ |
|||
@Schema(description = "头像URL", example = "http://example.com/avatar.jpg") |
|||
private String avatarUrl; |
|||
} |
@ -0,0 +1,20 @@ |
|||
package org.ycloud.aipan.controller.req; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Data; |
|||
|
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@Schema(description = "批量文件操作请求") |
|||
public class FileBatchReq { |
|||
|
|||
@Schema(description = "文件ID列表", example = "[1, 2, 3]") |
|||
private List<Long> fileIds; |
|||
|
|||
@Schema(description = "目标父级ID", example = "100") |
|||
private Long targetParentId; |
|||
|
|||
@Schema(description = "用户ID", example = "12345") |
|||
private Long accountId; |
|||
} |
@ -0,0 +1,27 @@ |
|||
package org.ycloud.aipan.controller.req; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
@Schema(description = "文件更新请求对象") |
|||
public class FileUpdateReq { |
|||
|
|||
/** |
|||
* 用户id |
|||
*/ |
|||
@Schema(description = "用户ID", example = "12345") |
|||
private Long accountId; |
|||
|
|||
/** |
|||
* 文件id |
|||
*/ |
|||
@Schema(description = "文件ID", example = "67890") |
|||
private Long fileId; |
|||
|
|||
/** |
|||
* 新的文件名 |
|||
*/ |
|||
@Schema(description = "新的文件名", example = "new_filename.txt") |
|||
private String newFilename; |
|||
} |
@ -0,0 +1,30 @@ |
|||
package org.ycloud.aipan.controller.req; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Data; |
|||
import lombok.experimental.Accessors; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
|
|||
@Data |
|||
@Accessors(chain = true) |
|||
@Schema(description = "文件上传请求") |
|||
public class FileUploadReq { |
|||
|
|||
@Schema(description = "文件名", example = "example.txt") |
|||
private String filename; |
|||
|
|||
@Schema(description = "文件唯一标识(MD5)", example = "d41d8cd98f00b204e9800998ecf8427e") |
|||
private String identifier; |
|||
|
|||
@Schema(description = "用户ID", example = "12345") |
|||
private Long accountId; |
|||
|
|||
@Schema(description = "父级目录ID", example = "100") |
|||
private Long parentId; |
|||
|
|||
@Schema(description = "文件大小(字节)", example = "1024") |
|||
private Long fileSize; |
|||
|
|||
@Schema(description = "文件对象") |
|||
private MultipartFile file; |
|||
} |
@ -0,0 +1,33 @@ |
|||
package org.ycloud.aipan.controller.req; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
@Data |
|||
@Builder |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Schema(description = "文件夹创建请求对象", requiredProperties = {"folderName", "parentId", "accountId"}) |
|||
public class FolderCreateReq { |
|||
|
|||
/** |
|||
* 文件夹名称 |
|||
*/ |
|||
@Schema(description = "文件夹名称", example = "MyFolder") |
|||
private String folderName; |
|||
|
|||
/** |
|||
* 上级文件夹ID |
|||
*/ |
|||
@Schema(description = "上级文件夹ID", example = "12345") |
|||
private Long parentId; |
|||
|
|||
/** |
|||
* 用户ID |
|||
*/ |
|||
@Schema(description = "用户ID", example = "67890") |
|||
private Long accountId; |
|||
} |
@ -0,0 +1,52 @@ |
|||
package org.ycloud.aipan.dto; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.*; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 用户信息表 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@Schema(name = "AccountDTO", description = "用户信息") |
|||
@Builder |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class AccountDTO implements Serializable { |
|||
|
|||
@Schema(description = "ID", example = "12345") |
|||
private Long id; |
|||
|
|||
@Schema(description = "用户名", example = "exampleUser") |
|||
private String username; |
|||
|
|||
@Schema(description = "用户头像", example = "http://example.com/avatar.jpg") |
|||
private String avatarUrl; |
|||
|
|||
@Schema(description = "手机号", example = "13800138000") |
|||
private String phone; |
|||
|
|||
@Schema(description = "用户角色 (COMMON, ADMIN)", example = "COMMON") |
|||
private String role; |
|||
|
|||
@Schema(description = "逻辑删除(1删除 0未删除)", example = "false") |
|||
private Boolean del; |
|||
|
|||
@Schema(description = "创建时间", example = "2023-10-01T12:34:56Z") |
|||
private Date gmtCreate; |
|||
|
|||
@Schema(description = "更新时间", example = "2023-10-02T12:34:56Z") |
|||
private Date gmtModified; |
|||
|
|||
@Schema(description = "根文件夹ID", example = "67890") |
|||
private Long rootFileId; |
|||
|
|||
@Schema(description = "根文件夹名称", example = "MyRootFolder") |
|||
private String rootFileName; |
|||
|
|||
@Schema(description = "存储信息") |
|||
private StorageDTO storageDTO; |
|||
} |
@ -0,0 +1,60 @@ |
|||
package org.ycloud.aipan.dto; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.*; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 用户文件表 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@Builder |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Schema(name = "AccountFileDO", description = "用户文件表") |
|||
public class AccountFileDTO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Schema(description = "id") |
|||
private Long id; |
|||
|
|||
@Schema(description = "用户ID") |
|||
private Long accountId; |
|||
|
|||
@Schema(description = "状态 0不是文件夹,1是文件夹") |
|||
private Integer isDir; |
|||
|
|||
@Schema(description = "上层文件夹ID,顶层文件夹为0") |
|||
private Long parentId; |
|||
|
|||
@Schema(description = "文件ID,真正存储的文件") |
|||
private Long fileId; |
|||
|
|||
@Schema(description = "文件名称") |
|||
private String fileName; |
|||
|
|||
@Schema(description = "文件类型:普通文件common 、压缩文件compress 、 excel 、 word 、 pdf 、 txt 、 图片img 、音频audio 、视频video 、ppt 、源码文件code 、 csv") |
|||
private String fileType; |
|||
|
|||
@Schema(description = "文件的后缀拓展名") |
|||
private String fileSuffix; |
|||
|
|||
@Schema(description = "文件大小,字节") |
|||
private Long fileSize; |
|||
|
|||
@Schema(description = "逻辑删除(0未删除,1已删除)") |
|||
private Boolean del; |
|||
|
|||
@Schema(description = "删除日期") |
|||
private Date delTime; |
|||
|
|||
@Schema(description = "更新时间") |
|||
private Date gmtModified; |
|||
|
|||
@Schema(description = "创建时间") |
|||
private Date gmtCreate; |
|||
} |
@ -0,0 +1,38 @@ |
|||
package org.ycloud.aipan.dto; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Builder; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
@Builder |
|||
public class FolderTreeNodeDTO { |
|||
|
|||
/** |
|||
* 文件id |
|||
*/ |
|||
private Long id ; |
|||
|
|||
/** |
|||
* 父文件ID |
|||
*/ |
|||
private Long parentId; |
|||
|
|||
|
|||
/** |
|||
* 文件名称 |
|||
*/ |
|||
private String label; |
|||
|
|||
/** |
|||
* 子节点列表 |
|||
*/ |
|||
private List<FolderTreeNodeDTO> children = new ArrayList<>(); |
|||
|
|||
} |
@ -0,0 +1,37 @@ |
|||
package org.ycloud.aipan.dto; |
|||
|
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
|
|||
/** |
|||
* 存储信息表 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@Schema(name = "StorageDTO", description = "存储信息表") |
|||
public class StorageDTO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Schema(description = "ID", example = "12345") |
|||
private Long id; |
|||
|
|||
@Schema(description = "所属用户", example = "67890") |
|||
private Long accountId; |
|||
|
|||
@Schema(description = "占用存储大小(字节)", example = "104857600") |
|||
private Long usedSize; |
|||
|
|||
@Schema(description = "总容量大小(字节)", example = "536870912") |
|||
private Long totalSize; |
|||
|
|||
@Schema(description = "创建时间", example = "2023-10-01T12:34:56Z") |
|||
private Date gmtCreate; |
|||
|
|||
@Schema(description = "更新时间", example = "2023-10-02T12:34:56Z") |
|||
private Date gmtModified; |
|||
} |
@ -0,0 +1,15 @@ |
|||
package org.ycloud.aipan.enums; |
|||
|
|||
public enum AccountRoleEnum { |
|||
|
|||
/** |
|||
* 普通用户 |
|||
*/ |
|||
COMMON, |
|||
|
|||
/** |
|||
* 管理员 |
|||
*/ |
|||
ADMIN; |
|||
|
|||
} |
@ -0,0 +1,38 @@ |
|||
package org.ycloud.aipan.enums; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
|
|||
@Getter |
|||
@AllArgsConstructor |
|||
public enum BizCodeEnum { |
|||
/** |
|||
* 账号 |
|||
*/ |
|||
ACCOUNT_REPEAT(250001, "账号已经存在"), |
|||
ACCOUNT_UNREGISTER(250002, "账号不存在"), |
|||
ACCOUNT_PWD_ERROR(250003, "账号或者密码错误"), |
|||
ACCOUNT_UNLOGIN(250004, "账号未登录"), |
|||
|
|||
/** |
|||
* 文件操作相关 |
|||
*/ |
|||
FILE_NOT_EXISTS(220404, "文件不存在"), |
|||
FILE_RENAME_REPEAT(220405, "文件名重复"), |
|||
FILE_DEL_BATCH_ILLEGAL(220406, "文件删除参数错误"), |
|||
FILE_TYPE_ERROR(220407, "文件类型错误"), |
|||
FILE_CHUNK_TASK_NOT_EXISTS(230408, "分片任务不存在"), |
|||
FILE_CHUNK_NOT_ENOUGH(230409, "分片数量不匹配,合并不够"), |
|||
FILE_STORAGE_NOT_ENOUGH(240403, "存储空间不足"), |
|||
FILE_TARGET_PARENT_ILLEGAL(250403, "目标父级目录不合法"), |
|||
SHARE_CANCEL_ILLEGAL(260403, "取消分享失败,参数不合法"), |
|||
SHARE_CODE_ILLEGAL(260404, "分享码不合法"), |
|||
SHARE_NOT_EXIST(260405, "分享不存在"), |
|||
SHARE_CANCEL(260406, "分享已取消"), |
|||
SHARE_EXPIRED(260407, "分享已过期"), |
|||
SHARE_FILE_ILLEGAL(260408, "分享的文件不合规"), |
|||
FILE_BATCH_UPDATE_ERROR(270101,"文件批量操作错误" ); |
|||
private final int code; |
|||
private final String message; |
|||
|
|||
} |
@ -0,0 +1,110 @@ |
|||
package org.ycloud.aipan.enums; |
|||
|
|||
import lombok.Getter; |
|||
|
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
|
|||
@Getter |
|||
public enum FileTypeEnum { |
|||
COMMON("common"), |
|||
COMPRESS("compress"), |
|||
EXCEL("excel"), |
|||
WORD("word"), |
|||
PDF("pdf"), |
|||
TXT("txt"), |
|||
IMG("img"), |
|||
AUDIO("audio"), |
|||
VIDEO("video"), |
|||
PPT("ppt"), |
|||
CODE("code"), |
|||
CSV("csv"); |
|||
|
|||
private final String type; |
|||
|
|||
private static final Map<String, FileTypeEnum> EXTENSION_MAP = new HashMap<>(); |
|||
|
|||
static { |
|||
for (FileTypeEnum fileType : values()) { |
|||
switch (fileType) { |
|||
case COMPRESS: |
|||
EXTENSION_MAP.put("zip", fileType); |
|||
EXTENSION_MAP.put("rar", fileType); |
|||
EXTENSION_MAP.put("7z", fileType); |
|||
break; |
|||
case EXCEL: |
|||
EXTENSION_MAP.put("xls", fileType); |
|||
EXTENSION_MAP.put("xlsx", fileType); |
|||
break; |
|||
case WORD: |
|||
EXTENSION_MAP.put("doc", fileType); |
|||
EXTENSION_MAP.put("docx", fileType); |
|||
break; |
|||
case PDF: |
|||
EXTENSION_MAP.put("pdf", fileType); |
|||
break; |
|||
case TXT: |
|||
EXTENSION_MAP.put("txt", fileType); |
|||
break; |
|||
case IMG: |
|||
EXTENSION_MAP.put("jpg", fileType); |
|||
EXTENSION_MAP.put("jpeg", fileType); |
|||
EXTENSION_MAP.put("png", fileType); |
|||
EXTENSION_MAP.put("gif", fileType); |
|||
EXTENSION_MAP.put("bmp", fileType); |
|||
break; |
|||
case AUDIO: |
|||
EXTENSION_MAP.put("mp3", fileType); |
|||
EXTENSION_MAP.put("wav", fileType); |
|||
EXTENSION_MAP.put("aac", fileType); |
|||
break; |
|||
case VIDEO: |
|||
EXTENSION_MAP.put("mp4", fileType); |
|||
EXTENSION_MAP.put("avi", fileType); |
|||
EXTENSION_MAP.put("mkv", fileType); |
|||
break; |
|||
case PPT: |
|||
EXTENSION_MAP.put("ppt", fileType); |
|||
EXTENSION_MAP.put("pptx", fileType); |
|||
break; |
|||
case CODE: |
|||
EXTENSION_MAP.put("java", fileType); |
|||
EXTENSION_MAP.put("c", fileType); |
|||
EXTENSION_MAP.put("cpp", fileType); |
|||
EXTENSION_MAP.put("py", fileType); |
|||
EXTENSION_MAP.put("js", fileType); |
|||
EXTENSION_MAP.put("html", fileType); |
|||
EXTENSION_MAP.put("css", fileType); |
|||
break; |
|||
case CSV: |
|||
EXTENSION_MAP.put("csv", fileType); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
FileTypeEnum(String type) { |
|||
this.type = type; |
|||
} |
|||
|
|||
public static FileTypeEnum fromExtension(String extension) { |
|||
if (extension == null || extension.isEmpty() || !isValidExtension(extension)) { |
|||
return COMMON; |
|||
} |
|||
|
|||
try { |
|||
return EXTENSION_MAP.getOrDefault(extension.toLowerCase(), COMMON); |
|||
} catch (NullPointerException e) { |
|||
// 记录日志
|
|||
System.err.println("Unexpected null pointer exception: " + e.getMessage()); |
|||
return COMMON; |
|||
} |
|||
} |
|||
|
|||
private static boolean isValidExtension(String extension) { |
|||
// 确保扩展名只包含字母和数字
|
|||
return extension.matches("[a-zA-Z0-9]+"); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
package org.ycloud.aipan.enums; |
|||
|
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Getter; |
|||
|
|||
@AllArgsConstructor |
|||
@Getter |
|||
public enum FolderFlagEnum { |
|||
|
|||
/** |
|||
* 非文件夹 |
|||
*/ |
|||
NO(0), |
|||
|
|||
/** |
|||
* 是文件夹 |
|||
*/ |
|||
YES(1); |
|||
|
|||
private final Integer code; |
|||
|
|||
} |
@ -0,0 +1,71 @@ |
|||
package org.ycloud.aipan.exception; |
|||
|
|||
import lombok.Data; |
|||
import lombok.EqualsAndHashCode; |
|||
import org.ycloud.aipan.enums.BizCodeEnum; |
|||
|
|||
/** |
|||
* 业务异常类,继承自 RuntimeException |
|||
* 用于封装业务逻辑中的异常信息 |
|||
*/ |
|||
@EqualsAndHashCode(callSuper = true) |
|||
@Data |
|||
public class BizException extends RuntimeException { |
|||
/** |
|||
* 异常代码 |
|||
*/ |
|||
private int code; |
|||
/** |
|||
* 异常消息 |
|||
*/ |
|||
private String msg; |
|||
/** |
|||
* 异常详细信息 |
|||
*/ |
|||
private String detail; |
|||
/** |
|||
* 构造函数,使用自定义的异常代码和消息 |
|||
* |
|||
* @param code 异常代码 |
|||
* @param message 异常消息 |
|||
*/ |
|||
public BizException(Integer code, String message) { |
|||
// 调用父类构造函数,设置异常消息
|
|||
super(message); |
|||
// 设置异常代码
|
|||
this.code = code; |
|||
// 设置异常消息
|
|||
this.msg = message; |
|||
} |
|||
|
|||
/** |
|||
* 构造函数,使用 BizCodeEnum 枚举中的异常代码和消息 |
|||
* |
|||
* @param bizCodeEnum 业务代码枚举 |
|||
*/ |
|||
public BizException(BizCodeEnum bizCodeEnum) { |
|||
// 调用父类构造函数,设置异常消息
|
|||
super(bizCodeEnum.getMessage()); |
|||
// 设置异常代码
|
|||
this.code = bizCodeEnum.getCode(); |
|||
// 设置异常消息
|
|||
this.msg = bizCodeEnum.getMessage(); |
|||
} |
|||
|
|||
/** |
|||
* 构造函数,使用 BizCodeEnum 枚举中的异常代码和消息,并包含原始异常的详细信息 |
|||
* |
|||
* @param bizCodeEnum 业务代码枚举 |
|||
* @param e 原始异常 |
|||
*/ |
|||
public BizException(BizCodeEnum bizCodeEnum, Exception e) { |
|||
// 调用父类构造函数,设置异常消息
|
|||
super(bizCodeEnum.getMessage()); |
|||
// 设置异常代码
|
|||
this.code = bizCodeEnum.getCode(); |
|||
// 设置异常消息
|
|||
this.msg = bizCodeEnum.getMessage(); |
|||
// 设置异常详细信息
|
|||
this.detail = e.toString(); |
|||
} |
|||
} |
@ -0,0 +1,40 @@ |
|||
package org.ycloud.aipan.exception; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.web.bind.annotation.ControllerAdvice; |
|||
import org.springframework.web.bind.annotation.ExceptionHandler; |
|||
import org.springframework.web.bind.annotation.ResponseBody; |
|||
import org.ycloud.aipan.util.JsonData; |
|||
|
|||
/** |
|||
* 自定义异常处理器 |
|||
* 用于捕获并处理全局异常,返回统一的JSON格式响应 |
|||
*/ |
|||
@ControllerAdvice |
|||
@Slf4j |
|||
public class CustomExceptionHandler { |
|||
|
|||
/** |
|||
* 处理所有异常的方法 |
|||
* |
|||
* @param e 捕获到的异常对象 |
|||
* @return JsonData对象,包含错误码和错误信息 |
|||
*/ |
|||
@ExceptionHandler(value = Exception.class) |
|||
@ResponseBody |
|||
public JsonData handler(Exception e){ |
|||
|
|||
// 判断异常是否为业务异常
|
|||
if(e instanceof BizException bizException){ |
|||
// 记录业务异常日志
|
|||
log.error("[业务异常]",e); |
|||
// 返回业务异常的错误码和错误信息
|
|||
return JsonData.buildCodeAndMsg(bizException.getCode(),bizException.getMsg()); |
|||
}else { |
|||
// 记录系统异常日志
|
|||
log.error("[系统异常]",e); |
|||
// 返回系统异常的错误信息
|
|||
return JsonData.buildError("系统异常"); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,72 @@ |
|||
package org.ycloud.aipan.interceptor; |
|||
|
|||
import io.jsonwebtoken.Claims; |
|||
import jakarta.servlet.http.HttpServletRequest; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.apache.commons.lang3.StringUtils; |
|||
import org.springframework.http.HttpMethod; |
|||
import org.springframework.http.HttpStatus; |
|||
import org.springframework.stereotype.Component; |
|||
import org.springframework.web.servlet.HandlerInterceptor; |
|||
import org.springframework.web.servlet.ModelAndView; |
|||
import org.ycloud.aipan.dto.AccountDTO; |
|||
import org.ycloud.aipan.enums.BizCodeEnum; |
|||
import org.ycloud.aipan.util.CommonUtil; |
|||
import org.ycloud.aipan.util.JsonData; |
|||
import org.ycloud.aipan.util.JwtUtil; |
|||
|
|||
|
|||
@Slf4j |
|||
@Component |
|||
public class LoginInterceptor implements HandlerInterceptor { |
|||
|
|||
public static ThreadLocal<AccountDTO> threadLocal = new ThreadLocal<>(); |
|||
|
|||
|
|||
@Override |
|||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { |
|||
//处理options请求
|
|||
if(HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod())){ |
|||
response.setStatus(HttpStatus.NO_CONTENT.value()); |
|||
return true; |
|||
} |
|||
|
|||
String token = request.getHeader("token"); |
|||
if(StringUtils.isBlank(token)){ |
|||
token = request.getParameter("token"); |
|||
} |
|||
|
|||
//如果存在token,就解析
|
|||
if(StringUtils.isNotBlank(token)){ |
|||
Claims claims = JwtUtil.checkLoginJWT(token); |
|||
if(claims == null){ |
|||
log.info("token 解析失败"); |
|||
CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN)); |
|||
return false; |
|||
} |
|||
|
|||
Long accountId = Long.valueOf(claims.get("accountId")+""); |
|||
String username = (String)claims.get("username"); |
|||
//创建accountDTO
|
|||
AccountDTO accountDTO = AccountDTO.builder().id(accountId).username(username).build(); |
|||
threadLocal.set(accountDTO); |
|||
return true; |
|||
} |
|||
|
|||
//没有token,未登录
|
|||
CommonUtil.sendJsonMessage(response, JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN)); |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { |
|||
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); |
|||
} |
|||
|
|||
@Override |
|||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { |
|||
//清理threadLocal,避免内存泄漏
|
|||
threadLocal.remove(); |
|||
} |
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.AccountFileDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 用户文件表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface AccountFileMapper extends BaseMapper<AccountFileDO> { |
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.AccountDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 用户信息表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface AccountMapper extends BaseMapper<AccountDO> { |
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.FileChunkDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 文件分片信息表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface FileChunkMapper extends BaseMapper<FileChunkDO> { |
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.FileDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 用户文件表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface FileMapper extends BaseMapper<FileDO> { |
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.FileSuffixDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 文件分类表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface FileSuffixMapper extends BaseMapper<FileSuffixDO> { |
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.FileTypeDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 文件类型表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface FileTypeMapper extends BaseMapper<FileTypeDO> { |
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.ShareFileDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 文件分享表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface ShareFileMapper extends BaseMapper<ShareFileDO> { |
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.ShareDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 用户分享表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface ShareMapper extends BaseMapper<ShareDO> { |
|||
|
|||
} |
@ -0,0 +1,16 @@ |
|||
package org.ycloud.aipan.mapper; |
|||
|
|||
import org.ycloud.aipan.model.StorageDO; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
|
|||
/** |
|||
* <p> |
|||
* 存储信息表 Mapper 接口 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
public interface StorageMapper extends BaseMapper<StorageDO> { |
|||
|
|||
} |
@ -0,0 +1,66 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableLogic; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 用户信息表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("account") |
|||
@Schema(name = "AccountDO", description = "用户信息表") |
|||
public class AccountDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Schema(description = "ID") |
|||
@TableId(value = "id", type = IdType.ASSIGN_ID) |
|||
private Long id; |
|||
|
|||
@Schema(description = "用户名") |
|||
@TableField("username") |
|||
private String username; |
|||
|
|||
@Schema(description = "密码") |
|||
@TableField("password") |
|||
private String password; |
|||
|
|||
@Schema(description = "用户头像") |
|||
@TableField("avatar_url") |
|||
private String avatarUrl; |
|||
|
|||
@Schema(description = "手机号") |
|||
@TableField("phone") |
|||
private String phone; |
|||
|
|||
@Schema(description = "用户角色 COMMON, ADMIN") |
|||
@TableField("role") |
|||
private String role; |
|||
|
|||
@Schema(description = "逻辑删除(1删除 0未删除)") |
|||
@TableField("del") |
|||
@TableLogic |
|||
private Boolean del; |
|||
|
|||
@Schema(description = "创建时间") |
|||
@TableField("gmt_create") |
|||
private Date gmtCreate; |
|||
|
|||
@Schema(description = "更新时间") |
|||
@TableField("gmt_modified") |
|||
private Date gmtModified; |
|||
} |
@ -0,0 +1,82 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableLogic; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 用户文件表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("account_file") |
|||
@Schema(name = "AccountFileDO", description = "用户文件表") |
|||
public class AccountFileDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Schema(description = "id") |
|||
@TableId(value = "id", type = IdType.ASSIGN_ID) |
|||
private Long id; |
|||
|
|||
@Schema(description = "用户ID") |
|||
@TableField("account_id") |
|||
private Long accountId; |
|||
|
|||
@Schema(description = "状态 0不是文件夹,1是文件夹") |
|||
@TableField("is_dir") |
|||
private Integer isDir; |
|||
|
|||
@Schema(description = "上层文件夹ID,顶层文件夹为0") |
|||
@TableField("parent_id") |
|||
private Long parentId; |
|||
|
|||
@Schema(description = "文件ID,真正存储的文件") |
|||
@TableField("file_id") |
|||
private Long fileId; |
|||
|
|||
@Schema(description = "文件名称") |
|||
@TableField("file_name") |
|||
private String fileName; |
|||
|
|||
@Schema(description = "文件类型:普通文件common 、压缩文件compress 、 excel 、 word 、 pdf 、 txt 、 图片img 、音频audio 、视频video 、ppt 、源码文件code 、 csv") |
|||
@TableField("file_type") |
|||
private String fileType; |
|||
|
|||
@Schema(description = "文件的后缀拓展名") |
|||
@TableField("file_suffix") |
|||
private String fileSuffix; |
|||
|
|||
@Schema(description = "文件大小,字节") |
|||
@TableField("file_size") |
|||
private Long fileSize; |
|||
|
|||
@Schema(description = "逻辑删除(0未删除,1已删除)") |
|||
@TableField("del") |
|||
@TableLogic |
|||
private Boolean del; |
|||
|
|||
@Schema(description = "删除日期") |
|||
@TableField("del_time") |
|||
private Date delTime; |
|||
|
|||
@Schema(description = "更新时间") |
|||
@TableField("gmt_modified") |
|||
private Date gmtModified; |
|||
|
|||
@Schema(description = "创建时间") |
|||
@TableField("gmt_create") |
|||
private Date gmtCreate; |
|||
} |
@ -0,0 +1,73 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 文件分片信息表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("file_chunk") |
|||
@Schema(name = "FileChunkDO", description = "文件分片信息表") |
|||
public class FileChunkDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@TableId(value = "id", type = IdType.ASSIGN_ID) |
|||
private Long id; |
|||
|
|||
@Schema(description = "文件唯一标识(md5)") |
|||
@TableField("identifier") |
|||
private String identifier; |
|||
|
|||
@Schema(description = "分片上传ID") |
|||
@TableField("upload_id") |
|||
private String uploadId; |
|||
|
|||
@Schema(description = "文件名") |
|||
@TableField("file_name") |
|||
private String fileName; |
|||
|
|||
@Schema(description = "所属桶名") |
|||
@TableField("bucket_name") |
|||
private String bucketName; |
|||
|
|||
@Schema(description = "文件的key") |
|||
@TableField("object_key") |
|||
private String objectKey; |
|||
|
|||
@Schema(description = "总文件大小(byte)") |
|||
@TableField("total_size") |
|||
private Long totalSize; |
|||
|
|||
@Schema(description = "每个分片大小(byte)") |
|||
@TableField("chunk_size") |
|||
private Long chunkSize; |
|||
|
|||
@Schema(description = "分片数量") |
|||
@TableField("chunk_num") |
|||
private Integer chunkNum; |
|||
|
|||
@Schema(description = "用户ID") |
|||
@TableField("account_id") |
|||
private Long accountId; |
|||
|
|||
@TableField("gmt_create") |
|||
private Date gmtCreate; |
|||
|
|||
@TableField("gmt_modified") |
|||
private Date gmtModified; |
|||
} |
@ -0,0 +1,70 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableLogic; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 用户文件表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("file") |
|||
@Schema(name = "FileDO", description = "用户文件表") |
|||
public class FileDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Schema(description = "文件id") |
|||
@TableId(value = "id", type = IdType.ASSIGN_ID) |
|||
private Long id; |
|||
|
|||
@Schema(description = "用户id,是哪个用户初次上传的") |
|||
@TableField("account_id") |
|||
private Long accountId; |
|||
|
|||
@Schema(description = "文件名称,秒传需要用到,冗余存储") |
|||
@TableField("file_name") |
|||
private String fileName; |
|||
|
|||
@Schema(description = "文件的后缀拓展名,冗余存储") |
|||
@TableField("file_suffix") |
|||
private String fileSuffix; |
|||
|
|||
@Schema(description = "文件大小,字节,冗余存储") |
|||
@TableField("file_size") |
|||
private Long fileSize; |
|||
|
|||
@Schema(description = "文件的key, 格式 日期/md5.拓展名,比如 2024-11-13/921674fd-cdaf-459a-be7b-109469e7050d.png") |
|||
@TableField("object_key") |
|||
private String objectKey; |
|||
|
|||
@Schema(description = "唯一标识,文件MD5") |
|||
@TableField("identifier") |
|||
private String identifier; |
|||
|
|||
@Schema(description = "逻辑删除(0未删除,1已删除)") |
|||
@TableField("del") |
|||
@TableLogic |
|||
private Boolean del; |
|||
|
|||
@Schema(description = "更新时间") |
|||
@TableField("gmt_modified") |
|||
private Date gmtModified; |
|||
|
|||
@Schema(description = "创建时间") |
|||
@TableField("gmt_create") |
|||
private Date gmtCreate; |
|||
} |
@ -0,0 +1,38 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 文件分类表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("file_suffix") |
|||
@Schema(name = "FileSuffixDO", description = "文件分类表") |
|||
public class FileSuffixDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@TableId(value = "id", type = IdType.AUTO) |
|||
private Integer id; |
|||
|
|||
@Schema(description = "文件扩展名") |
|||
@TableField("file_suffix") |
|||
private String fileSuffix; |
|||
|
|||
@Schema(description = "文件类型ID") |
|||
@TableField("file_type_id") |
|||
private Integer fileTypeId; |
|||
} |
@ -0,0 +1,35 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 文件类型表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("file_type") |
|||
@Schema(name = "FileTypeDO", description = "文件类型表") |
|||
public class FileTypeDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Schema(description = "主键ID") |
|||
@TableId(value = "id", type = IdType.AUTO) |
|||
private Integer id; |
|||
|
|||
@Schema(description = "文件类型名") |
|||
@TableField("file_type_name") |
|||
private String fileTypeName; |
|||
} |
@ -0,0 +1,75 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 用户分享表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("share") |
|||
@Schema(name = "ShareDO", description = "用户分享表") |
|||
public class ShareDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Schema(description = "分享id") |
|||
@TableId(value = "id", type = IdType.ASSIGN_ID) |
|||
private Long id; |
|||
|
|||
@Schema(description = "分享名称") |
|||
@TableField("share_name") |
|||
private String shareName; |
|||
|
|||
@Schema(description = "分享类型(no_code没有提取码 ,need_code有提取码)") |
|||
@TableField("share_type") |
|||
private String shareType; |
|||
|
|||
@Schema(description = "分享类型(0 永久有效;1: 7天有效;2: 30天有效)") |
|||
@TableField("share_day_type") |
|||
private Integer shareDayType; |
|||
|
|||
@Schema(description = "分享有效天数(永久有效为0)") |
|||
@TableField("share_day") |
|||
private Integer shareDay; |
|||
|
|||
@Schema(description = "分享结束时间") |
|||
@TableField("share_end_time") |
|||
private Date shareEndTime; |
|||
|
|||
@Schema(description = "分享链接地址") |
|||
@TableField("share_url") |
|||
private String shareUrl; |
|||
|
|||
@Schema(description = "分享提取码") |
|||
@TableField("share_code") |
|||
private String shareCode; |
|||
|
|||
@Schema(description = "分享状态 used正常, expired已失效, cancled取消") |
|||
@TableField("share_status") |
|||
private String shareStatus; |
|||
|
|||
@Schema(description = "分享创建人") |
|||
@TableField("account_id") |
|||
private Long accountId; |
|||
|
|||
@Schema(description = "创建时间") |
|||
@TableField("gmt_create") |
|||
private Date gmtCreate; |
|||
|
|||
@TableField("gmt_modified") |
|||
private Date gmtModified; |
|||
} |
@ -0,0 +1,52 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 文件分享表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("share_file") |
|||
@Schema(name = "ShareFileDO", description = "文件分享表") |
|||
public class ShareFileDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@Schema(description = "主键ID") |
|||
@TableId(value = "id", type = IdType.ASSIGN_ID) |
|||
private Long id; |
|||
|
|||
@Schema(description = "分享id") |
|||
@TableField("share_id") |
|||
private Long shareId; |
|||
|
|||
@Schema(description = "用户文件的ID") |
|||
@TableField("account_file_id") |
|||
private Long accountFileId; |
|||
|
|||
@Schema(description = "创建者id") |
|||
@TableField("account_id") |
|||
private Long accountId; |
|||
|
|||
@Schema(description = "分享时间") |
|||
@TableField("gmt_create") |
|||
private Date gmtCreate; |
|||
|
|||
@Schema(description = "更新时间") |
|||
@TableField("gmt_modified") |
|||
private Date gmtModified; |
|||
} |
@ -0,0 +1,51 @@ |
|||
package org.ycloud.aipan.model; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.annotation.TableField; |
|||
import com.baomidou.mybatisplus.annotation.TableId; |
|||
import com.baomidou.mybatisplus.annotation.TableName; |
|||
import java.io.Serializable; |
|||
import java.util.Date; |
|||
import io.swagger.v3.oas.annotations.media.Schema; |
|||
import lombok.Getter; |
|||
import lombok.Setter; |
|||
|
|||
/** |
|||
* <p> |
|||
* 存储信息表 |
|||
* </p> |
|||
* |
|||
* @author everyone |
|||
* @since 2025-02-12 |
|||
*/ |
|||
@Getter |
|||
@Setter |
|||
@TableName("storage") |
|||
@Schema(name = "StorageDO", description = "存储信息表") |
|||
public class StorageDO implements Serializable { |
|||
|
|||
private static final long serialVersionUID = 1L; |
|||
|
|||
@TableId(value = "id", type = IdType.ASSIGN_ID) |
|||
private Long id; |
|||
|
|||
@Schema(description = "所属用户") |
|||
@TableField("account_id") |
|||
private Long accountId; |
|||
|
|||
@Schema(description = "占用存储大小") |
|||
@TableField("used_size") |
|||
private Long usedSize; |
|||
|
|||
@Schema(description = "总容量大小,字节存储") |
|||
@TableField("total_size") |
|||
private Long totalSize; |
|||
|
|||
@Schema(description = "创建时间") |
|||
@TableField("gmt_create") |
|||
private Date gmtCreate; |
|||
|
|||
@Schema(description = "更新时间") |
|||
@TableField("gmt_modified") |
|||
private Date gmtModified; |
|||
} |
@ -0,0 +1,51 @@ |
|||
package org.ycloud.aipan.service; |
|||
|
|||
|
|||
import org.ycloud.aipan.controller.req.FileBatchReq; |
|||
import org.ycloud.aipan.controller.req.FileUpdateReq; |
|||
import org.ycloud.aipan.controller.req.FileUploadReq; |
|||
import org.ycloud.aipan.controller.req.FolderCreateReq; |
|||
import org.ycloud.aipan.dto.AccountFileDTO; |
|||
import org.ycloud.aipan.dto.FolderTreeNodeDTO; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface AccountFileService { |
|||
/** |
|||
* 获取文件列表 |
|||
*/ |
|||
List<AccountFileDTO> listFile(Long accountId, Long parentId); |
|||
/** |
|||
* 创建文件夹 |
|||
*/ |
|||
Long createFolder(FolderCreateReq createRootFolderReq); |
|||
|
|||
/** |
|||
* 重命名文件 |
|||
* 1、检查ID是否存在 |
|||
* 2、新旧文件名称不能一样 |
|||
* 3、同层文件名称不能一样 |
|||
* @param req |
|||
*/ |
|||
void renameFile(FileUpdateReq req); |
|||
|
|||
/** |
|||
* 查询文件树接口 (非递归方式) |
|||
* 1、查询用户全部文件夹 |
|||
* 2、拼装文件树 |
|||
*/ |
|||
List<FolderTreeNodeDTO> folderTree(Long accountId); |
|||
|
|||
|
|||
/** |
|||
* 普通小文件上传 |
|||
* @param req |
|||
*/ |
|||
void fileUpload(FileUploadReq req); |
|||
|
|||
/** |
|||
* 批量移动目标文件夹 |
|||
* @param req |
|||
*/ |
|||
void moveBatch(FileBatchReq req); |
|||
} |
@ -0,0 +1,24 @@ |
|||
package org.ycloud.aipan.service; |
|||
|
|||
import org.springframework.web.multipart.MultipartFile; |
|||
import org.ycloud.aipan.controller.req.AccountLoginReq; |
|||
import org.ycloud.aipan.controller.req.AccountRegisterReq; |
|||
import org.ycloud.aipan.dto.AccountDTO; |
|||
|
|||
public interface AccountService { |
|||
|
|||
/** |
|||
* 1、查询手机号是否重复 |
|||
* 2、加密密码 |
|||
* 3、插入数据库 |
|||
* 4、其他相关初始化操作 |
|||
*/ |
|||
void register(AccountRegisterReq req); |
|||
|
|||
|
|||
String uploadAvatar(MultipartFile file); |
|||
|
|||
AccountDTO login(AccountLoginReq req); |
|||
|
|||
AccountDTO queryDetail(Long id); |
|||
} |
@ -0,0 +1,9 @@ |
|||
package org.ycloud.aipan.service; |
|||
|
|||
import org.ycloud.aipan.dto.AccountFileDTO; |
|||
|
|||
import java.util.List; |
|||
|
|||
public interface FileService { |
|||
|
|||
} |
@ -0,0 +1,389 @@ |
|||
package org.ycloud.aipan.service.impl; |
|||
|
|||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
|||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.transaction.annotation.Transactional; |
|||
import org.springframework.util.CollectionUtils; |
|||
import org.ycloud.aipan.component.StoreEngine; |
|||
import org.ycloud.aipan.config.MinioConfig; |
|||
import org.ycloud.aipan.controller.req.FileBatchReq; |
|||
import org.ycloud.aipan.controller.req.FileUpdateReq; |
|||
import org.ycloud.aipan.controller.req.FileUploadReq; |
|||
import org.ycloud.aipan.controller.req.FolderCreateReq; |
|||
import org.ycloud.aipan.dto.AccountFileDTO; |
|||
import org.ycloud.aipan.dto.FolderTreeNodeDTO; |
|||
import org.ycloud.aipan.enums.BizCodeEnum; |
|||
import org.ycloud.aipan.enums.FileTypeEnum; |
|||
import org.ycloud.aipan.enums.FolderFlagEnum; |
|||
import org.ycloud.aipan.exception.BizException; |
|||
import org.ycloud.aipan.mapper.AccountFileMapper; |
|||
import org.ycloud.aipan.mapper.FileMapper; |
|||
import org.ycloud.aipan.model.AccountFileDO; |
|||
import org.ycloud.aipan.model.FileDO; |
|||
import org.ycloud.aipan.service.AccountFileService; |
|||
import org.ycloud.aipan.util.CommonUtil; |
|||
import org.ycloud.aipan.util.SpringBeanUtil; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.Objects; |
|||
import java.util.stream.Collectors; |
|||
|
|||
|
|||
@Slf4j |
|||
@Service |
|||
public class AccountFileServiceImpl implements AccountFileService { |
|||
@Autowired |
|||
private MinioConfig minioConfig; |
|||
@Autowired |
|||
private StoreEngine fileStoreEngine; |
|||
@Autowired |
|||
private AccountFileMapper accountFileMapper; |
|||
@Autowired |
|||
private FileMapper fileMapper; |
|||
|
|||
@Override |
|||
public List<AccountFileDTO> listFile(Long accountId, Long parentId) { |
|||
List<AccountFileDO> accountFileDOList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>() |
|||
.eq("account_id", accountId).eq("parent_id", parentId) |
|||
.orderByDesc("is_dir") |
|||
.orderByDesc("gmt_create") |
|||
); |
|||
return SpringBeanUtil.copyProperties(accountFileDOList, AccountFileDTO.class); |
|||
} |
|||
|
|||
@Override |
|||
public Long createFolder(FolderCreateReq req) { |
|||
AccountFileDTO accountFileDTO = AccountFileDTO.builder().accountId(req.getAccountId()) |
|||
.parentId(req.getParentId()) |
|||
.fileName(req.getFolderName()) |
|||
.isDir(FolderFlagEnum.YES.getCode()).build(); |
|||
return saveAccountFile(accountFileDTO); |
|||
} |
|||
|
|||
|
|||
@Override |
|||
public void renameFile(FileUpdateReq req) { |
|||
//检查ID是否存在
|
|||
AccountFileDO accountFileDO = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>() |
|||
.eq("id", req.getFileId()).eq("account_id", req.getAccountId())); |
|||
|
|||
if (accountFileDO == null) { |
|||
log.error("文件不存在,{}", req); |
|||
throw new BizException(BizCodeEnum.FILE_NOT_EXISTS); |
|||
} else { |
|||
//新旧文件名称不能一样
|
|||
if (Objects.equals(accountFileDO.getFileName(), req.getNewFilename())) { |
|||
log.error("文件名称重复,{}", req); |
|||
throw new BizException(BizCodeEnum.FILE_RENAME_REPEAT); |
|||
} |
|||
//同层文件名称不能一样
|
|||
Long selectCount = accountFileMapper.selectCount(new QueryWrapper<AccountFileDO>() |
|||
.eq("account_id", req.getAccountId()) |
|||
.eq("parent_id", accountFileDO.getParentId()) |
|||
.eq("file_name", req.getNewFilename())); |
|||
if (selectCount > 0) { |
|||
log.error("文件名称重复,{}", req); |
|||
throw new BizException(BizCodeEnum.FILE_RENAME_REPEAT); |
|||
} else { |
|||
accountFileDO.setFileName(req.getNewFilename()); |
|||
accountFileMapper.updateById(accountFileDO); |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
@Override |
|||
public List<FolderTreeNodeDTO> folderTree(Long accountId) { |
|||
//查询用户全部文件夹
|
|||
List<AccountFileDO> folderList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>() |
|||
.eq("account_id", accountId) |
|||
.eq("is_dir", FolderFlagEnum.YES.getCode()) |
|||
); |
|||
|
|||
if(CollectionUtils.isEmpty(folderList)){ |
|||
return List.of(); |
|||
} |
|||
//构建一个map, key是文件ID,value是文件对象 相当于一个数据源
|
|||
Map<Long, FolderTreeNodeDTO> folderMap = folderList.stream() |
|||
.collect(Collectors.toMap(AccountFileDO::getId, accountFileDO -> |
|||
FolderTreeNodeDTO.builder() |
|||
.id(accountFileDO.getId()) |
|||
.parentId(accountFileDO.getParentId()) |
|||
.label(accountFileDO.getFileName()) |
|||
.children(new ArrayList<>()) |
|||
.build() |
|||
)); |
|||
|
|||
//构建文件树,遍历数据源,为每个文件夹找到子文件夹
|
|||
for (FolderTreeNodeDTO node : folderMap.values()) { |
|||
Long parentId = node.getParentId(); |
|||
if(parentId!=null && folderMap.containsKey(parentId)){ |
|||
//获取父文件
|
|||
FolderTreeNodeDTO parentNode = folderMap.get(parentId); |
|||
//获取父文件夹的子节点位置
|
|||
List<FolderTreeNodeDTO> children = parentNode.getChildren(); |
|||
//将当前节点添加到对应的文件夹里面
|
|||
children.add(node); |
|||
} |
|||
} |
|||
//过滤根节点,即parentID是0的
|
|||
List<FolderTreeNodeDTO> rootFolderList = folderMap.values().stream() |
|||
.filter(node -> Objects.equals(node.getParentId(), 0L)) |
|||
.collect(Collectors.toList()); |
|||
|
|||
|
|||
return rootFolderList; |
|||
|
|||
} |
|||
/** |
|||
* 查询文件树接口 (非递归方式) |
|||
* 1、查询用户全部文件夹 |
|||
* 2、拼装文件树 |
|||
* @param accountId |
|||
* @return |
|||
*/ |
|||
//@Override
|
|||
public List<FolderTreeNodeDTO> folderTreeV2(Long accountId) { |
|||
//查询用户全部文件夹
|
|||
List<AccountFileDO> folderList = accountFileMapper.selectList(new QueryWrapper<AccountFileDO>() |
|||
.eq("account_id", accountId) |
|||
.eq("is_dir", FolderFlagEnum.YES.getCode()) |
|||
); |
|||
|
|||
if(CollectionUtils.isEmpty(folderList)){ |
|||
return List.of(); |
|||
} |
|||
|
|||
List<FolderTreeNodeDTO> folderTreeNodeDTOList = folderList.stream().map(file -> FolderTreeNodeDTO.builder() |
|||
.id(file.getId()) |
|||
.parentId(file.getParentId()) |
|||
.label(file.getFileName()) |
|||
.children(new ArrayList<>()) |
|||
.build()).toList(); |
|||
|
|||
//根据父文件ID进行分组,key是当前文件夹ID,value是对应的子文件夹列表,也是数据源
|
|||
Map<Long, List<FolderTreeNodeDTO>> folderMap = folderTreeNodeDTOList |
|||
.stream().collect(Collectors.groupingBy(FolderTreeNodeDTO::getParentId)); |
|||
|
|||
//处理拼装文件树
|
|||
for(FolderTreeNodeDTO node : folderTreeNodeDTOList){ |
|||
List<FolderTreeNodeDTO> children = folderMap.get(node.getId()); |
|||
//判断是否为空
|
|||
if(!CollectionUtils.isEmpty(children)){ |
|||
node.getChildren().addAll(children); |
|||
} |
|||
} |
|||
//过滤根节点,即parentID是0的
|
|||
List<FolderTreeNodeDTO> folderTreeNodeDTOS = folderTreeNodeDTOList.stream().filter(node -> Objects.equals(node.getParentId(), 0L)) |
|||
.collect(Collectors.toList()); |
|||
|
|||
return folderTreeNodeDTOS; |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 文件上传 |
|||
* 1、上传到存储引擎 |
|||
* 2、保存文件关系 |
|||
* 3、保存账号和文件的关系 |
|||
*/ |
|||
@Override |
|||
@Transactional(rollbackFor = Exception.class) |
|||
public void fileUpload(FileUploadReq req) { |
|||
//上传到存储引擎
|
|||
String storeFileObjectKey = storeFile(req); |
|||
//保存文件关系 + 保存账号和文件的关系
|
|||
saveFileAndAccountFile( req,storeFileObjectKey); |
|||
} |
|||
|
|||
/** |
|||
* 批量移动文件 |
|||
* 1、检查被移动的文件ID是否合法 |
|||
* 2、检查目标文件夹ID是否合法 |
|||
* 3、批量移动文件到目标文件夹(重复名称处理) |
|||
* |
|||
* @param req |
|||
*/ |
|||
@Override |
|||
public void moveBatch(FileBatchReq req) { |
|||
|
|||
// 检查被移动的文件ID是否合法
|
|||
List<AccountFileDO> accountFileDOList = checkFileIdLegal(req.getFileIds(),req.getAccountId()); |
|||
|
|||
//检查目标文件夹ID是否合法,包括子文件夹
|
|||
checkTargetParentIdLegal(req); |
|||
|
|||
//批量移动文件到目标文件夹(重复名称处理)
|
|||
accountFileDOList.forEach(this::processFileNameDuplicate); |
|||
|
|||
//更新文件或者文件夹的parent_id为目标文件夹的ID
|
|||
UpdateWrapper<AccountFileDO> updateWrapper = new UpdateWrapper<>(); |
|||
updateWrapper.in("id",req.getFileIds()) |
|||
.set("parent_id",req.getTargetParentId()); |
|||
int updateCount = accountFileMapper.update(null, updateWrapper); |
|||
if(updateCount!=req.getFileIds().size()){ |
|||
throw new BizException(BizCodeEnum.FILE_BATCH_UPDATE_ERROR); |
|||
} |
|||
|
|||
} |
|||
|
|||
|
|||
/** |
|||
* 检查目标文件夹ID是否合法,包括子文件夹 |
|||
* 1、目标的文件ID不能是文件 |
|||
* 2、要操作的文件列表不能包括目标文件ID |
|||
* @param req |
|||
*/ |
|||
private void checkTargetParentIdLegal(FileBatchReq req) { |
|||
//目标的文件ID不能是文件
|
|||
AccountFileDO targetAccountFileDO = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>() |
|||
.eq("id", req.getTargetParentId()) |
|||
.eq("is_dir", FolderFlagEnum.YES.getCode()) |
|||
.eq("account_id", req.getAccountId())); |
|||
if (targetAccountFileDO == null) { |
|||
log.error("目标文件ID不是文件,需要是文件夹,targetParentId={}", req.getTargetParentId()); |
|||
throw new BizException(BizCodeEnum.FILE_TARGET_PARENT_ILLEGAL); |
|||
} |
|||
} |
|||
/** |
|||
* 检查被移动的文件ID是否合法 |
|||
* @param fileIds |
|||
* @param accountId |
|||
* @return |
|||
*/ |
|||
private List<AccountFileDO> checkFileIdLegal(List<Long> fileIds, Long accountId) { |
|||
|
|||
List<AccountFileDO> accountFileDOList = accountFileMapper |
|||
.selectList(new QueryWrapper<AccountFileDO>().in("id", fileIds).eq("account_id", accountId)); |
|||
|
|||
if(accountFileDOList.size()!=fileIds.size()){ |
|||
log.error("文件ID数量不合法,ids={}", fileIds); |
|||
throw new BizException(BizCodeEnum.FILE_BATCH_UPDATE_ERROR); |
|||
} |
|||
//进一步完善的话,可以加个set,防止重复元素
|
|||
|
|||
return accountFileDOList; |
|||
} |
|||
|
|||
/** |
|||
* 保存文件和账号文件的关系到数据库 |
|||
* @param req |
|||
* @param storeFileObjectKey |
|||
*/ |
|||
public void saveFileAndAccountFile(FileUploadReq req, String storeFileObjectKey) { |
|||
//保存文件
|
|||
FileDO fileDO = saveFile(req,storeFileObjectKey); |
|||
|
|||
//保存文件账号关系
|
|||
AccountFileDTO accountFileDTO = AccountFileDTO.builder() |
|||
.accountId(req.getAccountId()) |
|||
.parentId(req.getParentId()) |
|||
.fileId(fileDO.getId()) |
|||
.fileName(req.getFilename()) |
|||
.isDir(FolderFlagEnum.NO.getCode()) |
|||
.fileSuffix(fileDO.getFileSuffix()) |
|||
.fileSize(req.getFileSize()) |
|||
.fileType(FileTypeEnum.fromExtension(fileDO.getFileSuffix()).name()) |
|||
.build(); |
|||
saveAccountFile(accountFileDTO); |
|||
|
|||
} |
|||
|
|||
private FileDO saveFile(FileUploadReq req, String storeFileObjectKey) { |
|||
FileDO fileDO = new FileDO(); |
|||
fileDO.setAccountId(req.getAccountId()); |
|||
fileDO.setFileName(req.getFilename()); |
|||
fileDO.setFileSize(req.getFile() !=null ? req.getFile().getSize():req.getFileSize()); |
|||
fileDO.setFileSuffix(CommonUtil.getFileSuffix(req.getFilename())); |
|||
fileDO.setObjectKey(storeFileObjectKey); |
|||
fileDO.setIdentifier(req.getIdentifier()); |
|||
fileMapper.insert(fileDO); |
|||
return fileDO; |
|||
} |
|||
|
|||
/** |
|||
* 上传文件到存储引擎,返回存储的文件路径 |
|||
* @param req |
|||
* @return |
|||
*/ |
|||
private String storeFile(FileUploadReq req) { |
|||
|
|||
String objectKey = CommonUtil.getFilePath(req.getFilename()); |
|||
fileStoreEngine.upload(minioConfig.getBucketName(), objectKey, req.getFile()); |
|||
return objectKey; |
|||
} |
|||
/** |
|||
* 处理用户和文件的关系,存储文件和文件夹都是可以的 |
|||
* <p> |
|||
* 1、检查父文件是否存在 |
|||
* 2、检查文件是否重复 |
|||
* 3、保存相关文件关系 |
|||
* |
|||
* @param accountFileDTO |
|||
* @return |
|||
*/ |
|||
private Long saveAccountFile(AccountFileDTO accountFileDTO) { |
|||
//检查父文件是否存在
|
|||
checkParentFileId(accountFileDTO); |
|||
AccountFileDO accountFileDO = SpringBeanUtil.copyProperties(accountFileDTO, AccountFileDO.class); |
|||
//检查文件是否重复 aa aa(1) aa(2)
|
|||
processFileNameDuplicate(accountFileDO); |
|||
//保存相关文件关系
|
|||
accountFileMapper.insert(accountFileDO); |
|||
|
|||
return accountFileDO.getId(); |
|||
} |
|||
|
|||
/** |
|||
* 检查父文件是否存在 |
|||
* |
|||
* @param accountFileDTO |
|||
*/ |
|||
private void checkParentFileId(AccountFileDTO accountFileDTO) { |
|||
if (accountFileDTO.getParentId() != 0) { |
|||
AccountFileDO accountFileDO = accountFileMapper.selectOne( |
|||
new QueryWrapper<AccountFileDO>() |
|||
.eq("id", accountFileDTO.getParentId()) |
|||
.eq("account_id", accountFileDTO.getAccountId())); |
|||
|
|||
if (accountFileDO == null) { |
|||
throw new BizException(BizCodeEnum.FILE_NOT_EXISTS); |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 处理文件是否重复, |
|||
* 文件夹重复和文件名重复处理规则不一样 |
|||
* |
|||
* @param accountFileDO |
|||
*/ |
|||
private void processFileNameDuplicate(AccountFileDO accountFileDO) { |
|||
Long selectCount = accountFileMapper.selectCount(new QueryWrapper<AccountFileDO>() |
|||
.eq("account_id", accountFileDO.getAccountId()) |
|||
.eq("parent_id", accountFileDO.getParentId()) |
|||
.eq("is_dir", accountFileDO.getIsDir()) |
|||
.eq("file_name", accountFileDO.getFileName())); |
|||
|
|||
if (selectCount > 0) { |
|||
//处理重复文件夹
|
|||
if (Objects.equals(accountFileDO.getIsDir(), FolderFlagEnum.YES.getCode())) { |
|||
accountFileDO.setFileName(accountFileDO.getFileName() + "_" + System.currentTimeMillis()); |
|||
} else { |
|||
//处理重复文件名,提取文件拓展名
|
|||
String[] split = accountFileDO.getFileName().split("\\."); |
|||
accountFileDO.setFileName(split[0] + "_" + System.currentTimeMillis() + "." + split[1]); |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,124 @@ |
|||
package org.ycloud.aipan.service.impl; |
|||
|
|||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.stereotype.Service; |
|||
import org.springframework.util.DigestUtils; |
|||
import org.springframework.web.multipart.MultipartFile; |
|||
import org.ycloud.aipan.component.StoreEngine; |
|||
import org.ycloud.aipan.config.AccountConfig; |
|||
import org.ycloud.aipan.config.MinioConfig; |
|||
import org.ycloud.aipan.controller.req.AccountLoginReq; |
|||
import org.ycloud.aipan.controller.req.AccountRegisterReq; |
|||
import org.ycloud.aipan.controller.req.FolderCreateReq; |
|||
import org.ycloud.aipan.dto.AccountDTO; |
|||
import org.ycloud.aipan.dto.StorageDTO; |
|||
import org.ycloud.aipan.enums.AccountRoleEnum; |
|||
import org.ycloud.aipan.enums.BizCodeEnum; |
|||
import org.ycloud.aipan.exception.BizException; |
|||
import org.ycloud.aipan.mapper.AccountFileMapper; |
|||
import org.ycloud.aipan.mapper.AccountMapper; |
|||
import org.ycloud.aipan.mapper.StorageMapper; |
|||
import org.ycloud.aipan.model.AccountDO; |
|||
import org.ycloud.aipan.model.AccountFileDO; |
|||
import org.ycloud.aipan.model.StorageDO; |
|||
import org.ycloud.aipan.service.AccountFileService; |
|||
import org.ycloud.aipan.service.AccountService; |
|||
import org.ycloud.aipan.util.CommonUtil; |
|||
import org.ycloud.aipan.util.SpringBeanUtil; |
|||
|
|||
import java.util.List; |
|||
|
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class AccountServiceImpl implements AccountService { |
|||
|
|||
@Autowired |
|||
private AccountMapper accountMapper; |
|||
@Autowired |
|||
private StoreEngine fileStoreEngine; |
|||
@Autowired |
|||
private MinioConfig minioConfig; |
|||
@Autowired |
|||
private AccountFileService accountFileService; |
|||
@Autowired |
|||
private StorageMapper storageMapper; |
|||
@Autowired |
|||
private AccountFileMapper accountFileMapper; |
|||
|
|||
@Override |
|||
public void register(AccountRegisterReq req) { |
|||
|
|||
//1、查询手机号是否重复
|
|||
List<AccountDO> accountDOList = accountMapper.selectList(new QueryWrapper<AccountDO>().eq("phone", req.getPhone())); |
|||
if (!accountDOList.isEmpty()) { |
|||
throw new BizException(BizCodeEnum.ACCOUNT_REPEAT); |
|||
} |
|||
|
|||
AccountDO accountDO = SpringBeanUtil.copyProperties(req, AccountDO.class); |
|||
|
|||
//加密密码
|
|||
String digestAsHex = DigestUtils.md5DigestAsHex((AccountConfig.ACCOUNT_SALT + req.getPassword()).getBytes()); |
|||
accountDO.setPassword(digestAsHex); |
|||
accountDO.setRole(AccountRoleEnum.COMMON.name()); |
|||
accountMapper.insert(accountDO); |
|||
|
|||
//其他操作 TODO
|
|||
//创建默认的存储空间
|
|||
StorageDO storageDO = new StorageDO(); |
|||
storageDO.setAccountId(accountDO.getId()); |
|||
storageDO.setUsedSize(0L); |
|||
storageDO.setTotalSize(AccountConfig.DEFAULT_STORAGE_SIZE); |
|||
storageMapper.insert(storageDO); |
|||
|
|||
//初始化根目录
|
|||
FolderCreateReq createRootFolderReq = FolderCreateReq.builder() |
|||
.accountId(accountDO.getId()) |
|||
.parentId(AccountConfig.ROOT_PARENT_ID) |
|||
.folderName(AccountConfig.ROOT_FOLDER_NAME) |
|||
.build(); |
|||
accountFileService.createFolder(createRootFolderReq); |
|||
|
|||
} |
|||
|
|||
@Override |
|||
public String uploadAvatar(MultipartFile file) { |
|||
String filename = CommonUtil.getFilePath(file.getOriginalFilename()); |
|||
fileStoreEngine.upload(minioConfig.getAvatarBucketName(), filename, file); |
|||
return minioConfig.getEndpoint() + "/" + minioConfig.getAvatarBucketName() + "/" + filename; |
|||
} |
|||
|
|||
@Override |
|||
public AccountDTO login(AccountLoginReq req) { |
|||
//处理密码
|
|||
String digestAsHex = DigestUtils.md5DigestAsHex((AccountConfig.ACCOUNT_SALT + req.getPassword()).getBytes()); |
|||
AccountDO accountDO = accountMapper.selectOne(new QueryWrapper<AccountDO>().eq("phone", req.getPhone()).eq("password", digestAsHex)); |
|||
if(accountDO == null){ |
|||
throw new BizException(BizCodeEnum.ACCOUNT_PWD_ERROR); |
|||
} |
|||
return SpringBeanUtil.copyProperties(accountDO,AccountDTO.class); |
|||
} |
|||
|
|||
@Override |
|||
public AccountDTO queryDetail(Long id) { |
|||
|
|||
//账号详情
|
|||
AccountDO accountDO = accountMapper.selectById(id); |
|||
AccountDTO accountDTO = SpringBeanUtil.copyProperties(accountDO, AccountDTO.class); |
|||
|
|||
//获取存储信息
|
|||
StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", id)); |
|||
accountDTO.setStorageDTO(SpringBeanUtil.copyProperties(storageDO, StorageDTO.class)); |
|||
|
|||
//获取文件信息
|
|||
AccountFileDO accountFileDO = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>() |
|||
.eq("account_id", id).eq("parent_id", AccountConfig.ROOT_PARENT_ID)); |
|||
if (accountFileDO != null) { |
|||
accountDTO.setRootFileId(accountFileDO.getId()); |
|||
accountDTO.setRootFileName(accountFileDO.getFileName()); |
|||
} |
|||
return accountDTO; |
|||
} |
|||
} |
@ -0,0 +1,13 @@ |
|||
package org.ycloud.aipan.service.impl; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.springframework.stereotype.Service; |
|||
import org.ycloud.aipan.service.FileService; |
|||
|
|||
|
|||
@Service |
|||
@Slf4j |
|||
public class FileServiceImpl implements FileService { |
|||
|
|||
|
|||
} |
@ -0,0 +1,61 @@ |
|||
package org.ycloud.aipan.util; |
|||
|
|||
import cn.hutool.core.date.DateUtil; |
|||
import cn.hutool.core.util.IdUtil; |
|||
import cn.hutool.core.util.StrUtil; |
|||
import jakarta.servlet.http.HttpServletResponse; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.io.IOException; |
|||
import java.io.PrintWriter; |
|||
|
|||
@Slf4j |
|||
public class CommonUtil { |
|||
|
|||
/** |
|||
* 响应json数据给前端 |
|||
* |
|||
* @param response HttpServletResponse对象,用于向客户端发送响应 |
|||
* @param obj 需要转换为json格式的对象 |
|||
*/ |
|||
public static void sendJsonMessage(HttpServletResponse response, Object obj) { |
|||
// 设置响应内容类型为json,并指定字符编码为utf-8
|
|||
response.setContentType("application/json; charset=utf-8"); |
|||
|
|||
try (PrintWriter writer = response.getWriter()) { |
|||
// 将对象转换为json字符串并写入响应输出流
|
|||
writer.print(JsonUtil.obj2Json(obj)); |
|||
// 刷新缓冲区,确保数据被发送到客户端
|
|||
response.flushBuffer(); |
|||
|
|||
} catch (IOException e) { |
|||
// 捕获并记录异常信息
|
|||
log.warn("响应json数据给前端异常:{}", e.getMessage()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 根据文件名称获取文件后缀 |
|||
* |
|||
* @param fileName 文件名 |
|||
* @return 文件后缀名 |
|||
*/ |
|||
public static String getFileSuffix(String fileName) { |
|||
// 从文件名中提取后缀名
|
|||
return fileName.substring(fileName.lastIndexOf(".") + 1); |
|||
} |
|||
|
|||
/** |
|||
* 根据文件后缀,生成文件存储路径:年/月/日/uuid.suffix 格式 |
|||
* |
|||
* @param fileName 文件名 |
|||
* @return 生成的文件存储路径 |
|||
*/ |
|||
public static String getFilePath(String fileName) { |
|||
// 获取文件后缀名
|
|||
String suffix = getFileSuffix(fileName); |
|||
// 生成文件在存储桶中的唯一键
|
|||
return StrUtil.format("{}/{}/{}/{}.{}", DateUtil.thisYear(), DateUtil.thisMonth() + 1, DateUtil.thisDayOfMonth(), IdUtil.randomUUID(), suffix); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,102 @@ |
|||
package org.ycloud.aipan.util; |
|||
|
|||
import com.alibaba.fastjson2.JSON; |
|||
import lombok.AllArgsConstructor; |
|||
import lombok.Data; |
|||
import lombok.NoArgsConstructor; |
|||
import org.ycloud.aipan.enums.BizCodeEnum; |
|||
|
|||
|
|||
/** |
|||
* 响应类 |
|||
*/ |
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class JsonData { |
|||
|
|||
/** |
|||
* 状态码 0 表示成功 |
|||
*/ |
|||
|
|||
private Integer code; |
|||
/** |
|||
* 数据 |
|||
*/ |
|||
private Object data; |
|||
/** |
|||
* 描述 |
|||
*/ |
|||
private String msg; |
|||
|
|||
/** |
|||
* 获取远程调用数据 |
|||
* |
|||
* @param typeReference 数据类型的引用 |
|||
* @param <T> 泛型类型 |
|||
* @return 返回解析后的对象 |
|||
*/ |
|||
public <T> T getData(Class<T> typeReference) { |
|||
return JSON.parseObject(JSON.toJSONString(data), typeReference); |
|||
} |
|||
|
|||
/** |
|||
* 成功,不传入数据 |
|||
* |
|||
* @return 返回一个状态码为0的JsonData对象 |
|||
*/ |
|||
public static JsonData buildSuccess() { |
|||
return new JsonData(0, null, null); |
|||
} |
|||
|
|||
/** |
|||
* 成功,传入数据 |
|||
* |
|||
* @param data 成功时返回的数据 |
|||
* @return 返回一个JsonData对象,其中包含状态码0和传入的数据 |
|||
*/ |
|||
public static JsonData buildSuccess(Object data) { |
|||
return new JsonData(0, data, null); |
|||
} |
|||
|
|||
/** |
|||
* 失败,传入描述信息 |
|||
* |
|||
* @param msg 失败时的描述信息 |
|||
* @return 返回一个JsonData对象,其中包含状态码-1和传入的描述信息 |
|||
*/ |
|||
public static JsonData buildError(String msg) { |
|||
return new JsonData(-1, null, msg); |
|||
} |
|||
|
|||
/** |
|||
* 自定义状态码和错误信息 |
|||
* |
|||
* @param code 自定义的状态码 |
|||
* @param msg 自定义的错误信息 |
|||
* @return 返回一个JsonData对象,其中包含传入的状态码和错误信息 |
|||
*/ |
|||
public static JsonData buildCodeAndMsg(int code, String msg) { |
|||
return new JsonData(code, null, msg); |
|||
} |
|||
|
|||
/** |
|||
* 自定义状态码和错误信息 |
|||
* |
|||
* @param codeEnum 自定义的状态码枚举 |
|||
* @return 返回一个JsonData对象,其中包含传入的状态码枚举对应的状态码和错误信息 |
|||
*/ |
|||
public static JsonData buildResult(BizCodeEnum codeEnum) { |
|||
return JsonData.buildCodeAndMsg(codeEnum.getCode(), codeEnum.getMessage()); |
|||
} |
|||
|
|||
/** |
|||
* 判断当前JsonData对象是否表示成功 |
|||
* |
|||
* @return 如果状态码为0,则返回true,表示成功;否则返回false,表示失败 |
|||
*/ |
|||
public boolean isSuccess() { |
|||
return code == 0; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,99 @@ |
|||
package org.ycloud.aipan.util; |
|||
|
|||
import com.fasterxml.jackson.annotation.JsonInclude; |
|||
import com.fasterxml.jackson.core.JsonParser; |
|||
import com.fasterxml.jackson.core.JsonProcessingException; |
|||
import com.fasterxml.jackson.databind.DeserializationFeature; |
|||
import com.fasterxml.jackson.databind.JavaType; |
|||
import com.fasterxml.jackson.databind.ObjectMapper; |
|||
import com.fasterxml.jackson.databind.SerializationFeature; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
|
|||
import java.text.SimpleDateFormat; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
@Slf4j |
|||
public class JsonUtil { |
|||
// 创建一个ObjectMapper对象,用于处理JSON数据的序列化和反序列化
|
|||
private static final ObjectMapper MAPPER = new ObjectMapper(); |
|||
|
|||
// 静态代码块,用于初始化ObjectMapper对象的配置
|
|||
static { |
|||
//设置可用单引号
|
|||
MAPPER.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); |
|||
//序列化的时候序列对象的所有属性
|
|||
MAPPER.setSerializationInclusion(JsonInclude.Include.ALWAYS); |
|||
//反序列化的时候如果多了其他属性,不抛出异常
|
|||
MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); |
|||
//下划线和驼峰互转
|
|||
//mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
|
|||
//如果是空对象的时候,不抛异常
|
|||
MAPPER.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); |
|||
//取消时间的转化格式,默认是时间戳,可以取消,同时需要设置要表现的时间格式
|
|||
MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); |
|||
MAPPER.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); |
|||
} |
|||
|
|||
/** |
|||
* 获取ObjectMapper对象 |
|||
* |
|||
* @return ObjectMapper对象 |
|||
*/ |
|||
public static ObjectMapper get() { |
|||
return MAPPER; |
|||
} |
|||
|
|||
/** |
|||
* 将对象转换为JSON字符串 |
|||
* |
|||
* @param obj 要转换的对象 |
|||
* @return JSON字符串 |
|||
*/ |
|||
public static String obj2Json(Object obj) { |
|||
String jsonStr = null; |
|||
try { |
|||
jsonStr = MAPPER.writeValueAsString(obj); |
|||
} catch (JsonProcessingException e) { |
|||
log.error("json格式化异常", e); |
|||
} |
|||
return jsonStr; |
|||
} |
|||
|
|||
/** |
|||
* 将JSON字符串转换为对象 |
|||
* |
|||
* @param jsonStr 要转换的JSON字符串 |
|||
* @param beanType 目标对象的类型 |
|||
* @return 转换后的对象 |
|||
*/ |
|||
public static <T> T json2Obj(String jsonStr, Class<T> beanType) { |
|||
T obj = null; |
|||
try { |
|||
obj = MAPPER.readValue(jsonStr, beanType); |
|||
} catch (Exception e) { |
|||
log.error("json格式化异常", e); |
|||
} |
|||
return obj; |
|||
} |
|||
|
|||
/** |
|||
* 将JSON数据转换为对象列表 |
|||
* |
|||
* @param jsonData 要转换的JSON数据 |
|||
* @param beanType 目标对象的类型 |
|||
* @return 转换后的对象列表 |
|||
*/ |
|||
public static <T> List<T> json2List(String jsonData, Class<T> beanType) { |
|||
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType); |
|||
try { |
|||
// 使用ObjectMapper将JSON数据转换为对象列表
|
|||
return MAPPER.readValue(jsonData, javaType); |
|||
} catch (Exception e) { |
|||
log.error("json格式化异常", e); |
|||
} |
|||
// 返回空列表
|
|||
return new ArrayList<>(0); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,121 @@ |
|||
package org.ycloud.aipan.util; |
|||
|
|||
import io.jsonwebtoken.Claims; |
|||
import io.jsonwebtoken.Jwts; |
|||
import io.jsonwebtoken.security.Keys; |
|||
import io.jsonwebtoken.security.SecureDigestAlgorithm; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.ycloud.aipan.dto.AccountDTO; |
|||
|
|||
import javax.crypto.SecretKey; |
|||
import java.util.Date; |
|||
|
|||
|
|||
@Slf4j |
|||
public class JwtUtil { |
|||
|
|||
// JWT的主题
|
|||
private static final String LOGIN_SUBJECT = "YUAN"; |
|||
|
|||
|
|||
//注意这个密钥长度需要足够长, 推荐:JWT的密钥,从环境变量中获取
|
|||
private final static String SECRET_KEY = "yuan.yuannet.netyuan.password.3421.sdfnet.com.efszyuaszgh"; |
|||
// 签名算法
|
|||
private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256; |
|||
// 使用密钥
|
|||
private final static SecretKey KEY = Keys.hmacShaKeyFor(SECRET_KEY.getBytes()); |
|||
// token过期时间,30天
|
|||
private static final long EXPIRED = 1000 * 60 * 60 * 24 * 7; |
|||
|
|||
/** |
|||
* 生成JWT |
|||
* |
|||
* @param accountDTO 登录账户信息 |
|||
* @return 生成的JWT字符串 |
|||
* @throws NullPointerException 如果传入的accountDTO为空 |
|||
*/ |
|||
public static String geneLoginJWT(AccountDTO accountDTO) { |
|||
if (accountDTO == null) { |
|||
throw new NullPointerException("对象为空"); |
|||
} |
|||
|
|||
// 创建 JWT token
|
|||
String token = Jwts.builder() |
|||
.subject(LOGIN_SUBJECT) |
|||
.claim("accountId", accountDTO.getId()) |
|||
.claim("username", accountDTO.getUsername()) |
|||
.issuedAt(new Date()) |
|||
.expiration(new Date(System.currentTimeMillis() + EXPIRED)) |
|||
.signWith(KEY, ALGORITHM) // 直接使用KEY即可
|
|||
.compact(); |
|||
|
|||
// 添加自定义前缀
|
|||
return addPrefix(token, LOGIN_SUBJECT); |
|||
} |
|||
|
|||
/** |
|||
* 校验JWT |
|||
* |
|||
* @param token JWT字符串 |
|||
* @return JWT的Claims部分 |
|||
* @throws IllegalArgumentException 如果传入的token为空或只包含空白字符 |
|||
* @throws RuntimeException 如果JWT签名验证失败、JWT已过期或JWT解密失败 |
|||
*/ |
|||
public static Claims checkLoginJWT(String token) { |
|||
try { |
|||
log.debug("开始校验 JWT: {}", token); |
|||
// 校验 Token 是否为空
|
|||
if (token == null || token.trim().isEmpty()) { |
|||
log.error("Token 不能为空"); |
|||
throw new IllegalArgumentException("Token 不能为空"); |
|||
} |
|||
token = token.trim(); |
|||
// 移除前缀
|
|||
token = removePrefix(token, LOGIN_SUBJECT); |
|||
log.debug("移除前缀后的 Token: {}", token); |
|||
// 解析 JWT
|
|||
Claims payload = Jwts.parser() |
|||
.verifyWith(KEY) //设置签名的密钥, 使用相同的 KEY
|
|||
.build() |
|||
.parseSignedClaims(token).getPayload(); |
|||
|
|||
log.info("JWT 解密成功,Claims: {}", payload); |
|||
return payload; |
|||
} catch (IllegalArgumentException e) { |
|||
log.error("JWT 校验失败: {}", e.getMessage(), e); |
|||
throw e; |
|||
} catch (io.jsonwebtoken.security.SignatureException e) { |
|||
log.error("JWT 签名验证失败: {}", e.getMessage(), e); |
|||
throw new RuntimeException("JWT 签名验证失败", e); |
|||
} catch (io.jsonwebtoken.ExpiredJwtException e) { |
|||
log.error("JWT 已过期: {}", e.getMessage(), e); |
|||
throw new RuntimeException("JWT 已过期", e); |
|||
} catch (Exception e) { |
|||
log.error("JWT 解密失败: {}", e.getMessage(), e); |
|||
throw new RuntimeException("JWT 解密失败", e); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 给token添加前缀 |
|||
* |
|||
* @param token 原始token字符串 |
|||
* @return 添加前缀后的token字符串 |
|||
*/ |
|||
private static String addPrefix(String token, String prefix) { |
|||
return prefix + token; |
|||
} |
|||
|
|||
/** |
|||
* 移除token的前缀 |
|||
* |
|||
* @param token 带前缀的token字符串 |
|||
* @return 移除前缀后的token字符串 |
|||
*/ |
|||
private static String removePrefix(String token, String prefix) { |
|||
if (token.startsWith(prefix)) { |
|||
return token.replace(prefix, "").trim(); |
|||
} |
|||
return token; |
|||
} |
|||
} |
@ -0,0 +1,59 @@ |
|||
package org.ycloud.aipan.util; |
|||
|
|||
import org.springframework.beans.BeanUtils; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
/** |
|||
* SpringBeanUtil 工具类,提供了对象属性复制的功能。 |
|||
*/ |
|||
public class SpringBeanUtil { |
|||
|
|||
/** |
|||
* 复制属性 |
|||
* |
|||
* @param <T> 目标对象类型 |
|||
* @param source 源对象 |
|||
* @param target 目标对象类型 |
|||
* @return 复制后的目标对象 |
|||
*/ |
|||
public static <T> T copyProperties(Object source, Class<T> target) { |
|||
try { |
|||
T t = target.getConstructor().newInstance(); |
|||
BeanUtils.copyProperties(source, t); |
|||
return t; |
|||
} catch (Exception e) { |
|||
throw new RuntimeException(e); |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 复制一份具有相同属性的列表 |
|||
* |
|||
* @param sourceList 源列表 |
|||
* @param target 目标对象的类型 |
|||
* @param <T> 目标对象的类型 |
|||
* @return 复制后的目标列表 |
|||
*/ |
|||
public static <T> List<T> copyProperties(List<?> sourceList, Class<T> target) { |
|||
ArrayList<T> targetList = new ArrayList<>(); |
|||
sourceList.forEach(source -> { |
|||
T t = copyProperties(source, target); |
|||
targetList.add(t); |
|||
}); |
|||
return targetList; |
|||
} |
|||
|
|||
/** |
|||
* 复制属性 |
|||
* |
|||
* @param source 源对象 |
|||
* @param target 目标对象 |
|||
*/ |
|||
public static void copyProperties(Object source, Object target){ |
|||
BeanUtils.copyProperties(source,target); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,47 @@ |
|||
server: |
|||
port: 8081 |
|||
|
|||
spring: |
|||
application: |
|||
name: ycloud-aipan |
|||
|
|||
datasource: |
|||
driver-class-name: com.mysql.cj.jdbc.Driver |
|||
url: jdbc:mysql://47.98.137.138:3306/ycloud-aipan?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true |
|||
username: root |
|||
password: Yuan625621105 |
|||
|
|||
data: |
|||
redis: |
|||
host: 47.98.137.138 |
|||
port: 6379 |
|||
password: yuan625621105 |
|||
|
|||
|
|||
# MyBatis-Plus配置 |
|||
mybatis-plus: |
|||
# 全局配置 |
|||
global-config: |
|||
# 数据库配置 |
|||
db-config: |
|||
# 逻辑删除字段 |
|||
logic-delete-field: del |
|||
# 逻辑删除值 |
|||
logic-delete-value: 1 |
|||
# 逻辑未删除值 |
|||
logic-not-delete-value: 0 |
|||
# 配置 |
|||
configuration: |
|||
# 日志实现 |
|||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl |
|||
# 驼峰命名转换 |
|||
map-underscore-to-camel-case: true |
|||
|
|||
|
|||
# minio配置 |
|||
minio: |
|||
endpoint: http://192.168.60.124:9000 |
|||
access-key: minio_root |
|||
access-secret: minio_123456 |
|||
bucket-name: ai-pan |
|||
avatar-bucket-name: avatar |
@ -0,0 +1,27 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.AccountFileMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.AccountFileDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="account_id" property="accountId" /> |
|||
<result column="is_dir" property="isDir" /> |
|||
<result column="parent_id" property="parentId" /> |
|||
<result column="file_id" property="fileId" /> |
|||
<result column="file_name" property="fileName" /> |
|||
<result column="file_type" property="fileType" /> |
|||
<result column="file_suffix" property="fileSuffix" /> |
|||
<result column="file_size" property="fileSize" /> |
|||
<result column="del" property="del" /> |
|||
<result column="del_time" property="delTime" /> |
|||
<result column="gmt_modified" property="gmtModified" /> |
|||
<result column="gmt_create" property="gmtCreate" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, account_id, is_dir, parent_id, file_id, file_name, file_type, file_suffix, file_size, del, del_time, gmt_modified, gmt_create |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,23 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.AccountMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.AccountDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="username" property="username" /> |
|||
<result column="password" property="password" /> |
|||
<result column="avatar_url" property="avatarUrl" /> |
|||
<result column="phone" property="phone" /> |
|||
<result column="role" property="role" /> |
|||
<result column="del" property="del" /> |
|||
<result column="gmt_create" property="gmtCreate" /> |
|||
<result column="gmt_modified" property="gmtModified" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, username, password, avatar_url, phone, role, del, gmt_create, gmt_modified |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,26 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.FileChunkMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.FileChunkDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="identifier" property="identifier" /> |
|||
<result column="upload_id" property="uploadId" /> |
|||
<result column="file_name" property="fileName" /> |
|||
<result column="bucket_name" property="bucketName" /> |
|||
<result column="object_key" property="objectKey" /> |
|||
<result column="total_size" property="totalSize" /> |
|||
<result column="chunk_size" property="chunkSize" /> |
|||
<result column="chunk_num" property="chunkNum" /> |
|||
<result column="account_id" property="accountId" /> |
|||
<result column="gmt_create" property="gmtCreate" /> |
|||
<result column="gmt_modified" property="gmtModified" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, identifier, upload_id, file_name, bucket_name, object_key, total_size, chunk_size, chunk_num, account_id, gmt_create, gmt_modified |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,24 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.FileMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.FileDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="account_id" property="accountId" /> |
|||
<result column="file_name" property="fileName" /> |
|||
<result column="file_suffix" property="fileSuffix" /> |
|||
<result column="file_size" property="fileSize" /> |
|||
<result column="object_key" property="objectKey" /> |
|||
<result column="identifier" property="identifier" /> |
|||
<result column="del" property="del" /> |
|||
<result column="gmt_modified" property="gmtModified" /> |
|||
<result column="gmt_create" property="gmtCreate" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, account_id, file_name, file_suffix, file_size, object_key, identifier, del, gmt_modified, gmt_create |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,17 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.FileSuffixMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.FileSuffixDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="file_suffix" property="fileSuffix" /> |
|||
<result column="file_type_id" property="fileTypeId" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, file_suffix, file_type_id |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,16 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.FileTypeMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.FileTypeDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="file_type_name" property="fileTypeName" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, file_type_name |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,20 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.ShareFileMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.ShareFileDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="share_id" property="shareId" /> |
|||
<result column="account_file_id" property="accountFileId" /> |
|||
<result column="account_id" property="accountId" /> |
|||
<result column="gmt_create" property="gmtCreate" /> |
|||
<result column="gmt_modified" property="gmtModified" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, share_id, account_file_id, account_id, gmt_create, gmt_modified |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,26 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.ShareMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.ShareDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="share_name" property="shareName" /> |
|||
<result column="share_type" property="shareType" /> |
|||
<result column="share_day_type" property="shareDayType" /> |
|||
<result column="share_day" property="shareDay" /> |
|||
<result column="share_end_time" property="shareEndTime" /> |
|||
<result column="share_url" property="shareUrl" /> |
|||
<result column="share_code" property="shareCode" /> |
|||
<result column="share_status" property="shareStatus" /> |
|||
<result column="account_id" property="accountId" /> |
|||
<result column="gmt_create" property="gmtCreate" /> |
|||
<result column="gmt_modified" property="gmtModified" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, share_name, share_type, share_day_type, share_day, share_end_time, share_url, share_code, share_status, account_id, gmt_create, gmt_modified |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,20 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
|||
<mapper namespace="org.ycloud.aipan.mapper.StorageMapper"> |
|||
|
|||
<!-- 通用查询映射结果 --> |
|||
<resultMap id="BaseResultMap" type="org.ycloud.aipan.model.StorageDO"> |
|||
<id column="id" property="id" /> |
|||
<result column="account_id" property="accountId" /> |
|||
<result column="used_size" property="usedSize" /> |
|||
<result column="total_size" property="totalSize" /> |
|||
<result column="gmt_create" property="gmtCreate" /> |
|||
<result column="gmt_modified" property="gmtModified" /> |
|||
</resultMap> |
|||
|
|||
<!-- 通用查询结果列 --> |
|||
<sql id="Base_Column_List"> |
|||
id, account_id, used_size, total_size, gmt_create, gmt_modified |
|||
</sql> |
|||
|
|||
</mapper> |
@ -0,0 +1,37 @@ |
|||
package org.ycloud.aipan; |
|||
|
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.jupiter.api.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
import org.ycloud.aipan.controller.req.AccountLoginReq; |
|||
import org.ycloud.aipan.controller.req.AccountRegisterReq; |
|||
import org.ycloud.aipan.dto.AccountDTO; |
|||
import org.ycloud.aipan.service.AccountService; |
|||
import org.ycloud.aipan.util.JwtUtil; |
|||
|
|||
@SpringBootTest |
|||
@Slf4j |
|||
public class AccountTest { |
|||
|
|||
@Autowired |
|||
private AccountService accountService; |
|||
|
|||
/** |
|||
* 注册方法测试 |
|||
*/ |
|||
@Test |
|||
public void testRegister() { |
|||
accountService.register(AccountRegisterReq.builder().phone("15600000000").password("123456").username("yuan").avatarUrl("https://yn-blog.oss-cn-chengdu.aliyuncs.com/index/logo.png").build()); |
|||
} |
|||
|
|||
/** |
|||
* 登录方法测试 |
|||
*/ |
|||
@Test |
|||
public void testLogin() { |
|||
AccountDTO accountDTO = accountService.login(AccountLoginReq.builder().phone("15600000000").password("123456").build()); |
|||
String token = JwtUtil.geneLoginJWT(accountDTO); |
|||
log.info("token: {}", token); |
|||
} |
|||
} |
@ -0,0 +1,183 @@ |
|||
package org.ycloud.aipan; |
|||
|
|||
import cn.hutool.core.date.DateUtil; |
|||
import com.amazonaws.HttpMethod; |
|||
import com.amazonaws.services.s3.AmazonS3Client; |
|||
import com.amazonaws.services.s3.model.*; |
|||
import com.amazonaws.util.IOUtils; |
|||
import lombok.SneakyThrows; |
|||
import lombok.extern.slf4j.Slf4j; |
|||
import org.junit.jupiter.api.Test; |
|||
import org.springframework.beans.factory.annotation.Autowired; |
|||
import org.springframework.boot.test.context.SpringBootTest; |
|||
|
|||
import java.io.ByteArrayInputStream; |
|||
import java.io.File; |
|||
import java.io.FileInputStream; |
|||
import java.io.FileOutputStream; |
|||
import java.net.URL; |
|||
import java.util.Date; |
|||
import java.util.Optional; |
|||
|
|||
@SpringBootTest |
|||
@Slf4j |
|||
class AmazonS3ClientTests { |
|||
|
|||
@Autowired |
|||
private AmazonS3Client amazonS3Client; |
|||
|
|||
|
|||
/** |
|||
* 判断bucket是否存在 |
|||
*/ |
|||
@Test |
|||
public void testBucketExists() { |
|||
boolean bucketExist = amazonS3Client.doesBucketExist("ai-pan1"); |
|||
log.info("bucket是否存在:{}", bucketExist); |
|||
} |
|||
|
|||
/** |
|||
* 创建bucket |
|||
*/ |
|||
@Test |
|||
public void testCreateBucket() { |
|||
String bucketName = "avatar"; |
|||
Bucket bucket = amazonS3Client.createBucket(bucketName); |
|||
log.info("bucket:{}", bucket); |
|||
} |
|||
|
|||
/** |
|||
* 删除bucket |
|||
*/ |
|||
@Test |
|||
public void testDeleteBucket() { |
|||
String bucketName = "ai-pan1"; |
|||
amazonS3Client.deleteBucket(bucketName); |
|||
} |
|||
|
|||
/** |
|||
* 获取全部bucket |
|||
*/ |
|||
@Test |
|||
public void testListBuckets() { |
|||
for (Bucket bucket : amazonS3Client.listBuckets()) { |
|||
log.info("bucket:{}", bucket.getName()); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 根据bucket名称获取bucket详情 |
|||
*/ |
|||
@Test |
|||
public void testGetBucket() { |
|||
String bucketName = "ai-pan1"; |
|||
Optional<Bucket> optionalBucket = amazonS3Client.listBuckets().stream().filter(bucket -> bucketName.equals(bucket.getName())).findFirst(); |
|||
if (optionalBucket.isPresent()) { |
|||
log.info("bucket:{}", optionalBucket.get()); |
|||
} else { |
|||
log.info("bucket不存在"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 上传单个文件,直接写入文本 |
|||
*/ |
|||
@Test |
|||
public void testUploadFile() { |
|||
PutObjectResult putObject = amazonS3Client.putObject("ai-pan", "test1.txt", "hello world11"); |
|||
log.info("putObject:{}", putObject); |
|||
} |
|||
|
|||
/** |
|||
* 上传单个文件,直接写入文本 |
|||
*/ |
|||
@Test |
|||
public void testUploadFile2() { |
|||
amazonS3Client.putObject("ai-pan", "test2.txt", new File("/Users/xdclass/Desktop/dpan.sql")); |
|||
} |
|||
|
|||
/** |
|||
* 上传文件 包括文件夹路径 不带斜杠 都一样 |
|||
*/ |
|||
@Test |
|||
public void testUploadFileWithDir1() { |
|||
amazonS3Client.putObject("ai-pan", "aa/bb/test3.txt", new File("/Users/xdclass/Desktop/dpan.sql")); |
|||
} |
|||
|
|||
/** |
|||
* 上传文件 包括文件夹路径 带斜杠 都一样 |
|||
*/ |
|||
@Test |
|||
public void testUploadFileWithDir2() { |
|||
amazonS3Client.putObject("ai-pan", "/a/b/test4.txt", new File("/Users/xdclass/Desktop/dpan.sql")); |
|||
} |
|||
|
|||
/** |
|||
* 上传文件,输入流的方式 带上文件元数据 |
|||
*/ |
|||
@Test |
|||
@SneakyThrows |
|||
public void testUploadFileWithMetadata() { |
|||
try (FileInputStream fileInputStream = new FileInputStream("/Users/xdclass/Desktop/dpan.sql");) { |
|||
ObjectMetadata objectMetadata = new ObjectMetadata(); |
|||
objectMetadata.setContentType("text/plain"); |
|||
amazonS3Client.putObject("ai-pan", "/meta/test5.txt", fileInputStream, objectMetadata); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 上传文件,输入流的方式 带上文件元数据 |
|||
*/ |
|||
@Test |
|||
@SneakyThrows |
|||
public void testUploadFileWithMetadata2() { |
|||
try (FileInputStream stream = new FileInputStream("/Users/xdclass/Desktop/dpan.sql");) { |
|||
byte[] bytes = IOUtils.toByteArray(stream); |
|||
ObjectMetadata objectMetadata = new ObjectMetadata(); |
|||
objectMetadata.setContentType("text/plain"); |
|||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); |
|||
// 上传
|
|||
amazonS3Client.putObject("ai-pan", "/meta/testIO.txt", byteArrayInputStream, objectMetadata); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取文件 |
|||
*/ |
|||
@Test |
|||
@SneakyThrows |
|||
public void testGetObject() { |
|||
try (FileOutputStream fileOutputStream = new FileOutputStream(new File("/Users/xdclass/Desktop/test5.txt"));) { |
|||
S3Object s3Object = amazonS3Client.getObject("ai-pan", "/meta/test5.txt"); |
|||
s3Object.getObjectContent().transferTo(fileOutputStream); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除文件 |
|||
*/ |
|||
@Test |
|||
public void testDeleteObject() { |
|||
amazonS3Client.deleteObject("ai-pan", "/meta/test5.txt"); |
|||
} |
|||
|
|||
/** |
|||
* 生成文件访问地址 |
|||
*/ |
|||
@Test |
|||
public void testGeneratePresignedUrl() { |
|||
// 预签名url过期时间(ms)
|
|||
long PRE_SIGN_URL_EXPIRE = 60 * 10 * 1000L; |
|||
// 计算预签名url的过期日期
|
|||
Date expireDate = DateUtil.offsetMillisecond(new Date(), (int) PRE_SIGN_URL_EXPIRE); |
|||
// 创建生成预签名url的请求,并设置过期时间和HTTP方法, withMethod是生成的URL访问方式
|
|||
GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest("avatar", "/2025/3/22/999ae223-cf98-4891-a7da-e3ab1d618719.jpg") |
|||
.withExpiration(expireDate).withMethod(HttpMethod.GET); |
|||
// 生成预签名url
|
|||
URL preSignedUrl = amazonS3Client.generatePresignedUrl(request); |
|||
// 输出预签名url
|
|||
System.out.println(preSignedUrl.toString()); |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,75 @@ |
|||
package org.ycloud.aipan.db; |
|||
|
|||
import com.baomidou.mybatisplus.annotation.IdType; |
|||
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
|||
import com.baomidou.mybatisplus.generator.FastAutoGenerator; |
|||
import com.baomidou.mybatisplus.generator.config.OutputFile; |
|||
import com.baomidou.mybatisplus.generator.config.TemplateType; |
|||
import com.baomidou.mybatisplus.generator.config.rules.DateType; |
|||
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; |
|||
import org.apache.ibatis.type.JdbcType; |
|||
|
|||
import java.util.Collections; |
|||
|
|||
|
|||
public class MyBatisPlusGenerator { |
|||
public static void main(String[] args) { |
|||
String userName = "root"; |
|||
String password = "Yuan625621105"; |
|||
String serverInfo = "47.98.137.138:3306"; |
|||
String targetModuleNamePath = "/"; |
|||
String dbName = "ycloud-aipan"; |
|||
|
|||
String[] tables = { |
|||
"account", "file","account_file","file_chunk", "file_suffix","file_type", "share", "share_file", "storage" |
|||
}; |
|||
// 使用 FastAutoGenerator 快速配置代码生成器
|
|||
FastAutoGenerator.create("jdbc:mysql://"+serverInfo+"/"+dbName+"?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&tinyInt1isBit=true", userName, password) |
|||
.globalConfig(builder -> { |
|||
builder.author("everyone") // 设置作者
|
|||
.commentDate("yyyy-MM-dd") |
|||
.enableSpringdoc() |
|||
.disableOpenDir() //禁止打开输出目录
|
|||
.dateType(DateType.ONLY_DATE) //定义生成的实体类中日期类型 DateType.ONLY_DATE 默认值: DateType.TIME_PACK
|
|||
.outputDir(System.getProperty("user.dir") + targetModuleNamePath + "/src/main/java"); // 指定输出目录
|
|||
}) |
|||
.packageConfig(builder -> { |
|||
builder.parent("org.ycloud.aipan") // 父包模块名
|
|||
.entity("model") //Entity 包名 默认值:entity
|
|||
.mapper("mapper") //Mapper 包名 默认值:mapper
|
|||
.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + targetModuleNamePath + "/src/main/resources/mapper")); // 设置mapperXml生成路,默认存放在mapper的xml下
|
|||
}) |
|||
.dataSourceConfig(builder -> {//Mysql下tinyint字段转换
|
|||
builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> { |
|||
if (JdbcType.TINYINT == metaInfo.getJdbcType()) { |
|||
return DbColumnType.BOOLEAN; |
|||
} |
|||
return typeRegistry.getColumnType(metaInfo); |
|||
}); |
|||
}) |
|||
.strategyConfig(builder -> { |
|||
builder.addInclude(tables) // 设置需要生成的表名 可变参数
|
|||
.entityBuilder()// Entity策略配置
|
|||
.enableFileOverride() // 开启生成Entity层文件覆盖
|
|||
.idType(IdType.ASSIGN_ID)//主键策略 雪花算法自动生成的id
|
|||
.enableLombok() //开启lombok
|
|||
.logicDeleteColumnName("del")// 说明逻辑删除是哪个字段
|
|||
.enableTableFieldAnnotation()// 属性加上注解说明
|
|||
.formatFileName("%sDO") //格式化生成的文件名称
|
|||
.controllerBuilder().disable()// Controller策略配置,这里不生成Controller层
|
|||
.serviceBuilder().disable()// Service策略配置,这里不生成Service层
|
|||
.mapperBuilder()// Mapper策略配置
|
|||
.enableFileOverride() // 开启生成Mapper层文件覆盖
|
|||
.formatMapperFileName("%sMapper")// 格式化Mapper文件名称
|
|||
.superClass(BaseMapper.class) //继承的父类
|
|||
.enableBaseResultMap() // 开启生成resultMap,
|
|||
.enableBaseColumnList() // 开启生成Sql片段
|
|||
.formatXmlFileName("%sMapper"); // 格式化xml文件名称
|
|||
}) |
|||
.templateConfig(builder -> { |
|||
// 不生成Controller
|
|||
builder.disable(TemplateType.CONTROLLER,TemplateType.SERVICE,TemplateType.SERVICE_IMPL); |
|||
}) |
|||
.execute(); // 执行生成
|
|||
} |
|||
} |
Loading…
Reference in new issue