Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
src/main/resources/application-dev.yml
src/main/resources/application-prod.yml
src/main/resources/super-star.yml
src/main/resources/bootstrap.yml
target/
src/main/resources/bootstrap.yml
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ SuperAutoStudy 是一个**开源、可自部署**的学习通任务自动化工
- 服务治理:Apache Dubbo
- 异步调度:RabbitMQ
- 缓存加速:Redis
- **灵活部署**:提供 Docker Compose 一键部署方案(支持 ARM/x86)
- **灵活部署**:支持本地或服务器部署;请按下方配置完成数据库和运行密钥设置

---

Expand All @@ -53,8 +53,15 @@ cd SuperAutoStudy

### 2. 部署方式

- **完整版(含中间件)**:参考 [部署文档](https://doc.xxtmooc.com/pages/793dcb/)
- **轻量版(仅刷课)**:使用 `lightweight` 分支或预编译 JAR(见 [Releases](https://github.com/DuanInnovator/SuperAutoStudy/releases))
- 创建 MySQL 数据库后导入 `sql/tihai.sql`。该文件会重建表,仅应在新数据库中执行。
- 复制并填写 `src/main/resources/application-dev.yml` 中的数据库配置。
- 设置 `SUPER_AUTO_CREDENTIAL_KEY`:值必须为 16、24 或 32 字节的随机字符串。它用于加密数据库中的账号密码和 Cookie,丢失后旧数据无法解密。
- 对外部署还必须设置 `SUPER_AUTO_API_TOKEN`,并在调用时传入 `Authorization: Bearer <token>` 或 `X-API-Token: <token>`;未配置令牌时仅允许本机回环地址访问。
- 执行 `mvn package`,再运行 `java -jar target/SuperAuto-1.0-SNAPSHOT.jar`。

首次部署请先调用 `POST /chaoxing/course` 验证账号并获取课程,再调用 `POST /chaoxing` 提交任务。可用 `GET /chaoxing/tasks?loginAccount=...` 查看任务,`DELETE /chaoxing/tasks/{taskId}` 暂停尚未执行的任务。

> 数据库从旧版本升级时,请先清理重复的“账号 + 课程”任务,再执行 `sql/migrations/V2__task_integrity.sql`。

> 💡 推荐首次使用者阅读 [快速上手指南](https://doc.xxtmooc.com/pages/793dcb/)。

Expand Down
34 changes: 10 additions & 24 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<version>1.18.36</version>
<scope>compile</scope>
</dependency>

Expand All @@ -63,12 +63,6 @@
<version>4.12.0</version> <!-- 最新稳定版 -->
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.4</version> <!-- 最新稳定版 -->
</dependency>

<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
Expand Down Expand Up @@ -226,13 +220,6 @@

</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>


<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
Expand Down Expand Up @@ -272,24 +259,23 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<version>3.11.0</version>
<configuration>
<release>8</release>
<compilerArgs>
<!-- 过期的方法的警告-->
<arg>-Xlint:deprecation</arg>
</compilerArgs>
<compilerArguments>
<!-- 是否输出所有的编译信息(包括类的加载等)-->
<!--<verbose />-->
<!-- 解决maven命令编译报错,因为rt.jar 和jce.jar在jre的lib下面,不在jdk的lib下面,
导致maven找不到(java7以后会出现这个问题),将这2个jar包拷贝到jdk的lib下面估计也好使-->
<bootclasspath>${java.home}\lib\rt.jar;${java.home}\lib\jce.jar</bootclasspath>
</compilerArguments>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>

</plugins>
</build>


</project>
</project>
5 changes: 5 additions & 0 deletions sql/migrations/V2__task_integrity.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Apply this migration to existing installations after resolving duplicate tasks.
ALTER TABLE th_wk_queue MODIFY COLUMN id varchar(36) NOT NULL COMMENT '任务id';
ALTER TABLE th_wk_queue ADD UNIQUE KEY uk_th_wk_queue_account_course (login_account, course_id);
ALTER TABLE th_wk_user ADD COLUMN id bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;
ALTER TABLE th_wk_user ADD UNIQUE KEY uk_th_wk_user_account (account);
5 changes: 3 additions & 2 deletions sql/th_wk_queue.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
DROP TABLE IF EXISTS `th_wk_queue`;
CREATE TABLE `th_wk_queue` (
`id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单id(注意:有可能在总订单表不存在这个id)',
`id` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务id',
`login_account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登陆账号',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`course_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '课程id',
Expand All @@ -17,7 +17,8 @@ CREATE TABLE `th_wk_queue` (
`retry_count` int(11) NULL DEFAULT 0 COMMENT '重试次数',
`creat_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`machine_num` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所处机器编号',
PRIMARY KEY (`id`) USING BTREE
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_th_wk_queue_account_course` (`login_account`, `course_id`)
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '网课任务队列表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
5 changes: 4 additions & 1 deletion sql/th_wk_user.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
DROP TABLE IF EXISTS `th_wk_user`;
CREATE TABLE `th_wk_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账号',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`school_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '学校名称',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
`fid` bigint(20) NULL DEFAULT NULL COMMENT '学校id',
`cookies` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '用户当前cookie'
`cookies` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '用户当前cookie',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_th_wk_user_account` (`account`)
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '网课-用户信息表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
10 changes: 7 additions & 3 deletions sql/tihai.sql
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ CREATE TABLE `th_wk_log` (
-- ----------------------------
DROP TABLE IF EXISTS `th_wk_queue`;
CREATE TABLE `th_wk_queue` (
`id` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '订单id(注意:有可能在总订单表不存在这个id)',
`id` varchar(36) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '任务id',
`login_account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登陆账号',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`course_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '课程id',
Expand All @@ -38,20 +38,24 @@ CREATE TABLE `th_wk_queue` (
`retry_count` int(11) NULL DEFAULT 0 COMMENT '重试次数',
`creat_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`machine_num` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所处机器编号',
PRIMARY KEY (`id`) USING BTREE
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_th_wk_queue_account_course` (`login_account`, `course_id`)
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '网课任务队列表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for th_wk_user
-- ----------------------------
DROP TABLE IF EXISTS `th_wk_user`;
CREATE TABLE `th_wk_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`account` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '账号',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
`school_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '学校名称',
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
`fid` bigint(20) NULL DEFAULT NULL COMMENT '学校id',
`cookies` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '用户当前cookie'
`cookies` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '用户当前cookie',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_th_wk_user_account` (`account`)
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '网课-用户信息表' ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
4 changes: 2 additions & 2 deletions src/main/java/com/tihai/config/FreeThreadPoolConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import com.tihai.factory.CustomThreadFactory;
import com.tihai.factory.PriorityRejectPolicy;
import com.tihai.properties.ThreadPoolProperties;
import com.tihai.queue.BoundedPriorityBlockingQueue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

Expand All @@ -30,7 +30,7 @@ public ThreadPoolExecutor customThreadPool(ThreadPoolProperties config) {

log.error("当前核心线程数为:{}", config.getCoreSize());
// 创建优先级阻塞队列
BlockingQueue<Runnable> queue = new PriorityBlockingQueue<>(config.getQueueCapacity());
BlockingQueue<Runnable> queue = new BoundedPriorityBlockingQueue<>(config.getQueueCapacity());

// 构建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/com/tihai/config/OperatorAuthFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.tihai.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

/**
* Protects the operational task endpoints. With no configured token the
* application is intentionally limited to loopback callers.
*/
@Component
public class OperatorAuthFilter extends OncePerRequestFilter {

private final String apiToken;

public OperatorAuthFilter(@Value("${security.api-token:}") String apiToken) {
this.apiToken = apiToken;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!request.getRequestURI().startsWith("/chaoxing")) {
filterChain.doFilter(request, response);
return;
}

if (isAuthorized(request)) {
filterChain.doFilter(request, response);
return;
}

response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":201,\"subCode\":401,\"msg\":\"未授权\"}");
}

private boolean isAuthorized(HttpServletRequest request) {
if (!StringUtils.hasText(apiToken)) {
String remoteAddress = request.getRemoteAddr();
return "127.0.0.1".equals(remoteAddress) || "::1".equals(remoteAddress)
|| "0:0:0:0:0:0:0:1".equals(remoteAddress);
}
String authorization = request.getHeader("Authorization");
String suppliedToken = authorization != null && authorization.startsWith("Bearer ")
? authorization.substring("Bearer ".length())
: request.getHeader("X-API-Token");
return suppliedToken != null && MessageDigest.isEqual(
apiToken.getBytes(StandardCharsets.UTF_8),
suppliedToken.getBytes(StandardCharsets.UTF_8));
}
}
29 changes: 16 additions & 13 deletions src/main/java/com/tihai/controller/SuperStarController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

import com.tihai.common.R;
import com.tihai.dubbo.dto.CourseSubmitTaskDTO;
import com.tihai.enums.BizCodeEnum;
import com.tihai.service.superstar.impl.SuperStarTaskServiceImpl;
import ma.glasnost.orika.MapperFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.List;

/**
* @Copyright : DuanInnovator
Expand All @@ -24,23 +23,14 @@ public class SuperStarController {
@Autowired
private SuperStarTaskServiceImpl chaoXingTaskService;

@Autowired
private MapperFacade mapperFacade;

/**
* 添加网课代刷任务
*
* @return
*/
@PostMapping("")
public R addChaxingTask(@RequestBody @Valid CourseSubmitTaskDTO courseSubmitTaskDTO) {

try {
chaoXingTaskService.addChaoXingTask(courseSubmitTaskDTO);
} catch (Exception e) {

return R.error(BizCodeEnum.TASK_ALREADY_EXIST.getCode(), BizCodeEnum.TASK_ALREADY_EXIST.getMsg());
}
public R addChaxingTask(@RequestBody @Valid CourseSubmitTaskDTO courseSubmitTaskDTO) throws Exception {
chaoXingTaskService.addChaoXingTask(courseSubmitTaskDTO);
return R.ok();
}

Expand All @@ -51,5 +41,18 @@ public R addChaxingTask(@RequestBody @Valid CourseSubmitTaskDTO courseSubmitTask
public void start(){
chaoXingTaskService.startChaoxingTask();
}

@GetMapping("/tasks")
public R listTasks(@RequestParam(required = false) String loginAccount) {
List<?> tasks = chaoXingTaskService.listTasks(loginAccount);
return R.ok().put(tasks);
}

@DeleteMapping("/tasks/{taskId}")
public R pauseTask(@PathVariable String taskId) {
return chaoXingTaskService.pauseTask(taskId)
? R.ok()
: R.error(404, "任务不存在或已完成");
}
}

12 changes: 8 additions & 4 deletions src/main/java/com/tihai/exception/GlobalException.java
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,14 @@ public R handleBindException(BindException ex) {

}

@ExceptionHandler(BusinessException.class)
public R handleBusinessException(BusinessException ex) {
return R.error(ex.getCode(), ex.getMsg(), ex.getData());
}

@ExceptionHandler(Exception.class) //捕获所有异常
@ExceptionHandler(Exception.class)
public R ex(Exception ex) {
ex.printStackTrace();
return R.error(404, BizCodeEnum.SYSTEM_ERROR.getMsg(), null);
log.error("未处理的请求异常", ex);
return R.error(BizCodeEnum.SYSTEM_ERROR.getCode(), BizCodeEnum.SYSTEM_ERROR.getMsg(), null);
}
}
}
21 changes: 5 additions & 16 deletions src/main/java/com/tihai/factory/PriorityRejectPolicy.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
package com.tihai.factory;

import com.tihai.queue.PriorityTaskWrapper;

import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor; // 关键修正:使用JDK标准线程池类
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;

/**
* 自定义拒绝策略(优先级降级重试)
* Rejects explicitly so the task lifecycle can return the task to PENDING.
*/
public class PriorityRejectPolicy implements RejectedExecutionHandler {

@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (executor.isShutdown()) {
return;
}

if (r instanceof PriorityTaskWrapper) {
PriorityTaskWrapper task = (PriorityTaskWrapper) r;
if(task.getPriority()>=1){
task.decreasePriority(1); // 优先级降级
}
executor.execute(task); // 重新入队
}
throw new RejectedExecutionException("任务队列已满或线程池已关闭");
}
}
}
Loading