移动/游戏
用 Appwrite Kotlin SDK 构建…
在 Kotlin/Android 或 Kotlin JVM 项目中使用 Appwrite SDK 实现客户端认证、数据库 CRUD、文件存储、实时订阅以及服务端 API Key 管理。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
用 Appwrite Kotlin SD特定
解决思路
- 安装依赖:Android 端在 build.gradle.kts 中添加 implementation("io.appwrite:sdk-for-android:+");Kotlin JVM 服务端添加 implementation("io.appwrite:sdk-for-kotlin:+")。
- 客户端初始化(Android):使用 Client(context).setEndpoint("https://<REGION>.cloud.appwrite.io/v1").setProject("[PROJECT_ID]")。
- 服务端初始化(Kotlin JVM):使用 Client().setEndpoint(...).setProject(System.getenv("APPWRITE_PROJECT_ID")).setKey(System.getenv("APPWRITE_API_KEY"))。
- 客户端认证:用 Account(client) 调用 create(userId = ID.unique(), email, password, name) 注册;createEmailPasswordSession(email, password) 登录;createOAuth2Session(activity = activity, provider = OAuthProvider.GOOGLE) 做 OAuth;account.get() 获取当前用户;account.deleteSession(sessionId = "current") 登出。
- 服务端用户管理:用 Users(client) 调用 create(userId = ID.unique(), email, password, name)、list()、get(userId = "[USER_ID]")、delete(userId = "[USER_ID]")。
- 数据库操作统一使用 TablesDB(不要用已废弃的 Databases 类,除非现有代码依赖或用户明确要求),并优先使用命名参数。
- 创建数据库(仅服务端):tablesDB.create(databaseId = ID.unique(), name = "My Database")。
- 创建行:tablesDB.createRow(databaseId, tableId, rowId = ID.unique(), data = mapOf(...));查询行:tablesDB.listRows(databaseId, tableId, queries = listOf(Query.equal(...), Query.limit(10)));获取行:tablesDB.getRow(...);更新行:tablesDB.updateRow(..., data = mapOf(...));删除行:tablesDB.deleteRow(...)。
- 字符串列类型:不要用已废弃的 string 类型,改用显式类型。varchar 最大 16,383 字符、size ≤ 768 时可全索引、内联存储并计入 64 KB 行大小限制,适合短且需索引的字段;text 最大 16,383 字符、仅前缀索引、off-page 存储;mediumtext 最大 4,194,303 字符、仅前缀索引、off-page;longtext 最大 1,073,741,823 字符、仅前缀索引、off-page。text/mediumtext/longtext 不需要 size 参数。
- 创建表时用 columns 列表指定列类型,例如 mapOf("key" to "title", "type" to "varchar", "size" to 255, "required" to true)。
- 查询方法:Query.equal、notEqual、lessThan、lessThanEqual、greaterThan、greaterThanEqual、between、isNull、isNotNull、startsWith、endsWith、contains、search(全文搜索需索引)。
- 排序与分页:Query.orderAsc、orderDesc;Query.limit(25)(默认 25,最大 100)、Query.offset(0)、Query.cursorAfter("[ROW_ID]")(优先使用游标分页)、Query.cursorBefore。
- 选择与逻辑:Query.select(listOf(...))、Query.or(listOf(...))、Query.and(listOf(...))(默认 AND)。
- 文件存储:用 Storage(client) 调用 createFile(bucketId, fileId = ID.unique(), file = InputFile.fromPath("/path/to/file.png"))、getFilePreview(bucketId, fileId, width = 300, height = 300)、listFiles(bucketId)、deleteFile(bucketId, fileId)。
- InputFile 工厂方法:InputFile.fromPath("/path/to/file.png") 从文件系统读取;InputFile.fromBytes(byteArray, "file.png") 从 ByteArray 读取。
- Teams:用 Teams(client) 调用 create(teamId = ID.unique(), name = "Engineering")、list()、createMembership(teamId, roles = listOf("editor"), email)、listMemberships(teamId)、updateMembership(teamId, membershipId, roles = listOf("admin"))、delete(teamId)。权限中用 Role.team("[TEAM_ID]") 表示所有成员,Role.team("[TEAM_ID]", "editor") 表示特定角色。
- 实时订阅(客户端):用 Realtime(client).subscribe(Channel.tablesdb("[DATABASE_ID]").table("[TABLE_ID]").row()) { response -> ... } 订阅行变化;可传入多个 Channel 订阅多个频道;用 subscription.close() 清理。
- 可用频道:account;tablesdb.[DB_ID].tables.[TABLE_ID].rows;tablesdb.[DB_ID].tables.[TABLE_ID].rows.[ROW_ID];buckets.[BUCKET_ID].files;buckets.[BUCKET_ID].files.[FILE_ID];teams;teams.[TEAM_ID];memberships;functions.[FUNCTION_ID].executions。响应字段包括 events、payload、channels、timestamp(ISO 8601)。
- 服务端函数:用 Functions(client) 调用 createExecution(functionId, body = """{"key": "value"}""")、listExecutions(functionId)。
- 编写 Kotlin 函数入口:fun main(context: RuntimeContext): RuntimeOutput,从 context.req 读取 body、bodyJson、headers、method、path、query;用 context.log 记录日志;用 context.res.json(...)、context.res.text(...)、context.res.empty()、context.res.redirect(...) 返回响应。
- SSR 认证需要两个客户端:Admin client 用 API Key、可复用单例、创建会话并绕过速率限制;Session client 用会话 cookie、每请求创建、绝不共享。
- SSR 邮箱密码登录(Ktor):用 Account(adminClient).createEmailPasswordSession(email, password) 创建会话,并把 session.secret 写入名为 a_session_<PROJECT_ID> 的 cookie,设置 httpOnly = true、secure = true、SameSite=Strict、path = "/"。
- SSR 认证请求:从 cookie 读取 a_session_[PROJECT_ID],用 Client().setEndpoint(...).setProject(...).setSession(session) 创建 sessionClient,再用 Account(sessionClient).get() 获取用户。
- SSR OAuth2 流程:第一步用 Account(adminClient).createOAuth2Token(provider = OAuthProvider.GITHUB, success = "...", failure = "...") 获取重定向 URL 并跳转;第二步在回调中用 account.createSession(userId = call.parameters["userId"]!!, secret = call.parameters["secret"]!!) 换取会话并写入 cookie。
- Cookie 安全:始终使用 httpOnly、secure、SameSite=Strict 防止 XSS;cookie 名必须是 a_session_<PROJECT_ID>。
- 转发用户代理:调用 sessionClient.setForwardedUserAgent(call.request.headers["User-Agent"]) 记录终端用户浏览器信息,便于调试与安全审计。
- 错误处理:捕获 io.appwrite.AppwriteException,读取 e.message、e.code(HTTP 状态码)、e.type(如 document_not_found)、e.response(完整响应体)。常见错误码:401 未授权、403 禁止、404 未找到、409 冲突、429 限流。
- 权限与角色:权限是字符串数组,用 Permission 和 Role 辅助类构建。默认无任何用户可访问,必须在行/文件级显式设置或从表/桶级继承。
- 行级权限示例:permissions = listOf(Permission.read(Role.user("[USER_ID]")), Permission.update(Role.user("[USER_ID]")), Permission.read(Role.team("[TEAM_ID]")), Permission.read(Role.any()))。
- 文件权限示例:permissions = listOf(Permission.read(Role.any()), Permission.update(Role.user("[USER_ID]")), Permission.delete(Role.user("[USER_ID"])))。
- 权限设置时机:需要按资源做访问控制时设置行/文件级权限;若表中所有行规则相同,则在表/桶级配置权限并让行权限留空。
- 避免常见权限错误:忘记设置权限会导致资源对所有人(包括创建者)不可访问;对 write/update/delete 使用 Role.any() 会允许包括未认证访客在内的任何人修改或删除;对敏感数据使用 Permission.read(Role.any()) 会使其公开可读。
适用边界 · 注意事项
- 不使用 Appwrite 作为后端服务时(本技能依赖 Appwrite 云服务或自托管实例)。
- 项目不是 Kotlin/Android 或 Kotlin JVM 技术栈时。
- 仅需纯前端静态页面、不涉及 Appwrite SDK 认证/数据库/存储/实时/函数能力时。