✨ Sentinel-流量控制
|
@ -240,7 +240,7 @@ spring:
|
|||
active: dev
|
||||
cloud:
|
||||
nacos:
|
||||
server-addr: ${NACOS_HOST:192.168.95.135}:8848
|
||||
server-addr: ${NACOS_HOST:192.168.3.150}:8848
|
||||
config:
|
||||
namespace: ${spring.profiles.active:dev} # 动态匹配当前profile
|
||||
group: DEFAULT_GROUP
|
||||
|
@ -425,15 +425,15 @@ public interface BunnyFeignClient {
|
|||
|
||||
### 负载均衡对比
|
||||
|
||||
| 特性 | 客户端负载均衡 (OpenFeign) | 服务端负载均衡 (Nginx等) |
|
||||
| ------------ | ----------------------------------- | ------------------------ |
|
||||
| **实现位置** | 客户端实现 | 服务端实现 |
|
||||
| **依赖关系** | 需要服务注册中心 | 不依赖注册中心 |
|
||||
| **性能** | 直接调用,减少网络跳转 | 需要经过代理服务器 |
|
||||
| **灵活性** | 可定制负载均衡策略 | 配置相对固定 |
|
||||
| **服务发现** | 集成服务发现机制 | 需要手动维护服务列表 |
|
||||
| **适用场景** | 微服务内部调用 | 对外暴露API或跨系统调用 |
|
||||
| **容错能力** | 集成熔断机制(如Sentinel、Hystrix) | 依赖代理服务器容错配置 |
|
||||
| 特性 | 客户端负载均衡 (OpenFeign) | 服务端负载均衡 (Nginx等) |
|
||||
|----------|---------------------------|------------------|
|
||||
| **实现位置** | 客户端实现 | 服务端实现 |
|
||||
| **依赖关系** | 需要服务注册中心 | 不依赖注册中心 |
|
||||
| **性能** | 直接调用,减少网络跳转 | 需要经过代理服务器 |
|
||||
| **灵活性** | 可定制负载均衡策略 | 配置相对固定 |
|
||||
| **服务发现** | 集成服务发现机制 | 需要手动维护服务列表 |
|
||||
| **适用场景** | 微服务内部调用 | 对外暴露API或跨系统调用 |
|
||||
| **容错能力** | 集成熔断机制(如Sentinel、Hystrix) | 依赖代理服务器容错配置 |
|
||||
|
||||
### 高级配置
|
||||
|
||||
|
@ -578,3 +578,209 @@ public interface ProductFeignClient {
|
|||
// 方法定义
|
||||
}
|
||||
```
|
||||
|
||||
## Sentinel 使用指南
|
||||
|
||||
> [!NOTE]
|
||||
> 如果安装完Sentinel打开控制面板可以看到服务,但簇点链路为空,可能原因:
|
||||
>
|
||||
> 1. 微服务与Sentinel不在同一IP段
|
||||
> 2. 服务未发送心跳到Sentinel Dashboard
|
||||
> 3. 未正确配置`spring.cloud.sentinel.transport.dashboard`
|
||||
|
||||
### 依赖引入
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
<version>2022.0.0.0</version> <!-- 建议指定版本 -->
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 基础配置
|
||||
|
||||
```yaml
|
||||
spring:
|
||||
cloud:
|
||||
sentinel:
|
||||
enabled: true
|
||||
eager: true # 提前初始化
|
||||
transport:
|
||||
dashboard: 192.168.3.150:8858 # Sentinel控制台地址
|
||||
port: 8719 # 本地启动的HTTP Server端口
|
||||
client-ip: ${spring.cloud.client.ip-address} # 客户端IP
|
||||
filter:
|
||||
enabled: true
|
||||
web-context-unify: false # 关闭统一上下文(链路模式需要)
|
||||
```
|
||||
|
||||
### 自定义异常处理
|
||||
|
||||
#### MVC接口自定义返回
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class MyBlockExceptionHandler implements BlockExceptionHandler {
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||
String s, BlockException e) throws Exception {
|
||||
response.setContentType("application/json;charset=utf-8");
|
||||
response.setStatus(429); // 建议使用429 Too Many Requests
|
||||
|
||||
Map<String, Object> result = Map.of(
|
||||
"code", 429,
|
||||
"message", "请求被限流",
|
||||
"timestamp", System.currentTimeMillis(),
|
||||
"rule", e.getRule()
|
||||
);
|
||||
|
||||
objectMapper.writeValue(response.getWriter(), result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### REST接口全局异常处理
|
||||
|
||||
```java
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BlockException.class)
|
||||
public ResponseEntity<Object> handleBlockException(BlockException e) {
|
||||
return ResponseEntity.status(429)
|
||||
.body(Map.of(
|
||||
"error", "Too Many Requests",
|
||||
"rule", e.getRule().toString()
|
||||
));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### @SentinelResource详解
|
||||
|
||||
#### 作用
|
||||
|
||||
- 定义资源名称(用于流量控制)
|
||||
- 指定限流/降级处理逻辑
|
||||
- 配置异常降级策略
|
||||
|
||||
#### blockHandler vs fallback区别
|
||||
|
||||
| 特性 | blockHandler | fallback |
|
||||
| -------- | ------------------------ | ------------------- |
|
||||
| 触发条件 | 流量控制/熔断时触发 | 业务异常时触发 |
|
||||
| 参数要求 | 需包含BlockException参数 | 需包含Throwable参数 |
|
||||
| 优先级 | 更高 | 更低 |
|
||||
| 典型场景 | 限流/熔断处理 | 业务降级处理 |
|
||||
|
||||
#### blockHandler使用示例
|
||||
|
||||
```java
|
||||
@Operation(summary = "创建订单")
|
||||
@SentinelResource(
|
||||
value = "orderService",
|
||||
blockHandler = "createBlockHandler",
|
||||
fallback = "createFallback"
|
||||
)
|
||||
@GetMapping("create")
|
||||
public Order createOrder(Long userId, Long productId) {
|
||||
return orderService.createOrder(productId, userId);
|
||||
}
|
||||
|
||||
// 限流处理(参数需匹配原方法且最后加BlockException)
|
||||
public Order createBlockHandler(Long userId, Long productId, BlockException ex) {
|
||||
log.warn("触发限流,rule={}", ex.getRule());
|
||||
return Order.fallbackOrder(userId, "限流:" + ex.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
// 降级处理(参数需匹配原方法且最后加Throwable)
|
||||
public Order createFallback(Long userId, Long productId, Throwable t) {
|
||||
log.error("业务异常", t);
|
||||
return Order.fallbackOrder(userId, "降级:" + t.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
### 流控规则详解
|
||||
|
||||
#### 阈值类型
|
||||
|
||||
| 类型 | 说明 | 适用场景 |
|
||||
| ------ | -------------------------------- | -------------------- |
|
||||
| QPS | 每秒请求数 | 绝大多数场景推荐使用 |
|
||||
| 线程数 | 并发线程数(统计服务内部线程数量) | 同步服务/耗时操作 |
|
||||
|
||||
> [!WARNING]
|
||||
> 线程数模式需要统计服务内部线程数量,性能开销较大,非必要不推荐使用
|
||||
|
||||
#### 集群模式
|
||||
|
||||
| 模式 | 说明 | 示意图 |
|
||||
| -------- | ---------------------------------- | ------------------------------------------------- |
|
||||
| 单机均摊 | 总阈值=单机阈值×节点数(均匀分配) |  |
|
||||
| 总体阈值 | 所有节点共享总阈值(按实际请求分配) | - |
|
||||
|
||||
### 流控模式
|
||||
|
||||
#### 1. 直接模式
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
流量 --直接--> 资源A
|
||||
```
|
||||
|
||||
- 最简单的模式,直接对资源生效
|
||||
|
||||
#### 2. 关联模式
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
流量 --入口A--> 资源A[[不限流]]
|
||||
流量 --入口B--> 资源B[[限流]]
|
||||
```
|
||||
|
||||
配置要求:
|
||||
|
||||
```yaml
|
||||
spring.cloud.sentinel.web-context-unify: false
|
||||
```
|
||||
|
||||
典型场景:写操作触发时限制读操作
|
||||
|
||||
#### 3. 链路模式
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
入口A --> 服务1 --> 资源X
|
||||
入口B --> 服务2 --> 资源X
|
||||
```
|
||||
|
||||
- 只针对特定入口的调用链路限流
|
||||
- 需要配合`@SentinelResource`标注资源点
|
||||
|
||||
### 流控效果
|
||||
|
||||
#### 1. 快速失败
|
||||
|
||||
- 直接抛出FlowException
|
||||
- 支持所有流控模式
|
||||
- 配置简单,性能最好
|
||||
|
||||
#### 2. Warm Up(预热)
|
||||
|
||||

|
||||
|
||||
- 冷启动阶段逐步提高阈值
|
||||
- 防止冷系统被突发流量击垮
|
||||
- 需配置预热时长(秒)
|
||||
|
||||
#### 3. 匀速排队
|
||||
|
||||

|
||||
|
||||
- 以恒定间隔处理请求
|
||||
- 需配置超时时间(毫秒)
|
||||
- 不支持QPS>1000的场景
|
||||
|
||||
|
|
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 81 KiB |
|
@ -22,5 +22,20 @@
|
|||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<!-- knife4j -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.springdoc</groupId> -->
|
||||
<!-- <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> -->
|
||||
<!-- <version>2.8.6</version> -->
|
||||
<!-- </dependency> -->
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>io.swagger</groupId> -->
|
||||
<!-- <artifactId>swagger-annotations</artifactId> -->
|
||||
<!-- <version>1.6.14</version> -->
|
||||
<!-- </dependency> -->
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
package cn.bunny.model.common.enums;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 统一返回结果状态信息类
|
||||
*/
|
||||
@Getter
|
||||
public enum ResultCodeEnum {
|
||||
// 成功操作 200
|
||||
SUCCESS(200, "操作成功"),
|
||||
CREATE_SUCCESS(200, "添加成功"),
|
||||
UPDATE_SUCCESS(200, "修改成功"),
|
||||
DELETE_SUCCESS(200, "删除成功"),
|
||||
SORT_SUCCESS(200, "排序成功"),
|
||||
SUCCESS_UPLOAD(200, "上传成功"),
|
||||
SUCCESS_LOGOUT(200, "退出成功"),
|
||||
LOGOUT_SUCCESS(200, "退出成功"),
|
||||
EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"),
|
||||
EMAIL_CODE_SEND_SUCCESS(200, "邮箱验证码已发送"),
|
||||
|
||||
// 验证错误 201
|
||||
USERNAME_OR_PASSWORD_NOT_EMPTY(201, "用户名或密码不能为空"),
|
||||
EMAIL_CODE_NOT_EMPTY(201, "邮箱验证码不能为空"),
|
||||
SEND_EMAIL_CODE_NOT_EMPTY(201, "请先发送邮箱验证码"),
|
||||
EMAIL_CODE_NOT_MATCHING(201, "邮箱验证码不匹配"),
|
||||
LOGIN_ERROR(500, "账号或密码错误"),
|
||||
LOGIN_ERROR_USERNAME_PASSWORD_NOT_EMPTY(201, "登录信息不能为空"),
|
||||
GET_BUCKET_EXCEPTION(201, "获取文件信息失败"),
|
||||
SEND_MAIL_CODE_ERROR(201, "邮件发送失败"),
|
||||
EMAIL_CODE_EMPTY(201, "邮箱验证码过期或不存在"),
|
||||
EMAIL_EXIST(201, "邮箱已存在"),
|
||||
REQUEST_IS_EMPTY(201, "请求数据为空"),
|
||||
DATA_TOO_LARGE(201, "请求数据为空"),
|
||||
UPDATE_NEW_PASSWORD_SAME_AS_OLD_PASSWORD(201, "新密码与密码相同"),
|
||||
|
||||
// 数据相关 206
|
||||
ILLEGAL_REQUEST(206, "非法请求"),
|
||||
REPEAT_SUBMIT(206, "重复提交"),
|
||||
DATA_ERROR(206, "数据异常"),
|
||||
EMAIL_USER_TEMPLATE_IS_EMPTY(206, "邮件模板为空"),
|
||||
EMAIL_TEMPLATE_IS_EMPTY(206, "邮件模板为空"),
|
||||
EMAIL_USER_IS_EMPTY(206, "关联邮件用户配置为空"),
|
||||
DATA_EXIST(206, "数据已存在"),
|
||||
DATA_NOT_EXIST(206, "数据不存在"),
|
||||
ALREADY_USER_EXCEPTION(206, "用户已存在"),
|
||||
USER_IS_EMPTY(206, "用户不存在"),
|
||||
FILE_NOT_EXIST(206, "文件不存在"),
|
||||
NEW_PASSWORD_SAME_OLD_PASSWORD(206, "新密码不能和旧密码相同"),
|
||||
MISSING_TEMPLATE_FILES(206, "缺少模板文件"),
|
||||
|
||||
// 身份过期 208
|
||||
LOGIN_AUTH(208, "请先登陆"),
|
||||
AUTHENTICATION_EXPIRED(208, "身份验证过期"),
|
||||
SESSION_EXPIRATION(208, "会话过期"),
|
||||
FAIL_NO_ACCESS_DENIED_USER_LOCKED(208, "该账户已封禁"),
|
||||
|
||||
// 209
|
||||
THE_SAME_USER_HAS_LOGGED_IN(209, "相同用户已登录"),
|
||||
|
||||
// 提示错误
|
||||
UPDATE_ERROR(216, "修改失败"),
|
||||
URL_ENCODE_ERROR(216, "URL编码失败"),
|
||||
ILLEGAL_CALLBACK_REQUEST_ERROR(217, "非法回调请求"),
|
||||
FETCH_USERINFO_ERROR(219, "获取用户信息失败"),
|
||||
ILLEGAL_DATA_REQUEST(219, "非法数据请求"),
|
||||
CLASS_NOT_FOUND(219, "类名不存在"),
|
||||
ADMIN_ROLE_CAN_NOT_DELETED(219, "无法删除admin角色"),
|
||||
ROUTER_RANK_NEED_LARGER_THAN_THE_PARENT(219, "设置路由等级需要大于或等于父级的路由等级"),
|
||||
|
||||
// 无权访问 403
|
||||
FAIL_NO_ACCESS_DENIED(403, "无权访问"),
|
||||
FAIL_NO_ACCESS_DENIED_USER_OFFLINE(403, "用户强制下线"),
|
||||
TOKEN_PARSING_FAILED(403, "token解析失败"),
|
||||
|
||||
// 系统错误 500
|
||||
UNKNOWN_EXCEPTION(500, "服务异常"),
|
||||
SERVICE_ERROR(500, "服务异常"),
|
||||
UPLOAD_ERROR(500, "上传失败"),
|
||||
FAIL(500, "失败"),
|
||||
;
|
||||
|
||||
private final Integer code;
|
||||
private final String message;
|
||||
|
||||
ResultCodeEnum(Integer code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package cn.bunny.model.common.result;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 封装分页查询结果
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Schema(name = "PageResult 对象", title = "分页返回结果", description = "分页返回结果")
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
@Schema(name = "pageNo", title = "当前页")
|
||||
private Long pageNo;
|
||||
|
||||
@Schema(name = "pageSize", title = "每页记录数")
|
||||
private Long pageSize;
|
||||
|
||||
@Schema(name = "total", title = "总记录数")
|
||||
private Long total;
|
||||
|
||||
@Schema(name = "list", title = "当前页数据集合")
|
||||
private List<T> list;
|
||||
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
package cn.bunny.model.common.result;
|
||||
|
||||
import cn.bunny.model.common.enums.ResultCodeEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class Result<T> {
|
||||
// 状态码
|
||||
private Integer code;
|
||||
// 返回消息
|
||||
private String message;
|
||||
// 返回数据
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* * 自定义返回体
|
||||
*
|
||||
* @param data 返回体
|
||||
* @return Result<T>
|
||||
*/
|
||||
protected static <T> Result<T> build(T data) {
|
||||
Result<T> result = new Result<>();
|
||||
result.setData(data);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* * 自定义返回体,使用ResultCodeEnum构建
|
||||
*
|
||||
* @param body 返回体
|
||||
* @param codeEnum 返回状态码
|
||||
* @return Result<T>
|
||||
*/
|
||||
public static <T> Result<T> build(T body, ResultCodeEnum codeEnum) {
|
||||
Result<T> result = build(body);
|
||||
result.setCode(codeEnum.getCode());
|
||||
result.setMessage(codeEnum.getMessage());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* * 自定义返回体
|
||||
*
|
||||
* @param body 返回体
|
||||
* @param code 返回状态码
|
||||
* @param message 返回消息
|
||||
* @return Result<T>
|
||||
*/
|
||||
public static <T> Result<T> build(T body, Integer code, String message) {
|
||||
Result<T> result = build(body);
|
||||
result.setCode(code);
|
||||
result.setMessage(message);
|
||||
result.setData(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作成功
|
||||
*
|
||||
* @return Result<T>
|
||||
*/
|
||||
public static <T> Result<T> success() {
|
||||
return success(null, ResultCodeEnum.SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作成功
|
||||
*
|
||||
* @param data baseCategory1List
|
||||
*/
|
||||
public static <T> Result<T> success(T data) {
|
||||
return build(data, ResultCodeEnum.SUCCESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作成功-状态码
|
||||
*
|
||||
* @param codeEnum 状态码
|
||||
*/
|
||||
public static <T> Result<T> success(ResultCodeEnum codeEnum) {
|
||||
return success(null, codeEnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作成功-自定义返回数据和状态码
|
||||
*
|
||||
* @param data 返回体
|
||||
* @param codeEnum 状态码
|
||||
*/
|
||||
public static <T> Result<T> success(T data, ResultCodeEnum codeEnum) {
|
||||
return build(data, codeEnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作失败-自定义返回数据和状态码
|
||||
*
|
||||
* @param data 返回体
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public static <T> Result<T> success(T data, String message) {
|
||||
return build(data, 200, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作失败-自定义返回数据和状态码
|
||||
*
|
||||
* @param data 返回体
|
||||
* @param code 状态码
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public static <T> Result<T> success(T data, Integer code, String message) {
|
||||
return build(data, code, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作失败
|
||||
*/
|
||||
public static <T> Result<T> error() {
|
||||
return Result.build(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作失败-自定义返回数据
|
||||
*
|
||||
* @param data 返回体
|
||||
*/
|
||||
public static <T> Result<T> error(T data) {
|
||||
return build(data, ResultCodeEnum.FAIL);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作失败-状态码
|
||||
*
|
||||
* @param codeEnum 状态码
|
||||
*/
|
||||
public static <T> Result<T> error(ResultCodeEnum codeEnum) {
|
||||
return build(null, codeEnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作失败-自定义返回数据和状态码
|
||||
*
|
||||
* @param data 返回体
|
||||
* @param codeEnum 状态码
|
||||
*/
|
||||
public static <T> Result<T> error(T data, ResultCodeEnum codeEnum) {
|
||||
return build(data, codeEnum);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作失败-自定义返回数据和状态码
|
||||
*
|
||||
* @param data 返回体
|
||||
* @param code 状态码
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public static <T> Result<T> error(T data, Integer code, String message) {
|
||||
return build(data, code, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* * 操作失败-自定义返回数据和状态码
|
||||
*
|
||||
* @param data 返回体
|
||||
* @param message 错误信息
|
||||
*/
|
||||
public static <T> Result<T> error(T data, String message) {
|
||||
return build(null, 500, message);
|
||||
}
|
||||
}
|
|
@ -68,6 +68,10 @@
|
|||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
|
|
|
@ -18,9 +18,15 @@
|
|||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>org.springdoc</groupId> -->
|
||||
<!-- <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> -->
|
||||
<!-- <version>2.8.6</version> -->
|
||||
<!-- </dependency> -->
|
||||
<!-- <dependency> -->
|
||||
<!-- <groupId>io.swagger</groupId> -->
|
||||
<!-- <artifactId>swagger-annotations</artifactId> -->
|
||||
<!-- <version>1.6.14</version> -->
|
||||
<!-- </dependency> -->
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -3,12 +3,16 @@ package cn.bunny.service.controller;
|
|||
import cn.bunny.model.order.bean.Order;
|
||||
import cn.bunny.service.config.OrderProperties;
|
||||
import cn.bunny.service.service.OrderService;
|
||||
import com.alibaba.csp.sentinel.annotation.SentinelResource;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/order")
|
||||
@RequiredArgsConstructor
|
||||
|
@ -25,18 +29,28 @@ public class OrderController {
|
|||
private final OrderProperties orderProperties;
|
||||
|
||||
@Operation(summary = "创建订单")
|
||||
@SentinelResource(value = "order", blockHandler = "createBlockHandler")
|
||||
@GetMapping("create")
|
||||
public Order createOrder(Long userId, Long productId) {
|
||||
return orderService.createOrder(productId, userId);
|
||||
}
|
||||
|
||||
public Order createBlockHandler(Long userId, Long productId, BlockException exception) {
|
||||
Order order = new Order();
|
||||
order.setUserId(userId);
|
||||
order.setAddress("xxx");
|
||||
order.setNickName(exception.getMessage());
|
||||
order.setProductList(List.of());
|
||||
return order;
|
||||
}
|
||||
|
||||
@Operation(summary = "读取配置")
|
||||
@GetMapping("config")
|
||||
public String config() {
|
||||
String timeout = orderProperties.getTimeout();
|
||||
String autoConfirm = orderProperties.getAutoConfirm();
|
||||
String dbUrl = orderProperties.getDbUrl();
|
||||
|
||||
|
||||
return "timeout:" + timeout + "\nautoConfirm:" + autoConfirm + "\norder.db-url" + dbUrl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package cn.bunny.service.exception;
|
||||
|
||||
import cn.bunny.model.common.result.Result;
|
||||
import com.alibaba.csp.sentinel.adapter.spring.webmvc_v6x.callback.BlockExceptionHandler;
|
||||
import com.alibaba.csp.sentinel.slots.block.BlockException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@Component
|
||||
public class MyBlockExceptionHandler implements BlockExceptionHandler {
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s, BlockException e) throws Exception {
|
||||
PrintWriter writer = httpServletResponse.getWriter();
|
||||
httpServletResponse.setContentType("application/json;charset=utf-8");
|
||||
|
||||
Result<String> result = new Result<>(500, "限制访问", null);
|
||||
String json = objectMapper.writeValueAsString(result);
|
||||
writer.write(json);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,14 @@ spring:
|
|||
read-timeout: 5000 # 最多等待对方 5s
|
||||
request-interceptors:
|
||||
- cn.bunny.service.interceptor.XTokenRequestInterceptor
|
||||
sentinel:
|
||||
enabled: true
|
||||
eager: true # 提前加载
|
||||
transport:
|
||||
dashboard: 192.168.3.150:8858
|
||||
filter:
|
||||
enabled: true
|
||||
# web-context-unify: false # 关闭统一上下文
|
||||
feign:
|
||||
sentinel:
|
||||
enabled: true
|
||||
enabled: true
|
||||
|
|
|
@ -10,7 +10,7 @@ spring:
|
|||
- feign
|
||||
cloud:
|
||||
nacos:
|
||||
server-addr: 192.168.95.135:8848
|
||||
server-addr: 192.168.3.150:8848
|
||||
config:
|
||||
import-check:
|
||||
enabled: false
|
||||
|
|
|
@ -2,4 +2,4 @@ server:
|
|||
port: 8001
|
||||
|
||||
nacos:
|
||||
server-addr: 192.168.95.135:8848
|
||||
server-addr: 192.168.3.150:8848
|
||||
|
|
|
@ -12,3 +12,11 @@ spring:
|
|||
config:
|
||||
import-check:
|
||||
enabled: false
|
||||
|
||||
sentinel:
|
||||
enabled: true
|
||||
eager: true # 提前加载
|
||||
transport:
|
||||
dashboard: 192.168.3.150:8858
|
||||
filter:
|
||||
enabled: true
|
|
@ -26,7 +26,7 @@ sudo systemctl daemon-reload && sudo systemctl restart docker
|
|||
|
||||
#### MySQL配置问题
|
||||
|
||||
| **特性** | `**my.cnf**` | `**conf.d**` **目录** |
|
||||
| **特性** | my.cnf | conf.d **目录** |
|
||||
| ------------ | :--------------------------- | :------------------------: |
|
||||
| **文件类型** | 单个文件 | 目录,包含多个 `.cnf` 文件 |
|
||||
| **配置方式** | 集中式配置 | 分布式配置 |
|
||||
|
|