liujie
2026-05-12 0bcfca6b2561d096a2ce26e2e7200cd0768b2b6d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
package com.xiaopeng.common.security.handler;
 
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiaopeng.common.core.constant.HttpStatus;
import com.xiaopeng.common.core.exception.DemoModeException;
import com.xiaopeng.common.core.exception.InnerAuthException;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.exception.auth.NotLoginException;
import com.xiaopeng.common.core.exception.auth.NotPermissionException;
import com.xiaopeng.common.core.exception.auth.NotRoleException;
import com.xiaopeng.common.core.exception.user.UserAppletException;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.core.web.domain.AjaxResult;
import feign.FeignException;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
 
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;
 
/**
 * 全局异常处理器
 *
 * @author ruoyi
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
 
    @Value("${spring.servlet.multipart.max-file-size:50MB}")
    private String maxFileSize;
    @Value("${spring.servlet.multipart.max-request-size:100MB}")
    private String maxRequestSize;
 
    @Value("${xiaopeng.xtiger.sso.issuer}")
    private String issuer;
 
    @Value("${xiaopeng.xtiger.sso.appId}")
    private String appId;
 
    @Value("${xiaopeng.xtiger.sso.serverHost}")
    private String serverHost;
 
    @Autowired
    private MessageSource messageSource;
 
    /**
     * 权限码异常
     */
    @ExceptionHandler(NotPermissionException.class)
    public AjaxResult handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
    }
 
    /**
     * 角色权限异常
     */
    @ExceptionHandler(NotRoleException.class)
    public AjaxResult handleNotRoleException(NotRoleException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
    }
 
    /**
     * 没有登录异常
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(NotLoginException.class)
    public AjaxResult notLoginException(NotLoginException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',登录校验失败'{}'", requestURI, e.getMessage());
        String s = UUID.randomUUID().toString();
        String state1 = s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24);
        String s1 = UUID.randomUUID().toString();
        String state2 = s1.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24);
        String nonce = RandomStringUtils.randomAlphanumeric(6);
        JSONObject jsonObject = JSON.parseObject("{\n" +
                "    \"uid\": null,\n" +
                "    \"username\": null,\n" +
                "    \"authUrl\": \"" + issuer + "/oidc/auth?scope=openid+profile+offline_access+phone+email&response_type=code&redirect_uri=" + serverHost + "/ssoAuthSdk/callback&state=" + state1 + "&nonce=" + nonce + "&client_id=" + appId + "\",\n" +
                "    \"logoutUrl\": null,\n" +
                "    \"callbackUrl\": \"" + serverHost + "/ssoAuthSdk/callback?state=" + state2 + "\"\n" +
                "  }");
        return AjaxResult.error(HttpStatus.UNAUTHORIZED, e.getMessage(), jsonObject);
    }
 
    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
                                                          HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return AjaxResult.error(e.getMessage());
    }
 
    /**
     * 业务异常
     */
    @ExceptionHandler(ServiceException.class)
    public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) {
        log.error(e.getMessage(), e);
        Integer code = e.getCode();
        String msg = e.getMessage();
        if (StringUtils.hasText(e.getMessageKey())) {
            msg = messageSource.getMessage(e.getMessageKey(), e.getArgs(), LocaleContextHolder.getLocale());
        }
        return StringUtils.isNotNull(code) ? AjaxResult.error(code, msg) : AjaxResult.error(msg);
    }
 
    /**
     * 拦截未知的运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生未知异常.", requestURI, e);
        return AjaxResult.error("系统繁忙,请稍后重试");
    }
 
    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生系统异常.", requestURI, e);
        return AjaxResult.error("系统繁忙,请稍后重试");
    }
 
    /**
     * 自定义验证异常
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult handleBindException(BindException e) {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
 
        // 支持国际化消息键(格式:{message.key})
        if (StringUtils.hasText(message) && message.startsWith("{") && message.endsWith("}")) {
            // 提取消息键
            String messageKey = message.substring(1, message.length() - 1);
            try {
                // 使用 MessageSource 获取国际化消息
                message = messageSource.getMessage(messageKey, null, LocaleContextHolder.getLocale());
            } catch (Exception ex) {
                log.warn("国际化消息键不存在: {}", messageKey);
                // 返回消息键本身作为降级方案
                message = messageKey;
            }
        }
 
        return AjaxResult.error(message);
    }
 
    /**
     * 自定义验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        // 收集所有字段校验错误,格式:fieldName: message
        StringBuilder sb = new StringBuilder();
        e.getBindingResult().getFieldErrors().forEach(fieldError -> {
            String message = fieldError.getDefaultMessage();
            if (StringUtils.hasText(message) && message.startsWith("{") && message.endsWith("}")) {
                String messageKey = message.substring(1, message.length() - 1);
                try {
                    message = messageSource.getMessage(messageKey, null, LocaleContextHolder.getLocale());
                } catch (Exception ex) {
                    log.warn("国际化消息键不存在: {}", messageKey);
                    message = messageKey;
                }
            }
            if (sb.length() > 0) {
                sb.append("; ");
            }
            sb.append(fieldError.getField()).append(": ").append(message);
        });
        return AjaxResult.error(sb.toString());
    }
 
    /**
     * 内部认证异常
     */
    @ExceptionHandler(InnerAuthException.class)
    public AjaxResult handleInnerAuthException(InnerAuthException e) {
        return AjaxResult.error(e.getMessage());
    }
 
    /**
     * 演示模式异常
     */
    @ExceptionHandler(DemoModeException.class)
    public AjaxResult handleDemoModeException(DemoModeException e) {
        return AjaxResult.error("演示模式,不允许操作");
    }
 
    /**
     * Feign调用异常 - 专门处理403 Forbidden异常
     * 解析并返回原始响应中的code、msg、data
     */
    @ExceptionHandler(FeignException.class)
    public AjaxResult handleFeignException(FeignException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}', Feign调用403异常: {}", requestURI, e.getMessage());
 
        // 获取原始响应内容
        String responseBody = e.contentUTF8();
        if (StringUtils.hasText(responseBody)) {
            try {
                // 使用 Jackson 解析 JSON 响应
                ObjectMapper objectMapper = new ObjectMapper();
                JsonNode jsonNode = objectMapper.readTree(responseBody);
 
                // 提取 code、msg、data 字段
                Integer code = jsonNode.has("code") ? jsonNode.get("code").asInt() : null;
                String msg = jsonNode.has("msg") ? jsonNode.get("msg").asText() : null;
                Object data = jsonNode.has("data") && !jsonNode.get("data").isNull() ?
                        objectMapper.treeToValue(jsonNode.get("data"), Object.class) : null;
 
                // 直接使用 AjaxResult 构造方法返回原始的code、msg、data
                if (code != null) {
                    return new AjaxResult(code, msg, data);
                } else {
                    return AjaxResult.error(msg != null ? msg : "服务调用失败");
                }
            } catch (Exception ex) {
                log.warn("解析Feign异常响应内容失败,返回默认错误: {}", ex.getMessage());
                // 解析失败时返回默认的403错误
                return AjaxResult.error(HttpStatus.FORBIDDEN, "服务调用失败");
            }
        }
 
        // 如果没有响应体,返回默认的403错误
        return AjaxResult.error(HttpStatus.FORBIDDEN, "服务调用失败");
    }
 
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public AjaxResult fileUpLoad(MaxUploadSizeExceededException e) {
        log.error("上传文件异常 => : {}", e.getMessage());
        return AjaxResult.error("文件识别大小超出限制,允许的大小在" + maxFileSize);
    }
 
    /**
     * 学生端单点登录-异常信息
     */
    @ExceptionHandler(UserAppletException.class)
    public AjaxResult<String> studyLoginExceptionHandler(UserAppletException e) {
        return AjaxResult.error(e.getCode(), e.getMessage());
    }
}