已实现文档浏览、视频播放功能,答题模块中简答题、判断题以及多选题的答案填充待实现;
部分异常捕获需要重做相关逻辑
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
target/
|
||||||
|
!.mvn/wrapper/maven-wrapper.jar
|
||||||
|
!**/src/main/**/target/
|
||||||
|
!**/src/test/**/target/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea/modules.xml
|
||||||
|
.idea/jarRepositories.xml
|
||||||
|
.idea/compiler.xml
|
||||||
|
.idea/libraries/
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
|
||||||
|
### Eclipse ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
build/
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
### Mac OS ###
|
||||||
|
.DS_Store
|
||||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
7
.idea/encodings.xml
generated
Normal file
7
.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/misc.xml
generated
Normal file
14
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
78
pom.xml
Normal file
78
pom.xml
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>work.slhaf</groupId>
|
||||||
|
<artifactId>CxstudyAuto</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.36</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>selenium-java</artifactId>
|
||||||
|
<version>4.29.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>2.0.17</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<version>1.5.17</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>ocr_api20210707</artifactId>
|
||||||
|
<version>3.1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.bonigarcia</groupId>
|
||||||
|
<artifactId>webdrivermanager</artifactId>
|
||||||
|
<version>5.9.3</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.36</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>RELEASE</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13.2</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!--<dependency>
|
||||||
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
<artifactId>htmlunit-driver</artifactId>
|
||||||
|
<version>4.13.0</version>
|
||||||
|
</dependency>-->
|
||||||
|
<!--<dependency>
|
||||||
|
<groupId>net.sourceforge.htmlunit</groupId>
|
||||||
|
<artifactId>htmlunit</artifactId>
|
||||||
|
<version>2.70.0</version>
|
||||||
|
</dependency>-->
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.source>21</maven.compiler.source>
|
||||||
|
<maven.compiler.target>21</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
</project>
|
||||||
21
src/main/java/work/slhaf/Main.java
Normal file
21
src/main/java/work/slhaf/Main.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package work.slhaf;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import work.slhaf.config.Config;
|
||||||
|
import work.slhaf.task.cxstudy.CxstudyTask;
|
||||||
|
import work.slhaf.util.AiUtil;
|
||||||
|
import work.slhaf.util.OcrUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws InterruptedException, IOException {
|
||||||
|
Config config = Config.load();
|
||||||
|
AiUtil.load(config);
|
||||||
|
OcrUtil.load(config);
|
||||||
|
CxstudyTask.run(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
77
src/main/java/work/slhaf/chat/ChatClient.java
Normal file
77
src/main/java/work/slhaf/chat/ChatClient.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package work.slhaf.chat;
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpRequest;
|
||||||
|
import cn.hutool.http.HttpResponse;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import work.slhaf.chat.constant.Constant;
|
||||||
|
import work.slhaf.chat.pojo.ChatBody;
|
||||||
|
import work.slhaf.chat.pojo.ChatResponse;
|
||||||
|
import work.slhaf.chat.pojo.Message;
|
||||||
|
import work.slhaf.chat.pojo.PrimaryChatResponse;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ChatClient {
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
private String url;
|
||||||
|
private String apikey;
|
||||||
|
private String model;
|
||||||
|
|
||||||
|
private int top_p;
|
||||||
|
private int temperature;
|
||||||
|
private int max_tokens;
|
||||||
|
|
||||||
|
public ChatClient(String url,String apikey,String model){
|
||||||
|
this.url = url;
|
||||||
|
this.apikey = apikey;
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatResponse runChat(List<Message> messages){
|
||||||
|
HttpRequest request = HttpRequest.post(url);
|
||||||
|
request.header("Content-Type", "application/json");
|
||||||
|
request.header("Authorization", "Bearer " + apikey);
|
||||||
|
|
||||||
|
ChatBody body;
|
||||||
|
if (top_p > 0) {
|
||||||
|
body = ChatBody.builder()
|
||||||
|
.model(model)
|
||||||
|
.messages(messages)
|
||||||
|
.top_p(top_p)
|
||||||
|
.temperature(temperature)
|
||||||
|
.max_tokens(max_tokens)
|
||||||
|
.build();
|
||||||
|
}else {
|
||||||
|
body = ChatBody.builder()
|
||||||
|
.model(model)
|
||||||
|
.messages(messages)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse response = request.body(JSONUtil.toJsonStr(body)).execute();
|
||||||
|
ChatResponse finalResponse;
|
||||||
|
try {
|
||||||
|
PrimaryChatResponse primaryChatResponse = JSONUtil.toBean(response.body(), PrimaryChatResponse.class);
|
||||||
|
finalResponse = ChatResponse.builder()
|
||||||
|
.type(Constant.Response.SUCCESS)
|
||||||
|
.message(primaryChatResponse.getChoices().get(0).getMessage().getContent())
|
||||||
|
.usageBean(primaryChatResponse.getUsage())
|
||||||
|
.build();
|
||||||
|
}catch (Exception e){
|
||||||
|
finalResponse = ChatResponse.builder()
|
||||||
|
.type(Constant.Response.ERROR)
|
||||||
|
.message(response.getStatus()+": "+response.body())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
response.close();
|
||||||
|
return finalResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
22
src/main/java/work/slhaf/chat/constant/Constant.java
Normal file
22
src/main/java/work/slhaf/chat/constant/Constant.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package work.slhaf.chat.constant;
|
||||||
|
|
||||||
|
public class Constant {
|
||||||
|
|
||||||
|
public static class Character {
|
||||||
|
public static final String USER = "user";
|
||||||
|
public static final String SYSTEM = "system";
|
||||||
|
public static final String ASSISTANT = "assistant";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Model {
|
||||||
|
public static final String DEEP_SEEK_CHAT = "deepseek-chat";
|
||||||
|
public static final String GLM_4_FLASH = "glm-4_flash";
|
||||||
|
public static final String GLM_4_PLUS = "glm-4_plus";
|
||||||
|
public static final String GLM_4_0520 = "glm-4_0520";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response {
|
||||||
|
public static final String SUCCESS = "success";
|
||||||
|
public static final String ERROR = "error";
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/main/java/work/slhaf/chat/pojo/ChatBody.java
Normal file
25
src/main/java/work/slhaf/chat/pojo/ChatBody.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package work.slhaf.chat.pojo;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ChatBody {
|
||||||
|
@NonNull
|
||||||
|
private String model;
|
||||||
|
@NonNull
|
||||||
|
private List<Message> messages;
|
||||||
|
@Builder.Default
|
||||||
|
private int temperature = 1;
|
||||||
|
@Builder.Default
|
||||||
|
private int top_p = 1;
|
||||||
|
private boolean stream;
|
||||||
|
@Builder.Default
|
||||||
|
private int max_tokens = 1024;
|
||||||
|
private int presence_penalty;
|
||||||
|
private int frequency_penalty;
|
||||||
|
}
|
||||||
16
src/main/java/work/slhaf/chat/pojo/ChatResponse.java
Normal file
16
src/main/java/work/slhaf/chat/pojo/ChatResponse.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package work.slhaf.chat.pojo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ChatResponse {
|
||||||
|
private String type;
|
||||||
|
private String message;
|
||||||
|
private PrimaryChatResponse.UsageBean usageBean;
|
||||||
|
}
|
||||||
14
src/main/java/work/slhaf/chat/pojo/Message.java
Normal file
14
src/main/java/work/slhaf/chat/pojo/Message.java
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package work.slhaf.chat.pojo;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class Message {
|
||||||
|
@NonNull
|
||||||
|
private String role;
|
||||||
|
@NonNull
|
||||||
|
private String content;
|
||||||
|
}
|
||||||
111
src/main/java/work/slhaf/chat/pojo/PrimaryChatResponse.java
Normal file
111
src/main/java/work/slhaf/chat/pojo/PrimaryChatResponse.java
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
package work.slhaf.chat.pojo;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class PrimaryChatResponse {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
/**
|
||||||
|
* object
|
||||||
|
*/
|
||||||
|
private String object;
|
||||||
|
/**
|
||||||
|
* created
|
||||||
|
*/
|
||||||
|
private int created;
|
||||||
|
/**
|
||||||
|
* model
|
||||||
|
*/
|
||||||
|
private String model;
|
||||||
|
/**
|
||||||
|
* choices
|
||||||
|
*/
|
||||||
|
private List<ChoicesBean> choices;
|
||||||
|
/**
|
||||||
|
* usage
|
||||||
|
*/
|
||||||
|
private UsageBean usage;
|
||||||
|
/**
|
||||||
|
* system_fingerprint
|
||||||
|
*/
|
||||||
|
private String system_fingerprint;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
public static class UsageBean {
|
||||||
|
/**
|
||||||
|
* prompt_tokens
|
||||||
|
*/
|
||||||
|
private int prompt_tokens;
|
||||||
|
/**
|
||||||
|
* completion_tokens
|
||||||
|
*/
|
||||||
|
private int completion_tokens;
|
||||||
|
/**
|
||||||
|
* total_tokens
|
||||||
|
*/
|
||||||
|
private int total_tokens;
|
||||||
|
/**
|
||||||
|
* prompt_cache_hit_tokens
|
||||||
|
*/
|
||||||
|
private int prompt_cache_hit_tokens;
|
||||||
|
/**
|
||||||
|
* prompt_cache_miss_tokens
|
||||||
|
*/
|
||||||
|
private int prompt_cache_miss_tokens;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "UsageBean{" +
|
||||||
|
"prompt_tokens=" + prompt_tokens +
|
||||||
|
", completion_tokens=" + completion_tokens +
|
||||||
|
", total_tokens=" + total_tokens +
|
||||||
|
", prompt_cache_hit_tokens=" + prompt_cache_hit_tokens +
|
||||||
|
", prompt_cache_miss_tokens=" + prompt_cache_miss_tokens +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
public static class ChoicesBean {
|
||||||
|
/**
|
||||||
|
* index
|
||||||
|
*/
|
||||||
|
private int index;
|
||||||
|
/**
|
||||||
|
* message
|
||||||
|
*/
|
||||||
|
private MessageBean message;
|
||||||
|
/**
|
||||||
|
* logprobs
|
||||||
|
*/
|
||||||
|
private Object logprobs;
|
||||||
|
/**
|
||||||
|
* finish_reason
|
||||||
|
*/
|
||||||
|
private String finish_reason;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
public static class MessageBean {
|
||||||
|
/**
|
||||||
|
* role
|
||||||
|
*/
|
||||||
|
private String role;
|
||||||
|
/**
|
||||||
|
* content
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main/java/work/slhaf/config/AiConfig.java
Normal file
21
src/main/java/work/slhaf/config/AiConfig.java
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package work.slhaf.config;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class AiConfig {
|
||||||
|
private String apikey;
|
||||||
|
private String model;
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return """
|
||||||
|
AiConfig:
|
||||||
|
apikey: '%s'
|
||||||
|
model: '%s'
|
||||||
|
""".formatted(apikey, model);
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/main/java/work/slhaf/config/Config.java
Normal file
89
src/main/java/work/slhaf/config/Config.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package work.slhaf.config;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONConfig;
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Slf4j
|
||||||
|
public class Config {
|
||||||
|
private CxstudyConfig cxstudyConfig;
|
||||||
|
private OcrConfig ocrConfig;
|
||||||
|
private AiConfig aiConfig;
|
||||||
|
|
||||||
|
private Config() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Config load() throws IOException {
|
||||||
|
File file = new File("./config.json");
|
||||||
|
boolean configExists = checkConfigExists(file);
|
||||||
|
|
||||||
|
if (!configExists) {
|
||||||
|
generateConfig(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadFromFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void generateConfig(File file) throws IOException {
|
||||||
|
Scanner sc = new Scanner(System.in);
|
||||||
|
System.out.println("\r\n---------------\r\n");
|
||||||
|
AiConfig aiConfig = new AiConfig();
|
||||||
|
System.out.println("recording ai config:");
|
||||||
|
System.out.print("apikey: ");
|
||||||
|
aiConfig.setApikey(sc.nextLine());
|
||||||
|
System.out.print("baseUrl: ");
|
||||||
|
aiConfig.setBaseUrl(sc.nextLine());
|
||||||
|
System.out.print("model: ");
|
||||||
|
aiConfig.setModel(sc.nextLine());
|
||||||
|
|
||||||
|
System.out.println("\r\n---------------\r\n");
|
||||||
|
OcrConfig ocrConfig = new OcrConfig();
|
||||||
|
System.out.println("recording ocr config:");
|
||||||
|
System.out.print("accessKeyId: ");
|
||||||
|
ocrConfig.setAccessKeyId(sc.nextLine());
|
||||||
|
System.out.print("accessKeySecret: ");
|
||||||
|
ocrConfig.setAccessKeySecret(sc.nextLine());
|
||||||
|
|
||||||
|
System.out.println("\r\n---------------\r\n");
|
||||||
|
CxstudyConfig studyConfig = new CxstudyConfig();
|
||||||
|
System.out.println("recording study config:");
|
||||||
|
System.out.print("phone: ");
|
||||||
|
studyConfig.setPhone(sc.nextLine());
|
||||||
|
System.out.print("password: ");
|
||||||
|
studyConfig.setPassword(sc.nextLine());
|
||||||
|
System.out.print("target: ");
|
||||||
|
studyConfig.setTarget(sc.nextLine());
|
||||||
|
|
||||||
|
System.out.println("\r\n---------------\r\n");
|
||||||
|
Config config = new Config();
|
||||||
|
config.cxstudyConfig = studyConfig;
|
||||||
|
config.ocrConfig = ocrConfig;
|
||||||
|
config.aiConfig = aiConfig;
|
||||||
|
String configStr = JSONUtil.toJsonPrettyStr(config);
|
||||||
|
System.out.println("this is your config file: \r\n" + configStr);
|
||||||
|
|
||||||
|
System.out.println("press ENTER to generate config: ");
|
||||||
|
sc.nextLine();
|
||||||
|
|
||||||
|
file.createNewFile();
|
||||||
|
FileUtils.writeStringToFile(file, configStr, StandardCharsets.UTF_8);
|
||||||
|
System.out.println(" config generated");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Config loadFromFile(File file) throws IOException {
|
||||||
|
return JSONUtil.toBean(FileUtils.readFileToString(file, StandardCharsets.UTF_8), Config.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checkConfigExists(File file) {
|
||||||
|
return file.exists();
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/main/java/work/slhaf/config/CxstudyConfig.java
Normal file
22
src/main/java/work/slhaf/config/CxstudyConfig.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package work.slhaf.config;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class CxstudyConfig {
|
||||||
|
private String phone;
|
||||||
|
private String password;
|
||||||
|
private String target;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return """
|
||||||
|
CxstudyConfig:
|
||||||
|
phone: '%s'
|
||||||
|
password: '%s'
|
||||||
|
target: '%s'
|
||||||
|
""".formatted(phone, password, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/main/java/work/slhaf/config/OcrConfig.java
Normal file
20
src/main/java/work/slhaf/config/OcrConfig.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package work.slhaf.config;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class OcrConfig {
|
||||||
|
private String accessKeyId;
|
||||||
|
private String accessKeySecret;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return """
|
||||||
|
OcrConfig:
|
||||||
|
accessKeyId: '%s'
|
||||||
|
accessKeySecret: '%s'
|
||||||
|
""".formatted(accessKeyId, accessKeySecret);
|
||||||
|
}
|
||||||
|
}
|
||||||
186
src/main/java/work/slhaf/pojo/OCRDataInfo.java
Normal file
186
src/main/java/work/slhaf/pojo/OCRDataInfo.java
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package work.slhaf.pojo;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
public class OCRDataInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* algo_version
|
||||||
|
*/
|
||||||
|
private String algo_version;
|
||||||
|
/**
|
||||||
|
* 文字内容-段落
|
||||||
|
*/
|
||||||
|
private String content;
|
||||||
|
/**
|
||||||
|
* height
|
||||||
|
*/
|
||||||
|
private int height;
|
||||||
|
/**
|
||||||
|
* orgHeight
|
||||||
|
*/
|
||||||
|
private int orgHeight;
|
||||||
|
/**
|
||||||
|
* orgWidth
|
||||||
|
*/
|
||||||
|
private int orgWidth;
|
||||||
|
/**
|
||||||
|
* prism_version
|
||||||
|
*/
|
||||||
|
private String prism_version;
|
||||||
|
/**
|
||||||
|
* prism_wnum
|
||||||
|
*/
|
||||||
|
private int prism_wnum;
|
||||||
|
/**
|
||||||
|
* prism_wordsInfo
|
||||||
|
*/
|
||||||
|
private List<PrismWordsInfoBean> prism_wordsInfo;
|
||||||
|
/**
|
||||||
|
* width
|
||||||
|
*/
|
||||||
|
private int width;
|
||||||
|
|
||||||
|
public static class PrismWordsInfoBean {
|
||||||
|
/**
|
||||||
|
* angle
|
||||||
|
*/
|
||||||
|
private int angle;
|
||||||
|
/**
|
||||||
|
* direction
|
||||||
|
*/
|
||||||
|
private int direction;
|
||||||
|
/**
|
||||||
|
* height
|
||||||
|
*/
|
||||||
|
private int height;
|
||||||
|
/**
|
||||||
|
* pos
|
||||||
|
*/
|
||||||
|
private List<PosBean> pos;
|
||||||
|
/**
|
||||||
|
* prob
|
||||||
|
*/
|
||||||
|
private int prob;
|
||||||
|
/**
|
||||||
|
* width
|
||||||
|
*/
|
||||||
|
private int width;
|
||||||
|
/**
|
||||||
|
* 文字内容-行
|
||||||
|
*/
|
||||||
|
private String word;
|
||||||
|
/**
|
||||||
|
* x
|
||||||
|
*/
|
||||||
|
private int x;
|
||||||
|
/**
|
||||||
|
* y
|
||||||
|
*/
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
public int getAngle() {
|
||||||
|
return angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAngle(int angle) {
|
||||||
|
this.angle = angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDirection() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirection(int direction) {
|
||||||
|
this.direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeight(int height) {
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PosBean> getPos() {
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPos(List<PosBean> pos) {
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProb() {
|
||||||
|
return prob;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProb(int prob) {
|
||||||
|
this.prob = prob;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWidth(int width) {
|
||||||
|
this.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWord() {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWord(String word) {
|
||||||
|
this.word = word;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setX(int x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setY(int y) {
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PosBean {
|
||||||
|
/**
|
||||||
|
* x
|
||||||
|
*/
|
||||||
|
private int x;
|
||||||
|
/**
|
||||||
|
* y
|
||||||
|
*/
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setX(int x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setY(int y) {
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
334
src/main/java/work/slhaf/task/cxstudy/CxstudyTask.java
Normal file
334
src/main/java/work/slhaf/task/cxstudy/CxstudyTask.java
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
package work.slhaf.task.cxstudy;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import io.github.bonigarcia.wdm.WebDriverManager;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.openqa.selenium.*;
|
||||||
|
import org.openqa.selenium.firefox.FirefoxDriver;
|
||||||
|
import org.openqa.selenium.interactions.Actions;
|
||||||
|
import org.openqa.selenium.support.ui.ExpectedConditions;
|
||||||
|
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||||
|
import work.slhaf.config.Config;
|
||||||
|
import work.slhaf.task.cxstudy.pojo.answer.Answer;
|
||||||
|
import work.slhaf.task.cxstudy.pojo.answer.MultiChoiceAnswer;
|
||||||
|
import work.slhaf.task.cxstudy.pojo.answer.SingleChoiceAnswer;
|
||||||
|
import work.slhaf.task.cxstudy.pojo.answer.TextAnswer;
|
||||||
|
import work.slhaf.task.cxstudy.pojo.question.MultiChoiceQuestion;
|
||||||
|
import work.slhaf.task.cxstudy.pojo.question.SingleChoiceQuestion;
|
||||||
|
import work.slhaf.task.cxstudy.pojo.question.Question;
|
||||||
|
import work.slhaf.task.cxstudy.pojo.question.TextQuestion;
|
||||||
|
import work.slhaf.util.AiUtil;
|
||||||
|
import work.slhaf.util.OcrUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CxstudyTask {
|
||||||
|
|
||||||
|
static WebDriver driver;
|
||||||
|
static Actions actions;
|
||||||
|
static WebDriverWait wait;
|
||||||
|
|
||||||
|
public static void run(Config config) throws InterruptedException {
|
||||||
|
|
||||||
|
String phone = config.getCxstudyConfig().getPhone();
|
||||||
|
String passwd = config.getCxstudyConfig().getPassword();
|
||||||
|
String targetClassName = config.getCxstudyConfig().getTarget();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
//注册driver
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
//登录、跳转页面
|
||||||
|
loginAndSwitch(phone, passwd, targetClassName);
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
List<WebElement> toBeFinished = getTodoTasks();
|
||||||
|
int chapterCount = 0;
|
||||||
|
while (!toBeFinished.isEmpty()) {
|
||||||
|
//记录元素对应文本
|
||||||
|
toBeFinished.getFirst().click();
|
||||||
|
chapterCount++;
|
||||||
|
log.info("处理第{}节", chapterCount);
|
||||||
|
// toBeFinished.get(14).click();
|
||||||
|
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe"));
|
||||||
|
int subTasksNum = driver.findElements(By.xpath(".//div[@class='ans-attach-ct']")).size();
|
||||||
|
for (int i = 0; i < subTasksNum; i++) {
|
||||||
|
//检测frame内部元素
|
||||||
|
//frame分为三层:
|
||||||
|
// 第一层:index: 6 保存所有含有内容的frame
|
||||||
|
// 第二层:index: i 视频层 i->prev_video_left
|
||||||
|
// 第三层:index: 0->newTestTitle, 2->fileBox
|
||||||
|
// int taskContainersNum = driver.findElements(By.xpath(".//div[@class='ans-attach-ct']")).size();
|
||||||
|
// log.info("发现{}个任务", taskContainersNum);
|
||||||
|
// for (int t = 0; t < taskContainersNum; t++) {
|
||||||
|
// handleVideoTask(i);
|
||||||
|
handleTestTask(i);
|
||||||
|
// handleDocTask(i);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.get(driver.getCurrentUrl());
|
||||||
|
driver.switchTo().defaultContent();
|
||||||
|
toBeFinished = getTodoTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
driver.quit();
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("\r\n---------------\r\n");
|
||||||
|
log.error(e.getLocalizedMessage());
|
||||||
|
log.error("\r\n---------------\r\n");
|
||||||
|
driver.quit();
|
||||||
|
log.error("Something went wrong, will retry later..");
|
||||||
|
Thread.sleep(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void handleDocTask(int i) {
|
||||||
|
try {
|
||||||
|
driver.switchTo().frame(i).switchTo().frame(2);
|
||||||
|
log.info("开始处理文档浏览...");
|
||||||
|
JavascriptExecutor js = (JavascriptExecutor) driver;
|
||||||
|
long lastHeight = (long) js.executeScript("return document.body.scrollHeight;");
|
||||||
|
long currentHeight = 0;
|
||||||
|
int step = 2000;
|
||||||
|
while (currentHeight < lastHeight) {
|
||||||
|
js.executeScript("window.scrollBy(0, arguments[0]);", step);
|
||||||
|
Thread.sleep(600);
|
||||||
|
currentHeight = (long) js.executeScript("return window.scrollY + window.innerHeight;");
|
||||||
|
lastHeight = (long) js.executeScript("return document.body.scrollHeight;");
|
||||||
|
}
|
||||||
|
log.info("文档浏览任务完成!");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
} finally {
|
||||||
|
driver.switchTo().defaultContent();
|
||||||
|
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleTestTask(int i) {
|
||||||
|
try {
|
||||||
|
driver.switchTo().frame(i).switchTo().frame(0);
|
||||||
|
wait.until(ExpectedConditions.elementToBeClickable(By.xpath(".//div[@class='singleQuesId']")));
|
||||||
|
log.info("开始处理测验...");
|
||||||
|
//所有题目元素
|
||||||
|
List<WebElement> questions = driver.findElements(By.xpath(".//div[@class='singleQuesId']"));
|
||||||
|
log.info("发现{}个题目...", questions.size());
|
||||||
|
for (WebElement question : questions) {
|
||||||
|
log.info("处理第{}个题目...", questions.indexOf(question));
|
||||||
|
//获取题目截图,通过OCR获取题目信息
|
||||||
|
WebElement questionTitleElement = question.findElement(By.xpath(".//div[@class='TiMu newTiMu']"));
|
||||||
|
File screenshot = questionTitleElement.getScreenshotAs(OutputType.FILE);
|
||||||
|
|
||||||
|
String questionTitle = OcrUtil.getContentOfImage(screenshot);
|
||||||
|
log.info("题目信息: {}", questionTitle);
|
||||||
|
try {
|
||||||
|
//尝试获取单选选项
|
||||||
|
//li role='radio'
|
||||||
|
question.findElement(By.xpath(".//li[@role='radio']"));
|
||||||
|
List<WebElement> choiceElements = question.findElements(By.xpath(".//li[@role='radio']"));
|
||||||
|
SingleChoiceQuestion singleChoiceQuestion = new SingleChoiceQuestion();
|
||||||
|
singleChoiceQuestion.setTitle(questionTitle);
|
||||||
|
List<String> choices = getChoices(choiceElements);
|
||||||
|
singleChoiceQuestion.setChoices(choices);
|
||||||
|
Answer primaryAnswer = getAnswer(singleChoiceQuestion);
|
||||||
|
SingleChoiceAnswer answer = (SingleChoiceAnswer) primaryAnswer;
|
||||||
|
String answerContent = answer.getAnswer();
|
||||||
|
log.info("答案: {}", answerContent);
|
||||||
|
String xpath = ".//span[@data='" + answerContent + "']";
|
||||||
|
question.findElement(By.xpath(xpath)).findElement(By.xpath("..")).findElement(By.xpath("..")).click();
|
||||||
|
continue;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
//尝试获取复选框
|
||||||
|
//li role='checkbox'
|
||||||
|
question.findElement(By.xpath(".//li[@role='checkbox']"));
|
||||||
|
List<WebElement> choiceElements = question.findElements(By.xpath(".//li[@role='checkbox']"));
|
||||||
|
MultiChoiceQuestion multiChoiceQuestion = new MultiChoiceQuestion();
|
||||||
|
multiChoiceQuestion.setTitle(questionTitle);
|
||||||
|
List<String> choices = getChoices(choiceElements);
|
||||||
|
multiChoiceQuestion.setChoices(choices);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
//尝试跳转到简答题输入框
|
||||||
|
driver.switchTo().frame("ueditor_0");
|
||||||
|
driver.findElement(By.tagName("p")).sendKeys("answer");
|
||||||
|
|
||||||
|
driver.switchTo().parentFrame();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("答题任务完成!");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
} finally {
|
||||||
|
driver.switchTo().defaultContent();
|
||||||
|
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> getChoices(List<WebElement> choiceElements) {
|
||||||
|
List<String> choices = new ArrayList<>();
|
||||||
|
for (WebElement choiceElement : choiceElements) {
|
||||||
|
String label = choiceElement.findElement(By.tagName("label")).getText();
|
||||||
|
String content = choiceElement.findElement(By.tagName("a")).getText();
|
||||||
|
choices.add(String.format("%s. %s", label, content));
|
||||||
|
}
|
||||||
|
return choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Answer getAnswer(Question question) throws InterruptedException {
|
||||||
|
Answer answer = null;
|
||||||
|
int tryTimes = 0;
|
||||||
|
while (tryTimes < 3) {
|
||||||
|
try {
|
||||||
|
String questionStr = JSONUtil.toJsonPrettyStr(question);
|
||||||
|
String responseStr = AiUtil.runChat(questionStr);
|
||||||
|
// 使用正则表达式提取Markdown代码块中的JSON内容
|
||||||
|
String jsonPattern = "```[\\s\\S]*?\\n([\\s\\S]*?)\\n```";
|
||||||
|
Pattern pattern = Pattern.compile(jsonPattern);
|
||||||
|
Matcher matcher = pattern.matcher(responseStr);
|
||||||
|
String jsonStr = "";
|
||||||
|
if (matcher.find()) {
|
||||||
|
jsonStr = matcher.group(1).trim();
|
||||||
|
}
|
||||||
|
if (jsonStr.isEmpty()) {
|
||||||
|
throw new Exception("AI 回答格式不匹配: " + jsonStr);
|
||||||
|
}
|
||||||
|
if (question instanceof SingleChoiceQuestion) {
|
||||||
|
answer = JSONUtil.toBean(jsonStr, SingleChoiceAnswer.class);
|
||||||
|
} else if (question instanceof MultiChoiceQuestion) {
|
||||||
|
answer = JSONUtil.toBean(jsonStr, MultiChoiceAnswer.class);
|
||||||
|
} else if (question instanceof TextQuestion) {
|
||||||
|
answer = JSONUtil.toBean(jsonStr, TextAnswer.class);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getLocalizedMessage());
|
||||||
|
log.error("3s后重试...");
|
||||||
|
Thread.sleep(3000);
|
||||||
|
tryTimes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleVideoTask(int i) {
|
||||||
|
try {
|
||||||
|
driver.switchTo().frame(i);
|
||||||
|
wait.until(ExpectedConditions.elementToBeClickable(By.xpath(".//div[@class='prev_video_left']")));
|
||||||
|
log.info("开始处理视频播放...");
|
||||||
|
WebElement play = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//button[@class='vjs-big-play-button']")));
|
||||||
|
Thread.sleep(2000);
|
||||||
|
play.click();
|
||||||
|
WebElement video = driver.findElement(By.id("video_html5_api"));
|
||||||
|
//获取总时长
|
||||||
|
String totalTime = driver.findElement(By.xpath("//span[@class='vjs-duration-display']")).getText();
|
||||||
|
// 获取视频的位置信息
|
||||||
|
Point location = video.getLocation();
|
||||||
|
Dimension size = video.getSize();
|
||||||
|
|
||||||
|
int videoX = location.getX();
|
||||||
|
int videoY = location.getY();
|
||||||
|
int videoWidth = size.getWidth();
|
||||||
|
int videoHeight = size.getHeight();
|
||||||
|
|
||||||
|
// 确定鼠标起始位置:进入视频区域
|
||||||
|
int startX = videoX + videoWidth / 4;
|
||||||
|
int startY = videoY + videoHeight / 4;
|
||||||
|
actions.moveToElement(video, startX, startY).perform();
|
||||||
|
Thread.sleep(500);
|
||||||
|
|
||||||
|
int step = 10; // 每次移动步长
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
for (int j = 0; j < 20; j++) {
|
||||||
|
int offsetX = (int) (Math.random() * step) - step / 2; // 随机左右移动
|
||||||
|
int offsetY = (int) (Math.random() * step) - step / 2; // 随机上下移动
|
||||||
|
actions.moveByOffset(offsetX, offsetY).perform();
|
||||||
|
Thread.sleep(200);
|
||||||
|
}
|
||||||
|
String currentTime = driver.findElement(By.xpath(".//span[@class='vjs-current-time-display']")).getText();
|
||||||
|
if (totalTime.equals("0:00") || totalTime.isEmpty()) {
|
||||||
|
totalTime = driver.findElement(By.xpath("//span[@class='vjs-duration-display']")).getText();
|
||||||
|
} else if (currentTime.equals(totalTime)) {
|
||||||
|
log.info(totalTime);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
log.info(currentTime + "/" + totalTime);
|
||||||
|
//恢复暂停
|
||||||
|
try {
|
||||||
|
WebElement playingButton = driver.findElement(By.xpath(".//button[@class='vjs-play-control vjs-control vjs-button vjs-paused']"));
|
||||||
|
playingButton.click();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("视频播放任务完成!");
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
} finally {
|
||||||
|
driver.switchTo().defaultContent();
|
||||||
|
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<WebElement> getTodoTasks() {
|
||||||
|
driver.switchTo().defaultContent();
|
||||||
|
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='posCatalog_select']")));
|
||||||
|
List<WebElement> toBeFinished = new ArrayList<>();
|
||||||
|
for (WebElement webElement : driver.findElements(By.xpath("//div[@class='posCatalog_select']"))) {
|
||||||
|
try {
|
||||||
|
webElement.findElement(By.xpath(".//span[@class='orangeNew']"));
|
||||||
|
toBeFinished.add(webElement);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("获取到{}个任务", toBeFinished.size());
|
||||||
|
return toBeFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loginAndSwitch(String phone, String passwd, String targetClassName) {
|
||||||
|
log.info("登录中...");
|
||||||
|
driver.get("https://i.chaoxing.com/");
|
||||||
|
WebElement phoneInput = driver.findElement(By.xpath("//input[@id='phone']"));
|
||||||
|
phoneInput.sendKeys(phone);
|
||||||
|
WebElement passwdInput = driver.findElement(By.xpath("//input[@id='pwd']"));
|
||||||
|
passwdInput.sendKeys(passwd);
|
||||||
|
WebElement submitInput = driver.findElement(By.xpath("//button[@id='loginBtn']"));
|
||||||
|
submitInput.click();
|
||||||
|
|
||||||
|
wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@name='课程']"))).click();
|
||||||
|
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(0));
|
||||||
|
String xpath = ".//span[@title='" + targetClassName + "']";
|
||||||
|
String targetClassHref = wait.until(ExpectedConditions.elementToBeClickable(By.xpath(xpath))).findElement(By.xpath("..")).getDomAttribute("href");
|
||||||
|
driver.get(targetClassHref);
|
||||||
|
|
||||||
|
driver.findElement(By.xpath("//a[@title='章节']")).click();
|
||||||
|
wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(0));
|
||||||
|
wait.until(ExpectedConditions.elementToBeClickable(By.xpath(".//div[@role='link']"))).findElement(By.xpath("..")).click();
|
||||||
|
log.info("登录成功!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void initialize() {
|
||||||
|
WebDriverManager.firefoxdriver().setup();
|
||||||
|
|
||||||
|
driver = new FirefoxDriver();
|
||||||
|
actions = new Actions(driver);
|
||||||
|
wait = new WebDriverWait(driver, Duration.ofSeconds(5));
|
||||||
|
log.info("webdriver创建完毕!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package work.slhaf.task.cxstudy.pojo.answer;
|
||||||
|
|
||||||
|
public class Answer {
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package work.slhaf.task.cxstudy.pojo.answer;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class MultiChoiceAnswer extends Answer {
|
||||||
|
private List<String> answers;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package work.slhaf.task.cxstudy.pojo.answer;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class SingleChoiceAnswer extends Answer {
|
||||||
|
private String answer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package work.slhaf.task.cxstudy.pojo.answer;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class TextAnswer extends Answer {
|
||||||
|
private String answer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package work.slhaf.task.cxstudy.pojo.question;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class MultiChoiceQuestion extends Question {
|
||||||
|
private String type = "多选题";
|
||||||
|
private List<String> choices;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package work.slhaf.task.cxstudy.pojo.question;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Question {
|
||||||
|
private String title;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package work.slhaf.task.cxstudy.pojo.question;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class SingleChoiceQuestion extends Question {
|
||||||
|
private String type = "单选题题";
|
||||||
|
private List<String> choices;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package work.slhaf.task.cxstudy.pojo.question;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class TextQuestion extends Question{
|
||||||
|
private String type = "简答题";
|
||||||
|
}
|
||||||
90
src/main/java/work/slhaf/util/AiUtil.java
Normal file
90
src/main/java/work/slhaf/util/AiUtil.java
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package work.slhaf.util;
|
||||||
|
|
||||||
|
import work.slhaf.chat.ChatClient;
|
||||||
|
import work.slhaf.chat.constant.Constant;
|
||||||
|
import work.slhaf.chat.pojo.ChatResponse;
|
||||||
|
import work.slhaf.chat.pojo.Message;
|
||||||
|
import work.slhaf.config.Config;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AiUtil {
|
||||||
|
private static ChatClient chatClient;
|
||||||
|
private static final String PROMPT = """
|
||||||
|
你是一个智能问卷系统助手。你将接收一个JSON格式的Question对象,需要根据Question的类型和内容,生成对应的Answer JSON,并将结果放入Markdown代码块中。
|
||||||
|
|
||||||
|
Question JSON可能包含以下类型:
|
||||||
|
1. 简答题(TextQuestion):包含title字段和type字段
|
||||||
|
2. 单选题(SingleChoiceQuestion):包含title字段、type字段和choices字段
|
||||||
|
3. 多选题(MultiChoiceQuestion):包含title字段、type字段和choices字段
|
||||||
|
|
||||||
|
请根据以下规则生成Answer JSON:
|
||||||
|
1. 如果Question类型是"简答题",生成TextAnswer对象,包含answer字段
|
||||||
|
2. 如果Question类型是"单选题",生成SingleChoiceAnswer对象,包含answer字段,且answer必须是choices中的一个选项
|
||||||
|
3. 如果Question类型是"多选题",生成MultiChoiceAnswer对象,包含answers字段,且answers必须是choices中的多个选项(至少选择一个)。注意:choices的格式为"A. 答案1","B. 答案2"等,你只需要返回选项字母(如"A"、"B"),不需要返回选项内容
|
||||||
|
|
||||||
|
示例1:
|
||||||
|
输入:
|
||||||
|
{
|
||||||
|
"title": "请描述你的工作经历",
|
||||||
|
"type": "简答题"
|
||||||
|
}
|
||||||
|
输出:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"answer": "我有5年Java开发经验,曾参与多个大型项目..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例2:
|
||||||
|
输入:
|
||||||
|
{
|
||||||
|
"title": "你最喜欢的编程语言是?",
|
||||||
|
"type": "单选题",
|
||||||
|
"choices": ["A. Java", "B. Python", "C. C++"]
|
||||||
|
}
|
||||||
|
输出:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"answer": "B"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例3:
|
||||||
|
输入:
|
||||||
|
{
|
||||||
|
"title": "你使用过哪些数据库?",
|
||||||
|
"type": "多选题",
|
||||||
|
"choices": ["A. MySQL", "B. MongoDB", "C. Redis"]
|
||||||
|
}
|
||||||
|
输出:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"answers": ["A", "C"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:两种选择题的选项内容(不包含序号)可能存在乱码,当输入的Question JSON为选择题时,你需要同时根据choices和title中的内容来确定正确选项。
|
||||||
|
请根据接收到的Question JSON,生成对应的Answer JSON,并将结果放入Markdown代码块中。确保答案内容合理且符合问题要求。
|
||||||
|
""";
|
||||||
|
|
||||||
|
private AiUtil() {}
|
||||||
|
|
||||||
|
public static void load(Config config) {
|
||||||
|
String apikey = config.getAiConfig().getApikey();
|
||||||
|
String model = config.getAiConfig().getModel();
|
||||||
|
String baseUrl = config.getAiConfig().getBaseUrl();
|
||||||
|
chatClient = new ChatClient(baseUrl, apikey, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String runChat(String message) {
|
||||||
|
List<Message> messages = new ArrayList<>();
|
||||||
|
Message system = new Message(Constant.Character.SYSTEM,PROMPT);
|
||||||
|
messages.add(system);
|
||||||
|
messages.add(new Message(Constant.Character.USER,message));
|
||||||
|
|
||||||
|
ChatResponse chatResponse = chatClient.runChat(messages);
|
||||||
|
return chatResponse.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
84
src/main/java/work/slhaf/util/OcrUtil.java
Normal file
84
src/main/java/work/slhaf/util/OcrUtil.java
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package work.slhaf.util;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.aliyun.ocr_api20210707.Client;
|
||||||
|
import com.aliyun.ocr_api20210707.models.RecognizeAdvancedRequest;
|
||||||
|
import com.aliyun.ocr_api20210707.models.RecognizeAdvancedResponse;
|
||||||
|
import com.aliyun.tea.TeaException;
|
||||||
|
import com.aliyun.teautil.models.RuntimeOptions;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import work.slhaf.config.Config;
|
||||||
|
import work.slhaf.pojo.OCRDataInfo;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class OcrUtil {
|
||||||
|
private static Client client;
|
||||||
|
|
||||||
|
private OcrUtil() {}
|
||||||
|
|
||||||
|
public static void load(Config basicConfig) {
|
||||||
|
//读取密钥
|
||||||
|
String accessKeyId = basicConfig.getOcrConfig().getAccessKeyId();
|
||||||
|
String accessKeySecret = basicConfig.getOcrConfig().getAccessKeySecret();
|
||||||
|
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
|
||||||
|
.setAccessKeyId(accessKeyId)
|
||||||
|
.setAccessKeySecret(accessKeySecret);
|
||||||
|
config.endpoint = "ocr-api.cn-hangzhou.aliyuncs.com";
|
||||||
|
try {
|
||||||
|
client = new com.aliyun.ocr_api20210707.Client(config);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建client出错");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
log.info("OCR client已创建");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getContentOfImage(File file) throws FileNotFoundException {
|
||||||
|
//设置请求信息
|
||||||
|
RecognizeAdvancedRequest recognizeAdvancedRequest = new RecognizeAdvancedRequest()
|
||||||
|
.setBody(new FileInputStream(file))
|
||||||
|
.setNeedRotate(Boolean.TRUE)
|
||||||
|
.setNeedRotate(Boolean.TRUE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
//发送请求并处理回应
|
||||||
|
RecognizeAdvancedResponse response = client.recognizeAdvancedWithOptions(recognizeAdvancedRequest, new RuntimeOptions());
|
||||||
|
OCRDataInfo ocrDataInfo = JSONUtil.toBean(response.getBody().getData(), OCRDataInfo.class);
|
||||||
|
if (!ocrDataInfo.getContent().isEmpty()) {
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
ocrDataInfo.getPrism_wordsInfo().forEach(prismWordsInfoBean -> {
|
||||||
|
str.append(prismWordsInfoBean.getWord());
|
||||||
|
if (!ocrDataInfo.getPrism_wordsInfo().get(ocrDataInfo.getPrism_wordsInfo().size() - 1).equals(prismWordsInfoBean)) {
|
||||||
|
str.append("\r\n");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return str.toString();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (TeaException error) {
|
||||||
|
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||||
|
// 错误 message
|
||||||
|
log.error(error.getMessage());
|
||||||
|
// 诊断地址
|
||||||
|
log.error(error.getData().get("Recommend").toString());
|
||||||
|
com.aliyun.teautil.Common.assertAsString(error.message);
|
||||||
|
return "ERROR";
|
||||||
|
} catch (Exception _error) {
|
||||||
|
TeaException error = new TeaException(_error.getMessage(), _error);
|
||||||
|
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||||
|
// 错误 message
|
||||||
|
log.error(error.getMessage());
|
||||||
|
// 诊断地址
|
||||||
|
log.error(error.getData().get("Recommend").toString());
|
||||||
|
com.aliyun.teautil.Common.assertAsString(error.message);
|
||||||
|
return "ERROR";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
src/test/java/ProjectTest.java
Normal file
13
src/test/java/ProjectTest.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import org.junit.Test;
|
||||||
|
import work.slhaf.config.Config;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ProjectTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void configTest() throws IOException {
|
||||||
|
Config config = Config.load();
|
||||||
|
System.out.println(config.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user