Sentinel
Sentinel copied to clipboard
[BUG] 系统规则与授权规则不生效
Issue Description
Type: bug report
Describe what happened
基于毕业版本的SpringCloudAlibaba+SpringCloud+SpringBoot下配置并使用Sentinel的系统规则与授权规则不生效。
三个基础版本如图所示:
Sentinel相关依赖如图所示:
定义测试用的Controller类:
`package com.nriet.cipas.controller.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.nriet.cipas.common.util.exception.ExceptionUtils; import com.nriet.cipas.environment.annotation.NotControllerResponseAdvice; import com.nriet.cipas.service.service.IUserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController @Slf4j public class UserController { @Autowired private IUserService userService;
@GetMapping("/user/getUserByName")
@SentinelResource(value = "/user/getUserByName",blockHandler = "blockHandlerGetUserByName")
public String getUserByName(@RequestParam("userName") String userName){
return userService.selectUserByName(userName);
}
public String blockHandlerGetUserByName(String userName, BlockException ex)
{
ExceptionUtils.extracted(ex);
return "";
}
@GetMapping("/user/{userId}")
@SentinelResource(value = "/user/userId",blockHandler = "blockHandler")
public String userId(@PathVariable("userId") String userId){
return "checked!";
}
public String blockHandler(String userId, BlockException ex)
{
ExceptionUtils.extracted(ex);
return "";
}
@GetMapping("/user/authority")
@SentinelResource(value = "/user/authority",blockHandler = "blockHandlerAuthority")
public String authority(@RequestParam("userId") String userId){
return "passed!";
}
public String blockHandlerAuthority(String userId, BlockException ex)
{
ExceptionUtils.extracted(ex);
return "";
}
} `
其他方法1: `package com.nriet.cipas.common.util.exception;
import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
public class ExceptionUtils {
public static void extracted(BlockException ex) {
if(ex instanceof FlowException) {
throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_FLOW_ERROR.getCode(), "selectUserByName" + "方法被限流了,请稍后重试");
} else if (ex instanceof DegradeException) {
throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_DEGRADE_ERROR.getCode(), "selectUserByName" + "方法被降级熔断了,请稍后重试");
} else if (ex instanceof ParamFlowException) {
throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_PARAMS_ERROR.getCode(), "selectUserByName" + "方法触发热点参数限流,请稍后重试");
} else if (ex instanceof AuthorityException) {
throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_AUTH_ERROR.getCode(), "selectUserByName" + "方法访问权限不足,请联系服务管理员");
} else if (ex instanceof SystemBlockException) {
throw new ApplicationDefinedException(ExceptionEnum.SENTINEL_SYSTEM_BLOCK_ERROR.getCode(), "selectUserByName" + "方法触发系统保护了,请稍后重试");
}
}
} `
全局异常处理类: `package com.nriet.cipas.environment.exception;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import com.alibaba.csp.sentinel.slots.system.SystemBlockException; import com.nriet.cipas.common.util.exception.ApplicationDefinedException; import com.nriet.cipas.common.util.exception.ExceptionEnum; import com.nriet.cipas.instance.vo.result.ResultVO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.web.bind.annotation.ResponseStatus;
import java.util.List; import java.util.stream.Collectors;
@ControllerAdvice @ResponseBody //统一在类前面加上@ResponseBody注解,这样在方法上就不需要添加了,返回的数据都是json格式的 public class GlobalExceptionAdvice { private Logger logger = LoggerFactory.getLogger(GlobalExceptionAdvice.class);
/**
* 使用form data方式调用接口,校验异常抛出 BindException
* @param e
* @return
*/
@ExceptionHandler({BindException.class})
public ResultVO<?> BindExceptionHandler(BindException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
logger.error(getMsg(e));
return new ResultVO<>(ExceptionEnum.ARGUMENT_ERROR.getCode(),ExceptionEnum.ARGUMENT_ERROR.getMsg() , collect.toString());
}
/**
* 处理参数校验异常。使用 json 请求体调用接口,校验异常抛出 MethodArgumentNotValidException
* 调用/globalExceptionTest/addUser接口测试。
* 传入:
* {"account":"222sada99999999999","password":"3","email":"a519991925"}
* 报错为:
* {
* "code": 108,
* "message": "参数不合法",
* "data": "[用户id不能为空, 邮箱格式不正确, 密码长度必须是6-16个字符, 账号长度必须是6-11个字符]"
* }
* @param e
* @return
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
public ResultVO<?> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> collect = fieldErrors.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
logger.error(getMsg(e));
logger.error(collect.toString());
return new ResultVO<>(ExceptionEnum.ARGUMENT_ERROR.getCode(),ExceptionEnum.ARGUMENT_ERROR.getMsg() , collect);
}
/**
* 处理业务自定义异常
* @param e
* @return
*/
@ExceptionHandler(value = ApplicationDefinedException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> ApplicationDefinedExceptionHandler(ApplicationDefinedException e){
logger.error(getMsg(e));
return new ResultVO<>(e.getCode(),e.getMessage(),null);
}
@ExceptionHandler(value = FlowException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> flowExceptionHandler(FlowException e){
logger.error(getMsg(e));
return new ResultVO<>(ExceptionEnum.SENTINEL_FLOW_ERROR.getCode(),ExceptionEnum.SENTINEL_FLOW_ERROR.getMsg(),null);
}
@ExceptionHandler(value = DegradeException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> degradeExceptionHandler(DegradeException e){
logger.error(getMsg(e));
return new ResultVO<>(ExceptionEnum.SENTINEL_DEGRADE_ERROR.getCode(),ExceptionEnum.SENTINEL_DEGRADE_ERROR.getMsg(),null);
}
@ExceptionHandler(value = ParamFlowException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> paramFlowException(ParamFlowException e){
logger.error(getMsg(e));
return new ResultVO<>(ExceptionEnum.SENTINEL_PARAMS_ERROR.getCode(),ExceptionEnum.SENTINEL_PARAMS_ERROR.getMsg(),null);
}
@ExceptionHandler(value = AuthorityException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> authorityExceptionHandler(AuthorityException e){
logger.error(getMsg(e));
return new ResultVO<>(ExceptionEnum.SENTINEL_AUTH_ERROR.getCode(),ExceptionEnum.SENTINEL_AUTH_ERROR.getMsg(),null);
}
@ExceptionHandler(value = SystemBlockException.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ResultVO<?> systemBlockExceptionHandler(SystemBlockException e){
logger.error(getMsg(e));
return new ResultVO<>(ExceptionEnum.SENTINEL_SYSTEM_BLOCK_ERROR.getCode(),ExceptionEnum.SENTINEL_SYSTEM_BLOCK_ERROR.getMsg(),null);
}
/**
* 处理其它异常
* @param e
* @return
*/
@ExceptionHandler(value = Exception.class)
//ResponseStatus没有必要了,因为我们在全局异常处理类中已经设置了,这里统一用200即可
// @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) public ResultVO<?> exceptionHandler(Exception e){ logger.error(getMsg(e)); return new ResultVO<>(ExceptionEnum.UNKNOW_ERROR.getCode(),ExceptionEnum.UNKNOW_ERROR.getMsg(),null); }
private static String getMsg(Exception e) {
StackTraceElement[] stackTrace = e.getStackTrace();
StringBuffer sb = new StringBuffer();
sb.append(e.toString()).append("\r\n");
for (StackTraceElement stackTraceElement : stackTrace) {
sb.append("\tat ").append(stackTraceElement.getClassName()).append(".")
.append(stackTraceElement.getMethodName())
.append("(").append(stackTraceElement.getFileName()).append(":").append(stackTraceElement.getLineNumber())
.append(")\r\n");
}
return sb.toString();
}
} `
origin解析类: `package com.nriet.cipas.environment.configuration;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component public class RequestOriginParserDefinition implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { // 当前 流控应用 放在了请求参数里面,可以放到的地方有很多,比如 参数/请求头/session/等等 return httpServletRequest.getParameter("sourceName"); } } `
bootstrap.properties相关Sentinel配置: `#sentinel spring.cloud.sentinel.transport.dashboard=localhost:8885 spring.cloud.sentinel.transport.clientIp=${spring.cloud.client.ip-address} spring.cloud.sentinel.transport.port=18719 spring.cloud.sentinel.web-context-unify=false spring.cloud.sentinel.eager=true
spring.cloud.sentinel.datasource.flow.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.flow.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.flow.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.flow.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.flow.nacos.data-id=${spring.application.name}-flow-rules spring.cloud.sentinel.datasource.flow.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.flow.nacos.data-type=json spring.cloud.sentinel.datasource.flow.nacos.rule-type=flow
spring.cloud.sentinel.datasource.degrade.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.degrade.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.degrade.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.degrade.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.degrade.nacos.data-id=${spring.application.name}-degrade-rules spring.cloud.sentinel.datasource.degrade.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.degrade.nacos.data-type=json spring.cloud.sentinel.datasource.degrade.nacos.rule-type=degrade
spring.cloud.sentinel.datasource.system.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.system.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.system.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.system.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.system.nacos.data-id=${spring.application.name}-system-rules spring.cloud.sentinel.datasource.system.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.system.nacos.data-type=json spring.cloud.sentinel.datasource.system.nacos.rule-type=system
spring.cloud.sentinel.datasource.authority.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.authority.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.authority.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.authority.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.authority.nacos.data-id=${spring.application.name}-authority-rules spring.cloud.sentinel.datasource.authority.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.authority.nacos.data-type=json spring.cloud.sentinel.datasource.authority.nacos.rule-type=authority
spring.cloud.sentinel.datasource.param-flow.nacos.server-addr=${spring.cloud.nacos.config.server-addr} spring.cloud.sentinel.datasource.param-flow.nacos.namespace=${spring.cloud.nacos.config.namespace} spring.cloud.sentinel.datasource.param-flow.nacos.username=${spring.cloud.nacos.config.username} spring.cloud.sentinel.datasource.param-flow.nacos.password=${spring.cloud.nacos.config.password} spring.cloud.sentinel.datasource.param-flow.nacos.data-id=${spring.application.name}-param-flow-rules spring.cloud.sentinel.datasource.param-flow.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.param-flow.nacos.data-type=json spring.cloud.sentinel.datasource.param-flow.nacos.rule-type=param-flow`
Sentinel-dashboard版本为1.8.0,可以读取、新增、修改、删除该应用程序所有配置。
应用服务启动正常,相关测试接口测试正常(包括限流、降级、热点三个规则,都能正常使用)
Describe what you expected to happen
无论通过手动修改Nacos上的关于系统规则与授权规则的配置文件、还是通过Dashboard修改,都无法生效。
例如以20并发调用测试接口,检验系统规则在并发数为3的生效情况,shiji实际并未触发:
例如配置授权规则,以白名单方式强制限定仅有“cipas-example2”的应用才能调用测试接口,也无法生效。
请问该如何处理这种问题?
像是RequestOriginParser,我查了很多资料,我使用的是com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser来做也无法使得授权规则生效
不生效是因为在抛异常的时候,内部处理有个空引用,异常就被吞掉了