目录
1、特点:接口规划、编译器自动类型检查、自动代码生成。但是客户端和服务端耦合性大
一、依赖管理
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();
-
}
-
}
十三、常用框架和比对
转载:https://blog.csdn.net/kk_lina/article/details/127703463
查看评论