查看自己登录的在线设备

pull/3727/head
hawklin2017 2 months ago
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

@ -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…
Cancel
Save