From 2153bd94071640a616e9602a8df684fc6585c26c Mon Sep 17 00:00:00 2001 From: zs <18031751980@163.com> Date: Fri, 9 Jan 2026 23:10:20 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 25 +++++++ .../controllers/auth_controller.py | 37 +++++++--- .../interfaces/service_interfaces.py | 19 +++++ .../fastapi_of_letcoing/models/db_models.py | 7 +- webapi/fastapi_of_letcoing/requirements.txt | 2 +- .../services/oidc_service.py | 14 +++- .../services/user_service.py | 73 ++++++++++++++++++- 7 files changed, 162 insertions(+), 15 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..365c4a3 --- /dev/null +++ b/.env @@ -0,0 +1,25 @@ +API_TOKEN=993cb7df-6ff5-40c3-9a9c-773921e3a12d +JWT_SECRET_KEY=MjM3NTg1NDM4ODQ0MTYxMTE3MzUxODIzMTA0ODY2NjQ= + +REDIS_HOST=hkg1.clusters.zeabur.com +REDIS_PORT=30123 +REDIS_DB=0 +REDIS_PASSWORD=2g8o1A45x9n3hsE0m7yrfL6VkbqJXaBW + +GITHUB_CLIENT_ID=Ov23liSEPDKQ0r51vqBC +GITHUB_CLIENT_SECRET=0ce1c2a2a19cb4562fb1b57e64de3c93877d656e + +OIDC_PROVIDERS=[ + { + "name": "iOSClub", + "issuer": "https://api.xauat.site", + "client_id": "ca_0200be03d08f4f58", + "client_secret": "$2b$10$AnmhmDe4lO27E.XQwHyWcOVOPiy/tBTymd4zMXt9i6l73vHlxkW1q" + } +] + +DB_HOST=hkg1.clusters.zeabur.com +DB_PORT=32627 +DB_NAME=lettest +DB_USER=root +DB_PASSWORD=60zquVE1c7TLInwOivZg9H543Kr2GM8p \ No newline at end of file diff --git a/webapi/fastapi_of_letcoing/controllers/auth_controller.py b/webapi/fastapi_of_letcoing/controllers/auth_controller.py index 99228da..c75d5d8 100644 --- a/webapi/fastapi_of_letcoing/controllers/auth_controller.py +++ b/webapi/fastapi_of_letcoing/controllers/auth_controller.py @@ -1,6 +1,6 @@ from flask import request, jsonify, url_for from flask_restx import Resource, Namespace, fields -from interfaces.service_interfaces import IConfigService, ILoggerService, IJWTService, IOIDCService +from interfaces.service_interfaces import IConfigService, ILoggerService, IJWTService, IOIDCService, IUserService from core.di_container import inject from models.auth_models import ( LoginRequest, LoginResponse, AuthCallbackRequest, @@ -88,10 +88,11 @@ class AuthCallbackController(Resource): @api.response(200, 'Success', auth_result_model) @api.response(400, 'Bad Request') def get(self, provider: str): - """处理认证回调""" + """处理认证回调,实现注册和登录集成""" # 注入服务 oidc_service = inject(IOIDCService) jwt_service = inject(IJWTService) + user_service = inject(IUserService) # 验证提供商 if not oidc_service.validate_provider(provider): @@ -125,14 +126,29 @@ class AuthCallbackController(Resource): if not user_info_data: return {'success': False, 'error': '获取用户信息失败'}, 500 + # 获取provider_id + provider_id = user_info_data.get('id') + if not provider_id: + return {'success': False, 'error': '无法获取提供商用户ID'}, 500 + + # 查找或创建用户(支持注册和登录集成) + try: + user_data = user_service.find_or_create_user( + provider=provider, + provider_id=provider_id, + user_info=user_info_data + ) + except Exception as e: + return {'success': False, 'error': f'用户处理失败: {str(e)}'}, 500 + # 创建用户信息对象 user_info = UserInfo( - id=user_info_data['id'], - username=user_info_data.get('username', ''), - email=user_info_data.get('email', ''), - name=user_info_data.get('name', ''), - avatar_url=user_info_data.get('avatar_url', ''), - provider=user_info_data.get('provider', provider) + id=user_data['id'], + username=user_data.get('username', ''), + email=user_data.get('email', ''), + name=user_data.get('name', ''), + avatar_url=user_data.get('avatar_url', ''), + provider=provider ) # 生成JWT令牌 @@ -150,7 +166,10 @@ class AuthCallbackController(Resource): return { 'success': True, 'user_info': user_info.to_dict(), - 'tokens': token_response.to_dict() + 'tokens': token_response.to_dict(), + 'is_new_user': not user_data.get('last_login') or ( + user_data.get('created_at') == user_data.get('updated_at') + ) }, 200 diff --git a/webapi/fastapi_of_letcoing/interfaces/service_interfaces.py b/webapi/fastapi_of_letcoing/interfaces/service_interfaces.py index f5a39e7..c264dc3 100644 --- a/webapi/fastapi_of_letcoing/interfaces/service_interfaces.py +++ b/webapi/fastapi_of_letcoing/interfaces/service_interfaces.py @@ -271,4 +271,23 @@ class IJWTService(ABC): Returns: 是否成功 """ + pass + + +class IUserService(ABC): + """用户服务接口""" + + @abstractmethod + async def find_or_create_user(self, provider: str, provider_id: str, user_info: Dict[str, Any]) -> Dict[str, Any]: + """ + 根据第三方登录信息查找或创建用户 + + Args: + provider: 登录提供商 (如 'github', 'api.xauat.site/sso/signup') + provider_id: 提供商的用户ID + user_info: 用户信息字典 + + Returns: + 用户信息字典 + """ pass \ No newline at end of file diff --git a/webapi/fastapi_of_letcoing/models/db_models.py b/webapi/fastapi_of_letcoing/models/db_models.py index 0047483..026eeb2 100644 --- a/webapi/fastapi_of_letcoing/models/db_models.py +++ b/webapi/fastapi_of_letcoing/models/db_models.py @@ -83,11 +83,14 @@ class User(BaseModel): """用户模型""" id = AutoField(primary_key=True, verbose_name="用户ID") - username = CharField(max_length=50, unique=True, null=False, verbose_name="用户名") + username = CharField(max_length=50, unique=True, null=True, verbose_name="用户名") email = CharField(max_length=100, unique=True, null=True, verbose_name="邮箱") - password_hash = CharField(max_length=255, null=False, verbose_name="密码哈希") + password_hash = CharField(max_length=255, null=True, verbose_name="密码") is_active = BooleanField(default=True, verbose_name="是否激活") last_login = DateTimeField(null=True, verbose_name="最后登录时间") + provider = CharField(max_length=50, null=True, verbose_name="登录提供商") + provider_id = CharField(max_length=255, null=True, verbose_name="提供商用户ID") + avatar_url = CharField(max_length=500, null=True, verbose_name="头像URL") class Meta: table_name = "users" diff --git a/webapi/fastapi_of_letcoing/requirements.txt b/webapi/fastapi_of_letcoing/requirements.txt index f8faadb..032e878 100644 --- a/webapi/fastapi_of_letcoing/requirements.txt +++ b/webapi/fastapi_of_letcoing/requirements.txt @@ -34,4 +34,4 @@ requests==2.32.5 rpds-py==0.30.0 urllib3==2.6.2 Werkzeug==3.1.4 -yarl==1.22.0 +yarl==1.22.0 \ No newline at end of file diff --git a/webapi/fastapi_of_letcoing/services/oidc_service.py b/webapi/fastapi_of_letcoing/services/oidc_service.py index b9b6297..c3d2138 100644 --- a/webapi/fastapi_of_letcoing/services/oidc_service.py +++ b/webapi/fastapi_of_letcoing/services/oidc_service.py @@ -175,9 +175,19 @@ class OIDCService(Injectable, IOIDCService): 'provider': provider } else: - # 标准 OIDC 用户信息端点 + # 标准 OIDC 用户信息端点(支持 api.xauat.site/sso/signup 等自定义提供商) resp = client.get('userinfo', token=token) - return resp.json() + user_data = resp.json() + + # 标准化用户信息格式 + return { + 'id': user_data.get('sub') or user_data.get('id'), + 'username': user_data.get('preferred_username') or user_data.get('username') or user_data.get('name'), + 'name': user_data.get('name', ''), + 'email': user_data.get('email', ''), + 'avatar_url': user_data.get('picture') or user_data.get('avatar_url', ''), + 'provider': provider + } except Exception as ex: self._logger_service.error(f"获取用户信息失败: {provider}", ex) diff --git a/webapi/fastapi_of_letcoing/services/user_service.py b/webapi/fastapi_of_letcoing/services/user_service.py index 5c742d0..1a6f645 100644 --- a/webapi/fastapi_of_letcoing/services/user_service.py +++ b/webapi/fastapi_of_letcoing/services/user_service.py @@ -162,4 +162,75 @@ class UserService(DatabaseService, Injectable): except DoesNotExist: return None except Exception as e: - raise RuntimeError(f"获取用户认证信息时发生错误: {e}") \ No newline at end of file + raise RuntimeError(f"获取用户认证信息时发生错误: {e}") + + async def find_or_create_user(self, provider: str, provider_id: str, user_info: Dict[str, Any]) -> Dict[str, Any]: + """ + 根据第三方登录信息查找或创建用户 + + Args: + provider: 登录提供商 (如 'github', 'api.xauat.site/sso/signup') + provider_id: 提供商的用户ID + user_info: 用户信息字典 + + Returns: + 用户信息字典 + """ + try: + # 尝试根据provider和provider_id查找用户 + try: + user = User.get( + (User.provider == provider) & + (User.provider_id == provider_id) + ) + + # 更新用户信息 + if user_info.get('username') and user.username != user_info['username']: + user.username = user_info['username'] + if user_info.get('email') and user.email != user_info['email']: + user.email = user_info['email'] + if user_info.get('avatar_url') and user.avatar_url != user_info['avatar_url']: + user.avatar_url = user_info['avatar_url'] + + user.last_login = datetime.now() + user.save() + + print(f"用户登录成功: {user.id} ({provider})") + return user.to_dict() + + except DoesNotExist: + # 用户不存在,创建新用户 + username = user_info.get('username') or f"{provider}_{provider_id}" + email = user_info.get('email') + + # 检查用户名是否已存在 + try: + existing_user = User.get(User.username == username) + username = f"{username}_{provider_id}" + except DoesNotExist: + pass + + # 检查邮箱是否已存在 + if email: + try: + existing_user = User.get(User.email == email) + email = None + except DoesNotExist: + pass + + user = User.create( + username=username, + email=email, + password_hash=None, + provider=provider, + provider_id=provider_id, + avatar_url=user_info.get('avatar_url'), + is_active=True, + last_login=datetime.now() + ) + + print(f"新用户注册成功: {user.id} ({provider})") + return user.to_dict() + + except Exception as e: + raise RuntimeError(f"查找或创建用户时发生错误: {e}") \ No newline at end of file -- Gitee From bc9929d9089554765cbfbf71d2c68defcc88731b Mon Sep 17 00:00:00 2001 From: zs <18031751980@163.com> Date: Thu, 29 Jan 2026 20:00:09 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E5=A4=84=E7=90=86=E8=AE=A4=E8=AF=81=E5=9B=9E=E8=B0=83=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/auth_controller.py | 4 ++-- webapi/fastapi_of_letcoing/main.py | 11 +++++------ webapi/fastapi_of_letcoing/simple_test.py | 12 ++++++++++++ 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 webapi/fastapi_of_letcoing/simple_test.py diff --git a/webapi/fastapi_of_letcoing/controllers/auth_controller.py b/webapi/fastapi_of_letcoing/controllers/auth_controller.py index c75d5d8..8b085fe 100644 --- a/webapi/fastapi_of_letcoing/controllers/auth_controller.py +++ b/webapi/fastapi_of_letcoing/controllers/auth_controller.py @@ -87,7 +87,7 @@ class AuthCallbackController(Resource): @api.doc('auth_callback') @api.response(200, 'Success', auth_result_model) @api.response(400, 'Bad Request') - def get(self, provider: str): + async def get(self, provider: str): """处理认证回调,实现注册和登录集成""" # 注入服务 oidc_service = inject(IOIDCService) @@ -133,7 +133,7 @@ class AuthCallbackController(Resource): # 查找或创建用户(支持注册和登录集成) try: - user_data = user_service.find_or_create_user( + user_data = await user_service.find_or_create_user( provider=provider, provider_id=provider_id, user_info=user_info_data diff --git a/webapi/fastapi_of_letcoing/main.py b/webapi/fastapi_of_letcoing/main.py index e2c237f..b0c3eb1 100644 --- a/webapi/fastapi_of_letcoing/main.py +++ b/webapi/fastapi_of_letcoing/main.py @@ -55,12 +55,11 @@ else: setup_services(app.config) # 初始化 OIDC 服务 -oidc_service = setup_services.__globals__.get('oidc_service') -if not oidc_service: - from core.di_container import get_container, inject - container = get_container() - oidc_service = container.resolve(IOIDCService) - oidc_service.initialize_oauth(app) +from core.di_container import get_container +container = get_container() +oidc_service = container.resolve(IOIDCService) +oidc_service.initialize_oauth(app) +print("OIDC 服务初始化成功") # 创建 API 实例 api = Api( diff --git a/webapi/fastapi_of_letcoing/simple_test.py b/webapi/fastapi_of_letcoing/simple_test.py new file mode 100644 index 0000000..90eec64 --- /dev/null +++ b/webapi/fastapi_of_letcoing/simple_test.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +print("Starting simple test...") + +# 测试基本导入 +try: + print("Importing Flask...") + from flask import Flask + print("Flask imported successfully!") +except Exception as e: + print(f"Error importing Flask: {e}") + +print("Test completed!") -- Gitee From 6413e0517ff2a16bb94e44b5169f316ac70c7574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?LuckyFish=E5=A5=BD=E7=89=9B=E7=9A=84?= <2383690354@qq.com> Date: Mon, 2 Feb 2026 16:11:57 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 365c4a3..0000000 --- a/.env +++ /dev/null @@ -1,25 +0,0 @@ -API_TOKEN=993cb7df-6ff5-40c3-9a9c-773921e3a12d -JWT_SECRET_KEY=MjM3NTg1NDM4ODQ0MTYxMTE3MzUxODIzMTA0ODY2NjQ= - -REDIS_HOST=hkg1.clusters.zeabur.com -REDIS_PORT=30123 -REDIS_DB=0 -REDIS_PASSWORD=2g8o1A45x9n3hsE0m7yrfL6VkbqJXaBW - -GITHUB_CLIENT_ID=Ov23liSEPDKQ0r51vqBC -GITHUB_CLIENT_SECRET=0ce1c2a2a19cb4562fb1b57e64de3c93877d656e - -OIDC_PROVIDERS=[ - { - "name": "iOSClub", - "issuer": "https://api.xauat.site", - "client_id": "ca_0200be03d08f4f58", - "client_secret": "$2b$10$AnmhmDe4lO27E.XQwHyWcOVOPiy/tBTymd4zMXt9i6l73vHlxkW1q" - } -] - -DB_HOST=hkg1.clusters.zeabur.com -DB_PORT=32627 -DB_NAME=lettest -DB_USER=root -DB_PASSWORD=60zquVE1c7TLInwOivZg9H543Kr2GM8p \ No newline at end of file -- Gitee