2
0

feat: integrated omada api

This commit is contained in:
caiyuchao
2024-11-21 18:23:14 +08:00
parent e9c35d9ac1
commit 64abc8d59a
14 changed files with 481 additions and 0 deletions

View File

@@ -2,6 +2,7 @@
cd ../../fe.wfc/ cd ../../fe.wfc/
git pull git pull
pnpm i
pnpm build pnpm build
cd ../be.wfc/docker/ cd ../be.wfc/docker/
./copy.sh ./copy.sh

33
pom.xml
View File

@@ -35,6 +35,8 @@
<minio.version>8.2.2</minio.version> <minio.version>8.2.2</minio.version>
<poi.version>4.1.2</poi.version> <poi.version>4.1.2</poi.version>
<transmittable-thread-local.version>2.14.4</transmittable-thread-local.version> <transmittable-thread-local.version>2.14.4</transmittable-thread-local.version>
<hutool.version>5.8.33</hutool.version>
<lombok.version>1.18.36</lombok.version>
</properties> </properties>
<!-- 依赖声明 --> <!-- 依赖声明 -->
@@ -152,6 +154,20 @@
<version>${transmittable-thread-local.version}</version> <version>${transmittable-thread-local.version}</version>
</dependency> </dependency>
<!-- hutool工具 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- lombok工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!-- 核心模块 --> <!-- 核心模块 -->
<dependency> <dependency>
<groupId>org.wfc</groupId> <groupId>org.wfc</groupId>
@@ -222,6 +238,13 @@
<version>${wfc.version}</version> <version>${wfc.version}</version>
</dependency> </dependency>
<!-- omada接口 -->
<dependency>
<groupId>org.wfc</groupId>
<artifactId>wfc-api-omada</artifactId>
<version>${wfc.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
@@ -340,5 +363,15 @@
<nacosNamespace>wfc-prod</nacosNamespace> <nacosNamespace>wfc-prod</nacosNamespace>
</properties> </properties>
</profile> </profile>
<!-- cyc -->
<profile>
<id>cyc</id>
<properties>
<!--当前环境-->
<profileName>cyc</profileName>
<nacosServerAddr>192.168.2.248:8848</nacosServerAddr>
<nacosNamespace>wfc-cyc</nacosNamespace>
</properties>
</profile>
</profiles> </profiles>
</project> </project>

View File

@@ -10,6 +10,7 @@
<modules> <modules>
<module>wfc-api-system</module> <module>wfc-api-system</module>
<module>wfc-api-omada</module>
</modules> </modules>
<artifactId>wfc-api</artifactId> <artifactId>wfc-api</artifactId>

View File

@@ -0,0 +1,30 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.wfc</groupId>
<artifactId>wfc-api</artifactId>
<version>3.6.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>wfc-api-omada</artifactId>
<description>
wfc-api-omada接口模块
</description>
<dependencies>
<!-- RuoYi Common Core-->
<dependency>
<groupId>org.wfc</groupId>
<artifactId>wfc-common-core</artifactId>
</dependency>
<dependency>
<groupId>org.wfc</groupId>
<artifactId>wfc-common-redis</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,22 @@
package org.wfc.omada.api;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.wfc.omada.api.config.FeignHttpsConfig;
import org.wfc.omada.api.config.FeignConfig;
/**
* @description: Omada接口
* @author: caiyuchao
* @date: 2024-11-20
*/
@FeignClient(name = "remoteOmadaFeign",url = "${omada.omada-url}", configuration = {FeignConfig.class, FeignHttpsConfig.class})
public interface RemoteOmadaFeign {
@RequestMapping(value = "/openapi/v1/${omada.omadac-id}/sites", method = RequestMethod.GET)
Object getData(@RequestParam("page") Integer page, @RequestParam("pageSize") Integer pageSize);
}

View File

@@ -0,0 +1,101 @@
package org.wfc.omada.api.config;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import org.wfc.common.redis.service.RedisService;
import org.wfc.omada.api.domain.vo.AuthorizeTokenVO;
import org.wfc.omada.api.domain.vo.OmadaResult;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @description: Feign 配置
* @author: caiyuchao
* @date: 2024-11-21
*/
@Configuration
public class FeignConfig implements RequestInterceptor {
@Autowired
private RedisService redisService;
@Autowired
private OmadaProperties omadaProperties;
private static final String REDIS_ACCESS_TOKEN = "wfc-api-omada:access-token";
private static final String REDIS_REFRESH_TOKEN = "wfc-api-omada:refresh-token";
private static final String OMADACID = "omadacId";
private static final String CLIENT_ID = "client_id";
private static final String CLIENT_SECRET = "client_secret";
private static final String GRANT_TYPE = "grant_type";
private static final String PRE_ACCESS_TOKEN = "AccessToken=";
private static final String REFRESH_TOKEN = "refresh_token";
private static final String AUTHORIZATION = "Authorization";
private static final String CLIENT_CREDENTIALS = "client_credentials";
private static final String OPENAPI_AUTHORIZE_TOKEN_PATH = "/openapi/authorize/token";
@Override
public void apply(RequestTemplate requestTemplate) {
// 获取redis中的访问令牌
String cacheAccessToken = redisService.getCacheObject(REDIS_ACCESS_TOKEN);
String authorization;
if (StrUtil.isBlank(cacheAccessToken)) {
// 访问令牌不存在获取redis中的刷新令牌
String cacheRefreshToken = redisService.getCacheObject(REDIS_REFRESH_TOKEN);
// 构造请求参数query和body
Map<String, String> body = new HashMap<>();
body.put(CLIENT_ID, omadaProperties.getClientId());
body.put(CLIENT_SECRET, omadaProperties.getClientSecret());
LinkedMultiValueMap<String, String> query = new LinkedMultiValueMap<>();
String url = omadaProperties.getOmadaUrl() + OPENAPI_AUTHORIZE_TOKEN_PATH;
if (StrUtil.isBlank(cacheRefreshToken)) {
// 获取访问令牌
// 调用客户端认证模式获取access token
body.put(OMADACID, omadaProperties.getOmadacId());
query.add(GRANT_TYPE, CLIENT_CREDENTIALS);
} else {
// 刷新访问令牌
query.add(GRANT_TYPE, REFRESH_TOKEN);
query.add(REFRESH_TOKEN, cacheRefreshToken);
}
HttpEntity<String> request = new HttpEntity<>(JSON.toJSONString(body));
String uriString = UriComponentsBuilder.fromHttpUrl(url).queryParams(query).build().toUriString();
// 发送调用请求
RestTemplate restTemplate = new RestTemplate(new RestTemplateConfig.HttpsClientRequestFactory());
ResponseEntity<OmadaResult<AuthorizeTokenVO>> responseEntity = restTemplate.exchange(uriString, HttpMethod.POST,
request, new ParameterizedTypeReference<OmadaResult<AuthorizeTokenVO>>() {
});
OmadaResult<AuthorizeTokenVO> omadaResult = responseEntity.getBody();
if (omadaResult == null) {
return;
}
String accessToken = omadaResult.getResult().getAccessToken();
String refreshToken = omadaResult.getResult().getRefreshToken();
authorization = PRE_ACCESS_TOKEN + accessToken;
// 保存访问令牌和刷新令牌到redis中
redisService.setCacheObject(REDIS_ACCESS_TOKEN, accessToken, 7000L, TimeUnit.SECONDS);
redisService.setCacheObject(REDIS_REFRESH_TOKEN, refreshToken, 13L, TimeUnit.DAYS);
} else {
authorization = PRE_ACCESS_TOKEN + cacheAccessToken;
}
if (StrUtil.isNotBlank(authorization)) {
// 添加授权请求头
requestTemplate.header(AUTHORIZATION, authorization);
}
}
}

View File

@@ -0,0 +1,55 @@
package org.wfc.omada.api.config;
import feign.Client;
import feign.Feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
/**
* @description: Feign https 配置
* @author: caiyuchao
* @date: 2024-11-21
*/
@Configuration
public class FeignHttpsConfig {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
@Bean
public Client generateClient() {
try {
SSLContext ctx = SSLContext.getInstance("SSL");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
ctx.init(null, new TrustManager[]{tm}, null);
return new Client.Default(ctx.getSocketFactory(), (hostname, session) -> true);
} catch (Exception e) {
return null;
}
}
}

View File

@@ -0,0 +1,21 @@
package org.wfc.omada.api.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @description: omada配置属性
* @author: caiyuchao
* @date: 2024-11-21
*/
@Data
@Component
@ConfigurationProperties(prefix = "omada")
public class OmadaProperties {
private String omadaUrl;
private String omadacId;
private String clientId;
private String clientSecret;
}

View File

@@ -0,0 +1,153 @@
package org.wfc.omada.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
/**
* @description: RestTemplate配置
* @author: caiyuchao
* @date: 2024-11-20
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
//支持https
public static class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
// SSLSocketFactory用于创建 SSLSockets
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
// 返回默认启用的密码套件。除非一个列表启用对SSL连接的握手会使用这些密码套件。
// 这些默认的服务的最低质量要求保密保护和服务器身份验证
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
// 返回的密码套件可用于SSL连接启用的名字
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(final Socket socket, final String host, final int port,
final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress,
final int localPort) throws
IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
final int localPort) throws
IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2"});
return socket;
}
}
}
}

View File

@@ -0,0 +1,23 @@
package org.wfc.omada.api.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* @description: 授权码VO
* @author: caiyuchao
* @date: 2024-11-21
*/
@Data
public class AuthorizeTokenVO implements Serializable {
private static final long serialVersionUID = 1L;
private String accessToken;
private String refreshToken;
private String tokenType;
private Integer expiresIn;
}

View File

@@ -0,0 +1,22 @@
package org.wfc.omada.api.domain.vo;
import lombok.Data;
import java.io.Serializable;
/**
* @description: 结果VO
* @author: caiyuchao
* @date: 2024-11-21
*/
@Data
public class OmadaResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
private Integer errorCode;
private String msg;
private T result;
}

View File

@@ -0,0 +1 @@
org.wfc.omada.api.config.OmadaProperties

View File

@@ -0,0 +1,6 @@
# Omada 配置
omada:
omada-url: 'https://192.168.2.248:8043'
omadac-id: 'f3aa6e479b94222581523710cc2c2a9d'
client-id: '5036e77c81a74008821c694a715fe2b8'
client-secret: '29faa06fb7f244b094377b48eb3083a7'

View File

@@ -113,6 +113,18 @@
<artifactId>swagger-annotations</artifactId> <artifactId>swagger-annotations</artifactId>
</dependency> </dependency>
<!-- hutool工具 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- lombok工具 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>