浏览代码

feat: 新增文件上传接口
1. 新增文件上传的接口,返回值中包含访问oss的token
2. yml有所改动,新增了oss配置
3. sql有改动给label的name加了一个唯一索引
4. 新增工具类SimpleCache,作为本机简单缓存
5. 给mybatis plus新增一个handler,用于再insert和update的时候更新createdTime和updateTime
6. pom中加了一个aliyun oss的依赖

sky 3 年之前
父节点
当前提交
c70d02c075
共有 28 个文件被更改,包括 597 次插入38 次删除
  1. 1 1
      generate/cn/edu/nju/plagdemo/model/po/AlgorithmPO.java
  2. 7 4
      generate/cn/edu/nju/plagdemo/model/po/DetectionFilePO.java
  3. 10 1
      pom.xml
  4. 1 0
      src/main/java/cn/edu/nju/plagdemo/PlagDemoApplication.java
  5. 9 0
      src/main/java/cn/edu/nju/plagdemo/api/IOssApi.java
  6. 145 0
      src/main/java/cn/edu/nju/plagdemo/api/impl/OssApiImpl.java
  7. 21 0
      src/main/java/cn/edu/nju/plagdemo/config/MyMetaObjectHandler.java
  8. 4 6
      src/main/java/cn/edu/nju/plagdemo/controller/DetectionFileController.java
  9. 5 2
      src/main/java/cn/edu/nju/plagdemo/model/dto/plag/BaseFileDTO.java
  10. 6 0
      src/main/java/cn/edu/nju/plagdemo/model/dto/plag/DetectionFileDTO.java
  11. 9 0
      src/main/java/cn/edu/nju/plagdemo/model/dto/plag/LabelDTO.java
  12. 9 0
      src/main/java/cn/edu/nju/plagdemo/model/dto/plag/OssNameAndLinkDTO.java
  13. 9 5
      src/main/java/cn/edu/nju/plagdemo/model/form/plag/BaseFileForm.java
  14. 11 6
      src/main/java/cn/edu/nju/plagdemo/model/form/plag/DetectionFileForm.java
  15. 12 0
      src/main/java/cn/edu/nju/plagdemo/model/form/plag/LabelForm.java
  16. 3 3
      src/main/java/cn/edu/nju/plagdemo/model/po/plag/BaseFilePO.java
  17. 4 4
      src/main/java/cn/edu/nju/plagdemo/model/po/plag/DetectionFilePO.java
  18. 13 0
      src/main/java/cn/edu/nju/plagdemo/model/vo/OssInfoVO.java
  19. 16 0
      src/main/java/cn/edu/nju/plagdemo/model/vo/OssTokenVO.java
  20. 2 1
      src/main/java/cn/edu/nju/plagdemo/service/IDetectionFileService.java
  21. 1 0
      src/main/java/cn/edu/nju/plagdemo/service/ILabelService.java
  22. 81 2
      src/main/java/cn/edu/nju/plagdemo/service/impl/DetectionFileServiceImpl.java
  23. 11 0
      src/main/java/cn/edu/nju/plagdemo/service/impl/LabelServiceImpl.java
  24. 108 0
      src/main/java/cn/edu/nju/plagdemo/utils/SimpleCache.java
  25. 13 2
      src/main/resources/application.yml
  26. 2 1
      src/main/resources/cheatDetection.sql
  27. 30 0
      src/test/java/cn/edu/nju/plagdemo/api/IOssApiTest.java
  28. 54 0
      src/test/java/cn/edu/nju/plagdemo/service/IDetectionFileServiceTest.java

+ 1 - 1
generate/cn/edu/nju/plagdemo/model/po/AlgorithmPO.java

