195 lines
11 KiB
Plaintext
195 lines
11 KiB
Plaintext
================================================================================
|
||
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 模块接线)。
|