mirror of https://github.com/bjdgyc/anylink.git
防爆增加前端手动解锁的功能
This commit is contained in:
parent
55d7300033
commit
cad74d7fdb
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue