增加项目的各个功能
This commit is contained in:
BIN
app/api/controller/__pycache__/__init__.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/__init__.cpython-38.pyc
Normal file
Binary file not shown.
BIN
app/api/controller/__pycache__/baseCrudController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/baseCrudController.cpython-38.pyc
Normal file
Binary file not shown.
BIN
app/api/controller/__pycache__/bugController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/bugController.cpython-38.pyc
Normal file
Binary file not shown.
BIN
app/api/controller/__pycache__/caseController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/caseController.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/api/controller/__pycache__/planController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/planController.cpython-38.pyc
Normal file
Binary file not shown.
BIN
app/api/controller/__pycache__/productController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/productController.cpython-38.pyc
Normal file
Binary file not shown.
BIN
app/api/controller/__pycache__/projectController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/projectController.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/api/controller/__pycache__/rbacController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/rbacController.cpython-38.pyc
Normal file
Binary file not shown.
BIN
app/api/controller/__pycache__/reportController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/reportController.cpython-38.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
app/api/controller/__pycache__/userController.cpython-38.pyc
Normal file
BIN
app/api/controller/__pycache__/userController.cpython-38.pyc
Normal file
Binary file not shown.
57
app/api/controller/baseCrudController.py
Normal file
57
app/api/controller/baseCrudController.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# encoding: UTF-8
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from common.sqlSession import SqlSession
|
||||
|
||||
|
||||
class BaseCrudController(object):
|
||||
"""通用 Controller 基类,封装公共的请求取值与序列化逻辑。"""
|
||||
|
||||
def __init__(self, req_data):
|
||||
# 每个 controller 持有一个独立 session,沿用当前项目的使用方式。
|
||||
self.session = SqlSession()
|
||||
# req_data 兼容 request.args 和 request.get_json() 两种来源。
|
||||
self.req_data = req_data
|
||||
|
||||
def close_session(self):
|
||||
if self.session:
|
||||
self.session.close()
|
||||
|
||||
@staticmethod
|
||||
def _get(req_data, *keys, default=None):
|
||||
"""按顺序读取多个候选参数名,兼容前后端字段别名。"""
|
||||
for key in keys:
|
||||
value = req_data.get(key)
|
||||
if value not in (None, ''):
|
||||
return value
|
||||
return default
|
||||
|
||||
@staticmethod
|
||||
def _format_value(value):
|
||||
"""将数据库对象中的特殊类型转成可直接返回给前端的值。"""
|
||||
if isinstance(value, datetime):
|
||||
return value.strftime('%Y-%m-%d %H:%M:%S')
|
||||
if isinstance(value, date):
|
||||
return value.strftime('%Y-%m-%d')
|
||||
if isinstance(value, Decimal):
|
||||
return float(value)
|
||||
return value
|
||||
|
||||
@classmethod
|
||||
def serialize(cls, item, exclude=None):
|
||||
"""单对象序列化,可按需排除不希望暴露给前端的字段。"""
|
||||
if not item:
|
||||
return {}
|
||||
exclude = exclude or []
|
||||
item_dict = item.to_dict()
|
||||
for key in exclude:
|
||||
item_dict.pop(key, None)
|
||||
for key, value in item_dict.items():
|
||||
item_dict[key] = cls._format_value(value)
|
||||
return item_dict
|
||||
|
||||
@classmethod
|
||||
def serialize_list(cls, items, exclude=None):
|
||||
"""列表对象序列化。"""
|
||||
return [cls.serialize(item, exclude) for item in items]
|
||||
325
app/api/controller/bugController.py
Normal file
325
app/api/controller/bugController.py
Normal file
@@ -0,0 +1,325 @@
|
||||
# encoding: UTF-8
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from flask import current_app
|
||||
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.bugModel import Bug, BugComment
|
||||
from ..model.productModel import Product
|
||||
from ..model.projectModel import Project
|
||||
from ..model.userModel import User
|
||||
from ..model.caseModel import Module
|
||||
from ..service.bugService import BugService
|
||||
from ..service.userService import UserService
|
||||
|
||||
|
||||
class BugUploadController(BaseCrudController):
|
||||
UPLOAD_FOLDER = 'attachment/bug_picture'
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp'}
|
||||
|
||||
def allowed_file(self, filename):
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in self.ALLOWED_EXTENSIONS
|
||||
|
||||
def bug_upload(self):
|
||||
if 'file' not in self.req_data.files:
|
||||
return '', '未找到上传文件'
|
||||
|
||||
file = self.req_data.files['file']
|
||||
if file.filename == '':
|
||||
return '', '文件名不能为空'
|
||||
|
||||
if not self.allowed_file(file.filename):
|
||||
return '', '不支持的文件格式,仅支持:png, jpg, jpeg, gif, bmp'
|
||||
|
||||
try:
|
||||
os.makedirs(self.UPLOAD_FOLDER, exist_ok=True)
|
||||
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
|
||||
ext = file.filename.rsplit('.', 1)[1].lower()
|
||||
new_filename = f'bug-{timestamp}-{uuid.uuid4().hex[:8]}.{ext}'
|
||||
file_path = os.path.join(self.UPLOAD_FOLDER, new_filename)
|
||||
file.save(file_path)
|
||||
|
||||
file_url = f'/uploads/{new_filename}'
|
||||
return file_url, ''
|
||||
except Exception as e:
|
||||
return '', f'文件上传失败:{str(e)}'
|
||||
|
||||
|
||||
class BugController(BaseCrudController):
|
||||
def bug_list(self):
|
||||
filters = []
|
||||
product_id = self._get(self.req_data, 'productId', 'product_id')
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
module_id = self._get(self.req_data, 'moduleId', 'module_id')
|
||||
bug_type = self._get(self.req_data, 'bugType', 'bug_type')
|
||||
severity = self._get(self.req_data, 'severity')
|
||||
priority = self._get(self.req_data, 'priority')
|
||||
status = self._get(self.req_data, 'status')
|
||||
assignee_id = self._get(self.req_data, 'assigneeId', 'assignee_id')
|
||||
reporter_id = self._get(self.req_data, 'reporterId', 'reporter_id')
|
||||
resolved_by = self._get(self.req_data, 'resolvedBy', 'resolved_by')
|
||||
reproduce_rate = self._get(self.req_data, 'reproduceRate', 'reproduce_rate')
|
||||
keyword = self._get(self.req_data, 'keyword')
|
||||
|
||||
if product_id:
|
||||
filters.append(Bug.product_id == int(product_id))
|
||||
if project_id:
|
||||
filters.append(Bug.project_id == int(project_id))
|
||||
if module_id:
|
||||
filters.append(Bug.module_id == int(module_id))
|
||||
if bug_type not in (None, ''):
|
||||
filters.append(Bug.bug_type == int(bug_type))
|
||||
if severity not in (None, ''):
|
||||
filters.append(Bug.severity == int(severity))
|
||||
if priority not in (None, ''):
|
||||
filters.append(Bug.priority == int(priority))
|
||||
if status not in (None, ''):
|
||||
filters.append(Bug.status == int(status))
|
||||
if assignee_id:
|
||||
filters.append(Bug.assignee_id == int(assignee_id))
|
||||
if reporter_id:
|
||||
filters.append(Bug.reporter_id == int(reporter_id))
|
||||
if resolved_by:
|
||||
filters.append(Bug.resolved_by == int(resolved_by))
|
||||
if reproduce_rate not in (None, ''):
|
||||
filters.append(Bug.reproduce_rate == int(reproduce_rate))
|
||||
if keyword:
|
||||
filters.append(Bug.title.like(f'%{keyword}%') | Bug.description.like(f'%{keyword}%'))
|
||||
|
||||
items, total = BugService.list_by_filters(
|
||||
self.session, Bug, filters,
|
||||
self._get(self.req_data, 'pageNo', 'page', default=1),
|
||||
self._get(self.req_data, 'pageSize', 'size', default=20),
|
||||
Bug.created_time
|
||||
)
|
||||
|
||||
user_ids = []
|
||||
for item in items:
|
||||
if item.assignee_id:
|
||||
user_ids.append(item.assignee_id)
|
||||
if item.reporter_id:
|
||||
user_ids.append(item.reporter_id)
|
||||
if item.resolved_by:
|
||||
user_ids.append(item.resolved_by)
|
||||
|
||||
user_info_map = UserService.get_user_info_map(self.session, user_ids) if user_ids else {}
|
||||
|
||||
result_list = []
|
||||
for item in items:
|
||||
bug_dict = item.to_dict()
|
||||
if item.assignee_id and item.assignee_id in user_info_map:
|
||||
bug_dict['assignee_name'] = user_info_map[item.assignee_id].get('real_name', '')
|
||||
else:
|
||||
bug_dict['assignee_name'] = ''
|
||||
if item.reporter_id and item.reporter_id in user_info_map:
|
||||
bug_dict['reporter_name'] = user_info_map[item.reporter_id].get('real_name', '')
|
||||
else:
|
||||
bug_dict['reporter_name'] = ''
|
||||
if item.resolved_by and item.resolved_by in user_info_map:
|
||||
bug_dict['resolved_by_name'] = user_info_map[item.resolved_by].get('real_name', '')
|
||||
else:
|
||||
bug_dict['resolved_by_name'] = ''
|
||||
result_list.append(bug_dict)
|
||||
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def bug_detail(self):
|
||||
bug_id = self._get(self.req_data, 'bugId', 'id')
|
||||
if not bug_id:
|
||||
return {}, 'bugId 为必传参数'
|
||||
item = BugService.get_by_id(self.session, Bug, bug_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应 Bug!'
|
||||
ret = self.serialize(item, ['is_delete'])
|
||||
|
||||
if item.product_id:
|
||||
product = self.session.query(Product).filter(Product.id == item.product_id, Product.is_delete == 0).first()
|
||||
ret['product_name'] = product.name if product else ''
|
||||
|
||||
if item.project_id:
|
||||
project = self.session.query(Project).filter(Project.id == item.project_id, Project.is_delete == 0).first()
|
||||
ret['project_name'] = project.name if project else ''
|
||||
|
||||
if item.reporter_id:
|
||||
reporter = self.session.query(User).filter(User.id == item.reporter_id, User.is_delete == 0).first()
|
||||
ret['reporter_name'] = reporter.real_name if reporter else ''
|
||||
|
||||
if item.assignee_id:
|
||||
assignee = self.session.query(User).filter(User.id == item.assignee_id, User.is_delete == 0).first()
|
||||
ret['assignee_name'] = assignee.real_name if assignee else ''
|
||||
|
||||
if item.module_id:
|
||||
module = self.session.query(Module).filter(Module.id == item.module_id, Module.is_delete == 0).first()
|
||||
ret['module_name'] = module.name if module else ''
|
||||
|
||||
if item.resolved_by:
|
||||
resolved_by_user = self.session.query(User).filter(User.id == item.resolved_by, User.is_delete == 0).first()
|
||||
ret['resolved_by_name'] = resolved_by_user.real_name if resolved_by_user else ''
|
||||
|
||||
comments = BugService.get_comments(self.session, bug_id)
|
||||
comment_user_ids = [c.user_id for c in comments if c.user_id]
|
||||
user_info_map = UserService.get_user_info_map(self.session, comment_user_ids) if comment_user_ids else {}
|
||||
serialized_comments = []
|
||||
for comment in comments:
|
||||
comment_dict = comment.to_dict()
|
||||
if comment.user_id and comment.user_id in user_info_map:
|
||||
comment_dict['user_name'] = user_info_map[comment.user_id].get('real_name', '')
|
||||
else:
|
||||
comment_dict['user_name'] = ''
|
||||
serialized_comments.append(comment_dict)
|
||||
ret['comments'] = serialized_comments
|
||||
|
||||
history_items = BugService.get_history(self.session, bug_id)
|
||||
user_ids = set()
|
||||
for h in history_items:
|
||||
if h.operator_id:
|
||||
user_ids.add(h.operator_id)
|
||||
if h.field_name in ('assignee_id', 'reporter_id', 'user_id', 'resolved_by'):
|
||||
if h.old_value:
|
||||
try:
|
||||
user_ids.add(int(h.old_value))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if h.new_value:
|
||||
try:
|
||||
user_ids.add(int(h.new_value))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
user_info_map = UserService.get_user_info_map(self.session, list(user_ids)) if user_ids else {}
|
||||
|
||||
serialized_history = []
|
||||
for h in history_items:
|
||||
h_dict = h.to_dict()
|
||||
if h.operator_id:
|
||||
h_dict['operator_id'] = user_info_map.get(h.operator_id, {}).get('real_name', h.operator_id)
|
||||
if h.field_name in ('assignee_id', 'reporter_id', 'user_id', 'resolved_by'):
|
||||
if h.old_value:
|
||||
try:
|
||||
old_uid = int(h.old_value)
|
||||
h_dict['old_value'] = user_info_map.get(old_uid, {}).get('real_name', h.old_value)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
if h.new_value:
|
||||
try:
|
||||
new_uid = int(h.new_value)
|
||||
h_dict['new_value'] = user_info_map.get(new_uid, {}).get('real_name', h.new_value)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
serialized_history.append(h_dict)
|
||||
|
||||
ret['history'] = serialized_history
|
||||
return ret, ''
|
||||
|
||||
def bug_create(self):
|
||||
title = self._get(self.req_data, 'title')
|
||||
product_id = self._get(self.req_data, 'productId', 'product_id')
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
if not title or not product_id or not project_id:
|
||||
return 0, 'title、productId、projectId 为必传参数'
|
||||
|
||||
bug_key = BugService.generate_bug_key(self.session)
|
||||
add_info = {
|
||||
'bug_key': bug_key,
|
||||
'title': title,
|
||||
'description': self._get(self.req_data, 'description'),
|
||||
'bug_type': int(self._get(self.req_data, 'bugType', 'bug_type', default=1)),
|
||||
'severity': int(self._get(self.req_data, 'severity', default=2)),
|
||||
'priority': int(self._get(self.req_data, 'priority', default=2)),
|
||||
'status': 0,
|
||||
'reporter_id': self._get(self.req_data, 'reporterId', 'reporter_id'),
|
||||
'assignee_id': self._get(self.req_data, 'assigneeId', 'assignee_id'),
|
||||
'product_id': product_id,
|
||||
'project_id': project_id,
|
||||
'module_id': self._get(self.req_data, 'moduleId', 'module_id'),
|
||||
'case_id': self._get(self.req_data, 'caseId', 'case_id'),
|
||||
'plan_id': self._get(self.req_data, 'planId', 'plan_id'),
|
||||
'environment': self._get(self.req_data, 'environment'),
|
||||
'steps': self._get(self.req_data, 'steps'),
|
||||
'solution': self._get(self.req_data, 'solution'),
|
||||
'resolve_version': self._get(self.req_data, 'resolveVersion', 'resolve_version'),
|
||||
'resolved_by': self._get(self.req_data, 'resolvedBy', 'resolved_by'),
|
||||
'reproduce_rate': self._get(self.req_data, 'reproduceRate', 'reproduce_rate'),
|
||||
'is_delete': 0
|
||||
}
|
||||
return BugService.create(self.session, Bug, add_info)
|
||||
|
||||
def bug_update(self):
|
||||
bug_id = self._get(self.req_data, 'bugId', 'id')
|
||||
if not bug_id:
|
||||
return 0, 'bugId 为必传参数'
|
||||
|
||||
update_info = {}
|
||||
field_mapping = [
|
||||
(('title',), 'title'),
|
||||
(('description',), 'description'),
|
||||
(('bugType', 'bug_type'), 'bug_type'),
|
||||
(('severity',), 'severity'),
|
||||
(('priority',), 'priority'),
|
||||
(('status',), 'status'),
|
||||
(('assigneeId', 'assignee_id'), 'assignee_id'),
|
||||
(('reporterId', 'reporter_id'), 'reporter_id'),
|
||||
(('moduleId', 'module_id'), 'module_id'),
|
||||
(('caseId', 'case_id'), 'case_id'),
|
||||
(('planId', 'plan_id'), 'plan_id'),
|
||||
(('environment',), 'environment'),
|
||||
(('steps',), 'steps'),
|
||||
(('solution',), 'solution'),
|
||||
(('resolveVersion', 'resolve_version'), 'resolve_version'),
|
||||
(('resolvedBy', 'resolved_by'), 'resolved_by'),
|
||||
(('reproduceRate', 'reproduce_rate'), 'reproduce_rate')
|
||||
]
|
||||
|
||||
for req_keys, column_key in field_mapping:
|
||||
value = self._get(self.req_data, *req_keys)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
|
||||
result = BugService.update_by_id(self.session, Bug, bug_id, update_info)
|
||||
|
||||
comment = self._get(self.req_data, 'comment')
|
||||
user_id = self._get(self.req_data, 'user_id', 'userId')
|
||||
if comment and user_id:
|
||||
BugService.add_comment(self.session, bug_id, comment, user_id)
|
||||
|
||||
return result
|
||||
|
||||
def bug_delete(self):
|
||||
bug_id = self._get(self.req_data, 'bugId', 'id')
|
||||
if not bug_id:
|
||||
return 0, 'bugId 为必传参数'
|
||||
return BugService.delete_by_id(self.session, Bug, bug_id)
|
||||
|
||||
def bug_history_add(self):
|
||||
bug_id = self._get(self.req_data, 'bugId', 'id')
|
||||
field_name = self._get(self.req_data, 'fieldName', 'field_name')
|
||||
old_value = self._get(self.req_data, 'oldValue', 'old_value')
|
||||
new_value = self._get(self.req_data, 'newValue', 'new_value')
|
||||
operator_id = self._get(self.req_data, 'operatorId', 'operator_id', 'user_id', 'userId')
|
||||
|
||||
if not bug_id:
|
||||
return 0, 'bugId 为必传参数'
|
||||
if not field_name:
|
||||
return 0, 'fieldName 为必传参数'
|
||||
if not operator_id:
|
||||
return 0, 'operatorId 为必传参数'
|
||||
|
||||
success = BugService.add_history(self.session, bug_id, field_name, old_value, new_value, operator_id)
|
||||
return 1 if success else 0, '' if success else '添加历史记录失败'
|
||||
|
||||
def bug_comment_add(self):
|
||||
user_id = self._get(self.req_data, 'user_id', 'reporter_id', 'reporterId')
|
||||
bug_id = self._get(self.req_data, 'bugId')
|
||||
content = self._get(self.req_data, 'content')
|
||||
if not bug_id:
|
||||
return 0, 'bugId 为必传参数'
|
||||
if not content:
|
||||
return 0, 'content 为必传参数'
|
||||
return BugService.add_comment(self.session, bug_id, content, user_id)
|
||||
|
||||
def bug_stats(self):
|
||||
product_id = self._get(self.req_data, 'productId', 'product_id')
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
return BugService.get_stats(self.session, product_id, project_id)
|
||||
413
app/api/controller/caseController.py
Normal file
413
app/api/controller/caseController.py
Normal file
@@ -0,0 +1,413 @@
|
||||
# encoding: UTF-8
|
||||
import os
|
||||
import json
|
||||
|
||||
from sqlalchemy import and_, or_
|
||||
from flask import g
|
||||
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.caseModel import CaseReview, CaseSnapshot, Module, TestCase
|
||||
from ..model.projectModel import Project
|
||||
from ..model.userModel import User
|
||||
from ..service.caseService import CaseService
|
||||
from logger import logger
|
||||
|
||||
|
||||
class CaseController(BaseCrudController):
|
||||
def module_list(self):
|
||||
project_id = self._get(self.req_data, 'projectId')
|
||||
parent_id = self._get(self.req_data, 'parentId')
|
||||
filters = []
|
||||
|
||||
if project_id:
|
||||
filters.append(Module.project_id == int(project_id))
|
||||
if parent_id not in (None, ''):
|
||||
filters.append(Module.parent_id == int(parent_id))
|
||||
|
||||
parent_module = Module.__table__.alias('parent')
|
||||
query = self.session.query(Module, parent_module.c.name.label('parent_name')).\
|
||||
outerjoin(parent_module, Module.parent_id == parent_module.c.id).\
|
||||
filter(*filters)
|
||||
|
||||
if hasattr(Module, 'is_delete'):
|
||||
query = query.filter(Module.is_delete == 0)
|
||||
|
||||
total = query.count()
|
||||
|
||||
page_num = int(self._get(self.req_data, 'pageNo', default=1))
|
||||
page_size = int(self._get(self.req_data, 'pageSize', default=200))
|
||||
|
||||
query = query.order_by(Module.id)
|
||||
items = query.offset((page_num - 1) * page_size).limit(page_size).all()
|
||||
|
||||
result_list = []
|
||||
for module, parent_name in items:
|
||||
module_dict = self.serialize(module, ['is_delete'])
|
||||
module_dict['parent_name'] = parent_name or ''
|
||||
result_list.append(module_dict)
|
||||
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def module_create(self):
|
||||
project_id = self._get(self.req_data, 'projectId')
|
||||
name = self._get(self.req_data, 'name')
|
||||
if not project_id or not name:
|
||||
return 0, 'projectId、name 为必传参数'
|
||||
add_info = {'project_id': project_id, 'parent_id': int(self._get(self.req_data, 'parentId', default=0)), 'name': name, 'sort_order': int(self._get(self.req_data, 'sortOrder', default=0)), 'path': self._get(self.req_data, 'path'), 'is_delete': 0}
|
||||
return CaseService.create(self.session, Module, add_info)
|
||||
|
||||
def module_update(self):
|
||||
module_id = self._get(self.req_data, 'moduleId', 'id')
|
||||
if not module_id:
|
||||
return 0, 'moduleId 为必传参数'
|
||||
update_info = {}
|
||||
for req_key, column_key in [('parentId', 'parent_id'), ('name', 'name'), ('sortOrder', 'sort_order'), ('path', 'path')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return CaseService.update_by_id(self.session, Module, module_id, update_info)
|
||||
|
||||
def module_delete(self):
|
||||
module_id = self._get(self.req_data, 'moduleId', 'id')
|
||||
if not module_id:
|
||||
return 0, 'moduleId 为必传参数'
|
||||
return CaseService.delete_by_id(self.session, Module, module_id)
|
||||
|
||||
def case_list(self):
|
||||
"""分页查询用例列表,支持项目名称、用例标题、优先级、类型、状态、是否自动化、标签过滤。"""
|
||||
filters = []
|
||||
|
||||
project_name = self._get(self.req_data, 'projectName')
|
||||
if project_name:
|
||||
filters.append(Project.name.like('%{}%'.format(project_name)))
|
||||
|
||||
project_id = self._get(self.req_data, 'projectId')
|
||||
if project_id:
|
||||
filters.append(TestCase.project_id == int(project_id))
|
||||
|
||||
module_name = self._get(self.req_data, 'moduleName', 'module_name')
|
||||
if module_name:
|
||||
filters.append(Module.name.like('%{}%'.format(module_name)))
|
||||
|
||||
for req_key, column in [('moduleId', TestCase.module_id), ('priority', TestCase.priority),
|
||||
('caseType', TestCase.case_type), ('status', TestCase.status),
|
||||
('isAuto', TestCase.is_auto)]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value not in (None, ''):
|
||||
filters.append(column == int(value))
|
||||
|
||||
keyword = self._get(self.req_data, 'keyword')
|
||||
if keyword:
|
||||
filters.append(TestCase.title.like('%{}%'.format(keyword)))
|
||||
|
||||
tag = self._get(self.req_data, 'tag')
|
||||
if tag:
|
||||
filters.append(TestCase.tags.any(tag))
|
||||
|
||||
created_by_name = self._get(self.req_data, 'createdBy')
|
||||
if created_by_name:
|
||||
filters.append(User.real_name.like('%{}%'.format(created_by_name)))
|
||||
|
||||
query = self.session.query(TestCase, Project.name.label('project_name'), Module.name.label('module_name'), User.real_name.label('created_by_name')).\
|
||||
join(Project, TestCase.project_id == Project.id, isouter=True).\
|
||||
join(Module, TestCase.module_id == Module.id, isouter=True).\
|
||||
join(User, TestCase.created_by == User.id, isouter=True).\
|
||||
filter(*filters)
|
||||
|
||||
if hasattr(TestCase, 'is_delete'):
|
||||
query = query.filter(TestCase.is_delete == 0)
|
||||
if hasattr(Project, 'is_delete'):
|
||||
query = query.filter(Project.is_delete == 0)
|
||||
if hasattr(Module, 'is_delete'):
|
||||
query = query.filter(or_(Module.is_delete == 0, Module.is_delete.is_(None)))
|
||||
if hasattr(User, 'is_delete'):
|
||||
query = query.filter(or_(User.is_delete == 0, User.is_delete.is_(None)))
|
||||
|
||||
total = query.count()
|
||||
|
||||
page_num = int(self._get(self.req_data, 'pageNo', 'page', default=1))
|
||||
page_size = int(self._get(self.req_data, 'pageSize', 'size', default=20))
|
||||
|
||||
query = query.order_by(TestCase.id.desc())
|
||||
items = query.offset((page_num - 1) * page_size).limit(page_size).all()
|
||||
|
||||
result_list = []
|
||||
for case, project_name, module_name, created_by_name in items:
|
||||
case_dict = self.serialize(case, ['is_delete'])
|
||||
case_dict['project_name'] = project_name or ''
|
||||
case_dict['module_name'] = module_name or ''
|
||||
case_dict['case_key'] = case_dict.get('case_key', '')
|
||||
case_dict['created_by_name'] = created_by_name or ''
|
||||
if not case_dict.get('steps'):
|
||||
case_dict['steps'] = ''
|
||||
result_list.append(case_dict)
|
||||
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def case_detail(self):
|
||||
case_id = self._get(self.req_data, 'caseId', 'id')
|
||||
if not case_id:
|
||||
return {}, 'caseId 为必传参数'
|
||||
item = CaseService.get_by_id(self.session, TestCase, case_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应用例!'
|
||||
result = self.serialize(item, ['is_delete'])
|
||||
if not result.get('steps'):
|
||||
result['steps'] = ''
|
||||
if item.module_id:
|
||||
module = self.session.query(Module).filter(Module.id == item.module_id).first()
|
||||
result['module_name'] = module.name if module else ''
|
||||
else:
|
||||
result['module_name'] = ''
|
||||
return result, ''
|
||||
|
||||
def case_create(self):
|
||||
project_id = self._get(self.req_data, 'projectId')
|
||||
title = self._get(self.req_data, 'title')
|
||||
if not project_id or not title:
|
||||
return 0, 'projectId、title 为必传参数'
|
||||
steps_value = self._get(self.req_data, 'steps', default='')
|
||||
if isinstance(steps_value, (list, dict)):
|
||||
steps_value = ''
|
||||
add_info = {
|
||||
'project_id': project_id,
|
||||
'module_id': self._get(self.req_data, 'moduleId'),
|
||||
'case_key': self._get(self.req_data, 'caseKey') or CaseService.next_case_key(self.session, project_id),
|
||||
'title': title,
|
||||
'preconditions': self._get(self.req_data, 'preconditions'),
|
||||
'steps': steps_value,
|
||||
'expected_results': self._get(self.req_data, 'expectedResults'),
|
||||
'priority': int(self._get(self.req_data, 'priority', default=2)),
|
||||
'case_type': int(self._get(self.req_data, 'caseType', default=1)),
|
||||
'tags': self._get(self.req_data, 'tags', default=[]),
|
||||
'status': int(self._get(self.req_data, 'status', default=1)),
|
||||
'is_auto': int(self._get(self.req_data, 'isAuto', default=0)),
|
||||
'created_by': getattr(g, 'current_user_id', None),
|
||||
'is_delete': 0
|
||||
}
|
||||
return CaseService.create(self.session, TestCase, add_info)
|
||||
|
||||
def case_update(self):
|
||||
"""更新用例内容,只更新请求中传入的字段。"""
|
||||
case_id = self._get(self.req_data, 'caseId', 'id')
|
||||
if not case_id:
|
||||
return 0, 'caseId 为必传参数'
|
||||
update_info = {}
|
||||
mapping = [('moduleId', 'module_id'), ('caseKey', 'case_key'), ('title', 'title'), ('preconditions', 'preconditions'), ('expectedResults', 'expected_results'), ('priority', 'priority'), ('caseType', 'case_type'), ('tags', 'tags'), ('status', 'status'), ('isAuto', 'is_auto')]
|
||||
for req_key, column_key in mapping:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
steps_value = self._get(self.req_data, 'steps')
|
||||
if steps_value is not None:
|
||||
if isinstance(steps_value, (list, dict)):
|
||||
steps_value = ''
|
||||
update_info['steps'] = steps_value
|
||||
return CaseService.update_by_id(self.session, TestCase, case_id, update_info)
|
||||
|
||||
def case_delete(self):
|
||||
case_id = self._get(self.req_data, 'caseId', 'id')
|
||||
if not case_id:
|
||||
return 0, 'caseId 为必传参数'
|
||||
return CaseService.delete_by_id(self.session, TestCase, case_id)
|
||||
|
||||
def snapshot_create(self):
|
||||
case_id = self._get(self.req_data, 'caseId')
|
||||
if not case_id:
|
||||
return 0, 'caseId 为必传参数'
|
||||
case_obj = CaseService.get_by_id(self.session, TestCase, case_id)
|
||||
if not case_obj:
|
||||
return 0, '未查询到对应用例!'
|
||||
version = CaseService.next_snapshot_version(self.session, case_id)
|
||||
snapshot = self.serialize(case_obj, ['is_delete'])
|
||||
if not snapshot.get('steps'):
|
||||
snapshot['steps'] = ''
|
||||
add_info = {'case_id': case_id, 'version': version, 'snapshot': snapshot, 'created_by': self._get(self.req_data, 'createdBy')}
|
||||
return CaseService.create(self.session, CaseSnapshot, add_info)
|
||||
|
||||
def snapshot_list(self):
|
||||
"""查询指定用例的快照历史。"""
|
||||
case_id = self._get(self.req_data, 'caseId')
|
||||
filters = [CaseSnapshot.case_id == int(case_id)] if case_id else []
|
||||
items, total = CaseService.list_by_filters(self.session, CaseSnapshot, filters, self._get(self.req_data, 'pageNo', default=1), self._get(self.req_data, 'pageSize', default=20), CaseSnapshot.created_time)
|
||||
return {'list': self.serialize_list(items), 'total': total}
|
||||
|
||||
def review_create(self):
|
||||
case_id = self._get(self.req_data, 'caseId')
|
||||
reviewer_id = self._get(self.req_data, 'reviewerId')
|
||||
if not case_id or not reviewer_id:
|
||||
return 0, 'caseId、reviewerId 为必传参数'
|
||||
return CaseService.create(self.session, CaseReview, {'case_id': case_id, 'reviewer_id': reviewer_id, 'comments': self._get(self.req_data, 'comments')})
|
||||
|
||||
def review_update(self):
|
||||
review_id = self._get(self.req_data, 'reviewId', 'id')
|
||||
if not review_id:
|
||||
return 0, 'reviewId 为必传参数'
|
||||
update_info = {}
|
||||
for req_key, column_key in [('status', 'status'), ('comments', 'comments'), ('diffContent', 'diff_content'), ('reviewedTime', 'reviewed_time')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return CaseService.update_by_id(self.session, CaseReview, review_id, update_info, soft_delete=False)
|
||||
|
||||
def review_list(self):
|
||||
"""查询用例评审记录列表。"""
|
||||
case_id = self._get(self.req_data, 'caseId')
|
||||
filters = [CaseReview.case_id == int(case_id)] if case_id else []
|
||||
items, total = CaseService.list_by_filters(self.session, CaseReview, filters, self._get(self.req_data, 'pageNo', default=1), self._get(self.req_data, 'pageSize', default=20), CaseReview.created_time)
|
||||
return {'list': self.serialize_list(items), 'total': total}
|
||||
|
||||
def case_import(self, file_path, project_id):
|
||||
"""批量导入用例"""
|
||||
try:
|
||||
from openpyxl import load_workbook
|
||||
except ImportError:
|
||||
return 0, '请先安装 openpyxl 依赖'
|
||||
|
||||
if not os.path.exists(file_path):
|
||||
return 0, '文件不存在'
|
||||
|
||||
if not project_id:
|
||||
return 0, 'projectId 为必传参数'
|
||||
|
||||
wb = load_workbook(file_path)
|
||||
sheet = wb.active
|
||||
|
||||
headers = {}
|
||||
for col in range(1, sheet.max_column + 1):
|
||||
header = str(sheet.cell(row=1, column=col).value).strip() if sheet.cell(row=1, column=col).value else ''
|
||||
if header:
|
||||
headers[header] = col
|
||||
|
||||
required_columns = ['所属模块', '用例标题', '前置条件', '步骤', '预期', '关键词', '优先级', '用例类型']
|
||||
for col in required_columns:
|
||||
if col not in headers:
|
||||
return 0, f'缺少必要列: {col}'
|
||||
|
||||
module_name_to_id = {}
|
||||
existing_modules = self.session.query(Module).filter(Module.project_id == int(project_id), Module.is_delete == 0).all()
|
||||
for module in existing_modules:
|
||||
module_name_to_id[module.name] = module.id
|
||||
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
fail_messages = []
|
||||
|
||||
for row in range(2, sheet.max_row + 1):
|
||||
try:
|
||||
module_path_str = str(sheet.cell(row=row, column=headers['所属模块']).value).strip() if sheet.cell(row=row, column=headers['所属模块']).value else ''
|
||||
|
||||
if not module_path_str:
|
||||
fail_count += 1
|
||||
fail_messages.append(f'第{row}行:所属模块为空')
|
||||
continue
|
||||
|
||||
module_names = [m.strip() for m in module_path_str.split('/') if m.strip()]
|
||||
|
||||
if not module_names:
|
||||
fail_count += 1
|
||||
fail_messages.append(f'第{row}行:所属模块格式不正确')
|
||||
continue
|
||||
|
||||
parent_id = 0
|
||||
module_id = None
|
||||
|
||||
for idx, module_name in enumerate(module_names):
|
||||
if module_name in module_name_to_id:
|
||||
parent_id = module_name_to_id[module_name]
|
||||
else:
|
||||
path_parts = module_names[:idx+1]
|
||||
module_path = '/' + '/'.join(path_parts)
|
||||
new_module = Module(
|
||||
project_id=int(project_id),
|
||||
parent_id=parent_id,
|
||||
name=module_name,
|
||||
path=module_path,
|
||||
is_delete=0
|
||||
)
|
||||
self.session.add(new_module)
|
||||
self.session.flush()
|
||||
module_name_to_id[module_name] = new_module.id
|
||||
parent_id = new_module.id
|
||||
|
||||
module_id = parent_id
|
||||
|
||||
title = str(sheet.cell(row=row, column=headers['用例标题']).value).strip() if sheet.cell(row=row, column=headers['用例标题']).value else ''
|
||||
preconditions = str(sheet.cell(row=row, column=headers['前置条件']).value).strip() if sheet.cell(row=row, column=headers['前置条件']).value else ''
|
||||
steps = str(sheet.cell(row=row, column=headers['步骤']).value).strip() if sheet.cell(row=row, column=headers['步骤']).value else ''
|
||||
expected_results = str(sheet.cell(row=row, column=headers['预期']).value).strip() if sheet.cell(row=row, column=headers['预期']).value else ''
|
||||
keywords = str(sheet.cell(row=row, column=headers['关键词']).value).strip() if sheet.cell(row=row, column=headers['关键词']).value else ''
|
||||
priority_str = str(sheet.cell(row=row, column=headers['优先级']).value).strip() if sheet.cell(row=row, column=headers['优先级']).value else '2'
|
||||
case_type_str = str(sheet.cell(row=row, column=headers['用例类型']).value).strip() if sheet.cell(row=row, column=headers['用例类型']).value else '1'
|
||||
|
||||
if not title:
|
||||
fail_count += 1
|
||||
fail_messages.append(f'第{row}行:用例标题为空')
|
||||
continue
|
||||
|
||||
priority_map = {'P0': 0, 'P1': 1, 'P2': 2, 'P3': 3}
|
||||
priority = priority_map.get(priority_str, int(priority_str) if priority_str.isdigit() else 2)
|
||||
|
||||
case_type_map = {'功能': 1, '性能': 2, '安全': 3, '接口': 4}
|
||||
case_type = case_type_map.get(case_type_str, int(case_type_str) if case_type_str.isdigit() else 1)
|
||||
|
||||
tags = [k.strip() for k in keywords.split(',')] if keywords else []
|
||||
|
||||
retry_count = 0
|
||||
max_retries = 5
|
||||
case_key = CaseService.next_case_key(self.session, project_id)
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
case = TestCase(
|
||||
project_id=int(project_id),
|
||||
module_id=module_id,
|
||||
case_key=case_key,
|
||||
title=title,
|
||||
preconditions=preconditions,
|
||||
steps=steps,
|
||||
expected_results=expected_results,
|
||||
priority=priority,
|
||||
case_type=case_type,
|
||||
tags=tags,
|
||||
status=1,
|
||||
is_auto=0,
|
||||
created_by=getattr(g, 'current_user_id', None),
|
||||
is_delete=0
|
||||
)
|
||||
self.session.add(case)
|
||||
self.session.flush()
|
||||
success_count += 1
|
||||
break
|
||||
except Exception as e:
|
||||
if 'duplicate key' in str(e).lower() or 'already exists' in str(e).lower():
|
||||
logger.warning(f'case_import case_key冲突,重新生成:{case_key}, 错误:{str(e)}')
|
||||
case_key = CaseService.next_case_key(self.session, project_id)
|
||||
retry_count += 1
|
||||
else:
|
||||
raise
|
||||
|
||||
if retry_count >= max_retries:
|
||||
fail_count += 1
|
||||
fail_messages.append(f'第{row}行:用例标题[{title}]导入失败:case_key生成失败')
|
||||
|
||||
except Exception as e:
|
||||
fail_count += 1
|
||||
fail_messages.append(f'第{row}行:导入失败 - {str(e)}')
|
||||
|
||||
try:
|
||||
self.session.commit()
|
||||
msg = f'导入完成:成功{success_count}条,失败{fail_count}条'
|
||||
if fail_messages:
|
||||
msg += f'。失败详情:{"; ".join(fail_messages[:10])}'
|
||||
if len(fail_messages) > 10:
|
||||
msg += f'...(共{len(fail_messages)}条)'
|
||||
return success_count, msg
|
||||
except Exception as e:
|
||||
self.session.rollback()
|
||||
return 0, f'提交失败:{str(e)}'
|
||||
|
||||
@staticmethod
|
||||
def get_template_path():
|
||||
"""获取模板文件路径"""
|
||||
return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))), 'attachment', '用例导入模版.xlsx')
|
||||
80
app/api/controller/dataBuilderController.py
Normal file
80
app/api/controller/dataBuilderController.py
Normal file
@@ -0,0 +1,80 @@
|
||||
# encoding: UTF-8
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.dataBuilderModel import DataBuilder, DataTask
|
||||
from ..service.dataBuilderService import DataBuilderService
|
||||
|
||||
|
||||
class DataBuilderController(BaseCrudController):
|
||||
"""造数器与造数任务相关接口控制器。"""
|
||||
|
||||
def builder_list(self):
|
||||
"""分页查询造数器列表,可按项目过滤。"""
|
||||
filters = []
|
||||
project_id = self._get(self.req_data, 'projectId')
|
||||
if project_id:
|
||||
filters.append(DataBuilder.project_id == int(project_id))
|
||||
items, total = DataBuilderService.list_by_filters(self.session, DataBuilder, filters,
|
||||
self._get(self.req_data, 'pageNo', default=1),
|
||||
self._get(self.req_data, 'pageSize', default=20),
|
||||
DataBuilder.created_time)
|
||||
return {'list': self.serialize_list(items, ['is_delete']), 'total': total}
|
||||
|
||||
def builder_detail(self):
|
||||
"""查询造数器详情。"""
|
||||
builder_id = self._get(self.req_data, 'builderId', 'id')
|
||||
if not builder_id:
|
||||
return {}, 'builderId 为必传参数'
|
||||
item = DataBuilderService.get_by_id(self.session, DataBuilder, builder_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应造数器!'
|
||||
return self.serialize(item, ['is_delete']), ''
|
||||
|
||||
def builder_create(self):
|
||||
"""创建造数器,definition 保存流程编排或模板定义。"""
|
||||
project_id = self._get(self.req_data, 'projectId')
|
||||
name = self._get(self.req_data, 'name')
|
||||
definition = self._get(self.req_data, 'definition')
|
||||
if not project_id or not name or definition is None:
|
||||
return 0, 'projectId、name、definition 为必传参数'
|
||||
add_info = {'project_id': project_id, 'name': name, 'description': self._get(self.req_data, 'description'),
|
||||
'builder_type': int(self._get(self.req_data, 'builderType', default=1)), 'definition': definition,
|
||||
'input_schema': self._get(self.req_data, 'inputSchema'),
|
||||
'output_example': self._get(self.req_data, 'outputExample'),
|
||||
'created_by': self._get(self.req_data, 'createdBy'), 'is_delete': 0}
|
||||
return DataBuilderService.create(self.session, DataBuilder, add_info)
|
||||
|
||||
def builder_update(self):
|
||||
builder_id = self._get(self.req_data, 'builderId', 'id')
|
||||
if not builder_id:
|
||||
return 0, 'builderId 为必传参数'
|
||||
update_info = {}
|
||||
for req_key, column_key in [('name', 'name'), ('description', 'description'), ('builderType', 'builder_type'),
|
||||
('definition', 'definition'), ('inputSchema', 'input_schema'),
|
||||
('outputExample', 'output_example')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return DataBuilderService.update_by_id(self.session, DataBuilder, builder_id, update_info)
|
||||
|
||||
def builder_delete(self):
|
||||
builder_id = self._get(self.req_data, 'builderId', 'id')
|
||||
if not builder_id:
|
||||
return 0, 'builderId 为必传参数'
|
||||
return DataBuilderService.delete_by_id(self.session, DataBuilder, builder_id)
|
||||
|
||||
def builder_execute(self):
|
||||
builder_id = self._get(self.req_data, 'builderId')
|
||||
if not builder_id:
|
||||
return {}, 'builderId 为必传参数'
|
||||
return DataBuilderService.execute_builder(self.session, builder_id,
|
||||
self._get(self.req_data, 'params', default={}),
|
||||
self._get(self.req_data, 'createdBy'))
|
||||
|
||||
def task_status(self):
|
||||
task_id = self._get(self.req_data, 'taskId')
|
||||
if not task_id:
|
||||
return {}, 'taskId 为必传参数'
|
||||
item = DataBuilderService.get_by_id(self.session, DataTask, task_id, soft_delete=False)
|
||||
if not item:
|
||||
return {}, '未查询到对应任务!'
|
||||
return self.serialize(item), ''
|
||||
192
app/api/controller/planController.py
Normal file
192
app/api/controller/planController.py
Normal file
@@ -0,0 +1,192 @@
|
||||
# encoding: UTF-8
|
||||
from datetime import datetime
|
||||
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.planModel import PlanCase, TestPlan, TestRound
|
||||
from ..model.caseModel import Module, TestCase
|
||||
from ..service.planService import PlanService
|
||||
from ..service.userService import UserService
|
||||
|
||||
|
||||
class PlanController(BaseCrudController):
|
||||
def plan_list(self):
|
||||
filters = []
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
status = self._get(self.req_data, 'status')
|
||||
keyword = self._get(self.req_data, 'keyword')
|
||||
owner_id = self._get(self.req_data, 'ownerId', 'owner_id', 'owner')
|
||||
if project_id:
|
||||
filters.append(TestPlan.project_id == int(project_id))
|
||||
if status not in (None, ''):
|
||||
filters.append(TestPlan.status == int(status))
|
||||
if keyword:
|
||||
filters.append(TestPlan.name.like('%{}%'.format(keyword)))
|
||||
if owner_id:
|
||||
filters.append(TestPlan.owner_id == int(owner_id))
|
||||
|
||||
items, total = PlanService.list_by_filters(self.session, TestPlan, filters, self._get(self.req_data, 'pageNo', 'page', default=1), self._get(self.req_data, 'pageSize', 'size', default=20), TestPlan.created_time)
|
||||
|
||||
owner_ids = [item.owner_id for item in items if item.owner_id]
|
||||
user_info_map = UserService.get_user_info_map(self.session, owner_ids) if owner_ids else {}
|
||||
|
||||
result_list = []
|
||||
for item in items:
|
||||
plan_dict = item.to_dict()
|
||||
if item.owner_id and item.owner_id in user_info_map:
|
||||
plan_dict['owner_name'] = user_info_map[item.owner_id].get('real_name', '')
|
||||
else:
|
||||
plan_dict['owner_name'] = ''
|
||||
result_list.append(plan_dict)
|
||||
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def plan_detail(self):
|
||||
plan_id = self._get(self.req_data, 'planId', 'id')
|
||||
if not plan_id:
|
||||
return {}, 'planId 为必传参数'
|
||||
item = PlanService.get_by_id(self.session, TestPlan, plan_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应计划!'
|
||||
ret = self.serialize(item, ['is_delete'])
|
||||
ret.update(PlanService.plan_stats(self.session, plan_id))
|
||||
return ret, ''
|
||||
|
||||
def plan_create(self):
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
name = self._get(self.req_data, 'name')
|
||||
if not project_id or not name:
|
||||
return 0, 'projectId、name 为必传参数'
|
||||
add_info = {'project_id': project_id, 'name': name, 'version': self._get(self.req_data, 'version'), 'description': self._get(self.req_data, 'description'), 'start_date': self._get(self.req_data, 'startDate', 'start_time'), 'end_date': self._get(self.req_data, 'endDate', 'end_time'), 'owner_id': self._get(self.req_data, 'ownerId', 'owner_id'), 'status': int(self._get(self.req_data, 'status', default=0)), 'environment_id': self._get(self.req_data, 'environmentId', 'environment_id'), 'is_delete': 0}
|
||||
return PlanService.create(self.session, TestPlan, add_info)
|
||||
|
||||
def plan_update(self):
|
||||
"""更新测试计划,只更新请求中传入的字段。"""
|
||||
plan_id = self._get(self.req_data, 'planId', 'id')
|
||||
if not plan_id:
|
||||
return 0, 'planId 为必传参数'
|
||||
update_info = {}
|
||||
for req_keys, column_key in [(('name', 'name'), 'name'), (('version', 'version'), 'version'), (('description', 'description'), 'description'), (('startDate', 'start_time', 'start_date'), 'start_date'), (('endDate', 'end_time', 'end_date'), 'end_date'), (('ownerId', 'owner_id'), 'owner_id'), (('status', 'status'), 'status'), (('environmentId', 'environment_id'), 'environment_id')]:
|
||||
value = self._get(self.req_data, *req_keys)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return PlanService.update_by_id(self.session, TestPlan, plan_id, update_info)
|
||||
|
||||
def plan_delete(self):
|
||||
plan_id = self._get(self.req_data, 'planId', 'id')
|
||||
if not plan_id:
|
||||
return 0, 'planId 为必传参数'
|
||||
return PlanService.delete_by_id(self.session, TestPlan, plan_id)
|
||||
|
||||
def round_create(self):
|
||||
plan_id = self._get(self.req_data, 'planId')
|
||||
round_no = self._get(self.req_data, 'roundNo')
|
||||
if not plan_id or not round_no:
|
||||
return 0, 'planId、roundNo 为必传参数'
|
||||
return PlanService.create(self.session, TestRound, {'plan_id': plan_id, 'round_no': round_no, 'name': self._get(self.req_data, 'name'), 'start_date': self._get(self.req_data, 'startDate'), 'end_date': self._get(self.req_data, 'endDate')})
|
||||
|
||||
def round_list(self):
|
||||
plan_id = self._get(self.req_data, 'planId')
|
||||
filters = [TestRound.plan_id == int(plan_id)] if plan_id else []
|
||||
items, total = PlanService.list_by_filters(self.session, TestRound, filters, self._get(self.req_data, 'pageNo', default=1), self._get(self.req_data, 'pageSize', default=50), TestRound.id)
|
||||
return {'list': self.serialize_list(items), 'total': total}
|
||||
|
||||
def plan_case_add(self):
|
||||
plan_id = self._get(self.req_data, 'planId')
|
||||
case_ids = self._get(self.req_data, 'caseIds', default=[])
|
||||
if not plan_id or not case_ids:
|
||||
return 0, 'planId、caseIds 为必传参数'
|
||||
batch_info_list = [{'plan_id': plan_id, 'case_id': case_id, 'assignee_id': self._get(self.req_data, 'assigneeId'), 'round_no': int(self._get(self.req_data, 'roundNo', default=1)), 'status': 0} for case_id in case_ids]
|
||||
return PlanService.batch_create(self.session, PlanCase, batch_info_list)
|
||||
|
||||
def plan_case_list(self):
|
||||
plan_id = self._get(self.req_data, 'planId')
|
||||
filters = [PlanCase.plan_id == int(plan_id)] if plan_id else []
|
||||
round_no = self._get(self.req_data, 'roundNo')
|
||||
if round_no not in (None, ''):
|
||||
filters.append(PlanCase.round_no == int(round_no))
|
||||
items, total = PlanService.list_by_filters(self.session, PlanCase, filters, self._get(self.req_data, 'pageNo', default=1), self._get(self.req_data, 'pageSize', default=20), PlanCase.id, asc=True)
|
||||
|
||||
case_ids = [item.case_id for item in items if item.case_id]
|
||||
case_info_map = {}
|
||||
module_info_map = {}
|
||||
if case_ids:
|
||||
cases = self.session.query(TestCase).filter(TestCase.id.in_(case_ids), TestCase.is_delete == 0).all()
|
||||
case_info_map = {case.id: {'case_key': case.case_key, 'title': case.title, 'module_id': case.module_id} for case in cases}
|
||||
|
||||
module_ids = [case.module_id for case in cases if case.module_id]
|
||||
if module_ids:
|
||||
modules = self.session.query(Module).filter(Module.id.in_(module_ids), Module.is_delete == 0).all()
|
||||
module_info_map = {module.id: module.name for module in modules}
|
||||
|
||||
result_list = []
|
||||
for item in items:
|
||||
case_dict = item.to_dict()
|
||||
if item.case_id and item.case_id in case_info_map:
|
||||
case_dict['case_key'] = case_info_map[item.case_id]['case_key']
|
||||
case_dict['case_title'] = case_info_map[item.case_id]['title']
|
||||
module_id = case_info_map[item.case_id].get('module_id')
|
||||
if module_id and module_id in module_info_map:
|
||||
case_dict['module_name'] = module_info_map[module_id]
|
||||
else:
|
||||
case_dict['module_name'] = ''
|
||||
else:
|
||||
case_dict['case_key'] = ''
|
||||
case_dict['case_title'] = ''
|
||||
case_dict['module_name'] = ''
|
||||
result_list.append(case_dict)
|
||||
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def plan_case_execute(self):
|
||||
plan_case_id = self._get(self.req_data, 'planCaseId', 'id')
|
||||
if not plan_case_id:
|
||||
return 0, 'planCaseId 为必传参数'
|
||||
|
||||
plan_case = PlanService.get_by_id(self.session, PlanCase, plan_case_id, soft_delete=False)
|
||||
if not plan_case:
|
||||
return 0, '未查询到对应计划用例!'
|
||||
|
||||
plan_id = plan_case.plan_id
|
||||
|
||||
update_info = {'status': int(self._get(self.req_data, 'status', default=0)), 'actual_result': self._get(self.req_data, 'actualResult'), 'defect_links': self._get(self.req_data, 'defectLinks', default=[]), 'attachments': self._get(self.req_data, 'attachments', default=[]), 'executed_time': datetime.now(), 'execution_duration': self._get(self.req_data, 'executionDuration')}
|
||||
result = PlanService.update_by_id(self.session, PlanCase, plan_case_id, update_info, soft_delete=False)
|
||||
|
||||
self._update_plan_status(plan_id)
|
||||
|
||||
return result
|
||||
|
||||
def _update_plan_status(self, plan_id):
|
||||
total = self.session.query(PlanCase).filter(PlanCase.plan_id == plan_id).count()
|
||||
if total == 0:
|
||||
return
|
||||
|
||||
unexecuted_count = self.session.query(PlanCase).filter(PlanCase.plan_id == plan_id, PlanCase.status == 0).count()
|
||||
passed_count = self.session.query(PlanCase).filter(PlanCase.plan_id == plan_id, PlanCase.status == 1).count()
|
||||
failed_count = self.session.query(PlanCase).filter(PlanCase.plan_id == plan_id, PlanCase.status.in_([2, 3])).count()
|
||||
|
||||
plan = PlanService.get_by_id(self.session, TestPlan, plan_id)
|
||||
if not plan:
|
||||
return
|
||||
|
||||
if plan.status == 3:
|
||||
return
|
||||
|
||||
if unexecuted_count == 0:
|
||||
if failed_count == 0:
|
||||
new_status = 4
|
||||
else:
|
||||
new_status = 2
|
||||
elif unexecuted_count < total:
|
||||
new_status = 1
|
||||
else:
|
||||
new_status = plan.status
|
||||
|
||||
if new_status != plan.status:
|
||||
PlanService.update_by_id(self.session, TestPlan, plan_id, {'status': new_status})
|
||||
|
||||
def progress(self):
|
||||
"""查询计划进度统计。"""
|
||||
plan_id = self._get(self.req_data, 'planId', 'plan_id')
|
||||
if not plan_id:
|
||||
return {}, 'planId 为必传参数'
|
||||
return PlanService.plan_stats(self.session, plan_id), ''
|
||||
64
app/api/controller/productController.py
Normal file
64
app/api/controller/productController.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# encoding: UTF-8
|
||||
import random
|
||||
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.productModel import Product
|
||||
from ..service.productService import ProductService
|
||||
|
||||
|
||||
class ProductController(BaseCrudController):
|
||||
"""产品相关接口控制器。"""
|
||||
|
||||
def product_list(self):
|
||||
filters = []
|
||||
keyword = self._get(self.req_data, 'keyword')
|
||||
status = self._get(self.req_data, 'status')
|
||||
if keyword:
|
||||
filters.append(Product.name.like('%{}%'.format(keyword)))
|
||||
if status not in (None, ''):
|
||||
filters.append(Product.status == int(status))
|
||||
items, total = ProductService.list_by_filters(self.session, Product, filters,
|
||||
self._get(self.req_data, 'pageNo', 'page', default=1),
|
||||
self._get(self.req_data, 'pageSize', 'size', default=20),
|
||||
Product.created_time)
|
||||
return {'list': self.serialize_list(items, ['is_delete']), 'total': total}
|
||||
|
||||
def product_detail(self):
|
||||
product_id = self._get(self.req_data, 'productId', 'id')
|
||||
if not product_id:
|
||||
return {}, 'productId 为必传参数'
|
||||
item = ProductService.get_by_id(self.session, Product, product_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应产品!'
|
||||
return self.serialize(item, ['is_delete']), ''
|
||||
|
||||
def product_create(self):
|
||||
name = self._get(self.req_data, 'name')
|
||||
if not name:
|
||||
return 0, 'name 为必传参数'
|
||||
add_info = {
|
||||
'name': name,
|
||||
'code': str(random.randint(100000, 999999)),
|
||||
'description': self._get(self.req_data, 'description'),
|
||||
'status': int(self._get(self.req_data, 'status', default=1)),
|
||||
'is_delete': 0
|
||||
}
|
||||
return ProductService.create(self.session, Product, add_info)
|
||||
|
||||
def product_update(self):
|
||||
product_id = self._get(self.req_data, 'productId', 'id')
|
||||
if not product_id:
|
||||
return 0, 'productId 为必传参数'
|
||||
update_info = {}
|
||||
for req_key, column_key in [('name', 'name'), ('code', 'code'), ('description', 'description'),
|
||||
('status', 'status')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return ProductService.update_by_id(self.session, Product, product_id, update_info)
|
||||
|
||||
def product_delete(self):
|
||||
product_id = self._get(self.req_data, 'productId', 'id')
|
||||
if not product_id:
|
||||
return 0, 'productId 为必传参数'
|
||||
return ProductService.delete_by_id(self.session, Product, product_id)
|
||||
198
app/api/controller/projectController.py
Normal file
198
app/api/controller/projectController.py
Normal file
@@ -0,0 +1,198 @@
|
||||
# encoding: UTF-8
|
||||
import random
|
||||
import string
|
||||
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.projectModel import Environment, Project, ProjectMember
|
||||
from ..service.projectService import ProjectService
|
||||
from ..service.userService import UserService
|
||||
from ..dao.rbacDao import RbacDao
|
||||
|
||||
|
||||
class ProjectController(BaseCrudController):
|
||||
"""项目、项目成员、环境配置相关接口控制器。"""
|
||||
|
||||
def project_list(self):
|
||||
"""分页查询项目列表。"""
|
||||
page_num = self._get(self.req_data, 'pageNo', 'page', default=1)
|
||||
page_size = self._get(self.req_data, 'pageSize', 'size', default=20)
|
||||
keyword = self._get(self.req_data, 'keyword')
|
||||
status = self._get(self.req_data, 'status')
|
||||
filter_list = []
|
||||
# 关键字先按项目名称模糊匹配。
|
||||
if keyword:
|
||||
filter_list.append(Project.name.like('%{}%'.format(keyword)))
|
||||
# 状态字段是枚举数字,查询时显式转 int。
|
||||
if status not in (None, ''):
|
||||
filter_list.append(Project.status == int(status))
|
||||
items, total = ProjectService.list_by_filters(self.session, Project, filter_list, page_num, page_size,
|
||||
Project.created_time)
|
||||
product_ids = list({item.product_id for item in items if item.product_id})
|
||||
product_map = ProjectService.get_product_map(self.session, product_ids)
|
||||
result_list = self.serialize_list(items, ['is_delete'])
|
||||
for item in result_list:
|
||||
item['product_name'] = product_map.get(item.get('product_id'), '')
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def project_detail(self):
|
||||
"""查询项目详情。"""
|
||||
project_id = self._get(self.req_data, 'projectId', 'id')
|
||||
if not project_id:
|
||||
return {}, 'projectId 为必传参数'
|
||||
item = ProjectService.get_by_id(self.session, Project, project_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应项目!'
|
||||
return self.serialize(item, ['is_delete']), ''
|
||||
|
||||
def project_create(self):
|
||||
"""创建项目。"""
|
||||
name = self._get(self.req_data, 'name')
|
||||
if not name:
|
||||
return 0, 'name 为必传参数'
|
||||
add_info = {
|
||||
'key': ''.join(random.choices(string.ascii_letters + string.digits, k=6)),
|
||||
'name': name,
|
||||
'product_id': self._get(self.req_data, 'productId', 'product_id'),
|
||||
'description': self._get(self.req_data, 'description'),
|
||||
'department': self._get(self.req_data, 'department'),
|
||||
# 默认状态为启用。
|
||||
'status': int(self._get(self.req_data, 'status', default=1)),
|
||||
'config': self._get(self.req_data, 'config', default={}),
|
||||
'created_by': self._get(self.req_data, 'createdBy'),
|
||||
'is_delete': 0
|
||||
}
|
||||
return ProjectService.create(self.session, Project, add_info)
|
||||
|
||||
def project_update(self):
|
||||
"""更新项目。"""
|
||||
project_id = self._get(self.req_data, 'projectId', 'id')
|
||||
if not project_id:
|
||||
return 0, 'projectId 为必传参数'
|
||||
update_info = {}
|
||||
# 仅更新前端实际传入的字段,避免把未传字段覆盖为空。
|
||||
for req_key, column_key in [('key', 'key'), ('name', 'name'), ('productId', 'product_id'),
|
||||
('product_id', 'product_id'), ('description', 'description'),
|
||||
('department', 'department'), ('status', 'status'), ('config', 'config')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return ProjectService.update_by_id(self.session, Project, project_id, update_info)
|
||||
|
||||
def project_delete(self):
|
||||
"""软删除项目。"""
|
||||
project_id = self._get(self.req_data, 'projectId', 'id')
|
||||
if not project_id:
|
||||
return 0, 'projectId 为必传参数'
|
||||
return ProjectService.delete_by_id(self.session, Project, project_id)
|
||||
|
||||
def environment_list(self):
|
||||
"""按项目查询环境配置列表。"""
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
if not project_id:
|
||||
return {'list': [], 'total': 0}
|
||||
items, total = ProjectService.list_by_filters(self.session, Environment,
|
||||
[Environment.project_id == int(project_id)],
|
||||
self._get(self.req_data, 'pageNo', default=1),
|
||||
self._get(self.req_data, 'pageSize', default=20),
|
||||
Environment.created_time)
|
||||
return {'list': self.serialize_list(items, ['is_delete']), 'total': total}
|
||||
|
||||
def environment_create(self):
|
||||
"""新增环境配置。"""
|
||||
project_id = self._get(self.req_data, 'project_id')
|
||||
name = self._get(self.req_data, 'name')
|
||||
variables = self._get(self.req_data, 'variables')
|
||||
if not project_id or not name or variables is None:
|
||||
return 0, 'projectId、name、variables 为必传参数'
|
||||
return ProjectService.create(self.session, Environment, {
|
||||
'project_id': project_id,
|
||||
'name': name,
|
||||
'variables': variables,
|
||||
# 兼容是否加密开关。
|
||||
'is_encrypted': bool(self._get(self.req_data, 'isEncrypted', default=False)),
|
||||
'is_delete': 0
|
||||
})
|
||||
|
||||
def environment_update(self):
|
||||
"""更新环境配置。"""
|
||||
env_id = self._get(self.req_data, 'environmentId', 'id')
|
||||
if not env_id:
|
||||
return 0, 'environmentId 为必传参数'
|
||||
update_info = {}
|
||||
for req_key, column_key in [('name', 'name'), ('variables', 'variables'), ('isEncrypted', 'is_encrypted')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return ProjectService.update_by_id(self.session, Environment, env_id, update_info)
|
||||
|
||||
def environment_delete(self):
|
||||
"""软删除环境配置。"""
|
||||
env_id = self._get(self.req_data, 'environmentId', 'id')
|
||||
if not env_id:
|
||||
return 0, 'environmentId 为必传参数'
|
||||
return ProjectService.delete_by_id(self.session, Environment, env_id)
|
||||
|
||||
def member_list(self):
|
||||
"""查询项目成员列表(带用户名、角色名称、项目名称)。"""
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
filters = [ProjectMember.project_id == int(project_id)] if project_id else []
|
||||
items, total = ProjectService.list_by_filters(self.session, ProjectMember, filters,
|
||||
self._get(self.req_data, 'pageNo', default=1),
|
||||
self._get(self.req_data, 'pageSize', default=20),
|
||||
ProjectMember.joined_time)
|
||||
result_list = self.serialize_list(items)
|
||||
if not result_list:
|
||||
return {'list': result_list, 'total': total}
|
||||
user_ids = [item.get('user_id') for item in result_list]
|
||||
project_ids = [item.get('project_id') for item in result_list]
|
||||
user_map = UserService.get_user_info_map(self.session, user_ids)
|
||||
project_map = ProjectService.get_project_name_map(self.session, project_ids)
|
||||
user_role_map = UserService.get_user_roles_map(self.session, user_ids)
|
||||
|
||||
for item in result_list:
|
||||
user_id = item.get('user_id')
|
||||
user_info = user_map.get(user_id, {})
|
||||
item['real_name'] = user_info.get('real_name', '')
|
||||
item['username'] = user_info.get('username', '')
|
||||
project_info = project_map.get(item.get('project_id'), {})
|
||||
item['project_name'] = project_info.get('name', '')
|
||||
|
||||
role_info = user_role_map.get(user_id, {})
|
||||
role_names = role_info.get('role_names', [])
|
||||
item['role_names'] = role_names
|
||||
item['role_name'] = ','.join(role_names) if role_names else ''
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def member_create(self):
|
||||
"""批量新增项目成员(根据用户系统角色自动映射项目成员角色)。"""
|
||||
project_id = self._get(self.req_data, 'project_id')
|
||||
user_ids = self._get(self.req_data, 'user_ids')
|
||||
if not project_id or not user_ids:
|
||||
return 0, 'project_id、user_ids 为必传参数'
|
||||
if not isinstance(user_ids, list):
|
||||
return 0, 'user_ids 必须为数组'
|
||||
if not user_ids:
|
||||
return 0, 'user_ids 不能为空'
|
||||
user_role_map = UserService.get_user_roles_map(self.session, user_ids)
|
||||
role_name_map = RbacDao.get_role_name_map(self.session)
|
||||
name_to_project_role = {name: role_id for role_id, name in role_name_map.items()}
|
||||
created_ids = []
|
||||
for user_id in user_ids:
|
||||
role_info = user_role_map.get(user_id, {})
|
||||
role_names = role_info.get('role_names', [])
|
||||
project_role = 0
|
||||
for role_name in role_names:
|
||||
if role_name in name_to_project_role:
|
||||
project_role = name_to_project_role[role_name]
|
||||
break
|
||||
if project_role == 0:
|
||||
return 0, f'用户 {user_id} 未分配有效角色,无法添加为项目成员'
|
||||
create_id, err_msg = ProjectService.create(self.session, ProjectMember, {
|
||||
'project_id': project_id,
|
||||
'user_id': user_id,
|
||||
'role': project_role
|
||||
})
|
||||
if err_msg:
|
||||
return 0, f'用户 {user_id} 添加失败:{err_msg}'
|
||||
created_ids.append(create_id)
|
||||
return created_ids[0] if len(created_ids) == 1 else created_ids, ''
|
||||
225
app/api/controller/projectHookController.py
Normal file
225
app/api/controller/projectHookController.py
Normal file
@@ -0,0 +1,225 @@
|
||||
# encoding: UTF-8
|
||||
import time
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import requests
|
||||
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.projectHookModel import ProjectHook
|
||||
from ..service.projectHookService import ProjectHookService
|
||||
|
||||
|
||||
class ProjectHookController(BaseCrudController):
|
||||
def hook_list(self):
|
||||
filters = []
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
hook_type = self._get(self.req_data, 'hookType', 'hook_type')
|
||||
|
||||
if project_id:
|
||||
filters.append(ProjectHook.project_id == int(project_id))
|
||||
if hook_type not in (None, ''):
|
||||
filters.append(ProjectHook.hook_type == int(hook_type))
|
||||
|
||||
items, total = ProjectHookService.list_by_filters(
|
||||
self.session, ProjectHook, filters,
|
||||
self._get(self.req_data, 'pageNo', 'page', default=1),
|
||||
self._get(self.req_data, 'pageSize', 'size', default=20),
|
||||
ProjectHook.created_time
|
||||
)
|
||||
|
||||
result_list = []
|
||||
hook_type_map = {1: '飞书', 2: '钉钉', 3: '企微'}
|
||||
for item in items:
|
||||
hook_dict = item.to_dict()
|
||||
hook_dict['hook_type_name'] = hook_type_map.get(item.hook_type, '')
|
||||
result_list.append(hook_dict)
|
||||
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def hook_detail(self):
|
||||
hook_id = self._get(self.req_data, 'hookId', 'id')
|
||||
if not hook_id:
|
||||
return {}, 'hookId 为必传参数'
|
||||
item = ProjectHookService.get_by_id(self.session, ProjectHook, hook_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应Hook配置!'
|
||||
ret = item.to_dict()
|
||||
hook_type_map = {1: '飞书', 2: '钉钉', 3: '企微'}
|
||||
ret['hook_type_name'] = hook_type_map.get(item.hook_type, '')
|
||||
return ret, ''
|
||||
|
||||
def hook_create(self):
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
hook_type = self._get(self.req_data, 'hookType', 'hook_type')
|
||||
webhook_url = self._get(self.req_data, 'webhookUrl', 'webhook_url')
|
||||
|
||||
if not project_id:
|
||||
return 0, 'projectId 为必传参数'
|
||||
if not hook_type:
|
||||
return 0, 'hookType 为必传参数'
|
||||
if not webhook_url:
|
||||
return 0, 'webhookUrl 为必传参数'
|
||||
|
||||
add_info = {
|
||||
'project_id': project_id,
|
||||
'hook_type': int(hook_type),
|
||||
'webhook_url': webhook_url,
|
||||
'secret': self._get(self.req_data, 'secret'),
|
||||
'enabled': int(self._get(self.req_data, 'enabled', default=1)),
|
||||
'description': self._get(self.req_data, 'description'),
|
||||
'config': self._get(self.req_data, 'config', default={}),
|
||||
'is_delete': 0
|
||||
}
|
||||
return ProjectHookService.create(self.session, ProjectHook, add_info)
|
||||
|
||||
def hook_update(self):
|
||||
hook_id = self._get(self.req_data, 'hookId', 'id')
|
||||
if not hook_id:
|
||||
return 0, 'hookId 为必传参数'
|
||||
|
||||
update_info = {}
|
||||
field_mapping = [
|
||||
(('hookType', 'hook_type'), 'hook_type'),
|
||||
(('webhookUrl', 'webhook_url'), 'webhook_url'),
|
||||
(('secret',), 'secret'),
|
||||
(('enabled',), 'enabled'),
|
||||
(('description',), 'description'),
|
||||
(('config',), 'config')
|
||||
]
|
||||
|
||||
for req_keys, column_key in field_mapping:
|
||||
value = self._get(self.req_data, *req_keys)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
|
||||
return ProjectHookService.update_by_id(self.session, ProjectHook, hook_id, update_info)
|
||||
|
||||
def hook_delete(self):
|
||||
hook_id = self._get(self.req_data, 'hookId', 'id')
|
||||
if not hook_id:
|
||||
return 0, 'hookId 为必传参数'
|
||||
return ProjectHookService.delete_by_id(self.session, ProjectHook, hook_id)
|
||||
|
||||
def hook_send(self):
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
title = self._get(self.req_data, 'title')
|
||||
content = self._get(self.req_data, 'content')
|
||||
hook_type = self._get(self.req_data, 'hookType', 'hook_type')
|
||||
hook_id = self._get(self.req_data, 'hookId', 'id')
|
||||
real_name = self._get(self.req_data, 'real_name', 'realName')
|
||||
|
||||
if not project_id:
|
||||
return 0, 'projectId 为必传参数'
|
||||
if not title:
|
||||
return 0, 'title 为必传参数'
|
||||
if not content:
|
||||
return 0, 'content 为必传参数'
|
||||
|
||||
at_prefix = f'@{real_name} ' if real_name else ''
|
||||
final_content = f'{at_prefix}{content}'
|
||||
|
||||
if hook_id:
|
||||
hook = ProjectHookService.get_by_id(self.session, ProjectHook, hook_id)
|
||||
if not hook or hook.is_delete == 1 or hook.enabled != 1:
|
||||
return 0, '未找到对应的Hook或Hook未启用'
|
||||
hooks = [hook]
|
||||
else:
|
||||
hooks = ProjectHookService.get_hooks_by_project(self.session, project_id, hook_type)
|
||||
if not hooks:
|
||||
return 0, '未配置对应的Hook'
|
||||
|
||||
results = []
|
||||
for hook in hooks:
|
||||
if hook.hook_type == 1:
|
||||
success, err_msg = self._send_feishu_message(hook.webhook_url, hook.secret, title, final_content)
|
||||
elif hook.hook_type == 2:
|
||||
success, err_msg = self._send_dingtalk_message(hook.webhook_url, hook.secret, title, final_content)
|
||||
elif hook.hook_type == 3:
|
||||
success, err_msg = self._send_wecom_message(hook.webhook_url, hook.secret, title, final_content)
|
||||
else:
|
||||
success, err_msg = False, '未知Hook类型'
|
||||
|
||||
results.append({
|
||||
'hook_id': hook.id,
|
||||
'hook_type': hook.hook_type,
|
||||
'success': success,
|
||||
'error': err_msg
|
||||
})
|
||||
|
||||
all_success = all(r['success'] for r in results)
|
||||
return 1 if all_success else 0, results
|
||||
|
||||
def _send_feishu_message(self, webhook_url, secret, title, content):
|
||||
timestamp = str(int(time.time()))
|
||||
sign = ''
|
||||
if secret:
|
||||
string_to_sign = f'{timestamp}\n{secret}'
|
||||
hmac_code = hmac.new(secret.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha256).digest()
|
||||
sign = base64.b64encode(hmac_code).decode('utf-8')
|
||||
|
||||
separator = '&' if '?' in webhook_url else '?'
|
||||
url = f'{webhook_url}{separator}timestamp={timestamp}&sign={sign}' if sign else webhook_url
|
||||
|
||||
payload = {
|
||||
'msg_type': 'text',
|
||||
'content': {
|
||||
'text': f'【{title}】\n\n{content}'
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=payload, timeout=10)
|
||||
result = response.json()
|
||||
if result.get('code') == 0:
|
||||
return True, ''
|
||||
else:
|
||||
return False, result.get('msg', '发送失败')
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def _send_dingtalk_message(self, webhook_url, secret, title, content):
|
||||
timestamp = str(int(time.time() * 1000))
|
||||
sign = ''
|
||||
if secret:
|
||||
string_to_sign = f'{timestamp}\n{secret}'
|
||||
hmac_code = hmac.new(secret.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha256).digest()
|
||||
sign = base64.b64encode(hmac_code).decode('utf-8')
|
||||
|
||||
separator = '&' if '?' in webhook_url else '?'
|
||||
url = f'{webhook_url}{separator}timestamp={timestamp}&sign={sign}' if sign else webhook_url
|
||||
|
||||
payload = {
|
||||
'msgtype': 'text',
|
||||
'text': {
|
||||
'content': f'【{title}】\n\n{content}'
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, json=payload, timeout=10)
|
||||
result = response.json()
|
||||
if result.get('errcode') == 0:
|
||||
return True, ''
|
||||
else:
|
||||
return False, result.get('errmsg', '发送失败')
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
def _send_wecom_message(self, webhook_url, secret, title, content):
|
||||
payload = {
|
||||
'msgtype': 'text',
|
||||
'text': {
|
||||
'content': f'【{title}】\n\n{content}'
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(webhook_url, json=payload, timeout=10)
|
||||
result = response.json()
|
||||
if result.get('errcode') == 0:
|
||||
return True, ''
|
||||
else:
|
||||
return False, result.get('errmsg', '发送失败')
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
257
app/api/controller/rbacController.py
Normal file
257
app/api/controller/rbacController.py
Normal file
@@ -0,0 +1,257 @@
|
||||
# encoding: UTF-8
|
||||
from flask import g
|
||||
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.rbacModel import Role, Permission, Menu, RolePermission
|
||||
from ..service.rbacService import RbacService
|
||||
|
||||
|
||||
class RbacController(BaseCrudController):
|
||||
def role_list(self):
|
||||
filters = []
|
||||
status = self._get(self.req_data, 'status')
|
||||
if status not in (None, ''):
|
||||
filters.append(Menu.status == int(status))
|
||||
return RbacService.build_menu_tree(
|
||||
self.session,
|
||||
filters,
|
||||
role_ids=getattr(g, 'current_role_ids', [])
|
||||
)
|
||||
|
||||
def role_page_list(self):
|
||||
filters = []
|
||||
keyword = self._get(self.req_data, 'keyword')
|
||||
status = self._get(self.req_data, 'status')
|
||||
if keyword:
|
||||
filters.append(Role.name.like('%{}%'.format(keyword)))
|
||||
if status not in (None, ''):
|
||||
filters.append(Role.status == int(status))
|
||||
items, total = RbacService.list_by_filters(self.session, Role, filters,
|
||||
self._get(self.req_data, 'pageNo', 'page', default=1),
|
||||
self._get(self.req_data, 'pageSize', 'size', default=20),
|
||||
Role.created_time)
|
||||
return {'list': self.serialize_list(items, ['is_delete']), 'total': total}
|
||||
|
||||
def role_detail(self):
|
||||
role_id = self._get(self.req_data, 'roleId', 'id')
|
||||
if not role_id:
|
||||
return {}, 'roleId 为必传参数'
|
||||
item = RbacService.get_by_id(self.session, Role, role_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应角色!'
|
||||
return self.serialize(item, ['is_delete']), ''
|
||||
|
||||
def role_create(self):
|
||||
code = self._get(self.req_data, 'code')
|
||||
name = self._get(self.req_data, 'name')
|
||||
if not code or not name:
|
||||
return 0, 'code、name 为必传参数'
|
||||
return RbacService.create(self.session, Role, {
|
||||
'code': code,
|
||||
'name': name,
|
||||
'description': self._get(self.req_data, 'description'),
|
||||
'status': int(self._get(self.req_data, 'status', default=1)),
|
||||
'is_system': int(self._get(self.req_data, 'isSystem', 'is_system', default=0)),
|
||||
'created_by': self._get(self.req_data, 'createdBy'),
|
||||
'is_delete': 0
|
||||
})
|
||||
|
||||
def role_update(self):
|
||||
role_id = self._get(self.req_data, 'roleId', 'id')
|
||||
if not role_id:
|
||||
return 0, 'roleId 为必传参数'
|
||||
update_info = {}
|
||||
for req_key, column_key in [('code', 'code'), ('name', 'name'), ('description', 'description'),
|
||||
('status', 'status'), ('isSystem', 'is_system'), ('is_system', 'is_system')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return RbacService.update_by_id(self.session, Role, role_id, update_info)
|
||||
|
||||
def role_delete(self):
|
||||
role_id = self._get(self.req_data, 'roleId', 'id')
|
||||
if not role_id:
|
||||
return 0, 'roleId 为必传参数'
|
||||
return RbacService.delete_by_id(self.session, Role, role_id)
|
||||
|
||||
def permission_list(self):
|
||||
filters = []
|
||||
keyword = self._get(self.req_data, 'keyword')
|
||||
module = self._get(self.req_data, 'module')
|
||||
status = self._get(self.req_data, 'status')
|
||||
if keyword:
|
||||
filters.append(Permission.name.like('%{}%'.format(keyword)))
|
||||
if module:
|
||||
filters.append(Permission.module == module)
|
||||
if status not in (None, ''):
|
||||
filters.append(Permission.status == int(status))
|
||||
items, total = RbacService.list_by_filters(self.session, Permission, filters,
|
||||
self._get(self.req_data, 'pageNo', 'page', default=1),
|
||||
self._get(self.req_data, 'pageSize', 'size', default=20),
|
||||
Permission.created_time)
|
||||
role_permission_items = self.session.query(RolePermission).filter(RolePermission.is_delete == 0).all()
|
||||
permission_role_map = {}
|
||||
for rp in role_permission_items:
|
||||
if rp.permission_id not in permission_role_map:
|
||||
permission_role_map[rp.permission_id] = []
|
||||
permission_role_map[rp.permission_id].append(rp.role_id)
|
||||
role_items = self.session.query(Role).filter(Role.is_delete == 0).all()
|
||||
role_map = {r.id: {'id': r.id, 'name': r.name} for r in role_items}
|
||||
result_list = []
|
||||
for item in items:
|
||||
item_dict = self.serialize(item, ['is_delete'])
|
||||
role_ids = permission_role_map.get(item.id, [])
|
||||
item_dict['roles'] = [role_map.get(rid) for rid in role_ids if role_map.get(rid)]
|
||||
result_list.append(item_dict)
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def permission_detail(self):
|
||||
permission_id = self._get(self.req_data, 'permissionId', 'id')
|
||||
if not permission_id:
|
||||
return {}, 'permissionId 为必传参数'
|
||||
item = RbacService.get_by_id(self.session, Permission, permission_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应权限!'
|
||||
return self.serialize(item, ['is_delete']), ''
|
||||
|
||||
def permission_create(self):
|
||||
code = self._get(self.req_data, 'code')
|
||||
name = self._get(self.req_data, 'name')
|
||||
if not code or not name:
|
||||
return 0, 'code、name 为必传参数'
|
||||
return RbacService.create(self.session, Permission, {
|
||||
'code': code,
|
||||
'name': name,
|
||||
'module': self._get(self.req_data, 'module'),
|
||||
'action': self._get(self.req_data, 'action'),
|
||||
'description': self._get(self.req_data, 'description'),
|
||||
'status': int(self._get(self.req_data, 'status', default=1)),
|
||||
'is_delete': 0
|
||||
})
|
||||
|
||||
def permission_update(self):
|
||||
permission_id = self._get(self.req_data, 'permissionId', 'id')
|
||||
if not permission_id:
|
||||
return 0, 'permissionId 为必传参数'
|
||||
update_info = {}
|
||||
for req_key, column_key in [('code', 'code'), ('name', 'name'), ('module', 'module'), ('action', 'action'),
|
||||
('description', 'description'), ('status', 'status')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return RbacService.update_by_id(self.session, Permission, permission_id, update_info)
|
||||
|
||||
def permission_delete(self):
|
||||
permission_id = self._get(self.req_data, 'permissionId', 'id')
|
||||
if not permission_id:
|
||||
return 0, 'permissionId 为必传参数'
|
||||
return RbacService.delete_by_id(self.session, Permission, permission_id)
|
||||
|
||||
def menu_tree(self):
|
||||
return RbacService.build_menu_tree(self.session, [])
|
||||
|
||||
def current_menu_list(self):
|
||||
filters = []
|
||||
status = self._get(self.req_data, 'status')
|
||||
if status not in (None, ''):
|
||||
filters.append(Menu.status == int(status))
|
||||
return RbacService.build_menu_tree(
|
||||
self.session,
|
||||
filters,
|
||||
role_ids=getattr(g, 'current_role_ids', [])
|
||||
)
|
||||
|
||||
def role_menu_tree(self):
|
||||
role_id = self._get(self.req_data, 'roleId')
|
||||
if not role_id:
|
||||
return {'tree': [], 'checkedKeys': []}, 'roleId 为必传参数'
|
||||
return {
|
||||
'tree': RbacService.build_menu_tree(self.session, []),
|
||||
'checkedKeys': RbacService.get_role_menu_ids(self.session, role_id)
|
||||
}, ''
|
||||
|
||||
def menu_detail(self):
|
||||
menu_id = self._get(self.req_data, 'menuId', 'id')
|
||||
if not menu_id:
|
||||
return {}, 'menuId 为必传参数'
|
||||
item = RbacService.get_by_id(self.session, Menu, menu_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应菜单!'
|
||||
return self.serialize(item, ['is_delete']), ''
|
||||
|
||||
def menu_create(self):
|
||||
name = self._get(self.req_data, 'name')
|
||||
if not name:
|
||||
return 0, 'name 为必传参数'
|
||||
return RbacService.create(self.session, Menu, {
|
||||
'parent_id': int(self._get(self.req_data, 'parentId', 'parent_id', default=0)),
|
||||
'name': name,
|
||||
'code': self._get(self.req_data, 'code'),
|
||||
'type': int(self._get(self.req_data, 'type', default=1)),
|
||||
'path': self._get(self.req_data, 'path'),
|
||||
'component': self._get(self.req_data, 'component'),
|
||||
'icon': self._get(self.req_data, 'icon'),
|
||||
'permission_code': self._get(self.req_data, 'permissionCode', 'permission_code'),
|
||||
'sort': int(self._get(self.req_data, 'sort', default=0)),
|
||||
'visible': int(self._get(self.req_data, 'visible', default=1)),
|
||||
'status': int(self._get(self.req_data, 'status', default=1)),
|
||||
'is_delete': 0
|
||||
})
|
||||
|
||||
def menu_update(self):
|
||||
menu_id = self._get(self.req_data, 'menuId', 'id')
|
||||
if not menu_id:
|
||||
return 0, 'menuId 为必传参数'
|
||||
update_info = {}
|
||||
field_pairs = [
|
||||
(('parentId', 'parent_id'), 'parent_id'),
|
||||
(('name',), 'name'),
|
||||
(('code',), 'code'),
|
||||
(('type',), 'type'),
|
||||
(('path',), 'path'),
|
||||
(('component',), 'component'),
|
||||
(('icon',), 'icon'),
|
||||
(('permissionCode', 'permission_code'), 'permission_code'),
|
||||
(('sort',), 'sort'),
|
||||
(('visible',), 'visible'),
|
||||
(('status',), 'status')
|
||||
]
|
||||
for req_keys, column_key in field_pairs:
|
||||
value = self._get(self.req_data, *req_keys)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return RbacService.update_by_id(self.session, Menu, menu_id, update_info)
|
||||
|
||||
def menu_delete(self):
|
||||
menu_id = self._get(self.req_data, 'menuId', 'id')
|
||||
if not menu_id:
|
||||
return 0, 'menuId 为必传参数'
|
||||
return RbacService.delete_by_id(self.session, Menu, menu_id)
|
||||
|
||||
def role_permission_list(self):
|
||||
role_id = self._get(self.req_data, 'roleId')
|
||||
if not role_id:
|
||||
return {'permissionIds': []}
|
||||
return {'permissionIds': RbacService.get_role_permission_ids(self.session, role_id)}
|
||||
|
||||
def role_permission_assign(self):
|
||||
role_ids = self._get(self.req_data, 'roleIds', default=[])
|
||||
permission_id = self._get(self.req_data, 'permissionId')
|
||||
if not role_ids:
|
||||
return 0, 'roleIds 为必传参数'
|
||||
if not permission_id:
|
||||
return 0, 'permissionId 为必传参数'
|
||||
return RbacService.assign_permissions(self.session, role_ids, permission_id)
|
||||
|
||||
def role_menu_list(self):
|
||||
role_id = self._get(self.req_data, 'roleId')
|
||||
if not role_id:
|
||||
return {'menuIds': []}
|
||||
return {'menuIds': RbacService.get_role_menu_ids(self.session, role_id)}
|
||||
|
||||
def role_menu_assign(self):
|
||||
role_id = self._get(self.req_data, 'roleId')
|
||||
menu_ids = self._get(self.req_data, 'menuIds', default=[])
|
||||
if not role_id:
|
||||
return 0, 'roleId 为必传参数'
|
||||
return RbacService.assign_menus(self.session, role_id, menu_ids)
|
||||
47
app/api/controller/reportController.py
Normal file
47
app/api/controller/reportController.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# encoding: UTF-8
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.planModel import TestPlan
|
||||
from ..model.reportModel import Report
|
||||
from ..service.reportService import ReportService
|
||||
|
||||
|
||||
class ReportController(BaseCrudController):
|
||||
"""测试报告相关接口控制器。"""
|
||||
|
||||
def report_list(self):
|
||||
"""分页查询报告列表,可按产品、项目、计划过滤。"""
|
||||
filters = []
|
||||
product_id = self._get(self.req_data, 'productId', 'product_id')
|
||||
if product_id:
|
||||
filters.append(Report.product_id == int(product_id))
|
||||
project_id = self._get(self.req_data, 'projectId', 'project_id')
|
||||
if project_id:
|
||||
filters.append(Report.project_id == int(project_id))
|
||||
plan_id = self._get(self.req_data, 'planId', 'plan_id')
|
||||
if plan_id:
|
||||
filters.append(Report.plan_id == int(plan_id))
|
||||
items, total = ReportService.list_by_filters(self.session, Report, filters, self._get(self.req_data, 'pageNo', default=1), self._get(self.req_data, 'pageSize', default=20), Report.generated_time)
|
||||
result_list = []
|
||||
for item in items:
|
||||
item_dict = self.serialize(item)
|
||||
plan = self.session.query(TestPlan).filter(TestPlan.id == item.plan_id).first()
|
||||
item_dict['plan_name'] = plan.name if plan else None
|
||||
result_list.append(item_dict)
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def report_detail(self):
|
||||
"""查询报告详情,返回 summary 和 HTML content。"""
|
||||
report_id = self._get(self.req_data, 'reportId', 'report_id', 'id')
|
||||
if not report_id:
|
||||
return {}, 'reportId 为必传参数'
|
||||
item = ReportService.get_by_id(self.session, Report, report_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应报告!'
|
||||
return self.serialize(item), ''
|
||||
|
||||
def report_generate(self):
|
||||
"""同步生成报告:聚合计划执行数据并落库。"""
|
||||
plan_id = self._get(self.req_data, 'planId', 'plan_id')
|
||||
if not plan_id:
|
||||
return 0, 'planId 为必传参数'
|
||||
return ReportService.generate_report(self.session, plan_id, self._get(self.req_data, 'generatedBy', 'generated_by'))
|
||||
128
app/api/controller/userController.py
Normal file
128
app/api/controller/userController.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# encoding: UTF-8
|
||||
from .baseCrudController import BaseCrudController
|
||||
from ..model.userModel import User
|
||||
from ..service.userService import UserService
|
||||
from ..utils.authMiddleware import TOKEN_REFRESH_THRESHOLD_SECONDS, create_token
|
||||
|
||||
|
||||
class UserController(BaseCrudController):
|
||||
def user_list(self):
|
||||
filters = []
|
||||
keyword = self._get(self.req_data, 'keyword')
|
||||
status = self._get(self.req_data, 'status')
|
||||
if keyword:
|
||||
filters.append(User.username.like('%{}%'.format(keyword)))
|
||||
if status not in (None, ''):
|
||||
filters.append(User.status == int(status))
|
||||
items, total = UserService.list_by_filters(self.session, User, filters,
|
||||
self._get(self.req_data, 'pageNo', 'page', default=1),
|
||||
self._get(self.req_data, 'pageSize', 'size', default=20),
|
||||
User.created_time)
|
||||
result_list = self.serialize_list(items, ['is_delete', 'password_hash'])
|
||||
role_map = UserService.get_user_roles_map(self.session, [item.id for item in items])
|
||||
for item in result_list:
|
||||
role_info = role_map.get(item.get('id'), {'role_ids': [], 'role_names': []})
|
||||
item['role_ids'] = role_info['role_ids']
|
||||
item['role_names'] = role_info['role_names']
|
||||
return {'list': result_list, 'total': total}
|
||||
|
||||
def user_detail(self):
|
||||
user_id = self._get(self.req_data, 'userId', 'id')
|
||||
if not user_id:
|
||||
return {}, 'userId 为必传参数'
|
||||
item = UserService.get_by_id(self.session, User, user_id)
|
||||
if not item:
|
||||
return {}, '未查询到对应用户!'
|
||||
ret = self.serialize(item, ['is_delete', 'password_hash'])
|
||||
ret['role_ids'] = UserService.get_user_role_ids(self.session, user_id)
|
||||
return ret, ''
|
||||
|
||||
def user_create(self):
|
||||
username = self._get(self.req_data, 'username')
|
||||
password = self._get(self.req_data, 'password')
|
||||
if not username or not password:
|
||||
return 0, 'username、password 为必传参数'
|
||||
return UserService.create(self.session, User, {
|
||||
'username': username,
|
||||
'real_name': self._get(self.req_data, 'realName', 'real_name'),
|
||||
'password_hash': password,
|
||||
'mobile': self._get(self.req_data, 'mobile'),
|
||||
'email': self._get(self.req_data, 'email'),
|
||||
'avatar': self._get(self.req_data, 'avatar'),
|
||||
'status': int(self._get(self.req_data, 'status', default=1)),
|
||||
'created_by': self._get(self.req_data, 'createdBy'),
|
||||
'is_delete': 0
|
||||
})
|
||||
|
||||
def user_update(self):
|
||||
user_id = self._get(self.req_data, 'userId', 'id')
|
||||
if not user_id:
|
||||
return 0, 'userId 为必传参数'
|
||||
update_info = {}
|
||||
for req_key, column_key in [('username', 'username'), ('realName', 'real_name'), ('real_name', 'real_name'),
|
||||
('password', 'password_hash'), ('mobile', 'mobile'), ('email', 'email'),
|
||||
('avatar', 'avatar'), ('status', 'status')]:
|
||||
value = self._get(self.req_data, req_key)
|
||||
if value is not None:
|
||||
update_info[column_key] = value
|
||||
return UserService.update_by_id(self.session, User, user_id, update_info)
|
||||
|
||||
def user_delete(self):
|
||||
user_id = self._get(self.req_data, 'userId', 'id')
|
||||
if not user_id:
|
||||
return 0, 'userId 为必传参数'
|
||||
return UserService.delete_by_id(self.session, User, user_id)
|
||||
|
||||
def user_role_list(self):
|
||||
user_id = self._get(self.req_data, 'userId')
|
||||
if not user_id:
|
||||
return {'roleIds': []}
|
||||
return {'roleIds': UserService.get_user_role_ids(self.session, user_id)}
|
||||
|
||||
def user_role_assign(self):
|
||||
user_id = self._get(self.req_data, 'userId')
|
||||
role_ids = self._get(self.req_data, 'roleIds', default=[])
|
||||
if not user_id:
|
||||
return 0, 'userId 为必传参数'
|
||||
return UserService.assign_roles(self.session, user_id, role_ids)
|
||||
|
||||
def register(self):
|
||||
username = self._get(self.req_data, 'username')
|
||||
password = self._get(self.req_data, 'password')
|
||||
if not username or not password:
|
||||
return 0, 'username、password 为必传参数'
|
||||
exist_user = UserService.get_by_username(self.session, username)
|
||||
if exist_user:
|
||||
return 0, '用户名已存在!'
|
||||
return UserService.create(self.session, User, {
|
||||
'username': username,
|
||||
'real_name': self._get(self.req_data, 'realName', 'real_name'),
|
||||
'password_hash': password,
|
||||
'mobile': self._get(self.req_data, 'mobile'),
|
||||
'email': self._get(self.req_data, 'email'),
|
||||
'avatar': self._get(self.req_data, 'avatar'),
|
||||
'status': 1,
|
||||
'created_by': self._get(self.req_data, 'createdBy'),
|
||||
'is_delete': 0
|
||||
})
|
||||
|
||||
def login(self):
|
||||
username = self._get(self.req_data, 'username')
|
||||
password = self._get(self.req_data, 'password')
|
||||
if not username or not password:
|
||||
return {}, 'username、password 为必传参数'
|
||||
user = UserService.get_by_username(self.session, username)
|
||||
if not user or user.password_hash != password:
|
||||
return {}, '用户名或密码错误!'
|
||||
if int(user.status) != 1:
|
||||
return {}, '用户已禁用!'
|
||||
UserService.update_last_login_time(self.session, user.id)
|
||||
token, expire_seconds = create_token(user.id)
|
||||
ret = self.serialize(user, ['is_delete', 'password_hash'])
|
||||
ret['role_ids'] = UserService.get_user_role_ids(self.session, user.id)
|
||||
ret['token'] = token
|
||||
ret['token_type'] = 'Bearer'
|
||||
ret['expires_in'] = expire_seconds
|
||||
ret['refresh_threshold_seconds'] = TOKEN_REFRESH_THRESHOLD_SECONDS
|
||||
ret['refresh_mechanism'] = '请求任意已登录接口时,若token剩余有效期小于阈值则自动续期到完整有效期'
|
||||
return ret, ''
|
||||
Reference in New Issue
Block a user