Skip to content

Commit f254569

Browse files
committed
update 1.1.4
1 parent f499c8a commit f254569

File tree

5 files changed

+138
-62
lines changed

5 files changed

+138
-62
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<p align="center">
66
<a href="https://gitee.com/senqi666/fastapi-vue-admin"><img src="https://gitee.com/senqi666/fastapi-vue-admin/badge/star.svg?theme=dark"></a>
77
<a href="https://github.com/SenQi-666/fastapi-vue-admin"><img src="https://img.shields.io/github/stars/SenQi-666/fastapi-vue-admin?style=social"></a>
8-
<a href="https://github.com/SenQi-666/fastapi-vue-admin/blob/main/LICENSE"><img src="https://img.shields.io/badge/License-MIT-orange"></a>
8+
<a href="https://gitee.com/senqi666/fastapi-vue-admin/blob/master/LICENSE"><img src="https://img.shields.io/badge/License-MIT-orange"></a>
99
<img src="https://img.shields.io/badge/Python-≥3.10-blue">
1010
<img src="https://img.shields.io/badge/NodeJS-≥20.0-blue">
1111
</p>
@@ -41,14 +41,14 @@ PC端演示地址:https://fastapi-vue-admin.senqiweb.cn
4141

4242
### 获取代码
4343

44-
> git clone https://github.com/SenQi-666/fastapi-vue-admin.git
44+
> git clone https://gitee.com/senqi666/fastapi-vue-admin.git
4545
4646
### 准备工作
4747

4848
```
4949
Python == 3.10(其他版本均未测试)
5050
nodejs >= 20.0(推荐使用最新版)
51-
PgSQL(推荐使用最新版
51+
PgSQL == 14(其他版本均未测试
5252
Redis(推荐使用最新版)
5353
```
5454

backend/app/scripts/initialize/main.py

Lines changed: 58 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,69 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33

4+
from typing import TypeVar, List, Dict
45
from app.core.config import settings
5-
from sqlalchemy import create_engine
6+
from sqlalchemy import create_engine, text
67
from sqlalchemy.orm import sessionmaker
78
from app.models.base import Model
89
from app.models import system
910
from pathlib import Path
1011
import orjson
1112

1213

14+
ModelType = TypeVar("ModelType", bound=Model)
15+
16+
1317
class InitializeData:
1418
"""
1519
初始化数据
1620
"""
1721

1822
SCRIPT_DIR: Path = Path.joinpath(settings.BASE_DIR, 'scripts', 'initialize')
1923

20-
def __init__(self):
24+
def __init__(self) -> None:
2125
self.engine = create_engine(self.__get_db_url(), echo=True, future=True)
2226
self.DBSession = sessionmaker(bind=self.engine)
23-
24-
def __get_db_url(self):
27+
self.prepare_init_models = [
28+
system.DeptModel,
29+
system.UserModel,
30+
system.MenuModel,
31+
system.PositionModel,
32+
system.RoleModel,
33+
system.OperationLogModel,
34+
system.RoleDeptsModel,
35+
system.RoleMenusModel,
36+
system.UserPositionsModel,
37+
system.UserRolesModel
38+
]
39+
40+
def __get_db_url(self) -> str:
2541
scheme = settings.SQL_DB_URL.scheme.split('+')[0]
2642
new_db_url = settings.SQL_DB_URL.unicode_string().replace(settings.SQL_DB_URL.scheme, scheme)
2743
return new_db_url
2844

29-
def __init_model(self):
45+
def __init_model(self) -> None:
3046
print("开始初始化数据库...")
3147
Model.metadata.create_all(
3248
self.engine,
33-
tables=[
34-
system.DeptModel.__table__,
35-
system.UserModel.__table__,
36-
system.MenuModel.__table__,
37-
system.PositionModel.__table__,
38-
system.RoleModel.__table__,
39-
system.OperationLogModel.__table__,
40-
system.RoleDeptsModel.__table__,
41-
system.RoleMenusModel.__table__,
42-
system.UserPositionsModel.__table__,
43-
system.UserRolesModel.__table__
44-
]
49+
tables=[modal.__table__ for modal in self.prepare_init_models]
4550
)
4651
print("数据库初始化完成!")
4752

