多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

TigShop双后端三前端架构解析:Spring Boot与ThinkPHP多端商城实践

TigShop双后端三前端架构解析:Spring Boot与ThinkPHP多端商城实践 简介TigShop开源商城系统是一套基于SpringBoot3与TP8后端、结合Vue3、TypeScript、Uniapp、Vite、Nuxt3、Element Plus前端框架的电商平台源码面向需要自建商城或研究现代前后端分离架构的开发者。资源包共2000个文件大小约105.23MB其中634个vue组件与598个js脚本构成主要前端逻辑303个css负责界面样式另有405个md文档提供阅读与开发指引以及sql数据库脚本、json配置和html页面等目录结构完整便于局部检索与整体学习。目前已有252人学习下载。包内附带安装配置、系统构建、功能模块介绍、接口说明等文档可指导从开发环境搭建到部署上线的完整流程源码基于分层模块组织适合学习SpringBoot3与Vue3的整合方式也能作为电商项目二次开发的参考起点。对于想掌握主流全栈技术选型或需要开源商城基座的开发者这是一份结构清晰、实用性较强的资源。1. TigShop 商城系统的第一印象双后端 三前端一套包把落地方式拆给你看第一次打开 TigShop 这个开源商城 zip 包时最意外的不是 Vue3、Nuxt3、Uniapp 三套前端齐活而是后端目录里同时躺着 Spring Boot 3 和 ThinkPHP 8 两套实现。这不是重复造轮子而是按场景分工Spring Boot 3 管订单、库存、支付这类强事务链路TP8 管 CMS、活动页这类高频改版的轻业务。对需要同时维护 PC 管理端、SSR 商城页和跨端小程序的团队来说这套包相当于把选型、对接和联调过程一起拆给你看。两个后端共用一套 MySQL 和 Redis三套前端共享 TypeScript 业务逻辑整个架构可以直接照着抄作业。如果你正在做多端商城改造或者需要一个前后端分离、多端分发的完整参考实现这个包很适合先解压跑通再按自己的业务改。后面各章我就按“拆结构 → 跑起来 → 二次开发 → 排坑”的顺序过一遍。2. TigShop 技术栈拆解双后端与三前端的定位、目录结构与运行前提2.1 双后端的边界Spring Boot 3 管钱TP8 管内容这个包最值得先看明白的是两个后端各自管什么。Spring Boot 3 端承载的是核心交易域——商品 SKU、购物车、订单状态机、支付回调、退款流程。这些链路对事务边界、分布式锁和消息补偿要求高Java 侧用 Transactional 配合 Redis 分布式锁是稳妥做法。TP8 端则负责运营域——首页装修、文章资讯、优惠券活动页、用户反馈这类需求经常要在一两天内改版上线PHP 的“改完即跑”节奏明显更顺手。选型时不要犯“一个后端包打天下”的错误。常见翻车场景是图省事把订单也放在 TP8 里写结果促销峰值时 MySQL 连接数被打满又不方便上成熟的分布式事务方案。反过来把活动页放 Spring Boot 里光是排版数据的表结构就要设计半天。TigShop 的做法更实际两套后端虽然业务独立但共用同一套 MySQL 逻辑库和 Redis通过 API 网关按路径分流/order、/pay 开头走 Java/cms、/activity 开头走 PHP。两个后端读写的是同一套核心表。比如用户表和订单表由 Java 端负责写入TP8 端在 CMS 后台展示用户列表、订单数量时只读这些表。为避免两边同时改同一行造成锁竞争他们遵循一个朴素约定写操作归属方唯一。Java 端写订单状态TP8 端写活动配置。如果业务上非要 PHP 改订单状态一律走 Java 端提供的 API而不是直接连表 UPDATE。这个约定不写进代码但要在团队规范里写明否则并发一上来就分不清是谁改了字段。2.2 三套前端为什么并存管理端、SSR 商城页与跨端 App 的分工Vue3 TypeScript Element Plus Vite 的组合管的是 PC 管理后台。Element Plus 的表格、表单、弹窗组件能快速搭出订单列表、SKU 编辑、权限分配这些中后台页面。Nuxt3 管的是商城前台它内置 SSR商品详情页能返回完整 HTML对搜索引擎友好Vue3 代码可以复用管理端封装的业务逻辑。Uniapp 是最后一条线同一套 Vue3 代码编译到微信小程序、H5 和 App适合团队没有精力维护三套原生客户端的情况。这套结构里 Vite 的角色容易被忽视。管理端和 Nuxt 端的构建都依赖 Vite而且都启用了 TypeScript 路径别名比如 / 指向 src 目录两端的 tsconfig.json 配置一致这直接影响到二次开发时复制组件是否会有路径报错。首次熟悉包时建议先看三个前端各自的 package.json scripts确认 dev、build、type-check 命令的写法。三端并非三份重复代码。包内通常把纯 TS 业务逻辑金额计算、促销规则判断、登录态封装放在各自 src/utils 下再通过一个 shared 目录共享。注意 Nuxt3 的 shared 目录不会自动映射到所有地方需要在 nuxt.config.ts 里配好 alias否则两端代码风格会越来越分叉——实际维护时最怕一个数字格式化函数同时存在三种写法。2.3 zip 解压后的目录结构与版本矩阵解压后根目录大致这样tigshop/ ├── tigshop-backend-java/ # Spring Boot 3 后端 ├── tigshop-server-php/ # ThinkPHP 8 后端 ├── tigshop-admin-web/ # Vue3 Element Plus 管理端 ├── tigshop-mobile-nuxt/ # Nuxt3 SSR 商城前台 ├── tigshop-uniapp/ # Uniapp 跨端工程 ├── doc/ │ ├── sql/ # 初始化脚本与增量脚本 │ └── api/ # 接口文档 └── README.md环境版本矩阵建议按下表核对版本不对会出现很多“玄学”问题实际都是依赖不兼容组件建议版本说明JDK17 及以上Spring Boot 3 要求基线PHP8.1 及以上TP8 类型增强依赖 8.1Node18 或 20 LTSVite 与 Nuxt3 的最低要求MySQL8.0字符集 utf8mb4排序规则 utf8mb4_unicode_ciRedis7.x缓存和分布式锁共用提示如果本机 PHP 版本还是 7.xTP8 端会在 composer install 阶段直接报语法错误先升级 PHP 再继续。SQL 初始化部分doc/sql 下一般会有 init.sql 和 upgrade 目录。常见做法是先建库再导入mysql -u root -p -e CREATE DATABASE tigshop DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql -u root -p tigshop doc/sql/init.sql逻辑说明CREATE DATABASE 显式指定 utf8mb4避免建表时继承 MySQL 默认的 latin1 排序规则插入 emoji 或生僻字时才不会报错。导入 init.sql 后如果有 upgrade 目录按文件名时间顺序依次导入里面通常是字段调整和索引补充。参数说明-e 用于执行无返回的 SQL 建库语句重定向符号 让 mysql 客户端读取 SQL 文件若升级脚本之间有依赖务必按文件名升序导入否则会报“字段不存在”的 SQL 错误。导入完成后做个快速体检确认核心表都建好了mysql -u root -p -e USE tigshop; SHOW TABLES; | head -30 mysql -u root -p -e USE tigshop; SHOW INDEX FROM tigshop_order;逻辑说明SHOW INDEX 主要看两个索引——订单号唯一索引防止重复单号用户 ID 普通索引加快订单列表按人查询。促销活动表则重点看 start_time、end_time 的普通索引按时间范围筛选是促销接口最频繁的查询条件。参数说明如果 init.sql 里没建这些索引促销高峰期全表扫描会把数据库拖垮。这条不如接口报错那么直观却是我见过最容易被忽略的隐患。3. 把 TigShop 跑起来双后端启动、三端联调与连通性验证3.1 先核对配置环境版本、数据库连接、Redis 与端口规划跑起来之前先做三件事核对环境版本、改配置、定端口。环境版本用三条命令扫一遍java -version php -v node -vJDK 必须是 17 及以上PHP 8.1 及以上Node 18 或 20 LTS。版本只是前提真正容易出问题的是后续配置文件里的连接信息。Spring Boot 端主要看 src/main/resources/application.ymlTP8 端看 .env 文件。两端的数据库连接必须指向同一个 tigshop 库Redis 默认用 0 号库即可。端口规划上我一般会避开默认值冲突按下面的表分配服务端口说明Spring Boot 3 API8080订单支付类接口TP8 API8081CMS 活动类接口Vue3 管理端 Vite dev5173Vite 默认Nuxt3 SSR3000Nuxt 默认Uniapp H5 dev5174避免与 5173 冲突提示Windows 下用 php think run 之前先确认防火墙放行了对应端口否则手机访问时会被系统防火墙静默拦截表现是请求超时而不是拒绝。3.2 Spring Boot 3 后端改数据源、启动并验证 JWTapplication.yml 里最需要动的是 spring.datasource 和 spring.data.redis。改完后用 Maven 包装器启动cd tigshop-backend-java ./mvnw spring-boot:run启动日志里看到“Started Application in xx seconds”后先别急着测业务接口按下面的顺序验证基础设施curl -X POST http://localhost:8080/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:admin123}逻辑说明该接口先查 MySQL 用户表再比对 BCrypt 哈希成功后把用户 ID 和角色写进 JWT 并存入 Redis返回的 accessToken 就是后续请求的凭证。参数说明密码字段按包内 init.sql 里预置的管理员账号填写默认账号通常在 README 里如果返回 401优先检查数据源里有没有导入 init.sql而不是怀疑代码。拿到 token 后继续验证鉴权链路curl http://localhost:8080/order/list?page1limit10 \ -H Authorization: Bearer 返回的token这一步能同时确认 Spring Security 拦截器、Redis token 校验、MySQL 查询三层是否打通。常见坑是 Redis 没起来表现是登录成功但访问业务接口时抛“Unable to connect to Redis”先 service redis start 再重试。3.3 TP8 后端composer 依赖、.env 配置与伪静态切换到 PHP 端后先装依赖cd tigshop-server-php composer install --no-dev --optimize-autoloader cp .env.example .env php think run --port 8081逻辑说明composer install 用于拉取 TP8 核心和中间件--no-dev 跳过调试工具包--optimize-autoloader 生成更快的类映射。.env.example 拷成 .env 后把数据库、Redis 连接改成和 Java 端一致。php think run 是内置开发服务器适合联调生产建议换 Nginx。参数说明--port 必须显式指定 8081避免和 Java 端 8080 冲突如果 PHP 代码里有 opcache 扩展改 .env 后记得重启 PHP 进程否则 .env 的缓存不会自动失效。验证 TP8 接口curl http://localhost:8081/api/cms/banner/list返回 JSON 数组即通过。若出现 404先看是不是伪静态没配对开发态下 php think run 自带路由解析生产 Nginx 的配置放到第 5 章排查。3.4 三端联调Vue3 管理端、Nuxt3 商城页与 Uniapp H5前端三端启动命令差异不大cd tigshop-admin-web npm install npm run devcd tigshop-mobile-nuxt npm install npm run devcd tigshop-uniapp npm install npm run dev:h5逻辑说明三端都依赖 npm 安装依赖但注意不要在三个目录里共用一个 node_modulesVue3 和 Nuxt3 的依赖版本互相独立硬套会引发依赖提升错乱。联调时最常改的是 API 地址配置。管理端通常在 .env.development 里配VITE_API_BASE_URLhttp://localhost:8080/api VITE_CMS_API_URLhttp://localhost:8081/apiNuxt3 端在 nuxt.config.ts 里用 runtimeConfig 注册export default defineNuxtConfig({ runtimeConfig: { public: { apiBase: http://localhost:8080/api, cmsBase: http://localhost:8081/api } } })逻辑说明管理端走订单交易接口商城页需要同时拉取商品和 CMS 装修数据所以两个地址都要配置。参数说明Uniapp 端在 src/config/index.ts 里通常按运行平台区分我一般这样写const platform import.meta.env.VITE_PLATFORM || h5 export const API_BASE platform mp-weixin ? https://api.example.test : http://localhost:8080/api逻辑说明小程序端要求 HTTPS 并配置合法域名开发期可以直接勾选“不校验合法域名”但真机预览必须用局域网 IPH5 端 localhost 就够用。启动完成后用一条链路验证全通管理端登录后F12 Network 出现 /promo/list 请求状态 200。Nuxt3 打开 http://localhost:3000右键“查看网页源代码”页面源码里能搜到商品标题和价格字符串说明 SSR 预取生效。Uniapp H5 控制台能打印出 CMS banner 的 JSON 数组。三者数据都来自同一套 MySQL任何一个页面报错都先回查对应后端的日志而不是先改前端代码。这一条规则帮我节省了大量排查时间。4. 二次开发实战从后端接口到三端页面的完整改动链路4.1 给 Spring Boot 3 端加一个“限时促销”列表接口先做后端的接口扩展这是前后端联调的基础。为什么先画后端因为这套商城是前后端分离的接口返回结构code / data / message是两端之间的契约。TigShop 里统一用 Result 包装我们自己加接口时也要保持同样结构前端 axios 拦截器才能直接解析。假设要加促销活动列表典型做法是建立 promo 模块实体、Mapper、Service、Controller 四层。实体简化如下Entity Table(name promo_activity) public class PromoActivity { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String title; private LocalDateTime startTime; private LocalDateTime endTime; private Integer status; }逻辑说明Table 映射数据库 promo_activity 表字段名遵循下划线转驼峰约定MyBatis-Plus 或 Spring Data JPA 都能自动映射。参数说明status 建议用 0 未开始、1 进行中、2 已结束不要用 Boolean促销这类业务大概率会扩展状态比如提前终止、已撤下。Controller 层暴露分页查询RestController RequestMapping(/promo) public class PromoController { GetMapping(/list) public ResultPageResultPromoActivity list( RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int limit) { PagePromoActivity p new Page(page, limit); return Result.ok(promoService.page(p)); } }逻辑说明defaultValue 保证了前端不传页码时也能返回第一页。分页对象直接传给 service避免手动写 limit 偏移量的低级错误。参数说明page 从 1 开始limit 建议做上限限制比如 limit 100 时强制按 100 处理防止有人恶意拖全表。启动 Java 端后用 curl 验证curl http://localhost:8080/promo/list?page1limit10 \ -H Authorization: Bearer token拿到 JSON 后就可以交前端对接。这一步能少掉很多前后端扯皮因为字段名、嵌套结构都是后端定的前端照单接。4.2 TP8 端扩展CMS 活动位接口与权限中间件PHP 端加接口更轻快。在 app/controller/Cms 下新增一个 Activity.php?php declare(strict_types1); namespace app\controller\Cms; use support\Request; use support\Response; use app\service\ActivityService; class Activity { public function list(Request $request): Response { $page (int) $request-get(page, 1); $limit min((int) $request-get(limit, 10), 100); $data ActivityService::page($page, $limit); return json([code 0, data $data]); } }逻辑说明declare(strict_types1) 开启严格类型后字符串数字不会自动转 int适合接口层强制类型。min 函数直接给 limit 兜底避免全表查询。参数说明get 第二个参数是默认值(int) 强转在请求参数为非数字时会得到 0可以在入口做参数校验TP8 有 validate 机制生产环境建议补上。定义路由时单独挂权限中间件Route::get(/cms/activity/list, [Activity::class, list])-middleware(AuthMiddleware::class);逻辑说明中间件和 Java 端拦截器思路一样AuthMiddleware 里检查 header 里的 Token不一致则返回 401。这里要注意 Java 端 JWT 和 TP8 端 token 体系是否共用如果共用同一 Redis key 规则两端要配置一致的签名密钥。参数说明中间件只挂在该路由上不要全局挂载以免 banner 这类公开接口也要登录态前端首屏 SSR 会拿不到数据。4.3 管理端页面改版Element Plus 表格与搜索栏新增列表页时Element Plus 的 el-table 是最常用的组件script setup langts interface PromoItem { id: number title: string startTime: string endTime: string status: number } const list refPromoItem[]([]) const total ref(0) const query reactive({ page: 1, limit: 10 }) async function loadData() { const { data } await http.get(/promo/list, { params: query }) list.value data.list total.value data.total } onMounted(loadData) /script template el-table :datalist v-loadingloading el-table-column propid labelID width80 / el-table-column proptitle label活动名称 / el-table-column propstartTime label开始时间 / el-table-column label状态 template #default{ row } el-tag :typerow.status 1 ? success : info {{ row.status 1 ? 进行中 : 已结束 }} /el-tag /template /el-table-column /el-table /template逻辑说明script setup 语法下 TypeScript 接口定义先声明 PromoItem编译器就能推断表格行类型避免 any 满天飞。http 是封装好的 axios 实例统一带了 Authorization 头。参数说明status 的展示用 template 插槽做自定义渲染比在数据里预先拼字符串更适合后续扩展比如加“未开始”状态只需改三元表达式。loading 状态需要自己在 loadData 里控制Element Plus 的 v-loading 指令绑定了布尔变量后表格区域会出现遮罩避免用户看到半截数据。4.4 Nuxt3 商城页服务端预取促销数据Nuxt3 页面里用 useFetch 实现 SSR 预取script setup langts const { data: promos } await useFetch(/api/promo/list, { baseURL: http://localhost:8080, query: { page: 1, limit: 4 } }) /script template section h2限时促销/h2 div v-forp in promos.list :keyp.id classpromo-card p{{ p.title }}/p time{{ p.startTime }}/time /div /section /template逻辑说明useFetch 在服务端渲染阶段就会请求 Java 后端把数据连同 HTML 一起输出用户首屏不用等前端再发一次 Ajax。参数说明baseURL 要指向 Java 端地址query 参数会拼到 URL 上。如果促销数据不要求首屏 SEO也可以改用 $fetch 在客户端按需加载减少服务端压力。4.5 Uniapp 跨端适配条件编译改登录态Uniapp 端最烦的是不同平台 API 差异。这套包用的是条件编译方案以登录态封装为例// src/utils/auth.ts import { getStorageSync, setStorageSync } from dcloudio/uni-app export function getToken(): string { // #ifdef H5 return localStorage.getItem(tigshop_token) || // #endif // #ifndef H5 return getStorageSync(tigshop_token) // #endif } export function setToken(token: string): void { // #ifdef H5 localStorage.setItem(tigshop_token, token) // #endif // #ifndef H5 setStorageSync(tigshop_token, token) // #endif }逻辑说明#ifdef H5 只在 H5 平台编译小程序和 App 走 getStorageSync避免在非浏览器环境直接操作 localStorage 抛错。参数说明条件编译是注释形式的预处理指令编辑器里默认高亮但复制到非 Uniapp 项目时会变成死代码注意识别。5. 避坑与常见问题排查把这套多端商城常见的坑一次说完5.1 两个后端抢 8080 端口服务起不来现象先启动 Java 端没问题再启动 TP8 端时终端报“Address already in use”或者反过来 Java 端被按到 8081 上导致后面接口全串。极端时 Vite 也会来凑热闹5173 被别的项目占掉管理端白屏。原因两端默认配置文件里都写着 8080 或 8000同时跑开发服务器必然冲突。解决按第 3 章的端口规划给 TP8 显式指定 8081且两端 API 里的 baseURL 都要写对端口。我一般在启动前先执行lsof -i :8080 -i :8081确认端口没被占再起服务。Windows 下用 netstat -ano | findstr :8080 代替 lsof。若本机 8080 被其他进程占用改 Java 端 server.port8082 也行但记得同步修改三个前端的 API 配置。5.2 前端联调时登录成功后刷新就掉登录态现象管理端输入账号密码拿到 token跳转列表页正常按 F5 刷新后页面跳回登录页。浏览器 Application 面板里看不到预期的凭证。原因浏览器跨域请求下后端响应里的 Set-Cookie 属性不正确。Java 端配置了 CORS 允许跨域但 Cookie 的 SameSite 默认 Lax跨端口访问时浏览器选择不写入。解决开发环境最省事的做法是不用 Cookie前端把 JWT 放 localStorage请求头 Authorization 携带。如果业务必须用 Cookie后端需设置 SameSiteNone; Secure同时要求 HTTPS本地联调成本会高很多。从实际经验看TigShop 这类前后端分离商城用 Authorization 头更稳也方便 Uniapp 端复用同一套鉴权代码。5.3 Nuxt3 构建时内存溢出或改完代码不生效现象npm run build 过程中报“JavaScript heap out of memory”npm run dev 改完页面代码后浏览器里还是旧数据。原因前者是 Nuxt3 客户端打包时默认堆内存不够常见于 Node 18 且项目里引用了大量 Element Plus 组件后者是 .nuxt 缓存目录没清理或 Vite 依赖预构建缓存失效。解决构建时显式扩大 Node 堆内存NODE_OPTIONS--max-old-space-size4096 npm run build开发态下改完代码先清缓存再重启rm -rf .nuxt node_modules/.vite npm run devOOM 之前日志会先出现“Reached heap limit”标识看到这个再调大数值清缓存是玄学但确实有效。从那次之后我每次新建 Nuxt3 页面都会等编译日志跑完再切浏览器不再凭直觉刷新。5.4 Uniapp 真机调试连不上本地后端现象H5 端页面数据正常微信开发者工具里接口全部报“request:fail”或者在手机预览时白屏控制台是网络层错误。原因H5 访问 localhost 指向电脑本机但开发者工具和真机运行时localhost 指向它们自己。开发者工具没勾选跳过域名校验或手机和电脑不在同一局域网。解决把 API 地址临时改成电脑的局域网 IP并让手机和电脑连同一 Wi-Fi。微信开发者工具右上角“详情-本地设置”勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。Android 模拟器特殊场景下要用 10.0.2.2 代替局域网 IP。改完 settings.json 里的代理后重启开发者工具再试。5.5 TP8 生产环境伪静态配置不对导致接口 404现象本地 php think run 接口正常部署到 Nginx 后访问 /api/cms/banner/list 返回 404加 index.php 又能访问。原因Nginx 的 location 配置没把请求转发给 ThinkPHP 的入口文件PHP 没拿到真实路由。解决在 server 块里配置标准的 TP8 伪静态规则location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php8.1-fpm.sock; }逻辑说明try_files 先找静态文件找不到就把请求交给 index.php 解析由 TP8 路由重新分发。参数说明fastcgi_pass 要和实际 PHP-FPM 版本一致如果装了 php8.2则路径也要换成 php8.2-fpm.sock否则 502。这一步配完记得 nginx -t nginx -s reload并到 /var/log/nginx/error.log 确认错误码已经变化。6. 进阶给 TigShop 加一层轻量可观测性与多环境构建跑通只是第一步商城上线后的稳定性更值钱。我这边会优先给这套架构加一个轻量可观测层不用额外部署链路追踪系统。Spring Boot 3 端引入 Actuator 和 Micrometer把接口耗时、JVM 内存、数据库连接池用量暴露成 Prometheus 指标。依赖加完后在 application.yml 里开放端点management: endpoints: web: exposure: include: health,metrics,prometheus这段配置把 /actuator/prometheus 端点暴露出来Prometheus 抓取后就能按 application 维度看接口吞吐和耗时分布。注意只放开这个端点不要用 include: * 把全部 actuator 端点暴露到公网。同时给 RestController 加一个注解切面记录请求路径、耗时和状态码。TP8 端在中间件里做同样的事把 /api 前缀的请求耗时写入 Redis 的 zset按分钟统计 P95。前端三端则统一在 axios 拦截器上报页面性能数据。多环境构建也是这套包容易踩的坑。三个前端各自有 dev 和 prod 配置最怕上线时有人手改 API 地址导致线上请求打到本地。我一般会在管理端和 Nuxt3 里加一层构建时校验prod 构建时 if 判断地址不是 https 就直接抛错从源头堵住这个失误。Uniapp 端则在 package.json 里预设 build:mp-weixin 和 build:app 两个脚本避免每次手动切换平台。这套可观测层加上后验证链路就完整了登录拿 token 访问 /promo/listPrometheus 里能看到该接口的请求量和 P95 耗时TP8 的 zset 能看到 CMS 接口流量分布Nuxt3 页面响应时间直接进入日志。三端数据对得上再谈上线。有回做促销活动运营反馈图片加载慢查了 Prometheus 发现 Java 端接口耗时正常问题出在静态资源链路。从那以后我每次部署 TigShop 都强制走一遍“登录 → 查指标 → 看日志”三连先确认可观测数据齐全再放量。如果你手里正好有这份 zip 包建议也先把这三步当成固定习惯。希望帮到你。本文还有配套的精品资源点击获取
返回列表