构建与部署 #

一、构建工具配置 #

1.1 Maven配置 #

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>embedded-app</artifactId>
    <version>1.0.0</version>
    <packaging>jar</packaging>
    
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <pi4j.version>2.3.0</pi4j.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>com.pi4j</groupId>
            <artifactId>pi4j-core</artifactId>
            <version>${pi4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.pi4j</groupId>
            <artifactId>pi4j-plugin-raspberrypi</artifactId>
            <version>${pi4j.version}</version>
        </dependency>
        <dependency>
            <groupId>com.pi4j</groupId>
            <artifactId>pi4j-plugin-pigpio</artifactId>
            <version>${pi4j.version}</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.example.Main</mainClass>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib/</classpathPrefix>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.6.1</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.5.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>com.example.Main</mainClass>
                                </transformer>
                            </transformers>
                            <finalName>${project.artifactId}-all</finalName>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

1.2 Gradle配置 #

groovy
plugins {
    id 'java'
    id 'application'
    id 'com.github.johnrengelman.shadow' version '8.1.1'
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

application {
    mainClass = 'com.example.Main'
}

repositories {
    mavenCentral()
}

def pi4jVersion = '2.3.0'

dependencies {
    implementation "com.pi4j:pi4j-core:${pi4jVersion}"
    implementation "com.pi4j:pi4j-plugin-raspberrypi:${pi4jVersion}"
    implementation "com.pi4j:pi4j-plugin-pigpio:${pi4jVersion}"
}

jar {
    manifest {
        attributes 'Main-Class': 'com.example.Main'
    }
}

shadowJar {
    archiveBaseName.set('embedded-app')
    archiveClassifier.set('all')
}

task deploy(type: Exec) {
    dependsOn 'shadowJar'
    
    def host = project.findProperty('deploy.host') ?: 'raspberry-pi'
    def user = project.findProperty('deploy.user') ?: 'pi'
    def dest = project.findProperty('deploy.dest') ?: '/home/pi/app'
    
    commandLine 'rsync', '-avz',
        "${buildDir}/libs/embedded-app-all.jar",
        "${user}@${host}:${dest}/"
}

二、部署脚本 #

2.1 部署脚本 #

bash
#!/bin/bash
set -e

APP_NAME="embedded-app"
JAR_FILE="target/embedded-app-all.jar"
REMOTE_HOST="raspberry-pi"
REMOTE_USER="pi"
REMOTE_DIR="/home/pi/${APP_NAME}"
SERVICE_NAME="${APP_NAME}.service"

echo "=== 部署 ${APP_NAME} ==="

echo "1. 构建项目..."
mvn clean package -DskipTests

echo "2. 创建远程目录..."
ssh ${REMOTE_USER}@${REMOTE_HOST} "mkdir -p ${REMOTE_DIR}"

echo "3. 上传JAR文件..."
scp ${JAR_FILE} ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/

echo "4. 创建systemd服务..."
ssh ${REMOTE_USER}@${REMOTE_HOST} "cat > /tmp/${SERVICE_NAME} << EOF
[Unit]
Description=${APP_NAME}
After=network.target

[Service]
Type=simple
User=${REMOTE_USER}
WorkingDirectory=${REMOTE_DIR}
ExecStart=/usr/bin/java -Xms64m -Xmx128m -jar ${REMOTE_DIR}/embedded-app-all.jar
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF"

echo "5. 安装服务..."
ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo mv /tmp/${SERVICE_NAME} /etc/systemd/system/"

echo "6. 重载并启动服务..."
ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo systemctl daemon-reload"
ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo systemctl enable ${SERVICE_NAME}"
ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo systemctl restart ${SERVICE_NAME}"

echo "7. 检查服务状态..."
ssh ${REMOTE_USER}@${REMOTE_HOST} "sudo systemctl status ${SERVICE_NAME}"

echo "=== 部署完成 ==="

2.2 启动脚本 #

bash
#!/bin/bash

APP_NAME="embedded-app"
JAR_FILE="embedded-app-all.jar"
LOG_DIR="/var/log/${APP_NAME}"
PID_FILE="/var/run/${APP_NAME}.pid"

JVM_OPTS="-server -Xms64m -Xmx128m \
    -XX:+UseSerialGC \
    -XX:+UseStringDeduplication \
    -Xshare:on \
    -XX:+ExitOnOutOfMemoryError"

start() {
    if [ -f $PID_FILE ]; then
        echo "Application is already running"
        exit 1
    fi
    
    mkdir -p $LOG_DIR
    
    echo "Starting ${APP_NAME}..."
    nohup java $JVM_OPTS -jar $JAR_FILE > $LOG_DIR/app.log 2>&1 &
    echo $! > $PID_FILE
    echo "Started with PID $(cat $PID_FILE)"
}

stop() {
    if [ ! -f $PID_FILE ]; then
        echo "Application is not running"
        exit 1
    fi
    
    echo "Stopping ${APP_NAME}..."
    kill $(cat $PID_FILE)
    rm $PID_FILE
    echo "Stopped"
}

status() {
    if [ -f $PID_FILE ]; then
        echo "${APP_NAME} is running with PID $(cat $PID_FILE)"
    else
        echo "${APP_NAME} is not running"
    fi
}

case "$1" in
    start) start ;;
    stop) stop ;;
    status) status ;;
    restart) stop; start ;;
    *) echo "Usage: $0 {start|stop|status|restart}" ;;
