├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── build-with-docker.sh
├── docker-compose.yml
├── lombok.config
├── pom.xml
├── run-with-docker-compose.sh
├── run-with-docker.sh
└── src
└── main
├── java
└── com
│ └── jetbrains
│ └── help
│ ├── JetbrainsHelpApplication.java
│ ├── context
│ ├── AgentContextHolder.java
│ ├── CertificateContextHolder.java
│ ├── LicenseContextHolder.java
│ ├── PluginsContextHolder.java
│ └── ProductsContextHolder.java
│ ├── controller
│ └── OpenApiController.java
│ ├── properties
│ └── JetbrainsHelpProperties.java
│ ├── route
│ └── IndexController.java
│ └── util
│ └── FileTools.java
└── resources
├── application.yml
├── banner.txt
├── external
├── agent
│ └── ja-netfilter.zip
├── certificate
│ └── root.key
└── data
│ ├── plugin.json
│ └── product.json
├── static
├── css
│ └── index.css
├── images
│ ├── aqua.svg
│ ├── clion.svg
│ ├── datagrip.svg
│ ├── dataspell.svg
│ ├── dotcover.svg
│ ├── dotmemory.svg
│ ├── dottrace.svg
│ ├── goland.svg
│ ├── icons.svg
│ ├── intellij-idea.svg
│ ├── phpstorm.svg
│ ├── plugin.svg
│ ├── pycharm.svg
│ ├── rider.svg
│ ├── rubymine.svg
│ ├── rustrover.svg
│ └── webstorm.svg
└── js
│ ├── index.js
│ └── jquery.js
└── templates
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 | ### Java template
35 | # Compiled class file
36 | *.class
37 |
38 | # Log file
39 | *.log
40 |
41 | # BlueJ files
42 | *.ctxt
43 |
44 | # Mobile Tools for Java (J2ME)
45 | .mtj.tmp/
46 |
47 | # Package Files #
48 | *.jar
49 | *.war
50 | *.nar
51 | *.ear
52 | *.zip
53 | *.tar.gz
54 | *.rar
55 |
56 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
57 | hs_err_pid*
58 | replay_pid*
59 |
60 | ### Maven template
61 | target/
62 | pom.xml.tag
63 | pom.xml.releaseBackup
64 | pom.xml.versionsBackup
65 | pom.xml.next
66 | release.properties
67 | dependency-reduced-pom.xml
68 | buildNumber.properties
69 | .mvn/timing.properties
70 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar
71 | .mvn/wrapper/maven-wrapper.jar
72 |
73 | # Eclipse m2e generated files
74 | # Eclipse Core
75 | .project
76 | # JDT-specific (Eclipse Java Development Tools)
77 | .classpath
78 |
79 | ### TortoiseGit template
80 | # Project-level settings
81 | /.tgitconfig
82 |
83 | ### VisualStudioCode template
84 | .vscode/*
85 | !.vscode/settings.json
86 | !.vscode/tasks.json
87 | !.vscode/launch.json
88 | !.vscode/extensions.json
89 | !.vscode/*.code-snippets
90 |
91 | # Local History for Visual Studio Code
92 | .history/
93 |
94 | # Built Visual Studio Code Extensions
95 | *.vsix
96 |
97 | ### JetBrains template
98 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
99 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
100 |
101 | # User-specific stuff
102 | .idea/**/workspace.xml
103 | .idea/**/tasks.xml
104 | .idea/**/usage.statistics.xml
105 | .idea/**/dictionaries
106 | .idea/**/shelf
107 |
108 | # AWS User-specific
109 | .idea/**/aws.xml
110 |
111 | # Generated files
112 | .idea/**/contentModel.xml
113 |
114 | # Sensitive or high-churn files
115 | .idea/**/dataSources/
116 | .idea/**/dataSources.ids
117 | .idea/**/dataSources.local.xml
118 | .idea/**/sqlDataSources.xml
119 | .idea/**/dynamic.xml
120 | .idea/**/uiDesigner.xml
121 | .idea/**/dbnavigator.xml
122 |
123 | # Gradle
124 | .idea/**/gradle.xml
125 | .idea/**/libraries
126 |
127 | # Gradle and Maven with auto-import
128 | # When using Gradle or Maven with auto-import, you should exclude module files,
129 | # since they will be recreated, and may cause churn. Uncomment if using
130 | # auto-import.
131 | # .idea/artifacts
132 | # .idea/compiler.xml
133 | # .idea/jarRepositories.xml
134 | # .idea/modules.xml
135 | # .idea/*.iml
136 | # .idea/modules
137 | # *.iml
138 | # *.ipr
139 |
140 | # CMake
141 | cmake-build-*/
142 |
143 | # Mongo Explorer plugin
144 | .idea/**/mongoSettings.xml
145 |
146 | # File-based project format
147 | *.iws
148 |
149 | # IntelliJ
150 | out/
151 |
152 | # mpeltonen/sbt-idea plugin
153 | .idea_modules/
154 |
155 | # JIRA plugin
156 | atlassian-ide-plugin.xml
157 |
158 | # Cursive Clojure plugin
159 | .idea/replstate.xml
160 |
161 | # SonarLint plugin
162 | .idea/sonarlint/
163 |
164 | # Crashlytics plugin (for Android Studio and IntelliJ)
165 | com_crashlytics_export_strings.xml
166 | crashlytics.properties
167 | crashlytics-build.properties
168 | fabric.properties
169 |
170 | # Editor-based Rest Client
171 | .idea/httpRequests
172 |
173 | # Android studio 3.1+ serialized cache file
174 | .idea/caches/build_file_checksums.ser
175 |
176 | ### Xcode template
177 | ## User settings
178 | xcuserdata/
179 |
180 | ## Xcode 8 and earlier
181 | *.xcscmblueprint
182 | *.xccheckout
183 |
184 | ### Eclipse template
185 | .metadata
186 | bin/
187 | tmp/
188 | *.tmp
189 | *.bak
190 | *.swp
191 | *~.nib
192 | local.properties
193 | .settings/
194 | .loadpath
195 | .recommenders
196 |
197 | # External tool builders
198 | .externalToolBuilders/
199 |
200 | # Locally stored "Eclipse launch configurations"
201 | *.launch
202 |
203 | # PyDev specific (Python IDE for Eclipse)
204 | *.pydevproject
205 |
206 | # CDT-specific (C/C++ Development Tooling)
207 | .cproject
208 |
209 | # CDT- autotools
210 | .autotools
211 |
212 | # Java annotation processor (APT)
213 | .factorypath
214 |
215 | # PDT-specific (PHP Development Tools)
216 | .buildpath
217 |
218 | # sbteclipse plugin
219 | .target
220 |
221 | # Tern plugin
222 | .tern-project
223 |
224 | # TeXlipse plugin
225 | .texlipse
226 |
227 | # STS (Spring Tool Suite)
228 | .springBeans
229 |
230 | # Code Recommenders
231 | .recommenders/
232 |
233 | # Annotation Processing
234 | .apt_generated/
235 | .apt_generated_test/
236 |
237 | # Scala IDE specific (Scala & Java development for Eclipse)
238 | .cache-main
239 | .scala_dependencies
240 | .worksheet
241 |
242 | # Uncomment this line if you wish to ignore the project description file.
243 | # Typically, this file would be tracked if it contains build/dependency configurations:
244 | #.project
245 |
246 | ### SVN template
247 | .svn/
248 |
249 | ### Vim template
250 | # Swap
251 | [._]*.s[a-v][a-z]
252 | !*.svg # comment out if you don't need vector files
253 | [._]*.sw[a-p]
254 | [._]s[a-rt-v][a-z]
255 | [._]ss[a-gi-z]
256 | [._]sw[a-p]
257 |
258 | # Session
259 | Session.vim
260 | Sessionx.vim
261 |
262 | # Temporary
263 | .netrwhist
264 | *~
265 | # Auto-generated tag files
266 | tags
267 | # Persistent undo
268 | [._]*.un~
269 |
270 | ### Windows template
271 | # Windows thumbnail cache files
272 | Thumbs.db
273 | Thumbs.db:encryptable
274 | ehthumbs.db
275 | ehthumbs_vista.db
276 |
277 | # Dump file
278 | *.stackdump
279 |
280 | # Folder config file
281 | [Dd]esktop.ini
282 |
283 | # Recycle Bin used on file shares
284 | $RECYCLE.BIN/
285 |
286 | # Windows Installer files
287 | *.cab
288 | *.msi
289 | *.msix
290 | *.msm
291 | *.msp
292 |
293 | # Windows shortcuts
294 | *.lnk
295 |
296 | ### macOS template
297 | # General
298 | .DS_Store
299 | .AppleDouble
300 | .LSOverride
301 |
302 | # Icon must end with two \r
303 | Icon
304 |
305 | # Thumbnails
306 | ._*
307 |
308 | # Files that might appear in the root of a volume
309 | .DocumentRevisions-V100
310 | .fseventsd
311 | .Spotlight-V100
312 | .TemporaryItems
313 | .Trashes
314 | .VolumeIcon.icns
315 | .com.apple.timemachine.donotpresent
316 |
317 | # Directories potentially created on remote AFP share
318 | .AppleDB
319 | .AppleDesktop
320 | Network Trash Folder
321 | Temporary Items
322 | .apdisk
323 |
324 | ### SublimeText template
325 | # Cache files for Sublime Text
326 | *.tmlanguage.cache
327 | *.tmPreferences.cache
328 | *.stTheme.cache
329 |
330 | # Workspace files are user-specific
331 | *.sublime-workspace
332 |
333 | # Project files should be checked into the repository, unless a significant
334 | # proportion of contributors will probably not be using Sublime Text
335 | # *.sublime-project
336 |
337 | # SFTP configuration file
338 | sftp-config.json
339 | sftp-config-alt*.json
340 |
341 | # Package control specific files
342 | Package Control.last-run
343 | Package Control.ca-list
344 | Package Control.ca-bundle
345 | Package Control.system-ca-bundle
346 | Package Control.cache/
347 | Package Control.ca-certs/
348 | Package Control.merged-ca-bundle
349 | Package Control.user-ca-bundle
350 | oscrypto-ca-bundle.crt
351 | bh_unicode_properties.cache
352 |
353 | # Sublime-github package stores a github token in this file
354 | # https://packagecontrol.io/packages/sublime-github
355 | GitHub.sublime-settings
356 |
357 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM maven:3-ibm-semeru-21-jammy as build
2 | WORKDIR /app
3 | COPY . .
4 | RUN mvn clean package
5 |
6 | FROM ibm-semeru-runtimes:open-21-jre
7 | WORKDIR /app
8 | COPY --from=build /app/target/Jetbrains-Help.jar Jetbrains-Help.jar
9 | ENV TZ=Asia/Shanghai
10 | RUN ln -sf /usr/share/zoneinfo/{TZ} /etc/localtime && echo "{TZ}" > /etc/timezone
11 | EXPOSE 10768
12 | ENTRYPOINT ["java", "-jar", "Jetbrains-Help.jar"]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 NotoChen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Jetbrains-Help
2 |
3 | ## 目录
4 | - [项目说明](#项目说明)
5 | - [仓库简要](#仓库简要)
6 | - [仓库趋势](#仓库趋势)
7 | - [支持版本](#支持版本)
8 | - [项目版本](#项目版本)
9 | - [功能列表](#功能列表)
10 | - [运行教程](#运行教程)
11 | - [拉取项目](#拉取项目)
12 | - [配置环境](#配置环境)
13 | - [本地运行](#本地运行)
14 | - [容器运行](#容器运行)
15 | - [运行服务](#运行服务)
16 | - [本地运行](#本地运行)
17 | - [有IDE](#有IDE)
18 | - [无IDE](#无IDE)
19 | - [容器运行](#容器运行)
20 | - [使用教程](#使用教程)
21 | - [下载依赖](#下载依赖)
22 | - [依赖配置](#依赖配置)
23 | - [可打开IDE](#可打开IDE)
24 | - [不可打开IDE](#可打开IDE)
25 |
26 | ## 项目说明
27 | ### 仓库简要
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | ### 仓库趋势
36 |
37 |
38 |
39 |
40 |
41 | ### 支持版本
42 |
43 |
44 |
45 |
46 | ### 项目版本
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | ### 功能列表
55 |
56 | | 功能 | DID |
57 | |:-------------------------|:---:|
58 | | Jetbrains全产品支持 | ✅ |
59 | | Jetbrains全插件支持 | ✅ |
60 | | 插件库全自动订阅官网更新 | ✅ |
61 | | 公私钥/证书, 自动生成管理 | ✅ |
62 | | power.conf文件自动配置 | ✅ |
63 | | ja-netfilter.zip自动打包 | ✅ |
64 | | 自定义License Show | ✅ |
65 | | 支持实时搜索 | ✅ |
66 | | 插件默认按名称排序 | ✅ |
67 | | 支持local/jar/dockerfile运行 | ✅ |
68 | | 单码全家桶激活支持 | ✅ |
69 | | …… | ☑️ |
70 |
71 | ## 运行教程
72 |
73 | > 以下是该项目详细运行教程, 尽量争取可以在各个环境下运作
74 |
75 | ### 拉取项目
76 |
77 | `clone` 本项目至本地
78 |
79 | ### 配置环境
80 |
81 | #### 本地运行
82 |
83 | 1. 需要 `Java` 环境,并且版本要求 **21**
84 | 2. 需要 `Maven` 环境,版本无要求,但建议采用最新版
85 |
86 | #### 容器运行
87 | 1. 需要 `Docker` 环境,版本无要求,但建议采用最新版
88 | 2. 如有 `Docker-Compos` 环境,更佳,但此环境**非必须**
89 |
90 | ### 运行服务
91 |
92 | #### 本地运行
93 |
94 | ##### 有IDE
95 |
96 | 1. 通过 `IDE` `Open` 项目
97 | 2. 配置项目相关环境
98 | 3. 运行 [JetbrainsHelpApplication.java](src%2Fmain%2Fjava%2Fcom%2Fjetbrains%2Fhelp%2FJetbrainsHelpApplication.java)
99 |
100 | ##### 无IDE
101 |
102 | 1. 系统终端 `Cd` 进入项目根目录
103 | 2. 运行打包命令 `mvn clean package`
104 | 3. 运行启动命令 `java -jar target/Jetbrains-Help.jar`
105 |
106 | #### 容器运行
107 |
108 | 1. 系统终端 `Cd` 进入项目根目录
109 |
110 | ##### 使用Docker
111 | 2. 运行 `Docker` 命令 `docker build -t jetbrains-help .`
112 | 3. **或者** 执行 [build-with-docker.sh](build-with-docker.sh)
113 | 4. 运行 `Docker` 命令 `docker run -d -p 10768:10768 --name jetbrains-help jetbrains-help`
114 | 5. **或者** 执行 [run-with-docker.sh](run-with-docker.sh)
115 |
116 | ##### 使用Docker-Compose
117 |
118 | 2. 运行 `Docker-Compose` 命令 `docker compose build && docker compose up -d`
119 | 3. **或者** 执行 [run-with-docker-compose.sh](run-with-docker-compose.sh)
120 |
121 | ### 使用教程
122 |
123 | 项目运行后, `Console` 会打印相关服务地址, 默认端口为 `10768`, 默认地址为 `127.0.0.1:10768`
124 |
125 | 可以点此直接访问 [Jetbrains-Help](http://127.0.0.1:10768)
126 |
127 | #### 下载依赖
128 |
129 | 阅读 **页面头部**,根据头部指引下载 `ja-netfilter.zip`
130 |
131 | 移动本地 `ja-netfilter.zip` 到自定义目录,**解压**
132 |
133 | #### 依赖配置
134 |
135 | ##### 可打开IDE
136 |
137 | - `进入IDE`
138 | - **点击** 菜单栏 `帮助(help)`
139 | - **点击** `编辑自定义虚拟机选型`
140 | - **键入** 如下配置
141 | ```
142 | -javaagent:you-path/ja-netfilter.jar
143 | --add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED
144 | --add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED
145 | ```
146 | - 将`you-path`替换为 [下载依赖](#下载依赖) 步骤中自定义目录
147 | - **重启** `IDE`
148 |
149 | ##### 不可打开IDE
150 |
151 | - **下载安装** [Toolbox](https://www.jetbrains.com/toolbox-app/)
152 | - **启动** `Toolbox`
153 | - **点击** `Toolbox` 找到对应 `IDE`
154 | - **点击** `IDE` 右侧的 `⋮`
155 | - **点击** `设置`
156 | - 找到 `配置` 选项
157 | - **点击** `编辑JVM选项`
158 | - **键入** 如下配置
159 | ```
160 | -javaagent:you-path/ja-netfilter.jar
161 | --add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED
162 | --add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED
163 | ```
164 | - 将`you-path`替换为 [下载依赖](#下载依赖) 步骤中自定义目录
165 | - **重启** `IDE`
166 |
167 |
--------------------------------------------------------------------------------
/build-with-docker.sh:
--------------------------------------------------------------------------------
1 | docker build -t jetbrains-help .
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 | jetbrains-help:
3 | build: .
4 | image: jetbrains-help:latest
5 | container_name: Jetbrains-Help
6 | ports:
7 | - 10768:10768
8 | volumes:
9 | - ./external:/external
--------------------------------------------------------------------------------
/lombok.config:
--------------------------------------------------------------------------------
1 | ## 以当前目录为根目录
2 | config.stopBubbling=true
3 |
4 | ## 默认以链式生成Setter
5 | lombok.accessors.chain=true
6 |
7 | ## 默认ToString包含超类
8 | lombok.tostring.callsuper=call
9 |
10 | ## 默认比较器包含超类
11 | lombok.equalsandhashcode.callsuper=call
12 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 3.2.4
9 |
10 |
11 | com.jetbrains.help
12 | Jetbrains-Help
13 | 0.0.1-SNAPSHOT
14 | Jetbrains-Help
15 | Jetbrains-Help
16 |
17 | 21
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter-thymeleaf
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-web
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-configuration-processor
31 | true
32 |
33 |
34 | org.projectlombok
35 | lombok
36 | true
37 |
38 |
39 |
40 | cn.hutool
41 | hutool-all
42 | 5.8.26
43 |
44 |
45 |
46 | org.bouncycastle
47 | bcpkix-jdk18on
48 | 1.78
49 |
50 |
51 | org.bouncycastle
52 | bcprov-jdk18on
53 | 1.78
54 |
55 |
56 |
57 |
58 | ${artifactId}
59 |
60 |
61 | org.springframework.boot
62 | spring-boot-maven-plugin
63 |
64 |
65 |
66 | org.projectlombok
67 | lombok
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/run-with-docker-compose.sh:
--------------------------------------------------------------------------------
1 | docker compose build && docker compose up -d
--------------------------------------------------------------------------------
/run-with-docker.sh:
--------------------------------------------------------------------------------
1 | docker run -d -p 10768:10768 --name jetbrains-help jetbrains-help
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/JetbrainsHelpApplication.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help;
2 |
3 | import cn.hutool.core.text.CharSequenceUtil;
4 | import cn.hutool.core.thread.ThreadUtil;
5 | import cn.hutool.extra.spring.SpringUtil;
6 | import com.jetbrains.help.context.*;
7 | import lombok.SneakyThrows;
8 | import lombok.extern.slf4j.Slf4j;
9 | import org.springframework.boot.SpringApplication;
10 | import org.springframework.boot.autoconfigure.SpringBootApplication;
11 | import org.springframework.boot.context.event.ApplicationReadyEvent;
12 | import org.springframework.context.annotation.Import;
13 | import org.springframework.context.event.EventListener;
14 | import org.springframework.scheduling.annotation.*;
15 |
16 | import java.net.InetAddress;
17 |
18 | @Slf4j(topic = "源项目入口")
19 | @EnableScheduling
20 | @Import(SpringUtil.class)
21 | @SpringBootApplication
22 | public class JetbrainsHelpApplication {
23 |
24 | public static void main(String[] args) {
25 | SpringApplication.run(JetbrainsHelpApplication.class, args);
26 | }
27 |
28 | @SneakyThrows
29 | @EventListener(ApplicationReadyEvent.class)
30 | public void ready() {
31 | ProductsContextHolder.init();
32 | PluginsContextHolder.init();
33 | CertificateContextHolder.init();
34 | AgentContextHolder.init();
35 |
36 | InetAddress localHost = InetAddress.getLocalHost();
37 | String address = CharSequenceUtil.format("http://{}:{}", localHost.getHostAddress(), SpringUtil.getProperty("server.port"));
38 | String runSuccessWarn = "\n====================================================================================\n" +
39 | "= Jetbrains-Help 启动成功~ =\n" +
40 | "= 访问地址:" + address + " =\n" +
41 | "====================================================================================\n";
42 | log.info(runSuccessWarn);
43 | }
44 |
45 | @Scheduled(cron = "0 0 12 * * ?")
46 | public void refresh() {
47 | ThreadUtil.execute(PluginsContextHolder::refreshJsonFile);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/context/AgentContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.context;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.core.io.IORuntimeException;
5 | import cn.hutool.core.io.IoUtil;
6 | import cn.hutool.core.text.CharSequenceUtil;
7 | import cn.hutool.core.util.ZipUtil;
8 | import cn.hutool.crypto.KeyUtil;
9 | import cn.hutool.crypto.PemUtil;
10 | import com.jetbrains.help.util.FileTools;
11 | import lombok.AccessLevel;
12 | import lombok.NoArgsConstructor;
13 | import lombok.SneakyThrows;
14 | import lombok.extern.slf4j.Slf4j;
15 |
16 | import java.io.File;
17 | import java.math.BigInteger;
18 | import java.nio.charset.StandardCharsets;
19 | import java.security.cert.X509Certificate;
20 | import java.security.interfaces.RSAPublicKey;
21 | import java.util.concurrent.CompletableFuture;
22 |
23 | @Slf4j(topic = "代理上下文")
24 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
25 | public class AgentContextHolder {
26 |
27 | private static final String JA_NETFILTER_FILE_PATH = "external/agent/ja-netfilter";
28 |
29 | private static final String POWER_CONF_FILE_NAME = JA_NETFILTER_FILE_PATH + "/config/power.conf";
30 |
31 | private static File jaNetfilterFile;
32 |
33 | private static File jaNetfilterZipFile;
34 |
35 | public static void init() {
36 | log.info("初始化中...");
37 | jaNetfilterZipFile = FileTools.getFileOrCreat(JA_NETFILTER_FILE_PATH + ".zip");
38 | if (!FileTools.fileExists(JA_NETFILTER_FILE_PATH)) {
39 | unzipJaNetfilter();
40 | if (!powerConfHasInit()) {
41 | log.info("配置初始化中...");
42 | loadPowerConf();
43 | zipJaNetfilter();
44 | log.info("配置初始化成功!");
45 | }
46 | }
47 | log.info("初始化成功!");
48 | }
49 |
50 | public static File jaNetfilterZipFile() {
51 | return AgentContextHolder.jaNetfilterZipFile;
52 | }
53 |
54 | private static boolean powerConfHasInit() {
55 | File powerConfFile = FileTools.getFileOrCreat(POWER_CONF_FILE_NAME);
56 | String powerConfStr;
57 | try {
58 | powerConfStr = IoUtil.readUtf8(FileUtil.getInputStream(powerConfFile));
59 | } catch (IORuntimeException e) {
60 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件读取失败!", POWER_CONF_FILE_NAME), e);
61 | }
62 | return CharSequenceUtil.containsAll(powerConfStr, "[Result]", "EQUAL,");
63 | }
64 |
65 | private static void loadPowerConf() {
66 | CompletableFuture
67 | .supplyAsync(AgentContextHolder::generatePowerConfigRule)
68 | .thenApply(AgentContextHolder::generatePowerConfigStr)
69 | .thenAccept(AgentContextHolder::overridePowerConfFileContent)
70 | .exceptionally(throwable -> {
71 | log.error("配置初始化失败!", throwable);
72 | return null;
73 | }).join();
74 | }
75 |
76 | @SneakyThrows
77 | private static String generatePowerConfigRule() {
78 | X509Certificate crt = (X509Certificate) KeyUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.crtFile()));
79 | RSAPublicKey publicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.publicKeyFile()));
80 | RSAPublicKey rootPublicKey = (RSAPublicKey) PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.rootKeyFile()));
81 | BigInteger x = new BigInteger(1, crt.getSignature());
82 | BigInteger y = BigInteger.valueOf(65537L);
83 | BigInteger z = rootPublicKey.getModulus();
84 | BigInteger r = x.modPow(publicKey.getPublicExponent(), publicKey.getModulus());
85 | return CharSequenceUtil.format("EQUAL,{},{},{}->{}", x, y, z, r);
86 | }
87 |
88 | private static String generatePowerConfigStr(String ruleValue) {
89 | return CharSequenceUtil.builder("[Result]", "\n", ruleValue).toString();
90 | }
91 |
92 | private static void overridePowerConfFileContent(String configStr) {
93 | File powerConfFile = FileTools.getFileOrCreat(POWER_CONF_FILE_NAME);
94 | try {
95 | FileUtil.writeString(configStr, powerConfFile, StandardCharsets.UTF_8);
96 | } catch (IORuntimeException e) {
97 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件写入失败!", POWER_CONF_FILE_NAME), e);
98 | }
99 | }
100 |
101 | private static void unzipJaNetfilter() {
102 | jaNetfilterFile = ZipUtil.unzip(jaNetfilterZipFile);
103 | }
104 |
105 | private static void zipJaNetfilter() {
106 | jaNetfilterZipFile = ZipUtil.zip(jaNetfilterFile);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/context/CertificateContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.context;
2 |
3 | import cn.hutool.core.date.DateField;
4 | import cn.hutool.core.date.DateUtil;
5 | import cn.hutool.core.io.FileUtil;
6 | import cn.hutool.crypto.PemUtil;
7 | import cn.hutool.crypto.SecureUtil;
8 | import com.jetbrains.help.util.FileTools;
9 | import lombok.AccessLevel;
10 | import lombok.NoArgsConstructor;
11 | import lombok.extern.slf4j.Slf4j;
12 | import org.bouncycastle.asn1.x500.X500Name;
13 | import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
14 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
15 | import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
16 | import org.bouncycastle.operator.ContentSigner;
17 | import org.bouncycastle.operator.OperatorCreationException;
18 | import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
19 |
20 | import java.io.File;
21 | import java.math.BigInteger;
22 | import java.nio.charset.StandardCharsets;
23 | import java.security.KeyPair;
24 | import java.security.PrivateKey;
25 | import java.security.PublicKey;
26 | import java.security.cert.Certificate;
27 | import java.security.cert.CertificateEncodingException;
28 | import java.security.cert.CertificateException;
29 |
30 | @Slf4j(topic = "证书上下文")
31 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
32 | public class CertificateContextHolder {
33 |
34 | private static final String ROOT_KEY_FILE_NAME = "external/certificate/root.key";
35 | private static final String PRIVATE_KEY_FILE_NAME = "external/certificate/private.key";
36 | private static final String PUBLIC_KEY_FILE_NAME = "external/certificate/public.key";
37 | private static final String CET_FILE_NAME = "external/certificate/ca.crt";
38 |
39 | private static File rootKeyFile;
40 |
41 | private static File privateKeyFile;
42 |
43 | private static File publicKeyFile;
44 |
45 | private static File crtFile;
46 |
47 | public static void init() {
48 | log.info("初始化中...");
49 | rootKeyFile = FileTools.getFileOrCreat(ROOT_KEY_FILE_NAME);
50 | if (!FileTools.fileExists(PRIVATE_KEY_FILE_NAME)
51 | || !FileTools.fileExists(PUBLIC_KEY_FILE_NAME)
52 | || !FileTools.fileExists(CET_FILE_NAME)) {
53 | log.info("证书生成中...");
54 | generateCertificate();
55 | log.info("证书生成成功!");
56 | } else {
57 | privateKeyFile = FileTools.getFileOrCreat(PRIVATE_KEY_FILE_NAME);
58 | publicKeyFile = FileTools.getFileOrCreat(PUBLIC_KEY_FILE_NAME);
59 | crtFile = FileTools.getFileOrCreat(CET_FILE_NAME);
60 | }
61 | log.info("初始化成功!");
62 | }
63 |
64 |
65 | public static File rootKeyFile() {
66 | return CertificateContextHolder.rootKeyFile;
67 | }
68 |
69 | public static File privateKeyFile() {
70 | return CertificateContextHolder.privateKeyFile;
71 | }
72 |
73 | public static File publicKeyFile() {
74 | return CertificateContextHolder.publicKeyFile;
75 | }
76 |
77 | public static File crtFile() {
78 | return CertificateContextHolder.crtFile;
79 | }
80 |
81 | public static void generateCertificate() {
82 | KeyPair keyPair = SecureUtil.generateKeyPair("RSA", 4096);
83 | PrivateKey privateKey = keyPair.getPrivate();
84 | PublicKey publicKey = keyPair.getPublic();
85 | privateKeyFile = FileTools.getFileOrCreat(PRIVATE_KEY_FILE_NAME);
86 | PemUtil.writePemObject("PRIVATE KEY", privateKey.getEncoded(), FileUtil.getWriter(privateKeyFile, StandardCharsets.UTF_8, false));
87 | publicKeyFile = FileTools.getFileOrCreat(PUBLIC_KEY_FILE_NAME);
88 | PemUtil.writePemObject("PUBLIC KEY", publicKey.getEncoded(), FileUtil.getWriter(publicKeyFile, StandardCharsets.UTF_8, false));
89 | JcaX509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
90 | new X500Name("CN=JetProfile CA"),
91 | BigInteger.valueOf(System.currentTimeMillis()),
92 | DateUtil.yesterday(),
93 | DateUtil.date().offset(DateField.YEAR, 100),
94 | new X500Name("CN=Jetbrains-Help"),
95 | SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()));
96 | try {
97 | ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(privateKey);
98 | Certificate certificate = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certificateBuilder.build(signer));
99 | crtFile = FileTools.getFileOrCreat(CET_FILE_NAME);
100 | PemUtil.writePemObject("CERTIFICATE", certificate.getEncoded(), FileUtil.getWriter(crtFile, StandardCharsets.UTF_8, false));
101 | } catch (OperatorCreationException e) {
102 | throw new IllegalArgumentException("证书运算符创建异常!", e);
103 | } catch (CertificateEncodingException e) {
104 | throw new IllegalArgumentException("证书编码异常", e);
105 | } catch (CertificateException e) {
106 | throw new IllegalArgumentException("证书读取异常", e);
107 | }
108 | }
109 |
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/context/LicenseContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.context;
2 |
3 | import cn.hutool.core.codec.Base64;
4 | import cn.hutool.core.collection.CollUtil;
5 | import cn.hutool.core.io.IoUtil;
6 | import cn.hutool.core.text.CharSequenceUtil;
7 | import cn.hutool.core.util.IdUtil;
8 | import cn.hutool.crypto.PemUtil;
9 | import cn.hutool.crypto.SecureUtil;
10 | import cn.hutool.crypto.SignUtil;
11 | import cn.hutool.crypto.asymmetric.Sign;
12 | import cn.hutool.json.JSONUtil;
13 | import lombok.AccessLevel;
14 | import lombok.Data;
15 | import lombok.NoArgsConstructor;
16 | import lombok.extern.slf4j.Slf4j;
17 |
18 | import java.security.PrivateKey;
19 | import java.security.PublicKey;
20 | import java.security.cert.Certificate;
21 | import java.security.cert.CertificateEncodingException;
22 | import java.util.List;
23 | import java.util.Set;
24 |
25 | import static cn.hutool.crypto.asymmetric.SignAlgorithm.SHA1withRSA;
26 |
27 | @Slf4j(topic = "授权上下文")
28 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
29 | public class LicenseContextHolder {
30 |
31 | public static String generateLicense(String licensesName, String assigneeName, String expiryDate, Set productCodeSet) {
32 | String licenseId = IdUtil.fastSimpleUUID();
33 | List products = productCodeSet.stream()
34 | .map(productCode -> new Product()
35 | .setCode(productCode)
36 | .setFallbackDate(expiryDate)
37 | .setPaidUpTo(expiryDate))
38 | .toList();
39 | LicensePart licensePart = new LicensePart()
40 | .setLicenseId(licenseId)
41 | .setLicenseeName(licensesName)
42 | .setAssigneeName(assigneeName)
43 | .setProducts(products);
44 | String licensePartJson = JSONUtil.toJsonStr(licensePart);
45 | String licensePartBase64 = Base64.encode(licensePartJson);
46 | PrivateKey privateKey = PemUtil.readPemPrivateKey(IoUtil.toStream(CertificateContextHolder.privateKeyFile()));
47 | PublicKey publicKey = PemUtil.readPemPublicKey(IoUtil.toStream(CertificateContextHolder.publicKeyFile()));
48 | Certificate certificate = SecureUtil.readX509Certificate(IoUtil.toStream(CertificateContextHolder.crtFile()));
49 | Sign sign = SignUtil.sign(SHA1withRSA, privateKey.getEncoded(), publicKey.getEncoded());
50 | String signatureBase64 = Base64.encode(sign.sign(licensePartJson));
51 | String certBase64;
52 | try {
53 | certBase64 = Base64.encode(certificate.getEncoded());
54 | } catch (CertificateEncodingException e) {
55 | throw new IllegalArgumentException("证书编码异常", e);
56 | }
57 | return CharSequenceUtil.format("{}-{}-{}-{}", licenseId, licensePartBase64, signatureBase64, certBase64);
58 | }
59 |
60 | @Data
61 | public static class LicensePart {
62 |
63 | private String licenseId;
64 | private String licenseeName;
65 | private String assigneeName;
66 | private List products;
67 | private String metadata = "0120230914PSAX000005";
68 | }
69 |
70 | @Data
71 | public static class Product {
72 | private String code;
73 | private String fallbackDate;
74 | private String paidUpTo;
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/context/PluginsContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.context;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.core.io.IORuntimeException;
5 | import cn.hutool.core.io.IoUtil;
6 | import cn.hutool.core.text.CharSequenceUtil;
7 | import cn.hutool.core.util.StrUtil;
8 | import cn.hutool.http.HttpUtil;
9 | import cn.hutool.json.JSONUtil;
10 | import com.jetbrains.help.util.FileTools;
11 | import lombok.AccessLevel;
12 | import lombok.Data;
13 | import lombok.NoArgsConstructor;
14 | import lombok.extern.slf4j.Slf4j;
15 |
16 | import java.io.File;
17 | import java.io.IOException;
18 | import java.io.InputStream;
19 | import java.nio.charset.StandardCharsets;
20 | import java.util.*;
21 | import java.util.concurrent.CompletableFuture;
22 |
23 | @Slf4j(topic = "插件上下文")
24 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
25 | public class PluginsContextHolder {
26 |
27 | private static final String PLUGIN_BASIC_URL = "https://plugins.jetbrains.com";
28 |
29 | private static final String PLUGIN_LIST_URL = PLUGIN_BASIC_URL + "/api/searchPlugins?max=10000&offset=0&orderBy=name";
30 |
31 | private static final String PLUGIN_INFO_URL = PLUGIN_BASIC_URL + "/api/plugins/";
32 |
33 | private static final String PLUGIN_JSON_FILE_NAME = "external/data/plugin.json";
34 |
35 | private static List pluginCacheList;
36 |
37 | private static File pluginsJsonFile;
38 |
39 | public static void init() {
40 | log.info("初始化中...");
41 | pluginsJsonFile = FileTools.getFileOrCreat(PLUGIN_JSON_FILE_NAME);
42 |
43 | String pluginJsonArray;
44 | try {
45 | pluginJsonArray = IoUtil.readUtf8(FileUtil.getInputStream(pluginsJsonFile));
46 | } catch (IORuntimeException e) {
47 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件读取失败!", PLUGIN_JSON_FILE_NAME), e);
48 | }
49 | if (CharSequenceUtil.isBlank(pluginJsonArray) || !JSONUtil.isTypeJSON(pluginJsonArray)) {
50 | pluginCacheList = new ArrayList<>();
51 | refreshJsonFile();
52 | } else {
53 | pluginCacheList = JSONUtil.toList(pluginJsonArray, PluginCache.class);
54 | log.info("初始化成功!");
55 | refreshJsonFile();
56 | }
57 | }
58 |
59 | public static List pluginCacheList() {
60 | return PluginsContextHolder.pluginCacheList;
61 | }
62 |
63 | public static void refreshJsonFile() {
64 | log.info("从'JetBrains.com'刷新中...");
65 | CompletableFuture
66 | .supplyAsync(PluginsContextHolder::pluginList)
67 | .thenApply(PluginsContextHolder::pluginListFilter)
68 | .thenApply(PluginsContextHolder::pluginConversion)
69 | .thenAccept(PluginsContextHolder::overrideJsonFile)
70 | .thenRun(() -> log.info("刷新成功!"))
71 | .exceptionally(throwable -> {
72 | log.error("刷新失败!", throwable);
73 | return null;
74 | });
75 | }
76 |
77 | public static void overrideJsonFile(List pluginCaches) {
78 | log.info("源大小 => [{}], 新增大小 => [{}]", pluginCacheList.size(), pluginCaches.size());
79 | pluginCacheList.addAll(pluginCaches);
80 | String jsonStr = JSONUtil.toJsonStr(pluginCacheList);
81 | try {
82 | FileUtil.writeString(JSONUtil.formatJsonStr(jsonStr), pluginsJsonFile, StandardCharsets.UTF_8);
83 | log.info("Json文件已覆写!");
84 | } catch (IORuntimeException e) {
85 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件写入失败!", PLUGIN_JSON_FILE_NAME), e);
86 | }
87 |
88 | }
89 |
90 | public static PluginList pluginList() {
91 | return HttpUtil.createGet(PLUGIN_LIST_URL)
92 | .thenFunction(response -> {
93 | try (InputStream is = response.bodyStream()) {
94 | if (!response.isOk()) {
95 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求失败! = {}", PLUGIN_LIST_URL, response));
96 | }
97 | PluginList pluginList = JSONUtil.toBean(IoUtil.readUtf8(is), PluginList.class);
98 | log.info("获取大小 => [{}]", pluginList.getTotal());
99 | return pluginList;
100 | } catch (IOException e) {
101 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求IO读取失败!", PLUGIN_LIST_URL), e);
102 | }
103 | });
104 | }
105 |
106 | public static List pluginListFilter(PluginList pluginList) {
107 | List plugins = pluginList.getPlugins()
108 | .stream()
109 | .filter(plugin -> !PluginsContextHolder.pluginCacheList.contains(new PluginCache().setId(plugin.getId())))
110 | .filter(plugin -> !CharSequenceUtil.equals(plugin.getPricingModel(), "FREE"))
111 | .toList();
112 | log.info("过滤后大小 => [{}]", plugins.size());
113 | return plugins;
114 | }
115 |
116 | public static List pluginConversion(List pluginList) {
117 | List list = pluginList
118 | .stream()
119 | .parallel()
120 | .map(plugin -> {
121 | String productCode = pluginInfo(plugin).getPurchaseInfo().getProductCode();
122 | return new PluginCache()
123 | .setId(plugin.getId())
124 | .setProductCode(productCode)
125 | .setName(plugin.getName())
126 | .setPricingModel(plugin.getPricingModel())
127 | .setIcon(StrUtil.isNotBlank(plugin.getIcon()) ? PLUGIN_BASIC_URL + plugin.getIcon() : null)
128 | ;
129 | })
130 | .toList();
131 | log.info("转换后大小 => [{}]", list.size());
132 | return list;
133 | }
134 |
135 | public static PluginInfo pluginInfo(PluginList.Plugin plugin) {
136 | return HttpUtil.createGet(PLUGIN_INFO_URL + plugin.getId())
137 | .thenFunction(response -> {
138 | try (InputStream is = response.bodyStream()) {
139 | if (!response.isOk()) {
140 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求失败! = {}", PLUGIN_INFO_URL, response));
141 | }
142 | PluginInfo pluginInfo = JSONUtil.toBean(IoUtil.readUtf8(is), PluginInfo.class);
143 | log.info("已抓取 => ID = [{}], 名称 = [{}], Code = [{}]", pluginInfo.getId(), plugin.getName(), pluginInfo.getPurchaseInfo().getProductCode());
144 | return pluginInfo;
145 | } catch (IOException e) {
146 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 请求IO读取失败!", PLUGIN_LIST_URL), e);
147 | }
148 | });
149 | }
150 |
151 |
152 | @Data
153 | public static class PluginCache {
154 |
155 | private Long id;
156 | private String productCode;
157 | private String name;
158 | private String pricingModel;
159 | private String icon;
160 |
161 | @Override
162 | public final boolean equals(Object o) {
163 | if (this == o) return true;
164 | if (!(o instanceof PluginCache that)) return false;
165 |
166 | return id.equals(that.id);
167 | }
168 |
169 | @Override
170 | public int hashCode() {
171 | return id.hashCode();
172 | }
173 | }
174 |
175 | @Data
176 | public static class PluginInfo {
177 |
178 | private Long id;
179 |
180 | private PurchaseInfo purchaseInfo;
181 |
182 | @Data
183 | public static class PurchaseInfo {
184 |
185 | private String productCode;
186 | }
187 | }
188 |
189 | @Data
190 | public static class PluginList {
191 |
192 | private List plugins;
193 | private Long total;
194 |
195 |
196 | @Data
197 | public static class Plugin {
198 |
199 | private Long id;
200 | private String name;
201 | private String preview;
202 | private Integer downloads;
203 | private String pricingModel;
204 | private String organization;
205 | private String icon;
206 | private String previewImage;
207 | private Double rating;
208 | private VendorInfo vendorInfo;
209 | }
210 |
211 | @Data
212 | public static class VendorInfo {
213 | private String name;
214 | private Boolean isVerified;
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/context/ProductsContextHolder.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.context;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.core.io.IORuntimeException;
5 | import cn.hutool.core.io.IoUtil;
6 | import cn.hutool.core.text.CharSequenceUtil;
7 | import cn.hutool.json.JSONUtil;
8 | import com.jetbrains.help.util.FileTools;
9 | import lombok.AccessLevel;
10 | import lombok.Data;
11 | import lombok.NoArgsConstructor;
12 | import lombok.extern.slf4j.Slf4j;
13 |
14 | import java.io.File;
15 | import java.util.List;
16 |
17 | @Slf4j(topic = "产品上下文")
18 | @NoArgsConstructor(access = AccessLevel.PRIVATE)
19 | public class ProductsContextHolder {
20 |
21 | private static final String PRODUCT_JSON_FILE_NAME = "external/data/product.json";
22 |
23 | private static List productCacheList;
24 |
25 | // TODO 通过该接口可以获取付费IDE的CODE
26 | // TODO https://data.services.jetbrains.com/products?fields=name,salesCode
27 |
28 | public static void init() {
29 | log.info("初始化中...");
30 | File productJsonFile = FileTools.getFileOrCreat(PRODUCT_JSON_FILE_NAME);
31 |
32 | String productJsonArray;
33 | try {
34 | productJsonArray = IoUtil.readUtf8(FileUtil.getInputStream(productJsonFile));
35 | } catch (IORuntimeException e) {
36 | throw new IllegalArgumentException(CharSequenceUtil.format("{} 文件读取失败!", PRODUCT_JSON_FILE_NAME), e);
37 | }
38 | if (CharSequenceUtil.isBlank(productJsonArray) || !JSONUtil.isTypeJSON(productJsonArray)) {
39 | log.error("产品数据不存在!");
40 | } else {
41 | productCacheList = JSONUtil.toList(productJsonArray, ProductCache.class);
42 | log.info("初始化成功!");
43 | }
44 | }
45 |
46 | public static List productCacheList() {
47 | return ProductsContextHolder.productCacheList;
48 | }
49 |
50 | @Data
51 | public static class ProductCache {
52 |
53 | private String name;
54 | private String productCode;
55 | private String iconClass;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/controller/OpenApiController.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.controller;
2 |
3 | import cn.hutool.core.collection.CollUtil;
4 | import cn.hutool.core.text.CharSequenceUtil;
5 | import cn.hutool.core.util.StrUtil;
6 | import com.jetbrains.help.context.LicenseContextHolder;
7 | import com.jetbrains.help.context.PluginsContextHolder;
8 | import com.jetbrains.help.context.ProductsContextHolder;
9 | import lombok.Data;
10 | import org.springframework.web.bind.annotation.PostMapping;
11 | import org.springframework.web.bind.annotation.RequestBody;
12 | import org.springframework.web.bind.annotation.RestController;
13 |
14 | import java.util.Collection;
15 | import java.util.HashSet;
16 | import java.util.List;
17 | import java.util.Set;
18 |
19 | @RestController
20 | public class OpenApiController {
21 |
22 | @Data
23 | public static class GenerateLicenseReqBody {
24 |
25 | private String licenseName;
26 |
27 | private String assigneeName;
28 |
29 | private String expiryDate;
30 |
31 | private String productCode;
32 | }
33 |
34 | @PostMapping("generateLicense")
35 | public String generateLicense(@RequestBody GenerateLicenseReqBody body) {
36 | Set productCodeSet;
37 | if (CharSequenceUtil.isBlank(body.getProductCode())) {
38 | List productCodeList = ProductsContextHolder.productCacheList()
39 | .stream()
40 | .map(ProductsContextHolder.ProductCache::getProductCode)
41 | .filter(StrUtil::isNotBlank)
42 | .map(productCode -> CharSequenceUtil.splitTrim(productCode, ","))
43 | .flatMap(Collection::stream)
44 | .toList();
45 | List pluginCodeList = PluginsContextHolder.pluginCacheList()
46 | .stream()
47 | .map(PluginsContextHolder.PluginCache::getProductCode)
48 | .filter(StrUtil::isNotBlank)
49 | .toList();
50 | productCodeSet = CollUtil.newHashSet(productCodeList);
51 | productCodeSet.addAll(pluginCodeList);
52 | }else {
53 | productCodeSet = CollUtil.newHashSet(CharSequenceUtil.splitTrim(body.getProductCode(), ','));
54 | }
55 | return LicenseContextHolder.generateLicense(
56 | body.getLicenseName(),
57 | body.getAssigneeName(),
58 | body.getExpiryDate(),
59 | productCodeSet
60 | );
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/properties/JetbrainsHelpProperties.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.properties;
2 |
3 | import lombok.Data;
4 | import org.springframework.boot.context.properties.ConfigurationProperties;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 | @Data
8 | @Configuration
9 | @ConfigurationProperties("help")
10 | public class JetbrainsHelpProperties {
11 |
12 | private String defaultLicenseName;
13 |
14 | private String defaultAssigneeName;
15 |
16 | private String defaultExpiryDate;
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/route/IndexController.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.route;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.core.io.IoUtil;
5 | import cn.hutool.core.text.CharSequenceUtil;
6 | import cn.hutool.core.util.StrUtil;
7 | import com.jetbrains.help.JetbrainsHelpApplication;
8 | import com.jetbrains.help.context.AgentContextHolder;
9 | import com.jetbrains.help.context.PluginsContextHolder;
10 | import com.jetbrains.help.context.ProductsContextHolder;
11 | import com.jetbrains.help.properties.JetbrainsHelpProperties;
12 | import lombok.AllArgsConstructor;
13 | import lombok.RequiredArgsConstructor;
14 | import org.springframework.core.io.InputStreamResource;
15 | import org.springframework.core.io.Resource;
16 | import org.springframework.http.MediaType;
17 | import org.springframework.http.ResponseEntity;
18 | import org.springframework.stereotype.Controller;
19 | import org.springframework.ui.Model;
20 | import org.springframework.web.bind.annotation.GetMapping;
21 | import org.springframework.web.bind.annotation.RequestParam;
22 | import org.springframework.web.bind.annotation.ResponseBody;
23 |
24 | import java.io.File;
25 | import java.util.List;
26 |
27 | import static org.springframework.http.HttpHeaders.CONTENT_DISPOSITION;
28 | import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;
29 |
30 | @Controller
31 | @RequiredArgsConstructor
32 | public class IndexController {
33 |
34 | private final JetbrainsHelpProperties jetbrainsHelpProperties;
35 |
36 | @GetMapping
37 | public String index(Model model) {
38 | List productCacheList = ProductsContextHolder.productCacheList();
39 | List pluginCacheList = PluginsContextHolder.pluginCacheList();
40 | model.addAttribute("products", productCacheList);
41 | model.addAttribute("plugins", pluginCacheList);
42 | model.addAttribute("defaults", jetbrainsHelpProperties);
43 | return "index";
44 | }
45 |
46 | @GetMapping("search")
47 | public String index(@RequestParam(required = false) String search, Model model) {
48 | List productCacheList = ProductsContextHolder.productCacheList();
49 | List pluginCacheList = PluginsContextHolder.pluginCacheList();
50 | if (CharSequenceUtil.isNotBlank(search)) {
51 | productCacheList = productCacheList.stream()
52 | .filter(productCache -> CharSequenceUtil.containsIgnoreCase(productCache.getName(), search))
53 | .toList();
54 | pluginCacheList = pluginCacheList.stream()
55 | .filter(pluginCache -> CharSequenceUtil.containsIgnoreCase(pluginCache.getName(), search))
56 | .toList();
57 | }
58 | model.addAttribute("products", productCacheList);
59 | model.addAttribute("plugins", pluginCacheList);
60 | model.addAttribute("defaults", jetbrainsHelpProperties);
61 | return "index::product-list";
62 | }
63 |
64 | @GetMapping("ja-netfilter")
65 | @ResponseBody
66 | public ResponseEntity downloadJaNetfilter() {
67 | File jaNetfilterZipFile = AgentContextHolder.jaNetfilterZipFile();
68 | return ResponseEntity.ok()
69 | .header(CONTENT_DISPOSITION, "attachment;filename=" + jaNetfilterZipFile.getName())
70 | .contentType(APPLICATION_OCTET_STREAM)
71 | .body(new InputStreamResource(FileUtil.getInputStream(jaNetfilterZipFile)));
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/com/jetbrains/help/util/FileTools.java:
--------------------------------------------------------------------------------
1 | package com.jetbrains.help.util;
2 |
3 | import cn.hutool.core.io.FileUtil;
4 | import cn.hutool.core.text.CharSequenceUtil;
5 | import cn.hutool.core.util.ObjectUtil;
6 | import org.springframework.boot.system.ApplicationHome;
7 | import org.springframework.core.io.ClassPathResource;
8 |
9 | import java.io.*;
10 |
11 | public interface FileTools {
12 |
13 | ApplicationHome application = new ApplicationHome();
14 |
15 |
16 | static boolean fileExists(String path) {
17 | return getFile(path).exists();
18 | }
19 |
20 | static File getFile(String path) {
21 | File homeDir = application.getDir();
22 | File source = application.getSource();
23 | ClassPathResource classPathResource = new ClassPathResource(path);
24 | return ObjectUtil.isNull(source) ? FileUtil.file(classPathResource.getPath()) : FileUtil.file(homeDir, path);
25 | }
26 |
27 | static File getFileOrCreat(String path) {
28 | File file = getFile(path);
29 | if (ObjectUtil.isNotNull(application.getSource())) {
30 | ClassPathResource classPathResource = new ClassPathResource(path);
31 | if (classPathResource.exists() && !file.exists()) {
32 | try (InputStream inputStream = classPathResource.getInputStream()) {
33 | FileUtil.writeFromStream(inputStream, file);
34 | } catch (Exception e) {
35 | throw new IllegalArgumentException(
36 | CharSequenceUtil.format("{} 文件读取失败!", path), e
37 | );
38 | }
39 | }
40 | }
41 | return file;
42 |
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | application:
3 | name: Jetbrains-Help
4 | thymeleaf:
5 | cache: false
6 | server:
7 | port: 10768
8 | help:
9 | default-license-name: 光云
10 | default-assignee-name: 藏柏
11 | default-expiry-date: 2111-11-11
--------------------------------------------------------------------------------
/src/main/resources/banner.txt:
--------------------------------------------------------------------------------
1 | ${AnsiColor.RED} ██╗███████╗████████╗██████╗ ██████╗ █████╗ ██╗███╗ ██╗███████╗ ██╗ ██╗███████╗██╗ ██████╗
2 | ${AnsiColor.CYAN} ██║██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██╔══██╗██║████╗ ██║██╔════╝ ██║ ██║██╔════╝██║ ██╔══██╗
3 | ${AnsiColor.BRIGHT_YELLOW} ██║█████╗ ██║ ██████╔╝██████╔╝███████║██║██╔██╗ ██║███████╗█████╗███████║█████╗ ██║ ██████╔╝
4 | ${AnsiColor.GREEN}██ ██║██╔══╝ ██║ ██╔══██╗██╔══██╗██╔══██║██║██║╚██╗██║╚════██║╚════╝██╔══██║██╔══╝ ██║ ██╔═══╝
5 | ${AnsiColor.BLUE}╚█████╔╝███████╗ ██║ ██████╔╝██║ ██║██║ ██║██║██║ ╚████║███████║ ██║ ██║███████╗███████╗██║
6 | ${AnsiColor.MAGENTA} ╚════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝
7 |
8 | ${AnsiColor.BRIGHT_YELLOW} Spring Boot Version: ${spring-boot.version}${AnsiColor.DEFAULT}
--------------------------------------------------------------------------------
/src/main/resources/external/agent/ja-netfilter.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NotoChen/Jetbrains-Help/3223e697caaae06b99dfeb0150921dc96b7cb932/src/main/resources/external/agent/ja-netfilter.zip
--------------------------------------------------------------------------------
/src/main/resources/external/certificate/root.key:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFOzCCAyOgAwIBAgIJANJssYOyg3nhMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
3 | BAMMDUpldFByb2ZpbGUgQ0EwHhcNMTUxMDAyMTEwMDU2WhcNNDUxMDI0MTEwMDU2
4 | WjAYMRYwFAYDVQQDDA1KZXRQcm9maWxlIENBMIICIjANBgkqhkiG9w0BAQEFAAOC
5 | Ag8AMIICCgKCAgEA0tQuEA8784NabB1+T2XBhpB+2P1qjewHiSajAV8dfIeWJOYG
6 | y+ShXiuedj8rL8VCdU+yH7Ux/6IvTcT3nwM/E/3rjJIgLnbZNerFm15Eez+XpWBl
7 | m5fDBJhEGhPc89Y31GpTzW0vCLmhJ44XwvYPntWxYISUrqeR3zoUQrCEp1C6mXNX
8 | EpqIGIVbJ6JVa/YI+pwbfuP51o0ZtF2rzvgfPzKtkpYQ7m7KgA8g8ktRXyNrz8bo
9 | iwg7RRPeqs4uL/RK8d2KLpgLqcAB9WDpcEQzPWegbDrFO1F3z4UVNH6hrMfOLGVA
10 | xoiQhNFhZj6RumBXlPS0rmCOCkUkWrDr3l6Z3spUVgoeea+QdX682j6t7JnakaOw
11 | jzwY777SrZoi9mFFpLVhfb4haq4IWyKSHR3/0BlWXgcgI6w6LXm+V+ZgLVDON52F
12 | LcxnfftaBJz2yclEwBohq38rYEpb+28+JBvHJYqcZRaldHYLjjmb8XXvf2MyFeXr
13 | SopYkdzCvzmiEJAewrEbPUaTllogUQmnv7Rv9sZ9jfdJ/cEn8e7GSGjHIbnjV2ZM
14 | Q9vTpWjvsT/cqatbxzdBo/iEg5i9yohOC9aBfpIHPXFw+fEj7VLvktxZY6qThYXR
15 | Rus1WErPgxDzVpNp+4gXovAYOxsZak5oTV74ynv1aQ93HSndGkKUE/qA/JECAwEA
16 | AaOBhzCBhDAdBgNVHQ4EFgQUo562SGdCEjZBvW3gubSgUouX8bMwSAYDVR0jBEEw
17 | P4AUo562SGdCEjZBvW3gubSgUouX8bOhHKQaMBgxFjAUBgNVBAMMDUpldFByb2Zp
18 | bGUgQ0GCCQDSbLGDsoN54TAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkq
19 | hkiG9w0BAQsFAAOCAgEAjrPAZ4xC7sNiSSqh69s3KJD3Ti4etaxcrSnD7r9rJYpK
20 | BMviCKZRKFbLv+iaF5JK5QWuWdlgA37ol7mLeoF7aIA9b60Ag2OpgRICRG79QY7o
21 | uLviF/yRMqm6yno7NYkGLd61e5Huu+BfT459MWG9RVkG/DY0sGfkyTHJS5xrjBV6
22 | hjLG0lf3orwqOlqSNRmhvn9sMzwAP3ILLM5VJC5jNF1zAk0jrqKz64vuA8PLJZlL
23 | S9TZJIYwdesCGfnN2AETvzf3qxLcGTF038zKOHUMnjZuFW1ba/12fDK5GJ4i5y+n
24 | fDWVZVUDYOPUixEZ1cwzmf9Tx3hR8tRjMWQmHixcNC8XEkVfztID5XeHtDeQ+uPk
25 | X+jTDXbRb+77BP6n41briXhm57AwUI3TqqJFvoiFyx5JvVWG3ZqlVaeU/U9e0gxn
26 | 8qyR+ZA3BGbtUSDDs8LDnE67URzK+L+q0F2BC758lSPNB2qsJeQ63bYyzf0du3wB
27 | /gb2+xJijAvscU3KgNpkxfGklvJD/oDUIqZQAnNcHe7QEf8iG2WqaMJIyXZlW3me
28 | 0rn+cgvxHPt6N4EBh5GgNZR4l0eaFEV+fxVsydOQYo1RIyFMXtafFBqQl6DDxujl
29 | FeU3FZ+Bcp12t7dlM4E0/sS1XdL47CfGVj4Bp+/VbF862HmkAbd7shs7sDQkHbU=
30 | -----END CERTIFICATE-----
31 |
--------------------------------------------------------------------------------
/src/main/resources/external/data/product.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "All Jetbrains's Product Or Plugin",
4 | "productCode": "",
5 | "iconClass": "icon-al"
6 | },
7 | {
8 | "name": "IntelliJ IDEA",
9 | "productCode": "II,PCWMP,PSI",
10 | "iconClass": "icon-ii"
11 | },
12 | {
13 | "name": "PhpStorm",
14 | "productCode": "PS,PCWMP,PSI",
15 | "iconClass": "icon-ps"
16 | },
17 | {
18 | "name": "AppCode",
19 | "productCode": "AC,PCWMP,PSI",
20 | "iconClass": "icon-ac"
21 | },
22 | {
23 | "name": "DataGrip",
24 | "productCode": "DB,PSI,PDB",
25 | "iconClass": "icon-db"
26 | },
27 | {
28 | "name": "RubyMine",
29 | "productCode": "RM,PCWMP,PSI",
30 | "iconClass": "icon-rm"
31 | },
32 | {
33 | "name": "WebStorm",
34 | "productCode": "WS,PCWMP,PSI",
35 | "iconClass": "icon-ws"
36 | },
37 | {
38 | "name": "Rider",
39 | "productCode": "RD,PDB,PSI,PCWMP",
40 | "iconClass": "icon-rd"
41 | },
42 | {
43 | "name": "CLion",
44 | "productCode": "CL,PSI,PCWMP",
45 | "iconClass": "icon-cl"
46 | },
47 | {
48 | "name": "PyCharm",
49 | "productCode": "PC,PSI,PCWMP",
50 | "iconClass": "icon-pc"
51 | },
52 | {
53 | "name": "GoLand",
54 | "productCode": "GO,PSI,PCWMP",
55 | "iconClass": "icon-go"
56 | },
57 | {
58 | "name": "DataSpell",
59 | "productCode": "DS,PSI,PDB,PCWMP",
60 | "iconClass": "icon-ds"
61 | },
62 | {
63 | "name": "dotMemory",
64 | "productCode": "DM",
65 | "iconClass": "icon-dm"
66 | },
67 | {
68 | "name": "Aqua",
69 | "productCode": "QA,PSI,PCWMP",
70 | "iconClass": "icon-qa"
71 | },
72 | {
73 | "name": "RustRover",
74 | "productCode": "RR,PSI,PCWMP",
75 | "iconClass": "icon-rr"
76 | }
77 | ]
--------------------------------------------------------------------------------
/src/main/resources/static/css/index.css:
--------------------------------------------------------------------------------
1 | .form {
2 | background-color: #15172b;
3 | border-radius: 20px;
4 | box-sizing: border-box;
5 | height: 500px;
6 | padding: 20px;
7 | width: 320px;
8 | }
9 |
10 | .title {
11 | color: #eee;
12 | font-family: sans-serif;
13 | font-size: 36px;
14 | font-weight: 600;
15 | margin-top: 30px;
16 | }
17 |
18 | .subtitle {
19 | color: #eee;
20 | font-family: sans-serif;
21 | font-size: 16px;
22 | font-weight: 600;
23 | margin-top: 10px;
24 | }
25 |
26 | .input-container {
27 | height: 50px;
28 | position: relative;
29 | width: 100%;
30 | }
31 |
32 | .ic1 {
33 | margin-top: 40px;
34 | }
35 |
36 | .ic2 {
37 | margin-top: 30px;
38 | }
39 |
40 | .input {
41 | background-color: #303245;
42 | border-radius: 12px;
43 | border: 0;
44 | box-sizing: border-box;
45 | color: #eee;
46 | font-size: 18px;
47 | height: 100%;
48 | outline: 0;
49 | padding: 4px 20px 0;
50 | width: 100%;
51 | }
52 |
53 | .cut {
54 | background-color: #15172b;
55 | border-radius: 10px;
56 | height: 20px;
57 | left: 20px;
58 | position: absolute;
59 | top: -20px;
60 | transform: translateY(0);
61 | transition: transform 200ms;
62 | width: 76px;
63 | }
64 |
65 | .cut-short {
66 | width: 50px;
67 | }
68 |
69 | .input:focus ~ .cut,
70 | .input:not(:placeholder-shown) ~ .cut {
71 | transform: translateY(8px);
72 | }
73 |
74 | .placeholder {
75 | color: #65657b;
76 | font-family: sans-serif;
77 | left: 20px;
78 | line-height: 14px;
79 | pointer-events: none;
80 | position: absolute;
81 | transform-origin: 0 50%;
82 | transition: transform 200ms, color 200ms;
83 | top: 20px;
84 | }
85 |
86 | .input:focus ~ .placeholder,
87 | .input:not(:placeholder-shown) ~ .placeholder {
88 | transform: translateY(-30px) translateX(10px) scale(0.75);
89 | }
90 |
91 | .input:not(:placeholder-shown) ~ .placeholder {
92 | color: #808097;
93 | }
94 |
95 | .input:focus ~ .placeholder {
96 | color: #dc2f55;
97 | }
98 |
99 | .submit {
100 | background-color: #08d;
101 | border-radius: 12px;
102 | border: 0;
103 | box-sizing: border-box;
104 | color: #eee;
105 | cursor: pointer;
106 | font-size: 18px;
107 | height: 50px;
108 | margin-top: 38px;
109 | text-align: center;
110 | width: 100%;
111 | }
112 |
113 | .submit:active {
114 | background-color: #06b;
115 | }
116 | :root {
117 | --text-grey: #9e9e9e;
118 | --text-main: rgba(0, 0, 0, 0.87);
119 | --spacing: 4px;
120 | --size: 64px;
121 | --radius: 1.5rem;
122 | --accent: #5380f7;
123 | --text-sm: 0.875rem;
124 | --main-bg: #fff;
125 | --card-bg: #fff;
126 | --hover-color: #eee;
127 | --border-color: rgba(0, 0, 0, 0.05);
128 | --grey-400: rgba(0, 0, 0, 0.04);
129 | --grey-600: rgba(0, 0, 0, 0.06);
130 | }
131 |
132 | @media (prefers-color-scheme: dark) {
133 | :root {
134 | --main-bg: rgb(0, 0, 0);
135 | --card-bg: rgb(31, 34, 38);
136 | --text-main: #d9d9d9;
137 | --text-grey: #6e767d;
138 | --accent: #1d9bf0;
139 | --hover-color: rgba(255, 255, 255, 0.07);
140 | --border-color: #4b4648;
141 | }
142 | }
143 |
144 | body {
145 | font-size: 1rem;
146 | line-height: 1.5;
147 | word-wrap: break-word;
148 | font-kerning: normal;
149 | font-family: 'Gotham SSm A', 'Gotham SSm B', 'Arial Unicode MS', Helvetica, sans-serif;
150 | margin: 0;
151 | padding: 0;
152 | -webkit-font-smoothing: antialiased;
153 | background-color: var(--main-bg);
154 | }
155 |
156 | * ul, * ol {
157 | list-style: none;
158 | padding: 0;
159 | margin: 0;
160 | }
161 |
162 | *[role='button'], button {
163 | cursor: pointer;
164 | }
165 |
166 | .color-primary {
167 | color: var(--text-main);
168 | }
169 |
170 |
171 | .mt-0 {
172 | margin-top: 0;
173 | }
174 |
175 |
176 | .radius-1 {
177 | border-radius: var(--radius);
178 | }
179 |
180 | .px-6 {
181 | padding-left: calc(var(--spacing) * 6);
182 | padding-right: calc(var(--spacing) * 6);
183 | }
184 |
185 | .py-6 {
186 | padding-left: calc(var(--spacing)* 6);
187 | padding-right: calc(var(--spacing)* 6);
188 | }
189 |
190 | .py-10 {
191 | padding-top: calc(var(--spacing) * 10);
192 | padding-bottom: calc(var(--spacing) * 10);
193 | }
194 |
195 | .pd-6 {
196 | padding: calc(var(--spacing) * 6);
197 | }
198 |
199 |
200 | .pt-1 {
201 | padding-top: var(--spacing);
202 | }
203 |
204 | .pb-0 {
205 | padding-bottom: 0;
206 | }
207 |
208 | .overflow-hidden {
209 | overflow: hidden;
210 | }
211 |
212 | .flex {
213 | display: flex;
214 | }
215 |
216 | .justify-between {
217 | justify-content: space-between;
218 | }
219 |
220 | .justify-center {
221 | justify-content: center;
222 | }
223 |
224 |
225 | .items-center {
226 | align-items: center;
227 | }
228 |
229 | .shrink-0 {
230 | flex-shrink: 0;
231 | }
232 |
233 | .text-grey {
234 | color: var(--text-grey);
235 | }
236 |
237 | .text-sm {
238 | font-size: 0.875rem;
239 | }
240 |
241 | .bg-card {
242 | background-color: var(--card-bg);
243 | }
244 |
245 | .truncate {
246 | /* display: -webkit-box; */
247 | /* -webkit-box-orient: vertical; */
248 | /* -webkit-line-clamp: var(--line, 3); */
249 | /* overflow: hidden; */
250 | }
251 |
252 | .truncate-1 {
253 | --line: 1;
254 | }
255 |
256 | .overflow-ellipsis {
257 | text-overflow: ellipsis;
258 | }
259 |
260 | .z-grid {
261 | display: grid;
262 | grid-gap: var(--gutter, 1rem);
263 | grid-template-columns: repeat(auto-fill, minmax(min(var(--space, 10rem), 100%), 1fr));
264 | }
265 |
266 |
267 | .card {
268 | box-shadow: rgb(0 0 0 / 30%) 0 8px 40px -12px;
269 | border-radius: 1.5rem;
270 | transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) 0ms;
271 | width: 90%;
272 | position: relative;
273 | overflow: visible;
274 | background-color: var(--card-bg);
275 | margin: 0 auto;
276 | }
277 |
278 | .card:hover {
279 | transform: translateY(-2px);
280 | }
281 |
282 | .card:hover .mask {
283 | bottom: -1.5rem;
284 | }
285 |
286 | .card:hover .mask-c-1 {
287 | bottom: -2.5rem;
288 | }
289 |
290 | .container {
291 | padding-top: calc(var(--spacing) * 10);
292 | }
293 |
294 | .container p {
295 | position: relative;
296 | cursor: pointer;
297 | transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
298 | }
299 |
300 | .container p::after {
301 | content: attr(data-content);
302 | position: absolute;
303 | transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
304 | color: transparent;
305 | top: 0;
306 | left: 0;
307 | width: 100%;
308 | height: 100%;
309 | display: flex;
310 | align-items: center;
311 | justify-content: center;
312 | border-radius: var(--radius);
313 | }
314 |
315 | .container p:hover {
316 | color: transparent;
317 | }
318 |
319 | .container p:hover::after {
320 | color: var(--text-main);
321 | background-color: var(--hover-color);
322 | }
323 |
324 | .toggle li {
325 | z-index: 99;
326 | position: relative;
327 | background: transparent;
328 | padding: 0 20px;
329 | color: var(--text-main);
330 | transition: background-color 250ms ease-out;
331 | }
332 |
333 | .toggle li:hover {
334 | background-color: var(--hover-color);
335 | }
336 |
337 | .toggle li.active a {
338 | color: var(--accent);
339 | }
340 |
341 | .toggle li:first-child {
342 | border-top-left-radius: var(--text-sm);
343 | border-top-right-radius: var(--text-sm);
344 | }
345 |
346 | .toggle li:last-child {
347 | border-bottom-left-radius: var(--text-sm);
348 | border-bottom-right-radius: var(--text-sm);
349 | }
350 |
351 | .toggle li:last-child a {
352 | border-bottom: 0;
353 | }
354 |
355 | .toggle li a {
356 | display: block;
357 | border-bottom: 1px solid var(--border-color);
358 | padding: 16px 0;
359 | color: inherit;
360 | text-decoration: none;
361 | white-space: nowrap;
362 | }
363 |
364 | .icon {
365 | background-image: url('../images/icons.svg?t=4567');
366 | background-size: 64px;
367 | background-position-x: 0;
368 | }
369 |
370 |
371 | .icon-ii {
372 | background-image: url('../images/intellij-idea.svg?v=1');
373 | }
374 |
375 | .icon-ps {
376 | background-image: url('../images/phpstorm.svg?v=1');
377 | }
378 |
379 | .icon-ac {
380 | background-position-y: -192px;
381 | }
382 |
383 | .icon-db {
384 | background-image: url('../images/datagrip.svg?v=1');
385 | }
386 |
387 | .icon-rm {
388 | background-image: url('../images/rubymine.svg?v=1');
389 | }
390 |
391 | .icon-ws {
392 | background-image: url('../images/webstorm.svg?v=1');
393 | }
394 |
395 | .icon-rd {
396 | background-image: url('../images/rider.svg?v=1');
397 | }
398 |
399 | .icon-cl {
400 | background-image: url('../images/clion.svg?v=1');
401 | }
402 |
403 | .icon-pc {
404 | background-image: url('../images/pycharm.svg?v=1');
405 | }
406 |
407 | .icon-go {
408 | background-image: url('../images/goland.svg?v=1');
409 | }
410 |
411 | .icon-ds {
412 | background-image: url('../images/dataspell.svg?v=1');
413 | }
414 |
415 | .icon-dc {
416 | background-image: url('../images/dotcover.svg?v=1');
417 | }
418 |
419 | .icon-dpn {
420 | background-image: url('../images/dottrace.svg?v=1');
421 | }
422 |
423 | .icon-dm {
424 | background-image: url('../images/dotmemory.svg?v=1');
425 | }
426 |
427 | .icon-rr {
428 | background-image: url('../images/rustrover.svg?v=1');
429 | }
430 |
431 | .icon-qa {
432 | background-image: url('../images/aqua.svg?v=1');
433 | }
434 |
435 | .mask {
436 | transition: 0.2s;
437 | position: absolute;
438 | z-index: -1;
439 | width: 88%;
440 | height: 100%;
441 | bottom: 0;
442 | border-radius: 1.5rem;
443 | background-color: var(--grey-600);
444 | left: 50%;
445 | transform: translateX(-50%);
446 | }
447 |
448 | .mask-c-1 {
449 | bottom: 0;
450 | width: 72%;
451 | background-color: var(--grey-400);
452 | }
453 |
454 | .avatar-wrapper {
455 | position: relative;
456 | width: var(--size);
457 | height: var(--size);
458 | font-size: 1.25rem;
459 | user-select: none;
460 | transform: translateY(50%);
461 | }
462 |
463 | .avatar-wrapper img, .avatar-wrapper .icon {
464 | width: 100%;
465 | height: 100%;
466 | margin: 0;
467 | background-color: var(--card-bg);
468 | color: transparent;
469 | object-fit: cover;
470 | text-align: center;
471 | text-indent: 10000px;
472 | }
473 |
474 |
475 | header.tip a {
476 | color: var(--accent);
477 | text-decoration: none;
478 | }
479 |
480 | header.tip p {
481 | word-break: break-word;
482 | word-wrap: break-word;
483 | }
484 |
485 |
486 | main hr {
487 | margin: 0;
488 | padding: 0;
489 | background: var(--border-color);
490 | height: 1px;
491 | border: none;
492 | }
493 |
494 | footer {
495 | --_size: 40px;
496 | padding-top: var(--_size);
497 | width: 96%;
498 | margin: calc(var(--spacing) * 10) auto 0;
499 | padding-bottom: var(--_size);
500 | border-top: 1px solid var(--border-color);
501 | -moz-box-align: center;
502 | -webkit-box-pack: justify;
503 | }
504 |
505 | footer .lt-panel p:nth-of-type(1) {
506 | color: inherit;
507 | }
508 | header.tip {
509 | top: 2.3%;
510 | background-color: var(--card-bg);
511 | color: var(--text-main);
512 | z-index: 99;
513 | width: 80%;
514 | margin: 0 auto;
515 | border-radius: 16px;
516 | box-shadow: rgb(0 0 0 / 30%) 0 8px 40px -12px;
517 | transition: transform 250ms ease, box-shadow 250ms ease;
518 | }
519 | .sticky {
520 | position: sticky;
521 | }
522 |
523 |
524 | .parent {
525 | position: relative;
526 | }
527 | .search {
528 | width: 300px;
529 | height: 40px;
530 | border-radius: 18px;
531 | outline: none;
532 | /*border: 1px solid #ccc;*/
533 | padding-left: 20px;
534 | /*position: absolute;*/
535 | }
--------------------------------------------------------------------------------
/src/main/resources/static/images/aqua.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/clion.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/datagrip.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/dataspell.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/dotcover.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/dotmemory.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/dottrace.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/goland.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/icons.svg:
--------------------------------------------------------------------------------
1 |
714 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/intellij-idea.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/phpstorm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/plugin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/pycharm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/rider.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/rubymine.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/rustrover.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/webstorm.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/main/resources/static/js/index.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function() {
2 | // Set default headers for AJAX requests
3 | $.ajaxSetup({
4 | headers: {
5 | 'Content-Type': 'application/json'
6 | }
7 | });
8 |
9 | // Function to handle submission of license information
10 | window.submitLicenseInfo = function () {
11 | let licenseInfo = {
12 | licenseeName: $('#licenseeName').val(),
13 | assigneeName: $('#assigneeName').val(),
14 | expiryDate: $('#expiryDate').val()
15 | };
16 | localStorage.setItem('licenseInfo', JSON.stringify(licenseInfo));
17 | $('#mask, #form').hide();
18 | };
19 |
20 | // Function to handle search input
21 | $('#search').on('input', function(e) {
22 | $("#product-list").load('/search?search=' + e.target.value);
23 | });
24 |
25 | // Function to show license form
26 | window.showLicenseForm = function () {
27 | let licenseInfo = JSON.parse(localStorage.getItem('licenseInfo'));
28 | $('#licenseeName').val(licenseInfo?.licenseeName || '光云');
29 | $('#assigneeName').val(licenseInfo?.assigneeName || '藏柏');
30 | $('#expiryDate').val(licenseInfo?.expiryDate || '2111-11-11');
31 | $('#mask, #form').show();
32 | };
33 |
34 | // Function to show VM options
35 | window.showVmoptins = function () {
36 | var text = "-javaagent:/(Your Path)/ja-netfilter/ja-netfilter.jar\n" +
37 | "--add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED\n" +
38 | "--add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED";
39 | copyText(text)
40 | .then((result) => {
41 | alert(result);
42 | });
43 | };
44 |
45 | // Function to copy license
46 | window.copyLicense = async function (e) {
47 | while (localStorage.getItem('licenseInfo') === null) {
48 | $('#mask, #form').show();
49 | await new Promise(r => setTimeout(r, 1000));
50 | }
51 | let licenseInfo = JSON.parse(localStorage.getItem('licenseInfo'));
52 | let productCode = $(e).closest('.card').data('productCodes');
53 | let data = {
54 | "licenseName": licenseInfo.licenseeName,
55 | "assigneeName": licenseInfo.assigneeName,
56 | "expiryDate": licenseInfo.expiryDate,
57 | "productCode": productCode,
58 | };
59 | $.post('/generateLicense', JSON.stringify(data))
60 | .then(response => {
61 | copyText(response)
62 | .then(() => {
63 | alert("已复制成功");
64 | })
65 | .catch(() => {
66 | alert("系统不支持复制功能,或者当前非SSL访问,若为Local环境,请使用127.0.0.1或者localhost访问.");
67 | });
68 | });
69 | };
70 |
71 | // Function to copy text to clipboard
72 | const copyText = async (val) => {
73 | if (navigator.clipboard && navigator.permissions) {
74 | return navigator.clipboard.writeText(val);
75 | } else {
76 | console.log(val);
77 | const scrollX = window.scrollX;
78 | const textArea = document.createElement('textarea')
79 | textArea.value = val
80 | // 使text area不在viewport,同时设置不可见
81 | document.body.appendChild(textArea)
82 | textArea.focus()
83 | textArea.select()
84 | try {
85 | const result = document.execCommand('copy');
86 | return result ? Promise.resolve() : Promise.reject();
87 | } catch (e) {
88 | return Promise.reject(e);
89 | } finally {
90 | textArea.remove();
91 | window.scrollTo(scrollX, 0);
92 | }
93 | }
94 | };
95 |
96 | });
97 |
--------------------------------------------------------------------------------
/src/main/resources/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Jetbrains-Help
8 |
9 |
10 |
11 |
12 |
25 |
26 |
27 |
28 |
30 |
38 |
39 |
41 |
43 | *********************************************************************************************************************************************************
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
53 |
62 |
63 |
65 |
67 | *********************************************************************************************************************************************************
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
78 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------