Files
2026-10-20 16:06:40 +08:00

60 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Status
GoFrame v2 后端已按 `document/architecture.md`(唯一权威定稿)完整落地:
- **25 张业务表 → 27 个模块**全部实现(auth/index + system7 + data7 + content3 + monitor4 + tool4);**目录平铺、不分域层级**:`api/v1/{m}.go`(单包 v1)、`internal/controller/{m}.go`(单包 controller)、`internal/logic/{m}.go`(单包 logic)、`internal/service/{m}.go`(接口+注册,手维护,勿整体运行 gf gen service);跨模块直接同包调用(如 user 使用 ResolveCityNames 无需 import)
- 中间件链:CORS → Response → RateLimit → Demo → JWT → Tenant → OperationLog;RateLimit 为固定窗口限流(`rateLimit.*` 配置,登录/验证码独立更严阈值 `authLimit`,计数经 utility/cache 共享,仅限 `/api/` 路径);`app.demo=true` 时 Demo 拦截全部写操作(提示"演示环境,禁止操作!"),读操作与公开白名单路径放行;权限与日志经 `gmeta` 标签在 `internal/router/bind.go` 反射为路由元数据统一处理(bind 同时补注册进 gf goai,`/api.json` 原生 OpenAPI + `/swagger` UI)
- 全局钩子 `internal/model/hooks`:审计字段盖章、软删除改写、租户隔离(`dao.{X}.Ctx` 是唯一查询入口)
- 代码生成器(tool 域):读表结构渲染 `templates/`(后端 `{{ }}` 定界;`templates/ui*` 前端模板因 Vue 插值冲突用 `[[ ]]` 定界)产出 后端八件套(api/controller/logic/service + dao/index、dao/internal、model/entity、model/do,与 gf gen dao 产出同构,生成后统一 gofmt) + 前端文件,并**同步创建菜单+权限节点、字典数据、router.go 注册行**(幂等);支持 HTTP 与 CLI 两种方式
- 整库迁移(`internal/migrate` + `cmd/migrator`):源库内省(表/列/索引/注释)→ 中间表示 → 目标方言 DDL(mysql/pgsql/sqlite/mssql/oracle 五 renderer,对齐 `resource/sql` 定稿)→ 分批搬运 → pgsql 序列修复;纯 CLI、gdb 动态建连裸读裸写(不走 dao,不触发全局钩子);`-drop-target-first` 清空目标库全部表
## 关键约定(与直觉相反的点)
- **主键统一 int 数据库自增**(2026-10-13 定稿):禁用雪花算法(`utility/id` 已删除);新增后用 `result.LastInsertId()` 回读;hooks 不填 id
- 软删列统一 `del_flag`;表前缀 `goframe_`
- **配置环境变量覆盖**(2026-10-20 定稿):`utility/config.RegisterEnvOverride()` 在 cmd.Run 最先接线 env-first 适配器(包装默认文件适配器,gcfg.Adapter 官方扩展点),12-Factor 优先级 GF_ 环境变量 > config.yaml;键名=配置路径大写下划线(`database.default.link` → `GF_DATABASE_DEFAULT_LINK`,**不存在 GF_GENV_ 前缀**);仅覆盖配置文件已有键,空值视为未设置;docker 部署经 compose environment 块把 .env 友好名(MYSQL_HOST 等)映射为 GF_ 规范键,勿原名透传
- 新增模块四件套平铺放置(api/v1、controller、logic、service 各一文件,同包 init 自动注册,无需 blank import);service 接口手维护;路由注册由生成器自动写入或手动 `router.go` `Bind(controller.CXxx)`
- **定时任务处理方法在 `internal/task`**:调度器与注册表在 internal/logic 的 job 模块(`logic.RegisterJobHandler`);任务实现平铺 `internal/task/{任务名}.go`,init 中自注册(注册名=url 字段值,http(s) 地址除外),依赖单向 task→logic(任务可复用 base 共享能力,logic 禁止反向 import task);由 `internal/cmd` 空白导入触发加载;调度消费 execute_policy(1并行/2单例/3放弃)与 is_sync(1同步/0异步),策略2/3叠加 `job:lock:{id}` 执行锁(utility/cache SET NX,多实例共享,TTL 300s 兜底);内置 LogClean 任务每日 02:30 分批物理清理三张日志表(保留天数 `job.logRetentionDays` 默认90,任务参数 retentionDays 可覆盖)
- **monitor 域三个无表模块**:在线用户(`/onlineUser/page`+`forceLogout`,登录 MarkOnline/登出 MarkOffline 写 `online:jti` zset+info 键,踢出=拉黑 jti)、服务器监控(`/server/info`:runtime/磁盘分平台文件 server_monitor_windows/other.go/MySQL/Redis 连通)、缓存监控(`/cache/info`:Redis INFO 解析,内存降级时返回 mode=memory);前端 `ui/src/views/monitor/{online,server,cache}`;OpenAPI 经 bind.go 补注册后 `/api.json` 全量可用
- **文件命名对齐 dao(snake_case)**:`api/v1/{模块}.go`、`internal/controller/{模块}.go`、`internal/logic/{模块}.go`、`internal/service/{模块}.go` 均用表名去前缀形式(如 `config_item.go`/`file_template.go`/`operation_log.go`),生成器后端目标同步此规则(前端文件仍用驼峰如 `fileTemplate.ts`)
- **log_type 值固定**(前端按此映射显示):add=1 update=2 delete=3 query=4 import=5 export=6 … generate=19 reset=24(见 `consts/log_type.go`,前端按此映射显示)
- **筛选对齐三件套**(2026-10-14 定稿):前端 `querySchemas.ts` 每个输入框 ↔ `XxxPageReq` 同名驼峰字段 ↔ spec 声明——字符串 Input → `LikeFields`(逐列模糊,列名声明、值取 req 同名驼峰json),Select/InputNumber → `EqFields`(零值跳过);`KeywordFields`(req.keyword 多列 OR)**能力保留在基座**,模块不声明,未来需要时再用;导出统一走 `xxxSpec.applyFilters` 与列表同套筛选
- 超管角色编码 `ROLE_SUPER_ADMIN`;超管 `getUser` 返回通配权限 `*:*:*`
- **角色数据权限**(`role.data_scope` 四档:1-全部 2-本部门及以下 3-本部门 4-仅本人):模块在 spec 声明 `DataScope: ApplyDataScope("dept_id")` 即生效(internal/logic/data_scope.go,共享工厂);多角色在身份加载层取最宽松,超管/平台绕过,未分配部门用户挂部门档降级为仅本人;**仅作用于 Page/List/导出**(applyFilters 链),Detail/Update/Delete 不过滤;档位文案走字典 `role_data_scope`;身份缓存(300s)含 data_scope+deptId,角色/用户变更主动清
- 定时任务 cron 用 gf gcron 的 `#` 前缀 6 段式(`# */5 * * * *` = 标准5段忽略秒)
- multipart 上传字段用 `*ghttp.UploadFile` + `p:"file"` 标签
- **上传两段式**:`upload.SaveTemp` 先入 `resource/upload/temp/日期/`(白名单+大小+图片嗅探校验),表单保存时 `upload.SaveFromTemp` 移动到正式子目录(user/article/notice/link/example/tenant 等);静态访问 `/upload/**` 由 `SetServerRoot("resource")` 提供
- **命名**:入参/出参 json 一律驼峰;**例外**:登录/令牌接口返回 `access_token/refresh_token/expires_in/token_type`(OAuth2 惯例,前端按此读取,勿改驼峰)
- IP 归属地:`utility/ip2region`(xdb 于 manifest/data/ip2region.xdb,配置 ip2region.path),登录日志/操作日志 location 自动填充;受信代理列表 `api.proxyIps`(支持 CIDR,信任 X-Forwarded-For)
- **logic 共享能力层** `internal/logic/base.go`(横切能力,组合式复用):文件字段(SaveFileField/SaveFileFields/SaveMultiFileField)、富文本占位符(LocalizeContentFields/ExpandContentFields,`[IMG_URL]` 为持久化契约)、枚举文本(DictGetText/SerializeMapOnRead)、唯一校验(CheckUnique,仅为 UX 层,唯一性真值靠 `uk_*` 索引)、事务(WithTrans,事务内禁 HTTP/文件IO、bcrypt 先算)、分块查询(ChunkQuery)、泛型树(BuildTree)、临时目录清理(CleanTemp,每日 03:30 gcron 触发,file.tempExpireDays 控制保留天数);纪律:只依赖 dao/utility/consts,导出函数上限15个
- **{field}Text 字典驱动**:枚举字段显示名从 `dict_item` 解析(唯一真相),模块头部声明 `xxxSerializeMaps = map[字段]字典编码`(编码规范 `{模块snake}_{字段snake}`,如 `user_status`、`job_execute_policy`),响应行用 `DictGetText(ctx, code, value)` 填充;字典由生成器 createDict 按**列注释枚举**自动创建(支持 `1-正常` 与 `1=正常` 两种写法),文案在字典管理页修改;进程内缓存300s。例外:`operation_log.type` 存注解常量值,用静态表 `LogTypeText`(consts.LogTypeNames)
- **生成器保护** `skipTables`:仅关联表(role_menu/user_role)整表跳过;其余表统一文件级处理——**缺失补生成、存在跳过、`generator.overwrite: true` 时覆盖**(唯一开关在 config.yaml,覆盖结果单独计入 Overwritten),菜单/字典/路由等幂等副作用照常执行
- 城市编解码:`ResolveCityNames / ResolveCityCodes`(internal/logic 包级函数),user 模块 Add/Update/Import/Detail 已接入
- 代码生成器 CLI:`go run ./cmd/generator -tables "goframe_x|注释" [-preview|-batch]`;覆盖模式由 `generator.overwrite` 配置统一控制;dao/entity/do 四件由生成器直接产出,生成后只需 `go build ./...` 并重启(`gf gen dao` 仅在**表结构变更**时用于刷新已实现模块的 dao)
## Commands
Windows 环境(无 make,直接用 gf/go):
```sh
go build ./... # 编译
go test ./... # 测试(sqlite 集成 + hooks MySQL 集成)
go vet ./... && gofmt -l . # 静态检查
gf gen dao -c hack/config.yaml # 表结构变更后重新生成 dao(service 手维护,勿用 gf gen)
go run ./cmd/generator -tables "goframe_order|订单表" # 代码生成(文件+菜单+字典+路由)
go run ./cmd/migrator -src-type=mysql -src-host=127.0.0.1 -src-hostport=3306 -src-database=easygoadmin.goframe.elevue -src-username=root -src-password=root -dst-type=pgsql -dst-host=127.0.0.1 -dst-hostport=5432 -dst-database=easygoadmin.goframe.elevue -dst-username=postgres -dst-password=123456 -drop-target-first # 整库迁移(源→目标方言,含结构+注释+索引+数据+序列)
go build -o bin/admin-server.exe main.go && ./bin/admin-server.exe # 运行(:8000)
```
## 环境
- MySQL `127.0.0.1:3306` root/root 库 `easygoadmin.goframe.elevue`;定稿脚本 `document/easygoadmin.goframe.elevue.sql`(人工导入维护)
- Redis `127.0.0.1:6379`(缓存降级内存);本地测试账号 admin/admin123
- 方言 DDL `resource/sql/{mysql,pgsql,sqlite,mssql,oracle}.sql`(后四份转换生成,**待人工校对**)
- 前端 `ui/`(Element Plus;**接口统一 `/api/v1/*`**(`/api` 为通用网关前缀,`/v1` 为版本前缀)——gmeta 路径/公开白名单/健康检查均按此注册,前端 baseURL `/api` + 版本前缀 `/v1` 拼接;登录返回读 `data.access_token`。**例外**:公开路由(`middleware/public.go` 的 `publicPaths`:`/api/login`、`/api/register`、`/api/captcha`、`/api/oauth2/token`、`/api/health`)一律**不带 `/v1`**(网关免版本路径,gmeta path / `router.go` / 白名单三处须一致,勿加 `/v1`)
- 生成器配置 `generator.parentMenuId=189`、`generator.overwrite=false`(覆盖开关);上传配置 `file.*`