feat: 添加module表status字段及相关接口优化

1. 在module表新增status字段(0:待确认;1:正常;2:弃用)
2. 修改/document/generate-cases接口,创建模块时设置status=0
3. 修改/case/restore接口,恢复用例时同步更新模块及其父模块的status为1
4. 修改/case/list接口,支持module_status参数过滤,默认只显示status=1的模块
5. 修改/case/list接口,返回module_path字段
6. 修改/plan/case/list接口,返回module_path字段
7. 修改/module/tree接口,默认筛选status=1的数据
8. 优化数据库连接池配置,添加连接验证和自动重试机制
This commit is contained in:
qiaoxinjiu
2026-05-18 10:04:56 +08:00
parent 4cc0f1453c
commit b71c4a66e1
14 changed files with 489 additions and 150 deletions

Binary file not shown.

View File

@@ -31,6 +31,8 @@ class CaseController(BaseCrudController):
if hasattr(Module, 'is_delete'): if hasattr(Module, 'is_delete'):
query = query.filter(Module.is_delete == 0) query = query.filter(Module.is_delete == 0)
if hasattr(Module, 'status'):
query = query.filter(Module.status == 1)
total = query.count() total = query.count()
@@ -90,11 +92,18 @@ class CaseController(BaseCrudController):
filters.append(Module.name.like('%{}%'.format(module_name))) filters.append(Module.name.like('%{}%'.format(module_name)))
for req_key, column in [('moduleId', TestCase.module_id), ('priority', TestCase.priority), for req_key, column in [('moduleId', TestCase.module_id), ('priority', TestCase.priority),
('caseType', TestCase.case_type), ('status', TestCase.status), ('caseType', TestCase.case_type), ('isAuto', TestCase.is_auto)]:
('isAuto', TestCase.is_auto)]:
value = self._get(self.req_data, req_key) value = self._get(self.req_data, req_key)
if value not in (None, ''): if value not in (None, ''):
filters.append(column == int(value)) filters.append(column == int(value))
is_ai_generated = self._get(self.req_data, 'isAiGenerated', 'is_ai_generated')
if is_ai_generated not in (None, ''):
filters.append(TestCase.is_ai_generated == int(is_ai_generated))
status = self._get(self.req_data, 'status')
if status not in (None, ''):
filters.append(TestCase.status == int(status))
else:
filters.append(TestCase.status != 0)
keyword = self._get(self.req_data, 'keyword') keyword = self._get(self.req_data, 'keyword')
if keyword: if keyword:
@@ -108,7 +117,7 @@ class CaseController(BaseCrudController):
if created_by_name: if created_by_name:
filters.append(User.real_name.like('%{}%'.format(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')).\ query = self.session.query(TestCase, Project.name.label('project_name'), Module.name.label('module_name'), Module.path.label('module_path'), User.real_name.label('created_by_name')).\
join(Project, TestCase.project_id == Project.id, isouter=True).\ join(Project, TestCase.project_id == Project.id, isouter=True).\
join(Module, TestCase.module_id == Module.id, isouter=True).\ join(Module, TestCase.module_id == Module.id, isouter=True).\
join(User, TestCase.created_by == User.id, isouter=True).\ join(User, TestCase.created_by == User.id, isouter=True).\
@@ -120,6 +129,14 @@ class CaseController(BaseCrudController):
query = query.filter(Project.is_delete == 0) query = query.filter(Project.is_delete == 0)
if hasattr(Module, 'is_delete'): if hasattr(Module, 'is_delete'):
query = query.filter(or_(Module.is_delete == 0, Module.is_delete.is_(None))) query = query.filter(or_(Module.is_delete == 0, Module.is_delete.is_(None)))
if hasattr(Module, 'status'):
module_status = self._get(self.req_data, 'moduleStatus', 'module_status')
if module_status not in (None, ''):
# 如果传入了module_status参数按指定状态过滤
query = query.filter(Module.status == int(module_status))
else:
# 默认只查询状态为1的模块
query = query.filter(or_(Module.status == 1, Module.status.is_(None)))
if hasattr(User, 'is_delete'): if hasattr(User, 'is_delete'):
query = query.filter(or_(User.is_delete == 0, User.is_delete.is_(None))) query = query.filter(or_(User.is_delete == 0, User.is_delete.is_(None)))
@@ -132,10 +149,11 @@ class CaseController(BaseCrudController):
items = query.offset((page_num - 1) * page_size).limit(page_size).all() items = query.offset((page_num - 1) * page_size).limit(page_size).all()
result_list = [] result_list = []
for case, project_name, module_name, created_by_name in items: for case, project_name, module_name, module_path, created_by_name in items:
case_dict = self.serialize(case, ['is_delete']) case_dict = self.serialize(case, ['is_delete'])
case_dict['project_name'] = project_name or '' case_dict['project_name'] = project_name or ''
case_dict['module_name'] = module_name or '' case_dict['module_name'] = module_name or ''
case_dict['module_path'] = module_path or ''
case_dict['case_key'] = case_dict.get('case_key', '') case_dict['case_key'] = case_dict.get('case_key', '')
case_dict['created_by_name'] = created_by_name or '' case_dict['created_by_name'] = created_by_name or ''
if not case_dict.get('steps'): if not case_dict.get('steps'):
@@ -184,6 +202,7 @@ class CaseController(BaseCrudController):
'tags': self._get(self.req_data, 'tags', default=[]), 'tags': self._get(self.req_data, 'tags', default=[]),
'status': int(self._get(self.req_data, 'status', default=1)), 'status': int(self._get(self.req_data, 'status', default=1)),
'is_auto': int(self._get(self.req_data, 'isAuto', default=0)), 'is_auto': int(self._get(self.req_data, 'isAuto', default=0)),
'is_ai_generated': int(self._get(self.req_data, 'isAiGenerated', default=0)),
'created_by': getattr(g, 'current_user_id', None), 'created_by': getattr(g, 'current_user_id', None),
'is_delete': 0 'is_delete': 0
} }
@@ -195,7 +214,7 @@ class CaseController(BaseCrudController):
if not case_id: if not case_id:
return 0, 'caseId 为必传参数' return 0, 'caseId 为必传参数'
update_info = {} 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')] 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'), ('isAiGenerated', 'is_ai_generated')]
for req_key, column_key in mapping: for req_key, column_key in mapping:
value = self._get(self.req_data, req_key) value = self._get(self.req_data, req_key)
if value is not None: if value is not None:
@@ -208,10 +227,83 @@ class CaseController(BaseCrudController):
return CaseService.update_by_id(self.session, TestCase, case_id, update_info) return CaseService.update_by_id(self.session, TestCase, case_id, update_info)
def case_delete(self): def case_delete(self):
case_id = self._get(self.req_data, 'caseId', 'id') case_ids = self._get(self.req_data, 'caseIds', 'caseId', 'ids', 'id')
if not case_id: if not case_ids:
return 0, 'caseId 为必传参数' return {}, 'caseIds 为必传参数'
return CaseService.delete_by_id(self.session, TestCase, case_id) if isinstance(case_ids, str):
case_ids = [case_id.strip() for case_id in case_ids.split(',') if case_id.strip()]
elif not isinstance(case_ids, list):
case_ids = [case_ids]
try:
case_ids = list({int(case_id) for case_id in case_ids if str(case_id).strip()})
except ValueError:
return {}, 'caseIds 必须为数字数组或英文逗号分隔的数字字符串'
if not case_ids:
return {}, 'caseIds 为必传参数'
delete_count = self.session.query(TestCase).filter(
TestCase.id.in_(case_ids),
TestCase.is_delete == 0
).update({'is_delete': 1}, synchronize_session=False)
err = self.session.done(close=False)
if err:
return {}, f'删除失败!{err}'
return {'caseIds': case_ids, 'deletedCount': delete_count}, ''
def case_restore(self):
case_ids = self._get(self.req_data, 'caseIds', 'caseId', 'ids', 'id')
if not case_ids:
return {}, 'caseIds 为必传参数'
if isinstance(case_ids, str):
case_ids = [case_id.strip() for case_id in case_ids.split(',') if case_id.strip()]
elif not isinstance(case_ids, list):
case_ids = [case_ids]
try:
case_ids = list({int(case_id) for case_id in case_ids if str(case_id).strip()})
except ValueError:
return {}, 'caseIds 必须为数字数组或英文逗号分隔的数字字符串'
if not case_ids:
return {}, 'caseIds 为必传参数'
# 先获取需要恢复的用例对应的模块ID
cases = self.session.query(TestCase).filter(
TestCase.id.in_(case_ids),
TestCase.status == 0,
TestCase.is_delete == 0
).all()
module_ids = list({case.module_id for case in cases if case.module_id})
# 更新用例状态
update_count = self.session.query(TestCase).filter(
TestCase.id.in_(case_ids),
TestCase.status == 0,
TestCase.is_delete == 0
).update({'status': 1}, synchronize_session=False)
# 更新模块状态为1同时更新父模块状态
if module_ids:
# 获取所有需要更新的模块ID包括父模块
all_module_ids = set(module_ids)
current_ids = module_ids.copy()
# 递归获取所有父模块ID
while current_ids:
parents = self.session.query(Module.parent_id).filter(
Module.id.in_(current_ids),
Module.parent_id != 0,
Module.is_delete == 0
).all()
parent_ids = [p[0] for p in parents if p[0] not in all_module_ids]
if not parent_ids:
break
all_module_ids.update(parent_ids)
current_ids = parent_ids
# 更新所有模块包括父模块的状态为1
self.session.query(Module).filter(
Module.id.in_(list(all_module_ids)),
Module.is_delete == 0
).update({'status': 1}, synchronize_session=False)
err = self.session.done(close=False)
if err:
return {}, f'恢复失败!{err}'
return {'caseIds': case_ids, 'updatedCount': update_count}, ''
def snapshot_create(self): def snapshot_create(self):
case_id = self._get(self.req_data, 'caseId') case_id = self._get(self.req_data, 'caseId')
@@ -374,6 +466,7 @@ class CaseController(BaseCrudController):
tags=tags, tags=tags,
status=1, status=1,
is_auto=0, is_auto=0,
is_ai_generated=0,
created_by=getattr(g, 'current_user_id', None), created_by=getattr(g, 'current_user_id', None),
is_delete=0 is_delete=0
) )

