Documentation
¶
Index ¶
- Constants
- Variables
- func CheckPassword(password, storedHash string) (matched bool, needsMigration bool)
- func HashPassword(password string) (string, error)
- func InitGlobalPinnedDB(ctx *config.Context)
- func QueryVerificationsByUIDs(ctx *config.Context, uids []string) (map[string]*VerificationInfo, error)
- func RegisterBotFriendApplyHook(hook BotFriendApplyHook)
- func RegisterGroupMemberChecker(fn GroupMemberCheckFunc)
- func RegisterGroupMemberExternalProvider(fn GroupMemberExternalProvider)
- func RemovePinnedForChannel(channelID string, channelType uint8)
- func RemovePinnedForUser(uid, channelID string, channelType uint8)
- func RemovePinnedForUserInSpace(uid, spaceID, channelID string, channelType uint8)
- func ResolveWebhookDisplayName(ctx *config.Context, uid string) (string, error)
- func SendQRCodeInfo(uuid string, qrcode *common.QRCodeModel)
- func SetAppBotResolver(fn AppBotResolverFunc)
- func ValidateName(name string) error
- type AddUserReq
- type AppBotResolverFunc
- type BlacklistModel
- type BotFriendApplyHook
- type Category
- type DB
- func (d *DB) AddOrRemoveBlacklistTx(uid string, touid string, blacklist int, version int64, tx *dbr.Tx) error
- func (d *DB) Blacklists(uid string) ([]*BlacklistModel, error)
- func (d *DB) Insert(m *Model) error
- func (d *DB) QueryByCategory(category string) ([]*Model, error)
- func (d *DB) QueryByEmail(email string) (*Model, error)
- func (d *DB) QueryByKeyword(keyword string) (*Model, error)
- func (d *DB) QueryByPhone(zone string, phone string) (*Model, error)
- func (d *DB) QueryByPhones(phones []string) ([]*Model, error)
- func (d *DB) QueryByUID(uid string) (*Model, error)
- func (d *DB) QueryByUIDs(uids []string) ([]*Model, error)
- func (d *DB) QueryByUsername(username string) (*Model, error)
- func (d *DB) QueryByUsernameCxt(ctx context.Context, username string) (*Model, error)
- func (d *DB) QueryByVercode(vercode string) (*Model, error)
- func (d *DB) QueryDetailByUID(uid string, loginUID string) (*Detail, error)
- func (d *DB) QueryDetailByUIDs(uids []string, loginUID string) ([]*Detail, error)
- func (d *DB) QueryLanguageByUID(uid string) (string, error)
- func (d *DB) QueryRoleByUID(uid string) (string, error)
- func (d *DB) QueryUIDsByUsernames(usernames []string) ([]string, error)
- func (d *DB) QueryUserWithOnlyShortNo(shortNo string) (*Model, error)
- func (d *DB) QueryWithAppID(appID string) ([]*Model, error)
- func (d *DB) UpdateAvatarUploadStatus(uid string, avatarVersion int64) error
- func (d *DB) UpdateLanguageByUID(uid, language string) error
- func (d *DB) UpdateUsersWithField(field string, value string, uid string) error
- func (d *DB) UpdateUsersWithFieldTx(field string, value string, uid string, tx *dbr.Tx) error
- type Detail
- type DetailModel
- type DeviceInfo
- type ExternalLoginReq
- type ExternalLoginResp
- type Friend
- type FriendApplyModel
- type FriendDetailModel
- type FriendModel
- type FriendReq
- type FriendResp
- type GroupMemberCheckFunc
- type GroupMemberExternalProvider
- type GroupMemberResp
- type IOnlineService
- type IService
- type LanguageService
- type LoginGuard
- type LoginLog
- type LoginLogDB
- type LoginLogModel
- type Manager
- type Model
- type OIDCVerificationClaims
- type OnLineUserResp
- type OnlineService
- type Pinned
- type PinnedChannelModel
- type PinnedDB
- func (d *PinnedDB) Add(uid, spaceID, channelID string, channelType uint8, maxLimit int) error
- func (d *PinnedDB) AreSpaceMembers(spaceID, uid1, uid2 string) (bool, error)
- func (d *PinnedDB) List(uid, spaceID string) ([]*PinnedChannelModel, error)
- func (d *PinnedDB) QueryPeerRobotInfo(peerUID string) (isRobot bool, creatorUID string, err error)
- func (d *PinnedDB) Remove(uid, spaceID, channelID string, channelType uint8) error
- func (d *PinnedDB) RemoveByChannel(channelID string, channelType uint8) error
- func (d *PinnedDB) RemoveByUIDAndChannel(uid, channelID string, channelType uint8) error
- func (d *PinnedDB) RemoveByUIDSpaceChannel(uid, spaceID, channelID string, channelType uint8) error
- func (d *PinnedDB) UpdateSort(uid, spaceID string, items []PinnedSortItem) error
- type PinnedSortError
- type PinnedSortItem
- type Resp
- type RoleService
- type Service
- func (s *Service) AddFriend(uid string, friend *FriendReq) error
- func (s *Service) AddUser(user *AddUserReq) error
- func (s *Service) AreSpaceMembers(spaceID, uid1, uid2 string) (bool, error)
- func (s *Service) ExistBlacklist(uid string, toUID string) (bool, error)
- func (s *Service) GetAllUserCount() (int64, error)
- func (s *Service) GetAllUsers() ([]*Resp, error)
- func (s *Service) GetDeviceOnline(uid string, deviceFlag config.DeviceFlag) (*config.OnlinestatusResp, error)
- func (s *Service) GetFriends(uid string) ([]*FriendResp, error)
- func (s *Service) GetFriendsWithToUIDs(uid string, toUIDs []string) ([]*FriendResp, error)
- func (s *Service) GetOnetimePrekeyCount(uid string) (int, error)
- func (s *Service) GetOnlineCount() (int64, error)
- func (s *Service) GetRegisterCountWithDateSpace(startDate, endDate string) (map[string]int64, error)
- func (s *Service) GetRegisterWithDate(date string) (int64, error)
- func (s *Service) GetUser(uid string) (*Resp, error)
- func (s *Service) GetUserDetail(uid string, loginUID string) (*UserDetailResp, error)
- func (s *Service) GetUserDetails(ctx context.Context, uids []string, loginUID string) ([]*UserDetailResp, error)
- func (s *Service) GetUserOnlineStatus(uids []string) ([]*OnLineUserResp, error)
- func (s *Service) GetUserSettings(uids []string, loginUID string) ([]*SettingResp, error)
- func (s *Service) GetUserUIDWithUsernames(usernames []string) ([]string, error)
- func (s *Service) GetUserWithQRVercode(qrVercode string) (*Resp, error)
- func (s *Service) GetUserWithUsername(username string) (*Resp, error)
- func (s *Service) GetUsers(uids []string) ([]*Resp, error)
- func (s *Service) GetUsersWithAppID(appID string) ([]*Resp, error)
- func (s *Service) GetUsersWithCategories(categories []string) ([]*Resp, error)
- func (s *Service) GetUsersWithCategory(category Category) ([]*Resp, error)
- func (s *Service) IsBindable(ctx context.Context, uid string) (bool, error)
- func (s *Service) IsFriend(uid string, toUID string) (bool, error)
- func (s *Service) LoginByExternalIdentity(ctx context.Context, req ExternalLoginReq) (*ExternalLoginResp, error)
- func (s *Service) QueryPeerRobotInfo(peerUID string) (bool, string, error)
- func (s *Service) SearchFriendsWithKeyword(uid string, keyword string) ([]*FriendResp, error)
- func (s *Service) SendOIDCBindSMS(ctx context.Context, zone, phone string) error
- func (s *Service) SetExternalLoginHandler(h externalLoginHandler)
- func (s *Service) SetOIDCBindHandler(h oidcBindHandler)
- func (s *Service) UpdateLoginPassword(req UpdateLoginPasswordReq) error
- func (s *Service) UpdateUser(req UserUpdateReq) error
- func (s *Service) UpdateUserMsgExpireSecond(uid string, msgExpireSecond int64) error
- func (s *Service) UpsertVerificationFromOIDC(ctx context.Context, uid string, claims OIDCVerificationClaims) error
- func (s *Service) VerifyOIDCBindSMS(ctx context.Context, zone, phone, code string) error
- func (s *Service) VerifyPasswordByUID(ctx context.Context, uid, password string) (bool, string, error)
- type Setting
- type SettingDB
- func (d *SettingDB) InsertUserSettingModel(setting *SettingModel) error
- func (d *SettingDB) InsertUserSettingModelTx(setting *SettingModel, tx *dbr.Tx) error
- func (d *SettingDB) QueryTwoUserSettingModel(uid, loginUID string) ([]*SettingModel, error)
- func (d *SettingDB) QueryUserSettingModel(uid, loginUID string) (*SettingModel, error)
- func (d *SettingDB) QueryUserSettings(uids []string, loginUID string) ([]*SettingModel, error)
- func (d *SettingDB) QueryWithUidsAndToUID(uids []string, toUID string) ([]*SettingModel, error)
- func (d *SettingDB) UpdateUserSettingModel(setting *SettingModel) error
- type SettingModel
- type SettingResp
- type SpaceSettingDB
- type SpaceSettingModel
- type Status
- type UpdateLoginPasswordReq
- type User
- func (u *User) GetFriendByVercode(vercode string) (*source.FriendModel, error)
- func (u *User) GetFriendByVercodes(vercodes []string) ([]*source.FriendModel, error)
- func (u *User) GetUserByMailListVercode(vercode string) (*source.UserModel, error)
- func (u *User) GetUserByQRVercode(qrvercode string) (*source.UserModel, error)
- func (u *User) GetUserByUID(uid string) (*source.UserModel, error)
- func (u *User) GetUserByVercode(vercode string) (*source.UserModel, error)
- func (u *User) IsBindable(_ context.Context, uid string) (bool, error)
- func (u *User) LoginByExternalIdentity(ctx context.Context, req ExternalLoginReq) (*ExternalLoginResp, error)
- func (u *User) Route(r *wkhttp.WKHttp)
- func (u *User) SendOIDCBindSMS(ctx context.Context, zone, phone string) error
- func (u *User) UserAvatar(c *wkhttp.Context)
- func (u *User) VerifyOIDCBindSMS(ctx context.Context, zone, phone, code string) error
- func (u *User) VerifyPasswordByUID(_ context.Context, uid, password string) (bool, string, error)
- type UserDetailResp
- type UserUpdateReq
- type VerificationInfo
Constants ¶
const ( // Web3VerifyLogin 校验登录 Web3VerifyLogin string = "login" // Web3VerifyPassword 校验密码 Web3VerifyPassword string = "password" )
const ( // CategoryCustomerService 客服 CategoryCustomerService = "customerService" // CategorySystem 系统账号 CategorySystem = "system" )
const ( IsDestroyNo = 0 IsDestroyApplying = 1 IsDestroyDone = 2 )
注销状态:0=正常,1=冷静期(可撤销),2=已注销(最终)
const ( BindReasonRateLimited = "rate_limited" // loginGuard 阈值已到,本次未走密码比对 BindReasonUserNotFound = "user_not_found" // uid 不存在,前端应统一兜底"账号或密码错误"避免枚举 BindReasonPasswordMismatch = "password_mismatch" // 密码错误 )
VerifyPasswordByUID 返回的 reason 枚举。typed const 而非 magic string,既能 让调用方 switch-case 时被 govet 检出拼写错误,也作为"matched=false 时 reason 必非空"不变式的可枚举值集合。
const ( WebhookUIDPrefix = webhookUIDPrefix WebhookExtraAvatarKey = webhookExtraAvatarKey )
导出别名仅供 incomingwebhook 包的测试做跨包契约一致性校验(见其 display_test.go), 把"本地复制的常量"与"上层源头常量"在编译期/测试期绑定,防止任一侧改动后悄悄漂移。 生产代码不依赖这些导出。
const ( // CacheKeyFriends 好友key CacheKeyFriends string = "lm-friends:" )
const (
ChannelServiceName = "channel"
)
const LanguageCacheKeyPrefix = "user_language:"
LanguageCacheKeyPrefix is the Redis key prefix for the `user_language:{uid}` hot cache. Exported so external infra (e.g. ops scripts that need to invalidate a hot key) can construct the key without duplicating the literal.
const LanguageCacheTTL = 5 * time.Minute
LanguageCacheTTL bounds the staleness of cross-device language switches: after a PUT /v1/user/language the resolver writes through the cache, but if another node skipped that invalidation the next read converges within this window.
const RoleCacheKeyPrefix = "user_role:"
RoleCacheKeyPrefix is the Redis key prefix for the `user_role:{uid}` hot cache. Exported so ops scripts / role-mutation sites can invalidate the key without duplicating the literal.
const RoleCacheTTL = 60 * time.Second
RoleCacheTTL bounds how long a stale system role can survive after a privilege change. The token used to bake the role in for its full lifetime (days); resolving per request against this cache caps that staleness to the TTL while keeping the auth hot path off MySQL for the common case. Kept deliberately short because this gates admin / superAdmin access.
const (
ThirdAuthcodePrefix = "thirdlogin:authcode:"
)
const (
UserRedDotCategoryFriendApply = "friendApply"
)
Variables ¶
var ( ErrUserNeedVerification = errors.New("user need verification") // 用户需要验证 // ErrUserDisabled / ErrUserDeviceInfoRequired are execLogin's client-facing // sentinels so every login entry point (main / OAuth / email / username) can // classify them uniformly: a disabled account is 403, a missing device info // is 400 — not a generic 500. errors.Is on these at the call site (see // respondExecLoginError) keeps the classification in one place. ErrUserDisabled = errors.New("该用户已被禁用") ErrUserDeviceInfoRequired = errors.New("登录设备信息不能为空!") )
var ( ErrPinnedLimitExceeded = errors.New("置顶数量已达上限") ErrPinnedAlreadyExists = errors.New("该频道已置顶") )
错误定义
var ErrDestroyStateConflict = errors.New("destroy state conflict")
ErrDestroyStateConflict 表示目标 uid 不在期望的注销状态:通常意味着并发请求已抢先改写。 调用方需以「业务冲突」而非「服务端错误」对外呈现。
var ErrExternalLoginNotConfigured = errors.New("user: external login handler not configured")
ErrExternalLoginNotConfigured 外部登录未注入 handler(通常是单测中未走 user.New 完整初始化)
var ErrLoginLocked = errors.New("登录失败次数过多,账号已被临时锁定,请稍后再试")
ErrLoginLocked 表示账号因连续登录失败被临时锁定。
var ErrOIDCBindNotConfigured = errors.New("user: oidc bind handler not configured")
ErrOIDCBindNotConfigured OIDC 自助绑定 handler 未注入(同 ErrExternalLoginNotConfigured 的故障模式:测试或部署时未完整走 user.New 初始化)。
var ErrUnsupportedLanguage = errors.New("user: language not in supported matrix")
ErrUnsupportedLanguage is returned by SetLanguage when the input is a well-formed but unsupported BCP 47 tag (i.e. not in the configured supported-language matrix). Callers can errors.Is-match this to choose a 4xx user-facing response, separate from infra errors (DB down, etc.) that should surface as generic 5xx-ish failures.
var ErrorUserNotExist = errors.New("用户不存在!")
var Names = []string{}/* 182 elements not displayed */
Names 注册用户随机名字
Functions ¶
func CheckPassword ¶
CheckPassword 验证密码是否匹配存储的哈希值 同时支持 bcrypt 和旧版 MD5(MD5(password)) 格式 返回: matched(是否匹配), needsMigration(是否需要迁移到 bcrypt)
func InitGlobalPinnedDB ¶
InitGlobalPinnedDB 初始化全局 PinnedDB(在 user 模块初始化时调用)
func QueryVerificationsByUIDs ¶
func QueryVerificationsByUIDs(ctx *config.Context, uids []string) (map[string]*VerificationInfo, error)
QueryVerificationsByUIDs 批量查询 user_verification 表,返回 uid → *VerificationInfo。 无实名记录的 uid 不会出现在 map 里 —— 调用方把缺失视为未实名 (realname_verified=false),与 UserDetailResp / loginUserDetailResp 的 omitempty 语义一致。
实现走 verificationDB.QueryByUIDs(单次 `IN (?)` 查询),每批 1000 uid 以 verificationBatchSize 为上限避免超大群(成员上限 100000)一次性打爆 MySQL packet / planner。零 N+1:外部循环次数 = len(uids)/batch,与成员数线性相关, 每批一次 round trip,和 service.GetUserDetails 同模式。
func RegisterBotFriendApplyHook ¶
func RegisterBotFriendApplyHook(hook BotFriendApplyHook)
RegisterBotFriendApplyHook 注册机器人好友申请通知回调
func RegisterGroupMemberChecker ¶
func RegisterGroupMemberChecker(fn GroupMemberCheckFunc)
RegisterGroupMemberChecker 注册群成员检查函数(供 group 模块调用)
func RegisterGroupMemberExternalProvider ¶
func RegisterGroupMemberExternalProvider(fn GroupMemberExternalProvider)
RegisterGroupMemberExternalProvider 注册群成员外部性字段提供者(供 group 模块调用)。 仅在 init 阶段调用一次,后续并发读安全由 RWMutex 提供。
func RemovePinnedForChannel ¶
RemovePinnedForChannel 清理频道的所有置顶(供其他模块调用)
func RemovePinnedForUser ¶
RemovePinnedForUser 清理用户在所有 Space 下指定频道的置顶(供其他模块调用) 注意:此方法会跨 Space 删除,仅用于全局性操作(如删好友)
func RemovePinnedForUserInSpace ¶
RemovePinnedForUserInSpace 清理用户在指定 Space 指定频道的置顶(供其他模块调用)
func ResolveWebhookDisplayName ¶ added in v1.6.2
ResolveWebhookDisplayName 返回合成发送者(如 incoming webhook 的 iwh_xxx,user 表里 没有对应行)的展示名,通过 BussDataSource.ChannelGet 注册链解析。离线推送链路用它兜底 渲染发送者名,避免 webhook 消息推送出来没有发件人名字。
返回 ("", nil) 表示没有任何模块处理该 uid(不是 webhook,或已删除);("", err) 仅在 datasource 真实故障时返回。调用方应把空名当作"无兜底",把 err 当作可记录的非致命错误。
func SendQRCodeInfo ¶
func SendQRCodeInfo(uuid string, qrcode *common.QRCodeModel)
SendQRCodeInfo 发送二维码数据
func SetAppBotResolver ¶
func SetAppBotResolver(fn AppBotResolverFunc)
SetAppBotResolver registers the App Bot identity resolver.
func ValidateName ¶
ValidateName checks that a display name is non-blank and does not contain the @ character, which is used as delimiter in token cache entries (uid@name@role). Allowing @ in names would enable privilege escalation via role injection.
非空校验(需求模块3):去除空白、控制字符、零宽/格式字符后无可见内容则拒绝。 ValidateName 是昵称写入的统一守门(注册、改名、管理员建/改号都经过它), 第三方登录刻意绕开本函数,故此处加非空不影响第三方 nickname 为空的登录流程。
Types ¶
type AddUserReq ¶
type AddUserReq struct {
Name string
UID string // 如果无值,则随机生成
Username string
ShortNo string // 如果无值,则自动生成
Zone string
Phone string
Email string
Password string
Robot int // 机器人 0.否 1.是
}
AddUserReq AddUserReq
type AppBotResolverFunc ¶
AppBotResolverFunc resolves an App Bot UID to display name. Returns empty if not found.
type BlacklistModel ¶
type BlacklistModel struct {
UID string // 用户唯一id
Name string // 用户名称
Username string // 用户名
db.BaseModel
}
BlacklistModel 黑名单用户
type BotFriendApplyHook ¶
type BotFriendApplyHook func(applyUID, applyName, robotID, remark, token, spaceID string)
BotFriendApplyHook 机器人好友申请通知回调 botfather 模块注册这个回调来接收通知,避免循环依赖 spaceID: 申请来源 Space,用于隔离通知到正确的 Space
type DB ¶
type DB struct {
// contains filtered or unexported fields
}
DB 用户db操作
func (*DB) AddOrRemoveBlacklistTx ¶
func (d *DB) AddOrRemoveBlacklistTx(uid string, touid string, blacklist int, version int64, tx *dbr.Tx) error
AddOrRemoveBlacklist 添加黑名单
func (*DB) Blacklists ¶
func (d *DB) Blacklists(uid string) ([]*BlacklistModel, error)
Blacklists 黑名单列表
func (*DB) QueryByCategory ¶
QueryByCategory 根据用户分类查询用户列表
func (*DB) QueryByEmail ¶
QueryByEmail 通过邮箱查询用户信息
func (*DB) QueryByKeyword ¶
QueryByKeyword 通过用户名查询用户信息
func (*DB) QueryByPhone ¶
QueryByPhone 通过手机号和区号查询用户信息
func (*DB) QueryByUID ¶
QueryByUID 通过用户uid查询用户信息
func (*DB) QueryByUIDs ¶
QueryByUIDs 根据用户uid查询用户信息
func (*DB) QueryByUsername ¶
QueryByUsername 通过用户名查询用户信息 支持用户名、手机号(格式:zone-phone)、邮箱三种方式登录
func (*DB) QueryByUsernameCxt ¶
QueryByUsernameCxt 通过用户名查询用户信息
func (*DB) QueryByVercode ¶
QueryByVercode 通过用户vercode查询用户信息
func (*DB) QueryDetailByUID ¶
QueryDetailByUID 查询用户详情
func (*DB) QueryDetailByUIDs ¶
QueryDetailByUIDs 查询用户详情集合
func (*DB) QueryLanguageByUID ¶ added in v1.5.0
QueryLanguageByUID returns the user's language preference column only. 用作 LanguageService 的真相源读取——只 SELECT 单列以保持 hot path 轻量, 避免 5min TTL 的 Redis 缓存 miss 把整行 Model 拉到内存。 (uid 不存在时返回 ("", nil);空串语义为"未显式设置"。)
func (*DB) QueryRoleByUID ¶ added in v1.7.0
QueryRoleByUID returns the user's system role column only ("superAdmin" / "admin" / "" for a normal user). Used by RoleService as the authoritative source behind the per-request role resolver — only SELECTs the single column to keep the auth hot path light, mirroring QueryLanguageByUID. (uid 不存在时返回 ("", nil),语义即"无系统角色"。)
func (*DB) QueryUIDsByUsernames ¶
QueryUIDsByUsernames 通过用户名查询用户uids
func (*DB) QueryUserWithOnlyShortNo ¶
QueryUserWithOnlyShortNo 通过short_no获取用户信息
func (*DB) QueryWithAppID ¶
QueryWithAppID 根据appID查询用户列表
func (*DB) UpdateAvatarUploadStatus ¶ added in v1.6.0
UpdateAvatarUploadStatus marks a user avatar as uploaded and stores the server-side object-key version used to avoid CDN query-string cache-key dependencies.
func (*DB) UpdateLanguageByUID ¶ added in v1.5.0
UpdateLanguageByUID 持久化用户语言偏好。空字符串表示清空(回到 OCTO_DEFAULT_LANGUAGE 语义);调用方负责在入库前完成 BCP 47 校验 (通常走 LanguageService.SetLanguage),这里不做二次校验。
func (*DB) UpdateUsersWithField ¶
UpdateUsersWithField 修改用户基本资料
type Detail ¶
type Detail struct {
Model
Mute int // 免打扰
Top int // 置顶
ChatPwdOn int //是否开启聊天密码
Screenshot int //截屏通知
RevokeRemind int //撤回提醒
Receipt int //消息回执
db.BaseModel
}
Detail 详情
type DetailModel ¶
type DetailModel struct {
Remark string //好友备注
ToUID string // 好友uid
ToName string // 好友名字
ToCategory string // 用户分类
Mute int // 免打扰
Top int // 置顶
Version int64 // 版本
Vercode string // 验证码 加好友需要
IsDeleted int // 是否删除
IsAlone int // 是否为单项好友
ShortNo string //短编号
ChatPwdOn int // 是否开启聊天密码
Blacklist int //是否在黑名单
Receipt int //消息是否回执
Robot int // 机器人0.否1.是
db.BaseModel
}
DetailModel 好友详情
type DeviceInfo ¶
DeviceInfo 登录设备信息(外部模块用,与内部 deviceReq 解耦)
type ExternalLoginReq ¶
type ExternalLoginReq struct {
ExistingUID string
// UID 仅新建用户场景下使用,ExistingUID 非空时忽略。
UID string // 调用方生成的 UID(避免重复 GenerUUID 后还要再回传)
// Name 在两条路径都用:新建时写入 user.name;ExistingUID 非空时与库中
// user.name 比较,不同则同步覆盖(issue #1307)。两条路径都会做 @ → _ 消毒。
Name string
Email string
Phone string
Zone string
DeviceFlag config.DeviceFlag
Device *DeviceInfo
// PublicIP 用于欢迎消息日志,可空
PublicIP string
// TrustedSSOCreate 调用方声明本次新建用户的身份已由可信 IdP 完成认证,
// 且已经过 oidc 模块的 IssuerAllowlist 准入校验(详见
// modules/oidc/bind_service.go BindService.Create 与
// modules/oidc/api.go callback `res.IsNew` 分支)。
//
// 置 true 时本方法**绕过** common.SystemSettings.RegisterOff() 全局开关:
// register.off=1 主要用于阻断公开 email/手机号注册入口与 GitHub/Gitee OAuth
// 自助建号通道,这两条通道的身份来源都是不受 dmwork 控制的外部输入。
// OIDC 通道在本 PR 后由 DM_OIDC_PROVIDER_ALLOW_NEW_USER 与
// OCTO_OIDC_BIND_ALLOW_CREATE 独立控制(IssuerAllowlist 兜底),
// 语义上属于"可信 IdP 授权创建",不应受 register.off 影响。
//
// 仅 OIDC 模块在 callback `res.IsNew=true` / `/bind/create` 路径上置 true;
// 其他外部 IdP(GitHub / Gitee 等)留 false,保留原有 register.off 守护。
TrustedSSOCreate bool
}
ExternalLoginReq external IdP(OIDC / OAuth)登录入参。
ExistingUID 为空表示按 IdP 返回的 claims 新建本地用户;非空则按已知 UID 登录。 调用方(oidc 模块的 ResolveOrLink)负责完成 (issuer, sub) → uid 的解析与绑定, 这里只负责签发 DMWork 会话 token + 推 WuKongIM。
type ExternalLoginResp ¶
ExternalLoginResp 外部登录结果。
LoginRespJSON 是 loginUserDetailResp 序列化后的 JSON 字符串,可直接落到 ThirdAuthcode Redis 缓冲区供前端短码轮询取走;调用方无需关心其内部结构。
type FriendApplyModel ¶
type FriendApplyModel struct {
UID string
ToUID string
Remark string
Token string
Status int // 状态 0.未处理 1.通过 2.拒绝
db.BaseModel
}
FriendApplyModel 好友申请记录
type FriendDetailModel ¶
type FriendDetailModel struct {
FriendModel
Name string // 用户名称
ToName string //对方用户名称
}
FriendDetailModel 好友资料
type FriendModel ¶
type FriendModel struct {
UID string
ToUID string
Flag int
Version int64
IsDeleted int
IsAlone int // 是否为单项好友
Vercode string
SourceVercode string //来源验证码
Initiator int //1:发起方
db.BaseModel
}
FriendModel 好友对象
type FriendResp ¶
FriendResp 用户好友
type GroupMemberCheckFunc ¶
GroupMemberCheckFunc 检查用户是否为群成员的函数类型
type GroupMemberExternalProvider ¶
type GroupMemberExternalProvider func(groupNo, uid string) ( isExternal int, sourceSpaceID, sourceSpaceName string, homeSpaceID, homeSpaceName string, err error, )
GroupMemberExternalProvider 返回群成员的外部来源/归属 Space 字段 (对齐 group 模块 /groups/{no}/members 的 memberDetailResp 命名)。
返回值:
- isExternal: 1 表示外部成员;0 表示内部
- sourceSpaceID / sourceSpaceName: 来源 Space(仅外部成员非空)
- homeSpaceID / homeSpaceName: 相对视角归属 Space (外部 → source space;内部 → 群自身 space)
- err: 底层查询失败
当 groupNo/uid 为空或成员不存在时,返回全零值和 nil error。 由 group 模块在 init 阶段通过 RegisterGroupMemberExternalProvider 注入, 避免 user 模块反向依赖 group 包,保留单模块编译、按需启用的能力。
type GroupMemberResp ¶
type GroupMemberResp struct {
UID string `json:"uid"` // 成员uid
GroupNo string `json:"group_no"` // 群唯一编号
Name string `json:"name"` // 群成员名称
Remark string `json:"remark"` // 成员备注
Role int `json:"role"` // 成员角色
IsDeleted int `json:"is_deleted"` // 是否删除
Status int `json:"status"` //成员状态0:正常,2:黑名单
Vercode string `json:"vercode"` // 验证码
InviteUID string `json:"invite_uid"` // 邀请人
Robot int `json:"robot"` // 机器人
ForbiddenExpirTime int64 `json:"forbidden_expir_time"` // 禁言时长
// YUJ-206:透传群成员的外部来源/归属 Space 字段,命名与 /groups/{no}/members
// 的 memberDetailResp 保持一致,供 Web/Android/iOS UserInfo 判定"同 Space 非
// 好友 → 直接发消息" vs "跨 Space 外部成员 → 仅可在群内交流"。
// 后端保留 IsExternal / SourceSpaceID / SourceSpaceName 的绝对语义不变;
// HomeSpaceID / HomeSpaceName 是对齐企微相对视角的视图字段:
// 外部成员 (is_external == 1) → home_space_id = source_space_id
// 内部成员 → home_space_id = group.space_id
IsExternal int `json:"is_external"` // 是否外部成员 0/1
SourceSpaceID string `json:"source_space_id"` // 来源 Space ID(外部成员使用)
SourceSpaceName string `json:"source_space_name"` // 来源 Space 名称
HomeSpaceID string `json:"home_space_id"` // 成员归属 Space ID(相对视角)
HomeSpaceName string `json:"home_space_name"` // 成员归属 Space 名称
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type IOnlineService ¶
type IService ¶
type IService interface {
//获取用户
GetUser(uid string) (*Resp, error)
// 获取用户详情(包括与loginUID的关系等等)
GetUserDetail(uid string, loginUID string) (*UserDetailResp, error)
// 批量获取用户详情。ctx 用于按请求协商语言渲染 BotFather 命令菜单(#335),
// 无请求上下文的调用方传 context.Background() 即回退部署默认语言。
GetUserDetails(ctx context.Context, uids []string, loginUID string) ([]*UserDetailResp, error)
// 通过用户名获取用户
GetUserWithUsername(username string) (*Resp, error)
// 通过用户名获取用户uid集合
GetUserUIDWithUsernames(usernames []string) ([]string, error)
// 批量获取用户信息
GetUsers(uids []string) ([]*Resp, error)
// 通过APPID获取用户
GetUsersWithAppID(appID string) ([]*Resp, error)
// 获取用户集合
GetUsersWithCategory(category Category) ([]*Resp, error)
// 获取指定类别的用户列表
GetUsersWithCategories(categories []string) ([]*Resp, error)
//查询某个人好友
GetFriendsWithToUIDs(uid string, toUIDs []string) ([]*FriendResp, error)
//查询某个用户的所有好友
GetFriends(uid string) ([]*FriendResp, error)
//添加一个好友
AddFriend(uid string, friend *FriendReq) error
//添加一个用户
AddUser(user *AddUserReq) error
// 通过qrvercode获取用户信息
GetUserWithQRVercode(qrVercode string) (*Resp, error)
// 获取总用户数量
GetAllUserCount() (int64, error)
// 查询某天注册用户数
GetRegisterWithDate(date string) (int64, error)
// 获取某个时间区间的注册数量
GetRegisterCountWithDateSpace(startDate, endDate string) (map[string]int64, error)
// IsFriend 查询两个用户是否为好友关系
IsFriend(uid string, toUID string) (bool, error)
// 获取在线用户
GetUserOnlineStatus([]string) ([]*OnLineUserResp, error)
// 更新用户信息
UpdateUser(req UserUpdateReq) error
// 获取所有用户
GetAllUsers() ([]*Resp, error)
// 更新登录密码
UpdateLoginPassword(req UpdateLoginPasswordReq) error
// GetUserSettings 获取用户的配置
GetUserSettings(uids []string, loginUID string) ([]*SettingResp, error)
// GetOnetimePrekeyCount 获取用户一次性signal key的数量(决定是否可以开启加密通讯)
GetOnetimePrekeyCount(uid string) (int, error)
// 获取设备在线状态
GetDeviceOnline(uid string, deviceFlag config.DeviceFlag) (*config.OnlinestatusResp, error)
// 查询在线用户总数量
GetOnlineCount() (int64, error)
// 存在黑明单
ExistBlacklist(uid string, toUID string) (bool, error)
// QueryPeerRobotInfo 返回目标用户是否为 bot 及其创建者 UID。
// 实现委托给 PinnedDB(user/db_pinned.go),与置顶频道访问校验共用同一 SQL 真源。
// 用于 messages_search 等模块的 p2p 访问门禁区分本人 bot / 他人 bot / 真人。
QueryPeerRobotInfo(peerUID string) (isRobot bool, creatorUID string, err error)
// AreSpaceMembers 校验两个 uid 是否同属一个在籍 Space。
// 实现委托给 PinnedDB(user/db_pinned.go),底层调用 pkg/space.CheckBothMembers。
// 用于 Space 模式下放行同 Space 成员间的 p2p 交互(无需互加好友)。
AreSpaceMembers(spaceID, uid1, uid2 string) (bool, error)
// 更新用户消息过期时长
UpdateUserMsgExpireSecond(uid string, msgExpireSecond int64) error
// 搜索好友
SearchFriendsWithKeyword(uid string, keyword string) ([]*FriendResp, error)
// LoginByExternalIdentity 给外部 IdP(OIDC / OAuth)登录流程签发 DMWork 会话。
//
// 内部委托给 *User 实现,需要在模块初始化时通过 (*Service).SetExternalLoginHandler 注入。
// 未注入或调用方为 *Service 以外的 IService 实现时,返回 ErrExternalLoginNotConfigured。
LoginByExternalIdentity(ctx context.Context, req ExternalLoginReq) (*ExternalLoginResp, error)
// UpsertVerificationFromOIDC 基于 Aegis OIDC identity_verification scope
// 返回的 claims 写入 user_verification 表(YUJ-382 / Aegis OIDC Phase 1 直切)。
//
// 自 2026-05-10 起替代原先的 verify-service HMAC 回调链路;权威写入口从
// /v1/internal/verification/complete 转移到 oidc callback(登录时即写)。
//
// 语义:幂等 upsert,冲突按 user_id 主键全字段覆盖。调用方(oidc callback)
// 负责判断 IsVerified / LegalName 非空 — 本方法再做一次防御式校验,空则 no-op。
// verifiedProvider 必须在 allowlist 白名单内(cas/wecom/feishu),否则返错不写。
UpsertVerificationFromOIDC(ctx context.Context, uid string, claims OIDCVerificationClaims) error
// VerifyPasswordByUID 给 OIDC 自助绑定流程做账号密码二次验证(需求 FR-3.1)。
//
// 与 username 登录路径同款的 bcrypt/MD5 兼容 + loginGuard 反爆破,但走独立
// 计数维度 ("oidc-bind:"+uid),避免与登录失败计数互相串扰。已注销/封号账号
// 视为不可用。
//
// 三种返回组合(任一其它组合都是 bug,调用方可断言):
// - (true, "", nil) 密码正确且账号可用 → 推进绑定流程
// - (false, BindReason*, nil) 业务拒绝 → 计入审计,前端按 reason 显示文案
// **(false, "", nil) 视为非法返回**,调用方应当
// 以基础设施异常处理(等同 err != nil)
// - (false, "", err) 基础设施异常 / 调用方 contract 违反(如空 uid)
// → 前端走兜底页 + 运维告警,不计审计失败计数
//
// reason 取值见 BindReason* 常量。
//
// 内部委托给 *User 实现,需通过 (*Service).SetOIDCBindHandler 注入;
// 未注入时返回 ErrOIDCBindNotConfigured。
VerifyPasswordByUID(ctx context.Context, uid, password string) (matched bool, reason string, err error)
// SendOIDCBindSMS 向给定手机号发送 OIDC 绑定 OTP(需求 FR-3.3)。
//
// **调用方(oidc 模块)负责保证 zone/phone 来自 OIDC claims 的
// phone_number/phone_number_verified,不接受用户输入** —— 否则攻击者
// 可用自己手机绑别人 sub。本方法仅做信道分发,不做来源校验。
//
// keyspace 隔离边界(踩坑勘误):
// - 验证码本身 (CacheKeySMSCode) 按 codeType 分桶 —— OIDC bind 流程的
// OTP 不会被其他流程的验证码覆盖,也不会覆盖别人;
// - **但底层 SMSService 的"sms_rate_limit:zone@phone"(1min 发送频率)、
// "sms_verify_lock:zone@phone"(10min 失败锁定)、"sms_verify_fail:..."
// 三个 key 都不带 codeType,跨流程共享**。后果:
// a. 用户刚走过 register/forget-pwd SMS 流程,1min 内进 OIDC bind
// 会被 "发送过于频繁" 挡住;
// b. OIDC bind 路径连续输错 3 次,该手机号其他 SMS 流程一并被
// 锁 10min。
// - bind_token 维度的"OTP 发送 ≤ 3 次"(SR-2.1)由 oidc 模块的
// BindStore.IncrAndCheck 单独兜底,与本层无关。
SendOIDCBindSMS(ctx context.Context, zone, phone string) error
// VerifyOIDCBindSMS 与 SendOIDCBindSMS 对称,复用底层 SMSService.Verify 的
// 锁定/重试限制。**注意 lock/failCount key 同样不带 codeType,跨流程共享**,
// 详见 SendOIDCBindSMS 注释。
VerifyOIDCBindSMS(ctx context.Context, zone, phone, code string) error
// IsBindable 给 oidc bind Confirm 路径在 identity.Insert 之前再校验一次
// uid 仍可绑定。
//
// 必要性:locator/VerifyPasswordByUID 都只在 verify 阶段过滤
// (is_destroy + status<>0)。verify→confirm 之间有 5min 用户交互窗口,
// 期间运维可能 disable / 用户可能自助 destroy。若 confirm 不复核就 Insert,
// 会给一个已不可绑定的账号写入 user_oidc_identity 行;残留脏数据让该
// 用户后续 OIDC 登录走 (issuer, sub) autolink 命中 → IssueSession 拒绝 →
// 死循环登录失败,需要人工 DB 清理。
//
// 实务上 TOCTOU 窗口仍 ~毫秒级(本方法返 true 后到 identity.Insert 之间),
// DB 层 uk_uid_issuer + 登录路径的 status 检查兜底,但本方法把窗口从
// "用户交互级"压缩到"DB 单次 round-trip 级",符合纵深防御。
//
// 返回:
// - (true, nil):账号可绑定
// - (false, nil):账号不可绑定(已 destroy / 已停用 / 不存在),调用方按业务拒绝处理
// - (false, err):基础设施错误,调用方按 internal_error 兜底
IsBindable(ctx context.Context, uid string) (bool, error)
}
IService 用户服务接口
type LanguageService ¶ added in v1.5.0
type LanguageService struct {
// contains filtered or unexported fields
}
LanguageService resolves the authoritative language preference for a user. It satisfies both pkg/auth.LanguageResolver (consumed by CacheTokenParser) and pkg/i18n.UserLanguageResolver (documentation interface for the i18n contract).
Lookup order: Redis `user_language:{uid}` → DB `user.language` → "". DB results are written back to Redis with a negative marker for empty values so subsequent requests for users without a preference don't hammer MySQL. SetLanguage handles cross-device invalidation by deleting the hot key on writes; absent a delete the entry expires within LanguageCacheTTL.
func NewLanguageService ¶ added in v1.5.0
func NewLanguageService(db languageReader, c cache.Cache) *LanguageService
NewLanguageService constructs a LanguageService with the canonical 5-minute hot cache TTL. cache and db must be non-nil; nil is a programmer error and panics on construction rather than silently degrading at request time.
func (*LanguageService) Resolve ¶ added in v1.5.0
Resolve returns the user's language preference or "" if none is set. Errors are returned to the caller; pkg/auth.CacheTokenParser specifically keeps the token snapshot on resolver error so an outage doesn't 5xx authentication.
func (*LanguageService) SetLanguage ¶ added in v1.5.0
func (s *LanguageService) SetLanguage(ctx context.Context, uid, lang string) error
SetLanguage validates the incoming preference, persists it to the DB and invalidates the hot cache so the next read on any node observes the new value within one round-trip. The empty string is accepted and clears the preference (back to "use OCTO_DEFAULT_LANGUAGE" semantics).
The DB write is performed via the embedded reader's underlying *DB when available; tests that supply a stub reader without a writer get an explicit error rather than a silent drop.
type LoginGuard ¶
type LoginGuard struct {
// contains filtered or unexported fields
}
LoginGuard 为登录接口提供连续失败计数与临时锁定能力,防止暴力破解 / 撞库。
计数维度:按请求传入的 account(username / phone / email)归一化后作为 key, 未登录阶段无 uid,使用 account 能覆盖"用户不存在"的探测场景。
存储:Redis INCR + EXPIRE,首次失败时设置 TTL,TTL 过期后自动解锁。
func NewLoginGuard ¶
NewLoginGuard 创建 LoginGuard。threshold <=0 或 window <=0 时使用默认值。
func (*LoginGuard) Check ¶
func (g *LoginGuard) Check(account string) error
Check 若当前失败计数已达阈值,返回 ErrLoginLocked;否则返回 nil。 空 account 视为无效标识,直接放行(由上层业务字段校验兜底)。
故障语义:Redis 不可用时 fail-open(仅记一条 warn),避免单点故障造成全量登录瘫痪。 代价是 Redis 抖动期间短暂失去暴力破解防护,但全局 IP 限流仍在生效作为兜底。
func (*LoginGuard) RecordFailure ¶
func (g *LoginGuard) RecordFailure(account string) error
RecordFailure 失败次数 +1,每次都重设 TTL。
为什么每次都 Expire:INCR+EXPIRE 非原子,若首次 Incr 成功而 Expire 失败,key 将永不过期 导致账号永久锁定。每次 Expire 保证即使前几次 Expire 失败,后续失败也会修复 TTL。 语义副作用:计数窗口从"固定窗口"变为"滑动窗口"(攻击者持续尝试时窗口会续期), 这对防暴力破解反而更严格,符合安全预期。
func (*LoginGuard) RecordFailureLogged ¶
func (g *LoginGuard) RecordFailureLogged(account string)
RecordFailureLogged 包装 RecordFailure,失败时只 warn 不扩散错误,方便 handler 调用。
func (*LoginGuard) ResetLogged ¶
func (g *LoginGuard) ResetLogged(account string)
ResetLogged 包装 Reset,失败时只 warn。
type LoginLogDB ¶
type LoginLogDB struct {
// contains filtered or unexported fields
}
LoginLogDB 登录日志DB
type LoginLogModel ¶
LoginLogModel 登录日志
type Model ¶
type Model struct {
AppID string //app id
UID string // 用户唯一id
Name string // 用户名称
Username string // 用户名
Email string // email地址
Password string // 用户密码
Category string //用户分类
Sex int //性别
ShortNo string //唯一短编号
ShortStatus int //唯一短编号是否修改0.否1.是
Zone string //区号
Phone string //手机号
ChatPwd string //聊天密码
LockScreenPwd string // 锁屏密码
LockAfterMinute int // 在几分钟后锁屏 0表示立即
DeviceLock int //是否开启设备锁
SearchByPhone int //是否可以通过手机号搜索0.否1.是
SearchByShort int //是否可以通过短编号搜索0.否1.是
NewMsgNotice int //新消息通知0.否1.是
MsgShowDetail int //显示消息通知详情0.否1.是
VoiceOn int //声音0.否1.是
ShockOn int //震动0.否1.是
OfflineProtection int // 离线保护
Version int64
Status int // 状态 0.禁用 1.启用
Vercode string //验证码
QRVercode string // 二维码验证码
IsUploadAvatar int // 是否上传过头像0:未上传1:已上传
AvatarVersion int64 // 头像对象版本,0 表示旧版稳定路径
Role string // 角色 admin/superAdmin
Robot int // 机器人0.否1.是
MuteOfApp int // app是否禁音(当pc登录的时候app可以设置禁音,当pc登录后有效)
IsDestroy int // 注销状态 0.正常 1.注销申请中(冷静期) 2.已注销
DestroyApplyAt dbr.NullTime // 注销申请时间
DestroyExpireAt dbr.NullTime // 注销到期执行时间
WXOpenid string // 微信openid
WXUnionid string // 微信unionid
GiteeUID string // gitee uid
GithubUID string // github uid
Web3PublicKey string // web3公钥
MsgExpireSecond int64 // 消息过期时长
Language string // 用户语言偏好(BCP 47,空表示沿用 OCTO_DEFAULT_LANGUAGE)
db.BaseModel
}
Model 用户db model
type OIDCVerificationClaims ¶
type OIDCVerificationClaims struct {
// Subject OIDC sub,作为 user_verification.source_sub 写入。
Subject string
// VerifiedProvider Aegis 返回的 provider 全名,如 "cas.example.com"。
// 存库前 strip 到一级(cas),不在 allowlist 内会被拒写。
VerifiedProvider string
// VerifiedAt Unix 秒,0 视为"未提供实名完成时间",Upsert 拒绝写入。
VerifiedAt int64
// LegalName 实名姓名。空字符串视为未实名,Upsert 拒绝写入。
LegalName string
// LegalEmail 实名邮箱,允许空。
LegalEmail string
}
OIDCVerificationClaims 从 OIDC id_token / userinfo claims 里摘出的实名字段子集。
在 user 包定义(而不是直接引用 oidc.IDTokenClaims),避免 user → oidc 反向 import 环 —— oidc 模块已经 import user。字段命名与 oidc 层一致,方便调用方直接拷贝。
type OnLineUserResp ¶
type OnlineService ¶
func NewOnlineService ¶
func NewOnlineService(ctx *config.Context) *OnlineService
func (*OnlineService) DeviceOnline ¶
func (o *OnlineService) DeviceOnline(uid string, device config.DeviceFlag) (bool, error)
func (*OnlineService) GetOnlineCount ¶
func (o *OnlineService) GetOnlineCount() (int64, error)
func (*OnlineService) GetUserLastOnlineStatus ¶
func (o *OnlineService) GetUserLastOnlineStatus(uids []string) ([]*config.OnlinestatusResp, error)
type Pinned ¶
Pinned 置顶频道 API
func (*Pinned) UpdateSort ¶
UpdateSort 更新置顶排序 PUT /v1/user/pinned/sort
type PinnedChannelModel ¶
type PinnedChannelModel struct {
ID int64 `db:"id"`
UID string `db:"uid"`
SpaceID string `db:"space_id"`
ChannelID string `db:"channel_id"`
ChannelType uint8 `db:"channel_type"`
SortOrder int `db:"sort_order"`
CreatedAt time.Time `db:"created_at"`
}
PinnedChannelModel 用户置顶频道模型
type PinnedDB ¶
PinnedDB 置顶频道数据库操作
func (*PinnedDB) Add ¶
Add 添加置顶频道。
并发一致性:
- 先 SELECT COUNT(*) ... FOR UPDATE 取当前读并在匹配范围上加 next-key lock, 串行化同一 (uid, space_id) 下的并发插入。REPEATABLE READ 下普通的 一致性读 COUNT 使用事务启动时的快照,看不到其他事务已提交的插入, 因此必须用 FOR UPDATE 保证上限检查的正确性。
- 唯一索引 uk_user_space_channel 配合 INSERT IGNORE 检测重复。
func (*PinnedDB) AreSpaceMembers ¶
AreSpaceMembers 校验 uid1、uid2 是否都是指定 Space 的在籍成员。 用于私聊置顶时,允许同 Space 成员(即便未互加好友)之间互相置顶。
func (*PinnedDB) List ¶
func (d *PinnedDB) List(uid, spaceID string) ([]*PinnedChannelModel, error)
List 获取用户置顶频道列表
func (*PinnedDB) QueryPeerRobotInfo ¶
QueryPeerRobotInfo 返回目标用户是否为机器人,以及(若为机器人)其创建者 UID。 用于私聊置顶时区分普通用户与 bot:非本人创建的 bot 必须是好友关系才允许对话 / 置顶。
边界:LEFT JOIN 条件限定 r.status=1,因此 user.robot=1 但 robot.status!=1 (已停用的 bot)的情况下,返回 isRobot=true, creatorUID=""。此时调用方的 creator 匹配必然失败,会 fallback 到 "请先添加该机器人为好友"——对已停用 bot 采用更严格的访问控制,符合与 modules/robot/event.go existRobot 一致的语义。
func (*PinnedDB) RemoveByChannel ¶
RemoveByChannel 根据频道删除所有用户的置顶(用于频道删除/群解散时清理)
func (*PinnedDB) RemoveByUIDAndChannel ¶
RemoveByUIDAndChannel 删除用户在所有 Space 下指定频道的置顶(用于删好友时清理) 注意:此方法会跨 Space 删除,仅用于全局性操作(如删好友)
func (*PinnedDB) RemoveByUIDSpaceChannel ¶
RemoveByUIDSpaceChannel 删除用户在指定 Space 指定频道的置顶(用于退群时清理)
func (*PinnedDB) UpdateSort ¶
func (d *PinnedDB) UpdateSort(uid, spaceID string, items []PinnedSortItem) error
UpdateSort 更新置顶排序。
行为说明:
- 忽略客户端提交的 SortOrder 字段,按 items 数组顺序从 1 开始重新编号, 避免客户端伪造 sort_order 造成冲突或超出范围。
- 校验所有提交的频道都已被当前用户在当前 Space 下置顶。
type PinnedSortError ¶
type PinnedSortError struct {
// contains filtered or unexported fields
}
PinnedSortError 表示 UpdateSort 请求参数校验失败,属于客户端错误。 handler 可以通过 errors.As 判断并直接把 message 透传给客户端, 以区分 DB/系统错误(走泛化的 "更新排序失败" 提示)。
func (*PinnedSortError) Error ¶
func (e *PinnedSortError) Error() string
type PinnedSortItem ¶
type PinnedSortItem struct {
ChannelID string `json:"channel_id"`
ChannelType uint8 `json:"channel_type"`
}
PinnedSortItem 排序项。SortOrder 由服务端按数组顺序重新分配, 客户端不需要也不应该提交;因此结构体中不包含 SortOrder 字段。
type Resp ¶
type Resp struct {
UID string
Name string
Zone string
Phone string
Email string
Status int // 用户状态 1 正常 2:黑名单 0 禁用
IsUploadAvatar int
NewMsgNotice int
MsgShowDetail int //显示消息通知详情0.否1.是
MsgExpireSecond int64
CreatedAt int64 // 注册时间 10位时间戳
IsDestroy int // 是否注销
Robot int // 机器人0.否1.是
}
Resp 用户返回
type RoleService ¶ added in v1.7.0
type RoleService struct {
// contains filtered or unexported fields
}
RoleService resolves the authoritative system role for a user. It satisfies pkg/auth.RoleResolver, consumed by CacheTokenParser so that a role baked into a token at login no longer outlives a demotion until token expiry.
Lookup order: Redis `user_role:{uid}` → DB `user.role` → "". DB results are written back to Redis (empty stored as a negative marker) so subsequent requests for normal users don't touch MySQL. The TTL alone bounds staleness; callers that demote/remove an admin should additionally invalidate the hot key (Invalidate) for immediate effect.
func NewRoleService ¶ added in v1.7.0
func NewRoleService(db roleReader, c cache.Cache) *RoleService
NewRoleService constructs a RoleService with the canonical TTL. cache and db must be non-nil; nil is a programmer error and panics on construction rather than silently degrading at request time.
func (*RoleService) Invalidate ¶ added in v1.7.0
func (s *RoleService) Invalidate(uid string)
Invalidate drops the hot-cache entry for a user so the next request re-reads the role from DB. Call after any mutation of user.role (e.g. removing an admin) to make the change take effect within one round-trip instead of waiting out RoleCacheTTL. Best-effort: a Redis error degrades to TTL-bounded staleness, not a failure.
func (*RoleService) ResolveRole ¶ added in v1.7.0
ResolveRole returns the user's current system role, or "" if none. Errors are surfaced to the caller; pkg/auth.CacheTokenParser keeps the token snapshot on resolver error so a cache/DB outage does not fail authentication.
type Service ¶
Service Service
func (*Service) AreSpaceMembers ¶ added in v1.7.0
AreSpaceMembers 委托 PinnedDB,详见 IService 注释。
func (*Service) ExistBlacklist ¶
func (*Service) GetAllUserCount ¶
GetAllUserCount 获取总用户数量
func (*Service) GetDeviceOnline ¶
func (s *Service) GetDeviceOnline(uid string, deviceFlag config.DeviceFlag) (*config.OnlinestatusResp, error)
func (*Service) GetFriends ¶
func (s *Service) GetFriends(uid string) ([]*FriendResp, error)
GetFriends 查询某个用户的所有好友
func (*Service) GetFriendsWithToUIDs ¶
func (s *Service) GetFriendsWithToUIDs(uid string, toUIDs []string) ([]*FriendResp, error)
GetFriendsWithToUIDs 查询一批好友
func (*Service) GetOnetimePrekeyCount ¶
func (*Service) GetRegisterCountWithDateSpace ¶
func (s *Service) GetRegisterCountWithDateSpace(startDate, endDate string) (map[string]int64, error)
GetRegisterCountWithDateSpace 获取某个时间区间的注册数量
func (*Service) GetRegisterWithDate ¶
GetRegisterWithDate 查询某天的注册量
func (*Service) GetUserDetail ¶
func (s *Service) GetUserDetail(uid string, loginUID string) (*UserDetailResp, error)
func (*Service) GetUserDetails ¶
func (*Service) GetUserOnlineStatus ¶
func (s *Service) GetUserOnlineStatus(uids []string) ([]*OnLineUserResp, error)
GetUserOnlineStatus 查询在线用户
func (*Service) GetUserSettings ¶
func (s *Service) GetUserSettings(uids []string, loginUID string) ([]*SettingResp, error)
func (*Service) GetUserUIDWithUsernames ¶
GetUserUIDWithUsernames 获取用户uid集合
func (*Service) GetUserWithQRVercode ¶
GetUserWithQRVercode 通过qrvercode获取用户信息
func (*Service) GetUserWithUsername ¶
GetUserWithUsername 获取用户
func (*Service) GetUsersWithAppID ¶
GetUsersWithAppID 通过appID获取用户集合
func (*Service) GetUsersWithCategories ¶
GetUsersWithCategories 获取指定类别的用户列表
func (*Service) GetUsersWithCategory ¶
GetUsersWithCategory 获取用户列表
func (*Service) IsBindable ¶ added in v1.4.0
IsBindable 详见 IService 注释。
func (*Service) LoginByExternalIdentity ¶
func (s *Service) LoginByExternalIdentity(ctx context.Context, req ExternalLoginReq) (*ExternalLoginResp, error)
LoginByExternalIdentity 委托给已注入的 handler。未注入时返回 ErrExternalLoginNotConfigured。
func (*Service) QueryPeerRobotInfo ¶ added in v1.7.0
QueryPeerRobotInfo 委托 PinnedDB,详见 IService 注释。
func (*Service) SearchFriendsWithKeyword ¶
func (s *Service) SearchFriendsWithKeyword(uid string, keyword string) ([]*FriendResp, error)
搜索好友
func (*Service) SendOIDCBindSMS ¶ added in v1.4.0
SendOIDCBindSMS 详见 IService 注释。
func (*Service) SetExternalLoginHandler ¶
func (s *Service) SetExternalLoginHandler(h externalLoginHandler)
SetExternalLoginHandler 注入外部 IdP 登录 handler(在 user.New 内部调用,生产路径下保证非空)
func (*Service) SetOIDCBindHandler ¶ added in v1.4.0
func (s *Service) SetOIDCBindHandler(h oidcBindHandler)
SetOIDCBindHandler 注入 OIDC 自助绑定 handler(在 user.New 内部调用, 生产路径下保证非空)。未注入时三个 OIDC 绑定方法返回 ErrOIDCBindNotConfigured。
func (*Service) UpdateLoginPassword ¶
func (s *Service) UpdateLoginPassword(req UpdateLoginPasswordReq) error
func (*Service) UpdateUser ¶
func (s *Service) UpdateUser(req UserUpdateReq) error
func (*Service) UpdateUserMsgExpireSecond ¶
func (*Service) UpsertVerificationFromOIDC ¶
func (s *Service) UpsertVerificationFromOIDC(ctx context.Context, uid string, claims OIDCVerificationClaims) error
UpsertVerificationFromOIDC 详见 IService 注释。
语义检查顺序:
- uid / VerifiedAt / LegalName 基本必填(防 oidc callback 上游误调)
- VerifiedProvider strip domain → allowlist 白名单
- 复用 verificationDB.Upsert(与 verify-service 写入同一 SQL 路径)
func (*Service) VerifyOIDCBindSMS ¶ added in v1.4.0
VerifyOIDCBindSMS 详见 IService 注释。
type SettingDB ¶
type SettingDB struct {
// contains filtered or unexported fields
}
SettingDB 设置db
func (*SettingDB) InsertUserSettingModel ¶
func (d *SettingDB) InsertUserSettingModel(setting *SettingModel) error
InsertUserSettingModel 插入用户设置
func (*SettingDB) InsertUserSettingModelTx ¶
func (d *SettingDB) InsertUserSettingModelTx(setting *SettingModel, tx *dbr.Tx) error
InsertUserSettingModelTx 插入用户设置
func (*SettingDB) QueryTwoUserSettingModel ¶
func (d *SettingDB) QueryTwoUserSettingModel(uid, loginUID string) ([]*SettingModel, error)
QueryTwoUserSettingModel 查询双方用户设置
func (*SettingDB) QueryUserSettingModel ¶
func (d *SettingDB) QueryUserSettingModel(uid, loginUID string) (*SettingModel, error)
QueryUserSettingModel 查询用户设置
func (*SettingDB) QueryUserSettings ¶
func (d *SettingDB) QueryUserSettings(uids []string, loginUID string) ([]*SettingModel, error)
func (*SettingDB) QueryWithUidsAndToUID ¶
func (d *SettingDB) QueryWithUidsAndToUID(uids []string, toUID string) ([]*SettingModel, error)
func (*SettingDB) UpdateUserSettingModel ¶
func (d *SettingDB) UpdateUserSettingModel(setting *SettingModel) error
UpdateUserSettingModel 更新用户设置
type SettingModel ¶
type SettingModel struct {
UID string // 用户UID
ToUID string // 对方uid
Mute int // 免打扰
Top int // 置顶
ChatPwdOn int // 是否开启聊天密码
Screenshot int //截屏通知
RevokeRemind int //撤回提醒
Blacklist int //黑名单
Receipt int //消息是否回执
Flame int // 是否开启阅后即焚
FlameSecond int // 阅后即焚秒数
Version int64 // 版本
Remark string // 备注
db.BaseModel
}
SettingModel 用户设置
type SettingResp ¶
type SpaceSettingDB ¶ added in v1.4.0
type SpaceSettingDB struct {
// contains filtered or unexported fields
}
func NewSpaceSettingDB ¶ added in v1.4.0
func NewSpaceSettingDB(session *dbr.Session) *SpaceSettingDB
func (*SpaceSettingDB) InsertIgnoreSpaceSetting ¶ added in v1.4.0
func (d *SpaceSettingDB) InsertIgnoreSpaceSetting(uid, spaceID string) error
func (*SpaceSettingDB) QuerySpaceSetting ¶ added in v1.4.0
func (d *SpaceSettingDB) QuerySpaceSetting(uid, spaceID string) (*SpaceSettingModel, error)
func (*SpaceSettingDB) UpdateSpaceSetting ¶ added in v1.4.0
func (d *SpaceSettingDB) UpdateSpaceSetting(uid, spaceID string, fields map[string]interface{}) error
type SpaceSettingModel ¶ added in v1.4.0
type UpdateLoginPasswordReq ¶
type User ¶
User 用户相关API
func (*User) GetFriendByVercode ¶
func (u *User) GetFriendByVercode(vercode string) (*source.FriendModel, error)
GetFriendByVercode 通过vercode获取好友信息
func (*User) GetFriendByVercodes ¶
func (u *User) GetFriendByVercodes(vercodes []string) ([]*source.FriendModel, error)
func (*User) GetUserByMailListVercode ¶
通过通讯录验证码获取用户信息
func (*User) GetUserByQRVercode ¶
GetUserByQRVercode 通过二维码验证码获取用户信息
func (*User) GetUserByUID ¶
GetUserByUID 通过UID获取用户信息
func (*User) GetUserByVercode ¶
GetUserByVercode 通过vercode获取用户信息
func (*User) IsBindable ¶ added in v1.4.0
IsBindable 详见 IService 注释。
可绑定条件比 VerifyPasswordByUID 更严:locator (oidc.dbBindLocator + DB QueryUIDsByEmail/Phone) 已经用 `is_destroy=0 AND status<>0` 过滤,本方法 必须与 locator 完全一致 —— 否则"verify 时被 locator 拒,改用别的入口 绕到 confirm"或"verify 通过后进入冷静期"两种场景就会出现 verify/confirm 不一致。
比 VerifyPasswordByUID 的 `IsDestroyDone || Status == 0` 多排除一个状态: is_destroy=1(冷静期内可撤销注销)。理由:5min bind 窗口内被用户主动发起 注销的账号,不该允许新 OIDC 身份绑定上去。
不计入 loginGuard 失败计数:Confirm 阶段调用,uid 已在 verify 阶段通过 密码/OTP 校验,这里只是状态二次确认。如果计失败会让"管理员在用户绑定 中途 disable"演变成"该 uid 被错锁 15min",体验差且无安全收益。
func (*User) LoginByExternalIdentity ¶
func (u *User) LoginByExternalIdentity(ctx context.Context, req ExternalLoginReq) (*ExternalLoginResp, error)
LoginByExternalIdentity 给外部 IdP(OIDC / OAuth)登录流程签发 DMWork 会话。
ExistingUID 非空 → 走 execLogin(已有用户); ExistingUID 为空 → 走 createUserWithRespAndTx(创建用户 + 登录,事务内)。
行为复用 GitHub 登录路径(api_github.go),oidc 模块通过 IService 间接调用。
func (*User) SendOIDCBindSMS ¶ added in v1.4.0
SendOIDCBindSMS 详见 IService 注释(含跨流程 keyspace 共享勘误)。
**安全契约(必须读)**:本方法不做"phone 来自 OIDC claims 而非用户输入"的来源校验, 因为 oidc 模块在 callback 阶段已经拿着可信 claims 转发过来。**如果未来 增加新的调用方,必须在该调用方内部确保 zone/phone 不可被用户控制**,否则:
- 攻击者可用自己手机号 + 任意 OIDC sub 发起绑定流程,把别人 sub 绑到 自己 dmwork 账号(违反 FR-3.3 / SR-4 反伪造手机号)
- 攻击者可批量请求发短信制造账号枚举 / 短信轰炸
实务防护(分层互补):
- **本层**:commonapi.SMSService 内置 1min 发送频率 + 10min 失败锁定, 但锁定 key 不带 codeType(详见 IService 注释),与 register/forget-pwd 等其他 SMS 流程共享 -> 提供"全局手机号粒度"的反滥用;
- **oidc 模块 BindService 层**:OCTO_OIDC_BIND_ISSUER_ALLOWLIST 收口 + bind_token 单次消费 + Redis 5min TTL + BindStore.IncrAndCheck (bind_token 维度的 OTP 发送/校验/确认 counter, 对应 SR-2.1) -> 提供"OIDC bind 流程粒度"的反爆破;
- 两层叠加,phone 输入来源校验是调用方契约,不在本层兜底。
func (*User) VerifyOIDCBindSMS ¶ added in v1.4.0
VerifyOIDCBindSMS 详见 IService 注释(含跨流程 keyspace 共享勘误)。
反爆破隔离边界:
- 与 loginGuard 的密码反爆破计数器独立(后者用 "oidc-bind:"+uid 前缀, SMSService 用 zone@phone 前缀)—— 用户即便密码尝试已被限流,仍可走 短信路径,反之亦然;
- 但底层 SMSService 的"3 次失败锁定 10 分钟"行为 lock key 不带 codeType, 与 register / forget-pwd / login_check_phone / destroy / email_login 等所有 SMS 流程共享 —— OIDC bind 路径错 3 次也会把该手机号其他 SMS 流程一并锁 10min(详见 IService.SendOIDCBindSMS 注释)。如需 codeType 维度的独立锁,P0 之后单独评估改造 commonapi.SMSService(影响 5 个调 用方,需 ops 决策)。
func (*User) VerifyPasswordByUID ¶ added in v1.4.0
VerifyPasswordByUID 详见 IService 注释。
失败维度按 uid 计:配合 SR-2.2 "每 uid 当日 ≤ 10 次失败"。当前用与登录共用 的 loginGuard 默认阈值(5 次/15min),P0 上线后视实际反爆破效果再调 oidcBindGuardPrefix 维度的独立阈值。
已注销/封号账号视为不可用,与 api_usernamelogin.go 行为对齐 —— 攻击者用 OIDC bind 探测一个已注销账号的密码与登录路径同样无意义。
type UserDetailResp ¶
type UserDetailResp struct {
UID string `json:"uid"`
Name string `json:"name"`
Username string `json:"username"`
Email string `json:"email,omitempty"` // email(仅自己能看)
Zone string `json:"zone,omitempty"` // 手机区号(仅自己能看)
Phone string `json:"phone,omitempty"` // 手机号(仅自己能看)
Mute int `json:"mute"` // 免打扰
Top int `json:"top"` // 置顶
Sex int `json:"sex"` //性别1:男
Category string `json:"category"` //用户分类 '客服'
ShortNo string `json:"short_no"` // 用户唯一短编号
ChatPwdOn int `json:"chat_pwd_on"` //是否开启聊天密码
Screenshot int `json:"screenshot"` //截屏通知
RevokeRemind int `json:"revoke_remind"` //撤回提醒
Receipt int `json:"receipt"` //消息是否回执
Online int `json:"online"` //是否在线
LastOffline int `json:"last_offline"` //最后一次离线时间
DeviceFlag config.DeviceFlag `json:"device_flag"` // 在线设备标记
Follow int `json:"follow"` //是否是好友
BeDeleted int `json:"be_deleted"` // 被删除
BeBlacklist int `json:"be_blacklist"` // 被拉黑
Code string `json:"code"` //加好友所需vercode TODO: code不再使用 请使用Vercode
Vercode string `json:"vercode"` //
SourceDesc string `json:"source_desc"` // 好友来源
Remark string `json:"remark"` //好友备注
IsUploadAvatar int `json:"is_upload_avatar"` // 是否上传头像
Status int `json:"status"` //用户状态 1 正常 2:黑名单
Robot int `json:"robot"` // 机器人0.否1.是
BotCommands string `json:"bot_commands,omitempty"` // 机器人命令列表JSON
BotDescription string `json:"bot_description,omitempty"` // Bot 简介
BotCreatorUID string `json:"bot_creator_uid,omitempty"` // Bot 创建者 UID
BotCreatorName string `json:"bot_creator_name,omitempty"` // Bot 创建者昵称
BotAutoApprove int `json:"bot_auto_approve,omitempty"` // 是否自动通过好友 0:否 1:是
BotAgentPlatform string `json:"bot_agent_platform,omitempty"` // Agent 平台名称
BotAgentVersion string `json:"bot_agent_version,omitempty"` // Agent 平台版本号
BotPluginVersion string `json:"bot_plugin_version,omitempty"` // DMWork 插件版本号
IsDestroy int `json:"is_destroy"` // 注销状态 0.正常 1.注销申请中(冷静期,仍可正常通信) 2.已注销
Flame int `json:"flame"` // 是否开启阅后即焚
FlameSecond int `json:"flame_second"` // 阅后即焚秒数
JoinGroupInviteUID string `json:"join_group_invite_uid"` // 加入群聊邀请人UID
JoinGroupInviteName string `json:"join_group_invite_name"` // 加入群聊邀请人名称
JoinGroupTime string `json:"join_group_time"` // 加入群聊时间
GroupMember *GroupMemberResp `json:"group_member,omitempty"` // 群成员信息
// OCTO 实名认证(YUJ-354 / GH#1300)
// RealnameVerified:该用户是否已完成 OCTO 实名认证(user_verification 有记录)。
// RealName:已认证时返回实名姓名;未认证留空。
// 目前对外可见(含非自己),因为实名结果会进入前端 displayName() 展示;
// 若后续产品要求"仅自己可见",改为仅当 loginUID == m.UID 时填充即可。
RealnameVerified bool `json:"realname_verified"`
RealName string `json:"real_name,omitempty"`
// RealnameVerifiedAt:实名认证完成时间 (Unix 秒)。未认证时为 0 被 omitempty 剥离。
// 三端客户端(Web/Android/iOS)在 Custom Tabs 实名回跳后读此字段做
// 「已认证 · YYYY-MM」展示;同时与 login / GET /v1/user/current 下发的
// 字段名保持一致(YUJ-413)。
RealnameVerifiedAt int64 `json:"realname_verified_at,omitempty"`
}
func NewUserDetailResp ¶
func NewUserDetailResp(m *Detail, remark, loginUID string, sourceFrom string, onLine int, lastOffline int, deviceFlag config.DeviceFlag, follow int, status int, beDeleted int, beBlacklist int, setting *SettingModel, vercode string) *UserDetailResp
type UserUpdateReq ¶
type VerificationInfo ¶
type VerificationInfo struct {
UID string
RealName string
RealnameVerifiedAt int64 // Unix 秒;未实名 / 时间零值时为 0
}
VerificationInfo 是对外导出的只读实名信息视图 —— 给其他模块(如 modules/group) 批量回填 response 用。YUJ-413 Scope B 引入:根因报告(YUJ-411 memory 07c6d080)发现 memberDetailResp 和 newChannelRespWithUserDetailResp 都漏下发 实名字段,这些调用方都不应再私有化 verificationDB 结构体,改走本视图。
字段对齐 login / current / friend-sync / conversation-sync 的 wire 协议:
realname_verified ← (info != nil) real_name ← info.RealName realname_verified_at ← info.RealnameVerifiedAt (Unix 秒)
Source Files
¶
- 1module.go
- api.go
- api_destroy.go
- api_device.go
- api_emaillogin.go
- api_friend.go
- api_gitee.go
- api_github.go
- api_i18n.go
- api_login_log.go
- api_maillist.go
- api_manager.go
- api_online.go
- api_pinned.go
- api_setting.go
- api_space_setting.go
- api_usernamelogin.go
- avatar_gen.go
- avatar_path.go
- bot_default_avatar.go
- const.go
- db.go
- db_device.go
- db_device_flag.go
- db_friend.go
- db_gitee.go
- db_github.go
- db_identities.go
- db_login_log.go
- db_maillist.go
- db_manager.go
- db_onetime_prekeys.go
- db_online.go
- db_pinned.go
- db_setting.go
- db_space_setting.go
- db_verification.go
- event_friend.go
- external_login.go
- friend_hook.go
- group_member_external_provider.go
- language_service.go
- login_guard.go
- oidc_bind.go
- password.go
- role_service.go
- service.go
- service_online.go
- source.go
- system_bot_avatar.go
- webhook.go
- webhook_identity.go