Browse Source

feat: 分享

master
Administrator 2 months ago
parent
commit
5c2d35750c
  1. 14
      src/main/java/org/ycloud/aipan/annotation/ShareCodeCheck.java
  2. 85
      src/main/java/org/ycloud/aipan/aspect/ShareCodeAspect.java
  3. 4
      src/main/java/org/ycloud/aipan/config/AccountConfig.java
  4. 119
      src/main/java/org/ycloud/aipan/controller/ShareController.java
  5. 14
      src/main/java/org/ycloud/aipan/controller/req/ShareCancelReq.java
  6. 11
      src/main/java/org/ycloud/aipan/controller/req/ShareCheckReq.java
  7. 42
      src/main/java/org/ycloud/aipan/controller/req/ShareCreateReq.java
  8. 13
      src/main/java/org/ycloud/aipan/controller/req/ShareFileQueryReq.java
  9. 24
      src/main/java/org/ycloud/aipan/controller/req/ShareFileTransferReq.java
  10. 15
      src/main/java/org/ycloud/aipan/dto/ShareAccountDTO.java
  11. 50
      src/main/java/org/ycloud/aipan/dto/ShareDTO.java
  12. 53
      src/main/java/org/ycloud/aipan/dto/ShareDetailDTO.java
  13. 51
      src/main/java/org/ycloud/aipan/dto/ShareSimpleDTO.java
  14. 2
      src/main/java/org/ycloud/aipan/enums/BizCodeEnum.java
  15. 39
      src/main/java/org/ycloud/aipan/enums/ShareDayTypeEnum.java
  16. 22
      src/main/java/org/ycloud/aipan/enums/ShareStatusEnum.java
  17. 18
      src/main/java/org/ycloud/aipan/enums/ShareTypeEnum.java
  18. 4
      src/main/java/org/ycloud/aipan/mapper/ShareFileMapper.java
  19. 3
      src/main/java/org/ycloud/aipan/model/ShareDO.java
  20. 3
      src/main/java/org/ycloud/aipan/model/ShareFileDO.java
  21. 2
      src/main/java/org/ycloud/aipan/model/StorageDO.java
  22. 29
      src/main/java/org/ycloud/aipan/service/AccountFileService.java
  23. 29
      src/main/java/org/ycloud/aipan/service/ShareService.java
  24. 6
      src/main/java/org/ycloud/aipan/service/impl/AccountFileServiceImpl.java
  25. 390
      src/main/java/org/ycloud/aipan/service/impl/ShareServiceImpl.java
  26. 55
      src/main/java/org/ycloud/aipan/util/JwtUtil.java
  27. 6
      src/main/resources/mapper/ShareFileMapper.xml

14
src/main/java/org/ycloud/aipan/annotation/ShareCodeCheck.java

@ -0,0 +1,14 @@
package org.ycloud.aipan.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Retention(RUNTIME)
@Target({ElementType.METHOD})
public @interface ShareCodeCheck {
}

85
src/main/java/org/ycloud/aipan/aspect/ShareCodeAspect.java

@ -0,0 +1,85 @@
package org.ycloud.aipan.aspect;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.ycloud.aipan.annotation.ShareCodeCheck;
import org.ycloud.aipan.enums.BizCodeEnum;
import org.ycloud.aipan.exception.BizException;
import org.ycloud.aipan.util.JsonData;
import org.ycloud.aipan.util.JwtUtil;
@Aspect
@Component
@Slf4j
public class ShareCodeAspect {
private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 设置当前线程共享shareID
* @param shareId
*/
public static void set(Long shareId){
threadLocal.set(shareId);
}
/**
* 获取当前线程绑定的shareID
* @return
*/
public static Long get(){
return threadLocal.get();
}
/**
* 定义 @Pointcut注解表达式
* 方式一@annotation当执行的方法上拥有指定的注解时生效我们采用这
* 方式二execution一般用于指定方法的执行
*/
@Pointcut("@annotation(shareCodeCheck)")
public void pointCutShareCodeCheck(ShareCodeCheck shareCodeCheck){
}
/**
* 环绕通知, 围绕着方法执行
* @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务
* 方式一单用 @Around("execution(* net.xdclass.controller.*.*(..))")可以
* 方式二@Pointcut和@Around联合注解也可以我们采用这个
*/
@Around("pointCutShareCodeCheck(shareCodeCheck)")
public Object around(ProceedingJoinPoint joinPoint, ShareCodeCheck shareCodeCheck) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
String shareToken = request.getHeader("share-token");
if(StringUtils.isBlank(shareToken)){
throw new BizException(BizCodeEnum.SHARE_CODE_ILLEGAL);
}
Claims claims = JwtUtil.checkShareJWT(shareToken);
if(claims == null){
log.error("share-token 解析失败");
return JsonData.buildResult(BizCodeEnum.SHARE_CODE_ILLEGAL);
}
Long shareId = Long.valueOf(claims.get(JwtUtil.CLAIM_SHARE_KEY)+"");
set(shareId);
log.info("环绕通知执行前");
Object obj = joinPoint.proceed();
log.info("环绕通知执行后");
return obj;
}
}

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

@ -22,4 +22,8 @@ public class AccountConfig {
* 根文件夹的父ID * 根文件夹的父ID
*/ */
public static final Long ROOT_PARENT_ID = 0L; public static final Long ROOT_PARENT_ID = 0L;
/**
* 网盘前端地址
*/
public static final String PAN_FRONT_DOMAIN_SHARE_API = "127.0.0.1:9999/share/";
} }

