First Commit

This commit is contained in:
Administrator
2025-05-12 12:04:42 +08:00
commit 6a5e13974c
1248 changed files with 366157 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
package quant.rich.emoney.config;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import lombok.extern.slf4j.Slf4j;
import quant.rich.emoney.interfaces.ConfigInfo;
import quant.rich.emoney.interfaces.IConfig;
/**
* 实现自动化注册 Config
*/
@Slf4j
@DependsOn("configService")
public class ConfigAutoRegistrar implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AssignableTypeFilter(IConfig.class));
scanner.addIncludeFilter(new AnnotationTypeFilter(ConfigInfo.class));
scanner.findCandidateComponents("quant.rich.emoney.entity.config").forEach(beanDefinition -> {
String className = beanDefinition.getBeanClassName();
try {
// 确保其 field 规则与 configService 内 field 生成规则一致,即:
// 如果 @ConfigInfo 指定了 field 的,使用该 field + "Config"
// 作为 beanName否则使用首字母小写的 simpleClassName 作为
// beanName且 simpleClassName 无论如何必须以 Config 作为结尾。
Class<?> clazz = Class.forName(className);
String beanName = clazz.getSimpleName().substring(0, 1).toLowerCase()
+ clazz.getSimpleName().substring(1);
if (!IConfig.class.isAssignableFrom(clazz)) {
log.warn("Config {} does not implement IConfig, ignore", beanName);
return;
}
if (!beanName.endsWith("Config")) {
log.warn("Config {}'s simple class name does not end with \"Config\", ignore", beanName);
return;
}
ConfigInfo info = clazz.getAnnotation(ConfigInfo.class);
if (info == null) {
log.warn("Config {} does not have @ConfigInfo annotation, ignore", clazz.getName());
return;
}
if (StringUtils.isNotBlank(info.field())) {
beanName = info.field() + "Config";
}
BeanDefinitionBuilder factoryBean = BeanDefinitionBuilder
.genericBeanDefinition(ConfigServiceFactoryBean.class)
.addConstructorArgValue(clazz);
// 注意此处注册 factoryBean 不意味着 FactoryBean.getObject() 方法立即被执行,
// Spring 管理的 Bean 默认在其被使用时才创建,所以如果 getObject() 调用一些方
// 法,这些方法会在初次使用 Bean 时才被创建。如果这些方法对于启动过程很重要,
// 需要在对应 Config(Bean) 上加上 @Bean 和 @Lazy(false) 注解,确保一旦准备好
// 相应的 Bean 就会被创建。
registry.registerBeanDefinition(beanName, factoryBean.getBeanDefinition());
log.info("Add config {} to bean register", beanName);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Failed to load class: " + className, e);
}
});
}
}

View File

@@ -0,0 +1,70 @@
package quant.rich.emoney.config;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import lombok.extern.slf4j.Slf4j;
import quant.rich.emoney.interfaces.IConfig;
import quant.rich.emoney.service.ConfigService;
/**
* 实现配置项自动载入
* @param <T>
*/
@Slf4j
public class ConfigServiceFactoryBean<T extends IConfig<T>> implements FactoryBean<T>, BeanNameAware {
private final Class<T> targetClass;
private String beanName;
@Autowired
private AutowireCapableBeanFactory beanFactory;
@Autowired
private ConfigService configService;
public ConfigServiceFactoryBean(Class<T> targetClass) {
this.targetClass = targetClass;
}
@Override
public void setBeanName(String name) {
this.beanName = name;
}
@Override
public T getObject() throws Exception {
ConstructionGuard.enter(targetClass);
boolean success = true;
try {
T bean = configService.getConfig(targetClass);
beanFactory.autowireBean(bean);
beanFactory.initializeBean(bean, beanName);
configService.saveOrUpdate(bean);
return bean;
}
catch (Exception e) {
log.error("Fail to load config: " + targetClass.getName(), e);
success = false;
throw e;
}
finally {
ConstructionGuard.exit(targetClass);
if (success) {
log.debug("getObject() for {} success", targetClass.toString());
}
}
}
@Override
public Class<?> getObjectType() {
return targetClass;
}
@Override
public boolean isSingleton() {
return true;
}
}

View File

@@ -0,0 +1,36 @@
package quant.rich.emoney.config;
import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.BeanCreationException;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ConstructionGuard {
private static final ThreadLocal<Set<Class<?>>> constructing = ThreadLocal.withInitial(HashSet::new);
public static boolean isConstructing(Class<?> clazz) {
return constructing.get().contains(clazz);
}
public static void enter(Class<?> clazz) {
log.debug("Enter construction for {}", clazz.toString());
if (isConstructing(clazz)) {
StringBuilder sb = new StringBuilder();
sb.append("Class ")
.append(clazz.getName())
.append(" is being constructed but is seems like circular involving.");
String msg = sb.toString();
log.error(msg);
throw new BeanCreationException(msg);
}
constructing.get().add(clazz);
}
public static void exit(Class<?> clazz) {
constructing.get().remove(clazz);
log.debug("Exit construction for {}", clazz.toString());
}
}