esac

三、自动化CI/CD #

3.1 GitHub Actions #

yaml
name: Build and Deploy

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
    
    - name: Build with Maven
      run: mvn clean package -DskipTests
    
    - name: Run tests
      run: mvn test
    
    - name: Upload artifact
      uses: actions/upload-artifact@v3
      with:
        name: embedded-app
        path: target/embedded-app-all.jar

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
    - name: Download artifact
      uses: actions/download-artifact@v3
      with:
        name: embedded-app
    
    - name: Deploy to Raspberry Pi
      uses: appleboy/scp-action@v0.1.4
      with:
        host: ${{ secrets.RASPBERRY_PI_HOST }}
        username: ${{ secrets.RASPBERRY_PI_USER }}
        key: ${{ secrets.RASPBERRY_PI_KEY }}
        source: "embedded-app-all.jar"
        target: "/home/pi/app"
    
    - name: Restart service
      uses: appleboy/ssh-action@v1.0.0
      with:
        host: ${{ secrets.RASPBERRY_PI_HOST }}
        username: ${{ secrets.RASPBERRY_PI_USER }}
        key: ${{ secrets.RASPBERRY_PI_KEY }}
        script: |
          sudo systemctl restart embedded-app.service
          sudo systemctl status embedded-app.service

四、Docker容器化 #

4.1 Dockerfile #

dockerfile
FROM eclipse-temurin:17-jre

WORKDIR /app

COPY target/embedded-app-all.jar app.jar

ENV JAVA_OPTS="-Xms64m -Xmx128m -XX:+UseSerialGC"

EXPOSE 8080

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

4.2 docker-compose.yml #

yaml
version: '3.8'

services:
  embedded-app:
    build: .
    container_name: embedded-app
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - ./data:/app/data
      - ./logs:/app/logs
    devices:
      - /dev/gpiomem:/dev/gpiomem
      - /dev/i2c-1:/dev/i2c-1
      - /dev/spidev0.0:/dev/spidev0.0
    environment:
      - TZ=Asia/Shanghai
      - JAVA_OPTS=-Xms64m -Xmx128m -XX:+UseSerialGC

五、总结 #

构建与部署要点:

  1. 构建工具:使用Maven或Gradle管理依赖和构建
  2. 打包方式:选择合适的打包方式(独立JAR或依赖分离)
  3. 部署脚本:编写自动化部署脚本
  4. 服务管理:使用systemd管理应用服务
  5. 容器化:考虑Docker容器化部署

下一章我们将学习项目实战案例。

最后更新:2026-03-27