From dadfd4c78df70c4539e8f8743c6c1b7a63fdc1de Mon Sep 17 00:00:00 2001 From: Alex Yang Date: Sun, 30 Nov 2025 11:52:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BF=AE=E6=94=B9=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=92=8C=E6=B3=A8=E9=94=80=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http/server.go | 83 ++++++++++++++++++++++ server.log | 41 ----------- static/index.html | 54 ++++++++++++++- static/js/main.js | 170 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 304 insertions(+), 44 deletions(-) delete mode 100644 server.log diff --git a/http/server.go b/http/server.go index 879a37e..13a5748 100644 --- a/http/server.go +++ b/http/server.go @@ -82,6 +82,10 @@ func (s *Server) Start() error { if s.config.EnableAPI { // 登录API端点,不需要认证 mux.HandleFunc("/api/login", s.handleLogin) + // 注销API端点,不需要认证 + mux.HandleFunc("/api/logout", s.handleLogout) + // 修改密码API端点,需要认证 + mux.HandleFunc("/api/change-password", s.loginRequired(s.handleChangePassword)) // 重定向/api到Swagger UI页面 mux.HandleFunc("/api", s.loginRequired(func(w http.ResponseWriter, r *http.Request) { @@ -1468,3 +1472,82 @@ func (s *Server) handleLogin(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "登录成功"}) logger.Info(fmt.Sprintf("用户 %s 登录成功", loginData.Username)) } + +// handleLogout 处理注销请求 +func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // 从Cookie中获取会话ID + cookie, err := r.Cookie("session_id") + if err == nil { + // 删除会话 + s.sessionsMutex.Lock() + delete(s.sessions, cookie.Value) + s.sessionsMutex.Unlock() + } + + // 清除Cookie + clearCookie := &http.Cookie{ + Name: "session_id", + Value: "", + Path: "/", + Expires: time.Unix(0, 0), + HttpOnly: true, + Secure: false, + } + http.SetCookie(w, clearCookie) + + // 返回成功响应 + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "注销成功"}) + logger.Info("用户注销成功") +} + +// handleChangePassword 处理修改密码请求 +func (s *Server) handleChangePassword(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + // 解析请求体 + var changePasswordData struct { + CurrentPassword string `json:"currentPassword"` + NewPassword string `json:"newPassword"` + } + + if err := json.NewDecoder(r.Body).Decode(&changePasswordData); err != nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(map[string]string{"error": "无效的请求体"}) + return + } + + // 验证当前密码 + if changePasswordData.CurrentPassword != s.config.Password { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(map[string]string{"error": "当前密码错误"}) + return + } + + // 更新密码 + s.config.Password = changePasswordData.NewPassword + + // 保存配置到文件 + if err := saveConfigToFile(s.globalConfig, "./config.json"); err != nil { + logger.Error("保存配置文件失败", "error", err) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(map[string]string{"error": "保存密码失败"}) + return + } + + // 返回成功响应 + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "密码修改成功"}) + logger.Info("密码修改成功") +} diff --git a/server.log b/server.log deleted file mode 100644 index 64307d1..0000000 --- a/server.log +++ /dev/null @@ -1,41 +0,0 @@ -2025/11/30 11:09:05 正在创建所需的文件和文件夹... -2025/11/30 11:09:05 所需文件和文件夹创建成功 -time="2025-11-30T11:09:05+08:00" level=debug msg="尝试加载Shield统计数据" file=/root/dnsbak/data/shield_stats.json -time="2025-11-30T11:09:05+08:00" level=info msg="Shield计数数据加载成功" blocked_entries=0 resolved_entries=0 -time="2025-11-30T11:09:05+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/filter.txt" -time="2025-11-30T11:09:05+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/adaway.txt" -time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/list/easylist.txt" -time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/costomize.txt" -time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/dsjh.txt" -time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hate-and-junk-extended.txt" -time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/costomize.txt" -time="2025-11-30T11:09:06+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/hosts/anti-remoterequests.txt" -time="2025-11-30T11:09:07+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/url-based-adguard.txt" -time="2025-11-30T11:09:07+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/ads-and-trackers.txt" -time="2025-11-30T11:09:08+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/malware.txt" -time="2025-11-30T11:09:09+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/hosts/costomize.txt" -time="2025-11-30T11:09:09+08:00" level=info msg="从缓存加载远程规则" url="http://gitea.amazehome.xyz/AMAZEHOME/hosts-and-Filters/raw/branch/main/rules/AWAvenue-Ads-Rule.txt" -time="2025-11-30T11:09:09+08:00" level=info msg="从缓存加载远程规则" url="https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/rules/cheat.txt" -time="2025-11-30T11:09:10+08:00" level=info msg="规则加载完成,域名规则: 189895, 排除规则: 653, 正则规则: 24094, hosts规则: 0" -time="2025-11-30T11:09:10+08:00" level=info msg="统计数据加载成功" -time="2025-11-30T11:09:10+08:00" level=info msg="查询日志加载成功" count=8608 -time="2025-11-30T11:09:10+08:00" level=info msg="DNS服务器已启动,监听端口: 5353" -time="2025-11-30T11:09:10+08:00" level=info msg="HTTP控制台已启动,监听端口: 8081" -time="2025-11-30T11:09:10+08:00" level=info msg="DNS TCP服务器启动,监听端口: 5353" -time="2025-11-30T11:09:10+08:00" level=info msg="启动Shield计数数据自动保存功能" file=./data/shield_stats.json interval=60 -time="2025-11-30T11:09:10+08:00" level=info msg="HTTP控制台服务器启动,监听地址: 0.0.0.0:8081" -time="2025-11-30T11:09:10+08:00" level=info msg="规则自动更新已启动" interval=3600 -time="2025-11-30T11:09:10+08:00" level=info msg="DNS UDP服务器启动,监听端口: 5353" -time="2025-11-30T11:09:10+08:00" level=info msg="启动统计数据自动保存功能" file=data/stats.json interval=300 -time="2025-11-30T11:09:10+08:00" level=error msg="DNS UDP服务器启动失败" error="listen udp :5353: bind: address already in use" -time="2025-11-30T11:09:10+08:00" level=info msg="Shield计数数据保存成功" blocked_entries=0 file=/root/dnsbak/data/shield_stats.json resolved_entries=0 -2025/11/30 11:09:18 正在关闭服务... -time="2025-11-30T11:09:18+08:00" level=info msg="统计数据保存成功" file=/root/dnsbak/data/stats.json -time="2025-11-30T11:09:18+08:00" level=info msg="查询日志保存成功" file=/root/dnsbak/data/querylog.json -time="2025-11-30T11:09:18+08:00" level=info msg="DNS服务器已停止" -time="2025-11-30T11:09:18+08:00" level=error msg="HTTP控制台服务器启动失败" error="http: Server closed" -time="2025-11-30T11:09:18+08:00" level=info msg="HTTP控制台服务器已停止" -time="2025-11-30T11:09:18+08:00" level=info msg="Shield计数数据保存成功" blocked_entries=0 file=/root/dnsbak/data/shield_stats.json resolved_entries=0 -time="2025-11-30T11:09:18+08:00" level=info msg="规则自动更新已停止" -2025/11/30 11:09:18 服务已关闭 -time="2025-11-30T11:09:18+08:00" level=warning msg="日志系统已关闭" diff --git a/static/index.html b/static/index.html index 0219a95..10cb62b 100644 --- a/static/index.html +++ b/static/index.html @@ -149,9 +149,22 @@ -
- 用户头像 - + +
+ + +
@@ -1047,6 +1060,41 @@ + + + diff --git a/static/js/main.js b/static/js/main.js index 81fa2b8..fa37d84 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -231,5 +231,175 @@ function formatUptime(milliseconds) { } } +// 账户功能 - 下拉菜单、注销和修改密码 +function setupAccountFeatures() { + // 下拉菜单功能 + const accountDropdown = document.getElementById('account-dropdown'); + const accountMenu = document.getElementById('account-menu'); + const changePasswordBtn = document.getElementById('change-password-btn'); + const logoutBtn = document.getElementById('logout-btn'); + const changePasswordModal = document.getElementById('change-password-modal'); + const closeModalBtn = document.getElementById('close-modal-btn'); + const cancelChangePasswordBtn = document.getElementById('cancel-change-password'); + const changePasswordForm = document.getElementById('change-password-form'); + const passwordMismatch = document.getElementById('password-mismatch'); + const newPassword = document.getElementById('new-password'); + const confirmPassword = document.getElementById('confirm-password'); + + // 点击外部关闭下拉菜单 + document.addEventListener('click', (e) => { + if (accountDropdown && !accountDropdown.contains(e.target)) { + accountMenu.classList.add('hidden'); + } + }); + + // 点击账户区域切换下拉菜单 + if (accountDropdown) { + accountDropdown.addEventListener('click', (e) => { + e.stopPropagation(); + accountMenu.classList.toggle('hidden'); + }); + } + + // 打开修改密码模态框 + if (changePasswordBtn) { + changePasswordBtn.addEventListener('click', () => { + accountMenu.classList.add('hidden'); + changePasswordModal.classList.remove('hidden'); + document.body.style.overflow = 'hidden'; + }); + } + + // 关闭修改密码模态框 + function closeModal() { + changePasswordModal.classList.add('hidden'); + document.body.style.overflow = ''; + changePasswordForm.reset(); + passwordMismatch.classList.add('hidden'); + } + + // 绑定关闭模态框事件 + if (closeModalBtn) { + closeModalBtn.addEventListener('click', closeModal); + } + + if (cancelChangePasswordBtn) { + cancelChangePasswordBtn.addEventListener('click', closeModal); + } + + // 点击模态框外部关闭模态框 + if (changePasswordModal) { + changePasswordModal.addEventListener('click', (e) => { + if (e.target === changePasswordModal) { + closeModal(); + } + }); + } + + // 按ESC键关闭模态框 + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && !changePasswordModal.classList.contains('hidden')) { + closeModal(); + } + }); + + // 密码匹配验证 + if (newPassword && confirmPassword) { + confirmPassword.addEventListener('input', () => { + if (newPassword.value !== confirmPassword.value) { + passwordMismatch.classList.remove('hidden'); + } else { + passwordMismatch.classList.add('hidden'); + } + }); + + newPassword.addEventListener('input', () => { + if (newPassword.value !== confirmPassword.value) { + passwordMismatch.classList.remove('hidden'); + } else { + passwordMismatch.classList.add('hidden'); + } + }); + } + + // 修改密码表单提交 + if (changePasswordForm) { + changePasswordForm.addEventListener('submit', async (e) => { + e.preventDefault(); + + // 验证密码匹配 + if (newPassword.value !== confirmPassword.value) { + passwordMismatch.classList.remove('hidden'); + return; + } + + const formData = new FormData(changePasswordForm); + const data = { + currentPassword: formData.get('currentPassword'), + newPassword: formData.get('newPassword') + }; + + try { + const response = await fetch('/api/change-password', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + + const result = await response.json(); + + if (response.ok && result.status === 'success') { + // 密码修改成功 + alert('密码修改成功'); + closeModal(); + } else { + // 密码修改失败 + alert(result.error || '密码修改失败'); + } + } catch (error) { + console.error('修改密码失败:', error); + alert('修改密码失败,请稍后重试'); + } + }); + } + + // 注销功能 + if (logoutBtn) { + logoutBtn.addEventListener('click', async () => { + try { + await fetch('/api/logout', { + method: 'POST' + }); + + // 重定向到登录页面 + window.location.href = '/login'; + } catch (error) { + console.error('注销失败:', error); + alert('注销失败,请稍后重试'); + } + }); + } +} + +// 初始化函数 +function init() { + // 设置导航 + setupNavigation(); + + // 设置账户功能 + setupAccountFeatures(); + + // 初始化页面 + initPageByHash(); + + // 添加hashchange事件监听,处理浏览器前进/后退按钮 + window.addEventListener('hashchange', initPageByHash); + + // 定期更新系统状态 + setInterval(updateSystemStatus, 5000); +} + // 页面加载完成后执行初始化 window.addEventListener('DOMContentLoaded', init); \ No newline at end of file