mirror of
https://github.com/woodchen-ink/Q58Connect.git
synced 2025-07-18 05:51:55 +08:00
feat: 添加代码块组件和警告组件,优化用户界面
This commit is contained in:
parent
ce3baad450
commit
54b3d8e661
50
README.md
50
README.md
@ -63,55 +63,7 @@ Q58论坛网址: https://q58.club
|
|||||||
|
|
||||||
## 用户应用接入本系统oauth2.0认证的方式:
|
## 用户应用接入本系统oauth2.0认证的方式:
|
||||||
|
|
||||||
1. 发起授权请求
|
写在"src\app\()\page.tsx"里了.
|
||||||
将用户重定向到授权页面
|
|
||||||
|
|
||||||
const authUrl = 'https://connect.q58.club/oauth/authorize?' +
|
|
||||||
new URLSearchParams({
|
|
||||||
response_type: 'code', // 必填,固定值
|
|
||||||
client_id: 'your_client_id', // 必填,您的应用ID
|
|
||||||
redirect_uri: 'https://your-app.com/callback',
|
|
||||||
state: 'random_state', // 建议提供,防CSRF攻击
|
|
||||||
scope: 'read_profile' // 可选,默认read_profile
|
|
||||||
});
|
|
||||||
|
|
||||||
window.location.href = authUrl;
|
|
||||||
|
|
||||||
2. 处理授权回调
|
|
||||||
在回调地址处理授权结果
|
|
||||||
|
|
||||||
// 获取访问令牌
|
|
||||||
const response = await fetch('https://connect.q58.club/api/oauth/access_token', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
code: '授权码', // 回调参数中的code
|
|
||||||
redirect_uri: 'https://your-app.com/callback'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const { access_token, expires_in } = await response.json();
|
|
||||||
|
|
||||||
3. 获取用户信息
|
|
||||||
使用访问令牌获取用户数据
|
|
||||||
|
|
||||||
const userInfo = await fetch('https://connect.q58.club/api/oauth/user', {
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${access_token}`
|
|
||||||
}
|
|
||||||
}).then(res => res.json());
|
|
||||||
|
|
||||||
// 返回数据示例:
|
|
||||||
{
|
|
||||||
"id": "user_xxx",
|
|
||||||
"email": "user@example.com",
|
|
||||||
"username": "username",
|
|
||||||
"name": "用户昵称",
|
|
||||||
"avatar_url": "https://...",
|
|
||||||
"groups": ["group1", "group2"]
|
|
||||||
}
|
|
||||||
|
|
||||||
## 添加新功能的准则
|
## 添加新功能的准则
|
||||||
|
|
||||||
|
75
oauth2.0的授权模式.txt
Normal file
75
oauth2.0的授权模式.txt
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
授权码模式(Authorization Code)
|
||||||
|
第一步:获取授权码
|
||||||
|
|
||||||
|
授权码请求链接格式:
|
||||||
|
|
||||||
|
http://localhost:8080/oauth/authorize?client_id=123456&response_type=code&scope=all&redirect_url=http://localhost:8080/oauth/token
|
||||||
|
返回的JSON格式:
|
||||||
|
|
||||||
|
{
|
||||||
|
"code": "123456",
|
||||||
|
"state": "123456"
|
||||||
|
}
|
||||||
|
第二步:申请令牌
|
||||||
|
|
||||||
|
令牌请求链接格式:
|
||||||
|
|
||||||
|
http://localhost:8080/oauth/token?client_id=123456&client_secret=123456&grant_type=authorization_code&code=123456&redirect_url=http://localhost:8080/oauth/callback
|
||||||
|
返回的JSON格式:
|
||||||
|
|
||||||
|
{
|
||||||
|
"access_token": "123456",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"scope": "read",
|
||||||
|
"refresh_token": "123456"
|
||||||
|
}
|
||||||
|
资源请求链接格式:
|
||||||
|
|
||||||
|
http://localhost:8080/oauth/resource?access_token=123456
|
||||||
|
返回的JSON格式:
|
||||||
|
|
||||||
|
{
|
||||||
|
"resource": "123456"
|
||||||
|
}
|
||||||
|
简化模式(Implicit)
|
||||||
|
简化模式跳过授权码,直接获取访问令牌,适用于没有后台服务程序的单页面应用。
|
||||||
|
|
||||||
|
令牌请求链接格式:
|
||||||
|
|
||||||
|
http://localhost:8080/oauth/token?client_id=123456&client_secret=123456&response_type=token&scope=all&redirect_url=http://localhost:8080/oauth/callback
|
||||||
|
返回的JSON格式:
|
||||||
|
|
||||||
|
{
|
||||||
|
"access_token": "123456",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"scope": "read",
|
||||||
|
"refresh_token": "123456"
|
||||||
|
}
|
||||||
|
密码模式(Password)
|
||||||
|
用户通过客户端使用用户名和密码向授权服务器请求授权,授权服务器向客户端发送访问令牌和更新令牌。
|
||||||
|
|
||||||
|
请求链接格式:
|
||||||
|
|
||||||
|
http://localhost:8080/oauth/token?client_id=123456&client_secret=123456&grant_type=password&username=admin&password=admin
|
||||||
|
返回的JSON格式:
|
||||||
|
|
||||||
|
{
|
||||||
|
"access_token": "123456",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"scope": "read",
|
||||||
|
"refresh_token": "123456"
|
||||||
|
}
|
||||||
|
客户端模式(Client Credentials)
|
||||||
|
客户端以自己的名义使用客户端ID和密钥向授权服务器请求授权,最简单的授权模式。
|
||||||
|
|
||||||
|
请求链接格式:
|
||||||
|
|
||||||
|
http://localhost:8080/oauth/token?client_id=123456&client_secret=123456&grant_type=client_credentials
|
||||||
|
返回的JSON格式:
|
||||||
|
|
||||||
|
{
|
||||||
|
"access_token": "123456",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"scope": "read",
|
||||||
|
"refresh_token": "123456"
|
||||||
|
}
|
@ -1,6 +1,15 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ArrowRight, CheckCircle2, Code2, Lock, Users } from "lucide-react";
|
import {
|
||||||
|
ArrowDownToLine,
|
||||||
|
ArrowRight,
|
||||||
|
CheckCircle2,
|
||||||
|
Code2,
|
||||||
|
Lock,
|
||||||
|
Send,
|
||||||
|
Users,
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -9,15 +18,10 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
|
import { CodeBlock } from "@/components/ui/code-block";
|
||||||
import { Container } from "@/components/ui/container";
|
import { Container } from "@/components/ui/container";
|
||||||
import { Section } from "@/components/ui/section";
|
import { Section } from "@/components/ui/section";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import {
|
|
||||||
TypographyH1,
|
|
||||||
TypographyH2,
|
|
||||||
TypographyLead,
|
|
||||||
TypographyMuted,
|
|
||||||
} from "@/components/ui/typography";
|
|
||||||
import { Header } from "@/components/layout/header";
|
import { Header } from "@/components/layout/header";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
@ -75,11 +79,11 @@ export default function HomePage() {
|
|||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
||||||
<span>完整的开发文档</span>
|
<span>完整的接入文档</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
||||||
<span>示例代码和SDK</span>
|
<span>示例代码</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -105,7 +109,7 @@ export default function HomePage() {
|
|||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
||||||
<span>防CSRF攻击</span>
|
<span>Serverless部署 高SLA</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -116,22 +120,22 @@ export default function HomePage() {
|
|||||||
<Users className="h-10 w-10 text-primary" />
|
<Users className="h-10 w-10 text-primary" />
|
||||||
<CardTitle className="text-xl">功能丰富</CardTitle>
|
<CardTitle className="text-xl">功能丰富</CardTitle>
|
||||||
<CardDescription className="text-base">
|
<CardDescription className="text-base">
|
||||||
提供完整的用户信息和权限管理功能
|
提供完整的应用管理, 仪表盘, API用户信息和权限管理功能
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<ul className="space-y-4 text-sm">
|
<ul className="space-y-4 text-sm">
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
||||||
<span>用户基本信息</span>
|
<span>API 返回信息完善</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
||||||
<span>用户组权限</span>
|
<span>仪表盘和管理美观界面</span>
|
||||||
</li>
|
</li>
|
||||||
<li className="flex items-center gap-2">
|
<li className="flex items-center gap-2">
|
||||||
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
<CheckCircle2 className="h-5 w-5 text-green-500" />
|
||||||
<span>管理员特权</span>
|
<span>限制应用可用者 接入个人应用</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@ -146,104 +150,223 @@ export default function HomePage() {
|
|||||||
<div className="mx-auto max-w-4xl">
|
<div className="mx-auto max-w-4xl">
|
||||||
<Card className="overflow-hidden border-2 p-6">
|
<Card className="overflow-hidden border-2 p-6">
|
||||||
<CardHeader className="bg-white dark:bg-gray-900">
|
<CardHeader className="bg-white dark:bg-gray-900">
|
||||||
<CardTitle className="text-2xl">快速开始</CardTitle>
|
<CardTitle className="text-2xl">
|
||||||
|
接入应用 (授权码模式)
|
||||||
|
</CardTitle>
|
||||||
<CardDescription className="text-base">
|
<CardDescription className="text-base">
|
||||||
按照以下步骤,快速接入 Q58 Connect
|
以下示例使用授权码(Authorization
|
||||||
|
Code)模式,这是最安全和最完整的OAuth 2.0授权模式,
|
||||||
|
适用于有后端服务器的应用。
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="bg-white p-0 dark:bg-gray-900">
|
<CardContent className="bg-white p-0 dark:bg-gray-900">
|
||||||
<Tabs defaultValue="auth" className="w-full">
|
<Tabs defaultValue="auth" className="w-full">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="auth">发起授权</TabsTrigger>
|
<TabsTrigger value="auth">
|
||||||
<TabsTrigger value="callback">处理回调</TabsTrigger>
|
<span className="flex h-5 w-6 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||||
<TabsTrigger value="userinfo">获取用户</TabsTrigger>
|
1
|
||||||
|
</span>
|
||||||
|
发起授权
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="callback">
|
||||||
|
<span className="flex h-5 w-6 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||||
|
2
|
||||||
|
</span>
|
||||||
|
处理回调
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="userinfo">
|
||||||
|
<span className="flex h-5 w-6 items-center justify-center rounded-full bg-primary/10 text-primary">
|
||||||
|
3
|
||||||
|
</span>
|
||||||
|
获取用户
|
||||||
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<TabsContent value="auth" className="mt-4">
|
<div className="relative mt-6">
|
||||||
<Card>
|
{/* <div className="absolute left-[3rem] top-0 h-full w-px bg-border" /> */}
|
||||||
<CardHeader>
|
<TabsContent value="auth" className="mt-4 space-y-4">
|
||||||
<CardTitle>1. 发起授权请求</CardTitle>
|
<Card className="border-2 shadow-sm transition-all hover:border-primary/50">
|
||||||
<CardDescription>
|
<CardHeader>
|
||||||
将用户重定向到授权页面
|
<div className="flex items-center gap-4">
|
||||||
</CardDescription>
|
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-white">
|
||||||
</CardHeader>
|
1
|
||||||
<CardContent>
|
</span>
|
||||||
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
|
<div>
|
||||||
<code className="text-sm">
|
<CardTitle>发起OAuth授权请求</CardTitle>
|
||||||
{`const authUrl = 'https://connect.q58.club/oauth/authorize?' +
|
<CardDescription>
|
||||||
|
构建授权URL并重定向用户到授权页面
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<Alert className="border-l-4 border-l-primary">
|
||||||
|
<Code2 className="h-4 w-4" />
|
||||||
|
<AlertTitle>请求地址</AlertTitle>
|
||||||
|
<AlertDescription className="font-mono">
|
||||||
|
GET https://connect.q58.club/oauth/authorize
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-2 font-medium">请求参数</h4>
|
||||||
|
<CodeBlock>
|
||||||
|
{`const authUrl = 'https://connect.q58.club/oauth/authorize?' +
|
||||||
new URLSearchParams({
|
new URLSearchParams({
|
||||||
response_type: 'code', // 必填,固定值
|
response_type: 'code', // 必填,固定值为"code"
|
||||||
client_id: 'your_client_id', // 必填,您的应用ID
|
client_id: 'your_client_id', // 必填,您的应用ID
|
||||||
redirect_uri: 'https://your-app.com/callback',
|
redirect_uri: 'https://your-app.com/callback', // 必填,回调地址
|
||||||
state: 'random_state', // 建议提供,防CSRF攻击
|
scope: 'read_profile' // 可选,权限范围,默认read_profile
|
||||||
scope: 'read_profile' // 可选,默认read_profile
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.location.href = authUrl;`}
|
window.location.href = authUrl;`}
|
||||||
</code>
|
</CodeBlock>
|
||||||
</pre>
|
</div>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
<Alert className="border-green-500">
|
||||||
</TabsContent>
|
<ArrowDownToLine className="h-4 w-4" />
|
||||||
<TabsContent value="callback" className="mt-4">
|
<AlertTitle>授权响应</AlertTitle>
|
||||||
<Card>
|
<AlertDescription>
|
||||||
<CardHeader>
|
<p className="mb-2">
|
||||||
<CardTitle>2. 处理授权回调</CardTitle>
|
用户授权后,将重定向到您的回调地址:
|
||||||
<CardDescription>
|
</p>
|
||||||
在回调地址处理授权结果
|
<pre className="mt-2 overflow-x-auto rounded bg-gray-100 p-4 dark:bg-gray-800">
|
||||||
</CardDescription>
|
<code className="text-sm">
|
||||||
</CardHeader>
|
https://your-app.com/callback?code=ac_xxxxxx...
|
||||||
<CardContent>
|
</code>
|
||||||
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
|
</pre>
|
||||||
<code className="text-sm">
|
<div className="mt-4 space-y-2">
|
||||||
{`// 获取访问令牌
|
<p className="font-medium">授权码说明:</p>
|
||||||
const response = await fetch('https://connect.q58.club/api/oauth/access_token', {
|
<ul className="list-inside list-disc space-y-1 text-sm text-muted-foreground">
|
||||||
|
<li>格式:ac_前缀 + 40位随机字符</li>
|
||||||
|
<li>有效期:10分钟</li>
|
||||||
|
<li>使用限制:仅可使用一次</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="callback" className="mt-4 space-y-4">
|
||||||
|
<Card className="border-2 shadow-sm transition-all hover:border-primary/50">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-white">
|
||||||
|
2
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<CardTitle>使用授权码交换访问令牌</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
在服务器端使用授权码换取访问令牌
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<Alert className="border-l-4 border-l-primary">
|
||||||
|
<Code2 className="h-4 w-4" />
|
||||||
|
<AlertTitle>请求地址</AlertTitle>
|
||||||
|
<AlertDescription className="font-mono">
|
||||||
|
POST
|
||||||
|
https://connect.q58.club/api/oauth/access_token
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-2 font-medium">请求示例</h4>
|
||||||
|
<CodeBlock>
|
||||||
|
{`const response = await fetch('https://connect.q58.club/api/oauth/access_token', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Authorization': 'Basic ' + btoa(client_id + ':' + client_secret)
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
code: '授权码', // 回调参数中的code
|
code: 'ac_xxxxx', // 必填,授权码
|
||||||
redirect_uri: 'https://your-app.com/callback'
|
redirect_uri: 'https://your-app.com/callback' // 必填,与请求授权码时相同
|
||||||
})
|
})
|
||||||
});
|
});`}
|
||||||
|
</CodeBlock>
|
||||||
|
</div>
|
||||||
|
|
||||||
const { access_token, expires_in } = await response.json();`}
|
<Alert className="border-green-500">
|
||||||
</code>
|
<ArrowDownToLine className="h-4 w-4" />
|
||||||
</pre>
|
<AlertTitle>响应数据</AlertTitle>
|
||||||
</CardContent>
|
<AlertDescription>
|
||||||
</Card>
|
<pre className="mt-2 overflow-x-auto rounded bg-gray-100 p-4 dark:bg-gray-800">
|
||||||
</TabsContent>
|
<code className="text-sm">
|
||||||
<TabsContent value="userinfo" className="mt-4">
|
{`{
|
||||||
<Card>
|
"access_token": "at_xxxxxxxx", // 访问令牌
|
||||||
<CardHeader>
|
"token_type": "bearer", // 令牌类型,固定为bearer
|
||||||
<CardTitle>3. 获取用户信息</CardTitle>
|
"expires_in": 604800 // 令牌有效期,单位秒(7天)
|
||||||
<CardDescription>
|
|
||||||
使用访问令牌获取用户数据
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
|
|
||||||
<code className="text-sm">
|
|
||||||
{`const userInfo = await fetch('https://connect.q58.club/api/oauth/user', {
|
|
||||||
headers: {
|
|
||||||
'Authorization': \`Bearer \${access_token}\`
|
|
||||||
}
|
|
||||||
}).then(res => res.json());
|
|
||||||
|
|
||||||
// 返回数据示例:
|
|
||||||
{
|
|
||||||
"id": "user_xxx",
|
|
||||||
"email": "user@example.com",
|
|
||||||
"username": "username",
|
|
||||||
"name": "用户昵称",
|
|
||||||
"avatar_url": "https://...",
|
|
||||||
"groups": ["group1", "group2"]
|
|
||||||
}`}
|
}`}
|
||||||
</code>
|
</code>
|
||||||
</pre>
|
</pre>
|
||||||
</CardContent>
|
</AlertDescription>
|
||||||
</Card>
|
</Alert>
|
||||||
</TabsContent>
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="userinfo" className="mt-4 space-y-4">
|
||||||
|
<Card className="border-2 shadow-sm transition-all hover:border-primary/50">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<span className="flex h-8 w-8 items-center justify-center rounded-full bg-primary text-white">
|
||||||
|
3
|
||||||
|
</span>
|
||||||
|
<div>
|
||||||
|
<CardTitle>获取用户信息</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
使用访问令牌获取已授权用户的详细信息
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<Alert className="border-l-4 border-l-primary">
|
||||||
|
<Code2 className="h-4 w-4" />
|
||||||
|
<AlertTitle>请求地址</AlertTitle>
|
||||||
|
<AlertDescription className="font-mono">
|
||||||
|
GET https://connect.q58.club/api/oauth/user
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="mb-2 font-medium">请求示例</h4>
|
||||||
|
<CodeBlock>
|
||||||
|
{`const userInfo = await fetch('https://connect.q58.club/api/oauth/user', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': \`Bearer \${access_token}\` // 使用获取到的访问令牌
|
||||||
|
}
|
||||||
|
}).then(res => res.json());`}
|
||||||
|
</CodeBlock>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Alert className="border-green-500">
|
||||||
|
<ArrowDownToLine className="h-4 w-4" />
|
||||||
|
<AlertTitle>响应数据</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
<pre className="mt-2 overflow-x-auto rounded bg-gray-100 p-4 dark:bg-gray-800">
|
||||||
|
<code className="text-sm">
|
||||||
|
{`{
|
||||||
|
"id": "user_xxx", // 用户唯一标识
|
||||||
|
"email": "user@example.com",// 邮箱地址
|
||||||
|
"username": "username", // 用户名
|
||||||
|
"name": "用户昵称", // 显示名称
|
||||||
|
"avatar_url": "https://...",// 头像URL
|
||||||
|
"admin": false, // 是否管理员
|
||||||
|
"groups": ["group1"], // 用户组
|
||||||
|
}`}
|
||||||
|
</code>
|
||||||
|
</pre>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</div>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import type { ExtendedAccessToken, ExtendedClient } from "@/types";
|
import type { ExtendedClient } from "@/types";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
|
||||||
import { getAuthorizationsByClientId } from "@/lib/dto/authorization";
|
import { getAuthorizationsByClientId } from "@/lib/dto/authorization";
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import type { Prisma } from "@prisma/client";
|
|
||||||
|
|
||||||
import { getClientsByUserId } from "@/lib/dto/client";
|
import { getClientsByUserId } from "@/lib/dto/client";
|
||||||
import { getCurrentUser } from "@/lib/session";
|
import { getCurrentUser } from "@/lib/session";
|
||||||
|
59
src/components/ui/alert.tsx
Normal file
59
src/components/ui/alert.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Alert.displayName = "Alert";
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertTitle.displayName = "AlertTitle";
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AlertDescription.displayName = "AlertDescription";
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription };
|
38
src/components/ui/code-block.tsx
Normal file
38
src/components/ui/code-block.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Check, Copy } from "lucide-react";
|
||||||
|
|
||||||
|
function CopyButton({ text }: { text: string }) {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const copy = () => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={copy}
|
||||||
|
className="absolute right-2 top-2 rounded-md p-2 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-4 w-4 text-green-500" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-4 w-4 text-gray-500" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CodeBlock({ children }: { children: string }) {
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<pre className="overflow-x-auto rounded-lg bg-gray-100 p-4 dark:bg-gray-800">
|
||||||
|
<code className="text-sm">{children}</code>
|
||||||
|
</pre>
|
||||||
|
<CopyButton text={children} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user