16 changed files with 462 additions and 21 deletions
-
51opai-api/src/main/java/com/bw/opai/auth/controller/OpaiAuthController.java
-
14opai-api/src/main/java/com/bw/opai/auth/dto/LoginRequest.java
-
18opai-api/src/main/java/com/bw/opai/auth/dto/UserDTO.java
-
21opai-api/src/main/java/com/bw/opai/auth/entity/SysUser.java
-
51opai-api/src/main/java/com/bw/opai/auth/interceptor/AuthInterceptor.java
-
21opai-api/src/main/java/com/bw/opai/auth/service/IRemoteAuthService.java
-
92opai-api/src/main/java/com/bw/opai/auth/service/impl/RemoteAuthServiceImpl.java
-
40opai-api/src/main/java/com/bw/opai/auth/utils/UserContext.java
-
8opai-api/src/main/java/com/bw/opai/common/Res.java
-
3opai-api/src/main/java/com/bw/opai/common/ResponseCode.java
-
14opai-api/src/main/java/com/bw/opai/config/CorsConfig.java
-
19opai-api/src/main/java/com/bw/opai/config/RestConfig.java
-
26opai-api/src/main/java/com/bw/opai/config/WebMvcConfig.java
-
25opai-api/src/main/java/com/bw/opai/exception/BizException.java
-
46opai-api/src/main/java/com/bw/opai/exception/GlobalExceptionHandler.java
-
2opai-api/src/main/resources/bootstrap.yml
@ -0,0 +1,51 @@ |
|||||
|
package com.bw.opai.auth.controller; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
|
||||
|
import com.bw.opai.auth.dto.LoginRequest; |
||||
|
import com.bw.opai.auth.entity.SysUser; |
||||
|
import com.bw.opai.auth.service.IRemoteAuthService; |
||||
|
import com.bw.opai.common.Res; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* opai 系统认证转发中心 |
||||
|
* @author jian.mao |
||||
|
* @date 2026年1月28日 |
||||
|
* @description |
||||
|
*/ |
||||
|
@RestController |
||||
|
@RequestMapping("/auth") |
||||
|
public class OpaiAuthController { |
||||
|
|
||||
|
@Autowired |
||||
|
private IRemoteAuthService remoteAuthService; |
||||
|
|
||||
|
/** |
||||
|
* opai 登录接口 |
||||
|
* 逻辑:透传给远端认证中心 |
||||
|
*/ |
||||
|
@PostMapping("/login") |
||||
|
public Res<String> login(@RequestBody LoginRequest loginReq) { |
||||
|
// 1. 基本参数校验 |
||||
|
if (loginReq == null || loginReq.getUsername() == null) { |
||||
|
return Res.fail("用户名不能为空"); |
||||
|
} |
||||
|
|
||||
|
// 2. 调用我们在 Service 里的转发逻辑 |
||||
|
return remoteAuthService.remoteLogin(loginReq.getUsername(), loginReq.getPassword()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* opai 注册接口 |
||||
|
* 逻辑:透传给远端认证中心开通账号 |
||||
|
*/ |
||||
|
@PostMapping("/register") |
||||
|
public Res<String> register(@RequestBody SysUser user) { |
||||
|
return remoteAuthService.remoteRegister(user); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
package com.bw.opai.auth.dto; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class LoginRequest { |
||||
|
private String username; |
||||
|
private String password; |
||||
|
|
||||
|
/** |
||||
|
* 关键:前端传过来当前是哪个系统 |
||||
|
*/ |
||||
|
private String appKey; |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
package com.bw.opai.auth.dto; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class UserDTO implements Serializable { |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
/** |
||||
|
* // 用户ID |
||||
|
*/ |
||||
|
private Long userId; |
||||
|
/** |
||||
|
* // 用户名 |
||||
|
*/ |
||||
|
private String username; |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
package com.bw.opai.auth.entity; |
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import lombok.Data; |
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@TableName("sys_user") |
||||
|
public class SysUser { |
||||
|
@TableId(type = IdType.AUTO) |
||||
|
private Long id; |
||||
|
|
||||
|
private String username; |
||||
|
|
||||
|
private String password; |
||||
|
|
||||
|
private String email; |
||||
|
|
||||
|
private LocalDateTime createTime; |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
package com.bw.opai.auth.interceptor; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.servlet.HandlerInterceptor; |
||||
|
|
||||
|
import com.bw.opai.auth.dto.UserDTO; |
||||
|
import com.bw.opai.auth.service.IRemoteAuthService; |
||||
|
import com.bw.opai.auth.utils.UserContext; |
||||
|
|
||||
|
/** |
||||
|
* 登录拦截 |
||||
|
* @author jian.mao |
||||
|
* @date 2026年1月28日 |
||||
|
* @description |
||||
|
*/ |
||||
|
@Component |
||||
|
public class AuthInterceptor implements HandlerInterceptor { |
||||
|
|
||||
|
@Autowired |
||||
|
private IRemoteAuthService remoteAuthService; |
||||
|
|
||||
|
@Override |
||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { |
||||
|
// 1. 拿到 Token (逻辑参考) |
||||
|
String token = request.getHeader("Authorization"); |
||||
|
if (token == null) { |
||||
|
throw new RuntimeException("缺失凭证"); // 会被全局异常捕获 |
||||
|
} |
||||
|
|
||||
|
// 2. 跨系统校验 |
||||
|
UserDTO user = remoteAuthService.verifyToken(token); |
||||
|
if (user == null) { |
||||
|
throw new RuntimeException("凭证无效"); |
||||
|
} |
||||
|
|
||||
|
// 3. 存入上下文,解决 UserContext resolved 报错 |
||||
|
UserContext.setUser(user); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { |
||||
|
// 4. 规范:用完必须清理 |
||||
|
UserContext.remove(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
package com.bw.opai.auth.service; |
||||
|
|
||||
|
import com.bw.opai.auth.dto.UserDTO; |
||||
|
import com.bw.opai.auth.entity.SysUser; |
||||
|
import com.bw.opai.common.Res; |
||||
|
|
||||
|
/** |
||||
|
* 用户管理service接口 |
||||
|
* @author jian.mao |
||||
|
* @date 2026年1月28日 |
||||
|
* @description |
||||
|
*/ |
||||
|
public interface IRemoteAuthService { |
||||
|
|
||||
|
/*** 登录中转 ***/ |
||||
|
Res<String> remoteLogin(String username, String password); |
||||
|
/*** 注册中转 ***/ |
||||
|
Res<String> remoteRegister(SysUser user); |
||||
|
/*** 核心鉴权:拿着 token 去 Auth-Server 换 UserDTO ***/ |
||||
|
UserDTO verifyToken(String token); |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
package com.bw.opai.auth.service.impl; |
||||
|
|
||||
|
|
||||
|
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.stereotype.Service; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
|
||||
|
import com.bw.opai.auth.dto.LoginRequest; |
||||
|
import com.bw.opai.auth.dto.UserDTO; |
||||
|
import com.bw.opai.auth.entity.SysUser; |
||||
|
import com.bw.opai.auth.service.IRemoteAuthService; |
||||
|
import com.bw.opai.common.Res; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
|
||||
|
@Service |
||||
|
public class RemoteAuthServiceImpl implements IRemoteAuthService { |
||||
|
|
||||
|
private static final Logger log = LoggerFactory.getLogger(RemoteAuthServiceImpl.class); |
||||
|
|
||||
|
@Autowired |
||||
|
private RestTemplate restTemplate; |
||||
|
|
||||
|
/** |
||||
|
* 显式引入 ObjectMapper 处理 JSON 转换,确保 UserDTO 转换不出错 |
||||
|
*/ |
||||
|
@Autowired |
||||
|
private ObjectMapper objectMapper; |
||||
|
|
||||
|
/** |
||||
|
* 从 Nacos 读取认证中心地址,如 http://192.168.1.100:8080 |
||||
|
*/ |
||||
|
@Value("${auth-server.url}") |
||||
|
private String authUrl; |
||||
|
|
||||
|
/** |
||||
|
* opai 系统在认证中心备案的 appKey |
||||
|
*/ |
||||
|
@Value("${opai.app-key}") |
||||
|
private String appKey; |
||||
|
|
||||
|
@Override |
||||
|
public Res<String> remoteLogin(String username, String password) { |
||||
|
String url = authUrl + "/auth/login"; |
||||
|
|
||||
|
LoginRequest loginReq = new LoginRequest(); |
||||
|
loginReq.setUsername(username); |
||||
|
loginReq.setPassword(password); |
||||
|
loginReq.setAppKey(appKey); |
||||
|
|
||||
|
try { |
||||
|
// 调用认证中心 AuthController.login |
||||
|
return restTemplate.postForObject(url, loginReq, Res.class); |
||||
|
} catch (Exception e) { |
||||
|
log.error("远程登录调用失败: {}", e.getMessage()); |
||||
|
return Res.unAuth("认证中心连接异常"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Res<String> remoteRegister(SysUser user) { |
||||
|
String url = authUrl + "/auth/register"; |
||||
|
try { |
||||
|
return restTemplate.postForObject(url, user, Res.class); |
||||
|
} catch (Exception e) { |
||||
|
log.error("远程注册调用失败: {}", e.getMessage()); |
||||
|
return Res.fail("认证中心连接异常"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public UserDTO verifyToken(String token) { |
||||
|
// 与 AuthController.verify 接口对应: @GetMapping("/verify") |
||||
|
String url = authUrl + "/auth/verify?token=" + token + "&appKey=" + appKey; |
||||
|
|
||||
|
try { |
||||
|
// 发起 GET 请求 |
||||
|
Res<?> result = restTemplate.getForObject(url, Res.class); |
||||
|
|
||||
|
if (result != null && result.getResCode() == 0 && result.getData() != null) { |
||||
|
// 规范点:跨系统数据转换,通过 ObjectMapper 将 LinkedHashMap 转换为真正的 UserDTO |
||||
|
return objectMapper.convertValue(result.getData(), UserDTO.class); |
||||
|
} |
||||
|
} catch (Exception e) { |
||||
|
log.error("Token 远程校验异常: {}", e.getMessage()); |
||||
|
// 校验失败返回 null,拦截器会根据此结果决定是否放行 |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
package com.bw.opai.auth.utils; |
||||
|
|
||||
|
import com.bw.opai.auth.dto.UserDTO; |
||||
|
|
||||
|
/** |
||||
|
* 严谨程序员必备:用户上下文容器 |
||||
|
* 作用:在当前线程中存储已登录的用户信息 |
||||
|
*/ |
||||
|
public class UserContext { |
||||
|
private static final ThreadLocal<UserDTO> USER_HOLDER = new ThreadLocal<>(); |
||||
|
|
||||
|
/** |
||||
|
* 存入当前登录用户 |
||||
|
*/ |
||||
|
public static void setUser(UserDTO user) { |
||||
|
USER_HOLDER.set(user); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取当前登录用户 |
||||
|
*/ |
||||
|
public static UserDTO getUser() { |
||||
|
return USER_HOLDER.get(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取当前用户ID |
||||
|
*/ |
||||
|
public static Long getUserId() { |
||||
|
UserDTO user = USER_HOLDER.get(); |
||||
|
return user != null ? user.getUserId() : null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 规范:请求结束必须清理,防止内存泄漏 |
||||
|
*/ |
||||
|
public static void remove() { |
||||
|
USER_HOLDER.remove(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
package com.bw.opai.config; |
||||
|
|
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
|
||||
|
@Configuration |
||||
|
public class RestConfig { |
||||
|
@Bean |
||||
|
public RestTemplate restTemplate() { |
||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); |
||||
|
// 建立连接超时 |
||||
|
factory.setConnectTimeout(3000); |
||||
|
// 响应超时 |
||||
|
factory.setReadTimeout(5000); |
||||
|
return new RestTemplate(factory); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
package com.bw.opai.config; |
||||
|
|
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
|
|
||||
|
import com.bw.opai.auth.interceptor.AuthInterceptor; |
||||
|
|
||||
|
@Configuration |
||||
|
public class WebMvcConfig implements WebMvcConfigurer { |
||||
|
|
||||
|
@Autowired |
||||
|
private AuthInterceptor authInterceptor; |
||||
|
|
||||
|
@Override |
||||
|
public void addInterceptors(InterceptorRegistry registry) { |
||||
|
registry.addInterceptor(authInterceptor) |
||||
|
// 拦截所有业务接口 |
||||
|
.addPathPatterns("/apps/**") |
||||
|
// 放行登录注册接口(否则就死循环了) |
||||
|
.excludePathPatterns("/auth/**") |
||||
|
.excludePathPatterns("/static/**"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
package com.bw.opai.exception; |
||||
|
|
||||
|
/** |
||||
|
* 自定义业务异常 |
||||
|
*/ |
||||
|
public class BizException extends RuntimeException { |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
private int code; |
||||
|
private String msg; |
||||
|
|
||||
|
public BizException(int code, String msg) { |
||||
|
super(msg); |
||||
|
this.code = code; |
||||
|
this.msg = msg; |
||||
|
} |
||||
|
|
||||
|
public int getCode() { |
||||
|
return code; |
||||
|
} |
||||
|
|
||||
|
public String getMsg() { |
||||
|
return msg; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
package com.bw.opai.exception; |
||||
|
|
||||
|
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice; |
||||
|
|
||||
|
import com.bw.opai.common.Res; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
/** |
||||
|
* 全局异常处理 |
||||
|
* @author jian.mao |
||||
|
* @date 2026年1月28日 |
||||
|
* @description |
||||
|
*/ |
||||
|
@RestControllerAdvice |
||||
|
@Slf4j |
||||
|
public class GlobalExceptionHandler { |
||||
|
|
||||
|
/** |
||||
|
* 处理自定义业务异常 (比如拦截器抛出的 401) |
||||
|
*/ |
||||
|
@ExceptionHandler(BizException.class) |
||||
|
public Res<?> handleBizException(BizException e) { |
||||
|
log.error("业务异常: {}", e.getMsg()); |
||||
|
return Res.fail(e.getMsg()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理未知的运行时异常 |
||||
|
*/ |
||||
|
@ExceptionHandler(RuntimeException.class) |
||||
|
public Res<?> handleRuntimeException(RuntimeException e) { |
||||
|
log.error("运行时异常: ", e); |
||||
|
return Res.fail(e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理所有其他未明确捕获的异常 |
||||
|
*/ |
||||
|
@ExceptionHandler(Exception.class) |
||||
|
public Res<?> handleException(Exception e) { |
||||
|
log.error("系统未知异常: ", e); |
||||
|
return Res.fail("后端接口调用失败"); |
||||
|
} |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue