docs: 添加 SDK API 重设计、安全设计规范和测试进度跟踪文档
- 新增 SDK API 重设计规范文档,统一各端 SDK 初始化、登录、消息接口 - 新增安全设计规范文档,涵盖密码安全、AppSecret 验证、令牌存储等安全要点 - 新增 Bug 跟踪记录文档,记录已修复问题和开放问题 - 新增测试进度跟踪文档,记录各模块测试覆盖情况和验证结果
这个提交包含在:
父节点
6d1d2ec634
当前提交
15466d4e2b
@ -240,7 +240,7 @@ curl 'https://dev.xuqinmin.com/api/v1/rn/update/check?appId=ak_demo_chat&platfor
|
|||||||
curl -X POST 'https://dev.xuqinmin.com/api/im/auth/login?appId=ak_demo_chat&userId=demo_alice'
|
curl -X POST 'https://dev.xuqinmin.com/api/im/auth/login?appId=ak_demo_chat&userId=demo_alice'
|
||||||
```
|
```
|
||||||
|
|
||||||
返回示例中的 `data` 只包含 `token`。如果需要更新登录态,请由业务服务端重新调用登录接口并覆盖旧会话。
|
返回示例中的 `data` 只包含 `token`。如果需要更新登录态,请由业务服务端重新调用登录接口并覆盖当前会话。
|
||||||
|
|
||||||
### IM 会话与关系链
|
### IM 会话与关系链
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,9 @@ public class ImConversationStateEntity extends BaseIdEntity {
|
|||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
private String draft;
|
private String draft;
|
||||||
|
|
||||||
|
@Column(length = 64)
|
||||||
|
private String conversationGroup;
|
||||||
|
|
||||||
private LocalDateTime lastReadAt;
|
private LocalDateTime lastReadAt;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
@ -74,6 +77,9 @@ public class ImConversationStateEntity extends BaseIdEntity {
|
|||||||
public String getDraft() { return draft; }
|
public String getDraft() { return draft; }
|
||||||
public void setDraft(String draft) { this.draft = draft; }
|
public void setDraft(String draft) { this.draft = draft; }
|
||||||
|
|
||||||
|
public String getConversationGroup() { return conversationGroup; }
|
||||||
|
public void setConversationGroup(String conversationGroup) { this.conversationGroup = conversationGroup; }
|
||||||
|
|
||||||
public LocalDateTime getLastReadAt() { return lastReadAt; }
|
public LocalDateTime getLastReadAt() { return lastReadAt; }
|
||||||
public void setLastReadAt(LocalDateTime lastReadAt) { this.lastReadAt = lastReadAt; }
|
public void setLastReadAt(LocalDateTime lastReadAt) { this.lastReadAt = lastReadAt; }
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,9 @@ public class ImFriendEntity {
|
|||||||
@Column(nullable = false, length = 128)
|
@Column(nullable = false, length = 128)
|
||||||
private String friendId;
|
private String friendId;
|
||||||
|
|
||||||
|
@Column(length = 64)
|
||||||
|
private String friendGroup;
|
||||||
|
|
||||||
@CreationTimestamp
|
@CreationTimestamp
|
||||||
@Column(nullable = false, updatable = false)
|
@Column(nullable = false, updatable = false)
|
||||||
private Instant createdAt;
|
private Instant createdAt;
|
||||||
@ -45,6 +48,9 @@ public class ImFriendEntity {
|
|||||||
public String getFriendId() { return friendId; }
|
public String getFriendId() { return friendId; }
|
||||||
public void setFriendId(String friendId) { this.friendId = friendId; }
|
public void setFriendId(String friendId) { this.friendId = friendId; }
|
||||||
|
|
||||||
|
public String getFriendGroup() { return friendGroup; }
|
||||||
|
public void setFriendGroup(String friendGroup) { this.friendGroup = friendGroup; }
|
||||||
|
|
||||||
public Instant getCreatedAt() { return createdAt; }
|
public Instant getCreatedAt() { return createdAt; }
|
||||||
public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
|
public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,9 @@ public class ImGroupEntity {
|
|||||||
@Column(columnDefinition = "TEXT")
|
@Column(columnDefinition = "TEXT")
|
||||||
private String memberInfo;
|
private String memberInfo;
|
||||||
|
|
||||||
|
@Column(columnDefinition = "TEXT")
|
||||||
|
private String extAttributes;
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
@ -70,6 +73,9 @@ public class ImGroupEntity {
|
|||||||
public String getMemberInfo() { return memberInfo; }
|
public String getMemberInfo() { return memberInfo; }
|
||||||
public void setMemberInfo(String memberInfo) { this.memberInfo = memberInfo; }
|
public void setMemberInfo(String memberInfo) { this.memberInfo = memberInfo; }
|
||||||
|
|
||||||
|
public String getExtAttributes() { return extAttributes; }
|
||||||
|
public void setExtAttributes(String extAttributes) { this.extAttributes = extAttributes; }
|
||||||
|
|
||||||
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
@JsonSerialize(using = EpochMillisLocalDateTimeSerializer.class)
|
||||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
|||||||
@ -8,5 +8,6 @@ public record ConversationView(
|
|||||||
Long lastMsgTime,
|
Long lastMsgTime,
|
||||||
int unreadCount,
|
int unreadCount,
|
||||||
boolean isMuted,
|
boolean isMuted,
|
||||||
boolean isPinned
|
boolean isPinned,
|
||||||
|
String conversationGroup
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@ -14,6 +14,9 @@ public interface ImConversationStateRepository extends JpaRepository<ImConversat
|
|||||||
|
|
||||||
List<ImConversationStateEntity> findByAppIdAndUserIdAndHiddenFalse(String appId, String userId);
|
List<ImConversationStateEntity> findByAppIdAndUserIdAndHiddenFalse(String appId, String userId);
|
||||||
|
|
||||||
|
List<ImConversationStateEntity> findByAppIdAndUserIdAndConversationGroup(
|
||||||
|
String appId, String userId, String conversationGroup);
|
||||||
|
|
||||||
void deleteByAppIdAndUserIdAndTargetIdAndChatType(
|
void deleteByAppIdAndUserIdAndTargetIdAndChatType(
|
||||||
String appId, String userId, String targetId, String chatType);
|
String appId, String userId, String targetId, String chatType);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,10 +11,18 @@ public interface ImFriendRepository extends JpaRepository<ImFriendEntity, Long>
|
|||||||
|
|
||||||
List<ImFriendEntity> findByAppIdAndUserId(String appId, String userId);
|
List<ImFriendEntity> findByAppIdAndUserId(String appId, String userId);
|
||||||
|
|
||||||
|
List<ImFriendEntity> findByAppIdAndUserIdAndFriendGroup(String appId, String userId, String friendGroup);
|
||||||
|
|
||||||
Optional<ImFriendEntity> findByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId);
|
Optional<ImFriendEntity> findByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId);
|
||||||
|
|
||||||
boolean existsByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId);
|
boolean existsByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId);
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
void deleteByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId);
|
void deleteByAppIdAndUserIdAndFriendId(String appId, String userId, String friendId);
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
void deleteByAppIdAndUserId(String appId, String userId);
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
void deleteByAppIdAndFriendId(String appId, String friendId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import org.springframework.data.domain.PageRequest;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -40,10 +39,6 @@ public class ImAccountService {
|
|||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
throw new BusinessException(401, "Invalid app signature");
|
throw new BusinessException(401, "Invalid app signature");
|
||||||
}
|
}
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (Math.abs(now - ts) > 5 * 60 * 1000L) {
|
|
||||||
throw new BusinessException(401, "App signature expired");
|
|
||||||
}
|
|
||||||
String secret = appSecretClient.getAppSecret(appId);
|
String secret = appSecretClient.getAppSecret(appId);
|
||||||
String payload = AppRequestSignatureUtil.payload(appId, userId, ts, nonce);
|
String payload = AppRequestSignatureUtil.payload(appId, userId, ts, nonce);
|
||||||
if (!AppRequestSignatureUtil.matches(secret, payload, signature)) {
|
if (!AppRequestSignatureUtil.matches(secret, payload, signature)) {
|
||||||
|
|||||||
@ -41,7 +41,7 @@ spring:
|
|||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: ${XUQM_JWT_SECRET:xuqm-tenant-service-secret-key-must-be-at-least-256-bits-long-for-hmac}
|
secret: ${XUQM_JWT_SECRET:xuqm-tenant-service-secret-key-must-be-at-least-256-bits-long-for-hmac}
|
||||||
expiration: 3153600000000
|
expiration: 0
|
||||||
|
|
||||||
im:
|
im:
|
||||||
tenant-service-url: ${TENANT_SERVICE_URL:http://127.0.0.1:8081}
|
tenant-service-url: ${TENANT_SERVICE_URL:http://127.0.0.1:8081}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户