feat(用户管理): 增加用户管理的用户邀请校验接口

【【系统设置】用户-邮箱邀请用户-收到的注册链接过期后,仍能正常打开注册页面】
https://www.tapd.cn/55049933/bugtrace/bugs/view/1155049933001035202
This commit is contained in:
song-tianyang 2024-02-04 14:27:09 +08:00 committed by 刘瑞斌
parent 5bebf4fe7c
commit 13bc39a45c
17 changed files with 71 additions and 37 deletions

View File

@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
@ -20,4 +21,8 @@ public class RemoteFileAttachInfo implements Serializable {
private String filePath;
private String commitMessage;
private long size;
public boolean fileIsNotExist() {
return StringUtils.isAnyBlank(branch, commitId);
}
}

View File

@ -45,7 +45,7 @@ public class GitRepository implements FileRepository {
GitFileRequest gitFileInfo = request.getGitFileRequest();
GitRepositoryUtil repositoryUtils = new GitRepositoryUtil(
gitFileInfo.getUrl(), gitFileInfo.getUserName(), gitFileInfo.getToken());
fileBytes = repositoryUtils.getFile(gitFileInfo.getUrl(), gitFileInfo.getCommitId());
fileBytes = repositoryUtils.getFile(request.getFileName(), gitFileInfo.getCommitId());
}
return fileBytes;
}

View File

