Compare commits

...

34 Commits

Author SHA1 Message Date
2d597bdeae project后端问题修改完成。 2024-11-21 16:38:41 +08:00
7077658a9b 删除错误导入,适配前端租户delete格式 2024-11-21 12:19:42 +08:00
251a471700 Merge remote-tracking branch 'origin/main' 2024-11-21 12:14:00 +08:00
7d1fc93e6f 项目管理中,添加了对传入日期的格式处理,修复了一些bug 2024-11-21 12:13:19 +08:00
752ba0b7c9 修改get项目时的返回内容格式,去除多余字典层,直接返回列表。 2024-11-21 11:17:19 +08:00
d580c3206d 添加了少量注释,去除了测试用的代码与对应注释。修复了前端传入estimator而后端接受estimators的错误,修复了新增项目时owner_id恒为1的错误 2024-11-21 10:59:14 +08:00
09cbf2eae8 登录过期时间改为15天 2024-11-20 22:54:43 +08:00
f5687c40ac 为了绿色勾勾! 2024-11-20 22:27:00 +08:00
1d3c024b35 简单优化一下格式,结束了 2024-11-20 22:23:19 +08:00
322b3408ab merge 2024-11-20 22:13:01 +08:00
924b2864d3 Merge remote-tracking branch 'refs/remotes/origin/dev/pjq' 2024-11-20 22:11:13 +08:00
854c5568e2 逻辑优化 2024-11-20 22:09:33 +08:00
4cf43c75d9 适应接口更改 2024-11-20 22:08:54 +08:00
b2fbe2cf4a CORS最后一commit 2024-11-20 22:08:21 +08:00
4ef428c620 项目管理对注释进行了修改,租户管理权限验证完成。 2024-11-20 22:04:13 +08:00
7af908a2af 修复tenant_id不能为空的问题
(cherry picked from commit 298830cd1e)
2024-11-20 21:18:29 +08:00
298830cd1e 修复tenant_id不能为空的问题 2024-11-20 21:18:05 +08:00
8d7d6f95ba 修复tenant_id不能为空的问题
(cherry picked from commit 8f2745aa41)
2024-11-20 21:17:24 +08:00
8f2745aa41 修复tenant_id不能为空的问题 2024-11-20 21:16:55 +08:00
ee96d2b22e 项目管理权限验证完成,添加了登录的普通用户可以查看所属项目的项目信息 2024-11-20 21:02:44 +08:00
e86d299dc1 Merge branch 'refs/heads/dev/pjq'
# Conflicts:
#	api/manage_project.py
#	api/manage_tanant.py
#	api/manage_user.py
#	dependencies.py
2024-11-20 20:02:42 +08:00
601cad31eb 项目管理bug修复,无权限验证 2024-11-20 19:48:02 +08:00
dfe85ce565 完成了租户管理的所有功能,暂时去除了权限管理以测试代码 2024-11-20 19:24:23 +08:00
bb11107f47 cors 2024-11-20 18:49:11 +08:00
31b74e8fff 修了些项目管理的bug,完成了不带权限验证的列举所有租户 2024-11-20 18:31:35 +08:00
15f467c23f 新增登录时返回用户类型 2024-11-20 18:15:09 +08:00
febc1eaca0 添加mysql支持 2024-11-20 17:49:51 +08:00
6f04dd699c 添加mysql支持 2024-11-20 17:32:32 +08:00
0912abdf29 连接到远程服务器测试通过。 2024-11-20 17:17:50 +08:00
0594809a48 项目管理_删除项目功能完成,依旧是暂时去除了权限验证以测试代码 2024-11-20 15:07:21 +08:00
a8e9a4920b dependencies中添加了一些import
项目管理中,新增与修改都已完成(暂时去除了身份验证)
ps.现在用的json是否带有project_id来区别新增与修改
2024-11-20 14:55:36 +08:00
20b7429aea dependencies中添加了一些import
项目管理中,新增大概是没问题的吧(暂时去除了身份验证)
2024-11-20 14:45:46 +08:00
5864efcf3e 将各个api.py中的示例代码注释掉了,租户管理简单完成了列举所有租户,项目管理简单完成了列举所有项目。
(设置python环境的时候自动加了些奇奇怪怪的文件更改,应该不至于出问题)
2024-11-19 21:38:36 +08:00
17cf72777f 测试 2024-11-19 20:15:57 +08:00
14 changed files with 369 additions and 17 deletions

