├── .DS_Store ├── .gitignore ├── INSTALL-2.0.md ├── INSTALL-2.0.upgrade.md ├── INSTALL.md ├── README.md ├── docker-allinone ├── Dockerfile ├── config-template.yml ├── deploy-to-dockerhub.sh ├── start-go-cqhttp.sh ├── start-webapp.sh ├── supervisord.conf └── updatejar.sh ├── docker-arm ├── Dockerfile ├── MSYH.TTC ├── config-template.yml ├── deploy-to-dockerhub.sh ├── gatgap.py ├── start-go-cqhttp.sh ├── start-webapp.sh └── supervisord.conf ├── docker-base ├── Dockerfile ├── MSYH.TTC └── deploy-to-dockerhub.sh ├── docker-compose-allinone.yml ├── docker-compose ├── config │ └── browsers.json ├── deploy.sh └── docker-compose.yml ├── docker ├── Dockerfile └── deploy-to-dockerhub.sh ├── donate └── Wechat.png ├── env.template.properties ├── install.sh ├── jd-qinglong 1.3版本群晖安装教程.pdf ├── pom.xml ├── src ├── .DS_Store ├── main │ ├── .DS_Store │ ├── java │ │ └── com │ │ │ ├── .DS_Store │ │ │ └── meread │ │ │ └── selenium │ │ │ ├── Application.java │ │ │ ├── bean │ │ │ ├── AssignSessionIdStatus.java │ │ │ ├── CaptchaImg.java │ │ │ ├── JDCookie.java │ │ │ ├── JDLoginType.java │ │ │ ├── JDOpResultBean.java │ │ │ ├── JDScreenBean.java │ │ │ ├── LoginType.java │ │ │ ├── MyChrome.java │ │ │ ├── MyChromeClient.java │ │ │ ├── NodeStatus.java │ │ │ ├── Point.java │ │ │ ├── QLConfig.java │ │ │ ├── QLToken.java │ │ │ ├── QLUploadStatus.java │ │ │ ├── QingLongCookieBean.java │ │ │ ├── SelenoidStatus.java │ │ │ ├── SlotStatus.java │ │ │ ├── StatClient.java │ │ │ └── qq │ │ │ │ ├── GroupMessage.java │ │ │ │ ├── PrivateMessage.java │ │ │ │ └── Sender.java │ │ │ ├── config │ │ │ ├── ChromeSessionInterceptor.java │ │ │ ├── HttpClientConfig.java │ │ │ ├── HttpClientUtil.java │ │ │ ├── RestTemplateConfig.java │ │ │ └── WebMvcConfig.java │ │ │ ├── controller │ │ │ ├── ErrorHandleController.java │ │ │ └── HelloController.java │ │ │ ├── entity │ │ │ └── QQBind.java │ │ │ ├── service │ │ │ ├── BaseWebDriverManager.java │ │ │ ├── BotService.java │ │ │ ├── JDService.java │ │ │ ├── ScheduleService.java │ │ │ ├── WSManager.java │ │ │ ├── WebDriverManager.java │ │ │ ├── WebDriverManagerLocal.java │ │ │ └── WebDriverManagerSelenoid.java │ │ │ ├── util │ │ │ ├── CommonAttributes.java │ │ │ ├── EnumConverter.java │ │ │ ├── FreemarkerUtils.java │ │ │ ├── OpenCVUtil.java │ │ │ ├── ScriptPython.java │ │ │ ├── SlideVerifyBlock.java │ │ │ ├── SpringUtils.java │ │ │ ├── WebDriverOpCallBack.java │ │ │ └── WebDriverUtil.java │ │ │ └── ws │ │ │ ├── MyHandshakeInterceptor.java │ │ │ ├── PageEventHandler.java │ │ │ ├── QQEventHandler.java │ │ │ └── WebSocketConfig.java │ └── resources │ │ ├── .DS_Store │ │ ├── application-debuglocal.properties │ │ ├── application-debugremote.properties │ │ ├── application-local.properties │ │ ├── application.properties │ │ ├── mock_captcha_points.txt │ │ ├── static │ │ ├── css │ │ │ ├── main.css │ │ │ └── slidercaptcha.min.css │ │ ├── favicon.ico │ │ ├── images │ │ │ ├── QQ20211013-125041@2x.png │ │ │ ├── a.jpeg │ │ │ └── a_small.png │ │ ├── img │ │ │ ├── a.jpeg │ │ │ ├── a_small.png │ │ │ ├── b.jpeg │ │ │ ├── b_small.png │ │ │ ├── c.jpeg │ │ │ ├── c_small.png │ │ │ ├── d.jpeg │ │ │ ├── d_small.png │ │ │ ├── e.jpeg │ │ │ ├── e_small.png │ │ │ ├── f.jpeg │ │ │ ├── f_small.png │ │ │ ├── g.jpeg │ │ │ ├── g_small.png │ │ │ ├── h.jpeg │ │ │ ├── h_small.png │ │ │ ├── i.jpeg │ │ │ └── i_small.png │ │ └── js │ │ │ ├── common.js │ │ │ ├── jquery.min.js │ │ │ ├── layer │ │ │ ├── layer.js │ │ │ ├── mobile │ │ │ │ ├── layer.js │ │ │ │ └── need │ │ │ │ │ └── layer.css │ │ │ └── theme │ │ │ │ └── default │ │ │ │ ├── icon-ext.png │ │ │ │ ├── icon.png │ │ │ │ ├── layer.css │ │ │ │ ├── loading-0.gif │ │ │ │ ├── loading-1.gif │ │ │ │ └── loading-2.gif │ │ │ ├── longbow.slidercaptcha.min.js │ │ │ └── ws.js │ │ └── templates │ │ ├── error │ │ ├── 404.ftl │ │ └── 500.ftl │ │ ├── fragment │ │ ├── chooseQL.ftl │ │ └── uploadRes.ftl │ │ ├── login.ftl │ │ ├── mock.ftl │ │ └── ws.ftl └── test │ └── java │ └── com │ └── meread │ └── selenium │ ├── DockerTest.java │ ├── GettingStartedWithService.java │ ├── GoogleSearchTest.java │ ├── LocalDriverTest.java │ ├── QRTest.java │ ├── TestBase.java │ ├── TestBaseSelenium.java │ ├── TestBaseSelenoid.java │ └── WsTest.java ├── 修复版安装教程.md └── 安装教程.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Eclipse template 2 | .metadata 3 | bin/ 4 | tmp/ 5 | *.tmp 6 | *.bak 7 | *.swp 8 | *~.nib 9 | local.properties 10 | .settings/ 11 | .loadpath 12 | .recommenders 13 | 14 | # External tool builders 15 | .externalToolBuilders/ 16 | 17 | # Locally stored "Eclipse launch configurations" 18 | *.launch 19 | 20 | # PyDev specific (Python IDE for Eclipse) 21 | *.pydevproject 22 | 23 | # CDT-specific (C/C++ Development Tooling) 24 | .cproject 25 | 26 | # CDT- autotools 27 | .autotools 28 | 29 | # Java annotation processor (APT) 30 | .factorypath 31 | 32 | # PDT-specific (PHP Development Tools) 33 | .buildpath 34 | 35 | # sbteclipse plugin 36 | .target 37 | 38 | # Tern plugin 39 | .tern-project 40 | 41 | # TeXlipse plugin 42 | .texlipse 43 | 44 | # STS (Spring Tool Suite) 45 | .springBeans 46 | 47 | # Code Recommenders 48 | .recommenders/ 49 | 50 | # Annotation Processing 51 | .apt_generated/ 52 | .apt_generated_test/ 53 | 54 | # Scala IDE specific (Scala & Java development for Eclipse) 55 | .cache-main 56 | .scala_dependencies 57 | .worksheet 58 | 59 | # Uncomment this line if you wish to ignore the project description file. 60 | # Typically, this file would be tracked if it contains build/dependency configurations: 61 | #.project 62 | 63 | ### Java template 64 | # Compiled class file 65 | *.class 66 | 67 | # Log file 68 | *.log 69 | 70 | # BlueJ files 71 | *.ctxt 72 | 73 | # Mobile Tools for Java (J2ME) 74 | .mtj.tmp/ 75 | 76 | # Package Files # 77 | *.jar 78 | *.war 79 | *.nar 80 | *.ear 81 | *.zip 82 | *.tar.gz 83 | *.rar 84 | 85 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 86 | hs_err_pid* 87 | 88 | ### JetBrains template 89 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 90 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 91 | 92 | # User-specific stuff 93 | .idea/**/workspace.xml 94 | .idea/**/tasks.xml 95 | .idea/**/usage.statistics.xml 96 | .idea/**/dictionaries 97 | .idea/**/shelf 98 | 99 | # Generated files 100 | .idea/**/contentModel.xml 101 | 102 | # Sensitive or high-churn files 103 | .idea/**/dataSources/ 104 | .idea/**/dataSources.ids 105 | .idea/**/dataSources.local.xml 106 | .idea/**/sqlDataSources.xml 107 | .idea/**/dynamic.xml 108 | .idea/**/uiDesigner.xml 109 | .idea/**/dbnavigator.xml 110 | 111 | # Gradle 112 | .idea/**/gradle.xml 113 | .idea/**/libraries 114 | 115 | # Gradle and Maven with auto-import 116 | # When using Gradle or Maven with auto-import, you should exclude module files, 117 | # since they will be recreated, and may cause churn. Uncomment if using 118 | # auto-import. 119 | # .idea/artifacts 120 | # .idea/compiler.xml 121 | # .idea/jarRepositories.xml 122 | # .idea/modules.xml 123 | # .idea/*.iml 124 | # .idea/modules 125 | # *.iml 126 | # *.ipr 127 | 128 | # CMake 129 | cmake-build-*/ 130 | 131 | # Mongo Explorer plugin 132 | .idea/**/mongoSettings.xml 133 | 134 | # File-based project format 135 | *.iws 136 | 137 | # IntelliJ 138 | out/ 139 | 140 | # mpeltonen/sbt-idea plugin 141 | .idea_modules/ 142 | 143 | # JIRA plugin 144 | atlassian-ide-plugin.xml 145 | 146 | # Cursive Clojure plugin 147 | .idea/replstate.xml 148 | 149 | # Crashlytics plugin (for Android Studio and IntelliJ) 150 | com_crashlytics_export_strings.xml 151 | crashlytics.properties 152 | crashlytics-build.properties 153 | fabric.properties 154 | 155 | # Editor-based Rest Client 156 | .idea/httpRequests 157 | 158 | # Android studio 3.1+ serialized cache file 159 | .idea/caches/build_file_checksums.ser 160 | 161 | target 162 | .idea 163 | 164 | env.properties 165 | qqbot 166 | docker-allinone/notify 167 | .docker 168 | .DS_Store 169 | go-cqhttp 170 | chromedriver* -------------------------------------------------------------------------------- /INSTALL-2.0.md: -------------------------------------------------------------------------------- 1 | ### 安装教程: 2 | 1. 创建一个空目录(用于存放env.properties和go-cqhttp数据) 3 | ``` 4 | mkdir jd-qinglong && cd jd-qinglong 5 | ``` 6 | 2. 下载配置文件模板,根据需要修改,不要缺少此文件 7 | ``` 8 | wget -O env.properties https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/env.template.properties 9 | ``` 10 | 国内请使用: 11 | ``` 12 | wget -O env.properties https://ghproxy.com/https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/env.template.properties 13 | ``` 14 | 3. 下载rubyangxg/jd-qinglong镜像,**_注意arm的请把1.9替换为arm_** 15 | ``` 16 | sudo docker pull rubyangxg/jd-qinglong:2.0 17 | ``` 18 | 4. 启动,其中env.properties中的SE_NODE_MAX_SESSIONS=8请根据机器配置改,机器要求最少1h2g,推荐2h4g **_注意这是1条命令,全部复制执行,注意\后面不要有空格_** 19 | ``` 20 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true -v [你的路径]/env.properties:/env.properties:rw -v [你的路径]/adbot:/adbot rubyangxg/jd-qinglong:2.0 21 | ``` 22 | 或者 23 | ``` 24 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true --restart always \ 25 | -v [你的路径]/env.properties:/env.properties:rw \ 26 | -v [你的路径]/adbot:/adbot \ 27 | rubyangxg/jd-qinglong:2.0 28 | ``` 29 | arm的启动有所不同,请仔细甄别 30 | ``` 31 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true --restart always \ 32 | -e "SPRING_PROFILES_ACTIVE=arm" \ 33 | -v [你的路径]/env.properties:/env.properties:rw \ 34 | -v [你的路径]/adbot:/adbot \ 35 | rubyangxg/jd-qinglong:2.0 36 | ``` 37 | 例子:**_注意这是1条命令,全部复制执行,注意\后面不要有空格_**, 38 | ``` 39 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true --restart always \ 40 | -v "$(pwd)"/env.properties:/env.properties:rw \ 41 | -v "$(pwd)"/adbot:/adbot \ 42 | rubyangxg/jd-qinglong:2.0 43 | ``` 44 | 或者编写docker-compose.yml 45 | ``` 46 | version: '3.3' 47 | services: 48 | jd-qinglong: 49 | ports: 50 | - 5701:8080 51 | - 9527:8090 52 | container_name: jd-login 53 | privileged: true 54 | volumes: 55 | - ./env.properties:/env.properties:rw 56 | - ./adbot:/adbot 57 | image: rubyangxg/jd-qinglong:2.0 58 | ``` 59 | 然后在docker-compose.yml目录下执行命令 60 | ``` 61 | docker-compose up -d 62 | ``` 63 | 5. 若要配置qq交互,往下看。 64 | 6. 使用: 65 | * env.properties中的ADBOT_QQ和ADBOT_PASSWORD必须配置,否则不能自动登录和识别机器人 66 | * 启动镜像后,请先访问8100,理论上会有一个待认证的机器人,你自己认证就行。如果没有,自行登录你的qq机器人(env.properties配置的那个),优先选择扫码登录,按照提示操作就行。 67 | * 登录成功后,重启镜像docker restart webapp 68 | * 如果碰到机器人假死,请执行 --> 重启 adbot 69 | 70 | 7. **恭喜你安装成功。好用的话给我点个star吧!** 71 | ### 更新教程: 72 | ``` 73 | docker rm -f webapp 74 | docker rmi rubyangxg/jd-qinglong:2.0 75 | ``` 76 | **上面两条命令执行完毕后,重新运行启动命令(安装教程第4步)** 77 | -------------------------------------------------------------------------------- /INSTALL-2.0.upgrade.md: -------------------------------------------------------------------------------- 1 | ### 本教程只适用于老版本升级过来的,需要改动的地方如下: 2 | * 以防万一,**升级前请务必备份go-cqhttp目录中的ql.db到其他文件夹,go-cqhttp文件夹不要删** 3 | * 以防万一,**升级前请务必备份go-cqhttp目录中的ql.db到其他文件夹,go-cqhttp文件夹不要删** 4 | * 以防万一,**升级前请务必备份go-cqhttp目录中的ql.db到其他文件夹,go-cqhttp文件夹不要删** 5 | 6 | 1. 镜像版本号升级为2.0 **_注意arm的请把2.0替换为arm_** 7 | ``` 8 | sudo docker pull rubyangxg/jd-qinglong:2.0 9 | ``` 10 | 2. 机器人新名字adbot(阿东机器人,阿这个字有点像狗),env.properties必须增加如下设置: 11 | ``` 12 | #########adbot管理平台用户名密码,请务必改为自己的######### 13 | AD_ADMIN_USERNAME=admin 14 | AD_ADMIN_PASSWORD=adbotadmin 15 | ##################### 16 | 17 | #########adbot(机器人qq)用户名密码######### 18 | ADBOT_QQ= 19 | ADBOT_QQ_PASSWORD= 20 | ##################### 21 | 22 | #########adbot回复消息模式,0私聊,1群聊######### 23 | ADBOT_REPLY_TYPE=0 24 | ##################### 25 | 26 | #########青龙选择模式 27 | # 0:显示青龙概要信息,让用户自己选择######### 28 | # 1:自动上传所有青龙中容量最大的,容量相同的,按配置顺序######### 29 | # 2:按配置顺序上传,满了则下一个######### 30 | QL_CHOOSE_TYPE=2 31 | ##################### 32 | ``` 33 | 3. 启动命令修改: 34 | * 增加了-p 8100:8100,左边的8100可自定义,需要开外网访问,用于网页上登录adbot,登录的用户名密码参见上方AD_ADMIN_USERNAME和AD_ADMIN_PASSWORD。不使用adbot的,无需映射8100端口 35 | * 挂载env.properties:ro改为env.properties:rw,用于之后做配置热生效。 36 | * 机器人实现替代~~go-cqhttp~~,统一为adbot,启动命令**_不要挂载go-cqhttp_**, 请仔细甄别。 37 | * 如果复制出来的命令\后面有空格,请去掉 38 | ``` 39 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true -v [你的路径]/env.properties:/env.properties:rw -v [你的路径]/adbot:/adbot rubyangxg/jd-qinglong:2.0 40 | ``` 41 | 或者 42 | ``` 43 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true --restart always \ 44 | -v [你的路径]/env.properties:/env.properties:rw \ 45 | -v [你的路径]/adbot:/adbot \ 46 | rubyangxg/jd-qinglong:2.0 47 | ``` 48 | arm的启动有所不同,请仔细甄别 49 | ``` 50 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true --restart always \ 51 | -e "SPRING_PROFILES_ACTIVE=arm" \ 52 | -v [你的路径]/env.properties:/env.properties:rw \ 53 | -v [你的路径]/adbot:/adbot \ 54 | rubyangxg/jd-qinglong:2.0 55 | ``` 56 | 例如:**_注意这是1条命令,全部复制执行,注意\后面不要有空格_**, 57 | ``` 58 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true --restart always \ 59 | -v "$(pwd)"/env.properties:/env.properties:rw \ 60 | -v "$(pwd)"/adbot:/adbot \ 61 | rubyangxg/jd-qinglong:2.0 62 | ``` 63 | 或者编写docker-compose.yml 64 | ``` 65 | version: '3.3' 66 | services: 67 | jd-qinglong: 68 | ports: 69 | - 5701:8080 70 | - 9527:8090 71 | container_name: jd-login 72 | privileged: true 73 | volumes: 74 | - ./env.properties:/env.properties:rw 75 | - ./adbot:/adbot 76 | image: rubyangxg/jd-qinglong:2.0 77 | ``` 78 | 然后在docker-compose.yml目录下执行命令 79 | ``` 80 | docker-compose up -d 81 | ``` 82 | 4. 使用: 83 | * 启动镜像后,请先访问8100,理论上会有一个待认证的机器人,你自己认证就行。如果没有,自行登录你的qq机器人(env.properties配置的那个),优先选择扫码登录,按照提示操作就行。 84 | * 登录成功后,重启镜像docker restart webapp 85 | * 如果碰到机器人假死,请执行 --> 重启 adbot 86 | 87 | 5. **恭喜你安装成功。好用的话给我点个star吧!** 88 | ### 更新教程: 89 | ``` 90 | docker rm -f webapp 91 | docker rmi rubyangxg/jd-qinglong:2.0 92 | ``` 93 | ``` 94 | docker exec -it webapp guide 95 | ``` 96 | **上面两条命令执行完毕后,重新运行启动命令(安装教程第4步)** 97 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | ### 安装教程: 2 | 1. 创建一个空目录(用于存放env.properties和go-cqhttp数据) 3 | ``` 4 | mkdir jd-qinglong && cd jd-qinglong 5 | ``` 6 | 2. 下载配置文件模板,根据需要修改,不要缺少此文件 7 | ``` 8 | wget -O env.properties https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/env.template.properties 9 | ``` 10 | 国内请使用: 11 | ``` 12 | wget -O env.properties https://ghproxy.com/https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/env.template.properties 13 | ``` 14 | 3. 下载rubyangxg/jd-qinglong镜像,**_注意arm的镜像请用rubyangxg/jd-qinglong:arm_** 15 | ``` 16 | sudo docker pull rubyangxg/jd-qinglong 17 | ``` 18 | 4. 启动,其中env.properties中的SE_NODE_MAX_SESSIONS=8请根据机器配置改,机器要求最少1h2g,推荐2h4g **_注意这是1条命令,全部复制执行_** 19 | ``` 20 | sudo docker run -d -p 5701:8080 -p 8100:8100 --name=webapp --restart always --privileged=true -v "$(pwd)"/env.properties:/env.properties:rw -v "$(pwd)"/adbot:/adbot rubyangxg/jd-qinglong 21 | ``` 22 | 或者 23 | ``` 24 | sudo docker run -d -p 5701:8080 -p 8100:8100 --name=webapp --privileged=true --restart always \ 25 | -v [你的路径]/env.properties:/env.properties:rw \ 26 | -v [你的路径]/adbot:/adbot \ 27 | rubyangxg/jd-qinglong 28 | ``` 29 | arm的启动有所不同,请仔细甄别 30 | ``` 31 | sudo docker run -d -p 5701:8080 -p 8100:8100 --name=webapp --privileged=true --restart always \ 32 | -e "SPRING_PROFILES_ACTIVE=arm" \ 33 | -v [你的路径]/env.properties:/env.properties:rw \ 34 | -v [你的路径]/adbot:/adbot \ 35 | rubyangxg/jd-qinglong 36 | ``` 37 | 例子:**_注意这是1条命令,全部复制执行,注意\后面不要有空格_**, 38 | ``` 39 | sudo docker run -d -p 5701:8080 -p 8100:8100 --name=webapp --privileged=true --restart always \ 40 | -v "$(pwd)"/env.properties:/env.properties:rw \ 41 | -v "$(pwd)"/adbot:/adbot \ 42 | rubyangxg/jd-qinglong 43 | ``` 44 | 或者编写docker-compose.yml 45 | ``` 46 | version: '3.3' 47 | services: 48 | jd-qinglong: 49 | ports: 50 | - 5701:8080 51 | - 8100:8100 52 | container_name: jd-login 53 | privileged: true 54 | volumes: 55 | - ./env.properties:/env.properties:rw 56 | - ./adbot:/adbot 57 | image: rubyangxg/jd-qinglong 58 | ``` 59 | 然后在docker-compose.yml目录下执行命令 60 | ``` 61 | docker-compose up -d 62 | ``` 63 | 5. 若要配置qq交互,往下看。 64 | 6. 使用: 65 | * env.properties中的ADBOT_QQ和ADBOT_PASSWORD必须配置,否则不能自动登录和识别机器人 66 | * 启动镜像后,请先访问8100,理论上会有一个待认证的机器人,你自己认证就行。如果没有,自行登录你的qq机器人(env.properties配置的那个),优先选择扫码登录,按照提示操作就行。 67 | * 登录成功后,重启镜像docker restart webapp 68 | * 如果碰到机器人假死,请执行 --> 重启 adbot 69 | 70 | 7. **恭喜你安装成功。好用的话给我点个star吧!** 71 | ### 更新教程: 72 | #### 进入你的安装目录: 73 | ``` 74 | cd jd-qinglong 75 | docker rm -f webapp && docker rmi rubyangxg/jd-qinglong && docker pull rubyangxg/jd-qinglong 76 | sudo docker run -d -p 5701:8080 -p 8100:8100 --name=webapp --privileged=true -v "$(pwd)"/env.properties:/env.properties:rw -v "$(pwd)"/adbot:/adbot rubyangxg/jd-qinglong 77 | ``` 78 | **上面两条命令执行完毕后,重新运行启动命令(安装教程第4步)** 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 【代码仅供参考学习,禁止商用和非法用途,否则,一切后果请用户自负。】 4 |

