enjoy!!!
@@ -0,0 +1,37 @@
|
|||||||
|
HELP.md
|
||||||
|
target/
|
||||||
|
logs/
|
||||||
|
.claude/
|
||||||
|
CLAUDE.md
|
||||||
|
ui.md
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM maven:3-openjdk-8 as build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN mvn clean package
|
||||||
|
|
||||||
|
FROM openjdk:8-jre-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /app/target/QiuMo-Jetbrains-Help.jar QiuMo-Jetbrains-Help.jar
|
||||||
|
ENV TZ=Asia/Shanghai
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
EXPOSE 10768
|
||||||
|
ENTRYPOINT ["java", "-jar", "QiuMo-Jetbrains-Help.jar"]
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# QiuMo JetBrains Help
|
||||||
|
|
||||||
|
一个基于Spring Boot的JetBrains许可证激活服务项目。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 模拟JetBrains官方许可证服务器
|
||||||
|
- 生成产品和插件激活码
|
||||||
|
- 提供JRebel许可证服务
|
||||||
|
- 集成ja-netfilter代理工具下载
|
||||||
|
- 现代化Web界面
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
|
||||||
|
- Java 1.8+
|
||||||
|
- Maven 3.x
|
||||||
|
|
||||||
|
### 运行应用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 克隆项目
|
||||||
|
git clone <repository-url>
|
||||||
|
cd QiuMo-Jetbrains-Help
|
||||||
|
|
||||||
|
# 编译项目
|
||||||
|
mvn clean compile
|
||||||
|
|
||||||
|
# 启动应用
|
||||||
|
mvn spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
应用启动后访问:http://localhost:10768
|
||||||
|
|
||||||
|
## 使用说明
|
||||||
|
|
||||||
|
1. **下载代理工具**:访问 `/ja-netfilter` 下载并配置ja-netfilter
|
||||||
|
2. **激活码方式**:在Web界面生成产品或插件激活码
|
||||||
|
3. **服务器方式**:配置许可证服务器地址为应用根地址
|
||||||
|
4. **JRebel激活**:使用 `{服务器地址}/{uuid}` 格式
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **后端**:Spring Boot 2.6.13, Java 1.8
|
||||||
|
- **前端**:Vue.js 3, TailwindCSS
|
||||||
|
- **构建**:Maven
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
本项目仅供学习交流使用。
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
qiumo-jetbrains-help:
|
||||||
|
build: .
|
||||||
|
image: qiumo-jetbrains-help:latest
|
||||||
|
container_name: QiuMo-Jetbrains-Help
|
||||||
|
ports:
|
||||||
|
- 10768:10768
|
||||||
|
volumes:
|
||||||
|
- ./external:/app/external
|
||||||
|
- ./logs:/app/logs
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<!-- 项目基本信息 -->
|
||||||
|
<groupId>com.qiumo</groupId>
|
||||||
|
<artifactId>QiuMo-Jetbrains-Help</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>QiuMo-Jetbrains-Help</name>
|
||||||
|
<description>QiuMo Jetbrains Help Application</description>
|
||||||
|
|
||||||
|
<!-- 版本属性管理 -->
|
||||||
|
<properties>
|
||||||
|
<java.version>1.8</java.version>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
|
||||||
|
<!-- 框架版本 -->
|
||||||
|
<spring-boot.version>2.6.13</spring-boot.version>
|
||||||
|
|
||||||
|
<!-- 依赖版本 -->
|
||||||
|
<lombok.version>1.18.24</lombok.version>
|
||||||
|
<hutool.version>5.8.11</hutool.version>
|
||||||
|
<bouncycastle.version>1.78</bouncycastle.version>
|
||||||
|
<logback.version>1.2.12</logback.version>
|
||||||
|
|
||||||
|
<!-- 插件版本 -->
|
||||||
|
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||||
|
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<!-- 依赖管理 -->
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<!-- 项目依赖 -->
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot Starter -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 日志依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok - 简化Java代码 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Hutool - Java工具类库 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>${hutool.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- BouncyCastle - 加密库 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk18on</artifactId>
|
||||||
|
<version>${bouncycastle.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk18on</artifactId>
|
||||||
|
<version>${bouncycastle.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jansi - 控制台彩色输出支持 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.fusesource.jansi</groupId>
|
||||||
|
<artifactId>jansi</artifactId>
|
||||||
|
<version>2.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 测试依赖 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<!-- 构建配置 -->
|
||||||
|
<build>
|
||||||
|
<finalName>${artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<!-- Maven编译插件 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven-compiler-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
<encoding>${project.build.sourceEncoding}</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Spring Boot Maven插件 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>com.qiumo.help.QiuMoJetbrainsHelpApplication</mainClass>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Maven测试插件 -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven-surefire-plugin.version}</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package com.qiumo.help;
|
||||||
|
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.thread.ThreadUtil;
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.qiumo.help.context.AgentContextHolder;
|
||||||
|
import com.qiumo.help.context.CertificateContextHolder;
|
||||||
|
import com.qiumo.help.context.PluginsContextHolder;
|
||||||
|
import com.qiumo.help.context.ProductsContextHolder;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* QiuMo JetBrains Help 应用程序主入口类
|
||||||
|
*
|
||||||
|
* <p>这是一个Spring Boot应用程序,用于提供JetBrains产品的许可证激活服务。
|
||||||
|
* 该应用程序主要功能包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>JetBrains产品许可证生成和验证</li>
|
||||||
|
* <li>许可证服务器模拟(兼容JetBrains官方许可证服务器协议)</li>
|
||||||
|
* <li>产品和插件信息管理</li>
|
||||||
|
* <li>证书和密钥管理</li>
|
||||||
|
* <li>ja-netfilter代理工具配置</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>应用启动后将在指定端口(默认10768)提供HTTP服务,用于处理许可证相关请求。
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j(topic = "项目入口")
|
||||||
|
@EnableScheduling // 启用Spring任务调度功能
|
||||||
|
@Import(SpringUtil.class) // 导入Hutool的Spring工具类
|
||||||
|
@SpringBootApplication
|
||||||
|
public class QiuMoJetbrainsHelpApplication {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用程序主入口方法
|
||||||
|
*
|
||||||
|
* @param args 命令行参数
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 启动Spring Boot应用程序
|
||||||
|
SpringApplication.run(QiuMoJetbrainsHelpApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用程序准备就绪事件监听器
|
||||||
|
*
|
||||||
|
* <p>当Spring Boot应用程序完全启动并准备好接收请求时触发此方法。
|
||||||
|
* 该方法负责初始化所有必要的上下文组件和输出启动成功信息。
|
||||||
|
*
|
||||||
|
* <p>初始化顺序说明:
|
||||||
|
* <ol>
|
||||||
|
* <li>产品上下文 - 初始化JetBrains产品信息</li>
|
||||||
|
* <li>插件上下文 - 初始化JetBrains插件信息</li>
|
||||||
|
* <li>证书上下文 - 初始化RSA密钥对和X.509证书</li>
|
||||||
|
* <li>代理上下文 - 初始化ja-netfilter代理配置</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @throws Exception 当初始化过程中发生错误时抛出异常
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
@EventListener(ApplicationReadyEvent.class)
|
||||||
|
public void ready() {
|
||||||
|
log.info("开始初始化应用程序组件...");
|
||||||
|
|
||||||
|
// 按顺序初始化各个上下文组件
|
||||||
|
ProductsContextHolder.init(); // 初始化产品信息上下文
|
||||||
|
PluginsContextHolder.init(); // 初始化插件信息上下文
|
||||||
|
CertificateContextHolder.init(); // 初始化证书和密钥上下文
|
||||||
|
AgentContextHolder.init(); // 初始化ja-netfilter代理上下文
|
||||||
|
|
||||||
|
// 获取本地IP地址和端口号,构建访问URL
|
||||||
|
InetAddress localHost = InetAddress.getLocalHost();
|
||||||
|
String serverPort = SpringUtil.getProperty("server.port");
|
||||||
|
String accessUrl = CharSequenceUtil.format("http://{}:{}", localHost.getHostAddress(), serverPort);
|
||||||
|
|
||||||
|
// 输出应用启动成功信息
|
||||||
|
String startupSuccessMessage = buildStartupSuccessMessage(accessUrl);
|
||||||
|
log.info(startupSuccessMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时刷新插件信息任务
|
||||||
|
*
|
||||||
|
* <p>每天中午12点执行一次,从JetBrains官网获取最新的插件信息。
|
||||||
|
* 此任务采用异步执行方式,不会阻塞应用程序的正常运行。
|
||||||
|
*
|
||||||
|
* <p>Cron表达式说明:"0 0 12 * * ?"
|
||||||
|
* <ul>
|
||||||
|
* <li>0 - 秒(第0秒)</li>
|
||||||
|
* <li>0 - 分钟(第0分钟)</li>
|
||||||
|
* <li>12 - 小时(12点)</li>
|
||||||
|
* <li>* - 日期(每一天)</li>
|
||||||
|
* <li>* - 月份(每个月)</li>
|
||||||
|
* <li>? - 星期(任意)</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Scheduled(cron = "0 0 12 * * ?")
|
||||||
|
public void refresh() {
|
||||||
|
log.info("开始执行定时刷新插件信息任务...");
|
||||||
|
// 异步执行插件信息刷新任务,避免阻塞主线程
|
||||||
|
ThreadUtil.execute(PluginsContextHolder::refreshJsonFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建应用启动成功消息
|
||||||
|
*
|
||||||
|
* @param accessUrl 应用访问地址
|
||||||
|
* @return 格式化的启动成功消息
|
||||||
|
*/
|
||||||
|
private String buildStartupSuccessMessage(String accessUrl) {
|
||||||
|
return "\n" +
|
||||||
|
"============================================================================================\n" +
|
||||||
|
"= QiuMo-Jetbrains-Help 启动成功! 🎉 =\n" +
|
||||||
|
"= =\n" +
|
||||||
|
String.format("= 访问地址: %-70s =\n", accessUrl) +
|
||||||
|
"= =\n" +
|
||||||
|
"= 功能说明: =\n" +
|
||||||
|
"= • 生成JetBrains产品许可证 =\n" +
|
||||||
|
"= • 模拟许可证服务器 =\n" +
|
||||||
|
"= • 下载ja-netfilter代理工具 =\n" +
|
||||||
|
"= =\n" +
|
||||||
|
"============================================================================================\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨域资源共享(CORS)配置
|
||||||
|
*
|
||||||
|
* <p>为了支持前端页面调用后端 API,需要配置 CORS 策略。
|
||||||
|
* 这个配置允许来自任何域名的请求访问 API。
|
||||||
|
*
|
||||||
|
* @return WebMvc配置器,用于设置跨域访问规则
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public WebMvcConfigurer corsConfigurer() {
|
||||||
|
return new WebMvcConfigurer() {
|
||||||
|
@Override
|
||||||
|
public void addCorsMappings(CorsRegistry registry) {
|
||||||
|
registry.addMapping("/**")
|
||||||
|
.allowedOrigins("*") // 允许所有来源
|
||||||
|
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的HTTP方法
|
||||||
|
.allowedHeaders("*") // 允许所有请求头
|
||||||
|
.maxAge(3600); // 预检请求的有效期(1小时)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package com.qiumo.help.context;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.util.HexUtil;
|
||||||
|
import cn.hutool.core.util.ZipUtil;
|
||||||
|
import cn.hutool.crypto.KeyUtil;
|
||||||
|
import cn.hutool.crypto.PemUtil;
|
||||||
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
|
import com.qiumo.help.util.FileTools;
|
||||||
|
import java.io.File;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bouncycastle.asn1.ASN1Encodable;
|
||||||
|
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||||
|
import org.bouncycastle.asn1.DERNull;
|
||||||
|
import org.bouncycastle.asn1.DEROctetString;
|
||||||
|
import org.bouncycastle.asn1.DERSequence;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ja-netfilter代理上下文管理器
|
||||||
|
*
|
||||||
|
* <p>此类负责管理ja-netfilter代理工具的初始化、配置和打包。
|
||||||
|
* ja-netfilter是一个基于Java Instrumentation API实现的动态字节码修改工具,
|
||||||
|
* 主要用于在应用程序运行时动态修改字节码,实现特定的功能。
|
||||||
|
*
|
||||||
|
* <p>主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>自动解压ja-netfilter工具包</li>
|
||||||
|
* <li>生成并配置power.conf配置文件</li>
|
||||||
|
* <li>生成适用于JetBrains产品的代理规则</li>
|
||||||
|
* <li>重新打包配置好的ja-netfilter工具</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>配置规则生成:
|
||||||
|
* <ul>
|
||||||
|
* <li>激活码规则:基于授权码证书和密钥生成</li>
|
||||||
|
* <li>许可证服务器规则:基于服务器证书和密钥生成</li>
|
||||||
|
* <li>使用RSA密码学算法和ASN.1编码</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>文件结构:
|
||||||
|
* <pre>
|
||||||
|
* external/agent/
|
||||||
|
* ├── ja-netfilter.zip // 原始工具包
|
||||||
|
* └── ja-netfilter/ // 解压后的目录
|
||||||
|
* ├── ja-netfilter.jar // 主程序
|
||||||
|
* ├── plugins/ // 插件目录
|
||||||
|
* └── config/ // 配置目录
|
||||||
|
* └── power.conf // 主配置文件
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>使用方法:
|
||||||
|
* <ol>
|
||||||
|
* <li>下载并解压ja-netfilter工具包</li>
|
||||||
|
* <li>在JVM启动参数中添加:{@code -javaagent:path/to/ja-netfilter.jar}</li>
|
||||||
|
* <li>重新启动目标应用程序</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j(topic = "代理上下文")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class AgentContextHolder {
|
||||||
|
|
||||||
|
// ==================== 常量定义 ====================
|
||||||
|
|
||||||
|
/** ja-netfilter工具文件夹路径 */
|
||||||
|
private static final String JA_NETFILTER_FILE_PATH = "external/agent/ja-netfilter";
|
||||||
|
|
||||||
|
/** ja-netfilter主配置文件路径 */
|
||||||
|
private static final String POWER_CONF_FILE_NAME = JA_NETFILTER_FILE_PATH + "/config/power.conf";
|
||||||
|
|
||||||
|
// ==================== 静态字段 ====================
|
||||||
|
|
||||||
|
/** ja-netfilter工具文件夹对象 */
|
||||||
|
private static File jaNetfilterFile;
|
||||||
|
|
||||||
|
/** ja-netfilter ZIP包文件对象 */
|
||||||
|
private static File jaNetfilterZipFile;
|
||||||
|
|
||||||
|
// ==================== 核心方法 ====================
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
log.info("初始化中...");
|
||||||
|
jaNetfilterZipFile = FileTools.getFileOrCreat(JA_NETFILTER_FILE_PATH + ".zip");
|
||||||
|
if (!FileTools.fileExists(JA_NETFILTER_FILE_PATH)) {
|
||||||
|
unzipJaNetfilter();
|
||||||
|
if (!powerConfHasInit()) {
|
||||||
|
log.info("配置初始化中...");
|
||||||
|
loadPowerConf();
|
||||||
|
zipJaNetfilter();
|
||||||
|
log.info("配置初始化成功!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("初始化成功!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File jaNetfilterZipFile() {
|
||||||
|
return AgentContextHolder.jaNetfilterZipFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void unzipJaNetfilter() {
|
||||||
|
jaNetfilterFile = ZipUtil.unzip(jaNetfilterZipFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void zipJaNetfilter() {
|
||||||
|
jaNetfilterZipFile = ZipUtil.zip(jaNetfilterFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean powerConfHasInit() {
|
||||||
|
File powerConfFile = FileTools.getFileOrCreat(POWER_CONF_FILE_NAME);
|
||||||
|
String powerConfStr;
|
||||||
|
try {
|
||||||
|
powerConfStr = IoUtil.readUtf8(FileUtil.getInputStream(powerConfFile));
|
||||||
|
} catch (IORuntimeException e) {
|
||||||
|
throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件读取失败!", POWER_CONF_FILE_NAME), e);
|
||||||
|
}
|
||||||
|
return CharSequenceUtil.containsAll(powerConfStr, "[Result]", "EQUAL,");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadPowerConf() {
|
||||||
|
CompletableFuture
|
||||||
|
.supplyAsync(AgentContextHolder::generateCodePowerConfigRule)
|
||||||
|
.thenApply(AgentContextHolder::generateServerPowerConfigRule)
|
||||||
|
.thenApply(AgentContextHolder::generatePowerConfigStr)
|
||||||
|
.thenAccept(AgentContextHolder::overridePowerConfFileContent)
|
||||||
|
.exceptionally(throwable -> {
|
||||||
|
log.error("配置初始化失败!", throwable);
|
||||||
|
return null;
|
||||||
|
}).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private static String generateCodePowerConfigRule() {
|
||||||
|
X509Certificate crt = (X509Certificate) KeyUtil.readX509Certificate(
|
||||||
|
IoUtil.toStream(CertificateContextHolder.codeCrtFile()));
|
||||||
|
RSAPublicKey publicKey = (RSAPublicKey) PemUtil.readPemPublicKey(
|
||||||
|
IoUtil.toStream(CertificateContextHolder.publicKeyFile()));
|
||||||
|
RSAPublicKey rootPublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(
|
||||||
|
IoUtil.toStream(CertificateContextHolder.codeRootKeyFile()));
|
||||||
|
BigInteger x = new BigInteger(1, crt.getSignature());
|
||||||
|
BigInteger y = BigInteger.valueOf(65537L);
|
||||||
|
BigInteger z = rootPublicKey.getModulus();
|
||||||
|
BigInteger r = x.modPow(publicKey.getPublicExponent(), publicKey.getModulus());
|
||||||
|
return CharSequenceUtil.format("; QiuMo Activation Code \nEQUAL,{},{},{}->{}", x, y, z, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private static String generateServerPowerConfigRule(String ruleValue) {
|
||||||
|
X509Certificate crt = (X509Certificate) KeyUtil.readX509Certificate(
|
||||||
|
IoUtil.toStream(CertificateContextHolder.serverChildCrtFile()));
|
||||||
|
RSAPublicKey rootPublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(
|
||||||
|
IoUtil.toStream(CertificateContextHolder.serverRootKeyFile()));
|
||||||
|
BigInteger x = new BigInteger(1, crt.getSignature());
|
||||||
|
BigInteger y = BigInteger.valueOf(65537L);
|
||||||
|
BigInteger z = rootPublicKey.getModulus();
|
||||||
|
byte[] tbsCertificate = crt.getTBSCertificate();
|
||||||
|
// 1、证书sha256摘要结果
|
||||||
|
byte[] bytes = DigestUtil.sha256(tbsCertificate);
|
||||||
|
String sha256Str = HexUtil.encodeHexStr(bytes);
|
||||||
|
// 2、计算的结果转换为ASN1格式数据
|
||||||
|
String transit = convertDataToASN1Format(sha256Str);
|
||||||
|
// 3、ASN1格式数据再进行填充
|
||||||
|
String fillingStr = filling512(transit);
|
||||||
|
// 4、填充后的数据转换为BigInteger数据,BigInteger输出的结果就是规则中替换的结果。
|
||||||
|
BigInteger r = new BigInteger(HexUtil.decodeHex(fillingStr));
|
||||||
|
return CharSequenceUtil.format("{}\n; QiuMo License Server \nEQUAL,{},{},{}->{}", ruleValue,x, y, z, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static String generatePowerConfigStr(String ruleValue) {
|
||||||
|
return CharSequenceUtil.builder("[Result]", "\n", ruleValue).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void overridePowerConfFileContent(String configStr) {
|
||||||
|
File powerConfFile = FileTools.getFileOrCreat(POWER_CONF_FILE_NAME);
|
||||||
|
try {
|
||||||
|
FileUtil.writeString(configStr, powerConfFile, StandardCharsets.UTF_8);
|
||||||
|
} catch (IORuntimeException e) {
|
||||||
|
throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件写入失败!", POWER_CONF_FILE_NAME), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 证书sha256摘要结果转换为ASN1格式数据
|
||||||
|
*
|
||||||
|
* @param sha256Data 证书sha256摘要结果
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String convertDataToASN1Format(String sha256Data) throws Exception {
|
||||||
|
// 构建内层 SEQUENCE
|
||||||
|
ASN1ObjectIdentifier algorithmOid = new ASN1ObjectIdentifier("2.16.840.1.101.3.4.2.1"); // sha-256
|
||||||
|
ASN1Encodable[] innerSequenceElements = {algorithmOid, DERNull.INSTANCE};
|
||||||
|
DERSequence innerSequence = new DERSequence(innerSequenceElements);
|
||||||
|
|
||||||
|
// 构建外层 SEQUENCE
|
||||||
|
byte[] octetStringBytes = Hex.decode(sha256Data);
|
||||||
|
ASN1Encodable octetString = new DEROctetString(octetStringBytes);
|
||||||
|
ASN1Encodable[] outerSequenceElements = {innerSequence, octetString};
|
||||||
|
DERSequence outerSequence = new DERSequence(outerSequenceElements);
|
||||||
|
|
||||||
|
// 将ASN.1结构编码为DER格式
|
||||||
|
byte[] encodedData = outerSequence.getEncoded();
|
||||||
|
|
||||||
|
// 将字节数组转换为十六进制字符串
|
||||||
|
String hexString = Hex.toHexString(encodedData);
|
||||||
|
return hexString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String filling512(String target) {
|
||||||
|
return filling(target, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String filling256(String target) {
|
||||||
|
return filling(target, 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String filling(String target, int length) {
|
||||||
|
int count = length - target.length() / 2 - 3;
|
||||||
|
StringBuilder strBuilder = new StringBuilder();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
strBuilder.append("ff");
|
||||||
|
}
|
||||||
|
return ("01" + strBuilder + "00" + target).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,549 @@
|
|||||||
|
package com.qiumo.help.context;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateField;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.crypto.KeyUtil;
|
||||||
|
import cn.hutool.crypto.PemUtil;
|
||||||
|
import cn.hutool.crypto.SecureUtil;
|
||||||
|
import com.qiumo.help.util.FileTools;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name;
|
||||||
|
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
||||||
|
import org.bouncycastle.asn1.x509.Extension;
|
||||||
|
import org.bouncycastle.asn1.x509.GeneralName;
|
||||||
|
import org.bouncycastle.asn1.x509.GeneralNames;
|
||||||
|
import org.bouncycastle.asn1.x509.GeneralNamesBuilder;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
||||||
|
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||||
|
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||||
|
import org.bouncycastle.cert.CertIOException;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||||
|
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||||
|
import org.bouncycastle.operator.ContentSigner;
|
||||||
|
import org.bouncycastle.operator.OperatorCreationException;
|
||||||
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 证书上下文持有者 - 负责管理RSA密钥对和X.509证书的生成、存储和访问
|
||||||
|
*
|
||||||
|
* <p>此类作为证书管理的核心工具,提供以下主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>自动生成RSA密钥对(支持2048位和4096位)</li>
|
||||||
|
* <li>生成自签名的X.509证书</li>
|
||||||
|
* <li>管理证书文件的存储和访问</li>
|
||||||
|
* <li>支持CA证书链的构建</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>使用场景:
|
||||||
|
* 主要用于JetBrains产品的许可证服务器搭建,生成必要的证书文件用于许可证验证。
|
||||||
|
*
|
||||||
|
* <p>证书体系结构:
|
||||||
|
* <ul>
|
||||||
|
* <li>授权码签名CA证书:用于授权码签名验证</li>
|
||||||
|
* <li>服务器CA证书:用于HTTPS通信</li>
|
||||||
|
* <li>服务器子证书:具体域名的服务器证书</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
@Slf4j(topic = "证书上下文")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class CertificateContextHolder {
|
||||||
|
|
||||||
|
// ==================== 常量定义 ====================
|
||||||
|
|
||||||
|
/** 授权码根密钥文件路径 */
|
||||||
|
private static final String CODE_ROOT_KEY_FILE_NAME = "external/certificate/code-root.key";
|
||||||
|
|
||||||
|
/** 服务器根密钥文件路径 */
|
||||||
|
private static final String SERVER_ROOT_KEY_FILE_NAME = "external/certificate/server-root.key";
|
||||||
|
|
||||||
|
/** 4096位私钥文件路径 */
|
||||||
|
private static final String PRIVATE_KEY_FILE_NAME = "external/certificate/private.key";
|
||||||
|
|
||||||
|
/** 4096位公钥文件路径 */
|
||||||
|
private static final String PUBLIC_KEY_FILE_NAME = "external/certificate/public.key";
|
||||||
|
|
||||||
|
/** 授权码证书文件路径 */
|
||||||
|
private static final String CODE_CET_FILE_NAME = "external/certificate/code-ca.crt";
|
||||||
|
|
||||||
|
/** 服务器证书文件路径 */
|
||||||
|
private static final String SERVER_CET_FILE_NAME = "external/certificate/server-ca.crt";
|
||||||
|
|
||||||
|
/** 服务器子证书文件路径 */
|
||||||
|
private static final String SERVER_CHILD_CET_FILE_NAME = "external/certificate/server-child-ca.crt";
|
||||||
|
|
||||||
|
/** 证书有效期(年) */
|
||||||
|
private static final int CERTIFICATE_VALIDITY_YEARS = 100;
|
||||||
|
|
||||||
|
/** RSA签名算法 */
|
||||||
|
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
|
||||||
|
|
||||||
|
/** BouncyCastle提供者标识 */
|
||||||
|
private static final String BC_PROVIDER = "BC";
|
||||||
|
|
||||||
|
/** RSA加密算法标识 */
|
||||||
|
private static final String RSA_ALGORITHM = "RSA";
|
||||||
|
|
||||||
|
/** 授权码签名CA的DN */
|
||||||
|
private static final String CODE_CA_DN = "CN=JetProfile CA";
|
||||||
|
|
||||||
|
/** 许可证服务器CA的DN */
|
||||||
|
private static final String LICENSE_SERVER_CA_DN = "CN=License Servers CA";
|
||||||
|
|
||||||
|
/** 应用程序主体DN */
|
||||||
|
private static final String APP_SUBJECT_DN = "CN=QiuMo-Jetbrains-Help";
|
||||||
|
|
||||||
|
/** 许可证服务器域名 */
|
||||||
|
private static final String LICENSE_SERVER_DOMAIN = "qiumo.lsrv.jetbrains.com";
|
||||||
|
|
||||||
|
/** 许可证服务器主体DN */
|
||||||
|
private static final String LICENSE_SERVER_SUBJECT_DN = "CN=" + LICENSE_SERVER_DOMAIN;
|
||||||
|
|
||||||
|
// ==================== 静态文件对象 ====================
|
||||||
|
|
||||||
|
/** 授权码根密钥文件对象 */
|
||||||
|
private static File codeRootKeyFile;
|
||||||
|
|
||||||
|
/** 服务器根密钥文件对象 */
|
||||||
|
private static File serverRootKeyFile;
|
||||||
|
|
||||||
|
/** 4096位私钥文件对象 */
|
||||||
|
private static File privateKeyFile;
|
||||||
|
|
||||||
|
/** 4096位公钥文件对象 */
|
||||||
|
private static File publicKeyFile;
|
||||||
|
|
||||||
|
/** 授权码证书文件对象 */
|
||||||
|
private static File codeCrtFile;
|
||||||
|
|
||||||
|
/** 服务器证书文件对象 */
|
||||||
|
private static File serverCrtFile;
|
||||||
|
|
||||||
|
/** 服务器子证书文件对象 */
|
||||||
|
private static File serverChildCrtFile;
|
||||||
|
|
||||||
|
// ==================== 初始化方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化证书上下文
|
||||||
|
*
|
||||||
|
* <p>此方法执行以下操作:
|
||||||
|
* <ol>
|
||||||
|
* <li>创建根密钥文件(如果不存在)</li>
|
||||||
|
* <li>检查所需的密钥和证书文件是否存在</li>
|
||||||
|
* <li>如果文件缺失,自动生成完整的证书体系</li>
|
||||||
|
* <li>初始化所有文件对象引用</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @throws RuntimeException 当文件操作或证书生成失败时抛出
|
||||||
|
*/
|
||||||
|
public static void init() {
|
||||||
|
log.info("证书上下文初始化开始...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 初始化根密钥文件
|
||||||
|
initializeRootKeyFiles();
|
||||||
|
|
||||||
|
// 检查并生成必要的证书文件
|
||||||
|
if (isCertificateGenerationRequired()) {
|
||||||
|
log.info("检测到证书文件缺失,开始生成证书...");
|
||||||
|
generateCertificate();
|
||||||
|
log.info("证书生成完成!");
|
||||||
|
} else {
|
||||||
|
log.info("证书文件已存在,直接加载...");
|
||||||
|
initializeExistingFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("证书上下文初始化成功!");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("证书上下文初始化失败", e);
|
||||||
|
throw new RuntimeException("证书初始化异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化根密钥文件
|
||||||
|
* 创建授权码根密钥和服务器根密钥文件(如果不存在)
|
||||||
|
*/
|
||||||
|
private static void initializeRootKeyFiles() {
|
||||||
|
codeRootKeyFile = FileTools.getFileOrCreat(CODE_ROOT_KEY_FILE_NAME);
|
||||||
|
serverRootKeyFile = FileTools.getFileOrCreat(SERVER_ROOT_KEY_FILE_NAME);
|
||||||
|
log.debug("根密钥文件初始化完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否需要生成证书
|
||||||
|
* 如果任意一个必需文件不存在,则需要重新生成所有证书
|
||||||
|
*
|
||||||
|
* @return 如果任意一个必需文件不存在则返回true
|
||||||
|
*/
|
||||||
|
private static boolean isCertificateGenerationRequired() {
|
||||||
|
return !FileTools.fileExists(PRIVATE_KEY_FILE_NAME)
|
||||||
|
|| !FileTools.fileExists(PUBLIC_KEY_FILE_NAME)
|
||||||
|
|| !FileTools.fileExists(CODE_CET_FILE_NAME)
|
||||||
|
|| !FileTools.fileExists(SERVER_CET_FILE_NAME)
|
||||||
|
|| !FileTools.fileExists(SERVER_CHILD_CET_FILE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化已存在的文件对象
|
||||||
|
* 当所有证书文件都存在时,直接创建文件对象引用
|
||||||
|
*/
|
||||||
|
private static void initializeExistingFiles() {
|
||||||
|
privateKeyFile = FileTools.getFileOrCreat(PRIVATE_KEY_FILE_NAME);
|
||||||
|
publicKeyFile = FileTools.getFileOrCreat(PUBLIC_KEY_FILE_NAME);
|
||||||
|
codeCrtFile = FileTools.getFileOrCreat(CODE_CET_FILE_NAME);
|
||||||
|
serverCrtFile = FileTools.getFileOrCreat(SERVER_CET_FILE_NAME);
|
||||||
|
serverChildCrtFile = FileTools.getFileOrCreat(SERVER_CHILD_CET_FILE_NAME);
|
||||||
|
log.debug("已存在文件对象初始化完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 文件访问方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取授权码根密钥文件对象
|
||||||
|
*
|
||||||
|
* @return 授权码根密钥文件
|
||||||
|
*/
|
||||||
|
public static File codeRootKeyFile() {
|
||||||
|
return codeRootKeyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务器根密钥文件对象
|
||||||
|
*
|
||||||
|
* @return 服务器根密钥文件
|
||||||
|
*/
|
||||||
|
public static File serverRootKeyFile() {
|
||||||
|
return serverRootKeyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取4096位私钥文件对象
|
||||||
|
*
|
||||||
|
* @return 4096位私钥文件
|
||||||
|
*/
|
||||||
|
public static File privateKeyFile() {
|
||||||
|
return privateKeyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取4096位公钥文件对象
|
||||||
|
*
|
||||||
|
* @return 4096位公钥文件
|
||||||
|
*/
|
||||||
|
public static File publicKeyFile() {
|
||||||
|
return publicKeyFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取授权码证书文件对象
|
||||||
|
*
|
||||||
|
* @return 授权码证书文件
|
||||||
|
*/
|
||||||
|
public static File codeCrtFile() {
|
||||||
|
return codeCrtFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务器证书文件对象
|
||||||
|
*
|
||||||
|
* @return 服务器证书文件
|
||||||
|
*/
|
||||||
|
public static File serverCrtFile() {
|
||||||
|
return serverCrtFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取服务器子证书文件对象
|
||||||
|
*
|
||||||
|
* @return 服务器子证书文件
|
||||||
|
*/
|
||||||
|
public static File serverChildCrtFile() {
|
||||||
|
return serverChildCrtFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 证书生成方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成完整的证书体系
|
||||||
|
*
|
||||||
|
* <p>此方法将生成以下内容:
|
||||||
|
* <ul>
|
||||||
|
* <li>4096位RSA密钥对(用于CA证书)</li>
|
||||||
|
* <li>授权码签名CA证书</li>
|
||||||
|
* <li>许可证服务器CA证书</li>
|
||||||
|
* <li>具体域名的服务器证书(包含扩展属性)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @throws RuntimeException 当密钥生成或证书创建失败时抛出
|
||||||
|
*/
|
||||||
|
public static void generateCertificate() {
|
||||||
|
log.info("开始生成证书体系...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 生成密钥对
|
||||||
|
log.debug("正在生成RSA密钥对...");
|
||||||
|
KeyPair keyPair4096 = generateRSAKeyPair(4096);
|
||||||
|
|
||||||
|
// 保存密钥对到文件
|
||||||
|
saveKeyPairToFiles(keyPair4096, PRIVATE_KEY_FILE_NAME, PUBLIC_KEY_FILE_NAME);
|
||||||
|
|
||||||
|
// 生成证书
|
||||||
|
log.debug("正在生成X.509证书...");
|
||||||
|
generateCertificates(keyPair4096);
|
||||||
|
|
||||||
|
log.info("证书体系生成完成");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("证书生成过程中发生错误", e);
|
||||||
|
throw new RuntimeException("证书生成失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成指定长度的RSA密钥对
|
||||||
|
*
|
||||||
|
* @param keySize RSA密钥长度(位)
|
||||||
|
* @return 生成的RSA密钥对
|
||||||
|
* @throws RuntimeException 当密钥生成失败时抛出
|
||||||
|
*/
|
||||||
|
private static KeyPair generateRSAKeyPair(int keySize) {
|
||||||
|
try {
|
||||||
|
log.debug("生成{}位RSA密钥对", keySize);
|
||||||
|
return SecureUtil.generateKeyPair(RSA_ALGORITHM, keySize);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("RSA密钥对生成失败: " + keySize + "位", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将密钥对保存到指定的文件中
|
||||||
|
*
|
||||||
|
* @param keyPair 要保存的密钥对
|
||||||
|
* @param privateKeyPath 私钥文件路径
|
||||||
|
* @param publicKeyPath 公钥文件路径
|
||||||
|
*/
|
||||||
|
private static void saveKeyPairToFiles(KeyPair keyPair, String privateKeyPath, String publicKeyPath) {
|
||||||
|
try {
|
||||||
|
// 保存私钥
|
||||||
|
File privateKeyFileObj = FileTools.getFileOrCreat(privateKeyPath);
|
||||||
|
PemUtil.writePemObject("PRIVATE KEY", keyPair.getPrivate().getEncoded(),
|
||||||
|
FileUtil.getWriter(privateKeyFileObj, StandardCharsets.UTF_8, false));
|
||||||
|
|
||||||
|
// 保存公钥
|
||||||
|
File publicKeyFileObj = FileTools.getFileOrCreat(publicKeyPath);
|
||||||
|
PemUtil.writePemObject("PUBLIC KEY", keyPair.getPublic().getEncoded(),
|
||||||
|
FileUtil.getWriter(publicKeyFileObj, StandardCharsets.UTF_8, false));
|
||||||
|
|
||||||
|
publicKeyFile = publicKeyFileObj;
|
||||||
|
privateKeyFile = privateKeyFileObj;
|
||||||
|
|
||||||
|
log.debug("密钥对已保存: {} 和 {}", privateKeyPath, publicKeyPath);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("密钥对文件保存失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成所有类型的X.509证书
|
||||||
|
*
|
||||||
|
* @param keyPair 4096位密钥对
|
||||||
|
*/
|
||||||
|
private static void generateCertificates(KeyPair keyPair) {
|
||||||
|
try {
|
||||||
|
PrivateKey privateKey4096 = keyPair.getPrivate();
|
||||||
|
PublicKey publicKey4096 = keyPair.getPublic();
|
||||||
|
|
||||||
|
// 创建内容签名器
|
||||||
|
ContentSigner signer = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).build(privateKey4096);
|
||||||
|
JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER);
|
||||||
|
|
||||||
|
// 生成授权码签名CA证书
|
||||||
|
Certificate codeCertificate = generateCodeCACertificate(publicKey4096, signer, certConverter);
|
||||||
|
|
||||||
|
// 生成许可证服务器CA证书
|
||||||
|
Certificate serverCertificate = generateServerCACertificate(publicKey4096, signer, certConverter);
|
||||||
|
|
||||||
|
// 生成服务器子证书(具有特殊扩展)
|
||||||
|
Certificate childCertificate = generateServerChildCertificate(publicKey4096, signer, certConverter);
|
||||||
|
|
||||||
|
// 保存所有证书到文件
|
||||||
|
saveCertificatesToFiles(codeCertificate, serverCertificate, childCertificate);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("X.509证书生成失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成授权码签名CA证书
|
||||||
|
*
|
||||||
|
* @param publicKey 公钥
|
||||||
|
* @param signer 内容签名器
|
||||||
|
* @param certConverter 证书转换器
|
||||||
|
* @return 生成的授权码签名CA证书
|
||||||
|
*/
|
||||||
|
private static Certificate generateCodeCACertificate(PublicKey publicKey, ContentSigner signer,
|
||||||
|
JcaX509CertificateConverter certConverter) throws Exception {
|
||||||
|
JcaX509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
|
||||||
|
new X500Name(CODE_CA_DN), // 颁发者DN
|
||||||
|
BigInteger.valueOf(System.currentTimeMillis()), // 序列号
|
||||||
|
DateUtil.yesterday(), // 生效日期(昨天,避免时钟偏差)
|
||||||
|
DateUtil.date().offset(DateField.YEAR, CERTIFICATE_VALIDITY_YEARS), // 过期日期
|
||||||
|
new X500Name(APP_SUBJECT_DN), // 主体DN
|
||||||
|
SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); // 公钥信息
|
||||||
|
|
||||||
|
return certConverter.getCertificate(certificateBuilder.build(signer));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成许可证服务器CA证书
|
||||||
|
*
|
||||||
|
* @param publicKey 公钥
|
||||||
|
* @param signer 内容签名器
|
||||||
|
* @param certConverter 证书转换器
|
||||||
|
* @return 生成的许可证服务器CA证书
|
||||||
|
*/
|
||||||
|
private static Certificate generateServerCACertificate(PublicKey publicKey, ContentSigner signer,
|
||||||
|
JcaX509CertificateConverter certConverter) throws Exception {
|
||||||
|
JcaX509v3CertificateBuilder serversCertBuilder = new JcaX509v3CertificateBuilder(
|
||||||
|
new X500Name(LICENSE_SERVER_CA_DN), // 颁发者DN
|
||||||
|
BigInteger.valueOf(System.currentTimeMillis()), // 序列号
|
||||||
|
DateUtil.yesterday(), // 生效日期
|
||||||
|
DateUtil.date().offset(DateField.YEAR, CERTIFICATE_VALIDITY_YEARS), // 过期日期
|
||||||
|
new X500Name(APP_SUBJECT_DN), // 主体DN
|
||||||
|
SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())); // 公钥信息
|
||||||
|
|
||||||
|
return certConverter.getCertificate(serversCertBuilder.build(signer));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成服务器子证书(包含扩展属性)
|
||||||
|
*
|
||||||
|
* <p>此证书包含以下扩展属性:
|
||||||
|
* <ul>
|
||||||
|
* <li>扩展密钥用途:服务器认证和客户端认证</li>
|
||||||
|
* <li>主体备用名称:DNS域名</li>
|
||||||
|
* <li>密钥用途:数字签名、密钥加密、密钥协商</li>
|
||||||
|
* <li>权威密钥标识符</li>
|
||||||
|
* <li>主体密钥标识符</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param publicKey4096 4096位公钥(用于权威密钥标识符)
|
||||||
|
* @param signer 内容签名器
|
||||||
|
* @param certConverter 证书转换器
|
||||||
|
* @return 生成的服务器子证书
|
||||||
|
*/
|
||||||
|
private static Certificate generateServerChildCertificate(PublicKey publicKey4096,
|
||||||
|
ContentSigner signer, JcaX509CertificateConverter certConverter) throws Exception {
|
||||||
|
JcaX509v3CertificateBuilder childCertBuilder = new JcaX509v3CertificateBuilder(
|
||||||
|
new X500Name(LICENSE_SERVER_CA_DN), // 颁发者DN
|
||||||
|
BigInteger.valueOf(System.currentTimeMillis()), // 序列号
|
||||||
|
DateUtil.yesterday(), // 生效日期
|
||||||
|
DateUtil.date().offset(DateField.YEAR, CERTIFICATE_VALIDITY_YEARS), // 过期日期
|
||||||
|
new X500Name(LICENSE_SERVER_SUBJECT_DN), // 主体DN(具体域名)
|
||||||
|
SubjectPublicKeyInfo.getInstance(publicKey4096.getEncoded())); // 公钥信息
|
||||||
|
|
||||||
|
// 添加证书扩展
|
||||||
|
addCertificateExtensions(childCertBuilder, publicKey4096);
|
||||||
|
|
||||||
|
return certConverter.getCertificate(childCertBuilder.build(signer));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为服务器子证书添加扩展属性
|
||||||
|
*
|
||||||
|
* @param certBuilder 证书构建器
|
||||||
|
* @param publicKey4096 4096位公钥
|
||||||
|
*/
|
||||||
|
private static void addCertificateExtensions(JcaX509v3CertificateBuilder certBuilder,PublicKey publicKey4096) throws Exception {
|
||||||
|
JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
|
||||||
|
|
||||||
|
// 1. 扩展密钥用途:服务器认证和客户端认证
|
||||||
|
certBuilder.addExtension(Extension.extendedKeyUsage, false,
|
||||||
|
new ExtendedKeyUsage(new KeyPurposeId[]{
|
||||||
|
KeyPurposeId.id_kp_serverAuth, // 服务器认证
|
||||||
|
KeyPurposeId.id_kp_clientAuth // 客户端认证
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 2. 主体备用名称:DNS域名
|
||||||
|
GeneralNamesBuilder generalNamesBuilder = new GeneralNamesBuilder();
|
||||||
|
GeneralName dnsName = new GeneralName(GeneralName.dNSName, LICENSE_SERVER_DOMAIN);
|
||||||
|
generalNamesBuilder.addName(dnsName);
|
||||||
|
GeneralNames generalNames = generalNamesBuilder.build();
|
||||||
|
certBuilder.addExtension(Extension.subjectAlternativeName, false, generalNames);
|
||||||
|
|
||||||
|
// 3. 密钥用途:数字签名、密钥加密、密钥协商
|
||||||
|
certBuilder.addExtension(Extension.keyUsage, true,
|
||||||
|
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.keyAgreement));
|
||||||
|
|
||||||
|
// 4. 权威密钥标识符(基于服务器根证书)
|
||||||
|
X509Certificate templateCert = (X509Certificate) KeyUtil.readX509Certificate(
|
||||||
|
IoUtil.toStream(serverRootKeyFile));
|
||||||
|
certBuilder.addExtension(Extension.authorityKeyIdentifier, false,
|
||||||
|
extUtils.createAuthorityKeyIdentifier(
|
||||||
|
SubjectPublicKeyInfo.getInstance(templateCert.getPublicKey().getEncoded())));
|
||||||
|
|
||||||
|
// 5. 主体密钥标识符(基于2048位公钥)
|
||||||
|
certBuilder.addExtension(Extension.subjectKeyIdentifier, false,
|
||||||
|
extUtils.createSubjectKeyIdentifier(SubjectPublicKeyInfo.getInstance(publicKey4096.getEncoded())));
|
||||||
|
|
||||||
|
log.debug("服务器子证书扩展属性添加完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将生成的证书保存到对应的文件中
|
||||||
|
*
|
||||||
|
* @param codeCertificate 授权码签名CA证书
|
||||||
|
* @param serverCertificate 许可证服务器CA证书
|
||||||
|
* @param childCertificate 服务器子证书
|
||||||
|
*/
|
||||||
|
private static void saveCertificatesToFiles(Certificate codeCertificate, Certificate serverCertificate,
|
||||||
|
Certificate childCertificate) throws Exception {
|
||||||
|
// 创建证书文件对象
|
||||||
|
codeCrtFile = FileTools.getFileOrCreat(CODE_CET_FILE_NAME);
|
||||||
|
serverCrtFile = FileTools.getFileOrCreat(SERVER_CET_FILE_NAME);
|
||||||
|
serverChildCrtFile = FileTools.getFileOrCreat(SERVER_CHILD_CET_FILE_NAME);
|
||||||
|
|
||||||
|
// 保存授权码签名CA证书
|
||||||
|
PemUtil.writePemObject("CERTIFICATE", codeCertificate.getEncoded(),
|
||||||
|
FileUtil.getWriter(codeCrtFile, StandardCharsets.UTF_8, false));
|
||||||
|
|
||||||
|
// 保存许可证服务器CA证书
|
||||||
|
PemUtil.writePemObject("CERTIFICATE", serverCertificate.getEncoded(),
|
||||||
|
FileUtil.getWriter(serverCrtFile, StandardCharsets.UTF_8, false));
|
||||||
|
|
||||||
|
// 保存服务器子证书
|
||||||
|
PemUtil.writePemObject("CERTIFICATE", childCertificate.getEncoded(),
|
||||||
|
FileUtil.getWriter(serverChildCrtFile, StandardCharsets.UTF_8, false));
|
||||||
|
|
||||||
|
log.debug("所有证书文件保存完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package com.qiumo.help.context;
|
||||||
|
|
||||||
|
import cn.hutool.core.codec.Base64;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.hutool.crypto.PemUtil;
|
||||||
|
import cn.hutool.crypto.SecureUtil;
|
||||||
|
import cn.hutool.crypto.SignUtil;
|
||||||
|
import cn.hutool.crypto.asymmetric.Sign;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static cn.hutool.crypto.asymmetric.SignAlgorithm.SHA1withRSA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证上下文管理器
|
||||||
|
*
|
||||||
|
* <p>此类为整个应用程序的核心组件之一,专门负责JetBrains产品激活码的生成。
|
||||||
|
* 它封装了复杂的密码学操作和许可证格式化逻辑,对外提供简单易用的API。
|
||||||
|
*
|
||||||
|
* <p>主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>生成JetBrains兼容的激活码</li>
|
||||||
|
* <li>支持多产品组合许可证</li>
|
||||||
|
* <li>自定义许可证有效期和授权信息</li>
|
||||||
|
* <li>RSA数字签名保证安全性</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>激活码格式:
|
||||||
|
* {@code 许可证ID-许可证内容Base64-数字签名Base64-证书Base64}
|
||||||
|
*
|
||||||
|
* <p>安全特性:
|
||||||
|
* <ul>
|
||||||
|
* <li>使用SHA1withRSA数字签名算法</li>
|
||||||
|
* <li>许可证内容使用JSON格式存储</li>
|
||||||
|
* <li>支持X.509证书链验证</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j(topic = "授权上下文")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE) // 防止实例化
|
||||||
|
public class LicenseContextHolder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成JetBrains产品激活码
|
||||||
|
*
|
||||||
|
* <p>此方法是整个类的核心方法,负责生成符合JetBrains规范的激活码。
|
||||||
|
* 生成的激活码包含了所有必要的许可证信息,并使用RSA数字签名确保安全性。
|
||||||
|
*
|
||||||
|
* <p>生成过程:
|
||||||
|
* <ol>
|
||||||
|
* <li>生成唯一的许可证ID</li>
|
||||||
|
* <li>构建包含产品信息的许可证对象</li>
|
||||||
|
* <li>将许可证对象转换为JSON并编码为Base64</li>
|
||||||
|
* <li>使用RSA私钥对许可证内容进行数字签名</li>
|
||||||
|
* <li>获取X.509证书并编码为Base64</li>
|
||||||
|
* <li>按照指定格式组装最终的激活码</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>激活码格式:{@code 许可证ID-许可证内容Base64-数字签名Base64-证书Base64}
|
||||||
|
*
|
||||||
|
* @param licensesName 许可证名称,通常为公司或组织名称
|
||||||
|
* @param assigneeName 被授权人名称,即许可证的使用者
|
||||||
|
* @param expiryDate 过期日期,格式为 yyyy-MM-dd
|
||||||
|
* @param productCodeSet 产品代码集合,包含所有需要激活的产品
|
||||||
|
* @return 符合JetBrains规范的激活码字符串
|
||||||
|
* @throws IllegalArgumentException 当密码学操作或证书处理失败时抛出
|
||||||
|
*/
|
||||||
|
public static String generateLicense(String licensesName, String assigneeName, String expiryDate,
|
||||||
|
Set<String> productCodeSet) {
|
||||||
|
log.info("开始生成许可证 - 许可证名称: {}, 被授权人: {}, 过期日期: {}, 产品数量: {}",
|
||||||
|
licensesName, assigneeName, expiryDate, productCodeSet.size());
|
||||||
|
|
||||||
|
// 1. 生成唯一的许可证ID
|
||||||
|
String licenseId = IdUtil.fastSimpleUUID();
|
||||||
|
log.debug("生成许可证ID: {}", licenseId);
|
||||||
|
|
||||||
|
// 2. 构建产品列表,为每个产品设置相同的过期日期
|
||||||
|
List<Product> products = productCodeSet.stream()
|
||||||
|
.map(productCode -> new Product()
|
||||||
|
.setCode(productCode) // 产品代码
|
||||||
|
.setFallbackDate(expiryDate) // 备用过期日期
|
||||||
|
.setPaidUpTo(expiryDate)) // 付费至日期
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 3. 构建许可证主体对象
|
||||||
|
LicensePart licensePart = new LicensePart()
|
||||||
|
.setLicenseId(licenseId) // 许可证ID
|
||||||
|
.setLicenseeName(licensesName) // 许可证名称
|
||||||
|
.setAssigneeName(assigneeName) // 被授权人
|
||||||
|
.setProducts(products); // 产品列表
|
||||||
|
|
||||||
|
// 4. 将许可证对象转换为JSON并进行Base64编码
|
||||||
|
String licensePartJson = JSONUtil.toJsonStr(licensePart);
|
||||||
|
String licensePartBase64 = Base64.encode(licensePartJson);
|
||||||
|
log.debug("许可证JSON内容长度: {} 字符", licensePartJson.length());
|
||||||
|
|
||||||
|
// 5. 加载密码学组件:私钥、公钥和证书
|
||||||
|
PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.privateKeyFile()));
|
||||||
|
PublicKey publicKey = PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.publicKeyFile()));
|
||||||
|
Certificate certificate = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.codeCrtFile()));
|
||||||
|
|
||||||
|
// 6. 使用SHA1withRSA算法进行数字签名
|
||||||
|
Sign sign = SignUtil.sign(SHA1withRSA, privateKey.getEncoded(), publicKey.getEncoded());
|
||||||
|
String signatureBase64 = Base64.encode(sign.sign(licensePartJson));
|
||||||
|
|
||||||
|
// 7. 将X.509证书编码为Base64
|
||||||
|
String certBase64;
|
||||||
|
try {
|
||||||
|
certBase64 = Base64.encode(certificate.getEncoded());
|
||||||
|
} catch (CertificateEncodingException e) {
|
||||||
|
log.error("证书编码失败", e);
|
||||||
|
throw new IllegalArgumentException("证书编码异常", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. 按照JetBrains格式组装最终的激活码
|
||||||
|
String activationCode = CharSequenceUtil.format("{}-{}-{}-{}",
|
||||||
|
licenseId, licensePartBase64, signatureBase64, certBase64);
|
||||||
|
|
||||||
|
log.info("许可证生成成功 - 激活码长度: {} 字符", activationCode.length());
|
||||||
|
|
||||||
|
return activationCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 内部数据类 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证主体信息实体类
|
||||||
|
*
|
||||||
|
* <p>封装了许可证的所有基本信息,包括许可证ID、名称、被授权人和产品列表。
|
||||||
|
* 此类的实例将被序列化为JSON格式,作为激活码的一部分。
|
||||||
|
*
|
||||||
|
* <p>字段说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>licenseId - 许可证的唯一标识符</li>
|
||||||
|
* <li>licenseeName - 许可证名称,通常为公司或组织名称</li>
|
||||||
|
* <li>assigneeName - 被授权人名称,即许可证的使用者</li>
|
||||||
|
* <li>products - 包含在许可证中的产品列表</li>
|
||||||
|
* <li>metadata - 元数据信息,包含版本和类型信息</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true) // 支持链式调用
|
||||||
|
public static class LicensePart {
|
||||||
|
|
||||||
|
/** 许可证唯一标识符,由UUID生成 */
|
||||||
|
private String licenseId;
|
||||||
|
|
||||||
|
/** 许可证名称,通常为公司或组织名称 */
|
||||||
|
private String licenseeName;
|
||||||
|
|
||||||
|
/** 被授权人名称,即许可证的使用者 */
|
||||||
|
private String assigneeName;
|
||||||
|
|
||||||
|
/** 包含在许可证中的产品列表 */
|
||||||
|
private List<Product> products;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元数据信息,默认值说明:
|
||||||
|
* - 01: 版本标识
|
||||||
|
* - 20230914: 日期标识
|
||||||
|
* - PSAX000005: 产品类型和序列号
|
||||||
|
*/
|
||||||
|
private String metadata = "0120230914PSAX000005";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品信息实体类
|
||||||
|
*
|
||||||
|
* <p>封装了单个产品在许可证中的所有相关信息。
|
||||||
|
* 每个产品都包含产品代码、备用过期日期和付费截止日期。
|
||||||
|
*
|
||||||
|
* <p>字段说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>code - 产品代码,如 "II"(代表IntelliJ IDEA)、"PS"(代表PhpStorm)等</li>
|
||||||
|
* <li>fallbackDate - 备用过期日期,通常与 paidUpTo 相同</li>
|
||||||
|
* <li>paidUpTo - 付费截止日期,表示许可证的有效期</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>常见产品代码:
|
||||||
|
* <ul>
|
||||||
|
* <li>II - IntelliJ IDEA Ultimate</li>
|
||||||
|
* <li>PS - PhpStorm</li>
|
||||||
|
* <li>WS - WebStorm</li>
|
||||||
|
* <li>PY - PyCharm Professional</li>
|
||||||
|
* <li>PC - PyCharm Community</li>
|
||||||
|
* <li>RM - RubyMine</li>
|
||||||
|
* <li>CL - CLion</li>
|
||||||
|
* <li>DB - DataGrip</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true) // 支持链式调用
|
||||||
|
public static class Product {
|
||||||
|
|
||||||
|
/** 产品代码,用于标识不同的JetBrains产品 */
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/** 备用过期日期,格式为 yyyy-MM-dd */
|
||||||
|
private String fallbackDate;
|
||||||
|
|
||||||
|
/** 付费截止日期,格式为 yyyy-MM-dd */
|
||||||
|
private String paidUpTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package com.qiumo.help.context;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.qiumo.help.util.FileTools;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JetBrains插件信息上下文管理器
|
||||||
|
*
|
||||||
|
* <p>此类负责管理JetBrains插件市场中付费插件的信息,包括插件ID、名称、产品代码、定价模式等。
|
||||||
|
* 插件信息通过JetBrains官方API实时获取,并缓存到本地JSON文件中,用于许可证生成时的插件选择。
|
||||||
|
*
|
||||||
|
* <p>主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>从JetBrains插件市场API获取插件信息</li>
|
||||||
|
* <li>过滤和缓存付费插件信息</li>
|
||||||
|
* <li>定期刷新插件信息以保持最新状态</li>
|
||||||
|
* <li>为许可证生成提供插件产品代码</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>数据流程:
|
||||||
|
* <ol>
|
||||||
|
* <li>从本地JSON文件加载已缓存的插件信息</li>
|
||||||
|
* <li>定期调用JetBrains API获取最新插件列表</li>
|
||||||
|
* <li>过滤掉免费插件,只保留付费插件</li>
|
||||||
|
* <li>获取每个插件的详细信息(特别是产品代码)</li>
|
||||||
|
* <li>更新本地缓存文件</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>API端点:
|
||||||
|
* <ul>
|
||||||
|
* <li>插件列表:{@code https://plugins.jetbrains.com/api/searchPlugins}</li>
|
||||||
|
* <li>插件详情:{@code https://plugins.jetbrains.com/api/plugins/{pluginId}}</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>缓存策略:
|
||||||
|
* <ul>
|
||||||
|
* <li>本地JSON文件存储:{@code external/data/plugin.json}</li>
|
||||||
|
* <li>定时刷新:每天中午12点自动刷新一次</li>
|
||||||
|
* <li>增量更新:只获取新增的付费插件</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j(topic = "插件上下文")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class PluginsContextHolder {
|
||||||
|
|
||||||
|
// ==================== 常量定义 ====================
|
||||||
|
|
||||||
|
/** JetBrains插件市场基础URL */
|
||||||
|
private static final String PLUGIN_BASIC_URL = "https://plugins.jetbrains.com";
|
||||||
|
|
||||||
|
/** 插件列表API地址,获取所有插件基本信息 */
|
||||||
|
private static final String PLUGIN_LIST_URL =
|
||||||
|
PLUGIN_BASIC_URL + "/api/searchPlugins?max=10000&offset=0&orderBy=name";
|
||||||
|
|
||||||
|
/** 插件详情API地址模板,用于获取单个插件的详细信息 */
|
||||||
|
private static final String PLUGIN_INFO_URL = PLUGIN_BASIC_URL + "/api/plugins/";
|
||||||
|
|
||||||
|
/** 插件信息缓存文件路径 */
|
||||||
|
private static final String PLUGIN_JSON_FILE_NAME = "external/data/plugin.json";
|
||||||
|
|
||||||
|
// ==================== 静态字段 ====================
|
||||||
|
|
||||||
|
/** 插件信息缓存列表,存储所有已加载的付费插件信息 */
|
||||||
|
private static List<PluginCache> pluginCacheList;
|
||||||
|
|
||||||
|
/** 插件JSON缓存文件对象 */
|
||||||
|
private static File pluginsJsonFile;
|
||||||
|
|
||||||
|
// ==================== 核心方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化插件上下文
|
||||||
|
*
|
||||||
|
* <p>此方法负责初始化插件信息管理系统,包括加载本地缓存和启动刷新任务。
|
||||||
|
* 如果本地缓存文件不存在或格式错误,会自动从网络获取最新数据。
|
||||||
|
*
|
||||||
|
* <p>初始化过程:
|
||||||
|
* <ol>
|
||||||
|
* <li>获取或创建插件缓存JSON文件</li>
|
||||||
|
* <li>读取并验证JSON文件内容</li>
|
||||||
|
* <li>如果数据有效,解析为对象列表</li>
|
||||||
|
* <li>如果数据无效,初始化为空列表</li>
|
||||||
|
* <li>启动异步刷新任务获取最新数据</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>注意事项:
|
||||||
|
* <ul>
|
||||||
|
* <li>此方法应在应用启动时调用,且只调用一次</li>
|
||||||
|
* <li>网络刷新任务是异步执行的,不会阻塞应用启动</li>
|
||||||
|
* <li>刷新失败不会影响应用正常运行</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException 当文件读取失败时抛出
|
||||||
|
*/
|
||||||
|
public static void init() {
|
||||||
|
log.info("开始初始化插件上下文...");
|
||||||
|
|
||||||
|
// 获取或创建插件缓存文件
|
||||||
|
pluginsJsonFile = FileTools.getFileOrCreat(PLUGIN_JSON_FILE_NAME);
|
||||||
|
log.debug("插件缓存文件路径: {}", pluginsJsonFile.getAbsolutePath());
|
||||||
|
|
||||||
|
// 读取并验证JSON文件内容
|
||||||
|
String pluginJsonArray;
|
||||||
|
try {
|
||||||
|
pluginJsonArray = IoUtil.readUtf8(FileUtil.getInputStream(pluginsJsonFile));
|
||||||
|
} catch (IORuntimeException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
CharSequenceUtil.format("{} 文件读取失败!", PLUGIN_JSON_FILE_NAME), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查JSON数据的有效性
|
||||||
|
if (CharSequenceUtil.isBlank(pluginJsonArray) || !JSONUtil.isTypeJSON(pluginJsonArray)) {
|
||||||
|
log.warn("插件缓存文件为空或格式错误,初始化为空列表");
|
||||||
|
pluginCacheList = new ArrayList<>();
|
||||||
|
// 启动异步刷新任务获取数据
|
||||||
|
refreshJsonFile();
|
||||||
|
} else {
|
||||||
|
// 解析JSON数据为插件对象列表
|
||||||
|
pluginCacheList = JSONUtil.toList(pluginJsonArray, PluginCache.class);
|
||||||
|
log.info("插件上下文初始化成功!加载插件数量: {}", pluginCacheList.size());
|
||||||
|
|
||||||
|
// 启动异步刷新任务获取最新数据
|
||||||
|
refreshJsonFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取插件信息缓存列表
|
||||||
|
*
|
||||||
|
* <p>返回已加载的所有JetBrains付费插件信息列表。
|
||||||
|
* 该列表包含插件ID、名称、产品代码、定价模式和图标等信息。
|
||||||
|
*
|
||||||
|
* <p>使用场景:
|
||||||
|
* <ul>
|
||||||
|
* <li>许可证生成时选择插件产品代码</li>
|
||||||
|
* <li>前端界面显示插件列表</li>
|
||||||
|
* <li>插件管理和统计功能</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>注意事项:
|
||||||
|
* <ul>
|
||||||
|
* <li>返回的是实际的列表引用,修改会影响缓存</li>
|
||||||
|
* <li>列表中只包含付费插件,免费插件已被过滤</li>
|
||||||
|
* <li>需要在 {@link #init()} 方法调用后使用</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return 插件信息缓存列表,不为null
|
||||||
|
*/
|
||||||
|
public static List<PluginCache> pluginCacheList() {
|
||||||
|
return PluginsContextHolder.pluginCacheList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void refreshJsonFile() {
|
||||||
|
log.info("从'JetBrains.com'刷新中...");
|
||||||
|
CompletableFuture
|
||||||
|
.supplyAsync(PluginsContextHolder::pluginList)
|
||||||
|
.thenApply(PluginsContextHolder::pluginListFilter)
|
||||||
|
.thenApply(PluginsContextHolder::pluginConversion)
|
||||||
|
.thenAccept(PluginsContextHolder::overrideJsonFile)
|
||||||
|
.thenRun(() -> log.info("刷新成功!"))
|
||||||
|
.exceptionally(throwable -> {
|
||||||
|
log.error("刷新失败!", throwable);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void overrideJsonFile(List<PluginCache> pluginCaches) {
|
||||||
|
log.info("源大小 => [{}], 新增大小 => [{}]", pluginCacheList.size(), pluginCaches.size());
|
||||||
|
pluginCacheList.addAll(pluginCaches);
|
||||||
|
String jsonStr = JSONUtil.toJsonStr(pluginCacheList);
|
||||||
|
try {
|
||||||
|
FileUtil.writeString(JSONUtil.formatJsonStr(jsonStr), pluginsJsonFile, StandardCharsets.UTF_8);
|
||||||
|
log.info("Json文件已覆写!");
|
||||||
|
} catch (IORuntimeException e) {
|
||||||
|
throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件写入失败!", PLUGIN_JSON_FILE_NAME), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginList pluginList() {
|
||||||
|
return HttpUtil.createGet(PLUGIN_LIST_URL)
|
||||||
|
.thenFunction(response -> {
|
||||||
|
try (InputStream is = response.bodyStream()) {
|
||||||
|
if (!response.isOk()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
CharSequenceUtil.format("{} 请求失败! = {}", PLUGIN_LIST_URL, response));
|
||||||
|
}
|
||||||
|
PluginList pluginList = JSONUtil.toBean(IoUtil.readUtf8(is), PluginList.class);
|
||||||
|
log.info("获取大小 => [{}]", pluginList.getTotal());
|
||||||
|
return pluginList;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求IO读取失败!", PLUGIN_LIST_URL),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PluginList.Plugin> pluginListFilter(PluginList pluginList) {
|
||||||
|
List<PluginList.Plugin> plugins = pluginList.getPlugins()
|
||||||
|
.stream()
|
||||||
|
.filter(plugin -> !PluginsContextHolder.pluginCacheList.contains(new PluginCache().setId(plugin.getId())))
|
||||||
|
.filter(plugin -> !CharSequenceUtil.equals(plugin.getPricingModel(), "FREE"))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
log.info("过滤后大小 => [{}]", plugins.size());
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PluginCache> pluginConversion(List<PluginList.Plugin> pluginList) {
|
||||||
|
List<PluginCache> list = pluginList
|
||||||
|
.stream()
|
||||||
|
.parallel()
|
||||||
|
.map(plugin -> {
|
||||||
|
String productCode = pluginInfo(plugin).getPurchaseInfo().getProductCode();
|
||||||
|
return new PluginCache()
|
||||||
|
.setId(plugin.getId())
|
||||||
|
.setProductCode(productCode)
|
||||||
|
.setName(plugin.getName())
|
||||||
|
.setPricingModel(plugin.getPricingModel())
|
||||||
|
.setIcon(StrUtil.isNotBlank(plugin.getIcon()) ? PLUGIN_BASIC_URL + plugin.getIcon() : null)
|
||||||
|
;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
log.info("转换后大小 => [{}]", list.size());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginInfo pluginInfo(PluginList.Plugin plugin) {
|
||||||
|
return HttpUtil.createGet(PLUGIN_INFO_URL + plugin.getId())
|
||||||
|
.thenFunction(response -> {
|
||||||
|
try (InputStream is = response.bodyStream()) {
|
||||||
|
if (!response.isOk()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
CharSequenceUtil.format("{} 请求失败! = {}", PLUGIN_INFO_URL, response));
|
||||||
|
}
|
||||||
|
PluginInfo pluginInfo = JSONUtil.toBean(IoUtil.readUtf8(is), PluginInfo.class);
|
||||||
|
log.info("已抓取 => ID = [{}], 名称 = [{}], Code = [{}]", pluginInfo.getId(), plugin.getName(),
|
||||||
|
pluginInfo.getPurchaseInfo().getProductCode());
|
||||||
|
return pluginInfo;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求IO读取失败!", PLUGIN_LIST_URL),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public static class PluginCache {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String productCode;
|
||||||
|
private String name;
|
||||||
|
private String pricingModel;
|
||||||
|
private String icon;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof PluginCache)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return id.equals(((PluginCache) o).id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return id.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public static class PluginInfo {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private PurchaseInfo purchaseInfo;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public static class PurchaseInfo {
|
||||||
|
|
||||||
|
private String productCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public static class PluginList {
|
||||||
|
|
||||||
|
private List<Plugin> plugins;
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public static class Plugin {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private String preview;
|
||||||
|
private Integer downloads;
|
||||||
|
private String pricingModel;
|
||||||
|
private String organization;
|
||||||
|
private String icon;
|
||||||
|
private String previewImage;
|
||||||
|
private Double rating;
|
||||||
|
private VendorInfo vendorInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public static class VendorInfo {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private Boolean isVerified;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package com.qiumo.help.context;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.qiumo.help.util.FileTools;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JetBrains产品信息上下文管理器
|
||||||
|
*
|
||||||
|
* <p>此类负责管理JetBrains所有IDE产品的信息,包括产品名称、产品代码和图标等。
|
||||||
|
* 产品信息从JSON配置文件中加载,用于许可证生成时的产品选择和识别。
|
||||||
|
*
|
||||||
|
* <p>主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>从JSON文件加载JetBrains产品信息</li>
|
||||||
|
* <li>提供产品信息的缓存和访问接口</li>
|
||||||
|
* <li>支持产品代码到产品名称的映射</li>
|
||||||
|
* <li>为许可证生成提供产品代码列表</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>支持的产品包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>IntelliJ IDEA Ultimate (II)</li>
|
||||||
|
* <li>PhpStorm (PS)</li>
|
||||||
|
* <li>WebStorm (WS)</li>
|
||||||
|
* <li>PyCharm Professional (PY)</li>
|
||||||
|
* <li>RubyMine (RM)</li>
|
||||||
|
* <li>CLion (CL)</li>
|
||||||
|
* <li>DataGrip (DB)</li>
|
||||||
|
* <li>GoLand (GO)</li>
|
||||||
|
* <li>Rider (RD)</li>
|
||||||
|
* <li>AppCode (AC)</li>
|
||||||
|
* <li>以及其他JetBrains系列产品</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>数据来源:
|
||||||
|
* 产品信息可以通过JetBrains官方API获取:
|
||||||
|
* {@code https://data.services.jetbrains.com/products?fields=name,salesCode}
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j(topic = "产品上下文")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE) // 防止实例化
|
||||||
|
public class ProductsContextHolder {
|
||||||
|
|
||||||
|
// ==================== 常量定义 ====================
|
||||||
|
|
||||||
|
/** 产品信息配置文件路径 */
|
||||||
|
private static final String PRODUCT_JSON_FILE_NAME = "external/data/product.json";
|
||||||
|
|
||||||
|
// ==================== 静态字段 ====================
|
||||||
|
|
||||||
|
/** 产品信息缓存列表,存储所有加载的产品信息 */
|
||||||
|
private static List<ProductCache> productCacheList;
|
||||||
|
|
||||||
|
// TODO 通过该接口可以获取付费IDE的CODE
|
||||||
|
// TODO https://data.services.jetbrains.com/products?fields=name,salesCode
|
||||||
|
|
||||||
|
public static void init() {
|
||||||
|
log.info("开始初始化产品上下文...");
|
||||||
|
|
||||||
|
// 获取或创建产品配置文件
|
||||||
|
File productJsonFile = FileTools.getFileOrCreat(PRODUCT_JSON_FILE_NAME);
|
||||||
|
log.debug("产品配置文件路径: {}", productJsonFile.getAbsolutePath());
|
||||||
|
|
||||||
|
// 读取JSON文件内容
|
||||||
|
String productJsonArray;
|
||||||
|
try {
|
||||||
|
productJsonArray = IoUtil.readUtf8(FileUtil.getInputStream(productJsonFile));
|
||||||
|
log.debug("成功读取产品配置文件,内容长度: {} 字符", productJsonArray.length());
|
||||||
|
} catch (IORuntimeException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
CharSequenceUtil.format("{} 文件读取失败!", PRODUCT_JSON_FILE_NAME), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证JSON文件内容并解析
|
||||||
|
if (CharSequenceUtil.isBlank(productJsonArray) || !JSONUtil.isTypeJSON(productJsonArray)) {
|
||||||
|
log.error("产品数据不存在或格式错误!配置文件: {}", PRODUCT_JSON_FILE_NAME);
|
||||||
|
// 初始化为空列表,避免空指针异常
|
||||||
|
productCacheList = new ArrayList<>();
|
||||||
|
} else {
|
||||||
|
// 解析JSON数据为产品对象列表
|
||||||
|
productCacheList = JSONUtil.toList(productJsonArray, ProductCache.class);
|
||||||
|
log.info("产品上下文初始化成功!加载产品数量: {}", productCacheList.size());
|
||||||
|
|
||||||
|
// 输出加载的产品信息(调试级别)
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
productCacheList.forEach(product ->
|
||||||
|
log.debug("加载产品: {} ({})", product.getName(), product.getProductCode()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取产品信息缓存列表
|
||||||
|
*
|
||||||
|
* <p>返回已加载的所有JetBrains产品信息列表。
|
||||||
|
* 该列表包含产品名称、产品代码和图标等信息。
|
||||||
|
*
|
||||||
|
* <p>使用场景:
|
||||||
|
* <ul>
|
||||||
|
* <li>许可证生成时选择产品代码</li>
|
||||||
|
* <li>前端界面显示产品列表</li>
|
||||||
|
* <li>产品代码到产品名称的映射</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>注意事项:
|
||||||
|
* <ul>
|
||||||
|
* <li>返回的是不可变列表,不能直接修改</li>
|
||||||
|
* <li>如果初始化失败,可能返回空列表</li>
|
||||||
|
* <li>需要在 {@link #init()} 方法调用后使用</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return 产品信息缓存列表,不为null
|
||||||
|
*/
|
||||||
|
public static List<ProductCache> productCacheList() {
|
||||||
|
return ProductsContextHolder.productCacheList;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 内部数据类 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 产品信息缓存实体类
|
||||||
|
*
|
||||||
|
* <p>封装了单个JetBrains产品的基本信息,包括产品名称、产品代码和图标类名。
|
||||||
|
* 该类的实例从 JSON 配置文件中反序列化得到。
|
||||||
|
*
|
||||||
|
* <p>字段说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>name - 产品显示名称,如 "IntelliJ IDEA Ultimate"</li>
|
||||||
|
* <li>productCode - 产品代码,如 "II" 或用逗号分隔的多个代码</li>
|
||||||
|
* <li>iconClass - CSS图标类名,用于前端界面显示</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>产品代码示例:
|
||||||
|
* <ul>
|
||||||
|
* <li>"II" - IntelliJ IDEA Ultimate</li>
|
||||||
|
* <li>"PS" - PhpStorm</li>
|
||||||
|
* <li>"WS" - WebStorm</li>
|
||||||
|
* <li>"PY" - PyCharm Professional</li>
|
||||||
|
* <li>"RM" - RubyMine</li>
|
||||||
|
* <li>"CL" - CLion</li>
|
||||||
|
* <li>"DB" - DataGrip</li>
|
||||||
|
* <li>"GO" - GoLand</li>
|
||||||
|
* <li>"RD" - Rider</li>
|
||||||
|
* <li>"AC" - AppCode</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>数据来源:
|
||||||
|
* 可以通过JetBrains官方API获取最新产品信息:
|
||||||
|
* {@code https://data.services.jetbrains.com/products?fields=name,salesCode}
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class ProductCache {
|
||||||
|
|
||||||
|
/** 产品显示名称,如 "IntelliJ IDEA Ultimate" */
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/** 产品代码,单个或多个用逗号分隔,如 "II" 或 "II,IC" */
|
||||||
|
private String productCode;
|
||||||
|
|
||||||
|
/** CSS图标类名,用于前端界面显示产品图标 */
|
||||||
|
private String iconClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package com.qiumo.help.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.qiumo.help.context.LicenseContextHolder;
|
||||||
|
import com.qiumo.help.context.PluginsContextHolder;
|
||||||
|
import com.qiumo.help.context.ProductsContextHolder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据接口控制器
|
||||||
|
*
|
||||||
|
* <p>此控制器提供前端所需的基础数据API,包括产品列表、插件列表等。
|
||||||
|
* 这些数据用于前端界面的动态展示和用户选择。
|
||||||
|
*
|
||||||
|
* <p>主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>提供JetBrains产品列表数据</li>
|
||||||
|
* <li>提供付费插件列表数据</li>
|
||||||
|
* <li>支持前端动态加载数据</li>
|
||||||
|
* <li>返回JSON格式的结构化数据</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Slf4j(topic = "数据接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api")
|
||||||
|
public class DataController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取JetBrains产品列表
|
||||||
|
*
|
||||||
|
* <p>返回所有支持的JetBrains IDE产品信息,包括产品名称、产品代码和描述等。
|
||||||
|
* 前端可以使用这些数据构建产品选择下拉框。
|
||||||
|
*
|
||||||
|
* <p>返回的产品信息包含:
|
||||||
|
* <ul>
|
||||||
|
* <li>name - 产品显示名称</li>
|
||||||
|
* <li>productCode - 产品代码(用于许可证生成)</li>
|
||||||
|
* <li>iconClass - 图标CSS类名</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>请求示例:
|
||||||
|
* <pre>
|
||||||
|
* GET /api/products
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>响应示例:
|
||||||
|
* <pre>
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* "name": "IntelliJ IDEA Ultimate",
|
||||||
|
* "productCode": "II",
|
||||||
|
* "iconClass": "icon-idea"
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* "name": "PhpStorm",
|
||||||
|
* "productCode": "PS",
|
||||||
|
* "iconClass": "icon-phpstorm"
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return JetBrains产品信息列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/products")
|
||||||
|
public List<ProductsContextHolder.ProductCache> getProducts() {
|
||||||
|
log.debug("获取产品列表,产品数量: {}", ProductsContextHolder.productCacheList().size());
|
||||||
|
return ProductsContextHolder.productCacheList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取JetBrains付费插件列表
|
||||||
|
*
|
||||||
|
* <p>返回所有支持的JetBrains付费插件信息,包括插件名称、ID和产品代码等。
|
||||||
|
* 前端可以使用这些数据构建插件选择下拉框。
|
||||||
|
*
|
||||||
|
* <p>返回的插件信息包含:
|
||||||
|
* <ul>
|
||||||
|
* <li>id - 插件唯一标识符</li>
|
||||||
|
* <li>name - 插件显示名称</li>
|
||||||
|
* <li>productCode - 产品代码(用于许可证生成)</li>
|
||||||
|
* <li>pricingModel - 定价模式</li>
|
||||||
|
* <li>icon - 插件图标URL</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>请求示例:
|
||||||
|
* <pre>
|
||||||
|
* GET /api/plugins
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>响应示例:
|
||||||
|
* <pre>
|
||||||
|
* [
|
||||||
|
* {
|
||||||
|
* "id": 7973,
|
||||||
|
* "name": "SonarLint",
|
||||||
|
* "productCode": "SONAR_LINT",
|
||||||
|
* "pricingModel": "PAID",
|
||||||
|
* "icon": "https://plugins.jetbrains.com/files/7973/icon.svg"
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @return JetBrains付费插件信息列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/plugins")
|
||||||
|
public List<PluginsContextHolder.PluginCache> getPlugins() {
|
||||||
|
log.debug("获取插件列表,插件数量: {}", PluginsContextHolder.pluginCacheList().size());
|
||||||
|
return PluginsContextHolder.pluginCacheList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成JetBrains产品激活码
|
||||||
|
*
|
||||||
|
* @param productCode 产品代码
|
||||||
|
* @param licenseeName 许可证名称
|
||||||
|
* @param assigneeName 被授权人名称
|
||||||
|
* @param expiryDate 过期日期
|
||||||
|
* @return 激活码字符串
|
||||||
|
*/
|
||||||
|
@GetMapping("/generateLicense")
|
||||||
|
public String generateLicense(
|
||||||
|
@RequestParam(required = false) String productCode,
|
||||||
|
@RequestParam String licenseeName,
|
||||||
|
@RequestParam String assigneeName,
|
||||||
|
@RequestParam String expiryDate) {
|
||||||
|
|
||||||
|
Set<String> productCodeSet;
|
||||||
|
|
||||||
|
if (CharSequenceUtil.isBlank(productCode)) {
|
||||||
|
// 未指定产品代码,自动包含所有可用产品
|
||||||
|
List<String> productCodeList = ProductsContextHolder.productCacheList()
|
||||||
|
.stream()
|
||||||
|
.map(ProductsContextHolder.ProductCache::getProductCode)
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.map(code -> CharSequenceUtil.splitTrim(code, ","))
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<String> pluginCodeList = PluginsContextHolder.pluginCacheList()
|
||||||
|
.stream()
|
||||||
|
.map(PluginsContextHolder.PluginCache::getProductCode)
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
productCodeSet = CollUtil.newHashSet(productCodeList);
|
||||||
|
productCodeSet.addAll(pluginCodeList);
|
||||||
|
} else {
|
||||||
|
productCodeSet = CollUtil.newHashSet(CharSequenceUtil.splitTrim(productCode, ','));
|
||||||
|
}
|
||||||
|
|
||||||
|
return LicenseContextHolder.generateLicense(
|
||||||
|
licenseeName,
|
||||||
|
assigneeName,
|
||||||
|
expiryDate,
|
||||||
|
productCodeSet
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成JetBrains插件激活码
|
||||||
|
*
|
||||||
|
* @param pluginId 插件ID
|
||||||
|
* @param licenseeName 许可证名称
|
||||||
|
* @param assigneeName 被授权人名称
|
||||||
|
* @param expiryDate 过期日期
|
||||||
|
* @return 插件激活码字符串
|
||||||
|
*/
|
||||||
|
@GetMapping("/generatePluginLicense")
|
||||||
|
public String generatePluginLicense(
|
||||||
|
@RequestParam String pluginId,
|
||||||
|
@RequestParam String licenseeName,
|
||||||
|
@RequestParam String assigneeName,
|
||||||
|
@RequestParam String expiryDate) {
|
||||||
|
|
||||||
|
String productCode = PluginsContextHolder.pluginCacheList()
|
||||||
|
.stream()
|
||||||
|
.filter(plugin -> Objects.equals(plugin.getId(), pluginId))
|
||||||
|
.map(PluginsContextHolder.PluginCache::getProductCode)
|
||||||
|
.filter(StrUtil::isNotBlank)
|
||||||
|
.findFirst()
|
||||||
|
.orElse("");
|
||||||
|
|
||||||
|
if (CharSequenceUtil.isBlank(productCode)) {
|
||||||
|
productCode = "PLUGIN_" + pluginId;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> productCodeSet = CollUtil.newHashSet(productCode);
|
||||||
|
|
||||||
|
return LicenseContextHolder.generateLicense(
|
||||||
|
licenseeName,
|
||||||
|
assigneeName,
|
||||||
|
expiryDate,
|
||||||
|
productCodeSet
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载ja-netfilter代理工具
|
||||||
|
*/
|
||||||
|
@GetMapping("/downloadAgent")
|
||||||
|
public void downloadAgent(HttpServletResponse response) throws IOException {
|
||||||
|
response.sendRedirect("/api/downloadZip");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.qiumo.help.controller;
|
||||||
|
|
||||||
|
import com.qiumo.help.service.JrebelService;
|
||||||
|
import java.util.UUID;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Slf4j(topic = "JRebel激活控制器")
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JRebelController {
|
||||||
|
|
||||||
|
private final JrebelService jrebelService;
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
@RequestMapping("/guid")
|
||||||
|
public void guid(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
response.setContentType("text/html; charset=utf-8");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
String body = UUID.randomUUID().toString();
|
||||||
|
response.getWriter().print(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping({"/jrebel/leases"})
|
||||||
|
public void jrebelLeases(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
jrebelService.jrebelLeasesHandler(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/jrebel/leases/1")
|
||||||
|
public void jrebelLeases1(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
jrebelService.jrebelLeases1Handler(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/agent/leases")
|
||||||
|
public void agentLeases(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
jrebelService.jrebelLeasesHandler(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/agent/leases/1")
|
||||||
|
public void agentLeases1(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
jrebelService.jrebelLeases1Handler(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/jrebel/validate-connection")
|
||||||
|
public void jrebelValidateHandler(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
jrebelService.jrebelValidateHandler(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
package com.qiumo.help.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.qiumo.help.context.LicenseContextHolder;
|
||||||
|
import com.qiumo.help.context.PluginsContextHolder;
|
||||||
|
import com.qiumo.help.context.ProductsContextHolder;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证代码生成控制器
|
||||||
|
*
|
||||||
|
* <p>此控制器专门用于生成JetBrains产品的激活码(许可证代码)。
|
||||||
|
* 它能够根据用户的输入参数生成适用于不同产品的激活码。
|
||||||
|
*
|
||||||
|
* <p>主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>生成个人或企业版许可证</li>
|
||||||
|
* <li>支持指定产品代码或自动包含所有产品</li>
|
||||||
|
* <li>自定义许可证名称、被授权人和过期日期</li>
|
||||||
|
* <li>支持JetBrains所有付费IDE和插件</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>生成的激活码格式为:
|
||||||
|
* {@code 许可证ID-许可证内容Base64-签名Base64-证书Base64}
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/license-code")
|
||||||
|
public class LicenseCodeController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成许可证请求参数实体类
|
||||||
|
*
|
||||||
|
* <p>封装了生成许可证所需的全部参数,包括许可证基本信息和产品代码。
|
||||||
|
*
|
||||||
|
* <p>参数说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>licenseName - 许可证名称,用于标识许可证的来源(如公司名称或个人名称)</li>
|
||||||
|
* <li>assigneeName - 被授权人名称,即许可证的使用者</li>
|
||||||
|
* <li>expiryDate - 过期日期,格式为 yyyy-MM-dd(如:2025-12-31)</li>
|
||||||
|
* <li>productCode - 产品代码,多个代码用逗号分隔,为空时包含所有产品</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>使用示例:
|
||||||
|
* <pre>
|
||||||
|
* {
|
||||||
|
* "licenseName": "QiuMo Technology",
|
||||||
|
* "assigneeName": "张三",
|
||||||
|
* "expiryDate": "2025-12-31",
|
||||||
|
* "productCode": "II,PS,WS,RM,PCC,PC,CLN"
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class GenerateLicenseReqBody {
|
||||||
|
|
||||||
|
/** 许可证名称(公司或组织名称) */
|
||||||
|
private String licenseName;
|
||||||
|
|
||||||
|
/** 被授权人名称(使用者名称) */
|
||||||
|
private String assigneeName;
|
||||||
|
|
||||||
|
/** 过期日期(格式:yyyy-MM-dd) */
|
||||||
|
private String expiryDate;
|
||||||
|
|
||||||
|
/** 产品代码(多个代码用逗号分隔,为空时包含所有产品) */
|
||||||
|
private String productCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成JetBrains产品激活码接口(GET方式)
|
||||||
|
*
|
||||||
|
* <p>此接口提供GET方式访问,用于前端页面直接调用生成激活码。
|
||||||
|
*
|
||||||
|
* @param productCode 产品代码
|
||||||
|
* @param licenseeName 许可证名称
|
||||||
|
* @param assigneeName 被授权人名称
|
||||||
|
* @param expiryDate 过期日期
|
||||||
|
* @return JetBrains产品激活码字符串
|
||||||
|
*/
|
||||||
|
@GetMapping("/generate")
|
||||||
|
public String generateLicenseByGet(
|
||||||
|
@RequestParam(required = false) String productCode,
|
||||||
|
@RequestParam String licenseeName,
|
||||||
|
@RequestParam String assigneeName,
|
||||||
|
@RequestParam String expiryDate) {
|
||||||
|
|
||||||
|
GenerateLicenseReqBody body = new GenerateLicenseReqBody();
|
||||||
|
body.setProductCode(productCode);
|
||||||
|
body.setLicenseName(licenseeName);
|
||||||
|
body.setAssigneeName(assigneeName);
|
||||||
|
body.setExpiryDate(expiryDate);
|
||||||
|
|
||||||
|
return generateLicense(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成JetBrains产品激活码接口
|
||||||
|
*
|
||||||
|
* <p>此接口用于生成JetBrains系列产品的激活码(许可证代码)。
|
||||||
|
* 生成的激活码可以用于激活各种JetBrains IDE和插件。
|
||||||
|
*
|
||||||
|
* <p>功能特点:
|
||||||
|
* <ul>
|
||||||
|
* <li>智能产品选择:如果未指定产品代码,自动包含所有可用产品</li>
|
||||||
|
* <li>灵活的过期设置:支持自定义过期日期</li>
|
||||||
|
* <li>完整产品支持:包括IDE和付费插件</li>
|
||||||
|
* <li>RSA数字签名:确保激活码的安全性和完整性</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>生成过程:
|
||||||
|
* <ol>
|
||||||
|
* <li>根据请求参数解析产品代码集合</li>
|
||||||
|
* <li>构建许可证对象,包含产品信息、有效期等</li>
|
||||||
|
* <li>使用RSA私钥对许可证内容进行数字签名</li>
|
||||||
|
* <li>组装最终的激活码字符串</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>请求示例:
|
||||||
|
* <pre>
|
||||||
|
* POST /generateLicense
|
||||||
|
* Content-Type: application/json
|
||||||
|
*
|
||||||
|
* {
|
||||||
|
* "licenseName": "QiuMo Technology",
|
||||||
|
* "assigneeName": "张三",
|
||||||
|
* "expiryDate": "2025-12-31",
|
||||||
|
* "productCode": "II,PS,WS"
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>响应示例:
|
||||||
|
* <pre>
|
||||||
|
* K9V7I1-FLS6QH-eyJsaWNlbnNlSWQiOi...(完整激活码)
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param body 生成许可证的请求参数
|
||||||
|
* @return JetBrains产品激活码字符串
|
||||||
|
*/
|
||||||
|
@PostMapping("/generate")
|
||||||
|
public String generateLicense(@RequestBody GenerateLicenseReqBody body) {
|
||||||
|
// 定义产品代码集合,用于存储所有需要包含在许可证中的产品代码
|
||||||
|
Set<String> productCodeSet;
|
||||||
|
|
||||||
|
// 判断是否指定了产品代码
|
||||||
|
if (CharSequenceUtil.isBlank(body.getProductCode())) {
|
||||||
|
// 未指定产品代码,自动包含所有可用产品
|
||||||
|
|
||||||
|
// 获取所有JetBrains IDE产品代码
|
||||||
|
List<String> productCodeList = ProductsContextHolder.productCacheList()
|
||||||
|
.stream()
|
||||||
|
.map(ProductsContextHolder.ProductCache::getProductCode) // 提取产品代码
|
||||||
|
.filter(StrUtil::isNotBlank) // 过滤空值
|
||||||
|
.map(productCode -> CharSequenceUtil.splitTrim(productCode, ",")) // 按逗号分割
|
||||||
|
.flatMap(Collection::stream) // 展平成一维数据流
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 获取所有付费插件代码
|
||||||
|
List<String> pluginCodeList = PluginsContextHolder.pluginCacheList()
|
||||||
|
.stream()
|
||||||
|
.map(PluginsContextHolder.PluginCache::getProductCode) // 提取插件产品代码
|
||||||
|
.filter(StrUtil::isNotBlank) // 过滤空值
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 合并IDE产品代码和插件代码,去除重复
|
||||||
|
productCodeSet = CollUtil.newHashSet(productCodeList);
|
||||||
|
productCodeSet.addAll(pluginCodeList);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 已指定产品代码,解析用户输入的产品代码列表
|
||||||
|
productCodeSet = CollUtil.newHashSet(CharSequenceUtil.splitTrim(body.getProductCode(), ','));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用许可证生成服务,生成最终的激活码
|
||||||
|
return LicenseContextHolder.generateLicense(
|
||||||
|
body.getLicenseName(), // 许可证名称
|
||||||
|
body.getAssigneeName(), // 被授权人名称
|
||||||
|
body.getExpiryDate(), // 过期日期
|
||||||
|
productCodeSet // 产品代码集合
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件激活码生成请求体类
|
||||||
|
*
|
||||||
|
* <p>此内部类用于封装生成JetBrains插件激活码时的请求参数。
|
||||||
|
* 专门针对付费插件的激活需求设计。
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class GeneratePluginLicenseReqBody {
|
||||||
|
/** 许可证名称(组织名称或公司名称) */
|
||||||
|
private String licenseeName;
|
||||||
|
|
||||||
|
/** 被授权人名称(使用者名称) */
|
||||||
|
private String assigneeName;
|
||||||
|
|
||||||
|
/** 过期日期(格式:yyyy-MM-dd) */
|
||||||
|
private String expiryDate;
|
||||||
|
|
||||||
|
/** 插件ID */
|
||||||
|
private String pluginId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package com.qiumo.help.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import com.qiumo.help.model.ObtainTicketResponse;
|
||||||
|
import com.qiumo.help.model.PingResponse;
|
||||||
|
import com.qiumo.help.model.ProlongTicketResponse;
|
||||||
|
import com.qiumo.help.model.ReleaseTicketResponse;
|
||||||
|
import com.qiumo.help.util.LicenseServerUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证服务器控制器
|
||||||
|
*
|
||||||
|
* <p>此控制器模拟JetBrains官方许可证服务器的行为,提供以下主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>获取许可证凭证(obtainTicket)</li>
|
||||||
|
* <li>检查服务器连通性(ping)</li>
|
||||||
|
* <li>延长许可证有效期(prolongTicket)</li>
|
||||||
|
* <li>释放许可证凭证(releaseTicket)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>所有的接口都返回XML格式的数据,并使用RSA签名保证数据的安全性。
|
||||||
|
*
|
||||||
|
* <p>路径说明:
|
||||||
|
* 所有的请求路径都以 {@code /rpc/} 开头,这是为了兼容JetBrains官方许可证服务器的API设计。
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j(topic = "许可证服务器") // 使用自定义日志主题
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value = "/rpc", produces = "text/xml; charset=UTF-8") // XML输出格式,兼容JetBrains协议
|
||||||
|
public class LicenseServerController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取许可证凭证接口
|
||||||
|
*
|
||||||
|
* <p>这是JetBrains产品首次连接许可证服务器时调用的接口。
|
||||||
|
* 该接口会生成一个许可证凭证(ticket),并返回相关的许可证信息。
|
||||||
|
*
|
||||||
|
* <p>请求参数说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>hostName - 客户端主机名,用于标识许可证使用者</li>
|
||||||
|
* <li>machineId - 客户端机器唯一标识符,用于硬件绑定</li>
|
||||||
|
* <li>salt - 加密盐值,用于增强通信安全性</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>返回内容包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>ticketId - 凭证唯一标识符</li>
|
||||||
|
* <li>ticketProperties - 凭证属性,包含许可证类型和有效期信息</li>
|
||||||
|
* <li>serverLease - 服务器租约信息</li>
|
||||||
|
* <li>confirmationStamp - 确认时间戳,用于验证请求的有效性</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param request HTTP请求对象,包含客户端参数
|
||||||
|
* @return 签名后的XML响应数据
|
||||||
|
*/
|
||||||
|
@RequestMapping("/obtainTicket.action")
|
||||||
|
public Object obtainTicket(HttpServletRequest request) {
|
||||||
|
// 提取请求参数
|
||||||
|
String hostName = request.getParameter("hostName");
|
||||||
|
String machineId = request.getParameter("machineId");
|
||||||
|
String salt = request.getParameter("salt");
|
||||||
|
|
||||||
|
log.debug("接收到获取许可证凭证请求 - 主机: {}, 机器ID: {}", hostName, machineId);
|
||||||
|
|
||||||
|
// 构建响应对象
|
||||||
|
ObtainTicketResponse response = new ObtainTicketResponse();
|
||||||
|
response.setAction("NONE"); // 无需额外操作
|
||||||
|
response.setConfirmationStamp(LicenseServerUtils.getConfirmationStamp(machineId)); // 生成确认时间戳
|
||||||
|
response.setLeaseSignature(LicenseServerUtils.getLeaseSignature()); // 生成租约签名
|
||||||
|
response.setMessage(""); // 无错误消息
|
||||||
|
response.setProlongationPeriod("600000"); // 延长周期(毫秒)
|
||||||
|
response.setResponseCode("OK"); // 响应成功
|
||||||
|
response.setSalt(salt); // 返回原始盐值
|
||||||
|
response.setServerLease(LicenseServerUtils.leaseContent); // 服务器租约内容
|
||||||
|
response.setServerUid(LicenseServerUtils.serverUid); // 服务器唯一标识
|
||||||
|
response.setTicketId(RandomUtil.randomString(10)); // 生成随机凭证ID
|
||||||
|
|
||||||
|
// 设置凭证属性,标识为个人许可证
|
||||||
|
response.setTicketProperties(String.format(
|
||||||
|
"licensee=%s\tlicenseeType=5\tmetadata=0120211231PSAN000005",
|
||||||
|
hostName
|
||||||
|
)); // Personal License 个人许可证标识
|
||||||
|
|
||||||
|
response.setValidationDeadlinePeriod("-1"); // 无验证截止日期
|
||||||
|
response.setValidationPeriod("600000"); // 验证周期(毫秒)
|
||||||
|
|
||||||
|
log.debug("许可证凭证生成成功 - 凭证ID: {}", response.getTicketId());
|
||||||
|
|
||||||
|
// 返回签名后的XML数据
|
||||||
|
return LicenseServerUtils.getSignXml(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证服务器连通性检查接口
|
||||||
|
*
|
||||||
|
* <p>JetBrains产品会定期调用此接口来检查许可证服务器的可用性。
|
||||||
|
* 该接口主要用于:
|
||||||
|
* <ul>
|
||||||
|
* <li>验证服务器是否正常运行</li>
|
||||||
|
* <li>检查网络连通性</li>
|
||||||
|
* <li>更新服务器状态信息</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>请求参数与 {@link #obtainTicket} 相同,但响应的内容略有不同。
|
||||||
|
*
|
||||||
|
* @param request HTTP请求对象,包含客户端参数
|
||||||
|
* @return 签名后的XML响应数据
|
||||||
|
*/
|
||||||
|
@RequestMapping("/ping.action")
|
||||||
|
public Object ping(HttpServletRequest request) {
|
||||||
|
// 提取请求参数
|
||||||
|
String hostName = request.getParameter("hostName");
|
||||||
|
String machineId = request.getParameter("machineId");
|
||||||
|
String salt = request.getParameter("salt");
|
||||||
|
|
||||||
|
log.debug("接收到Ping请求 - 主机: {}, 机器ID: {}", hostName, machineId);
|
||||||
|
|
||||||
|
// 构建响应对象
|
||||||
|
PingResponse response = new PingResponse();
|
||||||
|
response.setAction("NONE"); // 无需额外操作
|
||||||
|
response.setConfirmationStamp(LicenseServerUtils.getConfirmationStamp(machineId)); // 生成确认时间戳
|
||||||
|
response.setLeaseSignature(LicenseServerUtils.getLeaseSignature()); // 生成租约签名
|
||||||
|
response.setMessage(""); // 无错误消息
|
||||||
|
response.setResponseCode("OK"); // 响应成功
|
||||||
|
response.setSalt(salt); // 返回原始盐值
|
||||||
|
response.setServerLease(LicenseServerUtils.leaseContent); // 服务器租约内容
|
||||||
|
response.setServerUid(LicenseServerUtils.serverUid); // 服务器唯一标识
|
||||||
|
response.setValidationDeadlinePeriod("-1"); // 无验证截止日期
|
||||||
|
response.setValidationPeriod("600000"); // 验证周期(毫秒)
|
||||||
|
|
||||||
|
log.debug("Ping响应生成成功");
|
||||||
|
|
||||||
|
// 返回签名后的XML数据
|
||||||
|
return LicenseServerUtils.getSignXml(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延长许可证有效期接口
|
||||||
|
*
|
||||||
|
* <p>当现有的许可证凭证即将过期时,JetBrains产品会调用此接口来延长凭证的有效期。
|
||||||
|
* 该接口主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>更新许可证的有效期</li>
|
||||||
|
* <li>延长凭证的使用时间</li>
|
||||||
|
* <li>确保产品的连续使用</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>此接口的响应格式与 {@link #obtainTicket} 基本相同。
|
||||||
|
*
|
||||||
|
* @param request HTTP请求对象,包含客户端参数
|
||||||
|
* @return 签名后的XML响应数据
|
||||||
|
*/
|
||||||
|
@RequestMapping("/prolongTicket.action")
|
||||||
|
public Object prolongTicket(HttpServletRequest request) {
|
||||||
|
// 提取请求参数
|
||||||
|
String hostName = request.getParameter("hostName");
|
||||||
|
String machineId = request.getParameter("machineId");
|
||||||
|
String salt = request.getParameter("salt");
|
||||||
|
|
||||||
|
log.debug("接收到延长许可证请求 - 主机: {}, 机器ID: {}", hostName, machineId);
|
||||||
|
|
||||||
|
// 构建响应对象
|
||||||
|
ProlongTicketResponse response = new ProlongTicketResponse();
|
||||||
|
response.setAction("NONE"); // 无需额外操作
|
||||||
|
response.setConfirmationStamp(LicenseServerUtils.getConfirmationStamp(machineId)); // 生成确认时间戳
|
||||||
|
response.setLeaseSignature(LicenseServerUtils.getLeaseSignature()); // 生成租约签名
|
||||||
|
response.setMessage(""); // 无错误消息
|
||||||
|
response.setResponseCode("OK"); // 响应成功
|
||||||
|
response.setSalt(salt); // 返回原始盐值
|
||||||
|
response.setServerLease(LicenseServerUtils.leaseContent); // 服务器租约内容
|
||||||
|
response.setServerUid(LicenseServerUtils.serverUid); // 服务器唯一标识
|
||||||
|
response.setValidationDeadlinePeriod("-1"); // 无验证截止日期
|
||||||
|
response.setValidationPeriod("600000"); // 验证周期(毫秒)
|
||||||
|
|
||||||
|
log.debug("许可证延长响应生成成功");
|
||||||
|
|
||||||
|
// 返回签名后的XML数据
|
||||||
|
return LicenseServerUtils.getSignXml(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放许可证凭证接口
|
||||||
|
*
|
||||||
|
* <p>当JetBrains产品关闭或用户主动退出时,会调用此接口来释放占用的许可证凭证。
|
||||||
|
* 该接口主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>清理占用的许可证资源</li>
|
||||||
|
* <li>释放服务器端的相关缓存</li>
|
||||||
|
* <li>确保许可证能够被其他客户端使用</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>此接口通常在产品关闭时自动调用,也可以手动调用来主动释放许可证。
|
||||||
|
*
|
||||||
|
* @param request HTTP请求对象,包含客户端参数
|
||||||
|
* @return 签名后的XML响应数据
|
||||||
|
*/
|
||||||
|
@RequestMapping("/releaseTicket.action")
|
||||||
|
public Object releaseTicket(HttpServletRequest request) {
|
||||||
|
// 提取请求参数
|
||||||
|
String hostName = request.getParameter("hostName");
|
||||||
|
String machineId = request.getParameter("machineId");
|
||||||
|
String salt = request.getParameter("salt");
|
||||||
|
|
||||||
|
log.debug("接收到释放许可证请求 - 主机: {}, 机器ID: {}", hostName, machineId);
|
||||||
|
|
||||||
|
// 构建响应对象
|
||||||
|
ReleaseTicketResponse response = new ReleaseTicketResponse();
|
||||||
|
response.setAction("NONE"); // 无需额外操作
|
||||||
|
response.setConfirmationStamp(LicenseServerUtils.getConfirmationStamp(machineId)); // 生成确认时间戳
|
||||||
|
response.setLeaseSignature(LicenseServerUtils.getLeaseSignature()); // 生成租约签名
|
||||||
|
response.setMessage(""); // 无错误消息
|
||||||
|
response.setResponseCode("OK"); // 响应成功
|
||||||
|
response.setSalt(salt); // 返回原始盐值
|
||||||
|
response.setServerLease(LicenseServerUtils.leaseContent); // 服务器租约内容
|
||||||
|
response.setServerUid(LicenseServerUtils.serverUid); // 服务器唯一标识
|
||||||
|
response.setValidationDeadlinePeriod("-1"); // 无验证截止日期
|
||||||
|
response.setValidationPeriod("600000"); // 验证周期(毫秒)
|
||||||
|
|
||||||
|
log.debug("许可证释放响应生成成功");
|
||||||
|
|
||||||
|
// 返回签名后的XML数据
|
||||||
|
return LicenseServerUtils.getSignXml(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.qiumo.help.controller;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION;
|
||||||
|
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import com.qiumo.help.context.AgentContextHolder;
|
||||||
|
import java.io.File;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件下载控制器
|
||||||
|
*
|
||||||
|
* <p>此控制器专门用于提供文件下载服务,主要包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>ja-netfilter代理工具的下载</li>
|
||||||
|
* <li>其他相关工具文件的下载</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>ja-netfilter是一个基于Java Instrumentation API实现的动态字节码修改工具,
|
||||||
|
* 主要用于在应用程序运行时动态修改字节码,实现特定的功能。
|
||||||
|
*
|
||||||
|
* <p>下载的文件包含:
|
||||||
|
* <ul>
|
||||||
|
* <li>ja-netfilter核心程序</li>
|
||||||
|
* <li>预配置的配置文件</li>
|
||||||
|
* <li>相关的说明文档</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Slf4j(topic = "文件下载") // 使用自定义日志主题
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor // Lombok自动生成构造函数
|
||||||
|
public class ZipController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载ja-netfilter代理工具接口
|
||||||
|
*
|
||||||
|
* <p>此接口提供预配置的ja-netfilter工具包下载服务。
|
||||||
|
* ja-netfilter是一个强大的Java字节码修改工具,可以在运行时动态修改Java应用程序。
|
||||||
|
*
|
||||||
|
* <p>下载的文件包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>ja-netfilter.jar - 核心程序</li>
|
||||||
|
* <li>plugins/ - 各种功能插件</li>
|
||||||
|
* <li>config/ - 配置文件,已预配置了JetBrains产品的相关参数</li>
|
||||||
|
* <li>README.md - 使用说明</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>使用方法:
|
||||||
|
* <ol>
|
||||||
|
* <li>下载并解压文件</li>
|
||||||
|
* <li>在JetBrains IDE启动参数中添加:{@code -javaagent:/path/to/ja-netfilter.jar}</li>
|
||||||
|
* <li>重新启动IDE即可</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>注意事项:
|
||||||
|
* <ul>
|
||||||
|
* <li>请确保使用合法的许可证</li>
|
||||||
|
* <li>仅供学习和研究使用</li>
|
||||||
|
* <li>不得用于商业目的</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @return 包含ja-netfilter工具的ZIP文件响应
|
||||||
|
*/
|
||||||
|
@GetMapping("ja-netfilter")
|
||||||
|
public ResponseEntity<Resource> downloadJaNetfilter() {
|
||||||
|
log.info("接收到ja-netfilter下载请求");
|
||||||
|
|
||||||
|
// 从AgentContextHolder获取ja-netfilter ZIP文件
|
||||||
|
File jaNetfilterZipFile = AgentContextHolder.jaNetfilterZipFile();
|
||||||
|
|
||||||
|
log.debug("ja-netfilter文件路径: {}, 文件大小: {} 字节",
|
||||||
|
jaNetfilterZipFile.getAbsolutePath(),
|
||||||
|
jaNetfilterZipFile.length());
|
||||||
|
|
||||||
|
// 构建响应,设置为附件下载
|
||||||
|
ResponseEntity<Resource> response = ResponseEntity.ok()
|
||||||
|
.header(CONTENT_DISPOSITION, "attachment;filename=" + jaNetfilterZipFile.getName()) // 设置下载文件名
|
||||||
|
.contentType(APPLICATION_OCTET_STREAM) // 设置内容类型为二进制流
|
||||||
|
.body(new InputStreamResource(FileUtil.getInputStream(jaNetfilterZipFile))); // 设置响应体为文件流
|
||||||
|
|
||||||
|
log.info("ja-netfilter下载响应已生成");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.qiumo.help.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取许可证凭证响应实体类
|
||||||
|
*
|
||||||
|
* <p>此类封装了JetBrains许可证服务器在处理 "obtainTicket" 请求时返回的响应数据。
|
||||||
|
* 该响应包含了许可证凭证的所有必要信息,包括凭证ID、属性、服务器信息等。
|
||||||
|
*
|
||||||
|
* <p>使用JAXB注解支持XML序列化,保证与JetBrains官方许可证服务器协议的兼容性。
|
||||||
|
*
|
||||||
|
* <p>响应结构说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>action - 需要执行的操作,通常为 "NONE"</li>
|
||||||
|
* <li>confirmationStamp - 确认时间戳,用于验证请求的有效性</li>
|
||||||
|
* <li>leaseSignature - 租约签名,用于验证服务器的合法性</li>
|
||||||
|
* <li>ticketId - 凭证唯一标识符</li>
|
||||||
|
* <li>ticketProperties - 凭证属性,包含许可证类型和有效期信息</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@XmlRootElement(name = "ObtainTicketResponse") // XML根元素名称
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD) // 使用字段直接访问方式
|
||||||
|
public class ObtainTicketResponse implements Serializable {
|
||||||
|
|
||||||
|
// ==================== 响应字段定义 ====================
|
||||||
|
|
||||||
|
/** 需要执行的动作,通常为 "NONE" 表示无需额外操作 */
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
/** 确认时间戳,由服务器生成,用于验证请求的有效性和及时性 */
|
||||||
|
private String confirmationStamp;
|
||||||
|
|
||||||
|
/** 租约签名,用于验证服务器的合法性和权威性 */
|
||||||
|
private String leaseSignature;
|
||||||
|
|
||||||
|
/** 响应消息,通常为空,只在出现错误时包含错误信息 */
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/** 延长周期(毫秒),表示多久后可以申请延长许可证 */
|
||||||
|
private String prolongationPeriod;
|
||||||
|
|
||||||
|
/** 响应码,"OK" 表示成功,其他值表示各种错误情况 */
|
||||||
|
private String responseCode;
|
||||||
|
|
||||||
|
/** 加密盐值,从请求中原样返回,用于增强通信安全性 */
|
||||||
|
private String salt;
|
||||||
|
|
||||||
|
/** 服务器租约内容,包含过期时间和服务器标识 */
|
||||||
|
private String serverLease;
|
||||||
|
|
||||||
|
/** 服务器唯一标识符,用于标识许可证服务器实例 */
|
||||||
|
private String serverUid;
|
||||||
|
|
||||||
|
/** 许可证凭证的唯一标识符,由服务器随机生成 */
|
||||||
|
private String ticketId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证凭证属性,包含以下信息:
|
||||||
|
* - licensee: 许可证持有者名称
|
||||||
|
* - licenseeType: 许可证类型(如 5 表示个人许可证)
|
||||||
|
* - metadata: 元数据信息,包含版本和有效期信息
|
||||||
|
*/
|
||||||
|
private String ticketProperties;
|
||||||
|
|
||||||
|
/** 验证截止时间(毫秒),-1 表示无截止时间 */
|
||||||
|
private String validationDeadlinePeriod;
|
||||||
|
|
||||||
|
/** 验证周期(毫秒),表示多久后需要重新验证 */
|
||||||
|
private String validationPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.qiumo.help.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ping请求响应实体类
|
||||||
|
*
|
||||||
|
* <p>此类封装了JetBrains许可证服务器在处理 "ping" 请求时返回的响应数据。
|
||||||
|
* Ping请求主要用于检查许可证服务器的可用性和连通性。
|
||||||
|
*
|
||||||
|
* <p>与 {@link ObtainTicketResponse} 相比,此响应不包含许可证凭证相关信息,
|
||||||
|
* 主要用于确认服务器状态和更新连接信息。
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@XmlRootElement(name = "PingResponse") // XML根元素名称
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD) // 使用字段直接访问方式
|
||||||
|
public class PingResponse implements Serializable {
|
||||||
|
|
||||||
|
/** 需要执行的动作,通常为 "NONE" */
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
/** 确认时间戳,用于验证请求的有效性 */
|
||||||
|
private String confirmationStamp;
|
||||||
|
|
||||||
|
/** 租约签名,用于验证服务器的合法性 */
|
||||||
|
private String leaseSignature;
|
||||||
|
|
||||||
|
/** 响应消息,通常为空 */
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/** 响应码,"OK" 表示成功 */
|
||||||
|
private String responseCode;
|
||||||
|
|
||||||
|
/** 加密盐值,从请求中原样返回 */
|
||||||
|
private String salt;
|
||||||
|
|
||||||
|
/** 服务器租约内容 */
|
||||||
|
private String serverLease;
|
||||||
|
|
||||||
|
/** 服务器唯一标识符 */
|
||||||
|
private String serverUid;
|
||||||
|
|
||||||
|
/** 验证截止时间,-1 表示无截止时间 */
|
||||||
|
private String validationDeadlinePeriod;
|
||||||
|
|
||||||
|
/** 验证周期,表示多久后需要重新验证 */
|
||||||
|
private String validationPeriod;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.qiumo.help.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 延长许可证凭证响应实体类
|
||||||
|
*
|
||||||
|
* <p>此类封装了JetBrains许可证服务器在处理 "prolongTicket" 请求时返回的响应数据。
|
||||||
|
* 延长请求用于在许可证凭证即将过期时延长其有效期。
|
||||||
|
*
|
||||||
|
* <p>此响应的结构与 {@link ObtainTicketResponse} 基本相同,
|
||||||
|
* 但不包含新的凭证ID和属性,主要用于更新现有凭证的有效期。
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@XmlRootElement(name = "ProlongTicketResponse") // XML根元素名称
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD) // 使用字段直接访问方式
|
||||||
|
public class ProlongTicketResponse implements Serializable {
|
||||||
|
|
||||||
|
/** 需要执行的动作,通常为 "NONE" */
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
/** 确认时间戳,用于验证请求的有效性 */
|
||||||
|
private String confirmationStamp;
|
||||||
|
|
||||||
|
/** 租约签名,用于验证服务器的合法性 */
|
||||||
|
private String leaseSignature;
|
||||||
|
|
||||||
|
/** 响应消息,通常为空 */
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/** 响应码,"OK" 表示成功 */
|
||||||
|
private String responseCode;
|
||||||
|
|
||||||
|
/** 加密盐值,从请求中原样返回 */
|
||||||
|
private String salt;
|
||||||
|
|
||||||
|
/** 服务器租约内容 */
|
||||||
|
private String serverLease;
|
||||||
|
|
||||||
|
/** 服务器唯一标识符 */
|
||||||
|
private String serverUid;
|
||||||
|
|
||||||
|
/** 验证截止时间,-1 表示无截止时间 */
|
||||||
|
private String validationDeadlinePeriod;
|
||||||
|
|
||||||
|
/** 验证周期,表示多久后需要重新验证 */
|
||||||
|
private String validationPeriod;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.qiumo.help.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 释放许可证凭证响应实体类
|
||||||
|
*
|
||||||
|
* <p>此类封装了JetBrains许可证服务器在处理 "releaseTicket" 请求时返回的响应数据。
|
||||||
|
* 释放请求用于在JetBrains产品关闭或用户主动退出时清理资源。
|
||||||
|
*
|
||||||
|
* <p>此响应的结构与其他响应类型基本相同,
|
||||||
|
* 主要用于确认许可证凭证已成功释放和清理相关资源。
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@XmlRootElement(name = "ReleaseTicketResponse") // XML根元素名称
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD) // 使用字段直接访问方式
|
||||||
|
public class ReleaseTicketResponse implements Serializable {
|
||||||
|
|
||||||
|
/** 需要执行的动作,通常为 "NONE" */
|
||||||
|
private String action;
|
||||||
|
|
||||||
|
/** 确认时间戳,用于验证请求的有效性 */
|
||||||
|
private String confirmationStamp;
|
||||||
|
|
||||||
|
/** 租约签名,用于验证服务器的合法性 */
|
||||||
|
private String leaseSignature;
|
||||||
|
|
||||||
|
/** 响应消息,通常为空 */
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
/** 响应码,"OK" 表示成功 */
|
||||||
|
private String responseCode;
|
||||||
|
|
||||||
|
/** 加密盐值,从请求中原样返回 */
|
||||||
|
private String salt;
|
||||||
|
|
||||||
|
/** 服务器租约内容 */
|
||||||
|
private String serverLease;
|
||||||
|
|
||||||
|
/** 服务器唯一标识符 */
|
||||||
|
private String serverUid;
|
||||||
|
|
||||||
|
/** 验证截止时间,-1 表示无截止时间 */
|
||||||
|
private String validationDeadlinePeriod;
|
||||||
|
|
||||||
|
/** 验证周期,表示多久后需要重新验证 */
|
||||||
|
private String validationPeriod;
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.qiumo.help.service;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.json.JSONArray;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.qiumo.help.util.jrebel.JrebelSign;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class JrebelService {
|
||||||
|
|
||||||
|
private static final String SERVER_GUID = "a1b4aea8-b031-4302-b602-670a990272cb";
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public void jrebelLeasesHandler(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
response.setContentType("application/json; charset=utf-8");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
String clientRandomness = request.getParameter("randomness");
|
||||||
|
String username = request.getParameter("username");
|
||||||
|
String guid = request.getParameter("guid");
|
||||||
|
boolean offline = true;
|
||||||
|
String validFrom = "";
|
||||||
|
String validUntil = "";
|
||||||
|
String clientTime = request.getParameter("clientTime");
|
||||||
|
|
||||||
|
try {
|
||||||
|
long clientTimeMillis = Long.parseLong(clientTime);
|
||||||
|
validFrom = clientTime;
|
||||||
|
validUntil = String.valueOf(clientTimeMillis + 180L * 24 * 60 * 60 * 1000);
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
// 忽略非法输入,保持默认空值
|
||||||
|
}
|
||||||
|
JSONObject jsonObject = JSONUtil.createObj().set("serverVersion", "3.2.4")
|
||||||
|
.set("serverProtocolVersion", "1.1")
|
||||||
|
.set("serverGuid", SERVER_GUID)
|
||||||
|
.set("groupType", "managed")
|
||||||
|
.set("id", 1)
|
||||||
|
.set("licenseType", 1)
|
||||||
|
.set("evaluationLicense", false)
|
||||||
|
.set("signature",
|
||||||
|
"OJE9wGg2xncSb+VgnYT+9HGCFaLOk28tneMFhCbpVMKoC/Iq4LuaDKPirBjG4o394/UjCDGgTBpIrzcXNPdVxVr8PnQzpy7ZSToGO8wv/KIWZT9/ba7bDbA8/RZ4B37YkCeXhjaixpmoyz/CIZMnei4q7oWR7DYUOlOcEWDQhiY=")
|
||||||
|
.set("serverRandomness", "H2ulzLlh7E0=")
|
||||||
|
.set("statusCode", "SUCCESS")
|
||||||
|
.set("offline", offline)
|
||||||
|
.set("validFrom", validFrom)
|
||||||
|
.set("validUntil", validUntil)
|
||||||
|
.set("company", "Administrator")
|
||||||
|
.set("orderId", "")
|
||||||
|
.set("zeroIds", new JSONArray())
|
||||||
|
.set("licenseValidFrom", validFrom)
|
||||||
|
.set("licenseValidUntil", validUntil);
|
||||||
|
if (clientRandomness == null || username == null || guid == null) {
|
||||||
|
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||||
|
} else {
|
||||||
|
JrebelSign jrebelSign = new JrebelSign();
|
||||||
|
jrebelSign.toLeaseCreateJson(clientRandomness, guid, offline, validFrom, validUntil);
|
||||||
|
String signature = jrebelSign.getSignature();
|
||||||
|
jsonObject.set("signature", signature);
|
||||||
|
jsonObject.set("company", username);
|
||||||
|
response.getWriter().write(jsonObject.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public void jrebelValidateHandler(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
response.setContentType("application/json; charset=utf-8");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
JSONObject jsonObject = JSONUtil.createObj().set("serverVersion", "3.2.4")
|
||||||
|
.set("serverProtocolVersion", "1.1")
|
||||||
|
.set("serverGuid", SERVER_GUID)
|
||||||
|
.set("groupType", "managed")
|
||||||
|
.set("statusCode", "SUCCESS")
|
||||||
|
.set("company", "Administrator")
|
||||||
|
.set("canGetLease", true)
|
||||||
|
.set("licenseType", 1)
|
||||||
|
.set("evaluationLicense", false)
|
||||||
|
.set("seatPoolType", "standalone");
|
||||||
|
response.getWriter().write(jsonObject.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public void jrebelLeases1Handler(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
response.setContentType("application/json; charset=utf-8");
|
||||||
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
|
JSONObject jsonObject = JSONUtil.createObj().set("serverVersion", "3.2.4")
|
||||||
|
.set("serverProtocolVersion", "1.1")
|
||||||
|
.set("serverGuid", SERVER_GUID)
|
||||||
|
.set("groupType", "managed")
|
||||||
|
.set("statusCode", "SUCCESS")
|
||||||
|
.set("msg", null)
|
||||||
|
.set("statusMessage", null);
|
||||||
|
String username = request.getParameter("username");
|
||||||
|
if(ObjectUtil.isNotEmpty(username)){
|
||||||
|
jsonObject.set("company", username);
|
||||||
|
}
|
||||||
|
response.getWriter().write(jsonObject.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
package com.qiumo.help.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.text.CharSequenceUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import org.springframework.boot.system.ApplicationHome;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件工具接口
|
||||||
|
*
|
||||||
|
* <p>此接口提供了一系列静态方法用于处理应用程序中的文件操作。
|
||||||
|
* 它能够智能地处理开发环境和生产环境下的文件路径问题,
|
||||||
|
* 并提供了从类路径复制文件到外部目录的功能。
|
||||||
|
*
|
||||||
|
* <p>主要功能:
|
||||||
|
* <ul>
|
||||||
|
* <li>文件存在性检查</li>
|
||||||
|
* <li>智能文件路径解析(支持开发和生产环境)</li>
|
||||||
|
* <li>自动从类路径复制文件到外部目录</li>
|
||||||
|
* <li>目录结构的自动创建</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>路径解析规则:
|
||||||
|
* <ul>
|
||||||
|
* <li>开发环境:直接使用类路径中的文件</li>
|
||||||
|
* <li>生产环境:JAR包启动时,使用应用目录下的文件</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
public interface FileTools {
|
||||||
|
|
||||||
|
// ==================== 常量定义 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Boot应用程序主目录工具
|
||||||
|
* 用于获取应用程序的主目录和源文件信息
|
||||||
|
*/
|
||||||
|
ApplicationHome application = new ApplicationHome();
|
||||||
|
|
||||||
|
// ==================== 核心方法 ====================
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查指定路径的文件是否存在
|
||||||
|
*
|
||||||
|
* <p>此方法会根据当前的运行环境(开发或生产)来决定文件的实际位置。
|
||||||
|
* 在开发环境中,直接从类路径中查找文件;
|
||||||
|
* 在生产环境中,从应用程序所在目录下查找文件。
|
||||||
|
*
|
||||||
|
* @param path 相对路径,相对于类路径或应用程序目录
|
||||||
|
* @return 如果文件存在则返回true,否则返回false
|
||||||
|
*/
|
||||||
|
static boolean fileExists(String path) {
|
||||||
|
return getFile(path).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定路径的文件对象
|
||||||
|
*
|
||||||
|
* <p>此方法是FileTools的核心方法,它会根据当前的运行环境智能地选择文件路径。
|
||||||
|
*
|
||||||
|
* <p>路径解析规则:
|
||||||
|
* <ul>
|
||||||
|
* <li>开发环境(source为null):直接使用类路径中的文件</li>
|
||||||
|
* <li>生产环境(JAR包运行):使用应用程序所在目录下的文件</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>这种设计允许应用程序在不同环境下都能正确地找到所需的文件。
|
||||||
|
*
|
||||||
|
* @param path 相对路径,相对于类路径或应用程序目录
|
||||||
|
* @return 文件对象(可能不存在)
|
||||||
|
*/
|
||||||
|
static File getFile(String path) {
|
||||||
|
// 获取应用程序的主目录和源文件
|
||||||
|
File homeDir = application.getDir();
|
||||||
|
File source = application.getSource();
|
||||||
|
|
||||||
|
// 创建类路径资源对象,用于在开发环境中访问文件
|
||||||
|
ClassPathResource classPathResource = new ClassPathResource(path);
|
||||||
|
|
||||||
|
// 根据运行环境返回不同的文件对象
|
||||||
|
return ObjectUtil.isNull(source)
|
||||||
|
? FileUtil.file(classPathResource.getPath()) // 开发环境:使用类路径
|
||||||
|
: FileUtil.file(homeDir, path); // 生产环境:使用应用目录
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定路径的文件对象,如果文件不存在则从类路径复制
|
||||||
|
*
|
||||||
|
* <p>此方法是一个增强版的文件获取方法,它在 {@link #getFile(String)} 的基础上
|
||||||
|
* 添加了自动复制功能。当目标文件不存在时,会尝试从类路径中复制文件。
|
||||||
|
*
|
||||||
|
* <p>使用场景:
|
||||||
|
* <ul>
|
||||||
|
* <li>首次运行时初始化配置文件</li>
|
||||||
|
* <li>从内置资源复制模板文件</li>
|
||||||
|
* <li>确保在生产环境中有必要的文件</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>复制过程:
|
||||||
|
* <ol>
|
||||||
|
* <li>调用 {@link #getFile(String)} 获取目标文件对象</li>
|
||||||
|
* <li>在生产环境中,检查类路径中的源文件是否存在</li>
|
||||||
|
* <li>如果源文件存在但目标文件不存在,则进行复制操作</li>
|
||||||
|
* <li>复制过程中会自动创建必要的目录结构</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @param path 相对路径,相对于类路径或应用程序目录
|
||||||
|
* @return 文件对象(在此方法返回后应该存在)
|
||||||
|
* @throws IllegalArgumentException 当文件读取或复制失败时抛出
|
||||||
|
*/
|
||||||
|
static File getFileOrCreat(String path) {
|
||||||
|
// 获取目标文件对象
|
||||||
|
File file = getFile(path);
|
||||||
|
|
||||||
|
// 在生产环境中处理文件复制
|
||||||
|
if (ObjectUtil.isNotNull(application.getSource())) {
|
||||||
|
// 创建类路径资源对象
|
||||||
|
ClassPathResource classPathResource = new ClassPathResource(path);
|
||||||
|
|
||||||
|
// 检查是否需要从类路径复制文件
|
||||||
|
if (classPathResource.exists() && !file.exists()) {
|
||||||
|
try (InputStream inputStream = classPathResource.getInputStream()) {
|
||||||
|
// 使用Hutool的文件工具进行复制
|
||||||
|
FileUtil.writeFromStream(inputStream, file);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 抛出友好的错误信息
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
CharSequenceUtil.format("{} 文件读取失败!", path), e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
package com.qiumo.help.util;
|
||||||
|
|
||||||
|
import cn.hutool.core.codec.Base64;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.crypto.PemUtil;
|
||||||
|
import com.qiumo.help.context.CertificateContextHolder;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.Signature;
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.Marshaller;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 许可证服务器工具类
|
||||||
|
*
|
||||||
|
* <p>此工具类为许可证服务器提供各种工具方法,主要包括:
|
||||||
|
* <ul>
|
||||||
|
* <li>RSA数字签名功能</li>
|
||||||
|
* <li>确认时间戳生成</li>
|
||||||
|
* <li>租约签名生成</li>
|
||||||
|
* <li>XML数据签名和序列化</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>此类的所有方法都是静态的,不需要实例化。
|
||||||
|
* 所有的密码学操作都基于CertificateContextHolder中管理的证书和密钥。
|
||||||
|
*
|
||||||
|
* <p>安全性说明:
|
||||||
|
* <ul>
|
||||||
|
* <li>使用RSA非对称加密算法</li>
|
||||||
|
* <li>支持SHA1withRSA和SHA512withRSA签名算法</li>
|
||||||
|
* <li>所有签名都包含相应的X.509证书信息</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author QiuMo
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
@Slf4j(topic = "许可证服务器工具")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE) // 私有构造函数,防止实例化
|
||||||
|
|
||||||
|
public class LicenseServerUtils {
|
||||||
|
|
||||||
|
// ==================== 常量定义 ====================
|
||||||
|
|
||||||
|
/** 服务器唯一标识符,用于标识许可证服务器实例 */
|
||||||
|
public static final String serverUid = "qiumo";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器租约内容,格式为:过期时间戳:服务器UID
|
||||||
|
* 4102415999000 对应的日期为 2099-12-31 23:59:59,表示许可证长期有效
|
||||||
|
*/
|
||||||
|
public static final String leaseContent = "4102415999000:" + serverUid;
|
||||||
|
|
||||||
|
// ==================== 核心方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用指定的私钥和算法对内容进行数字签名
|
||||||
|
*
|
||||||
|
* <p>此方法是许可证服务器的核心签名功能,用于确保数据的完整性和可靠性。
|
||||||
|
* 签名过程采用标准的RSA数字签名算法,结果使用Base64编码。
|
||||||
|
*
|
||||||
|
* <p>支持的签名算法:
|
||||||
|
* <ul>
|
||||||
|
* <li>SHA1withRSA - 适用于一般安全级别</li>
|
||||||
|
* <li>SHA256withRSA - 适用于高安全级别</li>
|
||||||
|
* <li>SHA512withRSA - 适用于最高安全级别</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param content 需要签名的内容字符串
|
||||||
|
* @param privateKey RSA私钥,用于生成数字签名
|
||||||
|
* @param signAlgorithm 签名算法,如 "SHA1withRSA"、"SHA256withRSA" 等
|
||||||
|
* @return Base64编码的数字签名字符串
|
||||||
|
* @throws RuntimeException 当签名过程发生错误时抛出
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
public static String signContent(String content, PrivateKey privateKey, String signAlgorithm) {
|
||||||
|
log.debug("开始数字签名 - 算法: {}, 内容长度: {} 字符", signAlgorithm, content.length());
|
||||||
|
|
||||||
|
// 初始化签名器
|
||||||
|
Signature signature = Signature.getInstance(signAlgorithm);
|
||||||
|
signature.initSign(privateKey);
|
||||||
|
|
||||||
|
// 对内容进行签名(使用UTF-8编码)
|
||||||
|
signature.update(content.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
// 生成签名并转换为Base64格式
|
||||||
|
String signedContent = Base64.encode(signature.sign());
|
||||||
|
|
||||||
|
log.debug("数字签名完成 - 签名长度: {} 字符", signedContent.length());
|
||||||
|
|
||||||
|
return signedContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成机器确认时间戳
|
||||||
|
*
|
||||||
|
* <p>确认时间戳用于验证客户端请求的有效性和及时性。
|
||||||
|
* 它结合了当前时间戳、机器ID和数字签名,确保请求的安全性。
|
||||||
|
*
|
||||||
|
* <p>确认时间戳格式:
|
||||||
|
* {@code 时间戳:机器ID:签名算法:签名内容:证书Base64}
|
||||||
|
*
|
||||||
|
* <p>生成过程:
|
||||||
|
* <ol>
|
||||||
|
* <li>获取当前系统时间戳</li>
|
||||||
|
* <li>构建签名内容:时间戳 + ":" + 机器ID</li>
|
||||||
|
* <li>使用SHA1withRSA算法进行数字签名</li>
|
||||||
|
* <li>获取服务器子证书并转换为Base64</li>
|
||||||
|
* <li>组装最终的确认时间戳字符串</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @param machineId 客户端机器的唯一标识符
|
||||||
|
* @return 组装好的确认时间戳字符串
|
||||||
|
* @throws RuntimeException 当签名或证书处理发生错误时抛出
|
||||||
|
*/
|
||||||
|
public static String getConfirmationStamp(String machineId) {
|
||||||
|
log.debug("开始生成确认时间戳 - 机器ID: {}", machineId);
|
||||||
|
|
||||||
|
// 获取当前时间戳
|
||||||
|
long timeStamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// 构建需要签名的内容:时间戳 + ":" + 机器ID
|
||||||
|
String contentToSign = timeStamp + ":" + machineId;
|
||||||
|
|
||||||
|
// 获取私钥并进行数字签名
|
||||||
|
PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.privateKeyFile()));
|
||||||
|
String signature = signContent(contentToSign, privateKey, "SHA1withRSA");
|
||||||
|
|
||||||
|
// 获取服务器子证书并转换为Base64
|
||||||
|
String certificateBase64 = Base64.encode(PemUtil.readPem(IoUtil.toStream(CertificateContextHolder.serverChildCrtFile())));
|
||||||
|
|
||||||
|
// 组装最终的确认时间戳
|
||||||
|
String confirmationStamp = timeStamp + ":" + machineId + ":" + "SHA1withRSA" + ":" + signature + ":" + certificateBase64;
|
||||||
|
|
||||||
|
log.debug("确认时间戳生成完成 - 时间戳: {}", timeStamp);
|
||||||
|
|
||||||
|
return confirmationStamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成服务器租约签名
|
||||||
|
*
|
||||||
|
* <p>租约签名用于验证服务器的合法性和权威性。
|
||||||
|
* 它对预定义的租约内容进行数字签名,并附带相应的证书信息。
|
||||||
|
*
|
||||||
|
* <p>租约签名格式:
|
||||||
|
* {@code 签名算法-签名内容-证书Base64}
|
||||||
|
*
|
||||||
|
* <p>生成过程:
|
||||||
|
* <ol>
|
||||||
|
* <li>使用SHA512withRSA算法对租约内容进行数字签名</li>
|
||||||
|
* <li>获取授权码证书并转换为Base64</li>
|
||||||
|
* <li>按照指定格式组装签名结果</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>使用SHA512withRSA算法提供更高的安全性,适合于重要的服务器认证场景。
|
||||||
|
*
|
||||||
|
* @return 组装好的租约签名字符串
|
||||||
|
* @throws RuntimeException 当签名或证书处理发生错误时抛出
|
||||||
|
*/
|
||||||
|
public static String getLeaseSignature() {
|
||||||
|
log.debug("开始生成租约签名 - 租约内容: {}", leaseContent);
|
||||||
|
|
||||||
|
// 获取私钥并使用SHA512withRSA算法进行签名
|
||||||
|
PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.privateKeyFile()));
|
||||||
|
String signature = signContent(leaseContent, privateKey, "SHA512withRSA");
|
||||||
|
|
||||||
|
// 获取授权码证书并转换为Base64
|
||||||
|
String certificateBase64 = Base64.encode(PemUtil.readPem(IoUtil.toStream(CertificateContextHolder.codeCrtFile())));
|
||||||
|
|
||||||
|
// 按照格式组装租约签名
|
||||||
|
String leaseSignature = "SHA512withRSA-" + signature + "-" + certificateBase64;
|
||||||
|
|
||||||
|
log.debug("租约签名生成完成");
|
||||||
|
|
||||||
|
return leaseSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对XML数据进行数字签名并生成完整的XML响应
|
||||||
|
*
|
||||||
|
* <p>此方法是许可证服务器响应生成的核心方法。
|
||||||
|
* 它将Java对象序列化为XML,然后对XML内容进行数字签名,
|
||||||
|
* 最后在XML头部添加签名注释。
|
||||||
|
*
|
||||||
|
* <p>XML签名格式:
|
||||||
|
* <pre>
|
||||||
|
* <!-- SHA1withRSA-签名内容-证书Base64 -->
|
||||||
|
* <ResponseObject>
|
||||||
|
* ...实际XML内容...
|
||||||
|
* </ResponseObject>
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* <p>序列化过程:
|
||||||
|
* <ol>
|
||||||
|
* <li>使用JAXB将Java对象转换为XML字符串</li>
|
||||||
|
* <li>移除XML头部声明(设置JAXB_FRAGMENT为true)</li>
|
||||||
|
* <li>对XML内容使用SHA1withRSA进行数字签名</li>
|
||||||
|
* <li>获取服务器子证书并转换为Base64</li>
|
||||||
|
* <li>在XML内容前添加包含签名信息的注释</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* <p>此方法保证了XML响应的完整性和可验证性,
|
||||||
|
* JetBrains客户端可以通过验证签名来确认响应的真实性。
|
||||||
|
*
|
||||||
|
* @param xmlObject 需要签名的Java对象,必须具有JAXB注解
|
||||||
|
* @return 带有数字签名的完整XML字符串
|
||||||
|
* @throws RuntimeException 当XML序列化或签名过程发生错误时抛出
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
public static String getSignXml(Object xmlObject) {
|
||||||
|
log.debug("开始生成签名XML - 对象类型: {}", xmlObject.getClass().getSimpleName());
|
||||||
|
|
||||||
|
// 初始化JAXB上下文
|
||||||
|
JAXBContext jaxbContext = JAXBContext.newInstance(xmlObject.getClass());
|
||||||
|
|
||||||
|
// 创建序列化器
|
||||||
|
Marshaller marshaller = jaxbContext.createMarshaller();
|
||||||
|
|
||||||
|
// 设置序列化参数:去除XML头部声明
|
||||||
|
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
|
||||||
|
|
||||||
|
// 将Java对象序列化为XML字符串
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
marshaller.marshal(xmlObject, writer);
|
||||||
|
String xmlContent = writer.toString();
|
||||||
|
|
||||||
|
log.debug("XML序列化完成 - XML长度: {} 字符", xmlContent.length());
|
||||||
|
|
||||||
|
// 获取私钥并对XML内容进行数字签名
|
||||||
|
PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.privateKeyFile()));
|
||||||
|
String signature = signContent(xmlContent, privateKey, "SHA1withRSA");
|
||||||
|
|
||||||
|
// 获取服务器子证书并转换为Base64
|
||||||
|
String certificateBase64 = Base64.encode(PemUtil.readPem(IoUtil.toStream(CertificateContextHolder.serverChildCrtFile())));
|
||||||
|
|
||||||
|
// 构建带有签名注释的完整XML
|
||||||
|
String signedXml = "<!-- SHA1withRSA-" + signature + "-" + certificateBase64 + " -->\n" + xmlContent;
|
||||||
|
|
||||||
|
log.debug("签名XML生成完成 - 最终长度: {} 字符", signedXml.length());
|
||||||
|
|
||||||
|
return signedXml;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.qiumo.help.util.jrebel;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class ByteUtil {
|
||||||
|
|
||||||
|
private static final Random a;
|
||||||
|
|
||||||
|
public static String a(final byte[] binaryData) {
|
||||||
|
if (binaryData == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new String(Base64.getEncoder().encode(binaryData), Charset.forName("UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] a(final String s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Base64.getDecoder().decode(s.getBytes(Charset.forName("UTF-8")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] a(final int n) {
|
||||||
|
final byte[] array = new byte[n];
|
||||||
|
ByteUtil.a.nextBytes(array);
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
a = new Random();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.qiumo.help.util.jrebel;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
|
||||||
|
public class JrebelSign {
|
||||||
|
|
||||||
|
private String signature;
|
||||||
|
|
||||||
|
public void toLeaseCreateJson(String clientRandomness, String guid, boolean offline, String validFrom,
|
||||||
|
String validUntil) {
|
||||||
|
String serverRandomness = "H2ulzLlh7E0="; //服务端随机数,如果要自己生成,务必将其写到json的serverRandomness中
|
||||||
|
String installationGuidString = guid;
|
||||||
|
String s2 = "";
|
||||||
|
if (offline) {
|
||||||
|
s2 = CollUtil.join(
|
||||||
|
CollUtil.newArrayList(clientRandomness, serverRandomness, installationGuidString,
|
||||||
|
String.valueOf(offline), validFrom, validUntil), ";");
|
||||||
|
} else {
|
||||||
|
s2 = CollUtil.join(CollUtil.newArrayList(clientRandomness, serverRandomness, installationGuidString,
|
||||||
|
String.valueOf(offline)), ";");
|
||||||
|
}
|
||||||
|
final byte[] a2 = LicenseServer2ToJRebelPrivateKey.a(s2.getBytes());
|
||||||
|
this.signature = ByteUtil.a(a2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.qiumo.help.util.jrebel;
|
||||||
|
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyFactory;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.Signature;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
|
||||||
|
public class LicenseServer2ToJRebelPrivateKey {
|
||||||
|
|
||||||
|
private static final String b = "MIICXAIBAAKBgQDQ93CP6SjEneDizCF1P/MaBGf582voNNFcu8oMhgdTZ/N6qa6O7XJDr1FSCyaDdKSsPCdxPK7Y4Usq/fOPas2kCgYcRS/iebrtPEFZ/7TLfk39HLuTEjzo0/CNvjVsgWeh9BYznFaxFDLx7fLKqCQ6w1OKScnsdqwjpaXwXqiulwIDAQABAoGATOQvvBSMVsTNQkbgrNcqKdGjPNrwQtJkk13aO/95ZJxkgCc9vwPqPrOdFbZappZeHa5IyScOI2nLEfe+DnC7V80K2dBtaIQjOeZQt5HoTRG4EHQaWoDh27BWuJoip5WMrOd+1qfkOtZoRjNcHl86LIAh/+3vxYyebkug4UHNGPkCQQD+N4ZUkhKNQW7mpxX6eecitmOdN7Yt0YH9UmxPiW1LyCEbLwduMR2tfyGfrbZALiGzlKJize38shGC1qYSMvZFAkEA0m6psWWiTUWtaOKMxkTkcUdigalZ9xFSEl6jXFB94AD+dlPS3J5gNzTEmbPLc14VIWJFkO+UOrpl77w5uF2dKwJAaMpslhnsicvKMkv31FtBut5iK6GWeEafhdPfD94/bnidpP362yJl8Gmya4cI1GXvwH3pfj8S9hJVA5EFvgTB3QJBAJP1O1uAGp46X7Nfl5vQ1M7RYnHIoXkWtJ417Kb78YWPLVwFlD2LHhuy/okT4fk8LZ9LeZ5u1cp1RTdLIUqAiAECQC46OwOm87L35yaVfpUIjqg/1gsNwNsj8HvtXdF/9d30JIM3GwdytCvNRLqP35Ciogb9AO8ke8L6zY83nxPbClM=";
|
||||||
|
private static final byte[] c;
|
||||||
|
private static final BouncyCastleProvider d;
|
||||||
|
|
||||||
|
private static PrivateKey a() {
|
||||||
|
final PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(LicenseServer2ToJRebelPrivateKey.c);
|
||||||
|
try {
|
||||||
|
return KeyFactory.getInstance("RSA", LicenseServer2ToJRebelPrivateKey.d)
|
||||||
|
.generatePrivate(pkcs8EncodedKeySpec);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] a(final byte[] array) {
|
||||||
|
try {
|
||||||
|
final Signature instance = Signature.getInstance("SHA1withRSA", LicenseServer2ToJRebelPrivateKey.d);
|
||||||
|
instance.initSign(a());
|
||||||
|
instance.update(array);
|
||||||
|
return instance.sign();
|
||||||
|
} catch (GeneralSecurityException ex) {
|
||||||
|
throw new RuntimeException("License Server installation error 0000000F2", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
c = ByteUtil.a(
|
||||||
|
"MIICXAIBAAKBgQDQ93CP6SjEneDizCF1P/MaBGf582voNNFcu8oMhgdTZ/N6qa6O7XJDr1FSCyaDdKSsPCdxPK7Y4Usq/fOPas2kCgYcRS/iebrtPEFZ/7TLfk39HLuTEjzo0/CNvjVsgWeh9BYznFaxFDLx7fLKqCQ6w1OKScnsdqwjpaXwXqiulwIDAQABAoGATOQvvBSMVsTNQkbgrNcqKdGjPNrwQtJkk13aO/95ZJxkgCc9vwPqPrOdFbZappZeHa5IyScOI2nLEfe+DnC7V80K2dBtaIQjOeZQt5HoTRG4EHQaWoDh27BWuJoip5WMrOd+1qfkOtZoRjNcHl86LIAh/+3vxYyebkug4UHNGPkCQQD+N4ZUkhKNQW7mpxX6eecitmOdN7Yt0YH9UmxPiW1LyCEbLwduMR2tfyGfrbZALiGzlKJize38shGC1qYSMvZFAkEA0m6psWWiTUWtaOKMxkTkcUdigalZ9xFSEl6jXFB94AD+dlPS3J5gNzTEmbPLc14VIWJFkO+UOrpl77w5uF2dKwJAaMpslhnsicvKMkv31FtBut5iK6GWeEafhdPfD94/bnidpP362yJl8Gmya4cI1GXvwH3pfj8S9hJVA5EFvgTB3QJBAJP1O1uAGp46X7Nfl5vQ1M7RYnHIoXkWtJ417Kb78YWPLVwFlD2LHhuy/okT4fk8LZ9LeZ5u1cp1RTdLIUqAiAECQC46OwOm87L35yaVfpUIjqg/1gsNwNsj8HvtXdF/9d30JIM3GwdytCvNRLqP35Ciogb9AO8ke8L6zY83nxPbClM=");
|
||||||
|
d = new BouncyCastleProvider();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: QiuMo-Jetbrains-Help
|
||||||
|
server:
|
||||||
|
port: 10768
|
||||||
|
help:
|
||||||
|
default-license-name: QiuMo
|
||||||
|
default-assignee-name: 囚墨
|
||||||
|
default-expiry-date: 2111-11-11
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
${AnsiColor.RED} ██╗███████╗████████╗██████╗ ██████╗ █████╗ ██╗███╗ ██╗███████╗ ██╗ ██╗███████╗██╗ ██████╗
|
||||||
|
${AnsiColor.CYAN} ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗██║████╗ ██║██╔════╝ ██║ ██║██╔════╝██║ ██╔══██╗
|
||||||
|
${AnsiColor.BRIGHT_YELLOW} ██║█████╗ ██║ ██████╔╝██████╔╝███████║██║██╔██╗ ██║███████╗█████╗███████║█████╗ ██║ ██████╔╝
|
||||||
|
${AnsiColor.GREEN}██ ██║██╔══╝ ██║ ██╔══██╗██╔══██╗██╔══██║██║██║╚██╗██║╚════██║╚════╝██╔══██║██╔══╝ ██║ ██╔═══╝
|
||||||
|
${AnsiColor.BLUE}╚█████╔╝███████╗ ██║ ██████╔╝██║ ██║██║ ██║██║██║ ╚████║███████║ ██║ ██║███████╗███████╗██║
|
||||||
|
${AnsiColor.MAGENTA} ╚════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝
|
||||||
|
|
||||||
|
${AnsiColor.BRIGHT_YELLOW} Spring Boot Version: ${spring-boot.version}${AnsiColor.DEFAULT}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFOzCCAyOgAwIBAgIJANJssYOyg3nhMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
|
||||||
|
BAMMDUpldFByb2ZpbGUgQ0EwHhcNMTUxMDAyMTEwMDU2WhcNNDUxMDI0MTEwMDU2
|
||||||
|
WjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMIICIjANBgkqhkiG9w0BAQEFAAOC
|
||||||
|
Ag8AMIICCgKCAgEA0tQuEA8784NabB1+T2XBhpB+2P1qjewHiSajAV8dfIeWJOYG
|
||||||
|
y+ShXiuedj8rL8VCdU+yH7Ux/6IvTcT3nwM/E/3rjJIgLnbZNerFm15Eez+XpWBl
|
||||||
|
m5fDBJhEGhPc89Y31GpTzW0vCLmhJ44XwvYPntWxYISUrqeR3zoUQrCEp1C6mXNX
|
||||||
|
EpqIGIVbJ6JVa/YI+pwbfuP51o0ZtF2rzvgfPzKtkpYQ7m7KgA8g8ktRXyNrz8bo
|
||||||
|
iwg7RRPeqs4uL/RK8d2KLpgLqcAB9WDpcEQzPWegbDrFO1F3z4UVNH6hrMfOLGVA
|
||||||
|
xoiQhNFhZj6RumBXlPS0rmCOCkUkWrDr3l6Z3spUVgoeea+QdX682j6t7JnakaOw
|
||||||
|
jzwY777SrZoi9mFFpLVhfb4haq4IWyKSHR3/0BlWXgcgI6w6LXm+V+ZgLVDON52F
|
||||||
|
LcxnfftaBJz2yclEwBohq38rYEpb+28+JBvHJYqcZRaldHYLjjmb8XXvf2MyFeXr
|
||||||
|
SopYkdzCvzmiEJAewrEbPUaTllogUQmnv7Rv9sZ9jfdJ/cEn8e7GSGjHIbnjV2ZM
|
||||||
|
Q9vTpWjvsT/cqatbxzdBo/iEg5i9yohOC9aBfpIHPXFw+fEj7VLvktxZY6qThYXR
|
||||||
|
Rus1WErPgxDzVpNp+4gXovAYOxsZak5oTV74ynv1aQ93HSndGkKUE/qA/JECAwEA
|
||||||
|
AaOBhzCBhDAdBgNVHQ4EFgQUo562SGdCEjZBvW3gubSgUouX8bMwSAYDVR0jBEEw
|
||||||
|
P4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2Zp
|
||||||
|
bGUgQ0GCCQDSbLGDsoN54TAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAgEAjrPAZ4xC7sNiSSqh69s3KJD3Ti4etaxcrSnD7r9rJYpK
|
||||||
|
BMviCKZRKFbLv+iaF5JK5QWuWdlgA37ol7mLeoF7aIA9b60Ag2OpgRICRG79QY7o
|
||||||
|
uLviF/yRMqm6yno7NYkGLd61e5Huu+BfT459MWG9RVkG/DY0sGfkyTHJS5xrjBV6
|
||||||
|
hjLG0lf3orwqOlqSNRmhvn9sMzwAP3ILLM5VJC5jNF1zAk0jrqKz64vuA8PLJZlL
|
||||||
|
S9TZJIYwdesCGfnN2AETvzf3qxLcGTF038zKOHUMnjZuFW1ba/12fDK5GJ4i5y+n
|
||||||
|
fDWVZVUDYOPUixEZ1cwzmf9Tx3hR8tRjMWQmHixcNC8XEkVfztID5XeHtDeQ+uPk
|
||||||
|
X+jTDXbRb+77BP6n41briXhm57AwUI3TqqJFvoiFyx5JvVWG3ZqlVaeU/U9e0gxn
|
||||||
|
8qyR+ZA3BGbtUSDDs8LDnE67URzK+L+q0F2BC758lSPNB2qsJeQ63bYyzf0du3wB
|
||||||
|
/gb2+xJijAvscU3KgNpkxfGklvJD/oDUIqZQAnNcHe7QEf8iG2WqaMJIyXZlW3me
|
||||||
|
0rn+cgvxHPt6N4EBh5GgNZR4l0eaFEV+fxVsydOQYo1RIyFMXtafFBqQl6DDxujl
|
||||||
|
FeU3FZ+Bcp12t7dlM4E0/sS1XdL47CfGVj4Bp+/VbF862HmkAbd7shs7sDQkHbU=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFTDCCAzSgAwIBAgIJAMCrW9HV+hjZMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNV
|
||||||
|
BAMMEkxpY2Vuc2UgU2VydmVycyBDQTAgFw0xNjEwMTIxNDMwNTRaGA8yMTE2MTIy
|
||||||
|
NzE0MzA1NFowHTEbMBkGA1UEAwwSTGljZW5zZSBTZXJ2ZXJzIENBMIICIjANBgkq
|
||||||
|
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoT7LvHj3JKK2pgc5f02z+xEiJDcvlBi6
|
||||||
|
fIwrg/504UaMx3xWXAE5CEPelFty+QPRJnTNnSxqKQQmg2s/5tMJpL9lzGwXaV7a
|
||||||
|
rrcsEDbzV4el5mIXUnk77Bm/QVv48s63iQqUjVmvjQt9SWG2J7+h6X3ICRvF1sQB
|
||||||
|
yeat/cO7tkpz1aXXbvbAws7/3dXLTgAZTAmBXWNEZHVUTcwSg2IziYxL8HRFOH0+
|
||||||
|
GMBhHqa0ySmF1UTnTV4atIXrvjpABsoUvGxw+qOO2qnwe6ENEFWFz1a7pryVOHXg
|
||||||
|
P+4JyPkI1hdAhAqT2kOKbTHvlXDMUaxAPlriOVw+vaIjIVlNHpBGhqTj1aqfJpLj
|
||||||
|
qfDFcuqQSI4O1W5tVPRNFrjr74nDwLDZnOF+oSy4E1/WhL85FfP3IeQAIHdswNMJ
|
||||||
|
y+RdkPZCfXzSUhBKRtiM+yjpIn5RBY+8z+9yeGocoxPf7l0or3YF4GUpud202zgy
|
||||||
|
Y3sJqEsZksB750M0hx+vMMC9GD5nkzm9BykJS25hZOSsRNhX9InPWYYIi6mFm8QA
|
||||||
|
2Dnv8wxAwt2tDNgqa0v/N8OxHglPcK/VO9kXrUBtwCIfZigO//N3hqzfRNbTv/ZO
|
||||||
|
k9lArqGtcu1hSa78U4fuu7lIHi+u5rgXbB6HMVT3g5GQ1L9xxT1xad76k2EGEi3F
|
||||||
|
9B+tSrvru70CAwEAAaOBjDCBiTAdBgNVHQ4EFgQUpsRiEz+uvh6TsQqurtwXMd4J
|
||||||
|
8VEwTQYDVR0jBEYwRIAUpsRiEz+uvh6TsQqurtwXMd4J8VGhIaQfMB0xGzAZBgNV
|
||||||
|
BAMMEkxpY2Vuc2UgU2VydmVycyBDQYIJAMCrW9HV+hjZMAwGA1UdEwQFMAMBAf8w
|
||||||
|
CwYDVR0PBAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQCJ9+GQWvBS3zsgPB+1PCVc
|
||||||
|
oG6FY87N6nb3ZgNTHrUMNYdo7FDeol2DSB4wh/6rsP9Z4FqVlpGkckB+QHCvqU+d
|
||||||
|
rYPe6QWHIb1kE8ftTnwapj/ZaBtF80NWUfYBER/9c6To5moW63O7q6cmKgaGk6zv
|
||||||
|
St2IhwNdTX0Q5cib9ytE4XROeVwPUn6RdU/+AVqSOspSMc1WQxkPVGRF7HPCoGhd
|
||||||
|
vqebbYhpahiMWfClEuv1I37gJaRtsoNpx3f/jleoC/vDvXjAznfO497YTf/GgSM2
|
||||||
|
LCnVtpPQQ2vQbOfTjaBYO2MpibQlYpbkbjkd5ZcO5U5PGrQpPFrWcylz7eUC3c05
|
||||||
|
UVeygGIthsA/0hMCioYz4UjWTgi9NQLbhVkfmVQ5lCVxTotyBzoubh3FBz+wq2Qt
|
||||||
|
iElsBrCMR7UwmIu79UYzmLGt3/gBdHxaImrT9SQ8uqzP5eit54LlGbvGekVdAL5l
|
||||||
|
DFwPcSB1IKauXZvi1DwFGPeemcSAndy+Uoqw5XGRqE6jBxS7XVI7/4BSMDDRBz1u
|
||||||
|
a+JMGZXS8yyYT+7HdsybfsZLvkVmc9zVSDI7/MjVPdk6h0sLn+vuPC1bIi5edoNy
|
||||||
|
PdiG2uPH5eDO6INcisyPpLS4yFKliaO4Jjap7yzLU9pbItoWgCAYa2NpxuxHJ0tB
|
||||||
|
7tlDFnvaRnQukqSG+VqNWg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "All Jetbrains's Product Or Plugin",
|
||||||
|
"productCode": "",
|
||||||
|
"iconClass": "icon-al"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "IntelliJ IDEA",
|
||||||
|
"productCode": "II,PCWMP,PSI",
|
||||||
|
"iconClass": "icon-ii"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PhpStorm",
|
||||||
|
"productCode": "PS,PCWMP,PSI",
|
||||||
|
"iconClass": "icon-ps"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AppCode",
|
||||||
|
"productCode": "AC,PCWMP,PSI",
|
||||||
|
"iconClass": "icon-ac"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DataGrip",
|
||||||
|
"productCode": "DB,PSI,PDB",
|
||||||
|
"iconClass": "icon-db"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RubyMine",
|
||||||
|
"productCode": "RM,PCWMP,PSI",
|
||||||
|
"iconClass": "icon-rm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "WebStorm",
|
||||||
|
"productCode": "WS,PCWMP,PSI",
|
||||||
|
"iconClass": "icon-ws"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Rider",
|
||||||
|
"productCode": "RD,PDB,PSI,PCWMP",
|
||||||
|
"iconClass": "icon-rd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "CLion",
|
||||||
|
"productCode": "CL,PSI,PCWMP",
|
||||||
|
"iconClass": "icon-cl"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PyCharm",
|
||||||
|
"productCode": "PC,PSI,PCWMP",
|
||||||
|
"iconClass": "icon-pc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "GoLand",
|
||||||
|
"productCode": "GO,PSI,PCWMP",
|
||||||
|
"iconClass": "icon-go"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DataSpell",
|
||||||
|
"productCode": "DS,PSI,PDB,PCWMP",
|
||||||
|
"iconClass": "icon-ds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dotMemory",
|
||||||
|
"productCode": "DM",
|
||||||
|
"iconClass": "icon-dm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Aqua",
|
||||||
|
"productCode": "QA,PSI,PCWMP",
|
||||||
|
"iconClass": "icon-qa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RustRover",
|
||||||
|
"productCode": "RR,PSI,PCWMP",
|
||||||
|
"iconClass": "icon-rr"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
<!-- 定义日志文件的存储地址 -->
|
||||||
|
<property name="LOG_HOME" value="./logs" />
|
||||||
|
|
||||||
|
<!-- 控制台输出 - 彩色日志 -->
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<withJansi>true</withJansi>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||||
|
<!-- 彩色输出配置:标准单行格式 -->
|
||||||
|
<pattern>%gray(%d{yyyy-MM-dd HH:mm:ss.SSS}) %magenta([%thread]) %highlight(%-5level) %cyan(%logger{36}) - %msg%n</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 按照每天生成日志文件 -->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<!--日志文件输出的文件名-->
|
||||||
|
<FileNamePattern>${LOG_HOME}/qiumo-help.%d{yyyy-MM-dd}.log</FileNamePattern>
|
||||||
|
<!--日志文件保留天数-->
|
||||||
|
<MaxHistory>30</MaxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<!--日志文件最大的大小-->
|
||||||
|
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
|
||||||
|
<MaxFileSize>100MB</MaxFileSize>
|
||||||
|
</triggeringPolicy>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 错误日志单独输出 -->
|
||||||
|
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>ERROR</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
|
<FileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</FileNamePattern>
|
||||||
|
<MaxHistory>30</MaxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
|
||||||
|
<MaxFileSize>100MB</MaxFileSize>
|
||||||
|
</triggeringPolicy>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 异步输出 -->
|
||||||
|
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
|
||||||
|
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
|
||||||
|
<discardingThreshold>0</discardingThreshold>
|
||||||
|
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
|
||||||
|
<queueSize>512</queueSize>
|
||||||
|
<!-- 添加附加的appender,最多只能添加一个 -->
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 开发环境配置 -->
|
||||||
|
<springProfile name="dev">
|
||||||
|
<root level="DEBUG">
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<appender-ref ref="ASYNC_FILE" />
|
||||||
|
<appender-ref ref="ERROR_FILE" />
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- 测试环境配置 -->
|
||||||
|
<springProfile name="test">
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<appender-ref ref="ASYNC_FILE" />
|
||||||
|
<appender-ref ref="ERROR_FILE" />
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- 生产环境配置 -->
|
||||||
|
<springProfile name="prod">
|
||||||
|
<root level="WARN">
|
||||||
|
<appender-ref ref="ASYNC_FILE" />
|
||||||
|
<appender-ref ref="ERROR_FILE" />
|
||||||
|
</root>
|
||||||
|
</springProfile>
|
||||||
|
|
||||||
|
<!-- 默认配置 -->
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<appender-ref ref="ASYNC_FILE" />
|
||||||
|
<appender-ref ref="ERROR_FILE" />
|
||||||
|
</root>
|
||||||
|
|
||||||
|
<!-- 特定包的日志级别配置 -->
|
||||||
|
<logger name="com.qiumo.help" level="DEBUG" additivity="false">
|
||||||
|
<appender-ref ref="CONSOLE" />
|
||||||
|
<appender-ref ref="ASYNC_FILE" />
|
||||||
|
<appender-ref ref="ERROR_FILE" />
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<!-- Spring框架日志级别 -->
|
||||||
|
<logger name="org.springframework" level="INFO" />
|
||||||
|
|
||||||
|
<!-- Hibernate日志级别 -->
|
||||||
|
<logger name="org.hibernate" level="WARN" />
|
||||||
|
|
||||||
|
<!-- SQL日志级别 -->
|
||||||
|
<logger name="org.hibernate.SQL" level="DEBUG" />
|
||||||
|
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" />
|
||||||
|
|
||||||
|
<!-- 网络请求日志 -->
|
||||||
|
<logger name="org.apache.http" level="INFO" />
|
||||||
|
|
||||||
|
<!-- BouncyCastle日志级别 -->
|
||||||
|
<logger name="org.bouncycastle" level="WARN" />
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,328 @@
|
|||||||
|
/* 全局样式 */
|
||||||
|
body {
|
||||||
|
font-family: 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'PingFang SC', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 自定义滚动条 */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f5f9;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 动画效果 */
|
||||||
|
.fade-enter-active, .fade-leave-active {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from, .fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 卡片悬停效果 */
|
||||||
|
.card-hover {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮动画 */
|
||||||
|
.btn-primary {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 渐变背景 */
|
||||||
|
.gradient-bg {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-text {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 毛玻璃效果 */
|
||||||
|
.glass {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 加载动画 */
|
||||||
|
.loading {
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from { transform: rotate(0deg); }
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 模态框动画 */
|
||||||
|
.modal-enter-active, .modal-leave-active {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-enter-from, .modal-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 产品图标样式 */
|
||||||
|
.product-icon {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 激活码展示区域 */
|
||||||
|
.license-code-display {
|
||||||
|
background: #1f2937;
|
||||||
|
color: #f9fafb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 通知样式 */
|
||||||
|
.notification {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
z-index: 9999;
|
||||||
|
max-width: 400px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
|
animation: slideInRight 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.success {
|
||||||
|
background: #10b981;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification.error {
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 搜索框样式 */
|
||||||
|
.search-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
padding-left: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #9ca3af;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.license-code-display {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
right: 10px;
|
||||||
|
left: 10px;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 返回顶部按钮 */
|
||||||
|
.back-to-top {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 30px;
|
||||||
|
right: 30px;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-top:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.back-to-top {
|
||||||
|
bottom: 90px; /* 避开底部导航栏 */
|
||||||
|
right: 20px;
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JRebel页面特殊样式 */
|
||||||
|
.jrebel-container {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jrebel-step {
|
||||||
|
min-width: 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配置说明区域 */
|
||||||
|
.config-section {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-left: 4px solid #3b82f6;
|
||||||
|
padding: 16px;
|
||||||
|
margin: 16px 0;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-code {
|
||||||
|
background: #1f2937;
|
||||||
|
color: #f9fafb;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配置步骤优化 */
|
||||||
|
.config-step {
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-step-content {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移动端优化 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.license-code-display {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
right: 10px;
|
||||||
|
left: 10px;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-code {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 8px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-section {
|
||||||
|
padding: 12px;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 配置步骤移动端优化 */
|
||||||
|
.config-step-mobile {
|
||||||
|
flex-direction: column;
|
||||||
|
space-y: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-step-mobile .config-step-content {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 首页内容移动端优化 */
|
||||||
|
.home-section {
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-title {
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-subtitle {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="aqua_svg__a" x1="59.932" x2="1.336" y1="59.676" y2="1.079" gradientUnits="userSpaceOnUse"><stop offset=".25" stop-color="#7256FF"/><stop offset=".73" stop-color="#007DFE"/></linearGradient><linearGradient id="aqua_svg__b" x1="7.671" x2="61.125" y1="64.392" y2="39.609" gradientUnits="userSpaceOnUse"><stop offset=".3" stop-color="#00D886"/><stop offset=".54" stop-color="#7256FF"/></linearGradient></defs><path fill="url(#aqua_svg__a)" d="M0 4.125v34.127c0 1.659.993 3.155 2.52 3.8L39.943 57.85c.518.219 1.075.33 1.638.324l18.329-.15A4.125 4.125 0 0 0 64 53.9V36.234c0-.806-.236-1.593-.678-2.267L42.213 1.86A4.125 4.125 0 0 0 38.766 0H4.125A4.125 4.125 0 0 0 0 4.125Z"/><path fill="url(#aqua_svg__b)" d="M6 49.015v10.862a4.125 4.125 0 0 0 4.125 4.125h12.566c.2 0 .4-.014.598-.044l37.185-5.448A4.125 4.125 0 0 0 64 54.429V39.03a4.125 4.125 0 0 0-4.127-4.125l-18.504.005c-.426 0-.849.066-1.254.195L8.871 45.085A4.126 4.126 0 0 0 6 49.015H6Z"/><path fill="#00D886" d="M6 47.55v12.259a4.125 4.125 0 0 0 4.19 4.124L21 64c1.181-.019 2.531-.786 3.3-1.683l32.707-38.158c.64-.748.993-1.7.993-2.685V10.125A4.125 4.125 0 0 0 53.875 6H42.872c-1.19 0-2.321.514-3.105 1.409L7.021 44.834A4.123 4.123 0 0 0 6 47.55Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM20.746 31.242a7.32 7.32 0 0 1-2.755-2.786c-.668-1.183-1.003-2.503-1.003-3.961s.334-2.778 1.003-3.96a7.314 7.314 0 0 1 2.755-2.787c1.168-.675 2.474-1.014 3.917-1.014 1.443 0 2.739.338 3.907 1.013a7.278 7.278 0 0 1 2.75 2.787c.664 1.183.996 2.503.996 3.961s-.332 2.778-.997 3.96a7.278 7.278 0 0 1-2.749 2.788c-1.168.675-2.47 1.013-3.907 1.013-1.436 0-2.75-.338-3.917-1.013Zm6.308-2.229c.707-.446 1.261-1.065 1.661-1.854.4-.79.6-1.678.6-2.664 0-.986-.2-1.874-.6-2.664a4.6 4.6 0 0 0-1.661-1.854c-.708-.446-1.504-.67-2.39-.67-.886 0-1.685.223-2.396.67a4.62 4.62 0 0 0-1.672 1.854c-.403.79-.605 1.678-.605 2.664 0 .986.202 1.874.605 2.664.404.79.962 1.408 1.672 1.854.711.447 1.51.67 2.396.67.886 0 1.683-.223 2.39-.67Zm-2.16 6.034a2.666 2.666 0 0 1-1.126-1.077c-.261-.464-.391-1.011-.391-1.64l-.01-1.94h2.743v1.811c0 .221.043.413.129.573.085.161.209.283.37.365.16.082.352.123.574.123h1.833v2.165h-2.412c-.65 0-1.22-.127-1.71-.38ZM38.233 16.992h3.173l5.563 15.006h-2.947l-1.212-3.483h-5.842l-1.136 3.483H32.82l5.413-15.006Zm3.805 9.261-2.025-5.723-.225-.954-.215.954-1.908 5.723h4.373Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="clion_svg__a" x1="4.067" x2="62.664" y1="4.327" y2="62.923" gradientUnits="userSpaceOnUse"><stop offset=".29" stop-color="#009AE5"/><stop offset=".7" stop-color="#00D980"/></linearGradient><linearGradient id="clion_svg__b" x1="56.329" x2="2.874" y1="-.391" y2="24.393" gradientUnits="userSpaceOnUse"><stop offset=".3" stop-color="#FF2D90"/><stop offset=".54" stop-color="#009AE5"/></linearGradient></defs><path fill="url(#clion_svg__a)" d="M64 59.878V25.75a4.125 4.125 0 0 0-2.52-3.8L24.057 6.153a4.118 4.118 0 0 0-1.638-.325l-18.329.15A4.124 4.124 0 0 0 0 10.103v17.665c0 .806.236 1.593.678 2.267l21.109 32.109a4.124 4.124 0 0 0 3.447 1.859h34.641A4.125 4.125 0 0 0 64 59.878Z"/><path fill="url(#clion_svg__b)" d="M58 14.988V4.125A4.125 4.125 0 0 0 53.875 0H41.309c-.2 0-.4.014-.598.044L3.527 5.492A4.125 4.125 0 0 0 0 9.573v15.398a4.125 4.125 0 0 0 4.126 4.125l18.505-.005c.425 0 .848-.066 1.253-.195l31.246-9.98A4.126 4.126 0 0 0 58 14.988Z"/><path fill="#FF2D90" d="M58 16.453V4.194A4.125 4.125 0 0 0 53.81.07L43.003.008c-1.18.019-2.535.781-3.304 1.678L6.993 39.844c-.64.748-.993 1.7-.993 2.684v11.35a4.125 4.125 0 0 0 4.125 4.125h11.003c1.19 0 2.321-.514 3.104-1.409L56.98 19.17A4.124 4.124 0 0 0 58 16.453Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM20.747 31.242a7.287 7.287 0 0 1-2.744-2.786c-.665-1.183-.997-2.503-.997-3.961s.332-2.778.997-3.96a7.29 7.29 0 0 1 2.744-2.787c1.164-.675 2.465-1.014 3.901-1.014 1.215 0 2.33.225 3.345.675a6.862 6.862 0 0 1 2.535 1.892 6.443 6.443 0 0 1 1.355 2.793h-3.065a4.054 4.054 0 0 0-.895-1.431 4.038 4.038 0 0 0-1.431-.95 4.895 4.895 0 0 0-1.822-.332c-.887 0-1.687.222-2.402.665a4.61 4.61 0 0 0-1.677 1.827c-.404.776-.606 1.649-.606 2.62 0 .973.202 1.846.606 2.621a4.605 4.605 0 0 0 1.677 1.828c.715.443 1.515.664 2.402.664a4.88 4.88 0 0 0 1.822-.332 4.022 4.022 0 0 0 2.326-2.38h3.065a6.444 6.444 0 0 1-1.355 2.792 6.863 6.863 0 0 1-2.535 1.892c-1.015.45-2.13.675-3.345.675-1.436 0-2.736-.337-3.901-1.013v.003ZM37.33 16.992v12.37h7.117v2.636H34.414V16.992h2.916Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="datagrip_svg__a" x1="59.676" x2="1.08" y1="4.067" y2="62.663" gradientUnits="userSpaceOnUse"><stop offset=".28" stop-color="#7256FF"/><stop offset=".66" stop-color="#00D980"/></linearGradient><linearGradient id="datagrip_svg__b" x1="64.391" x2="39.607" y1="56.329" y2="2.874" gradientUnits="userSpaceOnUse"><stop offset=".3" stop-color="#FF43F2"/><stop offset=".54" stop-color="#7256FF"/></linearGradient></defs><path fill="url(#datagrip_svg__a)" d="M4.125 64h34.127a4.125 4.125 0 0 0 3.8-2.52L57.85 24.057c.219-.518.33-1.076.324-1.638l-.15-18.329A4.125 4.125 0 0 0 53.9 0H36.234c-.806 0-1.593.236-2.267.678L1.86 21.787A4.125 4.125 0 0 0 0 25.234v34.641A4.125 4.125 0 0 0 4.125 64Z"/><path fill="url(#datagrip_svg__b)" d="M49.013 58h10.862A4.125 4.125 0 0 0 64 53.875V41.309c0-.2-.014-.4-.044-.598L58.508 3.527A4.125 4.125 0 0 0 54.427 0H39.029a4.125 4.125 0 0 0-4.125 4.126l.005 18.505c0 .425.066.848.195 1.253l9.979 31.246a4.126 4.126 0 0 0 3.93 2.87Z"/><path fill="#FF43F2" d="M47.55 58h12.259a4.125 4.125 0 0 0 4.124-4.19L64 43c-.018-1.181-.785-2.531-1.682-3.3L24.159 6.993A4.126 4.126 0 0 0 21.474 6H10.125A4.125 4.125 0 0 0 6 10.125v11.003c0 1.19.514 2.321 1.409 3.104L44.834 56.98A4.123 4.123 0 0 0 47.55 58Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM17.012 16.992h5.713c1.422 0 2.696.321 3.821.965a6.882 6.882 0 0 1 2.642 2.674c.636 1.14.954 2.427.954 3.864 0 1.437-.318 2.725-.954 3.865a6.891 6.891 0 0 1-2.642 2.674c-1.125.643-2.4.964-3.821.964h-5.713V16.992Zm8.028 11.876c.672-.4 1.19-.973 1.554-1.72.365-.747.547-1.63.547-2.653 0-1.023-.182-1.907-.547-2.653-.364-.747-.882-1.32-1.554-1.721-.672-.4-1.454-.6-2.348-.6h-2.755v9.947h2.755c.893 0 1.676-.2 2.348-.6ZM35.426 31.242a7.287 7.287 0 0 1-2.744-2.786c-.664-1.183-.997-2.503-.997-3.961s.333-2.778.997-3.96 1.58-2.112 2.744-2.787c1.164-.675 2.466-1.014 3.902-1.014 1.164 0 2.238.208 3.221.622a6.909 6.909 0 0 1 2.487 1.747 6.362 6.362 0 0 1 1.42 2.594h-3.13a3.914 3.914 0 0 0-.927-1.228c-.39-.346-.845-.614-1.367-.803s-1.082-.284-1.683-.284c-.886 0-1.686.221-2.401.664a4.612 4.612 0 0 0-1.678 1.828c-.403.775-.605 1.649-.605 2.62s.202 1.846.605 2.621a4.607 4.607 0 0 0 1.678 1.828c.714.443 1.515.664 2.401.664.822 0 1.567-.154 2.235-.461.668-.307 1.199-.733 1.592-1.276a3.297 3.297 0 0 0 .633-1.833l.01.31h-3.526v-2.304h6.356v1.18c0 1.321-.32 2.517-.96 3.585a6.885 6.885 0 0 1-2.625 2.525c-1.112.614-2.357.921-3.736.921-1.38 0-2.737-.337-3.902-1.013v.002Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="dataspell_svg__a" x1="4.067" x2="62.664" y1="4.327" y2="62.923" gradientUnits="userSpaceOnUse"><stop offset=".28" stop-color="#007DFE"/><stop offset=".73" stop-color="#00D980"/></linearGradient><linearGradient id="dataspell_svg__b" x1="56.329" x2="2.875" y1="-.391" y2="24.392" gradientUnits="userSpaceOnUse"><stop offset=".32" stop-color="#F0EB18"/><stop offset=".55" stop-color="#007DFE"/></linearGradient></defs><path fill="url(#dataspell_svg__a)" d="M64 59.878V25.75a4.125 4.125 0 0 0-2.52-3.8L24.057 6.153a4.118 4.118 0 0 0-1.638-.325l-18.329.15A4.124 4.124 0 0 0 0 10.103v17.665c0 .806.236 1.593.678 2.267l21.109 32.109a4.124 4.124 0 0 0 3.447 1.859h34.641A4.125 4.125 0 0 0 64 59.878Z"/><path fill="url(#dataspell_svg__b)" d="M58 14.988V4.125A4.125 4.125 0 0 0 53.875 0H41.309c-.2 0-.4.014-.598.044L3.527 5.492A4.125 4.125 0 0 0 0 9.573v15.398a4.125 4.125 0 0 0 4.126 4.125l18.505-.005c.425 0 .848-.066 1.253-.195l31.246-9.98A4.126 4.126 0 0 0 58 14.988Z"/><path fill="#F0EB18" d="M58 16.45V4.191A4.125 4.125 0 0 0 53.81.067L43 0c-1.181.019-2.531.786-3.3 1.683L6.993 39.84c-.64.748-.993 1.7-.993 2.685v11.349A4.125 4.125 0 0 0 10.125 58h11.003c1.19 0 2.321-.514 3.104-1.409L56.98 19.166A4.124 4.124 0 0 0 58 16.45Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM17.012 16.992h5.713c1.422 0 2.696.321 3.821.965a6.882 6.882 0 0 1 2.642 2.674c.636 1.14.954 2.427.954 3.864 0 1.437-.318 2.725-.954 3.865a6.891 6.891 0 0 1-2.642 2.674c-1.125.643-2.4.964-3.821.964h-5.713V16.992Zm8.028 11.876c.672-.4 1.19-.973 1.554-1.72.365-.747.547-1.63.547-2.653 0-1.023-.182-1.907-.547-2.653-.364-.747-.882-1.32-1.554-1.721-.672-.4-1.454-.6-2.348-.6h-2.755v9.947h2.755c.893 0 1.676-.2 2.348-.6ZM34.713 31.664c-.84-.393-1.497-.943-1.973-1.65-.475-.708-.72-1.522-.734-2.444h2.937c0 .436.114.818.343 1.147.229.329.547.586.954.772.407.185.875.278 1.404.278.53 0 .952-.083 1.334-.251.382-.168.68-.402.89-.702.21-.3.316-.643.316-1.029 0-.479-.145-.875-.434-1.19-.29-.314-.688-.532-1.195-.654l-2.648-.59c-.714-.156-1.336-.426-1.865-.809a4.015 4.015 0 0 1-1.232-1.42c-.293-.564-.44-1.2-.44-1.908 0-.857.222-1.627.665-2.31.443-.682 1.06-1.215 1.854-1.597.793-.382 1.694-.573 2.701-.573s1.933.186 2.733.558 1.424.888 1.871 1.549c.446.66.674 1.42.68 2.277h-2.926c0-.364-.096-.691-.289-.98s-.466-.515-.82-.676a2.915 2.915 0 0 0-1.217-.24c-.457 0-.86.076-1.205.23a1.902 1.902 0 0 0-.81.643c-.193.275-.29.595-.29.96 0 .414.135.757.403 1.028.268.272.634.464 1.098.579l2.552.557c.735.15 1.392.435 1.967.852a4.45 4.45 0 0 1 1.345 1.544c.32.611.482 1.278.482 1.999 0 .886-.234 1.681-.702 2.385-.468.703-1.124 1.255-1.967 1.656-.843.4-1.808.6-2.894.6s-2.05-.197-2.889-.59Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="dotcover_svg__a" x1="56.329" x2="2.874" y1="-.391" y2="24.392" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#6B57FF"/><stop offset=".54" stop-color="#FF45ED"/></linearGradient><linearGradient id="dotcover_svg__b" x1="4.067" x2="62.664" y1="4.326" y2="62.923" gradientUnits="userSpaceOnUse"><stop offset=".12" stop-color="#FF45ED"/><stop offset=".92" stop-color="#FC801D"/></linearGradient></defs><path fill="#6B57FF" d="M58 16.446V4.187A4.125 4.125 0 0 0 53.81.063L42.765.239A4.125 4.125 0 0 0 39.7 1.68L6.993 39.837c-.64.748-.993 1.7-.993 2.685v11.35a4.125 4.125 0 0 0 4.125 4.124h11.003c1.19 0 2.321-.514 3.104-1.409L56.98 19.162A4.124 4.124 0 0 0 58 16.446Z"/><path fill="url(#dotcover_svg__a)" d="M58 14.988V4.125A4.125 4.125 0 0 0 53.875 0H41.309c-.2 0-.4.014-.598.044L3.527 5.492A4.125 4.125 0 0 0 0 9.573v15.398a4.125 4.125 0 0 0 4.126 4.125l18.505-.005c.425 0 .848-.066 1.253-.195l31.246-9.98A4.126 4.126 0 0 0 58 14.988Z"/><path fill="url(#dotcover_svg__b)" d="M64 59.878V25.75a4.125 4.125 0 0 0-2.52-3.8L24.057 6.153a4.118 4.118 0 0 0-1.638-.325l-18.329.15A4.124 4.124 0 0 0 0 10.103v17.665c0 .806.236 1.593.678 2.267l21.109 32.109a4.124 4.124 0 0 0 3.447 1.859h34.641A4.125 4.125 0 0 0 64 59.878Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM16.996 16.992h5.713c1.422 0 2.696.321 3.821.965a6.888 6.888 0 0 1 2.643 2.674c.636 1.14.954 2.427.954 3.864 0 1.437-.318 2.725-.954 3.865a6.888 6.888 0 0 1-2.642 2.674c-1.126.643-2.4.964-3.822.964h-5.713V16.992Zm8.028 11.876c.672-.4 1.19-.973 1.555-1.72.364-.747.547-1.63.547-2.653 0-1.023-.183-1.907-.547-2.653-.365-.747-.883-1.32-1.555-1.721-.672-.4-1.454-.6-2.347-.6h-2.755v9.947h2.755c.893 0 1.675-.2 2.347-.6ZM35.41 31.242a7.286 7.286 0 0 1-2.743-2.786c-.665-1.183-.997-2.503-.997-3.961s.332-2.778.997-3.96 1.58-2.112 2.744-2.787c1.164-.675 2.464-1.014 3.901-1.014 1.215 0 2.33.225 3.345.675a6.867 6.867 0 0 1 2.535 1.892 6.442 6.442 0 0 1 1.356 2.793h-3.066a4.054 4.054 0 0 0-.895-1.431 4.039 4.039 0 0 0-1.43-.95 4.896 4.896 0 0 0-1.823-.332c-.887 0-1.687.222-2.402.665a4.61 4.61 0 0 0-1.677 1.827c-.404.776-.606 1.649-.606 2.62 0 .973.202 1.846.606 2.621a4.606 4.606 0 0 0 1.677 1.828c.715.443 1.515.664 2.402.664.657 0 1.265-.11 1.822-.332a4.023 4.023 0 0 0 2.326-2.38h3.066a6.437 6.437 0 0 1-1.356 2.792 6.863 6.863 0 0 1-2.535 1.892c-1.015.45-2.13.675-3.345.675-1.436 0-2.736-.337-3.901-1.013v.003Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="dotmemory_svg__a" x1="59.676" x2="1.08" y1="4.067" y2="62.664" gradientUnits="userSpaceOnUse"><stop offset=".31" stop-color="#FF45ED"/><stop offset=".83" stop-color="#6B57FF"/></linearGradient><linearGradient id="dotmemory_svg__b" x1="64.391" x2="39.607" y1="56.329" y2="2.875" gradientUnits="userSpaceOnUse"><stop offset=".3" stop-color="#FDB60D"/><stop offset=".7" stop-color="#FF45ED"/></linearGradient></defs><path fill="url(#dotmemory_svg__a)" d="M4.125 64h34.127a4.125 4.125 0 0 0 3.8-2.52L57.85 24.057c.219-.518.33-1.076.324-1.638l-.15-18.329A4.125 4.125 0 0 0 53.9 0H36.234c-.806 0-1.593.236-2.267.678L1.86 21.787A4.125 4.125 0 0 0 0 25.234v34.641A4.125 4.125 0 0 0 4.125 64Z"/><path fill="url(#dotmemory_svg__b)" d="M49.013 58h10.862A4.125 4.125 0 0 0 64 53.875V41.309c0-.2-.014-.4-.044-.598L58.508 3.527A4.125 4.125 0 0 0 54.427 0H39.029a4.125 4.125 0 0 0-4.125 4.126l.005 18.505c0 .425.066.848.195 1.253l9.979 31.246a4.126 4.126 0 0 0 3.93 2.87Z"/><path fill="#FDB60D" d="M47.55 58h12.259a4.125 4.125 0 0 0 4.124-4.19L64 43c-.018-1.181-.785-2.531-1.682-3.3L24.16 6.993A4.126 4.126 0 0 0 21.474 6H10.125A4.125 4.125 0 0 0 6 10.125v11.003c0 1.19.514 2.321 1.409 3.104L44.834 56.98A4.123 4.123 0 0 0 47.55 58Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM17.012 16.992h5.713c1.422 0 2.696.321 3.821.965a6.888 6.888 0 0 1 2.643 2.674c.635 1.14.953 2.427.953 3.864 0 1.437-.318 2.725-.953 3.865a6.888 6.888 0 0 1-2.643 2.674c-1.125.643-2.4.964-3.821.964h-5.713V16.992Zm8.028 11.876c.672-.4 1.19-.973 1.554-1.72.365-.747.547-1.63.547-2.653 0-1.023-.182-1.907-.547-2.653-.364-.747-.882-1.32-1.554-1.721-.672-.4-1.454-.6-2.348-.6h-2.755v9.947h2.755c.893 0 1.676-.2 2.348-.6ZM32.232 16.992h4.073l3.087 9.85.258 1.287.225-1.286 2.98-9.85h4.137v15.005H44.1V21.29l.043-.782-3.495 11.49h-2.122l-3.451-11.415.043.707v10.708h-2.883V16.992h-.002Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="dottrace_svg__a" x1="-.391" x2="24.392" y1="7.671" y2="61.126" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#6B57FF"/><stop offset=".54" stop-color="#FF2358"/></linearGradient><linearGradient id="dottrace_svg__b" x1="4.325" x2="62.921" y1="59.932" y2="1.336" gradientUnits="userSpaceOnUse"><stop offset=".12" stop-color="#FF2358"/><stop offset=".92" stop-color="#FF45ED"/></linearGradient></defs><path fill="#6B57FF" d="M16.45 6H4.191a4.125 4.125 0 0 0-4.124 4.19l.176 11.044a4.125 4.125 0 0 0 1.44 3.066l38.159 32.707c.747.64 1.7.993 2.684.993h11.35A4.125 4.125 0 0 0 58 53.875V42.872c0-1.19-.514-2.321-1.41-3.105L19.167 7.021A4.123 4.123 0 0 0 16.45 6Z"/><path fill="url(#dottrace_svg__a)" d="M14.988 6H4.125A4.125 4.125 0 0 0 0 10.125v12.566c0 .2.014.4.044.598l5.448 37.185A4.125 4.125 0 0 0 9.573 64h15.398a4.125 4.125 0 0 0 4.125-4.127L29.09 41.37c0-.426-.066-.849-.195-1.254l-9.98-31.245A4.126 4.126 0 0 0 14.988 6V6Z"/><path fill="url(#dottrace_svg__b)" d="M59.876 0H25.748a4.125 4.125 0 0 0-3.8 2.52L6.151 39.943a4.118 4.118 0 0 0-.325 1.638l.15 18.329A4.125 4.125 0 0 0 10.101 64h17.666c.806 0 1.593-.236 2.266-.678l32.11-21.109A4.123 4.123 0 0 0 64 38.766V4.125A4.126 4.126 0 0 0 59.876 0Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM16.996 16.992h5.713c1.422 0 2.696.321 3.821.965a6.888 6.888 0 0 1 2.643 2.674c.636 1.14.954 2.427.954 3.864 0 1.437-.318 2.725-.954 3.865a6.888 6.888 0 0 1-2.642 2.674c-1.126.643-2.4.964-3.822.964h-5.713V16.992Zm8.028 11.876c.672-.4 1.19-.973 1.555-1.72.364-.747.547-1.63.547-2.653 0-1.023-.183-1.907-.547-2.653-.365-.747-.883-1.32-1.555-1.721-.672-.4-1.454-.6-2.347-.6h-2.755v9.947h2.755c.893 0 1.675-.2 2.347-.6ZM31.133 16.992H43.17v2.637h-4.545V32H35.7v-12.37h-4.566v-2.637Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="goland_svg__a" x1="64.391" x2="39.607" y1="56.329" y2="2.874" gradientUnits="userSpaceOnUse"><stop offset=".24" stop-color="#00D886"/><stop offset=".51" stop-color="#007DFE"/></linearGradient><linearGradient id="goland_svg__b" x1="59.676" x2="1.08" y1="4.067" y2="62.663" gradientUnits="userSpaceOnUse"><stop offset=".27" stop-color="#007DFE"/><stop offset=".7" stop-color="#D249FC"/></linearGradient></defs><path fill="#00D886" d="M47.55 58h12.259a4.125 4.125 0 0 0 4.124-4.19l-.176-11.044a4.125 4.125 0 0 0-1.44-3.066L24.159 6.993A4.126 4.126 0 0 0 21.474 6H10.125A4.125 4.125 0 0 0 6 10.125v11.003c0 1.19.514 2.321 1.409 3.104L44.834 56.98A4.123 4.123 0 0 0 47.55 58Z"/><path fill="url(#goland_svg__a)" d="M49.013 58h10.862A4.125 4.125 0 0 0 64 53.875V41.309c0-.2-.014-.4-.044-.598L58.508 3.527A4.124 4.124 0 0 0 54.427 0H39.029a4.125 4.125 0 0 0-4.125 4.126l.005 18.505c0 .425.066.848.195 1.253l9.979 31.246a4.126 4.126 0 0 0 3.93 2.87Z"/><path fill="url(#goland_svg__b)" d="M4.125 64h34.127a4.125 4.125 0 0 0 3.8-2.52L57.85 24.057c.219-.518.33-1.076.324-1.638l-.15-18.329A4.124 4.124 0 0 0 53.9 0H36.234c-.805 0-1.593.236-2.266.678L1.86 21.787A4.125 4.125 0 0 0 0 25.234v34.641A4.125 4.125 0 0 0 4.125 64Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M19.748 31.242a7.286 7.286 0 0 1-2.743-2.786c-.665-1.183-.997-2.503-.997-3.961s.332-2.778.997-3.96 1.58-2.112 2.743-2.787c1.165-.675 2.465-1.014 3.902-1.014 1.165 0 2.238.208 3.222.622a6.908 6.908 0 0 1 2.486 1.747 6.368 6.368 0 0 1 1.42 2.594h-3.13a3.913 3.913 0 0 0-.926-1.228c-.39-.346-.845-.614-1.367-.803s-1.083-.284-1.683-.284c-.887 0-1.687.221-2.402.664a4.611 4.611 0 0 0-1.677 1.828c-.404.775-.606 1.649-.606 2.62s.202 1.846.606 2.621a4.606 4.606 0 0 0 1.677 1.828c.715.443 1.515.664 2.402.664.821 0 1.566-.154 2.235-.461.668-.307 1.198-.733 1.591-1.276a3.295 3.295 0 0 0 .633-1.833l.01.31h-3.526v-2.304h6.357v1.18c0 1.321-.32 2.517-.96 3.585a6.885 6.885 0 0 1-2.626 2.525c-1.111.614-2.356.921-3.736.921s-2.737-.337-3.902-1.013v.002ZM36.271 31.242a7.32 7.32 0 0 1-2.755-2.786c-.668-1.183-1.002-2.503-1.002-3.961s.333-2.778 1.002-3.96a7.314 7.314 0 0 1 2.755-2.787c1.168-.675 2.474-1.014 3.918-1.014 1.443 0 2.738.338 3.907 1.013a7.279 7.279 0 0 1 2.749 2.787c.664 1.183.997 2.503.997 3.961s-.333 2.778-.997 3.96-1.581 2.113-2.75 2.788c-1.168.675-2.47 1.013-3.906 1.013-1.437 0-2.75-.338-3.918-1.013Zm6.308-2.229c.708-.446 1.262-1.065 1.662-1.854.4-.79.6-1.678.6-2.664 0-.986-.2-1.874-.6-2.664-.4-.79-.954-1.407-1.662-1.854-.707-.446-1.504-.67-2.39-.67-.885 0-1.685.223-2.395.67a4.62 4.62 0 0 0-1.672 1.854c-.404.79-.606 1.678-.606 2.664 0 .986.202 1.874.606 2.664.404.79.961 1.408 1.672 1.854.71.447 1.51.67 2.395.67.887 0 1.683-.223 2.39-.67ZM33 44H17v3h16v-3Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,713 @@
|
|||||||
|
<svg width="36" height="1080" viewBox="0 0 36 1080" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M26.6949 142.457L34.4572 121.934L28.2857 120.535L26.6949 142.457Z" fill="#087CFA"/>
|
||||||
|
<path d="M34.3611 135.943L26.6948 142.457L15.7543 139.639L34.3611 135.943Z" fill="url(#paint0_linear)"/>
|
||||||
|
<path d="M5.64003 124.622L2.14288 114.617L19.6046 109.543L5.64003 124.622Z" fill="url(#paint1_linear)"/>
|
||||||
|
<path d="M30.2606 128.599L25.3611 120.237L25.4537 120.178L19.6046 109.543L1.54285 129.048V142.457L34.3611 135.943L30.2606 128.599Z" fill="url(#paint2_linear)"/>
|
||||||
|
<path d="M7.71429 115.714H28.2857V136.286H7.71429V115.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 132.398H17.9932V133.683H10.2789V132.398Z" fill="white"/>
|
||||||
|
<path d="M13.0286 118.286H14.5714L17.856 126.01H16.0937L15.408 124.296H12.1509L11.4651 126.01H9.75085L13.0286 118.286ZM14.8046 122.801L13.776 120.312L12.7474 122.801H14.8046Z" fill="white"/>
|
||||||
|
<path d="M17.5852 122.184V122.163C17.5757 121.63 17.6744 121.1 17.8754 120.605C18.0763 120.111 18.3753 119.662 18.7543 119.286C19.1333 118.911 19.5845 118.615 20.0806 118.419C20.5768 118.222 21.1076 118.128 21.6412 118.142C22.2081 118.113 22.7747 118.205 23.3035 118.411C23.8324 118.618 24.3117 118.934 24.7097 119.338L23.6194 120.597C23.0975 120.054 22.3833 119.739 21.6309 119.719C20.3212 119.719 19.3749 120.806 19.3749 122.139V122.163C19.3749 123.497 20.2972 124.608 21.6309 124.608C22.5189 124.608 23.064 124.265 23.688 123.696L24.7646 124.8C24.3708 125.254 23.8795 125.613 23.3273 125.851C22.7752 126.088 22.1765 126.198 21.576 126.171C21.0498 126.18 20.5272 126.083 20.0392 125.886C19.5513 125.689 19.108 125.395 18.7357 125.023C18.3634 124.651 18.0696 124.208 17.872 123.721C17.6744 123.233 17.5768 122.71 17.5852 122.184" fill="white"/>
|
||||||
|
<path d="M13.3749 158.414L14.088 145.543L21.5726 149.691L13.3749 158.414Z" fill="url(#paint3_linear)"/>
|
||||||
|
<path d="M13.3748 158.414L14.088 145.543L4.58399 151.533L1.54285 169.742L13.3748 158.414Z" fill="url(#paint4_linear)"/>
|
||||||
|
<path d="M33.8023 155.407L29.568 146.798L21.5726 149.691L13.3748 158.414L1.54285 169.742L12.216 177.511L25.632 165.411L33.8023 155.407Z" fill="url(#paint5_linear)"/>
|
||||||
|
<path d="M28.2857 164.414V172.286H15.36L21.0411 176.681L29.2697 178.457L34.4571 166.714L28.2857 164.414Z" fill="url(#paint6_linear)"/>
|
||||||
|
<path d="M7.71429 151.714H28.2857V172.286H7.71429V151.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 168.398H17.9932V169.683H10.2789V168.398Z" fill="white"/>
|
||||||
|
<path d="M18.0172 154.265H19.7314V160.57H23.1086V162H18.0172V154.265Z" fill="white"/>
|
||||||
|
<path d="M9.73029 158.184V158.163C9.72087 157.63 9.81957 157.099 10.0205 156.605C10.2214 156.11 10.5203 155.661 10.8992 155.285C11.2782 154.909 11.7293 154.614 12.2255 154.417C12.7217 154.219 13.2526 154.125 13.7863 154.138C14.353 154.11 14.9192 154.203 15.4475 154.41C15.9758 154.617 16.4543 154.933 16.8514 155.338L15.7714 156.597C15.2483 156.054 14.5329 155.739 13.7794 155.719C12.4697 155.719 11.5269 156.806 11.5269 158.139V158.163C11.5269 159.497 12.4491 160.608 13.7794 160.608C14.6709 160.608 15.2126 160.265 15.8366 159.696L16.9063 160.8C16.5127 161.255 16.0209 161.615 15.468 161.853C14.915 162.09 14.3154 162.199 13.7143 162.171C13.1886 162.179 12.6668 162.082 12.1796 161.884C11.6925 161.686 11.25 161.393 10.8784 161.021C10.5068 160.649 10.2138 160.206 10.0165 159.719C9.81931 159.232 9.72197 158.71 9.73029 158.184" fill="white"/>
|
||||||
|
<path d="M32.3588 186.658L34.4571 200.133L30.5794 202.389L32.3588 186.658Z" fill="url(#paint7_linear)"/>
|
||||||
|
<path d="M32.3589 186.658L20.5749 181.543L17.2869 184.272L32.3589 186.658Z" fill="url(#paint8_linear)"/>
|
||||||
|
<path d="M23.784 214.457L5.49945 199.889L1.82745 210.933L23.784 214.457Z" fill="url(#paint9_linear)"/>
|
||||||
|
<path d="M26.3314 205.251L1.54285 196.735L23.784 214.457L26.3314 205.251Z" fill="url(#paint10_linear)"/>
|
||||||
|
<path d="M1.54285 181.776V196.735L30.1063 206.551L32.3588 186.658L1.54285 181.776Z" fill="url(#paint11_linear)"/>
|
||||||
|
<path d="M7.71429 187.673H28.2857V208.245H7.71429V187.673Z" fill="black"/>
|
||||||
|
<path d="M9.80231 190.347H13.1246C15.7989 190.347 17.6503 192.185 17.6503 194.582V194.606C17.6503 197.006 15.7989 198.864 13.1246 198.864H9.80231V190.347ZM11.6743 192.038V197.181H13.1246C13.4648 197.2 13.8053 197.148 14.1239 197.027C14.4426 196.907 14.7324 196.72 14.9743 196.481C15.2162 196.241 15.4048 195.952 15.528 195.635C15.6512 195.317 15.7061 194.977 15.6892 194.637V194.606C15.7069 194.264 15.6527 193.923 15.5301 193.603C15.4075 193.284 15.2193 192.994 14.9776 192.752C14.7359 192.51 14.4461 192.321 14.1269 192.198C13.8077 192.075 13.4662 192.02 13.1246 192.038H11.6743Z" fill="white"/>
|
||||||
|
<path d="M17.9829 194.63V194.606C17.9782 194.021 18.0906 193.442 18.3136 192.902C18.5366 192.362 18.8656 191.872 19.2811 191.462C19.6966 191.051 20.1902 190.728 20.7328 190.511C21.2754 190.294 21.8559 190.189 22.44 190.2C23.6657 190.135 24.8677 190.555 25.7863 191.369L24.6069 192.792C24.0074 192.223 23.2081 191.913 22.3817 191.928C21.0103 191.928 19.9337 193.135 19.9337 194.582V194.606C19.9337 196.162 21.0069 197.307 22.5154 197.307C23.1438 197.328 23.7628 197.151 24.2846 196.8V195.579H22.3989V193.961H26.0983V197.661C25.0893 198.537 23.7967 199.017 22.4606 199.011C19.8069 199.011 17.9829 197.143 17.9829 194.63V194.63Z" fill="white"/>
|
||||||
|
<path d="M9.60687 204.384H17.3212V205.67H9.60687V204.384Z" fill="white"/>
|
||||||
|
<path d="M30.5348 230.273L34.4571 238.971L27.744 250.457L23.6091 240.418L30.5348 230.273Z" fill="url(#paint12_linear)"/>
|
||||||
|
<path d="M23.6092 240.418L27.744 250.457L17.3417 246.881L23.6092 240.418Z" fill="#B74AF7"/>
|
||||||
|
<path d="M24.7303 226.838L22.5977 217.543H10.7726L1.54285 231.662L4.2137 238.149L1.54285 244.07L20.6571 227.808L24.7303 226.838Z" fill="url(#paint13_linear)"/>
|
||||||
|
<path d="M34.4571 224.53L20.6571 227.808L1.54285 244.07L13.872 250.457L23.6091 240.418L34.4571 224.53Z" fill="url(#paint14_linear)"/>
|
||||||
|
<path d="M7.71429 223.714H28.2857V244.286H7.71429V223.714Z" fill="black"/>
|
||||||
|
<path d="M9.64459 240.429H17.3589V241.714H9.64459V240.429Z" fill="white"/>
|
||||||
|
<path d="M9.06515 230.678V230.654C9.06319 230.073 9.17746 229.498 9.40123 228.962C9.625 228.426 9.95376 227.94 10.3681 227.533C10.7825 227.126 11.2741 226.806 11.8141 226.591C12.354 226.377 12.9313 226.273 13.512 226.286C14.7333 226.221 15.9311 226.639 16.848 227.448L15.672 228.867C15.0758 228.297 14.2786 227.986 13.4537 228C12.0823 228 11.016 229.2 11.016 230.64V230.664C11.016 232.217 12.0823 233.355 13.5874 233.355C14.2117 233.374 14.8259 233.195 15.3429 232.845V231.624H13.464V230.013H17.1429V233.695C16.1373 234.567 14.85 235.046 13.5189 235.042C10.8823 235.042 9.06515 233.187 9.06515 230.678V230.678Z" fill="white"/>
|
||||||
|
<path d="M17.9315 230.678V230.654C17.9335 230.068 18.053 229.488 18.2829 228.949C18.5128 228.409 18.8485 227.922 19.2701 227.515C19.6917 227.107 20.1906 226.789 20.7375 226.578C21.2843 226.367 21.8679 226.267 22.4537 226.286C25.0697 226.286 26.9486 228.237 26.9486 230.647V230.671C26.9493 231.258 26.832 231.84 26.6035 232.382C26.375 232.923 26.0401 233.413 25.6185 233.822C25.197 234.232 24.6975 234.552 24.1498 234.765C23.602 234.977 23.017 235.077 22.4297 235.059C19.8103 235.042 17.9315 233.091 17.9315 230.678V230.678ZM24.9977 230.678V230.654C25.0083 230.31 24.9497 229.967 24.8255 229.646C24.7012 229.325 24.5139 229.032 24.2744 228.784C24.035 228.537 23.7484 228.34 23.4315 228.205C23.1147 228.071 22.774 228.001 22.4297 228C22.088 227.999 21.7496 228.067 21.4348 228.2C21.12 228.332 20.8353 228.528 20.5977 228.773C20.3601 229.019 20.1746 229.31 20.0522 229.629C19.9299 229.948 19.8733 230.288 19.8857 230.63V230.654C19.8738 230.999 19.9314 231.344 20.0552 231.666C20.179 231.989 20.3664 232.283 20.6063 232.532C20.8462 232.781 21.1336 232.979 21.4516 233.115C21.7696 233.25 22.1115 233.32 22.4572 233.321C22.7998 233.322 23.139 233.253 23.4544 233.119C23.7697 232.986 24.0547 232.789 24.2921 232.542C24.5295 232.295 24.7144 232.003 24.8357 231.682C24.957 231.362 25.0121 231.02 24.9977 230.678V230.678Z" fill="white"/>
|
||||||
|
<path d="M6.60683 276.758L1.76569 272.935L4.61483 267.658L8.89711 269.091L6.60683 276.758Z" fill="url(#paint15_linear)"/>
|
||||||
|
<path d="M34.3097 262.317L33.7166 281.379L21.0377 286.457L14.1326 282L34.3097 262.317Z" fill="#087CFA"/>
|
||||||
|
<path d="M34.3097 262.317L28.0354 268.437L19.9783 258.552L23.9554 254.081L34.3097 262.317Z" fill="url(#paint16_linear)"/>
|
||||||
|
<path d="M14.1326 282L4.04916 285.645L6.16116 278.249L8.89716 269.091L1.39545 266.582L6.16116 253.543L16.9337 254.815L28.0354 268.437L14.1326 282Z" fill="url(#paint17_linear)"/>
|
||||||
|
<path d="M7.71429 259.714H28.2857V280.286H7.71429V259.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 276.398H17.9932V277.683H10.2789V276.398Z" fill="white"/>
|
||||||
|
<path d="M14.1292 263.698V262.293H10.2926V263.698H11.3692V268.553H10.2926V269.959H14.1292V268.553H13.056V263.698H14.1292Z" fill="white"/>
|
||||||
|
<path d="M17.808 270.069C17.2851 270.085 16.7668 269.967 16.3028 269.726C15.9218 269.521 15.5822 269.246 15.3017 268.917L16.3611 267.734C16.5547 267.952 16.7787 268.141 17.0263 268.296C17.2475 268.433 17.5032 268.504 17.7634 268.502C17.9026 268.508 18.0416 268.485 18.1711 268.434C18.3006 268.382 18.4176 268.304 18.5143 268.203C18.7208 267.924 18.8183 267.579 18.7886 267.233V262.286H20.5028V267.309C20.5105 267.715 20.4467 268.12 20.3143 268.505C20.2015 268.825 20.0172 269.115 19.7754 269.352C19.5335 269.59 19.2406 269.769 18.9188 269.877C18.5535 270 18.1697 270.061 17.784 270.055" fill="white"/>
|
||||||
|
<path d="M19.7109 295.673L18.6137 292.354L7.15885 289.886L1.54285 296.239L7.71428 299.397V295.673H19.7109Z" fill="url(#paint18_linear)"/>
|
||||||
|
<path d="M7.71428 297.833L1.54285 296.239L4.67999 315.082L7.70399 315.058L7.71428 297.833Z" fill="url(#paint19_linear)"/>
|
||||||
|
<path d="M28.2857 295.673H17.5508L21.864 291.84L30.1748 293.383L34.4571 304.025L28.2891 310.152L28.2857 295.673Z" fill="url(#paint20_linear)"/>
|
||||||
|
<path d="M28.2926 303.885L28.2857 316.245H10.6149L11.0709 318.933L22.1657 322.8L34.4572 315.442L28.2926 303.885Z" fill="url(#paint21_linear)"/>
|
||||||
|
<path d="M7.71429 295.714H28.2857V316.286H7.71429V295.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 312.398H17.9932V313.683H10.2789V312.398Z" fill="white"/>
|
||||||
|
<path d="M17.0057 304.893L18.0103 303.689C18.6414 304.272 19.4625 304.606 20.3212 304.629C21.0069 304.629 21.4389 304.351 21.4389 303.898V303.878C21.4389 303.446 21.1715 303.223 19.8789 302.894C18.3189 302.496 17.3143 302.064 17.3143 300.528V300.504C17.3143 299.102 18.4423 298.173 20.0229 298.173C21.0663 298.154 22.083 298.502 22.896 299.157L22.0115 300.439C21.4334 299.987 20.7315 299.721 19.9989 299.678C19.3475 299.678 19.0046 299.976 19.0046 300.363V300.387C19.0046 300.895 19.3475 301.073 20.6743 301.416C22.2446 301.827 23.1292 302.39 23.1292 303.737V303.761C23.1292 305.297 21.9566 306.161 20.2869 306.161C19.0783 306.166 17.9103 305.725 17.0057 304.923" fill="white"/>
|
||||||
|
<path d="M10.2857 298.286H13.44C15.2777 298.286 16.392 299.376 16.392 300.95V300.97C16.392 302.753 15.0034 303.679 13.2754 303.679H12V305.99H10.2857V298.286ZM13.3234 302.17C14.1703 302.17 14.6674 301.666 14.6674 301.005V300.984C14.6674 300.223 14.1394 299.815 13.296 299.815H12V302.17H13.3234Z" fill="white"/>
|
||||||
|
<path d="M24.6206 330.737L34.2378 338.736L30.7749 345.782L24.9772 344.174H19.9612L24.6206 330.737Z" fill="url(#paint22_linear)"/>
|
||||||
|
<path d="M14.9314 335.931L13.0697 345.782L12.8914 349.128L8.1977 351.161L1.54285 351.878L3.56228 330.597L15.6206 325.543L23.0503 330.415L14.9314 335.931Z" fill="url(#paint23_linear)"/>
|
||||||
|
<path d="M14.9314 335.931L15.8366 354.912L12.8297 358.457L1.54285 351.878L10.8103 338.067L14.9314 335.931Z" fill="url(#paint24_linear)"/>
|
||||||
|
<path d="M27.3497 334.546H15.9463L26.0229 325.543L27.3497 334.546Z" fill="url(#paint25_linear)"/>
|
||||||
|
<path d="M34.4572 354.987L24.4183 358.395L11.0503 354.631L14.9314 335.931L16.4811 334.546L24.6206 333.782L23.8834 341.955L30.3566 339.446L34.4572 354.987Z" fill="url(#paint26_linear)"/>
|
||||||
|
<path d="M7.71429 331.714H28.2857V352.286H7.71429V331.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 348.398H17.9932V349.683H10.2789V348.398Z" fill="white"/>
|
||||||
|
<path d="M10.2857 334.286H13.44C15.2777 334.286 16.392 335.376 16.392 336.95V336.97C16.392 338.753 15.0034 339.679 13.2754 339.679H12V341.99H10.2857V334.286ZM13.3234 338.17C14.1703 338.17 14.6674 337.666 14.6674 337.005V336.984C14.6674 336.223 14.1394 335.815 13.296 335.815H12V338.17H13.3234Z" fill="white"/>
|
||||||
|
<path d="M17.3109 338.163V338.143C17.301 337.614 17.3983 337.089 17.5969 336.599C17.7956 336.11 18.0914 335.665 18.4667 335.293C18.8419 334.92 19.2887 334.628 19.7801 334.433C20.2715 334.238 20.7973 334.145 21.3257 334.159C21.8872 334.13 22.4482 334.222 22.9719 334.426C23.4955 334.631 23.9698 334.944 24.3634 335.345L23.2869 336.59C22.7687 336.053 22.0611 335.741 21.3154 335.719C20.016 335.719 19.08 336.799 19.08 338.119V338.143C19.08 339.463 19.9954 340.563 21.3154 340.563C22.1966 340.563 22.7349 340.221 23.3417 339.662L24.4183 340.749C24.0306 341.202 23.5448 341.561 22.9976 341.799C22.4505 342.036 21.8565 342.146 21.2606 342.12C20.7391 342.128 20.2214 342.031 19.7382 341.835C19.255 341.639 18.8161 341.347 18.4477 340.978C18.0793 340.609 17.7888 340.17 17.5935 339.686C17.3982 339.203 17.3021 338.685 17.3109 338.163" fill="white"/>
|
||||||
|
<path d="M25.6148 368.887L13.9474 383.63L11.3143 394.409H29.0263L34.4571 372.339L25.6148 368.887Z" fill="url(#paint27_linear)"/>
|
||||||
|
<path d="M24.576 361.495H7.00456L1.54285 383.63H27.6891L24.576 361.495Z" fill="url(#paint28_linear)"/>
|
||||||
|
<path d="M25.4914 367.755L24.576 361.495L22.6011 367.755H25.4914Z" fill="url(#paint29_linear)"/>
|
||||||
|
<path d="M7.71429 367.714H28.2857V388.286H7.71429V367.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 384.398H17.9932V385.683H10.2789V384.398Z" fill="white"/>
|
||||||
|
<path d="M18.6994 376.347H17.8594V374.935H18.9429L19.2171 373.296H18.2469V371.88H19.4606L19.7383 370.265H21.1989L20.9143 371.88H22.5257L22.8034 370.269H24.2606L23.9863 371.88H24.8263V373.296H23.7429L23.4651 374.935H24.4389V376.347H23.2217L22.9337 378.007H21.4766L21.7611 376.347H20.16L19.8857 378.007H18.4114L18.6994 376.347ZM22.0046 374.935L22.2857 373.296H20.6777L20.4034 374.935H22.0046Z" fill="white"/>
|
||||||
|
<path d="M10.2865 370.265H13.8316C14.2411 370.238 14.6517 370.294 15.0393 370.429C15.4268 370.564 15.7833 370.775 16.0876 371.05C16.3119 371.285 16.4861 371.563 16.5999 371.867C16.7136 372.171 16.7645 372.495 16.7494 372.819V372.84C16.776 373.388 16.6139 373.929 16.2899 374.373C15.9794 374.77 15.5619 375.071 15.0865 375.24L16.9756 378.003H14.9871L13.3859 375.627H12.0008V378.003H10.2865V370.265ZM13.7151 374.037C14.0685 374.054 14.4157 373.939 14.6888 373.714C14.8003 373.615 14.8888 373.492 14.948 373.354C15.0072 373.217 15.0358 373.068 15.0316 372.919V372.898C15.0444 372.743 15.02 372.586 14.9604 372.442C14.9007 372.298 14.8077 372.17 14.6888 372.069C14.4014 371.868 14.0547 371.77 13.7048 371.791H12.0008V374.023L13.7151 374.037Z" fill="white"/>
|
||||||
|
<path d="M28.5737 505.543L19.1554 508.917L11.7325 505.543L9.28797 511.701H7.7074V529.879L30.5657 530.078L34.1143 511.975L28.5737 505.543Z" fill="url(#paint30_linear)"/>
|
||||||
|
<path d="M28.2926 517.354L13.2515 508.44L28.2926 526.07V517.354Z" fill="url(#paint31_linear)"/>
|
||||||
|
<path d="M14.9006 537.6L27.2023 535.961L25.2926 532.289H28.2926V526.07L13.248 508.413L1.20001 511.371L1.21373 528.651L8.13944 538.457L14.8629 537.603L14.8937 537.6H14.9006Z" fill="url(#paint32_linear)"/>
|
||||||
|
<path d="M7.71429 511.714H28.2857V532.286H7.71429V511.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 528.398H17.9932V529.684H10.2789V528.398Z" fill="white"/>
|
||||||
|
<path d="M17.9006 514.275H19.7349L21.768 517.546L23.8011 514.275H25.6354V522.007H23.9451V516.96L21.768 520.262H21.7234L19.5703 516.994V522.007H17.9006V514.275Z" fill="white"/>
|
||||||
|
<path d="M10.2858 514.286H13.8172C14.7955 514.286 15.5452 514.546 16.0663 515.067C16.2901 515.302 16.4641 515.579 16.5779 515.883C16.6916 516.186 16.7427 516.509 16.7281 516.833V516.857C16.7531 517.403 16.591 517.941 16.2686 518.383C15.96 518.779 15.545 519.08 15.0721 519.25L16.9612 521.993H14.9726L13.3715 519.624H11.9692V521.997H10.2858V514.286ZM13.7143 518.026C14.0584 518.042 14.3961 517.931 14.6641 517.714C14.7756 517.615 14.8641 517.492 14.9233 517.354C14.9825 517.217 15.011 517.068 15.0069 516.919V516.898C15.0197 516.743 14.9952 516.587 14.9356 516.444C14.876 516.3 14.7829 516.173 14.6641 516.072C14.3777 515.872 14.0323 515.774 13.6835 515.794H11.9761V518.013L13.7143 518.026Z" fill="white"/>
|
||||||
|
<path d="M5.97942 571.289L1.54285 544.992L9.74742 541.587L14.9863 544.701L19.7863 542.112L29.7874 545.952L24.1783 574.457L5.97942 571.289Z" fill="url(#paint33_linear)"/>
|
||||||
|
<path d="M34.4572 552.689L30.2092 542.198L22.5018 541.543L10.6046 552.974L13.8069 567.706L19.7863 571.899L34.4572 563.194L30.8572 556.443L34.4572 552.689Z" fill="url(#paint34_linear)"/>
|
||||||
|
<path d="M27.864 551.126L30.8571 556.444L34.4571 552.689L31.8137 546.154L27.864 551.126Z" fill="url(#paint35_linear)"/>
|
||||||
|
<path d="M7.71429 547.714H28.2857V568.286H7.71429V547.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 564.398H17.9932V565.683H10.2789V564.398Z" fill="white"/>
|
||||||
|
<path d="M17.5817 550.275L16.4331 554.777L15.12 550.275H13.8137L12.4971 554.777L11.352 550.275H9.552L11.7566 557.993H13.2034L14.4651 553.526L15.7131 557.993H17.1771L19.3783 550.275H17.5817Z" fill="white"/>
|
||||||
|
<path d="M19.7006 556.893L20.7052 555.689C21.3362 556.272 22.1574 556.606 23.016 556.629C23.7018 556.629 24.1338 556.354 24.1338 555.898V555.878C24.1338 555.446 23.8663 555.226 22.5738 554.894C21.0138 554.496 20.0092 554.064 20.0092 552.528V552.504C20.0092 551.102 21.1372 550.173 22.7178 550.173C23.7611 550.154 24.7779 550.502 25.5909 551.157L24.7063 552.439C24.1283 551.987 23.4263 551.721 22.6938 551.678C22.0423 551.678 21.6995 551.976 21.6995 552.363V552.387C21.6995 552.895 22.0423 553.073 23.3692 553.416C24.9395 553.827 25.824 554.39 25.824 555.741V555.761C25.824 557.297 24.6515 558.161 22.9818 558.161C21.7731 558.166 20.605 557.725 19.7006 556.923" fill="white"/>
|
||||||
|
<path d="M4 580H32V608H4V580Z" fill="url(#paint36_linear)"/>
|
||||||
|
<path d="M32 580V608H4L32 580V580Z" fill="black"/>
|
||||||
|
<path d="M20 602H28V604H20V602Z" fill="white"/>
|
||||||
|
<path d="M32.0994 617.434C31.6891 617.218 31.2456 617.073 30.7872 617.003C30.4995 616.971 12.995 613.609 12.0685 613.554C11.6139 613.528 11.1577 613.548 10.7072 613.615C2.56674 614.827 0.51945 624.737 7.23423 628.877C8.81627 629.85 10.715 630.17 12.5286 629.769C15.7088 629.076 30.042 625.537 30.9767 625.334C34.9542 624.47 35.7828 619.374 32.0994 617.434Z" fill="url(#paint37_linear)"/>
|
||||||
|
<path d="M32.2701 632.682C31.5817 631.91 19.1815 617.816 16.5311 615.517C15.9783 615.041 15.3726 614.63 14.7257 614.292C13.4918 613.646 12.0846 613.409 10.7072 613.615C3.35955 614.709 0.976019 622.889 5.51922 627.516C6.00943 628.016 19.66 643.193 19.7774 643.341C20.4266 644.154 21.2136 644.847 22.1026 645.388C23.6988 646.37 25.6146 646.693 27.4446 646.288C34.1681 644.824 36.3381 637.243 32.2701 632.682Z" fill="url(#paint38_linear)"/>
|
||||||
|
<path d="M29.661 630.669C29.3879 630.526 29.1049 630.402 28.8141 630.298C28.5844 630.216 10.1394 624.394 9.73239 624.292C9.04823 624.12 8.33704 624.086 7.63946 624.189C1.01985 625.175 -0.644814 633.234 4.81552 636.6C6.09869 637.391 22.918 645.838 23.3492 645.999C24.6579 646.488 26.0802 646.589 27.4446 646.288C35.3058 644.576 36.942 634.503 29.661 630.669Z" fill="url(#paint39_linear)"/>
|
||||||
|
<path d="M32.0993 617.434C31.4695 617.104 30.7512 616.983 30.048 617.088C29.6874 617.14 29.3336 617.232 28.9927 617.361C27.3442 617.989 6.24894 624.49 5.64569 624.752C4.50546 625.224 3.51931 626.004 2.79804 627.005C2.07677 628.006 1.64898 629.188 1.56274 630.419C1.47649 631.65 1.73521 632.881 2.30983 633.973C2.88444 635.064 3.75217 635.974 4.81552 636.6C6.102 637.392 7.646 637.652 9.12084 637.326C9.69244 637.203 10.2481 637.015 10.777 636.766C13.3871 635.529 31.9219 625.052 32.318 624.826C32.9847 624.455 33.5366 623.908 33.9135 623.244C34.2904 622.581 34.4779 621.826 34.4553 621.064C34.4327 620.301 34.2011 619.559 33.7856 618.919C33.3701 618.279 32.7868 617.765 32.0993 617.434V617.434Z" fill="url(#paint40_linear)"/>
|
||||||
|
<path d="M28.2856 619.714H7.71436V640.286H28.2856V619.714Z" fill="black"/>
|
||||||
|
<path d="M17.9927 636.399H10.2784V637.684H17.9927V636.399Z" fill="white"/>
|
||||||
|
<path d="M10.2784 622.259H13.2963C15.7282 622.259 17.4084 623.928 17.4084 626.106V626.128C17.4084 628.306 15.7282 629.997 13.2963 629.997H10.2784V622.259ZM11.9808 623.795V628.46H13.2963C13.6054 628.479 13.915 628.432 14.2048 628.322C14.4946 628.213 14.7581 628.044 14.9782 627.826C15.1982 627.608 15.3699 627.346 15.482 627.057C15.594 626.768 15.644 626.459 15.6287 626.15V626.128C15.6445 625.817 15.595 625.507 15.4834 625.217C15.3717 624.927 15.2005 624.663 14.9807 624.443C14.7608 624.224 14.4973 624.052 14.2072 623.941C13.9171 623.829 13.6067 623.78 13.2963 623.795H11.9808Z" fill="white"/>
|
||||||
|
<path d="M18.5105 622.266H20.2165V628.569H23.6002V630H18.5105V622.266Z" fill="white"/>
|
||||||
|
<path d="M9.92231 678.007L3.02402 659.866C2.59271 658.734 2.43892 657.514 2.57552 656.31C2.71212 655.105 3.13513 653.951 3.80917 652.944C4.48321 651.936 5.38868 651.105 6.44984 650.519C7.511 649.934 8.69699 649.611 9.9086 649.577C11.5409 649.535 13.1432 650.021 14.4774 650.962C15.8116 651.904 16.8065 653.25 17.3143 654.802L23.352 673.245C24.5692 676.965 11.2869 681.6 9.92231 678.007V678.007Z" fill="url(#paint41_linear)"/>
|
||||||
|
<path d="M30.6205 672.25L13.7143 681.394C12.7253 681.929 11.6367 682.254 10.5163 682.35C9.39603 682.445 8.26813 682.308 7.2031 681.948C6.13806 681.587 5.15879 681.011 4.32647 680.255C3.49414 679.499 2.82665 678.58 2.36569 677.554C1.91129 676.548 1.66494 675.46 1.64144 674.356C1.61795 673.252 1.81778 672.155 2.22895 671.13C2.64012 670.106 3.25414 669.175 4.03414 668.393C4.81414 667.612 5.74403 666.996 6.76797 666.583L24.5965 659.383C26.0702 658.788 27.7009 658.705 29.2272 659.148C30.7535 659.591 32.0869 660.533 33.0137 661.824C33.6058 662.645 34.0159 663.583 34.2166 664.576C34.4172 665.568 34.4039 666.592 34.1774 667.579C33.9509 668.566 33.5164 669.493 32.9031 670.298C32.2897 671.104 31.5116 671.769 30.6205 672.25V672.25Z" fill="url(#paint42_linear)"/>
|
||||||
|
<path d="M24.2331 672.408L6.77485 663.97C5.68392 663.442 4.7353 662.66 4.0082 661.69C3.2811 660.72 2.79665 659.59 2.59529 658.395C2.39394 657.199 2.48153 655.973 2.85076 654.819C3.21998 653.664 3.86012 652.614 4.71771 651.758C5.87285 650.603 7.37045 649.853 8.98694 649.619C10.6034 649.386 12.2521 649.681 13.6869 650.462L30.7269 659.719C32.3754 660.616 33.6041 662.127 34.1469 663.924C34.6898 665.72 34.5032 667.659 33.6274 669.319C32.7672 670.951 31.3069 672.185 29.5544 672.761C27.8019 673.338 25.8941 673.211 24.2331 672.408Z" fill="url(#paint43_linear)"/>
|
||||||
|
<path d="M6.37031 681.713C5.21702 681.187 4.20026 680.402 3.39872 679.42C2.59719 678.438 2.03237 677.285 1.74797 676.05C1.46358 674.814 1.46724 673.53 1.75867 672.296C2.0501 671.062 2.62148 669.912 3.4286 668.935L18.1989 651.086C18.7734 650.39 19.561 649.902 20.4401 649.697C21.3193 649.493 22.2413 649.583 23.064 649.954C24.0084 650.38 24.7584 651.145 25.1654 652.097C25.5724 653.05 25.6068 654.12 25.2617 655.097L17.5612 676.947C17.1832 678.028 16.5839 679.018 15.8017 679.854C15.0194 680.689 14.0713 681.353 13.0181 681.801C11.9648 682.25 10.8296 682.474 9.68488 682.459C8.54021 682.443 7.41129 682.189 6.37031 681.713V681.713Z" fill="url(#paint44_linear)"/>
|
||||||
|
<path d="M7.71429 655.714H28.2857V676.286H7.71429V655.714Z" fill="black"/>
|
||||||
|
<path d="M10.2788 672.398H17.9931V673.683H10.2788V672.398Z" fill="white"/>
|
||||||
|
<path d="M12.3531 659.825H9.99768V658.255H16.4091V659.825H14.0537V665.993H12.3531V659.825Z" fill="white"/>
|
||||||
|
<path d="M17.0502 662.146V662.126C17.0403 661.595 17.1382 661.067 17.3379 660.575C17.5376 660.083 17.8351 659.636 18.2123 659.262C18.5895 658.888 19.0386 658.594 19.5324 658.399C20.0263 658.203 20.5547 658.11 21.0857 658.125C21.6495 658.096 22.213 658.187 22.7391 658.392C23.2651 658.597 23.7416 658.911 24.1371 659.314L23.0537 660.562C22.5333 660.022 21.8216 659.708 21.072 659.688C19.7691 659.688 18.8297 660.771 18.8297 662.088V662.112C18.8297 663.439 19.7485 664.543 21.072 664.543C21.9565 664.543 22.4982 664.2 23.1085 663.638L24.192 664.731C23.801 665.185 23.3121 665.544 22.7622 665.782C22.2122 666.02 21.6156 666.129 21.0171 666.103C20.4947 666.111 19.976 666.015 19.4917 665.819C19.0073 665.623 18.5672 665.332 18.1973 664.963C17.8274 664.594 17.5353 664.155 17.3383 663.671C17.1412 663.187 17.0433 662.669 17.0502 662.146" fill="white"/>
|
||||||
|
<path d="M9.94282 718.399L31.296 714.744C32.073 714.612 32.7912 714.246 33.3543 713.694C33.9175 713.143 34.2986 712.432 34.4468 711.658C34.5464 711.144 34.5405 710.615 34.4296 710.103C34.3186 709.591 34.1049 709.107 33.8013 708.68C33.4978 708.253 33.1107 707.892 32.6635 707.619C32.2164 707.346 31.7185 707.166 31.2 707.091L9.73025 704.078C8.77129 703.943 7.79504 704.002 6.85914 704.25C5.92324 704.499 5.04669 704.933 4.28124 705.526C3.51579 706.119 2.87697 706.86 2.40251 707.704C1.92805 708.548 1.62757 709.479 1.51882 710.441C1.39194 711.558 1.52679 712.69 1.9127 713.745C2.2986 714.801 2.92498 715.753 3.74225 716.525C4.55952 717.297 5.54525 717.868 6.62145 718.194C7.69765 718.519 8.83476 718.589 9.94282 718.399V718.399Z" fill="url(#paint45_linear)"/>
|
||||||
|
<path d="M24.2674 687.113L3.84687 705.943C3.07765 706.652 2.47147 707.52 2.07016 708.486C1.66884 709.453 1.48195 710.495 1.52237 711.54C1.56278 712.586 1.82955 713.61 2.30426 714.543C2.77897 715.475 3.45033 716.294 4.27201 716.942C5.70171 718.066 7.50268 718.611 9.31611 718.468C11.1295 718.324 12.8223 717.503 14.0572 716.167L32.9143 695.76C33.4762 695.15 33.9081 694.432 34.184 693.65C34.4598 692.867 34.5738 692.037 34.519 691.21C34.4641 690.382 34.2417 689.574 33.865 688.835C33.4884 688.096 32.9655 687.442 32.328 686.911C31.1883 685.961 29.7425 685.459 28.2597 685.496C26.7768 685.533 25.3581 686.108 24.2674 687.113V687.113Z" fill="url(#paint46_linear)"/>
|
||||||
|
<path d="M28.3475 714.031L4.14517 696.631C3.40125 696.095 2.78675 695.4 2.34675 694.595C1.90675 693.791 1.65243 692.898 1.60246 691.983C1.55248 691.067 1.7081 690.152 2.05792 689.305C2.40774 688.458 2.94286 687.699 3.62402 687.086C4.78388 686.041 6.29813 685.478 7.85864 685.51C9.41914 685.543 10.9086 686.169 12.024 687.261L33.336 708.093C33.7091 708.457 34.0048 708.894 34.2057 709.375C34.4066 709.857 34.5085 710.374 34.5053 710.896C34.5021 711.418 34.3939 711.933 34.1871 712.413C33.9803 712.892 33.6792 713.324 33.3017 713.685C32.6456 714.31 31.7916 714.688 30.887 714.751C29.9825 714.814 29.0842 714.559 28.3475 714.031V714.031Z" fill="url(#paint47_linear)"/>
|
||||||
|
<path d="M7.71429 691.714H28.2857V712.286H7.71429V691.714Z" fill="black"/>
|
||||||
|
<path d="M10.2787 708.398H17.993V709.683H10.2787V708.398Z" fill="white"/>
|
||||||
|
<path d="M12.5074 698.942L9.53479 694.255H11.5268L13.3714 697.361L15.2502 694.255H17.1839L14.2114 698.911V701.993H12.5074V698.942Z" fill="white"/>
|
||||||
|
<path d="M20.5885 695.825H18.2365V694.255H24.6445V695.825H22.2925V701.993H20.5885V695.825Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.61183 754C13.0318 753.528 18.2317 751.636 22.6885 748.516C27.1453 745.396 30.7018 741.157 33 736.226C30.1429 732.143 26.3436 728.809 21.9241 726.506C17.5046 724.204 12.5952 723 7.61183 722.999C6.71072 722.999 5.80547 723.036 4.90023 723.118C2.60212 728.051 1.64285 733.502 2.11883 738.922C2.59482 744.343 4.48928 749.543 7.61183 754V754Z" fill="url(#paint48_linear)"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.92914 723.118C11.5286 725.802 17.1985 730.359 21.2401 736.226H33C30.152 732.141 26.3592 728.806 21.9446 726.503C17.5299 724.2 12.6241 722.997 7.64487 722.999C6.74376 722.999 5.83852 723.038 4.92914 723.118V723.118Z" fill="url(#paint49_linear)"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.2442 736.226C19.8264 745.58 7.61182 754 7.61182 754C18.7145 752.946 28.4531 746.291 33 736.226H21.2442Z" fill="url(#paint50_linear)"/>
|
||||||
|
<path d="M29.304 768.991L7.25488 771.357C5.94386 771.496 4.62846 771.135 3.57277 770.345C2.51709 769.555 1.79928 768.395 1.56345 767.098C1.43947 766.413 1.45383 765.71 1.60568 765.031C1.75754 764.351 2.04382 763.709 2.44767 763.142C2.85152 762.575 3.36476 762.094 3.95718 761.729C4.5496 761.363 5.20921 761.119 5.89717 761.013L27.8126 757.608C28.6605 757.476 29.5271 757.536 30.3487 757.783C31.1703 758.031 31.926 758.459 32.5602 759.037C33.1944 759.615 33.6909 760.328 34.0133 761.123C34.3356 761.918 34.4756 762.776 34.4229 763.632C34.3413 764.986 33.7822 766.268 32.845 767.249C31.9079 768.23 30.6532 768.847 29.304 768.991V768.991Z" fill="url(#paint51_linear)"/>
|
||||||
|
<path d="M21.1543 788.174L3.00683 769.838C2.49096 769.316 2.09066 768.692 1.83195 768.006C1.57325 767.319 1.46195 766.586 1.50528 765.854C1.54862 765.122 1.74563 764.407 2.08349 763.756C2.42134 763.105 2.89249 762.532 3.46625 762.075C4.33583 761.384 5.40325 760.989 6.51321 760.946C7.62317 760.904 8.71775 761.216 9.63768 761.839L31.0011 776.321C31.9921 776.994 32.8136 777.887 33.4011 778.931C33.9885 779.975 34.3259 781.14 34.3867 782.336C34.4475 783.533 34.2301 784.727 33.7515 785.824C33.2729 786.922 32.5462 787.894 31.6285 788.664C30.1395 789.911 28.236 790.553 26.2958 790.462C24.3555 790.371 22.5203 789.555 21.1543 788.174V788.174Z" fill="url(#paint52_linear)"/>
|
||||||
|
<path d="M25.2549 758.667L4.05257 774.514C3.60487 774.848 3.23111 775.271 2.95479 775.757C2.67848 776.242 2.50558 776.779 2.44697 777.335C2.38837 777.89 2.44532 778.452 2.61425 778.984C2.78317 779.517 3.06043 780.008 3.42857 780.429C4.04192 781.126 4.87664 781.591 5.79253 781.746C6.70843 781.901 7.64971 781.736 8.45828 781.279L31.5051 768.25C32.2492 767.83 32.89 767.249 33.3808 766.55C33.8716 765.851 34.2 765.051 34.342 764.208C34.4839 763.365 34.4359 762.502 34.2013 761.68C33.9668 760.859 33.5516 760.1 32.9863 759.459C32.0322 758.381 30.7074 757.701 29.2751 757.554C27.8429 757.408 26.4078 757.805 25.2549 758.667V758.667Z" fill="url(#paint53_linear)"/>
|
||||||
|
<path d="M23.4857 789.795L4.80002 781.402C4.24589 781.155 3.75479 780.786 3.36347 780.323C2.97216 779.859 2.69078 779.313 2.5404 778.726C2.39001 778.138 2.37452 777.524 2.49508 776.929C2.61565 776.335 2.86914 775.775 3.23659 775.293C3.6364 774.771 4.15793 774.355 4.7556 774.081C5.35327 773.807 6.00889 773.684 6.66516 773.722L27.1234 774.981C28.3413 775.055 29.5245 775.416 30.5767 776.034C31.6288 776.651 32.5203 777.509 33.1785 778.536C33.8367 779.564 34.2431 780.732 34.3646 781.946C34.4861 783.16 34.3193 784.386 33.8777 785.523C33.4967 786.502 32.9209 787.394 32.185 788.144C31.4492 788.894 30.5687 789.486 29.597 789.886C28.6253 790.285 27.5826 790.483 26.5321 790.468C25.4816 790.452 24.4452 790.223 23.4857 789.795V789.795Z" fill="url(#paint54_linear)"/>
|
||||||
|
<path d="M7.71429 763.714H28.2857V784.286H7.71429V763.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 780.398H17.9932V781.683H10.2789V780.398Z" fill="white"/>
|
||||||
|
<path d="M10.2857 770.685V766.255H12V770.64C12 771.902 12.6308 772.553 13.6731 772.553C14.7154 772.553 15.3428 771.922 15.3428 770.695V766.255H17.0571V770.626C17.0571 772.975 15.7406 774.127 13.6594 774.127C11.5783 774.127 10.2857 772.965 10.2857 770.685V770.685Z" fill="white"/>
|
||||||
|
<path d="M18.6034 766.255H21.7714C23.6194 766.255 24.7371 767.352 24.7371 768.936V768.957C24.7371 770.75 23.3417 771.679 21.6034 771.679H20.3074V774.003H18.5931L18.6034 766.255ZM21.6583 770.163C22.512 770.163 23.0091 769.653 23.0091 768.991V768.967C23.0091 768.206 22.4777 767.794 21.6377 767.794H20.3074V770.163H21.6583Z" fill="white"/>
|
||||||
|
<path d="M21.6686 793.543L1.54285 795.809V806.157L6.5897 822.957L31.896 816.542L21.6686 793.543Z" fill="url(#paint55_linear)"/>
|
||||||
|
<path d="M32.5509 803.986L24.6206 800.187L13.4949 820.217L16.5189 824.318L24.2263 826.457L31.6457 821.777L34.4572 812.966L32.5509 803.986Z" fill="url(#paint56_linear)"/>
|
||||||
|
<path d="M7.71429 799.714H28.2857V820.286H7.71429V799.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 816.398H17.9932V817.683H10.2789V816.398Z" fill="white"/>
|
||||||
|
<path d="M10.2857 802.258H13.3028C15.7337 802.258 17.4171 803.928 17.4171 806.105V806.129C17.4171 808.306 15.7371 809.997 13.3028 809.997H10.2857V802.258ZM12 803.794V808.457H13.3166C13.6257 808.475 13.9353 808.428 14.225 808.319C14.5147 808.209 14.7782 808.04 14.9981 807.822C15.2181 807.604 15.3896 807.342 15.5016 807.053C15.6136 806.765 15.6635 806.456 15.648 806.146V806.126C15.6637 805.815 15.6142 805.505 15.5026 805.215C15.391 804.925 15.2199 804.661 15.0003 804.441C14.7806 804.221 14.5173 804.05 14.2273 803.938C13.9373 803.826 13.6271 803.776 13.3166 803.791L12 803.794Z" fill="white"/>
|
||||||
|
<path d="M18.2229 806.184V806.163C18.2134 805.629 18.3123 805.099 18.5136 804.604C18.715 804.109 19.0146 803.66 19.3943 803.284C19.7741 802.908 20.2261 802.613 20.723 802.417C21.22 802.22 21.7516 802.127 22.2857 802.142C22.8521 802.114 23.4181 802.206 23.9464 802.412C24.4747 802.618 24.9533 802.934 25.3509 803.338L24.2606 804.597C23.7387 804.054 23.0244 803.739 22.272 803.719C20.9623 803.719 20.0194 804.806 20.0194 806.139V806.163C20.0194 807.497 20.9383 808.608 22.272 808.608C23.1634 808.608 23.7051 808.265 24.3291 807.696L25.3989 808.8C25.0056 809.255 24.5143 809.614 23.9621 809.852C23.4098 810.089 22.8109 810.199 22.2103 810.171C21.6843 810.18 21.1621 810.082 20.6745 809.885C20.187 809.687 19.7441 809.394 19.3722 809.022C19.0002 808.65 18.7068 808.207 18.5094 807.72C18.312 807.232 18.2145 806.71 18.2229 806.184" fill="white"/>
|
||||||
|
<path d="M5.2937 860.174L1.54285 847.245L24.8434 853.67L22.3577 862.457L5.2937 860.174Z" fill="url(#paint57_linear)"/>
|
||||||
|
<path d="M12.5829 829.584L21.3463 832.094L31.248 829.543L33.2092 843.12L10.9269 835.714L12.5829 829.584Z" fill="url(#paint58_linear)"/>
|
||||||
|
<path d="M34.4571 851.225L33.2091 843.12L13.0491 836.421L1.58399 838.879L1.54285 847.245L24.8434 853.67L34.4571 851.225Z" fill="url(#paint59_linear)"/>
|
||||||
|
<path d="M7.71429 835.714H28.2857V856.286H7.71429V835.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 852.398H17.9932V853.683H10.2789V852.398Z" fill="white"/>
|
||||||
|
<path d="M10.2857 838.258H13.3028C15.7337 838.258 17.4171 839.928 17.4171 842.105V842.129C17.4171 844.306 15.7371 845.997 13.3028 845.997H10.2857V838.258ZM12 839.794V844.457H13.3166C13.6257 844.475 13.9353 844.428 14.225 844.319C14.5147 844.209 14.7782 844.04 14.9981 843.822C15.2181 843.604 15.3896 843.342 15.5016 843.053C15.6136 842.765 15.6635 842.456 15.648 842.146V842.126C15.6637 841.815 15.6142 841.505 15.5026 841.215C15.391 840.925 15.2199 840.661 15.0003 840.441C14.7806 840.221 14.5173 840.05 14.2273 839.938C13.9373 839.826 13.6271 839.776 13.3166 839.791L12 839.794Z" fill="white"/>
|
||||||
|
<path d="M18.0034 838.262H19.8377L21.8743 841.536L23.9074 838.265H25.7417V846.003H24.048L24.0514 840.95L21.8743 844.255H21.8263L19.6731 840.984V846H18.0034V838.262Z" fill="white"/>
|
||||||
|
<path d="M23.9691 889.172L1.54285 884.688L8.16685 898.416L24.7097 895.533L23.9691 889.172Z" fill="url(#paint60_linear)"/>
|
||||||
|
<path d="M34.4571 880.231L25.3714 870.864L1.54285 875.887V884.688L30.9806 890.571L34.4571 880.231Z" fill="url(#paint61_linear)"/>
|
||||||
|
<path d="M34.4572 880.231L20.1703 865.502L8.97601 868.889L12.6412 880.039L30.9806 890.571L34.4572 880.231Z" fill="url(#paint62_linear)"/>
|
||||||
|
<path d="M31.8892 874.704L30.5932 865.502H25.6834H20.1703L8.97601 868.889L12.6412 880.039L31.8892 874.704Z" fill="url(#paint63_linear)"/>
|
||||||
|
<path d="M7.71429 871.714H28.2857V892.286H7.71429V871.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 888.398H17.9932V889.683H10.2789V888.398Z" fill="white"/>
|
||||||
|
<path d="M10.2857 874.258H13.3028C15.7337 874.258 17.4171 875.928 17.4171 878.105V878.129C17.4171 880.306 15.7371 881.997 13.3028 881.997H10.2857V874.258ZM12 875.794V880.457H13.3166C13.6257 880.475 13.9353 880.428 14.225 880.319C14.5147 880.209 14.7782 880.04 14.9981 879.822C15.2181 879.604 15.3896 879.342 15.5016 879.053C15.6136 878.765 15.6635 878.456 15.648 878.146V878.126C15.6637 877.815 15.6142 877.505 15.5026 877.215C15.391 876.925 15.2199 876.661 15.0003 876.441C14.7806 876.221 14.5173 876.05 14.2273 875.938C13.9373 875.826 13.6271 875.776 13.3166 875.791L12 875.794Z" fill="white"/>
|
||||||
|
<path d="M18.6583 874.258H21.8297C23.6811 874.258 24.7989 875.355 24.7989 876.943V876.963C24.7989 878.76 23.4034 879.706 21.6617 879.706H20.3657V882.034H18.6514L18.6583 874.258ZM21.7166 878.174C22.5703 878.174 23.088 877.663 23.088 876.998V876.974C23.088 876.209 22.5566 875.801 21.7166 875.801H20.3657V878.174H21.7166Z" fill="white"/>
|
||||||
|
<path d="M33.192 909.079L22.1143 901.543L1.54285 916.152L6.75428 934.457L29.2388 929.897L33.192 909.079Z" fill="url(#paint64_linear)"/>
|
||||||
|
<path d="M33.192 909.079L22.1143 901.543L19.3885 908.935V924.003H34.4571L33.192 909.079Z" fill="url(#paint65_linear)"/>
|
||||||
|
<path d="M7.71429 907.714H28.2857V928.286H7.71429V907.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 924.398H17.9932V925.683H10.2789V924.398Z" fill="white"/>
|
||||||
|
<path d="M10.2857 910.258H13.3028C15.7337 910.258 17.4171 911.928 17.4171 914.105V914.129C17.4171 916.306 15.7371 917.997 13.3028 917.997H10.2857V910.258ZM12 911.794V916.457H13.3166C13.6257 916.475 13.9353 916.428 14.225 916.319C14.5147 916.209 14.7782 916.04 14.9981 915.822C15.2181 915.604 15.3896 915.342 15.5016 915.053C15.6136 914.765 15.6635 914.456 15.648 914.146V914.126C15.6637 913.815 15.6142 913.505 15.5026 913.215C15.391 912.925 15.2199 912.661 15.0003 912.441C14.7806 912.221 14.5173 912.05 14.2273 911.938C13.9373 911.826 13.6271 911.776 13.3166 911.791L12 911.794Z" fill="white"/>
|
||||||
|
<path d="M20.6023 911.818H18.2469V910.248H24.6617V911.822L22.3063 911.818V917.993H20.6023V911.818Z" fill="white"/>
|
||||||
|
<path d="M34.4572 482.307L11.3829 469.502L26.832 492.456L30.0069 490.375L34.4572 482.307Z" fill="url(#paint66_linear)"/>
|
||||||
|
<path d="M25.2514 477.086L22.3611 470.019L15.9669 476.314L18.5829 499.155L24.7783 502.416L34.4571 496.762L25.2514 477.086Z" fill="url(#paint67_linear)"/>
|
||||||
|
<path d="M11.3828 469.502L1.54285 476.129L5.20113 498.734L14.6366 502.361L26.832 492.456L11.3828 469.502Z" fill="url(#paint68_linear)"/>
|
||||||
|
<path d="M7.71429 475.714H28.2857V496.286H7.71429V475.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 492.398H17.9932V493.683H10.2789V492.398Z" fill="white"/>
|
||||||
|
<path d="M10.2857 478.258H13.8171C14.2259 478.233 14.6356 478.29 15.0224 478.424C15.4091 478.559 15.7651 478.77 16.0697 479.043C16.2932 479.279 16.4673 479.557 16.5815 479.861C16.6958 480.164 16.7479 480.488 16.7348 480.813V480.833C16.7604 481.36 16.6117 481.88 16.3117 482.314C16.0118 482.748 15.5773 483.071 15.0754 483.233L16.968 485.997H14.976L13.32 483.521H12V485.997H10.2857V478.258ZM13.7143 482.03C14.544 482.03 15.0206 481.587 15.0206 480.936V480.915C15.0206 480.185 14.5131 479.808 13.6834 479.808H12V482.03H13.7143Z" fill="white"/>
|
||||||
|
<path d="M18.1475 478.258H21.1646C23.5955 478.258 25.2789 479.928 25.2789 482.105V482.129C25.2789 484.306 23.5989 485.997 21.1646 485.997H18.1475V478.258ZM19.8617 479.794V484.457H21.1783C21.4875 484.475 21.797 484.428 22.0868 484.319C22.3765 484.209 22.6399 484.04 22.8599 483.822C23.0798 483.604 23.2514 483.342 23.3634 483.053C23.4753 482.765 23.5252 482.456 23.5097 482.146V482.126C23.5255 481.815 23.4759 481.505 23.3644 481.215C23.2528 480.925 23.0817 480.661 22.862 480.441C22.6424 480.221 22.379 480.05 22.0891 479.938C21.7991 479.826 21.4888 479.776 21.1783 479.791L19.8617 479.794Z" fill="white"/>
|
||||||
|
<path d="M34.4572 446.307L11.3829 433.502L26.832 456.456L30.0069 454.375L34.4572 446.307Z" fill="url(#paint69_linear)"/>
|
||||||
|
<path d="M25.2514 441.086L22.3611 434.019L15.9669 440.314L18.5829 463.155L24.7783 466.416L34.4571 460.762L25.2514 441.086Z" fill="url(#paint70_linear)"/>
|
||||||
|
<path d="M11.3828 433.502L1.54285 440.129L5.20113 462.734L14.6366 466.361L26.832 456.456L11.3828 433.502Z" fill="url(#paint71_linear)"/>
|
||||||
|
<path d="M7.71429 439.714H28.2857V460.286H7.71429V439.714Z" fill="black"/>
|
||||||
|
<path d="M10.2789 456.398H17.9932V457.683H10.2789V456.398Z" fill="white"/>
|
||||||
|
<path d="M10.2857 442.258H13.8171C14.2259 442.233 14.6356 442.29 15.0224 442.424C15.4091 442.559 15.7651 442.77 16.0697 443.043C16.2932 443.279 16.4673 443.557 16.5815 443.861C16.6958 444.164 16.7479 444.488 16.7348 444.813V444.833C16.7604 445.36 16.6117 445.88 16.3117 446.314C16.0118 446.748 15.5773 447.071 15.0754 447.233L16.968 449.997H14.976L13.32 447.521H12V449.997H10.2857V442.258ZM13.7143 446.03C14.544 446.03 15.0206 445.587 15.0206 444.936V444.915C15.0206 444.185 14.5131 443.808 13.6834 443.808H12V446.03H13.7143Z" fill="white"/>
|
||||||
|
<path d="M18.1475 442.258H21.1646C23.5955 442.258 25.2789 443.928 25.2789 446.105V446.129C25.2789 448.306 23.5989 449.997 21.1646 449.997H18.1475V442.258ZM19.8617 443.794V448.457H21.1783C21.4875 448.475 21.797 448.428 22.0868 448.319C22.3765 448.209 22.6399 448.04 22.8599 447.822C23.0798 447.604 23.2514 447.342 23.3634 447.053C23.4753 446.765 23.5252 446.456 23.5097 446.146V446.126C23.5255 445.815 23.4759 445.505 23.3644 445.215C23.2528 444.925 23.0817 444.661 22.862 444.441C22.6424 444.221 22.379 444.05 22.0891 443.938C21.7991 443.826 21.4888 443.776 21.1783 443.791L19.8617 443.794Z" fill="white"/>
|
||||||
|
<path d="M10.9171 404.524L17.5214 414.814L6.80143 430.75L1.25 408.305L10.9171 404.524Z" fill="url(#paint72_linear)"/>
|
||||||
|
<path d="M10.295 404.764L11.3 397.25L25.7528 413.33L21.5414 417.494L24.7957 430.75H6.80139L10.295 404.764Z" fill="url(#paint73_linear)"/>
|
||||||
|
<path d="M18.1436 419.791H34.75L29.1986 397.25H11.3L18.1436 419.791Z" fill="url(#paint74_linear)"/>
|
||||||
|
<path d="M7.66284 403.663H28.3371V424.337H7.66284V403.663Z" fill="black"/>
|
||||||
|
<path d="M9.57715 406.343H13.5014C14.6022 406.343 15.4157 406.63 15.99 407.204C16.4686 407.683 16.7079 408.353 16.7079 409.166V409.214C16.7079 409.932 16.5164 410.458 16.1814 410.889C15.8464 411.32 15.4157 411.655 14.8414 411.846L16.9472 414.909H14.7457L12.975 412.277H11.3957V414.909H9.5293V406.343H9.57715ZM13.4057 410.506C13.8843 410.506 14.2193 410.411 14.4586 410.171C14.6979 409.932 14.8414 409.645 14.8414 409.31V409.262C14.8414 408.831 14.6979 408.544 14.4586 408.353C14.2193 408.161 13.8364 408.066 13.3579 408.066H11.4914V410.506H13.4057V410.506Z" fill="white"/>
|
||||||
|
<path d="M9.57715 420.461H17.33V421.753H9.57715V420.461Z" fill="white"/>
|
||||||
|
<path d="M22.1636 408.927H20.2493V407.204H22.1636V405.29H23.9343V407.204H25.8486V408.927H23.9343V410.841H22.1636V408.927Z" fill="white"/>
|
||||||
|
<path d="M19.005 413.808H17.0907V412.086H19.005V410.171H20.7757V412.086H22.69V413.808H20.7757V415.723H19.005V413.808Z" fill="white"/>
|
||||||
|
<path d="M3.59998 939.6V947.7V957.54V961.2H14.64L20.04 939.6H3.59998ZM12.54 957.6H7.19998V954H13.38L12.54 957.6ZM21.36 946.8L15.96 968.4H32.4V960.3V950.46V946.8H21.36ZM28.8 964.8H20.58L21.42 961.2H28.8V964.8Z" fill="url(#paint75_linear)"/>
|
||||||
|
<path d="M26.5 1006C25.0166 1006 23.5666 1005.56 22.3332 1004.74C21.0999 1003.91 20.1386 1002.74 19.5709 1001.37C19.0033 1000 18.8547 998.492 19.1441 997.037C19.4335 995.582 20.1478 994.246 21.1967 993.197C22.2456 992.148 23.582 991.434 25.0368 991.144C26.4917 990.855 27.9997 991.003 29.3701 991.571C30.7406 992.139 31.9119 993.1 32.736 994.333C33.5601 995.567 34 997.017 34 998.5V998.5C34 1000.49 33.2106 1002.39 31.8054 1003.8C30.4002 1005.21 28.4941 1006 26.506 1006H26.5Z" fill="url(#paint76_linear)"/>
|
||||||
|
<path d="M17 974H2V989H17V974Z" fill="black"/>
|
||||||
|
<path d="M34 974H19V989H34V974Z" fill="black"/>
|
||||||
|
<path d="M17 991H2V1006H17V991Z" fill="black"/>
|
||||||
|
<path d="M26.5 106C25.0166 106 23.5666 105.56 22.3332 104.736C21.0999 103.912 20.1386 102.741 19.5709 101.37C19.0033 99.9997 18.8547 98.4917 19.1441 97.0368C19.4335 95.582 20.1478 94.2456 21.1967 93.1967C22.2456 92.1478 23.582 91.4335 25.0368 91.1441C26.4917 90.8547 27.9997 91.0033 29.3701 91.5709C30.7406 92.1386 31.9119 93.0999 32.736 94.3332C33.5601 95.5666 34 97.0166 34 98.5C34 100.489 33.2098 102.397 31.8033 103.803C30.3968 105.21 28.4891 106 26.5 106Z" fill="url(#paint77_linear)"/>
|
||||||
|
<path d="M26.5 89C25.0166 89 23.5666 88.5601 22.3332 87.736C21.0999 86.9119 20.1386 85.7406 19.5709 84.3701C19.0033 82.9997 18.8547 81.4917 19.1441 80.0368C19.4335 78.582 20.1478 77.2456 21.1967 76.1967C22.2456 75.1478 23.582 74.4335 25.0368 74.1441C26.4917 73.8547 27.9997 74.0032 29.3701 74.5709C30.7406 75.1386 31.9119 76.0999 32.736 77.3332C33.5601 78.5666 34 80.0166 34 81.5V81.5C34 83.4881 33.2107 85.3948 31.8054 86.8012C30.4002 88.2075 28.4941 88.9984 26.506 89H26.5Z" fill="url(#paint78_linear)"/>
|
||||||
|
<path d="M17 74H2V89H17V74Z" fill="black"/>
|
||||||
|
<path d="M9.5 106C8.01664 106 6.56659 105.56 5.33323 104.736C4.09986 103.912 3.13856 102.741 2.57091 101.37C2.00325 99.9997 1.85472 98.4917 2.14411 97.0368C2.4335 95.582 3.14781 94.2456 4.1967 93.1967C5.2456 92.1478 6.58197 91.4335 8.03682 91.1441C9.49168 90.8547 10.9997 91.0033 12.3701 91.5709C13.7406 92.1386 14.9119 93.0999 15.736 94.3332C16.5601 95.5666 17 97.0166 17 98.5V98.5C17 100.489 16.2098 102.397 14.8033 103.803C13.3968 105.21 11.4891 106 9.5 106Z" fill="url(#paint79_linear)"/>
|
||||||
|
<path d="M17 2H2V17H17V2Z" fill="black"/>
|
||||||
|
<path d="M34 19H19V34H34V19ZM34 2H19V17H34V2ZM17 19H2V34H17V19Z" fill="url(#paint80_linear)"/>
|
||||||
|
<path d="M17 38H2V53H17V38Z" fill="black"/>
|
||||||
|
<path d="M34 55H19V70H34V55ZM34 38H19V53H34V38ZM17 55H2V70H17V55Z" fill="url(#paint81_linear)"/>
|
||||||
|
<g clip-path="url(#clip0)">
|
||||||
|
<path d="M22.5433 1009.36L23.1913 1022.75L6.52229 1029.34L1.36292 1016.02L22.5433 1009.36Z" fill="url(#paint82_linear)"/>
|
||||||
|
<path d="M34.294 1017.18L19.8985 1023.43L22.5433 1009.36L34.294 1017.18Z" fill="#21D789"/>
|
||||||
|
<path d="M19.0189 1020.25L34.294 1017.18V1031.63L23.8044 1035.55L19.5002 1031.67L19.0189 1020.25Z" fill="url(#paint83_linear)"/>
|
||||||
|
<path d="M12.6279 1016.01L23.1913 1017.66L34.294 1031.63L23.6246 1035.76L19.5002 1031.67L12.6279 1016.01Z" fill="url(#paint84_linear)"/>
|
||||||
|
<path d="M8.13821 1009.36L23.1913 1017.66L18.0011 1042.29H9.25709L1.68011 1034.46L8.13821 1009.36Z" fill="url(#paint85_linear)"/>
|
||||||
|
<path d="M28.3767 1015.79H7.7948V1036.37H28.3767V1015.79Z" fill="black"/>
|
||||||
|
<path d="M18.0858 1032.51H10.3676V1033.8H18.0858V1032.51Z" fill="white"/>
|
||||||
|
<path d="M10.375 1018.36H13.3811C15.8035 1018.36 17.4772 1020.03 17.4772 1022.2V1022.22C17.4772 1024.39 15.8035 1026.07 13.3811 1026.07H10.375V1018.36ZM12.0707 1019.9V1024.54H13.3811C14.7685 1024.54 15.7044 1023.61 15.7044 1022.24V1022.22C15.7202 1021.91 15.6709 1021.6 15.5597 1021.31C15.4485 1021.02 15.2779 1020.76 15.0589 1020.54C14.84 1020.32 14.5775 1020.15 14.2885 1020.04C13.9995 1019.93 13.6903 1019.88 13.3811 1019.9H12.0707Z" fill="white"/>
|
||||||
|
<path d="M18.0884 1024.99L19.0989 1023.78C19.7974 1024.35 20.5307 1024.72 21.4171 1024.72C22.1175 1024.72 22.5388 1024.44 22.5388 1023.99V1023.96C22.5388 1023.53 22.2725 1023.31 20.9739 1022.98C19.409 1022.58 18.3985 1022.14 18.3985 1020.6V1020.58C18.3985 1019.17 19.5311 1018.24 21.118 1018.24C22.1656 1018.22 23.1863 1018.57 24.0035 1019.23L23.1152 1020.51C22.4276 1020.04 21.7509 1019.75 21.0961 1019.75C20.4413 1019.75 20.0966 1020.05 20.0966 1020.42V1020.45C20.0966 1020.96 20.4304 1021.12 21.7728 1021.47C23.3486 1021.88 24.2369 1022.44 24.2369 1023.8V1023.82C24.2369 1025.36 23.0605 1026.23 21.3843 1026.23C20.1704 1026.23 18.9973 1025.79 18.0884 1024.99Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="paint0_linear" x1="15.7543" y1="139.2" x2="34.3611" y2="139.2" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.19" stop-color="#07C3F2"/>
|
||||||
|
<stop offset="0.9" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint1_linear" x1="111.279" y1="508.982" x2="1000.6" y2="508.982" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.19" stop-color="#07C3F2"/>
|
||||||
|
<stop offset="0.9" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint2_linear" x1="23.1154" y1="143.503" x2="6.80913" y2="120.744" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.09" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.48" stop-color="#07C3F2"/>
|
||||||
|
<stop offset="0.9" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint3_linear" x1="13.3749" y1="151.978" x2="22.8035" y2="151.978" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FF318C"/>
|
||||||
|
<stop offset="0.15" stop-color="#FB348C"/>
|
||||||
|
<stop offset="0.28" stop-color="#F03C8C"/>
|
||||||
|
<stop offset="0.42" stop-color="#DE4A8C"/>
|
||||||
|
<stop offset="0.54" stop-color="#C45D8B"/>
|
||||||
|
<stop offset="0.67" stop-color="#A2778B"/>
|
||||||
|
<stop offset="0.79" stop-color="#79958A"/>
|
||||||
|
<stop offset="0.91" stop-color="#49B98A"/>
|
||||||
|
<stop offset="1" stop-color="#21D789"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint4_linear" x1="9.59656" y1="149.719" x2="4.75542" y2="182.201" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.09" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.9" stop-color="#009AE5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint5_linear" x1="2971.02" y1="558.354" x2="16.2324" y2="3836.07" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.09" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.9" stop-color="#009AE5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint6_linear" x1="1215.42" y1="1246.04" x2="1835.75" y2="1366.07" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.09" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.9" stop-color="#009AE5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint7_linear" x1="31.6766" y1="193.927" x2="32.6365" y2="202.306" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.16" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.54" stop-color="#419FBC"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint8_linear" x1="22.92" y1="190.327" x2="25.3543" y2="184.101" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.95" stop-color="#21D789"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint9_linear" x1="9.46974" y1="198.161" x2="11.8355" y2="208.461" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.02" stop-color="#685CFB"/>
|
||||||
|
<stop offset="0.28" stop-color="#4A91CA"/>
|
||||||
|
<stop offset="0.51" stop-color="#34B7A7"/>
|
||||||
|
<stop offset="0.69" stop-color="#26CE91"/>
|
||||||
|
<stop offset="0.8" stop-color="#21D789"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint10_linear" x1="3.59313" y1="198.003" x2="32.4343" y2="213.929" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.08" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.89" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint11_linear" x1="3.76799" y1="194.163" x2="32.7566" y2="194.163" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.31" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.49" stop-color="#59A3B2"/>
|
||||||
|
<stop offset="0.77" stop-color="#B74AF7"/>
|
||||||
|
<stop offset="1" stop-color="#FF45ED"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint12_linear" x1="33.9531" y1="236.29" x2="21.0994" y2="247.169" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#087CFA"/>
|
||||||
|
<stop offset="0.02" stop-color="#0D7BFA"/>
|
||||||
|
<stop offset="0.37" stop-color="#5566F9"/>
|
||||||
|
<stop offset="0.66" stop-color="#8A57F8"/>
|
||||||
|
<stop offset="0.88" stop-color="#AB4EF7"/>
|
||||||
|
<stop offset="1" stop-color="#B74AF7"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint13_linear" x1="871.999" y1="1126.35" x2="1481.58" y2="520.096" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#087CFA"/>
|
||||||
|
<stop offset="0.02" stop-color="#0D7BFA"/>
|
||||||
|
<stop offset="0.37" stop-color="#5566F9"/>
|
||||||
|
<stop offset="0.66" stop-color="#8A57F8"/>
|
||||||
|
<stop offset="0.88" stop-color="#AB4EF7"/>
|
||||||
|
<stop offset="1" stop-color="#B74AF7"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint14_linear" x1="6.11656" y1="246.295" x2="29.8594" y2="231.038" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#087CFA"/>
|
||||||
|
<stop offset="0.1" stop-color="#1598D3"/>
|
||||||
|
<stop offset="0.22" stop-color="#23B6AA"/>
|
||||||
|
<stop offset="0.35" stop-color="#2DCC8B"/>
|
||||||
|
<stop offset="0.46" stop-color="#35DD74"/>
|
||||||
|
<stop offset="0.57" stop-color="#39E767"/>
|
||||||
|
<stop offset="0.67" stop-color="#3BEA62"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint15_linear" x1="3.82626" y1="272.301" x2="20.208" y2="271.467" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.09" stop-color="#FC801D"/>
|
||||||
|
<stop offset="0.23" stop-color="#B07F61"/>
|
||||||
|
<stop offset="0.41" stop-color="#577DB3"/>
|
||||||
|
<stop offset="0.53" stop-color="#1E7CE6"/>
|
||||||
|
<stop offset="0.59" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint16_linear" x1="30.5314" y1="270.555" x2="25.0697" y2="254.235" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FE2857"/>
|
||||||
|
<stop offset="0.08" stop-color="#CB3979"/>
|
||||||
|
<stop offset="0.16" stop-color="#9E4997"/>
|
||||||
|
<stop offset="0.25" stop-color="#7557B2"/>
|
||||||
|
<stop offset="0.34" stop-color="#5362C8"/>
|
||||||
|
<stop offset="0.44" stop-color="#386CDA"/>
|
||||||
|
<stop offset="0.54" stop-color="#2373E8"/>
|
||||||
|
<stop offset="0.66" stop-color="#1478F2"/>
|
||||||
|
<stop offset="0.79" stop-color="#0B7BF8"/>
|
||||||
|
<stop offset="1" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint17_linear" x1="6.4183" y1="261.123" x2="27.0172" y2="295.197" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FE2857"/>
|
||||||
|
<stop offset="0.08" stop-color="#FE295F"/>
|
||||||
|
<stop offset="0.21" stop-color="#FF2D76"/>
|
||||||
|
<stop offset="0.3" stop-color="#FF318C"/>
|
||||||
|
<stop offset="0.38" stop-color="#EA3896"/>
|
||||||
|
<stop offset="0.55" stop-color="#B248AE"/>
|
||||||
|
<stop offset="0.79" stop-color="#5A63D6"/>
|
||||||
|
<stop offset="1" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint18_linear" x1="8.84571" y1="299.52" x2="11.64" y2="293.935" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#AF1DF5"/>
|
||||||
|
<stop offset="0.21" stop-color="#BC20E4"/>
|
||||||
|
<stop offset="0.63" stop-color="#DD29B8"/>
|
||||||
|
<stop offset="1" stop-color="#FF318C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint19_linear" x1="0.534846" y1="311.05" x2="12.3257" y2="295.474" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.02" stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.42" stop-color="#B74AF7"/>
|
||||||
|
<stop offset="0.75" stop-color="#FF318C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint20_linear" x1="30.6171" y1="310.557" x2="22.4434" y2="291.165" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#293896"/>
|
||||||
|
<stop offset="0.08" stop-color="#3B3AA2"/>
|
||||||
|
<stop offset="0.29" stop-color="#6740C0"/>
|
||||||
|
<stop offset="0.49" stop-color="#8A44D8"/>
|
||||||
|
<stop offset="0.68" stop-color="#A347E9"/>
|
||||||
|
<stop offset="0.86" stop-color="#B249F3"/>
|
||||||
|
<stop offset="1" stop-color="#B74AF7"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint21_linear" x1="25.6046" y1="320.088" x2="20.0914" y2="312.01" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.02" stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.78" stop-color="#B74AF7"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint22_linear" x1="13.296" y1="338.259" x2="32.8835" y2="338.259" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#21D789"/>
|
||||||
|
<stop offset="1" stop-color="#07C3F2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint23_linear" x1="-10.0046" y1="353.321" x2="30.3291" y2="323.547" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.01" stop-color="#FCF84A"/>
|
||||||
|
<stop offset="0.11" stop-color="#A7EB62"/>
|
||||||
|
<stop offset="0.21" stop-color="#5FE077"/>
|
||||||
|
<stop offset="0.27" stop-color="#32DA84"/>
|
||||||
|
<stop offset="0.31" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.58" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.6" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.69" stop-color="#20D68C"/>
|
||||||
|
<stop offset="0.76" stop-color="#1ED497"/>
|
||||||
|
<stop offset="0.83" stop-color="#19D1A9"/>
|
||||||
|
<stop offset="0.9" stop-color="#13CCC2"/>
|
||||||
|
<stop offset="0.97" stop-color="#0BC6E1"/>
|
||||||
|
<stop offset="1" stop-color="#07C3F2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint24_linear" x1="5.93142" y1="362.054" x2="12.6583" y2="340.944" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#21D789"/>
|
||||||
|
<stop offset="0.16" stop-color="#24D888"/>
|
||||||
|
<stop offset="0.3" stop-color="#2FD985"/>
|
||||||
|
<stop offset="0.43" stop-color="#41DC80"/>
|
||||||
|
<stop offset="0.55" stop-color="#5AE079"/>
|
||||||
|
<stop offset="0.67" stop-color="#7AE46F"/>
|
||||||
|
<stop offset="0.79" stop-color="#A1EA64"/>
|
||||||
|
<stop offset="0.9" stop-color="#CFF157"/>
|
||||||
|
<stop offset="1" stop-color="#FCF84A"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint25_linear" x1="14.8389" y1="343.701" x2="29.4754" y2="324.021" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#21D789"/>
|
||||||
|
<stop offset="0.09" stop-color="#23D986"/>
|
||||||
|
<stop offset="0.17" stop-color="#2ADE7B"/>
|
||||||
|
<stop offset="0.25" stop-color="#36E669"/>
|
||||||
|
<stop offset="0.27" stop-color="#3BEA62"/>
|
||||||
|
<stop offset="0.35" stop-color="#47EB61"/>
|
||||||
|
<stop offset="0.49" stop-color="#67ED5D"/>
|
||||||
|
<stop offset="0.69" stop-color="#9AF156"/>
|
||||||
|
<stop offset="0.92" stop-color="#E0F64D"/>
|
||||||
|
<stop offset="1" stop-color="#FCF84A"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint26_linear" x1="37.2274" y1="346.207" x2="7.73144" y2="345.933" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.39" stop-color="#FCF84A"/>
|
||||||
|
<stop offset="0.46" stop-color="#ECF74C"/>
|
||||||
|
<stop offset="0.61" stop-color="#C1F451"/>
|
||||||
|
<stop offset="0.82" stop-color="#7EEF5A"/>
|
||||||
|
<stop offset="1" stop-color="#3BEA62"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint27_linear" x1="17.7394" y1="394.478" x2="31.9337" y2="373.793" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.02" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.4" stop-color="#DD1265"/>
|
||||||
|
<stop offset="1" stop-color="#FDB60D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint28_linear" x1="2.40342" y1="386.616" x2="24.5006" y2="365.832" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.02" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.66" stop-color="#DD1265"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint29_linear" x1="23.9246" y1="360.717" x2="24.1508" y2="373.776" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.06" stop-color="#DF1961"/>
|
||||||
|
<stop offset="0.7" stop-color="#F46330"/>
|
||||||
|
<stop offset="1" stop-color="#FC801D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint30_linear" x1="22.3028" y1="524.582" x2="18.1405" y2="513.662" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FE2857"/>
|
||||||
|
<stop offset="0.06" stop-color="#FE3052"/>
|
||||||
|
<stop offset="0.33" stop-color="#FD533B"/>
|
||||||
|
<stop offset="0.58" stop-color="#FC6C2A"/>
|
||||||
|
<stop offset="0.81" stop-color="#FC7B20"/>
|
||||||
|
<stop offset="1" stop-color="#FC801D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint31_linear" x1="14.376" y1="508.954" x2="20.8012" y2="514.845" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6B57FF"/>
|
||||||
|
<stop offset="1" stop-color="#FE2857"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint32_linear" x1="1.34401" y1="510.816" x2="22.5017" y2="537.71" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.3" stop-color="#FE2857"/>
|
||||||
|
<stop offset="0.63" stop-color="#FE2857"/>
|
||||||
|
<stop offset="0.64" stop-color="#FE3052"/>
|
||||||
|
<stop offset="0.7" stop-color="#FD533B"/>
|
||||||
|
<stop offset="0.76" stop-color="#FC6C2A"/>
|
||||||
|
<stop offset="0.81" stop-color="#FC7B20"/>
|
||||||
|
<stop offset="0.85" stop-color="#FC801D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint33_linear" x1="13.3303" y1="542.229" x2="21.8468" y2="572.894" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.28" stop-color="#07C3F2"/>
|
||||||
|
<stop offset="0.94" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint34_linear" x1="15.9875" y1="546.12" x2="30.3978" y2="567.25" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.14" stop-color="#FCF84A"/>
|
||||||
|
<stop offset="0.37" stop-color="#07C3F2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint35_linear" x1="609.841" y1="808.219" x2="689.982" y2="990.095" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.28" stop-color="#07C3F2"/>
|
||||||
|
<stop offset="0.94" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint36_linear" x1="6.15385" y1="581.615" x2="18.6526" y2="593.1" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#B42BF6"/>
|
||||||
|
<stop offset="0.556" stop-color="#6C70B8"/>
|
||||||
|
<stop offset="1" stop-color="#15DD69"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint37_linear" x1="11.6054" y1="621.687" x2="31.5803" y2="625.668" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.24211" stop-color="#3BEA62"/>
|
||||||
|
<stop offset="0.85664" stop-color="#FCF84A"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint38_linear" x1="9.46646" y1="615.821" x2="28.6455" y2="644.992" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.0176" stop-color="#3BEA62"/>
|
||||||
|
<stop offset="0.78604" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint39_linear" x1="9.1999" y1="632.052" x2="33.4914" y2="640.825" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.12092" stop-color="#1FAEB5"/>
|
||||||
|
<stop offset="0.97461" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint40_linear" x1="2.21211" y1="633.419" x2="33.5885" y2="619.513" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.12092" stop-color="#1FAEB5"/>
|
||||||
|
<stop offset="0.8556" stop-color="#FCF84A"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint41_linear" x1="14.3075" y1="669.59" x2="11.4926" y2="661.258" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#07C3F2"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint42_linear" x1="13.3474" y1="672.415" x2="23.4343" y2="667.351" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#07C3F2"/>
|
||||||
|
<stop offset="1" stop-color="#009AE5"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint43_linear" x1="26.136" y1="667.461" x2="9.44571" y2="654.861" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#009AE5"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint44_linear" x1="8.84231" y1="676.125" x2="20.2115" y2="652.971" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.3" stop-color="#07C3F2"/>
|
||||||
|
<stop offset="0.6" stop-color="#3BEA62"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint45_linear" x1="28.44" y1="707.722" x2="8.81139" y2="711.422" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#07C3F2"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint46_linear" x1="2675.43" y1="1498.53" x2="695.635" y2="3457.67" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#07C3F2"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint47_linear" x1="30.3977" y1="712.485" x2="8.39316" y2="692.253" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.15" stop-color="#07C3F2"/>
|
||||||
|
<stop offset="0.53" stop-color="#6B57FF"/>
|
||||||
|
<stop offset="1" stop-color="#FF318C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint48_linear" x1="15.974" y1="722.573" x2="19.2105" y2="753.252" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FCF84A"/>
|
||||||
|
<stop offset="0.32" stop-color="#ABE682"/>
|
||||||
|
<stop offset="0.79" stop-color="#36CDD2"/>
|
||||||
|
<stop offset="1" stop-color="#07C3F2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint49_linear" x1="4.10656" y1="723.044" x2="33.1322" y2="736.813" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#3BEA62"/>
|
||||||
|
<stop offset="1" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint50_linear" x1="6.75617" y1="753.02" x2="35.9803" y2="732.564" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#009AE5"/>
|
||||||
|
<stop offset="0.18" stop-color="#0490DD"/>
|
||||||
|
<stop offset="0.49" stop-color="#1073C6"/>
|
||||||
|
<stop offset="0.89" stop-color="#2346A1"/>
|
||||||
|
<stop offset="1" stop-color="#293896"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint51_linear" x1="33.2332" y1="762.679" x2="10.0183" y2="765.771" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FC801D"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint52_linear" x1="17.8045" y1="775.193" x2="19.7863" y2="766.814" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#07C3F2"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint53_linear" x1="29.3074" y1="762.867" x2="5.22514" y2="778.231" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FC801D"/>
|
||||||
|
<stop offset="0.92" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint54_linear" x1="3004.51" y1="2176.62" x2="1167.63" y2="909.273" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#07C3F2"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint55_linear" x1="18.9634" y1="819.703" x2="12.6206" y2="796.094" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.05" stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.12" stop-color="#7556FE"/>
|
||||||
|
<stop offset="0.24" stop-color="#8F53FB"/>
|
||||||
|
<stop offset="0.4" stop-color="#BA4DF5"/>
|
||||||
|
<stop offset="0.58" stop-color="#F446EE"/>
|
||||||
|
<stop offset="0.61" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.69" stop-color="#FF3BBE"/>
|
||||||
|
<stop offset="0.77" stop-color="#FF318C"/>
|
||||||
|
<stop offset="0.99" stop-color="#FC801D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint56_linear" x1="33.432" y1="816.487" x2="21.0514" y2="812.41" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.03" stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.39" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.49" stop-color="#FF4DD1"/>
|
||||||
|
<stop offset="0.7" stop-color="#FE6189"/>
|
||||||
|
<stop offset="1" stop-color="#FC801D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint57_linear" x1="10.3611" y1="838.611" x2="15.9908" y2="862.166" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.13" stop-color="#9A51F9"/>
|
||||||
|
<stop offset="0.27" stop-color="#C64CF4"/>
|
||||||
|
<stop offset="0.39" stop-color="#E548F0"/>
|
||||||
|
<stop offset="0.5" stop-color="#F846EE"/>
|
||||||
|
<stop offset="0.57" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.63" stop-color="#FF57C9"/>
|
||||||
|
<stop offset="0.81" stop-color="#FE8A65"/>
|
||||||
|
<stop offset="0.94" stop-color="#FDAA26"/>
|
||||||
|
<stop offset="1" stop-color="#FDB60D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint58_linear" x1="25.8789" y1="846.36" x2="20.2937" y2="830.355" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.14" stop-color="#8953FB"/>
|
||||||
|
<stop offset="0.44" stop-color="#D64AF2"/>
|
||||||
|
<stop offset="0.59" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.97" stop-color="#FDB60D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint59_linear" x1="13.9337" y1="854.746" x2="18.5691" y2="843.754" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.12" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.2" stop-color="#DF49F1"/>
|
||||||
|
<stop offset="0.3" stop-color="#BC4DF5"/>
|
||||||
|
<stop offset="0.42" stop-color="#9E51F9"/>
|
||||||
|
<stop offset="0.53" stop-color="#8854FC"/>
|
||||||
|
<stop offset="0.66" stop-color="#7855FD"/>
|
||||||
|
<stop offset="0.81" stop-color="#6E57FF"/>
|
||||||
|
<stop offset="1" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint60_linear" x1="4.85485" y1="882.346" x2="20.6606" y2="898.481" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.1" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.11" stop-color="#F846EE"/>
|
||||||
|
<stop offset="0.28" stop-color="#AC4FF7"/>
|
||||||
|
<stop offset="0.41" stop-color="#7D55FD"/>
|
||||||
|
<stop offset="0.47" stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.48" stop-color="#655DFE"/>
|
||||||
|
<stop offset="0.57" stop-color="#4482FA"/>
|
||||||
|
<stop offset="0.66" stop-color="#299EF6"/>
|
||||||
|
<stop offset="0.76" stop-color="#16B3F4"/>
|
||||||
|
<stop offset="0.85" stop-color="#0BBFF2"/>
|
||||||
|
<stop offset="0.94" stop-color="#07C3F2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint61_linear" x1="6.04113" y1="891.751" x2="17.5817" y2="882.134" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.04" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.05" stop-color="#FE45ED"/>
|
||||||
|
<stop offset="0.2" stop-color="#D14BF3"/>
|
||||||
|
<stop offset="0.36" stop-color="#AC4FF7"/>
|
||||||
|
<stop offset="0.51" stop-color="#9053FB"/>
|
||||||
|
<stop offset="0.67" stop-color="#7B55FD"/>
|
||||||
|
<stop offset="0.82" stop-color="#6F57FF"/>
|
||||||
|
<stop offset="0.97" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint62_linear" x1="20.2834" y1="873.459" x2="28.0423" y2="889.317" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.2" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.29" stop-color="#F646EE"/>
|
||||||
|
<stop offset="0.43" stop-color="#DD49F1"/>
|
||||||
|
<stop offset="0.61" stop-color="#B64EF6"/>
|
||||||
|
<stop offset="0.82" stop-color="#7F55FD"/>
|
||||||
|
<stop offset="0.89" stop-color="#6B57FF"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint63_linear" x1="10.8892" y1="874.107" x2="30.3806" y2="869.383" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.1" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.17" stop-color="#F64AED"/>
|
||||||
|
<stop offset="0.29" stop-color="#DD56EE"/>
|
||||||
|
<stop offset="0.44" stop-color="#B56AEE"/>
|
||||||
|
<stop offset="0.62" stop-color="#7E87F0"/>
|
||||||
|
<stop offset="0.81" stop-color="#38AAF1"/>
|
||||||
|
<stop offset="0.94" stop-color="#07C3F2"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint64_linear" x1="0.915418" y1="922.107" x2="33.0651" y2="913.814" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.12" stop-color="#6B57FF"/>
|
||||||
|
<stop offset="0.54" stop-color="#FF45ED"/>
|
||||||
|
<stop offset="0.85" stop-color="#DD1265"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint65_linear" x1="23.1325" y1="919.838" x2="33.3565" y2="905.822" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.19" stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.29" stop-color="#DE146A"/>
|
||||||
|
<stop offset="0.41" stop-color="#E21977"/>
|
||||||
|
<stop offset="0.53" stop-color="#E7218E"/>
|
||||||
|
<stop offset="0.66" stop-color="#EF2DAD"/>
|
||||||
|
<stop offset="0.79" stop-color="#F93CD5"/>
|
||||||
|
<stop offset="0.85" stop-color="#FF45ED"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint66_linear" x1="32.3417" y1="488.355" x2="6.9703" y2="473.798" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.48" stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.94" stop-color="#FDB60D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint67_linear" x1="17.256" y1="472.375" x2="27.312" y2="500.146" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.14" stop-color="#087CFA"/>
|
||||||
|
<stop offset="0.48" stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.96" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint68_linear" x1="9.72342" y1="473.232" x2="17.1497" y2="499.632" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.28" stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.97" stop-color="#FDB60D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint69_linear" x1="32.3417" y1="452.355" x2="6.9703" y2="437.798" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.48" stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.94" stop-color="#FDB60D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint70_linear" x1="17.256" y1="436.375" x2="27.312" y2="464.146" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.14" stop-color="#087CFA"/>
|
||||||
|
<stop offset="0.48" stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.96" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint71_linear" x1="9.72342" y1="437.232" x2="17.1497" y2="463.632" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.28" stop-color="#DD1265"/>
|
||||||
|
<stop offset="0.97" stop-color="#FDB60D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint72_linear" x1="5.10872" y1="404.975" x2="15.253" y2="430.634" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.22" stop-color="#C40B55"/>
|
||||||
|
<stop offset="0.683" stop-color="#E343E6"/>
|
||||||
|
<stop offset="0.925" stop-color="#F59252"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint73_linear" x1="26.2702" y1="432.325" x2="1.46244" y2="405.937" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.113" stop-color="#FFBD00"/>
|
||||||
|
<stop offset="0.586" stop-color="#E343E6"/>
|
||||||
|
<stop offset="0.817" stop-color="#EC841B"/>
|
||||||
|
<stop offset="0.935" stop-color="#FFBD00"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint74_linear" x1="13.4536" y1="396.326" x2="34.7308" y2="421.738" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.129" stop-color="#FFBD00"/>
|
||||||
|
<stop offset="0.64" stop-color="#E343E6"/>
|
||||||
|
<stop offset="0.909" stop-color="#C40B55"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint75_linear" x1="4.38748" y1="940.725" x2="20.25" y2="968.962" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#3AE65D"/>
|
||||||
|
<stop offset="0.563066" stop-color="#4F88B5"/>
|
||||||
|
<stop offset="0.988281" stop-color="#604AFB"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint76_linear" x1="16.972" y1="988.972" x2="32.506" y2="1004.51" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.26" stop-color="#F7C72A"/>
|
||||||
|
<stop offset="0.79" stop-color="#DD1265"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint77_linear" x1="13.138" y1="85.144" x2="31.708" y2="103.708" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#F7C72A"/>
|
||||||
|
<stop offset="0.43" stop-color="#D911A5"/>
|
||||||
|
<stop offset="1" stop-color="#9E00E9"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint78_linear" x1="21.8125" y1="75.4062" x2="30.7187" y2="87.5938" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.118129" stop-color="#F7C72A"/>
|
||||||
|
<stop offset="0.562199" stop-color="#F09B48"/>
|
||||||
|
<stop offset="0.89" stop-color="#E7636D"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint79_linear" x1="4.34375" y1="92.875" x2="15.125" y2="103.656" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#FF8364"/>
|
||||||
|
<stop offset="0.5" stop-color="#DF338E"/>
|
||||||
|
<stop offset="0.89" stop-color="#CC0DB4"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint80_linear" x1="8.52407" y1="8.52407" x2="31.8393" y2="31.8393" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.17" stop-color="#00AEEF"/>
|
||||||
|
<stop offset="0.54" stop-color="#7456FF"/>
|
||||||
|
<stop offset="0.85" stop-color="#B627F0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint81_linear" x1="8.52407" y1="44.5241" x2="31.8393" y2="67.8393" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.17" stop-color="#00AEEF"/>
|
||||||
|
<stop offset="0.54" stop-color="#7456FF"/>
|
||||||
|
<stop offset="0.85" stop-color="#B627F0"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint82_linear" x1="25.7859" y1="1019.35" x2="3.95762" y2="1019.35" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#21D789"/>
|
||||||
|
<stop offset="0.91691" stop-color="#FCF84A"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint83_linear" x1="26.6564" y1="1018.86" x2="26.6564" y2="1046.11" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop stop-color="#21D789"/>
|
||||||
|
<stop offset="1" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint84_linear" x1="44.7091" y1="1043.94" x2="12.2172" y2="1017.66" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.10491" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.96676" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="paint85_linear" x1="29.8346" y1="1006.82" x2="7.13729" y2="1032.54" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0.23484" stop-color="#21D789"/>
|
||||||
|
<stop offset="0.7396" stop-color="#087CFA"/>
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip0">
|
||||||
|
<rect width="33" height="34" fill="white" transform="translate(1.34998 1009)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="intellij-idea_svg__a" x1="-.391" x2="24.392" y1="7.671" y2="61.126" gradientUnits="userSpaceOnUse"><stop offset=".1" stop-color="#FC801D"/><stop offset=".59" stop-color="#FE2857"/></linearGradient><linearGradient id="intellij-idea_svg__b" x1="4.325" x2="62.921" y1="59.932" y2="1.336" gradientUnits="userSpaceOnUse"><stop offset=".21" stop-color="#FE2857"/><stop offset=".7" stop-color="#007EFF"/></linearGradient></defs><path fill="#FF8100" d="M16.45 6H4.191a4.125 4.125 0 0 0-4.124 4.19l.176 11.044a4.125 4.125 0 0 0 1.44 3.066l38.159 32.707c.747.64 1.7.993 2.684.993h11.35A4.125 4.125 0 0 0 58 53.875V42.872c0-1.19-.514-2.321-1.41-3.105L19.167 7.021A4.123 4.123 0 0 0 16.45 6Z"/><path fill="url(#intellij-idea_svg__a)" d="M14.988 6H4.125A4.125 4.125 0 0 0 0 10.125v12.566c0 .2.014.4.044.598l5.448 37.185A4.125 4.125 0 0 0 9.573 64h15.398a4.125 4.125 0 0 0 4.125-4.127L29.09 41.37c0-.426-.066-.849-.195-1.254l-9.98-31.245A4.126 4.126 0 0 0 14.988 6V6Z"/><path fill="url(#intellij-idea_svg__b)" d="M59.876 0H25.748a4.125 4.125 0 0 0-3.8 2.52L6.151 39.943a4.118 4.118 0 0 0-.325 1.638l.15 18.329A4.125 4.125 0 0 0 10.101 64h17.666c.806 0 1.593-.236 2.266-.678l32.11-21.109A4.123 4.123 0 0 0 64 38.766V4.125A4.125 4.125 0 0 0 59.876 0Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM17 29.383h2.98v-9.775H17v-2.616h8.843v2.616h-2.98v9.775h2.98V32H17v-2.616ZM27.643 29.298h2.154a2.38 2.38 0 0 0 1.163-.279c.34-.186.602-.448.788-.788.186-.34.279-.727.279-1.163V16.992h2.926v10.28c0 .9-.207 1.709-.622 2.427a4.45 4.45 0 0 1-1.715 1.688c-.728.408-1.546.611-2.454.611h-2.519v-2.7Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="phpstorm_svg__a" x1="56.329" x2="2.874" y1="-.391" y2="24.392" gradientUnits="userSpaceOnUse"><stop offset=".16" stop-color="#D249FC"/><stop offset=".55" stop-color="#FF2D90"/></linearGradient><linearGradient id="phpstorm_svg__b" x1="4.067" x2="62.664" y1="4.326" y2="62.923" gradientUnits="userSpaceOnUse"><stop offset=".3" stop-color="#FF2D90"/><stop offset=".7" stop-color="#7256FF"/></linearGradient></defs><path fill="#D249FC" d="M58 16.446V4.187A4.125 4.125 0 0 0 53.81.063L42.765.239A4.125 4.125 0 0 0 39.7 1.68L6.993 39.837c-.64.748-.993 1.7-.993 2.685v11.35a4.125 4.125 0 0 0 4.125 4.124h11.003c1.19 0 2.321-.514 3.104-1.409L56.98 19.162A4.124 4.124 0 0 0 58 16.446Z"/><path fill="url(#phpstorm_svg__a)" d="M58 14.988V4.125A4.125 4.125 0 0 0 53.875 0H41.309c-.2 0-.4.014-.598.044L3.527 5.492A4.125 4.125 0 0 0 0 9.573v15.398a4.125 4.125 0 0 0 4.126 4.125l18.505-.005c.425 0 .848-.066 1.253-.195l31.246-9.98A4.126 4.126 0 0 0 58 14.988Z"/><path fill="url(#phpstorm_svg__b)" d="M64 59.878V25.75a4.125 4.125 0 0 0-2.52-3.8L24.057 6.153a4.118 4.118 0 0 0-1.638-.325l-18.329.15A4.124 4.124 0 0 0 0 10.103v17.665c0 .806.236 1.593.678 2.267l21.109 32.109a4.124 4.124 0 0 0 3.447 1.859h34.641A4.125 4.125 0 0 0 64 59.878Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM16.993 16.992h6.442c1.057 0 1.984.193 2.78.579.798.386 1.412.929 1.845 1.63.432.7.648 1.51.648 2.432 0 .922-.22 1.759-.659 2.466-.44.708-1.063 1.256-1.87 1.646-.808.39-1.751.584-2.83.584h-3.43V32h-2.927V16.993Zm7.54 6.63c.369-.182.652-.443.852-.782.2-.34.3-.738.3-1.195s-.1-.842-.3-1.174c-.2-.333-.484-.59-.852-.772-.368-.182-.798-.273-1.291-.273h-3.323v4.47h3.323c.493 0 .923-.091 1.291-.273ZM32.572 31.664c-.84-.393-1.497-.943-1.972-1.65-.475-.708-.72-1.522-.735-2.444h2.937c0 .436.114.818.343 1.147.229.329.547.586.954.772.407.185.875.278 1.404.278.53 0 .952-.083 1.335-.251.382-.168.678-.402.89-.702s.315-.643.315-1.029c0-.479-.145-.875-.434-1.19-.29-.314-.688-.532-1.195-.654l-2.647-.59c-.715-.156-1.337-.426-1.866-.809a4.014 4.014 0 0 1-1.232-1.42c-.293-.564-.44-1.2-.44-1.908 0-.857.222-1.627.665-2.31.443-.682 1.061-1.215 1.854-1.597.794-.382 1.694-.573 2.702-.573s1.933.186 2.733.558c.8.372 1.424.888 1.87 1.549.447.66.674 1.42.681 2.277h-2.926c0-.364-.096-.691-.29-.98a1.903 1.903 0 0 0-.82-.676 2.915 2.915 0 0 0-1.216-.24c-.458 0-.86.076-1.206.23a1.902 1.902 0 0 0-.81.643c-.193.275-.289.595-.289.96 0 .414.134.757.402 1.028.268.272.634.464 1.099.579l2.551.557c.736.15 1.392.435 1.967.852a4.45 4.45 0 0 1 1.345 1.544c.321.611.482 1.278.482 1.999 0 .886-.234 1.681-.701 2.385-.469.703-1.124 1.255-1.967 1.656-.843.4-1.808.6-2.895.6-1.086 0-2.05-.197-2.888-.59Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none" viewBox="0 0 48 48"><defs><linearGradient id="code-with-me_svg__a" x1="3.679" x2="44.503" y1="5.32" y2="42.84" gradientUnits="userSpaceOnUse"><stop stop-color="#3BEA62"/><stop offset="1" stop-color="#6B57FF"/></linearGradient></defs><path fill="url(#code-with-me_svg__a)" d="M.007 0v36h18l9-36h-27Zm15 30h-9v-6h10.2l-1.2 6Zm15-18-9 36h27V12h-18Zm12 30h-13.8l1.8-6h12v6Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 453 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="pycharm_svg__a" x1="7.671" x2="61.126" y1="64.393" y2="39.609" gradientUnits="userSpaceOnUse"><stop offset=".1" stop-color="#00D886"/><stop offset=".59" stop-color="#F0EB18"/></linearGradient><linearGradient id="pycharm_svg__b" x1="59.933" x2="1.337" y1="59.676" y2="1.08" gradientUnits="userSpaceOnUse"><stop offset=".3" stop-color="#F0EB18"/><stop offset=".7" stop-color="#00C4F4"/></linearGradient></defs><path fill="#00D886" d="M6 47.55v12.259a4.125 4.125 0 0 0 4.19 4.124l11.044-.176a4.125 4.125 0 0 0 3.066-1.44l32.707-38.158c.64-.748.993-1.7.993-2.685V10.125A4.125 4.125 0 0 0 53.875 6H42.872c-1.19 0-2.321.514-3.105 1.409L7.021 44.834A4.123 4.123 0 0 0 6 47.55Z"/><path fill="url(#pycharm_svg__a)" d="M6 49.015v10.862a4.125 4.125 0 0 0 4.125 4.125h12.566c.2 0 .4-.014.598-.044l37.185-5.448A4.125 4.125 0 0 0 64 54.429V39.03a4.125 4.125 0 0 0-4.127-4.125l-18.504.005c-.426 0-.849.066-1.254.195L8.871 45.085A4.126 4.126 0 0 0 6 49.015H6Z"/><path fill="url(#pycharm_svg__b)" d="M0 4.125v34.127c0 1.659.993 3.155 2.52 3.8L39.943 57.85c.518.219 1.075.33 1.638.324l18.329-.15A4.125 4.125 0 0 0 64 53.9V36.234c0-.806-.236-1.593-.678-2.267L42.213 1.86A4.125 4.125 0 0 0 38.766 0H4.125A4.125 4.125 0 0 0 0 4.125Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM16.993 16.992h6.442c1.057 0 1.984.193 2.78.579.798.386 1.412.929 1.845 1.63.432.7.648 1.51.648 2.432 0 .922-.22 1.759-.659 2.466-.44.708-1.063 1.256-1.87 1.646-.808.39-1.751.584-2.83.584h-3.43V32h-2.927V16.993Zm7.54 6.63c.369-.182.652-.443.852-.782.2-.34.3-.738.3-1.195s-.1-.842-.3-1.174c-.2-.333-.484-.59-.852-.772-.368-.182-.798-.273-1.291-.273h-3.324v4.47h3.324c.493 0 .923-.091 1.291-.273ZM33.713 31.242a7.287 7.287 0 0 1-2.744-2.786c-.664-1.183-.996-2.503-.996-3.961s.332-2.778.996-3.96a7.29 7.29 0 0 1 2.744-2.787c1.165-.675 2.466-1.014 3.902-1.014 1.215 0 2.33.225 3.344.675a6.862 6.862 0 0 1 2.535 1.892 6.443 6.443 0 0 1 1.356 2.793h-3.066a4.054 4.054 0 0 0-.895-1.431 4.038 4.038 0 0 0-1.43-.95 4.896 4.896 0 0 0-1.823-.332c-.887 0-1.687.222-2.402.665a4.611 4.611 0 0 0-1.677 1.827c-.404.776-.606 1.649-.606 2.62 0 .973.202 1.846.606 2.621a4.607 4.607 0 0 0 1.677 1.828c.715.443 1.515.664 2.402.664.657 0 1.265-.11 1.822-.332a4.022 4.022 0 0 0 2.326-2.38h3.066a6.444 6.444 0 0 1-1.356 2.792 6.863 6.863 0 0 1-2.535 1.892c-1.015.45-2.129.675-3.344.675-1.436 0-2.737-.337-3.902-1.013v.003Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="rider_svg__a" x1="64.391" x2="39.607" y1="56.329" y2="2.874" gradientUnits="userSpaceOnUse"><stop offset=".21" stop-color="#007DFE"/><stop offset=".55" stop-color="#FFB700"/></linearGradient><linearGradient id="rider_svg__b" x1="59.676" x2="1.08" y1="4.067" y2="62.663" gradientUnits="userSpaceOnUse"><stop offset=".23" stop-color="#FFB700"/><stop offset=".73" stop-color="#FF0A67"/></linearGradient></defs><path fill="#007DFE" d="M47.55 58h12.258a4.125 4.125 0 0 0 4.124-4.19l-.176-11.044a4.124 4.124 0 0 0-1.44-3.066L24.158 6.993A4.126 4.126 0 0 0 21.474 6H10.125A4.125 4.125 0 0 0 6 10.125v11.003c0 1.19.514 2.321 1.409 3.104L44.834 56.98A4.124 4.124 0 0 0 47.55 58Z"/><path fill="url(#rider_svg__a)" d="M49.013 58h10.862A4.125 4.125 0 0 0 64 53.875V41.309c0-.2-.014-.4-.044-.598L58.508 3.527A4.125 4.125 0 0 0 54.427 0H39.029a4.125 4.125 0 0 0-4.125 4.126l.005 18.505c0 .425.066.848.195 1.253l9.979 31.246a4.126 4.126 0 0 0 3.93 2.87Z"/><path fill="url(#rider_svg__b)" d="M4.125 64h34.127a4.125 4.125 0 0 0 3.8-2.52L57.85 24.057c.219-.518.33-1.076.324-1.638l-.15-18.329A4.124 4.124 0 0 0 53.9 0H36.234c-.805 0-1.593.236-2.266.678L1.86 21.787A4.125 4.125 0 0 0 0 25.234v34.641A4.125 4.125 0 0 0 4.125 64Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM16.992 16.992h6.442c1.05 0 1.976.193 2.776.579.8.386 1.417.929 1.85 1.63.432.7.648 1.51.648 2.432 0 .922-.22 1.75-.66 2.46-.439.712-1.065 1.262-1.875 1.651-.811.39-1.753.584-2.825.584h-3.43v5.67h-2.926V16.992Zm7.54 6.63c.369-.183.653-.444.853-.783.2-.34.3-.734.3-1.184 0-.45-.1-.852-.3-1.185-.2-.332-.485-.59-.852-.772-.368-.182-.799-.273-1.292-.273h-3.323v4.47h3.323c.493 0 .924-.09 1.292-.273Zm-2.63 1.763h3.194L29.03 32h-3.355l-3.773-6.614ZM31.613 16.992h5.713c1.422 0 2.696.321 3.822.965a6.881 6.881 0 0 1 2.641 2.674c.636 1.14.954 2.427.954 3.864 0 1.437-.318 2.725-.953 3.865a6.89 6.89 0 0 1-2.642 2.674c-1.125.643-2.4.964-3.822.964h-5.713V16.992Zm8.028 11.876c.672-.4 1.19-.973 1.555-1.72.364-.747.547-1.63.547-2.653 0-1.023-.183-1.907-.547-2.653-.365-.747-.883-1.32-1.555-1.721-.672-.4-1.454-.6-2.347-.6h-2.755v9.947h2.755c.893 0 1.675-.2 2.347-.6Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="rubymine_svg__a" x1="4.325" x2="62.921" y1="59.932" y2="1.337" gradientUnits="userSpaceOnUse"><stop offset=".29" stop-color="#FF2358"/><stop offset=".75" stop-color="#7256FF"/></linearGradient><linearGradient id="rubymine_svg__b" x1="-.391" x2="24.393" y1="7.671" y2="61.125" gradientUnits="userSpaceOnUse"><stop offset=".29" stop-color="#FF8100"/><stop offset=".56" stop-color="#FF2358"/></linearGradient></defs><path fill="url(#rubymine_svg__a)" d="M59.875 0H25.748a4.125 4.125 0 0 0-3.8 2.52L6.151 39.942a4.118 4.118 0 0 0-.325 1.639l.15 18.328A4.125 4.125 0 0 0 10.101 64h17.666c.805 0 1.593-.235 2.266-.678l32.109-21.108A4.123 4.123 0 0 0 64 38.766V4.125A4.125 4.125 0 0 0 59.875 0Z"/><path fill="url(#rubymine_svg__b)" d="M14.987 6H4.126A4.125 4.125 0 0 0 0 10.125v12.566c0 .2.014.4.044.598l5.448 37.184A4.125 4.125 0 0 0 9.572 64H24.97a4.125 4.125 0 0 0 4.125-4.126l-.004-18.504c0-.426-.067-.85-.196-1.254L18.916 8.87A4.126 4.126 0 0 0 14.987 6V6Z"/><path fill="#FF8100" d="M16.45 6H4.19a4.125 4.125 0 0 0-4.124 4.19L0 21c.019 1.181.786 2.531 1.683 3.3l38.158 32.706c.748.641 1.7.993 2.684.993h11.35A4.125 4.125 0 0 0 58 53.874V42.871c0-1.189-.514-2.32-1.41-3.104L19.167 7.021A4.123 4.123 0 0 0 16.45 6Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM17.012 16.992h6.442c1.05 0 1.975.193 2.776.579.8.386 1.416.929 1.849 1.63.432.7.648 1.51.648 2.432 0 .922-.22 1.75-.66 2.46-.438.712-1.064 1.262-1.875 1.651-.811.39-1.752.584-2.824.584h-3.43v5.67h-2.926V16.992Zm7.54 6.63c.368-.183.652-.444.852-.783.2-.34.3-.734.3-1.184 0-.45-.1-.852-.3-1.185-.2-.332-.484-.59-.852-.772-.368-.182-.798-.273-1.292-.273h-3.323v4.47h3.323c.494 0 .924-.09 1.292-.273Zm-2.631 1.763h3.194L29.05 32h-3.355l-3.773-6.614ZM31.633 16.992h4.073l3.087 9.85.257 1.287.225-1.286 2.98-9.85h4.138v15.005h-2.894V21.29l.043-.782-3.494 11.49h-2.123l-3.451-11.415.043.707v10.708h-2.883V16.992h-.001Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="rustrover_svg__a" x1="7.671" x2="61.125" y1="64.393" y2="39.609" gradientUnits="userSpaceOnUse"><stop offset=".08" stop-color="#00D886"/><stop offset=".46" stop-color="#FFAB00"/></linearGradient><linearGradient id="rustrover_svg__b" x1="59.932" x2="1.336" y1="59.676" y2="1.08" gradientUnits="userSpaceOnUse"><stop offset=".19" stop-color="#FFAB00"/><stop offset=".83" stop-color="#FF004C"/></linearGradient></defs><path fill="#00D886" d="M6 47.55v12.258a4.125 4.125 0 0 0 4.19 4.124l11.044-.176a4.124 4.124 0 0 0 3.066-1.44l32.707-38.158c.64-.747.993-1.7.993-2.684V10.125A4.125 4.125 0 0 0 53.875 6H42.872c-1.19 0-2.321.514-3.105 1.409L7.021 44.833A4.124 4.124 0 0 0 6 47.55Z"/><path fill="url(#rustrover_svg__a)" d="M6 49.015v10.862a4.125 4.125 0 0 0 4.125 4.125h12.566c.2 0 .4-.014.598-.044l37.185-5.448A4.125 4.125 0 0 0 64 54.429V39.03a4.125 4.125 0 0 0-4.127-4.125l-18.504.005c-.426 0-.849.066-1.254.195L8.871 45.085A4.126 4.126 0 0 0 6 49.015H6Z"/><path fill="url(#rustrover_svg__b)" d="M0 4.125v34.127c0 1.659.993 3.155 2.52 3.8L39.943 57.85c.518.219 1.075.33 1.638.324l18.329-.15A4.125 4.125 0 0 0 64 53.9V36.234c0-.806-.236-1.593-.678-2.267L42.213 1.86A4.125 4.125 0 0 0 38.766 0H4.125A4.125 4.125 0 0 0 0 4.125Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM16.992 16.992h6.442c1.05 0 1.976.193 2.776.579.8.386 1.417.929 1.85 1.63.432.7.648 1.51.648 2.432 0 .922-.22 1.75-.66 2.46-.439.712-1.065 1.262-1.875 1.651-.811.39-1.753.584-2.825.584h-3.43v5.67h-2.926V16.992Zm7.54 6.63c.369-.183.653-.444.853-.783.2-.34.3-.734.3-1.184 0-.45-.1-.852-.3-1.185-.2-.332-.485-.59-.852-.772-.368-.182-.799-.273-1.292-.273h-3.323v4.47h3.323c.493 0 .924-.09 1.292-.273Zm-2.63 1.763h3.194L29.03 32h-3.355l-3.773-6.614ZM31.613 16.992h6.442c1.05 0 1.976.193 2.776.579.8.386 1.417.929 1.85 1.63.432.7.648 1.51.648 2.432 0 .922-.22 1.75-.66 2.46-.439.712-1.064 1.262-1.875 1.651-.811.39-1.753.584-2.824.584h-3.43v5.67h-2.927V16.992Zm7.54 6.63c.369-.183.653-.444.853-.783.2-.34.3-.734.3-1.184 0-.45-.1-.852-.3-1.185-.2-.332-.485-.59-.852-.772-.368-.182-.799-.273-1.292-.273h-3.323v4.47h3.323c.493 0 .924-.09 1.292-.273Zm-2.63 1.763h3.194L43.651 32h-3.355l-3.773-6.614Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="none" viewBox="0 0 64 64"><defs><linearGradient id="webstorm_svg__a" x1="7.671" x2="61.126" y1="64.392" y2="39.609" gradientUnits="userSpaceOnUse"><stop offset=".22" stop-color="#F0EB18"/><stop offset=".59" stop-color="#00C4F4"/></linearGradient><linearGradient id="webstorm_svg__b" x1="59.932" x2="1.337" y1="59.676" y2="1.079" gradientUnits="userSpaceOnUse"><stop offset=".19" stop-color="#00C4F4"/><stop offset=".83" stop-color="#007DFE"/></linearGradient></defs><path fill="#F0EB18" d="M6 47.55v12.258a4.125 4.125 0 0 0 4.19 4.124l11.044-.176a4.124 4.124 0 0 0 3.066-1.44l32.707-38.158c.64-.747.993-1.7.993-2.684V10.125A4.125 4.125 0 0 0 53.875 6H42.872c-1.19 0-2.321.514-3.105 1.409L7.021 44.833A4.124 4.124 0 0 0 6 47.55Z"/><path fill="url(#webstorm_svg__a)" d="M6 49.015v10.862a4.125 4.125 0 0 0 4.125 4.125h12.566c.2 0 .4-.014.598-.044l37.185-5.448A4.125 4.125 0 0 0 64 54.429V39.03a4.125 4.125 0 0 0-4.127-4.125l-18.504.005c-.426 0-.849.066-1.254.195L8.871 45.085A4.126 4.126 0 0 0 6 49.015H6Z"/><path fill="url(#webstorm_svg__b)" d="M0 4.125v34.127c0 1.659.993 3.155 2.52 3.8L39.943 57.85c.518.219 1.075.33 1.638.324l18.329-.15A4.125 4.125 0 0 0 64 53.9V36.234c0-.806-.236-1.593-.678-2.267L42.213 1.86A4.125 4.125 0 0 0 38.766 0H4.125A4.125 4.125 0 0 0 0 4.125Z"/><path fill="#000" d="M52 12H12v40h40V12Z"/><path fill="#fff" d="M33 44H17v3h16v-3ZM19.051 16.992l2.423 10.955 2.583-10.955h2.958l2.701 10.955 2.348-10.955h2.97l-3.645 15.006h-3.334l-2.53-10.9-2.561 10.9H19.64l-3.623-15.006h3.033ZM38.662 31.664c-.84-.393-1.497-.943-1.972-1.65-.475-.708-.72-1.522-.735-2.444h2.937c0 .436.114.818.343 1.147.229.329.547.586.954.772.407.185.875.278 1.404.278.53 0 .952-.083 1.335-.251.382-.168.678-.402.889-.702.21-.3.316-.643.316-1.029 0-.479-.145-.875-.434-1.19-.29-.314-.688-.532-1.195-.654l-2.648-.59c-.714-.156-1.336-.426-1.865-.809a4.015 4.015 0 0 1-1.232-1.42c-.293-.564-.44-1.2-.44-1.908 0-.857.222-1.627.665-2.31.443-.682 1.061-1.215 1.854-1.597.793-.382 1.694-.573 2.701-.573 1.008 0 1.934.186 2.734.558.8.372 1.423.888 1.87 1.549.447.66.673 1.42.68 2.277h-2.925c0-.364-.097-.691-.29-.98s-.466-.515-.82-.676a2.915 2.915 0 0 0-1.217-.24c-.457 0-.859.076-1.205.23a1.903 1.903 0 0 0-.81.643c-.193.275-.289.595-.289.96 0 .414.134.757.402 1.028.268.272.634.464 1.099.579l2.55.557c.737.15 1.392.435 1.968.852a4.45 4.45 0 0 1 1.345 1.544 4.23 4.23 0 0 1 .482 1.999c0 .886-.234 1.681-.702 2.385-.468.703-1.123 1.255-1.966 1.656-.844.4-1.809.6-2.894.6-1.086 0-2.05-.197-2.889-.59Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1,515 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>QiuMo JetBrains Help - JetBrains激活助手</title>
|
||||||
|
|
||||||
|
<!-- 核心技术栈 -->
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||||
|
|
||||||
|
<!-- Vue.js 3 -->
|
||||||
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||||
|
|
||||||
|
<!-- 自定义样式 -->
|
||||||
|
<link rel="stylesheet" href="/css/main.css" />
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50">
|
||||||
|
<div id="app">
|
||||||
|
<!-- 配置模态框 -->
|
||||||
|
<transition name="modal">
|
||||||
|
<div v-if="showConfigModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
|
||||||
|
<div class="bg-white rounded-2xl p-8 w-full max-w-md shadow-2xl">
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<div class="w-16 h-16 gradient-bg rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i class="fas fa-cog text-white text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900 mb-2">初始配置</h2>
|
||||||
|
<p class="text-gray-600">请配置您的许可证信息</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">许可证名称</label>
|
||||||
|
<input
|
||||||
|
v-model="config.licenseName"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入许可证名称"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">被许可人</label>
|
||||||
|
<input
|
||||||
|
v-model="config.assigneeName"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入被许可人姓名"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
@click="saveConfig"
|
||||||
|
:disabled="!config.licenseName || !config.assigneeName"
|
||||||
|
class="w-full btn-primary text-white py-3 px-6 rounded-lg font-medium disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
|
保存配置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<!-- 主导航 -->
|
||||||
|
<nav class="glass sticky top-0 z-40">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between items-center py-4">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<div class="w-10 h-10 gradient-bg rounded-xl flex items-center justify-center">
|
||||||
|
<i class="fas fa-rocket text-white text-lg"></i>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-xl font-bold gradient-text">QiuMo JetBrains Help</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center space-x-6">
|
||||||
|
<nav class="hidden md:flex space-x-8">
|
||||||
|
<a
|
||||||
|
v-for="item in navItems"
|
||||||
|
:key="item.id"
|
||||||
|
@click="navigateTo(item.id)"
|
||||||
|
:class="['cursor-pointer transition-colors font-medium', currentPage === item.id ? 'text-purple-600' : 'text-gray-600 hover:text-purple-600']">
|
||||||
|
<i :class="item.icon + ' mr-2'"></i>{{ item.name }}
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<button @click="showConfigModal = true" class="p-2 text-gray-600 hover:text-purple-600 transition-colors" title="配置">
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- 移动端导航 -->
|
||||||
|
<div class="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-40">
|
||||||
|
<div class="flex">
|
||||||
|
<button
|
||||||
|
v-for="item in navItems"
|
||||||
|
:key="item.id"
|
||||||
|
@click="navigateTo(item.id)"
|
||||||
|
:class="['flex-1 py-3 px-2 text-center transition-colors', currentPage === item.id ? 'text-purple-600 bg-purple-50' : 'text-gray-600']">
|
||||||
|
<i :class="item.icon + ' block text-lg mb-1'"></i>
|
||||||
|
<span class="text-xs">{{ item.name }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 页面内容 -->
|
||||||
|
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 pb-24 md:pb-8">
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<!-- 首页 -->
|
||||||
|
<div v-if="currentPage === 'home'" key="home">
|
||||||
|
<div class="text-center mb-8 md:mb-12">
|
||||||
|
<h2 class="home-title text-3xl md:text-4xl font-bold text-gray-900 mb-4">欢迎使用 <span class="gradient-text">JetBrains 激活助手</span></h2>
|
||||||
|
<p class="home-subtitle text-base md:text-lg text-gray-600 max-w-2xl mx-auto px-4">为您提供便捷的 JetBrains 产品激活解决方案,支持激活码和许可证服务器两种激活方式</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 当前配置信息 -->
|
||||||
|
<div class="home-section bg-white rounded-2xl p-4 md:p-6 shadow-lg mb-6 md:mb-8">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900">当前配置</h3>
|
||||||
|
<button @click="showConfigModal = true" class="text-sm text-purple-600 hover:text-purple-700 font-medium"><i class="fas fa-edit mr-1"></i>修改配置</button>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fas fa-certificate text-purple-600"></i>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p class="text-sm text-gray-500">许可证名称</p>
|
||||||
|
<p class="font-medium text-gray-900 truncate">{{ config.licenseName || '未设置' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-3">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||||
|
<i class="fas fa-user text-blue-600"></i>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<p class="text-sm text-gray-500">被许可人</p>
|
||||||
|
<p class="font-medium text-gray-900 truncate">{{ config.assigneeName || '未设置' }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 代理工具下载说明 -->
|
||||||
|
<div class="home-section bg-white rounded-2xl p-4 md:p-8 shadow-lg mb-6 md:mb-8">
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<div class="w-16 h-16 bg-gradient-to-r from-indigo-400 to-purple-500 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i class="fas fa-download text-white text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 mb-2">必备代理工具</h3>
|
||||||
|
<p class="text-gray-600 text-sm md:text-base px-2">激活前必须先下载并配置 ja-netfilter 代理工具</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-section">
|
||||||
|
<h4 class="font-semibold text-gray-900 mb-3">配置步骤:</h4>
|
||||||
|
<div class="space-y-4 text-sm text-gray-700">
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-start space-y-2 sm:space-y-0 sm:space-x-3 config-step">
|
||||||
|
<span class="w-6 h-6 bg-indigo-100 text-indigo-600 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">1</span>
|
||||||
|
<div class="config-step-content">
|
||||||
|
<p>下载并解压 ja-netfilter 工具包</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-start space-y-2 sm:space-y-0 sm:space-x-3 config-step">
|
||||||
|
<span class="w-6 h-6 bg-indigo-100 text-indigo-600 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">2</span>
|
||||||
|
<div class="config-step-content">
|
||||||
|
<p class="mb-2">在 IDE 的 VM options 中添加以下配置:</p>
|
||||||
|
<div class="config-code">
|
||||||
|
-javaagent:插件解压路径\ja-netfilter\ja-netfilter.jar<br />--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED<br />
|
||||||
|
--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-start space-y-2 sm:space-y-0 sm:space-x-3 config-step">
|
||||||
|
<span class="w-6 h-6 bg-indigo-100 text-indigo-600 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">3</span>
|
||||||
|
<div class="config-step-content">
|
||||||
|
<p>重启 IDE 后即可使用激活功能</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button @click="downloadAgent" class="w-full btn-primary text-white py-3 px-6 rounded-lg font-medium mt-6"><i class="fas fa-download mr-2"></i>下载 ja-netfilter 工具</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 激活方式 -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 md:gap-8 mb-8 md:mb-12">
|
||||||
|
<!-- 激活码激活 -->
|
||||||
|
<div class="home-section bg-white rounded-2xl p-6 md:p-8 shadow-lg card-hover">
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<div class="w-16 h-16 bg-gradient-to-r from-green-400 to-blue-500 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i class="fas fa-key text-white text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 mb-2">激活码激活</h3>
|
||||||
|
<p class="text-gray-600 text-sm md:text-base">生成产品激活码直接激活</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<div class="w-6 h-6 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span class="text-green-600 text-sm font-bold">1</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-700 text-sm md:text-base">选择需要激活的产品或插件</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<div class="w-6 h-6 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span class="text-green-600 text-sm font-bold">2</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-700 text-sm md:text-base">设置许可证到期时间</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<div class="w-6 h-6 bg-green-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span class="text-green-600 text-sm font-bold">3</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-700 text-sm md:text-base">生成激活码并在软件中输入</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 space-y-3">
|
||||||
|
<button @click="navigateTo('products')" class="w-full btn-primary text-white py-3 px-6 rounded-lg font-medium"><i class="fas fa-cube mr-2"></i>激活产品</button>
|
||||||
|
<button @click="navigateTo('plugins')" class="w-full bg-gray-100 text-gray-700 py-3 px-6 rounded-lg font-medium hover:bg-gray-200 transition-colors">
|
||||||
|
<i class="fas fa-puzzle-piece mr-2"></i>激活插件
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 服务器激活 -->
|
||||||
|
<div class="home-section bg-white rounded-2xl p-6 md:p-8 shadow-lg card-hover">
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<div class="w-16 h-16 bg-gradient-to-r from-purple-400 to-pink-500 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i class="fas fa-server text-white text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 mb-2">服务器激活</h3>
|
||||||
|
<p class="text-gray-600 text-sm md:text-base">配置许可证服务器统一激活</p>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<div class="w-6 h-6 bg-purple-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span class="text-purple-600 text-sm font-bold">1</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-700 text-sm md:text-base">下载并配置 ja-netfilter 代理</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<div class="w-6 h-6 bg-purple-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span class="text-purple-600 text-sm font-bold">2</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-700 text-sm md:text-base">在软件中配置许可证服务器地址</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<div class="w-6 h-6 bg-purple-100 rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
||||||
|
<span class="text-purple-600 text-sm font-bold">3</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-gray-700 text-sm md:text-base">启动软件自动获取许可证</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6">
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||||
|
<p class="text-sm text-gray-600 mb-2">许可证服务器地址:</p>
|
||||||
|
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2">
|
||||||
|
<input :value="serverUrl" readonly class="flex-1 bg-white border border-gray-300 rounded-lg px-3 py-2 text-sm min-w-0" />
|
||||||
|
<button @click="copyToClipboard(serverUrl)" class="px-3 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors whitespace-nowrap" title="复制">
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JRebel 特殊说明 -->
|
||||||
|
<div class="home-section bg-gradient-to-r from-orange-400 to-red-500 rounded-2xl p-6 md:p-8 text-white">
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center space-y-4 sm:space-y-0 sm:space-x-4 mb-4">
|
||||||
|
<div class="w-12 h-12 bg-white bg-opacity-20 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||||
|
<i class="fas fa-fire text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="text-center sm:text-left">
|
||||||
|
<h3 class="text-xl font-bold">JRebel 专属激活</h3>
|
||||||
|
<p class="opacity-90 text-sm md:text-base">JRebel 仅支持服务器激活模式</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button @click="navigateTo('jrebel')" class="w-full bg-white text-orange-600 py-3 px-6 rounded-lg font-medium hover:bg-gray-50 transition-colors">
|
||||||
|
<i class="fas fa-arrow-right mr-2"></i>前往 JRebel 激活
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 产品页面 -->
|
||||||
|
<div v-else-if="currentPage === 'products'" key="products">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 mb-4">JetBrains 产品</h2>
|
||||||
|
<p class="text-gray-600 mb-6">选择需要激活的 JetBrains 产品</p>
|
||||||
|
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="search-container max-w-md">
|
||||||
|
<i class="fas fa-search search-icon"></i>
|
||||||
|
<input
|
||||||
|
v-model="searchQuery"
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索产品..."
|
||||||
|
class="search-input w-full px-8 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div v-for="product in filteredProducts" :key="product.productCode" class="bg-white rounded-2xl p-6 shadow-lg card-hover cursor-pointer" @click="selectProduct(product)">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-2xl overflow-hidden">
|
||||||
|
<img :src="getProductIcon(product)" :alt="product.name" class="w-full h-full object-contain" @error="$event.target.src='/images/plugin.svg'" />
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-bold text-gray-900 mb-2">{{ product.name }}</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-4">{{ product.description || '专业的开发工具' }}</p>
|
||||||
|
<button class="w-full btn-primary text-white py-2 px-4 rounded-lg font-medium">生成激活码</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="filteredProducts.length === 0" class="text-center py-12">
|
||||||
|
<i class="fas fa-search text-gray-400 text-4xl mb-4"></i>
|
||||||
|
<p class="text-gray-500">没有找到匹配的产品</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 插件页面 -->
|
||||||
|
<div v-else-if="currentPage === 'plugins'" key="plugins">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 mb-4">JetBrains 插件</h2>
|
||||||
|
<p class="text-gray-600 mb-6">选择需要激活的 JetBrains 付费插件</p>
|
||||||
|
|
||||||
|
<!-- 搜索框 -->
|
||||||
|
<div class="search-container max-w-md">
|
||||||
|
<i class="fas fa-search search-icon"></i>
|
||||||
|
<input
|
||||||
|
v-model="searchQuery"
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索插件..."
|
||||||
|
class="search-input w-full px-8 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div v-for="plugin in filteredPlugins" :key="plugin.id" class="bg-white rounded-2xl p-6 shadow-lg card-hover cursor-pointer" @click="selectPlugin(plugin)">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="w-16 h-16 mx-auto mb-4 rounded-2xl overflow-hidden">
|
||||||
|
<img :src="getPluginIcon(plugin)" :alt="plugin.name" class="w-full h-full object-contain" @error="$event.target.src='/images/plugin.svg'" />
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-bold text-gray-900 mb-2">{{ plugin.name }}</h3>
|
||||||
|
<p class="text-gray-600 text-sm mb-4">{{ plugin.description || '功能强大的插件' }}</p>
|
||||||
|
<button class="w-full btn-primary text-white py-2 px-4 rounded-lg font-medium">生成激活码</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="filteredPlugins.length === 0" class="text-center py-12">
|
||||||
|
<i class="fas fa-search text-gray-400 text-4xl mb-4"></i>
|
||||||
|
<p class="text-gray-500">没有找到匹配的插件</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JRebel页面 -->
|
||||||
|
<div v-else-if="currentPage === 'jrebel'" key="jrebel" class="jrebel-container">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h2 class="text-3xl font-bold text-gray-900 mb-4"><i class="fas fa-fire text-orange-500 mr-3"></i>JRebel 激活</h2>
|
||||||
|
<p class="text-gray-600">JRebel 仅支持服务器激活模式</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white rounded-2xl p-6 md:p-8 shadow-lg">
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<div class="w-20 h-20 bg-gradient-to-r from-orange-400 to-red-500 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i class="fas fa-fire text-white text-3xl"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-2xl font-bold text-gray-900 mb-2">JRebel 服务器激活</h3>
|
||||||
|
<p class="text-gray-600">请按照以下步骤配置 JRebel 服务器激活</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6 mb-8">
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-start space-y-3 sm:space-y-0 sm:space-x-4">
|
||||||
|
<div class="w-8 h-8 bg-orange-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<span class="text-orange-600 font-bold">1</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 jrebel-step">
|
||||||
|
<h4 class="font-semibold text-gray-900 mb-2">复制服务器地址</h4>
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4">
|
||||||
|
<p class="text-sm text-gray-600 mb-2">JRebel 服务器地址:</p>
|
||||||
|
<div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-2">
|
||||||
|
<input :value="jrebelServerUrl" readonly class="flex-1 bg-white border border-gray-300 rounded-lg px-3 py-2 text-sm min-w-0" />
|
||||||
|
<button @click="copyToClipboard(jrebelServerUrl)" class="px-4 py-2 bg-orange-600 text-white rounded-lg hover:bg-orange-700 transition-colors whitespace-nowrap">
|
||||||
|
<i class="fas fa-copy mr-2"></i>复制
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-start space-y-3 sm:space-y-0 sm:space-x-4">
|
||||||
|
<div class="w-8 h-8 bg-orange-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<span class="text-orange-600 font-bold">2</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 jrebel-step">
|
||||||
|
<h4 class="font-semibold text-gray-900 mb-2">打开 JRebel 激活界面</h4>
|
||||||
|
<p class="text-gray-700">在 IDE 中找到 JRebel 插件,选择"Connect to License Server"</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-start space-y-3 sm:space-y-0 sm:space-x-4">
|
||||||
|
<div class="w-8 h-8 bg-orange-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<span class="text-orange-600 font-bold">3</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 jrebel-step">
|
||||||
|
<h4 class="font-semibold text-gray-900 mb-2">输入服务器地址</h4>
|
||||||
|
<p class="text-gray-700">将上述服务器地址粘贴到 JRebel 的服务器地址输入框中</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-start space-y-3 sm:space-y-0 sm:space-x-4">
|
||||||
|
<div class="w-8 h-8 bg-orange-100 rounded-full flex items-center justify-center flex-shrink-0">
|
||||||
|
<span class="text-orange-600 font-bold">4</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 jrebel-step">
|
||||||
|
<h4 class="font-semibold text-gray-900 mb-2">完成激活</h4>
|
||||||
|
<p class="text-gray-700">点击"Activate"按钮完成激活,享受 JRebel 热部署功能</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-orange-50 border border-orange-200 rounded-lg p-4">
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<i class="fas fa-info-circle text-orange-600 mt-0.5 flex-shrink-0"></i>
|
||||||
|
<div>
|
||||||
|
<h4 class="font-semibold text-orange-800 mb-1">注意事项</h4>
|
||||||
|
<ul class="text-orange-700 text-sm space-y-1">
|
||||||
|
<li>• 确保您的网络可以访问本服务器地址</li>
|
||||||
|
<li>• JRebel 激活后请保持服务器运行状态</li>
|
||||||
|
<li>• 如有问题,请检查防火墙和网络设置</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- 到期时间配置模态框 -->
|
||||||
|
<transition name="modal">
|
||||||
|
<div v-if="showLicenseModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
|
||||||
|
<div class="bg-white rounded-2xl p-8 w-full max-w-md shadow-2xl">
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<div class="w-16 h-16 gradient-bg rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<img
|
||||||
|
v-if="selectedItem"
|
||||||
|
:src="selectedItem.hasOwnProperty('id') ? getPluginIcon(selectedItem) : getProductIcon(selectedItem)"
|
||||||
|
:alt="selectedItem?.name"
|
||||||
|
class="w-8 h-8 object-contain"
|
||||||
|
@error="$event.target.style.display='none'" />
|
||||||
|
<i v-if="!selectedItem" class="fas fa-certificate text-white text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900 mb-2">生成激活码</h2>
|
||||||
|
<p class="text-gray-600">{{ selectedItem?.name }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">到期时间</label>
|
||||||
|
<input
|
||||||
|
v-model="licenseConfig.expiryDate"
|
||||||
|
type="date"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex space-x-3">
|
||||||
|
<button @click="showLicenseModal = false" class="flex-1 bg-gray-100 text-gray-700 py-3 px-6 rounded-lg font-medium hover:bg-gray-200 transition-colors">取消</button>
|
||||||
|
<button @click="generateLicense" :disabled="isGenerating" class="flex-1 btn-primary text-white py-3 px-6 rounded-lg font-medium disabled:opacity-50">
|
||||||
|
<i v-if="isGenerating" class="fas fa-spinner loading mr-2"></i>
|
||||||
|
{{ isGenerating ? '生成中...' : '生成激活码' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<!-- 激活码结果模态框 -->
|
||||||
|
<transition name="modal">
|
||||||
|
<div v-if="showResultModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
|
||||||
|
<div class="bg-white rounded-2xl p-6 md:p-8 w-full max-w-2xl shadow-2xl max-h-[90vh] overflow-y-auto">
|
||||||
|
<div class="text-center mb-6">
|
||||||
|
<div class="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i class="fas fa-check text-green-600 text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900 mb-2">激活码生成成功</h2>
|
||||||
|
<p class="text-gray-600">请复制以下激活码到软件中使用</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<span class="text-sm font-medium text-gray-700">激活码:</span>
|
||||||
|
<button @click="copyToClipboard(generatedLicense)" class="text-sm text-purple-600 hover:text-purple-700 font-medium"><i class="fas fa-copy mr-1"></i>复制</button>
|
||||||
|
</div>
|
||||||
|
<div class="license-code-display">{{ generatedLicense }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button @click="showResultModal = false" class="w-full btn-primary text-white py-3 px-6 rounded-lg font-medium">完成</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<!-- 返回顶部按钮 -->
|
||||||
|
<div @click="scrollToTop" class="back-to-top" :class="showBackToTop ? 'show' : ''">
|
||||||
|
<i class="fas fa-arrow-up"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 引入工具函数和应用逻辑 -->
|
||||||
|
<script src="/js/utils.js"></script>
|
||||||
|
<script src="/js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
// Vue 3 主应用
|
||||||
|
const { createApp } = Vue
|
||||||
|
|
||||||
|
// 主应用组件
|
||||||
|
const App = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
currentPage: 'home',
|
||||||
|
showConfigModal: false,
|
||||||
|
showLicenseModal: false,
|
||||||
|
showResultModal: false,
|
||||||
|
isGenerating: false,
|
||||||
|
config: {
|
||||||
|
licenseName: '',
|
||||||
|
assigneeName: ''
|
||||||
|
},
|
||||||
|
licenseConfig: {
|
||||||
|
expiryDate: ''
|
||||||
|
},
|
||||||
|
selectedItem: null,
|
||||||
|
generatedLicense: '',
|
||||||
|
products: [],
|
||||||
|
plugins: [],
|
||||||
|
filteredProducts: [],
|
||||||
|
filteredPlugins: [],
|
||||||
|
searchQuery: '',
|
||||||
|
navItems: [
|
||||||
|
{ id: 'home', name: '首页', icon: 'fas fa-home' },
|
||||||
|
{ id: 'products', name: '产品', icon: 'fas fa-cube' },
|
||||||
|
{ id: 'plugins', name: '插件', icon: 'fas fa-puzzle-piece' },
|
||||||
|
{ id: 'jrebel', name: 'JRebel', icon: 'fas fa-fire' }
|
||||||
|
],
|
||||||
|
showBackToTop: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
serverUrl() {
|
||||||
|
return `${window.location.origin}`
|
||||||
|
},
|
||||||
|
|
||||||
|
jrebelServerUrl() {
|
||||||
|
const uuid = Utils.generateUUID()
|
||||||
|
return `${window.location.origin}/${uuid}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
searchQuery(newQuery) {
|
||||||
|
this.filterItems(newQuery)
|
||||||
|
},
|
||||||
|
|
||||||
|
currentPage() {
|
||||||
|
this.searchQuery = ''
|
||||||
|
// 重置过滤结果
|
||||||
|
this.filteredProducts = [...this.products]
|
||||||
|
this.filteredPlugins = [...this.plugins]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.loadConfig()
|
||||||
|
this.loadProducts()
|
||||||
|
this.loadPlugins()
|
||||||
|
this.setDefaultExpiryDate()
|
||||||
|
|
||||||
|
// 监听滚动事件
|
||||||
|
const handleScroll = () => {
|
||||||
|
const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
|
||||||
|
this.showBackToTop = scrollTop > 300
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听滚动事件
|
||||||
|
window.addEventListener('scroll', handleScroll)
|
||||||
|
|
||||||
|
// 保存函数引用以便清理
|
||||||
|
this._handleScroll = handleScroll
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeUnmount() {
|
||||||
|
// 清理滚动事件监听器
|
||||||
|
if (this._handleScroll) {
|
||||||
|
window.removeEventListener('scroll', this._handleScroll)
|
||||||
|
document.removeEventListener('scroll', this._handleScroll)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// 配置相关
|
||||||
|
loadConfig() {
|
||||||
|
const config = StorageService.getConfig()
|
||||||
|
if (StorageService.isConfigured()) {
|
||||||
|
this.config = config
|
||||||
|
} else {
|
||||||
|
this.showConfigModal = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
saveConfig() {
|
||||||
|
if (this.config.licenseName && this.config.assigneeName) {
|
||||||
|
StorageService.saveConfig(this.config.licenseName, this.config.assigneeName)
|
||||||
|
this.showConfigModal = false
|
||||||
|
Utils.showNotification('配置保存成功')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 数据加载
|
||||||
|
async loadProducts() {
|
||||||
|
try {
|
||||||
|
this.products = await ApiService.getProducts()
|
||||||
|
this.filteredProducts = [...this.products]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载产品列表失败:', error)
|
||||||
|
Utils.showNotification('加载产品列表失败', 'error')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadPlugins() {
|
||||||
|
try {
|
||||||
|
this.plugins = await ApiService.getPlugins()
|
||||||
|
this.filteredPlugins = [...this.plugins]
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载插件列表失败:', error)
|
||||||
|
Utils.showNotification('加载插件列表失败', 'error')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 搜索功能
|
||||||
|
filterItems(query) {
|
||||||
|
const searchTerm = query.toLowerCase().trim()
|
||||||
|
|
||||||
|
if (this.currentPage === 'products') {
|
||||||
|
this.filteredProducts = this.products.filter((product) =>
|
||||||
|
product.name.toLowerCase().includes(searchTerm) ||
|
||||||
|
(product.description && product.description.toLowerCase().includes(searchTerm))
|
||||||
|
)
|
||||||
|
} else if (this.currentPage === 'plugins') {
|
||||||
|
this.filteredPlugins = this.plugins.filter((plugin) =>
|
||||||
|
plugin.name.toLowerCase().includes(searchTerm) ||
|
||||||
|
(plugin.description && plugin.description.toLowerCase().includes(searchTerm))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 选择产品/插件
|
||||||
|
selectProduct(product) {
|
||||||
|
this.selectedItem = product
|
||||||
|
this.showLicenseModal = true
|
||||||
|
},
|
||||||
|
|
||||||
|
selectPlugin(plugin) {
|
||||||
|
this.selectedItem = plugin
|
||||||
|
this.showLicenseModal = true
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生成激活码
|
||||||
|
async generateLicense() {
|
||||||
|
this.isGenerating = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isPlugin = this.selectedItem.hasOwnProperty('id')
|
||||||
|
let result
|
||||||
|
|
||||||
|
if (isPlugin) {
|
||||||
|
result = await ApiService.generatePluginLicense(this.selectedItem.id, this.config.licenseName, this.config.assigneeName, this.licenseConfig.expiryDate)
|
||||||
|
} else {
|
||||||
|
result = await ApiService.generateLicense(this.selectedItem.productCode, this.config.licenseName, this.config.assigneeName, this.licenseConfig.expiryDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.generatedLicense = result
|
||||||
|
this.showLicenseModal = false
|
||||||
|
this.showResultModal = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('生成激活码失败:', error)
|
||||||
|
Utils.showNotification('生成激活码失败,请重试', 'error')
|
||||||
|
} finally {
|
||||||
|
this.isGenerating = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 工具方法
|
||||||
|
downloadAgent() {
|
||||||
|
ApiService.downloadAgent()
|
||||||
|
},
|
||||||
|
|
||||||
|
copyToClipboard(text) {
|
||||||
|
Utils.copyToClipboard(text)
|
||||||
|
},
|
||||||
|
|
||||||
|
setDefaultExpiryDate() {
|
||||||
|
this.licenseConfig.expiryDate = Utils.getDefaultExpiryDate()
|
||||||
|
},
|
||||||
|
|
||||||
|
// 图标处理
|
||||||
|
getProductIcon(product) {
|
||||||
|
if (product.iconClass && product.iconClass.startsWith('icon-')) {
|
||||||
|
const iconName = product.iconClass.replace('icon-', '');
|
||||||
|
|
||||||
|
// 图标映射表
|
||||||
|
const iconMap = {
|
||||||
|
'ii': 'https://resources.jetbrains.com/storage/logos/web/intellij-idea/intellij-idea.svg',
|
||||||
|
'ps': 'https://resources.jetbrains.com/storage/logos/web/phpstorm/phpstorm.svg',
|
||||||
|
'ac': 'https://resources.jetbrains.com/storage/logos/web/appcode/appcode.svg',
|
||||||
|
'db': 'https://resources.jetbrains.com/storage/logos/web/datagrip/datagrip.svg',
|
||||||
|
'rm': 'https://resources.jetbrains.com/storage/logos/web/rubymine/rubymine.svg',
|
||||||
|
'ws': 'https://resources.jetbrains.com/storage/logos/web/webstorm/webstorm.svg',
|
||||||
|
'rd': 'https://resources.jetbrains.com/storage/logos/web/rider/rider.svg',
|
||||||
|
'cl': 'https://resources.jetbrains.com/storage/logos/web/clion/clion.svg',
|
||||||
|
'pc': 'https://resources.jetbrains.com/storage/logos/web/pycharm/pycharm.svg',
|
||||||
|
'go': 'https://resources.jetbrains.com/storage/logos/web/goland/goland.svg',
|
||||||
|
'ds': 'https://resources.jetbrains.com/storage/logos/web/dataspell/dataspell.svg',
|
||||||
|
'dc': 'https://resources.jetbrains.com/storage/logos/web/dotcover/dotcover.svg',
|
||||||
|
'dpn': 'https://resources.jetbrains.com/storage/logos/web/dottrace/dottrace.svg',
|
||||||
|
'dm': 'https://resources.jetbrains.com/storage/logos/web/dotmemory/dotmemory.svg',
|
||||||
|
'rr': 'https://resources.jetbrains.com/storage/logos/web/rustrover/rustrover.svg',
|
||||||
|
'qa': 'https://resources.jetbrains.com/storage/logos/web/aqua/aqua.svg',
|
||||||
|
'al': 'https://resources.jetbrains.com/storage/logos/web/toolbox/toolbox.svg'
|
||||||
|
};
|
||||||
|
|
||||||
|
return iconMap[iconName] || '/images/plugin.svg';
|
||||||
|
}
|
||||||
|
return '/images/plugin.svg';
|
||||||
|
},
|
||||||
|
|
||||||
|
getPluginIcon(plugin) {
|
||||||
|
return plugin.icon || '/images/plugin.svg'
|
||||||
|
},
|
||||||
|
|
||||||
|
// 页面跳转
|
||||||
|
navigateTo(page) {
|
||||||
|
this.currentPage = page
|
||||||
|
this.searchQuery = ''
|
||||||
|
},
|
||||||
|
|
||||||
|
// 返回顶部
|
||||||
|
scrollToTop() {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动应用
|
||||||
|
const app = createApp(App)
|
||||||
|
app.mount('#app')
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
// 工具函数
|
||||||
|
const Utils = {
|
||||||
|
// 生成UUID
|
||||||
|
generateUUID() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = (Math.random() * 16) | 0,
|
||||||
|
v = c == 'x' ? r : (r & 0x3) | 0x8
|
||||||
|
return v.toString(16)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
// 复制到剪贴板
|
||||||
|
async copyToClipboard(text) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
this.showNotification('已复制到剪贴板', 'success')
|
||||||
|
} catch (err) {
|
||||||
|
console.error('复制失败:', err)
|
||||||
|
// 降级方案
|
||||||
|
const textArea = document.createElement('textarea')
|
||||||
|
textArea.value = text
|
||||||
|
document.body.appendChild(textArea)
|
||||||
|
textArea.select()
|
||||||
|
try {
|
||||||
|
document.execCommand('copy')
|
||||||
|
this.showNotification('已复制到剪贴板', 'success')
|
||||||
|
} catch (err) {
|
||||||
|
this.showNotification('复制失败,请手动复制', 'error')
|
||||||
|
}
|
||||||
|
document.body.removeChild(textArea)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 显示通知
|
||||||
|
showNotification(message, type = 'success') {
|
||||||
|
// 移除现有通知
|
||||||
|
const existingNotification = document.querySelector('.notification')
|
||||||
|
if (existingNotification) {
|
||||||
|
existingNotification.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新通知
|
||||||
|
const notification = document.createElement('div')
|
||||||
|
notification.className = `notification ${type}`
|
||||||
|
notification.innerHTML = `
|
||||||
|
<div class="flex items-center">
|
||||||
|
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'} mr-2"></i>
|
||||||
|
<span>${message}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
document.body.appendChild(notification)
|
||||||
|
|
||||||
|
// 3秒后自动移除
|
||||||
|
setTimeout(() => {
|
||||||
|
if (notification.parentNode) {
|
||||||
|
notification.remove()
|
||||||
|
}
|
||||||
|
}, 3000)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化文件大小
|
||||||
|
formatFileSize(bytes) {
|
||||||
|
if (bytes === 0) return '0 B'
|
||||||
|
const k = 1024
|
||||||
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||||
|
},
|
||||||
|
|
||||||
|
// 设置产品图标
|
||||||
|
setProductIcon(element, iconClass) {
|
||||||
|
if (iconClass && iconClass.startsWith('icon-')) {
|
||||||
|
const iconName = iconClass.replace('icon-', '')
|
||||||
|
element.style.backgroundImage = `url('/images/${iconName}.svg')`
|
||||||
|
element.className = 'product-icon mx-auto mb-4 rounded-2xl'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 防抖函数
|
||||||
|
debounce(func, wait) {
|
||||||
|
let timeout
|
||||||
|
return function executedFunction(...args) {
|
||||||
|
const later = () => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
func(...args)
|
||||||
|
}
|
||||||
|
clearTimeout(timeout)
|
||||||
|
timeout = setTimeout(later, wait)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
formatDate(date) {
|
||||||
|
return new Date(date).toLocaleDateString('zh-CN')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 验证日期格式
|
||||||
|
isValidDate(dateString) {
|
||||||
|
const regex = /^\d{4}-\d{2}-\d{2}$/
|
||||||
|
if (!regex.test(dateString)) return false
|
||||||
|
const date = new Date(dateString)
|
||||||
|
return date instanceof Date && !isNaN(date)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取默认过期日期(一年后)
|
||||||
|
getDefaultExpiryDate() {
|
||||||
|
const date = new Date()
|
||||||
|
date.setFullYear(date.getFullYear() + 1)
|
||||||
|
return date.toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API 服务
|
||||||
|
const ApiService = {
|
||||||
|
baseURL: window.location.origin,
|
||||||
|
|
||||||
|
async get(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.baseURL}${url}`)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
return await response.json()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API GET error:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getText(url) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${this.baseURL}${url}`)
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
return await response.text()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('API GET text error:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取产品列表
|
||||||
|
async getProducts() {
|
||||||
|
return await this.get('/api/products')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取插件列表
|
||||||
|
async getPlugins() {
|
||||||
|
return await this.get('/api/plugins')
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生成产品激活码
|
||||||
|
async generateLicense(productCode, licenseeName, assigneeName, expiryDate) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
...(productCode && { productCode }),
|
||||||
|
licenseeName,
|
||||||
|
assigneeName,
|
||||||
|
expiryDate
|
||||||
|
})
|
||||||
|
return await this.getText(`/license-code/generate?${params}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 生成插件激活码
|
||||||
|
async generatePluginLicense(pluginId, licenseeName, assigneeName, expiryDate) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
pluginId,
|
||||||
|
licenseeName,
|
||||||
|
assigneeName,
|
||||||
|
expiryDate
|
||||||
|
})
|
||||||
|
return await this.getText(`/license-code/generate-plugin?${params}`)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 下载代理工具
|
||||||
|
downloadAgent() {
|
||||||
|
window.open('/ja-netfilter', '_blank')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 存储服务
|
||||||
|
const StorageService = {
|
||||||
|
// 保存配置
|
||||||
|
saveConfig(licenseName, assigneeName) {
|
||||||
|
localStorage.setItem('licenseName', licenseName)
|
||||||
|
localStorage.setItem('assigneeName', assigneeName)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取配置
|
||||||
|
getConfig() {
|
||||||
|
return {
|
||||||
|
licenseName: localStorage.getItem('licenseName') || '',
|
||||||
|
assigneeName: localStorage.getItem('assigneeName') || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 检查是否已配置
|
||||||
|
isConfigured() {
|
||||||
|
const config = this.getConfig()
|
||||||
|
return config.licenseName && config.assigneeName
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清除配置
|
||||||
|
clearConfig() {
|
||||||
|
localStorage.removeItem('licenseName')
|
||||||
|
localStorage.removeItem('assigneeName')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.qiumo.help;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class QiuMoJetbrainsHelpApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||