mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-07-15 10:31:16 +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", u.GetUsers)
|
||||||
userRouterGroup.POST("/get_users_online_status", u.GetUsersOnlineStatus)
|
userRouterGroup.POST("/get_users_online_status", u.GetUsersOnlineStatus)
|
||||||
userRouterGroup.POST("/get_users_online_token_detail", u.GetUsersOnlineTokenDetail)
|
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("/subscribe_users_status", u.SubscriberStatus)
|
||||||
userRouterGroup.POST("/get_users_status", u.GetUserStatus)
|
userRouterGroup.POST("/get_users_status", u.GetUserStatus)
|
||||||
userRouterGroup.POST("/get_subscribe_users_status", u.GetSubscribeUsersStatus)
|
userRouterGroup.POST("/get_subscribe_users_status", u.GetSubscribeUsersStatus)
|
||||||
|
|||||||
@ -33,6 +33,16 @@ type UserApi struct {
|
|||||||
config config.RpcRegisterName
|
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 {
|
func NewUserApi(client user.UserClient, discov discovery.SvcDiscoveryRegistry, config config.RpcRegisterName) UserApi {
|
||||||
return UserApi{Client: client, discov: discov, config: config}
|
return UserApi{Client: client, discov: discov, config: config}
|
||||||
}
|
}
|
||||||
@ -191,6 +201,59 @@ func (u *UserApi) GetUsersOnlineTokenDetail(c *gin.Context) {
|
|||||||
apiresp.GinSuccess(c, respResult)
|
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.
|
// SubscriberStatus Presence status of subscribed users.
|
||||||
func (u *UserApi) SubscriberStatus(c *gin.Context) {
|
func (u *UserApi) SubscriberStatus(c *gin.Context) {
|
||||||
a2r.Call(c, user.UserClient.SubscribeOrCancelUsersStatus, u.Client)
|
a2r.Call(c, user.UserClient.SubscribeOrCancelUsersStatus, u.Client)
|
||||||
|
|||||||
@ -68,6 +68,10 @@ type Client struct {
|
|||||||
UserID string `json:"userID"`
|
UserID string `json:"userID"`
|
||||||
IsBackground bool `json:"isBackground"`
|
IsBackground bool `json:"isBackground"`
|
||||||
SDKType string `json:"sdkType"`
|
SDKType string `json:"sdkType"`
|
||||||
|
SDKVersion string `json:"sdkVersion"`
|
||||||
|
DeviceName string `json:"deviceName"`
|
||||||
|
DeviceModel string `json:"deviceModel"`
|
||||||
|
LoginTimestamp int64 `json:"loginTimestamp"`
|
||||||
Encoder Encoder
|
Encoder Encoder
|
||||||
ctx *UserConnContext
|
ctx *UserConnContext
|
||||||
longConnServer LongConnServer
|
longConnServer LongConnServer
|
||||||
@ -95,6 +99,10 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn ClientConn, longConnServ
|
|||||||
c.closedErr = nil
|
c.closedErr = nil
|
||||||
c.token = ctx.GetToken()
|
c.token = ctx.GetToken()
|
||||||
c.SDKType = ctx.GetSDKType()
|
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.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
|
||||||
c.subLock = new(sync.Mutex)
|
c.subLock = new(sync.Mutex)
|
||||||
if c.subUserIDs != nil {
|
if c.subUserIDs != nil {
|
||||||
|
|||||||
@ -28,6 +28,9 @@ const (
|
|||||||
BackgroundStatus = "isBackground"
|
BackgroundStatus = "isBackground"
|
||||||
SendResponse = "isMsgResp"
|
SendResponse = "isMsgResp"
|
||||||
SDKType = "sdkType"
|
SDKType = "sdkType"
|
||||||
|
DeviceName = "deviceName"
|
||||||
|
DeviceModel = "deviceModel"
|
||||||
|
SDKVersion = "sdkVersion"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@ -38,6 +38,9 @@ type UserConnContextInfo struct {
|
|||||||
SDKType string `json:"sdkType"`
|
SDKType string `json:"sdkType"`
|
||||||
SendResponse bool `json:"sendResponse"`
|
SendResponse bool `json:"sendResponse"`
|
||||||
Background bool `json:"background"`
|
Background bool `json:"background"`
|
||||||
|
DeviceName string `json:"deviceName"`
|
||||||
|
DeviceModel string `json:"deviceModel"`
|
||||||
|
SDKVersion string `json:"sdkVersion"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserConnContext struct {
|
type UserConnContext struct {
|
||||||
@ -117,6 +120,9 @@ func (c *UserConnContext) parseByQuery(query url.Values, header http.Header) err
|
|||||||
OperationID: query.Get(OperationID),
|
OperationID: query.Get(OperationID),
|
||||||
Compression: query.Get(Compression),
|
Compression: query.Get(Compression),
|
||||||
SDKType: query.Get(SDKType),
|
SDKType: query.Get(SDKType),
|
||||||
|
DeviceName: query.Get(DeviceName),
|
||||||
|
DeviceModel: query.Get(DeviceModel),
|
||||||
|
SDKVersion: query.Get(SDKVersion),
|
||||||
}
|
}
|
||||||
platformID, err := strconv.Atoi(query.Get(PlatformID))
|
platformID, err := strconv.Atoi(query.Get(PlatformID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -260,3 +266,24 @@ func (c *UserConnContext) SetToken(token string) {
|
|||||||
func (c *UserConnContext) GetBackground() bool {
|
func (c *UserConnContext) GetBackground() bool {
|
||||||
return c != nil && c.info != nil && c.info.Background
|
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) {
|
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")
|
return nil, errs.ErrNoPermission.WrapMsg("only app manager")
|
||||||
}
|
}
|
||||||
|
log.ZDebug(ctx, "GetUsersOnlineStatus", "userIDs", req.UserIDs)
|
||||||
var resp msggateway.GetUsersOnlineStatusResp
|
var resp msggateway.GetUsersOnlineStatusResp
|
||||||
for _, userID := range req.UserIDs {
|
for _, userID := range req.UserIDs {
|
||||||
clients, ok := s.LongConnServer.GetUserAllCons(userID)
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +113,7 @@ func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUs
|
|||||||
uresp.UserID = userID
|
uresp.UserID = userID
|
||||||
for _, client := range clients {
|
for _, client := range clients {
|
||||||
if client == nil {
|
if client == nil {
|
||||||
|
log.ZWarn(ctx, "get users online status failed", errs.ErrRecordNotFound.WrapMsg("user client is nil"), "userID", userID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,11 +122,17 @@ func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUs
|
|||||||
ps.ConnID = client.ctx.GetConnID()
|
ps.ConnID = client.ctx.GetConnID()
|
||||||
ps.Token = client.token
|
ps.Token = client.token
|
||||||
ps.IsBackground = client.IsBackground
|
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.Status = constant.Online
|
||||||
uresp.DetailPlatformStatus = append(uresp.DetailPlatformStatus, ps)
|
uresp.DetailPlatformStatus = append(uresp.DetailPlatformStatus, ps)
|
||||||
}
|
}
|
||||||
if uresp.Status == constant.Online {
|
if uresp.Status == constant.Online {
|
||||||
resp.SuccessResult = append(resp.SuccessResult, uresp)
|
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
|
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