创建 Spring Boot 4 项目目录结构脚本

这份脚本适合在任意目录下直接生成一个新的 Spring Boot 项目骨架,并补齐最基础的 Gradle Kotlin DSL 文件。

脚本

spring_init_4.sh:

#!/bin/bash
 
if [ $# -eq 0 ]; then
  echo "错误: 请提供包名"
  echo "使用方法: $0 com.example.myapp [project-name]"
  exit 1
fi
 
PACKAGE_NAME=$1
if [[ "$PACKAGE_NAME" == *-* ]]; then
  echo "错误: 包名不能包含连字符 -"
  echo "如果项目目录需要使用连字符,请作为第二个参数传入"
  echo "示例: $0 io.github.lookoutldz.arkmusic ark-music"
  exit 1
fi
 
PACKAGE_PATH=$(echo $PACKAGE_NAME | sed 's/\./\//g')
LAST_PART=$(echo $PACKAGE_NAME | awk -F. '{print $NF}')
PROJECT_NAME="${2:-$LAST_PART}"
 
to_pascal_case() {
  echo "$1" | awk -F '[-_. ]+' '{
    for (i = 1; i <= NF; i++) {
      if ($i != "") {
        printf "%s%s", toupper(substr($i, 1, 1)), substr($i, 2)
      }
    }
    printf "\n"
  }'
}
 
MAIN_CLASS_NAME=$(to_pascal_case "$PROJECT_NAME")
 
PROJECT_ROOT="$(pwd)/$PROJECT_NAME"
MAIN_SRC="$PROJECT_ROOT/src/main/kotlin"
MAIN_RESOURCES="$PROJECT_ROOT/src/main/resources"
TEST_SRC="$PROJECT_ROOT/src/test/kotlin"
TEST_RESOURCES="$PROJECT_ROOT/src/test/resources"
CURRENT_DATE=$(date +"%Y/%-m/%-d")
ENV_FILE="$PROJECT_ROOT/.env"
APP_YAML_FILE="$MAIN_RESOURCES/application.yaml"
COMPOSE_FILE="$PROJECT_ROOT/docker-compose.local.yaml"
SETTINGS_FILE="$PROJECT_ROOT/settings.gradle.kts"
BUILD_FILE="$PROJECT_ROOT/build.gradle.kts"
GITIGNORE_FILE="$PROJECT_ROOT/.gitignore"
GITATTR_FILE="$PROJECT_ROOT/.gitattributes"
 
if [ -e "$PROJECT_ROOT" ]; then
  echo "错误: 目标目录已存在: $PROJECT_ROOT"
  exit 1
fi
 
echo "即将创建 Spring Boot 项目目录结构:"
echo "  包名: $PACKAGE_NAME"
echo "  项目名: $PROJECT_NAME"
echo "  目标目录: $PROJECT_ROOT"
read -r -p "确认继续创建? [y/N] " CONFIRM
 
case "$CONFIRM" in
  y|Y|yes|YES)
    ;;
  *)
    echo "已取消创建"
    exit 0
    ;;
esac
 
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/bootstrap"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/component"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/config"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/controller"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/dto"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/entity"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/enumeration"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/exception"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/repository"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/service/impl"
mkdir -p "$MAIN_SRC/$PACKAGE_PATH/util"
 
mkdir -p "$MAIN_RESOURCES/static"
mkdir -p "$MAIN_RESOURCES/templates"
mkdir -p "$MAIN_RESOURCES/db/migration"
mkdir -p "$MAIN_RESOURCES/META-INF"
 
mkdir -p "$TEST_SRC/$PACKAGE_PATH/controller"
mkdir -p "$TEST_SRC/$PACKAGE_PATH/service"
mkdir -p "$TEST_SRC/$PACKAGE_PATH/repository"
mkdir -p "$TEST_SRC/$PACKAGE_PATH/util"
mkdir -p "$TEST_RESOURCES"
 
MAIN_CLASS_FILE="$MAIN_SRC/$PACKAGE_PATH/${MAIN_CLASS_NAME}Application.kt"
TEST_CLASS_FILE="$TEST_SRC/$PACKAGE_PATH/${MAIN_CLASS_NAME}ApplicationTests.kt"
 
cat > "$MAIN_CLASS_FILE" << EOF
package $PACKAGE_NAME
 
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
 
@SpringBootApplication
class ${MAIN_CLASS_NAME}Application
 
fun main(args: Array<String>) {
    runApplication<${MAIN_CLASS_NAME}Application>(*args)
}
 
EOF
 
cat > "$TEST_CLASS_FILE" << EOF
package $PACKAGE_NAME
 
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
 
/**
 *  @author looko
 *  @date $CURRENT_DATE
 *
 */
@SpringBootTest
class ${MAIN_CLASS_NAME}ApplicationTests {
 
    @Test
    fun contextLoads() {}
}
 
EOF
 
cat > "$ENV_FILE" << EOF
############# used by docker compose and spring yml #############
# postgres
PG_IMAGE=nexusserver:8083/postgres:18-alpine
DB_HOST=localhost
DB_NAME=${PROJECT_NAME}
DB_USER=postgres
DB_PASS=postgres
 
EOF
 
cat > "$APP_YAML_FILE" << EOF
server:
  address: 0.0.0.0
  port: 8080
spring:
  application:
    name: "$PROJECT_NAME"
 
  # 本地靠 .env 给 Spring 补变量,生产靠 Compose 给进程注入变量;JAR 最终只认 Spring 的配置键。
  config:
    import: optional:file:./.env[.properties]
 
  # 数据库配置
  datasource:
    url: \${SPRING_DATASOURCE_URL:jdbc:postgresql://\${DB_HOST:127.0.0.1}:\${DB_PORT:5432}/\${DB_NAME:$PROJECT_NAME}?sslmode=disable}
    username: \${SPRING_DATASOURCE_USERNAME:\${DB_USER}}
    password: \${SPRING_DATASOURCE_PASSWORD:\${DB_PASS}}
    driver-class-name: org.postgresql.Driver
 
  # JPA配置
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: false
    open-in-view: true
    properties:
      hibernate:
        format_sql: true
 
EOF
 
cat > "$COMPOSE_FILE" << EOF
services:
  postgres:
    image: \${PG_IMAGE}
    environment:
      TZ: Asia/Shanghai
      POSTGRES_USER: \${DB_USER}
      POSTGRES_PASSWORD: \${DB_PASS}
      POSTGRES_DB: \${DB_NAME}
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C.UTF-8"
    volumes:
      - postgres_data:/var/lib/postgresql
    ports:
      - "5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    logging:
      options:
        max-size: "16m"
        max-file: "4"
 
volumes:
  postgres_data:
 
EOF
 
cat > "$SETTINGS_FILE" << EOF
rootProject.name = "$PROJECT_NAME"
 
EOF
 
cat > "$BUILD_FILE" << EOF
plugins {
    kotlin("jvm") version "2.3.21"
    kotlin("plugin.spring") version "2.3.21"
    id("org.springframework.boot") version "4.1.0"
    id("io.spring.dependency-management") version "1.1.7"
    kotlin("plugin.jpa") version "2.3.21"
}
 
 
group = "$PACKAGE_NAME"
version = "0.0.1-SNAPSHOT"
 
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}
 
repositories {
    maven {
        url = uri("http://nexusserver:8081/repository/maven-public/")
        isAllowInsecureProtocol = true
    }
}
 
dependencies {
    // Spring
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-restclient")
    implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
    implementation("org.springframework.boot:spring-boot-starter-webmvc")
    // Kotlin
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("tools.jackson.module:jackson-module-kotlin")
    // Driver
    runtimeOnly("org.postgresql:postgresql")
    // Test
    testImplementation("org.springframework.boot:spring-boot-starter-actuator-test")
    testImplementation("org.springframework.boot:spring-boot-starter-data-jpa-test")
    testImplementation("org.springframework.boot:spring-boot-starter-restclient-test")
    testImplementation("org.springframework.boot:spring-boot-starter-thymeleaf-test")
    testImplementation("org.springframework.boot:spring-boot-starter-webmvc-test")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
 
kotlin {
    compilerOptions {
        freeCompilerArgs.addAll("-Xjsr305=strict")
    }
}
 
tasks.withType<Test> {
    useJUnitPlatform()
}
 
EOF
 
cat > "$GITIGNORE_FILE" << EOF
.DS_Store
HELP.md
.gradle
.env
.env.*
!.env.sample
application-*.yml
!application-test.yml
build/
temp/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
 
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
 
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
 
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
 
### VS Code ###
.vscode/
 
### Kotlin ###
.kotlin
gradle.properties
 
docker-compose.local.yml
 
EOF
 
cat > "$GITATTR_FILE" << EOF
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
 
EOF
 
echo "SpringBoot项目目录结构已创建:"
echo "包名: $PACKAGE_NAME"
echo "主应用类: ${MAIN_CLASS_NAME}Application.kt"
echo "测试类: ${MAIN_CLASS_NAME}ApplicationTests.kt"
echo "项目名: $PROJECT_NAME"
echo "项目目录: $PROJECT_ROOT"
echo "Gradle 文件: settings.gradle.kts, build.gradle.kts"
echo "Git 文件: .gitignore"
find "$PROJECT_ROOT/src" -type d | sort
 

示例

./spring_init_4.sh io.github.lookoutldz.arkmusic ark-music