diff --git a/README.md b/README.md index e24cb92..0591a28 100644 --- a/README.md +++ b/README.md @@ -76,178 +76,127 @@ alembic revision --autogenerate -m "Initial migration" alembic upgrade head ``` -## 添加数据库表 +## 数据库管理 -### 1.创建模型文件: +### 添加新的数据库表 -**因为表格太多,可以把同类型表放在同一个文件,文件命名规则:ims_account_wechats、ims_account_wxapp、ims_account_xzapp等表格去除前缀和后缀,即为account.py** +1. 创建模型文件: + +将同类型的表模型放在同一个文件中。文件命名规则:去除表名的 `ims_` 前缀,例如: +- `ims_account_wechats`、`ims_account_wxapp` 等表 → `account.py` +- `ims_uni_account`、`ims_uni_settings` 等表 → `uni_account.py` ```python -# 这个模型类AccountWechats用于映射ims_account_wechats表: -# 1. 在ORM层中代表数据库表结构,方便CRUD操作。 -# 2. 在FastAPI中引用该模型时,可以使用Pydantic自动转换ORM对象到响应模型。 -# 3. 创建模型时需确保表名(__tablename__)与真实的数据库表名保持一致,否则查询和插入可能失败。 -# 4. 字段类型要与数据库中定义的列类型严格匹配,避免出现不兼容或异常。 -# 5. 通过Primary Key(acid)唯一标识记录,确保ORM能正确追踪和更新该对象。 -# 6. config.orm_mode = True:允许直接把SQLAlchemy模型实例转换为Pydantic模型对象。 - - -# filepath: ExamService/mooc/models/account.py +# filepath: mooc/models/account.py from sqlalchemy import Column, Integer, String, SmallInteger from mooc.db.database import Base -# 类名命名规则: 去除ims前缀后采用 帕斯卡命名法,无连接符每个单词手写大写 -# AccountWechats模型用于映射数据库表 ims_account_wechats -class AccountWechats(Base): - __tablename__ = "ims_account_wechats" +class AccountWechats(Base): + """微信公众号账号表""" + __tablename__ = "ims_account_wechats" + acid = Column(Integer, primary_key=True) uniacid = Column(Integer, nullable=False) - token = Column(String(32), nullable=False) - encodingaeskey = Column(String(255), nullable=False) - level = Column(SmallInteger, nullable=False) - name = Column(String(30), nullable=False) - account = Column(String(30), nullable=False) - original = Column(String(50), nullable=False) - signature = Column(String(100), nullable=False) - country = Column(String(10), nullable=False) - province = Column(String(3), nullable=False) - city = Column(String(15), nullable=False) - username = Column(String(30), nullable=False) - password = Column(String(32), nullable=False) - lastupdate = Column(Integer, nullable=False) - key = Column(String(50), nullable=False) - secret = Column(String(50), nullable=False) - styleid = Column(Integer, nullable=False) - subscribeurl = Column(String(120), nullable=False) - auth_refresh_token = Column(String(255), nullable=False) - - class Config: - from_attributes = True # 允许与Pydantic的ORM功能兼容,直接读取数据库模型对象 + # ... 其他字段 ``` -### 2.创建schema: -```python -# filepath: ExamService/mooc/schemas/account.py +2. 在 `mooc/models/__init__.py` 中注册表: +```python +# 添加到 expected_tables 集合中 +expected_tables = { + # ... 现有表 ... + 'ims_account_wechats', # 新添加的表 +} +``` + +3. 导出模型类: + +```python +# 在 mooc/models/__init__.py 中添加导出 +from mooc.models.account import AccountWechats +``` + +4. 创建 Schema: + +```python +# filepath: mooc/schemas/account.py from pydantic import BaseModel -from typing import Optional class AccountWechatsBase(BaseModel): - """ - 数据模型基类: AccountWechatsBase - 用于描述基础字段的类型、用途和注意点。 - - 注意: 必填字段都在这里声明,每个字段都对应数据库中的一列。 - """ - uniacid: int # 微信公众号/小程序的关联ID - token: str # 访问接口时使用的token - encodingaeskey: str # 用于消息加解密的AES密钥 - level: int # 认证等级,如订阅号/服务号 - name: str # 微信公众号名称 - account: str # 公众号帐号,如微信号 - original: str # 原始ID(通常以gh_开头) - signature: str # 公众号信息签名 - country: str # 国家 - province: str # 省份 - city: str # 城市 - username: str # 后台登录用户名 - password: str # 后台登录密码 - lastupdate: int # 最后一次更新的时间戳 - key: str # 开发者Key - secret: str # 开发者Secret - styleid: int # 模板样式ID - subscribeurl: str # 订阅链接 - auth_refresh_token: str # 用来刷新授权的token + uniacid: int + # ... 其他字段 class AccountWechatsCreate(AccountWechatsBase): - """ - 用于创建新微信帐号记录: - - 继承自AccountWechatsBase, 不额外添加字段 - - 仅表示此Schema专用于'创建'场景 - """ - - # 用于创建新记录,包含所有必填字段 pass class AccountWechatsUpdate(BaseModel): - """ - 用于更新已有微信帐号记录: - - 只包含可选字段,未在此处的内容将保持不变 - - 注意: exclude_unset=True 可以避免更新空值 - """ - token: Optional[str] # 可选更新token - encodingaeskey: Optional[str]# 可选更新AES key - -class AccountWechats(AccountWechatsBase): - """ - 表示完整的微信帐号记录: - - acid: 数据库主键ID - - 包含所有字段的最终模型,ORM转换时使用 - """ - acid: int # 表中的主键ID + # 可选字段用于更新 + uniacid: Optional[int] = None +class AccountWechatsRead(AccountWechatsBase): + acid: int + class Config: - from_attributes = True # 允许与ORM对象进行直接转换 + from_attributes = True ``` -### 3.创建 CRUD: -```python -# filepath: ExamService/mooc/crud/crud_account.py -from typing import Optional -from sqlalchemy.orm import Session +5. 创建 CRUD 操作: + +```python +# filepath: mooc/crud/crud_account.py from mooc.crud.crud_base import CRUDBase -from mooc.models.account import AccountWechats -from mooc.schemas.account import AccountWechatsCreate, AccountWechatsUpdate class CRUDAccountWechats(CRUDBase[AccountWechats, AccountWechatsCreate, AccountWechatsUpdate]): - """ - AccountWechats的CRUD操作类 - 继承自CRUDBase基类,实现基础的增删改查功能 - 可以根据需要添加自定义的查询方法 - """ def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountWechats]: - """ - 根据uniacid查询微信公众号账号 - :param db: 数据库会话 - :param uniacid: 统一公众号ID - :return: AccountWechats对象或None - """ - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) -# 实例化CRUD对象,方便在其他地方直接导入使用 +# 创建实例 account_wechats = CRUDAccountWechats(AccountWechats) - -# 使用示例: -""" -# 创建新记录 -new_account = account_wechats.create(db, obj_in=account_create_schema) - -# 根据ID获取记录 -account = account_wechats.get(db, id=1) - -# 根据uniacid获取记录 -account = account_wechats.get_by_uniacid(db, uniacid=100) - -# 更新记录 -updated = account_wechats.update(db, db_obj=existing_account, obj_in=update_data) - -# 删除记录 -account_wechats.remove(db, id=1) -""" ``` -### 4.注册模型: +6. 添加 API 路由: + ```python -# filepath: ExamService/mooc/db/base.py -from mooc.db.database import Base -from mooc.models.admin import Admin, Account, AccountWebapp -from mooc.models.account import AccountWechats # 新增,将创建好的模型类导入即可 +# filepath: mooc/api/v1/endpoints/account.py +@account_router.post("/wechats", response_model=AccountWechatsRead) +def create_wechat_account( + *, + db: Session = Depends(deps.get_db), + account_in: AccountWechatsCreate, +): + """创建微信公众号账号""" + return account_wechats.create(db=db, obj_in=account_in) ``` -### 5.运行代码更新数据库信息 +### 数据库表自动创建 + +应用启动时会自动执行以下操作: +1. 导入并验证所有模型定义 +2. 检查数据库中缺失的表 +3. 自动创建缺失的表 + +你可以通过查看应用日志了解表创建的详细情况: ```bash python main.py ``` +### 注意事项 + +1. 模型类名采用帕斯卡命名法(PascalCase),去除 `ims_` 前缀 +2. 表名(`__tablename__`)必须与数据库中的实际表名完全一致 +3. 字段类型要与数据库定义严格匹配 +4. 确保主键和索引的正确定义 +5. 添加适当的文档字符串说明表的用途 + +### 调试帮助 + +如果遇到问题,可以: +1. 检查日志输出了解具体错误 +2. 使用 `get_all_table_names()` 查看已注册的表 +3. 验证模型定义是否完整 +4. 确认数据库连接配置是否正确 + ## CRUDBase 使用指南 ### 简介 @@ -316,3 +265,82 @@ class CRUDYourModel(CRUDBase[YourModel, YourCreateSchema, YourUpdateSchema]): ## API文档 启动服务后访问: http://localhost:2333/docs + +### 使用 AI 生成模型代码 + +当你有一个新的数据库表需要添加时,可以使用以下方式让 AI 帮助生成代码: + +1. 准备 SQL 建表语句,例如: +```sql +CREATE TABLE `ims_uni_settings` ( + `uniacid` int(10) UNSIGNED NOT NULL, + `passport` varchar(200) NOT NULL, + `oauth` varchar(100) NOT NULL, + -- ... 其他字段 + PRIMARY KEY (`uniacid`) +) ENGINE = MyISAM CHARACTER SET = utf8 COLLATE = utf8_general_ci; +``` + +2. 使用以下提示词模板: +``` +请帮我为这个表生成 FastAPI + SQLAlchemy 的实现,包括: +1. SQLAlchemy 模型(models/xxx.py) +2. Pydantic Schema(schemas/xxx.py) +3. CRUD 操作(crud/crud_xxx.py) +4. API 路由(api/v1/endpoints/xxx.py) + +要求: +- 遵循项目的命名规范(去除ims_前缀,使用帕斯卡命名法) +- 确保字段类型映射正确 +- 包含必要的文档注释 +- 实现基本的 CRUD 操作和常用查询方法 + +[SQL建表语句] +``` + +3. 对生成的代码进行以下检查: +- 表名是否与数据库一致 +- 字段类型是否正确映射 +- 主键和索引是否正确定义 +- 是否符合项目的命名规范 +- CRUD 方法是否满足业务需求 + +4. 常用的 SQL 类型映射参考: +```python +# MySQL 到 SQLAlchemy 的类型映射 +int/bigint -> Integer +varchar/char -> String(length) +text -> Text +datetime -> DateTime +timestamp -> TIMESTAMP +tinyint -> SmallInteger +decimal -> DECIMAL(precision, scale) +``` + +5. 示例提示词: +``` +我需要为以下数据表生成 FastAPI 实现: +- 表名:ims_uni_settings +- 主键:uniacid +- 特殊需求: + * 需要通过 uniacid 查询的方法 + * 需要处理 Text 类型的 payment 字段 + * 所有字符串字段都是必填的 + +[SQL语句] +... + +请生成完整的代码实现。 +``` + +6. 代码生成后的集成步骤: +- 将模型类添加到对应的 models 文件 +- 在 models/__init__.py 中注册表名 +- 在 api.py 中注册新的路由 +- 检查并测试生成的 API 端点 + +这种方式可以: +- 快速生成符合项目规范的代码 +- 确保类型映射的准确性 +- 保持代码结构的一致性 +- 减少手动编码的错误 diff --git a/mooc/api/v1/api.py b/mooc/api/v1/api.py index cd7ebab..36083e8 100644 --- a/mooc/api/v1/api.py +++ b/mooc/api/v1/api.py @@ -1,9 +1,8 @@ from fastapi import APIRouter -from mooc.api.v1.endpoints import admin, wechat, account +from mooc.api.v1.endpoints import admin, wechat, account, uni_account api_router = APIRouter() -# עԱ·ɣǰ׺Ϊ/admins api_router.include_router( admin.admin_router, prefix="/admins", @@ -24,4 +23,12 @@ api_router.include_router( ) # Add WeChat endpoints -api_router.include_router(wechat.router, prefix="/wechat", tags=["wechat"]) \ No newline at end of file +api_router.include_router(wechat.router, prefix="/wechat", tags=["wechat"]) + + + +api_router.include_router( + uni_account.uni_account_router, + prefix="/uni-accounts", + tags=["uni-accounts"] +) \ No newline at end of file diff --git a/mooc/api/v1/endpoints/uni_account.py b/mooc/api/v1/endpoints/uni_account.py new file mode 100644 index 0000000..96b1229 --- /dev/null +++ b/mooc/api/v1/endpoints/uni_account.py @@ -0,0 +1,682 @@ +from typing import List +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from mooc.api import deps +from mooc.crud.crud_uni_account import ( + uni_account, uni_account_extra_modules, + uni_account_group, uni_account_menus, + uni_account_modules, uni_account_modules_shortcut, + uni_account_users, uni_group, + uni_link_uniacid, uni_modules, + uni_verifycode, uni_settings +) +from mooc.schemas.uni_account import ( + UniAccountCreate, + UniAccountRead, + UniAccountUpdate, + UniAccountExtraModulesCreate, + UniAccountExtraModulesRead, + UniAccountExtraModulesUpdate, + UniAccountGroupCreate, + UniAccountGroupRead, + UniAccountGroupUpdate, + UniAccountMenusCreate, + UniAccountMenusRead, + UniAccountMenusUpdate, + UniAccountModulesCreate, + UniAccountModulesRead, + UniAccountModulesUpdate, + UniAccountModulesShortcutCreate, + UniAccountModulesShortcutRead, + UniAccountModulesShortcutUpdate, + UniAccountUsersCreate, + UniAccountUsersRead, + UniAccountUsersUpdate, + UniGroupCreate, + UniGroupRead, + UniGroupUpdate, + UniLinkUniacidCreate, + UniLinkUniacidRead, + UniLinkUniacidUpdate, + UniModulesCreate, + UniModulesRead, + UniModulesUpdate, + UniVerifycodeCreate, + UniVerifycodeRead, + UniVerifycodeUpdate, + UniSettingsCreate, + UniSettingsRead, + UniSettingsUpdate +) + +uni_account_router = APIRouter() + +# UniAccount 路由 +@uni_account_router.post("/", response_model=UniAccountRead) +def create_uni_account( + *, + db: Session = Depends(deps.get_db), + uni_account_in: UniAccountCreate, +): + """ + 创建统一公众号账户 + """ + return uni_account.create(db=db, obj_in=uni_account_in) + +@uni_account_router.get("/{uniacid}", response_model=UniAccountRead) +def read_uni_account( + *, + db: Session = Depends(deps.get_db), + uniacid: int, +): + """ + 获取统一公众号账户信息 + """ + db_uni_account = uni_account.get(db=db, id=uniacid) + if not db_uni_account: + raise HTTPException( + status_code=404, + detail="UniAccount not found" + ) + return db_uni_account + +@uni_account_router.put("/{uniacid}", response_model=UniAccountRead) +def update_uni_account( + *, + db: Session = Depends(deps.get_db), + uniacid: int, + uni_account_in: UniAccountUpdate, +): + """ + 更新统一公众号账户信息 + """ + db_uni_account = uni_account.get(db=db, id=uniacid) + if not db_uni_account: + raise HTTPException( + status_code=404, + detail="UniAccount not found" + ) + return uni_account.update(db=db, db_obj=db_uni_account, obj_in=uni_account_in) + +@uni_account_router.delete("/{uniacid}", response_model=UniAccountRead) +def delete_uni_account( + *, + db: Session = Depends(deps.get_db), + uniacid: int, +): + """ + 删除统一公众号账户 + """ + db_uni_account = uni_account.get(db=db, id=uniacid) + if not db_uni_account: + raise HTTPException( + status_code=404, + detail="UniAccount not found" + ) + return uni_account.delete(db=db, id=uniacid) + +# UniAccountExtraModules 路由 +@uni_account_router.post("/modules", response_model=UniAccountExtraModulesRead) +def create_extra_modules( + *, + db: Session = Depends(deps.get_db), + modules_in: UniAccountExtraModulesCreate, +): + """ + 创建账户额外模块 + """ + return uni_account_extra_modules.create(db=db, obj_in=modules_in) + +@uni_account_router.get("/modules/{id}", response_model=UniAccountExtraModulesRead) +def read_extra_modules( + *, + db: Session = Depends(deps.get_db), + id: int, +): + """ + 获取账户额外模块信息 + """ + db_modules = uni_account_extra_modules.get(db=db, id=id) + if not db_modules: + raise HTTPException( + status_code=404, + detail="Extra modules not found" + ) + return db_modules + +@uni_account_router.get("/modules/by-uniacid/{uniacid}", response_model=UniAccountExtraModulesRead) +def read_extra_modules_by_uniacid( + *, + db: Session = Depends(deps.get_db), + uniacid: int, +): + """ + 通过uniacid获取账户额外模块信息 + """ + db_modules = uni_account_extra_modules.get_by_uniacid(db=db, uniacid=uniacid) + if not db_modules: + raise HTTPException( + status_code=404, + detail="Extra modules not found" + ) + return db_modules + +@uni_account_router.put("/modules/{id}", response_model=UniAccountExtraModulesRead) +def update_extra_modules( + *, + db: Session = Depends(deps.get_db), + id: int, + modules_in: UniAccountExtraModulesUpdate, +): + """ + 更新账户额外模块信息 + """ + db_modules = uni_account_extra_modules.get(db=db, id=id) + if not db_modules: + raise HTTPException( + status_code=404, + detail="Extra modules not found" + ) + return uni_account_extra_modules.update(db=db, db_obj=db_modules, obj_in=modules_in) + +@uni_account_router.delete("/modules/{id}", response_model=UniAccountExtraModulesRead) +def delete_extra_modules( + *, + db: Session = Depends(deps.get_db), + id: int, +): + """ + 删除账户额外模块 + """ + db_modules = uni_account_extra_modules.get(db=db, id=id) + if not db_modules: + raise HTTPException( + status_code=404, + detail="Extra modules not found" + ) + return uni_account_extra_modules.delete(db=db, id=id) + +# UniAccountGroup 路由 +@uni_account_router.post("/groups", response_model=UniAccountGroupRead) +def create_group( + *, + db: Session = Depends(deps.get_db), + group_in: UniAccountGroupCreate, +): + """创建账户组关联""" + return uni_account_group.create(db=db, obj_in=group_in) + +@uni_account_router.get("/groups/{id}", response_model=UniAccountGroupRead) +def read_group(*, db: Session = Depends(deps.get_db), id: int): + """获取账户组关联信息""" + group = uni_account_group.get(db=db, id=id) + if not group: + raise HTTPException(status_code=404, detail="Account group not found") + return group + +@uni_account_router.put("/groups/{id}", response_model=UniAccountGroupRead) +def update_group( + *, + db: Session = Depends(deps.get_db), + id: int, + group_in: UniAccountGroupUpdate, +): + """更新账户组关联信息""" + group = uni_account_group.get(db=db, id=id) + if not group: + raise HTTPException(status_code=404, detail="Account group not found") + return uni_account_group.update(db=db, db_obj=group, obj_in=group_in) + +@uni_account_router.delete("/groups/{id}", response_model=UniAccountGroupRead) +def delete_group(*, db: Session = Depends(deps.get_db), id: int): + """删除账户组关联""" + group = uni_account_group.get(db=db, id=id) + if not group: + raise HTTPException(status_code=404, detail="Account group not found") + return uni_account_group.delete(db=db, id=id) + +# UniAccountMenus 路由 +@uni_account_router.post("/menus", response_model=UniAccountMenusRead) +def create_menu( + *, + db: Session = Depends(deps.get_db), + menu_in: UniAccountMenusCreate, +): + """创建账户菜单""" + return uni_account_menus.create(db=db, obj_in=menu_in) + +@uni_account_router.get("/menus/{id}", response_model=UniAccountMenusRead) +def read_menu(*, db: Session = Depends(deps.get_db), id: int): + """获取账户菜单信息""" + menu = uni_account_menus.get(db=db, id=id) + if not menu: + raise HTTPException(status_code=404, detail="Account menu not found") + return menu + +@uni_account_router.get("/menus/by-uniacid/{uniacid}", response_model=UniAccountMenusRead) +def read_menu_by_uniacid(*, db: Session = Depends(deps.get_db), uniacid: int): + """通过uniacid获取账户菜单信息""" + menu = uni_account_menus.get_by_uniacid(db=db, uniacid=uniacid) + if not menu: + raise HTTPException(status_code=404, detail="Account menu not found") + return menu + +@uni_account_router.put("/menus/{id}", response_model=UniAccountMenusRead) +def update_menu( + *, + db: Session = Depends(deps.get_db), + id: int, + menu_in: UniAccountMenusUpdate, +): + """更新账户菜单信息""" + menu = uni_account_menus.get(db=db, id=id) + if not menu: + raise HTTPException(status_code=404, detail="Account menu not found") + return uni_account_menus.update(db=db, db_obj=menu, obj_in=menu_in) + +@uni_account_router.delete("/menus/{id}", response_model=UniAccountMenusRead) +def delete_menu(*, db: Session = Depends(deps.get_db), id: int): + """删除账户菜单""" + menu = uni_account_menus.get(db=db, id=id) + if not menu: + raise HTTPException(status_code=404, detail="Account menu not found") + return uni_account_menus.delete(db=db, id=id) + +# UniAccountModules 路由 +@uni_account_router.post("/modules", response_model=UniAccountModulesRead) +def create_module( + *, + db: Session = Depends(deps.get_db), + module_in: UniAccountModulesCreate, +): + """创建账户模块""" + return uni_account_modules.create(db=db, obj_in=module_in) + +@uni_account_router.get("/modules/{id}", response_model=UniAccountModulesRead) +def read_module(*, db: Session = Depends(deps.get_db), id: int): + """获取账户模块信息""" + module = uni_account_modules.get(db=db, id=id) + if not module: + raise HTTPException(status_code=404, detail="Account module not found") + return module + +@uni_account_router.get("/modules/by-uniacid/{uniacid}", response_model=UniAccountModulesRead) +def read_module_by_uniacid(*, db: Session = Depends(deps.get_db), uniacid: int): + """通过uniacid获取账户模块信息""" + module = uni_account_modules.get_by_uniacid(db=db, uniacid=uniacid) + if not module: + raise HTTPException(status_code=404, detail="Account module not found") + return module + +@uni_account_router.put("/modules/{id}", response_model=UniAccountModulesRead) +def update_module( + *, + db: Session = Depends(deps.get_db), + id: int, + module_in: UniAccountModulesUpdate, +): + """更新账户模块信息""" + module = uni_account_modules.get(db=db, id=id) + if not module: + raise HTTPException(status_code=404, detail="Account module not found") + return uni_account_modules.update(db=db, db_obj=module, obj_in=module_in) + +@uni_account_router.delete("/modules/{id}", response_model=UniAccountModulesRead) +def delete_module(*, db: Session = Depends(deps.get_db), id: int): + """删除账户模块""" + module = uni_account_modules.get(db=db, id=id) + if not module: + raise HTTPException(status_code=404, detail="Account module not found") + return uni_account_modules.delete(db=db, id=id) + +# UniAccountModulesShortcut 完整路由 +@uni_account_router.post("/modules-shortcut", response_model=UniAccountModulesShortcutRead) +def create_module_shortcut( + *, + db: Session = Depends(deps.get_db), + shortcut_in: UniAccountModulesShortcutCreate, +): + """创建模块快捷方式""" + return uni_account_modules_shortcut.create(db=db, obj_in=shortcut_in) + +@uni_account_router.get("/modules-shortcut/{id}", response_model=UniAccountModulesShortcutRead) +def read_module_shortcut(*, db: Session = Depends(deps.get_db), id: int): + """获取模块快捷方式""" + shortcut = uni_account_modules_shortcut.get(db=db, id=id) + if not shortcut: + raise HTTPException(status_code=404, detail="Module shortcut not found") + return shortcut + +@uni_account_router.get("/modules-shortcut/by-uniacid/{uniacid}", response_model=UniAccountModulesShortcutRead) +def read_module_shortcut_by_uniacid(*, db: Session = Depends(deps.get_db), uniacid: int): + """通过uniacid获取模块快捷方式""" + shortcut = uni_account_modules_shortcut.get_by_uniacid(db=db, uniacid=uniacid) + if not shortcut: + raise HTTPException(status_code=404, detail="Module shortcut not found") + return shortcut + +@uni_account_router.put("/modules-shortcut/{id}", response_model=UniAccountModulesShortcutRead) +def update_module_shortcut( + *, + db: Session = Depends(deps.get_db), + id: int, + shortcut_in: UniAccountModulesShortcutUpdate, +): + """更新模块快捷方式""" + shortcut = uni_account_modules_shortcut.get(db=db, id=id) + if not shortcut: + raise HTTPException(status_code=404, detail="Module shortcut not found") + return uni_account_modules_shortcut.update(db=db, db_obj=shortcut, obj_in=shortcut_in) + +@uni_account_router.delete("/modules-shortcut/{id}", response_model=UniAccountModulesShortcutRead) +def delete_module_shortcut(*, db: Session = Depends(deps.get_db), id: int): + """删除模块快捷方式""" + shortcut = uni_account_modules_shortcut.get(db=db, id=id) + if not shortcut: + raise HTTPException(status_code=404, detail="Module shortcut not found") + return uni_account_modules_shortcut.delete(db=db, id=id) + +# UniAccountUsers 完整路由 +@uni_account_router.post("/users", response_model=UniAccountUsersRead) +def create_account_user( + *, + db: Session = Depends(deps.get_db), + user_in: UniAccountUsersCreate, +): + """创建账户用户关联""" + return uni_account_users.create(db=db, obj_in=user_in) + +@uni_account_router.get("/users/{id}", response_model=UniAccountUsersRead) +def read_account_user(*, db: Session = Depends(deps.get_db), id: int): + """获取账户用户关联信息""" + user = uni_account_users.get(db=db, id=id) + if not user: + raise HTTPException(status_code=404, detail="Account user not found") + return user + +@uni_account_router.get("/users/by-uniacid/{uniacid}", response_model=UniAccountUsersRead) +def read_account_user_by_uniacid(*, db: Session = Depends(deps.get_db), uniacid: int): + """通过uniacid获取账户用户关联信息""" + user = uni_account_users.get_by_uniacid(db=db, uniacid=uniacid) + if not user: + raise HTTPException(status_code=404, detail="Account user not found") + return user + +@uni_account_router.get("/users/by-uid/{uid}", response_model=UniAccountUsersRead) +def read_account_user_by_uid(*, db: Session = Depends(deps.get_db), uid: int): + """通过uid获取账户用户关联信息""" + user = uni_account_users.get_by_uid(db=db, uid=uid) + if not user: + raise HTTPException(status_code=404, detail="Account user not found") + return user + +@uni_account_router.put("/users/{id}", response_model=UniAccountUsersRead) +def update_account_user( + *, + db: Session = Depends(deps.get_db), + id: int, + user_in: UniAccountUsersUpdate, +): + """更新账户用户关联信息""" + user = uni_account_users.get(db=db, id=id) + if not user: + raise HTTPException(status_code=404, detail="Account user not found") + return uni_account_users.update(db=db, db_obj=user, obj_in=user_in) + +@uni_account_router.delete("/users/{id}", response_model=UniAccountUsersRead) +def delete_account_user(*, db: Session = Depends(deps.get_db), id: int): + """删除账户用户关联""" + user = uni_account_users.get(db=db, id=id) + if not user: + raise HTTPException(status_code=404, detail="Account user not found") + return uni_account_users.delete(db=db, id=id) + +# UniGroup 完整路由 +@uni_account_router.post("/groups", response_model=UniGroupRead) +def create_uni_group( + *, + db: Session = Depends(deps.get_db), + group_in: UniGroupCreate, +): + """创建统一公众号组""" + return uni_group.create(db=db, obj_in=group_in) + +@uni_account_router.get("/groups/{id}", response_model=UniGroupRead) +def read_uni_group(*, db: Session = Depends(deps.get_db), id: int): + """获取统一公众号组信息""" + group = uni_group.get(db=db, id=id) + if not group: + raise HTTPException(status_code=404, detail="Uni group not found") + return group + +@uni_account_router.get("/groups/by-uniacid/{uniacid}", response_model=UniGroupRead) +def read_uni_group_by_uniacid(*, db: Session = Depends(deps.get_db), uniacid: int): + """通过uniacid获取统一公众号组信息""" + group = uni_group.get_by_uniacid(db=db, uniacid=uniacid) + if not group: + raise HTTPException(status_code=404, detail="Uni group not found") + return group + +@uni_account_router.get("/groups/by-owner/{owner_uid}", response_model=UniGroupRead) +def read_uni_group_by_owner(*, db: Session = Depends(deps.get_db), owner_uid: int): + """通过owner_uid获取统一公众号组信息""" + group = uni_group.get_by_owner(db=db, owner_uid=owner_uid) + if not group: + raise HTTPException(status_code=404, detail="Uni group not found") + return group + +@uni_account_router.put("/groups/{id}", response_model=UniGroupRead) +def update_uni_group( + *, + db: Session = Depends(deps.get_db), + id: int, + group_in: UniGroupUpdate, +): + """更新统一公众号组信息""" + group = uni_group.get(db=db, id=id) + if not group: + raise HTTPException(status_code=404, detail="Uni group not found") + return uni_group.update(db=db, db_obj=group, obj_in=group_in) + +@uni_account_router.delete("/groups/{id}", response_model=UniGroupRead) +def delete_uni_group(*, db: Session = Depends(deps.get_db), id: int): + """删除统一公众号组""" + group = uni_group.get(db=db, id=id) + if not group: + raise HTTPException(status_code=404, detail="Uni group not found") + return uni_group.delete(db=db, id=id) + +# UniLinkUniacid 路由 +@uni_account_router.post("/links", response_model=UniLinkUniacidRead) +def create_link_uniacid( + *, + db: Session = Depends(deps.get_db), + link_in: UniLinkUniacidCreate, +): + """创建公众号链接关系""" + return uni_link_uniacid.create(db=db, obj_in=link_in) + +@uni_account_router.get("/links/{id}", response_model=UniLinkUniacidRead) +def read_link_uniacid(*, db: Session = Depends(deps.get_db), id: int): + """获取公众号链接关系""" + link = uni_link_uniacid.get(db=db, id=id) + if not link: + raise HTTPException(status_code=404, detail="Link uniacid not found") + return link + +@uni_account_router.get("/links/by-uniacid/{uniacid}", response_model=UniLinkUniacidRead) +def read_link_uniacid_by_uniacid(*, db: Session = Depends(deps.get_db), uniacid: int): + """通过uniacid获取公众号链接关系""" + link = uni_link_uniacid.get_by_uniacid(db=db, uniacid=uniacid) + if not link: + raise HTTPException(status_code=404, detail="Link uniacid not found") + return link + +@uni_account_router.put("/links/{id}", response_model=UniLinkUniacidRead) +def update_link_uniacid( + *, + db: Session = Depends(deps.get_db), + id: int, + link_in: UniLinkUniacidUpdate, +): + """更新公众号链接关系""" + link = uni_link_uniacid.get(db=db, id=id) + if not link: + raise HTTPException(status_code=404, detail="Link uniacid not found") + return uni_link_uniacid.update(db=db, db_obj=link, obj_in=link_in) + +@uni_account_router.delete("/links/{id}", response_model=UniLinkUniacidRead) +def delete_link_uniacid(*, db: Session = Depends(deps.get_db), id: int): + """删除公众号链接关系""" + link = uni_link_uniacid.get(db=db, id=id) + if not link: + raise HTTPException(status_code=404, detail="Link uniacid not found") + return uni_link_uniacid.delete(db=db, id=id) + +# UniModules 路由 +@uni_account_router.post("/uni-modules", response_model=UniModulesRead) +def create_uni_module( + *, + db: Session = Depends(deps.get_db), + module_in: UniModulesCreate, +): + """创建统一模块""" + return uni_modules.create(db=db, obj_in=module_in) + +@uni_account_router.get("/uni-modules/{id}", response_model=UniModulesRead) +def read_uni_module(*, db: Session = Depends(deps.get_db), id: int): + """获取统一模块信息""" + module = uni_modules.get(db=db, id=id) + if not module: + raise HTTPException(status_code=404, detail="Uni module not found") + return module + +@uni_account_router.get("/uni-modules/by-uniacid/{uniacid}", response_model=UniModulesRead) +def read_uni_module_by_uniacid(*, db: Session = Depends(deps.get_db), uniacid: int): + """通过uniacid获取统一模块信息""" + module = uni_modules.get_by_uniacid(db=db, uniacid=uniacid) + if not module: + raise HTTPException(status_code=404, detail="Uni module not found") + return module + +@uni_account_router.put("/uni-modules/{id}", response_model=UniModulesRead) +def update_uni_module( + *, + db: Session = Depends(deps.get_db), + id: int, + module_in: UniModulesUpdate, +): + """更新统一模块信息""" + module = uni_modules.get(db=db, id=id) + if not module: + raise HTTPException(status_code=404, detail="Uni module not found") + return uni_modules.update(db=db, db_obj=module, obj_in=module_in) + +@uni_account_router.delete("/uni-modules/{id}", response_model=UniModulesRead) +def delete_uni_module(*, db: Session = Depends(deps.get_db), id: int): + """删除统一模块""" + module = uni_modules.get(db=db, id=id) + if not module: + raise HTTPException(status_code=404, detail="Uni module not found") + return uni_modules.delete(db=db, id=id) + +# UniVerifycode 路由 +@uni_account_router.post("/verifycodes", response_model=UniVerifycodeRead) +def create_verifycode( + *, + db: Session = Depends(deps.get_db), + verifycode_in: UniVerifycodeCreate, +): + """创建验证码""" + return uni_verifycode.create(db=db, obj_in=verifycode_in) + +@uni_account_router.get("/verifycodes/{id}", response_model=UniVerifycodeRead) +def read_verifycode(*, db: Session = Depends(deps.get_db), id: int): + """获取验证码信息""" + verifycode = uni_verifycode.get(db=db, id=id) + if not verifycode: + raise HTTPException(status_code=404, detail="Verifycode not found") + return verifycode + +@uni_account_router.get("/verifycodes/by-uniacid/{uniacid}", response_model=UniVerifycodeRead) +def read_verifycode_by_uniacid(*, db: Session = Depends(deps.get_db), uniacid: int): + """通过uniacid获取验证码信息""" + verifycode = uni_verifycode.get_by_uniacid(db=db, uniacid=uniacid) + if not verifycode: + raise HTTPException(status_code=404, detail="Verifycode not found") + return verifycode + +@uni_account_router.get("/verifycodes/by-receiver/{receiver}", response_model=UniVerifycodeRead) +def read_verifycode_by_receiver(*, db: Session = Depends(deps.get_db), receiver: str): + """通过接收者获取验证码信息""" + verifycode = uni_verifycode.get_by_receiver(db=db, receiver=receiver) + if not verifycode: + raise HTTPException(status_code=404, detail="Verifycode not found") + return verifycode + +@uni_account_router.put("/verifycodes/{id}", response_model=UniVerifycodeRead) +def update_verifycode( + *, + db: Session = Depends(deps.get_db), + id: int, + verifycode_in: UniVerifycodeUpdate, +): + """更新验证码信息""" + verifycode = uni_verifycode.get(db=db, id=id) + if not verifycode: + raise HTTPException(status_code=404, detail="Verifycode not found") + return uni_verifycode.update(db=db, db_obj=verifycode, obj_in=verifycode_in) + +@uni_account_router.delete("/verifycodes/{id}", response_model=UniVerifycodeRead) +def delete_verifycode(*, db: Session = Depends(deps.get_db), id: int): + """删除验证码""" + verifycode = uni_verifycode.get(db=db, id=id) + if not verifycode: + raise HTTPException(status_code=404, detail="Verifycode not found") + return uni_verifycode.delete(db=db, id=id) + +# UniSettings 路由 +@uni_account_router.post("/settings", response_model=UniSettingsRead) +def create_settings( + *, + db: Session = Depends(deps.get_db), + settings_in: UniSettingsCreate, +): + """创建公众号设置""" + # 检查是否已存在该uniacid的设置 + existing_settings = uni_settings.get_by_uniacid(db=db, uniacid=settings_in.uniacid) + if existing_settings: + raise HTTPException( + status_code=400, + detail="Settings for this uniacid already exists" + ) + return uni_settings.create_with_uniacid(db=db, obj_in=settings_in) + +@uni_account_router.get("/settings/{uniacid}", response_model=UniSettingsRead) +def read_settings(*, db: Session = Depends(deps.get_db), uniacid: int): + """获取公众号设置""" + settings = uni_settings.get_by_uniacid(db=db, uniacid=uniacid) + if not settings: + raise HTTPException(status_code=404, detail="Settings not found") + return settings + +@uni_account_router.put("/settings/{uniacid}", response_model=UniSettingsRead) +def update_settings( + *, + db: Session = Depends(deps.get_db), + uniacid: int, + settings_in: UniSettingsUpdate, +): + """更新公众号设置""" + settings = uni_settings.get_by_uniacid(db=db, uniacid=uniacid) + if not settings: + raise HTTPException(status_code=404, detail="Settings not found") + return uni_settings.update(db=db, db_obj=settings, obj_in=settings_in) + +@uni_account_router.delete("/settings/{uniacid}", response_model=UniSettingsRead) +def delete_settings(*, db: Session = Depends(deps.get_db), uniacid: int): + """删除公众号设置""" + settings = uni_settings.get_by_uniacid(db=db, uniacid=uniacid) + if not settings: + raise HTTPException(status_code=404, detail="Settings not found") + return uni_settings.delete(db=db, id=uniacid) \ No newline at end of file diff --git a/mooc/crud/crud_account.py b/mooc/crud/crud_account.py index 90fec6e..ad4352c 100644 --- a/mooc/crud/crud_account.py +++ b/mooc/crud/crud_account.py @@ -19,42 +19,37 @@ from mooc.schemas.account import ( class CRUDAccountWechats(CRUDBase[AccountWechats, AccountWechatsCreate, AccountWechatsUpdate]): def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountWechats]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) class CRUDAccountAliapp(CRUDBase[AccountAliapp, AccountAliappCreate, AccountAliappUpdate]): def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountAliapp]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) class CRUDAccountBaiduapp(CRUDBase[AccountBaiduapp, AccountBaiduappCreate, AccountBaiduappUpdate]): def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountBaiduapp]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) class CRUDAccountPhoneapp(CRUDBase[AccountPhoneapp, AccountPhoneappCreate, AccountPhoneappUpdate]): def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountPhoneapp]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) class CRUDAccountToutiaoapp(CRUDBase[AccountToutiaoapp, AccountToutiaoappCreate, AccountToutiaoappUpdate]): def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountToutiaoapp]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) class CRUDAccountWebapp(CRUDBase[AccountWebapp, AccountWebappCreate, AccountWebappUpdate]): def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountWebapp]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) class CRUDAccountWxapp(CRUDBase[AccountWxapp, AccountWxappCreate, AccountWxappUpdate]): def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountWxapp]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) class CRUDAccountXzapp(CRUDBase[AccountXzapp, AccountXzappCreate, AccountXzappUpdate]): def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[AccountXzapp]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() + return self.get_by_field(db, "uniacid", uniacid) -class CRUDAccount(CRUDBase[Account, None, None]): - def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[Account]: - return db.query(self.model).filter(self.model.uniacid == uniacid).first() - -# 实例化所有CRUD对象 -account = CRUDAccount(Account) +# 创建实例 account_wechats = CRUDAccountWechats(AccountWechats) account_aliapp = CRUDAccountAliapp(AccountAliapp) account_baiduapp = CRUDAccountBaiduapp(AccountBaiduapp) diff --git a/mooc/crud/crud_admin.py b/mooc/crud/crud_admin.py index 76d3e60..549f770 100644 --- a/mooc/crud/crud_admin.py +++ b/mooc/crud/crud_admin.py @@ -1,54 +1,22 @@ -from typing import List, Optional +from typing import Optional from sqlalchemy.orm import Session +from mooc.crud.crud_base import CRUDBase from mooc.models.admin import Admin from mooc.schemas.admin import AdminCreate, AdminUpdate - -class CRUDAdmin: - def get(self, db: Session, id: int) -> Optional[Admin]: - return db.query(Admin).filter(Admin.id == id).first() - - def get_by_username(self, db: Session, username: str) -> Optional[Admin]: - return db.query(Admin).filter(Admin.username == username).first() - - def get_multi( - self, db: Session, *, skip: int = 0, limit: int = 100 - ) -> List[Admin]: - return db.query(Admin).offset(skip).limit(limit).all() - - def create(self, db: Session, *, obj_in: AdminCreate) -> Admin: - db_obj = Admin( - weid=obj_in.weid, - username=obj_in.username, - password=obj_in.password, - pcate_id=obj_in.pcate_id, - cate_id=obj_in.cate_id, - relation_id=obj_in.relation_id, - is_delete=obj_in.is_delete - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - def update( - self, db: Session, *, db_obj: Admin, obj_in: AdminUpdate - ) -> Admin: - update_data = obj_in.dict(exclude_unset=True) - for field, value in update_data.items(): - setattr(db_obj, field, value) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj +class CRUDAdmin(CRUDBase[Admin, AdminCreate, AdminUpdate]): + def get_by_username(self, db: Session, *, username: str) -> Optional[Admin]: + return self.get_by_field(db, "username", username) def delete(self, db: Session, *, id: int) -> Admin: - obj = db.query(Admin).get(id) + """重写删除方法,实现软删除""" + obj = self.get(db, id=id) if obj: obj.is_delete = 1 db.add(obj) db.commit() + db.refresh(obj) return obj -admin = CRUDAdmin() +admin = CRUDAdmin(Admin) diff --git a/mooc/crud/crud_base.py b/mooc/crud/crud_base.py index 4be5a40..f5cb10c 100644 --- a/mooc/crud/crud_base.py +++ b/mooc/crud/crud_base.py @@ -2,9 +2,10 @@ from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union from fastapi.encoders import jsonable_encoder from pydantic import BaseModel from sqlalchemy.orm import Session +from sqlalchemy import Column from mooc.db.database import Base -ModelType = TypeVar("ModelType", bound=Base) +ModelType = TypeVar("ModelType", bound="Base") CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel) UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) @@ -18,8 +19,46 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): self.model = model def get(self, db: Session, id: Any) -> Optional[ModelType]: + """通过主键获取记录""" return db.query(self.model).filter(self.model.acid == id).first() + def get_by_field( + self, + db: Session, + field: str, + value: Any + ) -> Optional[ModelType]: + """ + 通过任意字段获取单条记录 + Args: + db: 数据库会话 + field: 字段名 + value: 字段值 + """ + return db.query(self.model).filter(getattr(self.model, field) == value).first() + + def get_multi_by_field( + self, + db: Session, + field: str, + value: Any, + *, + skip: int = 0, + limit: int = 100 + ) -> List[ModelType]: + """ + 通过任意字段获取多条记录 + Args: + db: 数据库会话 + field: 字段名 + value: 字段值 + skip: 跳过记录数 + limit: 返回记录数限制 + """ + return db.query(self.model).filter( + getattr(self.model, field) == value + ).offset(skip).limit(limit).all() + def get_multi( self, db: Session, *, skip: int = 0, limit: int = 100 ) -> List[ModelType]: diff --git a/mooc/crud/crud_uni_account.py b/mooc/crud/crud_uni_account.py new file mode 100644 index 0000000..d550275 --- /dev/null +++ b/mooc/crud/crud_uni_account.py @@ -0,0 +1,110 @@ +from typing import Optional +from sqlalchemy.orm import Session +from mooc.crud.crud_base import CRUDBase +from mooc.models.uni_account import ( + UniAccount, UniAccountExtraModules, + UniAccountGroup, UniAccountMenus, + UniAccountModules, + UniAccountModulesShortcut, + UniAccountUsers, + UniGroup, + UniLinkUniacid, + UniModules, + UniVerifycode, + UniSettings +) +from mooc.schemas.uni_account import ( + UniAccountCreate, UniAccountUpdate, + UniAccountExtraModulesCreate, UniAccountExtraModulesUpdate, + UniAccountGroupCreate, UniAccountGroupUpdate, + UniAccountMenusCreate, UniAccountMenusUpdate, + UniAccountModulesCreate, UniAccountModulesUpdate, + UniAccountModulesShortcutCreate, UniAccountModulesShortcutUpdate, + UniAccountUsersCreate, UniAccountUsersUpdate, + UniGroupCreate, UniGroupUpdate, + UniLinkUniacidCreate, UniLinkUniacidUpdate, + UniModulesCreate, UniModulesUpdate, + UniVerifycodeCreate, UniVerifycodeUpdate, + UniSettingsCreate, UniSettingsUpdate +) + +class CRUDUniAccount(CRUDBase[UniAccount, UniAccountCreate, UniAccountUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniAccount]: + return self.get_by_field(db, "uniacid", uniacid) + +class CRUDUniAccountExtraModules(CRUDBase[UniAccountExtraModules, UniAccountExtraModulesCreate, UniAccountExtraModulesUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniAccountExtraModules]: + return self.get_by_field(db, "uniacid", uniacid) + +class CRUDUniAccountGroup(CRUDBase[UniAccountGroup, UniAccountGroupCreate, UniAccountGroupUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniAccountGroup]: + return self.get_by_field(db, "uniacid", uniacid) + +class CRUDUniAccountMenus(CRUDBase[UniAccountMenus, UniAccountMenusCreate, UniAccountMenusUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniAccountMenus]: + return self.get_by_field(db, "uniacid", uniacid) + +class CRUDUniAccountModules(CRUDBase[UniAccountModules, UniAccountModulesCreate, UniAccountModulesUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniAccountModules]: + return self.get_by_field(db, "uniacid", uniacid) + +class CRUDUniAccountModulesShortcut(CRUDBase[UniAccountModulesShortcut, UniAccountModulesShortcutCreate, UniAccountModulesShortcutUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniAccountModulesShortcut]: + return self.get_by_field(db, "uniacid", uniacid) + +class CRUDUniAccountUsers(CRUDBase[UniAccountUsers, UniAccountUsersCreate, UniAccountUsersUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniAccountUsers]: + return self.get_by_field(db, "uniacid", uniacid) + + def get_by_uid(self, db: Session, *, uid: int) -> Optional[UniAccountUsers]: + return self.get_by_field(db, "uid", uid) + +class CRUDUniGroup(CRUDBase[UniGroup, UniGroupCreate, UniGroupUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniGroup]: + return self.get_by_field(db, "uniacid", uniacid) + + def get_by_owner(self, db: Session, *, owner_uid: int) -> Optional[UniGroup]: + return self.get_by_field(db, "owner_uid", owner_uid) + +class CRUDUniLinkUniacid(CRUDBase[UniLinkUniacid, UniLinkUniacidCreate, UniLinkUniacidUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniLinkUniacid]: + return self.get_by_field(db, "uniacid", uniacid) + +class CRUDUniModules(CRUDBase[UniModules, UniModulesCreate, UniModulesUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniModules]: + return self.get_by_field(db, "uniacid", uniacid) + +class CRUDUniVerifycode(CRUDBase[UniVerifycode, UniVerifycodeCreate, UniVerifycodeUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniVerifycode]: + return self.get_by_field(db, "uniacid", uniacid) + + def get_by_receiver(self, db: Session, *, receiver: str) -> Optional[UniVerifycode]: + return self.get_by_field(db, "receiver", receiver) + +class CRUDUniSettings(CRUDBase[UniSettings, UniSettingsCreate, UniSettingsUpdate]): + def get_by_uniacid(self, db: Session, *, uniacid: int) -> Optional[UniSettings]: + return db.query(self.model).filter(self.model.uniacid == uniacid).first() + + def create_with_uniacid( + self, db: Session, *, obj_in: UniSettingsCreate + ) -> UniSettings: + """创建设置时使用uniacid作为主键""" + db_obj = UniSettings(**obj_in.dict()) + db.add(db_obj) + db.commit() + db.refresh(db_obj) + return db_obj + +# 创建实例 +uni_account = CRUDUniAccount(UniAccount) +uni_account_extra_modules = CRUDUniAccountExtraModules(UniAccountExtraModules) +uni_account_group = CRUDUniAccountGroup(UniAccountGroup) +uni_account_menus = CRUDUniAccountMenus(UniAccountMenus) +uni_account_modules = CRUDUniAccountModules(UniAccountModules) +uni_account_modules_shortcut = CRUDUniAccountModulesShortcut(UniAccountModulesShortcut) +uni_account_users = CRUDUniAccountUsers(UniAccountUsers) +uni_group = CRUDUniGroup(UniGroup) +uni_link_uniacid = CRUDUniLinkUniacid(UniLinkUniacid) +uni_modules = CRUDUniModules(UniModules) +uni_verifycode = CRUDUniVerifycode(UniVerifycode) +uni_settings = CRUDUniSettings(UniSettings) \ No newline at end of file diff --git a/mooc/db/base.py b/mooc/db/base.py index 7ece256..6f6a026 100644 --- a/mooc/db/base.py +++ b/mooc/db/base.py @@ -9,5 +9,6 @@ from mooc.models.account import ( AccountWebapp, AccountWxapp, AccountXzapp, - Account, -) \ No newline at end of file + Account +) +from mooc.models.uni_account import UniAccount, UniAccountExtraModules, UniAccountGroup, UniAccountMenus, UniAccountModules diff --git a/mooc/db/database.py b/mooc/db/database.py index d54b0e1..8376672 100644 --- a/mooc/db/database.py +++ b/mooc/db/database.py @@ -1,8 +1,16 @@ -from sqlalchemy import create_engine +from sqlalchemy import create_engine, inspect from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from mooc.core.config import settings -from typing import Generator +from typing import Generator, Set +import importlib +import pkgutil +from pathlib import Path +import logging + +# 配置日志 +logger = logging.getLogger(__name__) + # 创建数据库引擎 engine = create_engine( settings.SQLALCHEMY_DATABASE_URI, @@ -16,9 +24,61 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) # 创建基类 Base = declarative_base() -def init_db(): - """初始化数据库""" - Base.metadata.create_all(bind=engine) +def get_existing_tables() -> Set[str]: + """获取数据库中已存在的表""" + inspector = inspect(engine) + return set(inspector.get_table_names()) + +def import_models() -> None: + """ + 自动导入所有模型 + 这确保所有模型类都被正确地注册到Base.metadata + """ + models_path = Path(__file__).parent.parent / "models" + for module_info in pkgutil.iter_modules([str(models_path)]): + importlib.import_module(f"mooc.models.{module_info.name}") + + # 导入后立即验证 + from mooc.models import verify_all_models + verify_all_models() + +def create_missing_tables() -> None: + """创建缺失的表""" + existing_tables = get_existing_tables() + metadata_tables = set(Base.metadata.tables.keys()) + missing_tables = metadata_tables - existing_tables + + if missing_tables: + logger.info(f"Creating missing tables: {missing_tables}") + # 只创建缺失的表 + for table_name in missing_tables: + if table_name in Base.metadata.tables: + Base.metadata.tables[table_name].create(engine) + else: + logger.info("All tables already exist") + +def init_db() -> None: + """ + 初始化数据库 + 1. 导入所有模型并验证 + 2. 检查并创建缺失的表 + """ + try: + # 确保所有模型都被导入并验证 + import_models() + logger.info("All models imported successfully") + + # 创建缺失的表 + create_missing_tables() + logger.info("Database initialization completed successfully") + + # 打印所有已注册的表名(用于调试) + from mooc.models import get_all_table_names + logger.debug(f"Registered tables: {get_all_table_names()}") + + except Exception as e: + logger.error(f"Database initialization failed: {str(e)}") + raise def get_db() -> Generator: """ diff --git a/mooc/models/__init__.py b/mooc/models/__init__.py index e69de29..1ea8c3d 100644 --- a/mooc/models/__init__.py +++ b/mooc/models/__init__.py @@ -0,0 +1,82 @@ +# 显式导入所有模型模块 +from mooc.models.admin import * # noqa +from mooc.models.account import * # noqa +from mooc.models.uni_account import * # noqa + +# 导出常用的模型类 +from mooc.models.account import ( + Account, + AccountWechats, + AccountAliapp, + AccountBaiduapp, + AccountPhoneapp, + AccountToutiaoapp, + AccountWebapp, + AccountWxapp, + AccountXzapp +) + +from mooc.models.uni_account import ( + UniAccount, + UniAccountExtraModules, + UniAccountGroup, + UniAccountMenus, + UniAccountModules, + UniAccountModulesShortcut, + UniAccountUsers, + UniGroup, + UniLinkUniacid, + UniModules, + UniSettings, + UniVerifycode +) + +def verify_all_models(): + """验证所有模型是否已正确注册到Base.metadata""" + from mooc.db.database import Base + + # 所有预期的表名 + expected_tables = { + 'ims_goouc_fullexam_admin', + # Account 相关表 + 'ims_account', + 'ims_account_aliapp', + 'ims_account_baiduapp', + 'ims_account_phoneapp', + 'ims_account_toutiaoapp', + 'ims_account_webapp', + 'ims_account_wechats', + 'ims_account_wxapp', + 'ims_account_xzapp', + + # UniAccount 相关表 + 'ims_uni_account', + 'ims_uni_account_extra_modules', + 'ims_uni_account_group', + 'ims_uni_account_menus', + 'ims_uni_account_modules', + 'ims_uni_account_modules_shortcut', + 'ims_uni_account_users', + 'ims_uni_group', + 'ims_uni_link_uniacid', + 'ims_uni_modules', + 'ims_uni_settings', + 'ims_uni_verifycode' + } + + actual_tables = set(Base.metadata.tables.keys()) + missing_tables = expected_tables - actual_tables + extra_tables = actual_tables - expected_tables + + if missing_tables: + raise RuntimeError(f"Missing tables in metadata: {missing_tables}") + + if extra_tables: + print(f"Warning: Found unexpected tables: {extra_tables}") + + return True + +def get_all_table_names(): + """获取所有已注册的表名""" + from mooc.db.database import Base + return sorted(Base.metadata.tables.keys()) diff --git a/mooc/models/uni_account.py b/mooc/models/uni_account.py new file mode 100644 index 0000000..a6400aa --- /dev/null +++ b/mooc/models/uni_account.py @@ -0,0 +1,187 @@ +from sqlalchemy import Column, Integer, String, Text, SmallInteger +from mooc.db.database import Base + +class UniAccount(Base): + __tablename__ = "ims_uni_account" + + uniacid = Column(Integer, primary_key=True, autoincrement=True) + groupid = Column(Integer, nullable=False) + name = Column(String(100), nullable=False) + description = Column(String(255), nullable=False) + default_acid = Column(Integer, nullable=False) + rank = Column(Integer) + title_initial = Column(String(1), nullable=False) + createtime = Column(Integer, nullable=False) + logo = Column(String(255), nullable=False) + qrcode = Column(String(255), nullable=False) + create_uid = Column(Integer, nullable=False) + + class Config: + from_attributes = True + +class UniAccountExtraModules(Base): + __tablename__ = "ims_uni_account_extra_modules" + + id = Column(Integer, primary_key=True, autoincrement=True) + uniacid = Column(Integer, index=True, nullable=False) + modules = Column(Text, nullable=False) + + class Config: + from_attributes = True + +class UniAccountGroup(Base): + __tablename__ = "ims_uni_account_group" + + id = Column(Integer, primary_key=True, autoincrement=True) + uniacid = Column(Integer, nullable=False) + groupid = Column(Integer, nullable=False) + + class Config: + from_attributes = True + +class UniAccountMenus(Base): + __tablename__ = "ims_uni_account_menus" + + id = Column(Integer, primary_key=True, autoincrement=True) + uniacid = Column(Integer, index=True, nullable=False) + menuid = Column(Integer, index=True, nullable=False) + type = Column(SmallInteger, nullable=False) + title = Column(String(30), nullable=False) + sex = Column(SmallInteger, nullable=False) + group_id = Column(Integer, nullable=False) + client_platform_type = Column(SmallInteger, nullable=False) + area = Column(String(50), nullable=False) + data = Column(Text, nullable=False) + status = Column(SmallInteger, nullable=False) + createtime = Column(Integer, nullable=False) + isdeleted = Column(SmallInteger, nullable=False) + + class Config: + from_attributes = True + +class UniAccountModules(Base): + __tablename__ = "ims_uni_account_modules" + + id = Column(Integer, primary_key=True, autoincrement=True) + uniacid = Column(Integer, index=True, nullable=False) + module = Column(String(50), index=True, nullable=False) + enabled = Column(SmallInteger, nullable=False) + settings = Column(Text, nullable=False) + shortcut = Column(SmallInteger, nullable=False) + displayorder = Column(Integer, nullable=False) + + class Config: + from_attributes = True + +class UniAccountModulesShortcut(Base): + __tablename__ = "ims_uni_account_modules_shortcut" + + id = Column(Integer, primary_key=True, autoincrement=True) + title = Column(String(200), nullable=False) + url = Column(String(250), nullable=False) + icon = Column(String(200), nullable=False) + uniacid = Column(Integer, nullable=False) + version_id = Column(Integer, nullable=False) + module_name = Column(String(200), nullable=False) + + class Config: + from_attributes = True + +class UniAccountUsers(Base): + __tablename__ = "ims_uni_account_users" + + id = Column(Integer, primary_key=True, autoincrement=True) + uniacid = Column(Integer, index=True, nullable=False) + uid = Column(Integer, index=True, nullable=False) + role = Column(String(255), nullable=False) + rank = Column(SmallInteger, nullable=False) + + class Config: + from_attributes = True + +class UniGroup(Base): + __tablename__ = "ims_uni_group" + + id = Column(Integer, primary_key=True, autoincrement=True) + owner_uid = Column(Integer, nullable=False) + name = Column(String(50), nullable=False) + modules = Column(Text, nullable=False) + templates = Column(String(5000), nullable=False) + uniacid = Column(Integer, index=True, nullable=False) + uid = Column(Integer, nullable=False) + + class Config: + from_attributes = True + +class UniLinkUniacid(Base): + __tablename__ = "ims_uni_link_uniacid" + + id = Column(Integer, primary_key=True, autoincrement=True) + uniacid = Column(Integer, nullable=False) + link_uniacid = Column(Integer, nullable=False) + version_id = Column(Integer, nullable=False) + module_name = Column(String(255), nullable=False) + + class Config: + from_attributes = True + +class UniModules(Base): + __tablename__ = "ims_uni_modules" + + id = Column(Integer, primary_key=True, autoincrement=True) + uniacid = Column(Integer, index=True, nullable=False) + module_name = Column(String(50), nullable=False) + + class Config: + from_attributes = True + +class UniVerifycode(Base): + __tablename__ = "ims_uni_verifycode" + + id = Column(Integer, primary_key=True, autoincrement=True) + uniacid = Column(Integer, nullable=False) + receiver = Column(String(50), nullable=False) + verifycode = Column(String(6), nullable=False) + total = Column(SmallInteger, nullable=False) + createtime = Column(Integer, nullable=False) + failed_count = Column(Integer) + + class Config: + from_attributes = True + +class UniSettings(Base): + __tablename__ = "ims_uni_settings" + + uniacid = Column(Integer, primary_key=True) + passport = Column(String(200), nullable=False) + oauth = Column(String(100), nullable=False) + jsauth_acid = Column(Integer, nullable=False) + notify = Column(String(2000), nullable=False) + creditnames = Column(String(500), nullable=False) + creditbehaviors = Column(String(500), nullable=False) + welcome = Column(String(60), nullable=False) + default = Column(String(60), nullable=False) + default_message = Column(String(2000), nullable=False) + payment = Column(Text, nullable=False) + stat = Column(String(300), nullable=False) + default_site = Column(Integer) + sync = Column(SmallInteger, nullable=False) + recharge = Column(String(500), nullable=False) + tplnotice = Column(String(2000), nullable=False) + grouplevel = Column(SmallInteger, nullable=False) + mcplugin = Column(String(500), nullable=False) + exchange_enable = Column(SmallInteger, nullable=False) + coupon_type = Column(SmallInteger, nullable=False) + menuset = Column(Text, nullable=False) + statistics = Column(String(100), nullable=False) + bind_domain = Column(String(200), nullable=False) + comment_status = Column(SmallInteger, nullable=False) + reply_setting = Column(SmallInteger, nullable=False) + default_module = Column(String(100), nullable=False) + attachment_limit = Column(Integer, nullable=False) + attachment_size = Column(String(20), nullable=False) + sync_member = Column(SmallInteger, nullable=False) + remote = Column(String(2000), nullable=False) + + class Config: + from_attributes = True \ No newline at end of file diff --git a/mooc/schemas/uni_account.py b/mooc/schemas/uni_account.py new file mode 100644 index 0000000..c49bd88 --- /dev/null +++ b/mooc/schemas/uni_account.py @@ -0,0 +1,319 @@ +from pydantic import BaseModel +from typing import Optional + +class UniAccountBase(BaseModel): + groupid: int + name: str + description: str + default_acid: int + rank: Optional[int] + title_initial: str + createtime: int + logo: str + qrcode: str + create_uid: int + +class UniAccountCreate(UniAccountBase): + pass + +class UniAccountUpdate(BaseModel): + groupid: Optional[int] + name: Optional[str] + description: Optional[str] + default_acid: Optional[int] + rank: Optional[int] + title_initial: Optional[str] + logo: Optional[str] + qrcode: Optional[str] + create_uid: Optional[int] + +class UniAccountRead(UniAccountBase): + uniacid: int + + class Config: + from_attributes = True + +class UniAccountExtraModulesBase(BaseModel): + uniacid: int + modules: str + +class UniAccountExtraModulesCreate(UniAccountExtraModulesBase): + pass + +class UniAccountExtraModulesUpdate(BaseModel): + modules: Optional[str] + +class UniAccountExtraModulesRead(UniAccountExtraModulesBase): + id: int + + class Config: + from_attributes = True + +class UniAccountGroupBase(BaseModel): + uniacid: int + groupid: int + +class UniAccountGroupCreate(UniAccountGroupBase): + pass + +class UniAccountGroupUpdate(BaseModel): + groupid: Optional[int] + +class UniAccountGroupRead(UniAccountGroupBase): + id: int + + class Config: + from_attributes = True + +class UniAccountMenusBase(BaseModel): + uniacid: int + menuid: int + type: int + title: str + sex: int + group_id: int + client_platform_type: int + area: str + data: str + status: int + createtime: int + isdeleted: int + +class UniAccountMenusCreate(UniAccountMenusBase): + pass + +class UniAccountMenusUpdate(BaseModel): + type: Optional[int] + title: Optional[str] + sex: Optional[int] + group_id: Optional[int] + client_platform_type: Optional[int] + area: Optional[str] + data: Optional[str] + status: Optional[int] + isdeleted: Optional[int] + +class UniAccountMenusRead(UniAccountMenusBase): + id: int + + class Config: + from_attributes = True + +class UniAccountModulesBase(BaseModel): + uniacid: int + module: str + enabled: int + settings: str + shortcut: int + displayorder: int + +class UniAccountModulesCreate(UniAccountModulesBase): + pass + +class UniAccountModulesUpdate(BaseModel): + module: Optional[str] + enabled: Optional[int] + settings: Optional[str] + shortcut: Optional[int] + displayorder: Optional[int] + +class UniAccountModulesRead(UniAccountModulesBase): + id: int + + class Config: + from_attributes = True + +class UniAccountModulesShortcutBase(BaseModel): + title: str + url: str + icon: str + uniacid: int + version_id: int + module_name: str + +class UniAccountModulesShortcutCreate(UniAccountModulesShortcutBase): + pass + +class UniAccountModulesShortcutUpdate(BaseModel): + title: Optional[str] + url: Optional[str] + icon: Optional[str] + version_id: Optional[int] + module_name: Optional[str] + +class UniAccountModulesShortcutRead(UniAccountModulesShortcutBase): + id: int + + class Config: + from_attributes = True + +class UniAccountUsersBase(BaseModel): + uniacid: int + uid: int + role: str + rank: int + +class UniAccountUsersCreate(UniAccountUsersBase): + pass + +class UniAccountUsersUpdate(BaseModel): + role: Optional[str] + rank: Optional[int] + +class UniAccountUsersRead(UniAccountUsersBase): + id: int + + class Config: + from_attributes = True + +class UniGroupBase(BaseModel): + owner_uid: int + name: str + modules: str + templates: str + uniacid: int + uid: int + +class UniGroupCreate(UniGroupBase): + pass + +class UniGroupUpdate(BaseModel): + name: Optional[str] + modules: Optional[str] + templates: Optional[str] + owner_uid: Optional[int] + uid: Optional[int] + +class UniGroupRead(UniGroupBase): + id: int + + class Config: + from_attributes = True + +class UniLinkUniacidBase(BaseModel): + uniacid: int + link_uniacid: int + version_id: int + module_name: str + +class UniLinkUniacidCreate(UniLinkUniacidBase): + pass + +class UniLinkUniacidUpdate(BaseModel): + link_uniacid: Optional[int] + version_id: Optional[int] + module_name: Optional[str] + +class UniLinkUniacidRead(UniLinkUniacidBase): + id: int + + class Config: + from_attributes = True + +class UniModulesBase(BaseModel): + uniacid: int + module_name: str + +class UniModulesCreate(UniModulesBase): + pass + +class UniModulesUpdate(BaseModel): + module_name: Optional[str] + +class UniModulesRead(UniModulesBase): + id: int + + class Config: + from_attributes = True + +class UniVerifycodeBase(BaseModel): + uniacid: int + receiver: str + verifycode: str + total: int + createtime: int + failed_count: Optional[int] + +class UniVerifycodeCreate(UniVerifycodeBase): + pass + +class UniVerifycodeUpdate(BaseModel): + verifycode: Optional[str] + total: Optional[int] + failed_count: Optional[int] + +class UniVerifycodeRead(UniVerifycodeBase): + id: int + + class Config: + from_attributes = True + +class UniSettingsBase(BaseModel): + uniacid: int + passport: str + oauth: str + jsauth_acid: int + notify: str + creditnames: str + creditbehaviors: str + welcome: str + default: str + default_message: str + payment: str + stat: str + default_site: Optional[int] + sync: int + recharge: str + tplnotice: str + grouplevel: int + mcplugin: str + exchange_enable: int + coupon_type: int + menuset: str + statistics: str + bind_domain: str + comment_status: int + reply_setting: int + default_module: str + attachment_limit: int + attachment_size: str + sync_member: int + remote: str + +class UniSettingsCreate(UniSettingsBase): + pass + +class UniSettingsUpdate(BaseModel): + passport: Optional[str] + oauth: Optional[str] + jsauth_acid: Optional[int] + notify: Optional[str] + creditnames: Optional[str] + creditbehaviors: Optional[str] + welcome: Optional[str] + default: Optional[str] + default_message: Optional[str] + payment: Optional[str] + stat: Optional[str] + default_site: Optional[int] + sync: Optional[int] + recharge: Optional[str] + tplnotice: Optional[str] + grouplevel: Optional[int] + mcplugin: Optional[str] + exchange_enable: Optional[int] + coupon_type: Optional[int] + menuset: Optional[str] + statistics: Optional[str] + bind_domain: Optional[str] + comment_status: Optional[int] + reply_setting: Optional[int] + default_module: Optional[str] + attachment_limit: Optional[int] + attachment_size: Optional[str] + sync_member: Optional[int] + remote: Optional[str] + +class UniSettingsRead(UniSettingsBase): + class Config: + from_attributes = True \ No newline at end of file