迪拜一家奢侈腕表品牌的官网,用来展示产品、获取客户,英文和阿拉伯语双语。三期都由我一人交付:设计文档、Spring Boot 后端、Vue 管理后台、React 前台和部署。已上线。
- 14张表,文字字段都是双语
- 80+个接口,前台与后台
- 15天提前:三期计划 3 月 3 日,2 月 16 日交付
- 1,200+次自然搜索点击,上线后 28 天
需求
客户的要求是:用品牌名在 Google 上能搜到,产品页有图片、视频、参数和价格,能链到社交媒体、电商平台和 WhatsApp,产品可以上下架。服务器和域名也由我负责。在中东打开要快,视频只在不拖慢页面的地方用。
写代码之前,我先写了架构设计文档(1172 行)、API 设计文档(1836 行)和后端说明。客户偏好 Java、Spring 和 MySQL,后端就用这套。
系统组成
后端是 Spring Boot 3、Java 17,MyBatis-Plus、Spring Security 加 JWT(24 小时令牌加刷新令牌),MySQL、Redis 和 MinIO。前台是 React 19、TypeScript、Tailwind、i18next 和 Framer Motion;管理后台是 Vue 3 加 Element Plus。公开接口在 /api/v1 下,管理接口在 /api/v1/admin 下。
14 张表:产品及其参数、图片、视频、保养说明和标签;分类;首页栏目;联系方式;文件;用户与角色。Redis 缓存产品数据,事务回滚时由补偿逻辑把旧的缓存写回去。
双语,双向
译文存在哪里
每个需要翻译的字段按语言分列:name、name_en、name_ar,参数的名称和数值也各有两种语言。最初的设计是由后端根据请求头决定返回哪种语言。二期改成:接口一次返回所有语言,由前端挑选,译文为空时回退。切换语言不用重新请求,缓存也不用按语言拆。
从右向左
切到阿拉伯语时,把页面方向设为从右向左、语言设为阿拉伯语,由浏览器用同一套组件镜像整个版面。只有页脚需要单独加方向样式。界面文案用 i18next 管理,两种语言各 117 行。
三期里的变化
实时客服:做好了,又拆掉
一期包含基于 Spring STOMP 的实时客服。服务端日志显示已发送的消息,客户端就是收不到。分五个阶段排查后,原因是发给单个用户的目标地址要求 WebSocket 会话绑定一个 Principal;在握手时给每个连接分配独立身份后就好了。六天后,二期决定不要实时客服,改用 WhatsApp 和社交媒体,我从数据库开始整层删掉,连同两张表。
客户自己能运营的首页
二期把首页分成四个可配置的栏目,配置存成 JSON;三期让后台可以指定每个栏目展示哪些产品。保养说明和固定 9 项的参数表(型号、防水、表壳材质、机芯、表带材质、表壳直径、厚度、表带宽度、重量)都做成了双语。
三期提前交付
自定义颜色的标签替换了固定的「Featured」角标,联系方式有了完整的管理,生产环境改用带版本号的镜像。计划 3 月 3 日完成,2 月 16 日交付。
部署与结果
三个 Docker 服务(API、前台、后台),Compose 按环境覆盖配置。三期时搭过 Gitea Actions,后来改成在本地构建带版本号的 x86 镜像、推到私有镜像仓库:固定标签,不用 latest。
上线后 28 天内,自然搜索点击 1,200 次以上,展示 4,300 次以上(Search Console)。
回头看:架构文档里建议用服务端渲染照顾搜索,实际交付的是单页应用,标题和描述写死。这满足了「搜品牌名能找到」的要求;要让单个产品也排得上,还需要预渲染页面和 sitemap。
时间线
- 2025.06.05一期:建仓库,数据库设计与文档
- 2025.06.18查清并修好实时客服收不到消息
- 2025.06.24二期:去掉实时客服;保养说明和参数双语
- 2025.06.27首页拆成四个可配置栏目
- 2026.02.09三期开始
- 2026.02.16标签、首页选品、联系方式管理;提前交付
- 2026.02.27生产环境改用带版本号的镜像