Browse Source

feat: 首次提交

master
Administrator 3 months ago
commit
78a6dfa230
  1. 38
      .gitignore
  2. 3409
      README.md
  3. 256
      doc/ycloud-pan.sql
  4. 149
      pom.xml
  5. 36
      src/main/java/org/ycloud/aipan/CloudApplication.java
  6. 68
      src/main/java/org/ycloud/aipan/component/LocalFileStoreEngine.java
  7. 226
      src/main/java/org/ycloud/aipan/component/MinIOFileStoreEngine.java
  8. 68
      src/main/java/org/ycloud/aipan/component/OSSFileStoreEngine.java
  9. 110
      src/main/java/org/ycloud/aipan/component/StoreEngine.java
  10. 25
      src/main/java/org/ycloud/aipan/config/AccountConfig.java
  11. 56
      src/main/java/org/ycloud/aipan/config/AmazonS3Config.java
  12. 29
      src/main/java/org/ycloud/aipan/config/InterceptorConfig.java
  13. 38
      src/main/java/org/ycloud/aipan/config/Knife4jConfig.java
  14. 30
      src/main/java/org/ycloud/aipan/config/MinioConfig.java
  15. 66
      src/main/java/org/ycloud/aipan/controller/AccountController.java
  16. 97
      src/main/java/org/ycloud/aipan/controller/FileController.java
  17. 27
      src/main/java/org/ycloud/aipan/controller/req/AccountLoginReq.java
  18. 35
      src/main/java/org/ycloud/aipan/controller/req/AccountRegisterReq.java
  19. 20
      src/main/java/org/ycloud/aipan/controller/req/FileBatchReq.java
  20. 27
      src/main/java/org/ycloud/aipan/controller/req/FileUpdateReq.java
  21. 30
      src/main/java/org/ycloud/aipan/controller/req/FileUploadReq.java
  22. 33
      src/main/java/org/ycloud/aipan/controller/req/FolderCreateReq.java
  23. 52
      src/main/java/org/ycloud/aipan/dto/AccountDTO.java
  24. 60
      src/main/java/org/ycloud/aipan/dto/AccountFileDTO.java
  25. 38
      src/main/java/org/ycloud/aipan/dto/FolderTreeNodeDTO.java
  26. 37
      src/main/java/org/ycloud/aipan/dto/StorageDTO.java
  27. 15
      src/main/java/org/ycloud/aipan/enums/AccountRoleEnum.java
  28. 38
      src/main/java/org/ycloud/aipan/enums/BizCodeEnum.java
  29. 110
      src/main/java/org/ycloud/aipan/enums/FileTypeEnum.java
  30. 22
      src/main/java/org/ycloud/aipan/enums/FolderFlagEnum.java
  31. 71
      src/main/java/org/ycloud/aipan/exception/BizException.java
  32. 40
      src/main/java/org/ycloud/aipan/exception/CustomExceptionHandler.java
  33. 72
      src/main/java/org/ycloud/aipan/interceptor/LoginInterceptor.java
  34. 16
      src/main/java/org/ycloud/aipan/mapper/AccountFileMapper.java
  35. 16
      src/main/java/org/ycloud/aipan/mapper/AccountMapper.java
  36. 16
      src/main/java/org/ycloud/aipan/mapper/FileChunkMapper.java
  37. 16
      src/main/java/org/ycloud/aipan/mapper/FileMapper.java
  38. 16
      src/main/java/org/ycloud/aipan/mapper/FileSuffixMapper.java
  39. 16
      src/main/java/org/ycloud/aipan/mapper/FileTypeMapper.java
  40. 16
      src/main/java/org/ycloud/aipan/mapper/ShareFileMapper.java
  41. 16
      src/main/java/org/ycloud/aipan/mapper/ShareMapper.java
  42. 16
      src/main/java/org/ycloud/aipan/mapper/StorageMapper.java
  43. 66
      src/main/java/org/ycloud/aipan/model/AccountDO.java
  44. 82
      src/main/java/org/ycloud/aipan/model/AccountFileDO.java
  45. 73
      src/main/java/org/ycloud/aipan/model/FileChunkDO.java
  46. 70
      src/main/java/org/ycloud/aipan/model/FileDO.java
  47. 38
      src/main/java/org/ycloud/aipan/model/FileSuffixDO.java
  48. 35
      src/main/java/org/ycloud/aipan/model/FileTypeDO.java
  49. 75
      src/main/java/org/ycloud/aipan/model/ShareDO.java
  50. 52
      src/main/java/org/ycloud/aipan/model/ShareFileDO.java
  51. 51
      src/main/java/org/ycloud/aipan/model/StorageDO.java
  52. 51
      src/main/java/org/ycloud/aipan/service/AccountFileService.java
  53. 24
      src/main/java/org/ycloud/aipan/service/AccountService.java
  54. 9
      src/main/java/org/ycloud/aipan/service/FileService.java
  55. 389
      src/main/java/org/ycloud/aipan/service/impl/AccountFileServiceImpl.java
  56. 124
      src/main/java/org/ycloud/aipan/service/impl/AccountServiceImpl.java
  57. 13
      src/main/java/org/ycloud/aipan/service/impl/FileServiceImpl.java
  58. 61
      src/main/java/org/ycloud/aipan/util/CommonUtil.java
  59. 102
      src/main/java/org/ycloud/aipan/util/JsonData.java
  60. 99
      src/main/java/org/ycloud/aipan/util/JsonUtil.java
  61. 121
      src/main/java/org/ycloud/aipan/util/JwtUtil.java
  62. 59
      src/main/java/org/ycloud/aipan/util/SpringBeanUtil.java
  63. 47
      src/main/resources/application.yml
  64. 27
      src/main/resources/mapper/AccountFileMapper.xml
  65. 23
      src/main/resources/mapper/AccountMapper.xml
  66. 26
      src/main/resources/mapper/FileChunkMapper.xml
  67. 24
      src/main/resources/mapper/FileMapper.xml
  68. 17
      src/main/resources/mapper/FileSuffixMapper.xml
  69. 16
      src/main/resources/mapper/FileTypeMapper.xml
  70. 20
      src/main/resources/mapper/ShareFileMapper.xml
  71. 26
      src/main/resources/mapper/ShareMapper.xml
  72. 20
      src/main/resources/mapper/StorageMapper.xml
  73. 0
      src/main/resources/static/favicon.ico
  74. 37
      src/test/java/org/ycloud/aipan/AccountTest.java
  75. 183
      src/test/java/org/ycloud/aipan/AmazonS3ClientTests.java
  76. 75
      src/test/java/org/ycloud/aipan/db/MyBatisPlusGenerator.java

