取消游客模式,强制登录后使用
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# A股工具 · 用户体系
|
# A股工具 · 用户体系
|
||||||
|
|
||||||
基于 **Go + PostgreSQL + Docker Compose** 的用户权限管理模块,角色覆盖:系统管理员、管理员、用户、游客。前端界面复用 `refer/` 目录下的暗色主题设计语言。
|
基于 **Go + PostgreSQL + Docker Compose** 的用户权限管理模块,角色覆盖:系统管理员、管理员、用户。前端界面复用 `refer/` 目录下的暗色主题设计语言。
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
@@ -50,13 +50,11 @@ WHERE username = '你的用户名';
|
|||||||
| 系统管理员 | 管理管理员、用户、系统配置 |
|
| 系统管理员 | 管理管理员、用户、系统配置 |
|
||||||
| 管理员 | 管理普通用户 |
|
| 管理员 | 管理普通用户 |
|
||||||
| 用户 | 访问业务功能、修改个人资料 |
|
| 用户 | 访问业务功能、修改个人资料 |
|
||||||
| 游客 | 仅查看公开内容,不可操作 |
|
|
||||||
|
|
||||||
## 主要 API
|
## 主要 API
|
||||||
|
|
||||||
| 方法 | 路径 | 说明 | 权限 |
|
| 方法 | 路径 | 说明 | 权限 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | /api/public | 公开信息 | 公开 |
|
|
||||||
| POST | /api/auth/register | 注册(默认 user) | 公开 |
|
| POST | /api/auth/register | 注册(默认 user) | 公开 |
|
||||||
| POST | /api/auth/login | 登录 | 公开 |
|
| POST | /api/auth/login | 登录 | 公开 |
|
||||||
| GET | /api/auth/me | 当前用户 | 需登录 |
|
| GET | /api/auth/me | 当前用户 | 需登录 |
|
||||||
@@ -107,5 +105,4 @@ npm run dev
|
|||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
- 生产环境请务必修改 `JWT_SECRET`。
|
- 生产环境请务必修改 `JWT_SECRET`。
|
||||||
- 游客通过首页直接访问,无需登录。
|
|
||||||
- 首次启动时 GORM 会自动创建表并插入默认角色。
|
- 首次启动时 GORM 会自动创建表并插入默认角色。
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ type AdminHandler struct {
|
|||||||
func allowedRolesForCreation(actor models.RoleName) []models.RoleName {
|
func allowedRolesForCreation(actor models.RoleName) []models.RoleName {
|
||||||
switch actor {
|
switch actor {
|
||||||
case models.RoleSystemAdmin:
|
case models.RoleSystemAdmin:
|
||||||
return []models.RoleName{models.RoleAdmin, models.RoleUser, models.RoleGuest}
|
return []models.RoleName{models.RoleAdmin, models.RoleUser}
|
||||||
case models.RoleAdmin:
|
case models.RoleAdmin:
|
||||||
return []models.RoleName{models.RoleUser, models.RoleGuest}
|
return []models.RoleName{models.RoleUser}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,13 +153,6 @@ func (h *AuthHandler) Logout(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"message": "登出成功"})
|
c.JSON(http.StatusOK, gin.H{"message": "登出成功"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AuthHandler) PublicInfo(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"message": "欢迎访问 A股工具公开信息",
|
|
||||||
"guest_allowed": true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashPassword(password string) (string, error) {
|
func hashPassword(password string) (string, error) {
|
||||||
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
return string(bytes), err
|
return string(bytes), err
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ type CurrentUser struct {
|
|||||||
Role models.RoleName
|
Role models.RoleName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var publicPaths = map[string]struct{}{
|
||||||
|
"/api/auth/register": {},
|
||||||
|
"/api/auth/login": {},
|
||||||
|
}
|
||||||
|
|
||||||
func AuthMiddleware(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
func AuthMiddleware(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
@@ -41,6 +46,17 @@ func AuthMiddleware(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, exists := c.Get("currentUser"); !exists {
|
||||||
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, ok := publicPaths[c.Request.URL.Path]; !ok {
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "error": "访问令牌无效或已过期"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ const (
|
|||||||
RoleSystemAdmin RoleName = "system_admin"
|
RoleSystemAdmin RoleName = "system_admin"
|
||||||
RoleAdmin RoleName = "admin"
|
RoleAdmin RoleName = "admin"
|
||||||
RoleUser RoleName = "user"
|
RoleUser RoleName = "user"
|
||||||
RoleGuest RoleName = "guest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r RoleName) String() string { return string(r) }
|
func (r RoleName) String() string { return string(r) }
|
||||||
@@ -43,10 +42,8 @@ func ParseRoleName(s string) (RoleName, error) {
|
|||||||
return RoleAdmin, nil
|
return RoleAdmin, nil
|
||||||
case "user":
|
case "user":
|
||||||
return RoleUser, nil
|
return RoleUser, nil
|
||||||
case "guest":
|
|
||||||
return RoleGuest, nil
|
|
||||||
default:
|
default:
|
||||||
return RoleGuest, fmt.Errorf("unknown role: %s", s)
|
return "", fmt.Errorf("unknown role: %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +102,6 @@ func SeedRoles(db *gorm.DB) error {
|
|||||||
{Name: string(RoleSystemAdmin), Description: strPtr("系统管理员,可管理管理员与系统配置"), Permissions: `["*"]`},
|
{Name: string(RoleSystemAdmin), Description: strPtr("系统管理员,可管理管理员与系统配置"), Permissions: `["*"]`},
|
||||||
{Name: string(RoleAdmin), Description: strPtr("管理员,可管理普通用户"), Permissions: `["users.read", "users.write", "users.create"]`},
|
{Name: string(RoleAdmin), Description: strPtr("管理员,可管理普通用户"), Permissions: `["users.read", "users.write", "users.create"]`},
|
||||||
{Name: string(RoleUser), Description: strPtr("普通用户,可访问业务功能"), Permissions: `["dashboard.read", "profile.write"]`},
|
{Name: string(RoleUser), Description: strPtr("普通用户,可访问业务功能"), Permissions: `["dashboard.read", "profile.write"]`},
|
||||||
{Name: string(RoleGuest), Description: strPtr("游客,仅可查看公开内容"), Permissions: `["public.read"]`},
|
|
||||||
}
|
}
|
||||||
for _, role := range roles {
|
for _, role := range roles {
|
||||||
var existing Role
|
var existing Role
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ func Setup(cfg *config.Config, db *gorm.DB) *gin.Engine {
|
|||||||
|
|
||||||
r.Use(middleware.AuthMiddleware(cfg, db))
|
r.Use(middleware.AuthMiddleware(cfg, db))
|
||||||
|
|
||||||
// 公开接口
|
// 公开接口(仅登录/注册)
|
||||||
r.GET("/api/public", authHandler.PublicInfo)
|
|
||||||
r.POST("/api/auth/register", authHandler.Register)
|
r.POST("/api/auth/register", authHandler.Register)
|
||||||
r.POST("/api/auth/login", authHandler.Login)
|
r.POST("/api/auth/login", authHandler.Login)
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ export interface UserInfo {
|
|||||||
id: string
|
id: string
|
||||||
username: string
|
username: string
|
||||||
email?: string
|
email?: string
|
||||||
role: 'system_admin' | 'admin' | 'user' | 'guest'
|
role: 'system_admin' | 'admin' | 'user'
|
||||||
status: string
|
status: string
|
||||||
created_at: string
|
created_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Role {
|
export interface Role {
|
||||||
id: number
|
id: number
|
||||||
name: 'system_admin' | 'admin' | 'user' | 'guest'
|
name: 'system_admin' | 'admin' | 'user'
|
||||||
description?: string
|
description?: string
|
||||||
permissions: string[]
|
permissions: string[]
|
||||||
created_at: string
|
created_at: string
|
||||||
@@ -79,9 +79,6 @@ async function request<T>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
publicInfo: () =>
|
|
||||||
request<{ message: string; guest_allowed: boolean }>('/api/public'),
|
|
||||||
|
|
||||||
register: (body: { username: string; email?: string; password: string }) =>
|
register: (body: { username: string; email?: string; password: string }) =>
|
||||||
request<AuthResponse>('/api/auth/register', {
|
request<AuthResponse>('/api/auth/register', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -30,13 +30,12 @@ export function clearAuth() {
|
|||||||
localStorage.removeItem('user')
|
localStorage.removeItem('user')
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RoleName = 'system_admin' | 'admin' | 'user' | 'guest'
|
export type RoleName = 'system_admin' | 'admin' | 'user'
|
||||||
|
|
||||||
const ROLE_RANK: Record<RoleName, number> = {
|
const ROLE_RANK: Record<RoleName, number> = {
|
||||||
system_admin: 3,
|
system_admin: 3,
|
||||||
admin: 2,
|
admin: 2,
|
||||||
user: 1,
|
user: 1,
|
||||||
guest: 0,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function roleRank(role: RoleName): number {
|
export function roleRank(role: RoleName): number {
|
||||||
@@ -51,8 +50,6 @@ export function roleLabel(role: RoleName): string {
|
|||||||
return '管理员'
|
return '管理员'
|
||||||
case 'user':
|
case 'user':
|
||||||
return '用户'
|
return '用户'
|
||||||
case 'guest':
|
|
||||||
return '游客'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { LayoutDashboard, ShieldCheck } from 'lucide-react'
|
||||||
import { LayoutDashboard, Globe, ShieldCheck } from 'lucide-react'
|
|
||||||
import { api } from '@/lib/api'
|
|
||||||
import { getStoredUser, roleLabel } from '@/lib/auth'
|
import { getStoredUser, roleLabel } from '@/lib/auth'
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
const [publicInfo, setPublicInfo] = useState<{ message: string } | null>(null)
|
|
||||||
const user = getStoredUser()
|
const user = getStoredUser()
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
api.publicInfo().then(setPublicInfo).catch(() => null)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -19,7 +12,7 @@ export function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-bold text-foreground">看板</h1>
|
<h1 className="text-xl font-bold text-foreground">看板</h1>
|
||||||
<p className="text-sm text-secondary">欢迎回来,{user?.username || '游客'}</p>
|
<p className="text-sm text-secondary">欢迎回来,{user?.username || '—'}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -27,14 +20,8 @@ export function Dashboard() {
|
|||||||
<Card
|
<Card
|
||||||
icon={<ShieldCheck className="h-5 w-5 text-accent" />}
|
icon={<ShieldCheck className="h-5 w-5 text-accent" />}
|
||||||
title="当前身份"
|
title="当前身份"
|
||||||
value={user ? roleLabel(user.role) : '游客'}
|
value={user ? roleLabel(user.role) : '—'}
|
||||||
desc={user ? `用户:${user.username}` : '未登录,仅可查看公开信息'}
|
desc={user ? `用户:${user.username}` : '加载中…'}
|
||||||
/>
|
|
||||||
<Card
|
|
||||||
icon={<Globe className="h-5 w-5 text-bear" />}
|
|
||||||
title="公开信息"
|
|
||||||
value="已接入"
|
|
||||||
desc={publicInfo?.message || '加载中…'}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -44,7 +31,6 @@ export function Dashboard() {
|
|||||||
<li><span className="text-accent font-medium">系统管理员</span>:可管理管理员、用户、系统配置</li>
|
<li><span className="text-accent font-medium">系统管理员</span>:可管理管理员、用户、系统配置</li>
|
||||||
<li><span className="text-accent font-medium">管理员</span>:可管理普通用户</li>
|
<li><span className="text-accent font-medium">管理员</span>:可管理普通用户</li>
|
||||||
<li><span className="text-accent font-medium">用户</span>:可访问业务功能、修改个人资料</li>
|
<li><span className="text-accent font-medium">用户</span>:可访问业务功能、修改个人资料</li>
|
||||||
<li><span className="text-accent font-medium">游客</span>:仅查看公开内容,不可操作</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ export const router = createBrowserRouter([
|
|||||||
{ path: '/login', element: <Login /> },
|
{ path: '/login', element: <Login /> },
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
element: <Layout />,
|
element: (
|
||||||
|
<AuthGuard requireAuth>
|
||||||
|
<Layout />
|
||||||
|
</AuthGuard>
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
{ index: true, element: <Dashboard /> },
|
{ index: true, element: <Dashboard /> },
|
||||||
{
|
{
|
||||||
@@ -23,11 +27,7 @@ export const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'profile',
|
path: 'profile',
|
||||||
element: (
|
element: <Profile />,
|
||||||
<AuthGuard requireAuth>
|
|
||||||
<Profile />
|
|
||||||
</AuthGuard>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user