From 1afafa8818b784e2bd4b6894a94ce74fa9aef1e6 Mon Sep 17 00:00:00 2001 From: ArgonarioD Date: Mon, 11 Jul 2022 21:39:30 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=84=E8=8C=83=E4=BA=86=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=BA=86closeTask=E6=B2=A1=E6=B3=95?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E5=B7=A5=E4=BD=9C=E7=9A=84bug=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E9=99=90=E6=B5=81=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=EF=BC=88=E6=9C=AA=E6=B5=8B=E8=AF=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 10 ++++ .../advice/ExceptionHandlerAdvice.java | 7 +++ .../annotation/RateLimit.java | 29 ++++++++++ .../projectmanagement/aop/RateLimitAOP.java | 53 ++++++++++++++++++ .../controller/AnnouncementController.java | 10 ++-- .../controller/ProjectController.java | 15 ++--- .../controller/ProjectGroupController.java | 14 ++--- .../controller/StaffController.java | 12 ++-- .../controller/TaskController.java | 15 ++--- .../rmdjzz/projectmanagement/entity/Task.java | 1 + .../exception/TooManyRequestException.java | 11 ++++ .../interceptor/TokenInterceptor.java | 4 +- .../service/ITaskService.java | 2 +- .../service/impl/StaffServiceImpl.java | 2 +- .../service/impl/TaskServiceImpl.java | 7 ++- .../projectmanagement/utils/FileUtils.java | 25 ++++++--- .../projectmanagement/utils/TokenUtils.java | 1 + src/main/resources/static/账户导入模板.xlsx | Bin 0 -> 9942 bytes 18 files changed, 172 insertions(+), 46 deletions(-) create mode 100644 src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/annotation/RateLimit.java create mode 100644 src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/aop/RateLimitAOP.java create mode 100644 src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/exception/TooManyRequestException.java create mode 100644 src/main/resources/static/账户导入模板.xlsx diff --git a/pom.xml b/pom.xml index 4dfca6b..52d2888 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,10 @@ org.springframework.boot spring-boot-starter-validation + + org.springframework.boot + spring-boot-starter-aop + org.springframework.boot @@ -71,6 +75,7 @@ spring-security-test test + com.baomidou mybatis-plus-boot-starter @@ -117,6 +122,11 @@ poi-ooxml 5.2.2 + + com.google.guava + guava + 31.1-jre + diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/advice/ExceptionHandlerAdvice.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/advice/ExceptionHandlerAdvice.java index c4ca39e..2ce4e01 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/advice/ExceptionHandlerAdvice.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/advice/ExceptionHandlerAdvice.java @@ -2,6 +2,7 @@ package cn.edu.hfut.rmdjzz.projectmanagement.advice; import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException; import cn.edu.hfut.rmdjzz.projectmanagement.exception.ForbiddenException; +import cn.edu.hfut.rmdjzz.projectmanagement.exception.TooManyRequestException; import cn.edu.hfut.rmdjzz.projectmanagement.exception.UnauthorizedException; import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.ResponseMap; import lombok.extern.slf4j.Slf4j; @@ -51,4 +52,10 @@ public class ExceptionHandlerAdvice { .collect(Collectors.joining(",")) ); } + + @ExceptionHandler(TooManyRequestException.class) + @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS) + public ResponseMap handleTooManyRequestException(TooManyRequestException e) { + return ResponseMap.of(HttpStatus.TOO_MANY_REQUESTS.value(), e.getMessage()); + } } diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/annotation/RateLimit.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/annotation/RateLimit.java new file mode 100644 index 0000000..a971953 --- /dev/null +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/annotation/RateLimit.java @@ -0,0 +1,29 @@ +package cn.edu.hfut.rmdjzz.projectmanagement.annotation; + +import java.lang.annotation.*; +import java.util.concurrent.TimeUnit; + +/** + * @author 佘语殊 + * @since 2022/7/11 16:57 + */ +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Target({ElementType.METHOD}) +public @interface RateLimit { + + /** + * 流量控制令牌桶标识符 + */ + String key() default ""; + + int permitsPerSecond(); + + long timeout() default 0; + + TimeUnit timeUnit() default TimeUnit.MILLISECONDS; + + long maxBurstSeconds() default 1; + + String msg() default "系统繁忙,请稍后再试"; +} diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/aop/RateLimitAOP.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/aop/RateLimitAOP.java new file mode 100644 index 0000000..84f0470 --- /dev/null +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/aop/RateLimitAOP.java @@ -0,0 +1,53 @@ +package cn.edu.hfut.rmdjzz.projectmanagement.aop; + +import cn.edu.hfut.rmdjzz.projectmanagement.annotation.RateLimit; +import cn.edu.hfut.rmdjzz.projectmanagement.exception.TooManyRequestException; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.RateLimiter; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; + +/** + * @author 佘语殊 + * @since 2022/7/11 17:23 + */ +@SuppressWarnings("UnstableApiUsage") +@Aspect +@Component +public class RateLimitAOP { + private final Map rateLimitMap = Maps.newConcurrentMap(); + + @Around("@annotation(cn.edu.hfut.rmdjzz.projectmanagement.annotation.RateLimit)") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + RateLimit limit = method.getAnnotation(RateLimit.class); + if (limit == null) { + return joinPoint.proceed(); + } + + String key = limit.key(); + RateLimiter limiter = rateLimitMap.get(key); + if (limiter == null) { + limiter = RateLimiter.create(limit.permitsPerSecond()); + Class clazz = limiter.getClass(); + //TODO: DEBUG TEST + Field burstSecondsField = clazz.getDeclaredField("maxBurstSeconds"); + burstSecondsField.setAccessible(true); + burstSecondsField.set(limiter, limit.maxBurstSeconds()); + rateLimitMap.put(key, limiter); + } + + if (!limiter.tryAcquire(limit.timeout(), limit.timeUnit())) { + throw new TooManyRequestException(limit.msg()); + } + return joinPoint.proceed(); + } +} diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/AnnouncementController.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/AnnouncementController.java index 9954183..35b1137 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/AnnouncementController.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/AnnouncementController.java @@ -29,7 +29,7 @@ public class AnnouncementController { @SneakyThrows @GetMapping - public ResponseList getAnnouncementList(@RequestHeader("Token") String token, @PathVariable Integer projectId) { + public ResponseList getAnnouncementList(@RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId) { if (projectGroupService.getProjectAccessLevel(token, projectId) == 0) { throw new ForbiddenException(IProjectGroupService.UNABLE_TO_ACCESS_PROJECT); } @@ -39,7 +39,7 @@ public class AnnouncementController { @SneakyThrows @GetMapping("/{announcementId}") public ResponseMap getAnnouncementById( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @PathVariable Long announcementId ) { @@ -52,7 +52,7 @@ public class AnnouncementController { @SneakyThrows @PostMapping public ResponseMap createAnnouncement( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @RequestBody Announcement announcement ) { @@ -73,7 +73,7 @@ public class AnnouncementController { /*@SneakyThrows @PutMapping("/{announcementId}") public ResponseMap modifyAnnouncement( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @PathVariable Long announcementId, @RequestBody Announcement announcement @@ -88,7 +88,7 @@ public class AnnouncementController { @SneakyThrows @DeleteMapping("/{announcementId}") public ResponseMap deleteAnnouncement( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @PathVariable Long announcementId ) { diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/ProjectController.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/ProjectController.java index 8a550d9..02a2c19 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/ProjectController.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/ProjectController.java @@ -5,6 +5,7 @@ import cn.edu.hfut.rmdjzz.projectmanagement.entity.dto.ProjectDTO; import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException; import cn.edu.hfut.rmdjzz.projectmanagement.service.IProjectGroupService; import cn.edu.hfut.rmdjzz.projectmanagement.service.IProjectService; +import cn.edu.hfut.rmdjzz.projectmanagement.utils.TokenUtils; import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.RequestPage; import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.ResponseList; import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.ResponseMap; @@ -35,7 +36,7 @@ public class ProjectController { @SneakyThrows @GetMapping public ResponseList getProjectListOfStaff( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @Valid RequestPage page, @Parameter(description = "参数列表见Project实体类,时间可以用xxxxStart与xxxxEnd来确定区间" , required = true) @RequestParam("paramMap") Map paramMap @@ -47,7 +48,7 @@ public class ProjectController { @SneakyThrows @GetMapping("/{projectId}") public ResponseMap getOneProjectBasicInfo( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable("projectId") Integer projectId ) { if (projectGroupService.getProjectAccessLevel(token, projectId) == 0) { @@ -59,7 +60,7 @@ public class ProjectController { @Operation(description = "根据Token获取该员工的Project数") @SneakyThrows @GetMapping("/count") - public ResponseMap getProjectNumOfStaff(@RequestHeader("Token") String token) { + public ResponseMap getProjectNumOfStaff(@RequestHeader(TokenUtils.HEADER_TOKEN) String token) { return ResponseMap.ofSuccess() .put("totalNum", projectService.countMyProjects(token)); } @@ -67,7 +68,7 @@ public class ProjectController { @SneakyThrows @PostMapping("/complete") public ResponseMap completeProject( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @Parameter(description = "只需要传projectId即可,例:{\"projectId\": 1}") @RequestBody Map map ) { @@ -78,7 +79,7 @@ public class ProjectController { @SneakyThrows @PostMapping - public ResponseMap createProject(@RequestHeader("Token") String token, @RequestBody Project project) { + public ResponseMap createProject(@RequestHeader(TokenUtils.HEADER_TOKEN) String token, @RequestBody Project project) { projectService.createProject(token, project); return ResponseMap.ofSuccess(); } @@ -86,7 +87,7 @@ public class ProjectController { @SneakyThrows @PutMapping("/{projectId}") public ResponseMap updateProject( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @RequestBody Project project ) { @@ -100,7 +101,7 @@ public class ProjectController { @SneakyThrows @GetMapping("/{projectId}/stats") public ResponseMap getProjectProcess( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId ) { return ResponseMap.ofSuccess(projectService.getProjectProcess(token, projectId)); diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/ProjectGroupController.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/ProjectGroupController.java index 3bf7fcd..ca553a8 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/ProjectGroupController.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/ProjectGroupController.java @@ -43,7 +43,7 @@ public class ProjectGroupController { @GetMapping public ResponseList getGroupMembers( @PathVariable Integer projectId, - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, RequestPage page ) { if (projectGroupService.getProjectAccessLevel(token, projectId) == 0) { @@ -59,7 +59,7 @@ public class ProjectGroupController { @SneakyThrows @GetMapping("/{staffId}") public ResponseMap getDesignatedStaffPosition( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @PathVariable Integer staffId ) { @@ -77,7 +77,7 @@ public class ProjectGroupController { @SneakyThrows @PostMapping public ResponseMap addGroupMember( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @Parameter(description = "只传staffUsername和projectStaffPosition") @RequestBody GroupPositionVO groupPosition ) { @@ -91,7 +91,7 @@ public class ProjectGroupController { @SneakyThrows @PutMapping("/{staffId}") public ResponseMap modifyDesignatedStaffPosition( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @PathVariable Integer staffId, @Parameter(description = "在body中只传projectStaffPosition") @RequestBody GroupPositionVO groupPosition @@ -105,7 +105,7 @@ public class ProjectGroupController { @SneakyThrows @GetMapping("/stats") public ResponseMap getGroupPositionsStatistics( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId ) { return ResponseMap.ofSuccess(projectGroupService.collectStatsForGroupPositions(token, projectId)); @@ -114,7 +114,7 @@ public class ProjectGroupController { @SneakyThrows @GetMapping("/{staffId}/stats") public ResponseList getProjectProcessOfStaff( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @PathVariable Integer staffId ) { @@ -129,7 +129,7 @@ public class ProjectGroupController { @SneakyThrows @PutMapping("/{staffId}/transfer") public ResponseMap transferStaffTasks( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId, @PathVariable Integer staffId, @RequestBody Map transferMap diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/StaffController.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/StaffController.java index eec249c..0d3ceb6 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/StaffController.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/StaffController.java @@ -39,7 +39,7 @@ public class StaffController { @SneakyThrows @PostMapping("/logout") - public ResponseMap logout(@RequestHeader("Token") String token) { + public ResponseMap logout(@RequestHeader(TokenUtils.HEADER_TOKEN) String token) { if (staffService.logout(token)) { return ResponseMap.ofSuccess("登出成功"); } @@ -49,8 +49,8 @@ public class StaffController { @SneakyThrows @PostMapping(value = "/import") public ResponseMap importStaffs( - @RequestHeader("Token") String token, - @RequestHeader("File-Digest") String digest, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, + @RequestHeader(FileUtils.HEADER_FILE_DIGEST) String digest, @RequestParam("uploadFile") MultipartFile uploadFile ) { if (null == uploadFile) { @@ -69,15 +69,15 @@ public class StaffController { @SneakyThrows @GetMapping("/import/template") - public ResponseMap downloadTemplate( - @RequestHeader("Token") String token, + public void downloadTemplate( + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, HttpServletResponse response ) { if (TokenUtils.getStaffGlobalLevel(token) > 2) { throw new ForbiddenException(ForbiddenException.UNABLE_TO_OPERATE); } if (FileUtils.downloadResource("static/账户导入模板.xlsx", response)) { - return ResponseMap.ofSuccess(); + return; } throw new BadRequestException(BadRequestException.OPERATE_FAILED); } diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/TaskController.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/TaskController.java index a4adf14..3c3e718 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/TaskController.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/controller/TaskController.java @@ -5,6 +5,7 @@ import cn.edu.hfut.rmdjzz.projectmanagement.entity.dto.TaskDTO; import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException; import cn.edu.hfut.rmdjzz.projectmanagement.service.IProjectService; import cn.edu.hfut.rmdjzz.projectmanagement.service.ITaskService; +import cn.edu.hfut.rmdjzz.projectmanagement.utils.TokenUtils; import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.ResponseList; import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.ResponseMap; import lombok.SneakyThrows; @@ -27,7 +28,7 @@ public class TaskController { @SneakyThrows @GetMapping("/{fatherId}/subtask") public ResponseList getSubTaskList( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable("projectId") Integer projectId, @PathVariable("fatherId") Long fatherId ) { @@ -37,7 +38,7 @@ public class TaskController { @SneakyThrows @GetMapping("/mine") - public ResponseList getMyTasks(@RequestHeader("Token") String token, @PathVariable("projectId") Integer projectId) { + public ResponseList getMyTasks(@RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable("projectId") Integer projectId) { List result = taskService.listMyTasks(token, projectId); return ResponseList.ofSuccess(result); } @@ -45,7 +46,7 @@ public class TaskController { @SneakyThrows @GetMapping("/subtask/exist") public ResponseMap existSubTask( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable("projectId") Integer projectId, @RequestParam("taskId") Long taskId ) { @@ -56,7 +57,7 @@ public class TaskController { @SneakyThrows @PostMapping public ResponseMap createTask( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable("projectId") Integer projectId, @RequestBody Task task ) { @@ -70,7 +71,7 @@ public class TaskController { @SneakyThrows @PutMapping("/{taskId}") public ResponseMap modifyTask( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable("projectId") Integer projectId, @PathVariable("taskId") Long taskId, @RequestBody Task task @@ -86,7 +87,7 @@ public class TaskController { @SneakyThrows @DeleteMapping("/{taskId}") public ResponseMap deleteTaskAndSubTask( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable("projectId") Integer projectId, @PathVariable("taskId") Long taskId ) { @@ -99,7 +100,7 @@ public class TaskController { @SneakyThrows @GetMapping("/stats") public ResponseMap getTaskTrend( - @RequestHeader("Token") String token, + @RequestHeader(TokenUtils.HEADER_TOKEN) String token, @PathVariable Integer projectId ) { if(!projectService.checkOpenStatus(projectId)) { diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/entity/Task.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/entity/Task.java index 3ea2b55..2f77fac 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/entity/Task.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/entity/Task.java @@ -57,6 +57,7 @@ public class Task { "demandSource:需求来源 (String), estimatedManHours:预估工时 (Integer), severity:严重程度 (String), recurrenceProbability:复现概率 (String)") @TableField(typeHandler = JacksonTypeHandler.class) private Map attachedInfo; + private Integer childrenCount; @TableField("is_deleted") @TableLogic private Boolean deleted; diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/exception/TooManyRequestException.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/exception/TooManyRequestException.java new file mode 100644 index 0000000..8d8cefb --- /dev/null +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/exception/TooManyRequestException.java @@ -0,0 +1,11 @@ +package cn.edu.hfut.rmdjzz.projectmanagement.exception; + +/** + * @author 佘语殊 + * @since 2022/7/11 17:35 + */ +public class TooManyRequestException extends Exception { + public TooManyRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/interceptor/TokenInterceptor.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/interceptor/TokenInterceptor.java index bc65bf2..294c5f1 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/interceptor/TokenInterceptor.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/interceptor/TokenInterceptor.java @@ -27,7 +27,7 @@ public class TokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws TokenException { System.out.println(httpServletRequest.getRequestURL() + " " + httpServletRequest.getMethod()); - String token = httpServletRequest.getHeader("Token"); + String token = httpServletRequest.getHeader(TokenUtils.HEADER_TOKEN); if (null == token || "".equals(token.trim())) { throw new TokenException("缺少Token"); } @@ -50,7 +50,7 @@ public class TokenInterceptor implements HandlerInterceptor { Objects.requireNonNull(TokenUtils.getDuration(token)), TimeUnit.SECONDS ); } - httpServletResponse.setHeader("Token", newToken); + httpServletResponse.setHeader(TokenUtils.HEADER_TOKEN, newToken); return true; } } diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/ITaskService.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/ITaskService.java index c16e35f..d8702a4 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/ITaskService.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/ITaskService.java @@ -45,7 +45,7 @@ public interface ITaskService extends IService { Task modifyTask(String token, Task task) throws BadRequestException, ForbiddenException; - Map> getProjectTaskTrend(String token, Integer projectId) throws BadRequestException, ForbiddenException; + Map> getProjectTaskTrend(String token, Integer projectId) throws ForbiddenException; Boolean transferStaffTasks(String token, Integer projectId, Integer transferredStaffId, Map transferMap) throws ForbiddenException, BadRequestException; diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/impl/StaffServiceImpl.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/impl/StaffServiceImpl.java index ba7be16..ae5ee78 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/impl/StaffServiceImpl.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/impl/StaffServiceImpl.java @@ -62,7 +62,7 @@ public class StaffServiceImpl extends ServiceImpl implements Objects.requireNonNull(TokenUtils.getDuration(token)), TimeUnit.SECONDS ); return new MapBuilder() - .put("Token", token) + .put(TokenUtils.HEADER_TOKEN, token) .putAll(BeanUtils.beanToMap(staff, false)) .build(); } diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/impl/TaskServiceImpl.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/impl/TaskServiceImpl.java index e121674..6cdc9b7 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/impl/TaskServiceImpl.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/service/impl/TaskServiceImpl.java @@ -246,13 +246,14 @@ public class TaskServiceImpl extends ServiceImpl implements IT throw new BadRequestException("还有子工作尚未完成"); } try { + boolean closed = false; if (typeChangeValue != 0) { task.setTaskClosedTime(LocalDateTime.now()); } if (typeChangeValue == 2) { - closeTaskAndSubTask(token, task.getTaskProjectId(), task.getTaskId()); + closed = closeTaskAndSubTask(token, task.getTaskProjectId(), task.getTaskId()); } - if (baseMapper.update(task, Wrappers.lambdaQuery().eq(Task::getTaskId, task.getTaskId())) == 0) { + if (!closed && baseMapper.update(task, Wrappers.lambdaQuery().eq(Task::getTaskId, task.getTaskId())) == 0) { throw new BadRequestException(BadRequestException.OPERATE_FAILED); } } catch (Exception e) { @@ -263,7 +264,7 @@ public class TaskServiceImpl extends ServiceImpl implements IT } @Override - public Map> getProjectTaskTrend(String token, Integer projectId) throws BadRequestException, ForbiddenException { + public Map> getProjectTaskTrend(String token, Integer projectId) throws ForbiddenException { if (projectGroupService.getProjectAccessLevel(token, projectId) == 0) { throw new ForbiddenException(ForbiddenException.UNABLE_TO_OPERATE); } diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/utils/FileUtils.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/utils/FileUtils.java index 6a5420b..4ea5e9f 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/utils/FileUtils.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/utils/FileUtils.java @@ -6,25 +6,36 @@ import org.springframework.util.MimeTypeUtils; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; -import java.io.*; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; /** * @author 佘语殊 * @since 2022/7/11 9:32 */ public class FileUtils { + + public static final String HEADER_FILE_DIGEST = "File-Digest"; + + /** + * 直接在响应体中用八位字节流输出文件内容,并在响应头中加入File-Digest作为md5文件摘要校验码 + *

+ * 在Controller层调用时需要返回void + */ public static Boolean downloadResource(String resourceName, HttpServletResponse response) throws IOException { - @Cleanup InputStream is = FileUtils.class.getResourceAsStream(resourceName); - if (is == null) { - throw new FileNotFoundException("该文件不存在"); - } - @Cleanup BufferedInputStream bis = new BufferedInputStream(is); + @Cleanup InputStream is = FileUtils.class.getClassLoader().getResourceAsStream(resourceName); + BufferedInputStream bis = new BufferedInputStream(is); + bis.mark(bis.available() + 1); response.setContentType(MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE); response.setCharacterEncoding("UTF-8"); ServletOutputStream out = response.getOutputStream(); - response.addHeader("File-Digest", DigestUtils.md5DigestAsHex(bis)); + response.addHeader(HEADER_FILE_DIGEST, DigestUtils.md5DigestAsHex(bis)); + bis.reset(); @Cleanup BufferedOutputStream bos = new BufferedOutputStream(out); bis.transferTo(bos); + bis.close(); bos.flush(); return true; } diff --git a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/utils/TokenUtils.java b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/utils/TokenUtils.java index c41330e..c2da352 100644 --- a/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/utils/TokenUtils.java +++ b/src/main/java/cn/edu/hfut/rmdjzz/projectmanagement/utils/TokenUtils.java @@ -19,6 +19,7 @@ import java.util.Date; public final class TokenUtils { private final static String PV_KEY = "SignedByRMDJZZ"; + public final static String HEADER_TOKEN = "Token"; private final static String STAFF_USERNAME = "staffUsername"; private final static String STAFF_ID = "staffId"; private final static String STAFF_GLOBAL_LEVEL = "staffGlobalLevel"; diff --git a/src/main/resources/static/账户导入模板.xlsx b/src/main/resources/static/账户导入模板.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..35cd573fa03da06a3db03730f95f8a03ded46bc3 GIT binary patch literal 9942 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