mirror of
				https://github.com/bjdgyc/anylink.git
				synced 2025-11-04 11:06:22 +08:00 
			
		
		
		
	防爆增加前端手动解锁的功能
This commit is contained in:
		@@ -97,7 +97,25 @@ func UnlockUser(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	lm.mu.Lock()
 | 
						lm.mu.Lock()
 | 
				
			||||||
	defer lm.mu.Unlock()
 | 
						defer lm.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	lm.Unlock(lockinfo.State)
 | 
						// 根据用户名和IP查找锁定状态
 | 
				
			||||||
 | 
						var state *LockState
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case lockinfo.IP == "" && lockinfo.Username != "":
 | 
				
			||||||
 | 
							state = lm.userLocks[lockinfo.Username] // 全局用户锁定
 | 
				
			||||||
 | 
						case lockinfo.Username != "" && lockinfo.IP != "":
 | 
				
			||||||
 | 
							if userIPMap, exists := lm.ipUserLocks[lockinfo.Username]; exists {
 | 
				
			||||||
 | 
								state = userIPMap[lockinfo.IP] // 单用户 IP 锁定
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							state = lm.ipLocks[lockinfo.IP] // 全局 IP 锁定
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if state == nil || !state.Locked {
 | 
				
			||||||
 | 
							RespError(w, RespInternalErr, fmt.Errorf("锁定状态未找到或已解锁"))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lm.Unlock(state)
 | 
				
			||||||
	base.Info("解锁成功:", lockinfo.Description, lockinfo.Username, lockinfo.IP)
 | 
						base.Info("解锁成功:", lockinfo.Description, lockinfo.Username, lockinfo.IP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	RespSucess(w, "解锁成功!")
 | 
						RespSucess(w, "解锁成功!")
 | 
				
			||||||
@@ -112,7 +130,7 @@ func (lm *LockManager) GetLocksInfo() []LockInfo {
 | 
				
			|||||||
	for ip, state := range lm.ipLocks {
 | 
						for ip, state := range lm.ipLocks {
 | 
				
			||||||
		if state.Locked {
 | 
							if state.Locked {
 | 
				
			||||||
			info := LockInfo{
 | 
								info := LockInfo{
 | 
				
			||||||
				Description: "全局 IP 锁定",
 | 
									Description: "全局IP锁定",
 | 
				
			||||||
				Username:    "",
 | 
									Username:    "",
 | 
				
			||||||
				IP:          ip,
 | 
									IP:          ip,
 | 
				
			||||||
				State: &LockState{
 | 
									State: &LockState{
 | 
				
			||||||
@@ -147,7 +165,7 @@ func (lm *LockManager) GetLocksInfo() []LockInfo {
 | 
				
			|||||||
		for ip, state := range ipStates {
 | 
							for ip, state := range ipStates {
 | 
				
			||||||
			if state.Locked {
 | 
								if state.Locked {
 | 
				
			||||||
				info := LockInfo{
 | 
									info := LockInfo{
 | 
				
			||||||
					Description: "单用户 IP 锁定",
 | 
										Description: "单用户IP锁定",
 | 
				
			||||||
					Username:    username,
 | 
										Username:    username,
 | 
				
			||||||
					IP:          ip,
 | 
										IP:          ip,
 | 
				
			||||||
					State: &LockState{
 | 
										State: &LockState{
 | 
				
			||||||
@@ -205,7 +223,7 @@ func (lm *LockManager) IsWhitelisted(ip string) bool {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (lm *LockManager) StartCleanupTicker() {
 | 
					func (lm *LockManager) StartCleanupTicker() {
 | 
				
			||||||
	lm.cleanupTicker = time.NewTicker(5 * time.Minute)
 | 
						lm.cleanupTicker = time.NewTicker(1 * time.Minute)
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
		for range lm.cleanupTicker.C {
 | 
							for range lm.cleanupTicker.C {
 | 
				
			||||||
			lm.CleanupExpiredLocks()
 | 
								lm.CleanupExpiredLocks()
 | 
				
			||||||
@@ -220,20 +238,23 @@ func (lm *LockManager) CleanupExpiredLocks() {
 | 
				
			|||||||
	defer lm.mu.Unlock()
 | 
						defer lm.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for ip, state := range lm.ipLocks {
 | 
						for ip, state := range lm.ipLocks {
 | 
				
			||||||
		if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
 | 
							if !lm.CheckLockState(state, now, base.Cfg.GlobalIPLockTime) ||
 | 
				
			||||||
 | 
								now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
 | 
				
			||||||
			delete(lm.ipLocks, ip)
 | 
								delete(lm.ipLocks, ip)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for user, state := range lm.userLocks {
 | 
						for user, state := range lm.userLocks {
 | 
				
			||||||
		if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
 | 
							if !lm.CheckLockState(state, now, base.Cfg.GlobalUserLockTime) ||
 | 
				
			||||||
 | 
								now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
 | 
				
			||||||
			delete(lm.userLocks, user)
 | 
								delete(lm.userLocks, user)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for user, ipMap := range lm.ipUserLocks {
 | 
						for user, ipMap := range lm.ipUserLocks {
 | 
				
			||||||
		for ip, state := range ipMap {
 | 
							for ip, state := range ipMap {
 | 
				
			||||||
			if now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
 | 
								if !lm.CheckLockState(state, now, base.Cfg.LockTime) ||
 | 
				
			||||||
 | 
									now.Sub(state.LastAttempt) > time.Duration(base.Cfg.GlobalLockStateExpirationTime)*time.Second {
 | 
				
			||||||
				delete(ipMap, ip)
 | 
									delete(ipMap, ip)
 | 
				
			||||||
				if len(ipMap) == 0 {
 | 
									if len(ipMap) == 0 {
 | 
				
			||||||
					delete(lm.ipUserLocks, user)
 | 
										delete(lm.ipUserLocks, user)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,6 +88,7 @@ func StartAdmin() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	r.HandleFunc("/statsinfo/list", StatsInfoList)
 | 
						r.HandleFunc("/statsinfo/list", StatsInfoList)
 | 
				
			||||||
	r.HandleFunc("/locksinfo/list", GetLocksInfo)
 | 
						r.HandleFunc("/locksinfo/list", GetLocksInfo)
 | 
				
			||||||
 | 
						r.HandleFunc("/locksinfo/unlok", UnlockUser)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// pprof
 | 
						// pprof
 | 
				
			||||||
	if base.Cfg.Pprof {
 | 
						if base.Cfg.Pprof {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,17 +7,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  <!--<div class="layout-aside" :style="aside_style">-->
 | 
					  <!--<div class="layout-aside" :style="aside_style">-->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <el-menu :collapse="!is_active"
 | 
					  <el-menu :collapse="!is_active" :default-active="route_path" :style="is_active ? 'width:200px' : ''" router
 | 
				
			||||||
           :default-active="route_path"
 | 
					    class="layout-menu" :collapse-transition="false" background-color="#545c64" text-color="#fff"
 | 
				
			||||||
           :style="is_active?'width:200px':''"
 | 
					    active-text-color="#ffd04b">
 | 
				
			||||||
           router
 | 
					 | 
				
			||||||
           class="layout-menu"
 | 
					 | 
				
			||||||
           :collapse-transition="false"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
           background-color="#545c64"
 | 
					 | 
				
			||||||
           text-color="#fff"
 | 
					 | 
				
			||||||
           active-text-color="#ffd04b"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    <el-menu-item index="/admin/home">
 | 
					    <el-menu-item index="/admin/home">
 | 
				
			||||||
      <i class="el-icon-s-home"></i>
 | 
					      <i class="el-icon-s-home"></i>
 | 
				
			||||||
      <span slot="title">首页</span>
 | 
					      <span slot="title">首页</span>
 | 
				
			||||||
@@ -44,6 +36,7 @@
 | 
				
			|||||||
      <el-menu-item index="/admin/user/list">用户列表</el-menu-item>
 | 
					      <el-menu-item index="/admin/user/list">用户列表</el-menu-item>
 | 
				
			||||||
      <el-menu-item index="/admin/user/policy">用户策略</el-menu-item>
 | 
					      <el-menu-item index="/admin/user/policy">用户策略</el-menu-item>
 | 
				
			||||||
      <el-menu-item index="/admin/user/online">在线用户</el-menu-item>
 | 
					      <el-menu-item index="/admin/user/online">在线用户</el-menu-item>
 | 
				
			||||||
 | 
					      <el-menu-item index="/admin/user/lockmanager">锁定管理</el-menu-item>
 | 
				
			||||||
      <el-menu-item index="/admin/user/ip_map">IP映射</el-menu-item>
 | 
					      <el-menu-item index="/admin/user/ip_map">IP映射</el-menu-item>
 | 
				
			||||||
    </el-submenu>
 | 
					    </el-submenu>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										109
									
								
								web/src/pages/user/LockManager.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								web/src/pages/user/LockManager.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div id="lock-manager">
 | 
				
			||||||
 | 
					        <el-card>
 | 
				
			||||||
 | 
					            <div slot="header">
 | 
				
			||||||
 | 
					                <el-button type="primary" @click="getLocks">刷新信息</el-button>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            <el-table :data="locksInfo" style="width: 100%" border>
 | 
				
			||||||
 | 
					                <el-table-column type="index" label="序号" width="60"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="description" label="描述"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="username" label="用户名"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="ip" label="IP地址"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="state.locked" label="状态" width="100">
 | 
				
			||||||
 | 
					                    <template slot-scope="scope">
 | 
				
			||||||
 | 
					                        <el-tag :type="scope.row.state.locked ? 'danger' : 'success'">
 | 
				
			||||||
 | 
					                            {{ scope.row.state.locked ? '已锁定' : '未锁定' }}
 | 
				
			||||||
 | 
					                        </el-tag>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="state.attempts" label="失败次数"></el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="state.lock_time" label="锁定截止时间">
 | 
				
			||||||
 | 
					                    <template slot-scope="scope">
 | 
				
			||||||
 | 
					                        {{ formatDate(scope.row.state.lock_time) }}
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column prop="state.lastAttempt" label="最后尝试时间">
 | 
				
			||||||
 | 
					                    <template slot-scope="scope">
 | 
				
			||||||
 | 
					                        {{ formatDate(scope.row.state.lastAttempt) }}
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					                <el-table-column label="操作">
 | 
				
			||||||
 | 
					                    <template slot-scope="scope">
 | 
				
			||||||
 | 
					                        <div class="button">
 | 
				
			||||||
 | 
					                            <el-button size="small" type="danger" @click="unlock(scope.row)">
 | 
				
			||||||
 | 
					                                解锁
 | 
				
			||||||
 | 
					                            </el-button>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </template>
 | 
				
			||||||
 | 
					                </el-table-column>
 | 
				
			||||||
 | 
					            </el-table>
 | 
				
			||||||
 | 
					        </el-card>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import axios from 'axios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					    name: 'LockManager',
 | 
				
			||||||
 | 
					    data() {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            locksInfo: []
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    methods: {
 | 
				
			||||||
 | 
					        getLocks() {
 | 
				
			||||||
 | 
					            axios.get('/locksinfo/list')
 | 
				
			||||||
 | 
					                .then(response => {
 | 
				
			||||||
 | 
					                    this.locksInfo = response.data.data;
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .catch(error => {
 | 
				
			||||||
 | 
					                    console.error('Failed to get locks info:', error);
 | 
				
			||||||
 | 
					                    this.$message.error('无法获取锁信息,请稍后再试。');
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        unlock(lock) {
 | 
				
			||||||
 | 
					            const lockInfo = {
 | 
				
			||||||
 | 
					                state: { locked: false },
 | 
				
			||||||
 | 
					                username: lock.username,
 | 
				
			||||||
 | 
					                ip: lock.ip,
 | 
				
			||||||
 | 
					                description: lock.description
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            axios.post('/locksinfo/unlok', lockInfo)
 | 
				
			||||||
 | 
					                .then(() => {
 | 
				
			||||||
 | 
					                    this.$message.success('解锁成功!');
 | 
				
			||||||
 | 
					                    this.getLocks();
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .catch(error => {
 | 
				
			||||||
 | 
					                    console.error('Failed to unlock:', error);
 | 
				
			||||||
 | 
					                    this.$message.error('解锁失败,请稍后再试。');
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        formatDate(dateString) {
 | 
				
			||||||
 | 
					            if (!dateString) return '';
 | 
				
			||||||
 | 
					            const date = new Date(dateString);
 | 
				
			||||||
 | 
					            return new Intl.DateTimeFormat('zh-CN', {
 | 
				
			||||||
 | 
					                year: 'numeric',
 | 
				
			||||||
 | 
					                month: '2-digit',
 | 
				
			||||||
 | 
					                day: '2-digit',
 | 
				
			||||||
 | 
					                hour: '2-digit',
 | 
				
			||||||
 | 
					                minute: '2-digit',
 | 
				
			||||||
 | 
					                second: '2-digit',
 | 
				
			||||||
 | 
					                hour12: false
 | 
				
			||||||
 | 
					            }).format(date);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    created() {
 | 
				
			||||||
 | 
					        this.getLocks();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					.button {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -1,35 +1,36 @@
 | 
				
			|||||||
import Vue from "vue";
 | 
					import Vue from "vue";
 | 
				
			||||||
import VueRouter from "vue-router";
 | 
					import VueRouter from "vue-router";
 | 
				
			||||||
import {getToken} from "./token";
 | 
					import { getToken } from "./token";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Vue.use(VueRouter)
 | 
					Vue.use(VueRouter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const routes = [
 | 
					const routes = [
 | 
				
			||||||
    {path: '/login', component: () => import('@/pages/Login')},
 | 
					    { path: '/login', component: () => import('@/pages/Login') },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        path: '/admin',
 | 
					        path: '/admin',
 | 
				
			||||||
        component: () => import('@/layout/Layout'),
 | 
					        component: () => import('@/layout/Layout'),
 | 
				
			||||||
        redirect: '/admin/home',
 | 
					        redirect: '/admin/home',
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
            {path: 'home', component: () => import('@/pages/Home')},
 | 
					            { path: 'home', component: () => import('@/pages/Home') },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {path: 'set/system', component: () => import('@/pages/set/System')},
 | 
					            { path: 'set/system', component: () => import('@/pages/set/System') },
 | 
				
			||||||
            {path: 'set/soft', component: () => import('@/pages/set/Soft')},
 | 
					            { path: 'set/soft', component: () => import('@/pages/set/Soft') },
 | 
				
			||||||
            {path: 'set/other', component: () => import('@/pages/set/Other')},
 | 
					            { path: 'set/other', component: () => import('@/pages/set/Other') },
 | 
				
			||||||
            {path: 'set/audit', component: () => import('@/pages/set/Audit')},
 | 
					            { path: 'set/audit', component: () => import('@/pages/set/Audit') },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {path: 'user/list', component: () => import('@/pages/user/List')},
 | 
					            { path: 'user/list', component: () => import('@/pages/user/List') },
 | 
				
			||||||
            {path: 'user/policy', component: () => import('@/pages/user/Policy')},
 | 
					            { path: 'user/policy', component: () => import('@/pages/user/Policy') },
 | 
				
			||||||
            {path: 'user/online', component: () => import('@/pages/user/Online')},
 | 
					            { path: 'user/online', component: () => import('@/pages/user/Online') },
 | 
				
			||||||
            {path: 'user/ip_map', component: () => import('@/pages/user/IpMap')},
 | 
					            { path: 'user/ip_map', component: () => import('@/pages/user/IpMap') },
 | 
				
			||||||
 | 
					            { path: 'user/lockmanager', component: () => import('@/pages/user/LockManager') },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {path: 'group/list', component: () => import('@/pages/group/List')},
 | 
					            { path: 'group/list', component: () => import('@/pages/group/List') },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {path: '*', redirect: '/admin/home'},
 | 
					    { path: '*', redirect: '/admin/home' },
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// 3. 创建 router 实例,然后传 `routes` 配置
 | 
					// 3. 创建 router 实例,然后传 `routes` 配置
 | 
				
			||||||
@@ -64,7 +65,7 @@ router.beforeEach((to, from, next) => {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (to.path === "/login") {
 | 
					    if (to.path === "/login") {
 | 
				
			||||||
        next({path: '/admin/home'});
 | 
					        next({ path: '/admin/home' });
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user