application.yaml 13.1 KB
spring:
  application:
    name: parking-server
  profiles:
    active: local
  # ========== 引入「存量业务参数」文件 ==========
  # 老工程按模块各有一份 application*.properties,红线第 9 条禁止改、importer 也故意不搬;
  # 但存量代码里有 37 个 `@Value("${...}")` 是业务参数(不是连接串),不补就启动失败。
  # 这一批集中收在 legacy-params.properties(由 tools/p2_audit_placeholders.py 捞出并核对)。
  # ⚠️ 优先级:application-{profile}.yaml(profile 专属)> 本文件 > 被 import 的文件,
  #    所以按环境覆盖这些参数时,写进 application-{profile}.yaml 即可。
  config:
    import: classpath:legacy-params.properties
  # ========== Boot 1.5 → 2.x 的默认值翻转(必配,不是可选优化)==========
  # 老工程是 Spring Boot 1.5.2,那时 spring.main.allow-circular-references 默认 true;
  # 2.6 起默认 false,启动即抛
  #   "The dependencies of some of the beans in the application context form a cycle"。
  # 存量代码是 2000+ 个 ServiceImpl 之间靠字段 @Autowired 互引,环是网状的
  # (实测:DealGeoInOutParkDataSchedule → GeoInOutParkReportInnerServiceImpl → BerthsDaoImpl
  #   ⇄ ParkFreeBerthBizServiceImpl),且老代码里没有一处用 @Lazy / setter 注入规避。
  # 拆环 = 改动存量业务装配方式,触碰项目红线第 6 条「禁止擅自重构业务逻辑」,故显式放行。
  main:
    allow-circular-references: true
    # ⚠️ 这里**故意不写** allow-bean-definition-overriding(2.1 起默认 false,保持即可):
    #    它是「两个**不同类**撞同一个 bean 名」的唯一探针,本次重构正是靠启动报错逐个揪出
    #    9 组真实冲突并补上显式 bean 名修掉的。为封闭 jar 里 ChargeConfig ↔ ChargeService
    #    那一组开全局放水,等于把最该发现的错误静默掉;那组已改用「精确排除」处理,
    #    见 ParkingServerApplication 类注释与 AGENTS.md 坑 13/14。
  # 资源信息:文件上传大小限制
  servlet:
    multipart:
      max-file-size: 20MB
      max-request-size: 20MB
  # ========== 邮件(存量 pay/park 的 MailService,发票/异常告警用)==========
  # 老工程把 spring.mail.* 写在**各模块的 application.properties** 里
  # (zteits-pay / zteits-park 各一份,7 个 profile 各一组,值逐字相同),
  # 红线第 9 条禁止改那些文件、importer 也故意不搬 —— 单 Jar 只有一份配置,
  # 所以在这里补。四个键缺一不可:
  #   host     —— MailSenderAutoConfiguration 的 @ConditionalOnProperty(prefix="spring.mail", name="host");
  #               缺 host 就没有 JavaMailSender Bean,而 MailService 是裸 @Autowired JavaMailSender,
  #               启动即报 `No qualifying bean of type 'org.springframework.mail.javamail.JavaMailSender'`
  #               (实测装配链:mailService → field 'sender')。
  #   username —— 两个 MailService 都用 @Value("${spring.mail.username}") **且没有默认值**,
  #               缺失是**占位符解析失败**,比缺 Bean 更早炸。
  #   password —— 只在真正发信时用(Boot 的 test-connection 默认 false,启动不会去连 SMTP);
  #               **真实密码不进仓库**(红线 11),生产用 SPRING_MAIL_PASSWORD 注入,
  #               待轮换的旧密码见 docs/密钥轮换清单.md。
  # 默认 host/port 取老工程原值(smtp.163.com:25,不是密钥);默认 username/password 留空,
  # 于是「能启动、发不出信、发信时以 auth 失败**报错**(而不是静默)」—— 这是没有凭据时的预期状态。
  mail:
    host: ${SPRING_MAIL_HOST:smtp.163.com}
    port: ${SPRING_MAIL_PORT:25}
    username: ${SPRING_MAIL_USERNAME:}
    password: ${SPRING_MAIL_PASSWORD:}
  # Jackson 全局配置
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
  mvc:
    pathmatch:
      matching-strategy: ANT_PATH_MATCHER
    # 未匹配到 Controller 时抛 NoHandlerFoundException,由 GlobalExceptionHandler 统一转成
    # CommonResult(404) 的 JSON 兜底(未迁移的模块就是这种「路由不存在」的状态)。
    # 否则前端拿到的是 Spring Boot 默认 HTML 错误页,无法按 code 处理。
    throw-exception-if-no-handler-found: true
  web:
    resources:
      # 关闭默认静态资源映射,配合上面的开关;knife4j / springdoc 的 /doc.html、/v3/api-docs
      # 由它们自己注册的 ResourceHandler 提供,与本开关无关(已实测仍可访问)。
      add-mappings: false
  # 定时任务(Quartz):P0 用内存存储,P1 起改为数据库存储
  quartz:
    job-store-type: memory
    properties:
      org:
        quartz:
          scheduler:
            instance-id: AUTO
            instance-name: parkingScheduler
          threadPool:
            threadCount: 20
  # 消息队列(Redis 实现)默认关闭外部 MQ
  rabbitmq:
    listener:
      simple:
        auto-startup: false