119
src/main/java/org/ycloud/aipan/controller/ShareController.java

@ -0,0 +1,119 @@
package org.ycloud.aipan.controller;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.ycloud.aipan.annotation.ShareCodeCheck;
import org.ycloud.aipan.aspect.ShareCodeAspect;
import org.ycloud.aipan.controller.req.*;
import org.ycloud.aipan.dto.AccountFileDTO;
import org.ycloud.aipan.dto.ShareDTO;
import org.ycloud.aipan.dto.ShareDetailDTO;
import org.ycloud.aipan.dto.ShareSimpleDTO;
import org.ycloud.aipan.enums.BizCodeEnum;
import org.ycloud.aipan.interceptor.LoginInterceptor;
import org.ycloud.aipan.service.ShareService;
import org.ycloud.aipan.util.JsonData;
import java.util.List;
@RestController
@RequestMapping("/api/share/v1")
public class ShareController {
@Autowired
private ShareService shareService;
/**
* 获取我的个人分享列表接口
*/
@GetMapping("list")
public JsonData list(){
List<ShareDTO> list = shareService.listShare();
return JsonData.buildSuccess(list);
}
/**
* 创建分享链接
*/
@PostMapping("create")
public JsonData create(@RequestBody ShareCreateReq req){
req.setAccountId(LoginInterceptor.threadLocal.get().getId());
ShareDTO shareDTO = shareService.createShare(req);
return JsonData.buildSuccess(shareDTO);
}
/**
* 取消分享
*/
@PostMapping("cancel")
public JsonData cancel(@RequestBody ShareCancelReq req){
req.setAccountId(LoginInterceptor.threadLocal.get().getId());
shareService.cancelShare(req);
return JsonData.buildSuccess();
}
/**
* 访问分享接口,返回基本的分享信息
* 情况一如果链接不需要校验码则一并返回token
* 情况二如果链接需要校验码则返回校验码调用对应接口校验码再返回token
*/
@GetMapping("visit")
public JsonData visit(@RequestParam(value = "shareId") Long shareId){
ShareSimpleDTO shareSimpleDTO = shareService.simpleDetail(shareId);
return JsonData.buildSuccess(shareSimpleDTO);
}
/**
* 校验分享码返回临时token
*/
@PostMapping("check_share_code")
public JsonData checkShareCode(@RequestBody ShareCheckReq req){
String shareToken = shareService.checkShareCode(req);
if(shareToken == null){
return JsonData.buildResult(BizCodeEnum.SHARE_NOT_EXIST);
}
return JsonData.buildSuccess(shareToken);
}
/**
* 查看分享详情接口
*/
@GetMapping("detail")
@ShareCodeCheck
public JsonData detail(){
ShareDetailDTO shareDetailDTO = shareService.detail(ShareCodeAspect.get());
return JsonData.buildSuccess(shareDetailDTO);
}
/**
* 查看某个分享文件夹下的文件列表
*/
@PostMapping("list_share_file")
@ShareCodeCheck
public JsonData listShareFile(@RequestBody ShareFileQueryReq req){
req.setShareId(ShareCodeAspect.get());
List<AccountFileDTO> list = shareService.listShareFile(req);
return JsonData.buildSuccess(list);
}
/**
* 文件转存
*/
@PostMapping("transfer")
@ShareCodeCheck
public JsonData transfer(@RequestBody ShareFileTransferReq req){
req.setShareId(ShareCodeAspect.get());
req.setAccountId(LoginInterceptor.threadLocal.get().getId());
shareService.transferShareFile(req);
return JsonData.buildSuccess();
}
}

14
src/main/java/org/ycloud/aipan/controller/req/ShareCancelReq.java

@ -0,0 +1,14 @@
package org.ycloud.aipan.controller.req;
import lombok.Data;
import java.util.List;
@Data
public class ShareCancelReq {
private List<Long> shareIds;
private Long accountId;
}

11
src/main/java/org/ycloud/aipan/controller/req/ShareCheckReq.java

@ -0,0 +1,11 @@
package org.ycloud.aipan.controller.req;
import lombok.Data;
@Data
public class ShareCheckReq {
private Long shareId;
private String shareCode;
}

42
src/main/java/org/ycloud/aipan/controller/req/ShareCreateReq.java

@ -0,0 +1,42 @@
package org.ycloud.aipan.controller.req;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ShareCreateReq {
/**
* 分享名称
*/
private String shareName;
/**
* 分享类型是否需要提取码
*/
private String shareType;
/**
* 分享有效天数0永久1-7天2-30天
*/
private Integer shareDayType;
/**
* 文件id列表
*/
private List<Long> fileIds;
/**
* 分享人id
*/
private Long accountId;
}

13
src/main/java/org/ycloud/aipan/controller/req/ShareFileQueryReq.java

@ -0,0 +1,13 @@
package org.ycloud.aipan.controller.req;
import lombok.Data;
@Data
public class ShareFileQueryReq {
private Long shareId;
/**
* 进入的目标文件夹ID
*/
private Long parentId;
}

24
src/main/java/org/ycloud/aipan/controller/req/ShareFileTransferReq.java

