mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-09 03:06:37 +08:00
feat: 简化用户隧道权限分配,支持多选隧道批量分配 (#8)
- 新增批量分配接口 POST /tunnel/user/batch-assign - 支持一次选择多个隧道进行分配 - 每个隧道可单独设置限速规则 - flow/num/flowResetTime/expTime 自动从用户设置继承 - 前端表单简化为隧道多选列表+限速选择 - 已分配的隧道显示'已分配'标记且不可重复选择
This commit is contained in:
@@ -0,0 +1,24 @@
|
|||||||
|
package com.admin.common.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class UserTunnelBatchAssignDto {
|
||||||
|
|
||||||
|
@NotNull(message = "用户ID不能为空")
|
||||||
|
private Integer userId;
|
||||||
|
|
||||||
|
@NotEmpty(message = "隧道列表不能为空")
|
||||||
|
private List<TunnelAssignItem> tunnels;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class TunnelAssignItem {
|
||||||
|
@NotNull(message = "隧道ID不能为空")
|
||||||
|
private Integer tunnelId;
|
||||||
|
|
||||||
|
private Integer speedId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,28 +13,15 @@ public class UserTunnelDto {
|
|||||||
@NotNull(message = "隧道ID不能为空")
|
@NotNull(message = "隧道ID不能为空")
|
||||||
private Integer tunnelId;
|
private Integer tunnelId;
|
||||||
|
|
||||||
@NotNull(message = "流量限制不能为空")
|
|
||||||
@Min(value = 0, message = "流量限制不能小于0")
|
@Min(value = 0, message = "流量限制不能小于0")
|
||||||
private Long flow;
|
private Long flow;
|
||||||
|
|
||||||
@NotNull(message = "转发数量不能为空")
|
|
||||||
@Min(value = 0, message = "转发数量不能小于0")
|
@Min(value = 0, message = "转发数量不能小于0")
|
||||||
private Integer num;
|
private Integer num;
|
||||||
|
|
||||||
/**
|
|
||||||
* 流量重置时间(时间戳)
|
|
||||||
*/
|
|
||||||
@NotNull(message = "流量重置时间不能为空")
|
|
||||||
private Long flowResetTime;
|
private Long flowResetTime;
|
||||||
|
|
||||||
/**
|
|
||||||
* 到期时间(时间戳)
|
|
||||||
*/
|
|
||||||
@NotNull(message = "到期时间不能为空")
|
|
||||||
private Long expTime;
|
private Long expTime;
|
||||||
|
|
||||||
/**
|
|
||||||
* 限速规则ID(可选,null表示不限速)
|
|
||||||
*/
|
|
||||||
private Integer speedId;
|
private Integer speedId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.admin.common.annotation.RequireRole;
|
|||||||
import com.admin.common.dto.TunnelDto;
|
import com.admin.common.dto.TunnelDto;
|
||||||
import com.admin.common.dto.TunnelUpdateDto;
|
import com.admin.common.dto.TunnelUpdateDto;
|
||||||
|
|
||||||
|
import com.admin.common.dto.UserTunnelBatchAssignDto;
|
||||||
import com.admin.common.dto.UserTunnelDto;
|
import com.admin.common.dto.UserTunnelDto;
|
||||||
import com.admin.common.dto.UserTunnelQueryDto;
|
import com.admin.common.dto.UserTunnelQueryDto;
|
||||||
import com.admin.common.dto.UserTunnelUpdateDto;
|
import com.admin.common.dto.UserTunnelUpdateDto;
|
||||||
@@ -78,6 +79,13 @@ public class TunnelController extends BaseController {
|
|||||||
public R assignUserTunnel(@Validated @RequestBody UserTunnelDto userTunnelDto) {
|
public R assignUserTunnel(@Validated @RequestBody UserTunnelDto userTunnelDto) {
|
||||||
return userTunnelService.assignUserTunnel(userTunnelDto);
|
return userTunnelService.assignUserTunnel(userTunnelDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@LogAnnotation
|
||||||
|
@RequireRole
|
||||||
|
@PostMapping("/user/batch-assign")
|
||||||
|
public R batchAssignUserTunnel(@Validated @RequestBody UserTunnelBatchAssignDto batchAssignDto) {
|
||||||
|
return userTunnelService.batchAssignUserTunnel(batchAssignDto);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询用户隧道权限列表
|
* 查询用户隧道权限列表
|
||||||
|
|||||||
@@ -1,49 +1,23 @@
|
|||||||
package com.admin.service;
|
package com.admin.service;
|
||||||
|
|
||||||
import com.admin.common.dto.UserTunnelDto;
|
import com.admin.common.dto.UserTunnelBatchAssignDto;
|
||||||
import com.admin.common.dto.UserTunnelQueryDto;
|
import com.admin.common.dto.UserTunnelDto;
|
||||||
import com.admin.common.dto.UserTunnelUpdateDto;
|
import com.admin.common.dto.UserTunnelQueryDto;
|
||||||
import com.admin.common.lang.R;
|
import com.admin.common.dto.UserTunnelUpdateDto;
|
||||||
import com.admin.entity.UserTunnel;
|
import com.admin.common.lang.R;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.admin.entity.UserTunnel;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
/**
|
|
||||||
* <p>
|
public interface UserTunnelService extends IService<UserTunnel> {
|
||||||
* 用户隧道权限服务类
|
|
||||||
* </p>
|
R assignUserTunnel(UserTunnelDto userTunnelDto);
|
||||||
*
|
|
||||||
* @author QAQ
|
R batchAssignUserTunnel(UserTunnelBatchAssignDto batchAssignDto);
|
||||||
* @since 2025-06-03
|
|
||||||
*/
|
R getUserTunnelList(UserTunnelQueryDto queryDto);
|
||||||
public interface UserTunnelService extends IService<UserTunnel> {
|
|
||||||
|
R removeUserTunnel(Integer id);
|
||||||
/**
|
|
||||||
* 分配用户隧道权限
|
R updateUserTunnel(UserTunnelUpdateDto updateDto);
|
||||||
* @param userTunnelDto 用户隧道权限数据
|
|
||||||
* @return 结果
|
}
|
||||||
*/
|
|
||||||
R assignUserTunnel(UserTunnelDto userTunnelDto);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询用户隧道权限列表
|
|
||||||
* @param queryDto 查询条件
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
R getUserTunnelList(UserTunnelQueryDto queryDto);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除用户隧道权限
|
|
||||||
* @param id ID
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
R removeUserTunnel(Integer id);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新用户隧道权限(包含流量、流量重置时间、到期时间)
|
|
||||||
* @param updateDto 更新数据
|
|
||||||
* @return 结果
|
|
||||||
*/
|
|
||||||
R updateUserTunnel(UserTunnelUpdateDto updateDto);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
+178
-120
@@ -1,120 +1,178 @@
|
|||||||
package com.admin.service.impl;
|
package com.admin.service.impl;
|
||||||
|
|
||||||
import com.admin.common.dto.*;
|
import com.admin.common.dto.*;
|
||||||
import com.admin.common.lang.R;
|
import com.admin.common.lang.R;
|
||||||
import com.admin.entity.UserTunnel;
|
import com.admin.entity.User;
|
||||||
import com.admin.mapper.TunnelMapper;
|
import com.admin.entity.UserTunnel;
|
||||||
import com.admin.mapper.UserTunnelMapper;
|
import com.admin.mapper.UserTunnelMapper;
|
||||||
import com.admin.service.TunnelService;
|
import com.admin.service.UserService;
|
||||||
import com.admin.service.UserTunnelService;
|
import com.admin.service.UserTunnelService;
|
||||||
import com.admin.service.ForwardService;
|
import com.admin.service.ForwardService;
|
||||||
import com.admin.service.NodeService;
|
import com.admin.entity.Forward;
|
||||||
import com.admin.common.utils.GostUtil;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.admin.entity.Forward;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.admin.entity.Tunnel;
|
import org.springframework.beans.BeanUtils;
|
||||||
import com.admin.entity.Node;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import org.springframework.stereotype.Service;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import javax.annotation.Resource;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import java.util.ArrayList;
|
||||||
import org.springframework.beans.BeanUtils;
|
import java.util.List;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import java.util.Set;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import java.util.stream.Collectors;
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
@Service
|
||||||
import javax.annotation.Resource;
|
public class UserTunnelServiceImpl extends ServiceImpl<UserTunnelMapper, UserTunnel> implements UserTunnelService {
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
@Resource
|
||||||
|
@Lazy
|
||||||
/**
|
private ForwardService forwardService;
|
||||||
* <p>
|
|
||||||
* 用户隧道权限服务实现类
|
@Resource
|
||||||
* 提供用户隧道权限的分配、查询、更新和删除功能
|
@Lazy
|
||||||
* 支持流量限制、数量限制、过期时间和限速规则的管理
|
private UserService userService;
|
||||||
* </p>
|
|
||||||
*
|
@Override
|
||||||
* @author QAQ
|
public R assignUserTunnel(UserTunnelDto userTunnelDto) {
|
||||||
* @since 2025-06-03
|
int count = this.count(new QueryWrapper<UserTunnel>()
|
||||||
*/
|
.eq("user_id", userTunnelDto.getUserId())
|
||||||
@Service
|
.eq("tunnel_id", userTunnelDto.getTunnelId()));
|
||||||
public class UserTunnelServiceImpl extends ServiceImpl<UserTunnelMapper, UserTunnel> implements UserTunnelService {
|
if (count > 0) return R.err("该用户已拥有此隧道权限");
|
||||||
|
|
||||||
|
User user = userService.getById(userTunnelDto.getUserId());
|
||||||
@Resource
|
if (user == null) return R.err("用户不存在");
|
||||||
@Lazy
|
|
||||||
private ForwardService forwardService;
|
UserTunnel userTunnel = new UserTunnel();
|
||||||
|
userTunnel.setUserId(userTunnelDto.getUserId());
|
||||||
@Override
|
userTunnel.setTunnelId(userTunnelDto.getTunnelId());
|
||||||
public R assignUserTunnel(UserTunnelDto userTunnelDto) {
|
userTunnel.setSpeedId(userTunnelDto.getSpeedId());
|
||||||
int count = this.count(new QueryWrapper<UserTunnel>().eq("user_id", userTunnelDto.getUserId()).eq("tunnel_id", userTunnelDto.getTunnelId()));
|
userTunnel.setStatus(1);
|
||||||
if (count > 0) return R.err("该用户已拥有此隧道权限");
|
userTunnel.setInFlow(0L);
|
||||||
UserTunnel userTunnel = new UserTunnel();
|
userTunnel.setOutFlow(0L);
|
||||||
BeanUtils.copyProperties(userTunnelDto, userTunnel);
|
|
||||||
userTunnel.setStatus(1);
|
userTunnel.setFlow(userTunnelDto.getFlow() != null ? userTunnelDto.getFlow() : user.getFlow());
|
||||||
this.save(userTunnel);
|
userTunnel.setNum(userTunnelDto.getNum() != null ? userTunnelDto.getNum() : user.getNum());
|
||||||
return R.ok();
|
userTunnel.setFlowResetTime(userTunnelDto.getFlowResetTime() != null ? userTunnelDto.getFlowResetTime() : user.getFlowResetTime());
|
||||||
}
|
userTunnel.setExpTime(userTunnelDto.getExpTime() != null ? userTunnelDto.getExpTime() : user.getExpTime());
|
||||||
|
|
||||||
@Override
|
this.save(userTunnel);
|
||||||
public R getUserTunnelList(UserTunnelQueryDto queryDto) {
|
return R.ok();
|
||||||
List<UserTunnelWithDetailDto> userTunnelWithDetails = this.baseMapper.getUserTunnelWithDetails(queryDto.getUserId());
|
}
|
||||||
return R.ok(userTunnelWithDetails);
|
|
||||||
}
|
@Override
|
||||||
|
public R batchAssignUserTunnel(UserTunnelBatchAssignDto batchAssignDto) {
|
||||||
@Override
|
User user = userService.getById(batchAssignDto.getUserId());
|
||||||
public R removeUserTunnel(Integer id) {
|
if (user == null) return R.err("用户不存在");
|
||||||
UserTunnel userTunnel = this.getById(id);
|
|
||||||
if (userTunnel == null) return R.err("未找到对应的用户隧道权限记录");
|
List<Integer> requestedTunnelIds = batchAssignDto.getTunnels().stream()
|
||||||
|
.map(UserTunnelBatchAssignDto.TunnelAssignItem::getTunnelId)
|
||||||
List<Forward> forwardList = forwardService.list(new QueryWrapper<Forward>().eq("user_id", userTunnel.getUserId()).eq("tunnel_id", userTunnel.getTunnelId()));
|
.collect(Collectors.toList());
|
||||||
for (Forward forward : forwardList) {
|
|
||||||
forwardService.deleteForward(forward.getId());
|
Set<Integer> existingTunnelIds = this.list(
|
||||||
}
|
new QueryWrapper<UserTunnel>()
|
||||||
this.removeById(id);
|
.eq("user_id", batchAssignDto.getUserId())
|
||||||
return R.ok();
|
.in("tunnel_id", requestedTunnelIds)
|
||||||
}
|
).stream().map(UserTunnel::getTunnelId).collect(Collectors.toSet());
|
||||||
|
|
||||||
@Override
|
List<UserTunnel> toSave = new ArrayList<>();
|
||||||
public R updateUserTunnel(UserTunnelUpdateDto updateDto) {
|
List<Integer> skippedIds = new ArrayList<>();
|
||||||
UserTunnel userTunnel = this.getById(updateDto.getId());
|
|
||||||
if (userTunnel == null) return R.err("隧道不存在");
|
for (UserTunnelBatchAssignDto.TunnelAssignItem item : batchAssignDto.getTunnels()) {
|
||||||
boolean speedChanged = hasSpeedChanged(userTunnel.getSpeedId(), updateDto.getSpeedId());
|
if (existingTunnelIds.contains(item.getTunnelId())) {
|
||||||
userTunnel.setFlow(updateDto.getFlow());
|
skippedIds.add(item.getTunnelId());
|
||||||
userTunnel.setNum(updateDto.getNum());
|
continue;
|
||||||
updateOptionalProperty(userTunnel::setFlowResetTime, updateDto.getFlowResetTime());
|
}
|
||||||
updateOptionalProperty(userTunnel::setExpTime, updateDto.getExpTime());
|
|
||||||
updateOptionalProperty(userTunnel::setStatus, updateDto.getStatus());
|
UserTunnel ut = new UserTunnel();
|
||||||
userTunnel.setSpeedId(updateDto.getSpeedId());
|
ut.setUserId(batchAssignDto.getUserId());
|
||||||
this.updateById(userTunnel);
|
ut.setTunnelId(item.getTunnelId());
|
||||||
if (speedChanged) {
|
ut.setSpeedId(item.getSpeedId());
|
||||||
List<Forward> forwardList = forwardService.list(new QueryWrapper<Forward>().eq("user_id", userTunnel.getUserId()).eq("tunnel_id", userTunnel.getTunnelId()));
|
ut.setStatus(1);
|
||||||
for (Forward forward : forwardList) {
|
ut.setInFlow(0L);
|
||||||
ForwardUpdateDto forwardUpdateDto = new ForwardUpdateDto();
|
ut.setOutFlow(0L);
|
||||||
forwardUpdateDto.setId(forward.getId());
|
ut.setFlow(user.getFlow());
|
||||||
forwardUpdateDto.setUserId(forward.getUserId());
|
ut.setNum(user.getNum());
|
||||||
forwardUpdateDto.setName(forward.getName());
|
ut.setFlowResetTime(user.getFlowResetTime());
|
||||||
forwardUpdateDto.setRemoteAddr(forward.getRemoteAddr());
|
ut.setExpTime(user.getExpTime());
|
||||||
forwardUpdateDto.setStrategy(forward.getStrategy());
|
toSave.add(ut);
|
||||||
forwardService.updateForward(forwardUpdateDto);
|
}
|
||||||
}
|
|
||||||
}
|
if (toSave.isEmpty()) {
|
||||||
return R.err("用户隧道权限更新失败");
|
return R.err("所选隧道用户均已拥有权限");
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> void updateOptionalProperty(java.util.function.Consumer<T> setter, T value) {
|
this.saveBatch(toSave);
|
||||||
if (value != null) {
|
|
||||||
setter.accept(value);
|
if (!skippedIds.isEmpty()) {
|
||||||
}
|
return R.ok("成功分配 " + toSave.size() + " 个隧道,跳过 " + skippedIds.size() + " 个已有权限的隧道");
|
||||||
}
|
}
|
||||||
|
return R.ok();
|
||||||
private boolean hasSpeedChanged(Integer oldSpeedId, Integer newSpeedId) {
|
}
|
||||||
if (oldSpeedId == null && newSpeedId == null) {
|
|
||||||
return false;
|
@Override
|
||||||
}
|
public R getUserTunnelList(UserTunnelQueryDto queryDto) {
|
||||||
if (oldSpeedId == null || newSpeedId == null) {
|
List<UserTunnelWithDetailDto> userTunnelWithDetails = this.baseMapper.getUserTunnelWithDetails(queryDto.getUserId());
|
||||||
return true;
|
return R.ok(userTunnelWithDetails);
|
||||||
}
|
}
|
||||||
return !oldSpeedId.equals(newSpeedId);
|
|
||||||
}
|
@Override
|
||||||
|
public R removeUserTunnel(Integer id) {
|
||||||
}
|
UserTunnel userTunnel = this.getById(id);
|
||||||
|
if (userTunnel == null) return R.err("未找到对应的用户隧道权限记录");
|
||||||
|
|
||||||
|
List<Forward> forwardList = forwardService.list(new QueryWrapper<Forward>()
|
||||||
|
.eq("user_id", userTunnel.getUserId())
|
||||||
|
.eq("tunnel_id", userTunnel.getTunnelId()));
|
||||||
|
for (Forward forward : forwardList) {
|
||||||
|
forwardService.deleteForward(forward.getId());
|
||||||
|
}
|
||||||
|
this.removeById(id);
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R updateUserTunnel(UserTunnelUpdateDto updateDto) {
|
||||||
|
UserTunnel userTunnel = this.getById(updateDto.getId());
|
||||||
|
if (userTunnel == null) return R.err("隧道不存在");
|
||||||
|
boolean speedChanged = hasSpeedChanged(userTunnel.getSpeedId(), updateDto.getSpeedId());
|
||||||
|
userTunnel.setFlow(updateDto.getFlow());
|
||||||
|
userTunnel.setNum(updateDto.getNum());
|
||||||
|
updateOptionalProperty(userTunnel::setFlowResetTime, updateDto.getFlowResetTime());
|
||||||
|
updateOptionalProperty(userTunnel::setExpTime, updateDto.getExpTime());
|
||||||
|
updateOptionalProperty(userTunnel::setStatus, updateDto.getStatus());
|
||||||
|
userTunnel.setSpeedId(updateDto.getSpeedId());
|
||||||
|
this.updateById(userTunnel);
|
||||||
|
if (speedChanged) {
|
||||||
|
List<Forward> forwardList = forwardService.list(new QueryWrapper<Forward>()
|
||||||
|
.eq("user_id", userTunnel.getUserId())
|
||||||
|
.eq("tunnel_id", userTunnel.getTunnelId()));
|
||||||
|
for (Forward forward : forwardList) {
|
||||||
|
ForwardUpdateDto forwardUpdateDto = new ForwardUpdateDto();
|
||||||
|
forwardUpdateDto.setId(forward.getId());
|
||||||
|
forwardUpdateDto.setUserId(forward.getUserId());
|
||||||
|
forwardUpdateDto.setName(forward.getName());
|
||||||
|
forwardUpdateDto.setRemoteAddr(forward.getRemoteAddr());
|
||||||
|
forwardUpdateDto.setStrategy(forward.getStrategy());
|
||||||
|
forwardService.updateForward(forwardUpdateDto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return R.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void updateOptionalProperty(java.util.function.Consumer<T> setter, T value) {
|
||||||
|
if (value != null) {
|
||||||
|
setter.accept(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasSpeedChanged(Integer oldSpeedId, Integer newSpeedId) {
|
||||||
|
if (oldSpeedId == null && newSpeedId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (oldSpeedId == null || newSpeedId == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return !oldSpeedId.equals(newSpeedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export const updateTunnelOrder = (data: { tunnels: Array<{ id: number; inx: numb
|
|||||||
|
|
||||||
// 用户隧道权限管理操作 - 全部使用POST请求
|
// 用户隧道权限管理操作 - 全部使用POST请求
|
||||||
export const assignUserTunnel = (data: any) => Network.post("/tunnel/user/assign", data);
|
export const assignUserTunnel = (data: any) => Network.post("/tunnel/user/assign", data);
|
||||||
|
export const batchAssignUserTunnel = (data: { userId: number; tunnels: Array<{ tunnelId: number; speedId?: number | null }> }) =>
|
||||||
|
Network.post("/tunnel/user/batch-assign", data);
|
||||||
export const getUserTunnelList = (queryData: any = {}) => Network.post("/tunnel/user/list", queryData);
|
export const getUserTunnelList = (queryData: any = {}) => Network.post("/tunnel/user/list", queryData);
|
||||||
export const removeUserTunnel = (params: any) => Network.post("/tunnel/user/remove", params);
|
export const removeUserTunnel = (params: any) => Network.post("/tunnel/user/remove", params);
|
||||||
export const updateUserTunnel = (data: any) => Network.post("/tunnel/user/update", data);
|
export const updateUserTunnel = (data: any) => Network.post("/tunnel/user/update", data);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
UserForm,
|
UserForm,
|
||||||
UserTunnel,
|
UserTunnel,
|
||||||
UserTunnelForm,
|
UserTunnelForm,
|
||||||
|
TunnelAssignItem,
|
||||||
Tunnel,
|
Tunnel,
|
||||||
SpeedLimit,
|
SpeedLimit,
|
||||||
Pagination as PaginationType
|
Pagination as PaginationType
|
||||||
@@ -42,6 +43,7 @@ import {
|
|||||||
deleteUser,
|
deleteUser,
|
||||||
getTunnelList,
|
getTunnelList,
|
||||||
assignUserTunnel,
|
assignUserTunnel,
|
||||||
|
batchAssignUserTunnel,
|
||||||
getUserTunnelList,
|
getUserTunnelList,
|
||||||
removeUserTunnel,
|
removeUserTunnel,
|
||||||
updateUserTunnel,
|
updateUserTunnel,
|
||||||
@@ -143,6 +145,7 @@ export default function UserPage() {
|
|||||||
speedId: null
|
speedId: null
|
||||||
});
|
});
|
||||||
const [assignLoading, setAssignLoading] = useState(false);
|
const [assignLoading, setAssignLoading] = useState(false);
|
||||||
|
const [batchTunnelSelections, setBatchTunnelSelections] = useState<Map<number, number | null>>(new Map());
|
||||||
|
|
||||||
// 编辑隧道权限相关状态
|
// 编辑隧道权限相关状态
|
||||||
const { isOpen: isEditTunnelModalOpen, onOpen: onEditTunnelModalOpen, onClose: onEditTunnelModalClose } = useDisclosure();
|
const { isOpen: isEditTunnelModalOpen, onOpen: onEditTunnelModalOpen, onClose: onEditTunnelModalClose } = useDisclosure();
|
||||||
@@ -342,6 +345,7 @@ export default function UserPage() {
|
|||||||
flowResetTime: 0,
|
flowResetTime: 0,
|
||||||
speedId: null
|
speedId: null
|
||||||
});
|
});
|
||||||
|
setBatchTunnelSelections(new Map());
|
||||||
onTunnelModalOpen();
|
onTunnelModalOpen();
|
||||||
loadUserTunnels(user.id);
|
loadUserTunnels(user.id);
|
||||||
};
|
};
|
||||||
@@ -385,6 +389,37 @@ export default function UserPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBatchAssignTunnel = async () => {
|
||||||
|
if (batchTunnelSelections.size === 0 || !currentUser) {
|
||||||
|
toast.error('请选择至少一个隧道');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setAssignLoading(true);
|
||||||
|
try {
|
||||||
|
const tunnelsToAssign: TunnelAssignItem[] = Array.from(batchTunnelSelections.entries()).map(
|
||||||
|
([tunnelId, speedId]) => ({ tunnelId, speedId })
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await batchAssignUserTunnel({
|
||||||
|
userId: currentUser.id,
|
||||||
|
tunnels: tunnelsToAssign
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.code === 0) {
|
||||||
|
toast.success(response.msg || '分配成功');
|
||||||
|
setBatchTunnelSelections(new Map());
|
||||||
|
loadUserTunnels(currentUser.id);
|
||||||
|
} else {
|
||||||
|
toast.error(response.msg || '分配失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('分配失败');
|
||||||
|
} finally {
|
||||||
|
setAssignLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleEditTunnel = (userTunnel: UserTunnel) => {
|
const handleEditTunnel = (userTunnel: UserTunnel) => {
|
||||||
setEditTunnelForm({
|
setEditTunnelForm({
|
||||||
...userTunnel,
|
...userTunnel,
|
||||||
@@ -526,6 +561,34 @@ export default function UserPage() {
|
|||||||
speedLimit => speedLimit.tunnelId === editTunnelForm?.tunnelId
|
speedLimit => speedLimit.tunnelId === editTunnelForm?.tunnelId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getSpeedLimitsForTunnel = (tunnelId: number) => {
|
||||||
|
return speedLimits.filter(sl => sl.tunnelId === tunnelId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleTunnelSelection = (tunnelId: number) => {
|
||||||
|
setBatchTunnelSelections(prev => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
if (newMap.has(tunnelId)) {
|
||||||
|
newMap.delete(tunnelId);
|
||||||
|
} else {
|
||||||
|
newMap.set(tunnelId, null);
|
||||||
|
}
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateTunnelSpeedLimit = (tunnelId: number, speedId: number | null) => {
|
||||||
|
setBatchTunnelSelections(prev => {
|
||||||
|
const newMap = new Map(prev);
|
||||||
|
newMap.set(tunnelId, speedId);
|
||||||
|
return newMap;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTunnelAssigned = (tunnelId: number) => {
|
||||||
|
return userTunnels.some(ut => ut.tunnelId === tunnelId);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<div className="px-3 lg:px-6 py-8">
|
<div className="px-3 lg:px-6 py-8">
|
||||||
@@ -878,107 +941,73 @@ export default function UserPage() {
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4">分配新权限</h3>
|
<h3 className="text-lg font-semibold mb-4">分配新权限</h3>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="text-sm text-gray-500 bg-gray-50 dark:bg-gray-800 p-3 rounded-lg">
|
||||||
<Select
|
流量限制、转发数量、到期时间、流量重置时间将自动继承用户设置
|
||||||
label="选择隧道"
|
</div>
|
||||||
selectedKeys={tunnelForm.tunnelId ? [tunnelForm.tunnelId.toString()] : []}
|
|
||||||
onSelectionChange={(keys) => {
|
<div className="border rounded-lg divide-y dark:divide-gray-700 max-h-64 overflow-y-auto">
|
||||||
const value = Array.from(keys)[0] as string;
|
{tunnels.map(tunnel => {
|
||||||
setTunnelForm(prev => ({ ...prev, tunnelId: Number(value) || null, speedId: null }));
|
const isAssigned = isTunnelAssigned(tunnel.id);
|
||||||
}}
|
const isSelected = batchTunnelSelections.has(tunnel.id);
|
||||||
>
|
const tunnelSpeedLimits = getSpeedLimitsForTunnel(tunnel.id);
|
||||||
{availableTunnels.map(tunnel => (
|
|
||||||
<SelectItem key={tunnel.id.toString()} textValue={tunnel.name}>
|
return (
|
||||||
{tunnel.name}
|
<div
|
||||||
</SelectItem>
|
key={tunnel.id}
|
||||||
))}
|
className={`p-3 flex items-center justify-between gap-4 ${isAssigned ? 'bg-gray-100 dark:bg-gray-800' : ''}`}
|
||||||
</Select>
|
>
|
||||||
|
<div className="flex items-center gap-3 flex-1">
|
||||||
<Select
|
<input
|
||||||
label="限速规则"
|
type="checkbox"
|
||||||
selectedKeys={tunnelForm.speedId ? [tunnelForm.speedId.toString()] : ["null"]}
|
checked={isSelected}
|
||||||
onSelectionChange={(keys) => {
|
disabled={isAssigned}
|
||||||
const value = Array.from(keys)[0] as string;
|
onChange={() => toggleTunnelSelection(tunnel.id)}
|
||||||
setTunnelForm(prev => ({ ...prev, speedId: value === "null" ? null : Number(value) }));
|
className="w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary disabled:opacity-50"
|
||||||
}}
|
/>
|
||||||
isDisabled={!tunnelForm.tunnelId}
|
<span className={isAssigned ? 'text-gray-400' : ''}>
|
||||||
>
|
{tunnel.name}
|
||||||
{[
|
</span>
|
||||||
<SelectItem key="null" textValue="不限速">不限速</SelectItem>,
|
{isAssigned && (
|
||||||
...availableSpeedLimits.map(speedLimit => (
|
<Chip size="sm" variant="flat" color="default">已分配</Chip>
|
||||||
<SelectItem key={speedLimit.id.toString()} textValue={speedLimit.name}>
|
)}
|
||||||
{speedLimit.name}
|
</div>
|
||||||
</SelectItem>
|
|
||||||
))
|
{isSelected && !isAssigned && (
|
||||||
]}
|
<Select
|
||||||
</Select>
|
size="sm"
|
||||||
|
label="限速规则"
|
||||||
<Input
|
className="w-40"
|
||||||
label="流量限制(GB)"
|
selectedKeys={batchTunnelSelections.get(tunnel.id) !== null && batchTunnelSelections.get(tunnel.id) !== undefined ? [batchTunnelSelections.get(tunnel.id)!.toString()] : ["null"]}
|
||||||
type="number"
|
onSelectionChange={(keys) => {
|
||||||
value={tunnelForm.flow.toString()}
|
const value = Array.from(keys)[0] as string;
|
||||||
onChange={(e) => {
|
updateTunnelSpeedLimit(tunnel.id, value === "null" ? null : Number(value));
|
||||||
const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999);
|
}}
|
||||||
setTunnelForm(prev => ({ ...prev, flow: value }));
|
>
|
||||||
}}
|
{[
|
||||||
min="1"
|
<SelectItem key="null" textValue="不限速">不限速</SelectItem>,
|
||||||
max="99999"
|
...tunnelSpeedLimits.map(sl => (
|
||||||
/>
|
<SelectItem key={sl.id.toString()} textValue={sl.name}>
|
||||||
|
{sl.name}
|
||||||
<Input
|
</SelectItem>
|
||||||
label="转发数量"
|
))
|
||||||
type="number"
|
]}
|
||||||
value={tunnelForm.num.toString()}
|
</Select>
|
||||||
onChange={(e) => {
|
)}
|
||||||
const value = Math.min(Math.max(Number(e.target.value) || 0, 1), 99999);
|
</div>
|
||||||
setTunnelForm(prev => ({ ...prev, num: value }));
|
);
|
||||||
}}
|
})}
|
||||||
min="1"
|
{tunnels.length === 0 && (
|
||||||
max="99999"
|
<div className="p-4 text-center text-gray-500">暂无可用隧道</div>
|
||||||
/>
|
)}
|
||||||
|
|
||||||
<Select
|
|
||||||
label="流量重置日期"
|
|
||||||
selectedKeys={[tunnelForm.flowResetTime.toString()]}
|
|
||||||
onSelectionChange={(keys) => {
|
|
||||||
const value = Array.from(keys)[0] as string;
|
|
||||||
setTunnelForm(prev => ({ ...prev, flowResetTime: Number(value) }));
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<>
|
|
||||||
<SelectItem key="0" textValue="不重置">
|
|
||||||
不重置
|
|
||||||
</SelectItem>
|
|
||||||
{Array.from({ length: 31 }, (_, i) => i + 1).map(day => (
|
|
||||||
<SelectItem key={day.toString()} textValue={`每月${day}号(0点重置)`}>
|
|
||||||
每月{day}号(0点重置)
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<DatePicker
|
|
||||||
label="到期时间"
|
|
||||||
value={tunnelForm.expTime ? parseDate(tunnelForm.expTime.toISOString().split('T')[0]) as any : null}
|
|
||||||
onChange={(date) => {
|
|
||||||
if (date) {
|
|
||||||
const jsDate = new Date(date.year, date.month - 1, date.day, 23, 59, 59);
|
|
||||||
setTunnelForm(prev => ({ ...prev, expTime: jsDate }));
|
|
||||||
} else {
|
|
||||||
setTunnelForm(prev => ({ ...prev, expTime: null }));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
showMonthAndYearPickers
|
|
||||||
className="cursor-pointer"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
onPress={handleAssignTunnel}
|
onPress={handleBatchAssignTunnel}
|
||||||
isLoading={assignLoading}
|
isLoading={assignLoading}
|
||||||
|
isDisabled={batchTunnelSelections.size === 0}
|
||||||
>
|
>
|
||||||
分配权限
|
分配权限 {batchTunnelSelections.size > 0 && `(${batchTunnelSelections.size}个隧道)`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,6 +58,15 @@ export interface UserTunnelForm {
|
|||||||
speedId: number | null;
|
speedId: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TunnelAssignItem {
|
||||||
|
tunnelId: number;
|
||||||
|
speedId: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserTunnelBatchAssignForm {
|
||||||
|
tunnels: TunnelAssignItem[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Tunnel {
|
export interface Tunnel {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user