@@ -36,7 +36,7 @@ public class AlgorithmPO extends Model<AlgorithmPO> {
     private String endpoint;
 
     @ApiModelProperty("是否可用")
-    private Byte isValid;
+    private Boolean isValid;
 
     @ApiModelProperty("默认选项")
     private String defaultOptions;

+ 7 - 4
generate/cn/edu/nju/plagdemo/model/po/DetectionFilePO.java

@@ -30,16 +30,19 @@ public class DetectionFilePO extends Model<DetectionFilePO> {
     @TableId(value = "id", type = IdType.AUTO)
     private Integer id;
 
-    @ApiModelProperty("文件名(用户输入的名字,默认为压缩包的文件名)
")
+    @ApiModelProperty("文件名(用户输入的名字,默认为压缩包的文件名)
+")
     private String name;
 
     @ApiModelProperty("oss存的真正的文件名(具有唯一性 name-yyyyMMddhhmmss-randomNum)")
     private String ossName;
 
-    @ApiModelProperty("过期时间(单位:小时。超过过期时间从oss中删除对应文件,同时oss	回调更改is_valid字段)
")
+    @ApiModelProperty("过期时间(单位:小时。超过过期时间从oss中删除对应文件,同时oss	回调更改is_valid字段)
+")
     private Integer expiration;
 
-    @ApiModelProperty("当前状态(正在上传/上传成功/上传失败/已过期)
")
+    @ApiModelProperty("当前状态(正在上传/上传成功/上传失败/已过期)
+")
     private String state;
 
     @ApiModelProperty("文件创建时间(即表单提交、文件准备上传的时间)")
@@ -58,7 +61,7 @@ public class DetectionFilePO extends Model<DetectionFilePO> {
     private Integer ownerId;
 
     @ApiModelProperty("是否包括基准文件")
-    private Byte hasBaseFile;
+    private Boolean hasBaseFile;
 
     @ApiModelProperty("基准文件id")
     private Integer baseFileId;

+ 10 - 1
pom.xml

@@ -87,7 +87,16 @@
             <artifactId>springfox-boot-starter</artifactId>
             <version>3.0.0</version>
         </dependency>
-
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-core</artifactId>
+            <version>4.5.20</version>
+        </dependency>
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>aliyun-java-sdk-sts</artifactId>
+            <version>3.1.0</version>
+        </dependency>
     </dependencies>
 
     <build>

+ 1 - 0
src/main/java/cn/edu/nju/plagdemo/PlagDemoApplication.java

@@ -3,6 +3,7 @@ package cn.edu.nju.plagdemo;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
 
 @MapperScan("cn.edu.nju.plagdemo.mapper")
 @SpringBootApplication(scanBasePackages = {"cn.edu.nju.plagdemo"})

+ 9 - 0
src/main/java/cn/edu/nju/plagdemo/api/IOssApi.java

@@ -0,0 +1,9 @@
+package cn.edu.nju.plagdemo.api;
+
+import cn.edu.nju.plagdemo.model.dto.plag.OssNameAndLinkDTO;
+import cn.edu.nju.plagdemo.model.vo.OssInfoVO;
+
+public interface IOssApi {
+   OssInfoVO getOssClientInfo();
+   OssNameAndLinkDTO getOssNameAndLink(String type, String fileName);
+}

+ 145 - 0
src/main/java/cn/edu/nju/plagdemo/api/impl/OssApiImpl.java

@@ -0,0 +1,145 @@
+package cn.edu.nju.plagdemo.api.impl;
+
+import cn.edu.nju.plagdemo.api.IOssApi;
+
+import cn.edu.nju.plagdemo.exception.DefinitionException;
+import cn.edu.nju.plagdemo.model.dto.plag.OssNameAndLinkDTO;
+import cn.edu.nju.plagdemo.model.vo.OssInfoVO;
+import cn.edu.nju.plagdemo.model.vo.OssTokenVO;
+import cn.edu.nju.plagdemo.utils.SimpleCache;
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.exceptions.ClientException;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.profile.IClientProfile;
+import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
+import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+
+/**
+ * ali oss临时授权访问
+ */
+@Slf4j
+@Service
+public class OssApiImpl implements IOssApi {
+    // STS接入地址,例如sts.cn-hangzhou.aliyuncs.com。
+    @Value("${oss.endpoint}")
+    String endpoint = "";
+
+    // 填写步骤1生成的RAM用户访问密钥AccessKey ID和AccessKey Secret。
+    @Value("${oss.accesskey.id}")
+    String accessKeyId = "";
+
+    @Value("${oss.accesskey.secret}")
+    String accessKeySecret = "";
+
+    // 填写步骤3获取的角色ARN。
+    @Value("${oss.role.arn}")
+    String roleArn = "";
+
+    // 自定义角色会话名称,用来区分不同的令牌,例如可填写为SessionTest。
+    @Value("${oss.role.sessionname}")
+    String roleSessionName = "";
+
+    // regionId表示RAM的地域ID。以华东1(杭州)地域为例,regionID填写为cn-hangzhou。也可以保留默认值,默认值为空字符串("")。
+    @Value("${oss.region}")
+    String regionId = "";
+
+    @Value("${oss.bucket}")
+    String bucket = "";
+
+    // 以下Policy用于限制仅允许使用临时访问凭证向目标存储空间examplebucket上传文件。
+    // 临时访问凭证最后获得的权限是步骤4设置的角色权限和该Policy设置权限的交集,即仅允许将文件上传至目标存储空间examplebucket下的exampledir目录。
+    // 如果policy为空,则用户将获得该角色下所有权限。
+    String policy = "{\n" +
+            "    \"Version\": \"1\",\n" +
+            "    \"Statement\": [\n" +
+            "        {\n" +
+            "            \"Effect\": \"Allow\",\n" +
+            "            \"Action\": [\n" +
+            "                \"oss:GetObject\",\n" +
+            "                \"oss:GetObjectAcl\",\n" +
+            "                \"oss:DeleteObject\",\n" +
+            "                \"oss:PutObject\",\n" +
+            "                \"oss:PutObjectAcl\",\n" +
+            "                \"oss:ListObjects\"\n" +
+            "            ],\n" +
+            "            \"Resource\": [\n" +
+            "                \"acs:oss:*:*:plag-file-bucket/development/basefile/*\",\n" +
+            "                \"acs:oss:*:*:plag-file-bucket/development/file/*\"\n" +
+            "            ]\n" +
+            "        }\n" +
+            "    ]\n" +
+            "}";
+    // 设置临时访问凭证的有效时间为3600秒。
+    Long durationSeconds = 3600L;
+
+    private OssTokenVO getSTSToken() {
+        OssTokenVO stsToken = (OssTokenVO) SimpleCache.get("sts_token");
+        if(stsToken != null) return stsToken;
+        try {
+            // 添加endpoint。适用于Java SDK 3.12.0及以上版本。
+            DefaultProfile.addEndpoint(regionId, "Sts", endpoint);
+            // 添加endpoint。适用于Java SDK 3.12.0以下版本。
+            // DefaultProfile.addEndpoint("",regionId, "Sts", endpoint);
+            // 构造default profile。
+            IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
+            // 构造client。
+            DefaultAcsClient client = new DefaultAcsClient(profile);
+            final AssumeRoleRequest request = new AssumeRoleRequest();
+            // 适用于Java SDK 3.12.0及以上版本。
+            request.setSysMethod(MethodType.POST);
+            // 适用于Java SDK 3.12.0以下版本。
+            //request.setMethod(MethodType.POST);
+            request.setRoleArn(roleArn);
+            request.setRoleSessionName(roleSessionName);
+            request.setPolicy(policy);
+            request.setDurationSeconds(durationSeconds);
+            final AssumeRoleResponse response = client.getAcsResponse(request);
+//            System.out.println("Expiration: " + response.getCredentials().getExpiration());
+//            System.out.println("Access Key Id: " + response.getCredentials().getAccessKeyId());
+//            System.out.println("Access Key Secret: " + response.getCredentials().getAccessKeySecret());
+//            System.out.println("Security Token: " + response.getCredentials().getSecurityToken());
+//            System.out.println("RequestId: " + response.getRequestId());
+            stsToken = new OssTokenVO();
+            stsToken.setSecurityToken(response.getCredentials().getSecurityToken());
+            stsToken.setAccessKeyId(response.getCredentials().getAccessKeyId());
+            stsToken.setAccessKeySecret(response.getCredentials().getSecurityToken());
+            SimpleCache.put("sts_token", stsToken, (durationSeconds - 5) * 1000);
+            return stsToken;
+        } catch (ClientException e) {
+            log.error("Failed:" +
+            "\nError code: " + e.getErrCode() +
+            "\nError message: " + e.getErrMsg() +
+            "\nRequestId: " + e.getRequestId(), e);
+            throw new DefinitionException(HttpStatus.INTERNAL_SERVER_ERROR, "获取sts token失败");
+        }
+    }
+
+    public OssInfoVO getOssClientInfo() { // type == "file" or "basefile"
+        OssTokenVO stsToken = getSTSToken();
+        return OssInfoVO.builder()
+                .token(stsToken)
+                .bucket(bucket)
+                .region(regionId)
+//                .ossLink("development/" + type + "/" + fileName + "-" + new Date().getTime() + ".zip")
+                .build();
+    }
+
+    public OssNameAndLinkDTO getOssNameAndLink(String type, String fileName) {
+        if(!(type.equals("file") || type.equals("basefile"))) return null;
+        OssNameAndLinkDTO ossNameAndLinkDTO = new OssNameAndLinkDTO();
+        String ossName = fileName + "-" + new Date().getTime();
+        String ossLink = "development/" + type + "/" + ossName + ".zip";
+        ossNameAndLinkDTO.setOssName(ossName);
+        ossNameAndLinkDTO.setOssLink(ossLink);
+        return ossNameAndLinkDTO;
+    }
+}

+ 21 - 0
src/main/java/cn/edu/nju/plagdemo/config/MyMetaObjectHandler.java

@@ -0,0 +1,21 @@
+package cn.edu.nju.plagdemo.config;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        this.setFieldValByName("createdTime", LocalDateTime.now(), metaObject);
+        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
+    }
+}

+ 4 - 6
src/main/java/cn/edu/nju/plagdemo/controller/DetectionFileController.java

@@ -1,6 +1,7 @@
 package cn.edu.nju.plagdemo.controller;
 
 import cn.edu.nju.plagdemo.exception.DefinitionException;
+import cn.edu.nju.plagdemo.model.dto.plag.DetectionFileDTO;
 import cn.edu.nju.plagdemo.model.form.plag.DetectionFileForm;
 import cn.edu.nju.plagdemo.model.po.plag.DetectionFilePO;
 import cn.edu.nju.plagdemo.service.IDetectionFileService;
@@ -21,15 +22,12 @@ public class DetectionFileController {
     @Autowired
     IDetectionFileService detectionFileService;
 
-    @ApiOperation(value = "新增待检测文件", notes = "")
+    @ApiOperation(value = "新增待检测文件", notes = "")
     @PostMapping(value = "/insert")
     public RestResult<?> insert(
             @Validated @RequestBody DetectionFileForm detectionFileForm
     ){
-        if(!detectionFileForm.checkStateValid()) {
-            throw new DefinitionException(HttpStatus.BAD_REQUEST, "无效的状态 \"" + detectionFileForm.getState() + "\"");
-        }
-        DetectionFilePO detectionFilePO = detectionFileService.save(detectionFileForm);
-        return RestResult.ok(detectionFilePO);
+        DetectionFileDTO detectionFileDTO = detectionFileService.save(detectionFileForm);
+        return RestResult.ok(detectionFileDTO);
     }
 }

+ 5 - 2
src/main/java/cn/edu/nju/plagdemo/model/dto/plag/BaseFileDTO.java

@@ -1,9 +1,11 @@
 package cn.edu.nju.plagdemo.model.dto.plag;
 
+import lombok.Data;
 import lombok.Getter;
 import lombok.Setter;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * <p>
@@ -13,8 +15,7 @@ import java.time.LocalDateTime;
  * @author LiXueSong
  * @since 2023-02-13
  */
-@Getter
-@Setter
+@Data
 public class BaseFileDTO {
 
     private Integer id;
@@ -26,6 +27,8 @@ public class BaseFileDTO {
     private String ossName;
 
 //    @ApiModelProperty("过期时间(单位:小时。超过过期时间从oss中删除对应文件,同时oss	回调更改is_valid字段) ")
+    // 后面调研的时候发现oss本身不支持给每个单独的文件设置过期时间,只能统一设置一个,因此该字段作废,默认设置为null(永久)
+    @Deprecated
     private Integer expiration;
 
 //    @ApiModelProperty("当前状态(正在上传/上传成功/上传失败/已过期) ")

+ 6 - 0
src/main/java/cn/edu/nju/plagdemo/model/dto/plag/DetectionFileDTO.java

@@ -1,7 +1,9 @@
 package cn.edu.nju.plagdemo.model.dto.plag;
 
+import cn.edu.nju.plagdemo.model.vo.OssInfoVO;
 import lombok.Data;
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * <p>
@@ -54,4 +56,8 @@ public class DetectionFileDTO {
     //@ApiModelProperty("压缩包层级结构(前两层)")
     private String level;
 
+    private List<LabelDTO> labelDTOS;
+
+    private OssInfoVO ossClientInfo;
+
 }

+ 9 - 0
src/main/java/cn/edu/nju/plagdemo/model/dto/plag/LabelDTO.java

@@ -0,0 +1,9 @@
+package cn.edu.nju.plagdemo.model.dto.plag;
+
+import lombok.Data;
+
+@Data
+public class LabelDTO {
+    private Integer id;
+    private String name;
+}

+ 9 - 0
src/main/java/cn/edu/nju/plagdemo/model/dto/plag/OssNameAndLinkDTO.java

@@ -0,0 +1,9 @@
+package cn.edu.nju.plagdemo.model.dto.plag;
+
+import lombok.Data;
+
+@Data
+public class OssNameAndLinkDTO {
+    String ossName;
+    String ossLink;
+}

+ 9 - 5
src/main/java/cn/edu/nju/plagdemo/model/form/plag/BaseFileForm.java

@@ -1,9 +1,12 @@
 package cn.edu.nju.plagdemo.model.form.plag;
 
+import lombok.Builder;
+import lombok.Data;
 import lombok.Getter;
 import lombok.Setter;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * <p>
@@ -13,26 +16,27 @@ import java.time.LocalDateTime;
  * @author LiXueSong
  * @since 2023-02-13
  */
-@Getter
-@Setter
+@Data
+@Builder
 public class BaseFileForm {
 
 //    @ApiModelProperty("文件名(用户输入的名字,默认为压缩包的文件名) ")
     private String name;
 
 //    @ApiModelProperty("oss存的真正的文件名(具有唯一性 name-yyyyMMddhhmmss-randomNum)")
-    private String ossName;
+//    private String ossName;
 
 //    @ApiModelProperty("过期时间(单位:小时。超过过期时间从oss中删除对应文件,同时oss	回调更改is_valid字段) ")
     private Integer expiration;
 
 //    @ApiModelProperty("当前状态(正在上传/上传成功/上传失败/已过期) ")
-    private String state;
+//    private String state;
 
 //    @ApiModelProperty("链接到file_type")
     private Integer fileType;
 
 //    @ApiModelProperty("oss链接")
-    private String ossLink;
+//    private String ossLink;
+
 
 }

+ 11 - 6
src/main/java/cn/edu/nju/plagdemo/model/form/plag/DetectionFileForm.java

@@ -1,9 +1,14 @@
 package cn.edu.nju.plagdemo.model.form.plag;
 
+import cn.edu.nju.plagdemo.model.FileStateEnum;
 import cn.edu.nju.plagdemo.model.dto.plag.BaseFileDTO;
+import lombok.Builder;
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -14,25 +19,26 @@ import java.time.LocalDateTime;
  * @since 2023-02-13
  */
 @Data
+@Builder
 public class DetectionFileForm {
 
     //@ApiModelProperty("文件名(用户输入的名字,默认为压缩包的文件名) ")
     private String name;
 
     //@ApiModelProperty("oss存的真正的文件名(具有唯一性 name-yyyyMMddhhmmss-randomNum)")
-    private String ossName;
+//    private String ossName;
 
     //@ApiModelProperty("过期时间(单位:小时。超过过期时间从oss中删除对应文件,同时oss	回调更改is_valid字段) ")
     private Integer expiration;
 
     //@ApiModelProperty("当前状态(正在上传/上传成功/上传失败/已过期) ")
-    private String state;
+//    private String state;
 
     //@ApiModelProperty("链接到file_type")
     private Integer fileType;
 
     //@ApiModelProperty("oss链接")
-    private String ossLink;
+//    private String ossLink;
 
     //@ApiModelProperty("是否包括基准文件")
     private Boolean hasBaseFile;
@@ -43,7 +49,6 @@ public class DetectionFileForm {
     //@ApiModelProperty("压缩包层级结构(前两层)")
     private String level;
 
-    public boolean checkStateValid() {
-        return false;
-    }
+    private List<LabelForm> labelForms;
+
 }

+ 12 - 0
src/main/java/cn/edu/nju/plagdemo/model/form/plag/LabelForm.java

@@ -0,0 +1,12 @@
+package cn.edu.nju.plagdemo.model.form.plag;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class LabelForm {
+    String name;
+}

+ 3 - 3
src/main/java/cn/edu/nju/plagdemo/model/po/plag/BaseFilePO.java

@@ -1,8 +1,6 @@
 package cn.edu.nju.plagdemo.model.po.plag;
 
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.*;
 import com.baomidou.mybatisplus.extension.activerecord.Model;
 import java.io.Serializable;
 import java.time.LocalDateTime;
@@ -43,9 +41,11 @@ public class BaseFilePO extends Model<BaseFilePO> {
     private String state;
 
     @ApiModelProperty("文件创建时间(即表单提交、文件准备上传的时间)")
+    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createdTime;
 
     @ApiModelProperty("文件创建成功/失败/失效的时刻")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
     private LocalDateTime updateTime;
 
     @ApiModelProperty("链接到file_type")

+ 4 - 4
src/main/java/cn/edu/nju/plagdemo/model/po/plag/DetectionFilePO.java

@@ -1,8 +1,6 @@
 package cn.edu.nju.plagdemo.model.po.plag;
 
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.*;
 import com.baomidou.mybatisplus.extension.activerecord.Model;
 import java.io.Serializable;
 import java.time.LocalDateTime;
@@ -43,9 +41,11 @@ public class DetectionFilePO extends Model<DetectionFilePO> {
     private String state;
 
     @ApiModelProperty("文件创建时间(即表单提交、文件准备上传的时间)")
+    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createdTime;
 
     @ApiModelProperty("文件创建成功/失败/失效的时刻")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
     private LocalDateTime updateTime;
 
     @ApiModelProperty("链接到file_type")
@@ -58,7 +58,7 @@ public class DetectionFilePO extends Model<DetectionFilePO> {
     private Integer ownerId;
 
     @ApiModelProperty("是否包括基准文件")
-    private Byte hasBaseFile;
+    private Boolean hasBaseFile;
 
     @ApiModelProperty("基准文件id")
     private Integer baseFileId;

+ 13 - 0
src/main/java/cn/edu/nju/plagdemo/model/vo/OssInfoVO.java

@@ -0,0 +1,13 @@
+package cn.edu.nju.plagdemo.model.vo;
+
+import lombok.Builder;
+import lombok.Data;
+
+@Data
+@Builder
+public class OssInfoVO {
+    private OssTokenVO token;
+//    private String ossLink; // oss路径,比如 develop/file1-1677673342000.zip
+    private String region;
+    private String bucket;
+}

+ 16 - 0
src/main/java/cn/edu/nju/plagdemo/model/vo/OssTokenVO.java

@@ -0,0 +1,16 @@
+package cn.edu.nju.plagdemo.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class OssTokenVO {
+    String accessKeyId; // 临时账号id
+    String accessKeySecret; // 临时账号密钥
+    String securityToken; // 临时token
+}

+ 2 - 1
src/main/java/cn/edu/nju/plagdemo/service/IDetectionFileService.java

@@ -1,5 +1,6 @@
 package cn.edu.nju.plagdemo.service;
 
+import cn.edu.nju.plagdemo.model.dto.plag.DetectionFileDTO;
 import cn.edu.nju.plagdemo.model.form.plag.DetectionFileForm;
 import cn.edu.nju.plagdemo.model.po.plag.DetectionFilePO;
 import com.baomidou.mybatisplus.extension.service.IService;
@@ -14,5 +15,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
  */
 public interface IDetectionFileService extends IService<DetectionFilePO> {
 
-    DetectionFilePO save(DetectionFileForm detectionFileForm);
+    DetectionFileDTO save(DetectionFileForm detectionFileForm);
 }

+ 1 - 0
src/main/java/cn/edu/nju/plagdemo/service/ILabelService.java

@@ -13,4 +13,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
  */
 public interface ILabelService extends IService<LabelPO> {
 
+    boolean getByName(LabelPO labelPO);
 }

+ 81 - 2
src/main/java/cn/edu/nju/plagdemo/service/impl/DetectionFileServiceImpl.java

@@ -1,12 +1,34 @@
 package cn.edu.nju.plagdemo.service.impl;
 
+import cn.edu.nju.plagdemo.api.IOssApi;
+import cn.edu.nju.plagdemo.api.impl.OssApiImpl;
+import cn.edu.nju.plagdemo.exception.DefinitionException;
+import cn.edu.nju.plagdemo.model.FileStateEnum;
+import cn.edu.nju.plagdemo.model.dto.plag.BaseFileDTO;
+import cn.edu.nju.plagdemo.model.dto.plag.DetectionFileDTO;
+import cn.edu.nju.plagdemo.model.dto.plag.LabelDTO;
+import cn.edu.nju.plagdemo.model.dto.plag.OssNameAndLinkDTO;
+import cn.edu.nju.plagdemo.model.form.plag.BaseFileForm;
 import cn.edu.nju.plagdemo.model.form.plag.DetectionFileForm;
+import cn.edu.nju.plagdemo.model.form.plag.LabelForm;
+import cn.edu.nju.plagdemo.model.po.plag.BaseFilePO;
 import cn.edu.nju.plagdemo.model.po.plag.DetectionFilePO;
 import cn.edu.nju.plagdemo.mapper.DetectionFileMapper;
+import cn.edu.nju.plagdemo.model.po.plag.LabelPO;
+import cn.edu.nju.plagdemo.model.vo.OssInfoVO;
+import cn.edu.nju.plagdemo.service.IBaseFileService;
 import cn.edu.nju.plagdemo.service.IDetectionFileService;
+import cn.edu.nju.plagdemo.service.ILabelService;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
 
 /**
  * <p>
@@ -19,8 +41,65 @@ import org.springframework.stereotype.Service;
 @Service
 public class DetectionFileServiceImpl extends ServiceImpl<DetectionFileMapper, DetectionFilePO> implements IDetectionFileService {
 
+    @Autowired
+    IBaseFileService baseFileService;
+
+    @Autowired
+    ILabelService labelService;
+
+    @Autowired
+    IOssApi ossApi;
+
     @Override
-    public DetectionFilePO save(DetectionFileForm detectionFileForm) { //TODO
-        return null;
+    @Transactional
+        public DetectionFileDTO save(DetectionFileForm detectionFileForm) { //TODO
+        DetectionFilePO detectionFilePO = new DetectionFilePO();
+        BeanUtils.copyProperties(detectionFileForm, detectionFilePO);
+        detectionFilePO.setState(FileStateEnum.UPLOADING.getState());
+        OssNameAndLinkDTO fileNameAndLink = ossApi.getOssNameAndLink("file", detectionFilePO.getName());
+        detectionFilePO.setOssName(fileNameAndLink.getOssName());
+        detectionFilePO.setOssLink(fileNameAndLink.getOssLink());
+        boolean ok = save(detectionFilePO);
+        if(!ok) throw new DefinitionException(HttpStatus.INTERNAL_SERVER_ERROR, "插入检测文件失败");
+
+        DetectionFileDTO detectionFileDTO = new DetectionFileDTO();
+        BeanUtils.copyProperties(detectionFilePO, detectionFileDTO);
+
+        if(detectionFileForm.getHasBaseFile().equals(Boolean.TRUE)) {
+            BaseFileForm baseFileForm = detectionFileForm.getBaseFile();
+            BaseFilePO baseFilePO = new BaseFilePO();
+            baseFilePO.setState(FileStateEnum.UPLOADING.getState());
+            BeanUtils.copyProperties(baseFileForm, baseFilePO);
+            OssNameAndLinkDTO basefileNameAndLink = ossApi.getOssNameAndLink("basefile", baseFilePO.getName());
+            baseFilePO.setOssLink(basefileNameAndLink.getOssLink());
+            baseFilePO.setOssName(basefileNameAndLink.getOssName());
+            ok = baseFileService.save(baseFilePO);
+            if(!ok) throw new DefinitionException(HttpStatus.INTERNAL_SERVER_ERROR, "插入基准文件失败");
+
+            BaseFileDTO baseFileDTO = new BaseFileDTO();
+            BeanUtils.copyProperties(baseFilePO, baseFileDTO);
+            detectionFileDTO.setBaseFile(baseFileDTO);
+        }
+        List<LabelDTO> labelDTOList = new ArrayList<>();
+        for (LabelForm labelForm : detectionFileForm.getLabelForms()) {
+            LabelPO labelPO = new LabelPO();
+            labelPO.setName(labelForm.getName());
+            boolean has = labelService.getByName(labelPO);
+            try { // 这么写是为了把labelId存入labelPO中
+                if(!has) labelService.save(labelPO);
+            } catch (Exception ignored) {
+                log.warn("已存在相同label,无效的插入. label name : " + labelForm.getName());
+                labelService.getByName(labelPO);
+            }
+            LabelDTO labelDTO = new LabelDTO();
+            BeanUtils.copyProperties(labelPO, labelDTO);
+            labelDTOList.add(labelDTO);
+        }
+        detectionFileDTO.setLabelDTOS(labelDTOList);
+
+        OssInfoVO ossClientInfo = ossApi.getOssClientInfo();
+        detectionFileDTO.setOssClientInfo(ossClientInfo);
+
+        return detectionFileDTO;
     }
 }

+ 11 - 0
src/main/java/cn/edu/nju/plagdemo/service/impl/LabelServiceImpl.java

@@ -3,6 +3,8 @@ package cn.edu.nju.plagdemo.service.impl;
 import cn.edu.nju.plagdemo.model.po.plag.LabelPO;
 import cn.edu.nju.plagdemo.mapper.LabelMapper;
 import cn.edu.nju.plagdemo.service.ILabelService;
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import org.springframework.stereotype.Service;
 
@@ -17,4 +19,13 @@ import org.springframework.stereotype.Service;
 @Service
 public class LabelServiceImpl extends ServiceImpl<LabelMapper, LabelPO> implements ILabelService {
 
+    @Override
+    public boolean getByName(LabelPO labelPO) {
+        QueryWrapper<LabelPO> tWrapper = new QueryWrapper<>();
+        tWrapper.eq("name", labelPO.getName());
+        LabelPO labelPO1 = baseMapper.selectOne(tWrapper);
+        if(labelPO1 == null) return false;
+        labelPO.setId(labelPO1.getId());
+        return true;
+    }
 }

+ 108 - 0
src/main/java/cn/edu/nju/plagdemo/utils/SimpleCache.java

@@ -0,0 +1,108 @@
+package cn.edu.nju.plagdemo.utils;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * ConcurrentHashMap 实现带过期时间的缓存
+ * 主要只在一两个地方用到缓存,自己实现一个比较轻量,上redis太重量级了
+ **/
+public class SimpleCache {
+
+    /**
+     * 预缓存信息
+     */
+    private static final Map<String, Object> CACHE_MAP = new ConcurrentHashMap<>();
+
+    /**
+     * 每个缓存生效时间1小时
+     */
+    public static final long CACHE_HOLD_TIME_1H = 60 * 60 * 1000L;
+
+    /**
+     * 每个缓存生效时间2小时
+     */
+    public static final long CACHE_HOLD_TIME_2H = 2 * 60 * 60 * 1000L;
+
+    /**
+     * 每个缓存生效时间24小时
+     */
+    public static final long CACHE_HOLD_TIME_24H = 24 * 60 * 60 * 1000L;
+
+    /**
+     * 存放一个缓存对象,默认保存时间2小时
+     *
+     * @param cacheName
+     * @param obj
+     */
+    public static void put(String cacheName, Object obj) {
+        put(cacheName, obj, CACHE_HOLD_TIME_2H);
+    }
+
+    /**
+     * 存放一个缓存对象,保存时间为holdTime
+     *
+     * @param cacheName
+     * @param obj
+     * @param holdTime
+     */
+    public static void put(String cacheName, Object obj, long holdTime) {
+        if (checkCacheName(cacheName)) {
+            return;
+        }
+        CACHE_MAP.put(cacheName, obj);
+        //缓存失效时间
+        CACHE_MAP.put(cacheName + "_HoldTime", System.currentTimeMillis() + holdTime);
+    }
+
+    /**
+     * 取出一个缓存对象
+     *
+     * @param cacheName
+     * @return
+     */
+    public static Object get(String cacheName) {
+        if (checkCacheName(cacheName)) {
+            return CACHE_MAP.get(cacheName);
+        }
+        return null;
+    }
+
+    /**
+     * 删除所有缓存
+     */
+    public static void removeAll() {
+        CACHE_MAP.clear();
+    }
+
+    /**
+     * 删除某个缓存
+     *
+     * @param cacheName
+     */
+    public static void remove(String cacheName) {
+        CACHE_MAP.remove(cacheName);
+        CACHE_MAP.remove(cacheName + "_HoldTime");
+    }
+
+    /**
+     * 检查缓存对象是否存在,
+     * 若不存在,则返回false
+     * 若存在,检查其是否已过有效期,如果已经过了则删除该缓存并返回false
+     *
+     * @param cacheName
+     * @return
+     */
+    public static boolean checkCacheName(String cacheName) {
+        Long cacheHoldTime = (Long) CACHE_MAP.get(cacheName + "_HoldTime");
+        if (cacheHoldTime == null || cacheHoldTime == 0L) {
+            return false;
+        }
+        if (cacheHoldTime < System.currentTimeMillis()) {
+            remove(cacheName);
+            return false;
+        }
+        return true;
+    }
+
+}

+ 13 - 2
src/main/resources/application.yml

@@ -12,7 +12,7 @@ spring:
     freemarker:
       cache: false    #页面不加载缓存,修改即时生效
   profiles:
-    active: lxs2
+    active: lxs
 server:
   servlet:
     context-path: /
@@ -21,4 +21,15 @@ springfox:
     enabled: true
 #logging: # 上线的时候取消注释
 #  file:
-#    name: app.log
+#    name: app.log
+
+oss:
+  endpoint: sts.cn-nanjing.aliyuncs.com
+  region: oss-cn-nanjing
+  bucket: plag-file-bucket
+  accesskey:
+    id: LTAI5tHhtNM66nQwRKwyYotL
+    secret: fTznJXTLMfySeMFRWWnghAaPAtaceV
+  role:
+    arn: acs:ram::1807432849970905:role/plagossadmin
+    sessionname: plag-detection

+ 2 - 1
src/main/resources/cheatDetection.sql

@@ -123,7 +123,8 @@ DROP TABLE IF EXISTS `label`;
 CREATE TABLE `label`  (
   `id` int(11) NOT NULL AUTO_INCREMENT,
   `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '标签名(比如coder/eval/手动上传/软工二作业/软工一2023作业,主要是\r\n为了查询方便)\r',
-  PRIMARY KEY (`id`) USING BTREE
+  PRIMARY KEY (`id`) USING BTREE,
+  UNIQUE INDEX `name`(`name`) USING BTREE -- 注意这里加了一个唯一索引
 ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
 
 -- ----------------------------

+ 30 - 0
src/test/java/cn/edu/nju/plagdemo/api/IOssApiTest.java

@@ -0,0 +1,30 @@
+package cn.edu.nju.plagdemo.api;
+
+import cn.edu.nju.plagdemo.model.dto.plag.OssNameAndLinkDTO;
+import cn.edu.nju.plagdemo.model.vo.OssInfoVO;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+class IOssApiTest {
+
+    @Autowired
+    IOssApi ossApi;
+
+    @Test
+    void getSTSToken() {
+        OssInfoVO ossClientInfo = ossApi.getOssClientInfo();
+        System.out.println(ossClientInfo);
+        OssNameAndLinkDTO ossNameAndLink = ossApi.getOssNameAndLink("file", "afile");
+        System.out.println(ossNameAndLink);
+    }
+    @Test
+    void getSTSToken2() {
+        OssInfoVO ossClientInfo = ossApi.getOssClientInfo();
+        OssInfoVO ossClientInfo2 = ossApi.getOssClientInfo();
+        assertEquals(ossClientInfo.getToken(), ossClientInfo2.getToken());
+    }
+}

+ 54 - 0
src/test/java/cn/edu/nju/plagdemo/service/IDetectionFileServiceTest.java

@@ -0,0 +1,54 @@
+package cn.edu.nju.plagdemo.service;
+
+import cn.edu.nju.plagdemo.model.dto.plag.DetectionFileDTO;
+import cn.edu.nju.plagdemo.model.form.plag.BaseFileForm;
+import cn.edu.nju.plagdemo.model.form.plag.DetectionFileForm;
+import cn.edu.nju.plagdemo.model.form.plag.LabelForm;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.annotation.Rollback;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@SpringBootTest
+class IDetectionFileServiceTest {
+
+    @Autowired
+    IDetectionFileService detectionFileService;
+
+    @Test
+    @Rollback(value = true)
+    @Transactional
+    void save() {
+        BaseFileForm baseFileForm = BaseFileForm.builder()
+//                .expiration(-1)
+                .fileType(1)
+                .name("testBaseFile")
+                .build();
+        List<LabelForm> labelForms = new ArrayList<>();
+        labelForms.add(new LabelForm("测试1lable1"));
+        labelForms.add(new LabelForm("测试1lable2"));
+        DetectionFileForm detectionFileForm = DetectionFileForm.builder()
+                .baseFile(baseFileForm)
+                .fileType(1)
+//                .expiration(-1)
+                .hasBaseFile(true)
+                .labelForms(labelForms)
+                .level("{\"key\": \"value\"}")
+                .name("testFile")
+                .build();
+        DetectionFileDTO detectionFileDTO = detectionFileService.save(detectionFileForm);
+        System.out.println(detectionFileDTO);
+        assertNotNull(detectionFileDTO);;
+        assertNotNull(detectionFileDTO.getBaseFile());
+        assertNotNull(detectionFileDTO.getCreatedTime());
+        assertNotNull(detectionFileDTO.getBaseFile().getCreatedTime());
+        assertNotNull(detectionFileDTO.getOssClientInfo());
+        assertNotNull(detectionFileDTO.getOssClientInfo().getToken());
+    }
+}