refactor: 员工同步数据库配置改为环境变量
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
- 前端隐藏数据库连接配置输入 - 只保留"启用开关"和"表名"配置 - 数据库连接从 EMPLOYEE_SYNC_DB_URL 环境变量读取 - 显示数据源配置状态 - 保留默认值用于向后兼容
This commit is contained in:
@@ -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}
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
style="margin-bottom: 20px;"
|
||||
>
|
||||
<template #default>
|
||||
<p>配置外部数据源,系统将自动同步员工信息(姓名、手机号、部门、岗位等)。</p>
|
||||
<p>系统将从钉钉员工数据源自动同步员工信息(姓名、手机号、部门、岗位等)。</p>
|
||||
<p style="margin-top: 8px;">同步的员工将自动创建系统账号,初始密码为 123456。</p>
|
||||
</template>
|
||||
</el-alert>
|
||||
@@ -111,7 +111,6 @@
|
||||
<el-form
|
||||
ref="syncFormRef"
|
||||
:model="syncForm"
|
||||
:rules="syncRules"
|
||||
label-width="140px"
|
||||
v-loading="syncLoading"
|
||||
>
|
||||
@@ -124,72 +123,29 @@
|
||||
<span class="form-tip">启用后将每日自动同步员工数据</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider content-position="left">数据库连接配置</el-divider>
|
||||
|
||||
<el-form-item label="数据库主机" prop="db_host">
|
||||
<el-input
|
||||
v-model="syncForm.db_host"
|
||||
placeholder="如:192.168.1.100"
|
||||
style="width: 300px;"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="端口" prop="db_port">
|
||||
<el-input-number
|
||||
v-model="syncForm.db_port"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
placeholder="3306"
|
||||
style="width: 150px;"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="数据库名" prop="db_name">
|
||||
<el-input
|
||||
v-model="syncForm.db_name"
|
||||
placeholder="请输入数据库名称"
|
||||
style="width: 300px;"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="用户名" prop="db_user">
|
||||
<el-input
|
||||
v-model="syncForm.db_user"
|
||||
placeholder="请输入数据库用户名"
|
||||
style="width: 300px;"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="db_password">
|
||||
<el-input
|
||||
v-model="syncForm.db_password"
|
||||
type="password"
|
||||
show-password
|
||||
:placeholder="syncForm.db_password_masked || '请输入数据库密码'"
|
||||
style="width: 300px;"
|
||||
/>
|
||||
<span class="form-tip" v-if="syncForm.db_password_masked && !syncForm.db_password">
|
||||
当前值: {{ syncForm.db_password_masked }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="员工表名" prop="table_name">
|
||||
<el-input
|
||||
v-model="syncForm.table_name"
|
||||
placeholder="v_钉钉员工表"
|
||||
style="width: 300px;"
|
||||
/>
|
||||
<span class="form-tip">表或视图需包含:员工姓名、手机号、所属部门、职位等字段</span>
|
||||
<span class="form-tip">视图需包含:员工姓名、手机号、所属部门、职位、钉钉用户ID等字段</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="数据源状态">
|
||||
<el-tag :type="syncForm.configured ? 'success' : 'warning'">
|
||||
{{ syncForm.configured ? '已配置' : '未配置' }}
|
||||
</el-tag>
|
||||
<span class="form-tip">数据源由系统管理员在后台配置</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveSyncConfig" :loading="syncSaving">
|
||||
保存配置
|
||||
</el-button>
|
||||
<el-button @click="testSyncConnection" :loading="syncTesting">
|
||||
<el-button @click="testSyncConnection" :loading="syncTesting" :disabled="!syncForm.configured">
|
||||
测试连接
|
||||
</el-button>
|
||||
<el-button @click="loadSyncConfig">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
@@ -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<FormRules>({
|
||||
]
|
||||
})
|
||||
|
||||
const syncRules = reactive<FormRules>({
|
||||
db_host: [
|
||||
{ required: false, message: '请输入数据库主机', trigger: 'blur' }
|
||||
],
|
||||
db_name: [
|
||||
{ required: false, message: '请输入数据库名', trigger: 'blur' }
|
||||
],
|
||||
db_user: [
|
||||
{ required: false, message: '请输入用户名', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
const syncRules = reactive<FormRules>({})
|
||||
|
||||
/**
|
||||
* 加载钉钉配置
|
||||
@@ -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('配置保存成功')
|
||||
|
||||
Reference in New Issue
Block a user