From 4b992c35ca40da5f7ef547c3f619cc2ac02fb319 Mon Sep 17 00:00:00 2001 From: Hanserwei Date: Sat, 4 Oct 2025 15:53:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(oss):=20=E5=AE=9E=E7=8E=B0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=8A=E4=BC=A0=E5=8A=9F=E8=83=BD=E5=B9=B6=E9=9B=86?= =?UTF-8?q?=E6=88=90Feign=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增文件上传接口,支持multipart/form-data格式 - 配置Spring Servlet multipart参数,设置文件大小限制 - 添加Feign客户端配置,支持表单提交 - 实现Feign请求拦截器,传递用户上下文信息 - 创建OSS服务API接口,用于文件上传 - 在用户服务中集成OSS RPC调用,实现头像和背景图上传 - 添加上传失败的业务异常处理 - 更新pom.xml依赖,引入OpenFeign、负载均衡及Feign表单相关组件 - 定义API常量和服务名称 - 启用Feign客户端扫描,支持跨服务调用 --- han-note-oss/han-note-oss-api/pom.xml | 19 ++++++++++++ .../hannote/oss/api/FileFeignApi.java | 26 +++++++++++++++++ .../hannote/oss/config/FeignFormConfig.java | 16 ++++++++++ .../hannote/oss/constant/ApiConstants.java | 9 ++++++ han-note-oss/han-note-oss-biz/pom.xml | 17 +++++++++++ .../oss/controller/FileController.java | 2 ++ .../src/main/resources/application.yml | 4 +++ han-note-user/han-note-user-biz/pom.xml | 4 +++ .../user/biz/HannoteUserBizApplication.java | 2 ++ .../user/biz/enums/ResponseCodeEnum.java | 3 +- .../hannote/user/biz/rpc/OssRpcService.java | 26 +++++++++++++++++ .../biz/service/impl/UserServiceImpl.java | 29 +++++++++++++++++-- .../src/main/resources/application.yml | 4 +++ .../pom.xml | 5 +++- .../config/FeignContextAutoConfiguration.java | 14 +++++++++ .../interceptor/FeignRequestInterceptor.java | 25 ++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 3 +- pom.xml | 12 ++++++++ 18 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/api/FileFeignApi.java create mode 100644 han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/config/FeignFormConfig.java create mode 100644 han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/constant/ApiConstants.java create mode 100644 han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/rpc/OssRpcService.java create mode 100644 hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java/com/hanserwei/framework/biz/context/config/FeignContextAutoConfiguration.java create mode 100644 hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java/com/hanserwei/framework/biz/context/interceptor/FeignRequestInterceptor.java diff --git a/han-note-oss/han-note-oss-api/pom.xml b/han-note-oss/han-note-oss-api/pom.xml index d162cf2..1e2548a 100644 --- a/han-note-oss/han-note-oss-api/pom.xml +++ b/han-note-oss/han-note-oss-api/pom.xml @@ -20,5 +20,24 @@ com.hanserwei hanserwei-common + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + io.github.openfeign.form + feign-form-spring + + + io.github.openfeign.form + feign-form + diff --git a/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/api/FileFeignApi.java b/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/api/FileFeignApi.java new file mode 100644 index 0000000..c5a656c --- /dev/null +++ b/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/api/FileFeignApi.java @@ -0,0 +1,26 @@ +package com.hanserwei.hannote.oss.api; + +import com.hanserwei.framework.common.response.Response; +import com.hanserwei.hannote.oss.config.FeignFormConfig; +import com.hanserwei.hannote.oss.constant.ApiConstants; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.multipart.MultipartFile; + +@FeignClient(name = ApiConstants.SERVICE_NAME, configuration = FeignFormConfig.class) +public interface FileFeignApi { + + String PREFIX = "/file"; + + /** + * 文件上传 + * + * @param file 文件 + * @return 文件上传结果 + */ + @PostMapping(value = PREFIX + "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + Response uploadFile(@RequestPart(value = "file") MultipartFile file); + +} \ No newline at end of file diff --git a/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/config/FeignFormConfig.java b/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/config/FeignFormConfig.java new file mode 100644 index 0000000..4ba89fb --- /dev/null +++ b/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/config/FeignFormConfig.java @@ -0,0 +1,16 @@ +package com.hanserwei.hannote.oss.config; + +import feign.codec.Encoder; +import feign.form.spring.SpringFormEncoder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + + +@Configuration +public class FeignFormConfig { + + @Bean + public Encoder feignFormEncoder() { + return new SpringFormEncoder(); + } +} \ No newline at end of file diff --git a/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/constant/ApiConstants.java b/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/constant/ApiConstants.java new file mode 100644 index 0000000..559f39e --- /dev/null +++ b/han-note-oss/han-note-oss-api/src/main/java/com/hanserwei/hannote/oss/constant/ApiConstants.java @@ -0,0 +1,9 @@ +package com.hanserwei.hannote.oss.constant; + +public interface ApiConstants { + + /** + * 服务名称 + */ + String SERVICE_NAME = "han-note-oss"; +} \ No newline at end of file diff --git a/han-note-oss/han-note-oss-biz/pom.xml b/han-note-oss/han-note-oss-biz/pom.xml index 8d011fd..a4bf379 100644 --- a/han-note-oss/han-note-oss-biz/pom.xml +++ b/han-note-oss/han-note-oss-biz/pom.xml @@ -75,6 +75,23 @@ com.qcloud cos_api + + com.hanserwei + hanserwei-spring-boot-starter-biz-operationlog + + + com.hanserwei + hanserwei-spring-boot-starter-jackson + + + com.hanserwei + han-note-oss-api + + + com.hanserwei + hanserwei-spring-boot-starter-biz-context + + diff --git a/han-note-oss/han-note-oss-biz/src/main/java/com/hanserwei/hannote/oss/controller/FileController.java b/han-note-oss/han-note-oss-biz/src/main/java/com/hanserwei/hannote/oss/controller/FileController.java index 54748cb..fb6a04d 100644 --- a/han-note-oss/han-note-oss-biz/src/main/java/com/hanserwei/hannote/oss/controller/FileController.java +++ b/han-note-oss/han-note-oss-biz/src/main/java/com/hanserwei/hannote/oss/controller/FileController.java @@ -1,5 +1,6 @@ package com.hanserwei.hannote.oss.controller; +import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder; import com.hanserwei.framework.common.response.Response; import com.hanserwei.hannote.oss.service.FileService; import jakarta.annotation.Resource; @@ -21,6 +22,7 @@ public class FileController { @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public Response uploadFile(@RequestPart(value = "file") MultipartFile file) { + log.info("当前用户 ID: {}", LoginUserContextHolder.getUserId()); return fileService.uploadFile(file); } diff --git a/han-note-oss/han-note-oss-biz/src/main/resources/application.yml b/han-note-oss/han-note-oss-biz/src/main/resources/application.yml index a814e02..eb46fe6 100644 --- a/han-note-oss/han-note-oss-biz/src/main/resources/application.yml +++ b/han-note-oss/han-note-oss-biz/src/main/resources/application.yml @@ -4,6 +4,10 @@ server: spring: profiles: active: dev # 默认激活 dev 本地开发环境 + servlet: + multipart: + max-file-size: 20MB # 单个文件最大大小 + max-request-size: 100MB # 单次请求最大大小(包含多个文件) storage: type: rustfs # 对象存储类型 \ No newline at end of file diff --git a/han-note-user/han-note-user-biz/pom.xml b/han-note-user/han-note-user-biz/pom.xml index 3474506..34c572e 100644 --- a/han-note-user/han-note-user-biz/pom.xml +++ b/han-note-user/han-note-user-biz/pom.xml @@ -66,6 +66,10 @@ com.hanserwei hanserwei-spring-boot-starter-biz-context + + com.hanserwei + han-note-oss-api + diff --git a/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/HannoteUserBizApplication.java b/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/HannoteUserBizApplication.java index 21ad5df..dc1639c 100644 --- a/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/HannoteUserBizApplication.java +++ b/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/HannoteUserBizApplication.java @@ -3,9 +3,11 @@ package com.hanserwei.hannote.user.biz; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; @SpringBootApplication @MapperScan("com.hanserwei.hannote.user.biz.domain.mapper") +@EnableFeignClients(basePackages = "com.hanserwei.hannote") public class HannoteUserBizApplication { public static void main(String[] args) { SpringApplication.run(HannoteUserBizApplication.class, args); diff --git a/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/enums/ResponseCodeEnum.java b/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/enums/ResponseCodeEnum.java index 1a61efe..97c1f0f 100644 --- a/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/enums/ResponseCodeEnum.java +++ b/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/enums/ResponseCodeEnum.java @@ -17,7 +17,8 @@ public enum ResponseCodeEnum implements BaseExceptionInterface { HAN_NOTE_ID_VALID_FAIL("USER-20002", "小憨书号请设置6-15个字符,仅可使用英文(必须)、数字、下划线"), SEX_VALID_FAIL("USER-20003", "性别错误"), INTRODUCTION_VALID_FAIL("USER-20004", "个人简介请设置1-100个字符"), - ; + UPLOAD_AVATAR_FAIL("USER-20005", "头像上传失败"), + UPLOAD_BACKGROUND_IMG_FAIL("USER-20006", "背景图上传失败"), ; // 异常码 diff --git a/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/rpc/OssRpcService.java b/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/rpc/OssRpcService.java new file mode 100644 index 0000000..0bd7c4c --- /dev/null +++ b/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/rpc/OssRpcService.java @@ -0,0 +1,26 @@ +package com.hanserwei.hannote.user.biz.rpc; + +import com.hanserwei.framework.common.response.Response; +import com.hanserwei.hannote.oss.api.FileFeignApi; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +@Component +public class OssRpcService { + + @Resource + private FileFeignApi fileFeignApi; + + public String uploadFile(MultipartFile file) { + // 调用对象存储服务上传文件 + Response response = fileFeignApi.uploadFile(file); + + if (!response.isSuccess()) { + return null; + } + + // 返回图片访问链接 + return (String) response.getData(); + } +} \ No newline at end of file diff --git a/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/service/impl/UserServiceImpl.java b/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/service/impl/UserServiceImpl.java index 2052cf3..4544921 100644 --- a/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/service/impl/UserServiceImpl.java +++ b/han-note-user/han-note-user-biz/src/main/java/com/hanserwei/hannote/user/biz/service/impl/UserServiceImpl.java @@ -3,6 +3,7 @@ package com.hanserwei.hannote.user.biz.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.common.base.Preconditions; import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder; +import com.hanserwei.framework.common.exception.ApiException; import com.hanserwei.framework.common.response.Response; import com.hanserwei.framework.common.utils.ParamUtils; import com.hanserwei.hannote.user.biz.domain.dataobject.UserDO; @@ -10,7 +11,9 @@ import com.hanserwei.hannote.user.biz.domain.mapper.UserDOMapper; import com.hanserwei.hannote.user.biz.enums.ResponseCodeEnum; import com.hanserwei.hannote.user.biz.enums.SexEnum; import com.hanserwei.hannote.user.biz.model.vo.UpdateUserInfoReqVO; +import com.hanserwei.hannote.user.biz.rpc.OssRpcService; import com.hanserwei.hannote.user.biz.service.UserService; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -23,6 +26,10 @@ import java.util.Objects; @Service @Slf4j public class UserServiceImpl extends ServiceImpl implements UserService { + + @Resource + private OssRpcService ossRpcService; + @Override public Response updateUserInfo(UpdateUserInfoReqVO updateUserInfoReqVO) { UserDO userDO = new UserDO(); @@ -35,7 +42,16 @@ public class UserServiceImpl extends ServiceImpl implement MultipartFile avatar = updateUserInfoReqVO.getAvatar(); if (Objects.nonNull(avatar)) { - // TODO: 上传头像,调用服务 + String avatarUrl = ossRpcService.uploadFile(avatar); + log.info("==> 调用 oss 服务成功,上传头像,url:{}", avatarUrl); + + // 若上传头像失败,则抛出业务异常 + if (StringUtils.isBlank(avatarUrl)) { + throw new ApiException(ResponseCodeEnum.UPLOAD_AVATAR_FAIL); + } + + userDO.setAvatar(avatarUrl); + needUpdate = true; } // 昵称 @@ -80,7 +96,16 @@ public class UserServiceImpl extends ServiceImpl implement // 背景图片 MultipartFile backgroundImg = updateUserInfoReqVO.getBackgroundImg(); if (Objects.nonNull(backgroundImg)) { - // TODO: 上传背景图片,调用服务 + String backgroundImgUrl = ossRpcService.uploadFile(backgroundImg); + log.info("==> 调用 oss 服务成功,上传背景图,url:{}", backgroundImg); + + // 若上传背景图失败,则抛出业务异常 + if (StringUtils.isBlank(backgroundImgUrl)) { + throw new ApiException(ResponseCodeEnum.UPLOAD_BACKGROUND_IMG_FAIL); + } + + userDO.setBackgroundImg(backgroundImgUrl); + needUpdate = true; } if (needUpdate) { diff --git a/han-note-user/han-note-user-biz/src/main/resources/application.yml b/han-note-user/han-note-user-biz/src/main/resources/application.yml index d30dcbc..e532ea4 100644 --- a/han-note-user/han-note-user-biz/src/main/resources/application.yml +++ b/han-note-user/han-note-user-biz/src/main/resources/application.yml @@ -4,6 +4,10 @@ server: spring: profiles: active: dev # 默认激活 dev 本地开发环境 + servlet: + multipart: + max-file-size: 20MB # 单个文件最大大小 + max-request-size: 100MB # 单次请求最大大小(包含多个文件) mybatis-plus: configuration: map-underscore-to-camel-case: true diff --git a/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/pom.xml b/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/pom.xml index 6f4ca31..93bb8b5 100644 --- a/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/pom.xml +++ b/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/pom.xml @@ -43,6 +43,9 @@ com.alibaba transmittable-thread-local - + + io.github.openfeign + feign-core + diff --git a/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java/com/hanserwei/framework/biz/context/config/FeignContextAutoConfiguration.java b/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java/com/hanserwei/framework/biz/context/config/FeignContextAutoConfiguration.java new file mode 100644 index 0000000..f6547e5 --- /dev/null +++ b/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java/com/hanserwei/framework/biz/context/config/FeignContextAutoConfiguration.java @@ -0,0 +1,14 @@ +package com.hanserwei.framework.biz.context.config; + +import com.hanserwei.framework.biz.context.interceptor.FeignRequestInterceptor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Bean; + +@AutoConfiguration +public class FeignContextAutoConfiguration { + + @Bean + public FeignRequestInterceptor feignRequestInterceptor() { + return new FeignRequestInterceptor(); + } +} \ No newline at end of file diff --git a/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java/com/hanserwei/framework/biz/context/interceptor/FeignRequestInterceptor.java b/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java/com/hanserwei/framework/biz/context/interceptor/FeignRequestInterceptor.java new file mode 100644 index 0000000..fae8810 --- /dev/null +++ b/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/java/com/hanserwei/framework/biz/context/interceptor/FeignRequestInterceptor.java @@ -0,0 +1,25 @@ +package com.hanserwei.framework.biz.context.interceptor; + +import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder; +import com.hanserwei.framework.common.constant.GlobalConstants; +import feign.RequestInterceptor; +import feign.RequestTemplate; +import lombok.extern.slf4j.Slf4j; + +import java.util.Objects; + +@Slf4j +public class FeignRequestInterceptor implements RequestInterceptor { + + @Override + public void apply(RequestTemplate requestTemplate) { + // 获取当前上下文中的用户 ID + Long userId = LoginUserContextHolder.getUserId(); + + // 若不为空,则添加到请求头中 + if (Objects.nonNull(userId)) { + requestTemplate.header(GlobalConstants.USER_ID, String.valueOf(userId)); + log.info("########## feign 请求设置请求头 userId: {}", userId); + } + } +} \ No newline at end of file diff --git a/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index d6d4fbb..98d7e67 100644 --- a/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/hanserwei-framework/hanserwei-spring-boot-starter-biz-context/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1 +1,2 @@ -com.hanserwei.framework.biz.context.config.ContextAutoConfiguration \ No newline at end of file +com.hanserwei.framework.biz.context.config.ContextAutoConfiguration +com.hanserwei.framework.biz.context.config.FeignContextAutoConfiguration \ No newline at end of file diff --git a/pom.xml b/pom.xml index f65c49e..616dc0f 100755 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ 1.1.1 2.3.3 5.6.227 + 3.8.0 @@ -203,6 +204,17 @@ cos_api ${cos-api.version} + + com.hanserwei + han-note-oss-api + ${revision} + + + + io.github.openfeign.form + feign-form + ${feign-form.version} +