Skip to content

控制器

控制器负责接收 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+)"]      // 错误,编译期报正则语法错误

基于 Apache-2.0 许可证发布