Compare commits

..

2 Commits

18 changed files with 181 additions and 49 deletions

View File

@ -6,6 +6,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import javax.annotation.Resource;
@ -36,4 +37,11 @@ public class RedisConfig {
return template;
}
@Bean
public StringRedisTemplate stringRedisTemplate() {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(factory);
return template;
}
}

View File

@ -1,8 +1,10 @@
package cn.edu.hfut.rmdjzz.projectmanagement.config;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.IPAddress;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
@ -25,11 +27,32 @@ public class SerializeConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper om = new ObjectMapper();
om.registerModule(javaTimeModule());
om.registerModule(customClassModule());
return om;
}
@SuppressWarnings({"Convert2Diamond", "NullableProblems", "rawtypes"})
@Bean
public Converter<String, Map> mapConverter() {
return new Converter<String, Map>() {
@Autowired
private ObjectMapper objectMapper;
@SneakyThrows
@Override
public Map convert(String source) {
return objectMapper.readValue(source, Map.class);
}
};
}
private JavaTimeModule javaTimeModule() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
//LocalDataTime
//LocalDateTime
javaTimeModule.addSerializer(LocalDateTime.class, new JsonSerializer<>() {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
@ -77,24 +100,27 @@ public class SerializeConfig {
}
});
om.registerModule(javaTimeModule);
return om;
return javaTimeModule;
}
@SuppressWarnings({"Convert2Diamond", "NullableProblems", "rawtypes"})
@Bean
public Converter<String, Map> mapConverter() {
return new Converter<String, Map>() {
@Autowired
private ObjectMapper objectMapper;
@SneakyThrows
//TODO: Redis可能改成tostringSerializer
private SimpleModule customClassModule() {
SimpleModule customClassModule = new SimpleModule("CustomClassModule");
customClassModule.addSerializer(IPAddress.class, new JsonSerializer<>() {
@Override
public Map convert(String source) {
return objectMapper.readValue(source, Map.class);
public void serialize(IPAddress value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value != null) {
gen.writeString(value.toString());
}
};
}
});
customClassModule.addDeserializer(IPAddress.class, new JsonDeserializer<>() {
@Override
public IPAddress deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return IPAddress.of(Long.decode(p.getValueAsString()));
}
});
return customClassModule;
}
}

View File

@ -32,6 +32,7 @@ public class WebConfig implements WebMvcConfigurer {
.excludePathPatterns("/hello", "/error") //测试
.excludePathPatterns("/staff/login") //登录
.excludePathPatterns("/swagger-ui.html", "/swagger-resources/**", "/swagger-ui/**",
"/v2/**", "/v3/**", "/webjars/**", "/doc.html"); //swagger
"/v2/**", "/v3/**", "/webjars/**", "/doc.html") //swagger
.excludePathPatterns("/favicon.ico", "/public/**"); //静态资源
}
}

View File

@ -46,7 +46,7 @@ public class AnnouncementController {
if (projectGroupService.getProjectAccessLevel(token, projectId) == 0) {
throw new ForbiddenException(IProjectGroupService.UNABLE_TO_ACCESS_PROJECT);
}
return ResponseMap.ofSuccess(announcementService.getAnnouncementById(announcementId));
return ResponseMap.ofSuccess(announcementService.getAnnouncementById(projectId, announcementId));
}
@SneakyThrows

View File

@ -2,7 +2,6 @@ package cn.edu.hfut.rmdjzz.projectmanagement.controller;
import cn.edu.hfut.rmdjzz.projectmanagement.entity.Staff;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.ForbiddenException;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.TokenException;
import cn.edu.hfut.rmdjzz.projectmanagement.service.IStaffService;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.FileUtils;
@ -12,12 +11,12 @@ import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.ResponseMap;
import io.swagger.v3.oas.annotations.Parameter;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
/**
@ -72,7 +71,8 @@ public class StaffController {
return ResponseMap.ofSuccess("成功导入" + successCount + "条数据");
}
@SneakyThrows
//取消功能
/*@SneakyThrows
@GetMapping("/import/template")
public void downloadTemplate(
@RequestHeader(TokenUtils.HEADER_TOKEN) String token,
@ -81,9 +81,18 @@ public class StaffController {
if (TokenUtils.getStaffGlobalLevel(token) > 2) {
throw new ForbiddenException(ForbiddenException.UNABLE_TO_OPERATE);
}
if (FileUtils.downloadResource("static/账户导入模板.xlsx", response)) {
if (FileUtils.downloadResource("static/public/账户导入模板.xlsx", response)) {
return;
}
throw new BadRequestException(BadRequestException.OPERATE_FAILED);
}*/
@SneakyThrows
@GetMapping("/import/template")
@ResponseStatus(HttpStatus.SEE_OTHER)
public ResponseMap downloadTemplate() {
return ResponseMap.of(HttpStatus.SEE_OTHER.value(),
HttpStatus.SEE_OTHER.getReasonPhrase())
.put("URI","/public/账户导入模板.xlsx");
}
}

