ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

从0到1构建前端分离项目:技术选型、工程搭建与团队协作实录

从0到1构建前端分离项目:技术选型、工程搭建与团队协作实录 “这个项目从立项到跑通第一个接口我整整记了两个星期的账。”这是我的研发日记第一篇。项目是给一家小型物流公司做一套业务管理系统团队五个人周期两个多月需求还在变。作为技术负责人我选择了直接用开发日志的方式记录整个过程——从技术选型到工程搭建从联调流程到团队分工每一步都留档方便后面复盘也方便任何一个中途加入的新人快速上手。这篇内容不打算做成教程式的“一步步教学”而是尽量还原一次真实的“从0到1”我们怎么定技术栈为什么这么定怎么把前后端工程从空目录铺到能跑通接口五个人怎么拆活、怎么协作以及那些文档里不会写、只有踩过才知道的坑。适合正在带小团队、准备启动一个前后端分离项目的同学参考哪怕你们的技术栈不完全一样踩坑思路和协作机制也基本通用。1. 项目启动前最花时间的环节技术选型与边界划定很多人启动项目恨不得第一天就把代码拉起来跑但我的习惯恰恰相反技术选型和工程边界划定值得花掉整个项目周期的10%时间。这个环节一旦草率后面所有开发节奏都会被拖累。1.1 后端选型为什么选Spring Boot 3.x而不是“更新”的框架我们这个团队里后端开发一共三个人全部有Java背景最熟悉的技术栈就是Spring Boot。当时我也认真考虑过用Go或者Node.js但从团队基因来看不划算——选一个大家没做过的东西至少有两周学习成本而且踩坑时连个能商量的人都没有。所以在后端上我们的组合是JDK 17 Spring Boot 3.2.xMyBatis-Plus作为持久层框架PostgreSQL 15作为主数据库Flyway做数据库版本管理选Spring Boot 3.x而不是2.x是因为既然是新项目没有历史包袱那肯定直接用最新的稳定主线。Spring Boot 3基于Spring Framework 6和Jakarta EE 9性能、安全补丁、生态适配都更健全。JDK 17是LTS版本生产环境用着放心。选MyBatis-Plus而不是Spring Data JPA很大程度是团队习惯问题。大家都写过MyBatis XML对动态SQL的掌控力更强而MyBatis-Plus又补足了单表CRUD的短板不用写重复的Mapper XML代码量少了一大截。尤其管理类系统查询条件复杂、多表关联多动态SQL是刚需。PostgreSQL是我自己“带私货”选的。比MySQL的优势是原生JSONB类型、CTE公共表表达式、更多的索引类型、以及扩展机制。这套业务系统里有一部分复杂的统计报表需求用PostgreSQL的窗口函数和CTE写起来比MySQL顺手太多。1.2 前端选型Vue 3 TypeScript是团队最优解前端团队两个人一个Vue经验多一个React和Vue都写过但更偏Vue。管理后台类系统Vue 3的组合式API写起来很舒服配合TypeScript维护成本和中大型页面的可读性都很有保障。具体技术栈Vue 3.x TypeScriptVite作为构建工具Pinia做状态管理Vue Router 4做路由Element Plus做UI组件库选择Vite不是因为它“流行”而是它基于ESM的开发服务器冷启动快得离谱。我们用Webpack 4的老项目冷启动可能要等二三十秒换成Vite后基本是秒开。前端开发的反馈循环短了一大截团队幸福感直接上升。UI组件库选Element Plus和Vue 3的生态绑定最紧表格、表单、弹窗等中后台场景组件齐全。Ant Design Vue的React血统太重用起来总感觉不够“Vue原生”。我们不做C端营销页不需要炫酷的动画Element Plus的中规中矩反而是优点。1.3 不要过度设计单体应用就够了这里要特别说一句很多人上来就规划微服务这其实是个陷阱。我们的业务量级、团队规模、交付周期都不支持微服务的落地。微服务解决的是“大规模团队并行开发”和“独立水平扩展”的问题五个人两个月交付的管理系统微服务只会让分布式事务、服务发现、配置中心这些复杂度压垮整个项目。所以我们选了单体应用加模块化分包将来要拆微服务按业务模块把代码抽出去就行。数据库层面也是同一个库但不同业务模块的表前缀分开、连接池隔离这样后续拆分的时候动作最小。基础设施也一样没用Kubernetes就用Docker Compose编排后端、前端、数据库三个容器单机部署简单可靠。等哪天真的需要横向扩展了再上K8s也不迟。2. 后端工程搭建目录结构、数据库版本管理、统一响应体设计从真正敲第一行代码到后端工程跑起来我花了大概两天时间。一天的产出是骨架和规范另一天的产出是核心的通用能力。后面所有业务开发都是在这个骨架上长出来的。2.1 工程结构与分层单模块还是多模块一开始我也纠结过Maven多模块——bootstrap、system、business、common分开。后来权衡了一下多模块的依赖管理成本对五个人团队来说太高了每次改common都要重新install一次。最终选择了单Maven模块按业务包名分包。结构大概这样com.company.tms ├── TmsApplication.java ├── common // 通用能力统一响应、异常、工具类、常量 ├── config // 配置类MyBatis、Flyway、安全等 ├── controller // 接口层 ├── service // 业务逻辑层 ├── mapper // MyBatis的Mapper接口 ├── entity // 数据库实体 ├── dto // 入参出参对象 └── enums // 枚举按技术分层而不是按业务域分包初期开发会更快——每个人很清楚自己要改的文件在哪个包里controller写接口、service写逻辑、mapper查数据不用思考。但缺点是业务域之间不隔离所以我们在service包内再按模块加子包比如service.user、service.order、service.report这样既保速度又留边界。2.2 数据库版本管理没有它就等于裸奔这里是我最坚持的一点任何新建的表结构变更必须通过Flyway迁移脚本不允许手工在数据库里改也不允许用JPA的ddl-auto自动建表。用Spring Boot MyBatis-Plus时很多人都喜欢直接在application.yml里配ddl-auto: update开发期图省事。但这个习惯一旦养成到了测试环境、生产环境你根本不知道数据库到底长什么样。表改过几次、字段有没有加索引、数据有没有被清过全是黑盒。Flyway的用法很简单在src/main/resources/db/migration下按版本号放SQL文件V1__init_schema.sql V2__add_user_table.sql V3__create_order_table.sql启动时Flyway会自动按版本号执行未跑过的脚本并且记录在flyway_schema_history表里。团队任何一个人拉最新的代码启动起来就是最新的表结构不需要手动执行任何SQL。我们在V1脚本里建了最基础的用户表、角色表、权限表每个字段都带注释命名统一用下划线风格。2.3 统一响应体与全局异常处理前后端分离项目里接口约定是第一步如果你连返回结构都不统一前端这边的封装就无从下手。从第一个接口开始我们就强制统一返回结构。{ code: 0, message: success, data: {} }对应的Java类是ResponseResultT三个字段业务码、提示信息、业务数据。code0表示成功非0表示失败错误码表单独放在ErrorCode枚举里每个错误码都有明确的语义。这里有个小经验错误码不要直接复用HTTP状态码。HTTP的404表示路由不存在而业务上可能是“订单不存在”两者混在一起会让前端判断逻辑非常混乱。全局异常处理是配合统一响应体用的。正常逻辑直接返回ResponseResult.success(data)异常则抛给全局的RestControllerAdvice去兜底。自定义了BizException业务异常凡是业务判断失败的地方直接throw new BizException(ErrorCode.ORDER_NOT_FOUND)不用在每个接口里写try-catch。这个设计看着简单但真正让代码整洁度提高了一个档次。2.4 实测第一个接口从DAO层到Controller层的完整链路第一个接口选了最不涉及业务的健康检查就一个/api/health返回当前服务状态。但它走完了完整的链路Controller接收请求、Service层处理逻辑、Mapper查库、统一响应返回。之后紧接着写了第一个有业务意义的接口用户注册。PostMapping(/api/user/register) public ResponseResultLong register(RequestBody Valid RegisterDTO dto) { Long userId userService.register(dto); return ResponseResult.success(userId); }Service层做的事情校验用户名是否重复、密码加密、插入用户表。密码加密用BCrypt这是Spring Security自带的加密器能自动加盐不需要自己处理盐值存储。这个接口虽小但确立了后续开发的范式DTO做参数校验、Service做业务、Controller只做路由。3. 前端工程搭建初始化脚手架、请求封装、路由与状态管理后端框架稳定后我花了一天时间把前端工程搭起来。前端的搭建不仅是能把页面跑起来更要让整个请求链路、鉴权逻辑、路由控制从一开始就规范化。3.1 Vite初始化比Webpack爽在哪创建项目用Vite官方脚手架npm create vitelatest tms-web -- --template vue-ts装完基础依赖后我又加了vue-router、pinia、axios、element-plus。这里有个版本兼容性问题npm create vite默认会装最新的Vite如果Node版本过低可能会报错建议Node直接上18省掉一堆版本兼容问题。Vite开发体验和Webpack的差距最直观的就是冷启动和热更新。冷启动一个包含几十个页面路由的项目Webpack可能要等十几秒Vite基本保持在一秒以内。原理是Vite利用了浏览器原生ESM支持启动时不用打包全部文件而是按需加载。对于开发阶段频繁改代码的体验提升太明显了。3.2 目录结构一开始就为长期维护留好位置前端目录我花了心思设计避免代码越写越乱src ├── api // 按模块拆分的接口定义 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── stores // Pinia状态 ├── types // TypeScript类型定义 ├── utils // 工具函数 └── views // 页面组件api目录下按后端模块划分user.ts、order.ts、report.ts。每个接口函数都返回带泛型的类型比如PromiseResponseResultUserInfo这样在组件里调用接口时回调函数的参数天然有类型提示根本不用去翻接口文档。3.3 axios封装拦截器才是核心axios的封装是前端工程质量的关键。我们的封装有两个重点请求拦截器和响应拦截器。// utils/request.ts const service axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器附带认证信息 service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理业务错误码 service.interceptors.response.use( response { const res response.data if (res.code ! 0) { ElMessage.error(res.message) if (res.code 401) { router.push(/login) } return Promise.reject(new Error(res.message)) } return res.data }, error { ElMessage.error(网络异常请稍后重试) return Promise.reject(error) } )这里踩过一个坑响应拦截器里如果直接return res.data调用方拿到的直接是data里的内容不再需要response.data.data这种两重取值的写法。前端同事一开始老是写错因为这个response类型是axios的完整响应而我们返回的是res.data两个data搞混了是家常便饭。所以我在TypeScript类型声明里把自定义返回结构写清楚了类型系统帮忙兜底。另外401的判断非常关键。token过期时后端统一返回code401这时前端应该做两件事清理本地token、跳转登录页。如果不做这层拦截每个业务页面里都要单独处理登录失效那可就太痛苦了。3.4 路由守卫与页面骨架路由配置对应后端的功能模块登录页、首页、用户管理、订单管理、报表中心。用meta字段标记每个路由是否需要登录、需要的权限码。{ path: /user, name: UserManage, component: () import(/views/user/index.vue), meta: { requiresAuth: true, permission: user:list } }路由守卫里做两件事未登录跳登录页已登录但权限不足则提示无权限。整个鉴权逻辑在前端侧是透明拦截的业务页面里不需要再判断用户角色。第一个能点击的完整页面是登录页。登录接口调用后端/api/auth/login成功后把token存到localStorage同时把用户信息存到Pinia。这套流程跑通后前后端算是真正“接上头”了。4. 前后端联调接口文档规范、跨域处理与首个完整流程前后端各自的任务完成了一部分后联调是整个项目最考验耐心的环节。我们用了好几个手段来减少联调期的摩擦但依然踩了几个有代表性的坑。4.1 接口文档先行但不求一步到位联调的基础是接口文档。我们用的是Apifox既可以设计接口文档又能生成Mock数据还能直接调试真实接口。好处是接口文档和调试在同一套环境下改动即时生效比Swagger UI在团队协作上体验好很多。文档定义的关键字段包括请求路径、请求方法、请求头、Query参数、Body的JSON结构、返回结构、以及每个字段的类型和说明。开发中重点是由后端先行定义因为后端写接口时对数据边界最清楚。具体流程是后端计划开发某个模块时先在Apifox建好接口文档然后把链接发到群里前端同事开始Mock联调后端实现完后前端切到真实环境调试。在文档时效上我们也踩了坑。有个接口改了返回字段后端忘了同步文档导致前端同事按旧文档开发联调时发现字段对不上排查了大半天。后来定了一条铁律谁改了接口谁负责当天下班前更新文档不改文档的接口不让上线。4.2 跨域问题本地开发用Vite代理解决跨域是前后端分离绕不开的话题。我们本地开发环境前端跑在5173端口后端跑在8080端口浏览器的同源策略会拦截不同端口的请求。解决方式有两种后端配CORS或者前端配代理。我的建议是本地开发优先用Vite代理测试环境优先用Nginx反代。CORS这东西虽然配置简单但一旦涉及带Cookie的请求withCredentials: true前后端都要配合改坑比较多。用代理则完全不需要后端知道前端的存在。Vite代理配置// vite.config.ts server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这个配置的意思是前端请求/api/xxx时Vite会把请求转发到http://localhost:8080/api/xxx浏览器看到的请求是同源的不存在跨域问题。4.3 从注册登录到用户列表首个完整业务闭环联调的第一个完整流程我们选了“注册 → 登录 → 获取用户列表 → 展示”这条链路。原因很简单这条链路涉及认证token的生成和校验是几乎所有接口的前置依赖把这条链路跑通了后面所有业务模块的联调都只是重复劳动。联调中发现了一个有意思的坑响应拦截器里已经写了401跳登录的逻辑但当用户手动点击“退出登录”时会主动调用后端接口让token失效而后端返回的也是401。这时候前端拦截器会再次跳转到登录页页面会闪一下。后来把退出接口的错误码单独定义为2000不触发401的跳转逻辑。这个细节虽然小但很能体现前后端契约设计时的互相理解。5. 团队分工与协作五个人怎么把活拆得明明白白技术栈和工程骨架都定了接下来最核心的事就是“分活”。五个人,两个前端、两个后端、我全栈兼任技术负责人怎么分工才能不打架、不堵塞、不返工5.1 按业务模块拆任务而不是按技术层拆最常见的错误分工方式就是“一个前端负责页面、一个后端负责接口”——这种按技术层拆的方式会导致每个人对业务的理解都不完整沟通成本成倍增加。我们选择的是全栈对应关系按模块拆成员角色负责模块我全栈/技术负责人系统脚手架、登录鉴权、接口规范、联调协调后端A后端开发用户管理、角色权限、组织架构后端B后端开发订单管理、报表统计、数据导入导出前端A前端开发登录页、用户管理、角色权限页面前端B前端开发订单管理、报表统计页面这样拆的好处是每个业务模块都有一个明确的后端人对一个明确的前端人。后端A和前段A在业务上是一对一的沟通时可以直接说“用户管理那个列表的分页参数改了”不需要经过中间人传达。但这里有个前提——模块之间要尽量解耦。比如权限模块和订单模块之间确实存在关系订单要关联用户但我们通过接口约定而不是数据库直接关联来解决这样两边并行开发时不需要等对方。5.2 Git分支策略简洁务实的GitFlow轻量版我见过很多团队一上来就搞复杂的GitFlowdevelop、release、hotfix、feature全上结果小团队根本维护不过来。我们的策略是主干开发加特性分支main分支保持可发布状态任何时刻都能上线dev分支日常开发集成分支feature/xxx分支每个功能从dev切出完成后合并回dev开发流程是每天早上从dev切出最新的feature分支写完自测后提交Merge Request简称MR指定模块对应的搭档Review。Code Review通过后合回dev。发测试环境前dev合并到main打tag。Git提交信息用的是Conventional Commits规范格式是type(scope): description例如feat(user): add user list page、fix(order): fix export encoding issue。这个规范的价值不在于格式本身而在于git log能直接当Changelog用看着提交记录就能快速定位某次改动的意图。5.3 任务拆解与工时估算用看板而不是项目管理系统我们用了极简的看板三列——待办、进行中、已完成。每张卡片只描述一个独立可交付的小任务比如“完成后端订单搜索接口含分页”而不是“做订单模块”。我对任务拆解的最小粒度的经验是一张卡片的工作量最好控制在0.5天到2天之间。超过2天的任务要继续拆否则每天站会时根本说不清“卡在哪了”。不到半天的任务比如“修一个按钮样式”没必要单独建卡挂在相关卡片下面就行。这里必须说一个反直觉的事任务拆得越细进度反而越快。因为每完成一个小卡片团队都会有实实在在的成就感而且很容易发现风险——如果一张卡到了第三天还没关闭那就说明任务估算有误或者遇到技术卡点需要赶紧介入。5.4 站会与节奏控制每天上午10点固定站会15分钟以内。每个人只用说三个问题昨天做了什么、今天打算做什么、有没有需要别人配合的阻塞项。我们不允许在站会上讨论解决方案超时的技术问题会后单独拉人聊这样能保证站会的高效。每周五下午做一次阶段复盘不是形式主义而是真正看本周完成率和下周计划。复盘时我会把看板数据拉出来看看每个人实际完成的故事点和预估差了多远用于校准后面的节奏。前两周的估时偏差在30%以上第三周开始就稳定在10%以内了——这个数据校正能力非常重要。6. 踩坑实录这些坑不踩一遍真的不长记性两个多月的开发周期里我记录了十几个能写进“从0到1”经验帖的坑。挑几个最有代表性的分享出来这些都是常规文档里不会写的内容。6.1 “在我机器上能跑”的真相环境统一太重要了项目启动第四天后端A说他本地跑不起来报一个奇怪的依赖异常。最后发现是他的JDK依然是11而项目用的Spring Boot 3.x强制要求JDK 17。他拉代码的时候根本没留意README里的环境要求。这个问题的本质是团队没有统一的开发环境校验。后来我们做了一件事在工程根目录放一个.java-version文件再加上README里明确写“安装JDK 17和Node 18否则项目无法启动”并且把这段放在README最显眼的位置。前端那边也有类似问题——Node 16跑Vite 5会直接报错。这之后“在我机器上能跑”的声音基本消失了。6.2 数据库字段命名不一致引发的联调事故有次前端A在联调用户列表的筛选功能时反馈接口返回的字段里没有createTime只有created_at。排查后发现数据库表字段是下划线风格created_at后端实体里MyBatis-Plus默认开启了驼峰映射自动转成了createdAt但后端的VO视图对象里字段名写的是createTime字段映射在最后一步断了。这个坑的根子是命名规范没有在项目启动时统一。我们后来在工程规范里明确规定数据库字段一律下划线代码统一驼峰MyBatis-Plus开启驼峰映射DTO/VO字段名必须和响应体完全一致。同时要求后端在合代码前自己先调用一次真实接口用浏览器看返回结构确认无误再接给前端。6.3 第一个完整流程联调时Mock数据和真实数据的差异Apifox的Mock功能确实香文档建好后前端马上就能用Mock数据开发页面。但Mock数据有个大坑Mock生成的数字ID是非常规整的大整数而后端真实数据是雪花算法生成的Long类型。前端存ID时如果用JavaScript的Number类型当ID超过JavaScript的Number.MAX_SAFE_INTEGER9007199254740991时会出现精度丢失。雪花ID显然已经超过了这个范围。第一次联调时前端A就发现了这个问题点击编辑用户页面显示的ID和列表里的不一致最后一位变成了0。解决方案是后端在JSON序列化时把Long类型的ID转成String传给前端JsonSerialize(using ToStringSerializer.class) private Long id;这是个前端“看着是String、传回后端能自动转Long”的处理方式。这个坑在纯前端团队或者纯后端团队各自开发时很难被发现只有在联调时才会暴露所以特别值得提一下。6.4 阶段复盘做对了什么做错了什么项目第一个月结束时我做了次相对正式的复盘。做得好的是技术选型足够克制没有引入团队不熟悉的组件和框架脚手架搭得比较细后面业务开发很顺接口文档早定和统一响应体落地及时联调效率高于预期做得不好的地方有些任务的工时估算过于乐观特别是前端写复杂表格页面的时间预估偏差很大环境问题在初期浪费了不少时间下一次项目启动时我会把“环境准备文档”作为第一步先写出来UI设计稿出来得比页面开发晚导致前端同学有些页面返工了两次写在最后带一个从0到1的项目最累的往往不是写代码而是把所有参与者的节奏调成同一个频率。技术选型、工程搭建、接口规范、分工协作所有这些“打地基”的工作短期看好像耽误了写业务代码的时间但长期看它们才是决定项目能不能按时交付的关键因素。这个系列的下一篇我会记录用户权限模块从需求到落地的完整过程包括RBAC模型怎么设计、动态菜单怎么实现、以及权限这块前后端怎么配合才不会互相踢皮球。项目还在继续坑也在继续踩希望这些记录能让正在走同样路的人少走一些弯路。
返回列表