ARTICLE DETAIL

资讯详情

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

Python+Vue3前后端分离的校园商家点评系统开发实战

Python+Vue3前后端分离的校园商家点评系统开发实战 前阵子在学校社团接了个人情活学校周边的商家有一百多家食堂窗口、奶茶店、打印店、小吃摊全算上新生完全靠学长口口相传。社团原本想搞个共享Excel让同学填评价被我劝住了——点评这类数据用Excel维护是一场灾难重复、乱填、没法聚合统计。最后决定干脆做个正式的校园商家消费点评系统。项目编号就叫python062后端用python写接口前端用vue3做页面前后端分离从零搭到能上线演示前后用了一个多月。这篇就把从需求、建模、接口、页面到联调部署的完整过程整理出来尤其是那些只看教程学不到的设计取舍和踩坑细节。打算做课设、毕设或者练手校园类项目的朋友可以直接拿这套思路去套。1. 项目背景与技术选型为什么校园点评系统要选pythonvue31.1 需求拆解点评系统的业务闭环到底有几条线动手之前别急着敲代码。我先把需求从头到尾捋了一遍发现校园商家消费点评系统看着简单实际上业务闭环很清晰用户逛商家、用户写点评、系统汇总评分、管理员维护商家信息。这四条线缺一条都不成立。拆成功能清单是这样的用户侧注册登录、浏览商家列表、按分类或关键词筛选、查看商家详情、发表点评、查看自己的点评记录商家侧商家信息维护名称、分类、位置、简介、商家评分聚合展示、点评数量统计管理侧商家信息增删改、不合规点评的隐藏或删除、普通用户和和管理员身份区分基础侧分页、搜索、登录状态保持、数据校验这个清单定下来之后核心就变成了三件事商家数据怎么组织、点评数据怎么写入和展示、评分怎么算得让人信服。前两件事是常规CRUD最难的是第三件——评分聚合和防刷后面专门讲。1.2 为什么选python vue3而不选别的组合技术选型这块很多人上来就纠结框架其实核心是先定分工。这个项目我用的是python Flask提供APIvue3 Vite做前端SPA再配合MySQL存数据。选python做后端不是因为它能写出性能多好的接口而是这个场景下它的效率优势太明显了。校园点评系统属于典型的管理信息系统业务逻辑以增删改查为主复杂运算场景几乎没有。python的Flask或FastAPI写这类CRUD接口非常快路由定义简洁、ORM模型直观加上整个社团里会python的人多后续维护成本低。如果你更习惯Django这套设计一样能平移Django自带的Admin后台在管理商家信息时甚至更省事。选vue3做前端核心是组件化和响应式状态管理。点评系统的页面存在大量重复结构——商家卡片、评分星星、点评列表项用组件封装后复用率极高。vue3的Composition API在组织业务逻辑时比Options API更直观尤其是一个页面上要同时处理筛选条件、评分预览、点评表单这些互相关联的状态时组合式函数composables能把逻辑拆得很干净。至于为什么不选传统的服务端模板渲染方案我的理由是这个项目需要频繁的前后端协作调试API接口文档一旦定下来两边可以并行开发。前端用Vite开发服务器做代理后端只专注于接口谁都不需要等对方。如果你之前只接触过后端渲染html的开发方式建议这次试试前后端分离体感完全不同。2. 后端设计python如何支撑商家、点评和评分三块核心业务2.1 数据模型六张表的建模思路重点是两个隐藏字段先说数据库设计。点评系统初看是四张表用户表、商家表、点评表再加上一张身份相关的表。实际落地时我扩展到了六张多出来的两张不是拍脑袋是踩过坑之后补的。核心表的字段设计如下用户表usersid主键自增username登录名唯一索引password_hash密码不能存明文用哈希nickname昵称前端展示用avatar头像URLrole角色user或admincreated_at注册时间商家表merchantsid主键name商家名称category分类比如食堂、奶茶、快餐、打印店location位置描述比如第三食堂二楼12号窗口description商家简介avg_score平均评分浮点数review_count点评总数status状态正常或关闭created_at、updated_at这里有一个关键字段容易被忽略avg_score和review_count这两个字段是冗余存储的聚合结果。很多新手设计表的时候会觉得平均评分可以在查询时用SQL的AVG函数现算没必要存字段。小规模数据确实能现算但商家列表页要展示评分和点评数而列表页往往还是分页的——每个商家都去点评表做一次聚合查询接口响应时间会明显拉长。更合理的做法是点评表只存单条点评的分数商家表冗余一个avg_score字段每次新点评或修改点评时同步重算这个商家的平均分和点评数。这是一个典型的以空间换时间思路。类似这样的冗余聚合字段在实际系统中非常常见。点评表reviewsid主键user_id外键关联用户表merchant_id外键关联商家表score1到5分content点评内容images点评图片存URL多个用逗号分隔statusvisible或hidden后台管理员可以隐藏违规点评created_at管理员日志表记录管理员对商家、点评的每一次操作这个表一开始没设计后来一个管理员误删了一条点评找不到操作记录才补上。虽然项目小但这种审计性质的表建议一开始就留着。为什么我说两张隐藏字段很关键一个是created_at和updated_at的成对出现——几乎所有表都需要记录创建时间和更新时间排查数据和排错时作用极大另一个是业务状态的冗余字段比如商家状态、点评状态不要真删数据用status字段做逻辑删除。校园项目可能只有十几个用户但养成这种习惯以后做任何系统都不会吃亏。2.2 接口设计RESTful风格下点评链路的完整规划后端接口我按照RESTful风格来组织把整个系统的API规划成几组。这部分的重点是接口契约要在编码前先定好前后端同时开工谁也不卡谁。核心接口如下POST /api/auth/register —— 注册POST /api/auth/login —— 登录返回tokenGET /api/merchants —— 商家列表参数category、keyword、page、page_sizeGET /api/merchants/ —— 商家详情POST /api/merchants —— 管理员新增商家PUT /api/merchants/ —— 管理员修改商家DELETE /api/merchants/ —— 管理员删除商家逻辑删除GET /api/merchants/ /reviews —— 商家的点评列表POST /api/reviews —— 提交点评GET /api/users/ /reviews —— 查看某个用户的点评记录DELETE /api/reviews/ —— 删除自己的点评或管理员删违规点评这个接口结构有几点讲究。第一点评的创建走POST /api/reviews但查询走GET /api/merchants/ /reviews这是把资源间的从属关系体现在URL里前端理解起来很顺畅。第二删除接口全部建议逻辑删除不是真的从库里删掉而是把status字段置为失效。点评数据不管是做统计还是做追溯都有价值物理删除容易把评分基数弄乱。需要专门说明的是登录授权。我用的方案是登录成功后后端签发一个token前端存在localStorage里之后每次请求在Header里带上Authorization字段。Flask这边写一个装饰器接口上标注 login_required 或 admin_required 就行。校园项目的体量不需要引入复杂的权限框架但装饰器的方式要理解——它本质上是把鉴权这个横切逻辑抽出来了不用在每个接口里重复写。2.3 评分聚合与防刷整个系统里最容易翻车的一个点评分是点评系统的灵魂也是最容易翻车的地方。一个用户反复给一个商家刷差评或者商家自己注册账号给自己刷好评评分就失真了。这里要讲清楚我采用的防刷和聚合策略。第一层同一用户对同一商家只能点评一次。实现方式就是给点评表的user_id merchant_id加联合唯一约束数据库层面保证。如果用户想修改点评就走修改接口而不是删了重发。这个约束在数据库层做比在前端简单拦截可靠一万倍。第二层评分聚合时做基础校验。当点评写入时后端要同步重算商家表的avg_score和review_count。这里有个细节score在存储时用整数还是带小数点的浮点数我的建议是点评分存整数1到5平均数在计算时再保留一位小数。原因很简单点评是一个离散行为用户打分就五个档存整数能减少数据异常聚合展示时更清晰。第三层水军识别。这个在校园项目里可以用轻量方案同一个IP段短时间内给不同商家连续打分或者一个账号在一天内点评超过5条就触发人工审核标记。用一条SQL就能查出来不需要机器学习。说实话校园系统能走到需要防刷这一步的用户量通常已经说明系统运作起来了。评分怎么算得让人信服我的最终公式是avg_score 该商家所有可见点评分数之和 / 可见点评总数。没有做加权也不需要。校园场景里样本量不大时间衰减反而会把数据搞复杂。但是展示层有个细节平均分旁边要显示XX条点评这个review_count就是公信力来源。只有一条5分和一百条4.5分用户心里自会有一杆秤。注意聚合逻辑必须放在后端事务里。提交点评、更新商家平均分、更新点评计数三步要么全成功要么全失败不然会出现点评写了但评分没更新的数据不一致问题。我最初没加事务测试时偶发数据对不上排查了半天才发现是并发写入时聚合更新丢了一步。加个事务装饰器一条语句就解决。3. 前端实现vue3组合式API下的页面开发实战3.1 脚手架搭建与目录规划vue3项目开局要做对的几件事前端部分用Vue 3 Vite Vue Router Pinia Element Plus这一套。先明确一点Vite是当前vue3项目的首选构建工具冷启动和热更新快不用再考虑Vue CLI了。如果你还没装好环境先确保本机有Node.js推荐LTS版本然后执行npm create vitelatest merchant-review-frontend -- --template vue cd merchant-review-frontend npm install npm install vue-router4 pinia axios element-plus装完依赖后的目录结构我是这样规划的src/ api/ # 所有axios请求的封装 assets/ # 静态资源 components/ # 通用组件商家卡片、评分星星、点评列表项 composables/ # 组合式函数useAuth、useMerchantList router/ # 路由配置 stores/ # pinia状态管理 views/ # 页面视图 utils/ # 工具函数 App.vue main.js这个目录结构对应的就是典型的vue3后台管理系统布局思路。api目录独立出来的价值是所有后端地址统一管理改一个字段不用满项目找。我在项目里把axios的baseURL、token拦截器、错误处理都集中在这里页面组件里不直接出现axios调用只调用api模块暴露的函数。路由层面要区分访客页面和管理员页面。商家列表、商家详情、登录注册是公开路由发表点评、个人中心需要登录态商家管理、点评管理必须管理员权限。Vue Router的导航守卫是唯一的拦截入口router.beforeEach((to, from, next) { const authStore useAuthStore() if (to.meta.requiresAuth !authStore.token) { next({ path: /login }) } else if (to.meta.requiresAdmin authStore.role ! admin) { next({ path: / }) } else { next() } })3.2 商家列表与筛选reactive状态管理怎么用才不晕商家列表页是前端体验的入口也是状态管理的典型场景。页面上同时存在这些互相关联的变量分类筛选项、关键词、当前页码、总页数、加载状态、商家数据数组、当前选中的排序方式。如果用Options API的data里面平铺十几个变量互相耦合维护起来很容易乱。这里我用reactive配合composition API来组织。关键点是把商家的筛选条件和筛选结果捆在一起管理。import { reactive, ref, watch, onMounted } from vue const filter reactive({ category: , keyword: , page: 1, pageSize: 12 }) const merchantList ref([]) const total ref(0) const loading ref(false) async function fetchMerchants() { loading.value true const { data } await getMerchants(filter) merchantList.value data.items total.value data.total loading.value false } watch(() filter.category, () { filter.page 1 fetchMerchants() }) onMounted(fetchMerchants)filter用reactive包成响应式对象watch监听分类变化并重置页码这就是vue3里处理列表筛选的典型姿势。核心逻辑在于筛选条件变化自动触发重新拉取数据而页面组件只需要关心filter对象本身。这就是组合式API对数据组织能力的提升也是很多人从vue2转vue3后最需要转变的思维。商家列表页还有个容易被忽略的细节空状态。筛选结果为零时页面不应该是光秃秃的白屏我专门写了一个空状态组件展示一张简单的插画配没有找到相关商家换个分类试试的文字。这个小细节看起来不起眼但对体验提升明显。3.3 商家详情与动态点评表单评分组件和表单校验的实现商家详情页是整个系统信息密度最高的页面商家信息卡片、平均分展示、评分分布、点评列表、点评表单。我的实现里评分展示和表单是分开的两个组件。评分展示组件我封装了一个StarRating组件传入score和size组件内部计算实心星星、半星和空星的个数。这里要强调评分展示不要用图片用CSS实现更灵活。比如半星可以用两个重叠的图标加overflow:hidden实现或者直接用Element Plus自带的el-rate组件但要注意el-rate默认是可交互的展示评分时要设置disabled属性。点评提交流程点评表单要处理的字段有评分、内容和图片。评分用el-rate内容用el-input的textarea模式再加图片上传。表单校验方面const reviewForm reactive({ merchantId: , score: 0, content: , images: }) const rules { score: [{ required: true, message: 请选择评分 }], content: [{ required: true, minLength: 5, maxLength: 500, message: 点评内容需在5到500字之间 }] }Content的最小长度校验是我特意加的。点评内容如果太短比如好赞对后来者没有参考价值。这里我踩过一个具体的坑Element Plus的form校验在自定义validate时如果函数没有返回callback会导致校验一直卡在pending状态。改用await内部逻辑并明确返回true或false后解决。这个细节在官方文档里不太醒目但实际开发经常碰到。商家详情页还有一个用户差评后想追评的场景我的设计是不允许追评或修改点评发布后只能走删除再新建。但删除后重新点评与数据库的联合唯一约束会产生冲突吗不会因为删除是逻辑删除联合唯一约束只约束非空组合逻辑删除的点评记录还在如果真实删除了用户就能再次点评。需要根据产品定位权衡——如果你的系统希望防止删差评重刷那物理删除反而要禁止逻辑删除之后也要在业务层拦截重复点评。4. 前后端联调、部署与真实踩坑记录4.1 联调前必须处理的三件事跨域、代理和接口契约前端开发服务器默认跑在5173端口后端Flask默认跑在5000端口这俩之间不做处理的话浏览器会因为跨域限制拦截所有真实请求。开发阶段最省事的方案不是在后端配CORS而是用Vite的proxy代理。在项目根目录的vite.config.js里配置import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { proxy: { /api: { target: http://127.0.0.1:5000, changeOrigin: true } } } })配置后前端代码里的axios请求都写相对路径比如/api/merchantsVite发现请求路径以/api开头自动转发到后端地址。这样浏览器看到的请求是同源的跨域问题在开发阶段直接绕过去。很多人第一步就卡在跨域我建议直接用这个方案比在后端折腾CORS的allow_origins更快。我踩过的一个坑是proxy配置后一定修改axios的baseURL为/api而不是写成http://127.0.0.1:5000/api。否则请求走了直连proxy完全不生效还会莫名产生一次预检请求。接口契约这块我建议在前端src/api目录下建一个统一的接口定义文件把后端接口的入参、出参类型全部标注清楚。前后端分离开发最怕的就是后端接口改了字段前端还不知情。我在项目里把每个接口封装成独立函数后端字段变化时只需要改一个地方。4.2 部署流程从本地开发到外网可访问开发完成后要部署。我部署的方案是前端用Nginx托管静态文件后端用Gunicorn启动Flask服务MySQL继续在服务器上运行。部署的几个关键步骤前端构建执行npm run build产物在dist目录把这个目录整个放到Nginx的web根目录下Nginx配置里做两件事一是托管前端静态资源二是把/api请求反向代理到后端的127.0.0.1:5000后端安装Gunicorn执行gunicorn -w 4 app:app启动绑定5000端口MySQL初始化数据库把建表SQL执行一遍关键Nginx配置是这个server { listen 80; server_name your_domain_or_ip; root /var/www/merchant-review-frontend/dist; index index.html; location /api { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location / { try_files $uri $uri/ /index.html; } }这里Nginx的try_files配置必须写否则vue-router用history模式时用户直接访问子路由路径比如/merchants/3刷新后Nginx会404找不到index.html。这个问题出现的频率极高先记住这个配置能少踩一个坑。关于部署还有两个建议。第一生产环境不要用Flask自带的开发服务器它单线程且不稳定Gunicorn配合Nginx是标准姿势。第二记得关闭后端调试模式设置环境变量FLASK_ENVproduction避免调试信息意外泄露。4.3 我记录的真实踩坑清单从环境安装到业务逻辑这个项目的踩坑记录我分成了四类写出来给后来者当提醒。第一类环境层面的坑。python官网下载安装时如果没勾选Add Python to PATH在命令行敲python会提示找不到命令。这是新手最常见的问题解决方法是重装时勾上或者手动把python安装路径加到环境变量。vue3项目创建时node版本低于16会报错我先用node -v检查了版本然后升级到LTS版本问题消失。这些坑都很基础但每次都拦下一批人。第二类python后端的坑。Flask读取JSON请求体时前端发送的Content-Type必须是application/json否则request.json是空的。Axios默认发送的是application/json但如果你用原生fetch且没手动设置header就很容易踩这个坑。另外返回中文数据时Flask默认的响应不会正确处理中文编码需要在app配置里设置 JSON_AS_ASCII False否则前端看到的就是\uXXXX转义字符。这个坑网上十几个帖子在问原因就是这个配置。第三类vue3业务逻辑的坑。用reactive定义表单对象时遇到动态添加或删除表单行数据比如管理员要给商家维护多个联系电话一会在列表里新增一行输入框一会删除一行。直接用reactive数组配合splice能正常工作但如果你用ref包数组在模板里很容易出现修改不触发更新的错觉。我的建议是表单行数据用reactive嵌套数组管理不要用ref。另外Element Plus的el-form的动态表单校验要给每个动态行设置唯一的prop路径比如contacts.0.phone、contacts.1.phone这个看一遍文档就能绕过去。第四类业务规则思考不周的坑。商家关闭后商家详情页还能不能访问点评列表要不要显示已关闭商家这个我在开发后期才意识到最后定的是关闭的商家不显示在首页列表但详情页可通过旧链接访问并显示暂停营业标记用户仍可查看历史点评但不能新发点评。这类边界场景写代码之前很难想全建议在后端接口里统一处理商家状态过滤而不是在每个前端页面里判断。5. 项目复盘这个点评系统还能怎么扩展项目上线演示后我又带着社团成员做了两次迭代虽然小的功能需求变了但整个框架一直很稳定。复盘下来有几个后来才发现值得做的扩展点如果你的时间够可以考虑加进去。商家维度的丰富。现在的商家只有文字描述和几张固定图片体验上还是比较原始的。扩展方向是给商家增加菜单列表、实拍图集、营业时间、联系电话甚至可以对接校园卡支付系统的使用热度做一个拥挤指数的展示。这些功能前端只是增加组件后端只需扩展商家表和对应的接口字段数据模型上不需要伤筋动骨。点评的互动能力。目前点评只有发布和删除用户之间没有互动。一个低成本但高价值的扩展是点评的有用顶帖按钮类似于大众点评的标记有用。实现也不复杂增加一张点赞关系表user_id、review_id商家详情页的点评列表按有用数排序一条pull request的复杂度。数据报表能力。系统运行一段时间后后台可以加一个简单的统计面板最高分商家TOP10、点评数最多的商家、评分趋势变化、用户活跃度。数据量小用SQL直接聚合就能出结果前端用简单的图表库比如ECharts展示。管理员的日常运营一下子就有了数据支撑。我个人实际做下来的体会是校园商家消费点评系统的难点从来不是在某个页面写得多炫而是把数据模型的边界定清楚、评分聚合的逻辑想明白、前后端接口契约达成一致。这三件事做好了这个项目的骨架就立住了后面所有迭代都是在这副骨架上面加肉。如果打算在python062这个项目编号基础上继续扩展建议优先做数据分析看板——因为点评数据本身就是一座矿握着它不用起来实在可惜。
返回列表