2
.env
View File

@ -1,3 +1,3 @@
ALGORITHM=HS256
DATABASE_URL=sqlite:///test.db
DATABASE_URL=mysql://shixun:TEzzsLddDRdDwXdE@120.53.31.148:3306/shixun
SECRET_KEY=your_secret_key

View File

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="jdk" jdkName="demo1" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -38,5 +38,26 @@
</library>
</libraries>
</data-source>
<data-source source="LOCAL" name="test [2]" uuid="13832c0f-6caa-4909-a819-f7b92b095c8f">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:C:\Users\15089\PycharmProjects\CostEvalPlatform\test.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
<libraries>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
</library>
<library>
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
</library>
</libraries>
</data-source>
</component>
</project>

14
.idea/deployment.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PublishConfigData" serverName="ecs2" remoteFilesAllowedToDisappearOnAutoupload="false">
<serverData>
<paths name="ecs2">
<serverdata>
<mappings>
<mapping deploy="./shixun/" local="$PROJECT_DIR$" web="/" />
</mappings>
</serverdata>
</paths>
</serverData>
</component>
</project>

14
.idea/webServers.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="WebServers">
<option name="servers">
<webServer id="c90c26ce-7c47-4054-9829-4cec3d848165" name="ecs2" url="http://ecs2.heshunme.xyz:10000">
<fileTransfer rootFolder="/root" accessType="SFTP" host="ecs2.heshunme.xyz" port="22" sshConfigId="f1f6e1a9-94a6-4cd9-ba1a-ad35f1bc3d3e" sshConfig="root@ecs2.heshunme.xyz:22 key" keyPair="true">
<advancedOptions>
<advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" />
</advancedOptions>
</fileTransfer>
</webServer>
</option>
</component>
</project>

View File

@ -20,7 +20,7 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None, s
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
expire = datetime.utcnow() + timedelta(days=15)
to_encode.update({"exp": expire})
print(settings, type(settings))
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
@ -30,6 +30,8 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None, s
# 登录路由
@router.post("/api/s1/login")
async def login(response: Response, user_data: dict, session: SessionDep):
if user_data.get('username') is None or user_data.get('password') is None:
raise HTTPException(status_code=401, detail="用户名或密码不能为空")
# 查询用户
user = session.exec(select(User).where(User.username == user_data['username'])).first()
@ -41,12 +43,19 @@ async def login(response: Response, user_data: dict, session: SessionDep):
token = create_access_token(data={"id": user.id, "role": user.role, "tanant_id": user.tenant.id})
# 设置cookie
response.set_cookie(key="session_token", value=token, httponly=True)
response.set_cookie(
key="session_token",
value=token,
httponly=True,
# domain=".ecs2.heshunme.xyz", # 确保域正确
samesite="none", # 根据需要设置samesite属性
secure=True,
)
# 关闭数据库会话
session.close()
return {"message": f"Login successful"}
return {"message": f"Login successful", "role": user.role}
@router.post("/api/s1/register")

View File

