docs(project): 更新需求与开发进度对比报告并完善Android SDK接口定义

- 添加了完整的XuqmGroup平台需求与开发进度对比报告
- 实现了Android SDK的ImApi接口定义,涵盖群组、好友、黑名单等完整功能
- 定义了IM消息、会话、群组、用户资料等核心数据模型
- 实现了Android SDK的ImSDK核心功能类,包括连接管理和消息处理
这个提交包含在:
XuqmGroup 2026-05-02 12:30:31 +08:00
父节点 20fc57ac97
当前提交 cfd0382ba2
共有 3 个文件被更改,包括 165 次插入10 次删除

查看文件

@ -4,18 +4,22 @@ import com.xuqm.sdk.XuqmLoginSession
import com.xuqm.sdk.XuqmSDK import com.xuqm.sdk.XuqmSDK
import com.xuqm.sdk.core.ServiceEndpointRegistry import com.xuqm.sdk.core.ServiceEndpointRegistry
import com.xuqm.sdk.im.api.AddMemberRequest import com.xuqm.sdk.im.api.AddMemberRequest
import com.xuqm.sdk.im.api.AttributeKeysRequest
import com.xuqm.sdk.im.api.CreateGroupRequest import com.xuqm.sdk.im.api.CreateGroupRequest
import com.xuqm.sdk.im.api.GroupReadReceiptRequest
import com.xuqm.sdk.im.api.ImApi import com.xuqm.sdk.im.api.ImApi
import com.xuqm.sdk.im.model.EditMessageRequest import com.xuqm.sdk.im.model.EditMessageRequest
import com.xuqm.sdk.im.api.MuteGroupMemberRequest import com.xuqm.sdk.im.api.MuteGroupMemberRequest
import com.xuqm.sdk.im.api.SetMutedRequest
import com.xuqm.sdk.im.api.SetPinnedRequest
import com.xuqm.sdk.im.api.SetGroupRoleRequest import com.xuqm.sdk.im.api.SetGroupRoleRequest
import com.xuqm.sdk.im.api.TransferOwnerRequest
import com.xuqm.sdk.im.api.UpdateGroupRequest import com.xuqm.sdk.im.api.UpdateGroupRequest
import com.xuqm.sdk.im.listener.ImEventListener import com.xuqm.sdk.im.listener.ImEventListener
import com.xuqm.sdk.im.model.BlacklistCheckResult
import com.xuqm.sdk.im.model.ConversationGroupItem
import com.xuqm.sdk.im.model.ImConnectionState import com.xuqm.sdk.im.model.ImConnectionState
import com.xuqm.sdk.im.model.ConversationData import com.xuqm.sdk.im.model.ConversationData
import com.xuqm.sdk.im.model.GroupJoinRequest import com.xuqm.sdk.im.model.GroupJoinRequest
import com.xuqm.sdk.im.model.GroupReadReceiptSummary
import com.xuqm.sdk.im.model.ImGroup import com.xuqm.sdk.im.model.ImGroup
import com.xuqm.sdk.im.model.ImMessage import com.xuqm.sdk.im.model.ImMessage
import com.xuqm.sdk.im.model.PageResult import com.xuqm.sdk.im.model.PageResult
@ -534,6 +538,15 @@ object ImSDK {
suspend fun muteGroupMember(groupId: String, userId: String, minutes: Long) = suspend fun muteGroupMember(groupId: String, userId: String, minutes: Long) =
withContext(Dispatchers.IO) { api.muteGroupMember(groupId, MuteGroupMemberRequest(userId, minutes)) } withContext(Dispatchers.IO) { api.muteGroupMember(groupId, MuteGroupMemberRequest(userId, minutes)) }
suspend fun transferGroupOwner(groupId: String, newOwnerId: String): ImGroup? =
withContext(Dispatchers.IO) { api.transferGroupOwner(groupId, TransferOwnerRequest(newOwnerId)).data }
suspend fun updateGroupAttributes(groupId: String, attributes: Map<String, Any?>): ImGroup? =
withContext(Dispatchers.IO) { api.updateGroupAttributes(groupId, attributes).data }
suspend fun removeGroupAttributes(groupId: String, keys: List<String>): ImGroup? =
withContext(Dispatchers.IO) { api.removeGroupAttributes(groupId, AttributeKeysRequest(keys)).data }
suspend fun dismissGroup(groupId: String) = suspend fun dismissGroup(groupId: String) =
withContext(Dispatchers.IO) { api.dismissGroup(groupId) } withContext(Dispatchers.IO) { api.dismissGroup(groupId) }
@ -555,9 +568,21 @@ object ImSDK {
suspend fun addFriend(friendId: String) = suspend fun addFriend(friendId: String) =
withContext(Dispatchers.IO) { api.addFriend(XuqmSDK.appKey, friendId) } withContext(Dispatchers.IO) { api.addFriend(XuqmSDK.appKey, friendId) }
suspend fun removeAllFriends() =
withContext(Dispatchers.IO) { api.removeAllFriends(XuqmSDK.appKey) }
suspend fun removeFriend(friendId: String) = suspend fun removeFriend(friendId: String) =
withContext(Dispatchers.IO) { api.removeFriend(friendId, XuqmSDK.appKey) } withContext(Dispatchers.IO) { api.removeFriend(friendId, XuqmSDK.appKey) }
suspend fun setFriendGroup(friendId: String, groupName: String? = null) =
withContext(Dispatchers.IO) { api.setFriendGroup(friendId, XuqmSDK.appKey, groupName) }
suspend fun listFriendGroups(): List<String> =
withContext(Dispatchers.IO) { api.listFriendGroups(XuqmSDK.appKey).data ?: emptyList() }
suspend fun listFriendsByGroup(groupName: String): List<String> =
withContext(Dispatchers.IO) { api.listFriendsByGroup(groupName, XuqmSDK.appKey).data ?: emptyList() }
suspend fun listFriendRequests(direction: String = "incoming"): List<FriendRequest> = suspend fun listFriendRequests(direction: String = "incoming"): List<FriendRequest> =
withContext(Dispatchers.IO) { api.listFriendRequests(XuqmSDK.appKey, direction).data ?: emptyList() } withContext(Dispatchers.IO) { api.listFriendRequests(XuqmSDK.appKey, direction).data ?: emptyList() }
@ -579,6 +604,9 @@ object ImSDK {
suspend fun removeFromBlacklist(blockedUserId: String) = suspend fun removeFromBlacklist(blockedUserId: String) =
withContext(Dispatchers.IO) { api.removeFromBlacklist(XuqmSDK.appKey, blockedUserId) } withContext(Dispatchers.IO) { api.removeFromBlacklist(XuqmSDK.appKey, blockedUserId) }
suspend fun checkBlacklist(targetUserId: String): BlacklistCheckResult? =
withContext(Dispatchers.IO) { api.checkBlacklist(XuqmSDK.appKey, targetUserId).data }
suspend fun getProfile(userId: String) = suspend fun getProfile(userId: String) =
withContext(Dispatchers.IO) { api.getProfile(userId, XuqmSDK.appKey).data } withContext(Dispatchers.IO) { api.getProfile(userId, XuqmSDK.appKey).data }
@ -601,14 +629,30 @@ object ImSDK {
suspend fun setConversationPinned(targetId: String, chatType: String, pinned: Boolean) = suspend fun setConversationPinned(targetId: String, chatType: String, pinned: Boolean) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
api.setConversationPinned(targetId, chatType, SetPinnedRequest(pinned)) api.setConversationPinned(targetId, XuqmSDK.appKey, chatType, pinned)
} }
suspend fun setConversationMuted(targetId: String, chatType: String, muted: Boolean) = suspend fun setConversationMuted(targetId: String, chatType: String, muted: Boolean) =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
api.setConversationMuted(targetId, chatType, SetMutedRequest(muted)) api.setConversationMuted(targetId, XuqmSDK.appKey, chatType, muted)
} }
suspend fun setConversationHidden(targetId: String, chatType: String, hidden: Boolean) =
withContext(Dispatchers.IO) {
api.setConversationHidden(targetId, XuqmSDK.appKey, chatType, hidden)
}
suspend fun setConversationGroup(targetId: String, chatType: String, groupName: String? = null) =
withContext(Dispatchers.IO) {
api.setConversationGroup(targetId, XuqmSDK.appKey, chatType, groupName)
}
suspend fun listConversationGroups(): List<String> =
withContext(Dispatchers.IO) { api.listConversationGroups(XuqmSDK.appKey).data ?: emptyList() }
suspend fun listConversationGroupItems(groupName: String): List<ConversationGroupItem> =
withContext(Dispatchers.IO) { api.listConversationGroupItems(groupName, XuqmSDK.appKey).data ?: emptyList() }
suspend fun markRead(targetId: String, chatType: String = "SINGLE") = suspend fun markRead(targetId: String, chatType: String = "SINGLE") =
withContext(Dispatchers.IO) { api.markRead(targetId, XuqmSDK.appKey, chatType) } withContext(Dispatchers.IO) { api.markRead(targetId, XuqmSDK.appKey, chatType) }
@ -618,6 +662,11 @@ object ImSDK {
suspend fun deleteConversation(targetId: String, chatType: String) = suspend fun deleteConversation(targetId: String, chatType: String) =
withContext(Dispatchers.IO) { api.deleteConversation(targetId, XuqmSDK.appKey, chatType) } withContext(Dispatchers.IO) { api.deleteConversation(targetId, XuqmSDK.appKey, chatType) }
suspend fun adminGroupReadReceipts(groupId: String, messageIds: List<String>): List<GroupReadReceiptSummary> =
withContext(Dispatchers.IO) {
api.adminGroupReadReceipts(groupId, XuqmSDK.appKey, GroupReadReceiptRequest(messageIds)).data ?: emptyList()
}
fun addListener(listener: ImEventListener) { fun addListener(listener: ImEventListener) {
Log.d(TAG, "addListener listener=${listener.javaClass.name}") Log.d(TAG, "addListener listener=${listener.javaClass.name}")
listeners.add(listener) listeners.add(listener)

查看文件

@ -1,9 +1,12 @@
package com.xuqm.sdk.im.api package com.xuqm.sdk.im.api
import com.xuqm.sdk.im.model.ConversationData import com.xuqm.sdk.im.model.ConversationData
import com.xuqm.sdk.im.model.BlacklistCheckResult
import com.xuqm.sdk.im.model.BlacklistEntry import com.xuqm.sdk.im.model.BlacklistEntry
import com.xuqm.sdk.im.model.ConversationGroupItem
import com.xuqm.sdk.im.model.EditMessageRequest import com.xuqm.sdk.im.model.EditMessageRequest
import com.xuqm.sdk.im.model.FriendRequest import com.xuqm.sdk.im.model.FriendRequest
import com.xuqm.sdk.im.model.GroupReadReceiptSummary
import com.xuqm.sdk.im.model.ImGroup import com.xuqm.sdk.im.model.ImGroup
import com.xuqm.sdk.im.model.GroupJoinRequest import com.xuqm.sdk.im.model.GroupJoinRequest
import com.xuqm.sdk.im.model.ImMessage import com.xuqm.sdk.im.model.ImMessage
@ -39,14 +42,16 @@ data class UpdateGroupRequest(val name: String? = null, val announcement: String
data class AddMemberRequest(val userId: String) data class AddMemberRequest(val userId: String)
data class SetPinnedRequest(val pinned: Boolean)
data class SetMutedRequest(val muted: Boolean)
data class SetGroupRoleRequest(val userId: String, val role: String) data class SetGroupRoleRequest(val userId: String, val role: String)
data class MuteGroupMemberRequest(val userId: String, val minutes: Long) data class MuteGroupMemberRequest(val userId: String, val minutes: Long)
data class TransferOwnerRequest(val newOwnerId: String)
data class AttributeKeysRequest(val keys: List<String>)
data class GroupReadReceiptRequest(val messageIds: List<String>)
interface ImApi { interface ImApi {
@POST("api/im/auth/login") @POST("api/im/auth/login")
@ -167,6 +172,24 @@ interface ImApi {
@Body request: MuteGroupMemberRequest, @Body request: MuteGroupMemberRequest,
): ApiResponse<ImGroup> ): ApiResponse<ImGroup>
@POST("api/im/groups/{groupId}/owner")
suspend fun transferGroupOwner(
@Path("groupId") groupId: String,
@Body request: TransferOwnerRequest,
): ApiResponse<ImGroup>
@PUT("api/im/groups/{groupId}/attributes")
suspend fun updateGroupAttributes(
@Path("groupId") groupId: String,
@Body attributes: Map<String, @JvmSuppressWildcards Any?>,
): ApiResponse<ImGroup>
@POST("api/im/groups/{groupId}/attributes/delete")
suspend fun removeGroupAttributes(
@Path("groupId") groupId: String,
@Body request: AttributeKeysRequest,
): ApiResponse<ImGroup>
@DELETE("api/im/groups/{groupId}") @DELETE("api/im/groups/{groupId}")
suspend fun dismissGroup(@Path("groupId") groupId: String): ApiResponse<Unit> suspend fun dismissGroup(@Path("groupId") groupId: String): ApiResponse<Unit>
@ -206,12 +229,31 @@ interface ImApi {
@Query("friendId") friendId: String, @Query("friendId") friendId: String,
): ApiResponse<Unit> ): ApiResponse<Unit>
@DELETE("api/im/friends")
suspend fun removeAllFriends(@Query("appId") appId: String): ApiResponse<Unit>
@DELETE("api/im/friends/{friendId}") @DELETE("api/im/friends/{friendId}")
suspend fun removeFriend( suspend fun removeFriend(
@Path("friendId") friendId: String, @Path("friendId") friendId: String,
@Query("appId") appId: String, @Query("appId") appId: String,
): ApiResponse<Unit> ): ApiResponse<Unit>
@PUT("api/im/friends/{friendId}/group")
suspend fun setFriendGroup(
@Path("friendId") friendId: String,
@Query("appId") appId: String,
@Query("groupName") groupName: String? = null,
): ApiResponse<Unit>
@GET("api/im/friends/groups")
suspend fun listFriendGroups(@Query("appId") appId: String): ApiResponse<List<String>>
@GET("api/im/friends/groups/{groupName}")
suspend fun listFriendsByGroup(
@Path("groupName") groupName: String,
@Query("appId") appId: String,
): ApiResponse<List<String>>
@GET("api/im/friend-requests") @GET("api/im/friend-requests")
suspend fun listFriendRequests( suspend fun listFriendRequests(
@Query("appId") appId: String, @Query("appId") appId: String,
@ -252,6 +294,12 @@ interface ImApi {
@Query("blockedUserId") blockedUserId: String, @Query("blockedUserId") blockedUserId: String,
): ApiResponse<Unit> ): ApiResponse<Unit>
@GET("api/im/blacklist/check")
suspend fun checkBlacklist(
@Query("appId") appId: String,
@Query("targetUserId") targetUserId: String,
): ApiResponse<BlacklistCheckResult>
@GET("api/im/accounts/{userId}") @GET("api/im/accounts/{userId}")
suspend fun getProfile( suspend fun getProfile(
@Path("userId") userId: String, @Path("userId") userId: String,
@ -273,17 +321,44 @@ interface ImApi {
@PUT("api/im/conversations/{targetId}/pinned") @PUT("api/im/conversations/{targetId}/pinned")
suspend fun setConversationPinned( suspend fun setConversationPinned(
@Path("targetId") targetId: String, @Path("targetId") targetId: String,
@Query("appId") appId: String,
@Query("chatType") chatType: String, @Query("chatType") chatType: String,
@Body request: SetPinnedRequest, @Query("pinned") pinned: Boolean,
): ApiResponse<Unit> ): ApiResponse<Unit>
@PUT("api/im/conversations/{targetId}/muted") @PUT("api/im/conversations/{targetId}/muted")
suspend fun setConversationMuted( suspend fun setConversationMuted(
@Path("targetId") targetId: String, @Path("targetId") targetId: String,
@Query("appId") appId: String,
@Query("chatType") chatType: String, @Query("chatType") chatType: String,
@Body request: SetMutedRequest, @Query("muted") muted: Boolean,
): ApiResponse<Unit> ): ApiResponse<Unit>
@PUT("api/im/conversations/{targetId}/hidden")
suspend fun setConversationHidden(
@Path("targetId") targetId: String,
@Query("appId") appId: String,
@Query("chatType") chatType: String,
@Query("hidden") hidden: Boolean,
): ApiResponse<Unit>
@PUT("api/im/conversations/{targetId}/group")
suspend fun setConversationGroup(
@Path("targetId") targetId: String,
@Query("appId") appId: String,
@Query("chatType") chatType: String,
@Query("groupName") groupName: String? = null,
): ApiResponse<Unit>
@GET("api/im/conversation-groups")
suspend fun listConversationGroups(@Query("appId") appId: String): ApiResponse<List<String>>
@GET("api/im/conversation-groups/{groupName}")
suspend fun listConversationGroupItems(
@Path("groupName") groupName: String,
@Query("appId") appId: String,
): ApiResponse<List<ConversationGroupItem>>
@PUT("api/im/conversations/{targetId}/read") @PUT("api/im/conversations/{targetId}/read")
suspend fun markRead( suspend fun markRead(
@Path("targetId") targetId: String, @Path("targetId") targetId: String,
@ -318,4 +393,11 @@ interface ImApi {
@Query("appId") appId: String, @Query("appId") appId: String,
@Query("chatType") chatType: String, @Query("chatType") chatType: String,
): ApiResponse<Unit> ): ApiResponse<Unit>
@POST("api/im/admin/groups/{groupId}/read-receipts")
suspend fun adminGroupReadReceipts(
@Path("groupId") groupId: String,
@Query("appId") appId: String,
@Body request: GroupReadReceiptRequest,
): ApiResponse<List<GroupReadReceiptSummary>>
} }

查看文件

@ -38,6 +38,7 @@ data class ImMessage(
data class ConversationData( data class ConversationData(
val targetId: String, val targetId: String,
val chatType: String, val chatType: String,
val conversationGroup: String? = null,
val lastMsgContent: String? = null, val lastMsgContent: String? = null,
val lastMsgType: String? = null, val lastMsgType: String? = null,
val lastMsgTime: Long = 0, val lastMsgTime: Long = 0,
@ -54,9 +55,17 @@ data class ImGroup(
val memberIds: String, val memberIds: String,
val adminIds: String, val adminIds: String,
val announcement: String? = null, val announcement: String? = null,
val memberInfo: String? = null,
val extAttributes: String? = null,
val createdAt: Long, val createdAt: Long,
) )
data class ConversationGroupItem(
val targetId: String,
val chatType: String,
val groupName: String,
)
data class UserProfile( data class UserProfile(
val userId: String, val userId: String,
val nickname: String, val nickname: String,
@ -93,6 +102,21 @@ data class BlacklistEntry(
val createdAt: Long, val createdAt: Long,
) )
data class BlacklistCheckResult(
val targetUserId: String,
val blockedByMe: Boolean,
val blockedByTarget: Boolean,
val eitherBlocked: Boolean,
)
data class GroupReadReceiptSummary(
val messageId: String,
val groupId: String,
val memberCount: Int,
val readCount: Int,
val unreadCount: Int,
)
enum class ChatType { SINGLE, GROUP } enum class ChatType { SINGLE, GROUP }
enum class MsgType { enum class MsgType {