这是一个系列 Blog作者将以一个 PHP 全栈工程师的身份利用 AI 工具claude code、codex、deepseek、豆包等从零开始学习 golang 语言并最终完成 ai-go-mallgithub | gitee开源项目的制作全程记录分享。在上一期我们已经完成 “注册数据库中间件、初始化模型结构和自动迁移”本期将完成泛型基服务、控制器、仓储实现自动发现和注册业务路由泛型基服务、控制器、仓储实现由于对大型 golang 项目基类还不熟悉直接同时让各大 AI给我一套 golang WEB 项目的 Handler → Service → Repository → Model 的完整示例代码。拿到示例代码后作者逐行做了仔细阅读并又问了一些问题加粗字体为结论以下问题只是作者在阅读代码时对不理解的地方提出的疑问读者可根据自己的实际情况提问和研究为什么 admin_repo.goadmin_service.go 都有后缀而 model 不需要叫做 admin_model.go 呢因为不加后缀结构体重名、无法使用NewAdminHandler、NewAdminService、NewAdminRepository 是 golang 标准写法吗是的golang 官方也这么写包名已经含有 admin 了NewAdminRepository中是否可以省略 Admin可以省略service 和 repository 层额外定义 interface不直接使用结构体以解耦、方便测试、方便扩展等理解了豆包、文心一言提供的示例代码之后我开始让 cc 直接在项目中写一套基础的代码出来写一个通用的 CRUD 实现通用文件于 Handler → Service → Repository 对应目录建立均以 base.go 命名service 和 repository 层额外定义 interface不直接使用结构体实现 Create、List、Edit、Delete 四个方法其中 Edit 分为 GET 和 POST 两种请求方式GET 为获取被修改的行数据POST 为修改数据只使用 GET 和 POST 两种请求方式不使用 PUT 和 DELETE 等请求方式除 GET/POST 外的请求方式在国内的 CDN/全站加速 兼容极差# 大模型是概率生成不是固定查表再加上采样策略、上下文、版本等影响同问题输出都会有差异以下 AI 输出、包括生成的代码仅供参考可直接忽略继续向下查看如何寻找问题和完成整改全部完成三个文件已创建并通过编译验证。以下是实现总结 --- 新建文件1. internal/repository/base.go — 数据访问层 ┌──────┬────────────────────────────────────────────────────────────────────────────────────────────────────┐ │ 项目 │ 内容 │ ├──────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ 接口 │ CrudRepository[T any]│ ├──────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ 实现 │ genericCrudRepository[T any]│ ├──────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ 方法 │ Create、GetByID、List、Update、Delete │ ├──────┼────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ 特点 │ List 使用 GORM Scopes 灵活组合筛选条件Update 支持 map[string]any 部分更新Delete 自动适配软删除 │ └──────┴────────────────────────────────────────────────────────────────────────────────────────────────────┘2. internal/service/base.go — 业务逻辑层 ┌──────┬─────────────────────────────────────────────────────────────────────┐ │ 项目 │ 内容 │ ├──────┼─────────────────────────────────────────────────────────────────────┤ │ 接口 │ CrudService[T any]│ ├──────┼─────────────────────────────────────────────────────────────────────┤ │ 实现 │ genericCrudService[T any]│ ├──────┼─────────────────────────────────────────────────────────────────────┤ │ 方法 │ Create、GetByID、List、Update、Delete │ ├──────┼─────────────────────────────────────────────────────────────────────┤ │ 特点 │ 基础实现直接委托 Repository子类可覆写添加业务校验如权限、去重 │ └──────┴─────────────────────────────────────────────────────────────────────┘3. internal/handler/base.go — 控制器层 ┌───────────────────────────┬──────────┬──────────────────────────────────┐ │ 路由 │ 方法 │ 说明 │ ├───────────────────────────┼──────────┼──────────────────────────────────┤ │ POST /{prefix}/create │ Create │ 新增记录 │ ├───────────────────────────┼──────────┼──────────────────────────────────┤ │ GET /{prefix}/list │ List │ 分页列表?page1page_size10 │ ├───────────────────────────┼──────────┼──────────────────────────────────┤ │ GET /{prefix}/:id/edit │ EditGet │ 获取被编辑的行数据 │ ├───────────────────────────┼──────────┼──────────────────────────────────┤ │ POST /{prefix}/:id/edit │ EditPost │ 执行修改 │ ├───────────────────────────┼──────────┼──────────────────────────────────┤ │ POST /{prefix}/:id/delete │ Delete │ 删除记录 │ └───────────────────────────┴──────────┴──────────────────────────────────┘ 统一响应格式{code:0/-1,message:...,data:{...}}后续使用方式示例 // 在 cmd/api/main.go 中初始化 userRepo :repository.NewCrudRepository[model.User](database.GetDB())userSvc :service.NewCrudService[model.User](userRepo)userHandler :handler.NewCrudHandler[model.User](userSvc)userHandler.RegisterRoutes(r.Group(/user))即可一键获得 /user 下的完整 CRUD 路由。 ✻ Crunchedfor5m 24s首先检查目录结构的变更是否合理有问题就先要求修改目录结构没问题之后才开始对代码reviewAI 按要求创建了三个 base.go 文件并没有创建多余的文件。AI 于repository/base.go建立了多个数据操作方法分别是Delete、List、Create、GetByID其中的 GetByID 方法用于根据主键 ID 查询单条记录其命名规范算是 Go 社区惯例但其余方法都是单个单词所以这里特意将GetByID改名为了Row干净利落但此时单行是Row多行的List当然应该改为Rows相当于和 database/sql 风格保持一致。此时Rows方法是支持分页的传递了 page 和 page_size 等参数而实际的项目中这里还得组装 whereorder 等参数AI 写的并不齐全暂时干脆将 page 、page_size 也取消掉保持基本实现即可后续结合实际项目需求再统一整理列表数据的查询方法。AI 使用了 GORMTraditional API的语法这里要求它改写为更新的Generics API语法。interface 和 struct 命名问题AI 使用了genericCrudRepository、CrudRepository等名称基础的 CRUD 实现是很方便扩展的假设扩展了还叫 xxCrud 就不合适而且作为基础接口和结构体当然是越简短越好而且这些定义本身就在base.go文件内直接改为Repository(struct)和IRepository(interface)等干净利落。控制器中 List 和 Edit 都被分为了两个处理方法特别是 Edit以 GET 和 POST 区分获取和编辑一般需要又单独注册路由所以干脆同 repo 层一样以 Row 获取以 Update 更新即可。统一响应结构和函数单独要求 cc 修改提出更多具体的要求AI 将 Response统一响应的结构体和方法放在了 handler/base.go 中这是跨 handler 共享的 API 契约比如错误处理中间件需要拆分出来并且加上 Time 字段让前端方便的做缓存、去重、排序等。AI 定义了多个响应函数Success(200)、BadRequest400、NotFound404、InternalError500等它们使用不同的 HTTP 状态码这种方案又与 CDN/全站加速 不兼容它们对非 200 的状态码处理完全是一团乱麻所以这里改为统一响应 200然后以 code 字段体现业务状态直接删除了BadRequest400、NotFound404函数InternalError暂留增加了与Success对应的Fail快捷响应函数统一使用 200 ok 状态码code 则默认为 1统一响应方法支持同时支持链式调用和函数式选项Functional Options两种调用方法链式调用response.New(c).Code(1).Message(测试).Send()函数式选项response.Fail/Success(c, response.WithMessage(参数错误), response.WithCode(1), response.WithData(test))函数式选项时后三个参数都可以省略将以上整理出来的发给 AI让其重新生成根据使用模型的不同可能还需要对话几轮最终生成的代码已开源自动发现和注册业务路由路由系统设计路由定义全部放入 internal/router 目录实现子级目录路由定义的自动发现init 空白导入的方案建立专门的 internal\router\registry\registry.go 文件来放置通用的 registry 函数以及用来空白导入子目录路由文件触发 init预设 /admin、/user、/ 三组示例路由里边注册一个测试用的 /ping、/admin/ping、/user/ping 路由即可其中 /ping 的路由注册放在 internal/router/index.go 中无需建立子目录internal/router/router.go 实现 Setup 函数后续于 main.go 内调用它即可完成全部业务路由的注册Setup 中不要创建新的 r.Group路由应该注册到那个分组由各个子路由文件中的 init 决定这里 AI 告诉我我的提示词出了一点问题internal\router\registry\registry.go里边放置空白导入的代码会造成循环导入因为空白导入的包内使用了registry函数遂将方案改为于最外层的router.go中完成空白导入。全部完成编译通过 ✅。最终结构 internal/router/ ├── router.go# Setup() 空白导入自动发现├── registry/ │ └── registry.go# Register(group, fn) Setup(engine)├── admin/ │ └── admin.go# GET /admin/ping → admin pong├── user/ │ └── user.go# GET /user/ping → user pong└── index.go# GET /ping → index pong以后新增路由模块只需两步新建子目录并 init()调 registry.Register()然后在 router.go 加一行 _ 导入即可。 ✻ Cookedfor3m 46s说几句题外话发给 AI 的提示中AI 于 xxx 建立了错误的文件这里要求它改为 xxx这种第三人称的视角也是能直接用的它能看懂包括错别字少量的模糊不清都没关系。语言初学者写提示词时往往会不确定自己对不对可以直接让 AI 确定是否合理如果合理则立即实现否则取消并给出解释。至此项目架构与地基已经完成后续主要是业务面的东西设计数据表登录注册CRUD 啥的那些作者就很熟悉了历时不到两个礼拜终于回到了以往的 “舒适圈”。今日产生的完整代码已经开源于github | gitee