☰
从零搭建经方药食两用平台:前后端分离开发与部署全记录
2026/10/15 7:01:00 网站建设 项目流程

前端跑起来很简单,npm install装完依赖,npm run dev起本地服务就行。真正要留意的是vite.config.js里的代理转发,开发环境跨域通常都靠它解决:

// vite.config.js export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })

提示:生产环境不要用这种代理方式,交给 Nginx 统一转发更稳妥,后面部署部分会细说。

3.2.2 列表页:搜索、分类、筛选的组合体验

经方库首页我设计成左侧分类树、右侧卡片列表的结构。左侧按"解表剂、清热剂、补益剂、祛湿剂"等功效分类组织,右侧卡片展示方名、出处、核心药材、功效标签。搜索框放在顶部,支持按方名、药材名、功效关键词检索。

列表页背后对应两个接口:分类查询和条件搜索。搜索我做了防抖处理,用户停止输入 300 毫秒后才发起请求,避免每次敲击键盘都触发后端查询。分页用的是若依框架里常用的PageHelper插件,在 MyBatis 的 mapper 里不用手写 limit,只要在 service 层调用PageHelper.startPage(pageNum, pageSize),后面的查询自动带上分页。

前端还要考虑搜索状态与 URL 同步。我把搜索关键词和当前分类 ID 同步到路由 query 参数,刷新页面后仍能恢复上次的查询状态,这个细节对体验提升很明显。

3.2.3 详情页:信息分层阅读

详情页我用抽屉加 Tab 的组合:左侧抽屉显示经方基础信息,右侧 Tab 分几个面板——"药材配伍"展示该经方包含的所有药材、用量、炮制方法;"适用人群与禁忌"提示使用注意;"本草考证"展示药材出处与药食两用依据。

药材配伍面板是前后端协同工作的重点。前端拿到经方详情后,通过关联接口一次获取药材列表,后端用@Select注解直接写多表联查语句返回。这里我用了 MyBatis 的@Results注解做字段映射,解决数据库下划线字段和 Java 驼峰属性不一致的问题:

@Select("SELECT h.id, h.herb_name, h.food_or_medicine, h.nature, h.flavor, " + "r.usage_dosage, r.processing_method " + "FROM herb_info h " + "LEFT JOIN jingfang_herb_ref r ON h.id = r.herb_id " + "WHERE r.jingfang_id = #{jingfangId}") @Results(id = "herbResultMap", value = { @Result(column = "herb_name", property = "herbName"), @Result(column = "food_or_medicine", property = "foodOrMedicine"), @Result(column = "usage_dosage", property = "usageDosage"), @Result(column = "processing_method", property = "processingMethod") }) List<HerbVO> listHerbsByJingfangId(Long jingfangId);

看到这里你可能会问:为什么不在 XML mapper 里写?因为这种简单查询用注解更直观,XML 更适合写复杂动态 SQL。我的习惯是:单表查询和简单关联用注解,多条件动态查询和复杂嵌套结果映射用 XML,二者都在 MyBatis 的能力范围内,看场景选。

3.2.4 后台管理:角色权限的动态菜单

后台管理端和前台的差别在于多了一层权限模型。用户登录后返回的 JWT 里解析出角色,后端通过拦截器校验接口权限,前端则用路由守卫根据角色动态注册可访问的菜单路由。

具体说:用户登录成功后,后端返回该用户的角色和权限标识列表,前端把权限列表存进 Pinia store。路由守卫在每次跳转前判断目标路由需要的权限码是否在当前用户的权限列表中,没有就直接跳转到 403 页面。菜单则是从全部路由配置里过滤出有权限的部分,用router.addRoute动态注册。

权限编码我做得比较细,比如经方管理有jingfang:add、jingfang:edit、jingfang:delete三个独立权限码,而不是简单的一个"经方管理员"角色覆盖全部操作。这样可以精细控制操作权限,也方便以后扩展新的角色类型。

3.3 前后端联调的五个典型问题

联调阶段往往是整个项目最耗时的地方,我把这次踩到的典型问题列出来,你以后大概率也会遇到。

