第一部分:IntelliJ IDEA 基础与环境配置
1.1 IntelliJ IDEA 概述与版本选择
IntelliJ IDEA 是 JetBrains 公司开发的 Java 集成开发环境,被公认为当前最智能、最高效的 Java IDE 之一。它提供强大的代码分析、智能提示、重构工具和丰富的插件生态系统。
版本选择策略:
- Community Edition(社区版):免费开源,支持 Java、Kotlin、Android 开发
- :商业许可,支持 Web 开发、企业框架、数据库工具等
IntelliJ IDEA 项目配置与 Web 部署指南涵盖了从环境搭建到生产部署的全流程。内容包括 IDE 基础设置、Maven 与 Gradle 项目管理、Web 模块创建、Tomcat 集成、Spring Boot 框架配置、数据库连接池优化、Docker 容器化部署及 CI/CD 流水线构建。此外还涉及多环境管理、应用监控、性能调优及安全最佳实践,旨在帮助开发者构建高效稳定的企业级 Java Web 应用。
IntelliJ IDEA 是 JetBrains 公司开发的 Java 集成开发环境,被公认为当前最智能、最高效的 Java IDE 之一。它提供强大的代码分析、智能提示、重构工具和丰富的插件生态系统。
对于 Web 开发,必须选择 Ultimate Edition,因为它包含:
// 配置示例:idea.properties 关键参数
-Xms2048m // 初始堆内存
-Xmx4096m // 最大堆内存
-XX:ReservedCodeCacheSize=512m // 代码缓存大小
-Dsun.io.useCanonCaches=false // 禁用规范缓存
-Djava.net.preferIPv4Stack=true // 优先使用 IPv4
text
Project (项目) └── Module (模块)
└── Package (包)
└── Class (类)
Ctrl+Shift+A:查找操作Ctrl+N:查找类Ctrl+Shift+N:查找文件Ctrl+Alt+L:格式化代码Ctrl+Alt+O:优化导入Shift+F10:运行当前配置Shift+F9:调试当前配置<!-- 生成的 pom.xml 示例 -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-web-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>
// build.gradle 示例
plugins {
id 'java'
id 'war'
}
group = 'com.example'
version = '1.0-SNAPSHOT'
sourceCompatibility = '11'
repositories {
mavenCentral()
}
dependencies {
// 依赖配置
}
text
my-project/
├── pom.xml (父 pom)
├── my-web-module/
│ ├── pom.xml
│ └── src/
├── my-service-module/
│ ├── pom.xml
│ └── src/
└── my-dao-module/
├── pom.xml
└── src/
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-parent-project</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<modules>
<module>my-web-module</module>
<module>my-service-module</module>
<module>my-dao-module</module>
</modules>
<dependencyManagement>
<dependencies>
<!-- 统一管理依赖版本 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.23</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement>
<!-- 统一管理插件 -->
</pluginManagement>
</build>
</project>
访问方式:文件 → 项目结构 或 Ctrl+Shift+Alt+S
1. 项目设置:
2. 模块设置:
3. 库管理:
4. 构面(Facets):
5. 工件(Artifacts):
访问方式:文件 → 设置 → 构建、执行、部署 → 编译器
// 推荐的编译器配置:
{
"自动构建项目": true,
"编译独立模块": false,
"使用外部构建": false,
"构建进程堆大小": 700,
"共享构建进程 VM 选项": "-Xmx512m",
"添加异常断言": true,
"生成无符号字节码": false,
"预编译检测注解": true
}
访问方式:运行 → 编辑配置 或 Shift+Alt+F10 → 0
配置模板类型:
text
web-module/
├── src/
│ └── main/
│ ├── java/ # Java 源代码
│ ├── resources/ # 资源文件
│ └── webapp/ # Web 资源
│ ├── WEB-INF/
│ │ ├── web.xml # 部署描述符
│ │ └── classes/
│ └── index.jsp # 首页
└── pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd" version="5.0">
<display-name>My Web Application</display-name>
<!-- 上下文参数 -->
<context-param>
<param-name>appName</param-name>
<param-value>MyApplication</param-value>
</context-param>
<!-- Servlet 配置 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- Servlet 映射 -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 欢迎文件列表 -->
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
// Spring Boot 配置类示例
@Configuration
public class ServletConfig {
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(createSslConnector());
tomcat.setPort(8443);
tomcat.setContextPath("/api");
return tomcat;
}
private Connector createSslConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("https");
connector.setSecure(true);
connector.setPort(8443);
return connector;
}
}
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("classpath:/static/", "classpath:/public/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
text
src/main/resources/
├── static/ # 静态资源
│ ├── css/
│ ├── js/
│ ├── images/
│ └── fonts/
├── templates/ # 模板文件
│ ├── thymeleaf/
│ ├── freemarker/
│ └── jsp/
└── application.properties # 配置文件
1. 下载和配置 Tomcat:
2. 在 IDEA 中集成 Tomcat:
3. 部署配置:
// Tomcat 部署配置示例
{
"服务器配置": {
"名称": "Local Tomcat",
"类型": "Tomcat 9.0.x",
"HTTP 端口": 8080,
"JMX 端口": 1099,
"启动前": "构建工件",
"更新操作": "重新部署",
"帧延迟": 3000
},
"部署": {
"工件": "my-web-app:war exploded",
"上下文路径": "/myapp",
"JRE": "11",
"VM 选项": "-Xms512m -Xmx1024m -Dspring.profiles.active=dev"
}
}
server.xml 配置示例片段:
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="200" minSpareThreads="10" enableLookups="false" acceptCount="100" disableUploadTimeout="true" compression="on" compressionMinSize="1024" compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/json"/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context path="/myapp" docBase="C:\projects\myapp" reloadable="true" crossContext="true"/>
</Host>
</Engine>
</Service>
</Server>
1. IDEA 设置:
2. Spring Boot DevTools 配置:
<!-- pom.xml 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
# application-dev.properties
spring.devtools.restart.enabled=true
spring.devtools.livereload.enabled=true
spring.devtools.restart.poll-interval=1000
spring.devtools.restart.quiet-period=400
spring.devtools.restart.exclude=static/**,public/**
3. JRebel 配置(商业工具):
text
src/main/resources/
├── application.properties # 主配置
├── application-dev.properties # 开发环境
├── application-test.properties # 测试环境
├── application-prod.properties # 生产环境
└── config/
├── logback-dev.xml # 开发日志配置
└── logback-prod.xml # 生产日志配置
// Spring Boot 多环境配置
@Configuration
@PropertySource({
"classpath:application.properties",
"classpath:application-${spring.profiles.active}.properties"
})
public class EnvironmentConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
// 开发环境数据源
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
// 生产环境数据源
}
}
// 开发环境运行配置
{
"类型": "Spring Boot",
"名称": "MyApp Dev",
"主类": "com.example.MyApplication",
"VM 选项": "-Dspring.profiles.active=dev -Xms512m -Xmx1024m",
"程序实参": "--server.port=8081",
"环境变量": "SPRING_PROFILES_ACTIVE=dev;DB_HOST=localhost"
}
# 服务器配置
server.port=8080
server.servlet.context-path=/api
server.servlet.session.timeout=30m
server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/json
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.maximum-pool-size=20
# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
# 日志配置
logging.level.root=INFO
logging.level.com.example=DEBUG
logging.file.name=logs/myapp.log
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# 静态资源
spring.resources.static-locations=classpath:/static/,classpath:/public/,file:./uploads/
spring.resources.cache.period=3600
spring.mvc.static-path-pattern=/static/**
# 上传文件配置
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
spring.servlet.multipart.enabled=true
@Configuration
@EnableWebMvc
@ComponentScan("com.example")
@EnableTransactionManagement
@EnableJpaRepositories("com.example.repository")
@EntityScan("com.example.entity")
@EnableCaching
@EnableScheduling
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/**/static/**", "/error");
}
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.US);
return slr;
}
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(3600);
return messageSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("products", "users");
}
}
@Configuration
public class DatabaseConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.example.entity")
.persistenceUnit("main")
.properties(jpaProperties())
.build();
}
@Bean
public PlatformTransactionManager transactionManager(
EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
private Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<>();
props.put("hibernate.hbm2ddl.auto", "validate");
props.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
props.put("hibernate.show_sql", true);
props.put("hibernate.format_sql", true);
props.put("hibernate.use_sql_comments", true);
return props;
}
@Bean
public Flyway flyway(DataSource dataSource) {
Flyway flyway = Flyway.configure()
.dataSource(dataSource)
.locations("classpath:db/migration")
.baselineOnMigrate(true)
.outOfOrder(true)
.validateOnMigrate(true)
.load();
flyway.migrate();
return flyway;
}
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/", "/favicon.ico", /**/*.png", /**/*.gif", /**/*.svg", /**/*.jpg", /**/*.html", /**/*.css", /**/*.js")
.permitAll()
.antMatchers("/api/auth/**")
.permitAll()
.antMatchers("/api/user/checkUsernameAvailability", "/api/user/checkEmailAvailability")
.permitAll()
.antMatchers(HttpMethod.GET, "/api/polls/**", "/api/users/**")
.permitAll()
.anyRequest()
.authenticated();
// 添加 JWT 过滤器
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// 添加 CORS 过滤器
http.addFilterBefore(corsFilter(), JwtAuthenticationFilter.class);
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://localhost:3000");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-enterprise-app</artifactId>
<version>1.0.0-RELEASE</version>
<packaging>war</packaging>
<name>My Enterprise Application</name>
<description>A comprehensive enterprise web application</description>
<url>http://www.example.com</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/>
</parent>
<organization>
<name>Example Inc.</name>
<url>http://www.example.com</url>
</organization>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<developers>
<developer>
<id>dev1</id>
<name>John Doe</name>
<email>[email protected]</email>
<roles>
<role>architect</role>
<role>developer</role>
</roles>
</developer>
</developers>
<properties>
<java.version>11</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>${project.reporting.outputEncoding}</project.reporting.outputEncoding>
<!-- 依赖版本 -->
<spring.version>5.3.23</spring.version>
<hibernate.version>5.6.11.Final</hibernate.version>
<jackson.version>2.13.4</jackson.version>
<logback.version>1.2.11</logback.version>
<junit.version>5.9.1</junit.version>
<!-- 插件版本 -->
<maven-compiler-plugin.version>3.10.1</maven-compiler-plugin.version>
<maven-war-plugin.version>3.3.2</maven-war-plugin.version>
<maven-surefire-plugin.version>3.0.0-M7</maven-surefire-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring BOM -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>${spring.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Jackson BOM -->
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- Spring Boot Starters -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<!-- Dev Tools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<excludes>
<exclude>**/*.properties</exclude>
<exclude>**/*.xml</exclude>
<exclude>**/*.yml</exclude>
</excludes>
</resource>
</resources>
<plugins>
<!-- Spring Boot Maven Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<mainClass>com.example.Application</mainClass>
<layout>WAR</layout>
<classifier>exec</classifier>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- War Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>${maven-war-plugin.version}</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<warSourceDirectory>src/main/webapp</warSourceDirectory>
<warName>${project.artifactId}</warName>
<outputDirectory>${project.build.directory}</outputDirectory>
<webResources>
<resource>
<directory>src/main/webapp</directory>
</resource>
</webResources>
</configuration>
</plugin>
<!-- Surefire Plugin for Testing -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<skipTests>false</skipTests>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/*IntegrationTest.java</exclude>
<exclude>**/*IT.java</exclude>
</excludes>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemPropertyVariables>
</configuration>
</plugin>
<!-- JaCoCo for Code Coverage -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
<execution>
<id>check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- Dependency Plugin for Analysis -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>analyze</id>
<goals>
<goal>analyze</goal>
</goals>
<configuration>
<failOnWarning>true</failOnWarning>
</configuration>
</execution>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
</executions>
</plugin>
<!-- Clean Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<filesets>
<fileset>
<directory>${project.build.directory}</directory>
<includes>
<include>**/*</include>
</includes>
<followSymlinks>false</followSymlinks>
</fileset>
<fileset>
<directory>${basedir}/logs</directory>
</fileset>
<fileset>
<directory>${basedir}/temp</directory>
</fileset>
</filesets>
</configuration>
</plugin>
<!-- Resources Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<encoding>${project.build.sourceEncoding}</encoding>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>pdf</nonFilteredFileExtension>
<nonFilteredFileExtension>jar</nonFilteredFileExtension>
<nonFilteredFileExtension>zip</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
<!-- Enforcer Plugin for Rules -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>enforce-versions</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[11,17)</version>
</requireJavaVersion>
<requireMavenVersion>
<version>[3.6,)</version>
</requireMavenVersion>
<banDuplicatePomDependencyVersions/>
<dependencyConvergence/>
<requireReleaseDeps>
<message>No Snapshots Allowed!</message>
</requireReleaseDeps>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- Versions Plugin -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.13.0</version>
<configuration>
<generateBackupPoms>false</generateBackupPoms>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>development</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<spring.profiles.active>dev</spring.profiles.active>
<build.profile.id>dev</build.profile.id>
</properties>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</profile>
<profile>
<id>testing</id>
<properties>
<spring.profiles.active>test</spring.profiles.active>
<build.profile.id>test</build.profile.id>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>false</skipTests>
<includes>
<include>**/*Test.java</include>
<include>**/*Tests.java</include>
<include>**/*IT.java</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>production</id>
<properties>
<spring.profiles.active>prod</spring.profiles.active>
<build.profile.id>prod</build.profile.id>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
<jvmArguments>
-Xms512m -Xmx2048m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:+UseStringDeduplication
</jvmArguments>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>docker</id>
<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.4.13</version>
<configuration>
<repository>${project.artifactId}</repository>
<tag>${project.version}</tag>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
<executions>
<execution>
<id>default</id>
<phase>install</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<reporting>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.4.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<show>private</show>
<nohelp>true</nohelp>
<additionalOptions>
<additionalOption>-Xdoclint:none</additionalOption>
</additionalOptions>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<reportSets>
<reportSet>
<reports>
<report>report</report>
</reports>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Central Plugin Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '2.7.4'
id 'io.spring.dependency-management' version '1.0.14.RELEASE'
id 'jacoco'
id 'com.palantir.docker' version '0.34.0'
}
group = 'com.example'
version = '1.0.0-RELEASE'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
developmentOnly runtimeClasspath {
extendsFrom developmentOnly
}
}
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
ext {
set('springCloudVersion', "2021.0.4")
set('testcontainersVersion', "1.17.4")
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "org.testcontainers:testcontainers-bom:${testcontainersVersion}"
}
}
dependencies {
// Spring Boot Starters
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
// Spring Cloud
implementation 'org.springframework.cloud:spring-cloud-starter-config'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.cloud:spring-cloud-starter-circuitbreaker-resilience4j'
// Database
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.h2database:h2'
// Security
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
// Utilities
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'com.google.guava:guava:31.1-jre'
implementation 'org.modelmapper:modelmapper:3.1.0'
// Cache
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.2'
// Monitoring
implementation 'io.micrometer:micrometer-registry-prometheus'
// Documentation
implementation 'org.springdoc:springdoc-openapi-ui:1.6.12'
// Development
developmentOnly 'org.springframework.boot:spring-boot-devtools'
// Testing
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.testcontainers:mysql'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'com.tngtech.archunit:archunit-junit5:1.0.0'
testImplementation 'org.awaitility:awaitility:4.2.0'
}
tasks.named('test') {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
exceptionFormat "full"
}
systemProperty 'spring.profiles.active', 'test'
finalizedBy jacocoTestReport
}
jacoco {
toolVersion = "0.8.8"
}
jacocoTestReport {
dependsOn test
reports {
xml.required = true
html.required = true
csv.required = false
}
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
'**/config/**',
'**/entity/**',
'**/dto/**',
'**/*Application*',
'**/exception/**'
])
}))
}
}
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.80
}
}
rule {
element = 'CLASS'
includes = ['com.example.service.*']
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.80
}
}
}
}
check.dependsOn jacocoTestCoverageVerification
bootJar {
archiveClassifier = 'boot'
launchScript()
}
war {
archiveClassifier = 'war'
enabled = true
}
springBoot {
buildInfo()
}
docker {
name "${project.name}:${project.version}"
files bootJar.archiveFile
buildArgs(['JAR_FILE': bootJar.archiveFileName.get()])
}
task copyDependencies(type: Copy) {
from configurations.runtimeClasspath into "${buildDir}/libs/dependencies"
}
build.dependsOn copyDependencies
// 自定义任务
task generateVersionProperties {
doLast {
def propertiesFile = file("$buildDir/resources/main/version.properties")
propertiesFile.parentFile.mkdirs()
def properties = new Properties()
properties.setProperty('version', project.version.toString())
properties.setProperty('build.time', new Date().format("yyyy-MM-dd HH:mm:ss"))
properties.setProperty('java.version', System.getProperty('java.version'))
properties.store(propertiesFile.newWriter(), null)
}
}
processResources.dependsOn generateVersionProperties
// 多环境配置
bootRun {
systemProperties = System.properties
}
// 性能优化
tasks.withType(JavaCompile) {
options.compilerArgs += ['-parameters', '-Xlint:unchecked', '-Xlint:deprecation']
options.encoding = 'UTF-8'
options.incremental = true
}
// 资源过滤
processResources {
filesMatching('**/application*.yml') {
filter org.apache.tools.ant.filters.ReplaceTokens, tokens: [
'app.version': project.version
]
}
}
# 多阶段构建 Dockerfile
# 第一阶段:构建阶段
FROM maven:3.8.6-eclipse-temurin-11 AS build
WORKDIR /app
# 复制 pom.xml 和下载依赖
COPY pom.xml .
RUN mvn dependency:go-offline -B
# 复制源代码并构建
COPY src ./src
RUN mvn clean package -DskipTests
# 第二阶段:运行阶段
FROM eclipse-temurin:11-jre-alpine
# 添加非 root 用户
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
WORKDIR /app
# 复制构建产物
COPY --from=build /app/target/*.war app.war
# 配置 JVM 参数
ENV JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+ParallelRefProcEnabled -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp -XX:+ExitOnOutOfMemoryError"
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
# 暴露端口
EXPOSE 8080
# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app/app.war"]
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: mysql_db
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./config/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- app-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
redis:
image: redis:7-alpine
container_name: redis_cache
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes
networks:
- app-network
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
app:
build:
context: .
dockerfile: Dockerfile
container_name: web_app
environment:
SPRING_PROFILES_ACTIVE: docker
DB_HOST: mysql
DB_PORT: 3306
REDIS_HOST: redis
REDIS_PORT: 6379
ports:
- "8080:8080"
- "5005:5005" # 远程调试端口
volumes:
- ./logs:/app/logs
- ./uploads:/app/uploads
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
deploy:
resources:
limits:
cpus: '1'
memory: 1024M
reservations:
cpus: '0.5'
memory: 512M
nginx:
image: nginx:1.23-alpine
container_name: web_nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./ssl:/etc/nginx/ssl:ro
- ./static:/usr/share/nginx/static:ro
depends_on:
- app
networks:
- app-network
prometheus:
image: prom/prometheus:latest
container_name: prometheus_monitor
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.console.libraries=/etc/prometheus/console_libraries'
- '--web.console.templates=/etc/prometheus/consoles'
- '--storage.tsdb.retention.time=200h'
- '--web.enable-lifecycle'
networks:
- app-network
grafana:
image: grafana/grafana:latest
container_name: grafana_dashboard
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
environment:
- GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
depends_on:
- prometheus
networks:
- app-network
networks:
app-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
volumes:
mysql_data:
redis_data:
prometheus_data:
grafana_data:
name: Java CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
release:
types: [ published ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testdb
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
redis:
image: redis:7-alpine
ports:
- 6379:6379
options: >-
--health-cmd="redis-cli ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- name: Build and Test
run: mvn -B clean test
env:
SPRING_PROFILES_ACTIVE: test
DB_HOST: localhost
DB_PORT: 3306
REDIS_HOST: localhost
REDIS_PORT: 6379
- name: Upload Test Results
uses: actions/upload-artifact@v3
if: always()
with:
name: test-results
path: target/surefire-reports/
- name: Upload Coverage Report
uses: codecov/codecov-action@v3
with:
file: ./target/site/jacoco/jacoco.xml
fail_ci_if_error: true
build:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- name: Build with Maven
run: mvn -B clean package -DskipTests
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Generate Deployment Package
run: |
mkdir -p deployment
cp target/*.war deployment/
cp Dockerfile deployment/
cp docker-compose.yml deployment/
cp -r config deployment/
tar -czf deployment-package.tar.gz deployment/
- name: Upload Deployment Package
uses: actions/upload-artifact@v3
with:
name: deployment-package
path: deployment-package.tar.gz
deploy:
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment:
name: production
url: https://your-domain.com
steps:
- name: Download Deployment Package
uses: actions/download-artifact@v3
with:
name: deployment-package
- name: Setup SSH
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Deploy to Server
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
tar -xzf deployment-package.tar.gz
scp -o StrictHostKeyChecking=no -r deployment/* ${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/
ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_HOST} "
cd ${DEPLOY_PATH} && docker-compose down && docker-compose pull && docker-compose up -d --build && docker system prune -af
"
- name: Verify Deployment
run: |
sleep 30
curl --retry 5 --retry-delay 10 --max-time 30 \n https://your-domain.com/actuator/health
- name: Notify Success
uses: 8398a7/action-slack@v3
with:
channel: '#deployments'
status: ${{ job.status }}
author_name: GitHub Actions
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs from '@aws-cdk/aws-ecs';
import * as ecs_patterns from '@aws-cdk/aws-ecs-patterns';
import * as rds from '@aws-cdk/aws-rds';
import * as elasticache from '@aws-cdk/aws-elasticache';
import * as secretsmanager from '@aws-cdk/aws-secretsmanager';
export class WebAppStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// 创建 VPC
const vpc = new ec2.Vpc(this, 'WebAppVPC', {
maxAzs: 2,
natGateways: 1,
subnetConfiguration: [
{ cidrMask: 24, name: 'Public', subnetType: ec2.SubnetType.PUBLIC },
{ cidrMask: 24, name: 'Private', subnetType: ec2.SubnetType.PRIVATE_WITH_NAT },
{ cidrMask: 28, name: 'Isolated', subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
]
});
// 创建 RDS MySQL 实例
const databaseSecret = new secretsmanager.Secret(this, 'DatabaseSecret', {
secretName: 'webapp-db-secret',
generateSecretString: {
secretStringTemplate: JSON.stringify({ username: 'admin' }),
generateStringKey: 'password',
excludePunctuation: true,
passwordLength: 16,
},
});
const database = new rds.DatabaseInstance(this, 'Database', {
engine: rds.DatabaseInstanceEngine.mysql({ version: rds.MysqlEngineVersion.VER_8_0_28 }),
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
credentials: rds.Credentials.fromSecret(databaseSecret),
storageEncrypted: true,
backupRetention: cdk.Duration.days(7),
deletionProtection: false,
databaseName: 'webappdb',
});
// 创建 ElastiCache Redis 集群
const redisSubnetGroup = new elasticache.CfnSubnetGroup(this, 'RedisSubnetGroup', {
description: 'Subnet group for Redis',
subnetIds: vpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_ISOLATED }).subnetIds,
});
const redis = new elasticache.CfnCacheCluster(this, 'RedisCache', {
cacheNodeType: 'cache.t3.micro',
engine: 'redis',
numCacheNodes: 1,
clusterName: 'webapp-redis',
vpcSecurityGroupIds: [/* security group IDs */],
cacheSubnetGroupName: redisSubnetGroup.ref,
});
// 创建 ECS 集群
const cluster = new ecs.Cluster(this, 'WebAppCluster', {
vpc,
containerInsights: true,
});
// 创建 Fargate 服务
const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'WebAppService', {
cluster,
memoryLimitMiB: 1024,
cpu: 512,
desiredCount: 2,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry('ghcr.io/your-org/your-app:latest'),
containerPort: 8080,
environment: {
SPRING_PROFILES_ACTIVE: 'prod',
DB_HOST: database.dbInstanceEndpointAddress,
DB_PORT: database.dbInstanceEndpointPort,
REDIS_HOST: redis.attrRedisEndpointAddress,
REDIS_PORT: redis.attrRedisEndpointPort,
},
secrets: {
DB_USERNAME: ecs.Secret.fromSecretsManager(databaseSecret, 'username'),
DB_PASSWORD: ecs.Secret.fromSecretsManager(databaseSecret, 'password'),
},
},
publicLoadBalancer: true,
});
// 配置自动伸缩
const scaling = fargateService.service.autoScaleTaskCount({
minCapacity: 2,
maxCapacity: 10,
});
scaling.scaleOnCpuUtilization('CpuScaling', {
targetUtilizationPercent: 70,
scaleInCooldown: cdk.Duration.seconds(60),
scaleOutCooldown: cdk.Duration.seconds(60),
});
scaling.scaleOnMemoryUtilization('MemoryScaling', {
targetUtilizationPercent: 70,
scaleInCooldown: cdk.Duration.seconds(60),
scaleOutCooldown: cdk.Duration.seconds(60),
});
// 允许 ECS 访问数据库
database.connections.allowFrom(fargateService.service, ec2.Port.tcp(3306));
// 输出负载均衡器 URL
new cdk.CfnOutput(this, 'LoadBalancerDNS', {
value: fargateService.loadBalancer.loadBalancerDnsName,
});
}
}
# Actuator 配置
management.endpoints.web.exposure.include=health,info,metrics,env,beans,loggers,prometheus
management.endpoints.web.exposure.exclude=threaddump,heapdump
management.endpoint.health.show-details=always
management.endpoint.health.probes.enabled=true
management.health.db.enabled=true
management.health.redis.enabled=true
management.health.diskspace.enabled=true
management.metrics.export.prometheus.enabled=true
management.metrics.distribution.percentiles-histogram.http.server.requests=true
management.metrics.enable.jvm=true
management.metrics.enable.logback=true
management.metrics.enable.process=true
management.metrics.enable.system=true
# 自定义健康指示器
management.health.custom.enabled=true
# 日志级别管理
management.endpoint.loggers.enabled=true
@Component
public class CustomHealthIndicator implements HealthIndicator {
private final DatabaseService databaseService;
private final CacheService cacheService;
private final ExternalService externalService;
public CustomHealthIndicator(DatabaseService databaseService, CacheService cacheService, ExternalService externalService) {
this.databaseService = databaseService;
this.cacheService = cacheService;
this.externalService = externalService;
}
@Override
public Health health() {
Health.Builder builder = Health.up();
// 检查数据库连接
try {
databaseService.checkConnection();
builder.withDetail("database", "connected");
} catch (Exception e) {
builder.down()
.withDetail("database", "disconnected")
.withDetail("databaseError", e.getMessage());
}
// 检查缓存连接
try {
cacheService.ping();
builder.withDetail("cache", "connected");
} catch (Exception e) {
builder.down()
.withDetail("cache", "disconnected")
.withDetail("cacheError", e.getMessage());
}
// 检查外部服务
try {
externalService.checkStatus();
builder.withDetail("externalService", "available");
} catch (Exception e) {
builder.withDetail("externalService", "unavailable")
.withDetail("externalServiceError", e.getMessage());
}
// 检查磁盘空间
File file = new File(".");
long freeSpace = file.getFreeSpace();
long totalSpace = file.getTotalSpace();
double freePercentage = (double) freeSpace / totalSpace * 100;
builder.withDetail("disk.free", formatBytes(freeSpace))
.withDetail("disk.total", formatBytes(totalSpace))
.withDetail("disk.freePercentage", String.format("%.2f%%", freePercentage));
if (freePercentage < 10) {
builder.status(Status.DOWN).withDetail("disk", "critical");
} else if (freePercentage < 20) {
builder.status(Status.DOWN).withDetail("disk", "warning");
}
return builder.build();
}
private String formatBytes(long bytes) {
if (bytes < 1024) return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(1024));
String pre = "KMGTPE".charAt(exp - 1) + "i";
return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
}
}
@Configuration
@EnableMetrics
public class MetricsConfig {
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("application", "webapp", "environment", "production");
}
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
@Bean
public CountedAspect countedAspect(MeterRegistry registry) {
return new CountedAspect(registry);
}
@Bean
public MeterBinder cacheMetrics() {
return new MeterBinder() {
@Override
public void bindTo(MeterRegistry registry) {
Gauge.builder("cache.size", CacheManager.getCache("users")::getSize)
.description("The number of entries in the cache")
.tags("cache", "users")
.register(registry);
}
};
}
}
# 生产环境 JVM 参数
-Xms512m # 初始堆大小
-Xmx2048m # 最大堆大小
-XX:MaxMetaspaceSize=512m # 最大元空间大小
-XX:+UseG1GC # 使用 G1 垃圾回收器
-XX:MaxGCPauseMillis=200 # 目标最大 GC 暂停时间
-XX:ParallelGCThreads=4 # 并行 GC 线程数
-XX:ConcGCThreads=2 # 并发 GC 线程数
-XX:+UseStringDeduplication # 字符串去重
-XX:+HeapDumpOnOutOfMemoryError # OOM 时生成堆转储
-XX:HeapDumpPath=/tmp/heapdump.hprof # 堆转储路径
-XX:+PrintGCDetails # 打印 GC 详情
-XX:+PrintGCDateStamps
-Xloggc:/tmp/gc.log # GC 日志文件
-XX:+UseCompressedOops # 使用压缩普通对象指针
-XX:+UseCompressedClassPointers # 使用压缩类指针
-XX:+AlwaysPreTouch # 启动时预接触内存页
-Djava.security.egd=file:/dev/./urandom # 随机数生成优化
-Dsun.net.inetaddr.ttl=60 # DNS 缓存 TTL
@Configuration
public class WebServerConfig {
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(connector -> {
// 优化连接器配置
connector.setProperty("maxThreads", "200");
connector.setProperty("minSpareThreads", "20");
connector.setProperty("maxConnections", "10000");
connector.setProperty("acceptCount", "100");
connector.setProperty("connectionTimeout", "20000");
connector.setProperty("maxKeepAliveRequests", "100");
connector.setProperty("keepAliveTimeout", "30000");
connector.setProperty("compression", "on");
connector.setProperty("compressionMinSize", "2048");
connector.setProperty("compressableMimeType", "text/html,text/xml,text/plain,text/css,text/javascript,application/json");
connector.setProperty("useBodyEncodingForURI", "true");
connector.setProperty("URIEncoding", "UTF-8");
});
return factory;
}
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CorsFilter());
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
@Bean
public FilterRegistrationBean<GzipFilter> gzipFilter() {
FilterRegistrationBean<GzipFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new GzipFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(Ordered.LOWEST_PRECEDENCE);
return registrationBean;
}
}
# HikariCP 连接池配置
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.pool-name=WebAppPool
spring.datasource.hikari.auto-commit=false
spring.datasource.hikari.leak-detection-threshold=60000
spring.datasource.hikari.initialization-fail-timeout=1
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
<property name="LOG_PATH" value="./logs"/>
<property name="LOG_ARCHIVE" value="${LOG_PATH}/archive"/>
<property name="APP_NAME" value="webapp"/>
<!-- 控制台输出 -->
<appender name="CONSOLE">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<filter>
<level>INFO</level>
</filter>
</appender>
<!-- 文件输出 -->
<appender name="FILE">
<file>${LOG_PATH}/${APP_NAME}.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy>
<fileNamePattern>${LOG_ARCHIVE}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy>
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 错误日志单独输出 -->
<appender name="ERROR_FILE">
<file>${LOG_PATH}/${APP_NAME}-error.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<filter>
<level>ERROR</level>
</filter>
<rollingPolicy>
<fileNamePattern>${LOG_ARCHIVE}/${APP_NAME}-error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>90</maxHistory>
</rollingPolicy>
</appender>
<!-- 访问日志 -->
<appender name="ACCESS_FILE">
<file>${LOG_PATH}/access.log</file>
<encoder>
<pattern>%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy>
<fileNamePattern>${LOG_ARCHIVE}/access.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!-- 异步日志 -->
<appender name="ASYNC_FILE">
<appender-ref ref="FILE"/>
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>true</includeCallerData>
<neverBlock>true</neverBlock>
</appender>
<!-- 日志级别配置 -->
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="ASYNC_FILE"/>
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ERROR_FILE"/>
</logger>
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate" level="WARN"/>
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.type.descriptor.sql" level="TRACE"/>
<logger name="com.zaxxer.hikari" level="INFO"/>
<logger name="org.apache.http" level="INFO"/>
<!-- 访问日志 -->
<logger name="ACCESS_LOG" level="INFO" additivity="false">
<appender-ref ref="ACCESS_FILE"/>
</logger>
<!-- 根日志 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</configuration>
名称:Remote Debug
主机:localhost
端口:5005
命令行实参:-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
# 启动应用时添加 JVM 参数
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 \n -jar your-application.jar
# Dockerfile 中添加调试支持
ENV JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
EXPOSE 5005
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
可能原因:
解决方案:
@Configuration
@EnableJpaRepositories(
basePackages = "com.example.repository",
considerNestedRepositories = true
)
@EntityScan(basePackages = "com.example.entity")
@ComponentScan(
basePackages = "com.example",
excludeFilters = @ComponentScan.Filter(
type = FilterType.REGEX,
pattern = "com.example.config.*Test.*"
)
)
public class FastStartupConfig {
@Bean
public CommandLineRunner cacheWarmup(CacheService cacheService) {
return args -> {
// 异步预热缓存
CompletableFuture.runAsync(cacheService::warmupCache);
};
}
}
检测工具:
常见内存泄漏场景:
预防措施:
@Component
public class MemoryLeakPrevention {
@PreDestroy
public void cleanup() {
// 清理静态资源
CacheManager.clearAll();
ThreadLocal.remove();
}
@Bean
public ServletListenerRegistrationBean<HttpSessionListener> sessionListener() {
return new ServletListenerRegistrationBean<>(new HttpSessionListener() {
@Override
public void sessionDestroyed(HttpSessionEvent se) {
// 清理会话相关资源
HttpSession session = se.getSession();
session.removeAttribute("largeObject");
}
});
}
}
检测方法:
-- 监控数据库连接
SHOW PROCESSLIST;
SHOW STATUS LIKE 'Threads_connected';
预防措施:
@Configuration
public class ConnectionLeakDetection {
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
// 启用泄漏检测
dataSource.setLeakDetectionThreshold(60000);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public ConnectionValidator connectionValidator() {
return new ConnectionValidator();
}
}
@Component
class ConnectionValidator {
@Scheduled(fixedDelay = 300000) // 每 5 分钟执行一次
public void validateConnections() {
// 验证和清理无效连接
}
}
text
com.example.application
├── Application.java # 主启动类
├── config/ # 配置类
│ ├── WebConfig.java
│ ├── SecurityConfig.java
│ ├── DatabaseConfig.java
│ └── CacheConfig.java
├── controller/ # 控制器层
│ ├── api/ # REST API
│ │ ├── v1/ # API 版本 v1
│ │ └── v2/ # API 版本 v2
│ └── web/ # Web 控制器
├── service/ # 服务层
│ ├── impl/ # 服务实现
│ ├── dto/ # 数据传输对象
│ └── mapper/ # 对象映射器
├── repository/ # 数据访问层
│ ├── entity/ # 实体类
│ ├── dao/ # 数据访问对象
│ └── jpa/ # JPA 仓库
├── domain/ # 领域层
│ ├── model/ # 领域模型
│ ├── event/ # 领域事件
│ └── service/ # 领域服务
├── infrastructure/ # 基础设施层
│ ├── cache/ # 缓存
│ ├── message/ # 消息队列
│ └── external/ # 外部服务集成
├── common/ # 公共模块
│ ├── exception/ # 异常处理
│ ├── constant/ # 常量定义
│ ├── util/ # 工具类
│ └── annotation/ # 自定义注解
└── aspect/ # 切面
├── log/ # 日志切面
├── metrics/ # 指标切面
└── transaction/ # 事务切面
<!-- checkstyle 配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<configLocation>checkstyle.xml</configLocation>
<encoding>UTF-8</encoding>
<consoleOutput>true</consoleOutput>
<failsOnError>true</failsOnError>
<linkXRef>false</linkXRef>
</configuration>
<executions>
<execution>
<id>validate</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- spotbugs 配置 -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.7.2.1</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<failOnError>true</failOnError>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- PMD 配置 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-pmd-plugin</artifactId>
<version>3.19.0</version>
<configuration>
<rulesets>
<ruleset>rulesets/java/quickstart.xml</ruleset>
</rulesets>
<failOnViolation>true</failOnViolation>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
// 分层测试策略
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
@Tag("integration")
class ApplicationIntegrationTests {
@Test
@DisplayName("完整的应用启动测试")
void contextLoads() {
// 验证 Spring 上下文加载
}
@Test
@DisplayName("REST API 集成测试")
void testRestApi() {
// 完整的 API 测试
}
}
@WebMvcTest
@AutoConfigureMockMvc
class ControllerTests {
@Test
@DisplayName("控制器单元测试")
void testController() {
// 控制器层测试
}
}
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class RepositoryTests {
@Test
@DisplayName("仓库层测试")
void testRepository() {
// 数据访问层测试
}
}
@ExtendWith(MockitoExtension.class)
class ServiceTests {
@Test
@DisplayName("服务层单元测试")
void testService() {
// 服务层测试
}
}
mvn org.owasp:dependency-check-maven:check
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // 使用高强度加密
}
@Configuration
public class HttpsConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.requiresChannel()
.requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
.requiresSecure();
return http.build();
}
}
@Bean
public SecurityFilterChain securityHeaders(HttpSecurity http) throws Exception {
http.headers()
.contentSecurityPolicy("default-src 'self'")
.and()
.xssProtection()
.and()
.frameOptions().deny()
.and()
.httpStrictTransportSecurity()
.includeSubDomains(true)
.maxAgeInSeconds(31536000);
return http.build();
}
IntelliJ IDEA 作为一款强大的 Java 开发工具,配合合理的项目配置和部署策略,可以显著提高 Web 应用的开发效率和运行稳定性。本指南涵盖了从项目创建到生产部署的全过程,包括:
通过遵循这些最佳实践,您可以构建出高性能、高可用、易维护的企业级 Web 应用。记住,配置管理是一个持续优化的过程,需要根据具体业务需求和运行环境不断调整和完善。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online