48-
def __init_data(self):
53+
def __init_data(self) -> None:
4954
print("开始初始化数据...")
50-
self.__init_dept()
51-
self.__init_user()
52-
self.__init_menu()
53-
self.__init_position()
54-
self.__init_role()
55-
self.__init_role_depts()
56-
self.__init_role_menus()
57-
self.__init_user_positions()
58-
self.__init_user_roles()
55+
56+
for model in self.prepare_init_models:
57+
max_rows = self.__generate_data(model)
58+
self.__update_sequence(model, max_rows)
59+
5960
print("数据初始化完成!")
6061

61-
def __generate_data(self, table_name: str, model: Model):
62+
def __generate_data(self, model: ModelType) -> int:
6263
session = self.DBSession()
6364

65+
table_name = model.__tablename__
66+
6467
data = self.__get_data(table_name)
6568
objs = [model(**item) for item in data]
6669
session.add_all(objs)
@@ -69,40 +72,41 @@ def __generate_data(self, table_name: str, model: Model):
6972
session.close()
7073
print(f"{table_name} 表数据已生成!")
7174

72-
def __get_data(self, filename: str):
73-
json_path = Path.joinpath(self.SCRIPT_DIR, 'data', f'{filename}.json')
74-
with open(json_path, 'r', encoding='utf-8') as f:
75-
data = orjson.loads(f.read())
76-
return data
75+
return len(objs)
7776

78-
def __init_dept(self):
79-
self.__generate_data("system_dept", system.DeptModel)
77+
def __get_data(self, filename: str) -> List[Dict]:
78+
try:
79+
json_path = Path.joinpath(self.SCRIPT_DIR, 'data', f'{filename}.json')
80+
with open(json_path, 'r', encoding='utf-8') as f:
81+
data = orjson.loads(f.read())
82+
return data
8083

81-
def __init_menu(self):
82-
self.__generate_data("system_menu", system.MenuModel)
84+
except FileNotFoundError:
85+
return []
8386

84-
def __init_position(self):
85-
self.__generate_data("system_position", system.PositionModel)
87+
def __update_sequence(self, model: ModelType, max_rows: int) -> None:
88+
table_name = model.__tablename__
8689

87-
def __init_role(self):
88-
self.__generate_data("system_role", system.RoleModel)
90+
# 检查模型中是否有自增字段
91+
sequence_name = None
92+
for col in model.__table__.columns:
93+
if col.autoincrement is True:
94+
sequence_name = f"{table_name}_{col.name}_seq"
95+
break
8996

90-
def __init_role_depts(self):
91-
self.__generate_data("system_role_depts", system.RoleDeptsModel)
92-
93-
def __init_role_menus(self):
94-
self.__generate_data("system_role_menus", system.RoleMenusModel)
95-
96-
def __init_user(self):
97-
self.__generate_data("system_user", system.UserModel)
97+
if not sequence_name:
98+
print(f"{table_name} 表无需设置自增序列值")
99+
return
98100

99-
def __init_user_positions(self):
100-
self.__generate_data("system_user_positions", system.UserPositionsModel)
101+
session = self.DBSession()
101102

102-
def __init_user_roles(self):
103-
self.__generate_data("system_user_roles", system.UserRolesModel)
103+
# 更新序列最大值
104+
new_value = max_rows + 1
105+
session.execute(text(f"ALTER SEQUENCE {sequence_name} RESTART WITH {new_value}"))
106+
session.commit()
107+
print(f"{table_name} 表的自增序列值已更新!")
104108

105-
def run(self):
109+
def run(self) -> None:
106110
"""
107111
执行初始化
108112
"""

