rainycode 1.1.0__tar.gz → 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {rainycode-1.1.0 → rainycode-1.1.1}/PKG-INFO +1 -1
  2. {rainycode-1.1.0 → rainycode-1.1.1}/common_depends/auth_depend.py +1 -0
  3. {rainycode-1.1.0 → rainycode-1.1.1}/common_servers/api/auth_api.py +2 -1
  4. {rainycode-1.1.0 → rainycode-1.1.1}/common_servers/service/auth_svc.py +115 -11
  5. {rainycode-1.1.0 → rainycode-1.1.1}/common_utils/bcrypt_util.py +2 -4
  6. {rainycode-1.1.0 → rainycode-1.1.1}/pyproject.toml +1 -1
  7. {rainycode-1.1.0 → rainycode-1.1.1}/rainycode.egg-info/PKG-INFO +1 -1
  8. {rainycode-1.1.0 → rainycode-1.1.1}/rainycode.egg-info/SOURCES.txt +0 -1
  9. rainycode-1.1.0/common_servers/managers/wechat_user_manager.py +0 -98
  10. {rainycode-1.1.0 → rainycode-1.1.1}/common_base/aiorequests.py +0 -0
  11. {rainycode-1.1.0 → rainycode-1.1.1}/common_base/consts.py +0 -0
  12. {rainycode-1.1.0 → rainycode-1.1.1}/common_base/exception.py +0 -0
  13. {rainycode-1.1.0 → rainycode-1.1.1}/common_base/logging.py +0 -0
  14. {rainycode-1.1.0 → rainycode-1.1.1}/common_base/response.py +0 -0
  15. {rainycode-1.1.0 → rainycode-1.1.1}/common_models/__init__.py +0 -0
  16. {rainycode-1.1.0 → rainycode-1.1.1}/common_models/base_model.py +0 -0
  17. {rainycode-1.1.0 → rainycode-1.1.1}/common_models/user_model.py +0 -0
  18. {rainycode-1.1.0 → rainycode-1.1.1}/common_models/wechat_model.py +0 -0
  19. {rainycode-1.1.0 → rainycode-1.1.1}/common_servers/api/wechat_api.py +0 -0
  20. {rainycode-1.1.0 → rainycode-1.1.1}/common_servers/router.py +0 -0
  21. {rainycode-1.1.0 → rainycode-1.1.1}/common_servers/schemas/__init__.py +0 -0
  22. {rainycode-1.1.0 → rainycode-1.1.1}/common_servers/schemas/auth_schema.py +0 -0
  23. {rainycode-1.1.0 → rainycode-1.1.1}/common_servers/service/wechat_svc.py +0 -0
  24. {rainycode-1.1.0 → rainycode-1.1.1}/common_utils/captcha_util.py +0 -0
  25. {rainycode-1.1.0 → rainycode-1.1.1}/common_utils/ip_util.py +0 -0
  26. {rainycode-1.1.0 → rainycode-1.1.1}/common_utils/jwt_util.py +0 -0
  27. {rainycode-1.1.0 → rainycode-1.1.1}/common_utils/snowflake_util.py +0 -0
  28. {rainycode-1.1.0 → rainycode-1.1.1}/common_utils/wechat_util.py +0 -0
  29. {rainycode-1.1.0 → rainycode-1.1.1}/core/base_config.py +0 -0
  30. {rainycode-1.1.0 → rainycode-1.1.1}/core/databases/aiodb.py +0 -0
  31. {rainycode-1.1.0 → rainycode-1.1.1}/core/databases/aioredis.py +0 -0
  32. {rainycode-1.1.0 → rainycode-1.1.1}/core/middleware/http_middleware.py +0 -0
  33. {rainycode-1.1.0 → rainycode-1.1.1}/core/start.py +0 -0
  34. {rainycode-1.1.0 → rainycode-1.1.1}/rainycode.egg-info/dependency_links.txt +0 -0
  35. {rainycode-1.1.0 → rainycode-1.1.1}/rainycode.egg-info/requires.txt +0 -0
  36. {rainycode-1.1.0 → rainycode-1.1.1}/rainycode.egg-info/top_level.txt +0 -0
  37. {rainycode-1.1.0 → rainycode-1.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rainycode
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: FastAPI base modules
5
5
  Requires-Python: >=3.8
6
6
  Requires-Dist: build==1.4.0
@@ -53,6 +53,7 @@ async def get_current_user(request: Request, token: str, security_scopes: Securi
53
53
 
54
54
  # 设置请求上下文
55
55
  request.state.user = user
56
+ request.state.token = token
56
57
  return user
57
58
 
58
59
 
@@ -30,10 +30,11 @@ async def refresh_token(refresh_token: str = Body(..., embed=True)):
30
30
 
31
31
  @router.post("/logout", summary="登出")
32
32
  async def logout(
33
- token: str = Depends(oauth2_scheme),
33
+ request: Request,
34
34
  refresh_token: str = Body(..., embed=True),
35
35
  _: User = Security(login_require),
36
36
  ):
37
+ token = request.state.token
37
38
  await AuthService.logout(token, refresh_token)
38
39
  return SuccessReturn(msg="登出成功")
39
40
 
@@ -16,6 +16,7 @@ from common_base.consts import (
16
16
  REDIS_PREFIX_LOGIN_LOCK_IP,
17
17
  )
18
18
  from common_base.exception import CustomException
19
+ from common_base.logging import logger
19
20
  from common_models.user_model import User
20
21
  from common_models.wechat_model import WechatApp, WechatUser, AppTypeEnum
21
22
  from common_utils.bcrypt_util import PwdUtil
@@ -23,7 +24,6 @@ from common_utils.jwt_util import JwtUtil
23
24
  from common_utils.wechat_util import WechatUtil
24
25
  from core.databases.aioredis import AioRedis
25
26
  from common_servers.schemas.auth_schema import UserRegister, UserLogin, WechatLogin
26
- from common_servers.managers.wechat_user_manager import WechatUserManager
27
27
  from tortoise.transactions import atomic
28
28
  from tortoise.expressions import Q
29
29
  from core.base_config import base_config
@@ -75,6 +75,7 @@ class AuthService:
75
75
  phone=user_in.phone,
76
76
  nickname=user_in.nickname
77
77
  )
78
+ logger.info(f"用户注册成功: username={user.username}, user_id={user.id}")
78
79
  return user
79
80
 
80
81
  @classmethod
@@ -83,7 +84,9 @@ class AuthService:
83
84
  用户密码登录
84
85
  """
85
86
  user = await cls._authenticate_password(user_in, client_ip)
86
- return await cls._create_tokens(user)
87
+ token_data = await cls._create_tokens(user)
88
+ logger.info(f"用户登录成功: username={user.username}, user_id={user.id}, ip={client_ip}")
89
+ return token_data
87
90
 
88
91
  @classmethod
89
92
  async def login_wechat_mini(cls, wechat_in: WechatLogin) -> dict:
@@ -99,9 +102,12 @@ class AuthService:
99
102
  刷新 Token
100
103
  """
101
104
  user_id = await AioRedis.get(f"{REDIS_PREFIX_REFRESH_TOKEN}{refresh_token}")
102
- user_id = int(user_id) if user_id else None
103
105
  if not user_id:
104
106
  raise CustomException(msg="无效或已过期的刷新令牌")
107
+ try:
108
+ user_id = int(user_id)
109
+ except (ValueError, TypeError):
110
+ raise CustomException(msg="无效的刷新令牌")
105
111
 
106
112
  user = await User.get_or_none(id=user_id)
107
113
  if not user or not user.is_active:
@@ -109,7 +115,9 @@ class AuthService:
109
115
 
110
116
  # Token 轮换:删除旧的,生成新的
111
117
  await AioRedis.delete(f"{REDIS_PREFIX_REFRESH_TOKEN}{refresh_token}")
112
- return await cls._create_tokens(user)
118
+ token_data = await cls._create_tokens(user)
119
+ logger.info(f"Token刷新成功: username={user.username}, user_id={user.id}")
120
+ return token_data
113
121
 
114
122
  @classmethod
115
123
  async def logout(cls, access_token: str, refresh_token: str):
@@ -133,6 +141,12 @@ class AuthService:
133
141
  if refresh_token:
134
142
  await AioRedis.delete(f"{REDIS_PREFIX_REFRESH_TOKEN}{refresh_token}")
135
143
 
144
+ # 记录登出日志
145
+ if payload:
146
+ user_id = payload.get("sub")
147
+ username = payload.get("username")
148
+ logger.info(f"用户登出: user_id={user_id}, username={username}")
149
+
136
150
  @classmethod
137
151
  async def get_wechat_oauth_url(cls, app_id: str, redirect_uri: str, state: str | None = None) -> str:
138
152
  """
@@ -241,8 +255,8 @@ class AuthService:
241
255
 
242
256
  user_info = await WechatUtil.get_user_info(access_token, openid)
243
257
 
244
- # 使用 WechatUserManager 创建或绑定用户
245
- user = await WechatUserManager.bind_or_register(
258
+ # 使用 _bind_or_register_wechat_user 创建或绑定用户
259
+ user = await cls._bind_or_register_wechat_user(
246
260
  openid=openid,
247
261
  app_config=app_config,
248
262
  unionid=wx_res.get("unionid"),
@@ -255,7 +269,7 @@ class AuthService:
255
269
 
256
270
  # 新用户,需要 snsapi_userinfo 授权
257
271
  # 缓存 state 信息供二次回调使用
258
- new_state = f"{state}_userinfo"
272
+ new_state = str(uuid.uuid4())
259
273
  await AioRedis.set(f"{REDIS_PREFIX_OAUTH_STATE}{new_state}", cache_value, ex=300)
260
274
 
261
275
  # 构建 snsapi_userinfo 授权链接
@@ -296,17 +310,19 @@ class AuthService:
296
310
  user = await User.get_or_none(username=data.username)
297
311
  if not user:
298
312
  await cls._record_login_fail(data.username, client_ip)
313
+ logger.warning(f"登录失败: 用户名不存在, username={data.username}, ip={client_ip}")
299
314
  raise CustomException(msg="用户名或密码错误")
300
315
 
301
316
  if not PwdUtil.verify_password(data.password, user.password):
302
317
  await cls._record_login_fail(data.username, client_ip)
318
+ logger.warning(f"登录失败: 密码错误, username={data.username}, ip={client_ip}")
303
319
  raise CustomException(msg="用户名或密码错误")
304
320
 
305
321
  if not user.is_active:
306
322
  raise CustomException(msg="用户已被禁用")
307
323
 
308
324
  # 清除失败记录
309
- await cls._clear_login_fail(data.username)
325
+ await cls._clear_login_fail(data.username, client_ip)
310
326
 
311
327
  # 更新最后登录时间
312
328
  user.last_login = datetime.now()
@@ -357,8 +373,8 @@ class AuthService:
357
373
  if phone and not re.match(r'^1[3-9]\d{9}$', phone):
358
374
  raise CustomException(msg="手机号格式不正确")
359
375
 
360
- # 6. 使用 WechatUserManager 处理用户绑定/注册
361
- return await WechatUserManager.bind_or_register(
376
+ # 6. 使用 _bind_or_register_wechat_user 处理用户绑定/注册
377
+ return await cls._bind_or_register_wechat_user(
362
378
  openid=openid,
363
379
  app_config=app_config,
364
380
  unionid=unionid,
@@ -397,9 +413,10 @@ class AuthService:
397
413
  await AioRedis.expire(ip_key, LOGIN_LOCK_SECONDS)
398
414
 
399
415
  @classmethod
400
- async def _clear_login_fail(cls, username: str):
416
+ async def _clear_login_fail(cls, username: str, client_ip: str):
401
417
  """清除登录失败记录"""
402
418
  await AioRedis.delete(f"{REDIS_PREFIX_LOGIN_LOCK_USER}{username}")
419
+ await AioRedis.delete(f"{REDIS_PREFIX_LOGIN_LOCK_IP}{client_ip}")
403
420
 
404
421
  @classmethod
405
422
  async def _create_tokens(cls, user: User) -> dict:
@@ -424,6 +441,93 @@ class AuthService:
424
441
  "expires_in": base_config.access_token_expire_minutes * 60,
425
442
  }
426
443
 
444
+ @classmethod
445
+ @atomic("main")
446
+ async def _bind_or_register_wechat_user(
447
+ cls,
448
+ openid: str,
449
+ app_config: WechatApp,
450
+ unionid: str | None = None,
451
+ phone: str | None = None,
452
+ nickname: str | None = None,
453
+ avatar: str | None = None,
454
+ session_key: str | None = None,
455
+ ) -> User:
456
+ """
457
+ 绑定已有用户或创建新用户(微信登录专用)
458
+
459
+ Args:
460
+ openid: 微信 OpenID
461
+ app_config: 微信应用配置
462
+ unionid: 微信 UnionID(可选,用于跨应用关联)
463
+ phone: 手机号(可选,小程序一键登录获取)
464
+ nickname: 微信昵称
465
+ avatar: 微信头像
466
+ session_key: 会话密钥(仅小程序)
467
+
468
+ Returns:
469
+ User: 用户对象
470
+ """
471
+ # 1. 检查 WechatUser 是否已存在
472
+ wechat_user = await WechatUser.get_or_none(
473
+ openid=openid, wechat_app=app_config
474
+ ).select_related("user")
475
+
476
+ if wechat_user:
477
+ # 已存在关联,更新信息
478
+ if session_key:
479
+ wechat_user.session_key = session_key
480
+ if nickname:
481
+ wechat_user.nickname = nickname
482
+ if avatar:
483
+ wechat_user.avatar = avatar
484
+ if phone and not wechat_user.user.phone:
485
+ wechat_user.user.phone = phone
486
+ await wechat_user.user.save()
487
+ await wechat_user.save()
488
+ return wechat_user.user
489
+
490
+ # 2. 检查 User 是否存在(通过 phone 或 unionid)
491
+ user = None
492
+ if phone:
493
+ user = await User.get_or_none(phone=phone)
494
+
495
+ # 如果有 UnionID,检查其他应用下的 WechatUser 是否关联了 User
496
+ if not user and unionid:
497
+ other_wechat_user = (
498
+ await WechatUser.filter(unionid=unionid)
499
+ .exclude(wechat_app=app_config)
500
+ .first()
501
+ .select_related("user")
502
+ )
503
+ if other_wechat_user:
504
+ user = other_wechat_user.user
505
+
506
+ # 3. 创建 User(如不存在)
507
+ if not user:
508
+ # 使用 UUID 避免并发冲突
509
+ username = f"wx_{uuid.uuid4().hex[:8]}"
510
+ user = await User.create(
511
+ username=username,
512
+ password="", # 微信用户无密码
513
+ phone=phone,
514
+ nickname=nickname or f"用户{username[-4:]}",
515
+ avatar=avatar,
516
+ )
517
+
518
+ # 4. 创建 WechatUser 关联
519
+ await WechatUser.create(
520
+ user=user,
521
+ wechat_app=app_config,
522
+ openid=openid,
523
+ unionid=unionid,
524
+ session_key=session_key,
525
+ nickname=nickname,
526
+ avatar=avatar,
527
+ )
528
+
529
+ return user
530
+
427
531
  @classmethod
428
532
  async def _build_redirect_response(cls, redirect_uri: str, token_data: dict, state: str | None = None) -> RedirectResponse:
429
533
  """构建带临时码的重定向响应"""
@@ -44,7 +44,7 @@ class PwdUtil:
44
44
  @classmethod
45
45
  def check_password_strength(cls, password: str) -> str | None:
46
46
  """
47
- 检查密码强度
47
+ 检查密码强度(不含长度检查,由 Schema min_length 处理)
48
48
 
49
49
  Args:
50
50
  password: 明文密码
@@ -52,12 +52,10 @@ class PwdUtil:
52
52
  Returns:
53
53
  str: 如果密码强度不够返回提示信息,否则返回None
54
54
  """
55
- if len(password) < 6:
56
- return "密码长度至少6位"
57
55
  if not any(c.isupper() for c in password):
58
56
  return "密码需要包含大写字母"
59
57
  if not any(c.islower() for c in password):
60
- return "密码需要包含小写字母"
58
+ return "密码需要包含小写字母"
61
59
  if not any(c.isdigit() for c in password):
62
60
  return "密码需要包含数字"
63
61
  return None
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "rainycode"
7
- version = "1.1.0"
7
+ version = "1.1.1"
8
8
  description = "FastAPI base modules"
9
9
  requires-python = ">=3.8"
10
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rainycode
3
- Version: 1.1.0
3
+ Version: 1.1.1
4
4
  Summary: FastAPI base modules
5
5
  Requires-Python: >=3.8
6
6
  Requires-Dist: build==1.4.0
@@ -12,7 +12,6 @@ common_models/wechat_model.py
12
12
  common_servers/router.py
13
13
  common_servers/api/auth_api.py
14
14
  common_servers/api/wechat_api.py
15
- common_servers/managers/wechat_user_manager.py
16
15
  common_servers/schemas/__init__.py
17
16
  common_servers/schemas/auth_schema.py
18
17
  common_servers/service/auth_svc.py
@@ -1,98 +0,0 @@
1
- import uuid
2
- from tortoise.transactions import atomic
3
- from common_models.user_model import User
4
- from common_models.wechat_model import WechatApp, WechatUser
5
-
6
-
7
- class WechatUserManager:
8
- """
9
- 微信用户绑定/注册统一处理
10
- 合并小程序和公众号的用户处理逻辑
11
- """
12
-
13
- @classmethod
14
- @atomic("main")
15
- async def bind_or_register(
16
- cls,
17
- openid: str,
18
- app_config: WechatApp,
19
- unionid: str | None = None,
20
- phone: str | None = None,
21
- nickname: str | None = None,
22
- avatar: str | None = None,
23
- session_key: str | None = None,
24
- ) -> User:
25
- """
26
- 绑定已有用户或创建新用户
27
-
28
- Args:
29
- openid: 微信 OpenID
30
- app_config: 微信应用配置
31
- unionid: 微信 UnionID(可选,用于跨应用关联)
32
- phone: 手机号(可选,小程序一键登录获取)
33
- nickname: 微信昵称
34
- avatar: 微信头像
35
- session_key: 会话密钥(仅小程序)
36
-
37
- Returns:
38
- User: 用户对象
39
- """
40
- # 1. 检查 WechatUser 是否已存在
41
- wechat_user = await WechatUser.get_or_none(
42
- openid=openid, wechat_app=app_config
43
- ).select_related("user")
44
-
45
- if wechat_user:
46
- # 已存在关联,更新信息
47
- if session_key:
48
- wechat_user.session_key = session_key
49
- if nickname:
50
- wechat_user.nickname = nickname
51
- if avatar:
52
- wechat_user.avatar = avatar
53
- if phone and not wechat_user.user.phone:
54
- wechat_user.user.phone = phone
55
- await wechat_user.user.save()
56
- await wechat_user.save()
57
- return wechat_user.user
58
-
59
- # 2. 检查 User 是否存在(通过 phone 或 unionid)
60
- user = None
61
- if phone:
62
- user = await User.get_or_none(phone=phone)
63
-
64
- # 如果有 UnionID,检查其他应用下的 WechatUser 是否关联了 User
65
- if not user and unionid:
66
- other_wechat_user = (
67
- await WechatUser.filter(unionid=unionid)
68
- .exclude(wechat_app=app_config)
69
- .first()
70
- .select_related("user")
71
- )
72
- if other_wechat_user:
73
- user = other_wechat_user.user
74
-
75
- # 3. 创建 User(如不存在)
76
- if not user:
77
- # 使用 UUID 避免并发冲突
78
- username = f"wx_{uuid.uuid4().hex[:8]}"
79
- user = await User.create(
80
- username=username,
81
- password="", # 微信用户无密码
82
- phone=phone,
83
- nickname=nickname or f"用户{username[-4:]}",
84
- avatar=avatar,
85
- )
86
-
87
- # 4. 创建 WechatUser 关联
88
- await WechatUser.create(
89
- user=user,
90
- wechat_app=app_config,
91
- openid=openid,
92
- unionid=unionid,
93
- session_key=session_key,
94
- nickname=nickname,
95
- avatar=avatar,
96
- )
97
-
98
- return user
File without changes
File without changes
File without changes