Merge pull request #6 from PlayEdu/dev

Dev
This commit is contained in:
Teng 2023-05-31 10:32:22 +08:00 committed by GitHub
commit 19235003d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 252 additions and 227 deletions

View File

@ -1,4 +1,4 @@
FROM openjdk:17 as builder
FROM eclipse-temurin:17 as builder
WORKDIR /app
@ -6,7 +6,7 @@ COPY . /app
RUN /app/docker-build.sh
FROM openjdk:17
FROM eclipse-temurin:17
WORKDIR /app

12
pom.xml
View File

@ -75,13 +75,6 @@
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--图形验证码依赖 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@ -139,6 +132,11 @@
<artifactId>hutool-core</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
<version>5.8.16</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>

View File

@ -154,6 +154,15 @@ public class AppConfigCheck implements ApplicationRunner {
setKeyValue("1");
}
},
new AppConfig() {
{
setName("禁止拖拽播放");
setSort(60);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_SWITCH);
setKeyName("player.disabled_drag");
setKeyValue("0");
}
},
});
put(
"学员配置",
@ -168,6 +177,55 @@ public class AppConfigCheck implements ApplicationRunner {
}
},
});
put(
"MinIO",
new AppConfig[] {
new AppConfig() {
{
setName("AccessKey");
setSort(10);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(CConfig.MINIO_ACCESS_KEY);
setKeyValue("");
}
},
new AppConfig() {
{
setName("SecretKey");
setSort(20);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(CConfig.MINIO_SECRET_KEY);
setKeyValue("");
}
},
new AppConfig() {
{
setName("Bucket");
setSort(30);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(CConfig.MINIO_BUCKET);
setKeyValue("");
}
},
new AppConfig() {
{
setName("Endpoint");
setSort(40);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(CConfig.MINIO_ENDPOINT);
setKeyValue("");
}
},
new AppConfig() {
{
setName("Domain");
setSort(50);
setFieldType(BackendConstant.APP_CONFIG_FIELD_TYPE_TEXT);
setKeyName(CConfig.MINIO_DOMAIN);
setKeyValue("");
}
},
});
}
};

View File

@ -1,58 +0,0 @@
/*
* Copyright 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.config;
import static com.google.code.kaptcha.Constants.*;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
@Configuration
public class KaptchaConfig {
@Bean(name = "captchaProducer")
public DefaultKaptcha getKaptchaBean() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// 是否边框
properties.setProperty(KAPTCHA_BORDER, "no");
// 字符颜色
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "red");
// 干扰线颜色
properties.setProperty(KAPTCHA_NOISE_COLOR, "red");
// 字符间距
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "5");
// 图片宽度
properties.setProperty(KAPTCHA_IMAGE_WIDTH, "120");
// 图片高度
properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "50");
// 字符大小
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "40");
// 字符长度
properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
// 字体样式
properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier");
defaultKaptcha.setConfig(new Config(properties));
return defaultKaptcha;
}
}

View File

@ -1,70 +0,0 @@
/*
* Copyright 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.config;
import io.minio.MinioAsyncClient;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import xyz.playedu.api.vendor.PlayEduMinioClient;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/2/28 16:38
*/
@Data
@Configuration
public class MinioConfig {
@Value("${minio.domain}")
private String domain;
@Value("${minio.bucket}")
private String bucket;
@Value("${minio.access-key}")
private String accessKey;
@Value("${minio.secret-key}")
private String secretKey;
@Value("${minio.end-point}")
private String endPoint;
@Bean
public MinioClient getMinioClient() {
return MinioClient.builder()
.endpoint(this.endPoint)
.credentials(this.accessKey, this.secretKey)
.build();
}
@Bean
public PlayEduMinioClient getPlayEduMinioClient() {
MinioAsyncClient client =
PlayEduMinioClient.builder()
.endpoint(this.endPoint)
.credentials(this.accessKey, this.secretKey)
.build();
return new PlayEduMinioClient(client);
}
}

