parent
fc483bdd60
commit
865b824c09
@ -0,0 +1,19 @@ |
||||
package entities |
||||
|
||||
import ( |
||||
"gorm.io/gorm" |
||||
"time" |
||||
) |
||||
|
||||
type CompanyAuthTicket struct { |
||||
ID uint `json:"id" xml:"id" gorm:"primaryKey;not null;comment:员工编号"` |
||||
CompanyID uint `json:"company_id" xml:"company_id" gorm:"comment:所属公司编号"` |
||||
EmployeeID uint `json:"employee_id" xml:"employee_id" gorm:"comment:所属员工编号"` |
||||
WechatOpenid string `json:"wechat_openid" xml:"wechat_openid" gorm:"size:100;not null;comment:关联微信号"` |
||||
Ticket string `json:"ticket" xml:"ticket" gorm:"comment:认证和授权凭证"` |
||||
CreatedAt time.Time `json:"create_time" xml:"create_time" gorm:"<-:false;comment:创建时间"` |
||||
UpdatedAt time.Time `json:"update_time" xml:"update_time" gorm:"<-:false;comment:更新时间"` |
||||
DeletedAt gorm.DeletedAt `json:"-" xml:"-" gorm:"comment:删除时间"` |
||||
|
||||
Employee *CompanyEmployee `json:"employee" xml:"employee" gorm:"foreignKey:EmployeeID"` |
||||
} |
@ -0,0 +1,92 @@ |
||||
package repositories |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"gorm.io/gorm" |
||||
"sorbet/internal/entities" |
||||
"sorbet/pkg/db" |
||||
"sorbet/pkg/ticket" |
||||
) |
||||
|
||||
type CompanyAuthTicketRepository struct { |
||||
*db.Repository[entities.CompanyAuthTicket] |
||||
} |
||||
|
||||
// NewCompanyAuthTicketRepository 创建公司部门仓库
|
||||
func NewCompanyAuthTicketRepository(orm *gorm.DB) *CompanyAuthTicketRepository { |
||||
return &CompanyAuthTicketRepository{ |
||||
db.NewRepositoryWith[entities.CompanyAuthTicket](orm, "id"), |
||||
} |
||||
} |
||||
|
||||
// TicketForLastLogin 最后一次登录生成的 ticket 信息
|
||||
func (r *CompanyAuthTicketRepository) TicketForLastLogin(ctx context.Context, openid string) (*entities.CompanyAuthTicket, error) { |
||||
var authTicket entities.CompanyAuthTicket |
||||
res := r.DB(ctx). |
||||
Model(&authTicket). |
||||
Where("wechat_openid=?", openid). |
||||
Order("updated_at DESC"). |
||||
Preload("Employee", func(tx *gorm.DB) *gorm.DB { |
||||
//return tx.
|
||||
// Preload("Company"). // 当前员工所在公司
|
||||
// Preload("Departments", func(tx *gorm.DB) *gorm.DB {
|
||||
// // 只查询当前公司的部门
|
||||
// return tx.Where("company_id = ?")
|
||||
// }) // 当前员工所在部门
|
||||
return tx.Preload("Company") |
||||
}). |
||||
Last(&authTicket) |
||||
if err := res.Error; err != nil { |
||||
return nil, err |
||||
} |
||||
return &authTicket, nil |
||||
} |
||||
|
||||
// EmployeeForLastLogin 使用微信最后一次登录的员工信息
|
||||
func (r *CompanyAuthTicketRepository) EmployeeForLastLogin(ctx context.Context, openid string) (*entities.CompanyEmployee, error) { |
||||
authTicket, err := r.TicketForLastLogin(ctx, openid) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return authTicket.Employee, nil |
||||
} |
||||
|
||||
func (r *CompanyAuthTicketRepository) GenerateForWechat(ctx context.Context, openid string) (employee *entities.CompanyEmployee, ticketString string, err error) { |
||||
employee, err = r.EmployeeForLastLogin(ctx, openid) |
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { |
||||
return |
||||
} |
||||
|
||||
// 通过最后一个 ticket 生成新的
|
||||
if employee == nil { |
||||
// 随机取一个员工信息
|
||||
var temp entities.CompanyEmployee |
||||
res := r.DB(ctx). |
||||
Where("wechat_openid=?", openid). |
||||
Preload("Departments"). |
||||
Preload("Company"). |
||||
Order("updated_at"). |
||||
First(&temp) |
||||
if err = res.Error; err != nil { |
||||
return |
||||
} |
||||
employee = &temp |
||||
} |
||||
|
||||
ticketString, err = ticket.Create(&ticket.Claims{ |
||||
UID: employee.ID, |
||||
Role: "company:employee", |
||||
}) |
||||
|
||||
authTicket := &entities.CompanyAuthTicket{ |
||||
CompanyID: employee.CompanyID, |
||||
EmployeeID: employee.ID, |
||||
WechatOpenid: employee.WechatOpenid, |
||||
Ticket: ticketString, |
||||
} |
||||
|
||||
err = r.Create(ctx, authTicket) |
||||
|
||||
return |
||||
} |
@ -1,16 +1,78 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"errors" |
||||
"github.com/labstack/echo/v4" |
||||
"gorm.io/gorm" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/errs" |
||||
"sorbet/internal/repositories" |
||||
"sorbet/internal/services/company/request" |
||||
"sorbet/pkg/crud" |
||||
"sorbet/pkg/db" |
||||
"sorbet/pkg/rsp" |
||||
"sorbet/pkg/wx" |
||||
) |
||||
|
||||
type CompanyEmployeeController struct { |
||||
crud.Controller[entities.CompanyEmployee, request.CompanyEmployeeUpsertRequest] |
||||
} |
||||
|
||||
func (c *CompanyEmployeeController) InitRoutes(r *echo.Group) { |
||||
c.RegisterRoutes("/company/employees", r) |
||||
func (ctr *CompanyEmployeeController) InitRoutes(r *echo.Group) { |
||||
ctr.RegisterRoutes("/company/employees", r) |
||||
} |
||||
|
||||
func (ctr *CompanyEmployeeController) RegisterRoutes(path string, r *echo.Group) { |
||||
ctr.Controller.RegisterRoutes(path, r) |
||||
r.POST(path+"/auth/login", ctr.Login) |
||||
} |
||||
|
||||
// Login 员工登录
|
||||
func (ctr *CompanyEmployeeController) Login(c echo.Context) error { |
||||
var req struct { |
||||
Code string `json:"code" xml:"code" form:"code"` |
||||
} |
||||
if err := c.Bind(&req); err != nil { |
||||
return err |
||||
} |
||||
login, err := wx.Login(c.Request().Context(), req.Code) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err = login.Err(); err != nil { |
||||
return err |
||||
} |
||||
|
||||
employee, ticketString, err := repositories. |
||||
NewCompanyAuthTicketRepository(ctr.MustORM(c)). |
||||
GenerateForWechat(c.Request().Context(), login.Openid) |
||||
if err != nil { |
||||
if !errors.Is(err, gorm.ErrRecordNotFound) { |
||||
return err |
||||
} |
||||
// 微信尚未绑定
|
||||
return errs.ErrWechatNotBound.WithData(echo.Map{ |
||||
"openid": login.Openid, |
||||
}) |
||||
} |
||||
|
||||
var companies []*entities.Company |
||||
err = ctr.MustORM(c). |
||||
Model(&entities.Company{}). |
||||
Where( |
||||
"EXISTS(?)", |
||||
db.Model(&entities.CompanyEmployee{}).Where("wechat_openid=?", employee.WechatOpenid), |
||||
). |
||||
Find(&companies). |
||||
Error |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
return rsp.Ok(c, echo.Map{ |
||||
"ticket": ticketString, |
||||
"openid": login.Openid, |
||||
"companies": companies, |
||||
"employee": employee, |
||||
}) |
||||
} |
||||
|
@ -0,0 +1,67 @@ |
||||
package controller |
||||
|
||||
import ( |
||||
"errors" |
||||
"github.com/labstack/echo/v4" |
||||
"github.com/rs/xid" |
||||
"gorm.io/gorm" |
||||
"sorbet/internal/entities" |
||||
"sorbet/internal/errs" |
||||
"sorbet/internal/services/system/request" |
||||
"sorbet/internal/util" |
||||
"sorbet/pkg/crud" |
||||
"sorbet/pkg/db" |
||||
"sorbet/pkg/rsp" |
||||
"sync" |
||||
) |
||||
|
||||
// 简单的使用内存实现,也就是重启的话必须重新登录
|
||||
var logins sync.Map |
||||
|
||||
type SystemAuthController struct{} |
||||
|
||||
func (s *SystemAuthController) InitRoutes(r *echo.Group) { |
||||
r.POST("/system/auth/login", s.Login) |
||||
} |
||||
|
||||
func (s *SystemAuthController) Login(c echo.Context) error { |
||||
req, err := crud.Bind[request.SystemAuthLoginRequest](c) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var user entities.SystemUser |
||||
err = db. |
||||
WithContext(c.Request().Context()). |
||||
Model(&user). |
||||
Where("username=?", req.Username). |
||||
First(&user). |
||||
Error |
||||
if err != nil { |
||||
if errors.Is(err, gorm.ErrRecordNotFound) { |
||||
return errs.ErrLoginWithUserpwd |
||||
} |
||||
return err |
||||
} |
||||
if !util.PasswordVerify(req.Password, user.Password) { |
||||
return errs.ErrLoginWithUserpwd |
||||
} |
||||
var ticket string |
||||
logins.Range(func(key, value any) bool { |
||||
x := value.(*entities.SystemUser) |
||||
if x.ID == user.ID { |
||||
ticket = key.(string) |
||||
return false |
||||
} |
||||
return true |
||||
}) |
||||
if ticket == "" { |
||||
ticket = xid.New().String() |
||||
logins.Store(ticket, &user) |
||||
|
||||
} |
||||
// todo 用户状态
|
||||
return rsp.Ok(c, echo.Map{ |
||||
"user": user, |
||||
"ticket": ticket, |
||||
}) |
||||
} |
@ -0,0 +1,15 @@ |
||||
package request |
||||
|
||||
import "sorbet/pkg/v" |
||||
|
||||
type SystemAuthLoginRequest struct { |
||||
Username string `json:"username" xml:"username" form:"username"` |
||||
Password string `json:"password" xml:"password" form:"password"` |
||||
} |
||||
|
||||
func (r *SystemAuthLoginRequest) Validate() error { |
||||
return v.Validate( |
||||
v.Value(r.Username, "username", "用户名").Required().MinLength(2), |
||||
v.Value(r.Password, "password", "登录密码").Required().MinLength(6), |
||||
) |
||||
} |
@ -0,0 +1,13 @@ |
||||
package util |
||||
|
||||
import "golang.org/x/crypto/bcrypt" |
||||
|
||||
func PasswordHash(password string) (string, error) { |
||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) |
||||
return string(bytes), err |
||||
} |
||||
|
||||
func PasswordVerify(password, hash string) bool { |
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) |
||||
return err == nil |
||||
} |
@ -0,0 +1 @@ |
||||
package util |
Loading…
Reference in new issue