38
.gitignore

@ -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

3409
README.md

File diff suppressed because it is too large

256
doc/ycloud-pan.sql

@ -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;

149
pom.xml

@ -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>

36
src/main/java/org/ycloud/aipan/CloudApplication.java

@ -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"));
}
}

68
src/main/java/org/ycloud/aipan/component/LocalFileStoreEngine.java

@ -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) {
}
}

226
src/main/java/org/ycloud/aipan/component/MinIOFileStoreEngine.java

@ -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);
}
}
}

68
src/main/java/org/ycloud/aipan/component/OSSFileStoreEngine.java

@ -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) {
}
}

110
src/main/java/org/ycloud/aipan/component/StoreEngine.java

@ -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);
}

25
src/main/java/org/ycloud/aipan/config/AccountConfig.java

@ -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;
}

56
src/main/java/org/ycloud/aipan/config/AmazonS3Config.java

@ -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();
}
}

29
src/main/java/org/ycloud/aipan/config/InterceptorConfig.java

@ -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");
}
}

38
src/main/java/org/ycloud/aipan/config/Knife4jConfig.java

@ -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") // 替换为作者的网站或个人资料链接
)
);
}
}

30
src/main/java/org/ycloud/aipan/config/MinioConfig.java

@ -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;
}

66
src/main/java/org/ycloud/aipan/controller/AccountController.java

@ -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);
}
}

97
src/main/java/org/ycloud/aipan/controller/FileController.java

@ -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();
}
}

27
src/main/java/org/ycloud/aipan/controller/req/AccountLoginReq.java

@ -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;
}

35
src/main/java/org/ycloud/aipan/controller/req/AccountRegisterReq.java

@ -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;
}

20
src/main/java/org/ycloud/aipan/controller/req/FileBatchReq.java

@ -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;
}

27
src/main/java/org/ycloud/aipan/controller/req/FileUpdateReq.java

@ -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;
}

30
src/main/java/org/ycloud/aipan/controller/req/FileUploadReq.java

@ -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;
}

33
src/main/java/org/ycloud/aipan/controller/req/FolderCreateReq.java

@ -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;
}

52
src/main/java/org/ycloud/aipan/dto/AccountDTO.java

@ -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;
}

60
src/main/java/org/ycloud/aipan/dto/AccountFileDTO.java

@ -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;
}

38
src/main/java/org/ycloud/aipan/dto/FolderTreeNodeDTO.java

@ -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<>();
}

37
src/main/java/org/ycloud/aipan/dto/StorageDTO.java

@ -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;
}

15
src/main/java/org/ycloud/aipan/enums/AccountRoleEnum.java

@ -0,0 +1,15 @@
package org.ycloud.aipan.enums;
public enum AccountRoleEnum {
/**
* 普通用户
*/
COMMON,
/**
* 管理员
*/
ADMIN;
}

38
src/main/java/org/ycloud/aipan/enums/BizCodeEnum.java

