diff --git a/pom.xml b/pom.xml
index e06d014..53fecdd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,26 +52,16 @@
spring-boot-starter-web
+
- org.springframework.boot
- spring-boot-starter-security
-
-
- org.springframework.boot
- spring-boot-starter-websocket
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ ${springdoc.version}
-
com.mysql
mysql-connector-j
- 8.4.0
- runtime
-
-
-
- org.postgresql
- postgresql
runtime
@@ -101,51 +91,33 @@
spring-boot-starter-test
test
-
- org.springdoc
- springdoc-openapi-starter-webmvc-ui
- ${springdoc.version}
-
-
-
- org.springframework.boot
- spring-boot-starter-data-redis
-
-
-
- io.jsonwebtoken
- jjwt-api
- ${jjwt.version}
-
-
- io.jsonwebtoken
- jjwt-impl
- ${jjwt.version}
- runtime
-
-
- io.jsonwebtoken
- jjwt-jackson
- ${jjwt.version}
- runtime
-
-
- org.springframework.boot
- spring-boot-starter-mail
-
-
-
- org.springframework.boot
- spring-boot-starter-quartz
-
-
-
- org.springframework.boot
- spring-boot-starter-aop
+ cc.amily49.common
+ amily-common
+ 0.0.1-SNAPSHOT
+
+
+ amily-nexus
+ Amily Snapshot Repository
+ https://nexus.silencelurker.xyz/repository/amily-maven-snapshot/
+
+ true
+ always
+
+
+
+ amily-nexus-release
+ Amily Release Repository
+ https://nexus.silencelurker.xyz/repository/amily-maven-release/
+
+ true
+
+
+
+
diff --git a/src/main/java/cc/amily49/api/module/ModuleApplication.java b/src/main/java/cc/amily49/api/module/ModuleApplication.java
index f577823..12692ae 100644
--- a/src/main/java/cc/amily49/api/module/ModuleApplication.java
+++ b/src/main/java/cc/amily49/api/module/ModuleApplication.java
@@ -2,22 +2,15 @@ package cc.amily49.api.module;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
-import org.springframework.data.web.config.EnableSpringDataWebSupport;
-import org.springframework.scheduling.annotation.EnableAsync;
-import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
-@SpringBootApplication(scanBasePackages = "cc.amily49.api.module")
-@EnableAsync
-@EnableScheduling
-@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO)
-@EnableRedisRepositories(basePackages = {
- // Add your redis repositories here
-})
+@SpringBootApplication
+@EnableJpaAuditing
+@ComponentScan(basePackages = {"cc.amily49.api.module", "cc.amily49.common"})
public class ModuleApplication {
- public static void main(String[] args) {
- SpringApplication.run(ModuleApplication.class, args);
- }
-
+ public static void main(String[] args) {
+ SpringApplication.run(ModuleApplication.class, args);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/cc/amily49/api/module/auth/filter/JwtAuthenticationFilter.java b/src/main/java/cc/amily49/api/module/auth/filter/JwtAuthenticationFilter.java
deleted file mode 100644
index 50dc64c..0000000
--- a/src/main/java/cc/amily49/api/module/auth/filter/JwtAuthenticationFilter.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package cc.amily49.api.module.auth.filter;
-
-import io.jsonwebtoken.ExpiredJwtException;
-import jakarta.annotation.Resource;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
-import org.springframework.stereotype.Component;
-import org.springframework.web.filter.OncePerRequestFilter;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-
-import cc.amily49.api.module.auth.model.JwtUser;
-import cc.amily49.api.module.auth.util.JwtUtil;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Stateless JWT Filter for Module Template
- *
- * @author Silence_Lurker by Gemini
- */
-@Component
-public class JwtAuthenticationFilter extends OncePerRequestFilter {
-
- @Resource
- private JwtUtil jwtUtil;
-
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
- throws ServletException, IOException {
-
- final String requestTokenHeader = request.getHeader("Authorization");
-
- String username = null;
- String jwtToken = null;
-
- if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
- jwtToken = requestTokenHeader.substring(7);
- try {
- username = jwtUtil.getUsernameFromToken(jwtToken);
- } catch (IllegalArgumentException e) {
- logger.warn("Unable to get JWT Token");
- } catch (ExpiredJwtException e) {
- logger.warn("JWT Token has expired");
- }
- }
-
- if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
-
- // Validate token signature and expiration ONLY (Stateless)
- if (jwtUtil.validateToken(jwtToken)) {
-
- // Extract roles from token
- List roles = jwtUtil.getRolesFromToken(jwtToken);
- Set authorities = null;
-
- if (roles != null) {
- authorities = roles.stream()
- .map(SimpleGrantedAuthority::new)
- .collect(Collectors.toSet());
- }
-
- // Create stateless user details
- JwtUser userDetails = new JwtUser(username, authorities);
-
- UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
- userDetails, null, userDetails.getAuthorities());
-
- auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
- SecurityContextHolder.getContext().setAuthentication(auth);
- }
- }
- chain.doFilter(request, response);
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/auth/model/JwtUser.java b/src/main/java/cc/amily49/api/module/auth/model/JwtUser.java
deleted file mode 100644
index 67fbf2a..0000000
--- a/src/main/java/cc/amily49/api/module/auth/model/JwtUser.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package cc.amily49.api.module.auth.model;
-
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
-import java.util.Collection;
-import java.util.Set;
-
-public class JwtUser implements UserDetails {
-
- private final String username;
- private final Set extends GrantedAuthority> authorities;
-
- public JwtUser(String username, Set extends GrantedAuthority> authorities) {
- this.username = username;
- this.authorities = authorities;
- }
-
- @Override
- public Collection extends GrantedAuthority> getAuthorities() {
- return authorities;
- }
-
- @Override
- public String getPassword() {
- return null; // No password in token-based auth
- }
-
- @Override
- public String getUsername() {
- return username;
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
-
- @Override
- public boolean isEnabled() {
- return true;
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/auth/util/JwtUtil.java b/src/main/java/cc/amily49/api/module/auth/util/JwtUtil.java
deleted file mode 100644
index 2d33627..0000000
--- a/src/main/java/cc/amily49/api/module/auth/util/JwtUtil.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package cc.amily49.api.module.auth.util;
-
-import io.jsonwebtoken.Claims;
-import io.jsonwebtoken.Jwts;
-import io.jsonwebtoken.security.Keys;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Component;
-
-import jakarta.annotation.PostConstruct;
-import javax.crypto.SecretKey;
-import java.io.Serializable;
-import java.util.Base64;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.List;
-import java.util.function.Function;
-
-/**
- * Utility class for handling JWT tokens.
- * Generation, validation, etc.
- *
- * @author Silence_Lurker by Gemini
- */
-@Component
-public class JwtUtil implements Serializable {
-
- private static final long serialVersionUID = -2550185165626007488L;
-
- // Token validity in milliseconds (e.g., 10 hours)
- public static final long JWT_TOKEN_VALIDITY = 10 * 60 * 60 * 1000;
-
- @Value("${amily.jwt.secret}")
- private String secretString;
-
- private SecretKey secretKey;
-
- @PostConstruct
- public void init() {
- byte[] decodedKey = Base64.getDecoder().decode(secretString);
- this.secretKey = Keys.hmacShaKeyFor(decodedKey);
- }
-
- public String getUsernameFromToken(String token) {
- return getClaimFromToken(token, Claims::getSubject);
- }
-
- public Date getExpirationDateFromToken(String token) {
- return getClaimFromToken(token, Claims::getExpiration);
- }
-
- public String getIpFromToken(String token) {
- return getClaimFromToken(token, claims -> claims.get("ip", String.class));
- }
-
- public List getRolesFromToken(String token) {
- return getClaimFromToken(token, claims -> claims.get("roles", List.class));
- }
-
- public T getClaimFromToken(String token, Function claimsResolver) {
- final Claims claims = getAllClaimsFromToken(token);
- return claimsResolver.apply(claims);
- }
-
- private Claims getAllClaimsFromToken(String token) {
- return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload();
- }
-
- private Boolean isTokenExpired(String token) {
- final Date expiration = getExpirationDateFromToken(token);
- return expiration.before(new Date());
- }
-
- /**
- * Validate token (Stateless)
- * Checks signature (implicit in getAllClaimsFromToken) and expiration.
- */
- public Boolean validateToken(String token) {
- try {
- return !isTokenExpired(token);
- } catch (Exception e) {
- return false;
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/cc/amily49/api/module/config/QuartzConfig.java b/src/main/java/cc/amily49/api/module/config/QuartzConfig.java
deleted file mode 100644
index e77021d..0000000
--- a/src/main/java/cc/amily49/api/module/config/QuartzConfig.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package cc.amily49.api.module.config;
-
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.autoconfigure.quartz.QuartzDataSource;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import javax.sql.DataSource;
-
-@Configuration
-public class QuartzConfig {
-
- @Bean
- @QuartzDataSource
- public DataSource quartzDataSource(@Qualifier("primaryDataSource") DataSource primaryDataSource) {
- return primaryDataSource;
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/config/SecurityConfig.java b/src/main/java/cc/amily49/api/module/config/SecurityConfig.java
deleted file mode 100644
index dab75a4..0000000
--- a/src/main/java/cc/amily49/api/module/config/SecurityConfig.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package cc.amily49.api.module.config;
-
-import java.util.Arrays;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
-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.web.SecurityFilterChain;
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-import org.springframework.web.cors.CorsConfiguration;
-import org.springframework.web.cors.CorsConfigurationSource;
-import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
-
-import cc.amily49.api.module.auth.filter.JwtAuthenticationFilter;
-import jakarta.annotation.Resource;
-
-/**
- * Security Configuration for Modules (Stateless, No Login)
- *
- * @author Silence_Lurker by Gemini
- */
-@Configuration
-@EnableWebSecurity
-@EnableMethodSecurity
-public class SecurityConfig {
-
- @Resource
- private JwtAuthenticationFilter jwtAuthenticationFilter;
-
- @Bean
- public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
- http
- .cors(cors -> cors.configurationSource(corsConfigurationSource()))
- .csrf(csrf -> csrf.disable())
- .headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()))
- .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
- .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
- .authorizeHttpRequests(authz -> authz
- // Swagger UI
- .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
- // Public endpoints (if any)
- .requestMatchers("/public/**").permitAll()
- // All other endpoints require authentication
- .anyRequest().authenticated());
-
- return http.build();
- }
-
- @Bean
- CorsConfigurationSource corsConfigurationSource() {
- CorsConfiguration strictConfig = new CorsConfiguration();
- strictConfig.setAllowedOriginPatterns(Arrays.asList(
- "http://localhost:*",
- "http://127.0.0.1:*",
- "http://*.amily49.cc",
- "https://*.amily49.cc",
- "https://amily49.cc"
- ));
- strictConfig.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
- strictConfig.setAllowCredentials(true);
- strictConfig.setAllowedHeaders(Arrays.asList("*"));
- strictConfig.setExposedHeaders(Arrays.asList("Authorization"));
-
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/**", strictConfig);
- return source;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/cc/amily49/api/module/config/datasource/DataSourceWarmupRunner.java b/src/main/java/cc/amily49/api/module/config/datasource/DataSourceWarmupRunner.java
deleted file mode 100644
index c2edcb8..0000000
--- a/src/main/java/cc/amily49/api/module/config/datasource/DataSourceWarmupRunner.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package cc.amily49.api.module.config.datasource;
-
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.stereotype.Component;
-import org.springframework.beans.factory.annotation.Qualifier;
-import javax.sql.DataSource;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-
-@Component
-public class DataSourceWarmupRunner implements CommandLineRunner {
-
- private final DataSource primaryDataSource;
-
- public DataSourceWarmupRunner(@Qualifier("primaryDataSource") DataSource primaryDataSource) {
- this.primaryDataSource = primaryDataSource;
- }
-
- @Override
- public void run(String... args) {
- System.out.println("Wait for database connection warmup...");
- long start = System.currentTimeMillis();
- try (Connection conn = primaryDataSource.getConnection();
- PreparedStatement ps = conn.prepareStatement("SELECT 1")) {
- try (ResultSet rs = ps.executeQuery()) {
- while(rs.next()) {
- // Just consume result
- }
- }
- long end = System.currentTimeMillis();
- System.out.println("Database connection warmup completed in " + (end - start) + "ms.");
- } catch (Exception e) {
- System.err.println("Database warmup failed: " + e.getMessage());
- // We don't throw exception here to allow app startup even if warmup fails,
- // though it might fail later on actual requests.
- }
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/config/datasource/PrimaryDataSourceConfig.java b/src/main/java/cc/amily49/api/module/config/datasource/PrimaryDataSourceConfig.java
deleted file mode 100644
index 4e1e19c..0000000
--- a/src/main/java/cc/amily49/api/module/config/datasource/PrimaryDataSourceConfig.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package cc.amily49.api.module.config.datasource;
-
-import jakarta.persistence.EntityManagerFactory;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
-import org.springframework.orm.jpa.JpaTransactionManager;
-import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.annotation.EnableTransactionManagement;
-
-import javax.sql.DataSource;
-
-@Configuration
-@EnableTransactionManagement
-@EnableJpaRepositories(
- basePackages = {
- "cc.amily49.api.module.auth.repository.jpa",
- "cc.amily49.api.module.common.repository.jpa"
- },
- entityManagerFactoryRef = "entityManagerFactoryPrimary",
- transactionManagerRef = "transactionManagerPrimary"
-)
-public class PrimaryDataSourceConfig {
-
- @Bean
- @Primary
- @ConfigurationProperties("spring.datasource.primary")
- public DataSourceProperties primaryDataSourceProperties() {
- return new DataSourceProperties();
- }
-
- @Bean(name = "primaryDataSource")
- @Primary
- @ConfigurationProperties("spring.datasource.primary.hikari")
- public DataSource primaryDataSource() {
- return primaryDataSourceProperties().initializeDataSourceBuilder().build();
- }
-
- @Bean(name = "entityManagerFactoryPrimary")
- @Primary
- public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(
- EntityManagerFactoryBuilder builder,
- @Qualifier("primaryDataSource") DataSource dataSource) {
- return builder
- .dataSource(dataSource)
- .packages("cc.amily49.api.module.auth", "cc.amily49.api.module.common") // Explicitly scan business modules
- .persistenceUnit("primary")
- .build();
- }
-
- @Bean(name = "transactionManagerPrimary")
- @Primary
- public PlatformTransactionManager transactionManagerPrimary(
- @Qualifier("entityManagerFactoryPrimary") EntityManagerFactory entityManagerFactory) {
- return new JpaTransactionManager(entityManagerFactory);
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/config/datasource/SecondaryDataSourceConfig.java b/src/main/java/cc/amily49/api/module/config/datasource/SecondaryDataSourceConfig.java
deleted file mode 100644
index 48ad853..0000000
--- a/src/main/java/cc/amily49/api/module/config/datasource/SecondaryDataSourceConfig.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package cc.amily49.api.module.config.datasource;
-
-import jakarta.persistence.EntityManagerFactory;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
-import org.springframework.orm.jpa.JpaTransactionManager;
-import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.annotation.EnableTransactionManagement;
-
-import javax.sql.DataSource;
-import java.util.HashMap;
-import java.util.Map;
-
-@Configuration
-@EnableTransactionManagement
-@EnableJpaRepositories(
- basePackages = "cc.amily49.api.module.warehouse",
- entityManagerFactoryRef = "entityManagerFactorySecondary",
- transactionManagerRef = "transactionManagerSecondary"
-)
-public class SecondaryDataSourceConfig {
-
- @Bean
- @ConfigurationProperties("spring.datasource.secondary")
- public DataSourceProperties secondaryDataSourceProperties() {
- return new DataSourceProperties();
- }
-
- @Bean(name = "secondaryDataSource")
- @ConfigurationProperties("spring.datasource.secondary.hikari")
- public DataSource secondaryDataSource() {
- return secondaryDataSourceProperties().initializeDataSourceBuilder().build();
- }
-
- @Bean(name = "entityManagerFactorySecondary")
- public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary(
- EntityManagerFactoryBuilder builder,
- @Qualifier("secondaryDataSource") DataSource dataSource) {
-
- Map properties = new HashMap<>();
- properties.put("hibernate.hbm2ddl.auto", "update"); // Auto-create tables for warehouse
- properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
-
- return builder
- .dataSource(dataSource)
- .packages("cc.amily49.api.module.warehouse")
- .persistenceUnit("secondary")
- .properties(properties)
- .build();
- }
-
- @Bean(name = "transactionManagerSecondary")
- public PlatformTransactionManager transactionManagerSecondary(
- @Qualifier("entityManagerFactorySecondary") EntityManagerFactory entityManagerFactory) {
- return new JpaTransactionManager(entityManagerFactory);
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/filter/FullApiFilter.java b/src/main/java/cc/amily49/api/module/filter/FullApiFilter.java
deleted file mode 100644
index ca06f1f..0000000
--- a/src/main/java/cc/amily49/api/module/filter/FullApiFilter.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package cc.amily49.api.module.filter;
-
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Component;
-import org.springframework.web.util.ContentCachingRequestWrapper;
-
-import cc.amily49.api.module.warehouse.entity.OperationLog;
-import cc.amily49.api.module.warehouse.service.LogService;
-import jakarta.annotation.Resource;
-import jakarta.servlet.Filter;
-import jakarta.servlet.FilterChain;
-import jakarta.servlet.ServletException;
-import jakarta.servlet.ServletRequest;
-import jakarta.servlet.ServletResponse;
-import jakarta.servlet.annotation.WebFilter;
-import jakarta.servlet.http.HttpServletRequest;
-import jakarta.servlet.http.HttpServletResponse;
-
-@Component
-public class FullApiFilter implements Filter {
-
- @Resource
- private LogService logService;
-
- @Value("${app.time-zone:Asia/Shanghai}")
- private String appTimeZone;
-
- @Override
- public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
- throws IOException, ServletException {
- HttpServletRequest req = (HttpServletRequest) request;
- // Wrap the request to cache the body content for logging
- ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(req);
- long startTime = System.currentTimeMillis();
-
- try {
- // Use the wrapped request for the filter chain
- chain.doFilter(wrappedRequest, response);
- } catch (Exception e) {
- req.setAttribute("filter_exception", e.getMessage());
- throw e;
- } finally {
- try {
- HttpServletResponse httpRes = (HttpServletResponse) response;
- String servletPath = wrappedRequest.getServletPath();
- String uri = wrappedRequest.getRequestURI();
-
- // 跳过高频接口日志
- if ("/message/newest".equals(servletPath)) {
- return;
- }
-
- long duration = System.currentTimeMillis() - startTime;
- String clientIp = getClientIp(wrappedRequest);
- String method = wrappedRequest.getMethod();
- String params;
-
- // 登录接口登录成功(状态码 2xx)时不记录参数,仅记录失败日志
- if ("/access/login".equals(servletPath) && httpRes.getStatus() >= 200 && httpRes.getStatus() < 300) {
- params = "[PROTECTED]";
- } else {
- params = getParams(wrappedRequest);
- }
-
- OperationLog opLog = new OperationLog();
- opLog.setIp(clientIp);
- opLog.setUrl(uri);
- opLog.setMethod(method);
- opLog.setParams(params);
- opLog.setDuration(duration);
- opLog.setCreateTime(LocalDateTime.now(ZoneId.of(appTimeZone)));
- opLog.setDescription("API Access Log");
-
- // 获取当前登录用户
- Authentication auth = SecurityContextHolder.getContext().getAuthentication();
- if (auth != null && auth.isAuthenticated() && !"anonymousUser".equals(auth.getPrincipal())) {
- opLog.setUsername(auth.getName());
- }
-
- // 记录可能的异常信息
- Object exception = req.getAttribute("filter_exception");
- if (exception != null) {
- opLog.setException(exception.toString());
- }
-
- logService.saveLog(opLog);
-
- } catch (Exception ex) {
- System.err.println("Failed to save access log: " + ex.getMessage());
- }
- }
- }
-
- private String getParams(ContentCachingRequestWrapper request) {
- StringBuilder params = new StringBuilder();
-
- // Query String
- String queryString = request.getQueryString();
- if (queryString != null && !queryString.isEmpty()) {
- params.append("Query: ").append(queryString);
- }
-
- // Body
- // Note: ContentCachingRequestWrapper only caches content after it has been read.
- // If the controller didn't read the body (e.g. GET request or error before reading), this will be empty.
- byte[] content = request.getContentAsByteArray();
- if (content.length > 0) {
- if (params.length() > 0) {
- params.append("; ");
- }
- try {
- String body = new String(content, StandardCharsets.UTF_8);
- // Simple truncation or formatting could be added here if needed
- params.append("Body: ").append(body);
- } catch (Exception e) {
- params.append("Body: [Error reading body]");
- }
- }
-
- return params.toString();
- }
-
- private String getClientIp(HttpServletRequest request) {
- String ip = request.getHeader("X-Forwarded-For");
- if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("X-Real-IP");
- }
- if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getRemoteAddr();
- }
-
- if (ip != null && ip.contains(",")) {
- ip = ip.split(",")[0].trim();
- }
-
- return ip;
- }
-
-}
\ No newline at end of file
diff --git a/src/main/java/cc/amily49/api/module/util/BaseEncoder.java b/src/main/java/cc/amily49/api/module/util/BaseEncoder.java
deleted file mode 100644
index 020ca16..0000000
--- a/src/main/java/cc/amily49/api/module/util/BaseEncoder.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package cc.amily49.api.module.util;
-
-import java.util.Base64;
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.HexFormat;
-
-/**
- * @author Silence_Lurker
- */
-public class BaseEncoder {
-
- public static enum Algorithm {
- BASE64,
- BASE64_URL,
- HEX,
- MD5,
- SHA256,
- SHA1,
- REMOVE_SPECIAL_CHARS, // 你原来的功能
- URL_ENCODE
- }
-
- private BaseEncoder() {
- }
-
- public static String encodeByTarget(String target, Algorithm algorithm) {
- if (target == null) {
- return null;
- }
-
- try {
- return switch (algorithm) {
- case BASE64 -> encodeBase64(target);
- case BASE64_URL -> encodeBase64Url(target);
- case HEX -> encodeHex(target);
- case MD5 -> encodeMD5(target);
- case SHA256 -> encodeSHA256(target);
- case SHA1 -> encodeSHA1(target);
- case REMOVE_SPECIAL_CHARS -> removeSpecialChars(target);
- case URL_ENCODE -> urlEncode(target);
- };
- } catch (Exception e) {
- throw new RuntimeException("编码失败: " + e.getMessage(), e);
- }
- }
-
- private static String encodeBase64(String target) {
- return Base64.getEncoder().encodeToString(target.getBytes(StandardCharsets.UTF_8));
- }
-
- private static String encodeBase64Url(String target) {
- return Base64.getUrlEncoder().encodeToString(target.getBytes(StandardCharsets.UTF_8));
- }
-
- private static String encodeHex(String target) {
- byte[] bytes = target.getBytes(StandardCharsets.UTF_8);
- return HexFormat.of().formatHex(bytes);
- }
-
- private static String encodeMD5(String target) throws NoSuchAlgorithmException {
- MessageDigest md = MessageDigest.getInstance("MD5");
- byte[] digest = md.digest(target.getBytes(StandardCharsets.UTF_8));
- return HexFormat.of().formatHex(digest);
- }
-
- private static String encodeSHA256(String target) throws NoSuchAlgorithmException {
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- byte[] digest = md.digest(target.getBytes(StandardCharsets.UTF_8));
- return HexFormat.of().formatHex(digest);
- }
-
- private static String encodeSHA1(String target) throws NoSuchAlgorithmException {
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- byte[] digest = md.digest(target.getBytes(StandardCharsets.UTF_8));
- return HexFormat.of().formatHex(digest);
- }
-
- private static String removeSpecialChars(String target) {
- return target.replaceAll("[^a-zA-Z0-9]", "");
- }
-
- private static String urlEncode(String target) {
- return java.net.URLEncoder.encode(target, StandardCharsets.UTF_8);
- }
-
- // 重载方法,保持向后兼容
- public static String encodeByTarget(String target) {
- return encodeByTarget(target, Algorithm.REMOVE_SPECIAL_CHARS);
- }
-
- // 工具方法:验证编码结果
- public static boolean verify(String original, String encoded, Algorithm algorithm) {
- String newEncoded = encodeByTarget(original, algorithm);
- return newEncoded.equals(encoded);
- }
-}
\ No newline at end of file
diff --git a/src/main/java/cc/amily49/api/module/util/UUIDGenerater.java b/src/main/java/cc/amily49/api/module/util/UUIDGenerater.java
deleted file mode 100644
index acac48c..0000000
--- a/src/main/java/cc/amily49/api/module/util/UUIDGenerater.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package cc.amily49.api.module.util;
-
-/**
- * @author Silence_Lurker
- */
-public class UUIDGenerater {
- private UUIDGenerater() {
- }
-
- public static String generate() {
- return java.util.UUID.randomUUID().toString();
- }
-
- public static String generateWithSalt(byte[] salt) {
- return java.util.UUID.nameUUIDFromBytes(salt).toString();
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/warehouse/annotation/Log.java b/src/main/java/cc/amily49/api/module/warehouse/annotation/Log.java
deleted file mode 100644
index e2c71fc..0000000
--- a/src/main/java/cc/amily49/api/module/warehouse/annotation/Log.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package cc.amily49.api.module.warehouse.annotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Log {
- /**
- * Operation description
- */
- String value() default "";
-}
diff --git a/src/main/java/cc/amily49/api/module/warehouse/aspect/LogAspect.java b/src/main/java/cc/amily49/api/module/warehouse/aspect/LogAspect.java
deleted file mode 100644
index bd42fa5..0000000
--- a/src/main/java/cc/amily49/api/module/warehouse/aspect/LogAspect.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package cc.amily49.api.module.warehouse.aspect;
-
-import cc.amily49.api.module.warehouse.annotation.Log;
-import cc.amily49.api.module.warehouse.entity.OperationLog;
-import cc.amily49.api.module.warehouse.service.LogService;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import jakarta.servlet.http.HttpServletRequest;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.aspectj.lang.ProceedingJoinPoint;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.reflect.MethodSignature;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.stereotype.Component;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import org.springframework.beans.factory.annotation.Value;
-
-@Aspect
-@Component
-@Slf4j
-@RequiredArgsConstructor
-public class LogAspect {
-
- private final LogService logService;
- private final ObjectMapper objectMapper;
-
- @Value("${app.time-zone:Asia/Shanghai}")
- private String appTimeZone;
-
- @Around("@annotation(logAnnotation)")
- public Object around(ProceedingJoinPoint point, Log logAnnotation) throws Throwable {
- long startTime = System.currentTimeMillis();
- Object result = null;
- String exceptionMsg = null;
-
- try {
- result = point.proceed();
- } catch (Throwable e) {
- exceptionMsg = e.getMessage();
- throw e;
- } finally {
- long duration = System.currentTimeMillis() - startTime;
- recordLog(point, logAnnotation, duration, exceptionMsg);
- }
- return result;
- }
-
- private void recordLog(ProceedingJoinPoint point, Log logAnnotation, long duration, String exceptionMsg) {
- try {
- MethodSignature signature = (MethodSignature) point.getSignature();
-
- OperationLog operationLog = new OperationLog();
- if (logAnnotation != null) {
- operationLog.setDescription(logAnnotation.value());
- }
-
- // Method info
- String className = point.getTarget().getClass().getName();
- String methodName = signature.getName();
- operationLog.setClassMethod(className + "." + methodName + "()");
-
- // Request info
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- if (attributes != null) {
- HttpServletRequest request = attributes.getRequest();
- operationLog.setUrl(request.getRequestURL().toString());
- operationLog.setMethod(request.getMethod());
- operationLog.setIp(getClientIp(request));
- }
-
- // Args (simplify params to avoid huge logs)
- // 如果是登录成功,脱敏参数
- if ("User Login".equals(logAnnotation.value()) && exceptionMsg == null) {
- operationLog.setParams("[PROTECTED]");
- } else {
- try {
- Object[] args = point.getArgs();
- // Filter out non-serializable objects like HttpServletRequest,
- // HttpServletResponse
- Object[] filteredArgs = Arrays.stream(args)
- .filter(arg -> !(arg instanceof jakarta.servlet.http.HttpServletRequest)
- && !(arg instanceof jakarta.servlet.http.HttpServletResponse)
- && !(arg instanceof org.springframework.web.multipart.MultipartFile))
- .toArray();
-
- String params = objectMapper.writeValueAsString(filteredArgs);
- // Truncate if too long
- if (params.length() > 2000) {
- params = params.substring(0, 2000) + "...";
- }
- operationLog.setParams(params);
- } catch (Exception e) {
- operationLog.setParams("Failed to serialize args: " + e.getMessage());
- }
- }
-
- // User info
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
- if (authentication != null && authentication.isAuthenticated() && !"anonymousUser".equals(authentication.getPrincipal())) {
- operationLog.setUsername(authentication.getName());
- } else {
- operationLog.setUsername("Anonymous");
- }
-
- operationLog.setException(exceptionMsg);
- operationLog.setDuration(duration);
- operationLog.setCreateTime(LocalDateTime.now(ZoneId.of(appTimeZone)));
-
- logService.saveLog(operationLog);
-
- } catch (Exception e) {
- log.error("LogAspect error", e);
- }
- }
-
- private String getClientIp(HttpServletRequest request) {
- String ip = request.getHeader("X-Forwarded-For");
- if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("Proxy-Client-IP");
- }
- if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getHeader("WL-Proxy-Client-IP");
- }
- if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
- ip = request.getRemoteAddr();
- }
- return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/warehouse/controller/LogController.java b/src/main/java/cc/amily49/api/module/warehouse/controller/LogController.java
deleted file mode 100644
index d9d4499..0000000
--- a/src/main/java/cc/amily49/api/module/warehouse/controller/LogController.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package cc.amily49.api.module.warehouse.controller;
-
-import cc.amily49.api.module.warehouse.entity.OperationLog;
-import cc.amily49.api.module.warehouse.repository.OperationLogRepository;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.RequiredArgsConstructor;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageRequest;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.domain.Sort;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/logs")
-@RequiredArgsConstructor
-@Tag(name = "System Logs", description = "Operations for querying system logs")
-public class LogController {
-
- private final OperationLogRepository operationLogRepository;
-
- @Operation(summary = "Get operation logs", description = "Retrieve paginated operation logs")
- @PreAuthorize("hasRole('ADMIN')")
- @GetMapping
- public ResponseEntity> getLogs(
- @RequestParam(defaultValue = "0") int page,
- @RequestParam(defaultValue = "20") int size,
- @RequestParam(defaultValue = "createTime") String sortBy,
- @RequestParam(defaultValue = "desc") String direction) {
-
- Sort sort = Sort.by(Sort.Direction.fromString(direction), sortBy);
- Pageable pageable = PageRequest.of(page, size, sort);
-
- Page logs = operationLogRepository.findAll(pageable);
- return ResponseEntity.ok(logs);
- }
-}
diff --git a/src/main/java/cc/amily49/api/module/warehouse/entity/OperationLog.java b/src/main/java/cc/amily49/api/module/warehouse/entity/OperationLog.java
deleted file mode 100644
index c1563e0..0000000
--- a/src/main/java/cc/amily49/api/module/warehouse/entity/OperationLog.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package cc.amily49.api.module.warehouse.entity;
-
-import jakarta.persistence.*;
-import lombok.Data;
-import org.hibernate.annotations.CreationTimestamp;
-
-import java.time.LocalDateTime;
-
-@Data
-@Entity
-@Table(name = "operation_log")
-public class OperationLog {
-
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
-
- /**
- * Operator username
- */
- @Column(length = 50)
- private String username;
-
- /**
- * Operation description (from annotation)
- */
- @Column(length = 255)
- private String description;
-
- /**
- * Request URL
- */
- @Column(length = 500)
- private String url;
-
- /**
- * HTTP Method (GET, POST, etc.)
- */
- @Column(length = 10)
- private String method;
-
- /**
- * Class and Method name called
- */
- @Column(length = 255)
- private String classMethod;
-
- /**
- * Client IP Address
- */
- @Column(length = 50)
- private String ip;
-
- /**
- * Request Parameters (JSON string, truncated if necessary)
- */
- @Column(columnDefinition = "TEXT")
- private String params;
-
- /**
- * Exception message if failed
- */
- @Column(columnDefinition = "TEXT")
- private String exception;
-
- /**
- * Execution duration in milliseconds
- */
- private Long duration;
-
- /**
- * Operation time
- */
- @Column(updatable = false)
- private LocalDateTime createTime;
-}
diff --git a/src/main/java/cc/amily49/api/module/warehouse/repository/OperationLogRepository.java b/src/main/java/cc/amily49/api/module/warehouse/repository/OperationLogRepository.java
deleted file mode 100644
index 32b0223..0000000
--- a/src/main/java/cc/amily49/api/module/warehouse/repository/OperationLogRepository.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package cc.amily49.api.module.warehouse.repository;
-
-import cc.amily49.api.module.warehouse.entity.OperationLog;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.stereotype.Repository;
-
-@Repository
-public interface OperationLogRepository extends JpaRepository {
-}
diff --git a/src/main/java/cc/amily49/api/module/warehouse/service/LogService.java b/src/main/java/cc/amily49/api/module/warehouse/service/LogService.java
deleted file mode 100644
index f5469f4..0000000
--- a/src/main/java/cc/amily49/api/module/warehouse/service/LogService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package cc.amily49.api.module.warehouse.service;
-
-import cc.amily49.api.module.warehouse.entity.OperationLog;
-import cc.amily49.api.module.warehouse.repository.OperationLogRepository;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Async;
-import org.springframework.stereotype.Service;
-
-@Service
-@RequiredArgsConstructor
-@Slf4j
-public class LogService {
-
- private final OperationLogRepository operationLogRepository;
-
- /**
- * Save log asynchronously
- */
- @Async
- public void saveLog(OperationLog logEntry) {
- try {
- operationLogRepository.save(logEntry);
- } catch (Exception e) {
- log.error("Failed to save operation log", e);
- }
- }
-}