5 | 6 |
7 | 8 | [![docker version][docker-version-image]][docker-version-url] [![docker pulls][docker-pulls-image]][docker-pulls-url] [![docker stars][docker-stars-image]][docker-stars-url] [![docker image size][docker-image-size-image]][docker-image-size-url] 9 | 10 | [docker-pulls-image]: https://img.shields.io/docker/pulls/rubyangxg/jd-qinglong?style=flat 11 | [docker-pulls-url]: https://hub.docker.com/r/rubyangxg/jd-qinglong 12 | [docker-version-image]: https://img.shields.io/docker/v/rubyangxg/jd-qinglong?style=flat 13 | [docker-version-url]: https://hub.docker.com/r/rubyangxg/jd-qinglong/tags?page=1&ordering=last_updated 14 | [docker-stars-image]: https://img.shields.io/docker/stars/rubyangxg/jd-qinglong?style=flat 15 | [docker-stars-url]: https://hub.docker.com/r/rubyangxg/jd-qinglong 16 | [docker-image-size-image]: https://img.shields.io/docker/image-size/rubyangxg/jd-qinglong?style=flat 17 | [docker-image-size-url]: https://hub.docker.com/r/rubyangxg/jd-qinglong 18 |
19 | 20 | ## 提醒一下 21 | * 请选择性能好的设备运行,最好是云服务器或pc、x86软路由、oracle-arm(4-24)、群晖。 22 | * 只支持青龙2.9+,2.8可能会出现无法上传问题。 23 | * 本项目可以不依赖青龙运行,获取的CK需网页上手工复制 24 | 25 | ## 一键安装(centos7+,ubuntu18+,debian9+,群晖,甲骨文arm需要ubuntu) 26 | ## (请事先切换root执行,并安装curl) 27 | **已经安装过的,请在jd-qinglong同级目录下执行,新安装的执行后会生成jd-qinglong文件夹** 28 | 29 | 国外用: 30 | ``` 31 | bash <(curl -s -L https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/install.sh) 32 | ``` 33 | 国内用: 34 | ``` 35 | bash <(curl -s -L https://ghproxy.com/https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/install.sh) 36 | ``` 37 | ## 如何安装 38 | * [源码地址](https://github.com/rubyangxg/jd-qinglong) 39 | * **群晖**安装参考源码路径下的 **jd-qinglong-群晖安装教程.pdf** 40 | * 修复版安装教程: [修复版安装教程.md](修复版安装教程.md) 41 | * ~~升级教程~~: [INSTALL-2.0.upgrade.md](INSTALL-2.0.upgrade.md) 42 | * ~~2.0安装教程~~: [INSTALL-2.0.md](INSTALL-2.0.md) 43 | * 整合安装教程与注意事项: [安装教程.md](安装教程.md) 44 | * 问题反馈移步: [TG交流群](https://t.me/joinchat/3JfrwNPoHFY2MGNl) 45 | 46 | ## 特别声明: 47 | 48 | * 本仓库涉及的任何解锁和解密分析脚本或代码,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断. 49 | 50 | * 本项目内所有资源文件,禁止任何公众号、自媒体进行任何形式的转载、发布。 51 | 52 | * rubyangxg对任何代码问题概不负责,包括但不限于由任何脚本错误导致的任何损失或损害. 53 | 54 | * 间接使用本仓库搭建的任何用户,包括但不限于建立VPS或在某些行为违反国家/地区法律或相关法规的情况下进行传播, rubyangxg对于由此引起的任何隐私泄漏或其他后果概不负责. 55 | 56 | * 请勿将本项目的任何内容用于商业或非法目的,否则后果自负. 57 | 58 | * 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关代码. 59 | 60 | * 任何以任何方式查看此项目的人或直接或间接使用本仓库项目的任何脚本的使用者都应仔细阅读此声明。rubyangxg 保留随时更改或补充此免责声明的权利。一旦使用并复制了任何本仓库项目的规则,则视为您已接受此免责声明. 61 | 62 | **您必须在下载后的24小时内从计算机或手机中完全删除以上内容.**
63 | > ***您使用或者复制了本仓库且本人制作的任何脚本,则视为`已接受`此声明,请仔细阅读*** 64 | 65 | ## 多谢 66 | 67 | * [青龙面板](https://github.com/whyour/qinglong) 68 | 69 | * [docker-selenium](https://github.com/SeleniumHQ/docker-selenium) 70 | ## 支持 71 | 如果觉得我的项目对你小有帮助 72 | 那么下面的微信赞赏码可以扫一扫啦: 73 | 微信小程序
74 | -------------------------------------------------------------------------------- /docker-allinone/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rubyangxg/selenium-base 2 | COPY config-template.yml / 3 | ARG JAR_FILE 4 | COPY ${JAR_FILE} app.jar 5 | COPY notify /opt/bin/notify 6 | #COPY jd_bean_change /opt/bin/jd_bean_change 7 | COPY start-webapp.sh /opt/bin/start-webapp.sh 8 | COPY start-go-cqhttp.sh /opt/bin/start-go-cqhttp.sh 9 | RUN chmod 755 /opt/bin/start-webapp.sh && chmod 755 /opt/bin/start-go-cqhttp.sh && chmod 755 /opt/bin/notify 10 | VOLUME /tmp 11 | EXPOSE 8080 12 | COPY supervisord.conf /etc/supervisord.conf 13 | CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] 14 | #JAVA_OPTS="-server -Xmx2g -Xms2g -Xss512k -Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom " 15 | #DEBUG_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8001,suspend=n " 16 | #ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom -jar -Dserver.port=8080 app.jar"] -------------------------------------------------------------------------------- /docker-allinone/config-template.yml: -------------------------------------------------------------------------------- 1 | # go-cqhttp 默认配置文件 2 | 3 | account: # 账号相关 4 | uin: XXXXXXXXX # QQ账号 5 | password: 'XXXXXXXXX' # 密码为空时使用扫码登录 6 | encrypt: false # 是否开启密码加密 7 | status: 0 # 在线状态 请参考 https://docs.go-cqhttp.org/guide/config.html#在线状态 8 | relogin: # 重连设置 9 | delay: 3 # 首次重连延迟, 单位秒 10 | interval: 3 # 重连间隔 11 | max-times: 0 # 最大重连次数, 0为无限制 12 | 13 | # 是否使用服务器下发的新地址进行重连 14 | # 注意, 此设置可能导致在海外服务器上连接情况更差 15 | use-sso-address: true 16 | 17 | heartbeat: 18 | # 心跳频率, 单位秒 19 | # -1 为关闭心跳 20 | interval: 5 21 | 22 | message: 23 | # 上报数据类型 24 | # 可选: string,array 25 | post-format: string 26 | # 是否忽略无效的CQ码, 如果为假将原样发送 27 | ignore-invalid-cqcode: false 28 | # 是否强制分片发送消息 29 | # 分片发送将会带来更快的速度 30 | # 但是兼容性会有些问题 31 | force-fragment: false 32 | # 是否将url分片发送 33 | fix-url: false 34 | # 下载图片等请求网络代理 35 | proxy-rewrite: '' 36 | # 是否上报自身消息 37 | report-self-message: false 38 | # 移除服务端的Reply附带的At 39 | remove-reply-at: false 40 | # 为Reply附加更多信息 41 | extra-reply-data: false 42 | # 跳过 Mime 扫描, 忽略错误数据 43 | skip-mime-scan: false 44 | 45 | output: 46 | # 日志等级 trace,debug,info,warn,error 47 | log-level: warn 48 | # 日志时效 单位天. 超过这个时间之前的日志将会被自动删除. 设置为 0 表示永久保留. 49 | log-aging: 15 50 | # 是否在每次启动时强制创建全新的文件储存日志. 为 false 的情况下将会在上次启动时创建的日志文件续写 51 | log-force-new: true 52 | # 是否启用 DEBUG 53 | debug: false # 开启调试模式 54 | 55 | # 默认中间件锚点 56 | default-middlewares: &default 57 | # 访问密钥, 强烈推荐在公网的服务器设置 58 | access-token: '' 59 | # 事件过滤器文件目录 60 | filter: '' 61 | # API限速设置 62 | # 该设置为全局生效 63 | # 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配 64 | # 目前该限速设置为令牌桶算法, 请参考: 65 | # https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin 66 | rate-limit: 67 | enabled: false # 是否启用限速 68 | frequency: 1 # 令牌回复频率, 单位秒 69 | bucket: 1 # 令牌桶大小 70 | 71 | database: # 数据库相关设置 72 | leveldb: 73 | # 是否启用内置leveldb数据库 74 | # 启用将会增加10-20MB的内存占用和一定的磁盘空间 75 | # 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能 76 | enable: true 77 | 78 | # 连接服务列表 79 | # 连接服务列表 80 | servers: 81 | # 添加方式,同一连接方式可添加多个,具体配置说明请查看文档 82 | #- http: # http 通信 83 | #- ws: # 正向 Websocket 84 | #- ws-reverse: # 反向 Websocket 85 | #- pprof: #性能分析服务器 86 | # 正向WS设置 87 | # - ws: 88 | # # 正向WS服务器监听地址 89 | # host: 127.0.0.1 90 | # # 正向WS服务器监听端口 91 | # port: 6700 92 | # middlewares: 93 | # <<: *default # 引用默认中间件 94 | # 反向WS设置 95 | - ws-reverse: 96 | # 反向WS Universal 地址 97 | # 注意 设置了此项地址后下面两项将会被忽略 98 | universal: ws://127.0.0.1:8080/ws/event 99 | # 反向WS API 地址 100 | api: 101 | # 反向WS Event 地址 102 | event: 103 | # 重连间隔 单位毫秒 104 | reconnect-interval: 3000 105 | middlewares: 106 | <<: *default # 引用默认中间件 -------------------------------------------------------------------------------- /docker-allinone/deploy-to-dockerhub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | detect_macos() { 4 | [[ $(uname -s) == Darwin ]] && is_macos=1 || is_macos=0 5 | } 6 | 7 | detect_macos 8 | 9 | #当前目录 10 | HOME=$( 11 | cd $(dirname $0) 12 | pwd 13 | ) 14 | 15 | git pull 16 | 17 | op=$1 18 | if [[ $op == 'push' ]]; then 19 | echo "构建并推送镜像" 20 | else 21 | op='local' 22 | echo "本地构建" 23 | fi 24 | 25 | if [ ! -f "$HOME/notify" ];then 26 | rm -rf qinglong && mkdir qinglong 27 | rm -rf notify 28 | git clone -b master --depth=1 https://github.com/whyour/qinglong.git 29 | cd qinglong/sample || exit 30 | 31 | if [[ $is_macos == 1 ]]; then 32 | sed -i '' 's/https:\/\/github.com\/whyour\/qinglong/https:\/\/github.com\/rubyangxg\/jd-qinglong/' notify.js 33 | else 34 | sed -i 's/https:\/\/github.com\/whyour\/qinglong/https:\/\/github.com\/rubyangxg\/jd-qinglong/' notify.js 35 | fi 36 | 37 | npm install 38 | cd ../shell || exit 39 | if [[ $is_macos == 1 ]]; then 40 | sed -i '' 's/\/ql\/scripts\/sendNotify.js/..\/sample\/notify.js/' notify.js 41 | else 42 | sed -i 's/\/ql\/scripts\/sendNotify.js/..\/sample\/notify.js/' notify.js 43 | fi 44 | 45 | pkg -t node16-linux-x64 notify.js 46 | cp notify $HOME/notify 47 | else 48 | echo "notify已存在" 49 | fi 50 | 51 | #if [ ! -f "$HOME/jd_bean_change" ];then 52 | # rm -rf JD_tencent_scf && mkdir JD_tencent_scf 53 | # rm -rf jd_bean_change 54 | # git clone -b master --depth=1 https://github.com/zero205/JD_tencent_scf.git 55 | # npm install 56 | # pkg -t node16-linux-x64 jd_bean_change.js 57 | # cp jd_bean_change $HOME/jd_bean_change 58 | #else 59 | # echo "jd_bean_change已存在" 60 | #fi 61 | 62 | cd $HOME || exit 63 | rm -rf .npm 64 | rm -rf .pkg-cache 65 | rm -rf qinglong 66 | 67 | docker rm -f webapp 68 | docker rmi -f rubyangxg/jd-qinglong:latest 69 | docker rmi -f rubyangxg/jd-qinglong:1.3 70 | 71 | if [ ! -f "$HOME/jd-qinglong-1.0.jar" ];then 72 | cd .. 73 | git pull 74 | mvn clean package -Dmaven.test.skip=true 75 | cp target/jd-qinglong-*.jar docker-allinone 76 | cd docker-allinone || exit 77 | else 78 | echo "jd-qinglong-1.0.jar已存在" 79 | fi 80 | 81 | docker build -t rubyangxg/jd-qinglong:latest --build-arg JAR_FILE=jd-qinglong-1.0.jar . 82 | docker build -t rubyangxg/jd-qinglong:1.3 --build-arg JAR_FILE=jd-qinglong-1.0.jar . 83 | if [[ $op == 'push' ]]; then 84 | docker login 85 | docker push rubyangxg/jd-qinglong:latest 86 | docker push rubyangxg/jd-qinglong:1.3 87 | fi 88 | 89 | cd /root/run 90 | docker stop webapp && docker rm -f webapp && docker rmi rubyangxg/jd-qinglong:1.3 91 | docker run -d -p 5701:8080 -p 8001:8001 --name=webapp --privileged=true -e "SPRING_PROFILES_ACTIVE=debugremote" -v "$(pwd)"/env.properties:/env.properties:ro -v "$(pwd)"/go-cqhttp:/go-cqhttp rubyangxg/jd-qinglong:1.3 92 | #docker run -d -p 5701:8080 --name=webapp --privileged=true -v "$(pwd)"/env.properties:/env.properties:ro -v "$(pwd)"/go-cqhttp:/go-cqhttp rubyangxg/jd-qinglong:1.3 93 | 94 | #mvn clean package -Dmaven.test.skip=true && docker-compose -f docker-compose-debug.yml --env-file=env.properties build --no-cache webapp 95 | #docker-compose -f docker-compose-debug.yml --env-file=env.properties up -d --no-deps && docker logs -f webapp 96 | -------------------------------------------------------------------------------- /docker-allinone/start-go-cqhttp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | file=/go-cqhttp/go-cqhttp 4 | if [ ! -f "$file" ]; then 5 | wget -O "go-cqhttp.tar.gz" https://ghproxy.com/https://github.com/Mrs4s/go-cqhttp/releases/download/v1.0.0-beta7-fix2/go-cqhttp_linux_amd64.tar.gz 6 | tar -zxvf go-cqhttp.tar.gz -C /go-cqhttp/ 7 | chmod +x /go-cqhttp/* 8 | cp /config-template.yml /go-cqhttp/config.yml 9 | else 10 | echo "go-cqhttp已存在" 11 | fi 12 | 13 | cd /go-cqhttp 14 | ./go-cqhttp -------------------------------------------------------------------------------- /docker-allinone/start-webapp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "开始启动webapp" 3 | java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8001 -Djava.security.egd=file:/dev/./urandom -jar -Dserver.port=8080 /app.jar -------------------------------------------------------------------------------- /docker-allinone/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:webapp] 5 | command=/opt/bin/start-webapp.sh 6 | autostart=true # 自动启动 7 | autorestart=false 8 | startsecs=0 9 | startretries=0 10 | 11 | ;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs" 12 | redirect_stderr=true 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | 16 | [program:qqbot] 17 | command=/opt/bin/start-go-cqhttp.sh 18 | autostart=true 19 | autorestart=false 20 | startsecs=0 21 | startretries=0 -------------------------------------------------------------------------------- /docker-allinone/updatejar.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | #当前目录 4 | HOME=$( 5 | cd $(dirname $0) 6 | pwd 7 | ) 8 | file=$HOME/jd-qinglong 9 | if [ ! -d "$file" ]; then 10 | git clone https://ghproxy.com/https://github.com/rubyangxg/jd-qinglong.git 11 | cd jd-qinglong 12 | else 13 | cd jd-qinglong 14 | echo "jd-qinglong已存在" 15 | git reset --hard 16 | git pull 17 | fi 18 | mvn clean package -Dmaven.test.skip=true 19 | cp target/jd-qinglong-1.0.jar ../ 20 | cd $HOME 21 | docker cp jd-qinglong-1.0.jar webapp-selenoid:/app.jar 22 | docker restart webapp-selenoid 23 | docker logs -f webapp-selenoid -------------------------------------------------------------------------------- /docker-arm/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM arm64v8/openjdk:11-jdk 2 | 3 | ENV TIME_ZONE=Asia/Shanghai 4 | 5 | VOLUME /tmp 6 | ARG JAR_FILE 7 | 8 | COPY config-template.yml / 9 | 10 | COPY ${JAR_FILE} app.jar 11 | COPY notify /opt/bin/notify 12 | #COPY jd_bean_change /opt/bin/jd_bean_change 13 | COPY start-webapp.sh /opt/bin/start-webapp.sh 14 | COPY start-go-cqhttp.sh /opt/bin/start-go-cqhttp.sh 15 | #COPY gatgap.py /opt/bin/gatgap.py 16 | #libopencv-dev python3-opencv 17 | 18 | RUN apt-get update && apt-get install -y lsof tzdata ca-certificates tzdata mailcap supervisor curl chromium chromium-driver && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ 19 | chmod 755 /opt/bin/start-webapp.sh ; \ 20 | chmod 755 /opt/bin/start-go-cqhttp.sh ; \ 21 | chmod 755 /opt/bin/notify 22 | # chmod 755 /opt/bin/gatgap.py 23 | 24 | EXPOSE 8080 25 | COPY supervisord.conf /etc/supervisord.conf 26 | CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] 27 | #JAVA_OPTS="-server -Xmx2g -Xms2g -Xss512k -Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom " 28 | #DEBUG_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8001,suspend=n " 29 | #ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom -jar -Dserver.port=8080 app.jar"] -------------------------------------------------------------------------------- /docker-arm/MSYH.TTC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/docker-arm/MSYH.TTC -------------------------------------------------------------------------------- /docker-arm/config-template.yml: -------------------------------------------------------------------------------- 1 | # go-cqhttp 默认配置文件 2 | 3 | account: # 账号相关 4 | uin: XXXXXXXXX # QQ账号 5 | password: 'XXXXXXXXX' # 密码为空时使用扫码登录 6 | encrypt: false # 是否开启密码加密 7 | status: 0 # 在线状态 请参考 https://docs.go-cqhttp.org/guide/config.html#在线状态 8 | relogin: # 重连设置 9 | delay: 3 # 首次重连延迟, 单位秒 10 | interval: 3 # 重连间隔 11 | max-times: 0 # 最大重连次数, 0为无限制 12 | 13 | # 是否使用服务器下发的新地址进行重连 14 | # 注意, 此设置可能导致在海外服务器上连接情况更差 15 | use-sso-address: true 16 | 17 | heartbeat: 18 | # 心跳频率, 单位秒 19 | # -1 为关闭心跳 20 | interval: 5 21 | 22 | message: 23 | # 上报数据类型 24 | # 可选: string,array 25 | post-format: string 26 | # 是否忽略无效的CQ码, 如果为假将原样发送 27 | ignore-invalid-cqcode: false 28 | # 是否强制分片发送消息 29 | # 分片发送将会带来更快的速度 30 | # 但是兼容性会有些问题 31 | force-fragment: false 32 | # 是否将url分片发送 33 | fix-url: false 34 | # 下载图片等请求网络代理 35 | proxy-rewrite: '' 36 | # 是否上报自身消息 37 | report-self-message: false 38 | # 移除服务端的Reply附带的At 39 | remove-reply-at: false 40 | # 为Reply附加更多信息 41 | extra-reply-data: false 42 | # 跳过 Mime 扫描, 忽略错误数据 43 | skip-mime-scan: false 44 | 45 | output: 46 | # 日志等级 trace,debug,info,warn,error 47 | log-level: warn 48 | # 日志时效 单位天. 超过这个时间之前的日志将会被自动删除. 设置为 0 表示永久保留. 49 | log-aging: 15 50 | # 是否在每次启动时强制创建全新的文件储存日志. 为 false 的情况下将会在上次启动时创建的日志文件续写 51 | log-force-new: true 52 | # 是否启用 DEBUG 53 | debug: false # 开启调试模式 54 | 55 | # 默认中间件锚点 56 | default-middlewares: &default 57 | # 访问密钥, 强烈推荐在公网的服务器设置 58 | access-token: '' 59 | # 事件过滤器文件目录 60 | filter: '' 61 | # API限速设置 62 | # 该设置为全局生效 63 | # 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配 64 | # 目前该限速设置为令牌桶算法, 请参考: 65 | # https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin 66 | rate-limit: 67 | enabled: false # 是否启用限速 68 | frequency: 1 # 令牌回复频率, 单位秒 69 | bucket: 1 # 令牌桶大小 70 | 71 | database: # 数据库相关设置 72 | leveldb: 73 | # 是否启用内置leveldb数据库 74 | # 启用将会增加10-20MB的内存占用和一定的磁盘空间 75 | # 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能 76 | enable: true 77 | 78 | # 连接服务列表 79 | # 连接服务列表 80 | servers: 81 | # 添加方式,同一连接方式可添加多个,具体配置说明请查看文档 82 | #- http: # http 通信 83 | #- ws: # 正向 Websocket 84 | #- ws-reverse: # 反向 Websocket 85 | #- pprof: #性能分析服务器 86 | # 正向WS设置 87 | # - ws: 88 | # # 正向WS服务器监听地址 89 | # host: 127.0.0.1 90 | # # 正向WS服务器监听端口 91 | # port: 6700 92 | # middlewares: 93 | # <<: *default # 引用默认中间件 94 | # 反向WS设置 95 | - ws-reverse: 96 | # 反向WS Universal 地址 97 | # 注意 设置了此项地址后下面两项将会被忽略 98 | universal: ws://127.0.0.1:8080/ws/event 99 | # 反向WS API 地址 100 | api: 101 | # 反向WS Event 地址 102 | event: 103 | # 重连间隔 单位毫秒 104 | reconnect-interval: 3000 105 | middlewares: 106 | <<: *default # 引用默认中间件 -------------------------------------------------------------------------------- /docker-arm/deploy-to-dockerhub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | detect_macos() { 4 | [[ $(uname -s) == Darwin ]] && is_macos=1 || is_macos=0 5 | } 6 | 7 | detect_macos 8 | 9 | #当前目录 10 | HOME=$( 11 | cd $(dirname $0) 12 | pwd 13 | ) 14 | 15 | git pull 16 | 17 | op=$1 18 | if [[ $op == 'push' ]]; then 19 | echo "构建并推送镜像" 20 | else 21 | op='local' 22 | echo "本地构建" 23 | fi 24 | 25 | if [ ! -f "$HOME/notify" ];then 26 | rm -rf qinglong && mkdir qinglong 27 | rm -rf notify 28 | git clone -b master --depth=1 https://github.com/whyour/qinglong.git 29 | cd qinglong/sample || exit 30 | 31 | if [[ $is_macos == 1 ]]; then 32 | sed -i '' 's/https:\/\/github.com\/whyour\/qinglong/https:\/\/github.com\/rubyangxg\/jd-qinglong/' notify.js 33 | else 34 | sed -i 's/https:\/\/github.com\/whyour\/qinglong/https:\/\/github.com\/rubyangxg\/jd-qinglong/' notify.js 35 | fi 36 | 37 | npm install 38 | cd ../shell || exit 39 | if [[ $is_macos == 1 ]]; then 40 | sed -i '' 's/\/ql\/scripts\/sendNotify.js/..\/sample\/notify.js/' notify.js 41 | else 42 | sed -i 's/\/ql\/scripts\/sendNotify.js/..\/sample\/notify.js/' notify.js 43 | fi 44 | 45 | pkg -t node12-linux-arm64 notify.js 46 | cp notify $HOME/notify 47 | else 48 | echo "notify已存在" 49 | fi 50 | 51 | cd $HOME || exit 52 | rm -rf .npm 53 | rm -rf .pkg-cache 54 | rm -rf qinglong 55 | 56 | docker rm -f webapp 57 | docker rmi -f rubyangxg/jd-qinglong:arm 58 | 59 | if [ ! -f "$HOME/jd-qinglong-1.0.jar" ];then 60 | cd .. 61 | git pull 62 | mvn clean package -Dmaven.test.skip=true 63 | cp target/jd-qinglong-*.jar $HOME 64 | cd $HOME || exit 65 | else 66 | echo "jd-qinglong-1.0.jar已存在" 67 | fi 68 | 69 | docker build -t rubyangxg/jd-qinglong:arm --build-arg JAR_FILE=jd-qinglong-1.0.jar . 70 | #docker build -t rubyangxg/jd-qinglong:1.1 --build-arg JAR_FILE=jd-qinglong-1.0.jar . 71 | if [[ $op == 'push' ]]; then 72 | docker login 73 | docker push rubyangxg/jd-qinglong:arm 74 | # docker push rubyangxg/jd-qinglong:1.1 75 | fi 76 | 77 | cd /root/run 78 | docker stop webapp && docker rm webapp 79 | docker run -d -p 5701:8080 --name=webapp --privileged=true -v "$(pwd)"/env.properties:/env.properties:ro -v "$(pwd)"/go-cqhttp:/go-cqhttp rubyangxg/jd-qinglong:arm 80 | -------------------------------------------------------------------------------- /docker-arm/gatgap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | import cv2 4 | 5 | image_target_path = sys.argv[1] 6 | image_template_path = sys.argv[2] 7 | img = cv2.imread(image_target_path, 0) 8 | img2 = img.copy() 9 | template = cv2.imread(image_template_path, 0) 10 | w, h = template.shape[::-1] 11 | methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCORR'] 12 | for meth in methods: 13 | img = img2.copy() 14 | method = eval(meth) 15 | res = cv2.matchTemplate(img, template, method) 16 | min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) 17 | top_left = max_loc 18 | bottom_right = (top_left[0] + w, top_left[1] + h) 19 | cv2.rectangle(img, top_left, bottom_right, 255, 2) 20 | print(top_left[0]) 21 | -------------------------------------------------------------------------------- /docker-arm/start-go-cqhttp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | file=/go-cqhttp/go-cqhttp 4 | if [ ! -f "$file" ]; then 5 | wget -O "go-cqhttp.tar.gz" https://ghproxy.com/https://github.com/Mrs4s/go-cqhttp/releases/download/v1.0.0-beta7-fix2/go-cqhttp_linux_arm64.tar.gz 6 | tar -zxvf go-cqhttp.tar.gz -C /go-cqhttp/ 7 | chmod +x /go-cqhttp/* 8 | cp /config-template.yml /go-cqhttp/config.yml 9 | else 10 | echo "go-cqhttp已存在" 11 | fi 12 | 13 | cd /go-cqhttp 14 | ./go-cqhttp -------------------------------------------------------------------------------- /docker-arm/start-webapp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "开始启动webapp" 3 | java -Djava.security.egd=file:/dev/./urandom -jar -Dserver.port=8080 /app.jar -------------------------------------------------------------------------------- /docker-arm/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | 4 | [program:webapp] 5 | command=/opt/bin/start-webapp.sh 6 | autostart=true 7 | autorestart=false 8 | startsecs=0 9 | startretries=0 10 | 11 | ;Logs (all Hub activity redirected to stdout so it can be seen through "docker logs" 12 | redirect_stderr=true 13 | stdout_logfile=/dev/stdout 14 | stdout_logfile_maxbytes=0 15 | 16 | [program:qqbot] 17 | command=/opt/bin/start-go-cqhttp.sh 18 | autostart=true 19 | autorestart=false 20 | startsecs=0 21 | startretries=0 -------------------------------------------------------------------------------- /docker-base/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk11:alpine 2 | 3 | ENV TZ=Asia/Shanghai 4 | COPY MSYH.TTC /usr/share/fonts/ 5 | RUN echo "http://dl-cdn.alpinelinux.org/alpine/edge/main" > /etc/apk/repositories \ 6 | && echo "http://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \ 7 | && echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories \ 8 | && echo "http://dl-cdn.alpinelinux.org/alpine/v3.12/main" >> /etc/apk/repositories \ 9 | && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ 10 | && echo $TZ >> /etc/timezone \ 11 | && apk add \ 12 | libstdc++ \ 13 | chromium \ 14 | chromium-chromedriver \ 15 | harfbuzz \ 16 | supervisor \ 17 | net-tools \ 18 | lsof \ 19 | # nss \ 20 | # freetype \ 21 | # ttf-freefont \ 22 | # font-noto-emoji \ 23 | # wqy-zenhei \ 24 | && rm -rf /var/cache/* \ 25 | && mkdir /var/cache/apk 26 | 27 | VOLUME /tmp 28 | EXPOSE 8080 29 | CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] 30 | -------------------------------------------------------------------------------- /docker-base/MSYH.TTC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/docker-base/MSYH.TTC -------------------------------------------------------------------------------- /docker-base/deploy-to-dockerhub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | #当前目录 5 | HOME=$( 6 | cd $(dirname $0) 7 | pwd 8 | ) 9 | 10 | git pull 11 | 12 | op=$1 13 | if [[ $op == 'push' ]]; then 14 | echo "构建并推送镜像" 15 | else 16 | op='local' 17 | echo "本地构建" 18 | fi 19 | 20 | docker build -t rubyangxg/selenium-base:latest . 21 | if [[ $op == 'push' ]]; then 22 | docker login 23 | docker push rubyangxg/selenium-base:latest 24 | fi -------------------------------------------------------------------------------- /docker-compose-allinone.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | webapp: 4 | container_name: webapp 5 | image: rubyangxg/jd-qinglong:allinone 6 | shm_size: 2gb 7 | ports: 8 | - "5701:8080" 9 | environment: 10 | - SE_NODE_MAX_SESSIONS=4 #这里设置资源数,根据机器性能设置 11 | volumes: 12 | - ./env.properties:/env.properties 13 | 14 | qinglong: 15 | container_name: qinglong 16 | image: whyour/qinglong:latest 17 | volumes: 18 | - ./data/config:/ql/config 19 | - ./data/log:/ql/log 20 | - ./data/db:/ql/db 21 | - ./data/scripts:/ql/scripts 22 | - ./data/repo:/ql/repo 23 | ports: 24 | - "0.0.0.0:5700:5700" 25 | environment: 26 | - ENABLE_HANGUP=true 27 | - ENABLE_WEB_PANEL=true 28 | restart: always 29 | 30 | -------------------------------------------------------------------------------- /docker-compose/config/browsers.json: -------------------------------------------------------------------------------- 1 | { 2 | "chrome": { 3 | "default": "89.0", 4 | "versions": { 5 | "89.0": { 6 | "image": "selenoid/chrome:89.0", 7 | "enableVNC": true, 8 | "port": "4444", 9 | "path": "/", 10 | "limit": 100, 11 | "privileged": true, 12 | "env" : ["LANG=en_US.UTF-8", "LANGUAGE=us:en", "LC_ALL=en_US.UTF-8"] 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /docker-compose/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | detect_macos() { 4 | [[ $(uname -s) == Darwin ]] && is_macos=1 || is_macos=0 5 | } 6 | 7 | detect_macos 8 | 9 | #当前目录 10 | HOME=$( 11 | cd $(dirname $0) 12 | pwd 13 | ) 14 | 15 | cd /root/docker-compose 16 | docker stop webapp-selenoid && docker rm -f webapp-selenoid && docker rmi rubyangxg/jd-qinglong:1.3 17 | docker-compose up -------------------------------------------------------------------------------- /docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | #networks: 3 | # selenoid: 4 | # external: 5 | # name: selenoid # 需要事先创建:docker network create selenoid 6 | 7 | #networks: 8 | # se-net: 9 | # ipam: 10 | # driver: default 11 | # config: 12 | # - subnet: 172.177.0.0/16 13 | 14 | services: 15 | selenoid: 16 | container_name: selenoid 17 | network_mode: bridge 18 | image: aerokube/selenoid:latest-release 19 | volumes: 20 | - "./config:/etc/selenoid" 21 | - "/var/run/docker.sock:/var/run/docker.sock" 22 | - "./logs:/opt/selenoid/logs" 23 | command: [ "-conf", "/etc/selenoid/browsers.json", 24 | "-video-output-dir", "/opt/selenoid/video", 25 | "-log-output-dir", "/opt/selenoid/logs", 26 | "-limit", "10", 27 | "-disable-queue", 28 | "-container-network", "bridge" ] 29 | ports: 30 | - "4444:4444" 31 | restart: always 32 | # networks: 33 | # - selenoid 34 | 35 | selenoid-ui: 36 | network_mode: bridge 37 | container_name: selenoid-ui 38 | image: "aerokube/selenoid-ui" 39 | links: 40 | - selenoid 41 | ports: 42 | - "8081:8080" 43 | command: [ "--selenoid-uri", "http://selenoid:4444" ] 44 | restart: always 45 | # networks: 46 | # - selenoid 47 | 48 | webapp-selenoid: 49 | network_mode: bridge 50 | container_name: webapp-selenoid 51 | image: rubyangxg/jd-qinglong:1.3 52 | privileged: true 53 | environment: 54 | - "SPRING_PROFILES_ACTIVE=debugremote" 55 | - "selenium.hub.url=http://selenoid:4444/wd/hub" 56 | - "selenium.hub.status.url=http://selenoid:4444/status" 57 | ports: 58 | - "5702:8080" 59 | - "8001:8001" 60 | volumes: 61 | - ./env.properties:/env.properties 62 | - /var/run/docker.sock:/var/run/docker.sock 63 | - ./go-cqhttp:/go-cqhttp 64 | depends_on: 65 | - selenoid 66 | links: 67 | - selenoid 68 | 69 | 70 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM adoptopenjdk/openjdk11:latest 2 | VOLUME /tmp 3 | ARG JAR_FILE 4 | COPY ${JAR_FILE} app.jar 5 | EXPOSE 8080 6 | #JAVA_OPTS="-server -Xmx2g -Xms2g -Xss512k -Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom " 7 | #DEBUG_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8001,suspend=n " 8 | ENTRYPOINT [ "sh", "-c", "java -Djava.security.egd=file:/dev/./urandom -jar -Dserver.port=8080 app.jar"] -------------------------------------------------------------------------------- /docker/deploy-to-dockerhub.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | git pull --allow-unrelated-histories 4 | mvn clean package -Dmaven.test.skip=true 5 | cp target/jd-qinglong-*.jar docker/ 6 | cd docker 7 | docker build -t rubyangxg/jd-qinglong:1.1 --build-arg JAR_FILE=jd-qinglong-1.0.jar . 8 | #docker push rubyangxg/jd-qinglong:1.1 9 | 10 | #mvn clean package -Dmaven.test.skip=true && docker-compose -f docker-compose-debug.yml --env-file=env.properties build --no-cache webapp 11 | #docker-compose -f docker-compose-debug.yml --env-file=env.properties up -d --no-deps && docker logs -f webapp -------------------------------------------------------------------------------- /donate/Wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/donate/Wechat.png -------------------------------------------------------------------------------- /env.template.properties: -------------------------------------------------------------------------------- 1 | #请根据机器配置合理调整大小,最小为2,该值表示可以同时多少人使用 2 | SE_NODE_MAX_SESSIONS=4 3 | #青龙上传模式 4 | #1 对接阿东QQ/微信机器人,对接xdd等用户请配置1 5 | #0 对接阿东QQ/微信机器人,对接青龙面板的用户,请配置0 6 | #2 都传送,配置了xdd传xdd,配置了青龙传青龙 7 | QL_UPLOAD_DIRECT=0 8 | #########青龙######### 9 | #指定获取到的ck要上传到哪个青龙面板 10 | 11 | QL_URL_1=http://青龙ip或域名:5700 12 | QL_CLIENTID_1= 13 | QL_SECRET_1= 14 | #自定义名称,用于展示 15 | QL_LABEL_1=腾讯云 16 | #青龙面板ck最大容量,超量不上传 17 | QL_CAPACITY_1=40 18 | 19 | 20 | #第2套青龙(openid登录) 21 | QL_LABEL_2=阿里云 22 | QL_URL_2=http://10.0.0.200:5700 23 | QL_CLIENTID_2=dd 24 | QL_SECRET_2=dd 25 | #青龙面板ck最大容量,超量不上传 26 | QL_CAPACITY_2=40 27 | 28 | #########推送,这里面的推送是指有人上传了ck更新了ck会告知,类似青龙的通知。######### 29 | ## 通知环境变量 30 | ## 1. Server酱 31 | ## https://sct.ftqq.com 32 | ## 下方填写 SCHKEY 值或 SendKey 值 33 | PUSH_KEY="" 34 | 35 | ## 2. BARK 36 | ## 下方填写app提供的设备码,例如:https://api.day.app/123 那么此处的设备码就是123 37 | BARK_PUSH="" 38 | ## 下方填写推送声音设置,例如choo,具体值请在bark-推送铃声-查看所有铃声 39 | BARK_SOUND="" 40 | ## 下方填写推送消息分组,默认为"QingLong" 41 | BARK_GROUP="QingLong" 42 | 43 | ## 3. Telegram 44 | ## 下方填写自己申请@BotFather的Token,如10xxx4:AAFcqxxxxgER5uw 45 | TG_BOT_TOKEN="" 46 | ## 下方填写 @getuseridbot 中获取到的纯数字ID 47 | TG_USER_ID="" 48 | ## Telegram 代理IP(选填) 49 | ## 下方填写代理IP地址,代理类型为 http,比如您代理是 http://127.0.0.1:1080,则填写 "127.0.0.1" 50 | ## 如需使用,请自行解除下一行的注释 51 | TG_PROXY_HOST="" 52 | ## Telegram 代理端口(选填) 53 | ## 下方填写代理端口号,代理类型为 http,比如您代理是 http://127.0.0.1:1080,则填写 "1080" 54 | ## 如需使用,请自行解除下一行的注释 55 | TG_PROXY_PORT="" 56 | ## Telegram 代理的认证参数(选填) 57 | TG_PROXY_AUTH="" 58 | ## Telegram api自建反向代理地址(选填) 59 | ## 教程:https://www.hostloc.com/thread-805441-1-1.html 60 | ## 如反向代理地址 http://aaa.bbb.ccc 则填写 aaa.bbb.ccc 61 | ## 如需使用,请赋值代理地址链接,并自行解除下一行的注释 62 | TG_API_HOST="" 63 | 64 | ## 4. 钉钉 65 | ## 官方文档:https://developers.dingtalk.com/document/app/custom-robot-access 66 | ## 下方填写token后面的内容,只需 https://oapi.dingtalk.com/robot/send?access_token=XXX 等于=符号后面的XXX即可 67 | DD_BOT_TOKEN="" 68 | DD_BOT_SECRET="" 69 | 70 | ## 5. 企业微信机器人 71 | ## 官方说明文档:https://work.weixin.qq.com/api/doc/90000/90136/91770 72 | ## 下方填写密钥,企业微信推送 webhook 后面的 key 73 | QYWX_KEY="" 74 | 75 | ## 6. 企业微信应用 76 | ## 参考文档:http://note.youdao.com/s/HMiudGkb 77 | ## 下方填写素材库图片id(corpid,corpsecret,touser,agentid),素材库图片填0为图文消息, 填1为纯文本消息 78 | QYWX_AM="" 79 | 80 | ## 7. iGot聚合 81 | ## 参考文档:https://wahao.github.io/Bark-MP-helper 82 | ## 下方填写iGot的推送key,支持多方式推送,确保消息可达 83 | IGOT_PUSH_KEY="" 84 | 85 | ## 8. Push Plus 86 | ## 官方网站:http://www.pushplus.plus 87 | ## 下方填写您的Token,微信扫码登录后一对一推送或一对多推送下面的token,只填 PUSH_PLUS_TOKEN 默认为一对一推送 88 | PUSH_PLUS_TOKEN="" 89 | ## 一对一多推送(选填) 90 | ## 下方填写您的一对多推送的 "群组编码" ,(一对多推送下面->您的群组(如无则新建)->群组编码) 91 | ## 1. 需订阅者扫描二维码 2、如果您是创建群组所属人,也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送 92 | PUSH_PLUS_USER="" 93 | 94 | ## 9. adbot 95 | ## gobot_url 推送到个人QQ: http://127.0.0.1/send_private_msg 群:http://127.0.0.1/send_group_msg 96 | ## gobot_token 填写在adbot文件设置的访问密钥 97 | ## gobot_qq 如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群 98 | ## adbot相关API https://docs.adbot.org/api 99 | GOBOT_URL="" 100 | GOBOT_TOKEN="" 101 | GOBOT_QQ="" 102 | ##################### 103 | 104 | #########XDD######### 105 | XDD_URL= 106 | XDD_TOKEN= 107 | ##################### 108 | 109 | #########页面自定义######### 110 | INDEX.TITLE=你的首页标题 111 | INDEX.NOTICE=你的公告 112 | ##################### 113 | 114 | #########自定义操作时限,单位 秒######### 115 | OP_TIME=180 116 | ##################### 117 | 118 | #########以下功能仅打赏可用######### 119 | 120 | #########打赏用户请配置######### 121 | VIP_CODE= 122 | ##################### 123 | 124 | #########QQBot监控群聊,此处配置qq群号######### 125 | MONITOR.QQ.GROUPID= 126 | ##################### 127 | 128 | #########qq机器人管理员######### 129 | MONITOR.QQ.MASTER= 130 | ##################### 131 | 132 | #########定时一对一推送######### 133 | #########资产一对一推送,,小时[0-23], 分钟[0-59],不要写00 02这种, 不支持同一天多时间段######### 134 | PUSH.QQ.ZICHAN=11 135 | PUSH.QQ.ZICHAN.MINUTES=24 136 | ########1表示同时给管理员发一份 137 | PUSH.MASTER=1 138 | #########个人白嫖榜一对一推送,小时[0-23], 分钟[0-59],不要写00 02这种, 不支持同一天多时间段######### 139 | PUSH.BP=15 140 | PUSH.BP.MINUTES=24 141 | ########1表示同时给管理员发一份 142 | PUSH.BP.MASTER=1 143 | #########CK失效一对一推送######### 144 | #########这个例子会每11:00 11:30 12:00 12:30检测CK失效并推送到绑定的微信或qq######## 145 | PUSH.CK.EXPIRE.HOUR=11,12 146 | PUSH.CK.EXPIRE.MINUTES=0,30 147 | ########1表示同时给管理员发一份,0不发给管理员 148 | PUSH.CK.EXPIRE.MASTER=1 149 | ##################### 150 | 151 | #########机器人管理平台用户名密码######### 152 | AD_ADMIN_USERNAME=admin 153 | AD_ADMIN_PASSWORD=adbotadmin 154 | ##################### 155 | 156 | #########机器人qq号和密码,必须配置######### 157 | ADBOT_QQ= 158 | ADBOT_QQ_PASSWORD= 159 | ##################### 160 | 161 | #########机器人回复消息模式,0私聊,1群聊######### 162 | ADBOT_REPLY_TYPE=0 163 | ##################### 164 | 165 | #########青龙选择模式 166 | # 0:显示青龙概要信息,让用户自己选择######### 167 | # 1:自动上传所有青龙中容量最大的,容量相同的,按配置顺序######### 168 | # 2:按配置顺序上传,满了则下一个######### 169 | QL_CHOOSE_TYPE=2 170 | ##################### 171 | 172 | #########微信机器人 173 | #########注意0:依赖于qq机器人的指令,所以qq机器人也要配置,先确保qq机器人能用,然后给MONITOR.QQ.MASTER(见上方配置)发送登录微信指令 174 | #########注意1:碰到提示在其他地方登陆,请打开微信机器人首页,点击【桌面微信已登陆】退出,然后再重新登陆。没有已登录提示的话,就重新扫码登陆一次 175 | #########注意2:微信的查询是靠昵称+备注识别的,如果机器人通讯录有多个昵称相同的,则会找备注,所以务必确保昵称+备注唯一性。 176 | #1启用微信机器人,0禁用 177 | WXBOT=0 178 | #微信机器人通讯录,管理员的备注 179 | WXBOT.MASTER=管理员123456 180 | #微信自动同意好友验证关键词,必须包含此关键词才自动同意,留空表示都能加 181 | WXBOT.ADDFRIEND.KEYWORD=阿东 182 | ##################### 183 | 184 | #########网页备注输入框,提示语,自行对应############ 185 | WEB.REMARK1.PLACEHOLDER=qq号绑定,方便查询 186 | WEB.REMARK2.PLACEHOLDER=qq号绑定,方便查询,填入此项忽略下面备注 187 | ####################################### 188 | 189 | #########是否启用公众号,1启用,0禁用 190 | #########公众号后台-》服务器配置-》服务器地址(URL)填写http(s)://阿东:端口/wx/portal/开发者ID(AppID),微信限制端口必须是80,自己搞下吧,用nginx方便一点 191 | WXMP=0 192 | #########公众号管理员openid,通过指令(设置管理员 [AD_ADMIN_USERNAME] [AD_ADMIN_PASSWORD])发送给公众号进行设置 193 | WXMP.MASTER=123456 194 | #########只适用于已认证的公众号,公众号后台-》设置与开发-》公众号设置-》注册信息-》原始ID 195 | WXMP.RZ= 196 | #########只适用于已认证的公众号,阿东的网页登录url,用于配置菜单,配置基本菜单请访问http://阿东服务器:端口/wx/menu/[你的公众号appId]/create 197 | WX.MP.WEBLOGIN.URL= 198 | 199 | wx.mp.configs[0].appId= 200 | wx.mp.configs[0].secret= 201 | wx.mp.configs[0].token= 202 | wx.mp.configs[0].aesKey= 203 | 204 | #阿东网页登录访问地址,http://localhost:5701 205 | ADONG.URL= 206 | 207 | #代理服务器,格式IP:端口,注意没有http(s):// 208 | #自己搭建的代理一定要配置上用户名密码做安全校验,否则被别人扫去偷跑流量了 209 | #代理搭建推荐tinyproxy(https://github.com/tinyproxy/tinyproxy),安装教程参考网上 210 | PROXY.URL= 211 | PROXY.USERNAME= 212 | PROXY.PASSWORD= 213 | 214 | #WxPusher一对一推送 215 | #此处配置appToken,如何获得?WxPusher微信推送服务,https://wxpusher.zjiecode.com/admin/main/app/appInfo进入后台,创建应用后获取。参考https://www.kejiwanjia.com/jiaocheng/27909.html 216 | #注意事项:后台事件回调地址填写你5701端口的外网地址,也就是ADONG.URL配置项/wxpusher/callback,比如http://localhost:5701/wxpusher/callback 217 | WXPUSHER.APPTOKEN= 218 | #此处配置管理员的uid,如何获得?管理员关注'WxPusher消息推送平台'后,查看我的uid,菜单》我的-》我的UID填入此处 219 | WXPUSHER.MASTER= 220 | 221 | #是否允许用户自定义备注,0禁用自定义,1启用自定义,不配置默认1 222 | REMARK.ENABLED=1 223 | -------------------------------------------------------------------------------- /jd-qinglong 1.3版本群晖安装教程.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/jd-qinglong 1.3版本群晖安装教程.pdf -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.springframework.boot 8 | spring-boot-starter-parent 9 | 2.3.12.RELEASE 10 | 11 | 12 | org.meread 13 | jd-qinglong 14 | 1.0 15 | 16 | UTF-8 17 | 11 18 | 11 19 | 11 20 | 4.0.0-rc-2 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-freemarker 31 | 32 | 33 | 34 | org.apache.commons 35 | commons-lang3 36 | 3.9 37 | 38 | 39 | com.amihaiemil.web 40 | docker-java-api 41 | 0.0.13 42 | 43 | 44 | org.glassfish 45 | javax.json 46 | 1.0.4 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | org.bytedeco 56 | javacv 57 | 1.5.6 58 | 59 | 60 | org.bytedeco 61 | javacpp 62 | 1.5.6 63 | 64 | 65 | org.bytedeco 66 | opencv 67 | 4.5.3-1.5.6 68 | macosx-x86_64 69 | 70 | 71 | org.bytedeco 72 | opencv 73 | 4.5.3-1.5.6 74 | linux-x86_64 75 | 76 | 77 | org.bytedeco 78 | opencv 79 | 4.5.3-1.5.6 80 | linux-arm64 81 | 82 | 83 | org.bytedeco 84 | openblas 85 | 0.3.17-1.5.6 86 | macosx-x86_64 87 | 88 | 89 | org.bytedeco 90 | openblas 91 | 0.3.17-1.5.6 92 | linux-x86_64 93 | 94 | 95 | org.bytedeco 96 | openblas 97 | 0.3.17-1.5.6 98 | linux-arm64 99 | 100 | 101 | commons-io 102 | commons-io 103 | 2.11.0 104 | 105 | 106 | 107 | 108 | 109 | 110 | org.springframework.boot 111 | spring-boot-devtools 112 | runtime 113 | true 114 | 115 | 116 | commons-beanutils 117 | commons-beanutils 118 | 1.9.4 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | org.springframework.boot 137 | spring-boot-starter-test 138 | test 139 | 140 | 141 | org.junit.vintage 142 | junit-vintage-engine 143 | 144 | 145 | 146 | 147 | org.apache.commons 148 | commons-collections4 149 | 4.4 150 | 151 | 152 | org.seleniumhq.selenium 153 | selenium-java 154 | 155 | 156 | junit 157 | junit 158 | test 159 | 160 | 161 | org.projectlombok 162 | lombok 163 | provided 164 | 165 | 166 | com.alibaba 167 | fastjson 168 | 1.2.78 169 | 170 | 171 | org.apache.httpcomponents 172 | httpclient 173 | 4.5.13 174 | 175 | 176 | com.google.zxing 177 | core 178 | 3.4.1 179 | 180 | 181 | com.google.zxing 182 | javase 183 | 3.4.1 184 | 185 | 186 | 187 | org.springframework.boot 188 | spring-boot-starter-websocket 189 | 190 | 191 | 192 | 193 | 194 | 195 | org.springframework.boot 196 | spring-boot-maven-plugin 197 | 198 | com.meread.selenium.Application 199 | true 200 | 201 | 202 | 203 | 204 | repackage 205 | 206 | 207 | 208 | 209 | 210 | org.apache.maven.plugins 211 | maven-surefire-plugin 212 | 2.19.1 213 | 214 | 215 | **/*Test 216 | 217 | 218 | 219 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/.DS_Store -------------------------------------------------------------------------------- /src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/.DS_Store -------------------------------------------------------------------------------- /src/main/java/com/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/java/com/.DS_Store -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/Application.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import com.meread.selenium.util.OpenCVUtil; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.scheduling.TaskScheduler; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; 10 | 11 | import java.io.IOException; 12 | 13 | @SpringBootApplication 14 | @EnableScheduling 15 | public class Application { 16 | 17 | @Bean 18 | public TaskScheduler taskScheduler() { 19 | ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); 20 | scheduler.setPoolSize(2); 21 | scheduler.setThreadNamePrefix("scheduled-task-"); 22 | scheduler.setDaemon(true); 23 | return scheduler; 24 | } 25 | 26 | public static void main(String[] args) { 27 | SpringApplication.run(Application.class, args); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/AssignSessionIdStatus.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.servlet.http.Cookie; 8 | 9 | /** 10 | * Created by yangxg on 2021/9/10 11 | * 12 | * @author yangxg 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class AssignSessionIdStatus { 18 | private MyChromeClient myChromeClient; 19 | private boolean isNew; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/CaptchaImg.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * Created by yangxg on 2021/10/15 8 | * 9 | * @author yangxg 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | public class CaptchaImg { 14 | private String big; 15 | private String small; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/JDCookie.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.util.StringUtils; 7 | 8 | /** 9 | * @author yangxg 10 | * @date 2021/9/18 11 | */ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class JDCookie { 16 | private String ptPin; 17 | private String ptKey; 18 | 19 | public static JDCookie parse(String ck) { 20 | JDCookie jdCookie = new JDCookie(); 21 | String[] split = ck.split(";"); 22 | for (String s : split) { 23 | if (s.startsWith("pt_key")) { 24 | jdCookie.setPtKey(s.split("=")[1]); 25 | } 26 | if (s.startsWith("pt_pin")) { 27 | jdCookie.setPtPin(s.split("=")[1]); 28 | } 29 | } 30 | return jdCookie; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "pt_key=" + ptKey + ";pt_pin=" + ptPin + ";"; 36 | } 37 | 38 | public boolean isEmpty() { 39 | return StringUtils.isEmpty(ptPin) && StringUtils.isEmpty(ptKey); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/JDLoginType.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | /** 4 | * @author yangxg 5 | * @date 2021/9/20 6 | */ 7 | public enum JDLoginType { 8 | phone, 9 | qr 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/JDOpResultBean.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * Created by yangxg on 2021/9/3 8 | * 9 | * @author yangxg 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | public class JDOpResultBean { 14 | private JDScreenBean screenBean; 15 | private boolean success; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/JDScreenBean.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * Created by yangxg on 2021/9/3 9 | * 10 | * @author yangxg 11 | */ 12 | @Data 13 | @AllArgsConstructor 14 | public class JDScreenBean { 15 | public enum PageStatus { 16 | EMPTY_URL("空白页面 data:"), 17 | VERIFY_FAILED("验证失败,请重新验证"), 18 | SESSION_EXPIRED("浏览器sessionId失效,请重新获取"), 19 | REQUIRE_VERIFY("安全验证"), 20 | 21 | REQUIRE_SCANQR("需要扫码"), 22 | REQUIRE_REFRESH("二维码失效,请点击刷新"), 23 | WAIT_QR_CONFIRM("扫描成功,请在手机确认"), 24 | WAIT_CUBE_SMSCODE("需要用户输入认证魔方发送的验证码"), 25 | 26 | VERIFY_CODE_MAX("对不起,短信验证码发送次数已达上限,请24小时后再试"), 27 | VERIFY_FAILED_MAX("验证码错误多次,请重新获取"), 28 | SUCCESS_CK("已经获取到ck了"), 29 | NORMAL("正常页面"), 30 | SHOULD_SEND_AUTH("需要点击发送验证码"), 31 | SHOULD_CLICK_LOGIN("需要点击登陆"), 32 | SWITCH_SMS_LOGIN("需要切换到短信验证码登录"), 33 | AGREE_AGREEMENT("若您输入的手机号未注册,将为您直接注册,注册即视为同意"), 34 | INTERNAL_ERROR("抛异常了"); 35 | private final String desc; 36 | PageStatus(String s) { 37 | this.desc = s; 38 | } 39 | 40 | public String getDesc() { 41 | return desc; 42 | } 43 | } 44 | private String screen; 45 | private String qr; 46 | private JDCookie ck; 47 | private PageStatus pageStatus; 48 | //手机验证码重新获取倒计时 49 | private int authCodeCountDown; 50 | //登录按钮是否可点击 51 | private boolean canClickLogin; 52 | //获取验证码是否可点击 53 | private boolean canSendAuth; 54 | //为了防止前端一直占用webdriver,5分钟要释放掉 55 | private Long sessionTimeOut; 56 | //统计信息 57 | private StatClient statClient; 58 | //截屏时间 59 | private long snapshotTime; 60 | //需要让前端提示的消息 61 | private String msg; 62 | //验证码 63 | private CaptchaImg captchaImg; 64 | 65 | public JDScreenBean(String screen,String qr, PageStatus pageStatus) { 66 | this.screen = screen; 67 | this.qr = qr; 68 | this.pageStatus = pageStatus; 69 | this.snapshotTime = System.currentTimeMillis(); 70 | } 71 | 72 | public JDScreenBean(String screen,String qr, PageStatus pageStatus,JDCookie ck) { 73 | this.screen = screen; 74 | this.qr = qr; 75 | this.pageStatus = pageStatus; 76 | this.ck = ck; 77 | this.snapshotTime = System.currentTimeMillis(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/LoginType.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | public enum LoginType { 4 | /* 5 | 网页登录 6 | */ 7 | WEB, 8 | /* 9 | qq机器人登录 10 | */ 11 | QQBOT 12 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/MyChrome.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.Data; 5 | import org.openqa.selenium.chrome.ChromeDriverService; 6 | import org.openqa.selenium.remote.RemoteWebDriver; 7 | 8 | /** 9 | * Created by yangxg on 2021/9/9 10 | * 11 | * @author yangxg 12 | */ 13 | @Data 14 | public class MyChrome { 15 | //这两个字段会随着创建chrome自动更新 16 | private RemoteWebDriver webDriver; 17 | private ChromeDriverService chromeDriverService; 18 | private JSONObject sessionInfoJson; 19 | private long expireTime; 20 | private String userTrackId; 21 | 22 | public MyChrome(RemoteWebDriver webDriver, ChromeDriverService chromeDriverService, long expireTime) { 23 | this.webDriver = webDriver; 24 | this.expireTime = expireTime; 25 | this.chromeDriverService = chromeDriverService; 26 | } 27 | 28 | public boolean isExpire() { 29 | return expireTime < System.currentTimeMillis(); 30 | } 31 | 32 | public String getChromeSessionId() { 33 | return webDriver.getSessionId().toString(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/MyChromeClient.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.web.socket.WebSocketSession; 7 | 8 | /** 9 | * Created by yangxg on 2021/9/27 10 | * 11 | * @author yangxg 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | public class MyChromeClient { 17 | //用户追踪号,如果是机器人登录,则是qq号,如果是网页登录则是httpsessionid 18 | private String userTrackId; 19 | //阿东登录方式 20 | private LoginType loginType; 21 | //京东登录方式 22 | private JDLoginType jdLoginType; 23 | //用户缓存 24 | private String trackPhone; 25 | private long trackQQ; 26 | private String trackRemark; 27 | private String trackCK; 28 | private String chromeSessionId; 29 | private long expireTime; 30 | private String wsId; 31 | //是否已经推送到xdd上了 32 | private boolean pushedXDD; 33 | 34 | public boolean isExpire() { 35 | return expireTime < System.currentTimeMillis(); 36 | } 37 | 38 | public long getExpireSeconds() { 39 | return (expireTime - System.currentTimeMillis()) / 1000; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/NodeStatus.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by yangxg on 2021/9/9 11 | * 12 | * @author yangxg 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class NodeStatus { 18 | private String nodeId; 19 | private String uri; 20 | private int maxSessions; 21 | private boolean isFullSession; 22 | private String availability; 23 | private List slotStatus; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/Point.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * Created by yangxg on 2021/10/13 10 | * 11 | * @author yangxg 12 | */ 13 | @Data 14 | @AllArgsConstructor 15 | public class Point { 16 | private int x; 17 | private int y; 18 | private long time; 19 | 20 | @Override 21 | public boolean equals(Object o) { 22 | if (this == o) return true; 23 | if (o == null || getClass() != o.getClass()) return false; 24 | Point point = (Point) o; 25 | return x == point.x && y == point.y; 26 | } 27 | 28 | @Override 29 | public int hashCode() { 30 | return Objects.hash(x, y); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/QLConfig.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.util.StringUtils; 7 | 8 | /** 9 | * @author yangxg 10 | * @date 2021/9/14 11 | */ 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class QLConfig { 16 | private int id; 17 | private String label; 18 | private String qlUrl; 19 | private String qlUsername; 20 | private String qlPassword; 21 | 22 | private String qlClientID; 23 | private String qlClientSecret; 24 | 25 | private QLToken qlToken; 26 | //最大容量 27 | private int capacity = 99; 28 | //当前剩余多少 29 | private int remain = 99; 30 | 31 | public boolean isValid() { 32 | boolean verify1 = !StringUtils.isEmpty(qlUrl); 33 | boolean verify2 = verify1 && !StringUtils.isEmpty(qlUsername) && !StringUtils.isEmpty(qlPassword); 34 | boolean verify3 = verify1 && !StringUtils.isEmpty(qlClientID) && !StringUtils.isEmpty(qlClientSecret); 35 | return verify1 && (verify2 || verify3); 36 | } 37 | 38 | public enum QLLoginType { 39 | /* 40 | 用户名密码登录 41 | */ 42 | USERNAME_PASSWORD("用户名密码"), 43 | /* 44 | OpenApi登录 45 | */ 46 | TOKEN("openId"); 47 | 48 | private String desc; 49 | 50 | QLLoginType(String desc) { 51 | this.desc = desc; 52 | } 53 | 54 | public String getDesc() { 55 | return desc; 56 | } 57 | } 58 | 59 | private QLLoginType qlLoginType; 60 | 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/QLToken.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Objects; 8 | 9 | /** 10 | * Created by yangxg on 2021/9/14 11 | * 12 | * @author yangxg 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class QLToken { 18 | private String token; 19 | private String tokenType; 20 | private long expiration; 21 | 22 | public QLToken(String token) { 23 | this.token = token; 24 | } 25 | 26 | public boolean isExpired() { 27 | return expiration > System.currentTimeMillis() / 1000; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | QLToken qlToken = (QLToken) o; 35 | return Objects.equals(token, qlToken.token); 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return "QLToken{" + 41 | "token='" + token + '\'' + 42 | ", tokenType='" + tokenType + '\'' + 43 | ", expiration=" + expiration + 44 | '}'; 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(token); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/QLUploadStatus.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * Created by yangxg on 2021/9/15 8 | * 9 | * @author yangxg 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | public class QLUploadStatus { 14 | private QLConfig qlConfig; 15 | private int uploadStatus; 16 | private boolean isFull; 17 | private String pushRes; 18 | private String xddRes; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/QingLongCookieBean.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author yangxg 7 | * @date 2021/9/11 8 | */ 9 | @Data 10 | public class QingLongCookieBean { 11 | //{"value":"AAABBB", 12 | // "_id":"xXoB7jGMN5sYYofb", 13 | // "created":1631291172989, 14 | // "status":0, 15 | // "timestamp":"Sat Sep 11 2021 00:26:12 GMT+0800 (中国标准时间)", 16 | // "position":4999999999.5, 17 | // "name":"JD_COOKIE"} 18 | private String value; 19 | private String name; 20 | private String _id; 21 | private long created; 22 | private int status; 23 | private String timestamp; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/SelenoidStatus.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by yangxg on 2021/9/22 12 | * 13 | * @author yangxg 14 | */ 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class SelenoidStatus { 19 | private int total; 20 | private int used; 21 | private int queued; 22 | private int pending; 23 | Map sessions; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/SlotStatus.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | import java.util.Date; 8 | 9 | /** 10 | * Created by yangxg on 2021/9/10 11 | * 12 | * @author yangxg 13 | */ 14 | @Data 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | public class SlotStatus { 18 | private String sessionId; 19 | private Date sessionStartTime; 20 | private String belongsToUri; 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/StatClient.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.bean; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | /** 7 | * Created by yangxg on 2021/9/28 8 | * 9 | * @author yangxg 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | public class StatClient { 14 | private int availChromeCount; 15 | private int webSessionCount; 16 | private int qqSessionCount; 17 | private int totalChromeCount; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/qq/GroupMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 bejson.com 3 | */ 4 | package com.meread.selenium.bean.qq; 5 | 6 | /** 7 | * Auto-generated: 2021-10-08 12:55:42 8 | * 9 | * @author bejson.com (i@bejson.com) 10 | * @website http://www.bejson.com/java2pojo/ 11 | */ 12 | public class GroupMessage { 13 | 14 | private String anonymous; 15 | private int font; 16 | private long group_id; 17 | private String message; 18 | private long message_id; 19 | private int message_seq; 20 | private String message_type; 21 | private String post_type; 22 | private String raw_message; 23 | private long self_id; 24 | private Sender sender; 25 | private String sub_type; 26 | private long time; 27 | private long user_id; 28 | public void setAnonymous(String anonymous) { 29 | this.anonymous = anonymous; 30 | } 31 | public String getAnonymous() { 32 | return anonymous; 33 | } 34 | 35 | public void setFont(int font) { 36 | this.font = font; 37 | } 38 | public int getFont() { 39 | return font; 40 | } 41 | 42 | public void setGroup_id(long group_id) { 43 | this.group_id = group_id; 44 | } 45 | public long getGroup_id() { 46 | return group_id; 47 | } 48 | 49 | public void setMessage(String message) { 50 | this.message = message; 51 | } 52 | public String getMessage() { 53 | return message; 54 | } 55 | 56 | public void setMessage_id(long message_id) { 57 | this.message_id = message_id; 58 | } 59 | public long getMessage_id() { 60 | return message_id; 61 | } 62 | 63 | public void setMessage_seq(int message_seq) { 64 | this.message_seq = message_seq; 65 | } 66 | public int getMessage_seq() { 67 | return message_seq; 68 | } 69 | 70 | public void setMessage_type(String message_type) { 71 | this.message_type = message_type; 72 | } 73 | public String getMessage_type() { 74 | return message_type; 75 | } 76 | 77 | public void setPost_type(String post_type) { 78 | this.post_type = post_type; 79 | } 80 | public String getPost_type() { 81 | return post_type; 82 | } 83 | 84 | public void setRaw_message(String raw_message) { 85 | this.raw_message = raw_message; 86 | } 87 | public String getRaw_message() { 88 | return raw_message; 89 | } 90 | 91 | public void setSelf_id(long self_id) { 92 | this.self_id = self_id; 93 | } 94 | public long getSelf_id() { 95 | return self_id; 96 | } 97 | 98 | public void setSender(Sender sender) { 99 | this.sender = sender; 100 | } 101 | public Sender getSender() { 102 | return sender; 103 | } 104 | 105 | public void setSub_type(String sub_type) { 106 | this.sub_type = sub_type; 107 | } 108 | public String getSub_type() { 109 | return sub_type; 110 | } 111 | 112 | public void setTime(long time) { 113 | this.time = time; 114 | } 115 | public long getTime() { 116 | return time; 117 | } 118 | 119 | public void setUser_id(long user_id) { 120 | this.user_id = user_id; 121 | } 122 | public long getUser_id() { 123 | return user_id; 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/qq/PrivateMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 bejson.com 3 | */ 4 | package com.meread.selenium.bean.qq; 5 | 6 | /** 7 | * Auto-generated: 2021-09-27 10:25:5 8 | * 9 | * @author bejson.com (i@bejson.com) 10 | * @website http://www.bejson.com/java2pojo/ 11 | */ 12 | public class PrivateMessage { 13 | 14 | private int font; 15 | private String message; 16 | private long message_id; 17 | private String message_type; 18 | private String post_type; 19 | private String raw_message; 20 | private long self_id; 21 | private Sender sender; 22 | private String sub_type; 23 | private long target_id; 24 | private long time; 25 | private long user_id; 26 | 27 | public void setFont(int font) { 28 | this.font = font; 29 | } 30 | 31 | public int getFont() { 32 | return font; 33 | } 34 | 35 | public void setMessage(String message) { 36 | this.message = message; 37 | } 38 | 39 | public String getMessage() { 40 | return message; 41 | } 42 | 43 | public void setMessage_id(long message_id) { 44 | this.message_id = message_id; 45 | } 46 | 47 | public long getMessage_id() { 48 | return message_id; 49 | } 50 | 51 | public void setMessage_type(String message_type) { 52 | this.message_type = message_type; 53 | } 54 | 55 | public String getMessage_type() { 56 | return message_type; 57 | } 58 | 59 | public void setPost_type(String post_type) { 60 | this.post_type = post_type; 61 | } 62 | 63 | public String getPost_type() { 64 | return post_type; 65 | } 66 | 67 | public void setRaw_message(String raw_message) { 68 | this.raw_message = raw_message; 69 | } 70 | 71 | public String getRaw_message() { 72 | return raw_message; 73 | } 74 | 75 | public void setSelf_id(long self_id) { 76 | this.self_id = self_id; 77 | } 78 | 79 | public long getSelf_id() { 80 | return self_id; 81 | } 82 | 83 | public void setSender(Sender sender) { 84 | this.sender = sender; 85 | } 86 | 87 | public Sender getSender() { 88 | return sender; 89 | } 90 | 91 | public void setSub_type(String sub_type) { 92 | this.sub_type = sub_type; 93 | } 94 | 95 | public String getSub_type() { 96 | return sub_type; 97 | } 98 | 99 | public void setTarget_id(long target_id) { 100 | this.target_id = target_id; 101 | } 102 | 103 | public long getTarget_id() { 104 | return target_id; 105 | } 106 | 107 | public void setTime(long time) { 108 | this.time = time; 109 | } 110 | 111 | public long getTime() { 112 | return time; 113 | } 114 | 115 | public void setUser_id(long user_id) { 116 | this.user_id = user_id; 117 | } 118 | 119 | public long getUser_id() { 120 | return user_id; 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/bean/qq/Sender.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 bejson.com 3 | */ 4 | package com.meread.selenium.bean.qq; 5 | 6 | /** 7 | * Auto-generated: 2021-10-08 12:55:42 8 | * 9 | * @author bejson.com (i@bejson.com) 10 | * @website http://www.bejson.com/java2pojo/ 11 | */ 12 | public class Sender { 13 | 14 | private int age; 15 | private String area; 16 | private String card; 17 | private String level; 18 | private String nickname; 19 | private String role; 20 | private String sex; 21 | private String title; 22 | private long user_id; 23 | public void setAge(int age) { 24 | this.age = age; 25 | } 26 | public int getAge() { 27 | return age; 28 | } 29 | 30 | public void setArea(String area) { 31 | this.area = area; 32 | } 33 | public String getArea() { 34 | return area; 35 | } 36 | 37 | public void setCard(String card) { 38 | this.card = card; 39 | } 40 | public String getCard() { 41 | return card; 42 | } 43 | 44 | public void setLevel(String level) { 45 | this.level = level; 46 | } 47 | public String getLevel() { 48 | return level; 49 | } 50 | 51 | public void setNickname(String nickname) { 52 | this.nickname = nickname; 53 | } 54 | public String getNickname() { 55 | return nickname; 56 | } 57 | 58 | public void setRole(String role) { 59 | this.role = role; 60 | } 61 | public String getRole() { 62 | return role; 63 | } 64 | 65 | public void setSex(String sex) { 66 | this.sex = sex; 67 | } 68 | public String getSex() { 69 | return sex; 70 | } 71 | 72 | public void setTitle(String title) { 73 | this.title = title; 74 | } 75 | public String getTitle() { 76 | return title; 77 | } 78 | 79 | public void setUser_id(long user_id) { 80 | this.user_id = user_id; 81 | } 82 | public long getUser_id() { 83 | return user_id; 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/config/ChromeSessionInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.config; 2 | 3 | import com.meread.selenium.service.BaseWebDriverManager; 4 | import com.meread.selenium.service.WebDriverManager; 5 | import com.meread.selenium.bean.MyChromeClient; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.servlet.HandlerInterceptor; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | 15 | /** 16 | * @author yangxg 17 | */ 18 | @Component 19 | public class ChromeSessionInterceptor implements HandlerInterceptor { 20 | Logger logger = LoggerFactory.getLogger(ChromeSessionInterceptor.class); 21 | 22 | @Autowired 23 | private BaseWebDriverManager driverFactory; 24 | 25 | /** 26 | * 在请求到达Controller控制器之前 通过拦截器执行一段代码 27 | * 如果方法返回true,继续执行后续操作 28 | * 如果返回false,执行中断请求处理,请求不会发送到Controller 29 | */ 30 | @Override 31 | public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { 32 | String servletSessionId = request.getSession().getId(); 33 | try { 34 | MyChromeClient cacheMyChromeClient = driverFactory.getCacheMyChromeClient(servletSessionId); 35 | if (cacheMyChromeClient != null && cacheMyChromeClient.isExpire()) { 36 | logger.info(cacheMyChromeClient.getChromeSessionId() + "过期了"); 37 | driverFactory.releaseWebDriver(cacheMyChromeClient.getChromeSessionId(),false); 38 | } 39 | } catch (Exception e) { 40 | e.printStackTrace(); 41 | } 42 | return true; 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/config/HttpClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.config; 2 | 3 | import lombok.Data; 4 | import org.apache.http.client.config.RequestConfig; 5 | import org.apache.http.conn.HttpClientConnectionManager; 6 | import org.apache.http.impl.client.CloseableHttpClient; 7 | import org.apache.http.impl.client.HttpClientBuilder; 8 | import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 9 | import org.springframework.boot.context.properties.ConfigurationProperties; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | @ConfigurationProperties(prefix = "httpclient") 15 | @Data 16 | public class HttpClientConfig { 17 | 18 | private Integer maxTotal; 19 | 20 | private Integer defaultMaxPerRoute; 21 | private Integer connectTimeout; 22 | 23 | private Integer connectionRequestTimeout; 24 | 25 | private Integer socketTimeout; 26 | 27 | /** 28 | * HttpClient连接池 29 | * @return 30 | */ 31 | @Bean 32 | public HttpClientConnectionManager httpClientConnectionManager() { 33 | PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(); 34 | connectionManager.setMaxTotal(maxTotal); 35 | connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); 36 | return connectionManager; 37 | } 38 | 39 | /** 40 | * 创建RequestConfig 41 | * @return 42 | */ 43 | @Bean 44 | public RequestConfig requestConfig() { 45 | return RequestConfig.custom().setConnectTimeout(connectTimeout) 46 | .setConnectionRequestTimeout(connectionRequestTimeout).setSocketTimeout(socketTimeout) 47 | .build(); 48 | } 49 | 50 | /** 51 | * 创建HttpClient 52 | * @param manager 53 | * @param config 54 | * @return 55 | */ 56 | @Bean 57 | public CloseableHttpClient httpClient(HttpClientConnectionManager manager, RequestConfig config) { 58 | return HttpClientBuilder.create().setConnectionManager(manager).setDefaultRequestConfig(config) 59 | .build(); 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/config/HttpClientUtil.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.http.NameValuePair; 5 | import org.apache.http.client.entity.UrlEncodedFormEntity; 6 | import org.apache.http.client.methods.CloseableHttpResponse; 7 | import org.apache.http.client.methods.HttpGet; 8 | import org.apache.http.client.methods.HttpPost; 9 | import org.apache.http.client.utils.URIBuilder; 10 | import org.apache.http.entity.ContentType; 11 | import org.apache.http.entity.StringEntity; 12 | import org.apache.http.impl.client.CloseableHttpClient; 13 | import org.apache.http.message.BasicNameValuePair; 14 | import org.apache.http.util.EntityUtils; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.stereotype.Component; 17 | 18 | import java.io.IOException; 19 | import java.net.URI; 20 | import java.nio.charset.Charset; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | @Component 26 | @Slf4j 27 | public class HttpClientUtil { 28 | 29 | @Autowired 30 | private CloseableHttpClient httpClient; 31 | 32 | public String doGet(String url, Map param, Map headers) { 33 | 34 | String resultString = ""; 35 | CloseableHttpResponse response = null; 36 | try { 37 | // 创建uri 38 | URIBuilder builder = new URIBuilder(url); 39 | if (param != null) { 40 | for (String key : param.keySet()) { 41 | builder.addParameter(key, param.get(key)); 42 | } 43 | } 44 | builder.setCharset(Charset.forName("utf-8")); 45 | URI uri = builder.build(); 46 | 47 | // 创建http GET请求 48 | HttpGet httpGet = new HttpGet(uri); 49 | if (headers != null && !headers.isEmpty()) { 50 | headers.forEach((name, value) -> httpGet.setHeader(name, value)); 51 | } 52 | 53 | // 执行请求 54 | response = httpClient.execute(httpGet); 55 | return EntityUtils.toString(response.getEntity(), "UTF-8"); 56 | } catch (Exception e) { 57 | e.printStackTrace(); 58 | } finally { 59 | try { 60 | close(response); 61 | httpClient.close(); 62 | } catch (IOException e) { 63 | log.error("doGet url:" + url + " fail,cause :" + e.getMessage(), e); 64 | } 65 | } 66 | return resultString; 67 | } 68 | 69 | public String doGet(String url) { 70 | return doGet(url, null, null); 71 | } 72 | 73 | public String doPost(String url, Map param, Map headers) { 74 | CloseableHttpResponse response = null; 75 | String resultString = ""; 76 | try { 77 | // 创建Http Post请求 78 | HttpPost httpPost = new HttpPost(url); 79 | // 创建参数列表 80 | if (param != null) { 81 | List paramList = new ArrayList<>(); 82 | for (String key : param.keySet()) { 83 | paramList.add(new BasicNameValuePair(key, param.get(key))); 84 | } 85 | // 模拟表单 86 | UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList, Charset.forName("UTF-8")); 87 | httpPost.setEntity(entity); 88 | } 89 | 90 | if (headers != null && !headers.isEmpty()) { 91 | headers.forEach((name, value) -> httpPost.setHeader(name, value)); 92 | } 93 | 94 | // 执行http请求 95 | response = httpClient.execute(httpPost); 96 | resultString = EntityUtils.toString(response.getEntity(), "utf-8"); 97 | } catch (Exception e) { 98 | log.error("doPost url:" + url + " fail,cause :" + e.getMessage(), e); 99 | } finally { 100 | close(response); 101 | } 102 | 103 | return resultString; 104 | } 105 | 106 | private void close(CloseableHttpResponse response) { 107 | try { 108 | if (response != null) { 109 | response.close(); 110 | } 111 | } catch (IOException e) { 112 | log.error(e.getMessage(), e); 113 | } 114 | } 115 | 116 | public String doPost(String url) { 117 | return doPost(url, null, null); 118 | } 119 | 120 | public String doPostJson(String url, String json) { 121 | CloseableHttpResponse response = null; 122 | String resultString = ""; 123 | try { 124 | // 创建Http Post请求 125 | HttpPost httpPost = new HttpPost(url); 126 | // 创建请求内容 127 | StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON); 128 | httpPost.setEntity(entity); 129 | // 执行http请求 130 | response = httpClient.execute(httpPost); 131 | resultString = EntityUtils.toString(response.getEntity(), "utf-8"); 132 | } catch (Exception e) { 133 | log.error("doJson url:" + url + " fail,cause :" + e.getMessage(), e); 134 | } finally { 135 | close(response); 136 | } 137 | 138 | return resultString; 139 | } 140 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.config; 2 | 3 | import org.apache.http.impl.client.HttpClientBuilder; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.client.ClientHttpRequestFactory; 7 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 8 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 9 | import org.springframework.http.converter.StringHttpMessageConverter; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import java.nio.charset.Charset; 13 | import java.nio.charset.StandardCharsets; 14 | 15 | @Configuration 16 | public class RestTemplateConfig { 17 | 18 | @Bean 19 | public RestTemplate restTemplate() { 20 | RestTemplate restTemplate = new RestTemplate(simpleClientHttpRequestFactory()); 21 | // 支持中文编码 22 | restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8)); 23 | return restTemplate; 24 | } 25 | 26 | @Bean 27 | public ClientHttpRequestFactory simpleClientHttpRequestFactory() { 28 | HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory( 29 | HttpClientBuilder.create().build()); 30 | clientHttpRequestFactory.setReadTimeout(10000); 31 | clientHttpRequestFactory.setConnectionRequestTimeout(10000); 32 | clientHttpRequestFactory.setConnectTimeout(10000); 33 | return clientHttpRequestFactory; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/config/WebMvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.config; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | @Configuration 12 | public class WebMvcConfig implements WebMvcConfigurer { 13 | 14 | private static final List EXCLUDE_PATH = Arrays.asList("/css/**", "/js/**", "/img/**", "/media/**", "/vendors/**"); 15 | 16 | @Autowired 17 | private ChromeSessionInterceptor chromeSessionInterceptor; 18 | 19 | /** 20 | * 注册拦截器 21 | * 22 | * @param registry 23 | */ 24 | @Override 25 | public void addInterceptors(InterceptorRegistry registry) { 26 | /** 27 | * addInterceptor 注册拦截器 28 | * addPathPatterns 配置拦截规则 29 | */ 30 | registry.addInterceptor(chromeSessionInterceptor) 31 | .addPathPatterns("/**") 32 | .excludePathPatterns(EXCLUDE_PATH); 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/controller/ErrorHandleController.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.controller; 2 | 3 | import com.meread.selenium.service.BaseWebDriverManager; 4 | import com.meread.selenium.service.JDService; 5 | import com.meread.selenium.service.WebDriverManager; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.web.servlet.error.ErrorController; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | 14 | @Controller 15 | public class ErrorHandleController implements ErrorController { 16 | 17 | @Autowired 18 | private JDService jdService; 19 | 20 | @Override 21 | public String getErrorPath() { 22 | return "/error"; 23 | } 24 | 25 | @RequestMapping("/error") 26 | public String handleError(HttpServletRequest request, Model model) { 27 | //获取statusCode 28 | Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); 29 | model.addAttribute("initSuccess", jdService.isInitSuccess()); 30 | if (statusCode == 404) { 31 | return "/error/404"; 32 | } else { 33 | return "/error/500"; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/entity/QQBind.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.entity; 2 | 3 | /** 4 | * Created by yangxg on 2021/9/27 5 | * 6 | * @author yangxg 7 | */ 8 | public class QQBind { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/service/ScheduleService.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.service; 2 | 3 | import com.meread.selenium.bean.QLConfig; 4 | import com.meread.selenium.bean.QLToken; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.scheduling.annotation.Scheduled; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by yangxg on 2021/10/12 14 | * 15 | * @author yangxg 16 | */ 17 | @Slf4j 18 | @Service 19 | public class ScheduleService { 20 | 21 | @Autowired 22 | private BaseWebDriverManager driverFactory; 23 | 24 | @Autowired 25 | private JDService jdService; 26 | 27 | /** 28 | * 和grid同步chrome状态,清理失效的session,并移除本地缓存 29 | */ 30 | @Scheduled(initialDelay = 10000, fixedDelay = 2000) 31 | public void heartbeat() { 32 | driverFactory.heartbeat(); 33 | } 34 | 35 | @Scheduled(initialDelay = 60000, fixedDelay = 30 * 60000) 36 | public void syncCK_count() { 37 | List qlConfigs = driverFactory.getQlConfigs(); 38 | if (qlConfigs != null) { 39 | for (QLConfig qlConfig : qlConfigs) { 40 | int oldSize = qlConfig.getRemain(); 41 | Boolean exec = driverFactory.exec(webDriver -> { 42 | jdService.fetchCurrentCKS_count(webDriver, qlConfig, ""); 43 | return true; 44 | }); 45 | if (exec != null && exec) { 46 | int newSize = qlConfig.getRemain(); 47 | log.info(qlConfig.getQlUrl() + " 容量从 " + oldSize + "变为" + newSize); 48 | } else { 49 | log.error("syncCK_count 执行失败"); 50 | } 51 | } 52 | } 53 | } 54 | 55 | @Scheduled(cron = "0 0 0 * * ?") 56 | public void refreshOpenIdToken() { 57 | List qlConfigs = driverFactory.getQlConfigs(); 58 | if (qlConfigs != null) { 59 | for (QLConfig qlConfig : qlConfigs) { 60 | if (qlConfig.getQlLoginType() == QLConfig.QLLoginType.TOKEN) { 61 | QLToken qlTokenOld = qlConfig.getQlToken(); 62 | jdService.fetchNewOpenIdToken(qlConfig); 63 | log.info(qlConfig.getQlToken() + " token 从" + qlTokenOld + " 变为 " + qlConfig.getQlToken()); 64 | } 65 | } 66 | } 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/service/WSManager.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.meread.selenium.bean.*; 5 | import com.meread.selenium.util.CommonAttributes; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.DisposableBean; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.scheduling.annotation.Scheduled; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.socket.TextMessage; 12 | import org.springframework.web.socket.WebSocketSession; 13 | 14 | import java.io.IOException; 15 | import java.util.*; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | 18 | @Component 19 | @Slf4j 20 | public class WSManager implements DisposableBean { 21 | 22 | @Autowired 23 | private JDService jdService; 24 | 25 | @Autowired 26 | private BaseWebDriverManager driverManager; 27 | 28 | public volatile boolean runningSchedule = false; 29 | public volatile boolean stopSchedule = false; 30 | 31 | public final Map> 32 | socketSessionPool = new ConcurrentHashMap<>(); 33 | 34 | public Map lastPageStatus = new ConcurrentHashMap<>(); 35 | 36 | public synchronized void addNew(WebSocketSession session) { 37 | String webSocketSessionId = session.getId(); 38 | String httpSessionId = (String) session.getAttributes().get(CommonAttributes.SESSION_ID); 39 | String jdLoginType = (String) session.getAttributes().get(CommonAttributes.JD_LOGIN_TYPE); 40 | Map socketSessionMap = socketSessionPool.get(httpSessionId); 41 | if (socketSessionMap == null) { 42 | socketSessionMap = new HashMap<>(); 43 | } 44 | socketSessionMap.put(webSocketSessionId, session); 45 | socketSessionPool.put(httpSessionId, socketSessionMap); 46 | MyChromeClient cacheMyChromeClient = driverManager.getCacheMyChromeClient(httpSessionId); 47 | if (cacheMyChromeClient == null) { 48 | driverManager.createNewMyChromeClient(httpSessionId, LoginType.WEB, JDLoginType.valueOf(jdLoginType)); 49 | } 50 | } 51 | 52 | public synchronized void removeOld(WebSocketSession session) { 53 | String webSocketSessionId = session.getId(); 54 | String httpSessionId = (String) session.getAttributes().get(CommonAttributes.SESSION_ID); 55 | Map socketSessionMap = socketSessionPool.get(httpSessionId); 56 | if (socketSessionMap != null) { 57 | WebSocketSession remove = socketSessionMap.remove(webSocketSessionId); 58 | if (remove != null && socketSessionMap.isEmpty()) { 59 | socketSessionPool.remove(httpSessionId); 60 | lastPageStatus.remove(httpSessionId); 61 | MyChromeClient cacheMyChromeClient = driverManager.getCacheMyChromeClient(httpSessionId); 62 | if (cacheMyChromeClient != null) { 63 | driverManager.releaseWebDriver(cacheMyChromeClient.getChromeSessionId(),false); 64 | } 65 | } 66 | } 67 | } 68 | 69 | public int getConnectionCount() { 70 | return socketSessionPool.size(); 71 | } 72 | 73 | @Scheduled(initialDelay = 10000, fixedDelay = 1000) 74 | public void heartbeat() { 75 | runningSchedule = true; 76 | if (!stopSchedule) { 77 | 78 | Set onlineUserTrackIds = socketSessionPool.keySet(); 79 | Set removeChromeSessionIds = new HashSet<>(); 80 | for (MyChrome chrome : driverManager.getChromes().values()) { 81 | if (chrome.getUserTrackId() != null && !onlineUserTrackIds.contains(chrome.getUserTrackId())) { 82 | removeChromeSessionIds.add(chrome.getChromeSessionId()); 83 | } 84 | } 85 | for (MyChromeClient client : driverManager.clients.values()) { 86 | if (client.getUserTrackId() != null && !onlineUserTrackIds.contains(client.getUserTrackId())) { 87 | removeChromeSessionIds.add(client.getChromeSessionId()); 88 | } 89 | } 90 | 91 | for (String s : removeChromeSessionIds) { 92 | driverManager.releaseWebDriver(s,false); 93 | } 94 | 95 | doPushScreen(); 96 | } 97 | runningSchedule = false; 98 | 99 | } 100 | 101 | private void doPushScreen() { 102 | Iterator>> it = socketSessionPool.entrySet().iterator(); 103 | while (it.hasNext()) { 104 | Map.Entry> entry = it.next(); 105 | String httpSessionId = entry.getKey(); 106 | Map socketSessionMap = entry.getValue(); 107 | MyChromeClient myChromeClient = driverManager.getCacheMyChromeClient(httpSessionId); 108 | if (myChromeClient != null) { 109 | JDScreenBean screen = jdService.getScreen(myChromeClient); 110 | 111 | if (myChromeClient.isExpire()) { 112 | driverManager.releaseWebDriver(myChromeClient.getChromeSessionId(),false); 113 | for (WebSocketSession socketSession : socketSessionMap.values()) { 114 | try { 115 | socketSession.sendMessage(new TextMessage(JSON.toJSONString(new JDScreenBean("", "", JDScreenBean.PageStatus.SESSION_EXPIRED)))); 116 | socketSession.close(); 117 | it.remove(); 118 | } catch (IOException e) { 119 | e.printStackTrace(); 120 | } 121 | } 122 | continue; 123 | } 124 | if (!myChromeClient.isPushedXDD()) { 125 | if (screen.getPageStatus().equals(JDScreenBean.PageStatus.SUCCESS_CK)) { 126 | log.info("已经获取到ck了 " + myChromeClient + ", ck = " + screen.getCk()); 127 | String xddRes = jdService.doXDDNotify(screen.getCk().toString()); 128 | log.info("doXDDNotify res = " + xddRes); 129 | myChromeClient.setPushedXDD(true); 130 | } 131 | } 132 | JDScreenBean oldScreen = lastPageStatus.get(httpSessionId); 133 | long diff = Integer.MAX_VALUE; 134 | if (oldScreen != null) { 135 | diff = System.currentTimeMillis() - oldScreen.getSnapshotTime(); 136 | } 137 | int threshold = 2000; 138 | if (CommonAttributes.debug) { 139 | threshold = 0; 140 | } 141 | if (oldScreen == null || oldScreen.getPageStatus() == null || oldScreen.getPageStatus() != screen.getPageStatus() || screen.getPageStatus() == JDScreenBean.PageStatus.REQUIRE_VERIFY || diff > threshold) { 142 | lastPageStatus.put(httpSessionId, screen); 143 | for (WebSocketSession socketSession : socketSessionMap.values()) { 144 | Object obj = socketSession.getAttributes().get("push"); 145 | if (obj != null && !((boolean) obj)) { 146 | continue; 147 | } 148 | try { 149 | socketSession.sendMessage(new TextMessage(JSON.toJSONString(screen))); 150 | } catch (IOException e) { 151 | e.printStackTrace(); 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | 160 | @Override 161 | public void destroy() throws Exception { 162 | for (Map socketSessionMap : socketSessionPool.values()) { 163 | for (WebSocketSession socketSession : socketSessionMap.values()) { 164 | socketSession.close(); 165 | } 166 | } 167 | } 168 | 169 | public Map getLastPageStatus() { 170 | return lastPageStatus; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/service/WebDriverManager.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.service; 2 | 3 | import com.meread.selenium.bean.JDLoginType; 4 | import com.meread.selenium.bean.LoginType; 5 | import com.meread.selenium.bean.MyChromeClient; 6 | import com.meread.selenium.util.WebDriverOpCallBack; 7 | 8 | /** 9 | * @author yangxg 10 | * @date 2021/9/7 11 | */ 12 | public interface WebDriverManager { 13 | 14 | MyChromeClient createNewMyChromeClient(String httpSessionId, LoginType loginType, JDLoginType jdLoginType); 15 | 16 | void releaseWebDriver(String chromeSessionId, boolean quit); 17 | 18 | void createChrome(); 19 | 20 | void createChromeOptions(); 21 | 22 | T exec(WebDriverOpCallBack executor); 23 | 24 | void destroyAll(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/util/CommonAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * 4 | * 5 | */ 6 | package com.meread.selenium.util; 7 | 8 | import org.springframework.web.socket.WebSocketSession; 9 | 10 | /** 11 | * 公共参数 12 | */ 13 | public final class CommonAttributes { 14 | 15 | public static final String TMPDIR = System.getProperty("java.io.tmpdir"); 16 | // public static final String TMPDIR = "/tmp"; 17 | public static final String SESSION_ID = "HTTP_SESSION_ID"; 18 | public static final String JD_LOGIN_TYPE = "JD_LOGIN_TYPE"; 19 | /** 20 | * 日期格式配比 21 | */ 22 | public static final String[] DATE_PATTERNS = new String[]{"yyyy", "yyyy-MM", "yyyyMM", "yyyy/MM", "yyyy-MM-dd", "yyyyMMdd", "yyyy/MM/dd", "yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss", "yyyy/MM/dd HH:mm:ss"}; 23 | public static boolean debug = false; 24 | public static WebSocketSession webSocketSession; 25 | public static boolean mockCaptcha = "1".equals(System.getenv("mockCaptcha")); 26 | 27 | /** 28 | * 不可实例化 29 | */ 30 | private CommonAttributes() { 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/util/EnumConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * 4 | * 5 | */ 6 | package com.meread.selenium.util; 7 | 8 | import org.apache.commons.beanutils.converters.AbstractConverter; 9 | 10 | /** 11 | * 枚举类型转换 12 | */ 13 | public class EnumConverter extends AbstractConverter { 14 | 15 | /** 16 | * 枚举类型 17 | */ 18 | private final Class enumClass; 19 | 20 | /** 21 | * @param enumClass 枚举类型 22 | */ 23 | public EnumConverter(Class enumClass) { 24 | this(enumClass, null); 25 | } 26 | 27 | /** 28 | * @param enumClass 枚举类型 29 | * @param defaultValue 默认值 30 | */ 31 | public EnumConverter(Class enumClass, Object defaultValue) { 32 | super(defaultValue); 33 | this.enumClass = enumClass; 34 | } 35 | 36 | /** 37 | * 获取默认类型 38 | * 39 | * @return 默认类型 40 | */ 41 | @Override 42 | protected Class getDefaultType() { 43 | return this.enumClass; 44 | } 45 | 46 | /** 47 | * 转换为枚举对象 48 | * 49 | * @param type 类型 50 | * @param value 值 51 | * @return 枚举对象 52 | */ 53 | @SuppressWarnings({"unchecked", "rawtypes"}) 54 | protected Object convertToType(Class type, Object value) { 55 | String stringValue = value.toString().trim(); 56 | return Enum.valueOf(type, stringValue); 57 | } 58 | 59 | /** 60 | * 转换为字符串 61 | * 62 | * @param value 值 63 | * @return 字符串 64 | */ 65 | protected String convertToString(Object value) { 66 | return value.toString(); 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/util/FreemarkerUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * 4 | * 5 | */ 6 | package com.meread.selenium.util; 7 | 8 | 9 | import freemarker.core.Environment; 10 | import freemarker.template.*; 11 | import freemarker.template.utility.DeepUnwrap; 12 | import org.apache.commons.beanutils.ConvertUtilsBean; 13 | import org.apache.commons.beanutils.Converter; 14 | import org.apache.commons.beanutils.converters.ArrayConverter; 15 | import org.apache.commons.beanutils.converters.DateConverter; 16 | import org.springframework.context.ApplicationContext; 17 | import org.springframework.util.Assert; 18 | import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer; 19 | 20 | import java.io.IOException; 21 | import java.io.StringReader; 22 | import java.io.StringWriter; 23 | import java.util.Date; 24 | import java.util.Map; 25 | import java.util.Map.Entry; 26 | 27 | /** 28 | * Utils - Freemarker 29 | */ 30 | @SuppressWarnings("unchecked") 31 | public final class FreemarkerUtils { 32 | 33 | /** 34 | * ConvertUtilsBean 35 | */ 36 | private static final ConvertUtilsBean convertUtils; 37 | 38 | static { 39 | convertUtils = new ConvertUtilsBean() { 40 | @Override 41 | public String convert(Object value) { 42 | if (value != null) { 43 | Class type = value.getClass(); 44 | if (type.isEnum() && super.lookup(type) == null) { 45 | super.register(new EnumConverter(type), type); 46 | } else if (type.isArray() && type.getComponentType().isEnum()) { 47 | if (super.lookup(type) == null) { 48 | ArrayConverter arrayConverter = new ArrayConverter(type, new EnumConverter(type.getComponentType()), 0); 49 | arrayConverter.setOnlyFirstToString(false); 50 | super.register(arrayConverter, type); 51 | } 52 | Converter converter = super.lookup(type); 53 | return ((String) converter.convert(String.class, value)); 54 | } 55 | } 56 | return super.convert(value); 57 | } 58 | 59 | @SuppressWarnings("rawtypes") 60 | @Override 61 | public Object convert(String value, Class clazz) { 62 | if (clazz.isEnum() && super.lookup(clazz) == null) { 63 | super.register(new EnumConverter(clazz), clazz); 64 | } 65 | return super.convert(value, clazz); 66 | } 67 | 68 | @SuppressWarnings("rawtypes") 69 | @Override 70 | public Object convert(String[] values, Class clazz) { 71 | if (clazz.isArray() && clazz.getComponentType().isEnum() && super.lookup(clazz.getComponentType()) == null) { 72 | super.register(new EnumConverter(clazz.getComponentType()), clazz.getComponentType()); 73 | } 74 | return super.convert(values, clazz); 75 | } 76 | 77 | @SuppressWarnings("rawtypes") 78 | @Override 79 | public Object convert(Object value, Class targetType) { 80 | if (super.lookup(targetType) == null) { 81 | if (targetType.isEnum()) { 82 | super.register(new EnumConverter(targetType), targetType); 83 | } else if (targetType.isArray() && targetType.getComponentType().isEnum()) { 84 | ArrayConverter arrayConverter = new ArrayConverter(targetType, new EnumConverter(targetType.getComponentType()), 0); 85 | arrayConverter.setOnlyFirstToString(false); 86 | super.register(arrayConverter, targetType); 87 | } 88 | } 89 | return super.convert(value, targetType); 90 | } 91 | }; 92 | 93 | DateConverter dateConverter = new DateConverter(); 94 | dateConverter.setPatterns(CommonAttributes.DATE_PATTERNS); 95 | convertUtils.register(dateConverter, Date.class); 96 | } 97 | 98 | /** 99 | * 不可实例化 100 | */ 101 | private FreemarkerUtils() { 102 | } 103 | 104 | /** 105 | * 解析模板 106 | * 107 | * @param template freemarker模板对象 108 | * @param model 数据 109 | * @return 解析后内容 110 | */ 111 | public static String process(Template template, Map model) throws IOException, TemplateException { 112 | StringWriter out = new StringWriter(); 113 | template.process(model, out); 114 | return out.toString(); 115 | } 116 | 117 | /** 118 | * 解析字符串模板 119 | * 120 | * @param template 字符串模板 121 | * @param model 数据 122 | * @return 解析后内容 123 | */ 124 | public static String process(String template, Map model) throws IOException, TemplateException { 125 | Configuration configuration = null; 126 | ApplicationContext applicationContext = SpringUtils.getApplicationContext(); 127 | if (applicationContext != null) { 128 | FreeMarkerConfigurer freeMarkerConfigurer = SpringUtils.getBean("freemarkerConfigurer", FreeMarkerConfigurer.class); 129 | configuration = freeMarkerConfigurer.getConfiguration(); 130 | } 131 | return process(template, model, configuration); 132 | } 133 | 134 | /** 135 | * 解析字符串模板 136 | * 137 | * @param template 字符串模板 138 | * @param model 数据 139 | * @param configuration 配置 140 | * @return 解析后内容 141 | */ 142 | public static String process(String template, Map model, Configuration configuration) throws IOException, TemplateException { 143 | if (template == null) { 144 | return null; 145 | } 146 | if (configuration == null) { 147 | configuration = new Configuration(Configuration.VERSION_2_3_23); 148 | } 149 | StringWriter out = new StringWriter(); 150 | new Template("template", new StringReader(template), configuration).process(model, out); 151 | return out.toString(); 152 | } 153 | 154 | /** 155 | * 获取参数 156 | * 157 | * @param name 名称 158 | * @param type 类型 159 | * @param params 参数 160 | * @param nullValue 若为空,返回自定义值 161 | * @return 参数, 若不存在则返回null 162 | */ 163 | public static T getParameter(String name, Class type, Map params, T nullValue) throws TemplateModelException { 164 | Assert.hasText(name); 165 | Assert.notNull(type); 166 | Assert.notNull(params); 167 | TemplateModel templateModel = params.get(name); 168 | if (templateModel == null) { 169 | return nullValue; 170 | } 171 | Object value = DeepUnwrap.unwrap(templateModel); 172 | return (T) convertUtils.convert(value, type); 173 | } 174 | 175 | public static T getParameter(String name, Class type, Map params) throws TemplateModelException { 176 | return getParameter(name, type, params, null); 177 | } 178 | 179 | /** 180 | * 获取变量 181 | * 182 | * @param name 名称 183 | * @param env Environment 184 | * @return 变量 185 | */ 186 | public static TemplateModel getVariable(String name, Environment env) throws TemplateModelException { 187 | Assert.hasText(name); 188 | Assert.notNull(env); 189 | return env.getVariable(name); 190 | } 191 | 192 | /** 193 | * 设置变量 194 | * 195 | * @param name 名称 196 | * @param value 变量值 197 | * @param env Environment 198 | */ 199 | public static void setVariable(String name, Object value, Environment env) throws TemplateException { 200 | Assert.hasText(name); 201 | Assert.notNull(env); 202 | if (value instanceof TemplateModel) { 203 | env.setVariable(name, (TemplateModel) value); 204 | } else { 205 | env.setVariable(name, new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_23).build().wrap(value)); 206 | } 207 | } 208 | 209 | /** 210 | * 设置变量 211 | * 212 | * @param variables 变量 213 | * @param env Environment 214 | */ 215 | public static void setVariables(Map variables, Environment env) throws TemplateException { 216 | Assert.notNull(variables); 217 | Assert.notNull(env); 218 | for (Entry entry : variables.entrySet()) { 219 | String name = entry.getKey(); 220 | Object value = entry.getValue(); 221 | if (value instanceof TemplateModel) { 222 | env.setVariable(name, (TemplateModel) value); 223 | } else { 224 | env.setVariable(name, new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_23).build().wrap(value)); 225 | } 226 | } 227 | } 228 | 229 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/util/OpenCVUtil.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.util; 2 | 3 | import org.apache.commons.io.FilenameUtils; 4 | import org.apache.commons.lang3.SystemUtils; 5 | import org.bytedeco.javacpp.DoublePointer; 6 | import org.bytedeco.javacv.Java2DFrameUtils; 7 | import org.bytedeco.opencv.opencv_core.*; 8 | 9 | import javax.imageio.ImageIO; 10 | import java.awt.image.BufferedImage; 11 | import java.io.IOException; 12 | import java.util.Objects; 13 | import java.util.concurrent.ThreadLocalRandom; 14 | 15 | import static org.bytedeco.opencv.global.opencv_core.*; 16 | import static org.bytedeco.opencv.global.opencv_imgcodecs.imread; 17 | import static org.bytedeco.opencv.global.opencv_imgcodecs.imwrite; 18 | import static org.bytedeco.opencv.global.opencv_imgproc.*; 19 | 20 | public class OpenCVUtil { 21 | 22 | public static void test() throws IOException { 23 | String property = System.getProperty("os.arch"); 24 | System.out.println("初始化opencv start..." + property); 25 | for (char a = 'a'; a <= 'i'; a++) { 26 | long tt1 = System.currentTimeMillis(); 27 | BufferedImage image = ImageIO.read(Objects.requireNonNull(OpenCVUtil.class.getClassLoader().getResourceAsStream("static/img/" + a + ".jpeg"))); 28 | BufferedImage imageSmall = ImageIO.read(Objects.requireNonNull(OpenCVUtil.class.getClassLoader().getResourceAsStream("static/img/" + a + "_small.png"))); 29 | Mat mat = Java2DFrameUtils.toMat(image); 30 | long tt2 = System.currentTimeMillis(); 31 | Mat matSmall = Java2DFrameUtils.toMat(imageSmall); 32 | Rect rect = getOffsetX(mat, matSmall, String.valueOf(a), false); 33 | System.out.println("init opencv calc " + a + " gap " + rect.x() + " end...耗时:" + (tt2 - tt1)); 34 | } 35 | System.out.println("初始化opencv end..."); 36 | } 37 | 38 | public static void test2() { 39 | for (char a = 'a'; a <= 'i'; a++) { 40 | String bigF = OpenCVUtil.class.getClassLoader().getResource("static/img/" + a + ".jpeg").getFile(); 41 | String smallF = OpenCVUtil.class.getClassLoader().getResource("static/img/" + a + "_small.png").getFile(); 42 | Rect rect = getOffsetX(bigF, smallF, true); 43 | System.out.println(rect.x()); 44 | } 45 | } 46 | 47 | public static Rect getOffsetX(Mat source, Mat find, String baseName, boolean debug) { 48 | //read in image default colors 49 | Mat sourceGrey = new Mat(source.size(), CV_8UC1); 50 | cvtColor(source, sourceGrey, COLOR_BGR2GRAY); 51 | 52 | Mat template = new Mat(); 53 | cvtColor(find, template, COLOR_BGR2GRAY); 54 | //Size for the result image 55 | Size size = new Size(sourceGrey.cols() - template.cols() + 1, sourceGrey.rows() - template.rows() + 1); 56 | Mat result = new Mat(size, CV_32FC1); 57 | matchTemplate(sourceGrey, template, result, TM_CCORR_NORMED); 58 | 59 | DoublePointer minVal = new DoublePointer(); 60 | DoublePointer maxVal = new DoublePointer(); 61 | Point min = new Point(); 62 | Point max = new Point(); 63 | minMaxLoc(result, minVal, maxVal, min, max, null); 64 | Rect rect = new Rect(max.x(), max.y(), template.cols(), template.rows()); 65 | rectangle(source, rect, redColor(), 2, 0, 0); 66 | 67 | if (debug) { 68 | imwrite(CommonAttributes.TMPDIR + "/" + baseName + ".origin.marked.jpeg", source); 69 | } 70 | 71 | return rect; 72 | } 73 | 74 | public static Rect getOffsetX(String source, String find, boolean debug) { 75 | Mat sourceMat = imread(source); 76 | Mat findMat = imread(find); 77 | String namePrefix = FilenameUtils.getBaseName(source); 78 | return getOffsetX(sourceMat, findMat, namePrefix, debug); 79 | } 80 | 81 | // some usefull things. 82 | public static Scalar randColor() { 83 | int b, g, r; 84 | b = ThreadLocalRandom.current().nextInt(0, 255 + 1); 85 | g = ThreadLocalRandom.current().nextInt(0, 255 + 1); 86 | r = ThreadLocalRandom.current().nextInt(0, 255 + 1); 87 | return new Scalar(b, g, r, 0); 88 | } 89 | 90 | // some usefull things. 91 | public static Scalar redColor() { 92 | return new Scalar(0, 0, 255, 0); 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/util/ScriptPython.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.nio.charset.StandardCharsets; 8 | 9 | public class ScriptPython { 10 | Process mProcess; 11 | 12 | public static void main(String[] args) { 13 | ScriptPython scriptPython = new ScriptPython(); 14 | scriptPython.runScript(); 15 | } 16 | 17 | public void runScript() { 18 | Process process; 19 | try { 20 | process = Runtime.getRuntime().exec(new String[]{"/Users/yangxg/java/my-project-2021/selenium-test/docker-arm/gatgap.py", "arg1", "arg2"}); 21 | mProcess = process; 22 | } catch (Exception e) { 23 | System.out.println("Exception Raised" + e.toString()); 24 | } 25 | InputStream stdout = mProcess.getInputStream(); 26 | BufferedReader reader = new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8)); 27 | String line; 28 | try { 29 | while ((line = reader.readLine()) != null) { 30 | System.out.println("stdout: " + line); 31 | } 32 | } catch (IOException e) { 33 | System.out.println("Exception in reading output" + e.toString()); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/util/SpringUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * 4 | * 5 | */ 6 | package com.meread.selenium.util; 7 | 8 | import org.springframework.beans.factory.DisposableBean; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.ApplicationContextAware; 11 | import org.springframework.context.annotation.Lazy; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.Assert; 14 | import org.springframework.web.servlet.LocaleResolver; 15 | 16 | import java.util.Locale; 17 | 18 | /** 19 | * Utils - Spring 20 | */ 21 | @Component("springUtils") 22 | @Lazy(false) 23 | public final class SpringUtils implements ApplicationContextAware, DisposableBean { 24 | 25 | /** 26 | * applicationContext 27 | */ 28 | private static ApplicationContext applicationContext; 29 | 30 | /** 31 | * 不可实例化 32 | */ 33 | private SpringUtils() { 34 | } 35 | 36 | /** 37 | * 获取applicationContext 38 | * 39 | * @return applicationContext 40 | */ 41 | public static ApplicationContext getApplicationContext() { 42 | return applicationContext; 43 | } 44 | 45 | public void setApplicationContext(ApplicationContext applicationContext) { 46 | SpringUtils.applicationContext = applicationContext; 47 | } 48 | 49 | /** 50 | * 获取实例 51 | * 52 | * @param name Bean名称 53 | * @return 实例 54 | */ 55 | public static Object getBean(String name) { 56 | Assert.hasText(name); 57 | return applicationContext.getBean(name); 58 | } 59 | 60 | /** 61 | * 获取实例 62 | * 63 | * @param name Bean名称 64 | * @param type Bean类型 65 | * @return 实例 66 | */ 67 | public static T getBean(String name, Class type) { 68 | Assert.hasText(name); 69 | Assert.notNull(type); 70 | return applicationContext.getBean(name, type); 71 | } 72 | 73 | /** 74 | * 获取国际化消息 75 | * 76 | * @param code 代码 77 | * @param args 参数 78 | * @return 国际化消息 79 | */ 80 | public static String getMessage(String code, Object... args) { 81 | LocaleResolver localeResolver = getBean("localeResolver", LocaleResolver.class); 82 | Locale locale = localeResolver.resolveLocale(null); 83 | return applicationContext.getMessage(code, args, locale); 84 | } 85 | 86 | @Override 87 | public void destroy() throws Exception { 88 | applicationContext = null; 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/util/WebDriverOpCallBack.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.util; 2 | 3 | import org.openqa.selenium.remote.RemoteWebDriver; 4 | 5 | /** 6 | * @author yangxg 7 | * @date 2021/9/27 8 | */ 9 | public interface WebDriverOpCallBack { 10 | T doBusiness(RemoteWebDriver webDriver); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/util/WebDriverUtil.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.util; 2 | 3 | import org.openqa.selenium.JavascriptExecutor; 4 | import org.openqa.selenium.remote.RemoteWebDriver; 5 | import org.openqa.selenium.support.ui.ExpectedCondition; 6 | import org.openqa.selenium.support.ui.WebDriverWait; 7 | 8 | /** 9 | * @author yangxg 10 | * @date 2021/9/13 11 | */ 12 | public class WebDriverUtil { 13 | public static boolean waitForJStoLoad(RemoteWebDriver webDriver) { 14 | 15 | WebDriverWait wait = new WebDriverWait(webDriver, 10); 16 | 17 | // wait for jQuery to load 18 | ExpectedCondition jQueryLoad = driver -> { 19 | try { 20 | JavascriptExecutor j = (JavascriptExecutor) driver; 21 | return ((Long) j.executeScript("return jQuery.active") == 0); 22 | } catch (Exception e) { 23 | return true; 24 | } 25 | }; 26 | 27 | // wait for Javascript to load 28 | ExpectedCondition jsLoad = driver -> { 29 | JavascriptExecutor j = (JavascriptExecutor) driver; 30 | return "complete".equals(j.executeScript("return document.readyState") 31 | .toString()); 32 | }; 33 | 34 | return wait.until(jQueryLoad) && wait.until(jsLoad); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/ws/MyHandshakeInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.ws; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.meread.selenium.util.CommonAttributes; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.http.server.ServerHttpRequest; 7 | import org.springframework.http.server.ServerHttpResponse; 8 | import org.springframework.http.server.ServletServerHttpRequest; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.socket.WebSocketHandler; 11 | import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; 12 | 13 | import javax.servlet.http.HttpServletRequest; 14 | import javax.servlet.http.HttpSession; 15 | import java.util.Map; 16 | 17 | /** 18 | * 类描述:拦截器 19 | * @author yangxg 20 | */ 21 | @Component 22 | @Slf4j 23 | public class MyHandshakeInterceptor extends HttpSessionHandshakeInterceptor { 24 | 25 | @Override 26 | public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, 27 | Map map) throws Exception { 28 | log.info("Before handshake " + request.getRemoteAddress()); 29 | if (request instanceof ServletServerHttpRequest) { 30 | ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request; 31 | 32 | HttpServletRequest servletRequest = serverHttpRequest.getServletRequest(); 33 | HttpSession session = servletRequest.getSession(true); 34 | 35 | String path = request.getURI().getPath(); 36 | map.put(CommonAttributes.JD_LOGIN_TYPE, path.substring(path.lastIndexOf('/') + 1)); 37 | 38 | if (session != null) { 39 | map.put(CommonAttributes.SESSION_ID, session.getId()); 40 | } 41 | } 42 | return super.beforeHandshake(request, response, handler, map); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/ws/PageEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.ws; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.meread.selenium.service.WSManager; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.socket.CloseStatus; 10 | import org.springframework.web.socket.TextMessage; 11 | import org.springframework.web.socket.WebSocketSession; 12 | import org.springframework.web.socket.handler.TextWebSocketHandler; 13 | 14 | import java.io.IOException; 15 | 16 | @Component 17 | @Slf4j 18 | public class PageEventHandler extends TextWebSocketHandler { 19 | 20 | @Autowired 21 | private WSManager wsManager; 22 | 23 | @Override 24 | public void afterConnectionEstablished(WebSocketSession session) { 25 | wsManager.addNew(session); 26 | } 27 | 28 | /** 29 | * socket 断开连接时 30 | * 31 | * @param session 32 | * @param status 33 | */ 34 | @Override 35 | public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { 36 | String webSocketSessionId = session.getId(); 37 | log.info("PageEventHandler close " + webSocketSessionId + ", CloseStatus" + status); 38 | wsManager.removeOld(session); 39 | } 40 | 41 | @Override 42 | public void handleTextMessage(WebSocketSession session, TextMessage message) 43 | throws IOException { 44 | String webSocketSessionId = session.getId(); 45 | String payload = message.getPayload(); 46 | JSONObject jsonObject = JSON.parseObject(payload); 47 | Boolean push = jsonObject.getBoolean("push"); 48 | if (push != null && !push) { 49 | session.getAttributes().put("push", push); 50 | } 51 | // session.sendMessage(new TextMessage("Hi " + webSocketSessionId + " how may we help you?")); 52 | Long pingTime = jsonObject.getLong("ping"); 53 | } 54 | 55 | // 错误处理(客户端突然关闭等接收到的错误) 56 | @Override 57 | public void handleTransportError(WebSocketSession arg0, Throwable arg1) throws Exception { 58 | if (arg0.isOpen()) { 59 | arg0.close(); 60 | } 61 | arg1.printStackTrace(); 62 | System.out.println("WS connection error,close..."); 63 | wsManager.removeOld(arg0); 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/ws/QQEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.ws; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.meread.selenium.service.BaseWebDriverManager; 6 | import com.meread.selenium.service.BotService; 7 | import com.meread.selenium.service.WebDriverManager; 8 | import com.meread.selenium.bean.qq.GroupMessage; 9 | import com.meread.selenium.bean.qq.PrivateMessage; 10 | import com.meread.selenium.util.CommonAttributes; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.beans.factory.config.YamlPropertiesFactoryBean; 15 | import org.springframework.core.io.FileSystemResource; 16 | import org.springframework.stereotype.Component; 17 | import org.springframework.util.StringUtils; 18 | import org.springframework.web.socket.CloseStatus; 19 | import org.springframework.web.socket.TextMessage; 20 | import org.springframework.web.socket.WebSocketSession; 21 | import org.springframework.web.socket.handler.TextWebSocketHandler; 22 | 23 | import java.io.File; 24 | import java.util.HashSet; 25 | import java.util.Properties; 26 | import java.util.Set; 27 | import java.util.UUID; 28 | import java.util.regex.Matcher; 29 | import java.util.regex.Pattern; 30 | 31 | @Component 32 | @Slf4j 33 | public class QQEventHandler extends TextWebSocketHandler { 34 | 35 | @Autowired 36 | private BotService botService; 37 | 38 | @Autowired 39 | private BaseWebDriverManager driverManager; 40 | 41 | @Value("${go-cqhttp.dir}") 42 | private String goCqHttpDir; 43 | 44 | private static final Pattern PATTERN = Pattern.compile("(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}"); 45 | private static final Pattern PATTERN2 = Pattern.compile("\\d{6}"); 46 | private static final Pattern PATTERN3 = Pattern.compile("青龙:(\\d+)"); 47 | private static final Pattern PATTERN4 = Pattern.compile("备注:(.*)"); 48 | 49 | /** 50 | * socket 建立成功事件 51 | */ 52 | @Override 53 | public void afterConnectionEstablished(WebSocketSession session) { 54 | String webSocketSessionId = session.getId(); 55 | log.info("afterConnectionEstablished " + webSocketSessionId); 56 | CommonAttributes.webSocketSession = session; 57 | } 58 | 59 | /** 60 | * socket 断开连接时 61 | * 62 | * @param session 63 | * @param status 64 | */ 65 | @Override 66 | public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { 67 | String webSocketSessionId = session.getId(); 68 | log.info("afterConnectionClosed " + webSocketSessionId + ", CloseStatus" + status); 69 | } 70 | 71 | @Override 72 | protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { 73 | if (goCqHttpDir.endsWith("/")) { 74 | goCqHttpDir = goCqHttpDir.substring(0, goCqHttpDir.length() - 1); 75 | } 76 | File configPath = new File(goCqHttpDir + "/config.yml"); 77 | if (!configPath.exists()) { 78 | log.warn(goCqHttpDir + "/config.yml文件不存在"); 79 | return; 80 | } 81 | YamlPropertiesFactoryBean yamlFactory = new YamlPropertiesFactoryBean(); 82 | yamlFactory.setResources(new FileSystemResource(configPath)); 83 | Properties props = yamlFactory.getObject(); 84 | String selfQQ = props.getProperty("account.uin", "0"); 85 | String selfGroup = driverManager.getProperties().getProperty("MONITOR.QQ.GROUPID",""); 86 | 87 | String payload = message.getPayload(); 88 | JSONObject jsonObject = JSON.parseObject(payload); 89 | String post_type = jsonObject.getString("post_type"); 90 | String message_type = jsonObject.getString("message_type"); 91 | if (!"message".equals(post_type)) { 92 | return; 93 | } 94 | 95 | log.info("payload = " + payload); 96 | 97 | String content = null; 98 | long senderQQ = 0; 99 | 100 | JSONObject jo = new JSONObject(); 101 | jo.put("action", "send_private_msg"); 102 | jo.put("echo", UUID.randomUUID().toString().replaceAll("-", "")); 103 | JSONObject params = new JSONObject(); 104 | jo.put("params", params); 105 | 106 | if ("group".equals(message_type)) { 107 | //群聊消息 108 | //处理私聊消息 109 | GroupMessage groupMessage = JSON.parseObject(payload, GroupMessage.class); 110 | content = groupMessage.getMessage(); 111 | senderQQ = groupMessage.getUser_id(); 112 | long group_id = groupMessage.getGroup_id(); 113 | if (!selfGroup.equals(String.valueOf(group_id))) { 114 | log.info("请配置MONITOR.QQ.GROUPID=qq群号,接收群是" + selfGroup); 115 | return; 116 | } 117 | params.put("group_id", group_id); 118 | } else if ("private".equals(message_type)) { 119 | //私聊消息 120 | PrivateMessage privateMessage = JSON.parseObject(payload, PrivateMessage.class); 121 | content = privateMessage.getMessage(); 122 | senderQQ = privateMessage.getUser_id(); 123 | long self_id = privateMessage.getSelf_id(); 124 | if (!selfQQ.equals(String.valueOf(self_id))) { 125 | log.info(goCqHttpDir + "/config.yml配置的qq号,不是此消息的接收人,接收人是" + self_id); 126 | return; 127 | } 128 | } else { 129 | log.info("不支持的消息类型"); 130 | return; 131 | } 132 | 133 | params.put("user_id", senderQQ); 134 | Matcher matcher = PATTERN.matcher(content); 135 | Matcher matcher2 = PATTERN2.matcher(content); 136 | Matcher matcher3 = PATTERN3.matcher(content); 137 | Matcher matcher4 = PATTERN4.matcher(content); 138 | if ("帮助".equals(content) || "help".equals(content) || "h".equals(content) || "hello".equals(content)) { 139 | params.put("message", "看文档吧"); 140 | } else if ("登录".equals(content) || "登陆".equals(content)) { 141 | log.info("处理" + senderQQ + "登录逻辑..."); 142 | params.put("message", "请输入手机号:"); 143 | } else if (matcher.matches()) { 144 | log.info("处理给手机号" + content + "发验证码逻辑"); 145 | botService.doSendSMS(senderQQ, content); 146 | } else if (matcher2.matches()) { 147 | log.info("接受了验证码" + content + ",处理登录逻辑"); 148 | botService.doLogin(senderQQ, content); 149 | } else if ("青龙状态".equals(content)) { 150 | String qlStatus = botService.getQLStatus(false); 151 | params.put("message", qlStatus); 152 | } else if (matcher3.matches()) { 153 | char[] chars = matcher3.group(1).toCharArray(); 154 | Set qlIds = new HashSet<>(); 155 | for (char c : chars) { 156 | int qlId = Integer.parseInt(String.valueOf(c)); 157 | qlIds.add(qlId); 158 | } 159 | botService.doUploadQinglong(senderQQ, qlIds); 160 | } else if (matcher4.matches()) { 161 | String remark = matcher4.group(1); 162 | botService.trackRemark(senderQQ, remark); 163 | } 164 | if (!StringUtils.isEmpty(params.getString("message"))) { 165 | //随机间隔时间 166 | int max = 8, min = 1; 167 | int random = (int) (Math.random() * (max - min) + min); 168 | Thread.sleep(random * 300L); 169 | session.sendMessage(new TextMessage(jo.toJSONString())); 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /src/main/java/com/meread/selenium/ws/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium.ws; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 6 | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; 7 | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; 8 | 9 | /** 10 | * WebSocket 服务端配置类 11 | */ 12 | @Configuration 13 | @EnableWebSocket 14 | public class WebSocketConfig implements WebSocketConfigurer { 15 | 16 | @Autowired 17 | private QQEventHandler qqEventHandler; 18 | 19 | @Autowired 20 | private PageEventHandler pageEventHandler; 21 | @Autowired 22 | private MyHandshakeInterceptor myHandshakeInterceptor; 23 | 24 | @Override 25 | public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 26 | registry.addHandler(qqEventHandler, "ws/event").setAllowedOrigins("*"); 27 | registry.addHandler(pageEventHandler, "ws/page/*").addInterceptors(myHandshakeInterceptor).setAllowedOrigins("*"); 28 | registry.addHandler(pageEventHandler, "sockjs/ws/page/*").addInterceptors(myHandshakeInterceptor).setAllowedOrigins("*").withSockJS(); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/main/resources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/.DS_Store -------------------------------------------------------------------------------- /src/main/resources/application-debuglocal.properties: -------------------------------------------------------------------------------- 1 | chrome.headless=false 2 | server.port=8080 3 | env.path=classpath:/env.properties 4 | SE_NODE_MAX_SESSIONS=10 5 | go-cqhttp.dir=/Users/yangxg/java/my-project-2021/selenium-test/tmp 6 | op.timeout=60 7 | chrome.timeout=150 8 | chrome.driver.path=/Users/yangxg/java/my-project-2021/selenium-test/chromedriver-94-mac 9 | -------------------------------------------------------------------------------- /src/main/resources/application-debugremote.properties: -------------------------------------------------------------------------------- 1 | chrome.headless=true 2 | env.path=/env.properties 3 | SE_NODE_MAX_SESSIONS=10 4 | op.timeout=60 5 | chrome.timeout=150 -------------------------------------------------------------------------------- /src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | chrome.headless=true 2 | server.port=8080 3 | env.path=classpath:/env.properties 4 | SE_NODE_MAX_SESSIONS=8 5 | go-cqhttp.dir=/Users/yangxg/java/my-project-2021/selenium-test/tmp 6 | chrome.driver.path=/Users/yangxg/java/project/jd-qinglong/chromedriver-94-mac 7 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.freemarker.template-loader-path:classpath:/templates 2 | spring.freemarker.suffix:.ftl 3 | spring.freemarker.expose-request-attributes=true 4 | spring.freemarker.request-context-attribute=request 5 | spring.freemarker.cache=false 6 | #spring.redis.host=localhost 7 | #spring.redis.port=6379 8 | #spring.redis.jedis.pool.max-active=8 9 | #spring.redis.jedis.pool.max-wait=2000 10 | #spring.redis.jedis.pool.max-idle=500 11 | #spring.redis.jedis.pool.min-idle=0 12 | #spring.redis.lettuce.shutdown-timeout=0 13 | httpclient.maxTotal=300 14 | httpclient.defaultMaxPerRoute=50 15 | httpclient.connectTimeout=1000 16 | httpclient.connectionRequestTimeout=500 17 | httpclient.socketTimeout=5000 18 | httpclient.staleConnectionCheckEnabled=true 19 | chrome.headless=true 20 | selenium.hub.url=http://localhost:4444/wd/hub 21 | selenium.hub.status.url=http://localhost:4444/status 22 | #此值要小于selenium session timeout时间 23 | op.timeout=180 24 | chrome.timeout=3600 25 | env.path=/env.properties 26 | login.type=phone 27 | SE_NODE_MAX_SESSIONS=4 28 | server.servlet.session.cookie.path=/ 29 | server.servlet.session.cookie.http-only=true 30 | server.servlet.session.cookie.name=jdq 31 | 32 | #spring.datasource.url=jdbc:sqlite:/go-cqhttp/db.sqlite 33 | #spring.datasource.driver-class-name=org.sqlite.JDBC 34 | #mybatis.mapper-locations=classpath:mapping/*Mapper.xml 35 | #mybatis.type-aliases-package=com.meread.selenium.entity 36 | go-cqhttp.dir=/go-cqhttp 37 | chrome.driver.path=/usr/bin/chromedriver 38 | -------------------------------------------------------------------------------- /src/main/resources/static/css/main.css: -------------------------------------------------------------------------------- 1 | .login-form form { 2 | margin-bottom: 15px; 3 | background: #f7f7f7; 4 | box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3); 5 | padding: 30px; 6 | } 7 | 8 | .login-form h2 { 9 | margin: 0 0 15px; 10 | } 11 | 12 | .form-control, .btn { 13 | min-height: 38px; 14 | border-radius: 2px; 15 | } 16 | 17 | .btn { 18 | font-size: 15px; 19 | font-weight: bold; 20 | } 21 | 22 | .alert-primary .btn-success { 23 | float: right; 24 | } 25 | 26 | .alert-primary span { 27 | line-height: 34px; 28 | } 29 | 30 | .alert-primary > div:after { 31 | clear: both; 32 | content: ''; 33 | display: table; 34 | } 35 | 36 | #notfound { 37 | position: relative; 38 | height: 100vh; 39 | background-color: #222; 40 | } 41 | 42 | #notfound .notfound { 43 | position: absolute; 44 | left: 50%; 45 | top: 50%; 46 | -webkit-transform: translate(-50%, -50%); 47 | -ms-transform: translate(-50%, -50%); 48 | transform: translate(-50%, -50%); 49 | } 50 | 51 | .notfound { 52 | max-width: 460px; 53 | width: 100%; 54 | text-align: center; 55 | line-height: 1.4; 56 | } 57 | 58 | .notfound .notfound-404 { 59 | height: 158px; 60 | line-height: 153px; 61 | } 62 | 63 | .notfound .notfound-404 h1 { 64 | font-family: 'Josefin Sans', sans-serif; 65 | color: #222; 66 | font-size: 220px; 67 | letter-spacing: 10px; 68 | margin: 0px; 69 | font-weight: 700; 70 | text-shadow: 2px 2px 0px #c9c9c9, -2px -2px 0px #c9c9c9; 71 | } 72 | 73 | .notfound .notfound-404 h1 > span { 74 | text-shadow: 2px 2px 0px #ffab00, -2px -2px 0px #ffab00, 0px 0px 8px #ff8700; 75 | } 76 | 77 | .notfound p { 78 | font-family: 'Josefin Sans', sans-serif; 79 | color: #c9c9c9; 80 | font-size: 16px; 81 | font-weight: 400; 82 | margin-top: 0px; 83 | margin-bottom: 15px; 84 | } 85 | 86 | .notfound a { 87 | font-family: 'Josefin Sans', sans-serif; 88 | font-size: 14px; 89 | text-decoration: none; 90 | text-transform: uppercase; 91 | background: transparent; 92 | color: #c9c9c9; 93 | border: 2px solid #c9c9c9; 94 | display: inline-block; 95 | padding: 10px 25px; 96 | font-weight: 700; 97 | -webkit-transition: 0.2s all; 98 | transition: 0.2s all; 99 | } 100 | 101 | .notfound a:hover { 102 | color: #ffab00; 103 | border-color: #ffab00; 104 | } 105 | 106 | @media only screen and (max-width: 480px) { 107 | .notfound .notfound-404 { 108 | height: 122px; 109 | line-height: 122px; 110 | } 111 | 112 | .notfound .notfound-404 h1 { 113 | font-size: 122px; 114 | } 115 | } 116 | 117 | .slidercaptcha { 118 | margin: 0 auto; 119 | width: 309px; 120 | height: 290px; 121 | border-radius: 4px; 122 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.125); 123 | margin-top: 4px; 124 | } 125 | 126 | .slidercaptcha .card-body { 127 | padding: 1rem; 128 | } 129 | 130 | .slidercaptcha canvas:first-child { 131 | border-radius: 4px; 132 | border: 1px solid #e6e8eb; 133 | } 134 | 135 | .slidercaptcha.card .card-header { 136 | background-image: none; 137 | background-color: rgba(0, 0, 0, 0.03); 138 | } 139 | 140 | .refreshIcon { 141 | top: -54px; 142 | } 143 | -------------------------------------------------------------------------------- /src/main/resources/static/css/slidercaptcha.min.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow-x: hidden 3 | } 4 | 5 | .block { 6 | position: absolute; 7 | left: 0; 8 | top: 0 9 | } 10 | 11 | .card { 12 | display: flex; 13 | flex-direction: column; 14 | min-width: 0; 15 | word-wrap: break-word; 16 | background-clip: border-box; 17 | border: 1px solid rgba(0, 0, 0, .125) 18 | } 19 | 20 | .card-header { 21 | padding: .55rem 1.25rem; 22 | margin-bottom: 0; 23 | background-color: rgba(0, 0, 0, .03); 24 | border-bottom: 1px solid rgba(0, 0, 0, .125) 25 | } 26 | 27 | .card-header:first-child { 28 | border-radius: calc(.25rem - 1px) calc(.25rem - 1px) 0 0 29 | } 30 | 31 | .card-body { 32 | flex: 1 1 auto; 33 | padding: 1.25rem 34 | } 35 | 36 | .sliderContainer { 37 | position: relative; 38 | text-align: center; 39 | line-height: 40px; 40 | background: #f7f9fa; 41 | color: #45494c; 42 | border-radius: 2px 43 | } 44 | 45 | .sliderbg { 46 | position: absolute; 47 | left: 0; 48 | right: 0; 49 | top: 0; 50 | background-color: #f7f9fa; 51 | height: 40px; 52 | border-radius: 2px; 53 | border: 1px solid #e6e8eb 54 | } 55 | 56 | .sliderContainer_active .slider { 57 | top: -1px; 58 | border: 1px solid #1991fa 59 | } 60 | 61 | .sliderContainer_active .sliderMask { 62 | border-width: 1px 0 1px 1px 63 | } 64 | 65 | .sliderContainer_success .slider { 66 | top: -1px; 67 | border: 1px solid #52ccba; 68 | background-color: #52ccba !important 69 | } 70 | 71 | .sliderContainer_success .sliderMask { 72 | border: 1px solid #52ccba; 73 | border-width: 1px 0 1px 1px; 74 | background-color: #d2f4ef 75 | } 76 | 77 | .sliderContainer_success .sliderIcon:before { 78 | content: "" 79 | } 80 | 81 | .sliderContainer_fail .slider { 82 | top: -1px; 83 | border: 1px solid #f57a7a; 84 | background-color: #f57a7a !important 85 | } 86 | 87 | .sliderContainer_fail .sliderMask { 88 | border: 1px solid #f57a7a; 89 | background-color: #fce1e1; 90 | border-width: 1px 0 1px 1px 91 | } 92 | 93 | .sliderContainer_fail .sliderIcon:before { 94 | content: "" 95 | } 96 | 97 | .sliderContainer_active .sliderText, .sliderContainer_success .sliderText, .sliderContainer_fail .sliderText { 98 | display: none 99 | } 100 | 101 | .sliderMask { 102 | position: absolute; 103 | left: 0; 104 | top: 0; 105 | height: 40px; 106 | border: 0 solid #1991fa; 107 | background: #d1e9fe; 108 | border-radius: 2px 109 | } 110 | 111 | .slider { 112 | position: absolute; 113 | top: 0; 114 | left: 0; 115 | width: 40px; 116 | height: 40px; 117 | background: #fff; 118 | box-shadow: 0 0 3px rgba(0, 0, 0, .3); 119 | cursor: pointer; 120 | transition: background .2s linear; 121 | border-radius: 2px; 122 | display: flex; 123 | align-items: center; 124 | justify-content: center 125 | } 126 | 127 | .slider:hover { 128 | background: #1991fa 129 | } 130 | 131 | .slider:hover .sliderIcon { 132 | background-position: 0 -13px 133 | } 134 | 135 | .sliderText { 136 | position: relative 137 | } 138 | 139 | .refreshIcon { 140 | position: absolute; 141 | right: 0; 142 | top: 0; 143 | cursor: pointer; 144 | margin: 6px; 145 | color: rgba(0, 0, 0, .25); 146 | font-size: 1rem; 147 | z-index: 5; 148 | transition: color .3s linear 149 | } 150 | 151 | .refreshIcon:hover { 152 | color: #6c757d 153 | } -------------------------------------------------------------------------------- /src/main/resources/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/static/images/QQ20211013-125041@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/images/QQ20211013-125041@2x.png -------------------------------------------------------------------------------- /src/main/resources/static/images/a.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/images/a.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/images/a_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/images/a_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/a.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/a.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/a_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/a_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/b.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/b.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/b_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/b_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/c.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/c.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/c_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/c_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/d.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/d.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/d_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/d_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/e.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/e.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/e_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/e_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/f.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/f.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/f_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/f_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/g.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/g.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/g_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/g_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/h.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/h.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/h_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/h_small.png -------------------------------------------------------------------------------- /src/main/resources/static/img/i.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/i.jpeg -------------------------------------------------------------------------------- /src/main/resources/static/img/i_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/img/i_small.png -------------------------------------------------------------------------------- /src/main/resources/static/js/layer/mobile/layer.js: -------------------------------------------------------------------------------- 1 | /*! layer mobile-v2.0.0 Web 通用弹出层组件 MIT License */ 2 | ;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'

