news 2026/5/8 21:50:30

账户维护、登出与多模态文件独立接口

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
账户维护、登出与多模态文件独立接口

1)登出:Header 或 Query 传 Token

@PostMapping("/logout")

public Result<Void> logout(

@RequestHeader(value = "Authorization", required = false) String authorization,

@RequestParam(value = "token", required = false) String token

) {

return loginSevice.logout(authorization, token);

}

2)注册:username+ 用当前请求拼publicBaseUrl(头像 URL)

@PostMapping(value = "/register", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)

public Result<AuthResponse> register(

@RequestParam("username") String username,

@RequestParam("phone") String phone,

@RequestParam("password") String password,

@RequestParam(value = "avatar", required = false) MultipartFile avatar,

HttpServletRequest servletRequest

) {

RegisterRequest request = new RegisterRequest(username, phone, password);

String publicBaseUrl = ServletUriComponentsBuilder.fromRequestUri(servletRequest)

.replacePath(servletRequest.getContextPath())

.replaceQuery(null)

.build()

.toUriString();

return loginSevice.register(request, avatar, publicBaseUrl);

}

3)更新当前用户:昵称 / 头像 multipart,校验后save

String normalizedUsername = trimToNull(username);

boolean hasUsername = StringUtils.hasText(normalizedUsername);

boolean hasAvatar = avatar != null && !avatar.isEmpty();

if (!hasUsername && !hasAvatar) {

return failure("A0440", "username or avatar is required");

}

if (hasUsername && normalizedUsername.length() > 50) {

return failure("A0441", "username length must be <= 50");

}

UserInfoEntity user = authResult.getData();

if (hasUsername) {

user.setUsername(normalizedUsername);

}

if (hasAvatar) {

String extension = resolveAvatarExtension(avatar);

if (!StringUtils.hasText(extension)) {

return failure("A0413", "avatar format is invalid, only jpg/jpeg/png/gif/webp is supported");

}

String publicBaseUrl = ServletUriComponentsBuilder.fromRequestUri(servletRequest)

.replacePath(servletRequest.getContextPath())

.replaceQuery(null)

.build()

.toUriString();

try {

user.setAvatarUrl(saveAvatarFile(avatar, extension, publicBaseUrl));

} catch (IOException ex) {

return failure("A0414", "avatar upload failed");

}

}

user = userInfoRepository.save(user);

4)健康档案 GET:loadOrInitHealthProfile无则创建

@GetMapping("/health-profile")

public Result<HealthProfileResponse> getHealthProfile(

@RequestHeader(value = "Authorization", required = false) String authorization,

@RequestParam(value = "token", required = false) String tokenParam

) {

Result<UserInfoEntity> authResult = authenticate(authorization, tokenParam);

if (authResult.isFail()) {

return failure(authResult.getCode(), authResult.getMessage());

}

UserInfoEntity user = authResult.getData();

HealthProfileEntity profile = loadOrInitHealthProfile(user.getUserId());

return success(toHealthProfileResponse(profile), "get health profile success");

}

private HealthProfileEntity loadOrInitHealthProfile(Long userId) {

Optional<HealthProfileEntity> optionalProfile = healthProfileRepository.findByUserId(userId);

if (optionalProfile.isPresent()) {

return optionalProfile.get();

}

HealthProfileEntity profile = new HealthProfileEntity();

profile.setUserId(userId);

return healthProfileRepository.save(profile);

}

5)独立上传:fileType白名单 + 可选messageId+ 写FileIndex

@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)

public Result<FileUploadResponse> upload(

@RequestHeader(value = "Authorization", required = false) String authorization,

@RequestParam(value = "token", required = false) String tokenParam,

@RequestParam("fileType") String fileType,

@RequestParam("file") MultipartFile file,

@RequestParam(value = "messageId", required = false) Long messageId,

HttpServletRequest servletRequest

) {

Result<UserInfoEntity> authResult = authenticate(authorization, tokenParam);

if (authResult.isFail()) {

return failure(authResult.getCode(), authResult.getMessage());

}

String normalizedFileType = trimToNull(fileType);

if (!StringUtils.hasText(normalizedFileType)) {

return failure("A0430", "fileType is required");

}

normalizedFileType = normalizedFileType.toUpperCase(Locale.ROOT);

if (!ALLOWED_FILE_TYPES.contains(normalizedFileType)) {

return failure("A0430", "fileType must be SYMPTOM_PIC, VOICE or PRESCRIPTION");

}

if (file == null || file.isEmpty()) {

return failure("A0431", "file is required");

}

String fileId = UUID.randomUUID().toString().replace("-", "");

String extension = resolveExtension(file.getOriginalFilename());

String storedName = StringUtils.hasText(extension) ? fileId + "." + extension : fileId;

Path rootPath = Paths.get(uploadRootDir).toAbsolutePath().normalize();

Path fileDirPath = rootPath.resolve(FILE_SUB_DIR).normalize();

Path targetPath = fileDirPath.resolve(storedName).normalize();

if (!targetPath.startsWith(fileDirPath)) {

return failure("A0434", "invalid file path");

}

try {

Files.createDirectories(fileDirPath);

file.transferTo(targetPath.toFile());

} catch (IOException ex) {

return failure("A0435", "save file failed");

}

String baseUrl = ServletUriComponentsBuilder.fromRequestUri(servletRequest)

.replacePath(servletRequest.getContextPath())

.replaceQuery(null)

.build()

.toUriString();

String objectUrl = baseUrl + PREFIX_UPLOADS + FILE_SUB_DIR + "/" + storedName;

FileIndexEntity entity = new FileIndexEntity();

entity.setFileId(fileId);

entity.setUserId(authResult.getData().getUserId());

entity.setMessageId(messageId);

entity.setFileType(normalizedFileType);

entity.setObjectUrl(objectUrl);

entity.setFileSize((int) Math.max(1, Math.ceil(file.getSize() / 1024.0)));

entity = fileIndexRepository.save(entity);

6)下载:按fileId+userId查归属,路径规范化防穿越

