前后端分离的 Web App 踩坑笔记

最近在做一个整整 10 学分的团队课设。

Environment

  • 前端:React + Antd
  • 后端:PHP + MySQL
  1. 刚开始技术选型时准备采用 Flask 作为后端架构,毕竟半年前大家刚做了 Flask 的单人项目,对于其他组员来说 Python 比 PHP 更容易上手一些。但是在 Python 中缩进对代码逻辑起到影响,如果编码过程或者冲突处理时没做好,可能会产生比较棘手的麻烦。最终还是选了 PHP,代价是增加了约一个月的学习成本,不过整体代码可读性和维护性都高了不少,依赖注入也很香。
  2. Jetbrains 全家桶自带 deployment 功能,可以快捷部署当前文件至 remote 以便测试。但是全量部署更适合 Github Actions。配置好 appleboy/[email protected],就能将 Node.js 项目的 dist 目录打包后再上传至生产环境,节约了不少时间。值得注意的是,当 source path 填写为 dist/ 时,文件将被部署到远程目录的 path/dist/ 下。在 scp 中可以使用 dist/* 来解决这个问题,在此插件中可以指定 strip_components: 1 来去除前导路径元素(推荐),或者在 nginx 中修改根目录至 wwwroot/dist。不建议修改 nginx 的 wwwroot 配置,这将导致 lnmp 无法更新 Let’s Encrypt 证书(也可以在 nginx 中为 .well-known 路径单独指定根目录)。
  3. 前后端跨域是非常棘手的一个问题,我们在此花了数十个小时 debug。CORS 的出现保护了用户免遭跨站攻击,但是也为前后端对接产生了巨大的麻烦。
    • 当用到跨域 API 调用,通常做法是在后端 header 加上 Access-Control-Allow-Origin: *,但 Fetch 请求不允许这种掩耳盗铃的操作。解决办法是动态指定参数或者将前端域名写死。
    • CORS 预检请求方法是 OPTIONS,因此需要设置 Access-Control-Allow-Methods: POST, GET, OPTIONS。根据 Issue #251 · whatwg/fetch 的讨论中,当 Fetch 的 withCredentials = true 时(跨域请求携带目标域的 Cookies),此处不允许通配符,因此应当避免使用 Access-Control-Allow-Methods: *。此规则同样也适用于 Access-Control-Allow-Methods,因此应当手动声明后端允许的 request header,例如 form-data
    • 必须设定 Access-Control-Allow-Credentials: true,才能使后端接受前端跨域发送的 Cookies。
    • 我们使用 PHPSESSION 来鉴权,但 Fetch 无法响应 response header 中的 set-cookie(并非 http-only),导致浏览器无法存储用户凭据。前端同学试了 axios 和 Webpack Proxy 也没有什么头绪。对此有两个临时解决方案:
      1. sessionId 直接塞在登录接口的 data 字段里回传给前端,然后手动设置 cookie(或者存储至 SessionStorage 中)。
      2. 让前端通过 XMLHttpRequest 发送原生的 POST 的请求,浏览器自己会响应 set-cookie 的字段。
  4. 自 MySQL 5.7 开始,正式引入了 JSON 作为字段类型。这意味着我们再也不用为同质化的数据表手写烦人的 SQL 文档了,只需要将他们抽象成一种数据,然后前后端再根据 JSON 中的某个字段来路由逻辑即可。