Compare commits
	
		
			3 Commits
		
	
	
		
			fead740a0f
			...
			e5383ce268
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e5383ce268 | |||
| 0b16d7d886 | |||
| ca3cf02396 | 
							
								
								
									
										336
									
								
								frontend/dashboard.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								frontend/dashboard.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,336 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="zh-CN">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					    <title>猪场管理系统 - 控制台</title>
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        * {
 | 
				
			||||||
 | 
					            margin: 0;
 | 
				
			||||||
 | 
					            padding: 0;
 | 
				
			||||||
 | 
					            box-sizing: border-box;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        body {
 | 
				
			||||||
 | 
					            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 | 
				
			||||||
 | 
					            background: #f5f7fa;
 | 
				
			||||||
 | 
					            min-height: 100vh;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .header {
 | 
				
			||||||
 | 
					            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            padding: 20px;
 | 
				
			||||||
 | 
					            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            justify-content: space-between;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .header h1 {
 | 
				
			||||||
 | 
					            font-size: 24px;
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            gap: 10px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .user-info {
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            gap: 15px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .logout-btn {
 | 
				
			||||||
 | 
					            background: rgba(255, 255, 255, 0.2);
 | 
				
			||||||
 | 
					            border: 1px solid rgba(255, 255, 255, 0.3);
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            padding: 8px 15px;
 | 
				
			||||||
 | 
					            border-radius: 5px;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            transition: all 0.3s ease;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .logout-btn:hover {
 | 
				
			||||||
 | 
					            background: rgba(255, 255, 255, 0.3);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .container {
 | 
				
			||||||
 | 
					            max-width: 1200px;
 | 
				
			||||||
 | 
					            margin: 30px auto;
 | 
				
			||||||
 | 
					            padding: 0 20px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .dashboard-grid {
 | 
				
			||||||
 | 
					            display: grid;
 | 
				
			||||||
 | 
					            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
 | 
				
			||||||
 | 
					            gap: 20px;
 | 
				
			||||||
 | 
					            margin-bottom: 30px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .card {
 | 
				
			||||||
 | 
					            background: white;
 | 
				
			||||||
 | 
					            border-radius: 15px;
 | 
				
			||||||
 | 
					            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
 | 
				
			||||||
 | 
					            padding: 25px;
 | 
				
			||||||
 | 
					            transition: transform 0.3s ease, box-shadow 0.3s ease;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .card:hover {
 | 
				
			||||||
 | 
					            transform: translateY(-5px);
 | 
				
			||||||
 | 
					            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .card h3 {
 | 
				
			||||||
 | 
					            color: #333;
 | 
				
			||||||
 | 
					            margin-bottom: 15px;
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            gap: 10px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .card p {
 | 
				
			||||||
 | 
					            color: #666;
 | 
				
			||||||
 | 
					            line-height: 1.6;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .device-control {
 | 
				
			||||||
 | 
					            background: white;
 | 
				
			||||||
 | 
					            border-radius: 15px;
 | 
				
			||||||
 | 
					            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
 | 
				
			||||||
 | 
					            padding: 25px;
 | 
				
			||||||
 | 
					            margin-bottom: 30px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .device-control h2 {
 | 
				
			||||||
 | 
					            color: #333;
 | 
				
			||||||
 | 
					            margin-bottom: 20px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .form-group {
 | 
				
			||||||
 | 
					            margin-bottom: 20px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label {
 | 
				
			||||||
 | 
					            display: block;
 | 
				
			||||||
 | 
					            margin-bottom: 8px;
 | 
				
			||||||
 | 
					            color: #555;
 | 
				
			||||||
 | 
					            font-weight: 500;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        select, input {
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            padding: 12px;
 | 
				
			||||||
 | 
					            border: 2px solid #e1e1e1;
 | 
				
			||||||
 | 
					            border-radius: 8px;
 | 
				
			||||||
 | 
					            font-size: 16px;
 | 
				
			||||||
 | 
					            outline: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        select:focus, input:focus {
 | 
				
			||||||
 | 
					            border-color: #667eea;
 | 
				
			||||||
 | 
					            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button {
 | 
				
			||||||
 | 
					            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            padding: 12px 25px;
 | 
				
			||||||
 | 
					            border-radius: 8px;
 | 
				
			||||||
 | 
					            font-size: 16px;
 | 
				
			||||||
 | 
					            font-weight: 600;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            transition: all 0.3s ease;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button:hover {
 | 
				
			||||||
 | 
					            transform: translateY(-2px);
 | 
				
			||||||
 | 
					            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .status-indicator {
 | 
				
			||||||
 | 
					            display: inline-block;
 | 
				
			||||||
 | 
					            width: 12px;
 | 
				
			||||||
 | 
					            height: 12px;
 | 
				
			||||||
 | 
					            border-radius: 50%;
 | 
				
			||||||
 | 
					            margin-right: 8px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .status-online {
 | 
				
			||||||
 | 
					            background-color: #27ae60;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .status-offline {
 | 
				
			||||||
 | 
					            background-color: #e74c3c;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .notification {
 | 
				
			||||||
 | 
					            padding: 15px;
 | 
				
			||||||
 | 
					            border-radius: 8px;
 | 
				
			||||||
 | 
					            margin: 15px 0;
 | 
				
			||||||
 | 
					            display: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .notification.success {
 | 
				
			||||||
 | 
					            background-color: #d4edda;
 | 
				
			||||||
 | 
					            color: #155724;
 | 
				
			||||||
 | 
					            border: 1px solid #c3e6cb;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .notification.error {
 | 
				
			||||||
 | 
					            background-color: #f8d7da;
 | 
				
			||||||
 | 
					            color: #721c24;
 | 
				
			||||||
 | 
					            border: 1px solid #f5c6cb;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @media (max-width: 768px) {
 | 
				
			||||||
 | 
					            .dashboard-grid {
 | 
				
			||||||
 | 
					                grid-template-columns: 1fr;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            .header {
 | 
				
			||||||
 | 
					                flex-direction: column;
 | 
				
			||||||
 | 
					                gap: 15px;
 | 
				
			||||||
 | 
					                text-align: center;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <div class="header">
 | 
				
			||||||
 | 
					        <h1>🐷 猪场管理系统</h1>
 | 
				
			||||||
 | 
					        <div class="user-info">
 | 
				
			||||||
 | 
					            <span id="username">用户</span>
 | 
				
			||||||
 | 
					            <button class="logout-btn" id="logoutBtn">退出登录</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="container">
 | 
				
			||||||
 | 
					        <div class="dashboard-grid">
 | 
				
			||||||
 | 
					            <div class="card">
 | 
				
			||||||
 | 
					                <h3>📊 系统状态</h3>
 | 
				
			||||||
 | 
					                <p>当前系统运行正常</p>
 | 
				
			||||||
 | 
					                <p>连接设备: <span id="deviceCount">0</span> 台</p>
 | 
				
			||||||
 | 
					                <p>今日操作: <span id="operationCount">0</span> 次</p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="card">
 | 
				
			||||||
 | 
					                <h3>🌡️ 环境监控</h3>
 | 
				
			||||||
 | 
					                <p>温度: <span id="temperature">25.6</span>°C</p>
 | 
				
			||||||
 | 
					                <p>湿度: <span id="humidity">65</span>%</p>
 | 
				
			||||||
 | 
					                <p>空气质量: 良好</p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="card">
 | 
				
			||||||
 | 
					                <h3>📡 设备连接</h3>
 | 
				
			||||||
 | 
					                <p>中继设备: <span class="status-indicator status-online"></span>在线</p>
 | 
				
			||||||
 | 
					                <p>风扇设备: <span class="status-indicator status-online"></span>在线</p>
 | 
				
			||||||
 | 
					                <p>水帘设备: <span class="status-indicator status-offline"></span>离线</p>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="device-control">
 | 
				
			||||||
 | 
					            <h2>⚙️ 设备控制</h2>
 | 
				
			||||||
 | 
					            <div class="notification" id="controlNotification"></div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <div class="form-group">
 | 
				
			||||||
 | 
					                <label for="deviceType">设备类型</label>
 | 
				
			||||||
 | 
					                <select id="deviceType">
 | 
				
			||||||
 | 
					                    <option value="fan">风扇</option>
 | 
				
			||||||
 | 
					                    <option value="water_curtain">水帘</option>
 | 
				
			||||||
 | 
					                </select>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group">
 | 
				
			||||||
 | 
					                <label for="deviceId">设备ID</label>
 | 
				
			||||||
 | 
					                <input type="text" id="deviceId" placeholder="请输入设备ID">
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div class="form-group">
 | 
				
			||||||
 | 
					                <label for="action">操作</label>
 | 
				
			||||||
 | 
					                <select id="action">
 | 
				
			||||||
 | 
					                    <option value="on">开启</option>
 | 
				
			||||||
 | 
					                    <option value="off">关闭</option>
 | 
				
			||||||
 | 
					                </select>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <button id="controlBtn">执行控制</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        // 检查用户是否已登录
 | 
				
			||||||
 | 
					        const token = localStorage.getItem('authToken');
 | 
				
			||||||
 | 
					        const username = localStorage.getItem('username');
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (!token) {
 | 
				
			||||||
 | 
					            window.location.href = '/index.html';
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            document.getElementById('username').textContent = username;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 退出登录
 | 
				
			||||||
 | 
					        document.getElementById('logoutBtn').addEventListener('click', function() {
 | 
				
			||||||
 | 
					            localStorage.removeItem('authToken');
 | 
				
			||||||
 | 
					            localStorage.removeItem('userId');
 | 
				
			||||||
 | 
					            localStorage.removeItem('username');
 | 
				
			||||||
 | 
					            window.location.href = '/index.html';
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 设备控制
 | 
				
			||||||
 | 
					        document.getElementById('controlBtn').addEventListener('click', async function() {
 | 
				
			||||||
 | 
					            const deviceType = document.getElementById('deviceType').value;
 | 
				
			||||||
 | 
					            const deviceId = document.getElementById('deviceId').value;
 | 
				
			||||||
 | 
					            const action = document.getElementById('action').value;
 | 
				
			||||||
 | 
					            const notification = document.getElementById('controlNotification');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (!deviceId) {
 | 
				
			||||||
 | 
					                showNotification('请输入设备ID', 'error');
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const response = await fetch('/api/v1/device/switch', {
 | 
				
			||||||
 | 
					                    method: 'POST',
 | 
				
			||||||
 | 
					                    headers: {
 | 
				
			||||||
 | 
					                        'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					                        'Authorization': `Bearer ${token}`
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    body: JSON.stringify({ 
 | 
				
			||||||
 | 
					                        device_type: deviceType,
 | 
				
			||||||
 | 
					                        device_id: deviceId,
 | 
				
			||||||
 | 
					                        action: action
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                const data = await response.json();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (response.ok && data.code === 0) {
 | 
				
			||||||
 | 
					                    showNotification(`设备控制成功: ${data.message}`, 'success');
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    showNotification(`控制失败: ${data.message || '未知错误'}`, 'error');
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (error) {
 | 
				
			||||||
 | 
					                showNotification('网络错误,请稍后重试', 'error');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 显示通知
 | 
				
			||||||
 | 
					        function showNotification(message, type) {
 | 
				
			||||||
 | 
					            const notification = document.getElementById('controlNotification');
 | 
				
			||||||
 | 
					            notification.textContent = message;
 | 
				
			||||||
 | 
					            notification.className = 'notification ' + type;
 | 
				
			||||||
 | 
					            notification.style.display = 'block';
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 3秒后自动隐藏
 | 
				
			||||||
 | 
					            setTimeout(() => {
 | 
				
			||||||
 | 
					                notification.style.display = 'none';
 | 
				
			||||||
 | 
					            }, 3000);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 模拟获取设备数量
 | 
				
			||||||
 | 
					        document.getElementById('deviceCount').textContent = '12';
 | 
				
			||||||
 | 
					        document.getElementById('operationCount').textContent = '24';
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										272
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,272 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="zh-CN">
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					    <title>猪场管理系统 - 登录</title>
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					        * {
 | 
				
			||||||
 | 
					            margin: 0;
 | 
				
			||||||
 | 
					            padding: 0;
 | 
				
			||||||
 | 
					            box-sizing: border-box;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        body {
 | 
				
			||||||
 | 
					            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 | 
				
			||||||
 | 
					            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
				
			||||||
 | 
					            min-height: 100vh;
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            justify-content: center;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .login-container {
 | 
				
			||||||
 | 
					            background: rgba(255, 255, 255, 0.95);
 | 
				
			||||||
 | 
					            backdrop-filter: blur(10px);
 | 
				
			||||||
 | 
					            border-radius: 20px;
 | 
				
			||||||
 | 
					            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
 | 
				
			||||||
 | 
					            padding: 40px;
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            max-width: 400px;
 | 
				
			||||||
 | 
					            text-align: center;
 | 
				
			||||||
 | 
					            animation: fadeIn 0.5s ease-out;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @keyframes fadeIn {
 | 
				
			||||||
 | 
					            from {
 | 
				
			||||||
 | 
					                opacity: 0;
 | 
				
			||||||
 | 
					                transform: translateY(-20px);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            to {
 | 
				
			||||||
 | 
					                opacity: 1;
 | 
				
			||||||
 | 
					                transform: translateY(0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .logo {
 | 
				
			||||||
 | 
					            width: 80px;
 | 
				
			||||||
 | 
					            height: 80px;
 | 
				
			||||||
 | 
					            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
				
			||||||
 | 
					            border-radius: 50%;
 | 
				
			||||||
 | 
					            margin: 0 auto 20px;
 | 
				
			||||||
 | 
					            display: flex;
 | 
				
			||||||
 | 
					            align-items: center;
 | 
				
			||||||
 | 
					            justify-content: center;
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            font-size: 30px;
 | 
				
			||||||
 | 
					            font-weight: bold;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        h1 {
 | 
				
			||||||
 | 
					            color: #333;
 | 
				
			||||||
 | 
					            margin-bottom: 10px;
 | 
				
			||||||
 | 
					            font-size: 28px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .subtitle {
 | 
				
			||||||
 | 
					            color: #666;
 | 
				
			||||||
 | 
					            margin-bottom: 30px;
 | 
				
			||||||
 | 
					            font-size: 14px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .form-group {
 | 
				
			||||||
 | 
					            margin-bottom: 20px;
 | 
				
			||||||
 | 
					            text-align: left;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        label {
 | 
				
			||||||
 | 
					            display: block;
 | 
				
			||||||
 | 
					            margin-bottom: 8px;
 | 
				
			||||||
 | 
					            color: #555;
 | 
				
			||||||
 | 
					            font-weight: 500;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        input {
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            padding: 15px;
 | 
				
			||||||
 | 
					            border: 2px solid #e1e1e1;
 | 
				
			||||||
 | 
					            border-radius: 10px;
 | 
				
			||||||
 | 
					            font-size: 16px;
 | 
				
			||||||
 | 
					            transition: all 0.3s ease;
 | 
				
			||||||
 | 
					            outline: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        input:focus {
 | 
				
			||||||
 | 
					            border-color: #667eea;
 | 
				
			||||||
 | 
					            box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button {
 | 
				
			||||||
 | 
					            width: 100%;
 | 
				
			||||||
 | 
					            padding: 15px;
 | 
				
			||||||
 | 
					            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 | 
				
			||||||
 | 
					            color: white;
 | 
				
			||||||
 | 
					            border: none;
 | 
				
			||||||
 | 
					            border-radius: 10px;
 | 
				
			||||||
 | 
					            font-size: 16px;
 | 
				
			||||||
 | 
					            font-weight: 600;
 | 
				
			||||||
 | 
					            cursor: pointer;
 | 
				
			||||||
 | 
					            transition: all 0.3s ease;
 | 
				
			||||||
 | 
					            margin-top: 10px;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button:hover {
 | 
				
			||||||
 | 
					            transform: translateY(-2px);
 | 
				
			||||||
 | 
					            box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button:active {
 | 
				
			||||||
 | 
					            transform: translateY(0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        button:disabled {
 | 
				
			||||||
 | 
					            opacity: 0.7;
 | 
				
			||||||
 | 
					            cursor: not-allowed;
 | 
				
			||||||
 | 
					            transform: none;
 | 
				
			||||||
 | 
					            box-shadow: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .error-message {
 | 
				
			||||||
 | 
					            color: #e74c3c;
 | 
				
			||||||
 | 
					            margin-top: 15px;
 | 
				
			||||||
 | 
					            padding: 10px;
 | 
				
			||||||
 | 
					            border-radius: 5px;
 | 
				
			||||||
 | 
					            background-color: #fdf2f2;
 | 
				
			||||||
 | 
					            border: 1px solid #f5c6cb;
 | 
				
			||||||
 | 
					            display: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .success-message {
 | 
				
			||||||
 | 
					            color: #27ae60;
 | 
				
			||||||
 | 
					            margin-top: 15px;
 | 
				
			||||||
 | 
					            padding: 10px;
 | 
				
			||||||
 | 
					            border-radius: 5px;
 | 
				
			||||||
 | 
					            background-color: #f2fdf2;
 | 
				
			||||||
 | 
					            border: 1px solid #c6f5cb;
 | 
				
			||||||
 | 
					            display: none;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .loading {
 | 
				
			||||||
 | 
					            display: none;
 | 
				
			||||||
 | 
					            margin: 20px auto;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        .loading-spinner {
 | 
				
			||||||
 | 
					            border: 3px solid #f3f3f3;
 | 
				
			||||||
 | 
					            border-top: 3px solid #667eea;
 | 
				
			||||||
 | 
					            border-radius: 50%;
 | 
				
			||||||
 | 
					            width: 30px;
 | 
				
			||||||
 | 
					            height: 30px;
 | 
				
			||||||
 | 
					            animation: spin 1s linear infinite;
 | 
				
			||||||
 | 
					            margin: 0 auto;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @keyframes spin {
 | 
				
			||||||
 | 
					            0% { transform: rotate(0deg); }
 | 
				
			||||||
 | 
					            100% { transform: rotate(360deg); }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        @media (max-width: 480px) {
 | 
				
			||||||
 | 
					            .login-container {
 | 
				
			||||||
 | 
					                margin: 20px;
 | 
				
			||||||
 | 
					                padding: 30px 20px;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            h1 {
 | 
				
			||||||
 | 
					                font-size: 24px;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <div class="login-container">
 | 
				
			||||||
 | 
					        <div class="logo">🐷</div>
 | 
				
			||||||
 | 
					        <h1>猪场管理系统</h1>
 | 
				
			||||||
 | 
					        <p class="subtitle">请登录您的账户</p>
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        <form id="loginForm">
 | 
				
			||||||
 | 
					            <div class="form-group">
 | 
				
			||||||
 | 
					                <label for="username">用户名</label>
 | 
				
			||||||
 | 
					                <input type="text" id="username" name="username" required placeholder="请输入用户名">
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <div class="form-group">
 | 
				
			||||||
 | 
					                <label for="password">密码</label>
 | 
				
			||||||
 | 
					                <input type="password" id="password" name="password" required placeholder="请输入密码">
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            <button type="submit" id="loginBtn">登录</button>
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        <div class="loading" id="loading">
 | 
				
			||||||
 | 
					            <div class="loading-spinner"></div>
 | 
				
			||||||
 | 
					            <p>登录中...</p>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        <div class="error-message" id="errorMessage"></div>
 | 
				
			||||||
 | 
					        <div class="success-message" id="successMessage"></div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					        document.getElementById('loginForm').addEventListener('submit', async function(e) {
 | 
				
			||||||
 | 
					            e.preventDefault();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            const username = document.getElementById('username').value;
 | 
				
			||||||
 | 
					            const password = document.getElementById('password').value;
 | 
				
			||||||
 | 
					            const loginBtn = document.getElementById('loginBtn');
 | 
				
			||||||
 | 
					            const loading = document.getElementById('loading');
 | 
				
			||||||
 | 
					            const errorMessage = document.getElementById('errorMessage');
 | 
				
			||||||
 | 
					            const successMessage = document.getElementById('successMessage');
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 隐藏之前的消息
 | 
				
			||||||
 | 
					            errorMessage.style.display = 'none';
 | 
				
			||||||
 | 
					            successMessage.style.display = 'none';
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 显示加载状态
 | 
				
			||||||
 | 
					            loginBtn.disabled = true;
 | 
				
			||||||
 | 
					            loading.style.display = 'block';
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                const response = await fetch('/api/v1/user/login', {
 | 
				
			||||||
 | 
					                    method: 'POST',
 | 
				
			||||||
 | 
					                    headers: {
 | 
				
			||||||
 | 
					                        'Content-Type': 'application/json'
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                    body: JSON.stringify({ username, password })
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                const data = await response.json();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (response.ok && data.code === 0) {
 | 
				
			||||||
 | 
					                    // 登录成功
 | 
				
			||||||
 | 
					                    successMessage.textContent = data.message;
 | 
				
			||||||
 | 
					                    successMessage.style.display = 'block';
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // 保存token到localStorage
 | 
				
			||||||
 | 
					                    localStorage.setItem('authToken', data.data.token);
 | 
				
			||||||
 | 
					                    localStorage.setItem('userId', data.data.id);
 | 
				
			||||||
 | 
					                    localStorage.setItem('username', data.data.username);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    // 跳转到主页面(这里暂时只是显示成功信息)
 | 
				
			||||||
 | 
					                    setTimeout(() => {
 | 
				
			||||||
 | 
					                        alert('登录成功!将跳转到主页面。');
 | 
				
			||||||
 | 
					                        // 这里可以重定向到主页面
 | 
				
			||||||
 | 
					                        // window.location.href = '/dashboard.html';
 | 
				
			||||||
 | 
					                    }, 1000);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // 登录失败
 | 
				
			||||||
 | 
					                    errorMessage.textContent = data.message || '登录失败';
 | 
				
			||||||
 | 
					                    errorMessage.style.display = 'block';
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } catch (error) {
 | 
				
			||||||
 | 
					                errorMessage.textContent = '网络错误,请稍后重试';
 | 
				
			||||||
 | 
					                errorMessage.style.display = 'block';
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
 | 
					                // 恢复按钮状态
 | 
				
			||||||
 | 
					                loginBtn.disabled = false;
 | 
				
			||||||
 | 
					                loading.style.display = 'none';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@@ -7,6 +7,7 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/api/middleware"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/api/middleware"
 | 
				
			||||||
@@ -63,7 +64,7 @@ type API struct {
 | 
				
			|||||||
// 初始化Gin引擎和相关配置
 | 
					// 初始化Gin引擎和相关配置
 | 
				
			||||||
func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRepo repository.OperationHistoryRepo, deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService) *API {
 | 
					func NewAPI(cfg *config.Config, userRepo repository.UserRepo, operationHistoryRepo repository.OperationHistoryRepo, deviceControlRepo repository.DeviceControlRepo, deviceRepo repository.DeviceRepo, websocketService *service.WebSocketService) *API {
 | 
				
			||||||
	// 设置Gin为发布模式
 | 
						// 设置Gin为发布模式
 | 
				
			||||||
	gin.SetMode(gin.ReleaseMode)
 | 
						gin.SetMode(gin.DebugMode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 创建Gin引擎实例
 | 
						// 创建Gin引擎实例
 | 
				
			||||||
	engine := gin.New()
 | 
						engine := gin.New()
 | 
				
			||||||
@@ -203,6 +204,26 @@ func (a *API) setupRoutes() {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 使用NoRoute处理器处理前端路由
 | 
				
			||||||
 | 
						a.engine.NoRoute(func(c *gin.Context) {
 | 
				
			||||||
 | 
							path := c.Request.URL.Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 判断是否为API路径
 | 
				
			||||||
 | 
							if strings.HasPrefix(path, "/api/") || strings.HasPrefix(path, "/ws/") {
 | 
				
			||||||
 | 
								// API路径返回404
 | 
				
			||||||
 | 
								c.JSON(http.StatusNotFound, gin.H{
 | 
				
			||||||
 | 
									"error": "API路径未找到",
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 其他路径提供前端静态文件服务
 | 
				
			||||||
 | 
							c.File("./frontend/index.html")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 静态文件服务 - 提供前端静态资源
 | 
				
			||||||
 | 
						a.engine.Static("/static", "./frontend/static")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: 添加更多路由
 | 
						// TODO: 添加更多路由
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,16 +65,8 @@ func (c *Controller) Register(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 对密码进行哈希处理
 | 
					 | 
				
			||||||
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		c.logger.Error("密码哈希处理失败: " + err.Error())
 | 
					 | 
				
			||||||
		controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "用户注册失败")
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 创建用户
 | 
						// 创建用户
 | 
				
			||||||
	user, err := c.userRepo.CreateUser(req.Username, string(hashedPassword))
 | 
						user, err := c.userRepo.CreateUser(req.Username, req.Password)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Error("创建用户失败: " + err.Error())
 | 
							c.logger.Error("创建用户失败: " + err.Error())
 | 
				
			||||||
		controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "用户注册失败")
 | 
							controller.SendErrorResponse(ctx, controller.InternalServerErrorCode, "用户注册失败")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,63 +52,55 @@ type Application struct {
 | 
				
			|||||||
// NewApplication 创建并返回一个新的核心应用实例
 | 
					// NewApplication 创建并返回一个新的核心应用实例
 | 
				
			||||||
// 初始化所有核心组件
 | 
					// 初始化所有核心组件
 | 
				
			||||||
func NewApplication(cfg *config.Config) *Application {
 | 
					func NewApplication(cfg *config.Config) *Application {
 | 
				
			||||||
	// 从配置中获取数据库连接字符串
 | 
					 | 
				
			||||||
	connectionString := cfg.GetDatabaseConnectionString()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 从配置中获取连接池参数
 | 
					 | 
				
			||||||
	maxOpenConns := cfg.Database.MaxOpenConns
 | 
					 | 
				
			||||||
	maxIdleConns := cfg.Database.MaxIdleConns
 | 
					 | 
				
			||||||
	connMaxLifetime := cfg.Database.ConnMaxLifetime
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化存储组件
 | 
					 | 
				
			||||||
	store := db.NewStorage(connectionString, maxOpenConns, maxIdleConns, connMaxLifetime)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化用户仓库
 | 
					 | 
				
			||||||
	userRepo := repository.NewUserRepo(store.GetDB())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化操作历史仓库
 | 
					 | 
				
			||||||
	operationHistoryRepo := repository.NewOperationHistoryRepo(store.GetDB())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化设备控制仓库
 | 
					 | 
				
			||||||
	deviceControlRepo := repository.NewDeviceControlRepo(store.GetDB())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化设备仓库
 | 
					 | 
				
			||||||
	deviceRepo := repository.NewDeviceRepo(store.GetDB())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化WebSocket服务
 | 
					 | 
				
			||||||
	websocketService := service.NewWebSocketService()
 | 
					 | 
				
			||||||
	// 设置WebSocket超时时间
 | 
					 | 
				
			||||||
	websocketService.SetDefaultTimeout(cfg.GetWebSocketTimeout())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化API组件
 | 
					 | 
				
			||||||
	apiInstance := api.NewAPI(cfg, userRepo, operationHistoryRepo, deviceControlRepo, deviceRepo, websocketService)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 初始化任务执行器组件(使用5个工作协程)
 | 
					 | 
				
			||||||
	taskExecutor := task.NewExecutor(5)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &Application{
 | 
						return &Application{
 | 
				
			||||||
		Storage:              store,
 | 
							Config: cfg,
 | 
				
			||||||
		API:                  apiInstance,
 | 
							logger: logs.NewLogger(),
 | 
				
			||||||
		TaskExecutor:         taskExecutor,
 | 
					 | 
				
			||||||
		UserRepo:             userRepo,
 | 
					 | 
				
			||||||
		OperationHistoryRepo: operationHistoryRepo,
 | 
					 | 
				
			||||||
		DeviceControlRepo:    deviceControlRepo,
 | 
					 | 
				
			||||||
		DeviceRepo:           deviceRepo,
 | 
					 | 
				
			||||||
		WebSocketService:     websocketService,
 | 
					 | 
				
			||||||
		Config:               cfg,
 | 
					 | 
				
			||||||
		logger:               logs.NewLogger(),
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Start 启动核心应用
 | 
					// Start 启动核心应用
 | 
				
			||||||
// 按正确顺序启动所有核心组件
 | 
					// 按正确顺序启动所有核心组件
 | 
				
			||||||
func (app *Application) Start() error {
 | 
					func (app *Application) Start() error {
 | 
				
			||||||
 | 
						// 从配置中获取数据库连接字符串
 | 
				
			||||||
 | 
						connectionString := app.Config.GetDatabaseConnectionString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 从配置中获取连接池参数
 | 
				
			||||||
 | 
						maxOpenConns := app.Config.Database.MaxOpenConns
 | 
				
			||||||
 | 
						maxIdleConns := app.Config.Database.MaxIdleConns
 | 
				
			||||||
 | 
						connMaxLifetime := app.Config.Database.ConnMaxLifetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化存储组件
 | 
				
			||||||
 | 
						app.Storage = db.NewStorage(connectionString, maxOpenConns, maxIdleConns, connMaxLifetime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 启动存储组件
 | 
						// 启动存储组件
 | 
				
			||||||
	if err := app.Storage.Connect(); err != nil {
 | 
						if err := app.Storage.Connect(); err != nil {
 | 
				
			||||||
		return fmt.Errorf("存储连接失败: %v", err)
 | 
							return fmt.Errorf("存储连接失败: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	app.logger.Info("存储连接成功")
 | 
						app.logger.Info("存储连接成功")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化用户仓库
 | 
				
			||||||
 | 
						app.UserRepo = repository.NewUserRepo(app.Storage.GetDB())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化操作历史仓库
 | 
				
			||||||
 | 
						app.OperationHistoryRepo = repository.NewOperationHistoryRepo(app.Storage.GetDB())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化设备控制仓库
 | 
				
			||||||
 | 
						app.DeviceControlRepo = repository.NewDeviceControlRepo(app.Storage.GetDB())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化设备仓库
 | 
				
			||||||
 | 
						app.DeviceRepo = repository.NewDeviceRepo(app.Storage.GetDB())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化WebSocket服务
 | 
				
			||||||
 | 
						app.WebSocketService = service.NewWebSocketService()
 | 
				
			||||||
 | 
						// 设置WebSocket超时时间
 | 
				
			||||||
 | 
						app.WebSocketService.SetDefaultTimeout(app.Config.GetWebSocketTimeout())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化API组件
 | 
				
			||||||
 | 
						app.API = api.NewAPI(app.Config, app.UserRepo, app.OperationHistoryRepo, app.DeviceControlRepo, app.DeviceRepo, app.WebSocketService)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 初始化任务执行器组件(使用5个工作协程)
 | 
				
			||||||
 | 
						app.TaskExecutor = task.NewExecutor(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 启动API组件
 | 
						// 启动API组件
 | 
				
			||||||
	if err := app.API.Start(); err != nil {
 | 
						if err := app.API.Start(); err != nil {
 | 
				
			||||||
		return fmt.Errorf("API启动失败: %v", err)
 | 
							return fmt.Errorf("API启动失败: %v", err)
 | 
				
			||||||
@@ -116,8 +108,8 @@ func (app *Application) Start() error {
 | 
				
			|||||||
	app.logger.Info("API启动成功")
 | 
						app.logger.Info("API启动成功")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 启动任务执行器组件
 | 
						// 启动任务执行器组件
 | 
				
			||||||
 | 
						app.logger.Info("启动任务执行器")
 | 
				
			||||||
	app.TaskExecutor.Start()
 | 
						app.TaskExecutor.Start()
 | 
				
			||||||
	app.logger.Info("任务执行器启动成功")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -131,8 +123,8 @@ func (app *Application) Stop() error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 停止任务执行器组件
 | 
						// 停止任务执行器组件
 | 
				
			||||||
 | 
						app.logger.Info("停止任务执行器")
 | 
				
			||||||
	app.TaskExecutor.Stop()
 | 
						app.TaskExecutor.Stop()
 | 
				
			||||||
	app.logger.Info("任务执行器已停止")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 停止存储组件
 | 
						// 停止存储组件
 | 
				
			||||||
	if err := app.Storage.Disconnect(); err != nil {
 | 
						if err := app.Storage.Disconnect(); err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,5 +24,6 @@ type Storage interface {
 | 
				
			|||||||
// 根据配置返回相应的存储实现
 | 
					// 根据配置返回相应的存储实现
 | 
				
			||||||
func NewStorage(connectionString string, maxOpenConns, maxIdleConns, connMaxLifetime int) Storage {
 | 
					func NewStorage(connectionString string, maxOpenConns, maxIdleConns, connMaxLifetime int) Storage {
 | 
				
			||||||
	// 当前默认返回PostgreSQL存储实现
 | 
						// 当前默认返回PostgreSQL存储实现
 | 
				
			||||||
	return NewPostgresStorage(connectionString, maxOpenConns, maxIdleConns, connMaxLifetime)
 | 
						s := NewPostgresStorage(connectionString, maxOpenConns, maxIdleConns, connMaxLifetime)
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user