View File

@@ -116,7 +116,7 @@ class PlanController(BaseCrudController):
module_ids = [case.module_id for case in cases if case.module_id] module_ids = [case.module_id for case in cases if case.module_id]
if module_ids: if module_ids:
modules = self.session.query(Module).filter(Module.id.in_(module_ids), Module.is_delete == 0).all() 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} module_info_map = {module.id: {'name': module.name, 'path': module.path} for module in modules}
result_list = [] result_list = []
for item in items: for item in items:
@@ -126,13 +126,16 @@ class PlanController(BaseCrudController):
case_dict['case_title'] = case_info_map[item.case_id]['title'] case_dict['case_title'] = case_info_map[item.case_id]['title']
module_id = case_info_map[item.case_id].get('module_id') module_id = case_info_map[item.case_id].get('module_id')
if module_id and module_id in module_info_map: if module_id and module_id in module_info_map:
case_dict['module_name'] = module_info_map[module_id] case_dict['module_name'] = module_info_map[module_id]['name']
case_dict['module_path'] = module_info_map[module_id].get('path', '')
else: else:
case_dict['module_name'] = '' case_dict['module_name'] = ''
case_dict['module_path'] = ''
else: else:
case_dict['case_key'] = '' case_dict['case_key'] = ''
case_dict['case_title'] = '' case_dict['case_title'] = ''
case_dict['module_name'] = '' case_dict['module_name'] = ''
case_dict['module_path'] = ''
result_list.append(case_dict) result_list.append(case_dict)
return {'list': result_list, 'total': total} return {'list': result_list, 'total': total}

