环境差异与已知问题
环境差异与已知问题
本篇汇总部署与开发中高频踩坑的环境差异与已知问题,上线前请逐项核对。
端口勘误
旧文档端口过时
旧文档写的「后端 8080 / 前端 5173」是错误的。实际端口以配置文件为准:
| 角色 | 实际端口 | 配置来源 | 旧文档(错误) |
|---|---|---|---|
| 后端 | 52856 | backend/src/main/resources/application.yml 中 server.port: 52856 | 8080 |
| 前端 | 3000 | frontend/vite.config.ts 中 server.port: 3000 | 5173 |
Vite 代理配置
// frontend/vite.config.ts
server: {
host: '0.0.0.0',
port: 3000,
proxy: {
'/api': {
target: 'http://127.0.0.1:52856',
changeOrigin: true,
rewrite: (path) => path // 无 rewrite,/api 前缀原样透传
}
}
}- 代理目标
http://127.0.0.1:52856指向后端实际端口。 - 无 rewrite:
/api前缀会原样透传给后端,与后端@RequestMapping("/api/xxx")对齐。 - 用
127.0.0.1而非localhost,避免某些 Node 版本把 localhost 解析成 IPv6::1而后端不监听 v6。
Docker 端口不一致
Docker 镜像 Dockerfile 默认 EXPOSE 8080,与后端实际 52856 不一致。容器化时需通过 SERVER_PORT=8080 环境变量覆盖,或将 Dockerfile / compose / nginx 统一改为 52856。详见 。
Redis
application.yml 中配置了远程 Redis,用于缓存、接口防抖、限流:
spring:
data:
redis:
host: ${REDIS_HOST:124.71.29.50} # 默认远程地址
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:MFrG]q7m@6n2}
database: ${REDIS_DATABASE:0}
timeout: ${REDIS_TIMEOUT:3s}
lettuce:
pool:
max-active: ${REDIS_POOL_MAX_ACTIVE:16}
max-idle: ${REDIS_POOL_MAX_IDLE:8}
min-idle: ${REDIS_POOL_MIN_IDLE:0}
max-wait: ${REDIS_POOL_MAX_WAIT:3s}默认连远程 Redis
默认 REDIS_HOST 是远程地址 124.71.29.50,密码硬编码在 yml 中。生产环境务必替换为自有 Redis,通过环境变量覆盖:
export REDIS_HOST=你的Redis地址
export REDIS_PORT=6379
export REDIS_PASSWORD=你的Redis密码接口防抖配置
api:
debounce:
enabled: ${API_DEBOUNCE_ENABLED:true}
ttl-ms: ${API_DEBOUNCE_TTL_MS:1000} # 防抖窗口 1 秒
key-prefix: ${API_DEBOUNCE_KEY_PREFIX:lowcode:api:debounce}
fail-open: ${API_DEBOUNCE_FAIL_OPEN:true} # Redis 故障时放行(不阻断业务)- 防抖基于 Redis 实现,
fail-open: true表示 Redis 不可用时请求正常放行(降级)。 - 若本地无 Redis,防抖 / 限流 / 缓存功能会降级,但不影响核心业务启动。
Lombok × JDK21
JDK 21 + Maven 命令行编译会崩
在 JDK 21 环境下用 mvn clean package 构建时,Lombok 注解处理可能失败,报 java.lang.NoSuchFieldError: TypeTag 或类似错误。这是 Lombok 与高版本 JDK 的已知兼容性问题。
解决方案(任选其一)
| 方案 | 说明 |
|---|---|
| 用 JDK 17(推荐) | 项目编译目标即 17(pom.xml 中 <java.version>17</java.version>),统一用 17 最稳妥 |
| 用 IntelliJ IDEA 编译 | IDEA 内置编译器对 Lombok 兼容性更好,通常能绕过此问题 |
用 javac --add-opens | 命令行强制打开相关模块:javac --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED ... |
项目使用的 Lombok 版本为 1.18.36(pom.xml 中 <lombok.version>1.18.36</lombok.version>),Hutool 5.8.29,jjwt 0.12.3,POI 5.2.5。
技术栈版本速查
| 组件 | 版本 |
|---|---|
| JDK | 17 |
| Spring Boot | 3.1.11 |
| MyBatis-Plus | 3.5.15 |
| MySQL 驱动 | 8.3.0 |
| Hutool | 5.8.29 |
| Lombok | 1.18.36 |
| jjwt | 0.12.3 |
| Apache POI | 5.2.5 |
| 前端 Vue | 3.5 |
| 前端 Vite | 8 |
| Element Plus | 2.13 |
| Pinia | 3 |
| ECharts | 6 |
| bpmn-js | 17 |
JWT 与安全
当前认证机制(已核对 SecurityConfig.java)
SecurityConfig 的过滤链配置如下(backend/src/main/java/com/lowcode/config/SecurityConfig.java):
http
.csrf(...).disable()
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/api/auth/login", "/api/auth/captcha", "/api/auth/register",
"/api/auth/sso/providers", "/api/auth/sso/authorize/**", "/api/auth/sso/callback/**", "/api/auth/sso/token",
"/api/open/**", "/api/mobile/**",
"/uploads/**", "/upload/**"
).permitAll()
.anyRequest().authenticated() // 其余接口必须认证
);实际是 authenticated(),非 permitAll
当前 SecurityConfig 对业务接口采用 anyRequest().authenticated(),JWT 是强制校验的:请求未携带有效 JWT 会被 Spring Security 拦截返回 401/403。早期文档 / 记忆中提到的「业务接口 permitAll、JWT 未强制」对应的是更早版本,已不适用于当前代码。请在你的分支以 SecurityConfig.java 实际配置为准。
公开白名单(permitAll)
以下路径无需认证即可访问:
/api/auth/login//api/auth/captcha//api/auth/register-- 登录、验证码、注册/api/auth/sso/**-- SSO 单点登录回调/api/open/**-- 开放平台 API(由ApiKeyAuthFilter单独鉴权)/api/mobile/**-- 移动端接口/uploads/**//upload/**-- 上传文件访问
JWT 过滤器行为
JwtAuthenticationFilter(OncePerRequestFilter)逻辑:
- 从请求中提取 token(Header
Authorization,前缀Bearer)。 - 校验签名与有效期(jjwt 0.12.3,有效期 24 小时 =
86400000ms)。 - 校验通过则解析用户名、
tenantId,加载UserDetails并填入SecurityContextHolder;同时把tenantId存入TenantContext(ThreadLocal)。 - 校验异常时只
logger.warn记录并继续过滤链(不主动 reject),由后续authenticated()规则决定是否放行。
残留安全风险(二次开发需注意)
虽然 authenticated() 已强制登录,但仍有以下待收紧项:
- 方法级授权不完整:
@EnableMethodSecurity已开启,但并非所有敏感接口都加了@PreAuthorize,部分接口只要登录即可访问,缺少角色 / 权限校验。 - 角色权限树部分硬编码:前端权限树与部分后端权限判断存在硬编码,未完全走数据库
sys_role_menu。 - JWT 密钥默认值:
jwt.secret是公开默认值lowcode-platform-secret-key-2024-...,生产必须修改。 - 过滤器吞异常:
JwtAuthenticationFilter把所有异常 catch 后仅 warn,不利于审计异常 token。
二次开发时建议按需补充 @PreAuthorize 注解、改随机密钥、完善异常日志。
多租户与 ThreadLocal 串号
TenantContext 是 ThreadLocal,存放当前请求的 tenantId。TenantLineInnerInterceptor 会自动给 SQL 追加 tenant_id 条件。
线程复用串号
JwtAuthenticationFilter 在 finally 中调用 TenantContext.clear(),确保请求结束后清理 ThreadLocal。二次开发中若手动开线程 / 用 @Async / 用线程池,必须手动清理或传递 TenantContext,否则会串租户数据。
系统表白名单(不追加 tenant_id 条件):sys_tenant / sys_user / sys_role / sys_menu / sys_user_role / sys_role_menu(见 application-h2.yml 中 tenant.ignore-tables)。
schema-mysql.sql 落后
不要单独用 schema-mysql.sql
backend/src/main/resources/schema-mysql.sql 仅 16 张表,远落后于 schema-h2.sql(53 张)。MySQL 初始化请用 docs/SQL_INIT.sql + docs/SQL_EXTENSION.sql + sql/mysql/*.sql 组合,详见 。
两个 schema 文件对比:
| 文件 | 表数 | 状态 |
|---|---|---|
schema-h2.sql | 53 | 最完整,H2 自动执行 |
schema-mysql.sql | 16 | 严重落后,仅 sys_* + 部分 lc_* |
schema-mysql.sql 落后的原因:MySQL 生产环境改用 docs/ 与 sql/mysql/ 下的分文件脚本维护,schema-mysql.sql 未同步更新。若你习惯用单文件 schema,可从 schema-h2.sql 转换(注意 H2 与 MySQL 语法差异,如 CLOB -> TEXT、TIMESTAMP 默认值等)。
双重 /api 前缀
最常见 404 原因
前端 axios baseURL = '/api',后端控制器 @RequestMapping("/api/xxx"),Vite/Nginx 代理保留 /api 前缀透传。因此前端 api 模块的请求路径里不要再写 /api。
| 前端写法 | Vite/Nginx 代理后 | 后端匹配 | 结果 |
|---|---|---|---|
request.get('/database/...') | /api/database/... | @GetMapping("/api/database/...") | ✅ 正确 |
request.get('/api/database/...') | /api/api/database/... | 无 | ❌ 404 |
历史上 BPM 多个前端接口文件因此踩坑(已修复)。新增接口务必遵守:
- 后端:
@RequestMapping("/api/模块名")。 - 前端 api 模块:
request.get('/模块名/...')(不带/api)。
排查双重 /api
- 浏览器 F12 网络面板查看实际请求 URL 是否含两个
/api。 - 检查前端
src/api/*.ts中请求路径是否以/api开头(应为相对路径如/database/...)。 - 检查
vite.config.ts/nginx.conf代理配置是否有rewrite去掉/api(本项目不应去掉)。
其他已知差异
多 Profile 密码差异
| 环境 | admin 默认密码 | 来源 |
|---|---|---|
| H2 | 123456 | data-h2.sql 种子 |
| MySQL 首次启动 | admin123 | DataInitializer.initAdminUser(),可被 -Dapp.admin.password 覆盖 |
| 新建租户 | 123456 | DataInitializer.initTenantAdmin() |
详见 。
无内置 prod profile
项目只有 h2 与 mysql 两个 profile,没有 application-prod.yml。生产用 mysql profile + 环境变量覆盖,或自行创建 application-prod.yml 后用 --spring.profiles.active=prod 激活。详见 。
无 spring-boot-devtools
backend/pom.xml 未引入 spring-boot-devtools,后端无自动热重载。详见 。
无 XML Mapper
所有 MyBatis-Plus Mapper 都是注解 / 方法式 Java 接口,resources/mapper/ 下没有 XML 文件。mybatis-plus.mapper-locations 虽配了 classpath*:/mapper/**/*.xml,但实际无 XML。
分页未用 PaginationInnerInterceptor
平台未引入 mybatis-plus-jsqlparser 依赖,因此未用 MyBatis-Plus 的 PaginationInnerInterceptor,而是自研 common.PageHelper.doPage(...) 走 LIMIT/OFFSET + COUNT。
更多常见问题见 。
