每日更新主页背景图片
本文将介绍如何利用 GitHub Actions 自动获取必应每日壁纸,并将其设置为博客的背景图片。
1. 项目结构
主要涉及以下几个部分:
- Java 程序:负责获取和处理必应壁纸
- GitHub Actions 配置:负责定时执行程序
- 网站配置:展示背景图片
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
中,核心功能包括:
- 调用必应壁纸 API 获取每日图片信息
- 下载壁纸图片到指定目录
- 更新壁纸相关数据
核心方法说明:
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. 数据结构
程序会生成两个主要文件:
- 壁纸图片: docs/.vuepress/public/images/bing/bingWallpaper.jpg
- 壁纸数据: 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>