后端基础框架

This commit is contained in:
2026-02-25 09:56:35 +08:00
commit b964b54fc6
146 changed files with 6919 additions and 0 deletions
@@ -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) {
}
}