393 lines
16 KiB
Python
393 lines
16 KiB
Python
from sqlalchemy.exc import SQLAlchemyError
|
|
from sqlalchemy.orm import Session
|
|
|
|
from backend.app.core.error_codes import ErrorCode
|
|
from backend.app.core.exceptions import AppException
|
|
from backend.app.repositories.system_repository import SystemRepository
|
|
|
|
|
|
class SystemService:
|
|
VALID_MENU_TYPES = {"catalog", "page", "button"}
|
|
|
|
def __init__(self) -> None:
|
|
self.repository = SystemRepository()
|
|
|
|
def list_users(self, session: Session | None = None, filters: dict | None = None) -> dict:
|
|
if session is not None:
|
|
try:
|
|
rows = self.repository.list_users(session, filters or {})
|
|
return {
|
|
"total": len(rows),
|
|
"page_no": 1,
|
|
"page_size": len(rows) or 20,
|
|
"list": [
|
|
self._build_user_row(user, role)
|
|
for user, role in rows
|
|
],
|
|
}
|
|
except SQLAlchemyError:
|
|
pass
|
|
|
|
return {
|
|
"total": 1,
|
|
"page_no": 1,
|
|
"page_size": 20,
|
|
"list": [
|
|
{
|
|
"user_id": 1,
|
|
"username": "sales01",
|
|
"real_name": "张三",
|
|
"mobile": "13800000000",
|
|
"role_id": 2,
|
|
"role_name": "业务员",
|
|
"role_code": "salesman",
|
|
"status": 1,
|
|
"created_at": "2026-05-14 10:00:00",
|
|
"updated_at": "2026-05-14 10:00:00",
|
|
}
|
|
],
|
|
}
|
|
|
|
def create_user(self, payload: dict, session: Session | None = None) -> dict:
|
|
self._validate_create_user(payload)
|
|
|
|
if session is not None:
|
|
try:
|
|
existed = self.repository.get_user_by_username(session, payload["username"])
|
|
if existed is not None:
|
|
raise AppException(code=ErrorCode.DUPLICATE, message="用户名已存在", status_code=400)
|
|
|
|
role = self.repository.get_role(session, payload["role_id"])
|
|
if role is None or role.status != 1:
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="角色不存在或已停用", status_code=404)
|
|
|
|
user = self.repository.create_user(
|
|
session,
|
|
{
|
|
**payload,
|
|
"password_hash": payload["password"],
|
|
},
|
|
)
|
|
session.commit()
|
|
return {
|
|
"user_id": user.id,
|
|
"username": user.username,
|
|
"real_name": user.real_name,
|
|
"mobile": user.mobile,
|
|
"role_id": user.role_id,
|
|
"status": user.status,
|
|
}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {
|
|
"user_id": 2,
|
|
"username": payload["username"],
|
|
"real_name": payload["real_name"],
|
|
"mobile": payload.get("mobile") or "",
|
|
"role_id": payload["role_id"],
|
|
"status": payload.get("status", 1),
|
|
}
|
|
|
|
def update_user(self, user_id: int, payload: dict, session: Session | None = None) -> dict:
|
|
self._validate_update_user(payload)
|
|
|
|
if session is not None:
|
|
try:
|
|
user = self.repository.get_user(session, user_id)
|
|
if user is None:
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="用户不存在", status_code=404)
|
|
|
|
role = self.repository.get_role(session, payload["role_id"])
|
|
if role is None or role.status != 1:
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="角色不存在或已停用", status_code=404)
|
|
|
|
self.repository.update_user(user, payload)
|
|
session.commit()
|
|
return {"user_id": user_id, "updated": True}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {"user_id": user_id, "updated": True}
|
|
|
|
def reset_password(self, user_id: int, new_password: str, session: Session | None = None) -> dict:
|
|
self._validate_password(new_password)
|
|
|
|
if session is not None:
|
|
try:
|
|
user = self.repository.get_user(session, user_id)
|
|
if user is None:
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="用户不存在", status_code=404)
|
|
|
|
self.repository.reset_password(user, new_password)
|
|
session.commit()
|
|
return {"user_id": user_id, "reset": True}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {"user_id": user_id, "reset": True}
|
|
|
|
def list_roles(self, session: Session | None = None, filters: dict | None = None) -> dict:
|
|
if session is not None:
|
|
try:
|
|
roles = self.repository.list_roles(session, filters or {})
|
|
return {
|
|
"total": len(roles),
|
|
"page_no": 1,
|
|
"page_size": len(roles) or 20,
|
|
"list": [
|
|
{
|
|
"role_id": role.id,
|
|
"role_name": role.role_name,
|
|
"role_code": role.role_code,
|
|
"status": role.status,
|
|
"remark": role.remark,
|
|
"created_at": role.created_at.strftime("%Y-%m-%d %H:%M:%S") if role.created_at else "",
|
|
}
|
|
for role in roles
|
|
],
|
|
}
|
|
except SQLAlchemyError:
|
|
pass
|
|
|
|
return {
|
|
"total": 2,
|
|
"page_no": 1,
|
|
"page_size": 20,
|
|
"list": [
|
|
{"role_id": 1, "role_name": "管理员", "role_code": "admin", "status": 1, "remark": "系统管理"},
|
|
{"role_id": 2, "role_name": "业务员", "role_code": "salesman", "status": 1, "remark": "业务录单"},
|
|
],
|
|
}
|
|
|
|
def create_role(self, payload: dict, session: Session | None = None) -> dict:
|
|
self._validate_create_role(payload)
|
|
|
|
if session is not None:
|
|
try:
|
|
existed = self.repository.get_role_by_code(session, payload["role_code"])
|
|
if existed is not None:
|
|
raise AppException(code=ErrorCode.DUPLICATE, message="角色编码已存在", status_code=400)
|
|
|
|
role = self.repository.create_role(session, payload)
|
|
session.commit()
|
|
return {
|
|
"role_id": role.id,
|
|
"role_name": role.role_name,
|
|
"role_code": role.role_code,
|
|
"status": role.status,
|
|
}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {
|
|
"role_id": 3,
|
|
"role_name": payload["role_name"],
|
|
"role_code": payload["role_code"],
|
|
"status": payload.get("status", 1),
|
|
}
|
|
|
|
def update_role(self, role_id: int, payload: dict, session: Session | None = None) -> dict:
|
|
self._validate_update_role(payload)
|
|
|
|
if session is not None:
|
|
try:
|
|
role = self.repository.get_role(session, role_id)
|
|
if role is None:
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="角色不存在", status_code=404)
|
|
if role.role_code == "admin" and payload.get("status", 1) != 1:
|
|
raise AppException(code=ErrorCode.BUSINESS_RULE_FAILED, message="管理员角色不能停用", status_code=400)
|
|
if payload.get("status", 1) != 1 and self.repository.count_active_users_by_role(session, role_id) > 0:
|
|
raise AppException(code=ErrorCode.BUSINESS_RULE_FAILED, message="当前角色下仍有启用用户", status_code=400)
|
|
|
|
self.repository.update_role(role, payload)
|
|
session.commit()
|
|
return {"role_id": role_id, "updated": True}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {"role_id": role_id, "updated": True}
|
|
|
|
def list_menus(self, session: Session | None = None, filters: dict | None = None) -> list[dict]:
|
|
if session is not None:
|
|
try:
|
|
menus = self.repository.list_menus(session, filters or {})
|
|
if menus:
|
|
return self._build_menu_tree(
|
|
[
|
|
{
|
|
"menu_id": menu.id,
|
|
"parent_id": menu.parent_id,
|
|
"menu_name": menu.menu_name,
|
|
"menu_path": menu.menu_path,
|
|
"menu_type": menu.menu_type,
|
|
"permission_code": menu.permission_code,
|
|
"icon": menu.icon,
|
|
"sort_no": menu.sort_no,
|
|
"status": menu.status,
|
|
}
|
|
for menu in menus
|
|
]
|
|
)
|
|
except SQLAlchemyError:
|
|
pass
|
|
|
|
return [
|
|
{
|
|
"menu_id": 1,
|
|
"parent_id": 0,
|
|
"menu_name": "订单管理",
|
|
"menu_path": "/orders",
|
|
"menu_type": "page",
|
|
"permission_code": "order:list",
|
|
"icon": "order",
|
|
"sort_no": 10,
|
|
"status": 1,
|
|
"children": [],
|
|
}
|
|
]
|
|
|
|
def create_menu(self, payload: dict, session: Session | None = None) -> dict:
|
|
self._validate_menu_payload(payload)
|
|
|
|
if session is not None:
|
|
try:
|
|
self._validate_menu_parent(session, payload["parent_id"], None)
|
|
existed = self.repository.get_menu_by_permission_code(session, payload["permission_code"])
|
|
if existed is not None:
|
|
raise AppException(code=ErrorCode.DUPLICATE, message="权限编码已存在", status_code=400)
|
|
|
|
menu = self.repository.create_menu(session, payload)
|
|
session.commit()
|
|
return {
|
|
"menu_id": menu.id,
|
|
"menu_name": menu.menu_name,
|
|
"permission_code": menu.permission_code,
|
|
"status": menu.status,
|
|
}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {
|
|
"menu_id": 2,
|
|
"menu_name": payload["menu_name"],
|
|
"permission_code": payload["permission_code"],
|
|
"status": payload.get("status", 1),
|
|
}
|
|
|
|
def update_menu(self, menu_id: int, payload: dict, session: Session | None = None) -> dict:
|
|
self._validate_menu_payload(payload)
|
|
|
|
if session is not None:
|
|
try:
|
|
menu = self.repository.get_menu(session, menu_id)
|
|
if menu is None:
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="菜单不存在", status_code=404)
|
|
|
|
self._validate_menu_parent(session, payload["parent_id"], menu_id)
|
|
existed = self.repository.get_menu_by_permission_code(session, payload["permission_code"])
|
|
if existed is not None and existed.id != menu_id:
|
|
raise AppException(code=ErrorCode.DUPLICATE, message="权限编码已存在", status_code=400)
|
|
|
|
self.repository.update_menu(menu, payload)
|
|
session.commit()
|
|
return {"menu_id": menu_id, "updated": True}
|
|
except AppException:
|
|
session.rollback()
|
|
raise
|
|
except SQLAlchemyError:
|
|
session.rollback()
|
|
|
|
return {"menu_id": menu_id, "updated": True}
|
|
|
|
def _build_user_row(self, user: object, role: object | None) -> dict:
|
|
return {
|
|
"user_id": user.id,
|
|
"username": user.username,
|
|
"real_name": user.real_name,
|
|
"mobile": user.mobile,
|
|
"role_id": user.role_id,
|
|
"role_name": role.role_name if role else "",
|
|
"role_code": role.role_code if role else "",
|
|
"status": user.status,
|
|
"created_at": user.created_at.strftime("%Y-%m-%d %H:%M:%S") if user.created_at else "",
|
|
"updated_at": user.updated_at.strftime("%Y-%m-%d %H:%M:%S") if user.updated_at else "",
|
|
}
|
|
|
|
def _build_menu_tree(self, menu_list: list[dict]) -> list[dict]:
|
|
nodes = {item["menu_id"]: {**item, "children": []} for item in menu_list}
|
|
roots: list[dict] = []
|
|
|
|
for item in nodes.values():
|
|
parent_id = item["parent_id"]
|
|
if parent_id and parent_id in nodes:
|
|
nodes[parent_id]["children"].append(item)
|
|
else:
|
|
roots.append(item)
|
|
|
|
return roots
|
|
|
|
def _validate_create_user(self, payload: dict) -> None:
|
|
if not payload["username"].strip():
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="用户名不能为空", status_code=400)
|
|
self._validate_password(payload["password"])
|
|
self._validate_update_user(payload)
|
|
|
|
def _validate_update_user(self, payload: dict) -> None:
|
|
if not payload["real_name"].strip():
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="姓名不能为空", status_code=400)
|
|
if not payload.get("role_id"):
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="角色不能为空", status_code=400)
|
|
|
|
def _validate_password(self, password: str) -> None:
|
|
if len(password.strip()) < 6:
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="密码长度不能少于6位", status_code=400)
|
|
|
|
def _validate_create_role(self, payload: dict) -> None:
|
|
if not payload["role_name"].strip():
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="角色名称不能为空", status_code=400)
|
|
if not payload["role_code"].strip():
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="角色编码不能为空", status_code=400)
|
|
|
|
def _validate_update_role(self, payload: dict) -> None:
|
|
if not payload["role_name"].strip():
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="角色名称不能为空", status_code=400)
|
|
|
|
def _validate_menu_payload(self, payload: dict) -> None:
|
|
if not payload["menu_name"].strip():
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="菜单名称不能为空", status_code=400)
|
|
if not payload["permission_code"].strip():
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="权限编码不能为空", status_code=400)
|
|
if payload["menu_type"] not in self.VALID_MENU_TYPES:
|
|
raise AppException(code=ErrorCode.PARAM_ERROR, message="菜单类型不合法", status_code=400)
|
|
|
|
def _validate_menu_parent(self, session: Session, parent_id: int, menu_id: int | None) -> None:
|
|
if not parent_id:
|
|
return
|
|
if menu_id is not None and parent_id == menu_id:
|
|
raise AppException(code=ErrorCode.BUSINESS_RULE_FAILED, message="父级菜单不能为自身", status_code=400)
|
|
parent_menu = self.repository.get_menu(session, parent_id)
|
|
if parent_menu is None:
|
|
raise AppException(code=ErrorCode.NOT_FOUND, message="父级菜单不存在", status_code=404)
|
|
|
|
|
|
system_service = SystemService()
|