'+(e?n.title[0]:n.title)+"

":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e=''+n.btn[0]+"",2===t&&(e=''+n.btn[1]+""+e),'
'+e+"
"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='

'+(n.content||"")+"

"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"
':"")+'
"+l+'
'+n.content+"
"+c+"
",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;odiv{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} -------------------------------------------------------------------------------- /src/main/resources/static/js/layer/theme/default/icon-ext.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/js/layer/theme/default/icon-ext.png -------------------------------------------------------------------------------- /src/main/resources/static/js/layer/theme/default/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/js/layer/theme/default/icon.png -------------------------------------------------------------------------------- /src/main/resources/static/js/layer/theme/default/loading-0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/js/layer/theme/default/loading-0.gif -------------------------------------------------------------------------------- /src/main/resources/static/js/layer/theme/default/loading-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/js/layer/theme/default/loading-1.gif -------------------------------------------------------------------------------- /src/main/resources/static/js/layer/theme/default/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubyangxg/jd-qinglong/810ca1bf88de56e71c51285862b8c0eb676bccd4/src/main/resources/static/js/layer/theme/default/loading-2.gif -------------------------------------------------------------------------------- /src/main/resources/static/js/ws.js: -------------------------------------------------------------------------------- 1 | var ws; 2 | // var url = "192.168.125.38:8080"; 3 | var url = window.location.host; 4 | 5 | function setConnected(connected) { 6 | $("#connect").prop("disabled", connected); 7 | $("#disconnect").prop("disabled", !connected); 8 | } 9 | 10 | function connect() { 11 | if ('WebSocket' in window) { 12 | ws = new WebSocket("ws://" + url + "/ws/page");//建立连接 13 | } else { 14 | ws = new SockJS("http://" + url + "/sockjs/ws/page");//建立连接 15 | } 16 | //建立连接处理 17 | ws.onopen = onOpen; 18 | //接收处理 19 | ws.onmessage = onMessage; 20 | //错误处理 21 | ws.onerror = onError; 22 | //断开连接处理 23 | ws.onclose = onClose; 24 | setConnected(true); 25 | } 26 | 27 | function onOpen(openEvent) { 28 | console.log("onOpen") 29 | } 30 | 31 | function onError() { 32 | console.log("onError") 33 | } 34 | 35 | function onClose() { 36 | console.log("onClose") 37 | } 38 | 39 | function onMessage(event) { 40 | helloWorld(event.data); 41 | } 42 | 43 | function disconnect() { 44 | if (ws != null) { 45 | ws.close(); 46 | } 47 | setConnected(false); 48 | console.log("Websocket is in disconnected state"); 49 | } 50 | 51 | function sendData() { 52 | var data = JSON.stringify({ 53 | 'user': $("#user").val() 54 | }) 55 | ws.send(data); 56 | } 57 | 58 | 59 | function helloWorld(message) { 60 | $("#helloworldmessage").append(" " + message + ""); 61 | } 62 | 63 | $(function () { 64 | $("form").on('submit', function (e) { 65 | e.preventDefault(); 66 | }); 67 | connect(); 68 | $("#disconnect").click(function () { 69 | disconnect(); 70 | }); 71 | $("#send").click(function () { 72 | sendData(); 73 | }); 74 | }); -------------------------------------------------------------------------------- /src/main/resources/templates/error/404.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 404页面 4 | 5 | 6 |

