✨ Sentinel-流量控制
|
@ -240,7 +240,7 @@ spring:
|
||||||
active: dev
|
active: dev
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
server-addr: ${NACOS_HOST:192.168.95.135}:8848
|
server-addr: ${NACOS_HOST:192.168.3.150}:8848
|
||||||
config:
|
config:
|
||||||
namespace: ${spring.profiles.active:dev} # 动态匹配当前profile
|
namespace: ${spring.profiles.active:dev} # 动态匹配当前profile
|
||||||
group: DEFAULT_GROUP
|
group: DEFAULT_GROUP
|
||||||
|
@ -426,7 +426,7 @@ public interface BunnyFeignClient {
|
||||||
### 负载均衡对比
|
### 负载均衡对比
|
||||||
|
|
||||||
| 特性 | 客户端负载均衡 (OpenFeign) | 服务端负载均衡 (Nginx等) |
|
| 特性 | 客户端负载均衡 (OpenFeign) | 服务端负载均衡 (Nginx等) |
|
||||||
| ------------ | ----------------------------------- | ------------------------ |
|
|----------|---------------------------|------------------|
|
||||||
| **实现位置** | 客户端实现 | 服务端实现 |
|
| **实现位置** | 客户端实现 | 服务端实现 |
|
||||||
| **依赖关系** | 需要服务注册中心 | 不依赖注册中心 |
|
| **依赖关系** | 需要服务注册中心 | 不依赖注册中心 |
|
||||||
| **性能** | 直接调用,减少网络跳转 | 需要经过代理服务器 |
|
| **性能** | 直接调用,减少网络跳转 | 需要经过代理服务器 |
|
||||||
|
@ -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>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
</dependency>
|
</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>
|
</dependencies>
|
||||||
</project>
|
</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>
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
|
|
@ -18,9 +18,15 @@
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<!-- <dependency> -->
|
||||||
<groupId>com.alibaba.cloud</groupId>
|
<!-- <groupId>org.springdoc</groupId> -->
|
||||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
<!-- <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> -->
|
||||||
</dependency>
|
<!-- <version>2.8.6</version> -->
|
||||||
|
<!-- </dependency> -->
|
||||||
|
<!-- <dependency> -->
|
||||||
|
<!-- <groupId>io.swagger</groupId> -->
|
||||||
|
<!-- <artifactId>swagger-annotations</artifactId> -->
|
||||||
|
<!-- <version>1.6.14</version> -->
|
||||||
|
<!-- </dependency> -->
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -3,12 +3,16 @@ package cn.bunny.service.controller;
|
||||||
import cn.bunny.model.order.bean.Order;
|
import cn.bunny.model.order.bean.Order;
|
||||||
import cn.bunny.service.config.OrderProperties;
|
import cn.bunny.service.config.OrderProperties;
|
||||||
import cn.bunny.service.service.OrderService;
|
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 io.swagger.v3.oas.annotations.Operation;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/order")
|
@RequestMapping("/api/order")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@ -25,11 +29,21 @@ public class OrderController {
|
||||||
private final OrderProperties orderProperties;
|
private final OrderProperties orderProperties;
|
||||||
|
|
||||||
@Operation(summary = "创建订单")
|
@Operation(summary = "创建订单")
|
||||||
|
@SentinelResource(value = "order", blockHandler = "createBlockHandler")
|
||||||
@GetMapping("create")
|
@GetMapping("create")
|
||||||
public Order createOrder(Long userId, Long productId) {
|
public Order createOrder(Long userId, Long productId) {
|
||||||
return orderService.createOrder(productId, userId);
|
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 = "读取配置")
|
@Operation(summary = "读取配置")
|
||||||
@GetMapping("config")
|
@GetMapping("config")
|
||||||
public String config() {
|
public String config() {
|
||||||
|
|
|
@ -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
|
read-timeout: 5000 # 最多等待对方 5s
|
||||||
request-interceptors:
|
request-interceptors:
|
||||||
- cn.bunny.service.interceptor.XTokenRequestInterceptor
|
- 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:
|
feign:
|
||||||
sentinel:
|
sentinel:
|
||||||
enabled: true
|
enabled: true
|
|
@ -10,7 +10,7 @@ spring:
|
||||||
- feign
|
- feign
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
server-addr: 192.168.95.135:8848
|
server-addr: 192.168.3.150:8848
|
||||||
config:
|
config:
|
||||||
import-check:
|
import-check:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
|
@ -2,4 +2,4 @@ server:
|
||||||
port: 8001
|
port: 8001
|
||||||
|
|
||||||
nacos:
|
nacos:
|
||||||
server-addr: 192.168.95.135:8848
|
server-addr: 192.168.3.150:8848
|
||||||
|
|
|
@ -12,3 +12,11 @@ spring:
|
||||||
config:
|
config:
|
||||||
import-check:
|
import-check:
|
||||||
enabled: false
|
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配置问题
|
#### MySQL配置问题
|
||||||
|
|
||||||
| **特性** | `**my.cnf**` | `**conf.d**` **目录** |
|
| **特性** | my.cnf | conf.d **目录** |
|
||||||
| ------------ | :--------------------------- | :------------------------: |
|
| ------------ | :--------------------------- | :------------------------: |
|
||||||
| **文件类型** | 单个文件 | 目录,包含多个 `.cnf` 文件 |
|
| **文件类型** | 单个文件 | 目录,包含多个 `.cnf` 文件 |
|
||||||
| **配置方式** | 集中式配置 | 分布式配置 |
|
| **配置方式** | 集中式配置 | 分布式配置 |
|
||||||
|
|