后端基础框架
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
package oailab.com.apistart;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = "oailab.com", exclude = {SecurityAutoConfiguration.class})
|
||||
@EnableScheduling
|
||||
@EnableCaching
|
||||
@EnableAsync
|
||||
@EnableTransactionManagement
|
||||
@Slf4j
|
||||
@PropertySource(value = {"file:${user.dir}/config/application.yml"})
|
||||
public class ApiStartApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ApiStartApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package oailab.com.apistart.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Cross-origin configuration.
|
||||
*
|
||||
* @author Benny Deng
|
||||
* @see <a href="http://www.bianmaren.com">Benny Deng</a>
|
||||
* @since 6/17/24 PM11:19
|
||||
* @version 1.0
|
||||
**/
|
||||
@Configuration
|
||||
public class CorsConfig implements WebMvcConfigurer{
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowCredentials(true)
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedHeaders("*")
|
||||
.allowedMethods("*");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package oailab.com.apistart.config;
|
||||
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import oailab.com.apistart.interceptor.SysTokenInterceptor;
|
||||
import oailab.com.apistart.interceptor.WebTokenInterceptor;
|
||||
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.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Mvc configuration.
|
||||
*
|
||||
* @author Benny Deng
|
||||
* @version 1.0
|
||||
* @see <a href="http://www.bianmaren.com">Benny Deng</a>
|
||||
* @since 6/17/24 PM11:19
|
||||
**/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class WebMvcConf implements WebMvcConfigurer {
|
||||
|
||||
@Resource
|
||||
private SysTokenInterceptor sysTokenInterceptor;
|
||||
@Resource
|
||||
private WebTokenInterceptor webTokenInterceptor;
|
||||
|
||||
|
||||
@Override
|
||||
public void addViewControllers(ViewControllerRegistry registry) {
|
||||
// 将非 /api/** 的请求转发到 index.html
|
||||
registry.addViewController("/{path:[^\\.]*}")
|
||||
.setViewName("forward:/index.html");
|
||||
registry.addViewController("/{path1:[^\\.]*}/{path2:[^\\.]*}")
|
||||
.setViewName("forward:/index.html");
|
||||
registry.addViewController("/{path1:[^\\.]*}/{path2:[^\\.]*}/{path3:[^\\.]*}")
|
||||
.setViewName("forward:/index.html");
|
||||
registry.addViewController("/{path1:[^\\.]*}/{path2:[^\\.]*}/{path3:[^\\.]*}/{path4:[^\\.]*}")
|
||||
.setViewName("forward:/index.html");
|
||||
registry.addViewController("/{path1:[^\\.]*}/{path2:[^\\.]*}/{path3:[^\\.]*}/{path4:[^\\.]*}/{path5:[^\\.]*}")
|
||||
.setViewName("forward:/index.html");
|
||||
// 根据需要支持更深的路径
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// 确保静态资源可以正常访问
|
||||
registry.addResourceHandler("/**")
|
||||
.addResourceLocations("classpath:/static/");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(sysTokenInterceptor).addPathPatterns("/api/sys/**");
|
||||
registry.addInterceptor(webTokenInterceptor).addPathPatterns("/api/web/**");
|
||||
registry.addInterceptor(sysTokenInterceptor).addPathPatterns("/api/payment/**");
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package oailab.com.apistart.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import oailab.com.apiservice.constants.LoginConstants;
|
||||
import oailab.com.apiservice.enums.sys.ErrorCodeEnum;
|
||||
import oailab.com.apiservice.exception.BizException;
|
||||
import oailab.com.apiservice.mybatis.entity.sys.TenantAccount;
|
||||
import oailab.com.apiservice.mybatis.entity.sys.TenantWorkspace;
|
||||
import oailab.com.apiservice.mybatis.entity.workspace.EndUsers;
|
||||
import oailab.com.apiservice.mybatis.service.sys.TenantAccountService;
|
||||
import oailab.com.apiservice.mybatis.service.sys.TenantWorkspaceService;
|
||||
import oailab.com.apiservice.mybatis.service.workspace.EndUsersService;
|
||||
|
||||
/**
|
||||
* Base controller..
|
||||
*
|
||||
* @author Benny Deng
|
||||
* @version 1.0
|
||||
* @see <a href="http://www.bianmaren.com">Benny Deng</a>
|
||||
* @since 6/17/24 PM11:19
|
||||
**/
|
||||
public class BaseController {
|
||||
|
||||
@Resource
|
||||
private TenantAccountService tenantAccountService;
|
||||
@Resource
|
||||
private TenantWorkspaceService tenantWorkspaceService;
|
||||
@Resource
|
||||
private EndUsersService endUsersService;
|
||||
|
||||
/**
|
||||
* 获取当前登录的账号
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 租户账号
|
||||
*/
|
||||
protected String getCurrentTenantAccountFromReq(HttpServletRequest request) {
|
||||
return getAttrFromReq(request, LoginConstants.SYS_LOGIN_USER_ACCOUNT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前工作间ID
|
||||
*
|
||||
* @param request 请求
|
||||
*/
|
||||
protected String getCurrentWorkspaceIdFromReq(HttpServletRequest request) {
|
||||
return getAttrFromReqHeaders(request, LoginConstants.HEADER_WORKSPACE_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前工作间
|
||||
*
|
||||
* @param request 请求
|
||||
*/
|
||||
protected TenantWorkspace getCurrentWorkspaceFromReq(HttpServletRequest request) {
|
||||
TenantWorkspace workspace = tenantWorkspaceService.getById(getCurrentWorkspaceIdFromReq(request));
|
||||
if (null == workspace) {
|
||||
throw new BizException(ErrorCodeEnum.WorkspaceNotFound);
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录的租户
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 租户
|
||||
*/
|
||||
protected TenantAccount getCurrentTenantFromReq(HttpServletRequest request) {
|
||||
return tenantAccountService.getByAccount(getCurrentTenantAccountFromReq(request));
|
||||
}
|
||||
|
||||
protected String getEndUserIdFromFromReq(HttpServletRequest request) {
|
||||
return getAttrFromReq(request, LoginConstants.WEB_LOGIN_USER_ACCOUNT);
|
||||
}
|
||||
|
||||
protected EndUsers getEndUserFromFromReq(HttpServletRequest request) {
|
||||
return endUsersService.getById(getEndUserIdFromFromReq(request));
|
||||
}
|
||||
|
||||
protected String getAppIdFromReq(HttpServletRequest request) {
|
||||
return getAttrFromReq(request, LoginConstants.API_APP_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中获取属性数据
|
||||
*
|
||||
* @param request 请求
|
||||
* @param key 属性key
|
||||
* @return 属性值
|
||||
*/
|
||||
protected String getAttrFromReq(HttpServletRequest request, String key) {
|
||||
Object userId = request.getAttribute(key);
|
||||
if (null != userId) {
|
||||
return userId.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中获取信息
|
||||
*
|
||||
* @param request 请求
|
||||
* @param key 属性key
|
||||
*/
|
||||
protected String getAttrFromReqHeaders(HttpServletRequest request, String key) {
|
||||
Object userId = request.getHeader(key);
|
||||
if (null != userId) {
|
||||
return userId.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
protected String getApiTokenFromReq(HttpServletRequest request) {
|
||||
// 通常从 header 中读取:Authorization: Bearer token
|
||||
String authHeader = request.getHeader("Authorization");
|
||||
if (StrUtil.isNotBlank(authHeader) && authHeader.startsWith("Bearer ")) {
|
||||
return authHeader.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package oailab.com.apistart.interceptor;
|
||||
|
||||
import cn.hutool.jwt.JWT;
|
||||
import cn.hutool.jwt.JWTUtil;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import oailab.com.apiservice.annotation.PassToken;
|
||||
import oailab.com.apiservice.constants.LoginConstants;
|
||||
import oailab.com.apiservice.enums.sys.ErrorCodeEnum;
|
||||
import oailab.com.apiservice.exception.BizException;
|
||||
import oailab.com.apiservice.mybatis.entity.sys.TenantAccount;
|
||||
import oailab.com.apiservice.mybatis.service.sys.TenantAccountService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Token validation interceptor.
|
||||
*
|
||||
* @author Benny Deng
|
||||
* @version 1.0
|
||||
* @see <a href="http://www.bianmaren.com">Benny Deng</a>
|
||||
* @since 6/17/24 PM11:19
|
||||
**/
|
||||
@Slf4j
|
||||
@Component("sysTokenInterceptor")
|
||||
public class SysTokenInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Resource
|
||||
private TenantAccountService tenantAccountService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
|
||||
|
||||
// 从 http 请求头中取出 token
|
||||
String token = request.getHeader(LoginConstants.LOGIN_AUTHORIZATION);
|
||||
|
||||
// 如果不是映射到方法直接通过
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Method method = handlerMethod.getMethod();
|
||||
|
||||
// 检查是否有 @PassToken 注释,有则跳过认证
|
||||
if (method.isAnnotationPresent(PassToken.class)) {
|
||||
PassToken passToken = method.getAnnotation(PassToken.class);
|
||||
if (passToken.required()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 无token
|
||||
if (token == null) {
|
||||
throw new BizException(ErrorCodeEnum.TokenIsNull);
|
||||
}
|
||||
|
||||
token = extractBearerToken(token);
|
||||
|
||||
// 获取 token 中的 user account
|
||||
String account;
|
||||
JWT jwt = JWTUtil.parseToken(token);
|
||||
account = jwt.getPayload("account").toString();
|
||||
|
||||
TenantAccount tenantAccount = tenantAccountService.getByAccount(account);
|
||||
if (null == tenantAccount) {
|
||||
throw new BizException(ErrorCodeEnum.AccountDoesNotExist);
|
||||
}
|
||||
|
||||
// 验证 token
|
||||
if (!JWTUtil.verify(token, tenantAccount.getPassword().getBytes())) {
|
||||
throw new BizException(ErrorCodeEnum.AuthTokenInvalid);
|
||||
}
|
||||
|
||||
// 将提取的account,放入请求
|
||||
request.setAttribute(LoginConstants.SYS_LOGIN_USER_ACCOUNT, account);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove "Bearer " prefix
|
||||
*
|
||||
* @param authorizationHeader 授权码
|
||||
*/
|
||||
private static String extractBearerToken(String authorizationHeader) {
|
||||
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
|
||||
// Remove "Bearer " prefix
|
||||
return authorizationHeader.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, ModelAndView modelAndView) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package oailab.com.apistart.interceptor;
|
||||
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import oailab.com.apiservice.annotation.PassToken;
|
||||
import oailab.com.apiservice.constants.LoginConstants;
|
||||
import oailab.com.apiservice.enums.sys.ErrorCodeEnum;
|
||||
import oailab.com.apiservice.exception.BizException;
|
||||
import oailab.com.apiservice.mybatis.entity.workspace.EndUsers;
|
||||
import oailab.com.apiservice.mybatis.service.workspace.EndUsersService;
|
||||
import oailab.com.apiservice.service.PassportBusinessService;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Token validation interceptor.
|
||||
*
|
||||
* @author Benny Deng
|
||||
* @version 1.0
|
||||
* @see <a href="http://www.bianmaren.com">Benny Deng</a>
|
||||
* @since 6/17/24 PM11:19
|
||||
**/
|
||||
@Slf4j
|
||||
@Component("webTokenInterceptor")
|
||||
public class WebTokenInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Resource
|
||||
private PassportBusinessService passportBusinessService;
|
||||
@Resource
|
||||
private EndUsersService endUsersService;
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) {
|
||||
|
||||
// 从 http 请求头中取出 token
|
||||
String token = request.getHeader(LoginConstants.LOGIN_AUTHORIZATION);
|
||||
|
||||
// 如果不是映射到方法直接通过
|
||||
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Method method = handlerMethod.getMethod();
|
||||
|
||||
// 检查是否有 @PassToken 注释,有则跳过认证
|
||||
if (method.isAnnotationPresent(PassToken.class)) {
|
||||
PassToken passToken = method.getAnnotation(PassToken.class);
|
||||
if (passToken.required()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 无token
|
||||
if (token == null) {
|
||||
throw new BizException(ErrorCodeEnum.TokenIsNull);
|
||||
}
|
||||
|
||||
token = extractBearerToken(token);
|
||||
|
||||
Map<String, Object> payload = passportBusinessService.parse(token);
|
||||
|
||||
String endUserId = (String) payload.get("endUserId");
|
||||
|
||||
EndUsers endUser = endUsersService.getById(endUserId);
|
||||
if (null == endUser) {
|
||||
throw new BizException(ErrorCodeEnum.AccountDoesNotExist);
|
||||
}
|
||||
|
||||
// 验证 token
|
||||
if (!passportBusinessService.verify(token)) {
|
||||
throw new BizException(ErrorCodeEnum.AuthTokenInvalid);
|
||||
}
|
||||
|
||||
// 将提取的account,放入请求
|
||||
request.setAttribute(LoginConstants.WEB_LOGIN_USER_ACCOUNT, endUserId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove "Bearer " prefix
|
||||
*
|
||||
* @param authorizationHeader 授权码
|
||||
*/
|
||||
private static String extractBearerToken(String authorizationHeader) {
|
||||
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
|
||||
// Remove "Bearer " prefix
|
||||
return authorizationHeader.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, ModelAndView modelAndView) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, Exception ex) {
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user