2、云原生微服务实践-服务开发框架设计和实践

news2025/7/13 22:56:30

目录

一、依赖管理

二、服务模块管理 api、svc

三、其他文件管理

1、私密配置文件

2、前端页面单页文件

四、单体仓库 mono-repo

1、单体仓库和多仓库的对比: 

2、单体仓库优点

五、接口参数校验

六、统一异常处理

七、DTO(数据传输对象)和DMO(数据模型对象)

八、强类型接口设计

1、特点:接口规划、编译器自动类型检查、自动代码生成。但是客户端和服务端耦合性大

2、Spring Feign


一、依赖管理

JavaSpring项目使用Maven管理所有微服务先继承实现父模块依赖,根据自己模块业务再附加依赖。common-lib公共共享模块,服务开发封装类。

二、服务模块管理 api、svc

每个服务由两个模块组成,接口模块和服务模块,例如account-aip、account-svc,一般api接口模块上传到maven仓库进行管理,api为强类型客户端,其他服务调用相关服务的api即可。

三、其他文件管理

1、私密配置文件

除了服务外还有config目录进行对私密文件的存储,使用spring的本地私密配置方式,config里面的私密控制不会被监听到版本控制。

2、前端页面单页文件

四、单体仓库 mono-repo

1、单体仓库和多仓库的对比: 

2、单体仓库优点

  • 易于规范代码风格
  • 易于集成和部署,配合构建工具可以一键部署
  • 易于理解,方便开发人员把握整体建构
  • 易于复用,可以抽取公共功能,进行重构

五、接口参数校验

spring有成套且成熟的校验注解,开发成员只需要填写注释。开发人员也可以根据自己的业务校验规则定义注解,实现ConstraintValidator,重写校验规则即可。

例如:

六、统一异常处理

package xyz.staffjoy.common.error;

import com.github.structlog4j.ILogger;
import com.github.structlog4j.SLoggerFactory;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.NoHandlerFoundException;
import xyz.staffjoy.common.api.BaseResponse;
import xyz.staffjoy.common.api.ResultCode;
import xyz.staffjoy.common.auth.PermissionDeniedException;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;

@RestControllerAdvice
public class GlobalExceptionTranslator {

