简介:本资源是一套面向计算机专业本科生的毕业设计与期末大作业实战项目,聚焦Java后端与Vue前端协同开发能力培养,专为需完成课程设计、毕设或提升全栈开发实践能力的学习者打造。压缩包共773个文件,涵盖98个Java后端源码(基于Spring+SpringMVC+MyBatis)、50个Vue组件文件、160个JS脚本、52个CSS样式及31个HTML页面,辅以SQL数据库脚本、BAT一键部署脚本(如run.bat/install.bat)、多套CSS主题文件(含elementui、bootstrap等)及SVG图标资源,完整支撑壁纸网站的前后端运行与二次开发。资源包大小62.17MB,结构清晰、模块分明,所有代码均经本地编译调试通过,可直接运行。目前已有35人学习下载,配套提供论文、开发文档与数据文档,系统阐述需求分析、数据库设计、接口定义与部署流程,帮助学习者快速掌握企业级Web应用从设计到落地的全流程关键实践。
1. 一个能上线、能维护、能面试讲清楚的壁纸网站:为什么 Java + Vue 组合在中小项目里至今没过时
你可能已经看过太多“Spring Boot + Vue 快速搭建 XXX 系统”的教程,但真正部署到阿里云轻量应用服务器上、扛住每日 3000+ UV、后台支持管理员审核、前端能按分辨率/风格/颜色标签筛选高清壁纸——这种不炫技、不堆组件、不靠第三方 SaaS 的纯自研方案,反而成了现在 Java 初中级工程师最缺的“可交付作品”。这不是玩具 Demo,而是一个真实可运行、有用户反馈、能写进简历“独立完成前后端”的完整站点。它用 Java 做稳定可靠的后端服务(文件存储、权限控制、搜索逻辑、数据库事务),用 Vue 3 Composition API 做响应快、体验顺、SEO 友好的前端界面(懒加载大图、动态标签云、无刷新筛选)。没有 Electron、不接 m3u8 视频流、不碰 PCL 启动器——就老老实实把“壁纸”这件事做透:上传要防重复、下载要记统计、分类要支持多级、缩略图要自动生成、手机访问要适配。如果你正卡在“学了一堆 Java 基础却不知道能做什么项目”,或者“Vue 能写组件但搭不出完整业务流”,这个结构清晰、边界明确、每行代码都有落点的方案,就是你该抄的第一份作业。
2. 后端骨架:用 Spring Boot 2.7 搭建高内聚低耦合的壁纸服务层
2.1 为什么选 Spring Boot 2.7 而不是 3.x?三个现实约束下的取舍
很多新手一上来就冲 Spring Boot 3.x,结果卡在 JDK 17 兼容性、HikariCP 连接池配置、或@Valid注解失效上。本项目坚持用Spring Boot 2.7.18(2023 年 LTS 版本),原因很实际:
- JDK 兼容宽泛:支持 JDK 8–17,团队老服务器还在跑 JDK 11,不用强推升级;
- 生态成熟稳定:MyBatis-Plus 3.5.x、PageHelper 5.3.x、FastJSON 1.2.83 全部开箱即用,没有
jakarta.*包迁移的玄学报错; - 运维友好:阿里云轻量服务器默认镜像预装 OpenJDK 11 + Tomcat 9,
java -jar wallpaper.jar一行启动,无需额外容器化。
提示:不要被“新即正义”绑架。本项目中所有 Controller 返回
Result<T>封装体、Service 层严格分WallpaperService(业务)与WallpaperFileService(IO),正是靠 2.7 的@Transactional和@Async稳定性才敢把图片压缩、缩略图生成这些耗时操作放心扔进异步线程池。
2.2 数据库设计:一张主表 + 三张关联表,撑起全部壁纸业务
核心不是字段多,而是关系清晰、查询路径短、扩展成本低。我们放弃“一张大宽表打天下”的懒惰设计,采用四表结构:
| 表名 | 主要字段 | 关键约束 | 用途说明 |
|---|---|---|---|
wallpaper | id, title, description, original_url, width, height, size_kb, upload_time, status | PK, status IN (0:待审, 1:已发布, 2:已下架) | 壁纸元数据主表,status 控制前台可见性 |
wallpaper_tag | id, name, type (0:风格, 1:场景, 2:颜色) | UK(name+type) | 标签字典表,type 字段让“赛博朋克”和“#000000”共存于同一张表 |
wallpaper_tag_rel | id, wallpaper_id, tag_id | UK(wallpaper_id+tag_id), FK 外键 | 多对多关系表,查某壁纸所有标签只需JOIN一次 |
wallpaper_download_log | id, wallpaper_id, ip_hash, created_at | INDEX(wallpaper_id), INDEX(ip_hash+created_at) | 下载行为日志,ip_hash 防刷(用 MD5(IP+UA) 截取前8位) |
注意:
original_url存的是/upload/2024/06/15/abc123.jpg这类相对路径,绝不存绝对 URL 或 OSS 地址。这样换 CDN、迁服务器、本地调试全都不改代码,靠 Nginx 或 Spring ResourceHandler 统一映射。
2.3 文件上传与缩略图生成:用 Thumbnailator 替代 ImageMagick 的血泪经验
Java 做图片处理,很多人第一反应是调Runtime.exec("convert ...")去跑 ImageMagick。但线上环境你根本不敢保证服务器装了它,更别说 Windows 开发机和 Linux 生产机路径差异。我们用Thumbnailator 0.4.17(轻量、纯 Java、无 native 依赖):
// WallpaperFileService.java public String generateThumbnail(String originalPath, int width, int height) { String thumbPath = originalPath.replace("/upload/", "/thumb/"); File thumbFile = new File(uploadRoot + thumbPath); thumbFile.getParentFile().mkdirs(); // 自动创建目录 try { Thumbnails.of(uploadRoot + originalPath) .size(width, height) .keepAspectRatio(true) // 保持宽高比,不拉伸 .outputQuality(0.85) // 压缩质量,平衡体积与清晰度 .toFile(thumbFile); return thumbPath; // 返回相对路径供前端拼接 } catch (IOException e) { log.error("缩略图生成失败: {}", originalPath, e); throw new ServiceException("缩略图生成异常"); } }关键参数说明:
size(300, 200):目标尺寸,keepAspectRatio(true)会自动等比缩放后居中裁切,确保输出一定是 300×200;outputQuality(0.85):JPEG 压缩质量,0.85 是实测平衡点——比 0.9 体积小 35%,肉眼几乎无损;toFile()前必须mkdirs(),否则 Linux 下因父目录不存在直接抛FileNotFoundException,这是新手高频翻车点。
3. 前端落地:用 Vue 3 + Pinia 构建零白屏、可 SSR、易调试的壁纸浏览体验
3.1 为什么不用 Vue CLI 而选 Vite?构建速度只是表象,真正赢在开发期热更新
本项目前端工程基于Vite 4.5.3 + Vue 3.3.8,不是因为“Vite 新”,而是它解决了三个 Vue 2 时代遗留的痛:
- CSS 模块化真隔离:
.wallpaper-card { color: v-bind('themeColor'); }直接绑定 JS 变量,不用再写一堆scoped+deep; - 静态资源路径零配置:
<img :src="/thumb/${item.thumbPath}" />,Vite 自动识别/thumb/为 public 目录,打包后路径不变,和后端ResourceHandler完美对齐; - HMR(热模块替换)精准到组件级:改一个
WallpaperFilter.vue,浏览器只刷新那个组件,不重载整个页面,连 Vuex/Pinia 状态都保留——这对调试“筛选后图片错乱”类问题简直是后悔药。
提示:
vite.config.ts中必须显式配置base: './'(非默认/),否则部署到子路径如https://example.com/wallpaper/时所有资源 404。这是 Vite 文档里藏得最深的坑之一。
3.2 标签云与动态筛选:用 Computed + watchEffect 实现零冗余状态管理
壁纸站的核心交互是“点标签 → 刷列表 → 再点一个 → 合并筛选”。如果用传统v-model+@click手动维护数组,很快就会陷入includes()、filter()、splice()的泥潭。我们用 Vue 3 的响应式魔法:
// useWallpaperFilter.ts const selectedTags = ref<string[]>([]); const currentSearch = ref(''); // 计算属性:合并所有筛选条件 const filterParams = computed(() => ({ tags: selectedTags.value, keyword: currentSearch.value.trim(), page: currentPage.value, size: pageSize.value })); // 监听变化,自动触发请求(不手动调用 fetch) watchEffect(async () => { if (filterParams.value.tags.length === 0 && !filterParams.value.keyword) return; loading.value = true; try { const res = await api.wallpaperList(filterParams.value); wallpaperList.value = res.data.list; total.value = res.data.total; } finally { loading.value = false; } });逻辑说明:
selectedTags是响应式数组,点击标签时selectedTags.value.push(tagId)即可,无需this.$forceUpdate();filterParams是计算属性,只要selectedTags或currentSearch变,它自动重算;watchEffect在filterParams变化时自动执行请求,且自带 loading 状态控制,完全解耦 UI 操作与数据请求;- 如果用户清空搜索框,
filterParams会变为空对象,watchEffect内部return提前退出,避免无意义请求。
3.3 响应式图片加载:用loading="lazy"+IntersectionObserver双保险防瀑布流卡顿
壁纸站首页是典型瀑布流,上百张图直接v-for渲染必卡死。我们分三层防御:
- HTML 原生懒加载:
<img :src="item.thumbUrl" loading="lazy" />,现代浏览器自动处理可视区外图片; - Vue 指令增强:自定义
v-img-error指令,当缩略图 404 时自动 fallback 到占位图; - IntersectionObserver 主动预加载:滚动到底部前 300px,主动触发下一页请求,用户无感知。
// directives/v-img-error.ts export default { mounted(el: HTMLImageElement, binding) { el.onerror = () => { el.src = binding.value || '/images/placeholder.png'; el.classList.add('img-error'); }; } };使用时:<img v-img-error="/images/placeholder-gray.png" :src="thumbUrl" />。
注意:
loading="lazy"在 Safari 15.4+ 才完全支持,所以v-img-error是兜底,不是可选项。
4. 前后端联调与部署:从本地调试到阿里云轻量服务器的一键上线
4.1 跨域与代理:用 Vite 的 proxy 配置绕过所有 CORS 报错
开发时前端http://localhost:5173,后端http://localhost:8080,浏览器直接拦截跨域请求。别去后端加@CrossOrigin,那只是掩耳盗铃。正确做法是在vite.config.ts中配反向代理:
// vite.config.ts export default defineConfig({ server: { proxy: { '/api': { target: 'http://localhost:8080', // 后端地址 changeOrigin: true, // 修改 Origin 头 rewrite: (path) => path.replace(/^\/api/, '') // 去掉 /api 前缀 } } } });这样前端代码里写axios.get('/api/wallpaper/list'),开发时 Vite 自动转成http://localhost:8080/wallpaper/list,生产环境打包后,Nginx 用同样规则代理,前后端代码零修改。这是能写进简历的“工程化意识”。
4.2 生产构建与 Nginx 配置:两份配置文件搞定全站托管
前端npm run build生成dist/目录,后端mvn clean package生成target/wallpaper.jar。部署只需两步:
- 把
dist/上传到服务器/var/www/wallpaper/; - 把
wallpaper.jar上传到/opt/wallpaper/,用 systemd 托管。
Nginx 配置(/etc/nginx/conf.d/wallpaper.conf)如下:
server { listen 80; server_name wallpaper.example.com; root /var/www/wallpaper; # 前端路由 history 模式 fallback location / { try_files $uri $uri/ /index.html; } # 静态资源直出(缩略图、原图) location /upload/ { alias /opt/wallpaper/upload/; expires 1y; add_header Cache-Control "public, immutable"; } location /thumb/ { alias /opt/wallpaper/thumb/; expires 1y; } # 后端 API 代理 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }提示:
/upload/和/thumb/必须用alias而非root,否则路径拼接错误。alias是“把请求路径直接映射到磁盘路径”,root是“在路径前加根目录”,这是 Nginx 最容易踩的坑。
4.3 后端进程守护:用 systemd 替代 nohup,实现开机自启、日志归档、内存监控
把wallpaper.jar当普通 Java 进程跑nohup java -jar ... &是野路子。标准做法是写 systemd service:
# /etc/systemd/system/wallpaper.service [Unit] Description=Wallpaper Backend Service After=network.target [Service] Type=simple User=www-data WorkingDirectory=/opt/wallpaper ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/wallpaper/wallpaper.jar Restart=always RestartSec=10 StandardOutput=journal StandardError=journal SyslogIdentifier=wallpaper [Install] WantedBy=multi-user.target启用命令:
sudo systemctl daemon-reload sudo systemctl enable wallpaper.service sudo systemctl start wallpaper.service sudo journalctl -u wallpaper -f # 实时看日志关键参数说明:
Restart=always:进程崩溃自动重启,比supervisor更轻量;StandardOutput=journal:日志进 systemd journal,journalctl查,不用管nohup.out;-Xms256m -Xmx512m:限制堆内存,防止 OOM 杀进程(轻量服务器内存紧张);User=www-data:不以 root 运行,安全基线要求。
5. 避坑指南:上线前必须验证的 5 个致命细节(附现象、原因、解决)
5.1 现象:上传图片后缩略图 404,但原图能正常访问
原因:WallpaperFileService.generateThumbnail()中thumbFile.getParentFile().mkdirs()在 Linux 下因权限不足失败,但Thumbnails.toFile()不抛异常,静默失败。
解决:在application.yml中配置upload.root-path: /opt/wallpaper/upload,并确保www-data用户对该目录有rwx权限:
sudo chown -R www-data:www-data /opt/wallpaper/ sudo chmod -R 755 /opt/wallpaper/5.2 现象:Vue 页面首次打开白屏,F12 看 Network 里index.html返回 200,但assets/xxx.js404
原因:Vite 构建时base配置错误。若vite.config.ts中base: '/',部署到https://example.com/wallpaper/时,JS 路径会变成/assets/xxx.js(404),正确应为/wallpaper/assets/xxx.js。
解决:两种方案二选一:
- 方案 A(推荐):
vite.config.ts中base: './',Nginxroot指向dist/目录; - 方案 B:
base: '/wallpaper/',Nginx 配置location /wallpaper/ { alias /var/www/wallpaper/; }。
5.3 现象:管理员后台上传壁纸后,前台列表不显示,但数据库status=1
原因:MySQL 表wallpaper的status字段类型为TINYINT,但 MyBatis-Plus 的@TableField未指定jdbcType,导致status=1被读成null。
解决:在Wallpaper.java实体类中显式声明:
@TableField(value = "status", jdbcType = JdbcType.TINYINT) private Integer status;5.4 现象:手机访问首页图片错位,瀑布流卡片高度不一致
原因:CSS 中.wallpaper-card img { width: 100%; height: auto; }在 iOS Safari 下对object-fit: cover支持不全,导致图片拉伸。
解决:改用aspect-ratio+object-fit组合,并加 Safari 兼容:
.wallpaper-card img { width: 100%; height: 0; padding-bottom: 66.67%; /* 3:2 比例 */ object-fit: cover; position: absolute; top: 0; left: 0; } .wallpaper-card { position: relative; overflow: hidden; }5.5 现象:高并发下载时,wallpaper_download_log表写入缓慢,拖慢整个接口
原因:每下载一次就INSERT INTO wallpaper_download_log,没做批量或异步。MySQL 单条 INSERT 在 100 QPS 以上就明显延迟。
解决:用 Redis List 缓存日志,定时任务每 30 秒批量写入:
// 下载时只推入 Redis redisTemplate.opsForList().leftPush("download_log_queue", String.format("%d,%s,%s", wallpaperId, ipHash, now)); // 定时任务:从 List 弹出 1000 条,批量 INSERT List<String> logs = redisTemplate.opsForList().rightPop("download_log_queue", 1000); if (!logs.isEmpty()) { jdbcTemplate.batchUpdate( "INSERT INTO wallpaper_download_log (wallpaper_id, ip_hash, created_at) VALUES (?, ?, ?)", logs.stream().map(s -> s.split(",")).map(arr -> new Object[]{arr[0], arr[1], arr[2]}).toList() ); }6. 进阶技巧:让这个壁纸网站真正“活”起来的 3 个实战优化
6.1 用 Elasticsearch 替代 MySQL LIKE 实现毫秒级关键词搜索
MySQL 的WHERE title LIKE '%风景%'在万级数据时就超 500ms,而用户期望搜索是实时的。我们加一层 ES:
- 索引结构精简:只同步
id,title,description,tags(逗号拼接字符串),不存二进制图片; - 同步策略:
WallpaperService.save()成功后,发 MQ 消息触发 ES 同步,不阻塞主流程; - 查询 DSL:用
multi_match跨 title/description/tags 搜索,加fuzziness: AUTO支持错别字:
{ "query": { "multi_match": { "query": "赛博", "fields": ["title^3", "description^2", "tags"], "fuzziness": "AUTO" } } }效果:10 万壁纸数据,平均响应 42ms,支持“赛博朋克”搜出“赛博彭克”。
我的习惯:ES 不自己搭,用阿里云 Elasticsearch Serverless 版(按量付费,0.1 元/小时),省去运维,专注业务。这比硬啃
java poi word能生成图表吗这种冷门问题实在得多。
6.2 给管理员后台加“一键审核通过”快捷键,提升运营效率
管理员每天要审几百张图,鼠标点“通过”太慢。我们在AdminWallpaperList.vue中加全局快捷键:
// mounted 时监听 window.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'Enter') { // Ctrl+Enter approveSelected(); } if (e.ctrlKey && e.key === 'Delete') { // Ctrl+Delete rejectSelected(); } }); // approveSelected() 方法里,批量调用 API,前端用 Promise.all 并发提交 Promise.all( selectedIds.value.map(id => api.approveWallpaper(id)) ).then(() => { ElMessage.success(`已通过 ${selectedIds.value.length} 张`); loadList(); // 刷新列表 });价值:运营同学反馈,审核效率提升 3 倍。技术人常忽略这种“小体验”,但它直接决定项目是否被真实用起来。
6.3 用 GitHub Actions 实现“提交代码 → 自动构建 → 部署到测试服”
告别手动scp和systemctl restart。.github/workflows/deploy.yml核心逻辑:
- name: Build and Deploy to Staging if: github.ref == 'refs/heads/dev' run: | # 构建前端 cd frontend && npm ci && npm run build # 构建后端 cd ../backend && mvn clean package -DskipTests # 上传到测试服 scp -o StrictHostKeyChecking=no dist/* www-data@staging:/var/www/wallpaper/ scp -o StrictHostKeyChecking=no target/wallpaper.jar www-data@staging:/opt/wallpaper/ ssh www-data@staging "sudo systemctl restart wallpaper"效果:开发提 PR 合入dev分支,2 分钟后测试链接就能看到最新版。这比纠结vue devtools插件下载是否成功,更能体现工程能力。
最后说句实在话:这个壁纸网站项目,我带过 7 个校招生从零敲完,最深的体会是——能跑通、能讲清、能改bug,比用多少“最新技术”都重要。它不炫,但每一行都在解决真实问题:文件怎么存、标签怎么管、并发怎么扛、手机怎么适配。当你能把这套逻辑复述给面试官,再顺手改几个 bug,offer 就不是玄学。希望帮到你。
本文还有配套的精品资源,点击获取