diff --git a/docker/start-frontend.sh b/docker/start-frontend.sh
index 300a715..567db7a 100755
--- a/docker/start-frontend.sh
+++ b/docker/start-frontend.sh
@@ -2,6 +2,7 @@
cd ../../fe.wfc/
git pull
+pnpm i
pnpm build
cd ../be.wfc/docker/
./copy.sh
diff --git a/pom.xml b/pom.xml
index 1108e2b..e4079d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,8 @@
8.2.2
4.1.2
2.14.4
+ 5.8.33
+ 1.18.36
@@ -152,6 +154,20 @@
${transmittable-thread-local.version}
+
+
+ cn.hutool
+ hutool-all
+ ${hutool.version}
+
+
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
org.wfc
@@ -222,6 +238,13 @@
${wfc.version}
+
+
+ org.wfc
+ wfc-api-omada
+ ${wfc.version}
+
+
@@ -340,5 +363,15 @@
wfc-prod
+
+
+ cyc
+
+
+ cyc
+ 192.168.2.248:8848
+ wfc-cyc
+
+
\ No newline at end of file
diff --git a/wfc-api/pom.xml b/wfc-api/pom.xml
index 1b57099..664960e 100644
--- a/wfc-api/pom.xml
+++ b/wfc-api/pom.xml
@@ -10,6 +10,7 @@
wfc-api-system
+ wfc-api-omada
wfc-api
diff --git a/wfc-api/wfc-api-omada/pom.xml b/wfc-api/wfc-api-omada/pom.xml
new file mode 100644
index 0000000..636c3d9
--- /dev/null
+++ b/wfc-api/wfc-api-omada/pom.xml
@@ -0,0 +1,30 @@
+
+
+ org.wfc
+ wfc-api
+ 3.6.4
+
+ 4.0.0
+
+ wfc-api-omada
+
+
+ wfc-api-omada接口模块
+
+
+
+
+
+
+ org.wfc
+ wfc-common-core
+
+
+
+ org.wfc
+ wfc-common-redis
+
+
+
+
diff --git a/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/RemoteOmadaFeign.java b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/RemoteOmadaFeign.java
new file mode 100644
index 0000000..09abcdb
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/RemoteOmadaFeign.java
@@ -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);
+
+}
diff --git a/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/FeignConfig.java b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/FeignConfig.java
new file mode 100644
index 0000000..3e6464f
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/FeignConfig.java
@@ -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 body = new HashMap<>();
+ body.put(CLIENT_ID, omadaProperties.getClientId());
+ body.put(CLIENT_SECRET, omadaProperties.getClientSecret());
+ LinkedMultiValueMap 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 request = new HttpEntity<>(JSON.toJSONString(body));
+ String uriString = UriComponentsBuilder.fromHttpUrl(url).queryParams(query).build().toUriString();
+ // 发送调用请求
+ RestTemplate restTemplate = new RestTemplate(new RestTemplateConfig.HttpsClientRequestFactory());
+ ResponseEntity> responseEntity = restTemplate.exchange(uriString, HttpMethod.POST,
+ request, new ParameterizedTypeReference>() {
+ });
+ OmadaResult 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);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/FeignHttpsConfig.java b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/FeignHttpsConfig.java
new file mode 100644
index 0000000..7af777d
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/FeignHttpsConfig.java
@@ -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;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/OmadaProperties.java b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/OmadaProperties.java
new file mode 100644
index 0000000..5a38425
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/OmadaProperties.java
@@ -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;
+}
diff --git a/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/RestTemplateConfig.java b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/RestTemplateConfig.java
new file mode 100644
index 0000000..16886ea
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/config/RestTemplateConfig.java
@@ -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;
+ }
+ }
+ }
+}
diff --git a/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/domain/vo/AuthorizeTokenVO.java b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/domain/vo/AuthorizeTokenVO.java
new file mode 100644
index 0000000..93d9415
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/domain/vo/AuthorizeTokenVO.java
@@ -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;
+}
diff --git a/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/domain/vo/OmadaResult.java b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/domain/vo/OmadaResult.java
new file mode 100644
index 0000000..0e0c9be
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/java/org/wfc/omada/api/domain/vo/OmadaResult.java
@@ -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 implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private Integer errorCode;
+
+ private String msg;
+
+ private T result;
+}
diff --git a/wfc-api/wfc-api-omada/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/wfc-api/wfc-api-omada/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..943069e
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.wfc.omada.api.config.OmadaProperties
\ No newline at end of file
diff --git a/wfc-api/wfc-api-omada/src/main/resources/application.yml b/wfc-api/wfc-api-omada/src/main/resources/application.yml
new file mode 100644
index 0000000..f74e073
--- /dev/null
+++ b/wfc-api/wfc-api-omada/src/main/resources/application.yml
@@ -0,0 +1,6 @@
+# Omada 配置
+omada:
+ omada-url: 'https://192.168.2.248:8043'
+ omadac-id: 'f3aa6e479b94222581523710cc2c2a9d'
+ client-id: '5036e77c81a74008821c694a715fe2b8'
+ client-secret: '29faa06fb7f244b094377b48eb3083a7'
diff --git a/wfc-common/wfc-common-core/pom.xml b/wfc-common/wfc-common-core/pom.xml
index d57f2c6..f5689fe 100644
--- a/wfc-common/wfc-common-core/pom.xml
+++ b/wfc-common/wfc-common-core/pom.xml
@@ -113,6 +113,18 @@
swagger-annotations
+
+
+ cn.hutool
+ hutool-all
+
+
+
+
+ org.projectlombok
+ lombok
+
+