From 3ac5678deb59b66a6466f719e6bd1b4afcc3a790 Mon Sep 17 00:00:00 2001 From: slhaf Date: Tue, 25 Mar 2025 12:34:10 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AD=94=E9=A2=98=E6=A8=A1=E5=9D=97=E5=AE=8C?= =?UTF-8?q?=E6=88=90;=20=E5=B0=86CxstudyTask=E8=B0=83=E6=95=B4=E4=B8=BA?= =?UTF-8?q?=E6=99=AE=E9=80=9A=E7=B1=BB,=20=E9=80=9A=E8=BF=87initialize?= =?UTF-8?q?=E5=85=A5=E5=8F=A3=E8=BF=9B=E8=A1=8C=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?;=20=E8=B0=83=E6=95=B4=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84;=20?= =?UTF-8?q?=E7=BB=86=E5=8C=96=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91;=20=E6=B7=BB=E5=8A=A0=E4=BA=86=E9=85=8D=E7=BD=AE:=20?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E5=90=AF=E7=94=A8AI=E7=AD=94=E9=A2=98?= =?UTF-8?q?=E3=80=81=E6=98=AF=E5=90=A6=E5=90=AF=E7=94=A8=E6=97=A0=E5=A4=B4?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F;=20=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=B8=AD=E5=8F=AF=E9=80=89=E6=8B=A9Selenium=E9=A9=B1=E5=8A=A8?= =?UTF-8?q?=EF=BC=8C=E4=BD=86=E9=99=A4firefox=E5=A4=96=E5=9D=87=E6=9C=AA?= =?UTF-8?q?=E7=BB=8F=E6=B5=8B=E8=AF=95;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src/main/java/work/slhaf/Main.java | 12 +- src/main/java/work/slhaf/chat/ChatClient.java | 29 +- src/main/java/work/slhaf/config/AiConfig.java | 6 +- src/main/java/work/slhaf/config/Config.java | 104 +++- .../java/work/slhaf/config/CxstudyConfig.java | 6 +- .../java/work/slhaf/config/OcrConfig.java | 4 +- .../slhaf/exception/AiRequestException.java | 7 + .../exception/AiResponseFormatException.java | 7 + .../slhaf/exception/OcrRequestException.java | 7 + .../java/work/slhaf/pojo/answer/Answer.java | 4 + .../pojo/answer/MultiChoiceAnswer.java | 2 +- .../pojo/answer/SingleChoiceAnswer.java | 2 +- .../cxstudy => }/pojo/answer/TextAnswer.java | 2 +- .../pojo/question/MultiChoiceQuestion.java | 2 +- .../cxstudy => }/pojo/question/Question.java | 2 +- .../pojo/question/SingleChoiceQuestion.java | 2 +- .../pojo/question/TextQuestion.java | 2 +- .../java/work/slhaf/task/CxstudyTask.java | 457 ++++++++++++++++++ .../work/slhaf/task/cxstudy/CxstudyTask.java | 334 ------------- .../task/cxstudy/pojo/answer/Answer.java | 4 - src/main/java/work/slhaf/util/AiUtil.java | 24 +- src/main/java/work/slhaf/util/OcrUtil.java | 25 +- 23 files changed, 621 insertions(+), 424 deletions(-) create mode 100644 src/main/java/work/slhaf/exception/AiRequestException.java create mode 100644 src/main/java/work/slhaf/exception/AiResponseFormatException.java create mode 100644 src/main/java/work/slhaf/exception/OcrRequestException.java create mode 100644 src/main/java/work/slhaf/pojo/answer/Answer.java rename src/main/java/work/slhaf/{task/cxstudy => }/pojo/answer/MultiChoiceAnswer.java (82%) rename src/main/java/work/slhaf/{task/cxstudy => }/pojo/answer/SingleChoiceAnswer.java (79%) rename src/main/java/work/slhaf/{task/cxstudy => }/pojo/answer/TextAnswer.java (78%) rename src/main/java/work/slhaf/{task/cxstudy => }/pojo/question/MultiChoiceQuestion.java (84%) rename src/main/java/work/slhaf/{task/cxstudy => }/pojo/question/Question.java (62%) rename src/main/java/work/slhaf/{task/cxstudy => }/pojo/question/SingleChoiceQuestion.java (84%) rename src/main/java/work/slhaf/{task/cxstudy => }/pojo/question/TextQuestion.java (79%) create mode 100644 src/main/java/work/slhaf/task/CxstudyTask.java delete mode 100644 src/main/java/work/slhaf/task/cxstudy/CxstudyTask.java delete mode 100644 src/main/java/work/slhaf/task/cxstudy/pojo/answer/Answer.java diff --git a/.gitignore b/.gitignore index 3103ea5..f9a81fc 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ build/ ### Mac OS ### .DS_Store /config.json +/config.json.bak diff --git a/src/main/java/work/slhaf/Main.java b/src/main/java/work/slhaf/Main.java index 18f0a37..842b2d3 100644 --- a/src/main/java/work/slhaf/Main.java +++ b/src/main/java/work/slhaf/Main.java @@ -2,7 +2,7 @@ package work.slhaf; import lombok.extern.slf4j.Slf4j; import work.slhaf.config.Config; -import work.slhaf.task.cxstudy.CxstudyTask; +import work.slhaf.task.CxstudyTask; import work.slhaf.util.AiUtil; import work.slhaf.util.OcrUtil; @@ -13,9 +13,13 @@ 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); + if (config.isAiEnable()){ + AiUtil.load(config.getAiConfig()); + OcrUtil.load(config.getOcrConfig()); + } + + CxstudyTask cxstudyTask = CxstudyTask.initialize(config); + cxstudyTask.run(); } } \ No newline at end of file diff --git a/src/main/java/work/slhaf/chat/ChatClient.java b/src/main/java/work/slhaf/chat/ChatClient.java index c5e84ad..8f1e810 100644 --- a/src/main/java/work/slhaf/chat/ChatClient.java +++ b/src/main/java/work/slhaf/chat/ChatClient.java @@ -3,7 +3,6 @@ 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; @@ -12,7 +11,6 @@ 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 @@ -28,13 +26,13 @@ public class ChatClient { private int temperature; private int max_tokens; - public ChatClient(String url,String apikey,String model){ + public ChatClient(String url, String apikey, String model) { this.url = url; this.apikey = apikey; this.model = model; } - public ChatResponse runChat(List messages){ + public ChatResponse runChat(List messages) { HttpRequest request = HttpRequest.post(url); request.header("Content-Type", "application/json"); request.header("Authorization", "Bearer " + apikey); @@ -48,7 +46,7 @@ public class ChatClient { .temperature(temperature) .max_tokens(max_tokens) .build(); - }else { + } else { body = ChatBody.builder() .model(model) .messages(messages) @@ -57,19 +55,14 @@ public class ChatClient { 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(); - } + + 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(); + response.close(); return finalResponse; } diff --git a/src/main/java/work/slhaf/config/AiConfig.java b/src/main/java/work/slhaf/config/AiConfig.java index a6668f4..cbfa15a 100644 --- a/src/main/java/work/slhaf/config/AiConfig.java +++ b/src/main/java/work/slhaf/config/AiConfig.java @@ -6,9 +6,9 @@ import lombok.Setter; @Getter @Setter public class AiConfig { - private String apikey; - private String model; - private String baseUrl; + private String apikey = ""; + private String model = ""; + private String baseUrl = ""; @Override public String toString() { diff --git a/src/main/java/work/slhaf/config/Config.java b/src/main/java/work/slhaf/config/Config.java index 4c07286..da54264 100644 --- a/src/main/java/work/slhaf/config/Config.java +++ b/src/main/java/work/slhaf/config/Config.java @@ -1,7 +1,5 @@ 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; @@ -10,11 +8,16 @@ import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import java.util.Scanner; @Data @Slf4j public class Config { + private String browser; + private boolean aiEnable; + private boolean headless; private CxstudyConfig cxstudyConfig; private OcrConfig ocrConfig; private AiConfig aiConfig; @@ -23,7 +26,7 @@ public class Config { } public static Config load() throws IOException { - File file = new File("./config.json"); + File file = new File("config.json"); boolean configExists = checkConfigExists(file); if (!configExists) { @@ -36,47 +39,98 @@ public class Config { 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("开始记录配置文件: "); + Config config = new Config(); + + System.out.println("选择Selenium驱动,必须与安装的浏览器保持一致。该程序开发时使用firefox驱动,其余浏览器驱动未经过测试"); + System.out.println("可选值: edge, firefox, safari, chrome"); + List browsers = new ArrayList<>(); + browsers.add("edge"); + browsers.add("firefox"); + browsers.add("chrome"); + String browser; + System.out.print("输入你的浏览器名称: "); + while (true) { + browser = sc.nextLine(); + if (browsers.contains(browser)){ + config.setBrowser(browser); + break; + } + System.out.println("可选值为: edge, firefox, chrome"); + System.out.print("输入你的浏览器名称: " ); + } System.out.println("\r\n---------------\r\n"); + System.out.println("是否显示GUI界面?第一次运行建议选择显示,测试没有问题后,再修改配置文件为隐藏GUI界面"); + System.out.print("输入 y (启用) 或 n (禁用) : "); + String headlessStr = ""; + boolean headless; + while (true) { + headlessStr = sc.nextLine(); + if (headlessStr.equals("y") || headlessStr.equals("n")){ + headless = headlessStr.equals("y"); + break; + } + System.out.print("输入 y (启用) 或 n (禁用) : "); + } + config.setHeadless(headless); + + System.out.println("\r\n---------------\r\n"); + String aiEnableStr; + boolean aiEnable; + System.out.print("是否启用答题功能(y / n): "); + while (true) { + aiEnableStr = sc.nextLine(); + if (aiEnableStr.equals("y") || aiEnableStr.equals("n")) { + aiEnable = aiEnableStr.equals("y"); + break; + } + System.out.println("请输入 y (启用) 或 n (禁用) : "); + } + config.setAiEnable(aiEnable); + + AiConfig aiConfig = new AiConfig(); 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()); + if (aiEnable) { + System.out.println("记录AI配置: "); + 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"); + System.out.println("记录OCR(用于题目识别)配置: "); + 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: "); + System.out.println("记录学习通配置: "); + System.out.print("手机号: "); studyConfig.setPhone(sc.nextLine()); - System.out.print("password: "); + System.out.print("密码: "); studyConfig.setPassword(sc.nextLine()); - System.out.print("target: "); + System.out.print("目标课程: "); 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: "); + System.out.println("配置文件如下: \r\n" + configStr); + System.out.println("配置文件将位于: "+file.getAbsolutePath()); + System.out.println("输入 ENTER 以创建配置文件: "); sc.nextLine(); file.createNewFile(); FileUtils.writeStringToFile(file, configStr, StandardCharsets.UTF_8); - System.out.println(" config generated"); + System.out.println("配置文件生成完毕"); } private static Config loadFromFile(File file) throws IOException { diff --git a/src/main/java/work/slhaf/config/CxstudyConfig.java b/src/main/java/work/slhaf/config/CxstudyConfig.java index efd04c9..f225e92 100644 --- a/src/main/java/work/slhaf/config/CxstudyConfig.java +++ b/src/main/java/work/slhaf/config/CxstudyConfig.java @@ -6,9 +6,9 @@ import lombok.Setter; @Getter @Setter public class CxstudyConfig { - private String phone; - private String password; - private String target; + private String phone = ""; + private String password = ""; + private String target = ""; @Override public String toString() { diff --git a/src/main/java/work/slhaf/config/OcrConfig.java b/src/main/java/work/slhaf/config/OcrConfig.java index 6c77ae6..4f4cd6a 100644 --- a/src/main/java/work/slhaf/config/OcrConfig.java +++ b/src/main/java/work/slhaf/config/OcrConfig.java @@ -6,8 +6,8 @@ import lombok.Setter; @Getter @Setter public class OcrConfig { - private String accessKeyId; - private String accessKeySecret; + private String accessKeyId = ""; + private String accessKeySecret = ""; @Override public String toString() { diff --git a/src/main/java/work/slhaf/exception/AiRequestException.java b/src/main/java/work/slhaf/exception/AiRequestException.java new file mode 100644 index 0000000..6fee98f --- /dev/null +++ b/src/main/java/work/slhaf/exception/AiRequestException.java @@ -0,0 +1,7 @@ +package work.slhaf.exception; + +public class AiRequestException extends RuntimeException { + public AiRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/work/slhaf/exception/AiResponseFormatException.java b/src/main/java/work/slhaf/exception/AiResponseFormatException.java new file mode 100644 index 0000000..2a9ae22 --- /dev/null +++ b/src/main/java/work/slhaf/exception/AiResponseFormatException.java @@ -0,0 +1,7 @@ +package work.slhaf.exception; + +public class AiResponseFormatException extends RuntimeException { + public AiResponseFormatException(String message) { + super(message); + } +} diff --git a/src/main/java/work/slhaf/exception/OcrRequestException.java b/src/main/java/work/slhaf/exception/OcrRequestException.java new file mode 100644 index 0000000..166b9ad --- /dev/null +++ b/src/main/java/work/slhaf/exception/OcrRequestException.java @@ -0,0 +1,7 @@ +package work.slhaf.exception; + +public class OcrRequestException extends RuntimeException { + public OcrRequestException(String message) { + super(message); + } +} diff --git a/src/main/java/work/slhaf/pojo/answer/Answer.java b/src/main/java/work/slhaf/pojo/answer/Answer.java new file mode 100644 index 0000000..244fb97 --- /dev/null +++ b/src/main/java/work/slhaf/pojo/answer/Answer.java @@ -0,0 +1,4 @@ +package work.slhaf.pojo.answer; + +public class Answer { +} diff --git a/src/main/java/work/slhaf/task/cxstudy/pojo/answer/MultiChoiceAnswer.java b/src/main/java/work/slhaf/pojo/answer/MultiChoiceAnswer.java similarity index 82% rename from src/main/java/work/slhaf/task/cxstudy/pojo/answer/MultiChoiceAnswer.java rename to src/main/java/work/slhaf/pojo/answer/MultiChoiceAnswer.java index 0e532ca..2909b52 100644 --- a/src/main/java/work/slhaf/task/cxstudy/pojo/answer/MultiChoiceAnswer.java +++ b/src/main/java/work/slhaf/pojo/answer/MultiChoiceAnswer.java @@ -1,4 +1,4 @@ -package work.slhaf.task.cxstudy.pojo.answer; +package work.slhaf.pojo.answer; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/work/slhaf/task/cxstudy/pojo/answer/SingleChoiceAnswer.java b/src/main/java/work/slhaf/pojo/answer/SingleChoiceAnswer.java similarity index 79% rename from src/main/java/work/slhaf/task/cxstudy/pojo/answer/SingleChoiceAnswer.java rename to src/main/java/work/slhaf/pojo/answer/SingleChoiceAnswer.java index ae79c73..f57ed02 100644 --- a/src/main/java/work/slhaf/task/cxstudy/pojo/answer/SingleChoiceAnswer.java +++ b/src/main/java/work/slhaf/pojo/answer/SingleChoiceAnswer.java @@ -1,4 +1,4 @@ -package work.slhaf.task.cxstudy.pojo.answer; +package work.slhaf.pojo.answer; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/work/slhaf/task/cxstudy/pojo/answer/TextAnswer.java b/src/main/java/work/slhaf/pojo/answer/TextAnswer.java similarity index 78% rename from src/main/java/work/slhaf/task/cxstudy/pojo/answer/TextAnswer.java rename to src/main/java/work/slhaf/pojo/answer/TextAnswer.java index ed0021a..ff6237c 100644 --- a/src/main/java/work/slhaf/task/cxstudy/pojo/answer/TextAnswer.java +++ b/src/main/java/work/slhaf/pojo/answer/TextAnswer.java @@ -1,4 +1,4 @@ -package work.slhaf.task.cxstudy.pojo.answer; +package work.slhaf.pojo.answer; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/work/slhaf/task/cxstudy/pojo/question/MultiChoiceQuestion.java b/src/main/java/work/slhaf/pojo/question/MultiChoiceQuestion.java similarity index 84% rename from src/main/java/work/slhaf/task/cxstudy/pojo/question/MultiChoiceQuestion.java rename to src/main/java/work/slhaf/pojo/question/MultiChoiceQuestion.java index bb639ea..8a04984 100644 --- a/src/main/java/work/slhaf/task/cxstudy/pojo/question/MultiChoiceQuestion.java +++ b/src/main/java/work/slhaf/pojo/question/MultiChoiceQuestion.java @@ -1,4 +1,4 @@ -package work.slhaf.task.cxstudy.pojo.question; +package work.slhaf.pojo.question; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/work/slhaf/task/cxstudy/pojo/question/Question.java b/src/main/java/work/slhaf/pojo/question/Question.java similarity index 62% rename from src/main/java/work/slhaf/task/cxstudy/pojo/question/Question.java rename to src/main/java/work/slhaf/pojo/question/Question.java index 39c2539..4ab897b 100644 --- a/src/main/java/work/slhaf/task/cxstudy/pojo/question/Question.java +++ b/src/main/java/work/slhaf/pojo/question/Question.java @@ -1,4 +1,4 @@ -package work.slhaf.task.cxstudy.pojo.question; +package work.slhaf.pojo.question; import lombok.Data; diff --git a/src/main/java/work/slhaf/task/cxstudy/pojo/question/SingleChoiceQuestion.java b/src/main/java/work/slhaf/pojo/question/SingleChoiceQuestion.java similarity index 84% rename from src/main/java/work/slhaf/task/cxstudy/pojo/question/SingleChoiceQuestion.java rename to src/main/java/work/slhaf/pojo/question/SingleChoiceQuestion.java index 261278e..3285eb1 100644 --- a/src/main/java/work/slhaf/task/cxstudy/pojo/question/SingleChoiceQuestion.java +++ b/src/main/java/work/slhaf/pojo/question/SingleChoiceQuestion.java @@ -1,4 +1,4 @@ -package work.slhaf.task.cxstudy.pojo.question; +package work.slhaf.pojo.question; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/work/slhaf/task/cxstudy/pojo/question/TextQuestion.java b/src/main/java/work/slhaf/pojo/question/TextQuestion.java similarity index 79% rename from src/main/java/work/slhaf/task/cxstudy/pojo/question/TextQuestion.java rename to src/main/java/work/slhaf/pojo/question/TextQuestion.java index 604f2e7..337c9db 100644 --- a/src/main/java/work/slhaf/task/cxstudy/pojo/question/TextQuestion.java +++ b/src/main/java/work/slhaf/pojo/question/TextQuestion.java @@ -1,4 +1,4 @@ -package work.slhaf.task.cxstudy.pojo.question; +package work.slhaf.pojo.question; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/src/main/java/work/slhaf/task/CxstudyTask.java b/src/main/java/work/slhaf/task/CxstudyTask.java new file mode 100644 index 0000000..17b41b7 --- /dev/null +++ b/src/main/java/work/slhaf/task/CxstudyTask.java @@ -0,0 +1,457 @@ +package work.slhaf.task; + +import cn.hutool.json.JSONUtil; +import io.github.bonigarcia.wdm.WebDriverManager; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.openqa.selenium.*; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.edge.EdgeDriver; +import org.openqa.selenium.edge.EdgeOptions; +import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.firefox.FirefoxOptions; +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.exception.AiRequestException; +import work.slhaf.exception.AiResponseFormatException; +import work.slhaf.exception.OcrRequestException; +import work.slhaf.pojo.answer.Answer; +import work.slhaf.pojo.answer.MultiChoiceAnswer; +import work.slhaf.pojo.answer.SingleChoiceAnswer; +import work.slhaf.pojo.answer.TextAnswer; +import work.slhaf.pojo.question.MultiChoiceQuestion; +import work.slhaf.pojo.question.Question; +import work.slhaf.pojo.question.SingleChoiceQuestion; +import work.slhaf.pojo.question.TextQuestion; +import work.slhaf.util.AiUtil; +import work.slhaf.util.OcrUtil; + +import java.io.File; +import java.io.FileNotFoundException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +@Getter +@Setter +public class CxstudyTask { + + private WebDriver driver; + private Actions actions; + private WebDriverWait wait; + private JavascriptExecutor executor; + private String phone; + private String passwd; + private String targetClassName; + private boolean aiEnable; + + private CxstudyTask() { + } + + public void run() throws InterruptedException { + + login(); + while (true) { + try { + switchPage(); + //登录、跳转页面 + Thread.sleep(2000); + List toBeFinished = getTodoTasks(); + int chapterCount = 0; + while (!toBeFinished.isEmpty()) { + //记录元素对应文本 + toBeFinished.getFirst().click(); + chapterCount++; + log.info("处理第{}节", chapterCount); + wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe")); + int subTasksNum = driver.findElements(By.xpath(".//div[@class='ans-attach-ct']")).size(); + log.info("发现{}个任务", subTasksNum); + for (int i = 0; i < subTasksNum; i++) { + //检测frame内部元素 + //frame分为三层: + // 第一层:index: 6 保存所有含有内容的frame + // 第二层:index: i 视频层 i->prev_video_left + // 第三层:index: 0->newTestTitle, 2->fileBox + handleVideoTask(i); + if (aiEnable) { + handleTestTask(i); + } + handleDocTask(i); + + } + + driver.get(driver.getCurrentUrl()); + driver.switchTo().defaultContent(); + toBeFinished = getTodoTasks(); + } + + log.info("作答完毕!"); + driver.quit(); + break; + }catch (AiRequestException | OcrRequestException e) { + log.error(e.getMessage()); + driver.quit(); + break; + }catch (WebDriverException e) { + log.error("\r\n---------------\r\n"); + log.error(e.getLocalizedMessage()); + log.error("\r\n---------------\r\n"); + log.error("Something went wrong, will retry later.."); + driver.get("https://i.chaoxing.com/"); + Thread.sleep(5000); + switchPage(); + + } + } + } + + + private void handleDocTask(int i) throws InterruptedException { + try { + driver.switchTo().frame(i).switchTo().frame(2); + log.info("开始处理文档浏览..."); + long lastHeight = (long) executor.executeScript("return document.body.scrollHeight;"); + long currentHeight = 0; + int step = 2000; + while (currentHeight < lastHeight) { + executor.executeScript("window.scrollBy(0, arguments[0]);", step); + Thread.sleep(600); + currentHeight = (long) executor.executeScript("return window.scrollY + window.innerHeight;"); + lastHeight = (long) executor.executeScript("return document.body.scrollHeight;"); + } + log.info("文档浏览任务完成!"); + } catch (WebDriverException ignored) { + } finally { + driver.switchTo().defaultContent(); + wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe")); + } + + } + + private void handleTestTask(int i) { + try { + wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(i)); + wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(0)); + wait.until(ExpectedConditions.elementToBeClickable(By.xpath(".//div[@class='singleQuesId']"))); + log.info("开始处理测验..."); + //所有题目元素 + List 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']")); + handleSingleChoiceQuestion(question, questionTitle); + continue; + } catch (WebDriverException ignored) { + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + //尝试获取复选框 + //li role='checkbox' + question.findElement(By.xpath(".//li[@role='checkbox']")); + handleMultiChoiceQuestion(question, questionTitle); + continue; + } catch (WebDriverException ignored) { + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + try { + //尝试跳转到简答题输入框 + driver.switchTo().frame("ueditor_0"); + + TextQuestion textQuestion = new TextQuestion(); + textQuestion.setTitle(questionTitle); + Answer answer = getAnswer(textQuestion); + TextAnswer textAnswer = (TextAnswer) answer; + WebElement inputArea = driver.findElement(By.xpath(".//body[@contenteditable='true']")); + //输入答案,并检测是否正确输入 + while (Objects.equals(inputArea.getText(), "")) { + inputArea.click(); + inputArea.sendKeys(textAnswer.getAnswer()); + } + driver.switchTo().parentFrame(); + driver.switchTo().parentFrame(); + } catch (WebDriverException ignored) { + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + log.info("答题任务完成!"); + //点击提交按钮,等待并点击弹窗 + driver.findElement(By.xpath(".//a[@class='btnSubmit workBtnIndex']")).click(); + wait.until(ExpectedConditions.elementToBeClickable(By.xpath(".//a[@id='popok']"))).click(); + + } catch (WebDriverException ignored) { + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } finally { + driver.switchTo().defaultContent(); + wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe")); + } + } + + private void handleMultiChoiceQuestion(WebElement question, String questionTitle) throws InterruptedException { + List choiceElements = question.findElements(By.xpath(".//li[@role='checkbox']")); + MultiChoiceQuestion multiChoiceQuestion = new MultiChoiceQuestion(); + multiChoiceQuestion.setTitle(questionTitle); + List choices = getChoices(choiceElements); + multiChoiceQuestion.setChoices(choices); + Answer answer = getAnswer(multiChoiceQuestion); + MultiChoiceAnswer multiChoiceAnswer = (MultiChoiceAnswer) answer; + List answers = multiChoiceAnswer.getAnswers(); + //输入答案,并检测是否正确输入 + String script = getCheckScript(question); + while (Objects.equals(executor.executeScript(script), "")) { + for (String answerContent : answers) { + log.info("答案: {}", answerContent); + String xpath = ".//span[@data='" + answerContent + "']"; + WebElement clickElement = question.findElement(By.xpath(xpath)).findElement(By.xpath("..")).findElement(By.xpath("..")); + String check = clickElement.getDomAttribute("aria-checked"); +// question.findElement(By.xpath(xpath)).findElement(By.xpath("..")).findElement(By.xpath("..")).click(); + while (check == null || check.equals("false")) { + clickElement.click(); + check = clickElement.getDomAttribute("aria-checked"); + } + } + } + } + + private void handleSingleChoiceQuestion(WebElement question, String questionTitle) throws InterruptedException { + List choiceElements = question.findElements(By.xpath(".//li[@role='radio']")); + SingleChoiceQuestion singleChoiceQuestion = new SingleChoiceQuestion(); + singleChoiceQuestion.setTitle(questionTitle); + List choices = getChoices(choiceElements); + singleChoiceQuestion.setChoices(choices); + Answer primaryAnswer = getAnswer(singleChoiceQuestion); + SingleChoiceAnswer answer = (SingleChoiceAnswer) primaryAnswer; + String answerContent = answer.getAnswer(); + log.info("答案: {}", answerContent); + if (choices.size() == 2) { + answerContent = answerContent.equals("A") ? "true" : "false"; + } + String xpath = ".//span[@data='" + answerContent + "']"; + //输入答案,并检测是否正确输入 + String script = getCheckScript(question); + while (Objects.equals(executor.executeScript(script), "")) { + question.findElement(By.xpath(xpath)).findElement(By.xpath("..")).findElement(By.xpath("..")).click(); + } + } + + private String getCheckScript(WebElement question) { + WebElement finalInputElement = question.findElement(By.xpath(".//input[@type='hidden']")); + String id = finalInputElement.getDomAttribute("id"); + return "return document.getElementById('" + id + "').value;"; + } + + private List getChoices(List choiceElements) { + List 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 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 AiResponseFormatException("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 (AiResponseFormatException e) { + log.warn(e.getLocalizedMessage()); + log.warn("3s后重试..."); + Thread.sleep(3000); + tryTimes++; + } + } + + return answer; + } + + private void handleVideoTask(int i) throws InterruptedException { + 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 = 50; // 每次移动步长 + + 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; // 随机上下移动 + try { + actions.moveByOffset(offsetX, offsetY).perform(); + }catch (Exception e) { + actions.moveToElement(video, startX, startY).perform(); + } + Thread.sleep(200); + } + String currentTime = driver.findElement(By.xpath(".//span[@class='vjs-current-time-display']")).getText(); + if (currentTime.isEmpty()){ + actions.moveToElement(video, startX, startY).perform(); + 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)) { + 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 (WebDriverException ignored) { + } + } + log.info("视频播放任务完成!"); + } catch (WebDriverException ignored) { + } finally { + driver.switchTo().defaultContent(); + wait.until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("iframe")); + } + } + + private List getTodoTasks() { + driver.switchTo().defaultContent(); + wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='posCatalog_select']"))); + List 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 (WebDriverException ignored) { + } + } + log.info("获取到{}个任务", toBeFinished.size()); + return toBeFinished; + } + + private void login() { + log.info("登录中..."); + driver.get("https://i.chaoxing.com/"); + WebElement phoneInput = wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//input[@id='phone']"))); +// WebElement phoneInput = driver.findElement(); + 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(); + log.info("登录成功!"); + } + + private void switchPage() throws InterruptedException { + log.info("跳转中..."); + System.out.println(driver.getTitle()); + Thread.sleep(5); + try { + wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@name='课程']"))).click(); + }catch (TimeoutException e){ + driver.findElement(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(); + } + + public static CxstudyTask initialize(Config config) { + log.info("任务创建中..."); + + WebDriverManager.firefoxdriver().clearDriverCache().setup(); + WebDriver driver = switch (config.getBrowser()) { + case "firefox" -> config.isHeadless() ? new FirefoxDriver(new FirefoxOptions().addArguments("--headless")) : new FirefoxDriver(); + case "edge" ->config.isHeadless() ? new EdgeDriver(new EdgeOptions().addArguments("--headless")) : new EdgeDriver(); + case "chrome" ->config.isHeadless() ? new ChromeDriver(new ChromeOptions().addArguments("--headless")) : new ChromeDriver(); + default -> throw new IllegalStateException("不支持的浏览器驱动!: " + config.getBrowser()); + }; + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); + Actions actions = new Actions(driver); + JavascriptExecutor executor = (JavascriptExecutor) driver; + + CxstudyTask cxstudyTask = getCxstudyTask(config, driver, wait, actions, executor); + + log.info("任务创建完毕!"); + return cxstudyTask; + } + + private static CxstudyTask getCxstudyTask(Config config, WebDriver driver, WebDriverWait wait, Actions actions, JavascriptExecutor executor) { + CxstudyTask cxstudyTask = new CxstudyTask(); + cxstudyTask.setDriver(driver); + cxstudyTask.setWait(wait); + cxstudyTask.setActions(actions); + cxstudyTask.setExecutor(executor); + + cxstudyTask.setPhone(config.getCxstudyConfig().getPhone()); + cxstudyTask.setPasswd(config.getCxstudyConfig().getPassword()); + cxstudyTask.setTargetClassName(config.getCxstudyConfig().getTarget()); + + cxstudyTask.setAiEnable(config.isAiEnable()); + return cxstudyTask; + } +} diff --git a/src/main/java/work/slhaf/task/cxstudy/CxstudyTask.java b/src/main/java/work/slhaf/task/cxstudy/CxstudyTask.java deleted file mode 100644 index b2a5e8a..0000000 --- a/src/main/java/work/slhaf/task/cxstudy/CxstudyTask.java +++ /dev/null @@ -1,334 +0,0 @@ -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 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 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 choiceElements = question.findElements(By.xpath(".//li[@role='radio']")); - SingleChoiceQuestion singleChoiceQuestion = new SingleChoiceQuestion(); - singleChoiceQuestion.setTitle(questionTitle); - List 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 choiceElements = question.findElements(By.xpath(".//li[@role='checkbox']")); - MultiChoiceQuestion multiChoiceQuestion = new MultiChoiceQuestion(); - multiChoiceQuestion.setTitle(questionTitle); - List 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 getChoices(List choiceElements) { - List 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 getTodoTasks() { - driver.switchTo().defaultContent(); - wait.until(ExpectedConditions.elementToBeClickable(By.xpath("//div[@class='posCatalog_select']"))); - List 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创建完毕!"); - } -} diff --git a/src/main/java/work/slhaf/task/cxstudy/pojo/answer/Answer.java b/src/main/java/work/slhaf/task/cxstudy/pojo/answer/Answer.java deleted file mode 100644 index 1f4d433..0000000 --- a/src/main/java/work/slhaf/task/cxstudy/pojo/answer/Answer.java +++ /dev/null @@ -1,4 +0,0 @@ -package work.slhaf.task.cxstudy.pojo.answer; - -public class Answer { -} diff --git a/src/main/java/work/slhaf/util/AiUtil.java b/src/main/java/work/slhaf/util/AiUtil.java index 8a54746..de8e2a0 100644 --- a/src/main/java/work/slhaf/util/AiUtil.java +++ b/src/main/java/work/slhaf/util/AiUtil.java @@ -1,14 +1,17 @@ package work.slhaf.util; +import lombok.extern.slf4j.Slf4j; 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 work.slhaf.config.AiConfig; +import work.slhaf.exception.AiRequestException; import java.util.ArrayList; import java.util.List; +@Slf4j public class AiUtil { private static ChatClient chatClient; private static final String PROMPT = """ @@ -65,16 +68,16 @@ public class AiUtil { } ``` - 注意:两种选择题的选项内容(不包含序号)可能存在乱码,当输入的Question JSON为选择题时,你需要同时根据choices和title中的内容来确定正确选项。 + 注意:两种选择题的选项内容(不包含序号)可能存在乱码,当输入的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(); + public static void load(AiConfig config) { + String apikey = config.getApikey(); + String model = config.getModel(); + String baseUrl = config.getBaseUrl(); chatClient = new ChatClient(baseUrl, apikey, model); } @@ -83,8 +86,13 @@ public class AiUtil { Message system = new Message(Constant.Character.SYSTEM,PROMPT); messages.add(system); messages.add(new Message(Constant.Character.USER,message)); - - ChatResponse chatResponse = chatClient.runChat(messages); + ChatResponse chatResponse; + try { + chatResponse = chatClient.runChat(messages); + }catch (Exception e) { + log.error(e.getMessage()); + throw new AiRequestException("ai答题运行出错,请检查配置或者关闭ai答题"); + } return chatResponse.getMessage(); } } diff --git a/src/main/java/work/slhaf/util/OcrUtil.java b/src/main/java/work/slhaf/util/OcrUtil.java index c8c09c1..d315f16 100644 --- a/src/main/java/work/slhaf/util/OcrUtil.java +++ b/src/main/java/work/slhaf/util/OcrUtil.java @@ -7,7 +7,8 @@ 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.config.OcrConfig; +import work.slhaf.exception.OcrRequestException; import work.slhaf.pojo.OCRDataInfo; import java.io.File; @@ -20,10 +21,10 @@ public class OcrUtil { private OcrUtil() {} - public static void load(Config basicConfig) { + public static void load(OcrConfig basicConfig) { //读取密钥 - String accessKeyId = basicConfig.getOcrConfig().getAccessKeyId(); - String accessKeySecret = basicConfig.getOcrConfig().getAccessKeySecret(); + String accessKeyId = basicConfig.getAccessKeyId(); + String accessKeySecret = basicConfig.getAccessKeySecret(); com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config() .setAccessKeyId(accessKeyId) .setAccessKeySecret(accessKeySecret); @@ -67,18 +68,10 @@ public class OcrUtil { 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"; - } + throw new OcrRequestException("OCR请求出错,检查配置或者关闭ai答题"); + } catch (Exception e) { + log.error(e.getMessage()); + throw new OcrRequestException("OCR请求出错,检查配置或者关闭ai答题"); } } }