@ -21,13 +21,13 @@ public class FileAssociationSourceUtil {
public static final Map<String, String> QUERY_SQL = new HashMap<>();
static {
QUERY_SQL.put(SOURCE_TYPE_BUG, "SELECT id AS sourceId,title AS sourceName FROM bug");
QUERY_SQL.put(SOURCE_TYPE_FUNCTIONAL_CASE, "SELECT id AS sourceId,name AS sourceName FROM functional_case");
QUERY_SQL.put(SOURCE_TYPE_API_DEBUG, "SELECT id AS sourceId,name AS sourceName FROM api_debug");
QUERY_SQL.put(SOURCE_TYPE_API_SCENARIO, "SELECT id AS sourceId,name AS sourceName FROM api_scenario");
QUERY_SQL.put(SOURCE_TYPE_API_TEST_CASE, "SELECT id AS sourceId,name AS sourceName FROM api_test_case");
QUERY_SQL.put(SOURCE_TYPE_API_DEFINITION, "SELECT id AS sourceId,name AS sourceName FROM api_definition");
QUERY_SQL.put(SOURCE_TYPE_API_DEFINITION_MOCK, "SELECT id AS sourceId,name AS sourceName FROM api_definition_mock");
QUERY_SQL.put(SOURCE_TYPE_BUG, "SELECT id AS sourceId, num AS sourceNum, title AS sourceName FROM bug");
QUERY_SQL.put(SOURCE_TYPE_FUNCTIONAL_CASE, "SELECT id AS sourceId, num AS sourceNum, name AS sourceName FROM functional_case");
QUERY_SQL.put(SOURCE_TYPE_API_DEBUG, "SELECT id AS sourceId, id AS sourceNum, name AS sourceName FROM api_debug");
QUERY_SQL.put(SOURCE_TYPE_API_SCENARIO, "SELECT id AS sourceId, num AS sourceNum, name AS sourceName FROM api_scenario");
QUERY_SQL.put(SOURCE_TYPE_API_TEST_CASE, "SELECT id AS sourceId, num AS sourceNum, name AS sourceName FROM api_test_case");
QUERY_SQL.put(SOURCE_TYPE_API_DEFINITION, "SELECT id AS sourceId, num AS sourceNum, name AS sourceName FROM api_definition");
QUERY_SQL.put(SOURCE_TYPE_API_DEFINITION_MOCK, "SELECT id AS sourceId, expect_num AS sourceNum, name AS sourceName FROM api_definition_mock");
}
public static void validate(String type) {

View File

@ -35,6 +35,7 @@ public class FilterChainUtils {
filterChainDefinitionMap.put("/system/version/current", "anon");
//用户通过邮箱邀请自行注册的接口
filterChainDefinitionMap.put("/system/user/check-invite/**", "anon");
filterChainDefinitionMap.put("/system/user/register-by-invite", "anon");
// 下载测试资源

View File

@ -50,9 +50,14 @@ public class GitRepositoryUtil {
public byte[] getFile(String filePath, String commitId) throws Exception {
LogUtils.info("准备获取文件. repositoryUrl" + repositoryUrl + "; filePath" + filePath + "; commitId" + commitId);
InMemoryRepository repo = this.getGitRepositoryInMemory(repositoryUrl, userName, token);
ObjectId fileCommitObjectId = repo.resolve(commitId);
ObjectId objectId = this.getTreeWork(repo, fileCommitObjectId, filePath).getObjectId(0);
TreeWalk treeWalk = this.getTreeWork(repo, fileCommitObjectId, filePath);
if (!treeWalk.next()) {
return null;
}
ObjectId objectId = treeWalk.getObjectId(0);
ObjectLoader loader = repo.open(objectId);
byte[] returnBytes = loader.getBytes();
this.closeConnection(repo);

View File

@ -39,10 +39,14 @@ public class TempFileUtils {
}
}
public static byte[] compressPic(byte[] fileBytes) throws IOException {
byte[] compressBytes = compressPic(new ByteArrayInputStream(fileBytes));
return compressBytes.length > 0 ? compressBytes : fileBytes;
}
public static byte[] compressPic(InputStream imgInputStream) throws IOException {
// 读取原始图像
BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(fileBytes));
BufferedImage originalImage = ImageIO.read(imgInputStream);
if (originalImage != null) {
int width = originalImage.getWidth();
int height = originalImage.getHeight();
@ -65,11 +69,10 @@ public class TempFileUtils {
ImageIO.setUseCache(false);
ImageIO.write(previewImage, "JPEG", outputStream);
return outputStream.toByteArray();
} catch (Exception e) {
LogUtils.error(e);
} catch (Exception ignore) {
}
}
return fileBytes;
return new byte[0];
}
private static int getCompressFactor(int width, int height) {

View File

@ -5,5 +5,6 @@ import lombok.Data;
@Data
public class FileAssociationSource {
private String sourceId;
private String sourceNum;
private String sourceName;
}

View File

@ -13,6 +13,9 @@ public class FileAssociationResponse {
@Schema(description = "资源Id")
private String sourceId;
@Schema(description = "资源编号")
private String sourceNum;
@Schema(description = "文件Id")
private String fileId;

View File

@ -28,8 +28,8 @@
resultMap="BaseResultMap">
SELECT
f.id,
updateUser.name as updateUser,
createUser.name AS createUser,
updateUser.name as update_user,
createUser.name AS create_uUser,
f.module_id,
f.name,
f.type,

View File

@ -62,7 +62,7 @@ public class FileAssociationService {
//查找文件信息
Map<String,FileMetadata> fileIdMap = this.getFileIdMap( associationList.stream().map(FileAssociation::getFileId).collect(Collectors.toList()));
//查找资源信息
Map<String,String> sourceIdNameMap = this.getAssociationSourceMap(
Map<String, FileAssociationSource> sourceIdNameMap = this.getAssociationSourceMap(
associationList.stream().collect(
Collectors.groupingBy(FileAssociation::getSourceType,Collectors.mapping(FileAssociation::getSourceId,Collectors.toList()))
));
@ -75,7 +75,8 @@ public class FileAssociationService {
response.setId(item.getId());
response.setSourceId(item.getSourceId());
response.setFileId(item.getFileId());
response.setSourceName(sourceIdNameMap.get(item.getSourceId()));
response.setSourceName(sourceIdNameMap.get(item.getSourceId()).getSourceName());
response.setSourceNum(sourceIdNameMap.get(item.getSourceId()).getSourceNum());
response.setSourceType(item.getSourceType());
response.setFileVersion(fileIdMap.get(item.getFileId()).getFileVersion());
responseList.add(response);
@ -94,14 +95,14 @@ public class FileAssociationService {
}
//通过资源类型Map查找关联表
private Map<String, String> getAssociationSourceMap(Map<String, List<String>> sourceTypeToIdMap) {
private Map<String, FileAssociationSource> getAssociationSourceMap(Map<String, List<String>> sourceTypeToIdMap) {
List<FileAssociationSource> sourceQueryList = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : sourceTypeToIdMap.entrySet()) {
String sourceType =entry.getKey();
sourceQueryList.addAll(
extFileAssociationMapper.selectAssociationSourceBySourceTableAndIdList(FileAssociationSourceUtil.getQuerySql(sourceType),entry.getValue()));
}
return sourceQueryList.stream().collect(Collectors.toMap(FileAssociationSource::getSourceId, FileAssociationSource::getSourceName));
return sourceQueryList.stream().collect(Collectors.toMap(FileAssociationSource::getSourceId, item -> item));
}
/**

View File

@ -182,6 +182,7 @@ public class FileManagementService {
FileModuleRepositoryDTO repositoryDTO = getFileModuleRepositoryDTO(fileMetadata.getModuleId());
FileMetadataRepositoryDTO metadataRepositoryDTO = getFileMetadataRepositoryDTO(fileMetadata.getId());
fileRequest.setGitFileRequest(repositoryDTO, metadataRepositoryDTO);
fileRequest.setFileName(fileMetadata.getPath());
}
return fileService.download(fileRequest);

View File

@ -271,9 +271,9 @@ public class FileMetadataService {
}
FileMetadataExample example = new FileMetadataExample();
if (StringUtils.isBlank(id)) {
example.createCriteria().andNameEqualTo(fileName).andTypeEqualTo(type).andProjectIdEqualTo(projectId).andStorageEqualTo(StorageType.MINIO.name());
example.createCriteria().andNameEqualTo(fileName).andTypeEqualTo(type).andLatestEqualTo(true).andProjectIdEqualTo(projectId).andStorageEqualTo(StorageType.MINIO.name());
} else {
example.createCriteria().andNameEqualTo(fileName).andTypeEqualTo(type).andProjectIdEqualTo(projectId).andIdNotEqualTo(id).andStorageEqualTo(StorageType.MINIO.name());
example.createCriteria().andNameEqualTo(fileName).andTypeEqualTo(type).andLatestEqualTo(true).andProjectIdEqualTo(projectId).andIdNotEqualTo(id).andStorageEqualTo(StorageType.MINIO.name());
}
if (fileMetadataMapper.countByExample(example) > 0) {
throw new MSException(Translator.get("file.name.exist") + ":" + fileName);
@ -289,10 +289,12 @@ public class FileMetadataService {
if (TempFileUtils.isImage(fileMetadata.getType())) {
//图片文件自动生成预览图
byte[] previewImg = TempFileUtils.compressPic(file.getBytes());
byte[] previewImg = TempFileUtils.compressPic(file.getInputStream());
if (previewImg.length > 0) {
uploadFileRequest.setFolder(DefaultRepositoryDir.getFileManagementPreviewDir(fileMetadata.getProjectId()));
fileService.upload(previewImg, uploadFileRequest);
}
}
return filePath;
}
@ -312,14 +314,10 @@ public class FileMetadataService {
public byte[] getFileByte(FileMetadata fileMetadata) {
String filePath = null;
if (TempFileUtils.isImgTmpFileExists(fileMetadata.getId())) {
filePath = TempFileUtils.getTmpFilePath(fileMetadata.getId());
} else {
try {
filePath = TempFileUtils.createFile(TempFileUtils.getTmpFilePath(fileMetadata.getId()), fileManagementService.getFile(fileMetadata));
} catch (Exception ignore) {
}
}
return TempFileUtils.getFile(filePath);
}

View File

@ -15,12 +15,12 @@ import io.metersphere.project.mapper.FileMetadataRepositoryMapper;
import io.metersphere.project.mapper.FileModuleRepositoryMapper;
import io.metersphere.sdk.constants.ModuleConstants;
import io.metersphere.sdk.constants.StorageType;
import io.metersphere.sdk.dto.RemoteFileAttachInfo;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.GitRepositoryUtil;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import io.metersphere.sdk.dto.RemoteFileAttachInfo;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.sdk.util.GitRepositoryUtil;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
@ -137,7 +137,7 @@ public class FileRepositoryService extends FileModuleService {
GitRepositoryUtil utils = new GitRepositoryUtil(repository.getUrl(), repository.getUserName(), repository.getToken());
RemoteFileAttachInfo fileAttachInfo = utils.selectLastCommitIdByBranch(request.getBranch(), request.getFilePath());
if (fileAttachInfo == null) {
if (fileAttachInfo == null || fileAttachInfo.fileIsNotExist()) {
throw new MSException(Translator.get("file.not.exist"));
}
FileMetadata fileMetadata = fileMetadataService.genFileMetadata(request.getFilePath(), StorageType.GIT.name(), fileAttachInfo.getSize(), request.isEnable(),

View File

@ -189,6 +189,12 @@ public class UserController {
return userService.saveInviteRecord(request, SessionUtils.getUser());
}
@GetMapping("/check-invite/{inviteId}")
@Operation(summary = "系统设置-系统-用户-用户接受注册邀请并创建账户")
public void checkInviteNum(@PathVariable String inviteId) {
userService.getUserInviteAndCheckEfficient(inviteId);
}
@PostMapping("/register-by-invite")
@Operation(summary = "系统设置-系统-用户-用户接受注册邀请并创建账户")
public String registerByInvite(@Validated @RequestBody UserRegisterRequest request) throws Exception {

View File

@ -6,6 +6,7 @@ import io.metersphere.sdk.constants.OperationLogConstants;
import io.metersphere.sdk.constants.UserRoleEnum;
import io.metersphere.sdk.constants.UserRoleScope;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.*;
import io.metersphere.system.dto.user.response.UserTableResponse;
import io.metersphere.system.log.constants.OperationLogModule;
@ -72,8 +73,8 @@ public class UserRoleRelationService {
log.setCreateTime(operationTime);
log.setSourceId(user.getId());
log.setContent(user.getName() + StringUtils.SPACE
+ operationType + StringUtils.SPACE
+ "UserRole" + StringUtils.SPACE
+ Translator.get(StringUtils.lowerCase(operationType)) + StringUtils.SPACE
+ Translator.get("permission.project_group.name") + StringUtils.SPACE
+ userRole.getName());
log.setOriginalValue(JSON.toJSONBytes(userRole));
logs.add(log);

View File

@ -521,11 +521,16 @@ public class UserService {
javaMailSender.send(mimeMessage);
}
public String registerByInvite(UserRegisterRequest request) throws Exception {
UserInvite userInvite = userInviteService.selectEfficientInviteById(request.getInviteId());
public UserInvite getUserInviteAndCheckEfficient(String inviteId) {
UserInvite userInvite = userInviteService.selectEfficientInviteById(inviteId);
if (userInvite == null) {
throw new MSException(Translator.get("user.not.invite.or.expired"));
}
return userInvite;
}
public String registerByInvite(UserRegisterRequest request) throws Exception {
UserInvite userInvite = this.getUserInviteAndCheckEfficient(request.getInviteId());
//检查邮箱是否已经注册
this.validateUserInfo(new ArrayList<>() {{
this.add(userInvite.getEmail());

View File

@ -1362,6 +1362,7 @@ public class UserControllerTests extends BaseTest {
this.requestPost(UserRequestUtils.URL_INVITE_REGISTER, request).andExpect(BAD_REQUEST_MATCHER);
//测试正常创建
this.requestGetWithOk("/system/user/check-invite/" + request.getInviteId());
request.setName("建国通过邮箱邀请");
MvcResult mvcResult = userRequestUtils.responsePost(UserRequestUtils.URL_INVITE_REGISTER, request);
String resultHolderStr = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
@ -1380,6 +1381,7 @@ public class UserControllerTests extends BaseTest {
this.testUserInviteSuccess();
}
String inviteId = INVITE_RECORD_ID_LIST.get(1);
//400-用户名为空
UserRegisterRequest request = new UserRegisterRequest();
request.setInviteId(inviteId);
@ -1409,8 +1411,9 @@ public class UserControllerTests extends BaseTest {
request.setName("建国通过邮箱邀请2");
request.setPassword(IDGenerator.nextStr());
userRequestUtils.requestPost(UserRequestUtils.URL_INVITE_REGISTER, request, ERROR_REQUEST_MATCHER);
this.requestGet("/system/user/check-invite/" + request.getInviteId()).andExpect(ERROR_REQUEST_MATCHER);
//500-邀请ID已过期且暂未删除
//500-邀请ID已过期且暂未删除 同时校验邀请检测接口
UserInvite invite = userInviteMapper.selectByPrimaryKey(inviteId);
invite.setInviteTime(invite.getInviteTime() - 1000 * 60 * 60 * 24);
userInviteMapper.updateByPrimaryKeySelective(invite);
@ -1420,6 +1423,7 @@ public class UserControllerTests extends BaseTest {
request.setName("建国通过邮箱邀请2");
request.setPassword(IDGenerator.nextStr());
userRequestUtils.requestPost(UserRequestUtils.URL_INVITE_REGISTER, request, ERROR_REQUEST_MATCHER);
this.requestGet("/system/user/check-invite/" + inviteId).andExpect(ERROR_REQUEST_MATCHER);
//500-用户邮箱在用户注册之前已经被注册过了
//首先还原邀请时间