    static final ILogger logger = SLoggerFactory.getLogger(GlobalExceptionTranslator.class);

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public BaseResponse handleError(MissingServletRequestParameterException e) {
        logger.warn("Missing Request Parameter", e);
        String message = String.format("Missing Request Parameter: %s", e.getParameterName());
        return BaseResponse
                .builder()
                .code(ResultCode.PARAM_MISS)
                .message(message)
                .build();
    }

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public BaseResponse handleError(MethodArgumentTypeMismatchException e) {
        logger.warn("Method Argument Type Mismatch", e);
        String message = String.format("Method Argument Type Mismatch: %s", e.getName());
        return BaseResponse
                .builder()
                .code(ResultCode.PARAM_TYPE_ERROR)
                .message(message)
                .build();
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public BaseResponse handleError(MethodArgumentNotValidException e) {
        logger.warn("Method Argument Not Valid", e);
        BindingResult result = e.getBindingResult();
        FieldError error = result.getFieldError();
        String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
        return BaseResponse
                .builder()
                .code(ResultCode.PARAM_VALID_ERROR)
                .message(message)
                .build();
    }

    @ExceptionHandler(BindException.class)
    public BaseResponse handleError(BindException e) {
        logger.warn("Bind Exception", e);
        FieldError error = e.getFieldError();
        String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
        return BaseResponse
                .builder()
                .code(ResultCode.PARAM_BIND_ERROR)
                .message(message)
                .build();
    }

    @ExceptionHandler(ConstraintViolationException.class)
    public BaseResponse handleError(ConstraintViolationException e) {
        logger.warn("Constraint Violation", e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        ConstraintViolation<?> violation = violations.iterator().next();
        String path = ((PathImpl) violation.getPropertyPath()).getLeafNode().getName();
        String message = String.format("%s:%s", path, violation.getMessage());
        return BaseResponse
                .builder()
                .code(ResultCode.PARAM_VALID_ERROR)
                .message(message)
                .build();
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public BaseResponse handleError(NoHandlerFoundException e) {
        logger.error("404 Not Found", e);
        return BaseResponse
                .builder()
                .code(ResultCode.NOT_FOUND)
                .message(e.getMessage())
                .build();
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public BaseResponse handleError(HttpMessageNotReadableException e) {
        logger.error("Message Not Readable", e);
        return BaseResponse
                .builder()
                .code(ResultCode.MSG_NOT_READABLE)
                .message(e.getMessage())
                .build();
    }

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public BaseResponse handleError(HttpRequestMethodNotSupportedException e) {
        logger.error("Request Method Not Supported", e);
        return BaseResponse
                .builder()
                .code(ResultCode.METHOD_NOT_SUPPORTED)
                .message(e.getMessage())
                .build();
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public BaseResponse handleError(HttpMediaTypeNotSupportedException e) {
        logger.error("Media Type Not Supported", e);
        return BaseResponse
                .builder()
                .code(ResultCode.MEDIA_TYPE_NOT_SUPPORTED)
                .message(e.getMessage())
                .build();
    }

    @ExceptionHandler(ServiceException.class)
    public BaseResponse handleError(ServiceException e) {
        logger.error("Service Exception", e);
        return BaseResponse
                .builder()
                .code(e.getResultCode())
                .message(e.getMessage())
                .build();
    }

    @ExceptionHandler(PermissionDeniedException.class)
    public BaseResponse handleError(PermissionDeniedException e) {
        logger.error("Permission Denied", e);
        return BaseResponse
                .builder()
                .code(e.getResultCode())
                .message(e.getMessage())
                .build();
    }

    @ExceptionHandler(Throwable.class)
    public BaseResponse handleError(Throwable e) {
        logger.error("Internal Server Error", e);
        return BaseResponse
                .builder()
                .code(ResultCode.INTERNAL_SERVER_ERROR)
                .message(e.getMessage())
                .build();
    }
}

package xyz.staffjoy.common.error;

import lombok.Getter;
import xyz.staffjoy.common.api.ResultCode;

/**
 * Business Service Exception
 *
 * @author william
 */
public class ServiceException extends RuntimeException {
    private static final long serialVersionUID = 2359767895161832954L;

    @Getter
    private final ResultCode resultCode;

    public ServiceException(String message) {
        super(message);
        this.resultCode = ResultCode.FAILURE;
    }

    public ServiceException(ResultCode resultCode) {
        super(resultCode.getMsg());
        this.resultCode = resultCode;
    }

    public ServiceException(ResultCode resultCode, String msg) {
        super(msg);
        this.resultCode = resultCode;
    }

    public ServiceException(ResultCode resultCode, Throwable cause) {
        super(cause);
        this.resultCode = resultCode;
    }

    public ServiceException(String msg, Throwable cause) {
        super(msg, cause);
        this.resultCode = ResultCode.FAILURE;
    }

    /**
     * for better performance
     *
     * @return Throwable
     */
    @Override
    public Throwable fillInStackTrace() {
        return this;
    }

    public Throwable doFillInStackTrace() {
        return super.fillInStackTrace();
    }
}

七、DTO(数据传输对象)和DMO(数据模型对象)

dto与dmo互转:

转换工具地址:https://github.com/modelmapper/modelmapper

modelmap使用详情:https://blog.csdn.net/m0_54797663/article/details/115277264

八、强类型接口设计

1、特点:接口规划、编译器自动类型检查、自动代码生成。但是客户端和服务端耦合性大

通过Spring Feign结合强、若类型的结合,既能达到强类型接口的规范性又能达到弱类型接口的解耦性。

2、Spring Feign

注意:返回类型如果根据泛型进行包装的,springfeign是无法解析的,因为编译之后的泛型会被擦除!  

九、环境隔离

public class EnvConstant {
    public static final String ENV_DEV = "dev";
    public static final String ENV_TEST = "test";
    public static final String ENV_UAT = "uat"; // similar to staging
    public static final String ENV_PROD = "prod";
}
package xyz.staffjoy.common.env;

import lombok.*;

import java.util.HashMap;
import java.util.Map;

// environment related configuration
@Data
@Builder
public class EnvConfig {

    private String name;
    private boolean debug;
    private String externalApex;
    private String internalApex;
    private String scheme;

    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    private static Map<String, EnvConfig> map;

    static {
        map = new HashMap<String, EnvConfig>();
        EnvConfig envConfig = EnvConfig.builder().name(EnvConstant.ENV_DEV)
                .debug(true)
                .externalApex("staffjoy-v2.local")
                .internalApex(EnvConstant.ENV_DEV)
                .scheme("http")
                .build();
        map.put(EnvConstant.ENV_DEV, envConfig);

        envConfig = EnvConfig.builder().name(EnvConstant.ENV_TEST)
                .debug(true)
                .externalApex("staffjoy-v2.local")
                .internalApex(EnvConstant.ENV_DEV)
                .scheme("http")
                .build();
        map.put(EnvConstant.ENV_TEST, envConfig);

        // for aliyun k8s demo, enable debug and use http and staffjoy-uat.local
        // in real world, disable debug and use http and staffjoy-uat.xyz in UAT environment
        envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT)
                .debug(true)
                .externalApex("staffjoy-uat.local")
                .internalApex(EnvConstant.ENV_UAT)
                .scheme("http")
                .build();
        map.put(EnvConstant.ENV_UAT, envConfig);

//        envConfig = EnvConfig.builder().name(EnvConstant.ENV_UAT)
//                .debug(false)
//                .externalApex("staffjoy-uat.xyz")
//                .internalApex(EnvConstant.ENV_UAT)
//                .scheme("https")
//                .build();
//        map.put(EnvConstant.ENV_UAT, envConfig);

        envConfig = EnvConfig.builder().name(EnvConstant.ENV_PROD)
                .debug(false)
                .externalApex("staffjoy.com")
                .internalApex(EnvConstant.ENV_PROD)
                .scheme("https")
                .build();
        map.put(EnvConstant.ENV_PROD, envConfig);
    }

    public static EnvConfig getEnvConfg(String env) {
        EnvConfig envConfig = map.get(env);
        if (envConfig == null) {
            envConfig = map.get(EnvConstant.ENV_DEV);
        }
        return envConfig;
    }
}

十、异步调用处理

配置异步线程池:

package xyz.staffjoy.account.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import xyz.staffjoy.common.async.ContextCopyingDecorator;
import xyz.staffjoy.common.config.StaffjoyRestConfig;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
@Import(value = {StaffjoyRestConfig.class})
@SuppressWarnings(value = "Duplicates")
public class AppConfig {

    public static final String ASYNC_EXECUTOR_NAME = "asyncExecutor";

    @Bean(name=ASYNC_EXECUTOR_NAME)
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // for passing in request scope context
        executor.setTaskDecorator(new ContextCopyingDecorator());
        executor.setCorePoolSize(3);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(100);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setThreadNamePrefix("AsyncThread-");
        executor.initialize();
        return executor;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

注意:调用方与被调用方不能在同一个bean中。

十二、集成Swagger文档

SpringBoot引入swagger依赖

<dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
        </dependency>
package xyz.staffjoy.account.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("xyz.staffjoy.account.controller"))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiEndPointsInfo())
                .useDefaultResponseMessages(false);
    }

    private ApiInfo apiEndPointsInfo() {
        return new ApiInfoBuilder().title("Account REST API")
                .description("Staffjoy Account REST API")
                .contact(new Contact("bobo", "https://github.com/jskillcloud", "bobo@jskillcloud.com"))
                .license("The MIT License")
                .licenseUrl("https://opensource.org/licenses/MIT")
                .version("V2")
                .build();
    }
}

