恢复 #12 from LiteyukiStudio/revert-11-main
Some checks failed
Deploy VitePress site to Spage / build (push) Failing after 49s
Build & Publish All-in-One / Build Frontend (push) Failing after 1m27s
Build & Publish All-in-One / Build Backend (386, freebsd, 386) (push) Has been skipped
Build & Publish All-in-One / Build Backend (arm, 5, linux, armv5) (push) Has been skipped
Build & Publish All-in-One / Build Backend (arm, 6, linux, armv6) (push) Has been skipped
Build & Publish All-in-One / Build Backend (arm, 7, linux, armv7) (push) Has been skipped
Build & Publish All-in-One / Build Backend (arm, freebsd, arm) (push) Has been skipped
Build & Publish All-in-One / Build Backend (arm64, darwin, arm64) (push) Has been skipped
Build & Publish All-in-One / Build Backend (arm64, freebsd, arm64) (push) Has been skipped
Build & Publish All-in-One / Build Backend (arm64, linux, arm64) (push) Has been skipped
Build & Publish All-in-One / Build Backend (arm64, windows, arm64) (push) Has been skipped
Build & Publish All-in-One / Build Backend (loong64, linux, loong64) (push) Has been skipped
Build & Publish All-in-One / Build Backend (mips, linux, mips) (push) Has been skipped
Build & Publish All-in-One / Build Backend (mips64, linux, mips64) (push) Has been skipped
Build & Publish All-in-One / Build Backend (mips64le, linux, mips64le) (push) Has been skipped
Build & Publish All-in-One / Build Backend (mipsle, linux, mipsle) (push) Has been skipped
Build & Publish All-in-One / Build Backend (ppc64, linux, ppc64) (push) Has been skipped
Build & Publish All-in-One / Build Backend (ppc64le, linux, ppc64le) (push) Has been skipped
Build & Publish All-in-One / Build Backend (riscv64, linux, riscv64) (push) Has been skipped
Build & Publish All-in-One / Build Backend (s390x, linux, s390x) (push) Has been skipped
Build & Publish All-in-One / Build Backend (sse2, 386, linux, 386-sse2) (push) Has been skipped
Build & Publish All-in-One / Build Backend (v1, amd64, darwin, amd64-v1) (push) Has been skipped
Build & Publish All-in-One / Build Backend (v1, amd64, freebsd, amd64-v1) (push) Has been skipped
Build & Publish All-in-One / Build Backend (v1, amd64, linux, amd64-v1) (push) Has been skipped
Build & Publish All-in-One / Build Backend (v1, amd64, windows, amd64-v1) (push) Has been skipped
Build & Publish All-in-One / Build Backend (v3, amd64, darwin, amd64-v3) (push) Has been skipped
Build & Publish All-in-One / Build Backend (v3, amd64, freebsd, amd64-v3) (push) Has been skipped
Build & Publish All-in-One / Build Backend (v3, amd64, linux, amd64-v3) (push) Has been skipped
Build & Publish All-in-One / Build Backend (v3, amd64, windows, amd64-v3) (push) Has been skipped
Build & Publish All-in-One / Build & Publish Container Images (push) Has been skipped
Build & Publish All-in-One / Publish GitHub Release (push) Has been skipped

 恢复 "🗑️ 删除不必要的英文注释"
This commit is contained in:
Nanaloveyuki
2025-06-18 18:40:43 +08:00
committed by GitHub
32 changed files with 348 additions and 212 deletions

View File

