diff --git a/application-tenant/tenant-admin/pom.xml b/application-tenant/tenant-admin/pom.xml
index e33206c..eb54156 100644
--- a/application-tenant/tenant-admin/pom.xml
+++ b/application-tenant/tenant-admin/pom.xml
@@ -149,6 +149,11 @@
apelet
+ common-msg-notice
+ 1.0.0
+
+
+ apelet
common-association
1.0.0
diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java
index 3a0d02d..00ac785 100644
--- a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java
+++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/TenantAdminApplication.java
@@ -15,9 +15,10 @@ import org.springframework.scheduling.annotation.EnableScheduling;
* @date 2023-08-04
*/
@ComponentScan("apelet")
+@EnableDiscoveryClient
@EnableScheduling
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
-@MapperScan("apelet.common.**.dao")
+@MapperScan({"apelet.common.**.dao", "apelet.msgnotice.mapper"})
public class TenantAdminApplication {
public static void main(String[] args) {
diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/config/FilterConfig.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/config/FilterConfig.java
index 43dd5ba..f5a18c3 100644
--- a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/config/FilterConfig.java
+++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/config/FilterConfig.java
@@ -42,6 +42,7 @@ public class FilterConfig {
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addExposedHeader(applicationConfig.getRefreshedTokenHeaderKey());
corsConfiguration.setAllowCredentials(true);
+ corsConfiguration.setMaxAge(3600L);
configSource.registerCorsConfiguration("/**", corsConfiguration);
}
return new CorsFilter(configSource);
diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/config/MultiDataSourceConfig.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/config/MultiDataSourceConfig.java
index 2a39305..957958b 100644
--- a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/config/MultiDataSourceConfig.java
+++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/config/MultiDataSourceConfig.java
@@ -21,8 +21,7 @@ import java.util.Map;
* @date 2023-08-04
*/
@Configuration
-@EnableTransactionManagement
-@MapperScan(value = {"apelet.tenantadmin.upms.dao","apelet.tenantadmin.tenant.dao", "apelet.common.*.dao"})
+@MapperScan(value = {"apelet.tenantadmin.upms.dao","apelet.tenantadmin.tenant.dao", "apelet.common.*.dao", "apelet.association.dao"})
public class MultiDataSourceConfig extends BaseMultiDataSourceConfig {
@Bean(initMethod = "init", destroyMethod = "close")
diff --git a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/LoginController.java b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/LoginController.java
index e106625..9516e0e 100644
--- a/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/LoginController.java
+++ b/application-tenant/tenant-admin/src/main/java/apelet/tenantadmin/upms/controller/LoginController.java
@@ -1080,6 +1080,7 @@ public class LoginController {
}
List menuList = allMenuList.stream()
.filter(m -> m.getMenuType() <= SysMenuType.TYPE_MENU).collect(Collectors.toList());
+ jsonData.put("user", user);
jsonData.put("menuList", menuList);
jsonData.put("permCodeList", permCodeList);
Set permSet = null;
diff --git a/application-tenant/tenant-admin/src/main/resources/application-config.yml b/application-tenant/tenant-admin/src/main/resources/application-config.yml
index 23b47e3..f753b89 100644
--- a/application-tenant/tenant-admin/src/main/resources/application-config.yml
+++ b/application-tenant/tenant-admin/src/main/resources/application-config.yml
@@ -86,13 +86,13 @@ knife4j:
aliyun:
oss:
- enabled: false
+ enabled: true
expireSeconds: 1000
- # 下面几项均需在申请阿里云OSS后,根据自己的实际情况进行配置。
- endpoint:
- accessKey:
- secretKey:
- bucketName:
+ endpoint: https://oss-cn-beijing.aliyuncs.com
+ accessKey: LTAI5t5fEgzRRtdmBvczVS6r
+ secretKey: uESFiyyi6EwCkM5w4a4pbx6TmVhqgx
+ bucketName: yy-test12
+
minio:
enabled: false
@@ -145,7 +145,7 @@ common-redis:
# master-slave:
# 每个节点逗号分隔,第一个为主节点,其余为从节点。同时每个节点前必须以redis://开头。
# address: redis://localhost:6379,redis://localhost:6378,...
- address: redis://localhost:6379
+ address: redis://192.168.100.25:6379
# 链接超时,单位毫秒。
timeout: 6000
# 单位毫秒。分布式锁的超时检测时长。
@@ -153,7 +153,7 @@ common-redis:
lockWatchdogTimeout: 60000
# redis 密码,空可以不填。
password:
- database: 0
+ database: 1
pool:
# 连接池数量。
poolSize: 20
diff --git a/application-tenant/tenant-admin/src/main/resources/bootstrap.yml b/application-tenant/tenant-admin/src/main/resources/bootstrap.yml
index 7b52d96..b74406d 100644
--- a/application-tenant/tenant-admin/src/main/resources/bootstrap.yml
+++ b/application-tenant/tenant-admin/src/main/resources/bootstrap.yml
@@ -2,7 +2,7 @@ spring:
application:
name: tenant-admin
profiles:
- active: @profile.name@
+ active: dev
main:
allow-bean-definition-overriding: true
allow-circular-references: true
diff --git a/application-tenant/tenant-admin/src/main/resources/tenant-admin-dev.yml b/application-tenant/tenant-admin/src/main/resources/tenant-admin-dev.yml
index 304caf6..d622b1e 100644
--- a/application-tenant/tenant-admin/src/main/resources/tenant-admin-dev.yml
+++ b/application-tenant/tenant-admin/src/main/resources/tenant-admin-dev.yml
@@ -5,7 +5,7 @@ logging:
config: classpath:logback-spring.xml
server:
- port: 8077
+ port: 8099
application:
# Jwt令牌加密的签名值。该值的长度要超过10个字符(过短会报错)。
@@ -133,7 +133,7 @@ sequence:
# 是否使用基于美团Leaf的分布式Id生成器。
advanceIdGenerator: true
# 多个zk服务之间逗号分隔。
- zkAddress: localhost:2181
+ zkAddress: 192.168.100.25:2181
# 与本机的ip一起构成zk中标识不同服务实例的key值。
idPort: 18083
# zk中生成WorkNode的路径。不同的业务可以使用不同的路径,以免冲突。
@@ -146,7 +146,7 @@ common-log:
kafkaTopic: SysOperationLog-TenantAdmin
mybatis-plus:
- mapper-locations: classpath:apelet/tenantadmin/tenant/dao/mapper/*Mapper.xml,apelet/tenantadmin/upms/dao/mapper/*Mapper.xml,apelet/common/report/dao/mapper/*Mapper.xml,apelet/common/datasync/dao/mapper/*Mapper.xml,apelet/common/log/dao/mapper/*Mapper.xml,apelet/common/flow/dao/mapper/*Mapper.xml,apelet/common/online/dao/mapper/*Mapper.xml,apelet/common/generator/dao/mapper/*Mapper.xml,apelet/common/botp/dao/mapper/*Mapper.xml,apelet/tenantadmin/app/dao/mapper/*Mapper.xml,apelet/common/mobile/dao/mapper/*Mapper.xml,apelet/common/eas/dao/mapper/*Mapper.xml
+ mapper-locations: classpath:apelet/tenantadmin/tenant/dao/mapper/*Mapper.xml,apelet/common/demo/report/dao/mapper/*Mapper.xml,apelet/tenantadmin/upms/dao/mapper/*Mapper.xml,apelet/common/report/dao/mapper/*Mapper.xml,apelet/common/datasync/dao/mapper/*Mapper.xml,apelet/common/log/dao/mapper/*Mapper.xml,apelet/common/flow/dao/mapper/*Mapper.xml,apelet/common/online/dao/mapper/*Mapper.xml,apelet/common/generator/dao/mapper/*Mapper.xml,apelet/common/botp/dao/mapper/*Mapper.xml,apelet/tenantadmin/app/dao/mapper/*Mapper.xml,apelet/common/mobile/dao/mapper/*Mapper.xml,apelet/common/eas/dao/mapper/*Mapper.xml,classpath:mapper/*Mapper.xml
type-aliases-package: apelet.tenantadmin.*.model,apelet.common.report.model,apelet.common.datasync.model,apelet.common.log.model,apelet.common.flow.model,apelet.common.online.model,apelet.common.mobile.model,apelet.common.eas.model
global-config:
db-config:
@@ -203,17 +203,17 @@ spring:
druid:
# 租户管理数据源。
tenant-admin:
- url: jdbc:mysql://frp-use.com:51850/apelet_xhgl?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
+ url: jdbc:mysql://192.168.100.35:3306/apelet_xhgl?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: X7&9p8L2@6z4K7!8
# 租户业务平台的通用业务数据,如字典、在线表单、流程、报表的定义等数据。
tenant-common:
- url: jdbc:mysql://frp-use.com:51850/apelet_xhgl?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
+ url: jdbc:mysql://192.168.100.35:3306/apelet_xhgl?characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
username: root
password: X7&9p8L2@6z4K7!8
# 默认生成的操作日志数据源配置。
operation-log:
- url: jdbc:mysql://frp-use.com:51850/apelet_xhgl?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
+ url: jdbc:mysql://192.168.100.35:3306/apelet_xhgl?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root
password: X7&9p8L2@6z4K7!8
driverClassName: com.mysql.cj.jdbc.Driver
diff --git a/common/common-association/pom.xml b/common/common-association/pom.xml
index 2226fb4..8740d9b 100644
--- a/common/common-association/pom.xml
+++ b/common/common-association/pom.xml
@@ -23,6 +23,7 @@
javacv-platform
1.5.13
+
apelet
common-msg-notice
@@ -35,6 +36,11 @@
apelet
+ common-aliyun-oss
+ 1.0.0
+
+
+ apelet
common-generator
1.0.0
@@ -63,6 +69,8 @@
javase
3.5.1
+
+
diff --git a/common/common-association/src/main/java/apelet/association/controller/AssociationActivitiesController.java b/common/common-association/src/main/java/apelet/association/controller/AssociationActivitiesController.java
index 4f635ad..91060c2 100644
--- a/common/common-association/src/main/java/apelet/association/controller/AssociationActivitiesController.java
+++ b/common/common-association/src/main/java/apelet/association/controller/AssociationActivitiesController.java
@@ -14,7 +14,9 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
-import java.util.*;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
@RestController
diff --git a/common/common-association/src/main/java/apelet/association/controller/CourseController.java b/common/common-association/src/main/java/apelet/association/controller/CourseController.java
new file mode 100644
index 0000000..61eb0b9
--- /dev/null
+++ b/common/common-association/src/main/java/apelet/association/controller/CourseController.java
@@ -0,0 +1,171 @@
+package apelet.association.controller;
+
+import apelet.association.model.CourseCourseware;
+import apelet.association.model.CourseVideo;
+import apelet.association.service.CourseCoursewareService;
+import apelet.association.service.CourseService;
+import apelet.association.service.CourseVideoService;
+import apelet.common.aliyun.oss.config.AliyunOssProperties;
+import apelet.common.aliyun.oss.wrapper.AliyunOssTemplate;
+
+import apelet.common.core.exception.MyRuntimeException;
+import apelet.common.core.object.*;
+import apelet.common.generator.utils.OrmGenDataSourceUtil;
+import apelet.common.orm.impl.Filter;
+import apelet.common.orm.impl.FilterItem;
+import apelet.common.orm.impl.Selector;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.IdUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClient;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.GetObjectRequest;
+import com.aliyun.oss.model.OSSObject;
+import com.aliyun.oss.model.ObjectMetadata;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.PostConstruct;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+
+
+/**
+ * 课程管理
+ */
+@RestController
+@RequestMapping("/tenantadmin/Course")
+public class CourseController {
+
+
+ @Autowired
+ private CourseCoursewareService courseCoursewareService;
+ @Autowired
+ private CourseVideoService courseVideoService;
+ @Autowired
+ private AliyunOssTemplate aliyunOssTemplate;
+ @Autowired
+ private OSS ossClient;
+
+
+ private static final String VIDEO_MIME = "video/mp4";
+
+ /**
+ * 视频断点续播 —— 根据学习进度自动从对应字节偏移量开始流式输出
+ */
+ @GetMapping("/queryVideoProgress")
+ public void queryVideoProgress(
+ @RequestParam("videoId") Long videoId,
+ @RequestParam("userId") Long userId,
+ HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+
+ CourseVideo courseVideo = courseVideoService.getById(videoId);
+ if (courseVideo == null || courseVideo.getVideoUrl() == null) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND, "视频不存在");
+ return;
+ }
+
+ String videoUrl = courseVideo.getVideoUrl();
+ JSONArray jsonArray = JSON.parseArray(videoUrl);
+ JSONObject urlObject = jsonArray.getJSONObject(0);
+ String filePath = urlObject.getString("uploadPath") + "/" + urlObject.getString("filename");
+ AliyunOssProperties properties = aliyunOssTemplate.getProperties();
+ String bucketName = properties.getBucketName();
+ ObjectMetadata meta = ossClient.getObjectMetadata(bucketName, filePath);
+ long totalSize = meta.getContentLength();
+ long start = courseVideoService.getByteOffsetByTime(videoId, userId);
+ long end = totalSize - 1;
+ String rangeHeader = request.getHeader(HttpHeaders.RANGE);
+ if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
+ String rangeVal = rangeHeader.substring(6);
+ String[] arr = rangeVal.split("-");
+ if (!CharSequenceUtil.isBlank(arr[0])) {
+ start = Long.parseLong(arr[0]);
+ }
+ if (arr.length > 1 && !CharSequenceUtil.isBlank(arr[1])) {
+ end = Long.parseLong(arr[1]);
+ }
+ }
+ start = Math.max(start, 0);
+ end = Math.min(end, totalSize - 1);
+ if (start >= totalSize) {
+ response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes */" + totalSize);
+ response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
+ return;
+ }
+ long sliceLength = end - start + 1;
+ response.setContentType(VIDEO_MIME);
+ response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
+ response.setHeader(HttpHeaders.CONTENT_RANGE,
+ String.format("bytes %d-%d/%d", start, end, totalSize));
+ response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(sliceLength));
+ response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
+ GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, filePath);
+ getObjectRequest.setRange(start, end);
+ try (OSSObject ossObject = ossClient.getObject(getObjectRequest);
+ InputStream is = ossObject.getObjectContent()) {
+ byte[] buffer = new byte[8192];
+ int read;
+ while ((read = is.read(buffer)) != -1) {
+ response.getOutputStream().write(buffer, 0, read);
+ }
+ response.getOutputStream().flush();
+ }
+ }
+
+
+ /**
+ * 保存课程视频学习记录
+ *
+ * @return
+ */
+ @PostMapping("/videoLearning")
+ public ResponseResult> videoLearning(@RequestBody CourseVideo courseVideo) {
+ courseVideoService.saveVideoLearning(courseVideo);
+ return ResponseResult.success();
+ }
+
+
+ /**
+ * 查询课件学习进度
+ *
+ * @param coursewareId
+ * @param userId
+ * @return
+ */
+ @GetMapping("/queryCoursewareProgress")
+ public ResponseResult> queryCoursewareProgress(
+ @RequestParam("coursewareId") Long coursewareId, @RequestParam("userId") Long userId) {
+ Object result = courseCoursewareService.getCourseLearningProgress(coursewareId, userId);
+ return ResponseResult.success(result);
+ }
+
+
+ /**
+ * 课程课件学习记录
+ *
+ * @return
+ */
+ @PostMapping("/coursewareLearning")
+ public ResponseResult> coursewareLearning(@RequestBody CourseCourseware courseCourseware) {
+ courseCoursewareService.saveCoursewareLearning(courseCourseware);
+ return ResponseResult.success();
+ }
+
+
+}
diff --git a/common/common-association/src/main/java/apelet/association/controller/DoPracticeProblemsController.java b/common/common-association/src/main/java/apelet/association/controller/DoPracticeProblemsController.java
new file mode 100644
index 0000000..4b73ddd
--- /dev/null
+++ b/common/common-association/src/main/java/apelet/association/controller/DoPracticeProblemsController.java
@@ -0,0 +1,183 @@
+package apelet.association.controller;
+
+import apelet.association.dto.UserExerciseDto;
+import apelet.association.model.*;
+import apelet.association.service.*;
+import apelet.common.core.exception.MyRuntimeException;
+import apelet.common.core.object.*;
+import apelet.common.generator.utils.OrmGenDataSourceUtil;
+import apelet.common.orm.impl.Filter;
+import apelet.common.orm.impl.FilterItem;
+import apelet.common.orm.impl.Selector;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.IdUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.PostConstruct;
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/tenantadmin/questionBank")
+public class DoPracticeProblemsController {
+
+ @Autowired
+ private OrmGenDataSourceUtil ormGenDataSourceUtil;
+
+ @Autowired
+ private ExamService examService;
+
+ @Autowired
+ private QuestionBankService questionBankService;
+ @Autowired
+ private QuestionBankEntryService questionBankEntryService;
+
+ @Autowired
+ private ExamQuestionsService examQuestionsService;
+ @Autowired
+ private ExamQuestionsEntryService examQuestionsEntryService;
+
+ @Autowired
+ private RedissonClient redissonClient;
+
+ @PostConstruct
+ public void checkRedisConfig() throws IOException {
+ System.out.println("Redisson config: {}" + redissonClient.getConfig().toYAML());
+ System.out.println("Redisson config: {}" + redissonClient.getConfig().toYAML());
+ System.out.println("Redisson config: {}" + redissonClient.getConfig().toYAML());
+ }
+
+ /**
+ * 刷题
+ *
+ * @param id 题库id
+ * @return
+ */
+ @GetMapping("/doPracticeProblems")
+ public ResponseResult> doPracticeProblems(@RequestParam(name = "id") String id) {
+ Map result = new HashMap<>();
+ ObjectValue questionBank = ormGenDataSourceUtil.queryOne("question_bank", id);
+ ObjectCollection collection = questionBank.getObjectCollection("question_bank_entry");
+ if (collection == null || collection.isEmpty()) {
+ throw new MyRuntimeException("题库下没有试题, 请先添加试题!!!");
+ }
+ result.put("questionBankId", id);
+ result.put("questionBankName", questionBank.getString("name"));
+ List list = new ArrayList<>();
+
+ //保存用户刷题记录
+ long batchNumber = IdUtil.getSnowflakeNextId();
+ result.put("batchNumber", batchNumber);
+
+ collection.forEach(f -> {
+ ObjectValue examQuestions = f.getObjectValue("exam_questions_id");
+ list.add(examQuestions.getString("id"));
+
+ ObjectValue userExercise = new ObjectValue("user_exercise");
+ ObjectValue xySysUser = new ObjectValue("xy_sys_user");
+ xySysUser.put("id", TokenData.takeFromRequest().getUserId());
+ userExercise.put("question_bank_id", questionBank);
+ userExercise.put("exam_questions_id", examQuestions);
+ userExercise.put("membership_apply_id", xySysUser);
+ userExercise.put("batch", batchNumber);
+ userExercise.put("is_correct", 0);
+ userExercise.put("create_time", DateUtil.now());
+ try {
+ ormGenDataSourceUtil.addNew(userExercise.getTableName(), userExercise);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ List