Files
2026-07-30 10:01:19 +08:00

165 lines
5.1 KiB
Go

package platform
import (
"strings"
"git.apinb.com/bsm-sdk/core/crypto/token"
"git.apinb.com/bsm-sdk/core/env"
"git.apinb.com/bsm-sdk/core/errcode"
"git.apinb.com/bsm-sdk/core/infra"
"git.apinb.com/bsm-sdk/core/middleware"
"git.apinb.com/heqiapp/platforms/backend/api/internal/impl"
"git.apinb.com/heqiapp/platforms/backend/api/internal/logic/common"
"git.apinb.com/heqiapp/platforms/backend/api/internal/models"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
)
// LoginRequest 是平台总后台的账号密码登录请求。
type LoginRequest struct {
Username string `json:"username" binding:"required,max=64"`
Password string `json:"password" binding:"required"`
}
// LoginReply 是后台登录成功后的访问凭证与账号状态。
type LoginReply struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
Identity string `json:"identity"`
DisplayName string `json:"display_name"`
RoleCode string `json:"role_code"`
}
// Login 校验平台账号密码并签发 BSM JWT。
func Login(ctx *gin.Context) {
var request LoginRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var account models.PlatformAccount
err := impl.DBService.Where("username = ?", strings.TrimSpace(request.Username)).First(&account).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
infra.Response.Error(ctx, errcode.ErrPassword)
return
}
infra.Response.Error(ctx, err)
return
}
if account.Status != common.StatusEnable {
infra.Response.Error(ctx, errcode.ErrAccountDisabled)
return
}
if bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(request.Password)) != nil {
infra.Response.Error(ctx, errcode.ErrPassword)
return
}
extend := map[string]string{"username": account.Username, "display_name": account.DisplayName}
if account.PlatformRoleCode == "root" {
extend["location_scope"] = "precise"
} else {
var role models.PlatformRole
if impl.DBService.Select("location_scope").Where("role_code = ? AND status = ?", account.PlatformRoleCode, common.StatusEnable).First(&role).Error == nil &&
role.LocationScope == "precise" {
extend["location_scope"] = "precise"
}
}
accessToken, err := token.New(env.Runtime.JwtSecretKey).GenerateJwt(
0,
account.Identity,
"platform_admin",
account.PlatformRoleCode,
extend,
nil,
)
if err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, LoginReply{
AccessToken: accessToken,
TokenType: "JWT",
Identity: account.Identity,
DisplayName: account.DisplayName,
RoleCode: account.PlatformRoleCode,
})
}
// CurrentProfile 返回当前已认证的平台管理员资料。
func CurrentProfile(ctx *gin.Context) {
claims, err := middleware.ParseAuth(ctx)
if err != nil {
infra.Response.Error(ctx, err)
return
}
var account models.PlatformAccount
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
return
}
menus, err := LoadPlatformMenus(account.PlatformRoleCode)
if err != nil {
infra.Response.Error(ctx, errcode.ErrPermissionDenied)
return
}
menuCodes := make([]string, 0, len(menus))
seenMenuCodes := make(map[string]bool, len(menus))
for _, menu := range menus {
if menu.Identity != "" && !seenMenuCodes[menu.Identity] {
menuCodes = append(menuCodes, menu.Identity)
seenMenuCodes[menu.Identity] = true
}
}
infra.Response.Success(ctx, gin.H{
"identity": account.Identity, "username": account.Username, "display_name": account.DisplayName,
"avatar": account.Avatar, "role_code": account.PlatformRoleCode, "menu_codes": menuCodes,
})
}
// ChangePasswordRequest 是已登录账号的改密请求。
type ChangePasswordRequest struct {
CurrentPassword string `json:"current_password" binding:"required"`
NewPassword string `json:"new_password" binding:"required"`
}
// ChangePassword 修改当前账号密码并解除首次登录改密限制。
func ChangePassword(ctx *gin.Context) {
claims, err := middleware.ParseAuth(ctx)
if err != nil {
infra.Response.Error(ctx, err)
return
}
var request ChangePasswordRequest
if err := ctx.ShouldBindJSON(&request); err != nil {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
if !common.IsValidAccountPassword(request.NewPassword) {
infra.Response.Error(ctx, errcode.ErrInvalidArgument)
return
}
var account models.PlatformAccount
if err := impl.DBService.Where("identity = ?", claims.Identity).First(&account).Error; err != nil {
infra.Response.Error(ctx, errcode.ErrRecordNotFound)
return
}
if bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(request.CurrentPassword)) != nil {
infra.Response.Error(ctx, errcode.ErrPassword)
return
}
passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.NewPassword), bcrypt.DefaultCost)
if err != nil {
infra.Response.Error(ctx, err)
return
}
if err := impl.DBService.Model(&account).Update("password_hash", string(passwordHash)).Error; err != nil {
infra.Response.Error(ctx, err)
return
}
infra.Response.Success(ctx, gin.H{"changed": true})
}