公司当前项目使用的是Apifox作为API调试/文档工具,所以以这个为例。市面上的工具应该都逐步开放了AI能力,需要关注一下,只要能提供原始数据就行,不能的话那也没办法喂给AI。
上一篇已经让AI写UI了,这一篇让AI写接口请求。首先思考一下可能碰到的问题。
一、可能遇到的问题
默认网络请求用的retrofit,其它也是一通百通。
1、问题一:适配请求头
项目中网络请求的拦截器已经定义了请求头,读取的原始数据如何关联?答案就是在skill中告诉AI,并让它模仿你的代码写。
2、问题二:适配请求参数中有一些公共部分
项目中网络请求的拦截器已经定义了一些公共请求参数,读取的原始数据如何关联?例如:
{
"app": {
"locale": "{{locale}}",
"tid": "{{terminal_id}}",
"platform": "{{platform}}"
},
"data": {
//每个接口只有这里有差异
}
}
只有data块中有差异。答案其实同上,skill中告诉AI,并让它模仿你的代码。
3、问题三:返回数据脱壳
例如项目中的壳是这样:
data class ApiResponse<T>(
val code: Int,
val msg: String,
val data: T
) : BaseResponse<T>() {...}
那我们需要的解析的Data Class可能只需要脱壳的部分,如何让AI知道?答案也同上,skill中告诉AI,并让它模仿你的代码。
4、问题四:viewmodel中网络请求如何写
道理同上。
5、问题五:代码放在哪个文件
这个问题的解答要看情况了,我们先看下面只给代码的示例,最后再探讨一下这个问题。
二、获取接口原始数据
这一步依赖MCP的开放能力,后端兄弟接口怎么定义的,字段怎么写的,Android如何封装Data Class都依赖这些原始数据。接下来看通过Apifox如何办到的。
Apifox的MCP现阶段还不能集成到Codex中使用,启动的时候会报错:
⚠ MCP client for `apifox_api_docs` failed to start: MCP startup failed: handshaking with MCP server failed: connection closed: initialize response
⚠ MCP startup incomplete (failed: apifox_api_docs)
原因是Apifox的MCP返回不符合Codex定义的规范。
那换其他方式,打开Apifox终端(当前V版本2.7.58 (2.7.58)),选择需要生成代码的接口:
如上操作,可以得到复制的一段信息如下:
请访问以下链接获取接口“天气信息V3”的接口定义信息:https://api.apifox.cn/temp-links/api/%E5%A4%A9%E6%B0%6%81%AFv3-253061517?t=e0be3ab5-e8bf-4ec2-be81-f1489
这样我们就拿到了接口的原始数据链接。
三、如何编辑skills
这一步跟项目强关联,因为每个项目写法都不一样,文件不一样,所以skill的写法因项目而异。
先自己手动写一份粗糙的,然后给AI帮你改一改。下面贴一份修改后的skill:
---
name: api_to_request
description: 请访问用户提供的接口文档链接,解析请求/响应结构,并按项目约定生成:Retrofit 接口、@Body(仅 data 节点)、剥壳后的 Response data class、以及优化后的 ViewModel 调用代码(全部用 Markdown 代码块输出)。
---
访问链接生成 Retrofit / Request / Response / ViewModel 代码
何时使用
当用户提供一个接口定义链接(Swagger/Apifox/Postman 文档/自研文档页面等),并要求按项目既定规范生成 Android(Kotlin) 网络层代码时使用。
输入
• 必须:接口文档链接(包含请求方式、path、请求体、返回体说明)
• 可选:项目约束/示例(已有 api service、已有 response/request 命名、字段命名风格、是否可空、是否用 @SerializedName 等)
⸻
输出(必须全部提供,且全部用 Markdown 代码块输出)
1. 代码一:Retrofit 接口定义(放入 java/api 对应 service)
2. 代码二:请求 @Body(仅 data 节点)(Request data class 或 Map 形态)
3. 代码三:剥壳后的 Response data class(放入 java/data)
4. 代码四:ViewModel 中实际请求代码(优化版)(基于现有 requestWithHttpException 机制)
5. 代码五:View 层 StateFlow 收集示例(统一回调)
如需说明,仅在代码块外给简短要点,不要发散。
⸻
核心约定(严格遵守)
A. Retrofit 接口风格
• 按接口文档请求类型使用:@GET/@POST/@PUT/@DELETE/...
• 路径按文档给出,例如:@POST("/v1/iot/camera/discoverEvents")
• 返回统一:ApiResponse<T?>
• 必须写 KDoc 注释,说明「做什么」
• 形参规则:
• 若接口使用 body:@Body request: XxxRequest 或 @Body params: Map<String, Any?>
• 若 data 节点为空:不要要求调用方传参
• 优先:suspend fun xxx(): ApiResponse<Resp?>
• 仅当必须显式 body(后端/网关要求)时:@Body params: Map<String, Any?> = emptyMap()
B. 请求体只给 data 节点
项目通过拦截器 AddPublicParamsInterceptor.kt 统一注入公共字段(例如 app.locale/tid/alias/platform 等),因此:
• @Body 只包含 data 的内容
• 不要在 Request 中声明公共字段(如 app/tid 等)
• 若 data 为空,调用方不传参(或由默认 {} 兜底)
C. Response 必须“剥壳后”
网络返回会被 ApiResponse.kt 统一剥壳,因此输出的 Response 类只描述真正业务数据结构:
• 输出为 data class XxxResponse(...)
• 字段名遵循服务端返回字段(通常 snake_case)
• 是否用 @SerializedName:按项目既有写法决定
• 可空性:以文档标注为准;没标注时宁可保守为可空
• 嵌套结构需拆分成内部 data class(或独立 data class,按项目习惯)
D. ViewModel 请求代码要更“干净”
关注`BaseViewModelApiResult.kt`类,里面包含示例:
• requestWithHttpException(...) 获取结果,返回值使用when 分支解析
• 参数构造清晰(优先 buildMap {} 或 request data class)
• 方法命名与注释统一清晰
E. View层使用StateFlow回调
• 注意使用View层的协程
⸻
严格流程(每次都按此执行)
1. 打开链接并提取接口定义
• 请求方式、path
• query/path/header/body 结构(如有)
• data 节点字段清单:类型、必填/可选、枚举/限制(如有)
• 返回体结构:识别 wrapper,并提取业务 data(用于“剥壳后”Response)
2. 生成代码一:Retrofit 接口定义
• HTTP 方法与 path 必须与文档一致
• 返回类型固定 ApiResponse<T?>
3. 生成代码二:@Body(仅 data)
• 字段少且动态:给 buildMap { put(...) } 示例
• 字段多/有嵌套:建 XxxRequest data class(只含 data 字段)
• data 为空:不要求调用方传参(或默认 {})
4. 生成代码三:Response data class(剥壳后)
• 仅业务字段,按返回体 data 的结构拆分嵌套类
5. 生成代码四:ViewModel 调用(优化版)
• 统一用私有封装减少重复
• StateFlow 输出统一 UiState
6. 生成代码五:View 层收集示例
• 使用 repeatOnLifecycle + collect
7. 最终输出格式固定
• 依次输出:代码一/二/三/四/五
• 所有代码必须放在 Markdown 代码块中
⸻
推荐写法模板(用于生成 ViewModel 优化版)
private val _deviceState = MutableStateFlow<UiState<TripHistoryResponse?>>(UiState.Idle)
val deviceState: StateFlow<UiState<TripHistoryResponse?>> = _deviceState.asStateFlow()
fun discoverTrips() {
viewModelScope.launch {
_deviceState.value = UiState.Loading
val response = requestWithHttpException<TripHistoryResponse?, Any?> { cameraDeviceApiService.discoverTrips(tripsRequest) }
when (response) {
is ApiResult.Success -> {
_deviceState.value = UiState.Success(response.data)
}
is ApiResult.Fail -> {
_deviceState.value = UiState.Fail(code = response.body.code, msg = response.body.msg)
}
is ApiResult.Error -> {
_deviceState.value = UiState.Error(errorCode = response.e.errCode, errorMsg = response.e.errorMsg)
}
}
}
}
View 层模板(输出时作为“代码五”)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.deviceState.collect { state ->
when (state) {
UiState.Idle -> Unit
UiState.Loading -> {
// show loading
}
is UiState.Success -> {
// TODO handle success
}
is UiState.Fail -> {
// TODO handle fail
}
is UiState.Error -> {
// TODO handle error
}
}
}
}
}
⸻
你输出时的固定结构(必须遵守)
代码一:Retrofit 接口定义
// 放在对应 ApiService 中
代码二:@Body(仅 data)
// XxxRequest data class 或 buildMap 示例(二选一或都给,看接口复杂度)
代码三:Response data class(剥壳后)
// 放在 java/data 下,参考项目现有 response 写法
代码四:ViewModel 请求代码(优化版)
// 给出可直接粘贴的 ViewModel 方法(包含必要的私有封装 + StateFlow)
代码五:View 层 StateFlow 收集示例
// repeatOnLifecycle + collect 模板
⸻
质量检查清单(输出前自检)
• Retrofit 注解的 HTTP 方法与 path 与文档一致
• @Body 只包含 data 节点;data 为空时不强迫传参(除非必须显式 body)
• Response 已剥壳:不再包含 ApiResponse 外层结构
• ViewModel 已减少重复样板,参数构造清晰,回调透传简洁一致
• 所有代码均在 Markdown 代码块中输出
Apifox复制链接的内容因为匹配skill的description,所以给出链接就可以触发上面的skill。Codex生成的代码如下(剔除了敏感信息):
代码一:Retrofit 接口定义
// 放在对应 ApiService 中
import com.foxxusa.beaver.deps.network_use.ApiResponse
import retrofit2.http.Body
import retrofit2.http.POST
interface HomeManagementApiService {
/**
* 获取天气信息V3
*/
@POST("/v1/iot/weather/getCurrentInfo")
suspend fun getWeatherInfoV3(
@Body request: WeatherInfoV3Request
): ApiResponse<WeatherInfoV3Response?>
}
代码二:@Body(仅 data)
// XxxRequest data class
data class WeatherInfoV3Request(
val lon: Double,
val lat: Double,
val unit: WeatherUnit = WeatherUnit.IMPERIAL
)
enum class WeatherUnit {
METRIC,
IMPERIAL
}
代码三:Response data class(剥壳后)
// 放在 java/data 下,参考项目现有 response 写法
data class WeatherInfoV3Response(
val day: Boolean,
val weather: WeatherV3,
val temperature: Double,
val wind: Double,
val pressure: Double,
val humidity: Double,
val aqi: String
)
data class WeatherV3(
val condition: String,
val icon: String? = null
)
代码四:ViewModel 请求代码(优化版)
private val homeManagementApiService = MainNetworkApi.homeManagementApiService
private val _weatherInfoState =
MutableStateFlow<UiState<WeatherInfoV3Response?>>(UiState.Idle)
val weatherInfoState: StateFlow<UiState<WeatherInfoV3Response?>> =
_weatherInfoState.asStateFlow()
fun fetchWeatherInfoV3(lon: Double, lat: Double, unit: WeatherUnit = WeatherUnit.IMPERIAL) {
viewModelScope.launch {
_weatherInfoState.value = UiState.Loading
val request = WeatherInfoV3Request(lon = lon, lat = lat, unit = unit)
val result = requestWithHttpException<WeatherInfoV3Response?, Any?> {
homeManagementApiService.getWeatherInfoV3(request)
}
when (result) {
is ApiResult.Success -> {
_weatherInfoState.value = UiState.Success(result.data)
}
is ApiResult.Fail -> {
_weatherInfoState.value =
UiState.Fail(code = result.body.code, msg = result.body.msg)
}
is ApiResult.Error -> {
_weatherInfoState.value =
UiState.Error(errorCode = result.e.errCode, errorMsg = result.e.errorMsg)
}
}
}
}
代码五:View 层 StateFlow 收集示例
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.weatherInfoState.collect { state ->
when (state) {
UiState.Idle -> Unit
UiState.Loading -> {
// show loading
}
is UiState.Success -> {
// TODO handle success
}
is UiState.Fail -> {
// TODO handle fail
}
is UiState.Error -> {
// TODO handle error
}
}
}
}
}
生成的代码可用度极高,很大程度上解放了生产力。
这个是单个网络请求,还可以编写分页网络请求、并发网络请求的skill等。
四、AI的放权
AI生成的代码可用度这么高,那是不是应该放权直接让AI写文件呢?
1、如果是一个新项目,定义好项目架构后就大胆的让AI生成文件自己写代码吧,能”摸鱼"就别动手,人生苦短我用AI。
2、如果是一个成熟度高的项目,建议以小颗粒度的形式让AI生成或修改文件代码,这样影响的范围可控,git提交的时候请仔细审查AI生成的代码。
3、如果是一个成熟度高的项目,修改的需求复杂,牵扯面广,或者公司规定不让AI写代码只让辅助参考,那还是开只读模式自己写吧。
回到最上面的问题五:代码放哪个文件?
答案就简单了。
- 项目架构清晰,让AI干活,可以完全让AI自主决策,后续自己调整。
- 可以在skill中添加提示加以引导,让AI放哪个文件/目录。
- 只读模式,自己处理AI生成的代码,前期可以开只读熟悉AI的使用,等熟练后就可以让AI自己写文件了。