View File

@@ -17,6 +17,7 @@ class Module(Base):
sort_order = Column(Integer, default=0, comment='排序') sort_order = Column(Integer, default=0, comment='排序')
path = Column(String(512), comment='模块路径') path = Column(String(512), comment='模块路径')
is_delete = Column(Integer, default=0, comment='0未删除1已删除') is_delete = Column(Integer, default=0, comment='0未删除1已删除')
status = Column(Integer, default=0, comment='0待确认1正常2弃用')
class TestCase(Base): class TestCase(Base):
@@ -34,6 +35,7 @@ class TestCase(Base):
tags = Column(ARRAY(String(64)), server_default=text("'{}'::varchar[]"), comment='标签') tags = Column(ARRAY(String(64)), server_default=text("'{}'::varchar[]"), comment='标签')
status = Column(SmallInteger, default=1, comment='1:正常 2:已废弃 3:评审中 4评审通过') status = Column(SmallInteger, default=1, comment='1:正常 2:已废弃 3:评审中 4评审通过')
is_auto = Column(Integer, default=0, comment='0未实现自动化1已实现自动化') is_auto = Column(Integer, default=0, comment='0未实现自动化1已实现自动化')
is_ai_generated = Column(Integer, default=0, comment='0非AI生成1AI生成')
created_by = Column(BigInteger, comment='创建人') created_by = Column(BigInteger, comment='创建人')
is_delete = Column(Integer, default=0, comment='0未删除1已删除') is_delete = Column(Integer, default=0, comment='0未删除1已删除')
created_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间') created_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间')

