From d3b646198b689f1bafe1872663342ec871da4110 Mon Sep 17 00:00:00 2001
From: downdawn <1436759077@qq.com>
Date: Sun, 15 Jun 2025 16:02:44 +0800
Subject: [PATCH 01/10] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DPostgreSQL=20SQL?=
=?UTF-8?q?=E8=AF=AD=E6=B3=95=E9=94=99=E8=AF=AF=EF=BC=8C=E5=B0=86=E5=8F=8D?=
=?UTF-8?q?=E5=BC=95=E5=8F=B7=E6=9B=BF=E6=8D=A2=E4=B8=BA=E5=8F=8C=E5=BC=95?=
=?UTF-8?q?=E5=8F=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/sql/postgresql/init_test_data.sql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/sql/postgresql/init_test_data.sql b/backend/sql/postgresql/init_test_data.sql
index eadfb8cec..107709bb7 100644
--- a/backend/sql/postgresql/init_test_data.sql
+++ b/backend/sql/postgresql/init_test_data.sql
@@ -103,7 +103,7 @@ insert into sys_data_scope (id, name, status, created_time, updated_time)
values (1, '测试部门数据权限', 1, '2025-06-09 16:53:29', null),
(2, '测试部门及以下数据权限', 1, '2025-06-09 16:53:40', null);
-insert into sys_data_rule (id, name, model, `column`, operator, expression, `value`, created_time, updated_time)
+insert into sys_data_rule (id, name, model, "column", operator, expression, "value", created_time, updated_time)
values (1, '部门名称等于测试', '部门', 'name', 1, 0, '测试', '2025-06-09 16:56:06', null),
(2, '父部门 ID 等于 1', '部门', 'parent_id', 0, 0, '1', '2025-06-09 17:16:14', null);
From e8dfd9c248ab4c95c271d2ad496ce7218c9ed04b Mon Sep 17 00:00:00 2001
From: downdawn <1436759077@qq.com>
Date: Sun, 15 Jun 2025 18:03:14 +0800
Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=9B=AA?=
=?UTF-8?q?=E8=8A=B1=E7=AE=97=E6=B3=95ID=E5=AE=9E=E7=8E=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/common/model.py | 12 ++-
backend/utils/snowflake.py | 161 +++++++++++++++++++++++++++++++++++++
2 files changed, 172 insertions(+), 1 deletion(-)
create mode 100644 backend/utils/snowflake.py
diff --git a/backend/common/model.py b/backend/common/model.py
index f006b506a..bd95e56a8 100644
--- a/backend/common/model.py
+++ b/backend/common/model.py
@@ -3,10 +3,11 @@
from datetime import datetime
from typing import Annotated
-from sqlalchemy import DateTime
+from sqlalchemy import BigInteger, DateTime
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy.orm import DeclarativeBase, Mapped, MappedAsDataclass, declared_attr, mapped_column
+from backend.utils.snowflake import snowflake
from backend.utils.timezone import timezone
# 通用 Mapped 类型主键, 需手动添加,参考以下使用方式
@@ -17,6 +18,15 @@
]
+# 雪花算法ID,使用方法同 id_key
+snowflake_id_key = Annotated[
+ int,
+ mapped_column(
+ BigInteger, primary_key=True, index=True, default=snowflake.generate, sort_order=-999, comment='雪花算法主键 ID'
+ ),
+]
+
+
# Mixin: 一种面向对象编程概念, 使结构变得更加清晰, `Wiki `__
class UserMixin(MappedAsDataclass):
"""用户 Mixin 数据类"""
diff --git a/backend/utils/snowflake.py b/backend/utils/snowflake.py
new file mode 100644
index 000000000..84054a0f8
--- /dev/null
+++ b/backend/utils/snowflake.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Twitter雪花算法(Snowflake)的Python实现
+用于生成分布式唯一ID
+
+ID结构(64位):
+- 1位符号位,固定为0
+- 41位时间戳(毫秒级)
+- 5位数据中心ID
+- 5位机器ID
+- 12位序列号
+
+https://github.com/twitter-archive/snowflake/blob/snowflake-2010/src/main/scala/com/twitter/service/snowflake/IdWorker.scala
+"""
+
+import time
+
+from dataclasses import dataclass
+
+
+class SnowflakeException(Exception):
+ """雪花算法基础异常类"""
+
+ pass
+
+
+class InvalidSystemClock(SnowflakeException):
+ """时钟回拨异常"""
+
+ pass
+
+
+class InvalidWorkerId(SnowflakeException):
+ """无效的工作节点ID异常"""
+
+ pass
+
+
+class InvalidDatacenterId(SnowflakeException):
+ """无效的数据中心ID异常"""
+
+ pass
+
+
+@dataclass
+class SnowflakeConfig:
+ """雪花算法配置类"""
+
+ # 64位ID的划分
+ WORKER_ID_BITS: int = 5
+ DATACENTER_ID_BITS: int = 5
+ SEQUENCE_BITS: int = 12
+
+ # 最大取值计算
+ MAX_WORKER_ID: int = -1 ^ (-1 << WORKER_ID_BITS) # 2**5-1 0b11111
+ MAX_DATACENTER_ID: int = -1 ^ (-1 << DATACENTER_ID_BITS)
+
+ # 移位偏移计算
+ WORKER_ID_SHIFT: int = SEQUENCE_BITS
+ DATACENTER_ID_SHIFT: int = SEQUENCE_BITS + WORKER_ID_BITS
+ TIMESTAMP_LEFT_SHIFT: int = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS
+
+ # 序号循环掩码
+ SEQUENCE_MASK: int = -1 ^ (-1 << SEQUENCE_BITS)
+
+ # 元年时间戳, 2010-01-01 00:00:00
+ EPOCH: int = 1262275200000
+
+ # 默认配置
+ DEFAULT_DATACENTER_ID: int = 1
+ DEFAULT_WORKER_ID: int = 0
+ DEFAULT_SEQUENCE: int = 0
+
+
+class Snowflake:
+ """雪花算法ID生成器"""
+
+ def __init__(
+ self,
+ datacenter_id: int = SnowflakeConfig.DEFAULT_DATACENTER_ID,
+ worker_id: int = SnowflakeConfig.DEFAULT_WORKER_ID,
+ sequence: int = SnowflakeConfig.DEFAULT_SEQUENCE,
+ ):
+ """
+ 初始化ID生成器
+
+ :param datacenter_id: 数据中心ID (0-31)
+ :param worker_id: 工作节点ID (0-31)
+ :param sequence: 起始序列号
+ """
+ if not 0 <= worker_id <= SnowflakeConfig.MAX_WORKER_ID:
+ raise InvalidWorkerId(f'工作节点ID必须在0-{SnowflakeConfig.MAX_WORKER_ID}之间')
+
+ if not 0 <= datacenter_id <= SnowflakeConfig.MAX_DATACENTER_ID:
+ raise InvalidDatacenterId(f'数据中心ID必须在0-{SnowflakeConfig.MAX_DATACENTER_ID}之间')
+
+ self.worker_id = worker_id
+ self.datacenter_id = datacenter_id
+ self.sequence = sequence
+ self.last_timestamp = -1
+
+ def __call__(self) -> int:
+ """使实例可调用,用于default_factory"""
+ return self.generate()
+
+ @staticmethod
+ def _gen_timestamp() -> int:
+ """
+ 生成当前时间戳,单位:毫秒
+
+ :return: 当前时间戳
+ """
+ return int(time.time() * 1000)
+
+ def _til_next_millis(self, last_timestamp: int) -> int:
+ """
+ 等待到下一个毫秒
+
+ :param last_timestamp: 上次生成ID的时间戳
+ :return: 下一个毫秒的时间戳
+ """
+ timestamp = self._gen_timestamp()
+ while timestamp <= last_timestamp:
+ timestamp = self._gen_timestamp()
+ return timestamp
+
+ def generate(self) -> int:
+ """
+ 获取新的唯一ID,单位:毫秒
+
+ :return: 新的唯一ID
+ """
+ timestamp = self._gen_timestamp()
+
+ if timestamp < self.last_timestamp:
+ raise InvalidSystemClock(f'系统时钟回拨,拒绝生成ID直到 {self.last_timestamp}')
+
+ if timestamp == self.last_timestamp:
+ self.sequence = (self.sequence + 1) & SnowflakeConfig.SEQUENCE_MASK
+ if self.sequence == 0:
+ timestamp = self._til_next_millis(self.last_timestamp)
+ else:
+ self.sequence = 0
+
+ self.last_timestamp = timestamp
+
+ new_id = (
+ ((timestamp - SnowflakeConfig.EPOCH) << SnowflakeConfig.TIMESTAMP_LEFT_SHIFT)
+ | (self.datacenter_id << SnowflakeConfig.DATACENTER_ID_SHIFT)
+ | (self.worker_id << SnowflakeConfig.WORKER_ID_SHIFT)
+ | self.sequence
+ )
+ return new_id
+
+
+snowflake = Snowflake()
+
+if __name__ == '__main__':
+ for _ in range(10):
+ print(snowflake.generate())
From c22d55e98b465de10533fd26ece8a26293f8fe69 Mon Sep 17 00:00:00 2001
From: Wu Clan
Date: Sun, 15 Jun 2025 23:37:42 +0800
Subject: [PATCH 03/10] =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=9B=AA=E8=8A=B1?=
=?UTF-8?q?=E7=AE=97=E6=B3=95=E5=92=8C=E4=B8=BB=E9=94=AE=E7=B1=BB=E5=9E=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/app/admin/model/dept.py | 4 +-
backend/app/admin/model/login_log.py | 4 +-
backend/app/admin/model/m2m.py | 26 +-
backend/app/admin/model/menu.py | 4 +-
backend/common/model.py | 21 +-
backend/plugin/code_generator/model/column.py | 4 +-
backend/plugin/oauth2/model/user_social.py | 6 +-
backend/sql/mysql/create_tables.sql | 224 ++++----
backend/sql/postgresql/create_tables.sql | 511 +++++++++++-------
backend/utils/snowflake.py | 131 ++---
10 files changed, 516 insertions(+), 419 deletions(-)
diff --git a/backend/app/admin/model/dept.py b/backend/app/admin/model/dept.py
index f85fa407e..22996391c 100644
--- a/backend/app/admin/model/dept.py
+++ b/backend/app/admin/model/dept.py
@@ -4,7 +4,7 @@
from typing import TYPE_CHECKING, Optional
-from sqlalchemy import Boolean, ForeignKey, String
+from sqlalchemy import BigInteger, Boolean, ForeignKey, String
from sqlalchemy.dialects.postgresql import INTEGER
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -32,7 +32,7 @@ class Dept(Base):
# 父级部门一对多
parent_id: Mapped[int | None] = mapped_column(
- ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, index=True, comment='父部门ID'
+ BigInteger, ForeignKey('sys_dept.id', ondelete='SET NULL'), default=None, index=True, comment='父部门ID'
)
parent: Mapped[Optional['Dept']] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[Optional[list['Dept']]] = 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 757bd93f2..13a717f62 100644
--- a/backend/app/admin/model/login_log.py
+++ b/backend/app/admin/model/login_log.py
@@ -7,7 +7,7 @@
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column
-from backend.common.model import DataClassBase, id_key
+from backend.common.model import DataClassBase, snowflake_id_key
from backend.utils.timezone import timezone
@@ -16,7 +16,7 @@ class LoginLog(DataClassBase):
__tablename__ = 'sys_login_log'
- id: Mapped[id_key] = mapped_column(init=False)
+ id: Mapped[snowflake_id_key] = mapped_column(init=False)
user_uuid: Mapped[str] = mapped_column(String(50), comment='用户UUID')
username: Mapped[str] = mapped_column(String(20), comment='用户名')
status: Mapped[int] = mapped_column(insert_default=0, comment='登录状态(0失败 1成功)')
diff --git a/backend/app/admin/model/m2m.py b/backend/app/admin/model/m2m.py
index bfb66db4a..3d9c09f3a 100644
--- a/backend/app/admin/model/m2m.py
+++ b/backend/app/admin/model/m2m.py
@@ -1,33 +1,33 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-from sqlalchemy import INT, Column, ForeignKey, Integer, Table
+from sqlalchemy import BigInteger, Column, ForeignKey, Table
from backend.common.model import MappedBase
sys_user_role = Table(
'sys_user_role',
MappedBase.metadata,
- Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
- Column('user_id', Integer, ForeignKey('sys_user.id', ondelete='CASCADE'), primary_key=True, comment='用户ID'),
- Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'),
+ 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'),
)
sys_role_menu = Table(
'sys_role_menu',
MappedBase.metadata,
- Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
- Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色ID'),
- Column('menu_id', Integer, ForeignKey('sys_menu.id', ondelete='CASCADE'), primary_key=True, comment='菜单ID'),
+ 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'),
)
sys_role_data_scope = Table(
'sys_role_data_scope',
MappedBase.metadata,
- Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键 ID'),
- Column('role_id', Integer, ForeignKey('sys_role.id', ondelete='CASCADE'), primary_key=True, comment='角色 ID'),
+ 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(
'data_scope_id',
- Integer,
+ BigInteger,
ForeignKey('sys_data_scope.id', ondelete='CASCADE'),
primary_key=True,
comment='数据范围 ID',
@@ -37,17 +37,17 @@
sys_data_scope_rule = Table(
'sys_data_scope_rule',
MappedBase.metadata,
- Column('id', INT, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
+ Column('id', BigInteger, primary_key=True, unique=True, index=True, autoincrement=True, comment='主键ID'),
Column(
'data_scope_id',
- Integer,
+ BigInteger,
ForeignKey('sys_data_scope.id', ondelete='CASCADE'),
primary_key=True,
comment='数据范围 ID',
),
Column(
'data_rule_id',
- Integer,
+ BigInteger,
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 d565eeeb4..8d63a8364 100644
--- a/backend/app/admin/model/menu.py
+++ b/backend/app/admin/model/menu.py
@@ -4,7 +4,7 @@
from typing import TYPE_CHECKING, Optional
-from sqlalchemy import ForeignKey, String
+from sqlalchemy import BigInteger, ForeignKey, String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -42,7 +42,7 @@ class Menu(Base):
# 父级菜单一对多
parent_id: Mapped[int | None] = mapped_column(
- ForeignKey('sys_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID'
+ BigInteger, ForeignKey('sys_menu.id', ondelete='SET NULL'), default=None, index=True, comment='父菜单ID'
)
parent: Mapped[Optional['Menu']] = relationship(init=False, back_populates='children', remote_side=[id])
children: Mapped[Optional[list['Menu']]] = relationship(init=False, back_populates='parent')
diff --git a/backend/common/model.py b/backend/common/model.py
index bd95e56a8..5ab0407f6 100644
--- a/backend/common/model.py
+++ b/backend/common/model.py
@@ -14,15 +14,30 @@
# MappedBase -> id: Mapped[id_key]
# DataClassBase && Base -> id: Mapped[id_key] = mapped_column(init=False)
id_key = Annotated[
- int, mapped_column(primary_key=True, index=True, autoincrement=True, sort_order=-999, comment='主键 ID')
+ int,
+ mapped_column(
+ BigInteger,
+ primary_key=True,
+ unique=True,
+ index=True,
+ autoincrement=True,
+ sort_order=-999,
+ comment='主键 ID',
+ ),
]
-# 雪花算法ID,使用方法同 id_key
+# 雪花算法 Mapped 类型主键,使用方法与 id_key 相同
snowflake_id_key = Annotated[
int,
mapped_column(
- BigInteger, primary_key=True, index=True, default=snowflake.generate, sort_order=-999, comment='雪花算法主键 ID'
+ BigInteger,
+ primary_key=True,
+ unique=True,
+ index=True,
+ default=snowflake.generate,
+ sort_order=-999,
+ comment='雪花算法主键 ID',
),
]
diff --git a/backend/plugin/code_generator/model/column.py b/backend/plugin/code_generator/model/column.py
index 5bb9a424d..1357aa0f3 100644
--- a/backend/plugin/code_generator/model/column.py
+++ b/backend/plugin/code_generator/model/column.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
from typing import TYPE_CHECKING, Union
-from sqlalchemy import ForeignKey, String
+from sqlalchemy import BigInteger, ForeignKey, String
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -33,6 +33,6 @@ class GenColumn(DataClassBase):
# 代码生成业务模型一对多
gen_business_id: Mapped[int] = mapped_column(
- ForeignKey('gen_business.id', ondelete='CASCADE'), default=0, comment='代码生成业务ID'
+ BigInteger, ForeignKey('gen_business.id', ondelete='CASCADE'), default=0, comment='代码生成业务ID'
)
gen_business: Mapped[Union['GenBusiness', None]] = relationship(init=False, back_populates='gen_column')
diff --git a/backend/plugin/oauth2/model/user_social.py b/backend/plugin/oauth2/model/user_social.py
index 1e18e10a9..c01a46ae0 100644
--- a/backend/plugin/oauth2/model/user_social.py
+++ b/backend/plugin/oauth2/model/user_social.py
@@ -4,7 +4,7 @@
from typing import TYPE_CHECKING
-from sqlalchemy import ForeignKey, String
+from sqlalchemy import BigInteger, ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column, relationship
from backend.common.model import Base, id_key
@@ -23,5 +23,7 @@ class UserSocial(Base):
source: Mapped[str] = mapped_column(String(20), comment='第三方用户来源')
# 用户社交信息一对多
- user_id: Mapped[int] = mapped_column(ForeignKey('sys_user.id', ondelete='CASCADE'), comment='用户关联ID')
+ user_id: Mapped[int] = mapped_column(
+ BigInteger, ForeignKey('sys_user.id', ondelete='CASCADE'), comment='用户关联ID'
+ )
user: Mapped[User | None] = relationship(init=False, backref='socials')
diff --git a/backend/sql/mysql/create_tables.sql b/backend/sql/mysql/create_tables.sql
index 02e3a48b6..16d5d3d72 100644
--- a/backend/sql/mysql/create_tables.sql
+++ b/backend/sql/mysql/create_tables.sql
@@ -1,6 +1,6 @@
create table gen_business
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
app_name varchar(50) not null comment '应用名称(英文)',
table_name varchar(255) not null comment '表名称(英文)',
@@ -15,17 +15,16 @@ create table gen_business
remark longtext null comment '备注',
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
+ constraint ix_gen_business_id
+ unique (id),
constraint table_name
unique (table_name)
)
comment '代码生成业务表';
-create index ix_gen_business_id
- on gen_business (id);
-
create table gen_column
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
name varchar(50) not null comment '列名称',
comment varchar(255) null comment '列描述',
@@ -36,7 +35,9 @@ create table gen_column
length int not null comment '列长度',
is_pk tinyint(1) not null comment '是否主键',
is_nullable tinyint(1) not null comment '是否可为空',
- gen_business_id int not null comment '代码生成业务ID',
+ gen_business_id bigint not null comment '代码生成业务ID',
+ constraint ix_gen_column_id
+ unique (id),
constraint gen_column_ibfk_1
foreign key (gen_business_id) references gen_business (id)
on delete cascade
@@ -46,12 +47,9 @@ create table gen_column
create index gen_business_id
on gen_column (gen_business_id);
-create index ix_gen_column_id
- on gen_column (id);
-
create table sys_config
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
name varchar(20) not null comment '名称',
type varchar(20) null comment '类型',
@@ -61,30 +59,16 @@ create table sys_config
remark longtext null comment '备注',
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
+ constraint ix_sys_config_id
+ unique (id),
constraint `key`
unique (`key`)
)
comment '参数配置表';
-create index ix_sys_config_id
- on sys_config (id);
-
-create table sys_data_scope
-(
- id int auto_increment comment '主键 ID'
- primary key,
- name varchar(50) not null comment '名称',
- status int not null comment '状态(0停用 1正常)',
- created_time datetime not null comment '创建时间',
- updated_time datetime null comment '更新时间',
- constraint name
- unique (name)
-)
- comment '数据范围表';
-
create table sys_data_rule
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
name varchar(500) not null comment '名称',
model varchar(50) not null comment 'SQLA 模型名,对应 DATA_PERMISSION_MODELS 键名',
@@ -92,29 +76,55 @@ create table sys_data_rule
operator int not null comment '运算符(0:and、1:or)',
expression int not null comment '表达式(0:==、1:!=、2:>、3:>=、4:<、5:<=、6:in、7:not_in)',
value varchar(255) not null comment '规则值',
- scope_id int null comment '数据范围关联 ID',
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
+ constraint ix_sys_data_rule_id
+ unique (id),
constraint name
- unique (name),
- constraint sys_data_rule_ibfk_1
- foreign key (scope_id) references sys_data_scope (id)
- on delete set null
+ unique (name)
)
comment '数据规则表';
-create index ix_sys_data_rule_id
- on sys_data_rule (id);
+create table sys_data_scope
+(
+ id bigint auto_increment comment '主键 ID'
+ primary key,
+ name varchar(50) not null comment '名称',
+ status int not null comment '状态(0停用 1正常)',
+ created_time datetime not null comment '创建时间',
+ updated_time datetime null comment '更新时间',
+ constraint ix_sys_data_scope_id
+ unique (id),
+ constraint name
+ unique (name)
+)
+ comment '数据范围表';
+
+create table sys_data_scope_rule
+(
+ id bigint auto_increment comment '主键ID',
+ data_scope_id bigint not null comment '数据范围 ID',
+ data_rule_id bigint not null comment '数据规则 ID',
+ primary key (id, data_scope_id, data_rule_id),
+ constraint ix_sys_data_scope_rule_id
+ unique (id),
+ constraint sys_data_scope_rule_ibfk_1
+ foreign key (data_scope_id) references sys_data_scope (id)
+ on delete cascade,
+ constraint sys_data_scope_rule_ibfk_2
+ foreign key (data_rule_id) references sys_data_rule (id)
+ on delete cascade
+);
-create index scope_id
- on sys_data_rule (scope_id);
+create index data_rule_id
+ on sys_data_scope_rule (data_rule_id);
-create index ix_sys_data_scope_id
- on sys_data_scope (id);
+create index data_scope_id
+ on sys_data_scope_rule (data_scope_id);
create table sys_dept
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
name varchar(50) not null comment '部门名称',
sort int not null comment '排序',
@@ -123,24 +133,23 @@ create table sys_dept
email varchar(50) null comment '邮箱',
status int not null comment '部门状态(0停用 1正常)',
del_flag tinyint(1) not null comment '删除标志(0删除 1存在)',
- parent_id int null comment '父部门ID',
+ parent_id bigint null comment '父部门ID',
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
+ constraint ix_sys_dept_id
+ unique (id),
constraint sys_dept_ibfk_1
foreign key (parent_id) references sys_dept (id)
on delete set null
)
comment '部门表';
-create index ix_sys_dept_id
- on sys_dept (id);
-
create index ix_sys_dept_parent_id
on sys_dept (parent_id);
create table sys_dict_type
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
name varchar(32) not null comment '字典类型名称',
code varchar(32) not null comment '字典类型编码',
@@ -149,22 +158,26 @@ create table sys_dict_type
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
constraint code
- unique (code)
+ unique (code),
+ constraint ix_sys_dict_type_id
+ unique (id)
)
comment '字典类型表';
create table sys_dict_data
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
label varchar(32) not null comment '字典标签',
value varchar(32) not null comment '字典值',
sort int not null comment '排序',
status int not null comment '状态(0停用 1正常)',
remark longtext null comment '备注',
- type_id int not null comment '字典类型关联ID',
+ type_id bigint not null comment '字典类型关联ID',
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
+ constraint ix_sys_dict_data_id
+ unique (id),
constraint label
unique (label),
constraint sys_dict_data_ibfk_1
@@ -173,18 +186,12 @@ create table sys_dict_data
)
comment '字典数据表';
-create index ix_sys_dict_data_id
- on sys_dict_data (id);
-
create index type_id
on sys_dict_data (type_id);
-create index ix_sys_dict_type_id
- on sys_dict_type (id);
-
create table sys_login_log
(
- id int auto_increment comment '主键 ID'
+ id bigint not null comment '雪花算法主键 ID'
primary key,
user_uuid varchar(50) not null comment '用户UUID',
username varchar(20) not null comment '用户名',
@@ -199,23 +206,22 @@ create table sys_login_log
device varchar(50) null comment '设备',
msg longtext not null comment '提示消息',
login_time datetime not null comment '登录时间',
- created_time datetime not null comment '创建时间'
+ created_time datetime not null comment '创建时间',
+ constraint ix_sys_login_log_id
+ unique (id)
)
comment '登录日志表';
-create index ix_sys_login_log_id
- on sys_login_log (id);
-
create table sys_menu
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
title varchar(50) not null comment '菜单标题',
name varchar(50) not null comment '菜单名称',
- path varchar(200) not null comment '路由地址',
+ path varchar(200) null comment '路由地址',
sort int not null comment '排序',
icon varchar(100) null comment '菜单图标',
- type int not null comment '菜单类型(0目录 1菜单 2按钮)',
+ type int not null comment '菜单类型(0目录 1菜单 2按钮 3内嵌 4外链)',
component varchar(255) null comment '组件路径',
perms varchar(100) null comment '权限标识',
status int not null comment '菜单状态(0停用 1正常)',
@@ -223,24 +229,23 @@ create table sys_menu
cache int not null comment '是否缓存(0否 1是)',
link longtext null comment '外链地址',
remark longtext null comment '备注',
- parent_id int null comment '父菜单ID',
+ parent_id bigint null comment '父菜单ID',
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
+ constraint ix_sys_menu_id
+ unique (id),
constraint sys_menu_ibfk_1
foreign key (parent_id) references sys_menu (id)
on delete set null
)
comment '菜单表';
-create index ix_sys_menu_id
- on sys_menu (id);
-
create index ix_sys_menu_parent_id
on sys_menu (parent_id);
create table sys_notice
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
title varchar(50) not null comment '标题',
type int not null comment '类型(0:通知、1:公告)',
@@ -249,16 +254,15 @@ create table sys_notice
status int not null comment '状态(0:隐藏、1:显示)',
content longtext not null comment '内容',
created_time datetime not null comment '创建时间',
- updated_time datetime null comment '更新时间'
+ updated_time datetime null comment '更新时间',
+ constraint ix_sys_notice_id
+ unique (id)
)
comment '系统通知公告表';
-create index ix_sys_notice_id
- on sys_notice (id);
-
create table sys_opera_log
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
trace_id varchar(32) not null comment '请求跟踪 ID',
username varchar(20) null comment '用户名',
@@ -279,16 +283,15 @@ create table sys_opera_log
msg longtext null comment '提示消息',
cost_time float not null comment '请求耗时(ms)',
opera_time datetime not null comment '操作时间',
- created_time datetime not null comment '创建时间'
+ created_time datetime not null comment '创建时间',
+ constraint ix_sys_opera_log_id
+ unique (id)
)
comment '操作日志表';
-create index ix_sys_opera_log_id
- on sys_opera_log (id);
-
create table sys_role
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
name varchar(20) not null comment '角色名称',
status int not null comment '角色状态(0停用 1正常)',
@@ -296,19 +299,18 @@ create table sys_role
remark longtext null comment '备注',
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
+ constraint ix_sys_role_id
+ unique (id),
constraint name
unique (name)
)
comment '角色表';
-create index ix_sys_role_id
- on sys_role (id);
-
create table sys_role_data_scope
(
- id int auto_increment comment '主键 ID',
- role_id int not null comment '角色 ID',
- data_scope_id int not null comment '数据范围 ID',
+ id bigint auto_increment comment '主键 ID',
+ role_id bigint not null comment '角色 ID',
+ data_scope_id bigint not null comment '数据范围 ID',
primary key (id, role_id, data_scope_id),
constraint ix_sys_role_data_scope_id
unique (id),
@@ -328,9 +330,9 @@ create index role_id
create table sys_role_menu
(
- id int auto_increment comment '主键ID',
- role_id int not null comment '角色ID',
- menu_id int not null comment '菜单ID',
+ id bigint auto_increment comment '主键ID',
+ role_id bigint not null comment '角色ID',
+ menu_id bigint not null comment '菜单ID',
primary key (id, role_id, menu_id),
constraint ix_sys_role_menu_id
unique (id),
@@ -350,31 +352,31 @@ create index role_id
create table sys_user
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
uuid varchar(50) not null,
username varchar(20) not null comment '用户名',
nickname varchar(20) not null comment '昵称',
- password varchar(255) null comment '密码',
- salt varbinary(255) null comment '加密盐',
- email varchar(50) not null comment '邮箱',
+ password varchar(255) not null comment '密码',
+ salt varbinary(255) not null comment '加密盐',
+ email varchar(50) null comment '邮箱',
+ phone varchar(11) null comment '手机号',
+ avatar varchar(255) null comment '头像',
+ status int not null comment '用户账号状态(0停用 1正常)',
is_superuser tinyint(1) not null comment '超级权限(0否 1是)',
is_staff tinyint(1) not null comment '后台管理登陆(0否 1是)',
- status int not null comment '用户账号状态(0停用 1正常)',
is_multi_login tinyint(1) not null comment '是否重复登陆(0否 1是)',
- avatar varchar(255) null comment '头像',
- phone varchar(11) null comment '手机号',
join_time datetime not null comment '注册时间',
last_login_time datetime null comment '上次登录',
- dept_id int null comment '部门关联ID',
+ dept_id bigint null comment '部门关联ID',
created_time datetime not null comment '创建时间',
updated_time datetime null comment '更新时间',
constraint ix_sys_user_email
unique (email),
+ constraint ix_sys_user_id
+ unique (id),
constraint ix_sys_user_username
unique (username),
- constraint nickname
- unique (nickname),
constraint uuid
unique (uuid),
constraint sys_user_ibfk_1
@@ -386,17 +388,14 @@ create table sys_user
create index dept_id
on sys_user (dept_id);
-create index ix_sys_user_id
- on sys_user (id);
-
create index ix_sys_user_status
on sys_user (status);
create table sys_user_role
(
- id int auto_increment comment '主键ID',
- user_id int not null comment '用户ID',
- role_id int not null comment '角色ID',
+ id bigint auto_increment comment '主键ID',
+ user_id bigint not null comment '用户ID',
+ role_id bigint not null comment '角色ID',
primary key (id, user_id, role_id),
constraint ix_sys_user_role_id
unique (id),
@@ -416,26 +415,21 @@ create index user_id
create table sys_user_social
(
- id int auto_increment comment '主键 ID'
+ id bigint auto_increment comment '主键 ID'
primary key,
- source varchar(20) not null comment '第三方用户来源',
- open_id varchar(20) null comment '第三方用户 open id',
- sid varchar(20) null comment '第三方用户 ID',
- union_id varchar(20) null comment '第三方用户 union id',
- scope varchar(120) null comment '第三方用户授予的权限',
- code varchar(50) null comment '用户的授权 code',
- user_id int null comment '用户关联ID',
- created_time datetime not null comment '创建时间',
- updated_time datetime null comment '更新时间',
+ sid varchar(20) not null comment '第三方用户 ID',
+ source varchar(20) not null comment '第三方用户来源',
+ user_id bigint not null comment '用户关联ID',
+ created_time datetime not null comment '创建时间',
+ updated_time datetime null comment '更新时间',
+ constraint ix_sys_user_social_id
+ unique (id),
constraint sys_user_social_ibfk_1
foreign key (user_id) references sys_user (id)
- on delete set null
+ on delete cascade
)
comment '用户社交表(OAuth2)';
-create index ix_sys_user_social_id
- on sys_user_social (id);
-
create index user_id
on sys_user_social (user_id);
diff --git a/backend/sql/postgresql/create_tables.sql b/backend/sql/postgresql/create_tables.sql
index 547cfb854..ebe1dc42e 100644
--- a/backend/sql/postgresql/create_tables.sql
+++ b/backend/sql/postgresql/create_tables.sql
@@ -1,12 +1,59 @@
+create table sys_data_rule
+(
+ id bigserial,
+ name varchar(500) not null,
+ model varchar(50) not null,
+ "column" varchar(20) not null,
+ operator integer not null,
+ expression integer not null,
+ value varchar(255) not null,
+ created_time timestamp with time zone not null,
+ updated_time timestamp with time zone,
+ primary key (),
+ unique ()
+);
+
+comment on table sys_data_rule is '数据规则表';
+
+comment on column sys_data_rule.id is '主键 ID';
+
+comment on column sys_data_rule.name is '名称';
+
+comment on column sys_data_rule.model is 'SQLA 模型名,对应 DATA_PERMISSION_MODELS 键名';
+
+comment on column sys_data_rule."column" is '模型字段名';
+
+comment on column sys_data_rule.operator is '运算符(0:and、1:or)';
+
+comment on column sys_data_rule.expression is '表达式(0:==、1:!=、2:>、3:>=、4:<、5:<=、6:in、7:not_in)';
+
+comment on column sys_data_rule.value is '规则值';
+
+comment on column sys_data_rule.created_time is '创建时间';
+
+comment on column sys_data_rule.updated_time is '更新时间';
+
+alter table sys_data_rule
+ owner to postgres;
+
+create unique index sys_data_rule_pkey
+ on sys_data_rule (id);
+
+create unique index sys_data_rule_name_key
+ on sys_data_rule (name);
+
+create unique index ix_sys_data_rule_id
+ on sys_data_rule (id);
+
create table sys_data_scope
(
- id serial
- primary key,
- name varchar(50) not null
- unique,
+ id bigserial,
+ name varchar(50) not null,
status integer not null,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key (),
+ unique ()
);
comment on table sys_data_scope is '数据范围表';
@@ -21,13 +68,21 @@ comment on column sys_data_scope.created_time is '创建时间';
comment on column sys_data_scope.updated_time is '更新时间';
-create index ix_sys_data_scope_id
+alter table sys_data_scope
+ owner to postgres;
+
+create unique index sys_data_scope_pkey
+ on sys_data_scope (id);
+
+create unique index sys_data_scope_name_key
+ on sys_data_scope (name);
+
+create unique index ix_sys_data_scope_id
on sys_data_scope (id);
create table sys_dept
(
- id serial
- primary key,
+ id bigserial,
name varchar(50) not null,
sort integer not null,
leader varchar(20),
@@ -35,11 +90,12 @@ create table sys_dept
email varchar(50),
status integer not null,
del_flag integer not null,
- parent_id integer
- references sys_dept
- on delete set null,
+ parent_id bigint
+ references ??? ()
+ on delete set null,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key ()
);
comment on table sys_dept is '部门表';
@@ -66,7 +122,13 @@ comment on column sys_dept.created_time is '创建时间';
comment on column sys_dept.updated_time is '更新时间';
-create index ix_sys_dept_id
+alter table sys_dept
+ owner to postgres;
+
+create unique index sys_dept_pkey
+ on sys_dept (id);
+
+create unique index ix_sys_dept_id
on sys_dept (id);
create index ix_sys_dept_parent_id
@@ -74,8 +136,7 @@ create index ix_sys_dept_parent_id
create table sys_login_log
(
- id serial
- primary key,
+ id bigint not null,
user_uuid varchar(50) not null,
username varchar(20) not null,
status integer not null,
@@ -89,12 +150,13 @@ create table sys_login_log
device varchar(50),
msg text not null,
login_time timestamp with time zone not null,
- created_time timestamp with time zone not null
+ created_time timestamp with time zone not null,
+ primary key ()
);
comment on table sys_login_log is '登录日志表';
-comment on column sys_login_log.id is '主键 ID';
+comment on column sys_login_log.id is '雪花算法主键 ID';
comment on column sys_login_log.user_uuid is '用户UUID';
@@ -124,16 +186,21 @@ comment on column sys_login_log.login_time is '登录时间';
comment on column sys_login_log.created_time is '创建时间';
-create index ix_sys_login_log_id
+alter table sys_login_log
+ owner to postgres;
+
+create unique index sys_login_log_pkey
+ on sys_login_log (id);
+
+create unique index ix_sys_login_log_id
on sys_login_log (id);
create table sys_menu
(
- id serial
- primary key,
+ id bigserial,
title varchar(50) not null,
name varchar(50) not null,
- path varchar(200) not null,
+ path varchar(200),
sort integer not null,
icon varchar(100),
type integer not null,
@@ -144,11 +211,12 @@ create table sys_menu
cache integer not null,
link text,
remark text,
- parent_id integer
- references sys_menu
- on delete set null,
+ parent_id bigint
+ references ??? ()
+ on delete set null,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key ()
);
comment on table sys_menu is '菜单表';
@@ -165,7 +233,7 @@ comment on column sys_menu.sort is '排序';
comment on column sys_menu.icon is '菜单图标';
-comment on column sys_menu.type is '菜单类型(0目录 1菜单 2按钮)';
+comment on column sys_menu.type is '菜单类型(0目录 1菜单 2按钮 3内嵌 4外链)';
comment on column sys_menu.component is '组件路径';
@@ -187,16 +255,21 @@ comment on column sys_menu.created_time is '创建时间';
comment on column sys_menu.updated_time is '更新时间';
-create index ix_sys_menu_id
+alter table sys_menu
+ owner to postgres;
+
+create unique index sys_menu_pkey
on sys_menu (id);
create index ix_sys_menu_parent_id
on sys_menu (parent_id);
+create unique index ix_sys_menu_id
+ on sys_menu (id);
+
create table sys_opera_log
(
- id serial
- primary key,
+ id bigserial,
trace_id varchar(32) not null,
username varchar(20),
method varchar(20) not null,
@@ -216,7 +289,8 @@ create table sys_opera_log
msg text,
cost_time double precision not null,
opera_time timestamp with time zone not null,
- created_time timestamp with time zone not null
+ created_time timestamp with time zone not null,
+ primary key ()
);
comment on table sys_opera_log is '操作日志表';
@@ -263,20 +337,26 @@ comment on column sys_opera_log.opera_time is '操作时间';
comment on column sys_opera_log.created_time is '创建时间';
-create index ix_sys_opera_log_id
+alter table sys_opera_log
+ owner to postgres;
+
+create unique index sys_opera_log_pkey
+ on sys_opera_log (id);
+
+create unique index ix_sys_opera_log_id
on sys_opera_log (id);
create table sys_role
(
- id serial
- primary key,
- name varchar(20) not null
- unique,
+ id bigserial,
+ name varchar(20) not null,
status integer not null,
is_filter_scopes integer not null,
remark text,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key (),
+ unique ()
);
comment on table sys_role is '角色表';
@@ -295,22 +375,31 @@ comment on column sys_role.created_time is '创建时间';
comment on column sys_role.updated_time is '更新时间';
-create index ix_sys_role_id
+alter table sys_role
+ owner to postgres;
+
+create unique index sys_role_pkey
+ on sys_role (id);
+
+create unique index sys_role_name_key
+ on sys_role (name);
+
+create unique index ix_sys_role_id
on sys_role (id);
create table sys_config
(
- id serial
- primary key,
+ id bigserial,
name varchar(20) not null,
type varchar(20),
- key varchar(50) not null
- unique,
+ key varchar(50) not null,
value text not null,
is_frontend integer not null,
remark text,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key (),
+ unique ()
);
comment on table sys_config is '参数配置表';
@@ -333,20 +422,29 @@ comment on column sys_config.created_time is '创建时间';
comment on column sys_config.updated_time is '更新时间';
-create index ix_sys_config_id
+alter table sys_config
+ owner to postgres;
+
+create unique index sys_config_pkey
+ on sys_config (id);
+
+create unique index sys_config_key_key
+ on sys_config (key);
+
+create unique index ix_sys_config_id
on sys_config (id);
create table sys_dict_type
(
- id serial
- primary key,
+ id bigserial,
name varchar(32) not null,
- code varchar(32) not null
- unique,
+ code varchar(32) not null,
status integer not null,
remark text,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key (),
+ unique ()
);
comment on table sys_dict_type is '字典类型表';
@@ -365,13 +463,21 @@ comment on column sys_dict_type.created_time is '创建时间';
comment on column sys_dict_type.updated_time is '更新时间';
-create index ix_sys_dict_type_id
+alter table sys_dict_type
+ owner to postgres;
+
+create unique index sys_dict_type_pkey
+ on sys_dict_type (id);
+
+create unique index sys_dict_type_code_key
+ on sys_dict_type (code);
+
+create unique index ix_sys_dict_type_id
on sys_dict_type (id);
create table sys_notice
(
- id serial
- primary key,
+ id bigserial,
title varchar(50) not null,
type integer not null,
author varchar(16) not null,
@@ -379,7 +485,8 @@ create table sys_notice
status integer not null,
content text not null,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key ()
);
comment on table sys_notice is '系统通知公告表';
@@ -402,16 +509,20 @@ comment on column sys_notice.created_time is '创建时间';
comment on column sys_notice.updated_time is '更新时间';
-create index ix_sys_notice_id
+alter table sys_notice
+ owner to postgres;
+
+create unique index sys_notice_pkey
+ on sys_notice (id);
+
+create unique index ix_sys_notice_id
on sys_notice (id);
create table gen_business
(
- id serial
- primary key,
+ id bigserial,
app_name varchar(50) not null,
- table_name varchar(255) not null
- unique,
+ table_name varchar(255) not null,
doc_comment varchar(255) not null,
table_comment varchar(255),
class_name varchar(50),
@@ -422,7 +533,9 @@ create table gen_business
gen_path varchar(255),
remark text,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key (),
+ unique ()
);
comment on table gen_business is '代码生成业务表';
@@ -455,62 +568,28 @@ comment on column gen_business.created_time is '创建时间';
comment on column gen_business.updated_time is '更新时间';
-create index ix_gen_business_id
- on gen_business (id);
-
-create table sys_data_rule
-(
- id serial
- primary key,
- name varchar(500) not null
- unique,
- model varchar(50) not null,
- "column" varchar(20) not null,
- operator integer not null,
- expression integer not null,
- value varchar(255) not null,
- scope_id integer
- references sys_data_scope
- on delete set null,
- created_time timestamp with time zone not null,
- updated_time timestamp with time zone
-);
-
-comment on table sys_data_rule is '数据规则表';
-
-comment on column sys_data_rule.id is '主键 ID';
-
-comment on column sys_data_rule.name is '名称';
-
-comment on column sys_data_rule.model is 'SQLA 模型名,对应 DATA_PERMISSION_MODELS 键名';
-
-comment on column sys_data_rule."column" is '模型字段名';
-
-comment on column sys_data_rule.operator is '运算符(0:and、1:or)';
-
-comment on column sys_data_rule.expression is '表达式(0:==、1:!=、2:>、3:>=、4:<、5:<=、6:in、7:not_in)';
-
-comment on column sys_data_rule.value is '规则值';
+alter table gen_business
+ owner to postgres;
-comment on column sys_data_rule.scope_id is '数据范围关联 ID';
-
-comment on column sys_data_rule.created_time is '创建时间';
+create unique index gen_business_pkey
+ on gen_business (id);
-comment on column sys_data_rule.updated_time is '更新时间';
+create unique index gen_business_table_name_key
+ on gen_business (table_name);
-create index ix_sys_data_rule_id
- on sys_data_rule (id);
+create unique index ix_gen_business_id
+ on gen_business (id);
create table sys_role_menu
(
- id serial,
- role_id integer not null
- references sys_role
- on delete cascade,
- menu_id integer not null
- references sys_menu
- on delete cascade,
- primary key (id, role_id, menu_id)
+ id bigserial,
+ role_id bigint not null
+ references ??? ()
+ on delete cascade,
+ menu_id bigint not null
+ references ??? ()
+ on delete cascade,
+ primary key ()
);
comment on column sys_role_menu.id is '主键ID';
@@ -519,19 +598,25 @@ comment on column sys_role_menu.role_id is '角色ID';
comment on column sys_role_menu.menu_id is '菜单ID';
+alter table sys_role_menu
+ owner to postgres;
+
+create unique index sys_role_menu_pkey
+ on sys_role_menu (id, role_id, menu_id);
+
create unique index ix_sys_role_menu_id
on sys_role_menu (id);
create table sys_role_data_scope
(
- id serial,
- role_id integer not null
- references sys_role
- on delete cascade,
- data_scope_id integer not null
- references sys_data_scope
- on delete cascade,
- primary key (id, role_id, data_scope_id)
+ id bigserial,
+ role_id bigint not null
+ references ??? ()
+ on delete cascade,
+ data_scope_id bigint not null
+ references ??? ()
+ on delete cascade,
+ primary key ()
);
comment on column sys_role_data_scope.id is '主键 ID';
@@ -540,34 +625,66 @@ comment on column sys_role_data_scope.role_id is '角色 ID';
comment on column sys_role_data_scope.data_scope_id is '数据范围 ID';
+alter table sys_role_data_scope
+ owner to postgres;
+
+create unique index sys_role_data_scope_pkey
+ on sys_role_data_scope (id, role_id, data_scope_id);
+
create unique index ix_sys_role_data_scope_id
on sys_role_data_scope (id);
+create table sys_data_scope_rule
+(
+ id bigserial,
+ data_scope_id bigint not null
+ references ??? ()
+ on delete cascade,
+ data_rule_id bigint not null
+ references ??? ()
+ on delete cascade,
+ primary key ()
+);
+
+comment on column sys_data_scope_rule.id is '主键ID';
+
+comment on column sys_data_scope_rule.data_scope_id is '数据范围 ID';
+
+comment on column sys_data_scope_rule.data_rule_id is '数据规则 ID';
+
+alter table sys_data_scope_rule
+ owner to postgres;
+
+create unique index sys_data_scope_rule_pkey
+ on sys_data_scope_rule (id, data_scope_id, data_rule_id);
+
+create unique index ix_sys_data_scope_rule_id
+ on sys_data_scope_rule (id);
+
create table sys_user
(
- id serial
- primary key,
- uuid varchar(50) not null
- unique,
+ id bigserial,
+ uuid varchar(50) not null,
username varchar(20) not null,
- nickname varchar(20) not null
- unique,
- password varchar(255),
- salt bytea,
- email varchar(50) not null,
+ nickname varchar(20) not null,
+ password varchar(255) not null,
+ salt bytea not null,
+ email varchar(50),
+ phone varchar(11),
+ avatar varchar(255),
+ status integer not null,
is_superuser integer not null,
is_staff integer not null,
- status integer not null,
is_multi_login integer not null,
- avatar varchar(255),
- phone varchar(11),
join_time timestamp with time zone not null,
last_login_time timestamp with time zone,
- dept_id integer
- references sys_dept
- on delete set null,
+ dept_id bigint
+ references ??? ()
+ on delete set null,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key (),
+ unique ()
);
comment on table sys_user is '用户表';
@@ -584,17 +701,17 @@ comment on column sys_user.salt is '加密盐';
comment on column sys_user.email is '邮箱';
-comment on column sys_user.is_superuser is '超级权限(0否 1是)';
+comment on column sys_user.phone is '手机号';
-comment on column sys_user.is_staff is '后台管理登陆(0否 1是)';
+comment on column sys_user.avatar is '头像';
comment on column sys_user.status is '用户账号状态(0停用 1正常)';
-comment on column sys_user.is_multi_login is '是否重复登陆(0否 1是)';
+comment on column sys_user.is_superuser is '超级权限(0否 1是)';
-comment on column sys_user.avatar is '头像';
+comment on column sys_user.is_staff is '后台管理登陆(0否 1是)';
-comment on column sys_user.phone is '手机号';
+comment on column sys_user.is_multi_login is '是否重复登陆(0否 1是)';
comment on column sys_user.join_time is '注册时间';
@@ -606,33 +723,42 @@ comment on column sys_user.created_time is '创建时间';
comment on column sys_user.updated_time is '更新时间';
-create unique index ix_sys_user_email
- on sys_user (email);
+alter table sys_user
+ owner to postgres;
-create index ix_sys_user_id
+create unique index sys_user_pkey
on sys_user (id);
+create unique index sys_user_uuid_key
+ on sys_user (uuid);
+
+create unique index ix_sys_user_email
+ on sys_user (email);
+
create unique index ix_sys_user_username
on sys_user (username);
+create unique index ix_sys_user_id
+ on sys_user (id);
+
create index ix_sys_user_status
on sys_user (status);
create table sys_dict_data
(
- id serial
- primary key,
- label varchar(32) not null
- unique,
+ id bigserial,
+ label varchar(32) not null,
value varchar(32) not null,
sort integer not null,
status integer not null,
remark text,
- type_id integer not null
- references sys_dict_type
- on delete cascade,
+ type_id bigint not null
+ references ??? ()
+ on delete cascade,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key (),
+ unique ()
);
comment on table sys_dict_data is '字典数据表';
@@ -655,13 +781,21 @@ comment on column sys_dict_data.created_time is '创建时间';
comment on column sys_dict_data.updated_time is '更新时间';
-create index ix_sys_dict_data_id
+alter table sys_dict_data
+ owner to postgres;
+
+create unique index sys_dict_data_pkey
+ on sys_dict_data (id);
+
+create unique index sys_dict_data_label_key
+ on sys_dict_data (label);
+
+create unique index ix_sys_dict_data_id
on sys_dict_data (id);
create table gen_column
(
- id serial
- primary key,
+ id bigserial,
name varchar(50) not null,
comment varchar(255),
type varchar(20) not null,
@@ -671,9 +805,10 @@ create table gen_column
length integer not null,
is_pk boolean not null,
is_nullable boolean not null,
- gen_business_id integer not null
- references gen_business
- on delete cascade
+ gen_business_id bigint not null
+ references ??? ()
+ on delete cascade,
+ primary key ()
);
comment on table gen_column is '代码生成模型列表';
@@ -700,19 +835,25 @@ comment on column gen_column.is_nullable is '是否可为空';
comment on column gen_column.gen_business_id is '代码生成业务ID';
-create index ix_gen_column_id
+alter table gen_column
+ owner to postgres;
+
+create unique index gen_column_pkey
+ on gen_column (id);
+
+create unique index ix_gen_column_id
on gen_column (id);
create table sys_user_role
(
- id serial,
- user_id integer not null
- references sys_user
- on delete cascade,
- role_id integer not null
- references sys_role
- on delete cascade,
- primary key (id, user_id, role_id)
+ id bigserial,
+ user_id bigint not null
+ references ??? ()
+ on delete cascade,
+ role_id bigint not null
+ references ??? ()
+ on delete cascade,
+ primary key ()
);
comment on column sys_user_role.id is '主键ID';
@@ -721,41 +862,35 @@ comment on column sys_user_role.user_id is '用户ID';
comment on column sys_user_role.role_id is '角色ID';
+alter table sys_user_role
+ owner to postgres;
+
+create unique index sys_user_role_pkey
+ on sys_user_role (id, user_id, role_id);
+
create unique index ix_sys_user_role_id
on sys_user_role (id);
create table sys_user_social
(
- id serial
- primary key,
+ id bigserial,
+ sid varchar(20) not null,
source varchar(20) not null,
- open_id varchar(20),
- sid varchar(20),
- union_id varchar(20),
- scope varchar(120),
- code varchar(50),
- user_id integer
- references sys_user
- on delete set null,
+ user_id bigint not null
+ references ??? ()
+ on delete cascade,
created_time timestamp with time zone not null,
- updated_time timestamp with time zone
+ updated_time timestamp with time zone,
+ primary key ()
);
comment on table sys_user_social is '用户社交表(OAuth2)';
comment on column sys_user_social.id is '主键 ID';
-comment on column sys_user_social.source is '第三方用户来源';
-
-comment on column sys_user_social.open_id is '第三方用户 open id';
-
-comment on column sys_user_social.uid is '第三方用户 ID';
-
-comment on column sys_user_social.union_id is '第三方用户 union id';
-
-comment on column sys_user_social.scope is '第三方用户授予的权限';
+comment on column sys_user_social.sid is '第三方用户 ID';
-comment on column sys_user_social.code is '用户的授权 code';
+comment on column sys_user_social.source is '第三方用户来源';
comment on column sys_user_social.user_id is '用户关联ID';
@@ -763,5 +898,11 @@ comment on column sys_user_social.created_time is '创建时间';
comment on column sys_user_social.updated_time is '更新时间';
-create index ix_sys_user_social_id
+alter table sys_user_social
+ owner to postgres;
+
+create unique index sys_user_social_pkey
+ on sys_user_social (id);
+
+create unique index ix_sys_user_social_id
on sys_user_social (id);
diff --git a/backend/utils/snowflake.py b/backend/utils/snowflake.py
index 84054a0f8..b70d25ecf 100644
--- a/backend/utils/snowflake.py
+++ b/backend/utils/snowflake.py
@@ -1,161 +1,106 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
-"""
-Twitter雪花算法(Snowflake)的Python实现
-用于生成分布式唯一ID
-
-ID结构(64位):
-- 1位符号位,固定为0
-- 41位时间戳(毫秒级)
-- 5位数据中心ID
-- 5位机器ID
-- 12位序列号
-
-https://github.com/twitter-archive/snowflake/blob/snowflake-2010/src/main/scala/com/twitter/service/snowflake/IdWorker.scala
-"""
-
import time
from dataclasses import dataclass
+from backend.common.exception import errors
-class SnowflakeException(Exception):
- """雪花算法基础异常类"""
-
- pass
-
-
-class InvalidSystemClock(SnowflakeException):
- """时钟回拨异常"""
-
- pass
-
-
-class InvalidWorkerId(SnowflakeException):
- """无效的工作节点ID异常"""
-
- pass
-
-
-class InvalidDatacenterId(SnowflakeException):
- """无效的数据中心ID异常"""
- pass
-
-
-@dataclass
+@dataclass(frozen=True)
class SnowflakeConfig:
"""雪花算法配置类"""
- # 64位ID的划分
+ # 位分配
WORKER_ID_BITS: int = 5
DATACENTER_ID_BITS: int = 5
SEQUENCE_BITS: int = 12
- # 最大取值计算
- MAX_WORKER_ID: int = -1 ^ (-1 << WORKER_ID_BITS) # 2**5-1 0b11111
- MAX_DATACENTER_ID: int = -1 ^ (-1 << DATACENTER_ID_BITS)
+ # 最大值
+ MAX_WORKER_ID: int = (1 << WORKER_ID_BITS) - 1 # 31
+ MAX_DATACENTER_ID: int = (1 << DATACENTER_ID_BITS) - 1 # 31
+ SEQUENCE_MASK: int = (1 << SEQUENCE_BITS) - 1 # 4095
- # 移位偏移计算
+ # 位移偏移
WORKER_ID_SHIFT: int = SEQUENCE_BITS
DATACENTER_ID_SHIFT: int = SEQUENCE_BITS + WORKER_ID_BITS
TIMESTAMP_LEFT_SHIFT: int = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS
- # 序号循环掩码
- SEQUENCE_MASK: int = -1 ^ (-1 << SEQUENCE_BITS)
-
- # 元年时间戳, 2010-01-01 00:00:00
+ # 元年时间戳
EPOCH: int = 1262275200000
- # 默认配置
+ # 默认值
DEFAULT_DATACENTER_ID: int = 1
DEFAULT_WORKER_ID: int = 0
DEFAULT_SEQUENCE: int = 0
class Snowflake:
- """雪花算法ID生成器"""
+ """雪花算法类"""
def __init__(
self,
- datacenter_id: int = SnowflakeConfig.DEFAULT_DATACENTER_ID,
- worker_id: int = SnowflakeConfig.DEFAULT_WORKER_ID,
+ cluster_id: int = SnowflakeConfig.DEFAULT_DATACENTER_ID,
+ node_id: int = SnowflakeConfig.DEFAULT_WORKER_ID,
sequence: int = SnowflakeConfig.DEFAULT_SEQUENCE,
):
"""
- 初始化ID生成器
+ 初始化雪花算法生成器
- :param datacenter_id: 数据中心ID (0-31)
- :param worker_id: 工作节点ID (0-31)
+ :param cluster_id: 集群 ID (0-31)
+ :param node_id: 节点 ID (0-31)
:param sequence: 起始序列号
"""
- if not 0 <= worker_id <= SnowflakeConfig.MAX_WORKER_ID:
- raise InvalidWorkerId(f'工作节点ID必须在0-{SnowflakeConfig.MAX_WORKER_ID}之间')
+ if cluster_id < 0 or cluster_id > SnowflakeConfig.MAX_DATACENTER_ID:
+ raise errors.ForbiddenError(msg=f'集群编号必须在 0-{SnowflakeConfig.MAX_DATACENTER_ID} 之间')
+ if node_id < 0 or node_id > SnowflakeConfig.MAX_WORKER_ID:
+ raise errors.ForbiddenError(msg=f'节点编号必须在 0-{SnowflakeConfig.MAX_WORKER_ID} 之间')
- if not 0 <= datacenter_id <= SnowflakeConfig.MAX_DATACENTER_ID:
- raise InvalidDatacenterId(f'数据中心ID必须在0-{SnowflakeConfig.MAX_DATACENTER_ID}之间')
-
- self.worker_id = worker_id
- self.datacenter_id = datacenter_id
+ self.node_id = node_id
+ self.cluster_id = cluster_id
self.sequence = sequence
self.last_timestamp = -1
- def __call__(self) -> int:
- """使实例可调用,用于default_factory"""
- return self.generate()
-
@staticmethod
- def _gen_timestamp() -> int:
- """
- 生成当前时间戳,单位:毫秒
-
- :return: 当前时间戳
- """
+ def _current_millis() -> int:
+ """返回当前毫秒时间戳"""
return int(time.time() * 1000)
- def _til_next_millis(self, last_timestamp: int) -> int:
+ def _next_millis(self, last_timestamp: int) -> int:
"""
- 等待到下一个毫秒
+ 等待至下一毫秒
- :param last_timestamp: 上次生成ID的时间戳
- :return: 下一个毫秒的时间戳
+ :param last_timestamp: 上次生成 ID 的时间戳
+ :return:
"""
- timestamp = self._gen_timestamp()
+ timestamp = self._current_millis()
while timestamp <= last_timestamp:
- timestamp = self._gen_timestamp()
+ time.sleep((last_timestamp - timestamp + 1) / 1000.0)
+ timestamp = self._current_millis()
return timestamp
def generate(self) -> int:
- """
- 获取新的唯一ID,单位:毫秒
-
- :return: 新的唯一ID
- """
- timestamp = self._gen_timestamp()
+ """生成雪花 ID"""
+ timestamp = self._current_millis()
if timestamp < self.last_timestamp:
- raise InvalidSystemClock(f'系统时钟回拨,拒绝生成ID直到 {self.last_timestamp}')
+ raise errors.ForbiddenError(msg=f'系统时间倒退,拒绝生成 ID 直到 {self.last_timestamp}')
if timestamp == self.last_timestamp:
self.sequence = (self.sequence + 1) & SnowflakeConfig.SEQUENCE_MASK
if self.sequence == 0:
- timestamp = self._til_next_millis(self.last_timestamp)
+ timestamp = self._next_millis(self.last_timestamp)
else:
self.sequence = 0
self.last_timestamp = timestamp
- new_id = (
+ return (
((timestamp - SnowflakeConfig.EPOCH) << SnowflakeConfig.TIMESTAMP_LEFT_SHIFT)
- | (self.datacenter_id << SnowflakeConfig.DATACENTER_ID_SHIFT)
- | (self.worker_id << SnowflakeConfig.WORKER_ID_SHIFT)
+ | (self.cluster_id << SnowflakeConfig.DATACENTER_ID_SHIFT)
+ | (self.node_id << SnowflakeConfig.WORKER_ID_SHIFT)
| self.sequence
)
- return new_id
snowflake = Snowflake()
-
-if __name__ == '__main__':
- for _ in range(10):
- print(snowflake.generate())
From 6ca670865993184a166db331238f4dab0f9e52b8 Mon Sep 17 00:00:00 2001
From: Wu Clan
Date: Sun, 15 Jun 2025 23:48:34 +0800
Subject: [PATCH 04/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=94=99=E8=AF=AF?=
=?UTF-8?q?=E5=BC=95=E7=94=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/app/admin/model/login_log.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/app/admin/model/login_log.py b/backend/app/admin/model/login_log.py
index 13a717f62..757bd93f2 100644
--- a/backend/app/admin/model/login_log.py
+++ b/backend/app/admin/model/login_log.py
@@ -7,7 +7,7 @@
from sqlalchemy.dialects.postgresql import TEXT
from sqlalchemy.orm import Mapped, mapped_column
-from backend.common.model import DataClassBase, snowflake_id_key
+from backend.common.model import DataClassBase, id_key
from backend.utils.timezone import timezone
@@ -16,7 +16,7 @@ class LoginLog(DataClassBase):
__tablename__ = 'sys_login_log'
- id: Mapped[snowflake_id_key] = mapped_column(init=False)
+ 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='用户名')
status: Mapped[int] = mapped_column(insert_default=0, comment='登录状态(0失败 1成功)')
From 5fd1e8e790b9ed9303f95419b9c0c7bcda94b3a8 Mon Sep 17 00:00:00 2001
From: Wu Clan
Date: Mon, 16 Jun 2025 02:08:45 +0800
Subject: [PATCH 05/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=9B=AA=E8=8A=B1?=
=?UTF-8?q?=E8=AF=A6=E6=83=85=E9=93=BE=E6=8E=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/common/model.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/backend/common/model.py b/backend/common/model.py
index 5ab0407f6..9a123d37e 100644
--- a/backend/common/model.py
+++ b/backend/common/model.py
@@ -28,6 +28,7 @@
# 雪花算法 Mapped 类型主键,使用方法与 id_key 相同
+# 详情:https://fastapi-practices.github.io/fastapi_best_architecture_docs/backend/reference/pk.html
snowflake_id_key = Annotated[
int,
mapped_column(
From 65f85f1f61eb1148eac6232e1633168fa0b66d01 Mon Sep 17 00:00:00 2001
From: downdawn <1436759077@qq.com>
Date: Mon, 16 Jun 2025 10:12:32 +0800
Subject: [PATCH 06/10] feat: add snowflake ID parser method
---
backend/utils/snowflake.py | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/backend/utils/snowflake.py b/backend/utils/snowflake.py
index b70d25ecf..834b06bd9 100644
--- a/backend/utils/snowflake.py
+++ b/backend/utils/snowflake.py
@@ -102,5 +102,26 @@ def generate(self) -> int:
| self.sequence
)
+ @staticmethod
+ def parse_id(snowflake_id: int) -> dict:
+ """
+ 解析雪花ID,获取其包含的详细信息
+
+ :param snowflake_id: 雪花ID
+ :return: 包含时间戳、集群ID、节点ID和序列号的字典
+ """
+ timestamp = (snowflake_id >> SnowflakeConfig.TIMESTAMP_LEFT_SHIFT) + SnowflakeConfig.EPOCH
+ cluster_id = (snowflake_id >> SnowflakeConfig.DATACENTER_ID_SHIFT) & SnowflakeConfig.MAX_DATACENTER_ID
+ node_id = (snowflake_id >> SnowflakeConfig.WORKER_ID_SHIFT) & SnowflakeConfig.MAX_WORKER_ID
+ sequence = snowflake_id & SnowflakeConfig.SEQUENCE_MASK
+
+ return {
+ "timestamp": timestamp,
+ "datetime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp / 1000)),
+ "cluster_id": cluster_id,
+ "node_id": node_id,
+ "sequence": sequence
+ }
+
snowflake = Snowflake()
From 70dac6ca1789f07cd45366d1931ebeb14abf2110 Mon Sep 17 00:00:00 2001
From: Wu Clan
Date: Mon, 16 Jun 2025 10:41:28 +0800
Subject: [PATCH 07/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=8B=AC=E7=AB=8B?=
=?UTF-8?q?=E6=89=A7=E8=A1=8C=E5=BC=82=E5=B8=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/common/dataclasses.py | 9 +++++++++
backend/plugin/tools.py | 2 +-
backend/utils/{_asyncio.py => _await.py} | 0
backend/utils/snowflake.py | 22 ++++++++++++----------
4 files changed, 22 insertions(+), 11 deletions(-)
rename backend/utils/{_asyncio.py => _await.py} (100%)
diff --git a/backend/common/dataclasses.py b/backend/common/dataclasses.py
index 0268a90df..35ec6a94a 100644
--- a/backend/common/dataclasses.py
+++ b/backend/common/dataclasses.py
@@ -64,3 +64,12 @@ class TokenPayload:
@dataclasses.dataclass
class UploadUrl:
url: str
+
+
+@dataclasses.dataclass
+class SnowflakeInfo:
+ timestamp: int
+ datetime: str
+ cluster_id: int
+ node_id: int
+ sequence: int
diff --git a/backend/plugin/tools.py b/backend/plugin/tools.py
index 7f8d7cda8..9ce314584 100644
--- a/backend/plugin/tools.py
+++ b/backend/plugin/tools.py
@@ -22,7 +22,7 @@
from backend.core.path_conf import PLUGIN_DIR
from backend.database.redis import RedisCli, redis_client
from backend.plugin.errors import PluginConfigError, PluginInjectError
-from backend.utils._asyncio import run_await
+from backend.utils._await import run_await
from backend.utils.import_parse import import_module_cached
diff --git a/backend/utils/_asyncio.py b/backend/utils/_await.py
similarity index 100%
rename from backend/utils/_asyncio.py
rename to backend/utils/_await.py
diff --git a/backend/utils/snowflake.py b/backend/utils/snowflake.py
index 834b06bd9..857c366f1 100644
--- a/backend/utils/snowflake.py
+++ b/backend/utils/snowflake.py
@@ -4,7 +4,9 @@
from dataclasses import dataclass
+from backend.common.dataclasses import SnowflakeInfo
from backend.common.exception import errors
+from backend.core.conf import settings
@dataclass(frozen=True)
@@ -103,25 +105,25 @@ def generate(self) -> int:
)
@staticmethod
- def parse_id(snowflake_id: int) -> dict:
+ def parse_id(snowflake_id: int) -> SnowflakeInfo:
"""
- 解析雪花ID,获取其包含的详细信息
+ 解析雪花 ID,获取其包含的详细信息
:param snowflake_id: 雪花ID
- :return: 包含时间戳、集群ID、节点ID和序列号的字典
+ :return:
"""
timestamp = (snowflake_id >> SnowflakeConfig.TIMESTAMP_LEFT_SHIFT) + SnowflakeConfig.EPOCH
cluster_id = (snowflake_id >> SnowflakeConfig.DATACENTER_ID_SHIFT) & SnowflakeConfig.MAX_DATACENTER_ID
node_id = (snowflake_id >> SnowflakeConfig.WORKER_ID_SHIFT) & SnowflakeConfig.MAX_WORKER_ID
sequence = snowflake_id & SnowflakeConfig.SEQUENCE_MASK
- return {
- "timestamp": timestamp,
- "datetime": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp / 1000)),
- "cluster_id": cluster_id,
- "node_id": node_id,
- "sequence": sequence
- }
+ return SnowflakeInfo(
+ timestamp=timestamp,
+ datetime=time.strftime(settings.DATETIME_FORMAT, time.localtime(timestamp / 1000)),
+ cluster_id=cluster_id,
+ node_id=node_id,
+ sequence=sequence,
+ )
snowflake = Snowflake()
From 4ee0226f6ab7fa5b0b18730d2b1464e7bcdf1637 Mon Sep 17 00:00:00 2001
From: Wu Clan
Date: Mon, 16 Jun 2025 10:45:18 +0800
Subject: [PATCH 08/10] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F?=
=?UTF-8?q?=E6=97=B6=E9=97=B4=E9=94=99=E8=AF=AF=E7=B1=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
backend/utils/snowflake.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/utils/snowflake.py b/backend/utils/snowflake.py
index 857c366f1..225cafaaf 100644
--- a/backend/utils/snowflake.py
+++ b/backend/utils/snowflake.py
@@ -86,7 +86,7 @@ def generate(self) -> int:
timestamp = self._current_millis()
if timestamp < self.last_timestamp:
- raise errors.ForbiddenError(msg=f'系统时间倒退,拒绝生成 ID 直到 {self.last_timestamp}')
+ raise errors.ServerError(msg=f'系统时间倒退,拒绝生成 ID 直到 {self.last_timestamp}')
if timestamp == self.last_timestamp:
self.sequence = (self.sequence + 1) & SnowflakeConfig.SEQUENCE_MASK
From ee81706d77d681885da551956280111cc23b9d21 Mon Sep 17 00:00:00 2001
From: downdawn <1436759077@qq.com>
Date: Mon, 23 Jun 2025 11:34:01 +0800
Subject: [PATCH 09/10] refactor: Optimize HTTP status codes for better REST
API semantics
---
backend/app/admin/service/auth_service.py | 4 +--
.../app/admin/service/data_rule_service.py | 4 +--
.../app/admin/service/data_scope_service.py | 4 +--
backend/app/admin/service/dept_service.py | 8 +++---
backend/app/admin/service/menu_service.py | 6 ++---
backend/app/admin/service/plugin_service.py | 22 ++++++++--------
backend/app/admin/service/role_service.py | 4 +--
backend/app/admin/service/user_service.py | 12 ++++-----
backend/app/task/service/task_service.py | 2 +-
backend/common/exception/errors.py | 9 +++++++
backend/common/security/jwt.py | 26 ++++++++++++++++---
.../service/business_service.py | 2 +-
.../code_generator/service/column_service.py | 2 +-
.../code_generator/service/gen_service.py | 2 +-
.../plugin/config/service/config_service.py | 6 ++---
.../plugin/dict/service/dict_data_service.py | 4 +--
.../plugin/dict/service/dict_type_service.py | 4 +--
backend/plugin/tools.py | 2 +-
backend/utils/file_ops.py | 10 +++----
backend/utils/snowflake.py | 4 +--
20 files changed, 83 insertions(+), 54 deletions(-)
diff --git a/backend/app/admin/service/auth_service.py b/backend/app/admin/service/auth_service.py
index be8e51f4a..52ddd9154 100644
--- a/backend/app/admin/service/auth_service.py
+++ b/backend/app/admin/service/auth_service.py
@@ -93,7 +93,7 @@ async def login(
user = await self.user_verify(db, obj.username, obj.password)
captcha_code = await redis_client.get(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
if not captcha_code:
- raise errors.ForbiddenError(msg='验证码失效,请重新获取')
+ raise errors.RequestError(msg='验证码失效,请重新获取')
if captcha_code.lower() != obj.captcha.lower():
raise errors.CustomError(error=CustomErrorCode.CAPTCHA_ERROR)
await redis_client.delete(f'{settings.CAPTCHA_LOGIN_REDIS_PREFIX}:{request.state.ip}')
@@ -122,7 +122,7 @@ async def login(
except errors.NotFoundError as e:
log.error('登陆错误: 用户名不存在')
raise errors.NotFoundError(msg=e.msg)
- except (errors.ForbiddenError, errors.CustomError) as e:
+ except (errors.RequestError, errors.CustomError) as e:
if not user:
log.error('登陆错误: 用户密码有误')
task = BackgroundTask(
diff --git a/backend/app/admin/service/data_rule_service.py b/backend/app/admin/service/data_rule_service.py
index ec0ac23b5..916d07d1a 100644
--- a/backend/app/admin/service/data_rule_service.py
+++ b/backend/app/admin/service/data_rule_service.py
@@ -87,7 +87,7 @@ async def create(*, obj: CreateDataRuleParam) -> None:
async with async_db_session.begin() as db:
data_rule = await data_rule_dao.get_by_name(db, obj.name)
if data_rule:
- raise errors.ForbiddenError(msg='数据规则已存在')
+ raise errors.ConflictError(msg='数据规则已存在')
await data_rule_dao.create(db, obj)
@staticmethod
@@ -105,7 +105,7 @@ async def update(*, pk: int, obj: UpdateDataRuleParam) -> int:
raise errors.NotFoundError(msg='数据规则不存在')
if data_rule.name != obj.name:
if await data_rule_dao.get_by_name(db, obj.name):
- raise errors.ForbiddenError(msg='数据规则已存在')
+ raise errors.ConflictError(msg='数据规则已存在')
count = await data_rule_dao.update(db, pk, obj)
return count
diff --git a/backend/app/admin/service/data_scope_service.py b/backend/app/admin/service/data_scope_service.py
index eb56901dd..88099d2fe 100644
--- a/backend/app/admin/service/data_scope_service.py
+++ b/backend/app/admin/service/data_scope_service.py
@@ -78,7 +78,7 @@ async def create(*, obj: CreateDataScopeParam) -> None:
async with async_db_session.begin() as db:
data_scope = await data_scope_dao.get_by_name(db, obj.name)
if data_scope:
- raise errors.ForbiddenError(msg='数据范围已存在')
+ raise errors.ConflictError(msg='数据范围已存在')
await data_scope_dao.create(db, obj)
@staticmethod
@@ -96,7 +96,7 @@ async def update(*, pk: int, obj: UpdateDataScopeParam) -> int:
raise errors.NotFoundError(msg='数据范围不存在')
if data_scope.name != obj.name:
if await data_scope_dao.get_by_name(db, obj.name):
- raise errors.ForbiddenError(msg='数据范围已存在')
+ raise errors.ConflictError(msg='数据范围已存在')
count = await data_scope_dao.update(db, pk, obj)
for role in await data_scope.awaitable_attrs.roles:
for user in await role.awaitable_attrs.users:
diff --git a/backend/app/admin/service/dept_service.py b/backend/app/admin/service/dept_service.py
index ab0a0e9a9..049e229b4 100644
--- a/backend/app/admin/service/dept_service.py
+++ b/backend/app/admin/service/dept_service.py
@@ -61,7 +61,7 @@ async def create(*, obj: CreateDeptParam) -> None:
async with async_db_session.begin() as db:
dept = await dept_dao.get_by_name(db, obj.name)
if dept:
- raise errors.ForbiddenError(msg='部门名称已存在')
+ raise errors.ConflictError(msg='部门名称已存在')
if obj.parent_id:
parent_dept = await dept_dao.get(db, obj.parent_id)
if not parent_dept:
@@ -83,7 +83,7 @@ async def update(*, pk: int, obj: UpdateDeptParam) -> int:
raise errors.NotFoundError(msg='部门不存在')
if dept.name != obj.name:
if await dept_dao.get_by_name(db, obj.name):
- raise errors.ForbiddenError(msg='部门名称已存在')
+ raise errors.ConflictError(msg='部门名称已存在')
if obj.parent_id:
parent_dept = await dept_dao.get(db, obj.parent_id)
if not parent_dept:
@@ -104,10 +104,10 @@ async def delete(*, pk: int) -> int:
async with async_db_session.begin() as db:
dept = await dept_dao.get_with_relation(db, pk)
if dept.users:
- raise errors.ForbiddenError(msg='部门下存在用户,无法删除')
+ raise errors.ConflictError(msg='部门下存在用户,无法删除')
children = await dept_dao.get_children(db, pk)
if children:
- raise errors.ForbiddenError(msg='部门下存在子部门,无法删除')
+ raise errors.ConflictError(msg='部门下存在子部门,无法删除')
count = await dept_dao.delete(db, pk)
for user in dept.users:
await redis_client.delete(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
diff --git a/backend/app/admin/service/menu_service.py b/backend/app/admin/service/menu_service.py
index f9a3c0b78..28a0235b1 100644
--- a/backend/app/admin/service/menu_service.py
+++ b/backend/app/admin/service/menu_service.py
@@ -78,7 +78,7 @@ async def create(*, obj: CreateMenuParam) -> None:
async with async_db_session.begin() as db:
title = await menu_dao.get_by_title(db, obj.title)
if title:
- raise errors.ForbiddenError(msg='菜单标题已存在')
+ raise errors.ConflictError(msg='菜单标题已存在')
if obj.parent_id:
parent_menu = await menu_dao.get(db, obj.parent_id)
if not parent_menu:
@@ -100,7 +100,7 @@ async def update(*, pk: int, obj: UpdateMenuParam) -> int:
raise errors.NotFoundError(msg='菜单不存在')
if menu.title != obj.title:
if await menu_dao.get_by_title(db, obj.title):
- raise errors.ForbiddenError(msg='菜单标题已存在')
+ raise errors.ConflictError(msg='菜单标题已存在')
if obj.parent_id:
parent_menu = await menu_dao.get(db, obj.parent_id)
if not parent_menu:
@@ -124,7 +124,7 @@ async def delete(*, pk: int) -> int:
async with async_db_session.begin() as db:
children = await menu_dao.get_children(db, pk)
if children:
- raise errors.ForbiddenError(msg='菜单下存在子菜单,无法删除')
+ raise errors.ConflictError(msg='菜单下存在子菜单,无法删除')
menu = await menu_dao.get(db, pk)
count = await menu_dao.delete(db, pk)
if menu:
diff --git a/backend/app/admin/service/plugin_service.py b/backend/app/admin/service/plugin_service.py
index 9120dd91b..954a808c6 100644
--- a/backend/app/admin/service/plugin_service.py
+++ b/backend/app/admin/service/plugin_service.py
@@ -55,24 +55,24 @@ async def install_zip(*, file: UploadFile) -> None:
contents = await file.read()
file_bytes = io.BytesIO(contents)
if not zipfile.is_zipfile(file_bytes):
- raise errors.ForbiddenError(msg='插件压缩包格式非法')
+ raise errors.RequestError(msg='插件压缩包格式非法')
with zipfile.ZipFile(file_bytes) as zf:
# 校验压缩包
plugin_namelist = zf.namelist()
plugin_name = plugin_namelist[0].split('/')[0]
if not plugin_namelist or plugin_name not in file.filename:
- raise errors.ForbiddenError(msg='插件压缩包内容非法')
+ raise errors.RequestError(msg='插件压缩包内容非法')
if (
len(plugin_namelist) <= 3
or f'{plugin_name}/plugin.toml' not in plugin_namelist
or f'{plugin_name}/README.md' not in plugin_namelist
):
- raise errors.ForbiddenError(msg='插件压缩包内缺少必要文件')
+ raise errors.RequestError(msg='插件压缩包内缺少必要文件')
# 插件是否可安装
full_plugin_path = os.path.join(PLUGIN_DIR, plugin_name)
if os.path.exists(full_plugin_path):
- raise errors.ForbiddenError(msg='此插件已安装')
+ raise errors.ConflictError(msg='此插件已安装')
else:
os.makedirs(full_plugin_path, exist_ok=True)
@@ -99,11 +99,11 @@ async def install_git(*, repo_url: str):
"""
match = is_git_url(repo_url)
if not match:
- raise errors.ForbiddenError(msg='Git 仓库地址格式非法')
+ raise errors.RequestError(msg='Git 仓库地址格式非法')
repo_name = match.group('repo')
plugins = await redis_client.lrange(settings.PLUGIN_REDIS_PREFIX, 0, -1)
if repo_name in plugins:
- raise errors.ForbiddenError(msg=f'{repo_name} 插件已安装')
+ raise errors.ConflictError(msg=f'{repo_name} 插件已安装')
try:
porcelain.clone(repo_url, os.path.join(PLUGIN_DIR, repo_name), checkout=True)
except Exception as e:
@@ -124,11 +124,11 @@ async def install(self, *, type: PluginType, file: UploadFile | None = None, rep
"""
if type == PluginType.zip:
if not file:
- raise errors.ForbiddenError(msg='ZIP 压缩包不能为空')
+ raise errors.RequestError(msg='ZIP 压缩包不能为空')
await self.install_zip(file=file)
elif type == PluginType.git:
if not repo_url:
- raise errors.ForbiddenError(msg='Git 仓库地址不能为空')
+ raise errors.RequestError(msg='Git 仓库地址不能为空')
await self.install_git(repo_url=repo_url)
@staticmethod
@@ -141,7 +141,7 @@ async def uninstall(*, plugin: str):
"""
plugin_dir = os.path.join(PLUGIN_DIR, plugin)
if not os.path.exists(plugin_dir):
- raise errors.ForbiddenError(msg='插件不存在')
+ raise errors.NotFoundError(msg='插件不存在')
await uninstall_requirements_async(plugin)
bacup_dir = os.path.join(PLUGIN_DIR, f'{plugin}.{timezone.now().strftime("%Y%m%d%H%M%S")}.backup')
shutil.move(plugin_dir, bacup_dir)
@@ -159,7 +159,7 @@ async def update_status(*, plugin: str):
"""
plugin_info = await redis_client.get(f'{settings.PLUGIN_REDIS_PREFIX}:info:{plugin}')
if not plugin_info:
- raise errors.ForbiddenError(msg='插件不存在')
+ raise errors.NotFoundError(msg='插件不存在')
plugin_info = json.loads(plugin_info)
# 更新持久缓存状态
@@ -184,7 +184,7 @@ async def build(*, plugin: str) -> io.BytesIO:
"""
plugin_dir = os.path.join(PLUGIN_DIR, plugin)
if not os.path.exists(plugin_dir):
- raise errors.ForbiddenError(msg='插件不存在')
+ raise errors.NotFoundError(msg='插件不存在')
bio = io.BytesIO()
with zipfile.ZipFile(bio, 'w') as zf:
diff --git a/backend/app/admin/service/role_service.py b/backend/app/admin/service/role_service.py
index 08854bf43..760b666a4 100644
--- a/backend/app/admin/service/role_service.py
+++ b/backend/app/admin/service/role_service.py
@@ -98,7 +98,7 @@ async def create(*, obj: CreateRoleParam) -> None:
async with async_db_session.begin() as db:
role = await role_dao.get_by_name(db, obj.name)
if role:
- raise errors.ForbiddenError(msg='角色已存在')
+ raise errors.ConflictError(msg='角色已存在')
await role_dao.create(db, obj)
@staticmethod
@@ -116,7 +116,7 @@ async def update(*, pk: int, obj: UpdateRoleParam) -> int:
raise errors.NotFoundError(msg='角色不存在')
if role.name != obj.name:
if await role_dao.get_by_name(db, obj.name):
- raise errors.ForbiddenError(msg='角色已存在')
+ raise errors.ConflictError(msg='角色已存在')
count = await role_dao.update(db, pk, obj)
for user in await role.awaitable_attrs.users:
await redis_client.delete_prefix(f'{settings.JWT_USER_REDIS_PREFIX}:{user.id}')
diff --git a/backend/app/admin/service/user_service.py b/backend/app/admin/service/user_service.py
index acdcc9307..59e9f76ef 100644
--- a/backend/app/admin/service/user_service.py
+++ b/backend/app/admin/service/user_service.py
@@ -81,10 +81,10 @@ async def create(*, request: Request, obj: AddUserParam) -> None:
async with async_db_session.begin() as db:
superuser_verify(request)
if await user_dao.get_by_username(db, obj.username):
- raise errors.ForbiddenError(msg='用户名已注册')
+ raise errors.ConflictError(msg='用户名已注册')
obj.nickname = obj.nickname if obj.nickname else f'#{random.randrange(88888, 99999)}'
if not obj.password:
- raise errors.ForbiddenError(msg='密码不允许为空')
+ raise errors.RequestError(msg='密码不允许为空')
if not await dept_dao.get(db, obj.dept_id):
raise errors.NotFoundError(msg='部门不存在')
for role_id in obj.roles:
@@ -110,7 +110,7 @@ async def update(*, request: Request, pk: int, obj: UpdateUserParam) -> int:
raise errors.ForbiddenError(msg='只能修改自己的信息')
if obj.username != user.username:
if await user_dao.get_by_username(db, obj.username):
- raise errors.ForbiddenError(msg='用户名已注册')
+ raise errors.ConflictError(msg='用户名已注册')
for role_id in obj.roles:
if not await role_dao.get(db, role_id):
raise errors.NotFoundError(msg='角色不存在')
@@ -231,7 +231,7 @@ async def update_permission(self, *, request: Request, pk: int, type: UserPermis
elif type == UserPermissionType.multi_login:
count = await self.update_multi_login(request=request, pk=pk)
else:
- raise errors.ForbiddenError(msg='权限类型不存在')
+ raise errors.RequestError(msg='权限类型不存在')
return count
@staticmethod
@@ -248,9 +248,9 @@ async def reset_pwd(*, pk: int, obj: ResetPasswordParam) -> int:
if not user:
raise errors.NotFoundError(msg='用户不存在')
if not password_verify(obj.old_password, user.password):
- raise errors.ForbiddenError(msg='原密码错误')
+ raise errors.RequestError(msg='原密码错误')
if obj.new_password != obj.confirm_password:
- raise errors.ForbiddenError(msg='密码输入不一致')
+ raise errors.RequestError(msg='密码输入不一致')
new_pwd = get_hash_password(obj.new_password, user.salt)
count = await user_dao.reset_password(db, user.id, new_pwd)
key_prefix = [
diff --git a/backend/app/task/service/task_service.py b/backend/app/task/service/task_service.py
index 7061e848a..6a27a349a 100644
--- a/backend/app/task/service/task_service.py
+++ b/backend/app/task/service/task_service.py
@@ -39,7 +39,7 @@ async def get_all() -> list[str]:
"""获取所有已注册的 Celery 任务列表"""
registered_tasks = await run_in_threadpool(celery_app.control.inspect().registered)
if not registered_tasks:
- raise errors.ForbiddenError(msg='Celery 服务未启动')
+ raise errors.ServerError(msg='Celery 服务未启动')
tasks = list(registered_tasks.values())[0]
return tasks
diff --git a/backend/common/exception/errors.py b/backend/common/exception/errors.py
index e334298d8..64db8a03b 100644
--- a/backend/common/exception/errors.py
+++ b/backend/common/exception/errors.py
@@ -98,3 +98,12 @@ class TokenError(HTTPError):
def __init__(self, *, msg: str = 'Not Authenticated', headers: dict[str, Any] | None = None):
super().__init__(code=self.code, msg=msg, headers=headers or {'WWW-Authenticate': 'Bearer'})
+
+
+class ConflictError(BaseExceptionMixin):
+ """资源冲突异常"""
+
+ code = StandardResponseCode.HTTP_409
+
+ def __init__(self, *, msg: str = 'Conflict', data: Any = None, background: BackgroundTask | None = None):
+ super().__init__(msg=msg, data=data, background=background)
diff --git a/backend/common/security/jwt.py b/backend/common/security/jwt.py
index 81c6d7383..717a4df06 100644
--- a/backend/common/security/jwt.py
+++ b/backend/common/security/jwt.py
@@ -6,8 +6,9 @@
from typing import Any
from uuid import uuid4
-from fastapi import Depends, Request
+from fastapi import Depends, HTTPException, Request
from fastapi.security import HTTPBearer
+from fastapi.security.http import HTTPAuthorizationCredentials
from fastapi.security.utils import get_authorization_scheme_param
from jose import ExpiredSignatureError, JWTError, jwt
from pwdlib import PasswordHash
@@ -19,14 +20,33 @@
from backend.app.admin.schema.user import GetUserInfoWithRelationDetail
from backend.common.dataclasses import AccessToken, NewToken, RefreshToken, TokenPayload
from backend.common.exception import errors
+from backend.common.exception.errors import TokenError
from backend.core.conf import settings
from backend.database.db import async_db_session
from backend.database.redis import redis_client
from backend.utils.serializers import select_as_dict
from backend.utils.timezone import timezone
+
+class CustomHTTPBearer(HTTPBearer):
+ """
+ 自定义 HTTPBearer 认证类
+
+ 重写 __call__ 方法,当认证失败时:
+ - 如果原始异常是 403 状态码,则抛出 401 状态码的 TokenError
+ - 其他异常保持原样抛出
+ """
+ async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None:
+ try:
+ return await super().__call__(request)
+ except HTTPException as e:
+ if e.status_code == 403:
+ raise TokenError()
+ raise e
+
+
# JWT authorizes dependency injection
-DependsJwtAuth = Depends(HTTPBearer())
+DependsJwtAuth = Depends(CustomHTTPBearer())
password_hash = PasswordHash((BcryptHasher(),))
@@ -156,7 +176,7 @@ async def create_refresh_token(session_uuid: str, user_id: int, multi_login: boo
async def create_new_token(
- refresh_token: str, session_uuid: str, user_id: int, multi_login: bool, **kwargs
+ refresh_token: str, session_uuid: str, user_id: int, multi_login: bool, **kwargs
) -> NewToken:
"""
生成新的 token
diff --git a/backend/plugin/code_generator/service/business_service.py b/backend/plugin/code_generator/service/business_service.py
index f7027199e..12f1680d3 100644
--- a/backend/plugin/code_generator/service/business_service.py
+++ b/backend/plugin/code_generator/service/business_service.py
@@ -43,7 +43,7 @@ async def create(*, obj: CreateGenBusinessParam) -> None:
async with async_db_session.begin() as db:
business = await gen_business_dao.get_by_name(db, obj.table_name)
if business:
- raise errors.ForbiddenError(msg='代码生成业务已存在')
+ raise errors.ConflictError(msg='代码生成业务已存在')
await gen_business_dao.create(db, obj)
@staticmethod
diff --git a/backend/plugin/code_generator/service/column_service.py b/backend/plugin/code_generator/service/column_service.py
index efcfa7cb9..7879b71b1 100644
--- a/backend/plugin/code_generator/service/column_service.py
+++ b/backend/plugin/code_generator/service/column_service.py
@@ -76,7 +76,7 @@ async def update(*, pk: int, obj: UpdateGenModelParam) -> int:
if obj.name != model.name:
gen_models = await gen_model_dao.get_all_by_business(db, obj.gen_business_id)
if obj.name in [gen_model.name for gen_model in gen_models]:
- raise errors.ForbiddenError(msg='模型列名已存在')
+ raise errors.ConflictError(msg='模型列名已存在')
pd_type = sql_type_to_pydantic(obj.type)
return await gen_model_dao.update(db, pk, obj, pd_type=pd_type)
diff --git a/backend/plugin/code_generator/service/gen_service.py b/backend/plugin/code_generator/service/gen_service.py
index ab28743a6..cb6c7c2bf 100644
--- a/backend/plugin/code_generator/service/gen_service.py
+++ b/backend/plugin/code_generator/service/gen_service.py
@@ -55,7 +55,7 @@ async def import_business_and_model(*, obj: ImportParam) -> None:
business_info = await gen_business_dao.get_by_name(db, obj.table_name)
if business_info:
- raise errors.ForbiddenError(msg='已存在相同数据库表业务')
+ raise errors.ConflictError(msg='已存在相同数据库表业务')
table_name = table_info[0]
new_business = GenBusiness(
diff --git a/backend/plugin/config/service/config_service.py b/backend/plugin/config/service/config_service.py
index bd799f747..5bbd0c942 100644
--- a/backend/plugin/config/service/config_service.py
+++ b/backend/plugin/config/service/config_service.py
@@ -52,10 +52,10 @@ async def create(*, obj: CreateConfigParam) -> None:
"""
async with async_db_session.begin() as db:
if obj.type in settings.CONFIG_BUILT_IN_TYPES:
- raise errors.ForbiddenError(msg='非法类型参数')
+ raise errors.RequestError(msg='非法类型参数')
config = await config_dao.get_by_key(db, obj.key)
if config:
- raise errors.ForbiddenError(msg=f'参数配置 {obj.key} 已存在')
+ raise errors.ConflictError(msg=f'参数配置 {obj.key} 已存在')
await config_dao.create(db, obj)
@staticmethod
@@ -74,7 +74,7 @@ async def update(*, pk: int, obj: UpdateConfigParam) -> int:
if config.key != obj.key:
config = await config_dao.get_by_key(db, obj.key)
if config:
- raise errors.ForbiddenError(msg=f'参数配置 {obj.key} 已存在')
+ raise errors.ConflictError(msg=f'参数配置 {obj.key} 已存在')
count = await config_dao.update(db, pk, obj)
return count
diff --git a/backend/plugin/dict/service/dict_data_service.py b/backend/plugin/dict/service/dict_data_service.py
index 3e3b6b978..c6c8b7c1f 100644
--- a/backend/plugin/dict/service/dict_data_service.py
+++ b/backend/plugin/dict/service/dict_data_service.py
@@ -50,7 +50,7 @@ async def create(*, obj: CreateDictDataParam) -> None:
async with async_db_session.begin() as db:
dict_data = await dict_data_dao.get_by_label(db, obj.label)
if dict_data:
- raise errors.ForbiddenError(msg='字典数据已存在')
+ raise errors.ConflictError(msg='字典数据已存在')
dict_type = await dict_type_dao.get(db, obj.type_id)
if not dict_type:
raise errors.NotFoundError(msg='字典类型不存在')
@@ -71,7 +71,7 @@ async def update(*, pk: int, obj: UpdateDictDataParam) -> int:
raise errors.NotFoundError(msg='字典数据不存在')
if dict_data.label != obj.label:
if await dict_data_dao.get_by_label(db, obj.label):
- raise errors.ForbiddenError(msg='字典数据已存在')
+ raise errors.ConflictError(msg='字典数据已存在')
dict_type = await dict_type_dao.get(db, obj.type_id)
if not dict_type:
raise errors.NotFoundError(msg='字典类型不存在')
diff --git a/backend/plugin/dict/service/dict_type_service.py b/backend/plugin/dict/service/dict_type_service.py
index 3ed1c3ff9..4112b7ff3 100644
--- a/backend/plugin/dict/service/dict_type_service.py
+++ b/backend/plugin/dict/service/dict_type_service.py
@@ -34,7 +34,7 @@ async def create(*, obj: CreateDictTypeParam) -> None:
async with async_db_session.begin() as db:
dict_type = await dict_type_dao.get_by_code(db, obj.code)
if dict_type:
- raise errors.ForbiddenError(msg='字典类型已存在')
+ raise errors.ConflictError(msg='字典类型已存在')
await dict_type_dao.create(db, obj)
@staticmethod
@@ -52,7 +52,7 @@ async def update(*, pk: int, obj: UpdateDictTypeParam) -> int:
raise errors.NotFoundError(msg='字典类型不存在')
if dict_type.code != obj.code:
if await dict_type_dao.get_by_code(db, obj.code):
- raise errors.ForbiddenError(msg='字典类型已存在')
+ raise errors.ConflictError(msg='字典类型已存在')
count = await dict_type_dao.update(db, pk, obj)
return count
diff --git a/backend/plugin/tools.py b/backend/plugin/tools.py
index 6e20b8375..0c2d78b56 100644
--- a/backend/plugin/tools.py
+++ b/backend/plugin/tools.py
@@ -331,4 +331,4 @@ async def __call__(self, request: Request) -> None:
log.error(f'插件 {self.plugin} 状态未初始化或丢失,需重启服务自动修复')
raise PluginInjectError(f'插件 {self.plugin} 状态未初始化或丢失,请联系系统管理员')
if not int(plugin_status.get(self.plugin)):
- raise errors.ForbiddenError(msg=f'插件 {self.plugin} 未启用,请联系系统管理员')
+ raise errors.ServerError(msg=f'插件 {self.plugin} 未启用,请联系系统管理员')
diff --git a/backend/utils/file_ops.py b/backend/utils/file_ops.py
index fbca97398..7fd78dd39 100644
--- a/backend/utils/file_ops.py
+++ b/backend/utils/file_ops.py
@@ -38,18 +38,18 @@ def file_verify(file: UploadFile) -> None:
filename = file.filename
file_ext = filename.split('.')[-1].lower()
if not file_ext:
- raise errors.ForbiddenError(msg='未知的文件类型')
+ raise errors.RequestError(msg='未知的文件类型')
if file_ext == FileType.image:
if file_ext not in settings.UPLOAD_IMAGE_EXT_INCLUDE:
- raise errors.ForbiddenError(msg='此图片格式暂不支持')
+ raise errors.RequestError(msg='此图片格式暂不支持')
if file.size > settings.UPLOAD_IMAGE_SIZE_MAX:
- raise errors.ForbiddenError(msg='图片超出最大限制,请重新选择')
+ raise errors.RequestError(msg='图片超出最大限制,请重新选择')
elif file_ext == FileType.video:
if file_ext not in settings.UPLOAD_VIDEO_EXT_INCLUDE:
- raise errors.ForbiddenError(msg='此视频格式暂不支持')
+ raise errors.RequestError(msg='此视频格式暂不支持')
if file.size > settings.UPLOAD_VIDEO_SIZE_MAX:
- raise errors.ForbiddenError(msg='视频超出最大限制,请重新选择')
+ raise errors.RequestError(msg='视频超出最大限制,请重新选择')
async def upload_file(file: UploadFile) -> str:
diff --git a/backend/utils/snowflake.py b/backend/utils/snowflake.py
index 225cafaaf..1a2d16249 100644
--- a/backend/utils/snowflake.py
+++ b/backend/utils/snowflake.py
@@ -54,9 +54,9 @@ def __init__(
:param sequence: 起始序列号
"""
if cluster_id < 0 or cluster_id > SnowflakeConfig.MAX_DATACENTER_ID:
- raise errors.ForbiddenError(msg=f'集群编号必须在 0-{SnowflakeConfig.MAX_DATACENTER_ID} 之间')
+ raise errors.RequestError(msg=f'集群编号必须在 0-{SnowflakeConfig.MAX_DATACENTER_ID} 之间')
if node_id < 0 or node_id > SnowflakeConfig.MAX_WORKER_ID:
- raise errors.ForbiddenError(msg=f'节点编号必须在 0-{SnowflakeConfig.MAX_WORKER_ID} 之间')
+ raise errors.RequestError(msg=f'节点编号必须在 0-{SnowflakeConfig.MAX_WORKER_ID} 之间')
self.node_id = node_id
self.cluster_id = cluster_id
From b1b1d3f04b70cb87b24b7950448fd6d710e69853 Mon Sep 17 00:00:00 2001
From: downdawn <1436759077@qq.com>
Date: Mon, 23 Jun 2025 11:36:50 +0800
Subject: [PATCH 10/10] docs: Add custom HTTPBearer annotation
---
backend/common/security/jwt.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/backend/common/security/jwt.py b/backend/common/security/jwt.py
index 717a4df06..bb82c7044 100644
--- a/backend/common/security/jwt.py
+++ b/backend/common/security/jwt.py
@@ -32,9 +32,7 @@ class CustomHTTPBearer(HTTPBearer):
"""
自定义 HTTPBearer 认证类
- 重写 __call__ 方法,当认证失败时:
- - 如果原始异常是 403 状态码,则抛出 401 状态码的 TokenError
- - 其他异常保持原样抛出
+ https://github.com/fastapi/fastapi/issues/10177
"""
async def __call__(self, request: Request) -> HTTPAuthorizationCredentials | None:
try: