diff --git a/backend/.env.example b/backend/.env.example index 03e90cc3..04bf90b1 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,10 +1,10 @@ # Env ENVIRONMENT='dev' # Database -DATABASE_TYPE='mysql' +DATABASE_TYPE='postgresql' DATABASE_HOST='127.0.0.1' -DATABASE_PORT=3306 -DATABASE_USER='root' +DATABASE_PORT=5432 +DATABASE_USER='postgres' DATABASE_PASSWORD='123456' # Redis REDIS_HOST='127.0.0.1' diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 2d378eb4..7615b7a3 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -12,7 +12,6 @@ def get_app_models() -> list[type]: apps = [d for d in list_dirs if os.path.isdir(os.path.join(app_path, d)) and d != '__pycache__'] objs = [] - for app in apps: module_path = f'backend.app.{app}.model' obj = get_model_objects(module_path) diff --git a/backend/app/admin/model/data_rule.py b/backend/app/admin/model/data_rule.py index 87642c3c..ab0253d2 100644 --- a/backend/app/admin/model/data_rule.py +++ b/backend/app/admin/model/data_rule.py @@ -2,7 +2,8 @@ from typing import TYPE_CHECKING -from sqlalchemy import String +import sqlalchemy as sa + from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.app.admin.model.m2m import sys_data_scope_rule @@ -18,14 +19,14 @@ class DataRule(Base): __tablename__ = 'sys_data_rule' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(500), unique=True, comment='名称') - model: Mapped[str] = mapped_column(String(50), comment='SQLA 模型名,对应 DATA_PERMISSION_MODELS 键名') - column: Mapped[str] = mapped_column(String(20), comment='模型字段名') + name: Mapped[str] = mapped_column(sa.String(500), unique=True, comment='名称') + model: Mapped[str] = mapped_column(sa.String(50), comment='SQLA 模型名,对应 DATA_PERMISSION_MODELS 键名') + column: Mapped[str] = mapped_column(sa.String(20), comment='模型字段名') operator: Mapped[int] = mapped_column(comment='运算符(0:and、1:or)') expression: Mapped[int] = mapped_column( comment='表达式(0:==、1:!=、2:>、3:>=、4:<、5:<=、6:in、7:not_in)', ) - value: Mapped[str] = mapped_column(String(255), comment='规则值') + value: Mapped[str] = mapped_column(sa.String(255), comment='规则值') # 数据范围规则多对多 scopes: Mapped[list[DataScope]] = relationship(init=False, secondary=sys_data_scope_rule, back_populates='rules') diff --git a/backend/app/admin/model/data_scope.py b/backend/app/admin/model/data_scope.py index 3b7552bc..b95d21bf 100644 --- a/backend/app/admin/model/data_scope.py +++ b/backend/app/admin/model/data_scope.py @@ -2,7 +2,8 @@ from typing import TYPE_CHECKING -from sqlalchemy import String +import sqlalchemy as sa + from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.app.admin.model.m2m import sys_data_scope_rule, sys_role_data_scope @@ -18,7 +19,7 @@ class DataScope(Base): __tablename__ = 'sys_data_scope' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(50), unique=True, comment='名称') + name: Mapped[str] = mapped_column(sa.String(50), unique=True, comment='名称') status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') # 数据范围规则多对多 diff --git a/backend/app/admin/model/dept.py b/backend/app/admin/model/dept.py index 066b71c3..6d70cd6b 100644 --- a/backend/app/admin/model/dept.py +++ b/backend/app/admin/model/dept.py @@ -2,8 +2,9 @@ from typing import TYPE_CHECKING -from sqlalchemy import BigInteger, Boolean, ForeignKey, String -from sqlalchemy.dialects.postgresql import INTEGER +import sqlalchemy as sa + +from sqlalchemy.dialects.mysql import TINYINT from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.common.model import Base, id_key @@ -18,25 +19,19 @@ class Dept(Base): __tablename__ = 'sys_dept' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(50), comment='部门名称') + name: Mapped[str] = mapped_column(sa.String(50), comment='部门名称') sort: Mapped[int] = mapped_column(default=0, comment='排序') - leader: Mapped[str | None] = mapped_column(String(20), default=None, comment='负责人') - phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机') - email: Mapped[str | None] = mapped_column(String(50), default=None, comment='邮箱') + leader: Mapped[str | None] = mapped_column(sa.String(20), default=None, comment='负责人') + phone: Mapped[str | None] = mapped_column(sa.String(11), default=None, comment='手机') + email: Mapped[str | None] = mapped_column(sa.String(50), default=None, comment='邮箱') status: Mapped[int] = mapped_column(default=1, comment='部门状态(0停用 1正常)') del_flag: Mapped[bool] = mapped_column( - Boolean().with_variant(INTEGER, 'postgresql'), - default=False, - comment='删除标志(0删除 1存在)', + sa.INTEGER().with_variant(TINYINT, 'mysql'), default=False, comment='删除标志(0删除 1存在)' ) # 父级部门一对多 parent_id: Mapped[int | None] = mapped_column( - BigInteger, - ForeignKey('sys_dept.id', ondelete='SET NULL'), - default=None, - index=True, - comment='父部门ID', + sa.BigInteger, sa.ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, index=True, comment='父部门ID' ) parent: Mapped[Dept | None] = relationship(init=False, back_populates='children', remote_side=[id]) children: Mapped[list[Dept] | None] = relationship(init=False, back_populates='parent') diff --git a/backend/app/admin/model/login_log.py b/backend/app/admin/model/login_log.py index 0f27c951..048e7d82 100644 --- a/backend/app/admin/model/login_log.py +++ b/backend/app/admin/model/login_log.py @@ -1,8 +1,8 @@ from datetime import datetime -from sqlalchemy import String +import sqlalchemy as sa + from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import TEXT from sqlalchemy.orm import Mapped, mapped_column from backend.common.model import DataClassBase, TimeZone, id_key @@ -15,18 +15,18 @@ class LoginLog(DataClassBase): __tablename__ = 'sys_login_log' id: Mapped[id_key] = mapped_column(init=False) - user_uuid: Mapped[str] = mapped_column(String(50), comment='用户UUID') - username: Mapped[str] = mapped_column(String(20), comment='用户名') + user_uuid: Mapped[str] = mapped_column(sa.String(50), comment='用户UUID') + username: Mapped[str] = mapped_column(sa.String(20), comment='用户名') status: Mapped[int] = mapped_column(insert_default=0, comment='登录状态(0失败 1成功)') - ip: Mapped[str] = mapped_column(String(50), comment='登录IP地址') - country: Mapped[str | None] = mapped_column(String(50), comment='国家') - region: Mapped[str | None] = mapped_column(String(50), comment='地区') - city: Mapped[str | None] = mapped_column(String(50), comment='城市') - user_agent: Mapped[str] = mapped_column(String(255), comment='请求头') - os: Mapped[str | None] = mapped_column(String(50), comment='操作系统') - browser: Mapped[str | None] = mapped_column(String(50), comment='浏览器') - device: Mapped[str | None] = mapped_column(String(50), comment='设备') - msg: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='提示消息') + ip: Mapped[str] = mapped_column(sa.String(50), comment='登录IP地址') + country: Mapped[str | None] = mapped_column(sa.String(50), comment='国家') + region: Mapped[str | None] = mapped_column(sa.String(50), comment='地区') + city: Mapped[str | None] = mapped_column(sa.String(50), comment='城市') + user_agent: Mapped[str] = mapped_column(sa.String(255), comment='请求头') + os: Mapped[str | None] = mapped_column(sa.String(50), comment='操作系统') + browser: Mapped[str | None] = mapped_column(sa.String(50), comment='浏览器') + device: Mapped[str | None] = mapped_column(sa.String(50), comment='设备') + msg: Mapped[str] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), comment='提示消息') login_time: Mapped[datetime] = mapped_column(TimeZone, comment='登录时间') created_time: Mapped[datetime] = mapped_column( TimeZone, diff --git a/backend/app/admin/model/m2m.py b/backend/app/admin/model/m2m.py index 4d567646..310d3a8b 100644 --- a/backend/app/admin/model/m2m.py +++ b/backend/app/admin/model/m2m.py @@ -1,52 +1,62 @@ -from sqlalchemy import BigInteger, Column, ForeignKey, Table +import sqlalchemy as sa from backend.common.model import MappedBase -sys_user_role = Table( +sys_user_role = sa.Table( 'sys_user_role', MappedBase.metadata, - Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), - Column('user_id', BigInteger, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'), - Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), + sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + sa.Column( + 'user_id', sa.BigInteger, sa.ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID' + ), + sa.Column( + 'role_id', sa.BigInteger, sa.ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID' + ), ) -sys_role_menu = Table( +sys_role_menu = sa.Table( 'sys_role_menu', MappedBase.metadata, - Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), - Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'), - Column('menu_id', BigInteger, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'), + sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + sa.Column( + 'role_id', sa.BigInteger, sa.ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID' + ), + sa.Column( + 'menu_id', sa.BigInteger, sa.ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID' + ), ) -sys_role_data_scope = Table( +sys_role_data_scope = sa.Table( 'sys_role_data_scope', MappedBase.metadata, - Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键 ID'), - Column('role_id', BigInteger, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色 ID'), - Column( + sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键 ID'), + sa.Column( + 'role_id', sa.BigInteger, sa.ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色 ID' + ), + sa.Column( 'data_scope_id', - BigInteger, - ForeignKey('sys_data_scope.id', ondelete='CASCADE'), + sa.BigInteger, + sa.ForeignKey('sys_data_scope.id', ondelete='CASCADE'), primary_key=True, comment='数据范围 ID', ), ) -sys_data_scope_rule = Table( +sys_data_scope_rule = sa.Table( 'sys_data_scope_rule', MappedBase.metadata, - Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), - Column( + sa.Column('id', sa.BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'), + sa.Column( 'data_scope_id', - BigInteger, - ForeignKey('sys_data_scope.id', ondelete='CASCADE'), + sa.BigInteger, + sa.ForeignKey('sys_data_scope.id', ondelete='CASCADE'), primary_key=True, comment='数据范围 ID', ), - Column( + sa.Column( 'data_rule_id', - BigInteger, - ForeignKey('sys_data_rule.id', ondelete='CASCADE'), + sa.BigInteger, + sa.ForeignKey('sys_data_rule.id', ondelete='CASCADE'), primary_key=True, comment='数据规则 ID', ), diff --git a/backend/app/admin/model/menu.py b/backend/app/admin/model/menu.py index a765fae9..8f0166fe 100644 --- a/backend/app/admin/model/menu.py +++ b/backend/app/admin/model/menu.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from sqlalchemy import BigInteger, ForeignKey, String +import sqlalchemy as sa + from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import TEXT from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.app.admin.model.m2m import sys_role_menu @@ -20,35 +20,25 @@ class Menu(Base): __tablename__ = 'sys_menu' id: Mapped[id_key] = mapped_column(init=False) - title: Mapped[str] = mapped_column(String(50), comment='菜单标题') - name: Mapped[str] = mapped_column(String(50), comment='菜单名称') - path: Mapped[str | None] = mapped_column(String(200), comment='路由地址') + title: Mapped[str] = mapped_column(sa.String(50), comment='菜单标题') + name: Mapped[str] = mapped_column(sa.String(50), comment='菜单名称') + path: Mapped[str | None] = mapped_column(sa.String(200), comment='路由地址') sort: Mapped[int] = mapped_column(default=0, comment='排序') - icon: Mapped[str | None] = mapped_column(String(100), default=None, comment='菜单图标') + icon: Mapped[str | None] = mapped_column(sa.String(100), default=None, comment='菜单图标') type: Mapped[int] = mapped_column(default=0, comment='菜单类型(0目录 1菜单 2按钮 3内嵌 4外链)') - component: Mapped[str | None] = mapped_column(String(255), default=None, comment='组件路径') - perms: Mapped[str | None] = mapped_column(String(100), default=None, comment='权限标识') + component: Mapped[str | None] = mapped_column(sa.String(255), default=None, comment='组件路径') + perms: Mapped[str | None] = mapped_column(sa.String(100), default=None, comment='权限标识') status: Mapped[int] = mapped_column(default=1, comment='菜单状态(0停用 1正常)') display: Mapped[int] = mapped_column(default=1, comment='是否显示(0否 1是)') cache: Mapped[int] = mapped_column(default=1, comment='是否缓存(0否 1是)') link: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='外链地址', - ) - remark: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='备注', + sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='外链地址' ) + remark: Mapped[str | None] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='备注') # 父级菜单一对多 parent_id: Mapped[int | None] = mapped_column( - BigInteger, - ForeignKey('sys_menu.id', ondelete='SET NULL'), - default=None, - index=True, - comment='父菜单ID', + sa.BigInteger, sa.ForeignKey('sys_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID' ) parent: Mapped[Menu | None] = relationship(init=False, back_populates='children', remote_side=[id]) children: Mapped[list[Menu] | None] = relationship(init=False, back_populates='parent') diff --git a/backend/app/admin/model/opera_log.py b/backend/app/admin/model/opera_log.py index 3d3d4615..ec0e39fd 100644 --- a/backend/app/admin/model/opera_log.py +++ b/backend/app/admin/model/opera_log.py @@ -1,8 +1,8 @@ from datetime import datetime -from sqlalchemy import String -from sqlalchemy.dialects.mysql import JSON, LONGTEXT -from sqlalchemy.dialects.postgresql import TEXT +import sqlalchemy as sa + +from sqlalchemy.dialects.mysql import LONGTEXT from sqlalchemy.orm import Mapped, mapped_column from backend.common.model import DataClassBase, TimeZone, id_key @@ -15,28 +15,25 @@ class OperaLog(DataClassBase): __tablename__ = 'sys_opera_log' id: Mapped[id_key] = mapped_column(init=False) - trace_id: Mapped[str] = mapped_column(String(32), comment='请求跟踪 ID') - username: Mapped[str | None] = mapped_column(String(20), comment='用户名') - method: Mapped[str] = mapped_column(String(20), comment='请求类型') - title: Mapped[str] = mapped_column(String(255), comment='操作模块') - path: Mapped[str] = mapped_column(String(500), comment='请求路径') - ip: Mapped[str] = mapped_column(String(50), comment='IP地址') - country: Mapped[str | None] = mapped_column(String(50), comment='国家') - region: Mapped[str | None] = mapped_column(String(50), comment='地区') - city: Mapped[str | None] = mapped_column(String(50), comment='城市') - user_agent: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='请求头') - os: Mapped[str | None] = mapped_column(String(50), comment='操作系统') - browser: Mapped[str | None] = mapped_column(String(50), comment='浏览器') - device: Mapped[str | None] = mapped_column(String(50), comment='设备') - args: Mapped[str | None] = mapped_column(JSON(), comment='请求参数') + trace_id: Mapped[str] = mapped_column(sa.String(32), comment='请求跟踪 ID') + username: Mapped[str | None] = mapped_column(sa.String(20), comment='用户名') + method: Mapped[str] = mapped_column(sa.String(20), comment='请求类型') + title: Mapped[str] = mapped_column(sa.String(255), comment='操作模块') + path: Mapped[str] = mapped_column(sa.String(500), comment='请求路径') + ip: Mapped[str] = mapped_column(sa.String(50), comment='IP地址') + country: Mapped[str | None] = mapped_column(sa.String(50), comment='国家') + region: Mapped[str | None] = mapped_column(sa.String(50), comment='地区') + city: Mapped[str | None] = mapped_column(sa.String(50), comment='城市') + user_agent: Mapped[str] = mapped_column(sa.String(255), comment='请求头') + os: Mapped[str | None] = mapped_column(sa.String(50), comment='操作系统') + browser: Mapped[str | None] = mapped_column(sa.String(50), comment='浏览器') + device: Mapped[str | None] = mapped_column(sa.String(50), comment='设备') + args: Mapped[str | None] = mapped_column(sa.JSON(), comment='请求参数') status: Mapped[int] = mapped_column(comment='操作状态(0异常 1正常)') - code: Mapped[str] = mapped_column(String(20), insert_default='200', comment='操作状态码') - msg: Mapped[str | None] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='提示消息') + code: Mapped[str] = mapped_column(sa.String(20), insert_default='200', comment='操作状态码') + msg: Mapped[str | None] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), comment='提示消息') cost_time: Mapped[float] = mapped_column(insert_default=0.0, comment='请求耗时(ms)') opera_time: Mapped[datetime] = mapped_column(TimeZone, comment='操作时间') created_time: Mapped[datetime] = mapped_column( - TimeZone, - init=False, - default_factory=timezone.now, - comment='创建时间', + TimeZone, init=False, default_factory=timezone.now, comment='创建时间' ) diff --git a/backend/app/admin/model/role.py b/backend/app/admin/model/role.py index 9647218f..7937a271 100644 --- a/backend/app/admin/model/role.py +++ b/backend/app/admin/model/role.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from sqlalchemy import Boolean, String -from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import INTEGER, TEXT +import sqlalchemy as sa + +from sqlalchemy.dialects.mysql import LONGTEXT, TINYINT from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.app.admin.model.m2m import sys_role_data_scope, sys_role_menu, sys_user_role @@ -20,18 +20,12 @@ class Role(Base): __tablename__ = 'sys_role' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(20), unique=True, comment='角色名称') + name: Mapped[str] = mapped_column(sa.String(20), unique=True, comment='角色名称') status: Mapped[int] = mapped_column(default=1, comment='角色状态(0停用 1正常)') is_filter_scopes: Mapped[bool] = mapped_column( - Boolean().with_variant(INTEGER, 'postgresql'), - default=True, - comment='过滤数据权限(0否 1是)', - ) - remark: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='备注', + sa.INTEGER().with_variant(TINYINT, 'mysql'), default=True, comment='过滤数据权限(0否 1是)' ) + remark: Mapped[str | None] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='备注') # 角色用户多对多 users: Mapped[list[User]] = relationship(init=False, secondary=sys_user_role, back_populates='roles') diff --git a/backend/app/admin/model/user.py b/backend/app/admin/model/user.py index 4b15f77c..2719ba99 100644 --- a/backend/app/admin/model/user.py +++ b/backend/app/admin/model/user.py @@ -3,8 +3,10 @@ from datetime import datetime from typing import TYPE_CHECKING -from sqlalchemy import VARBINARY, Boolean, ForeignKey, String -from sqlalchemy.dialects.postgresql import BYTEA, INTEGER +import sqlalchemy as sa + +from sqlalchemy.dialects.mysql import TINYINT +from sqlalchemy.dialects.postgresql import BYTEA from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.app.admin.model.m2m import sys_user_role @@ -22,43 +24,32 @@ class User(Base): __tablename__ = 'sys_user' id: Mapped[id_key] = mapped_column(init=False) - uuid: Mapped[str] = mapped_column(String(50), init=False, default_factory=uuid4_str, unique=True) - username: Mapped[str] = mapped_column(String(20), unique=True, index=True, comment='用户名') - nickname: Mapped[str] = mapped_column(String(20), comment='昵称') - password: Mapped[str | None] = mapped_column(String(255), comment='密码') - salt: Mapped[bytes | None] = mapped_column(VARBINARY(255).with_variant(BYTEA(255), 'postgresql'), comment='加密盐') - email: Mapped[str | None] = mapped_column(String(50), default=None, unique=True, index=True, comment='邮箱') - phone: Mapped[str | None] = mapped_column(String(11), default=None, comment='手机号') - avatar: Mapped[str | None] = mapped_column(String(255), default=None, comment='头像') + uuid: Mapped[str] = mapped_column(sa.String(50), init=False, default_factory=uuid4_str, unique=True) + username: Mapped[str] = mapped_column(sa.String(20), unique=True, index=True, comment='用户名') + nickname: Mapped[str] = mapped_column(sa.String(20), comment='昵称') + password: Mapped[str | None] = mapped_column(sa.String(255), comment='密码') + salt: Mapped[bytes | None] = mapped_column(BYTEA(255).with_variant(sa.VARBINARY, 'mysql'), comment='加密盐') + email: Mapped[str | None] = mapped_column(sa.String(50), default=None, unique=True, index=True, comment='邮箱') + phone: Mapped[str | None] = mapped_column(sa.String(11), default=None, comment='手机号') + avatar: Mapped[str | None] = mapped_column(sa.String(255), default=None, comment='头像') status: Mapped[int] = mapped_column(default=1, index=True, comment='用户账号状态(0停用 1正常)') is_superuser: Mapped[bool] = mapped_column( - Boolean().with_variant(INTEGER, 'postgresql'), - default=False, - comment='超级权限(0否 1是)', + sa.INTEGER().with_variant(TINYINT, 'mysql'), default=False, comment='超级权限(0否 1是)' ) is_staff: Mapped[bool] = mapped_column( - Boolean().with_variant(INTEGER, 'postgresql'), - default=False, - comment='后台管理登陆(0否 1是)', + sa.INTEGER().with_variant(TINYINT, 'mysql'), default=False, comment='后台管理登陆(0否 1是)' ) is_multi_login: Mapped[bool] = mapped_column( - Boolean().with_variant(INTEGER, 'postgresql'), - default=False, - comment='是否重复登陆(0否 1是)', + sa.INTEGER().with_variant(TINYINT, 'mysql'), default=False, comment='是否重复登陆(0否 1是)' ) join_time: Mapped[datetime] = mapped_column(TimeZone, init=False, default_factory=timezone.now, comment='注册时间') last_login_time: Mapped[datetime | None] = mapped_column( - TimeZone, - init=False, - onupdate=timezone.now, - comment='上次登录', + TimeZone, init=False, onupdate=timezone.now, comment='上次登录' ) # 部门用户一对多 dept_id: Mapped[int | None] = mapped_column( - ForeignKey('sys_dept.id', ondelete='SET NULL'), - default=None, - comment='部门关联ID', + sa.ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, comment='部门关联ID' ) dept: Mapped[Dept | None] = relationship(init=False, back_populates='users') diff --git a/backend/app/task/model/scheduler.py b/backend/app/task/model/scheduler.py index 2d6ce5f8..c36d59ce 100644 --- a/backend/app/task/model/scheduler.py +++ b/backend/app/task/model/scheduler.py @@ -2,14 +2,10 @@ from datetime import datetime -from sqlalchemy import ( - JSON, - Boolean, - String, - event, -) -from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import INTEGER, TEXT +import sqlalchemy as sa + +from sqlalchemy import event +from sqlalchemy.dialects.mysql import LONGTEXT, TINYINT from sqlalchemy.orm import Mapped, mapped_column from backend.common.exception import errors @@ -25,37 +21,29 @@ class TaskScheduler(Base): __tablename__ = 'task_scheduler' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(50), unique=True, comment='任务名称') - task: Mapped[str] = mapped_column(String(255), comment='要运行的 Celery 任务') - args: Mapped[str | None] = mapped_column(JSON(), comment='任务可接收的位置参数') - kwargs: Mapped[str | None] = mapped_column(JSON(), comment='任务可接收的关键字参数') - queue: Mapped[str | None] = mapped_column(String(255), comment='CELERY_TASK_QUEUES 中定义的队列') - exchange: Mapped[str | None] = mapped_column(String(255), comment='低级别 AMQP 路由的交换机') - routing_key: Mapped[str | None] = mapped_column(String(255), comment='低级别 AMQP 路由的路由密钥') + name: Mapped[str] = mapped_column(sa.String(50), unique=True, comment='任务名称') + task: Mapped[str] = mapped_column(sa.String(255), comment='要运行的 Celery 任务') + args: Mapped[str | None] = mapped_column(sa.JSON(), comment='任务可接收的位置参数') + kwargs: Mapped[str | None] = mapped_column(sa.JSON(), comment='任务可接收的关键字参数') + queue: Mapped[str | None] = mapped_column(sa.String(255), comment='CELERY_TASK_QUEUES 中定义的队列') + exchange: Mapped[str | None] = mapped_column(sa.String(255), comment='低级别 AMQP 路由的交换机') + routing_key: Mapped[str | None] = mapped_column(sa.String(255), comment='低级别 AMQP 路由的路由密钥') start_time: Mapped[datetime | None] = mapped_column(TimeZone, comment='任务开始触发的时间') expire_time: Mapped[datetime | None] = mapped_column(TimeZone, comment='任务不再触发的截止时间') expire_seconds: Mapped[int | None] = mapped_column(comment='任务不再触发的秒数时间差') type: Mapped[int] = mapped_column(comment='调度类型(0间隔 1定时)') interval_every: Mapped[int | None] = mapped_column(comment='任务再次运行前的间隔周期数') - interval_period: Mapped[str | None] = mapped_column(String(255), comment='任务运行之间的周期类型') - crontab: Mapped[str | None] = mapped_column(String(50), default='* * * * *', comment='任务运行的 Crontab 计划') + interval_period: Mapped[str | None] = mapped_column(sa.String(255), comment='任务运行之间的周期类型') + crontab: Mapped[str | None] = mapped_column(sa.String(50), default='* * * * *', comment='任务运行的 Crontab 计划') one_off: Mapped[bool] = mapped_column( - Boolean().with_variant(INTEGER, 'postgresql'), - default=False, - comment='是否仅运行一次', + sa.INTEGER().with_variant(TINYINT, 'mysql'), default=False, comment='是否仅运行一次' ) enabled: Mapped[bool] = mapped_column( - Boolean().with_variant(INTEGER, 'postgresql'), - default=True, - comment='是否启用任务', + sa.INTEGER().with_variant(TINYINT, 'mysql'), default=True, comment='是否启用任务' ) total_run_count: Mapped[int] = mapped_column(default=0, comment='任务触发的总次数') last_run_time: Mapped[datetime | None] = mapped_column(TimeZone, default=None, comment='任务最后触发的时间') - remark: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='备注', - ) + remark: Mapped[str | None] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='备注') no_changes: bool = False diff --git a/backend/plugin/code_generator/model/business.py b/backend/plugin/code_generator/model/business.py index b919102c..01db2d33 100644 --- a/backend/plugin/code_generator/model/business.py +++ b/backend/plugin/code_generator/model/business.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from sqlalchemy import String +import sqlalchemy as sa + from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import TEXT from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.common.model import Base, id_key @@ -19,21 +19,21 @@ class GenBusiness(Base): __tablename__ = 'gen_business' id: Mapped[id_key] = mapped_column(init=False) - app_name: Mapped[str] = mapped_column(String(50), comment='应用名称(英文)') - table_name: Mapped[str] = mapped_column(String(255), unique=True, comment='表名称(英文)') - doc_comment: Mapped[str] = mapped_column(String(255), comment='文档注释(用于函数/参数文档)') - table_comment: Mapped[str | None] = mapped_column(String(255), default=None, comment='表描述') + app_name: Mapped[str] = mapped_column(sa.String(50), comment='应用名称(英文)') + table_name: Mapped[str] = mapped_column(sa.String(255), unique=True, comment='表名称(英文)') + doc_comment: Mapped[str] = mapped_column(sa.String(255), comment='文档注释(用于函数/参数文档)') + table_comment: Mapped[str | None] = mapped_column(sa.String(255), default=None, comment='表描述') # relate_model_fk: Mapped[int | None] = mapped_column(default=None, comment='关联表外键') - class_name: Mapped[str | None] = mapped_column(String(50), default=None, comment='基础类名(默认为英文表名称)') - schema_name: Mapped[str | None] = mapped_column(String(50), default=None, comment='Schema 名称 (默认为英文表名称)') - filename: Mapped[str | None] = mapped_column(String(50), default=None, comment='基础文件名(默认为英文表名称)') + class_name: Mapped[str | None] = mapped_column(sa.String(50), default=None, comment='基础类名(默认为英文表名称)') + schema_name: Mapped[str | None] = mapped_column( + sa.String(50), default=None, comment='Schema 名称 (默认为英文表名称)' + ) + filename: Mapped[str | None] = mapped_column(sa.String(50), default=None, comment='基础文件名(默认为英文表名称)') default_datetime_column: Mapped[bool] = mapped_column(default=True, comment='是否存在默认时间列') - api_version: Mapped[str] = mapped_column(String(20), default='v1', comment='代码生成 api 版本,默认为 v1') - gen_path: Mapped[str | None] = mapped_column(String(255), default=None, comment='代码生成路径(默认为 app 根路径)') - remark: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='备注', + api_version: Mapped[str] = mapped_column(sa.String(20), default='v1', comment='代码生成 api 版本,默认为 v1') + gen_path: Mapped[str | None] = mapped_column( + sa.String(255), default=None, comment='代码生成路径(默认为 app 根路径)' ) + remark: Mapped[str | None] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='备注') # 代码生成业务模型列一对多 gen_column: Mapped[list[GenColumn]] = relationship(init=False, back_populates='gen_business') diff --git a/backend/plugin/code_generator/model/column.py b/backend/plugin/code_generator/model/column.py index e99adba1..e997b7dd 100644 --- a/backend/plugin/code_generator/model/column.py +++ b/backend/plugin/code_generator/model/column.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from sqlalchemy import BigInteger, ForeignKey, String +import sqlalchemy as sa + from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import TEXT from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.common.model import DataClassBase, id_key @@ -19,14 +19,12 @@ class GenColumn(DataClassBase): __tablename__ = 'gen_column' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(50), comment='列名称') - comment: Mapped[str | None] = mapped_column(String(255), default=None, comment='列描述') - type: Mapped[str] = mapped_column(String(20), default='String', comment='SQLA 模型列类型') - pd_type: Mapped[str] = mapped_column(String(20), default='str', comment='列类型对应的 pydantic 类型') + name: Mapped[str] = mapped_column(sa.String(50), comment='列名称') + comment: Mapped[str | None] = mapped_column(sa.String(255), default=None, comment='列描述') + type: Mapped[str] = mapped_column(sa.String(20), default='String', comment='SQLA 模型列类型') + pd_type: Mapped[str] = mapped_column(sa.String(20), default='str', comment='列类型对应的 pydantic 类型') default: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='列默认值', + sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='列默认值' ) sort: Mapped[int | None] = mapped_column(default=1, comment='列排序') length: Mapped[int] = mapped_column(default=0, comment='列长度') @@ -35,9 +33,6 @@ class GenColumn(DataClassBase): # 代码生成业务模型列一对多 gen_business_id: Mapped[int] = mapped_column( - BigInteger, - ForeignKey('gen_business.id', ondelete='CASCADE'), - default=0, - comment='代码生成业务ID', + sa.BigInteger, sa.ForeignKey('gen_business.id', ondelete='CASCADE'), default=0, comment='代码生成业务ID' ) gen_business: Mapped[GenBusiness | None] = relationship(init=False, back_populates='gen_column') diff --git a/backend/plugin/config/model/config.py b/backend/plugin/config/model/config.py index 3e29bfc3..4aea40b4 100644 --- a/backend/plugin/config/model/config.py +++ b/backend/plugin/config/model/config.py @@ -1,6 +1,6 @@ -from sqlalchemy import Boolean, String -from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import INTEGER, TEXT +import sqlalchemy as sa + +from sqlalchemy.dialects.mysql import LONGTEXT, TINYINT from sqlalchemy.orm import Mapped, mapped_column from backend.common.model import Base, id_key @@ -12,17 +12,11 @@ class Config(Base): __tablename__ = 'sys_config' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(20), comment='名称') - type: Mapped[str | None] = mapped_column(String(20), server_default=None, comment='类型') - key: Mapped[str] = mapped_column(String(50), unique=True, comment='键名') - value: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='键值') + name: Mapped[str] = mapped_column(sa.String(20), comment='名称') + type: Mapped[str | None] = mapped_column(sa.String(20), server_default=None, comment='类型') + key: Mapped[str] = mapped_column(sa.String(50), unique=True, comment='键名') + value: Mapped[str] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), comment='键值') is_frontend: Mapped[bool] = mapped_column( - Boolean().with_variant(INTEGER, 'postgresql'), - default=False, - comment='是否前端', - ) - remark: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='备注', + sa.INTEGER().with_variant(TINYINT, 'mysql'), default=False, comment='是否前端' ) + remark: Mapped[str | None] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='备注') diff --git a/backend/plugin/dict/model/dict_data.py b/backend/plugin/dict/model/dict_data.py index 98b27a75..e0b7e4a5 100644 --- a/backend/plugin/dict/model/dict_data.py +++ b/backend/plugin/dict/model/dict_data.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from sqlalchemy import ForeignKey, String +import sqlalchemy as sa + from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import TEXT from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.common.model import Base, id_key @@ -19,22 +19,16 @@ class DictData(Base): __tablename__ = 'sys_dict_data' id: Mapped[id_key] = mapped_column(init=False) - type_code: Mapped[str] = mapped_column(String(32), comment='对应的字典类型编码') - label: Mapped[str] = mapped_column(String(32), comment='字典标签') - value: Mapped[str] = mapped_column(String(32), comment='字典值') - color: Mapped[str | None] = mapped_column(String(32), default=None, comment='标签颜色') + type_code: Mapped[str] = mapped_column(sa.String(32), comment='对应的字典类型编码') + label: Mapped[str] = mapped_column(sa.String(32), comment='字典标签') + value: Mapped[str] = mapped_column(sa.String(32), comment='字典值') + color: Mapped[str | None] = mapped_column(sa.String(32), default=None, comment='标签颜色') sort: Mapped[int] = mapped_column(default=0, comment='排序') status: Mapped[int] = mapped_column(default=1, comment='状态(0停用 1正常)') - remark: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='备注', - ) + remark: Mapped[str | None] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='备注') # 字典类型一对多 type_id: Mapped[int] = mapped_column( - ForeignKey('sys_dict_type.id', ondelete='CASCADE'), - default=0, - comment='字典类型关联ID', + sa.ForeignKey('sys_dict_type.id', ondelete='CASCADE'), default=0, comment='字典类型关联ID' ) type: Mapped[DictType] = relationship(init=False, back_populates='datas') diff --git a/backend/plugin/dict/model/dict_type.py b/backend/plugin/dict/model/dict_type.py index e181d4cc..7a1444bb 100644 --- a/backend/plugin/dict/model/dict_type.py +++ b/backend/plugin/dict/model/dict_type.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from sqlalchemy import String +import sqlalchemy as sa + from sqlalchemy.dialects.mysql import LONGTEXT -from sqlalchemy.dialects.postgresql import TEXT from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.common.model import Base, id_key @@ -19,13 +19,9 @@ class DictType(Base): __tablename__ = 'sys_dict_type' id: Mapped[id_key] = mapped_column(init=False) - name: Mapped[str] = mapped_column(String(32), comment='字典类型名称') - code: Mapped[str] = mapped_column(String(32), unique=True, comment='字典类型编码') - remark: Mapped[str | None] = mapped_column( - LONGTEXT().with_variant(TEXT, 'postgresql'), - default=None, - comment='备注', - ) + name: Mapped[str] = mapped_column(sa.String(32), comment='字典类型名称') + code: Mapped[str] = mapped_column(sa.String(32), unique=True, comment='字典类型编码') + remark: Mapped[str | None] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), default=None, comment='备注') # 字典类型一对多 datas: Mapped[list[DictData]] = relationship(init=False, back_populates='type') diff --git a/backend/plugin/notice/model/notice.py b/backend/plugin/notice/model/notice.py index 5de70136..3ccea51b 100644 --- a/backend/plugin/notice/model/notice.py +++ b/backend/plugin/notice/model/notice.py @@ -1,4 +1,5 @@ -from sqlalchemy import TEXT, String +import sqlalchemy as sa + from sqlalchemy.dialects.mysql import LONGTEXT from sqlalchemy.orm import Mapped, mapped_column @@ -11,7 +12,7 @@ class Notice(Base): __tablename__ = 'sys_notice' id: Mapped[id_key] = mapped_column(init=False) - title: Mapped[str] = mapped_column(String(50), comment='标题') + title: Mapped[str] = mapped_column(sa.String(50), comment='标题') type: Mapped[int] = mapped_column(comment='类型(0:通知、1:公告)') status: Mapped[int] = mapped_column(comment='状态(0:隐藏、1:显示)') - content: Mapped[str] = mapped_column(LONGTEXT().with_variant(TEXT, 'postgresql'), comment='内容') + content: Mapped[str] = mapped_column(sa.TEXT().with_variant(LONGTEXT, 'mysql'), comment='内容') diff --git a/backend/plugin/oauth2/model/user_social.py b/backend/plugin/oauth2/model/user_social.py index 452296d4..c6a0b55d 100644 --- a/backend/plugin/oauth2/model/user_social.py +++ b/backend/plugin/oauth2/model/user_social.py @@ -2,7 +2,8 @@ from typing import TYPE_CHECKING -from sqlalchemy import BigInteger, ForeignKey, String +import sqlalchemy as sa + from sqlalchemy.orm import Mapped, mapped_column, relationship from backend.common.model import Base, id_key @@ -17,13 +18,11 @@ class UserSocial(Base): __tablename__ = 'sys_user_social' id: Mapped[id_key] = mapped_column(init=False) - sid: Mapped[str] = mapped_column(String(255), comment='第三方用户 ID') - source: Mapped[str] = mapped_column(String(20), comment='第三方用户来源') + sid: Mapped[str] = mapped_column(sa.String(255), comment='第三方用户 ID') + source: Mapped[str] = mapped_column(sa.String(20), comment='第三方用户来源') # 用户社交信息一对多 user_id: Mapped[int] = mapped_column( - BigInteger, - ForeignKey('sys_user.id', ondelete='CASCADE'), - comment='用户关联ID', + sa.BigInteger, sa.ForeignKey('sys_user.id', ondelete='CASCADE'), comment='用户关联ID' ) user: Mapped[User | None] = relationship(init=False, backref='socials') diff --git a/docker-compose.yml b/docker-compose.yml index ae624162..b0e4cf7b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,41 +48,41 @@ services: supervisord -c /etc/supervisor/supervisord.conf supervisorctl restart - fba_mysql: - image: mysql:8.0.41 + fba_postgres: + image: postgres:16 ports: - - "${DOCKER_MYSQL_MAP_PORT:-3306}:3306" - container_name: fba_mysql + - "${DOCKER_POSTGRES_MAP_PORT:-5432}:5432" + container_name: fba_postgres restart: always environment: - MYSQL_DATABASE: fba - MYSQL_ROOT_PASSWORD: 123456 + POSTGRES_DB: fba + POSTGRES_PASSWORD: 123456 TZ: Asia/Shanghai volumes: - - fba_mysql:/var/lib/mysql + - fba_postgres:/var/lib/postgresql/data networks: - fba_network - command: - --default-authentication-plugin=mysql_native_password - --character-set-server=utf8mb4 - --collation-server=utf8mb4_general_ci - --lower_case_table_names=1 - # # 如果你是 postgres 用户,应保留 fba_postgres 容器脚本并删除 fba_mysql 容器脚本 - # fba_postgres: - # image: postgres:16 - # ports: - # - "${DOCKER_POSTGRES_MAP_PORT:-5432}:5432" - # container_name: fba_postgres - # restart: always - # environment: - # POSTGRES_DB: fba - # POSTGRES_PASSWORD: 123456 - # TZ: Asia/Shanghai - # volumes: - # - fba_postgres:/var/lib/postgresql/data - # networks: - # - fba_network +# # 如果你是 mysql 用户,应保留 fba_mysql 容器脚本并删除 fba_postgres 容器脚本 +# fba_mysql: +# image: mysql:8.0.41 +# ports: +# - "${DOCKER_MYSQL_MAP_PORT:-3306}:3306" +# container_name: fba_mysql +# restart: always +# environment: +# MYSQL_DATABASE: fba +# MYSQL_ROOT_PASSWORD: 123456 +# TZ: Asia/Shanghai +# volumes: +# - fba_mysql:/var/lib/mysql +# networks: +# - fba_network +# command: +# --default-authentication-plugin=mysql_native_password +# --character-set-server=utf8mb4 +# --collation-server=utf8mb4_general_ci +# --lower_case_table_names=1 fba_redis: image: redis