本文是 极客时间 杨波 《Spring Boot 与 Kubernetes 云原生微服务实践》 的学习笔记。如果对原课程有兴趣,欢迎扫码订阅(见文章末尾)
@FeignClient(name = "account-service", path = "/v1/account", url = "${staffjoy.account-service-endpoint}")
// TODO Client side validation can be enabled as needed
// @Validated
public interface AccountClient {
@PostMapping(path = "/create")
GenericAccountResponse createAccount(@RequestHeader(AuthConstant.AUTHORIZATION_HEADER) String authz, @RequestBody @Valid CreateAccountRequest request);
}
@RestController
@RequestMapping("/v1/account")
@Validated
public class AccountController {
@PostMapping(path = "/create")
@Authorize(value = {
AuthConstant.AUTHORIZATION_SUPPORT_USER,
AuthConstant.AUTHORIZATION_WWW_SERVICE,
AuthConstant.AUTHORIZATION_COMPANY_SERVICE
})
public GenericAccountResponse createAccount(@RequestBody @Valid CreateAccountRequest request) {
AccountDto accountDto = accountService.create(request.getName(), request.getEmail(), request.getPhoneNumber());
GenericAccountResponse genericAccountResponse = new GenericAccountResponse(accountDto);
return genericAccountResponse;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CreateAccountRequest {
private String name;
@Email(message = "Invalid email")
private String email;
@PhoneNumber
private String phoneNumber;
@AssertTrue(message = "Empty request")
private boolean isValidRequest() {
return StringUtils.hasText(name) || StringUtils.hasText(email) || StringUtils.hasText(phoneNumber);
}
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class GenericAccountResponse extends BaseResponse {
private AccountDto account;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class ListAccountResponse extends BaseResponse {
private AccountList accountList;
}
@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PhoneNumber {
String message() default "Invalid phone number";
Class[] groups() default {};
Class[] payload() default {};
}
public class PhoneNumberValidator implements ConstraintValidator {
@Override
public boolean isValid(String phoneField, ConstraintValidatorContext context) {
if (phoneField == null) return true; // can be null
return phoneField != null && phoneField.matches("[0-9]+")
&& (phoneField.length() > 8) && (phoneField.length() < 14);
}
}
Spring Boot 错误异常梳理
Spring mvc 错误异常处理
View Object
Data Transfer Object
Data Model Object
Domain Object
Persistent Object
互转 ModelMapper
:
public class AccountService {
private AccountDto convertToDto(Account account) {
return modelMapper.map(account, AccountDto.class);
}
private Account convertToModel(AccountDto accountDto) {
return modelMapper.map(accountDto, Account.class);
}
}
EnvConfig
Json Web Token
JWT: Header+Payload+Signature
base64(Header)+"."+base64(Payload)+"."+base64(Signature)
不保证传输的安全性,但保证其不可篡改性
调用网关必须进行Auth,内部服务之间,只需要带header,比较服务请求是谁就可以了(user_id)
feign源码剖析
Feign使用教程
@PostConstruct
public void init() {
// init structured logging
StructLog4J.setFormatter(JsonFormatter.getInstance());
// global log fields setting, 所有日志都会包含env, service 信息
StructLog4J.setMandatoryContextSupplier(() -> new Object[]{
"env", activeProfile,
"service", appName});
}
public AccountDto create(String name, String email, String phoneNumber) {
// ...
LogEntry auditLog = LogEntry.builder()
.authorization(AuthContext.getAuthz())
.currentUserId(AuthContext.getUserId())
.targetType("account")
.targetId(account.getId())
.updatedContents(account.toString())
.build();
logger.info("created account", auditLog);
// ...
}
@Bean
public SentryClient sentryClient() {
SentryClient sentryClient = Sentry.init(staffjoyProps.getSentryDsn());
sentryClient.setEnvironment(activeProfile);
sentryClient.setRelease(staffjoyProps.getDeployEnv());
sentryClient.addTag("service", appName);
return sentryClient;
}
@PreDestroy
public void destroy() {
sentryClient().closeConnection();
}
@ConfigurationProperties(prefix="staffjoy.common")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StaffjoyProps {
@NotBlank
private String sentryDsn;
@NotBlank
// DeployEnvVar is set by Kubernetes during a new deployment so we can identify the code version
private String deployEnv;
}
public void handleError(ILogger log, String errMsg) {
log.error(errMsg);
if (!envConfig.isDebug()) {
sentryClient.sendMessage(errMsg);
}
}
public void handleException(ILogger log, Exception ex, String errMsg) {
log.error(errMsg, ex);
if (!envConfig.isDebug()) {
sentryClient.sendException(ex);
}
}