Compare commits

...

7 Commits

Author SHA1 Message Date
ning
47898bf2e7 code refactor 2024-06-20 15:01:21 +08:00
ning
fe220c8d91 feat: ldap support defaultTeams 2024-06-19 16:37:49 +08:00
ning
5dcb86ba06 change board api 2024-06-19 15:41:55 +08:00
ning
744f4ca724 update users api 2024-06-17 09:59:38 +08:00
ning
3fff1e820a refactor: skip user verify when sso login 2024-06-06 14:22:57 +08:00
Yang Zhiyan
bd2a67e1f7 optimize: add order and betwen query (#1966) 2024-05-27 17:49:18 +08:00
Yang Zhiyan
48daa857da optimize: add user last active time (#1965) 2024-05-24 18:25:01 +08:00
8 changed files with 146 additions and 34 deletions

View File

@@ -272,7 +272,7 @@ func (rt *Router) boardGetsByGids(c *gin.Context) {
ginx.Dangerous(err)
if len(gids) == 0 {
ginx.Bomb(http.StatusForbidden, "forbidden")
ginx.NewRender(c).Data([]int{}, nil)
return
}
}

View File

@@ -55,7 +55,7 @@ func (rt *Router) loginPost(c *gin.Context) {
var err error
lc := rt.Sso.LDAP.Copy()
if lc.Enable {
user, err = ldapx.LdapLogin(rt.Ctx, f.Username, authPassWord, lc.DefaultRoles, lc)
user, err = ldapx.LdapLogin(rt.Ctx, f.Username, authPassWord, lc.DefaultRoles, lc.DefaultTeams, lc)
if err != nil {
logger.Debugf("ldap login failed: %v username: %s", err, f.Username)
var errLoginInN9e error

View File

@@ -143,6 +143,8 @@ func (rt *Router) user() gin.HandlerFunc {
c.Set("user", user)
c.Set("isadmin", user.IsAdmin())
// Update user.LastActiveTime
rt.UserCache.SetLastActiveTime(user.Id, time.Now().Unix())
c.Next()
}
}

View File

@@ -40,13 +40,17 @@ func (rt *Router) userFindAll(c *gin.Context) {
}
func (rt *Router) userGets(c *gin.Context) {
stime, etime := getTimeRange(c)
limit := ginx.QueryInt(c, "limit", 20)
query := ginx.QueryStr(c, "query", "")
order := ginx.QueryStr(c, "order", "username")
desc := ginx.QueryBool(c, "desc", false)
total, err := models.UserTotal(rt.Ctx, query)
rt.UserCache.UpdateUsersLastActiveTime()
total, err := models.UserTotal(rt.Ctx, query, stime, etime)
ginx.Dangerous(err)
list, err := models.UserGets(rt.Ctx, query, limit, ginx.Offset(c, limit))
list, err := models.UserGets(rt.Ctx, query, limit, ginx.Offset(c, limit), stime, etime, order, desc)
ginx.Dangerous(err)
user := c.MustGet("user").(*models.User)

View File

@@ -129,6 +129,7 @@ func (uc *UserCacheType) SyncUsers() {
}
go uc.loopSyncUsers()
go uc.loopUpdateLastActiveTime()
}
func (uc *UserCacheType) loopSyncUsers() {
@@ -194,3 +195,56 @@ func (uc *UserCacheType) syncUsers() error {
return nil
}
func (uc *UserCacheType) SetLastActiveTime(userId int64, lastActiveTime int64) {
uc.Lock()
defer uc.Unlock()
if user, exists := uc.users[userId]; exists {
user.LastActiveTime = lastActiveTime
}
}
func (uc *UserCacheType) loopUpdateLastActiveTime() {
defer func() {
if r := recover(); r != nil {
logger.Errorf("panic to loopUpdateLastActiveTime(), err: %v", r)
}
}()
// Sync every five minutes
duration := 5 * time.Minute
for {
time.Sleep(duration)
if err := uc.UpdateUsersLastActiveTime(); err != nil {
logger.Warningf("failed to update users' last active time: %v", err)
}
}
}
func (uc *UserCacheType) UpdateUsersLastActiveTime() error {
// read the full list of users from the database
users, err := models.UserGetAll(uc.ctx)
if err != nil {
return errors.WithMessage(err, "failed to get all users from database")
}
for _, dbUser := range users {
cacheUser := uc.GetByUserId(dbUser.Id)
if cacheUser == nil {
continue
}
if dbUser.LastActiveTime >= cacheUser.LastActiveTime {
continue
}
err = models.UpdateUserLastActiveTime(uc.ctx, cacheUser.Id, cacheUser.LastActiveTime)
if err != nil {
logger.Warningf("failed to update last active time for user %d: %v", cacheUser.Id, err)
return err
}
}
return nil
}

View File

@@ -230,7 +230,8 @@ type BoardBusigroup struct {
}
type Users struct {
Belong string `gorm:"column:belong;varchar(16);default:'';comment:belong"`
Belong string `gorm:"column:belong;varchar(16);default:'';comment:belong"`
LastActiveTime int64 `gorm:"column:last_active_time;type:int;default:0;comment:last_active_time"`
}
type SsoConfig struct {

View File

@@ -40,26 +40,27 @@ var (
)
type User struct {
Id int64 `json:"id" gorm:"primaryKey"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Password string `json:"-"`
Phone string `json:"phone"`
Email string `json:"email"`
Portrait string `json:"portrait"`
Roles string `json:"-"` // 这个字段写入数据库
RolesLst []string `json:"roles" gorm:"-"` // 这个字段和前端交互
TeamsLst []int64 `json:"-" gorm:"-"` // 这个字段方便映射团队,前端和数据库都不用到
Contacts ormx.JSONObj `json:"contacts"` // 内容为 map[string]string 结构
Maintainer int `json:"maintainer"` // 是否给管理员发消息 0:not send 1:send
CreateAt int64 `json:"create_at"`
CreateBy string `json:"create_by"`
UpdateAt int64 `json:"update_at"`
UpdateBy string `json:"update_by"`
Belong string `json:"belong"`
Admin bool `json:"admin" gorm:"-"` // 方便前端使用
UserGroupsRes []*UserGroupRes `json:"user_groups" gorm:"-"`
BusiGroupsRes []*BusiGroupRes `json:"busi_groups" gorm:"-"`
Id int64 `json:"id" gorm:"primaryKey"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Password string `json:"-"`
Phone string `json:"phone"`
Email string `json:"email"`
Portrait string `json:"portrait"`
Roles string `json:"-"` // 这个字段写入数据库
RolesLst []string `json:"roles" gorm:"-"` // 这个字段和前端交互
TeamsLst []int64 `json:"-" gorm:"-"` // 这个字段方便映射团队,前端和数据库都不用到
Contacts ormx.JSONObj `json:"contacts"` // 内容为 map[string]string 结构
Maintainer int `json:"maintainer"` // 是否给管理员发消息 0:not send 1:send
CreateAt int64 `json:"create_at"`
CreateBy string `json:"create_by"`
UpdateAt int64 `json:"update_at"`
UpdateBy string `json:"update_by"`
Belong string `json:"belong"`
Admin bool `json:"admin" gorm:"-"` // 方便前端使用
UserGroupsRes []*UserGroupRes `json:"user_groups" gorm:"-"`
BusiGroupsRes []*BusiGroupRes `json:"busi_groups" gorm:"-"`
LastActiveTime int64 `json:"last_active_time"`
}
type UserGroupRes struct {
@@ -194,8 +195,10 @@ func (u *User) Add(ctx *ctx.Context) error {
}
func (u *User) Update(ctx *ctx.Context, selectField interface{}, selectFields ...interface{}) error {
if err := u.Verify(); err != nil {
return err
if u.Belong == "" {
if err := u.Verify(); err != nil {
return err
}
}
return DB(ctx).Model(u).Select(selectField, selectFields...).Updates(u).Error
@@ -218,6 +221,13 @@ func (u *User) UpdatePassword(ctx *ctx.Context, password, updateBy string) error
}).Error
}
func UpdateUserLastActiveTime(ctx *ctx.Context, userId int64, lastActiveTime int64) error {
return DB(ctx).Model(&User{}).Where("id = ?", userId).Updates(map[string]interface{}{
"last_active_time": lastActiveTime,
"update_at": time.Now().Unix(),
}).Error
}
func (u *User) Del(ctx *ctx.Context) error {
return DB(ctx).Transaction(func(tx *gorm.DB) error {
if err := tx.Where("user_id=?", u.Id).Delete(&UserGroupMember{}).Error; err != nil {
@@ -328,12 +338,18 @@ func PassLogin(ctx *ctx.Context, username, pass string) (*User, error) {
return user, nil
}
func UserTotal(ctx *ctx.Context, query string) (num int64, err error) {
func UserTotal(ctx *ctx.Context, query string, stime, etime int64) (num int64, err error) {
db := DB(ctx).Model(&User{})
if stime != 0 && etime != 0 {
db = db.Where("last_active_time between ? and ?", stime, etime)
}
if query != "" {
q := "%" + query + "%"
num, err = Count(DB(ctx).Model(&User{}).Where("username like ? or nickname like ? or phone like ? or email like ?", q, q, q, q))
num, err = Count(db.Where("username like ? or nickname like ? or phone like ? or email like ?", q, q, q, q))
} else {
num, err = Count(DB(ctx).Model(&User{}))
num, err = Count(db)
}
if err != nil {
@@ -343,15 +359,30 @@ func UserTotal(ctx *ctx.Context, query string) (num int64, err error) {
return num, nil
}
func UserGets(ctx *ctx.Context, query string, limit, offset int) ([]User, error) {
session := DB(ctx).Limit(limit).Offset(offset).Order("username")
func UserGets(ctx *ctx.Context, query string, limit, offset int, stime, etime int64,
order string, desc bool) ([]User, error) {
session := DB(ctx)
if stime != 0 && etime != 0 {
session = session.Where("last_active_time between ? and ?", stime, etime)
}
if desc {
order = order + " desc"
} else {
order = order + " asc"
}
session = session.Order(order)
if query != "" {
q := "%" + query + "%"
session = session.Where("username like ? or nickname like ? or phone like ? or email like ?", q, q, q, q)
}
var users []User
err := session.Find(&users).Error
err := session.Limit(limit).Offset(offset).Find(&users).Error
if err != nil {
return users, errors.WithMessage(err, "failed to query user")
}

View File

@@ -34,6 +34,7 @@ type Config struct {
TLS bool
StartTLS bool
DefaultRoles []string
DefaultTeams []int64
RoleTeamMapping []RoleTeamMapping
}
@@ -55,6 +56,7 @@ type SsoClient struct {
TLS bool
StartTLS bool
DefaultRoles []string
DefaultTeams []int64
RoleTeamMapping map[string]RoleTeamMapping
Ticker *time.Ticker
@@ -109,6 +111,7 @@ func (s *SsoClient) Reload(cf Config) {
s.TLS = cf.TLS
s.StartTLS = cf.StartTLS
s.DefaultRoles = cf.DefaultRoles
s.DefaultTeams = cf.DefaultTeams
s.SyncAdd = cf.SyncAddUsers
s.SyncDel = cf.SyncDelUsers
s.SyncInterval = cf.SyncInterval
@@ -135,8 +138,12 @@ func (s *SsoClient) Copy() *SsoClient {
newRoles := make([]string, len(s.DefaultRoles))
copy(newRoles, s.DefaultRoles)
newTeams := make([]int64, len(s.DefaultTeams))
copy(newTeams, s.DefaultTeams)
lc := *s
lc.DefaultRoles = newRoles
lc.DefaultTeams = newTeams
s.RUnlock()
@@ -291,7 +298,7 @@ func (s *SsoClient) genLdapAttributeSearchList() []string {
return ldapAttributes
}
func LdapLogin(ctx *ctx.Context, username, pass string, defaultRoles []string, ldap *SsoClient) (*models.User, error) {
func LdapLogin(ctx *ctx.Context, username, pass string, defaultRoles []string, defaultTeams []int64, ldap *SsoClient) (*models.User, error) {
sr, err := ldap.LoginCheck(username, pass)
if err != nil {
return nil, err
@@ -331,6 +338,10 @@ func LdapLogin(ctx *ctx.Context, username, pass string, defaultRoles []string, l
}
}
if len(roleTeamMapping.Teams) == 0 {
roleTeamMapping.Teams = defaultTeams
}
// Synchronize group information
if err = models.UserGroupMemberSync(ctx, roleTeamMapping.Teams, user.Id, coverTeams); err != nil {
logger.Errorf("ldap.error: failed to update user(%s) group member err: %+v", user, err)
@@ -347,6 +358,15 @@ func LdapLogin(ctx *ctx.Context, username, pass string, defaultRoles []string, l
return nil, errors.WithMessage(err, "failed to add user")
}
if len(roleTeamMapping.Teams) == 0 {
for _, gid := range defaultTeams {
err = models.UserGroupMemberAdd(ctx, gid, user.Id)
if err != nil {
logger.Errorf("user:%v gid:%d UserGroupMemberAdd: %s", user, gid, err)
}
}
}
if err = models.UserGroupMemberSync(ctx, roleTeamMapping.Teams, user.Id, false); err != nil {
logger.Errorf("ldap.error: failed to update user(%s) group member err: %+v", user, err)
}