View File

@@ -0,0 +1,25 @@
package quant.rich.emoney.config;
import javax.sql.DataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DataSourceConfig {
@Bean(name = "postgreDataSource")
@ConfigurationProperties(prefix = "spring.datasource.postgre")
public DataSource postgreDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "sqliteDataSource")
@ConfigurationProperties(prefix = "spring.datasource.sqlite")
public DataSource sqliteDataSource() {
DataSource dataSource = DataSourceBuilder.create().build();
return dataSource;
}
}

View File

@@ -0,0 +1,38 @@
package quant.rich.emoney.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import quant.rich.emoney.interceptor.BaseInterceptor;
import quant.rich.emoney.service.ConfigService;
/**
* Emoney-Auto 配置项
*
* @author Doghole
*
*/
@Configuration
@Import(ConfigAutoRegistrar.class)
public class EmoneyAutoConfig implements WebMvcConfigurer {
@Autowired
BaseInterceptor baseInterceptor;
@Autowired
ConfigService configService;
/**
* 简单鉴权/配置项注入拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(baseInterceptor).addPathPatterns("/**");
}
}

View File

@@ -0,0 +1,31 @@
package quant.rich.emoney.config;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
@Configuration
public class KaptchaConfig {
@Bean(name = "kaptchaProperties")
@ConfigurationProperties
Properties getKaptchaProperties() {
return new Properties();
}
@Bean
@DependsOn("kaptchaProperties")
DefaultKaptcha getDefaultKaptcha(@Autowired Properties kaptchaProperties) {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Config config = new Config(kaptchaProperties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}

View File

@@ -0,0 +1,47 @@
package quant.rich.emoney.config;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
@Configuration
@MapperScan(basePackages = "quant.rich.emoney.mapper.postgre", sqlSessionTemplateRef = "postgreSqlSessionTemplate")
public class PostgreMybatisConfig {
@Bean("postgreSqlSessionFactory")
public SqlSessionFactory postgreSqlSessionFactory(
@Qualifier("postgreDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
factory.setPlugins(interceptor);
return factory.getObject();
}
@Bean("postgreSqlSessionTemplate")
public SqlSessionTemplate sqliteSqlSessionTemplate(@Qualifier("postgreSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean("postgreTransactionManager")
public DataSourceTransactionManager postgreTransacetionManager(@Qualifier("postgreDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}

View File

@@ -0,0 +1,25 @@
package quant.rich.emoney.config;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ConfigurationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Reflections configuration, support autowired
*
* @author Bakwat
*
*/
@Configuration
public class ReflectionsConfig {
@Bean("reflections")
Reflections reflections() {
return new Reflections(new ConfigurationBuilder()
.addScanners(Scanners.MethodsAnnotated, Scanners.SubTypes, Scanners.TypesAnnotated)
.forPackages("quant.rich.emoney"));
}
}

View File

@@ -0,0 +1,71 @@
package quant.rich.emoney.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import lombok.RequiredArgsConstructor;
import quant.rich.emoney.service.AuthService;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final AuthService userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.headers(headers -> headers.cacheControl(cache -> cache.disable()))
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/*/login").permitAll()
.requestMatchers("/admin/*/static/**").permitAll()
.requestMatchers("/public/**").permitAll()
.requestMatchers("/captcha/**").permitAll()
.requestMatchers("/api/**").permitAll()
.requestMatchers("/img/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form // 开启表单登录,并指定登录页
.loginPage("/admin/v1/login") // 指定登录页
.loginProcessingUrl("/admin/v1/doLogin") // 处理登录请求的 URL
.defaultSuccessUrl("/admin/v1/", false) // 登录成功后默认跳转
.permitAll())
.logout(logout -> logout
.logoutUrl("/admin/v1/logout")
.logoutSuccessUrl("/admin/v1/login")
.invalidateHttpSession(true)
.permitAll())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
);
;
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(@Autowired PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return new ProviderManager(provider);
}
@SuppressWarnings("deprecation")
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}

View File

@@ -0,0 +1,49 @@
package quant.rich.emoney.config;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
@Configuration
@MapperScan(basePackages = "quant.rich.emoney.mapper.sqlite", sqlSessionTemplateRef = "sqliteSqlSessionTemplate")
public class SqliteMybatisConfig {
public static final String SQLITE_TRANSACTION_MANAGER = "sqliteTransactionManager";
@Bean("sqliteSqlSessionFactory")
public SqlSessionFactory sqliteSqlSessionFactory(
@Qualifier("sqliteDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQLITE));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
factory.setPlugins(interceptor);
return factory.getObject();
}
@Bean("sqliteSqlSessionTemplate")
public SqlSessionTemplate sqliteSqlSessionTemplate(@Qualifier("sqliteSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean("sqliteTransactionManager")
public DataSourceTransactionManager postgreTransacetionManager(@Qualifier("sqliteDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}