Skip to content

每日更新主页背景图片

约 1856 字大约 6 分钟

GitHub ActionJava

2025-03-10

本文将介绍如何利用 GitHub Actions 自动获取必应每日壁纸,并将其设置为博客的背景图片。

1. 项目结构

主要涉及以下几个部分:

  1. Java 程序:负责获取和处理必应壁纸
  2. GitHub Actions 配置:负责定时执行程序
  3. 网站配置:展示背景图片

2. Java 程序实现

2.1 依赖配置

中添加所需依赖:


<dependencies>
    <!-- HTTP客户端 -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.13</version>
    </dependency>
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.13.4.1</version>
    </dependency>
    <!-- 日志 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>
</dependencies>

2.2 核心代码实现

主要实现在 BingWallpaperApp.java 中,核心功能包括:

  1. 调用必应壁纸 API 获取每日图片信息
  2. 下载壁纸图片到指定目录
  3. 更新壁纸相关数据

核心方法说明:

  • fetchBingData :获取必应 API 数据
  • downloadImage :下载壁纸图片
  • updateWallpaperData :更新壁纸数据文件
public class BingWallpaperApp {
    private static final Logger logger = LoggerFactory.getLogger(BingWallpaperApp.class);
    private static final String BING_API_URL = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=zh-CN";
    private static final String BING_BASE_URL = "https://www.bing.com";
    private static final String IMAGES_DIR = "docs/.vuepress/public/images";
    private static final String WALLPAPER_DATA_FILE = "docs/.vuepress/public/data/bing-wallpaper.json";

    public static void main(String[] args) {
        try {
            logger.info("开始执行Bing壁纸更新任务");

            // 确保目录存在
            createDirectoryIfNotExists(IMAGES_DIR);
            createDirectoryIfNotExists(Paths.get(WALLPAPER_DATA_FILE).getParent().toString());

            // 获取Bing壁纸信息
            String bingData = fetchBingData();
            ObjectMapper mapper = new ObjectMapper();
            JsonNode rootNode = mapper.readTree(bingData);

            // 解析壁纸数据
            JsonNode imageNode = rootNode.path("images").get(0);
            String imageUrl = BING_BASE_URL + imageNode.path("url").asText();
            String copyright = imageNode.path("copyright").asText();
            String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());

            // 下载壁纸
            String fileName = "bingWallpaper.jpg";
            String localPath = IMAGES_DIR + "/" + fileName;
            downloadImage(imageUrl, localPath);

            // 更新数据文件
            updateWallpaperData(date, copyright, fileName);

            logger.info("Bing壁纸更新任务完成");
        } catch (Exception e) {
            logger.error("执行Bing壁纸更新任务时发生错误", e);
            System.exit(1);
        }
    }

    private static String fetchBingData() throws IOException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet request = new HttpGet(BING_API_URL);
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                return EntityUtils.toString(response.getEntity());
            }
        }
    }

    private static void downloadImage(String imageUrl, String localPath) throws IOException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet request = new HttpGet(imageUrl);
            try (CloseableHttpResponse response = httpClient.execute(request);
                 InputStream inputStream = response.getEntity().getContent();
                 FileOutputStream outputStream = new FileOutputStream(localPath)) {

                byte[] buffer = new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }

                logger.info("壁纸已下载到: {}", localPath);
            }
        }
    }

    private static void updateWallpaperData(String date, String copyright, String fileName) throws IOException {
        File dataFile = new File(WALLPAPER_DATA_FILE);
        ObjectMapper mapper = new ObjectMapper();

        JsonNode rootNode;
        if (dataFile.exists()) {
            rootNode = mapper.readTree(dataFile);
        } else {
            rootNode = mapper.createObjectNode();
        }

        ((com.fasterxml.jackson.databind.node.ObjectNode) rootNode).put("lastUpdate", date);
        ((com.fasterxml.jackson.databind.node.ObjectNode) rootNode).put("copyright", copyright);
        ((com.fasterxml.jackson.databind.node.ObjectNode) rootNode).put("image", "images/bing/" + fileName);

        mapper.writerWithDefaultPrettyPrinter().writeValue(dataFile, rootNode);
        logger.info("壁纸数据已更新");
    }

    private static void createDirectoryIfNotExists(String dirPath) throws IOException {
        Path path = Paths.get(dirPath);
        if (!Files.exists(path)) {
            Files.createDirectories(path);
            logger.info("创建目录: {}", dirPath);
        }
    }
}

