Commit 818b6cd1e1169c2826befdb10806a5d4baa44b27

Authored by 黄 x
1 parent a05f9e79

feat: add frp remote manager

  1 +package org.thingsboard.server.controller.yunteng;
  2 +
  3 +import io.swagger.annotations.Api;
  4 +import io.swagger.annotations.ApiOperation;
  5 +import lombok.RequiredArgsConstructor;
  6 +import org.springframework.security.access.prepost.PreAuthorize;
  7 +import org.springframework.web.bind.annotation.*;
  8 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  9 +import org.thingsboard.server.common.data.yunteng.dto.FrpInfoDTO;
  10 +import org.thingsboard.server.dao.yunteng.service.YtFrpInfoService;
  11 +
  12 +@RestController
  13 +@RequestMapping("api/yt/frp")
  14 +@Api(tags = "Frp内网穿透信息")
  15 +@RequiredArgsConstructor
  16 +@PreAuthorize("hasAnyAuthority('TENANT_ADMIN','CUSTOMER_USER')")
  17 +public class YtFrpInfoController {
  18 +
  19 + private final YtFrpInfoService frpInfoService;
  20 +
  21 + @GetMapping("/{proxyName}")
  22 + @ApiOperation("通过代理名称或SN获取内网穿透信息")
  23 + public FrpInfoDTO findFrpInfoByProxyName(@PathVariable("proxyName") String proxyName)
  24 + throws ThingsboardException {
  25 + return frpInfoService.findFrpInfoByProxyName(proxyName);
  26 + }
  27 +
  28 + @PutMapping("/{proxyName}/{enableRemote}")
  29 + @ApiOperation("是否开启远程连接:0关闭 1开启")
  30 + public FrpInfoDTO updateFrpInfoByProxyName(
  31 + @PathVariable("proxyName") String proxyName,
  32 + @PathVariable("enableRemote") Integer enableRemote)
  33 + throws ThingsboardException {
  34 + FrpInfoDTO frpInfoDTO = frpInfoService.findFrpInfoByProxyName(proxyName);
  35 + frpInfoDTO.setEnableRemote(enableRemote);
  36 + return frpInfoService.saveOrUpdateFrpInfo(frpInfoDTO);
  37 + }
  38 +}
... ...
1 1 package org.thingsboard.server.controller.yunteng;
2 2
  3 +import com.fasterxml.jackson.databind.JsonNode;
  4 +import com.fasterxml.jackson.databind.node.ObjectNode;
3 5 import io.swagger.annotations.ApiOperation;
4 6 import lombok.RequiredArgsConstructor;
5 7 import org.springframework.util.Assert;
6 8 import org.springframework.web.bind.annotation.*;
7 9 import org.thingsboard.server.common.data.exception.ThingsboardException;
  10 +import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException;
  11 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  12 +import org.thingsboard.server.common.data.yunteng.dto.FrpInfoDTO;
8 13 import org.thingsboard.server.common.data.yunteng.dto.request.AccountReqDTO;
9   -import org.thingsboard.server.common.data.yunteng.dto.request.SendResetPasswordEmailMsg;
  14 +import org.thingsboard.server.common.data.yunteng.enums.FrpPluginsEnum;
10 15 import org.thingsboard.server.common.data.yunteng.enums.MsgTemplatePurposeEnum;
  16 +import org.thingsboard.server.common.data.yunteng.enums.StatusEnum;
  17 +import org.thingsboard.server.common.data.yunteng.utils.JacksonUtil;
  18 +import org.thingsboard.server.controller.BaseController;
  19 +import org.thingsboard.server.dao.yunteng.service.YtFrpInfoService;
11 20 import org.thingsboard.server.dao.yunteng.service.YtSmsService;
12 21 import org.thingsboard.server.dao.yunteng.service.YtUserService;
13 22
  23 +import java.net.UnknownHostException;
  24 +import java.util.Optional;
  25 +