server:
  port: 48080
  servlet:
    encoding:
      charset: UTF-8
      force: true

# ========== 项目业务配置 ==========
parking:
  info:
    version: 1.0.0
    base-package: com.zteits.parking
  # 注:P0 的骨架占位开关 `parking.p0.enabled` 已随 P0StubConfiguration 一起删除。
  #     P1 起 OAuth2TokenCommonApi / PermissionCommonApi / DictDataCommonApi 三个跨模块契约
  #     均由 parking-module-system 的 OAuth2TokenServiceImpl / PermissionServiceImpl /
  #     DictDataServiceImpl 提供(三者分别继承了对应契约接口)。
  web:
    admin-api:
      prefix: /admin-api
      controller: '**.controller.admin.**'
    app-api:
      prefix: /app-api
      controller: '**.controller.app.**'
    admin-ui:
      url: http://localhost:5173
  security:
    token-header: Authorization
    token-parameter: token
    mock-enable: false
    mock-secret: test
    password-encoder-length: 4
    permit-all-urls:
      - /actuator/**
      - /doc.html
      - /swagger-ui/**
      - /swagger-ui.html
      - /v3/api-docs/**
      - /webjars/**
      - /favicon.ico
  swagger:
    title: ZTEITS 智能停车云平台
    description: 停车云平台管理后台接口文档(模块化单体,单 Jar 发布)
    author: ZIEITS
    version: 1.0.0
    url: https://www.zteits.com
    email: dev@zteits.com
    license: MIT
    license-url: https://opensource.org/licenses/MIT
  cache:
    redis-scan-batch-size: 30
  # ========== 刷新缓存共享密钥(老门户 RefreshCacheController 4 条 @NoAuth)==========
  # 老代码写死 32 位共享密钥,迁移时按 AGENTS.md「批 6 第三十四切片」④ (B) 改配置化(红线 11)。
  # 代码里**零密钥、无默认值**;这里默认取环境变量、缺省为空 ⇒ 任何环境不配即 fail-closed
  # (一律回 "token不正确")。本地测试值在 application-local.yaml 覆盖;生产用
  # PARKING_REFRESH_CACHE_TOKEN 或 --parking.refresh-cache.token=... 配成现网原串。
  refresh-cache:
    token: ${REFRESH_CACHE_TOKEN:}
  # ========== 老新组织 id 换算表(P3 组织口径桥,批 6 第四十七切片)==========
  # 新 `system_org.id` 与存量业务表里存的 `sys_org.id` 是两个空间(坑 22)。
  # `LoginUserBridge` 用它把登录人的 orgId 换算成老 id 再交给存量服务;格式 `新id:老id,...`。
  # **默认空 ⇒ 恒等映射**,任何环境不配都与改造前完全一致(配置在 profile/环境变量里给)。
  # ========== 老新操作员 id 换算表(P3 ②,第一百六十三切片)==========
  # 新 `system_users.id` 与存量审计列(create_op/modify_op/create_empid/s_op_id)里的
  # `sys_op.id` 是两个空间。`LoginUserBridge` 用它把 opId 换算成老操作员 id;
  # 格式 `新id:老id,...`。**默认空 ⇒ 恒等映射**,任何环境不配都与改造前完全一致。
  legacy:
    org-id-mapping: ${LEGACY_ORG_ID_MAPPING:}
    op-id-mapping: ${LEGACY_OP_ID_MAPPING:}

# ========== 企业微信(存量 zteits-wxcp-portal 的告警发送)==========
# 随迁进 parking-module-system 的 com.zteits.wxcp.* 用 @ConfigurationProperties(wechat.cp) 绑定。
# ⚠️ 这两个键**必须存在**:WxCpConfiguration.initServices() 是 @PostConstruct,
#    里面直接 properties.getAppConfigs().stream() —— appConfigs 为 null 会启动即 NPE。
#    这里给**空列表**是刻意的:空列表能正常绑定(不执行任何 lambda),
#    于是没有企业微信凭据的环境也能启动,只是真的要走告警时
#    WxCpConfiguration.getCpService(agentId) 返回 null(外部集成未配置)。
# 生产/测试环境在此之上覆盖(凭据本身见 docs/密钥轮换清单.md,不进仓库):
#   wechat:
#     cp:
#       corp-id: ww...
#       app-configs:
#         - agent-id: 1000002
#           secret: ${WECHAT_CP_SECRET_1000002:}
#           token: ${WECHAT_CP_TOKEN_1000002:}
#           aes-key: ${WECHAT_CP_AESKEY_1000002:}
wechat:
  cp:
    corp-id: ${WECHAT_CP_CORP_ID:}
    app-configs: []

# ========== SpringDoc / Knife4j 接口文档 ==========
springdoc:
  api-docs:
    enabled: true
    path: /v3/api-docs
  swagger-ui:
    path: /swagger-ui
knife4j:
  enable: true
  setting:
    language: zh_cn

# ========== MyBatis-Plus 配置 ==========
# 核心:存量 mapper XML 与新建的 MyBatis-Plus 共用同一个 SqlSessionFactory,
#      mapper-locations 一条通配同时命中「新代码 Mapper XML」与「存量 mybatis/mapper|smapper」。
mybatis-plus:
  mapper-locations:
    - classpath*:mapper/**/*.xml
    - classpath*:mybatis/mapper/**/*.xml
    - classpath*:mybatis/smapper/**/*.xml
  type-aliases-package: com.zteits.parking.module.*.dal.dataobject,com.zteits.parking.module.*.domain
  global-config:
    banner: false
    db-config:
      id-type: AUTO
      # ⚠️ 存量六个库**没有 del_flag 列**(已核验:0 处),因此这里不配置全局逻辑删除。
      #    详见 parking-spring-boot-starter-mybatis 的 BaseDO 类注释。
      table-underline: true
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false
    call-setters-on-nulls: true
    jdbc-type-for-null: 'null'
    # ↓↓↓ 以下 3 项是**照搬老工程** mybatis-plugin.xml 的 settings
    #     (zteits-{park,pay,bill}/src/main/resources/mybatis/mybatis-plugin.xml,三份逐字相同)。
    #     老工程走的是自己 new SqlSessionFactoryBean 的路线,这些 settings 写在 XML 里;
    #     新工程的 SqlSessionFactory 由 MyBatis-Plus 建,所以要把**非默认值**的挑出来补在这里。
    #     逐项对照过 10 条 setting,只有下面 3 条与 MyBatis 默认值不同:
    #       mapUnderscoreToCamelCase=true   —— 已在上方
    #       cacheEnabled=false              —— 已在上方
    #       localCacheScope=STATEMENT       —— MyBatis 默认 SESSION,**必须补**
    #       useGeneratedKeys=true           —— MyBatis 默认 **false**,**必须补**
    #       defaultStatementTimeout=60      —— 默认 null(不超时),**必须补**
    #       lazyLoadingEnabled / multipleResultSetsEnabled / useColumnLabel /
    #       autoMappingBehavior / defaultExecutorType —— 与默认值一致,不必写
    local-cache-scope: statement
    default-statement-timeout: 60
    use-generated-keys: true