View File

@ -13,6 +13,7 @@ public class AnnouncementDTO {
private Long announcementId;
private Integer announcementPublisherId;
private String announcementPublisherName;
private Integer announcementPublisherAccessLevel;
private LocalDateTime announcementPublishTime;
private String announcementTitle;
private String announcementContent;

View File

@ -7,6 +7,8 @@ import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.Iterator;
/**
* @author
@ -20,7 +22,15 @@ public class CorsInterceptor implements HandlerInterceptor {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Headers", "Content-Type,Token");
response.setHeader("Access-Control-Allow-Credentials","false");
response.setHeader("Access-Control-Allow-Credentials", "false");
//test
Enumeration<String> headerNames = request.getHeaderNames();
Iterator<String> nameIter = headerNames.asIterator();
while (nameIter.hasNext()) {
System.out.println(nameIter.next());
}
// 如果是OPTIONS则结束请求
if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
response.setStatus(HttpStatus.OK.value());

View File

@ -14,5 +14,5 @@ import java.util.List;
public interface AnnouncementMapper extends BaseMapper<Announcement> {
List<AnnouncementDTO> selectAnnouncementList(@Param("projectId") Integer projectId);
AnnouncementDTO selectAnnouncementById(@Param("announcementId") Long announcementId);
AnnouncementDTO selectAnnouncementById(@Param("projectId") Integer projectId, @Param("announcementId") Long announcementId);
}

View File

@ -16,7 +16,7 @@ public interface IAnnouncementService extends IService<Announcement> {
List<AnnouncementDTO> getAnnouncementList(Integer projectId);
AnnouncementDTO getAnnouncementById(Long announcementId);
AnnouncementDTO getAnnouncementById(Integer projectId, Long announcementId);
Boolean updateAnnouncement(String token, Integer projectId, Announcement announcement) throws ForbiddenException, BadRequestException;

View File

@ -28,7 +28,7 @@ public interface IProjectGroupService extends IService<ProjectGroup> {
Boolean removeMember(String token, Integer projectId, Integer targetId) throws ForbiddenException, BadRequestException;
Boolean updateStaffPositions(String token, Integer projectId, Integer targetId, String positions) throws ForbiddenException;
Boolean updateStaffPositions(String token, Integer projectId, Integer targetId, String positions) throws ForbiddenException, BadRequestException;
/**
* @return 0AccessLevel11

View File

@ -31,8 +31,8 @@ public class AnnouncementServiceImpl extends ServiceImpl<AnnouncementMapper, Ann
}
@Override
public AnnouncementDTO getAnnouncementById(Long announcementId) {
return baseMapper.selectAnnouncementById(announcementId);
public AnnouncementDTO getAnnouncementById(Integer projectId, Long announcementId) {
return baseMapper.selectAnnouncementById(projectId, announcementId);
}
@Override
@ -64,8 +64,7 @@ public class AnnouncementServiceImpl extends ServiceImpl<AnnouncementMapper, Ann
throw new ForbiddenException(ForbiddenException.UNABLE_TO_OPERATE);
}
Announcement rawAnnouncement = baseMapper.selectOne(Wrappers.<Announcement>lambdaQuery()
.select(Announcement::getProjectId)
.select(Announcement::getAnnouncementPublisherId)
.select(Announcement::getProjectId, Announcement::getAnnouncementPublisherId)
.eq(Announcement::getAnnouncementId, announcementId)
);
if (!Objects.equals(projectId, rawAnnouncement.getProjectId())) {

View File

@ -44,6 +44,9 @@ public class ProjectGroupServiceImpl extends ServiceImpl<ProjectGroupMapper, Pro
@Override
public Boolean insertNewMember(String token, Integer projectId, String targetUsername, String positions) throws ForbiddenException, BadRequestException {
if (targetUsername.equals("root")) {
throw new BadRequestException(IStaffService.STAFF_DOES_NOT_EXIST);
}
int accessLevel = getProjectAccessLevel(token, projectId);
int targetLevel = 3;
@ -51,6 +54,9 @@ public class ProjectGroupServiceImpl extends ServiceImpl<ProjectGroupMapper, Pro
if (targetStaff == null) {
throw new BadRequestException(IStaffService.STAFF_DOES_NOT_EXIST);
}
if (getProjectAccessLevelIgnoreGlobalLevel(targetStaff.getStaffId(), projectId) != 0) {
throw new BadRequestException("该成员已经在本项目中");
}
if (accessLevel == 0) {
throw new ForbiddenException(IProjectGroupService.UNABLE_TO_ACCESS_PROJECT);
@ -91,9 +97,12 @@ public class ProjectGroupServiceImpl extends ServiceImpl<ProjectGroupMapper, Pro
}
@Override
public Boolean updateStaffPositions(String token, Integer projectId, Integer targetId, String positions) throws ForbiddenException {
public Boolean updateStaffPositions(String token, Integer projectId, Integer targetId, String positions) throws ForbiddenException, BadRequestException {
int accessLevel = getProjectAccessLevel(token, projectId);
int originTargetLevel = getProjectAccessLevelIgnoreGlobalLevel(targetId, projectId);
if (originTargetLevel == 0) {
throw new BadRequestException("该项目中不存在该成员");
}
int targetLevel = 3;
if (accessLevel == 0) {

View File

@ -9,6 +9,7 @@ import cn.edu.hfut.rmdjzz.projectmanagement.utils.BeanUtils;
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.http.IPAddress;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
@ -45,34 +46,35 @@ public class StaffServiceImpl extends ServiceImpl<StaffMapper, Staff> implements
@SuppressWarnings("ConstantConditions")
@Override
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);
String requestIpAddressHex = IPAddress.of(requestIpAddress).toString();
if (Boolean.FALSE.equals(redisTemplate.hasKey(requestIpAddressHex))) {
redisTemplate.opsForValue().set(requestIpAddressHex, 0, LOGIN_TRY_COUNT_TIMEOUT, TimeUnit.MINUTES);
}
int loginCount = (int) redisTemplate.opsForValue().get(requestIpAddress);
int loginCount = (int) redisTemplate.opsForValue().get(requestIpAddressHex);
if (loginCount >= LOGIN_MAX_TRY_COUNT) {
throw new ForbiddenException(String.format("还需要等待%s才能登录"
, TimeUtils.secondsFormat(redisTemplate.getExpire(requestIpAddress))));
, TimeUtils.secondsFormat(redisTemplate.getExpire(requestIpAddressHex))));
}
if (staffUsername == null || staffUsername.strip().length() == 0)
//throw new BadRequestException("用户名为空");
throwLoginException(requestIpAddress, loginCount);
throw loginException(requestIpAddressHex, loginCount);
if (!staffUsername.equals(staffUsername.replaceAll("[^a-zA-Z0-9]", "")))
//throw new BadRequestException("用户名格式错误");
throwLoginException(requestIpAddress, loginCount);
throw loginException(requestIpAddressHex, loginCount);
if (password == null || password.trim().length() != 32)
//throw new BadRequestException("密码格式错误");
throwLoginException(requestIpAddress, loginCount);
throw loginException(requestIpAddressHex, loginCount);
Staff staff = getOne(Wrappers.<Staff>lambdaQuery().eq(Staff::getStaffUsername, staffUsername));
if (staff == null)
//throw new BadRequestException(STAFF_DOES_NOT_EXIST);
throwLoginException(requestIpAddress, loginCount);
throw loginException(requestIpAddressHex, loginCount);
password = DigestUtils.md5DigestAsHex((password + staff.getStaffSalt()).getBytes());
if (!staff.getStaffPassword().equals(password)) {
throwLoginException(requestIpAddress, loginCount);
throw loginException(requestIpAddressHex, loginCount);
}
String token = TokenUtils.getToken(staff.getStaffUsername(), staff.getStaffId(), staff.getStaffGlobalLevel(), tokenDuration);
redisTemplate.opsForValue().set(
@ -80,15 +82,16 @@ public class StaffServiceImpl extends ServiceImpl<StaffMapper, Staff> implements
token,
Objects.requireNonNull(TokenUtils.getDuration(token)), TimeUnit.SECONDS
);
redisTemplate.delete(requestIpAddressHex);
return new MapBuilder()
.put(TokenUtils.HEADER_TOKEN, token)
.putAll(BeanUtils.beanToMap(staff, false))
.build();
}
private void throwLoginException(String requestIpAddress, int loginCount) throws BadRequestException {
private BadRequestException loginException(String requestIpAddress, int loginCount) {
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));
return new BadRequestException(String.format("登录失败,%d分钟内还有%d次登录机会", LOGIN_TRY_COUNT_TIMEOUT, LOGIN_MAX_TRY_COUNT - loginCount));
}
@Override

View File

@ -0,0 +1,43 @@
package cn.edu.hfut.rmdjzz.projectmanagement.utils.http;
import lombok.AllArgsConstructor;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* @author
* @since 2022/7/12 17:12
*/
@AllArgsConstructor(staticName = "of")
public class IPAddress {
private long ipHex;
public static IPAddress of(String ip) {
long ipHex = 0;
String[] split = ip.split("\\.");
for (String s : split) {
ipHex = (ipHex << 8) + Integer.parseInt(s);
}
return new IPAddress(ipHex);
}
public String getIpString() {
List<String> ipSectionList = new LinkedList<>();
long tempHex = ipHex;
int section;
while (tempHex > 0) {
section = (int) (tempHex & 0xFF);
ipSectionList.add(String.valueOf(section));
tempHex >>= 8;
}
Collections.reverse(ipSectionList);
return String.join(".", ipSectionList);
}
@Override
public String toString() {
return "#" + Long.toHexString(ipHex);
}
}

View File

@ -12,7 +12,7 @@
announcement_publish_time,
announcement_title
FROM announcement
JOIN (SELECT staff_id, staff_fullname FROM staff) s
JOIN (SELECT staff_id, staff_fullname FROM staff) AS s
ON staff_id = announcement.announcement_publisher_id
WHERE is_deleted = 0
AND project_id = #{projectId}
@ -22,12 +22,17 @@
SELECT announcement_id,
announcement_publisher_id,
s.staff_fullname AS announcement_publisher_name,
IFNULL(pg.project_access_level, 1) AS announcement_publisher_access_level,
announcement_publish_time,
announcement_title,
announcement_content
FROM announcement
JOIN (SELECT staff_id, staff_fullname FROM staff) s
ON staff_id = announcement.announcement_publisher_id
JOIN (SELECT staff_id, staff_fullname FROM staff) AS s
ON s.staff_id = announcement.announcement_publisher_id
LEFT JOIN (SELECT staff_id, project_access_level
FROM project_group
WHERE project_id = #{projectId}) AS pg
ON pg.staff_id = announcement_publisher_id
WHERE is_deleted = 0
AND announcement_id = #{announcementId};
</select>

View File

@ -1,9 +1,11 @@
package cn.edu.hfut.rmdjzz.projectmanagement;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.IPAddress;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.time.LocalDateTime;
@ -15,14 +17,21 @@ import java.time.LocalDateTime;
public class RedisTests {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void test() {
System.out.println(redisTemplate.opsForValue().get("l1"));
System.out.println(redisTemplate.opsForValue().get(0x15A2));
}
@Test
void testValue() {
redisTemplate.opsForValue().set("time", LocalDateTime.now());
}
@Test
void stringTest() {
stringRedisTemplate.opsForValue().set(IPAddress.of("192.168.0.1").toString(),String.valueOf(2));
}
}

View File

@ -1,12 +1,12 @@
package cn.edu.hfut.rmdjzz.projectmanagement;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.http.IPAddress;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
@ -24,4 +24,13 @@ public class SerializeTests {
public void serializeTime() {
System.out.println(objectMapper.readValue("1657166400", LocalDateTime.class));
}
@SneakyThrows
@Test
public void serializeIPTest() {
IPAddress ip = IPAddress.of("192.108.1.1");
System.out.println(objectMapper.writeValueAsString(ip));
System.out.println(ip.getIpString());
System.out.println(objectMapper.readValue("0xc06c0101",IPAddress.class));
}
}