<#if !initSuccess>未启动完成,请耐心等待!<#else>找不到资源!!!

7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/templates/error/500.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 500页面 4 | 5 | 6 |

服务器出现异常!!!

7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragment/chooseQL.ftl: -------------------------------------------------------------------------------- 1 | <#if qlConfigs?has_content> 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <#list qlConfigs as s> 16 | 17 | 23 | 24 | 25 | <#-- --> 26 | 27 | 28 | 29 |
勾选青龙可用容量
18 |
19 | 21 |
22 |
${s.label!(s.qlUrl)}${s.remain}${s.qlLoginType}
30 |
31 | <#else> 32 |
33 | 无数据 34 |
35 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragment/uploadRes.ftl: -------------------------------------------------------------------------------- 1 | <#if uploadStatuses?has_content> 2 | 3 | 4 | 5 | 6 | <#-- --> 7 | 8 | 9 | 10 | 11 | <#list uploadStatuses as s> 12 | class="table-success" <#else> class="table-warning"> 13 | 14 | <#-- --> 15 | 16 | 17 | 18 | 19 |
青龙登录方式是否成功
${s.qlConfig.label!(s.qlConfig.qlUrl)}${s.qlConfig.qlLoginType}<#if s.uploadStatus gt 0>成功<#else>失败
20 | <#else> 21 |
22 | 无数据 23 |
24 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.ftl: -------------------------------------------------------------------------------- 1 | <#assign base = request.contextPath /> 2 | 3 | 4 | 5 | 6 | 7 | 8 | <#if indexTitle??>${indexTitle}<#else>阿东CK自助获取</#if> 9 | 10 | 11 | 12 | 13 | 14 | 15 | <#if error ??> 16 |
17 |
18 |
19 |

