控制器
控制器负责接收 HTTP 请求、调用服务层逻辑,并将结果写回响应。ACE 使用编译期宏 @Controller 标注类,@Get/@Post 等标注方法,在构建阶段生成路由注册代码——无运行时反射开销。
基本用法
cangjie
package task_api.controller
import ace_framework.*
import task_api.service.TaskService
@Controller["/tasks"]
public class TaskController {
@Inject var svc: TaskService
@Get["/"]
public func list(): Array<TaskView> {
svc.listAll()
}
@Get["/:id"]
public func get(@PathParam id: Int64): TaskView {
svc.get(id) ?? throw NotFoundException("Task ${id} not found")
}
@Post["/"]
public func create(@Body req: CreateTaskReq): TaskView {
svc.create(req.title, req.priority, req.owner)
}
@Put["/:id"]
public func update(@PathParam id: Int64, @Body req: UpdateTaskReq): TaskView {
svc.update(id, req)
}
@Delete["/:id"]
public func delete(@PathParam id: Int64): Unit {
svc.delete(id)
}
}@Controller["/tasks"] 设置该类下所有路由的统一前缀。方法返回值会被自动处理:
| 返回类型 | 行为 |
|---|---|
String | 直接作为响应体(Content-Type: text/plain) |
结构体 / class | 自动 JSON 序列化(Content-Type: application/json) |
Array<T> / ArrayList<T> | 同上,序列化为 JSON 数组 |
Unit | 不写响应体,由方法内部通过 ctx 手动写入 |
HTTP 方法装饰器
| 装饰器 | HTTP 方法 | 示例路径 |
|---|---|---|
@Get[path] | GET | @Get["/users/:id"] |
@Post[path] | POST | @Post["/users"] |
@Put[path] | PUT | @Put["/users/:id"] |
@Delete[path] | DELETE | @Delete["/users/:id"] |
@Patch[path] | PATCH | @Patch["/users/:id/status"] |
路径支持 :name(命名参数)、:name?(可选)、:name+(一段或多段)、*name(通配)以及内联正则 :id([0-9]+)。
参数绑定装饰器
路径参数 @PathParam
cangjie
@Get["/:category/:id([0-9]+)"]
public func detail(
@PathParam category: String,
@PathParam id: Int64
): ProductView { ... }查询参数 @Query
cangjie
@Get["/search"]
public func search(
@Query["keyword"] keyword: String, // 可选,无值时为空字符串
@Query["page!"] page: Int64, // ! 表示必填,缺失时 400
@Query["size"] size: Int64
): PageResult<TaskView> { ... }@Query["key!"] 中的 ! 后缀表示该参数必填;缺失时框架自动返回 400 Bad Request。
表单参数 @Param
cangjie
@Post["/login"]
public func login(
@Param["username"] user: String,
@Param["password"] pwd: String
): TokenResp { ... }请求体 @Body
cangjie
@Post["/"]
public func create(@Body req: CreateTaskReq): TaskView { ... }@Body 会将请求体 JSON 反序列化为指定类型。目标类型需实现 Deserializable(@Serializable 宏自动生成)。
文件上传 @File / @Files
cangjie
@Post["/upload"]
public func upload(
@File["avatar!"] avatar: UploadFile,
@Files["attachments"] files: Array<UploadFile>
): UploadResult { ... }@File["field!"] 对应单文件必填字段;@Files["field"] 对应多文件字段。
访问原始 Context
当需要手动操控响应头、状态码或流式写入时,可在方法参数中声明 ctx: Context:
cangjie
@Get["/export"]
public func export(ctx: Context): Unit {
ctx.setHeader("Content-Disposition", "attachment; filename=tasks.csv")
ctx.status = 200
ctx.body = svc.exportCsv()
}也可通过字段注入获取当前请求 Context(请求作用域活属性):
cangjie
@Controller["/me"]
public class MeController {
@Inject var ctx: Context // 每次请求自动刷新
@Get["/"]
public func whoami(): String {
ctx.getHeader("X-User-Id") ?? "anonymous"
}
}完整 CRUD 示例
cangjie
package task_api.controller
import ace_framework.*
import task_api.dto.*
import task_api.service.TaskService
@Controller["/api/tasks"]
public class TaskController {
@Inject var svc: TaskService
@Get["/"]
public func list(
@Query["page"] page: Int64,
@Query["size"] size: Int64
): PageResult<TaskView> {
svc.page(page, size)
}
@Get["/:id([0-9]+)"]
public func get(@PathParam id: Int64): TaskView {
svc.get(id) ?? throw NotFoundException("task not found")
}
@Post["/"]
public func create(@Body req: CreateTaskReq): TaskView {
svc.create(req.title, req.priority, req.owner)
}
@Patch["/:id/status"]
public func updateStatus(
@PathParam id: Int64,
@Query["status!"] status: String
): TaskView {
svc.updateStatus(id, status)
}
@Delete["/:id"]
public func delete(@PathParam id: Int64, ctx: Context): Unit {
svc.delete(id)
ctx.status = 204
}
}单例注意事项
控制器类是 Singleton Bean,整个应用生命周期只创建一个实例。 不要在控制器字段中存储请求级状态(如当前用户、请求 ID 等)。 请求级数据应通过 ctx.state 传递,或使用 @Service["request"] 请求作用域 Bean。
路径参数内联正则
仓颉 std.regex 使用 POSIX 语法,不支持 \d。路径参数内联正则请写 [0-9] 而非 \d:
cangjie
@Get["/:id([0-9]+)"] // 正确
@Get["/:id(\d+)"] // 错误,编译期报正则语法错误