package cn.iocoder.yudao.framework.web.core.handler;
|
|
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.exceptions.ExceptionUtil;
|
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.framework.common.biz.infra.logger.ApiErrorLogCommonApi;
|
import cn.iocoder.yudao.framework.common.biz.infra.logger.dto.ApiErrorLogCreateReqDTO;
|
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
|
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
|
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
|
import com.google.common.util.concurrent.UncheckedExecutionException;
|
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.validation.ConstraintViolation;
|
import jakarta.validation.ConstraintViolationException;
|
import jakarta.validation.ValidationException;
|
import lombok.AllArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
import org.springframework.security.access.AccessDeniedException;
|
import org.springframework.util.Assert;
|
import org.springframework.validation.BindException;
|
import org.springframework.validation.FieldError;
|
import org.springframework.validation.ObjectError;
|
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
import org.springframework.web.bind.MissingServletRequestParameterException;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
import org.springframework.web.servlet.NoHandlerFoundException;
|
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
import tools.jackson.databind.exc.InvalidFormatException;
|
|
import java.time.LocalDateTime;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
|
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
|
|
/**
|
* 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号
|
*
|
* @author 芋道源码
|
*/
|
@RestControllerAdvice
|
@AllArgsConstructor
|
@Slf4j
|
public class GlobalExceptionHandler {
|
|
/**
|
* 忽略的 ServiceException 错误提示,避免打印过多 logger
|
*/
|
public static final Set<String> IGNORE_ERROR_MESSAGES = SetUtils.asSet("无效的刷新令牌");
|
|
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
private final String applicationName;
|
|
private final ApiErrorLogCommonApi apiErrorLogApi;
|
|
/**
|
* 处理所有异常,主要是提供给 Filter 使用
|
* 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。
|
*
|
* @param request 请求
|
* @param ex 异常
|
* @return 通用返回
|
*/
|
public CommonResult<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
|
if (ex instanceof MissingServletRequestParameterException) {
|
return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
|
}
|
if (ex instanceof MethodArgumentTypeMismatchException) {
|
return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
|
}
|
if (ex instanceof MethodArgumentNotValidException) {
|
return methodArgumentNotValidExceptionExceptionHandler((MethodArgumentNotValidException) ex);
|
}
|
if (ex instanceof BindException) {
|
return bindExceptionHandler((BindException) ex);
|
}
|
if (ex instanceof ConstraintViolationException) {
|
return constraintViolationExceptionHandler((ConstraintViolationException) ex);
|
}
|
if (ex instanceof ValidationException) {
|
return validationException((ValidationException) ex);
|
}
|
if (ex instanceof MaxUploadSizeExceededException) {
|
return maxUploadSizeExceededExceptionHandler((MaxUploadSizeExceededException) ex);
|
}
|
if (ex instanceof NoHandlerFoundException) {
|
return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex);
|
}
|
if (ex instanceof NoResourceFoundException) {
|
return noResourceFoundExceptionHandler(request, (NoResourceFoundException) ex);
|
}
|
if (ex instanceof HttpRequestMethodNotSupportedException) {
|
return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
|
}
|
if (ex instanceof HttpMediaTypeNotSupportedException) {
|
return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
|
}
|
if (ex instanceof ServiceException) {
|
return serviceExceptionHandler((ServiceException) ex);
|
}
|
if (ex instanceof AccessDeniedException) {
|
return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);
|
}
|
return defaultExceptionHandler(request, ex);
|
}
|
|
/**
|
* 处理 SpringMVC 请求参数缺失
|
*
|
* 例如说,接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数
|
*/
|
@ExceptionHandler(value = MissingServletRequestParameterException.class)
|
public CommonResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
|
log.warn("[missingServletRequestParameterExceptionHandler]", ex);
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
|
}
|
|
/**
|
* 处理 SpringMVC 请求参数类型错误
|
*
|
* 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String
|
*/
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
|
log.warn("[methodArgumentTypeMismatchExceptionHandler]", ex);
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
|
}
|
|
/**
|
* 处理 SpringMVC 参数校验不正确
|
*/
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
public CommonResult<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
|
log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
|
// 获取 errorMessage
|
String errorMessage = null;
|
FieldError fieldError = ex.getBindingResult().getFieldError();
|
if (fieldError == null) {
|
// 组合校验,参考自 https://t.zsxq.com/3HVTx
|
List<ObjectError> allErrors = ex.getBindingResult().getAllErrors();
|
if (CollUtil.isNotEmpty(allErrors)) {
|
errorMessage = allErrors.get(0).getDefaultMessage();
|
}
|
} else {
|
errorMessage = fieldError.getDefaultMessage();
|
}
|
// 转换 CommonResult
|
if (StrUtil.isEmpty(errorMessage)) {
|
return CommonResult.error(BAD_REQUEST);
|
}
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", errorMessage));
|
}
|
|
/**
|
* 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验
|
*/
|
@ExceptionHandler(BindException.class)
|
public CommonResult<?> bindExceptionHandler(BindException ex) {
|
log.warn("[handleBindException]", ex);
|
FieldError fieldError = ex.getFieldError();
|
assert fieldError != null; // 断言,避免告警
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
|
}
|
|
/**
|
* 处理 SpringMVC 请求参数类型错误
|
*
|
* 例如说,接口上设置了 @RequestBody 实体中 xx 属性类型为 Integer,结果传递 xx 参数类型为 String
|
*/
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
@SuppressWarnings("PatternVariableCanBeUsed")
|
public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
|
log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
|
if (ex.getCause() instanceof InvalidFormatException) {
|
InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
|
}
|
if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) {
|
return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失");
|
}
|
return defaultExceptionHandler(ServletUtils.getRequest(), ex);
|
}
|
|
/**
|
* 处理 Validator 校验不通过产生的异常
|
*/
|
@ExceptionHandler(value = ConstraintViolationException.class)
|
public CommonResult<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
|
log.warn("[constraintViolationExceptionHandler]", ex);
|
ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()));
|
}
|
|
/**
|
* 处理 Dubbo Consumer 本地参数校验时,抛出的 ValidationException 异常
|
*/
|
@ExceptionHandler(value = ValidationException.class)
|
public CommonResult<?> validationException(ValidationException ex) {
|
log.warn("[constraintViolationExceptionHandler]", ex);
|
// 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读
|
return CommonResult.error(BAD_REQUEST);
|
}
|
|
/**
|
* 处理上传文件过大异常
|
*/
|
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
public CommonResult<?> maxUploadSizeExceededExceptionHandler(MaxUploadSizeExceededException ex) {
|
return CommonResult.error(BAD_REQUEST.getCode(), "上传文件过大,请调整后重试");
|
}
|
|
/**
|
* 处理 SpringMVC 请求地址不存在
|
*
|
* 注意,它需要设置如下两个配置项:
|
* 1. spring.mvc.throw-exception-if-no-handler-found 为 true
|
* 2. spring.mvc.static-path-pattern 为 /statics/**
|
*/
|
@ExceptionHandler(NoHandlerFoundException.class)
|
public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
|
log.warn("[noHandlerFoundExceptionHandler]", ex);
|
return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
|
}
|
|
/**
|
* 处理 SpringMVC 请求地址不存在
|
*/
|
@ExceptionHandler(NoResourceFoundException.class)
|
private CommonResult<?> noResourceFoundExceptionHandler(HttpServletRequest req, NoResourceFoundException ex) {
|
log.warn("[noResourceFoundExceptionHandler]", ex);
|
return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getResourcePath()));
|
}
|
|
/**
|
* 处理 SpringMVC 请求方法不正确
|
*
|
* 例如说,A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配
|
*/
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
public CommonResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
|
log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
|
return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
|
}
|
|
/**
|
* 处理 SpringMVC 请求的 Content-Type 不正确
|
*
|
* 例如说,A 接口的 Content-Type 为 application/json,结果请求的 Content-Type 为 application/octet-stream,导致不匹配
|
*/
|
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
|
public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) {
|
log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex);
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage()));
|
}
|
|
/**
|
* 处理 Spring Security 权限不足的异常
|
*
|
* 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截
|
*/
|
@ExceptionHandler(value = AccessDeniedException.class)
|
public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
|
log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", WebFrameworkUtils.getLoginUserId(req),
|
req.getRequestURL(), ex);
|
return CommonResult.error(FORBIDDEN);
|
}
|
|
/**
|
* 处理 Guava UncheckedExecutionException
|
*
|
* 例如说,缓存加载报错,可见 <a href="https://t.zsxq.com/UszdH">https://t.zsxq.com/UszdH</a>
|
*/
|
@ExceptionHandler(value = UncheckedExecutionException.class)
|
public CommonResult<?> uncheckedExecutionExceptionHandler(HttpServletRequest req, UncheckedExecutionException ex) {
|
return allExceptionHandler(req, ex.getCause());
|
}
|
|
/**
|
* 处理业务异常 ServiceException
|
*
|
* 例如说,商品库存不足,用户手机号已存在。
|
*/
|
@ExceptionHandler(value = ServiceException.class)
|
public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
|
// 不包含的时候,才进行打印,避免 ex 堆栈过多
|
if (!IGNORE_ERROR_MESSAGES.contains(ex.getMessage())) {
|
// 即使打印,也只打印第一层 StackTraceElement,并且使用 warn 在控制台输出,更容易看到
|
try {
|
StackTraceElement[] stackTraces = ex.getStackTrace();
|
for (StackTraceElement stackTrace : stackTraces) {
|
if (ObjUtil.notEqual(stackTrace.getClassName(), ServiceExceptionUtil.class.getName())) {
|
log.warn("[serviceExceptionHandler]\n\t{}", stackTrace);
|
break;
|
}
|
}
|
} catch (Exception ignored) {
|
// 忽略日志,避免影响主流程
|
}
|
}
|
return CommonResult.error(ex.getCode(), ex.getMessage());
|
}
|
|
/**
|
* 处理系统异常,兜底处理所有的一切
|
*/
|
@ExceptionHandler(value = Exception.class)
|
public CommonResult<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
|
// 特殊:如果是 ServiceException 的异常,则直接返回
|
// 例如说:https://gitee.com/zhijiantianya/yudao-cloud/issues/ICSSRM、https://gitee.com/zhijiantianya/yudao-cloud/issues/ICT6FM
|
if (ex.getCause() != null && ex.getCause() instanceof ServiceException) {
|
return serviceExceptionHandler((ServiceException) ex.getCause());
|
}
|
|
// 情况一:处理表不存在的异常
|
CommonResult<?> tableNotExistsResult = handleTableNotExists(ex);
|
if (tableNotExistsResult != null) {
|
return tableNotExistsResult;
|
}
|
|
// 情况二:处理异常
|
log.error("[defaultExceptionHandler]", ex);
|
// 插入异常日志
|
createExceptionLog(req, ex);
|
// 返回 ERROR CommonResult
|
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
|
}
|
|
private void createExceptionLog(HttpServletRequest req, Throwable e) {
|
// 插入错误日志
|
ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();
|
try {
|
// 初始化 errorLog
|
buildExceptionLog(errorLog, req, e);
|
// 执行插入 errorLog
|
apiErrorLogApi.createApiErrorLogAsync(errorLog);
|
} catch (Throwable th) {
|
log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
|
}
|
}
|
|
private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) {
|
// 处理用户信息
|
errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));
|
errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request));
|
// 设置异常字段
|
errorLog.setExceptionName(e.getClass().getName());
|
errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));
|
errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
|
errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e));
|
StackTraceElement[] stackTraceElements = e.getStackTrace();
|
Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
|
StackTraceElement stackTraceElement = stackTraceElements[0];
|
errorLog.setExceptionClassName(stackTraceElement.getClassName());
|
errorLog.setExceptionFileName(stackTraceElement.getFileName());
|
errorLog.setExceptionMethodName(stackTraceElement.getMethodName());
|
errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
|
// 设置其它字段
|
errorLog.setTraceId(TracerUtils.getTraceId());
|
errorLog.setApplicationName(applicationName);
|
errorLog.setRequestUrl(request.getRequestURI());
|
Map<String, Object> requestParams = MapUtil.<String, Object>builder()
|
.put("query", ServletUtils.getParamMap(request))
|
.put("body", ServletUtils.getBody(request)).build();
|
errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));
|
errorLog.setRequestMethod(request.getMethod());
|
errorLog.setUserAgent(ServletUtils.getUserAgent(request));
|
errorLog.setUserIp(ServletUtils.getClientIP(request));
|
errorLog.setExceptionTime(LocalDateTime.now());
|
}
|
|
/**
|
* 处理 Table 不存在的异常情况
|
*
|
* @param ex 异常
|
* @return 如果是 Table 不存在的异常,则返回对应的 CommonResult
|
*/
|
private CommonResult<?> handleTableNotExists(Throwable ex) {
|
String message = ExceptionUtil.getRootCauseMessage(ex);
|
if (!message.contains("doesn't exist")) {
|
return null;
|
}
|
// 1. 数据报表
|
if (message.contains("report_")) {
|
log.error("[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[报表模块 yudao-module-report - 表结构未导入][参考 https://cloud.iocoder.cn/report/ 开启]");
|
}
|
// 2. 工作流
|
if (message.contains("bpm_")) {
|
log.error("[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[工作流模块 yudao-module-bpm - 表结构未导入][参考 https://cloud.iocoder.cn/bpm/ 开启]");
|
}
|
// 3. 微信公众号
|
if (message.contains("mp_")) {
|
log.error("[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[微信公众号 yudao-module-mp - 表结构未导入][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
|
}
|
// 4. 商城系统
|
if (StrUtil.containsAny(message, "product_", "promotion_", "trade_")) {
|
log.error("[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[商城系统 yudao-module-mall - 已禁用][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
|
}
|
// 5. ERP 系统
|
if (message.contains("erp_")) {
|
log.error("[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
|
}
|
// 6. WMS 仓库管理系统
|
if (message.contains("wms_")) {
|
log.error("[WMS 仓库管理系统 yudao-module-wms - 表结构未导入][参考 https://doc.iocoder.cn/wms/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[WMS 仓库管理系统 yudao-module-wms - 表结构未导入][参考 https://doc.iocoder.cn/wms/build/ 开启]");
|
}
|
// 7. CRM 系统
|
if (message.contains("crm_")) {
|
log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
}
|
// 8. MES 系统
|
if (message.contains("mes_")) {
|
log.error("[MES 系统 yudao-module-mes - 表结构未导入][参考 https://doc.iocoder.cn/mes/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[MES 系统 yudao-module-mes - 表结构未导入][参考 https://doc.iocoder.cn/mes/build/ 开启]");
|
}
|
// 9. IM 即时通讯
|
if (message.contains("im_")) {
|
log.error("[IM 即时通讯 yudao-module-im - 表结构未导入][参考 https://doc.iocoder.cn/im/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[IM 即时通讯 yudao-module-im - 表结构未导入][参考 https://doc.iocoder.cn/im/build/ 开启]");
|
}
|
// 10. 支付平台
|
if (message.contains("pay_")) {
|
log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
|
}
|
// 11. AI 大模型
|
if (message.contains("ai_")) {
|
log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
|
}
|
// 12. IoT 物联网
|
if (message.contains("iot_")) {
|
log.error("[IoT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
"[IoT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
|
}
|
return null;
|
}
|
|
}
|