@ -0,0 +1,24 @@
package org.ycloud.aipan.controller.req;
import lombok.Data;
import java.util.List;
@Data
public class ShareFileTransferReq {
private Long shareId;
private Long accountId;
/**
* 目标文件夹ID
*/
private Long parentId;
/**
* 转存的文件ID
*/
private List<Long> fileIds;
}

15
src/main/java/org/ycloud/aipan/dto/ShareAccountDTO.java

@ -0,0 +1,15 @@
package org.ycloud.aipan.dto;
import lombok.Data;
@Data
public class ShareAccountDTO {
private Long id;
private String username;
private String avatarUrl;
}

50
src/main/java/org/ycloud/aipan/dto/ShareDTO.java

@ -0,0 +1,50 @@
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 = "ShareDTO", description = "用户分享对象")
public class ShareDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "分享id")
private Long id;
@Schema(description = "分享名称")
private String shareName;
@Schema(description = "分享类型(no_code没有提取码 ,need_code有提取码)")
private String shareType;
@Schema(description = "分享类型(0 永久有效;1: 7天有效;2: 30天有效)")
private Integer shareDayType;
@Schema(description = "分享有效天数(永久有效为0)")
private Integer shareDay;
@Schema(description = "分享结束时间")
private Date shareEndTime;
@Schema(description = "分享链接地址")
private String shareUrl;
@Schema(description = "分享提取码")
private String shareCode;
@Schema(description = "分享状态 used正常, expired已失效, cancled取消")
private String shareStatus;
@Schema(description = "分享创建人")
private Long accountId;
@Schema(description = "创建时间")
private Date gmtCreate;
}

53
src/main/java/org/ycloud/aipan/dto/ShareDetailDTO.java

@ -0,0 +1,53 @@
package org.ycloud.aipan.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
@Getter
@Setter
@Schema(name = "ShareDetailDTO", description = "分享链接详情对象")
@Accessors(chain = true)
public class ShareDetailDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "分享id")
private Long id;
@Schema(description = "分享名称")
private String shareName;
@Schema(description = "分享类型(no_code没有提取码 ,need_code有提取码)")
private String shareType;
@Schema(description = "分享类型(0 永久有效;1: 7天有效;2: 30天有效)")
private Integer shareDayType;
@Schema(description = "分享有效天数(永久有效为0)")
private Integer shareDay;
@Schema(description = "分享结束时间")
private Date shareEndTime;
@Schema(description = "分享链接地址")
private String shareUrl;
/**
* 分享者信息
*/
private ShareAccountDTO shareAccountDTO;
/**
* 分享的文件信息
*/
private List<AccountFileDTO> fileDTOList;
}

51
src/main/java/org/ycloud/aipan/dto/ShareSimpleDTO.java

@ -0,0 +1,51 @@
package org.ycloud.aipan.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
@Getter
@Setter
@Schema(name = "ShareSimpleDTO", description = "分享链接简单对象")
@Accessors(chain = true)
public class ShareSimpleDTO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "分享id")
private Long id;
@Schema(description = "分享名称")
private String shareName;
@Schema(description = "分享类型(no_code没有提取码 ,need_code有提取码)")
private String shareType;
@Schema(description = "分享类型(0 永久有效;1: 7天有效;2: 30天有效)")
private Integer shareDayType;
@Schema(description = "分享有效天数(永久有效为0)")
private Integer shareDay;
@Schema(description = "分享结束时间")
private Date shareEndTime;
@Schema(description = "分享链接地址")
private String shareUrl;
/**
* 分享者信息
*/
private ShareAccountDTO shareAccountDTO;
/**
* 分享令牌不需要校验码的时候才会有这个
*/
private String shareToken;
}

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