2.3打包配置

为了能在 GitHub Actions 中运行,需要将程序打包成可执行 jar 包。在 pom.xml 中添加打包插件:


<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <archive>
                    <manifest>
                        <mainClass>com.exiao.BingWallpaperApp</mainClass>
                    </manifest>
                </archive>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <finalName>${project.name}</finalName>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

3. GitHub Actions 配置

创建 .github/workflows/update-bing-wallpaper.yml 文件,配置定时任务:

name: Update Bing Wallpaper

on:
  push:
    #  main 分支接收到推送时触发
    branches: [ main ]
  schedule:
    # 定时任务(UTC 时间每天 0 点,北京时间 8 点)
    - cron: '0 0 * * *'

jobs:
  build:
    # 使用最新版 Ubuntu 运行环境
    runs-on: ubuntu-latest

    steps:
      # 检出代码仓库
      - uses: actions/checkout@v2
      # 设置 JDK 8 环境
      - name: Set up JDK 1.8
        uses: actions/setup-java@v1
        with:
          java-version: 1.8
      # 输出当前时间(用于日志记录)
      - name: current date
        run: date
      # Maven 打包项目
      - name: Build with Maven
        run: mvn -B package --file pom.xml
      # 运行 Java 程序生成壁纸
      - name: Run Java Application
        run: java -jar target/bing-wallpaper-jar-with-dependencies.jar
      # 提交变更文件(壁纸图片)
      - name: Commit files
        run: |
          git config --local user.email "[email protected]"
          git config --local user.name "My Github Actions"
          git add -A  # 简化添加所有变更文件
          git commit -m "chore: 日常更新bing壁纸" || echo "没有新变更需要提交"
          git pull --rebase  # 添加 rebase 避免合并提交
      # 推送变更到 main 分支
      - name: Push changes
        uses: ad-m/github-push-action@master
        with:
          # github_token  GitHub Actions 自动生成的认证令牌,主要用于:
          # 身份验证 - 允许工作流向仓库推送代码变更
          # 权限控制 - 自动拥有当前仓库的读写权限(但仅限于当前仓库)
          # 安全性 -  GitHub 自动生成和管理,无需手动配置敏感信息
          # 在https://github.com/settings/tokens设置一个Personal access tokens (classic)
          # 将这个token设置到仓库的settings/secrets/actions
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: main

同时需要配置另一个workflow,用于在update-bing-wallpaper.yml完成后触发部署, 而这个workflow主要负责收到push后的自动部署:

name: deploy

on:
  # 添加 workflow_run 触发器
  workflow_run:
    workflows: [ "Update Bing Wallpaper" ]
    branches: [ main ]
    types:
      - completed
  # 每当 push  main 分支时触发部署
  push:
    branches: [ main ]
    paths-ignore: # 添加路径过滤
      - 'docs/壁纸/**'
      - 'target/**'
  # 手动触发部署
  workflow_dispatch:

jobs:
  docs:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4
        with:
          # “最近更新时间”  git 日志相关信息,需要拉取全部提交记录
          fetch-depth: 0

      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          # 选择要使用的 pnpm 版本
          version: 8
          # 使用 pnpm 安装依赖
          run_install: true

      # 这里选择WebP是因为WebP 在相同质量下,文件大小通常比 JPG  PNG 更小。
      # 实际体验下来首页加载图片的速度也快多了,体感很好,而且视觉差别差不多
      - name: Install WebP tools
        run: sudo apt-get update && sudo apt-get install -y webp

      - name: Convert images to WebP
        run: |
          find docs/.vuepress/public/images -type f -name "*.jpg" -exec sh -c 'cwebp -q 80 "$1" -o "${1%.jpg}.webp"' _ {} \;
          find docs/.vuepress/public/images -type f -name "*.jpg" -exec rm {} \;

      # 运行构建脚本
      - name: Build VuePress site
        run: pnpm docs:build

      # 查看 workflow 的文档来获取更多信息
      # @see https://github.com/crazy-max/ghaction-github-pages
      - name: Deploy to GitHub Pages
        uses: crazy-max/ghaction-github-pages@v4
        with:
          # 部署到 gh-pages 分支
          target_branch: gh-pages
          # 部署目录为 VuePress 的默认输出目录
          build_dir: docs/.vuepress/dist
        env:
          # @see https://docs.github.com/cn/actions/reference/authentication-in-a-workflow#about-the-github_token-secret
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

4. 数据结构

程序会生成两个主要文件:

  1. 壁纸图片: docs/.vuepress/public/images/bing/bingWallpaper.jpg
  2. 壁纸数据: docs/.vuepress/public/data/bing-wallpaper.json
{
  "lastUpdate": "2024-03-10",
  "copyright": "图片版权信息",
  "image": "images/bing/bingWallpaper.jpg"
}

5. 网站配置

主要实现在 HomeBanner.vue 中,代码如下:


<template>
  <div class="home-banner" :style="bannerStyle">
    <div class="banner-content">
      <div class="avatar-container">
        <img class="avatar" :src="avatarUrl" alt="Exiao's Avatar" />
      </div>
      <h1 class="banner-text" style="font-size: 26px">🍐欢迎来到霄霄的技术博客~呀呼~</h1>
    </div>
  </div>
</template>

<script setup>
  import {ref, onMounted} from 'vue'

  // 背景图片 URL
  const backgroundUrl = ref('')
  // 你的头像 URL,请替换为你的实际头像地址
  const avatarUrl = ref('/images/homeAvatar.jpg')

  // 判断是否为移动端
  const isMobile = () => {
    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
        navigator.userAgent
    )
  }

  // 背景样式
  const bannerStyle = ref({
    backgroundImage: 'none',
    backgroundSize: 'cover',
    backgroundPosition: 'center'
  })

  // 使用本地 Bing 壁纸
  const useBingWallpaper = () => {
    try {
      // 直接使用本地壁纸路径
      const imageUrl = '/images/bingWallpaper.jpg'
      backgroundUrl.value = imageUrl
      bannerStyle.value.backgroundImage = `url(${imageUrl})`
    } catch (error) {
      console.error('加载本地壁纸出错:', error)
      // 使用默认背景图
      bannerStyle.value.backgroundImage = 'url(/images/defaultHomeBanner.jpg)'
    }
  }

  onMounted(() => {
    useBingWallpaper()
  })
</script>

<style scoped>
  .home-banner {
    width: 100%;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    overflow: hidden;
  }

  .home-banner::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, 0.4);
    z-index: 1;
  }

  .banner-content {
    position: relative;
    z-index: 2;
    text-align: center;
    color: white;
    padding: 2rem;
  }

  .avatar-container {
    margin-bottom: 2rem;
  }

  .avatar {
    width: 150px;
    height: 150px;
    border-radius: 50%;
    border: 3px solid white;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
    object-fit: cover;
  }

  .banner-text {
    font-size: 2.5rem;
    font-weight: bold;
    text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
    margin: 0;
  }

  @media (max-width: 768px) {
    .avatar {
      width: 120px;
      height: 120px;
    }

    .banner-text {
      font-size: 1.8rem;
    }
  }
</style>

————————到底啦!————————