实现修改密码和注销功能

This commit is contained in:
Alex Yang
2025-11-30 11:52:41 +08:00
parent 72aa2846e5
commit dadfd4c78d
4 changed files with 304 additions and 44 deletions

View File

@@ -82,6 +82,10 @@ func (s *Server) Start() error {
if s.config.EnableAPI { if s.config.EnableAPI {
// 登录API端点不需要认证 // 登录API端点不需要认证
mux.HandleFunc("/api/login", s.handleLogin) 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页面 // 重定向/api到Swagger UI页面
mux.HandleFunc("/api", s.loginRequired(func(w http.ResponseWriter, r *http.Request) { 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": "登录成功"}) json.NewEncoder(w).Encode(map[string]string{"status": "success", "message": "登录成功"})
logger.Info(fmt.Sprintf("用户 %s 登录成功", loginData.Username)) 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("密码修改成功")
}

View File

@@ -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="日志系统已关闭"

View File

@@ -149,9 +149,22 @@
<button class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100"> <button class="p-2 text-gray-500 hover:text-gray-700 rounded-full hover:bg-gray-100">
<i class="fa fa-bell text-lg"></i> <i class="fa fa-bell text-lg"></i>
</button> </button>
<div class="flex items-center"> <!-- 账户下拉菜单 -->
<img src="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-8 h-8 rounded-full"> <div class="relative group" id="account-dropdown">
<span class="ml-2 hidden md:block">管理员</span> <button class="flex items-center p-2 rounded-full hover:bg-gray-100 transition-colors focus:outline-none">
<img src="https://picsum.photos/id/1005/40/40" alt="用户头像" class="w-8 h-8 rounded-full">
<span class="ml-2 hidden md:block">管理员</span>
<i class="fa fa-caret-down ml-1 text-xs hidden md:block"></i>
</button>
<!-- 下拉菜单 -->
<div class="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg py-2 z-50 hidden group-hover:block" id="account-menu">
<button id="change-password-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
<i class="fa fa-key mr-2"></i>修改密码
</button>
<button id="logout-btn" class="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors">
<i class="fa fa-sign-out mr-2"></i>注销
</button>
</div>
</div> </div>
</div> </div>
</header> </header>
@@ -1047,6 +1060,41 @@
</main> </main>
</div> </div>
<!-- 修改密码模态框 -->
<div id="change-password-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md p-6">
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-semibold">修改密码</h3>
<button id="close-modal-btn" class="text-gray-500 hover:text-gray-700 focus:outline-none">
<i class="fa fa-times text-xl"></i>
</button>
</div>
<form id="change-password-form">
<div class="mb-4">
<label for="current-password" class="block text-sm font-medium text-gray-700 mb-1">当前密码</label>
<input type="password" id="current-password" name="currentPassword" placeholder="请输入当前密码" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" required>
</div>
<div class="mb-4">
<label for="new-password" class="block text-sm font-medium text-gray-700 mb-1">新密码</label>
<input type="password" id="new-password" name="newPassword" placeholder="请输入新密码" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" required>
</div>
<div class="mb-6">
<label for="confirm-password" class="block text-sm font-medium text-gray-700 mb-1">确认密码</label>
<input type="password" id="confirm-password" name="confirmPassword" placeholder="请再次输入新密码" class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" required>
<div id="password-mismatch" class="text-danger text-sm mt-1 hidden">新密码和确认密码不匹配</div>
</div>
<div class="flex items-center justify-end space-x-3">
<button type="button" id="cancel-change-password" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition-colors">取消</button>
<button type="submit" id="save-password-btn" class="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90 transition-colors">保存</button>
</div>
</form>
</div>
</div>
<!-- 脚本 --> <!-- 脚本 -->
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script src="js/api.js"></script> <script src="js/api.js"></script>

View File

@@ -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); window.addEventListener('DOMContentLoaded', init);