适配了Task新的数据结构,完成了限流控制并通过了测试,实现了多次登录失败banIP,为MybatisPlus添加了防全表更新及删除的插件

master
ArgonarioD 2022-07-12 12:01:04 +08:00
parent 8cc1111489
commit 1a001fb599
18 changed files with 153 additions and 31 deletions

View File

@ -23,3 +23,7 @@ group /project/{projectId}/group
--- ---
导入账户 大权限 导入账户 大权限
工作进度统计
个人/项目工作项具体完成统计
工作项时间进度统计

View File

@ -26,26 +26,28 @@ public class ExceptionHandlerAdvice {
@ResponseStatus(HttpStatus.UNAUTHORIZED) @ResponseStatus(HttpStatus.UNAUTHORIZED)
public ResponseMap handleUnauthorizedException(Exception e) { public ResponseMap handleUnauthorizedException(Exception e) {
// log.error(ExceptionUtils.getStackTrace(e)); // log.error(ExceptionUtils.getStackTrace(e));
// log.error(e.getMessage()); log.error(e.getMessage(), e);
return ResponseMap.of(HttpStatus.UNAUTHORIZED.value(), e.getMessage()); return ResponseMap.of(HttpStatus.UNAUTHORIZED.value(), e.getMessage());
} }
@ExceptionHandler(BadRequestException.class) @ExceptionHandler(BadRequestException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseMap handleBadRequestException(BadRequestException e) { public ResponseMap handleBadRequestException(BadRequestException e) {
// log.error(e.getMessage()); log.error(e.getMessage(), e);
return ResponseMap.of(HttpStatus.BAD_REQUEST.value(), e.getMessage()); return ResponseMap.of(HttpStatus.BAD_REQUEST.value(), e.getMessage());
} }
@ExceptionHandler(ForbiddenException.class) @ExceptionHandler(ForbiddenException.class)
@ResponseStatus(HttpStatus.FORBIDDEN) @ResponseStatus(HttpStatus.FORBIDDEN)
public ResponseMap handleForbiddenException(ForbiddenException e) { public ResponseMap handleForbiddenException(ForbiddenException e) {
log.error(e.getMessage(), e);
return ResponseMap.of(HttpStatus.FORBIDDEN.value(), e.getMessage()); return ResponseMap.of(HttpStatus.FORBIDDEN.value(), e.getMessage());
} }
@ExceptionHandler(BindException.class) @ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseMap handleBindException(BindException e) { public ResponseMap handleBindException(BindException e) {
log.error(e.getMessage(), e);
return ResponseMap.of(HttpStatus.BAD_REQUEST.value(), return ResponseMap.of(HttpStatus.BAD_REQUEST.value(),
e.getAllErrors().stream() e.getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage) .map(DefaultMessageSourceResolvable::getDefaultMessage)
@ -56,6 +58,7 @@ public class ExceptionHandlerAdvice {
@ExceptionHandler(TooManyRequestException.class) @ExceptionHandler(TooManyRequestException.class)
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS) @ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public ResponseMap handleTooManyRequestException(TooManyRequestException e) { public ResponseMap handleTooManyRequestException(TooManyRequestException e) {
log.error(e.getMessage(), e);
return ResponseMap.of(HttpStatus.TOO_MANY_REQUESTS.value(), e.getMessage()); return ResponseMap.of(HttpStatus.TOO_MANY_REQUESTS.value(), e.getMessage());
} }
} }

View File

@ -7,6 +7,7 @@ import java.util.concurrent.TimeUnit;
* @author * @author
* @since 2022/7/11 16:57 * @since 2022/7/11 16:57
*/ */
//TODO: 加到代码里
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})

View File

@ -38,7 +38,6 @@ public class RateLimitAOP {
if (limiter == null) { if (limiter == null) {
limiter = RateLimiter.create(limit.permitsPerSecond()); limiter = RateLimiter.create(limit.permitsPerSecond());
Class<? extends RateLimiter> clazz = limiter.getClass(); Class<? extends RateLimiter> clazz = limiter.getClass();
//TODO: DEBUG TEST
Field burstSecondsField = clazz.getDeclaredField("maxBurstSeconds"); Field burstSecondsField = clazz.getDeclaredField("maxBurstSeconds");
burstSecondsField.setAccessible(true); burstSecondsField.setAccessible(true);
burstSecondsField.set(limiter, limit.maxBurstSeconds()); burstSecondsField.set(limiter, limit.maxBurstSeconds());

View File

@ -2,6 +2,7 @@ package cn.edu.hfut.rmdjzz.projectmanagement.config;
import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
@ -23,6 +24,7 @@ public class MybatisPlusConfig {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor; return interceptor;
} }
} }

View File

@ -1,5 +1,6 @@
package cn.edu.hfut.rmdjzz.projectmanagement.controller; package cn.edu.hfut.rmdjzz.projectmanagement.controller;
import cn.edu.hfut.rmdjzz.projectmanagement.annotation.RateLimit;
import cn.edu.hfut.rmdjzz.projectmanagement.entity.Project; import cn.edu.hfut.rmdjzz.projectmanagement.entity.Project;
import cn.edu.hfut.rmdjzz.projectmanagement.entity.dto.ProjectDTO; import cn.edu.hfut.rmdjzz.projectmanagement.entity.dto.ProjectDTO;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException; import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException;
@ -33,6 +34,7 @@ public class ProjectController {
private IProjectGroupService projectGroupService; private IProjectGroupService projectGroupService;
@Operation(summary = "根据Token获取该员工的ProjectList") @Operation(summary = "根据Token获取该员工的ProjectList")
//@RateLimit(permitsPerSecond = 1, maxBurstSeconds = 5)
@SneakyThrows @SneakyThrows
@GetMapping @GetMapping
public ResponseList<ProjectDTO> getProjectListOfStaff( public ResponseList<ProjectDTO> getProjectListOfStaff(

View File

@ -111,6 +111,7 @@ public class ProjectGroupController {
return ResponseMap.ofSuccess(projectGroupService.collectStatsForGroupPositions(token, projectId)); return ResponseMap.ofSuccess(projectGroupService.collectStatsForGroupPositions(token, projectId));
} }
//FIXME: DELETE
@SneakyThrows @SneakyThrows
@GetMapping("/{staffId}/stats") @GetMapping("/{staffId}/stats")
public ResponseList<StaffProcessDTO> getProjectProcessOfStaff( public ResponseList<StaffProcessDTO> getProjectProcessOfStaff(

View File

@ -7,6 +7,7 @@ import cn.edu.hfut.rmdjzz.projectmanagement.exception.TokenException;
import cn.edu.hfut.rmdjzz.projectmanagement.service.IStaffService; import cn.edu.hfut.rmdjzz.projectmanagement.service.IStaffService;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.FileUtils; import cn.edu.hfut.rmdjzz.projectmanagement.utils.FileUtils;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.TokenUtils; import cn.edu.hfut.rmdjzz.projectmanagement.utils.TokenUtils;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.HttpUtils;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.ResponseMap; import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.ResponseMap;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@ -15,6 +16,7 @@ import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.Objects; import java.util.Objects;
@ -32,9 +34,12 @@ public class StaffController {
@PostMapping("/login") @PostMapping("/login")
public ResponseMap login( public ResponseMap login(
@Parameter(description = "只需要传入staffUsername和staffPassword两个属性即可staffPassword需要md5加密后传输") @Parameter(description = "只需要传入staffUsername和staffPassword两个属性即可staffPassword需要md5加密后传输")
@RequestBody Staff staff @RequestBody Staff staff,
HttpServletRequest request
) { ) {
return ResponseMap.ofSuccess("登录成功", staffService.login(staff.getStaffUsername(), staff.getStaffPassword())); String requestIpAddress = HttpUtils.getRequestIpAddress(request);
return ResponseMap.ofSuccess("登录成功",
staffService.login(requestIpAddress, staff.getStaffUsername(), staff.getStaffPassword()));
} }
@SneakyThrows @SneakyThrows

View File

@ -25,6 +25,7 @@ public class TaskController {
private ITaskService taskService; private ITaskService taskService;
@Autowired @Autowired
private IProjectService projectService; private IProjectService projectService;
@SneakyThrows @SneakyThrows
@GetMapping("/{fatherId}/subtask") @GetMapping("/{fatherId}/subtask")
public ResponseList<TaskDTO> getSubTaskList( public ResponseList<TaskDTO> getSubTaskList(
@ -61,7 +62,7 @@ public class TaskController {
@PathVariable("projectId") Integer projectId, @PathVariable("projectId") Integer projectId,
@RequestBody Task task @RequestBody Task task
) { ) {
if(!projectService.checkOpenStatus(projectId)) if (!projectService.checkOpenStatus(projectId))
throw new BadRequestException(IProjectService.PROJECT_UNOPENED); throw new BadRequestException(IProjectService.PROJECT_UNOPENED);
task.setTaskProjectId(projectId); task.setTaskProjectId(projectId);
taskService.insertTask(token, task); taskService.insertTask(token, task);
@ -76,7 +77,7 @@ public class TaskController {
@PathVariable("taskId") Long taskId, @PathVariable("taskId") Long taskId,
@RequestBody Task task @RequestBody Task task
) { ) {
if(!projectService.checkOpenStatus(projectId)) if (!projectService.checkOpenStatus(projectId))
throw new BadRequestException(IProjectService.PROJECT_UNOPENED); throw new BadRequestException(IProjectService.PROJECT_UNOPENED);
task.setTaskProjectId(projectId); task.setTaskProjectId(projectId);
task.setTaskId(taskId); task.setTaskId(taskId);
@ -91,22 +92,32 @@ public class TaskController {
@PathVariable("projectId") Integer projectId, @PathVariable("projectId") Integer projectId,
@PathVariable("taskId") Long taskId @PathVariable("taskId") Long taskId
) { ) {
if(!projectService.checkOpenStatus(projectId)) if (!projectService.checkOpenStatus(projectId))
throw new BadRequestException(IProjectService.PROJECT_UNOPENED); throw new BadRequestException(IProjectService.PROJECT_UNOPENED);
taskService.deleteTaskAndSubTask(token, projectId, taskId); taskService.deleteTaskAndSubTask(token, projectId, taskId);
return ResponseMap.ofSuccess(); return ResponseMap.ofSuccess();
} }
@SneakyThrows @SneakyThrows
@GetMapping("/stats") @GetMapping("/stats/trend")
public ResponseMap getTaskTrend( public ResponseMap getTaskTrend(
@RequestHeader(TokenUtils.HEADER_TOKEN) String token, @RequestHeader(TokenUtils.HEADER_TOKEN) String token,
@PathVariable Integer projectId @PathVariable Integer projectId
) { ) {
if(!projectService.checkOpenStatus(projectId)) { if (!projectService.checkOpenStatus(projectId)) {
throw new BadRequestException(IProjectService.PROJECT_UNOPENED); throw new BadRequestException(IProjectService.PROJECT_UNOPENED);
} }
return ResponseMap.ofSuccess("查询成功", taskService.getProjectTaskTrend(token, projectId)); return ResponseMap.ofSuccess("查询成功", taskService.getProjectTaskTrend(token, projectId));
} }
//TODO:
@GetMapping({"/stats", "/stats/{staffId}"})
public ResponseMap getProjectStatistics(
@RequestHeader(TokenUtils.HEADER_TOKEN) String token,
@PathVariable Integer projectId,
@PathVariable(required = false) Integer staffId
) {
return ResponseMap.ofSuccess();
}
} }

View File

@ -27,7 +27,6 @@ public class TaskDTO {
private LocalDateTime taskClosedTime; private LocalDateTime taskClosedTime;
private Integer taskPriority; private Integer taskPriority;
private String taskDescription; private String taskDescription;
@TableField(typeHandler = JacksonTypeHandler.class)
private Map<String, Object> attachedInfo; private Map<String, Object> attachedInfo;
private Boolean hasChildren; private Integer childrenCount;
} }

View File

@ -17,7 +17,7 @@ public interface IStaffService extends IService<Staff> {
String STAFF_DOES_NOT_EXIST = "用户不存在"; String STAFF_DOES_NOT_EXIST = "用户不存在";
Map<String, Object> login(String username, String password) throws BadRequestException, TokenException; Map<String, Object> login(String requestIpAddress, String username, String password) throws BadRequestException, TokenException, ForbiddenException;
Boolean logout(String token) throws TokenException; Boolean logout(String token) throws TokenException;

View File

@ -7,6 +7,7 @@ import cn.edu.hfut.rmdjzz.projectmanagement.mapper.StaffMapper;
import cn.edu.hfut.rmdjzz.projectmanagement.service.IStaffService; import cn.edu.hfut.rmdjzz.projectmanagement.service.IStaffService;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.BeanUtils; import cn.edu.hfut.rmdjzz.projectmanagement.utils.BeanUtils;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.MapBuilder; import cn.edu.hfut.rmdjzz.projectmanagement.utils.MapBuilder;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.TimeUtils;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.TokenUtils; import cn.edu.hfut.rmdjzz.projectmanagement.utils.TokenUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@ -35,26 +36,44 @@ import java.util.concurrent.TimeUnit;
@Service @Service
public class StaffServiceImpl extends ServiceImpl<StaffMapper, Staff> implements IStaffService { public class StaffServiceImpl extends ServiceImpl<StaffMapper, Staff> implements IStaffService {
private static final Long tokenDuration = 5 * 60 * 60L; private static final Long tokenDuration = 5 * 60 * 60L;
private static final int LOGIN_TRY_COUNT_TIMEOUT = 15; //minutes
private static final int LOGIN_MAX_TRY_COUNT = 5;
@Autowired @Autowired
private RedisTemplate<Object, Object> redisTemplate; private RedisTemplate<Object, Object> redisTemplate;
//TODO: ban掉多次登录失败的IP @SuppressWarnings("ConstantConditions")
@Override @Override
public Map<String, Object> login(String staffUsername, String password) throws BadRequestException { public Map<String, Object> login(String requestIpAddress, String staffUsername, String password) throws BadRequestException, ForbiddenException {
if (Boolean.FALSE.equals(redisTemplate.hasKey(requestIpAddress))) {
redisTemplate.opsForValue().set(requestIpAddress, 0, LOGIN_TRY_COUNT_TIMEOUT, TimeUnit.MINUTES);
}
int loginCount = (int) redisTemplate.opsForValue().get(requestIpAddress);
if (loginCount >= LOGIN_MAX_TRY_COUNT) {
throw new ForbiddenException(String.format("还需要等待%s才能登录"
, TimeUtils.secondsFormat(redisTemplate.getExpire(requestIpAddress))));
}
if (staffUsername == null || staffUsername.strip().length() == 0) if (staffUsername == null || staffUsername.strip().length() == 0)
throw new BadRequestException("用户名为空"); //throw new BadRequestException("用户名为空");
else if (!staffUsername.equals(staffUsername.replaceAll("[^a-zA-Z0-9]", ""))) throwLoginException(requestIpAddress, loginCount);
throw new BadRequestException("用户名格式错误"); if (!staffUsername.equals(staffUsername.replaceAll("[^a-zA-Z0-9]", "")))
else if (password == null || password.trim().length() != 32) //throw new BadRequestException("用户名格式错误");
throw new BadRequestException("密码格式错误"); throwLoginException(requestIpAddress, loginCount);
if (password == null || password.trim().length() != 32)
//throw new BadRequestException("密码格式错误");
throwLoginException(requestIpAddress, loginCount);
Staff staff = getOne(Wrappers.<Staff>lambdaQuery().eq(Staff::getStaffUsername, staffUsername)); Staff staff = getOne(Wrappers.<Staff>lambdaQuery().eq(Staff::getStaffUsername, staffUsername));
if (staff == null) if (staff == null)
throw new BadRequestException(STAFF_DOES_NOT_EXIST); //throw new BadRequestException(STAFF_DOES_NOT_EXIST);
throwLoginException(requestIpAddress, loginCount);
password = DigestUtils.md5DigestAsHex((password + staff.getStaffSalt()).getBytes()); password = DigestUtils.md5DigestAsHex((password + staff.getStaffSalt()).getBytes());
if (!staff.getStaffPassword().equals(password)) if (!staff.getStaffPassword().equals(password)) {
throw new BadRequestException("密码错误"); throwLoginException(requestIpAddress, loginCount);
}
String token = TokenUtils.getToken(staff.getStaffUsername(), staff.getStaffId(), staff.getStaffGlobalLevel(), tokenDuration); String token = TokenUtils.getToken(staff.getStaffUsername(), staff.getStaffId(), staff.getStaffGlobalLevel(), tokenDuration);
redisTemplate.opsForValue().set( redisTemplate.opsForValue().set(
Objects.<Integer>requireNonNull(TokenUtils.getStaffId(token)), Objects.<Integer>requireNonNull(TokenUtils.getStaffId(token)),
@ -67,6 +86,11 @@ public class StaffServiceImpl extends ServiceImpl<StaffMapper, Staff> implements
.build(); .build();
} }
private void throwLoginException(String requestIpAddress, int loginCount) throws BadRequestException {
redisTemplate.opsForValue().set(requestIpAddress, ++loginCount, LOGIN_TRY_COUNT_TIMEOUT, TimeUnit.MINUTES);
throw new BadRequestException(String.format("登录失败,%d分钟内还有%d次登录机会", LOGIN_TRY_COUNT_TIMEOUT, LOGIN_MAX_TRY_COUNT - loginCount));
}
@Override @Override
public Boolean logout(String token) { public Boolean logout(String token) {
Integer staffId = TokenUtils.getStaffId(token); Integer staffId = TokenUtils.getStaffId(token);

View File

@ -286,10 +286,14 @@ public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements IT
task.setTaskStatus(Task.STATUS_WAITING); task.setTaskStatus(Task.STATUS_WAITING);
task.setChildrenCount(0); task.setChildrenCount(0);
task.setTaskClosedTime(null); task.setTaskClosedTime(null);
if (baseMapper.insert(task) == 0) { if (baseMapper.insert(task) == 0 ||
baseMapper.update(null,
Wrappers.<Task>lambdaUpdate()
.eq(Task::getTaskId, task.getTaskFatherId())
.setSql("children_count = children_count + 1")) != 1
) {
throw new BadRequestException(BadRequestException.OPERATE_FAILED); throw new BadRequestException(BadRequestException.OPERATE_FAILED);
} }
baseMapper.update(null, Wrappers.<Task>lambdaUpdate().setSql("children_count = children_count + 1"));
} catch (Exception e) { } catch (Exception e) {
log.error(e.getMessage(), e); log.error(e.getMessage(), e);
throw new BadRequestException(BadRequestException.OPERATE_FAILED); throw new BadRequestException(BadRequestException.OPERATE_FAILED);

View File

@ -84,6 +84,9 @@ public class BeanUtils {
return false; return false;
} }
/**
* objectset
*/
public static boolean checkInSet(Object object, Object... set) { public static boolean checkInSet(Object object, Object... set) {
return Arrays.asList(set).contains(object); return Arrays.asList(set).contains(object);
} }

View File

@ -63,4 +63,22 @@ public class TimeUtils {
} }
return true; return true;
} }
/**
*
*/
public static String secondsFormat(long seconds) {
long minutes = 0;
if ((minutes = seconds / 60) > 0) {
seconds %= 60;
}
StringBuilder sb = new StringBuilder();
if (minutes > 0) {
sb.append(minutes).append("分");
}
if (seconds > 0) {
sb.append(seconds).append("秒");
}
return sb.toString();
}
} }

View File

@ -0,0 +1,48 @@
package cn.edu.hfut.rmdjzz.projectmanagement.utils.http;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author
* @since 2022/7/12 10:57
*/
public class HttpUtils {
public static String getRequestIpAddress(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
// ipAddress = this.getRequest().getRemoteAddr();
return ipAddress;
}
}

View File

@ -11,7 +11,7 @@
task_name, task_name,
task_project_id, task_project_id,
task_holder_id, task_holder_id,
s.staff_fullname AS task_holder_name, s.staff_fullname AS task_holder_name,
task_status, task_status,
task_type, task_type,
t.task_father_id, t.task_father_id,
@ -22,11 +22,9 @@
task_priority, task_priority,
task_description, task_description,
attached_info, attached_info,
judge.task_father_id IS NOT NULL AS has_children children_count
FROM task AS t FROM task AS t
JOIN (SELECT staff_id, staff_fullname FROM staff) AS s ON t.task_holder_id = s.staff_id JOIN (SELECT staff_id, staff_fullname FROM staff) AS s ON t.task_holder_id = s.staff_id
LEFT JOIN (SELECT DISTINCT task_father_id FROM task WHERE is_deleted = 0) AS judge
ON t.task_id = judge.task_father_id
WHERE is_deleted = 0 WHERE is_deleted = 0
AND task_project_id = #{projectId} AND task_project_id = #{projectId}
AND t.task_father_id = #{fatherId} AND t.task_father_id = #{fatherId}

View File

@ -18,7 +18,7 @@ public class RedisTests {
@Test @Test
void test() { void test() {
redisTemplate.opsForList().rightPush(123456, 89); System.out.println(redisTemplate.opsForValue().get("l1"));
} }
@Test @Test