第一个是跨域问题。开发环境代理配置好后基本没烦恼,但生产环境如果前端 Nginx 和后端 Tomcat 分开部署,跨域就来了。我在后端加了全局 CORS 配置类,允许指定域名跨域;同时要求前端统一走/api前缀,由 Nginx 统一转发,双保险。

第二个是日期格式。后端默认返回的 LocalDateTime 是 ISO 格式,前端需要解析成YYYY-MM-DD HH:mm展示。我在 JSON 序列化配置里统一指定了格式,避免每个接口单独处理:

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8

第三个是空值处理。查询结果里某些字段是 null 时,前端显示"undefined"很刺眼。我在后端 VO 里对需要展示的字段设置了默认值,再配合前端的兜底过滤器,双管齐下解决。

第四个是关键字参数编码。搜索关键词如果是中文,直接用 axios 传参偶尔会出现乱码。前端在请求拦截器里对params做一次encodeURIComponent,后端再按 UTF-8 解码,问题就消失了。

第五个是事务边界。批量导入经方数据时,我一开始把解析和插入写在同一个方法里,中间一行数据出错会导致整个事务回滚,但定位是哪一行出错却不容易。后来我把数据解析和数据库插入分开成两个方法,解析阶段先校验行格式并收集错误行号,插入阶段再对整体开事务,既保证了原子性,又能在出错时快速定位。

4. 部署上线与压测复盘:踩过的坑和对应解法

项目开发完只是开始,部署上线才是真正检验系统的地方。我前后部署过两次,第一次在某云服务器上踩了三个坑,第二次基本顺利通过,下面把这些经验完整记录下来。

4.1 首次部署的三个真实事故

事故一:MySQL 时区导致的时间偏差。项目在本地运行没问题,部署到服务器后,查询出来的时间全部比本地晚 8 小时。排查后发现是服务器 MySQL 的时区设置和本地不一致。修改方式:在 MySQL 连接串中显式添加serverTimezone=Asia/Shanghai&useSSL=false,同时检查服务器系统时区,确保date命令显示的时间正确。这个坑很经典,几乎每个前后端分离项目都可能遇到。

事故二:数据库编码导致中文乱码。导入数据后,后台管理端显示的药材名称全是问号。检查了数据库表字符集,发现建表时部分表默认用了 latin1。解决方案是统一把库、表、字段的字符集改为 utf8mb4,顺便验证了连接串里也要加characterEncoding=utf-8。utf8mb4 相比 utf8 多支持表情符号和更多生僻字,药材名里有些罕见字,用 utf8mb4 更保险。

事故三:JWT 密钥泄露。我把 JWT 的签名密钥直接写在 application.yml 里,第一次部署后没多久,就发现服务器日志里有异常的 token 试错记录。我怀疑是密钥暴露在代码仓库里导致的风险点,于是改成从环境变量读取,线上配置和代码分离。安全这事不能偷懒,特别是涉及用户信息和健康数据的系统,密钥管理必须认真对待。

# application-prod.yml jwt: secret: ${JWT_SECRET} expire-time: 7200

事故四:Nginx 代理配置遗漏 WebSocket。前端的消息通知模块用了 WebSocket 推送收藏变化提醒,部署后发现只能在本地开 debug 模式能收到,一部署就失联。Nginx 配置里忘记加Upgrade和Connection请求头,导致 WebSocket 握手失败。补上后恢复正常:

location /ws/ { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }

4.2 压测数据与系统表现

部署稳定后,我用压测工具做了短暂的并发测试。测试环境是单台 4 核 8G 云服务器,MySQL 和 Nginx 都在同一台机器上。压测场景设定为:检索经方接口 100 并发持续 60 秒,平均响应时间 98ms,错误率 0%;登录接口 200 并发持续 60 秒,平均响应时间 220ms,错误率 0.5%,主要错误集中在 token 生成时 RSA 签名的 CPU 开销上。

整体表现可接受,但登录接口高峰性能一般。后来优化的手段是把 JWT 签名算法从 RS256 换成 HS256,减少公钥私钥加解密的开销,错误率归零,响应时间降到 160ms 左右。对单体项目来说,没有必须用非对称加密的场景,HS256 足够安全且性能好。

4.3 数据安全与备份策略

