fix: 合并本地静态资源性能修复
This commit is contained in:
parent
58528c16b9
commit
b5097cd2df
@ -167,7 +167,7 @@
|
|||||||
- 云数据库连接与系统数据库配置必须分开。显式开启 `UPSTREAM_STORED_CONFIG_ENABLED` 后,云数据库密码在 `system_database_connections` 中同时保留旧版 `APP_KEY` 密文用于紧急回滚,并使用系统数据库登录密码派生的密钥写入 `shared_password` 供受信任开发机共享;真实值仍不得进入代码、提交、PR、共享文档或日志。系统数据库固定主机、端口、库名和账号以密文团队资料保存在代码中,不含数据库密码;新人页面只允许填写系统数据库密码,由它解锁固定资料并自动启用 TLS。禁止恢复主机、端口、账号、TLS、证书或 CA 路径等新人表单。系统数据库密码轮换前必须先完成共享云数据库密文重加密、数据库备份和团队连接资料密文更新。只有隔离排错或尚未迁移的旧环境才允许显式开启本机覆盖,并通过私有 `DB_<来源>_*` 环境变量提供完整连接。中继开发机只保留已收紧 ACL 的隧道端点配置,不保存数据库密码。
|
- 云数据库连接与系统数据库配置必须分开。显式开启 `UPSTREAM_STORED_CONFIG_ENABLED` 后,云数据库密码在 `system_database_connections` 中同时保留旧版 `APP_KEY` 密文用于紧急回滚,并使用系统数据库登录密码派生的密钥写入 `shared_password` 供受信任开发机共享;真实值仍不得进入代码、提交、PR、共享文档或日志。系统数据库固定主机、端口、库名和账号以密文团队资料保存在代码中,不含数据库密码;新人页面只允许填写系统数据库密码,由它解锁固定资料并自动启用 TLS。禁止恢复主机、端口、账号、TLS、证书或 CA 路径等新人表单。系统数据库密码轮换前必须先完成共享云数据库密文重加密、数据库备份和团队连接资料密文更新。只有隔离排错或尚未迁移的旧环境才允许显式开启本机覆盖,并通过私有 `DB_<来源>_*` 环境变量提供完整连接。中继开发机只保留已收紧 ACL 的隧道端点配置,不保存数据库密码。
|
||||||
- 本地开发的标准入口只有“系统设置 → 数据库管理 → 系统数据库”。开发者只填写并验证同一套系统数据库密码,其他连接参数由项目自动设置;成功后系统用户、侧边栏、系统设置和云数据库登记必须继续使用该系统数据库。不得再把主业务连接的 `DB_HOST` 改回本机 MySQL 容器,否则会形成一套互不相干的本地数据,导致用户、配置、来源映射和业务结果与团队不一致。
|
- 本地开发的标准入口只有“系统设置 → 数据库管理 → 系统数据库”。开发者只填写并验证同一套系统数据库密码,其他连接参数由项目自动设置;成功后系统用户、侧边栏、系统设置和云数据库登记必须继续使用该系统数据库。不得再把主业务连接的 `DB_HOST` 改回本机 MySQL 容器,否则会形成一套互不相干的本地数据,导致用户、配置、来源映射和业务结果与团队不一致。
|
||||||
- 两个现有来源的标识和数据库映射固定为:`jiansun` 对应 `JianSun` 旧业务库,`operations_replica` 对应 `JSReportReplica` 的 `rpt` 只读视图。不得把 `shujuku`、本机测试库名或历史截图中的临时名称写回共享记录,也不得交换两个来源的账号或数据库名。
|
- 两个现有来源的标识和数据库映射固定为:`jiansun` 对应 `JianSun` 旧业务库,`operations_replica` 对应 `JSReportReplica` 的 `rpt` 只读视图。不得把 `shujuku`、本机测试库名或历史截图中的临时名称写回共享记录,也不得交换两个来源的账号或数据库名。
|
||||||
- 开发者拉取包含本规则的最新代码后,必须确保 `docker-compose.local.yml` 已挂载 `./modules:/var/www/html/modules`,并至少重建一次应用容器、清除 Laravel 配置缓存。禁止只重启旧镜像后根据旧模块结果判断共享配置无效。
|
- 开发者拉取包含本规则的最新代码后,由 Codex 运行不带参数的 `deploy/local-init.sh`。脚本根据 Dockerfile、依赖锁文件、前端源码和静态资源计算镜像指纹,构建输入变化或必要静态资源缺失时自动重建,并清除 Laravel 配置缓存;不得要求新人判断是否使用 `--build`。`docker-compose.local.yml` 必须继续挂载 `./modules:/var/www/html/modules`,禁止只重启旧镜像后根据旧模块或旧静态资源判断主线无效。
|
||||||
- 本机临时引导库的新增迁移由 `deploy/local-init.sh` 和新版容器入口自动补齐;该逻辑只允许在 `SYSTEM_DATABASE_MODE=bootstrap`、MySQL 驱动且主机为固定容器服务名 `mysql` 时运行。连接共享系统数据库后必须自动跳过,普通开发者不需要也不得自行执行 `php artisan migrate`;看到本机迁移 `Pending` 时应重新运行标准初始化脚本,而不是操作共享数据库。
|
- 本机临时引导库的新增迁移由 `deploy/local-init.sh` 和新版容器入口自动补齐;该逻辑只允许在 `SYSTEM_DATABASE_MODE=bootstrap`、MySQL 驱动且主机为固定容器服务名 `mysql` 时运行。连接共享系统数据库后必须自动跳过,普通开发者不需要也不得自行执行 `php artisan migrate`;看到本机迁移 `Pending` 时应重新运行标准初始化脚本,而不是操作共享数据库。
|
||||||
- Codex 排查开发机连接时,必须先确认当前主业务库仍是共享系统数据库,再确认 `UPSTREAM_STORED_CONFIG_ENABLED=true`、`UPSTREAM_LOCAL_CONFIG_ENABLED=false`,随后通过 `UpstreamSourceRegistry` 验证完整连接来自 `system_database_connections`。不得通过填写本机 `DB_JIANSUN_*`、`DB_OPERATIONS_REPLICA_*`、复制他人 `.env` 或统一 `APP_KEY` 来掩盖共享配置读取错误。
|
- Codex 排查开发机连接时,必须先确认当前主业务库仍是共享系统数据库,再确认 `UPSTREAM_STORED_CONFIG_ENABLED=true`、`UPSTREAM_LOCAL_CONFIG_ENABLED=false`,随后通过 `UpstreamSourceRegistry` 验证完整连接来自 `system_database_connections`。不得通过填写本机 `DB_JIANSUN_*`、`DB_OPERATIONS_REPLICA_*`、复制他人 `.env` 或统一 `APP_KEY` 来掩盖共享配置读取错误。
|
||||||
- 系统数据库统一保存应用数据、云数据库配置、上游数据字典和生产共享快照,但不是 SQL Server 网络代理。本地开发应用直接使用系统数据库中的连接资料访问两套上游;前提是该开发机网络本身能够到达数据库端点。
|
- 系统数据库统一保存应用数据、云数据库配置、上游数据字典和生产共享快照,但不是 SQL Server 网络代理。本地开发应用直接使用系统数据库中的连接资料访问两套上游;前提是该开发机网络本身能够到达数据库端点。
|
||||||
|
|||||||
@ -38,7 +38,7 @@ http://127.0.0.1:18088
|
|||||||
下面的命令由 Codex 在 Git Bash、WSL、macOS 或 Linux 终端中执行,不交给新人:
|
下面的命令由 Codex 在 Git Bash、WSL、macOS 或 Linux 终端中执行,不交给新人:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bash deploy/local-init.sh --build
|
bash deploy/local-init.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
脚本会:
|
脚本会:
|
||||||
@ -54,7 +54,7 @@ bash deploy/local-init.sh --build
|
|||||||
|
|
||||||
这个账号只存在于本机临时数据库中。系统数据库切换完成后,请使用系统数据库里属于自己的账号登录,不要继续把临时账号当作团队共享账号。
|
这个账号只存在于本机临时数据库中。系统数据库切换完成后,请使用系统数据库里属于自己的账号登录,不要继续把临时账号当作团队共享账号。
|
||||||
|
|
||||||
初始化中断后,Codex 直接重新执行 `bash deploy/local-init.sh`,脚本会复用已有镜像并从数据库初始化阶段继续。只有依赖、Dockerfile 或构建资源发生变化时才使用 `--build`;确认只启动现有镜像时可使用 `--skip-build`。
|
初始化中断后,Codex 直接重新执行 `bash deploy/local-init.sh`。脚本会根据 Dockerfile、依赖锁文件、前端源码和静态资源生成镜像指纹:输入未变化时复用镜像,发生变化或缺少必要静态资源时自动重建,并从数据库初始化阶段继续。`--build` 只用于强制重建;`--skip-build` 仅允许镜像指纹和静态资源均已通过校验时使用。
|
||||||
|
|
||||||
拉取新代码后再次运行该脚本时,本机临时引导库会自动更新;连接团队共享系统数据库后,这项自动更新会安全跳过。普通开发者任何时候都不需要执行 `php artisan migrate`,也不得对团队共享系统数据库自行迁移。
|
拉取新代码后再次运行该脚本时,本机临时引导库会自动更新;连接团队共享系统数据库后,这项自动更新会安全跳过。普通开发者任何时候都不需要执行 `php artisan migrate`,也不得对团队共享系统数据库自行迁移。
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ docker compose -f docker-compose.local.yml exec -u www-data app php artisan opti
|
|||||||
# 检查应用是否真正就绪
|
# 检查应用是否真正就绪
|
||||||
docker compose -f docker-compose.local.yml exec -u www-data app php artisan app:ready
|
docker compose -f docker-compose.local.yml exec -u www-data app php artisan app:ready
|
||||||
|
|
||||||
# 运行测试(首次需使用 local-init.sh --build 构建开发依赖)
|
# 运行测试(首次运行 local-init.sh 会自动构建开发依赖)
|
||||||
docker compose -f docker-compose.local.yml exec -u www-data app php artisan test --do-not-cache-result
|
docker compose -f docker-compose.local.yml exec -u www-data app php artisan test --do-not-cache-result
|
||||||
|
|
||||||
# 停止容器,但保留本地数据
|
# 停止容器,但保留本地数据
|
||||||
@ -149,7 +149,7 @@ docker compose -f docker-compose.local.yml start
|
|||||||
## 修改后的生效方式
|
## 修改后的生效方式
|
||||||
|
|
||||||
- PHP、配置、模块、页面模板和路由目录已经挂载到容器,通常清理缓存或重启应用即可;
|
- PHP、配置、模块、页面模板和路由目录已经挂载到容器,通常清理缓存或重启应用即可;
|
||||||
- Composer 依赖、前端依赖、Dockerfile 或构建资源发生变化时,执行 `bash deploy/local-init.sh --build`;
|
- Composer 依赖、前端依赖、Dockerfile 或构建资源发生变化时,重新执行 `bash deploy/local-init.sh`,脚本会自动重建;
|
||||||
- 修改 `runtime/.env` 后,执行:
|
- 修改 `runtime/.env` 后,执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
1. 启动 Docker Desktop;
|
1. 启动 Docker Desktop;
|
||||||
2. 在系统数据库页面输入负责人提供的系统数据库密码。
|
2. 在系统数据库页面输入负责人提供的系统数据库密码。
|
||||||
|
|
||||||
其余工作交给 Codex:拉取最新 `main`、执行 `bash deploy/local-init.sh --build`、处理本机引导库迁移、启动服务、清理缓存、打开 `http://127.0.0.1:18088`,并在连接后自动核对两套上游、数据字典和真实只读查询。Codex 不得把这些命令、迁移或排错步骤转交给新人执行。
|
其余工作交给 Codex:拉取最新 `main`、执行 `bash deploy/local-init.sh`、处理本机引导库迁移、启动服务、清理缓存、打开 `http://127.0.0.1:18088`,并在连接后自动核对两套上游、数据字典和真实只读查询。初始化脚本会比较当前构建输入与镜像指纹,并校验必要静态资源;需要更新时自动重建,不再要求新人判断是否添加 `--build`。Codex 不得把这些命令、迁移或排错步骤转交给新人执行。
|
||||||
|
|
||||||
首次登录账号由初始化脚本生成并保存在本机私有凭据文件中。连接系统数据库成功后,临时登录会自动失效,改用系统数据库中属于自己的正式账号登录。
|
首次登录账号由初始化脚本生成并保存在本机私有凭据文件中。连接系统数据库成功后,临时登录会自动失效,改用系统数据库中属于自己的正式账号登录。
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ return [
|
|||||||
'zoom' => [
|
'zoom' => [
|
||||||
'enabled' => false,
|
'enabled' => false,
|
||||||
],
|
],
|
||||||
'fontFamily' => '"Quicksand", sans-serif',
|
'fontFamily' => '"JetBrains Mono", "Microsoft YaHei", "PingFang SC", monospace',
|
||||||
'foreColor' => '#595959',
|
'foreColor' => '#595959',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
# 本地部署用 Dockerfile
|
# 本地部署用 Dockerfile
|
||||||
# 基于 deploy/Dockerfile,额外安装 composer 依赖与前端构建产物
|
# 基于 deploy/Dockerfile,额外安装 composer 依赖与前端构建产物
|
||||||
# 由 docker-compose.local.yml 使用
|
# 由 docker-compose.local.yml 使用
|
||||||
@ -66,29 +68,47 @@ RUN ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
|
|||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
# ---------------- 安装依赖 ----------------
|
||||||
|
# 锁文件先于源码复制,普通 PHP/Blade 改动不会让 Composer 和 npm 重新下载。
|
||||||
|
COPY composer.json composer.lock ./
|
||||||
|
RUN --mount=type=cache,target=/tmp/composer-cache \
|
||||||
|
COMPOSER_CACHE_DIR=/tmp/composer-cache composer install --prefer-dist \
|
||||||
|
--no-interaction \
|
||||||
|
--no-scripts \
|
||||||
|
--no-progress \
|
||||||
|
--no-ansi
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN --mount=type=cache,target=/root/.npm \
|
||||||
|
npm ci --no-audit --no-fund --legacy-peer-deps
|
||||||
|
|
||||||
# ---------------- 复制源码 ----------------
|
# ---------------- 复制源码 ----------------
|
||||||
COPY . /var/www/html
|
COPY . /var/www/html
|
||||||
COPY deploy/apache-vhost.conf /etc/apache2/sites-available/000-default.conf
|
COPY deploy/apache-vhost.conf /etc/apache2/sites-available/000-default.conf
|
||||||
COPY deploy/php.ini /usr/local/etc/php/conf.d/jiangling.ini
|
COPY deploy/php.ini /usr/local/etc/php/conf.d/jiangling.ini
|
||||||
COPY deploy/entrypoint.sh /usr/local/bin/jiangling-entrypoint
|
COPY deploy/entrypoint.sh /usr/local/bin/jiangling-entrypoint
|
||||||
|
COPY deploy/local-healthcheck.sh /usr/local/bin/jiangling-healthcheck
|
||||||
|
|
||||||
# ---------------- 安装 PHP 依赖(本地开发模式,包含 PHPUnit 等测试工具)----------------
|
RUN composer dump-autoload --no-scripts --optimize
|
||||||
RUN composer install --prefer-dist \
|
|
||||||
--no-interaction \
|
|
||||||
--no-scripts \
|
|
||||||
--no-progress \
|
|
||||||
--no-ansi \
|
|
||||||
&& composer dump-autoload --no-scripts --optimize
|
|
||||||
|
|
||||||
# ---------------- 构建前端资源 ----------------
|
# ---------------- 构建前端资源 ----------------
|
||||||
# 先装依赖(python3/make/g++ 让 node-sass 能编译)
|
|
||||||
RUN npm install --no-audit --no-fund --legacy-peer-deps
|
|
||||||
# 提升 node 堆内存上限;dev 模式无压缩,避免 mix/webpack 生产构建 OOM
|
# 提升 node 堆内存上限;dev 模式无压缩,避免 mix/webpack 生产构建 OOM
|
||||||
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
ENV NODE_OPTIONS="--max-old-space-size=2048"
|
||||||
RUN npm run dev
|
RUN npm run dev \
|
||||||
|
&& mkdir -p public/vendor/apexcharts public/vendor/js-cookie \
|
||||||
|
&& cp node_modules/apexcharts/dist/apexcharts.min.js \
|
||||||
|
public/vendor/apexcharts/apexcharts.min.js \
|
||||||
|
&& cp node_modules/js-cookie/dist/js.cookie.min.js \
|
||||||
|
public/vendor/js-cookie/js.cookie.js \
|
||||||
|
&& test -s public/js/manifest.js \
|
||||||
|
&& test -s public/js/vendor.js \
|
||||||
|
&& test -s public/css/app.css \
|
||||||
|
&& test -s public/mix-manifest.json \
|
||||||
|
&& test -s public/vendor/apexcharts/apexcharts.min.js \
|
||||||
|
&& test -s public/vendor/js-cookie/js.cookie.js
|
||||||
|
|
||||||
# ---------------- 目录与权限 ----------------
|
# ---------------- 目录与权限 ----------------
|
||||||
RUN chmod +x /usr/local/bin/jiangling-entrypoint \
|
RUN chmod +x /usr/local/bin/jiangling-entrypoint /usr/local/bin/jiangling-healthcheck \
|
||||||
&& mkdir -p \
|
&& mkdir -p \
|
||||||
storage/app/public \
|
storage/app/public \
|
||||||
storage/app/temp \
|
storage/app/temp \
|
||||||
@ -100,5 +120,10 @@ RUN chmod +x /usr/local/bin/jiangling-entrypoint \
|
|||||||
bootstrap/cache \
|
bootstrap/cache \
|
||||||
&& chown -R www-data:www-data storage bootstrap/cache public
|
&& chown -R www-data:www-data storage bootstrap/cache public
|
||||||
|
|
||||||
|
ARG LOCAL_BUILD_FINGERPRINT=unknown
|
||||||
|
|
||||||
|
LABEL com.caiwu.project="CaiWuXiTong" \
|
||||||
|
com.caiwu.local-build-fingerprint="$LOCAL_BUILD_FINGERPRINT"
|
||||||
|
|
||||||
ENTRYPOINT ["jiangling-entrypoint"]
|
ENTRYPOINT ["jiangling-entrypoint"]
|
||||||
CMD ["apache2-foreground"]
|
CMD ["apache2-foreground"]
|
||||||
|
|||||||
@ -9,6 +9,13 @@ ServerName localhost
|
|||||||
Require all granted
|
Require all granted
|
||||||
</Directory>
|
</Directory>
|
||||||
|
|
||||||
|
# 静态文件缺失时必须快速返回 404,不能交给 Laravel 触发登录重定向、
|
||||||
|
# 数据库连接和整页重复渲染。
|
||||||
|
RewriteEngine On
|
||||||
|
RewriteCond %{REQUEST_URI} ^/(public/|vendor/livewire/)
|
||||||
|
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
|
||||||
|
RewriteRule ^ - [R=404,L]
|
||||||
|
|
||||||
SetEnvIf Authorization "(.+)" HTTP_AUTHORIZATION=$1
|
SetEnvIf Authorization "(.+)" HTTP_AUTHORIZATION=$1
|
||||||
|
|
||||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||||
|
|||||||
21
deploy/local-healthcheck.sh
Normal file
21
deploy/local-healthcheck.sh
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
for required_asset in \
|
||||||
|
public/js/manifest.js \
|
||||||
|
public/js/vendor.js \
|
||||||
|
public/css/app.css \
|
||||||
|
public/mix-manifest.json \
|
||||||
|
public/vendor/apexcharts/apexcharts.min.js \
|
||||||
|
public/vendor/js-cookie/js.cookie.js
|
||||||
|
do
|
||||||
|
test -s "$required_asset"
|
||||||
|
done
|
||||||
|
|
||||||
|
runuser -u www-data -- php artisan app:ready --quiet
|
||||||
|
|
||||||
|
# Artisan 能先于 Apache 启动完成;必须同时确认真实 HTTP 入口已经响应,
|
||||||
|
# 防止初始化脚本在容器“看似健康”时过早访问并得到空响应。
|
||||||
|
curl --fail --silent --show-error --max-time 5 \
|
||||||
|
http://127.0.0.1:18088/auth/login \
|
||||||
|
>/dev/null
|
||||||
@ -64,6 +64,55 @@ set_env_value() {
|
|||||||
chmod 600 "$environment_file" 2>/dev/null || true
|
chmod 600 "$environment_file" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculate_local_image_fingerprint() {
|
||||||
|
{
|
||||||
|
printf '%s\0' \
|
||||||
|
.dockerignore \
|
||||||
|
composer.json \
|
||||||
|
composer.lock \
|
||||||
|
package.json \
|
||||||
|
package-lock.json \
|
||||||
|
webpack.mix.js \
|
||||||
|
tailwind.config.js \
|
||||||
|
presets.js \
|
||||||
|
safelist.txt \
|
||||||
|
manifest.json \
|
||||||
|
serviceworker.js \
|
||||||
|
deploy/Dockerfile.local \
|
||||||
|
deploy/apache-vhost.conf \
|
||||||
|
deploy/php.ini \
|
||||||
|
deploy/entrypoint.sh \
|
||||||
|
deploy/local-healthcheck.sh
|
||||||
|
|
||||||
|
find resources/assets resources/views resources/lang -type f -print0
|
||||||
|
find public -type f \
|
||||||
|
! -path 'public/js/*' \
|
||||||
|
! -path 'public/vendor/*' \
|
||||||
|
! -path 'public/css/app.css' \
|
||||||
|
! -name 'mix-manifest.json' \
|
||||||
|
-print0
|
||||||
|
} \
|
||||||
|
| sort -z \
|
||||||
|
| xargs -0 sha256sum \
|
||||||
|
| sha256sum \
|
||||||
|
| awk '{ print $1 }'
|
||||||
|
}
|
||||||
|
|
||||||
|
image_has_required_assets() {
|
||||||
|
docker run --rm --entrypoint sh "$app_image" -lc '
|
||||||
|
for required_asset in \
|
||||||
|
/var/www/html/public/js/manifest.js \
|
||||||
|
/var/www/html/public/js/vendor.js \
|
||||||
|
/var/www/html/public/css/app.css \
|
||||||
|
/var/www/html/public/mix-manifest.json \
|
||||||
|
/var/www/html/public/vendor/apexcharts/apexcharts.min.js \
|
||||||
|
/var/www/html/public/vendor/js-cookie/js.cookie.js
|
||||||
|
do
|
||||||
|
test -s "$required_asset" || exit 1
|
||||||
|
done
|
||||||
|
' >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
echo "==> 工作目录: $(pwd)"
|
echo "==> 工作目录: $(pwd)"
|
||||||
|
|
||||||
if ! command -v docker >/dev/null 2>&1; then
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
@ -118,14 +167,81 @@ if docker image inspect "$app_image" >/dev/null 2>&1; then
|
|||||||
image_exists=1
|
image_exists=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$build_mode" = "build" ] || { [ "$build_mode" = "auto" ] && [ "$image_exists" -eq 0 ]; }; then
|
local_image_fingerprint="$(calculate_local_image_fingerprint)"
|
||||||
echo "==> [1/5] 构建本地开发镜像..."
|
stored_image_fingerprint=""
|
||||||
docker compose -f "$compose_file" build app
|
image_contract_ok=0
|
||||||
elif [ "$build_mode" = "skip" ] && [ "$image_exists" -eq 0 ]; then
|
|
||||||
echo "错误:本地开发镜像不存在,不能使用 --skip-build。" >&2
|
if [ "$image_exists" -eq 1 ]; then
|
||||||
|
stored_image_fingerprint="$(
|
||||||
|
docker image inspect "$app_image" \
|
||||||
|
--format '{{ index .Config.Labels "com.caiwu.local-build-fingerprint" }}' \
|
||||||
|
2>/dev/null \
|
||||||
|
|| true
|
||||||
|
)"
|
||||||
|
|
||||||
|
if image_has_required_assets; then
|
||||||
|
image_contract_ok=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
build_reason=""
|
||||||
|
if [ "$build_mode" = "build" ]; then
|
||||||
|
build_reason="已要求强制构建"
|
||||||
|
elif [ "$image_exists" -eq 0 ]; then
|
||||||
|
build_reason="本地镜像不存在"
|
||||||
|
elif [ "$image_contract_ok" -ne 1 ]; then
|
||||||
|
build_reason="现有镜像缺少必要静态资源"
|
||||||
|
elif [ "$stored_image_fingerprint" != "$local_image_fingerprint" ]; then
|
||||||
|
build_reason="前端、依赖或运行镜像配置已经变化"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$build_mode" = "skip" ] && [ -n "$build_reason" ]; then
|
||||||
|
echo "错误:$build_reason,不能安全使用 --skip-build。" >&2
|
||||||
|
echo "请不带参数重新运行本脚本,由系统自动更新镜像。" >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$build_reason" ]; then
|
||||||
|
build_attempt=1
|
||||||
|
while [ "$build_attempt" -le 2 ]; do
|
||||||
|
local_image_fingerprint="$(calculate_local_image_fingerprint)"
|
||||||
|
echo "==> [1/5] 构建本地开发镜像:$build_reason。"
|
||||||
|
docker compose -f "$compose_file" build \
|
||||||
|
--build-arg "LOCAL_BUILD_FINGERPRINT=$local_image_fingerprint" \
|
||||||
|
app
|
||||||
|
|
||||||
|
built_image_fingerprint="$(
|
||||||
|
docker image inspect "$app_image" \
|
||||||
|
--format '{{ index .Config.Labels "com.caiwu.local-build-fingerprint" }}'
|
||||||
|
)"
|
||||||
|
current_image_fingerprint="$(calculate_local_image_fingerprint)"
|
||||||
|
|
||||||
|
if [ "$current_image_fingerprint" != "$local_image_fingerprint" ]; then
|
||||||
|
if [ "$build_attempt" -lt 2 ]; then
|
||||||
|
echo " 构建期间检测到源码变化,自动按最新源码再构建一次。"
|
||||||
|
build_reason="源码在构建期间发生变化"
|
||||||
|
build_attempt=$((build_attempt + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "错误:构建期间源码持续变化,请等待当前文件修改完成后重试。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$built_image_fingerprint" != "$local_image_fingerprint" ]; then
|
||||||
|
echo "错误:本地镜像版本标识与当前源码不一致。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! image_has_required_assets; then
|
||||||
|
echo "错误:本地镜像缺少必要静态资源,请查看上方构建日志。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
break
|
||||||
|
done
|
||||||
else
|
else
|
||||||
echo "==> [1/5] 复用现有本地开发镜像。需要更新依赖时请使用 --build。"
|
echo "==> [1/5] 本地开发镜像与当前源码一致,直接复用。"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "==> [2/5] 启动本地容器..."
|
echo "==> [2/5] 启动本地容器..."
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
# - mysql 只用于首次登录和填写“系统数据库”前的临时引导
|
# - mysql 只用于首次登录和填写“系统数据库”前的临时引导
|
||||||
# - 保存系统数据库后,用户、设置和云数据库登记都来自共享系统数据库
|
# - 保存系统数据库后,用户、设置和云数据库登记都来自共享系统数据库
|
||||||
#
|
#
|
||||||
# 首次启动:bash deploy/local-init.sh --build
|
# 标准启动:bash deploy/local-init.sh(自动判断是否需要重建镜像)
|
||||||
# 停止: docker compose -f docker-compose.local.yml down
|
# 停止: docker compose -f docker-compose.local.yml down
|
||||||
# 日志: docker compose -f docker-compose.local.yml logs -f app
|
# 日志: docker compose -f docker-compose.local.yml logs -f app
|
||||||
#
|
#
|
||||||
@ -35,7 +35,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- jiangling_net
|
- jiangling_net
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "runuser", "-u", "www-data", "--", "php", "artisan", "app:ready", "--quiet"]
|
test: ["CMD", "jiangling-healthcheck"]
|
||||||
interval: 15s
|
interval: 15s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|||||||
@ -289,7 +289,7 @@ JIANSUN_SLOW_QUERY_COOLDOWN=300
|
|||||||
|
|
||||||
本地首次启动时页面必须明确显示“本地临时引导库”,不能把本机 MySQL 标成财务系统配置数据库。开发者连接共享系统数据库时只输入密码;系统自动解锁固定地址、端口、库名、账号和 TLS 配置,只检查数据库能够连接且基础系统表存在。迁移版本、管理员权限映射、公司映射和云数据库共享密码均不得作为连接拦截条件。连接成功后立即清除临时账号 Session,并要求使用共享库正式账号重新登录;`super_admin` 和 `admin` 角色即使缺少单项权限关联,也不得被后台权限中间件阻挡。
|
本地首次启动时页面必须明确显示“本地临时引导库”,不能把本机 MySQL 标成财务系统配置数据库。开发者连接共享系统数据库时只输入密码;系统自动解锁固定地址、端口、库名、账号和 TLS 配置,只检查数据库能够连接且基础系统表存在。迁移版本、管理员权限映射、公司映射和云数据库共享密码均不得作为连接拦截条件。连接成功后立即清除临时账号 Session,并要求使用共享库正式账号重新登录;`super_admin` 和 `admin` 角色即使缺少单项权限关联,也不得被后台权限中间件阻挡。
|
||||||
|
|
||||||
开发者拉取新代码后,`deploy/local-init.sh` 和新版容器入口必须自动补齐本机临时引导库的新增迁移,不能把 `Pending` 状态交给新人处理。该自动逻辑必须同时校验 `SYSTEM_DATABASE_MODE=bootstrap`、MySQL 驱动和固定容器主机名 `mysql`;处于共享系统数据库模式时必须直接跳过,普通开发者不得执行或被提示执行共享数据库迁移。
|
开发者拉取新代码后,由 Codex 直接运行不带参数的 `deploy/local-init.sh`。脚本必须根据 Dockerfile、依赖锁文件、前端源码和静态资源计算镜像指纹,必要静态资源缺失或构建输入变化时自动重建;不得要求新人判断是否使用 `--build`。脚本和新版容器入口还必须自动补齐本机临时引导库的新增迁移,不能把 `Pending` 状态交给新人处理。迁移逻辑必须同时校验 `SYSTEM_DATABASE_MODE=bootstrap`、MySQL 驱动和固定容器主机名 `mysql`;处于共享系统数据库模式时必须直接跳过,普通开发者不得执行或被提示执行共享数据库迁移。
|
||||||
|
|
||||||
系统数据库连接测试从应用容器发起,但新人页面只显示密码输入框。主机、端口、库名、账号和 TLS 均来自使用系统数据库密码加密的团队固定连接资料;不得把这些高级连接项重新暴露给普通开发者。密码错误必须回到原页面显示中文提示,网络或数据库暂不可用也必须保留旧连接并显示中文原因,不得出现 500。
|
系统数据库连接测试从应用容器发起,但新人页面只显示密码输入框。主机、端口、库名、账号和 TLS 均来自使用系统数据库密码加密的团队固定连接资料;不得把这些高级连接项重新暴露给普通开发者。密码错误必须回到原页面显示中文提示,网络或数据库暂不可用也必须保留旧连接并显示中文原因,不得出现 500。
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ql-container {
|
.ql-container {
|
||||||
font-family: "Quicksand", sans-serif !important;
|
font-family: "JetBrains Mono", "Microsoft YaHei", "PingFang SC", monospace !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ql-editor p {
|
.ql-editor p {
|
||||||
@ -285,4 +285,4 @@
|
|||||||
|
|
||||||
.ql-snow .ql-hidden {
|
.ql-snow .ql-hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
@once
|
||||||
|
@push('chart_libraries')
|
||||||
|
<script src="{{ asset('public/vendor/apexcharts/apexcharts.min.js') }}"></script>
|
||||||
|
@endpush
|
||||||
|
@endonce
|
||||||
@ -31,9 +31,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/vue-html-editor.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/vue-html-editor.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
@stack('scripts_start')
|
@stack('scripts_start')
|
||||||
|
|
||||||
<script src="{{ asset('public/vendor/apexcharts/apexcharts.min.js') }}"></script>
|
@stack('chart_libraries')
|
||||||
|
|
||||||
@stack('charts')
|
@stack('charts')
|
||||||
|
|
||||||
|
|||||||
@ -24,9 +24,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/app.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/app.css?v=' . version('short')) }}" type="text/css">
|
||||||
@ -58,4 +55,3 @@
|
|||||||
|
|
||||||
@stack('head_end')
|
@stack('head_end')
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@ -22,9 +22,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|||||||
@ -22,9 +22,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|||||||
@ -12,7 +12,6 @@
|
|||||||
<base href="{{ config('app.url') . '/' }}">
|
<base href="{{ config('app.url') . '/' }}">
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/app.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/app.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
@stack('css')
|
@stack('css')
|
||||||
|
|||||||
@ -24,9 +24,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/vue-html-editor.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/vue-html-editor.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|||||||
@ -24,9 +24,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
@stack('scripts_start')
|
@stack('scripts_start')
|
||||||
|
|
||||||
<script src="{{ asset('public/vendor/apexcharts/apexcharts.min.js') }}"></script>
|
@stack('chart_libraries')
|
||||||
|
|
||||||
@stack('charts')
|
@stack('charts')
|
||||||
|
|
||||||
|
|||||||
@ -24,9 +24,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
@stack('scripts_start')
|
@stack('scripts_start')
|
||||||
|
|
||||||
<script src="{{ asset('public/vendor/apexcharts/apexcharts.min.js') }}"></script>
|
@stack('chart_libraries')
|
||||||
|
|
||||||
@stack('charts')
|
@stack('charts')
|
||||||
|
|
||||||
|
|||||||
@ -24,9 +24,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css//third_party/swiper-bundle.min.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|||||||
@ -20,9 +20,6 @@
|
|||||||
<!--Icons-->
|
<!--Icons-->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/fonts/material-icons/style.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|
||||||
<!-- Font -->
|
|
||||||
<link rel="stylesheet" href="{{ asset('public/vendor/quicksand/css/quicksand.css?v=' . version('short')) }}" type="text/css">
|
|
||||||
|
|
||||||
<!-- Css -->
|
<!-- Css -->
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/element.css?v=' . version('short')) }}" type="text/css">
|
||||||
<link rel="stylesheet" href="{{ asset('public/css/app.css?v=' . version('short')) }}" type="text/css">
|
<link rel="stylesheet" href="{{ asset('public/css/app.css?v=' . version('short')) }}" type="text/css">
|
||||||
|
|||||||
@ -37,6 +37,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('components.charts.apexcharts-loader')
|
||||||
|
|
||||||
@push('body_scripts')
|
@push('body_scripts')
|
||||||
{!! $charts['bar']->script() !!}
|
{!! $charts['bar']->script() !!}
|
||||||
{!! $charts['donut']->script() !!}
|
{!! $charts['donut']->script() !!}
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('components.charts.apexcharts-loader')
|
||||||
|
|
||||||
@push('body_scripts')
|
@push('body_scripts')
|
||||||
{!! $chart->script() !!}
|
{!! $chart->script() !!}
|
||||||
@endpush
|
@endpush
|
||||||
|
|||||||
@ -48,6 +48,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('components.charts.apexcharts-loader')
|
||||||
|
|
||||||
@push('body_scripts')
|
@push('body_scripts')
|
||||||
{!! $chart->script() !!}
|
{!! $chart->script() !!}
|
||||||
@endpush
|
@endpush
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('components.charts.apexcharts-loader')
|
||||||
|
|
||||||
@push('body_scripts')
|
@push('body_scripts')
|
||||||
{!! $chart->script() !!}
|
{!! $chart->script() !!}
|
||||||
@endpush
|
@endpush
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@include('components.charts.apexcharts-loader')
|
||||||
|
|
||||||
@push('body_scripts')
|
@push('body_scripts')
|
||||||
{!! $chart->script() !!}
|
{!! $chart->script() !!}
|
||||||
@endpush
|
@endpush
|
||||||
|
|||||||
187
tests/Unit/Deployment/LocalStaticAssetsTest.php
Normal file
187
tests/Unit/Deployment/LocalStaticAssetsTest.php
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Deployment;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Blade;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class LocalStaticAssetsTest extends TestCase
|
||||||
|
{
|
||||||
|
public function test_local_image_builds_and_checks_required_static_assets(): void
|
||||||
|
{
|
||||||
|
$dockerfile = file_get_contents(base_path('deploy/Dockerfile.local'));
|
||||||
|
$healthcheck = file_get_contents(base_path('deploy/local-healthcheck.sh'));
|
||||||
|
|
||||||
|
$this->assertIsString($dockerfile);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'com.caiwu.local-build-fingerprint="$LOCAL_BUILD_FINGERPRINT"',
|
||||||
|
$dockerfile
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'node_modules/apexcharts/dist/apexcharts.min.js',
|
||||||
|
$dockerfile
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'node_modules/js-cookie/dist/js.cookie.min.js',
|
||||||
|
$dockerfile
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertIsString($healthcheck);
|
||||||
|
foreach ([
|
||||||
|
'public/js/manifest.js',
|
||||||
|
'public/js/vendor.js',
|
||||||
|
'public/css/app.css',
|
||||||
|
'public/mix-manifest.json',
|
||||||
|
'public/vendor/apexcharts/apexcharts.min.js',
|
||||||
|
'public/vendor/js-cookie/js.cookie.js',
|
||||||
|
] as $requiredAsset) {
|
||||||
|
$this->assertStringContainsString($requiredAsset, $healthcheck);
|
||||||
|
}
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'runuser -u www-data -- php artisan app:ready --quiet',
|
||||||
|
$healthcheck
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'http://127.0.0.1:18088/auth/login',
|
||||||
|
$healthcheck
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_local_initializer_rebuilds_stale_or_incomplete_images(): void
|
||||||
|
{
|
||||||
|
$initializer = file_get_contents(base_path('deploy/local-init.sh'));
|
||||||
|
|
||||||
|
$this->assertIsString($initializer);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'calculate_local_image_fingerprint',
|
||||||
|
$initializer
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'image_has_required_assets',
|
||||||
|
$initializer
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'现有镜像缺少必要静态资源',
|
||||||
|
$initializer
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'--build-arg "LOCAL_BUILD_FINGERPRINT=$local_image_fingerprint"',
|
||||||
|
$initializer
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'构建期间检测到源码变化,自动按最新源码再构建一次',
|
||||||
|
$initializer
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'本地镜像缺少必要静态资源,请查看上方构建日志',
|
||||||
|
$initializer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_missing_static_files_are_not_routed_through_laravel(): void
|
||||||
|
{
|
||||||
|
$apache = file_get_contents(base_path('deploy/apache-vhost.conf'));
|
||||||
|
|
||||||
|
$this->assertIsString($apache);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'RewriteCond %{REQUEST_URI} ^/(public/|vendor/livewire/)',
|
||||||
|
$apache
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f',
|
||||||
|
$apache
|
||||||
|
);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
'RewriteRule ^ - [R=404,L]',
|
||||||
|
$apache
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_apexcharts_is_loaded_only_by_chart_views(): void
|
||||||
|
{
|
||||||
|
foreach ([
|
||||||
|
'admin',
|
||||||
|
'portal',
|
||||||
|
'print',
|
||||||
|
] as $layout) {
|
||||||
|
$scripts = file_get_contents(
|
||||||
|
resource_path("views/components/layouts/{$layout}/scripts.blade.php")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertIsString($scripts);
|
||||||
|
$this->assertStringContainsString("@stack('chart_libraries')", $scripts);
|
||||||
|
$this->assertStringNotContainsString(
|
||||||
|
"asset('public/vendor/apexcharts/apexcharts.min.js')",
|
||||||
|
$scripts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$loader = file_get_contents(
|
||||||
|
resource_path('views/components/charts/apexcharts-loader.blade.php')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertIsString($loader);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
"asset('public/vendor/apexcharts/apexcharts.min.js')",
|
||||||
|
$loader
|
||||||
|
);
|
||||||
|
|
||||||
|
$renderedLoader = Blade::render(<<<'BLADE'
|
||||||
|
@include('components.charts.apexcharts-loader')
|
||||||
|
@include('components.charts.apexcharts-loader')
|
||||||
|
@stack('chart_libraries')
|
||||||
|
BLADE);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
1,
|
||||||
|
substr_count($renderedLoader, 'public/vendor/apexcharts/apexcharts.min.js')
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
'views/widgets/line_chart.blade.php',
|
||||||
|
'views/widgets/donut_chart.blade.php',
|
||||||
|
'views/widgets/cash_flow.blade.php',
|
||||||
|
'views/widgets/bar_chart.blade.php',
|
||||||
|
'views/components/reports/summary/chart.blade.php',
|
||||||
|
] as $chartView) {
|
||||||
|
$view = file_get_contents(resource_path($chartView));
|
||||||
|
|
||||||
|
$this->assertIsString($view);
|
||||||
|
$this->assertStringContainsString(
|
||||||
|
"@include('components.charts.apexcharts-loader')",
|
||||||
|
$view
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_views_and_component_styles_do_not_depend_on_quicksand(): void
|
||||||
|
{
|
||||||
|
$headFiles = glob(
|
||||||
|
resource_path('views/components/layouts/*/head.blade.php')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertIsArray($headFiles);
|
||||||
|
$this->assertNotEmpty($headFiles);
|
||||||
|
|
||||||
|
foreach ($headFiles as $headFile) {
|
||||||
|
$head = file_get_contents($headFile);
|
||||||
|
|
||||||
|
$this->assertIsString($head);
|
||||||
|
$this->assertStringNotContainsString(
|
||||||
|
'public/vendor/quicksand/css/quicksand.css',
|
||||||
|
$head
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ([
|
||||||
|
base_path('config/apexcharts.php'),
|
||||||
|
public_path('css/third_party/vue-html-editor_custom.css'),
|
||||||
|
] as $componentStyle) {
|
||||||
|
$contents = file_get_contents($componentStyle);
|
||||||
|
|
||||||
|
$this->assertIsString($contents);
|
||||||
|
$this->assertStringNotContainsString('Quicksand', $contents);
|
||||||
|
$this->assertStringContainsString('JetBrains Mono', $contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,7 +30,7 @@ class WebProcessOwnershipTest extends TestCase
|
|||||||
|
|
||||||
$this->assertIsString($compose);
|
$this->assertIsString($compose);
|
||||||
$this->assertStringContainsString(
|
$this->assertStringContainsString(
|
||||||
'test: ["CMD", "runuser", "-u", "www-data", "--", "php", "artisan", "app:ready", "--quiet"]',
|
'test: ["CMD", "jiangling-healthcheck"]',
|
||||||
$compose
|
$compose
|
||||||
);
|
);
|
||||||
$this->assertMatchesRegularExpression(
|
$this->assertMatchesRegularExpression(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user