Spring Boot + Vue 全栈项目:待办应用从0到1
某天需要一个练手项目,把前后端分离的流程跑通。选了最经典的待办应用,后端 Spring Boot + MySQL + Redis(做列表缓存),前端 Vue 3。前后花了两个小时,用 AI 辅助写了大部分代码。
技术栈
- 后端:Spring Boot 2.7.0,Spring Data JPA,MySQL 8.0,Redis(Jedis)
- 前端:Vue 3,Axios,Vite
- 部署:后端 jar 包用 nohup 跑在 ECS,前端静态文件放 Nginx,配置跨域
后端代码
application.properties
properties
spring.datasource.url=jdbc:mysql://localhost:3306/todo_db?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
# Redis 配置(本地默认)
spring.redis.host=localhost
spring.redis.port=6379Todo 实体类
java
@Entity
@Table(name = "todos")
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private boolean completed;
// 构造器、getter/setter 省略
}TodoRepository
java
@Repository
public interface TodoRepository extends JpaRepository<Todo, Long> {
}TodoService(含 Redis 缓存逻辑)
java
@Service
public class TodoService {
@Autowired
private TodoRepository todoRepository;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String TODOS_CACHE_KEY = "all_todos";
public List<Todo> getAllTodos() {
// 先从 Redis 取
List<Todo> cached = (List<Todo>) redisTemplate.opsForValue().get(TODOS_CACHE_KEY);
if (cached != null) {
return cached;
}
List<Todo> todos = todoRepository.findAll();
redisTemplate.opsForValue().set(TODOS_CACHE_KEY, todos, 10, TimeUnit.MINUTES);
return todos;
}
public Todo addTodo(String title) {
Todo todo = new Todo();
todo.setTitle(title);
todo.setCompleted(false);
Todo saved = todoRepository.save(todo);
// 添加后让缓存失效,下次查询重新加载
redisTemplate.delete(TODOS_CACHE_KEY);
return saved;
}
public void deleteTodo(Long id) {
todoRepository.deleteById(id);
redisTemplate.delete(TODOS_CACHE_KEY);
}
}TodoController
java
@RestController
@CrossOrigin(origins = "http://localhost:5173") // 前端开发端口
@RequestMapping("/api/todos")
public class TodoController {
@Autowired
private TodoService todoService;
@GetMapping
public List<Todo> getAll() {
return todoService.getAllTodos();
}
@PostMapping
public Todo add(@RequestParam String title) {
return todoService.addTodo(title);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
todoService.deleteTodo(id);
}
}前端代码
安装依赖
bash
npm create vite@latest todo-frontend -- --template vue
cd todo-frontend
npm install axiosmain.js
javascript
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')App.vue
vue
<template>
<div>
<h1>待办事项</h1>
<input v-model="newTitle" @keyup.enter="addTodo" placeholder="输入任务" />
<button @click="addTodo">添加</button>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.title }}
<button @click="deleteTodo(todo.id)">删除</button>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
todos: [],
newTitle: ''
}
},
mounted() {
this.fetchTodos()
},
methods: {
async fetchTodos() {
const res = await axios.get('http://localhost:8080/api/todos')
this.todos = res.data
},
async addTodo() {
if (!this.newTitle.trim()) return
await axios.post(`http://localhost:8080/api/todos?title=${this.newTitle}`)
this.newTitle = ''
this.fetchTodos()
},
async deleteTodo(id) {
await axios.delete(`http://localhost:8080/api/todos/${id}`)
this.fetchTodos()
}
}
}
</script>配置跨域(后端已加@CrossOrigin,前端无需额外处理)
如果后端部署到云服务器,前端需要替换 localhost:8080 为实际后端 IP。
遇到的坑及解决
1. Redis 缓存一致性问题
一开始只在查询时写入缓存,添加/删除时没有删除缓存,导致前端看到的数据不一致。解决方案:在 addTodo 和 deleteTodo 中删除 all_todos 这个 key。
2. 跨域请求失败
前后端分离,前端端口 5173,后端 8080,浏览器拦截。加 @CrossOrigin(origins = "http://localhost:5173") 解决。生产环境换成具体域名。
3. 前端 axios 请求参数格式
POST 请求传 ?title=xxx 是 URL 编码方式。也可以改用 @RequestBody,但简化起见就用 @RequestParam。
4. Spring Data JPA 懒加载问题
没有涉及关联关系,所以没有遇到。
部署要点
- 后端打包:
mvn clean package,生成todo-0.0.1-SNAPSHOT.jar - 上传到 ECS,运行:
nohup java -jar todo-0.0.1-SNAPSHOT.jar > log.log 2>&1 & - 前端打包:
npm run build,得到 dist 文件夹 - 将 dist 内容放到 Nginx 的
/var/www/html,配置 Nginx 反向代理/api/到后端地址,避免跨域。
总结
这个项目麻雀虽小五脏俱全:前后端分离、数据库、缓存、跨域、打包部署。花了两小时用 AI 辅助生成大部分代码,主要精力花在调试缓存一致性和跨域上。面试时可以直接演示这个 demo,并能说清楚每个技术点的作用。
如果需要提升,可以加入 JWT 鉴权、用户隔离、Redis 持久化配置等,但目前版本足够展示全栈基础。