176 lines
6.7 KiB
Go
176 lines
6.7 KiB
Go
// 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})
|
|
}
|