ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue3美食分享平台架构设计与实践

SpringBoot+Vue3美食分享平台架构设计与实践 1. 项目概述与核心价值这个美食分享平台系统采用SpringBootVue的前后端分离架构是一个典型的Web 2.0应用。我在实际开发中发现这类系统最核心的价值在于解决了三个痛点一是让普通用户能够零门槛发布和发现美食内容二是通过社交化功能形成用户粘性三是为餐饮从业者提供精准的展示渠道。系统包含用户端和管理端两个维度。用户端主要功能有图文食谱发布与浏览支持Markdown格式地理位置标记与附近美食发现用户互动点赞/收藏/评论个性化推荐feed流管理端则包含内容审核工作流用户行为数据分析看板敏感词过滤系统广告位管理系统技术选型上特别选择了SpringBoot 2.7 Vue3的组合这个搭配在2023年新项目中已经成为主流选择。相比传统的SSM架构启动速度提升40%以上内存占用减少约30%。2. 技术架构详解2.1 后端SpringBoot设计采用经典的三层架构但做了些优化调整com.foodshare ├── config # 配置类 ├── controller # 暴露的API接口 ├── service # 业务逻辑 │ ├── impl # 实现类 ├── dao # 数据访问层 ├── entity # 实体类 ├── util # 工具包 └── exception # 异常处理数据库使用MySQL 8.0有几个关键设计点用户表采用纵向分表将基础信息与扩展信息分离内容表使用JSON类型存储富文本内容建立复合索引 (user_id, create_time) 优化查询缓存策略值得特别说明Cacheable(value recipes, key #id, unless #result null) public Recipe getRecipeById(Long id) { return recipeMapper.selectById(id); }采用Redis二级缓存对热点数据设置不同的过期时间食谱详情30分钟用户信息24小时排行榜数据5分钟2.2 前端Vue3实现使用Vite作为构建工具相比传统Webpack冷启动时间从45s降至1.8sHMR更新速度提升3-5倍核心组件设计template RecipeCard v-foritem in recipeList :keyitem.id :dataitem likehandleLike / /template script setup // 使用Composition API const recipeList ref([]) const loading ref(false) const fetchData async () { loading.value true try { const res await axios.get(/api/recipes) recipeList.value res.data } finally { loading.value false } } /script状态管理采用Pinia替代Vuex// stores/recipe.js export const useRecipeStore defineStore(recipe, { state: () ({ favorites: [] }), actions: { async addFavorite(id) { await api.favorite(id) this.favorites.push(id) } } })3. 关键功能实现3.1 美食内容发布流程采用阿里云OSS进行图片存储前端实现压缩上传const upload async (file) { const compressedFile await imageCompression(file, { maxSizeMB: 1, maxWidthOrHeight: 1920 }) const formData new FormData() formData.append(file, compressedFile) return axios.post(/api/upload, formData, { headers: { Content-Type: multipart/form-data } }) }后端使用Spring的MultipartFile接收PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return Result.fail(文件不能为空); } String fileName UUID.randomUUID() getFileExtension(file.getOriginalFilename()); String url ossClient.upload(file.getInputStream(), fileName); return Result.success(url); }3.2 智能推荐算法基于用户行为的协同过滤实现public ListRecipe recommend(Long userId) { // 1. 获取用户历史行为 ListUserBehavior behaviors behaviorMapper.selectByUser(userId); // 2. 计算相似用户 MapLong, Double similarUsers findSimilarUsers(behaviors); // 3. 生成推荐列表 return generateRecommendations(similarUsers); }采用Redis的SortedSet实现实时排行榜// 更新热度值 redisTemplate.opsForZSet().incrementScore( recipe:hot, recipeId, type.equals(view) ? 1 : (type.equals(like) ? 3 : 5) ); // 获取TOP10 SetLong topIds redisTemplate.opsForZSet() .reverseRange(recipe:hot, 0, 9);4. 部署方案详解4.1 后端部署采用Docker Compose编排version: 3 services: app: build: . ports: - 8080:8080 environment: - SPRING_PROFILES_ACTIVEprod depends_on: - redis - mysql mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysql redis: image: redis:6 ports: - 6379:6379生产环境建议配置JVM参数-Xms512m -Xmx1024m -XX:MaxMetaspaceSize256m使用Nginx做反向代理和负载均衡开启GZIP压缩节省带宽4.2 前端部署Vue项目构建优化// vite.config.js export default defineConfig({ build: { rollupOptions: { output: { manualChunks(id) { if (id.includes(node_modules)) { return vendor } } } } } })Nginx配置示例server { listen 80; server_name foodshare.example.com; gzip on; gzip_types text/plain application/xml text/css application/javascript; location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; } }5. 开发中的典型问题与解决方案5.1 跨域问题处理前后端分离开发时常见的CORS问题我们的解决方案Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }生产环境更安全的做法是配置具体的域名而非通配符启用CSRF保护设置严格的CORS策略5.2 大文件上传优化针对视频等大文件上传的解决方案前端分片每片2MB后端合并分片断点续传实现核心代码片段// 前端分片 const chunkSize 2 * 1024 * 1024 const chunks Math.ceil(file.size / chunkSize) for (let i 0; i chunks; i) { const chunk file.slice(i * chunkSize, (i 1) * chunkSize) await uploadChunk(chunk, i, file.name) }// 后端合并 public void mergeChunks(String fileName, int totalChunks) { File outputFile new File(uploadPath, fileName); try (FileOutputStream fos new FileOutputStream(outputFile)) { for (int i 0; i totalChunks; i) { File chunkFile new File(uploadPath, fileName .part i); Files.copy(chunkFile.toPath(), fos); chunkFile.delete(); } } }6. 系统安全加固方案6.1 认证与授权采用JWT Spring Security的方案Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers(/api/auth/**).permitAll() .anyRequest().authenticated() .and() .addFilter(new JwtAuthenticationFilter(authenticationManager())) .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); return http.build(); } }JWT工具类关键实现public String generateToken(UserDetails userDetails) { MapString, Object claims new HashMap(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); }6.2 敏感数据保护对用户密码等敏感信息处理// 使用BCryptPasswordEncoder Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 存储时加密 public void register(User user) { user.setPassword(passwordEncoder.encode(user.getPassword())); userMapper.insert(user); }数据库连接池加密配置spring.datasource.urljdbc:mysql://localhost:3306/foodshare spring.datasource.usernameroot spring.datasource.passwordENC(AES加密后的密文)7. 性能优化实践7.1 数据库优化通过EXPLAIN分析慢查询建立合适的索引-- 为食谱表建立复合索引 CREATE INDEX idx_recipe_user_time ON recipe(user_id, create_time); -- 分页查询优化 SELECT * FROM recipe WHERE status 1 ORDER BY create_time DESC LIMIT 20 OFFSET 0;使用Spring Data JPA的查询优化public interface RecipeRepository extends JpaRepositoryRecipe, Long { EntityGraph(attributePaths {user}) Query(SELECT r FROM Recipe r WHERE r.id :id) OptionalRecipe findWithUserById(Param(id) Long id); }7.2 前端性能提升实施懒加载和代码分割template img v-lazyimageUrl altrecipe image /template script setup const RecipeEditor defineAsyncComponent(() import(./components/RecipeEditor.vue) ) /script使用Web Worker处理CPU密集型任务// worker.js self.onmessage function(e) { const result heavyCalculation(e.data) self.postMessage(result) } // 主线程 const worker new Worker(./worker.js) worker.postMessage(data) worker.onmessage (e) { console.log(Result:, e.data) }8. 监控与运维方案8.1 应用监控集成Prometheus GrafanaConfiguration EnablePrometheusEndpoint EnableSpringBootMetricsCollector public class PrometheusConfig {}关键监控指标JVM内存使用接口响应时间数据库连接池状态缓存命中率8.2 日志收集使用ELK栈处理日志!-- logback-spring.xml -- appender nameLOGSTASH classnet.logstash.logback.appender.LogstashTcpSocketAppender destinationlogstash:5044/destination encoder classnet.logstash.logback.encoder.LogstashEncoder/ /appender日志规范建议统一日志格式区分不同级别日志记录关键业务操作避免记录敏感信息9. 扩展功能设计思路9.1 小程序端适配基于uni-app的多端适配方案// 条件编译 // #ifdef MP-WEIXIN wx.login({ success(res) { if (res.code) { // 获取微信code } } }) // #endif接口兼容处理GetMapping(/recipes) public ResultListRecipe listRecipes( RequestParam(required false) String platform) { if (wechat.equals(platform)) { // 返回小程序专用数据结构 } else { // 返回Web端数据结构 } }9.2 短视频功能集成使用FFmpeg处理视频public void processVideo(File input) { String cmd String.format(ffmpeg -i %s -vf scale720:-2 -c:v libx264 %s, input.getPath(), output.getPath()); Runtime.getRuntime().exec(cmd); }前端视频播放器实现template video-player :options{ controls: true, sources: [{ src: videoUrl, type: video/mp4 }] } / /template10. 项目演进路线10.1 技术债清理计划识别出的主要技术债缺乏完整的单元测试覆盖部分老代码使用过时的API文档不完善解决方案逐步补充单元测试目标覆盖率80%制定代码重构日历使用Swagger完善API文档10.2 架构演进方向未来可能的架构升级微服务化拆分引入消息队列削峰填谷实现多活部署灰度发布能力建设初期可以先从简单的开始// 使用Spring Cloud Stream集成RabbitMQ EnableBinding(Source.class) public class MessageProducer { Autowired private Source source; public void sendNotification(Notification notification) { source.output().send(MessageBuilder .withPayload(notification) .build()); } }在开发这个系统的过程中我发现最难的不是技术实现而是平衡用户体验与系统性能。比如在推荐算法场景实时性要求与计算复杂度之间就需要反复权衡。最终我们采用了近实时计算本地缓存的折中方案既保证了用户体验又避免了系统过载。
返回列表