SpringCloud Alibaba无人售货柜实战(三):基础公共模块搭建——统一返回、异常、工具类、常量

发布时间:2026/8/2 1:28:32

SpringCloud Alibaba无人售货柜实战(三):基础公共模块搭建——统一返回、异常、工具类、常量 SpringCloud Alibaba无人售货柜实战三基础公共模块搭建——统一返回、异常、工具类、常量写代码最怕什么一百个接口一百种返回格式。今天把公共模块搭好后面七个微服务直接引依赖就行。一、公共模块的意义微服务项目最容易出现的问题每个服务各写各的返回格式、各搞各的异常处理、各造各的工具类。结果就是——改一个返回字段要翻七个代码仓库。公共模块common解决三个问题代码复用Result、工具类、常量写一次到处用统一规范所有接口返回格式一致前端不用猜减少重复异常处理、Feign配置等公共逻辑集中管理二、项目工程结构采用 Maven 多模块结构父POM统一管理版本vending-machine/ ├── pom.xml # 父POM管版本 ├── vending-common/ # 公共模块 │ ├── pom.xml │ └── src/main/java/com/heipiao/common/ │ ├── result/ # 统一返回 │ │ ├── Result.java │ │ └── PageResult.java │ ├── exception/ # 异常体系 │ │ ├── BusinessException.java │ │ ├── SystemException.java │ │ └── GlobalExceptionHandler.java │ ├── errorcode/ # 错误码 │ │ ├── ErrorCode.java │ │ ├── SystemErrorCode.java │ │ ├── ProductErrorCode.java │ │ └── OrderErrorCode.java │ ├── constant/ # 常量枚举 │ │ ├── OrderStatus.java │ │ ├── PayStatus.java │ │ └── DeviceStatus.java │ ├── context/ # 请求上下文 │ │ ├── UserContext.java │ │ └── UserContextHolder.java │ ├── utils/ # 工具类 │ │ ├── JwtUtils.java │ │ ├── RedisUtils.java │ │ ├── MqttUtils.java │ │ └── DateUtils.java │ └── feign/ # Feign配置 │ ├── FeignAuthInterceptor.java │ └── FeignConfig.java ├── vending-gateway/ # 网关 ├── vending-device-service/ # 设备服务 ├── vending-product-service/ # 商品服务 ├── vending-order-service/ # 订单服务 └── ...其他服务三、统一返回封装3.1 Result类所有接口统一返回ResultT前端只需要判断code 200。DataSchema(description统一返回结果)publicclassResultTimplementsSerializable{Schema(description状态码)privateIntegercode;Schema(description提示信息)privateStringmessage;Schema(description数据)privateTdata;Schema(description时间戳)privateLongtimestamp;privateResult(){this.timestampSystem.currentTimeMillis();}publicstaticTResultTsuccess(){returnsuccess(null);}publicstaticTResultTsuccess(Tdata){ResultTresultnewResult();result.setCode(200);result.setMessage(success);result.setData(data);returnresult;}publicstaticTResultTsuccess(Tdata,Stringmessage){ResultTresultnewResult();result.setCode(200);result.setMessage(message);result.setData(data);returnresult;}publicstaticTResultTfail(Integercode,Stringmessage){ResultTresultnewResult();result.setCode(code);result.setMessage(message);returnresult;}publicstaticTResultTfail(ErrorCodeerrorCode){returnfail(errorCode.getCode(),errorCode.getMessage());}}3.2 PageResult 分页返回DataSchema(description分页返回结果)publicclassPageResultTimplementsSerializable{Schema(description当前页)privateLongpageNum;Schema(description每页条数)privateLongpageSize;Schema(description总条数)privateLongtotal;Schema(description总页数)privateLongpages;Schema(description数据列表)privateListTlist;publicstaticTPageResultTof(ListTlist,Longtotal,LongpageNum,LongpageSize){PageResultTresultnewPageResult();result.setList(list);result.setTotal(total);result.setPageNum(pageNum);result.setPageSize(pageSize);result.setPages((totalpageSize-1)/pageSize);returnresult;}}四、统一错误码体系错误码不是随便编的要有规律。格式模块(2位) 类型(2位) 序号(3位)。publicinterfaceErrorCode{IntegergetCode();StringgetMessage();}GetterAllArgsConstructorpublicenumSystemErrorCodeimplementsErrorCode{SUCCESS(20000,操作成功),UNKNOWN_ERROR(50000,系统未知异常),PARAM_INVALID(40001,参数校验失败),UNAUTHORIZED(40100,未登录或token已过期),FORBIDDEN(40300,无权限访问),SERVICE_UNAVAILABLE(50300,服务暂时不可用),GATEWAY_TIMEOUT(50400,网关请求超时);privatefinalIntegercode;privatefinalStringmessage;}GetterAllArgsConstructorpublicenumOrderErrorCodeimplementsErrorCode{ORDER_NOT_FOUND(30001,订单不存在),ORDER_STATUS_INVALID(30002,订单状态不允许此操作),ORDER_ALREADY_PAID(30003,订单已支付请勿重复操作),DEVICE_BUSY(30004,设备正忙请稍后再试),STOCK_NOT_ENOUGH(30005,库存不足),PAY_FAILED(30006,支付失败);privatefinalIntegercode;privatefinalStringmessage;}模块编号系统20/50商品10订单30支付40设备50用户60。一眼看错误码就知道是哪个服务出了问题。五、全局异常处理5.1 自定义异常GetterpublicclassBusinessExceptionextendsRuntimeException{privatefinalIntegercode;publicBusinessException(ErrorCodeerrorCode){super(errorCode.getMessage());this.codeerrorCode.getCode();}publicBusinessException(ErrorCodeerrorCode,Stringmessage){super(message);this.codeerrorCode.getCode();}}GetterpublicclassSystemExceptionextendsRuntimeException{privatefinalIntegercode;publicSystemException(ErrorCodeerrorCode){super(errorCode.getMessage());this.codeerrorCode.getCode();}}区分原则BusinessException是用户操作导致的比如库存不足SystemException是系统内部错误比如数据库连接失败。5.2 全局异常处理器RestControllerAdviceSlf4jpublicclassGlobalExceptionHandler{ExceptionHandler(BusinessException.class)publicResultVoidhandleBusiness(BusinessExceptione){log.warn(业务异常: code{}, msg{},e.getCode(),e.getMessage());returnResult.fail(e.getCode(),e.getMessage());}ExceptionHandler(SystemException.class)publicResultVoidhandleSystem(SystemExceptione){log.error(系统异常: code{}, msg{},e.getCode(),e.getMessage());returnResult.fail(e.getCode(),e.getMessage());}ExceptionHandler(MethodArgumentNotValidException.class)publicResultVoidhandleValidation(MethodArgumentNotValidExceptione){Stringmsge.getBindingResult().getFieldErrors().stream().map(error-error.getField(): error.getDefaultMessage()).collect(Collectors.joining(; ));returnResult.fail(SystemErrorCode.PARAM_INVALID.getCode(),msg);}ExceptionHandler(Exception.class)publicResultVoidhandleUnknown(Exceptione){log.error(未知异常,e);returnResult.fail(SystemErrorCode.UNKNOWN_ERROR);}}六、常量枚举GetterAllArgsConstructorpublicenumOrderStatus{WAITING_OPEN(0,待开柜),OPENED(1,已开柜),WAITING_SETTLE(2,已关门待结算),PAID(3,已支付),CANCELLED(4,已取消),PAY_FAILED(5,支付失败),REFUNDED(6,已退款);privatefinalIntegercode;privatefinalStringdesc;publicstaticOrderStatusof(Integercode){for(OrderStatuss:values()){if(s.code.equals(code)){returns;}}thrownewBusinessException(SystemErrorCode.PARAM_INVALID,无效的订单状态: code);}}GetterAllArgsConstructorpublicenumDeviceStatus{OFFLINE(0,离线),ONLINE(1,在线),FAULT(2,故障),MAINTENANCE(3,维护中);}七、请求上下文 UserContext网关解析JWT后把用户信息通过HTTP Header透传到下游服务。下游服务用拦截器取出存入ThreadLocal。DatapublicclassUserContext{privateLonguserId;privateStringopenid;privateStringnickname;privateStringrole;privatestaticfinalThreadLocalUserContextHOLDERnewThreadLocal();publicstaticvoidset(UserContextcontext){HOLDER.set(context);}publicstaticUserContextget(){returnHOLDER.get();}publicstaticLonggetUserId(){UserContextctxHOLDER.get();returnctx!null?ctx.getUserId():null;}publicstaticvoidclear(){HOLDER.remove();}}配合拦截器使用ComponentSlf4jpublicclassUserContextInterceptorimplementsHandlerInterceptor{OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler){StringuserIdrequest.getHeader(X-User-Id);Stringopenidrequest.getHeader(X-User-Openid);Stringrolerequest.getHeader(X-User-Role);if(StrUtil.isNotBlank(userId)){UserContextcontextnewUserContext();context.setUserId(Long.valueOf(userId));context.setOpenid(openid);context.setRole(role);UserContext.set(context);}returntrue;}OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex){UserContext.clear();}}八、Feign公共配置微服务间Feign调用时需要把当前用户信息透传下去否则下游服务拿不到用户ID。ComponentpublicclassFeignAuthInterceptorimplementsRequestInterceptor{Overridepublicvoidapply(RequestTemplatetemplate){UserContextcontextUserContext.get();if(context!null){if(context.getUserId()!null){template.header(X-User-Id,String.valueOf(context.getUserId()));}if(StrUtil.isNotBlank(context.getOpenid())){template.header(X-User-Openid,context.getOpenid());}if(StrUtil.isNotBlank(context.getRole())){template.header(X-User-Role,context.getRole());}}}}这段代码解决了微服务调用链上的用户身份传递问题。A服务调B服务时Feign拦截器自动把用户Header带过去B服务的拦截器再解析存入ThreadLocal。九、父POM依赖管理父POM用dependencyManagement统一版本子模块引依赖不写versionpropertiesjava.version17/java.versionspring-boot.version3.2.0/spring-boot.versionspring-cloud.version2023.0.0/spring-cloud.versionspring-cloud-alibaba.version2023.0.1.0/spring-cloud-alibaba.versionmybatis-plus.version3.5.5/mybatis-plus.versionhutool.version5.8.25/hutool.version/propertiesdependencyManagementdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-dependencies/artifactIdversion${spring-boot.version}/versiontypepom/typescopeimport/scope/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-dependencies/artifactIdversion${spring-cloud.version}/versiontypepom/typescopeimport/scope/dependencydependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-alibaba-dependencies/artifactIdversion${spring-cloud-alibaba.version}/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagement十、小结公共模块看着不起眼但它是整个项目的基础设施。Result统一了返回格式ErrorCode统一了错误体系GlobalExceptionHandler统一了异常处理UserContext Feign拦截器打通了用户身份传递。这些搭好了后面写业务代码就像搭积木一样——引依赖、写Controller、调Service干净利落。

相关新闻