 十三、常用框架和比对

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/5674.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

尝试改善科研V2

参考链接&#xff1a; https://fulequn.github.io/2022/09/26/Article202209261/ https://www.xljsci.com/ https://apps.ankiweb.net/ https://www.explainpaper.com/ 1 从动机上促成科研 将科研这件事情分成准备工作、活动本身、活动的结果。 1.1 准备工作 准备工作十分简…

湖南郴州王瑞平、大衣哥、谷传民、孟文豪唱响《知心世界》主题曲

在大衣哥和谷传民对簿公堂之时&#xff0c;来自湖南郴州的王瑞平&#xff0c;准备在他们之间架起一座沟通的桥梁。众所周知&#xff0c;大衣哥和谷传民的官司&#xff0c;是因为《火火的情怀》版权&#xff0c;其实这首歌的版权&#xff0c;并不是只属于谷传民一人。 著名音乐人…

第一个springBoot maven 项目

1. env: java 11 IntelliJ IDEA 2021.3.2 (Community Edition) 2. file->new project->Maven: pom.xml 需要导入的包&#xff1a;后面三个是jdk8升级到11后&#xff0c;可能会出错&#xff0c;需要用到的包 <parent><groupId>org.springframework.boot<…

python面向对象之类和对象相关知识

python面向对象之类和对象相关知识 一、面向对象简介 1、什么是面向对象 面向对象是一种编程思想&#xff0c;把数据和对数据的多个操作方法封装在一起组成类&#xff0c;这样通过这个类创建出来的对象,就可以直接调用这些方法了。2、面向对象相关的术语 类&#xff1a;用来…

一次金融APP的解密历程

声明&#xff1a;本文仅限于技术讨论与分享&#xff0c;严禁用于非法途径。若读者因此作出任何危害网络安全行为后果自负&#xff0c;与本号及原作者无关。 前言&#xff1a; 客户仅提供官网下载地址给我们测试。但是由于官网的版本不是最新的&#xff0c;APP会强制你升级。而…

搭建lamp平台

apache安装步骤 检查是否已经rpm安装httpd服务&#xff0c;已安装则卸载服务。 [rootlocalhost ~]# rpm -e rpm -qa | grep httpd --nodeps 开发工具安装 如果编译安装无法执行&#xff0c;可能是开发工具没有安装&#xff0c;执行下面命令即可安装。&#xff08;如已安装则跳…

【springboot进阶】优雅使用 MapStruct 进行类复制

项目中经常会遇到这样的一个情况&#xff1a;从数据库读取到数据&#xff0c;并不是直接返回给前端做展示的&#xff0c;还需要字段的加工&#xff0c;例如记录的时间戳是不需要的、一些敏感数据更是不能等等。传统的做法就是创建一个新的类&#xff0c;然后写一堆的get/set方法…

数据结构【队列】

文章目录&#xff08;一&#xff09;队列定义&#xff08;二&#xff09;队列实现&#xff08;1&#xff09;创建结构体&#xff08;2&#xff09;具体函数实现及解析1.1 初始化队列1.2入队列1.3出队列1.4取队首元素1.5取队尾元素1.6返回队列个数1.7判断是否为空1.8销毁队列&am…

springCloud的 consul的下载与安装

下载地址&#xff1a;Install | Consul | HashiCorp Developer 下载自己需要使用的版本 下载后会有一个exe 文件通过cmd 命令行来执行这个exe 文件consul agent -dev -client0.0.0.0 出现此页面后执行8500 端口 请求地址&#xff1a;http://127.0.0.1:8500/ 出现此页面说明启…

黑苹果入门:必备工具篇

以下给大家汇总的这些软件工具都是我们在安装使用黑苹果过程中可能会用到的&#xff0c;至于使用方法&#xff0c;在这里我就不做过多介绍了。 本次只提供软件下载地址&#xff0c;不提供使用方法&#xff0c;不知道如何使用软件工具的童鞋&#xff0c;可以在百度翻翻相关教程…

第5章 C语言高级的库函数

文章目录文档配套视频讲解链接地址第05章 C库函数5.1 assert.h 断言库5.2 ctype.h 测试和映射字符5.3 math.h 数学库5.4 stdlib.h 标准库1. 字符串转整数、浮点数2. strtod 把字符串中的数字转换成浮点数并返回数字的下一个字符的位置3. strtol 字符串转整数4. strtoul 字符串转…

vue3 antd多级动态菜单(二)后台管理系统(两种方法过滤有无子菜单children)

vue3 antd 多级动态菜单&#xff08;精修版本&#xff09; 两种方法实现对children的筛选相关文章推送&#xff08;供参考&#xff09;场景复现实现效果解决方法hasChildren与noChilren函数过滤v-if v-else判断有无children【推荐】&#x1f525;两种方法公用代码sunmmary下期预…

ESP32 入门笔记06: WIFI时钟 + FreeRTOS+《两只老虎》 (ESP32 for Arduino IDE)

ESP32FreeRTOS Esp32 模块中已经提供了 FreeRTOS&#xff08;实时操作系统&#xff09;固件。 FreeRTOS有助于提高系统性能和管理模块的资源。FreeRTOS允许用户处理多项任务&#xff0c;如测量传感器读数&#xff0c;发出网络请求&#xff0c;控制电机速度等&#xff0c;所有…

靶向肿瘤代谢,助力攻克癌症

肿瘤代谢的简介 肿瘤代谢的起源在于奥托沃伯格 (Otto Warburg) 的假设&#xff0c;他也因发现线粒体呼吸链复合物 IV 而获得 1931 年诺贝尔生理学或医学奖。Warburg 观察到&#xff0c;与正常组织相比&#xff0c;体外癌组织切片使用大量葡萄糖生成乳酸 (即使在有氧的情况下也是…

SBT10100VDC-ASEMI低压降贴片肖特基二极管SBT10100VDC

编辑-Z SBT10100VDC在TO-263封装里采用的2个芯片&#xff0c;其尺寸都是62MIL&#xff0c;是一款低压降贴片肖特基二极管。SBT10100VDC的浪涌电流Ifsm为150A&#xff0c;漏电流(Ir)为4uA&#xff0c;其工作时耐温度范围为-65~150摄氏度。SBT10100VDC采用金属硅芯片材质&#x…

QScintilla代码跳转时indicator工作不正确的问题

首先看我这几个文章&#xff0c;知道一下indicator是什么&#xff0c;以及上下文&#xff1a; https://biao2488890051.blog.csdn.net/article/details/126798996?spm1001.2014.3001.5502 目标&#xff1a; 我现在要做按住 ctrl 鼠标左键点击释放 发生函数/变量的 定义/声明…

Pandas中你一定要掌握的时间序列相关高级功能

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; 数据分析实战系列&#xff1a;https://www.showmeai.tech/tutorials/40 &#x1f4d8; 本文地址&#xff1a;https://www.showmeai.tech/article-detail/389 &#x1f4e2; 声明&#xff1a;版权所有&#xff0c;转…

ceph浅谈

总谈 ceph简介 用上ceph&#xff0c;多台机器的磁盘空间在一起了&#xff0c;在一台机器上就可以看到使用所有空间。 还可以保存多份安全备份 存储先ceph&#xff0c;自我管理修复&#xff0c;跨机房&#xff0c;节点越多&#xff0c;并行化&#xff0c;论上&#xff0c;节点越…

【虚幻引擎UE】UE5 实现相机录制视频并导出(C++调用外部exe)

说明: 该功能暂不支持导出声音。 由于OpenCV3和UE5不太兼容,因此考虑制作外部exe实现视频合成。 一、创建渲染目标 二、创建Actor加场景捕获组件2D 三、创建UE5内的C++代码 1、实现 SavePicToFile 导出图片蓝图函数 .cpp文件 // Fill out your copyright notice in the De…

数字集成电路设计(二、Verilog HDL基础知识)

文章目录1. 语言要素1.1 空白符1.2 注释符1.3 标识符1.3.1 转义标识符1.4 关键字1.5 数值1.5.1 整数及其表示方式1.5.2 实数及其表示方式1.5.3 字符串及其表示方式2. 数据类型2.1 物理数据类型2.1.1 连线型2.1.2 寄存器型2.2 连线型和寄存器型数据类型的声明2.2.1 连线型数据类…