View File

@ -29,4 +29,10 @@ public class CConfig {
public static final String SYSTEM_H5_URL = "system.h5_url";
public static final String MEMBER_DEFAULT_AVATAR = "member.default_avatar";
public static final String MINIO_ACCESS_KEY = "minio.access_key";
public static final String MINIO_SECRET_KEY = "minio.secret_key";
public static final String MINIO_BUCKET = "minio.bucket";
public static final String MINIO_ENDPOINT = "minio.endpoint";
public static final String MINIO_DOMAIN = "minio.domain";
}

View File

@ -37,11 +37,11 @@ import java.util.List;
@Slf4j
public class ExceptionController {
// @ExceptionHandler(Exception.class)
// public JsonResponse exceptionHandler(Exception e) {
// log.error(e.getMessage());
// return JsonResponse.error("系统错误", 500);
// }
@ExceptionHandler(Exception.class)
public JsonResponse exceptionHandler(Exception e) {
log.error(e.getMessage());
return JsonResponse.error("系统错误", 500);
}
@ExceptionHandler(ServiceException.class)
public JsonResponse serviceExceptionHandler(ServiceException e) {

View File

@ -36,11 +36,6 @@ import xyz.playedu.api.util.HelperUtil;
import java.util.HashMap;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/2/28 16:26
*/
@RestController
@Slf4j
@RequestMapping("/backend/v1/upload")

View File

@ -61,6 +61,7 @@ public class SystemController {
data.put("player-bullet-secret-text", configs.get("player.bullet_secret_text"));
data.put("player-bullet-secret-color", configs.get("player.bullet_secret_color"));
data.put("player-bullet-secret-opacity", configs.get("player.bullet_secret_opacity"));
data.put("player-disabled-drag", configs.get("player.disabled_drag"));
return JsonResponse.data(data);
}

View File

@ -22,7 +22,7 @@ import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import xyz.playedu.api.event.UserDestroyEvent;
import xyz.playedu.api.service.UserService;
import xyz.playedu.api.service.*;
/**
* @Author 杭州白书科技有限公司
@ -35,8 +35,23 @@ public class UserDestroyListener {
@Autowired private UserService userService;
@Autowired private UserCourseHourRecordService userCourseHourRecordService;
@Autowired private UserCourseRecordService userCourseRecordService;
@Autowired private UserLearnDurationRecordService userLearnDurationRecordService;
@Autowired private UserLearnDurationStatsService userLearnDurationStatsService;
@Autowired private UserLoginRecordService userLoginRecordService;
@EventListener
public void updateLoginInfo(UserDestroyEvent event) {
public void remoteRelation(UserDestroyEvent event) {
userService.removeRelateDepartmentsByUserId(event.getUserId());
userCourseHourRecordService.remove(event.getUserId());
userCourseRecordService.destroy(event.getUserId());
userLearnDurationRecordService.remove(event.getUserId());
userLearnDurationStatsService.remove(event.getUserId());
userLoginRecordService.remove(event.getUserId());
}
}

View File

@ -18,16 +18,12 @@ package xyz.playedu.api.service;
import com.baomidou.mybatisplus.extension.service.IService;
import xyz.playedu.api.domain.AppConfig;
import xyz.playedu.api.types.config.MinioConfig;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author tengteng
* @description 针对表app_config的数据库操作Service
* @createDate 2023-03-09 11:13:33
*/
public interface AppConfigService extends IService<AppConfig> {
Map<String, Long> allKeys();
@ -37,4 +33,6 @@ public interface AppConfigService extends IService<AppConfig> {
void saveFromMap(HashMap<String, String> data);
Map<String, String> keyValues();
MinioConfig getMinioConfig();
}

View File

@ -51,6 +51,8 @@ public interface UserCourseHourRecordService extends IService<UserCourseHourReco
void remove(Integer userId, Integer courseId);
void remove(Integer userId);
void remove(Integer userId, Integer courseId, Integer hourId);
List<UserCourseHourRecordCourseCountMapper> getUserCourseHourCount(

View File

@ -45,6 +45,8 @@ public interface UserCourseRecordService extends IService<UserCourseRecord> {
void destroy(Integer userId, Integer courseId);
void destroy(Integer userId);
void removeByCourseId(Integer courseId);
List<UserCourseRecord> chunks(List<Integer> ids, List<String> fields);

View File

@ -26,4 +26,6 @@ import xyz.playedu.api.domain.UserLearnDurationRecord;
*/
public interface UserLearnDurationRecordService extends IService<UserLearnDurationRecord> {
void store(Integer userId, Integer courseId, Integer hourId, Long startTime, Long endTime);
void remove(Integer userId);
}

View File

@ -40,4 +40,6 @@ public interface UserLearnDurationStatsService extends IService<UserLearnDuratio
Long userDuration(Integer userId);
List<UserLearnDurationStats> dateBetween(Integer userId, String startAt, String endAt);
void remove(Integer userId);
}

View File

@ -35,7 +35,7 @@ public interface UserLoginRecordService extends IService<UserLoginRecord> {
String browserVersion,
String os);
void saveIpArea(Integer id, String area);
void logout(Integer userid, String jti);
void remove(Integer userId);
}

View File

@ -19,9 +19,11 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import xyz.playedu.api.constant.CConfig;
import xyz.playedu.api.domain.AppConfig;
import xyz.playedu.api.mapper.AppConfigMapper;
import xyz.playedu.api.service.AppConfigService;
import xyz.playedu.api.types.config.MinioConfig;
import java.util.ArrayList;
import java.util.HashMap;
@ -89,4 +91,16 @@ public class AppConfigServiceImpl extends ServiceImpl<AppConfigMapper, AppConfig
return list(query().getWrapper().eq("is_hidden", 0)).stream()
.collect(Collectors.toMap(AppConfig::getKeyName, AppConfig::getKeyValue));
}
@Override
public MinioConfig getMinioConfig() {
MinioConfig minioConfig = new MinioConfig();
Map<String, String> config = keyValues();
minioConfig.setAccessKey(config.get(CConfig.MINIO_ACCESS_KEY));
minioConfig.setSecretKey(config.get(CConfig.MINIO_SECRET_KEY));
minioConfig.setBucket(config.get(CConfig.MINIO_BUCKET));
minioConfig.setEndpoint(config.get(CConfig.MINIO_ENDPOINT));
minioConfig.setDomain(config.get(CConfig.MINIO_DOMAIN));
return minioConfig;
}
}

View File

@ -15,27 +15,19 @@
*/
package xyz.playedu.api.service.impl;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import jakarta.annotation.Resource;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.FastByteArrayOutputStream;
import xyz.playedu.api.service.ImageCaptchaService;
import xyz.playedu.api.types.ImageCaptchaResult;
import xyz.playedu.api.util.Base64Util;
import xyz.playedu.api.util.HelperUtil;
import xyz.playedu.api.util.RedisUtil;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
@Slf4j
@Service
public class ImageCaptchaServiceImpl implements ImageCaptchaService {
@ -46,29 +38,21 @@ public class ImageCaptchaServiceImpl implements ImageCaptchaService {
@Value("${playedu.captcha.expire}")
private Long ConfigExpire;
@Resource private DefaultKaptcha defaultKaptcha;
@Override
public ImageCaptchaResult generate() throws IOException {
public ImageCaptchaResult generate() {
ImageCaptchaResult imageCaptcha = new ImageCaptchaResult();
BufferedImage image;
// 生成验证码
LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(240, 100, 4, 1);
// 图形验证码的key[api是无状态的需要key来锁定验证码的值]
String randomKey = HelperUtil.randomString(16);
imageCaptcha.setKey(randomKey);
// 生成验证码
imageCaptcha.setCode(defaultKaptcha.createText());
image = defaultKaptcha.createImage(imageCaptcha.getCode());
imageCaptcha.setCode(lineCaptcha.getCode());
imageCaptcha.setImage("data:image/png;base64," + lineCaptcha.getImageBase64());
// 写入到redis中
RedisUtil.set(getCacheKey(randomKey), imageCaptcha.getCode(), ConfigExpire);
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
ImageIO.write(image, "png", os);
String base64 = "data:image/png;base64," + Base64Util.encode(os.toByteArray());
imageCaptcha.setImage(base64);
RedisUtil.set(getCacheKey(imageCaptcha.getKey()), imageCaptcha.getCode(), ConfigExpire);
return imageCaptcha;
}

View File

@ -15,10 +15,7 @@
*/
package xyz.playedu.api.service.impl;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.*;
import io.minio.http.Method;
import lombok.SneakyThrows;
@ -27,8 +24,10 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import xyz.playedu.api.config.MinioConfig;
import xyz.playedu.api.exception.ServiceException;
import xyz.playedu.api.service.AppConfigService;
import xyz.playedu.api.service.MinioService;
import xyz.playedu.api.types.config.MinioConfig;
import xyz.playedu.api.vendor.PlayEduMinioClient;
import java.io.ByteArrayInputStream;
@ -36,70 +35,111 @@ import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/**
* @Author 杭州白书科技有限公司
*
* @create 2023/3/7 13:29
*/
@Service
public class MinioServiceImpl implements MinioService {
@Autowired private MinioConfig c;
private MinioConfig minioConfig = null;
@Autowired private MinioClient client;
@Autowired private AppConfigService appConfigService;
@Autowired private PlayEduMinioClient playEduMinioClient;
@SneakyThrows
private MinioConfig getMinioConfig() {
if (minioConfig == null) {
minioConfig = appConfigService.getMinioConfig();
if (minioConfig.getAccessKey().isBlank()
|| minioConfig.getSecretKey().isBlank()
|| minioConfig.getBucket().isBlank()
|| minioConfig.getDomain().isBlank()
|| minioConfig.getEndpoint().isBlank()) {
throw new ServiceException("MinIO服务未配置");
}
}
return minioConfig;
}
private String bucket() {
return getMinioConfig().getBucket();
}
public MinioClient getMinioClient() {
MinioConfig c = getMinioConfig();
return MinioClient.builder()
.endpoint(c.getEndpoint())
.credentials(c.getAccessKey(), c.getSecretKey())
.build();
}
public PlayEduMinioClient getPlayEduMinioClient() {
MinioConfig c = getMinioConfig();
MinioAsyncClient client =
PlayEduMinioClient.builder()
.endpoint(c.getEndpoint())
.credentials(c.getAccessKey(), c.getSecretKey())
.build();
return new PlayEduMinioClient(client);
}
@Override
public String url(String path) {
return c.getDomain() + c.getBucket() + "/" + path;
MinioConfig c = getMinioConfig();
return c.getDomain()
+ (c.getDomain().endsWith("/") ? "" : "/")
+ c.getBucket()
+ "/"
+ path;
}
@Override
@SneakyThrows
public String saveFile(MultipartFile file, String savePath, String contentType) {
PutObjectArgs objectArgs =
PutObjectArgs.builder().bucket(c.getBucket()).object(savePath).stream(
PutObjectArgs.builder().bucket(bucket()).object(savePath).stream(
file.getInputStream(), file.getSize(), -1)
.contentType(contentType)
.build();
client.putObject(objectArgs);
getMinioClient().putObject(objectArgs);
return url(savePath);
}
@Override
public String uploadId(String path) {
return playEduMinioClient.uploadId(c.getBucket(), path);
return getPlayEduMinioClient().uploadId(bucket(), path);
}
@Override
@SneakyThrows
public String chunkPreSignUrl(String filename, String partNumber, String uploadId) {
Map<String, String> extraQueryParams = new HashMap<>();
extraQueryParams.put("partNumber", partNumber + "");
extraQueryParams.put("partNumber", partNumber);
extraQueryParams.put("uploadId", uploadId);
return client.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(c.getBucket())
.object(filename)
.method(Method.PUT)
.expiry(60 * 60 * 24)
.extraQueryParams(extraQueryParams)
.build());
return getMinioClient()
.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucket())
.object(filename)
.method(Method.PUT)
.expiry(60 * 60 * 24)
.extraQueryParams(extraQueryParams)
.build());
}
@Override
public String merge(String filename, String uploadId) {
playEduMinioClient.merge(c.getBucket(), filename, uploadId);
getPlayEduMinioClient().merge(bucket(), filename, uploadId);
return url(filename);
}
@Override
@SneakyThrows
public void removeByPath(String path) {
client.removeObject(RemoveObjectArgs.builder().bucket(c.getBucket()).object(path).build());
getMinioClient()
.removeObject(RemoveObjectArgs.builder().bucket(bucket()).object(path).build());
}
@Override
@ -108,12 +148,12 @@ public class MinioServiceImpl implements MinioService {
InputStream inputStream = new ByteArrayInputStream(file);
PutObjectArgs objectArgs =
PutObjectArgs.builder().bucket(c.getBucket()).object(savePath).stream(
PutObjectArgs.builder().bucket(bucket()).object(savePath).stream(
inputStream, file.length, -1)
.contentType(contentType)
.build();
client.putObject(objectArgs);
getMinioClient().putObject(objectArgs);
return url(savePath);
}

View File

@ -151,6 +151,11 @@ public class UserCourseHourRecordServiceImpl
remove(query().getWrapper().eq("user_id", userId).eq("course_id", courseId));
}
@Override
public void remove(Integer userId) {
remove(query().getWrapper().eq("user_id", userId));
}
@Override
public PaginationResult<UserCourseHourRecord> paginate(
int page, int size, UserCourseHourRecordPaginateFilter filter) {

View File

@ -138,6 +138,11 @@ public class UserCourseRecordServiceImpl
remove(query().getWrapper().in("user_id", userId).eq("course_id", courseId));
}
@Override
public void destroy(Integer userId) {
remove(query().getWrapper().in("user_id", userId));
}
@Override
public void decrease(Integer userId, Integer courseId, int count) {
UserCourseRecord record = find(userId, courseId);

View File

@ -57,4 +57,9 @@ public class UserLearnDurationRecordServiceImpl
save(record);
}
@Override
public void remove(Integer userId) {
remove(query().getWrapper().eq("user_id", userId));
}
}

View File

@ -114,4 +114,9 @@ public class UserLearnDurationStatsServiceImpl
return list(
query().getWrapper().eq("user_id", userId).between("created_date", startAt, endAt));
}
@Override
public void remove(Integer userId) {
remove(query().getWrapper().eq("user_id", userId));
}
}

View File

@ -54,14 +54,6 @@ public class UserLoginRecordServiceImpl extends ServiceImpl<UserLoginRecordMappe
return record;
}
@Override
public void saveIpArea(Integer id, String area) {
UserLoginRecord record = new UserLoginRecord();
record.setId(id);
record.setIpArea(area);
updateById(record);
}
@Override
public void logout(Integer userid, String jti) {
UserLoginRecord record =
@ -79,4 +71,9 @@ public class UserLoginRecordServiceImpl extends ServiceImpl<UserLoginRecordMappe
updateById(newRecord);
}
@Override
public void remove(Integer userId) {
remove(query().getWrapper().eq("user_id", userId));
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.api.types.config;
import lombok.Data;
@Data
public class MinioConfig {
private String accessKey;
private String secretKey;
private String bucket;
private String endpoint;
private String domain;
}

View File

@ -47,14 +47,6 @@ spring:
await-termination: true
thread-name-prefix: "playedu-default-thread"
# Minio
minio:
access-key: ""
secret-key: ""
end-point: ""
bucket: ""
domain: ""
mybatis:
mapper-locations: classpath:mapper/*.xml