这个平台涉及用户收藏内容和健康相关信息,备份是必须认真对待的。我写了一个简单的备份脚本,每天凌晨全量备份 MySQL 数据库,保留最近 7 天备份;同时用定时任务同步备份上传的图片文件和附件到云存储。备份脚本核心逻辑就一行命令加上保留策略:

#!/bin/bash # 每日备份 MySQL 全部业务库 mysqldump -u backup_user -p'password' --single-transaction --routines business_db > /backup/$(date +%Y%m%d_%H%M%S).sql # 保留最近 7 天,删除更早的备份 find /backup -name "*.sql" -mtime +7 -delete

虽然有云服务商的磁盘快照功能,但本地的定时逻辑备份仍然多一层保障,更稳。

5. 这类系统的扩展空间与二次开发入口

做完这个经方药食两用服务平台,我的体会是:它表面看是一个中医药信息管理系统,本质上是一个垂直领域的知识管理加内容分发平台。只要把基础打好,可以往多个方向扩展。

5.1 业务功能扩展方向

AI 辅助推荐是当前最有想象力的方向。现在的推荐模块还是基于标签规则,比如根据用户收藏的经方中常用的药材,推荐包含该药材的其他经方。更进一步可以接入大语言模型,自动把用户提出的症状描述转化为检索条件,在不替代医生诊断的前提下辅助用户了解相关知识。这个方向对后端的要求是预留好向量化接口和外部 API 的调用层。

社区问答互动能让平台活跃度上一个台阶。用户可以在经方详情页下提问、分享自己的使用心得,管理员审核后展示。数据库层需要增加帖子表和评论表,用户表增加关注数、粉丝数字段,整体改动不大,但对系统的社交属性提升明显。

语音播报对老年用户很友好。很多关注健康养生的用户年龄偏大,语音播报药材材性和操作步骤能显著降低使用门槛。前端可以用 Web Speech API 或接入第三方语音服务实现,后端只需在内容表里增加一个音频文件地址字段。

5.2 二次开发前你需要了解的关键点

如果你想把这份源码拿来改成自己的系统,有几个关键点需要先心里有数。

第一个是理解了权限模型再动代码。系统的权限设计是经典的 RBAC 思路,用户-角色-权限三层结构。如果你新增一个"养生师"角色,不需要改后端权限拦截逻辑,只需要在数据库角色表里加一条记录,然后给该角色分配权限码。前端的路由配置里也是同理,只要动态菜单的过滤逻辑没破坏,新增角色的权限就能自动生效。

第二个是前端组件复用。我在components目录下沉了不少公共组件,比如药材选择器、经方卡片、空状态组件。二次开发时尽量复用这些组件,不要每个页面重复造轮子。如果你要改卡片展示样式,只需要改一个地方,其他页面自动同步,维护成本会低很多。

第三个是数据字典的运用。系统的药材分类、功效分类、禁忌类型等下拉选项都挂在数据字典表里。你在后台直接改字典项就能变更前端下拉选项,完全不用发版。如果 API 返回的字典项不满足需求,修改字典缓存即可生效。

5.3 我的一些个人建议

如果让我重新做一遍,我会在起点阶段就把文件存储抽象成接口,而不是直接绑定本地目录。原因很简单:项目上线后用户量一旦上来,本地上传的文件会占满磁盘,迁移到云存储要改的代码到处都有。抽象出统一的存储接口后,本地实现和云实现随时切换。

另一个建议是提前把日志体系搭好,而不是等出了线上问题再补。统一使用日志框架,按天滚动输出,并为登录、增删改操作打上操作日志,能在排查问题时节省大量时间。实际操作中,线上故障的定位速度往往不取决于代码写得多好,而是日志留得够不够全。

最终,前端可以尝试引入单元测试,至少把核心的 API 封装层和权限判断逻辑覆盖住。我这个项目在这块投入得不多,好在目前业务不复杂,bug 大多是边界条件引起的,通过人工回归能兜住。如果后续功能增多,测试的优先级就得往后端核心逻辑和前端公共方法上提。

这个系统从跟着教程走一遍,到真正读懂每一个关键设计,中间差的正是对整个项目从数据库到前端、从开发到部署的全局理解。希望这份复盘能帮你减少一些二次开发中的弯路,也欢迎在实际改造中遇到问题时多回来对照看看。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询