404

20 |
21 |

没有足够的资源调度,请稍后重试

22 | 刷新重试 23 |
24 |
25 | <#elseif !initSuccess> 26 |
27 |
28 |
29 |

502

30 |
31 |

未启动完成,请耐心等待!

32 | 刷新重试 33 |
34 |
35 | <#else> 36 |
37 | 52 | 60 | <#if indexNotice??> 61 | 64 | 65 | <#if jdLoginType == 'phone'> 66 |
67 |

<#if indexTitle??>${indexTitle}<#else>😀阿东CK自助获取😀

68 |
69 |
70 |
71 | 72 |
73 | 75 |
76 |
77 |
78 |
79 | 80 |
81 | 83 |
84 | 85 |
86 |
87 |
88 | 89 |
90 |
91 | 92 |
93 |
94 | 110 |
111 |
112 | 113 |
114 |
115 | <#else> 116 |
117 |
118 | 使用京东绑定的QQ扫码登陆 119 |
120 |
121 |
122 |
123 | 125 |
126 |
127 | 128 | <#if qlConfigs?has_content> 129 |
130 |
131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | <#list qlConfigs as s> 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
#青龙可用容量
${s.id}${s.label!(s.qlUrl)}${s.remain!(0)}
149 |
150 |
151 | <#else> 152 | 155 | 156 |
157 | 158 | 159 | 160 | 161 | 162 | 170 | 171 | 172 | 173 | 174 | 178 | 179 | -------------------------------------------------------------------------------- /src/main/resources/templates/mock.ftl: -------------------------------------------------------------------------------- 1 | <#assign base = request.contextPath /> 2 | 3 | 4 | 5 | 6 | log_tracks 7 | 8 | 37 | 97 | 98 | 99 |
100 |
101 | 102 |
在绿色的方块中滑动鼠标
103 | 104 |
105 |
106 |
107 |
108 |
109 | 110 | -------------------------------------------------------------------------------- /src/main/resources/templates/ws.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 17 | noVNC 18 | 19 | 20 | 21 | 59 | 60 | 179 | 180 | 181 | 182 |
183 |
Loading
184 |
Send CtrlAltDel
185 |
186 |
187 | 188 |
189 | 190 | -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/DockerTest.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import com.amihaiemil.docker.Container; 4 | import com.amihaiemil.docker.Containers; 5 | import com.amihaiemil.docker.UnixDocker; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | 10 | /** 11 | * Created by yangxg on 2021/9/22 12 | * 13 | * @author yangxg 14 | */ 15 | public class DockerTest { 16 | public static void main(String[] args) throws IOException { 17 | Containers containers = new UnixDocker(new File("/var/run/docker.sock")).containers(); 18 | for (Container container : containers) { 19 | String image = container.getString("Image"); 20 | if ("selenoid/chrome:89.0".equals(image)) { 21 | container.remove(true, true, false); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/GettingStartedWithService.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import org.junit.*; 4 | import org.openqa.selenium.WebDriver; 5 | import org.openqa.selenium.chrome.ChromeDriverService; 6 | import org.openqa.selenium.chrome.ChromeOptions; 7 | import org.openqa.selenium.remote.RemoteWebDriver; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | 12 | public class GettingStartedWithService { 13 | 14 | private static ChromeDriverService service; 15 | 16 | private WebDriver driver; 17 | 18 | @BeforeClass 19 | public static void createAndStartService() throws IOException { 20 | service = new ChromeDriverService.Builder() 21 | .usingDriverExecutable(new File("/Users/yangxg/java/project/jd-qinglong/chromedriver-94-mac")) 22 | .usingAnyFreePort() 23 | .build(); 24 | service.start(); 25 | } 26 | 27 | 28 | @AfterClass 29 | public static void stopService() { 30 | service.stop(); 31 | } 32 | 33 | 34 | @Before 35 | public void createDriver() { 36 | driver = new RemoteWebDriver(service.getUrl(), new ChromeOptions()); 37 | } 38 | 39 | 40 | @After 41 | public void quitDriver() { 42 | driver.quit(); 43 | } 44 | 45 | @Test 46 | public void testGoogleSearch() { 47 | driver.get("http://www.google.com"); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/GoogleSearchTest.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONArray; 5 | import com.alibaba.fastjson.JSONObject; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.http.impl.client.HttpClientBuilder; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | import org.openqa.selenium.*; 11 | import org.openqa.selenium.chrome.ChromeOptions; 12 | import org.openqa.selenium.html5.LocalStorage; 13 | import org.openqa.selenium.remote.RemoteExecuteMethod; 14 | import org.openqa.selenium.remote.RemoteWebDriver; 15 | import org.openqa.selenium.remote.html5.RemoteWebStorage; 16 | import org.openqa.selenium.support.ui.ExpectedCondition; 17 | import org.openqa.selenium.support.ui.WebDriverWait; 18 | import org.springframework.http.HttpEntity; 19 | import org.springframework.http.HttpHeaders; 20 | import org.springframework.http.HttpMethod; 21 | import org.springframework.http.ResponseEntity; 22 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 23 | import org.springframework.http.converter.StringHttpMessageConverter; 24 | import org.springframework.util.StringUtils; 25 | import org.springframework.web.client.RestTemplate; 26 | 27 | import java.nio.charset.StandardCharsets; 28 | 29 | @Slf4j 30 | public class GoogleSearchTest extends TestBase { 31 | 32 | public GoogleSearchTest() { 33 | super(getOpt()); 34 | } 35 | 36 | private static MutableCapabilities getOpt() { 37 | ChromeOptions chromeOptions = new ChromeOptions(); 38 | chromeOptions.setExperimentalOption("excludeSwitches", new String[]{"enable-automation"}); 39 | chromeOptions.setExperimentalOption("useAutomationExtension", false); 40 | chromeOptions.addArguments("lang=zh-CN,zh,zh-TW,en-US,en"); 41 | chromeOptions.addArguments("user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"); 42 | chromeOptions.addArguments("disable-blink-features=AutomationControlled"); 43 | return chromeOptions; 44 | } 45 | 46 | public boolean waitForJStoLoad(RemoteWebDriver webDriver) { 47 | 48 | WebDriverWait wait = new WebDriverWait(webDriver, 30); 49 | 50 | // wait for jQuery to load 51 | ExpectedCondition jQueryLoad = new ExpectedCondition() { 52 | @Override 53 | public Boolean apply(WebDriver driver) { 54 | try { 55 | JavascriptExecutor j = (JavascriptExecutor) driver; 56 | return ((Long) j.executeScript("return jQuery.active") == 0); 57 | } catch (Exception e) { 58 | return true; 59 | } 60 | } 61 | }; 62 | 63 | // wait for Javascript to load 64 | ExpectedCondition jsLoad = new ExpectedCondition() { 65 | @Override 66 | public Boolean apply(WebDriver driver) { 67 | JavascriptExecutor j = (JavascriptExecutor) driver; 68 | return j.executeScript("return document.readyState") 69 | .toString().equals("complete"); 70 | } 71 | }; 72 | 73 | return wait.until(jQueryLoad) && wait.until(jsLoad); 74 | } 75 | 76 | @Test 77 | public void enterGoogleSearchAndViewResults() { 78 | WebDriver webDriver = getDriver(); 79 | By searchLocator = By.cssSelector("input[value='Google Search']"); 80 | webDriver.navigate().to("http://www.google.com"); 81 | WebElement searchText = webDriver.findElement(By.cssSelector("input[title=Search]")); 82 | searchText.sendKeys("hi"); 83 | WebElement searchButton = webDriver.findElement(searchLocator); 84 | searchButton.click(); 85 | Assert.assertEquals("hi - Google Search", webDriver.getTitle()); 86 | } 87 | 88 | @Test 89 | public void enterGoogleSearchAndImageSearch() { 90 | WebDriver webDriver = getDriver(); 91 | By searchLocator = By.cssSelector("input[value='Google Search']"); 92 | webDriver.navigate().to("http://www.google.com"); 93 | WebElement searchText = webDriver.findElement(By.cssSelector("input[title=Search]")); 94 | searchText.sendKeys("hi"); 95 | WebElement searchButton = webDriver.findElement(searchLocator); 96 | searchButton.click(); 97 | WebElement imageSearch = webDriver.findElement(By.xpath("//a[contains(text(), 'Images')]")); 98 | imageSearch.click(); 99 | } 100 | 101 | 102 | } -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/LocalDriverTest.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import com.meread.selenium.util.OpenCVUtil; 4 | import org.junit.Test; 5 | import org.openqa.selenium.remote.RemoteWebDriver; 6 | 7 | import java.io.IOException; 8 | 9 | /** 10 | * @author yangxg 11 | * @date 2021/10/9 12 | */ 13 | public class LocalDriverTest { 14 | @Test 15 | public void test1() throws IOException { 16 | OpenCVUtil.test(); 17 | } 18 | 19 | @Test 20 | public void test2() throws IOException { 21 | OpenCVUtil.test2(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/QRTest.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import com.meread.selenium.bean.JDCookie; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.junit.Test; 6 | import org.openqa.selenium.By; 7 | import org.openqa.selenium.Cookie; 8 | import org.openqa.selenium.JavascriptExecutor; 9 | import org.openqa.selenium.WebDriver; 10 | import org.openqa.selenium.chrome.ChromeOptions; 11 | import org.openqa.selenium.firefox.FirefoxOptions; 12 | import org.openqa.selenium.remote.RemoteWebDriver; 13 | import org.openqa.selenium.support.ui.ExpectedCondition; 14 | import org.openqa.selenium.support.ui.WebDriverWait; 15 | 16 | import java.util.Map; 17 | import java.util.Set; 18 | 19 | @Slf4j 20 | public class QRTest extends TestBaseSelenium { 21 | 22 | public QRTest() { 23 | super(chromeOptions); 24 | } 25 | 26 | public boolean waitForJStoLoad(RemoteWebDriver webDriver) { 27 | 28 | WebDriverWait wait = new WebDriverWait(webDriver, 30); 29 | 30 | // wait for jQuery to load 31 | ExpectedCondition jQueryLoad = new ExpectedCondition() { 32 | @Override 33 | public Boolean apply(WebDriver driver) { 34 | try { 35 | JavascriptExecutor j = (JavascriptExecutor) driver; 36 | return ((Long) j.executeScript("return jQuery.active") == 0); 37 | } catch (Exception e) { 38 | return true; 39 | } 40 | } 41 | }; 42 | 43 | // wait for Javascript to load 44 | ExpectedCondition jsLoad = new ExpectedCondition() { 45 | @Override 46 | public Boolean apply(WebDriver driver) { 47 | JavascriptExecutor j = (JavascriptExecutor) driver; 48 | return j.executeScript("return document.readyState") 49 | .toString().equals("complete"); 50 | } 51 | }; 52 | 53 | return wait.until(jQueryLoad) && wait.until(jsLoad); 54 | } 55 | 56 | public static final ChromeOptions chromeOptions; 57 | 58 | static { 59 | chromeOptions = new ChromeOptions(); 60 | chromeOptions.setExperimentalOption("excludeSwitches", new String[]{"enable-automation"}); 61 | chromeOptions.setExperimentalOption("useAutomationExtension", true); 62 | chromeOptions.addArguments("lang=zh-CN,zh,zh-TW,en-US,en"); 63 | chromeOptions.addArguments("user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"); 64 | chromeOptions.addArguments("disable-blink-features=AutomationControlled"); 65 | chromeOptions.setCapability("selenoid:options", Map.of( 66 | "enableVNC", true, 67 | "enableVideo", false, 68 | "enableLog", true 69 | )); 70 | chromeOptions.addArguments("--disable-gpu"); 71 | chromeOptions.addArguments("--headless"); 72 | chromeOptions.addArguments("--no-sandbox"); 73 | chromeOptions.addArguments("--disable-extensions"); 74 | chromeOptions.addArguments("--disable-software-rasterizer"); 75 | chromeOptions.addArguments("--ignore-ssl-errors=yes"); 76 | chromeOptions.addArguments("--ignore-certificate-errors"); 77 | chromeOptions.addArguments("--allow-running-insecure-content"); 78 | chromeOptions.addArguments("--window-size=500,700"); 79 | } 80 | 81 | @Test 82 | public void testQQ_login_selenium() { 83 | JDCookie ck = new JDCookie(); 84 | WebDriver webDriver = getDriver(DriverType.selenium); 85 | loop2get_ck(ck, webDriver); 86 | } 87 | 88 | @Test 89 | public void testQQ_login_selenoid() { 90 | JDCookie ck = new JDCookie(); 91 | WebDriver webDriver = getDriver(DriverType.selenoid); 92 | loop2get_ck(ck, webDriver); 93 | } 94 | 95 | private void loop2get_ck(JDCookie ck, WebDriver webDriver) { 96 | while (true) { 97 | webDriver.navigate().to("https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=100273020&redirect_uri=https%3A%2F%2Fplogin.m.jd.com%2Fcgi-bin%2Fml%2Fqqcallback%3Flsid%3D6pifrkrkjsb6t3mvpr0tplsrgpqa51rcq9sitj2dbej5h617&state=y20nntf3"); 98 | try { 99 | Thread.sleep(2000); 100 | } catch (InterruptedException e) { 101 | e.printStackTrace(); 102 | } 103 | String pageText = webDriver.findElement(By.tagName("body")).getText(); 104 | System.out.println(pageText); 105 | //二维码失效 请点击刷新 106 | Set cookies = webDriver.manage().getCookies(); 107 | for (Cookie cookie : cookies) { 108 | if ("pt_key".equals(cookie.getName())) { 109 | ck.setPtKey(cookie.getValue()); 110 | break; 111 | } 112 | } 113 | for (Cookie cookie : cookies) { 114 | if ("pt_pin".equals(cookie.getName())) { 115 | ck.setPtPin(cookie.getValue()); 116 | break; 117 | } 118 | } 119 | if (!ck.isEmpty()) { 120 | System.out.println(ck.toString()); 121 | break; 122 | } 123 | } 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/TestBase.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import org.junit.After; 4 | import org.junit.AfterClass; 5 | import org.junit.Before; 6 | import org.junit.runners.Parameterized; 7 | import org.openqa.selenium.MutableCapabilities; 8 | import org.openqa.selenium.WebDriver; 9 | import org.openqa.selenium.chrome.ChromeOptions; 10 | import org.openqa.selenium.firefox.FirefoxOptions; 11 | import org.openqa.selenium.remote.RemoteWebDriver; 12 | 13 | import java.net.URL; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | /** 17 | * Created by yangxg on 2021/9/1 18 | * 19 | * @author yangxg 20 | */ 21 | public class TestBase { 22 | 23 | protected static ThreadLocal driver = new ThreadLocal<>(); 24 | 25 | public RemoteWebDriver getDriver() { 26 | return driver.get(); 27 | } 28 | 29 | public MutableCapabilities capabilities; 30 | 31 | @Parameterized.Parameters 32 | public static MutableCapabilities[] getBrowserCapabilities() { 33 | return new MutableCapabilities[]{ 34 | new ChromeOptions() 35 | }; 36 | } 37 | 38 | public TestBase(MutableCapabilities capabilities) { 39 | this.capabilities = capabilities; 40 | } 41 | 42 | @Before 43 | public void setUp() throws Exception { 44 | RemoteWebDriver webDriver = new RemoteWebDriver(new URL("http://10.10.10.6:4444/"), capabilities); 45 | webDriver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS); 46 | driver.set(webDriver); 47 | } 48 | 49 | @After 50 | public void tearDown() { 51 | getDriver().quit(); 52 | } 53 | 54 | @AfterClass 55 | public static void remove() { 56 | driver.remove(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/TestBaseSelenium.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import org.junit.After; 4 | import org.junit.AfterClass; 5 | import org.junit.Before; 6 | import org.openqa.selenium.chrome.ChromeOptions; 7 | import org.openqa.selenium.remote.RemoteWebDriver; 8 | 9 | import java.net.URL; 10 | 11 | /** 12 | * Created by yangxg on 2021/9/1 13 | * 14 | * @author yangxg 15 | */ 16 | public class TestBaseSelenium { 17 | 18 | public enum DriverType { 19 | selenium, 20 | selenoid 21 | } 22 | 23 | protected static ThreadLocal driver = new ThreadLocal<>(); 24 | protected static ThreadLocal driver_selenoid = new ThreadLocal<>(); 25 | 26 | public RemoteWebDriver getDriver(DriverType type) { 27 | return type == DriverType.selenium ? driver.get() : driver_selenoid.get(); 28 | } 29 | 30 | public ChromeOptions capabilities; 31 | 32 | 33 | public TestBaseSelenium(ChromeOptions capabilities) { 34 | this.capabilities = capabilities; 35 | } 36 | 37 | @Before 38 | public void setUp() throws Exception { 39 | RemoteWebDriver webDriver = new RemoteWebDriver(new URL("http://localhost:4445/wd/hub"), capabilities); 40 | RemoteWebDriver webDriver_selenoid = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), capabilities); 41 | driver.set(webDriver); 42 | driver_selenoid.set(webDriver_selenoid); 43 | } 44 | 45 | @After 46 | public void tearDown() { 47 | getDriver(DriverType.selenium).quit(); 48 | getDriver(DriverType.selenoid).quit(); 49 | } 50 | 51 | @AfterClass 52 | public static void remove() { 53 | driver.remove(); 54 | driver_selenoid.remove(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/TestBaseSelenoid.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import org.junit.After; 4 | import org.junit.AfterClass; 5 | import org.junit.Before; 6 | import org.openqa.selenium.chrome.ChromeOptions; 7 | import org.openqa.selenium.remote.RemoteWebDriver; 8 | 9 | import java.net.URL; 10 | 11 | /** 12 | * Created by yangxg on 2021/9/1 13 | * 14 | * @author yangxg 15 | */ 16 | public class TestBaseSelenoid { 17 | 18 | protected static ThreadLocal driver = new ThreadLocal<>(); 19 | 20 | public RemoteWebDriver getDriver() { 21 | return driver.get(); 22 | } 23 | 24 | public ChromeOptions capabilities; 25 | 26 | 27 | public TestBaseSelenoid(ChromeOptions capabilities) { 28 | this.capabilities = capabilities; 29 | } 30 | 31 | @Before 32 | public void setUp() throws Exception { 33 | RemoteWebDriver webDriver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), capabilities); 34 | driver.set(webDriver); 35 | } 36 | 37 | @After 38 | public void tearDown() { 39 | getDriver().quit(); 40 | } 41 | 42 | @AfterClass 43 | public static void remove() { 44 | driver.remove(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/meread/selenium/WsTest.java: -------------------------------------------------------------------------------- 1 | package com.meread.selenium; 2 | 3 | import com.amihaiemil.docker.Container; 4 | import com.amihaiemil.docker.Containers; 5 | import com.amihaiemil.docker.UnixDocker; 6 | import com.meread.selenium.ws.PageEventHandler; 7 | import org.springframework.web.socket.client.standard.StandardWebSocketClient; 8 | import org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport; 9 | import org.springframework.web.socket.sockjs.client.SockJsClient; 10 | import org.springframework.web.socket.sockjs.client.Transport; 11 | import org.springframework.web.socket.sockjs.client.WebSocketTransport; 12 | 13 | import java.io.File; 14 | import java.io.IOException; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by yangxg on 2021/9/22 20 | * 21 | * @author yangxg 22 | */ 23 | public class WsTest { 24 | public static void main(String[] args) throws IOException { 25 | // curl --include \ 26 | // --no-buffer \ 27 | // --header "Connection: Upgrade" \ 28 | // --header "Upgrade: websocket" \ 29 | // --header "Host: localhost:6700" \ 30 | // --header "Origin: http://localhost:6700" \ 31 | // --header "Sec-WebSocket-Key: XXXXXXXXX" \ 32 | // --header "Sec-WebSocket-Version: 13" \ 33 | // http://localhost:6700/ 34 | List transports = new ArrayList<>(2); 35 | transports.add(new WebSocketTransport(new StandardWebSocketClient())); 36 | transports.add(new RestTemplateXhrTransport()); 37 | 38 | SockJsClient sockJsClient = new SockJsClient(transports); 39 | sockJsClient.doHandshake(new PageEventHandler(), "ws://localhost:8080/ws/page"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /修复版安装教程.md: -------------------------------------------------------------------------------- 1 | ### 安装教程: 2 | ``` 3 | cd jd-qinglong/adbot 4 | rm -rf adbot device/ gmc_config.json logs/ 5 | cd ../ 6 | docker stop webapp 7 | docker rm -f webapp 8 | docker pull rubyangxg/jd-qinglong 9 | docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true -v "$(pwd)"/env.properties:/env.properties:rw -v "$(pwd)"/adbot:/adbot rubyangxg/jd-qinglong 10 | cd adbot 11 | chmod +x adbot 12 | ``` 13 | 14 | ### 修改start-adbot.sh中的用户名、密码、端口 15 | 16 | 启动机器人平台命令 17 | ``` 18 | ./start-adbot.sh start 19 | ``` 20 | 重启机器人平台命令 21 | ``` 22 | ./start-adbot.sh restart 23 | ``` 24 | 停止机器人平台命令 25 | ``` 26 | ./start-adbot.sh stop 27 | ``` 28 | 29 | 1、如何暴露访问端口? 30 | 31 | 服务器暴露8100和5701即可 32 | 33 | 2、如何修改暴露端口 34 | * 8100是机器人平台登陆端口,对应start-adbot.sh中的8100 35 | * 5701对应docker run启动命令中的5701,阿东网页入口,后台/admin/migrate入口 36 | * 9527对应docker run启动命令中的9527,阿东管理程序入口(隐藏),用于机器人更新、guide更新等 37 | * 5701和9527对应gmc_config.json中的5701和9527,表示qq机器人接受消息后上报端口 38 | -------------------------------------------------------------------------------- /安装教程.md: -------------------------------------------------------------------------------- 1 | ### 安装教程: 2 | 一:手动安装: 3 | 1、创建一个空目录(用于存放env.properties和go-cqhttp数据) 4 | mkdir jd-qinglong && cd jd-qinglong 5 | 2、下载配置文件模板,根据需要修改,不要缺少此文件 6 | 国外请使用: 7 | wget -O env.properties https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/env.template.properties 8 | 国内请使用: 9 | wget -O env.properties https://ghproxy.com/https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/env.template.properties 10 | 11 | 3、下载rubyangxg/jd-qinglong镜像,注意arm的请把2.0替换为arm,X86使用2.0或者latest即可 12 | sudo docker pull rubyangxg/jd-qinglong:2.0 13 | 14 | 4、启动,其中env.properties中的SE_NODE_MAX_SESSIONS=8请根据机器配置改,机器要求最少1h2g,推荐2h4g 注意这是1条命令,全部复制执行,注意\后面不要有空格 15 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --restart always --privileged=true -v [你的路径]/env.properties:/env.properties:rw -v [你的路径]/adbot:/adbot rubyangxg/jd-qinglong:2.0 16 | 或者 17 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true --restart always \ 18 | -v [你的路径]/env.properties:/env.properties:rw \ 19 | -v [你的路径]/adbot:/adbot \ 20 | rubyangxg/jd-qinglong:2.0 21 | 22 | 5、arm的启动有所不同,请仔细甄别 23 | sudo docker run -d -p 5701:8080 -p 9527:8090 --name=webapp --privileged=true --restart always \ 24 | -e "SPRING_PROFILES_ACTIVE=arm" \ 25 | -v [你的路径]/env.properties:/env.properties:rw \ 26 | -v [你的路径]/adbot:/adbot \ 27 | rubyangxg/jd-qinglong:arm 28 | 29 | 6、或者目录下编写docker-compose.yml,arm请将2.0改为arm 30 | 31 | version: '3.3' 32 | services: 33 | jd-qinglong: 34 | ports: 35 | - 5701:8080 36 | - 9527:8090 37 | container_name: jd-login 38 | privileged: true 39 | volumes: 40 | - ./env.properties:/env.properties:rw 41 | - ./adbot:/adbot 42 | image: rubyangxg/jd-qinglong:2.0 43 | 44 | 然后在docker-compose.yml目录下执行命令 45 | 46 | docker-compose up -d 47 | 48 | 7、若要配置qq交互,往下看。 49 | 50 | 使用: 51 | 52 | env.properties中的ADBOT_QQ和ADBOT_PASSWORD必须配置,否则不能自动登录和识别机器人 53 | 启动镜像后,请先访问8100,理论上会有一个待认证的机器人,你自己认证就行。如果没有,自行登录你的qq机器人(env.properties配置的那个),优先选择扫码登录,按照提示操作就行。 54 | 登录成功后,重启镜像docker restart webapp 55 | 如果碰到机器人假死,请执行 --> 重启 adbot 56 | 恭喜你安装成功。好用的话给我点个star吧! 57 | 58 | ### 修改start-adbot.sh中的用户名、密码、端口 59 | 60 | ###如遇到手动和一键安装无法启动机器人 61 | ###机器人命令须cd到adbot目录执行 62 | 63 | 启动机器人平台命令 64 | ``` 65 | ./start-adbot.sh start 66 | ``` 67 | 重启机器人平台命令 68 | ``` 69 | ./start-adbot.sh restart 70 | ``` 71 | 停止机器人平台命令 72 | ``` 73 | ./start-adbot.sh stop 74 | 75 | 8、ssh更新命令: 76 | 77 | docker exec -it webapp guide 78 | 79 | 80 | 8.1、如何暴露访问端口? 81 | 82 | 服务器暴露8100和5701即可 83 | 84 | 8.2、如何修改暴露端口 85 | 86 | * 8100是机器人平台登陆端口,对应start-adbot.sh中的8100 87 | * 5701对应docker run启动命令中的5701,阿东网页入口,后台/admin/migrate入口 88 | * 9527对应docker run启动命令中的9527,阿东管理程序入口(隐藏),用于机器人更新、guide更新等 89 | * 5701和9527对应gmc_config.json中的5701和9527,表示qq机器人接受消息后上报端口 90 | 91 | 8.3如遇8100无法打开请放行8100端口 92 | 93 | 1、打开SSH终端; 94 | 2、输入“netstat -tlunp”命令查看端口占用情况,8100在tcp6下则需要放行,云服务器可去服务器后台防火墙放行 95 | 3、firewall-cmd --zone=public --add-port=端口号/tcp --permanent,端口号改为8100或你自定义的端口回车 96 | 4、重启防火墙 97 | systemctl restart firewalld.service 98 | 99 | 9、出现验证白屏的需修改配置文件下ADONG.URL=http://localhost:5701为自己的IP或域名加端口保存,并重启阿东容器 100 | 101 | 二:一键安装 102 | ###推荐使用库首页一键安装 103 | 一键安装(centos7+,ubuntu18+,debian9+,群晖,甲骨文arm需要ubuntu) 104 | 105 | 国外使用: 106 | bash <(curl -s -L https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/install.sh) 107 | 国内用: 108 | bash <(curl -s -L https://ghproxy.com/https://raw.githubusercontent.com/rubyangxg/jd-qinglong/master/install.sh) 109 | 110 | 群晖注意事项: 111 | 1.须cd到docker目录执行一键安装 112 | 2.如无法使用一键安装,须下载install.sh并修改路径为docker下jd-qinglong对应目录 113 | adbotDir="你的jd-qinglong路径"/adbot 114 | "你的jd-qinglong路径"/env.properties 115 | "你的jd-qinglong路径"/adbot:/adbot 116 | 运行bash install.sh安装即可 117 | --------------------------------------------------------------------------------