# ========== PageHelper(仅存量代码搬迁时按需使用,新代码禁止)==========
pagehelper:
  helper-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  # ⚠️ `page-size-zero: true` 是**必须**的,不是可选项:它决定 `startPage(n, 0)` 是
  #    「查全部」还是「查空」,而存量代码里 60+ 个导出端点全都靠 `new BaseInfo(1, 0)`
  #    表达「不分页、导全部」。
  #
  #    老栈 PageHelper 5.0.0:`startPage(1, 0)` → LIMIT **不拼** → 全量返回(total=1, list.size=1)
  #    新栈 PageHelper 6.1.1(`page-size-zero` 默认 false):拼 `LIMIT 0` → **0 行**(total=1, list.size=0)
  #    实测证据与复现脚本见 AGENTS.md「pageSize=0:老栈查全部、新栈查空」一节
  #    (/tmp/pgprobe 的两版本对照实验)。不设这一项,所有导出都会**静默导出空表**。
  #
  #    老工程 application.properties 里没写这一项 —— 那不是"老栈也不需要",而是 5.0.0 里
  #    pageSize=0 本来就不拼 LIMIT,`pageSizeZero` 只管 RowBounds 那条路径。换了 PageHelper
  #    大版本后语义变了,必须在**新栈**显式补回来。
  page-size-zero: true
  # 老工程 application.properties 里还有 `pagehelper.dialect=com.github.pagehelper.PageHelper`
  # 与 `pagehelper.params=countSql`:前者是 5.x 的自定义 dialect 类全名(6.x 已废弃该配置形态),
  # 后者是 supportMethodsArguments 的默认值,二者都无迁移必要,故不搬。

# ========== Actuator 健康检查 ==========
management:
  endpoints:
    web:
      base-path: /actuator
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

# ========== 日志 ==========
logging:
  file:
    name: ./logs/${spring.application.name}/${spring.application.name}.log