modify: auth 插件
This commit is contained in:
@@ -0,0 +1,52 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from chongming_worker import auto_bind_models
|
||||||
|
from chongming_worker.runtime import create_workers as _create_workers
|
||||||
|
from chongming_password import hash_password
|
||||||
|
|
||||||
|
from .router import *
|
||||||
|
|
||||||
|
|
||||||
|
auto_bind_models("auth")
|
||||||
|
|
||||||
|
|
||||||
|
async def create_workers(session, config, count=1, worker_manager=None):
|
||||||
|
from chongming_database import Database
|
||||||
|
from .global_var import set_global_var
|
||||||
|
from .models.auth import Users
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
db_path = Path(config.get("data_dir", "./data")) / "auth.db"
|
||||||
|
db = Database(db_path)
|
||||||
|
await db.initialize()
|
||||||
|
|
||||||
|
# 初始化管理员
|
||||||
|
async with db.get_session() as sess:
|
||||||
|
select_stmt = select(Users) \
|
||||||
|
.where(Users.username == "admin") # type: ignore
|
||||||
|
result = await sess.execute(select_stmt)
|
||||||
|
user: Users | None = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
user = Users(
|
||||||
|
username="admin",
|
||||||
|
hashed_password=hash_password("admin"),
|
||||||
|
email="admin@example.com",
|
||||||
|
created_at=datetime.now(),
|
||||||
|
updated_at=datetime.now(),
|
||||||
|
is_superuser=True
|
||||||
|
)
|
||||||
|
sess.add(user)
|
||||||
|
await sess.commit()
|
||||||
|
|
||||||
|
set_global_var("db", db)
|
||||||
|
|
||||||
|
return _create_workers(session, config, count, worker_manager)
|
||||||
|
|
||||||
|
async def cleanup(worker_item: str):
|
||||||
|
from .global_var import db
|
||||||
|
|
||||||
|
if db is not None:
|
||||||
|
await db.dispose()
|
||||||
|
print(f"{worker_item} 清理完毕")
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from chongming_config import get_config
|
||||||
|
from chongming_jwt import JWT
|
||||||
|
from chongming_database import Database
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
def set_global_var(name: str, value: Any) -> None:
|
||||||
|
"""
|
||||||
|
设置当前模块的全局变量。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 变量名(字符串)
|
||||||
|
value: 任意值
|
||||||
|
"""
|
||||||
|
globals()[name] = value
|
||||||
|
|
||||||
|
config = get_config()
|
||||||
|
|
||||||
|
db: Database | None = None
|
||||||
|
|
||||||
|
secret = config["security"]["secret_key"]
|
||||||
|
algorithm = config["security"]["algorithm"]
|
||||||
|
jwt = JWT(secret, algorithm)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"db",
|
||||||
|
"jwt"
|
||||||
|
]
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"""
|
||||||
|
模型导出 — auth
|
||||||
|
自动生成,请勿手动修改
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .pydantic import AuthResponse
|
||||||
|
from .pydantic import ChangePasswordPostRequest
|
||||||
|
from .pydantic import ChangePasswordPostResponse
|
||||||
|
from .pydantic import LoginPostRequest
|
||||||
|
from .pydantic import LoginPostResponse
|
||||||
|
from .pydantic import MeGetRequest
|
||||||
|
from .pydantic import MeGetResponse
|
||||||
|
from .pydantic import RegisterPostRequest
|
||||||
|
from .pydantic import RegisterPostResponse
|
||||||
|
from .pydantic import UpdateProfilePostRequest
|
||||||
|
from .pydantic import UpdateProfilePostResponse
|
||||||
|
|
||||||
|
from .auth import Users
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"AuthResponse",
|
||||||
|
"ChangePasswordPostRequest",
|
||||||
|
"ChangePasswordPostResponse",
|
||||||
|
"LoginPostRequest",
|
||||||
|
"LoginPostResponse",
|
||||||
|
"MeGetRequest",
|
||||||
|
"MeGetResponse",
|
||||||
|
"RegisterPostRequest",
|
||||||
|
"RegisterPostResponse",
|
||||||
|
"UpdateProfilePostRequest",
|
||||||
|
"UpdateProfilePostResponse",
|
||||||
|
"Users",
|
||||||
|
]
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sqlmodel import Field, SQLModel, Column
|
||||||
|
from sqlalchemy import func, DateTime
|
||||||
|
|
||||||
|
|
||||||
|
def primary_key_create():
|
||||||
|
return f"{uuid4()}_{os.getpid()}_{int(time.time())}"
|
||||||
|
|
||||||
|
|
||||||
|
class Users(SQLModel, table=True):
|
||||||
|
__tablename__ = "users" # type: ignore
|
||||||
|
|
||||||
|
# ---------- 主键 ----------
|
||||||
|
id: str = Field(
|
||||||
|
default_factory=primary_key_create,
|
||||||
|
primary_key=True,
|
||||||
|
index=True,
|
||||||
|
description="用户唯一ID(UUID格式)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------- 登录凭证(唯一索引) ----------
|
||||||
|
username: str = Field(
|
||||||
|
max_length=50,
|
||||||
|
unique=True,
|
||||||
|
index=True,
|
||||||
|
nullable=False,
|
||||||
|
description="登录用户名"
|
||||||
|
)
|
||||||
|
email: str = Field(
|
||||||
|
max_length=255,
|
||||||
|
unique=True,
|
||||||
|
index=True,
|
||||||
|
nullable=False,
|
||||||
|
description="电子邮箱"
|
||||||
|
)
|
||||||
|
hashed_password: str = Field(
|
||||||
|
max_length=255,
|
||||||
|
nullable=False,
|
||||||
|
description="加密后的密码(不存明文)"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------- 个人信息(可选) ----------
|
||||||
|
full_name: Optional[str] = Field(
|
||||||
|
default=None,
|
||||||
|
max_length=100,
|
||||||
|
description="用户全名"
|
||||||
|
)
|
||||||
|
avatar_url: Optional[str] = Field(
|
||||||
|
default=None,
|
||||||
|
max_length=500,
|
||||||
|
description="头像图片链接"
|
||||||
|
)
|
||||||
|
phone: Optional[str] = Field(
|
||||||
|
default=None,
|
||||||
|
max_length=20,
|
||||||
|
description="手机号码"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------- 安全与状态标识 ----------
|
||||||
|
is_active: bool = Field(
|
||||||
|
default=True,
|
||||||
|
description="账户是否启用(用于软禁用)"
|
||||||
|
)
|
||||||
|
is_verified: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="邮箱/手机是否已验证"
|
||||||
|
)
|
||||||
|
is_superuser: bool = Field(
|
||||||
|
default=False,
|
||||||
|
description="是否为超级管理员"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------- 审计时间戳(数据库自动维护) ----------
|
||||||
|
created_at: datetime = Field(
|
||||||
|
sa_column=Column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
server_default=func.now(),
|
||||||
|
nullable=False,
|
||||||
|
comment="创建时间"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
updated_at: datetime = Field(
|
||||||
|
sa_column=Column(
|
||||||
|
DateTime(timezone=True),
|
||||||
|
server_default=func.now(),
|
||||||
|
onupdate=func.now(),
|
||||||
|
nullable=False,
|
||||||
|
comment="最后更新时间"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------- 最后登录记录(可选) ----------
|
||||||
|
last_login_at: Optional[datetime] = Field(
|
||||||
|
default=None,
|
||||||
|
sa_column=Column(DateTime(timezone=True), nullable=True),
|
||||||
|
description="最后一次登录时间"
|
||||||
|
)
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
"""
|
||||||
|
自动生成的 Pydantic 模型 — auth
|
||||||
|
来源: config/workers/auth/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, Optional
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class ChangePasswordPostRequest(BaseModel):
|
||||||
|
"""修改密码 — 请求体"""
|
||||||
|
user_id: str = Field(...)
|
||||||
|
old_password: str = Field(...)
|
||||||
|
new_password: str = Field(...)
|
||||||
|
|
||||||
|
class ChangePasswordPostResponse(BaseModel):
|
||||||
|
"""修改密码 — 响应体"""
|
||||||
|
result: str = Field(...)
|
||||||
|
message: str = Field(...)
|
||||||
|
timestamp: Optional[float] = Field(default=None)
|
||||||
|
|
||||||
|
class LoginPostRequest(BaseModel):
|
||||||
|
"""登录 — 请求体"""
|
||||||
|
username: str = Field(...)
|
||||||
|
password: str = Field(...)
|
||||||
|
|
||||||
|
class LoginPostResponse(BaseModel):
|
||||||
|
"""登录 — 响应体"""
|
||||||
|
result: str = Field(...)
|
||||||
|
token: str = Field(...)
|
||||||
|
message: str = Field(...)
|
||||||
|
timestamp: Optional[float] = Field(default=None)
|
||||||
|
|
||||||
|
class MeGetRequest(BaseModel):
|
||||||
|
"""获取当前用户信息 — 请求体"""
|
||||||
|
user_id: str = Field(...)
|
||||||
|
|
||||||
|
class MeGetResponse(BaseModel):
|
||||||
|
"""获取当前用户信息 — 响应体"""
|
||||||
|
result: str = Field(...)
|
||||||
|
id: str = Field(...)
|
||||||
|
username: str = Field(...)
|
||||||
|
email: str = Field(...)
|
||||||
|
full_name: Optional[str] = Field(default=None)
|
||||||
|
avatar_url: Optional[str] = Field(default=None)
|
||||||
|
phone: Optional[str] = Field(default=None)
|
||||||
|
is_active: bool = Field(...)
|
||||||
|
is_verified: bool = Field(...)
|
||||||
|
is_superuser: bool = Field(...)
|
||||||
|
created_at: Optional[str] = Field(default=None)
|
||||||
|
updated_at: Optional[str] = Field(default=None)
|
||||||
|
last_login_at: Optional[str] = Field(default=None)
|
||||||
|
|
||||||
|
class RegisterPostRequest(BaseModel):
|
||||||
|
"""用户注册 — 请求体"""
|
||||||
|
username: str = Field(...)
|
||||||
|
password: str = Field(...)
|
||||||
|
email: str = Field(...)
|
||||||
|
full_name: str = Field(...)
|
||||||
|
phone: str = Field(...)
|
||||||
|
|
||||||
|
class RegisterPostResponse(BaseModel):
|
||||||
|
"""用户注册 — 响应体"""
|
||||||
|
result: str = Field(...)
|
||||||
|
message: str = Field(...)
|
||||||
|
user_id: Optional[str] = Field(default=None)
|
||||||
|
timestamp: Optional[float] = Field(default=None)
|
||||||
|
|
||||||
|
class UpdateProfilePostRequest(BaseModel):
|
||||||
|
"""更新用户资料 — 请求体"""
|
||||||
|
user_id: str = Field(...)
|
||||||
|
full_name: str = Field(...)
|
||||||
|
email: str = Field(...)
|
||||||
|
avatar_url: str = Field(...)
|
||||||
|
phone: str = Field(...)
|
||||||
|
|
||||||
|
class UpdateProfilePostResponse(BaseModel):
|
||||||
|
"""更新用户资料 — 响应体"""
|
||||||
|
result: str = Field(...)
|
||||||
|
message: str = Field(...)
|
||||||
|
timestamp: Optional[float] = Field(default=None)
|
||||||
|
|
||||||
|
|
||||||
|
# 所有响应模型的联合类型
|
||||||
|
AuthResponse = LoginPostResponse | ChangePasswordPostResponse | MeGetResponse | UpdateProfilePostResponse | RegisterPostResponse
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from .login import login
|
||||||
|
from .register import register
|
||||||
|
from .me import me
|
||||||
|
from .update_profile import update_profile
|
||||||
|
from .change_password import change_password
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["login", "register", "me", "update_profile", "change_password"]
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from chongming_worker import rpc_method
|
||||||
|
from chongming_password import hash_password, verify_password
|
||||||
|
|
||||||
|
from ..models.pydantic import (
|
||||||
|
ChangePasswordPostRequest,
|
||||||
|
ChangePasswordPostResponse
|
||||||
|
)
|
||||||
|
from ..models.auth import Users
|
||||||
|
|
||||||
|
|
||||||
|
@rpc_method(
|
||||||
|
subject="auth.change_password",
|
||||||
|
method="POST",
|
||||||
|
summary="修改密码",
|
||||||
|
response_model=ChangePasswordPostResponse,
|
||||||
|
)
|
||||||
|
async def change_password(change_password_post_request: ChangePasswordPostRequest) -> ChangePasswordPostResponse:
|
||||||
|
from ..global_var import db
|
||||||
|
|
||||||
|
if db is None:
|
||||||
|
raise RuntimeError("Database not initialized")
|
||||||
|
|
||||||
|
user_id = change_password_post_request.user_id
|
||||||
|
old_password = change_password_post_request.old_password
|
||||||
|
new_password = change_password_post_request.new_password
|
||||||
|
|
||||||
|
if not old_password:
|
||||||
|
return ChangePasswordPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="旧密码不能为空",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
if not new_password:
|
||||||
|
return ChangePasswordPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="新密码不能为空",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
if old_password == new_password:
|
||||||
|
return ChangePasswordPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="新密码不能与旧密码相同",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
async with db.get_session() as session:
|
||||||
|
stmt = select(Users).filter_by(id=user_id)
|
||||||
|
result = await session.execute(stmt)
|
||||||
|
user: Users | None = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return ChangePasswordPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="用户不存在",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
# 验证旧密码
|
||||||
|
if not verify_password(old_password, user.hashed_password):
|
||||||
|
return ChangePasswordPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="旧密码错误",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新密码
|
||||||
|
user.hashed_password = hash_password(new_password)
|
||||||
|
user.updated_at = datetime.now()
|
||||||
|
session.add(user)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
return ChangePasswordPostResponse(
|
||||||
|
result="success",
|
||||||
|
message="密码修改成功",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from chongming_worker import rpc_method
|
||||||
|
from chongming_password import verify_password
|
||||||
|
|
||||||
|
from ..models.pydantic import (
|
||||||
|
LoginPostRequest,
|
||||||
|
LoginPostResponse
|
||||||
|
)
|
||||||
|
from ..models.auth import Users
|
||||||
|
|
||||||
|
|
||||||
|
@rpc_method(
|
||||||
|
subject="auth.login",
|
||||||
|
method="POST",
|
||||||
|
summary="登录",
|
||||||
|
response_model=LoginPostResponse,
|
||||||
|
)
|
||||||
|
async def login(login_post_request: LoginPostRequest) -> LoginPostResponse:
|
||||||
|
from ..global_var import jwt, db
|
||||||
|
|
||||||
|
if db is None:
|
||||||
|
raise RuntimeError("Database not initialized")
|
||||||
|
async with db.get_session() as session:
|
||||||
|
statement = select(Users).filter_by(
|
||||||
|
username=login_post_request.username,
|
||||||
|
)
|
||||||
|
result = await session.execute(statement)
|
||||||
|
user: Users | None = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return LoginPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="用户名错误",
|
||||||
|
token="",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
if not verify_password(login_post_request.password, user.hashed_password):
|
||||||
|
return LoginPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="密码错误",
|
||||||
|
token="",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
return LoginPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="用户已经注销或被禁用",
|
||||||
|
token="",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
user.last_login_at = datetime.now()
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"sub": user.id,
|
||||||
|
"username": user.username,
|
||||||
|
"email": user.email,
|
||||||
|
"full_name": user.full_name,
|
||||||
|
"avatar_url": user.avatar_url,
|
||||||
|
"phone": user.phone,
|
||||||
|
"is_verified": user.is_verified,
|
||||||
|
"is_superuser": user.is_superuser,
|
||||||
|
"created_at": user.created_at.isoformat(),
|
||||||
|
"updated_at": user.updated_at.isoformat(),
|
||||||
|
"last_login_at": user.last_login_at.isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
token = jwt.encode(
|
||||||
|
json.dumps(payload),
|
||||||
|
expires_in_seconds=10800 # 3小时有效期
|
||||||
|
)
|
||||||
|
|
||||||
|
return LoginPostResponse(
|
||||||
|
result="success",
|
||||||
|
message="登录成功",
|
||||||
|
token=token,
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import time
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from chongming_worker import rpc_method
|
||||||
|
|
||||||
|
from ..models.pydantic import (
|
||||||
|
MeGetRequest,
|
||||||
|
MeGetResponse
|
||||||
|
)
|
||||||
|
from ..models.auth import Users
|
||||||
|
|
||||||
|
|
||||||
|
@rpc_method(
|
||||||
|
subject="auth.me",
|
||||||
|
method="GET",
|
||||||
|
summary="获取当前用户信息",
|
||||||
|
response_model=MeGetResponse,
|
||||||
|
)
|
||||||
|
async def me(me_get_request: MeGetRequest) -> MeGetResponse:
|
||||||
|
from ..global_var import db
|
||||||
|
|
||||||
|
if db is None:
|
||||||
|
raise RuntimeError("Database not initialized")
|
||||||
|
|
||||||
|
async with db.get_session() as session:
|
||||||
|
stmt = select(Users).filter_by(id=me_get_request.user_id)
|
||||||
|
result = await session.execute(stmt)
|
||||||
|
user: Users | None = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return MeGetResponse(
|
||||||
|
result="fail",
|
||||||
|
id="",
|
||||||
|
username="",
|
||||||
|
email="",
|
||||||
|
is_active=False,
|
||||||
|
is_verified=False,
|
||||||
|
is_superuser=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
return MeGetResponse(
|
||||||
|
result="success",
|
||||||
|
id=user.id,
|
||||||
|
username=user.username,
|
||||||
|
email=user.email,
|
||||||
|
full_name=user.full_name,
|
||||||
|
avatar_url=user.avatar_url,
|
||||||
|
phone=user.phone,
|
||||||
|
is_active=user.is_active,
|
||||||
|
is_verified=user.is_verified,
|
||||||
|
is_superuser=user.is_superuser,
|
||||||
|
created_at=user.created_at.isoformat() if user.created_at else None,
|
||||||
|
updated_at=user.updated_at.isoformat() if user.updated_at else None,
|
||||||
|
last_login_at=user.last_login_at.isoformat() if user.last_login_at else None,
|
||||||
|
)
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from chongming_worker import rpc_method
|
||||||
|
from chongming_password import hash_password
|
||||||
|
|
||||||
|
from ..models.pydantic import (
|
||||||
|
RegisterPostRequest,
|
||||||
|
RegisterPostResponse
|
||||||
|
)
|
||||||
|
from ..models.auth import Users
|
||||||
|
|
||||||
|
|
||||||
|
@rpc_method(
|
||||||
|
subject="auth.register",
|
||||||
|
method="POST",
|
||||||
|
summary="用户注册",
|
||||||
|
response_model=RegisterPostResponse,
|
||||||
|
)
|
||||||
|
async def register(register_post_request: RegisterPostRequest) -> RegisterPostResponse:
|
||||||
|
from ..global_var import db
|
||||||
|
|
||||||
|
if db is None:
|
||||||
|
raise RuntimeError("Database not initialized")
|
||||||
|
|
||||||
|
username = register_post_request.username.strip()
|
||||||
|
email = register_post_request.email.strip()
|
||||||
|
full_name = register_post_request.full_name.strip() or None
|
||||||
|
phone = register_post_request.phone.strip() or None
|
||||||
|
|
||||||
|
# 校验必填字段
|
||||||
|
if not username:
|
||||||
|
return RegisterPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="用户名不能为空",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
if not email:
|
||||||
|
return RegisterPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="邮箱不能为空",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
if not register_post_request.password:
|
||||||
|
return RegisterPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="密码不能为空",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
async with db.get_session() as session:
|
||||||
|
# 检查用户名是否已存在
|
||||||
|
stmt = select(Users).filter_by(username=username)
|
||||||
|
result = await session.execute(stmt)
|
||||||
|
if result.scalar_one_or_none() is not None:
|
||||||
|
return RegisterPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="用户名已存在",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
# 检查邮箱是否已存在
|
||||||
|
stmt = select(Users).filter_by(email=email)
|
||||||
|
result = await session.execute(stmt)
|
||||||
|
if result.scalar_one_or_none() is not None:
|
||||||
|
return RegisterPostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="邮箱已被注册",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建新用户
|
||||||
|
user = Users(
|
||||||
|
username=username,
|
||||||
|
hashed_password=hash_password(register_post_request.password),
|
||||||
|
email=email,
|
||||||
|
full_name=full_name,
|
||||||
|
phone=phone,
|
||||||
|
created_at=datetime.now(),
|
||||||
|
updated_at=datetime.now(),
|
||||||
|
)
|
||||||
|
session.add(user)
|
||||||
|
await session.commit()
|
||||||
|
await session.refresh(user)
|
||||||
|
|
||||||
|
return RegisterPostResponse(
|
||||||
|
result="success",
|
||||||
|
message="注册成功",
|
||||||
|
user_id=user.id,
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from chongming_worker import rpc_method
|
||||||
|
|
||||||
|
from ..models.pydantic import (
|
||||||
|
UpdateProfilePostRequest,
|
||||||
|
UpdateProfilePostResponse
|
||||||
|
)
|
||||||
|
from ..models.auth import Users
|
||||||
|
|
||||||
|
|
||||||
|
@rpc_method(
|
||||||
|
subject="auth.update_profile",
|
||||||
|
method="POST",
|
||||||
|
summary="更新用户资料",
|
||||||
|
response_model=UpdateProfilePostResponse,
|
||||||
|
)
|
||||||
|
async def update_profile(update_profile_post_request: UpdateProfilePostRequest) -> UpdateProfilePostResponse:
|
||||||
|
from ..global_var import db
|
||||||
|
|
||||||
|
if db is None:
|
||||||
|
raise RuntimeError("Database not initialized")
|
||||||
|
|
||||||
|
user_id = update_profile_post_request.user_id
|
||||||
|
|
||||||
|
async with db.get_session() as session:
|
||||||
|
stmt = select(Users).filter_by(id=user_id)
|
||||||
|
result = await session.execute(stmt)
|
||||||
|
user: Users | None = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
return UpdateProfilePostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="用户不存在",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
|
||||||
|
# 更新字段(只更新非空值)
|
||||||
|
full_name = update_profile_post_request.full_name.strip()
|
||||||
|
email = update_profile_post_request.email.strip()
|
||||||
|
avatar_url = update_profile_post_request.avatar_url.strip()
|
||||||
|
phone = update_profile_post_request.phone.strip()
|
||||||
|
|
||||||
|
if full_name:
|
||||||
|
user.full_name = full_name
|
||||||
|
if email:
|
||||||
|
# 检查邮箱是否被其他用户占用
|
||||||
|
if email != user.email:
|
||||||
|
check_stmt = select(Users).filter_by(email=email)
|
||||||
|
check_result = await session.execute(check_stmt)
|
||||||
|
if check_result.scalar_one_or_none() is not None:
|
||||||
|
return UpdateProfilePostResponse(
|
||||||
|
result="fail",
|
||||||
|
message="邮箱已被其他用户使用",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
|
user.email = email
|
||||||
|
if avatar_url:
|
||||||
|
user.avatar_url = avatar_url
|
||||||
|
if phone:
|
||||||
|
user.phone = phone
|
||||||
|
|
||||||
|
user.updated_at = datetime.now()
|
||||||
|
session.add(user)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
return UpdateProfilePostResponse(
|
||||||
|
result="success",
|
||||||
|
message="资料更新成功",
|
||||||
|
timestamp=time.time()
|
||||||
|
)
|
||||||
+10
-6
@@ -15,7 +15,6 @@ Example Worker 插件 — 演示 @rpc_method + 显式模型绑定用法
|
|||||||
- 装饰器 subject 中的操作名 → 模型前缀(驼峰)
|
- 装饰器 subject 中的操作名 → 模型前缀(驼峰)
|
||||||
example.add → Add, example.subtract → Subtract
|
example.add → Add, example.subtract → Subtract
|
||||||
"""
|
"""
|
||||||
import modelvault
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from chongming_worker import auto_bind_models
|
from chongming_worker import auto_bind_models
|
||||||
@@ -37,14 +36,19 @@ auto_bind_models("example")
|
|||||||
async def create_workers(session, config, count=1, worker_manager=None):
|
async def create_workers(session, config, count=1, worker_manager=None):
|
||||||
"""插件入口 — 委托给 chongming_worker 运行时创建 Worker 协程"""
|
"""插件入口 — 委托给 chongming_worker 运行时创建 Worker 协程"""
|
||||||
from .global_var import set_global_var
|
from .global_var import set_global_var
|
||||||
|
from chongming_database import Database
|
||||||
|
|
||||||
db_path = Path(config.get("data_dir", "./data")) / "example.modelvault"
|
db_path = Path(config.get("data_dir", "./data")) / "example.db"
|
||||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
db = await modelvault.AsyncDatabase.open(str(db_path)) # type: ignore
|
db = Database(db_path)
|
||||||
|
await db.initialize()
|
||||||
set_global_var("db", db)
|
set_global_var("db", db)
|
||||||
|
|
||||||
return _create_workers(session, config, count, worker_manager)
|
return _create_workers(session, config, count, worker_manager)
|
||||||
|
|
||||||
async def cleanup(worker_item: str):
|
async def cleanup(worker_item: str):
|
||||||
print(f"{worker_item}关闭")
|
from .global_var import db
|
||||||
pass
|
|
||||||
|
if db is not None:
|
||||||
|
await db.dispose()
|
||||||
|
print(f"{worker_item} 清理完毕")
|
||||||
|
|||||||
+8
-10
@@ -1,9 +1,10 @@
|
|||||||
import sys
|
from typing import Any
|
||||||
from typing import Any, Callable, Awaitable, List
|
from .models import *
|
||||||
import modelvault
|
from chongming_database import Database
|
||||||
|
|
||||||
|
|
||||||
|
db: Database | None = None
|
||||||
|
|
||||||
# 初始定义(可以保留 db 作为示例)
|
|
||||||
db: modelvault.AsyncDatabase | None = None # type: ignore
|
|
||||||
|
|
||||||
def set_global_var(name: str, value: Any) -> None:
|
def set_global_var(name: str, value: Any) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -15,10 +16,7 @@ def set_global_var(name: str, value: Any) -> None:
|
|||||||
"""
|
"""
|
||||||
globals()[name] = value
|
globals()[name] = value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 但这样会污染导出列表,不推荐自动添加。
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"db",
|
|
||||||
"set_global_var",
|
"set_global_var",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ from .pydantic import MultiplyGetResponse
|
|||||||
from .pydantic import SubtractGetRequest
|
from .pydantic import SubtractGetRequest
|
||||||
from .pydantic import SubtractGetResponse
|
from .pydantic import SubtractGetResponse
|
||||||
|
|
||||||
|
from .counter import Counter
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"AddGetRequest",
|
"AddGetRequest",
|
||||||
"AddGetResponse",
|
"AddGetResponse",
|
||||||
@@ -29,4 +31,5 @@ __all__ = [
|
|||||||
"MultiplyGetResponse",
|
"MultiplyGetResponse",
|
||||||
"SubtractGetRequest",
|
"SubtractGetRequest",
|
||||||
"SubtractGetResponse",
|
"SubtractGetResponse",
|
||||||
|
"Counter"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
# src/plugins/example/models/counter.py
|
from sqlmodel import Field, SQLModel
|
||||||
from pydantic import BaseModel
|
|
||||||
import modelvault
|
|
||||||
|
|
||||||
class Counter(BaseModel):
|
|
||||||
# 主键字段
|
|
||||||
__modelvault_primary_key__ = "name"
|
|
||||||
# 唯一索引
|
|
||||||
__modelvault_indexes__ = [
|
|
||||||
modelvault.models.unique("name"),
|
|
||||||
]
|
|
||||||
|
|
||||||
name: str # 计数器名称(例如 "visits")
|
class Counter(SQLModel, table=True):
|
||||||
value: int = 0
|
__tablename__ = "counter" # type: ignore
|
||||||
|
name: str = Field(primary_key=True, index=True)
|
||||||
|
value: int = Field(default=0)
|
||||||
|
|
||||||
@@ -4,6 +4,7 @@ from .multiply import multiply
|
|||||||
from .divide import divide
|
from .divide import divide
|
||||||
from .count import count_get
|
from .count import count_get
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"add",
|
"add",
|
||||||
"add_post",
|
"add_post",
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ async def add(add_get_request:AddGetRequest) -> AddGetResponse:
|
|||||||
summary="Add two numbers via POST",
|
summary="Add two numbers via POST",
|
||||||
response_model=AddPostResponse,
|
response_model=AddPostResponse,
|
||||||
)
|
)
|
||||||
async def add_post(add_post_request: AddPostRequest, session: zenoh.Session) -> AddPostResponse:
|
async def add_post(add_post_request: AddPostRequest, session: zenoh.Session, user_info: dict | None = None) -> AddPostResponse:
|
||||||
"""加法运算(POST)— 通过 JSON Body 传入 a、b,返回它们的和"""
|
"""加法运算(POST)— 通过 JSON Body 传入 a、b,返回它们的和"""
|
||||||
|
print("----------user_info-----------")
|
||||||
|
print(user_info)
|
||||||
async with Client(session=session) as client:
|
async with Client(session=session) as client:
|
||||||
add_result = await client.call(
|
add_result = await client.call(
|
||||||
worker_type="example",
|
worker_type="example",
|
||||||
|
|||||||
+20
-11
@@ -1,4 +1,4 @@
|
|||||||
import modelvault
|
from sqlalchemy import select, update
|
||||||
|
|
||||||
from chongming_worker import rpc_method
|
from chongming_worker import rpc_method
|
||||||
|
|
||||||
@@ -15,19 +15,28 @@ from ..models.counter import Counter
|
|||||||
async def count_get() -> CountGetResponse:
|
async def count_get() -> CountGetResponse:
|
||||||
from ..global_var import db
|
from ..global_var import db
|
||||||
|
|
||||||
# 确保数据库已初始化(如果未初始化,则同步初始化,但这里假设已就绪)
|
|
||||||
if db is None:
|
if db is None:
|
||||||
raise RuntimeError("Database not initialized")
|
raise RuntimeError("Database not initialized")
|
||||||
|
async with db.get_session() as session:
|
||||||
counters = modelvault.models.async_collection(db, Counter) # type: ignore
|
statement = select(Counter).where(Counter.name == "visits") # type: ignore
|
||||||
async with db.transaction():
|
result = await session.execute(statement)
|
||||||
counter = await counters.get("visits")
|
counter = result.scalar_one_or_none()
|
||||||
|
|
||||||
if counter is None:
|
if counter is None:
|
||||||
counter = Counter(name="visits", value=0)
|
counter = Counter(name="visits", value=1)
|
||||||
await counters.insert(counter)
|
session.add(counter)
|
||||||
|
await session.commit()
|
||||||
|
return CountGetResponse(name="visits", value=1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
new_value = counter.value + 1
|
new_value = counter.value + 1
|
||||||
await counters.update("visits", {"value": new_value})
|
|
||||||
counter.value = new_value # 更新本地对象以便返回
|
|
||||||
|
|
||||||
return CountGetResponse(name=counter.name, value=counter.value) # type: ignore
|
update_stmt = (
|
||||||
|
update(Counter)
|
||||||
|
.where(Counter.name == "visits") # type: ignore
|
||||||
|
.values(value=new_value)
|
||||||
|
)
|
||||||
|
await session.execute(update_stmt)
|
||||||
|
await session.commit()
|
||||||
|
|
||||||
|
return CountGetResponse(name="visits", value=new_value)
|
||||||
|
|||||||
@@ -26,4 +26,4 @@ async def divide(divide_get_request: DivideGetRequest) -> DivideGetResponse:
|
|||||||
result=divide_get_request.a / divide_get_request.b,
|
result=divide_get_request.a / divide_get_request.b,
|
||||||
operation="divide",
|
operation="divide",
|
||||||
timestamp=time.time(),
|
timestamp=time.time(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,4 +19,4 @@ async def multiply(multiply_get_request=MultiplyGetRequest) -> MultiplyGetRespon
|
|||||||
result=multiply_get_request.a * multiply_get_request.b,
|
result=multiply_get_request.a * multiply_get_request.b,
|
||||||
operation="multiply",
|
operation="multiply",
|
||||||
timestamp=time.time(),
|
timestamp=time.time(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,4 +19,4 @@ async def subtract(subtract_get_request: SubtractGetRequest) -> SubtractGetRespo
|
|||||||
result=subtract_get_request.a - subtract_get_request.b,
|
result=subtract_get_request.a - subtract_get_request.b,
|
||||||
operation="subtract",
|
operation="subtract",
|
||||||
timestamp=time.time(),
|
timestamp=time.time(),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user