Kimai 2.71 配置参数详解

本文逐层拆解 Kimai 2.71.0 的配置体系:环境变量与 `.env` 文件的优先级实测、`local.yaml` 对 `kimai.yaml` 的覆盖机制验证、时间跟踪/费率规则/权限/用户认证/邮件/发票/日历/主题的全部配置段默认值,以及配置改动生效路径(`kimai:reload`)。全部参数值来自真实部署环境的 `bin/console debug:config` 与 `debug:dotenv` 回显,费率联动行为经 API 实测验证。产品认知见 [《Kimai 2.71 介绍与选型指南》](https://www.quzuo.net/document/sK41JUhCNu0x),部署过程见 [《Kimai 2.71 Docker Compose 部署实战》](https://www.quzuo.net/document/3CNq42eP7Z4D)。

KimaiKimai工时管理PHP 应用2026/10/27

Kimai 2.71 配置参数详解

本文逐层拆解 Kimai 2.71.0 的配置体系:环境变量与 .env 文件的优先级实测、local.yaml 对 kimai.yaml 的覆盖机制验证、时间跟踪/费率规则/权限/用户认证/邮件/发票/日历/主题的全部配置段默认值,以及配置改动生效路径(kimai:reload)。全部参数值来自真实部署环境的 bin/console debug:config 与 debug:dotenv 回显,费率联动行为经 API 实测验证。产品认知见 《Kimai 2.71 介绍与选型指南》,部署过程见 《Kimai 2.71 Docker Compose 部署实战》。

一、配置体系总览

1.1 三层配置来源与优先级

Kimai 基于 Symfony 6.4 的标准配置体系,实际生效值由三层来源叠加决定,优先级从高到低:

层级位置承载内容生效方式
① 运行环境变量进程环境(Compose environment:、shell export)数据库连接、密钥、可信主机、邮件等部署相关项立即生效(重启服务后)
② .env 文件安装目录根 /opt/kimai/.env同上的文件形态兜底值被层级 ① 覆盖
③ YAML 配置config/packages/kimai.yaml(默认)与 config/packages/local.yaml(用户覆盖)应用行为(时间跟踪规则、权限、费率、日历、主题等)需 kimai:reload 重建缓存

用 bin/console debug:dotenv 可以直观看到三个层级谁说了算:

Dotenv Variables & Files
========================

 [WARNING] The file .env.dist gets skipped due to the existence of .env.

Scanned Files (in descending priority)
--------------------------------------

 * ⨯ .env.local.php
 * ⨯ .env.prod.local
 * ⨯ .env.prod
 * ⨯ .env.local
 * ✓ .env

Variables
---------

 ------------------- -------------------------------------------------------------------------------------- -----------------------------------
  Variable            Value                                                                                  .env
 ------------------- -------------------------------------------------------------------------------------- -----------------------------------
  APP_ENV             prod                                                                                   prod
  APP_SECRET          5f0a1c9e2b7d4a8f3e6c1d9b0a7f4e2c
  CORS_ALLOW_ORIGIN   ^https?://localhost(:[0-9]+)?$                                                         ^https?://localhost(:[0-9]+)?$...
  DATABASE_URL        mysql://kimaiuser:q1w2e3r4@kimai-2.71.0-db/kimai?charset=utf8mb4&serverVersion=8.3.0   mysql://user:password@127.0.0....
  DEFAULT_URI         https://127.0.0.1:8000                                                                 https://127.0.0.1:8000
  MAILER_FROM         kimai@example.com                                                                      kimai@example.com
  MAILER_URL          null://localhost                                                                       null://null
 ------------------- -------------------------------------------------------------------------------------- -----------------------------------

这张表的读法:Value 列是最终生效值,.env 列是文件里的值。APP_SECRET 与 DATABASE_URL 两行的 .env 列为空——实际值来自运行环境变量(容器编排注入),.env 文件中的同名占位(mysql://user:password@127.0.0.1)被环境变量覆盖。这正是 Compose 部署的标准形态:敏感凭据走编排环境变量,.env 只留非敏感默认值。

1.2 查看生效配置的两把尺子

  • bin/console debug:config kimai:输出 kimai: 配置树合并后的最终值(本地.yaml 已叠加);
  • bin/console debug:dotenv:输出环境变量三层的叠加结果。

修改配置后的生效路径:YAML 层改动必须执行 bin/console kimai:reload(重建 Symfony 生产缓存),环境变量层改动必须重启服务进程。本文所有覆盖实测均按此流程验证。

二、环境变量详解

2.1 全量变量与默认值

以下为 2.71.0 安装目录 .env 文件原文(本文实测环境逐行拷贝),注释保留官方原意:

# Configure your database connection and the correct server version - find it with: SELECT VERSION();
DATABASE_URL=mysql://user:password@127.0.0.1:3306/database?charset=utf8mb4&serverVersion=10.5.8-MariaDB

# You MUST change this to a long and random character sequence, e.g.: php -r 'echo bin2hex(random_bytes(32));'
APP_SECRET=

# The base URL of your Kimai installation
DEFAULT_URI=https://127.0.0.1:8000

# SECURITY WARNING: you SHOULD configure the allowed domain name(s) for your installation
# TRUSTED_HOSTS='^www\.kimai\.cloud$'

# Outgoing emails use this email-address as sender
MAILER_FROM=kimai@example.com

# Email connection - see https://www.kimai.org/documentation/emails.html
MAILER_URL=null://null

# Running behind reverse proxies? Try these:
# TRUSTED_PROXIES=127.0.0.1,127.0.0.2

# regexp to define allowed frontend origins for API requests
CORS_ALLOW_ORIGIN=^https?://localhost(:[0-9]+)?$

# only change to "dev" if you are developing with/for Kimai
APP_ENV=prod

逐项说明:

变量必填作用与实测要点
DATABASE_URL✅Doctrine 连接串:mysql://用户:密码@主机:端口/库?charset=utf8mb4&serverVersion=<真实版本>。serverVersion 必须与真实版本一致(本文实测 8.3.0)——Doctrine 按它选择 SQL 方言与迁移平台策略;charset=utf8mb4 官方默认串自带,不可省(中文/emoji 存储依赖它)
APP_SECRET官方要求Symfony 会话/CSRF/记住我令牌的加密熵源。官方建议 php -r 'echo bin2hex(random_bytes(32));' 生成 64 位十六进制。实测发现:Compose 部署漏配时官方镜像会自动生成随机密钥并持久化到 var/data 卷(部署篇排障案例一),但生产环境应显式配置以保证可审计可迁移
DEFAULT_URI建议系统发邮件/生成绝对链接用的基地址。默认 https://127.0.0.1:8000,生产必须改成对外域名(否则密码重置邮件里的链接不可用)
TRUSTED_HOSTS建议Symfony 可信主机正则,如 ^kimai\.example\.com$。不配置时 Symfony 接受任意 Host 头;配置后白名单外的主机名直接拒绝。多个主机用 `
TRUSTED_PROXIES可选反向代理场景下,声明哪些来源 IP 的 X-Forwarded-* 头可信;默认注释(不信任任何代理头)
MAILER_FROM可选外发邮件的发件人地址,默认 kimai@example.com
MAILER_URL可选邮件传输器 DSN,默认 null://null(空传输器:邮件不真正发出,实测见 8.2 节)
CORS_ALLOW_ORIGIN可选API 跨域白名单正则,默认只放行 localhost;前端域名与 API 不同源时必须改
APP_ENV固定prod(默认)/dev/test。官方注释明确:除非为 Kimai 做开发,否则不要改 dev

2.2 容器部署的变量注入点

官方镜像读取的变量与 .env 同名,但来源是容器环境变量(优先级高于文件)。部署篇编排文件里注入的正是这几个:

    environment:
      - APP_SECRET=5f0a1c9e2b7d4a8f3e6c1d9b0a7f4e2c
      - 'TRUSTED_HOSTS=^(localhost|127\.0\.0\.1|kimai-2\.71\.0-web)$'
      - ADMINMAIL=admin@example.com
      - ADMINPASS=q1w2e3r4
      - "DATABASE_URL=mysql://kimaiuser:q1w2e3r4@kimai-2.71.0-db/kimai?charset=utf8mb4&serverVersion=8.3.0"

其中 ADMINMAIL/ADMINPASS 是官方镜像特有的首启建号变量(不属于 Symfony 层,只被镜像入口脚本消费);其余变量进入 1.1 节的层级 ①。TRUSTED_PROXIES 在镜像中默认注入 127.0.0.1(实测 docker inspect 可见),反代场景按需调整。

三、YAML 配置与 local.yaml 覆盖机制

3.1 文件布局

/opt/kimai/
├── .env                                  # 环境变量兜底值(层级 ②)
└── config/packages/
    ├── kimai.yaml                        # Kimai 主配置(官方默认,头部声明 DO NOT EDIT)
    ├── local.yaml                        # 用户覆盖文件(默认不存在,需自行创建)
    ├── security.yaml / framework.yaml    # Symfony 框架级配置
    ├── doctrine.yaml / monolog.yaml ...  # Doctrine、日志等
    └── tabler.yaml                       # 前端 Tabler 主题配置

kimai.yaml 文件头部官方原话:

# DO NOT EDIT THIS FILE, INSTEAD CREATE THE FILE "local.yaml" AND ADD YOUR SETTINGS IN THERE.
# See https://www.kimai.org/documentation/local-yaml.html
#
# Be aware that this file is YAML format and the indentation is important.
# Each config level needs to be indented with 4 additional spaces.

即:kimai.yaml 是默认值清单(每次升级会被镜像覆盖),用户改动一律写进 local.yaml,键路径与 kimai.yaml 一致即可按「后加载者胜」逐键覆盖(YAML 深合并,未写的键保持默认)。

3.2 覆盖实测

在 config/packages/local.yaml 写入两项覆盖(markdown_content 开启 Markdown 渲染、duration_increment 改 30 分钟步进):

kimai:
    timesheet:
        markdown_content: true
        duration_increment: 30

执行 bin/console debug:config kimai 核验合并结果:

    timesheet:
        markdown_content: true        # 覆盖生效(默认 false)
        duration_increment: 30        # 覆盖生效(默认 15)
        default_begin: now            # 未覆盖 → 保持默认
        mode: default                 # 未覆盖 → 保持默认
        time_increment: 15            # 未覆盖 → 保持默认

改动对运行中服务生效还需重建缓存:

bin/console kimai:reload

实测回显:

 // Warming up the cache for the prod environment with debug false

 [OK] Cache for the "prod" environment (debug=false) was successfully warmed.


 [OK] Kimai config was reloaded

💡 技术拓展:为什么 YAML 改动不能「热生效」 Symfony 生产模式把 YAML 配置编译进 var/cache/prod 的 PHP 类里,请求路径上不存在「每次读 YAML」的动作——这是它性能的来源。因此配置文件的任何改动都需要一次「重新编译」:kimai:reload 内部就是清空缓存再预热(其输出先 clear 后 warm)。部署篇重启实验也印证了这一点:容器每次启动都会重跑一次缓存重建。

四、时间跟踪参数(kimai.timesheet)

以下为 debug:config kimai 输出的 timesheet 段完整默认值(2.71.0 实测):

kimai:
    timesheet:
        default_begin: now          # 新建时间记录的预填开始时间
        mode: default               # 记录模式
        markdown_content: false     # 描述字段是否渲染 Markdown
        duration_increment: 15      # 时长下拉步进(分钟);null=跟随取整规则,0=禁用下拉
        time_increment: 15          # 开始/结束时间分钟选择步进
        rounding:                   # 取整规则(按下标命名,可自定义多条)
            default:
                days: [monday, tuesday, wednesday, thursday, friday, saturday, sunday]
                begin: 1            # 开始时间向下取整到 1 分钟
                end: 1              # 结束时间向上取整到 1 分钟
                duration: 0         # 时长取整增量(0=不取整)
                mode: default
        rates: {  }                 # 分时段费率规则(如周末上浮),见 5.2
        active_entries:
            hard_limit: 1           # 每用户同时运行中的记录上限
        rules:
            allow_future_times: true          # 允许记录未来时间
            allow_zero_duration: true         # 允许零时长记录
            allow_overbooking_budget: true    # 允许超预算继续记账
            allow_overlapping_records: true   # 允许时间重叠的多条记录
            lockdown_period_start: null       # 锁定期开始(配合项目锁定日期)
            lockdown_period_end: null
            lockdown_period_timezone: null
            lockdown_grace_period: null       # 锁定期宽限(天数)
            break_warning_duration: 0         # 休息时长警告阈值(分钟)
            long_running_duration: 0          # 长时间未停止警告阈值(分钟)
            require_activity: true            # 记录必须选择活动
            break_time_active: false          # 是否启用休息时间扣减

要点解读:

  • mode 三种取值:default(自由补录,默认)、duration(只填时长不填起止)、punch(打卡模式——禁止任意补录,只能开始/停止,对应「时钟模式」)。当前环境 API 实测 GET /api/config/timesheet 回显 {"trackingMode":"default","defaultBeginTime":"now","activeEntriesHardLimit":1,"isAllowFutureTimes":true,"isAllowOverlapping":true},与 YAML 值一一对应(trackingMode 即 mode);
  • hard_limit: 1 的实际语义:用户再次开始记录时,前一条自动停止(不是拒绝);调大后超过 1 条时才要求手工停止。实测中连续创建两条运行中记录,第一条被自动结束,与该默认值一致;
  • rounding 的 mode 支持 default(四舍五入口径见上)/nearest/earliest 等策略,days 数组限定规则生效的星期——可实现「工作日取整、周末不取整」类差异化;
  • lockdown_* 系列是审计场景的核心:设定锁定期后,用户不能再修改/新增落在锁定期内的历史记录(配合宽限期给管理员留缓冲)。

五、费率规则实战

5.1 三级费率来源与优先级

Kimai 的金额计算取费率的优先级为:时间记录自身指定 > 活动级费率 > 项目级费率 > 客户级费率 > 用户时薪 > 系统默认(0)。费率分两种形态:hourly_rate(按小时折算)与 fixed_rate(每条记录一口价)。三种实体(活动/项目/客户)都有 rates 子资源(数据库表 kimai2_activities_rates / projects_rates / customers_rates),每条费率可带 user 字段做「针对特定用户」的定向费率。

实测过程(全程 API 操作,认证方式见命令篇):

# 给活动 2 设置小时费率 350 / 内部成本价 280
curl -s -X POST -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
  -d '{"rate":350,"internalRate":280}' http://127.0.0.1:8136/api/activities/2/rates

回显:

{"id":1,"user":null,"rate":350.0,"internalRate":280.0,"isFixed":false}

isFixed: false 表示小时费率(true 则为固定费率)。创建一条 1.5 小时记录验证折算:

curl -s -X POST -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
  -d '{"project":2,"activity":2,"begin":"2026-10-27T14:00:00","end":"2026-10-27T15:30:00"}' \
  http://127.0.0.1:8136/api/timesheets

回显(rate = 1.5 × 350 = 525,internalRate = 1.5 × 280 = 420,hourlyRate 回显取到的费率来源):

{"id":4,"begin":"2026-10-27T14:00:00+0800","end":"2026-10-27T15:30:00+0800","duration":5400,
 "rate":525.0,"internalRate":420.0,"fixedRate":null,"hourlyRate":350.0,...}

固定费率形态实测(创建时直接指定 fixedRate,与时长无关):

{"id":3,"begin":"2026-10-27T10:00:00+0800","end":"2026-10-27T12:30:00+0800","duration":9000,
 "rate":1200.0,"fixedRate":1200.0,"hourlyRate":null,...}

两条记录、两种费率形态合计 1725,与后续 CLI 生成发票的总价(Total 1725 CNY)精确一致,费率链路端到端验证通过。

5.2 分时段费率(rates 规则)

kimai.yaml 注释给出的官方示例——周末上浮 50%:

kimai:
    timesheet:
        rates:
            weekend:
                days: ['saturday', 'sunday']
                factor: 1.5

factor 乘在最终时薪上;days 之外的可用维度与 rounding 相同(按星期选择生效日)。该段默认 { }(空)。

六、权限体系参数(kimai.permissions)

6.1 sets / maps / roles 三段结构

Kimai 的权限体系在 YAML 里由三段组成,语义是「权限名 → 权限集 → 角色」:

kimai:
    permissions:
        sets:                 # 权限集:一组权限名的命名集合
            ACTIVITIES: ['view_activity','create_activity','edit_activity','budget_activity','time_activity','delete_activity','permissions_activity']
            TIMESHEET: ['view_own_timesheet','start_own_timesheet','stop_own_timesheet','create_own_timesheet','edit_own_timesheet','export_own_timesheet','delete_own_timesheet','weekly_own_timesheet']
            TIMESHEET_OTHER: ['view_other_timesheet','start_other_timesheet','stop_other_timesheet','create_other_timesheet','edit_other_timesheet','export_other_timesheet','delete_other_timesheet']
            # ……共 30 余个集合(ACTIVITIES/PROJECTS/CUSTOMERS 各分 _TEAMLEAD/_ALL_TEAM 变体、INVOICE、PROFILE、TAGS、USER、RATE、EXPORT、BILLABLE、TEAMS、LOCKDOWN、REPORTING、EVERYONE 等)
        maps:                 # 角色映射:角色 → 权限集列表
            ROLE_USER: ['TIMESHEET','PROFILE', 'EVERYONE']
            ROLE_TEAMLEAD: ['ACTIVITIES_TEAMLEAD','PROJECTS_TEAMLEAD','CUSTOMERS_TEAMLEAD','TIMESHEET_OTHER','INVOICE','TIMESHEET','PROFILE','EXPORT','BILLABLE','TAGS','REPORTING', 'EVERYONE']
            ROLE_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING', 'EVERYONE']
            ROLE_SUPER_ADMIN: ['ACTIVITIES','PROJECTS','CUSTOMERS','INVOICE','INVOICE_ADMIN','TIMESHEET','TIMESHEET_OTHER','PROFILE','PROFILE_OTHER','USER','TEAMS','RATE','RATE_OTHER','EXPORT','BILLABLE','TAGS','LOCKDOWN','REPORTING', 'EVERYONE']
        roles:                # 角色直授:不走集合的零散权限
            ROLE_USER: ['view_team_member','time_team_project','create_tag','view_reporting']
            ROLE_TEAMLEAD: ['view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile','view_team_member','hours_other_profile']
            ROLE_ADMIN: ['hourly-rate_own_profile','edit_exported_timesheet','teams_own_profile','view_team_member','view_all_data','contract_other_profile','hours_other_profile','create_export_template']
            ROLE_SUPER_ADMIN: ['hourly-rate_own_profile','hourly-rate_other_profile','roles_own_profile','supervisor_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet','teams_own_profile','view_team_member','upload_invoice_template','view_all_data','contract_other_profile','hours_other_profile','create_export_template']

要点:

  • 四个内置角色的能力分界线在 maps:ROLE_USER 只能动自己的时间表和个人配置;ROLE_TEAMLEAD 增加「看/管他人的时间表 + 开发票 + 导出」;ROLE_ADMIN 增加客户/项目/活动全套管理、费率管理、锁定期操作;ROLE_SUPER_ADMIN 独占用户管理(USER)、系统配置(system_configuration)、插件管理(plugins)、发票模板上传(upload_invoice_template)与全局数据可见(view_all_data);
  • 命名规律:*_own_* / *_other_* 区分本人与他人的数据;_teamlead_ 后缀是团队领导对团队数据的变体权限;集合名 EVERYONE 是全员基线(含 api_access——API 访问本身就是一项权限);
  • 扩展角色:在 local.yaml 中给新角色名(如 ROLE_PM)组合 sets 即可,无需改代码;自定义角色经命令篇的 kimai:user:promote 授予。

七、用户与认证参数

7.1 user 段默认值

kimai:
    user:
        login: true                    # 是否开放登录页
        theme: auto                    # 用户默认主题(auto=跟随系统深浅色)
        registration: false            # 开放自助注册(默认关闭,默认仅管理员建号)
        wizard: true                   # 首次登录引导向导
        password_reset: true           # 允许自助密码重置(邮件)
        password_reset_retry_ttl: 3600 # 重置请求重试间隔(秒)
        password_reset_token_ttl: 86400# 重置令牌有效期(秒,24 小时)
        password_strength: 0           # 密码强度校验级别(0=不强制)
        auth_change: false             # 是否允许在认证方式间切换

password_strength 是 2.70.0 引入的新参数(0–4,数值越高越严格)。

7.2 LDAP 配置(local.yaml 覆盖形态)

LDAP 在 debug:config 中的默认结构如下(activate: false 即未启用;字段默认值实测):

kimai:
    ldap:
        activate: false
        connection:
            host: null
            port: 389
            useStartTls: false
            useSsl: false
            bindRequiresDn: true
            accountFilterFormat: null
        user:
            baseDn: null
            filter: ''
            attributesFilter: '(objectClass=*)'
            usernameAttribute: uid
            attributes: {  }
        role:
            baseDn: null
            usernameAttribute: dn
            nameAttribute: cn
            userDnAttribute: member
            groups: {  }

启用形态(引自官方文档 kimai.org/documentation/ldap.html 的 local.yaml 样例,2026-10-27 取证,完整字段以官方页为准):

kimai:
    ldap:
        activate: true
        connection:
            # 可用空格分隔多台:'ldap://ldap1.local ldap://ldap2.local',ldaps:// 同理
            host: ldap://ldap.example.local
            # port: 389                # useSsl 与 useStartTls 互斥,官方建议优先 StartTLS
            # useSsl: false
            # useStartTls: false
            # username: ...            # 服务账号(部分服务器要求 DN 形式)
            # password: ...
        user:
            baseDn: ou=people,dc=example,dc=local
            filter: '(objectClass=inetOrgPerson)'
        role:
            baseDn: ou=groups,dc=example,dc=local

LDAP 用户登录后本地自动建号(authenticator 显示为 ldap,kimai:user:list 可见),2.69.0 起支持把 LDAP/SAML 账号转回内部账号(auth_change 相关)。本环境无 LDAP 服务器,以上启用样例为官方文档转述,字段结构为 2.71.0 实测默认值。

7.3 SAML 配置结构

SAML 段默认结构(activate: false):

kimai:
    saml:
        activate: false
        title: 'Login with SAML'
        provider: default
        usernameAttribute: null
        roles:
            resetOnLogin: true      # 登录时以 IdP 返回的角色重置本地角色
            attribute: null
            mapping: {  }
        mapping: {  }               # IdP 属性 → Kimai 用户字段映射
        connection:
            cleanupLongRelayState: false
            organization: {  }

官方文档站提供 Azure AD、Google、Keycloak、Authentik、Microsoft ADFS 五个 SAML 对接专页;元数据与证书字段在 connection.organization 与 mapping 中按 IdP 填写。

八、邮件参数

8.1 MAILER_URL 传输器 DSN

MAILER_URL 使用 Symfony Mailer 的 DSN 语法,常见取值:

DSN行为
null://null(.env 默认)/ null://localhost(镜像默认)空传输器:邮件「发送」成功但不落地、不投递
smtp://用户:密码@smtp.example.com:465SMTPS 直连
smtp://smtp.example.com:25明文 SMTP
smtp://smtp.example.com:587?encryption=tls&auth_mode=loginSTARTTLS 提交端口

8.2 发信实测

bin/console kimai:mail:test admin@example.com
echo "exit=$?"

实测回显(null://localhost 下命令静默成功,无任何输出、退出码 0):

exit=0

解读:kimai:mail:test <收件人> 用 MAILER_URL/MAILER_FROM 配置发一封测试邮件;在默认空传输器下它只走完 Symfony 邮件管道即返回成功——「命令成功」不代表邮件真的发出。生产切换 SMTP 后执行同一条命令,失败时会打印传输异常(认证失败/连接拒绝等),才是有效的发信验证。同理,依赖邮件的「密码重置」功能在默认配置下不可用(用户收不到邮件但系统不报错),上线前必须配置真实 SMTP。

九、发票、导出、日历、主题与其他参数

9.1 invoice 段(发票)

kimai:
    invoice:
        defaults:                     # 发票文档(模板)搜索目录
            - 'var/invoices/'
            - 'templates/invoice/renderer/'
        documents: {  }               # 用户上传模板追加目录
        number_format: '{Y}/{cy,3}'   # 发票编号格式(默认 2026/001)
        upload_twig: false            # 是否允许上传 Twig 模板(有代码执行面,默认关)
        rounding_mode: classic        # 金额舍入(classic/ceiling/floor)

编号格式占位符:{Y} 年、{cy,3} 客户维度计数器(3 位补零);可组合 {D}、{M}、{date}、{cc,4}(客户编号)等。实测环境当前编号 2026/001 与默认格式吻合。

9.2 export 段(导出)

kimai:
    export:
        defaults:
            - 'var/export/'
            - 'templates/export/renderer/'
        documents: {  }
        timeout: 60            # 导出渲染超时(秒)

9.3 calendar 段(日历)

kimai:
    calendar:
        week_numbers: true           # 显示周数
        day_limit: 4                 # 日视图最多显示条数
        slot_duration: '00:30:00'    # 时间槽粒度
        businessHours: {begin: '08:00', end: '20:00'}
        visibleHours: {begin: '00:00', end: '23:59'}
        weekends: true
        dragdrop_amount: 5           # 拖拽创建的条目数
        title_pattern: '{activity}'  # 日历条目标题模板
        google: {api_key: null, sources: {  }}   # Google 日历聚合(节假日)

9.4 theme 段(主题与品牌)

kimai:
    theme:
        show_about: true
        colors_limited: true         # 限制可选颜色为内置色板
        color_choices: 'Silver|#c0c0c0,Gray|#808080,...'   # 内置 28 色板(实测完整串)
        branding:
            logo: null               # 品牌定制:登录页 logo
            mini: null               # 侧栏收起 logo
            company: null            # 公司名
            title: null              # 浏览器标题
        avatar_url: false            # 允许用户填外部头像 URL

品牌定制四件套(logo/mini/company/title)支持相对路径(放 var/data)或绝对 URL;color_choices 可整体替换为组织自定义色板。

9.5 customer / project / activity 段(实体编号与选择器)

kimai:
    customer:
        language: en
        choice_pattern: '{name}'     # 下拉选择器显示模板
        number_format: '{cc,4}'      # 客户编号格式(实测 Acme = 0002、Beta = 0003)
        rules: {allow_duplicate_number: false}
    project:
        choice_pattern: '{name}'
        copy_teams_on_create: false
        number_format: '{pc,4}'
        allow_duplicate_number: false
    activity:
        choice_pattern: '{name}'
        allow_inline_create: false
        number_format: '{ac,4}'
        allow_duplicate_number: false

choice_pattern 支持 {name}、{number}、{comment} 等占位组合,大型实例常用 {name} ({number}) 改善下拉可读性。编号格式占位({cc,4}/{pc,4}/{ac,4})为「对象类别 + 4 位计数」,与 5.1 节 API 实测回显(客户/项目/活动 number 全为 0003 起跳)一致。

9.6 defaults 段(新对象默认值)

kimai:
    defaults:
        customer: {timezone: null, country: DE, currency: EUR}   # 新客户默认国家/币种
        user: {timezone: null, language: en, locale: null, theme: auto}

国内部署建议在 local.yaml 中改为 country: CN、currency: CNY、language: zh_CN,减少每次建客户的手工修正(实测环境中已通过 Web 建号时的表单默认值体现为 CNY/Asia/Shanghai)。

十、排障实录

10.1 改了 local.yaml 没生效

现象:local.yaml 写入覆盖项后刷新页面,行为未变。

根因:YAML 改动需要缓存重建(见 3.2 节原理)。

处置:bin/console kimai:reload,输出两个 [OK](cache warmed + config reloaded)后生效;确认手段是 debug:config kimai 看合并值而非看文件。

10.2 .env 里改了 DATABASE_URL 不生效

现象:编辑 .env 的 DATABASE_URL 后重启,应用仍连旧库。

根因:层级 ① 的运行环境变量优先于 .env(1.1 节 debug:dotenv 实测:.env 列的占位值被环境变量覆盖)。

处置:Compose 部署下改编排 environment: 段再 up -d;.env 只在无同名环境变量时才被读取。

10.3 duration_increment 配 null 被按默认处理

现象:按旧文档把 duration_increment 配成 ~(YAML null)期望「禁用时长下拉」,实际下拉仍出现且步进 15。

根因:2.71.0 的合并默认值中 duration_increment: 15(debug:config 实测),null 语义为「跟随取整规则」而非禁用;配置 0 才是禁用。

处置:明确意图——0 禁用、正整数作步进、null 跟随 rounding 规则。

10.4 密码重置邮件「成功」但收不到

现象:用户触发密码重置提示已发送,实际收不到邮件;kimai:mail:test 又显示成功。

根因:默认 MAILER_URL=null://null 是空传输器(8.2 节实测:静默 exit=0),邮件从未离开服务器。

处置:配置真实 SMTP DSN 后重测 kimai:mail:test;DEFAULT_URI 同步改为对外域名,否则重置链接不可点。

10.5 中文/emoji 存库报错或乱码

现象:客户名等含中文的字段写入报编码错误或显示 ?。

根因:连接串丢了 charset=utf8mb4(官方默认串自带),或手工建的库默认字符集不是 utf8mb4。

处置:核对 DATABASE_URL 含 charset=utf8mb4&serverVersion=<真实版本>;本文实测环境全链路(建客户「Beta 咨询公司」→ 发票 PDF → CSV 导出)中文往返无乱码。

10.6 配置速查:改什么、在哪里、怎么生效

需求配置位置生效方式
换数据库编排 environment.DATABASE_URL重启服务
换域名/收紧可信主机TRUSTED_HOSTS(编排或 .env)重启服务
时间表改打卡模式local.yaml → kimai.timesheet.mode: punchkimai:reload
周末费率上浮local.yaml → kimai.timesheet.rateskimai:reload
新增自定义角色local.yaml → kimai.permissions.maps/roleskimai:reload
开放自助注册local.yaml → kimai.user.registration: truekimai:reload
接 LDAP/SAMLlocal.yaml → kimai.ldap/saml.activate: truekimai:reload
发票编号规则local.yaml → kimai.invoice.number_formatkimai:reload
品牌 logo/标题local.yaml → kimai.theme.brandingkimai:reload
新客户默认 CN/CNYlocal.yaml → kimai.defaults.customerkimai:reload

CLI 侧的命令行工具链(含 kimai:reload、kimai:install、发票与导出命令的全量实测)见 《Kimai 2.71 常用命令与运维实战》。