目录
1、特点:接口规划、编译器自动类型检查、自动代码生成。但是客户端和服务端耦合性大
JavaSpring项目使用Maven管理所有微服务先继承实现父模块依赖,根据自己模块业务再附加依赖。common-lib公共共享模块,服务开发封装类。

每个服务由两个模块组成,接口模块和服务模块,例如account-aip、account-svc,一般api接口模块上传到maven仓库进行管理,api为强类型客户端,其他服务调用相关服务的api即可。
除了服务外还有config目录进行对私密文件的存储,使用spring的本地私密配置方式,config里面的私密控制不会被监听到版本控制。

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
> 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互转:
转换工具地址:https://github.com/modelmapper/modelmapper
modelmap使用详情:https://blog.csdn.net/m0_54797663/article/details/115277264
通过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
map; -
- static {
- map = new HashMap
(); - 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中。
SpringBoot引入swagger依赖
-
io.springfox -
springfox-swagger2 -
-
-
io.springfox -
springfox-swagger-ui -
- 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();
- }
- }
