diff --git a/backend/app/api/v1/system_settings.py b/backend/app/api/v1/system_settings.py index 01fae31..e41a396 100644 --- a/backend/app/api/v1/system_settings.py +++ b/backend/app/api/v1/system_settings.py @@ -297,38 +297,27 @@ async def get_employee_sync_config( 获取员工同步配置 仅限管理员访问 + 数据库连接从环境变量读取,前端只能配置表名和开关 """ check_admin_permission(current_user) + import os tenant_id = await get_or_create_tenant_id(db) - # 获取配置 - db_host = await get_system_config(db, tenant_id, 'employee_sync', 'DB_HOST') - db_port = await get_system_config(db, tenant_id, 'employee_sync', 'DB_PORT') - db_name = await get_system_config(db, tenant_id, 'employee_sync', 'DB_NAME') - db_user = await get_system_config(db, tenant_id, 'employee_sync', 'DB_USER') - db_password = await get_system_config(db, tenant_id, 'employee_sync', 'DB_PASSWORD') + # 从数据库获取表名和开关配置 table_name = await get_system_config(db, tenant_id, 'employee_sync', 'TABLE_NAME') enabled = await get_feature_switch(db, tenant_id, 'employee_sync') - # 脱敏处理密码 - password_masked = None - if db_password: - if len(db_password) > 4: - password_masked = '****' + db_password[-2:] - else: - password_masked = '****' + # 从环境变量检查数据源是否已配置 + sync_db_url = os.environ.get('EMPLOYEE_SYNC_DB_URL', '') + configured = bool(sync_db_url) return ResponseModel( message="获取成功", data={ - "db_host": db_host, - "db_port": int(db_port) if db_port else None, - "db_name": db_name, - "db_user": db_user, - "db_password_masked": password_masked, "table_name": table_name or "v_钉钉员工表", "enabled": enabled, + "configured": configured, # 数据源是否已在环境变量配置 } ) @@ -343,27 +332,13 @@ async def update_employee_sync_config( 更新员工同步配置 仅限管理员访问 + 只能配置表名和开关,数据库连接从环境变量读取 """ check_admin_permission(current_user) tenant_id = await get_or_create_tenant_id(db) try: - if config.db_host is not None: - await set_system_config(db, tenant_id, 'employee_sync', 'DB_HOST', config.db_host) - - if config.db_port is not None: - await set_system_config(db, tenant_id, 'employee_sync', 'DB_PORT', str(config.db_port)) - - if config.db_name is not None: - await set_system_config(db, tenant_id, 'employee_sync', 'DB_NAME', config.db_name) - - if config.db_user is not None: - await set_system_config(db, tenant_id, 'employee_sync', 'DB_USER', config.db_user) - - if config.db_password is not None: - await set_system_config(db, tenant_id, 'employee_sync', 'DB_PASSWORD', config.db_password) - if config.table_name is not None: await set_system_config(db, tenant_id, 'employee_sync', 'TABLE_NAME', config.table_name) @@ -398,32 +373,29 @@ async def test_employee_sync_connection( 测试员工同步数据库连接 仅限管理员访问 + 使用环境变量中的数据库连接配置 """ check_admin_permission(current_user) + import os tenant_id = await get_or_create_tenant_id(db) - # 获取配置 - db_host = await get_system_config(db, tenant_id, 'employee_sync', 'DB_HOST') - db_port = await get_system_config(db, tenant_id, 'employee_sync', 'DB_PORT') - db_name = await get_system_config(db, tenant_id, 'employee_sync', 'DB_NAME') - db_user = await get_system_config(db, tenant_id, 'employee_sync', 'DB_USER') - db_password = await get_system_config(db, tenant_id, 'employee_sync', 'DB_PASSWORD') - table_name = await get_system_config(db, tenant_id, 'employee_sync', 'TABLE_NAME') or "v_钉钉员工表" + # 从环境变量获取数据库连接 + sync_db_url = os.environ.get('EMPLOYEE_SYNC_DB_URL', '') - if not all([db_host, db_port, db_name, db_user, db_password]): + if not sync_db_url: return ResponseModel( code=400, - message="配置不完整,请先填写所有数据库连接信息" + message="数据源未配置,请联系系统管理员配置 EMPLOYEE_SYNC_DB_URL 环境变量" ) + # 获取表名配置 + table_name = await get_system_config(db, tenant_id, 'employee_sync', 'TABLE_NAME') or "v_钉钉员工表" + try: from sqlalchemy.ext.asyncio import create_async_engine - # 构建连接URL - db_url = f"mysql+aiomysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}?charset=utf8mb4" - - engine = create_async_engine(db_url, echo=False, pool_pre_ping=True) + engine = create_async_engine(sync_db_url, echo=False, pool_pre_ping=True) async with engine.connect() as conn: # 测试查询员工表 @@ -433,7 +405,7 @@ async def test_employee_sync_connection( await engine.dispose() return ResponseModel( - message=f"连接成功!员工表共有 {count} 条记录", + message=f"连接成功!员工表共有 {count} 条在职员工", data={"employee_count": count} ) diff --git a/backend/app/services/employee_sync_service.py b/backend/app/services/employee_sync_service.py index 6864ed9..77831a3 100644 --- a/backend/app/services/employee_sync_service.py +++ b/backend/app/services/employee_sync_service.py @@ -23,8 +23,7 @@ logger = get_logger(__name__) class EmployeeSyncService: """员工同步服务""" - # 默认外部数据库连接配置(向后兼容) - DEFAULT_DB_URL = "mysql+aiomysql://neuron_new:NWxGM6CQoMLKyEszXhfuLBIIo1QbeK@120.77.144.233:29613/neuron_new?charset=utf8mb4" + # 默认外部数据库连接配置(向后兼容,从环境变量读取) DEFAULT_TABLE_NAME = "v_钉钉员工表" def __init__(self, db: AsyncSession, tenant_id: int = 1): @@ -34,46 +33,43 @@ class EmployeeSyncService: self.table_name = self.DEFAULT_TABLE_NAME self._db_url = None - async def _get_config_from_db(self) -> tuple: - """从数据库获取员工同步配置""" - result = await self.db.execute( - text(""" - SELECT config_key, config_value - FROM tenant_configs - WHERE tenant_id = :tenant_id AND config_group = 'employee_sync' - """), - {"tenant_id": self.tenant_id} - ) - rows = result.fetchall() - - config = {} - for key, value in rows: - config[key] = value - - return config + async def _get_table_name_from_db(self) -> str: + """从数据库获取员工表名配置""" + try: + result = await self.db.execute( + text(""" + SELECT config_value + FROM tenant_configs + WHERE tenant_id = :tenant_id + AND config_group = 'employee_sync' + AND config_key = 'TABLE_NAME' + """), + {"tenant_id": self.tenant_id} + ) + row = result.fetchone() + return row[0] if row else self.DEFAULT_TABLE_NAME + except Exception: + return self.DEFAULT_TABLE_NAME - async def _build_db_url(self) -> str: - """构建数据库连接URL""" - config = await self._get_config_from_db() + def _get_db_url_from_env(self) -> str: + """从环境变量获取数据库连接URL""" + import os - db_host = config.get('DB_HOST') - db_port = config.get('DB_PORT') - db_name = config.get('DB_NAME') - db_user = config.get('DB_USER') - db_password = config.get('DB_PASSWORD') - self.table_name = config.get('TABLE_NAME') or self.DEFAULT_TABLE_NAME + # 优先使用环境变量中的完整URL + db_url = os.environ.get('EMPLOYEE_SYNC_DB_URL', '') - # 如果配置完整,使用配置的URL - if all([db_host, db_port, db_name, db_user, db_password]): - return f"mysql+aiomysql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}?charset=utf8mb4" + if db_url: + return db_url - # 否则使用默认配置 - logger.warning(f"租户 {self.tenant_id} 未配置员工同步数据库,使用默认配置") - return self.DEFAULT_DB_URL + # 向后兼容:如果没有配置环境变量,使用默认值 + logger.warning("EMPLOYEE_SYNC_DB_URL 环境变量未配置,使用默认数据源") + return "mysql+aiomysql://neuron_new:NWxGM6CQoMLKyEszXhfuLBIIo1QbeK@120.77.144.233:29613/neuron_new?charset=utf8mb4" async def __aenter__(self): """异步上下文管理器入口""" - self._db_url = await self._build_db_url() + self._db_url = self._get_db_url_from_env() + self.table_name = await self._get_table_name_from_db() + self.external_engine = create_async_engine( self._db_url, echo=False, diff --git a/frontend/src/views/admin/system-settings.vue b/frontend/src/views/admin/system-settings.vue index 6e7543b..ac96c60 100644 --- a/frontend/src/views/admin/system-settings.vue +++ b/frontend/src/views/admin/system-settings.vue @@ -103,7 +103,7 @@ style="margin-bottom: 20px;" > @@ -111,7 +111,6 @@ @@ -124,72 +123,29 @@ 启用后将每日自动同步员工数据 - 数据库连接配置 - - - - - - - - - - - - - - - - - - - - - 当前值: {{ syncForm.db_password_masked }} - - - - 表或视图需包含:员工姓名、手机号、所属部门、职位等字段 + 视图需包含:员工姓名、手机号、所属部门、职位、钉钉用户ID等字段 + + + + + {{ syncForm.configured ? '已配置' : '未配置' }} + + 数据源由系统管理员在后台配置 保存配置 - + 测试连接 - 重置 @@ -229,13 +185,8 @@ const dingtalkForm = reactive({ // 员工同步配置表单 const syncForm = reactive({ enabled: false, - db_host: '', - db_port: 3306, - db_name: '', - db_user: '', - db_password: '', - db_password_masked: '', table_name: 'v_钉钉员工表', + configured: false, // 数据源是否已配置 }) // 表单验证规则 @@ -251,17 +202,7 @@ const dingtalkRules = reactive({ ] }) -const syncRules = reactive({ - db_host: [ - { required: false, message: '请输入数据库主机', trigger: 'blur' } - ], - db_name: [ - { required: false, message: '请输入数据库名', trigger: 'blur' } - ], - db_user: [ - { required: false, message: '请输入用户名', trigger: 'blur' } - ] -}) +const syncRules = reactive({}) /** * 加载钉钉配置 @@ -341,17 +282,11 @@ const loadSyncConfig = async () => { const response = await request.get('/api/v1/settings/employee-sync') if (response.code === 200 && response.data) { syncForm.enabled = response.data.enabled || false - syncForm.db_host = response.data.db_host || '' - syncForm.db_port = response.data.db_port || 3306 - syncForm.db_name = response.data.db_name || '' - syncForm.db_user = response.data.db_user || '' - syncForm.db_password = '' - syncForm.db_password_masked = response.data.db_password_masked || '' syncForm.table_name = response.data.table_name || 'v_钉钉员工表' + syncForm.configured = response.data.configured || false } } catch (error: any) { console.error('加载员工同步配置失败:', error) - // 不显示错误提示,可能是表不存在 } finally { syncLoading.value = false } @@ -363,19 +298,11 @@ const loadSyncConfig = async () => { const saveSyncConfig = async () => { syncSaving.value = true try { - const updateData: any = { + const updateData = { enabled: syncForm.enabled, - db_host: syncForm.db_host, - db_port: syncForm.db_port, - db_name: syncForm.db_name, - db_user: syncForm.db_user, table_name: syncForm.table_name, } - if (syncForm.db_password) { - updateData.db_password = syncForm.db_password - } - const response = await request.put('/api/v1/settings/employee-sync', updateData) if (response.code === 200) { ElMessage.success('配置保存成功')