mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-06-26 12:29:28 +08:00
查看自己登录的在线设备
This commit is contained in:
parent
a5fcb4ed23
commit
34c13cf608
@ -134,6 +134,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
|
||||
userRouterGroup.POST("/get_users", u.GetUsers)
|
||||
userRouterGroup.POST("/get_users_online_status", u.GetUsersOnlineStatus)
|
||||
userRouterGroup.POST("/get_users_online_token_detail", u.GetUsersOnlineTokenDetail)
|
||||
userRouterGroup.POST("/get_self_login_platforms", u.GetSelfLoginPlatforms)
|
||||
userRouterGroup.POST("/subscribe_users_status", u.SubscriberStatus)
|
||||
userRouterGroup.POST("/get_users_status", u.GetUserStatus)
|
||||
userRouterGroup.POST("/get_subscribe_users_status", u.GetSubscribeUsersStatus)
|
||||
|
||||
@ -33,6 +33,16 @@ type UserApi struct {
|
||||
config config.RpcRegisterName
|
||||
}
|
||||
|
||||
type GetSelfLoginPlatformsResp struct {
|
||||
PlatformID int32 `json:"platformID"`
|
||||
ConnID string `json:"connID"`
|
||||
IsBackground bool `json:"isBackground"`
|
||||
LoginTime int64 `json:"loginTime"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
DeviceModel string `json:"deviceModel"`
|
||||
SDKVersion string `json:"sdkVersion"`
|
||||
}
|
||||
|
||||
func NewUserApi(client user.UserClient, discov discovery.SvcDiscoveryRegistry, config config.RpcRegisterName) UserApi {
|
||||
return UserApi{Client: client, discov: discov, config: config}
|
||||
}
|
||||
@ -191,6 +201,59 @@ func (u *UserApi) GetUsersOnlineTokenDetail(c *gin.Context) {
|
||||
apiresp.GinSuccess(c, respResult)
|
||||
}
|
||||
|
||||
// GetSelfLoginPlatforms Get online terminals for current user.
|
||||
func (u *UserApi) GetSelfLoginPlatforms(c *gin.Context) {
|
||||
opUserID, ok := c.Get(constant.OpUserID)
|
||||
if !ok {
|
||||
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("operator user id not found"))
|
||||
return
|
||||
}
|
||||
userID, _ := opUserID.(string)
|
||||
if userID == "" {
|
||||
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("operator user id is empty"))
|
||||
return
|
||||
}
|
||||
req := msggateway.GetUsersOnlineStatusReq{
|
||||
UserIDs: []string{userID},
|
||||
}
|
||||
conns, err := u.discov.GetConns(c, u.config.MessageGateway)
|
||||
if err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
log.ZDebug(c, "GetSelfLoginPlatforms", "userID", userID)
|
||||
result := make([]*GetSelfLoginPlatformsResp, 0, 8)
|
||||
for _, v := range conns {
|
||||
msgClient := msggateway.NewMsgGatewayClient(v)
|
||||
reply, err := msgClient.GetUsersOnlineStatus(c, &req)
|
||||
if err != nil {
|
||||
log.ZWarn(c, "GetUsersOnlineStatus rpc err", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.ZDebug(c, "GetSelfLoginPlatforms", "userID", userID, "reply", reply.SuccessResult)
|
||||
|
||||
for _, r := range reply.SuccessResult {
|
||||
if r.UserID != userID || r.Status != constant.Online {
|
||||
log.ZDebug(c, "GetUsersOnlineStatus result not match", "userID", r.UserID, "status", r.Status)
|
||||
continue
|
||||
}
|
||||
for _, detail := range r.DetailPlatformStatus {
|
||||
result = append(result, &GetSelfLoginPlatformsResp{
|
||||
PlatformID: detail.PlatformID,
|
||||
ConnID: detail.ConnID,
|
||||
IsBackground: detail.IsBackground,
|
||||
LoginTime: detail.LoginTime,
|
||||
DeviceName: detail.DeviceName,
|
||||
DeviceModel: detail.DeviceModel,
|
||||
SDKVersion: detail.SdkVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
apiresp.GinSuccess(c, result)
|
||||
}
|
||||
|
||||
// SubscriberStatus Presence status of subscribed users.
|
||||
func (u *UserApi) SubscriberStatus(c *gin.Context) {
|
||||
a2r.Call(c, user.UserClient.SubscribeOrCancelUsersStatus, u.Client)
|
||||
|
||||
@ -68,6 +68,10 @@ type Client struct {
|
||||
UserID string `json:"userID"`
|
||||
IsBackground bool `json:"isBackground"`
|
||||
SDKType string `json:"sdkType"`
|
||||
SDKVersion string `json:"sdkVersion"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
DeviceModel string `json:"deviceModel"`
|
||||
LoginTimestamp int64 `json:"loginTimestamp"`
|
||||
Encoder Encoder
|
||||
ctx *UserConnContext
|
||||
longConnServer LongConnServer
|
||||
@ -95,6 +99,10 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn ClientConn, longConnServ
|
||||
c.closedErr = nil
|
||||
c.token = ctx.GetToken()
|
||||
c.SDKType = ctx.GetSDKType()
|
||||
c.SDKVersion = ctx.GetSDKVersion()
|
||||
c.DeviceName = ctx.GetDeviceName()
|
||||
c.DeviceModel = ctx.GetDeviceModel()
|
||||
c.LoginTimestamp = time.Now().Unix()
|
||||
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
|
||||
c.subLock = new(sync.Mutex)
|
||||
if c.subUserIDs != nil {
|
||||
|
||||
@ -28,6 +28,9 @@ const (
|
||||
BackgroundStatus = "isBackground"
|
||||
SendResponse = "isMsgResp"
|
||||
SDKType = "sdkType"
|
||||
DeviceName = "deviceName"
|
||||
DeviceModel = "deviceModel"
|
||||
SDKVersion = "sdkVersion"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@ -38,6 +38,9 @@ type UserConnContextInfo struct {
|
||||
SDKType string `json:"sdkType"`
|
||||
SendResponse bool `json:"sendResponse"`
|
||||
Background bool `json:"background"`
|
||||
DeviceName string `json:"deviceName"`
|
||||
DeviceModel string `json:"deviceModel"`
|
||||
SDKVersion string `json:"sdkVersion"`
|
||||
}
|
||||
|
||||
type UserConnContext struct {
|
||||
@ -117,6 +120,9 @@ func (c *UserConnContext) parseByQuery(query url.Values, header http.Header) err
|
||||
OperationID: query.Get(OperationID),
|
||||
Compression: query.Get(Compression),
|
||||
SDKType: query.Get(SDKType),
|
||||
DeviceName: query.Get(DeviceName),
|
||||
DeviceModel: query.Get(DeviceModel),
|
||||
SDKVersion: query.Get(SDKVersion),
|
||||
}
|
||||
platformID, err := strconv.Atoi(query.Get(PlatformID))
|
||||
if err != nil {
|
||||
@ -260,3 +266,24 @@ func (c *UserConnContext) SetToken(token string) {
|
||||
func (c *UserConnContext) GetBackground() bool {
|
||||
return c != nil && c.info != nil && c.info.Background
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetDeviceName() string {
|
||||
if c == nil || c.info == nil {
|
||||
return ""
|
||||
}
|
||||
return c.info.DeviceName
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetDeviceModel() string {
|
||||
if c == nil || c.info == nil {
|
||||
return ""
|
||||
}
|
||||
return c.info.DeviceModel
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetSDKVersion() string {
|
||||
if c == nil || c.info == nil {
|
||||
return ""
|
||||
}
|
||||
return c.info.SDKVersion
|
||||
}
|
||||
|
||||
@ -95,13 +95,17 @@ func NewServer(longConnServer LongConnServer, conf *Config, ready func(srv *Serv
|
||||
}
|
||||
|
||||
func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUsersOnlineStatusReq) (*msggateway.GetUsersOnlineStatusResp, error) {
|
||||
if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) {
|
||||
opUserID := mcontext.GetOpUserID(ctx)
|
||||
isSelfQuery := len(req.UserIDs) == 1 && opUserID != "" && req.UserIDs[0] == opUserID
|
||||
if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) && !isSelfQuery {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("only app manager")
|
||||
}
|
||||
log.ZDebug(ctx, "GetUsersOnlineStatus", "userIDs", req.UserIDs)
|
||||
var resp msggateway.GetUsersOnlineStatusResp
|
||||
for _, userID := range req.UserIDs {
|
||||
clients, ok := s.LongConnServer.GetUserAllCons(userID)
|
||||
if !ok {
|
||||
if !ok || len(clients) == 0 {
|
||||
log.ZWarn(ctx, "get users online status failed", errs.ErrRecordNotFound.WrapMsg("get client failed"), "userID", userID)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -109,6 +113,7 @@ func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUs
|
||||
uresp.UserID = userID
|
||||
for _, client := range clients {
|
||||
if client == nil {
|
||||
log.ZWarn(ctx, "get users online status failed", errs.ErrRecordNotFound.WrapMsg("user client is nil"), "userID", userID)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -117,11 +122,17 @@ func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUs
|
||||
ps.ConnID = client.ctx.GetConnID()
|
||||
ps.Token = client.token
|
||||
ps.IsBackground = client.IsBackground
|
||||
ps.LoginTime = client.LoginTimestamp
|
||||
ps.DeviceName = client.DeviceName
|
||||
ps.DeviceModel = client.DeviceModel
|
||||
ps.SdkVersion = client.SDKVersion
|
||||
uresp.Status = constant.Online
|
||||
uresp.DetailPlatformStatus = append(uresp.DetailPlatformStatus, ps)
|
||||
}
|
||||
if uresp.Status == constant.Online {
|
||||
resp.SuccessResult = append(resp.SuccessResult, uresp)
|
||||
} else {
|
||||
log.ZWarn(ctx, "get users online status failed", errs.ErrRecordNotFound.WrapMsg("user not online"), "userID", userID)
|
||||
}
|
||||
}
|
||||
return &resp, nil
|
||||
|
||||
145
scripts/test/get_self_login_platforms_api_test.sh
Executable file
145
scripts/test/get_self_login_platforms_api_test.sh
Executable file
@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================
|
||||
# get_self_login_platforms 接口测试脚本
|
||||
#
|
||||
# 覆盖接口:
|
||||
# POST /auth/get_user_token
|
||||
# POST /user/get_self_login_platforms
|
||||
#
|
||||
# 说明:
|
||||
# 本脚本仅做 HTTP 接口测试,不建立 WS 连接。
|
||||
# ============================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HOST="${HOST:-http://127.0.0.1:10002}"
|
||||
USER_ID="${USER_ID:-5694418935}"
|
||||
PLATFORM_ID="${PLATFORM_ID:-2}"
|
||||
ADMIN_TOKEN="${ADMIN_TOKEN:-}"
|
||||
OPENIM_SECRET="${OPENIM_SECRET:-openIM123}"
|
||||
ADMIN_USER_ID="${ADMIN_USER_ID:-imAdmin}"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--host) HOST="$2"; shift 2 ;;
|
||||
--user-id) USER_ID="$2"; shift 2 ;;
|
||||
--platform-id) PLATFORM_ID="$2"; shift 2 ;;
|
||||
*)
|
||||
echo "未知参数: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
need_cmd() {
|
||||
command -v "$1" >/dev/null 2>&1 || {
|
||||
echo "缺少依赖命令: $1"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
need_cmd curl
|
||||
need_cmd jq
|
||||
|
||||
op_id() {
|
||||
echo "self-login-platforms-test-$$-$(date +%s%N)"
|
||||
}
|
||||
|
||||
get_admin_token() {
|
||||
local uid body resp token last_resp
|
||||
local -a candidates=("${ADMIN_USER_ID}" "openIM123456" "imAdmin")
|
||||
last_resp=""
|
||||
|
||||
for uid in "${candidates[@]}"; do
|
||||
body="{\"secret\":\"${OPENIM_SECRET}\",\"userID\":\"${uid}\"}"
|
||||
resp="$(curl -sS -X POST "${HOST}/auth/get_admin_token" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "operationID: $(op_id)" \
|
||||
-d "$body")"
|
||||
last_resp="$resp"
|
||||
|
||||
token="$(python3 - <<'PY' "$resp"
|
||||
import json
|
||||
import sys
|
||||
|
||||
raw = sys.argv[1]
|
||||
try:
|
||||
obj = json.loads(raw)
|
||||
except Exception:
|
||||
print("")
|
||||
raise SystemExit(0)
|
||||
|
||||
token = ""
|
||||
if isinstance(obj, dict):
|
||||
data = obj.get("data")
|
||||
if isinstance(data, dict):
|
||||
token = data.get("token") or data.get("Token") or ""
|
||||
if not token:
|
||||
token = obj.get("token") or obj.get("Token") or ""
|
||||
print(token)
|
||||
PY
|
||||
)"
|
||||
if [[ -n "$token" ]]; then
|
||||
echo "自动获取管理员 token 成功,userID=${uid}" >&2
|
||||
printf '%s' "$token"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "get_admin_token raw response: $last_resp" >&2
|
||||
echo "自动获取管理员 token 失败,请检查 HOST/OPENIM_SECRET/ADMIN_USER_ID 或直接设置 ADMIN_TOKEN" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [[ -z "${ADMIN_TOKEN}" ]]; then
|
||||
echo "==> 1) ADMIN_TOKEN 未设置,尝试自动获取管理员 token"
|
||||
ADMIN_TOKEN="$(get_admin_token)"
|
||||
fi
|
||||
|
||||
echo "==> 2) 获取用户 token"
|
||||
TOKEN_RESP=$(curl -sS -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "operationID: $(op_id)" \
|
||||
-H "token: ${ADMIN_TOKEN}" \
|
||||
-d "{\"userID\":\"${USER_ID}\",\"platformID\":${PLATFORM_ID}}" \
|
||||
"${HOST}/auth/get_user_token")
|
||||
|
||||
ERR_CODE=$(echo "${TOKEN_RESP}" | jq -r '.errCode // "null"')
|
||||
if [[ "${ERR_CODE}" != "0" ]]; then
|
||||
echo "获取 token 失败: ${TOKEN_RESP}"
|
||||
exit 1
|
||||
fi
|
||||
TOKEN=$(echo "${TOKEN_RESP}" | jq -r '.data.token // empty')
|
||||
if [[ -z "${TOKEN}" ]]; then
|
||||
echo "token 为空: ${TOKEN_RESP}"
|
||||
exit 1
|
||||
fi
|
||||
echo "token 获取成功, userID=${USER_ID}"
|
||||
|
||||
echo "==> 3) 调用 /user/get_self_login_platforms"
|
||||
RESP=$(curl -sS -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "operationID: $(op_id)" \
|
||||
-H "token: ${TOKEN}" \
|
||||
-d '{}' \
|
||||
"${HOST}/user/get_self_login_platforms")
|
||||
|
||||
echo "原始响应: ${RESP}"
|
||||
ERR_CODE=$(echo "${RESP}" | jq -r '.errCode // "null"')
|
||||
if [[ "${ERR_CODE}" != "0" ]]; then
|
||||
echo "接口调用失败: ${RESP}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "==> 4) 校验响应结构"
|
||||
DATA_TYPE=$(echo "${RESP}" | jq -r '(.data | type) // "null"')
|
||||
if [[ "${DATA_TYPE}" != "array" ]]; then
|
||||
echo "返回 data 不是数组: ${RESP}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "结构校验通过(data 为数组)"
|
||||
echo "返回 data:"
|
||||
echo "${RESP}" | jq '.data'
|
||||
|
||||
echo "测试通过: get_self_login_platforms"
|
||||
Loading…
x
Reference in New Issue
Block a user