@ -12,6 +12,7 @@ type AdminApi struct{}
var Admin = AdminApi{} var Admin = AdminApi{}
// CreateUser 创建用户 // CreateUser 创建用户
// Create User
func (AdminApi) CreateUser(ctx context.Context, c *app.RequestContext) { func (AdminApi) CreateUser(ctx context.Context, c *app.RequestContext) {
var userDTO *UserDTO var userDTO *UserDTO
err := c.BindJSON(&userDTO) err := c.BindJSON(&userDTO)

View File

@ -17,6 +17,7 @@ type OrgApi struct{}
var Org = OrgApi{} var Org = OrgApi{}
// OrganizationDTO 组织信息数据传输对象 // OrganizationDTO 组织信息数据传输对象
// Organization Information Data Transfer Object (DTO)
func getOrg(ctx context.Context) *models.Organization { func getOrg(ctx context.Context) *models.Organization {
org, ok := ctx.Value("userOrg").(*models.Organization) org, ok := ctx.Value("userOrg").(*models.Organization)
if !ok { if !ok {
@ -26,6 +27,7 @@ func getOrg(ctx context.Context) *models.Organization {
} }
// UserOrgAuth 组织权限检查 // UserOrgAuth 组织权限检查
// Organization permission check
func (OrgApi) UserOrgAuth(ctx context.Context, c *app.RequestContext) { func (OrgApi) UserOrgAuth(ctx context.Context, c *app.RequestContext) {
user := middle.Auth.GetUser(ctx, c) user := middle.Auth.GetUser(ctx, c)
// 当 id 为空的 POST 请求默认为 create // 当 id 为空的 POST 请求默认为 create
@ -47,7 +49,7 @@ func (OrgApi) UserOrgAuth(ctx context.Context, c *app.RequestContext) {
return return
} }
// 判断权限 (GET 请求需要用户权限,其他请求需要管理员权限) // 判断权限 (GET 请求需要用户权限,其他请求需要管理员权限)
// Determine permissions (GET requests require user permissions, other requests require admin permissions)
var authType string var authType string
if string(c.Method()) == "GET" { if string(c.Method()) == "GET" {
authType = "member" authType = "member"
@ -65,6 +67,7 @@ func (OrgApi) UserOrgAuth(ctx context.Context, c *app.RequestContext) {
} }
// OrganizationDTO 组织信息数据传输对象 // OrganizationDTO 组织信息数据传输对象
// Organization Information Data Transfer Object (DTO)
func (OrgApi) ToDTO(org *models.Organization) OrganizationDTO { func (OrgApi) ToDTO(org *models.Organization) OrganizationDTO {
return OrganizationDTO{ return OrganizationDTO{
ID: org.ID, ID: org.ID,
@ -78,22 +81,23 @@ func (OrgApi) ToDTO(org *models.Organization) OrganizationDTO {
} }
// CreateOrganization 创建组织 // CreateOrganization 创建组织
// Create Organization
func (OrgApi) CreateOrganization(ctx context.Context, c *app.RequestContext) { func (OrgApi) CreateOrganization(ctx context.Context, c *app.RequestContext) {
// 绑定参数 // 绑定参数
// Bind parameters
req := &CreateOrgReq{} req := &CreateOrgReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {
resps.BadRequest(c, resps.ParameterError) resps.BadRequest(c, resps.ParameterError)
return return
} }
// 检验组织名称是否存在 // 检验组织名称是否存在
// Check if the organization name already exists
if store.Org.OrgNameIsExist(req.Name) { if store.Org.OrgNameIsExist(req.Name) {
resps.BadRequest(c, "organization name already exists") resps.BadRequest(c, "organization name already exists")
return return
} }
// 创建组织 // 创建组织
// Create organization
user := middle.Auth.GetUser(ctx, c) user := middle.Auth.GetUser(ctx, c)
org := models.Organization{ org := models.Organization{
Name: req.Name, Name: req.Name,
@ -114,6 +118,7 @@ func (OrgApi) CreateOrganization(ctx context.Context, c *app.RequestContext) {
} }
// UpdateOrganization 更新组织信息 // UpdateOrganization 更新组织信息
// Update Organization Information
func (OrgApi) UpdateOrganization(ctx context.Context, c *app.RequestContext) { func (OrgApi) UpdateOrganization(ctx context.Context, c *app.RequestContext) {
req := &UpdateOrgReq{} req := &UpdateOrgReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {
@ -121,7 +126,7 @@ func (OrgApi) UpdateOrganization(ctx context.Context, c *app.RequestContext) {
return return
} }
org := getOrg(ctx) org := getOrg(ctx)
// 更新 // 更新 Update
org.DisplayName = req.DisplayName org.DisplayName = req.DisplayName
org.Email = req.Email org.Email = req.Email
org.Description = *req.Description org.Description = *req.Description
@ -136,6 +141,7 @@ func (OrgApi) UpdateOrganization(ctx context.Context, c *app.RequestContext) {
} }
// GetOrganizationProject 获取组织项目 // GetOrganizationProject 获取组织项目
// Get Organization Projects
func (OrgApi) GetOrganizationProject(ctx context.Context, c *app.RequestContext) { func (OrgApi) GetOrganizationProject(ctx context.Context, c *app.RequestContext) {
req := &GetOrgProjectReq{} req := &GetOrgProjectReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {
@ -143,7 +149,7 @@ func (OrgApi) GetOrganizationProject(ctx context.Context, c *app.RequestContext)
return return
} }
org := getOrg(ctx) org := getOrg(ctx)
// 查询 // 查询 Query
projects, total, err := store.Project.ListByOwner(constants.OwnerTypeOrg, strconv.Itoa(int(org.ID)), req.Page, req.Limit) projects, total, err := store.Project.ListByOwner(constants.OwnerTypeOrg, strconv.Itoa(int(org.ID)), req.Page, req.Limit)
if err != nil { if err != nil {
resps.InternalServerError(c, "Failed to get projects") resps.InternalServerError(c, "Failed to get projects")
@ -161,6 +167,7 @@ func (OrgApi) GetOrganizationProject(ctx context.Context, c *app.RequestContext)
} }
// DeleteOrganization 删除组织 // DeleteOrganization 删除组织
// Delete Organization
func (OrgApi) DeleteOrganization(ctx context.Context, c *app.RequestContext) { func (OrgApi) DeleteOrganization(ctx context.Context, c *app.RequestContext) {
org := getOrg(ctx) org := getOrg(ctx)
// 删除组织 // 删除组织
@ -172,6 +179,7 @@ func (OrgApi) DeleteOrganization(ctx context.Context, c *app.RequestContext) {
} }
// GetOrganization 获取组织信息 // GetOrganization 获取组织信息
// Get Organization Information
func (OrgApi) GetOrganization(ctx context.Context, c *app.RequestContext) { func (OrgApi) GetOrganization(ctx context.Context, c *app.RequestContext) {
org := getOrg(ctx) org := getOrg(ctx)
resps.Ok(c, resps.OK, map[string]any{ resps.Ok(c, resps.OK, map[string]any{
@ -180,6 +188,7 @@ func (OrgApi) GetOrganization(ctx context.Context, c *app.RequestContext) {
} }
// GetOrganizationUsers 获取组织用户 // GetOrganizationUsers 获取组织用户
// Get Organization Users
func (OrgApi) GetOrganizationUsers(ctx context.Context, c *app.RequestContext) { func (OrgApi) GetOrganizationUsers(ctx context.Context, c *app.RequestContext) {
org := getOrg(ctx) org := getOrg(ctx)
resps.Ok(c, resps.OK, map[string]any{ resps.Ok(c, resps.OK, map[string]any{
@ -199,19 +208,20 @@ func (OrgApi) GetOrganizationUsers(ctx context.Context, c *app.RequestContext) {
} }
// AddOrganizationUser 添加组织用户 // AddOrganizationUser 添加组织用户
// Add Organization User
func (OrgApi) AddOrganizationUser(ctx context.Context, c *app.RequestContext) { func (OrgApi) AddOrganizationUser(ctx context.Context, c *app.RequestContext) {
req := &OrgUserReq{} req := &OrgUserReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {
resps.BadRequest(c, resps.ParameterError) resps.BadRequest(c, resps.ParameterError)
return return
} }
// 查询 // 查询 Query
user, err := store.User.GetByID(req.UserID) user, err := store.User.GetByID(req.UserID)
if err != nil || user == nil { if err != nil || user == nil {
resps.NotFound(c, resps.TargetNotFound) resps.NotFound(c, resps.TargetNotFound)
return return
} }
// 新增 // 新增 Add
org := getOrg(ctx) org := getOrg(ctx)
if req.Role == "member" { if req.Role == "member" {
org.Members = append(org.Members, user) org.Members = append(org.Members, user)
@ -230,19 +240,20 @@ func (OrgApi) AddOrganizationUser(ctx context.Context, c *app.RequestContext) {
} }
// DeleteOrganizationUser 删除组织用户 // DeleteOrganizationUser 删除组织用户
// Delete Organization User
func (OrgApi) DeleteOrganizationUser(ctx context.Context, c *app.RequestContext) { func (OrgApi) DeleteOrganizationUser(ctx context.Context, c *app.RequestContext) {
req := &OrgUserReq{} req := &OrgUserReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {
resps.BadRequest(c, resps.ParameterError) resps.BadRequest(c, resps.ParameterError)
return return
} }
// 查询 // 查询 Query
user, err := store.User.GetByID(req.UserID) user, err := store.User.GetByID(req.UserID)
if err != nil || user == nil { if err != nil || user == nil {
resps.NotFound(c, resps.TargetNotFound) resps.NotFound(c, resps.TargetNotFound)
return return
} }
// 删除 // 删除 Delete
org := getOrg(ctx) org := getOrg(ctx)
if req.Role == "member" { if req.Role == "member" {
for i, member := range org.Members { for i, member := range org.Members {

View File

@ -1,12 +1,13 @@
package handlers package handlers
// OrganizationDTO 组织信息数据传输对象 // OrganizationDTO 组织信息数据传输对象
// Organization Information Data Transfer Object (DTO)
type OrganizationDTO struct { type OrganizationDTO struct {
ID uint `json:"id"` // 组织ID ID uint `json:"id"` // 组织ID Organization ID
Name string `json:"name"` // 组织名称 Name string `json:"name"` // 组织名称 Organization Name
DisplayName *string `json:"display_name"` // 显示名称 DisplayName *string `json:"display_name"` // 显示名称 Display Name
Email *string `json:"email"` // 邮箱地址 Email *string `json:"email"` // 邮箱地址 Email Address
Description string `json:"description"` // 描述信息 Description string `json:"description"` // 描述信息 Description
AvatarURL *string `json:"avatar_url"` // 头像URL Avatar URL AvatarURL *string `json:"avatar_url"` // 头像URL Avatar URL
ProjectLimit int `json:"project_limit"` // 项目数量限制 Project Limit ProjectLimit int `json:"project_limit"` // 项目数量限制 Project Limit
Members []UserDTO `json:"members"` // 组织成员 Members Members []UserDTO `json:"members"` // 组织成员 Members

View File

@ -18,6 +18,7 @@ type ProjectApi struct {
var Project = ProjectApi{} var Project = ProjectApi{}
// ProjectDTO 项目信息数据传输对象 // ProjectDTO 项目信息数据传输对象
// Project Information Data Transfer Object (DTO)
func (ProjectApi) toDTO(project *models.Project, full bool) ProjectDTO { func (ProjectApi) toDTO(project *models.Project, full bool) ProjectDTO {
projectDto := ProjectDTO{ projectDto := ProjectDTO{
Description: project.Description, Description: project.Description,
@ -40,6 +41,7 @@ func (ProjectApi) toDTO(project *models.Project, full bool) ProjectDTO {
} }
// GetProject 获取项目信息 // GetProject 获取项目信息
// Get project information
func getProject(ctx context.Context) *models.Project { func getProject(ctx context.Context) *models.Project {
project, ok := ctx.Value("userProject").(*models.Project) project, ok := ctx.Value("userProject").(*models.Project)
if !ok { if !ok {
@ -49,6 +51,7 @@ func getProject(ctx context.Context) *models.Project {
} }
// UserProjectAuth 用户项目权限认证 // UserProjectAuth 用户项目权限认证
// User project authorization
func (ProjectApi) UserProjectAuth(ctx context.Context, c *app.RequestContext) { func (ProjectApi) UserProjectAuth(ctx context.Context, c *app.RequestContext) {
user := middle.Auth.GetUser(ctx, c) user := middle.Auth.GetUser(ctx, c)
projectIdStr := c.Param("id") projectIdStr := c.Param("id")
@ -69,12 +72,12 @@ func (ProjectApi) UserProjectAuth(ctx context.Context, c *app.RequestContext) {
c.Abort() c.Abort()
return return
} }
// 项目权限判断 // 项目权限判断 Project authorization check
if store.Project.UserIsOwner(project, user.ID) { if store.Project.UserIsOwner(project, user.ID) {
context.WithValue(ctx, "userProject", project) context.WithValue(ctx, "userProject", project)
return return
} }
// 组织权限判断 // 组织权限判断 Organization authorization check
if project.OwnerType == constants.OwnerTypeOrg { if project.OwnerType == constants.OwnerTypeOrg {
// 组织查询 // 组织查询
org, err := store.Org.GetOrgById(project.OwnerID) org, err := store.Org.GetOrgById(project.OwnerID)
@ -106,6 +109,7 @@ func (ProjectApi) UserProjectAuth(ctx context.Context, c *app.RequestContext) {
} }
// Create 创建项目 // Create 创建项目
// Create project
func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) { func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) {
req := CreateProjectReq{} req := CreateProjectReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {
@ -113,10 +117,10 @@ func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) {
return return
} }
user := middle.Auth.GetUser(ctx, c) user := middle.Auth.GetUser(ctx, c)
// 校验权限 // 校验权限 Check permissions
if req.OwnerType == constants.OwnerTypeOrg { if req.OwnerType == constants.OwnerTypeOrg {
// 如果为组织,需要具有组织管理员权限 // 如果为组织,需要具有组织管理员权限
// If it is an organization, you need to have the organization administrator permission
org, err := store.Org.GetOrgById(req.OwnerID) org, err := store.Org.GetOrgById(req.OwnerID)
if err != nil || org == nil { if err != nil || org == nil {
resps.InternalServerError(c, resps.ParameterError) resps.InternalServerError(c, resps.ParameterError)
@ -127,7 +131,7 @@ func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) {
} }
} else if req.OwnerType == constants.OwnerTypeUser { } else if req.OwnerType == constants.OwnerTypeUser {
// 如果为用户,仅允许为自己添加 // 如果为用户,仅允许为自己添加
// If it is a user, only allow adding for user-self
req.OwnerID = user.ID req.OwnerID = user.ID
} else { } else {
resps.BadRequest(c, resps.ParameterError) resps.BadRequest(c, resps.ParameterError)
@ -151,6 +155,7 @@ func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) {
} }
// Update 更新项目 // Update 更新项目
// Update project
func (ProjectApi) Update(ctx context.Context, c *app.RequestContext) { func (ProjectApi) Update(ctx context.Context, c *app.RequestContext) {
req := UpdateProjectReq{} req := UpdateProjectReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {
@ -162,7 +167,7 @@ func (ProjectApi) Update(ctx context.Context, c *app.RequestContext) {
resps.NotFound(c, resps.TargetNotFound) resps.NotFound(c, resps.TargetNotFound)
return return
} }
// 更新数据 // 更新数据 Update data
project.Description = *req.Description project.Description = *req.Description
project.DisplayName = req.DisplayName project.DisplayName = req.DisplayName
project.Name = *req.Name project.Name = *req.Name
@ -176,6 +181,7 @@ func (ProjectApi) Update(ctx context.Context, c *app.RequestContext) {
} }
// Delete 删除项目 // Delete 删除项目
// Delete project
func (ProjectApi) Delete(ctx context.Context, c *app.RequestContext) { func (ProjectApi) Delete(ctx context.Context, c *app.RequestContext) {
project := getProject(ctx) project := getProject(ctx)
if project == nil { if project == nil {
@ -190,6 +196,7 @@ func (ProjectApi) Delete(ctx context.Context, c *app.RequestContext) {
} }
// Info 获取项目信息 // Info 获取项目信息
// Get project information
func (ProjectApi) Info(ctx context.Context, c *app.RequestContext) { func (ProjectApi) Info(ctx context.Context, c *app.RequestContext) {
project := getProject(ctx) project := getProject(ctx)
if project == nil { if project == nil {
@ -202,6 +209,7 @@ func (ProjectApi) Info(ctx context.Context, c *app.RequestContext) {
} }
// GetOwners 获取项目所有者列表 // GetOwners 获取项目所有者列表
// Get project owner list
func (ProjectApi) GetOwners(ctx context.Context, c *app.RequestContext) { func (ProjectApi) GetOwners(ctx context.Context, c *app.RequestContext) {
project := getProject(ctx) project := getProject(ctx)
if project == nil { if project == nil {
@ -219,6 +227,7 @@ func (ProjectApi) GetOwners(ctx context.Context, c *app.RequestContext) {
} }
// AddOwner 添加项目所有者 // AddOwner 添加项目所有者
// Add project owner
func (ProjectApi) AddOwner(ctx context.Context, c *app.RequestContext) { func (ProjectApi) AddOwner(ctx context.Context, c *app.RequestContext) {
project := getProject(ctx) project := getProject(ctx)
if project == nil { if project == nil {
@ -236,14 +245,14 @@ func (ProjectApi) AddOwner(ctx context.Context, c *app.RequestContext) {
resps.NotFound(c, resps.TargetNotFound) resps.NotFound(c, resps.TargetNotFound)
return return
} }
// 判断用户是否已存在权限列表 // 判断用户是否已存在权限列表 Check if the user already exists in the permission list
for _, owner := range project.Owners { for _, owner := range project.Owners {
if owner.ID == user.ID { if owner.ID == user.ID {
resps.BadRequest(c, "User already exists in the permission list") resps.BadRequest(c, "User already exists in the permission list")
return return
} }
} }
// 添加用户 // 添加用户 Add user
if err := store.Project.AddOwner(project, user); err != nil { if err := store.Project.AddOwner(project, user); err != nil {
resps.InternalServerError(c, resps.ParameterError) resps.InternalServerError(c, resps.ParameterError)
return return
@ -254,6 +263,7 @@ func (ProjectApi) AddOwner(ctx context.Context, c *app.RequestContext) {
} }
// DeleteOwner 删除项目所有者 // DeleteOwner 删除项目所有者
// Delete project owner
func (ProjectApi) DeleteOwner(ctx context.Context, c *app.RequestContext) { func (ProjectApi) DeleteOwner(ctx context.Context, c *app.RequestContext) {
project := getProject(ctx) project := getProject(ctx)
if project == nil { if project == nil {
@ -265,13 +275,13 @@ func (ProjectApi) DeleteOwner(ctx context.Context, c *app.RequestContext) {
resps.BadRequest(c, resps.ParameterError) resps.BadRequest(c, resps.ParameterError)
return return
} }
// 查询用户 // 查询用户 Query user
user, err := store.User.GetByID(req.UserID) user, err := store.User.GetByID(req.UserID)
if err != nil || user == nil { if err != nil || user == nil {
resps.NotFound(c, resps.TargetNotFound) resps.NotFound(c, resps.TargetNotFound)
return return
} }
// 删除用户 // 删除用户 Delete user
if err := store.Project.DeleteOwner(project, user); err != nil { if err := store.Project.DeleteOwner(project, user); err != nil {
resps.InternalServerError(c, resps.ParameterError) resps.InternalServerError(c, resps.ParameterError)
return return
@ -282,6 +292,7 @@ func (ProjectApi) DeleteOwner(ctx context.Context, c *app.RequestContext) {
} }
// GetSites 获取站点列表 // GetSites 获取站点列表
// Get site list
func (ProjectApi) GetSites(ctx context.Context, c *app.RequestContext) { func (ProjectApi) GetSites(ctx context.Context, c *app.RequestContext) {
req := GetSiteListReq{} req := GetSiteListReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {

View File

@ -1,43 +1,48 @@
package handlers package handlers
// ProjectDTO 项目信息数据传输对象 // ProjectDTO 项目信息数据传输对象
// Project Information Data Transfer Object (DTO)
type ProjectDTO struct { type ProjectDTO struct {
ID uint `json:"id"` // 项目ID ID uint `json:"id"` // 项目ID Project ID
Name string `json:"name"` // 项目名称 Name string `json:"name"` // 项目名称 Project Name
DisplayName *string `json:"display_name"` // 项目显示名称 DisplayName *string `json:"display_name"` // 项目显示名称 Project Display Name
Description string `json:"description"` // 项目描述 Description string `json:"description"` // 项目描述 Project Description
OwnerType string `json:"owner_type"` // 项目拥有者类型 OwnerType string `json:"owner_type"` // 项目拥有者类型 Project Owner Type
OwnerID uint `json:"owner_id"` // 项目拥有者ID OwnerID uint `json:"owner_id"` // 项目拥有者ID Project Owner ID
Owners []UserDTO `json:"owners"` // 项目拥有者列表 Owners []UserDTO `json:"owners"` // 项目拥有者列表 Project Owner List
SiteLimit int `json:"site_limit"` // 项目站点数量限制 SiteLimit int `json:"site_limit"` // 项目站点数量限制 Project Site Limit
} }
// CreateProjectReq 创建项目请求参数 // CreateProjectReq 创建项目请求参数
// Create Project Request Parameters
type CreateProjectReq struct { type CreateProjectReq struct {
Name string `json:"name" binding:"required"` // 项目名称 Name string `json:"name" binding:"required"` // 项目名称 Project Name
DisplayName *string `json:"display_name"` // 项目显示名称 DisplayName *string `json:"display_name"` // 项目显示名称 Project Display Name
Description string `json:"description"` // 项目描述 Description string `json:"description"` // 项目描述 Project Description
OwnerType string `json:"owner_type" binding:"required" vd:"in($,'user','organization')"` // 项目拥有者类型 OwnerType string `json:"owner_type" binding:"required" vd:"in($,'user','organization')"` // 项目拥有者类型 Project Owner Type
OwnerID uint `json:"owner_id" binding:"required"` // 项目拥有者ID OwnerID uint `json:"owner_id" binding:"required"` // 项目拥有者ID Project Owner ID
} }
// UpdateProjectReq 更新项目请求参数 // UpdateProjectReq 更新项目请求参数
// Update Project Request Parameters
type UpdateProjectReq struct { type UpdateProjectReq struct {
Name *string `json:"name"` // 项目名称 Name *string `json:"name"` // 项目名称 Project Name
DisplayName *string `json:"display_name"` // 项目显示名称 DisplayName *string `json:"display_name"` // 项目显示名称 Project Display Name
Description *string `json:"description"` // 项目描述 Description *string `json:"description"` // 项目描述 Project Description
} }
// ProjectUserReq 项目用户请求参数 // ProjectUserReq 项目用户请求参数
// Project User Request Parameters
type ProjectUserReq struct { type ProjectUserReq struct {
UserID uint `json:"user_id" binding:"required"` // 用户ID UserID uint `json:"user_id" binding:"required"` // 用户ID User ID
} }
// GetProjectListReq 获取项目列表请求参数 // GetProjectListReq 获取项目列表请求参数
// Get Project List Request Parameters
type GetSiteListReq struct { type GetSiteListReq struct {
Page int `form:"page" binding:"required"` // 页码 Page int `form:"page" binding:"required"` // 页码 Page
Limit int `form:"limit" binding:"required"` // 每页数量 Limit int `form:"limit" binding:"required"` // 每页数量 Page Limit
Project string `form:"project" binding:"required"` // 项目名称 Project string `form:"project" binding:"required"` // 项目名称 Project Name
SiteName string `form:"site_name"` // 站点名称 SiteName string `form:"site_name"` // 站点名称 Site Name
} }

View File

@ -2,10 +2,9 @@ package handlers
import ( import (
"context" "context"
"strconv"
"github.com/LiteyukiStudio/spage/spage/models" "github.com/LiteyukiStudio/spage/spage/models"
"github.com/LiteyukiStudio/spage/spage/store" "github.com/LiteyukiStudio/spage/spage/store"
"strconv"
"github.com/LiteyukiStudio/spage/resps" "github.com/LiteyukiStudio/spage/resps"
"github.com/cloudwego/hertz/pkg/app" "github.com/cloudwego/hertz/pkg/app"
@ -17,6 +16,7 @@ type SiteApi struct {
var Site = SiteApi{} var Site = SiteApi{}
// ToDTO 站点信息数据传输对象 // ToDTO 站点信息数据传输对象
// Site Information Data Transfer Object (DTO)
func (SiteApi) ToDTO(site *models.Site, full bool) SiteDTO { func (SiteApi) ToDTO(site *models.Site, full bool) SiteDTO {
siteDTO := SiteDTO{ siteDTO := SiteDTO{
Description: site.Description, Description: site.Description,
@ -60,6 +60,7 @@ func (SiteApi) SiteAuth(ctx context.Context, c *app.RequestContext) {
} }
// Create 创建站点 // Create 创建站点
// Create Site
func (SiteApi) Create(ctx context.Context, c *app.RequestContext) { func (SiteApi) Create(ctx context.Context, c *app.RequestContext) {
req := CreateSiteReq{} req := CreateSiteReq{}
if err := c.BindAndValidate(&req); err != nil { if err := c.BindAndValidate(&req); err != nil {

View File

@ -1,28 +1,30 @@
package handlers package handlers
// SiteDTO 网站详情 // SiteDTO 网站详情
// Site Detail
type SiteDTO struct { type SiteDTO struct {
ID uint `json:"id"` // 网站ID ID uint `json:"id"` // 网站ID WebSiteID
Name string `json:"name"` // 网站名称 Name string `json:"name"` // 网站名称 WebSiteName
Description string `json:"description"` // 网站描述 Description string `json:"description"` // 网站描述 WebSiteDescription
ProjectID uint `json:"project_id"` // 项目ID ProjectID uint `json:"project_id"` // 项目ID ProjectID
Project ProjectDTO `json:"project"` // 项目详情 Project ProjectDTO `json:"project"` // 项目详情 ProjectDetail
SubDomain *string `json:"sub_domain"` // 子域名 SubDomain *string `json:"sub_domain"` // 子域名 SubDomain
Domains []string `json:"domains"` // 域名 Domains []string `json:"domains"` // 域名 Domains
} }
// CreateSiteReq 创建网站请求参数 // CreateSiteReq 创建网站请求参数
// Create Site Request Parameters
type CreateSiteReq struct { type CreateSiteReq struct {
Name string `json:"name" binding:"required"` // 网站名称 Name string `json:"name" binding:"required"` // 网站名称 WebSiteName
Description string `json:"description"` // 网站描述 Description string `json:"description"` // 网站描述 WebSiteDescription
ProjectID uint `json:"project_id" binding:"required"` // 项目ID ProjectID uint `json:"project_id" binding:"required"` // 项目ID ProjectID
SubDomain *string `json:"sub_domain"` // 子域名 SubDomain *string `json:"sub_domain"` // 子域名 SubDomain
Domains []string `json:"domains"` // 域名 Domains []string `json:"domains"` // 域名 Domains
} }
type UpdateSiteReq struct { type UpdateSiteReq struct {
Name *string `json:"name"` // 网站名称 Name *string `json:"name"` // 网站名称 WebSiteName
Description *string `json:"description"` // 网站描述 Description *string `json:"description"` // 网站描述 WebSiteDescription
SubDomain *string `json:"sub_domain"` // 子域名 SubDomain *string `json:"sub_domain"` // 子域名 SubDomain
Domains []string `json:"domains"` // 域名 Domains []string `json:"domains"` // 域名 Domains
} }

View File

@ -21,6 +21,7 @@ type UserApi struct{}
var User = UserApi{} var User = UserApi{}
// UserDTO 用户信息数据传输对象 // UserDTO 用户信息数据传输对象
// User Information Data Transfer Object (DTO)
func (UserApi) ToDTO(user *models.User, self bool) UserDTO { func (UserApi) ToDTO(user *models.User, self bool) UserDTO {
userDTO := UserDTO{ userDTO := UserDTO{
ID: user.ID, ID: user.ID,
@ -38,6 +39,7 @@ func (UserApi) ToDTO(user *models.User, self bool) UserDTO {
} }
// Login 用户登录 // Login 用户登录
// User login
func (UserApi) Login(ctx context.Context, c *app.RequestContext) { func (UserApi) Login(ctx context.Context, c *app.RequestContext) {
loginReq := &LoginReq{} loginReq := &LoginReq{}
// TODO: 这里需要验证验证码 // TODO: 这里需要验证验证码
@ -90,6 +92,7 @@ func (UserApi) Login(ctx context.Context, c *app.RequestContext) {
} }
// Logout 用户登出 // Logout 用户登出
// User logout
func (UserApi) Logout(ctx context.Context, c *app.RequestContext) { func (UserApi) Logout(ctx context.Context, c *app.RequestContext) {
// 删除cookie // 删除cookie
c.SetCookie("token", "", -1, "/", "", protocol.CookieSameSiteLaxMode, true, true) c.SetCookie("token", "", -1, "/", "", protocol.CookieSameSiteLaxMode, true, true)
@ -98,6 +101,7 @@ func (UserApi) Logout(ctx context.Context, c *app.RequestContext) {
} }
// GetCaptcha 获取验证码 // GetCaptcha 获取验证码
// Get captcha
func (UserApi) GetCaptcha(ctx context.Context, c *app.RequestContext) { func (UserApi) GetCaptcha(ctx context.Context, c *app.RequestContext) {
resps.Ok(c, "ok", map[string]any{ resps.Ok(c, "ok", map[string]any{
"provider": config.CaptchaType, "provider": config.CaptchaType,
@ -107,6 +111,7 @@ func (UserApi) GetCaptcha(ctx context.Context, c *app.RequestContext) {
} }
// GetUserOrgs 获取用户的组织 // GetUserOrgs 获取用户的组织
// Get user organizations
func (UserApi) GetOrgs(ctx context.Context, c *app.RequestContext) { func (UserApi) GetOrgs(ctx context.Context, c *app.RequestContext) {
userID := c.Param("id") userID := c.Param("id")
crtUser := middle.Auth.GetUser(ctx, c) crtUser := middle.Auth.GetUser(ctx, c)
@ -133,6 +138,7 @@ func (UserApi) GetOrgs(ctx context.Context, c *app.RequestContext) {
} }
// GetUserProjects 获取用户的项目 // GetUserProjects 获取用户的项目
// Get user projects
func (UserApi) GetProjects(ctx context.Context, c *app.RequestContext) { func (UserApi) GetProjects(ctx context.Context, c *app.RequestContext) {
userID := c.Param("id") userID := c.Param("id")
crtUser := middle.Auth.GetUser(ctx, c) crtUser := middle.Auth.GetUser(ctx, c)
@ -157,6 +163,7 @@ func (UserApi) GetProjects(ctx context.Context, c *app.RequestContext) {
} }
// GetUser 获取用户信息 // GetUser 获取用户信息
// Get user information
func (UserApi) GetUser(ctx context.Context, c *app.RequestContext) { func (UserApi) GetUser(ctx context.Context, c *app.RequestContext) {
userID := c.Param("id") userID := c.Param("id")
crtUser := middle.Auth.GetUser(ctx, c) crtUser := middle.Auth.GetUser(ctx, c)
@ -177,6 +184,7 @@ func (UserApi) GetUser(ctx context.Context, c *app.RequestContext) {
} }
// Register 用户注册 // Register 用户注册
// User registration
func (UserApi) Register(ctx context.Context, c *app.RequestContext) { func (UserApi) Register(ctx context.Context, c *app.RequestContext) {
// 接收参数 // 接收参数
request := &RegisterReq{} request := &RegisterReq{}
@ -221,6 +229,7 @@ func (UserApi) Register(ctx context.Context, c *app.RequestContext) {
} }
// UpdateUser 更新用户信息 // UpdateUser 更新用户信息
// Update user information
func (UserApi) UpdateUser(ctx context.Context, c *app.RequestContext) { func (UserApi) UpdateUser(ctx context.Context, c *app.RequestContext) {
userDTO := &UserDTO{} userDTO := &UserDTO{}
if err := c.BindJSON(userDTO); err != nil { if err := c.BindJSON(userDTO); err != nil {

View File

@ -1,30 +1,33 @@
package handlers package handlers
// RegisterReq 注册请求结构体 // RegisterReq 注册请求结构体
// Registration request structure
type RegisterReq struct { type RegisterReq struct {
Username string `json:"username" binding:"required"` // 用户名 Username string `json:"username" binding:"required"` // 用户名 Username
Password string `json:"password" binding:"required"` // 密码 Password string `json:"password" binding:"required"` // 密码 Password
Email string `json:"email" binding:"required"` // 邮箱 Email string `json:"email" binding:"required"` // 邮箱 Email
} }
// LoginReq 登录请求结构体 // LoginReq 登录请求结构体
// Login request structure
type LoginReq struct { type LoginReq struct {
Username string `json:"username" binding:"required"` // 用户名 Username string `json:"username" binding:"required"` // 用户名 Username
Password string `json:"password" binding:"required"` // 密码 Password string `json:"password" binding:"required"` // 密码 Password
CaptchaToken string `json:"captcha_token" binding:"required"` // 验证码 CaptchaToken string `json:"captcha_token" binding:"required"` // 验证码 Token
} }
// OrganizationDTO 组织信息数据传输对象 // OrganizationDTO 组织信息数据传输对象
// Organization Information Data Transfer Object (DTO)
type UserDTO struct { type UserDTO struct {
ID uint `json:"id"` // 用户ID ID uint `json:"id"` // 用户ID User ID
Name string `json:"name"` // 用户名 Name string `json:"name"` // 用户名 Username
DisplayName *string `json:"display_name"` // 显示名称 DisplayName *string `json:"display_name"` // 显示名称 DisplayName
Email *string `json:"email"` // 邮箱 Email *string `json:"email"` // 邮箱 Email
Description string `json:"description"` // 描述 Description string `json:"description"` // 描述 Description
Avatar *string `json:"avatar_url"` // 头像 Avatar *string `json:"avatar_url"` // 头像 Avatar URL
Role string `json:"role"` // 角色 Role string `json:"role"` // 角色 Role
Organizations []OrganizationDTO `json:"organizations"` // 组织 Organizations []OrganizationDTO `json:"organizations"` // 组织 Organizations
Language string `json:"language"` // 语言 Language string `json:"language"` // 语言 Language
//Password string `json:"password"` // 密码 //Password string `json:"password"` // 密码 Password
} }

View File

@ -11,6 +11,7 @@ import (
) )
// getMimeType 根据文件扩展名返回相应的 MIME 类型 // getMimeType 根据文件扩展名返回相应的 MIME 类型
// as the file extension returns the corresponding MIME type
func getMimeType(path string) string { func getMimeType(path string) string {
// 这里可以根据文件扩展名返回相应的 MIME 类型 // 这里可以根据文件扩展名返回相应的 MIME 类型
ext := filepath.Ext(path) ext := filepath.Ext(path)
@ -18,6 +19,7 @@ func getMimeType(path string) string {
} }
// WebHandler 处理静态文件请求的 Handler // WebHandler 处理静态文件请求的 Handler
// Handler for static file requests
func WebHandler(ctx context.Context, c *app.RequestContext) { func WebHandler(ctx context.Context, c *app.RequestContext) {
path := "dist" + string(c.Path()) path := "dist" + string(c.Path())
file, err := static.WebFS.Open(path) file, err := static.WebFS.Open(path)

View File

@ -3,14 +3,13 @@ package middle
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"time"
"github.com/LiteyukiStudio/spage/config" "github.com/LiteyukiStudio/spage/config"
"github.com/LiteyukiStudio/spage/constants" "github.com/LiteyukiStudio/spage/constants"
models "github.com/LiteyukiStudio/spage/spage/models" models "github.com/LiteyukiStudio/spage/spage/models"
store "github.com/LiteyukiStudio/spage/spage/store" store "github.com/LiteyukiStudio/spage/spage/store"
"github.com/LiteyukiStudio/spage/utils" "github.com/LiteyukiStudio/spage/utils"
"strings"
"time"
"github.com/LiteyukiStudio/spage/resps" "github.com/LiteyukiStudio/spage/resps"
"github.com/cloudwego/hertz/pkg/app" "github.com/cloudwego/hertz/pkg/app"
@ -22,6 +21,7 @@ type authType struct{}
var Auth = authType{} var Auth = authType{}
// PersistentHandler 持久化处理函数,使用依赖注入到 utils 中防止循环引用 // PersistentHandler 持久化处理函数,使用依赖注入到 utils 中防止循环引用
// Persistent Handler Function, using dependency injection to prevent circular references
func PersistentHandler(userID uint) (*models.Token, error) { func PersistentHandler(userID uint) (*models.Token, error) {
token, err := store.JWT.CreateToken(userID) token, err := store.JWT.CreateToken(userID)
if err != nil { if err != nil {
@ -31,25 +31,31 @@ func PersistentHandler(userID uint) (*models.Token, error) {
} }
// RevokeChecker 令牌撤销检查器,使用依赖注入到 utils 中防止循环引用 // RevokeChecker 令牌撤销检查器,使用依赖注入到 utils 中防止循环引用
// Token Revocation Checker, using dependency injection to prevent circular references
func RevokeChecker(tokenID uint) bool { func RevokeChecker(tokenID uint) bool {
return store.JWT.IsTokenRevoked(tokenID) return store.JWT.IsTokenRevoked(tokenID)
} }
// UseAuth 中间件函数 // UseAuth 中间件函数
// Middleware function for authentication
func (authType) UseAuth() app.HandlerFunc { func (authType) UseAuth() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) { return func(ctx context.Context, c *app.RequestContext) {
// 1. 检查认证方式 // 1. 检查认证方式
// 1. Check authentication method
authHeader := string(c.GetHeader("Authorization")) authHeader := string(c.GetHeader("Authorization"))
// 认证方式1使用 Authorization Header // 认证方式1使用 Authorization Header
// Authentication method 1: Use Authorization Header
if authHeader != "" { if authHeader != "" {
// 检查 token 是否以 "Bearer " 开头,如果是,则去掉前缀 // 检查 token 是否以 "Bearer " 开头,如果是,则去掉前缀
// Check if the token starts with "Bearer ", if so, remove the prefix
token := authHeader token := authHeader
if strings.HasPrefix(token, "Bearer ") { if strings.HasPrefix(token, "Bearer ") {
token = strings.TrimPrefix(token, "Bearer ") token = strings.TrimPrefix(token, "Bearer ")
} }
// 验证令牌 // 验证令牌
// Verify token
claims, err := utils.Token.ParseToken(token, RevokeChecker) claims, err := utils.Token.ParseToken(token, RevokeChecker)
if err != nil { if err != nil {
resps.Unauthorized(c, "Invalid token") resps.Unauthorized(c, "Invalid token")
@ -58,15 +64,18 @@ func (authType) UseAuth() app.HandlerFunc {
} }
// 将用户信息存储到上下文中 // 将用户信息存储到上下文中
// Store user information in the context
c.Set("user", claims.UserID) c.Set("user", claims.UserID)
c.Next(ctx) c.Next(ctx)
return return
} }
// 认证方式2使用 Cookie启用无感刷新 // 认证方式2使用 Cookie启用无感刷新
// Authentication method 2: Use Cookie (Enable silent refresh)
token := string(c.Cookie("token")) token := string(c.Cookie("token"))
if token == "" { if token == "" {
// 尝试通过 refresh_token 刷新 // 尝试通过 refresh_token 刷新
// Try to refresh by refresh_token
refreshToken := string(c.Cookie("refresh_token")) refreshToken := string(c.Cookie("refresh_token"))
if refreshToken == "" { if refreshToken == "" {
resps.BadRequest(c, "Refresh token not found 1") resps.BadRequest(c, "Refresh token not found 1")
@ -75,6 +84,7 @@ func (authType) UseAuth() app.HandlerFunc {
} }
// 验证刷新令牌 // 验证刷新令牌
// Verify refresh token
refreshClaims, err := utils.Token.ParseToken(refreshToken, RevokeChecker) refreshClaims, err := utils.Token.ParseToken(refreshToken, RevokeChecker)
if err != nil { if err != nil {
resps.Unauthorized(c, "Refresh token expired or invalid 2") resps.Unauthorized(c, "Refresh token expired or invalid 2")
@ -83,6 +93,7 @@ func (authType) UseAuth() app.HandlerFunc {
} }
// 生成新的访问令牌 // 生成新的访问令牌
// Generate new access token
newToken, err := utils.Token.CreateToken(refreshClaims.UserID, time.Duration(config.TokenExpireTime)*time.Second, false, PersistentHandler) newToken, err := utils.Token.CreateToken(refreshClaims.UserID, time.Duration(config.TokenExpireTime)*time.Second, false, PersistentHandler)
if err != nil { if err != nil {
resps.InternalServerError(c, "Create access token failed 3") resps.InternalServerError(c, "Create access token failed 3")
@ -91,18 +102,22 @@ func (authType) UseAuth() app.HandlerFunc {
} }
// 设置新的访问令牌 // 设置新的访问令牌
// Set new access token
c.SetCookie("token", newToken, config.TokenExpireTime, "/", "", protocol.CookieSameSiteLaxMode, true, true) c.SetCookie("token", newToken, config.TokenExpireTime, "/", "", protocol.CookieSameSiteLaxMode, true, true)
// 保存用户信息并继续请求 // 保存用户信息并继续请求
// Save user information and continue request
c.Set("user", refreshClaims.UserID) c.Set("user", refreshClaims.UserID)
c.Next(ctx) c.Next(ctx)
return return
} }
// Cookie 中存在 token验证其有效性 // Cookie 中存在 token验证其有效性
// Cookie contains token, verify its validity
claims, err := utils.Token.ParseToken(token, RevokeChecker) claims, err := utils.Token.ParseToken(token, RevokeChecker)
if err != nil { if err != nil {
// token 无效,尝试刷新 // token 无效,尝试刷新
// Token is invalid, try to refresh
refreshToken := string(c.Cookie("refresh_token")) refreshToken := string(c.Cookie("refresh_token"))
if refreshToken == "" { if refreshToken == "" {
resps.Unauthorized(c, "Refresh token not found 4") resps.Unauthorized(c, "Refresh token not found 4")
@ -111,6 +126,7 @@ func (authType) UseAuth() app.HandlerFunc {
} }
// 验证刷新令牌 // 验证刷新令牌
// Verify refresh token
refreshClaims, err := utils.Token.ParseToken(refreshToken, RevokeChecker) refreshClaims, err := utils.Token.ParseToken(refreshToken, RevokeChecker)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -120,6 +136,7 @@ func (authType) UseAuth() app.HandlerFunc {
} }
// 生成新的访问令牌 // 生成新的访问令牌
// Generate new access token
newToken, err := utils.Token.CreateToken(refreshClaims.UserID, time.Duration(config.TokenExpireTime)*time.Second, false, PersistentHandler) newToken, err := utils.Token.CreateToken(refreshClaims.UserID, time.Duration(config.TokenExpireTime)*time.Second, false, PersistentHandler)
if err != nil { if err != nil {
resps.InternalServerError(c, "Create access token failed 6") resps.InternalServerError(c, "Create access token failed 6")
@ -128,21 +145,25 @@ func (authType) UseAuth() app.HandlerFunc {
} }
// 设置新的访问令牌 // 设置新的访问令牌
// Set new access token
c.SetCookie("token", newToken, config.TokenExpireTime, "/", "", protocol.CookieSameSiteLaxMode, true, true) c.SetCookie("token", newToken, config.TokenExpireTime, "/", "", protocol.CookieSameSiteLaxMode, true, true)
// 保存用户信息并继续请求 // 保存用户信息并继续请求
// Save user information and continue request
c.Set("user", refreshClaims.UserID) c.Set("user", refreshClaims.UserID)
c.Next(ctx) c.Next(ctx)
return return
} }
// token 有效,继续请求 // token 有效,继续请求
// Token is valid, continue request
ctx = context.WithValue(ctx, "user", claims.UserID) ctx = context.WithValue(ctx, "user", claims.UserID)
c.Next(ctx) c.Next(ctx)
} }
} }
// IsAdmin 是一个中间件,用于检查用户是否为管理员 // IsAdmin 是一个中间件,用于检查用户是否为管理员
// IsAdmin is a middleware that checks if the user is an admin
func (authType) IsAdmin() app.HandlerFunc { func (authType) IsAdmin() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) { return func(ctx context.Context, c *app.RequestContext) {
user := Auth.GetUser(ctx, c) user := Auth.GetUser(ctx, c)
@ -156,6 +177,7 @@ func (authType) IsAdmin() app.HandlerFunc {
} }
// GetUser 从已认证的上下文中获取用户信息,如果用户不存在则终止请求并返回 // GetUser 从已认证的上下文中获取用户信息,如果用户不存在则终止请求并返回
// GetUser retrieves user information from the authenticated context, if the user does not exist it terminates the request and returns
func (authType) GetUser(ctx context.Context, c *app.RequestContext) *models.User { func (authType) GetUser(ctx context.Context, c *app.RequestContext) *models.User {
userID := ctx.Value("user").(uint) userID := ctx.Value("user").(uint)
if userID == 0 { if userID == 0 {

View File

@ -2,7 +2,6 @@ package middle
import ( import (
"context" "context"
"github.com/LiteyukiStudio/spage/config" "github.com/LiteyukiStudio/spage/config"
"github.com/LiteyukiStudio/spage/constants" "github.com/LiteyukiStudio/spage/constants"
"github.com/LiteyukiStudio/spage/utils" "github.com/LiteyukiStudio/spage/utils"
@ -22,6 +21,7 @@ type CaptchaReq struct {
} }
// UseCaptcha 中间件函数,用于验证验证码 // UseCaptcha 中间件函数,用于验证验证码
// Middleware function for captcha verification
func (captchaType) UseCaptcha() app.HandlerFunc { func (captchaType) UseCaptcha() app.HandlerFunc {
captchaConfig := &utils.CaptchaConfig{ captchaConfig := &utils.CaptchaConfig{
Type: config.CaptchaType, Type: config.CaptchaType,
@ -38,6 +38,7 @@ func (captchaType) UseCaptcha() app.HandlerFunc {
} }
if config.Mode == constants.ModeDev && req.CaptchaToken == constants.CaptchaDevPasscode { if config.Mode == constants.ModeDev && req.CaptchaToken == constants.CaptchaDevPasscode {
// 开发模式密钥 // 开发模式密钥
// Dev mode passkey
c.Next(ctx) c.Next(ctx)
return return
} }
@ -56,6 +57,7 @@ func (captchaType) UseCaptcha() app.HandlerFunc {
return return
} }
c.Next(ctx) // 如果验证码验证成功,则继续下一个处理程序 c.Next(ctx) // 如果验证码验证成功,则继续下一个处理程序
// If captcha verification is successful, continue to the next handler
return return
} }

View File

@ -13,6 +13,7 @@ type corsType struct{}
var Cors = corsType{} var Cors = corsType{}
// UseCors 跨域中间件 // UseCors 跨域中间件
// Cross-domain middleware
func (corsType) UseCors() app.HandlerFunc { func (corsType) UseCors() app.HandlerFunc {
var allowedOrigins []string var allowedOrigins []string
if config.Mode == constants.ModeDev { if config.Mode == constants.ModeDev {

View File

@ -14,6 +14,7 @@ type traceType struct{}
var Trace = traceType{} var Trace = traceType{}
// UseTrace 中间件函数,用于记录请求日志 // UseTrace 中间件函数,用于记录请求日志
// Middleware function for request logging
func (traceType) UseTrace() app.HandlerFunc { func (traceType) UseTrace() app.HandlerFunc {
return func(ctx context.Context, c *app.RequestContext) { return func(ctx context.Context, c *app.RequestContext) {
start := time.Now() start := time.Now()

View File

@ -5,56 +5,61 @@ import "gorm.io/gorm"
// User 用户模型 // User 用户模型
type User struct { type User struct {
gorm.Model gorm.Model
Name string `gorm:"not null;unique"` // 用户的唯一名称 Name string `gorm:"not null;unique"` // 用户的唯一名称 User's unique name
DisplayName *string `gorm:"column:display_name"` // 用户的显示名称 DisplayName *string `gorm:"column:display_name"` // 用户的显示名称 User's display name
Email *string `gorm:"unique"` // 用户的电子邮件地址,只有用户的电子邮件地址是唯一的(用于 oidc 身份验证) Email *string `gorm:"unique"` // 用户的电子邮件地址,只有用户的电子邮件地址是唯一的(用于 oidc 身份验证) User's email address, only the user's email address is unique (used for oidc authentication)
Description string `gorm:"default:'No description.'"` // 用户描述 Description string `gorm:"default:'No description.'"` // 用户描述 User description
AvatarURL *string `gorm:"column:avatar_url"` // 留空以使用 AvatarURL *string `gorm:"column:avatar_url"` // 留空以使用 Gravatar Leave blank to use Gravatar
Role string `gorm:"not null;default:member"` // 用户的全局角色 Role string `gorm:"not null;default:member"` // 用户的全局角色 User's global role
Organizations []*Organization `gorm:"many2many:organization_members;"` // 隶属于许多组织 Organizations []*Organization `gorm:"many2many:organization_members;"` // 隶属于许多组织 Many organizations the user belongs to
ProjectLimit int `gorm:"default:-1"` // 用户的项目限制0 表示无限制 ProjectLimit int `gorm:"default:-1"` // 用户的项目限制0 表示无限制 User's project limit, 0 means no limit
Language string `gorm:"default:'zh-cn'"` // 用户的语言,默认为英语 Language string `gorm:"default:'zh-cn'"` // 用户的语言,默认为英语 User's language, default to English
Flag string `gorm:"default:'0'"` // system_admin 的另一面旗帜 Flag string `gorm:"default:'0'"` // system_admin 的另一面旗帜 The other side of system_admin flag
Password *string `gorm:"column:password"` // 用户的密码(经过哈希处理),仅用于本地身份验证 Password *string `gorm:"column:password"` // 用户的密码(经过哈希处理),仅用于本地身份验证 User's password (hashed), only used for local authentication
} }
// TableName 用户 // TableName 用户
// User
func (User) TableName() string { func (User) TableName() string {
return "users" return "users"
} }
// Organization 组织模型 // Organization 组织模型
// Organization Model
type Organization struct { type Organization struct {
gorm.Model gorm.Model
Name string `gorm:"not null;unique"` // 组织的唯一名称 Name string `gorm:"not null;unique"` // 组织的唯一名称 Organization's unique name
DisplayName *string `gorm:"column:display_name"` // 组织的显示名称 DisplayName *string `gorm:"column:display_name"` // 组织的显示名称 Organization's display name
Email *string `gorm:"column:email"` // 组织的电子邮件地址 Email *string `gorm:"column:email"` // 组织的电子邮件地址 Organization's email address
Description string `gorm:"default:'No description.'"` // 组织描述 Description string `gorm:"default:'No description.'"` // 组织描述 Organization description
AvatarURL *string `gorm:"column:avatar_url"` // 留空以使用 AvatarURL *string `gorm:"column:avatar_url"` // 留空以使用 Gravatar Leave blank to use Gravatar
Members []*User `gorm:"many2many:organization_members;"` // 组织的成员包含创建者 Members []*User `gorm:"many2many:organization_members;"` // 组织的成员包含创建者 (including the creator)
Owners []User `gorm:"many2many:organization_owners;"` // 组织的所有者(无反向关系)包含创建者 Owners []User `gorm:"many2many:organization_owners;"` // 组织的所有者(无反向关系)包含创建者 (including the creator)
ProjectLimit int `gorm:"default:0"` // 组织的项目限制0遵循策略-1无限制 ProjectLimit int `gorm:"default:0"` // 组织的项目限制0遵循策略-1无限制 Organization's project limit, 0: follow the policy, -1: unlimited
} }
// TableName 组织 // TableName 组织
// Organization
func (Organization) TableName() string { func (Organization) TableName() string {
return "organizations" return "organizations"
} }
// Project 项目模型 // Project 项目模型
// Project Model
type Project struct { type Project struct {
gorm.Model gorm.Model
Name string `gorm:"not null;unique"` // 项目的唯一名称 Name string `gorm:"not null;unique"` // 项目的唯一名称 Project's unique name
DisplayName *string `gorm:"column:display_name"` // 项目的显示名称 DisplayName *string `gorm:"column:display_name"` // 项目的显示名称 Project's display name
Description string `gorm:"default:'No description.'"` // 项目描述 Description string `gorm:"default:'No description.'"` // 项目描述 Project description
OwnerID uint `gorm:"not null"` // 所有者 ID用户 ID 或组织 ID OwnerID uint `gorm:"not null"` // 所有者 ID用户 ID 或组织 ID Owner ID (user ID or organization ID)
OwnerType string `gorm:"not null"` // 所有者类型,可以是用户或组织 OwnerType string `gorm:"not null"` // 所有者类型,可以是用户或组织 Owner type, can be user or organization
Owners []User `gorm:"many2many:project_owners;"` // 项目的所有者,无反向关系 Owners []User `gorm:"many2many:project_owners;"` // 项目的所有者,无反向关系 Project's owners, no reverse relation
Members []*User `gorm:"many2many:project_members;"` // 项目的成员 Members []*User `gorm:"many2many:project_members;"` // 项目的成员 Project's members
SiteLimit int `gorm:"default:0"` // 项目的站点限制0遵循策略-1无限制 SiteLimit int `gorm:"default:0"` // 项目的站点限制0遵循策略-1无限制 Project's site limit, 0: follow the policy, -1: unlimited
} }
// TableName 项目 // TableName 项目
// Project
func (Project) TableName() string { func (Project) TableName() string {
return "projects" return "projects"
} }

View File

@ -4,12 +4,12 @@ import "gorm.io/gorm"
type File struct { type File struct {
gorm.Model gorm.Model
ID uint `gorm:"primaryKey" json:"id"` // 文件ID ID uint `gorm:"primaryKey" json:"id"` // 文件ID File ID
Path string `gorm:"not null" json:"path"` // 文件路径,相较于根目录的相对路径 Path string `gorm:"not null" json:"path"` // 文件路径,相较于根目录的相对路径 File path, relative to the root directory
MD5 string `gorm:"not null" json:"md5"` // 文件哈希值 MD5 string `gorm:"not null" json:"md5"` // 文件哈希值 File hash
} }
// TableName 自定义表名 // TableName 自定义表名 Custom table name
func (File) TableName() string { func (File) TableName() string {
return "projects" return "projects"
} }

View File

@ -3,6 +3,7 @@ package models
import "gorm.io/gorm" import "gorm.io/gorm"
// Migrate 迁移模型通过依赖注入的方式使用gorm.DB进行数据库操作 // Migrate 迁移模型通过依赖注入的方式使用gorm.DB进行数据库操作
// Migrate models, using gorm.DB for database operations through dependency injection
func Migrate(db *gorm.DB) error { func Migrate(db *gorm.DB) error {
if err := db.AutoMigrate( if err := db.AutoMigrate(
// entity.go // entity.go

View File

@ -4,17 +4,26 @@ import "gorm.io/gorm"
type OIDCConfig struct { type OIDCConfig struct {
gorm.Model gorm.Model
AdminGroups []string `gorm:"type:json;column:admin_groups;default:'[]'"` // 平台管理员组,默认为:[]string{}*为匹配所有组,储存为逗号分隔的字符串 AdminGroups []string `gorm:"type:json;column:admin_groups;default:'[]'"` // 平台管理员组,默认为:[]string{}*为匹配所有组,储存为逗号分隔的字符串
AllowedGroups []string `gorm:"type:json;column:allowed_groups;default:'[\"*\"]'"` // 允许登录的组,默认为:[]string{"*"}*为匹配所有组,储存为逗号分隔的字符串 // Admin groups, default is: []string{}, * matches all groups, stored as a comma-separated string
ClientID string `gorm:"column:client_id"` // 客户端ID AllowedGroups []string `gorm:"type:json;column:allowed_groups;default:'[\"*\"]'"` // 允许登录的组,默认为:[]string{"*"}*为匹配所有组,储存为逗号分隔的字符串
ClientSecret string `gorm:"column:client_secret"` // 客户端密钥 // Allowed groups for login, default is: []string{"*"}, * matches all groups, stored as a comma-separated string
DisplayName string `gorm:"column:display_name"` // 显示名称,例如:轻雪通行证 ClientID string `gorm:"column:client_id"` // 客户端ID
GroupsClaim *string `gorm:"default:groups"` // 组声明,默认为:"groups" // Client ID
Icon *string `gorm:"column:icon"` // 图标url为空则使用内置默认图标 ClientSecret string `gorm:"column:client_secret"` // 客户端密钥
OidcDiscoveryURL string `gorm:"column:oidc_discovery_url"` // OpenID自动发现URL例如 https://pass.liteyuki.icu/.well-known/openid-configuration // Client Secret
DisplayName string `gorm:"column:display_name"` // 显示名称,例如:轻雪通行证
// Display name, e.g., Light Snow Passport
GroupsClaim *string `gorm:"default:groups"` // 组声明,默认为:"groups"
// Groups claim, default is: "groups"
Icon *string `gorm:"column:icon"` // 图标url为空则使用内置默认图标
// Icon URL, if empty use the built-in default icon
OidcDiscoveryURL string `gorm:"column:oidc_discovery_url"` // OpenID自动发现URL例如 https://pass.liteyuki.icu/.well-known/openid-configuration
// OpenID auto-discovery URL, e.g., https://pass.liteyuki.icu/.well-known/openid-configuration
} }
// TableName 重写表名 // TableName 重写表名
// Rewrite table name
func (OIDCConfig) TableName() string { func (OIDCConfig) TableName() string {
return "oidc_configs" return "oidc_configs"
} }

View File

@ -6,30 +6,30 @@ import (
type Site struct { type Site struct {
gorm.Model gorm.Model
Name string `gorm:"unique"` // 站点名称 Name string `gorm:"unique"` // 站点名称 Site name
Description string `gorm:"size:255"` // 站点描述 Description string `gorm:"size:255"` // 站点描述 Site description
ProjectID uint `gorm:"not null"` // 项目ID ProjectID uint `gorm:"not null"` // 项目ID Project ID
Project Project `gorm:"foreignKey:ProjectID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` // 项目 Project Project `gorm:"foreignKey:ProjectID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` // 项目 Project
SubDomain string `gorm:"unique;size:255"` // 子域前缀 SubDomain string `gorm:"unique;size:255"` // 子域前缀 Subdomain prefix
Domains []string `gorm:"type:json;default:'[]'"` // 允许的域名json格式 Domains []string `gorm:"type:json;default:'[]'"` // 允许的域名json格式 Allowed domains, json format
} }
// 站点表名 // 站点表名 Site table name
func (Site) TableName() string { func (Site) TableName() string {
return "sites" return "sites"
} }
// 站点发布表 // 站点发布表 Site release table
type SiteRelease struct { type SiteRelease struct {
gorm.Model gorm.Model
SiteID uint `gorm:"not null"` // 站点ID SiteID uint `gorm:"not null"` // 站点ID Site ID
Site Site `gorm:"foreignKey:SiteID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Site Site `gorm:"foreignKey:SiteID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Tag string `gorm:"not null"` // 版本标签 Tag string `gorm:"not null"` // 版本标签 Version tag
FileID uint `gorm:"not null"` // 版本文件ID FileID uint `gorm:"not null"` // 版本文件ID Version file ID
File File `gorm:"foreignKey:FileID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` // 版本文件 File File `gorm:"foreignKey:FileID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` // 版本文件 Version file
} }
// 站点发布表名 // 站点发布表名 Site release table name
func (SiteRelease) TableName() string { func (SiteRelease) TableName() string {
return "site_releases" return "site_releases"
} }

View File

@ -8,77 +8,78 @@ import (
) )
// Run 运行路由服务 // Run 运行路由服务
// Run router service
func Run() error { func Run() error {
// 运行路由 // 运行路由 Run router
H := server.New(server.WithHostPorts(":"+config.ServerPort), server.WithMaxRequestBodySize(config.FileMaxSize)) H := server.New(server.WithHostPorts(":"+config.ServerPort), server.WithMaxRequestBodySize(config.FileMaxSize))
H.Use(middle.Cors.UseCors(), middle.Trace.UseTrace()) H.Use(middle.Cors.UseCors(), middle.Trace.UseTrace())
apiV1 := H.Group("/api/v1") apiV1 := H.Group("/api/v1")
apiV1.Use(middle.Auth.UseAuth()) apiV1.Use(middle.Auth.UseAuth())
apiV1WithoutAuth := H.Group("/api/v1") apiV1WithoutAuth := H.Group("/api/v1")
apiV1WithoutAuthAndCaptcha := H.Group("/api/v1") // 不需要登录和验证码的路由 apiV1WithoutAuthAndCaptcha := H.Group("/api/v1") // 不需要登录和验证码的路由 Group without auth and captcha
{ {
apiV1WithoutAuthAndCaptcha.GET("/user/captcha", handlers.User.GetCaptcha) // 取验证码 apiV1WithoutAuthAndCaptcha.GET("/user/captcha", handlers.User.GetCaptcha) // 取验证码 Get captcha
apiV1WithoutAuth.POST("/user/logout", handlers.User.Logout) apiV1WithoutAuth.POST("/user/logout", handlers.User.Logout)
apiV1WithoutAuth.POST("/user/register", handlers.User.Register).Use(middle.Captcha.UseCaptcha()) // 注册 apiV1WithoutAuth.POST("/user/register", handlers.User.Register).Use(middle.Captcha.UseCaptcha()) // 注册 Register
apiV1WithoutAuth.POST("/user/login", handlers.User.Login).Use(middle.Captcha.UseCaptcha()) apiV1WithoutAuth.POST("/user/login", handlers.User.Login).Use(middle.Captcha.UseCaptcha())
userGroup := apiV1.Group("/user") userGroup := apiV1.Group("/user")
{ {
userGroup.PUT("", handlers.User.UpdateUser) // 更新用户信息 userGroup.PUT("", handlers.User.UpdateUser) // 更新用户信息 Update user info
userGroup.GET("", handlers.User.GetUser) // 获取用户信息 userGroup.GET("", handlers.User.GetUser) // 获取用户信息 Get user info
userGroup.GET("/:id", handlers.User.GetUser) // 获取用户信息 userGroup.GET("/:id", handlers.User.GetUser) // 获取用户信息 Get user info
userGroup.GET("/:id/projects", handlers.User.GetProjects) // 获取用户项目 userGroup.GET("/:id/projects", handlers.User.GetProjects) // 获取用户项目 Get user projects
userGroup.GET("/:id/orgs", handlers.User.GetOrgs) // 获取用户组织 userGroup.GET("/:id/orgs", handlers.User.GetOrgs) // 获取用户组织 Get user orgs
} }
orgGroup := apiV1.Group("/org", handlers.Org.UserOrgAuth) orgGroup := apiV1.Group("/org", handlers.Org.UserOrgAuth)
{ {
orgGroup.POST("", handlers.Org.CreateOrganization) // 创建组织 orgGroup.POST("", handlers.Org.CreateOrganization) // 创建组织 Create organization
orgGroup.PUT("/:id", handlers.Org.UpdateOrganization) // 更新组织 orgGroup.PUT("/:id", handlers.Org.UpdateOrganization) // 更新组织 Update organization
orgGroup.DELETE("/:id", handlers.Org.DeleteOrganization) // 删除组织 orgGroup.DELETE("/:id", handlers.Org.DeleteOrganization) // 删除组织 Delete organization
orgGroup.GET("/:id", handlers.Org.GetOrganization) // 获取组织信息 orgGroup.GET("/:id", handlers.Org.GetOrganization) // 获取组织信息 Get organization info
orgGroup.GET("/:id/projects", handlers.Org.GetOrganizationProject) // 获取组织项目 orgGroup.GET("/:id/projects", handlers.Org.GetOrganizationProject) // 获取组织项目 Get organization projects
orgGroup.GET("/:id/users", handlers.Org.GetOrganizationUsers) // 获取组织所有成员和所有者 orgGroup.GET("/:id/users", handlers.Org.GetOrganizationUsers) // 获取组织所有成员和所有者 Get organization users
orgGroup.PUT("/:id/users", handlers.Org.AddOrganizationUser) // 添加组织成员或所有者 orgGroup.PUT("/:id/users", handlers.Org.AddOrganizationUser) // 添加组织成员或所有者 Add organization user
orgGroup.DELETE("/:id/users", handlers.Org.DeleteOrganizationUser) // 删除组织成员或所有者 orgGroup.DELETE("/:id/users", handlers.Org.DeleteOrganizationUser) // 删除组织成员或所有者 Delete organization user
} }
projectGroup := apiV1.Group("/project", handlers.Project.UserProjectAuth) projectGroup := apiV1.Group("/project", handlers.Project.UserProjectAuth)
{ {
projectGroup.POST("", handlers.Project.Create) // 创建项目 projectGroup.POST("", handlers.Project.Create) // 创建项目 Create project
projectGroup.PUT("/:id", handlers.Project.Update) // 更新项目 projectGroup.PUT("/:id", handlers.Project.Update) // 更新项目 Update project
projectGroup.DELETE("/:id", handlers.Project.Delete) // 删除项目 projectGroup.DELETE("/:id", handlers.Project.Delete) // 删除项目 Delete project
projectGroup.GET("/:id", handlers.Project.Info) // 获取项目信息 projectGroup.GET("/:id", handlers.Project.Info) // 获取项目信息 Get project info
projectGroup.GET("/:id/owners", handlers.Project.GetOwners) // 获取项目所有者 projectGroup.GET("/:id/owners", handlers.Project.GetOwners) // 获取项目所有者 Get project owners
projectGroup.PUT("/:id/owner", handlers.Project.AddOwner) // 更新项目所有者 projectGroup.PUT("/:id/owner", handlers.Project.AddOwner) // 更新项目所有者 Add project owner
projectGroup.DELETE("/:id/owner", handlers.Project.DeleteOwner) // 删除项目所有者 projectGroup.DELETE("/:id/owner", handlers.Project.DeleteOwner) // 删除项目所有者 Delete project owner
projectGroup.GET("/:id/sites", handlers.Project.GetSites) // 获取项目站点 projectGroup.GET("/:id/sites", handlers.Project.GetSites) // 获取项目站点 Get project sites
siteGroup := projectGroup.Group("/:id/site", handlers.Site.SiteAuth) siteGroup := projectGroup.Group("/:id/site", handlers.Site.SiteAuth)
{ {
siteGroup.POST("", handlers.Site.Create) // 创建站点 siteGroup.POST("", handlers.Site.Create) // 创建站点 Create site
siteGroup.PUT("/:site_id", handlers.Site.Update) // 更新站点 siteGroup.PUT("/:site_id", handlers.Site.Update) // 更新站点 Update site
siteGroup.DELETE("/:site_id", handlers.Site.Delete) // 删除站点 siteGroup.DELETE("/:site_id", handlers.Site.Delete) // 删除站点 Delete site
siteGroup.GET("/:site_id", handlers.Site.Info) // 获取网站信息 siteGroup.GET("/:site_id", handlers.Site.Info) // 获取网站信息 Get site info
siteGroup.GET("/:site_id/releases", handlers.Release.ReleaseList) // 获取站点 release 列表 siteGroup.GET("/:site_id/releases", handlers.Release.ReleaseList) // 获取站点 release 列表
siteRelease := siteGroup.Group("/:site_id/release") siteRelease := siteGroup.Group("/:site_id/release")
{ {
siteRelease.POST("", handlers.Release.Create) // 创建站点发布 siteRelease.POST("", handlers.Release.Create) // 创建站点发布 Create site release
siteRelease.DELETE("", handlers.Release.Delete) // 删除站点版本 siteRelease.DELETE("", handlers.Release.Delete) // 删除站点版本 Delete site release
siteRelease.POST("/activation", handlers.Release.Activation) // 指定使用该站点版本 siteRelease.POST("/activation", handlers.Release.Activation) // 指定使用该站点版本
} }
} }
} }
fileGroup := apiV1.Group("/file") fileGroup := apiV1.Group("/file")
{ {
fileGroup.POST("", handlers.File.UploadFileStream) // 上传文件 fileGroup.POST("", handlers.File.UploadFileStream) // 上传文件 Upload file
fileGroup.GET("") // 下载文件 fileGroup.GET("") // 下载文件 Download file
fileGroup.DELETE("") // 删除文件 fileGroup.DELETE("") // 删除文件 Delete file
} }
adminGroup := apiV1.Group("/admin") // 管理员路由 adminGroup := apiV1.Group("/admin") // 管理员路由
adminGroup.Use(middle.Auth.IsAdmin()) adminGroup.Use(middle.Auth.IsAdmin())
{ {
adminUser := adminGroup.Group("/user") adminUser := adminGroup.Group("/user")
{ {
adminUser.POST("", handlers.Admin.CreateUser) // 创建用户 adminUser.POST("", handlers.Admin.CreateUser) // 创建用户 Create user
} }
adminNode := adminGroup.Group("/node") adminNode := adminGroup.Group("/node")
{ {
@ -95,21 +96,21 @@ func Run() error {
} }
// 设置静态文件目录 // 设置静态文件目录 Set static file directory
web := H.Group("") web := H.Group("")
{ {
web.GET("/*any", handlers.WebHandler) web.GET("/*any", handlers.WebHandler)
} }
// 运行服务 // 运行服务 Run service
if config.Mode == "dev" { if config.Mode == "dev" {
// 开发模式 // 开发模式 Development mode
err := H.Run() err := H.Run()
if err != nil { if err != nil {
return err return err
} }
} else { } else {
// 生产模式 // 生产模式 Production mode
H.Spin() H.Spin()
} }
return nil return nil

View File

@ -9,6 +9,7 @@ type JWTType struct{}
var JWT = JWTType{} var JWT = JWTType{}
// CreateToken 创建令牌 // CreateToken 创建令牌
// Create Token
func (JWTType) CreateToken(userID uint) (*models.Token, error) { func (JWTType) CreateToken(userID uint) (*models.Token, error) {
token := &models.Token{ token := &models.Token{
UserID: userID, UserID: userID,
@ -20,11 +21,14 @@ func (JWTType) CreateToken(userID uint) (*models.Token, error) {
} }
// IsTokenRevoked 检查令牌是否被撤销 // IsTokenRevoked 检查令牌是否被撤销
// Check if a token has been revoked
func (JWTType) IsTokenRevoked(tokenID uint) bool { func (JWTType) IsTokenRevoked(tokenID uint) bool {
var count int64 var count int64
// 查询是否存在该令牌(未被删除的) // 查询是否存在该令牌(未被删除的)
// Check if the token exists (not deleted)
err := DB.Model(&models.Token{}).Where("id = ?", tokenID).Count(&count).Error err := DB.Model(&models.Token{}).Where("id = ?", tokenID).Count(&count).Error
// 如果查询出错或找不到令牌,默认视为已撤销(安全优先) // 如果查询出错或找不到令牌,默认视为已撤销(安全优先)
// If the query fails or no token is found, assume it's revoked (safety first)
if err != nil || count == 0 { if err != nil || count == 0 {
return true return true
} }
@ -32,6 +36,7 @@ func (JWTType) IsTokenRevoked(tokenID uint) bool {
} }
// RevokeTokenByID 撤销令牌 // RevokeTokenByID 撤销令牌
// Revoke Token
func (JWTType) RevokeTokenByID(id uint) error { func (JWTType) RevokeTokenByID(id uint) error {
if err := DB.Where("id = ?", id).Delete(&models.Token{}).Error; err != nil { if err := DB.Where("id = ?", id).Delete(&models.Token{}).Error; err != nil {
return err return err
@ -40,6 +45,7 @@ func (JWTType) RevokeTokenByID(id uint) error {
} }
// RevokeTokenByUserID 撤销用户的所有令牌 // RevokeTokenByUserID 撤销用户的所有令牌
// Revoke all tokens for a user
func (JWTType) RevokeTokenByUserID(userID uint) error { func (JWTType) RevokeTokenByUserID(userID uint) error {
if err := DB.Where("user_id = ?", userID).Delete(&models.Token{}).Error; err != nil { if err := DB.Where("user_id = ?", userID).Delete(&models.Token{}).Error; err != nil {
return err return err

View File

@ -10,6 +10,7 @@ type orgType struct {
var Org = orgType{} var Org = orgType{}
// ListByUserID 通过UserID获取用户组织支持分页和预加载关系 // ListByUserID 通过UserID获取用户组织支持分页和预加载关系
// Get Organizations by UserID, support pagination and preload relationships
func (o *orgType) ListByUserID(userID string, page, limit int) (orgs []models.Organization, err error) { func (o *orgType) ListByUserID(userID string, page, limit int) (orgs []models.Organization, err error) {
// 使用连接查询 // 使用连接查询
query := DB.Joins("JOIN organization_members ON organizations.id = organization_members.organization_id"). query := DB.Joins("JOIN organization_members ON organizations.id = organization_members.organization_id").
@ -27,12 +28,14 @@ func (o *orgType) ListByUserID(userID string, page, limit int) (orgs []models.Or
} }
// GetOrgById 通过ID获取组织 // GetOrgById 通过ID获取组织
// Get Organization by ID
func (o *orgType) GetOrgById(id uint) (org *models.Organization, err error) { func (o *orgType) GetOrgById(id uint) (org *models.Organization, err error) {
err = DB.Model(&models.Organization{}).Where("id = ?", id).Preload("Members").Preload("Owners").First(&org).Error err = DB.Model(&models.Organization{}).Where("id = ?", id).Preload("Members").Preload("Owners").First(&org).Error
return return
} }
// OrgNameIsExist 判断组织名称是否存在 // OrgNameIsExist 判断组织名称是否存在
// Check if the organization name exists
func (o *orgType) OrgNameIsExist(name string) bool { func (o *orgType) OrgNameIsExist(name string) bool {
var count int64 var count int64
DB.Model(&models.Organization{}).Where("name = ?", name).Count(&count) DB.Model(&models.Organization{}).Where("name = ?", name).Count(&count)
@ -40,6 +43,7 @@ func (o *orgType) OrgNameIsExist(name string) bool {
} }
// GetUserAuth 获取用户在组织中的权限 // GetUserAuth 获取用户在组织中的权限
// Get User's Authority in Organization
func (o *orgType) GetUserAuth(org *models.Organization, userID uint) (auth string) { func (o *orgType) GetUserAuth(org *models.Organization, userID uint) (auth string) {
for _, owner := range org.Owners { for _, owner := range org.Owners {
if owner.ID == userID { if owner.ID == userID {

View File

@ -2,7 +2,6 @@ package store
import ( import (
"fmt" "fmt"
"github.com/LiteyukiStudio/spage/constants" "github.com/LiteyukiStudio/spage/constants"
"github.com/LiteyukiStudio/spage/spage/models" "github.com/LiteyukiStudio/spage/spage/models"
) )
@ -13,17 +12,20 @@ type projectType struct {
var Project = projectType{} var Project = projectType{}
// Create 创建项目 // Create 创建项目
// Create a project
func (p *projectType) Create(project *models.Project) (err error) { func (p *projectType) Create(project *models.Project) (err error) {
return DB.Create(project).Error return DB.Create(project).Error
} }
// GetByID 通过项目ID获取项目 // GetByID 通过项目ID获取项目
// Get Project by ID
func (p *projectType) GetByID(id uint) (project *models.Project, err error) { func (p *projectType) GetByID(id uint) (project *models.Project, err error) {
err = DB.First(&project, id).Preload("Owners").Error err = DB.First(&project, id).Preload("Owners").Error
return return
} }
// UserIsOwner 判断用户是否是项目的所有者 // UserIsOwner 判断用户是否是项目的所有者
// Check if a user is the owner of a project
func (p *projectType) UserIsOwner(project *models.Project, userID uint) bool { func (p *projectType) UserIsOwner(project *models.Project, userID uint) bool {
if project.OwnerType == constants.OwnerTypeUser && project.OwnerID == userID { if project.OwnerType == constants.OwnerTypeUser && project.OwnerID == userID {
return true return true
@ -37,6 +39,7 @@ func (p *projectType) UserIsOwner(project *models.Project, userID uint) bool {
} }
// ListByOwner 通过用户ID获取项目列表支持分页和从新到旧排序 // ListByOwner 通过用户ID获取项目列表支持分页和从新到旧排序
// Get Project List by UserID, support pagination and new to old sorting
func (p *projectType) ListByOwner(ownerType, ownerID string, page, limit int) (projects []models.Project, total int64, err error) { func (p *projectType) ListByOwner(ownerType, ownerID string, page, limit int) (projects []models.Project, total int64, err error) {
tableName := "" tableName := ""
switch ownerType { switch ownerType {
@ -71,16 +74,19 @@ func (p *projectType) Delete(project *models.Project) (err error) {
} }
// AddOwner 为项目添加所有者 // AddOwner 为项目添加所有者
// Add Owner to a project
func (p *projectType) AddOwner(project *models.Project, user *models.User) (err error) { func (p *projectType) AddOwner(project *models.Project, user *models.User) (err error) {
return DB.Model(project).Association("Owners").Append(user) return DB.Model(project).Association("Owners").Append(user)
} }
// DeleteOwner 从项目删除所有者 // DeleteOwner 从项目删除所有者
// Delete Owner from a project
func (p *projectType) DeleteOwner(project *models.Project, user *models.User) (err error) { func (p *projectType) DeleteOwner(project *models.Project, user *models.User) (err error) {
return DB.Model(project).Association("Owners").Delete(user) return DB.Model(project).Association("Owners").Delete(user)
} }
// GetSiteList 获取项目下的站点列表 // GetSiteList 获取项目下的站点列表
// Get Site List of a project
func (p *projectType) GetSiteList(project *models.Project, page, limit int) (sites []models.Site, total int64, err error) { func (p *projectType) GetSiteList(project *models.Project, page, limit int) (sites []models.Site, total int64, err error) {
sites, total, err = Paginate[models.Site]( sites, total, err = Paginate[models.Site](
DB, DB,

View File

@ -10,11 +10,13 @@ type SiteType struct {
var Site = SiteType{} var Site = SiteType{}
// Create 创建站点 // Create 创建站点
// Create Site
func (s *SiteType) Create(site *models.Site) (err error) { func (s *SiteType) Create(site *models.Site) (err error) {
return DB.Create(site).Error return DB.Create(site).Error
} }
// GetByID 根据id获取站点信息 // GetByID 根据id获取站点信息
// Get Site Info by ID
func (s *SiteType) GetByID(id uint) (site *models.Site, err error) { func (s *SiteType) GetByID(id uint) (site *models.Site, err error) {
site = &models.Site{} site = &models.Site{}
err = DB.Where("id = ?", id).Preload("Project").First(site).Error err = DB.Where("id = ?", id).Preload("Project").First(site).Error

View File

@ -3,9 +3,6 @@ package store
import ( import (
"errors" "errors"
"fmt" "fmt"
"plugin"
"runtime"
"github.com/LiteyukiStudio/spage/config" "github.com/LiteyukiStudio/spage/config"
"github.com/LiteyukiStudio/spage/constants" "github.com/LiteyukiStudio/spage/constants"
"github.com/LiteyukiStudio/spage/spage/models" "github.com/LiteyukiStudio/spage/spage/models"
@ -14,6 +11,8 @@ import (
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/logger" "gorm.io/gorm/logger"
"plugin"
"runtime"
) )
var DB *gorm.DB var DB *gorm.DB
@ -21,13 +20,13 @@ var DB *gorm.DB
// DBConfig 数据库配置结构体 // DBConfig 数据库配置结构体
type DBConfig struct { type DBConfig struct {
Driver string // 数据库驱动类型,例如 "sqlite" 或 "postgres" Database driver type, e.g., "sqlite" or "postgres" Driver string // 数据库驱动类型,例如 "sqlite" 或 "postgres" Database driver type, e.g., "sqlite" or "postgres"
Path string // 路径 Path string // SQLite 路径 SQLite path
Host string // PostgreSQL 主机名 Host string // PostgreSQL 主机名 PostgreSQL hostname
Port int // PostgreSQL 端口 Port int // PostgreSQL 端口 PostgreSQL port
User string // PostgreSQL 用户名 User string // PostgreSQL 用户名 PostgreSQL username
Password string // PostgreSQL 密码 Password string // PostgreSQL 密码 PostgreSQL password
DBName string // PostgreSQL 数据库名 DBName string // PostgreSQL 数据库名 PostgreSQL database name
SSLMode string // PostgreSQL SSL 模式 SSLMode string // PostgreSQL SSL 模式 PostgreSQL SSL mode
} }
// loadDBConfig 从配置文件加载数据库配置 // loadDBConfig 从配置文件加载数据库配置

View File

@ -2,7 +2,6 @@ package store
import ( import (
"errors" "errors"
"github.com/LiteyukiStudio/spage/constants" "github.com/LiteyukiStudio/spage/constants"
"github.com/LiteyukiStudio/spage/spage/models" "github.com/LiteyukiStudio/spage/spage/models"
@ -21,10 +20,10 @@ func (u *userType) Create(user *models.User) (err error) {
// GetByName 根据名称获取用户 // GetByName 根据名称获取用户
func (u *userType) GetByName(name string) (user *models.User, err error) { func (u *userType) GetByName(name string) (user *models.User, err error) {
user = &models.User{} // 初始化指针 user = &models.User{} // 初始化指针 // Initialize pointer
err = DB.Where("name = ?", name).First(user).Error err = DB.Where("name = ?", name).First(user).Error
if err != nil { if err != nil {
return nil, err // 出错时返回 return nil, err // 出错时返回nil When an error occurs, return nil
} }
return user, nil return user, nil
} }
@ -74,26 +73,27 @@ func (u *userType) DeleteByID(id uint) (err error) {
} }
// UpdateSystemAdmin 更新系统管理员用户,不存在则创建 // UpdateSystemAdmin 更新系统管理员用户,不存在则创建
// Update System Admin User, create if not exist
func (u *userType) UpdateSystemAdmin(user *models.User) (err error) { func (u *userType) UpdateSystemAdmin(user *models.User) (err error) {
// 设置该用户为系统管理员 // 设置该用户为系统管理员 Set this user as a system admin
user.Flag = constants.FlagSystemAdmin user.Flag = constants.FlagSystemAdmin
user.Role = constants.RoleAdmin user.Role = constants.RoleAdmin
// 尝试查找系统管理员 // 尝试查找系统管理员 Try to find system admin
existingAdmin := models.User{} existingAdmin := models.User{}
result := DB.Where("flag = ?", constants.FlagSystemAdmin).First(&existingAdmin) result := DB.Where("flag = ?", constants.FlagSystemAdmin).First(&existingAdmin)
if result.Error != nil { if result.Error != nil {
// 如果不存在系统管理员(记录未找到),则创建一个 // 如果不存在系统管理员(记录未找到),则创建一个 If there is no system admin (record not found), create one
if errors.Is(result.Error, gorm.ErrRecordNotFound) { if errors.Is(result.Error, gorm.ErrRecordNotFound) {
// 创建新的系统管理员 // 创建新的系统管理员 Create new system admin
return DB.Create(user).Error return DB.Create(user).Error
} }
// 其他错误则直接返回 // 其他错误则直接返回 Other errors are returned directly
return result.Error return result.Error
} }
// 系统管理员已存在,更新信息 // 系统管理员已存在,更新信息 System admin exists, update information
// 保留ID更新其他字段 // 保留ID更新其他字段 Keep ID, update other fields
user.ID = existingAdmin.ID user.ID = existingAdmin.ID
return DB.Model(&existingAdmin).Updates(user).Error return DB.Model(&existingAdmin).Updates(user).Error
} }

View File

@ -7,6 +7,8 @@ import (
// Paginate 封装通用的分页查询逻辑 // Paginate 封装通用的分页查询逻辑
// T 是任意数据模型类型 // T 是任意数据模型类型
// Paginate generic pagination query logic
// T is any data model type
func Paginate[T any](db *gorm.DB, page, limit int, conditions ...any) (items []T, total int64, err error) { func Paginate[T any](db *gorm.DB, page, limit int, conditions ...any) (items []T, total int64, err error) {
// 查询总记录数 // 查询总记录数
countDB := db countDB := db
@ -19,11 +21,13 @@ func Paginate[T any](db *gorm.DB, page, limit int, conditions ...any) (items []T
} }
// 确保分页参数有效 // 确保分页参数有效
// Ensure pagination parameters are valid
if limit <= 0 { if limit <= 0 {
limit = config.PageLimit limit = config.PageLimit
} }
// 执行分页查询 // 执行分页查询
// Execute pagination query
queryDB := db queryDB := db
if len(conditions) > 0 { if len(conditions) > 0 {
queryDB = queryDB.Where(conditions[0], conditions[1:]...) queryDB = queryDB.Where(conditions[0], conditions[1:]...)
@ -39,6 +43,7 @@ func Paginate[T any](db *gorm.DB, page, limit int, conditions ...any) (items []T
} }
// WithPreloads 添加预加载关系的辅助函数 // WithPreloads 添加预加载关系的辅助函数
// Add a helper function to add preloaded relationships
func WithPreloads(db *gorm.DB, preloads ...string) *gorm.DB { func WithPreloads(db *gorm.DB, preloads ...string) *gorm.DB {
for _, preload := range preloads { for _, preload := range preloads {
db = db.Preload(preload) db = db.Preload(preload)

View File

@ -2,7 +2,6 @@ package utils
import ( import (
"fmt" "fmt"
"github.com/LiteyukiStudio/spage/constants" "github.com/LiteyukiStudio/spage/constants"
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
) )
@ -18,6 +17,7 @@ type CaptchaConfig struct {
} }
// VerifyCaptcha 根据提供的配置和令牌验证验证码 // VerifyCaptcha 根据提供的配置和令牌验证验证码
// Verify captcha based on provided configuration and token
func (captchaType) VerifyCaptcha(restyClient *resty.Client, captchaConfig *CaptchaConfig, captchaToken string) (bool, error) { func (captchaType) VerifyCaptcha(restyClient *resty.Client, captchaConfig *CaptchaConfig, captchaToken string) (bool, error) {
switch captchaConfig.Type { switch captchaConfig.Type {
case constants.CaptchaTypeDisable: case constants.CaptchaTypeDisable:

View File

@ -14,16 +14,17 @@ type EmailType struct{}
var Email = EmailType{} var Email = EmailType{}
type EmailConfig struct { type EmailConfig struct {
Enable bool // 邮箱启用状态 Enable bool // 邮箱启用状态 Email enable status
Username string // 邮箱用户名 Username string // 邮箱用户名 Email username
Address string // 邮箱地址 Address string // 邮箱地址 Email address
Host string // 邮箱服务器地址 Host string // 邮箱服务器地址 Email server address
Port string // 邮箱服务器端口 Port string // 邮箱服务器端口 Email server port
Password string // 邮箱密码 Password string // 邮箱密码 Email password
SSL bool // 是否使用SSL SSL bool // 是否使用SSL Email use SSL
} }
// SendTemplate 发送HTML模板从配置文件中读取邮箱配置 // SendTemplate 发送HTML模板从配置文件中读取邮箱配置
// Send HTML template, read email configuration from the configuration file
func SendTemplate(emailConfig *EmailConfig, target, htmlTemplate string, placeholders map[string]string) error { func SendTemplate(emailConfig *EmailConfig, target, htmlTemplate string, placeholders map[string]string) error {
for placeholder, value := range placeholders { for placeholder, value := range placeholders {
htmlTemplate = strings.ReplaceAll(htmlTemplate, placeholder, value) htmlTemplate = strings.ReplaceAll(htmlTemplate, placeholder, value)
@ -36,8 +37,10 @@ func SendTemplate(emailConfig *EmailConfig, target, htmlTemplate string, placeho
} }
// SendEmail 发送邮件 // SendEmail 发送邮件
// Send Email
func SendEmail(emailConfig *EmailConfig, target, content string, isHTML bool) error { func SendEmail(emailConfig *EmailConfig, target, content string, isHTML bool) error {
// 如果配置未启用则直接返回nil // 如果配置未启用则直接返回nil
// If the configuration is not enabled, return nil directly
if !emailConfig.Enable { if !emailConfig.Enable {
return nil return nil
} }
@ -58,6 +61,7 @@ func SendEmail(emailConfig *EmailConfig, target, content string, isHTML bool) er
} }
// 在函数退出时关闭连接 // 在函数退出时关闭连接
// Close the connection when the function exits
defer func(conn net.Conn) { defer func(conn net.Conn) {
err := conn.Close() err := conn.Close()
if err != nil { if err != nil {
@ -70,40 +74,47 @@ func SendEmail(emailConfig *EmailConfig, target, content string, isHTML bool) er
if err != nil { if err != nil {
return err return err
// todo: 处理连接时的错误 // todo: 处理连接时的错误
// todo: Handle errors during connection
} }
defer func(client *smtp.Client) { defer func(client *smtp.Client) {
err := client.Quit() err := client.Quit()
if err != nil { if err != nil {
// todo: 处理关闭连接时的错误 // todo: 处理关闭连接时的错误
// todo: Handle errors when closing the connection
} }
}(client) }(client)
if err = client.Auth(auth); err != nil { if err = client.Auth(auth); err != nil {
return err return err
// todo: 处理身份验证时的错误 // todo: 处理身份验证时的错误
// todo: Handle errors during authentication
} }
if err = client.Mail(emailConfig.Address); err != nil { if err = client.Mail(emailConfig.Address); err != nil {
return err return err
// todo: 处理发件人时的错误 // todo: 处理发件人时的错误
// todo: Handle errors when processing the sender
} }
if err = client.Rcpt(target); err != nil { if err = client.Rcpt(target); err != nil {
return err return err
// todo: 处理收件人时的错误 // todo: 处理收件人时的错误
// todo: Handle errors when processing recipients
} }
writer, err := client.Data() writer, err := client.Data()
if err != nil { if err != nil {
return err return err
// todo: 处理数据写入器创建时的错误 // todo: 处理数据写入器创建时的错误
// todo: Handle errors when creating the data writer
} }
defer func(writer io.WriteCloser) { defer func(writer io.WriteCloser) {
err := writer.Close() err := writer.Close()
if err != nil { if err != nil {
// todo: 处理关闭写入器时的错误 // todo: 处理关闭写入器时的错误
// todo: Handle errors when closing the writer
} }
}(writer) }(writer)

View File

@ -1,9 +1,8 @@
package utils package utils
import ( import (
"strconv"
"github.com/LiteyukiStudio/spage/config" "github.com/LiteyukiStudio/spage/config"
"strconv"
"github.com/cloudwego/hertz/pkg/app" "github.com/cloudwego/hertz/pkg/app"
) )
@ -13,12 +12,13 @@ type ctxType struct{}
var Ctx = ctxType{} var Ctx = ctxType{}
// GetPageLimit 封装从上下文中的query获取查询参数并转换为整数的函数出错返回默认值 // GetPageLimit 封装从上下文中的query获取查询参数并转换为整数的函数出错返回默认值
// packing from the query in the context and converting it to an integer, returning the default value on error
func (ctxType) GetPageLimit(c *app.RequestContext) (page, limit int) { func (ctxType) GetPageLimit(c *app.RequestContext) (page, limit int) {
pageString := c.Query("page") pageString := c.Query("page")
limitString := c.Query("limit") limitString := c.Query("limit")
page, err := strconv.Atoi(pageString) page, err := strconv.Atoi(pageString)
if err != nil || page <= 0 { if err != nil || page <= 0 {
page = 1 // 默认第一页 page = 1 // 默认第一页 Default to the first page
} }
limit, err = strconv.Atoi(limitString) limit, err = strconv.Atoi(limitString)

View File

@ -14,6 +14,7 @@ type PasswordType struct {
var Password = PasswordType{} var Password = PasswordType{}
// HashPassword 密码哈希函数 // HashPassword 密码哈希函数
// hash password function
func (u *PasswordType) HashPassword(password string, salt string) (string, error) { func (u *PasswordType) HashPassword(password string, salt string) (string, error) {
saltedPassword := Password.addSalt(password, salt) saltedPassword := Password.addSalt(password, salt)
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(saltedPassword), bcrypt.DefaultCost) hashedPassword, err := bcrypt.GenerateFromPassword([]byte(saltedPassword), bcrypt.DefaultCost)
@ -24,6 +25,8 @@ func (u *PasswordType) HashPassword(password string, salt string) (string, error
} }
// VerifyPassword 验证密码 // VerifyPassword 验证密码
// verify password
func (u *PasswordType) VerifyPassword(password, hashedPassword string, salt string) bool { func (u *PasswordType) VerifyPassword(password, hashedPassword string, salt string) bool {
saltedPassword := Password.addSalt(password, salt) saltedPassword := Password.addSalt(password, salt)
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(saltedPassword)) err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(saltedPassword))
@ -34,6 +37,10 @@ func (u *PasswordType) VerifyPassword(password, hashedPassword string, salt stri
// password: 原始密码 // password: 原始密码
// salt: 盐值 // salt: 盐值
// 返回值: 加盐后的密码 // 返回值: 加盐后的密码
// Add salt function
// password: original password
// salt: salt value
// return value: salted password
func (u *PasswordType) addSalt(password string, salt string) string { func (u *PasswordType) addSalt(password string, salt string) string {
combined := password + salt combined := password + salt
hash := sha256.New() hash := sha256.New()
@ -45,12 +52,17 @@ func (u *PasswordType) addSalt(password string, salt string) string {
// password: 待检查的密码 // password: 待检查的密码
// level: 复杂度级别(1-4) // level: 复杂度级别(1-4)
// 返回值: 是否满足复杂度要求 // 返回值: 是否满足复杂度要求
// Check password complexity based on the specified level
// password: the password to be checked
// level: complexity level (1-4)
// return value: whether it meets the complexity requirements
func (u *PasswordType) CheckPasswordComplexity(password string, level int) bool { func (u *PasswordType) CheckPasswordComplexity(password string, level int) bool {
if len(password) <= 8 { if len(password) <= 8 {
return false return false
} }
// 定义各种字符类型的检查标志 // 定义各种字符类型的检查标志
// Define flags for checking various character types
var ( var (
hasLower bool hasLower bool
hasUpper bool hasUpper bool

View File

@ -1,10 +1,9 @@
package utils package utils
import ( import (
"time"
"github.com/LiteyukiStudio/spage/config" "github.com/LiteyukiStudio/spage/config"
"github.com/LiteyukiStudio/spage/spage/models" "github.com/LiteyukiStudio/spage/spage/models"
"time"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"gorm.io/gorm" "gorm.io/gorm"
@ -16,13 +15,15 @@ var Token = TokenType{}
type Claims struct { type Claims struct {
jwt.RegisteredClaims jwt.RegisteredClaims
UserID uint `json:"user_id"` // 用户ID用于身份验证 UserID uint `json:"user_id"` // 用户ID用于身份验证 Verify user identity using the User ID
TokenID uint `json:"token_id"` // 令牌ID用于服务端会话维持 TokenID uint `json:"token_id"` // 令牌ID用于服务端会话维持 Keep the token ID for server-side session maintenance
Stateful bool `json:"stateful"` // 是否为有状态Token Stateful bool `json:"stateful"` // 是否为有状态Token Whether it is a stateful Token
} }
// CreateToken 生成用户会话令牌默认24小时有效 // CreateToken 生成用户会话令牌默认24小时有效
// stateful=false的无状态Token不会做持久化在实例重启后失效 // stateful=false的无状态Token不会做持久化在实例重启后失效
// Create a user session token (default 24 hours valid)
// stateful=false tokens are not persistent, and they will expire after the instance restarts
func (TokenType) CreateToken(userID uint, duration time.Duration, stateful bool, persistentHandler func(uint) (*models.Token, error)) (string, error) { func (TokenType) CreateToken(userID uint, duration time.Duration, stateful bool, persistentHandler func(uint) (*models.Token, error)) (string, error) {
var tokenModel *models.Token var tokenModel *models.Token
var err error var err error
@ -53,6 +54,7 @@ func (TokenType) CreateToken(userID uint, duration time.Duration, stateful bool,
} }
// ParseToken 解析JWT令牌 // ParseToken 解析JWT令牌
// Parse JWT token
func (TokenType) ParseToken(tokenString string, revokeChecker func(uint) bool) (*Claims, error) { func (TokenType) ParseToken(tokenString string, revokeChecker func(uint) bool) (*Claims, error) {
claims := &Claims{} claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) { token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) {
@ -68,6 +70,7 @@ func (TokenType) ParseToken(tokenString string, revokeChecker func(uint) bool) (
} }
// 有状态token被吊销也视为过期 // 有状态token被吊销也视为过期
// Revoked stateful tokens are considered expired
if claims.Stateful { if claims.Stateful {
if revokeChecker(claims.UserID) { if revokeChecker(claims.UserID) {
return nil, jwt.ErrTokenExpired return nil, jwt.ErrTokenExpired