Full Code of wannanbigpig/gin-layout for AI

master 48da724a1693 cached
397 files
1.4 MB
440.8k tokens
2815 symbols
1 requests
Download .txt
Showing preview only (1,630K chars total). Download the full file or copy to clipboard to get everything.
Repository: wannanbigpig/gin-layout
Branch: master
Commit: 48da724a1693
Files: 397
Total size: 1.4 MB

Directory structure:
gitextract_fx8etgvi/

├── .air.toml
├── .github/
│   ├── pull_request_template.md
│   └── workflows/
│       ├── codeql.yml
│       └── go.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── MEMORY.md
├── README.en.md
├── README.md
├── build.sh
├── cmd/
│   ├── .gitignore
│   ├── bootstrapx/
│   │   ├── bootstrap.go
│   │   └── bootstrap_test.go
│   ├── command/
│   │   └── command.go
│   ├── completion.go
│   ├── cron/
│   │   ├── cron.go
│   │   ├── schedule.go
│   │   ├── schedule_test.go
│   │   ├── task_record_test.go
│   │   ├── tasks.go
│   │   └── tasks_test.go
│   ├── root.go
│   ├── service/
│   │   └── service.go
│   ├── version/
│   │   └── version.go
│   └── worker/
│       └── worker.go
├── config/
│   ├── .gitignore
│   ├── autoload/
│   │   ├── app.go
│   │   ├── jwt.go
│   │   ├── logger.go
│   │   ├── mysql.go
│   │   ├── queue.go
│   │   └── redis.go
│   ├── config.go
│   ├── config.yaml.example
│   ├── config_clone.go
│   ├── config_load.go
│   ├── config_load_test.go
│   ├── config_test.go
│   ├── provider.go
│   ├── runtime.go
│   ├── runtime_test.go
│   └── testing_helper.go
├── data/
│   ├── data.go
│   ├── data_test.go
│   ├── migrations/
│   │   ├── 20260425000001_init_table.down.sql
│   │   ├── 20260425000001_init_table.up.sql
│   │   ├── 20260425000002_init_data.down.sql
│   │   ├── 20260425000002_init_data.up.sql
│   │   ├── 20260515010000_upload_file_objects.down.sql
│   │   └── 20260515010000_upload_file_objects.up.sql
│   ├── mysql.go
│   ├── redis.go
│   ├── runtime_health.go
│   └── runtime_health_test.go
├── docs/
│   ├── COMMANDS_AND_TASKS.en.md
│   ├── COMMANDS_AND_TASKS.md
│   ├── DONATE.en.md
│   ├── DONATE.md
│   ├── MIGRATE_COMMANDS.en.md
│   ├── MIGRATE_COMMANDS.md
│   ├── SECURITY_PERMISSION_FIXES_2026-05.md
│   ├── SYSTEM_CONFIG_AND_DICT_GUIDELINES.en.md
│   └── SYSTEM_CONFIG_AND_DICT_GUIDELINES.md
├── go.mod
├── go.sum
├── internal/
│   ├── access/
│   │   └── casbin/
│   │       ├── adapter.go
│   │       ├── casbin.go
│   │       ├── enforcer_init.go
│   │       └── policy_ops.go
│   ├── console/
│   │   ├── confirm.go
│   │   ├── demo/
│   │   │   └── demo.go
│   │   ├── init/
│   │   │   └── init.go
│   │   ├── migrate/
│   │   │   └── migrate.go
│   │   ├── system_init/
│   │   │   └── system_init.go
│   │   └── task/
│   │       ├── task.go
│   │       └── task_test.go
│   ├── controller/
│   │   ├── admin_v1/
│   │   │   ├── auth.go
│   │   │   ├── auth_admin_user.go
│   │   │   ├── auth_api.go
│   │   │   ├── auth_dept.go
│   │   │   ├── auth_file_resource.go
│   │   │   ├── auth_login_log.go
│   │   │   ├── auth_menu.go
│   │   │   ├── auth_request_log.go
│   │   │   ├── auth_role.go
│   │   │   ├── auth_session.go
│   │   │   ├── auth_storage_config.go
│   │   │   ├── auth_sys_config.go
│   │   │   ├── auth_sys_dict.go
│   │   │   ├── auth_task_center.go
│   │   │   ├── auth_test.go
│   │   │   ├── dashboard.go
│   │   │   └── sys_common.go
│   │   ├── sys_base.go
│   │   ├── sys_base_test.go
│   │   └── sys_demo.go
│   ├── cron/
│   │   ├── queue_fallback.go
│   │   ├── queue_fallback_test.go
│   │   ├── registry.go
│   │   └── registry_test.go
│   ├── filestorage/
│   │   ├── aliyun_oss.go
│   │   ├── local.go
│   │   ├── local_test.go
│   │   ├── s3.go
│   │   └── types.go
│   ├── global/
│   │   ├── api_auth_mode.go
│   │   ├── api_auth_mode_test.go
│   │   ├── auth.go
│   │   ├── common.go
│   │   ├── context_keys.go
│   │   └── system_defaults.go
│   ├── jobs/
│   │   ├── audit_log.go
│   │   ├── audit_log_test.go
│   │   └── registry.go
│   ├── middleware/
│   │   ├── admin_auth.go
│   │   ├── admin_auth_test.go
│   │   ├── audit_context.go
│   │   ├── audit_queue.go
│   │   ├── audit_queue_test.go
│   │   ├── cors.go
│   │   ├── cors_test.go
│   │   ├── database_ready.go
│   │   ├── database_ready_test.go
│   │   ├── logger.go
│   │   ├── logger_bench_test.go
│   │   ├── logger_recorder.go
│   │   ├── logger_storage.go
│   │   ├── logger_test.go
│   │   ├── parse_token.go
│   │   ├── recovery.go
│   │   ├── request_cost.go
│   │   ├── request_locale.go
│   │   └── request_locale_test.go
│   ├── model/
│   │   ├── admin_login_logs.go
│   │   ├── admin_user_dept_map.go
│   │   ├── admin_user_role_map.go
│   │   ├── admin_users.go
│   │   ├── admin_users_test.go
│   │   ├── api.go
│   │   ├── base.go
│   │   ├── base_crud.go
│   │   ├── base_list.go
│   │   ├── base_list_test.go
│   │   ├── base_owner.go
│   │   ├── base_test.go
│   │   ├── base_tree.go
│   │   ├── dept.go
│   │   ├── dept_role_map.go
│   │   ├── file_upload.go
│   │   ├── file_upload_test.go
│   │   ├── login_security_state.go
│   │   ├── menu.go
│   │   ├── menu_api_map.go
│   │   ├── menu_i18n.go
│   │   ├── modelDict/
│   │   │   └── base.go
│   │   ├── request_logs.go
│   │   ├── role.go
│   │   ├── role_menu_map.go
│   │   ├── sys_config.go
│   │   ├── sys_dict.go
│   │   ├── sys_i18n.go
│   │   └── task_center.go
│   ├── pkg/
│   │   ├── auditdiff/
│   │   │   ├── diff.go
│   │   │   └── diff_test.go
│   │   ├── errors/
│   │   │   ├── code.go
│   │   │   ├── code_test.go
│   │   │   ├── en-us.go
│   │   │   ├── error.go
│   │   │   └── zh-cn.go
│   │   ├── func_make/
│   │   │   ├── func_make.go
│   │   │   └── func_make_test.go
│   │   ├── i18n/
│   │   │   ├── locale.go
│   │   │   └── locale_test.go
│   │   ├── logger/
│   │   │   ├── logger.go
│   │   │   └── logger_test.go
│   │   ├── query_builder/
│   │   │   ├── query_builder.go
│   │   │   └── query_builder_test.go
│   │   ├── request/
│   │   │   ├── request.go
│   │   │   └── request_test.go
│   │   ├── response/
│   │   │   ├── response.go
│   │   │   └── response_test.go
│   │   ├── testkit/
│   │   │   ├── secret.go
│   │   │   └── secret_test.go
│   │   └── utils/
│   │       ├── desensitize.go
│   │       ├── format_time.go
│   │       ├── sensitive/
│   │       │   ├── fields.go
│   │       │   ├── http_mask.go
│   │       │   ├── mask.go
│   │       │   └── string_mask.go
│   │       ├── token/
│   │       │   ├── jwt.go
│   │       │   └── jwt_test.go
│   │       ├── utils.go
│   │       └── utils_test.go
│   ├── queue/
│   │   ├── asynqx/
│   │   │   ├── asynq.go
│   │   │   ├── asynq_test.go
│   │   │   ├── inspector_test.go
│   │   │   └── task_record_test.go
│   │   ├── queue.go
│   │   └── queue_test.go
│   ├── resources/
│   │   ├── admin_user.go
│   │   ├── api.go
│   │   ├── base.go
│   │   ├── base_test.go
│   │   ├── common.go
│   │   ├── dept.go
│   │   ├── file_resource.go
│   │   ├── file_resource_test.go
│   │   ├── login_log.go
│   │   ├── login_log_test.go
│   │   ├── menu.go
│   │   ├── request_log.go
│   │   ├── role.go
│   │   ├── session.go
│   │   ├── sys_config.go
│   │   ├── sys_dict.go
│   │   └── task_center.go
│   ├── routers/
│   │   ├── admin_router.go
│   │   ├── defs.go
│   │   ├── deps.go
│   │   ├── meta.go
│   │   ├── register.go
│   │   ├── router.go
│   │   ├── router_deps_test.go
│   │   ├── router_test.go
│   │   └── validate.go
│   ├── runtime/
│   │   └── config_reload.go
│   ├── service/
│   │   ├── access/
│   │   │   ├── api_cache.go
│   │   │   ├── api_cache_test.go
│   │   │   ├── common.go
│   │   │   ├── coordinator.go
│   │   │   ├── graph_loader.go
│   │   │   ├── menu_api_defaults.go
│   │   │   ├── menu_api_defaults_test.go
│   │   │   ├── scope_resolver.go
│   │   │   ├── system_defaults.go
│   │   │   ├── system_defaults_test.go
│   │   │   ├── transaction.go
│   │   │   ├── user_permission_sync.go
│   │   │   ├── user_permission_sync_bench_test.go
│   │   │   └── user_permission_sync_test.go
│   │   ├── admin/
│   │   │   ├── admin_user.go
│   │   │   ├── admin_user_bind.go
│   │   │   ├── admin_user_create_test.go
│   │   │   ├── admin_user_mutation.go
│   │   │   ├── admin_user_test.go
│   │   │   ├── audit_diff.go
│   │   │   └── audit_diff_test.go
│   │   ├── api_permission/
│   │   │   ├── api.go
│   │   │   ├── api_test.go
│   │   │   ├── audit_diff.go
│   │   │   └── audit_diff_test.go
│   │   ├── audit/
│   │   │   ├── list_helpers.go
│   │   │   ├── login_log.go
│   │   │   ├── login_log_test.go
│   │   │   ├── request_log.go
│   │   │   ├── request_log_manage.go
│   │   │   ├── request_log_manage_test.go
│   │   │   └── request_log_write.go
│   │   ├── auth/
│   │   │   ├── login.go
│   │   │   ├── login_bench_test.go
│   │   │   ├── login_blacklist.go
│   │   │   ├── login_helpers_test.go
│   │   │   ├── login_log_helpers.go
│   │   │   ├── login_refresh.go
│   │   │   ├── login_revoke.go
│   │   │   ├── login_security.go
│   │   │   ├── login_security_test.go
│   │   │   ├── login_token_ops.go
│   │   │   ├── login_types.go
│   │   │   ├── login_types_test.go
│   │   │   ├── principal.go
│   │   │   ├── session.go
│   │   │   └── session_test.go
│   │   ├── common.go
│   │   ├── common_test.go
│   │   ├── common_upload_helpers.go
│   │   ├── common_upload_helpers_test.go
│   │   ├── dashboard/
│   │   │   └── overview.go
│   │   ├── dept/
│   │   │   ├── audit_diff.go
│   │   │   ├── audit_diff_test.go
│   │   │   ├── dept.go
│   │   │   ├── dept_mutation.go
│   │   │   └── dept_test.go
│   │   ├── file_object.go
│   │   ├── file_reference.go
│   │   ├── file_reference_test.go
│   │   ├── file_resource.go
│   │   ├── file_resource_folder_upload.go
│   │   ├── file_resource_test.go
│   │   ├── i18n_text.go
│   │   ├── menu/
│   │   │   ├── audit_diff.go
│   │   │   ├── audit_diff_test.go
│   │   │   ├── menu.go
│   │   │   ├── menu_edit.go
│   │   │   ├── menu_query.go
│   │   │   └── menu_test.go
│   │   ├── role/
│   │   │   ├── audit_diff.go
│   │   │   ├── audit_diff_test.go
│   │   │   ├── role.go
│   │   │   ├── role_mutation.go
│   │   │   └── role_test.go
│   │   ├── storage_config.go
│   │   ├── sys_base.go
│   │   ├── sys_config/
│   │   │   ├── audit_diff.go
│   │   │   ├── audit_request_body.go
│   │   │   ├── cache.go
│   │   │   ├── cache_sync.go
│   │   │   ├── cache_sync_test.go
│   │   │   ├── runtime_audit.go
│   │   │   ├── runtime_audit_test.go
│   │   │   ├── sys_config.go
│   │   │   ├── sys_config_mask_test.go
│   │   │   ├── typed_value.go
│   │   │   └── typed_value_test.go
│   │   ├── sys_dict/
│   │   │   ├── audit_diff.go
│   │   │   └── sys_dict.go
│   │   ├── system/
│   │   │   ├── init.go
│   │   │   ├── migration_runner.go
│   │   │   ├── reset.go
│   │   │   ├── reset_path_test.go
│   │   │   └── reset_test.go
│   │   └── taskcenter/
│   │       ├── action.go
│   │       ├── action_test.go
│   │       ├── audit_diff.go
│   │       ├── audit_diff_test.go
│   │       ├── list_helpers.go
│   │       ├── query.go
│   │       ├── recorder.go
│   │       └── recorder_test.go
│   └── validator/
│       ├── binding.go
│       ├── binding_i18n_test.go
│       ├── form/
│       │   ├── admin_user.go
│       │   ├── admin_user_test.go
│       │   ├── auth.go
│       │   ├── common.go
│       │   ├── dept.go
│       │   ├── file_resource.go
│       │   ├── file_resource_test.go
│       │   ├── id_array_validation_test.go
│       │   ├── login_log.go
│       │   ├── menu.go
│       │   ├── menu_test.go
│       │   ├── permission.go
│       │   ├── permission_test.go
│       │   ├── request_log.go
│       │   ├── request_log_test.go
│       │   ├── role.go
│       │   ├── role_test.go
│       │   ├── session.go
│       │   ├── session_test.go
│       │   ├── storage_config.go
│       │   ├── storage_config_test.go
│       │   ├── sys_config.go
│       │   ├── sys_config_test.go
│       │   ├── sys_dict.go
│       │   ├── sys_dict_test.go
│       │   ├── task_center.go
│       │   └── task_center_test.go
│       ├── rules.go
│       ├── runtime.go
│       ├── translation.go
│       └── validator_test.go
├── main.go
├── pkg/
│   ├── convert/
│   │   └── convert.go
│   └── utils/
│       ├── captcha/
│       │   └── captcha.go
│       ├── crypto/
│       │   ├── README.md
│       │   ├── crypto.go
│       │   ├── crypto_aes.go
│       │   └── types.go
│       ├── helpers.go
│       ├── helpers_test.go
│       ├── http.go
│       ├── http_test.go
│       ├── upload.go
│       ├── upload_test.go
│       ├── utils.go
│       └── utils_test.go
├── policy.csv
├── rbac_model.conf
└── tests/
    ├── README.md
    ├── admin_test/
    │   ├── README.md
    │   ├── admin_test.go
    │   ├── admin_user_test.go
    │   ├── auth_routes_test.go
    │   ├── common_routes_test.go
    │   ├── department_test.go
    │   ├── log_routes_test.go
    │   ├── menu_test.go
    │   ├── permission_routes_test.go
    │   ├── public_routes_test.go
    │   ├── role_test.go
    │   ├── system_routes_test.go
    │   ├── task_routes_test.go
    │   └── test_helpers_test.go
    ├── ping_test.go
    └── test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .air.toml
================================================
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
  args_bin = []
  # 使用相对路径,或者不指定配置文件让程序自动查找
  bin = "./tmp/main service"
  cmd = "go build -o ./tmp/main"
  # 设置环境变量
  env = ["GO_ENV=development"]
  delay = 0
  exclude_dir = ["assets", "tmp", "vendor", "testdata", "logs", "tests"]
  exclude_file = []
  exclude_regex = ["_test.go"]
  exclude_unchanged = false
  follow_symlink = false
  full_bin = ""
  include_dir = []
  include_ext = ["go", "tpl", "tmpl", "html"]
  include_file = []
  kill_delay = "0s"
  log = "./logs/build-errors.log"
  poll = false
  poll_interval = 0
  rerun = false
  rerun_delay = 500
  send_interrupt = false
  stop_on_error = false

[color]
  app = ""
  build = "yellow"
  main = "magenta"
  runner = "green"
  watcher = "cyan"

[log]
  main_only = false
  time = false

[misc]
  clean_on_exit = false

[screen]
  clear_on_rebuild = false
  keep_scroll = true


================================================
FILE: .github/pull_request_template.md
================================================
## 变更说明

- 主要目标:
- 影响模块:
- 风险等级:低 / 中 / 高

## 自查清单

- [ ] 已确认本次改动解决的是主要问题,而不是无关重构
- [ ] 已调研相关代码并复用现有实现(未重复造轮子)
- [ ] 已评估影响范围与兼容性(必要时给出回滚方案)
- [ ] 已补充或更新测试(至少覆盖一个新增/修改分支)

## 可读性专项检查(必填)

- [ ] 未引入“仅一层转发且无语义增量”的新方法
- [ ] 如果新增拆分,已说明拆分理由(事务边界 / 复用 / 测试隔离)
- [ ] 方法职责保持单一,命名表达清晰
- [ ] 跨文件跳转成本可接受(阅读主流程不需要反复来回定位)

## 验证记录

- 本地执行命令:
  - `go test ./...`
- 关键结果:

## 关联信息

- 关联 Issue / 任务:
- 额外说明:


================================================
FILE: .github/workflows/codeql.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"

on:
  push:
    branches: [ master, x_l_admin ]
  pull_request:
    # The branches below must be a subset of the branches above
    branches: [ master, x_l_admin ]
  schedule:
    - cron: '27 0 * * 6'

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ 'go' ]
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
        # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      # Initializes the CodeQL tools for scanning.
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: ${{ matrix.language }}
          # If you wish to specify custom queries, you can do so here or in a config file.
          # By default, queries listed here will override any specified in a config file.
          # Prefix the list here with "+" to use these queries and those in the config file.

          # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
          # queries: security-extended,security-and-quality


      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
      # If this step fails, then you should remove it and run the build manually (see below)
      - name: Autobuild
        uses: github/codeql-action/autobuild@v2

      # ℹ️ Command-line programs to run using the OS shell.
      # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun

      #   If the Autobuild fails above, remove it and uncomment the following three lines.
      #   modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.

      # - run: |
      #   echo "Run, Build Application using script"
      #   ./location_of_script_within_repo/buildscript.sh

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2

================================================
FILE: .github/workflows/go.yml
================================================
name: Go

on:
  push:
    branches: [ master, x_l_admin ]
  pull_request:
    branches: [ master, x_l_admin ]

jobs:

  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        go-version: [ '1.26.x' ]
    steps:
      - uses: actions/checkout@v3

      - name: Setup Go ${{ matrix.go-version }}
        uses: actions/setup-go@v4
        with:
          go-version: ${{ matrix.go-version }}

      - name: Display Go version
        run: go version

      - name: Build
        run: go build -v ./...

      - name: Test
        run: go test $(go list ./... | grep -v /tests/)

================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# IDE and editor files
.idea
.vscode
*.DS_Store

# Runtime directories
storage
logs/
tmp
build/

# Local config and environment files
*.yaml
*.ini
.env
.env.local
.env.*.local

# Claude Code local settings (keep memory but ignore local configs)
.claude

# Application binaries
gin-layout
gin-layout/
go-layout
/migrate

# Documentation (project-specific, not for version control)
PROJECT_ARCHITECTURE.md
CODE_OPTIMIZATION_REPORT.md

# Go cache
.gocache
.go-test-cache

# Memory (if you want to keep it local only)
# memory/
.aitasks


================================================
FILE: .golangci.yml
================================================
# golangci-lint 配置文件
# 参考: https://golangci-lint.run/usage/configuration/

run:
  # 超时时间
  timeout: 5m
  # 并发数
  concurrency: 4
  # 包含的测试文件
  tests: true
  # 跳过目录
  skip-dirs:
    - vendor
    - build
    - dist
    - logs
    - storage
    - tmp
  # 跳过文件
  skip-files:
    - ".*\\.pb\\.go$"
    - ".*\\.gen\\.go$"

# 输出配置
output:
  # 输出格式: colored-line-number, line-number, json, tab, checkstyle, code-climate, html, junit-xml, github-actions
  format: colored-line-number
  # 打印问题数量
  print-issued-lines: true
  # 打印 linter 名称
  print-linter-name: true
  # 不打印欢迎信息
  uniq-by-line: false
  # 打印源代码行
  print-welcome: false

# 启用的 linter
linters:
  enable:
    # 代码质量
    - errcheck          # 检查错误处理
    - gosimple          # 简化代码建议
    - govet             # go vet 检查
    - ineffassign       # 检查未使用的赋值
    - staticcheck       # 静态分析
    - unused            # 检查未使用的代码
    
    # 代码风格
    - gofmt             # 代码格式检查
    - goimports         # import 语句检查
    - misspell          # 拼写检查
    - revive            # Go 代码检查工具(替代 golint)
    - stylecheck        # 风格检查
    
    # 性能
    - prealloc          # 预分配切片检查
    
    # 复杂度
    - gocyclo           # 圈复杂度检查
    - gocognit          # 认知复杂度检查
    
    # 其他
    - exportloopref     # 循环变量引用检查
    - gocritic          # 代码审查建议
    - gosec             # 安全检查
    - nakedret          # 检查裸返回
    - noctx             # 检查 context 传递
    - rowserrcheck      # 检查 rows.Err()
    - sqlclosecheck     # 检查 SQL 连接关闭

linters-settings:
  # errcheck 配置
  errcheck:
    check-type-assertions: true
    check-blank: true
  
  # gocyclo 配置 - 圈复杂度阈值
  gocyclo:
    min-complexity: 15
  
  # gocognit 配置 - 认知复杂度阈值
  gocognit:
    min-complexity: 15
  
  # revive 配置
  revive:
    rules:
      - name: exported
        severity: warning
      - name: var-naming
        severity: warning
      - name: package-comments
        severity: warning
      - name: range
        severity: warning
      - name: increment-decrement
        severity: warning
      - name: error-return
        severity: warning
      - name: error-strings
        severity: warning
      - name: error-naming
        severity: warning
      - name: receiver-naming
        severity: warning
      - name: unexported-return
        severity: warning
      - name: time-equal
        severity: warning
      - name: banned-characters
        severity: warning
      - name: context-keys-type
        severity: warning
      - name: context-as-argument
        severity: warning
      - name: if-return
        severity: warning
      - name: increment-decrement
        severity: warning
      - name: var-declaration
        severity: warning
      - name: range-val-in-closure
        severity: warning
      - name: range-val-address
        severity: warning
      - name: waitgroup-by-value
        severity: warning
      - name: atomic
        severity: warning
      - name: empty-lines
        severity: warning
      - name: line-length-limit
        severity: warning
        arguments:
          - 120
  
  # gocritic 配置
  gocritic:
    enabled-tags:
      - diagnostic
      - experimental
      - opinionated
      - performance
      - style
    disabled-checks:
      - dupImport # 允许重复导入(某些情况下需要)
      - ifElseChain # 允许 if-else 链
      - octalLiteral # 允许八进制字面量
  
  # gosec 配置
  gosec:
    # 严重程度: low, medium, high
    severity: medium
    # 置信度: low, medium, high
    confidence: medium
    # 排除的规则
    excludes:
      - G104 # 审计错误未检查(某些情况下可以接受)
      - G401 # 弱随机数生成(某些场景可以接受)
      - G501 # 导入黑名单(某些依赖是必需的)

issues:
  # 排除的问题
  exclude-rules:
    # 排除测试文件中的某些检查
    - path: _test\.go
      linters:
        - errcheck
        - gosec
        - gocritic
        - gocyclo
        - gocognit
  
  # 最大问题数(0 表示不限制)
  max-issues-per-linter: 0
  max-same-issues: 0
  
  # 排除的问题模式
  exclude:
    # 排除 "Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked"
    - 'Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
    # 排除 "exported function .* should have comment"
    - 'exported function .* should have comment'
    # 排除 "comment on exported .* should be of the form"
    - 'comment on exported .* should be of the form'



================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2022 wannanbigpig

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: MEMORY.md
================================================
# MEMORY

这份文档写给接手 `gin-layout` 的 AI Agent。它不是项目营销介绍,而是为了让下一位 AI 尽快知道:

- 项目当前真实状态是什么
- 主要入口、核心链路和高风险区域在哪
- 关键约束和禁止事项是什么
- 写代码时如何保持当前项目风格

## 0. 快速结论

先记住这些,不确定时再往下查细节:

- 代码是真相,文档只是辅助;文档和代码冲突时,以代码为准并顺手修正文档。
- 新功能优先沿用现有 `service / model / resources / validator` 分层。
- 保持现有工程范式,不引入重框架化、函数式大重写、新 DI 容器或 repository 全量替换。
- Casbin 使用 `github.com/casbin/casbin/v3`。
- 鉴权链路采用 claims-first:请求上下文保存 `AuthPrincipal`,不是完整数据库 `AdminUser`。
- 错误文案按请求语言国际化返回;需要自定义错误文案时优先使用错误码或 message key,而不是在 controller 里硬编码中文。
- 请求审计是“审计快照 + 队列优先”:队列开启时异步落库,队列关闭时才同步落库审计日志。
- CORS 使用常规 `["*"]` 语义。
- 菜单列表/树只返回当前语言 `title`;菜单详情返回 `title_i18n`,不返回 `title`。
- 继续改代码时保持小步修改、局部重组、命名稳定。
- 改完代码必须自己验证;新增或修改 HTTP 行为默认要补 `tests/` 接口级测试。

## 1. 禁止事项

当前代码库禁止这样做:

- 引入 `casbin/v2` 或混用不同主版本的 Casbin adapter。
- 在认证中间件里每个请求都回表查询完整 `AdminUser`;账号状态变更应通过服务层撤销 token 或更新会话状态生效。
- 把 `AuthPrincipal.AdminUser()` 当成数据库实时状态。
- 在队列开启时同步落库请求审计日志。
- 把请求日志设计成“每个请求同步写文件 + 异步审计”的双写链路。
- 把 CORS 空数组当成全放开;显式全放开使用 `["*"]`。
- 让菜单列表或用户菜单树返回 `title_i18n`。
- 让菜单详情返回 `title`。
- 让菜单写接口接收 `title`;写接口使用 `title_i18n`。
- 把业务逻辑、事务或批量数据修复塞进 controller。
- 把所有逻辑塞回单个超大 service 文件。
- 绕过 `PermissionSyncCoordinator` 分散写入 Casbin 权限。
- 只改代码不跑测试,或把验证责任留给用户或下一个 AI。

## 2. 当前定位

项目名:`gin-layout`

这是一个偏后台管理场景的 Go 后端骨架,内置:

- JWT 登录、校验、刷新、登出
- Casbin RBAC 接口权限控制
- 管理员、角色、部门、菜单、API 权限体系
- 请求日志、登录日志、审计日志
- 文件上传与本地文件访问
- CLI 命令、定时任务、异步队列 worker

运行形态包含三类进程:

1. `service`
   - 提供 HTTP API
   - 构建请求审计日志快照
   - 队列开启时把审计快照投递到异步队列
   - 队列关闭时在当前请求链路同步落库审计日志

2. `worker`
   - 消费异步任务
   - 主要消费请求审计日志异步落库

3. `cron`
   - 负责周期任务

## 3. 当前事实

这些是当前实现与开发规范。

### 3.1 Casbin v3

权限引擎是:

- `github.com/casbin/casbin/v3`
- `github.com/casbin/gorm-adapter/v3`

相关入口:

- [internal/access/casbin/casbin.go](/Users/liuml/data/go/src/go-layout/internal/access/casbin/casbin.go:1)
- [internal/access/casbin/adapter.go](/Users/liuml/data/go/src/go-layout/internal/access/casbin/adapter.go:1)

### 3.2 Claims-First 鉴权

请求上下文保存 claims 快照,不保存完整数据库 `AdminUser` 模型:

- [internal/service/auth/principal.go](/Users/liuml/data/go/src/go-layout/internal/service/auth/principal.go:1)
- [internal/middleware/parse_token.go](/Users/liuml/data/go/src/go-layout/internal/middleware/parse_token.go:1)
- [internal/middleware/admin_auth.go](/Users/liuml/data/go/src/go-layout/internal/middleware/admin_auth.go:1)

这意味着:

- 中间件不应为每个请求回表查用户。
- 需要用户实时数据库状态的地方,由具体业务接口显式查库。
- `AuthPrincipal.AdminUser()` 是轻量投影,不代表数据库最新状态。

### 3.3 请求日志

请求日志链路以审计日志为核心:

- 请求链路:缓存请求体、录制响应体,构建审计快照。
- 队列开启:把审计快照投给队列,由 worker 异步落库。
- 队列关闭:在当前请求链路同步落库审计日志。

全局 zap 文件日志用于系统日志、错误日志和 panic 日志;每个请求日志按审计快照链路处理。

关键文件:

- [internal/middleware/logger.go](/Users/liuml/data/go/src/go-layout/internal/middleware/logger.go:1)
- [internal/middleware/audit_queue.go](/Users/liuml/data/go/src/go-layout/internal/middleware/audit_queue.go:1)
- [internal/jobs/audit_log.go](/Users/liuml/data/go/src/go-layout/internal/jobs/audit_log.go:1)
- [cmd/worker/worker.go](/Users/liuml/data/go/src/go-layout/cmd/worker/worker.go:1)

### 3.4 CORS

支持常规 `*` 语义:

- `cors_origins: ["*"]`
- `cors_methods: ["*"]`
- `cors_headers: ["*"]`
- `cors_expose_headers: ["*"]`

实现位置:

- [internal/middleware/cors.go](/Users/liuml/data/go/src/go-layout/internal/middleware/cors.go:1)
- [config/config.yaml.example](/Users/liuml/data/go/src/go-layout/config/config.yaml.example:17)

### 3.5 错误国际化

错误文案由请求语言驱动:

- `RequestLocaleHandler` 从 `Accept-Language` 解析语言并写入请求上下文。
- 响应层根据上下文语言选择错误文案语言。
- 通用业务错误默认走错误码文案表。
- 需要细粒度错误文案时,优先使用 `message key`,由响应层统一国际化。
- 参数校验错误由 validator translator 输出多语言文案,字段名优先使用 `label/json/form` 标签。

关键文件:

- [internal/middleware/request_locale.go](/Users/liuml/data/go/src/go-layout/internal/middleware/request_locale.go:1)
- [internal/pkg/i18n/locale.go](/Users/liuml/data/go/src/go-layout/internal/pkg/i18n/locale.go:1)
- [internal/pkg/errors/error.go](/Users/liuml/data/go/src/go-layout/internal/pkg/errors/error.go:1)
- [internal/pkg/errors/zh-cn.go](/Users/liuml/data/go/src/go-layout/internal/pkg/errors/zh-cn.go:1)
- [internal/pkg/errors/en-us.go](/Users/liuml/data/go/src/go-layout/internal/pkg/errors/en-us.go:1)
- [internal/pkg/response/response.go](/Users/liuml/data/go/src/go-layout/internal/pkg/response/response.go:1)
- [internal/validator/runtime.go](/Users/liuml/data/go/src/go-layout/internal/validator/runtime.go:1)
- [internal/validator/translation.go](/Users/liuml/data/go/src/go-layout/internal/validator/translation.go:1)
- [internal/validator/binding.go](/Users/liuml/data/go/src/go-layout/internal/validator/binding.go:1)

### 3.6 菜单 I18n

菜单标题由请求语言驱动:

- 列表/树:只返回当前语言 `title`,不返回 `title_i18n`。
- 详情:返回 `title_i18n` 供编辑回填,不返回 `title`。
- 写接口:使用 `title_i18n`,支持 `zh-CN` 与 `en-US`,至少一种非空。

关键文件:

- [internal/controller/admin_v1/auth_menu.go](/Users/liuml/data/go/src/go-layout/internal/controller/admin_v1/auth_menu.go:1)
- [internal/service/menu/menu_query.go](/Users/liuml/data/go/src/go-layout/internal/service/menu/menu_query.go:1)
- [internal/service/menu/menu_edit.go](/Users/liuml/data/go/src/go-layout/internal/service/menu/menu_edit.go:1)
- [internal/resources/menu.go](/Users/liuml/data/go/src/go-layout/internal/resources/menu.go:1)
- [tests/admin_test/menu_test.go](/Users/liuml/data/go/src/go-layout/tests/admin_test/menu_test.go:1)

### 3.7 文件组织

典型职责包:

- `internal/service/admin/`
- `internal/service/role/`
- `internal/service/dept/`
- `internal/service/access/`
- `internal/validator/`
- `internal/model/`

继续改时,优先往对应职责文件里放;可以同包拆文件,保持文件职责清晰。

## 4. 任务阅读索引

新会话建议先看:

1. [README.md](/Users/liuml/data/go/src/go-layout/README.md:1)
2. [docs/COMMANDS_AND_TASKS.md](/Users/liuml/data/go/src/go-layout/docs/COMMANDS_AND_TASKS.md:1)
3. [cmd/root.go](/Users/liuml/data/go/src/go-layout/cmd/root.go:1)
4. [cmd/service/service.go](/Users/liuml/data/go/src/go-layout/cmd/service/service.go:1)
5. [internal/routers](/Users/liuml/data/go/src/go-layout/internal/routers)

按任务追加阅读:

- 新增或修改后台接口:
  - [internal/routers](/Users/liuml/data/go/src/go-layout/internal/routers)
  - [internal/controller/admin_v1](/Users/liuml/data/go/src/go-layout/internal/controller/admin_v1)
  - [internal/validator/form](/Users/liuml/data/go/src/go-layout/internal/validator/form)
  - [internal/service](/Users/liuml/data/go/src/go-layout/internal/service)
  - [internal/resources](/Users/liuml/data/go/src/go-layout/internal/resources)
  - [tests/admin_test](/Users/liuml/data/go/src/go-layout/tests/admin_test)

- 权限 / Casbin / 菜单可见性:
  - [internal/access/casbin](/Users/liuml/data/go/src/go-layout/internal/access/casbin)
  - [internal/service/access](/Users/liuml/data/go/src/go-layout/internal/service/access)
  - [rbac_model.conf](/Users/liuml/data/go/src/go-layout/rbac_model.conf:1)

- 鉴权 / token / 当前用户:
  - [internal/middleware/parse_token.go](/Users/liuml/data/go/src/go-layout/internal/middleware/parse_token.go:1)
  - [internal/middleware/admin_auth.go](/Users/liuml/data/go/src/go-layout/internal/middleware/admin_auth.go:1)
  - [internal/service/auth](/Users/liuml/data/go/src/go-layout/internal/service/auth)

- 请求日志 / 审计 / 队列:
  - [internal/middleware/logger.go](/Users/liuml/data/go/src/go-layout/internal/middleware/logger.go:1)
  - [internal/middleware/audit_queue.go](/Users/liuml/data/go/src/go-layout/internal/middleware/audit_queue.go:1)
  - [internal/jobs/audit_log.go](/Users/liuml/data/go/src/go-layout/internal/jobs/audit_log.go:1)
  - [internal/service/audit](/Users/liuml/data/go/src/go-layout/internal/service/audit)
  - [cmd/worker/worker.go](/Users/liuml/data/go/src/go-layout/cmd/worker/worker.go:1)

- 菜单标题国际化:
  - [internal/service/menu](/Users/liuml/data/go/src/go-layout/internal/service/menu)
  - [internal/model/menu_i18n.go](/Users/liuml/data/go/src/go-layout/internal/model/menu_i18n.go:1)
  - [internal/resources/menu.go](/Users/liuml/data/go/src/go-layout/internal/resources/menu.go:1)
  - [tests/admin_test/menu_test.go](/Users/liuml/data/go/src/go-layout/tests/admin_test/menu_test.go:1)

- 模型基础能力 / 列表分页 / 删除:
  - [internal/model/base.go](/Users/liuml/data/go/src/go-layout/internal/model/base.go:1)
  - [internal/model/base_crud.go](/Users/liuml/data/go/src/go-layout/internal/model/base_crud.go:1)
  - [internal/model/base_list.go](/Users/liuml/data/go/src/go-layout/internal/model/base_list.go:1)
  - [internal/model/base_tree.go](/Users/liuml/data/go/src/go-layout/internal/model/base_tree.go:1)

## 5. 目录边界

目录需要按职责边界理解,而不只是物理位置。

### 5.1 `cmd/`

负责程序入口和启动编排,不负责业务逻辑。

关键命令:

- `service`
- `worker`
- `cron`
- `command`

### 5.2 `internal/controller/`

HTTP 控制器层。

职责:

- 收参
- 调 validator/form
- 调 service
- 返回 response

不应该在 controller 里写复杂业务判断、事务或批量数据修复逻辑。

### 5.3 `internal/service/`

业务层核心。

职责:

- 业务规则
- 事务编排
- 模型组合查询
- 触发权限同步
- 触发 token 撤销

这个项目的大部分改动都应该优先落在 service 层。

### 5.4 `internal/model/`

GORM 模型层和基础数据访问层。

职责:

- 表结构
- 通用 CRUD
- 列表分页
- 树形节点辅助

复杂业务判断放在 service 层,model 保持数据模型与通用数据访问职责。

### 5.5 `internal/resources/`

响应整形层。

职责:

- 把 model / service 结果转换成前端要的结构。

响应整形采用显式 transformer,不直接把 model 原样 JSON 输出。

### 5.6 `internal/validator/` 与 `internal/validator/form/`

参数对象和校验规则层。

职责:

- 定义请求参数 struct。
- 定义 tag 校验规则。
- 统一错误翻译和绑定错误处理。

### 5.7 `internal/service/access/`

权限同步协调层。

职责拆分为:

- `api_cache`
- `scope_resolver`
- `graph_loader`
- `coordinator`
- `menu_api_defaults`
- `system_defaults`
- `user_permission_sync`

外层统一从 `PermissionSyncCoordinator` 进入,业务层通过协调器触发 Casbin 权限同步。

## 6. 开发风格

### 6.1 命名

- service 名称以业务对象命名,如 `AdminUserService`、`RoleService`。
- constructor 用 `NewXxxService()`。
- 业务入口方法名直接用动词:`List`、`Create`、`Update`、`Delete`、`BindRole`。
- 内部辅助方法用小写,尽量语义直接:`buildListCondition`、`updateDeptRole`。

避免引入抽象但缺少项目语境的命名,例如:

- `Processor`
- `Manager`
- `Facade`
- `RepositoryFactory`

只有代码里存在明确同类模式时,才沿用同类命名。

### 6.2 结构

优先做“同包拆文件”,新增 package 前先确认职责边界确实独立。

结构整理方式:

- 保留 `package` 不变。
- 保留原 service 名称不变。
- 把一个大文件拆成多个职责文件。

### 6.3 错误处理

业务错误优先使用:

- `internal/pkg/errors`

典型方式:

- `e.NewBusinessError(...)`

业务错误码优先复用,保持现有错误码风格。

### 6.4 事务与权限刷新

事务与权限刷新规范:

- service 层显式事务。
- 事务工具统一复用。
- 权限刷新或缓存刷新在事务提交后处理。

“事务 + 后置刷新”保持在 service 层编排。

### 6.5 响应

接口响应走统一响应封装,不直接返回裸对象。

所以:

- controller 保持现有 response 风格。
- 列表接口尽量返回 collection。
- detail 接口尽量走 transformer。

### 6.6 注释

注释使用少量中文,说明职责和意图;除非必要复杂需求才写长注释。

可以写:

- `// BindRole 绑定角色。`
- `// ReloadPolicyCache 在事务提交后刷新共享 Casbin Enforcer 的内存策略。`

注释保持高信息密度,只解释职责、意图和复杂逻辑。

## 7. 写代码硬规则

### 7.1 先复用,再新增

做任何功能前,先查:

- service 是否存在类似逻辑。
- model 是否存在通用方法。
- validator 是否存在相同校验模式。
- resources 是否存在类似 transformer。

优先复用现有能力。

### 7.2 小步修改

这个项目是工程型仓库,不是实验仓库。

可以:

- 局部重构。
- 同包拆文件。
- 补测试。

保持现有范式,尤其避免突然引入:

- repository 模式全量替换。
- 泛型 service 框架大改。
- 新 DI 容器。
- 新 HTTP 框架。

### 7.3 保持接口签名稳定

如果只是做结构优化或内部修复:

- controller 签名不改。
- service 对外方法名尽量不改。
- HTTP API 不改。
- CLI 命令名不改。

### 7.4 新增行为要配测试

尤其是下面几类:

- 鉴权
- 权限同步
- CORS
- 日志截断
- token 撤销
- 默认值生成
- 事务提交后刷新

如果是新增接口,不仅要补直接相关的测试,还要默认在 `tests/` 目录下补接口级测试用例。

原因:

- 项目有明确的 `tests/admin_test` 集成测试入口。
- 只补 service 或 middleware 级测试,不足以证明接口链路真实可用。
- 新接口至少要验证路由、鉴权、请求参数、响应结构中的关键路径。

默认要求:

- 新增 HTTP 接口:补 `tests/` 目录下的接口测试。
- 修改现有 HTTP 接口行为:补或更新 `tests/` 目录下对应测试。
- 只改内部实现且对外行为不变:可以只补包内测试,但要能说明为什么不需要接口测试。

### 7.5 改完先验证

改完代码后,先自己跑测试,再决定是否结束本轮工作。

最小要求:

- 小改动:跑直接受影响包测试。
- 中等改动:跑相关子系统测试。
- 公共层或高风险改动:优先跑 `go test ./...`。

以下结束方式不符合项目要求:

- 只改代码,不跑测试。
- 只说“理论上没问题”。
- 只让用户自己去验证。

如果因为环境限制无法完成某项测试,结果里必须明确:

- 哪些测试跑了。
- 哪些测试没跑。
- 没跑的原因是什么。

推荐基线:

```bash
go test ./internal/...
go test ./...
go test ./internal/middleware ./internal/service/access ./internal/service/auth
```

## 8. 新增后台接口路径

建议按这个顺序:

1. 在 `internal/validator/form/` 定义参数 struct。
2. 在 `internal/model/` 复用或补充数据访问方法。
3. 在 `internal/service/` 写业务逻辑。
4. 在 `internal/resources/` 补返回结构。
5. 在 `internal/controller/admin_v1/` 加控制器方法。
6. 在 `internal/routers/` 的声明式路由树里注册。
7. 如涉及权限元数据,同步执行 `command api-route`。
8. 如涉及角色/菜单/API 关系变化,确认权限重建链路是否需要触发。
9. 在 `tests/` 目录下补接口级测试。

## 9. 高风险区域

### 9.1 `internal/service/access`

会影响:

- 菜单可见性
- API 权限
- Casbin 最终策略

### 9.2 `internal/service/auth`

风险点:

- 鉴权链路采用 claims-first。
- token 黑名单 / 撤销逻辑集中在认证服务内。
- 改不好会影响所有登录态请求。

### 9.3 `internal/middleware/logger*`

风险点:

- 请求体截断。
- 响应体捕获。
- 队列异步投递。
- 队列关闭时的同步审计落库。
- 性能与日志正确性平衡。

### 9.4 `internal/model/base_*`

风险点:

- 很多 model 共用。
- 一旦改坏,会影响列表、分页、删除、防误删逻辑。

## 10. 接手检查命令

先看工作树状态:

```bash
git status --short
```

再看测试基线:

```bash
go test ./...
```

如果只改中间件 / 权限 / 鉴权,优先跑对应子集:

```bash
go test ./internal/middleware ./internal/service/access ./internal/service/auth
```

## 11. 文档关系

根目录文档建议这样理解:

- [README.md](/Users/liuml/data/go/src/go-layout/README.md:1)
  - 对外项目介绍、基础使用说明。

- [docs/COMMANDS_AND_TASKS.md](/Users/liuml/data/go/src/go-layout/docs/COMMANDS_AND_TASKS.md:1)
  - 命令说明、定时任务与操作约束。

- 本文档
  - 给下一位 AI 的“当前状态 + 接手约束 + 写码风格”说明。

如果文档和代码冲突:

- 以代码为准。
- 再顺手修正文档。

## 12. 一句话接手策略

先读入口和当前链路,再读与你任务相关的 service;优先复用现有结构,保持命名和接口稳定,用小步修改把需求做完,维持项目既有风格。


================================================
FILE: README.en.md
================================================
# <div align="center">gin-layout</div>

<div align="center">
  <a href="./README.md">中文</a> | <strong>English</strong>
</div>

<br />

<div align="center">
  <strong>A Gin-based admin backend scaffold</strong>
</div>

<div align="center">
  Built with JWT auth, RBAC, request/login logging, file upload, readiness probes, validation, request-locale i18n, declarative routing, and CLI initialization commands.
</div>

<br />

<div align="center">
  <img src="https://github.com/wannanbigpig/gin-layout/actions/workflows/go.yml/badge.svg" alt="go" />
  <img src="https://github.com/wannanbigpig/gin-layout/actions/workflows/codeql.yml/badge.svg" alt="codeql" />
  <img src="https://goreportcard.com/badge/github.com/wannanbigpig/gin-layout" alt="go report card" />
  <img src="https://img.shields.io/github/license/wannanbigpig/gin-layout" alt="license" />
  <img src="https://img.shields.io/badge/Go-%3E%3D1.23-blue.svg" alt="go version" />
</div>

<br />

## Why This Exists

Most admin projects start with the same goal: get login, permissions, menus, uploads, and logs working quickly. In practice, the same engineering problems show up again and again:

- Auth, permissions, logging, and file handling are split across too many places
- Route declarations, menus, and API permissions drift apart over time
- The same admin infrastructure is rewritten repeatedly across projects
- Config, command, migration, and deployment workflows lack a clear baseline

`gin-layout` is built to turn these repeated backend concerns into a reusable, production-oriented foundation for admin systems.

## Highlights

| Capability | Description |
| --- | --- |
| Auth | JWT login, token verification, auto refresh, and blacklist support |
| RBAC | Admin, role, department, menu, and API permission management |
| Route Metadata | Declarative route tree generates both Gin routes and API metadata |
| Logs | Built-in login logs, request logs, and unified response structure |
| File Access | File upload and public / private file access |
| I18n | Error messages and menu titles are resolved by `Accept-Language` (`zh-CN` / `en-US`) |
| Health Probes | Built-in `/ping` and `/health/readiness` for liveness and dependency-readiness checks |
| Tooling | CLI commands for initialization, route sync, permission rebuild, and migrations |
| Hot Reload | Partial config hot reload with fallback to previous live instances on failure |

## Related Resources

- Frontend project: [go-admin-ui](https://github.com/wannanbigpig/go-admin-ui)
- Online docs: [Apifox](https://wannanbigpig.apifox.cn/)
- Demo: [Live Demo](https://x-l-admin.wannanbigpig.com/)
- Commands and jobs guide: [docs/COMMANDS_AND_TASKS.en.md](./docs/COMMANDS_AND_TASKS.en.md)
- Migration command guide: [docs/MIGRATE_COMMANDS.en.md](./docs/MIGRATE_COMMANDS.en.md)

## Quick Start

### 1. Requirements

- `Go >= 1.23`
- `MySQL >= 5.7`
- `Redis >= 5.0` (optional)

### 2. Install

```bash
git clone https://github.com/wannanbigpig/gin-layout.git
cd gin-layout
go mod download
```

### 3. Run Migrations

Recommended project commands:

```bash
go run main.go command migrate        # defaults to migrate up
go run main.go command migrate check
```

After migrations finish, a default baseline dataset is inserted, including the super admin account `super_admin / 123456`. It is only recommended for local initialization. Change the password immediately after the first login.

For migration file creation, timestamp naming rules, and full `down/goto/force/version` usage, see [docs/MIGRATE_COMMANDS.en.md](./docs/MIGRATE_COMMANDS.en.md).

### 4. Configure

For source runs, `GO_ENV=development` is recommended. Without `-c`:

- development mode uses `config.yaml` in the current working directory
- if missing, it copies from `config/config.yaml.example` in the current working directory
- non-development mode resolves `config.yaml` next to the executable and copies from sibling `config.yaml.example` when needed

You can also copy and edit the config file manually.

Minimal example:

```yaml
app:
  app_env: local
  debug: true
  language: zh_CN
  trusted_proxies:
    - 127.0.0.1
  watch_config: true
  # allow_degraded_startup: false

jwt:
  ttl: 7200
  refresh_ttl: 3600
  secret_key: change-me-to-a-random-secret

mysql:
  enable: true
  host: 127.0.0.1
  port: 3306
  database: go_layout
  username: root
  password: your_password

redis:
  enable: true
  host: 127.0.0.1
  port: 6379
  password: ""
  database: 0

queue:
  enable: true
  use_default_redis: true
  namespace: go_layout
  concurrency: 8
  strict_priority: false
  queues:
    critical: 4
    default: 2
    audit: 2
    low: 1
  audit_max_retry: 3
  audit_timeout_seconds: 10
```

Notes:

- `jwt.secret_key` is required and cannot be empty
- If you only run the API and do not need async jobs, set `queue.enable` to `false`
- If `queue.enable=true` but you do not want to reuse `redis.*`, set `queue.use_default_redis` to `false` and fill in `queue.redis.*`

### 5. Start Service

```bash
GO_ENV=development go run main.go service
```

To explicitly set the listen host or port:

```bash
GO_ENV=development go run main.go service -H 127.0.0.1 -P 9001
```

If `queue.enable=true`, start the worker in a separate process as well:

```bash
GO_ENV=development go run main.go worker
```

### 6. Verify

```bash
curl http://127.0.0.1:9001/ping
curl http://127.0.0.1:9001/health/readiness
```

- `/ping` returns `pong` when the HTTP process is alive.
- `/health/readiness` returns `ready=true` when required dependencies are ready for the current runtime mode.

## Core Ideas

### Prefer Declarative Routing

Admin routes are maintained in a single declarative route tree. The current entry is `AdminRouteTree()` in `internal/routers/admin_router.go`. Gin route registration and API metadata initialization are both generated from that tree so route code and permission metadata do not drift apart.

### Database Relations Are The Source Of Truth

The current permission model treats database relations as the source of truth, while Casbin performs final API authorization checks. The `rebuild-user-permissions` command rebuilds each user's final API permissions from user, department, role, menu, and API relationships stored in the database.

### Hot Reload Is Tiered

The project supports config hot reload, but not every setting can be applied live. Supported resources are rebuilt when possible. If a rebuild fails, the previous live instance is kept so the service can continue running.

### Request-Locale Driven Texts

The request pipeline parses `Accept-Language` (currently `zh-CN` / `en-US`) and applies normalized fallback behavior:

- Error messages are resolved by request locale, with fallback to default locale (`zh-CN`).
- Menu list / user menu tree returns localized `title` only.
- Menu detail returns `title_i18n` for edit backfill, not `title`.
- Menu create/update writes `title_i18n`; at least one of `zh-CN` / `en-US` must be non-empty.

## Commands

Help:

```bash
go run main.go -h
go run main.go command -h
go run main.go service --help
```

Common commands:

| Command | Description |
| --- | --- |
| `go run main.go version` | Print the current version |
| `go run main.go service` | Start the API service |
| `go run main.go service -H 0.0.0.0 -P 9001` | Explicitly set the listen host and port |
| `go run main.go worker` | Start the Asynq async worker |
| `go run main.go cron` | Start scheduled jobs |
| `go run main.go command demo` | Run the demo command |
| `go run main.go command api-route` | Scan the declarative route tree and rebuild the `api` route table |
| `go run main.go command rebuild-user-permissions` | Rebuild final user API permissions from database relationships |
| `go run main.go command init-system` | Roll back and rerun migrations, rebuild API routes, and rebuild user permissions |
| `go run main.go -c ./config.yaml command task scan-async` | Scan async task registration against the task-definition mirror |
| `go run main.go -c ./config.yaml command task scan-cron` | Scan cron task definitions against the task-definition mirror |
| `go run main.go -c ./config.yaml command migrate check` | Validate migration naming and up/down pairing |
| `go run main.go -c ./config.yaml command migrate up` | Apply all pending migrations |
| `go run main.go -c ./config.yaml command migrate down 1` | Roll back one migration version |

If the config file is not in the default location:

```bash
go run main.go -c /path/to/config.yaml service
go run main.go -c /path/to/config.yaml command init-system
```

See [docs/MIGRATE_COMMANDS.en.md](./docs/MIGRATE_COMMANDS.en.md) for full migration command details.

## Configuration

### Config Resolution

Config lookup order:

1. Explicit `-c` / `--config`
2. `config.yaml` in the current working directory for development mode
3. if missing, copy from `config/config.yaml.example` in the current working directory
4. `config.yaml` next to the executable for non-development mode
5. if missing, copy from sibling `config.yaml.example`

### Key Settings

| Key | Description |
| --- | --- |
| `app.base_path` | Base directory for logs, uploaded files, and other local paths; when not set it follows `GO_ENV` (development=current working directory, otherwise executable directory) |
| `app.allow_degraded_startup` | Only applies to the `service` command; allows the HTTP service to start when dependency initialization fails, exposing the not-ready state through readiness and route guards |
| `app.base_url` | URL prefix used to generate public file access URLs |
| `app.trusted_proxies` | Trusted proxy addresses or CIDRs that affect `ClientIP()` and log IPs |
| `jwt.secret_key` | Required; do not use weak placeholder values in production |
| `jwt.ttl` / `jwt.refresh_ttl` | Token expiration and auto-refresh threshold |
| `mysql` | Database enable flag and connection settings |
| `redis` | Cache, blacklist, and distributed lock settings |
| `queue.use_default_redis` | `true` reuses `redis.*`; `false` uses the independent `queue.redis.*` connection |
| `queue` | Asynq enable flag, queue concurrency, priorities, and audit-log retry settings |
| `logger` | Log output, rotation, and retention strategy |

If requests pass through Nginx, Ingress, or a load balancer, keep `app.trusted_proxies` aligned or client IP logging may be inaccurate.

### Worker And Cron

- `service` serves the HTTP API.
- `worker` consumes Asynq jobs. The current first phase only moves request audit-log persistence to Asynq.
- When `queue.enable=false`, you do not need to start `worker`; request audit logs are persisted synchronously in the current request flow.
- `cron` owns scheduled jobs. The demo cron task is controlled by the system config `task.cron_demo_enabled` and is disabled by default after initialization.
- `reset-system-data` is only registered when `app.enable_reset_system_cron=true` is explicitly configured, and startup logs a high-risk warning.
- Do not register the same recurring business task in both `cron` and the async worker flow, or it will run twice.

Note: `reset-system-data` currently calls `system.ReinitializeSystemData()`, which rolls back migrations and rebuilds system data. Keep `app.enable_reset_system_cron=false` in production, and only enable it temporarily when you explicitly need to rebuild system data.

### Hot Reload

Enable it with:

```yaml
app:
  watch_config: true
```

Hot-reload supported:

- `logger.*`
- `mysql.*`
- `redis.*`
- `app.base_url`
- `app.cors_*`
- `jwt.ttl`
- `jwt.refresh_ttl`

Detected but requires restart:

- `app.trusted_proxies`
- `app.language`
- `app.allow_degraded_startup`
- `jwt.secret_key`
- service listen address and port
- route structure

Notes:

- `watch_config=true` only enables file watching; it does not mean every setting is safely swappable
- MySQL, Redis, and Casbin instances are rebuilt from the new config and the old instance is kept on failure
- JWT secret hot reload is not currently supported; changes are logged and only take effect after restart

## Development

### Add A New Endpoint

1. Write the controller in `internal/controller/`
2. Write the business logic in `internal/service/`
3. Define request params in `internal/validator/form/`
4. Declare the route in `AdminRouteTree()`
5. Run `go run main.go command api-route` if the API route table needs to be refreshed

### Validation Conventions

- For enum-like fields (for example `status`, `is_auth`, `method`), prefer explicit `oneof` constraints.
- For ID arrays (for example `role_ids`, `menu_list`, `api_list`), prefer `dive,gt=0` so invalid IDs such as `0` are rejected at validation stage.
- When adding/changing validation rules, add both positive and negative tests to prevent regressions.

### Test

```bash
go test ./...
go test ./tests/admin_test
```

Tests prefer the root `config.yaml`. If MySQL or Redis is unavailable in the current environment, the test setup falls back to example config paths for cases that can run without those external services.

## Deployment

### Build

```bash
go build -o go-layout main.go
./go-layout service
```

If `-c` is not provided, use `GO_ENV=development` in development and ensure `config/config.yaml.example` exists in the current working directory or `config.yaml` has already been generated. For binary deployment, keep the config file next to the executable.

### Supervisor

```ini
[program:go-layout]
command=/path/to/go-layout -c /path/to/config.yaml service
directory=/path/to/go-layout
autostart=true
autorestart=true
startsecs=5
user=www-data
redirect_stderr=true
stdout_logfile=/path/to/go-layout/supervisord.log
```

### Nginx

```nginx
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:9001;
    }
}
```

If you are behind a reverse proxy, add the proxy address or CIDR to `app.trusted_proxies`.

## Project Layout

```text
gin-layout/
├── cmd/                    # CLI entrypoints
├── config/                 # Config structs and example config
├── data/                   # MySQL / Redis and migrations
├── docs/                   # Supplementary docs and resources
├── internal/
│   ├── access/             # Access and permission infrastructure
│   ├── controller/         # Controllers
│   ├── middleware/         # Middlewares
│   ├── model/              # Data models
│   ├── resources/          # Resource transformers
│   ├── routers/            # Declarative routing
│   ├── service/            # Business services
│   └── validator/          # Request validation
├── pkg/                    # Shared utilities
├── storage/                # File storage
├── tests/                  # Route and integration tests
└── README.md
```

## 💝 Support This Project

Thanks for using `gin-layout`.

If this project helps you, you can support its ongoing development and maintenance.

<a href="./docs/DONATE.en.md">
  <img src="https://img.shields.io/badge/BUY_ME_A_COFFEE-SUPPORT_AUTHOR-f08a24?style=for-the-badge&logo=buymeacoffee&logoColor=ffdd00&labelColor=4a4a4a" alt="Support the author" />
</a>

## License

This project is released under the MIT License. See [LICENSE](LICENSE).

## Contributing

Issues and pull requests are welcome.

## Disclaimer

This project is provided **“as is”**, without any express or implied warranty. It may contain defects, security vulnerabilities, or implementations that do not fit a specific business scenario. Before using it in production, you should perform your own code review, security hardening, configuration review, permission validation, and data backup. Any issues caused by using, relying on, deploying, modifying, or operating this project are the responsibility of the user.


================================================
FILE: README.md
================================================
# <div align="center">gin-layout</div>

<div align="center">
  <strong>中文</strong> | <a href="./README.en.md">English</a>
</div>

<br />

<div align="center">
  <strong>基于 Gin 的后台管理系统脚手架</strong>
</div>

<div align="center">
  内置 JWT 认证、RBAC 权限、请求/登录日志、文件上传、readiness 探针、参数校验、请求语言国际化、声明式路由和 CLI 初始化命令。
</div>

<br />

<div align="center">
  <img src="https://github.com/wannanbigpig/gin-layout/actions/workflows/go.yml/badge.svg" alt="go" />
  <img src="https://github.com/wannanbigpig/gin-layout/actions/workflows/codeql.yml/badge.svg" alt="codeql" />
  <img src="https://goreportcard.com/badge/github.com/wannanbigpig/gin-layout" alt="go report card" />
  <img src="https://img.shields.io/github/license/wannanbigpig/gin-layout" alt="license" />
  <img src="https://img.shields.io/badge/Go-%3E%3D1.23-blue.svg" alt="go version" />
</div>

<br />

## 项目定位

很多后台项目一开始都只是想“先把登录、权限、菜单、上传和日志跑起来”,但真正进入开发后,通常会很快碰到这些重复问题:

- 认证、权限、日志和文件能力分散,初始化成本高
- 路由、菜单、API 权限关系容易逐步失控
- 不同项目里同一套后台基础设施被反复重写
- 配置、命令、迁移和部署流程缺少统一约定

`gin-layout` 的目标很明确:把后台管理场景里高频、重复、工程化要求高的基础能力沉淀成一套可以直接落地的后端骨架。

## 核心特性

| 能力 | 说明 |
| --- | --- |
| Auth | 内置 JWT 登录、Token 校验、自动刷新、黑名单 |
| RBAC | 管理员、角色、部门、菜单、API 权限管理 |
| Route Metadata | 声明式路由树统一生成 Gin 路由和 API 元数据 |
| Logs | 内置登录日志、请求日志、统一响应结构 |
| File Access | 文件上传与公开 / 私有文件访问 |
| I18n | 基于 `Accept-Language` 返回错误文案与菜单标题(支持 `zh-CN` / `en-US`) |
| Health Probes | 提供 `/ping` 与 `/health/readiness`,便于存活检测与依赖就绪检查 |
| Tooling | 提供 CLI 初始化、路由同步、权限重建、迁移配套能力 |
| Hot Reload | 支持部分配置热更新,失败时保留旧实例继续运行 |

## 相关资源

- 前端项目:[go-admin-ui](https://github.com/wannanbigpig/go-admin-ui)
- 在线文档:[Apifox](https://wannanbigpig.apifox.cn/)
- 演示地址:[在线演示](https://x-l-admin.wannanbigpig.com/)
- 命令与任务文档:[docs/COMMANDS_AND_TASKS.md](./docs/COMMANDS_AND_TASKS.md)
- 迁移命令详解:[docs/MIGRATE_COMMANDS.md](./docs/MIGRATE_COMMANDS.md)

## 快速开始

### 1. 环境要求

- `Go >= 1.23`
- `MySQL >= 5.7`
- `Redis >= 5.0`(可选)

### 2. 安装项目

```bash
git clone https://github.com/wannanbigpig/gin-layout.git
cd gin-layout
go mod download
```

### 3. 执行迁移

推荐使用项目命令:

```bash
go run main.go command migrate        # 默认等价于 migrate up
go run main.go command migrate check
```

迁移执行完成后会写入一套默认基础数据,其中包含超级管理员账号 `super_admin / 123456`。仅建议用于本地初始化,首次登录后请立即修改密码。

迁移文件创建、时间戳命名规范、`down/goto/force/version` 等详细说明见:[docs/MIGRATE_COMMANDS.md](./docs/MIGRATE_COMMANDS.md)。

### 4. 配置项目

源码运行时建议带上 `GO_ENV=development`。未显式传入 `-c` 时:

- 开发模式会把当前工作目录下的 `config/config.yaml.example` 自动复制为项目根目录 `config.yaml`
- 非开发模式会在可执行文件同级查找 `config.yaml`,若不存在则尝试从同目录 `config.yaml.example` 复制

也可以手动复制配置文件后再修改。

最小配置示例:

```yaml
app:
  app_env: local
  debug: true
  language: zh_CN
  trusted_proxies:
    - 127.0.0.1
  watch_config: true
  # allow_degraded_startup: false

jwt:
  ttl: 7200
  refresh_ttl: 3600
  secret_key: change-me-to-a-random-secret

mysql:
  enable: true
  host: 127.0.0.1
  port: 3306
  database: go_layout
  username: root
  password: your_password

redis:
  enable: true
  host: 127.0.0.1
  port: 6379
  password: ""
  database: 0

queue:
  enable: true
  use_default_redis: true
  namespace: go_layout
  concurrency: 8
  strict_priority: false
  queues:
    critical: 4
    default: 2
    audit: 2
    low: 1
  audit_max_retry: 3
  audit_timeout_seconds: 10
```

注意:

- `jwt.secret_key` 为必填项,不能为空
- 如果只启动 API、不启用异步任务,可以把 `queue.enable` 设为 `false`
- 如果 `queue.enable=true` 但不想复用 `redis.*`,请把 `queue.use_default_redis` 设为 `false`,并补齐 `queue.redis.*`

### 5. 启动服务

```bash
GO_ENV=development go run main.go service
```

需要显式指定监听地址或端口时:

```bash
GO_ENV=development go run main.go service -H 127.0.0.1 -P 9001
```

如果启用了 `queue.enable=true`,还需要单独启动 worker:

```bash
GO_ENV=development go run main.go worker
```

### 6. 验证服务

```bash
curl http://127.0.0.1:9001/ping
curl http://127.0.0.1:9001/health/readiness
```

- `/ping` 返回 `pong`,说明 HTTP 进程已正常启动
- `/health/readiness` 返回 `ready=true`,说明当前配置下需要的依赖已经就绪

## 设计思路

### 声明式路由优先

后台路由维护在一棵声明式路由树中,目前入口位于 `internal/routers/admin_router.go` 的 `AdminRouteTree()`。Gin 路由注册和 API 元数据初始化都从这棵树生成,避免“代码路由”和“权限路由”长期分叉。

### 数据库关系是权限真相

当前权限模型采用“数据库关系为真相,Casbin 负责最终接口判定”的方式。角色、部门、菜单和 API 的业务关系以数据库为准,`rebuild-user-permissions` 命令会按这些关系重建用户最终 API 权限。

### 配置热更新是分级的

项目支持配置热更新,但不是所有配置都会在运行中立即生效。支持热更新的资源会尝试重建;如果重建失败,会继续保留旧实例运行,避免把服务直接打挂。

### 请求语言驱动文案

项目会在请求链路读取 `Accept-Language`(当前支持 `zh-CN` / `en-US`),并做归一化与降级处理:

- 错误码文案:按请求语言返回;无法识别时降级到默认语言(`zh-CN`)。
- 菜单列表/用户菜单树:仅返回 `title`,由后端按请求语言解析后返回。
- 菜单详情:返回 `title_i18n`(用于前端编辑回填),不返回 `title`。
- 菜单新增/编辑:写接口使用 `title_i18n`,目前支持 `zh-CN` 与 `en-US`,两者至少一种非空。

## 常用命令

查看帮助:

```bash
go run main.go -h
go run main.go command -h
go run main.go service --help
```

常用命令:

| 命令 | 说明 |
| --- | --- |
| `go run main.go version` | 输出当前版本号 |
| `go run main.go service` | 启动 API 服务 |
| `go run main.go service -H 0.0.0.0 -P 9001` | 显式指定监听地址与端口 |
| `go run main.go worker` | 启动 Asynq 异步任务消费进程 |
| `go run main.go cron` | 启动定时任务 |
| `go run main.go command demo` | 运行示例命令 |
| `go run main.go command api-route -y` | 扫描声明式路由树并重建 `api` 路由表 |
| `go run main.go command rebuild-user-permissions -y` | 按数据库关系重建用户最终 API 权限 |
| `go run main.go command init-system -y` | 回滚并重新执行迁移、重建 API 路由、重建用户权限 |
| `go run main.go -c ./config.yaml command task scan-async` | 扫描异步任务注册与任务定义镜像 |
| `go run main.go -c ./config.yaml command task scan-cron` | 扫描定时任务定义与任务定义镜像 |
| `go run main.go -c ./config.yaml command migrate check` | 校验迁移文件命名与 up/down 配对 |
| `go run main.go -c ./config.yaml command migrate up` | 执行全部未应用迁移 |
| `go run main.go -c ./config.yaml command migrate down 1` | 回滚 1 个迁移版本 |

如果配置文件不在默认位置,可以显式指定:

```bash
go run main.go -c /path/to/config.yaml service
go run main.go -c /path/to/config.yaml command init-system
```

迁移命令完整参数见:[docs/MIGRATE_COMMANDS.md](./docs/MIGRATE_COMMANDS.md)。

补充说明:

- `api-route`、`rebuild-user-permissions`、`init-system` 默认会二次确认;自动化场景建议显式加 `-y`
- `init-system` 会清空并重建系统数据,只适合本地初始化或明确允许重置的环境

## 配置说明

### 配置查找顺序

配置文件查找顺序:

1. 显式传入 `-c` / `--config`
2. `GO_ENV=development` 时,使用当前工作目录的 `config.yaml`
3. 若第 2 步缺失,则尝试从当前工作目录的 `config/config.yaml.example` 复制生成
4. 非开发模式下,使用可执行文件所在目录的 `config.yaml`
5. 若第 4 步缺失,则尝试从可执行文件同级的 `config.yaml.example` 复制生成

### 主要配置项

| 配置项 | 说明 |
| --- | --- |
| `app.base_path` | 日志、上传文件等本地路径的基础目录;未配置时默认按 `GO_ENV` 选择(development=当前工作目录,其他=可执行文件目录) |
| `app.allow_degraded_startup` | 仅 `service` 命令生效;依赖初始化失败时允许 HTTP 服务先启动,并通过 readiness / 路由守卫暴露未就绪状态 |
| `app.base_url` | 文件访问 URL 前缀,用于生成公开文件地址 |
| `app.trusted_proxies` | 受信任代理地址或网段,影响 `ClientIP()` 与日志 IP |
| `jwt.secret_key` | 必填;生产环境不能使用弱占位值 |
| `jwt.ttl` / `jwt.refresh_ttl` | Token 过期时间与自动刷新阈值 |
| `mysql` | 数据库开关与连接信息 |
| `redis` | 缓存、黑名单和分布式锁配置 |
| `queue.use_default_redis` | `true` 复用 `redis.*`;`false` 时改用 `queue.redis.*` 独立连接 |
| `queue` | Asynq 异步任务开关、队列命名空间、并发度、优先级和审计日志重试策略 |
| `logger` | 日志输出、切割和保留策略 |

如果通过 Nginx、Ingress 或负载均衡转发请求,需要同步配置 `app.trusted_proxies`,否则客户端 IP 可能记录不准确。

### Worker 与 Cron

- `service` 负责提供 HTTP API。
- `worker` 负责消费 Asynq 异步任务。当前首版只接入请求审计日志异步落库。
- `queue.enable=false` 时,不需要启动 `worker`,请求审计日志会在当前请求链路同步落库。
- `cron` 负责定时任务调度;演示定时任务由系统配置 `task.cron_demo_enabled` 控制,初始化默认关闭。
- `reset-system-data` 需要显式配置 `app.enable_reset_system_cron=true` 才会注册,并在启动时输出高风险告警。
- 不要把同一个周期任务同时注册到 `cron` 和 `worker` 体系里,否则会重复执行。

注意:`reset-system-data` 当前调用的是 `system.ReinitializeSystemData()`,会回滚迁移并重建系统数据。生产环境建议保持 `app.enable_reset_system_cron=false`,只有在明确需要重建系统数据时才临时开启。

### 热更新

启用方式:

```yaml
app:
  watch_config: true
```

支持热更新:

- `logger.*`
- `mysql.*`
- `redis.*`
- `app.base_url`
- `app.cors_*`
- `jwt.ttl`
- `jwt.refresh_ttl`

仅检测并提示“需要重启”:

- `app.trusted_proxies`
- `app.language`
- `app.allow_degraded_startup`
- `jwt.secret_key`
- 服务监听地址与端口
- 路由结构

说明:

- `watch_config=true` 只表示启用监听,不代表所有配置都能无损切换
- MySQL、Redis、Casbin 会按新配置重建实例,失败时保留旧实例
- JWT 密钥当前不支持热更新,修改后会记录告警并继续使用旧密钥,直到进程重启

## 开发指南

### 新增接口流程

1. 在 `internal/controller/` 编写控制器
2. 在 `internal/service/` 编写业务逻辑
3. 在 `internal/validator/form/` 定义请求参数
4. 在 `AdminRouteTree()` 中声明路由
5. 需要更新 API 路由表时执行 `go run main.go command api-route`

### 参数校验约定

- 枚举字段(如 `status`、`is_auth`、`method`)建议使用 `oneof` 显式约束。
- ID 数组字段(如 `role_ids`、`menu_list`、`api_list`)建议统一使用 `dive,gt=0`,避免无效 ID(如 `0`)进入业务层。
- 新增/修改校验规则时,建议同时补充正反例单测,避免后续回归。

### 测试

```bash
go test ./...
go test ./tests/admin_test
```

测试会优先使用项目根目录 `config.yaml`。如果当前环境的 MySQL 或 Redis 不可用,会自动回退到示例配置运行可脱离外部依赖的测试。

## 部署说明

### 构建

```bash
go build -o go-layout main.go
./go-layout service
```

如果没有显式传 `-c`,请在开发环境使用 `GO_ENV=development`,并确保当前工作目录存在 `config/config.yaml.example`(或已生成 `config.yaml`);部署二进制时请保证可执行文件同级存在配置文件。

### Supervisor

```ini
[program:go-layout]
command=/path/to/go-layout -c /path/to/config.yaml service
directory=/path/to/go-layout
autostart=true
autorestart=true
startsecs=5
user=www-data
redirect_stderr=true
stdout_logfile=/path/to/go-layout/supervisord.log
```

### Nginx

```nginx
server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://127.0.0.1:9001;
    }
}
```

如果前面有反向代理,请把代理地址或网段加入 `app.trusted_proxies`。

## 目录结构

```text
gin-layout/
├── cmd/                    # 命令行入口
├── config/                 # 配置结构与示例配置
├── data/                   # MySQL / Redis 与迁移
├── docs/                   # 补充文档与资源
├── internal/
│   ├── access/             # 权限基础设施
│   ├── controller/         # 控制器
│   ├── middleware/         # 中间件
│   ├── model/              # 数据模型
│   ├── resources/          # 资源转换
│   ├── routers/            # 声明式路由
│   ├── service/            # 业务逻辑
│   └── validator/          # 参数验证
├── pkg/                    # 通用工具
├── storage/                # 文件存储
├── tests/                  # 路由与集成测试
└── README.md
```

## 💝 赞助项目

感谢你使用 `gin-layout`。

如果这个项目对你有帮助,欢迎支持项目的持续开发与维护。

<a href="./docs/DONATE.md">
  <img src="https://img.shields.io/badge/BUY_ME_A_COFFEE-%E6%94%AF%E6%8C%81%E4%BD%9C%E8%80%85-f08a24?style=for-the-badge&logo=buymeacoffee&logoColor=ffdd00&labelColor=4a4a4a" alt="支持作者" />
</a>

## 许可证

本项目采用 MIT 许可证,详见 [LICENSE](LICENSE)。

## 贡献

欢迎提交 Issue 和 Pull Request。

## 免责声明

本项目按 **“现状”提供**,不附带任何明示或默示担保。项目可能存在缺陷、安全漏洞或与特定业务场景不匹配的实现;上线前请自行完成代码审查、安全加固、配置审查、权限验收和数据备份。因使用、依赖、部署、改造或运维本项目导致的问题,由使用者自行承担。


================================================
FILE: build.sh
================================================
#!/bin/bash

set -e  # 遇到错误立即退出

# ==================== 配置区域 ====================
PROJECT_NAME="go-layout"
BUILD_DIR="build"
DIST_DIR="${BUILD_DIR}/dist"
KEEP_VERSIONS=3

# 默认平台(单平台构建时使用)
DEFAULT_OS="linux"
DEFAULT_ARCH="amd64"

# 多平台构建列表
PLATFORMS=(
    "linux/amd64"
    "linux/arm64"
    "darwin/amd64"
    "darwin/arm64"
    "windows/amd64"
)

# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# 生成版本号
VERSION=$(date +"%Y%m%d_%H%M%S")

# ==================== 工具函数 ====================

usage() {
    cat << EOF
用法:$0 [选项]

选项:
  -o, --os <os>       目标操作系统 (linux, darwin, windows)
  -a, --arch <arch>   目标架构 (amd64, arm64, 386)
  -p, --platform      指定平台 (格式:os/arch,如 linux/amd64)
  -a, --all           构建所有支持的平台
  -n, --no-compress   不使用 UPX 压缩
  -k, --keep <num>    保留的历史版本数量 (默认:3,0=全部清理)
  -h, --help          显示帮助信息

示例:
  $0                              # 使用默认平台 linux/amd64
  $0 -o linux -a arm64            # 构建 linux/arm64
  $0 -p darwin/amd64              # 构建 darwin/amd64
  $0 --all                        # 构建所有平台
  $0 --all -n                     # 构建所有平台,不压缩
  $0 --all -k 5                   # 构建所有平台,保留 5 个历史版本
  $0 --clean-only -k 0            # 仅清理,删除所有历史版本
EOF
    exit 0
}

# 清理 macOS 资源分叉文件
clean_macos_artifacts() {
    local target_dir="$1"
    if command -v dot_clean &> /dev/null; then
        dot_clean "${target_dir}" 2>/dev/null || true
    fi
    find "${target_dir}" -type f \( -name "._*" -o -name ".DS_Store" \) -delete 2>/dev/null || true
}

# 获取文件大小(字节)
get_file_size() {
    stat -f%z "$1" 2>/dev/null || stat -c%s "$1" 2>/dev/null
}

# 获取人类可读的文件大小
get_human_size() {
    local bytes=$1
    if [ "$bytes" -lt 1024 ]; then
        echo "${bytes}B"
    elif [ "$bytes" -lt 1048576 ]; then
        awk "BEGIN {printf \"%.1fK\", $bytes/1024}"
    else
        awk "BEGIN {printf \"%.1fM\", $bytes/1048576}"
    fi
}

# ==================== 构建流程 ====================

print_header() {
    local os="$1"
    local arch="$2"
    echo -e "${BLUE}=========================================="
    echo "构建:${os}/${arch}"
    echo "版本:${VERSION}"
    echo "==========================================${NC}"
}

# 编译 Go 二进制文件
build_binary() {
    local os="$1"
    local arch="$2"
    local dist_dir="$3"
    local binary_name="$4"

    echo "正在编译..."

    local binary_file="${dist_dir}/${binary_name}"
    # Windows 平台添加.exe 后缀
    if [ "$os" = "windows" ]; then
        binary_file="${dist_dir}/${binary_name}.exe"
    fi

    if ! CGO_ENABLED=0 GOOS="${os}" GOARCH="${arch}" go build \
        -ldflags="-w -s" -trimpath -o "${binary_file}" .; then
        echo -e "${RED}编译失败:${os}/${arch}${NC}"
        return 1
    fi

    local size=$(get_file_size "${binary_file}")
    echo "编译完成:$(get_human_size ${size})"
    return 0
}

# 使用 UPX 压缩二进制文件
compress_with_upx() {
    local os="$1"
    local dist_dir="$2"
    local binary_name="$3"
    local use_compress="$4"

    if [ "$use_compress" = "false" ]; then
        echo "跳过 UPX 压缩"
        return 0
    fi

    if ! command -v upx &> /dev/null; then
        echo "提示:安装 UPX 可减小二进制大小 (brew install upx)"
        return 0
    fi

    local binary_file="${dist_dir}/${binary_name}"
    [ "$os" = "windows" ] && binary_file="${dist_dir}/${binary_name}.exe"

    echo "正在使用 UPX 压缩..."
    local original_size=$(get_file_size "${binary_file}")

    if upx --best --lzma "${binary_file}" > /dev/null 2>&1; then
        local compressed_size=$(get_file_size "${binary_file}")
        local ratio=$(awk "BEGIN {printf \"%.1f\", (1 - ${compressed_size}/${original_size}) * 100}")
        echo -e "${GREEN}压缩完成:$(get_human_size ${compressed_size}) (压缩率:${ratio}%)${NC}"
    else
        echo -e "${YELLOW}UPX 压缩失败,使用原始二进制${NC}"
    fi
}

# 复制必要的资源文件
copy_resources() {
    local dist_dir="$1"

    echo "复制资源文件..."

    # 配置文件示例
    [ -f "config/config.yaml.example" ] && cp "config/config.yaml.example" "${dist_dir}/"
    [ -f "AI_DEPLOYMENT.md" ] && cp "AI_DEPLOYMENT.md" "${dist_dir}/"

    # 数据库迁移文件
    if [ -d "data/migrations" ]; then
        mkdir -p "${dist_dir}/data/migrations"
        rsync -av --exclude='._*' --exclude='.DS_Store' \
            data/migrations/ "${dist_dir}/data/migrations/" 2>/dev/null \
            || cp -r data/migrations/* "${dist_dir}/data/migrations/" 2>/dev/null || true
    fi

    # 权限相关文件
    [ -f "policy.csv" ] && cp "policy.csv" "${dist_dir}/"
    [ -f "rbac_model.conf" ] && cp "rbac_model.conf" "${dist_dir}/"
}

# 创建压缩包
create_tarball() {
    local os="$1"
    local arch="$2"
    local dist_dir="$3"

    local tarball_name="${PROJECT_NAME}_${os}_${arch}_${VERSION}.tar.gz"

    # 输出到 stderr(不污染返回值)
    echo "创建压缩包..." >&2
    cd "${BUILD_DIR}"

    # Windows 平台使用 zip 格式
    if [ "$os" = "windows" ]; then
        tarball_name="${PROJECT_NAME}_${os}_${arch}_${VERSION}.zip"
        if command -v zip &> /dev/null; then
            zip -rq "${tarball_name}" dist
        else
            echo -e "${YELLOW}警告:zip 命令不可用,使用 tar.gz 格式${NC}" >&2
            tar -czf "${tarball_name%.zip}.tar.gz" -C dist .
            tarball_name="${tarball_name%.zip}.tar.gz"
        fi
    else
        tar -czf "${tarball_name}" -C dist .
    fi

    cd ..
    # 只输出文件名(用于返回值)
    echo "TARBALL:${tarball_name}"
}

# 验证压缩包
verify_tarball() {
    local tarball="$1"

    echo "验证压缩包..."

    # Windows zip 文件验证
    if [[ "$tarball" == *.zip ]]; then
        if command -v unzip &> /dev/null; then
            unzip -l "${tarball}" | grep -q "\._" && {
                echo -e "${RED}错误:压缩包中包含 macOS 资源分叉文件!${NC}"
                return 1
            }
        fi
    else
        if tar -tzf "${tarball}" | grep -q "\._"; then
            echo -e "${RED}错误:压缩包中包含 macOS 资源分叉文件!${NC}"
            return 1
        fi
    fi

    local file_count
    if [[ "$tarball" == *.zip ]]; then
        file_count=$(unzip -l "${tarball}" | tail -1 | awk '{print $2}')
    else
        file_count=$(tar -tzf "${tarball}" | wc -l | tr -d ' ')
    fi
    echo "验证通过:共 ${file_count} 个文件"
    return 0
}

# 构建单个平台
build_platform() {
    local os="$1"
    local arch="$2"
    local use_compress="$3"

    local platform_dist="${DIST_DIR}/${os}_${arch}"
    mkdir -p "${platform_dist}"

    print_header "${os}" "${arch}"

    if ! build_binary "${os}" "${arch}" "${platform_dist}" "${PROJECT_NAME}"; then
        return 1
    fi

    compress_with_upx "${os}" "${platform_dist}" "${PROJECT_NAME}" "${use_compress}"
    copy_resources "${platform_dist}"

    echo "清理 macOS 资源分叉文件..."
    clean_macos_artifacts "${platform_dist}"

    local tarball
    tarball=$(create_tarball "${os}" "${arch}" "${platform_dist}")
    tarball="${tarball#TARBALL:}"

    # 压缩包已在 BUILD_DIR 目录下,无需移动
    # 清理平台临时目录
    rm -rf "${platform_dist}"

    if ! verify_tarball "${BUILD_DIR}/${tarball}"; then
        return 1
    fi

    echo -e "${GREEN}构建完成:${tarball}${NC}"
    echo ""
    return 0
}

# 清理旧版本
cleanup_old_versions() {
    local keep_count="${1:-$KEEP_VERSIONS}"

    if [ "$keep_count" -eq 0 ]; then
        echo "清理所有历史版本..."
        rm -f "${BUILD_DIR}"/${PROJECT_NAME}_*_*.tar.gz "${BUILD_DIR}"/${PROJECT_NAME}_*_*.zip 2>/dev/null || true
        echo "已删除所有历史版本"
        return
    fi

    echo "清理旧版本(保留最新${keep_count}个)..."

    for platform in "${PLATFORMS[@]}"; do
        local os="${platform%/*}"
        local arch="${platform#*/}"
        local pattern="${PROJECT_NAME}_${os}_${arch}_*"

        local all_versions=($(ls -t "${BUILD_DIR}"/${pattern}.tar.gz "${BUILD_DIR}"/${pattern}.zip 2>/dev/null || true))
        local count=${#all_versions[@]}

        if [ "${count}" -le "${keep_count}" ]; then
            continue
        fi

        local delete_count=$((count - keep_count))
        for ((i=keep_count; i<count; i++)); do
            echo "  删除:$(basename "${all_versions[$i]}")"
            rm -f "${all_versions[$i]}"
        done
    done
    echo "旧版本清理完成"
}

# ==================== 参数解析 ====================

BUILD_ALL=false
USE_COMPRESS=true
TARGET_OS=""
TARGET_ARCH=""
PLATFORM=""
CLEAN_ONLY=false
KEEP_COUNT=3

while [[ $# -gt 0 ]]; do
    case $1 in
        -o|--os)
            TARGET_OS="$2"
            shift 2
            ;;
        -a|--arch)
            TARGET_ARCH="$2"
            shift 2
            ;;
        -p|--platform)
            PLATFORM="$2"
            shift 2
            ;;
        --all|-all)
            BUILD_ALL=true
            shift
            ;;
        -n|--no-compress)
            USE_COMPRESS=false
            shift
            ;;
        -k|--keep)
            KEEP_COUNT="$2"
            shift 2
            ;;
        --clean-only)
            CLEAN_ONLY=true
            shift
            ;;
        -h|--help)
            usage
            ;;
        *)
            echo -e "${RED}未知选项:$1${NC}"
            usage
            ;;
    esac
done

# ==================== 主流程 ====================

main() {
    # 仅清理模式
    if [ "$CLEAN_ONLY" = true ]; then
        echo "=========================================="
        echo "清理历史版本"
        echo "=========================================="
        cleanup_old_versions "${KEEP_COUNT}"
        if [ "$KEEP_COUNT" -eq 0 ]; then
            echo "所有历史版本已清理完成"
        else
            echo "旧版本清理完成(保留最新${KEEP_COUNT}个)"
        fi
        ls -lh "${BUILD_DIR}"/*.tar.gz "${BUILD_DIR}"/*.zip 2>/dev/null || echo "无构建文件"
        return
    fi

    echo "=========================================="
    echo "Go 项目构建脚本"
    echo "版本:${VERSION}"
    echo "=========================================="

    # 准备构建目录
    echo "清理旧的构建文件..."
    rm -rf "${DIST_DIR}"
    mkdir -p "${DIST_DIR}"

    if [ "$BUILD_ALL" = true ]; then
        # 多平台构建
        echo -e "${BLUE}开始多平台构建...${NC}"

        for platform in "${PLATFORMS[@]}"; do
            local os="${platform%/*}"
            local arch="${platform#*/}"

            if ! build_platform "${os}" "${arch}" "${USE_COMPRESS}"; then
                echo -e "${YELLOW}跳过失败的平台:${platform}${NC}"
            fi
        done

        cleanup_old_versions "${KEEP_COUNT}"

        echo "=========================================="
        echo -e "${GREEN}所有平台构建完成!${NC}"
        echo "输出目录:${BUILD_DIR}/"
        ls -lh "${BUILD_DIR}"/*.tar.gz "${BUILD_DIR}"/*.zip 2>/dev/null || true
        echo "=========================================="

    elif [ -n "$PLATFORM" ]; then
        # 指定平台构建
        if [[ ! "$PLATFORM" =~ ^[a-z0-9]+/[a-z0-9]+$ ]]; then
            echo -e "${RED}错误:平台格式不正确,应为 os/arch 格式${NC}"
            exit 1
        fi

        local os="${PLATFORM%/*}"
        local arch="${PLATFORM#*/}"
        build_platform "${os}" "${arch}" "${USE_COMPRESS}"

        echo "=========================================="
        echo -e "${GREEN}构建完成!${NC}"
        ls -lh "${BUILD_DIR}"/*.tar.gz "${BUILD_DIR}"/*.zip 2>/dev/null || true
        echo "=========================================="

    else
        # 单平台构建(使用默认或命令行指定)
        local os="${TARGET_OS:-$DEFAULT_OS}"
        local arch="${TARGET_ARCH:-$DEFAULT_ARCH}"
        build_platform "${os}" "${arch}" "${USE_COMPRESS}"

        echo "=========================================="
        echo -e "${GREEN}构建完成!${NC}"
        ls -lh "${BUILD_DIR}"/*.tar.gz "${BUILD_DIR}"/*.zip 2>/dev/null || true
        echo "=========================================="
    fi
}

main "$@"


================================================
FILE: cmd/.gitignore
================================================
!.gitignore
go-layout

================================================
FILE: cmd/bootstrapx/bootstrap.go
================================================
package bootstrapx

import (
	"context"
	"fmt"
	"time"

	"github.com/spf13/cobra"
	"github.com/wannanbigpig/gin-layout/config"
	"github.com/wannanbigpig/gin-layout/data"
	taskcron "github.com/wannanbigpig/gin-layout/internal/cron"
	log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
	"github.com/wannanbigpig/gin-layout/internal/queue"
	"github.com/wannanbigpig/gin-layout/internal/service/sys_config"
	"github.com/wannanbigpig/gin-layout/internal/service/system"
	"github.com/wannanbigpig/gin-layout/internal/validator"
	"go.uber.org/zap"
)

const errorLoadingLocation = "Error loading location: %v"

// Requirements 描述命令运行前需要初始化的依赖。
type Requirements struct {
	Data                 bool
	Validator            bool
	Queue                bool
	AllowDegradedStartup bool
}

var (
	initializeDataFunc      = InitializeData
	initializeValidatorFunc = InitializeValidator
	initializeQueueFunc     = InitializeQueue
)

// InitializeConfig 初始化配置。
func InitializeConfig(configPath string) error {
	return config.InitConfig(configPath)
}

// InitializeTimezone 根据配置设置进程时区。
func InitializeTimezone() {
	cfg := config.GetConfig()
	if cfg.Timezone == nil {
		return
	}

	location, err := time.LoadLocation(*cfg.Timezone)
	if err != nil {
		if log.Logger != nil {
			log.Logger.Error(fmt.Sprintf(errorLoadingLocation, err), zap.Error(err))
		}
		fmt.Printf(errorLoadingLocation+"\n", err)
		return
	}
	time.Local = location
}

// InitializeLogger 初始化全局日志组件。
func InitializeLogger() error {
	return log.InitLogger()
}

// InitializeData 初始化数据源依赖。
func InitializeData() error {
	if err := data.InitData(); err != nil {
		return err
	}
	taskcron.RegisterHandler(taskcron.HandlerCronResetSystemData, func(ctx context.Context, payload map[string]any) error {
		_ = ctx
		_ = payload
		return system.ReinitializeSystemData()
	})
	if err := sys_config.NewSysConfigService().WarmupRuntimeConfigIfAvailable(); err != nil {
		return err
	}
	return taskcron.SyncBuiltinDefinitionsIfAvailable(config.GetConfig())
}

// InitializeValidator 初始化参数校验器。
func InitializeValidator() error {
	return validator.InitValidatorTrans("zh")
}

// WrapCommand 为命令注入统一的初始化逻辑,并保留原有 PreRunE/RunE。
func WrapCommand(cmd *cobra.Command, req Requirements) *cobra.Command {
	if cmd == nil {
		return cmd
	}

	originalPreRunE := cmd.PreRunE
	cmd.PreRunE = func(c *cobra.Command, args []string) error {
		if req.Data {
			if err := initializeDataFunc(); err != nil {
				if shouldAllowDegradedStartup(req) {
					logDependencyInitWarning(c, "data", err)
				} else {
					return err
				}
			}
		}
		if req.Validator {
			if err := initializeValidatorFunc(); err != nil {
				return err
			}
		}
		if req.Queue {
			if err := initializeQueueFunc(); err != nil {
				if shouldAllowDegradedStartup(req) {
					logDependencyInitWarning(c, "queue", err)
				} else {
					return err
				}
			}
		}
		if originalPreRunE != nil {
			return originalPreRunE(c, args)
		}
		return nil
	}

	return cmd
}

// InitializeQueue 初始化队列发布者。
func InitializeQueue() error {
	cfg := config.GetConfig()
	if !cfg.Queue.Enable {
		return nil
	}
	if err := queue.InitPublisher(cfg); err != nil {
		return err
	}
	return queue.InitInspector(cfg)
}

func shouldAllowDegradedStartup(req Requirements) bool {
	if !req.AllowDegradedStartup {
		return false
	}
	cfg := config.GetConfig()
	return cfg != nil && cfg.AllowDegradedStartup
}

func logDependencyInitWarning(cmd *cobra.Command, dependency string, err error) {
	if err == nil {
		return
	}
	commandPath := ""
	if cmd != nil {
		commandPath = cmd.CommandPath()
	}
	if log.Logger != nil {
		log.Logger.Warn("Dependency initialization failed, continue with degraded startup",
			zap.String("command", commandPath),
			zap.String("dependency", dependency),
			zap.Error(err))
		return
	}
	fmt.Printf("warning: dependency initialization failed, continue with degraded startup; command=%s dependency=%s err=%v\n", commandPath, dependency, err)
}


================================================
FILE: cmd/bootstrapx/bootstrap_test.go
================================================
package bootstrapx

import (
	"errors"
	"testing"

	"github.com/spf13/cobra"
	"go.uber.org/zap"

	"github.com/wannanbigpig/gin-layout/config"
	log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
)

func TestWrapCommandReturnsDataErrorWhenDegradedStartupDisabled(t *testing.T) {
	restoreInit := stubBootstrapInitializers(
		func() error { return errTestDataInit },
		func() error { return nil },
		func() error { return nil },
	)
	defer restoreInit()

	restoreConfig := setAllowDegradedStartup(t, false)
	defer restoreConfig()

	cmd := WrapCommand(&cobra.Command{Use: "service"}, Requirements{
		Data:                 true,
		AllowDegradedStartup: true,
	})

	err := cmd.PreRunE(cmd, nil)
	if !errors.Is(err, errTestDataInit) {
		t.Fatalf("expected data init error, got %v", err)
	}
}

func TestWrapCommandAllowsDataAndQueueErrorsWhenEnabled(t *testing.T) {
	dataCalled := false
	validatorCalled := false
	queueCalled := false
	originalCalled := false

	restoreInit := stubBootstrapInitializers(
		func() error {
			dataCalled = true
			return errTestDataInit
		},
		func() error {
			validatorCalled = true
			return nil
		},
		func() error {
			queueCalled = true
			return errTestQueueInit
		},
	)
	defer restoreInit()

	restoreConfig := setAllowDegradedStartup(t, true)
	defer restoreConfig()

	cmd := WrapCommand(&cobra.Command{
		Use: "service",
		PreRunE: func(cmd *cobra.Command, args []string) error {
			originalCalled = true
			return nil
		},
	}, Requirements{
		Data:                 true,
		Validator:            true,
		Queue:                true,
		AllowDegradedStartup: true,
	})

	if err := cmd.PreRunE(cmd, nil); err != nil {
		t.Fatalf("expected degraded startup to continue, got %v", err)
	}
	if !dataCalled || !validatorCalled || !queueCalled {
		t.Fatalf("expected all initializers to run, got data=%v validator=%v queue=%v", dataCalled, validatorCalled, queueCalled)
	}
	if !originalCalled {
		t.Fatal("expected original PreRunE to be called")
	}
}

func TestWrapCommandKeepsStrictModeForCommandsWithoutOptIn(t *testing.T) {
	restoreInit := stubBootstrapInitializers(
		func() error { return errTestDataInit },
		func() error { return nil },
		func() error { return nil },
	)
	defer restoreInit()

	restoreConfig := setAllowDegradedStartup(t, true)
	defer restoreConfig()

	cmd := WrapCommand(&cobra.Command{Use: "cron"}, Requirements{
		Data: true,
	})

	err := cmd.PreRunE(cmd, nil)
	if !errors.Is(err, errTestDataInit) {
		t.Fatalf("expected strict command to return data init error, got %v", err)
	}
}

func TestWrapCommandKeepsValidatorStrictEvenWhenDegradedStartupEnabled(t *testing.T) {
	restoreInit := stubBootstrapInitializers(
		func() error { return errTestDataInit },
		func() error { return errTestValidatorInit },
		func() error { return nil },
	)
	defer restoreInit()

	restoreConfig := setAllowDegradedStartup(t, true)
	defer restoreConfig()

	cmd := WrapCommand(&cobra.Command{Use: "service"}, Requirements{
		Data:                 true,
		Validator:            true,
		AllowDegradedStartup: true,
	})

	err := cmd.PreRunE(cmd, nil)
	if !errors.Is(err, errTestValidatorInit) {
		t.Fatalf("expected validator init error, got %v", err)
	}
}

var (
	errTestDataInit      = errors.New("data init failed")
	errTestQueueInit     = errors.New("queue init failed")
	errTestValidatorInit = errors.New("validator init failed")
)

func stubBootstrapInitializers(dataFn, validatorFn, queueFn func() error) func() {
	previousData := initializeDataFunc
	previousValidator := initializeValidatorFunc
	previousQueue := initializeQueueFunc
	initializeDataFunc = dataFn
	initializeValidatorFunc = validatorFn
	initializeQueueFunc = queueFn

	return func() {
		initializeDataFunc = previousData
		initializeValidatorFunc = previousValidator
		initializeQueueFunc = previousQueue
	}
}

func setAllowDegradedStartup(t *testing.T, enabled bool) func() {
	t.Helper()

	originalLogger := log.Logger
	restoreConfig := config.UpdateConfigForTesting(func(cfg *config.Conf) {
		cfg.AllowDegradedStartup = enabled
	})
	log.Logger = zap.NewNop()

	return func() {
		restoreConfig()
		log.Logger = originalLogger
	}
}


================================================
FILE: cmd/command/command.go
================================================
package command

import (
	"github.com/spf13/cobra"

	"github.com/wannanbigpig/gin-layout/cmd/bootstrapx"
	"github.com/wannanbigpig/gin-layout/internal/console/demo"
	initconsole "github.com/wannanbigpig/gin-layout/internal/console/init"
	migrateconsole "github.com/wannanbigpig/gin-layout/internal/console/migrate"
	"github.com/wannanbigpig/gin-layout/internal/console/system_init"
	taskconsole "github.com/wannanbigpig/gin-layout/internal/console/task"
)

var (
	Cmd = &cobra.Command{
		Use:     "command",
		Short:   "The control head runs the command",
		Example: "go-layout command demo",
	}
)

func init() {
	registerSubCommands()
}

// registerSubCommands 注册子命令
func registerSubCommands() {
	// 一次性运行脚本
	Cmd.AddCommand(demo.Cmd)
	Cmd.AddCommand(bootstrapx.WrapCommand(initconsole.ApiRouteCmd, bootstrapx.Requirements{Data: true}))               // 初始化API路由表: go-layout command api-route
	Cmd.AddCommand(bootstrapx.WrapCommand(initconsole.RebuildUserPermissionsCmd, bootstrapx.Requirements{Data: true})) // 重建用户最终 API 权限: go-layout command rebuild-user-permissions
	Cmd.AddCommand(bootstrapx.WrapCommand(system_init.InitSystemCmd, bootstrapx.Requirements{Data: true}))             // 初始化系统: go-layout command init-system
	Cmd.AddCommand(migrateconsole.Cmd)                                                                                 // 迁移管理: go-layout command migrate up / down / create / check
	Cmd.AddCommand(bootstrapx.WrapCommand(taskconsole.Cmd, bootstrapx.Requirements{Data: true}))                       // 任务扫描: go-layout command task scan-async
}


================================================
FILE: cmd/completion.go
================================================
package cmd

import (
	"fmt"
	"os"

	"github.com/spf13/cobra"
)

var completionNoDesc bool

var completionCmd = &cobra.Command{
	Use:   "completion [bash|zsh|fish|powershell]",
	Short: "Generate shell completion scripts",
	Long: `Generate shell completion scripts for go-layout.

Load examples:
  bash:       source <(go-layout completion bash)
  zsh:        source <(go-layout completion zsh)
  fish:       go-layout completion fish | source
  powershell: go-layout completion powershell | Out-String | Invoke-Expression`,
	Args: cobra.ExactArgs(1),
	RunE: func(cmd *cobra.Command, args []string) error {
		switch args[0] {
		case "bash":
			return rootCmd.GenBashCompletionV2(os.Stdout, !completionNoDesc)
		case "zsh":
			return rootCmd.GenZshCompletion(os.Stdout)
		case "fish":
			return rootCmd.GenFishCompletion(os.Stdout, !completionNoDesc)
		case "powershell":
			return rootCmd.GenPowerShellCompletionWithDesc(os.Stdout)
		default:
			return fmt.Errorf("unsupported shell type %q", args[0])
		}
	},
}

func init() {
	completionCmd.Flags().BoolVar(&completionNoDesc, "no-descriptions", false, "Disable completion descriptions where supported")
}


================================================
FILE: cmd/cron/cron.go
================================================
package cron

import (
	"context"
	"fmt"
	"os"
	"os/signal"
	"syscall"

	"github.com/robfig/cron/v3"
	"github.com/spf13/cobra"
	"go.uber.org/zap"

	"github.com/wannanbigpig/gin-layout/cmd/bootstrapx"
	"github.com/wannanbigpig/gin-layout/data"
	log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
)

var (
	Cmd = bootstrapx.WrapCommand(&cobra.Command{
		Use:     "cron",
		Short:   "Starting a scheduled task",
		Example: "go-layout cron",
		RunE: func(cmd *cobra.Command, args []string) error {
			return Start()
		},
	}, bootstrapx.Requirements{Data: true})
)

// Start 启动定时任务服务
func Start() error {
	crontab, err := newScheduler()
	if err != nil {
		return err
	}
	if err := registerTasks(crontab); err != nil {
		log.Logger.Error("定时任务启动失败", zap.Error(err))
		return fmt.Errorf("定时任务启动失败: %w", err)
	}

	// 启动定时器
	crontab.Start()

	log.Logger.Info("Cron service started successfully")

	// 优雅关闭
	waitForShutdown()
	stopCtx := crontab.Stop()
	<-stopCtx.Done()
	if err := data.Shutdown(); err != nil {
		return fmt.Errorf("shutdown data resources failed: %w", err)
	}
	log.Logger.Info("Cron service stopped gracefully")
	return nil
}

func newScheduler() (*cron.Cron, error) {
	logger := &cronLogger{}
	scheduler := cron.New(
		cron.WithSeconds(),
		cron.WithChain(cron.Recover(logger)),
	)
	if scheduler == nil {
		return nil, fmt.Errorf("创建定时任务调度器失败")
	}
	return scheduler, nil
}

// waitForShutdown 等待关闭信号,实现优雅关闭
func waitForShutdown() {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go handleSignals(cancel)
	<-ctx.Done()
}

// handleSignals 处理系统信号(SIGINT、SIGTERM)
func handleSignals(cancel context.CancelFunc) {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	sig := <-sigChan
	log.Logger.Warn("Received shutdown signal", zap.String("signal", sig.String()))
	cancel()
}

// cronLogger 定时任务日志记录器
type cronLogger struct{}

// Info 记录信息日志
func (cl *cronLogger) Info(msg string, keysAndValues ...interface{}) {
	if len(keysAndValues) > 0 {
		log.Logger.Info(fmt.Sprintf(msg, keysAndValues...))
	} else {
		log.Logger.Info(msg)
	}
}

// Error 记录错误日志
func (cl *cronLogger) Error(err error, msg string, keysAndValues ...interface{}) {
	errorMsg := err.Error()
	if len(keysAndValues) > 0 {
		errorMsg += " " + fmt.Sprintf(msg, keysAndValues...)
	} else if msg != "" {
		errorMsg += " " + msg
	}
	log.Logger.Error(errorMsg, zap.Error(err))
}


================================================
FILE: cmd/cron/schedule.go
================================================
package cron

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/robfig/cron/v3"
	"go.uber.org/zap"

	"github.com/wannanbigpig/gin-layout/internal/model"
	log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
	"github.com/wannanbigpig/gin-layout/internal/service/taskcenter"
)

// Scheduler 提供链式的任务注册方式。
type Scheduler struct {
	logger *cronLogger
	tasks  []*scheduledTask
}

type scheduledTask struct {
	name      string
	spec      string
	specErr   error
	run       func() error
	skipIfRun bool
}

// TaskBuilder 用于链式声明任务调度规则。
type TaskBuilder struct {
	task *scheduledTask
}

var cronSpecParser = cron.NewParser(
	cron.Second |
		cron.Minute |
		cron.Hour |
		cron.Dom |
		cron.Month |
		cron.Dow |
		cron.Descriptor,
)

func registerTasks(crontab *cron.Cron) error {
	logger := &cronLogger{}
	schedule := NewSchedule(logger)
	defineSchedule(schedule)
	return schedule.Register(crontab)
}

// NewSchedule 创建任务声明器。
func NewSchedule(logger *cronLogger) *Scheduler {
	return &Scheduler{
		logger: logger,
		tasks:  make([]*scheduledTask, 0, 4),
	}
}

// Call 注册一个函数任务,默认启用防重入。
func (s *Scheduler) Call(name string, fn func()) *TaskBuilder {
	task := &scheduledTask{
		name:      name,
		run:       func() error { fn(); return nil },
		skipIfRun: true,
	}
	s.tasks = append(s.tasks, task)
	return &TaskBuilder{task: task}
}

// CallE 注册一个返回 error 的函数任务。
func (s *Scheduler) CallE(name string, fn func() error) *TaskBuilder {
	task := &scheduledTask{
		name:      name,
		run:       fn,
		skipIfRun: true,
	}
	s.tasks = append(s.tasks, task)
	return &TaskBuilder{task: task}
}

// Cron 直接使用 cron 表达式。
func (b *TaskBuilder) Cron(spec string) *TaskBuilder {
	b.task.spec = spec
	return b
}

// EveryFiveSeconds 每 5 秒执行一次,适合本地测试任务。
func (b *TaskBuilder) EveryFiveSeconds() *TaskBuilder {
	return b.Cron("0/5 * * * * *")
}

// DailyAt 每天固定时间执行,支持 HH:MM 或 HH:MM:SS。
func (b *TaskBuilder) DailyAt(value string) *TaskBuilder {
	spec, err := dailyAtSpec(value)
	if err != nil {
		b.task.specErr = err
		return b
	}
	b.task.spec = spec
	return b
}

// WithoutOverlapping 表示任务执行期间跳过重入。
func (b *TaskBuilder) WithoutOverlapping() *TaskBuilder {
	b.task.skipIfRun = true
	return b
}

// AllowOverlap 允许任务重入。
func (b *TaskBuilder) AllowOverlap() *TaskBuilder {
	b.task.skipIfRun = false
	return b
}

// Register 把声明过的任务统一注册到 cron 实例中。
func (s *Scheduler) Register(crontab *cron.Cron) error {
	for _, task := range s.tasks {
		if err := s.registerTask(crontab, task); err != nil {
			return err
		}
	}
	return nil
}

func (s *Scheduler) registerTask(crontab *cron.Cron, task *scheduledTask) error {
	if task.specErr != nil {
		return fmt.Errorf("定时任务 %s 调度表达式无效: %w", task.name, task.specErr)
	}
	if task.spec == "" {
		return fmt.Errorf("定时任务 %s 缺少调度表达式", task.name)
	}

	chain := cron.NewChain(cron.Recover(s.logger))
	if task.skipIfRun {
		chain = cron.NewChain(
			cron.SkipIfStillRunning(s.logger),
			cron.Recover(s.logger),
		)
	}

	if _, err := crontab.AddJob(task.spec, chain.Then(s.recordedJob(task))); err != nil {
		return fmt.Errorf("添加定时任务失败 [%s] (schedule: %s): %w", task.name, task.spec, err)
	}

	log.Logger.Info("定时任务添加成功",
		zap.String("name", task.name),
		zap.String("schedule", task.spec),
		zap.Bool("skip_if_still_running", task.skipIfRun),
	)
	return nil
}

func (s *Scheduler) recordedJob(task *scheduledTask) cron.Job {
	return cron.FuncJob(func() {
		if task == nil || task.run == nil {
			return
		}

		ctx := context.Background()
		taskCode := "cron:" + task.name
		run, recordErr := taskcenter.NewRunRecorder().Start(ctx, taskcenter.RunStart{
			TaskCode: taskCode,
			Kind:     model.TaskKindCron,
			Source:   model.TaskSourceCron,
			SourceID: task.name,
			CronSpec: task.spec,
		})
		if recordErr != nil {
			log.Logger.Warn("记录定时任务开始失败",
				zap.String("name", task.name),
				zap.Error(recordErr))
		}

		err := task.run()
		if err != nil {
			log.Logger.Error("定时任务执行失败",
				zap.String("name", task.name),
				zap.Error(err))
		}

		if run == nil {
			return
		}
		finishInput := taskcenter.RunFinish{Error: err}
		nextRunAt, nextRunErr := calculateNextRunAt(task.spec, time.Now())
		if nextRunErr != nil {
			log.Logger.Warn("计算定时任务下次执行时间失败",
				zap.String("name", task.name),
				zap.String("schedule", task.spec),
				zap.Error(nextRunErr))
		}
		finishInput.NextRunAt = nextRunAt
		if recordErr := taskcenter.NewRunRecorder().Finish(ctx, run, finishInput); recordErr != nil {
			log.Logger.Warn("记录定时任务结束失败",
				zap.String("name", task.name),
				zap.Error(recordErr))
		}
	})
}

func calculateNextRunAt(spec string, base time.Time) (*time.Time, error) {
	spec = strings.TrimSpace(spec)
	if spec == "" {
		return nil, nil
	}

	schedule, err := cronSpecParser.Parse(spec)
	if err != nil {
		return nil, err
	}

	nextRunAt := schedule.Next(base)
	if nextRunAt.IsZero() {
		return nil, nil
	}
	return &nextRunAt, nil
}

func dailyAtSpec(value string) (string, error) {
	parts := strings.Split(value, ":")
	switch len(parts) {
	case 2:
		hour, err := parseTimePart("hour", parts[0], 0, 23)
		if err != nil {
			return "", err
		}
		minute, err := parseTimePart("minute", parts[1], 0, 59)
		if err != nil {
			return "", err
		}
		return fmt.Sprintf("0 %d %d * * *", minute, hour), nil
	case 3:
		hour, err := parseTimePart("hour", parts[0], 0, 23)
		if err != nil {
			return "", err
		}
		minute, err := parseTimePart("minute", parts[1], 0, 59)
		if err != nil {
			return "", err
		}
		second, err := parseTimePart("second", parts[2], 0, 59)
		if err != nil {
			return "", err
		}
		return fmt.Sprintf("%d %d %d * * *", second, minute, hour), nil
	default:
		return "", fmt.Errorf("invalid daily time format: %s", value)
	}
}

func parseTimePart(name, raw string, min, max int) (int, error) {
	raw = strings.TrimSpace(raw)
	value, err := strconv.Atoi(raw)
	if err != nil {
		return 0, fmt.Errorf("invalid %s value %q", name, raw)
	}
	if value < min || value > max {
		return 0, fmt.Errorf("%s value out of range [%d,%d]: %d", name, min, max, value)
	}
	return value, nil
}


================================================
FILE: cmd/cron/schedule_test.go
================================================
package cron

import (
	"strings"
	"testing"

	"github.com/robfig/cron/v3"
)

func TestDailyAtSpecWithHHMM(t *testing.T) {
	spec, err := dailyAtSpec("02:30")
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if spec != "0 30 2 * * *" {
		t.Fatalf("unexpected cron spec: %s", spec)
	}
}

func TestDailyAtSpecWithHHMMSS(t *testing.T) {
	spec, err := dailyAtSpec("03:04:05")
	if err != nil {
		t.Fatalf("expected no error, got %v", err)
	}
	if spec != "5 4 3 * * *" {
		t.Fatalf("unexpected cron spec: %s", spec)
	}
}

func TestDailyAtSpecRejectsInvalidInput(t *testing.T) {
	_, err := dailyAtSpec("invalid")
	if err == nil {
		t.Fatal("expected invalid input to return error")
	}
}

func TestDailyAtSpecRejectsOutOfRange(t *testing.T) {
	_, err := dailyAtSpec("25:00")
	if err == nil {
		t.Fatal("expected out of range input to return error")
	}
}

func TestSchedulerRegisterReturnsDailyAtError(t *testing.T) {
	schedule := NewSchedule(&cronLogger{})
	schedule.Call("bad-task", func() {}).DailyAt("bad-time")

	c := cron.New(cron.WithSeconds())
	err := schedule.Register(c)
	if err == nil {
		t.Fatal("expected register to return error for invalid daily time")
	}
	if !strings.Contains(err.Error(), "调度表达式无效") {
		t.Fatalf("unexpected error: %v", err)
	}
}


================================================
FILE: cmd/cron/task_record_test.go
================================================
package cron

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/wannanbigpig/gin-layout/internal/model"
	"github.com/wannanbigpig/gin-layout/internal/service/taskcenter"
)

func TestRecordedJobRecordsCallEFailure(t *testing.T) {
	fake := &fakeCronRunRecorder{}
	restore := taskcenter.SetRecorderForTesting(fake)
	defer restore()

	expectedErr := errors.New("cron failed")
	schedule := NewSchedule(&cronLogger{})
	builder := schedule.CallE("demo", func() error {
		return expectedErr
	}).EveryFiveSeconds()

	startAt := time.Now()
	schedule.recordedJob(builder.task).Run()

	if len(fake.starts) != 1 {
		t.Fatalf("expected 1 start call, got %d", len(fake.starts))
	}
	start := fake.starts[0]
	if start.TaskCode != "cron:demo" || start.Kind != model.TaskKindCron || start.Source != model.TaskSourceCron {
		t.Fatalf("unexpected start input: %#v", start)
	}
	if start.CronSpec != "0/5 * * * * *" {
		t.Fatalf("unexpected cron spec: %s", start.CronSpec)
	}
	if len(fake.finishes) != 1 {
		t.Fatalf("expected 1 finish call, got %d", len(fake.finishes))
	}
	if !errors.Is(fake.finishes[0].Error, expectedErr) {
		t.Fatalf("unexpected finish error: %v", fake.finishes[0].Error)
	}
	if fake.finishes[0].NextRunAt == nil {
		t.Fatal("expected finish next run at to be set")
	}
	if !fake.finishes[0].NextRunAt.After(startAt) {
		t.Fatalf("expected next run at after start time, got %s", fake.finishes[0].NextRunAt.Format(time.DateTime))
	}
}

func TestCalculateNextRunAtInvalidSpec(t *testing.T) {
	nextRunAt, err := calculateNextRunAt("bad spec", time.Now())
	if err == nil {
		t.Fatal("expected parse error for invalid cron spec")
	}
	if nextRunAt != nil {
		t.Fatalf("expected nil next run at on parse error, got %v", nextRunAt)
	}
}

type fakeCronRunRecorder struct {
	starts   []taskcenter.RunStart
	finishes []taskcenter.RunFinish
}

func (f *fakeCronRunRecorder) Enqueue(ctx context.Context, input taskcenter.RunStart) (*model.TaskRun, error) {
	_ = ctx
	f.starts = append(f.starts, input)
	return &model.TaskRun{BaseModel: model.BaseModel{ID: uint(len(f.starts))}, TaskCode: input.TaskCode, Source: input.Source}, nil
}

func (f *fakeCronRunRecorder) Start(ctx context.Context, input taskcenter.RunStart) (*model.TaskRun, error) {
	_ = ctx
	f.starts = append(f.starts, input)
	return &model.TaskRun{BaseModel: model.BaseModel{ID: uint(len(f.starts))}, TaskCode: input.TaskCode, Source: input.Source}, nil
}

func (f *fakeCronRunRecorder) Finish(ctx context.Context, run *model.TaskRun, input taskcenter.RunFinish) error {
	_ = ctx
	_ = run
	f.finishes = append(f.finishes, input)
	return nil
}


================================================
FILE: cmd/cron/tasks.go
================================================
package cron

import (
	"context"
	"strings"

	"go.uber.org/zap"

	"github.com/wannanbigpig/gin-layout/config"
	taskcron "github.com/wannanbigpig/gin-layout/internal/cron"
	"github.com/wannanbigpig/gin-layout/internal/model"
	log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
)

func defineSchedule(schedule *Scheduler) {
	cfg := config.GetConfig()
	for _, definition := range taskcron.BuiltinTaskDefinitions(cfg) {
		if definition.Kind != model.TaskKindCron || definition.Status != model.TaskStatusEnabled {
			continue
		}
		if strings.TrimSpace(definition.Code) == "" || strings.TrimSpace(definition.CronSpec) == "" {
			continue
		}

		taskName := taskNameFromCode(definition.Code)
		handler := definition.Handler
		if definition.IsHighRisk == model.TaskHighRisk {
			log.Logger.Warn("高风险定时任务已启用",
				zap.String("name", taskName),
				zap.String("schedule", definition.CronSpec),
			)
		}
		schedule.CallE(taskName, func() error {
			return taskcron.ExecuteHandler(context.Background(), handler, nil)
		}).
			Cron(definition.CronSpec).
			WithoutOverlapping()
	}
}

func taskNameFromCode(code string) string {
	code = strings.TrimSpace(code)
	if strings.HasPrefix(code, "cron:") {
		trimmed := strings.TrimPrefix(code, "cron:")
		if trimmed != "" {
			return trimmed
		}
	}
	return code
}


================================================
FILE: cmd/cron/tasks_test.go
================================================
package cron

import (
	"testing"

	"github.com/wannanbigpig/gin-layout/config"
)

func TestDefineScheduleSkipsResetTaskByDefault(t *testing.T) {
	restoreConfig := config.UpdateConfigForTesting(func(cfg *config.Conf) {
		cfg.EnableResetSystemCron = false
	})
	defer restoreConfig()

	schedule := NewSchedule(&cronLogger{})
	defineSchedule(schedule)

	if hasScheduledTask(schedule, "reset-system-data") {
		t.Fatal("expected reset-system-data task to be skipped by default")
	}
	if hasScheduledTask(schedule, "demo") {
		t.Fatal("expected demo task to be skipped by default")
	}
}

func TestDefineScheduleRegistersResetTaskWhenEnabled(t *testing.T) {
	restoreConfig := config.UpdateConfigForTesting(func(cfg *config.Conf) {
		cfg.EnableResetSystemCron = true
	})
	defer restoreConfig()

	schedule := NewSchedule(&cronLogger{})
	defineSchedule(schedule)

	if !hasScheduledTask(schedule, "reset-system-data") {
		t.Fatal("expected reset-system-data task to be registered when enabled")
	}
}

func hasScheduledTask(schedule *Scheduler, name string) bool {
	for _, task := range schedule.tasks {
		if task.name == name {
			return true
		}
	}
	return false
}


================================================
FILE: cmd/root.go
================================================
package cmd

import (
	"fmt"
	"os"
	"strings"

	"github.com/spf13/cobra"
	"go.uber.org/zap"

	"github.com/wannanbigpig/gin-layout/cmd/bootstrapx"
	"github.com/wannanbigpig/gin-layout/cmd/command"
	"github.com/wannanbigpig/gin-layout/cmd/cron"
	"github.com/wannanbigpig/gin-layout/cmd/service"
	"github.com/wannanbigpig/gin-layout/cmd/version"
	"github.com/wannanbigpig/gin-layout/cmd/worker"
	log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
	"github.com/wannanbigpig/gin-layout/internal/runtime"
)

const (
	welcomeMessage = "Welcome to go-layout. Use -h to see more commands"
)

var (
	rootCmd = &cobra.Command{
		Use:           "go-layout",
		Short:         "go-layout",
		SilenceUsage:  true,
		SilenceErrors: true,
		Long: `Gin framework is used as the core of this project to build a scaffold,
based on the project can be quickly completed business development, out of the box 📦`,
		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
			if shouldSkipBootstrap(cmd) {
				return nil
			}
			if err := bootstrapx.InitializeConfig(configPath); err != nil {
				return err
			}
			bootstrapx.InitializeTimezone()
			if err := bootstrapx.InitializeLogger(); err != nil {
				return err
			}
			return nil
		},
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Printf("%s\n", welcomeMessage)
		},
	}
	configPath string
)

func init() {
	runtime.RegisterConfigReloadHandlers()
	registerFlags()
	registerCommands()
}

// registerFlags 注册命令行标志
func registerFlags() {
	rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "The absolute path of the configuration file")
}

func shouldSkipBootstrap(cmd *cobra.Command) bool {
	if cmd == nil {
		return false
	}
	if cmd.Name() == "help" {
		return true
	}
	commandPath := cmd.CommandPath()
	switch commandPath {
	case "go-layout", "go-layout version", "go-layout help":
		return true
	default:
		return strings.HasPrefix(commandPath, "go-layout completion") ||
			strings.HasPrefix(commandPath, "go-layout __complete")
	}
}

// registerCommands 注册子命令
func registerCommands() {
	rootCmd.AddCommand(version.Cmd) // 查看版本: go-layout version
	rootCmd.AddCommand(completionCmd)
	rootCmd.AddCommand(service.Cmd) // 启动服务: go-layout service
	rootCmd.AddCommand(command.Cmd) // 运行命令: go-layout command demo / go-layout command init api-route
	rootCmd.AddCommand(cron.Cmd)    // 启动计划任务: go-layout cron
	rootCmd.AddCommand(worker.Cmd)  // 启动异步任务 worker: go-layout worker
}

// Execute 执行命令
func Execute() {
	if err := rootCmd.Execute(); err != nil {
		if log.Logger != nil {
			log.Logger.Error("Command execution failed", zap.Error(err))
		}
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}
}


================================================
FILE: cmd/service/service.go
================================================
package service

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/spf13/cobra"
	"go.uber.org/zap"

	"github.com/wannanbigpig/gin-layout/cmd/bootstrapx"
	"github.com/wannanbigpig/gin-layout/data"
	log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
	_ "github.com/wannanbigpig/gin-layout/internal/queue/asynqx"
	"github.com/wannanbigpig/gin-layout/internal/routers"
)

const (
	defaultHost            = "0.0.0.0"
	defaultPort            = 9001
	gracefulShutdownTimout = 10 * time.Second
)

var (
	Cmd = bootstrapx.WrapCommand(&cobra.Command{
		Use:     "service",
		Short:   "Start API service",
		Example: "go-layout service -c config.yml",
		RunE: func(cmd *cobra.Command, args []string) error {
			return run()
		},
	}, bootstrapx.Requirements{Data: true, Validator: true, Queue: true, AllowDegradedStartup: true})
	host string
	port int
)

func init() {
	registerFlags()
}

// registerFlags 注册命令行标志
func registerFlags() {
	Cmd.Flags().StringVarP(&host, "host", "H", defaultHost, "监听服务器地址")
	Cmd.Flags().IntVarP(&port, "port", "P", defaultPort, "监听服务器端口")
}

// run 运行服务器
func run() error {
	engine, err := routers.SetRouters()
	if err != nil {
		return fmt.Errorf("build router failed: %w", err)
	}
	address := fmt.Sprintf("%s:%d", host, port)
	server := &http.Server{
		Addr:    address,
		Handler: engine,
	}

	errChan := make(chan error, 1)
	go func() {
		log.Logger.Info("API service starting", zap.String("address", address))
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			errChan <- err
		}
		close(errChan)
	}()

	return waitForShutdown(server, errChan)
}

func waitForShutdown(server *http.Server, errChan <-chan error) error {
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	defer signal.Stop(sigChan)

	select {
	case err, ok := <-errChan:
		if ok && err != nil {
			return err
		}
		return nil
	case sig := <-sigChan:
		log.Logger.Warn("Received API shutdown signal", zap.String("signal", sig.String()))
	}

	ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimout)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		return fmt.Errorf("shutdown http server failed: %w", err)
	}
	if err := data.Shutdown(); err != nil {
		return fmt.Errorf("shutdown data resources failed: %w", err)
	}

	log.Logger.Info("API service stopped gracefully")
	return nil
}


================================================
FILE: cmd/version/version.go
================================================
package version

import (
	"fmt"

	"github.com/spf13/cobra"

	"github.com/wannanbigpig/gin-layout/internal/global"
)

var (
	// Cmd 版本信息命令
	Cmd = &cobra.Command{
		Use:     "version",
		Short:   "Get version info",
		Example: "go-layout version",
		RunE: func(cmd *cobra.Command, args []string) error {
			fmt.Println(global.Version)
			return nil
		},
	}
)


================================================
FILE: cmd/worker/worker.go
================================================
package worker

import (
	"fmt"

	"github.com/spf13/cobra"
	"go.uber.org/zap"

	"github.com/wannanbigpig/gin-layout/cmd/bootstrapx"
	"github.com/wannanbigpig/gin-layout/config"
	"github.com/wannanbigpig/gin-layout/data"
	taskcron "github.com/wannanbigpig/gin-layout/internal/cron"
	"github.com/wannanbigpig/gin-layout/internal/jobs"
	log "github.com/wannanbigpig/gin-layout/internal/pkg/logger"
	"github.com/wannanbigpig/gin-layout/internal/queue/asynqx"
)

var Cmd = bootstrapx.WrapCommand(&cobra.Command{
	Use:     "worker",
	Short:   "Start async worker",
	Example: "go-layout worker -c config.yml",
	RunE: func(cmd *cobra.Command, args []string) error {
		return run()
	},
}, bootstrapx.Requirements{Data: true})

func run() error {
	cfg := config.GetConfig()
	if cfg == nil {
		return fmt.Errorf("queue config is not initialized")
	}
	if !cfg.Queue.Enable {
		return fmt.Errorf("queue.enable is false")
	}
	if cfg.Queue.UseDefaultRedis {
		if !cfg.Redis.Enable {
			return fmt.Errorf("queue uses default redis, but redis.enable is false")
		}
		if err := data.GetRedisInitError(); err != nil {
			return fmt.Errorf("redis initialization failed: %w", err)
		}
		if data.RedisClient() == nil {
			return fmt.Errorf("redis client is unavailable")
		}
	}

	registry := jobs.NewRegistry()
	cronFallbackHandlers := 0
	if cfg.Queue.ConsumeCronFallback {
		cronFallbackHandlers = taskcron.RegisterQueueFallbackHandlers(registry, cfg)
	}

	server, mux, err := asynqx.NewServer(cfg, registry)
	if err != nil {
		return err
	}

	log.Logger.Info("Async worker starting",
		zap.Int("concurrency", cfg.Queue.Concurrency),
		zap.Bool("strict_priority", cfg.Queue.StrictPriority),
		zap.Bool("consume_cron_fallback", cfg.Queue.ConsumeCronFallback),
		zap.Int("cron_fallback_handlers", cronFallbackHandlers),
		zap.Any("queues", cfg.Queue.Queues))

	if err := server.Run(mux); err != nil {
		return err
	}
	if err := data.Shutdown(); err != nil {
		return fmt.Errorf("shutdown data resources failed: %w", err)
	}

	log.Logger.Info("Async worker stopped gracefully")
	return nil
}


================================================
FILE: config/.gitignore
================================================
*.yaml
*.ini

================================================
FILE: config/autoload/app.go
================================================
package autoload

import (
	"github.com/wannanbigpig/gin-layout/pkg/utils"
)

// AppConfig 定义应用运行时基础配置。
type AppConfig struct {
	// AppEnv 应用环境标识,如:local(本地)、dev(开发)、prod(生产)
	AppEnv string `mapstructure:"app_env"`
	// Debug 是否开启调试模式,true 时输出详细调试信息
	Debug bool `mapstructure:"debug"`
	// Language 国际化语言,如:zh_CN(中文)、en_US(英文)
	Language string `mapstructure:"language"`
	// WatchConfig 是否开启配置热更新,true 时配置变更自动重载
	WatchConfig bool `mapstructure:"watch_config"`
	// BasePath 应用基础路径,用于拼接文件存储路径。
	// 未在配置文件显式设置时,会在配置加载阶段优先回填为当前工作目录。
	BasePath string `mapstructure:"base_path"`
	// BaseURL 文件访问的基础 URL(如:https://example.com),用于拼接文件访问地址
	BaseURL string `mapstructure:"base_url"`
	// Timezone 时区设置,nil 时使用系统默认时区
	Timezone *string `mapstructure:"timezone"`
	// TrustedProxies 受信任代理列表,仅这些代理转发的 X-Forwarded-For/X-Real-IP 会被信任
	// 生产环境应配置为负载均衡或反向代理的 IP/网段
	TrustedProxies []string `mapstructure:"trusted_proxies"`
	// CorsOrigins CORS 允许的源列表,如:["http://localhost:3000", "https://example.com"]
	// 使用 ["*"] 表示允许所有源(生产环境慎用)
	CorsOrigins []string `mapstructure:"cors_origins"`
	// CorsMethods 允许的 HTTP 方法,如:["GET", "POST", "PUT", "DELETE"]
	// 使用 ["*"] 表示允许全部已支持方法,空数组使用默认值
	CorsMethods []string `mapstructure:"cors_methods"`
	// CorsHeaders 允许的请求头,如:["Content-Type", "Authorization"]
	// 使用 ["*"] 表示允许全部请求头
	CorsHeaders []string `mapstructure:"cors_headers"`
	// CorsExposeHeaders 暴露的响应头,如:["Content-Length", "X-Request-Id"]
	// 使用 ["*"] 表示暴露全部响应头
	CorsExposeHeaders []string `mapstructure:"cors_expose_headers"`
	// CorsMaxAge 预检请求(OPTIONS)缓存时间(秒),默认 43200(12 小时)
	CorsMaxAge int `mapstructure:"cors_max_age"`
	// CorsCredentials 是否允许携带凭证(cookies、Authorization 头等),默认 false
	CorsCredentials bool `mapstructure:"cors_credentials"`
	// AllowDegradedStartup 是否允许 service 在依赖初始化失败时降级启动。
	// true 时仅 HTTP 服务会继续启动,由 readiness 与路由守卫体现未就绪状态。
	AllowDegradedStartup bool `mapstructure:"allow_degraded_startup"`
	// EnableResetSystemCron 是否启用高风险的系统重建定时任务。
	// 默认 false,避免在非预期环境触发系统数据重建。
	EnableResetSystemCron bool `mapstructure:"enable_reset_system_cron"`
}

var App = AppConfig{
	AppEnv:                "local", // 默认本地环境
	Debug:                 true,    // 默认开启调试模式
	Language:              "zh_CN", // 默认中文
	WatchConfig:           false,   // 默认关闭配置热更新
	BasePath:              getDefaultPath(),
	BaseURL:               "",                    // 默认空,需要配置
	Timezone:              nil,                   // 默认使用系统时区
	TrustedProxies:        []string{"127.0.0.1"}, // 默认只信任本地
	CorsOrigins:           []string{},            // 默认空数组,不放行跨域来源;使用 ["*"] 表示允许所有源
	CorsMethods:           []string{},            // 默认空数组,使用默认方法列表;使用 ["*"] 表示允许全部已支持方法
	CorsHeaders:           []string{},            // 默认空数组,按请求头自动放行预检头;使用 ["*"] 表示允许全部请求头
	CorsExposeHeaders:     []string{},            // 默认空数组,默认暴露全部响应头;使用 ["*"] 明确表示暴露全部响应头
	CorsMaxAge:            43200,                 // 默认 12 小时(43200 秒)
	CorsCredentials:       false,                 // 默认不允许携带凭证
	AllowDegradedStartup:  false,                 // 默认关闭降级启动,依赖初始化失败时直接退出
	EnableResetSystemCron: false,                 // 默认关闭高风险系统重建定时任务
}

func getDefaultPath() (path string) {
	// 初始化时优先按 GO_ENV 处理:
	// - development: 当前工作目录
	// - 其他环境: 可执行文件所在目录
	// 配置加载阶段会按 app.base_path 是否显式配置进一步修正。
	path, err := utils.GetDefaultPath()
	if err != nil || path == "" {
		path = "."
	}
	return
}


================================================
FILE: config/autoload/jwt.go
================================================
package autoload

import "time"

// JwtConfig 定义 JWT 相关配置。
type JwtConfig struct {
	// TTL Token 有效期(秒),默认 7200 秒(2 小时)
	TTL time.Duration `mapstructure:"ttl"`
	// RefreshTTL Token 刷新阈值(秒)。
	// 默认 0:不主动刷新 Token
	// 大于 0 时:当 Token 剩余有效期小于该值时,自动刷新 Token 并在 Response Header 中返回新 Token
	// 推荐设置为 TTL/2,例如 TTL=7200 时,RefreshTTL=3600
	RefreshTTL time.Duration `mapstructure:"refresh_ttl"`
	// SecretKey JWT 签名密钥,用于生成和验证 Token
	// 启动时会校验非空;生产环境还会拒绝弱占位值和长度不足的密钥
	// 建议使用随机密钥,例如:openssl rand -hex 32
	SecretKey string `mapstructure:"secret_key"`
}

var Jwt = JwtConfig{
	TTL:        7200, // Token 有效期 2 小时
	RefreshTTL: 0,    // 0 表示不主动刷新 Token
	SecretKey:  "",   // 默认空,启动时必须由配置提供有效密钥
}


================================================
FILE: config/autoload/logger.go
================================================
package autoload

// DivisionTime 定义按时间切割日志时的参数。
type DivisionTime struct {
	// MaxAge 日志文件保留的最大天数,过期会被删除
	MaxAge int `mapstructure:"max_age"`
	// RotationTime 多长时间切割一次日志文件,单位小时(24 表示每天切割)
	RotationTime int `mapstructure:"rotation_time"`
}

// DivisionSize 定义按大小切割日志时的参数。
type DivisionSize struct {
	// MaxSize 日志文件的最大大小(以 MB 为单位),超过该值会触发切割
	MaxSize int `mapstructure:"max_size"`
	// MaxBackups 保留的旧日志文件最大个数,超过会被删除
	MaxBackups int `mapstructure:"max_backups"`
	// MaxAge 旧日志文件保留的最大天数,过期会被删除
	MaxAge int `mapstructure:"max_age"`
	// Compress 是否压缩/归档旧日志文件(gzip 格式)
	Compress bool `mapstructure:"compress"`
}

// LoggerConfig 定义日志输出与切割策略。
type LoggerConfig struct {
	// Output 日志输出方式:file(输出到文件)、stderr(输出到标准错误)
	Output string `mapstructure:"output"`
	// DefaultDivision 默认切割方式:time(按时间)、size(按大小)
	DefaultDivision string `mapstructure:"default_division"`
	// Filename 日志文件名
	Filename string `mapstructure:"file_name"`
	// DivisionTime 按时间切割的参数配置
	DivisionTime DivisionTime `mapstructure:"division_time"`
	// DivisionSize 按大小切割的参数配置
	DivisionSize DivisionSize `mapstructure:"division_size"`
}

var Logger = LoggerConfig{
	Output:          "file", // 默认输出到文件
	DefaultDivision: "time", // 默认按时间切割
	Filename:        "gin-layout.sys.log",
	DivisionTime: DivisionTime{
		MaxAge:       15,  // 日志保留 15 天
		RotationTime: 24,  // 每 24 小时切割一次
	},
	DivisionSize: DivisionSize{
		MaxSize:    20,    // 日志文件最大 20MB
		MaxBackups: 15,    // 最多保留 15 个备份
		MaxAge:     15,    // 日志保留 15 天
		Compress:   false, // 默认不压缩
	},
}


================================================
FILE: config/autoload/mysql.go
================================================
package autoload

import "time"

// MysqlConfig 定义 MySQL 连接与连接池配置。
type MysqlConfig struct {
	// Enable 是否启用 MySQL 连接
	Enable bool `mapstructure:"enable"`
	// Host 数据库服务器地址
	Host string `mapstructure:"host"`
	// Username 数据库用户名
	Username string `mapstructure:"username"`
	// Password 数据库密码
	Password string `mapstructure:"password"`
	// Port 数据库端口
	Port uint16 `mapstructure:"port"`
	// Database 数据库名称
	Database string `mapstructure:"database"`
	// Charset 字符集,推荐 utf8mb4
	Charset string `mapstructure:"charset"`
	// TablePrefix 表名前缀,用于区分不同应用的表
	TablePrefix string `mapstructure:"table_prefix"`
	// MaxIdleConns 最大空闲连接数
	MaxIdleConns int `mapstructure:"max_idle_conns"`
	// MaxOpenConns 最大打开连接数(并发连接数上限)
	MaxOpenConns int `mapstructure:"max_open_conns"`
	// MaxLifetime 连接最大存活时间,超时会被复用前重新创建
	MaxLifetime time.Duration `mapstructure:"max_lifetime"`
	// LogLevel GORM 日志级别:1=silent, 2=error, 3=warn, 4=info
	LogLevel int `mapstructure:"log_level"`
	// PrintSql 是否打印 SQL 到控制台,调试时使用
	PrintSql bool `mapstructure:"print_sql"`
}

// Mysql 数据库默认配置。
var Mysql = MysqlConfig{
	Enable:       false, // 默认关闭,需要时开启
	Host:         "127.0.0.1",
	Username:     "root",
	Password:     "root1234",
	Port:         3306,
	Database:     "test",
	Charset:      "utf8mb4",
	TablePrefix:  "",
	MaxIdleConns: 10,
	MaxOpenConns: 100,
	MaxLifetime:  time.Hour, // 连接存活 1 小时
	LogLevel:     4,         // info 级别
	PrintSql:     false,     // 默认不打印 SQL
}


================================================
FILE: config/autoload/queue.go
================================================
package autoload

// QueueRedisConfig 队列使用的 Redis 连接配置。
type QueueRedisConfig struct {
	// Host Redis 服务器地址
	Host string `mapstructure:"host" yaml:"host"`
	// Port Redis 服务器端口
	Port string `mapstructure:"port" yaml:"port"`
	// Password Redis 密码,空字符串表示无密码
	Password string `mapstructure:"password" yaml:"password"`
	// Database 数据库编号
	Database int `mapstructure:"database" yaml:"database"`
}

// QueueConfig 异步任务队列配置。
type QueueConfig struct {
	// Enable 是否启用异步队列。false 时同步执行任务(如审计日志直接写库)
	Enable bool `mapstructure:"enable" yaml:"enable"`
	// UseDefaultRedis 是否复用全局 redis 配置。
	// true: 使用 redis.* 作为队列连接(默认)
	// false: 使用 queue.redis.* 作为队列独立连接
	UseDefaultRedis bool `mapstructure:"use_default_redis" yaml:"use_default_redis"`
	// Redis 队列独立 Redis 配置,仅当 UseDefaultRedis=false 时生效
	Redis QueueRedisConfig `mapstructure:"redis" yaml:"redis"`
	// Namespace 队列命名空间前缀,用于隔离不同应用的队列
	Namespace string `mapstructure:"namespace" yaml:"namespace"`
	// Concurrency Worker Server 的最大并发协程数(全局上限)
	// 建议值:开发环境 2-4,小流量生产 8-16,中等流量 16-32
	// 注意:并发过高会增加数据库压力,审计日志类任务建议 8-16
	Concurrency int `mapstructure:"concurrency" yaml:"concurrency"`
	// StrictPriority 是否严格优先级模式。
	// true: 必须处理完高优先级队列的所有任务后,才处理低优先级队列
	// false: 按权重比例调度,高优先级队列的任务被调度的概率更大(推荐)
	StrictPriority bool `mapstructure:"strict_priority" yaml:"strict_priority"`
	// ConsumeCronFallback 是否允许 worker 消费历史误入 Asynq 的非高风险 cron 任务。
	// 仅用于清理旧队列残留,不影响 cron 调度开关。
	ConsumeCronFallback bool `mapstructure:"consume_cron_fallback" yaml:"consume_cron_fallback"`
	// Queues 各队列的权重配置,key 为队列名,value 为权重值
	// 新增队列必须在此配置,否则 Worker 不会消费该队列的任务!
	// 权重决定任务被调度的概率,不是分配的协程数量!
	// 所有队列共享 Concurrency 个协程,权重越高越容易被优先调度
	// 调度概率 = 该队列权重 / 所有队列权重之和
	// 示例(总权重=4+2+2+1=9):
	//   critical: 权重 4 → 4/9≈44% 概率被调度(支付回调、短信发送)
	//   default:  权重 2 → 2/9≈22% 概率被调度(普通异步任务)
	//   audit:    权重 2 → 2/9≈22% 概率被调度(请求日志、登录日志)
	//   low:      权重 1 → 1/9≈11% 概率被调度(批量通知、数据导出)
	Queues map[string]int `mapstructure:"queues" yaml:"queues"`
	// AuditMaxRetry 审计日志队列的最大重试次数
	AuditMaxRetry int `mapstructure:"audit_max_retry" yaml:"audit_max_retry"`
	// AuditTimeoutSeconds 审计日志任务的超时时间(秒)
	AuditTimeoutSeconds int `mapstructure:"audit_timeout_seconds" yaml:"audit_timeout_seconds"`
}

// Queue 队列默认配置。
var Queue = QueueConfig{
	Enable:          false, // 默认关闭队列,同步执行
	UseDefaultRedis: true,  // 默认复用全局 redis 配置
	Redis: QueueRedisConfig{
		Host:     "127.0.0.1",
		Port:     "6379",
		Password: "",
		Database: 0,
	},
	Namespace:      "go_layout",
	Concurrency:    8,     // 整个 worker 最多同时处理 8 个任务(全局上限)
	StrictPriority: false, // 按权重比例调度,非严格优先级
	// 默认不让 worker 消费 cron 类型任务;需要清理历史残留时再显式开启。
	ConsumeCronFallback: false,
	Queues: map[string]int{
		// 权重值表示调度概率,不是协程数量!
		// 所有队列共享 8 个协程,权重越高越容易被优先调度
		// 总权重 = 4+2+2+1 = 9
		// critical 被选中的概率 ≈ 4/9 ≈ 44%
		"critical": 4, // 权重 4,约 44% 概率被调度(如支付回调)
		"default":  2, // 权重 2,约 22% 概率被调度(普通任务)
		"audit":    2, // 权重 2,约 22% 概率被调度(审计日志)
		"low":      1, // 权重 1,约 11% 概率被调度(批量通知)
	},
	AuditMaxRetry:       3,  // 审计日志失败最多重试 3 次
	AuditTimeoutSeconds: 10, // 审计日志任务超时 10 秒
}


================================================
FILE: config/autoload/redis.go
================================================
package autoload

import "time"

// RedisConfig 定义 Redis 连接配置。
type RedisConfig struct {
	// Enable 是否启用 Redis 连接
	Enable bool `mapstructure:"enable"`
	// Host Redis 服务器地址
	Host string `mapstructure:"host"`
	// Port Redis 服务器端口
	Port string `mapstructure:"port"`
	// Password Redis 密码,空字符串表示无密码
	Password string `mapstructure:"password"`
	// Database 数据库编号,默认 0
	Database int `mapstructure:"database"`
	// PoolSize 连接池大小(最大连接数)
	PoolSize int `mapstructure:"pool_size"`
	// MinIdleConns 最小空闲连接数
	MinIdleConns int `mapstructure:"min_idle_conns"`
	// ConnMaxIdleTime 连接最大空闲时间,超时会被回收
	ConnMaxIdle time.Duration `mapstructure:"conn_max_idle_time"`
	// ConnMaxLifetime 连接最大存活时间,超时会被重新创建
	ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
	// ReadTimeout 读取超时时间
	ReadTimeout time.Duration `mapstructure:"read_timeout"`
	// WriteTimeout 写入超时时间
	WriteTimeout time.Duration `mapstructure:"write_timeout"`
}

// Redis 默认配置。
var Redis = RedisConfig{
	Enable:          false, // 默认关闭,需要时开启
	Host:            "127.0.0.1",
	Password:        "",
	Port:            "6379",
	Database:        0,
	PoolSize:        10,
	MinIdleConns:    5,
	ConnMaxIdle:     5 * time.Minute,  // 空闲 5 分钟回收
	ConnMaxLifetime: 30 * time.Minute, // 连接存活 30 分钟
	ReadTimeout:     3 * time.Second,  // 读取超时 3 秒
	WriteTimeout:    3 * time.Second,  // 写入超时 3 秒
}


================================================
FILE: config/config.go
================================================
package config

import (
	"sort"
	"sync"
	"sync/atomic"

	"github.com/spf13/viper"
	"github.com/wannanbigpig/gin-layout/config/autoload"
)

// Conf 配置项主结构体
type Conf struct {
	autoload.AppConfig `mapstructure:"app"`
	Mysql              autoload.MysqlConfig  `mapstructure:"mysql"`
	Redis              autoload.RedisConfig  `mapstructure:"redis"`
	Logger             autoload.LoggerConfig `mapstructure:"logger"`
	Jwt                autoload.JwtConfig    `mapstructure:"jwt"`
	Queue              autoload.QueueConfig  `mapstructure:"queue"`
}

var (
	Config = &Conf{
		AppConfig: cloneAppConfig(autoload.App),
		Mysql:     autoload.Mysql,
		Redis:     autoload.Redis,
		Logger:    autoload.Logger,
		Jwt:       autoload.Jwt,
		Queue:     cloneQueueConfig(autoload.Queue),
	}
	once        sync.Once
	initErr     error
	V           *viper.Viper
	configValue atomic.Value

	reloadHandlersMu sync.RWMutex
	reloadHandlers   []ConfigReloadHandler
)

// ConfigReloadHandler 在配置热更新时被调用。
type ConfigReloadHandler struct {
	Name     string
	Priority int
	Handle   func(oldConfig, newConfig *Conf, diff ConfigDiff) error
}

// ConfigDiff 描述配置变更摘要。
type ConfigDiff struct {
	LoggerChanged         bool
	MysqlChanged          bool
	RedisChanged          bool
	JWTChanged            bool
	JWTSecretChanged      bool
	BaseURLChanged        bool
	CORSChanged           bool
	TrustedProxiesChanged bool
	LightAppChanged       bool
	RestartRequiredFields []string
	ChangedFields         []string
}

// GetConfig 返回当前生效的配置快照。
func GetConfig() *Conf {
	if cfg, ok := configValue.Load().(*Conf); ok && cfg != nil {
		return cfg
	}
	return Config
}

// RegisterConfigReloadHandler 注册配置热更新回调。
func RegisterConfigReloadHandler(handler ConfigReloadHandler) {
	if handler.Name == "" {
		return
	}

	reloadHandlersMu.Lock()
	defer reloadHandlersMu.Unlock()

	for i := range reloadHandlers {
		if reloadHandlers[i].Name == handler.Name {
			reloadHandlers[i] = handler
			sortConfigReloadHandlersLocked()
			return
		}
	}

	reloadHandlers = append(reloadHandlers, handler)
	sortConfigReloadHandlersLocked()
}

func sortConfigReloadHandlersLocked() {
	sort.SliceStable(reloadHandlers, func(i, j int) bool {
		if reloadHandlers[i].Priority == reloadHandlers[j].Priority {
			return reloadHandlers[i].Name < reloadHandlers[j].Name
		}
		return reloadHandlers[i].Priority < reloadHandlers[j].Priority
	})
}


================================================
FILE: config/config.yaml.example
================================================
# 该文件为配置示例文件,请复制该文件改名为 config.yaml, 不要直接修改该文件,修改无意义
app:
  app_env: local
  debug: true
  language: zh_CN
#  allow_degraded_startup: false   # service 启动时依赖初始化失败是否继续启动;仅建议在需要“先起服务再排障”场景开启
#  enable_reset_system_cron: false # 高风险:是否启用 reset-system-data 定时任务;默认关闭,避免误重建系统数据
#  watch_config: false
#  base_path: ""
#  base_url: "https://example.com"  # 文件访问的基础URL,用于拼接本地文件访问地址
#  trusted_proxies:                # 受信任代理列表,仅这些代理转发的 X-Forwarded-For / X-Real-IP 会被信任
#    - "127.0.0.1"
#    # 生产环境可按实际代理网段配置,例如:
#    # - "10.0.0.0/8"
#    # - "172.16.0.0/12"
#    # - "192.168.0.0/16"
#  # CORS 跨域配置
#  cors_origins:                    # CORS允许的源列表,使用 ["*"] 表示允许所有源
#    - "http://localhost:3000"
#    - "http://localhost:8080"
#    - "https://example.com"
#    # 支持通配符匹配,例如:
#    - "https://*.wannanbigpig.com"  # 匹配所有 wannanbigpig.com 的子域名(如 https://x-l-admin.wannanbigpig.com)
#    # 或者明确指定:
#    - "https://x-l-admin.wannanbigpig.com"
#  cors_methods:                    # 允许的HTTP方法,使用 ["*"] 表示允许全部已支持方法,空数组使用默认值
#    - "GET"
#    - "POST"
#    - "PUT"
#    - "PATCH"
#    - "DELETE"
#    - "HEAD"
#    - "OPTIONS"
#  cors_headers:                    # 允许的请求头,使用 ["*"] 表示允许所有请求头
#    - "Content-Type"
#    - "Authorization"
#    - "X-Requested-With"
#  cors_expose_headers:             # 暴露的响应头,使用 ["*"] 表示暴露所有响应头
#    - "Content-Length"
#    - "X-Request-Id"
#  cors_max_age: 43200              # 预检请求缓存时间(秒),默认 43200(12小时)
#  cors_credentials: false          # 是否允许携带凭证(cookies等),默认 false
jwt:
  ttl: 7200
  refresh_ttl: 3600
  secret_key: <YOUR_SECRET_KEY>  # 请替换为随机密钥;启动时会校验非空,生产环境还会拒绝弱占位值和短密钥
mysql:
  enable: false
  host: 127.0.0.1
  port: 3306
  database: test
  username: root
  password: root1234
  charset: utf8mb4
  table_prefix: ""
  max_idle_conns: 10
  max_open_conns: 100
  max_lifetime: 3600s
redis:
  enable: false
  host: 127.0.0.1
  port: 6379
  password:
  database: 0
queue:
  enable: false
  use_default_redis: true  # true=复用 redis.*;false=使用 queue.redis.* 独立连接
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    database: 0
  namespace: go_layout
  concurrency: 8
  strict_priority: false
  consume_cron_fallback: false # true=worker 兜底消费历史误入 Asynq 的非高风险 cron 任务
  queues:
    critical: 4
    default: 2
    audit: 2
    low: 1
  audit_max_retry: 3
  audit_timeout_seconds: 10
logger:
  # 日志输出默认为文件,stderr 可选
  output: file
  default_division: time
  file_name: gin-layout.sys.log
  division_time:
    max_age: 15
    rotation_time: 24
  division_size:
    max_size: 20
    max_backups: 15
    max_age: 15
    compress: false


================================================
FILE: config/config_clone.go
================================================
package config

import (
	"fmt"
	"io"
	"os"

	"github.com/wannanbigpig/gin-layout/config/autoload"
)

func setActiveConfig(cfg *Conf) {
	Config = cfg
	configValue.Store(cfg)
}

func cloneDefaultConfig() *Conf {
	return &Conf{
		AppConfig: cloneAppConfig(autoload.App),
		Mysql:     autoload.Mysql,
		Redis:     autoload.Redis,
		Logger:    autoload.Logger,
		Jwt:       autoload.Jwt,
		Queue:     cloneQueueConfig(autoload.Queue),
	}
}

func cloneAppConfig(src autoload.AppConfig) autoload.AppConfig {
	cloned := src
	cloned.TrustedProxies = cloneStringSlice(src.TrustedProxies)
	cloned.CorsOrigins = cloneStringSlice(src.CorsOrigins)
	cloned.CorsMethods = cloneStringSlice(src.CorsMethods)
	cloned.CorsHeaders = cloneStringSlice(src.CorsHeaders)
	cloned.CorsExposeHeaders = cloneStringSlice(src.CorsExposeHeaders)
	if src.Timezone != nil {
		tz := *src.Timezone
		cloned.Timezone = &tz
	}
	return cloned
}

func cloneQueueConfig(src autoload.QueueConfig) autoload.QueueConfig {
	cloned := src
	if src.Queues != nil {
		cloned.Queues = make(map[string]int, len(src.Queues))
		for key, value := range src.Queues {
			cloned.Queues[key] = value
		}
	}
	return cloned
}

func cloneStringSlice(src []string) []string {
	if src == nil {
		return nil
	}
	return append([]string(nil), src...)
}

// copyConf 复制配置示例文件
func copyConf(exampleConfig, config string) error {
	fileInfo, err := os.Stat(config)
	if err == nil {
		if !fileInfo.IsDir() {
			return nil
		}
		return fmt.Errorf("配置文件目录存在同名的文件夹,无法创建配置文件")
	}
	if !os.IsNotExist(err) {
		return fmt.Errorf("初始化失败: %w", err)
	}

	source, err := os.Open(exampleConfig)
	if err != nil {
		return fmt.Errorf("创建配置文件失败,配置示例文件不存在: %w", err)
	}
	defer func(source *os.File) {
		_ = source.Close()
	}(source)

	dst, err := os.Create(config)
	if err != nil {
		return fmt.Errorf("生成配置文件失败: %w", err)
	}
	defer func(dst *os.File) {
		_ = dst.Close()
	}(dst)

	if _, err := io.Copy(dst, source); err != nil {
		return fmt.Errorf("写入配置文件失败: %w", err)
	}
	return nil
}


================================================
FILE: config/config_load.go
================================================
package config

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"

	"github.com/wannanbigpig/gin-layout/pkg/utils"
)

// InitConfig 初始化配置系统并加载首个生效快照。
func InitConfig(configPath string) error {
	once.Do(func() {
		var loaded *Conf
		loaded, initErr = load(configPath)
		if initErr != nil {
			return
		}
		initErr = validateJWTSecretKey(loaded)
		if initErr != nil {
			return
		}
		setActiveConfig(loaded)
	})
	return initErr
}

func checkJwtSecretKey() error {
	return validateJWTSecretKey(GetConfig())
}

func validateJWTSecretKey(cfg *Conf) error {
	if cfg == nil {
		return fmt.Errorf("config is nil")
	}
	secret := strings.TrimSpace(cfg.Jwt.SecretKey)
	if secret == "" {
		return fmt.Errorf("jwt.secret_key is empty, please set a non-empty secret key")
	}

	isProd := strings.EqualFold(cfg.AppEnv, "prod") || strings.EqualFold(cfg.AppEnv, "production")
	if !isProd {
		return nil
	}

	weakSecrets := map[string]struct{}{
		"<your_secret_key>":    {},
		"your-secret-key-here": {},
		"default-secret-key":   {},
		"change-me":            {},
		"changeme":             {},
		"secret":               {},
		"123456":               {},
	}
	if _, ok := weakSecrets[strings.ToLower(secret)]; ok {
		return fmt.Errorf("jwt.secret_key uses a weak placeholder value in production")
	}
	if len(secret) < 16 {
		return fmt.Errorf("jwt.secret_key is too short in production, require at least 16 characters")
	}

	return nil
}

func load(configPath string) (*Conf, error) {
	filePath, err := resolveConfigPath(configPath)
	if err != nil {
		return nil, err
	}

	V = viper.New()
	V.SetConfigFile(filePath)
	if err := V.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			return nil, fmt.Errorf("未找到配置: %w", err)
		}
		return nil, fmt.Errorf("读取配置出错: %w", err)
	}

	loaded := cloneDefaultConfig()
	if err := V.Unmarshal(loaded); err != nil {
		return nil, fmt.Errorf("映射配置出错: %w", err)
	}

	resolveEnvVars(loaded)
	ensureBasePathDefault(loaded, V.IsSet("app.base_path"))

	ensureCorsDefaults(loaded)
	registerConfigWatcherIfNeeded(loaded)
	return loaded, nil
}

func resolveConfigPath(configPath string) (string, error) {
	if configPath != "" {
		return configPath, nil
	}

	exampleConfig, targetConfig, err := resolveDefaultConfigFiles()
	if err != nil {
		return "", err
	}
	if err := copyConf(exampleConfig, targetConfig); err != nil {
		return "", err
	}
	return targetConfig, nil
}

func resolveDefaultConfigFiles() (string, string, error) {
	if os.Getenv("GO_ENV") == "development" {
		return resolveDevelopmentConfigFiles()
	}

	runDirectory, err := utils.GetCurrentPath()
	if err != nil {
		return "", "", fmt.Errorf("获取执行文件目录失败: %w", err)
	}
	return filepath.Join(runDirectory, "config.yaml.example"), filepath.Join(runDirectory, "config.yaml"), nil
}

func resolveDevelopmentConfigFiles() (string, string, error) {
	workDir, err := os.Getwd()
	if err != nil {
		return "", "", fmt.Errorf("获取工作目录失败: %w", err)
	}

	exampleConfig := filepath.Join(workDir, "config", "config.yaml.example")
	if !fileExists(exampleConfig) {
		exampleConfig = filepath.Join(workDir, "config.yaml.example")
	}
	return exampleConfig, filepath.Join(workDir, "config.yaml"), nil
}

func fileExists(path string) bool {
	if strings.TrimSpace(path) == "" {
		return false
	}
	info, err := os.Stat(path)
	if err != nil {
		return false
	}
	return !info.IsDir()
}

func ensureBasePathDefault(cfg *Conf, basePathConfigured bool) {
	if cfg == nil {
		return
	}
	if basePathConfigured && strings.TrimSpace(cfg.BasePath) != "" {
		return
	}

	if os.Getenv("GO_ENV") == "development" {
		workDir, err := os.Getwd()
		if err == nil && strings.TrimSpace(workDir) != "" {
			cfg.BasePath = workDir
			return
		}
	}

	runDir, err := utils.GetCurrentPath()
	if err == nil && strings.TrimSpace(runDir) != "" {
		cfg.BasePath = runDir
		return
	}

	workDir, err := os.Getwd()
	if err == nil && strings.TrimSpace(workDir) != "" {
		cfg.BasePath = workDir
		return
	}
	cfg.BasePath = strings.TrimSpace(cfg.BasePath)
	if cfg.BasePath == "" {
		cfg.BasePath = "."
	}
}

func registerConfigWatcherIfNeeded(cfg *Conf) {
	if !cfg.WatchConfig {
		return
	}

	V.WatchConfig()
	V.OnConfigChange(func(in fsnotify.Event) {
		initErr = reloadConfigFromWatcher()
	})
}

func ensureCorsDefaults(cfg *Conf) {
	if cfg.CorsOrigins == nil {
		cfg.CorsOrigins = []string{}
	}
	if cfg.CorsMethods == nil {
		cfg.CorsMethods = []string{}
	}
	if cfg.CorsHeaders == nil {
		cfg.CorsHeaders = []string{}
	}
	if cfg.CorsExposeHeaders == nil {
		cfg.CorsExposeHeaders = []string{}
	}
	if cfg.TrustedProxies == nil {
		cfg.TrustedProxies = []string{"127.0.0.1"}
	}
	if cfg.CorsMaxAge == 0 {
		cfg.CorsMaxAge = 43200
	}
}

func reloadConfigFromWatcher() error {
	if err := V.ReadInConfig(); err != nil {
		return fmt.Errorf("重新读取配置出错: %w", err)
	}

	next := cloneDefaultConfig()
	if err := V.Unmarshal(next); err != nil {
		return fmt.Errorf("重新映射配置出错: %w", err)
	}
	resolveEnvVars(next)
	ensureBasePathDefault(next, V.IsSet("app.base_path"))
	ensureCorsDefaults(next)
	if err := validateJWTSecretKey(next); err != nil {
		return fmt.Errorf("JWT 配置校验失败: %w", err)
	}

	current := GetConfig()
	diff := BuildConfigDiff(current, next)
	applied := BuildAppliedConfig(current, next, diff)

	reloadHandlersMu.RLock()
	handlers := append([]ConfigReloadHandler(nil), reloadHandlers...)
	reloadHandlersMu.RUnlock()

	for _, handler := range handlers {
		if handler.Handle == nil {
			continue
		}
		if err := handler.Handle(current, applied, diff); err != nil {
			return fmt.Errorf("配置热更新失败[%s]: %w", handler.Name, err)
		}
	}

	setActiveConfig(applied)
	return nil
}

func resolveEnvVars(cfg *Conf) {
	cfg.Mysql.Username = resolveEnvVar(cfg.Mysql.Username)
	cfg.Mysql.Password = resolveEnvVar(cfg.Mysql.Password)
	cfg.Mysql.Host = resolveEnvVar(cfg.Mysql.Host)
	cfg.Redis.Password = resolveEnvVar(cfg.Redis.Password)
	cfg.Redis.Host = resolveEnvVar(cfg.Redis.Host)
	cfg.Queue.Redis.Password = resolveEnvVar(cfg.Queue.Redis.Password)
	cfg.Queue.Redis.Host = resolveEnvVar(cfg.Queue.Redis.Host)
	cfg.Jwt.SecretKey = resolveEnvVar(cfg.Jwt.SecretKey)
}

func resolveEnvVar(val string) string {
	if !strings.HasPrefix(val, "${") || !strings.HasSuffix(val, "}") {
		return val
	}
	envKey := val[2 : len(val)-1]
	if envVal := os.Getenv(envKey); envVal != "" {
		return envVal
	}
	return val
}


================================================
FILE: config/config_load_test.go
================================================
package config

import (
	"os"
	"path/filepath"
	"testing"
)

func assertSamePath(t *testing.T, expected string, actual string) {
	t.Helper()
	expectedResolved, err := filepath.EvalSymlinks(expected)
	if err != nil {
		expectedResolved = filepath.Clean(expected)
	}
	actualResolved, err := filepath.EvalSymlinks(actual)
	if err != nil {
		actualResolved = filepath.Clean(actual)
	}
	if expectedResolved != actualResolved {
		t.Fatalf("expected %s, got %s", expectedResolved, actualResolved)
	}
}

func TestResolveConfigPathPrefersWorkingDirectoryConfig(t *testing.T) {
	t.Setenv("GO_ENV", "development")

	workDir := t.TempDir()
	configPath := filepath.Join(workDir, "config.yaml")
	if err := os.WriteFile(configPath, []byte("app:\n  port: 8080\n"), 0o644); err != nil {
		t.Fatalf("write config file failed: %v", err)
	}

	originWD, err := os.Getwd()
	if err != nil {
		t.Fatalf("getwd failed: %v", err)
	}
	if err := os.Chdir(workDir); err != nil {
		t.Fatalf("chdir failed: %v", err)
	}
	t.Cleanup(func() {
		_ = os.Chdir(originWD)
	})

	resolved, err := resolveConfigPath("")
	if err != nil {
		t.Fatalf("resolveConfigPath failed: %v", err)
	}
	assertSamePath(t, configPath, resolved)
}

func TestResolveDefaultConfigFilesUsesWorkingDirectoryExample(t *testing.T) {
	t.Setenv("GO_ENV", "development")

	workDir := t.TempDir()
	configDir := filepath.Join(workDir, "config")
	if err := os.MkdirAll(configDir, 0o755); err != nil {
		t.Fatalf("mkdir config dir failed: %v", err)
	}

	examplePath := filepath.Join(configDir, "config.yaml.example")
	if err := os.WriteFile(examplePath, []byte("app:\n  port: 8080\n"), 0o644); err != nil {
		t.Fatalf("write example file failed: %v", err)
	}

	originWD, err := os.Getwd()
	if err != nil {
		t.Fatalf("getwd failed: %v", err)
	}
	if err := os.Chdir(workDir); err != nil {
		t.Fatalf("chdir failed: %v", err)
	}
	t.Cleanup(func() {
		_ = os.Chdir(originWD)
	})

	gotExample, gotTarget, err := resolveDefaultConfigFiles()
	if err != nil {
		t.Fatalf("resolveDefaultConfigFiles failed: %v", err)
	}
	assertSamePath(t, examplePath, gotExample)
	expectedTarget := filepath.Join(filepath.Dir(filepath.Dir(gotExample)), "config.yaml")
	if gotTarget != expectedTarget {
		t.Fatalf("expected target %s, got %s", expectedTarget, gotTarget)
	}
}

func TestEnsureBasePathDefaultUsesWorkingDirectoryWhenNotConfigured(t *testing.T) {
	t.Setenv("GO_ENV", "development")

	workDir := t.TempDir()
	originWD, err := os.Getwd()
	if err != nil {
		t.Fatalf("getwd failed: %v", err)
	}
	if err := os.Chdir(workDir); err != nil {
		t.Fatalf("chdir failed: %v", err)
	}
	t.Cleanup(func() {
		_ = os.Chdir(originWD)
	})

	cfg := cloneDefaultConfig()
	cfg.BasePath = "/tmp/legacy-base-path"
	ensureBasePathDefault(cfg, false)
	assertSamePath(t, workDir, cfg.BasePath)
}

func TestEnsureBasePathDefaultKeepsExplicitConfiguredValue(t *testing.T) {
	cfg := cloneDefaultConfig()
	cfg.BasePath = "/tmp/custom-base-path"
	ensureBasePathDefault(cfg, true)
	if cfg.BasePath != "/tmp/custom-base-path" {
		t.Fatalf("expected explicit base_path to be kept, got %s", cfg.BasePath)
	}
}

func TestValidateJWTSecretKeyRejectsNilConfig(t *testing.T) {
	if err := validateJWTSecretKey(nil); err == nil {
		t.Fatal("expected nil config to return error")
	}
}

func TestCheckJwtSecretKeyRejectsEmptySecret(t *testing.T) {
	original := GetConfig()
	testCfg := cloneDefaultConfig()
	testCfg.Jwt.SecretKey = ""
	setActiveConfig(testCfg)
	t.Cleanup(func() { setActiveConfig(original) })

	if err := checkJwtSecretKey(); err == nil {
		t.Fatal("expected empty jwt secret key to return error")
	}
}

func TestCheckJwtSecretKeyRejectsWeakProdSecret(t *testing.T) {
	original := GetConfig()
	testCfg := cloneDefaultConfig()
	testCfg.AppEnv = "prod"
	testCfg.Jwt.SecretKey = "default-secret-key"
	setActiveConfig(testCfg)
	t.Cleanup(func() { setActiveConfig(original) })

	if err := checkJwtSecretKey(); err == nil {
		t.Fatal("expected weak prod jwt secret key to return error")
	}
}

func TestCheckJwtSecretKeyAllowsLocalWeakSecret(t *testing.T) {
	original := GetConfig()
	testCfg := cloneDefaultConfig()
	testCfg.AppEnv = "local"
	testCfg.Jwt.SecretKey = "default-secret-key"
	setActiveConfig(testCfg)
	t.Cleanup(func() { setActiveConfig(original) })

	if err := checkJwtSecretKey(); err != nil {
		t.Fatalf("expected local weak secret key to pass, got %v", err)
	}
}


================================================
FILE: config/config_test.go
================================================
package config

import "testing"

func resetConfigReloadHandlersForTest(t *testing.T) {
	t.Helper()
	reloadHandlersMu.Lock()
	defer reloadHandlersMu.Unlock()
	reloadHandlers = nil
}

func TestRegisterConfigReloadHandlerDeduplicatesByName(t *testing.T) {
	resetConfigReloadHandlersForTest(t)

	RegisterConfigReloadHandler(ConfigReloadHandler{Name: "data", Priority: 20})
	RegisterConfigReloadHandler(ConfigReloadHandler{Name: "data", Priority: 10})

	reloadHandlersMu.RLock()
	defer reloadHandlersMu.RUnlock()

	if len(reloadHandlers) != 1 {
		t.Fatalf("expected 1 handler, got %d", len(reloadHandlers))
	}
	if reloadHandlers[0].Priority != 10 {
		t.Fatalf("expected overwritten priority 10, got %d", reloadHandlers[0].Priority)
	}
}

func TestRegisterConfigReloadHandlerKeepsStablePriorityOrder(t *testing.T) {
	resetConfigReloadHandlersForTest(t)

	RegisterConfigReloadHandler(ConfigReloadHandler{Name: "warnings", Priority: 100})
	RegisterConfigReloadHandler(ConfigReloadHandler{Name: "logger", Priority: 10})
	RegisterConfigReloadHandler(ConfigReloadHandler{Name: "data", Priority: 20})

	reloadHandlersMu.RLock()
	defer reloadHandlersMu.RUnlock()

	if len(reloadHandlers) != 3 {
		t.Fatalf("expected 3 handlers, got %d", len(reloadHandlers))
	}

	got := []string{reloadHandlers[0].Name, reloadHandlers[1].Name, reloadHandlers[2].Name}
	want := []string{"logger", "data", "warnings"}
	for i := range want {
		if got[i] != want[i] {
			t.Fatalf("unexpected order: got %v want %v", got, want)
		}
	}
}

func TestRegisterConfigReloadHandlerIgnoresEmptyName(t *testing.T) {
	resetConfigReloadHandlersForTest(t)

	RegisterConfigReloadHandler(ConfigReloadHandler{Priority: 1})

	reloadHandlersMu.RLock()
	defer reloadHandlersMu.RUnlock()

	if len(reloadHandlers) != 0 {
		t.Fatalf("expected empty-name handler to be ignored, got %d handlers", len(reloadHandlers))
	}
}


================================================
FILE: config/provider.go
================================================
package config

// GetConfigFrom 通过指定 provider 获取配置,并保证返回值非 nil。
func GetConfigFrom(provider func() *Conf) *Conf {
	if provider == nil {
		return &Conf{}
	}

	cfg := provider()
	if cfg == nil {
		return &Conf{}
	}
	return cfg
}


================================================
FILE: config/runtime.go
================================================
package config

import "reflect"

// BuildConfigDiff 生成配置差异摘要。
func BuildConfigDiff(oldConfig, newConfig *Conf) ConfigDiff {
	diff := ConfigDiff{}
	if oldConfig == nil || newConfig == nil {
		return diff
	}

	diff.LoggerChanged = !reflect.DeepEqual(oldConfig.Logger, newConfig.Logger)
	diff.MysqlChanged = !reflect.DeepEqual(oldConfig.Mysql, newConfig.Mysql)
	diff.RedisChanged = !reflect.DeepEqual(oldConfig.Redis, newConfig.Redis)
	diff.JWTChanged = oldConfig.Jwt.TTL != newConfig.Jwt.TTL || oldConfig.Jwt.RefreshTTL != newConfig.Jwt.RefreshTTL
	diff.JWTSecretChanged = oldConfig.Jwt.SecretKey != newConfig.Jwt.SecretKey
	diff.BaseURLChanged = oldConfig.BaseURL != newConfig.BaseURL
	diff.CORSChanged = !reflect.DeepEqual(oldConfig.CorsOrigins, newConfig.CorsOrigins) ||
		!reflect.DeepEqual(oldConfig.CorsMethods, newConfig.CorsMethods) ||
		!reflect.DeepEqual(oldConfig.CorsHeaders, newConfig.CorsHeaders) ||
		!reflect.DeepEqual(oldConfig.CorsExposeHeaders, newConfig.CorsExposeHeaders) ||
		oldConfig.CorsMaxAge != newConfig.CorsMaxAge ||
		oldConfig.CorsCredentials != newConfig.CorsCredentials
	diff.TrustedProxiesChanged = !reflect.DeepEqual(oldConfig.TrustedProxies, newConfig.TrustedProxies)
	diff.LightAppChanged = oldConfig.BasePath != newConfig.BasePath ||
		oldConfig.AppEnv != newConfig.AppEnv ||
		oldConfig.Debug != newConfig.Debug ||
		oldConfig.WatchConfig != newConfig.WatchConfig ||
		oldConfig.Language != newConfig.Language ||
		oldConfig.AllowDegradedStartup != newConfig.AllowDegradedStartup

	if diff.LoggerChanged {
		diff.ChangedFields = append(diff.ChangedFields, "logger.*")
	}
	if diff.MysqlChanged {
		diff.ChangedFields = append(diff.ChangedFields, "mysql.*")
	}
	if diff.RedisChanged {
		diff.ChangedFields = append(diff.ChangedFields, "redis.*")
	}
	if diff.JWTChanged {
		diff.ChangedFields = append(diff.ChangedFields, "jwt.ttl", "jwt.refresh_ttl")
	}
	if diff.JWTSecretChanged {
		diff.ChangedFields = append(diff.ChangedFields, "jwt.secret_key")
		diff.RestartRequiredFields = append(diff.RestartRequiredFields, "jwt.secret_key")
	}
	if diff.BaseURLChanged {
		diff.ChangedFields = append(diff.ChangedFields, "app.base_url")
	}
	if diff.CORSChanged {
		diff.ChangedFields = append(diff.ChangedFields, "app.cors_*")
	}
	if diff.TrustedProxiesChanged {
		diff.ChangedFields = append(diff.ChangedFields, "app.trusted_proxies")
		diff.RestartRequiredFields = append(diff.RestartRequiredFields, "app.trusted_proxies")
	}
	if oldConfig.Language != newConfig.Language {
		diff.RestartRequiredFields = append(diff.RestartRequiredFields, "app.language")
	}
	if oldConfig.AllowDegradedStartup != newConfig.AllowDegradedStartup {
		diff.ChangedFields = append(diff.ChangedFields, "app.allow_degraded_startup")
		diff.RestartRequiredFields = append(diff.RestartRequiredFields, "app.allow_degraded_startup")
	}

	return diff
}

// BuildAppliedConfig 返回当前进程应采用的配置快照。
// 对不支持热更新的字段保持旧值,避免配置快照与实际运行状态不一致。
func BuildAppliedConfig(oldConfig, newConfig *Conf, diff ConfigDiff) *Conf {
	if oldConfig == nil {
		return newConfig
	}
	applied := *newConfig
	applied.AppConfig = cloneAppConfig(newConfig.AppConfig)
	applied.Queue = cloneQueueConfig(newConfig.Queue)

	if diff.JWTSecretChanged {
		applied.Jwt.SecretKey = oldConfig.Jwt.SecretKey
	}
	if diff.TrustedProxiesChanged {
		applied.TrustedProxies = cloneStringSlice(oldConfig.TrustedProxies)
	}
	if oldConfig.Language != newConfig.Language {
		applied.Language = oldConfig.Language
	}
	if oldConfig.AllowDegradedStartup != newConfig.AllowDegradedStartup {
		applied.AllowDegradedStartup = oldConfig.AllowDegradedStartup
	}

	return &applied
}


================================================
FILE: config/runtime_test.go
================================================
package config

import (
	"testing"

	. "github.com/wannanbigpig/gin-layout/config/autoload"
)

func TestBuildConfigDiff(t *testing.T) {
	oldCfg := &Conf{
		AppConfig: App,
		Mysql:     Mysql,
		Redis:     Redis,
		Logger:    Logger,
		Jwt:       Jwt,
	}
	newCfg := &Conf{
		AppConfig: App,
		Mysql:     Mysql,
		Redis:     Redis,
		Logger:    Logger,
		Jwt:       Jwt,
	}

	newCfg.Logger.Output = "stderr"
	newCfg.Redis.Enable = true
	newCfg.BaseURL = "https://example.com"
	newCfg.CorsOrigins = []string{"https://ui.example.com"}
	newCfg.TrustedProxies = []string{"10.0.0.0/8"}
	newCfg.Jwt.TTL = 999
	newCfg.Jwt.SecretKey = "new-secret"
	newCfg.Language = "en"

	diff := BuildConfigDiff(oldCfg, newCfg)
	if !diff.LoggerChanged {
		t.Fatalf("expected logger change")
	}
	if !diff.RedisChanged {
		t.Fatalf("expected redis change")
	}
	if !diff.JWTChanged {
		t.Fatalf("expected jwt ttl change")
	}
	if !diff.JWTSecretChanged {
		t.Fatalf("expected jwt secret change")
	}
	if !diff.BaseURLChanged {
		t.Fatalf("expected base_url change")
	}
	if !diff.CORSChanged {
		t.Fatalf("expected cors change")
	}
	if !diff.TrustedProxiesChanged {
		t.Fatalf("expected trusted proxies change")
	}
	if len(diff.RestartRequiredFields) == 0 {
		t.Fatalf("expected restart-required fields")
	}
}

func TestBuildAppliedConfigKeepsUnsupportedFields(t *testing.T) {
	oldCfg := &Conf{
		AppConfig: App,
		Mysql:     Mysql,
		Redis:     Redis,
		Logger:    Logger,
		Jwt: JwtConfig{
			TTL:        100,
			RefreshTTL: 10,
			SecretKey:  "old-secret",
		},
	}
	oldCfg.TrustedProxies = []string{"127.0.0.1"}
	oldCfg.Language = "zh_CN"

	newCfg := &Conf{
		AppConfig: App,
		Mysql:     Mysql,
		Redis:     Redis,
		Logger:    Logger,
		Jwt: JwtConfig{
			TTL:        200,
			RefreshTTL: 20,
			SecretKey:  "new-secret",
		},
	}
	newCfg.TrustedProxies = []string{"10.0.0.0/8"}
	newCfg.Language = "en"
	newCfg.BaseURL = "https://cdn.example.com"

	diff := BuildConfigDiff(oldCfg, newCfg)
	applied := BuildAppliedConfig(oldCfg, newCfg, diff)

	if applied.Jwt.SecretKey != "old-secret" {
		t.Fatalf("expected jwt secret to remain old, got %q", applied.Jwt.SecretKey)
	}
	if applied.Language != "zh_CN" {
		t.Fatalf("expected language to remain old, got %q", applied.Language)
	}
	if len(applied.TrustedProxies) != 1 || applied.TrustedProxies[0] != "127.0.0.1" {
		t.Fatalf("expected trusted proxies to remain old, got %#v", applied.TrustedProxies)
	}
	if applied.BaseURL != "https://cdn.example.com" {
		t.Fatalf("expected supported field base_url to update, got %q", applied.BaseURL)
	}
	if applied.Jwt.TTL != 200 {
		t.Fatalf("expected supported jwt ttl to update, got %v", applied.Jwt.TTL)
	}
}


================================================
FILE: config/testing_helper.go
================================================
package config

// CloneConf 返回配置的深拷贝,避免测试场景下共享可变引用。
func CloneConf(src *Conf) *Conf {
	if src == nil {
		return &Conf{}
	}

	cloned := *src
	cloned.AppConfig = cloneAppConfig(src.AppConfig)
	cloned.Queue = cloneQueueConfig(src.Queue)
	return &cloned
}

// ReplaceConfigForTesting 替换当前配置并返回恢复函数。
func ReplaceConfigForTesting(cfg *Conf) func() {
	previous := CloneConf(GetConfig())

	if cfg == nil {
		setActiveConfig(&Conf{})
	} else {
		setActiveConfig(CloneConf(cfg))
	}

	return func() {
		setActiveConfig(previous)
	}
}

// UpdateConfigForTesting 在当前配置副本上应用变更并返回恢复函数。
func UpdateConfigForTesting(mutator func(cfg *Conf)) func() {
	next := CloneConf(GetConfig())
	if mutator != nil {
		mutator(next)
	}
	return ReplaceConfigForTesting(next)
}


================================================
FILE: data/data.go
================================================
package data

import (
	"errors"
	"fmt"
	"sync"

	c "github.com/wannanbigpig/gin-layout/config"
)

var once sync.Once
var initErr error

// InitData 按配置初始化 MySQL 和 Redis 数据源。
func InitData() error {
	once.Do(func() {
		cfg := c.GetConfig()
		var errs []error

		if cfg.Mysql.Enable {
			if err := initMysql(); err != nil {
				errs = append(errs, fmt.Errorf("mysql init error: %w", err))
			}
		}

		if cfg.Redis.Enable {
			if err := initRedis(); err != nil {
				errs = append(errs, fmt.Errorf("redis init error: %w", err))
			}
		}

		if len(errs) > 0 {
			initErr = errors.Join(errs...)
		}
	})

	return initErr
}

// Shutdown 关闭当前已初始化的数据源。
func Shutdown() error {
	var firstErr error

	if err := CloseRedis(); err != nil && firstErr == nil {
		firstErr = err
	}
	if err := CloseMysql(); err != nil && firstErr == nil {
		firstErr = err
	}

	return firstErr
}


================================================
FILE: data/data_test.go
================================================
package data

import "testing"

func TestShutdownWithoutInitializedResources(t *testing.T) {
	if err := Shutdown(); err != nil {
		t.Fatalf("shutdown should be safe without initialized resources: %v", err)
	}
	if err := Shutdown(); err != nil {
		t.Fatalf("shutdown should be idempotent: %v", err)
	}
}


================================================
FILE: data/migrations/20260425000001_init_table.down.sql
================================================
BEGIN;

-- 删除系统参数与字典相关表
DROP TABLE IF EXISTS `sys_dict_item_i18n`;
DROP TABLE IF EXISTS `sys_dict_type_i18n`;
DROP TABLE IF EXISTS `sys_config_i18n`;
DROP TABLE IF EXISTS `sys_dict_item`;
DROP TABLE IF EXISTS `sys_dict_type`;
DROP TABLE IF EXISTS `sys_config`;
-- 删除任务中心相关表
DROP TABLE IF EXISTS `cron_task_states`;
DROP TABLE IF EXISTS `task_run_events`;
DROP TABLE IF EXISTS `task_runs`;
DROP TABLE IF EXISTS `task_definitions`;
-- 删除管理员表
DROP TABLE IF EXISTS `admin_user`;
-- 删除路由表
DROP TABLE IF EXISTS `api`;
-- 删除路由分组表
DROP TABLE IF EXISTS `api_group`;
-- 删除菜单多语言标题表
DROP TABLE IF EXISTS `menu_i18n`;
-- 删除菜单表
DROP TABLE IF EXISTS `menu`;
-- 删除部门表
DROP TABLE IF EXISTS `department`;
-- 删除角色表
DROP TABLE IF EXISTS `role`;
-- 删除用户部门映射表
DROP TABLE IF EXISTS `admin_user_department_map`;
-- 删除部门角色映射表
DROP TABLE IF EXISTS `department_role_map`;
-- 删除用户菜单映射表
DROP TABLE IF EXISTS `admin_user_menu_map`;
-- 删除菜单权限映射表
DROP TABLE IF EXISTS `menu_api_map`;
-- 删除角色菜单映射表
DROP TABLE IF EXISTS `role_menu_map`;
-- 删除用户角色映射表
DROP TABLE IF EXISTS `admin_user_role_map`;
-- 删除请求日志表
DROP TABLE IF EXISTS `request_logs`;
-- 删除登录安全状态表
DROP TABLE IF EXISTS `login_security_state`;
-- 删除管理员登录日志表
DROP TABLE IF EXISTS `admin_login_logs`;
-- 删除casbin规则表
DROP TABLE IF EXISTS `casbin_rule`;
-- 删除文件上传表
DROP TABLE IF EXISTS `upload_file_references`;
DROP TABLE IF EXISTS `upload_file_folders`;
DROP TABLE IF EXISTS `upload_files`;

COMMIT;


================================================
FILE: data/migrations/20260425000001_init_table.up.sql
================================================
BEGIN;

-- 创建管理员表
CREATE TABLE IF NOT EXISTS `admin_user`
(
    `id`                int unsigned                                                 NOT NULL AUTO_INCREMENT,
    `nickname`          varchar(30)                                                  NOT NULL DEFAULT '' COMMENT '昵称',
    `username`          varchar(30)                                                  NOT NULL DEFAULT '' COMMENT '用户名',
    `password`          varchar(255)                                                 NOT NULL DEFAULT '' COMMENT '密码',
    `phone_number`      varchar(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '手机号',
    `full_phone_number` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '带区号的手机号',
    `country_code`      varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '国际区号',
    `email`             varchar(120)                                                 NOT NULL DEFAULT '' COMMENT '邮箱',
    `avatar`            varchar(255)                                                 NOT NULL DEFAULT '' COMMENT '头像',
    `status`            tinyint(1)                                                   NOT NULL DEFAULT '1' COMMENT '状态 1启用 0禁用',
    `is_super_admin`    tinyint(1)                                                   NOT NULL DEFAULT '1' COMMENT '是否超级管理员(拥有所有权限) 1是 0不是',
    `last_login`        datetime                                                              DEFAULT NULL COMMENT '最后登录时间',
    `last_ip`           varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT '最后登录IP',
    `created_at`        datetime                                                              DEFAULT NULL,
    `updated_at`        datetime                                                              DEFAULT NULL,
    `deleted_at`        int                                                          NOT NULL DEFAULT '0' COMMENT '删除时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `adu_u_d` (`username`, `deleted_at`),
    KEY `idx_status_deleted_at` (`status`, `deleted_at`),
    KEY `idx_full_phone_number_deleted_at` (`full_phone_number`, `deleted_at`),
    KEY `idx_email_deleted_at` (`email`, `deleted_at`),
    KEY `idx_created_at_deleted_at` (`created_at`, `deleted_at`)
) ENGINE = InnoDB
  AUTO_INCREMENT = 10000
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_0900_ai_ci COMMENT ='后台管理用户表';

-- 创建权限分组表
CREATE TABLE IF NOT EXISTS `api_group`
(
    `id`         int unsigned                                          NOT NULL AUTO_INCREMENT,
    `pid`        int unsigned                                          NOT NULL DEFAULT '0' COMMENT '上级组织id',
    `code`       varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT 'code',
    `name`       varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '分组名称',
    `created_at` datetime                                                       DEFAULT NULL,
    `updated_at` datetime                                                       DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `ag_code` (`code`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='权限分组表';

-- 创建权限表
CREATE TABLE IF NOT EXISTS `api`
(
    `id`           int unsigned                                           NOT NULL AUTO_INCREMENT,
    `code`         varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '权限唯一code码',
    `group_code`   varchar(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '分组唯一code码',
    `name`         varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '权限名称',
    `description`  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '描述',
    `method`       varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '接口请求方法',
    `route`        varchar(160) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '接口路由',
    `func`         varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '接口方法',
    `func_path`    varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '接口方法路径',
    `is_auth`      tinyint unsigned                                       NOT NULL DEFAULT '1' COMMENT '接口鉴权模式 0无需登录 1需要登录 2需要登录且需要API权限',
    `is_effective` tinyint unsigned                                       NOT NULL DEFAULT '1' COMMENT '接口是否可用 1是 0否',
    `sort`         int unsigned                                           NOT NULL DEFAULT '0' COMMENT '排序',
    `created_at`   datetime                                                        DEFAULT NULL,
    `updated_at`   datetime                                                        DEFAULT NULL,
    `deleted_at`   int                                                    NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `api_uniq_code_del` (`code`, `deleted_at`) USING BTREE,
    KEY `api_idx_route_method_deleted_at` (`route`, `method`, `deleted_at`) USING BTREE,
    KEY `idx_group_code_deleted_at_sort` (`group_code`, `deleted_at`, `sort`) USING BTREE,
    KEY `idx_is_auth_deleted_at` (`is_auth`, `deleted_at`) USING BTREE,
    KEY `idx_updated_at` (`updated_at`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='权限表';

-- 创建菜单表
CREATE TABLE IF NOT EXISTS `menu`
(
    `id`                int                                                    NOT NULL AUTO_INCREMENT,
    `icon`              varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '图标',
    `code`              varchar(120) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '前端权限标识',
    `path`              varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '前端路由路径',
    `full_path`         varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '完整前端路由路径',
    `redirect`          varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '重定向路由名称',
    `name`              varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '前端路由名称',
    `component`         varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '前端组件路径',
    `animate_enter`     varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '进入动画,动画类参考https://animate.style/',
    `animate_leave`     varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '离开动画,动画类参考https://animate.style/',
    `animate_duration`  float(2, 2)                                            NOT NULL DEFAULT '0.00' COMMENT '动画持续时间',
    `is_show`           tinyint                                                NOT NULL DEFAULT '0' COMMENT '是否显示,1是 0否',
    `status`            tinyint                                                NOT NULL DEFAULT '0' COMMENT '状态,1正常 0禁用',
    `is_auth`           tinyint                                                NOT NULL DEFAULT '0' COMMENT '是否需要授权,1是 0否 ',
    `is_external_links` tinyint                                                NOT NULL DEFAULT '0' COMMENT '是否外链,1是 0否 ',
    `is_new_window`     tinyint                                                NOT NULL DEFAULT '0' COMMENT '是否新窗口打开, 1是 0否',
    `sort`              int                                                    NOT NULL DEFAULT '0' COMMENT '排序,数字越大,排名越靠前',
    `type`              tinyint                                                NOT NULL DEFAULT '1' COMMENT '菜单类型,1目录,2菜单,3按钮',
    `pid`               int                                                    NOT NULL DEFAULT '0' COMMENT '上级菜单id',
    `level`             tinyint                                                NOT NULL DEFAULT '0' COMMENT '层级',
    `pids`              varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '层级序列,多个用英文逗号隔开',
    `children_num`      int                                                    NOT NULL DEFAULT '0' COMMENT '子集数量',
    `description`       varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '描述',
    `created_at`        datetime                                                        DEFAULT NULL COMMENT '创建时间',
    `updated_at`        datetime                                                        DEFAULT NULL COMMENT '更新时间',
    `deleted_at`        int                                                    NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`) USING BTREE,
    KEY `uniq_code_del` (`code`, `deleted_at`) USING BTREE,
    KEY `idx_name_del` (`name`, `deleted_at`) USING BTREE,
    KEY `idx_path_del` (`path`, `deleted_at`) USING BTREE,
    KEY `idx_is_auth_del` (`is_auth`, `deleted_at`) USING BTREE,
    KEY `idx_status_del` (`status`, `deleted_at`) USING BTREE,
    KEY `idx_pid_deleted_at_sort_id` (`pid`, `deleted_at`, `sort`, `id`) USING BTREE,
    KEY `idx_pids_deleted_at` (`pids`, `deleted_at`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='菜单表';

-- 创建菜单多语言标题表
CREATE TABLE IF NOT EXISTS `menu_i18n`
(
    `id`         int unsigned                                           NOT NULL AUTO_INCREMENT,
    `menu_id`    int unsigned                                           NOT NULL DEFAULT '0' COMMENT '菜单ID',
    `locale`     varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '语言代码,如 zh-CN、en-US',
    `title`      varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '菜单标题',
    `created_at` datetime                                                        DEFAULT NULL,
    `updated_at` datetime                                                        DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_menu_id_locale` (`menu_id`, `locale`) USING BTREE,
    KEY `idx_locale_menu_id` (`locale`, `menu_id`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='菜单多语言标题表';

-- 创建组织表
CREATE TABLE IF NOT EXISTS `department`
(
    `id`          int unsigned                                           NOT NULL AUTO_INCREMENT,
    `code`        varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '部门业务编码',
    `is_system`   tinyint                                                NOT NULL DEFAULT '0' COMMENT '是否系统保留对象,1是 0否',
    `pid`         int unsigned                                           NOT NULL DEFAULT '0' COMMENT '上级部门id',
    `pids`        varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '',
    `name`        varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '部门名称',
    `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '描述',
    `level`       tinyint                                                NOT NULL DEFAULT '1' COMMENT '层级',
    `sort`        int                                                    NOT NULL DEFAULT '0' COMMENT '排序',
    `children_num` int                                                   NOT NULL DEFAULT '0' COMMENT '子集数量',
    `user_number` int                                                    NOT NULL DEFAULT '0' COMMENT '部门用户数量',
    `created_at`  datetime                                                        DEFAULT NULL,
    `updated_at`  datetime                                                        DEFAULT NULL,
    `deleted_at`  int                                                    NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_code_deleted_at` (`code`, `deleted_at`),
    KEY `idx_name_deleted_at` (`name`, `deleted_at`),
    KEY `idx_is_system_deleted_at` (`is_system`, `deleted_at`),
    KEY `idx_pid_deleted_at_sort_id` (`pid`, `deleted_at`, `sort`, `id`),
    KEY `idx_pids_deleted_at` (`pids`, `deleted_at`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='部门表';

-- 创建角色表
CREATE TABLE IF NOT EXISTS `role`
(
    `id`          int unsigned                                           NOT NULL AUTO_INCREMENT,
    `code`        varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '角色业务编码',
    `is_system`   tinyint                                                NOT NULL DEFAULT '0' COMMENT '是否系统保留对象,1是 0否',
    `pid`         int unsigned                                           NOT NULL DEFAULT '0' COMMENT '上级id',
    `pids`        varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '上级id路径链',
    `name`        varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '角色名称',
    `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '描述',
    `level`       tinyint                                                NOT NULL DEFAULT '1' COMMENT '层级',
    `sort`        mediumint                                              NOT NULL DEFAULT '0' COMMENT '排序',
    `children_num` int unsigned                                          NOT NULL DEFAULT '0' COMMENT '子集数量',
    `status`      tinyint                                                NOT NULL DEFAULT '0' COMMENT '是否启用状态,1是,0否',
    `created_at`  datetime                                                        DEFAULT NULL,
    `updated_at`  datetime                                                        DEFAULT NULL,
    `deleted_at`  int                                                    NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_code_deleted_at` (`code`, `deleted_at`),
    KEY `idx_name_deleted_at` (`name`, `deleted_at`),
    KEY `idx_is_system_deleted_at` (`is_system`, `deleted_at`),
    KEY `idx_pid_deleted_at_sort_id` (`pid`, `deleted_at`, `sort`, `id`),
    KEY `idx_status_deleted_at` (`status`, `deleted_at`),
    KEY `idx_pids_deleted_at` (`pids`, `deleted_at`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='角色表';

-- 创建用户部门映射表
CREATE TABLE IF NOT EXISTS `admin_user_department_map`
(
    `id`         int unsigned NOT NULL AUTO_INCREMENT,
    `uid`        int unsigned NOT NULL DEFAULT '0' COMMENT 'admin_users表id',
    `dept_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '部门id,department表id',
    `is_admin`   tinyint      NOT NULL DEFAULT '0' COMMENT '是否管理员,1是,0否',
    `created_at` datetime              DEFAULT NULL,
    `updated_at` datetime              DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `idx_uid_dept_id` (`uid`, `dept_id`),
    KEY `idx_dept_id_uid` (`dept_id`, `uid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='用户部门映射表';

-- 创建菜单权限映射表
CREATE TABLE IF NOT EXISTS `menu_api_map`
(
    `id`         int unsigned NOT NULL AUTO_INCREMENT,
    `menu_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '菜单id,对应menu表id',
    `api_id`     int unsigned NOT NULL DEFAULT '0' COMMENT '接口id,对应api表id',
    `created_at` datetime              DEFAULT NULL,
    `updated_at` datetime              DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `idx_menu_id_api_id` (`menu_id`, `api_id`),
    KEY `idx_api_id_menu_id` (`api_id`, `menu_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='菜单权限映射表';

-- menu_api_map 维护菜单与 API 的静态关系。
-- api 表由 `go-layout command api-route` 或 `init-system` 写入后,
-- 默认菜单与 API 的初始绑定由 Go 初始化逻辑幂等补齐。

-- 创建角色菜单映射表
CREATE TABLE IF NOT EXISTS `role_menu_map`
(
    `id`         int unsigned NOT NULL AUTO_INCREMENT,
    `role_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '角色id,对应roles表id',
    `menu_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '菜单id,对应menu表id',
    `created_at` datetime              DEFAULT NULL,
    `updated_at` datetime              DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `idx_role_id_menu_id` (`role_id`, `menu_id`),
    KEY `idx_menu_id_role_id` (`menu_id`, `role_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='角色菜单映射表';

-- 创建用户菜单映射表
CREATE TABLE IF NOT EXISTS `admin_user_menu_map`
(
    `id`         int unsigned NOT NULL AUTO_INCREMENT,
    `uid`        int unsigned NOT NULL DEFAULT '0' COMMENT 'uid,admin_users表id',
    `menu_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '菜单id',
    `created_at` datetime              DEFAULT NULL,
    `updated_at` datetime              DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `idx_uid_menu_id` (`uid`, `menu_id`),
    KEY `idx_menu_id_uid` (`menu_id`, `uid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='用户菜单映射表';

-- 创建部门角色映射表
CREATE TABLE IF NOT EXISTS `department_role_map`
(
    `id`         int unsigned NOT NULL AUTO_INCREMENT,
    `dept_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '部门id,对应department表id',
    `role_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '角色id,对应roles表id',
    `created_at` datetime              DEFAULT NULL,
    `updated_at` datetime              DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `idx_dept_id_role_id` (`dept_id`, `role_id`),
    KEY `idx_role_id_dept_id` (`role_id`, `dept_id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='部门角色映射表';

-- 创建用户角色映射表
CREATE TABLE IF NOT EXISTS `admin_user_role_map`
(
    `id`         int unsigned NOT NULL AUTO_INCREMENT,
    `uid`        int unsigned NOT NULL DEFAULT '0' COMMENT 'uid,admin_users表id',
    `role_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '角色id',
    `created_at` datetime              DEFAULT NULL,
    `updated_at` datetime              DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `idx_uid_role_id` (`uid`, `role_id`),
    KEY `idx_role_id_uid` (`role_id`, `uid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_bin
  ROW_FORMAT = DYNAMIC COMMENT ='用户角色映射表';

-- 创建管理员登录日志表
CREATE TABLE IF NOT EXISTS `admin_login_logs`
(
    `id`                 bigint unsigned  NOT NULL AUTO_INCREMENT,
    `uid`                int unsigned     NOT NULL DEFAULT '0' COMMENT '用户ID(登录失败时为0)',
    `username`           varchar(50)      NOT NULL DEFAULT '' COMMENT '登录账号',
    `jwt_id`             char(36)         NOT NULL DEFAULT '' COMMENT 'JWT唯一标识(jti claim)',
    `access_token`       text                      DEFAULT NULL COMMENT '访问令牌(加密保存)',
    `refresh_token`      text                      DEFAULT NULL COMMENT '刷新令牌(加密保存)',
    `token_hash`         char(64)         NOT NULL DEFAULT '' COMMENT 'Token的SHA256哈希值',
    `refresh_token_hash` char(64)         NOT NULL DEFAULT '' COMMENT 'Refresh Token的哈希值',
    `ip`                 varchar(45)      NOT NULL DEFAULT '' COMMENT '登录IP(支持IPv6)',
    `user_agent`         varchar(1024)    NOT NULL DEFAULT '' COMMENT '用户代理(浏览器/设备信息)',
    `os`                 varchar(50)      NOT NULL DEFAULT '' COMMENT '操作系统',
    `browser`            varchar(50)      NOT NULL DEFAULT '' COMMENT '浏览器',
    `execution_time`     int(11)          NOT NULL DEFAULT '0' COMMENT '登录耗时(毫秒)',
    `login_status`       tinyint(1)       NOT NULL DEFAULT '1' COMMENT '登录状态:1=成功, 0=失败',
    `login_fail_reason`  varchar(255)     NOT NULL DEFAULT '' COMMENT '登录失败原因',
    `type`               tinyint(1)       NOT NULL DEFAULT '1' COMMENT '操作类型:1=登录操作, 2=刷新token',
    `is_revoked`         tinyint(1)       NOT NULL DEFAULT '0' COMMENT '是否被撤销:0=否, 1=是',
    `revoked_code`       tinyint(1)       NOT NULL DEFAULT '0' COMMENT '撤销原因码:1=用户主动登出(退出登录), 2=系统强制登出(账号被封), 3=系统刷新token, 4=用户禁用(针对某个设备下线操作), 5=其他原因, 6=用户自己修改密码, 7=管理员修改用户密码',
    `revoked_reason`     varchar(255)     NOT NULL DEFAULT '' COMMENT '撤销原因',
    `revoked_at`         datetime                  DEFAULT NULL COMMENT '撤销时间',
    `token_expires`      datetime                  DEFAULT NULL COMMENT 'Token过期时间',
    `refresh_expires`    datetime                  DEFAULT NULL COMMENT 'Refresh Token过期时间',
    `created_at`         datetime                  DEFAULT NULL,
    `updated_at`         datetime                  DEFAULT NULL,
    `deleted_at`         int              NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`),
    KEY `aall_deleted_at_created_at` (`deleted_at`, `created_at`),
    KEY `aall_login_status_deleted_at_created_at` (`login_status`, `deleted_at`, `created_at`),
    KEY `aall_is_revoked_deleted_at_revoked_at` (`is_revoked`, `deleted_at`, `revoked_at`),
    KEY `aall_uid_deleted_at_is_revoked_login_status_token_expires` (`uid`, `deleted_at`, `is_revoked`, `login_status`, `token_expires`),
    KEY `aall_jwt_id_deleted_at_is_revoked` (`jwt_id`, `deleted_at`, `is_revoked`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='管理员登录日志表';

-- 创建登录安全状态表
CREATE TABLE IF NOT EXISTS `login_security_state`
(
    `id`             int unsigned NOT NULL AUTO_INCREMENT,
    `username`       varchar(50)  NOT NULL DEFAULT '' COMMENT '登录账号',
    `fail_count`     int unsigned NOT NULL DEFAULT '0' COMMENT '连续失败次数',
    `lock_until`     datetime              DEFAULT NULL COMMENT '锁定截止时间',
    `last_failed_at` datetime              DEFAULT NULL COMMENT '最近失败时间',
    `created_at`     datetime              DEFAULT NULL,
    `updated_at`     datetime              DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `lss_username` (`username`),
    KEY `lss_lock_until` (`lock_until`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='登录安全状态表';

-- 创建请求日志表
CREATE TABLE IF NOT EXISTS `request_logs`
(
    `id`              bigint(20)   NOT NULL AUTO_INCREMENT COMMENT '日志ID',
    `request_id`      varchar(64)  NOT NULL DEFAULT '' COMMENT '请求唯一标识',
    `jwt_id`          varchar(36)  NOT NULL DEFAULT '' COMMENT '请求授权的jwtId',
    `operator_id`     bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '操作ID(用户ID)',
    `ip`              varchar(45)  NOT NULL DEFAULT '' COMMENT '客户端IP地址',
    `user_agent`      varchar(1024) NOT NULL DEFAULT '' COMMENT '用户代理(浏览器/设备信息)',
    `os`             varchar(50)  NOT NULL DEFAULT '' COMMENT '操作系统',
    `browser`        varchar(50)  NOT NULL DEFAULT '' COMMENT '浏览器',
    `method`          varchar(10)  NOT NULL DEFAULT '' COMMENT 'HTTP请求方法(GET/POST等)',
    `base_url`        varchar(160) NOT NULL DEFAULT '' COMMENT '请求基础URL',
    `operation_name`  varchar(255) NOT NULL DEFAULT '' COMMENT '操作名称',
    `operation_status` int(11) NOT NULL DEFAULT '0' COMMENT '操作状态码(响应返回的code,0=成功,其他=失败)',
    `is_high_risk`    tinyint(1)    NOT NULL DEFAULT '0' COMMENT '是否高危操作 1是 0否',
    `operator_account` varchar(50) NOT NULL DEFAULT '' COMMENT '操作账号',
    `operator_name`   varchar(50)  NOT NULL DEFAULT '' COMMENT '操作人员',
    `request_headers` text                  DEFAULT NULL COMMENT '请求头(JSON格式)',
    `request_query`   text                  DEFAULT NULL COMMENT '请求参数',
    `request_body`    text                  DEFAULT NULL COMMENT '请求体',
    `change_diff`     longtext              DEFAULT NULL COMMENT '关键变更前后差异(JSON)',
    `response_status` int(11)      NOT NULL DEFAULT '0' COMMENT '响应状态码',
    `response_body`   text                  DEFAULT NULL COMMENT '响应体',
    `response_header` text                  DEFAULT NULL COMMENT '响应头',
    `execution_time`  DECIMAL(10,4) NOT NULL DEFAULT '0.0000' COMMENT '执行时间(毫秒,支持小数,最多4位)',
    `created_at`      datetime              DEFAULT NULL COMMENT '创建时间',
    `updated_at`      datetime              DEFAULT NULL COMMENT '更新时间',
    PRIMARY KEY (`id`),
    KEY `rl_request_id` (`request_id`),
    KEY `rl_operator_id_created_at` (`operator_id`, `created_at`),
    KEY `rl_base_url_method_created_at` (`base_url`, `method`, `created_at`),
    KEY `rl_operation_status_created_at` (`operation_status`, `created_at`),
    KEY `rl_response_status_operator_id_created_at` (`response_status`, `operator_id`, `created_at`),
    KEY `rl_operator_account_created_at` (`operator_account`, `created_at`),
    KEY `rl_created_at` (`created_at`),
    KEY `rl_jwt_id` (`jwt_id`),
    KEY `rl_is_high_risk_created_at` (`is_high_risk`, `created_at`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='请求日志表';


CREATE TABLE IF NOT EXISTS `casbin_rule`
(
    `id`    bigint unsigned NOT NULL AUTO_INCREMENT,
    `ptype` varchar(100) DEFAULT NULL,
    `v0`    varchar(100) DEFAULT NULL,
    `v1`    varchar(100) DEFAULT NULL,
    `v2`    varchar(100) DEFAULT NULL,
    `v3`    varchar(100) DEFAULT NULL,
    `v4`    varchar(100) DEFAULT NULL,
    `v5`    varchar(100) DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `idx_casbin_rule` (`ptype`, `v0`, `v1`, `v2`, `v3`, `v4`, `v5`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_0900_ai_ci COMMENT ='casbin_rule表';

CREATE TABLE IF NOT EXISTS `upload_files`
(
    `id`          int unsigned NOT NULL AUTO_INCREMENT,
    `uid`         int unsigned NOT NULL DEFAULT '0' COMMENT '用户ID',
    `folder_id`   int unsigned NOT NULL DEFAULT '0' COMMENT '逻辑目录ID',
    `logical_path` varchar(1024) NOT NULL DEFAULT '/' COMMENT '逻辑路径快照',
    `display_name` varchar(255) NOT NULL DEFAULT '' COMMENT '展示名称',
    `origin_name` varchar(255) NOT NULL DEFAULT '' COMMENT '文件源名称',
    `name`        varchar(255) NOT NULL DEFAULT '' COMMENT '文件名称(UUID+扩展名)',
    `path`        varchar(255) NOT NULL DEFAULT '' COMMENT '文件相对路径(相对于storage/public或storage/private)',
    `size`        int unsigned NOT NULL DEFAULT '0' COMMENT '文件大小(字节)',
    `ext`         varchar(20)  NOT NULL DEFAULT '' COMMENT '文件扩展名',
    `hash`        varchar(64)  NOT NULL DEFAULT '' COMMENT '文件SHA256哈希值(用于去重)',
    `uuid`        varchar(32)  NOT NULL DEFAULT '' COMMENT '文件UUID(用于URL访问,32位十六进制字符串,不带连字符)',
    `mime_type`   varchar(100) NOT NULL DEFAULT '' COMMENT 'MIME类型(如:image/jpeg, application/pdf)',
    `file_type`   varchar(20)  NOT NULL DEFAULT '' COMMENT '文件类型:image,pdf,word,excel,ppt,archive,text,audio,video,other',
    `is_public`   tinyint      NOT NULL DEFAULT '0' COMMENT '是否公开访问: 0否 1是',
    `storage_driver` varchar(20)  NOT NULL DEFAULT 'local' COMMENT '存储驱动:local,aliyun_oss,s3',
    `storage_base` varchar(512) NOT NULL DEFAULT '' COMMENT '存储基础位置',
    `bucket`      varchar(128) NOT NULL DEFAULT '' COMMENT '存储桶',
    `storage_path` varchar(512) NOT NULL DEFAULT '' COMMENT '实际存储路径',
    `object_key`  varchar(512) NOT NULL DEFAULT '' COMMENT '对象key',
    `etag`        varchar(128) NOT NULL DEFAULT '' COMMENT '对象ETag',
    `storage_status` varchar(32) NOT NULL DEFAULT 'stored' COMMENT '存储状态:stored,delete_failed',
    `upload_source` varchar(20) NOT NULL DEFAULT 'backend' COMMENT '上传来源:backend,direct,system',
    `upload_scene` varchar(60) NOT NULL DEFAULT '' COMMENT '上传场景',
    `upload_status` varchar(20) NOT NULL DEFAULT 'uploaded' COMMENT '上传状态:pending,uploaded,failed',
    `last_accessed_at` datetime DEFAULT NULL COMMENT '最后访问时间',
    `deleted_by`  int unsigned NOT NULL DEFAULT '0' COMMENT '删除人',
    `deleted_reason` varchar(255) NOT NULL DEFAULT '' COMMENT '删除原因',
    `created_at`  datetime              DEFAULT NULL COMMENT '创建时间',
    `updated_at`  datetime              DEFAULT NULL COMMENT '更新时间',
    `deleted_at`  int unsigned NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`),
    KEY `idx_uid_created_at` (`uid`, `created_at`),
    KEY `idx_folder_created_at` (`folder_id`, `created_at`),
    KEY `idx_hash_is_public` (`hash`, `is_public`),
    KEY `idx_file_type_created_at` (`file_type`, `created_at`),
    KEY `idx_storage_driver_status` (`storage_driver`, `storage_status`),
    KEY `idx_deleted_at_created_at` (`deleted_at`, `created_at`),
    UNIQUE KEY `idx_uuid` (`uuid`),
    KEY `idx_is_public_uuid` (`is_public`, `uuid`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='上传文件表';

CREATE TABLE IF NOT EXISTS `upload_file_folders`
(
    `id`           int unsigned NOT NULL AUTO_INCREMENT,
    `parent_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '父目录ID',
    `name`         varchar(120) NOT NULL DEFAULT '' COMMENT '目录名称',
    `logical_path` varchar(1024) NOT NULL DEFAULT '/' COMMENT '逻辑路径',
    `sort`         int NOT NULL DEFAULT '0' COMMENT '排序',
    `created_by`   int unsigned NOT NULL DEFAULT '0' COMMENT '创建人',
    `updated_by`   int unsigned NOT NULL DEFAULT '0' COMMENT '更新人',
    `created_at`   datetime DEFAULT NULL COMMENT '创建时间',
    `updated_at`   datetime DEFAULT NULL COMMENT '更新时间',
    `deleted_at`   int unsigned NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uniq_parent_name_deleted` (`parent_id`, `name`, `deleted_at`),
    KEY `idx_parent_sort` (`parent_id`, `sort`, `id`),
    KEY `idx_logical_path` (`logical_path`(191))
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='上传文件逻辑目录表';

CREATE TABLE IF NOT EXISTS `upload_file_references`
(
    `id`          int unsigned NOT NULL AUTO_INCREMENT,
    `file_id`     int unsigned NOT NULL DEFAULT '0' COMMENT 'upload_files.id',
    `uuid`        varchar(32)  NOT NULL DEFAULT '' COMMENT '文件UUID',
    `owner_type`  varchar(60)  NOT NULL DEFAULT '' COMMENT '引用对象类型',
    `owner_id`    int unsigned NOT NULL DEFAULT '0' COMMENT '引用对象ID',
    `owner_field` varchar(60)  NOT NULL DEFAULT '' COMMENT '引用字段',
    `created_at`  datetime DEFAULT NULL COMMENT '创建时间',
    `updated_at`  datetime DEFAULT NULL COMMENT '更新时间',
    PRIMARY KEY (`id`),
    UNIQUE KEY `uniq_owner_file_field` (`owner_type`, `owner_id`, `owner_field`, `file_id`),
    KEY `idx_file_id` (`file_id`),
    KEY `idx_uuid` (`uuid`),
    KEY `idx_owner` (`owner_type`, `owner_id`, `owner_field`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='上传文件引用关系表';

CREATE TABLE IF NOT EXISTS `sys_config`
(
    `id`           int unsigned                                           NOT NULL AUTO_INCREMENT,
    `config_key`   varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '参数键名',
    `config_value` text                                                            DEFAULT NULL COMMENT '参数值',
    `value_type`   varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT 'string' COMMENT '值类型:string,number,bool,json',
    `group_code`   varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT 'default' COMMENT '参数分组',
    `is_system`    tinyint unsigned                                       NOT NULL DEFAULT '0' COMMENT '是否系统内置:0否,1是',
    `is_sensitive` tinyint unsigned                                       NOT NULL DEFAULT '0' COMMENT '是否敏感配置:0否,1是',
    `is_visible`   tinyint unsigned                                       NOT NULL DEFAULT '1' COMMENT '是否在系统参数页展示:0否,1是',
    `manage_tab`   varchar(60)                                            NOT NULL DEFAULT '' COMMENT '专属配置Tab',
    `status`       tinyint unsigned                                       NOT NULL DEFAULT '1' COMMENT '状态:0禁用,1启用',
    `sort`         int unsigned                                           NOT NULL DEFAULT '0' COMMENT '排序',
    `remark`       varchar(255)                                           NOT NULL DEFAULT '' COMMENT '备注',
    `created_at`   datetime                                                        DEFAULT NULL,
    `updated_at`   datetime                                                        DEFAULT NULL,
    `deleted_at`   int unsigned                                           NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_config_key_deleted_at` (`config_key`, `deleted_at`) USING BTREE,
    KEY `idx_group_status_deleted_at_sort` (`group_code`, `status`, `deleted_at`, `sort`) USING BTREE,
    KEY `idx_visible_deleted_at_sort` (`is_visible`, `deleted_at`, `sort`) USING BTREE,
    KEY `idx_status_deleted_at` (`status`, `deleted_at`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='系统参数表';

CREATE TABLE IF NOT EXISTS `sys_dict_type`
(
    `id`          int unsigned                                           NOT NULL AUTO_INCREMENT,
    `type_code`   varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '字典类型编码',
    `is_system`   tinyint unsigned                                       NOT NULL DEFAULT '0' COMMENT '是否系统内置:0否,1是',
    `status`      tinyint unsigned                                       NOT NULL DEFAULT '1' COMMENT '状态:0禁用,1启用',
    `sort`        int unsigned                                           NOT NULL DEFAULT '0' COMMENT '排序',
    `remark`      varchar(255)                                           NOT NULL DEFAULT '' COMMENT '备注',
    `created_at`  datetime                                                        DEFAULT NULL,
    `updated_at`  datetime                                                        DEFAULT NULL,
    `deleted_at`  int unsigned                                           NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_type_code_deleted_at` (`type_code`, `deleted_at`) USING BTREE,
    KEY `idx_status_deleted_at_sort` (`status`, `deleted_at`, `sort`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='系统字典类型表';

CREATE TABLE IF NOT EXISTS `sys_dict_item`
(
    `id`         int unsigned                                           NOT NULL AUTO_INCREMENT,
    `type_code`  varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '字典类型编码',
    `value`      varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL DEFAULT '' COMMENT '字典值',
    `color`      varchar(30)                                            NOT NULL DEFAULT '' COMMENT '展示颜色',
    `tag_type`   varchar(30)                                            NOT NULL DEFAULT '' COMMENT '前端标签类型',
    `is_default` tinyint unsigned                                       NOT NULL DEFAULT '0' COMMENT '是否默认项:0否,1是',
    `is_system`  tinyint unsigned                                       NOT NULL DEFAULT '0' COMMENT '是否系统内置:0否,1是',
    `status`     tinyint unsigned                                       NOT NULL DEFAULT '1' COMMENT '状态:0禁用,1启用',
    `sort`       int unsigned                                           NOT NULL DEFAULT '0' COMMENT '排序',
    `remark`     varchar(255)                                           NOT NULL DEFAULT '' COMMENT '备注',
    `created_at` datetime                                                        DEFAULT NULL,
    `updated_at` datetime                                                        DEFAULT NULL,
    `deleted_at` int unsigned                                           NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_type_value_deleted_at` (`type_code`, `value`, `deleted_at`) USING BTREE,
    KEY `idx_type_status_deleted_at_sort` (`type_code`, `status`, `deleted_at`, `sort`) USING BTREE,
    KEY `idx_status_deleted_at` (`status`, `deleted_at`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='系统字典项表';

CREATE TABLE IF NOT EXISTS `sys_config_i18n`
(
    `id`          int unsigned                                           NOT NULL AUTO_INCREMENT,
    `config_id`   int unsigned                                           NOT NULL DEFAULT '0' COMMENT '系统参数ID',
    `locale`      varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '语言编码',
    `config_name` varchar(100)                                           NOT NULL DEFAULT '' COMMENT '参数名称',
    `created_at`  datetime                                                        DEFAULT NULL,
    `updated_at`  datetime                                                        DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_config_id_locale` (`config_id`, `locale`) USING BTREE,
    KEY `idx_locale_config_name` (`locale`, `config_name`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='系统参数多语言表';

CREATE TABLE IF NOT EXISTS `sys_dict_type_i18n`
(
    `id`           int unsigned                                           NOT NULL AUTO_INCREMENT,
    `dict_type_id` int unsigned                                           NOT NULL DEFAULT '0' COMMENT '字典类型ID',
    `locale`       varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '语言编码',
    `type_name`    varchar(100)                                           NOT NULL DEFAULT '' COMMENT '字典类型名称',
    `created_at`   datetime                                                        DEFAULT NULL,
    `updated_at`   datetime                                                        DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_dict_type_id_locale` (`dict_type_id`, `locale`) USING BTREE,
    KEY `idx_locale_type_name` (`locale`, `type_name`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='系统字典类型多语言表';

CREATE TABLE IF NOT EXISTS `sys_dict_item_i18n`
(
    `id`           int unsigned                                           NOT NULL AUTO_INCREMENT,
    `dict_item_id` int unsigned                                           NOT NULL DEFAULT '0' COMMENT '字典项ID',
    `locale`       varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin  NOT NULL DEFAULT '' COMMENT '语言编码',
    `label`        varchar(100)                                           NOT NULL DEFAULT '' COMMENT '字典标签',
    `created_at`   datetime                                                        DEFAULT NULL,
    `updated_at`   datetime                                                        DEFAULT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE KEY `uniq_dict_item_id_locale` (`dict_item_id`, `locale`) USING BTREE,
    KEY `idx_locale_label` (`locale`, `label`) USING BTREE
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='系统字典项多语言表';

CREATE TABLE IF NOT EXISTS `task_definitions`
(
    `id`             int unsigned NOT NULL AUTO_INCREMENT,
    `code`           varchar(120) NOT NULL DEFAULT '' COMMENT '任务唯一编码',
    `name`           varchar(120) NOT NULL DEFAULT '' COMMENT '任务名称',
    `kind`           varchar(20)  NOT NULL DEFAULT '' COMMENT '任务类型 async/cron/manual',
    `queue`          varchar(60)  NOT NULL DEFAULT '' COMMENT '队列名称',
    `cron_spec`      varchar(120) NOT NULL DEFAULT '' COMMENT 'Cron 表达式',
    `handler`        varchar(255) NOT NULL DEFAULT '' COMMENT '处理器标识',
    `status`         tinyint(1)   NOT NULL DEFAULT '1' COMMENT '状态 1启用 0停用',
    `allow_manual`   tinyint(1)   NOT NULL DEFAULT '0' COMMENT '是否允许手动触发 1是 0否',
    `allow_retry`    tinyint(1)   NOT NULL DEFAULT '1' COMMENT '是否允许手动重试 1是 0否',
    `is_high_risk`   tinyint(1)   NOT NULL DEFAULT '0' COMMENT '是否高危任务 1是 0否',
    `remark`         varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
    `created_at`     datetime             DEFAULT NULL,
    `updated_at`     datetime             DEFAULT NULL,
    `deleted_at`     int          NOT NULL DEFAULT '0' COMMENT '删除时间戳',
    PRIMARY KEY (`id`),
    UNIQUE KEY `td_code_deleted_at` (`code`, `deleted_at`),
    KEY `td_kind_status_deleted_at` (`kind`, `status`, `deleted_at`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='任务定义表';

CREATE TABLE IF NOT EXISTS `task_runs`
(
    `id`              bigint unsigned NOT NULL AUTO_INCREMENT,
    `task_code`       varchar(120)    NOT NULL DEFAULT '' COMMENT '任务唯一编码',
    `kind`            varchar(20)     NOT NULL DEFAULT '' COMMENT '任务类型 async/cron/manual',
    `source`          varchar(20)     NOT NULL DEFAULT '' COMMENT '来源 queue/cron/manual',
    `source_id`       varchar(120)    NOT NULL DEFAULT '' COMMENT '来源任务ID,如 Asynq task id',
    `queue`           varchar(60)     NOT NULL DEFAULT '' COMMENT '队列名称',
    `trigger_user_id` bigint unsigned NOT NULL DEFAULT '0' COMMENT '触发人ID',
    `trigger_account` varchar(60)     NOT NULL DEFAULT '' COMMENT '触发人账号',
    `status`          varchar(20)     NOT NULL DEFAULT '' COMMENT '执行状态 pending/running/success/failed/canceled/retrying',
    `attempt`         int             NOT NULL DEFAULT '0' COMMENT '当前尝试次数',
    `max_retry`       int             NOT NULL DEFAULT '0' COMMENT '最大重试次数',
    `payload`         mediumtext               DEFAULT NULL COMMENT '任务 payload',
    `error_message`   text                     DEFAULT NULL COMMENT '失败原因',
    `started_at`      datetime                 DEFAULT NULL COMMENT '开始时间',
    `finished_at`     datetime                 DEFAULT NULL COMMENT '结束时间',
    `duration_ms`     decimal(10, 4)  NOT NULL DEFAULT '0.0000' COMMENT '执行耗时毫秒',
    `created_at`      datetime                 DEFAULT NULL,
    `updated_at`      datetime                 DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `tr_task_code_created_at` (`task_code`, `created_at`),
    KEY `tr_status_created_at` (`status`, `created_at`),
    KEY `tr_source_source_id` (`source`, `source_id`),
    KEY `tr_kind_created_at` (`kind`, `created_at`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='任务执行记录表';

CREATE TABLE IF NOT EXISTS `task_run_events`
(
    `id`         bigint unsigned NOT NULL AUTO_INCREMENT,
    `run_id`     bigint unsigned NOT NULL DEFAULT '0' COMMENT '任务执行记录ID',
    `event_type` varchar(30)     NOT NULL DEFAULT '' COMMENT '事件类型 enqueue/start/retry/fail/success/cancel',
    `message`    text                    DEFAULT NULL COMMENT '事件说明',
    `meta`       text                    DEFAULT NULL COMMENT '事件元数据 JSON',
    `created_at` datetime                DEFAULT NULL,
    `updated_at` datetime                DEFAULT NULL,
    PRIMARY KEY (`id`),
    KEY `tre_run_id_created_at` (`run_id`, `created_at`),
    KEY `tre_event_type_created_at` (`event_type`, `created_at`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='任务执行事件表';

CREATE TABLE IF NOT EXISTS `cron_task_states`
(
    `id`               bigint unsigned NOT NULL AUTO_INCREMENT,
    `task_code`        varchar(120)    NOT NULL DEFAULT '' COMMENT '任务唯一编码',
    `cron_spec`        varchar(120)    NOT NULL DEFAULT '' COMMENT 'Cron 表达式',
    `last_run_id`      bigint unsigned NOT NULL DEFAULT '0' COMMENT '最近执行记录ID',
    `last_status`      varchar(20)     NOT NULL DEFAULT '' COMMENT '最近执行状态',
    `last_started_at`  datetime                 DEFAULT NULL COMMENT '最近开始时间',
    `last_finished_at` datetime                 DEFAULT NULL COMMENT '最近结束时间',
    `next_run_at`      datetime                 DEFAULT NULL COMMENT '下次执行时间',
    `last_error`       text                     DEFAULT NULL COMMENT '最近失败原因',
    `created_at`       datetime                 DEFAULT NULL,
    `updated_at`       datetime                 DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `cts_task_code` (`task_code`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4
  COLLATE = utf8mb4_unicode_ci COMMENT ='定时任务最近状态表';

COMMIT;


================================================
FILE: data/migrations/20260425000002_init_data.down.sql
================================================
BEGIN;

-- 回滚系统数据

-- 删除任务中心/系统管理新增菜单与映射
DELETE FROM `role_menu_map` WHERE `menu_id` BETWEEN 42 AND 76;
DELETE FROM `menu_i18n` WHERE `menu_id` BETWEEN 42 AND 76;
DELETE FROM `menu` WHERE `id` BETWEEN 42 AND 76;
DELETE FROM `api_group` WHERE `code` IN ('system', 'sysConfig', 'sysDict', 'task', 'file', 'session');

-- 删除系统参数/字典初始化数据
DELETE FROM `sys_dict_item_i18n`
WHERE `dict_item_id` IN (
    SELECT `id`
    FROM `sys_dict_item`
    WHERE `type_code` IN ('common_status', 'yes_no', 'menu_type', 'api_auth_mode', 'http_method', 'task_kind', 'task_source', 'task_run_status')
      AND `deleted_at` = 0
);
DELETE FROM `sys_dict_type_i18n`
WHERE `dict_type_id` IN (
    SELECT `id`
    FROM `sys_dict_type`
    WHERE `type_code` IN ('common_status', 'yes_no', 'menu_type', 'api_auth_mode', 'http_method', 'task_kind', 'task_source', 'task_run_status')
      AND `deleted_at` = 0
);
DELETE FROM `sys_config_i18n`
WHERE `config_id` IN (
    SELECT `id`
    FROM `sys_config`
    WHERE `config_key` IN ('auth.login_lock_enabled', 'auth.login_max_failures', 'auth.login_lock_minutes', 'task.cron_demo_enabled', 'audit.sensitive_fields', 'storage.active_driver', 'storage.config')
      AND `deleted_at` = 0
);
DELETE FROM `sys_dict_item`
WHERE `type_code` IN ('common_status', 'yes_no', 'menu_type', 'api_auth_mode', 'http_method', 'task_kind', 'task_source', 'task_run_status')
  AND `deleted_at` = 0;
DELETE FROM `sys_dict_type`
WHERE `type_code` IN ('common_status', 'yes_no', 'menu_type', 'api_auth_mode', 'http_method', 'task_kind', 'task_source', 'task_run_status')
  AND `deleted_at` = 0;
DELETE FROM `sys_config`
WHERE `config_key` IN ('auth.login_lock_enabled', 'auth.login_max_failures', 'auth.login_lock_minutes', 'task.cron_demo_enabled', 'audit.sensitive_fields', 'storage.active_driver', 'storage.config')
  AND `deleted_at` = 0;

-- 删除角色菜单映射
DELETE FROM `role_menu_map` WHERE `role_id` = 1;

-- 删除菜单翻译数据
DELETE FROM `menu_i18n` WHERE `menu_id` BETWEEN 1 AND 41;

-- 删除菜单数据
DELETE FROM `menu` WHERE `id` BETWEEN 1 AND 41;

-- 删除权限分组数据
DELETE FROM `api_group` WHERE `id` BETWEEN 1 AND 9;

-- 删除管理员用户部门/角色映射
DELETE FROM `admin_user_role_map` WHERE `uid` = 1 AND `role_id` = 1;
DELETE FROM `admin_user_department_map` WHERE `uid` = 1 AND `dept_id` = 1;

-- 删除默认角色和部门
DELETE FROM `role` WHERE `id` = 1;
DELETE FROM `department` WHERE `id` = 1;

-- 删除管理员用户数据
DELETE FROM `admin_user` WHERE `id` = 1;

COMMIT;


================================================
FILE: data/migrations/20260425000002_init_data.up.sql
================================================
BEGIN;

-- 初始化系统数据

-- 初始密码 123456
INSERT INTO `admin_user` (`id`, `nickname`, `username`, `password`, `phone_number`, `full_phone_number`,
                            `country_code`, `email`, `avatar`, `status`,
                            `is_super_admin`,
                            `created_at`, `updated_at`, `deleted_at`)
VALUES (1, '超级管理员', 'super_admin', '$2a$10$OuKQoJGH7xkCgwFISmDve.euBDbOCnYEJX6R22QMeLxCLwdoJ4iyi', '18888888888',
        '8618888888888', '86', 'admin@go-layout.com', 'https://avatars.githubusercontent.com/u/48752601?v=4', 1, 1,
        '2023-05-01 00:00:00', '2023-05-01 00:00:00', 0);

INSERT INTO `department` (`id`, `code`, `is_system`, `pid`, `pids`, `name`, `description`, `level`, `sort`,
                          `children_num`, `user_number`, `created_at`, `updated_at`, `deleted_at`)
VALUES (1, 'default_department', 1, 0, '0', '默认部门', '系统默认部门', 1, 100, 0, 1,
        '2023-05-01 00:00:00', '2023-05-01 00:00:00', 0);

INSERT INTO `role` (`id`, `code`, `is_system`, `pid`, `pids`, `name`, `description`, `level`, `sort`, `children_num`,
                    `status`, `created_at`, `updated_at`, `deleted_at`)
VALUES (1, 'super_admin', 1, 0, '0', '超级管理员', '系统默认超级管理员角色', 1, 100, 0, 1,
        '2023-05-01 00:00:00', '2023-05-01 00:00:00', 0);

INSERT INTO `admin_user_department_map` (`uid`, `dept_id`, `is_admin`, `created_at`, `updated_at`)
VALUES (1, 1, 1, '2023-05-01 00:00:00', '2023-05-01 00:00:00');

INSERT INTO `admin_user_role_map` (`uid`, `role_id`, `created_at`, `updated_at`)
VALUES (1, 1, '2023-05-01 00:00:00', '2023-05-01 00:00:00');

-- 初始化权限分组数据
INSERT INTO `api_group` (`id`, `pid`, `code`, `name`, `created_at`, `updated_at`)
VALUES (1, 0, 'other', '其他', '2025-04-26 18:00:00', '2025-04-26 18:00:00'),
       (2, 0, 'login', '登录模块', '2025-04-26 18:00:00', '2025-04-26 18:00:00'),
       (3, 0, 'auth', '权限模块', '2025-04-26 18:00:00', '2025-04-26 18:00:00'),
       (4, 3, 'adminUser', '管理员模块', '2025-04-26 18:00:00', '2025-04-26 18:00:00'),
       (5, 3, 'api', 'API模块', '2025-04-26 18:00:00', '2025-04-26 18:00:00'),
       (6, 3, 'role', '角色模块', '2025-04-26 18:00:00', '2025-04-26 18:00:00'),
       (7, 3, 'menu', '菜单模块', '2025-04-26 18:00:00', '2025-04-26 18:00:00'),
       (8, 0, 'common', '公共模块', '2025-04-26 18:00:00', '2025-04-26 18:00:00'),
       (9, 0, 'log', '日志模块', '2025-04-26 18:00:00', '2025-04-26 18:00:00');

-- 初始化菜单数据
INSERT INTO `menu` (`id`, `icon`, `code`, `path`, `full_path`, `redirect`, `name`, `component`, `animate_enter`, `animate_leave`, `animate_duration`, `is_show`, `status`, `is_auth`, `is_external_links`, `is_new_window`, `sort`, `type`, `pid`, `level`, `pids`, `children_num`, `description`, `created_at`, `updated_at`, `deleted_at`) VALUES
(1, 'ep:menu', '', '', '/', '', 'Home', 'home/index.vue', '', '', 0.00, 1, 1, 0, 0, 0, 100, 2, 0, 1, '0', 0, '', '2024-09-27 13:36:50', '2025-11-15 14:36:40', 0),
(2, 'ant-design:lock-outlined', '', 'permission', '/permission', 'AdminUserList', 'Permission', '', '', '', 0.00, 1, 1, 1, 0, 0, 99, 1, 0, 1, '0', 0, '', '2025-04-16 15:36:33', '2025-04-22 18:16:25', 0),
(3, 'ant-design:api-outlined', '', 'list', '/permission/list', '', 'PermissionList', 'permission/api.vue', '', '', 0.00, 1, 1, 1, 0, 0, 100, 2, 2, 2, '0,2', 2, '', '2025-04-16 15:41:54', '2025-11-25 17:23:53', 0),
(4, 'ant-design:menu-outlined', '', 'menu-list', '/permission/menu-list', '', 'MenuList', 'permission/menuList.vue', '', '', 0.00, 1, 1, 1, 0, 0, 105, 2, 2, 2, '0,2', 5, '', '2025-04-16 15:45:31', '2025-11-25 17:23:44', 0),
(5, 'lucide:info', '', '/about', '/about', '', 'About', 'about/index.vue', '', '', 0.00, 1, 1, 1, 0, 0, 78, 2, 0, 1, '0', 0, '', '2025-04-16 16:47:58', '2025-04-23 15:01:05', 0),
(7, 'ep:user', '', 'admin-user-list', '/permission/admin-user-list', '', 'AdminUserList', 'permission/adminUser.vue', '', '', 0.00, 1, 1, 1, 0, 0, 120, 2, 2, 2, '0,2', 5, '', '2025-04-19 11:19:36', '2025-11-25 17:20:23', 0),
(8, 'ant-design:usergroup-add-outlined', '', 'role-list', '/permission/role-list', '', 'RoleList', 'permission/role.vue', '', '', 0.00, 1, 1, 1, 0, 0, 115, 2, 2, 2, '0,2', 5, '', '2025-04-21 16:51:22', '2025-11-25 17:22:21', 0),
(9, 'tdesign:tree-square-dot', '', 'department-list', '/permission/department-list', '', 'DepartmentList', 'permission/department.vue', '', '', 0.00, 1, 1, 1, 0, 0, 115, 2, 2, 2, '0,2', 6, '', '2025-04-21 16:51:22', '2025-11-25 17:21:30', 0),
(10, 'ant-design:edit-filled', 'adminUser:update', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 7, 3, '0,2,7', 0, '', '2025-11-13 16:45:19', '2025-11-18 17:14:25', 0),
(11, 'ant-design:plus-outlined', 'adminUser:add', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 7, 3, '0,2,7', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:22:41', 0),
(12, 'ant-design:user-switch-outlined', 'adminUser:bindRole', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 7, 3, '0,2,7', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:20:53', 0),
(13, 'ant-design:delete-outlined', 'adminUser:delete', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 7, 3, '0,2,7', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:24:34', 0),
(14, 'ant-design:plus-outlined', 'menu:add', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 4, 3, '0,2,4', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:39:20', 0),
(15, 'ant-design:plus-circle-outlined', 'menu:addChild', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 4, 3, '0,2,4', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:39:03', 0),
(16, 'ant-design:edit-filled', 'menu:update', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 4, 3, '0,2,4', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:38:12', 0),
(17, 'ant-design:delete-outlined', 'menu:delete', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 4, 3, '0,2,4', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:37:02', 0),
(18, 'ant-design:plus-outlined', 'role:add', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 8, 3, '0,2,8', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:36:08', 0),
(19, 'ant-design:edit-filled', 'role:update', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 8, 3, '0,2,8', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:36:22', 0),
(20, 'ant-design:delete-outlined', 'role:delete', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 8, 3, '0,2,8', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:32:12', 0),
(21, 'ant-design:plus-outlined', 'department:add', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 9, 3, '0,2,9', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:29:43', 0),
(22, 'ant-design:plus-circle-outlined', 'department:addChild', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 9, 3, '0,2,9', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:29:07', 0),
(23, 'ant-design:edit-filled', 'department:update', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 9, 3, '0,2,9', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:33:40', 0),
(24, 'ant-design:user-switch-outlined', 'department:bindRole', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 9, 3, '0,2,9', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:27:57', 0),
(25, 'ant-design:delete-outlined', 'department:delete', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 9, 3, '0,2,9', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:26:52', 0),
(26, 'ant-design:edit-filled', 'api:update', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 3, 3, '0,2,3', 0, '', '2025-11-15 10:06:52', '2025-11-18 17:39:39', 0),
(28, 'ant-design:plus-circle-outlined', 'role:addChild', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 8, 3, '0,2,8', 0, '', '2025-11-17 17:46:21', '2025-11-18 17:41:45', 0),
(29, 'ep:tickets', '', 'log', '/log', 'RequestLog', 'Log', '', '', '', 0.00, 1, 1, 1, 0, 0, 98, 1, 0, 1, '0', 3, '', '2025-11-20 16:16:47', '2025-11-20 16:17:04', 0),
(30, 'ep:document', '', 'request-log', '/log/request-log', '', 'RequestLog', 'log/request.vue', '', '', 0.00, 1, 1, 1, 0, 0, 100, 2, 29, 2, '0,29', 4, '', '2025-11-20 16:14:39', '2025-11-25 17:25:13', 0),
(31, 'ep:document', '', 'admin-login-log', '/log/admin-login-log', '', 'AdminLoginLog', 'log/adminLogin.vue', '', '', 0.00, 1, 1, 1, 0, 0, 100, 2, 29, 2, '0,29', 2, '', '2025-11-20 16:16:47', '2025-11-25 17:24:30', 0),
(32, 'ep:document', 'adminLoginLog:detail', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 31, 3, '0,29,31', 0, '', '2025-11-22 11:48:10', '2025-11-22 11:48:10', 0),
(33, 'ep:document', 'requestLog:detail', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 30, 3, '0,29,30', 0, '', '2025-11-22 11:48:44', '2025-11-22 11:48:44', 0),
(34, 'ant-design:search-outlined', 'adminUser:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 7, 3, '0,2,7', 0, '', '2025-11-25 17:20:23', '2025-11-25 17:20:23', 0),
(35, 'ant-design:search-outlined', 'department:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 9, 3, '0,2,9', 0, '', '2025-11-25 17:21:22', '2025-11-25 17:21:22', 0),
(36, 'ant-design:search-outlined', 'role:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 8, 3, '0,2,8', 0, '', '2025-11-25 17:22:13', '2025-11-25 17:22:13', 0),
(37, 'ant-design:search-outlined', 'menu:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 4, 3, '0,2,4', 0, '', '2025-11-25 17:23:02', '2025-11-25 17:23:02', 0),
(38, 'ant-design:search-outlined', 'api:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 3, 3, '0,2,3', 0, '', '2025-11-25 17:23:35', '2025-11-25 17:23:35', 0),
(39, 'ant-design:search-outlined', 'adminLoginLog:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 31, 3, '0,29,31', 0, '', '2025-11-25 17:24:20', '2025-11-25 17:24:20', 0),
(40, 'ant-design:search-outlined', 'requestLog:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 30, 3, '0,29,30', 0, '', '2025-11-25 17:25:04', '2025-11-25 17:25:04', 0),
(42, 'ep:setting', '', 'system', '/system', 'SysConfig', 'System', '', '', '', 0.00, 1, 1, 1, 0, 0, 96, 1, 0, 1, '0', 3, '', NOW(), NOW(), 0),
(43, 'ep:operation', '', 'config', '/system/config', '', 'SysConfig', 'system/config.vue', '', '', 0.00, 1, 1, 1, 0, 0, 100, 2, 42, 2, '0,42', 8, '', NOW(), NOW(), 0),
(44, 'ep:collection-tag', '', 'dict', '/system/dict', '', 'SysDict', 'system/dict.vue', '', '', 0.00, 1, 1, 1, 0, 0, 90, 2, 42, 2, '0,42', 4, '', NOW(), NOW(), 0),
(45, 'ant-design:search-outlined', 'sysConfig:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 43, 3, '0,42,43', 0, '', NOW(), NOW(), 0),
(46, 'ant-design:plus-outlined', 'sysConfig:add', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 90, 3, 43, 3, '0,42,43', 0, '', NOW(), NOW(), 0),
(47, 'ant-design:edit-filled', 'sysConfig:update', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 80, 3, 43, 3, '0,42,43', 0, '', NOW(), NOW(), 0),
(48, 'ant-design:delete-outlined', 'sysConfig:delete', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 70, 3, 43, 3, '0,42,43', 0, '', NOW(), NOW(), 0),
(49, 'ep:refresh', 'sysConfig:refresh', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 60, 3, 43, 3, '0,42,43', 0, '', NOW(), NOW(), 0),
(50, 'ant-design:search-outlined', 'sysDict:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 44, 3, '0,42,44', 0, '', NOW(), NOW(), 0),
(51, 'ant-design:plus-outlined', 'sysDict:add', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 90, 3, 44, 3, '0,42,44', 0, '', NOW(), NOW(), 0),
(52, 'ant-design:edit-filled', 'sysDict:update', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 80, 3, 44, 3, '0,42,44', 0, '', NOW(), NOW(), 0),
(53, 'ant-design:delete-outlined', 'sysDict:delete', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 70, 3, 44, 3, '0,42,44', 0, '', NOW(), NOW(), 0),
(54, 'ri:task-line', '', 'task', '/task', 'TaskCenter', 'Task', '', '', '', 0.00, 1, 1, 1, 0, 0, 97, 1, 0, 1, '0', 1, '', NOW(), NOW(), 0),
(55, 'ri:task-line', '', 'center', '/task/center', '', 'TaskCenter', 'system/task.vue', '', '', 0.00, 1, 1, 1, 0, 0, 100, 2, 54, 2, '0,54', 7, '', NOW(), NOW(), 0),
(56, 'ant-design:search-outlined', 'task:list', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 100, 3, 55, 3, '0,54,55', 0, '', NOW(), NOW(), 0),
(57, 'ep:video-play', 'task:trigger', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 90, 3, 55, 3, '0,54,55', 0, '', NOW(), NOW(), 0),
(58, 'ep:refresh-right', 'task:retry', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 80, 3, 55, 3, '0,54,55', 0, '', NOW(), NOW(), 0),
(59, 'ep:circle-close', 'task:cancel', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 70, 3, 55, 3, '0,54,55', 0, '', NOW(), NOW(), 0),
(60, 'ep:view', 'task:detail', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 60, 3, 55, 3, '0,54,55', 0, '', NOW(), NOW(), 0),
(61, 'ant-design:search-outlined', 'requestLog:export', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 80, 3, 30, 3, '0,29,30', 0, '', NOW(), NOW(), 0),
(62, 'ep:setting', 'requestLog:maskConfig', '', '', '', '', '', '', '', 0.00, 1, 1, 1, 0, 0, 70, 3, 30, 3, '0,29,30', 0, '', NOW(), NOW(), 0),
(63, 'ep:folder-opened', '', 'file', '/system/file', '', 'FileResource', 'system/file.
Download .txt
gitextract_fx8etgvi/

├── .air.toml
├── .github/
│   ├── pull_request_template.md
│   └── workflows/
│       ├── codeql.yml
│       └── go.yml
├── .gitignore
├── .golangci.yml
├── LICENSE
├── MEMORY.md
├── README.en.md
├── README.md
├── build.sh
├── cmd/
│   ├── .gitignore
│   ├── bootstrapx/
│   │   ├── bootstrap.go
│   │   └── bootstrap_test.go
│   ├── command/
│   │   └── command.go
│   ├── completion.go
│   ├── cron/
│   │   ├── cron.go
│   │   ├── schedule.go
│   │   ├── schedule_test.go
│   │   ├── task_record_test.go
│   │   ├── tasks.go
│   │   └── tasks_test.go
│   ├── root.go
│   ├── service/
│   │   └── service.go
│   ├── version/
│   │   └── version.go
│   └── worker/
│       └── worker.go
├── config/
│   ├── .gitignore
│   ├── autoload/
│   │   ├── app.go
│   │   ├── jwt.go
│   │   ├── logger.go
│   │   ├── mysql.go
│   │   ├── queue.go
│   │   └── redis.go
│   ├── config.go
│   ├── config.yaml.example
│   ├── config_clone.go
│   ├── config_load.go
│   ├── config_load_test.go
│   ├── config_test.go
│   ├── provider.go
│   ├── runtime.go
│   ├── runtime_test.go
│   └── testing_helper.go
├── data/
│   ├── data.go
│   ├── data_test.go
│   ├── migrations/
│   │   ├── 20260425000001_init_table.down.sql
│   │   ├── 20260425000001_init_table.up.sql
│   │   ├── 20260425000002_init_data.down.sql
│   │   ├── 20260425000002_init_data.up.sql
│   │   ├── 20260515010000_upload_file_objects.down.sql
│   │   └── 20260515010000_upload_file_objects.up.sql
│   ├── mysql.go
│   ├── redis.go
│   ├── runtime_health.go
│   └── runtime_health_test.go
├── docs/
│   ├── COMMANDS_AND_TASKS.en.md
│   ├── COMMANDS_AND_TASKS.md
│   ├── DONATE.en.md
│   ├── DONATE.md
│   ├── MIGRATE_COMMANDS.en.md
│   ├── MIGRATE_COMMANDS.md
│   ├── SECURITY_PERMISSION_FIXES_2026-05.md
│   ├── SYSTEM_CONFIG_AND_DICT_GUIDELINES.en.md
│   └── SYSTEM_CONFIG_AND_DICT_GUIDELINES.md
├── go.mod
├── go.sum
├── internal/
│   ├── access/
│   │   └── casbin/
│   │       ├── adapter.go
│   │       ├── casbin.go
│   │       ├── enforcer_init.go
│   │       └── policy_ops.go
│   ├── console/
│   │   ├── confirm.go
│   │   ├── demo/
│   │   │   └── demo.go
│   │   ├── init/
│   │   │   └── init.go
│   │   ├── migrate/
│   │   │   └── migrate.go
│   │   ├── system_init/
│   │   │   └── system_init.go
│   │   └── task/
│   │       ├── task.go
│   │       └── task_test.go
│   ├── controller/
│   │   ├── admin_v1/
│   │   │   ├── auth.go
│   │   │   ├── auth_admin_user.go
│   │   │   ├── auth_api.go
│   │   │   ├── auth_dept.go
│   │   │   ├── auth_file_resource.go
│   │   │   ├── auth_login_log.go
│   │   │   ├── auth_menu.go
│   │   │   ├── auth_request_log.go
│   │   │   ├── auth_role.go
│   │   │   ├── auth_session.go
│   │   │   ├── auth_storage_config.go
│   │   │   ├── auth_sys_config.go
│   │   │   ├── auth_sys_dict.go
│   │   │   ├── auth_task_center.go
│   │   │   ├── auth_test.go
│   │   │   ├── dashboard.go
│   │   │   └── sys_common.go
│   │   ├── sys_base.go
│   │   ├── sys_base_test.go
│   │   └── sys_demo.go
│   ├── cron/
│   │   ├── queue_fallback.go
│   │   ├── queue_fallback_test.go
│   │   ├── registry.go
│   │   └── registry_test.go
│   ├── filestorage/
│   │   ├── aliyun_oss.go
│   │   ├── local.go
│   │   ├── local_test.go
│   │   ├── s3.go
│   │   └── types.go
│   ├── global/
│   │   ├── api_auth_mode.go
│   │   ├── api_auth_mode_test.go
│   │   ├── auth.go
│   │   ├── common.go
│   │   ├── context_keys.go
│   │   └── system_defaults.go
│   ├── jobs/
│   │   ├── audit_log.go
│   │   ├── audit_log_test.go
│   │   └── registry.go
│   ├── middleware/
│   │   ├── admin_auth.go
│   │   ├── admin_auth_test.go
│   │   ├── audit_context.go
│   │   ├── audit_queue.go
│   │   ├── audit_queue_test.go
│   │   ├── cors.go
│   │   ├── cors_test.go
│   │   ├── database_ready.go
│   │   ├── database_ready_test.go
│   │   ├── logger.go
│   │   ├── logger_bench_test.go
│   │   ├── logger_recorder.go
│   │   ├── logger_storage.go
│   │   ├── logger_test.go
│   │   ├── parse_token.go
│   │   ├── recovery.go
│   │   ├── request_cost.go
│   │   ├── request_locale.go
│   │   └── request_locale_test.go
│   ├── model/
│   │   ├── admin_login_logs.go
│   │   ├── admin_user_dept_map.go
│   │   ├── admin_user_role_map.go
│   │   ├── admin_users.go
│   │   ├── admin_users_test.go
│   │   ├── api.go
│   │   ├── base.go
│   │   ├── base_crud.go
│   │   ├── base_list.go
│   │   ├── base_list_test.go
│   │   ├── base_owner.go
│   │   ├── base_test.go
│   │   ├── base_tree.go
│   │   ├── dept.go
│   │   ├── dept_role_map.go
│   │   ├── file_upload.go
│   │   ├── file_upload_test.go
│   │   ├── login_security_state.go
│   │   ├── menu.go
│   │   ├── menu_api_map.go
│   │   ├── menu_i18n.go
│   │   ├── modelDict/
│   │   │   └── base.go
│   │   ├── request_logs.go
│   │   ├── role.go
│   │   ├── role_menu_map.go
│   │   ├── sys_config.go
│   │   ├── sys_dict.go
│   │   ├── sys_i18n.go
│   │   └── task_center.go
│   ├── pkg/
│   │   ├── auditdiff/
│   │   │   ├── diff.go
│   │   │   └── diff_test.go
│   │   ├── errors/
│   │   │   ├── code.go
│   │   │   ├── code_test.go
│   │   │   ├── en-us.go
│   │   │   ├── error.go
│   │   │   └── zh-cn.go
│   │   ├── func_make/
│   │   │   ├── func_make.go
│   │   │   └── func_make_test.go
│   │   ├── i18n/
│   │   │   ├── locale.go
│   │   │   └── locale_test.go
│   │   ├── logger/
│   │   │   ├── logger.go
│   │   │   └── logger_test.go
│   │   ├── query_builder/
│   │   │   ├── query_builder.go
│   │   │   └── query_builder_test.go
│   │   ├── request/
│   │   │   ├── request.go
│   │   │   └── request_test.go
│   │   ├── response/
│   │   │   ├── response.go
│   │   │   └── response_test.go
│   │   ├── testkit/
│   │   │   ├── secret.go
│   │   │   └── secret_test.go
│   │   └── utils/
│   │       ├── desensitize.go
│   │       ├── format_time.go
│   │       ├── sensitive/
│   │       │   ├── fields.go
│   │       │   ├── http_mask.go
│   │       │   ├── mask.go
│   │       │   └── string_mask.go
│   │       ├── token/
│   │       │   ├── jwt.go
│   │       │   └── jwt_test.go
│   │       ├── utils.go
│   │       └── utils_test.go
│   ├── queue/
│   │   ├── asynqx/
│   │   │   ├── asynq.go
│   │   │   ├── asynq_test.go
│   │   │   ├── inspector_test.go
│   │   │   └── task_record_test.go
│   │   ├── queue.go
│   │   └── queue_test.go
│   ├── resources/
│   │   ├── admin_user.go
│   │   ├── api.go
│   │   ├── base.go
│   │   ├── base_test.go
│   │   ├── common.go
│   │   ├── dept.go
│   │   ├── file_resource.go
│   │   ├── file_resource_test.go
│   │   ├── login_log.go
│   │   ├── login_log_test.go
│   │   ├── menu.go
│   │   ├── request_log.go
│   │   ├── role.go
│   │   ├── session.go
│   │   ├── sys_config.go
│   │   ├── sys_dict.go
│   │   └── task_center.go
│   ├── routers/
│   │   ├── admin_router.go
│   │   ├── defs.go
│   │   ├── deps.go
│   │   ├── meta.go
│   │   ├── register.go
│   │   ├── router.go
│   │   ├── router_deps_test.go
│   │   ├── router_test.go
│   │   └── validate.go
│   ├── runtime/
│   │   └── config_reload.go
│   ├── service/
│   │   ├── access/
│   │   │   ├── api_cache.go
│   │   │   ├── api_cache_test.go
│   │   │   ├── common.go
│   │   │   ├── coordinator.go
│   │   │   ├── graph_loader.go
│   │   │   ├── menu_api_defaults.go
│   │   │   ├── menu_api_defaults_test.go
│   │   │   ├── scope_resolver.go
│   │   │   ├── system_defaults.go
│   │   │   ├── system_defaults_test.go
│   │   │   ├── transaction.go
│   │   │   ├── user_permission_sync.go
│   │   │   ├── user_permission_sync_bench_test.go
│   │   │   └── user_permission_sync_test.go
│   │   ├── admin/
│   │   │   ├── admin_user.go
│   │   │   ├── admin_user_bind.go
│   │   │   ├── admin_user_create_test.go
│   │   │   ├── admin_user_mutation.go
│   │   │   ├── admin_user_test.go
│   │   │   ├── audit_diff.go
│   │   │   └── audit_diff_test.go
│   │   ├── api_permission/
│   │   │   ├── api.go
│   │   │   ├── api_test.go
│   │   │   ├── audit_diff.go
│   │   │   └── audit_diff_test.go
│   │   ├── audit/
│   │   │   ├── list_helpers.go
│   │   │   ├── login_log.go
│   │   │   ├── login_log_test.go
│   │   │   ├── request_log.go
│   │   │   ├── request_log_manage.go
│   │   │   ├── request_log_manage_test.go
│   │   │   └── request_log_write.go
│   │   ├── auth/
│   │   │   ├── login.go
│   │   │   ├── login_bench_test.go
│   │   │   ├── login_blacklist.go
│   │   │   ├── login_helpers_test.go
│   │   │   ├── login_log_helpers.go
│   │   │   ├── login_refresh.go
│   │   │   ├── login_revoke.go
│   │   │   ├── login_security.go
│   │   │   ├── login_security_test.go
│   │   │   ├── login_token_ops.go
│   │   │   ├── login_types.go
│   │   │   ├── login_types_test.go
│   │   │   ├── principal.go
│   │   │   ├── session.go
│   │   │   └── session_test.go
│   │   ├── common.go
│   │   ├── common_test.go
│   │   ├── common_upload_helpers.go
│   │   ├── common_upload_helpers_test.go
│   │   ├── dashboard/
│   │   │   └── overview.go
│   │   ├── dept/
│   │   │   ├── audit_diff.go
│   │   │   ├── audit_diff_test.go
│   │   │   ├── dept.go
│   │   │   ├── dept_mutation.go
│   │   │   └── dept_test.go
│   │   ├── file_object.go
│   │   ├── file_reference.go
│   │   ├── file_reference_test.go
│   │   ├── file_resource.go
│   │   ├── file_resource_folder_upload.go
│   │   ├── file_resource_test.go
│   │   ├── i18n_text.go
│   │   ├── menu/
│   │   │   ├── audit_diff.go
│   │   │   ├── audit_diff_test.go
│   │   │   ├── menu.go
│   │   │   ├── menu_edit.go
│   │   │   ├── menu_query.go
│   │   │   └── menu_test.go
│   │   ├── role/
│   │   │   ├── audit_diff.go
│   │   │   ├── audit_diff_test.go
│   │   │   ├── role.go
│   │   │   ├── role_mutation.go
│   │   │   └── role_test.go
│   │   ├── storage_config.go
│   │   ├── sys_base.go
│   │   ├── sys_config/
│   │   │   ├── audit_diff.go
│   │   │   ├── audit_request_body.go
│   │   │   ├── cache.go
│   │   │   ├── cache_sync.go
│   │   │   ├── cache_sync_test.go
│   │   │   ├── runtime_audit.go
│   │   │   ├── runtime_audit_test.go
│   │   │   ├── sys_config.go
│   │   │   ├── sys_config_mask_test.go
│   │   │   ├── typed_value.go
│   │   │   └── typed_value_test.go
│   │   ├── sys_dict/
│   │   │   ├── audit_diff.go
│   │   │   └── sys_dict.go
│   │   ├── system/
│   │   │   ├── init.go
│   │   │   ├── migration_runner.go
│   │   │   ├── reset.go
│   │   │   ├── reset_path_test.go
│   │   │   └── reset_test.go
│   │   └── taskcenter/
│   │       ├── action.go
│   │       ├── action_test.go
│   │       ├── audit_diff.go
│   │       ├── audit_diff_test.go
│   │       ├── list_helpers.go
│   │       ├── query.go
│   │       ├── recorder.go
│   │       └── recorder_test.go
│   └── validator/
│       ├── binding.go
│       ├── binding_i18n_test.go
│       ├── form/
│       │   ├── admin_user.go
│       │   ├── admin_user_test.go
│       │   ├── auth.go
│       │   ├── common.go
│       │   ├── dept.go
│       │   ├── file_resource.go
│       │   ├── file_resource_test.go
│       │   ├── id_array_validation_test.go
│       │   ├── login_log.go
│       │   ├── menu.go
│       │   ├── menu_test.go
│       │   ├── permission.go
│       │   ├── permission_test.go
│       │   ├── request_log.go
│       │   ├── request_log_test.go
│       │   ├── role.go
│       │   ├── role_test.go
│       │   ├── session.go
│       │   ├── session_test.go
│       │   ├── storage_config.go
│       │   ├── storage_config_test.go
│       │   ├── sys_config.go
│       │   ├── sys_config_test.go
│       │   ├── sys_dict.go
│       │   ├── sys_dict_test.go
│       │   ├── task_center.go
│       │   └── task_center_test.go
│       ├── rules.go
│       ├── runtime.go
│       ├── translation.go
│       └── validator_test.go
├── main.go
├── pkg/
│   ├── convert/
│   │   └── convert.go
│   └── utils/
│       ├── captcha/
│       │   └── captcha.go
│       ├── crypto/
│       │   ├── README.md
│       │   ├── crypto.go
│       │   ├── crypto_aes.go
│       │   └── types.go
│       ├── helpers.go
│       ├── helpers_test.go
│       ├── http.go
│       ├── http_test.go
│       ├── upload.go
│       ├── upload_test.go
│       ├── utils.go
│       └── utils_test.go
├── policy.csv
├── rbac_model.conf
└── tests/
    ├── README.md
    ├── admin_test/
    │   ├── README.md
    │   ├── admin_test.go
    │   ├── admin_user_test.go
    │   ├── auth_routes_test.go
    │   ├── common_routes_test.go
    │   ├── department_test.go
    │   ├── log_routes_test.go
    │   ├── menu_test.go
    │   ├── permission_routes_test.go
    │   ├── public_routes_test.go
    │   ├── role_test.go
    │   ├── system_routes_test.go
    │   ├── task_routes_test.go
    │   └── test_helpers_test.go
    ├── ping_test.go
    └── test.go
Download .txt
Showing preview only (281K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (2815 symbols across 360 files)

FILE: cmd/bootstrapx/bootstrap.go
  constant errorLoadingLocation (line 20) | errorLoadingLocation = "Error loading location: %v"
  type Requirements (line 23) | type Requirements struct
  function InitializeConfig (line 37) | func InitializeConfig(configPath string) error {
  function InitializeTimezone (line 42) | func InitializeTimezone() {
  function InitializeLogger (line 60) | func InitializeLogger() error {
  function InitializeData (line 65) | func InitializeData() error {
  function InitializeValidator (line 81) | func InitializeValidator() error {
  function WrapCommand (line 86) | func WrapCommand(cmd *cobra.Command, req Requirements) *cobra.Command {
  function InitializeQueue (line 126) | func InitializeQueue() error {
  function shouldAllowDegradedStartup (line 137) | func shouldAllowDegradedStartup(req Requirements) bool {
  function logDependencyInitWarning (line 145) | func logDependencyInitWarning(cmd *cobra.Command, dependency string, err...

FILE: cmd/bootstrapx/bootstrap_test.go
  function TestWrapCommandReturnsDataErrorWhenDegradedStartupDisabled (line 14) | func TestWrapCommandReturnsDataErrorWhenDegradedStartupDisabled(t *testi...
  function TestWrapCommandAllowsDataAndQueueErrorsWhenEnabled (line 36) | func TestWrapCommandAllowsDataAndQueueErrorsWhenEnabled(t *testing.T) {
  function TestWrapCommandKeepsStrictModeForCommandsWithoutOptIn (line 85) | func TestWrapCommandKeepsStrictModeForCommandsWithoutOptIn(t *testing.T) {
  function TestWrapCommandKeepsValidatorStrictEvenWhenDegradedStartupEnabled (line 106) | func TestWrapCommandKeepsValidatorStrictEvenWhenDegradedStartupEnabled(t...
  function stubBootstrapInitializers (line 135) | func stubBootstrapInitializers(dataFn, validatorFn, queueFn func() error...
  function setAllowDegradedStartup (line 150) | func setAllowDegradedStartup(t *testing.T, enabled bool) func() {

FILE: cmd/command/command.go
  function init (line 22) | func init() {
  function registerSubCommands (line 27) | func registerSubCommands() {

FILE: cmd/completion.go
  function init (line 39) | func init() {

FILE: cmd/cron/cron.go
  function Start (line 31) | func Start() error {
  function newScheduler (line 57) | func newScheduler() (*cron.Cron, error) {
  function waitForShutdown (line 70) | func waitForShutdown() {
  function handleSignals (line 79) | func handleSignals(cancel context.CancelFunc) {
  type cronLogger (line 89) | type cronLogger struct
    method Info (line 92) | func (cl *cronLogger) Info(msg string, keysAndValues ...interface{}) {
    method Error (line 101) | func (cl *cronLogger) Error(err error, msg string, keysAndValues ...in...

FILE: cmd/cron/schedule.go
  type Scheduler (line 19) | type Scheduler struct
    method Call (line 63) | func (s *Scheduler) Call(name string, fn func()) *TaskBuilder {
    method CallE (line 74) | func (s *Scheduler) CallE(name string, fn func() error) *TaskBuilder {
    method Register (line 119) | func (s *Scheduler) Register(crontab *cron.Cron) error {
    method registerTask (line 128) | func (s *Scheduler) registerTask(crontab *cron.Cron, task *scheduledTa...
    method recordedJob (line 156) | func (s *Scheduler) recordedJob(task *scheduledTask) cron.Job {
  type scheduledTask (line 24) | type scheduledTask struct
  type TaskBuilder (line 33) | type TaskBuilder struct
    method Cron (line 85) | func (b *TaskBuilder) Cron(spec string) *TaskBuilder {
    method EveryFiveSeconds (line 91) | func (b *TaskBuilder) EveryFiveSeconds() *TaskBuilder {
    method DailyAt (line 96) | func (b *TaskBuilder) DailyAt(value string) *TaskBuilder {
    method WithoutOverlapping (line 107) | func (b *TaskBuilder) WithoutOverlapping() *TaskBuilder {
    method AllowOverlap (line 113) | func (b *TaskBuilder) AllowOverlap() *TaskBuilder {
  function registerTasks (line 47) | func registerTasks(crontab *cron.Cron) error {
  function NewSchedule (line 55) | func NewSchedule(logger *cronLogger) *Scheduler {
  function calculateNextRunAt (line 204) | func calculateNextRunAt(spec string, base time.Time) (*time.Time, error) {
  function dailyAtSpec (line 222) | func dailyAtSpec(value string) (string, error) {
  function parseTimePart (line 254) | func parseTimePart(name, raw string, min, max int) (int, error) {

FILE: cmd/cron/schedule_test.go
  function TestDailyAtSpecWithHHMM (line 10) | func TestDailyAtSpecWithHHMM(t *testing.T) {
  function TestDailyAtSpecWithHHMMSS (line 20) | func TestDailyAtSpecWithHHMMSS(t *testing.T) {
  function TestDailyAtSpecRejectsInvalidInput (line 30) | func TestDailyAtSpecRejectsInvalidInput(t *testing.T) {
  function TestDailyAtSpecRejectsOutOfRange (line 37) | func TestDailyAtSpecRejectsOutOfRange(t *testing.T) {
  function TestSchedulerRegisterReturnsDailyAtError (line 44) | func TestSchedulerRegisterReturnsDailyAtError(t *testing.T) {

FILE: cmd/cron/task_record_test.go
  function TestRecordedJobRecordsCallEFailure (line 13) | func TestRecordedJobRecordsCallEFailure(t *testing.T) {
  function TestCalculateNextRunAtInvalidSpec (line 51) | func TestCalculateNextRunAtInvalidSpec(t *testing.T) {
  type fakeCronRunRecorder (line 61) | type fakeCronRunRecorder struct
    method Enqueue (line 66) | func (f *fakeCronRunRecorder) Enqueue(ctx context.Context, input taskc...
    method Start (line 72) | func (f *fakeCronRunRecorder) Start(ctx context.Context, input taskcen...
    method Finish (line 78) | func (f *fakeCronRunRecorder) Finish(ctx context.Context, run *model.T...

FILE: cmd/cron/tasks.go
  function defineSchedule (line 15) | func defineSchedule(schedule *Scheduler) {
  function taskNameFromCode (line 41) | func taskNameFromCode(code string) string {

FILE: cmd/cron/tasks_test.go
  function TestDefineScheduleSkipsResetTaskByDefault (line 9) | func TestDefineScheduleSkipsResetTaskByDefault(t *testing.T) {
  function TestDefineScheduleRegistersResetTaskWhenEnabled (line 26) | func TestDefineScheduleRegistersResetTaskWhenEnabled(t *testing.T) {
  function hasScheduledTask (line 40) | func hasScheduledTask(schedule *Scheduler, name string) bool {

FILE: cmd/root.go
  constant welcomeMessage (line 22) | welcomeMessage = "Welcome to go-layout. Use -h to see more commands"
  function init (line 53) | func init() {
  function registerFlags (line 60) | func registerFlags() {
  function shouldSkipBootstrap (line 64) | func shouldSkipBootstrap(cmd *cobra.Command) bool {
  function registerCommands (line 82) | func registerCommands() {
  function Execute (line 92) | func Execute() {

FILE: cmd/service/service.go
  constant defaultHost (line 23) | defaultHost            = "0.0.0.0"
  constant defaultPort (line 24) | defaultPort            = 9001
  constant gracefulShutdownTimout (line 25) | gracefulShutdownTimout = 10 * time.Second
  function init (line 41) | func init() {
  function registerFlags (line 46) | func registerFlags() {
  function run (line 52) | func run() error {
  function waitForShutdown (line 75) | func waitForShutdown(server *http.Server, errChan <-chan error) error {

FILE: cmd/worker/worker.go
  function run (line 27) | func run() error {

FILE: config/autoload/app.go
  type AppConfig (line 8) | type AppConfig struct
  function getDefaultPath (line 70) | func getDefaultPath() (path string) {

FILE: config/autoload/jwt.go
  type JwtConfig (line 6) | type JwtConfig struct

FILE: config/autoload/logger.go
  type DivisionTime (line 4) | type DivisionTime struct
  type DivisionSize (line 12) | type DivisionSize struct
  type LoggerConfig (line 24) | type LoggerConfig struct

FILE: config/autoload/mysql.go
  type MysqlConfig (line 6) | type MysqlConfig struct

FILE: config/autoload/queue.go
  type QueueRedisConfig (line 4) | type QueueRedisConfig struct
  type QueueConfig (line 16) | type QueueConfig struct

FILE: config/autoload/redis.go
  type RedisConfig (line 6) | type RedisConfig struct

FILE: config/config.go
  type Conf (line 13) | type Conf struct
  type ConfigReloadHandler (line 41) | type ConfigReloadHandler struct
  type ConfigDiff (line 48) | type ConfigDiff struct
  function GetConfig (line 63) | func GetConfig() *Conf {
  function RegisterConfigReloadHandler (line 71) | func RegisterConfigReloadHandler(handler ConfigReloadHandler) {
  function sortConfigReloadHandlersLocked (line 91) | func sortConfigReloadHandlersLocked() {

FILE: config/config_clone.go
  function setActiveConfig (line 11) | func setActiveConfig(cfg *Conf) {
  function cloneDefaultConfig (line 16) | func cloneDefaultConfig() *Conf {
  function cloneAppConfig (line 27) | func cloneAppConfig(src autoload.AppConfig) autoload.AppConfig {
  function cloneQueueConfig (line 41) | func cloneQueueConfig(src autoload.QueueConfig) autoload.QueueConfig {
  function cloneStringSlice (line 52) | func cloneStringSlice(src []string) []string {
  function copyConf (line 60) | func copyConf(exampleConfig, config string) error {

FILE: config/config_load.go
  function InitConfig (line 16) | func InitConfig(configPath string) error {
  function checkJwtSecretKey (line 32) | func checkJwtSecretKey() error {
  function validateJWTSecretKey (line 36) | func validateJWTSecretKey(cfg *Conf) error {
  function load (line 69) | func load(configPath string) (*Conf, error) {
  function resolveConfigPath (line 97) | func resolveConfigPath(configPath string) (string, error) {
  function resolveDefaultConfigFiles (line 112) | func resolveDefaultConfigFiles() (string, string, error) {
  function resolveDevelopmentConfigFiles (line 124) | func resolveDevelopmentConfigFiles() (string, string, error) {
  function fileExists (line 137) | func fileExists(path string) bool {
  function ensureBasePathDefault (line 148) | func ensureBasePathDefault(cfg *Conf, basePathConfigured bool) {
  function registerConfigWatcherIfNeeded (line 181) | func registerConfigWatcherIfNeeded(cfg *Conf) {
  function ensureCorsDefaults (line 192) | func ensureCorsDefaults(cfg *Conf) {
  function reloadConfigFromWatcher (line 213) | func reloadConfigFromWatcher() error {
  function resolveEnvVars (line 250) | func resolveEnvVars(cfg *Conf) {
  function resolveEnvVar (line 261) | func resolveEnvVar(val string) string {

FILE: config/config_load_test.go
  function assertSamePath (line 9) | func assertSamePath(t *testing.T, expected string, actual string) {
  function TestResolveConfigPathPrefersWorkingDirectoryConfig (line 24) | func TestResolveConfigPathPrefersWorkingDirectoryConfig(t *testing.T) {
  function TestResolveDefaultConfigFilesUsesWorkingDirectoryExample (line 51) | func TestResolveDefaultConfigFilesUsesWorkingDirectoryExample(t *testing...
  function TestEnsureBasePathDefaultUsesWorkingDirectoryWhenNotConfigured (line 87) | func TestEnsureBasePathDefaultUsesWorkingDirectoryWhenNotConfigured(t *t...
  function TestEnsureBasePathDefaultKeepsExplicitConfiguredValue (line 108) | func TestEnsureBasePathDefaultKeepsExplicitConfiguredValue(t *testing.T) {
  function TestValidateJWTSecretKeyRejectsNilConfig (line 117) | func TestValidateJWTSecretKeyRejectsNilConfig(t *testing.T) {
  function TestCheckJwtSecretKeyRejectsEmptySecret (line 123) | func TestCheckJwtSecretKeyRejectsEmptySecret(t *testing.T) {
  function TestCheckJwtSecretKeyRejectsWeakProdSecret (line 135) | func TestCheckJwtSecretKeyRejectsWeakProdSecret(t *testing.T) {
  function TestCheckJwtSecretKeyAllowsLocalWeakSecret (line 148) | func TestCheckJwtSecretKeyAllowsLocalWeakSecret(t *testing.T) {

FILE: config/config_test.go
  function resetConfigReloadHandlersForTest (line 5) | func resetConfigReloadHandlersForTest(t *testing.T) {
  function TestRegisterConfigReloadHandlerDeduplicatesByName (line 12) | func TestRegisterConfigReloadHandlerDeduplicatesByName(t *testing.T) {
  function TestRegisterConfigReloadHandlerKeepsStablePriorityOrder (line 29) | func TestRegisterConfigReloadHandlerKeepsStablePriorityOrder(t *testing....
  function TestRegisterConfigReloadHandlerIgnoresEmptyName (line 52) | func TestRegisterConfigReloadHandlerIgnoresEmptyName(t *testing.T) {

FILE: config/provider.go
  function GetConfigFrom (line 4) | func GetConfigFrom(provider func() *Conf) *Conf {

FILE: config/runtime.go
  function BuildConfigDiff (line 6) | func BuildConfigDiff(oldConfig, newConfig *Conf) ConfigDiff {
  function BuildAppliedConfig (line 71) | func BuildAppliedConfig(oldConfig, newConfig *Conf, diff ConfigDiff) *Co...

FILE: config/runtime_test.go
  function TestBuildConfigDiff (line 9) | func TestBuildConfigDiff(t *testing.T) {
  function TestBuildAppliedConfigKeepsUnsupportedFields (line 61) | func TestBuildAppliedConfigKeepsUnsupportedFields(t *testing.T) {

FILE: config/testing_helper.go
  function CloneConf (line 4) | func CloneConf(src *Conf) *Conf {
  function ReplaceConfigForTesting (line 16) | func ReplaceConfigForTesting(cfg *Conf) func() {
  function UpdateConfigForTesting (line 31) | func UpdateConfigForTesting(mutator func(cfg *Conf)) func() {

FILE: data/data.go
  function InitData (line 15) | func InitData() error {
  function Shutdown (line 41) | func Shutdown() error {

FILE: data/data_test.go
  function TestShutdownWithoutInitializedResources (line 5) | func TestShutdownWithoutInitializedResources(t *testing.T) {

FILE: data/migrations/20260425000001_init_table.up.sql
  type `admin_user` (line 4) | CREATE TABLE IF NOT EXISTS `admin_user`
  type `api_group` (line 34) | CREATE TABLE IF NOT EXISTS `api_group`
  type `api` (line 50) | CREATE TABLE IF NOT EXISTS `api`
  type `menu` (line 79) | CREATE TABLE IF NOT EXISTS `menu`
  type `menu_i18n` (line 121) | CREATE TABLE IF NOT EXISTS `menu_i18n`
  type `department` (line 138) | CREATE TABLE IF NOT EXISTS `department`
  type `role` (line 166) | CREATE TABLE IF NOT EXISTS `role`
  type `admin_user_department_map` (line 195) | CREATE TABLE IF NOT EXISTS `admin_user_department_map`
  type `menu_api_map` (line 212) | CREATE TABLE IF NOT EXISTS `menu_api_map`
  type `role_menu_map` (line 232) | CREATE TABLE IF NOT EXISTS `role_menu_map`
  type `admin_user_menu_map` (line 248) | CREATE TABLE IF NOT EXISTS `admin_user_menu_map`
  type `department_role_map` (line 264) | CREATE TABLE IF NOT EXISTS `department_role_map`
  type `admin_user_role_map` (line 280) | CREATE TABLE IF NOT EXISTS `admin_user_role_map`
  type `admin_login_logs` (line 296) | CREATE TABLE IF NOT EXISTS `admin_login_logs`
  type `login_security_state` (line 334) | CREATE TABLE IF NOT EXISTS `login_security_state`
  type `request_logs` (line 351) | CREATE TABLE IF NOT EXISTS `request_logs`
  type `casbin_rule` (line 393) | CREATE TABLE IF NOT EXISTS `casbin_rule`
  type `upload_files` (line 409) | CREATE TABLE IF NOT EXISTS `upload_files`
  type `upload_file_folders` (line 455) | CREATE TABLE IF NOT EXISTS `upload_file_folders`
  type `upload_file_references` (line 475) | CREATE TABLE IF NOT EXISTS `upload_file_references`
  type `sys_config` (line 494) | CREATE TABLE IF NOT EXISTS `sys_config`
  type `sys_dict_type` (line 520) | CREATE TABLE IF NOT EXISTS `sys_dict_type`
  type `sys_dict_item` (line 538) | CREATE TABLE IF NOT EXISTS `sys_dict_item`
  type `sys_config_i18n` (line 561) | CREATE TABLE IF NOT EXISTS `sys_config_i18n`
  type `sys_dict_type_i18n` (line 576) | CREATE TABLE IF NOT EXISTS `sys_dict_type_i18n`
  type `sys_dict_item_i18n` (line 591) | CREATE TABLE IF NOT EXISTS `sys_dict_item_i18n`
  type `task_definitions` (line 606) | CREATE TABLE IF NOT EXISTS `task_definitions`
  type `task_runs` (line 630) | CREATE TABLE IF NOT EXISTS `task_runs`
  type `task_run_events` (line 659) | CREATE TABLE IF NOT EXISTS `task_run_events`
  type `cron_task_states` (line 675) | CREATE TABLE IF NOT EXISTS `cron_task_states`

FILE: data/migrations/20260515010000_upload_file_objects.up.sql
  type `upload_file_objects` (line 1) | CREATE TABLE IF NOT EXISTS `upload_file_objects`

FILE: data/mysql.go
  type mysqlSlot (line 33) | type mysqlSlot struct
  constant mysqlProbeTimeout (line 37) | mysqlProbeTimeout = 2 * time.Second
  type Writer (line 50) | type Writer interface
  type WriterLog (line 55) | type WriterLog struct
    method Printf (line 58) | func (w WriterLog) Printf(format string, args ...interface{}) {
  function GenerateDSN (line 65) | func GenerateDSN(cfg *c.Conf) string {
  function initMysql (line 105) | func initMysql() error {
  function reloadMysql (line 109) | func reloadMysql(cfg *c.Conf) error {
  function openMysql (line 136) | func openMysql(cfg *c.Conf) (*gorm.DB, error) {
  function MysqlDB (line 190) | func MysqlDB() *gorm.DB {
  function MysqlInitError (line 203) | func MysqlInitError() error {
  function MysqlRuntimeStatus (line 208) | func MysqlRuntimeStatus() RuntimeHealthStatus {
  function MysqlReady (line 228) | func MysqlReady() bool {
  function ReloadMysql (line 233) | func ReloadMysql(cfg *c.Conf) error {
  function CloseMysql (line 238) | func CloseMysql() error {
  function currentMysql (line 258) | func currentMysql() *gorm.DB {
  function getSQLDB (line 265) | func getSQLDB(db *gorm.DB) *sql.DB {
  function mysqlUnavailableError (line 276) | func mysqlUnavailableError() error {

FILE: data/redis.go
  type redisSlot (line 24) | type redisSlot struct
  constant redisProbeTimeout (line 28) | redisProbeTimeout = 2 * time.Second
  function initRedis (line 36) | func initRedis() error {
  function reloadRedis (line 40) | func reloadRedis(cfg *c.Conf) error {
  function openRedis (line 63) | func openRedis(cfg *c.Conf) (*redis.Client, error) {
  function RedisClient (line 93) | func RedisClient() *redis.Client {
  function GetRedisInitError (line 106) | func GetRedisInitError() error {
  function RedisRuntimeStatus (line 111) | func RedisRuntimeStatus() RuntimeHealthStatus {
  function RedisReady (line 131) | func RedisReady() bool {
  function ReloadRedis (line 136) | func ReloadRedis(cfg *c.Conf) error {
  function CloseRedis (line 141) | func CloseRedis() error {
  function currentRedis (line 156) | func currentRedis() *redis.Client {
  function redisUnavailableError (line 163) | func redisUnavailableError() error {

FILE: data/runtime_health.go
  constant defaultRuntimeHealthTTL (line 8) | defaultRuntimeHealthTTL = 3 * time.Second
  type RuntimeHealthStatus (line 11) | type RuntimeHealthStatus struct
  type runtimeHealthCache (line 17) | type runtimeHealthCache struct
    method Check (line 30) | func (c *runtimeHealthCache) Check(probe func() error) RuntimeHealthSt...
    method SeedReady (line 49) | func (c *runtimeHealthCache) SeedReady() {
    method Reset (line 58) | func (c *runtimeHealthCache) Reset() {
  function newRuntimeHealthCache (line 23) | func newRuntimeHealthCache(ttl time.Duration) *runtimeHealthCache {

FILE: data/runtime_health_test.go
  function TestMysqlRuntimeStatusCachesProbeResult (line 13) | func TestMysqlRuntimeStatusCachesProbeResult(t *testing.T) {
  function TestMysqlRuntimeStatusReturnsProbeFailure (line 51) | func TestMysqlRuntimeStatusReturnsProbeFailure(t *testing.T) {
  function TestRedisRuntimeStatusCachesProbeResult (line 84) | func TestRedisRuntimeStatusCachesProbeResult(t *testing.T) {
  function TestRedisRuntimeStatusReturnsProbeFailure (line 120) | func TestRedisRuntimeStatusReturnsProbeFailure(t *testing.T) {
  function backupMysqlState (line 151) | func backupMysqlState() func() {
  function backupRedisState (line 167) | func backupRedisState() func() {

FILE: internal/access/casbin/adapter.go
  function newAdapter (line 10) | func newAdapter(db *gorm.DB) (*gormadapter.Adapter, error) {
  function newEnforcerFromDB (line 15) | func newEnforcerFromDB(m model.Model, db *gorm.DB) (*casbin.Enforcer, er...

FILE: internal/access/casbin/casbin.go
  type CasbinEnforcer (line 14) | type CasbinEnforcer struct
    method SetDB (line 64) | func (e *CasbinEnforcer) SetDB(tx *gorm.DB) *CasbinEnforcer {
    method registerCustomFunctions (line 74) | func (e *CasbinEnforcer) registerCustomFunctions() {
  function InitEnforcer (line 27) | func InitEnforcer() error {
  function GetEnforcer (line 37) | func GetEnforcer() (*CasbinEnforcer, error) {
  function ReloadPolicy (line 55) | func ReloadPolicy() error {

FILE: internal/access/casbin/enforcer_init.go
  function getModelPath (line 17) | func getModelPath() (string, error) {
  function isInTransaction (line 26) | func isInTransaction(db *gorm.DB) bool {
  function ReloadEnforcer (line 31) | func ReloadEnforcer() error {
  function initEnforcerLocked (line 37) | func initEnforcerLocked() error {

FILE: internal/access/casbin/policy_ops.go
  method execute (line 11) | func (e *CasbinEnforcer) execute(tx *gorm.DB, fn func(enforcer casbin.IE...
  method EditPolicyPermissions (line 30) | func (e *CasbinEnforcer) EditPolicyPermissions(user string, policy [][]s...
  method EditPolicyPermissionsBatch (line 37) | func (e *CasbinEnforcer) EditPolicyPermissionsBatch(subjectPolicies map[...
  method EditPolicyRoles (line 49) | func (e *CasbinEnforcer) EditPolicyRoles(user string, policy []string, t...
  method WithTransaction (line 78) | func (e *CasbinEnforcer) WithTransaction(tx *gorm.DB, fn func(enforcer c...
  function firstTx (line 83) | func firstTx(tx []*gorm.DB) *gorm.DB {
  function replacePermissions (line 90) | func replacePermissions(enforcer casbin.IEnforcer, subject string, polic...
  function toSubjectPolicies (line 110) | func toSubjectPolicies(subject string, policy [][]string) [][]string {

FILE: internal/console/confirm.go
  function ConfirmOperation (line 14) | func ConfirmOperation(prompt string, assumeYes bool) bool {

FILE: internal/console/demo/demo.go
  function init (line 22) | func init() {
  function demo (line 26) | func demo() {

FILE: internal/console/init/init.go
  constant msgProcessingComplete (line 15) | msgProcessingComplete     = "Processing complete."
  constant msgFailedToSaveRoute (line 16) | msgFailedToSaveRoute      = "Failed to save the initial route data to th...
  constant msgUserPermissionComplete (line 17) | msgUserPermissionComplete = "User API permissions rebuilt successfully."
  constant msgFailedToRebuildPerms (line 18) | msgFailedToRebuildPerms   = "Failed to rebuild final user API permissions."
  function init (line 44) | func init() {
  function runInitApiRoute (line 50) | func runInitApiRoute() error {
  function runRebuildUserPermissions (line 69) | func runRebuildUserPermissions() error {

FILE: internal/console/migrate/migrate.go
  constant defaultMigrationDir (line 22) | defaultMigrationDir    = "data/migrations"
  constant defaultMigrationExt (line 23) | defaultMigrationExt    = "sql"
  constant defaultTimeFormat (line 24) | defaultTimeFormat      = "20060102150405"
  constant defaultMigrationDigits (line 25) | defaultMigrationDigits = 6
  function init (line 144) | func init() {
  function registerFlags (line 149) | func registerFlags() {
  function registerSubCommands (line 164) | func registerSubCommands() {
  function runCreate (line 174) | func runCreate(rawName string) error {
  function runCheck (line 221) | func runCheck() error {
  function runUp (line 266) | func runUp(args []string) error {
  function runDown (line 302) | func runDown(args []string) error {
  function runWithMigrator (line 349) | func runWithMigrator(fn func(*migrate.Migrate) error) error {
  function buildMigrator (line 359) | func buildMigrator() (*migrate.Migrate, error) {
  function resolveMigrationDirForCreate (line 367) | func resolveMigrationDirForCreate() (string, error) {
  function resolveMigrationDirForCheck (line 377) | func resolveMigrationDirForCheck() (string, error) {
  function ensureDirExists (line 388) | func ensureDirExists(path string) (string, error) {
  function normalizeMigrationName (line 407) | func normalizeMigrationName(raw string) string {
  function nextMigrationVersion (line 417) | func nextMigrationVersion(files []migrationFile) (string, error) {
  type migrationFile (line 451) | type migrationFile struct
  function loadMigrationFiles (line 457) | func loadMigrationFiles(dir string, strict bool) ([]migrationFile, error) {
  function parseIntArg (line 493) | func parseIntArg(value string, argName string) (int, error) {
  function parseUint64Arg (line 501) | func parseUint64Arg(value string, argName string) (uint, error) {
  function remapLegacySequentialVersionIfNeeded (line 512) | func remapLegacySequentialVersionIfNeeded(m *migrate.Migrate) error {

FILE: internal/console/system_init/system_init.go
  function init (line 34) | func init() {
  function runInitSystem (line 39) | func runInitSystem() error {

FILE: internal/console/task/task.go
  type asyncScanRow (line 19) | type asyncScanRow struct
  function init (line 49) | func init() {
  function runScanAsync (line 54) | func runScanAsync() error {
  function runScanCron (line 73) | func runScanCron() error {
  function collectRegistryTaskTypes (line 92) | func collectRegistryTaskTypes(registry queue.Registry) []string {
  function collectBuiltinDefinitionsByKind (line 113) | func collectBuiltinDefinitionsByKind(kind string) map[string]model.TaskD...
  function sortedDefinitionCodes (line 129) | func sortedDefinitionCodes(definitions map[string]model.TaskDefinition) ...
  function loadDBDefinitionsByKind (line 138) | func loadDBDefinitionsByKind(kind string) (map[string]model.TaskDefiniti...
  function buildAsyncScanRows (line 163) | func buildAsyncScanRows(taskTypes []string, builtinMap map[string]model....
  function printScanRows (line 191) | func printScanRows(rows []asyncScanRow, dbReady bool) {
  function printScanSummary (line 210) | func printScanSummary(sourceLabel string, taskTypes []string, rows []asy...
  function contains (line 266) | func contains(values []string, target string) bool {
  function yesNo (line 275) | func yesNo(value bool) string {

FILE: internal/console/task/task_test.go
  function TestBuildAsyncScanRowsMarksMissingDefinitions (line 9) | func TestBuildAsyncScanRowsMarksMissingDefinitions(t *testing.T) {
  function TestBuildAsyncScanRowsSkipsDBStateWhenDBNotReady (line 36) | func TestBuildAsyncScanRowsSkipsDBStateWhenDBNotReady(t *testing.T) {
  function TestSortedDefinitionCodes (line 60) | func TestSortedDefinitionCodes(t *testing.T) {

FILE: internal/controller/admin_v1/auth.go
  type LoginController (line 18) | type LoginController struct
    method Login (line 28) | func (api LoginController) Login(c *gin.Context) {
    method LoginCaptcha (line 69) | func (api LoginController) LoginCaptcha(c *gin.Context) {
    method Logout (line 80) | func (api LoginController) Logout(c *gin.Context) {
    method CheckToken (line 102) | func (api LoginController) CheckToken(c *gin.Context) {
  function NewLoginController (line 23) | func NewLoginController() *LoginController {

FILE: internal/controller/admin_v1/auth_admin_user.go
  type AdminUserController (line 14) | type AdminUserController struct
    method GetUserInfo (line 24) | func (api AdminUserController) GetUserInfo(c *gin.Context) {
    method UpdateProfile (line 35) | func (api AdminUserController) UpdateProfile(c *gin.Context) {
    method GetUserMenuInfo (line 52) | func (api AdminUserController) GetUserMenuInfo(c *gin.Context) {
    method Create (line 64) | func (api AdminUserController) Create(c *gin.Context) {
    method Update (line 80) | func (api AdminUserController) Update(c *gin.Context) {
    method List (line 96) | func (api AdminUserController) List(c *gin.Context) {
    method Delete (line 107) | func (api AdminUserController) Delete(c *gin.Context) {
    method BindRole (line 123) | func (api AdminUserController) BindRole(c *gin.Context) {
    method Detail (line 139) | func (api AdminUserController) Detail(c *gin.Context) {
    method GetFullPhone (line 155) | func (api AdminUserController) GetFullPhone(c *gin.Context) {
    method GetFullEmail (line 173) | func (api AdminUserController) GetFullEmail(c *gin.Context) {
  function NewAdminUserController (line 19) | func NewAdminUserController() *AdminUserController {

FILE: internal/controller/admin_v1/auth_api.go
  type ApiController (line 14) | type ApiController struct
    method Update (line 24) | func (api ApiController) Update(c *gin.Context) {
    method List (line 40) | func (api ApiController) List(c *gin.Context) {
  function NewApiController (line 19) | func NewApiController() *ApiController {

FILE: internal/controller/admin_v1/auth_dept.go
  type DeptController (line 14) | type DeptController struct
    method List (line 24) | func (api DeptController) List(c *gin.Context) {
    method Create (line 35) | func (api DeptController) Create(c *gin.Context) {
    method Update (line 51) | func (api DeptController) Update(c *gin.Context) {
    method Delete (line 67) | func (api DeptController) Delete(c *gin.Context) {
    method Detail (line 83) | func (api DeptController) Detail(c *gin.Context) {
    method BindRole (line 99) | func (api DeptController) BindRole(c *gin.Context) {
  function NewDeptController (line 19) | func NewDeptController() *DeptController {

FILE: internal/controller/admin_v1/auth_file_resource.go
  type FileResourceController (line 16) | type FileResourceController struct
    method List (line 25) | func (api FileResourceController) List(c *gin.Context) {
    method Detail (line 36) | func (api FileResourceController) Detail(c *gin.Context) {
    method Delete (line 51) | func (api FileResourceController) Delete(c *gin.Context) {
    method TrashList (line 74) | func (api FileResourceController) TrashList(c *gin.Context) {
    method Restore (line 85) | func (api FileResourceController) Restore(c *gin.Context) {
    method Destroy (line 97) | func (api FileResourceController) Destroy(c *gin.Context) {
    method References (line 109) | func (api FileResourceController) References(c *gin.Context) {
    method FolderTree (line 118) | func (api FileResourceController) FolderTree(c *gin.Context) {
    method FolderCreate (line 127) | func (api FileResourceController) FolderCreate(c *gin.Context) {
    method FolderUpdate (line 140) | func (api FileResourceController) FolderUpdate(c *gin.Context) {
    method FolderDelete (line 153) | func (api FileResourceController) FolderDelete(c *gin.Context) {
    method FolderMove (line 165) | func (api FileResourceController) FolderMove(c *gin.Context) {
    method Move (line 178) | func (api FileResourceController) Move(c *gin.Context) {
    method UploadLocal (line 191) | func (api FileResourceController) UploadLocal(c *gin.Context) {
    method UploadCredential (line 210) | func (api FileResourceController) UploadCredential(c *gin.Context) {
    method UploadComplete (line 223) | func (api FileResourceController) UploadComplete(c *gin.Context) {
  function NewFileResourceController (line 20) | func NewFileResourceController() *FileResourceController {

FILE: internal/controller/admin_v1/auth_login_log.go
  type AdminLoginLogController (line 13) | type AdminLoginLogController struct
    method List (line 23) | func (api AdminLoginLogController) List(c *gin.Context) {
    method Detail (line 34) | func (api AdminLoginLogController) Detail(c *gin.Context) {
  function NewAdminLoginLogController (line 18) | func NewAdminLoginLogController() *AdminLoginLogController {

FILE: internal/controller/admin_v1/auth_menu.go
  type MenuController (line 15) | type MenuController struct
    method Create (line 25) | func (api MenuController) Create(c *gin.Context) {
    method Update (line 41) | func (api MenuController) Update(c *gin.Context) {
    method UpdateAllMenuPermissions (line 57) | func (api MenuController) UpdateAllMenuPermissions(c *gin.Context) {
    method Detail (line 72) | func (api MenuController) Detail(c *gin.Context) {
    method List (line 88) | func (api MenuController) List(c *gin.Context) {
    method Delete (line 98) | func (api MenuController) Delete(c *gin.Context) {
  function NewMenuController (line 20) | func NewMenuController() *MenuController {

FILE: internal/controller/admin_v1/auth_request_log.go
  type RequestLogController (line 14) | type RequestLogController struct
    method List (line 24) | func (api RequestLogController) List(c *gin.Context) {
    method Detail (line 35) | func (api RequestLogController) Detail(c *gin.Context) {
    method Export (line 51) | func (api RequestLogController) Export(c *gin.Context) {
    method MaskConfig (line 68) | func (api RequestLogController) MaskConfig(c *gin.Context) {
    method UpdateMaskConfig (line 74) | func (api RequestLogController) UpdateMaskConfig(c *gin.Context) {
  function NewRequestLogController (line 19) | func NewRequestLogController() *RequestLogController {

FILE: internal/controller/admin_v1/auth_role.go
  type RoleController (line 14) | type RoleController struct
    method List (line 24) | func (api RoleController) List(c *gin.Context) {
    method Create (line 35) | func (api RoleController) Create(c *gin.Context) {
    method Update (line 51) | func (api RoleController) Update(c *gin.Context) {
    method Delete (line 67) | func (api RoleController) Delete(c *gin.Context) {
    method Detail (line 83) | func (api RoleController) Detail(c *gin.Context) {
  function NewRoleController (line 19) | func NewRoleController() *RoleController {

FILE: internal/controller/admin_v1/auth_session.go
  type SessionController (line 13) | type SessionController struct
    method List (line 22) | func (api SessionController) List(c *gin.Context) {
    method Revoke (line 33) | func (api SessionController) Revoke(c *gin.Context) {
  function NewSessionController (line 17) | func NewSessionController() *SessionController {

FILE: internal/controller/admin_v1/auth_storage_config.go
  type StorageConfigController (line 13) | type StorageConfigController struct
    method Config (line 21) | func (api StorageConfigController) Config(c *gin.Context) {
    method Save (line 30) | func (api StorageConfigController) Save(c *gin.Context) {
    method Test (line 49) | func (api StorageConfigController) Test(c *gin.Context) {
  function NewStorageConfigController (line 17) | func NewStorageConfigController() *StorageConfigController {

FILE: internal/controller/admin_v1/auth_sys_config.go
  type SysConfigController (line 15) | type SysConfigController struct
    method List (line 23) | func (api SysConfigController) List(c *gin.Context) {
    method Detail (line 31) | func (api SysConfigController) Detail(c *gin.Context) {
    method Value (line 44) | func (api SysConfigController) Value(c *gin.Context) {
    method Create (line 57) | func (api SysConfigController) Create(c *gin.Context) {
    method Update (line 73) | func (api SysConfigController) Update(c *gin.Context) {
    method Delete (line 89) | func (api SysConfigController) Delete(c *gin.Context) {
    method Refresh (line 103) | func (api SysConfigController) Refresh(c *gin.Context) {
  function NewSysConfigController (line 19) | func NewSysConfigController() *SysConfigController {

FILE: internal/controller/admin_v1/auth_sys_dict.go
  type SysDictController (line 14) | type SysDictController struct
    method TypeList (line 22) | func (api SysDictController) TypeList(c *gin.Context) {
    method TypeDetail (line 30) | func (api SysDictController) TypeDetail(c *gin.Context) {
    method TypeCreate (line 43) | func (api SysDictController) TypeCreate(c *gin.Context) {
    method TypeUpdate (line 57) | func (api SysDictController) TypeUpdate(c *gin.Context) {
    method TypeDelete (line 71) | func (api SysDictController) TypeDelete(c *gin.Context) {
    method ItemList (line 85) | func (api SysDictController) ItemList(c *gin.Context) {
    method ItemCreate (line 93) | func (api SysDictController) ItemCreate(c *gin.Context) {
    method ItemUpdate (line 107) | func (api SysDictController) ItemUpdate(c *gin.Context) {
    method ItemDelete (line 121) | func (api SysDictController) ItemDelete(c *gin.Context) {
    method Options (line 135) | func (api SysDictController) Options(c *gin.Context) {
  function NewSysDictController (line 18) | func NewSysDictController() *SysDictController {

FILE: internal/controller/admin_v1/auth_task_center.go
  type TaskCenterController (line 14) | type TaskCenterController struct
    method TaskList (line 23) | func (api TaskCenterController) TaskList(c *gin.Context) {
    method RunList (line 34) | func (api TaskCenterController) RunList(c *gin.Context) {
    method RunDetail (line 45) | func (api TaskCenterController) RunDetail(c *gin.Context) {
    method RunEvents (line 60) | func (api TaskCenterController) RunEvents(c *gin.Context) {
    method CronStateList (line 75) | func (api TaskCenterController) CronStateList(c *gin.Context) {
    method Trigger (line 86) | func (api TaskCenterController) Trigger(c *gin.Context) {
    method Retry (line 109) | func (api TaskCenterController) Retry(c *gin.Context) {
    method Cancel (line 133) | func (api TaskCenterController) Cancel(c *gin.Context) {
  function NewTaskCenterController (line 18) | func NewTaskCenterController() *TaskCenterController {

FILE: internal/controller/admin_v1/auth_test.go
  function TestExtractAccessToken (line 24) | func TestExtractAccessToken(t *testing.T) {
  function TestCheckTokenWithoutAuthorization (line 42) | func TestCheckTokenWithoutAuthorization(t *testing.T) {
  function TestLoginWithoutRequiredParams (line 62) | func TestLoginWithoutRequiredParams(t *testing.T) {
  function TestBuildLoginLogInfo (line 82) | func TestBuildLoginLogInfo(t *testing.T) {
  function TestBuildLoginLogInfoFallbacksUnknownForMissingUserAgent (line 106) | func TestBuildLoginLogInfoFallbacksUnknownForMissingUserAgent(t *testing...
  function initControllerAuthTest (line 123) | func initControllerAuthTest(t *testing.T) {

FILE: internal/controller/admin_v1/dashboard.go
  type DashboardController (line 10) | type DashboardController struct
    method Overview (line 18) | func (api DashboardController) Overview(c *gin.Context) {
  function NewDashboardController (line 14) | func NewDashboardController() *DashboardController {

FILE: internal/controller/admin_v1/sys_common.go
  constant defaultUploadPath (line 15) | defaultUploadPath = "default"
  type CommonController (line 19) | type CommonController struct
    method Upload (line 29) | func (api CommonController) Upload(c *gin.Context) {
    method GetFile (line 66) | func (api CommonController) GetFile(c *gin.Context) {
  function NewCommonController (line 24) | func NewCommonController() *CommonController {

FILE: internal/controller/sys_base.go
  type Api (line 20) | type Api struct
    method Success (line 25) | func (api Api) Success(c *gin.Context, data ...any) {
    method FailCode (line 35) | func (api Api) FailCode(c *gin.Context, code int, data ...any) {
    method FailCodeByKey (line 45) | func (api Api) FailCodeByKey(c *gin.Context, code int, key string, arg...
    method Fail (line 50) | func (api Api) Fail(c *gin.Context, code int, message string, data ......
    method Err (line 61) | func (api Api) Err(c *gin.Context, err error) {
    method GetCurrentUserID (line 89) | func (api Api) GetCurrentUserID(c *gin.Context) uint {
    method GetCurrentAdminUserSnapshot (line 94) | func (api Api) GetCurrentAdminUserSnapshot(c *gin.Context) *model.Admi...
    method GetCurrentAdminUserDetail (line 102) | func (api Api) GetCurrentAdminUserDetail(c *gin.Context) (*resources.A...

FILE: internal/controller/sys_base_test.go
  function TestApiErrMapsDBUninitializedToDependencyNotReady (line 16) | func TestApiErrMapsDBUninitializedToDependencyNotReady(t *testing.T) {

FILE: internal/controller/sys_demo.go
  type DemoController (line 10) | type DemoController struct
    method HelloWorld (line 20) | func (api DemoController) HelloWorld(c *gin.Context) {
  function NewDemoController (line 15) | func NewDemoController() *DemoController {

FILE: internal/cron/queue_fallback.go
  function RegisterQueueFallbackHandlers (line 13) | func RegisterQueueFallbackHandlers(registry queue.Registry, cfg *config....

FILE: internal/cron/queue_fallback_test.go
  function TestRegisterQueueFallbackHandlersRegistersDisabledNonHighRiskCron (line 12) | func TestRegisterQueueFallbackHandlersRegistersDisabledNonHighRiskCron(t...
  function TestRegisterQueueFallbackHandlersSkipsHighRiskCron (line 29) | func TestRegisterQueueFallbackHandlersSkipsHighRiskCron(t *testing.T) {
  function findRegistration (line 40) | func findRegistration(registry queue.Registry, taskType string) (queue.R...

FILE: internal/cron/registry.go
  constant TaskCodeCronDemo (line 22) | TaskCodeCronDemo            = "cron:demo"
  constant TaskCodeCronResetSystemData (line 23) | TaskCodeCronResetSystemData = "cron:reset-system-data"
  constant HandlerCronDemo (line 25) | HandlerCronDemo            = "cron.demo"
  constant HandlerCronResetSystemData (line 26) | HandlerCronResetSystemData = "cron.reset-system-data"
  constant cronLogTimeFormat (line 29) | cronLogTimeFormat = "2006-01-02 15:04:05"
  type HandlerFunc (line 31) | type HandlerFunc
  function RegisterHandler (line 43) | func RegisterHandler(handler string, fn HandlerFunc) {
  function ExecuteHandler (line 53) | func ExecuteHandler(ctx context.Context, handler string, payload map[str...
  function BuiltinTaskDefinitions (line 65) | func BuiltinTaskDefinitions(cfg *config.Conf) []model.TaskDefinition {
  function SyncBuiltinDefinitionsIfAvailable (line 121) | func SyncBuiltinDefinitionsIfAvailable(cfg *config.Conf) error {
  function syncBuiltinDefinitions (line 132) | func syncBuiltinDefinitions(db *gorm.DB, cfg *config.Conf) error {
  function upsertTaskDefinition (line 145) | func upsertTaskDefinition(db *gorm.DB, definition model.TaskDefinition) ...

FILE: internal/cron/registry_test.go
  function TestSyncBuiltinDefinitionsUpsert (line 14) | func TestSyncBuiltinDefinitionsUpsert(t *testing.T) {
  function assertTaskDefinitionCount (line 40) | func assertTaskDefinitionCount(t *testing.T, db *gorm.DB, expected int64) {
  function assertTaskStatus (line 52) | func assertTaskStatus(t *testing.T, db *gorm.DB, code string, expected u...
  function assertTaskAllowManual (line 64) | func assertTaskAllowManual(t *testing.T, db *gorm.DB, code string, expec...
  function newTaskDefinitionSyncTestDB (line 76) | func newTaskDefinitionSyncTestDB(t *testing.T) *gorm.DB {

FILE: internal/filestorage/aliyun_oss.go
  type AliyunOSSDriver (line 13) | type AliyunOSSDriver struct
    method Name (line 33) | func (d *AliyunOSSDriver) Name() string { return "aliyun_oss" }
    method Put (line 35) | func (d *AliyunOSSDriver) Put(ctx context.Context, input PutInput) (Pu...
    method Open (line 50) | func (d *AliyunOSSDriver) Open(ctx context.Context, bucket, objectKey ...
    method Exists (line 58) | func (d *AliyunOSSDriver) Exists(ctx context.Context, bucket, objectKe...
    method Delete (line 62) | func (d *AliyunOSSDriver) Delete(ctx context.Context, bucket, objectKe...
    method URL (line 67) | func (d *AliyunOSSDriver) URL(bucket, objectKey string, isPublic bool)...
    method SignedURL (line 74) | func (d *AliyunOSSDriver) SignedURL(ctx context.Context, bucket, objec...
  function NewAliyunOSSDriver (line 19) | func NewAliyunOSSDriver(config AliyunOSSConfig) *AliyunOSSDriver {

FILE: internal/filestorage/local.go
  type LocalDriver (line 14) | type LocalDriver struct
    method Name (line 25) | func (d *LocalDriver) Name() string { return "local" }
    method Put (line 27) | func (d *LocalDriver) Put(_ context.Context, input PutInput) (PutResul...
    method Open (line 46) | func (d *LocalDriver) Open(_ context.Context, bucket, objectKey string...
    method Exists (line 54) | func (d *LocalDriver) Exists(_ context.Context, bucket, objectKey stri...
    method Delete (line 69) | func (d *LocalDriver) Delete(_ context.Context, bucket, objectKey stri...
    method URL (line 80) | func (d *LocalDriver) URL(bucket, objectKey string, _ bool) string {
    method SignedURL (line 87) | func (d *LocalDriver) SignedURL(_ context.Context, bucket, objectKey s...
    method resolve (line 91) | func (d *LocalDriver) resolve(bucket, objectKey string) (string, error) {
  function NewLocalDriver (line 19) | func NewLocalDriver(config LocalConfig, fallbackPublicPath, fallbackPriv...
  function firstNonEmpty (line 114) | func firstNonEmpty(values ...string) string {

FILE: internal/filestorage/local_test.go
  function TestLocalDriverPutExistsOpenDelete (line 11) | func TestLocalDriverPutExistsOpenDelete(t *testing.T) {

FILE: internal/filestorage/s3.go
  type S3Driver (line 15) | type S3Driver struct
    method Name (line 44) | func (d *S3Driver) Name() string { return "s3" }
    method Put (line 46) | func (d *S3Driver) Put(ctx context.Context, input PutInput) (PutResult...
    method Open (line 61) | func (d *S3Driver) Open(ctx context.Context, bucket, objectKey string)...
    method Exists (line 69) | func (d *S3Driver) Exists(ctx context.Context, bucket, objectKey strin...
    method Delete (line 77) | func (d *S3Driver) Delete(ctx context.Context, bucket, objectKey strin...
    method URL (line 82) | func (d *S3Driver) URL(bucket, objectKey string, isPublic bool) string {
    method SignedURL (line 89) | func (d *S3Driver) SignedURL(ctx context.Context, bucket, objectKey st...
  function NewS3Driver (line 22) | func NewS3Driver(ctx context.Context, config S3Config) (*S3Driver, error) {

FILE: internal/filestorage/types.go
  constant MaskPlaceholder (line 9) | MaskPlaceholder = "******"
  type Config (line 11) | type Config struct
  type LocalConfig (line 20) | type LocalConfig struct
  type AliyunOSSConfig (line 26) | type AliyunOSSConfig struct
  type S3Config (line 37) | type S3Config struct
  type PutInput (line 47) | type PutInput struct
  type PutResult (line 55) | type PutResult struct
  type Driver (line 61) | type Driver interface
  function DefaultConfig (line 71) | func DefaultConfig() Config {

FILE: internal/global/api_auth_mode.go
  type ApiAuthMode (line 4) | type ApiAuthMode
    method RequiresLogin (line 16) | func (m ApiAuthMode) RequiresLogin() bool {
    method RequiresAPIPermission (line 21) | func (m ApiAuthMode) RequiresAPIPermission() bool {
    method Label (line 26) | func (m ApiAuthMode) Label() string {
  constant ApiAuthModeNone (line 8) | ApiAuthModeNone ApiAuthMode = iota
  constant ApiAuthModeLogin (line 10) | ApiAuthModeLogin
  constant ApiAuthModeAuth (line 12) | ApiAuthModeAuth

FILE: internal/global/api_auth_mode_test.go
  function TestApiAuthModeRequiresLogin (line 5) | func TestApiAuthModeRequiresLogin(t *testing.T) {
  function TestApiAuthModeRequiresAPIPermission (line 17) | func TestApiAuthModeRequiresAPIPermission(t *testing.T) {
  function TestApiAuthModeLabel (line 29) | func TestApiAuthModeLabel(t *testing.T) {

FILE: internal/global/auth.go
  constant SuperAdminId (line 4) | SuperAdminId          uint = 1
  constant Issuer (line 5) | Issuer                     = "go-layout"
  constant PcAdminSubject (line 6) | PcAdminSubject             = "pc-admin-token"
  constant CasbinAdminUserPrefix (line 7) | CasbinAdminUserPrefix      = "adminUser"
  constant CasbinRolePrefix (line 8) | CasbinRolePrefix           = "role"
  constant CasbinMenuPrefix (line 9) | CasbinMenuPrefix           = "menu"
  constant CasbinDeptPrefix (line 10) | CasbinDeptPrefix           = "dept"
  constant CasbinSeparator (line 11) | CasbinSeparator            = ":"

FILE: internal/global/common.go
  constant Version (line 5) | Version = "0.9.2"
  constant PerPage (line 7) | PerPage = 10
  constant Yes (line 9) | Yes uint8 = 1
  constant No (line 11) | No uint8 = 0
  constant ChinaCountryCode (line 13) | ChinaCountryCode = "86"
  constant SUCCESS (line 15) | SUCCESS = "SUCCESS"
  constant ERROR (line 17) | ERROR = "ERROR"

FILE: internal/global/context_keys.go
  constant ContextKeyUID (line 4) | ContextKeyUID              = "uid"
  constant ContextKeyAdminUser (line 5) | ContextKeyAdminUser        = "admin_user"
  constant ContextKeyAuthPrincipal (line 6) | ContextKeyAuthPrincipal    = "auth_principal"
  constant ContextKeyLocale (line 7) | ContextKeyLocale           = "locale"
  constant ContextKeyRequestID (line 8) | ContextKeyRequestID        = "requestId"
  constant ContextKeyRequestStartTime (line 9) | ContextKeyRequestStartTime = "requestStartTime"
  constant ContextKeyAuditChangeDiff (line 10) | ContextKeyAuditChangeDiff  = "auditChangeDiff"
  constant ContextKeyAuditHighRisk (line 11) | ContextKeyAuditHighRisk    = "auditHighRisk"
  constant ContextKeyAuditRequestBody (line 12) | ContextKeyAuditRequestBody = "auditRequestBody"

FILE: internal/global/system_defaults.go
  constant DefaultDepartmentCode (line 4) | DefaultDepartmentCode = "default_department"
  constant SuperAdminRoleCode (line 5) | SuperAdminRoleCode    = "super_admin"

FILE: internal/jobs/audit_log.go
  constant AuditLogTaskType (line 16) | AuditLogTaskType = "audit:request_log.write"
  constant AuditQueueName (line 17) | AuditQueueName   = "audit"
  constant AuditLogKindRequest (line 19) | AuditLogKindRequest = "request"
  constant AuditLogKindPanic (line 20) | AuditLogKindPanic   = "panic"
  type AuditLogHandlerDeps (line 24) | type AuditLogHandlerDeps struct
  type AuditLogPayload (line 29) | type AuditLogPayload struct
    method Validate (line 47) | func (p AuditLogPayload) Validate() error {
  function NewAuditLogPayload (line 35) | func NewAuditLogPayload(kind string, snapshot *auditsvc.AuditLogSnapshot...
  function EnqueueAuditLog (line 58) | func EnqueueAuditLog(ctx context.Context, kind string, snapshot *auditsv...
  function auditLogOptions (line 67) | func auditLogOptions() []queue.JobOption {
  function RegisterAll (line 86) | func RegisterAll(registry queue.Registry) {
  function RegisterAllWithDeps (line 91) | func RegisterAllWithDeps(registry queue.Registry, deps AuditLogHandlerDe...

FILE: internal/jobs/audit_log_test.go
  function TestNewAuditLogPayload (line 12) | func TestNewAuditLogPayload(t *testing.T) {
  function TestAuditLogHandlerReturnsSkipRetryForInvalidPayload (line 25) | func TestAuditLogHandlerReturnsSkipRetryForInvalidPayload(t *testing.T) {
  function TestAuditLogHandlerPersistsSnapshot (line 43) | func TestAuditLogHandlerPersistsSnapshot(t *testing.T) {

FILE: internal/jobs/registry.go
  function NewRegistry (line 6) | func NewRegistry() queue.Registry {

FILE: internal/middleware/admin_auth.go
  type routeAuthChecker (line 18) | type routeAuthChecker interface
  type permissionDeps (line 22) | type permissionDeps struct
  function newDefaultPermissionDeps (line 29) | func newDefaultPermissionDeps() permissionDeps {
  function AdminAuthHandler (line 37) | func AdminAuthHandler() gin.HandlerFunc {
  function AdminAuthHandlerWithDeps (line 42) | func AdminAuthHandlerWithDeps(deps permissionDeps) gin.HandlerFunc {
  function isSuperAdmin (line 85) | func isSuperAdmin(principal *auth.AuthPrincipal) bool {
  function checkPermission (line 90) | func checkPermission(c *gin.Context, userID uint, deps permissionDeps) e...
  function loadEnforcer (line 98) | func loadEnforcer(deps permissionDeps) (*casbinx.CasbinEnforcer, error) {
  function enforcePermission (line 107) | func enforcePermission(enforcer *casbinx.CasbinEnforcer, c *gin.Context,...

FILE: internal/middleware/admin_auth_test.go
  type stubRouteChecker (line 21) | type stubRouteChecker struct
    method CheckoutRouteIsAuth (line 25) | func (s stubRouteChecker) CheckoutRouteIsAuth(string, string) bool {
  function TestAdminAuthHandlerWithDepsAllowsPublicRouteWhenDenied (line 29) | func TestAdminAuthHandlerWithDepsAllowsPublicRouteWhenDenied(t *testing....
  function TestAdminAuthHandlerWithDepsReturnsServerErrWhenEnforcerLoadFails (line 65) | func TestAdminAuthHandlerWithDepsReturnsServerErrWhenEnforcerLoadFails(t...
  function TestAdminAuthHandlerWithDepsReturnsAuthorizationMessageWhenDenied (line 105) | func TestAdminAuthHandlerWithDepsReturnsAuthorizationMessageWhenDenied(t...
  function buildTestEnforcer (line 146) | func buildTestEnforcer(t *testing.T) *casbin.Enforcer {
  function decodeResult (line 170) | func decodeResult(t *testing.T, body []byte) *response.Result {

FILE: internal/middleware/audit_context.go
  function SetAuditChangeDiff (line 13) | func SetAuditChangeDiff(c *gin.Context, before any, after any) {
  function SetAuditChangeDiffRaw (line 28) | func SetAuditChangeDiffRaw(c *gin.Context, rawJSON string) {
  function SetAuditHighRisk (line 36) | func SetAuditHighRisk(c *gin.Context, highRisk bool) {
  function SetAuditRequestBodyRaw (line 44) | func SetAuditRequestBodyRaw(c *gin.Context, rawJSON string) {

FILE: internal/middleware/audit_queue.go
  constant auditEnqueueTimeout (line 20) | auditEnqueueTimeout = 2 * time.Second
  type auditQueueDeps (line 30) | type auditQueueDeps struct
  function setAuditQueueDepsForTesting (line 35) | func setAuditQueueDepsForTesting(deps auditQueueDeps) func() {
  function enqueueAuditLog (line 61) | func enqueueAuditLog(c *gin.Context, kind string, snapshot *audit.AuditL...
  function reportAuditPersistenceResult (line 102) | func reportAuditPersistenceResult(kind string, snapshot *audit.AuditLogS...

FILE: internal/middleware/audit_queue_test.go
  function TestEnqueueAuditLogDelegatesToPublisher (line 23) | func TestEnqueueAuditLogDelegatesToPublisher(t *testing.T) {
  function TestEnqueueAuditLogFailureDoesNotPanic (line 64) | func TestEnqueueAuditLogFailureDoesNotPanic(t *testing.T) {
  function TestEnqueueAuditLogResetsUnavailableFlagAfterSuccess (line 75) | func TestEnqueueAuditLogResetsUnavailableFlagAfterSuccess(t *testing.T) {
  function TestEnqueueAuditLogMarksUnavailableWhenPublisherUnavailable (line 96) | func TestEnqueueAuditLogMarksUnavailableWhenPublisherUnavailable(t *test...
  function TestEnqueueAuditLogPersistsSynchronouslyWhenQueueDisabled (line 117) | func TestEnqueueAuditLogPersistsSynchronouslyWhenQueueDisabled(t *testin...
  function TestEnqueueAuditLogHandlesStorageUnavailableWhenQueueDisabled (line 147) | func TestEnqueueAuditLogHandlesStorageUnavailableWhenQueueDisabled(t *te...
  function TestReportAuditPersistenceResultMarksStorageUnavailable (line 168) | func TestReportAuditPersistenceResultMarksStorageUnavailable(t *testing....
  function TestReportAuditPersistenceResultResetsStorageUnavailableAfterSuccess (line 184) | func TestReportAuditPersistenceResultResetsStorageUnavailableAfterSucces...

FILE: internal/middleware/cors.go
  function CorsHandler (line 16) | func CorsHandler() gin.HandlerFunc {
  function resolveAllowOrigin (line 53) | func resolveAllowOrigin(origin string, cfg *config.Conf) (string, bool) {
  function getAllowedMethods (line 63) | func getAllowedMethods(cfg *config.Conf) []string {
  function defaultMethods (line 73) | func defaultMethods() []string {
  function getAllowedHeaders (line 79) | func getAllowedHeaders(c *gin.Context, cfg *config.Conf) string {
  function getExposeHeaders (line 97) | func getExposeHeaders(cfg *config.Conf) string {
  function getMaxAge (line 107) | func getMaxAge(cfg *config.Conf) int {
  function isOriginAllowed (line 114) | func isOriginAllowed(origin string, cfg *config.Conf) bool {
  function hasWildcardOrigin (line 134) | func hasWildcardOrigin(cfg *config.Conf) bool {
  function hasWildcardValue (line 143) | func hasWildcardValue(values []string) bool {

FILE: internal/middleware/cors_test.go
  function TestCorsHandlerAllowsWildcardOrigin (line 14) | func TestCorsHandlerAllowsWildcardOrigin(t *testing.T) {
  function TestCorsHandlerReflectsOriginWhenCredentialsEnabled (line 46) | func TestCorsHandlerReflectsOriginWhenCredentialsEnabled(t *testing.T) {
  function TestCorsHandlerRejectsUnknownOrigin (line 81) | func TestCorsHandlerRejectsUnknownOrigin(t *testing.T) {
  function TestCorsHandlerAllowsWildcardMethodsAndHeaders (line 109) | func TestCorsHandlerAllowsWildcardMethodsAndHeaders(t *testing.T) {
  function TestGetExposeHeadersSupportsWildcard (line 147) | func TestGetExposeHeadersSupportsWildcard(t *testing.T) {

FILE: internal/middleware/database_ready.go
  function DatabaseReadyGuard (line 13) | func DatabaseReadyGuard() gin.HandlerFunc {
  function OptionalDatabaseReadyGuard (line 19) | func OptionalDatabaseReadyGuard() gin.HandlerFunc {
  function databaseReadyGuard (line 23) | func databaseReadyGuard(skipWhenNoUID bool) gin.HandlerFunc {

FILE: internal/middleware/database_ready_test.go
  type dependencyErrorResponse (line 17) | type dependencyErrorResponse struct
  function TestDatabaseReadyGuardBlocksWhenMysqlUnavailable (line 21) | func TestDatabaseReadyGuardBlocksWhenMysqlUnavailable(t *testing.T) {
  function TestOptionalDatabaseReadyGuardKeepsUnauthenticatedRequests (line 44) | func TestOptionalDatabaseReadyGuardKeepsUnauthenticatedRequests(t *testi...
  function TestOptionalDatabaseReadyGuardBlocksAuthenticatedRequests (line 69) | func TestOptionalDatabaseReadyGuardBlocksAuthenticatedRequests(t *testin...
  function disableMysqlForGuardTest (line 96) | func disableMysqlForGuardTest(t *testing.T) func() {
  function assertDependencyNotReadyCode (line 114) | func assertDependencyNotReadyCode(t *testing.T, recorder *httptest.Respo...

FILE: internal/middleware/logger.go
  constant defaultStatusCode (line 12) | defaultStatusCode   = http.StatusOK
  constant maxRequestBodySize (line 13) | maxRequestBodySize  = 16 * 1024
  constant maxResponseBodySize (line 14) | maxResponseBodySize = 32 * 1024
  function CustomLogger (line 18) | func CustomLogger() gin.HandlerFunc {
  function prepareRequestLogging (line 27) | func prepareRequestLogging(c *gin.Context) *responseRecorder {
  function finalizeRequestLogging (line 38) | func finalizeRequestLogging(c *gin.Context, recorder *responseRecorder) {
  function logRequest (line 47) | func logRequest(c *gin.Context, recorder *responseRecorder) {
  function shouldSkipRequestLogging (line 52) | func shouldSkipRequestLogging(c *gin.Context, recorder *responseRecorder...
  function publishRequestAuditLog (line 67) | func publishRequestAuditLog(c *gin.Context, recorder *responseRecorder) {

FILE: internal/middleware/logger_bench_test.go
  function BenchmarkCustomLoggerJSONPost (line 16) | func BenchmarkCustomLoggerJSONPost(b *testing.B) {

FILE: internal/middleware/logger_recorder.go
  type requestBodyCache (line 15) | type requestBodyCache struct
  type responseRecorder (line 22) | type responseRecorder struct
    method Write (line 32) | func (r *responseRecorder) Write(b []byte) (int, error) {
    method WriteString (line 38) | func (r *responseRecorder) WriteString(s string) (int, error) {
    method WriteHeader (line 44) | func (r *responseRecorder) WriteHeader(statusCode int) {
    method cacheBody (line 85) | func (r *responseRecorder) cacheBody(b []byte) {
  function cacheRequestBody (line 50) | func cacheRequestBody(c *gin.Context) {
  function createResponseRecorder (line 76) | func createResponseRecorder(c *gin.Context) *responseRecorder {
  function parseResponse (line 109) | func parseResponse(c *gin.Context, recorder *responseRecorder) *response...
  function shouldCaptureResponseBody (line 127) | func shouldCaptureResponseBody(c *gin.Context) bool {
  function readRequestBody (line 138) | func readRequestBody(c *gin.Context) []byte {
  function getRequestBodyCache (line 166) | func getRequestBodyCache(c *gin.Context) *requestBodyCache {
  function truncateBytes (line 175) | func truncateBytes(body []byte, maxSize int) []byte {
  function shouldSkipRequestBodyCache (line 182) | func shouldSkipRequestBodyCache(c *gin.Context) bool {
  function snapshotRequestBody (line 194) | func snapshotRequestBody(req *http.Request) ([]byte, int, bool, error) {

FILE: internal/middleware/logger_storage.go
  function buildRequestAuditLogSnapshot (line 21) | func buildRequestAuditLogSnapshot(c *gin.Context, recorder *responseReco...
  function buildPanicAuditLogSnapshot (line 28) | func buildPanicAuditLogSnapshot(c *gin.Context, panicMessage string) *au...
  type auditRequestMeta (line 46) | type auditRequestMeta struct
  type auditOperatorMeta (line 61) | type auditOperatorMeta struct
  function buildAuditLogSnapshot (line 69) | func buildAuditLogSnapshot(c *gin.Context, recorder *responseRecorder, o...
  function collectAuditRequestMeta (line 106) | func collectAuditRequestMeta(c *gin.Context) *auditRequestMeta {
  function collectAuditOperatorMeta (line 135) | func collectAuditOperatorMeta(c *gin.Context) auditOperatorMeta {
  function calculateExecutionTimeMs (line 150) | func calculateExecutionTimeMs(c *gin.Context) float64 {
  function resolveAuditResponseStatus (line 156) | func resolveAuditResponseStatus(recorder *responseRecorder) int {
  function buildMaskedRequestBody (line 163) | func buildMaskedRequestBody(c *gin.Context) string {
  function resolveAuditRequestBody (line 184) | func resolveAuditRequestBody(c *gin.Context) string {
  function buildMaskedResponseBody (line 195) | func buildMaskedResponseBody(recorder *responseRecorder) string {
  function resolveAuditHighRisk (line 211) | func resolveAuditHighRisk(c *gin.Context, method string) uint8 {
  function resolveAuditChangeDiff (line 249) | func resolveAuditChangeDiff(c *gin.Context, isHighRisk uint8, requestBod...
  function operationStatusFromResponse (line 281) | func operationStatusFromResponse(recorder *responseRecorder, resp *respo...
  function getOperationName (line 292) | func getOperationName(route string, method string, headerOperationName s...

FILE: internal/middleware/logger_test.go
  function TestCacheRequestBody (line 20) | func TestCacheRequestBody(t *testing.T) {
  function TestCacheRequestBodySkipsGet (line 34) | func TestCacheRequestBodySkipsGet(t *testing.T) {
  function TestCacheRequestBodySkipsMultipartRequests (line 46) | func TestCacheRequestBodySkipsMultipartRequests(t *testing.T) {
  function TestParseResponse (line 60) | func TestParseResponse(t *testing.T) {
  function TestParseResponseForNonJSON (line 80) | func TestParseResponseForNonJSON(t *testing.T) {
  function TestBuildMaskedBodies (line 95) | func TestBuildMaskedBodies(t *testing.T) {
  function TestReadRequestBodyPreservesLargeRequestBody (line 138) | func TestReadRequestBodyPreservesLargeRequestBody(t *testing.T) {
  function TestOperationStatusFromResponse (line 163) | func TestOperationStatusFromResponse(t *testing.T) {
  function TestLogRequestSkipsPing (line 179) | func TestLogRequestSkipsPing(t *testing.T) {
  function TestBuildRequestAuditLogSnapshotUsesPrincipal (line 190) | func TestBuildRequestAuditLogSnapshotUsesPrincipal(t *testing.T) {
  function TestBuildRequestAuditLogSnapshotUsesAuditRequestBodyOverride (line 221) | func TestBuildRequestAuditLogSnapshotUsesAuditRequestBodyOverride(t *tes...
  function TestBuildRequestAuditLogSnapshotMarksHighRiskAndDiff (line 243) | func TestBuildRequestAuditLogSnapshotMarksHighRiskAndDiff(t *testing.T) {
  function TestBuildRequestAuditLogSnapshotGetRequestIsNotHighRiskByDefault (line 279) | func TestBuildRequestAuditLogSnapshotGetRequestIsNotHighRiskByDefault(t ...

FILE: internal/middleware/parse_token.go
  function ParseTokenHandler (line 17) | func ParseTokenHandler() gin.HandlerFunc {

FILE: internal/middleware/recovery.go
  constant panicErrorPrefix (line 23) | panicErrorPrefix = "An error occurred in the server's internal code: "
  constant panicRecoveredMsg (line 25) | panicRecoveredMsg = "panic recovered"
  function CustomRecovery (line 30) | func CustomRecovery() gin.HandlerFunc {
  function handlePanic (line 36) | func handlePanic(c *gin.Context, err interface{}) {
  type PanicExceptionRecord (line 76) | type PanicExceptionRecord struct
    method Write (line 79) | func (p *PanicExceptionRecord) Write(b []byte) (n int, err error) {

FILE: internal/middleware/request_cost.go
  function RequestCostHandler (line 16) | func RequestCostHandler() gin.HandlerFunc {

FILE: internal/middleware/request_locale.go
  constant acceptLanguageHeader (line 10) | acceptLanguageHeader = "Accept-Language"
  function RequestLocaleHandler (line 13) | func RequestLocaleHandler() gin.HandlerFunc {
  function LocaleFromContext (line 22) | func LocaleFromContext(c *gin.Context) string {

FILE: internal/middleware/request_locale_test.go
  function TestRequestLocaleHandler (line 13) | func TestRequestLocaleHandler(t *testing.T) {
  function TestLocaleFromContextFallback (line 34) | func TestLocaleFromContextFallback(t *testing.T) {

FILE: internal/model/admin_login_logs.go
  constant LoginTypeLogin (line 13) | LoginTypeLogin   uint8 = 1
  constant LoginTypeRefresh (line 14) | LoginTypeRefresh uint8 = 2
  constant LoginStatusSuccess (line 19) | LoginStatusSuccess uint8 = 1
  constant LoginStatusFail (line 20) | LoginStatusFail    uint8 = 0
  constant IsRevokedNo (line 37) | IsRevokedNo  = global.No
  constant IsRevokedYes (line 38) | IsRevokedYes = global.Yes
  constant RevokedCodeUserLogout (line 43) | RevokedCodeUserLogout          uint8 = 1
  constant RevokedCodeSystemForce (line 44) | RevokedCodeSystemForce         uint8 = 2
  constant RevokedCodeTokenRefresh (line 45) | RevokedCodeTokenRefresh        uint8 = 3
  constant RevokedCodeUserDisable (line 46) | RevokedCodeUserDisable         uint8 = 4
  constant RevokedCodeOther (line 47) | RevokedCodeOther               uint8 = 5
  constant RevokedCodePasswordChangeSelf (line 48) | RevokedCodePasswordChangeSelf  uint8 = 6
  constant RevokedCodePasswordChangeAdmin (line 49) | RevokedCodePasswordChangeAdmin uint8 = 7
  type AdminLoginLogs (line 64) | type AdminLoginLogs struct
    method TableName (line 94) | func (m *AdminLoginLogs) TableName() string {
    method LoginStatusMap (line 99) | func (m *AdminLoginLogs) LoginStatusMap() string {
    method TypeMap (line 104) | func (m *AdminLoginLogs) TypeMap() string {
    method IsRevokedMap (line 109) | func (m *AdminLoginLogs) IsRevokedMap() string {
    method RevokedCodeMap (line 114) | func (m *AdminLoginLogs) RevokedCodeMap() string {
    method Create (line 119) | func (m *AdminLoginLogs) Create() error {
    method FindByJwtId (line 128) | func (m *AdminLoginLogs) FindByJwtId(jwtId string) error {
    method UpdateRevokedStatusByJwtIds (line 137) | func (m *AdminLoginLogs) UpdateRevokedStatusByJwtIds(jwtIds []string, ...
    method FindActiveTokensByUserId (line 155) | func (m *AdminLoginLogs) FindActiveTokensByUserId(userId uint, now tim...
  function NewAdminLoginLogs (line 89) | func NewAdminLoginLogs() *AdminLoginLogs {

FILE: internal/model/admin_user_dept_map.go
  type AdminUserDeptMap (line 4) | type AdminUserDeptMap struct
    method TableName (line 15) | func (m *AdminUserDeptMap) TableName() string {
    method CreateBatch (line 19) | func (m *AdminUserDeptMap) CreateBatch(mappings []*AdminUserDeptMap) e...
    method DeptIdsByUid (line 28) | func (m *AdminUserDeptMap) DeptIdsByUid(uid uint) ([]uint, error) {
    method UidsByDeptIds (line 41) | func (m *AdminUserDeptMap) UidsByDeptIds(deptIds []uint) ([]uint, erro...
    method UserDeptMapByUids (line 57) | func (m *AdminUserDeptMap) UserDeptMapByUids(uids []uint) (map[uint][]...
    method CountByCondition (line 83) | func (m *AdminUserDeptMap) CountByCondition(condition string, args ......
    method CreateOne (line 96) | func (m *AdminUserDeptMap) CreateOne() error {
  function NewAdminUserDeptMap (line 10) | func NewAdminUserDeptMap() *AdminUserDeptMap {

FILE: internal/model/admin_user_role_map.go
  type AdminUserRoleMap (line 4) | type AdminUserRoleMap struct
    method TableName (line 15) | func (m *AdminUserRoleMap) TableName() string {
    method CreateBatch (line 19) | func (m *AdminUserRoleMap) CreateBatch(mappings []*AdminUserRoleMap) e...
    method RoleIdsByUid (line 28) | func (m *AdminUserRoleMap) RoleIdsByUid(uid uint) ([]uint, error) {
    method UidsByRoleIds (line 41) | func (m *AdminUserRoleMap) UidsByRoleIds(roleIds []uint) ([]uint, erro...
    method UserRoleMapByUids (line 57) | func (m *AdminUserRoleMap) UserRoleMapByUids(uids []uint) (map[uint][]...
    method CountByCondition (line 81) | func (m *AdminUserRoleMap) CountByCondition(condition string, args ......
    method CreateOne (line 94) | func (m *AdminUserRoleMap) CreateOne() error {
  function NewAdminUserRoleMap (line 10) | func NewAdminUserRoleMap() *AdminUserRoleMap {

FILE: internal/model/admin_users.go
  constant AdminUserStatusEnabled (line 14) | AdminUserStatusEnabled  uint8 = 1
  constant AdminUserStatusDisabled (line 15) | AdminUserStatusDisabled uint8 = 0
  type AdminUser (line 31) | type AdminUser struct
    method TableName (line 54) | func (m *AdminUser) TableName() string {
    method GetUserInfo (line 59) | func (m *AdminUser) GetUserInfo(username string) error {
    method IsSuperAdminMap (line 71) | func (m *AdminUser) IsSuperAdminMap() string {
    method StatusMap (line 76) | func (m *AdminUser) StatusMap() string {
    method SyncUserRows (line 88) | func (m *AdminUser) SyncUserRows(userIDs []uint) ([]SyncUserRow, error) {
    method AllIds (line 107) | func (m *AdminUser) AllIds() ([]uint, error) {
    method ExistsWithLock (line 120) | func (m *AdminUser) ExistsWithLock(condition string, args ...any) (boo...
    method ExistsWithLockExcludeId (line 138) | func (m *AdminUser) ExistsWithLockExcludeId(field string, value string...
    method GetByIdWithPreload (line 161) | func (m *AdminUser) GetByIdWithPreload(id uint, relations ...string) e...
  function NewAdminUsers (line 49) | func NewAdminUsers() *AdminUser {
  type SyncUserRow (line 81) | type SyncUserRow struct

FILE: internal/model/admin_users_test.go
  function TestExistsWithLockExcludeIdRejectsUnknownField (line 8) | func TestExistsWithLockExcludeIdRejectsUnknownField(t *testing.T) {
  function TestExistsWithLockExcludeIdAllowedFieldReturnsDBErrorWhenUninitialized (line 16) | func TestExistsWithLockExcludeIdAllowedFieldReturnsDBErrorWhenUninitiali...

FILE: internal/model/api.go
  type Api (line 12) | type Api struct
    method TableName (line 32) | func (m *Api) TableName() string {
    method InitRegisters (line 37) | func (m *Api) InitRegisters(data []map[string]any, date string) error {
    method IsAuthMap (line 59) | func (m *Api) IsAuthMap() string {
    method IsEffectiveMap (line 64) | func (m *Api) IsEffectiveMap() string {
    method FindIdsByRouteAndMethod (line 69) | func (m *Api) FindIdsByRouteAndMethod(routes []string, methods []strin...
    method FindByIds (line 85) | func (m *Api) FindByIds(ids []uint) ([]Api, error) {
  function NewApi (line 27) | func NewApi() *Api {

FILE: internal/model/base.go
  type BaseModel (line 16) | type BaseModel struct
  type ContainsDeleteBaseModel (line 34) | type ContainsDeleteBaseModel struct
  function GetDB (line 40) | func GetDB(model ...any) (*gorm.DB, error) {
  function validateModelArg (line 60) | func validateModelArg(model any) error {

FILE: internal/model/base_crud.go
  method Paginate (line 13) | func (m *BaseModel) Paginate(page, pageSize int) func(db *gorm.DB) *gorm...
  method Count (line 30) | func (m *BaseModel) Count(condition string, args ...any) (count int64, e...
  method GetById (line 50) | func (m *BaseModel) GetById(id uint) error {
  method GetAllById (line 63) | func (m *BaseModel) GetAllById(id uint) error {
  method GetDetail (line 76) | func (m *BaseModel) GetDetail(condition string, val ...any) error {
  method ExistsById (line 89) | func (m *BaseModel) ExistsById(id uint) (bool, error) {
  method Exists (line 97) | func (m *BaseModel) Exists(condition string, args ...any) (bool, error) {
  method UpdateById (line 113) | func (m *BaseModel) UpdateById(id uint, data map[string]any) error {
  method DeleteByID (line 126) | func (m *BaseModel) DeleteByID(id uint) (int64, error) {
  method DeleteWhere (line 140) | func (m *BaseModel) DeleteWhere(condition string, args ...any) error {
  method Create (line 156) | func (m *BaseModel) Create(data map[string]any) error {
  method CreateBatch (line 169) | func (m *BaseModel) CreateBatch(data []map[string]any) error {
  method Save (line 182) | func (m *BaseModel) Save() error {

FILE: internal/model/base_list.go
  type BaseModelInterface (line 15) | type BaseModelInterface interface
  type ListOptionalParams (line 23) | type ListOptionalParams struct
  function normalizeOrderBy (line 37) | func normalizeOrderBy(orderBy string, allowed map[string]struct{}) (stri...
  function normalizeSelectFields (line 81) | func normalizeSelectFields(fields string) (string, error) {
  function buildListQuery (line 111) | func buildListQuery[T any, M BaseModelInterface[T]](model M, condition s...
  function countListTotal (line 170) | func countListTotal[T any, M BaseModelInterface[T]](model M, baseQuery *...
  function ListPageE (line 207) | func ListPageE[T any, M BaseModelInterface[T]](model M, page, perPage in...
  function ListE (line 247) | func ListE[T any, M BaseModelInterface[T]](model M, condition string, ar...
  function VerifyExistingIDs (line 271) | func VerifyExistingIDs[T any, M BaseModelInterface[T]](model M, ids []ui...
  function ExtractColumnsByCondition (line 289) | func ExtractColumnsByCondition[T any, M BaseModelInterface[T], R any](mo...

FILE: internal/model/base_list_test.go
  function TestNormalizeOrderByAcceptsValidFields (line 8) | func TestNormalizeOrderByAcceptsValidFields(t *testing.T) {
  function TestNormalizeOrderByRejectsInjection (line 18) | func TestNormalizeOrderByRejectsInjection(t *testing.T) {
  function TestNormalizeOrderByChecksAllowList (line 25) | func TestNormalizeOrderByChecksAllowList(t *testing.T) {
  function TestNormalizeSelectFieldsAcceptsValidFields (line 38) | func TestNormalizeSelectFieldsAcceptsValidFields(t *testing.T) {
  function TestNormalizeSelectFieldsRejectsInjection (line 48) | func TestNormalizeSelectFieldsRejectsInjection(t *testing.T) {

FILE: internal/model/base_owner.go
  type ownerBinder (line 5) | type ownerBinder interface
  method SetDB (line 10) | func (m *BaseModel) SetDB(tx *gorm.DB) *BaseModel {
  method bindOwner (line 15) | func (m *BaseModel) bindOwner(owner any) {
  function BindModel (line 20) | func BindModel[T any](m T) T {
  method self (line 27) | func (m *BaseModel) self() (any, error) {
  method GetDB (line 35) | func (m *BaseModel) GetDB(model ...any) (*gorm.DB, error) {

FILE: internal/model/base_test.go
  function TestGetDBReturnsErrorWhenUninitialized (line 8) | func TestGetDBReturnsErrorWhenUninitialized(t *testing.T) {
  function TestGetDBRejectsTypedNilModelArg (line 15) | func TestGetDBRejectsTypedNilModelArg(t *testing.T) {
  function TestListEReturnsErrorWhenUninitialized (line 23) | func TestListEReturnsErrorWhenUninitialized(t *testing.T) {
  function TestListPageEReturnsErrorWhenUninitialized (line 30) | func TestListPageEReturnsErrorWhenUninitialized(t *testing.T) {
  function TestBaseModelSelfReturnsErrorWithoutBinding (line 37) | func TestBaseModelSelfReturnsErrorWithoutBinding(t *testing.T) {
  function TestNewModelBindsOwner (line 45) | func TestNewModelBindsOwner(t *testing.T) {
  function TestInstanceMethodReturnsBindingErrorBeforeDBError (line 56) | func TestInstanceMethodReturnsBindingErrorBeforeDBError(t *testing.T) {
  function TestCountReturnsBindingErrorBeforeDBError (line 64) | func TestCountReturnsBindingErrorBeforeDBError(t *testing.T) {
  function TestBoundCountReturnsDBErrorWhenUninitialized (line 72) | func TestBoundCountReturnsDBErrorWhenUninitialized(t *testing.T) {

FILE: internal/model/base_tree.go
  function HasChildren (line 6) | func HasChildren[T any, M BaseModelInterface[T]](model M, pid uint) (boo...
  function UpdateChildrenNum (line 15) | func UpdateChildrenNum[T any, M BaseModelInterface[T]](model M, pid uint...

FILE: internal/model/dept.go
  type Department (line 10) | type Department struct
    method TableName (line 30) | func (m *Department) TableName() string {
    method IsSystemDepartment (line 34) | func (m *Department) IsSystemDepartment() bool {
    method FindByCode (line 39) | func (m *Department) FindByCode(code string) error {
    method UpdateUserNumberByIds (line 48) | func (m *Department) UpdateUserNumberByIds(deptIds []uint, updateExpr ...
    method UpdateChildrenPidsByParent (line 62) | func (m *Department) UpdateChildrenPidsByParent(parentID uint, updateE...
  function NewDepartment (line 25) | func NewDepartment() *Department {

FILE: internal/model/dept_role_map.go
  type DeptRoleMap (line 4) | type DeptRoleMap struct
    method TableName (line 15) | func (m *DeptRoleMap) TableName() string {
    method DeleteByDeptId (line 19) | func (m *DeptRoleMap) DeleteByDeptId(deptId uint) error {
    method CreateBatch (line 23) | func (m *DeptRoleMap) CreateBatch(mappings []*DeptRoleMap) error {
    method RoleIdsByDeptIds (line 32) | func (m *DeptRoleMap) RoleIdsByDeptIds(deptIds []uint) ([]uint, error) {
    method DeptIdsByRoleIds (line 48) | func (m *DeptRoleMap) DeptIdsByRoleIds(roleIds []uint) ([]uint, error) {
    method DeptRoleMapByDeptIds (line 64) | func (m *DeptRoleMap) DeptRoleMapByDeptIds(deptIds []uint) (map[uint][...
  function NewDeptRoleMap (line 10) | func NewDeptRoleMap() *DeptRoleMap {

FILE: internal/model/file_upload.go
  constant StorageDriverLocal (line 6) | StorageDriverLocal     = "local"
  constant StorageDriverAliyunOSS (line 7) | StorageDriverAliyunOSS = "aliyun_oss"
  constant StorageDriverS3 (line 8) | StorageDriverS3        = "s3"
  constant StorageStatusStored (line 10) | StorageStatusStored       = "stored"
  constant StorageStatusDeleteFailed (line 11) | StorageStatusDeleteFailed = "delete_failed"
  type UploadFiles (line 14) | type UploadFiles struct
    method TableName (line 54) | func (m *UploadFiles) TableName() string {
    method Create (line 59) | func (m *UploadFiles) Create() error {
  function NewUploadFiles (line 49) | func NewUploadFiles() *UploadFiles {
  constant UploadSourceBackend (line 68) | UploadSourceBackend = "backend"
  constant UploadSourceDirect (line 69) | UploadSourceDirect  = "direct"
  constant UploadSourceSystem (line 70) | UploadSourceSystem  = "system"
  constant UploadStatusPending (line 72) | UploadStatusPending  = "pending"
  constant UploadStatusUploaded (line 73) | UploadStatusUploaded = "uploaded"
  constant UploadStatusFailed (line 74) | UploadStatusFailed   = "failed"
  type UploadFileObject (line 77) | type UploadFileObject struct
    method TableName (line 95) | func (m *UploadFileObject) TableName() string {
  function NewUploadFileObject (line 91) | func NewUploadFileObject() *UploadFileObject {
  type UploadFileFolder (line 99) | type UploadFileFolder struct
    method TableName (line 113) | func (m *UploadFileFolder) TableName() string {
  function NewUploadFileFolder (line 109) | func NewUploadFileFolder() *UploadFileFolder {
  type UploadFileReference (line 117) | type UploadFileReference struct
    method TableName (line 130) | func (m *UploadFileReference) TableName() string {
  function NewUploadFileReference (line 126) | func NewUploadFileReference() *UploadFileReference {

FILE: internal/model/file_upload_test.go
  function TestUploadFilesETagColumnName (line 10) | func TestUploadFilesETagColumnName(t *testing.T) {

FILE: internal/model/login_security_state.go
  type LoginSecurityState (line 10) | type LoginSecurityState struct
    method TableName (line 22) | func (m *LoginSecurityState) TableName() string {
    method FindByUsername (line 27) | func (m *LoginSecurityState) FindByUsername(username string) error {
  function NewLoginSecurityState (line 18) | func NewLoginSecurityState() *LoginSecurityState {

FILE: internal/model/menu.go
  type Menu (line 10) | type Menu struct
    method MenuTypeMap (line 47) | func (m *Menu) MenuTypeMap() string {
    method IsExternalLinksMap (line 51) | func (m *Menu) IsExternalLinksMap() string {
    method IsAuthMap (line 55) | func (m *Menu) IsAuthMap() string {
    method IsShowMap (line 59) | func (m *Menu) IsShowMap() string {
    method IsNewWindowMap (line 63) | func (m *Menu) IsNewWindowMap() string {
    method StatusMap (line 68) | func (m *Menu) StatusMap() string {
    method GetApiIds (line 72) | func (m *Menu) GetApiIds() []uint {
    method TableName (line 91) | func (m *Menu) TableName() string {
    method AllIds (line 96) | func (m *Menu) AllIds() ([]uint, error) {
    method EnabledIdsByIds (line 109) | func (m *Menu) EnabledIdsByIds(ids []uint) ([]uint, error) {
    method ExistsExcludeId (line 125) | func (m *Menu) ExistsExcludeId(field string, value string, excludeId u...
    method FindPidsByIds (line 160) | func (m *Menu) FindPidsByIds(ids []uint) ([]MenuTreeNode, error) {
    method FindIdsByCodes (line 176) | func (m *Menu) FindIdsByCodes(codes []string) ([]Menu, error) {
    method FindDescendantsById (line 192) | func (m *Menu) FindDescendantsById(id uint) ([]Menu, error) {
    method UpdateById (line 205) | func (m *Menu) UpdateById(id uint, data map[string]any) error {
  constant CATALOGUE (line 37) | CATALOGUE uint8 = 1
  constant MENU (line 38) | MENU uint8 = 2
  constant BUTTON (line 39) | BUTTON uint8 = 3
  function NewMenu (line 86) | func NewMenu() *Menu {
  function isAllowedMenuUniqueField (line 144) | func isAllowedMenuUniqueField(field string) bool {
  type MenuTreeNode (line 154) | type MenuTreeNode struct

FILE: internal/model/menu_api_map.go
  type MenuApiMap (line 6) | type MenuApiMap struct
    method TableName (line 17) | func (m *MenuApiMap) TableName() string {
    method CreateBatch (line 21) | func (m *MenuApiMap) CreateBatch(mappings []*MenuApiMap) error {
    method ApiIdsByMenuId (line 30) | func (m *MenuApiMap) ApiIdsByMenuId(menuId uint) ([]uint, error) {
    method MenuIdsByApiIds (line 43) | func (m *MenuApiMap) MenuIdsByApiIds(apiIds []uint) ([]uint, error) {
    method ApiPermissionsByMenuIds (line 65) | func (m *MenuApiMap) ApiPermissionsByMenuIds(menuIds []uint) ([]ApiPer...
    method MenuApiPermissionsByMenuIds (line 93) | func (m *MenuApiMap) MenuApiPermissionsByMenuIds(menuIds []uint) ([]Me...
  function NewMenuApiMap (line 12) | func NewMenuApiMap() *MenuApiMap {
  type ApiPermission (line 59) | type ApiPermission struct
  type MenuApiPermission (line 86) | type MenuApiPermission struct

FILE: internal/model/menu_i18n.go
  type MenuI18n (line 11) | type MenuI18n struct
    method TableName (line 23) | func (m *MenuI18n) TableName() string {
    method UpsertMenuTitles (line 28) | func (m *MenuI18n) UpsertMenuTitles(menuID uint, localeTitles map[stri...
    method LocalizedTitleMapByMenuIDs (line 65) | func (m *MenuI18n) LocalizedTitleMapByMenuIDs(menuIDs []uint, localePr...
    method LocaleTitleMapByMenuID (line 122) | func (m *MenuI18n) LocaleTitleMapByMenuID(menuID uint) (map[string]str...
    method DeleteByMenuIDs (line 149) | func (m *MenuI18n) DeleteByMenuIDs(menuIDs []uint, tx ...*gorm.DB) err...
  function NewMenuI18n (line 18) | func NewMenuI18n() *MenuI18n {

FILE: internal/model/modelDict/base.go
  type Dict (line 5) | type Dict
    method Map (line 7) | func (d Dict) Map(k uint8) string {

FILE: internal/model/request_logs.go
  type RequestLogs (line 4) | type RequestLogs struct
    method TableName (line 35) | func (m *RequestLogs) TableName() string {
    method Create (line 40) | func (m *RequestLogs) Create() error {
  function NewRequestLogs (line 30) | func NewRequestLogs() *RequestLogs {

FILE: internal/model/role.go
  type Role (line 17) | type Role struct
    method TableName (line 37) | func (m *Role) TableName() string {
    method StatusMap (line 42) | func (m *Role) StatusMap() string {
    method IsSystemRole (line 46) | func (m *Role) IsSystemRole() bool {
    method AllRoleStatusInfos (line 58) | func (m *Role) AllRoleStatusInfos() ([]RoleStatusInfo, error) {
    method AllTreeNodes (line 77) | func (m *Role) AllTreeNodes() ([]RoleTreeNode, error) {
    method EnabledIdsByIds (line 90) | func (m *Role) EnabledIdsByIds(ids []uint) ([]uint, error) {
    method FindByCode (line 106) | func (m *Role) FindByCode(code string) error {
    method FindPidsByIds (line 115) | func (m *Role) FindPidsByIds(ids []uint) ([]RoleTreeNode, error) {
    method SubtreeIdsByRootIds (line 131) | func (m *Role) SubtreeIdsByRootIds(rootIDs []uint) ([]uint, error) {
    method UpdateChildrenPidsByParent (line 153) | func (m *Role) UpdateChildrenPidsByParent(parentID uint, updateExpr st...
  function NewRole (line 32) | func NewRole() *Role {
  type RoleStatusInfo (line 51) | type RoleStatusInfo struct
  type RoleTreeNode (line 71) | type RoleTreeNode struct

FILE: internal/model/role_menu_map.go
  type RoleMenuMap (line 4) | type RoleMenuMap struct
    method TableName (line 15) | func (m *RoleMenuMap) TableName() string {
    method CreateBatch (line 19) | func (m *RoleMenuMap) CreateBatch(mappings []*RoleMenuMap) error {
    method MenuIdsByRoleIds (line 28) | func (m *RoleMenuMap) MenuIdsByRoleIds(roleIds []uint) ([]uint, error) {
    method RoleIdsByMenuIds (line 44) | func (m *RoleMenuMap) RoleIdsByMenuIds(menuIds []uint) ([]uint, error) {
    method RoleMenuMapByRoleIds (line 60) | func (m *RoleMenuMap) RoleMenuMapByRoleIds(roleIds []uint) (map[uint][...
  function NewRoleMenuMap (line 10) | func NewRoleMenuMap() *RoleMenuMap {

FILE: internal/model/sys_config.go
  constant SysConfigValueTypeString (line 10) | SysConfigValueTypeString = "string"
  constant SysConfigValueTypeNumber (line 11) | SysConfigValueTypeNumber = "number"
  constant SysConfigValueTypeBool (line 12) | SysConfigValueTypeBool   = "bool"
  constant SysConfigValueTypeJSON (line 13) | SysConfigValueTypeJSON   = "json"
  type SysConfig (line 17) | type SysConfig struct
    method TableName (line 38) | func (m *SysConfig) TableName() string {
    method IsProtected (line 43) | func (m *SysConfig) IsProtected() bool {
    method FindByKey (line 57) | func (m *SysConfig) FindByKey(key string) error {
    method ExistsByKeyExcludeID (line 66) | func (m *SysConfig) ExistsByKeyExcludeID(key string, excludeID uint) (...
    method EnabledConfigs (line 83) | func (m *SysConfig) EnabledConfigs(tx ...*gorm.DB) ([]SysConfig, error) {
  function NewSysConfig (line 34) | func NewSysConfig() *SysConfig {
  function NormalizeValueType (line 48) | func NormalizeValueType(valueType string) string {

FILE: internal/model/sys_dict.go
  type SysDictType (line 4) | type SysDictType struct
    method TableName (line 39) | func (m *SysDictType) TableName() string {
    method IsProtected (line 48) | func (m *SysDictType) IsProtected() bool {
    method FindByTypeCode (line 58) | func (m *SysDictType) FindByTypeCode(typeCode string) error {
    method ExistsByTypeCodeExcludeID (line 67) | func (m *SysDictType) ExistsByTypeCodeExcludeID(typeCode string, exclu...
  type SysDictItem (line 16) | type SysDictItem struct
    method TableName (line 43) | func (m *SysDictItem) TableName() string {
    method IsProtected (line 53) | func (m *SysDictItem) IsProtected() bool {
    method ExistsByValueExcludeID (line 84) | func (m *SysDictItem) ExistsByValueExcludeID(typeCode, value string, e...
    method FindByTypeCodeAndValue (line 101) | func (m *SysDictItem) FindByTypeCodeAndValue(typeCode, value string) e...
    method EnabledItemsByTypeCode (line 110) | func (m *SysDictItem) EnabledItemsByTypeCode(typeCode string) ([]SysDi...
    method CountByTypeCode (line 123) | func (m *SysDictItem) CountByTypeCode(typeCode string) (int64, error) {
  function NewSysDictType (line 31) | func NewSysDictType() *SysDictType {
  function NewSysDictItem (line 35) | func NewSysDictItem() *SysDictItem {

FILE: internal/model/sys_i18n.go
  type SysConfigI18n (line 11) | type SysConfigI18n struct
    method TableName (line 44) | func (m *SysConfigI18n) TableName() string {
    method UpsertConfigNames (line 56) | func (m *SysConfigI18n) UpsertConfigNames(configID uint, localeNames m...
    method LocalizedNameMapByConfigIDs (line 155) | func (m *SysConfigI18n) LocalizedNameMapByConfigIDs(configIDs []uint, ...
    method LocaleNameMapByConfigID (line 175) | func (m *SysConfigI18n) LocaleNameMapByConfigID(configID uint) (map[st...
    method DeleteByConfigIDs (line 199) | func (m *SysConfigI18n) DeleteByConfigIDs(configIDs []uint, tx ...*gor...
    method listRowsByConfigIDs (line 329) | func (m *SysConfigI18n) listRowsByConfigIDs(configIDs []uint, localePr...
  type SysDictTypeI18n (line 18) | type SysDictTypeI18n struct
    method TableName (line 48) | func (m *SysDictTypeI18n) TableName() string {
    method UpsertTypeNames (line 89) | func (m *SysDictTypeI18n) UpsertTypeNames(dictTypeID uint, localeNames...
    method LocalizedNameMapByTypeIDs (line 213) | func (m *SysDictTypeI18n) LocalizedNameMapByTypeIDs(typeIDs []uint, lo...
    method LocaleNameMapByTypeID (line 233) | func (m *SysDictTypeI18n) LocaleNameMapByTypeID(dictTypeID uint) (map[...
    method DeleteByTypeIDs (line 257) | func (m *SysDictTypeI18n) DeleteByTypeIDs(dictTypeIDs []uint, tx ...*g...
    method listRowsByTypeIDs (line 345) | func (m *SysDictTypeI18n) listRowsByTypeIDs(typeIDs []uint, localePrio...
  type SysDictItemI18n (line 25) | type SysDictItemI18n struct
    method TableName (line 52) | func (m *SysDictItemI18n) TableName() string {
    method UpsertLabels (line 122) | func (m *SysDictItemI18n) UpsertLabels(dictItemID uint, localeLabels m...
    method LocalizedLabelMapByItemIDs (line 271) | func (m *SysDictItemI18n) LocalizedLabelMapByItemIDs(itemIDs []uint, l...
    method LocaleLabelMapByItemID (line 291) | func (m *SysDictItemI18n) LocaleLabelMapByItemID(dictItemID uint) (map...
    method DeleteByItemIDs (line 315) | func (m *SysDictItemI18n) DeleteByItemIDs(dictItemIDs []uint, tx ...*g...
    method listRowsByItemIDs (line 361) | func (m *SysDictItemI18n) listRowsByItemIDs(itemIDs []uint, localePrio...
  function NewSysConfigI18n (line 32) | func NewSysConfigI18n() *SysConfigI18n {
  function NewSysDictTypeI18n (line 36) | func NewSysDictTypeI18n() *SysDictTypeI18n {
  function NewSysDictItemI18n (line 40) | func NewSysDictItemI18n() *SysDictItemI18n {
  function normalizeLocalePriority (line 377) | func normalizeLocalePriority(localePriority []string) []string {
  function pickLocalizedText (line 394) | func pickLocalizedText(localeText map[string]string, priorities []string...

FILE: internal/model/task_center.go
  constant TaskKindAsync (line 8) | TaskKindAsync = "async"
  constant TaskKindCron (line 9) | TaskKindCron  = "cron"
  constant TaskStatusEnabled (line 11) | TaskStatusEnabled  uint8 = 1
  constant TaskStatusDisabled (line 12) | TaskStatusDisabled uint8 = 0
  constant TaskManualAllowed (line 14) | TaskManualAllowed    uint8 = 1
  constant TaskManualNotAllowed (line 15) | TaskManualNotAllowed uint8 = 0
  constant TaskRetryAllowed (line 17) | TaskRetryAllowed    uint8 = 1
  constant TaskRetryNotAllowed (line 18) | TaskRetryNotAllowed uint8 = 0
  constant TaskHighRisk (line 20) | TaskHighRisk    uint8 = 1
  constant TaskNotHighRisk (line 21) | TaskNotHighRisk uint8 = 0
  constant TaskSourceQueue (line 23) | TaskSourceQueue  = "queue"
  constant TaskSourceCron (line 24) | TaskSourceCron   = "cron"
  constant TaskSourceManual (line 25) | TaskSourceManual = "manual"
  constant TaskRunStatusPending (line 27) | TaskRunStatusPending  = "pending"
  constant TaskRunStatusRunning (line 28) | TaskRunStatusRunning  = "running"
  constant TaskRunStatusSuccess (line 29) | TaskRunStatusSuccess  = "success"
  constant TaskRunStatusFailed (line 30) | TaskRunStatusFailed   = "failed"
  constant TaskRunStatusCanceled (line 31) | TaskRunStatusCanceled = "canceled"
  constant TaskRunStatusRetrying (line 32) | TaskRunStatusRetrying = "retrying"
  constant TaskEventEnqueue (line 34) | TaskEventEnqueue = "enqueue"
  constant TaskEventStart (line 35) | TaskEventStart   = "start"
  constant TaskEventRetry (line 36) | TaskEventRetry   = "retry"
  constant TaskEventFail (line 37) | TaskEventFail    = "fail"
  constant TaskEventSuccess (line 38) | TaskEventSuccess = "success"
  constant TaskEventCancel (line 39) | TaskEventCancel  = "cancel"
  type TaskDefinition (line 43) | type TaskDefinition struct
    method TableName (line 62) | func (m *TaskDefinition) TableName() string {
  function NewTaskDefinition (line 58) | func NewTaskDefinition() *TaskDefinition {
  type TaskRun (line 67) | type TaskRun struct
    method TableName (line 90) | func (m *TaskRun) TableName() string {
  function NewTaskRun (line 86) | func NewTaskRun() *TaskRun {
  type TaskRunEvent (line 95) | type TaskRunEvent struct
    method TableName (line 107) | func (m *TaskRunEvent) TableName() string {
  function NewTaskRunEvent (line 103) | func NewTaskRunEvent() *TaskRunEvent {
  type CronTaskState (line 112) | type CronTaskState struct
    method TableName (line 128) | func (m *CronTaskState) TableName() string {
  function NewCronTaskState (line 124) | func NewCronTaskState() *CronTaskState {

FILE: internal/pkg/auditdiff/diff.go
  type ChangeDiffItem (line 12) | type ChangeDiffItem struct
  type FieldRule (line 22) | type FieldRule struct
  function BuildFieldDiff (line 30) | func BuildFieldDiff(before, after map[string]any, rules []FieldRule) []C...
  function Marshal (line 69) | func Marshal(items []ChangeDiffItem) string {
  function formatDisplayValue (line 80) | func formatDisplayValue(rule FieldRule, value any) string {
  function valueLabelKey (line 94) | func valueLabelKey(value any) string {
  function valuesEqual (line 131) | func valuesEqual(before, after any) bool {

FILE: internal/pkg/auditdiff/diff_test.go
  function TestBuildFieldDiffMapsStatusLabel (line 8) | func TestBuildFieldDiffMapsStatusLabel(t *testing.T) {
  function TestMarshalReturnsJSONString (line 35) | func TestMarshalReturnsJSONString(t *testing.T) {
  function TestMarshalReturnsEmptyArrayWhenNoDiff (line 54) | func TestMarshalReturnsEmptyArrayWhenNoDiff(t *testing.T) {

FILE: internal/pkg/errors/code.go
  constant SUCCESS (line 9) | SUCCESS                   = 0
  constant FAILURE (line 10) | FAILURE                   = 1
  constant AuthorizationErr (line 11) | AuthorizationErr          = 403
  constant NotFound (line 12) | NotFound                  = 404
  constant CaptchaErr (line 13) | CaptchaErr                = 400
  constant NotLogin (line 14) | NotLogin                  = 401
  constant ServerErr (line 15) | ServerErr                 = 500
  constant InvalidParameter (line 16) | InvalidParameter          = 10000
  constant UserDoesNotExist (line 17) | UserDoesNotExist          = 10001
  constant UserDisable (line 18) | UserDisable               = 10002
  constant ServiceDependencyNotReady (line 19) | ServiceDependencyNotReady = 10003
  constant TooManyRequests (line 20) | TooManyRequests           = 10102
  constant FileIdentifierInvalid (line 23) | FileIdentifierInvalid = 11001
  constant FilePrivateAuthNeeded (line 24) | FilePrivateAuthNeeded = 11002
  constant FileAccessDenied (line 25) | FileAccessDenied      = 11003
  constant FileUploadPartialFail (line 26) | FileUploadPartialFail = 11004
  constant FileReferenced (line 27) | FileReferenced        = 11005
  constant UserPasswordWrong (line 30) | UserPasswordWrong       = 20001
  constant UserExists (line 31) | UserExists              = 20002
  constant PhoneNumberExists (line 32) | PhoneNumberExists       = 20003
  constant EmailExists (line 33) | EmailExists             = 20004
  constant UsernameRequired (line 34) | UsernameRequired        = 20005
  constant NicknameRequired (line 35) | NicknameRequired        = 20006
  constant PasswordProcessFailed (line 36) | PasswordProcessFailed   = 20007
  constant SuperAdminCannotModify (line 37) | SuperAdminCannotModify  = 20008
  constant SuperAdminCannotDisable (line 38) | SuperAdminCannotDisable = 20009
  constant SuperAdminCannotDelete (line 39) | SuperAdminCannotDelete  = 20010
  constant SamePassword (line 40) | SamePassword            = 20011
  constant RoleNotFound (line 41) | RoleNotFound            = 20012
  constant RoleExists (line 42) | RoleExists              = 20013
  constant RoleHasChildren (line 43) | RoleHasChildren         = 20014
  constant RoleCannotDelete (line 44) | RoleCannotDelete        = 20015
  constant ParentRoleNotExists (line 45) | ParentRoleNotExists     = 20016
  constant ParentRoleInvalid (line 46) | ParentRoleInvalid       = 20017
  constant MaxRoleDepth (line 47) | MaxRoleDepth            = 20018
  constant MaxChildRoles (line 48) | MaxChildRoles           = 20019
  constant MenuNotFound (line 49) | MenuNotFound            = 20020
  constant MenuExists (line 50) | MenuExists              = 20021
  constant MenuHasChildren (line 51) | MenuHasChildren         = 20022
  constant MenuCannotDelete (line 52) | MenuCannotDelete        = 20023
  constant DepartmentNotFound (line 53) | DepartmentNotFound      = 20024
  constant DepartmentExists (line 54) | DepartmentExists        = 20025
  constant DepartmentHasChildren (line 55) | DepartmentHasChildren   = 20026
  constant DepartmentCannotDelete (line 56) | DepartmentCannotDelete  = 20027
  constant ParentDeptNotExists (line 57) | ParentDeptNotExists     = 20028
  constant ParentDeptInvalid (line 58) | ParentDeptInvalid       = 20029
  constant MaxDeptDepth (line 59) | MaxDeptDepth            = 20030
  constant CasbinInitFailed (line 60) | CasbinInitFailed        = 20031
  constant TokenGenerateFailed (line 61) | TokenGenerateFailed     = 20032
  constant LoginFailed (line 62) | LoginFailed             = 20033
  constant CreateUserFailed (line 63) | CreateUserFailed        = 20034
  constant UpdateUserFailed (line 64) | UpdateUserFailed        = 20035
  constant DeleteUserFailed (line 65) | DeleteUserFailed        = 20036
  constant QueryUserDeptFailed (line 66) | QueryUserDeptFailed     = 20037
  constant SuperAdminMustKeepRole (line 67) | SuperAdminMustKeepRole  = 20038
  constant MaxMenuDepth (line 68) | MaxMenuDepth            = 20039
  constant ParentMenuNotExists (line 69) | ParentMenuNotExists     = 20040
  constant ParentMenuTypeInvalid (line 70) | ParentMenuTypeInvalid   = 20041
  constant ParentMenuInvalid (line 71) | ParentMenuInvalid       = 20042
  constant MenuCodeExists (line 72) | MenuCodeExists          = 20043
  constant MenuRouteNameExists (line 73) | MenuRouteNameExists     = 20044
  constant MenuPathExists (line 74) | MenuPathExists          = 20045
  constant LoginAccountLocked (line 75) | LoginAccountLocked      = 20046
  constant PasswordRequired (line 76) | PasswordRequired        = 20047
  constant MsgKeyAuthSessionExpired (line 80) | MsgKeyAuthSessionExpired        = "auth.session.expired"
  constant MsgKeyAuthPermissionInitFailed (line 81) | MsgKeyAuthPermissionInitFailed  = "auth.permission.init_failed"
  constant MsgKeyAuthPermissionCheckFailed (line 82) | MsgKeyAuthPermissionCheckFailed = "auth.permission.check_failed"
  constant MsgKeyAuthAPIOperationDenied (line 83) | MsgKeyAuthAPIOperationDenied    = "auth.api.operation_denied"
  constant MsgKeyAuthAccountLocked (line 84) | MsgKeyAuthAccountLocked         = "auth.account.locked"
  type ErrorText (line 88) | type ErrorText struct
    method Text (line 100) | func (et *ErrorText) Text(code int) (str string) {
    method TextByKey (line 117) | func (et *ErrorText) TextByKey(key string, args ...any) (string, bool) {
  function NewErrorText (line 93) | func NewErrorText(language string) *ErrorText {

FILE: internal/pkg/errors/code_test.go
  function TestText (line 7) | func TestText(t *testing.T) {
  function TestTextByKey (line 21) | func TestTextByKey(t *testing.T) {

FILE: internal/pkg/errors/error.go
  type BusinessError (line 13) | type BusinessError struct
    method Error (line 23) | func (e *BusinessError) Error() string {
    method GetCode (line 35) | func (e *BusinessError) GetCode() int {
    method GetMessage (line 40) | func (e *BusinessError) GetMessage() string {
    method GetMessageKey (line 45) | func (e *BusinessError) GetMessageKey() string {
    method GetMessageArgs (line 50) | func (e *BusinessError) GetMessageArgs() []any {
    method HasExplicitMessage (line 55) | func (e *BusinessError) HasExplicitMessage() bool {
    method HasMessageKey (line 60) | func (e *BusinessError) HasMessageKey() bool {
    method SetCode (line 65) | func (e *BusinessError) SetCode(code int) {
    method SetMessage (line 70) | func (e *BusinessError) SetMessage(message string) {
    method AppendContextErr (line 78) | func (e *BusinessError) AppendContextErr(err error) {
    method GetContextErr (line 83) | func (e *BusinessError) GetContextErr() []error {
  function NewBusinessError (line 88) | func NewBusinessError(code int, message ...string) *BusinessError {
  function NewBusinessErrorWithKey (line 105) | func NewBusinessErrorWithKey(code int, messageKey string, messageArgs .....
  type Error (line 126) | type Error struct
    method AsBusinessError (line 129) | func (e *Error) AsBusinessError(err error) (*BusinessError, error) {
  function NewDependencyNotReadyError (line 138) | func NewDependencyNotReadyError(message ...string) *BusinessError {
  function IsDependencyNotReady (line 143) | func IsDependencyNotReady(err error) bool {

FILE: internal/pkg/func_make/func_make.go
  type FuncMap (line 9) | type FuncMap
    method Register (line 17) | func (f FuncMap) Register(name string, fn any) error {
    method Registers (line 27) | func (f FuncMap) Registers(funcMap map[string]any) (err error) {
    method Call (line 38) | func (f FuncMap) Call(name string, params ...any) (result []reflect.Va...
  function New (line 12) | func New() FuncMap {

FILE: internal/pkg/func_make/func_make_test.go
  function TestRegisters (line 16) | func TestRegisters(t *testing.T) {
  function TestRegister (line 23) | func TestRegister(t *testing.T) {
  function TestCall (line 36) | func TestCall(t *testing.T) {

FILE: internal/pkg/i18n/locale.go
  constant LocaleZhCN (line 10) | LocaleZhCN    = "zh-CN"
  constant LocaleEnUS (line 11) | LocaleEnUS    = "en-US"
  constant DefaultLocale (line 12) | DefaultLocale = LocaleZhCN
  function NormalizeLocale (line 16) | func NormalizeLocale(locale string) string {
  function ParseAcceptLanguage (line 33) | func ParseAcceptLanguage(headerValue string) string {
  function ParseLocaleMap (line 56) | func ParseLocaleMap(raw string) map[string]string {
  function MarshalLocaleMap (line 78) | func MarshalLocaleMap(data map[string]string) string {
  function ResolveLocalizedText (line 103) | func ResolveLocalizedText(defaultText string, i18nRaw string, locale str...
  function MergeLocaleJSON (line 132) | func MergeLocaleJSON(existingRaw string, incoming map[string]string, loc...
  function ToErrorLanguage (line 162) | func ToErrorLanguage(locale string) string {

FILE: internal/pkg/i18n/locale_test.go
  function TestParseAcceptLanguage (line 5) | func TestParseAcceptLanguage(t *testing.T) {
  function TestResolveLocalizedText (line 26) | func TestResolveLocalizedText(t *testing.T) {
  function TestMergeLocaleJSON (line 39) | func TestMergeLocaleJSON(t *testing.T) {

FILE: internal/pkg/logger/logger.go
  function InitLogger (line 30) | func InitLogger() error {
  function ReloadLogger (line 43) | func ReloadLogger(cfg *config.Conf) error {
  function current (line 60) | func current() *zap.Logger {
  function setLogger (line 70) | func setLogger(logger *zap.Logger) {
  function ReplaceLoggerForTesting (line 79) | func ReplaceLoggerForTesting(logger *zap.Logger) func() {
  function Info (line 93) | func Info(msg string, fields ...zap.Field) {
  function Error (line 98) | func Error(msg string, fields ...zap.Field) {
  function Warn (line 103) | func Warn(msg string, fields ...zap.Field) {
  function buildLogger (line 108) | func buildLogger(cfg *config.Conf) (*zap.Logger, error) {
  function getRotateWriter (line 141) | func getRotateWriter(cfg *config.Conf, filename string) (io.Writer, erro...
  function getLumberJackWriter (line 157) | func getLumberJackWriter(cfg *config.Conf, filename string) io.Writer {

FILE: internal/pkg/logger/logger_test.go
  function TestLoggerDefaultIsNotNil (line 5) | func TestLoggerDefaultIsNotNil(t *testing.T) {
  function TestLoggerWrappersDoNotPanic (line 11) | func TestLoggerWrappersDoNotPanic(t *testing.T) {

FILE: internal/pkg/query_builder/query_builder.go
  type QueryBuilder (line 5) | type QueryBuilder struct
    method AddCondition (line 17) | func (qb *QueryBuilder) AddCondition(cond string, args ...any) *QueryB...
    method AddLike (line 25) | func (qb *QueryBuilder) AddLike(field, value string) *QueryBuilder {
    method AddEq (line 33) | func (qb *QueryBuilder) AddEq(field string, value any) *QueryBuilder {
    method AddIn (line 41) | func (qb *QueryBuilder) AddIn(field string, values []uint) *QueryBuild...
    method AddExists (line 49) | func (qb *QueryBuilder) AddExists(subQuery string) *QueryBuilder {
    method AddConditionf (line 56) | func (qb *QueryBuilder) AddConditionf(cond string, args ...any) *Query...
    method AddKeywordLike (line 60) | func (qb *QueryBuilder) AddKeywordLike(keyword string, fields ...strin...
    method Build (line 76) | func (qb *QueryBuilder) Build() (string, []any) {
  function New (line 10) | func New() *QueryBuilder {
  function hasValue (line 87) | func hasValue(value any) bool {

FILE: internal/pkg/query_builder/query_builder_test.go
  function TestQueryBuilderBuildsExpectedCondition (line 5) | func TestQueryBuilderBuildsExpectedCondition(t *testing.T) {
  function TestQueryBuilderSkipsEmptyValues (line 24) | func TestQueryBuilderSkipsEmptyValues(t *testing.T) {

FILE: internal/pkg/request/request.go
  function GetQueryParams (line 15) | func GetQueryParams(c *gin.Context) map[string]any {
  function GetAccessToken (line 28) | func GetAccessToken(c *gin.Context) (string, error) {

FILE: internal/pkg/request/request_test.go
  function TestGetAccessTokenSuccess (line 11) | func TestGetAccessTokenSuccess(t *testing.T) {
  function TestGetAccessTokenNilContext (line 27) | func TestGetAccessTokenNilContext(t *testing.T) {
  function TestGetQueryParamsNilContext (line 34) | func TestGetQueryParamsNilContext(t *testing.T) {

FILE: internal/pkg/response/response.go
  type Result (line 17) | type Result struct
  function NewResult (line 26) | func NewResult() *Result {
  type Response (line 37) | type Response struct
    method Fail (line 53) | func (r *Response) Fail(c *gin.Context, code int, msg string, data ......
    method FailCode (line 63) | func (r *Response) FailCode(c *gin.Context, code int, msg ...string) {
    method FailCodeByKey (line 72) | func (r *Response) FailCodeByKey(c *gin.Context, code int, key string,...
    method Success (line 79) | func (r *Response) Success(c *gin.Context) {
    method WithDataSuccess (line 85) | func (r *Response) WithDataSuccess(c *gin.Context, data interface{}) {
    method SetCode (line 92) | func (r *Response) SetCode(code int) *Response {
    method SetHttpCode (line 98) | func (r *Response) SetHttpCode(code int) *Response {
    method WithData (line 109) | func (r *Response) WithData(data any) *Response {
    method SetMessage (line 123) | func (r *Response) SetMessage(message string) *Response {
    method SetMessageKey (line 131) | func (r *Response) SetMessageKey(key string, args ...any) *Response {
    method json (line 138) | func (r *Response) json(c *gin.Context) {
  function Resp (line 45) | func Resp() *Response {
  type defaultRes (line 104) | type defaultRes struct
  function Success (line 167) | func Success(c *gin.Context, data ...any) {
  function FailCode (line 176) | func FailCode(c *gin.Context, code int, data ...any) {
  function FailCodeByKey (line 185) | func FailCodeByKey(c *gin.Context, code int, key string, args ...any) {
  function Fail (line 190) | func Fail(c *gin.Context, code int, message string, data ...any) {
  function emptyObject (line 198) | func emptyObject() map[string]any {
  function isNilData (line 202) | func isNilData(data any) bool {
  function isObjectData (line 216) | func isObjectData(data any) bool {

FILE: internal/pkg/response/response_test.go
  function TestSuccessDefaultsDataToEmptyObject (line 15) | func TestSuccessDefaultsDataToEmptyObject(t *testing.T) {
  function TestWithNilDataReturnsEmptyObject (line 38) | func TestWithNilDataReturnsEmptyObject(t *testing.T) {
  function TestScalarDataStillWrapped (line 61) | func TestScalarDataStillWrapped(t *testing.T) {
  function TestInt64DataStillWrapped (line 83) | func TestInt64DataStillWrapped(t *testing.T) {
  function TestTypedNilPointerReturnsEmptyObject (line 105) | func TestTypedNilPointerReturnsEmptyObject(t *testing.T) {
  function TestNilSliceReturnsEmptyObject (line 133) | func TestNilSliceReturnsEmptyObject(t *testing.T) {
  function TestSliceDataWrappedAsObject (line 157) | func TestSliceDataWrappedAsObject(t *testing.T) {
  function TestFailCodeByKeyResolvesMessage (line 179) | func TestFailCodeByKeyResolvesMessage(t *testing.T) {

FILE: internal/pkg/testkit/secret.go
  function SecretKey (line 6) | func SecretKey(scope string) string {

FILE: internal/pkg/testkit/secret_test.go
  function TestSecretKey (line 5) | func TestSecretKey(t *testing.T) {

FILE: internal/pkg/utils/desensitize.go
  type DesensitizeRule (line 9) | type DesensitizeRule struct
    method Apply (line 28) | func (r *DesensitizeRule) Apply(s string) string {
    method applyToPart (line 45) | func (r *DesensitizeRule) applyToPart(s string) string {
    method min (line 76) | func (r *DesensitizeRule) min(a, b int) int {
  function NewPhoneRule (line 18) | func NewPhoneRule() *DesensitizeRule {
  function NewEmailRule (line 23) | func NewEmailRule() *DesensitizeRule {

FILE: internal/pkg/utils/format_time.go
  type FormatDate (line 11) | type FormatDate struct
    method MarshalJSON (line 20) | func (t FormatDate) MarshalJSON() ([]byte, error) {
    method Value (line 28) | func (t FormatDate) Value() (driver.Value, error) {
    method Scan (line 37) | func (t *FormatDate) Scan(v interface{}) error {
    method String (line 46) | func (t *FormatDate) String() string {
    method UnmarshalJSON (line 54) | func (t *FormatDate) UnmarshalJSON(data []byte) error {
  constant timeFormat (line 16) | timeFormat = "2006-01-02 15:04:05"

FILE: internal/pkg/utils/sensitive/fields.go
  constant maskTokenPrefixLen (line 12) | maskTokenPrefixLen    = 6
  constant maskTokenSuffixLen (line 13) | maskTokenSuffixLen    = 6
  constant maskPhonePrefixLen (line 14) | maskPhonePrefixLen    = 3
  constant maskPhoneSuffixLen (line 15) | maskPhoneSuffixLen    = 4
  constant maskEmailPrefixLen (line 16) | maskEmailPrefixLen    = 2
  constant maskIdCardPrefixLen (line 17) | maskIdCardPrefixLen   = 6
  constant maskIdCardSuffixLen (line 18) | maskIdCardSuffixLen   = 4
  constant maskBankCardPrefixLen (line 19) | maskBankCardPrefixLen = 4
  constant maskBankCardSuffixLen (line 20) | maskBankCardSuffixLen = 4
  constant maskDefaultPrefixLen (line 21) | maskDefaultPrefixLen  = 1
  constant maskDefaultSuffixLen (line 22) | maskDefaultSuffixLen  = 1
  type SensitiveFieldsConfig (line 26) | type SensitiveFieldsConfig struct
  type sensitiveFieldsManager (line 34) | type sensitiveFieldsManager struct
    method applyConfig (line 81) | func (m *sensitiveFieldsManager) applyConfig(config SensitiveFieldsCon...
    method cloneFieldSet (line 203) | func (m *sensitiveFieldsManager) cloneFieldSet(source map[string]bool)...
  function currentFieldsManager (line 53) | func currentFieldsManager() *sensitiveFieldsManager {
  function newSensitiveFieldsManager (line 69) | func newSensitiveFieldsManager(config SensitiveFieldsConfig) *sensitiveF...
  function defaultSensitiveFieldsConfig (line 89) | func defaultSensitiveFieldsConfig() SensitiveFieldsConfig {
  function DefaultSensitiveFieldsConfig (line 138) | func DefaultSensitiveFieldsConfig() SensitiveFieldsConfig {
  function LoadSensitiveFieldsConfig (line 143) | func LoadSensitiveFieldsConfig(config SensitiveFieldsConfig) {
  function GetSensitiveFieldsConfig (line 151) | func GetSensitiveFieldsConfig() SensitiveFieldsConfig {
  function sliceToMap (line 165) | func sliceToMap(slice []string) map[string]bool {
  function getCommonFields (line 178) | func getCommonFields() map[string]bool {
  function getRequestHeaderFields (line 183) | func getRequestHeaderFields() map[string]bool {
  function getRequestBodyFields (line 188) | func getRequestBodyFields() map[string]bool {
  function getResponseHeaderFields (line 193) | func getResponseHeaderFields() map[string]bool {
  function getResponseBodyFields (line 198) | func getResponseBodyFields() map[string]bool {
  function mapKeys (line 214) | func mapKeys(source map[string]bool) []string {

FILE: internal/pkg/utils/sensitive/http_mask.go
  function maskHeaders (line 11) | func maskHeaders(headers http.Header, sensitiveFields map[string]bool) s...
  function GetMaskedRequestHeaders (line 33) | func GetMaskedRequestHeaders(headers http.Header) string {
  function GetMaskedResponseHeaders (line 38) | func GetMaskedResponseHeaders(headers http.Header) string {
  function GetMaskedRequestBody (line 43) | func GetMaskedRequestBody(bodyBytes []byte, contentType string) string {
  function GetMaskedResponseBody (line 62) | func GetMaskedResponseBody(bodyBytes []byte) string {
  function MaskQueryString (line 73) | func MaskQueryString(queryString string) string {
  function maskJSONBytes (line 95) | func maskJSONBytes(bodyBytes []byte, sensitiveFields map[string]bool) st...
  function maskStringSlice (line 109) | func maskStringSlice(values []string, fn func(string) string) []string {
  function isValidUTF8 (line 117) | func isValidUTF8(data []byte) bool {

FILE: internal/pkg/utils/sensitive/mask.go
  function maskMap (line 6) | func maskMap(m map[string]interface{}, sensitiveFields map[string]bool) ...
  function maskSensitiveDataWithFields (line 23) | func maskSensitiveDataWithFields(data interface{}, sensitiveFields map[s...
  function maskArrayWithFields (line 37) | func maskArrayWithFields(arr []interface{}, sensitiveFields map[string]b...

FILE: internal/pkg/utils/sensitive/string_mask.go
  function maskString (line 5) | func maskString(s string) string {
  function maskValue (line 12) | func maskValue(v interface{}) interface{} {
  function maskSensitiveString (line 28) | func maskSensitiveString(s string) string {
  function authPrefix (line 44) | func authPrefix(s string) (string, bool) {
  function applyMatchers (line 57) | func applyMatchers(s string, replacers ...func(string) string) string {
  function maskAuthToken (line 74) | func maskAuthToken(s, prefix string) string {
  function isSensitiveField (line 86) | func isSensitiveField(fieldName string, sensitiveFields map[string]bool)...
  function maskToken (line 101) | func maskToken(token string) string {
  function maskPhone (line 109) | func maskPhone(phone string) string {
  function maskEmail (line 116) | func maskEmail(email string) string {
  function maskIdCard (line 130) | func maskIdCard(idCard string) string {
  function maskBankCard (line 141) | func maskBankCard(cardNo string) string {
  function maskDefault (line 150) | func maskDefault(s string) string {

FILE: internal/pkg/utils/token/jwt.go
  type AdminUserInfo (line 19) | type AdminUserInfo struct
  function Generate (line 32) | func Generate(claims jwt.Claims) (string, error) {
  function Refresh (line 45) | func Refresh(claims jwt.Claims) (string, error) {
  function Parse (line 50) | func Parse(accessToken string, claims jwt.Claims, options ...jwt.ParserO...
  function GetAccessToken (line 70) | func GetAccessToken(authorization string) (accessToken string, err error) {
  type AdminCustomClaims (line 86) | type AdminCustomClaims struct
  function NewAdminCustomClaims (line 92) | func NewAdminCustomClaims(user *model.AdminUser) AdminCustomClaims {

FILE: internal/pkg/utils/token/jwt_test.go
  function TestGenerate (line 9) | func TestGenerate(t *testing.T) {
  function TestParse (line 19) | func TestParse(t *testing.T) {
  function TestGetAccessToken (line 28) | func TestGetAccessToken(t *testing.T) {

FILE: internal/pkg/utils/utils.go
  function CalculateChanges (line 28) | func CalculateChanges[T comparable](existingIds, ids []T) (toDelete, toA...
  function RandString (line 41) | func RandString(n int) string {
  function TrimPrefixAndSuffixAND (line 66) | func TrimPrefixAndSuffixAND(s string) string {

FILE: internal/pkg/utils/utils_test.go
  function TestCalculateChanges (line 8) | func TestCalculateChanges(t *testing.T) {
  function TestRandString (line 17) | func TestRandString(t *testing.T) {
  function BenchmarkRandString (line 24) | func BenchmarkRandString(b *testing.B) {
  function TestDesensitizeRule (line 31) | func TestDesensitizeRule(b *testing.T) {
  function BenchmarkTrimPrefixAndSuffixAND (line 44) | func BenchmarkTrimPrefixAndSuffixAND(b *testing.B) {

FILE: internal/queue/asynqx/asynq.go
  function init (line 19) | func init() {
  type publisher (line 24) | type publisher struct
    method Enqueue (line 75) | func (p *publisher) Enqueue(ctx context.Context, job queue.Job) (queue...
  type inspector (line 29) | type inspector struct
    method DeleteTask (line 100) | func (i *inspector) DeleteTask(ctx context.Context, queueName, taskID ...
    method CancelProcessing (line 108) | func (i *inspector) CancelProcessing(ctx context.Context, taskID strin...
  function NewPublisher (line 35) | func NewPublisher(cfg *config.Conf) (queue.Publisher, error) {
  function NewInspector (line 56) | func NewInspector(cfg *config.Conf) (queue.Inspector, error) {
  function NewServer (line 117) | func NewServer(cfg *config.Conf, registry queue.Registry) (*asynq.Server...
  function recordAsynqTaskStart (line 163) | func recordAsynqTaskStart(ctx context.Context, task *asynq.Task) *model....
  function recordAsynqTaskFinish (line 194) | func recordAsynqTaskFinish(ctx context.Context, run *model.TaskRun, task...
  function currentQueueNamespace (line 206) | func currentQueueNamespace() string {
  function newRedisConnOpt (line 214) | func newRedisConnOpt(cfg *config.Conf) (asynq.RedisClientOpt, error) {
  function normalizeInspectorError (line 247) | func normalizeInspectorError(err error) error {
  function mapOptions (line 260) | func mapOptions(namespace string, options []queue.JobOption) []asynq.Opt...
  function prefixedQueues (line 279) | func prefixedQueues(namespace string, queues map[string]int) map[string]...
  function prefixedQueueName (line 291) | func prefixedQueueName(namespace, name string) string {
  function unprefixQueueName (line 303) | func unprefixQueueName(namespace, name string) string {

FILE: internal/queue/asynqx/asynq_test.go
  function TestPrefixedQueueName (line 14) | func TestPrefixedQueueName(t *testing.T) {
  function TestMapOptions (line 26) | func TestMapOptions(t *testing.T) {
  function TestNewRedisConnOptUsesDefaultRedis (line 52) | func TestNewRedisConnOptUsesDefaultRedis(t *testing.T) {
  function TestNewRedisConnOptUsesQueueRedis (line 79) | func TestNewRedisConnOptUsesQueueRedis(t *testing.T) {
  function TestNewRedisConnOptReturnsErrorWhenDefaultRedisDisabled (line 108) | func TestNewRedisConnOptReturnsErrorWhenDefaultRedisDisabled(t *testing....

FILE: internal/queue/asynqx/inspector_test.go
  function TestNormalizeInspectorError (line 12) | func TestNormalizeInspectorError(t *testing.T) {

FILE: internal/queue/asynqx/task_record_test.go
  function TestRecordAsynqTaskStartAndFinish (line 14) | func TestRecordAsynqTaskStartAndFinish(t *testing.T) {
  type fakeRunRecorder (line 44) | type fakeRunRecorder struct
    method Enqueue (line 49) | func (f *fakeRunRecorder) Enqueue(ctx context.Context, input taskcente...
    method Start (line 55) | func (f *fakeRunRecorder) Start(ctx context.Context, input taskcenter....
    method Finish (line 61) | func (f *fakeRunRecorder) Finish(ctx context.Context, run *model.TaskR...

FILE: internal/queue/queue.go
  constant DefaultQueue (line 22) | DefaultQueue = "default"
  type Job (line 25) | type Job interface
  type JobInfo (line 33) | type JobInfo struct
  type Publisher (line 40) | type Publisher interface
  type Inspector (line 45) | type Inspector interface
  type Handler (line 51) | type Handler
  type Registry (line 54) | type Registry interface
  type Registration (line 60) | type Registration struct
  type Validatable (line 66) | type Validatable interface
  type JobOptionType (line 71) | type JobOptionType
  constant JobOptionMaxRetry (line 74) | JobOptionMaxRetry  JobOptionType = "max_retry"
  constant JobOptionQueue (line 75) | JobOptionQueue     JobOptionType = "queue"
  constant JobOptionTimeout (line 76) | JobOptionTimeout   JobOptionType = "timeout"
  constant JobOptionRetention (line 77) | JobOptionRetention JobOptionType = "retention"
  constant JobOptionTaskID (line 78) | JobOptionTaskID    JobOptionType = "task_id"
  type JobOption (line 82) | type JobOption struct
  type jsonJob (line 89) | type jsonJob struct
    method Type (line 169) | func (j *jsonJob) Type() string {
    method Queue (line 173) | func (j *jsonJob) Queue() string {
    method Payload (line 180) | func (j *jsonJob) Payload() ([]byte, error) {
    method Options (line 184) | func (j *jsonJob) Options() []JobOption {
  type skipRetryError (line 96) | type skipRetryError struct
    method Error (line 188) | func (e *skipRetryError) Error() string {
    method Unwrap (line 195) | func (e *skipRetryError) Unwrap() error {
    method Is (line 202) | func (e *skipRetryError) Is(target error) bool {
  function WithMaxRetry (line 100) | func WithMaxRetry(n int) JobOption {
  function WithQueue (line 104) | func WithQueue(name string) JobOption {
  function WithTimeout (line 108) | func WithTimeout(timeout time.Duration) JobOption {
  function WithRetention (line 112) | func WithRetention(retention time.Duration) JobOption {
  function WithTaskID (line 116) | func WithTaskID(taskID string) JobOption {
  function NewJSONJob (line 121) | func NewJSONJob(taskType, queueName string, payload any, opts ...JobOpti...
  function Publish (line 134) | func Publish(ctx context.Context, job Job) (JobInfo, error) {
  function PublishJSON (line 143) | func PublishJSON(ctx context.Context, taskType, queueName string, payloa...
  function RegisterJSON (line 148) | func RegisterJSON[T any](registry Registry, taskType string, handler fun...
  function SkipRetry (line 165) | func SkipRetry(err error) error {
  type publisherFactory (line 206) | type publisherFactory
  type inspectorFactory (line 207) | type inspectorFactory
  function RegisterPublisherFactory (line 222) | func RegisterPublisherFactory(factory func(cfg *config.Conf) (Publisher,...
  function InitPublisher (line 229) | func InitPublisher(cfg *config.Conf) error {
  function RegisterInspectorFactory (line 256) | func RegisterInspectorFactory(factory func(cfg *config.Conf) (Inspector,...
  function InitInspector (line 263) | func InitInspector(cfg *config.Conf) error {
  function PublisherOrNil (line 290) | func PublisherOrNil() Publisher {
  function PublisherInitError (line 297) | func PublisherInitError() error {
  function InspectorOrNil (line 304) | func InspectorOrNil() Inspector {
  function InspectorInitError (line 311) | func InspectorInitError() error {
  function DeleteTask (line 318) | func DeleteTask(ctx context.Context, queueName, taskID string) error {
  function CancelProcessing (line 327) | func CancelProcessing(ctx context.Context, taskID string) error {
  function SetPublisherForTesting (line 336) | func SetPublisherForTesting(publisher Publisher) func() {
  function SetInspectorForTesting (line 353) | func SetInspectorForTesting(inspector Inspector) func() {
  function validatePayload (line 369) | func validatePayload(payload any) error {
  type memoryRegistry (line 379) | type memoryRegistry struct
    method Register (line 391) | func (r *memoryRegistry) Register(taskType string, handler Handler) {
    method Entries (line 400) | func (r *memoryRegistry) Entries() []Registration {
  function NewRegistry (line 385) | func NewRegistry() Registry {

FILE: internal/queue/queue_test.go
  type testPayload (line 12) | type testPayload struct
    method Validate (line 16) | func (p testPayload) Validate() error {
  type stubPublisher (line 23) | type stubPublisher struct
    method Enqueue (line 35) | func (s *stubPublisher) Enqueue(ctx context.Context, job Job) (JobInfo...
  type stubInspector (line 27) | type stubInspector struct
    method DeleteTask (line 41) | func (s *stubInspector) DeleteTask(ctx context.Context, queueName, tas...
    method CancelProcessing (line 49) | func (s *stubInspector) CancelProcessing(ctx context.Context, taskID s...
  function TestPublishJSONUsesGlobalPublisher (line 56) | func TestPublishJSONUsesGlobalPublisher(t *testing.T) {
  function TestRegisterJSONDecodesAndValidatesPayload (line 73) | func TestRegisterJSONDecodesAndValidatesPayload(t *testing.T) {
  function TestRegisterJSONReturnsSkipRetryForInvalidPayload (line 99) | func TestRegisterJSONReturnsSkipRetryForInvalidPayload(t *testing.T) {
  function TestInitPublisherStoresLastError (line 121) | func TestInitPublisherStoresLastError(t *testing.T) {
  function TestDeleteTaskUsesGlobalInspector (line 151) | func TestDeleteTaskUsesGlobalInspector(t *testing.T) {
  function TestCancelProcessingUsesGlobalInspector (line 164) | func TestCancelProcessingUsesGlobalInspector(t *testing.T) {
  function TestDeleteTaskReturnsUnavailableWithoutInspector (line 177) | func TestDeleteTaskReturnsUnavailableWithoutInspector(t *testing.T) {

FILE: internal/resources/admin_user.go
  type AdminUserResources (line 12) | type AdminUserResources struct
    method SetCustomFields (line 39) | func (r *AdminUserResources) SetCustomFields(data *model.AdminUser) {
  type AdminUserTransformer (line 34) | type AdminUserTransformer struct
    method ToCollection (line 82) | func (AdminUserTransformer) ToCollection(page, perPage int, total int6...
  function NewAdminUserTransformer (line 70) | func NewAdminUserTransformer() AdminUserTransformer {

FILE: internal/resources/api.go
  type ApiResources (line 8) | type ApiResources struct
  type ApiTransformer (line 25) | type ApiTransformer struct
    method ToStruct (line 41) | func (ApiTransformer) ToStruct(data *model.Api) *ApiResources {
    method ToCollection (line 62) | func (ApiTransformer) ToCollection(page, perPage int, total int64, dat...
  function NewApiTransformer (line 30) | func NewApiTransformer() ApiTransformer {

FILE: internal/resources/base.go
  type Resources (line 12) | type Resources interface
  type CustomFieldSetter (line 18) | type CustomFieldSetter interface
  type BaseResources (line 23) | type BaseResources struct
  method ToStruct (line 28) | func (br BaseResources[T, R]) ToStruct(data T) R {
  method ToCollection (line 34) | func (br BaseResources[T, R]) ToCollection(page, perPage int, total int6...
  function ToAnySlice (line 43) | func ToAnySlice[T any](data []T) []any {
  type TreeNode (line 52) | type TreeNode interface
  type Identifiable (line 57) | type Identifiable interface
  type TreeResources (line 63) | type TreeResources interface
  type TreeResource (line 69) | type TreeResource struct
  method ToStruct (line 74) | func (tr TreeResource[T, R]) ToStruct(data T) R {
  method BuildTree (line 80) | func (tr TreeResource[T, R]) BuildTree(data []T, pidFn func(T) uint, idF...
  method BuildTreeByNode (line 107) | func (tr TreeResource[T, R]) BuildTreeByNode(data []T, rootID uint) []R {
  function toGenericStruct (line 139) | func toGenericStruct[T any, R any](data T, newFunc func() R) (R, error) {
  type Paginate (line 153) | type Paginate struct
    method calculateLastPage (line 161) | func (p *Paginate) calculateLastPage() {
  type ResponseCollectionInterface (line 182) | type ResponseCollectionInterface interface
  type Collection (line 189) | type Collection struct
    method GetPaginate (line 195) | func (p *Collection) GetPaginate() *Paginate {
    method SetPaginate (line 200) | func (p *Collection) SetPaginate(page, perPage int, total int64) *Coll...
    method ToCollection (line 211) | func (p *Collection) ToCollection(data []any) *Collection {
  function NewCollection (line 217) | func NewCollection() *Collection {
  function ToRawCollection (line 222) | func ToRawCollection[T any](page, perPage int, total int64, data []T) *C...

FILE: internal/resources/base_test.go
  type baseTestModel (line 5) | type baseTestModel struct
  type baseTestResource (line 9) | type baseTestResource struct
    method SetCustomFields (line 14) | func (r *baseTestResource) SetCustomFields(data baseTestModel) {
  function TestBaseResourcesToCollectionTransformsItems (line 18) | func TestBaseResourcesToCollectionTransformsItems(t *testing.T) {
  function TestPaginateCalculateLastPageUsesIntegerCeil (line 39) | func TestPaginateCalculateLastPageUsesIntegerCeil(t *testing.T) {

FILE: internal/resources/common.go
  type department (line 3) | type department struct

FILE: internal/resources/dept.go
  type DeptResources (line 11) | type DeptResources struct
    method SetChildren (line 29) | func (r *DeptResources) SetChildren(children []*DeptResources) {
    method GetID (line 34) | func (r *DeptResources) GetID() uint {
    method GetPID (line 39) | func (r *DeptResources) GetPID() uint {
    method SetCustomFields (line 49) | func (r *DeptResources) SetCustomFields(data *model.Department) {
  type DeptTreeTransformer (line 44) | type DeptTreeTransformer struct
  function NewDeptTreeTransformer (line 64) | func NewDeptTreeTransformer() DeptTreeTransformer {

FILE: internal/resources/file_resource.go
  type FileResourceResources (line 12) | type FileResourceResources struct
    method SetCustomFields (line 71) | func (r *FileResourceResources) SetCustomFields(data *model.UploadFile...
  type FileResourceTransformer (line 57) | type FileResourceTransformer struct
  function NewFileResourceTransformer (line 61) | func NewFileResourceTransformer() FileResourceTransformer {
  function fileStorageStatusName (line 86) | func fileStorageStatusName(status string) string {
  function fileUploadSourceName (line 101) | func fileUploadSourceName(source string) string {
  function fileUploadStatusName (line 114) | func fileUploadStatusName(status string) string {
  type FileFolderResources (line 127) | type FileFolderResources struct
  type FileFolderTransformer (line 140) | type FileFolderTransformer struct
  function NewFileFolderTransformer (line 144) | func NewFileFolderTransformer() FileFolderTransformer {
  type FileMoveResult (line 154) | type FileMoveResult struct
  type FileUploadCredentialResources (line 160) | type FileUploadCredentialResources struct
  type FileReferenceResources (line 180) | type FileReferenceResources struct
    method SetCustomFields (line 207) | func (r *FileReferenceResources) SetCustomFields(data *model.UploadFil...
  type FileReferenceTransformer (line 193) | type FileReferenceTransformer struct
  function NewFileReferenceTransformer (line 197) | func NewFileReferenceTransformer() FileReferenceTransformer {
  function fileReferenceSourceName (line 212) | func fileReferenceSourceName(ownerType string) string {
  function fileReferenceFieldName (line 221) | func fileReferenceFieldName(ownerField string) string {
  function buildFileResourceURL (line 230) | func buildFileResourceURL(uuid string) string {

FILE: internal/resources/file_resource_test.go
  function TestFileReferenceTransformerAddsDisplayNames (line 9) | func TestFileReferenceTransformerAddsDisplayNames(t *testing.T) {
  function TestFileReferenceTransformerFallsBackToRawNames (line 24) | func TestFileReferenceTransformerFallsBackToRawNames(t *testing.T) {
  function TestFileResourceTransformerAddsStatusDisplayNames (line 37) | func TestFileResourceTransformerAddsStatusDisplayNames(t *testing.T) {

FILE: internal/resources/login_log.go
  type AdminLoginLogBaseResources (line 9) | type AdminLoginLogBaseResources struct
  type AdminLoginLogListResources (line 32) | type AdminLoginLogListResources struct
  type AdminLoginLogResources (line 37) | type AdminLoginLogResources struct
  type AdminLoginLogTransformer (line 49) | type AdminLoginLogTransformer struct
    method ToStruct (line 90) | func (r AdminLoginLogTransformer) ToStruct(data *model.AdminLoginLogs)...
    method ToCollection (line 105) | func (r AdminLoginLogTransformer) ToCollection(page, perPage int, tota...
  function NewAdminLoginLogTransformer (line 54) | func NewAdminLoginLogTransformer() AdminLoginLogTransformer {
  function buildAdminLoginLogBaseResources (line 65) | func buildAdminLoginLogBaseResources(data *model.AdminLoginLogs) AdminLo...

FILE: internal/resources/login_log_test.go
  function TestAdminLoginLogTransformerOnlyExposeTokenHashesInDetail (line 12) | func TestAdminLoginLogTransformerOnlyExposeTokenHashesInDetail(t *testin...

FILE: internal/resources/menu.go
  type MenuBaseResources (line 9) | type MenuBaseResources struct
  type MenuResources (line 36) | type MenuResources struct
  type MenuTransformer (line 52) | type MenuTransformer struct
    method ToStruct (line 68) | func (m MenuTransformer) ToStruct(data *model.Menu) *MenuResources {
    method ToStructWithTitles (line 73) | func (m MenuTransformer) ToStructWithTitles(data *model.Menu, title st...
    method ToCollection (line 78) | func (m MenuTransformer) ToCollection(page, perPage int, total int64, ...
    method ToCollectionWithTitles (line 83) | func (m MenuTransformer) ToCollectionWithTitles(page, perPage int, tot...
  function NewMenuTransformer (line 57) | func NewMenuTransformer() MenuTransformer {
  function buildMenuBaseResources (line 92) | func buildMenuBaseResources(v *model.Menu, title string) MenuBaseResourc...
  function buildMenuResource (line 121) | func buildMenuResource(v *model.Menu, title string, titleI18n map[string...
  type MenuCollectionResources (line 139) | type MenuCollectionResources struct
    method SetChildren (line 145) | func (r *MenuCollectionResources) SetChildren(children []*MenuCollecti...
    method GetID (line 150) | func (r *MenuCollectionResources) GetID() uint {
    method GetPID (line 155) | func (r *MenuCollectionResources) GetPID() uint {
    method SetCustomFields (line 160) | func (r *MenuCollectionResources) SetCustomFields(data *model.Menu) {
  function buildListMenuResource (line 165) | func buildListMenuResource(v *model.Menu, title string) *MenuCollectionR...
  function BuildMenuTree (line 174) | func BuildMenuTree(data []*model.Menu, rootID uint, titleByMenuID map[ui...
  function resolveTitleByMenuID (line 203) | func resolveTitleByMenuID(menuID uint, titleByMenuID map[uint]string) st...

FILE: internal/resources/request_log.go
  type RequestLogBaseResources (line 9) | type RequestLogBaseResources struct
  type RequestLogListResources (line 28) | type RequestLogListResources struct
  type RequestLogResources (line 33) | type RequestLogResources struct
  type RequestLogTransformer (line 49) | type RequestLogTransformer struct
    method ToStruct (line 86) | func (r RequestLogTransformer) ToStruct(data *model.RequestLogs) *Requ...
    method ToCollection (line 105) | func (r RequestLogTransformer) ToCollection(page, perPage int, total i...
  function NewRequestLogTransformer (line 54) | func NewRequestLogTransformer() RequestLogTransformer {
  function buildRequestLogBaseResources (line 65) | func buildRequestLogBaseResources(data *model.RequestLogs) RequestLogBas...
  function getOperationStatusName (line 117) | func getOperationStatusName(code int) string {

FILE: internal/resources/role.go
  type RoleResources (line 11) | type RoleResources struct
    method GetID (line 29) | func (r *RoleResources) GetID() uint {
    method GetPID (line 34) | func (r *RoleResources) GetPID() uint {
    method SetCustomFields (line 39) | func (r *RoleResources) SetCustomFields(data *model.Role) {
  type RoleTransformer (line 54) | type RoleTransformer struct
  function NewRoleTransformer (line 59) | func NewRoleTransformer() RoleTransformer {

FILE: internal/resources/session.go
  type SessionResources (line 9) | type SessionResources struct
  type SessionTransformer (line 25) | type SessionTransformer struct
  function NewSessionTransformer (line 29) | func NewSessionTransformer() SessionTransformer {

FILE: internal/resources/sys_config.go
  type SysConfigResources (line 9) | type SysConfigResources struct
  type SysConfigTransformer (line 29) | type SysConfigTransformer struct
  function NewSysConfigTransformer (line 33) | func NewSysConfigTransformer() SysConfigTransformer {

FILE: internal/resources/sys_dict.go
  type SysDictTypeResources (line 9) | type SysDictTypeResources struct
  type SysDictItemResources (line 23) | type SysDictItemResources struct
  type SysDictOptionResources (line 41) | type SysDictOptionResources struct
  type SysDictTypeTransformer (line 49) | type SysDictTypeTransformer struct
  type SysDictItemTransformer (line 53) | type SysDictItemTransformer struct
  function NewSysDictTypeTransformer (line 57) | func NewSysDictTypeTransformer() SysDictTypeTransformer {
  function NewSysDictItemTransformer (line 67) | func NewSysDictItemTransformer() SysDictItemTransformer {
  function ToSysDictOptions (line 77) | func ToSysDictOptions(items []model.SysDictItem) []SysDictOptionResources {

FILE: internal/resources/task_center.go
  type TaskDefinitionResources (line 9) | type TaskDefinitionResources struct
  type TaskDefinitionTransformer (line 27) | type TaskDefinitionTransformer struct
  function NewTaskDefinitionTransformer (line 31) | func NewTaskDefinitionTransformer() TaskDefinitionTransformer {
  type TaskRunBaseResources (line 42) | type TaskRunBaseResources struct
  type TaskRunListResources (line 62) | type TaskRunListResources struct
  type TaskRunResources (line 67) | type TaskRunResources struct
  type TaskRunEventResources (line 74) | type TaskRunEventResources struct
  type TaskRunTransformer (line 84) | type TaskRunTransformer struct
    method ToStruct (line 119) | func (r TaskRunTransformer) ToStruct(data *model.TaskRun) *TaskRunReso...
    method ToCollection (line 128) | func (r TaskRunTransformer) ToCollection(page, perPage int, total int6...
  function NewTaskRunTransformer (line 88) | func NewTaskRunTransformer() TaskRunTransformer {
  function buildTaskRunBaseResources (line 98) | func buildTaskRunBaseResources(data *model.TaskRun) TaskRunBaseResources {
  type TaskRunEventTransformer (line 140) | type TaskRunEventTransformer struct
  function NewTaskRunEventTransformer (line 144) | func NewTaskRunEventTransformer() TaskRunEventTransformer {
  type CronTaskStateResources (line 155) | type CronTaskStateResources struct
  type CronTaskStateTransformer (line 169) | type CronTaskStateTransformer struct
  function NewCronTaskStateTransformer (line 173) | func NewCronTaskStateTransformer() CronTaskStateTransformer {

FILE: internal/routers/admin_router.go
  function GET (line 12) | func GET(path, title string, auth AuthMode, handlers ...gin.HandlerFunc)...
  function POST (line 16) | func POST(path, title string, auth AuthMode, handlers ...gin.HandlerFunc...
  function AdminRouteTree (line 22) | func AdminRouteTree(deps *ControllerDeps) RouteGroupDef {
  function adminOtherGroup (line 35) | func adminOtherGroup(deps *ControllerDeps) RouteGroupDef {
  function adminAuthGroup (line 55) | func adminAuthGroup(deps *ControllerDeps) RouteGroupDef {
  function dashboardGroup (line 74) | func dashboardGroup(deps *ControllerDeps) RouteGroupDef {
  function commonGroup (line 85) | func commonGroup(deps *ControllerDeps) RouteGroupDef {
  function authGroup (line 96) | func authGroup(deps *ControllerDeps) RouteGroupDef {
  function adminUserGroup (line 118) | func adminUserGroup(deps *ControllerDeps) RouteGroupDef {
  function permissionGroup (line 142) | func permissionGroup(deps *ControllerDeps) RouteGroupDef {
  function menuGroup (line 154) | func menuGroup(deps *ControllerDeps) RouteGroupDef {
  function roleGroup (line 170) | func roleGroup(deps *ControllerDeps) RouteGroupDef {
  function deptGroup (line 185) | func deptGroup(deps *ControllerDeps) RouteGroupDef {
  function systemGroup (line 201) | func systemGroup(deps *ControllerDeps) RouteGroupDef {
  function logGroup (line 271) | func logGroup(deps *ControllerDeps) RouteGroupDef {
  function taskGroup (line 298) | func taskGroup(deps *ControllerDeps) RouteGroupDef {

FILE: internal/routers/defs.go
  constant AuthModeNone (line 14) | AuthModeNone = global.ApiAuthModeNone
  constant AuthModeLogin (line 16) | AuthModeLogin = global.ApiAuthModeLogin
  constant AuthModeAuth (line 18) | AuthModeAuth = global.ApiAuthModeAuth
  type RouteDef (line 23) | type RouteDef struct
    method WithDesc (line 33) | func (r RouteDef) WithDesc(desc string) RouteDef {
  type RouteGroupDef (line 40) | type RouteGroupDef struct
  type RouteMeta (line 50) | type RouteMeta struct
  type RouteMetaMap (line 61) | type RouteMetaMap

FILE: internal/routers/deps.go
  type ControllerDeps (line 12) | type ControllerDeps struct
  function DefaultControllerDeps (line 37) | func DefaultControllerDeps() *ControllerDeps {
  function normalizeControllerDeps (line 62) | func normalizeControllerDeps(deps *ControllerDeps) *ControllerDeps {
  function MockControllerDeps (line 122) | func MockControllerDeps(deps *ControllerDeps) *ControllerDeps {

FILE: internal/routers/meta.go
  function CollectRouteMeta (line 6) | func CollectRouteMeta(root RouteGroupDef) RouteMetaMap {
  function collectRouteMeta (line 12) | func collectRouteMeta(metaMap RouteMetaMap, group RouteGroupDef, basePat...

FILE: internal/routers/register.go
  function RegisterRoutes (line 10) | func RegisterRoutes(engine *gin.Engine, root RouteGroupDef) {
  function registerGroup (line 14) | func registerGroup(routes *gin.RouterGroup, group RouteGroupDef) {
  function normalizeRelativePath (line 29) | func normalizeRelativePath(path string) string {
  function joinFullPath (line 37) | func joinFullPath(parts ...string) string {

FILE: internal/routers/router.go
  function SetRouters (line 20) | func SetRouters() (*gin.Engine, error) {
  function SetRoutersWithTree (line 25) | func SetRoutersWithTree(routeTree RouteGroupDef) (*gin.Engine, error) {
  function createEngine (line 46) | func createEngine() (*gin.Engine, error) {
  function ReleaseRouter (line 88) | func ReleaseRouter() *gin.Engine {
  function AppRouteTree (line 100) | func AppRouteTree() RouteGroupDef {
  type readinessStatus (line 133) | type readinessStatus struct
  type readinessComponent (line 139) | type readinessComponent struct
  type dependencyStatus (line 145) | type dependencyStatus struct
  function buildReadinessStatus (line 152) | func buildReadinessStatus() readinessStatus {
  function buildMySQLReadiness (line 177) | func buildMySQLReadiness(cfg *config.Conf) dependencyStatus {
  function buildRedisReadiness (line 208) | func buildRedisReadiness(cfg *config.Conf) dependencyStatus {
  function buildQueueReadiness (line 239) | func buildQueueReadiness(cfg *config.Conf) dependencyStatus {

FILE: internal/routers/router_deps_test.go
  function TestAdminRouteTree_WithCustomDeps (line 16) | func TestAdminRouteTree_WithCustomDeps(t *testing.T) {
  function TestValidateRouteTree (line 42) | func TestValidateRouteTree(t *testing.T) {
  function TestAdminRouteTree_DefaultDeps (line 67) | func TestAdminRouteTree_DefaultDeps(t *testing.T) {
  function TestCollectRouteMeta (line 82) | func TestCollectRouteMeta(t *testing.T) {
  function BenchmarkAdminRouteTree (line 90) | func BenchmarkAdminRouteTree(b *testing.B) {
  function TestRouterIntegration (line 101) | func TestRouterIntegration(t *testing.T) {

FILE: internal/routers/router_test.go
  function TestSetRoutersRegistersApiMetadata (line 16) | func TestSetRoutersRegistersApiMetadata(t *testing.T) {
  function TestSetRoutersRegistersPermissionCriticalRoutes (line 32) | func TestSetRoutersRegistersPermissionCriticalRoutes(t *testing.T) {
  function TestSetRoutersRegistersCriticalRoutes (line 61) | func TestSetRoutersRegistersCriticalRoutes(t *testing.T) {
  function TestLoginRouteReturnsDependencyNotReadyWhenMysqlUnavailable (line 89) | func TestLoginRouteReturnsDependencyNotReadyWhenMysqlUnavailable(t *test...
  function TestLoginCaptchaRouteRemainsAvailableWithoutMysql (line 116) | func TestLoginCaptchaRouteRemainsAvailableWithoutMysql(t *testing.T) {
  function TestReadinessRouteReportsMysqlUnavailable (line 142) | func TestReadinessRouteReportsMysqlUnavailable(t *testing.T) {
  type routeResult (line 174) | type routeResult struct
  function disableMysqlForRouterTest (line 178) | func disableMysqlForRouterTest(t *testing.T) func() {
  function disableDependenciesForReadinessTest (line 196) | func disableDependenciesForReadinessTest(t *testing.T) func() {

FILE: internal/routers/validate.go
  type RouteTreeError (line 10) | type RouteTreeError struct
    method Error (line 15) | func (e *RouteTreeError) Error() string {
  function ValidateRouteTree (line 25) | func ValidateRouteTree(root RouteGroupDef) error {
  function validateRouteTree (line 29) | func validateRouteTree(group RouteGroupDef, basePath string, seen map[st...
  function isValidHTTPMethod (line 74) | func isValidHTTPMethod(method string) bool {

FILE: internal/runtime/config_reload.go
  function RegisterConfigReloadHandlers (line 18) | func RegisterConfigReloadHandlers() {
  function reloadLogger (line 43) | func reloadLogger(oldConfig, newConfig *config.Conf, diff config.ConfigD...
  function reloadData (line 50) | func reloadData(oldConfig, newConfig *config.Conf, diff config.ConfigDif...
  function reloadCasbin (line 66) | func reloadCasbin(oldConfig, newConfig *config.Conf, diff config.ConfigD...
  function logWarnings (line 80) | func logWarnings(oldConfig, newConfig *config.Conf, diff config.ConfigDi...

FILE: internal/service/access/api_cache.go
  constant apiRedisKey (line 24) | apiRedisKey                 = "api_info_map"
  constant apiCacheRedisTimeout (line 25) | apiCacheRedisTimeout        = 3 * time.Second
  constant apiCacheRefreshTotalTimeout (line 26) | apiCacheRefreshTotalTimeout = 15 * time.Second
  constant apiCacheWriteBatch (line 27) | apiCacheWriteBatch          = 500
  type ApiRouteInfo (line 31) | type ApiRouteInfo struct
  type apiRouteCacheMetrics (line 36) | type apiRouteCacheMetrics struct
  type apiRouteCacheEntry (line 46) | type apiRouteCacheEntry struct
  type ApiRouteCacheMetricsSnapshot (line 52) | type ApiRouteCacheMetricsSnapshot struct
  type ApiRouteCacheService (line 64) | type ApiRouteCacheService struct
    method ensureRuntimeDeps (line 80) | func (s *ApiRouteCacheService) ensureRuntimeDeps() {
    method currentConfig (line 92) | func (s *ApiRouteCacheService) currentConfig() *config.Conf {
    method MetricsSnapshot (line 98) | func (s *ApiRouteCacheService) MetricsSnapshot() ApiRouteCacheMetricsS...
    method ResetMetrics (line 123) | func (s *ApiRouteCacheService) ResetMetrics() {
    method cacheKey (line 135) | func (s *ApiRouteCacheService) cacheKey(route string, method string) s...
    method refreshTempKey (line 159) | func (s *ApiRouteCacheService) refreshTempKey() string {
    method writeRouteCacheBatch (line 163) | func (s *ApiRouteCacheService) writeRouteCacheBatch(parent context.Con...
    method RefreshCache (line 182) | func (s *ApiRouteCacheService) RefreshCache() error {
    method GetRouteInfo (line 275) | func (s *ApiRouteCacheService) GetRouteInfo(route string, method strin...
    method loadRouteInfoFromSource (line 320) | func (s *ApiRouteCacheService) loadRouteInfoFromSource(route string, m...
    method CheckoutRouteIsAuth (line 351) | func (s *ApiRouteCacheService) CheckoutRouteIsAuth(route string, metho...
    method GetApiName (line 363) | func (s *ApiRouteCacheService) GetApiName(route string, method string)...
  function NewApiRouteCacheService (line 72) | func NewApiRouteCacheService() *ApiRouteCacheService {
  function redisContext (line 139) | func redisContext() (context.Context, context.CancelFunc) {
  function redisContextWithTimeout (line 143) | func redisContextWithTimeout(parent context.Context, timeout time.Durati...
  function logError (line 374) | func logError(message string, err error, route string, method string) {

FILE: internal/service/access/api_cache_test.go
  function TestApiRouteCacheServiceDefaultsWithoutDatabase (line 15) | func TestApiRouteCacheServiceDefaultsWithoutDatabase(t *testing.T) {
  function TestApiRouteCacheServiceCacheKey (line 28) | func TestApiRouteCacheServiceCacheKey(t *testing.T) {
  function TestApiRouteCacheServiceGetRouteInfoSingleflightDeduplicates (line 36) | func TestApiRouteCacheServiceGetRouteInfoSingleflightDeduplicates(t *tes...
  function TestApiRouteCacheServiceResetMetrics (line 104) | func TestApiRouteCacheServiceResetMetrics(t *testing.T) {
  function TestApiRouteCacheServiceCheckoutRouteIsAuthUsesThreeStateMode (line 131) | func TestApiRouteCacheServiceCheckoutRouteIsAuthUsesThreeStateMode(t *te...
  function TestRedisContextWithTimeoutHonorsParentDeadline (line 163) | func TestRedisContextWithTimeoutHonorsParentDeadline(t *testing.T) {
  function TestApiRouteCacheServiceRefreshTempKeyUsesShadowKey (line 183) | func TestApiRouteCacheServiceRefreshTempKeyUsesShadowKey(t *testing.T) {

FILE: internal/service/access/common.go
  function defaultReloadPolicy (line 9) | func defaultReloadPolicy() error {
  function getPolicyEnforcer (line 14) | func getPolicyEnforcer() (*casbinx.CasbinEnforcer, error) {
  function FirstTx (line 23) | func FirstTx(tx []*gorm.DB) *gorm.DB {

FILE: internal/service/access/coordinator.go
  type PermissionSyncCoordinator (line 10) | type PermissionSyncCoordinator struct
    method SyncAll (line 46) | func (c *PermissionSyncCoordinator) SyncAll() error {
    method SyncAllInTx (line 54) | func (c *PermissionSyncCoordinator) SyncAllInTx(tx *gorm.DB) error {
    method SyncUser (line 62) | func (c *PermissionSyncCoordinator) SyncUser(userID uint, tx ...*gorm....
    method SyncUsers (line 67) | func (c *PermissionSyncCoordinator) SyncUsers(userIDs []uint, tx ...*g...
    method SyncUsersAffectedByScope (line 72) | func (c *PermissionSyncCoordinator) SyncUsersAffectedByScope(scope Per...
    method SyncUsersAffectedByAPIs (line 81) | func (c *PermissionSyncCoordinator) SyncUsersAffectedByAPIs(apiIDs []u...
    method SyncUsersAffectedByMenus (line 86) | func (c *PermissionSyncCoordinator) SyncUsersAffectedByMenus(menuIDs [...
    method SyncUsersAffectedByRoles (line 91) | func (c *PermissionSyncCoordinator) SyncUsersAffectedByRoles(roleIDs [...
    method SyncUsersAffectedByDepartments (line 96) | func (c *PermissionSyncCoordinator) SyncUsersAffectedByDepartments(dep...
    method ClearUser (line 101) | func (c *PermissionSyncCoordinator) ClearUser(userID uint, tx ...*gorm...
    method AccessibleMenuIDs (line 106) | func (c *PermissionSyncCoordinator) AccessibleMenuIDs(userID uint, inc...
    method ReloadPolicyCache (line 111) | func (c *PermissionSyncCoordinator) ReloadPolicyCache() error {
    method ReloadPolicyCacheWithMessage (line 116) | func (c *PermissionSyncCoordinator) ReloadPolicyCacheWithMessage(_ str...
    method ReloadPolicyCacheWithCode (line 121) | func (c *PermissionSyncCoordinator) ReloadPolicyCacheWithCode(code int...
    method RunAfterCommit (line 129) | func (c *PermissionSyncCoordinator) RunAfterCommit(db *gorm.DB, _ stri...
    method RunAfterCommitWithCode (line 134) | func (c *PermissionSyncCoordinator) RunAfterCommitWithCode(db *gorm.DB...
  type PermissionSyncCoordinatorDeps (line 18) | type PermissionSyncCoordinatorDeps struct
  function NewPermissionSyncCoordinator (line 26) | func NewPermissionSyncCoordinator() *PermissionSyncCoordinator {
  function NewPermissionSyncCoordinatorWithDeps (line 31) | func NewPermissionSyncCoordinatorWithDeps(deps PermissionSyncCoordinator...

FILE: internal/service/access/graph_loader.go
  method collectUserPolicies (line 17) | func (s *UserPermissionSyncService) collectUserPolicies(userID uint, tx ...
  method collectPoliciesForUsers (line 41) | func (s *UserPermissionSyncService) collectPoliciesForUsers(userIDs []ui...
  method collectActiveUserIDs (line 85) | func (s *UserPermissionSyncService) collectActiveUserIDs(userIDs []uint,...
  method collectMenuPermissionData (line 104) | func (s *UserPermissionSyncService) collectMenuPermissionData(menuIDs []...
  method userRoleIDs (line 116) | func (s *UserPermissionSyncService) userRoleIDs(userID uint, tx ...*gorm...
  method expandRoleIDs (line 145) | func (s *UserPermissionSyncService) expandRoleIDs(roleIDs []uint, tx ......
  method RoleMenuIDs (line 170) | func (s *UserPermissionSyncService) RoleMenuIDs(roleIDs []uint, tx ...*g...
  method expandMenuIDsWithParents (line 190) | func (s *UserPermissionSyncService) expandMenuIDsWithParents(menuIDs []u...
  method menuAPIPolicies (line 214) | func (s *UserPermissionSyncService) menuAPIPolicies(menuIDs []uint, tx ....
  method allUserIDs (line 239) | func (s *UserPermissionSyncService) allUserIDs(tx ...*gorm.DB) ([]uint, ...
  method userInfo (line 247) | func (s *UserPermissionSyncService) userInfo(userID uint, tx ...*gorm.DB...
  method UserKey (line 259) | func (s *UserPermissionSyncService) UserKey(userID uint) string {
  type roleStatusInfo (line 265) | type roleStatusInfo struct
  type roleMenuIDMap (line 271) | type roleMenuIDMap
    method AllMenuIDs (line 273) | func (m roleMenuIDMap) AllMenuIDs() []uint {
  method userBaseRoleMap (line 281) | func (s *UserPermissionSyncService) userBaseRoleMap(userIDs []uint, tx *...
  method appendDirectRoles (line 311) | func (s *UserPermissionSyncService) appendDirectRoles(userRoleMap map[ui...
  method userDepartmentMap (line 324) | func (s *UserPermissionSyncService) userDepartmentMap(userIDs []uint, tx...
  method departmentRoleMap (line 330) | func (s *UserPermissionSyncService) departmentRoleMap(deptIDs []uint, tx...
  method loadRoleStatusMap (line 336) | func (s *UserPermissionSyncService) loadRoleStatusMap(tx *gorm.DB) (map[...
  method roleMenuMap (line 355) | func (s *UserPermissionSyncService) roleMenuMap(roleIDs []uint, tx *gorm...
  method enabledMenuSet (line 365) | func (s *UserPermissionSyncService) enabledMenuSet(menuIDs []uint, tx *g...
  method menuPolicyMap (line 384) | func (s *UserPermissionSyncService) menuPolicyMap(menuIDs []uint, tx *go...
  function buildAncestorSet (line 409) | func buildAncestorSet(baseIDs []uint, collect func(add func(uint))) []ui...
  function addAncestorIDs (line 425) | func addAncestorIDs(pids string, add func(uint)) {
  function isSyncableUser (line 440) | func isSyncableUser(userInfo *model.AdminUser) bool {
  function expandUserRoles (line 447) | func expandUserRoles(userRoleMap map[uint][]uint, roleStatusMap map[uint...
  function buildUserPolicies (line 458) | func buildUserPolicies(roleIDs []uint, roleMenuMap roleMenuIDMap, enable...
  function collectEnabledMenuSet (line 463) | func collectEnabledMenuSet(roleIDs []uint, roleMenuMap roleMenuIDMap, en...
  function dedupePolicies (line 475) | func dedupePolicies(menuSet map[uint]struct{}, menuPolicies map[uint][][...
  function expandRoleAncestors (line 494) | func expandRoleAncestors(roleIDs []uint, roleStatusMap map[uint]roleStat...

FILE: internal/service/access/menu_api_defaults.go
  type defaultMenuAPIBinding (line 10) | type defaultMenuAPIBinding struct
  type MenuAPIDefaultsService (line 111) | type MenuAPIDefaultsService struct
    method Sync (line 152) | func (s *MenuAPIDefaultsService) Sync(tx ...*gorm.DB) error {
  type MenuAPIDefaultsServiceDeps (line 117) | type MenuAPIDefaultsServiceDeps struct
  function NewMenuAPIDefaultsService (line 123) | func NewMenuAPIDefaultsService() *MenuAPIDefaultsService {
  function NewMenuAPIDefaultsServiceWithDeps (line 128) | func NewMenuAPIDefaultsServiceWithDeps(deps MenuAPIDefaultsServiceDeps) ...
  function defaultMenuAPIBindings (line 138) | func defaultMenuAPIBindings() []defaultMenuAPIBinding {
  function cloneMenuAPIBindings (line 142) | func cloneMenuAPIBindings(source []defaultMenuAPIBinding) []defaultMenuA...
  function defaultMenuAPIDB (line 180) | func defaultMenuAPIDB(tx *gorm.DB) (*gorm.DB, error) {
  function collectMenuAPIBindingKeys (line 187) | func collectMenuAPIBindingKeys(bindings []defaultMenuAPIBinding) (menuCo...
  type defaultMenuAPITargets (line 209) | type defaultMenuAPITargets struct
  function loadDefaultMenuAPITargets (line 214) | func loadDefaultMenuAPITargets(db *gorm.DB, menuCodes []string, routes [...
  function buildDefaultMenuAPIMappings (line 243) | func buildDefaultMenuAPIMappings(bindings []defaultMenuAPIBinding, targe...

FILE: internal/service/access/menu_api_defaults_test.go
  function TestNewMenuAPIDefaultsServiceUsesIsolatedDefaultBindings (line 10) | func TestNewMenuAPIDefaultsServiceUsesIsolatedDefaultBindings(t *testing...
  function TestNewMenuAPIDefaultsServiceWithDepsClonesBindings (line 25) | func TestNewMenuAPIDefaultsServiceWithDepsClonesBindings(t *testing.T) {
  function TestNewMenuAPIDefaultsServiceWithDepsAllowsEmptyBindings (line 40) | func TestNewMenuAPIDefaultsServiceWithDepsAllowsEmptyBindings(t *testing...
  function TestDefaultMenuAPIBindingsCoverManagementRoutes (line 50) | func TestDefaultMenuAPIBindingsCoverManagementRoutes(t *testing.T) {
  function TestCollectMenuAPIBindingKeysKeepsUniqueValues (line 79) | func TestCollectMenuAPIBindingKeysKeepsUniqueValues(t *testing.T) {
  function TestBuildDefaultMenuAPIMappingsSkipsMissingTargets (line 99) | func TestBuildDefaultMenuAPIMappingsSkipsMissingTargets(t *testing.T) {

FILE: internal/service/access/scope_resolver.go
  type PermissionChangeScope (line 10) | type PermissionChangeScope struct
  type AffectedUsersResolver (line 19) | type AffectedUsersResolver struct
    method Resolve (line 27) | func (r *AffectedUsersResolver) Resolve(scope PermissionChangeScope, t...
    method userIDsByRoles (line 80) | func (r *AffectedUsersResolver) userIDsByRoles(roleIDs []uint, tx ...*...
    method expandRoleSubtree (line 121) | func (r *AffectedUsersResolver) expandRoleSubtree(roleIDs []uint, tx ....
  function NewAffectedUsersResolver (line 22) | func NewAffectedUsersResolver() *AffectedUsersResolver {

FILE: internal/service/access/system_defaults.go
  type SystemDefaultsService (line 16) | type SystemDefaultsService struct
    method Ensure (line 24) | func (s *SystemDefaultsService) Ensure(tx ...*gorm.DB) error {
    method IsProtectedRole (line 40) | func (s *SystemDefaultsService) IsProtectedRole(role *model.Role) bool {
    method IsProtectedDepartment (line 45) | func (s *SystemDefaultsService) IsProtectedDepartment(dept *model.Depa...
    method EnsureSuperAdminRoleMenus (line 50) | func (s *SystemDefaultsService) EnsureSuperAdminRoleMenus(tx ...*gorm....
    method ensureWithTx (line 54) | func (s *SystemDefaultsService) ensureWithTx(tx *gorm.DB) error {
    method ensureDefaultDepartment (line 80) | func (s *SystemDefaultsService) ensureDefaultDepartment(tx *gorm.DB) (...
    method ensureSuperAdminRole (line 127) | func (s *SystemDefaultsService) ensureSuperAdminRole(tx *gorm.DB) (*mo...
    method ensureSuperAdminUser (line 187) | func (s *SystemDefaultsService) ensureSuperAdminUser(tx *gorm.DB) error {
    method ensureSuperAdminUserDept (line 210) | func (s *SystemDefaultsService) ensureSuperAdminUserDept(tx *gorm.DB, ...
    method ensureSuperAdminUserRole (line 225) | func (s *SystemDefaultsService) ensureSuperAdminUserRole(tx *gorm.DB, ...
    method ensureSuperAdminRoleMenusWithTx (line 240) | func (s *SystemDefaultsService) ensureSuperAdminRoleMenusWithTx(tx *go...
    method RequireSuperAdminRoleForUser (line 273) | func (s *SystemDefaultsService) RequireSuperAdminRoleForUser(uid uint,...
  function NewSystemDefaultsService (line 19) | func NewSystemDefaultsService() *SystemDefaultsService {

FILE: internal/service/access/system_defaults_test.go
  function TestSystemDefaultsServiceProtectedPredicates (line 10) | func TestSystemDefaultsServiceProtectedPredicates(t *testing.T) {
  function TestRequireSuperAdminRoleForNonSuperAdminUserSkipsLookup (line 42) | func TestRequireSuperAdminRoleForNonSuperAdminUserSkipsLookup(t *testing...

FILE: internal/service/access/transaction.go
  function RunInTransaction (line 6) | func RunInTransaction(db *gorm.DB, fn func(tx *gorm.DB) error) error {

FILE: internal/service/access/user_permission_sync.go
  type UserPermissionSyncService (line 11) | type UserPermissionSyncService struct
    method ReloadPolicyCache (line 39) | func (s *UserPermissionSyncService) ReloadPolicyCache() error {
    method SyncUser (line 44) | func (s *UserPermissionSyncService) SyncUser(userID uint, tx ...*gorm....
    method SyncUsers (line 49) | func (s *UserPermissionSyncService) SyncUsers(userIDs []uint, tx ...*g...
    method SyncAllUsers (line 60) | func (s *UserPermissionSyncService) SyncAllUsers(tx ...*gorm.DB) error {
    method ClearUser (line 75) | func (s *UserPermissionSyncService) ClearUser(userID uint, tx ...*gorm...
    method AccessibleMenuIDs (line 87) | func (s *UserPermissionSyncService) AccessibleMenuIDs(userID uint, inc...
    method withSyncTransaction (line 106) | func (s *UserPermissionSyncService) withSyncTransaction(tx []*gorm.DB,...
    method forEachUser (line 121) | func (s *UserPermissionSyncService) forEachUser(userIDs []uint, fn fun...
    method batchSyncUsersWithEnforcer (line 140) | func (s *UserPermissionSyncService) batchSyncUsersWithEnforcer(userIDs...
    method syncUserWithTx (line 162) | func (s *UserPermissionSyncService) syncUserWithTx(userID uint, tx *go...
  type UserPermissionSyncServiceDeps (line 17) | type UserPermissionSyncServiceDeps struct
  function NewUserPermissionSyncService (line 23) | func NewUserPermissionSyncService() *UserPermissionSyncService {
  function NewUserPermissionSyncServiceWithDeps (line 28) | func NewUserPermissionSyncServiceWithDeps(deps UserPermissionSyncService...
  function UniqueUintSlice (line 178) | func UniqueUintSlice(values []uint) []uint {

FILE: internal/service/access/user_permission_sync_bench_test.go
  function BenchmarkBatchPermissionSync (line 5) | func BenchmarkBatchPermissionSync(b *testing.B) {

FILE: internal/service/access/user_permission_sync_test.go
  function TestUserPermissionSyncForEachUserDeduplicatesInOrder (line 12) | func TestUserPermissionSyncForEachUserDeduplicatesInOrder(t *testing.T) {
  function TestUserPermissionSyncForEachUserStopsOnError (line 30) | func TestUserPermissionSyncForEachUserStopsOnError(t *testing.T) {
  function TestExpandRoleAncestorsSkipsDisabledRoles (line 45) | func TestExpandRoleAncestorsSkipsDisabledRoles(t *testing.T) {
  function TestPermissionSyncCoordinatorRunAfterCommitReloadsOnce (line 59) | func TestPermissionSyncCoordinatorRunAfterCommitReloadsOnce(t *testing.T) {

FILE: internal/service/admin/admin_user.go
  type AdminUserService (line 23) | type AdminUserService struct
    method handleMutationError (line 38) | func (s *AdminUserService) handleMutationError(err error, fallbackCode...
    method revokeUserTokens (line 51) | func (s *AdminUserService) revokeUserTokens(tx *gorm.DB, userID uint, ...
    method revokeUserTokensAfterCommit (line 64) | func (s *AdminUserService) revokeUserTokensAfterCommit(items []userTok...
    method GetUserInfo (line 71) | func (s *AdminUserService) GetUserInfo(id uint) (*resources.AdminUserR...
    method GetUserMenuInfo (line 85) | func (s *AdminUserService) GetUserMenuInfo(id uint, locale string) (an...
    method List (line 129) | func (s *AdminUserService) List(params *form.AdminUserList) *resources...
    method adminUserListOptions (line 142) | func (s *AdminUserService) adminUserListOptions() model.ListOptionalPa...
    method buildListCondition (line 154) | func (s *AdminUserService) buildListCondition(params *form.AdminUserLi...
    method userMenuQuery (line 188) | func (s *AdminUserService) userMenuQuery(isSuperAdmin bool, menuIDs []...
  constant menuQuerySuperAdmin (line 28) | menuQuerySuperAdmin = "status = ?"
  constant menuQueryNoAuth (line 29) | menuQueryNoAuth     = "status = ? AND is_auth = ?"
  constant menuQueryWithAuth (line 30) | menuQueryWithAuth   = "status = ? AND (is_auth = ? OR (is_auth = ? AND i...
  function NewAdminUserService (line 34) | func NewAdminUserService() *AdminUserService {
  type userTokenRevocation (line 58) | type userTokenRevocation struct
  function zeroToNil (line 175) | func zeroToNil(value uint) any {
  type adminUserEditParams (line 199) | type adminUserEditParams struct

FILE: internal/service/admin/admin_user_bind.go
  method BindDept (line 17) | func (s *AdminUserService) BindDept(uid uint, deptId []uint, tx ...*gorm...
  method updateDeptUserNumber (line 61) | func (s *AdminUserService) updateDeptUserNumber(deptIds []uint, delta in...
  method BindRole (line 80) | func (s *AdminUserService) BindRole(params *form.BindRole) error {
  method updateAdminUserRole (line 108) | func (s *AdminUserService) updateAdminUserRole(uid uint, roleIds []uint,...

FILE: internal/service/admin/admin_user_create_test.go
  function TestAdminUserCreateRequiresUsername (line 11) | func TestAdminUserCreateRequiresUsername(t *testing.T) {
  function TestAdminUserCreateRequiresNickname (line 22) | func TestAdminUserCreateRequiresNickname(t *testing.T) {
  function TestAdminUserCreateRequiresPassword (line 35) | func TestAdminUserCreateRequiresPassword(t *testing.T) {
  function assertBusinessErrorMessage (line 48) | func assertBusinessErrorMessage(t *testing.T, err error, code int, messa...

FILE: internal/service/admin/admin_user_mutation.go
  method Create (line 16) | func (s *AdminUserService) Create(params *form.CreateAdminUser) error {
  method Update (line 31) | func (s *AdminUserService) Update(params *form.UpdateAdminUser) error {
  method saveAdminUserMutation (line 53) | func (s *AdminUserService) saveAdminUserMutation(params *adminUserEditPa...
  method applyUpdateFields (line 152) | func (s *AdminUserService) applyUpdateFields(adminUserModel *model.Admin...
  method applyCreateFields (line 203) | func (s *AdminUserService) applyCreateFields(adminUserModel *model.Admin...
  function setHashedPassword (line 243) | func setHashedPassword(adminUserModel *model.AdminUser, plainPassword st...
  method UpdateProfile (line 253) | func (s *AdminUserService) UpdateProfile(uid uint, params *form.UpdatePr...
  method validateUniqueFieldsWithLock (line 342) | func (s *AdminUserService) validateUniqueFieldsWithLock(tx *gorm.DB, par...
  method Delete (line 389) | func (s *AdminUserService) Delete(id uint) error {

FILE: internal/service/admin/admin_user_test.go
  function TestAdminUserBuildListCondition (line 17) | func TestAdminUserBuildListCondition(t *testing.T) {
  function TestUniqueUintSlice (line 39) | func TestUniqueUintSlice(t *testing.T) {
  function TestUserPermissionSyncUserKey (line 49) | func TestUserPermissionSyncUserKey(t *testing.T) {
  function TestAdminUserMenuQuery (line 56) | func TestAdminUserMenuQuery(t *testing.T) {
  function TestAdminUserHandleMutationErrorKeepsBusinessError (line 75) | func TestAdminUserHandleMutationErrorKeepsBusinessError(t *testing.T) {
  function TestAdminUserHandleMutationErrorWrapsPlainError (line 85) | func TestAdminUserHandleMutationErrorWrapsPlainError(t *testing.T) {
  function TestAdminUserListOptionsDepartmentSelectFields (line 92) | func TestAdminUserListOptionsDepartmentSelectFields(t *testing.T) {
  function TestApplyUpdateFieldsDefaultsCountryCodeWhenPhoneChanges (line 114) | func TestApplyUpdateFieldsDefaultsCountryCodeWhenPhoneChanges(t *testing...
  function TestApplyUpdateFieldsClearsFullPhoneWhenPhoneCleared (line 136) | func TestApplyUpdateFieldsClearsFullPhoneWhenPhoneCleared(t *testing.T) {
  function TestApplyUpdateFieldsRejectsSuperAdminPasswordChange (line 160) | func TestApplyUpdateFieldsRejectsSuperAdminPasswordChange(t *testing.T) {

FILE: internal/service/admin/audit_diff.go
  method CreateWithAuditDiff (line 47) | func (s *AdminUserService) CreateWithAuditDiff(params *form.CreateAdminU...
  method UpdateWithAuditDiff (line 69) | func (s *AdminUserService) UpdateWithAuditDiff(params *form.UpdateAdminU...
  method DeleteWithAuditDiff (line 88) | func (s *AdminUserService) DeleteWithAuditDiff(id uint) (string, error) {
  method UpdateProfileWithAuditDiff (line 100) | func (s *AdminUserService) UpdateProfileWithAuditDiff(uid uint, params *...
  method BindRoleWithAuditDiff (line 119) | func (s *AdminUserService) BindRoleWithAuditDiff(params *form.BindRole) ...
  method snapshotAdminUserByUsername (line 138) | func (s *AdminUserService) snapshotAdminUserByUsername(username string) ...
  method snapshotAdminUserByID (line 150) | func (s *AdminUserService) snapshotAdminUserByID(id uint) (map[string]an...
  method snapshotAdminUserRoleBinding (line 180) | func (s *AdminUserService) snapshotAdminUserRoleBinding(userID uint) (ma...
  function buildAdminUserDiff (line 196) | func buildAdminUserDiff(before, after map[string]any) string {
  function sortUintSlice (line 201) | func sortUintSlice(values []uint) {

FILE: internal/service/admin/audit_diff_test.go
  function TestBuildAdminUserDiffIncludesStatusDisplay (line 8) | func TestBuildAdminUserDiffIncludesStatusDisplay(t *testing.T) {

FILE: internal/service/api_permission/api.go
  type ApiService (line 16) | type ApiService struct
    method Update (line 26) | func (s *ApiService) Update(params *form.UpdatePermission) error {
    method ListPage (line 58) | func (s *ApiService) ListPage(params *form.ListPermission) *resources....
    method buildListCondition (line 80) | func (s *ApiService) buildListCondition(params *form.ListPermission) (...
  function NewApiService (line 21) | func NewApiService() *ApiService {
  function emptyToNil (line 95) | func emptyToNil(value string) any {

FILE: internal/service/api_permission/api_test.go
  function TestApiBuildListCondition (line 9) | func TestApiBuildListCondition(t *testing.T) {

FILE: internal/service/api_permission/audit_diff.go
  method UpdateWithAuditDiff (line 27) | func (s *ApiService) UpdateWithAuditDiff(params *form.UpdatePermission) ...
  method snapshotAPIPermissionByID (line 46) | func (s *ApiService) snapshotAPIPermissionByID(id uint) (map[string]any,...

FILE: internal/service/api_permission/audit_diff_test.go
  function TestAPIPermissionDiffIncludesAuthModeDisplay (line 10) | func TestAPIPermissionDiffIncludesAuthModeDisplay(t *testing.T) {

FILE: internal/service/audit/list_helpers.go
  type logListQuery (line 5) | type logListQuery struct
    method addEq (line 13) | func (q *logListQuery) addEq(field string, value any) *logListQuery {
    method addLike (line 18) | func (q *logListQuery) addLike(field, value string) *logListQuery {
    method addCondition (line 23) | func (q *logListQuery) addCondition(condition string, args ...any) *lo...
    method addCreatedAtRange (line 28) | func (q *logListQuery) addCreatedAtRange(startTime, endTime string) *l...
  function newLogListQuery (line 9) | func newLogListQuery() *logListQuery {
  function uintFilterValue (line 38) | func uintFilterValue(value uint) any {

FILE: internal/service/audit/login_log.go
  type AdminLoginLogService (line 16) | type AdminLoginLogService struct
    method ensureRuntimeDeps (line 42) | func (s *AdminLoginLogService) ensureRuntimeDeps() {
    method currentConfig (line 48) | func (s *AdminLoginLogService) currentConfig() *config.Conf {
    method List (line 54) | func (s *AdminLoginLogService) List(params *form.AdminLoginLogList) *r...
    method Detail (line 98) | func (s *AdminLoginLogService) Detail(id uint) (any, error) {
  type AdminLoginLogServiceDeps (line 23) | type AdminLoginLogServiceDeps struct
  function NewAdminLoginLogService (line 29) | func NewAdminLoginLogService() *AdminLoginLogService {
  function NewAdminLoginLogServiceWithDeps (line 34) | func NewAdminLoginLogServiceWithDeps(deps AdminLoginLogServiceDeps) *Adm...
  function decryptLoginTokenIfNeeded (line 111) | func decryptLoginTokenIfNeeded(token, decryptKey string) string {

FILE: internal/service/audit/login_log_test.go
  function TestDecryptLoginTokenIfNeeded (line 12) | func TestDecryptLoginTokenIfNeeded(t *testing.T) {
  function TestDecryptLoginTokenIfNeededFallbackOnDecryptError (line 26) | func TestDecryptLoginTokenIfNeededFallbackOnDecryptError(t *testing.T) {
  function TestCurrentAuditConfigUsesInjectedProvider (line 35) | func TestCurrentAuditConfigUsesInjectedProvider(t *testing.T) {

FILE: internal/service/audit/request_log.go
  type RequestLogService (line 14) | type RequestLogService struct
    method List (line 24) | func (s *RequestLogService) List(params *form.RequestLogList) *resourc...
    method Detail (line 74) | func (s *RequestLogService) Detail(id uint) (any, error) {
  function NewRequestLogService (line 19) | func NewRequestLogService() *RequestLogService {
  type requestLogQueryInput (line 84) | type requestLogQueryInput struct
  function buildRequestLogQuery (line 97) | func buildRequestLogQuery(input requestLogQueryInput) *logListQuery {

FILE: internal/service/audit/request_log_manage.go
  constant defaultRequestLogExportLimit (line 23) | defaultRequestLogExportLimit = 1000
  constant requestLogMaskConfigGroupCode (line 26) | requestLogMaskConfigGroupCode = "audit"
  constant requestLogMaskConfigSort (line 27) | requestLogMaskConfigSort      = 95
  constant requestLogMaskConfigRemark (line 28) | requestLogMaskConfigRemark    = "请求日志脱敏字段配置"
  constant requestLogMaskConfigManageTab (line 29) | requestLogMaskConfigManageTab = "audit_mask"
  method ExportCSV (line 46) | func (s *RequestLogService) ExportCSV(params *form.RequestLogExport) ([]...
  method GetMaskConfig (line 143) | func (s *RequestLogService) GetMaskConfig() map[string]any {
  function toMaskConfigMap (line 151) | func toMaskConfigMap(cfg sensitive.SensitiveFieldsConfig) map[string]any {
  method UpdateMaskConfig (line 162) | func (s *RequestLogService) UpdateMaskConfig(params *form.RequestLogMask...
  method UpdateMaskConfigWithAuditDiff (line 192) | func (s *RequestLogService) UpdateMaskConfigWithAuditDiff(params *form.R...
  function buildMaskConfigAuditDiff (line 202) | func buildMaskConfigAuditDiff(before, after map[string]any) string {
  function normalizeSensitiveFieldList (line 207) | func normalizeSensitiveFieldList(input []string) []string {
  function loadMaskConfigFromSysConfig (line 227) | func loadMaskConfigFromSysConfig() (sensitive.SensitiveFieldsConfig, err...
  function decodeMaskConfig (line 238) | func decodeMaskConfig(raw string) (sensitive.SensitiveFieldsConfig, erro...
  function saveMaskConfigToSysConfig (line 256) | func saveMaskConfigToSysConfig(config sensitive.SensitiveFieldsConfig) (...

FILE: internal/service/audit/request_log_manage_test.go
  function TestDecodeMaskConfigNormalizesFields (line 10) | func TestDecodeMaskConfigNormalizesFields(t *testing.T) {
  function TestDecodeMaskConfigEmptyUsesDefault (line 48) | func TestDecodeMaskConfigEmptyUsesDefault(t *testing.T) {
  function TestBuildMaskConfigAuditDiff (line 63) | func TestBuildMaskConfigAuditDiff(t *testing.T) {

FILE: internal/service/audit/request_log_write.go
  type AuditLogSnapshot (line 8) | type AuditLogSnapshot struct
  function PersistAuditLog (line 34) | func PersistAuditLog(snapshot *AuditLogSnapshot) error {

FILE: internal/service/auth/login.go
  type LoginService (line 23) | type LoginService struct
    method ensureRuntimeDeps (line 89) | func (s *LoginService) ensureRuntimeDeps() {
    method currentConfig (line 120) | func (s *LoginService) currentConfig() *config.Conf {
    method Login (line 126) | func (s *LoginService) Login(username, password string, logInfo LoginL...
    method validateUser (line 166) | func (s *LoginService) validateUser(username, password string) (*model...
    method recordLoginLog (line 188) | func (s *LoginService) recordLoginLog(adminUser *model.AdminUser, clai...
    method Refresh (line 215) | func (s *LoginService) Refresh(id uint, logInfo LoginLogInfo) (*TokenR...
    method newAdminCustomClaims (line 246) | func (s *LoginService) newAdminCustomClaims(user *model.AdminUser) tok...
  type LoginServiceDeps (line 46) | type LoginServiceDeps struct
  function NewLoginService (line 68) | func NewLoginService() *LoginService {
  function NewLoginServiceWithDeps (line 73) | func NewLoginServiceWithDeps(deps LoginServiceDeps) *LoginService {

FILE: internal/service/auth/login_bench_test.go
  function BenchmarkResolvePrincipal (line 12) | func BenchmarkResolvePrincipal(b *testing.B) {

FILE: internal/service/auth/login_blacklist.go
  constant redisOpTimeout (line 23) | redisOpTimeout = 3 * time.Second
  method Logout (line 28) | func (s *LoginService) Logout(accessToken string) error {
  method parseToken (line 57) | func (s *LoginService) parseToken(accessToken string) (*token.AdminCusto...
  method IsInBlacklist (line 76) | func (s *LoginService) IsInBlacklist(jwtId string) (bool, error) {
  method writeTokenToBlacklist (line 94) | func (s *LoginService) writeTokenToBlacklist(jwtID string, remainingTime...
  method getBlacklistKey (line 113) | func (s *LoginService) getBlacklistKey(jwtId string) string {
  method addTokensToBlacklist (line 118) | func (s *LoginService) addTokensToBlacklist(loginLogs []model.AdminLogin...
  method calculateRemainingTime (line 160) | func (s *LoginService) calculateRemainingTime(tokenExpires *utils.Format...
  method RevokeUserTokens (line 172) | func (s *LoginService) RevokeUserTokens(userId uint, revokedCode uint8, ...
  function collectJWTIDs (line 204) | func collectJWTIDs(loginLogs []model.AdminLoginLogs) []string {

FILE: internal/service/auth/login_helpers_test.go
  function TestExtractErrorMessage (line 24) | func TestExtractErrorMessage(t *testing.T) {
  function TestCalculateTokenHash (line 39) | func TestCalculateTokenHash(t *testing.T) {
  function TestGetBlacklistKey (line 50) | func TestGetBlacklistKey(t *testing.T) {
  function TestCalculateRemainingTime (line 59) | func TestCalculateRemainingTime(t *testing.T) {
  function TestBuildRefreshLockKey (line 79) | func TestBuildRefreshLockKey(t *testing.T) {
  function TestShouldRefreshToken (line 89) | func TestShouldRefreshToken(t *testing.T) {
  function TestIsPrincipalValidSkipsFallbackWhenMysqlUnavailable (line 120) | func TestIsPrincipalValidSkipsFallbackWhenMysqlUnavailable(t *testing.T) {
  function TestIsPrincipalValidFallsBackToDatabaseWhenMysqlReady (line 147) | func TestIsPrincipalValidFallsBackToDatabaseWhenMysqlReady(t *testing.T) {
  function TestIsPrincipalValidChecksDatabaseWhenRedisMisses (line 174) | func TestIsPrincipalValidChecksDatabaseWhenRedisMisses(t *testing.T) {
  function TestIsPrincipalValidRejectsRevokedToken (line 201) | func TestIsPrincipalValidRejectsRevokedToken(t *testing.T) {
  function TestValidateUserReturnsDependencyErrorWhenDBUnavailable (line 222) | func TestValidateUserReturnsDependencyErrorWhenDBUnavailable(t *testing....
  function TestLogoutFallsBackToDatabaseRevocationWhenRedisUnavailable (line 239) | func TestLogoutFallsBackToDatabaseRevocationWhenRedisUnavailable(t *test...
  function TestLogoutTreatsRedisWriteFailureAsSuccessAfterDatabaseRevocation (line 287) | func TestLogoutTreatsRedisWriteFailureAsSuccessAfterDatabaseRevocation(t...
  function TestAcquireMemoryLock (line 347) | func TestAcquireMemoryLock(t *testing.T) {
  function TestNewLoginServiceSharesDefaultRefreshLockStore (line 356) | func TestNewLoginServiceSharesDefaultRefreshLockStore(t *testing.T) {
  function TestNewLoginServiceWithDepsUsesCustomRefreshLockStore (line 367) | func TestNewLoginServiceWithDepsUsesCustomRefreshLockStore(t *testing.T) {
  function TestAcquireRefreshLockFallsBackToMemoryWhenRedisDisabled (line 378) | func TestAcquireRefreshLockFallsBackToMemoryWhenRedisDisabled(t *testing...
  function TestResolvePrincipalSkipsAutoRefreshWhenMysqlUnavailable (line 402) | func TestResolvePrincipalSkipsAutoRefreshWhenMysqlUnavailable(t *testing...
  function signTokenForTest (line 428) | func signTokenForTest(claims jwt.Claims, secret string) (string, error) {

FILE: internal/service/auth/login_log_helpers.go
  method buildLoginLog (line 19) | func (s *LoginService) buildLoginLog(uid uint, username, jwtId, accessTo...
  method encryptAndSetToken (line 40) | func (s *LoginService) encryptAndSetToken(loginLog *model.AdminLoginLogs...
  method encryptToken (line 53) | func (s *LoginService) encryptToken(key, token, tokenType string, uid ui...
  method extractErrorMessage (line 63) | func (s *LoginService) extractErrorMessage(err error) string {
  method ExtractErrorMessage (line 72) | func (s *LoginService) ExtractErrorMessage(err error) string {
  method RecordLoginFailLog (line 77) | func (s *LoginService) RecordLoginFailLog(username, failReason string, l...
  method calculateTokenHash (line 105) | func (s *LoginService) calculateTokenHash(accessToken string) string {
  function logLoginAsyncError (line 110) | func logLoginAsyncError(message string, fields ...zap.Field) {

FILE: internal/service/auth/login_refresh.go
  constant refreshLockRedisTimeout (line 18) | refreshLockRedisTimeout = 2 * time.Second
  constant unknownUserAgentPart (line 19) | unknownUserAgentPart = "Unknown"
  method BuildLoginLogInfo (line 22) | func (s *LoginService) BuildLoginLogInfo(c *gin.Context) LoginLogInfo {
  function normalizeUserAgentPart (line 37) | func normalizeUserAgentPart(value string) string {
  method tryRefreshToken (line 46) | func (s *LoginService) tryRefreshToken(principal *AuthPrincipal) {
  method shouldRefreshToken (line 66) | func (s *LoginService) shouldRefreshToken(principal *AuthPrincipal) bool {
  method buildRefreshLockKey (line 82) | func (s *LoginService) buildRefreshLockKey(claims *token.AdminCustomClai...
  method acquireRefreshLock (line 87) | func (s *LoginService) acquireRefreshLock(lockKey string, claims *token....
  method acquireRedisLock (line 105) | func (s *LoginService) acquireRedisLock(lockKey string) (func(), bool, e...
  method acquireMemoryLock (line 127) | func (s *LoginService) acquireMemoryLock(lockKey string) func() {
  method doRefreshToken (line 135) | func (s *LoginService) doRefreshToken(principal *AuthPrincipal) {

FILE: internal/service/auth/login_revoke.go
  constant revokeLogAsyncTimeout (line 16) | revokeLogAsyncTimeout = 5 * time.Second
  method isTokenRevokedInLog (line 19) | func (s *LoginService) isTokenRevokedInLog(jwtId string) bool {
  method markTokensRevoked (line 36) | func (s *LoginService) markTokensRevoked(ctx context.Context, jwtIds []s...
  function logRevokeError (line 56) | func logRevokeError(message string, fields ...zap.Field) {

FILE: internal/service/auth/login_security.go
  constant defaultLoginMaxFailures (line 20) | defaultLoginMaxFailures = 5
  constant defaultLoginLockMinutes (line 21) | defaultLoginLockMinutes = 15
  type loginLockPolicy (line 24) | type loginLockPolicy struct
  method CheckLoginAllowed (line 31) | func (s *LoginService) CheckLoginAllowed(username string) error {
  method HandleLoginFailure (line 36) | func (s *LoginService) HandleLoginFailure(username, failReason string, l...
  method ensureLoginAllowed (line 46) | func (s *LoginService) ensureLoginAllowed(username string) error {
  method incrementLoginFailState (line 81) | func (s *LoginService) incrementLoginFailState(username string) error {
  method clearLoginFailState (line 124) | func (s *LoginService) clearLoginFailState(username string) error {
  method loginLockPolicy (line 144) | func (s *LoginService) loginLockPolicy() loginLockPolicy {
  method shouldCountLockFailure (line 159) | func (s *LoginService) shouldCountLockFailure(err error) bool {
  function applyLoginFailState (line 172) | func applyLoginFailState(state *model.LoginSecurityState, now time.Time,...
  function isTableNotFoundErr (line 188) | func isTableNotFoundErr(err error) bool {

FILE: internal/service/auth/login_security_test.go
  function TestShouldCountLockFailure (line 12) | func TestShouldCountLockFailure(t *testing.T) {
  function TestApplyLoginFailStateLocksWhenThresholdReached (line 34) | func TestApplyLoginFailStateLocksWhenThresholdReached(t *testing.T) {
  function TestApplyLoginFailStateResetsExpiredLock (line 58) | func TestApplyLoginFailStateResetsExpiredLock(t *testing.T) {

FILE: internal/service/auth/login_token_ops.go
  method CheckToken (line 13) | func (s *LoginService) CheckToken(accessToken string) (*model.AdminUser,...
  method ResolvePrincipal (line 22) | func (s *LoginService) ResolvePrincipal(accessToken string) (*AuthPrinci...
  method resolvePrincipalFromClaims (line 30) | func (s *LoginService) resolvePrincipalFromClaims(claims *token.AdminCus...
  method isPrincipalValid (line 53) | func (s *LoginService) isPrincipalValid(claims *token.AdminCustomClaims)...
  method shouldLogRedisFallback (line 79) | func (s *LoginService) shouldLogRedisFallback(err error) bool {

FILE: internal/service/auth/login_types.go
  constant tokenTypeBearer (line 11) | tokenTypeBearer   = "Bearer"
  constant blacklistPrefix (line 12) | blacklistPrefix   = "blacklist:"
  constant refreshLockPrefix (line 13) | refreshLockPrefix = "refresh_token_lock:"
  constant refreshLockTTL (line 14) | refreshLockTTL    = 5 * time.Second
  constant defaultTokenTTL (line 15) | defaultTokenTTL   = 24 * time.Hour
  type refreshTokenLock (line 19) | type refreshTokenLock struct
    method getLock (line 33) | func (r *refreshTokenLock) getLock(key string) *sync.Mutex {
  function newRefreshTokenLock (line 25) | func newRefreshTokenLock(ttl, cleanupInterval time.Duration) *refreshTok...
  function defaultRefreshLockStore (line 60) | func defaultRefreshLockStore() *refreshTokenLock {
  type TokenResponse (line 68) | type TokenResponse struct
  type LoginLogInfo (line 76) | type LoginLogInfo struct

FILE: internal/service/auth/login_types_test.go
  function TestRefreshTokenLockReusesMutexBeforeExpiry (line 9) | func TestRefreshTokenLockReusesMutexBeforeExpiry(t *testing.T) {
  function TestRefreshTokenLockCreatesNewMutexAfterExpiry (line 20) | func TestRefreshTokenLockCreatesNewMutexAfterExpiry(t *testing.T) {
  function TestRefreshTokenLockIsSafeUnderConcurrentAccess (line 32) | func TestRefreshTokenLockIsSafeUnderConcurrentAccess(t *testing.T) {

FILE: internal/service/auth/principal.go
  type AuthPrincipal (line 15) | type AuthPrincipal struct
    method AdminUser (line 50) | func (p *AuthPrincipal) AdminUser() *model.AdminUser {
  function newAuthPrincipalFromClaims (line 28) | func newAuthPrincipalFromClaims(claims *token.AdminCustomClaims) *AuthPr...
  function StoreAuthPrincipal (line 69) | func StoreAuthPrincipal(c *gin.Context, principal *AuthPrincipal) {
  function GetAuthPrincipal (line 78) | func GetAuthPrincipal(c *gin.Context) *AuthPrincipal {

FILE: internal/service/auth/session.go
  constant defaultSessionRevokeReason (line 20) | defaultSessionRevokeReason = "管理员强制下线"
  method ListSessions (line 23) | func (s *LoginService) ListSessions(params *form.SessionList) *resources...
  method RevokeSession (line 69) | func (s *LoginService) RevokeSession(ctx context.Context, id uint, reaso...
  method loadRevocableSession (line 98) | func (s *LoginService) loadRevocableSession(id uint) (*model.AdminLoginL...

FILE: internal/service/auth/session_test.go
  function TestRevokeSessionUpdatesDatabaseWhenRedisUnavailable (line 15) | func TestRevokeSessionUpdatesDatabaseWhenRedisUnavailable(t *testing.T) {
  function TestRevokeSessionRejectsExpiredSession (line 52) | func TestRevokeSessionRejectsExpiredSession(t *testing.T) {
  function newSessionTestDB (line 73) | func newSessionTestDB(t *testing.T) *gorm.DB {

FILE: internal/service/common.go
  type CommonService (line 21) | type CommonService struct
    method UploadImages (line 33) | func (s CommonService) UploadImages(files []*multipart.FileHeader, pat...
    method UploadImage (line 50) | func (s CommonService) UploadImage(fileHeader *multipart.FileHeader, i...
    method GetFileAccessPath (line 199) | func (s CommonService) GetFileAccessPath(fileUUID string, checkAuth bo...
  constant maxUploadFileSize (line 25) | maxUploadFileSize int64 = 10 * 1024 * 1024
  function NewCommonService (line 28) | func NewCommonService() *CommonService {
  type FileAccessResult (line 194) | type FileAccessResult struct

FILE: internal/service/common_test.go
  function TestVisibilityFlag (line 15) | func TestVisibilityFlag(t *testing.T) {
  function TestStorageBasePath (line 20) | func TestStorageBasePath(t *testing.T) {
  function TestNormalizeUploadPath (line 32) | func TestNormalizeUploadPath(t *testing.T) {
  function TestBuildFileURL (line 42) | func TestBuildFileURL(t *testing.T) {
  function TestFillFileInfoFromModel (line 56) | func TestFillFileInfoFromModel(t *testing.T) {
  function TestFillFileInfoFromUploadResult (line 80) | func TestFillFileInfoFromUploadResult(t *testing.T) {
  function TestSummarizeImageUploadResults (line 104) | func TestSummarizeImageUploadResults(t *testing.T) {
  function TestSummarizeImageUploadResultsAllFailed (line 116) | func TestSummarizeImageUploadResultsAllFailed(t *testing.T) {
  function TestIsPartialImageUploadError (line 127) | func TestIsPartialImageUploadError(t *testing.T) {

FILE: internal/service/common_upload_helpers.go
  constant defaultUploadSubDir (line 18) | defaultUploadSubDir = "default"
  function buildFileURL (line 20) | func buildFileURL(uuid string) string {
  function setFileFailure (line 31) | func setFileFailure(info *utils.FileInfo, reason string, err error) (*ut...
  function visibilityFlag (line 37) | func visibilityFlag(isPublic bool) uint8 {
  function storageBasePath (line 44) | func storageBasePath(isPublic bool) string {
  function storageBaseForDriver (line 52) | func storageBaseForDriver(driverName string, isPublic bool, bucket strin...
  function normalizeUploadPath (line 59) | func normalizeUploadPath(path string) (string, error) {
  function resolveUploadDestination (line 78) | func resolveUploadDestination(basePath, uploadPath string) (string, erro...
  function findReusableUploadFile (line 96) | func findReusableUploadFile(hash string, isPublic uint8) (*model.UploadF...
  function existingUploadFileExists (line 104) | func existingUploadFileExists(basePath, relativePath string) bool {
  function fillFileInfoFromModel (line 113) | func fillFileInfoFromModel(fileInfo *utils.FileInfo, uploadFile *model.U...
  function fillFileInfoFromUploadResult (line 125) | func fillFileInfoFromUploadResult(fileInfo *utils.FileInfo, result *util...
  function classifyUploadFileType (line 137) | func classifyUploadFileType(mimeType string) string {
  function cleanupStoredUpload (line 163) | func cleanupStoredUpload(path string) {
  function summarizeImageUploadResults (line 170) | func summarizeImageUploadResults(filesInfo []*utils.FileInfo) ([]*utils....
  function IsPartialImageUploadError (line 193) | func IsPartialImageUploadError(err error) bool {
  function initUploadResult (line 198) | func initUploadResult(fileHeader *multipart.FileHeader) *utils.FileInfo {

FILE: internal/service/common_upload_helpers_test.go
  function TestNormalizeUploadPathRejectsTraversal (line 5) | func TestNormalizeUploadPathRejectsTraversal(t *testing.T) {
  function TestNormalizeUploadPathKeepsRelativeSubdirs (line 14) | func TestNormalizeUploadPathKeepsRelativeSubdirs(t *testing.T) {

FILE: internal/service/dashboard/overview.go
  type Metric (line 14) | type Metric struct
  type ActivityItem (line 24) | type ActivityItem struct
  type UserLogin (line 32) | type UserLogin struct
  type Overview (line 37) | type Overview struct
  type OverviewService (line 43) | type OverviewService struct
    method Overview (line 51) | func (s *OverviewService) Overview() (*Overview, error) {
  function NewOverviewService (line 47) | func NewOverviewService() *OverviewService {
  function countRows (line 115) | func countRows(db *gorm.DB) (int64, error) {
  function countDistinct (line 123) | func countDistinct(db *gorm.DB, field string) (int64, error) {
  function taskCompletionRate (line 131) | func taskCompletionRate(db *gorm.DB, start time.Time, end time.Time) (fl...
  function buildActivities (line 146) | func buildActivities(db *gorm.DB) []ActivityItem {
  function formatChange (line 180) | func formatChange(current int64, previous int64) string {
  function strconvFormatFloat (line 195) | func strconvFormatFloat(value float64) string {
  function changeType (line 199) | func changeType(current int64, previous int64) string {
  function inverseChangeType (line 206) | func inverseChangeType(current int64, previous int64) string {
  function activityType (line 213) | func activityType(status int) string {

FILE: internal/service/dept/audit_diff.go
  method CreateWithAuditDiff (line 31) | func (s *DeptService) CreateWithAuditDiff(params *form.CreateDept) (stri...
  method UpdateWithAuditDiff (line 52) | func (s *DeptService) UpdateWithAuditDiff(params *form.UpdateDept) (stri...
  method DeleteWithAuditDiff (line 71) | func (s *DeptService) DeleteWithAuditDiff(id uint) (string, error) {
  method BindRoleWithAuditDiff (line 83) | func (s *DeptService) BindRoleWithAuditDiff(params *form.DeptBindRole) (...
  method snapshotDeptByID (line 102) | func (s *DeptService) snapshotDeptByID(id uint) (map[string]any, error) {
  method snapshotDeptRoleBinding (line 128) | func (s *DeptService) snapshotDeptRoleBinding(deptID uint) (map[string]a...
  function buildDeptDiff (line 146) | func buildDeptDiff(before, after map[string]any) string {

FILE: internal/service/dept/audit_diff_test.go
  function TestBuildDeptDiffContainsRoleIDs (line 8) | func TestBuildDeptDiffContainsRoleIDs(t *testing.T) {

FILE: internal/service/dept/dept.go
  constant maxDeptLevel (line 18) | maxDeptLevel = 5
  constant deptRootPid (line 19) | deptRootPid  = 0
  type DeptService (line 23) | type DeptService struct
    method List (line 33) | func (s *DeptService) List(params *form.ListDept) any {
    method buildListCondition (line 47) | func (s *DeptService) buildListCondition(params *form.ListDept) (strin...
    method Create (line 55) | func (s *DeptService) Create(params *form.CreateDept) error {
    method Update (line 66) | func (s *DeptService) Update(params *form.UpdateDept) error {
    method Delete (line 78) | func (s *DeptService) Delete(id uint) error {
    method Detail (line 94) | func (s *DeptService) Detail(id uint) (any, error) {
    method BindRole (line 103) | func (s *DeptService) BindRole(params *form.DeptBindRole) error {
    method updateDeptRole (line 133) | func (s *DeptService) updateDeptRole(deptId uint, roleIds []uint, tx ....
    method userIDsByDept (line 178) | func (s *DeptService) userIDsByDept(deptId uint, tx ...*gorm.DB) ([]ui...
  function NewDeptService (line 28) | func NewDeptService() *DeptService {

FILE: internal/service/dept/dept_mutation.go
  type deptMutation (line 16) | type deptMutation struct
  method applyDeptMutation (line 31) | func (s *DeptService) applyDeptMutation(params *deptMutation) (*model.De...
  method generateDeptCode (line 134) | func (s *DeptService) generateDeptCode() string {
  method buildPidsUpdateExpr (line 146) | func (s *DeptService) buildPidsUpdateExpr(originPids, newPids string) st...
  method executeDeleteTransaction (line 167) | func (s *DeptService) executeDeleteTransaction(dept *model.Department, i...

FILE: internal/service/dept/dept_test.go
  function TestGenerateDeptCodeUsesUniqueDefaultPrefix (line 5) | func TestGenerateDeptCodeUsesUniqueDefaultPrefix(t *testing.T) {

FILE: internal/service/file_object.go
  type uploadFileObjectInput (line 15) | type uploadFileObjectInput struct
  function findReusableFileObject (line 28) | func findReusableFileObject(tx *gorm.DB, storageDriver, bucket, hash str...
  function findFileObjectByID (line 41) | func findFileObjectByID(tx *gorm.DB, id uint) (*model.UploadFileObject, ...
  function createFileObject (line 52) | func createFileObject(tx *gorm.DB, input uploadFileObjectInput) (*model....
  function ensureFileObject (line 79) | func ensureFileObject(tx *gorm.DB, input uploadFileObjectInput) (*model....
  function normalizeFileObjectBucket (line 89) | func normalizeFileObjectBucket(storageDriver, bucket string) string {
  function applyObjectToUploadFile (line 96) | func applyObjectToUploadFile(uploadFile *model.UploadFiles, object *mode...
  method deletePhysicalObject (line 122) | func (s *FileResourceService) deletePhysicalObject(object *model.UploadF...
  method deleteObjectIfUnreferenced (line 147) | func (s *FileResourceService) deleteObjectIfUnreferenced(db *gorm.DB, ob...

FILE: internal/service/file_reference.go
  type FileReferenceService (line 16) | type FileReferenceService struct
    method BindReference (line 28) | func (s *FileReferenceService) BindReference(fileURL, ownerType string...
    method ReleaseReference (line 57) | func (s *FileReferenceService) ReleaseReference(fileURL, ownerType str...
    method ReleaseReferencesByOwner (line 72) | func (s *FileReferenceService) ReleaseReferencesByOwner(ownerType stri...
    method HasActiveReferences (line 87) | func (s *FileReferenceService) HasActiveReferences(fileID uint) (bool,...
    method List (line 102) | func (s *FileReferenceService) List(params *form.FileReferenceList) *r...
    method ReferencesByFileID (line 149) | func (s *FileReferenceService) ReferencesByFileID(fileID uint) ([]*mod...
    method dbOrDefault (line 182) | func (s *FileReferenceService) dbOrDefault() (*gorm.DB, error) {
  function NewFileReferenceService (line 20) | func NewFileReferenceService(tx ...*gorm.DB) *FileReferenceService {
  function buildFileReferenceListQuery (line 121) | func buildFileReferenceListQuery(params *form.FileReferenceList) *query_...
  function referenceListFileID (line 139) | func referenceListFileID(params *form.FileReferenceList) uint {
  function ExtractFileUUID (line 162) | func ExtractFileUUID(raw string) string {

FILE: internal/service/file_reference_test.go
  function TestExtractFileUUID (line 10) | func TestExtractFileUUID(t *testing.T) {
  function TestReferenceListFileIDUsesIDAlias (line 26) | func TestReferenceListFileIDUsesIDAlias(t *testing.T) {
  function TestReferenceListFileIDPrefersFileID (line 33) | func TestReferenceListFileIDPrefersFileID(t *testing.T) {
  function TestBuildFileReferenceListQuerySkipsZeroOwnerID (line 40) | func TestBuildFileReferenceListQuerySkipsZeroOwnerID(t *testing.T) {
  function TestBuildFileReferenceListQueryAddsPositiveOwnerID (line 51) | func TestBuildFileReferenceListQueryAddsPositiveOwnerID(t *testing.T) {

FILE: internal/service/file_resource.go
  type FileResourceService (line 22) | type FileResourceService struct
    method List (line 79) | func (s *FileResourceService) List(params *form.FileResourceList) *res...
    method Detail (line 125) | func (s *FileResourceService) Detail(id uint) (any, error) {
    method Delete (line 146) | func (s *FileResourceService) Delete(id uint, deletedBy uint, reason s...
    method Restore (line 174) | func (s *FileResourceService) Restore(id uint) error {
    method Destroy (line 193) | func (s *FileResourceService) Destroy(id uint) error {
    method References (line 219) | func (s *FileResourceService) References(params *form.FileReferenceLis...
    method findByID (line 232) | func (s *FileResourceService) findByID(id uint) (*model.UploadFiles, e...
    method findByIDUnscoped (line 246) | func (s *FileResourceService) findByIDUnscoped(id uint) (*model.Upload...
    method deletePhysicalFile (line 258) | func (s *FileResourceService) deletePhysicalFile(uploadFile *model.Upl...
    method storageDriverByName (line 287) | func (s *FileResourceSer
Condensed preview — 397 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,699K chars).
[
  {
    "path": ".air.toml",
    "chars": 902,
    "preview": "root = \".\"\ntestdata_dir = \"testdata\"\ntmp_dir = \"tmp\"\n\n[build]\n  args_bin = []\n  # 使用相对路径,或者不指定配置文件让程序自动查找\n  bin = \"./tmp"
  },
  {
    "path": ".github/pull_request_template.md",
    "chars": 391,
    "preview": "## 变更说明\n\n- 主要目标:\n- 影响模块:\n- 风险等级:低 / 中 / 高\n\n## 自查清单\n\n- [ ] 已确认本次改动解决的是主要问题,而不是无关重构\n- [ ] 已调研相关代码并复用现有实现(未重复造轮子)\n- [ ] 已评估"
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 2770,
    "preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
  },
  {
    "path": ".github/workflows/go.yml",
    "chars": 589,
    "preview": "name: Go\n\non:\n  push:\n    branches: [ master, x_l_admin ]\n  pull_request:\n    branches: [ master, x_l_admin ]\n\njobs:\n\n  "
  },
  {
    "path": ".gitignore",
    "chars": 803,
    "preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Ou"
  },
  {
    "path": ".golangci.yml",
    "chars": 4298,
    "preview": "# golangci-lint 配置文件\n# 参考: https://golangci-lint.run/usage/configuration/\n\nrun:\n  # 超时时间\n  timeout: 5m\n  # 并发数\n  concurr"
  },
  {
    "path": "LICENSE",
    "chars": 1069,
    "preview": "MIT License\n\nCopyright (c) 2022 wannanbigpig\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
  },
  {
    "path": "MEMORY.md",
    "chars": 12761,
    "preview": "# MEMORY\n\n这份文档写给接手 `gin-layout` 的 AI Agent。它不是项目营销介绍,而是为了让下一位 AI 尽快知道:\n\n- 项目当前真实状态是什么\n- 主要入口、核心链路和高风险区域在哪\n- 关键约束和禁止事项是什么"
  },
  {
    "path": "README.en.md",
    "chars": 15867,
    "preview": "# <div align=\"center\">gin-layout</div>\n\n<div align=\"center\">\n  <a href=\"./README.md\">中文</a> | <strong>English</strong>\n<"
  },
  {
    "path": "README.md",
    "chars": 10222,
    "preview": "# <div align=\"center\">gin-layout</div>\n\n<div align=\"center\">\n  <strong>中文</strong> | <a href=\"./README.en.md\">English</a"
  },
  {
    "path": "build.sh",
    "chars": 11344,
    "preview": "#!/bin/bash\n\nset -e  # 遇到错误立即退出\n\n# ==================== 配置区域 ====================\nPROJECT_NAME=\"go-layout\"\nBUILD_DIR=\"bu"
  },
  {
    "path": "cmd/.gitignore",
    "chars": 21,
    "preview": "!.gitignore\ngo-layout"
  },
  {
    "path": "cmd/bootstrapx/bootstrap.go",
    "chars": 3930,
    "preview": "package bootstrapx\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/wannanbigpig/gin-layout/c"
  },
  {
    "path": "cmd/bootstrapx/bootstrap_test.go",
    "chars": 4136,
    "preview": "package bootstrapx\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/wannanbigp"
  },
  {
    "path": "cmd/command/command.go",
    "chars": 1566,
    "preview": "package command\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/wannanbigpig/gin-layout/cmd/bootstrapx\"\n\t\"github.com/w"
  },
  {
    "path": "cmd/completion.go",
    "chars": 1155,
    "preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar completionNoDesc bool\n\nvar completionCmd = &cobra.C"
  },
  {
    "path": "cmd/cron/cron.go",
    "chars": 2424,
    "preview": "package cron\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/robfig/cron/v3\"\n\t\"github.com/spf13/"
  },
  {
    "path": "cmd/cron/schedule.go",
    "chars": 6029,
    "preview": "package cron\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/robfig/cron/v3\"\n\t\"go.uber.org/zap\"\n"
  },
  {
    "path": "cmd/cron/schedule_test.go",
    "chars": 1271,
    "preview": "package cron\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/robfig/cron/v3\"\n)\n\nfunc TestDailyAtSpecWithHHMM(t *testing.T)"
  },
  {
    "path": "cmd/cron/task_record_test.go",
    "chars": 2605,
    "preview": "package cron\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/model\"\n\t\"g"
  },
  {
    "path": "cmd/cron/tasks.go",
    "chars": 1306,
    "preview": "package cron\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/wannanbigpig/gin-layout/config\"\n\ttaskcron"
  },
  {
    "path": "cmd/cron/tasks_test.go",
    "chars": 1154,
    "preview": "package cron\n\nimport (\n\t\"testing\"\n\n\t\"github.com/wannanbigpig/gin-layout/config\"\n)\n\nfunc TestDefineScheduleSkipsResetTask"
  },
  {
    "path": "cmd/root.go",
    "chars": 2687,
    "preview": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/wannanbigpig/g"
  },
  {
    "path": "cmd/service/service.go",
    "chars": 2437,
    "preview": "package service\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\""
  },
  {
    "path": "cmd/version/version.go",
    "chars": 358,
    "preview": "package version\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/global\"\n)\n\nva"
  },
  {
    "path": "cmd/worker/worker.go",
    "chars": 2068,
    "preview": "package worker\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/wannanbigpig/gin-layout/cmd/"
  },
  {
    "path": "config/.gitignore",
    "chars": 12,
    "preview": "*.yaml\n*.ini"
  },
  {
    "path": "config/autoload/app.go",
    "chars": 3301,
    "preview": "package autoload\n\nimport (\n\t\"github.com/wannanbigpig/gin-layout/pkg/utils\"\n)\n\n// AppConfig 定义应用运行时基础配置。\ntype AppConfig s"
  },
  {
    "path": "config/autoload/jwt.go",
    "chars": 679,
    "preview": "package autoload\n\nimport \"time\"\n\n// JwtConfig 定义 JWT 相关配置。\ntype JwtConfig struct {\n\t// TTL Token 有效期(秒),默认 7200 秒(2 小时)\n"
  },
  {
    "path": "config/autoload/logger.go",
    "chars": 1506,
    "preview": "package autoload\n\n// DivisionTime 定义按时间切割日志时的参数。\ntype DivisionTime struct {\n\t// MaxAge 日志文件保留的最大天数,过期会被删除\n\tMaxAge int `m"
  },
  {
    "path": "config/autoload/mysql.go",
    "chars": 1426,
    "preview": "package autoload\n\nimport \"time\"\n\n// MysqlConfig 定义 MySQL 连接与连接池配置。\ntype MysqlConfig struct {\n\t// Enable 是否启用 MySQL 连接\n\tE"
  },
  {
    "path": "config/autoload/queue.go",
    "chars": 3018,
    "preview": "package autoload\n\n// QueueRedisConfig 队列使用的 Redis 连接配置。\ntype QueueRedisConfig struct {\n\t// Host Redis 服务器地址\n\tHost string"
  },
  {
    "path": "config/autoload/redis.go",
    "chars": 1332,
    "preview": "package autoload\n\nimport \"time\"\n\n// RedisConfig 定义 Redis 连接配置。\ntype RedisConfig struct {\n\t// Enable 是否启用 Redis 连接\n\tEnabl"
  },
  {
    "path": "config/config.go",
    "chars": 2374,
    "preview": "package config\n\nimport (\n\t\"sort\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/spf13/viper\"\n\t\"github.com/wannanbigpig/gin-layout/"
  },
  {
    "path": "config/config.yaml.example",
    "chars": 2560,
    "preview": "# 该文件为配置示例文件,请复制该文件改名为 config.yaml, 不要直接修改该文件,修改无意义\napp:\n  app_env: local\n  debug: true\n  language: zh_CN\n#  allow_degra"
  },
  {
    "path": "config/config_clone.go",
    "chars": 2002,
    "preview": "package config\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/wannanbigpig/gin-layout/config/autoload\"\n)\n\nfunc setActiveConf"
  },
  {
    "path": "config/config_load.go",
    "chars": 6401,
    "preview": "package config\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/spf13/vi"
  },
  {
    "path": "config/config_load_test.go",
    "chars": 4367,
    "preview": "package config\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n)\n\nfunc assertSamePath(t *testing.T, expected string, actual "
  },
  {
    "path": "config/config_test.go",
    "chars": 1866,
    "preview": "package config\n\nimport \"testing\"\n\nfunc resetConfigReloadHandlersForTest(t *testing.T) {\n\tt.Helper()\n\treloadHandlersMu.Lo"
  },
  {
    "path": "config/provider.go",
    "chars": 228,
    "preview": "package config\n\n// GetConfigFrom 通过指定 provider 获取配置,并保证返回值非 nil。\nfunc GetConfigFrom(provider func() *Conf) *Conf {\n\tif p"
  },
  {
    "path": "config/runtime.go",
    "chars": 3618,
    "preview": "package config\n\nimport \"reflect\"\n\n// BuildConfigDiff 生成配置差异摘要。\nfunc BuildConfigDiff(oldConfig, newConfig *Conf) ConfigDi"
  },
  {
    "path": "config/runtime_test.go",
    "chars": 2671,
    "preview": "package config\n\nimport (\n\t\"testing\"\n\n\t. \"github.com/wannanbigpig/gin-layout/config/autoload\"\n)\n\nfunc TestBuildConfigDiff"
  },
  {
    "path": "config/testing_helper.go",
    "chars": 746,
    "preview": "package config\n\n// CloneConf 返回配置的深拷贝,避免测试场景下共享可变引用。\nfunc CloneConf(src *Conf) *Conf {\n\tif src == nil {\n\t\treturn &Conf{}"
  },
  {
    "path": "data/data.go",
    "chars": 864,
    "preview": "package data\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\n\tc \"github.com/wannanbigpig/gin-layout/config\"\n)\n\nvar once sync.Once\nva"
  },
  {
    "path": "data/data_test.go",
    "chars": 303,
    "preview": "package data\n\nimport \"testing\"\n\nfunc TestShutdownWithoutInitializedResources(t *testing.T) {\n\tif err := Shutdown(); err "
  },
  {
    "path": "data/migrations/20260425000001_init_table.down.sql",
    "chars": 1420,
    "preview": "BEGIN;\n\n-- 删除系统参数与字典相关表\nDROP TABLE IF EXISTS `sys_dict_item_i18n`;\nDROP TABLE IF EXISTS `sys_dict_type_i18n`;\nDROP TABLE"
  },
  {
    "path": "data/migrations/20260425000001_init_table.up.sql",
    "chars": 43323,
    "preview": "BEGIN;\n\n-- 创建管理员表\nCREATE TABLE IF NOT EXISTS `admin_user`\n(\n    `id`                int unsigned                        "
  },
  {
    "path": "data/migrations/20260425000002_init_data.down.sql",
    "chars": 2416,
    "preview": "BEGIN;\n\n-- 回滚系统数据\n\n-- 删除任务中心/系统管理新增菜单与映射\nDELETE FROM `role_menu_map` WHERE `menu_id` BETWEEN 42 AND 76;\nDELETE FROM `men"
  },
  {
    "path": "data/migrations/20260425000002_init_data.up.sql",
    "chars": 35957,
    "preview": "BEGIN;\n\n-- 初始化系统数据\n\n-- 初始密码 123456\nINSERT INTO `admin_user` (`id`, `nickname`, `username`, `password`, `phone_number`, `"
  },
  {
    "path": "data/migrations/20260515010000_upload_file_objects.down.sql",
    "chars": 141,
    "preview": "ALTER TABLE `upload_files`\n    DROP KEY `idx_file_object_id`,\n    DROP COLUMN `file_object_id`;\n\nDROP TABLE IF EXISTS `u"
  },
  {
    "path": "data/migrations/20260515010000_upload_file_objects.up.sql",
    "chars": 1536,
    "preview": "CREATE TABLE IF NOT EXISTS `upload_file_objects`\n(\n    `id`             int unsigned NOT NULL AUTO_INCREMENT,\n    `stora"
  },
  {
    "path": "data/mysql.go",
    "chars": 6021,
    "preview": "package data\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n"
  },
  {
    "path": "data/redis.go",
    "chars": 3510,
    "preview": "package data\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/redis/go-redis/v9\"\n\n\tc \"github."
  },
  {
    "path": "data/runtime_health.go",
    "chars": 1116,
    "preview": "package data\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\nconst defaultRuntimeHealthTTL = 3 * time.Second\n\n// RuntimeHealthStatus 表示依赖最近"
  },
  {
    "path": "data/runtime_health_test.go",
    "chars": 4382,
    "preview": "package data\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/redis/go-redis/v9\"\n\t\"gorm.io/driver/sqlite\"\n\t\"gorm.io/"
  },
  {
    "path": "docs/COMMANDS_AND_TASKS.en.md",
    "chars": 14491,
    "preview": "# Commands, Scheduled Jobs, and Queue Usage\n\nThis document covers:\n\n- Available project commands and what they do\n- How "
  },
  {
    "path": "docs/COMMANDS_AND_TASKS.md",
    "chars": 9644,
    "preview": "# 命令、定时任务与队列使用说明\n\n本文档集中说明以下内容:\n\n- 项目支持的命令及用途\n- 如何启动 `service`、`worker`、`cron`\n- 如何新增定时任务\n- 如何创建、发布、消费异步队列任务\n- 如何把任务放入指定队"
  },
  {
    "path": "docs/DONATE.en.md",
    "chars": 999,
    "preview": "# <div align=\"center\">Support gin-layout</div>\n\n<div align=\"center\">\n  <a href=\"./DONATE.md\">中文</a> | <strong>English</s"
  },
  {
    "path": "docs/DONATE.md",
    "chars": 666,
    "preview": "# <div align=\"center\">赞助 gin-layout</div>\n\n<div align=\"center\">\n  <strong>简体中文</strong> | <a href=\"./DONATE.en.md\">Engli"
  },
  {
    "path": "docs/MIGRATE_COMMANDS.en.md",
    "chars": 6817,
    "preview": "# Migration Command Guide\n\nThis document explains the built-in `command migrate` command group, including:\n\n- why the pr"
  },
  {
    "path": "docs/MIGRATE_COMMANDS.md",
    "chars": 4822,
    "preview": "# 迁移命令详细说明\n\n本文档说明项目内置的 `command migrate` 命令组,包括:\n\n- 为什么统一使用项目命令\n- 迁移文件命名规范\n- `create/check/up/down/goto/force/version` 的"
  },
  {
    "path": "docs/SECURITY_PERMISSION_FIXES_2026-05.md",
    "chars": 2327,
    "preview": "# Security and Permission Fixes - 2026-05\n\n## Scope\n\n- Backend project: `go-layout`\n- Frontend project: `x-l-admin-vue3`"
  },
  {
    "path": "docs/SYSTEM_CONFIG_AND_DICT_GUIDELINES.en.md",
    "chars": 6754,
    "preview": "# Backend Guidelines for System Config and Dictionary\n\nThis document defines the `sys_config` and `sys_dict` boundary fr"
  },
  {
    "path": "docs/SYSTEM_CONFIG_AND_DICT_GUIDELINES.md",
    "chars": 2898,
    "preview": "# 系统配置与系统字典后端接入规范\n\n本文档从后端视角约定 `sys_config` 与 `sys_dict` 的使用边界。核心目标是避免把权限、状态机、数据结构、安全边界等后端核心规则做成后台可随意修改的配置,同时为运行时策略和展示型枚举"
  },
  {
    "path": "go.mod",
    "chars": 7617,
    "preview": "module github.com/wannanbigpig/gin-layout\n\ngo 1.26\n\nrequire (\n\tgithub.com/casbin/casbin/v3 v3.10.0\n\tgithub.com/casbin/go"
  },
  {
    "path": "go.sum",
    "chars": 48754,
    "preview": "filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=\nfilippo.io/edwards25519 v1.2.0/go.mod h1:"
  },
  {
    "path": "internal/access/casbin/adapter.go",
    "chars": 593,
    "preview": "package casbinx\n\nimport (\n\t\"github.com/casbin/casbin/v3\"\n\t\"github.com/casbin/casbin/v3/model\"\n\tgormadapter \"github.com/c"
  },
  {
    "path": "internal/access/casbin/casbin.go",
    "chars": 1500,
    "preview": "package casbinx\n\nimport (\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/casbin/casbin/v3\"\n\t\"github.com/casbin/casbin/v3/model\"\n\t_ \"git"
  },
  {
    "path": "internal/access/casbin/enforcer_init.go",
    "chars": 1493,
    "preview": "package casbinx\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/casbin/casbin/v3/model\"\n\t\"gorm.io/gorm\"\n"
  },
  {
    "path": "internal/access/casbin/policy_ops.go",
    "chars": 2738,
    "preview": "package casbinx\n\nimport (\n\t\"errors\"\n\n\t\"github.com/casbin/casbin/v3\"\n\t\"gorm.io/gorm\"\n)\n\n// execute 使用共享 Enforcer 或事务级 Enf"
  },
  {
    "path": "internal/console/confirm.go",
    "chars": 664,
    "preview": "package console\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\tlog \"github.com/wannanbigpig/gin-layout/internal/pkg/logger"
  },
  {
    "path": "internal/console/demo/demo.go",
    "chars": 404,
    "preview": "package demo\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar (\n\tCmd = &cobra.Command{\n\t\tUse:     \"demo\",\n\t\tShort:   \""
  },
  {
    "path": "internal/console/init/init.go",
    "chars": 2729,
    "preview": "package init\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\tconsolex \"github.com/wannanbigpig/gin-layo"
  },
  {
    "path": "internal/console/migrate/migrate.go",
    "chars": 14653,
    "preview": "package migrate\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"git"
  },
  {
    "path": "internal/console/system_init/system_init.go",
    "chars": 1465,
    "preview": "package system_init\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"go.uber.org/zap\"\n\n\tconsolex \"github.com/wannanbigpig/g"
  },
  {
    "path": "internal/console/task/task.go",
    "chars": 7121,
    "preview": "package task\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"text/tabwriter\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/wannan"
  },
  {
    "path": "internal/console/task/task_test.go",
    "chars": 1916,
    "preview": "package task\n\nimport (\n\t\"testing\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/model\"\n)\n\nfunc TestBuildAsyncScanRowsMa"
  },
  {
    "path": "internal/controller/admin_v1/auth.go",
    "chars": 2910,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_admin_user.go",
    "chars": 4412,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_api.go",
    "chars": 1179,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_dept.go",
    "chars": 2552,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_file_resource.go",
    "chars": 6201,
    "preview": "package admin_v1\n\nimport (\n\tstderrors \"errors\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/intern"
  },
  {
    "path": "internal/controller/admin_v1/auth_login_log.go",
    "chars": 1138,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_menu.go",
    "chars": 2833,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_request_log.go",
    "chars": 2265,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_role.go",
    "chars": 2179,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_session.go",
    "chars": 1078,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_storage_config.go",
    "chars": 1614,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_sys_config.go",
    "chars": 3266,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_sys_dict.go",
    "chars": 3990,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_task_center.go",
    "chars": 4105,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/auth_test.go",
    "chars": 5124,
    "preview": "package admin_v1\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"ti"
  },
  {
    "path": "internal/controller/admin_v1/dashboard.go",
    "chars": 586,
    "preview": "package admin_v1\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\"\n\t\"gith"
  },
  {
    "path": "internal/controller/admin_v1/sys_common.go",
    "chars": 2213,
    "preview": "package admin_v1\n\nimport (\n\t\"os\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/controller\""
  },
  {
    "path": "internal/controller/sys_base.go",
    "chars": 3177,
    "preview": "package controller\n\nimport (\n\tstderrors \"errors\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/wannanbig"
  },
  {
    "path": "internal/controller/sys_base_test.go",
    "chars": 895,
    "preview": "package controller\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\""
  },
  {
    "path": "internal/controller/sys_demo.go",
    "chars": 477,
    "preview": "package controller\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\n// DemoController Demo控制器\ntype DemoController struct"
  },
  {
    "path": "internal/cron/queue_fallback.go",
    "chars": 968,
    "preview": "package taskcron\n\nimport (\n\t\"context\"\n\n\t\"github.com/wannanbigpig/gin-layout/config\"\n\t\"github.com/wannanbigpig/gin-layout"
  },
  {
    "path": "internal/cron/queue_fallback_test.go",
    "chars": 1410,
    "preview": "package taskcron\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/wannanbigpig/gin-layout/config\"\n\t\"github.com/wannanbigpig"
  },
  {
    "path": "internal/cron/registry.go",
    "chars": 4210,
    "preview": "package taskcron\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\n\t\"githu"
  },
  {
    "path": "internal/cron/registry_test.go",
    "chars": 3270,
    "preview": "package taskcron\n\nimport (\n\t\"testing\"\n\n\t\"gorm.io/driver/sqlite\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/wannanbigpig/gin-layout/co"
  },
  {
    "path": "internal/filestorage/aliyun_oss.go",
    "chars": 2769,
    "preview": "package filestorage\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss\"\n\t"
  },
  {
    "path": "internal/filestorage/local.go",
    "chars": 3050,
    "preview": "package filestorage\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype Local"
  },
  {
    "path": "internal/filestorage/local_test.go",
    "chars": 1425,
    "preview": "package filestorage\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestLocalDriverPutExistsOpenDelete("
  },
  {
    "path": "internal/filestorage/s3.go",
    "chars": 3096,
    "preview": "package filestorage\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go-v2/aws\"\n\tawsconfig \"githu"
  },
  {
    "path": "internal/filestorage/types.go",
    "chars": 2201,
    "preview": "package filestorage\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"time\"\n)\n\nconst MaskPlaceholder = \"******\"\n\ntype Config struct {\n\tLocal "
  },
  {
    "path": "internal/global/api_auth_mode.go",
    "chars": 732,
    "preview": "package global\n\n// ApiAuthMode 定义 API 路由的鉴权模式。\ntype ApiAuthMode uint8\n\nconst (\n\t// ApiAuthModeNone 无需登录,无需 API 权限校验。\n\tAp"
  },
  {
    "path": "internal/global/api_auth_mode_test.go",
    "chars": 1126,
    "preview": "package global\n\nimport \"testing\"\n\nfunc TestApiAuthModeRequiresLogin(t *testing.T) {\n\tif ApiAuthModeNone.RequiresLogin() "
  },
  {
    "path": "internal/global/auth.go",
    "chars": 334,
    "preview": "package global\n\nconst (\n\tSuperAdminId          uint = 1\n\tIssuer                     = \"go-layout\"\n\tPcAdminSubject       "
  },
  {
    "path": "internal/global/common.go",
    "chars": 417,
    "preview": "package global\n\nconst (\n\t// Version is the current gin-layout version.\n\tVersion = \"0.9.2\"\n\t// PerPage is the default per"
  },
  {
    "path": "internal/global/context_keys.go",
    "chars": 425,
    "preview": "package global\n\nconst (\n\tContextKeyUID              = \"uid\"\n\tContextKeyAdminUser        = \"admin_user\"\n\tContextKeyAuthPr"
  },
  {
    "path": "internal/global/system_defaults.go",
    "chars": 111,
    "preview": "package global\n\nconst (\n\tDefaultDepartmentCode = \"default_department\"\n\tSuperAdminRoleCode    = \"super_admin\"\n)\n"
  },
  {
    "path": "internal/jobs/audit_log.go",
    "chars": 2862,
    "preview": "package jobs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/wannanbigpig/gin-layout/config\"\n\tlog \"github.com/wannanbi"
  },
  {
    "path": "internal/jobs/audit_log_test.go",
    "chars": 2183,
    "preview": "package jobs\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/queue\"\n\tauditsvc \""
  },
  {
    "path": "internal/jobs/registry.go",
    "chars": 219,
    "preview": "package jobs\n\nimport \"github.com/wannanbigpig/gin-layout/internal/queue\"\n\n// NewRegistry 创建并注册当前版本的全部任务处理器。\nfunc NewRegi"
  },
  {
    "path": "internal/middleware/admin_auth.go",
    "chars": 3582,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\"\n\n\tcasbinx \"github.com/wannanbigpig/g"
  },
  {
    "path": "internal/middleware/admin_auth_test.go",
    "chars": 4685,
    "preview": "package middleware\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/casbin"
  },
  {
    "path": "internal/middleware/audit_context.go",
    "chars": 1023,
    "preview": "package middleware\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-lay"
  },
  {
    "path": "internal/middleware/audit_queue.go",
    "chars": 3543,
    "preview": "package middleware\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/zap\""
  },
  {
    "path": "internal/middleware/audit_queue_test.go",
    "chars": 6128,
    "preview": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github"
  },
  {
    "path": "internal/middleware/cors.go",
    "chars": 3477,
    "preview": "package middleware\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/wannanbigpig/gin-layout"
  },
  {
    "path": "internal/middleware/cors_test.go",
    "chars": 4360,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wann"
  },
  {
    "path": "internal/middleware/database_ready.go",
    "chars": 871,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/data\"\n\t\"github.com/wannan"
  },
  {
    "path": "internal/middleware/database_ready_test.go",
    "chars": 3368,
    "preview": "package middleware\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\n"
  },
  {
    "path": "internal/middleware/logger.go",
    "chars": 1769,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/job"
  },
  {
    "path": "internal/middleware/logger_bench_test.go",
    "chars": 1242,
    "preview": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\n"
  },
  {
    "path": "internal/middleware/logger_recorder.go",
    "chars": 5079,
    "preview": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"gith"
  },
  {
    "path": "internal/middleware/logger_storage.go",
    "chars": 8225,
    "preview": "package middleware\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"g"
  },
  {
    "path": "internal/middleware/logger_test.go",
    "chars": 10654,
    "preview": "package middleware\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"gith"
  },
  {
    "path": "internal/middleware/parse_token.go",
    "chars": 876,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\treq \"github.com/wannanbigpig/gin-layout/internal/pkg/request\""
  },
  {
    "path": "internal/middleware/recovery.go",
    "chars": 2488,
    "preview": "package middleware\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/"
  },
  {
    "path": "internal/middleware/request_cost.go",
    "chars": 477,
    "preview": "package middleware\n\nimport (\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/google/uuid\"\n\n\t\"github.com/wannanbigpig/g"
  },
  {
    "path": "internal/middleware/request_locale.go",
    "chars": 782,
    "preview": "package middleware\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/global\"\n\t\"github"
  },
  {
    "path": "internal/middleware/request_locale_test.go",
    "chars": 1008,
    "preview": "package middleware\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wann"
  },
  {
    "path": "internal/model/admin_login_logs.go",
    "chars": 5463,
    "preview": "package model\n\nimport (\n\t\"time\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/global\"\n\t\"github.com/wannanbigpig/gin-lay"
  },
  {
    "path": "internal/model/admin_user_dept_map.go",
    "chars": 2376,
    "preview": "package model\n\n// AdminUserDeptMap 管理员用户部门关系表\ntype AdminUserDeptMap struct {\n\tBaseModel\n\tUid    uint `json:\"uid\"`     //"
  },
  {
    "path": "internal/model/admin_user_role_map.go",
    "chars": 2263,
    "preview": "package model\n\n// AdminUserRoleMap 管理员用户角色关系表\ntype AdminUserRoleMap struct {\n\tBaseModel\n\tUid    uint `json:\"uid\"`     //"
  },
  {
    "path": "internal/model/admin_users.go",
    "chars": 4596,
    "preview": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"gorm.io/gorm/clause\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/model/modelDict\"\n\t"
  },
  {
    "path": "internal/model/admin_users_test.go",
    "chars": 578,
    "preview": "package model\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestExistsWithLockExcludeIdRejectsUnknownField(t *testing.T) {\n\tadm"
  },
  {
    "path": "internal/model/api.go",
    "chars": 2588,
    "preview": "package model\n\nimport (\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/global\"\n\t\""
  },
  {
    "path": "internal/model/base.go",
    "chars": 1973,
    "preview": "package model\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/plugin/soft_delete\"\n\n\t\"github.com/wannanb"
  },
  {
    "path": "internal/model/base_crud.go",
    "chars": 3744,
    "preview": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/gl"
  },
  {
    "path": "internal/model/base_list.go",
    "chars": 7284,
    "preview": "package model\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n\n\t\"github.com/wannanbigpig/g"
  },
  {
    "path": "internal/model/base_list_test.go",
    "chars": 1398,
    "preview": "package model\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestNormalizeOrderByAcceptsValidFields(t *testing.T) {\n\torderBy, e"
  },
  {
    "path": "internal/model/base_owner.go",
    "chars": 893,
    "preview": "package model\n\nimport \"gorm.io/gorm\"\n\ntype ownerBinder interface {\n\tbindOwner(any)\n}\n\n// SetDB 为当前模型绑定事务或指定数据库连接。\nfunc ("
  },
  {
    "path": "internal/model/base_test.go",
    "chars": 1975,
    "preview": "package model\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\nfunc TestGetDBReturnsErrorWhenUninitialized(t *testing.T) {\n\t_, err := G"
  },
  {
    "path": "internal/model/base_tree.go",
    "chars": 854,
    "preview": "package model\n\nimport \"gorm.io/gorm\"\n\n// HasChildren 判断指定父节点是否存在子节点。\nfunc HasChildren[T any, M BaseModelInterface[T]](mo"
  },
  {
    "path": "internal/model/dept.go",
    "chars": 2642,
    "preview": "package model\n\nimport (\n\t\"gorm.io/gorm\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/global\"\n)\n\n// Department 部门表\ntype"
  },
  {
    "path": "internal/model/dept_role_map.go",
    "chars": 1949,
    "preview": "package model\n\n// DeptRoleMap 部门角色关联表\ntype DeptRoleMap struct {\n\tBaseModel\n\tDeptId uint `json:\"dept_id\"` // 菜单ID\n\tRoleId"
  },
  {
    "path": "internal/model/file_upload.go",
    "chars": 4934,
    "preview": "package model\n\nimport \"github.com/wannanbigpig/gin-layout/internal/pkg/utils\"\n\nconst (\n\tStorageDriverLocal     = \"local\""
  },
  {
    "path": "internal/model/file_upload_test.go",
    "chars": 479,
    "preview": "package model\n\nimport (\n\t\"sync\"\n\t\"testing\"\n\n\t\"gorm.io/gorm/schema\"\n)\n\nfunc TestUploadFilesETagColumnName(t *testing.T) {"
  },
  {
    "path": "internal/model/login_security_state.go",
    "chars": 1113,
    "preview": "package model\n\nimport (\n\t\"strings\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/pkg/utils\"\n)\n\n// LoginSecurityState 记录"
  },
  {
    "path": "internal/model/menu.go",
    "chars": 5555,
    "preview": "package model\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/model/modelDict\"\n)\n\n// Menu 权限路由表\ntype Men"
  },
  {
    "path": "internal/model/menu_api_map.go",
    "chars": 2651,
    "preview": "package model\n\nimport \"github.com/wannanbigpig/gin-layout/internal/global\"\n\n// MenuApiMap 权限路由表\ntype MenuApiMap struct {"
  },
  {
    "path": "internal/model/menu_i18n.go",
    "chars": 4010,
    "preview": "package model\n\nimport (\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n)\n\n// MenuI18n 菜单标题多语言表。\ntype MenuI18n struct"
  },
  {
    "path": "internal/model/modelDict/base.go",
    "chars": 334,
    "preview": "package modelDict\n\nimport \"github.com/wannanbigpig/gin-layout/internal/global\"\n\ntype Dict map[uint8]string\n\nfunc (d Dict"
  },
  {
    "path": "internal/model/request_logs.go",
    "chars": 1784,
    "preview": "package model\n\n// RequestLogs 请求日志表\ntype RequestLogs struct {\n\tBaseModel\n\tRequestID       string  `json:\"request_id\"`   "
  },
  {
    "path": "internal/model/role.go",
    "chars": 4724,
    "preview": "package model\n\nimport (\n\t\"gorm.io/gorm\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/global\"\n\t\"github.com/wannanbigpig"
  },
  {
    "path": "internal/model/role_menu_map.go",
    "chars": 1823,
    "preview": "package model\n\n// RoleMenuMap 角色菜单关联表\ntype RoleMenuMap struct {\n\tBaseModel\n\tMenuId uint `json:\"menu_id\"` // 菜单ID\n\tRoleId"
  },
  {
    "path": "internal/model/sys_config.go",
    "chars": 3311,
    "preview": "package model\n\nimport (\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n)\n\nconst (\n\tSysConfigValueTypeString = \"string\"\n\tSysConfigValueTypeN"
  },
  {
    "path": "internal/model/sys_dict.go",
    "chars": 4749,
    "preview": "package model\n\n// SysDictType 系统字典类型表。\ntype SysDictType struct {\n\tContainsDeleteBaseModel\n\tTypeCode     string          "
  },
  {
    "path": "internal/model/sys_i18n.go",
    "chars": 11981,
    "preview": "package model\n\nimport (\n\t\"sort\"\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/clause\"\n)\n\ntype SysConfigI18n struct {\n\tBaseM"
  },
  {
    "path": "internal/model/task_center.go",
    "chars": 4121,
    "preview": "package model\n\nimport (\n\t\"github.com/wannanbigpig/gin-layout/internal/pkg/utils\"\n)\n\nconst (\n\tTaskKindAsync = \"async\"\n\tTa"
  },
  {
    "path": "internal/pkg/auditdiff/diff.go",
    "chars": 3380,
    "preview": "package auditdiff\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// ChangeDiffItem 表示单个字段变更。\ntype"
  },
  {
    "path": "internal/pkg/auditdiff/diff_test.go",
    "chars": 1389,
    "preview": "package auditdiff\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n)\n\nfunc TestBuildFieldDiffMapsStatusLabel(t *testing.T) {\n\titems"
  },
  {
    "path": "internal/pkg/errors/code.go",
    "chars": 3515,
    "preview": "package errors\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\nconst (\n\tSUCCESS                   = 0\n\tFAILURE                   = 1\n\tAut"
  },
  {
    "path": "internal/pkg/errors/code_test.go",
    "chars": 686,
    "preview": "package errors\n\nimport (\n\t\"testing\"\n)\n\nfunc TestText(t *testing.T) {\n\tvar errorText = NewErrorText(\"zh_CN\")\n\tif \"OK\" != "
  },
  {
    "path": "internal/pkg/errors/en-us.go",
    "chars": 4323,
    "preview": "package errors\n\nvar enUSText = map[int]string{\n\tSUCCESS:                   \"OK\",\n\tFAILURE:                   \"FAIL\",\n\tNo"
  },
  {
    "path": "internal/pkg/errors/error.go",
    "chars": 3638,
    "preview": "package errors\n\nimport (\n\tstderrors \"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\tc \"github.com/wannanbigpig/gin-layout/config\"\n\t\"github."
  },
  {
    "path": "internal/pkg/errors/zh-cn.go",
    "chars": 2860,
    "preview": "package errors\n\nvar zhCNText = map[int]string{\n\tSUCCESS:                   \"OK\",\n\tFAILURE:                   \"FAIL\",\n\tNo"
  },
  {
    "path": "internal/pkg/func_make/func_make.go",
    "chars": 1064,
    "preview": "package func_make\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n)\n\n// FuncMap 保存可按名称调用的函数映射。\ntype FuncMap map[string]reflect.Value\n\n// "
  },
  {
    "path": "internal/pkg/func_make/func_make_test.go",
    "chars": 752,
    "preview": "package func_make\n\nimport (\n\t\"testing\"\n)\n\nvar (\n\tfuncMap = map[string]interface{}{\n\t\t\"test\": func(str string) string {\n\t"
  },
  {
    "path": "internal/pkg/i18n/locale.go",
    "chars": 3886,
    "preview": "package i18n\n\nimport (\n\t\"encoding/json\"\n\t\"sort\"\n\t\"strings\"\n)\n\nconst (\n\tLocaleZhCN    = \"zh-CN\"\n\tLocaleEnUS    = \"en-US\"\n"
  },
  {
    "path": "internal/pkg/i18n/locale_test.go",
    "chars": 1510,
    "preview": "package i18n\n\nimport \"testing\"\n\nfunc TestParseAcceptLanguage(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\thead"
  },
  {
    "path": "internal/pkg/logger/logger.go",
    "chars": 3663,
    "preview": "package logger\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\trotatelogs \"github.com/lestrrat-"
  },
  {
    "path": "internal/pkg/logger/logger_test.go",
    "chars": 384,
    "preview": "package logger\n\nimport \"testing\"\n\nfunc TestLoggerDefaultIsNotNil(t *testing.T) {\n\tif Logger == nil {\n\t\tt.Fatal(\"expected"
  },
  {
    "path": "internal/pkg/query_builder/query_builder.go",
    "chars": 2348,
    "preview": "package query_builder\n\nimport \"strings\"\n\ntype QueryBuilder struct {\n\tconditions []string\n\targs       []any\n}\n\nfunc New()"
  },
  {
    "path": "internal/pkg/query_builder/query_builder_test.go",
    "chars": 810,
    "preview": "package query_builder\n\nimport \"testing\"\n\nfunc TestQueryBuilderBuildsExpectedCondition(t *testing.T) {\n\tstatus := int8(1)"
  },
  {
    "path": "internal/pkg/request/request.go",
    "chars": 759,
    "preview": "package request\n\nimport (\n\t\"errors\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-layout/internal/pkg/util"
  },
  {
    "path": "internal/pkg/request/request_test.go",
    "chars": 894,
    "preview": "package request\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc TestGetAccess"
  },
  {
    "path": "internal/pkg/response/response.go",
    "chars": 4892,
    "preview": "package response\n\nimport (\n\t\"net/http\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/wannanbigpig/gin-la"
  },
  {
    "path": "internal/pkg/response/response_test.go",
    "chars": 5457,
    "preview": "package response\n\nimport (\n\t\"encoding/json\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"gith"
  },
  {
    "path": "internal/pkg/testkit/secret.go",
    "chars": 273,
    "preview": "package testkit\n\nimport \"strings\"\n\n// SecretKey 返回测试专用密钥,统一管理避免散落硬编码。\nfunc SecretKey(scope string) string {\n\tscope = str"
  },
  {
    "path": "internal/pkg/testkit/secret_test.go",
    "chars": 445,
    "preview": "package testkit\n\nimport \"testing\"\n\nfunc TestSecretKey(t *testing.T) {\n\tsecret := SecretKey(\"auth\")\n\tif secret == \"\" {\n\t\t"
  },
  {
    "path": "internal/pkg/utils/desensitize.go",
    "chars": 1795,
    "preview": "package utils\n\nimport (\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\n// DesensitizeRule 描述字符串脱敏策略。\ntype DesensitizeRule struct {\n\tKeepP"
  },
  {
    "path": "internal/pkg/utils/format_time.go",
    "chars": 1259,
    "preview": "package utils\n\nimport (\n\t\"database/sql/driver\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n)\n\n// FormatDate 为时间字段提供统一的 JSON/SQL 编解码行为。\ntyp"
  },
  {
    "path": "internal/pkg/utils/sensitive/fields.go",
    "chars": 6390,
    "preview": "package sensitive\n\nimport (\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n)\n\nconst (\n\tmaskTokenPrefixLen    = 6\n\tm"
  },
  {
    "path": "internal/pkg/utils/sensitive/http_mask.go",
    "chars": 3058,
    "preview": "package sensitive\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"unicode/utf8\"\n)\n\nfunc maskHeaders(heade"
  },
  {
    "path": "internal/pkg/utils/sensitive/mask.go",
    "chars": 1193,
    "preview": "package sensitive\n\nimport \"strings\"\n\n// maskMap 对 map 进行递归脱敏(使用指定的敏感字段列表)\nfunc maskMap(m map[string]interface{}, sensiti"
  },
  {
    "path": "internal/pkg/utils/sensitive/string_mask.go",
    "chars": 3961,
    "preview": "package sensitive\n\nimport \"strings\"\n\nfunc maskString(s string) string {\n\tif s == \"\" {\n\t\treturn s\n\t}\n\treturn applyMatcher"
  },
  {
    "path": "internal/pkg/utils/token/jwt.go",
    "chars": 3554,
    "preview": "package token\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n\t\"github.com/google/uuid\"\n\n"
  },
  {
    "path": "internal/pkg/utils/token/jwt_test.go",
    "chars": 716,
    "preview": "package token\n\nimport (\n\t\"testing\"\n\n\t\"github.com/golang-jwt/jwt/v5\"\n)\n\nfunc TestGenerate(t *testing.T) {\n\tclaims := jwt."
  },
  {
    "path": "internal/pkg/utils/utils.go",
    "chars": 1777,
    "preview": "package utils\n\nimport (\n\t\"math/rand\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/samber/lo\"\n)\n\n// CalculateChanges 计算差集 ("
  },
  {
    "path": "internal/pkg/utils/utils_test.go",
    "chars": 1154,
    "preview": "package utils\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestCalculateChanges(t *testing.T) {\n\texistingIds := []int{1, 2, 3, 4,"
  },
  {
    "path": "internal/queue/asynqx/asynq.go",
    "chars": 8278,
    "preview": "package asynqx\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/hibiken/asynq\"\n\t\"go.uber.org/zap\"\n\n\t\"githu"
  },
  {
    "path": "internal/queue/asynqx/asynq_test.go",
    "chars": 3167,
    "preview": "package asynqx\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/hibiken/asynq\"\n\n\t\"github.com/wannanbigpig/gin-layout/config\"\n\t"
  },
  {
    "path": "internal/queue/asynqx/inspector_test.go",
    "chars": 952,
    "preview": "package asynqx\n\nimport (\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/hibiken/asynq\"\n\n\t\"github.com/wannanbigpig/gin-layout/interna"
  },
  {
    "path": "internal/queue/asynqx/task_record_test.go",
    "chars": 2156,
    "preview": "package asynqx\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/hibiken/asynq\"\n\n\t\"github.com/wannanbigpig/gin-lay"
  },
  {
    "path": "internal/queue/queue.go",
    "chars": 9470,
    "preview": "package queue\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/wannanbigpig/gin-layo"
  },
  {
    "path": "internal/queue/queue_test.go",
    "chars": 4897,
    "preview": "package queue\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/wannanbigpig/gin-layout/config\"\n\t\"github.com/wanna"
  }
]

// ... and 197 more files (download for full content)

About this extraction

This page contains the full source code of the wannanbigpig/gin-layout GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 397 files (1.4 MB), approximately 440.8k tokens, and a symbol index with 2815 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!