diff --git a/__pycache__/const.cpython-38.pyc b/__pycache__/const.cpython-38.pyc index d898989..eccf79f 100644 Binary files a/__pycache__/const.cpython-38.pyc and b/__pycache__/const.cpython-38.pyc differ diff --git a/app/api/__pycache__/views.cpython-38.pyc b/app/api/__pycache__/views.cpython-38.pyc index cd0518a..cbd46df 100644 Binary files a/app/api/__pycache__/views.cpython-38.pyc and b/app/api/__pycache__/views.cpython-38.pyc differ diff --git a/app/api/controller/__pycache__/caseController.cpython-38.pyc b/app/api/controller/__pycache__/caseController.cpython-38.pyc index 92c9c24..e3e5e3a 100644 Binary files a/app/api/controller/__pycache__/caseController.cpython-38.pyc and b/app/api/controller/__pycache__/caseController.cpython-38.pyc differ diff --git a/app/api/controller/__pycache__/planController.cpython-38.pyc b/app/api/controller/__pycache__/planController.cpython-38.pyc index 362c354..3368fc4 100644 Binary files a/app/api/controller/__pycache__/planController.cpython-38.pyc and b/app/api/controller/__pycache__/planController.cpython-38.pyc differ diff --git a/app/api/controller/caseController.py b/app/api/controller/caseController.py index b5f361f..aef146a 100644 --- a/app/api/controller/caseController.py +++ b/app/api/controller/caseController.py @@ -31,6 +31,8 @@ class CaseController(BaseCrudController): if hasattr(Module, 'is_delete'): query = query.filter(Module.is_delete == 0) + if hasattr(Module, 'status'): + query = query.filter(Module.status == 1) total = query.count() @@ -90,11 +92,18 @@ class CaseController(BaseCrudController): 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)]: + ('caseType', TestCase.case_type), ('isAuto', TestCase.is_auto)]: value = self._get(self.req_data, req_key) if value not in (None, ''): 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') if keyword: @@ -108,7 +117,7 @@ class CaseController(BaseCrudController): 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')).\ + 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(Module, TestCase.module_id == Module.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) if hasattr(Module, 'is_delete'): 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'): 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() 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['project_name'] = project_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['created_by_name'] = created_by_name or '' if not case_dict.get('steps'): @@ -184,6 +202,7 @@ class CaseController(BaseCrudController): '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)), + 'is_ai_generated': int(self._get(self.req_data, 'isAiGenerated', default=0)), 'created_by': getattr(g, 'current_user_id', None), 'is_delete': 0 } @@ -195,7 +214,7 @@ class CaseController(BaseCrudController): 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')] + 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: value = self._get(self.req_data, req_key) if value is not None: @@ -208,10 +227,83 @@ class CaseController(BaseCrudController): 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) + 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 为必传参数' + 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): case_id = self._get(self.req_data, 'caseId') @@ -374,6 +466,7 @@ class CaseController(BaseCrudController): tags=tags, status=1, is_auto=0, + is_ai_generated=0, created_by=getattr(g, 'current_user_id', None), is_delete=0 ) diff --git a/app/api/controller/planController.py b/app/api/controller/planController.py index b7ee195..e85c849 100644 --- a/app/api/controller/planController.py +++ b/app/api/controller/planController.py @@ -116,7 +116,7 @@ class PlanController(BaseCrudController): 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} + module_info_map = {module.id: {'name': module.name, 'path': module.path} for module in modules} result_list = [] for item in items: @@ -126,13 +126,16 @@ class PlanController(BaseCrudController): 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] + case_dict['module_name'] = module_info_map[module_id]['name'] + case_dict['module_path'] = module_info_map[module_id].get('path', '') else: case_dict['module_name'] = '' + case_dict['module_path'] = '' else: case_dict['case_key'] = '' case_dict['case_title'] = '' case_dict['module_name'] = '' + case_dict['module_path'] = '' result_list.append(case_dict) return {'list': result_list, 'total': total} diff --git a/app/api/model/__pycache__/caseModel.cpython-38.pyc b/app/api/model/__pycache__/caseModel.cpython-38.pyc index 58bf4b7..f0611cf 100644 Binary files a/app/api/model/__pycache__/caseModel.cpython-38.pyc and b/app/api/model/__pycache__/caseModel.cpython-38.pyc differ diff --git a/app/api/model/caseModel.py b/app/api/model/caseModel.py index e798739..96915d0 100644 --- a/app/api/model/caseModel.py +++ b/app/api/model/caseModel.py @@ -17,6 +17,7 @@ class Module(Base): sort_order = Column(Integer, default=0, comment='排序') path = Column(String(512), comment='模块路径') is_delete = Column(Integer, default=0, comment='0:未删除;1:已删除') + status = Column(Integer, default=0, comment='0:待确认;1:正常;2:弃用') class TestCase(Base): @@ -34,6 +35,7 @@ class TestCase(Base): tags = Column(ARRAY(String(64)), server_default=text("'{}'::varchar[]"), comment='标签') status = Column(SmallInteger, default=1, comment='1:正常 2:已废弃 3:评审中 4:评审通过') is_auto = Column(Integer, default=0, comment='0:未实现自动化;1:已实现自动化') + is_ai_generated = Column(Integer, default=0, comment='0:非AI生成;1:AI生成') created_by = Column(BigInteger, comment='创建人') is_delete = Column(Integer, default=0, comment='0:未删除;1:已删除') created_time = Column(TIMESTAMP, server_default=text('CURRENT_TIMESTAMP'), nullable=True, comment='创建时间') diff --git a/app/api/views.py b/app/api/views.py index 3cf7147..6a23381 100644 --- a/app/api/views.py +++ b/app/api/views.py @@ -18,6 +18,8 @@ from .controller.userController import UserController from .controller.bugController import BugController, BugUploadController from .controller.projectHookController import ProjectHookController from .controller.automationController import AutomationController +from .controller.skillController import SkillController +from .controller.documentSourceController import DocumentSourceController api = Blueprint('api', __name__) @@ -514,16 +516,31 @@ def case_update(): @permission_required('case:delete') def case_delete(): 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: logger.warning(f'case_delete失败:{err_msg}, 请求参数:{request.get_json()}') 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: logger.error(f'case_delete异常:{str(e)}, 请求参数:{request.get_json()}, 堆栈:{traceback.format_exc()}') 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']) @login_required @permission_required('case:create') @@ -922,6 +939,159 @@ def automation_execution_abort(): 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: 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() + diff --git a/check_permission_table.py b/check_permission_table.py deleted file mode 100644 index a5c9e7d..0000000 --- a/check_permission_table.py +++ /dev/null @@ -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() \ No newline at end of file diff --git a/common/__pycache__/sqlSession.cpython-38.pyc b/common/__pycache__/sqlSession.cpython-38.pyc index d2a95ab..c728a5e 100644 Binary files a/common/__pycache__/sqlSession.cpython-38.pyc and b/common/__pycache__/sqlSession.cpython-38.pyc differ diff --git a/common/sqlSession.py b/common/sqlSession.py index 3895627..0c0cdbf 100644 --- a/common/sqlSession.py +++ b/common/sqlSession.py @@ -2,6 +2,7 @@ from sqlalchemy import create_engine, text from sqlalchemy.orm import sessionmaker from urllib.parse import quote_plus as urlquote +import time from const import sparkatp_sql_uri @@ -10,6 +11,10 @@ from logger import logger _ENGINE_CACHE = {} _SESSION_FACTORY_CACHE = {} +# 重试配置 +MAX_RETRY_ATTEMPTS = 3 +RETRY_DELAY = 1 # 秒 + """ sql操作: @@ -32,28 +37,52 @@ class SqlSession: def build_postgres_uri(host, port, user, password, database): return f"postgresql+psycopg2://{user}:{urlquote(str(password))}@{host}:{port}/{database}" - def get_session(self): - session_factory = _SESSION_FACTORY_CACHE.get(self.sql_uri) - if session_factory is None: - engine = _ENGINE_CACHE.get(self.sql_uri) - if engine is None: - engine = create_engine( - self.sql_uri, - pool_size=20, - max_overflow=30, - pool_pre_ping=True, - pool_recycle=1200, - pool_timeout=60, - pool_use_lifo=True, - connect_args={ - 'connect_timeout': 10, - 'options': '-c timezone=Asia/Shanghai' - } - ) - _ENGINE_CACHE[self.sql_uri] = engine - session_factory = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False) - _SESSION_FACTORY_CACHE[self.sql_uri] = session_factory - return session_factory() + def get_session(self, retry_count=0): + try: + session_factory = _SESSION_FACTORY_CACHE.get(self.sql_uri) + if session_factory is None: + engine = _ENGINE_CACHE.get(self.sql_uri) + if engine is None: + engine = create_engine( + self.sql_uri, + pool_size=10, + max_overflow=20, + pool_recycle=180, # 3分钟回收连接,避免连接被服务器断开 + pool_pre_ping=True, # 获取连接前先验证有效性 + pool_timeout=15, # 获取连接超时时间 + connect_args={ + 'connect_timeout': 5, + 'options': '-c timezone=Asia/Shanghai', + 'keepalives': 1, + 'keepalives_idle': 30, + 'keepalives_interval': 5, + 'keepalives_count': 3 + } + ) + _ENGINE_CACHE[self.sql_uri] = engine + session_factory = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False) + _SESSION_FACTORY_CACHE[self.sql_uri] = 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): return self._session.query(*args) diff --git a/const.py b/const.py index 25c0720..b339198 100644 --- a/const.py +++ b/const.py @@ -29,8 +29,8 @@ RES_CODE = { 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-platform-prod' +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' EXECUTE_DB_CONFIG = { 'ZHYY': { 'st': { @@ -83,8 +83,8 @@ STRESS_URI = 'https://qe.bg.huohua.cn' QE_DOMAIN = 'https://qe.bg.huohua.cn' PASSWORD = quote('AcUVeRb8lN') -REDIS_URL = 'redis://127.0.0.1:7379/15' -# REDIS_URL = 'redis://124.220.32.45:7379/15' +# REDIS_URL = 'redis://127.0.0.1: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_USER = os.environ.get('JENKINS_USER', 'jenkins') diff --git a/generate_automation_menus.py b/generate_automation_menus.py deleted file mode 100644 index b57706c..0000000 --- a/generate_automation_menus.py +++ /dev/null @@ -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() \ No newline at end of file