2 Commits

  1. 51
      opai-api/src/main/java/com/bw/opai/auth/controller/OpaiAuthController.java
  2. 14
      opai-api/src/main/java/com/bw/opai/auth/dto/LoginRequest.java
  3. 18
      opai-api/src/main/java/com/bw/opai/auth/dto/UserDTO.java
  4. 21
      opai-api/src/main/java/com/bw/opai/auth/entity/SysUser.java
  5. 51
      opai-api/src/main/java/com/bw/opai/auth/interceptor/AuthInterceptor.java
  6. 21
      opai-api/src/main/java/com/bw/opai/auth/service/IRemoteAuthService.java
  7. 92
      opai-api/src/main/java/com/bw/opai/auth/service/impl/RemoteAuthServiceImpl.java
  8. 40
      opai-api/src/main/java/com/bw/opai/auth/utils/UserContext.java
  9. 38
      opai-api/src/main/java/com/bw/opai/common/Res.java
  10. 3
      opai-api/src/main/java/com/bw/opai/common/ResponseCode.java
  11. 16
      opai-api/src/main/java/com/bw/opai/config/CorsConfig.java
  12. 19
      opai-api/src/main/java/com/bw/opai/config/RestConfig.java
  13. 26
      opai-api/src/main/java/com/bw/opai/config/WebMvcConfig.java
  14. 25
      opai-api/src/main/java/com/bw/opai/exception/BizException.java
  15. 46
      opai-api/src/main/java/com/bw/opai/exception/GlobalExceptionHandler.java
  16. 2
      opai-api/src/main/resources/bootstrap.yml

51
opai-api/src/main/java/com/bw/opai/auth/controller/OpaiAuthController.java

@ -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);
}
}

14
opai-api/src/main/java/com/bw/opai/auth/dto/LoginRequest.java

@ -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;
}

18
opai-api/src/main/java/com/bw/opai/auth/dto/UserDTO.java

@ -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;
}

21
opai-api/src/main/java/com/bw/opai/auth/entity/SysUser.java

@ -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;
}

51
opai-api/src/main/java/com/bw/opai/auth/interceptor/AuthInterceptor.java

@ -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();
}
}

21
opai-api/src/main/java/com/bw/opai/auth/service/IRemoteAuthService.java

@ -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);
}

92
opai-api/src/main/java/com/bw/opai/auth/service/impl/RemoteAuthServiceImpl.java

@ -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;
}
}

40
opai-api/src/main/java/com/bw/opai/auth/utils/UserContext.java

@ -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();
}
}

38
opai-api/src/main/java/com/bw/opai/common/Res.java

@ -28,21 +28,22 @@ public class Res<T> {
this.data = data;
}
/**
* 分页结果封装
* @param page MyBatis-Plus 分页对象
* @param <T> 实体类型
* @return Res<Map> 包含 total/page/size/records
*/
public static <T> Res<Map<String, Object>> page(Page<T> page) {
Map<String, Object> pageData = new HashMap<>();
pageData.put("total", page.getTotal());
pageData.put("page", page.getCurrent());
pageData.put("size", page.getSize());
pageData.put("records", page.getRecords());
return Res.ok(pageData);
}
/**
* 分页结果封装
*
* @param page MyBatis-Plus 分页对象
* @param <T> 实体类型
* @return Res<Map> 包含 total/page/size/records
*/
public static <T> Res<Map<String, Object>> page(Page<T> page) {
Map<String, Object> pageData = new HashMap<>();
pageData.put("total", page.getTotal());
pageData.put("page", page.getCurrent());
pageData.put("size", page.getSize());
pageData.put("records", page.getRecords());
return Res.ok(pageData);
}
public static <T> Res<T> ok(T data) {
return new Res<T>(ResponseCode.SUCCESS.code(), ResponseCode.SUCCESS.message(), data);
}
@ -50,10 +51,17 @@ public class Res<T> {
public static <T> Res<T> fail(String msg) {
return new Res<T>(ResponseCode.FAIL.code(), msg, null);
}
public static <T> Res<T> checkError(T error) {
return new Res<T>(ResponseCode.FAIL.code(), ResponseCode.CHECKERROR.message(), error);
}
public static <T> Res<T> unAuth(String msg) {
return new Res<T>(ResponseCode.AUTHRROR.code(), msg, null);
}
// getter & setter
public int getResCode() {
return resCode;

3
opai-api/src/main/java/com/bw/opai/common/ResponseCode.java

@ -10,7 +10,8 @@ public enum ResponseCode {
SUCCESS(0, "success"),
FAIL(-1, "fail"),
CHECKERROR(400, "paramsError");
CHECKERROR(400, "paramsError"),
AUTHRROR(401, "loginError");
private final int code;
private final String message;

16
opai-api/src/main/java/com/bw/opai/config/CorsConfig.java

@ -1,5 +1,6 @@
package com.bw.opai.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -7,12 +8,19 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
// Nacos 读取如果没配默认放行 localhost:8080
@Value("${opai.cors.allowed-origins}")
private String[] allowedOrigins;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
// 1. 核心直接使用注入的数组
.allowedOrigins(allowedOrigins)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(false);
// 2. 核心必须设为 true否则 AUTH_TOKEN Cookie 存不下来
.allowCredentials(true)
.maxAge(3600);
}
}
}

19
opai-api/src/main/java/com/bw/opai/config/RestConfig.java

@ -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);
}
}

26
opai-api/src/main/java/com/bw/opai/config/WebMvcConfig.java

@ -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/**");
}
}

25
opai-api/src/main/java/com/bw/opai/exception/BizException.java

@ -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;
}
}

46
opai-api/src/main/java/com/bw/opai/exception/GlobalExceptionHandler.java

@ -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("后端接口调用失败");
}
}

2
opai-api/src/main/resources/bootstrap.yml

@ -48,5 +48,5 @@ logging:
root: info
com.alibaba.nacos.client.config.impl: WARN
file:
path: ../logs
path: ./logs
Loading…
Cancel
Save