implement user authentication and database initialization, add models for user, comment, label, and OIDC configuration

This commit is contained in:
2025-07-22 06:18:23 +08:00
parent 99a3f80e12
commit d1a040617f
23 changed files with 602 additions and 19 deletions

14
internal/model/comment.go Normal file
View File

@ -0,0 +1,14 @@
package model
import "gorm.io/gorm"
type Comment struct {
gorm.Model
UserID uint `gorm:"index"` // 评论的用户ID
User User `gorm:"foreignKey:UserID;references:ID"` // 关联的用户
TargetID uint `gorm:"index"` // 目标ID
TargetType string `gorm:"index"` // 目标类型,如 "post", "page"
ReplyID uint `gorm:"index"` // 回复的评论ID
Content string `gorm:"type:text"` // 评论内容
Depth int `gorm:"default:0"` // 评论的层级深度
}

1
internal/model/file.go Normal file
View File

@ -0,0 +1 @@
package model

10
internal/model/label.go Normal file
View File

@ -0,0 +1,10 @@
package model
import "gorm.io/gorm"
type Label struct {
gorm.Model
Key string `gorm:"uniqueIndex"` // 标签键,唯一标识
Value string `gorm:"type:text"` // 标签值,描述标签的内容
Color string `gorm:"type:text"` // 前端可用颜色代码
}

View File

@ -0,0 +1,89 @@
package model
import (
"fmt"
"gorm.io/gorm"
"resty.dev/v3"
"time"
)
type OidcConfig struct {
gorm.Model
Name string `gorm:"uniqueIndex"`
ClientID string `gorm:"column:client_id"` // 客户端ID
ClientSecret string `gorm:"column:client_secret"` // 客户端密钥
DisplayName string `gorm:"column:display_name"` // 显示名称,例如:轻雪通行证
GroupsClaim *string `gorm:"default:groups"` // 组声明,默认为:"groups"
Icon *string `gorm:"column:icon"` // 图标url为空则使用内置默认图标
OidcDiscoveryUrl string `gorm:"column:oidc_discovery_url"` // OpenID自动发现URL例如 https://pass.liteyuki.icu/.well-known/openid-configuration
Enabled bool `gorm:"column:enabled;default:true"` // 是否启用
// 以下字段为自动获取字段,每次更新配置时自动填充
Issuer string
AuthorizationEndpoint string
TokenEndpoint string
UserInfoEndpoint string
JwksUri string
}
type oidcDiscoveryResp struct {
Issuer string `json:"issuer" validate:"required"`
AuthorizationEndpoint string `json:"authorization_endpoint" validate:"required"`
TokenEndpoint string `json:"token_endpoint" validate:"required"`
UserInfoEndpoint string `json:"userinfo_endpoint" validate:"required"`
JwksUri string `json:"jwks_uri" validate:"required"`
// 可选字段
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
ResponseTypesSupported []string `json:"response_types_supported,omitempty"`
GrantTypesSupported []string `json:"grant_types_supported,omitempty"`
SubjectTypesSupported []string `json:"subject_types_supported,omitempty"`
IdTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
ClaimsSupported []string `json:"claims_supported,omitempty"`
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
}
func updateOidcConfigFromUrl(url string) (*oidcDiscoveryResp, error) {
client := resty.New()
client.SetTimeout(10 * time.Second) // 设置超时时间
var discovery oidcDiscoveryResp
resp, err := client.R().
SetHeader("Accept", "application/json").
SetResult(&discovery).
Get(url)
if err != nil {
return nil, fmt.Errorf("请求OIDC发现端点失败: %w", err)
}
if resp.StatusCode() != 200 {
return nil, fmt.Errorf("请求OIDC发现端点失败状态码: %d", resp.StatusCode())
}
// 验证必要字段
if discovery.Issuer == "" ||
discovery.AuthorizationEndpoint == "" ||
discovery.TokenEndpoint == "" ||
discovery.UserInfoEndpoint == "" ||
discovery.JwksUri == "" {
return nil, fmt.Errorf("OIDC发现端点响应缺少必要字段")
}
return &discovery, nil
}
func (o *OidcConfig) BeforeSave(tx *gorm.DB) (err error) {
// 设置默认值
if o.GroupsClaim == nil {
defaultGroupsClaim := "groups"
o.GroupsClaim = &defaultGroupsClaim
}
// 只有在创建新记录或更新 OidcDiscoveryUrl 字段时才更新端点信息
if tx.Statement.Changed("OidcDiscoveryUrl") {
discoveryResp, err := updateOidcConfigFromUrl(o.OidcDiscoveryUrl)
if err != nil {
return fmt.Errorf("更新OIDC配置失败: %w", err)
}
o.Issuer = discoveryResp.Issuer
o.AuthorizationEndpoint = discoveryResp.AuthorizationEndpoint
o.TokenEndpoint = discoveryResp.TokenEndpoint
o.UserInfoEndpoint = discoveryResp.UserInfoEndpoint
o.JwksUri = discoveryResp.JwksUri
}
return nil
}

1
internal/model/page.go Normal file
View File

@ -0,0 +1 @@
package model

12
internal/model/post.go Normal file
View File

@ -0,0 +1,12 @@
package model
import "gorm.io/gorm"
type Post struct {
gorm.Model
UserID uint `gorm:"index"` // 发布者的用户ID
User User `gorm:"foreignKey:UserID;references:ID"` // 关联的用户
Title string `gorm:"type:text;not null"` // 帖子标题
Content string `gorm:"type:text;not null"` // 帖子内容
Labels []Label `gorm:"many2many:post_labels;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"` // 关联的标签
}

15
internal/model/user.go Normal file
View File

@ -0,0 +1,15 @@
package model
import "gorm.io/gorm"
type User struct {
gorm.Model
Username string `gorm:"unique;index"` // 用户名,唯一
Nickname string
AvatarUrl string
Email string `gorm:"unique;index"`
Gender string
Role string `gorm:"default:'user'"`
Password string // 密码,存储加密后的值
}