@ -34,7 +34,7 @@ public enum BizCodeEnum {
FILE_BATCH_UPDATE_ERROR(270101,"文件批量操作错误" ), FILE_BATCH_UPDATE_ERROR(270101,"文件批量操作错误" ),
FILE_DIR_NOT_EXIST(270102,"用户的上级目录不存在" ), FILE_DIR_NOT_EXIST(270102,"用户的上级目录不存在" ),
FILE_DIR_ERROR(270103,"用户的上级目录错误" ), FILE_DIR_ERROR(270103,"用户的上级目录错误" ),
SHARE_CANCELED( 260406, "分享已取消"),
; ;
private final int code; private final int code;
private final String message; private final String message;

39
src/main/java/org/ycloud/aipan/enums/ShareDayTypeEnum.java

@ -0,0 +1,39 @@
package org.ycloud.aipan.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 分享链接的日期类型
*/
@Getter
@AllArgsConstructor
public enum ShareDayTypeEnum {
PERMANENT(0,0),
SEVEN_DAYS(1,7),
THIRTY_DAYS(2,30);
private Integer dayType;
private Integer days;
/**
* 根据类型获取对应的天数
*/
public static Integer getDaysByType(Integer dayType){
for (ShareDayTypeEnum value : ShareDayTypeEnum.values()) {
if(value.getDayType().equals(dayType)){
return value.getDays();
}
}
//默认是7天有效
return SEVEN_DAYS.days;
}
}

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

@ -0,0 +1,22 @@
package org.ycloud.aipan.enums;
/**
* 分享状态
*/
public enum ShareStatusEnum {
/**
* 已使用
*/
USED,
/**
* 已过期
*/
EXPIRED,
/**
* 已取消
*/
CANCELED;
}

18
src/main/java/org/ycloud/aipan/enums/ShareTypeEnum.java

@ -0,0 +1,18 @@
package org.ycloud.aipan.enums;
import lombok.Getter;
@Getter
public enum ShareTypeEnum {
/**
* 无码
*/
NO_CODE,
/**
* 需要码
*/
NEED_CODE;
}

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

@ -3,6 +3,8 @@ package org.ycloud.aipan.mapper;
import org.ycloud.aipan.model.ShareFileDO; import org.ycloud.aipan.model.ShareFileDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/** /**
* <p> * <p>
* 文件分享表 Mapper 接口 * 文件分享表 Mapper 接口
@ -12,5 +14,5 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
* @since 2025-02-12 * @since 2025-02-12
*/ */
public interface ShareFileMapper extends BaseMapper<ShareFileDO> { public interface ShareFileMapper extends BaseMapper<ShareFileDO> {
void insertBatch(List<ShareFileDO> shareFileDOS);
} }

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

@ -7,8 +7,10 @@ import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.Accessors;
/** /**
* <p> * <p>
@ -22,6 +24,7 @@ import lombok.Setter;
@Setter @Setter
@TableName("share") @TableName("share")
@Schema(name = "ShareDO", description = "用户分享表") @Schema(name = "ShareDO", description = "用户分享表")
@Builder
public class ShareDO implements Serializable { public class ShareDO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

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

@ -7,8 +7,10 @@ import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable; import java.io.Serializable;
import java.util.Date; import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.Accessors;
/** /**
* <p> * <p>
@ -22,6 +24,7 @@ import lombok.Setter;
@Setter @Setter
@TableName("share_file") @TableName("share_file")
@Schema(name = "ShareFileDO", description = "文件分享表") @Schema(name = "ShareFileDO", description = "文件分享表")
@Builder
public class ShareFileDO implements Serializable { public class ShareFileDO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

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

@ -9,6 +9,7 @@ import java.util.Date;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.experimental.Accessors;
/** /**
* <p> * <p>
@ -22,6 +23,7 @@ import lombok.Setter;
@Setter @Setter
@TableName("storage") @TableName("storage")
@Schema(name = "StorageDO", description = "存储信息表") @Schema(name = "StorageDO", description = "存储信息表")
@Accessors(chain = true)
public class StorageDO implements Serializable { public class StorageDO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;

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

@ -4,6 +4,7 @@ package org.ycloud.aipan.service;
import org.ycloud.aipan.controller.req.*; import org.ycloud.aipan.controller.req.*;
import org.ycloud.aipan.dto.AccountFileDTO; import org.ycloud.aipan.dto.AccountFileDTO;
import org.ycloud.aipan.dto.FolderTreeNodeDTO; import org.ycloud.aipan.dto.FolderTreeNodeDTO;
import org.ycloud.aipan.model.AccountFileDO;
import java.util.List; import java.util.List;
@ -12,6 +13,7 @@ public interface AccountFileService {
* 获取文件列表 * 获取文件列表
*/ */
List<AccountFileDTO> listFile(Long accountId, Long parentId); List<AccountFileDTO> listFile(Long accountId, Long parentId);
/** /**
* 创建文件夹 * 创建文件夹
*/ */
@ -22,9 +24,10 @@ public interface AccountFileService {
* 1检查ID是否存在 * 1检查ID是否存在
* 2新旧文件名称不能一样 * 2新旧文件名称不能一样
* 3同层文件名称不能一样 * 3同层文件名称不能一样
*
* @param req * @param req
*/ */
void renameFile(FileUpdateReq req); void renameFile(FileUpdateReq req);
/** /**
* 查询文件树接口 非递归方式 * 查询文件树接口 非递归方式
@ -73,6 +76,28 @@ public interface AccountFileService {
*/ */
Boolean secondUpload(FileSecondUploadReq req); Boolean secondUpload(FileSecondUploadReq req);
/**
* 保存文件和账号文件的关系到数据库
*/
void saveFileAndAccountFile(FileUploadReq req, String storeFileObjectKey); void saveFileAndAccountFile(FileUploadReq req, String storeFileObjectKey);
/**
* 检查被移动的文件ID是否合法
*/
List<AccountFileDO> checkFileIdLegal(List<Long> fileIds, Long accountId);
/**
* 包括递归处理生成新的ID
*/
List<AccountFileDO> findBatchCopyFileWithRecur(List<AccountFileDO> accountFileDOList, Long targetParentId);
/**
* 检查存储空间和更新存储空间
*/
boolean checkAndUpdateCapacity(Long accountId, Long fileSize);
/**
* 递归查找
*/
void findAllAccountFileDOWithRecur(List<AccountFileDO> allAccountFileDOList, List<AccountFileDO> prepareAccountFileDOList, boolean onlyFolder);
} }

29
src/main/java/org/ycloud/aipan/service/ShareService.java

@ -0,0 +1,29 @@
package org.ycloud.aipan.service;
import org.ycloud.aipan.controller.req.*;
import org.ycloud.aipan.dto.AccountFileDTO;
import org.ycloud.aipan.dto.ShareDTO;
import org.ycloud.aipan.dto.ShareDetailDTO;
import org.ycloud.aipan.dto.ShareSimpleDTO;
import java.util.List;
public interface ShareService {
List<ShareDTO> listShare();
ShareDTO createShare(ShareCreateReq req);
void cancelShare(ShareCancelReq req);
ShareSimpleDTO simpleDetail(Long shareId);
String checkShareCode(ShareCheckReq req);
ShareDetailDTO detail(Long shareId);
List<AccountFileDTO> listShareFile(ShareFileQueryReq req);
void transferShareFile(ShareFileTransferReq req);
}

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

