1. 项目概述
作为一个拥有多年开发经验的程序员,我想分享一个基于SpringBoot+Vue技术的菜谱交流平台的设计与实现过程。这个项目是我最近完成的一个计算机毕业设计,旨在为烹饪爱好者提供一个集菜谱分享、互动交流和管理功能于一体的综合性平台。
在当今快节奏的生活中,越来越多人开始关注健康饮食和烹饪技巧。然而,现有的菜谱平台要么功能单一,要么用户体验不佳。因此,我决定开发这个平台,它采用前后端分离架构,后端使用SpringBoot框架,前端使用Vue.js,数据库采用MySQL,通过MyBatis实现数据持久化。
这个平台的主要特点包括:
- 丰富的菜谱浏览和搜索功能
- 用户互动功能(点赞、收藏、评论)
- 实用的食材计算和营养分析工具
- 完善的社区论坛和资讯系统
- 强大的后台管理功能
2. 技术选型与架构设计
2.1 技术栈选择
在项目初期,我经过仔细的技术调研和对比,最终确定了以下技术栈:
后端技术栈:
- SpringBoot 2.7.0:简化Spring应用的初始搭建和开发过程
- Spring Security:提供认证和授权功能
- MyBatis-Plus:简化数据库操作
- MySQL 8.0:关系型数据库
- Redis:缓存热点数据,提高系统响应速度
前端技术栈:
- Vue.js 3.0:渐进式JavaScript框架
- Element Plus:UI组件库
- Axios:HTTP客户端
- Vue Router:路由管理
- Vuex:状态管理
开发工具:
- IntelliJ IDEA:Java开发IDE
- VS Code:前端开发IDE
- Navicat:数据库管理工具
- Postman:API测试工具
2.2 系统架构设计
系统采用经典的三层架构设计:
- 表现层(UI):负责用户界面展示和交互,使用Vue.js实现
- 业务逻辑层(BLL):处理业务逻辑,使用SpringBoot实现
- 数据层(DL):负责数据存储和访问,使用MySQL+MyBatis实现
架构图如下:
code复制┌───────────────────────────────────────────────────────────────┐
│ 表现层 (Vue.js) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ 组件库 │ │ 路由管理 │ │ 状态管理 │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 业务逻辑层 (SpringBoot) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ 控制器层 │ │ 服务层 │ │ 安全认证 │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
└───────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ 数据层 (MySQL) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ 数据访问层 │ │ 缓存层 │ │ 数据模型 │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
└───────────────────────────────────────────────────────────────┘
2.3 数据库设计
数据库设计采用了规范化的设计方法,主要包含以下核心表:
-
用户相关表:
- user:用户基本信息
- user_group:用户组信息
- auth:权限管理
-
内容相关表:
- recipe_information:菜谱信息
- recipe_type:菜谱分类
- cooking_videos:烹饪视频
- ranking_information:排行信息
-
互动相关表:
- comment:评论
- collect:收藏
- praise:点赞
- forum:论坛
-
工具相关表:
- ingredient_calculator:食材计算器
- nutritional_analysis:营养分析
完整的ER图展示了这些表之间的关系,确保数据的一致性和完整性。
3. 核心功能实现
3.1 用户模块实现
用户模块是系统的基础,包括注册、登录、个人信息管理等功能。
注册功能实现:
java复制@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public Result register(@RequestBody UserRegisterDTO userDTO) {
// 验证用户名是否已存在
if (userService.existsUsername(userDTO.getUsername())) {
return Result.error("用户名已存在");
}
// 验证邮箱是否已注册
if (userService.existsEmail(userDTO.getEmail())) {
return Result.error("邮箱已注册");
}
// 密码加密
String encodedPassword = passwordEncoder.encode(userDTO.getPassword());
// 创建用户实体
User user = new User();
user.setUsername(userDTO.getUsername());
user.setPassword(encodedPassword);
user.setEmail(userDTO.getEmail());
user.setNickname(userDTO.getNickname());
user.setAvatar(DEFAULT_AVATAR);
// 保存用户
if (userService.save(user)) {
return Result.success("注册成功");
} else {
return Result.error("注册失败");
}
}
}
登录功能实现:
java复制@PostMapping("/login")
public Result login(@RequestBody UserLoginDTO userDTO) {
// 验证用户名和密码
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userDTO.getUsername(), userDTO.getPassword());
try {
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 生成JWT令牌
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String token = jwtUtil.generateToken(userDetails);
// 返回令牌和用户基本信息
Map<String, Object> data = new HashMap<>();
data.put("token", token);
data.put("user", userService.getUserInfo(userDetails.getUsername()));
return Result.success("登录成功", data);
} catch (BadCredentialsException e) {
return Result.error("用户名或密码错误");
}
}
3.2 菜谱模块实现
菜谱模块是系统的核心功能,包括菜谱的发布、浏览、搜索等功能。
菜谱发布功能:
java复制@PostMapping("/recipe")
@PreAuthorize("hasRole('USER')")
public Result publishRecipe(@RequestBody RecipePublishDTO recipeDTO) {
// 获取当前用户
User currentUser = getCurrentUser();
// 创建菜谱实体
RecipeInformation recipe = new RecipeInformation();
recipe.setRecipeName(recipeDTO.getRecipeName());
recipe.setRecipeType(recipeDTO.getRecipeType());
recipe.setRecipeLabel(recipeDTO.getRecipeLabel());
recipe.setRecipeIngredients(recipeDTO.getRecipeIngredients());
recipe.setRecipeFlavors(recipeDTO.getRecipeFlavors());
recipe.setTeachingVideos(recipeDTO.getTeachingVideos());
recipe.setUserInformation(currentUser.getUserId());
recipe.setDetailedContent(recipeDTO.getDetailedContent());
// 处理封面图片上传
if (recipeDTO.getCoverPhoto() != null) {
String coverUrl = fileService.uploadImage(recipeDTO.getCoverPhoto());
recipe.setCoverPhoto(coverUrl);
}
// 保存菜谱
if (recipeService.save(recipe)) {
return Result.success("菜谱发布成功");
} else {
return Result.error("菜谱发布失败");
}
}
菜谱搜索功能:
java复制@GetMapping("/recipes")
public Result searchRecipes(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String type,
@RequestParam(required = false) String tag,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
// 构建查询条件
QueryWrapper<RecipeInformation> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(keyword)) {
queryWrapper.like("recipe_name", keyword)
.or().like("recipe_ingredients", keyword)
.or().like("detailed_content", keyword);
}
if (StringUtils.isNotBlank(type)) {
queryWrapper.eq("recipe_type", type);
}
if (StringUtils.isNotBlank(tag)) {
queryWrapper.like("recipe_label", tag);
}
// 分页查询
Page<RecipeInformation> pageInfo = new Page<>(page, size);
IPage<RecipeInformation> recipePage = recipeService.page(pageInfo, queryWrapper);
// 返回结果
Map<String, Object> data = new HashMap<>();
data.put("list", recipePage.getRecords());
data.put("total", recipePage.getTotal());
data.put("pages", recipePage.getPages());
return Result.success(data);
}
3.3 互动功能实现
互动功能包括点赞、收藏、评论等,增强了用户之间的交流。
点赞功能实现:
java复制@PostMapping("/praise")
@PreAuthorize("hasRole('USER')")
public Result addPraise(@RequestBody PraiseDTO praiseDTO) {
User currentUser = getCurrentUser();
// 检查是否已点赞
if (praiseService.existsPraise(currentUser.getUserId(),
praiseDTO.getSourceTable(),
praiseDTO.getSourceId())) {
return Result.error("您已经点过赞了");
}
// 创建点赞记录
Praise praise = new Praise();
praise.setUserId(currentUser.getUserId());
praise.setSourceTable(praiseDTO.getSourceTable());
praise.setSourceId(praiseDTO.getSourceId());
if (praiseService.save(praise)) {
// 更新点赞数
updatePraiseCount(praiseDTO.getSourceTable(), praiseDTO.getSourceId(), 1);
return Result.success("点赞成功");
} else {
return Result.error("点赞失败");
}
}
private void updatePraiseCount(String sourceTable, Integer sourceId, int delta) {
switch (sourceTable) {
case "recipe_information":
recipeService.updatePraiseCount(sourceId, delta);
break;
case "cooking_videos":
cookingVideoService.updatePraiseCount(sourceId, delta);
break;
case "ranking_information":
rankingInfoService.updatePraiseCount(sourceId, delta);
break;
// 其他表处理...
}
}
评论功能实现:
java复制@PostMapping("/comment")
@PreAuthorize("hasRole('USER')")
public Result addComment(@RequestBody CommentDTO commentDTO) {
User currentUser = getCurrentUser();
// 创建评论
Comment comment = new Comment();
comment.setUserId(currentUser.getUserId());
comment.setSourceTable(commentDTO.getSourceTable());
comment.setSourceId(commentDTO.getSourceId());
comment.setContent(commentDTO.getContent());
comment.setNickname(currentUser.getNickname());
comment.setAvatar(currentUser.getAvatar());
// 如果是回复评论
if (commentDTO.getReplyToId() != null && commentDTO.getReplyToId() > 0) {
comment.setReplyToId(commentDTO.getReplyToId());
}
if (commentService.save(comment)) {
// 更新评论数
updateCommentCount(commentDTO.getSourceTable(), commentDTO.getSourceId(), 1);
return Result.success("评论成功", comment);
} else {
return Result.error("评论失败");
}
}
4. 实用工具模块实现
4.1 食材计算器
食材计算器是一个实用功能,可以根据用餐人数自动计算所需食材量。
前端实现:
vue复制<template>
<div class="calculator-container">
<el-form :model="form" label-width="120px">
<el-form-item label="姓名">
<el-input v-model="form.userName" placeholder="请输入您的姓名"></el-input>
</el-form-item>
<el-form-item label="食用人数">
<el-input-number
v-model="form.numberOfPeople"
:min="1"
:max="20"
placeholder="请输入用餐人数"></el-input-number>
</el-form-item>
<el-form-item label="食材内容">
<el-input
type="textarea"
:rows="5"
v-model="form.ingredientsContent"
placeholder="请输入食材清单,每行一种食材,格式:食材名称 基准量(如:大米 100g)">
</el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="calculate">计算</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
<div v-if="resultVisible" class="result-container">
<h3>计算结果</h3>
<el-table :data="resultData" border style="width: 100%">
<el-table-column prop="name" label="食材名称" width="180"></el-table-column>
<el-table-column prop="baseAmount" label="基准量"></el-table-column>
<el-table-column prop="calculatedAmount" label="计算用量"></el-table-column>
</el-table>
</div>
</div>
</template>
<script>
export default {
data() {
return {
form: {
userName: '',
numberOfPeople: 1,
ingredientsContent: ''
},
resultVisible: false,
resultData: []
}
},
methods: {
calculate() {
if (!this.form.ingredientsContent.trim()) {
this.$message.error('请输入食材内容');
return;
}
this.$axios.post('/api/calculator/calculate', this.form)
.then(response => {
this.resultData = response.data.data;
this.resultVisible = true;
this.$message.success('计算成功');
})
.catch(error => {
this.$message.error('计算失败: ' + error.message);
});
},
reset() {
this.form = {
userName: '',
numberOfPeople: 1,
ingredientsContent: ''
};
this.resultVisible = false;
}
}
}
</script>
后端实现:
java复制@RestController
@RequestMapping("/api/calculator")
public class IngredientCalculatorController {
@Autowired
private IngredientCalculatorService calculatorService;
@PostMapping("/calculate")
public Result calculate(@RequestBody IngredientCalculatorDTO calculatorDTO) {
try {
// 解析食材内容
List<Ingredient> ingredients = parseIngredients(calculatorDTO.getIngredientsContent());
// 计算用量
List<IngredientResult> results = calculatorService.calculate(
ingredients,
calculatorDTO.getNumberOfPeople());
// 保存计算记录
if (StringUtils.isNotBlank(calculatorDTO.getUserName())) {
saveCalculationRecord(calculatorDTO, results);
}
return Result.success(results);
} catch (Exception e) {
return Result.error("计算失败: " + e.getMessage());
}
}
private List<Ingredient> parseIngredients(String content) {
List<Ingredient> ingredients = new ArrayList<>();
String[] lines = content.split("\n");
for (String line : lines) {
line = line.trim();
if (line.isEmpty()) continue;
// 解析每行食材,格式:名称 基准量(如:大米 100g)
String[] parts = line.split("\\s+", 2);
if (parts.length != 2) {
throw new IllegalArgumentException("食材格式不正确: " + line);
}
Ingredient ingredient = new Ingredient();
ingredient.setName(parts[0]);
ingredient.setBaseAmount(parts[1]);
ingredients.add(ingredient);
}
return ingredients;
}
private void saveCalculationRecord(IngredientCalculatorDTO dto, List<IngredientResult> results) {
IngredientCalculator record = new IngredientCalculator();
record.setUserInformation(getCurrentUserId());
record.setUserName(dto.getUserName());
record.setNumberOfPeopleConsuming(String.valueOf(dto.getNumberOfPeople()));
// 构建食材内容字符串
StringBuilder ingredientsContent = new StringBuilder();
for (Ingredient ingredient : results) {
ingredientsContent.append(ingredient.getName())
.append(" ")
.append(ingredient.getBaseAmount())
.append("\n");
}
record.setIngredientsContent(ingredientsContent.toString());
// 构建计算结果字符串
StringBuilder calculatedUsage = new StringBuilder();
for (IngredientResult result : results) {
calculatedUsage.append(result.getName())
.append(" ")
.append(result.getCalculatedAmount())
.append("\n");
}
record.setCalculateUsage(calculatedUsage.toString());
calculatorService.save(record);
}
}
4.2 营养分析工具
营养分析工具可以根据用户输入的食材,估算菜品的营养成分。
实现逻辑:
java复制@Service
public class NutritionalAnalysisServiceImpl implements NutritionalAnalysisService {
@Autowired
private IngredientNutritionRepository nutritionRepo;
@Override
public NutritionalAnalysisResult analyze(String gender, List<String> ingredients) {
NutritionalAnalysisResult result = new NutritionalAnalysisResult();
// 根据性别设置基础代谢率
double bmr = "男".equals(gender) ? 1700 : 1500;
result.setBaseMetabolicRate(bmr);
// 分析每种食材的营养成分
Map<String, NutritionalInfo> nutritionMap = new HashMap<>();
double totalCalories = 0;
double totalProtein = 0;
double totalFat = 0;
double totalCarbohydrate = 0;
for (String ingredient : ingredients) {
// 从数据库获取食材营养信息
IngredientNutrition nutrition = nutritionRepo.findByName(ingredient);
if (nutrition != null) {
NutritionalInfo info = new NutritionalInfo();
info.setCalories(nutrition.getCalories());
info.setProtein(nutrition.getProtein());
info.setFat(nutrition.getFat());
info.setCarbohydrate(nutrition.getCarbohydrate());
nutritionMap.put(ingredient, info);
totalCalories += nutrition.getCalories();
totalProtein += nutrition.getProtein();
totalFat += nutrition.getFat();
totalCarbohydrate += nutrition.getCarbohydrate();
}
}
result.setIngredientsNutrition(nutritionMap);
result.setTotalCalories(totalCalories);
result.setTotalProtein(totalProtein);
result.setTotalFat(totalFat);
result.setTotalCarbohydrate(totalCarbohydrate);
// 计算占每日推荐量的百分比
result.setCaloriesPercentage(totalCalories / bmr * 100);
result.setProteinPercentage(totalProtein / ("男".equals(gender) ? 60 : 50) * 100);
result.setFatPercentage(totalFat / ("男".equals(gender) ? 70 : 60) * 100);
result.setCarbohydratePercentage(totalCarbohydrate / 300 * 100);
// 生成健康建议
result.setHealthAdvice(generateHealthAdvice(result));
return result;
}
private String generateHealthAdvice(NutritionalAnalysisResult result) {
StringBuilder advice = new StringBuilder();
// 热量建议
if (result.getCaloriesPercentage() > 120) {
advice.append("当前菜品热量较高,建议减少高热量食材或减少食用量。");
} else if (result.getCaloriesPercentage() < 80) {
advice.append("当前菜品热量适中,可以适当增加一些高营养食材。");
} else {
advice.append("当前菜品热量适中,营养均衡。");
}
// 蛋白质建议
if (result.getProteinPercentage() > 120) {
advice.append("蛋白质含量较高,适合增肌人群。");
} else if (result.getProteinPercentage() < 80) {
advice.append("可以适当增加一些优质蛋白质来源如瘦肉、豆制品等。");
}
// 脂肪建议
if (result.getFatPercentage() > 120) {
advice.append("脂肪含量偏高,建议减少油炸食品和高脂肪食材。");
}
// 碳水化合物建议
if (result.getCarbohydratePercentage() > 120) {
advice.append("碳水化合物含量较高,糖尿病患者需谨慎食用。");
}
return advice.toString();
}
}
5. 系统安全与性能优化
5.1 安全防护措施
-
认证与授权:
- 使用Spring Security实现基于角色的访问控制
- JWT令牌认证,避免会话状态维护
- 密码加密存储(BCrypt)
-
数据安全:
- SQL注入防护(MyBatis参数化查询)
- XSS防护(前端过滤+后端转义)
- CSRF防护(Spring Security默认启用)
-
API安全:
- 接口限流(Redis实现)
- 敏感操作日志记录
- 重要接口签名验证
安全配置示例:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/recipes/**").permitAll()
.antMatchers("/api/user/register").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
5.2 性能优化策略
-
缓存策略:
- Redis缓存热点数据(菜谱列表、排行信息等)
- 本地缓存频繁访问的用户信息
- 缓存穿透防护(布隆过滤器)
-
数据库优化:
- 合理设计索引(特别是查询频繁的字段)
- 分库分表设计(用户数据与内容数据分离)
- 读写分离(主从复制)
-
前端优化:
- 组件懒加载
- 图片懒加载
- 数据分页加载
缓存实现示例:
java复制@Service
public class RecipeServiceImpl implements RecipeService {
@Autowired
private RecipeMapper recipeMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String RECIPE_CACHE_PREFIX = "recipe:";
private static final String RECIPE_LIST_CACHE_KEY = "recipe:list:";
@Override
@Cacheable(value = "recipe", key = "#id")
public RecipeInformation getById(Integer id) {
return recipeMapper.selectById(id);
}
@Override
@Cacheable(value = "recipeList", key = "#type + ':' + #page + ':' + #size")
public List<RecipeInformation> getListByType(String type, Integer page, Integer size) {
QueryWrapper<RecipeInformation> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(type)) {
queryWrapper.eq("recipe_type", type);
}
queryWrapper.orderByDesc("create_time");
Page<RecipeInformation> pageInfo = new Page<>(page, size);
IPage<RecipeInformation> recipePage = recipeMapper.selectPage(pageInfo, queryWrapper);
return recipePage.getRecords();
}
@Override
@CacheEvict(value = {"recipe", "recipeList"}, allEntries = true)
public boolean updateRecipe(RecipeInformation recipe) {
return recipeMapper.updateById(recipe) > 0;
}
@Override
public List<RecipeInformation> getHotRecipes(int limit) {
String cacheKey = "recipe:hot:" + limit;
List<RecipeInformation> cached = (List<RecipeInformation>) redisTemplate.opsForValue().get(cacheKey);
if (cached != null) {
return cached;
}
QueryWrapper<RecipeInformation> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("praise_len", "hits")
.last("limit " + limit);
List<RecipeInformation> hotRecipes = recipeMapper.selectList(queryWrapper);
// 缓存10分钟
redisTemplate.opsForValue().set(cacheKey, hotRecipes, 10, TimeUnit.MINUTES);
return hotRecipes;
}
}
6. 系统测试与部署
6.1 测试策略与方法
-
单元测试:
- 使用JUnit测试业务逻辑
- Mockito模拟依赖组件
-
集成测试:
- 测试API接口
- 测试数据库交互
-
前端测试:
- Jest单元测试
- Cypress端到端测试
-
性能测试:
- JMeter模拟高并发场景
- 测试系统吞吐量和响应时间
测试示例:
java复制@SpringBootTest
public class RecipeServiceTest {
@Autowired
private RecipeService recipeService;
@MockBean
private RecipeMapper recipeMapper;
@Test
public void testGetRecipeById() {
// 准备测试数据
RecipeInformation mockRecipe = new RecipeInformation();
mockRecipe.setRecipeId(1);
mockRecipe.setRecipeName("测试菜谱");
// 模拟Mapper行为
when(recipeMapper.selectById(1)).thenReturn(mockRecipe);
// 调用测试方法
RecipeInformation result = recipeService.getById(1);
// 验证结果
assertNotNull(result);
assertEquals("测试菜谱", result.getRecipeName());
// 验证交互
verify(recipeMapper, times(1)).selectById(1);
}
@Test
public void testSearchRecipes() {
// 准备测试数据
List<RecipeInformation> mockList = new ArrayList<>();
RecipeInformation recipe1 = new RecipeInformation();
recipe1.setRecipeId(1);
recipe1.setRecipeName("红烧肉");
mockList.add(recipe1);
// 模拟Mapper行为
when(recipeMapper.selectList(any())).thenReturn(mockList);
// 调用测试方法
List<RecipeInformation> results = recipeService.searchRecipes("红烧", null, null, 1, 10);
// 验证结果
assertEquals(1, results.size());
assertEquals("红烧肉", results.get(0).getRecipeName());
}
}
6.2 部署方案
-
后端部署:
- 使用Docker容器化部署
- Nginx反向代理和负载均衡
- 多实例部署保证高可用
-
前端部署:
- 静态资源部署到CDN
- Nginx托管前端应用
-
数据库部署:
- 主从复制配置
- 定期备份策略
Docker部署示例:
dockerfile复制# 后端Dockerfile
FROM openjdk:11-jdk
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
dockerfile复制# 前端Dockerfile
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx复制# Nginx配置示例
upstream backend {
server backend1:8080;
server backend2:8080;
}
server {
listen 80;
server_name example.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
7. 项目总结与展望
7.1 项目成果
通过这个项目的开发,我实现了以下目标:
- 完成了功能完善的菜谱交流平台,包含前后端所有功能模块
- 实践了现代化的Web开发技术栈(SpringBoot+Vue)
- 掌握了系统架构设计、数据库设计和性能优化的方法
- 实现了完整的开发流程,包括需求分析、设计、编码、测试和部署
7.2 经验总结
在开发过程中,我积累了一些宝贵的经验:
- 技术选型:选择成熟稳定的技术栈可以大大提高开发效率
- 模块化设计:良好的模块划分使系统更易于维护和扩展
- 代码规范:统一的代码风格和注释规范有助于团队协作
- 测试驱动:完善的测试用例可以及早发现问题,减少后期修复成本
7.3 未来改进方向
虽然项目已经实现了基本功能,但仍有改进空间:
- 移动端适配:开发专门的移动应用或PWA版本
- AI推荐:引入机器学习算法实现个性化菜谱推荐
- 社交功能增强:增加用户关注、私信等社交功能
- 国际化支持:支持多语言,拓展国际市场
在实际开发中,我发现前后端分离架构确实带来了很多优势,如前后端可以并行开发、技术栈选择更灵活等。但同时也带来了一些挑战,如接口文档维护、跨域问题处理等。通过这个项目,我对这些问题都有了更深入的理解和解决方案。