backend/app/services/system/auth_service.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
from sqlalchemy.ext.asyncio import AsyncSession
1313
from app.core.exceptions import CustomException
1414
from aioredis import Redis
15-
from captcha.image import ImageCaptcha
16-
from io import BytesIO
17-
from app.utils.tools import get_random_character
15+
from app.utils.tools import get_random_character, generate_captcha
1816
from datetime import timedelta
1917
from app.core.config import settings
2018
from app.crud.system import UserCRUD
@@ -104,7 +102,7 @@ async def get_captcha(cls, redis: Redis) -> Dict[str, Union[CaptchaKey, CaptchaB
104102
random_strings = random.sample(list(total_strings), 4)
105103
captcha_string = "".join(random_strings)
106104

107-
captcha: BytesIO = ImageCaptcha().generate(captcha_string)
105+
captcha = generate_captcha(captcha_string)
108106
captcha_bytes = captcha.getvalue()
109107
captcha_base64 = base64.b64encode(captcha_bytes).decode()
110108

backend/app/utils/tools.py

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
# -*- coding: utf-8 -*-
33

44
from typing import Any, List, Dict, Sequence, Optional
5-
import importlib, uuid
5+
import importlib, uuid, random
66
from pathlib import Path
77
from app.crud.base import ModelType
88
from app.models.base import Model
99
from sqlalchemy.sql.elements import ColumnElement
1010
from fastapi import UploadFile
1111
from app.core.config import settings
12+
from io import BytesIO
13+
from PIL import Image, ImageDraw, ImageFont
1214

1315

1416
def import_module(module: str) -> Any:
@@ -67,6 +69,78 @@ def get_random_character() -> str:
6769
return uuid.uuid4().hex
6870

6971

72+
# 生成带有噪声和干扰的验证码图片
73+
def generate_captcha(code) -> BytesIO:
74+
"""
75+
生成带有噪声和干扰的验证码图片
76+
:return: 验证码图片流
77+
"""
78+
# 创建一张随机颜色背景的图片
79+
background_color = (random.randint(200, 255), random.randint(200, 255), random.randint(200, 255))
80+
width, height = 160, 60
81+
image = Image.new('RGB', (width, height), color=background_color)
82+
83+
# 获取一个绘图对象
84+
draw = ImageDraw.Draw(image)
85+
86+
# 字体设置(如果需要自定义字体,请替换下面的字体路径)
87+
font = ImageFont.truetype("./app/resources/gantians.otf", 42)
88+
89+
# 计算验证码文本的总宽度
90+
total_text_width = 0
91+
for char in code:
92+
# 计算文本的宽度
93+
bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).textbbox((0, 0), char, font=font)
94+
text_width = bbox[2] - bbox[0]
95+
total_text_width += text_width
96+
97+
# 计算每个字符的起始位置
98+
x_offset = (width - total_text_width) / 2
99+
# 计算文本的高度
100+
bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).textbbox((0, 0), code[0], font=font)
101+
text_height = bbox[3] - bbox[1]
102+
y_offset = (height - text_height) / 2 - draw.textbbox((0, 0), code[0], font=font)[1]
103+
104+
# 绘制每个字符(单独的颜色和扭曲)
105+
for char in code:
106+
# 随机选择字体颜色
107+
text_color = (random.randint(0, 100), random.randint(0, 100), random.randint(0, 100))
108+
109+
# 计算字符位置并稍微扭曲
110+
bbox = ImageDraw.Draw(Image.new('RGB', (1, 1))).textbbox((0, 0), char, font=font)
111+
char_width = bbox[2] - bbox[0]
112+
char_x = x_offset + random.uniform(-3, 3)
113+
char_y = y_offset + random.uniform(-5, 5)
114+
115+
# 绘制字符
116+
draw.text((char_x, char_y), char, font=font, fill=text_color)
117+
118+
# 更新下一个字符的位置
119+
x_offset += char_width + random.uniform(2, 8)
120+
121+
# 添加少量的圆圈干扰
122+
for _ in range(random.randint(2, 4)):
123+
# 随机位置和大小
124+
x = random.randint(0, width)
125+
y = random.randint(0, height)
126+
radius = random.randint(5, 10)
127+
draw.ellipse((x - radius, y - radius, x + radius, y + radius), outline=text_color)
128+
129+
# 添加少量的噪点
130+
for _ in range(random.randint(10, 20)):
131+
x = random.randint(0, width - 1)
132+
y = random.randint(0, height - 1)
133+
noise_size = random.randint(2, 4)
134+
noise_color = (random.randint(0, 50), random.randint(0, 50), random.randint(0, 50))
135+
draw.rectangle([x, y, x + noise_size, y + noise_size], fill=noise_color)
136+
137+
# 返回验证码图片流
138+
stream = BytesIO()
139+
image.save(stream, format='PNG')
140+
141+
return stream
142+
143+
70144
def get_parent_id_map(model_list: Sequence[Model]) -> Dict[int, int]:
71145
"""
72146
获取父级ID的映射集

backend/requirements.txt

156 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)