@ -363,7 +363,8 @@ public class AccountFileServiceImpl implements AccountFileService {
* @param accountId * @param accountId
* @return * @return
*/ */
private List<AccountFileDO> checkFileIdLegal(List<Long> fileIds, Long accountId) { @Override
public List<AccountFileDO> checkFileIdLegal(List<Long> fileIds, Long accountId) {
List<AccountFileDO> accountFileDOList = accountFileMapper List<AccountFileDO> accountFileDOList = accountFileMapper
.selectList(new QueryWrapper<AccountFileDO>().in("id", fileIds).eq("account_id", accountId)); .selectList(new QueryWrapper<AccountFileDO>().in("id", fileIds).eq("account_id", accountId));
@ -500,6 +501,7 @@ public class AccountFileServiceImpl implements AccountFileService {
* @param prepareAccountFileDOList 待查询的文件和文件夹 * @param prepareAccountFileDOList 待查询的文件和文件夹
* @param onlyFolder 控制是否只存储文件 * @param onlyFolder 控制是否只存储文件
*/ */
@Override
public void findAllAccountFileDOWithRecur(List<AccountFileDO> allAccountFileDOList, List<AccountFileDO> prepareAccountFileDOList, boolean onlyFolder) { public void findAllAccountFileDOWithRecur(List<AccountFileDO> allAccountFileDOList, List<AccountFileDO> prepareAccountFileDOList, boolean onlyFolder) {
for (AccountFileDO accountFileDO : prepareAccountFileDOList) { for (AccountFileDO accountFileDO : prepareAccountFileDOList) {
@ -523,6 +525,7 @@ public class AccountFileServiceImpl implements AccountFileService {
* @param targetParentId * @param targetParentId
* @return * @return
*/ */
@Override
public List<AccountFileDO> findBatchCopyFileWithRecur(List<AccountFileDO> accountFileDOList, Long targetParentId) { public List<AccountFileDO> findBatchCopyFileWithRecur(List<AccountFileDO> accountFileDOList, Long targetParentId) {
List<AccountFileDO> newAccountFileDOList = new ArrayList<>(); List<AccountFileDO> newAccountFileDOList = new ArrayList<>();
accountFileDOList.forEach(accountFileDO -> doCopyChildRecord(newAccountFileDOList, accountFileDO, targetParentId)); accountFileDOList.forEach(accountFileDO -> doCopyChildRecord(newAccountFileDOList, accountFileDO, targetParentId));
@ -584,6 +587,7 @@ public class AccountFileServiceImpl implements AccountFileService {
* @param fileSize * @param fileSize
* @return * @return
*/ */
@Override
public boolean checkAndUpdateCapacity(Long accountId, Long fileSize) { public boolean checkAndUpdateCapacity(Long accountId, Long fileSize) {
StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", accountId)); StorageDO storageDO = storageMapper.selectOne(new QueryWrapper<StorageDO>().eq("account_id", accountId));
Long totalSize = storageDO.getTotalSize(); Long totalSize = storageDO.getTotalSize();

390
src/main/java/org/ycloud/aipan/service/impl/ShareServiceImpl.java

@ -0,0 +1,390 @@
package org.ycloud.aipan.service.impl;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.ycloud.aipan.config.AccountConfig;
import org.ycloud.aipan.controller.req.*;
import org.ycloud.aipan.dto.*;
import org.ycloud.aipan.enums.BizCodeEnum;
import org.ycloud.aipan.enums.ShareDayTypeEnum;
import org.ycloud.aipan.enums.ShareStatusEnum;
import org.ycloud.aipan.enums.ShareTypeEnum;
import org.ycloud.aipan.exception.BizException;
import org.ycloud.aipan.interceptor.LoginInterceptor;
import org.ycloud.aipan.mapper.AccountFileMapper;
import org.ycloud.aipan.mapper.AccountMapper;
import org.ycloud.aipan.mapper.ShareFileMapper;
import org.ycloud.aipan.mapper.ShareMapper;
import org.ycloud.aipan.model.AccountDO;
import org.ycloud.aipan.model.AccountFileDO;
import org.ycloud.aipan.model.ShareDO;
import org.ycloud.aipan.model.ShareFileDO;
import org.ycloud.aipan.service.AccountFileService;
import org.ycloud.aipan.service.ShareService;
import org.ycloud.aipan.util.JwtUtil;
import org.ycloud.aipan.util.SpringBeanUtil;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
@Service
@Slf4j
public class ShareServiceImpl implements ShareService {
@Autowired
private ShareMapper shareMapper;
@Autowired
private ShareFileMapper shareFileMapper;
@Autowired
private AccountFileService fileService;
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFileMapper accountFileMapper;
@Override
public List<ShareDTO> listShare() {
AccountDTO accountDTO = LoginInterceptor.threadLocal.get();
List<ShareDO> shareDOList = shareMapper.selectList(new QueryWrapper<ShareDO>().eq("account_id", accountDTO.getId()).orderByDesc("gmt_create"));
return SpringBeanUtil.copyProperties(shareDOList, ShareDTO.class);
}
/**
* * 检查分享文件的权限
* * 生成分享链接和持久化数据库
* * 生成分享详情和持久化数据库
* @param req
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public ShareDTO createShare(ShareCreateReq req) {
//1、检查分享文件的权限
List<Long> fileIds = req.getFileIds();
fileService.checkFileIdLegal(fileIds, req.getAccountId());
//2、生成分享链接和持久化数据库
Integer dayType = req.getShareDayType();
Integer shareDays = ShareDayTypeEnum.getDaysByType(dayType);
Long shareId = IdUtil.getSnowflakeNextId();
//生成分享链接
String shareUrl = AccountConfig.PAN_FRONT_DOMAIN_SHARE_API + shareId;
log.info("shareUrl:{}",shareUrl);
ShareDO shareDO = ShareDO.builder()
.id(shareId)
.shareName(req.getShareName())
.shareType(ShareTypeEnum.valueOf(req.getShareType()).name())
.shareDayType(dayType)
.shareDay(shareDays)
.shareUrl(shareUrl)
.shareStatus(ShareStatusEnum.USED.name())
.accountId(req.getAccountId()).build();
if(ShareDayTypeEnum.PERMANENT.getDayType().equals(dayType)){
shareDO.setShareEndTime(Date.from(LocalDate.of(9999,12,31)
.atStartOfDay(ZoneId.systemDefault()).toInstant()));
}else {
shareDO.setShareEndTime(new Date(System.currentTimeMillis() + shareDays * 24 * 3600 * 1000L));
}
if(ShareTypeEnum.NEED_CODE.name().equalsIgnoreCase(req.getShareType())){
//生成提取码 6位
String shareCode = RandomStringUtils.randomAlphabetic(6).toUpperCase();
shareDO.setShareCode(shareCode);
}
shareMapper.insert(shareDO);
//3、生成分享详情和持久化数据库
List<ShareFileDO> shareFileDOS = new ArrayList<>();
fileIds.forEach(fileId -> {
ShareFileDO shareFileDO = ShareFileDO.builder()
.shareId(shareId)
.accountFileId(fileId)
.accountId(req.getAccountId())
.build();
shareFileDOS.add(shareFileDO);
});
shareFileMapper.insertBatch(shareFileDOS);
return SpringBeanUtil.copyProperties(shareDO, ShareDTO.class);
}
@Override
public void cancelShare(ShareCancelReq req) {
List<ShareDO> shareDOList = shareMapper.selectList(new QueryWrapper<ShareDO>().eq("account_id", req.getAccountId()).in("id", req.getShareIds()));
if(shareDOList.size() != req.getShareIds().size()){
log.error("cancelShare,shareIds:{}",req.getShareIds());
throw new BizException(BizCodeEnum.SHARE_CANCEL_ILLEGAL);
}
// 删除分享链接
shareMapper.deleteBatchIds(req.getShareIds());
//删除分享详情
shareFileMapper.delete(new QueryWrapper<ShareFileDO>().in("share_id", req.getShareIds()));
}
/**
* * 检查分享状态
* * 查询分享记录实体
* * 查询分享者信息
* * 判断是否需要生成校验码,不需要的话可以直接生成分享token
* @param shareId
* @return
*/
@Override
public ShareSimpleDTO simpleDetail(Long shareId) {
// 检查分享状态
ShareDO shareDO = checkShareStatus(shareId);
ShareSimpleDTO shareSimpleDTO = SpringBeanUtil.copyProperties(shareDO, ShareSimpleDTO.class);
//查询分享者信息
ShareAccountDTO shareAccountDTO = getShareAccount(shareDO.getAccountId());
shareSimpleDTO.setShareAccountDTO(shareAccountDTO);
//判断是否需要校验码
if(ShareTypeEnum.NO_CODE.name().equalsIgnoreCase(shareDO.getShareType())){
//直接生成分享token
String shareToken = JwtUtil.geneShareJWT(shareDO.getId());
shareSimpleDTO.setShareToken(shareToken);
}
return shareSimpleDTO;
}
@Override
public String checkShareCode(ShareCheckReq req) {
ShareDO shareDO = shareMapper.selectOne(new QueryWrapper<ShareDO>().eq("id", req.getShareId()).eq("share_code", req.getShareCode()).eq("share_status", ShareStatusEnum.USED.name()));
if(shareDO != null){
//判断是否过期
if(shareDO.getShareEndTime().getTime() > System.currentTimeMillis()){
//生成分享token
return JwtUtil.geneShareJWT(shareDO.getId());
}else {
log.error("分享链接已失效:{}",req.getShareId());
throw new BizException(BizCodeEnum.SHARE_EXPIRED);
}
}
return null;
}
/**
* 分享详情接口
* * 查询分享记录实体
* * 检查分享状态
* * 查询分享文件信息
* * 查询分享者信息
* * 构造分析详情对象返回
* @param shareId
* @return
*/
@Override
public ShareDetailDTO detail(Long shareId) {
//查询分享记录实体
ShareDO shareDO = checkShareStatus(shareId);
ShareDetailDTO shareDetailDTO = SpringBeanUtil.copyProperties(shareDO, ShareDetailDTO.class);
//查询分享文件信息
List<AccountFileDO> accountFileDOList = getShareFileInfo(shareId);
List<AccountFileDTO> accountFileDTOList = SpringBeanUtil.copyProperties(accountFileDOList, AccountFileDTO.class);
shareDetailDTO.setFileDTOList(accountFileDTOList);
//查询分享者信息
ShareAccountDTO shareAccountDTO = getShareAccount(shareDO.getAccountId());
shareDetailDTO.setShareAccountDTO(shareAccountDTO);
return shareDetailDTO;
}
/**
* * 检查分享链接状态
* * 查询分享ID是否在分享的文件列表中需要获取分享文件列表的全部文件夹和子文件夹
* * 分组后获取某个文件夹下面所有的子文件夹
* * 根据父文件夹ID获取子文件夹列表
* @param req
* @return
*/
@Override
public List<AccountFileDTO> listShareFile(ShareFileQueryReq req) {
//检查分享链接状态
ShareDO shareDO = checkShareStatus(req.getShareId());
//查询分享ID是否在分享的文件列表中(需要获取分享文件列表的全部文件夹和子文件夹)
List<AccountFileDO> accountFileDOList = checkShareFileIdOnStatus(shareDO.getId(), List.of(req.getParentId()));
List<AccountFileDTO> accountFileDTOList = SpringBeanUtil.copyProperties(accountFileDOList, AccountFileDTO.class);
//分组后获取某个文件夹下面所有的子文件夹
Map<Long, List<AccountFileDTO>> fileListMap = accountFileDTOList.stream()
.collect(Collectors.groupingBy(AccountFileDTO::getParentId));
//根据父文件夹ID获取子文件夹列表
List<AccountFileDTO> childFileList = fileListMap.get(req.getParentId());
if(CollectionUtils.isEmpty(childFileList)){
return List.of();
}
return childFileList;
}
/**
* * 分享链接是否状态准确
* * 转存的文件是否是分享链接里面的文件
* * 目标文件夹是否是当前用户的
* * 获取转存的文件
* * 保存需要转存的文件列表递归子文件
* * 同步更新所有文件的accountId为当前用户的id
* * 计算存储空间大小检查是否足够
* * 更新关联对象信息存储文件映射关系
* @param req
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void transferShareFile(ShareFileTransferReq req) {
//分享链接是否状态准确
checkShareStatus(req.getShareId());
//转存的文件是否是分享链接里面的文件
checkInShareFiles(req.getFileIds(),req.getShareId());
//目标文件夹是否是当前用户的
AccountFileDO currentAccountDO = accountFileMapper.selectOne(new QueryWrapper<AccountFileDO>()
.eq("id", req.getParentId()).eq("account_id", req.getAccountId()));
if(currentAccountDO == null){
log.error("目标文件夹不是当前用户的,{}",req);
throw new BizException(BizCodeEnum.FILE_NOT_EXISTS);
}
//获取转存的文件
List<AccountFileDO> shareFileList = accountFileMapper.selectBatchIds(req.getFileIds());
//保存需要转存的文件列表(递归子文件)
List<AccountFileDO> batchTransferFileList = fileService.findBatchCopyFileWithRecur(shareFileList, req.getParentId());
//同步更新所有文件的accountId为当前用户的id
batchTransferFileList.forEach(file -> {
file.setAccountId(req.getAccountId());
});
//计算存储空间大小,检查是否足够
if(!fileService.checkAndUpdateCapacity(req.getAccountId(),batchTransferFileList.stream()
.map(accountFileDO -> accountFileDO.getFileSize() == null ? 0 : accountFileDO.getFileSize())
.mapToLong(Long::valueOf).sum())){
throw new BizException(BizCodeEnum.FILE_STORAGE_NOT_ENOUGH);
}
//更新关联对象信息,存储文件映射关系
accountFileMapper.insertFileBatch(batchTransferFileList);
}
private void checkInShareFiles(List<Long> fileIds, Long shareId) {
//获取分享链接的文件
List<ShareFileDO> shareFileDOS = shareFileMapper.selectList(new QueryWrapper<ShareFileDO>().eq("share_id", shareId));
List<Long> shareFileIds = shareFileDOS.stream().map(ShareFileDO::getAccountFileId).toList();
//找文件实体
List<AccountFileDO> shareAccountFileDOList = accountFileMapper.selectBatchIds(shareFileIds);
//递归找分享链接里面的所有子文件
List<AccountFileDO> allShareFiles = new ArrayList<>();
fileService.findAllAccountFileDOWithRecur(allShareFiles, shareAccountFileDOList, false);
//提取全部文件的ID
List<Long> allShareFileIds = allShareFiles.stream().map(AccountFileDO::getId).toList();
//判断要转存的文件是否在里面
for (Long fileId : fileIds) {
if(!allShareFileIds.contains(fileId)){
log.error("文件不在分享链接里面,fileId:{}",fileId);
throw new BizException(BizCodeEnum.SHARE_FILE_ILLEGAL);
}
}
}
/**
* 返回分享的文件列表包括子文件
* @param shareId
* @param fileIdList
* @return
*/
private List<AccountFileDO> checkShareFileIdOnStatus(Long shareId, List<Long> fileIdList) {
//需要获取分享文件列表的全部文件夹和子文件内容
List<AccountFileDO> shareFileInfoList = getShareFileInfo(shareId);
List<AccountFileDO> allAccountFileDOList = new ArrayList<>();
//获取全部文件,递归
fileService.findAllAccountFileDOWithRecur(allAccountFileDOList, shareFileInfoList, false);
if(CollectionUtils.isEmpty(allAccountFileDOList)){
return List.of();
}
//把分享的对象文件的全部文件夹放到集合里面,判断目标文件集合是否都在里面
Set<Long> allFileIdSet = allAccountFileDOList.stream().map(AccountFileDO::getId).collect(Collectors.toSet());
if(!allFileIdSet.containsAll(fileIdList)){
log.error("目标文件ID列表 不再 分享的文件列表中,{}",fileIdList);
throw new BizException(BizCodeEnum.SHARE_FILE_ILLEGAL);
}
return allAccountFileDOList;
}
private List<AccountFileDO> getShareFileInfo(Long shareId) {
//找分享文件列表
List<ShareFileDO> shareFileDOS = shareFileMapper.selectList(new QueryWrapper<ShareFileDO>().select("account_file_id")
.eq("share_id", shareId));
List<Long> shareFileIdList = shareFileDOS.stream().map(ShareFileDO::getAccountFileId).toList();
//查找文件对象
return accountFileMapper.selectBatchIds(shareFileIdList);
}
/**
* 获取分享者信息
* @param accountId
* @return
*/
private ShareAccountDTO getShareAccount(Long accountId) {
if(accountId != null){
AccountDO accountDO = accountMapper.selectById(accountId);
if(accountDO != null){
return SpringBeanUtil.copyProperties(accountDO, ShareAccountDTO.class);
}
}
return null;
}
/**
* 检查分享状态
* @param shareId
* @return
*/
private ShareDO checkShareStatus(Long shareId) {
ShareDO shareDO = shareMapper.selectById(shareId);
if(shareDO == null){
log.error("分享链接不存在:{}",shareId);
throw new BizException(BizCodeEnum.SHARE_NOT_EXIST);
}
//暂时未用,直接物理删除,可以调整
if(ShareStatusEnum.CANCELED.name().equalsIgnoreCase(shareDO.getShareStatus())){
log.error("分享链接已取消:{}",shareId);
throw new BizException(BizCodeEnum.SHARE_CANCELED);
}
//判断分享是否过期
if(shareDO.getShareEndTime().before(new Date())){
log.error("分享链接已过期:{}",shareId);
throw new BizException(BizCodeEnum.SHARE_EXPIRED);
}
return shareDO;
}
}

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

@ -17,6 +17,14 @@ public class JwtUtil {
// JWT的主题 // JWT的主题
private static final String LOGIN_SUBJECT = "YUAN"; private static final String LOGIN_SUBJECT = "YUAN";
//分享主题
private static final String SHARE_SUBJECT = "YUAN_SHARE";
//分享ID
public static final String CLAIM_SHARE_KEY = "SHARE_ID";
//分享token过期时间,1小时
private static final long SHARE_TOKEN_EXPIRE = 1000 * 60 * 60;
//注意这个密钥长度需要足够长, 推荐:JWT的密钥,从环境变量中获取 //注意这个密钥长度需要足够长, 推荐:JWT的密钥,从环境变量中获取
private final static String SECRET_KEY = "yuan.yuannet.netyuan.password.3421.sdfnet.com.efszyuaszgh"; private final static String SECRET_KEY = "yuan.yuannet.netyuan.password.3421.sdfnet.com.efszyuaszgh";
@ -118,4 +126,51 @@ public class JwtUtil {
} }
return token; return token;
} }
/**
* 创建分享的令牌
*/
public static String geneShareJWT( Object claimValue) {
// 创建 JWT token
String compact = Jwts.builder()
.subject(SHARE_SUBJECT)
.claim(CLAIM_SHARE_KEY, claimValue)
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + SHARE_TOKEN_EXPIRE))
.signWith(KEY, ALGORITHM) // 直接使用KEY即可
.compact();
return compact;
}
/**
* 创建分享的令牌
*/
public static Claims checkShareJWT(String token) {
try {
log.debug("开始校验 Share JWT: {}", token);
// 校验 Token 是否为空
if (token == null || token.trim().isEmpty()) {
log.error("Share Token 不能为空");
return null;
}
token = token.trim();
// 解析 JWT
Claims payload = Jwts.parser()
.verifyWith(KEY) //设置签名的密钥, 使用相同的 KEY
.build()
.parseSignedClaims(token).getPayload();
log.info("Share JWT 解密成功,Claims: {}", payload);
return payload;
} catch (IllegalArgumentException e) {
log.error("JWT 校验失败: {}", e.getMessage(), e);
} catch (io.jsonwebtoken.security.SignatureException e) {
log.error("JWT 签名验证失败: {}", e.getMessage(), e);
} catch (io.jsonwebtoken.ExpiredJwtException e) {
log.error("JWT 已过期: {}", e.getMessage(), e);
} catch (Exception e) {
log.error("JWT 解密失败: {}", e.getMessage(), e);
}
return null;
}
} }

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

@ -16,5 +16,11 @@
<sql id="Base_Column_List"> <sql id="Base_Column_List">
id, share_id, account_file_id, account_id, gmt_create, gmt_modified id, share_id, account_file_id, account_id, gmt_create, gmt_modified
</sql> </sql>
<insert id="insertBatch" parameterType="java.util.List">
insert into share_file (id,share_id, account_file_id, account_id) values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.id},#{item.shareId},#{item.accountFileId},#{item.accountId})
</foreach>
</insert>
</mapper> </mapper>

Loading…
Cancel
Save