mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-06-29 06:20:32 +08:00
commit
afc64057d9
82
internal/api/phone_sn.go
Normal file
82
internal/api/phone_sn.go
Normal file
@ -0,0 +1,82 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/tools/apiresp"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
)
|
||||
|
||||
type PhoneSNApi struct {
|
||||
db database.PhoneSN
|
||||
}
|
||||
|
||||
func NewPhoneSNApi(db database.PhoneSN) *PhoneSNApi {
|
||||
return &PhoneSNApi{db: db}
|
||||
}
|
||||
|
||||
type phoneGetSNInfoReq struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
}
|
||||
|
||||
type phoneGetSNInfoResp struct {
|
||||
IsSnd bool `json:"is_snd"`
|
||||
UserID int64 `json:"userID"`
|
||||
}
|
||||
|
||||
type phoneSetSNInfoReq struct {
|
||||
Phone string `json:"phone" binding:"required"`
|
||||
UserID int64 `json:"userID"`
|
||||
IsSnd bool `json:"is_snd"`
|
||||
}
|
||||
|
||||
// GetSNInfo POST /phone/get_sn_info
|
||||
func (a *PhoneSNApi) GetSNInfo(c *gin.Context) {
|
||||
var req phoneGetSNInfoReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
apiresp.GinError(c, errs.ErrArgs.WrapMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
phone := strings.TrimSpace(req.Phone)
|
||||
if phone == "" {
|
||||
apiresp.GinError(c, errs.ErrArgs.WrapMsg("phone is empty"))
|
||||
return
|
||||
}
|
||||
info, err := a.db.GetByPhone(c, phone)
|
||||
if err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
resp := phoneGetSNInfoResp{IsSnd: false, UserID: 0}
|
||||
if info != nil {
|
||||
resp.IsSnd = info.IsSnd
|
||||
resp.UserID = info.UserID
|
||||
}
|
||||
apiresp.GinSuccess(c, resp)
|
||||
}
|
||||
|
||||
// SetSNInfo POST /phone/set_sn_info
|
||||
func (a *PhoneSNApi) SetSNInfo(c *gin.Context) {
|
||||
var req phoneSetSNInfoReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
apiresp.GinError(c, errs.ErrArgs.WrapMsg(err.Error()))
|
||||
return
|
||||
}
|
||||
phone := strings.TrimSpace(req.Phone)
|
||||
if phone == "" {
|
||||
apiresp.GinError(c, errs.ErrArgs.WrapMsg("phone is empty"))
|
||||
return
|
||||
}
|
||||
if err := a.db.Upsert(c, phone, req.UserID, req.IsSnd); err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
apiresp.GinSuccess(c, nil)
|
||||
}
|
||||
@ -70,6 +70,10 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phoneSNDB, err := mgo.NewPhoneSNMongo(mgocli.GetDB())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blacklistCtrl := controller.NewUserGlobalBlackDatabase(userGlobalBlackDB)
|
||||
|
||||
authConn, err := client.GetConn(ctx, config.Share.RpcRegisterName.Auth)
|
||||
@ -127,6 +131,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
|
||||
m := NewMessageApi(msg.NewMsgClient(msgConn), rpcli.NewUserClient(userConn), config.Share.IMAdminUserID)
|
||||
cp := NewCaptchaApi(pbcaptcha.NewCaptchaClient(captchaConn))
|
||||
bl := NewUserGlobalBlackApi(blacklistCtrl, userDB, config.Share.IMAdminUserID, rpcli.NewAuthClient(authConn))
|
||||
phoneSN := NewPhoneSNApi(phoneSNDB)
|
||||
userRouterGroup := r.Group("/user")
|
||||
{
|
||||
userRouterGroup.POST("/user_register", u.UserRegister)
|
||||
@ -307,6 +312,11 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, co
|
||||
}
|
||||
|
||||
{
|
||||
phoneGroup := r.Group("/phone")
|
||||
phoneGroup.POST("/get_sn_info", phoneSN.GetSNInfo)
|
||||
phoneGroup.POST("/set_sn_info", phoneSN.SetSNInfo)
|
||||
}
|
||||
{
|
||||
rc := NewRtcApi(rtc.NewRtcServiceClient(rtcConn))
|
||||
rtcGroup := r.Group("/rtc")
|
||||
rtcGroup.POST("/signal_message_assemble", rc.SignalMessageAssemble)
|
||||
@ -390,4 +400,5 @@ var Whitelist = []string{
|
||||
"/auth/get_admin_token",
|
||||
"/auth/parse_token",
|
||||
"/captcha",
|
||||
"/phone/get_sn_info",
|
||||
}
|
||||
|
||||
69
pkg/common/storage/database/mgo/phone_sn.go
Normal file
69
pkg/common/storage/database/mgo/phone_sn.go
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
package mgo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
func NewPhoneSNMongo(db *mongo.Database) (database.PhoneSN, error) {
|
||||
coll := db.Collection(database.PhoneSNInfoName)
|
||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
Keys: bson.D{{Key: "phone", Value: 1}},
|
||||
Options: options.Index().SetUnique(true),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
return &phoneSNMgo{coll: coll}, nil
|
||||
}
|
||||
|
||||
type phoneSNMgo struct {
|
||||
coll *mongo.Collection
|
||||
}
|
||||
|
||||
func (p *phoneSNMgo) GetByPhone(ctx context.Context, phone string) (*model.PhoneSNInfo, error) {
|
||||
if phone == "" {
|
||||
return nil, nil
|
||||
}
|
||||
doc, err := mongoutil.FindOne[*model.PhoneSNInfo](ctx, p.coll, bson.M{"phone": phone})
|
||||
if err != nil {
|
||||
if errs.ErrRecordNotFound.Is(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
func (p *phoneSNMgo) Upsert(ctx context.Context, phone string, userID int64, isSnd bool) error {
|
||||
if phone == "" {
|
||||
return errs.ErrArgs.WrapMsg("phone is empty")
|
||||
}
|
||||
now := time.Now().UnixMilli()
|
||||
filter := bson.M{"phone": phone}
|
||||
setDoc := bson.M{
|
||||
"is_snd": isSnd,
|
||||
"user_id": userID,
|
||||
"update_time": now,
|
||||
}
|
||||
update := bson.M{
|
||||
"$set": setDoc,
|
||||
"$setOnInsert": bson.M{"phone": phone},
|
||||
}
|
||||
opts := options.Update().SetUpsert(true)
|
||||
_, err := p.coll.UpdateOne(ctx, filter, update, opts)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
@ -18,6 +18,7 @@ const (
|
||||
SeqConversationName = "seq"
|
||||
SeqUserName = "seq_user"
|
||||
UserGlobalBlackName = "user_global_black_list"
|
||||
PhoneSNInfoName = "phone_sn_info"
|
||||
SignalInvitationName = "signal_invitation"
|
||||
SignalRecordName = "signal_record"
|
||||
)
|
||||
|
||||
20
pkg/common/storage/database/phone_sn.go
Normal file
20
pkg/common/storage/database/phone_sn.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
)
|
||||
|
||||
// PhoneSN 手机号 is_snd 持久化
|
||||
type PhoneSN interface {
|
||||
// GetByPhone 按手机号查询;无记录时返回 (nil, nil)
|
||||
GetByPhone(ctx context.Context, phone string) (*model.PhoneSNInfo, error)
|
||||
// Upsert 写入或更新 is_snd 与 user_id
|
||||
Upsert(ctx context.Context, phone string, userID int64, isSnd bool) error
|
||||
}
|
||||
14
pkg/common/storage/model/phone_sn.go
Normal file
14
pkg/common/storage/model/phone_sn.go
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright © 2024 OpenIM. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
|
||||
package model
|
||||
|
||||
// PhoneSNInfo 手机号与 is_snd、关联 user_id(每条以 phone 唯一)
|
||||
type PhoneSNInfo struct {
|
||||
Phone string `bson:"phone"`
|
||||
UserID int64 `bson:"user_id"`
|
||||
IsSnd bool `bson:"is_snd"`
|
||||
UpdateTime int64 `bson:"update_time"`
|
||||
}
|
||||
104
scripts/phone_sn_api.sh
Executable file
104
scripts/phone_sn_api.sh
Executable file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# 测试 open-im-server 手机号 is_snd 接口(/phone/get_sn_info、/phone/set_sn_info)
|
||||
#
|
||||
# 用法:
|
||||
# 查询(无需 token,已加入 GinParseToken 白名单):
|
||||
# ./scripts/phone_sn_api.sh get <phone>
|
||||
#
|
||||
# 写入(需要用户 token):
|
||||
# OPENIM_TOKEN="<用户 token>" ./scripts/phone_sn_api.sh set <phone> <userID整数> <is_snd:0|1|true|false>
|
||||
#
|
||||
# 环境变量(可覆盖):
|
||||
# OPENIM_API_ADDR 默认 http://127.0.0.1:10002
|
||||
# OPENIM_TOKEN set 时必填(Header: token)
|
||||
# OPERATION_ID 默认自动生成
|
||||
|
||||
OPENIM_API_ADDR="${OPENIM_API_ADDR:-http://127.0.0.1:10002}"
|
||||
OPENIM_TOKEN="${OPENIM_TOKEN:-}"
|
||||
OPERATION_ID="${OPERATION_ID:-phone_sn_$(date +%s)_$RANDOM}"
|
||||
|
||||
ACTION="${1:-}"
|
||||
PHONE="${2:-}"
|
||||
USER_ID="${3:-}"
|
||||
IS_SND_RAW="${4:-}"
|
||||
|
||||
die() {
|
||||
echo "ERROR: $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
查询(无需 token):
|
||||
./scripts/phone_sn_api.sh get <phone>
|
||||
|
||||
写入(需要 OPENIM_TOKEN):
|
||||
OPENIM_TOKEN="<用户token>" ./scripts/phone_sn_api.sh set <phone> <userID整数> <is_snd:0|1|true|false>
|
||||
|
||||
环境变量:
|
||||
OPENIM_API_ADDR 默认 http://127.0.0.1:10002
|
||||
OPENIM_TOKEN set 时必填
|
||||
OPERATION_ID 可选,默认自动生成
|
||||
EOF
|
||||
}
|
||||
|
||||
curl_post() {
|
||||
local path=$1
|
||||
local json_body=$2
|
||||
local with_token=${3:-0}
|
||||
local -a hdrs=(
|
||||
-H "Content-Type: application/json"
|
||||
-H "operationID: ${OPERATION_ID}"
|
||||
)
|
||||
if [[ "$with_token" == "1" ]]; then
|
||||
[[ -n "$OPENIM_TOKEN" ]] || die "set 接口需要环境变量 OPENIM_TOKEN(用户 token)"
|
||||
hdrs+=(-H "token: ${OPENIM_TOKEN}")
|
||||
fi
|
||||
curl -sS "${hdrs[@]}" -X POST "${OPENIM_API_ADDR}${path}" -d "${json_body}"
|
||||
}
|
||||
|
||||
pretty_json() {
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
jq .
|
||||
else
|
||||
cat
|
||||
fi
|
||||
}
|
||||
|
||||
case "$ACTION" in
|
||||
get)
|
||||
[[ -n "$PHONE" ]] || die "用法: $0 get <phone>"
|
||||
body=$(printf '{"phone":"%s"}' "$PHONE")
|
||||
echo "==> POST ${OPENIM_API_ADDR}/phone/get_sn_info"
|
||||
echo " body: ${body}"
|
||||
resp=$(curl_post "/phone/get_sn_info" "$body" 0)
|
||||
echo "$resp" | pretty_json
|
||||
;;
|
||||
set)
|
||||
[[ -n "$PHONE" && -n "$USER_ID" && -n "$IS_SND_RAW" ]] || die "用法: $0 set <phone> <userID> <is_snd:0|1|true|false>"
|
||||
case "$IS_SND_RAW" in
|
||||
1|true|True|TRUE) is_snd=true ;;
|
||||
0|false|False|FALSE) is_snd=false ;;
|
||||
*) die "is_snd 必须是 0、1、true 或 false" ;;
|
||||
esac
|
||||
# userID 必须为 JSON 数字
|
||||
if ! [[ "$USER_ID" =~ ^-?[0-9]+$ ]]; then
|
||||
die "userID 必须为整数"
|
||||
fi
|
||||
body=$(printf '{"phone":"%s","userID":%s,"is_snd":%s}' "$PHONE" "$USER_ID" "$is_snd")
|
||||
echo "==> POST ${OPENIM_API_ADDR}/phone/set_sn_info"
|
||||
echo " body: ${body}"
|
||||
resp=$(curl_post "/phone/set_sn_info" "$body" 1)
|
||||
echo "$resp" | pretty_json
|
||||
;;
|
||||
""|-h|--help|help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
die "未知动作: $ACTION,使用 -h 查看帮助"
|
||||
;;
|
||||
esac
|
||||
Loading…
x
Reference in New Issue
Block a user