Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions gmonad/middlewares/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ func JWT(permission string) gin.HandlerFunc {
return
}

perms, err := models.GetUserWithPermissions(claims.Uid)
userID := uint(claims.Uid)
perms, err := models.GetUserWithPermissions(userID)
if err != nil {
utils.ErrorResponse(c, http.StatusUnauthorized, "Unauthorized action", nil)
c.Abort()
Expand All @@ -57,7 +58,7 @@ func JWT(permission string) gin.HandlerFunc {
}
}

c.Set("uid", claims.Uid)
c.Set("uid", userID)
c.Set("permissions", claims.Permissions)
c.Next()
}
Expand Down
37 changes: 35 additions & 2 deletions gmonad/utils/jwt.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package utils

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"strconv"
"time"

"github.com/golang-jwt/jwt/v5"
Expand All @@ -11,9 +15,38 @@ import (
// JWT 密钥
var jwtSecret = viper.GetString("jwt.secret")

type UserID uint

func (u *UserID) UnmarshalJSON(data []byte) error {
var raw any
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.UseNumber()
if err := decoder.Decode(&raw); err != nil {
return err
}

switch value := raw.(type) {
case json.Number:
return u.setFromString(value.String())
case string:
return u.setFromString(value)
default:
return fmt.Errorf("invalid uid type: %T", value)
}
}

func (u *UserID) setFromString(value string) error {
parsed, err := strconv.ParseUint(value, 10, 0)
if err != nil {
return fmt.Errorf("invalid uid: %q", value)
}
*u = UserID(parsed)
return nil
}

// 结构体定义 JWT 负载
type Claims struct {
Uid uint `json:"uid"`
Uid UserID `json:"uid"`
Email string `json:"email"`
Avatar string `json:"avatar"`
Username string `json:"username"`
Expand All @@ -26,7 +59,7 @@ type Claims struct {
func GenerateToken(uid uint, email, avatar, username, github string, permissions []string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour * 7)
claims := Claims{
Uid: uid,
Uid: UserID(uid),
Email: email,
Avatar: avatar,
Username: username,
Expand Down
8 changes: 8 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
/* config options here */
async rewrites() {
return [
{
source: '/v1/:path*',
destination: 'https://gmonad.cc/api/v1/:path*',
},
];
},
typescript: {
ignoreBuildErrors: true, // 忽略 TypeScript 检查
},
Expand Down
30 changes: 22 additions & 8 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import Auth from './Auth';
import { useState, useEffect } from 'react';
// import { SiWechat, SiX } from 'react-icons/si';

// 官方资源导航配置:桌面下拉和移动端抽屉共用,新增入口时只维护这里即可。
const officialResourceLinks = [
{ key: 'blog', href: '/blogs', label: '博客', icon: '📝' },
{ key: 'newsletter', href: '/newsletter', label: '周报', icon: '📰' },
];

export default function Header() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mounted, setMounted] = useState(false);
Expand Down Expand Up @@ -79,7 +85,7 @@ export default function Header() {
<div className={styles.headerContent}>
<Link href="/" passHref>
<div className={styles.logoInfo} style={{ cursor: 'pointer' }}>
<Image preview={false} width={30} src="/logo.png" className={styles.logo} />
<Image preview={false} width={30} src="/logo.png" alt="Monad 中文社区 Logo" className={styles.logo} />
<span className={styles.logoTitle}>Monad 中文社区</span>
</div>
</Link>
Expand Down Expand Up @@ -138,9 +144,10 @@ export default function Header() {
</Dropdown>
<Dropdown
menu={{
items: [
{ key: 'blog', label: <Link href="/blogs">博客</Link> },
],
items: officialResourceLinks.map(({ key, href, label }) => ({
key,
label: <Link href={href}>{label}</Link>,
})),
}}
placement="bottom"
trigger={['hover']}
Expand Down Expand Up @@ -264,10 +271,17 @@ export default function Header() {
<div className={styles.mobileMenuSection}>
<h3 className={styles.mobileMenuSectionTitle}>官方资源</h3>
<div className={styles.mobileMenuLinks}>
<Link href="/blogs" className={styles.mobileMenuLink} onClick={() => setMobileMenuOpen(false)}>
<span>📝</span>
<span>博客</span>
</Link>
{officialResourceLinks.map(({ key, href, label, icon }) => (
<Link
key={key}
href={href}
className={styles.mobileMenuLink}
onClick={() => setMobileMenuOpen(false)}
>
<span>{icon}</span>
<span>{label}</span>
</Link>
))}
</div>
</div>
</div>
Expand Down
Loading
Loading