Add user registration with jingrow cloud integration
This commit is contained in:
parent
03d6b988e2
commit
55a9024a1a
@ -10,6 +10,12 @@ const router = createRouter({
|
||||
component: () => import('../../views/auth/Login.vue'),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: '/signup',
|
||||
name: 'Signup',
|
||||
component: () => import('../../views/auth/Signup.vue'),
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
name: 'AppLayout',
|
||||
@ -250,7 +256,7 @@ router.beforeEach(async (to, _from, next) => {
|
||||
|
||||
if (to.meta.requiresAuth && !authStore.isLoggedIn) {
|
||||
next('/login')
|
||||
} else if (to.path === '/login' && authStore.isLoggedIn) {
|
||||
} else if ((to.path === '/login' || to.path === '/signup') && authStore.isLoggedIn) {
|
||||
next('/')
|
||||
} else {
|
||||
next()
|
||||
|
||||
@ -28,13 +28,29 @@
|
||||
"Username": "用户名",
|
||||
"Password": "密码",
|
||||
"Login": "登录",
|
||||
"Sign up": "注册",
|
||||
"Connect to Jingrow SaaS platform": "连接到 Jingrow SaaS 平台",
|
||||
"Please enter username": "请输入用户名",
|
||||
"Please enter password": "请输入密码",
|
||||
"Password must be at least 6 characters": "密码长度不能少于6位",
|
||||
"Username must be at least 3 characters": "用户名至少需要3个字符",
|
||||
"Login successful": "登录成功",
|
||||
"Login failed": "登录失败",
|
||||
"Login failed, please check username and password": "登录失败,请检查用户名和密码",
|
||||
"Create your account": "创建您的账户",
|
||||
"Confirm Password": "确认密码",
|
||||
"Please confirm password": "请确认密码",
|
||||
"Passwords do not match": "两次输入的密码不一致",
|
||||
"Email (Optional)": "邮箱(可选)",
|
||||
"Phone Number": "手机号",
|
||||
"Phone Number (Optional)": "手机号(可选)",
|
||||
"Please enter a valid email address": "请输入有效的邮箱地址",
|
||||
"Please enter phone number": "请输入手机号",
|
||||
"Please enter a valid phone number": "请输入有效的手机号码",
|
||||
"Sign up successful": "注册成功",
|
||||
"Sign up failed": "注册失败",
|
||||
"Sign up failed, please try again": "注册失败,请重试",
|
||||
"Already have an account?": "已有账户?",
|
||||
"System and personal settings": "系统配置和个人设置",
|
||||
"Personal Settings": "个人设置",
|
||||
"Email": "邮箱",
|
||||
|
||||
@ -110,6 +110,110 @@ export const logoutApi = async (): Promise<void> => {
|
||||
}
|
||||
}
|
||||
|
||||
// 注册接口
|
||||
export interface SignupRequest {
|
||||
username: string
|
||||
password: string
|
||||
email?: string
|
||||
phone_number?: string
|
||||
}
|
||||
|
||||
export interface SignupResponse {
|
||||
success: boolean
|
||||
message?: string
|
||||
error?: string
|
||||
user?: UserInfo
|
||||
}
|
||||
|
||||
export const signupApi = async (data: SignupRequest): Promise<SignupResponse> => {
|
||||
try {
|
||||
const response = await fetch(`/jingrow/signup`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(data)
|
||||
})
|
||||
|
||||
// 先克隆响应,以便可以多次读取(如果需要)
|
||||
const responseClone = response.clone()
|
||||
|
||||
let result: any = {}
|
||||
|
||||
// 尝试解析响应体
|
||||
try {
|
||||
result = await response.json()
|
||||
} catch (e) {
|
||||
// 如果JSON解析失败,尝试读取文本
|
||||
try {
|
||||
const text = await responseClone.text()
|
||||
return {
|
||||
success: false,
|
||||
error: text || `注册请求失败 (HTTP ${response.status})`
|
||||
}
|
||||
} catch (textError) {
|
||||
return {
|
||||
success: false,
|
||||
error: `注册请求失败 (HTTP ${response.status})`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:提取错误消息
|
||||
const extractErrorMessage = (data: any): string => {
|
||||
// 如果 detail 是对象,提取其中的 message
|
||||
if (data.detail && typeof data.detail === 'object') {
|
||||
return data.detail.message || data.detail.detail || JSON.stringify(data.detail)
|
||||
}
|
||||
// 如果 detail 是字符串,直接使用
|
||||
if (typeof data.detail === 'string') {
|
||||
return data.detail
|
||||
}
|
||||
// 尝试其他字段
|
||||
if (typeof data.message === 'string') {
|
||||
return data.message
|
||||
}
|
||||
if (typeof data.error === 'string') {
|
||||
return data.error
|
||||
}
|
||||
return '注册请求失败'
|
||||
}
|
||||
|
||||
// 如果响应不成功,提取错误消息
|
||||
if (!response.ok) {
|
||||
// FastAPI HTTPException 返回格式: {"detail": "错误消息"} 或 {"detail": {message: "错误消息"}}
|
||||
const errorMsg = extractErrorMessage(result) || `注册请求失败 (HTTP ${response.status})`
|
||||
console.error('注册API错误:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
result: result,
|
||||
errorMsg: errorMsg
|
||||
})
|
||||
return { success: false, error: errorMsg }
|
||||
}
|
||||
|
||||
// 检查业务逻辑错误(success 为 false)
|
||||
if (!result.success) {
|
||||
const errorMsg = extractErrorMessage(result) || '注册失败'
|
||||
return { success: false, error: errorMsg }
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: result.message || '注册成功',
|
||||
user: result.user || undefined
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 处理网络错误或其他异常
|
||||
return {
|
||||
success: false,
|
||||
error: error.message || '网络错误,请检查网络连接后重试'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 仅使用会话Cookie的最小鉴权头部(不影响现有API Key逻辑)
|
||||
export function get_session_api_headers() {
|
||||
return {
|
||||
|
||||
@ -12,7 +12,8 @@
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
size="large"
|
||||
size="medium"
|
||||
:show-label="false"
|
||||
@keyup.enter="handleLogin"
|
||||
>
|
||||
<n-form-item path="username">
|
||||
@ -48,14 +49,18 @@
|
||||
block
|
||||
:loading="loading"
|
||||
@click="handleLogin"
|
||||
class="brand-button"
|
||||
>
|
||||
{{ t('Login') }}
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<div class="login-footer">
|
||||
<div class="login-footer" v-if="showSignupLink">
|
||||
<n-text depth="3">
|
||||
<router-link to="/signup" class="signup-link">
|
||||
{{ t('Sign up') }}
|
||||
</router-link>
|
||||
</n-text>
|
||||
</div>
|
||||
</div>
|
||||
@ -76,6 +81,7 @@ const authStore = useAuthStore()
|
||||
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
const showSignupLink = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
username: '',
|
||||
@ -123,6 +129,20 @@ onMounted(async () => {
|
||||
// 如果已经登录,直接跳转
|
||||
if (authStore.isLoggedIn) {
|
||||
router.push('/')
|
||||
return
|
||||
}
|
||||
|
||||
// 检查服务器配置,判断是否显示注册链接
|
||||
try {
|
||||
const response = await fetch('/jingrow/server-config')
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
if (data.success && data.jingrow_server_url === 'https://cloud.jingrow.com') {
|
||||
showSignupLink.value = true
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get server config:', error)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -139,7 +159,7 @@ onMounted(async () => {
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
max-width: 480px;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 40px;
|
||||
@ -148,7 +168,26 @@ onMounted(async () => {
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 32px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
:deep(.n-form-item) {
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-form-item:last-child) {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-form-item:not(.n-form-item--error) .n-form-item__feedback-wrapper) {
|
||||
min-height: 0 !important;
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-form-item--error .n-form-item__feedback-wrapper) {
|
||||
margin-top: 4px !important;
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
.logo {
|
||||
@ -172,4 +211,31 @@ onMounted(async () => {
|
||||
text-align: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.signup-link {
|
||||
color: #18a058;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.signup-link:hover {
|
||||
color: #36ad6a;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:deep(.brand-button) {
|
||||
background-color: #18a058 !important;
|
||||
border-color: #18a058 !important;
|
||||
}
|
||||
|
||||
:deep(.brand-button:hover) {
|
||||
background-color: #36ad6a !important;
|
||||
border-color: #36ad6a !important;
|
||||
}
|
||||
|
||||
:deep(.brand-button:focus) {
|
||||
background-color: #18a058 !important;
|
||||
border-color: #18a058 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
337
apps/jingrow/frontend/src/views/auth/Signup.vue
Normal file
337
apps/jingrow/frontend/src/views/auth/Signup.vue
Normal file
@ -0,0 +1,337 @@
|
||||
<template>
|
||||
<div class="signup-container">
|
||||
<div class="signup-card">
|
||||
<div class="signup-header">
|
||||
<div class="logo">
|
||||
<img src="/logo.svg" :alt="appName" width="48" height="48" />
|
||||
</div>
|
||||
<h1 class="title">{{ appName }}</h1>
|
||||
</div>
|
||||
|
||||
<n-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
size="medium"
|
||||
:show-label="false"
|
||||
@keyup.enter="handleSignup"
|
||||
>
|
||||
<n-form-item path="username">
|
||||
<n-input
|
||||
v-model:value="formData.username"
|
||||
:placeholder="t('Username')"
|
||||
:input-props="{ autocomplete: 'username' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="tabler:user" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="password">
|
||||
<n-input
|
||||
v-model:value="formData.password"
|
||||
type="password"
|
||||
:placeholder="t('Password')"
|
||||
:input-props="{ autocomplete: 'new-password' }"
|
||||
show-password-on="click"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="tabler:lock" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="confirmPassword">
|
||||
<n-input
|
||||
v-model:value="formData.confirmPassword"
|
||||
type="password"
|
||||
:placeholder="t('Confirm Password')"
|
||||
:input-props="{ autocomplete: 'new-password' }"
|
||||
show-password-on="click"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="tabler:lock" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="email">
|
||||
<n-input
|
||||
v-model:value="formData.email"
|
||||
:placeholder="t('Email (Optional)')"
|
||||
:input-props="{ autocomplete: 'email', type: 'email' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="tabler:mail" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item path="phoneNumber">
|
||||
<n-input
|
||||
v-model:value="formData.phoneNumber"
|
||||
:placeholder="t('Phone Number')"
|
||||
:input-props="{ autocomplete: 'tel' }"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon icon="tabler:phone" />
|
||||
</template>
|
||||
</n-input>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item>
|
||||
<n-button
|
||||
type="primary"
|
||||
size="medium"
|
||||
block
|
||||
:loading="loading"
|
||||
@click="handleSignup"
|
||||
class="brand-button"
|
||||
>
|
||||
{{ t('Sign up') }}
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
|
||||
<div class="signup-footer">
|
||||
<n-text depth="3">
|
||||
{{ t('Already have an account?') }}
|
||||
<router-link to="/login" class="login-link">
|
||||
{{ t('Login') }}
|
||||
</router-link>
|
||||
</n-text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NForm, NFormItem, NInput, NButton, NText, useMessage } from 'naive-ui'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useAuthStore } from '../../shared/stores/auth'
|
||||
import { t } from '../../shared/i18n'
|
||||
import { signupApi } from '../../shared/api/auth'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const formRef = ref()
|
||||
const loading = ref(false)
|
||||
|
||||
const formData = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
email: '',
|
||||
phoneNumber: ''
|
||||
})
|
||||
|
||||
const validatePasswordMatch = (_rule: any, value: string) => {
|
||||
if (value !== formData.password) {
|
||||
return new Error(t('Passwords do not match'))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const rules = {
|
||||
username: [
|
||||
{ required: true, message: t('Please enter username'), trigger: 'blur' },
|
||||
{ min: 3, message: t('Username must be at least 3 characters'), trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: t('Please enter password'), trigger: 'blur' },
|
||||
{ min: 6, message: t('Password must be at least 6 characters'), trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: t('Please confirm password'), trigger: 'blur' },
|
||||
{ validator: validatePasswordMatch, trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{
|
||||
validator: (_rule: any, value: string) => {
|
||||
if (!value) return true
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(value)) {
|
||||
return new Error(t('Please enter a valid email address'))
|
||||
}
|
||||
return true
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
phoneNumber: [
|
||||
{ required: true, message: t('Please enter phone number'), trigger: 'blur' },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: t('Please enter a valid phone number'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const handleSignup = async () => {
|
||||
try {
|
||||
await formRef.value?.validate()
|
||||
loading.value = true
|
||||
|
||||
const result = await signupApi({
|
||||
username: formData.username,
|
||||
password: formData.password,
|
||||
email: formData.email || undefined,
|
||||
phone_number: formData.phoneNumber
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
message.success(t('Sign up successful'))
|
||||
// 注册成功后,后端已经设置了 cookie 并返回了 user 信息
|
||||
// 如果有 user 信息,直接使用;否则尝试调用登录 API
|
||||
if (result.user) {
|
||||
authStore.user = result.user
|
||||
authStore.isAuthenticated = true
|
||||
localStorage.setItem('jingrow_user', JSON.stringify(result.user))
|
||||
localStorage.setItem('jingrow_authenticated', 'true')
|
||||
router.push('/')
|
||||
} else {
|
||||
// 如果没有返回 user 信息,尝试调用登录 API
|
||||
const loginResult = await authStore.login(formData.username, formData.password)
|
||||
if (loginResult.success) {
|
||||
router.push('/')
|
||||
} else {
|
||||
message.warning(loginResult.error || t('注册成功,但自动登录失败,请手动登录'))
|
||||
router.push('/login')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const errorMsg = result.error || t('Sign up failed')
|
||||
console.error('注册失败:', errorMsg, result)
|
||||
message.error(errorMsg)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('注册异常:', error)
|
||||
message.error(error.message || t('Sign up failed, please try again'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 从 localStorage 读取应用名称,默认 Jingrow,与侧边栏保持一致
|
||||
const appName = computed(() => localStorage.getItem('appName') || 'Jingrow')
|
||||
|
||||
onMounted(async () => {
|
||||
// 初始化认证状态
|
||||
await authStore.initAuth()
|
||||
|
||||
// 如果已经登录,直接跳转
|
||||
if (authStore.isLoggedIn) {
|
||||
router.push('/')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.signup-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.signup-card {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
max-height: calc(100vh - 40px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.signup-header {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.n-form-item) {
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
|
||||
:deep(.n-form-item:last-child) {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
/* 只在有错误时才显示反馈区域,减少默认间距 */
|
||||
:deep(.n-form-item:not(.n-form-item--error) .n-form-item__feedback-wrapper) {
|
||||
min-height: 0 !important;
|
||||
margin-top: 0 !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-form-item--error .n-form-item__feedback-wrapper) {
|
||||
margin-top: 4px !important;
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.logo img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1f2937;
|
||||
margin: 0 0 6px 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.signup-footer {
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.login-link {
|
||||
color: #18a058;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
margin-left: 4px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.login-link:hover {
|
||||
color: #36ad6a;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
:deep(.brand-button) {
|
||||
background-color: #18a058 !important;
|
||||
border-color: #18a058 !important;
|
||||
}
|
||||
|
||||
:deep(.brand-button:hover) {
|
||||
background-color: #36ad6a !important;
|
||||
border-color: #36ad6a !important;
|
||||
}
|
||||
|
||||
:deep(.brand-button:focus) {
|
||||
background-color: #18a058 !important;
|
||||
border-color: #18a058 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -4,7 +4,8 @@ from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
import logging
|
||||
|
||||
from jingrow.utils.auth import login, logout, get_user_info, set_context
|
||||
from jingrow.utils.auth import login, logout, get_user_info, set_context, get_jingrow_cloud_url, get_jingrow_cloud_api_headers
|
||||
from jingrow.config import Config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
@ -13,6 +14,18 @@ class LoginRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
class SignupRequest(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
email: Optional[str] = None
|
||||
phone_number: Optional[str] = None
|
||||
|
||||
class SignupResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
user: Optional[dict] = None
|
||||
session_cookie: Optional[str] = None
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
@ -111,6 +124,112 @@ async def logout_route(session_cookie: Optional[str] = Depends(get_session_cooki
|
||||
logger.error(f"登出异常: {str(e)}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"登出异常: {str(e)}")
|
||||
|
||||
@router.post("/jingrow/signup", response_model=SignupResponse)
|
||||
async def signup_route(signup_data: SignupRequest):
|
||||
"""注册路由"""
|
||||
import requests
|
||||
|
||||
try:
|
||||
# 调用 jcloud signup_with_username API
|
||||
url = f"{get_jingrow_cloud_url()}/api/action/jcloud.api.account.signup_with_username"
|
||||
data = {"username": signup_data.username, "password": signup_data.password}
|
||||
if signup_data.email:
|
||||
data["email"] = signup_data.email
|
||||
if signup_data.phone_number:
|
||||
data["phone_number"] = signup_data.phone_number
|
||||
|
||||
response = requests.post(url, headers=get_jingrow_cloud_api_headers(), json=data, timeout=30)
|
||||
|
||||
# 打印完整的响应信息用于调试
|
||||
logger.info(f"=== 注册API调用详情 ===")
|
||||
logger.info(f"URL: {url}")
|
||||
logger.info(f"请求数据: {data}")
|
||||
logger.info(f"HTTP状态码: {response.status_code}")
|
||||
logger.info(f"响应头: {dict(response.headers)}")
|
||||
logger.info(f"响应文本: {response.text}")
|
||||
|
||||
# 解析响应
|
||||
if response.status_code != 200:
|
||||
error_text = response.text[:500] if response.text else ""
|
||||
logger.error(f"注册请求失败: HTTP {response.status_code}, URL: {url}, 响应: {error_text}")
|
||||
raise HTTPException(status_code=400, detail=f"注册请求失败 (HTTP {response.status_code})")
|
||||
|
||||
try:
|
||||
result = response.json()
|
||||
logger.info(f"解析后的JSON结果: {result}")
|
||||
except Exception as e:
|
||||
error_text = response.text[:500] if response.text else ""
|
||||
logger.error(f"注册失败:无法解析服务器响应: {str(e)}, 响应文本: {error_text}")
|
||||
raise HTTPException(status_code=400, detail="注册失败:无法解析服务器响应")
|
||||
|
||||
# jcloud API 返回格式:{"message": {"success": bool, "message": str}, ...}
|
||||
# 需要从 message 字段中提取 success 和 message
|
||||
message_obj = result.get("message")
|
||||
if isinstance(message_obj, dict):
|
||||
# message 是对象,从中提取 success 和 message
|
||||
success_value = message_obj.get("success")
|
||||
success_message = message_obj.get("message", "注册成功")
|
||||
else:
|
||||
# message 是字符串,检查顶层的 success 字段
|
||||
success_value = result.get("success")
|
||||
success_message = result.get("message", message_obj if isinstance(message_obj, str) else "注册成功")
|
||||
|
||||
# 检查注册结果
|
||||
if success_value is False:
|
||||
logger.error(f"注册失败: {success_message}, 完整响应: {result}")
|
||||
raise HTTPException(status_code=400, detail=success_message)
|
||||
|
||||
# 注册成功
|
||||
logger.info(f"注册成功: {success_message}")
|
||||
|
||||
# 注册成功后,用户已在 jcloud 端登录,需要在 jingrow 端也建立会话
|
||||
# 注意:用户刚注册可能还未完全同步,如果登录失败,返回成功但让前端处理
|
||||
session_cookie = None
|
||||
user_info = None
|
||||
|
||||
try:
|
||||
login_result = login(signup_data.username, signup_data.password)
|
||||
if login_result.get("success"):
|
||||
session_cookie = login_result.get("session_cookie")
|
||||
# 获取用户信息
|
||||
if session_cookie:
|
||||
user_info_result = get_user_info(session_cookie)
|
||||
if user_info_result.get("success"):
|
||||
user_info = user_info_result.get("user_info")
|
||||
else:
|
||||
# 登录失败,记录日志但不抛出异常(注册本身是成功的)
|
||||
logger.warning(f"注册成功但获取会话失败: {login_result.get('message', '未知错误')}")
|
||||
except Exception as e:
|
||||
# 登录异常,记录日志但不抛出异常(注册本身是成功的)
|
||||
logger.warning(f"注册成功但获取会话时发生异常: {str(e)}")
|
||||
|
||||
# 返回成功响应
|
||||
# 如果有 session cookie,设置 cookie;如果没有,前端会尝试调用登录 API
|
||||
response_data = {
|
||||
"success": True,
|
||||
"message": success_message,
|
||||
"user": user_info
|
||||
}
|
||||
|
||||
if session_cookie:
|
||||
return create_response_with_cookie(response_data, session_cookie)
|
||||
else:
|
||||
# 如果没有 session cookie,返回普通响应(不设置 cookie)
|
||||
# 前端会处理这种情况,尝试调用登录 API
|
||||
return JSONResponse(content=response_data)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"注册异常: {str(e)}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=f"注册异常: {str(e)}")
|
||||
|
||||
@router.get("/jingrow/server-config")
|
||||
async def get_server_config():
|
||||
return {
|
||||
"success": True,
|
||||
"jingrow_server_url": Config.jingrow_server_url
|
||||
}
|
||||
|
||||
@router.get("/jingrow/user-info", response_model=UserInfoResponse)
|
||||
async def get_user_info_route(session_cookie: Optional[str] = Depends(get_session_cookie)):
|
||||
"""获取用户信息路由"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user