Optional<FileIndexEntity> optionalFile = fileIndexRepository.findByFileIdAndUserId(normalizedFileId, authResult.getData().getUserId());

if (optionalFile.isEmpty()) {

return ResponseEntity.status(HttpStatus.NOT_FOUND).body(failure("A0432", "file not found"));

}

FileIndexEntity entity = optionalFile.get();

Path filePath = resolveStoredFilePath(entity.getObjectUrl());

if (filePath == null || !Files.exists(filePath)) {

return ResponseEntity.status(HttpStatus.NOT_FOUND).body(failure("A0433", "file resource not found"));

}

try {

Resource resource = new UrlResource(filePath.toUri());

String contentType = Files.probeContentType(filePath);

if (!StringUtils.hasText(contentType)) {

contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;

}

return ResponseEntity.ok()

.contentType(MediaType.parseMediaType(contentType))

.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filePath.getFileName() + "\"")

.body(resource);

} catch (MalformedURLException ex) {

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(failure("A0436", "file resource open failed"));

} catch (IOException ex) {

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(failure("A0437", "file content type detect failed"));

}

}

private Path resolveStoredFilePath(String objectUrl) {

String normalizedPath = trimToNull(objectUrl);

if (!StringUtils.hasText(normalizedPath)) {

return null;

}

if (normalizedPath.startsWith("http://") || normalizedPath.startsWith("https://")) {

normalizedPath = URI.create(normalizedPath).getPath();

}

if (!normalizedPath.startsWith(PREFIX_UPLOADS)) {

return null;

}

String relativePath = normalizedPath.substring(PREFIX_UPLOADS.length());

Path rootPath = Paths.get(uploadRootDir).toAbsolutePath().normalize();

Path filePath = rootPath.resolve(relativePath).normalize();

if (!filePath.startsWith(rootPath)) {

return null;

}

return filePath;

}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/8 21:48:46

ProdMan:为AI原生PM打造的结构化工作流与产品记忆框架

1. 项目概述&#xff1a;一个为AI原生产品经理设计的结构化工作流框架如果你正在用Claude Code、Cursor这类AI编码助手来构建产品&#xff0c;那你一定经历过这种循环&#xff1a;每次打开一个新对话&#xff0c;都得把产品背景、用户画像、技术栈限制从头到尾再解释一遍&#…

作者头像 李华
网站建设 2026/5/8 21:44:32

长期使用Taotoken聚合API对项目月度账单清晰度的感受

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 长期使用Taotoken聚合API对项目月度账单清晰度的感受 1. 项目成本管理的初始挑战 在引入大模型能力到项目开发的早期阶段&#xf…

作者头像 李华
网站建设 2026/5/8 21:44:32

【布局优化】基于改进SLP与遗传算法的梁场布局优化附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长毕业设计辅导、数学建模、数据处理、程序设计科研仿真。 &#x1f34e;完整代码获取 定制创新 论文复现点击&#xff1a;Matlab科研工作室 &#x1f447; 关注我领取海量matlab电子书和数学建模资料 &…

作者头像 李华
网站建设 2026/5/8 21:36:38

终极鸣潮自动化指南:开源工具OK-WW如何解放你的双手

终极鸣潮自动化指南&#xff1a;开源工具OK-WW如何解放你的双手 【免费下载链接】ok-wuthering-waves 鸣潮 后台自动战斗 自动刷声骸 一键日常 Automation for Wuthering Waves 项目地址: https://gitcode.com/GitHub_Trending/ok/ok-wuthering-waves 你是否厌倦了在《鸣…

作者头像 李华
网站建设 2026/5/8 21:32:34

Rust代码可视化:基于rustc语义分析生成精准调用关系图

1. 项目概述与核心价值最近在梳理一个中型Rust项目的代码依赖和架构时&#xff0c;我遇到了一个挺典型的痛点&#xff1a;虽然cargo的依赖管理很强大&#xff0c;但当你想要直观地理解模块间的调用关系、特别是那些跨越多个crate的复杂交互时&#xff0c;光看Cargo.toml和代码文…

作者头像 李华