View File

@@ -18,6 +18,8 @@ from .controller.userController import UserController
from .controller.bugController import BugController, BugUploadController from .controller.bugController import BugController, BugUploadController
from .controller.projectHookController import ProjectHookController from .controller.projectHookController import ProjectHookController
from .controller.automationController import AutomationController from .controller.automationController import AutomationController
from .controller.skillController import SkillController
from .controller.documentSourceController import DocumentSourceController
api = Blueprint('api', __name__) api = Blueprint('api', __name__)
@@ -514,16 +516,31 @@ def case_update():
@permission_required('case:delete') @permission_required('case:delete')
def case_delete(): def case_delete():
try: try:
delete_id, err_msg = CaseController(request.get_json() or {}).case_delete() ret, err_msg = CaseController(request.get_json() or {}).case_delete()
if err_msg: if err_msg:
logger.warning(f'case_delete失败{err_msg}, 请求参数:{request.get_json()}') logger.warning(f'case_delete失败{err_msg}, 请求参数:{request.get_json()}')
return ApiResponse.build_failure(40012, msg=err_msg) return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': delete_id}) return ApiResponse.build_success(20000, data=ret)
except Exception as e: except Exception as e:
logger.error(f'case_delete异常{str(e)}, 请求参数:{request.get_json()}, 堆栈:{traceback.format_exc()}') logger.error(f'case_delete异常{str(e)}, 请求参数:{request.get_json()}, 堆栈:{traceback.format_exc()}')
return ApiResponse.build_failure(40012, msg=f'删除失败:{str(e)[:100]}') return ApiResponse.build_failure(40012, msg=f'删除失败:{str(e)[:100]}')
@api.route('/case/restore', methods=['POST'])
@login_required
@permission_required('case:update')
def case_restore():
try:
ret, err_msg = CaseController(request.get_json() or {}).case_restore()
if err_msg:
logger.warning(f'case_restore失败{err_msg}, 请求参数:{request.get_json()}')
return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000, data=ret)
except Exception as e:
logger.error(f'case_restore异常{str(e)}, 请求参数:{request.get_json()}, 堆栈:{traceback.format_exc()}')
return ApiResponse.build_failure(40012, msg=f'恢复失败:{str(e)[:100]}')
@api.route('/case/import', methods=['POST']) @api.route('/case/import', methods=['POST'])
@login_required @login_required
@permission_required('case:create') @permission_required('case:create')
@@ -922,6 +939,159 @@ def automation_execution_abort():
controller.close_session() controller.close_session()
# =========================
# 测试 Skills 与业务规则接口
# =========================
@api.route('/skill/create', methods=['POST'])
@login_required
@permission_required('skill:create')
def skill_create():
controller = SkillController(request.get_json() or {})
try:
create_id, err_msg = controller.skill_create()
if err_msg:
return ApiResponse.build_failure(40009, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': create_id})
finally:
controller.close_session()
@api.route('/skill/update', methods=['POST'])
@login_required
@permission_required('skill:update')
def skill_update():
controller = SkillController(request.get_json() or {})
try:
update_id, err_msg = controller.skill_update()
if err_msg:
return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': update_id})
finally:
controller.close_session()
@api.route('/skill/delete', methods=['POST'])
@login_required
@permission_required('skill:delete')
def skill_delete():
controller = SkillController(request.get_json() or {})
try:
delete_id, err_msg = controller.skill_delete()
if err_msg:
return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': delete_id})
finally:
controller.close_session()
@api.route('/skill/detail', methods=['GET'])
@login_required
@permission_required('skill:detail')
def skill_detail():
controller = SkillController(request.args)
try:
ret, err_msg = controller.skill_detail()
if err_msg:
return ApiResponse.build_failure(40011, msg=err_msg)
return ApiResponse.build_success(20000, data=ret)
finally:
controller.close_session()
@api.route('/skill/list', methods=['GET'])
@login_required
@permission_required('skill:list')
def skill_list():
controller = SkillController(request.args)
try:
return ApiResponse.build_success(20000, data=controller.skill_list())
finally:
controller.close_session()
@api.route('/skill-rule/list', methods=['GET'])
@login_required
@permission_required('skill:list')
def skill_rule_list():
controller = SkillController(request.args)
try:
ret, err_msg = controller.skill_rule_list()
if err_msg:
return ApiResponse.build_failure(40011, msg=err_msg)
return ApiResponse.build_success(20000, data=ret)
finally:
controller.close_session()
@api.route('/business-rule/create', methods=['POST'])
@login_required
@permission_required('business-rule:create')
def business_rule_create():
controller = SkillController(request.get_json() or {})
try:
create_id, err_msg = controller.business_rule_create()
if err_msg:
return ApiResponse.build_failure(40009, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': create_id})
finally:
controller.close_session()
@api.route('/business-rule/update', methods=['POST'])
@login_required
@permission_required('business-rule:update')
def business_rule_update():
controller = SkillController(request.get_json() or {})
try:
update_id, err_msg = controller.business_rule_update()
if err_msg:
return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': update_id})
finally:
controller.close_session()
@api.route('/business-rule/delete', methods=['POST'])
@login_required
@permission_required('business-rule:delete')
def business_rule_delete():
controller = SkillController(request.get_json() or {})
try:
delete_id, err_msg = controller.business_rule_delete()
if err_msg:
return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': delete_id})
finally:
controller.close_session()
@api.route('/business-rule/detail', methods=['GET'])
@login_required
@permission_required('business-rule:detail')
def business_rule_detail():
controller = SkillController(request.args)
try:
ret, err_msg = controller.business_rule_detail()
if err_msg:
return ApiResponse.build_failure(40011, msg=err_msg)
return ApiResponse.build_success(20000, data=ret)
finally:
controller.close_session()
@api.route('/business-rule/list', methods=['GET'])
@login_required
@permission_required('business-rule:list')
def business_rule_list():
controller = SkillController(request.args)
try:
return ApiResponse.build_success(20000, data=controller.business_rule_list())
finally:
controller.close_session()
# ========================= # =========================
# 报告接口 # 报告接口
# ========================= # =========================
@@ -1600,3 +1770,156 @@ def bug_upload():
finally: finally:
controller.close_session() controller.close_session()
# =========================
# 文档源接口 (PRD文档/飞书链接)
# =========================
@api.route('/document/list', methods=['GET'])
@login_required
@permission_required('document:list')
def document_list():
controller = DocumentSourceController(request.args)
try:
return ApiResponse.build_success(20000, data=controller.document_list())
finally:
controller.close_session()
@api.route('/document/detail', methods=['GET'])
@login_required
@permission_required('document:detail')
def document_detail():
controller = DocumentSourceController(request.args)
try:
ret, err_msg = controller.document_detail()
if err_msg:
return ApiResponse.build_failure(40011, msg=err_msg)
return ApiResponse.build_success(20000, data=ret)
finally:
controller.close_session()
@api.route('/document/create', methods=['POST'])
@login_required
@permission_required('document:create')
def document_create():
controller = DocumentSourceController(request.get_json() or {})
try:
create_id, err_msg = controller.document_create()
if err_msg:
return ApiResponse.build_failure(40009, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': create_id})
finally:
controller.close_session()
@api.route('/document/update', methods=['POST'])
@login_required
@permission_required('document:update')
def document_update():
controller = DocumentSourceController(request.get_json() or {})
try:
update_id, err_msg = controller.document_update()
if err_msg:
return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': update_id})
finally:
controller.close_session()
@api.route('/document/delete', methods=['POST'])
@login_required
@permission_required('document:delete')
def document_delete():
controller = DocumentSourceController(request.get_json() or {})
try:
delete_id, err_msg = controller.document_delete()
if err_msg:
return ApiResponse.build_failure(40012, msg=err_msg)
return ApiResponse.build_success(20000, data={'id': delete_id})
finally:
controller.close_session()
@api.route('/document/refresh', methods=['POST'])
@login_required
@permission_required('document:update')
def document_refresh():
controller = DocumentSourceController(request.get_json() or {})
try:
success, err_msg = controller.document_refresh()
if err_msg:
return ApiResponse.build_failure(40009, msg=err_msg)
return ApiResponse.build_success(20000, data={'success': success})
finally:
controller.close_session()
@api.route('/document/generate-cases', methods=['POST'])
@login_required
@permission_required('document:generate')
def document_generate_cases():
controller = DocumentSourceController(request.get_json() or {})
try:
ret, err_msg = controller.document_generate_cases()
if err_msg:
return ApiResponse.build_failure(40009, msg=err_msg)
return ApiResponse.build_success(20000, data=ret)
finally:
controller.close_session()
@api.route('/document/match-modules', methods=['POST'])
@login_required
@permission_required('document:generate')
def document_match_modules():
controller = DocumentSourceController(request.get_json() or {})
try:
ret = controller.document_match_modules()
return ApiResponse.build_success(20000, data=ret)
finally:
controller.close_session()
@api.route('/document/import-cases', methods=['POST'])
@login_required
@permission_required('document:import')
def document_import_cases():
controller = DocumentSourceController(request.get_json() or {})
try:
success_count, err_msg = controller.document_import_cases()
if err_msg:
return ApiResponse.build_failure(40009, msg=err_msg)
return ApiResponse.build_success(20000, data={'successCount': success_count})
finally:
controller.close_session()
@api.route('/document/batch-create-modules', methods=['POST'])
@login_required
@permission_required('module:create')
def document_batch_create_modules():
controller = DocumentSourceController(request.get_json() or {})
try:
ret, err_msg = controller.document_batch_create_modules()
if err_msg:
return ApiResponse.build_failure(40009, msg=err_msg)
return ApiResponse.build_success(20000, data=ret)
finally:
controller.close_session()
@api.route('/document/upload', methods=['POST'])
@login_required
@permission_required('document:create')
def document_upload():
controller = DocumentSourceController(request)
try:
ret, err_msg = controller.document_upload()
if err_msg:
return ApiResponse.build_failure(40009, msg=err_msg)
return ApiResponse.build_success(20000, data=ret)
finally:
controller.close_session()

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env python
# encoding: UTF-8
import psycopg2
def check_permission_table():
try:
conn = psycopg2.connect(
host="39.170.26.156",
port=8366,
dbname="test",
user="postgres",
password="difyai123456"
)
cursor = conn.cursor()
cursor.execute("""
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'permission'
ORDER BY ordinal_position;
""")
print("Permission 表结构:")
print("-" * 60)
print(f"{'字段名':<20} {'类型':<20} {'是否可空'}")
print("-" * 60)
for row in cursor.fetchall():
print(f"{row[0]:<20} {row[1]:<20} {row[2]}")
conn.close()
except Exception as e:
print("Error: " + str(e))
if __name__ == "__main__":
check_permission_table()

View File

@@ -2,6 +2,7 @@
from sqlalchemy import create_engine, text from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker from sqlalchemy.orm import sessionmaker
from urllib.parse import quote_plus as urlquote from urllib.parse import quote_plus as urlquote
import time
from const import sparkatp_sql_uri from const import sparkatp_sql_uri
@@ -10,6 +11,10 @@ from logger import logger
_ENGINE_CACHE = {} _ENGINE_CACHE = {}
_SESSION_FACTORY_CACHE = {} _SESSION_FACTORY_CACHE = {}
# 重试配置
MAX_RETRY_ATTEMPTS = 3
RETRY_DELAY = 1 # 秒
""" """
sql操作 sql操作
@@ -32,28 +37,52 @@ class SqlSession:
def build_postgres_uri(host, port, user, password, database): def build_postgres_uri(host, port, user, password, database):
return f"postgresql+psycopg2://{user}:{urlquote(str(password))}@{host}:{port}/{database}" return f"postgresql+psycopg2://{user}:{urlquote(str(password))}@{host}:{port}/{database}"
def get_session(self): def get_session(self, retry_count=0):
try:
session_factory = _SESSION_FACTORY_CACHE.get(self.sql_uri) session_factory = _SESSION_FACTORY_CACHE.get(self.sql_uri)
if session_factory is None: if session_factory is None:
engine = _ENGINE_CACHE.get(self.sql_uri) engine = _ENGINE_CACHE.get(self.sql_uri)
if engine is None: if engine is None:
engine = create_engine( engine = create_engine(
self.sql_uri, self.sql_uri,
pool_size=20, pool_size=10,
max_overflow=30, max_overflow=20,
pool_pre_ping=True, pool_recycle=180, # 3分钟回收连接避免连接被服务器断开
pool_recycle=1200, pool_pre_ping=True, # 获取连接前先验证有效性
pool_timeout=60, pool_timeout=15, # 获取连接超时时间
pool_use_lifo=True,
connect_args={ connect_args={
'connect_timeout': 10, 'connect_timeout': 5,
'options': '-c timezone=Asia/Shanghai' 'options': '-c timezone=Asia/Shanghai',
'keepalives': 1,
'keepalives_idle': 30,
'keepalives_interval': 5,
'keepalives_count': 3
} }
) )
_ENGINE_CACHE[self.sql_uri] = engine _ENGINE_CACHE[self.sql_uri] = engine
session_factory = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False) session_factory = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False)
_SESSION_FACTORY_CACHE[self.sql_uri] = session_factory _SESSION_FACTORY_CACHE[self.sql_uri] = session_factory
return session_factory() session = session_factory()
# 验证连接是否有效
try:
session.execute(text("SELECT 1"))
except Exception as e:
logger.warning(f"连接验证失败,尝试重新获取连接: {e}")
# 清除缓存的session factory强制创建新连接
_SESSION_FACTORY_CACHE.pop(self.sql_uri, None)
_ENGINE_CACHE.pop(self.sql_uri, None)
raise
return session
except Exception as e:
if retry_count < MAX_RETRY_ATTEMPTS:
logger.warning(f"获取数据库连接失败,第 {retry_count + 1} 次重试: {e}")
time.sleep(RETRY_DELAY * (retry_count + 1))
return self.get_session(retry_count + 1)
else:
logger.error(f"获取数据库连接失败,已重试 {MAX_RETRY_ATTEMPTS} 次: {e}")
raise
def query(self, *args): def query(self, *args):
return self._session.query(*args) return self._session.query(*args)

View File

@@ -29,8 +29,8 @@ RES_CODE = {
40013: 'scene_id不能为空!' 40013: 'scene_id不能为空!'
} }
# sparkatp_sql_uri = f'postgresql+psycopg2://postgres:{urlquote("difyai123456")}@39.170.26.156:8366/test' sparkatp_sql_uri = f'postgresql+psycopg2://postgres:{urlquote("difyai123456")}@39.170.26.156:8366/test'
sparkatp_sql_uri = f'postgresql+psycopg2://postgres:{urlquote("difyai123456")}@39.170.26.156:8366/test-platform-prod' # sparkatp_sql_uri = f'postgresql+psycopg2://postgres:{urlquote("difyai123456")}@39.170.26.156:8366/test-platform-prod'
EXECUTE_DB_CONFIG = { EXECUTE_DB_CONFIG = {
'ZHYY': { 'ZHYY': {
'st': { 'st': {
@@ -83,8 +83,8 @@ STRESS_URI = 'https://qe.bg.huohua.cn'
QE_DOMAIN = 'https://qe.bg.huohua.cn' QE_DOMAIN = 'https://qe.bg.huohua.cn'
PASSWORD = quote('AcUVeRb8lN') PASSWORD = quote('AcUVeRb8lN')
REDIS_URL = 'redis://127.0.0.1:7379/15' # REDIS_URL = 'redis://127.0.0.1:7379/15'
# REDIS_URL = 'redis://124.220.32.45:7379/15' REDIS_URL = 'redis://124.220.32.45:7379/15'
JENKINS_BASE_URL = os.environ.get('JENKINS_BASE_URL', 'http://39.170.26.156:8256/') JENKINS_BASE_URL = os.environ.get('JENKINS_BASE_URL', 'http://39.170.26.156:8256/')
JENKINS_USER = os.environ.get('JENKINS_USER', 'jenkins') JENKINS_USER = os.environ.get('JENKINS_USER', 'jenkins')

View File

@@ -1,75 +0,0 @@
#!/usr/bin/env python
# encoding: UTF-8
import psycopg2
def generate_menu_sql():
try:
conn = psycopg2.connect(
host="39.170.26.156",
port=8366,
dbname="test",
user="postgres",
password="difyai123456"
)
cursor = conn.cursor()
# 查询自动化模块的权限
cursor.execute("""
SELECT code, name, action
FROM permission
WHERE module = 'automation' AND is_delete = 0
ORDER BY id;
""")
permissions = cursor.fetchall()
print("自动化模块权限列表:")
print("-" * 60)
print(f"{'权限代码':<30} {'权限名称':<20} {'action'}")
print("-" * 60)
for p in permissions:
print(f"{p[0]:<30} {p[1]:<20} {p[2]}")
# 查询已存在的menu
cursor.execute("SELECT code FROM menu WHERE is_delete = 0")
existing_menus = {row[0] for row in cursor.fetchall()}
conn.close()
# 生成SQL
sql_lines = []
sql_lines.append("-- ==============================================")
sql_lines.append("-- 自动化执行模块 - 权限对应的按钮菜单")
sql_lines.append("-- ==============================================")
sql_lines.append("")
sort_order = 1
for code, name, action in permissions:
menu_code = f"automation_{action}"
if menu_code in existing_menus:
print(f"跳过已存在的菜单: {menu_code}")
continue
sql_lines.append(f"-- {name} 按钮")
sql_lines.append(f"INSERT INTO menu (name, code, type, path, component, icon, permission_code, parent_id, sort, visible, status, is_delete)")
sql_lines.append(f"SELECT '{name}', '{menu_code}', 3, '', '', '', '{code}',")
sql_lines.append(f" (SELECT id FROM menu WHERE code = 'automation_list'), {sort_order}, 1, 1, 0")
sql_lines.append(f"WHERE NOT EXISTS (SELECT 1 FROM menu WHERE code = '{menu_code}');")
sql_lines.append("")
sort_order += 1
sql_lines.append("COMMIT;")
# 写入文件
with open("automation_menus.sql", "w", encoding="utf-8") as f:
f.write("\n".join(sql_lines))
print(f"\n已生成 SQL 文件: automation_menus.sql")
print(f"共生成 {sort_order - 1} 条菜单记录")
except Exception as e:
print("Error: " + str(e))
if __name__ == "__main__":
generate_menu_sql()