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 |
package controller |
||||||
|
|
||||||
import ( |
import ( |
||||||
|
"errors" |
||||||
"github.com/labstack/echo/v4" |
"github.com/labstack/echo/v4" |
||||||
|
"gorm.io/gorm" |
||||||
"sorbet/internal/entities" |
"sorbet/internal/entities" |
||||||
|
"sorbet/internal/errs" |
||||||
|
"sorbet/internal/repositories" |
||||||
"sorbet/internal/services/company/request" |
"sorbet/internal/services/company/request" |
||||||
"sorbet/pkg/crud" |
"sorbet/pkg/crud" |
||||||
|
"sorbet/pkg/db" |
||||||
|
"sorbet/pkg/rsp" |
||||||
|
"sorbet/pkg/wx" |
||||||
) |
) |
||||||
|
|
||||||
type CompanyEmployeeController struct { |
type CompanyEmployeeController struct { |
||||||
crud.Controller[entities.CompanyEmployee, request.CompanyEmployeeUpsertRequest] |
crud.Controller[entities.CompanyEmployee, request.CompanyEmployeeUpsertRequest] |
||||||
} |
} |
||||||
|
|
||||||
func (c *CompanyEmployeeController) InitRoutes(r *echo.Group) { |
func (ctr *CompanyEmployeeController) InitRoutes(r *echo.Group) { |
||||||
c.RegisterRoutes("/company/employees", r) |
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