docs(deploy): 添加部署文档和安全设计规范
- 新增 XuqmGroup 部署文档,包含部署方案、架构建议和部署步骤 - 添加安全设计规范,涵盖密码安全、AppSecret验证和服务端API认证 - 补充平台REST API规范,定义Server-to-Server调用接口和错误码 - 创建Java IM服务端SDK计划文档,规划Maven包发布和接口实现
这个提交包含在:
父节点
7909e4cf66
当前提交
69f92836b7
@ -18,7 +18,7 @@
|
|||||||
| API 域名 | `https://dev.xuqinmin.com` |
|
| API 域名 | `https://dev.xuqinmin.com` |
|
||||||
| 登录服务 | `demo-service`(`/api/demo/auth/*`) |
|
| 登录服务 | `demo-service`(`/api/demo/auth/*`) |
|
||||||
| IM WebSocket | `wss://im.dev.xuqinmin.com/ws/im` |
|
| IM WebSocket | `wss://im.dev.xuqinmin.com/ws/im` |
|
||||||
| 演示 AppId | `ak_demo_chat` |
|
| 演示 AppKey | `ak_demo_chat` |
|
||||||
| 演示用户 | `demo_alice`(Alice)、`demo_bob`(Bob) |
|
| 演示用户 | `demo_alice`(Alice)、`demo_bob`(Bob) |
|
||||||
| 演示模块 | `chat-home` |
|
| 演示模块 | `chat-home` |
|
||||||
| 本地 App 版本码 | `1`(写死,便于触发更新) |
|
| 本地 App 版本码 | `1`(写死,便于触发更新) |
|
||||||
@ -95,7 +95,7 @@ npm run ios # iOS 模拟器
|
|||||||
### Section 3 · 群聊演示
|
### Section 3 · 群聊演示
|
||||||
|
|
||||||
1. 点击 **"+ 创建演示群"** → 创建含 Alice + Bob 的群组并订阅群 topic
|
1. 点击 **"+ 创建演示群"** → 创建含 Alice + Bob 的群组并订阅群 topic
|
||||||
2. 点击 **"加载群列表"** 列出当前 appId 下所有群组
|
2. 点击 **"加载群列表"** 列出当前 appKey 下所有群组
|
||||||
3. 点击群组名称切换当前群,自动拉取群历史
|
3. 点击群组名称切换当前群,自动拉取群历史
|
||||||
4. 使用消息类型选择器在群内发送任意类型消息
|
4. 使用消息类型选择器在群内发送任意类型消息
|
||||||
5. 点击 **"全部类型演示"** 依次发送 12 种群消息
|
5. 点击 **"全部类型演示"** 依次发送 12 种群消息
|
||||||
@ -128,7 +128,7 @@ cd XuqmGroup-RNChatDemo
|
|||||||
|
|
||||||
发布内容:
|
发布内容:
|
||||||
|
|
||||||
- `appId=ak_demo_chat` Android App 版本 `1.0.1`(versionCode=2)
|
- `appKey=ak_demo_chat` Android App 版本 `1.0.1`(versionCode=2)
|
||||||
- `moduleId=chat-home` Android RN bundle 版本 `1.0.1`
|
- `moduleId=chat-home` Android RN bundle 版本 `1.0.1`
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -147,7 +147,7 @@ cd XuqmGroup-RNChatDemo
|
|||||||
|
|
||||||
所有接口通过 Nginx 反代至 `https://dev.xuqinmin.com`。
|
所有接口通过 Nginx 反代至 `https://dev.xuqinmin.com`。
|
||||||
|
|
||||||
`demo-service` 负责 demo 账号体系的注册、登录、找回密码和资料更新;登录成功后会返回 `demoToken`、`imToken` 和 `profile`。其中 `profile.appId` 用于后续初始化 IM 和更新能力,不走租户服务登录链路。
|
`demo-service` 负责 demo 账号体系的注册、登录、找回密码和资料更新;登录成功后会返回 `demoToken`、`imToken` 和 `profile`。其中 `profile.appKey` 用于后续初始化 IM 和更新能力,不走租户服务登录链路。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ BUNDLE_FILE="${ASSET_DIR}/chat-home.android.bundle"
|
|||||||
echo "Uploading demo APK metadata to ${BASE_URL}"
|
echo "Uploading demo APK metadata to ${BASE_URL}"
|
||||||
|
|
||||||
APP_UPLOAD_JSON="$(curl -fsS -X POST "${BASE_URL}/api/v1/updates/app/upload" \
|
APP_UPLOAD_JSON="$(curl -fsS -X POST "${BASE_URL}/api/v1/updates/app/upload" \
|
||||||
-F "appId=${APP_ID}" \
|
-F "appKey=${APP_ID}" \
|
||||||
-F "platform=ANDROID" \
|
-F "platform=ANDROID" \
|
||||||
-F "versionName=1.0.1" \
|
-F "versionName=1.0.1" \
|
||||||
-F "versionCode=2" \
|
-F "versionCode=2" \
|
||||||
@ -36,7 +36,7 @@ curl -fsS -X POST "${BASE_URL}/api/v1/updates/app/${APP_VERSION_ID}/publish" >/d
|
|||||||
echo "Uploading demo RN bundle to ${BASE_URL}"
|
echo "Uploading demo RN bundle to ${BASE_URL}"
|
||||||
|
|
||||||
RN_UPLOAD_JSON="$(curl -fsS -X POST "${BASE_URL}/api/v1/rn/upload" \
|
RN_UPLOAD_JSON="$(curl -fsS -X POST "${BASE_URL}/api/v1/rn/upload" \
|
||||||
-F "appId=${APP_ID}" \
|
-F "appKey=${APP_ID}" \
|
||||||
-F "moduleId=${MODULE_ID}" \
|
-F "moduleId=${MODULE_ID}" \
|
||||||
-F "platform=ANDROID" \
|
-F "platform=ANDROID" \
|
||||||
-F "version=1.0.1" \
|
-F "version=1.0.1" \
|
||||||
|
|||||||
@ -12,7 +12,7 @@ async function request<T>(
|
|||||||
options: { method?: string; body?: unknown; params?: Record<string, string>; skipAuth?: boolean } = {},
|
options: { method?: string; body?: unknown; params?: Record<string, string>; skipAuth?: boolean } = {},
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
let url = BASE + path
|
let url = BASE + path
|
||||||
const params = { appId: APP_ID, ...(options.params ?? {}) }
|
const params = { appKey: APP_ID, ...(options.params ?? {}) }
|
||||||
url += '?' + new URLSearchParams(params).toString()
|
url += '?' + new URLSearchParams(params).toString()
|
||||||
|
|
||||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
||||||
@ -33,7 +33,7 @@ async function request<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface UserProfile {
|
export interface UserProfile {
|
||||||
appId: string
|
appKey: string
|
||||||
userId: string
|
userId: string
|
||||||
nickname: string
|
nickname: string
|
||||||
avatar: string
|
avatar: string
|
||||||
@ -52,7 +52,7 @@ export const demoApi = {
|
|||||||
return request('/auth/register', {
|
return request('/auth/register', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
skipAuth: true,
|
skipAuth: true,
|
||||||
body: { appId: APP_ID, userId, password, nickname },
|
body: { appKey: APP_ID, userId, password, nickname },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ export const demoApi = {
|
|||||||
return request('/auth/login', {
|
return request('/auth/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
skipAuth: true,
|
skipAuth: true,
|
||||||
body: { appId: APP_ID, userId, password },
|
body: { appKey: APP_ID, userId, password },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ export const demoApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
resetPassword(userId: string, newPassword: string): Promise<void> {
|
resetPassword(userId: string, newPassword: string): Promise<void> {
|
||||||
return request('/auth/reset-password', { method: 'POST', skipAuth: true, body: { appId: APP_ID, userId, newPassword } })
|
return request('/auth/reset-password', { method: 'POST', skipAuth: true, body: { appKey: APP_ID, userId, newPassword } })
|
||||||
},
|
},
|
||||||
|
|
||||||
changePassword(oldPassword: string, newPassword: string): Promise<void> {
|
changePassword(oldPassword: string, newPassword: string): Promise<void> {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ interface Props {
|
|||||||
currentUserId: string
|
currentUserId: string
|
||||||
peerUserId: string
|
peerUserId: string
|
||||||
peerNickname: string
|
peerNickname: string
|
||||||
appId: string
|
appKey: string
|
||||||
onLog: (msg: string) => void
|
onLog: (msg: string) => void
|
||||||
groupMessages: ImMessage[]
|
groupMessages: ImMessage[]
|
||||||
onMergeMessage: (msg: ImMessage) => void
|
onMergeMessage: (msg: ImMessage) => void
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const [state, setState] = useState<AuthState>({ ready: false, userId: null, profile: null })
|
const [state, setState] = useState<AuthState>({ ready: false, userId: null, profile: null })
|
||||||
|
|
||||||
const initSDK = useCallback(async (profile: UserProfile, imToken: string) => {
|
const initSDK = useCallback(async (profile: UserProfile, imToken: string) => {
|
||||||
const appKey = profile.appId || APP_ID
|
const appKey = profile.appKey || APP_ID
|
||||||
await XuqmSDK.initialize({ appKey })
|
await XuqmSDK.initialize({ appKey })
|
||||||
await XuqmSDK.login({ userId: profile.userId, userSig: imToken })
|
await XuqmSDK.login({ userId: profile.userId, userSig: imToken })
|
||||||
setState({ ready: true, userId: profile.userId, profile })
|
setState({ ready: true, userId: profile.userId, profile })
|
||||||
|
|||||||
@ -19,7 +19,7 @@ interface ConvWithMeta extends ConversationData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ConversationListScreen() {
|
export default function ConversationListScreen() {
|
||||||
const appId = 'ak_demo_chat'
|
const appKey = 'ak_demo_chat'
|
||||||
const navigation = useNavigation<Nav>()
|
const navigation = useNavigation<Nav>()
|
||||||
const [conversations, setConversations] = useState<ConvWithMeta[]>([])
|
const [conversations, setConversations] = useState<ConvWithMeta[]>([])
|
||||||
const profileCache = useRef<Record<string, UserProfile>>({})
|
const profileCache = useRef<Record<string, UserProfile>>({})
|
||||||
@ -30,10 +30,10 @@ export default function ConversationListScreen() {
|
|||||||
try {
|
try {
|
||||||
const results = await ImSDK.searchUsers(conv.targetId)
|
const results = await ImSDK.searchUsers(conv.targetId)
|
||||||
const match = results.find(u => u.userId === conv.targetId)
|
const match = results.find(u => u.userId === conv.targetId)
|
||||||
profile = match ? toDemoUserProfile(match) : { appId, userId: conv.targetId, nickname: conv.targetId, avatar: '', gender: 'UNKNOWN', status: '' }
|
profile = match ? toDemoUserProfile(match) : { appKey, userId: conv.targetId, nickname: conv.targetId, avatar: '', gender: 'UNKNOWN', status: '' }
|
||||||
profileCache.current[conv.targetId] = profile
|
profileCache.current[conv.targetId] = profile
|
||||||
} catch {
|
} catch {
|
||||||
profile = { appId, userId: conv.targetId, nickname: conv.targetId, avatar: '', gender: 'UNKNOWN', status: '' }
|
profile = { appKey, userId: conv.targetId, nickname: conv.targetId, avatar: '', gender: 'UNKNOWN', status: '' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { ...conv, targetName: profile.nickname, targetAvatar: profile.avatar }
|
return { ...conv, targetName: profile.nickname, targetAvatar: profile.avatar }
|
||||||
|
|||||||
@ -13,7 +13,7 @@ type Props = NativeStackScreenProps<RootStackParams, 'GroupMembers'>
|
|||||||
|
|
||||||
export default function GroupMembersScreen({ route }: Props) {
|
export default function GroupMembersScreen({ route }: Props) {
|
||||||
const { groupId } = route.params
|
const { groupId } = route.params
|
||||||
const appId = 'ak_demo_chat'
|
const appKey = 'ak_demo_chat'
|
||||||
const [members, setMembers] = useState<UserProfile[]>([])
|
const [members, setMembers] = useState<UserProfile[]>([])
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ export default function GroupMembersScreen({ route }: Props) {
|
|||||||
ids.map(async id => {
|
ids.map(async id => {
|
||||||
const res = await ImSDK.searchUsers(id)
|
const res = await ImSDK.searchUsers(id)
|
||||||
const match = res.find(u => u.userId === id)
|
const match = res.find(u => u.userId === id)
|
||||||
return match ? toDemoUserProfile(match, appId) : { appId, userId: id, nickname: id, avatar: '', gender: 'UNKNOWN' as const, status: '' }
|
return match ? toDemoUserProfile(match, appKey) : { appKey, userId: id, nickname: id, avatar: '', gender: 'UNKNOWN' as const, status: '' }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
setMembers(profiles)
|
setMembers(profiles)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { UserProfile } from '../api/demo'
|
import type { UserProfile } from '../api/demo'
|
||||||
|
|
||||||
export interface SearchUserProfileLike {
|
export interface SearchUserProfileLike {
|
||||||
appId?: string
|
appKey?: string
|
||||||
userId: string
|
userId: string
|
||||||
nickname?: string | null
|
nickname?: string | null
|
||||||
avatar?: string | null
|
avatar?: string | null
|
||||||
@ -9,9 +9,9 @@ export interface SearchUserProfileLike {
|
|||||||
status?: string | null
|
status?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toDemoUserProfile(user: SearchUserProfileLike, appId = 'ak_demo_chat'): UserProfile {
|
export function toDemoUserProfile(user: SearchUserProfileLike, appKey = 'ak_demo_chat'): UserProfile {
|
||||||
return {
|
return {
|
||||||
appId: user.appId ?? appId,
|
appKey: user.appKey ?? appKey,
|
||||||
userId: user.userId,
|
userId: user.userId,
|
||||||
nickname: user.nickname ?? user.userId,
|
nickname: user.nickname ?? user.userId,
|
||||||
avatar: user.avatar ?? '',
|
avatar: user.avatar ?? '',
|
||||||
@ -20,6 +20,6 @@ export function toDemoUserProfile(user: SearchUserProfileLike, appId = 'ak_demo_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toDemoUserProfiles(users: SearchUserProfileLike[], appId = 'ak_demo_chat'): UserProfile[] {
|
export function toDemoUserProfiles(users: SearchUserProfileLike[], appKey = 'ak_demo_chat'): UserProfile[] {
|
||||||
return users.map(user => toDemoUserProfile(user, appId))
|
return users.map(user => toDemoUserProfile(user, appKey))
|
||||||
}
|
}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户