Appearance
Web服务与业务开发
一、API 与 HTTP
API 是什么
- API 是系统对外提供的功能入口
- 前端或者其他服务可以通过 API 调用后端功能
- 不同的 API 一般对应不同的请求路径和业务功能
HTTP 是什么
- HTTP 是前后端之间常用的通信协议
- 它规定了请求和响应应该怎样传输
- 前端按照 HTTP 协议发送请求,后端处理后再返回 HTTP 响应
一次 HTTP 请求中通常有什么
- 请求方法:例如
GET、POST、PUT、DELETE - 请求路径:例如
/api/v1/chat/context - 请求参数:可能来自 body、query、path 或 form
- 请求头:可以携带 Token、数据格式等信息
- 请求体:用于携带 JSON、表单、文件或者其他数据
请求体是什么
- 请求体是 HTTP 请求中携带数据的部分
- JSON 是请求体的一种常见格式
- 请求体也可能是普通表单、文件上传或者二进制数据
二、请求参数的来源
body 参数
- 放在 HTTP 请求体中
- 常用于提交 JSON 或表单数据
- 后端需要把原始数据解析成能够处理的结构
query 参数
- 放在 URL 的
?后面
例如:
text
/users?page=1&size=10path 参数
- 直接放在请求路径中
例如:
text
/context/123这里的 123 就可能是一个会话 ID。
form 参数
- 通过表单格式提交的数据
- 可以同时传普通文本参数和上传文件
三、JSON 与 Go 结构体
JSON 和 Go struct 的关系
- JSON 是前端常用的数据格式
- struct 是后端用来接收和处理数据的结构体
- 前端发送 JSON 后,后端会按照字段关系把 JSON 解析进 struct
为什么需要结构体
- 结构体可以明确每个字段的名字和类型
- 可以把原始请求数据整理成后端能够稳定处理的数据
- 方便 API 层、Service 层和其他模块之间传递
请求对象是什么
- 用来承载这次请求参数的结构体
Req一般表示请求参数Resp一般表示返回结果
例如:
go
req := chat.DeleteContextReq{}表示:
- 创建一个空的
DeleteContextReq请求结构体 - 后续把这次删除会话需要的参数解析进去
前端 JSON 是怎样转换成 Go 结构体的
go
func DeleteContext(ctx *http.Context) (interface{}, error) {
req := chat.DeleteContextReq{}
if err := ctx.ParseBodyToStruct(&req); err != nil {
return nil, err
}
req.UserId = ctx.UserId()
return nil, chat.DeleteContext(&req)
}处理过程是:
req := chat.DeleteContextReq{}:创建一个空的请求结构体ctx.ParseBodyToStruct(&req):把请求体中的 JSON 解析进req- 传入
&req:让解析函数可以直接修改原来的结构体 req.UserId = ctx.UserId():补充可信的用户身份chat.DeleteContext(&req):把整理好的参数传给 Service 层
结构体作为数据流转载体
- 前端传来的 JSON、表单和文件属于原始请求数据
- API 层先对数据进行解析、校验和补充
- 再把需要继续处理的数据整理进结构体
- Service 层通过结构体获取这次业务需要的参数
四、HTTP Context
http.Context 是什么
- 这里的
http.Context是项目中对当前 HTTP 请求的封装 - 它保存并提供处理当前请求需要的信息
Context 里面通常有什么
- 当前请求对象
- 响应输出对象
- body、query、path、form 等请求参数
- 当前登录用户的信息
- 请求路径和请求头
- 向前端返回响应需要的方法
ctx *http.Context 是什么意思
ctx是当前 HTTP 请求的上下文变量*http.Context表示拿到的是这个请求上下文的地址- API 函数可以通过
ctx读取请求,也可以通过它返回响应
HTTP Context 和业务 Context 的区别
- HTTP Context:表示当前这一次 HTTP 请求
- 业务 Context:表示聊天会话、任务状态等业务数据
- 两者都叫 Context,但负责的内容不同
五、路由与 API 函数
路由是什么
- 路由负责把请求路径和 API 函数对应起来
- 写出一个 API 函数后,还需要注册路由,前端才能通过 URL 调用它
完整关系是:
text
前端请求 URL
→ 路由匹配
→ 进入对应的 API 函数
→ API 函数处理请求请求方法和路由的关系
- 同一个路径可以通过不同请求方法表示不同操作
GET:查询数据POST:创建或提交数据PUT:更新数据DELETE:删除数据
六、Swagger 接口文档
Swagger 注解是什么
- 用来描述接口并生成接口文档的规范注释
- 可以说明接口名称、参数、返回结果和请求路径
@Summary、@Param、@Router 是什么
- 都是 Swagger 使用的固定标签
- 标签本身有固定规范
- 标签后面的具体内容由项目开发者填写
常见作用:
@Summary:说明接口主要功能@Param:说明接口参数@Router:说明请求路径和请求方法
Swagger 和路由注册的区别
- Swagger 注解主要用于生成接口文档
- 真正决定 URL 调用哪个函数的,是项目中的路由注册代码
- Swagger 描述接口,Router 代码真正连接接口
七、API 层与 Service 层
API 层负责什么
- 接收 HTTP 请求
- 从 body、query、path 或 form 中解析参数
- 把参数转换成结构体或者对应的值
- 校验参数是否合法
- 补充用户身份、默认值等可信参数
- 调用 Service 层
- 返回业务数据或者错误
Service 层负责什么
- 执行真正的业务逻辑
- 根据参数判断业务应该怎样处理
- 调用数据库、缓存或者外部服务
- 组织最终的业务结果
- 把结果或者错误返回给 API 层
为什么要分成 API 层和 Service 层
- API 层负责处理请求入口
- Service 层负责处理业务
- 两层分开后,接口处理和业务逻辑不会全部混在一起
- 后续修改接口或者复用业务逻辑时会更方便
为什么 API 层看起来像在转发
- API 层一般不会承担复杂的核心业务
- 它主要负责为 Service 层准备正确、可信、格式统一的参数
- 然后调用 Service 层完成真正的业务处理
八、调用不同包中的函数
chat.SetStoryLine 是不是自己调用自己
- 不是
- 当前 API 函数和
chat.SetStoryLine属于不同的包 - 即使函数名字相同,只要包不同,就是两个不同的函数
例如:
go
func SetStoryLine(ctx *http.Context) (interface{}, error) {
var form chat.SetStoryLineReq
if err := ctx.ParseBodyToStruct(&form); err != nil {
return nil, err
}
form.UserId = ctx.UserId()
return nil, chat.SetStoryLine(&form)
}这里:
- 当前的
SetStoryLine是 API 层函数 chat.SetStoryLine(&form)是 Service 包中的业务函数- API 层准备参数后,再调用 Service 层完成设置故事线的业务
九、可信用户身份注入
用户 ID 是怎样注入的
go
req.UserId = ctx.UserId()表示:
- 从当前请求上下文中获取已经登录用户的真实 ID
- 再把这个 ID 写进请求结构体
- Service 层后续根据这个 ID 处理当前用户的数据
为什么 ctx.UserId() 是可信的
- 用户身份不是直接相信前端 JSON 中传来的值
- 一般由鉴权流程验证 Token 后得到
- 验证成功后,再把真实用户 ID 保存进当前 HTTP Context
完整过程可以理解成:
text
前端携带 Token
→ 鉴权中间件验证 Token
→ 得到真实用户身份
→ 把 UserId 写进 HTTP Context
→ API 通过 ctx.UserId() 获取
→ 写入请求结构体中间件是什么
- 中间件是在请求进入具体 API 函数之前统一执行的处理
- 常用于鉴权、日志、跨域和请求信息记录
- 多个 API 都需要的公共逻辑,可以放进中间件统一完成
十、参数校验与默认值
为什么需要参数校验
- 前端可能漏传参数
- 可能传入错误类型
- 文件可能为空或者过大
- 参数可能不符合业务要求
- 如果不校验,错误数据会继续进入 Service 层
默认值是什么
- 当前端没有传某个可选参数时,后端自动使用预设值
例如:
go
language := ctx.DefaultPostForm("language", "zh-CN")表示:
- 前端传了
language,就使用前端传入的值 - 前端没有传,就默认使用
zh-CN
API 层为什么要补充参数
- 前端只负责传用户能够决定的数据
- 用户 ID、服务器配置等可信信息由后端补充
- API 层把这些信息整理完整后,再传给 Service 层
十一、HTTP 状态与项目错误
ctx.BadRequest() 是什么
- 表示当前请求的参数或者格式不符合要求
- 一般对应 HTTP 状态码
400
项目里的错误对象
ErrorInvalidParams:请求参数不合法ErrorInternal:服务器内部处理失败
错误处理过程可以理解成:
text
发生错误
→ 返回项目中的错误对象
→ 上层统一处理错误
→ 按照规定格式返回给前端错误提前返回
- 参数解析、校验或者业务调用失败后,立即
return - 当前函数结束
- 后面的业务逻辑不会继续执行
- 防止错误数据继续向后传递
十二、一次请求的完整处理流程
text
客户端按照 HTTP 协议发送请求
→ 路由找到对应的 API 函数
→ 中间件完成鉴权等公共处理
→ API 从 body、query、path 或 form 中解析参数
→ 把参数转换成 Go 结构体
→ 校验参数并补充可信的 UserId、默认值等数据
→ 把整理好的结构体传给 Service 层
→ Service 执行真正的业务逻辑
→ 返回业务结果或者错误
→ 后端按照统一格式响应前端以删除会话为例:
text
前端发送删除会话请求
→ 路由进入 DeleteContext
→ JSON 被解析成 DeleteContextReq
→ 从 ctx 中获取可信的 UserId
→ 调用 Service 层的 DeleteContext
→ Service 判断会话是否属于当前用户
→ 执行删除操作
→ 返回成功结果或者错误