概述
微服务的概念
微服务架构就是吧一个大系统按业务功能分解成多个职责单一的小系统,每一个小模块尽量专一地只做一件事情,并利用简单的方法使得多个小系统相互协作,组合成一个大系统之后再统一对外提供整体服务。
SpringCloud的主要维度
- 服务注册与发现
- 服务调用和负载均衡
- 分布式事务
- 服务熔断和降级
- 服务链路追踪
- 服务网关
- 分布式配置管理
附:Swagger3的使用
常用注解
- @Tag:标注在controller类上,用于标识controller的作用
- @Parameter:标注在参数旁,用于标注参数的作用
- @Parameters:标注在参数旁,用于参数的多重说明
- @Schema:标注在model层的JavaBean,用于描述模型作用以及每个属性的作用
- @Operation:标注在方法上,表述方法的作用
- @ApiResponse:标注在方法上,描述相应状态码
注解案例:
controller类:
@RestController
@Slf4j
@Tag(name = "支付微服务模块",description = "支付crud")
public class PayController {
@Resource
private PayService payService;
@PostMapping(value = "/pay/add")
@Operation(summary = "新增", description = "新增支付流水方法")
public String addPay(@RequestBody Pay pay){
int add = payService.add(pay);
log.info("新增数据:" + pay.toString());
return "成功插入记录";
}
@DeleteMapping("pay/del/{id}")
@Operation(summary = "删除", description = "删除支付流水方法")
public Integer deletePay(@PathVariable("id") Integer id){
int delete = payService.delete(id);
log.info("删除数据的id:" + id);
return delete;
}
@PutMapping("/pay/update")
@Operation(summary = "更新", description = "修改支付流水方法")
public String updatePay(@RequestBody PayDto payDto){
Pay pay = new Pay();
BeanUtils.copyProperties(payDto,pay);
int update = payService.update(pay);
log.info("成功更新记录" + payDto);
return "成功更新记录";
}
@GetMapping("/pay/get/{id}")
@Operation(summary = "查询", description = "查询支付流水方法")
public PayDto getById(@PathVariable("id") Integer id){
Pay byId = payService.getById(id);
PayDto payDto = new PayDto();
BeanUtils.copyProperties(byId,payDto);
log.info("查询记录:" + payDto);
return payDto;
}
@GetMapping("/pay/getAll")
@Operation(summary = "查询所有", description = "查询所有的支付流水")
public List<PayDto> getAll(){
List<Pay> all = payService.getAll();
List<PayDto> collect = all.stream().map((item) -> {
PayDto payDto = new PayDto();
BeanUtils.copyProperties(item, payDto);
return payDto;
}).collect(Collectors.toList());
log.info("查询记录:" + collect);
return collect;
}
}
实体类:
@Table(name = "t_pay")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(title = "支付实体")
public class Pay {
@Id
@GeneratedValue(generator = "JDBC")
@Schema(title = "主键id")
private Integer id;
/**
* 支付流水号
*/
@Column(name = "pay_no")
@Schema(title = "支付流水号")
private String payNo;
/**
* 订单流水号
*/
@Column(name = "order_no")
@Schema(title = "订单流水号")
private String orderNo;
/**
* 用户账号ID
*/
@Column(name = "user_id")
@Schema(title = "用户账号id")
private Integer userId;
/**
* 交易金额
*/
@Schema(title = "交易金额")
private BigDecimal amount;
/**
* 删除标志,默认0不删除,1删除
*/
@Schema(title = "默认删除标志")
private Byte deleted;
/**
* 创建时间
*/
@Column(name = "create_time")
@Schema(title = "创建时间")
private Date createTime;
/**
* 更新时间
*/
@Column(name = "update_time")
@Schema(title = "更新时间")
private Date updateTime;
}
配置类的写法:
@Configuration
public class Swagger3Config
{
@Bean
public GroupedOpenApi PayApi()
{
return GroupedOpenApi.builder().group("支付微服务模块").pathsToMatch("/pay/**").build();
}
@Bean
public GroupedOpenApi OtherApi()
{
return GroupedOpenApi.builder().group("其它微服务模块").pathsToMatch("/other/**", "/others").build();
}
/*@Bean
public GroupedOpenApi CustomerApi()
{
return GroupedOpenApi.builder().group("客户微服务模块").pathsToMatch("/customer/**", "/customers").build();
}*/
@Bean
public OpenAPI docsOpenApi()
{
return new OpenAPI()
.info(new Info().title("cloud2024")
.description("通用设计rest")
.version("v1.0"))
.externalDocs(new ExternalDocumentation()
.description("www.ergou.com")
.url("<https://yiyan.baidu.com/>"));
}
}
Swagger3调用方式:http://ip地址:端口/swagger-ui/index.html
附:定义响应返回标准格式
定义返回标准格式三大标配:
- code状态值:由后端统一定义各种返回结果的状态码
- message描述:本次接口调用的结果描述
- data数据:本次返回的数据
拓展:有时候会加第四个,timestamp:接口调用返回时间
步骤
1.新建枚举类ReturnCodeEnum
http请求返回的状态码:
| 分类 | 区间 | 分类描述 |
|---|---|---|
| 1** | 100~199 | 信息,服务器收到请求,需要请求者继续执行操作 |
| 2** | 200~299 | 成功,操作被成功接收并处理 |
| 3** | 300~399 | 重定向,需要进一步的操作以完成请求 |
| 4** | 400~499 | 客户端错误,请求中包含语法错误或者无法完成请求 |
| 5** | 500~599 | 服务器错误,服务器再处理请求的过程中发生了错误 |
ReturnCodeEnum枚举类:
@Getter
public enum ReturnCodeEnum
{
/**操作失败**/
RC999("999","操作XXX失败"),
/**操作成功**/
RC200("200","success"),
/**服务降级**/
RC201("201","服务开启降级保护,请稍后再试!"),
/**热点参数限流**/
RC202("202","热点参数限流,请稍后再试!"),
/**系统规则不满足**/
RC203("203","系统规则不满足要求,请稍后再试!"),
/**授权规则不通过**/
RC204("204","授权规则不通过,请稍后再试!"),
/**access_denied**/
RC403("403","无访问权限,请联系管理员授予权限"),
/**access_denied**/
RC401("401","匿名用户访问无权限资源时的异常"),
RC404("404","404页面找不到的异常"),
/**服务异常**/
RC500("500","系统异常,请稍后重试"),
RC375("375","数学运算异常,请稍后重试"),
INVALID_TOKEN("2001","访问令牌不合法"),
ACCESS_DENIED("2003","没有权限访问该资源"),
CLIENT_AUTHENTICATION_FAILED("1001","客户端认证失败"),
USERNAME_OR_PASSWORD_ERROR("1002","用户名或密码错误"),
BUSINESS_ERROR("1004","业务逻辑异常"),
UNSUPPORTED_GRANT_TYPE("1003", "不支持的认证模式");
/**自定义状态码**/
private final String code;
/**自定义描述**/
private final String message;
ReturnCodeEnum(String code, String message){
this.code = code;
this.message = message;
}
//遍历枚举V1,传统版
public static ReturnCodeEnum getReturnCodeEnum(String code)
{
for (ReturnCodeEnum element : ReturnCodeEnum.values()) {
if(element.getCode().equalsIgnoreCase(code))
{
return element;
}
}
return null;
}
//遍历枚举V2,stream流式计算版
public static ReturnCodeEnum getReturnCodeEnumV2(String code)
{
return Arrays.stream(ReturnCodeEnum.values()).filter(x -> x.getCode().equalsIgnoreCase(code)).findFirst().orElse(null);
}
}
2.新建统一定义返回对象
@Data
@Accessors(chain = true)
public class ResultData<T> {
private String code;/** 结果状态 ,具体状态码参见枚举类ReturnCodeEnum.java*/
private String message;
private T data;
private long timestamp ;
public ResultData (){
this.timestamp = System.currentTimeMillis();
}
public static <T> ResultData<T> success(T data) {
ResultData<T> resultData = new ResultData<>();
resultData.setCode(ReturnCodeEnum.RC200.getCode());
resultData.setMessage(ReturnCodeEnum.RC200.getMessage());
resultData.setData(data);
return resultData;
}
public static <T> ResultData<T> fail(String code, String message) {
ResultData<T> resultData = new ResultData<>();
resultData.setCode(code);
resultData.setMessage(message);
return resultData;
}
}
3.新建全局异常处理器
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler
{
/**
* 默认全局异常处理。
* @param e the e
* @return ResultData
*/
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResultData<String> exception(Exception e) {
System.out.println("----come in GlobalExceptionHandler");
log.error("全局异常信息exception:{}", e.getMessage(), e);
return ResultData.fail(ReturnCodeEnum.RC500.getCode(),e.getMessage());
}
}
RestTemplate
RestTemplate提供了多种便捷访问远程Http的方法,是一种简单便捷的访问restful服务模板类,是Spring提供的用于访问Rest服务的客户端模板工具集
使用restTemplate访问restful的接口有三个参数:
- url:rest请求地址
- responseType:http响应转换被转换成的类型
- requestMap:请求参数
getForObject方法/getForEntity方法
如果返回对象为响应题中数据转换成的对象,也就是返回了一个json信息,并不包含响应头、响应状态码、响应体这种结构。就是使用getForObject方法。
如果返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头、响应状态码、响应体等。就是使用getForEntity方法
参数有两个,第一个是url,第二个是json信息或者响应体中的信息对应的类型的类的class实例。如果有请求头之类的请求参数信息,可以加第三个参数传入相应的参数
PostForObject方法/postForEntity方法
与上述大致,不同的是,多了一个参数,即:第一个参数是url,第二个参数是接收请求体信息的对象,第三个参数是json信息或者响应体中的信息对应的类型的类的class实例。如果有请求头之类的请求参数信息,可以加第四个参数传入相应的参数
delete方法/put方法
delete只有一个参数,就是url。
put有两个参数,第一个是url,第二个是接收请求体信息的对象。
配置类
将RestTemplate的类型的对象加入到ioc容器
@Configuration
public class RestTemplateConfig
{
@Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}
使用案例
@RestController
public class OrderController {
public static final String PaymentSrv_URL = "<http://localhost:8001>";
@Resource
private RestTemplate restTemplate;
@PostMapping("/consumer/pay/add")
public ResultData addOrder(@RequestBody PayDTO payDto){
return restTemplate.postForObject(PaymentSrv_URL + "/pay/add",payDto,ResultData.class);
}
@GetMapping("/consumer/pay/get/{id}")
public ResultData getOrder(@PathVariable("id") int id){
return restTemplate.getForObject(PaymentSrv_URL + "/pay/get/" + id,ResultData.class);
}
@DeleteMapping("/consumer/pay/del/{id}")
public ResultData deleteOrder(@PathVariable("id") int id){
restTemplate.delete(PaymentSrv_URL + "/pay/del/" + id);
return ResultData.success("成功删除");
}
@PutMapping("/consumer/pay/update")
public ResultData updateOrder(@RequestBody PayDTO payDto){
restTemplate.put(PaymentSrv_URL + "/pay/update",payDto);
return ResultData.success("成功更新");
}
}
通用模块
在多个微服务模块中,通常会有通用的一些部分,比如对外暴露通用的组件、api、接口、工具类等。
在将这些部分加入到通用模块中后,要clean+install做成通用包将其通用模块加入到maven本地仓库中去。
然后在pom文件中的dependcies中引入自己的通用包。
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.ergou.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>