@ -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;
}

110
src/main/java/org/ycloud/aipan/enums/FileTypeEnum.java

@ -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]+");
}
}

22
src/main/java/org/ycloud/aipan/enums/FolderFlagEnum.java

@ -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;
}

71
src/main/java/org/ycloud/aipan/exception/BizException.java

@ -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();
}
}

40
src/main/java/org/ycloud/aipan/exception/CustomExceptionHandler.java

@ -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("系统异常");
}
}
}

72
src/main/java/org/ycloud/aipan/interceptor/LoginInterceptor.java

@ -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();
}
}

16
src/main/java/org/ycloud/aipan/mapper/AccountFileMapper.java

@ -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> {
}

16
src/main/java/org/ycloud/aipan/mapper/AccountMapper.java

@ -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> {
}

16
src/main/java/org/ycloud/aipan/mapper/FileChunkMapper.java

@ -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> {
}

16
src/main/java/org/ycloud/aipan/mapper/FileMapper.java

@ -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> {
}

16
src/main/java/org/ycloud/aipan/mapper/FileSuffixMapper.java

@ -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> {
}

16
src/main/java/org/ycloud/aipan/mapper/FileTypeMapper.java

@ -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> {
}

16
src/main/java/org/ycloud/aipan/mapper/ShareFileMapper.java

@ -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> {
}

16
src/main/java/org/ycloud/aipan/mapper/ShareMapper.java

@ -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> {
}

16
src/main/java/org/ycloud/aipan/mapper/StorageMapper.java

@ -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> {
}

66
src/main/java/org/ycloud/aipan/model/AccountDO.java

@ -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;
}

82
src/main/java/org/ycloud/aipan/model/AccountFileDO.java

@ -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;
}

73
src/main/java/org/ycloud/aipan/model/FileChunkDO.java

@ -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;
}

70
src/main/java/org/ycloud/aipan/model/FileDO.java

@ -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;
}

38
src/main/java/org/ycloud/aipan/model/FileSuffixDO.java

@ -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;
}

35
src/main/java/org/ycloud/aipan/model/FileTypeDO.java

@ -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;
}

75
src/main/java/org/ycloud/aipan/model/ShareDO.java

@ -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;
}

52
src/main/java/org/ycloud/aipan/model/ShareFileDO.java

@ -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;
}

51
src/main/java/org/ycloud/aipan/model/StorageDO.java

@ -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;
}

51
src/main/java/org/ycloud/aipan/service/AccountFileService.java

@ -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);
}

24
src/main/java/org/ycloud/aipan/service/AccountService.java

@ -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);
}

9
src/main/java/org/ycloud/aipan/service/FileService.java

@ -0,0 +1,9 @@
package org.ycloud.aipan.service;
import org.ycloud.aipan.dto.AccountFileDTO;
import java.util.List;
public interface FileService {
}

389
src/main/java/org/ycloud/aipan/service/impl/AccountFileServiceImpl.java

@ -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]);
}
}
}
}

124
src/main/java/org/ycloud/aipan/service/impl/AccountServiceImpl.java

@ -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;
}
}

13
src/main/java/org/ycloud/aipan/service/impl/FileServiceImpl.java

@ -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 {
}

61
src/main/java/org/ycloud/aipan/util/CommonUtil.java

@ -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);
}
}

102
src/main/java/org/ycloud/aipan/util/JsonData.java

@ -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;
}
}

99
src/main/java/org/ycloud/aipan/util/JsonUtil.java

@ -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);
}
}

121
src/main/java/org/ycloud/aipan/util/JwtUtil.java

@ -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;
}
}

59
src/main/java/org/ycloud/aipan/util/SpringBeanUtil.java

@ -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);
}
}

47
src/main/resources/application.yml

@ -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

27
src/main/resources/mapper/AccountFileMapper.xml

@ -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>

23
src/main/resources/mapper/AccountMapper.xml

@ -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>

26
src/main/resources/mapper/FileChunkMapper.xml

@ -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>

24
src/main/resources/mapper/FileMapper.xml

@ -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>

17
src/main/resources/mapper/FileSuffixMapper.xml

@ -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>

16
src/main/resources/mapper/FileTypeMapper.xml

@ -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>

20
src/main/resources/mapper/ShareFileMapper.xml

@ -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>

26
src/main/resources/mapper/ShareMapper.xml

@ -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>

20
src/main/resources/mapper/StorageMapper.xml

@ -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
src/main/resources/static/favicon.ico

37
src/test/java/org/ycloud/aipan/AccountTest.java

@ -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);
}
}

183
src/test/java/org/ycloud/aipan/AmazonS3ClientTests.java

@ -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());
}
}

75
src/test/java/org/ycloud/aipan/db/MyBatisPlusGenerator.java

@ -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…
Cancel
Save