// Package staff 实现工作人员 App 的服务端业务接口。 package staff import ( "strings" "time" "git.apinb.com/bsm-sdk/core/errcode" "git.apinb.com/bsm-sdk/core/infra" "git.apinb.com/heqiapp/platforms/backend/api/internal/impl" clientcommon "git.apinb.com/heqiapp/platforms/backend/api/internal/logic/client/common" base "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" ) var supportedRoles = map[string]bool{"delivery": true, "installer": true, "operations": true} // Login 登录启用且角色受支持、资质有效的工作人员账户。 func Login(ctx *gin.Context) { var request struct { Phone string `json:"phone" binding:"required"` Mode string `json:"mode" binding:"required,oneof=password verification_code"` Password string `json:"password"` Code string `json:"code"` RequestIdentity string `json:"request_identity"` } if ctx.ShouldBindJSON(&request) != nil || !clientcommon.ValidPhone(request.Phone) { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } var account models.StaffAccount if impl.DBService.Where("phone = ? AND status = ?", strings.TrimSpace(request.Phone), base.StatusEnable).First(&account).Error != nil || !supportedRoles[account.RoleCode] { infra.Response.Error(ctx, errcode.ErrPassword) return } valid := request.Mode == "password" && bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(request.Password)) == nil if request.Mode == "verification_code" { valid = clientcommon.VerifyCode("service_app", account.Phone, "login", request.RequestIdentity, request.Code) } if !valid { infra.Response.Error(ctx, errcode.ErrPassword) return } accessToken, err := clientcommon.IssueToken(account.Identity, "service_app", account.RoleCode, map[string]string{"phone": account.Phone}) if err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"access_token": accessToken, "token_type": "JWT", "identity": account.Identity, "role_code": account.RoleCode}) } // Profile 返回工作人员岗位和归属。 func Profile(ctx *gin.Context) { account, ok := clientcommon.StaffAccount(ctx) if !ok { return } infra.Response.Success(ctx, gin.H{ "identity": account.Identity, "name": account.Name, "phone": account.Phone, "avatar": account.Avatar, "role_code": account.RoleCode, "work_status": account.WorkStatus, }) } // Preflight 返回当前单角色账号可由服务端确认的作业前置条件。 func Preflight(ctx *gin.Context) { account, ok := clientcommon.StaffAccount(ctx) if !ok { return } var credential models.StaffCredential credentialFound := impl.DBService. Where("staff_account_id = ? AND status = ?", account.ID, base.StatusEnable). Order("expired_at desc"). First(&credential).Error == nil credentialValid := credentialFound && (credential.ExpiredAt == nil || credential.ExpiredAt.After(time.Now())) organizationIdentity, organizationName, organizationType := "", "", "" if account.DeliveryBasicID != 0 { var organization models.DeliveryBasic if impl.DBService.First(&organization, account.DeliveryBasicID).Error == nil { organizationIdentity, organizationName, organizationType = organization.Identity, organization.Name, "delivery" } } else if account.GasBasicID != 0 { var organization models.GasBasic if impl.DBService.First(&organization, account.GasBasicID).Error == nil { organizationIdentity, organizationName, organizationType = organization.Identity, organization.Name, "gas" } } checks := gin.H{ "account": gin.H{"status": "passed"}, "role": gin.H{"status": "passed", "role_code": account.RoleCode}, "organization": gin.H{"status": checkStatus(organizationIdentity != ""), "identity": organizationIdentity, "name": organizationName, "type": organizationType}, "credential": gin.H{"status": checkStatus(credentialValid), "expired_at": credential.ExpiredAt}, "attendance": gin.H{"status": checkStatus(account.WorkStatus == "on_duty"), "work_status": account.WorkStatus}, "daily_training": gin.H{"status": "not_configured"}, "service_area": gin.H{"status": "not_configured"}, "authorized_device": gin.H{"status": "not_configured"}, } infra.Response.Success(ctx, gin.H{ "role_code": account.RoleCode, "work_status": account.WorkStatus, "can_work": organizationIdentity != "" && credentialValid && account.WorkStatus == "on_duty", "checks": checks, }) } func checkStatus(passed bool) string { if passed { return "passed" } return "blocked" } // ChangePassword 修改当前工作人员登录密码。 func ChangePassword(ctx *gin.Context) { account, ok := clientcommon.StaffAccount(ctx) if !ok { return } var request struct { CurrentPassword string `json:"current_password" binding:"required"` NewPassword string `json:"new_password" binding:"required"` } if ctx.ShouldBindJSON(&request) != nil || !base.IsValidAccountPassword(request.NewPassword) || bcrypt.CompareHashAndPassword([]byte(account.PasswordHash), []byte(request.CurrentPassword)) != nil { infra.Response.Error(ctx, errcode.ErrPassword) return } hash, err := base.PasswordHash(request.NewPassword) if err != nil { infra.Response.Error(ctx, err) return } if err := impl.DBService.Model(&account).Update("password_hash", hash).Error; err != nil { infra.Response.Error(ctx, err) return } infra.Response.Success(ctx, gin.H{"changed": true}) } // ResetPassword 使用验证码重置工作人员登录密码,不开放注册。 func ResetPassword(ctx *gin.Context) { var request struct { Phone string `json:"phone" binding:"required"` NewPassword string `json:"new_password" binding:"required"` Code string `json:"code" binding:"required"` RequestIdentity string `json:"request_identity" binding:"required"` } if ctx.ShouldBindJSON(&request) != nil || !base.IsValidAccountPassword(request.NewPassword) || !clientcommon.VerifyCode("service_app", request.Phone, "reset_login_password", request.RequestIdentity, request.Code) { infra.Response.Error(ctx, errcode.ErrInvalidArgument) return } hash, err := base.PasswordHash(request.NewPassword) if err != nil { infra.Response.Error(ctx, err) return } result := impl.DBService.Model(&models.StaffAccount{}). Where("phone = ? AND status = ?", strings.TrimSpace(request.Phone), base.StatusEnable). Update("password_hash", hash) if result.Error != nil { infra.Response.Error(ctx, result.Error) return } if result.RowsAffected != 1 { infra.Response.Error(ctx, errcode.ErrRecordNotFound) return } infra.Response.Success(ctx, gin.H{"changed": true}) }