@ -2,9 +2,158 @@
# @Time : 2024/11/19 下午8:05
# @FileName: manage_project.py
# @Software: PyCharm
from datetime import datetime
from typing import List
from fastapi import APIRouter
from sqlalchemy import delete
from dependencies import *
from models import Project, ProjectUserLink
router = APIRouter()
TenantRole = 1
# 列举所有项目
@router.get("/api/s1/project")
async def get_project(response: Response, session: SessionDep, current_user: User = Depends(get_current_user)):
# 只有角色为 0、1、2 或 3 的用户才可以访问
if current_user.role == 0:
# 角色为0显示所有项目
projects = session.query(Project).all()
elif current_user.role == 1:
# 角色为1显示tenant_id匹配的项目即属于当前租户的项目
projects = session.query(Project).filter(Project.owner_id == current_user.tenant_id).all()
elif current_user.role in [2, 3]:
# 角色为2或3显示与当前用户相关联的项目
projects = (
session.query(Project)
.join(ProjectUserLink)
.filter(ProjectUserLink.user_id == current_user.id)
.all()
)
else:
raise HTTPException(status_code=403, detail="You do not have permission to view projects.")
if not projects:
raise HTTPException(status_code=404, detail="Project not found or you have no projects.")
# 返回项目的基本信息
return [
{
"name": project.name,
"requirement": project.requirement,
"start_time": project.start_time,
"deadline": project.deadline
}
for project in projects
]
# 新增与修改项目
@router.post("/api/s1/project")
async def create_project(data: dict, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 1:
raise HTTPException(status_code=403, detail="Only Tenant admin users can add or update projects.")
# project_id = data.get("project_id")
name = data.get("name")
requirement = data.get("requirement")
start_time_str = data.get("start_time")
deadline_str = data.get("deadline")
estimators = data.get("estimator")
auditors = data.get("auditor")
# 验证是否缺少必要参数
if not name or not requirement or not start_time_str or not deadline_str:
raise HTTPException(status_code=400, detail="Need more details")
# 验证开始时间是否早于结束时间
# 去掉 'Z' 和毫秒部分
start_time_str = start_time_str.split('.')[0].rstrip('Z')
deadline_str = deadline_str.split('.')[0].rstrip('Z')
start_time = datetime.strptime(start_time_str, "%Y-%m-%dT%H:%M:%S")
deadline = datetime.strptime(deadline_str, "%Y-%m-%dT%H:%M:%S")
if start_time > deadline:
raise HTTPException(status_code=400, detail="Start time must be before deadline")
# 验证是否有传入评估/审核员
if not estimators or not auditors:
raise HTTPException(status_code=400, detail="Need more estimators or auditors")
users: List[User] = []
# 验证评估审核员是否存在
for username in estimators + auditors:
query_estimator = select(User).where(User.username == username)
if user := session.exec(query_estimator).first():
users.append(user)
project = session.exec(select(Project).where(Project.name == name)).first()
if project and project.owner_id != current_user.tenant_id:
raise HTTPException(status_code=403, detail="You do not have permission to modify this project.")
# 更新项目还是新增项目
if project:
# 更新项目内容
project.name = name
project.requirement = requirement
project.start_time = start_time
project.deadline = deadline
else:
project = Project(
name=name,
requirement=requirement,
start_time=start_time,
deadline=deadline,
owner_id=current_user.tenant_id,
)
# 处理项目和用户的关联
# 先清除现有的关联
# 生成删除语句并执行
project.users = []
project.users = users
session.add(project)
session.commit()
# 提交事务
session.commit()
session.refresh(project)
return {"message": "Added or updated successfully",
"information": project,
}
# 删除项目
@router.delete("/api/s1/project")
async def delete_project(name: str, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 1:
raise HTTPException(status_code=403, detail="Only Tenant admin users can delete projects.")
# project_name = data.get("name")
project_name = name
if not project_name:
raise HTTPException(status_code=400, detail="Project name is required")
# 查找项目
project = session.exec(
select(Project).where(Project.name == project_name)).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
# 删除与项目相关的用户链接
# 先清除现有的关联
stmt = delete(ProjectUserLink).where(ProjectUserLink.project_id == project.id)
session.execute(stmt)
# 删除项目
session.delete(project)
session.commit()
return {"detail": "Project deleted successfully"}

View File

@ -3,6 +3,137 @@
# @FileName: manage_tanant.py
# @Software: PyCharm
from fastapi import APIRouter
from sqlalchemy import delete
from dependencies import *
router = APIRouter()
from fastapi import HTTPException, Response
from models import Tenant, User # 假设你已导入 Tenant 和 User 模型
from dependencies import SessionDep # 假设 SessionDep 是数据库会话的依赖
# 列举所有租户
@router.get("/api/s1/tenant")
async def get_tenant(response: Response, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 0:
raise HTTPException(status_code=403, detail="Only Superadmin can list all tenants.")
tenants = session.query(Tenant).all() # 获取所有租户
if not tenants:
raise HTTPException(status_code=404, detail="No tenants found")
tenant_data = []
for tenant in tenants:
# 获取该租户中 role=1 的第一个用户(如果存在)
tenant_user = next((user for user in tenant.users if user.role == 1), None)
# 获取该租户中除了 role=1 以外的用户数量
user_num = len([user for user in tenant.users if user.role != 1])
# 构建租户信息
tenant_info = {
"name": tenant.name,
"username": tenant_user.username if tenant_user else None, # 如果找到 role=1 的用户,返回其 username
"user_num": user_num # 除去 role=1 的用户数量
}
tenant_data.append(tenant_info)
return {"tenants": tenant_data}
# 新增和修改租户
@router.post("/api/s1/tenant")
async def create_or_update_tenant(data: dict, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 0:
raise HTTPException(status_code=403, detail="Only Superadmin can add or update tenants.")
name = data.get("name")
username = data.get("username")
password = data.get("password")
# 验证是否缺少必要参数
if not name:
raise HTTPException(status_code=400, detail="Need more name")
if username:
# 如果 username 不为空,判断为新建租户
# 检查租户名是否已存在
tenant_query = select(Tenant).where(Tenant.name == name)
existing_tenant = session.exec(tenant_query).first()
if existing_tenant:
raise HTTPException(status_code=409, detail="Tenant name already exists")
# 创建新租户
tenant = Tenant(name=name)
session.add(tenant)
session.commit()
session.refresh(tenant)
# 创建新用户
user = User(
username=username,
password=password, # 记得加密密码
role=1, # 默认role为1
tenant_id=tenant.id,
)
session.add(user)
# 提交事务
session.commit()
return {"message": "Tenant and User added successfully"}
else:
# 如果 username 为空,执行更新操作
# 根据租户名称查找 Tenant
tenant_query = select(Tenant).where(Tenant.name == name)
tenant = session.exec(tenant_query).first()
# 如果找不到对应的租户,抛出错误
if not tenant:
raise HTTPException(status_code=404, detail="Tenant not found")
# 找到租户后,根据 tenant_id 查找该租户下的所有用户
user_query = select(User).where(User.tenant_id == tenant.id)
user = session.exec(user_query).first()
# 如果找不到对应的用户,抛出错误
if not user:
raise HTTPException(status_code=404, detail="User not found")
user.password = password
session.add(user)
session.commit()
return {"message": "Tenant and User update successfully"}
# 删除租户
@router.delete("/api/s1/tenant")
async def delete_tenant(name: str, user_num: int, session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 0:
raise HTTPException(status_code=403, detail="Only Superadmin can delete tenants.")
tenant_name = name
if not tenant_name:
raise HTTPException(status_code=400, detail="Tenant name is required")
# 查找租户
tenant = session.exec(
select(Tenant).where(Tenant.name == tenant_name)).first()
if not tenant:
raise HTTPException(status_code=404, detail="Tenant not found")
# 删除与租户相关的用户
stmt = delete(User).where(User.tenant_id == tenant.id)
session.execute(stmt)
# 删除租户
session.delete(tenant)
session.commit()
return {"detail": "Tenant deleted successfully"}

View File

@ -3,7 +3,7 @@
# @Author : 河瞬
# @FileName: manage_user.py
# @Software: PyCharm
from fastapi import HTTPException, APIRouter, Depends, Request
from fastapi import HTTPException, APIRouter, Depends
from sqlmodel import select
from dependencies import SessionDep, get_current_user
@ -14,7 +14,7 @@ router = APIRouter()
# 枚举成员
@router.get("/api/s1/user")
async def list_users(request: Request, session: SessionDep, current_user: User = Depends(get_current_user)):
async def list_users(session: SessionDep, current_user: User = Depends(get_current_user)):
if current_user.role != 1:
raise HTTPException(status_code=403, detail="Only admin users can list users")
@ -30,7 +30,7 @@ async def add_or_update_user(data: dict, session: SessionDep, current_user: User
raise HTTPException(status_code=403, detail="Only admin users can add or update users")
username = data.get("username")
password = data.get("password")
password = data.get("password", None)
role = data.get("role")
if role not in ["auditor", "estimator"]:
raise HTTPException(status_code=400, detail="Invalid role")
@ -42,11 +42,9 @@ async def add_or_update_user(data: dict, session: SessionDep, current_user: User
user = session.exec(select(User).where(User.username == username, User.tenant_id == current_user.tenant_id)).first()
if user:
if password == "":
user.role = role
else:
if password:
user.password = password
user.role = role
user.role = role
session.add(user)
session.commit()
return {"detail": "User updated successfully"}

View File

@ -14,5 +14,6 @@ class Settings(BaseSettings):
class Config:
env_file = ".env"
if __name__ == '__main__':
print(Settings().ALGORITHM)
print(Settings().ALGORITHM)

13
main.py
View File

@ -1,6 +1,7 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from api import login_reg, manage_project, manage_tanant, manage_user
from database import create_db_and_tables
@ -20,6 +21,18 @@ async def lifespan(app: FastAPI):
app = FastAPI(lifespan=lifespan)
# noinspection PyTypeChecker
app.add_middleware(
CORSMiddleware,
# allow_origins=["*"],
allow_origins=["http://localhost:8080", "http://localhost:5000"],
# 允许所有来源,也可以指定具体的来源,例如 ["http://example.com", "https://example.com"]
allow_credentials=True, # 允许携带凭证如cookies
allow_methods=["*"], # 允许所有方法,也可以指定具体的方法,例如 ["GET", "POST", "PUT", "DELETE"]
allow_headers=["*"], # 允许所有头部,也可以指定具体的头部,例如 ["Content-Type", "Authorization"]
)
app.include_router(login_reg.router)
app.include_router(manage_tanant.router)
app.include_router(manage_user.router)

View File

@ -29,7 +29,7 @@ class User(SQLModel, table=True):
username: str = Field(index=True)
password: str
role: int
tenant_id: int = Field(default=None, foreign_key="Tenant.id")
tenant_id: int | None = Field(default=None, foreign_key="Tenant.id")
tenant: Tenant = Relationship(back_populates="users")
projects: List["Project"] = Relationship(back_populates="users", link_model=ProjectUserLink)

View File

@ -3,4 +3,6 @@ python-jose~=3.3.0
uvicorn~=0.32.0
pydantic~=2.9.2
pydantic-settings~=2.6.1
sqlmodel~=0.0.22
mysqlclient
sqlmodel~=0.0.22
sqlalchemy~=2.0.36

View File

@ -18,7 +18,7 @@ def create_db_and_tables():
# 创建一个测试客户端
client = TestClient(app)
session: Session = None
session: Session | None = None
class TestLoginReg(unittest.TestCase):
@ -69,7 +69,7 @@ class TestLoginReg(unittest.TestCase):
# 发送登录请求
response = client.post("/api/s1/login", json={"username": "testuser", "password": "testpassword"})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {"message": "Login successful"})
self.assertEqual(response.json(), {'message': 'Login successful', 'role': 1})
def test_register(self):
# 发送注册请求