================================================================================ EasyGoAdmin 后端开发者文档 · 13 开发 FAQ 与排障 ================================================================================ 按主题分组的常见问题,结论均可回溯到代码位置(括号内为文件)。 -------------------------------------------------------------------------------- A. 接口与路由 -------------------------------------------------------------------------------- Q1: 为什么接口 404?项目文档里不是说有 /v1 吗? A: 实际路由无 /v1 版本段(api/v1 只是包名)。全部 gmeta path 直接以 /api 开头(如 GET /api/user/page)。新增接口后必须:重启服务;若为新模块还需 router.go 中 Bind(controller.CXxx)(internal/router/router.go)。 确认路由是否注册可开 server.dumpRouterMap 或查 GET /api.json。 Q2: 返回 403 "没有该操作权限"? A: gmeta permission 非空且当前用户 Perms 不含该节点(Bind 包装层判定)。 处理:角色管理 → 勾选对应菜单下权限节点 → 保存(会清身份缓存)。 超管(ROLE_SUPER_ADMIN)不受限。permission 留空 = 登录即可。 Q3: 返回 401 但 token 刚签发? A: 依次排查:① Authorization 头格式(Bearer + 空格);② jwt.secret 是否 多实例/重启后不一致;③ 令牌是否已入黑名单(登出/轮换/强退, blacklist:token:{jti});④ access 已过期(默认 2h)应先走 /api/oauth2/token 刷新。 Q4: 演示环境提示“演示环境,禁止操作!”? A: app.demo=true 时写操作全部拒绝(middleware/demo.go)。生产改 false。 Q5: 偶发 429 “请求过于频繁”? A: 固定窗口限流:普通 /api 120 次/分钟/IP/路径;登录/验证码/令牌兑换 10 次/分钟/IP(rateLimit.*)。反向代理后如全部请求变成同一 IP, 检查 api.proxyIps 是否包含代理地址(否则 XFF 不被信任)。 Q6: 在线调试文档在哪里? A: GET /swagger(本地 Redoc,不依赖境外 CDN);GET /api.json(OpenAPI JSON, 含全部补注册的闭包路由)。 -------------------------------------------------------------------------------- B. 认证与权限 -------------------------------------------------------------------------------- Q7: 验证码总是失败? A: ① 校验为一次性(命中即删),前端须每次先取 /api/captcha;② Redis 不可用 降级内存后多实例不共享(验证码与请求可能落在不同实例);③ 开发可用 captcha.bypassCode 万能码,生产必须留空。 Q8: 修改了角色权限但用户仍被拒绝(或仍放行)? A: 身份缓存 user:identity:{uid} TTL 300s。保存角色权限/分配角色会主动清缓存 (role/user 模块);手工改库不会——等 5 分钟或删除对应缓存键。 Q9: 忘记密码/重置密码? A: 超管走 PUT /api/user/resetPwd;本人走 PUT /api/index/updatePassword (需旧密码)。密码存储 bcrypt(password+salt);旧 md5 数据登录会被 IsBcrypt 拒绝,需重置。 Q10: 登录报“该用户名存在于多个租户,请携带租户编码登录”? A: 未指定 tenantCode 时全局查 username,多租户同名优先平台租户(tenant_id=0), 仍歧义则要求携带 tenantCode(logic/auth.go findUser)。 -------------------------------------------------------------------------------- C. 数据与租户 -------------------------------------------------------------------------------- Q11: Update 报 1062 Duplicate entry(如 uk_tenant_mobile)? A: 唯一预检范围必须等于唯一索引范围。索引含 tenant_id + del_flag,预检须按 “被改行的现租户”检查(user 模块已按行现租户实现;spec 用 UniqueRule{Field, Group:"tenant_id"})。注意:直接 UPDATE 全表数据/迁移 后预检与索引不一致是最常见根因。 Q12: 租户用户看到了别的租户数据? A: 排查是否绕过 dao.Ctx 直连 g.DB()(钩子条件不生效);排查该表是否登记进 hooks.tenantTables(新增租户级表必须手工加入,共 9 张)。 Q13: 超管新增数据落到哪个租户? A: 未显式指定时 hooks.insertHook 填 tenant.defaultId(默认 1);显式传 0 可创建平台共享行。业务取归属租户用 consts.GetTenantID(超管=默认租户)。 Q14: 数据被“删除”了但数据库里还有? A: 删除接口全部是软删(del_flag=1,deleteHook 改写)。物理清除走 LogClean 仅限三张日志表;业务表恢复需手工 UPDATE del_flag=0。 Q15: 新加了表/列,代码里读不到? A: gf gen dao -c hack/config.yaml 刷新 dao/entity/do → go build → 重启。 service 接口文件手工维护,勿运行 gf gen service。 -------------------------------------------------------------------------------- D. 文件与富文本 -------------------------------------------------------------------------------- Q16: 上传成功但页面图片 404? A: ① 库中存相对 uploadDir 路径,展示必须经 GetFileUrl(基座 readTransform 自动处理,手写查询勿直接输出原值);② file.domainUrl 与实际可访问地址 一致性;③ 静态服务根为 resource/(SetServerRoot),确认 uploadDir 位于 resource/upload 或已做目录映射。 Q17: 富文本图片路径是 [IMG_URL]/...? A: 这是持久化契约(存库格式),读取侧必须经 Detail/List(基座自动 ExpandContentFields)或手工调用 ExpandContentFields;禁止业务直接读 content 原值给第三方。 Q18: temp 目录越来越大? A: CleanTemp 由 cmd.go 的 gcron 每日 03:30 触发,删除 temp 下超过 file.tempExpireDays(默认 7 天)的日期目录;确认配置非 0 且服务在运行。 Q19: 上传被拒绝(类型/大小)? A: 扩展名白名单 file.fileExt / file.imageExt;大小 file.maxSize(MB); 图片另做魔数嗅探。.svg 可上传但响应强制 attachment+nosniff(防 XSS)。 -------------------------------------------------------------------------------- E. 定时任务 -------------------------------------------------------------------------------- Q20: 任务到点不执行? A: ① 任务 status 必须=1(运行中);② cron 为 gcron 6 段(秒 分 时 日 月 周), 可用 # 前缀写标准 5 段("# */5 * * * *");③ 调度器启动延时 2s 且仅 DaemonInit 一次——改库后需 Resume/Status 或重启;④ 查 GET /api/job/daemonStatus 与 /api/server/info 的 Scheduler (mounted vs expected 差值=挂载失败)。 Q21: 任务执行失败(任务日志 exception)? A: url 为 http(s):检查可达性、params 是否为合法 JSON(作为 POST body)、 30s 超时;url 为本地方法名:须在 internal/task 注册且进程已加载 (RegisterJobHandler 重复注册会 panic)。多实例下策略 2/3 依赖 Redis 锁。 Q22: 如何新增内置任务? A: 见 09 章第 8 节:task 包新文件 + init 注册 + 任务表配置 url=注册名 + go build 重启。 -------------------------------------------------------------------------------- F. 缓存与降级 -------------------------------------------------------------------------------- Q23: Redis 挂了会怎样? A: utility/cache 自动降级进程内存(启动探测 + 运行期自动切换,Warning 日志)。 功能可用,但限流计数/任务锁/在线用户/黑名单/验证码退化为单机语义, 多实例部署会不一致。/api/cache/info 与 /api/server/info 可查当前模式。 Q24: 字典改了文案没生效? A: DictGetText 进程内缓存 300s;可调 GET /api/dict/refreshCache 主动刷新 (清 dict:item:{code} 键),或等自然过期。 -------------------------------------------------------------------------------- G. 代码生成器 -------------------------------------------------------------------------------- Q25: 生成器把我的手工修改覆盖了? A: generator.overwrite=true 时生成即覆盖。恢复手段:git 还原;或改回 false 仅补缺失文件。批量覆盖前务必备份。 Q26: 生成后编译报错/路由没生效? A: ① go build ./...(生成的 go 文件需编译);② 确认 router.go 已插入 Bind 行(生成器自动写入,幂等);③ 重启服务;④ 菜单/权限已建, 需给角色勾选权限节点。 Q27: 非 MySQL 库生成效果差? A: 表注释/枚举解析依赖 information_schema(MySQL)。建议在 MySQL 上执行 生成,或人工补齐注释后重新生成。 -------------------------------------------------------------------------------- H. 数据库迁移 -------------------------------------------------------------------------------- Q28: 迁移报“目标库已存在同名表”? A: 非 -drop-target-first 模式下同名表拦截(建表为 IF NOT EXISTS,数据会 追加,必须前置拦截)。加 -drop-target-first 会清空目标库全部表,慎用。 Q29: 迁移报“表 xxx 无主键,无法分页搬运”? A: 搬运按主键游标分页,无主键表先补主键。 Q30: pgsql 迁移后插入主键冲突? A: 序列修复(setval MAX(id)+1)在迁移末段执行;若手工导数据未走迁移器, 需手工修序。mssql 用 DBCC CHECKIDENT;mysql/sqlite/oracle 自动续接。 -------------------------------------------------------------------------------- I. 工程与质量 -------------------------------------------------------------------------------- Q31: 标准命令有哪些? A: go build ./... | go test ./... | go vet ./... && gofmt -l . | gf gen dao -c hack/config.yaml | make run/build/test/vet/fmt。 Q32: 接口回归怎么比对? A: hack/golden/ 与 hack/after* 存有响应黄金样本;比对时带录制参数 (如 pageSize=3)对齐动态值后再 DIFF。 Q33: 日志在哪里? A: 控制台 + log/ 目录(logger.path)。操作日志通道满丢弃会有 “操作日志通道已满”Warning;中间件依赖缺失启动时有 Warning 提示。 Q34: Windows 重启服务的快捷方式? A: 见 document/启动命令.txt:按端口找 PID → taskkill → 编译 → 启动。 Q35: 用户列表数据权限(data_scope)不生效/过滤范围不对? A: 逐项排查(机制见 03 章第 6 节): 1) 角色 data_scope 值是否正确(SELECT data_scope FROM goframe_role); 2) 用户是否挂了多角色(多角色取最宽松——任一角色为"全部"即不过滤); 3) 身份缓存陈旧:DEL easygoadmin:user:identity:{uid} 或重登 (正常经角色/用户编辑接口变更会主动清); 4) 直接改库 dept_id 不经 API 不清缓存(最长 300s 延迟); 5) 未分配部门(dept_id=0)挂部门档会降级为"仅本人"——属安全兜底设计; 6) 超管/ROLE_SUPER_ADMIN 永远绕过,测试过滤效果需用普通账号; 7) 模块未声明 DataScope 挂点则完全不过滤(当前仅 user 模块接线)。