├── .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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 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 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 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 |
13 |

14 | 🇨🇳 下载 ja-netfilter.zip , 然后配置 15 | 你的JetBrains IDE(anything)'s IDE.vmoptions 配置文件!
16 | 🇨🇳 当然你也可以 重新定制激活授权 用以自定义你的激活信息!
17 | 🇨🇳 请注意,此页面仅由 19 | 个人所有! 20 |

21 |
22 | 23 |
24 |
25 |
26 | 27 | 28 |
30 |
31 |
32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 |

41 |

43 | ********************************************************************************************************************************************************* 44 |

45 |
46 |
47 |
48 |
49 |
50 | 51 |
53 |
54 |
55 |
56 | 58 |
59 |
60 |
61 |
62 |
63 |

65 |

67 | ********************************************************************************************************************************************************* 68 |

69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 78 | 103 | 104 | 105 | 106 | --------------------------------------------------------------------------------