14 26 import static org.thingsboard.server.common.data.yunteng.constant.FastIotConstants.CHINA_MOBILE_PATTERN;
15 27
16 28 @RestController
17 29 @RequestMapping("api/yt/noauth")
18 30 @RequiredArgsConstructor
19   -public class YtNoAuthController {
  31 +public class YtNoAuthController extends BaseController {
20 32
21 33 private final YtSmsService smsService;
22 34 private final YtUserService userService;
  35 + private final YtFrpInfoService frpInfoService;
23 36
24 37 @PostMapping("/sendLoginSmsCode/{phoneNumber}")
25 38 public boolean sendVerificationCode(@PathVariable("phoneNumber") String phoneNumber) {
... ... @@ -30,8 +43,10 @@ public class YtNoAuthController {
30 43
31 44 @PostMapping("/reset/{phoneNumber}")
32 45 @ApiOperation("密码找回")
33   - public void saveForgetPassword(@PathVariable("phoneNumber") String phoneNumber,@RequestBody AccountReqDTO forget) throws ThingsboardException {
34   - userService.forgetPassword(phoneNumber,forget);
  46 + public void saveForgetPassword(
  47 + @PathVariable("phoneNumber") String phoneNumber, @RequestBody AccountReqDTO forget)
  48 + throws ThingsboardException {
  49 + userService.forgetPassword(phoneNumber, forget);
35 50 }
36 51
37 52 @PostMapping("/resetCode/{phoneNumber}")
... ... @@ -39,4 +54,57 @@ public class YtNoAuthController {
39 54 public void forgetPasswordCode(@PathVariable("phoneNumber") String phoneNumber) {
40 55 smsService.sendSmsCode(phoneNumber, MsgTemplatePurposeEnum.FOR_FORGET_PASSWORD);
41 56 }
  57 +
  58 + @PostMapping("/handler")
  59 + @ApiOperation("操作Frp内网穿透信息")
  60 + public JsonNode forgetPasswordCode(@RequestBody ObjectNode params)
  61 + throws ThingsboardException, UnknownHostException {
  62 + String value =
  63 + Optional.ofNullable(params)
  64 + .map(param -> params.get("op").asText())
  65 + .orElseThrow(
  66 + () -> new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage()));
  67 + ObjectNode objectNode = JacksonUtil.newObjectNode();
  68 + ObjectNode content =
  69 + Optional.ofNullable(params.get("content"))
  70 + .map(data -> JacksonUtil.convertValue(params.get("content"), ObjectNode.class))
  71 + .orElseThrow(
  72 + () -> new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage()));
  73 + String sn = null;
  74 + FrpInfoDTO queryFrpInfoDTO = null;
  75 + if (!value.equals(FrpPluginsEnum.Login.name()) && !value.equals(FrpPluginsEnum.Ping.name())) {
  76 + sn =
  77 + Optional.ofNullable(content.get("proxy_name"))
  78 + .map(proxyName -> content.get("proxy_name").asText())
  79 + .orElseThrow(
  80 + () -> new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage()));
  81 + queryFrpInfoDTO = frpInfoService.findFrpInfoByProxyName(sn);
  82 + }
  83 +
  84 + if (value.equals(FrpPluginsEnum.NewProxy.name())) {
  85 + // 创建新代理,保存客户端数据到数据库,如已存在数据则更新数据即可,设置连接状态为在线
  86 + frpInfoService.saveOrUpdateContent(content, queryFrpInfoDTO, sn);
  87 + }
  88 + if (value.equals(FrpPluginsEnum.CloseProxy.name())) {
  89 + // 关闭代理,设置连接状态为离线
  90 + if (null != queryFrpInfoDTO) {
  91 + queryFrpInfoDTO.setStatus(StatusEnum.OFFLINE.getIndex());
  92 + frpInfoService.saveOrUpdateFrpInfo(queryFrpInfoDTO);
  93 + }
  94 + }
  95 + if (value.equals(FrpPluginsEnum.NewUserConn.name())) {
  96 + // 如果平台允许远程连接为开,则允许连接。否则拒绝执行操作
  97 + if (null == queryFrpInfoDTO || queryFrpInfoDTO.getEnableRemote() == 0) {
  98 + objectNode.put("reject", true);
  99 + objectNode.put("reject_reason", "invalid user");
  100 + return objectNode;
  101 + }
  102 + }
  103 + /** 第一种 拒绝执行操作 { "reject": true, "reject_reason": "invalid user" } */
  104 + /** 第二种 允许且内容不需要变动 { "reject": false, "unchange": true } */
  105 + /** 第三种 允许且需要替换操作内容 { "unchange": "false", "content": { ... // 替换后的操作信息,格式必须和请求时的一致 } } */
  106 + objectNode.put("reject", false);
  107 + objectNode.put("unchange", true);
  108 + return objectNode;
  109 + }
42 110 }
... ...
... ... @@ -87,6 +87,8 @@ public final class ModelConstants {
87 87 public static final String IOTFS_OPINION_TABLE_NAME = "iotfs_opinion";
88 88 /** 第三方用户表 */
89 89 public static final String IOTFS_THIRD_USER_TABLE_NAME = "iotfs_third_user";
  90 + /** frp内网穿透信息表 */
  91 + public static final String IOTFS_FRP_INFO_NAME = "iotfs_frp_info";
90 92 }
91 93
92 94 public static class TableFields {
... ...
... ... @@ -68,6 +68,7 @@ public enum ErrorMessage {
68 68 VIDEO_PLATFORM_CONFIG_ERROR(400048,"流媒体平台信息配置有误"),
69 69 VIDEO_PLATFORM_NEED_ENABLE_HLS_HTTPS(400049,"平台需开启https的hls协议"),
70 70 NAME_EXISTED(400050,"名称【%s】已存在"),
  71 + INVALID_PARAMETER_OR_NOT_MATCH_TENANT(400051,"参数错误或该条数据不属于当前租户"),
71 72 HAVE_NO_PERMISSION(500002,"没有修改权限");
72 73 private final int code;
73 74 private String message;
... ...
  1 +package org.thingsboard.server.common.data.yunteng.dto;
  2 +
  3 +import lombok.Data;
  4 +import lombok.EqualsAndHashCode;
  5 +
  6 +@EqualsAndHashCode(callSuper = false)
  7 +@Data
  8 +public class FrpInfoDTO extends BaseDTO {
  9 +
  10 + /** 访问地址 */
  11 + private String address;
  12 +
  13 + /** 代理名称或设备SN */
  14 + private String proxyName;
  15 +
  16 + /** 端口 */
  17 + private Integer remotePort;
  18 +
  19 + /** 客户端FRP运行ID */
  20 + private String runId;
  21 +
  22 + /** 状态:0离线 1在线 */
  23 + private Integer status;
  24 +
  25 + /** 允许远程 */
  26 + private Integer enableRemote;
  27 +
  28 + /** 备注 */
  29 + private String remark;
  30 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +public enum FrpPluginsEnum {
  4 + /**
  5 + * FRP心跳
  6 + */
  7 + Ping,
  8 + /**
  9 + * FRP登录
  10 + */
  11 + Login,
  12 + /**
  13 + * FRP创建代理
  14 + */
  15 + NewProxy,
  16 + /**
  17 + * FRP关闭代理
  18 + */
  19 + CloseProxy,
  20 + /**
  21 + * FRP创建新的工作连接
  22 + */
  23 + NewWorkConn,
  24 + /**
  25 + * FRP创建新的用户连接
  26 + */
  27 + NewUserConn
  28 +
  29 +}
... ...
  1 +package org.thingsboard.server.common.data.yunteng.enums;
  2 +
  3 +import lombok.Getter;
  4 +import lombok.Setter;
  5 +
  6 +public enum StatusEnum {
  7 + OFFLINE("离线",0),
  8 + ONLINE("在线",1);
  9 +
  10 + @Getter
  11 + @Setter
  12 + private String name;
  13 + @Getter
  14 + @Setter
  15 + private Integer index;
  16 +
  17 + StatusEnum(String name, Integer index) {
  18 + this.name = name;
  19 + this.index = index;
  20 + }
  21 +
  22 +}
... ...
  1 +package org.thingsboard.server.dao.yunteng.entities;
  2 +
  3 +import com.baomidou.mybatisplus.annotation.*;
  4 +import lombok.Data;
  5 +import lombok.EqualsAndHashCode;
  6 +import org.thingsboard.server.common.data.yunteng.constant.ModelConstants;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@TableName(value = ModelConstants.Table.IOTFS_FRP_INFO_NAME, autoResultMap = true)
  13 +public class FrpInfo extends BaseEntity {
  14 +
  15 + private static final long serialVersionUID = 970621268444886639L;
  16 +
  17 + @TableField(fill = FieldFill.INSERT)
  18 + private LocalDateTime createTime;
  19 +
  20 + @TableField(fill = FieldFill.UPDATE)
  21 + private LocalDateTime updateTime;
  22 +
  23 + /**
  24 + * 访问地址
  25 + */
  26 + private String address;
  27 +
  28 + /** 代理名称或设备SN */
  29 + private String proxyName;
  30 +
  31 + /** 端口 */
  32 + private Integer remotePort;
  33 +
  34 + /** 客户端FRP运行ID */
  35 + private String runId;
  36 +
  37 + /** 状态:0离线 1在线 */
  38 + private Integer status;
  39 +
  40 + /** 允许远程 */
  41 + private Integer enableRemote;
  42 +
  43 + /** 备注 */
  44 + private String remark;
  45 +}
... ...
  1 +package org.thingsboard.server.dao.yunteng.impl;
  2 +
  3 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  4 +import com.baomidou.mybatisplus.core.metadata.IPage;
  5 +import com.fasterxml.jackson.databind.node.ObjectNode;
  6 +import org.apache.commons.lang3.StringUtils;
  7 +import org.springframework.stereotype.Service;
  8 +import org.thingsboard.server.common.data.yunteng.core.exception.YtDataValidationException;
  9 +import org.thingsboard.server.common.data.yunteng.core.message.ErrorMessage;
  10 +import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO;
  11 +import org.thingsboard.server.common.data.yunteng.dto.FrpInfoDTO;
  12 +import org.thingsboard.server.common.data.yunteng.enums.StatusEnum;
  13 +import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
  14 +import org.thingsboard.server.dao.yunteng.entities.FrpInfo;
  15 +import org.thingsboard.server.dao.yunteng.mapper.FrpInfoMapper;
  16 +import org.thingsboard.server.dao.yunteng.service.AbstractBaseService;
  17 +import org.thingsboard.server.dao.yunteng.service.YtFrpInfoService;
  18 +
  19 +import java.net.InetAddress;
  20 +import java.net.UnknownHostException;
  21 +import java.time.LocalDateTime;
  22 +import java.util.Map;
  23 +import java.util.Optional;
  24 +
  25 +@Service
  26 +public class YtFrpInfoServiceImpl extends AbstractBaseService<FrpInfoMapper, FrpInfo>
  27 + implements YtFrpInfoService {
  28 + @Override
  29 + public YtPageData<FrpInfoDTO> page(Map<String, Object> queryMap) {
  30 + String proxyName =
  31 + queryMap.get("proxyName") != null ? queryMap.get("proxyName").toString() : null;
  32 + IPage<FrpInfo> frpInfoIPage =
  33 + baseMapper.selectPage(
  34 + getPage(queryMap, "create_time", false),
  35 + new LambdaQueryWrapper<FrpInfo>()
  36 + .eq(StringUtils.isNotEmpty(proxyName), FrpInfo::getProxyName, proxyName));
  37 + return getPageData(frpInfoIPage, FrpInfoDTO.class);
  38 + }
  39 +
  40 + @Override
  41 + public FrpInfoDTO saveOrUpdateFrpInfo(FrpInfoDTO frpInfoDTO) {
  42 + if (null == frpInfoDTO) {
  43 + throw new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  44 + }
  45 + if (StringUtils.isEmpty(frpInfoDTO.getId())) {
  46 + frpInfoDTO.setCreateTime(LocalDateTime.now());
  47 + baseMapper.insert(frpInfoDTO.getEntity(FrpInfo.class));
  48 + } else {
  49 + FrpInfo frpInfo =
  50 + baseMapper.selectOne(
  51 + new LambdaQueryWrapper<FrpInfo>().eq(FrpInfo::getId, frpInfoDTO.getId()));
  52 + frpInfoDTO.setUpdateTime(LocalDateTime.now());
  53 + Optional.ofNullable(frpInfo)
  54 + .map(info -> baseMapper.updateById(frpInfoDTO.getEntity(FrpInfo.class)))
  55 + .orElseThrow(
  56 + () ->
  57 + new YtDataValidationException(
  58 + ErrorMessage.INVALID_PARAMETER_OR_NOT_MATCH_TENANT.getMessage()));
  59 + }
  60 + return frpInfoDTO;
  61 + }
  62 +
  63 + @Override
  64 + public boolean deleteFrpInfo(DeleteDTO deleteDTO) {
  65 + if (deleteDTO.getIds().isEmpty()) {
  66 + throw new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  67 + }
  68 + return baseMapper.deleteBatchIds(deleteDTO.getIds()) > 0;
  69 + }
  70 +
  71 + @Override
  72 + public FrpInfoDTO findFrpInfoById(String id) {
  73 + if (StringUtils.isEmpty(id)) {
  74 + throw new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  75 + }
  76 + FrpInfo frpInfo =
  77 + baseMapper.selectOne(new LambdaQueryWrapper<FrpInfo>().eq(FrpInfo::getId, id));
  78 + return Optional.ofNullable(frpInfo)
  79 + .map(info -> frpInfo.getDTO(FrpInfoDTO.class))
  80 + .orElseThrow(
  81 + () ->
  82 + new YtDataValidationException(
  83 + ErrorMessage.INVALID_PARAMETER_OR_NOT_MATCH_TENANT.getMessage()));
  84 + }
  85 +
  86 + @Override
  87 + public FrpInfoDTO findFrpInfoByProxyName(String proxyName) {
  88 + if (StringUtils.isEmpty(proxyName)) {
  89 + throw new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage());
  90 + }
  91 + FrpInfo frpInfo =
  92 + baseMapper.selectOne(
  93 + new LambdaQueryWrapper<FrpInfo>().eq(StringUtils.isNotEmpty(proxyName),FrpInfo::getProxyName, proxyName));
  94 + return Optional.ofNullable(frpInfo).map(info -> frpInfo.getDTO(FrpInfoDTO.class)).orElse(null);
  95 + }
  96 +
  97 + @Override
  98 + public FrpInfoDTO saveOrUpdateContent(ObjectNode content, FrpInfoDTO queryFrpInfoDTO, String sn)
  99 + throws UnknownHostException {
  100 + String runId =
  101 + Optional.ofNullable(content.get("user"))
  102 + .map(user -> content.get("user").get("run_id").asText())
  103 + .orElseThrow(
  104 + () -> new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage()));
  105 + Integer remotePort =
  106 + Optional.ofNullable(content.get("remote_port"))
  107 + .map(remote -> content.get("remote_port").asInt())
  108 + .orElseThrow(
  109 + () -> new YtDataValidationException(ErrorMessage.INVALID_PARAMETER.getMessage()));
  110 +
  111 + FrpInfoDTO saveFrpInfoDTO = new FrpInfoDTO();
  112 + if (null == queryFrpInfoDTO) {
  113 + saveFrpInfoDTO.setProxyName(sn);
  114 + saveFrpInfoDTO.setRemotePort(remotePort);
  115 + saveFrpInfoDTO.setRunId(runId);
  116 + saveFrpInfoDTO.setStatus(StatusEnum.ONLINE.getIndex());
  117 + } else {
  118 + queryFrpInfoDTO.setRunId(runId);
  119 + queryFrpInfoDTO.setStatus(StatusEnum.ONLINE.getIndex());
  120 + saveFrpInfoDTO = queryFrpInfoDTO;
  121 + }
  122 + saveFrpInfoDTO.setAddress(
  123 + "http://" + InetAddress.getLocalHost().getHostAddress() + ":" + remotePort);
  124 + return saveOrUpdateFrpInfo(saveFrpInfoDTO);
  125 + }
  126 +}
... ...
  1 +package org.thingsboard.server.dao.yunteng.mapper;
  2 +
  3 +import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  4 +import org.apache.ibatis.annotations.Mapper;
  5 +import org.thingsboard.server.dao.yunteng.entities.FrpInfo;
  6 +
  7 +@Mapper
  8 +public interface FrpInfoMapper extends BaseMapper<FrpInfo> {
  9 +
  10 +}
... ...
  1 +package org.thingsboard.server.dao.yunteng.service;
  2 +
  3 +import com.fasterxml.jackson.databind.node.ObjectNode;
  4 +import org.thingsboard.server.common.data.yunteng.dto.DeleteDTO;
  5 +import org.thingsboard.server.common.data.yunteng.dto.FrpInfoDTO;
  6 +import org.thingsboard.server.common.data.yunteng.utils.tools.YtPageData;
  7 +
  8 +import java.net.UnknownHostException;
  9 +import java.util.Map;
  10 +
  11 +public interface YtFrpInfoService {
  12 +
  13 + YtPageData<FrpInfoDTO> page(Map<String, Object> queryMap);
  14 +
  15 + FrpInfoDTO saveOrUpdateFrpInfo(FrpInfoDTO frpInfoDTO);
  16 +
  17 + boolean deleteFrpInfo(DeleteDTO deleteDTO);
  18 +
  19 + FrpInfoDTO findFrpInfoById(String id);
  20 +
  21 + FrpInfoDTO findFrpInfoByProxyName(String proxyName);
  22 +
  23 + FrpInfoDTO saveOrUpdateContent(ObjectNode content, FrpInfoDTO queryFrpInfoDTO, String sn)
  24 + throws UnknownHostException;
  25 +}
... ...