ARTICLE DETAIL

资讯详情

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

QQ村旅游网站实战:Java+SSM与Flask双后端架构设计全解析

QQ村旅游网站实战:Java+SSM与Flask双后端架构设计全解析 做了几年Java后端也在学校接过不少课程设计和毕业设计类的项目我发现旅游网站这类题目是出现频率最高的几种之一。原因也简单业务模块清晰、技术栈覆盖全面、又有真实的应用场景可以讲清楚。但这次遇到的“QQ村旅游网站”不太一样它没有走“Java一套到底”的老路而是用了JavaSSM做核心业务同时引入了Flask做轻量服务端这个搭配值得好好拆一拆。先交代一下这个项目是干什么的。QQ村是一个典型的乡村旅游目的地整个网站围绕游客行前、行中、行后三个环节做了景点展示、旅游线路推荐、攻略文章、地图导览、特色介绍和酒店预订六类核心内容。管理员在后台维护这些数据游客在前台浏览、搜索、下单收藏属于很标准的内容管理轻交易型Web应用。技术选型上SSM负责主体业务和后台管理Flask负责推荐、查询类轻接口和部分数据聚合逻辑两者通过HTTP接口通信。这套方案非常适合学习Java企业级开发又想了解Python Web的读者也适合拿来作为课程设计或毕业设计的原型参考。我要讲的内容不只是把模块列一遍而是会从架构设计的角度把这个项目为什么这么拆、每一步怎么做、部署时容易踩哪些坑全部讲透。哪怕你现在只学过Java基础只要能照着思路走也可以自己复现一个能跑起来、能答辩、能演示的完整版本。1. 项目整体设计与模块拆分1.1 QQ村旅游网站的核心需求解析做这类项目第一步不是写代码而是把业务需求拆清楚。QQ村旅游网站的用户只有两类游客和管理员。游客端要解决的核心问题是“到了QQ村怎么玩、怎么住、吃什么特色”所以前台的核心页面就围绕这几个信息需求展开——景点列表页要能看到每个景点的图片、简介和开放时间线路页要把多个景点串成半天游、一日游的行程攻略页是图文混排的文章内容地图页则需要景点位置标记和线路走向酒店页展示房型价格并支持预订。管理员端的核心需求则简单得多维护以上所有内容的增删改查处理订单统计访问量。想清楚这两类需求后数据库表就能很自然地设计出来控制器路由也能直接对应页面跳转。很多同学做课程设计喜欢上来就建表做到后面发现字段不够用再回头改表改代码来回折腾。我个人的习惯是先把页面原型画出来哪怕是纸上的草图页面上有哪些字段就建哪些字段这样数据库设计一次就能到位。1.2 为什么是JavaSSMFlask而不是单一技术栈先说说SSM。SSM是SpringSpringMVCMyBatis的组合在国内企业级开发里占有率很高招聘市场上这些岗位也多所以毕设和课程设计选它对找工作也有直接帮助。Spring负责对象管理和事务SpringMVC负责Web层的请求分发MyBatis负责数据库操作三层配合很成熟。对于景点、线路、订单这类事务性和一致性要求高的业务用Java这套体系来写稳定性是有保障的。那为什么还要加一个Flask核心原因有两点。第一旅游网站的攻略推荐和景点匹配本质上是内容聚合和按标签、关键词、热度做排序推荐这类逻辑用Python写起来比Java简洁得多代码量能少一半以上。第二Flask是一个非常轻量的Web框架一个脚本文件就能启动一个服务很适合做独立的数据接口层给前台页面提供推荐结果、搜索补全和统计图表数据。也就是说SSM管“重业务”Flask管“轻接口”各干各擅长的事再用HTTP请求把二者串起来。这个组合也可以理解成餐厅的厨房配置SSM是负责正菜出品的主厨团队流程规范能应对高峰期的复杂订单Flask是专门做甜品和饮品的那位厨师动作快、灵活能把搭配和推荐这类事情做得更精致。两者不冲突各有各的菜单窗口而已。1.3 功能模块全景与前后台划分整个网站从功能上分成两大块我在设计文档里画了一张功能清单表这里直接放出来给大家参考功能域功能点说明技术侧重前台-景点景点列表、景点详情、景点搜索分页、按分类筛选SSMJSP/HTML前台-线路一日游、半日游、主题游线路景点组合、图文介绍SSM前台-攻略攻略文章列表、详情、分类图文混排、按热度推荐SSMFlask推荐接口前台-地图景点位置标注、线路连线调用地图JS APIFlask提供坐标数据前台-特色特产、民俗、节庆展示分类卡片展示SSM前台-酒店酒店列表、房型、预订订单生成、状态流转SSMMysql后台-管理内容管理、订单管理、数据统计管理员登录、CRUDSSMECharts辅助-推荐热门景点推荐、相似攻略推荐标签匹配、热度排序Flask独立服务这张表的作用不只是给需求评审用更重要的是定接口。每一个功能域都能对应到一组合约明确的HTTP接口后端开发和前端页面调用各自按接口并行效率高很多。2. SSM核心架构与Flask服务的设计细节2.1 SSM框架整合的关键配置SSM整合是这类项目最容易卡住的一步很多新手在配置文件上就要折腾两三天。我建议直接用Maven构建通过pom.xml统一管理依赖版本避免Spring和MyBatis版本冲突。核心依赖就是spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、jackson-databind、javax.servlet-api这几个版本选择上不需要追新稳定即可。配置文件方面至少要准备三个applicationContext.xml、springmvc.xml和jdbc.properties。applicationContext.xml里配置数据源、SqlSessionFactoryBean和Mapper扫描同时开启事务管理器。springmvc.xml里配置组件扫描、注解驱动和视图解析器。这里有一个很多人会忽略的细节Spring容器和SpringMVC容器扫描的包一定要分开Spring容器扫service和daoSpringMVC容器只扫controller否则会出现事务失效或者Bean重复创建的问题。这也是我在跑项目时踩过的一个坑后面会在常见问题里细说。MyBatis这边我习惯使用Mapper接口加XML文件的方式好处是SQL语句清晰可维护尤其是景点列表这种需要动态多条件查询的页面用where加if标签动态拼接条件非常方便比Java代码里拼SQL字符串要安全得多也不用担心SQL注入。2.2 SSM常用注解在项目中的实际应用SSM面试题里经常考注解但实际项目中到底哪些注解最常用很多新手并清楚。我按使用频率排个序Controller和RestController用于标注控制器Service标注业务层Repository标注DAO层Autowired做依赖注入按类型自动装配RequestMapping及其衍生注解GetMapping、PostMapping绑定URL路由PathVariable和RequestParam做参数绑定Transactional在订单生成这类写操作上声明事务。在QQ村旅游网站里景点控制器的典型写法是这样的Controller RequestMapping(/scenic) public class ScenicController { Autowired private ScenicService scenicService; GetMapping(/list) public String list(RequestParam(defaultValue 1) Integer page, RequestParam(required false) String category, Model model) { PageResultScenic result scenicService.pageQuery(page, 10, category); model.addAttribute(result, result); return scenic/list; } GetMapping(/detail/{id}) public String detail(PathVariable Integer id, Model model) { Scenic scenic scenicService.getById(id); model.addAttribute(scenic, scenic); return scenic/detail; } }注意这里的Model传值方式它会把后端数据绑定到JSP页面上前端通过EL表达式${scenic.name}直接取值。如果走前后端分离就改成RestController配合ResponseBody返回JSON给前端Ajax调用。很多课程设计里两种混用容易乱我的习惯是页面跳转用传统ControllerModel接口数据统一用RestControllerJSON各司其职。2.3 Flask在系统里到底扮演什么角色Flask在这套架构里的定位一开始就要定清楚它不是用来替代SSM的而是提供一个数据聚合和推荐的服务层。我在项目里用Flask实现了三个功能热门景点推荐接口、攻略关键词匹配推荐、地图数据接口。前两个都是读数据库然后算权重返回JSON第三个是按景点ID返回经纬度坐标和线路连线数据。这三个功能用Flask写非常简洁。比如热门景点推荐核心逻辑就是按景点热度评分排序再加标签过滤Python代码十几行就完成了from flask import Flask, jsonify, request import pymysql app Flask(__name__) def get_db(): conn pymysql.connect( host127.0.0.1, userroot, password123456, databaseqq_travel, charsetutf8mb4 ) return conn app.route(/api/recommend/scenic, methods[GET]) def recommend_scenic(): category request.args.get(category, ) conn get_db() cursor conn.cursor() if category: sql (SELECT id, name, cover, score, heat FROM scenic WHERE category%s ORDER BY heat DESC LIMIT 6) cursor.execute(sql, (category,)) else: sql SELECT id, name, cover, score, heat FROM scenic ORDER BY heat DESC LIMIT 6 cursor.execute(sql) rows cursor.fetchall() result [ {id: r[0], name: r[1], cover: r[2], score: float(r[3]), heat: r[4]} for r in rows ] cursor.close() conn.close() return jsonify({code: 0, data: result}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)有人会问这个接口用Java写不也一样能实现吗没错能实现但代价是写一个Service加一个Mapper XML再加一个Controller代码量是Flask的三倍左右。对于推荐这种逻辑经常要调整的功能Python改起来快调试成本低。Flask只是把你需要的数据通过HTTP接口暴露出来SSM的页面直接Ajax请求http://localhost:5000/api/recommend/scenic两边是彻底的解耦状态任何一边改动都不影响另一边。2.4 数据库设计与数据流关系数据库设计是整个项目的地基这一步没做好后面所有功能都会受影响。QQ村旅游网站我建了这些核心表用户表、景点表、线路表、线路景点关联表、攻略表、酒店表、订单表、收藏表、管理员表。景点表是内容核心字段包括景点名称、所属分类、封面图、详细介绍、开放时间、建议游玩时长、热度、评分、经度、纬度。有了经纬度地图标注功能就能直接用了。线路表的设计要注意多对多关系一条线路包含多个景点一个景点也属于多条线路所以要建一张关联表。酒店表则要拆出房型表和订单表一个酒店多个房型一个房型多条订单这样后面统计各房型的销售情况就方便了。订单表里我会额外加一个status字段用int类型表示订单状态0待支付、1已支付、2已完成、3已取消状态流转逻辑在Service层统一处理前端只负责展示文案。数据库连接这块SSM用的是连接池我在项目里用了阿里的Druid配置很简单但要注意在jdbc.properties里加上useUnicodetruecharacterEncodingutf8否则存中文进MySQL会出现乱码。Flask那边直接用PyMySQL连接断开要记得关闭否则并发一高就会报“Too many connections”的错误。3. 核心业务模块的实现与实操记录3.1 景点与线路模块分页、搜索与组合景点模块是整个网站访问量最大的页面所以列表页的查询效率要重视。我用MyBatis写了一个动态SQL支持按名称模糊搜索、按分类筛选、按热度排序再配合PageHelper分页插件一个方法就能覆盖所有查询场景。PageHelper的用法很简单在Service调用前设置PageHelper.startPage(pageNum, pageSize)紧接着的查询语句就会被自动拦截并生成分页SQL返回PageInfo对象后可以直接拿到总记录数和当前页数据。线路模块的实现逻辑稍微复杂一些。一条旅游线路比如“QQ村经典一日游”包含景点A、景点B、景点C还要有预计游玩时长和步行或车程衔接说明。我在设计时给线路表加了duration字段和transport字段前者记录总时长后者记录景点间交通方式这样页面能直观展示“09:00-11:00 参观A11:20-11:40 步行至B”这样的行程时间轴效果。3.2 攻略与地图模块推荐算法与坐标展示攻略模块使用了Flask的关键词匹配接口来实现“相关攻略推荐”。大概思路是这样攻略表里每条记录都有标签字段比如“亲子游”“摄影”“美食”用户在详情页浏览时前端把当前攻略的标签传到Flask接口接口查找标签重叠度最高且热度最高的其他攻略返回3条作为“相关阅读”。实现上就是Python里对标签做交集计算再排序代码非常直白。地图模块是QQ村旅游网站的亮点。我的实现方案是在后台录入景点时同步录入经纬度坐标地图页面加载时通过Flask接口一次性获取所有景点坐标然后调用地图JS API进行标注和连线。这里注意一定不要在前端页面里硬编码坐标数据否则后台修改了景点位置页面刷新还是不刷新必须依赖接口动态获取。地图API的key相对固定社区的潜规则是演示项目用测试key就够了。3.3 酒店预订模块事务与订单状态管理酒店预订模块是最能体现工程经验的地方因为涉及订单事务和状态一致性。游客提交预订时系统要做三件事检查房型剩余数量、扣减库存、生成订单记录。这三件事必须在一个事务里完成任何一个失败都要全部回滚否则会出现订单生成了但库存没扣或者库存扣了但订单没生成的数据不一致问题。实现时我在Service层的bookRoom方法上加Transactional注解方法内依次调用库存检查和更新、订单插入逻辑。这里要特别强调两点。第一库存字段要在SQL里带上库存判断条件比如UPDATE room_type SET stock stock - 1 WHERE id ? AND stock 0返回影响行数为0说明没库存了直接抛异常回滚。第二事务方法内部不能捕获异常后吞掉必须让事务管理器感知到运行时异常才会回滚否则就成“假事务”了。Transactional(rollbackFor Exception.class) public Order bookRoom(Integer userId, Integer roomTypeId, Date checkIn, Date checkOut) { RoomType room roomTypeMapper.selectByIdForUpdate(roomTypeId); if (room.getStock() 0) { throw new BusinessException(该房型已满房); } int rows roomTypeMapper.deductStock(roomTypeId); if (rows 0) { throw new BusinessException(该房型已满房); } // 计算金额生成订单 Order order new Order(); // ... 设置订单字段 orderMapper.insert(order); return order; }3.4 用户登录与权限拦截Session设计与密码安全用户登录这块我使用的是SessionCookie的传统方案没有引入Spring Security因为对这个规模的项目来说一套简单的拦截器就足够了。密码存储不能明文我用的是MD5加盐的方式注册时生成一段随机字符串作为盐把密码和盐拼起来做MD5数据库中同时存盐和密文。校验登录时再取盐重算比对。虽然现在业界推荐BCrypt但课程设计里用加盐MD5也已经能讲清楚安全思路面试时能说出为什么不能明文存储就是一个加分项。权限控制方面我在SpringMVC配置里注册了一个拦截器拦截路径是/admin/**放行路径是/admin/login和所有前台静态资源。拦截器里检查Session中是否存在adminUser对象不存在就重定向到登录页。注意前台游客访问景点、攻略、酒店列表都不需要登录只有提交订单和收藏时才要求登录。4. 本地部署调试与常见问题排查实录4.1 从零到跑通本地部署的完整操作流程拿到源码后本地部署我一般按这个顺序走基本不会出问题。第一步准备环境安装JDK 1.8、Maven 3.6、MySQL 5.7、Python 3.8并配置好环境变量。第二步初始化数据库用Navicat或命令行执行项目里的qq_travel.sql脚本建库建表并插入初始数据。第三步导入Java后端到IDEA等待Maven下载依赖后修改jdbc.properties里的数据库用户名和密码配置Tomcat启动项目Java后端默认跑在8080端口。第四步进入Flask目录创建虚拟环境后pip install flask pymysql修改db连接配置然后运行python app.pyFlask服务跑在5000端口。部署顺序上我建议先启动MySQL再启动Java后端最后启动Flask。如果Java后端启动时报数据库连接错误八成是MySQL没有启动或者密码没改对。如果Flask接口请求超时或返回500先检查MySQL是否允许远程连接本地一般默认是允许的主要问题还是在端口或密码上。4.2 我踩过的三个高频坑乱码、跨域和端口占用乱码是这类项目最见鬼的问题现象是页面上看到一堆问号或乱码。我的排查顺序是固定的先确认数据库表字符集是不是utf8mb4再检查jdbc连接串有没有加characterEncodingutf8然后用Postman直接请求后端接口看返回数据是否乱码。如果接口返回乱码就是后端编码问题如果接口正常而页面乱码就是JSP页面编码问题在JSP顶部加上% page pageEncodingUTF-8 %即可解决。跨域问题是加了Flask之后才出现的。SSM的页面跑在8080端口Flask接口跑在5000端口浏览器同源策略直接让Ajax请求失败。解决办法有两个一是在Flask这边启用CORS二是在Java后端做接口转发。我推荐前者因为Flask只开一个接口、配置极简from flask_cors import CORS CORS(app)端口占用也很常见。Tomcat默认8080端口被占时启动日志会显示Port already in use改成9090就行。Flask的5000端口被占时把app.run里的port改成5001同时前端请求地址同步改。这里建议用IDE的控制台看日志比黑窗口直观得多。4.3 常见报错与排查速查表我把项目运行中遇到的典型报错整理成了表格分享给各位照着排查至少能节省一天时间。报错现象可能原因解决方案启动报BeanCreationExceptionMapper接口没有被扫描检查applicationContext.xml中mybatis:scan包路径是否正确页面上EL表达式原样输出JSP没有引入JSTL标签库pom.xml添加jstl依赖页面头部引入% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %接口返回401/无权限自定义拦截器拦截了路由检查拦截器放行路径是否遗漏了Flask接口对应的静态资源MySQL连接拒绝账号密码错误或MySQL未启动命令行测试连接确认参数无误中文插入数据库后是问号连接串缺少字符集参数jdbc连接串加useUnicodetruecharacterEncodingutf8Flask启动报ImportError依赖未安装完整确认pip install flask flask-cors pymysql全部执行成功404错误且Tomcat无日志映射路径和请求URL不一致检查类上RequestMapping与方法上注解拼接后的完整路径4.4 调试技巧与效率提升经验调试这类前后端混构项目我最推荐的工具组合是Postman加浏览器开发者工具。每个后端接口写完我都要在Postman里先跑通再交给前端对接这样能彻底分清问题在前端还是后端。页面调试时打开开发者工具的Network面板看具体是哪个请求返回了500或4xx响应体里通常有异常信息比盲猜高效得多。日志输出也是排查利器。Java后端我习惯在关键方法里打印入参和出参通过logback日志配置文件把SQL执行日志打出来MyBatis执行了什么SQL一目了然。Flask那边直接使用app.logger.info()记录请求参数和返回结果Python端的print在调试时更好用但运行时要记得注释掉。这看起来是小问题但很多同学项目跑不起来最后发现就是日志看不到关键信息白白浪费时间。5. 项目扩展与我的实际体会最后聊几点实际做完这个项目的体会。双后端架构一开始会让人觉得多此一举但真正部署完、把两边的接口调通之后你会体会到模块化架构带来的好处改动推荐逻辑不用动主业务代码新功能可以快速用Python脚本验证这种模式其实是很多大厂后端隔离复杂业务和简单查询的常规做法提前接触完全不亏。在代码组织上我强烈建议把前端页面统一放到webapp目录按scenic、route、guide、hotel建子目录一个模块一个文件夹。JSP命名要和后端Controller跳转逻辑一致省得后面维护时找不到文件。Flask服务的代码也按蓝图Blueprint来组织不要全堆在一个app.py里蓝图为每个功能域提供独立路由模块结构清晰后续加接口只加文件不改主入口。这个项目做完后可以扩展的方向还挺多比如把静态内容放对象存储、把订单模块对接真正支付网关、用Redis缓存热门景点列表、在Flask里做更复杂的协同过滤推荐。每一步都能单独展开成一篇文章但核心骨架就是前面讲的这套SSMFlask双后端架构。如果你现在正打算拿这个项目做课程设计或者毕业设计我的建议是先把数据库脚本跑起来、把SSM页面清单页浏览通再考虑Flask的推荐接口。先把主链路走通再完善细节整个过程你会对Java Web开发和Python Web开发都有更深的理解。这套流程我跑过很多遍按这个顺序推进是最稳妥也最高效的。
返回列表