项目:ERP-CRM | 版本:v1.1 | 生成日期:2026-10-05 | 依据:源码分析(v1.1:2026-10-06 一致性回刷,与 16-20 篇对齐)
| 组件 | 版本要求 | 来源依据 | 说明 |
|---|---|---|---|
| JDK | 17 | erp-crm/pom.xml(java.version=17)、Dockerfile 用 eclipse-temurin:17-jre | 启动脚本 JVM 参数已按 JDK17 修正(erpcrm.sh 注释:UseParallelOldGC 已移除,改用统一日志,GC 交给默认 G1) |
| Maven | 3.9+ | Dockerfile 构建阶段 maven:3.9-eclipse-temurin-17 | 仓库配置阿里云镜像(pom repositories) |
| MySQL | 8.4 | ops/deploy/docker-compose.yml(image: mysql:8.4)、CI 服务容器同版本 | 需 utf8mb4;主库 + 租户库同实例即可 |
| Redis | 7.x | compose redis:7-alpine | 必须运行(JWT 会话/限流/锁定都依赖);本机默认 127.0.0.1:6379 无密码 |
| Node / pnpm | ≥22.22.1 / 10.x | erpcrm-ui/package.json engines、CI corepack prepare pnpm@10.34.5 | 实测 v24.21.0 可用;preinstall 强制 pnpm(npx only-allow pnpm) |
| 浏览器 | 非 IE11 | .browserslistrc、vite build target es2020 | — |
端口规划:后端 9090(业务)、9091(Actuator/Prometheus,生产应绑内网);前端 dev 80 / 生产 nginx 80。
# 建库(主库 + 一个测试租户库)
mysql -uroot -p -e "CREATE DATABASE \`erp-crm-master\` DEFAULT CHARACTER SET utf8mb4; \
CREATE DATABASE \`ryt_nelub7xr\` DEFAULT CHARACTER SET utf8mb4;"
# 灌入结构(主库 5 表 + 种子;租户库 40 表 + 菜单/字典/角色种子)
mysql -uroot -p --default-character-set=utf8mb4 erp-crm-master < erp-crm/sql/erp-crm-master.sql
mysql -uroot -p --default-character-set=utf8mb4 ryt_nelub7xr < erp-crm/src/main/resources/init-sql-script/erp-crm-tenant-sample.sql关键一步——修正 tenant1 的登记行(种子里的口令是 ROTATED 占位、URL 指向历史库)。参照 CI 脚本 erp-crm/.github/workflows/backend.yml 第 71-81 行的同源逻辑:
UPDATE `erp-crm-master`.master_tenant
SET username='root', password='<你的root密码>',
expiration_date = DATE_ADD(NOW(), INTERVAL 365 DAY),
url='jdbc:mysql://127.0.0.1:3306/ryt_nelub7xr?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8'
WHERE tenant='tenant1';
-- 租户模板不含用户数据,补插 admin(密码 admin123 的 BCrypt 哈希)
INSERT INTO `ryt_nelub7xr`.sys_user (dept_id, user_name, nick_name, password, status, del_flag, create_by, create_time)
SELECT 100, 'admin', '超级管理员', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', 'init', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `ryt_nelub7xr`.sys_user WHERE user_name='admin' AND del_flag='0');cd erp-crm
cp .env.example .env # 然后逐项填写(.env 已 gitignore,不进版本库)
# 生成密钥:
# openssl rand -base64 64 → EC_TOKEN_SECRET(≥64 字节,jjwt HS512 硬性要求,缺失/过短启动失败)
# openssl rand -base64 32 → EC_TENANT_PASSWORD_KEY(租户库口令加密;本地可不配=加解密直通)cd erp-crm
mvn package -DskipTests # 构建出 target/erpcrm.jar(spring-boot-maven-plugin repackage)
java -jar target/erpcrm.jar # 前台启动;或后台:
nohup java -jar target/erpcrm.jar > /tmp/erpcrm.log 2>&1 &
# 或使用启停脚本(自动 source 同目录 .env,支持 start|stop|restart|status)
./erpcrm.sh start # Linux/Mac(JDK17 JVM 参数已修正)
# Windows:双击 erpcrm.bat,菜单选 [1]start(jps 定位进程,javaw 启动)AGENTS.md 提醒:不要用
mvn -q package 2>&1 | tail之类管道吞退出码——package失败时旧 jar 仍在,容易误判"启动成功但行为没变"。
验证:http://localhost:9090/swagger-ui/index.html(接口文档)、http://localhost:9090/druid/index.html(Druid 控制台,账号取 .env 的 EC_DRUID_USER/EC_DRUID_PASSWORD,白名单 EC_DRUID_ALLOW 默认仅本机)、http://localhost:9091/actuator/health。
cd erpcrm-ui
pnpm install # 首次;换 node 版本后如遇诡异构建失败:rm -rf node_modules && pnpm install
pnpm dev # http://localhost:80 ,/dev-api → 127.0.0.1:9090vite.config.ts 的代理已做容错:后端不可用时代理 15 秒超时并返回 502 JSON("后端服务不可用,请确认 erpcrm 服务 :9090 已启动"),不会打崩 dev server。登录:http://localhost → 租户 tenant1 / 账号 admin / 密码 admin123。
cd erp-crm && mvn test # 13 个测试类;依赖本机 MySQL/Redis 与 .env(CI 同套件)
cd ../erpcrm-ui
pnpm lint && pnpm typecheck && pnpm test # eslint(0 警告) + vue-tsc + vitest加载机制:erp-crm/src/main/resources/application.yml 以 ${EC_*} 占位符引用,取值来源优先级 = 真实环境变量 > .env(经 spring.config.import: optional:file:./.env[.properties] 导入,erpcrm.sh/docker-compose 也会 source/export)。前端另有独立三份 env 文件(erpcrm-ui/.env{,.development,.production})。
| 变量(后端) | 用途 | 必填 | 默认/示例 |
|---|---|---|---|
EC_TOKEN_SECRET | JWT 签名密钥(HS512 ≥64 字节) | 必填,缺失启动失败 | openssl rand -base64 64 |
EC_DB_USERNAME / EC_DB_PASSWORD | 主库凭据 | 必填 | root / *** |
EC_DB_URL | 主库 URL(可覆盖) | 否 | jdbc:mysql://localhost:3306/erp-crm-master?...(application-druid.yml 默认值) |
EC_TENANT_DB_HOST / EC_TENANT_DB_PORT | 租户建库目标 | 否 | localhost / 3306 |
EC_TENANT_DB_USERNAME / EC_TENANT_DB_PASSWORD | 注册建库凭据(最高敏感级) | 必填 | root / *** |
EC_TENANT_DB_PREFIX | 租户库名前缀 | 否 | ec_ |
EC_TENANT_PASSWORD_KEY | master_tenant 口令 AES-256-GCM 加密密钥 | prod 必配(EcProdConfigVerifier);本地/CI 不配=直通 | openssl rand -base64 32 |
EC_REDIS_HOST(容器)/ EC_REDIS_PASSWORD | Redis 连接 | 否 | 127.0.0.1(yml)/ 空 |
EC_UPLOAD_DIR | 上传目录(yml ec.profile) | 否 | ./upload |
EC_DRUID_USER / EC_DRUID_PASSWORD / EC_DRUID_ALLOW | Druid 控制台账号与 IP 白名单 | 必填(yml 无默认) | ec / *** / 127.0.0.1 |
EC_BACKUP_PASSWD | 备份加密口令(ops/backup.sh;否则读 ~/.erpcrm-backup-passwd) | 备份时必居其一 | openssl rand -base64 32 |
后端可调参数(application.yml / application-druid.yml):token.expireTime(JWT 分钟数,当前 120,剩余 ≤20 分钟滑动续期)、ec.tenant.expiration-remind-days(默认 7)、ec.tenant.expiration-job-cron(默认 0 0 3 * * ?)、ec.datasource.idle-evict-minutes(默认 30)、spring.datasource.druid.tenant.*(租户池规格 initialSize/minIdle=1、maxActive=10——勿改回主库规格)、server.port=9090、management.server.port=9091、xss.urlPatterns(/system、/monitor、/tool)、上传上限 10MB/20MB。
前端 env(erpcrm-ui):
| 变量 | 文件 | 说明 |
|---|---|---|
VITE_PORT=80 | .env / .env.production | dev 端口(两处保持一致防漂移,.env.production 注释明示) |
VITE_APP_BASE_API | .env.development=/dev-api、.env.production=/prod-api | 接口前缀(vite 代理 / nginx 反代) |
VITE_ROUTER_HISTORY="hash" | .env* | 路由历史模式 |
VITE_COMPRESSION="both"、VITE_CDN=false | .env.production | 构建产物 gzip+br 压缩;CDN 关闭 |
生产外置配置:复制 erp-crm/config/application-prod.yml.example 为 config/application-prod.yml,以 SPRING_PROFILES_ACTIVE=prod,druid 启动——该模板关闭 Druid 控制台与 springdoc、重定向 ec.profile 到 /data/erpcrm/upload;prod 激活时 EcProdConfigVerifier 逐项校验必需密钥,缺失即 fail-fast(erp-crm/src/main/java/com/ec/common/config/EcProdConfigVerifier.java,测试 EcProdConfigVerifierTest)。
erp-crm/Dockerfile:两阶段——maven:3.9-eclipse-temurin-17 先只拷 pom.xml 执行 dependency:go-offline(利用层缓存离线依赖),再拷 src 执行 mvn package -DskipTests("测试由 CI 负责,镜像内跳过");运行阶段 eclipse-temurin:17-jre,EXPOSE 9090/9091,ENTRYPOINT java $JAVA_OPTS -jar app.jar。erpcrm-ui/Dockerfile:node:22-alpine + corepack 钉 pnpm 10.34.5 → pnpm install --frozen-lockfile && pnpm build;运行阶段 nginx:stable-alpine 拷 dist 与 nginx.conf(/static/ 30 天 immutable 缓存、/prod-api/ 反代 host.docker.internal:9090(client_max_body_size 50m)、SPA fallback、gzip)。cd erp-crm/ops/deploy
docker compose --env-file ../../.env up -d --build
# 访问:前端 http://localhost(nginx /prod-api → 后端);后端 http://localhost:9090;指标 http://localhost:9091编排包含五个服务:mysql(8.4,root 口令取 EC_DB_PASSWORD,docker-entrypoint-initdb.d 自动灌 01-master.sql + 02-tenant.sql,数据落命名卷 mysql-data)、redis(7-alpine,healthcheck)、db-init(一次性任务:把 tenant1 修正为指向容器内 MySQL 并补建 admin——注释明示"与 CI 同源逻辑";heredoc 加引号防 bcrypt 哈希 $2a$ 被 shell 展开)、backend(depends_on mysql/redis 健康 + db-init 完成;容器网络地址用 EC_DB_URL=jdbc:mysql://mysql:3306/...、EC_TENANT_DB_HOST=mysql、EC_REDIS_HOST=redis 覆盖 .env 的本机地址)、frontend(extra_hosts: host.docker.internal:host-gateway 兜底 Linux)。注意:本机此前无 Docker daemon,该 compose 未实测,仅过 YAML 语法校验(AGENTS.md 记录,属已知风险)。
erp-crm/ops/ 提供 Prometheus(prometheus/prometheus.yml + rules/)与 Grafana(grafana/dashboards + provisioning)配置,抓取后端 9091 /actuator/prometheus;业务指标清单见 01 篇第 10 条(ErpMetrics)。
erp-crm/ops/backup.sh 逐库 mysqldump --single-transaction --routines --triggers --hex-blob → gzip → openssl enc -aes-256-cbc -pbkdf2 加密,产物 <库名>_时间戳.sql.gz.enc 落 db-backup/auto/(默认,--out 可改),chmod 600;库清单 = 主库 + master_tenant 中 status='1' 且 database_name 非空的库名(与 SHOW DATABASES 求交集——指向不存在库的历史租户行自动跳过);保留期 --keep-days 默认 14 天。crontab 示例(脚本头部):30 2 * * * /path/to/erp-crm/ops/backup.sh >> /tmp/erpcrm-backup.log 2>&1。解密命令:openssl enc -d -aes-256-cbc -pbkdf2 -pass file:$HOME/.erpcrm-backup-passwd -in <file> | gunzip > x.sql。安全细节:凭据经 MYSQL_PWD 环境变量传递(不进进程列表);管道失败时删除 openssl 可能写出的空壳文件防"空备份当真"。MasterTenantPasswordMigrator 自动把 master_tenant 中的明文口令改写为 enc:v1: 密文(需 EC_TENANT_PASSWORD_KEY);轮换该密钥需同步重写主库口令或清空重灌(config/README.md)。erp-crm/src/test/java/com/ec/,13 个测试类)| 测试类 | 覆盖 |
|---|---|
auth/LoginAuthChainIntegrationTest | 登录全链路:租户校验、密码错误计数/锁定、JWT 签发与续期 |
crm/CrmApprovalFlowIntegrationTest | 审批提交/通过/驳回与补偿回退 |
crm/CrmCustomerOwnerGuardIntegrationTest | 非管理员越权转移/删除守卫 |
erp/ErpStockInIntegrationTest、erp/ErpTransferExecuteIntegrationTest | 采购入库 CAS、调拨执行与负库存回滚 |
saas/TenantRegisterIntegrationTest | 注册建库与主库登记 |
saas/job/TenantExpirationJobIntegrationTest | 到期巡检(调 sweepOnce()) |
saas/security/TenantDbPasswordCipherTest、saas/service/MasterTenantPasswordMigratorTest、saas/service/MasterTenantServiceImplTest | 口令加解密、启动迁移、主库服务 |
web/service/TenantQuotaIntegrationTest | 套餐配额拦截 |
common/config/EcProdConfigVerifierTest | prod 密钥 fail-fast |
测试基座 AbstractIntegrationTest:CI(.github/workflows/backend.yml)用 MySQL 8.4 / Redis 7 服务容器,建 erp-crm-master 与 ryt_nelub7xr 两库、执行与本地相同的两个 SQL、修正 tenant1 并补种 admin(同源逻辑也复刻在 compose 的 db-init),密钥全部以 CI 环境变量注入(工作流 env: 段);本机跑 mvn test 则复用本地 MySQL/Redis + .env。AGENTS.md 口径:47 例测试,本机与 CI 均为全绿基线(2026-09-28)。
pnpm lint:eslint(--max-warnings 0,带缓存)+ prettier;pnpm typecheck:vue-tsc --noEmit --skipLibCheck(业务 JS 不在检查范围,tsconfig exclude **/*.js,AGENTS.md 明示"新工具逻辑请配套写 vitest 用例")。pnpm test:vitest(vitest.config.ts),现有用例集中在 src/utils/erpcrm.test.js 与 src/composables/useDict.test.js。pnpm build:rimraf dist && vite build && generate-version-file(NODE_OPTIONS 8G;产物含 gzip/brotli,version-rocket 用于发版提示)。构建已知坑:vite.config.ts 的 advancedChunks(rolldown 弃用名)会静默吞掉部分构建错误,排查时先临时注释它;rolldown 锁 1.2.5(pnpm.overrides),升级后应迁移到 codeSplitting API。| 工作流 | 触发 | 步骤 |
|---|---|---|
erp-crm/.github/workflows/backend.yml | push/PR 影响 .java/.xml/.yml/.sql/pom | MySQL+Redis 服务容器 → 建库灌 SQL 修 tenant1 → mvn -B -ntp test |
erpcrm-ui/.github/workflows/frontend.yml | push/PR 影响 src/build/配置 | Node 22 + corepack pnpm 10.34.5 → pnpm install --frozen-lockfile → lint → typecheck → test → build |
两个仓库均已配置私有远端(SSH 协议,git@github.com:zhangzhengyang27/{erp-crm,erpcrm-ui}.git),推送后 Actions 自动生效。
@Transactional 内切 @DataSource(MASTER) 不换连接——跨库必须独立非事务方法(AGENTS.md 多租户第 6 条;SysLoginService 的 finally 重锚是范例)。spring.datasource.druid.tenant.* 保持小规格,改大会随租户数线性放大 MySQL 连接占用(application-druid.yml 注释)。MasterTenantServiceImpl 直接用 MasterTenantMapper 读写将绕过口令加解密层(AGENTS.md 技术栈节明示)。</> 合法内容),改动前需重新评估(application.yml xss 段 + AGENTS.md 评估结论)。ops/deploy/docker-compose.yml 仅过 YAML 语法校验,首次容器化部署需预留验证时间(AGENTS.md 记录)。erp-crm-tenant-sample.sql 内部分列注释为历史转码乱码(db-backup/repair_mojibake_20260907.sql 为修复产物),不影响结构;新建模板改动时勿以乱码注释为准。GET /captchaImage + Redis captcha_codes:{uuid})在当前源码中不存在(无任何 captcha 端点,LoginBody 仅 username/password 两字段)——验证码系"先恢复、后按产品决策移除"(git 8339bee→b036eec,考古见《16-决策记录ADR》ADR-006);是否重新实现属待决产品项,见 13 篇 D01。