diff --git a/handlers/admin_handler.go b/handlers/admin_handler.go
index 4fac5b9..9576ca8 100644
--- a/handlers/admin_handler.go
+++ b/handlers/admin_handler.go
@@ -1,6 +1,7 @@
package handlers
import (
+ "math"
"net/http"
"strconv"
@@ -11,11 +12,12 @@ import (
)
type AdminHandler struct {
- adminService *services.AdminService
+ adminService *services.AdminService
+ clientService *services.ClientService
}
-func NewAdminHandler(adminService *services.AdminService) *AdminHandler {
- return &AdminHandler{adminService: adminService}
+func NewAdminHandler(adminService *services.AdminService, clientService *services.ClientService) *AdminHandler {
+ return &AdminHandler{adminService: adminService, clientService: clientService}
}
func (h *AdminHandler) ShowAdminLogin(c *gin.Context) {
@@ -63,20 +65,153 @@ func (h *AdminHandler) ListUsers(c *gin.Context) {
})
}
+// ListClients 显示客户端列表页面
func (h *AdminHandler) ListClients(c *gin.Context) {
- page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
- pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
+ page := 1
+ pageSize := 10
- clients, total, err := h.adminService.ListClients(page, pageSize)
+ // 从查询参数获取分页信息
+ if pageStr := c.Query("page"); pageStr != "" {
+ if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
+ page = p
+ }
+ }
+ if pageSizeStr := c.Query("page_size"); pageSizeStr != "" {
+ if ps, err := strconv.Atoi(pageSizeStr); err == nil && ps > 0 {
+ pageSize = ps
+ }
+ }
+
+ // 获取客户端列表
+ clients, total, err := h.clientService.GetClients(page, pageSize)
if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ c.HTML(http.StatusInternalServerError, "error.html", gin.H{
+ "error": err.Error(),
+ })
return
}
c.HTML(http.StatusOK, "admin_clients.html", gin.H{
"clients": clients,
- "total": total,
"page": page,
"pageSize": pageSize,
+ "total": total,
+ "lastPage": int(math.Ceil(float64(total) / float64(pageSize))),
})
}
+
+// ShowCreateUser 显示创建用户页面
+func (h *AdminHandler) ShowCreateUser(c *gin.Context) {
+ c.HTML(http.StatusOK, "admin_create_user.html", gin.H{
+ "title": "创建用户",
+ })
+}
+
+// HandleCreateUser 处理创建用户请求
+func (h *AdminHandler) HandleCreateUser(c *gin.Context) {
+ username := c.PostForm("username")
+ password := c.PostForm("password")
+ email := c.PostForm("email")
+
+ if username == "" || password == "" || email == "" {
+ c.HTML(http.StatusBadRequest, "admin_create_user.html", gin.H{
+ "title": "创建用户",
+ "error": "用户名、密码和邮箱都不能为空",
+ })
+ return
+ }
+
+ _, err := h.adminService.CreateUser(username, password, email)
+ if err != nil {
+ c.HTML(http.StatusBadRequest, "admin_create_user.html", gin.H{
+ "title": "创建用户",
+ "error": "创建用户失败:" + err.Error(),
+ })
+ return
+ }
+
+ c.Redirect(http.StatusFound, "/admin/users")
+}
+
+// ShowEditUser 显示编辑用户页面
+func (h *AdminHandler) ShowEditUser(c *gin.Context) {
+ userID, err := strconv.ParseUint(c.Param("id"), 10, 32)
+ if err != nil {
+ c.HTML(http.StatusBadRequest, "error.html", gin.H{
+ "error": "无效的用户ID",
+ })
+ return
+ }
+
+ user, err := h.adminService.GetUser(uint(userID))
+ if err != nil {
+ c.HTML(http.StatusNotFound, "error.html", gin.H{
+ "error": "用户不存在",
+ })
+ return
+ }
+
+ c.HTML(http.StatusOK, "admin_edit_user.html", gin.H{
+ "title": "编辑用户",
+ "user": user,
+ })
+}
+
+// HandleEditUser 处理编辑用户请求
+func (h *AdminHandler) HandleEditUser(c *gin.Context) {
+ userID, err := strconv.ParseUint(c.Param("id"), 10, 32)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"})
+ return
+ }
+
+ username := c.PostForm("username")
+ email := c.PostForm("email")
+ isActiveStr := c.PostForm("is_active")
+ var isActive *bool
+ if isActiveStr != "" {
+ active := isActiveStr == "true"
+ isActive = &active
+ }
+
+ _, err = h.adminService.UpdateUser(uint(userID), username, email, isActive)
+ if err != nil {
+ c.HTML(http.StatusBadRequest, "admin_edit_user.html", gin.H{
+ "title": "编辑用户",
+ "error": "更新用户失败:" + err.Error(),
+ })
+ return
+ }
+
+ // 如果提供了新密码,则更新密码
+ newPassword := c.PostForm("password")
+ if newPassword != "" {
+ err = h.adminService.UpdateUserPassword(uint(userID), newPassword)
+ if err != nil {
+ c.HTML(http.StatusBadRequest, "admin_edit_user.html", gin.H{
+ "title": "编辑用户",
+ "error": "更新密码失败:" + err.Error(),
+ })
+ return
+ }
+ }
+
+ c.Redirect(http.StatusFound, "/admin/users")
+}
+
+// HandleDeleteUser 处理删除用户请求
+func (h *AdminHandler) HandleDeleteUser(c *gin.Context) {
+ userID, err := strconv.ParseUint(c.Param("id"), 10, 32)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "无效的用户ID"})
+ return
+ }
+
+ err = h.adminService.DeleteUser(uint(userID))
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "删除用户失败:" + err.Error()})
+ return
+ }
+
+ c.Redirect(http.StatusFound, "/admin/users")
+}
diff --git a/handlers/client_handler.go b/handlers/client_handler.go
new file mode 100644
index 0000000..ea06fcb
--- /dev/null
+++ b/handlers/client_handler.go
@@ -0,0 +1,75 @@
+package handlers
+
+import (
+ "net/http"
+
+ "oidc-oauth2-server/services"
+
+ "github.com/gin-gonic/gin"
+)
+
+type ClientHandler struct {
+ clientService *services.ClientService
+}
+
+func NewClientHandler(clientService *services.ClientService) *ClientHandler {
+ return &ClientHandler{clientService: clientService}
+}
+
+// RegisterRoutes 注册路由
+func (h *ClientHandler) RegisterRoutes(router *gin.Engine) {
+ api := router.Group("/api")
+ {
+ api.POST("/clients", h.CreateClient)
+ api.PUT("/clients/:id", h.UpdateClient)
+ api.DELETE("/clients/:id", h.DeleteClient)
+ }
+}
+
+// CreateClient 创建客户端
+func (h *ClientHandler) CreateClient(c *gin.Context) {
+ var req services.ClientRegistrationRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ client, err := h.clientService.RegisterClient(&req)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusCreated, client)
+}
+
+// UpdateClient 更新客户端
+func (h *ClientHandler) UpdateClient(c *gin.Context) {
+ clientID := c.Param("id")
+
+ var req services.ClientRegistrationRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ client, err := h.clientService.UpdateClient(clientID, &req)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, client)
+}
+
+// DeleteClient 删除客户端
+func (h *ClientHandler) DeleteClient(c *gin.Context) {
+ clientID := c.Param("id")
+
+ if err := h.clientService.DeleteClient(clientID); err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
+ return
+ }
+
+ c.Status(http.StatusOK)
+}
diff --git a/main.go b/main.go
index 1c10a4f..5e43504 100644
--- a/main.go
+++ b/main.go
@@ -56,6 +56,9 @@ func main() {
"add": func(a, b int) int {
return a + b
},
+ "multiply": func(a, b int) int {
+ return a * b
+ },
})
r.LoadHTMLGlob("templates/*")
@@ -75,7 +78,8 @@ func main() {
oidcHandler := handlers.NewOIDCHandler(config.GlobalConfig.OAuth.IssuerURL, oauthService, authService)
registrationHandler := handlers.NewRegistrationHandler(clientService)
tokenHandler := handlers.NewTokenHandler(tokenService)
- adminHandler := handlers.NewAdminHandler(adminService)
+ adminHandler := handlers.NewAdminHandler(adminService, clientService)
+ clientHandler := handlers.NewClientHandler(clientService)
// 认证路由
r.GET("/login", authHandler.ShowLogin)
@@ -113,6 +117,16 @@ func main() {
authorized.GET("/dashboard", adminHandler.Dashboard)
authorized.GET("/users", adminHandler.ListUsers)
authorized.GET("/clients", adminHandler.ListClients)
+
+ // 用户管理路由
+ authorized.GET("/users/create", adminHandler.ShowCreateUser)
+ authorized.POST("/users/create", adminHandler.HandleCreateUser)
+ authorized.GET("/users/:id/edit", adminHandler.ShowEditUser)
+ authorized.POST("/users/:id/edit", adminHandler.HandleEditUser)
+ authorized.POST("/users/:id/delete", adminHandler.HandleDeleteUser)
+
+ // 客户端管理API路由
+ clientHandler.RegisterRoutes(r)
}
}
diff --git a/models/client.go b/models/client.go
index f6cf75c..a768bab 100644
--- a/models/client.go
+++ b/models/client.go
@@ -4,13 +4,16 @@ import (
"time"
"gorm.io/datatypes"
- "gorm.io/gorm"
)
+// Client 表示 OAuth2 客户端
type Client struct {
- gorm.Model
- ClientID string `gorm:"uniqueIndex;not null"`
- ClientSecret string `gorm:"not null"`
+ ID uint `json:"id"`
+ ClientID string `json:"client_id" gorm:"unique"`
+ ClientSecret string `json:"client_secret,omitempty"`
+ Name string `json:"name"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
RedirectURIs datatypes.JSON `gorm:"type:json"`
TokenEndpointAuthMethod string `gorm:"not null"`
GrantTypes datatypes.JSON `gorm:"type:json"`
@@ -25,8 +28,19 @@ type Client struct {
SoftwareID string `gorm:"type:varchar(255)"`
SoftwareVersion string `gorm:"type:varchar(255)"`
IsActive bool `gorm:"default:true"`
- CreatedAt time.Time
- UpdatedAt time.Time
+}
+
+// CreateClientRequest 创建客户端的请求结构
+type CreateClientRequest struct {
+ ClientID string `json:"client_id" binding:"required"`
+ ClientSecret string `json:"client_secret" binding:"required"`
+ Name string `json:"name" binding:"required"`
+}
+
+// UpdateClientRequest 更新客户端的请求结构
+type UpdateClientRequest struct {
+ ClientID string `json:"client_id" binding:"required"`
+ Name string `json:"name" binding:"required"`
}
func (c *Client) TableName() string {
diff --git a/services/admin.go b/services/admin.go
index 13131b5..950626f 100644
--- a/services/admin.go
+++ b/services/admin.go
@@ -177,3 +177,72 @@ func (s *AdminService) ListClients(page, pageSize int) ([]models.Client, int64,
err := s.db.Offset((page - 1) * pageSize).Limit(pageSize).Find(&clients).Error
return clients, total, err
}
+
+// CreateUser 创建新用户
+func (s *AdminService) CreateUser(username, password, email string) (*models.User, error) {
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ return nil, err
+ }
+
+ user := &models.User{
+ Username: username,
+ Password: string(hashedPassword),
+ Email: email,
+ IsActive: true,
+ }
+
+ if err := s.db.Create(user).Error; err != nil {
+ return nil, err
+ }
+
+ return user, nil
+}
+
+// UpdateUser 更新用户信息
+func (s *AdminService) UpdateUser(id uint, username, email string, isActive *bool) (*models.User, error) {
+ user := &models.User{}
+ if err := s.db.First(user, id).Error; err != nil {
+ return nil, err
+ }
+
+ if username != "" {
+ user.Username = username
+ }
+ if email != "" {
+ user.Email = email
+ }
+ if isActive != nil {
+ user.IsActive = *isActive
+ }
+
+ if err := s.db.Save(user).Error; err != nil {
+ return nil, err
+ }
+
+ return user, nil
+}
+
+// UpdateUserPassword 更新用户密码
+func (s *AdminService) UpdateUserPassword(id uint, password string) error {
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
+ if err != nil {
+ return err
+ }
+
+ return s.db.Model(&models.User{}).Where("id = ?", id).Update("password", string(hashedPassword)).Error
+}
+
+// DeleteUser 删除用户
+func (s *AdminService) DeleteUser(id uint) error {
+ return s.db.Delete(&models.User{}, id).Error
+}
+
+// GetUser 获取单个用户信息
+func (s *AdminService) GetUser(id uint) (*models.User, error) {
+ user := &models.User{}
+ if err := s.db.First(user, id).Error; err != nil {
+ return nil, err
+ }
+ return user, nil
+}
diff --git a/services/client.go b/services/client.go
index a8ff806..dbd0520 100644
--- a/services/client.go
+++ b/services/client.go
@@ -209,3 +209,58 @@ func generateSecureToken(length int) string {
}
return base64.RawURLEncoding.EncodeToString(b)
}
+
+// GetClients 获取客户端列表(分页)
+func (s *ClientService) GetClients(page, pageSize int) ([]ClientResponse, int64, error) {
+ var clients []models.Client
+ var total int64
+
+ // 获取总数
+ if err := s.db.Model(&models.Client{}).Count(&total).Error; err != nil {
+ return nil, 0, err
+ }
+
+ // 获取分页数据
+ offset := (page - 1) * pageSize
+ if err := s.db.Offset(offset).Limit(pageSize).Find(&clients).Error; err != nil {
+ return nil, 0, err
+ }
+
+ // 转换为响应格式
+ responses := make([]ClientResponse, len(clients))
+ for i, client := range clients {
+ // 解析 JSON 字段
+ var redirectURIs, grantTypes, responseTypes, scopes, contacts []string
+ json.Unmarshal(client.RedirectURIs, &redirectURIs)
+ json.Unmarshal(client.GrantTypes, &grantTypes)
+ json.Unmarshal(client.ResponseTypes, &responseTypes)
+ json.Unmarshal(client.Scopes, &scopes)
+ json.Unmarshal(client.Contacts, &contacts)
+
+ responses[i] = ClientResponse{
+ ClientID: client.ClientID,
+ ClientSecret: client.ClientSecret,
+ ClientIDIssuedAt: client.CreatedAt,
+ RedirectURIs: redirectURIs,
+ TokenEndpointAuthMethod: client.TokenEndpointAuthMethod,
+ GrantTypes: grantTypes,
+ ResponseTypes: responseTypes,
+ ClientName: client.ClientName,
+ ClientURI: client.ClientURI,
+ LogoURI: client.LogoURI,
+ Scope: func() string {
+ if len(scopes) > 0 {
+ return scopes[0]
+ }
+ return ""
+ }(),
+ Contacts: contacts,
+ TosURI: client.TosURI,
+ PolicyURI: client.PolicyURI,
+ SoftwareID: client.SoftwareID,
+ SoftwareVersion: client.SoftwareVersion,
+ }
+ }
+
+ return responses, total, nil
+}
diff --git a/templates/admin_clients.html b/templates/admin_clients.html
index e7ba984..c2fd841 100644
--- a/templates/admin_clients.html
+++ b/templates/admin_clients.html
@@ -1,38 +1,281 @@
{{template "header" .}}
-
客户端管理
-
-
-
- | ID |
- 客户端ID |
- 名称 |
- 创建时间 |
-
-
-
- {{range .clients}}
-
- | {{.ID}} |
- {{.ClientID}} |
- {{.Name}} |
- {{.CreatedAt.Format "2006-01-02 15:04:05"}} |
-
- {{end}}
-
-
+
+
+ 客户端管理
+
+
+
+
+
+
+
+
+ | 客户端ID |
+ 客户端密钥 |
+ 名称 |
+ 重定向URI |
+ 创建时间 |
+ 操作 |
+
+
+
+ {{range .clients}}
+
+ | {{.ClientID}} |
+ {{.ClientSecret}} |
+ {{.ClientName}} |
+
+
+ {{range $i, $uri := .RedirectURIs}}{{if $i}}, {{end}}{{$uri}}{{end}}
+
+ |
+ {{.ClientIDIssuedAt.Format "2006-01-02 15:04:05"}} |
+
+ {{$redirectURIsStr := ""}}
+ {{range $i, $uri := .RedirectURIs}}
+ {{if $i}}
+ {{$redirectURIsStr = printf "%s,%s" $redirectURIsStr $uri}}
+ {{else}}
+ {{$redirectURIsStr = $uri}}
+ {{end}}
+ {{end}}
+
+
+
+
+ |
+
+ {{end}}
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{template "footer" .}}
\ No newline at end of file
diff --git a/templates/admin_create_user.html b/templates/admin_create_user.html
new file mode 100644
index 0000000..2372917
--- /dev/null
+++ b/templates/admin_create_user.html
@@ -0,0 +1,32 @@
+
+
+
+ 创建用户 - OIDC OAuth2 管理系统
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/admin_edit_user.html b/templates/admin_edit_user.html
new file mode 100644
index 0000000..f095329
--- /dev/null
+++ b/templates/admin_edit_user.html
@@ -0,0 +1,36 @@
+
+
+
+ 编辑用户 - OIDC OAuth2 管理系统
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/admin_users.html b/templates/admin_users.html
index 6d0010a..cc4cf19 100644
--- a/templates/admin_users.html
+++ b/templates/admin_users.html
@@ -1,38 +1,110 @@
-{{template "header" .}}
-
-
用户管理
-
-
-
- | ID |
- 用户名 |
- 邮箱 |
- 创建时间 |
-
-
-
- {{range .users}}
-
- | {{.ID}} |
- {{.Username}} |
- {{.Email}} |
- {{.CreatedAt.Format "2006-01-02 15:04:05"}} |
-
- {{end}}
-
-
-
-
-{{template "footer" .}}
\ No newline at end of file
+
+
+
+
+ {{if .error}}
+
{{.error}}
+ {{end}}
+
+
+
+
+ | ID |
+ 用户名 |
+ 邮箱 |
+ 状态 |
+ 创建时间 |
+ 操作 |
+
+
+
+ {{range .users}}
+
+ | {{.ID}} |
+ {{.Username}} |
+ {{.Email}} |
+
+ {{if .IsActive}}
+ 激活
+ {{else}}
+ 禁用
+ {{end}}
+ |
+ {{.CreatedAt.Format "2006-01-02 15:04:05"}} |
+
+ 编辑
+
+ |
+
+ {{end}}
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/templates/header.html b/templates/header.html
index a250bc8..36fb0ac 100644
--- a/templates/header.html
+++ b/templates/header.html
@@ -4,6 +4,7 @@
OIDC OAuth2 管理系统
+