mirror of
https://github.com/LiteyukiStudio/spage.git
synced 2025-07-15 02:21:01 +00:00
⏪ 恢复 #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
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:
@ -12,6 +12,7 @@ type AdminApi struct{}
|
||||
var Admin = AdminApi{}
|
||||
|
||||
// CreateUser 创建用户
|
||||
// Create User
|
||||
func (AdminApi) CreateUser(ctx context.Context, c *app.RequestContext) {
|
||||
var userDTO *UserDTO
|
||||
err := c.BindJSON(&userDTO)
|
||||
|
@ -17,6 +17,7 @@ type OrgApi struct{}
|
||||
var Org = OrgApi{}
|
||||
|
||||
// OrganizationDTO 组织信息数据传输对象
|
||||
// Organization Information Data Transfer Object (DTO)
|
||||
func getOrg(ctx context.Context) *models.Organization {
|
||||
org, ok := ctx.Value("userOrg").(*models.Organization)
|
||||
if !ok {
|
||||
@ -26,6 +27,7 @@ func getOrg(ctx context.Context) *models.Organization {
|
||||
}
|
||||
|
||||
// UserOrgAuth 组织权限检查
|
||||
// Organization permission check
|
||||
func (OrgApi) UserOrgAuth(ctx context.Context, c *app.RequestContext) {
|
||||
user := middle.Auth.GetUser(ctx, c)
|
||||
// 当 id 为空的 POST 请求默认为 create
|
||||
@ -47,7 +49,7 @@ func (OrgApi) UserOrgAuth(ctx context.Context, c *app.RequestContext) {
|
||||
return
|
||||
}
|
||||
// 判断权限 (GET 请求需要用户权限,其他请求需要管理员权限)
|
||||
|
||||
// Determine permissions (GET requests require user permissions, other requests require admin permissions)
|
||||
var authType string
|
||||
if string(c.Method()) == "GET" {
|
||||
authType = "member"
|
||||
@ -65,6 +67,7 @@ func (OrgApi) UserOrgAuth(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// OrganizationDTO 组织信息数据传输对象
|
||||
// Organization Information Data Transfer Object (DTO)
|
||||
func (OrgApi) ToDTO(org *models.Organization) OrganizationDTO {
|
||||
return OrganizationDTO{
|
||||
ID: org.ID,
|
||||
@ -78,22 +81,23 @@ func (OrgApi) ToDTO(org *models.Organization) OrganizationDTO {
|
||||
}
|
||||
|
||||
// CreateOrganization 创建组织
|
||||
// Create Organization
|
||||
func (OrgApi) CreateOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
// 绑定参数
|
||||
|
||||
// Bind parameters
|
||||
req := &CreateOrgReq{}
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
resps.BadRequest(c, resps.ParameterError)
|
||||
return
|
||||
}
|
||||
// 检验组织名称是否存在
|
||||
|
||||
// Check if the organization name already exists
|
||||
if store.Org.OrgNameIsExist(req.Name) {
|
||||
resps.BadRequest(c, "organization name already exists")
|
||||
return
|
||||
}
|
||||
// 创建组织
|
||||
|
||||
// Create organization
|
||||
user := middle.Auth.GetUser(ctx, c)
|
||||
org := models.Organization{
|
||||
Name: req.Name,
|
||||
@ -114,6 +118,7 @@ func (OrgApi) CreateOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// UpdateOrganization 更新组织信息
|
||||
// Update Organization Information
|
||||
func (OrgApi) UpdateOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
req := &UpdateOrgReq{}
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
@ -121,7 +126,7 @@ func (OrgApi) UpdateOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
return
|
||||
}
|
||||
org := getOrg(ctx)
|
||||
// 更新
|
||||
// 更新 Update
|
||||
org.DisplayName = req.DisplayName
|
||||
org.Email = req.Email
|
||||
org.Description = *req.Description
|
||||
@ -136,6 +141,7 @@ func (OrgApi) UpdateOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetOrganizationProject 获取组织项目
|
||||
// Get Organization Projects
|
||||
func (OrgApi) GetOrganizationProject(ctx context.Context, c *app.RequestContext) {
|
||||
req := &GetOrgProjectReq{}
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
@ -143,7 +149,7 @@ func (OrgApi) GetOrganizationProject(ctx context.Context, c *app.RequestContext)
|
||||
return
|
||||
}
|
||||
org := getOrg(ctx)
|
||||
// 查询
|
||||
// 查询 Query
|
||||
projects, total, err := store.Project.ListByOwner(constants.OwnerTypeOrg, strconv.Itoa(int(org.ID)), req.Page, req.Limit)
|
||||
if err != nil {
|
||||
resps.InternalServerError(c, "Failed to get projects")
|
||||
@ -161,6 +167,7 @@ func (OrgApi) GetOrganizationProject(ctx context.Context, c *app.RequestContext)
|
||||
}
|
||||
|
||||
// DeleteOrganization 删除组织
|
||||
// Delete Organization
|
||||
func (OrgApi) DeleteOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
org := getOrg(ctx)
|
||||
// 删除组织
|
||||
@ -172,6 +179,7 @@ func (OrgApi) DeleteOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetOrganization 获取组织信息
|
||||
// Get Organization Information
|
||||
func (OrgApi) GetOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
org := getOrg(ctx)
|
||||
resps.Ok(c, resps.OK, map[string]any{
|
||||
@ -180,6 +188,7 @@ func (OrgApi) GetOrganization(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetOrganizationUsers 获取组织用户
|
||||
// Get Organization Users
|
||||
func (OrgApi) GetOrganizationUsers(ctx context.Context, c *app.RequestContext) {
|
||||
org := getOrg(ctx)
|
||||
resps.Ok(c, resps.OK, map[string]any{
|
||||
@ -199,19 +208,20 @@ func (OrgApi) GetOrganizationUsers(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// AddOrganizationUser 添加组织用户
|
||||
// Add Organization User
|
||||
func (OrgApi) AddOrganizationUser(ctx context.Context, c *app.RequestContext) {
|
||||
req := &OrgUserReq{}
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
resps.BadRequest(c, resps.ParameterError)
|
||||
return
|
||||
}
|
||||
// 查询
|
||||
// 查询 Query
|
||||
user, err := store.User.GetByID(req.UserID)
|
||||
if err != nil || user == nil {
|
||||
resps.NotFound(c, resps.TargetNotFound)
|
||||
return
|
||||
}
|
||||
// 新增
|
||||
// 新增 Add
|
||||
org := getOrg(ctx)
|
||||
if req.Role == "member" {
|
||||
org.Members = append(org.Members, user)
|
||||
@ -230,19 +240,20 @@ func (OrgApi) AddOrganizationUser(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// DeleteOrganizationUser 删除组织用户
|
||||
// Delete Organization User
|
||||
func (OrgApi) DeleteOrganizationUser(ctx context.Context, c *app.RequestContext) {
|
||||
req := &OrgUserReq{}
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
resps.BadRequest(c, resps.ParameterError)
|
||||
return
|
||||
}
|
||||
// 查询
|
||||
// 查询 Query
|
||||
user, err := store.User.GetByID(req.UserID)
|
||||
if err != nil || user == nil {
|
||||
resps.NotFound(c, resps.TargetNotFound)
|
||||
return
|
||||
}
|
||||
// 删除
|
||||
// 删除 Delete
|
||||
org := getOrg(ctx)
|
||||
if req.Role == "member" {
|
||||
for i, member := range org.Members {
|
||||
|
@ -1,12 +1,13 @@
|
||||
package handlers
|
||||
|
||||
// OrganizationDTO 组织信息数据传输对象
|
||||
// Organization Information Data Transfer Object (DTO)
|
||||
type OrganizationDTO struct {
|
||||
ID uint `json:"id"` // 组织ID
|
||||
Name string `json:"name"` // 组织名称
|
||||
DisplayName *string `json:"display_name"` // 显示名称
|
||||
Email *string `json:"email"` // 邮箱地址
|
||||
Description string `json:"description"` // 描述信息
|
||||
ID uint `json:"id"` // 组织ID Organization ID
|
||||
Name string `json:"name"` // 组织名称 Organization Name
|
||||
DisplayName *string `json:"display_name"` // 显示名称 Display Name
|
||||
Email *string `json:"email"` // 邮箱地址 Email Address
|
||||
Description string `json:"description"` // 描述信息 Description
|
||||
AvatarURL *string `json:"avatar_url"` // 头像URL Avatar URL
|
||||
ProjectLimit int `json:"project_limit"` // 项目数量限制 Project Limit
|
||||
Members []UserDTO `json:"members"` // 组织成员 Members
|
||||
|
@ -18,6 +18,7 @@ type ProjectApi struct {
|
||||
var Project = ProjectApi{}
|
||||
|
||||
// ProjectDTO 项目信息数据传输对象
|
||||
// Project Information Data Transfer Object (DTO)
|
||||
func (ProjectApi) toDTO(project *models.Project, full bool) ProjectDTO {
|
||||
projectDto := ProjectDTO{
|
||||
Description: project.Description,
|
||||
@ -40,6 +41,7 @@ func (ProjectApi) toDTO(project *models.Project, full bool) ProjectDTO {
|
||||
}
|
||||
|
||||
// GetProject 获取项目信息
|
||||
// Get project information
|
||||
func getProject(ctx context.Context) *models.Project {
|
||||
project, ok := ctx.Value("userProject").(*models.Project)
|
||||
if !ok {
|
||||
@ -49,6 +51,7 @@ func getProject(ctx context.Context) *models.Project {
|
||||
}
|
||||
|
||||
// UserProjectAuth 用户项目权限认证
|
||||
// User project authorization
|
||||
func (ProjectApi) UserProjectAuth(ctx context.Context, c *app.RequestContext) {
|
||||
user := middle.Auth.GetUser(ctx, c)
|
||||
projectIdStr := c.Param("id")
|
||||
@ -69,12 +72,12 @@ func (ProjectApi) UserProjectAuth(ctx context.Context, c *app.RequestContext) {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// 项目权限判断
|
||||
// 项目权限判断 Project authorization check
|
||||
if store.Project.UserIsOwner(project, user.ID) {
|
||||
context.WithValue(ctx, "userProject", project)
|
||||
return
|
||||
}
|
||||
// 组织权限判断
|
||||
// 组织权限判断 Organization authorization check
|
||||
if project.OwnerType == constants.OwnerTypeOrg {
|
||||
// 组织查询
|
||||
org, err := store.Org.GetOrgById(project.OwnerID)
|
||||
@ -106,6 +109,7 @@ func (ProjectApi) UserProjectAuth(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// Create 创建项目
|
||||
// Create project
|
||||
func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) {
|
||||
req := CreateProjectReq{}
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
@ -113,10 +117,10 @@ func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) {
|
||||
return
|
||||
}
|
||||
user := middle.Auth.GetUser(ctx, c)
|
||||
// 校验权限
|
||||
// 校验权限 Check permissions
|
||||
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)
|
||||
if err != nil || org == nil {
|
||||
resps.InternalServerError(c, resps.ParameterError)
|
||||
@ -127,7 +131,7 @@ func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
} else if req.OwnerType == constants.OwnerTypeUser {
|
||||
// 如果为用户,仅允许为自己添加
|
||||
|
||||
// If it is a user, only allow adding for user-self
|
||||
req.OwnerID = user.ID
|
||||
} else {
|
||||
resps.BadRequest(c, resps.ParameterError)
|
||||
@ -151,6 +155,7 @@ func (ProjectApi) Create(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// Update 更新项目
|
||||
// Update project
|
||||
func (ProjectApi) Update(ctx context.Context, c *app.RequestContext) {
|
||||
req := UpdateProjectReq{}
|
||||
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)
|
||||
return
|
||||
}
|
||||
// 更新数据
|
||||
// 更新数据 Update data
|
||||
project.Description = *req.Description
|
||||
project.DisplayName = req.DisplayName
|
||||
project.Name = *req.Name
|
||||
@ -176,6 +181,7 @@ func (ProjectApi) Update(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// Delete 删除项目
|
||||
// Delete project
|
||||
func (ProjectApi) Delete(ctx context.Context, c *app.RequestContext) {
|
||||
project := getProject(ctx)
|
||||
if project == nil {
|
||||
@ -190,6 +196,7 @@ func (ProjectApi) Delete(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// Info 获取项目信息
|
||||
// Get project information
|
||||
func (ProjectApi) Info(ctx context.Context, c *app.RequestContext) {
|
||||
project := getProject(ctx)
|
||||
if project == nil {
|
||||
@ -202,6 +209,7 @@ func (ProjectApi) Info(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetOwners 获取项目所有者列表
|
||||
// Get project owner list
|
||||
func (ProjectApi) GetOwners(ctx context.Context, c *app.RequestContext) {
|
||||
project := getProject(ctx)
|
||||
if project == nil {
|
||||
@ -219,6 +227,7 @@ func (ProjectApi) GetOwners(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// AddOwner 添加项目所有者
|
||||
// Add project owner
|
||||
func (ProjectApi) AddOwner(ctx context.Context, c *app.RequestContext) {
|
||||
project := getProject(ctx)
|
||||
if project == nil {
|
||||
@ -236,14 +245,14 @@ func (ProjectApi) AddOwner(ctx context.Context, c *app.RequestContext) {
|
||||
resps.NotFound(c, resps.TargetNotFound)
|
||||
return
|
||||
}
|
||||
// 判断用户是否已存在权限列表
|
||||
// 判断用户是否已存在权限列表 Check if the user already exists in the permission list
|
||||
for _, owner := range project.Owners {
|
||||
if owner.ID == user.ID {
|
||||
resps.BadRequest(c, "User already exists in the permission list")
|
||||
return
|
||||
}
|
||||
}
|
||||
// 添加用户
|
||||
// 添加用户 Add user
|
||||
if err := store.Project.AddOwner(project, user); err != nil {
|
||||
resps.InternalServerError(c, resps.ParameterError)
|
||||
return
|
||||
@ -254,6 +263,7 @@ func (ProjectApi) AddOwner(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// DeleteOwner 删除项目所有者
|
||||
// Delete project owner
|
||||
func (ProjectApi) DeleteOwner(ctx context.Context, c *app.RequestContext) {
|
||||
project := getProject(ctx)
|
||||
if project == nil {
|
||||
@ -265,13 +275,13 @@ func (ProjectApi) DeleteOwner(ctx context.Context, c *app.RequestContext) {
|
||||
resps.BadRequest(c, resps.ParameterError)
|
||||
return
|
||||
}
|
||||
// 查询用户
|
||||
// 查询用户 Query user
|
||||
user, err := store.User.GetByID(req.UserID)
|
||||
if err != nil || user == nil {
|
||||
resps.NotFound(c, resps.TargetNotFound)
|
||||
return
|
||||
}
|
||||
// 删除用户
|
||||
// 删除用户 Delete user
|
||||
if err := store.Project.DeleteOwner(project, user); err != nil {
|
||||
resps.InternalServerError(c, resps.ParameterError)
|
||||
return
|
||||
@ -282,6 +292,7 @@ func (ProjectApi) DeleteOwner(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetSites 获取站点列表
|
||||
// Get site list
|
||||
func (ProjectApi) GetSites(ctx context.Context, c *app.RequestContext) {
|
||||
req := GetSiteListReq{}
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
|
@ -1,43 +1,48 @@
|
||||
package handlers
|
||||
|
||||
// ProjectDTO 项目信息数据传输对象
|
||||
// Project Information Data Transfer Object (DTO)
|
||||
type ProjectDTO struct {
|
||||
ID uint `json:"id"` // 项目ID
|
||||
Name string `json:"name"` // 项目名称
|
||||
DisplayName *string `json:"display_name"` // 项目显示名称
|
||||
Description string `json:"description"` // 项目描述
|
||||
OwnerType string `json:"owner_type"` // 项目拥有者类型
|
||||
OwnerID uint `json:"owner_id"` // 项目拥有者ID
|
||||
Owners []UserDTO `json:"owners"` // 项目拥有者列表
|
||||
SiteLimit int `json:"site_limit"` // 项目站点数量限制
|
||||
ID uint `json:"id"` // 项目ID Project ID
|
||||
Name string `json:"name"` // 项目名称 Project Name
|
||||
DisplayName *string `json:"display_name"` // 项目显示名称 Project Display Name
|
||||
Description string `json:"description"` // 项目描述 Project Description
|
||||
OwnerType string `json:"owner_type"` // 项目拥有者类型 Project Owner Type
|
||||
OwnerID uint `json:"owner_id"` // 项目拥有者ID Project Owner ID
|
||||
Owners []UserDTO `json:"owners"` // 项目拥有者列表 Project Owner List
|
||||
SiteLimit int `json:"site_limit"` // 项目站点数量限制 Project Site Limit
|
||||
}
|
||||
|
||||
// CreateProjectReq 创建项目请求参数
|
||||
// Create Project Request Parameters
|
||||
type CreateProjectReq struct {
|
||||
Name string `json:"name" binding:"required"` // 项目名称
|
||||
DisplayName *string `json:"display_name"` // 项目显示名称
|
||||
Description string `json:"description"` // 项目描述
|
||||
OwnerType string `json:"owner_type" binding:"required" vd:"in($,'user','organization')"` // 项目拥有者类型
|
||||
OwnerID uint `json:"owner_id" binding:"required"` // 项目拥有者ID
|
||||
Name string `json:"name" binding:"required"` // 项目名称 Project Name
|
||||
DisplayName *string `json:"display_name"` // 项目显示名称 Project Display Name
|
||||
Description string `json:"description"` // 项目描述 Project Description
|
||||
OwnerType string `json:"owner_type" binding:"required" vd:"in($,'user','organization')"` // 项目拥有者类型 Project Owner Type
|
||||
OwnerID uint `json:"owner_id" binding:"required"` // 项目拥有者ID Project Owner ID
|
||||
|
||||
}
|
||||
|
||||
// UpdateProjectReq 更新项目请求参数
|
||||
// Update Project Request Parameters
|
||||
type UpdateProjectReq struct {
|
||||
Name *string `json:"name"` // 项目名称
|
||||
DisplayName *string `json:"display_name"` // 项目显示名称
|
||||
Description *string `json:"description"` // 项目描述
|
||||
Name *string `json:"name"` // 项目名称 Project Name
|
||||
DisplayName *string `json:"display_name"` // 项目显示名称 Project Display Name
|
||||
Description *string `json:"description"` // 项目描述 Project Description
|
||||
}
|
||||
|
||||
// ProjectUserReq 项目用户请求参数
|
||||
// Project User Request Parameters
|
||||
type ProjectUserReq struct {
|
||||
UserID uint `json:"user_id" binding:"required"` // 用户ID
|
||||
UserID uint `json:"user_id" binding:"required"` // 用户ID User ID
|
||||
}
|
||||
|
||||
// GetProjectListReq 获取项目列表请求参数
|
||||
// Get Project List Request Parameters
|
||||
type GetSiteListReq struct {
|
||||
Page int `form:"page" binding:"required"` // 页码
|
||||
Limit int `form:"limit" binding:"required"` // 每页数量
|
||||
Project string `form:"project" binding:"required"` // 项目名称
|
||||
SiteName string `form:"site_name"` // 站点名称
|
||||
Page int `form:"page" binding:"required"` // 页码 Page
|
||||
Limit int `form:"limit" binding:"required"` // 每页数量 Page Limit
|
||||
Project string `form:"project" binding:"required"` // 项目名称 Project Name
|
||||
SiteName string `form:"site_name"` // 站点名称 Site Name
|
||||
}
|
||||
|
@ -2,10 +2,9 @@ package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/spage/models"
|
||||
"github.com/LiteyukiStudio/spage/spage/store"
|
||||
"strconv"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/resps"
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
@ -17,6 +16,7 @@ type SiteApi struct {
|
||||
var Site = SiteApi{}
|
||||
|
||||
// ToDTO 站点信息数据传输对象
|
||||
// Site Information Data Transfer Object (DTO)
|
||||
func (SiteApi) ToDTO(site *models.Site, full bool) SiteDTO {
|
||||
siteDTO := SiteDTO{
|
||||
Description: site.Description,
|
||||
@ -60,6 +60,7 @@ func (SiteApi) SiteAuth(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// Create 创建站点
|
||||
// Create Site
|
||||
func (SiteApi) Create(ctx context.Context, c *app.RequestContext) {
|
||||
req := CreateSiteReq{}
|
||||
if err := c.BindAndValidate(&req); err != nil {
|
||||
|
@ -1,28 +1,30 @@
|
||||
package handlers
|
||||
|
||||
// SiteDTO 网站详情
|
||||
// Site Detail
|
||||
type SiteDTO struct {
|
||||
ID uint `json:"id"` // 网站ID
|
||||
Name string `json:"name"` // 网站名称
|
||||
Description string `json:"description"` // 网站描述
|
||||
ProjectID uint `json:"project_id"` // 项目ID
|
||||
Project ProjectDTO `json:"project"` // 项目详情
|
||||
SubDomain *string `json:"sub_domain"` // 子域名
|
||||
Domains []string `json:"domains"` // 域名
|
||||
ID uint `json:"id"` // 网站ID WebSiteID
|
||||
Name string `json:"name"` // 网站名称 WebSiteName
|
||||
Description string `json:"description"` // 网站描述 WebSiteDescription
|
||||
ProjectID uint `json:"project_id"` // 项目ID ProjectID
|
||||
Project ProjectDTO `json:"project"` // 项目详情 ProjectDetail
|
||||
SubDomain *string `json:"sub_domain"` // 子域名 SubDomain
|
||||
Domains []string `json:"domains"` // 域名 Domains
|
||||
}
|
||||
|
||||
// CreateSiteReq 创建网站请求参数
|
||||
// Create Site Request Parameters
|
||||
type CreateSiteReq struct {
|
||||
Name string `json:"name" binding:"required"` // 网站名称
|
||||
Description string `json:"description"` // 网站描述
|
||||
ProjectID uint `json:"project_id" binding:"required"` // 项目ID
|
||||
SubDomain *string `json:"sub_domain"` // 子域名
|
||||
Domains []string `json:"domains"` // 域名
|
||||
Name string `json:"name" binding:"required"` // 网站名称 WebSiteName
|
||||
Description string `json:"description"` // 网站描述 WebSiteDescription
|
||||
ProjectID uint `json:"project_id" binding:"required"` // 项目ID ProjectID
|
||||
SubDomain *string `json:"sub_domain"` // 子域名 SubDomain
|
||||
Domains []string `json:"domains"` // 域名 Domains
|
||||
}
|
||||
|
||||
type UpdateSiteReq struct {
|
||||
Name *string `json:"name"` // 网站名称
|
||||
Description *string `json:"description"` // 网站描述
|
||||
SubDomain *string `json:"sub_domain"` // 子域名
|
||||
Domains []string `json:"domains"` // 域名
|
||||
Name *string `json:"name"` // 网站名称 WebSiteName
|
||||
Description *string `json:"description"` // 网站描述 WebSiteDescription
|
||||
SubDomain *string `json:"sub_domain"` // 子域名 SubDomain
|
||||
Domains []string `json:"domains"` // 域名 Domains
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ type UserApi struct{}
|
||||
var User = UserApi{}
|
||||
|
||||
// UserDTO 用户信息数据传输对象
|
||||
// User Information Data Transfer Object (DTO)
|
||||
func (UserApi) ToDTO(user *models.User, self bool) UserDTO {
|
||||
userDTO := UserDTO{
|
||||
ID: user.ID,
|
||||
@ -38,6 +39,7 @@ func (UserApi) ToDTO(user *models.User, self bool) UserDTO {
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
// User login
|
||||
func (UserApi) Login(ctx context.Context, c *app.RequestContext) {
|
||||
loginReq := &LoginReq{}
|
||||
// TODO: 这里需要验证验证码
|
||||
@ -90,6 +92,7 @@ func (UserApi) Login(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// Logout 用户登出
|
||||
// User logout
|
||||
func (UserApi) Logout(ctx context.Context, c *app.RequestContext) {
|
||||
// 删除cookie
|
||||
c.SetCookie("token", "", -1, "/", "", protocol.CookieSameSiteLaxMode, true, true)
|
||||
@ -98,6 +101,7 @@ func (UserApi) Logout(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetCaptcha 获取验证码
|
||||
// Get captcha
|
||||
func (UserApi) GetCaptcha(ctx context.Context, c *app.RequestContext) {
|
||||
resps.Ok(c, "ok", map[string]any{
|
||||
"provider": config.CaptchaType,
|
||||
@ -107,6 +111,7 @@ func (UserApi) GetCaptcha(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetUserOrgs 获取用户的组织
|
||||
// Get user organizations
|
||||
func (UserApi) GetOrgs(ctx context.Context, c *app.RequestContext) {
|
||||
userID := c.Param("id")
|
||||
crtUser := middle.Auth.GetUser(ctx, c)
|
||||
@ -133,6 +138,7 @@ func (UserApi) GetOrgs(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetUserProjects 获取用户的项目
|
||||
// Get user projects
|
||||
func (UserApi) GetProjects(ctx context.Context, c *app.RequestContext) {
|
||||
userID := c.Param("id")
|
||||
crtUser := middle.Auth.GetUser(ctx, c)
|
||||
@ -157,6 +163,7 @@ func (UserApi) GetProjects(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// GetUser 获取用户信息
|
||||
// Get user information
|
||||
func (UserApi) GetUser(ctx context.Context, c *app.RequestContext) {
|
||||
userID := c.Param("id")
|
||||
crtUser := middle.Auth.GetUser(ctx, c)
|
||||
@ -177,6 +184,7 @@ func (UserApi) GetUser(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
// User registration
|
||||
func (UserApi) Register(ctx context.Context, c *app.RequestContext) {
|
||||
// 接收参数
|
||||
request := &RegisterReq{}
|
||||
@ -221,6 +229,7 @@ func (UserApi) Register(ctx context.Context, c *app.RequestContext) {
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户信息
|
||||
// Update user information
|
||||
func (UserApi) UpdateUser(ctx context.Context, c *app.RequestContext) {
|
||||
userDTO := &UserDTO{}
|
||||
if err := c.BindJSON(userDTO); err != nil {
|
||||
|
@ -1,30 +1,33 @@
|
||||
package handlers
|
||||
|
||||
// RegisterReq 注册请求结构体
|
||||
// Registration request structure
|
||||
type RegisterReq struct {
|
||||
Username string `json:"username" binding:"required"` // 用户名
|
||||
Password string `json:"password" binding:"required"` // 密码
|
||||
Email string `json:"email" binding:"required"` // 邮箱
|
||||
Username string `json:"username" binding:"required"` // 用户名 Username
|
||||
Password string `json:"password" binding:"required"` // 密码 Password
|
||||
Email string `json:"email" binding:"required"` // 邮箱 Email
|
||||
|
||||
}
|
||||
|
||||
// LoginReq 登录请求结构体
|
||||
// Login request structure
|
||||
type LoginReq struct {
|
||||
Username string `json:"username" binding:"required"` // 用户名
|
||||
Password string `json:"password" binding:"required"` // 密码
|
||||
CaptchaToken string `json:"captcha_token" binding:"required"` // 验证码
|
||||
Username string `json:"username" binding:"required"` // 用户名 Username
|
||||
Password string `json:"password" binding:"required"` // 密码 Password
|
||||
CaptchaToken string `json:"captcha_token" binding:"required"` // 验证码 Token
|
||||
}
|
||||
|
||||
// OrganizationDTO 组织信息数据传输对象
|
||||
// Organization Information Data Transfer Object (DTO)
|
||||
type UserDTO struct {
|
||||
ID uint `json:"id"` // 用户ID
|
||||
Name string `json:"name"` // 用户名
|
||||
DisplayName *string `json:"display_name"` // 显示名称
|
||||
Email *string `json:"email"` // 邮箱
|
||||
Description string `json:"description"` // 描述
|
||||
Avatar *string `json:"avatar_url"` // 头像
|
||||
Role string `json:"role"` // 角色
|
||||
Organizations []OrganizationDTO `json:"organizations"` // 组织
|
||||
Language string `json:"language"` // 语言
|
||||
//Password string `json:"password"` // 密码
|
||||
ID uint `json:"id"` // 用户ID User ID
|
||||
Name string `json:"name"` // 用户名 Username
|
||||
DisplayName *string `json:"display_name"` // 显示名称 DisplayName
|
||||
Email *string `json:"email"` // 邮箱 Email
|
||||
Description string `json:"description"` // 描述 Description
|
||||
Avatar *string `json:"avatar_url"` // 头像 Avatar URL
|
||||
Role string `json:"role"` // 角色 Role
|
||||
Organizations []OrganizationDTO `json:"organizations"` // 组织 Organizations
|
||||
Language string `json:"language"` // 语言 Language
|
||||
//Password string `json:"password"` // 密码 Password
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// getMimeType 根据文件扩展名返回相应的 MIME 类型
|
||||
// as the file extension returns the corresponding MIME type
|
||||
func getMimeType(path string) string {
|
||||
// 这里可以根据文件扩展名返回相应的 MIME 类型
|
||||
ext := filepath.Ext(path)
|
||||
@ -18,6 +19,7 @@ func getMimeType(path string) string {
|
||||
}
|
||||
|
||||
// WebHandler 处理静态文件请求的 Handler
|
||||
// Handler for static file requests
|
||||
func WebHandler(ctx context.Context, c *app.RequestContext) {
|
||||
path := "dist" + string(c.Path())
|
||||
file, err := static.WebFS.Open(path)
|
||||
|
@ -3,14 +3,13 @@ package middle
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/config"
|
||||
"github.com/LiteyukiStudio/spage/constants"
|
||||
models "github.com/LiteyukiStudio/spage/spage/models"
|
||||
store "github.com/LiteyukiStudio/spage/spage/store"
|
||||
"github.com/LiteyukiStudio/spage/utils"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/resps"
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
@ -22,6 +21,7 @@ type authType struct{}
|
||||
var Auth = authType{}
|
||||
|
||||
// PersistentHandler 持久化处理函数,使用依赖注入到 utils 中防止循环引用
|
||||
// Persistent Handler Function, using dependency injection to prevent circular references
|
||||
func PersistentHandler(userID uint) (*models.Token, error) {
|
||||
token, err := store.JWT.CreateToken(userID)
|
||||
if err != nil {
|
||||
@ -31,25 +31,31 @@ func PersistentHandler(userID uint) (*models.Token, error) {
|
||||
}
|
||||
|
||||
// RevokeChecker 令牌撤销检查器,使用依赖注入到 utils 中防止循环引用
|
||||
// Token Revocation Checker, using dependency injection to prevent circular references
|
||||
func RevokeChecker(tokenID uint) bool {
|
||||
return store.JWT.IsTokenRevoked(tokenID)
|
||||
}
|
||||
|
||||
// UseAuth 中间件函数
|
||||
// Middleware function for authentication
|
||||
func (authType) UseAuth() app.HandlerFunc {
|
||||
return func(ctx context.Context, c *app.RequestContext) {
|
||||
// 1. 检查认证方式
|
||||
// 1. Check authentication method
|
||||
authHeader := string(c.GetHeader("Authorization"))
|
||||
|
||||
// 认证方式1:使用 Authorization Header
|
||||
// Authentication method 1: Use Authorization Header
|
||||
if authHeader != "" {
|
||||
// 检查 token 是否以 "Bearer " 开头,如果是,则去掉前缀
|
||||
// Check if the token starts with "Bearer ", if so, remove the prefix
|
||||
token := authHeader
|
||||
if strings.HasPrefix(token, "Bearer ") {
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
}
|
||||
|
||||
// 验证令牌
|
||||
// Verify token
|
||||
claims, err := utils.Token.ParseToken(token, RevokeChecker)
|
||||
if err != nil {
|
||||
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.Next(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// 认证方式2:使用 Cookie(启用无感刷新)
|
||||
// Authentication method 2: Use Cookie (Enable silent refresh)
|
||||
token := string(c.Cookie("token"))
|
||||
if token == "" {
|
||||
// 尝试通过 refresh_token 刷新
|
||||
// Try to refresh by refresh_token
|
||||
refreshToken := string(c.Cookie("refresh_token"))
|
||||
if refreshToken == "" {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
// 保存用户信息并继续请求
|
||||
// Save user information and continue request
|
||||
c.Set("user", refreshClaims.UserID)
|
||||
c.Next(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// Cookie 中存在 token,验证其有效性
|
||||
// Cookie contains token, verify its validity
|
||||
claims, err := utils.Token.ParseToken(token, RevokeChecker)
|
||||
if err != nil {
|
||||
// token 无效,尝试刷新
|
||||
// Token is invalid, try to refresh
|
||||
refreshToken := string(c.Cookie("refresh_token"))
|
||||
if refreshToken == "" {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
// 保存用户信息并继续请求
|
||||
// Save user information and continue request
|
||||
c.Set("user", refreshClaims.UserID)
|
||||
c.Next(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
// token 有效,继续请求
|
||||
// Token is valid, continue request
|
||||
ctx = context.WithValue(ctx, "user", claims.UserID)
|
||||
c.Next(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// IsAdmin 是一个中间件,用于检查用户是否为管理员
|
||||
// IsAdmin is a middleware that checks if the user is an admin
|
||||
func (authType) IsAdmin() app.HandlerFunc {
|
||||
return func(ctx context.Context, c *app.RequestContext) {
|
||||
user := Auth.GetUser(ctx, c)
|
||||
@ -156,6 +177,7 @@ func (authType) IsAdmin() app.HandlerFunc {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
userID := ctx.Value("user").(uint)
|
||||
if userID == 0 {
|
||||
|
@ -2,7 +2,6 @@ package middle
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/config"
|
||||
"github.com/LiteyukiStudio/spage/constants"
|
||||
"github.com/LiteyukiStudio/spage/utils"
|
||||
@ -22,6 +21,7 @@ type CaptchaReq struct {
|
||||
}
|
||||
|
||||
// UseCaptcha 中间件函数,用于验证验证码
|
||||
// Middleware function for captcha verification
|
||||
func (captchaType) UseCaptcha() app.HandlerFunc {
|
||||
captchaConfig := &utils.CaptchaConfig{
|
||||
Type: config.CaptchaType,
|
||||
@ -38,6 +38,7 @@ func (captchaType) UseCaptcha() app.HandlerFunc {
|
||||
}
|
||||
if config.Mode == constants.ModeDev && req.CaptchaToken == constants.CaptchaDevPasscode {
|
||||
// 开发模式密钥
|
||||
// Dev mode passkey
|
||||
c.Next(ctx)
|
||||
return
|
||||
}
|
||||
@ -56,6 +57,7 @@ func (captchaType) UseCaptcha() app.HandlerFunc {
|
||||
return
|
||||
}
|
||||
c.Next(ctx) // 如果验证码验证成功,则继续下一个处理程序
|
||||
// If captcha verification is successful, continue to the next handler
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ type corsType struct{}
|
||||
var Cors = corsType{}
|
||||
|
||||
// UseCors 跨域中间件
|
||||
// Cross-domain middleware
|
||||
func (corsType) UseCors() app.HandlerFunc {
|
||||
var allowedOrigins []string
|
||||
if config.Mode == constants.ModeDev {
|
||||
|
@ -14,6 +14,7 @@ type traceType struct{}
|
||||
var Trace = traceType{}
|
||||
|
||||
// UseTrace 中间件函数,用于记录请求日志
|
||||
// Middleware function for request logging
|
||||
func (traceType) UseTrace() app.HandlerFunc {
|
||||
return func(ctx context.Context, c *app.RequestContext) {
|
||||
start := time.Now()
|
||||
|
@ -5,56 +5,61 @@ import "gorm.io/gorm"
|
||||
// User 用户模型
|
||||
type User struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"not null;unique"` // 用户的唯一名称
|
||||
DisplayName *string `gorm:"column:display_name"` // 用户的显示名称
|
||||
Email *string `gorm:"unique"` // 用户的电子邮件地址,只有用户的电子邮件地址是唯一的(用于 oidc 身份验证)
|
||||
Description string `gorm:"default:'No description.'"` // 用户描述
|
||||
AvatarURL *string `gorm:"column:avatar_url"` // 留空以使用
|
||||
Role string `gorm:"not null;default:member"` // 用户的全局角色
|
||||
Organizations []*Organization `gorm:"many2many:organization_members;"` // 隶属于许多组织
|
||||
ProjectLimit int `gorm:"default:-1"` // 用户的项目限制,0 表示无限制
|
||||
Language string `gorm:"default:'zh-cn'"` // 用户的语言,默认为英语
|
||||
Flag string `gorm:"default:'0'"` // system_admin 的另一面旗帜
|
||||
Password *string `gorm:"column:password"` // 用户的密码(经过哈希处理),仅用于本地身份验证
|
||||
Name string `gorm:"not null;unique"` // 用户的唯一名称 User's unique name
|
||||
DisplayName *string `gorm:"column:display_name"` // 用户的显示名称 User's display name
|
||||
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.'"` // 用户描述 User description
|
||||
AvatarURL *string `gorm:"column:avatar_url"` // 留空以使用 Gravatar Leave blank to use Gravatar
|
||||
Role string `gorm:"not null;default:member"` // 用户的全局角色 User's global role
|
||||
Organizations []*Organization `gorm:"many2many:organization_members;"` // 隶属于许多组织 Many organizations the user belongs to
|
||||
ProjectLimit int `gorm:"default:-1"` // 用户的项目限制,0 表示无限制 User's project limit, 0 means no limit
|
||||
Language string `gorm:"default:'zh-cn'"` // 用户的语言,默认为英语 User's language, default to English
|
||||
Flag string `gorm:"default:'0'"` // system_admin 的另一面旗帜 The other side of system_admin flag
|
||||
Password *string `gorm:"column:password"` // 用户的密码(经过哈希处理),仅用于本地身份验证 User's password (hashed), only used for local authentication
|
||||
}
|
||||
|
||||
// TableName 用户
|
||||
// User
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// Organization 组织模型
|
||||
// Organization Model
|
||||
type Organization struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"not null;unique"` // 组织的唯一名称
|
||||
DisplayName *string `gorm:"column:display_name"` // 组织的显示名称
|
||||
Email *string `gorm:"column:email"` // 组织的电子邮件地址
|
||||
Description string `gorm:"default:'No description.'"` // 组织描述
|
||||
AvatarURL *string `gorm:"column:avatar_url"` // 留空以使用
|
||||
Members []*User `gorm:"many2many:organization_members;"` // 组织的成员包含创建者
|
||||
Owners []User `gorm:"many2many:organization_owners;"` // 组织的所有者(无反向关系)包含创建者
|
||||
ProjectLimit int `gorm:"default:0"` // 组织的项目限制,0:遵循策略,-1:无限制
|
||||
Name string `gorm:"not null;unique"` // 组织的唯一名称 Organization's unique name
|
||||
DisplayName *string `gorm:"column:display_name"` // 组织的显示名称 Organization's display name
|
||||
Email *string `gorm:"column:email"` // 组织的电子邮件地址 Organization's email address
|
||||
Description string `gorm:"default:'No description.'"` // 组织描述 Organization description
|
||||
AvatarURL *string `gorm:"column:avatar_url"` // 留空以使用 Gravatar Leave blank to use Gravatar
|
||||
Members []*User `gorm:"many2many:organization_members;"` // 组织的成员包含创建者 (including the creator)
|
||||
Owners []User `gorm:"many2many:organization_owners;"` // 组织的所有者(无反向关系)包含创建者 (including the creator)
|
||||
ProjectLimit int `gorm:"default:0"` // 组织的项目限制,0:遵循策略,-1:无限制 Organization's project limit, 0: follow the policy, -1: unlimited
|
||||
}
|
||||
|
||||
// TableName 组织
|
||||
// Organization
|
||||
func (Organization) TableName() string {
|
||||
return "organizations"
|
||||
}
|
||||
|
||||
// Project 项目模型
|
||||
// Project Model
|
||||
type Project struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"not null;unique"` // 项目的唯一名称
|
||||
DisplayName *string `gorm:"column:display_name"` // 项目的显示名称
|
||||
Description string `gorm:"default:'No description.'"` // 项目描述
|
||||
OwnerID uint `gorm:"not null"` // 所有者 ID(用户 ID 或组织 ID)
|
||||
OwnerType string `gorm:"not null"` // 所有者类型,可以是用户或组织
|
||||
Owners []User `gorm:"many2many:project_owners;"` // 项目的所有者,无反向关系
|
||||
Members []*User `gorm:"many2many:project_members;"` // 项目的成员
|
||||
SiteLimit int `gorm:"default:0"` // 项目的站点限制,0:遵循策略,-1:无限制
|
||||
Name string `gorm:"not null;unique"` // 项目的唯一名称 Project's unique name
|
||||
DisplayName *string `gorm:"column:display_name"` // 项目的显示名称 Project's display name
|
||||
Description string `gorm:"default:'No description.'"` // 项目描述 Project description
|
||||
OwnerID uint `gorm:"not null"` // 所有者 ID(用户 ID 或组织 ID) Owner ID (user ID or organization ID)
|
||||
OwnerType string `gorm:"not null"` // 所有者类型,可以是用户或组织 Owner type, can be user or organization
|
||||
Owners []User `gorm:"many2many:project_owners;"` // 项目的所有者,无反向关系 Project's owners, no reverse relation
|
||||
Members []*User `gorm:"many2many:project_members;"` // 项目的成员 Project's members
|
||||
SiteLimit int `gorm:"default:0"` // 项目的站点限制,0:遵循策略,-1:无限制 Project's site limit, 0: follow the policy, -1: unlimited
|
||||
}
|
||||
|
||||
// TableName 项目
|
||||
// Project
|
||||
func (Project) TableName() string {
|
||||
return "projects"
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import "gorm.io/gorm"
|
||||
|
||||
type File struct {
|
||||
gorm.Model
|
||||
ID uint `gorm:"primaryKey" json:"id"` // 文件ID
|
||||
Path string `gorm:"not null" json:"path"` // 文件路径,相较于根目录的相对路径
|
||||
MD5 string `gorm:"not null" json:"md5"` // 文件哈希值
|
||||
ID uint `gorm:"primaryKey" json:"id"` // 文件ID File ID
|
||||
Path string `gorm:"not null" json:"path"` // 文件路径,相较于根目录的相对路径 File path, relative to the root directory
|
||||
MD5 string `gorm:"not null" json:"md5"` // 文件哈希值 File hash
|
||||
}
|
||||
|
||||
// TableName 自定义表名
|
||||
// TableName 自定义表名 Custom table name
|
||||
func (File) TableName() string {
|
||||
return "projects"
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package models
|
||||
import "gorm.io/gorm"
|
||||
|
||||
// Migrate 迁移模型,通过依赖注入的方式,使用gorm.DB进行数据库操作
|
||||
// Migrate models, using gorm.DB for database operations through dependency injection
|
||||
func Migrate(db *gorm.DB) error {
|
||||
if err := db.AutoMigrate(
|
||||
// entity.go
|
||||
|
@ -5,16 +5,25 @@ import "gorm.io/gorm"
|
||||
type OIDCConfig struct {
|
||||
gorm.Model
|
||||
AdminGroups []string `gorm:"type:json;column:admin_groups;default:'[]'"` // 平台管理员组,默认为:[]string{},*为匹配所有组,储存为逗号分隔的字符串
|
||||
// Admin groups, default is: []string{}, * matches all groups, stored as a comma-separated string
|
||||
AllowedGroups []string `gorm:"type:json;column:allowed_groups;default:'[\"*\"]'"` // 允许登录的组,默认为:[]string{"*"},*为匹配所有组,储存为逗号分隔的字符串
|
||||
// Allowed groups for login, default is: []string{"*"}, * matches all groups, stored as a comma-separated string
|
||||
ClientID string `gorm:"column:client_id"` // 客户端ID
|
||||
// Client ID
|
||||
ClientSecret string `gorm:"column:client_secret"` // 客户端密钥
|
||||
// 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 重写表名
|
||||
// Rewrite table name
|
||||
func (OIDCConfig) TableName() string {
|
||||
return "oidc_configs"
|
||||
}
|
||||
|
@ -6,30 +6,30 @@ import (
|
||||
|
||||
type Site struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"unique"` // 站点名称
|
||||
Description string `gorm:"size:255"` // 站点描述
|
||||
ProjectID uint `gorm:"not null"` // 项目ID
|
||||
Project Project `gorm:"foreignKey:ProjectID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` // 项目
|
||||
SubDomain string `gorm:"unique;size:255"` // 子域前缀
|
||||
Domains []string `gorm:"type:json;default:'[]'"` // 允许的域名,json格式
|
||||
Name string `gorm:"unique"` // 站点名称 Site name
|
||||
Description string `gorm:"size:255"` // 站点描述 Site description
|
||||
ProjectID uint `gorm:"not null"` // 项目ID Project ID
|
||||
Project Project `gorm:"foreignKey:ProjectID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` // 项目 Project
|
||||
SubDomain string `gorm:"unique;size:255"` // 子域前缀 Subdomain prefix
|
||||
Domains []string `gorm:"type:json;default:'[]'"` // 允许的域名,json格式 Allowed domains, json format
|
||||
}
|
||||
|
||||
// 站点表名
|
||||
// 站点表名 Site table name
|
||||
func (Site) TableName() string {
|
||||
return "sites"
|
||||
}
|
||||
|
||||
// 站点发布表
|
||||
// 站点发布表 Site release table
|
||||
type SiteRelease struct {
|
||||
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"`
|
||||
Tag string `gorm:"not null"` // 版本标签
|
||||
FileID uint `gorm:"not null"` // 版本文件ID
|
||||
File File `gorm:"foreignKey:FileID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` // 版本文件
|
||||
Tag string `gorm:"not null"` // 版本标签 Version tag
|
||||
FileID uint `gorm:"not null"` // 版本文件ID Version file ID
|
||||
File File `gorm:"foreignKey:FileID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"` // 版本文件 Version file
|
||||
}
|
||||
|
||||
// 站点发布表名
|
||||
// 站点发布表名 Site release table name
|
||||
func (SiteRelease) TableName() string {
|
||||
return "site_releases"
|
||||
}
|
||||
|
@ -8,77 +8,78 @@ import (
|
||||
)
|
||||
|
||||
// Run 运行路由服务
|
||||
// Run router service
|
||||
func Run() error {
|
||||
// 运行路由
|
||||
// 运行路由 Run router
|
||||
H := server.New(server.WithHostPorts(":"+config.ServerPort), server.WithMaxRequestBodySize(config.FileMaxSize))
|
||||
H.Use(middle.Cors.UseCors(), middle.Trace.UseTrace())
|
||||
apiV1 := H.Group("/api/v1")
|
||||
|
||||
apiV1.Use(middle.Auth.UseAuth())
|
||||
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/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())
|
||||
userGroup := apiV1.Group("/user")
|
||||
{
|
||||
userGroup.PUT("", handlers.User.UpdateUser) // 更新用户信息
|
||||
userGroup.GET("", handlers.User.GetUser) // 获取用户信息
|
||||
userGroup.GET("/:id", handlers.User.GetUser) // 获取用户信息
|
||||
userGroup.GET("/:id/projects", handlers.User.GetProjects) // 获取用户项目
|
||||
userGroup.GET("/:id/orgs", handlers.User.GetOrgs) // 获取用户组织
|
||||
userGroup.PUT("", handlers.User.UpdateUser) // 更新用户信息 Update user info
|
||||
userGroup.GET("", handlers.User.GetUser) // 获取用户信息 Get user info
|
||||
userGroup.GET("/:id", handlers.User.GetUser) // 获取用户信息 Get user info
|
||||
userGroup.GET("/:id/projects", handlers.User.GetProjects) // 获取用户项目 Get user projects
|
||||
userGroup.GET("/:id/orgs", handlers.User.GetOrgs) // 获取用户组织 Get user orgs
|
||||
}
|
||||
orgGroup := apiV1.Group("/org", handlers.Org.UserOrgAuth)
|
||||
{
|
||||
orgGroup.POST("", handlers.Org.CreateOrganization) // 创建组织
|
||||
orgGroup.PUT("/:id", handlers.Org.UpdateOrganization) // 更新组织
|
||||
orgGroup.DELETE("/:id", handlers.Org.DeleteOrganization) // 删除组织
|
||||
orgGroup.GET("/:id", handlers.Org.GetOrganization) // 获取组织信息
|
||||
orgGroup.GET("/:id/projects", handlers.Org.GetOrganizationProject) // 获取组织项目
|
||||
orgGroup.GET("/:id/users", handlers.Org.GetOrganizationUsers) // 获取组织所有成员和所有者
|
||||
orgGroup.PUT("/:id/users", handlers.Org.AddOrganizationUser) // 添加组织成员或所有者
|
||||
orgGroup.DELETE("/:id/users", handlers.Org.DeleteOrganizationUser) // 删除组织成员或所有者
|
||||
orgGroup.POST("", handlers.Org.CreateOrganization) // 创建组织 Create organization
|
||||
orgGroup.PUT("/:id", handlers.Org.UpdateOrganization) // 更新组织 Update organization
|
||||
orgGroup.DELETE("/:id", handlers.Org.DeleteOrganization) // 删除组织 Delete organization
|
||||
orgGroup.GET("/:id", handlers.Org.GetOrganization) // 获取组织信息 Get organization info
|
||||
orgGroup.GET("/:id/projects", handlers.Org.GetOrganizationProject) // 获取组织项目 Get organization projects
|
||||
orgGroup.GET("/:id/users", handlers.Org.GetOrganizationUsers) // 获取组织所有成员和所有者 Get organization users
|
||||
orgGroup.PUT("/:id/users", handlers.Org.AddOrganizationUser) // 添加组织成员或所有者 Add organization user
|
||||
orgGroup.DELETE("/:id/users", handlers.Org.DeleteOrganizationUser) // 删除组织成员或所有者 Delete organization user
|
||||
}
|
||||
projectGroup := apiV1.Group("/project", handlers.Project.UserProjectAuth)
|
||||
{
|
||||
projectGroup.POST("", handlers.Project.Create) // 创建项目
|
||||
projectGroup.PUT("/:id", handlers.Project.Update) // 更新项目
|
||||
projectGroup.DELETE("/:id", handlers.Project.Delete) // 删除项目
|
||||
projectGroup.GET("/:id", handlers.Project.Info) // 获取项目信息
|
||||
projectGroup.GET("/:id/owners", handlers.Project.GetOwners) // 获取项目所有者
|
||||
projectGroup.PUT("/:id/owner", handlers.Project.AddOwner) // 更新项目所有者
|
||||
projectGroup.DELETE("/:id/owner", handlers.Project.DeleteOwner) // 删除项目所有者
|
||||
projectGroup.GET("/:id/sites", handlers.Project.GetSites) // 获取项目站点
|
||||
projectGroup.POST("", handlers.Project.Create) // 创建项目 Create project
|
||||
projectGroup.PUT("/:id", handlers.Project.Update) // 更新项目 Update project
|
||||
projectGroup.DELETE("/:id", handlers.Project.Delete) // 删除项目 Delete project
|
||||
projectGroup.GET("/:id", handlers.Project.Info) // 获取项目信息 Get project info
|
||||
projectGroup.GET("/:id/owners", handlers.Project.GetOwners) // 获取项目所有者 Get project owners
|
||||
projectGroup.PUT("/:id/owner", handlers.Project.AddOwner) // 更新项目所有者 Add project owner
|
||||
projectGroup.DELETE("/:id/owner", handlers.Project.DeleteOwner) // 删除项目所有者 Delete project owner
|
||||
projectGroup.GET("/:id/sites", handlers.Project.GetSites) // 获取项目站点 Get project sites
|
||||
siteGroup := projectGroup.Group("/:id/site", handlers.Site.SiteAuth)
|
||||
{
|
||||
siteGroup.POST("", handlers.Site.Create) // 创建站点
|
||||
siteGroup.PUT("/:site_id", handlers.Site.Update) // 更新站点
|
||||
siteGroup.DELETE("/:site_id", handlers.Site.Delete) // 删除站点
|
||||
siteGroup.GET("/:site_id", handlers.Site.Info) // 获取网站信息
|
||||
siteGroup.POST("", handlers.Site.Create) // 创建站点 Create site
|
||||
siteGroup.PUT("/:site_id", handlers.Site.Update) // 更新站点 Update site
|
||||
siteGroup.DELETE("/:site_id", handlers.Site.Delete) // 删除站点 Delete site
|
||||
siteGroup.GET("/:site_id", handlers.Site.Info) // 获取网站信息 Get site info
|
||||
|
||||
siteGroup.GET("/:site_id/releases", handlers.Release.ReleaseList) // 获取站点 release 列表
|
||||
siteRelease := siteGroup.Group("/:site_id/release")
|
||||
{
|
||||
siteRelease.POST("", handlers.Release.Create) // 创建站点发布
|
||||
siteRelease.DELETE("", handlers.Release.Delete) // 删除站点版本
|
||||
siteRelease.POST("", handlers.Release.Create) // 创建站点发布 Create site release
|
||||
siteRelease.DELETE("", handlers.Release.Delete) // 删除站点版本 Delete site release
|
||||
siteRelease.POST("/activation", handlers.Release.Activation) // 指定使用该站点版本
|
||||
}
|
||||
}
|
||||
}
|
||||
fileGroup := apiV1.Group("/file")
|
||||
{
|
||||
fileGroup.POST("", handlers.File.UploadFileStream) // 上传文件
|
||||
fileGroup.GET("") // 下载文件
|
||||
fileGroup.DELETE("") // 删除文件
|
||||
fileGroup.POST("", handlers.File.UploadFileStream) // 上传文件 Upload file
|
||||
fileGroup.GET("") // 下载文件 Download file
|
||||
fileGroup.DELETE("") // 删除文件 Delete file
|
||||
}
|
||||
adminGroup := apiV1.Group("/admin") // 管理员路由
|
||||
adminGroup.Use(middle.Auth.IsAdmin())
|
||||
{
|
||||
adminUser := adminGroup.Group("/user")
|
||||
{
|
||||
adminUser.POST("", handlers.Admin.CreateUser) // 创建用户
|
||||
adminUser.POST("", handlers.Admin.CreateUser) // 创建用户 Create user
|
||||
}
|
||||
adminNode := adminGroup.Group("/node")
|
||||
{
|
||||
@ -95,21 +96,21 @@ func Run() error {
|
||||
|
||||
}
|
||||
|
||||
// 设置静态文件目录
|
||||
// 设置静态文件目录 Set static file directory
|
||||
web := H.Group("")
|
||||
{
|
||||
web.GET("/*any", handlers.WebHandler)
|
||||
}
|
||||
|
||||
// 运行服务
|
||||
// 运行服务 Run service
|
||||
if config.Mode == "dev" {
|
||||
// 开发模式
|
||||
// 开发模式 Development mode
|
||||
err := H.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// 生产模式
|
||||
// 生产模式 Production mode
|
||||
H.Spin()
|
||||
}
|
||||
return nil
|
||||
|
@ -9,6 +9,7 @@ type JWTType struct{}
|
||||
var JWT = JWTType{}
|
||||
|
||||
// CreateToken 创建令牌
|
||||
// Create Token
|
||||
func (JWTType) CreateToken(userID uint) (*models.Token, error) {
|
||||
token := &models.Token{
|
||||
UserID: userID,
|
||||
@ -20,11 +21,14 @@ func (JWTType) CreateToken(userID uint) (*models.Token, error) {
|
||||
}
|
||||
|
||||
// IsTokenRevoked 检查令牌是否被撤销
|
||||
// Check if a token has been revoked
|
||||
func (JWTType) IsTokenRevoked(tokenID uint) bool {
|
||||
var count int64
|
||||
// 查询是否存在该令牌(未被删除的)
|
||||
// Check if the token exists (not deleted)
|
||||
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 {
|
||||
return true
|
||||
}
|
||||
@ -32,6 +36,7 @@ func (JWTType) IsTokenRevoked(tokenID uint) bool {
|
||||
}
|
||||
|
||||
// RevokeTokenByID 撤销令牌
|
||||
// Revoke Token
|
||||
func (JWTType) RevokeTokenByID(id uint) error {
|
||||
if err := DB.Where("id = ?", id).Delete(&models.Token{}).Error; err != nil {
|
||||
return err
|
||||
@ -40,6 +45,7 @@ func (JWTType) RevokeTokenByID(id uint) error {
|
||||
}
|
||||
|
||||
// RevokeTokenByUserID 撤销用户的所有令牌
|
||||
// Revoke all tokens for a user
|
||||
func (JWTType) RevokeTokenByUserID(userID uint) error {
|
||||
if err := DB.Where("user_id = ?", userID).Delete(&models.Token{}).Error; err != nil {
|
||||
return err
|
||||
|
@ -10,6 +10,7 @@ type orgType struct {
|
||||
var Org = orgType{}
|
||||
|
||||
// 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) {
|
||||
// 使用连接查询
|
||||
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获取组织
|
||||
// Get Organization by ID
|
||||
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
|
||||
return
|
||||
}
|
||||
|
||||
// OrgNameIsExist 判断组织名称是否存在
|
||||
// Check if the organization name exists
|
||||
func (o *orgType) OrgNameIsExist(name string) bool {
|
||||
var count int64
|
||||
DB.Model(&models.Organization{}).Where("name = ?", name).Count(&count)
|
||||
@ -40,6 +43,7 @@ func (o *orgType) OrgNameIsExist(name string) bool {
|
||||
}
|
||||
|
||||
// GetUserAuth 获取用户在组织中的权限
|
||||
// Get User's Authority in Organization
|
||||
func (o *orgType) GetUserAuth(org *models.Organization, userID uint) (auth string) {
|
||||
for _, owner := range org.Owners {
|
||||
if owner.ID == userID {
|
||||
|
@ -2,7 +2,6 @@ package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/constants"
|
||||
"github.com/LiteyukiStudio/spage/spage/models"
|
||||
)
|
||||
@ -13,17 +12,20 @@ type projectType struct {
|
||||
var Project = projectType{}
|
||||
|
||||
// Create 创建项目
|
||||
// Create a project
|
||||
func (p *projectType) Create(project *models.Project) (err error) {
|
||||
return DB.Create(project).Error
|
||||
}
|
||||
|
||||
// GetByID 通过项目ID获取项目
|
||||
// Get Project by ID
|
||||
func (p *projectType) GetByID(id uint) (project *models.Project, err error) {
|
||||
err = DB.First(&project, id).Preload("Owners").Error
|
||||
return
|
||||
}
|
||||
|
||||
// UserIsOwner 判断用户是否是项目的所有者
|
||||
// Check if a user is the owner of a project
|
||||
func (p *projectType) UserIsOwner(project *models.Project, userID uint) bool {
|
||||
if project.OwnerType == constants.OwnerTypeUser && project.OwnerID == userID {
|
||||
return true
|
||||
@ -37,6 +39,7 @@ func (p *projectType) UserIsOwner(project *models.Project, userID uint) bool {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
tableName := ""
|
||||
switch ownerType {
|
||||
@ -71,16 +74,19 @@ func (p *projectType) Delete(project *models.Project) (err error) {
|
||||
}
|
||||
|
||||
// AddOwner 为项目添加所有者
|
||||
// Add Owner to a project
|
||||
func (p *projectType) AddOwner(project *models.Project, user *models.User) (err error) {
|
||||
return DB.Model(project).Association("Owners").Append(user)
|
||||
}
|
||||
|
||||
// DeleteOwner 从项目删除所有者
|
||||
// Delete Owner from a project
|
||||
func (p *projectType) DeleteOwner(project *models.Project, user *models.User) (err error) {
|
||||
return DB.Model(project).Association("Owners").Delete(user)
|
||||
}
|
||||
|
||||
// GetSiteList 获取项目下的站点列表
|
||||
// Get Site List of a project
|
||||
func (p *projectType) GetSiteList(project *models.Project, page, limit int) (sites []models.Site, total int64, err error) {
|
||||
sites, total, err = Paginate[models.Site](
|
||||
DB,
|
||||
|
@ -10,11 +10,13 @@ type SiteType struct {
|
||||
var Site = SiteType{}
|
||||
|
||||
// Create 创建站点
|
||||
// Create Site
|
||||
func (s *SiteType) Create(site *models.Site) (err error) {
|
||||
return DB.Create(site).Error
|
||||
}
|
||||
|
||||
// GetByID 根据id获取站点信息
|
||||
// Get Site Info by ID
|
||||
func (s *SiteType) GetByID(id uint) (site *models.Site, err error) {
|
||||
site = &models.Site{}
|
||||
err = DB.Where("id = ?", id).Preload("Project").First(site).Error
|
||||
|
@ -3,9 +3,6 @@ package store
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"plugin"
|
||||
"runtime"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/config"
|
||||
"github.com/LiteyukiStudio/spage/constants"
|
||||
"github.com/LiteyukiStudio/spage/spage/models"
|
||||
@ -14,6 +11,8 @@ import (
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"plugin"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
@ -21,13 +20,13 @@ var DB *gorm.DB
|
||||
// DBConfig 数据库配置结构体
|
||||
type DBConfig struct {
|
||||
Driver string // 数据库驱动类型,例如 "sqlite" 或 "postgres" Database driver type, e.g., "sqlite" or "postgres"
|
||||
Path string // 路径
|
||||
Host string // PostgreSQL 主机名
|
||||
Port int // PostgreSQL 端口
|
||||
User string // PostgreSQL 用户名
|
||||
Password string // PostgreSQL 密码
|
||||
DBName string // PostgreSQL 数据库名
|
||||
SSLMode string // PostgreSQL SSL 模式
|
||||
Path string // SQLite 路径 SQLite path
|
||||
Host string // PostgreSQL 主机名 PostgreSQL hostname
|
||||
Port int // PostgreSQL 端口 PostgreSQL port
|
||||
User string // PostgreSQL 用户名 PostgreSQL username
|
||||
Password string // PostgreSQL 密码 PostgreSQL password
|
||||
DBName string // PostgreSQL 数据库名 PostgreSQL database name
|
||||
SSLMode string // PostgreSQL SSL 模式 PostgreSQL SSL mode
|
||||
}
|
||||
|
||||
// loadDBConfig 从配置文件加载数据库配置
|
||||
|
@ -2,7 +2,6 @@ package store
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/constants"
|
||||
"github.com/LiteyukiStudio/spage/spage/models"
|
||||
|
||||
@ -21,10 +20,10 @@ func (u *userType) Create(user *models.User) (err error) {
|
||||
|
||||
// GetByName 根据名称获取用户
|
||||
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
|
||||
if err != nil {
|
||||
return nil, err // 出错时返回
|
||||
return nil, err // 出错时返回nil When an error occurs, return nil
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
@ -74,26 +73,27 @@ func (u *userType) DeleteByID(id uint) (err error) {
|
||||
}
|
||||
|
||||
// UpdateSystemAdmin 更新系统管理员用户,不存在则创建
|
||||
// Update System Admin User, create if not exist
|
||||
func (u *userType) UpdateSystemAdmin(user *models.User) (err error) {
|
||||
// 设置该用户为系统管理员
|
||||
// 设置该用户为系统管理员 Set this user as a system admin
|
||||
user.Flag = constants.FlagSystemAdmin
|
||||
user.Role = constants.RoleAdmin
|
||||
|
||||
// 尝试查找系统管理员
|
||||
// 尝试查找系统管理员 Try to find system admin
|
||||
existingAdmin := models.User{}
|
||||
result := DB.Where("flag = ?", constants.FlagSystemAdmin).First(&existingAdmin)
|
||||
|
||||
if result.Error != nil {
|
||||
// 如果不存在系统管理员(记录未找到),则创建一个
|
||||
// 如果不存在系统管理员(记录未找到),则创建一个 If there is no system admin (record not found), create one
|
||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
||||
// 创建新的系统管理员
|
||||
// 创建新的系统管理员 Create new system admin
|
||||
return DB.Create(user).Error
|
||||
}
|
||||
// 其他错误则直接返回
|
||||
// 其他错误则直接返回 Other errors are returned directly
|
||||
return result.Error
|
||||
}
|
||||
// 系统管理员已存在,更新信息
|
||||
// 保留ID,更新其他字段
|
||||
// 系统管理员已存在,更新信息 System admin exists, update information
|
||||
// 保留ID,更新其他字段 Keep ID, update other fields
|
||||
user.ID = existingAdmin.ID
|
||||
return DB.Model(&existingAdmin).Updates(user).Error
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
|
||||
// Paginate 封装通用的分页查询逻辑
|
||||
// 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) {
|
||||
// 查询总记录数
|
||||
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 {
|
||||
limit = config.PageLimit
|
||||
}
|
||||
|
||||
// 执行分页查询
|
||||
// Execute pagination query
|
||||
queryDB := db
|
||||
if len(conditions) > 0 {
|
||||
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 添加预加载关系的辅助函数
|
||||
// Add a helper function to add preloaded relationships
|
||||
func WithPreloads(db *gorm.DB, preloads ...string) *gorm.DB {
|
||||
for _, preload := range preloads {
|
||||
db = db.Preload(preload)
|
||||
|
@ -2,7 +2,6 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/constants"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
@ -18,6 +17,7 @@ type CaptchaConfig struct {
|
||||
}
|
||||
|
||||
// VerifyCaptcha 根据提供的配置和令牌验证验证码
|
||||
// Verify captcha based on provided configuration and token
|
||||
func (captchaType) VerifyCaptcha(restyClient *resty.Client, captchaConfig *CaptchaConfig, captchaToken string) (bool, error) {
|
||||
switch captchaConfig.Type {
|
||||
case constants.CaptchaTypeDisable:
|
||||
|
@ -14,16 +14,17 @@ type EmailType struct{}
|
||||
var Email = EmailType{}
|
||||
|
||||
type EmailConfig struct {
|
||||
Enable bool // 邮箱启用状态
|
||||
Username string // 邮箱用户名
|
||||
Address string // 邮箱地址
|
||||
Host string // 邮箱服务器地址
|
||||
Port string // 邮箱服务器端口
|
||||
Password string // 邮箱密码
|
||||
SSL bool // 是否使用SSL
|
||||
Enable bool // 邮箱启用状态 Email enable status
|
||||
Username string // 邮箱用户名 Email username
|
||||
Address string // 邮箱地址 Email address
|
||||
Host string // 邮箱服务器地址 Email server address
|
||||
Port string // 邮箱服务器端口 Email server port
|
||||
Password string // 邮箱密码 Email password
|
||||
SSL bool // 是否使用SSL Email use SSL
|
||||
}
|
||||
|
||||
// SendTemplate 发送HTML模板,从配置文件中读取邮箱配置
|
||||
// Send HTML template, read email configuration from the configuration file
|
||||
func SendTemplate(emailConfig *EmailConfig, target, htmlTemplate string, placeholders map[string]string) error {
|
||||
for placeholder, value := range placeholders {
|
||||
htmlTemplate = strings.ReplaceAll(htmlTemplate, placeholder, value)
|
||||
@ -36,8 +37,10 @@ func SendTemplate(emailConfig *EmailConfig, target, htmlTemplate string, placeho
|
||||
}
|
||||
|
||||
// SendEmail 发送邮件
|
||||
// Send Email
|
||||
func SendEmail(emailConfig *EmailConfig, target, content string, isHTML bool) error {
|
||||
// 如果配置未启用,则直接返回nil
|
||||
// If the configuration is not enabled, return nil directly
|
||||
if !emailConfig.Enable {
|
||||
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) {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
@ -70,40 +74,47 @@ func SendEmail(emailConfig *EmailConfig, target, content string, isHTML bool) er
|
||||
if err != nil {
|
||||
return err
|
||||
// todo: 处理连接时的错误
|
||||
// todo: Handle errors during connection
|
||||
}
|
||||
|
||||
defer func(client *smtp.Client) {
|
||||
err := client.Quit()
|
||||
if err != nil {
|
||||
// todo: 处理关闭连接时的错误
|
||||
// todo: Handle errors when closing the connection
|
||||
}
|
||||
}(client)
|
||||
|
||||
if err = client.Auth(auth); err != nil {
|
||||
return err
|
||||
// todo: 处理身份验证时的错误
|
||||
// todo: Handle errors during authentication
|
||||
}
|
||||
|
||||
if err = client.Mail(emailConfig.Address); err != nil {
|
||||
return err
|
||||
// todo: 处理发件人时的错误
|
||||
// todo: Handle errors when processing the sender
|
||||
}
|
||||
|
||||
if err = client.Rcpt(target); err != nil {
|
||||
return err
|
||||
// todo: 处理收件人时的错误
|
||||
// todo: Handle errors when processing recipients
|
||||
}
|
||||
|
||||
writer, err := client.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
// todo: 处理数据写入器创建时的错误
|
||||
// todo: Handle errors when creating the data writer
|
||||
}
|
||||
|
||||
defer func(writer io.WriteCloser) {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
// todo: 处理关闭写入器时的错误
|
||||
// todo: Handle errors when closing the writer
|
||||
}
|
||||
}(writer)
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/config"
|
||||
"strconv"
|
||||
|
||||
"github.com/cloudwego/hertz/pkg/app"
|
||||
)
|
||||
@ -13,12 +12,13 @@ type ctxType struct{}
|
||||
var Ctx = ctxType{}
|
||||
|
||||
// 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) {
|
||||
pageString := c.Query("page")
|
||||
limitString := c.Query("limit")
|
||||
page, err := strconv.Atoi(pageString)
|
||||
if err != nil || page <= 0 {
|
||||
page = 1 // 默认第一页
|
||||
page = 1 // 默认第一页 Default to the first page
|
||||
}
|
||||
|
||||
limit, err = strconv.Atoi(limitString)
|
||||
|
@ -14,6 +14,7 @@ type PasswordType struct {
|
||||
var Password = PasswordType{}
|
||||
|
||||
// HashPassword 密码哈希函数
|
||||
// hash password function
|
||||
func (u *PasswordType) HashPassword(password string, salt string) (string, error) {
|
||||
saltedPassword := Password.addSalt(password, salt)
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(saltedPassword), bcrypt.DefaultCost)
|
||||
@ -24,6 +25,8 @@ func (u *PasswordType) HashPassword(password string, salt string) (string, error
|
||||
}
|
||||
|
||||
// VerifyPassword 验证密码
|
||||
// verify password
|
||||
|
||||
func (u *PasswordType) VerifyPassword(password, hashedPassword string, salt string) bool {
|
||||
saltedPassword := Password.addSalt(password, salt)
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(saltedPassword))
|
||||
@ -34,6 +37,10 @@ func (u *PasswordType) VerifyPassword(password, hashedPassword string, salt stri
|
||||
// password: 原始密码
|
||||
// salt: 盐值
|
||||
// 返回值: 加盐后的密码
|
||||
// Add salt function
|
||||
// password: original password
|
||||
// salt: salt value
|
||||
// return value: salted password
|
||||
func (u *PasswordType) addSalt(password string, salt string) string {
|
||||
combined := password + salt
|
||||
hash := sha256.New()
|
||||
@ -45,12 +52,17 @@ func (u *PasswordType) addSalt(password string, salt string) string {
|
||||
// password: 待检查的密码
|
||||
// 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 {
|
||||
if len(password) <= 8 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 定义各种字符类型的检查标志
|
||||
// Define flags for checking various character types
|
||||
var (
|
||||
hasLower bool
|
||||
hasUpper bool
|
||||
|
@ -1,10 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/LiteyukiStudio/spage/config"
|
||||
"github.com/LiteyukiStudio/spage/spage/models"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"gorm.io/gorm"
|
||||
@ -16,13 +15,15 @@ var Token = TokenType{}
|
||||
|
||||
type Claims struct {
|
||||
jwt.RegisteredClaims
|
||||
UserID uint `json:"user_id"` // 用户ID,用于身份验证
|
||||
TokenID uint `json:"token_id"` // 令牌ID,用于服务端会话维持
|
||||
Stateful bool `json:"stateful"` // 是否为有状态Token
|
||||
UserID uint `json:"user_id"` // 用户ID,用于身份验证 Verify user identity using the User ID
|
||||
TokenID uint `json:"token_id"` // 令牌ID,用于服务端会话维持 Keep the token ID for server-side session maintenance
|
||||
Stateful bool `json:"stateful"` // 是否为有状态Token Whether it is a stateful Token
|
||||
}
|
||||
|
||||
// CreateToken 生成用户会话令牌(默认24小时有效)
|
||||
// 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) {
|
||||
var tokenModel *models.Token
|
||||
var err error
|
||||
@ -53,6 +54,7 @@ func (TokenType) CreateToken(userID uint, duration time.Duration, stateful bool,
|
||||
}
|
||||
|
||||
// ParseToken 解析JWT令牌
|
||||
// Parse JWT token
|
||||
func (TokenType) ParseToken(tokenString string, revokeChecker func(uint) bool) (*Claims, error) {
|
||||
claims := &Claims{}
|
||||
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被吊销也视为过期
|
||||
// Revoked stateful tokens are considered expired
|
||||
if claims.Stateful {
|
||||
if revokeChecker(claims.UserID) {
|
||||
return nil, jwt.ErrTokenExpired
|
||||
|
Reference in New Issue
Block a user