Compare commits
41 Commits
515bd8fae2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fcf381b8f1 | |||
| fb29acc145 | |||
| 7182371c92 | |||
| 09b326c07a | |||
| 49963bb49c | |||
| bf2a80917c | |||
| deabd5f7f5 | |||
| 0802f6fe70 | |||
| 679241588f | |||
| fe7128dd4e | |||
| 1184ea7895 | |||
| e468be74b7 | |||
| 57166af2f4 | |||
| fcb8ac9c22 | |||
| 7f2fda16ec | |||
| 9cd43c0e74 | |||
| b86f37443c | |||
| 36e5231c6c | |||
| 868e0bb7bd | |||
| 504dd8d964 | |||
| 6277e3ab42 | |||
| 0f5169c1d7 | |||
| 5ebf40101d | |||
| 5858bf2ecc | |||
| bddf6c0936 | |||
| 340bc5b5e3 | |||
| 2d76ed507e | |||
| 494ab77486 | |||
| d3cfa80613 | |||
| 66eb02cb08 | |||
| 0b0311d2d9 | |||
| 7b68184787 | |||
| bc9334f5ab | |||
| aff862d161 | |||
| 15e909c318 | |||
| 260c2c79f1 | |||
| 4135b72648 | |||
| 5404f295e4 | |||
| 948144e7b2 | |||
| bc4c74f881 | |||
| f4498e5676 |
BIN
debug_roi.jpg
BIN
debug_roi.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
7142
docx/enlish.sql
Normal file
7142
docx/enlish.sql
Normal file
File diff suppressed because one or more lines are too long
5728
docx/enlish_v2.sql
Normal file
5728
docx/enlish_v2.sql
Normal file
File diff suppressed because one or more lines are too long
11395
docx/enlish_v3.sql
Normal file
11395
docx/enlish_v3.sql
Normal file
File diff suppressed because one or more lines are too long
@@ -105,6 +105,74 @@
|
|||||||
<artifactId>tess4j</artifactId>
|
<artifactId>tess4j</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 阿里云短信发送 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.aliyun</groupId>
|
||||||
|
<artifactId>dysmsapi20170525</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.dev33</groupId>
|
||||||
|
<artifactId>sa-token-redis-template</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 密码加密 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tomcat.embed</groupId>
|
||||||
|
<artifactId>tomcat-embed-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-codec</groupId>
|
||||||
|
<artifactId>commons-codec</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-openai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.ai</groupId>
|
||||||
|
<artifactId>spring-ai-starter-model-openai</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.zxing</groupId>
|
||||||
|
<artifactId>javase</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package com.yinlihupo.enlish.service;
|
|||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@MapperScan("com.yinlihupo.enlish.service.domain.mapper")
|
@MapperScan("com.yinlihupo.enlish.service.domain.mapper")
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
|
@EnableAsync
|
||||||
public class EnlishServiceApplication {
|
public class EnlishServiceApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.yinlihupo.enlish.service.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = "aliyun")
|
||||||
|
@Component
|
||||||
|
@Data
|
||||||
|
public class AliyunAccessKeyProperties {
|
||||||
|
private String accessKeyId;
|
||||||
|
private String accessKeySecret;
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.yinlihupo.enlish.service.config;
|
||||||
|
|
||||||
|
import com.aliyun.dysmsapi20170525.Client;
|
||||||
|
import com.aliyun.teaopenapi.models.Config;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class AliyunSmsClientConfig {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AliyunAccessKeyProperties aliyunAccessKeyProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Client smsClient() {
|
||||||
|
try {
|
||||||
|
Config config = new Config()
|
||||||
|
// 必填
|
||||||
|
.setAccessKeyId(aliyunAccessKeyProperties.getAccessKeyId())
|
||||||
|
// 必填
|
||||||
|
.setAccessKeySecret(aliyunAccessKeyProperties.getAccessKeySecret());
|
||||||
|
|
||||||
|
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
|
||||||
|
config.endpoint = "dysmsapi.aliyuncs.com";
|
||||||
|
|
||||||
|
return new Client(config);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("初始化阿里云短信发送客户端错误: ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.yinlihupo.enlish.service.config;
|
||||||
|
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class PasswordEncoderConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
// BCrypt 是一种安全且适合密码存储的哈希算法,它在进行哈希时会自动加入“盐”,增加密码的安全性。
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.yinlihupo.enlish.service.config;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||||
|
import cn.dev33.satoken.router.SaHttpMethod;
|
||||||
|
import cn.dev33.satoken.router.SaRouter;
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(new SaInterceptor(handler -> {
|
||||||
|
log.info("Sa-Token 拦截器: {}", handler);
|
||||||
|
|
||||||
|
|
||||||
|
SaRouter.match(SaHttpMethod.OPTIONS)
|
||||||
|
.free(r -> System.out.println("--------OPTIONS预检请求,不做处理"))
|
||||||
|
.back();
|
||||||
|
|
||||||
|
SaRouter.match("/**")
|
||||||
|
.notMatch("/login/**")
|
||||||
|
.notMatch("/plan/word/voice")
|
||||||
|
.notMatch("/plan/word/voice/tts")
|
||||||
|
.check(r -> StpUtil.checkLogin());
|
||||||
|
|
||||||
|
SaRouter.match("/admin/**")
|
||||||
|
.notMatch("/plan/word/voice")
|
||||||
|
.notMatch("/plan/word/voice/tts")
|
||||||
|
.check(r -> StpUtil.checkRole("root"));
|
||||||
|
|
||||||
|
}))
|
||||||
|
.addPathPatterns("/**")
|
||||||
|
.excludePathPatterns("/error");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.yinlihupo.enlish.service.config;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpInterface;
|
||||||
|
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.yinlihupo.enlish.service.constant.RoleConstants;
|
||||||
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class StpInterfaceImpl implements StpInterface {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private StringRedisTemplate stringRedisTemplate;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||||
|
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getRoleList(Object loginId, String loginType) {
|
||||||
|
long l = 0L;
|
||||||
|
if (loginId instanceof String loginIdStr) {
|
||||||
|
l = Long.parseLong(loginIdStr);
|
||||||
|
}
|
||||||
|
return userToRole(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> userToRole(Long userId) {
|
||||||
|
|
||||||
|
String keys = stringRedisTemplate.opsForValue().get(RoleConstants.buildUserRoleKey(userId));
|
||||||
|
if (keys != null) {
|
||||||
|
try {
|
||||||
|
return JsonUtils.parseList(keys, String.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,15 @@ public class ExamWordsConstant {
|
|||||||
public static final int ZONE_F_SIZE = 7;
|
public static final int ZONE_F_SIZE = 7;
|
||||||
|
|
||||||
|
|
||||||
|
// 摸底
|
||||||
public static final int EXAM_TYPE_BASELINE = 1;
|
public static final int EXAM_TYPE_BASELINE = 1;
|
||||||
|
// 中期
|
||||||
|
public static final int EXAM_TYPE_MIDTERM = 2;
|
||||||
|
// 期末
|
||||||
|
public static final int EXAM_TYPE_FINAL = 3;
|
||||||
|
// 小测
|
||||||
|
public static final int EXAM_TYPE_TEST = 4;
|
||||||
|
|
||||||
|
|
||||||
public static int getZoneA(int gradeId) {
|
public static int getZoneA(int gradeId) {
|
||||||
return switch (gradeId) {
|
return switch (gradeId) {
|
||||||
@@ -129,4 +137,17 @@ public class ExamWordsConstant {
|
|||||||
default -> "";
|
default -> "";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String day2Chinese(int day) {
|
||||||
|
return switch (day) {
|
||||||
|
case 1 -> "一";
|
||||||
|
case 2 -> "二";
|
||||||
|
case 3 -> "三";
|
||||||
|
case 4 -> "四";
|
||||||
|
case 5 -> "五";
|
||||||
|
case 6 -> "六";
|
||||||
|
case 7 -> "七";
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
package com.yinlihupo.enlish.service.constant;
|
package com.yinlihupo.enlish.service.constant;
|
||||||
|
|
||||||
public interface LessonPlanConstant {
|
public class LessonPlanConstant {
|
||||||
|
|
||||||
String TITLE = "Title";
|
public static final String TITLE = "Title";
|
||||||
String PASSAGE = "The Passage";
|
public static final String PASSAGE = "ThePassage";
|
||||||
String QUIZ = "Quiz";
|
public static final String QUIZ = "Quiz";
|
||||||
String ANSWER_KEY_EXPLANATION = "Answer Key & Explanation";
|
public static final String ANSWER_KEY_EXPLANATION = "AnswerKey&Explanation";
|
||||||
String FULL_TRANSLATION = "Full Translation";
|
public static final String FULL_TRANSLATION = "FullTranslation";
|
||||||
|
|
||||||
|
// 正在生成学案标识
|
||||||
|
public static final String GENERATING_PLAN = "GeneratingPlan";
|
||||||
|
|
||||||
|
public static String buildGeneratePlanContent(Integer studentId) {
|
||||||
|
return GENERATING_PLAN + ":" + studentId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.yinlihupo.enlish.service.constant;
|
||||||
|
|
||||||
|
public class RoleConstants {
|
||||||
|
|
||||||
|
public final static String USER_ROLE = "user:role";
|
||||||
|
public final static String ROLE = "role";
|
||||||
|
|
||||||
|
public static String buildUserRoleKey(Long userId) {
|
||||||
|
return USER_ROLE + ":" + userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.yinlihupo.enlish.service.constant;
|
||||||
|
|
||||||
|
public class StudentConstant {
|
||||||
|
|
||||||
|
public static final String ANALYZE_STUDENT_STUDY = "analyzeStudentStudy";
|
||||||
|
|
||||||
|
public static String buildAnalyzeStudentStudyKey(Integer studentId) {
|
||||||
|
return ANALYZE_STUDENT_STUDY + ":" + studentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.yinlihupo.enlish.service.constant;
|
||||||
|
|
||||||
|
public class UserRedisConstants {
|
||||||
|
|
||||||
|
public static final String USER_LOGIN_CODE = "user:login:code:";
|
||||||
|
|
||||||
|
public static final String USER_INVITATION_CODE = "user:invitation:code:";
|
||||||
|
|
||||||
|
public static String buildUserLoginCode(String phone) {
|
||||||
|
return USER_LOGIN_CODE + phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String buildUserInvitationCode(String code) {
|
||||||
|
return USER_INVITATION_CODE + code;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.yinlihupo.enlish.service.controller;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.constant.UserRedisConstants;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.RoleDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.admin.CreateInvitationCodeReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.admin.CreateInvitationCodeRspVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.user.CreateUserReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.user.FindUserListRepVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.user.FindUserListRspVO;
|
||||||
|
import com.yinlihupo.enlish.service.service.RoleService;
|
||||||
|
import com.yinlihupo.enlish.service.service.UserService;
|
||||||
|
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.yinlihupo.framework.common.response.PageResponse;
|
||||||
|
import com.yinlihupo.framework.common.response.Response;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RequestMapping("/admin/")
|
||||||
|
@RestController
|
||||||
|
public class AdminController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserService userService;
|
||||||
|
@Resource
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
@Resource
|
||||||
|
private RoleService roleService;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@PostMapping("user/list")
|
||||||
|
@ApiOperationLog(description = "查询用户列表")
|
||||||
|
public PageResponse<FindUserListRspVO> listUser(@RequestBody FindUserListRepVO findUserListRepVO) {
|
||||||
|
Integer userTotal = userService.findUserTotal();
|
||||||
|
if (userTotal == null || userTotal == 0) {
|
||||||
|
throw new RuntimeException("暂无用户");
|
||||||
|
}
|
||||||
|
|
||||||
|
Integer page = findUserListRepVO.getPage();
|
||||||
|
Integer pageSize = findUserListRepVO.getPageSize();
|
||||||
|
String name = findUserListRepVO.getName();
|
||||||
|
List<UserDO> usersList = userService.findUsersList(page, pageSize, name.isEmpty() ? null : name);
|
||||||
|
Map<Long, RoleDO> userId2RoleMap = roleService.findUserId2RoleMap(usersList.stream().map(UserDO::getId).toList());
|
||||||
|
return PageResponse.success(usersList.stream().map(userDO -> FindUserListRspVO.builder()
|
||||||
|
.id(userDO.getId())
|
||||||
|
.name(userDO.getName())
|
||||||
|
.phone(userDO.getPhone())
|
||||||
|
.roleName(userId2RoleMap.get(userDO.getId()).getRoleName())
|
||||||
|
.build()).toList(), page, userTotal, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("user/create")
|
||||||
|
public Response<Void> createUser(@RequestBody CreateUserReqVO createUserReqVO) {
|
||||||
|
String name = createUserReqVO.getName();
|
||||||
|
String phone = createUserReqVO.getPhone();
|
||||||
|
String password = createUserReqVO.getPassword();
|
||||||
|
|
||||||
|
userService.createUser(UserDO.builder()
|
||||||
|
.name(name)
|
||||||
|
.phone(phone)
|
||||||
|
.password(passwordEncoder.encode(password))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
return Response.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("user/create/invitation/code")
|
||||||
|
@ApiOperationLog(description = "创建邀请码")
|
||||||
|
public Response<CreateInvitationCodeRspVO> createInvitationCode(@RequestBody CreateInvitationCodeReqVO createInvitationCodeReqVO) {
|
||||||
|
|
||||||
|
String code = String.valueOf(new Random().nextInt(1000000));
|
||||||
|
Integer limit = createInvitationCodeReqVO.getLimit();
|
||||||
|
Integer expire = createInvitationCodeReqVO.getExpire();
|
||||||
|
|
||||||
|
redisTemplate.opsForValue().set(UserRedisConstants.buildUserInvitationCode(code), limit);
|
||||||
|
redisTemplate.expire(UserRedisConstants.buildUserInvitationCode(code), expire, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
return Response.success(CreateInvitationCodeRspVO.builder().invitationCode(code).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,25 +42,27 @@ public class ExamWordsController {
|
|||||||
|
|
||||||
@PostMapping("generate")
|
@PostMapping("generate")
|
||||||
public void generateFeltExamWords(@RequestBody GenerateExamWordsReqVO generateExamWordsReqVO, HttpServletResponse response) {
|
public void generateFeltExamWords(@RequestBody GenerateExamWordsReqVO generateExamWordsReqVO, HttpServletResponse response) {
|
||||||
Integer gradeId = generateExamWordsReqVO.getGradeId();
|
|
||||||
Integer level = generateExamWordsReqVO.getLevel();
|
|
||||||
Integer type = generateExamWordsReqVO.getType();
|
Integer type = generateExamWordsReqVO.getType();
|
||||||
Integer studentId = generateExamWordsReqVO.getStudentId();
|
Integer studentId = generateExamWordsReqVO.getStudentId();
|
||||||
if (studentId == null || gradeId == null || level == null) {
|
if (studentId == null) {
|
||||||
throw new RuntimeException("参数错误");
|
throw new RuntimeException("参数错误");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ExamWordsDO examWordsDO = examWordsService.generateExamWords(gradeId, level, studentId, type);
|
ExamWordsDO examWordsDO = examWordsService.generateExamWords(studentId, type);
|
||||||
if (examWordsDO == null || examWordsDO.getWordIds().isEmpty()) {
|
if (examWordsDO == null || examWordsDO.getWordIds().isEmpty()) {
|
||||||
throw new RuntimeException("没有单词");
|
throw new RuntimeException("没有单词");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyService.findVocabularyBankDOListById(examWordsDO.getWordIds());
|
List<VocabularyBankDO> vocabularyBankDOS = vocabularyService.findVocabularyBankDOListById(examWordsDO.getWordIds());
|
||||||
List<Word> assessmentWords = vocabularyBankDOS.stream().map(vocabularyBankDO -> Word.builder()
|
List<Word> assessmentWords = vocabularyBankDOS.stream().map(vocabularyBankDO -> Word.builder()
|
||||||
.id(vocabularyBankDO.getId())
|
.id(vocabularyBankDO.getId())
|
||||||
.title(vocabularyBankDO.getWord())
|
.title(vocabularyBankDO.getWord())
|
||||||
.definition(vocabularyBankDO.getDefinition())
|
.definition(vocabularyBankDO.getDefinition().length() > 6 ? vocabularyBankDO.getDefinition().substring(0, 6) : vocabularyBankDO.getDefinition())
|
||||||
.build()).toList();
|
.build()).toList();
|
||||||
|
// bug: 获取单词后,单词的id会乱序、 需要重新更新考试记录中的 id
|
||||||
|
examWordsDO.setWordIds(assessmentWords.stream().map(Word::getId).toList());
|
||||||
|
examWordsService.updateExamWordsWordIdsOrder(examWordsDO);
|
||||||
|
log.info("生成试卷成功 {}", examWordsDO);
|
||||||
List<StudentDetail> studentDetailList = studentService.getStudentDetailList(Collections.singletonList(studentId));
|
List<StudentDetail> studentDetailList = studentService.getStudentDetailList(Collections.singletonList(studentId));
|
||||||
List<Map<String, Object>> maps = studentDetailList.stream().map(studentDetail -> {
|
List<Map<String, Object>> maps = studentDetailList.stream().map(studentDetail -> {
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
@@ -70,10 +72,16 @@ public class ExamWordsController {
|
|||||||
data.put("examStr", examWordsDO.getTitle());
|
data.put("examStr", examWordsDO.getTitle());
|
||||||
data.put("words", assessmentWords);
|
data.put("words", assessmentWords);
|
||||||
data.put("answer", assessmentWords);
|
data.put("answer", assessmentWords);
|
||||||
|
|
||||||
|
List<Word> words1 = assessmentWords.subList(0, assessmentWords.size() / 2);
|
||||||
|
List<Word> words2 = assessmentWords.subList(assessmentWords.size() / 2, assessmentWords.size());
|
||||||
|
data.put("words1", words1);
|
||||||
|
data.put("words2", words2);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
WordExportUtil.generateExamWords(maps, response, templateWordPath);
|
WordExportUtil.generateExamWords(maps, examWordsDO, response, templateWordPath);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
||||||
@@ -105,12 +113,17 @@ public class ExamWordsController {
|
|||||||
PageResponse<ExamWordsResultRspVO> getExamWordsResult(@RequestBody ExamWordsResultReqVO examWordsResultReqVO) {
|
PageResponse<ExamWordsResultRspVO> getExamWordsResult(@RequestBody ExamWordsResultReqVO examWordsResultReqVO) {
|
||||||
Integer page = examWordsResultReqVO.getPage();
|
Integer page = examWordsResultReqVO.getPage();
|
||||||
Integer size = examWordsResultReqVO.getSize();
|
Integer size = examWordsResultReqVO.getSize();
|
||||||
|
Integer classId = examWordsResultReqVO.getClassId();
|
||||||
|
Integer gradeId = examWordsResultReqVO.getGradeId();
|
||||||
|
String studentName = examWordsResultReqVO.getStudentName();
|
||||||
Integer total = examWordsJudgeService.getExamWordsJudgeResultCount();
|
Integer total = examWordsJudgeService.getExamWordsJudgeResultCount();
|
||||||
List<ExamWordsJudgeResultDO> examWordsJudgeResult = examWordsJudgeService.getExamWordsJudgeResult(page, size);
|
List<ExamWordsJudgeResultDO> examWordsJudgeResult = examWordsJudgeService.getExamWordsJudgeResult(page, size, classId, gradeId, studentName);
|
||||||
List<ExamWordsResultRspVO> list = examWordsJudgeResult.stream().map(examWordsJudgeResultDO -> ExamWordsResultRspVO
|
List<ExamWordsResultRspVO> list = examWordsJudgeResult.stream().map(examWordsJudgeResultDO -> ExamWordsResultRspVO
|
||||||
.builder()
|
.builder()
|
||||||
.id(examWordsJudgeResultDO.getId())
|
.id(examWordsJudgeResultDO.getId())
|
||||||
.studentId(examWordsJudgeResultDO.getStudentId())
|
.studentId(examWordsJudgeResultDO.getStudentId())
|
||||||
|
.studentName(examWordsJudgeResultDO.getStudentId() != null ? studentService.getStudentById(examWordsJudgeResultDO.getStudentId()).getName() : "")
|
||||||
|
.examWordsTitle(examWordsJudgeResultDO.getExamWordsId() != null ? examWordsService.getExamWordsDOById(examWordsJudgeResultDO.getExamWordsId()).getTitle() : "")
|
||||||
.examWordsId(examWordsJudgeResultDO.getExamWordsId())
|
.examWordsId(examWordsJudgeResultDO.getExamWordsId())
|
||||||
.startDate(examWordsJudgeResultDO.getStartDate())
|
.startDate(examWordsJudgeResultDO.getStartDate())
|
||||||
.correctWordCount(examWordsJudgeResultDO.getCorrectWordCount())
|
.correctWordCount(examWordsJudgeResultDO.getCorrectWordCount())
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
package com.yinlihupo.enlish.service.controller;
|
package com.yinlihupo.enlish.service.controller;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.constant.LessonPlanConstant;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||||
import com.yinlihupo.enlish.service.model.vo.plan.AddLessonPlanReqVO;
|
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||||
import com.yinlihupo.enlish.service.model.vo.plan.DownLoadLessonPlanReqVO;
|
import com.yinlihupo.enlish.service.model.vo.plan.*;
|
||||||
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
||||||
|
import com.yinlihupo.enlish.service.utils.TTSUtil;
|
||||||
import com.yinlihupo.enlish.service.utils.WordExportUtil;
|
import com.yinlihupo.enlish.service.utils.WordExportUtil;
|
||||||
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.yinlihupo.framework.common.response.PageResponse;
|
||||||
import com.yinlihupo.framework.common.response.Response;
|
import com.yinlihupo.framework.common.response.Response;
|
||||||
import com.yinlihupo.framework.common.util.JsonUtils;
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
@@ -28,8 +33,13 @@ public class LessonPlanController {
|
|||||||
@Resource
|
@Resource
|
||||||
private LessonPlansService lessonPlanService;
|
private LessonPlansService lessonPlanService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
@Resource(name = "taskExecutor")
|
@Resource(name = "taskExecutor")
|
||||||
private Executor taskExecutor;
|
private Executor taskExecutor;
|
||||||
|
@Resource
|
||||||
|
private TTSUtil ttsUtil;
|
||||||
|
|
||||||
@Value("${templates.plan.weekday}")
|
@Value("${templates.plan.weekday}")
|
||||||
private String planWeekday;
|
private String planWeekday;
|
||||||
@@ -41,8 +51,12 @@ public class LessonPlanController {
|
|||||||
public Response<String> generateLessonPlan(@RequestBody AddLessonPlanReqVO addLessonPlanReqVO) {
|
public Response<String> generateLessonPlan(@RequestBody AddLessonPlanReqVO addLessonPlanReqVO) {
|
||||||
Integer studentId = addLessonPlanReqVO.getStudentId();
|
Integer studentId = addLessonPlanReqVO.getStudentId();
|
||||||
Integer unitId = addLessonPlanReqVO.getUnitId();
|
Integer unitId = addLessonPlanReqVO.getUnitId();
|
||||||
|
Integer wordSize = addLessonPlanReqVO.getWordSize();
|
||||||
try {
|
try {
|
||||||
taskExecutor.execute(() -> lessonPlanService.generateLessonPlans(studentId, unitId));
|
if (redisTemplate.opsForValue().get(LessonPlanConstant.buildGeneratePlanContent(studentId)) != null) {
|
||||||
|
throw new RuntimeException("学案正在生成,请耐心等待");
|
||||||
|
}
|
||||||
|
taskExecutor.execute(() -> lessonPlanService.generateLessonPlans(studentId, unitId, wordSize));
|
||||||
return Response.success("生成学案成功,请等待 10 分钟");
|
return Response.success("生成学案成功,请等待 10 分钟");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
@@ -59,13 +73,63 @@ public class LessonPlanController {
|
|||||||
try {
|
try {
|
||||||
Map<String, Object> map = JsonUtils.parseMap(lessonPlanById.getContentDetails(), String.class, Object.class);
|
Map<String, Object> map = JsonUtils.parseMap(lessonPlanById.getContentDetails(), String.class, Object.class);
|
||||||
if (!lessonPlanById.getTitle().contains("复习")) {
|
if (!lessonPlanById.getTitle().contains("复习")) {
|
||||||
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById.getTitle(), response, planWeekday, true);
|
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById, response, planWeekday, true);
|
||||||
} else {
|
} else {
|
||||||
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById.getTitle(), response, planWeekend, false);
|
WordExportUtil.generateLessonPlanDocx(map, lessonPlanById, response, planWeekend, false);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("word/voice")
|
||||||
|
@ApiOperationLog(description = "获取单词")
|
||||||
|
public Response<FindWordVoiceRspVO> findPlanWordVoice(@RequestBody FindWordVoiceReqVO findWordVoiceReqVO) {
|
||||||
|
Integer id = findWordVoiceReqVO.getPlanId();
|
||||||
|
LessonPlansDO lessonPlanById = lessonPlanService.findLessonPlanById(id);
|
||||||
|
try {
|
||||||
|
Map<String, Object> map = JsonUtils.parseMap(lessonPlanById.getContentDetails(), String.class, Object.class);
|
||||||
|
Object syncVocabList = map.get("syncVocabList");
|
||||||
|
List<VocabularyBankDO> list = JsonUtils.parseList(JsonUtils.toJsonString(syncVocabList), VocabularyBankDO.class);
|
||||||
|
List<String> words = list.stream().map(VocabularyBankDO::getWord).toList();
|
||||||
|
|
||||||
|
return Response.success(FindWordVoiceRspVO.builder().words(words).build());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
return Response.fail("获取单词失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("word/voice/tts")
|
||||||
|
public void findPlanWordVoiceTTS(@RequestBody FindWordTTSVoiceReqVO findWordVoiceReqVO, HttpServletResponse response) {
|
||||||
|
ttsUtil.generateWordVoice(findWordVoiceReqVO.getText(), response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("check")
|
||||||
|
@ApiOperationLog(description = "检测学案是否在生成")
|
||||||
|
public Response<String> checkLessonPlan(@RequestBody FindIsGeneratePlanReqVO findIsGeneratePlanReqVO) {
|
||||||
|
Integer studentId = findIsGeneratePlanReqVO.getStudentId();
|
||||||
|
String key = LessonPlanConstant.buildGeneratePlanContent(studentId);
|
||||||
|
if (redisTemplate.opsForValue().get(key) != null) {
|
||||||
|
return Response.fail();
|
||||||
|
}
|
||||||
|
return Response.success("学案生成完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("student/list")
|
||||||
|
@ApiOperationLog(description = "查询学生学案")
|
||||||
|
public Response<FindPlanStudentListRspVO> findStudentPlans(@RequestBody FindPlanStudentReqVO findPlanStudentReqVO) {
|
||||||
|
List<LessonPlansDO> lessonPlansDOS = lessonPlanService.findLessonPlansByStudentId(findPlanStudentReqVO.getStudentId());
|
||||||
|
List<LessonPlanItem> list = lessonPlansDOS.stream().map(lessonPlansDO -> LessonPlanItem
|
||||||
|
.builder()
|
||||||
|
.id(lessonPlansDO.getId())
|
||||||
|
.isFinished(0)
|
||||||
|
.title(lessonPlansDO.getTitle())
|
||||||
|
.build())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return Response.success(FindPlanStudentListRspVO.builder().lessonPlanItems(list).build());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.yinlihupo.enlish.service.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.login.LoginReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.login.VerificationCodeReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.service.LoginService;
|
||||||
|
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.yinlihupo.framework.common.response.Response;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RequestMapping("/login/")
|
||||||
|
@RestController
|
||||||
|
@Slf4j
|
||||||
|
public class LoginController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private LoginService loginService;
|
||||||
|
|
||||||
|
@PostMapping("login")
|
||||||
|
@ApiOperationLog(description = "登录")
|
||||||
|
public Response<String> login(@RequestBody LoginReqVO loginReqVO) {
|
||||||
|
try {
|
||||||
|
loginService.login(loginReqVO.getPhone(), loginReqVO.getName(), loginReqVO.getPassword(), loginReqVO.getCode(), loginReqVO.getInvitationCode());
|
||||||
|
return Response.success(StpUtil.getTokenInfo().getTokenValue());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("注册或登录失败 {}", e.getMessage());
|
||||||
|
return Response.fail("注册或登录失败 " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("logout")
|
||||||
|
@ApiOperationLog(description = "登出")
|
||||||
|
public Response<Void> logout() {
|
||||||
|
try {
|
||||||
|
StpUtil.logout();
|
||||||
|
return Response.success();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("登出失败 {}", e.getMessage());
|
||||||
|
return Response.fail("登出失败 " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("sendVerificationCode")
|
||||||
|
@ApiOperationLog(description = "发送验证码")
|
||||||
|
public Response<Void> sendVerificationCode(@RequestBody VerificationCodeReqVO verificationCodeReqVO) {
|
||||||
|
try {
|
||||||
|
loginService.sendVerificationCode(verificationCodeReqVO.getPhone());
|
||||||
|
return Response.success();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("发送验证码失败 {}", e.getMessage());
|
||||||
|
return Response.fail("发送验证码失败 " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ package com.yinlihupo.enlish.service.controller;
|
|||||||
import com.yinlihupo.enlish.service.domain.dataobject.ClassDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.ClassDO;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.GradeDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.GradeDO;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.exam.WordMasteryDetail;
|
||||||
import com.yinlihupo.enlish.service.model.vo.student.*;
|
import com.yinlihupo.enlish.service.model.vo.student.*;
|
||||||
import com.yinlihupo.enlish.service.service.ClassService;
|
import com.yinlihupo.enlish.service.service.ClassService;
|
||||||
import com.yinlihupo.enlish.service.service.GradeService;
|
import com.yinlihupo.enlish.service.service.GradeService;
|
||||||
@@ -46,6 +48,8 @@ public class StudentController {
|
|||||||
.id(studentDO.getId())
|
.id(studentDO.getId())
|
||||||
.name(studentDO.getName())
|
.name(studentDO.getName())
|
||||||
.classId(studentDO.getClassId())
|
.classId(studentDO.getClassId())
|
||||||
|
.className(classService.findClassById(studentDO.getClassId()).getTitle())
|
||||||
|
.gradeName(gradeService.findByClassId(studentDO.getGradeId()).getTitle())
|
||||||
.gradeId(studentDO.getGradeId())
|
.gradeId(studentDO.getGradeId())
|
||||||
.build()).toList();
|
.build()).toList();
|
||||||
|
|
||||||
@@ -85,4 +89,28 @@ public class StudentController {
|
|||||||
studentService.deleteStudent(deleteStudentReqVO.getStudentId());
|
studentService.deleteStudent(deleteStudentReqVO.getStudentId());
|
||||||
return Response.success();
|
return Response.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("analyze")
|
||||||
|
@ApiOperationLog(description = "学生学习分析")
|
||||||
|
public Response<String> analyzeStudentStudy(@RequestBody AnalyzeStudentStudyReqVO analyzeStudentStudyReqVO) {
|
||||||
|
String analyzeStudentStudy = studentService.analyzeStudentStudy(analyzeStudentStudyReqVO.getStudentId());
|
||||||
|
return Response.success(analyzeStudentStudy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("mastery/detail")
|
||||||
|
@ApiOperationLog(description = "查询学生单词掌握详情")
|
||||||
|
public Response<List<FindStudentMasteryDetailRspVO>> findStudentMasteryDetail(@RequestBody FindStudentMasteryDetailReqVO findStudentMasteryDetailReqVO) {
|
||||||
|
Integer studentId = findStudentMasteryDetailReqVO.getStudentId();
|
||||||
|
List<WordMasteryDetail> studentWordMasteryDetail = studentService.findStudentWordMasteryDetail(studentId);
|
||||||
|
|
||||||
|
List<FindStudentMasteryDetailRspVO> list = studentWordMasteryDetail.stream().map(wordMasteryDetail -> FindStudentMasteryDetailRspVO.builder()
|
||||||
|
.word(wordMasteryDetail.getWord())
|
||||||
|
.reviewCount(wordMasteryDetail.getReviewCount())
|
||||||
|
.memoryStrength(wordMasteryDetail.getMemoryStrength())
|
||||||
|
.updateTime(wordMasteryDetail.getUpdate_time())
|
||||||
|
.build()
|
||||||
|
).toList();
|
||||||
|
|
||||||
|
return Response.success(list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.yinlihupo.enlish.service.controller;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.user.FindUserInfoRspVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.user.UpdateUserInfoReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.service.UserService;
|
||||||
|
import com.yinlihupo.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.yinlihupo.framework.common.response.Response;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/")
|
||||||
|
@Slf4j
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@PostMapping("info")
|
||||||
|
public Response<FindUserInfoRspVO> info() {
|
||||||
|
|
||||||
|
UserDO user = userService.findUser();
|
||||||
|
FindUserInfoRspVO findUserInfoRspVO = FindUserInfoRspVO.builder()
|
||||||
|
.id(user.getId())
|
||||||
|
.name(user.getName())
|
||||||
|
.build();
|
||||||
|
return Response.success(findUserInfoRspVO);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("update-user-info")
|
||||||
|
@ApiOperationLog(description = "修改用户信息")
|
||||||
|
public Response<String> updatePassword(@RequestBody UpdateUserInfoReqVO updateUserInfoReqVO) {
|
||||||
|
try {
|
||||||
|
String code = updateUserInfoReqVO.getCode();
|
||||||
|
String newPassword = updateUserInfoReqVO.getNewPassword();
|
||||||
|
String phone = updateUserInfoReqVO.getPhone();
|
||||||
|
String name = updateUserInfoReqVO.getName();
|
||||||
|
userService.updateUserInfo(newPassword, code, phone, name);
|
||||||
|
|
||||||
|
StpUtil.logout();
|
||||||
|
|
||||||
|
return Response.success();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("修改密码失败 {}", e.getMessage());
|
||||||
|
return Response.fail(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.yinlihupo.enlish.service.controller;
|
package com.yinlihupo.enlish.service.controller;
|
||||||
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindStudentWordDetailReqVO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindStudentWordDetailRspVO;
|
||||||
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindWordTitleReqVO;
|
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindWordTitleReqVO;
|
||||||
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindWordTitleRspVO;
|
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindWordTitleRspVO;
|
||||||
import com.yinlihupo.enlish.service.service.VocabularyService;
|
import com.yinlihupo.enlish.service.service.VocabularyService;
|
||||||
@@ -30,4 +32,11 @@ public class VocabularyController {
|
|||||||
.build();
|
.build();
|
||||||
return Response.success(findWordTitleRspVO);
|
return Response.success(findWordTitleRspVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("student/detail")
|
||||||
|
@ApiOperationLog(description = "查询学生单词详情")
|
||||||
|
public Response<FindStudentWordDetailRspVO> findStudentWordDetail(@RequestBody FindStudentWordDetailReqVO vo) {
|
||||||
|
return Response.success(vocabularyService.findStudentWordDetail(vo.getId()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class PlanExamDO {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private Integer planId;
|
||||||
|
|
||||||
|
private Integer examId;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class RoleDO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String roleName;
|
||||||
|
|
||||||
|
private String roleKey;
|
||||||
|
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
private Integer isDeleted;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class StudentStageLearningRemarkDO {
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
private String commentContent;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class UserDO {
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String openid;
|
||||||
|
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.dataobject;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class UserRoleRelDO {
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private Long userId;
|
||||||
|
|
||||||
|
private Long roleId;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -7,4 +7,6 @@ public interface ExamWordsDOMapper {
|
|||||||
int insert(ExamWordsDO record);
|
int insert(ExamWordsDO record);
|
||||||
|
|
||||||
ExamWordsDO selectById(Integer id);
|
ExamWordsDO selectById(Integer id);
|
||||||
|
|
||||||
|
void updateWordIdsOrder(ExamWordsDO examWordsDO);
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,13 @@ public interface ExamWordsJudgeResultDOMapper {
|
|||||||
|
|
||||||
Integer selectCount();
|
Integer selectCount();
|
||||||
|
|
||||||
|
Integer selectUnfinishedCount();
|
||||||
|
|
||||||
ExamWordsJudgeResultDO selectDetailById(@Param("id") Integer id);
|
ExamWordsJudgeResultDO selectDetailById(@Param("id") Integer id);
|
||||||
|
|
||||||
List<ExamWordsJudgeResultDO> selectByStudentId(@Param("studentId") Integer studentId);
|
List<ExamWordsJudgeResultDO> selectByStudentId(@Param("studentId") Integer studentId);
|
||||||
|
|
||||||
|
List<ExamWordsJudgeResultDO> selectByStudentIdAndLimitTime(@Param("studentId") Integer studentId);
|
||||||
|
|
||||||
|
List<ExamWordsJudgeResultDO> selectByPageAndStudentIds(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize, @Param("studentIds") List<Integer> studentIds);
|
||||||
}
|
}
|
||||||
@@ -14,4 +14,6 @@ public interface LessonPlansDOMapper {
|
|||||||
List<LessonPlansDO> findLessonPlansByStudentId(@Param("ids") List<Integer> ids);
|
List<LessonPlansDO> findLessonPlansByStudentId(@Param("ids") List<Integer> ids);
|
||||||
|
|
||||||
LessonPlansDO selectByLessonId(@Param("lessonId") Integer lessonId);
|
LessonPlansDO selectByLessonId(@Param("lessonId") Integer lessonId);
|
||||||
|
|
||||||
|
List<LessonPlansDO> selectByStudentId(@Param("studentId") Integer studentId);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.PlanExamDO;
|
||||||
|
|
||||||
|
public interface PlanExamDOMapper {
|
||||||
|
void insert(PlanExamDO record);
|
||||||
|
|
||||||
|
PlanExamDO selectByExamId(Integer examId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.RoleDO;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface RoleDOMapper {
|
||||||
|
|
||||||
|
List<RoleDO> selectAll();
|
||||||
|
|
||||||
|
List<RoleDO> selectByIds(@Param("ids") List<Long> ids);
|
||||||
|
}
|
||||||
@@ -23,4 +23,10 @@ public interface StudentDOMapper {
|
|||||||
int selectStudentCountByClassId(@Param("classId") Integer classId);
|
int selectStudentCountByClassId(@Param("classId") Integer classId);
|
||||||
|
|
||||||
int updateStudentActualGradeId(@Param("studentId") Integer studentId, @Param("gradeId") Integer gradeId);
|
int updateStudentActualGradeId(@Param("studentId") Integer studentId, @Param("gradeId") Integer gradeId);
|
||||||
|
|
||||||
|
List<StudentDO> selectStudentDOListByClassId(@Param("classId") Integer classId);
|
||||||
|
|
||||||
|
List<StudentDO> selectStudentDOListByGradeId(@Param("gradeId") Integer gradeId);
|
||||||
|
|
||||||
|
List<StudentDO> selectStudentDOListByName(@Param("name") String name);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.StudentStageLearningRemarkDO;
|
||||||
|
|
||||||
|
public interface StudentStageLearningRemarkDOMapper {
|
||||||
|
|
||||||
|
void insert(StudentStageLearningRemarkDO studentStageLearningRemarkDO);
|
||||||
|
}
|
||||||
@@ -25,4 +25,6 @@ public interface UnitDOMapper {
|
|||||||
List<UnitDO> selectUnitDOList(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
|
List<UnitDO> selectUnitDOList(@Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
|
||||||
|
|
||||||
Integer selectUnitDOListCount();
|
Integer selectUnitDOListCount();
|
||||||
|
|
||||||
|
List<UnitDO> selectByUnitName(@Param("unitName") String unitName);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UserDOMapper {
|
||||||
|
|
||||||
|
UserDO selectByPhone(String phone);
|
||||||
|
|
||||||
|
void insert(UserDO userDO);
|
||||||
|
|
||||||
|
void updatePassword(@Param("id") Long id, @Param("password") String password);
|
||||||
|
|
||||||
|
void updateUserInfo(@Param("id") Long id, @Param("name") String name, @Param("password") String password, @Param("phone") String phone);
|
||||||
|
|
||||||
|
UserDO selectById(Long id);
|
||||||
|
|
||||||
|
List<UserDO> selectUserDOList(@Param("name") String name, @Param("startIndex") Integer startIndex, @Param("pageSize") Integer pageSize);
|
||||||
|
|
||||||
|
int selectUserTotal();
|
||||||
|
|
||||||
|
void createUser(UserDO userDO);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.yinlihupo.enlish.service.domain.mapper;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UserRoleRelDO;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UserRoleRelDOMapper {
|
||||||
|
|
||||||
|
List<UserRoleRelDO> selectAll();
|
||||||
|
|
||||||
|
List<UserRoleRelDO> selectByUserIds(@Param("userIds") List<Long> userIds);
|
||||||
|
}
|
||||||
@@ -24,4 +24,8 @@ public interface VocabularyBankDOMapper {
|
|||||||
List<VocabularyBankDO> selectVocabularyBankListByGradeIdRandom(@Param("gradeId") Integer gradeId, @Param("wordCount") Integer wordCount);
|
List<VocabularyBankDO> selectVocabularyBankListByGradeIdRandom(@Param("gradeId") Integer gradeId, @Param("wordCount") Integer wordCount);
|
||||||
|
|
||||||
Integer selectWordTotal();
|
Integer selectWordTotal();
|
||||||
|
|
||||||
|
List<VocabularyBankDO> selectByUnitIds(@Param("unitIds") List<Integer> unitIds);
|
||||||
|
|
||||||
|
List<VocabularyBankDO> selectByGradeIdAndNotMatchIds(@Param("gradeId") Integer gradeId, @Param("ids") List<Integer> ids, @Param("wordCount") Integer wordCount);
|
||||||
}
|
}
|
||||||
@@ -16,4 +16,12 @@ public interface WordMasteryLogDOMapper {
|
|||||||
int batchUpdateStudentMastery(@Param("wordMasteryLogDOs") List<WordMasteryLogDO> wordMasteryLogDOs);
|
int batchUpdateStudentMastery(@Param("wordMasteryLogDOs") List<WordMasteryLogDO> wordMasteryLogDOs);
|
||||||
|
|
||||||
int selectStudentStrengthCount(@Param("studentId") Integer studentId);
|
int selectStudentStrengthCount(@Param("studentId") Integer studentId);
|
||||||
|
|
||||||
|
List<WordMasteryLogDO> selectByStudentIdAndLimitTime(@Param("studentId") Integer studentId);
|
||||||
|
|
||||||
|
List<WordMasteryLogDO> selectAllByStudentId(@Param("studentId") Integer studentId);
|
||||||
|
|
||||||
|
Integer selectMasteryCount(@Param("studentId") Integer studentId);
|
||||||
|
|
||||||
|
Integer selectNotMasteryCount(@Param("studentId") Integer studentId);
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
|
|||||||
// ----------- 通用异常状态码 -----------
|
// ----------- 通用异常状态码 -----------
|
||||||
SYSTEM_ERROR("AUTH-10000", "出错啦,后台小哥正在努力修复中..."),
|
SYSTEM_ERROR("AUTH-10000", "出错啦,后台小哥正在努力修复中..."),
|
||||||
PARAM_NOT_VALID("AUTH-10001", "参数错误"),
|
PARAM_NOT_VALID("AUTH-10001", "参数错误"),
|
||||||
|
NOT_LOGIN("AUTH-10002", "请先登录")
|
||||||
// ----------- 业务异常状态码 -----------
|
// ----------- 业务异常状态码 -----------
|
||||||
|
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.yinlihupo.enlish.service.exception;
|
package com.yinlihupo.enlish.service.exception;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
import com.yinlihupo.enlish.service.enums.ResponseCodeEnum;
|
import com.yinlihupo.enlish.service.enums.ResponseCodeEnum;
|
||||||
import com.yinlihupo.framework.common.exception.BizException;
|
import com.yinlihupo.framework.common.exception.BizException;
|
||||||
import com.yinlihupo.framework.common.response.Response;
|
import com.yinlihupo.framework.common.response.Response;
|
||||||
@@ -96,4 +97,11 @@ public class GlobalExceptionHandler {
|
|||||||
log.error("{} request error, ", request.getRequestURI(), e);
|
log.error("{} request error, ", request.getRequestURI(), e);
|
||||||
return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
|
return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({ NotLoginException.class })
|
||||||
|
@ResponseBody
|
||||||
|
public Response<Object> handleNotLoginException(HttpServletRequest request, NotLoginException e) {
|
||||||
|
log.warn("{} request error, ", request.getRequestURI(), e);
|
||||||
|
return Response.fail(ResponseCodeEnum.NOT_LOGIN);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.bo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class Sentence {
|
||||||
|
|
||||||
|
@JsonProperty("target_word")
|
||||||
|
private String targetWord;
|
||||||
|
@JsonProperty("grade_level")
|
||||||
|
private String gradeLevel;
|
||||||
|
@JsonProperty("question")
|
||||||
|
private String question;
|
||||||
|
@JsonProperty("chinese_clue")
|
||||||
|
private String chineseClue;
|
||||||
|
@JsonProperty("correct_answer")
|
||||||
|
private String correctAnswer;
|
||||||
|
@JsonProperty("grammar_point")
|
||||||
|
private String grammarPoint;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.bo.exam;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class ExamWordsJudgeResultDetail {
|
||||||
|
|
||||||
|
private Integer correctWordCount;
|
||||||
|
|
||||||
|
private Integer wrongWordCount;
|
||||||
|
|
||||||
|
private LocalDateTime startDate;
|
||||||
|
|
||||||
|
private List<String> correctWords;
|
||||||
|
|
||||||
|
private List<String> wrongWords;
|
||||||
|
|
||||||
|
private String msg;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.bo.exam;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class WordMasteryDetail {
|
||||||
|
|
||||||
|
private String word;
|
||||||
|
|
||||||
|
private Integer reviewCount;
|
||||||
|
|
||||||
|
private Double memoryStrength;
|
||||||
|
|
||||||
|
private LocalDateTime update_time;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.admin;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class CreateInvitationCodeReqVO {
|
||||||
|
|
||||||
|
// 限制人数
|
||||||
|
private Integer limit;
|
||||||
|
// 有效期
|
||||||
|
private Integer expire;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.admin;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class CreateInvitationCodeRspVO {
|
||||||
|
|
||||||
|
private String invitationCode;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -13,4 +13,8 @@ public class ExamWordsResultReqVO {
|
|||||||
|
|
||||||
private Integer page;
|
private Integer page;
|
||||||
private Integer size;
|
private Integer size;
|
||||||
|
|
||||||
|
private Integer classId;
|
||||||
|
private Integer gradeId;
|
||||||
|
private String studentName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import java.time.LocalDateTime;
|
|||||||
@Data
|
@Data
|
||||||
@Builder
|
@Builder
|
||||||
public class ExamWordsResultRspVO {
|
public class ExamWordsResultRspVO {
|
||||||
|
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
private String ansSheetPath;
|
private String ansSheetPath;
|
||||||
@@ -20,6 +21,10 @@ public class ExamWordsResultRspVO {
|
|||||||
|
|
||||||
private Integer examWordsId;
|
private Integer examWordsId;
|
||||||
|
|
||||||
|
private String studentName;
|
||||||
|
|
||||||
|
private String examWordsTitle;
|
||||||
|
|
||||||
private Integer correctWordCount;
|
private Integer correctWordCount;
|
||||||
|
|
||||||
private Integer wrongWordCount;
|
private Integer wrongWordCount;
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import java.util.List;
|
|||||||
@Builder
|
@Builder
|
||||||
public class GenerateExamWordsReqVO {
|
public class GenerateExamWordsReqVO {
|
||||||
|
|
||||||
private Integer gradeId;
|
|
||||||
private Integer level;
|
|
||||||
private Integer type;
|
private Integer type;
|
||||||
private Integer studentId;
|
private Integer studentId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.login;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class LoginReqVO {
|
||||||
|
|
||||||
|
private String phone;
|
||||||
|
private String name;
|
||||||
|
private String password;
|
||||||
|
private String code;
|
||||||
|
private String invitationCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.login;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class VerificationCodeReqVO {
|
||||||
|
|
||||||
|
private String phone;
|
||||||
|
}
|
||||||
@@ -13,4 +13,5 @@ public class AddLessonPlanReqVO {
|
|||||||
|
|
||||||
private Integer studentId;
|
private Integer studentId;
|
||||||
private Integer unitId;
|
private Integer unitId;
|
||||||
|
private Integer wordSize;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindIsGeneratePlanReqVO {
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindPlanStudentListRspVO {
|
||||||
|
|
||||||
|
List<LessonPlanItem> lessonPlanItems;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindPlanStudentReqVO {
|
||||||
|
private Integer studentId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindWordTTSVoiceReqVO {
|
||||||
|
|
||||||
|
String text;
|
||||||
|
String voice;
|
||||||
|
String format;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindWordVoiceReqVO {
|
||||||
|
|
||||||
|
private Integer planId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.plan;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindWordVoiceRspVO {
|
||||||
|
|
||||||
|
private List<String> words;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.yinlihupo.enlish.service.model.vo.student;
|
package com.yinlihupo.enlish.service.model.vo.student;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
@@ -16,5 +17,6 @@ public class AddStudentReqVO {
|
|||||||
private String name;
|
private String name;
|
||||||
private Integer classId;
|
private Integer classId;
|
||||||
private Integer gradeId;
|
private Integer gradeId;
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.student;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class AnalyzeStudentStudyReqVO {
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.student;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindStudentMasteryDetailReqVO {
|
||||||
|
|
||||||
|
private Integer studentId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.student;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindStudentMasteryDetailRspVO {
|
||||||
|
private String word;
|
||||||
|
|
||||||
|
private Integer reviewCount;
|
||||||
|
|
||||||
|
private Double memoryStrength;
|
||||||
|
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -15,4 +15,6 @@ public class StudentItemRspVO {
|
|||||||
private String name;
|
private String name;
|
||||||
private Integer classId;
|
private Integer classId;
|
||||||
private Integer gradeId;
|
private Integer gradeId;
|
||||||
|
private String className;
|
||||||
|
private String gradeName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.user;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class CreateUserReqVO {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private String phone;
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.user;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindUserInfoRspVO {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.user;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindUserListRepVO {
|
||||||
|
|
||||||
|
private Integer page;
|
||||||
|
private Integer pageSize;
|
||||||
|
private String name;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.user;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindUserListRspVO {
|
||||||
|
private Long id;
|
||||||
|
private String name;
|
||||||
|
private String phone;
|
||||||
|
private String roleName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.user;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class UpdateUserInfoReqVO {
|
||||||
|
|
||||||
|
private String newPassword;
|
||||||
|
private String name;
|
||||||
|
private String phone;
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.vocabulary;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class FindStudentWordDetailReqVO {
|
||||||
|
|
||||||
|
private Integer id;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.yinlihupo.enlish.service.model.vo.vocabulary;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class FindStudentWordDetailRspVO {
|
||||||
|
/**
|
||||||
|
* 已掌握单词数
|
||||||
|
*/
|
||||||
|
private Integer masteredWordCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未掌握单词数
|
||||||
|
*/
|
||||||
|
private Integer unmasteredWordCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待审查单词数(推荐使用,简洁通用)
|
||||||
|
*/
|
||||||
|
private Integer pendingReviewWordCount;
|
||||||
|
}
|
||||||
@@ -9,10 +9,12 @@ public interface ExamWordsJudgeService {
|
|||||||
|
|
||||||
void judgeExamWords(int count);
|
void judgeExamWords(int count);
|
||||||
|
|
||||||
List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize);
|
List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize, Integer classId, Integer gradeId, String studentName);
|
||||||
|
|
||||||
Integer getExamWordsJudgeResultCount();
|
Integer getExamWordsJudgeResultCount();
|
||||||
|
|
||||||
|
Integer getExamUnfinishedCount();
|
||||||
|
|
||||||
ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id);
|
ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id);
|
||||||
|
|
||||||
List<ExamWordsJudgeResultDO> getStudentExamWordsResultList(Integer studentId);
|
List<ExamWordsJudgeResultDO> getStudentExamWordsResultList(Integer studentId);
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface ExamWordsService {
|
public interface ExamWordsService {
|
||||||
|
|
||||||
ExamWordsDO generateExamWords(Integer gradeId, Integer level, Integer studentId, Integer type);
|
ExamWordsDO generateExamWords(Integer studentId, Integer type);
|
||||||
|
|
||||||
int saveExamWordsPngToDbAndLocal(MultipartFile file);
|
int saveExamWordsPngToDbAndLocal(MultipartFile file);
|
||||||
|
|
||||||
|
void updateExamWordsWordIdsOrder(ExamWordsDO examWordsDO);
|
||||||
|
|
||||||
|
ExamWordsDO getExamWordsDOById(Integer id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface LessonPlansService {
|
public interface LessonPlansService {
|
||||||
void generateLessonPlans(Integer studentId, Integer unitId);
|
void generateLessonPlans(Integer studentId, Integer unitId, Integer wordSize);
|
||||||
|
|
||||||
List<LessonPlansDO> findLessonPlans(List<Integer> ids);
|
List<LessonPlansDO> findLessonPlans(List<Integer> ids);
|
||||||
|
|
||||||
LessonPlansDO findLessonPlanById(Integer id);
|
LessonPlansDO findLessonPlanById(Integer id);
|
||||||
|
|
||||||
|
List<LessonPlansDO> findLessonPlansByStudentId(Integer studentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service;
|
||||||
|
|
||||||
|
public interface LoginService {
|
||||||
|
|
||||||
|
void login(String phone, String name, String reqPassword, String reqCode, String invitationCode);
|
||||||
|
|
||||||
|
void sendVerificationCode(String phone);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.RoleDO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface RoleService {
|
||||||
|
|
||||||
|
void pushRolePermission2Redis();
|
||||||
|
|
||||||
|
Map<Long, RoleDO> findUserId2RoleMap(List<Long> userIds);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.yinlihupo.enlish.service.service;
|
|||||||
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
||||||
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
|
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.exam.WordMasteryDetail;
|
||||||
import com.yinlihupo.enlish.service.model.vo.student.AddStudentReqVO;
|
import com.yinlihupo.enlish.service.model.vo.student.AddStudentReqVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -20,4 +21,8 @@ public interface StudentService {
|
|||||||
void addStudent(AddStudentReqVO addStudentReqVO);
|
void addStudent(AddStudentReqVO addStudentReqVO);
|
||||||
|
|
||||||
void deleteStudent(Integer studentId);
|
void deleteStudent(Integer studentId);
|
||||||
|
|
||||||
|
String analyzeStudentStudy(Integer studentId);
|
||||||
|
|
||||||
|
List<WordMasteryDetail> findStudentWordMasteryDetail(Integer studentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UserService {
|
||||||
|
|
||||||
|
UserDO findUser();
|
||||||
|
|
||||||
|
List<UserDO> findUsersList(int page, int limit, String name);
|
||||||
|
|
||||||
|
Integer findUserTotal();
|
||||||
|
|
||||||
|
void createUser(UserDO userDO);
|
||||||
|
|
||||||
|
void updateUserInfo(String password, String reqCode, String phone, String name);
|
||||||
|
}
|
||||||
@@ -2,10 +2,13 @@ package com.yinlihupo.enlish.service.service;
|
|||||||
|
|
||||||
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindStudentWordDetailRspVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface VocabularyService {
|
public interface VocabularyService {
|
||||||
|
|
||||||
List<VocabularyBankDO> findVocabularyBankDOListById(List<Integer> ids);
|
List<VocabularyBankDO> findVocabularyBankDOListById(List<Integer> ids);
|
||||||
|
|
||||||
|
FindStudentWordDetailRspVO findStudentWordDetail(Integer studentId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,31 @@
|
|||||||
package com.yinlihupo.enlish.service.service.exam;
|
package com.yinlihupo.enlish.service.service.exam;
|
||||||
|
|
||||||
import com.yinlihupo.enlish.service.constant.ExamWordsConstant;
|
import com.yinlihupo.enlish.service.constant.ExamWordsConstant;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.*;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsJudgeResultDO;
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.*;
|
import com.yinlihupo.enlish.service.domain.mapper.*;
|
||||||
import com.yinlihupo.enlish.service.service.ExamWordsService;
|
import com.yinlihupo.enlish.service.service.ExamWordsService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ExamWordsServiceImpl implements ExamWordsService {
|
public class ExamWordsServiceImpl implements ExamWordsService {
|
||||||
|
|
||||||
@Resource
|
|
||||||
private GradeUnitDOMapper gradeUnitDOMapper;
|
|
||||||
@Resource
|
@Resource
|
||||||
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
||||||
@Resource
|
@Resource
|
||||||
@@ -38,6 +36,8 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
|||||||
private ExamWordsJudgeResultDOMapper examWordsJudgeResultDOMapper;
|
private ExamWordsJudgeResultDOMapper examWordsJudgeResultDOMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private StudentDOMapper studentDOMapper;
|
private StudentDOMapper studentDOMapper;
|
||||||
|
@Resource
|
||||||
|
private UnitDOMapper unitDOMapper;
|
||||||
|
|
||||||
@Value("${templates.count}")
|
@Value("${templates.count}")
|
||||||
private Integer wordCount;
|
private Integer wordCount;
|
||||||
@@ -46,17 +46,29 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = RuntimeException.class)
|
@Transactional(rollbackFor = RuntimeException.class)
|
||||||
public ExamWordsDO generateExamWords(Integer gradeId, Integer level, Integer studentId, Integer type) {
|
public ExamWordsDO generateExamWords(Integer studentId, Integer type) {
|
||||||
|
|
||||||
|
|
||||||
ExamWordsDO examWordsDO;
|
ExamWordsDO examWordsDO;
|
||||||
|
|
||||||
if (type == ExamWordsConstant.EXAM_TYPE_BASELINE) {
|
if (type == ExamWordsConstant.EXAM_TYPE_BASELINE) {
|
||||||
log.info("生成摸底测试");
|
log.info("生成摸底测试");
|
||||||
examWordsDO = generateBaselineExamWords(studentId);
|
examWordsDO = generateBaselineExamWords(studentId);
|
||||||
} else {
|
} else if (type == ExamWordsConstant.EXAM_TYPE_MIDTERM) {
|
||||||
// todo 生成期中考试待实现
|
log.info("生成期中测试");
|
||||||
examWordsDO = null;
|
examWordsDO = generateMidtermExamWords(studentId);
|
||||||
|
} else {
|
||||||
|
log.info("生成期末测试");
|
||||||
|
examWordsDO = generateFinalExamWords(studentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Integer> wordIds = new ArrayList<>(examWordsDO.getWordIds());
|
||||||
|
if (wordIds.size() < wordCount) {
|
||||||
|
log.info("单词数量不足,补充单词");
|
||||||
|
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||||
|
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectByGradeIdAndNotMatchIds(studentDO.getGradeId(), wordIds, wordCount - wordIds.size());
|
||||||
|
List<Integer> list = new ArrayList<>(vocabularyBankDOS.stream().map(VocabularyBankDO::getId).toList());
|
||||||
|
wordIds.addAll(list);
|
||||||
|
examWordsDO.setWordIds(wordIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
return examWordsDO;
|
return examWordsDO;
|
||||||
@@ -95,11 +107,54 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
|||||||
.gradeId(gradeId)
|
.gradeId(gradeId)
|
||||||
.level(1)
|
.level(1)
|
||||||
.type(ExamWordsConstant.EXAM_TYPE_BASELINE)
|
.type(ExamWordsConstant.EXAM_TYPE_BASELINE)
|
||||||
.title("摸低测试测试" + studentDO.getName())
|
.title("摸低测试" + studentDO.getName())
|
||||||
.createdAt(LocalDateTime.now())
|
.createdAt(LocalDateTime.now())
|
||||||
.wordIds(vocabularyBankDOS.stream().map(VocabularyBankDO::getId).toList())
|
.wordIds(vocabularyBankDOS.stream().map(VocabularyBankDO::getId).toList())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
return getExamWordsDO(studentId, examWordsDO);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExamWordsDO generateMidtermExamWords(Integer studentId) {
|
||||||
|
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||||
|
Integer gradeId = studentDO.getGradeId();
|
||||||
|
List<UnitDO> unitDOS = unitDOMapper.selectByUnitName(ExamWordsConstant.getGradeName(gradeId) + "上");
|
||||||
|
ExamWordsDO examWordsDO = getExamWordsDO(studentId, studentDO, gradeId, unitDOS, ExamWordsConstant.EXAM_TYPE_MIDTERM);
|
||||||
|
examWordsDO.setTitle("期中测试" + studentDO.getName());
|
||||||
|
return getExamWordsDO(studentId, examWordsDO);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExamWordsDO generateFinalExamWords(Integer studentId) {
|
||||||
|
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||||
|
Integer gradeId = studentDO.getGradeId();
|
||||||
|
List<UnitDO> unitDOS = unitDOMapper.selectByUnitName(ExamWordsConstant.getGradeName(gradeId));
|
||||||
|
ExamWordsDO examWordsDO = getExamWordsDO(studentId, studentDO, gradeId, unitDOS, ExamWordsConstant.EXAM_TYPE_FINAL);
|
||||||
|
examWordsDO.setTitle("期末测试" + studentDO.getName());
|
||||||
|
return getExamWordsDO(studentId, examWordsDO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ExamWordsDO getExamWordsDO(Integer studentId, StudentDO studentDO, Integer gradeId, List<UnitDO> unitDOS, Integer type) {
|
||||||
|
if (unitDOS.isEmpty()) {
|
||||||
|
throw new RuntimeException("没有找到对应的单元");
|
||||||
|
}
|
||||||
|
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectByUnitIds(unitDOS.stream().map(UnitDO::getId).toList());
|
||||||
|
|
||||||
|
ExamWordsDO examWordsDO = ExamWordsDO.builder()
|
||||||
|
.gradeId(gradeId)
|
||||||
|
.level(1)
|
||||||
|
.type(type)
|
||||||
|
.title(studentDO.getName())
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.wordIds(vocabularyBankDOS.stream().map(VocabularyBankDO::getId).toList())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return getExamWordsDO(studentId, examWordsDO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ExamWordsDO getExamWordsDO(Integer studentId, ExamWordsDO examWordsDO) {
|
||||||
int insert = examWordsDOMapper.insert(examWordsDO);
|
int insert = examWordsDOMapper.insert(examWordsDO);
|
||||||
if (insert <= 0) {
|
if (insert <= 0) {
|
||||||
throw new RuntimeException("插入考试失败");
|
throw new RuntimeException("插入考试失败");
|
||||||
@@ -116,29 +171,65 @@ public class ExamWordsServiceImpl implements ExamWordsService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = RuntimeException.class)
|
@Transactional(rollbackFor = RuntimeException.class)
|
||||||
public int saveExamWordsPngToDbAndLocal(MultipartFile file) {
|
public int saveExamWordsPngToDbAndLocal(MultipartFile file) {
|
||||||
|
// 1. 基础校验:判空
|
||||||
File dir = new File(tmpPng);
|
if (file == null || file.isEmpty()) {
|
||||||
if (!dir.exists()) {
|
throw new RuntimeException("上传文件不能为空");
|
||||||
dir.mkdirs();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. 安全校验:检查后缀名白名单
|
||||||
|
String originalFilename = file.getOriginalFilename();
|
||||||
|
String extension = StringUtils.getFilenameExtension(originalFilename); // Spring工具类
|
||||||
|
List<String> allowedExtensions = Arrays.asList("png", "jpg", "jpeg");
|
||||||
|
|
||||||
|
if (extension == null || !allowedExtensions.contains(extension.toLowerCase())) {
|
||||||
|
throw new RuntimeException("不支持的文件格式,仅支持: " + allowedExtensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 准备目录 (使用 NIO)
|
||||||
|
// 假设 tmpPng 是配置好的基础路径字符串
|
||||||
|
Path directoryPath = Paths.get(tmpPng);
|
||||||
try {
|
try {
|
||||||
String originalFilename = file.getOriginalFilename();
|
if (!Files.exists(directoryPath)) {
|
||||||
String suffix = "";
|
Files.createDirectories(directoryPath);
|
||||||
if (originalFilename != null && originalFilename.contains(".")) {
|
|
||||||
suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
|
|
||||||
}
|
}
|
||||||
String newFileName = UUID.randomUUID() + suffix;
|
|
||||||
String path = tmpPng + newFileName;
|
|
||||||
|
|
||||||
File dest = new File(path);
|
// 4. 生成文件名 (防止文件名冲突)
|
||||||
file.transferTo(dest);
|
String newFileName = UUID.randomUUID().toString() + "." + extension;
|
||||||
|
|
||||||
|
// 5. 组合最终路径 (自动处理分隔符)
|
||||||
|
Path targetPath = directoryPath.resolve(newFileName);
|
||||||
|
|
||||||
|
// 6. 保存文件
|
||||||
|
file.transferTo(targetPath.toAbsolutePath().toFile());
|
||||||
|
String string = targetPath.toAbsolutePath().toFile().toString();
|
||||||
|
log.info("文件上传成功路径为 {}", string);
|
||||||
|
if (!targetPath.toFile().exists()) {
|
||||||
|
log.error("文件上传失败: {}", newFileName);
|
||||||
|
throw new RuntimeException("文件上传失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 入库
|
||||||
|
// 建议:存相对路径或文件名,不要存 targetPath.toString() 的绝对路径
|
||||||
|
// 这里为了演示,假设 insert 依然接受字符串,建议存 newFileName
|
||||||
|
int insert = examWordsJudgeResultDOMapper.insert(targetPath.toString());
|
||||||
|
|
||||||
|
log.info("上传文件成功: {}", newFileName);
|
||||||
|
return insert;
|
||||||
|
|
||||||
return examWordsJudgeResultDOMapper.insert(path);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("上传失败", e);
|
log.error("文件上传失败: {}", originalFilename, e);
|
||||||
|
throw new RuntimeException("上传失败,请稍后重试", e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateExamWordsWordIdsOrder(ExamWordsDO examWordsDO) {
|
||||||
|
examWordsDOMapper.updateWordIdsOrder(examWordsDO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExamWordsDO getExamWordsDOById(Integer id) {
|
||||||
|
return examWordsDOMapper.selectById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.yinlihupo.enlish.service.model.bo.exam.ActionType;
|
|||||||
import com.yinlihupo.enlish.service.model.bo.exam.DiagnosisResult;
|
import com.yinlihupo.enlish.service.model.bo.exam.DiagnosisResult;
|
||||||
import com.yinlihupo.enlish.service.model.bo.exam.ZoneStats;
|
import com.yinlihupo.enlish.service.model.bo.exam.ZoneStats;
|
||||||
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
|
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
|
||||||
|
import com.yinlihupo.enlish.service.service.StudentLessonPlansService;
|
||||||
import com.yinlihupo.enlish.service.utils.PngUtil;
|
import com.yinlihupo.enlish.service.utils.PngUtil;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -40,23 +41,29 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
private GradeUnitDOMapper gradeUnitDOMapper;
|
private GradeUnitDOMapper gradeUnitDOMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private StudentDOMapper studentDOMapper;
|
private StudentDOMapper studentDOMapper;
|
||||||
|
@Resource
|
||||||
|
private PlanExamDOMapper planExamDOMapper;
|
||||||
|
@Resource
|
||||||
|
private StudentLessonPlansService studentLessonPlansService;
|
||||||
|
|
||||||
@Value("${templates.data}")
|
@Value("${templates.data}")
|
||||||
private String tessdataPath;
|
private String tessdataPath;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void judgeExamWords(int count) {
|
public void judgeExamWords(int count) {
|
||||||
List<ExamWordsJudgeResultDO> examWordsJudgeResultDOS = examWordsJudgeResultDOMapper.selectUnfinishedExamWordsJudgeResultDOList(count);
|
List<ExamWordsJudgeResultDO> examWordsJudgeResultDOS = examWordsJudgeResultDOMapper.selectUnfinishedExamWordsJudgeResultDOList(count);
|
||||||
for (ExamWordsJudgeResultDO examWordsJudgeResultDO : examWordsJudgeResultDOS) {
|
for (ExamWordsJudgeResultDO examWordsJudgeResultDO : examWordsJudgeResultDOS) {
|
||||||
|
String ansSheetPath = null;
|
||||||
try {
|
try {
|
||||||
String ansSheetPath = examWordsJudgeResultDO.getAnsSheetPath();
|
ansSheetPath = examWordsJudgeResultDO.getAnsSheetPath();
|
||||||
List<CoordinatesXY> coordinatesXIES = PngUtil.analysisXY(ansSheetPath);
|
List<CoordinatesXY> coordinatesXIES = PngUtil.analysisXY(ansSheetPath);
|
||||||
|
|
||||||
// 从图片中获取学生 id 和考试 id
|
// 从图片中获取学生 id 和考试 id
|
||||||
StudentExamId studentExamId = PngUtil.analyzeExamWordsIdAndStudentId(ansSheetPath, tessdataPath, coordinatesXIES);
|
StudentExamId studentExamId = PngUtil.analyzeExamWordsIdAndStudentId(ansSheetPath, tessdataPath, coordinatesXIES);
|
||||||
Integer examWordsJudgeResultDOId = examWordsJudgeResultDO.getId();
|
Integer examWordsJudgeResultDOId = examWordsJudgeResultDO.getId();
|
||||||
if (studentExamId == null) {
|
if (studentExamId == null) {
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未识别学生 id 和考试 id");
|
log.info("未找到学生 id 和考试 id");
|
||||||
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未识别学生和考试");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +81,7 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ExamWordsDO examWordsDO = examWordsDOMapper.selectById(examWordsId);
|
ExamWordsDO examWordsDO = examWordsDOMapper.selectById(examWordsId);
|
||||||
if(examWordsDO == null) {
|
if (examWordsDO == null) {
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未找到考试");
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "未找到考试");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -93,9 +100,13 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
.wrongWordCount(unmemorizedWordIds.size())
|
.wrongWordCount(unmemorizedWordIds.size())
|
||||||
.isFinished(1)
|
.isFinished(1)
|
||||||
.build();
|
.build();
|
||||||
// 判断考试等级
|
|
||||||
judgeExamActualGrade(wordsJudgeResultDO, examWordsDO);
|
|
||||||
|
|
||||||
|
if (examWordsDO.getType().equals(ExamWordsConstant.EXAM_TYPE_BASELINE)) {
|
||||||
|
// 判断考试等级
|
||||||
|
judgeExamActualGrade(wordsJudgeResultDO, examWordsDO);
|
||||||
|
} else {
|
||||||
|
wordsJudgeResultDO.setMsg("此次考试" + examWordsDO.getTitle() + "答对单词数为" + memorizedWordIds.size());
|
||||||
|
}
|
||||||
int updated = examWordsJudgeResultDOMapper.updateExamWordsJudgeResultDO(wordsJudgeResultDO);
|
int updated = examWordsJudgeResultDOMapper.updateExamWordsJudgeResultDO(wordsJudgeResultDO);
|
||||||
if (updated != 1) {
|
if (updated != 1) {
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新考试记录失败");
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDOId, "更新考试记录失败");
|
||||||
@@ -103,6 +114,12 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
}
|
}
|
||||||
log.info("更新考试记录成功");
|
log.info("更新考试记录成功");
|
||||||
|
|
||||||
|
PlanExamDO planExamDO = planExamDOMapper.selectByExamId(examWordsId);
|
||||||
|
if (planExamDO != null) {
|
||||||
|
studentLessonPlansService.finishStudentLessonPlan(studentId, planExamDO.getPlanId());
|
||||||
|
log.info("完成学案成功, planId: {}", planExamDO.getPlanId());
|
||||||
|
}
|
||||||
|
|
||||||
List<WordMasteryLogDO> wordMasteryLogDOS = new ArrayList<>(unmemorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
|
List<WordMasteryLogDO> wordMasteryLogDOS = new ArrayList<>(unmemorizedWordIds.stream().map(wordId -> WordMasteryLogDO.builder()
|
||||||
.wordId(wordId)
|
.wordId(wordId)
|
||||||
.studentId(studentId)
|
.studentId(studentId)
|
||||||
@@ -131,9 +148,14 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
boolean delete = new File(ansSheetPath).delete();
|
boolean delete = new File(ansSheetPath).delete();
|
||||||
if (delete) {
|
if (delete) {
|
||||||
log.info("删除文件成功:{}", ansSheetPath);
|
log.info("删除文件成功:{}", ansSheetPath);
|
||||||
|
} else {
|
||||||
|
log.error("删除文件失败:{}", ansSheetPath);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("识别考试失败 {}", e.getMessage());
|
log.error("识别考试失败 {}", e.getMessage());
|
||||||
|
if (ansSheetPath != null) {
|
||||||
|
new File(ansSheetPath).delete();
|
||||||
|
}
|
||||||
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDO.getId(), e.getMessage());
|
examWordsJudgeResultDOMapper.updateMsg(examWordsJudgeResultDO.getId(), e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,9 +276,9 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
boolean canUpgrade = zoneB.getAccuracy() >= 0.8 && (zoneA.getTotalCount() == 0 || zoneA.getAccuracy() >= 0.6);
|
boolean canUpgrade = zoneB.getAccuracy() >= 0.8 && (zoneA.getTotalCount() == 0 || zoneA.getAccuracy() >= 0.6);
|
||||||
if (canUpgrade) {
|
if (canUpgrade) {
|
||||||
return new DiagnosisResult(
|
return new DiagnosisResult(
|
||||||
anchorGrade + 1,
|
Math.max(anchorGrade + 1, ExamWordsConstant.GRADE_8),
|
||||||
ActionType.UPGRADE,
|
ActionType.UPGRADE,
|
||||||
"恭喜!您对" + ExamWordsConstant.getGradeName(anchorGrade) + " 的掌握非常扎实,且具备挑战" + ExamWordsConstant.getGradeName(anchorGrade + 1) + " 的潜力。系统将为您解锁更高阶词库。"
|
"恭喜!您对" + ExamWordsConstant.getGradeName(anchorGrade) + " 的掌握非常扎实,且具备挑战" + ExamWordsConstant.getGradeName(Math.max(anchorGrade + 1, ExamWordsConstant.GRADE_8)) + " 的潜力。系统将为您解锁更高阶词库。"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +299,7 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
} else if (zoneC.getAccuracy() >= 0.6) {
|
} else if (zoneC.getAccuracy() >= 0.6) {
|
||||||
// Zone C 勉强及格,Zone B 不及格 -> 降一级
|
// Zone C 勉强及格,Zone B 不及格 -> 降一级
|
||||||
return new DiagnosisResult(
|
return new DiagnosisResult(
|
||||||
anchorGrade - 1,
|
Math.max(anchorGrade - 1, ExamWordsConstant.GRADE_1),
|
||||||
ActionType.DOWNGRADE,
|
ActionType.DOWNGRADE,
|
||||||
"检测到" + ExamWordsConstant.getGradeName(anchorGrade) + " 学习吃力,且" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 存在模糊点。建议降级回溯,巩固 " + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + "。"
|
"检测到" + ExamWordsConstant.getGradeName(anchorGrade) + " 学习吃力,且" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 存在模糊点。建议降级回溯,巩固 " + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + "。"
|
||||||
);
|
);
|
||||||
@@ -286,14 +308,14 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
// 检查 Zone D
|
// 检查 Zone D
|
||||||
if (zoneD.getTotalCount() > 0 && zoneD.getAccuracy() >= 0.6) {
|
if (zoneD.getTotalCount() > 0 && zoneD.getAccuracy() >= 0.6) {
|
||||||
return new DiagnosisResult(
|
return new DiagnosisResult(
|
||||||
anchorGrade - 2,
|
Math.max(anchorGrade - 2, ExamWordsConstant.GRADE_1),
|
||||||
ActionType.DOWNGRADE,
|
ActionType.DOWNGRADE,
|
||||||
"您的" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 和 " + ExamWordsConstant.getGradeName(anchorGrade) + " 均存在脱节。建议从 " + ExamWordsConstant.getGradeName(zoneD.getGradeId()) + " 开始系统补漏。"
|
"您的" + ExamWordsConstant.getGradeName(zoneC.getGradeId()) + " 和 " + ExamWordsConstant.getGradeName(anchorGrade) + " 均存在脱节。建议从 " + ExamWordsConstant.getGradeName(zoneD.getGradeId()) + " 开始系统补漏。"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// 彻底崩盘,可能需要熔断,或者定级到更低
|
// 彻底崩盘,可能需要熔断,或者定级到更低
|
||||||
return new DiagnosisResult(
|
return new DiagnosisResult(
|
||||||
Math.max(1, anchorGrade - 3),
|
Math.max(ExamWordsConstant.GRADE_1, anchorGrade - 3),
|
||||||
ActionType.DOWNGRADE,
|
ActionType.DOWNGRADE,
|
||||||
"基础薄弱,建议暂停当前进度,从低年级核心高频词重新开始。"
|
"基础薄弱,建议暂停当前进度,从低年级核心高频词重新开始。"
|
||||||
);
|
);
|
||||||
@@ -310,8 +332,24 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize) {
|
public List<ExamWordsJudgeResultDO> getExamWordsJudgeResult(Integer page, Integer pageSize, Integer classId, Integer gradeId, String studentName) {
|
||||||
return examWordsJudgeResultDOMapper.selectByPage((page - 1) * pageSize, pageSize);
|
|
||||||
|
List<Integer> studentIds = new ArrayList<>();
|
||||||
|
if (classId != null) {
|
||||||
|
studentIds.addAll(studentDOMapper.selectStudentDOListByClassId(classId).stream().map(StudentDO::getId).toList());
|
||||||
|
}
|
||||||
|
if (gradeId != null) {
|
||||||
|
studentIds.addAll(studentDOMapper.selectStudentDOListByGradeId(gradeId).stream().map(StudentDO::getId).toList());
|
||||||
|
}
|
||||||
|
if (!studentName.isEmpty()) {
|
||||||
|
studentIds.addAll(studentDOMapper.selectStudentDOListByName(studentName).stream().map(StudentDO::getId).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (studentIds.isEmpty()) {
|
||||||
|
return examWordsJudgeResultDOMapper.selectByPage((page - 1) * pageSize, pageSize);
|
||||||
|
} else {
|
||||||
|
return examWordsJudgeResultDOMapper.selectByPageAndStudentIds((page - 1) * pageSize, page * pageSize, studentIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -319,6 +357,11 @@ public class ExamWordsJudgeServiceImpl implements ExamWordsJudgeService {
|
|||||||
return examWordsJudgeResultDOMapper.selectCount();
|
return examWordsJudgeResultDOMapper.selectCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getExamUnfinishedCount() {
|
||||||
|
return examWordsJudgeResultDOMapper.selectUnfinishedCount();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id) {
|
public ExamWordsJudgeResultDO getExamWordsJudgeResultDOById(Integer id) {
|
||||||
return examWordsJudgeResultDOMapper.selectDetailById(id);
|
return examWordsJudgeResultDOMapper.selectDetailById(id);
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service.login;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import com.aliyun.dysmsapi20170525.Client;
|
||||||
|
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
|
||||||
|
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
||||||
|
import com.aliyun.teautil.models.RuntimeOptions;
|
||||||
|
import com.yinlihupo.enlish.service.constant.UserRedisConstants;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.UserDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.service.LoginService;
|
||||||
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class LoginServiceImpl implements LoginService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserDOMapper userDOMapper;
|
||||||
|
@Resource
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private Client client;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void login(String phone, String name, String reqPassword, String reqCode, String invitationCode) {
|
||||||
|
UserDO userDO = userDOMapper.selectByPhone(phone);
|
||||||
|
log.info("userDO:{}", userDO);
|
||||||
|
|
||||||
|
String code = JsonUtils.toJsonString(redisTemplate.opsForValue().get(UserRedisConstants.buildUserLoginCode(phone)));
|
||||||
|
if (userDO == null) {
|
||||||
|
if (code == null || !code.equals(reqCode)) {
|
||||||
|
throw new RuntimeException("验证码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
Object invitationObj = redisTemplate.opsForValue().get(UserRedisConstants.buildUserInvitationCode(invitationCode));
|
||||||
|
if (invitationObj == null) {
|
||||||
|
throw new RuntimeException("邀请码错误");
|
||||||
|
}
|
||||||
|
int invitationLimit = Integer.parseInt(JsonUtils.toJsonString(invitationObj));
|
||||||
|
if (invitationLimit <= 0) {
|
||||||
|
throw new RuntimeException("邀请码已使用完毕");
|
||||||
|
}
|
||||||
|
redisTemplate.opsForValue().set(UserRedisConstants.buildUserInvitationCode(invitationCode), invitationLimit - 1);
|
||||||
|
|
||||||
|
userDO = UserDO.builder()
|
||||||
|
.phone(phone)
|
||||||
|
.name(name)
|
||||||
|
.password(passwordEncoder.encode(reqPassword))
|
||||||
|
.build();
|
||||||
|
userDOMapper.insert(userDO);
|
||||||
|
StpUtil.login(userDO.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code != null && code.equals(reqCode)) {
|
||||||
|
StpUtil.login(userDO.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reqPassword != null && passwordEncoder.matches(reqPassword, userDO.getPassword())) {
|
||||||
|
StpUtil.login(userDO.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("登录错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendVerificationCode(String phone) {
|
||||||
|
String code = RandomUtil.randomNumbers(6);
|
||||||
|
String key = UserRedisConstants.buildUserLoginCode(phone);
|
||||||
|
redisTemplate.opsForValue().set(key, code);
|
||||||
|
redisTemplate.expire(key, 5, TimeUnit.MINUTES);
|
||||||
|
String signName = "短信测试";
|
||||||
|
String templateCode = "SMS_154950909";
|
||||||
|
String templateParam = String.format("{\"code\":\"%s\"}", code);
|
||||||
|
try {
|
||||||
|
sendMessage(phone, signName, templateCode, templateParam);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("==> 短信发送失败, phone: {}, signName: {}, templateCode: {}, templateParam: {}", phone, signName, templateCode, templateParam);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送短信
|
||||||
|
*/
|
||||||
|
public void sendMessage(String signName, String templateCode, String phone, String templateParam) throws Exception {
|
||||||
|
SendSmsRequest sendSmsRequest = new SendSmsRequest()
|
||||||
|
.setSignName(signName)
|
||||||
|
.setTemplateCode(templateCode)
|
||||||
|
.setPhoneNumbers(phone)
|
||||||
|
.setTemplateParam(templateParam);
|
||||||
|
RuntimeOptions runtime = new RuntimeOptions();
|
||||||
|
|
||||||
|
|
||||||
|
log.info("==> 开始短信发送, phone: {}, signName: {}, templateCode: {}, templateParam: {}", phone, signName, templateCode, templateParam);
|
||||||
|
|
||||||
|
// 发送短信
|
||||||
|
SendSmsResponse response = client.sendSmsWithOptions(sendSmsRequest, runtime);
|
||||||
|
|
||||||
|
log.info("==> 短信发送成功, response: {}", JsonUtils.toJsonString(response));
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,26 @@
|
|||||||
package com.yinlihupo.enlish.service.service.plan;
|
package com.yinlihupo.enlish.service.service.plan;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.constant.ExamWordsConstant;
|
||||||
import com.yinlihupo.enlish.service.constant.LessonPlanConstant;
|
import com.yinlihupo.enlish.service.constant.LessonPlanConstant;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.*;
|
import com.yinlihupo.enlish.service.domain.dataobject.*;
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.*;
|
import com.yinlihupo.enlish.service.domain.mapper.*;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.Sentence;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.Word;
|
||||||
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
import com.yinlihupo.enlish.service.service.LessonPlansService;
|
||||||
import com.yinlihupo.enlish.service.utils.DifyArticleClient;
|
import com.yinlihupo.enlish.service.utils.DifyClient;
|
||||||
import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil;
|
import com.yinlihupo.enlish.service.utils.StringToPlanMapUtil;
|
||||||
import com.yinlihupo.framework.common.util.JsonUtils;
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -35,22 +39,34 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
@Resource
|
@Resource
|
||||||
private GradeDOMapper gradeDOMapper;
|
private GradeDOMapper gradeDOMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private DifyArticleClient difyArticleClient;
|
private DifyClient difyClient;
|
||||||
|
@Resource
|
||||||
@Value("${templates.plan.weekday}")
|
private ExamWordsDOMapper examWordsDOMapper;
|
||||||
private String planWeekday;
|
@Resource
|
||||||
@Value("${templates.plan.weekend}")
|
private StudentExamWordsDOMapper studentExamWordsDOMapper;
|
||||||
private String planWeekend;
|
@Resource
|
||||||
|
private StudentDOMapper studentDOMapper;
|
||||||
|
@Resource
|
||||||
|
private PlanExamDOMapper planExamDOMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private ClassDOMapper classDOMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
public void generateLessonPlans(Integer studentId, Integer unitId, Integer wordSize) {
|
||||||
public void generateLessonPlans(Integer studentId, Integer unitId) {
|
String key = LessonPlanConstant.buildGeneratePlanContent(studentId);
|
||||||
|
redisTemplate.opsForValue().set(key, studentId);
|
||||||
|
redisTemplate.expire(key, 7, TimeUnit.MINUTES);
|
||||||
|
|
||||||
|
log.info("开始生成计划");
|
||||||
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOAllByUnitId(unitId);
|
List<VocabularyBankDO> vocabularyBankDOS = vocabularyBankDOMapper.selectVocabularyBankDOAllByUnitId(unitId);
|
||||||
UnitDO unitDO = unitDOMapper.selectByPrimaryKey(unitId);
|
UnitDO unitDO = unitDOMapper.selectByPrimaryKey(unitId);
|
||||||
GradeUnitDO gradeUnitDO = gradeUnitDOMapper.selectByUnitId(unitId);
|
GradeUnitDO gradeUnitDO = gradeUnitDOMapper.selectByUnitId(unitId);
|
||||||
GradeDO gradeDO = gradeDOMapper.selectById(gradeUnitDO.getGradeId());
|
GradeDO gradeDO = gradeDOMapper.selectById(gradeUnitDO.getGradeId());
|
||||||
|
|
||||||
|
List<VocabularyBankDO> totalWords = new ArrayList<>();
|
||||||
|
|
||||||
// 补差词汇所用词汇的
|
// 补差词汇所用词汇的
|
||||||
List<VocabularyBankDO> vocabularyBankListStudentNotMaster = getVocabListRandom(vocabularyBankDOMapper
|
List<VocabularyBankDO> vocabularyBankListStudentNotMaster = getVocabListRandom(vocabularyBankDOMapper
|
||||||
.selectVocabularyBankListStudentNotMaster(gradeUnitDO.getGradeId(), studentId), 50);
|
.selectVocabularyBankListStudentNotMaster(gradeUnitDO.getGradeId(), studentId), 50);
|
||||||
@@ -58,15 +74,25 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
int countGap = gapSize / 5;
|
int countGap = gapSize / 5;
|
||||||
|
|
||||||
int syncSize = vocabularyBankDOS.size();
|
int syncSize = vocabularyBankDOS.size();
|
||||||
int countSync = syncSize / 5;
|
wordSize = wordSize <= 0 ? syncSize / 5 : wordSize;
|
||||||
int checkTotal = 50;
|
int checkTotal = 50;
|
||||||
List<List<VocabularyBankDO>> weeksSync = new ArrayList<>();
|
List<List<VocabularyBankDO>> weeksSync = new ArrayList<>();
|
||||||
List<List<VocabularyBankDO>> weeksGap = new ArrayList<>();
|
List<List<VocabularyBankDO>> weeksGap = new ArrayList<>();
|
||||||
|
int j = 0;
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
List<VocabularyBankDO> syncVocabList = vocabularyBankDOS.subList(i * countSync, Math.min((i + 1) * countSync, syncSize));
|
List<VocabularyBankDO> syncVocabList;
|
||||||
|
if ((i + 1) * wordSize < syncSize) {
|
||||||
|
syncVocabList = vocabularyBankDOS.subList(i * wordSize, (i + 1) * wordSize);
|
||||||
|
} else {
|
||||||
|
syncVocabList = new ArrayList<>(weeksSync.get(Math.min(j++, weeksSync.size() - 1)));
|
||||||
|
}
|
||||||
List<VocabularyBankDO> gapVocabList = vocabularyBankListStudentNotMaster.subList(i * countGap, Math.min(i * countGap + countGap, gapSize));
|
List<VocabularyBankDO> gapVocabList = vocabularyBankListStudentNotMaster.subList(i * countGap, Math.min(i * countGap + countGap, gapSize));
|
||||||
weeksSync.add(syncVocabList);
|
weeksSync.add(syncVocabList);
|
||||||
weeksGap.add(gapVocabList);
|
weeksGap.add(gapVocabList);
|
||||||
|
|
||||||
|
totalWords.addAll(syncVocabList);
|
||||||
|
totalWords.addAll(gapVocabList);
|
||||||
|
|
||||||
List<VocabularyBankDO> reviewVocabList = new ArrayList<>();
|
List<VocabularyBankDO> reviewVocabList = new ArrayList<>();
|
||||||
List<VocabularyBankDO> checkList = new ArrayList<>();
|
List<VocabularyBankDO> checkList = new ArrayList<>();
|
||||||
// 艾宾浩斯遗忘曲线
|
// 艾宾浩斯遗忘曲线
|
||||||
@@ -116,21 +142,29 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
.build();
|
.build();
|
||||||
studentLessonPlansDOMapper.insert(studentLessonPlansDO);
|
studentLessonPlansDOMapper.insert(studentLessonPlansDO);
|
||||||
|
|
||||||
|
Integer examId = (Integer) lessonPlanMap.get("examId");
|
||||||
|
PlanExamDO planExamDO = PlanExamDO.builder()
|
||||||
|
.planId(lessonPlansDO.getId())
|
||||||
|
.examId(examId)
|
||||||
|
.build();
|
||||||
|
planExamDOMapper.insert(planExamDO);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
log.info("生成第{}天计划失败,失败原因 {}", i + 1, e.getMessage());
|
||||||
}
|
}
|
||||||
log.info("生成第{}天计划成功", i + 1);
|
log.info("生成第{}天计划成功", i + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
int syncWeekender = syncSize / 2;
|
log.info("开始生成周末计划");
|
||||||
|
int syncWeekendSize = totalWords.size() / 2;
|
||||||
for (int i = 0; i < 2; i++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
List<VocabularyBankDO> checkList = vocabularyBankDOS.subList(i * syncWeekender, Math.min((i + 1) * syncWeekender, syncSize));
|
List<VocabularyBankDO> checkList = totalWords.subList(i * syncWeekendSize, Math.min((i + 1) * syncWeekendSize, syncWeekendSize));
|
||||||
Map<String, Object> map = generateWeekendPlans(checkList, i + 6, gradeDO, unitDO, studentId);
|
Map<String, Object> map = generateWeekendPlans(checkList, i + 6, gradeDO, unitDO, studentId);
|
||||||
|
|
||||||
LessonPlansDO lessonPlansDO = LessonPlansDO.builder()
|
LessonPlansDO lessonPlansDO = LessonPlansDO.builder()
|
||||||
.title(map.get("title").toString())
|
.title(map.get("examStr").toString() + "复习")
|
||||||
.gradeId(gradeDO.getId().toString())
|
.gradeId(gradeDO.getId().toString())
|
||||||
.unitId(unitDO.getId())
|
.unitId(unitDO.getId())
|
||||||
.createdAt(LocalDateTime.now())
|
.createdAt(LocalDateTime.now())
|
||||||
@@ -138,6 +172,13 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
.build();
|
.build();
|
||||||
lessonPlansDOMapper.insert(lessonPlansDO);
|
lessonPlansDOMapper.insert(lessonPlansDO);
|
||||||
|
|
||||||
|
Integer examId = (Integer) map.get("examId");
|
||||||
|
PlanExamDO planExamDO = PlanExamDO.builder()
|
||||||
|
.planId(lessonPlansDO.getId())
|
||||||
|
.examId(examId)
|
||||||
|
.build();
|
||||||
|
planExamDOMapper.insert(planExamDO);
|
||||||
|
|
||||||
StudentLessonPlansDO studentLessonPlansDO = StudentLessonPlansDO.builder()
|
StudentLessonPlansDO studentLessonPlansDO = StudentLessonPlansDO.builder()
|
||||||
.studentId(studentId)
|
.studentId(studentId)
|
||||||
.planId(lessonPlansDO.getId())
|
.planId(lessonPlansDO.getId())
|
||||||
@@ -160,14 +201,43 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
return lessonPlansDOMapper.selectByLessonId(id);
|
return lessonPlansDOMapper.selectByLessonId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<LessonPlansDO> findLessonPlansByStudentId(Integer studentId) {
|
||||||
|
return lessonPlansDOMapper.selectByStudentId(studentId);
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> checkList,
|
|
||||||
|
private Map<String, Object> generateWeekendPlans(List<VocabularyBankDO> words,
|
||||||
int day,
|
int day,
|
||||||
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws IOException {
|
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws IOException {
|
||||||
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("title", "第" + day + "天" + "复习" + gradeDO.getTitle() + unitDO.getTitle() + studentId);
|
words.forEach(word -> word.setDefinition(word.getDefinition().length() > 5 ? word.getDefinition().substring(0, 5) : word.getDefinition()));
|
||||||
data.put("checkList", checkList);
|
List<Integer> wordIds = words.stream().map(VocabularyBankDO::getId).toList();
|
||||||
|
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||||
|
|
||||||
|
String ExamTitle = gradeDO.getTitle() + unitDO.getTitle() + "教案小测第" + ExamWordsConstant.day2Chinese(day) + "天" + studentDO.getName();
|
||||||
|
ExamWordsDO examWordsDO = ExamWordsDO.builder()
|
||||||
|
.gradeId(gradeDO.getId())
|
||||||
|
.level(1)
|
||||||
|
.wordIds(wordIds)
|
||||||
|
.type(ExamWordsConstant.EXAM_TYPE_TEST)
|
||||||
|
.title(ExamTitle)
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
examWordsDOMapper.insert(examWordsDO);
|
||||||
|
studentExamWordsDOMapper.insertStudentsExam(studentId, examWordsDO.getId());
|
||||||
|
|
||||||
|
ClassDO classDO = classDOMapper.selectClassDOById(studentDOMapper.selectStudentById(studentId).getClassId());
|
||||||
|
data.put("examId", examWordsDO.getId());
|
||||||
|
data.put("studentId", studentId);
|
||||||
|
data.put("studentStr", gradeDO.getTitle() + " " + classDO.getTitle() + " " + studentDO.getName());
|
||||||
|
data.put("examStr", ExamTitle);
|
||||||
|
|
||||||
|
List<VocabularyBankDO> words1 = words.subList(0, words.size() / 2);
|
||||||
|
List<VocabularyBankDO> words2 = words.subList(words.size() / 2, words.size());
|
||||||
|
data.put("words1", words1);
|
||||||
|
data.put("words2", words2);
|
||||||
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||||
// Configure config = Configure.builder()
|
// Configure config = Configure.builder()
|
||||||
// .bind("checkList", policy)
|
// .bind("checkList", policy)
|
||||||
@@ -186,7 +256,8 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
List<VocabularyBankDO> checkList,
|
List<VocabularyBankDO> checkList,
|
||||||
int day,
|
int day,
|
||||||
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws Exception {
|
GradeDO gradeDO, UnitDO unitDO, Integer studentId) throws Exception {
|
||||||
String title = gradeDO.getTitle() + " " + unitDO.getTitle() + " " + "第" + day + "天" + studentId;
|
|
||||||
|
String title = gradeDO.getTitle() + " " + unitDO.getTitle() + " " + "第" + day + "天";
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("title", title);
|
data.put("title", title);
|
||||||
data.put("syncVocabList", syncVocabList);
|
data.put("syncVocabList", syncVocabList);
|
||||||
@@ -194,15 +265,17 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
data.put("reviewVocabList", reviewVocabList);
|
data.put("reviewVocabList", reviewVocabList);
|
||||||
data.put("checkList", checkList);
|
data.put("checkList", checkList);
|
||||||
data.put("checkListAns", checkList);
|
data.put("checkListAns", checkList);
|
||||||
|
|
||||||
// 中译英
|
// 中译英
|
||||||
List<VocabularyBankDO> drillRound1 = new ArrayList<>(syncVocabList);
|
List<Word> list = syncVocabList.stream().map(vocabularyBankDO -> Word.builder().title(vocabularyBankDO.getWord()).definition(vocabularyBankDO.getDefinition()).build()).toList();
|
||||||
|
list.forEach(word -> word.setTitle(" "));
|
||||||
|
|
||||||
|
List<Word> drillRound1 = new ArrayList<>(list);
|
||||||
Collections.shuffle(drillRound1);
|
Collections.shuffle(drillRound1);
|
||||||
data.put("drillRound1", drillRound1);
|
data.put("drillRound1", drillRound1);
|
||||||
List<VocabularyBankDO> drillRound2 = new ArrayList<>(syncVocabList);
|
List<Word> drillRound2 = new ArrayList<>(list);
|
||||||
Collections.shuffle(drillRound2);
|
Collections.shuffle(drillRound2);
|
||||||
data.put("drillRound2", drillRound2);
|
data.put("drillRound2", drillRound2);
|
||||||
List<VocabularyBankDO> drillRound3 = new ArrayList<>(syncVocabList);
|
List<Word> drillRound3 = new ArrayList<>(list);
|
||||||
Collections.shuffle(drillRound3);
|
Collections.shuffle(drillRound3);
|
||||||
data.put("drillRound3", drillRound3);
|
data.put("drillRound3", drillRound3);
|
||||||
|
|
||||||
@@ -211,8 +284,10 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
mixedDrill.addAll(syncVocabList);
|
mixedDrill.addAll(syncVocabList);
|
||||||
mixedDrill.addAll(gapVocabList);
|
mixedDrill.addAll(gapVocabList);
|
||||||
mixedDrill.addAll(reviewVocabList);
|
mixedDrill.addAll(reviewVocabList);
|
||||||
Collections.shuffle(mixedDrill);
|
List<Word> mixedList = new ArrayList<>(mixedDrill.stream().map(vocabularyBankDO -> Word.builder().title(vocabularyBankDO.getWord()).definition(vocabularyBankDO.getDefinition()).build()).toList());
|
||||||
data.put("mixedDrill", mixedDrill);
|
mixedList.forEach(word -> word.setDefinition(" "));
|
||||||
|
Collections.shuffle(mixedList);
|
||||||
|
data.put("mixedDrill", mixedList);
|
||||||
|
|
||||||
// 文章 A
|
// 文章 A
|
||||||
log.info("生成文章 A 中文开始");
|
log.info("生成文章 A 中文开始");
|
||||||
@@ -239,6 +314,50 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
data.put("articleBans", mapB.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION));
|
data.put("articleBans", mapB.get(LessonPlanConstant.ANSWER_KEY_EXPLANATION));
|
||||||
data.put("articleBtran", mapB.get(LessonPlanConstant.FULL_TRANSLATION));
|
data.put("articleBtran", mapB.get(LessonPlanConstant.FULL_TRANSLATION));
|
||||||
|
|
||||||
|
// 连词成句
|
||||||
|
List<Sentence> sentences = difyClient.sendSentenceAnalyze(syncVocabList.subList(0, Math.max(10, syncVocabList.size())), gradeDO.getTitle());
|
||||||
|
data.put("sentences", sentences);
|
||||||
|
data.put("sentencesAns", sentences);
|
||||||
|
log.info( "生成连词成句成功");
|
||||||
|
|
||||||
|
|
||||||
|
// 教案小测
|
||||||
|
List<VocabularyBankDO> words = new ArrayList<>(syncVocabList);
|
||||||
|
words.addAll(gapVocabList);
|
||||||
|
words.addAll(reviewVocabList);
|
||||||
|
if (words.size() < 100) {
|
||||||
|
words.addAll(vocabularyBankDOMapper.selectVocabularyBankListByGradeIdRandom(gradeDO.getId(), 100 - words.size()));
|
||||||
|
} else {
|
||||||
|
words = words.subList(0, 100);
|
||||||
|
}
|
||||||
|
words.forEach(word -> word.setDefinition(word.getDefinition().length() > 5 ? word.getDefinition().substring(0, 5) : word.getDefinition()));
|
||||||
|
List<Integer> wordIds = words.stream().map(VocabularyBankDO::getId).toList();
|
||||||
|
StudentDO studentDO = studentDOMapper.selectStudentById(studentId);
|
||||||
|
|
||||||
|
String ExamTitle = gradeDO.getTitle() + unitDO.getTitle() + "教案小测 第" + ExamWordsConstant.day2Chinese(day) + "天" + studentDO.getName();
|
||||||
|
ExamWordsDO examWordsDO = ExamWordsDO.builder()
|
||||||
|
.gradeId(gradeDO.getId())
|
||||||
|
.level(1)
|
||||||
|
.wordIds(wordIds)
|
||||||
|
.type(ExamWordsConstant.EXAM_TYPE_TEST)
|
||||||
|
.title(ExamTitle)
|
||||||
|
.createdAt(LocalDateTime.now())
|
||||||
|
.build();
|
||||||
|
examWordsDOMapper.insert(examWordsDO);
|
||||||
|
studentExamWordsDOMapper.insertStudentsExam(studentId, examWordsDO.getId());
|
||||||
|
|
||||||
|
data.put("examId", examWordsDO.getId());
|
||||||
|
data.put("studentId", studentId);
|
||||||
|
data.put("studentStr", studentDO.getName());
|
||||||
|
data.put("examStr", ExamTitle);
|
||||||
|
|
||||||
|
List<VocabularyBankDO> words1 = words.subList(0, wordIds.size() / 2);
|
||||||
|
List<VocabularyBankDO> words2 = words.subList(wordIds.size() / 2, wordIds.size());
|
||||||
|
data.put("words1", words1);
|
||||||
|
data.put("words2", words2);
|
||||||
|
|
||||||
|
log.info("生成教案小测成功");
|
||||||
|
|
||||||
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
// LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||||
// Configure config = Configure.builder()
|
// Configure config = Configure.builder()
|
||||||
// .bind("syncVocabList", policy)
|
// .bind("syncVocabList", policy)
|
||||||
@@ -269,7 +388,7 @@ public class LessonPlansServiceImpl implements LessonPlansService {
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
do {
|
do {
|
||||||
log.info("第{}次生成文章中文开始", ++i);
|
log.info("第{}次生成文章中文开始", ++i);
|
||||||
String answer = difyArticleClient.sendChat(string, String.valueOf(studentId) + UUID.randomUUID(), null).getAnswer();
|
String answer = difyClient.sendChat(string, String.valueOf(studentId) + UUID.randomUUID(), null).getAnswer();
|
||||||
map = StringToPlanMapUtil.parseTextToMap(answer);
|
map = StringToPlanMapUtil.parseTextToMap(answer);
|
||||||
} while (map.get(LessonPlanConstant.TITLE) == null
|
} while (map.get(LessonPlanConstant.TITLE) == null
|
||||||
|| map.get(LessonPlanConstant.PASSAGE) == null
|
|| map.get(LessonPlanConstant.PASSAGE) == null
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service.role;
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.constant.RoleConstants;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.RoleDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UserRoleRelDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.RoleDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.UserDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.UserRoleRelDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.service.RoleService;
|
||||||
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class RoleServiceImpl implements RoleService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private RoleDOMapper roleDOMapper;
|
||||||
|
@Resource
|
||||||
|
private UserRoleRelDOMapper userRoleRelDOMapper;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pushRolePermission2Redis() {
|
||||||
|
List<RoleDO> roleDOS = roleDOMapper.selectAll();
|
||||||
|
List<String> roleKeys = roleDOS.stream().map(RoleDO::getRoleKey).toList();
|
||||||
|
log.info("将角色同步到 redis 中, {}", roleKeys);
|
||||||
|
redisTemplate.opsForValue().set(RoleConstants.ROLE, JsonUtils.toJsonString(roleKeys));
|
||||||
|
|
||||||
|
Map<Long, RoleDO> roleId2RoleDO = roleDOS.stream().collect(Collectors.toMap(RoleDO::getId, roleDO -> roleDO));
|
||||||
|
|
||||||
|
List<UserRoleRelDO> userRoleRelDOS = userRoleRelDOMapper.selectAll();
|
||||||
|
log.info("将用户角色关系同步到 redis 中, {}", userRoleRelDOS);
|
||||||
|
Map<Long, List<UserRoleRelDO>> userId2UserRoleRelDOs = userRoleRelDOS.stream().collect(Collectors.groupingBy(UserRoleRelDO::getUserId));
|
||||||
|
|
||||||
|
userId2UserRoleRelDOs.forEach((userId, userRoleRelDOs) -> {
|
||||||
|
List<Long> roleIds = userRoleRelDOs.stream().map(UserRoleRelDO::getRoleId).toList();
|
||||||
|
List<RoleDO> roleDOs = roleIds.stream().map(roleId2RoleDO::get).toList();
|
||||||
|
List<String> user2RoleKeys = roleDOs.stream().map(RoleDO::getRoleKey).toList();
|
||||||
|
log.info("将用户 {} 的角色同步到 redis 中, {}", userId, roleKeys);
|
||||||
|
// 不要使用 JsonUtils.toJsonString(user2RoleKeys); 会造成二次序列化
|
||||||
|
redisTemplate.opsForValue().set(RoleConstants.buildUserRoleKey(userId), user2RoleKeys);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<Long, RoleDO> findUserId2RoleMap(List<Long> userIds) {
|
||||||
|
List<UserRoleRelDO> userRoleRelDOS = userRoleRelDOMapper.selectByUserIds(userIds);
|
||||||
|
Map<Long, UserRoleRelDO> userId2UserRoleRelDOs = userRoleRelDOS.stream().collect(Collectors.toMap(UserRoleRelDO::getUserId, userRoleRelDO -> userRoleRelDO));
|
||||||
|
|
||||||
|
List<RoleDO> roleDOS = roleDOMapper.selectByIds(userRoleRelDOS.stream().map(UserRoleRelDO::getRoleId).toList());
|
||||||
|
Map<Long, RoleDO> roleId2RoleDO = roleDOS.stream().collect(Collectors.toMap(RoleDO::getId, roleDO -> roleDO));
|
||||||
|
|
||||||
|
return userIds.stream().collect(Collectors.toMap(userId -> userId, userId -> roleId2RoleDO.get(userId2UserRoleRelDOs.get(userId).getRoleId())));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
package com.yinlihupo.enlish.service.service.student;
|
package com.yinlihupo.enlish.service.service.student;
|
||||||
|
|
||||||
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.ClassDO;
|
import com.yinlihupo.enlish.service.constant.StudentConstant;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.GradeDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.*;
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.StudentDO;
|
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.*;
|
import com.yinlihupo.enlish.service.domain.mapper.*;
|
||||||
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
|
import com.yinlihupo.enlish.service.model.bo.StudentDetail;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.exam.ExamWordsJudgeResultDetail;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.exam.WordMasteryDetail;
|
||||||
import com.yinlihupo.enlish.service.model.vo.student.AddStudentReqVO;
|
import com.yinlihupo.enlish.service.model.vo.student.AddStudentReqVO;
|
||||||
import com.yinlihupo.enlish.service.service.StudentService;
|
import com.yinlihupo.enlish.service.service.StudentService;
|
||||||
|
import com.yinlihupo.enlish.service.utils.DifyClient;
|
||||||
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.*;
|
||||||
import java.util.Map;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -29,6 +33,14 @@ public class StudentServiceImpl implements StudentService {
|
|||||||
private VocabularyBankDOMapper vocabularyBankMapper;
|
private VocabularyBankDOMapper vocabularyBankMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private WordMasteryLogDOMapper wordMasteryLogDOMapper;
|
private WordMasteryLogDOMapper wordMasteryLogDOMapper;
|
||||||
|
@Resource
|
||||||
|
private ExamWordsJudgeResultDOMapper examWordsJudgeResultDOMapper;
|
||||||
|
@Resource
|
||||||
|
private DifyClient difyClient;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private StudentStageLearningRemarkDOMapper studentStageLearningRemarkDOMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<StudentDO> getStudentsByClassIdAndGradeId(Integer classId, Integer gradeId, String name, Integer pageNo, Integer pageSize) {
|
public List<StudentDO> getStudentsByClassIdAndGradeId(Integer classId, Integer gradeId, String name, Integer pageNo, Integer pageSize) {
|
||||||
@@ -94,4 +106,94 @@ public class StudentServiceImpl implements StudentService {
|
|||||||
public void deleteStudent(Integer studentId) {
|
public void deleteStudent(Integer studentId) {
|
||||||
studentDOMapper.deleteById(studentId);
|
studentDOMapper.deleteById(studentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String analyzeStudentStudy(Integer studentId) {
|
||||||
|
|
||||||
|
String key = StudentConstant.buildAnalyzeStudentStudyKey(studentId);
|
||||||
|
if (redisTemplate.hasKey(key)) {
|
||||||
|
Object ans = redisTemplate.opsForValue().get(key);
|
||||||
|
return JsonUtils.toJsonString(ans);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ExamWordsJudgeResultDO> examWordsJudgeResultDOS = examWordsJudgeResultDOMapper.selectByStudentIdAndLimitTime(studentId);
|
||||||
|
|
||||||
|
List<Integer> wordIds = new java.util.ArrayList<>(examWordsJudgeResultDOS.stream().map(ExamWordsJudgeResultDO::getCorrectWordIds).flatMap(List::stream).toList());
|
||||||
|
wordIds.addAll(examWordsJudgeResultDOS.stream().map(ExamWordsJudgeResultDO::getWrongWordIds).flatMap(List::stream).toList());
|
||||||
|
List<VocabularyBankDO> vocabularyBankDOS = new ArrayList<>();
|
||||||
|
if (!wordIds.isEmpty()) {
|
||||||
|
vocabularyBankDOS = vocabularyBankMapper.selectVocabularyBankDOListByIds(wordIds);
|
||||||
|
}
|
||||||
|
Map<Integer, VocabularyBankDO> id2Word = vocabularyBankDOS.stream().collect(Collectors.toMap(VocabularyBankDO::getId, vocabularyBankDO -> vocabularyBankDO));
|
||||||
|
|
||||||
|
List<ExamWordsJudgeResultDetail> examWordsJudgeResultDetails = new ArrayList<>();
|
||||||
|
for (ExamWordsJudgeResultDO examWordsJudgeResultDO : examWordsJudgeResultDOS) {
|
||||||
|
List<Integer> correctWordIds = examWordsJudgeResultDO.getCorrectWordIds();
|
||||||
|
List<String> correctWords = correctWordIds.stream().map(id2Word::get).map(VocabularyBankDO::getWord).toList();
|
||||||
|
List<Integer> wrongWordIds = examWordsJudgeResultDO.getWrongWordIds();
|
||||||
|
List<String> wrongWords = wrongWordIds.stream().map(id2Word::get).map(VocabularyBankDO::getWord).toList();
|
||||||
|
|
||||||
|
examWordsJudgeResultDetails.add(ExamWordsJudgeResultDetail.builder()
|
||||||
|
.correctWordCount(examWordsJudgeResultDO.getCorrectWordCount())
|
||||||
|
.wrongWordCount(examWordsJudgeResultDO.getWrongWordCount())
|
||||||
|
.startDate(examWordsJudgeResultDO.getStartDate())
|
||||||
|
.correctWords(correctWords)
|
||||||
|
.wrongWords(wrongWords)
|
||||||
|
.msg(examWordsJudgeResultDO.getMsg()).build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> studentStudyInfo = new HashMap<>();
|
||||||
|
studentStudyInfo.put("考试记录", examWordsJudgeResultDetails);
|
||||||
|
|
||||||
|
List<WordMasteryLogDO> wordMasteryLogDOS = wordMasteryLogDOMapper.selectByStudentIdAndLimitTime(studentId);
|
||||||
|
List<VocabularyBankDO> masteredWords = vocabularyBankMapper.selectVocabularyBankDOListByIds(wordMasteryLogDOS.stream().map(WordMasteryLogDO::getWordId).toList());
|
||||||
|
Map<Integer, VocabularyBankDO> id2MasteryWord = masteredWords.stream().collect(Collectors.toMap(VocabularyBankDO::getId, vocabularyBankDO -> vocabularyBankDO));
|
||||||
|
List<WordMasteryDetail> wordMasteryDetails = new ArrayList<>();
|
||||||
|
for (WordMasteryLogDO wordMasteryLogDO : wordMasteryLogDOS) {
|
||||||
|
wordMasteryDetails.add(WordMasteryDetail.builder()
|
||||||
|
.word(id2MasteryWord.get(wordMasteryLogDO.getWordId()).getWord())
|
||||||
|
.reviewCount(wordMasteryLogDO.getReviewCount())
|
||||||
|
.memoryStrength(wordMasteryLogDO.getMemoryStrength())
|
||||||
|
.update_time(wordMasteryLogDO.getUpdate_time())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
studentStudyInfo.put("单词掌握情况", wordMasteryDetails);
|
||||||
|
|
||||||
|
try {
|
||||||
|
String analyze = difyClient.sendStudentAnalyze(JsonUtils.toJsonString(studentStudyInfo)).getAnswer();
|
||||||
|
// 设置过期时间 3 天
|
||||||
|
redisTemplate.opsForValue().set(key, analyze);
|
||||||
|
redisTemplate.expire(key, 3, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
studentStageLearningRemarkDOMapper.insert(StudentStageLearningRemarkDO.builder()
|
||||||
|
.studentId(studentId)
|
||||||
|
.commentContent(analyze)
|
||||||
|
.createTime(LocalDateTime.now())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
return analyze;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<WordMasteryDetail> findStudentWordMasteryDetail(Integer studentId) {
|
||||||
|
|
||||||
|
List<WordMasteryLogDO> wordMasteryLogDOS = wordMasteryLogDOMapper.selectAllByStudentId(studentId);
|
||||||
|
List<VocabularyBankDO> masteredWords = vocabularyBankMapper.selectVocabularyBankDOListByIds(wordMasteryLogDOS.stream().map(WordMasteryLogDO::getWordId).toList());
|
||||||
|
Map<Integer, VocabularyBankDO> id2MasteryWord = masteredWords.stream().collect(Collectors.toMap(VocabularyBankDO::getId, vocabularyBankDO -> vocabularyBankDO));
|
||||||
|
List<WordMasteryDetail> wordMasteryDetails = new ArrayList<>();
|
||||||
|
for (WordMasteryLogDO wordMasteryLogDO : wordMasteryLogDOS) {
|
||||||
|
wordMasteryDetails.add(WordMasteryDetail.builder()
|
||||||
|
.word(id2MasteryWord.get(wordMasteryLogDO.getWordId()).getWord())
|
||||||
|
.reviewCount(wordMasteryLogDO.getReviewCount())
|
||||||
|
.memoryStrength(wordMasteryLogDO.getMemoryStrength())
|
||||||
|
.update_time(wordMasteryLogDO.getUpdate_time())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
return wordMasteryDetails;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.yinlihupo.enlish.service.service.user;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
|
import com.yinlihupo.enlish.service.constant.UserRedisConstants;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.UserDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.UserDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.service.UserService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UserDOMapper userDOMapper;
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
@Resource
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDO findUser() {
|
||||||
|
String loginIdStr =(String) StpUtil.getLoginId();
|
||||||
|
Long loginId = Long.parseLong(loginIdStr);
|
||||||
|
return userDOMapper.selectById(loginId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserDO> findUsersList(int page, int limit, String name) {
|
||||||
|
|
||||||
|
return userDOMapper.selectUserDOList(name, (page - 1) * limit, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer findUserTotal() {
|
||||||
|
return userDOMapper.selectUserTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createUser(UserDO userDO) {
|
||||||
|
userDOMapper.insert(userDO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateUserInfo(String password, String reqCode, String phone, String name) {
|
||||||
|
long id = Integer.parseInt(String.valueOf(StpUtil.getLoginId()));
|
||||||
|
UserDO userDO = userDOMapper.selectById(id);
|
||||||
|
|
||||||
|
String key = UserRedisConstants.buildUserLoginCode(userDO.getPhone());
|
||||||
|
String code = Objects.requireNonNull(redisTemplate.opsForValue().get(key)).toString();
|
||||||
|
if (code == null || !code.equals(reqCode)) {
|
||||||
|
throw new RuntimeException("验证码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password != null) {
|
||||||
|
password = passwordEncoder.encode(password);
|
||||||
|
}
|
||||||
|
userDOMapper.updateUserInfo(id, name, password, phone);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package com.yinlihupo.enlish.service.service.vocabulary;
|
|||||||
|
|
||||||
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||||
import com.yinlihupo.enlish.service.domain.mapper.VocabularyBankDOMapper;
|
import com.yinlihupo.enlish.service.domain.mapper.VocabularyBankDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.domain.mapper.WordMasteryLogDOMapper;
|
||||||
|
import com.yinlihupo.enlish.service.model.vo.vocabulary.FindStudentWordDetailRspVO;
|
||||||
import com.yinlihupo.enlish.service.service.VocabularyService;
|
import com.yinlihupo.enlish.service.service.VocabularyService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -14,9 +16,19 @@ public class VocabularyServiceImpl implements VocabularyService {
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
private VocabularyBankDOMapper vocabularyBankDOMapper;
|
||||||
|
@Resource
|
||||||
|
private WordMasteryLogDOMapper wordMasteryLogDOMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<VocabularyBankDO> findVocabularyBankDOListById(List<Integer> ids) {
|
public List<VocabularyBankDO> findVocabularyBankDOListById(List<Integer> ids) {
|
||||||
return vocabularyBankDOMapper.selectVocabularyBankDOListByIds(ids);
|
return vocabularyBankDOMapper.selectVocabularyBankDOListByIds(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FindStudentWordDetailRspVO findStudentWordDetail(Integer studentId) {
|
||||||
|
Integer wordMastery = wordMasteryLogDOMapper.selectMasteryCount(studentId);
|
||||||
|
Integer wordNotMastery = wordMasteryLogDOMapper.selectNotMasteryCount(studentId);
|
||||||
|
Integer total = vocabularyBankDOMapper.selectWordTotal();
|
||||||
|
return FindStudentWordDetailRspVO.builder().masteredWordCount(wordMastery).unmasteredWordCount(wordNotMastery).pendingReviewWordCount(total - wordMastery - wordNotMastery).build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.yinlihupo.enlish.service.job;
|
package com.yinlihupo.enlish.service.task;
|
||||||
|
|
||||||
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
|
import com.yinlihupo.enlish.service.service.ExamWordsJudgeService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@Slf4j
|
||||||
public class AutoJudgeExamWordsTask {
|
public class AutoJudgeExamWordsTask {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
@@ -15,7 +15,9 @@ public class AutoJudgeExamWordsTask {
|
|||||||
|
|
||||||
@Scheduled(fixedRate = 5000)
|
@Scheduled(fixedRate = 5000)
|
||||||
public void autoJudgeExamWords() {
|
public void autoJudgeExamWords() {
|
||||||
System.out.println("【固定频率】开始自动判卷,时间:" + LocalDateTime.now());
|
if (examWordsJudgeService.getExamUnfinishedCount() != 0) {
|
||||||
examWordsJudgeService.judgeExamWords(5);
|
log.info("有试卷待检测,开始检测");
|
||||||
|
examWordsJudgeService.judgeExamWords(5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.yinlihupo.enlish.service.task;
|
||||||
|
|
||||||
|
|
||||||
|
import com.yinlihupo.enlish.service.service.RoleService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class UserRoleTask {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RoleService roleService;
|
||||||
|
|
||||||
|
@Scheduled(cron = "0 0 1 * * ?")
|
||||||
|
public void PushRolePermissions2Redis() {
|
||||||
|
log.info("定时任务,将系统权限推送到 redis 中");
|
||||||
|
roleService.pushRolePermission2Redis();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,9 @@ package com.yinlihupo.enlish.service.utils; // 修改为你的包名
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.VocabularyBankDO;
|
||||||
|
import com.yinlihupo.enlish.service.model.bo.Sentence;
|
||||||
|
import com.yinlihupo.framework.common.util.JsonUtils;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@@ -14,20 +17,25 @@ import java.net.http.HttpRequest;
|
|||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class DifyArticleClient {
|
public class DifyClient {
|
||||||
|
|
||||||
@Value("${ai.key}")
|
@Value("${ai.key}")
|
||||||
private String apiKey;
|
private String apiKey;
|
||||||
|
@Value("${ai.analyzeKey}")
|
||||||
|
private String analyzeKey;
|
||||||
|
@Value("${ai.sentenceKey}")
|
||||||
|
private String sentenceKey;
|
||||||
@Value("${ai.url}")
|
@Value("${ai.url}")
|
||||||
private String baseUrl;
|
private String baseUrl;
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
// 构造函数
|
// 构造函数
|
||||||
public DifyArticleClient() {
|
public DifyClient() {
|
||||||
|
|
||||||
this.httpClient = HttpClient.newBuilder()
|
this.httpClient = HttpClient.newBuilder()
|
||||||
.connectTimeout(Duration.ofSeconds(10)) // 连接超时
|
.connectTimeout(Duration.ofSeconds(10)) // 连接超时
|
||||||
@@ -35,6 +43,82 @@ public class DifyArticleClient {
|
|||||||
this.objectMapper = new ObjectMapper();
|
this.objectMapper = new ObjectMapper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Sentence> sendSentenceAnalyze(List<VocabularyBankDO> list, String grade) throws Exception {
|
||||||
|
// 1. 构建请求体对象
|
||||||
|
ChatRequest payload = new ChatRequest();
|
||||||
|
payload.setQuery(JsonUtils.toJsonString(list.stream().map(VocabularyBankDO::getWord).toList()));
|
||||||
|
payload.setUser("admin");
|
||||||
|
|
||||||
|
HashMap<String, Object> objectObjectHashMap = new HashMap<>();
|
||||||
|
objectObjectHashMap.put("grade", grade);
|
||||||
|
payload.setResponseMode("blocking"); // 使用阻塞模式,一次性返回
|
||||||
|
payload.setInputs(objectObjectHashMap);
|
||||||
|
|
||||||
|
// 2. 序列化为 JSON 字符串
|
||||||
|
String jsonBody = objectMapper.writeValueAsString(payload);
|
||||||
|
|
||||||
|
// 3. 构建 HTTP 请求
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(baseUrl))
|
||||||
|
.header("Authorization", "Bearer " + sentenceKey)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||||
|
.timeout(Duration.ofSeconds(30)) // 读取超时
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 4. 发送请求
|
||||||
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
// 5. 检查状态码
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new RuntimeException("Dify 请求失败: HTTP " + response.statusCode() + " | Body: " + response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 反序列化响应体
|
||||||
|
DifyResponse difyResponse = objectMapper.readValue(response.body(), DifyResponse.class);
|
||||||
|
|
||||||
|
String answer = difyResponse.getAnswer();
|
||||||
|
answer = answer.replace("json", "");
|
||||||
|
answer = answer.replace("```", "");
|
||||||
|
|
||||||
|
return JsonUtils.parseList(answer, Sentence.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public DifyResponse sendStudentAnalyze(String query) throws Exception {
|
||||||
|
String endpoint = this.baseUrl;
|
||||||
|
|
||||||
|
// 1. 构建请求体对象
|
||||||
|
ChatRequest payload = new ChatRequest();
|
||||||
|
payload.setQuery(query);
|
||||||
|
payload.setUser(String.valueOf(1));
|
||||||
|
payload.setResponseMode("blocking"); // 使用阻塞模式,一次性返回
|
||||||
|
payload.setInputs(new HashMap<>());
|
||||||
|
|
||||||
|
// 2. 序列化为 JSON 字符串
|
||||||
|
String jsonBody = objectMapper.writeValueAsString(payload);
|
||||||
|
|
||||||
|
// 3. 构建 HTTP 请求
|
||||||
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create(endpoint))
|
||||||
|
.header("Authorization", "Bearer " + analyzeKey)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||||
|
.timeout(Duration.ofSeconds(30)) // 读取超时
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// 4. 发送请求
|
||||||
|
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
|
|
||||||
|
// 5. 检查状态码
|
||||||
|
if (response.statusCode() != 200) {
|
||||||
|
throw new RuntimeException("Dify 请求失败: HTTP " + response.statusCode() + " | Body: " + response.body());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 反序列化响应体
|
||||||
|
return objectMapper.readValue(response.body(), DifyResponse.class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送对话请求 (阻塞模式)
|
* 发送对话请求 (阻塞模式)
|
||||||
*
|
*
|
||||||
@@ -64,7 +148,7 @@ public class DifyArticleClient {
|
|||||||
// 3. 构建 HTTP 请求
|
// 3. 构建 HTTP 请求
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
HttpRequest request = HttpRequest.newBuilder()
|
||||||
.uri(URI.create(endpoint))
|
.uri(URI.create(endpoint))
|
||||||
.header("Authorization", "Bearer " + this.apiKey)
|
.header("Authorization", "Bearer " + apiKey)
|
||||||
.header("Content-Type", "application/json")
|
.header("Content-Type", "application/json")
|
||||||
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
|
||||||
.timeout(Duration.ofSeconds(30)) // 读取超时
|
.timeout(Duration.ofSeconds(30)) // 读取超时
|
||||||
@@ -15,6 +15,7 @@ import org.opencv.imgproc.Imgproc;
|
|||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBufferByte;
|
import java.awt.image.DataBufferByte;
|
||||||
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -31,29 +32,11 @@ public class PngUtil {
|
|||||||
// 获取起始坐标
|
// 获取起始坐标
|
||||||
public static List<CoordinatesXY> analysisXY(String imagePath) {
|
public static List<CoordinatesXY> analysisXY(String imagePath) {
|
||||||
|
|
||||||
|
Mat binary = image2BinaryMath(imagePath);
|
||||||
Mat src = Imgcodecs.imread(imagePath);
|
Mat src = Imgcodecs.imread(imagePath);
|
||||||
|
|
||||||
if (src.empty()) {
|
|
||||||
System.out.println("无法读取图片,请检查路径。");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 预处理
|
|
||||||
// 3.1 转换为灰度图
|
|
||||||
Mat gray = new Mat();
|
|
||||||
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
|
|
||||||
|
|
||||||
// 3.2 二值化处理 (Thresholding)
|
|
||||||
// 使用 THRESH_BINARY_INV (反转二值化),因为我们需要找的是白色背景上的黑色块。
|
|
||||||
// 反转后,黑色块变成白色(255),背景变成黑色(0),方便 findContours 查找。
|
|
||||||
Mat binary = new Mat();
|
|
||||||
// 阈值设为 50 左右即可,因为块是纯黑的
|
|
||||||
Imgproc.threshold(gray, binary, 50, 255, Imgproc.THRESH_BINARY_INV);
|
|
||||||
|
|
||||||
// 4. 查找轮廓
|
// 4. 查找轮廓
|
||||||
List<MatOfPoint> contours = new ArrayList<>();
|
List<MatOfPoint> contours = new ArrayList<>();
|
||||||
Mat hierarchy = new Mat();
|
Mat hierarchy = new Mat();
|
||||||
// RETR_EXTERNAL 只检测最外层轮廓,忽略块内部可能存在的噪点
|
|
||||||
Imgproc.findContours(binary, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
|
Imgproc.findContours(binary, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
|
||||||
|
|
||||||
System.out.println("检测到的轮廓总数: " + contours.size());
|
System.out.println("检测到的轮廓总数: " + contours.size());
|
||||||
@@ -89,67 +72,44 @@ public class PngUtil {
|
|||||||
System.out.println("------------------------------------------------");
|
System.out.println("------------------------------------------------");
|
||||||
list.add(CoordinatesXY.builder().x(rect.x).y(rect.y).width(rect.width).height(rect.height).build());
|
list.add(CoordinatesXY.builder().x(rect.x).y(rect.y).width(rect.width).height(rect.height).build());
|
||||||
// 可选:在原图上画出框,用于调试验证
|
// 可选:在原图上画出框,用于调试验证
|
||||||
// Imgproc.rectangle(src, rect, new Scalar(0, 0, 255), 2); // 红色框
|
Imgproc.rectangle(src, rect, new Scalar(0, 0, 255), 2); // 红色框
|
||||||
// Imgproc.putText(src, "#" + blockCount, new Point(rect.x, rect.y - 5), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 255), 1);
|
Imgproc.putText(src, "#" + blockCount, new Point(rect.x, rect.y - 5), Imgproc.FONT_HERSHEY_SIMPLEX, 0.5, new Scalar(0, 0, 255), 1);
|
||||||
// Imgcodecs.imwrite("output_red.png", src);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Imgcodecs.imwrite("output_red.png", src);
|
||||||
System.out.println("找到 " + blockCount + " 个黑色块。");
|
System.out.println("找到 " + blockCount + " 个黑色块。");
|
||||||
|
|
||||||
// 获取每一列的宽度
|
// 计算起始坐标
|
||||||
list.sort(Comparator.comparingInt(CoordinatesXY::getHeight));
|
|
||||||
int height = list.get(list.size() - 1).getHeight() / ExamWordsConstant.PGN_COL;
|
|
||||||
|
|
||||||
// 删除两列答题卡区块
|
|
||||||
list.sort(Comparator.comparingInt(CoordinatesXY::getWidth));
|
|
||||||
list.remove(list.size() - 1);
|
|
||||||
list.remove(list.size() - 1);
|
|
||||||
list.sort(Comparator.comparingInt(CoordinatesXY::getX));
|
list.sort(Comparator.comparingInt(CoordinatesXY::getX));
|
||||||
|
|
||||||
// 计算起始坐标
|
list.forEach(coordinatesXY -> coordinatesXY.setHeight(coordinatesXY.getHeight() / 51));
|
||||||
List<CoordinatesXY> ans = getCoordinatesXIES(list, height);
|
list.forEach(coordinatesXY -> coordinatesXY.setWidth(coordinatesXY.getWidth() / 3));
|
||||||
|
list.forEach(coordinatesXY -> coordinatesXY.setX(coordinatesXY.getX() + coordinatesXY.getWidth() * 2));
|
||||||
|
|
||||||
src.release();
|
log.info("起始坐标: {}", list);
|
||||||
binary.release();
|
|
||||||
hierarchy.release();
|
|
||||||
binary.release();
|
|
||||||
|
|
||||||
return ans;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取(未背熟)单词的 id
|
// 获取(未背熟)单词的 id
|
||||||
public static List<Integer> analyzePngForUnmemorizedWordIds(String filePath, List<Integer> wordIds, List<CoordinatesXY> coordinatesXYList) {
|
public static List<Integer> analyzePngForUnmemorizedWordIds(String filePath, List<Integer> wordIds, List<CoordinatesXY> coordinatesXYList) {
|
||||||
|
|
||||||
Mat src = Imgcodecs.imread(filePath);
|
|
||||||
if (src.empty()) {
|
|
||||||
log.error("无法读取图片,请检查路径: {}", filePath);
|
|
||||||
throw new RuntimeException("无法读取图片");
|
|
||||||
}
|
|
||||||
|
|
||||||
Mat gray = new Mat();
|
Mat binary = image2BinaryMath(filePath);
|
||||||
Mat binary = new Mat();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
|
|
||||||
// 建议:如果光照不均匀,考虑使用 THRESH_OTSU 自动阈值,或者自适应阈值
|
|
||||||
Imgproc.threshold(gray, binary, 150, 255, Imgproc.THRESH_BINARY_INV);
|
|
||||||
// 调试时打印
|
|
||||||
// Imgcodecs.imwrite("output_binary.png", binary);
|
|
||||||
List<Integer> answer = new ArrayList<>();
|
List<Integer> answer = new ArrayList<>();
|
||||||
int words_index = 0;
|
int words_index = 0;
|
||||||
|
|
||||||
for (int i = 0; i < coordinatesXYList.size(); i++) {
|
for (CoordinatesXY coordinatesXY : coordinatesXYList) {
|
||||||
CoordinatesXY coordinatesXY = coordinatesXYList.get(i);
|
|
||||||
|
|
||||||
int width = coordinatesXY.getWidth();
|
int width = coordinatesXY.getWidth();
|
||||||
int height = coordinatesXY.getHeight();
|
int height = coordinatesXY.getHeight();
|
||||||
int currentX = coordinatesXY.getX();
|
int currentX = coordinatesXY.getX();
|
||||||
int currentY = coordinatesXY.getY();
|
int currentY = coordinatesXY.getY() + height;
|
||||||
|
|
||||||
int count = i == 0 ? ExamWordsConstant.PGN_COL - 1 : ExamWordsConstant.PGN_COL;
|
|
||||||
|
|
||||||
// 内层循环:遍历这一列的每一行
|
// 内层循环:遍历这一列的每一行
|
||||||
for (int j = 0; j < count; j++) {
|
for (int j = 0; j < 50; j++) {
|
||||||
// 安全检查:防止单词列表比格子少导致越界
|
// 安全检查:防止单词列表比格子少导致越界
|
||||||
if (words_index >= wordIds.size()) {
|
if (words_index >= wordIds.size()) {
|
||||||
log.warn("单词列表耗尽,停止检测。格子数多于单词数。");
|
log.warn("单词列表耗尽,停止检测。格子数多于单词数。");
|
||||||
@@ -168,11 +128,14 @@ public class PngUtil {
|
|||||||
Rect rect = new Rect(currentX + 1, currentY + 1, width - 2, height - 2);
|
Rect rect = new Rect(currentX + 1, currentY + 1, width - 2, height - 2);
|
||||||
Mat region = binary.submat(rect);
|
Mat region = binary.submat(rect);
|
||||||
int countNonZero = Core.countNonZero(region);
|
int countNonZero = Core.countNonZero(region);
|
||||||
|
log.info("当前位置为 words_index={},坐标为 x={} y={} 当前区域非零像素数: {}", words_index, currentX, currentY, countNonZero);
|
||||||
if (countNonZero > 800) {
|
if (countNonZero > 1000) {
|
||||||
Integer id = wordIds.get(words_index);
|
Integer id = wordIds.get(words_index);
|
||||||
answer.add(id);
|
answer.add(id);
|
||||||
log.info("检测到标记(未背熟):ID={}", id);
|
log.info("检测到标记(未背熟):ID={}, 当前坐标 x = {} y = {} ", id, currentX + 1, currentY + 1);
|
||||||
|
}
|
||||||
|
if (countNonZero == 0) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
region.release();
|
region.release();
|
||||||
@@ -185,8 +148,6 @@ public class PngUtil {
|
|||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
|
||||||
src.release();
|
|
||||||
gray.release();
|
|
||||||
binary.release();
|
binary.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -206,21 +167,21 @@ public class PngUtil {
|
|||||||
Rect roiRect = new Rect(0, 0, left.getX(), left.getY());
|
Rect roiRect = new Rect(0, 0, left.getX(), left.getY());
|
||||||
Mat roi = new Mat(src, roiRect);
|
Mat roi = new Mat(src, roiRect);
|
||||||
|
|
||||||
// 3. 图像预处理 (提高 OCR 准确率)
|
// // 3. 图像预处理 (提高 OCR 准确率)
|
||||||
// 3.1 转为灰度图
|
// // 3.1 转为灰度图
|
||||||
Mat gray = new Mat();
|
// Mat gray = new Mat();
|
||||||
Imgproc.cvtColor(roi, gray, Imgproc.COLOR_BGR2GRAY);
|
// Imgproc.cvtColor(roi, gray, Imgproc.COLOR_BGR2GRAY);
|
||||||
|
//
|
||||||
// 3.2 二值化 (Thresholding)
|
// // 3.2 二值化 (Thresholding)
|
||||||
// 使用 OTSU 算法自动寻找最佳阈值,或者手动指定阈值
|
// // 使用 OTSU 算法自动寻找最佳阈值,或者手动指定阈值
|
||||||
Mat binary = new Mat();
|
// Mat binary = new Mat();
|
||||||
Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
|
// Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
|
||||||
|
|
||||||
// 可选:保存预处理后的图片查看效果
|
// 可选:保存预处理后的图片查看效果
|
||||||
// Imgcodecs.imwrite("debug_roi.jpg", binary);
|
Imgcodecs.imwrite("debug_roi.jpg", src);
|
||||||
|
|
||||||
// 4. 将 OpenCV Mat 转换为 BufferedImage (供 Tess4J 使用)
|
// 4. 将 OpenCV Mat 转换为 BufferedImage (供 Tess4J 使用)
|
||||||
BufferedImage processedImage = matToBufferedImage(binary);
|
BufferedImage processedImage = matToBufferedImage(src);
|
||||||
|
|
||||||
// 5. 使用 Tesseract 进行 OCR 识别
|
// 5. 使用 Tesseract 进行 OCR 识别
|
||||||
ITesseract instance = new Tesseract();
|
ITesseract instance = new Tesseract();
|
||||||
@@ -245,6 +206,50 @@ public class PngUtil {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Mat image2BinaryMath(String imagePath) {
|
||||||
|
|
||||||
|
if (!new File(imagePath).exists()) {
|
||||||
|
log.error("图片不存在,请检查路径: {}", imagePath);
|
||||||
|
throw new RuntimeException("图片不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat src = Imgcodecs.imread(imagePath);
|
||||||
|
|
||||||
|
if (src.empty()) {
|
||||||
|
log.info("无法读取图片,请检查路径: {}", imagePath);
|
||||||
|
throw new RuntimeException("无法读取图片");
|
||||||
|
}
|
||||||
|
|
||||||
|
Mat gray = new Mat();
|
||||||
|
//转换为灰度图
|
||||||
|
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
|
||||||
|
|
||||||
|
Imgproc.GaussianBlur(gray, gray, new Size(5, 5), 0);
|
||||||
|
|
||||||
|
Mat binary = new Mat();
|
||||||
|
Imgproc.adaptiveThreshold(gray, binary, 255,
|
||||||
|
Imgproc.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||||
|
Imgproc.THRESH_BINARY_INV, 25, 10);
|
||||||
|
|
||||||
|
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3, 3));
|
||||||
|
|
||||||
|
//开运算 (Open):先腐蚀后膨胀,用于去除背景中的微小噪点
|
||||||
|
Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_OPEN, kernel);
|
||||||
|
|
||||||
|
// 闭运算 (Close):先膨胀后腐蚀,用于连接断裂的区域并填充块内部的空洞
|
||||||
|
// 如果块比较大且内部反光严重,可以将 Size(3,3) 改为 Size(5,5) 或更大
|
||||||
|
Mat closeKernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(7, 7));
|
||||||
|
Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_CLOSE, closeKernel);
|
||||||
|
|
||||||
|
// 保存二值化过程图用于调试 (生产环境可注释)
|
||||||
|
Imgcodecs.imwrite("debug_binary_natural.png", binary);
|
||||||
|
|
||||||
|
src.release();
|
||||||
|
gray.release();
|
||||||
|
|
||||||
|
return binary;
|
||||||
|
}
|
||||||
|
|
||||||
private static @NonNull StudentExamId getStudentExamId(Pattern pattern, String result) {
|
private static @NonNull StudentExamId getStudentExamId(Pattern pattern, String result) {
|
||||||
Matcher matcher = pattern.matcher(result);
|
Matcher matcher = pattern.matcher(result);
|
||||||
StudentExamId studentExamId = new StudentExamId(0, 0);
|
StudentExamId studentExamId = new StudentExamId(0, 0);
|
||||||
@@ -276,21 +281,4 @@ public class PngUtil {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull List<CoordinatesXY> getCoordinatesXIES(List<CoordinatesXY> list, int height) {
|
|
||||||
List<CoordinatesXY> ans = new ArrayList<>();
|
|
||||||
CoordinatesXY left = new CoordinatesXY();
|
|
||||||
left.setX(list.get(1).getX());
|
|
||||||
left.setWidth(list.get(1).getWidth());
|
|
||||||
left.setHeight(height);
|
|
||||||
left.setY(list.get(0).getY() + left.getHeight());
|
|
||||||
ans.add(left);
|
|
||||||
|
|
||||||
CoordinatesXY right = new CoordinatesXY();
|
|
||||||
right.setX(list.get(2).getX());
|
|
||||||
right.setY(list.get(0).getY());
|
|
||||||
right.setWidth(list.get(1).getWidth());
|
|
||||||
right.setHeight(height);
|
|
||||||
ans.add(right);
|
|
||||||
return ans;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.yinlihupo.enlish.service.utils;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.ai.openai.OpenAiAudioSpeechModel;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class TTSUtil {
|
||||||
|
|
||||||
|
private final OpenAiAudioSpeechModel speechModel;
|
||||||
|
|
||||||
|
public TTSUtil(OpenAiAudioSpeechModel speechModel) {
|
||||||
|
this.speechModel = speechModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成语音并将二进制流写入 HTTP 响应
|
||||||
|
*
|
||||||
|
* @param text 需要转换的文本
|
||||||
|
* @param response HTTP 响应对象
|
||||||
|
*/
|
||||||
|
public void generateWordVoice(String text, HttpServletResponse response) {
|
||||||
|
// 1. 参数校验
|
||||||
|
if (!StringUtils.hasText(text)) {
|
||||||
|
sendError(response, HttpServletResponse.SC_BAD_REQUEST, "Input text cannot be empty");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 3. 调用 OpenAI 接口获取音频数据
|
||||||
|
byte[] audioBytes = speechModel.call(text);
|
||||||
|
|
||||||
|
// 4. 设置 HTTP 响应头
|
||||||
|
response.setContentType("audio/mpeg");
|
||||||
|
response.setContentLength(audioBytes.length);
|
||||||
|
|
||||||
|
// 可选:如果不希望浏览器自动播放,而是强制下载,请取消下面这行的注释
|
||||||
|
// response.setHeader("Content-Disposition", "attachment; filename=\"speech.mp3\"");
|
||||||
|
|
||||||
|
// 5. 将音频数据写入响应流
|
||||||
|
try (ServletOutputStream outputStream = response.getOutputStream()) {
|
||||||
|
outputStream.write(audioBytes);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("TTS Generation failed: {}", e.getMessage());
|
||||||
|
sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "TTS Generation failed: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:发送错误响应
|
||||||
|
private void sendError(HttpServletResponse response, int status, String message) {
|
||||||
|
try {
|
||||||
|
response.sendError(status, message);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// 忽略这里的错误,因为响应可能已经提交
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,25 @@ package com.yinlihupo.enlish.service.utils;
|
|||||||
|
|
||||||
import com.deepoove.poi.XWPFTemplate;
|
import com.deepoove.poi.XWPFTemplate;
|
||||||
import com.deepoove.poi.config.Configure;
|
import com.deepoove.poi.config.Configure;
|
||||||
|
import com.deepoove.poi.data.PictureType;
|
||||||
|
import com.deepoove.poi.data.Pictures;
|
||||||
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
|
||||||
|
import com.google.zxing.BarcodeFormat;
|
||||||
|
import com.google.zxing.EncodeHintType;
|
||||||
|
import com.google.zxing.MultiFormatWriter;
|
||||||
|
import com.google.zxing.WriterException;
|
||||||
|
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||||
|
import com.google.zxing.common.BitMatrix;
|
||||||
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.ExamWordsDO;
|
||||||
|
import com.yinlihupo.enlish.service.domain.dataobject.LessonPlansDO;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
@@ -23,6 +36,8 @@ public class WordExportUtil {
|
|||||||
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
|
||||||
config = Configure.builder()
|
config = Configure.builder()
|
||||||
.bind("words", policy)
|
.bind("words", policy)
|
||||||
|
.bind("words1", policy)
|
||||||
|
.bind("words2", policy)
|
||||||
.bind("answer", policy)
|
.bind("answer", policy)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -37,18 +52,23 @@ public class WordExportUtil {
|
|||||||
.bind("mixedDrill", policyLessonPlanWeekday)
|
.bind("mixedDrill", policyLessonPlanWeekday)
|
||||||
.bind("checkList", policyLessonPlanWeekday)
|
.bind("checkList", policyLessonPlanWeekday)
|
||||||
.bind("checkListAns", policyLessonPlanWeekday)
|
.bind("checkListAns", policyLessonPlanWeekday)
|
||||||
|
.bind("sentences", policyLessonPlanWeekday)
|
||||||
|
.bind("sentencesAns", policyLessonPlanWeekday)
|
||||||
|
.bind("words1", policy)
|
||||||
|
.bind("words2", policy)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
LoopRowTableRenderPolicy policyLessonPlan = new LoopRowTableRenderPolicy();
|
LoopRowTableRenderPolicy policyLessonPlan = new LoopRowTableRenderPolicy();
|
||||||
configLessonPlanWeekend = Configure.builder()
|
configLessonPlanWeekend = Configure.builder()
|
||||||
.bind("checkList", policyLessonPlan)
|
.bind("words1", policy)
|
||||||
|
.bind("words2", policy)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 公共入口:根据数据量决定是导出单文件还是压缩包
|
* 公共入口:根据数据量决定是导出单文件还是压缩包
|
||||||
*/
|
*/
|
||||||
public static void generateExamWords(List<Map<String, Object>> data, HttpServletResponse response, String templateWordPath) {
|
public static void generateExamWords(List<Map<String, Object>> data, ExamWordsDO examWordsDO, HttpServletResponse response, String templateWordPath) {
|
||||||
if (data == null || data.isEmpty()) {
|
if (data == null || data.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -56,10 +76,10 @@ public class WordExportUtil {
|
|||||||
try {
|
try {
|
||||||
if (data.size() == 1) {
|
if (data.size() == 1) {
|
||||||
// 如果只有一份数据,直接导出 docx,用户体验更好
|
// 如果只有一份数据,直接导出 docx,用户体验更好
|
||||||
generateExamWordsDocx(data.get(0), response, templateWordPath);
|
generateExamWordsDocx(data.get(0), examWordsDO, response, templateWordPath);
|
||||||
} else {
|
} else {
|
||||||
// 如果有多份数据,打包导出
|
// 如果有多份数据,打包导出
|
||||||
generateExamWordsZip(data, response, templateWordPath);
|
generateExamWordsZip(data, examWordsDO, response, templateWordPath);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
@@ -67,8 +87,8 @@ public class WordExportUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void generateLessonPlanDocx(Map<String, Object> map, String fileName, HttpServletResponse response, String templateWordPath, boolean isWeekday) throws IOException {
|
public static void generateLessonPlanDocx(Map<String, Object> map, LessonPlansDO lessonPlan, HttpServletResponse response, String templateWordPath, boolean isWeekday) throws IOException {
|
||||||
fileName = URLEncoder.encode(fileName + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
String fileName = URLEncoder.encode(lessonPlan.getTitle() + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||||
|
|
||||||
// 3. 设置响应头
|
// 3. 设置响应头
|
||||||
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||||
@@ -81,6 +101,8 @@ public class WordExportUtil {
|
|||||||
} else {
|
} else {
|
||||||
template = XWPFTemplate.compile(inputStream, configLessonPlanWeekend);
|
template = XWPFTemplate.compile(inputStream, configLessonPlanWeekend);
|
||||||
}
|
}
|
||||||
|
String url = "http://english.yinlihupo.cn/#/plan/tts?planId=" + lessonPlan.getId();
|
||||||
|
map.put("img", Pictures.ofBytes(generateQR(url), PictureType.PNG).create());
|
||||||
OutputStream out = response.getOutputStream();
|
OutputStream out = response.getOutputStream();
|
||||||
template.render(map);
|
template.render(map);
|
||||||
template.write(out);
|
template.write(out);
|
||||||
@@ -92,9 +114,9 @@ public class WordExportUtil {
|
|||||||
/**
|
/**
|
||||||
* 核心补充:批量渲染并打包为 ZIP
|
* 核心补充:批量渲染并打包为 ZIP
|
||||||
*/
|
*/
|
||||||
private static void generateExamWordsZip(List<Map<String, Object>> data, HttpServletResponse response, String templateWordPath) throws IOException {
|
private static void generateExamWordsZip(List<Map<String, Object>> data, ExamWordsDO examWordsDO, HttpServletResponse response, String templateWordPath) throws IOException {
|
||||||
// 1. 设置响应头为 ZIP
|
// 1. 设置响应头为 ZIP
|
||||||
String zipName = URLEncoder.encode("批量导出_摸底测试.zip", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
String zipName = URLEncoder.encode("批量导出_" + examWordsDO.getTitle() + ".zip", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||||
response.setContentType("application/zip");
|
response.setContentType("application/zip");
|
||||||
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + zipName);
|
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + zipName);
|
||||||
|
|
||||||
@@ -106,7 +128,7 @@ public class WordExportUtil {
|
|||||||
for (Map<String, Object> itemData : data) {
|
for (Map<String, Object> itemData : data) {
|
||||||
// 3. 确定压缩包内的文件名
|
// 3. 确定压缩包内的文件名
|
||||||
// 优先从 map 中获取 'fileName' 字段,否则使用默认编号
|
// 优先从 map 中获取 'fileName' 字段,否则使用默认编号
|
||||||
String entryName = (String) itemData.getOrDefault("fileName", "摸底测试_" + index);
|
String entryName = (String) itemData.getOrDefault("fileName", + index);
|
||||||
// 确保文件名后缀正确
|
// 确保文件名后缀正确
|
||||||
if (!entryName.endsWith(".docx")) {
|
if (!entryName.endsWith(".docx")) {
|
||||||
entryName += ".docx";
|
entryName += ".docx";
|
||||||
@@ -143,9 +165,9 @@ public class WordExportUtil {
|
|||||||
/**
|
/**
|
||||||
* 现有的单文件导出逻辑
|
* 现有的单文件导出逻辑
|
||||||
*/
|
*/
|
||||||
private static void generateExamWordsDocx(Map<String, Object> data, HttpServletResponse response, String templateWordPath) throws IOException {
|
private static void generateExamWordsDocx(Map<String, Object> data, ExamWordsDO examWordsDO, HttpServletResponse response, String templateWordPath) throws IOException {
|
||||||
|
|
||||||
String fileName = URLEncoder.encode("摸底测试" + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
String fileName = URLEncoder.encode(examWordsDO.getTitle() + ".docx", StandardCharsets.UTF_8).replaceAll("\\+", "%20");
|
||||||
|
|
||||||
// 3. 设置响应头
|
// 3. 设置响应头
|
||||||
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
|
||||||
@@ -160,4 +182,38 @@ public class WordExportUtil {
|
|||||||
out.flush();
|
out.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] generateQR(String content) {
|
||||||
|
int width = 300;
|
||||||
|
int height = 300;
|
||||||
|
String format = "png";
|
||||||
|
|
||||||
|
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||||
|
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
|
||||||
|
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
|
||||||
|
hints.put(EncodeHintType.MARGIN, 1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. 生成比特矩阵
|
||||||
|
BitMatrix bitMatrix = new MultiFormatWriter().encode(
|
||||||
|
content,
|
||||||
|
BarcodeFormat.QR_CODE,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
hints
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. 将 BitMatrix 转为字节数组
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
// MatrixToImageWriter 是 Zxing 提供的工具类
|
||||||
|
MatrixToImageWriter.writeToStream(bitMatrix, format, outputStream);
|
||||||
|
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
|
||||||
|
} catch (WriterException | IOException e) {
|
||||||
|
// 建议增加对 IOException 的捕获,因为涉及流操作
|
||||||
|
throw new RuntimeException("生成二维码失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ spring:
|
|||||||
datasource:
|
datasource:
|
||||||
driver-class-name: com.mysql.cj.jdbc.Driver # 指定数据库驱动类
|
driver-class-name: com.mysql.cj.jdbc.Driver # 指定数据库驱动类
|
||||||
# 数据库连接信息
|
# 数据库连接信息
|
||||||
url: jdbc:mysql://124.220.58.5:3306/enlish?allowMultiQueries=true
|
url: jdbc:mysql://124.220.58.5:3306/dev_english?allowMultiQueries=true
|
||||||
username: root # 数据库用户名
|
username: root # 数据库用户名
|
||||||
password: YLHP@admin123 # 数据库密码
|
password: YLHP@admin123 # 数据库密码
|
||||||
data:
|
data:
|
||||||
@@ -19,19 +19,34 @@ spring:
|
|||||||
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
min-idle: 0 # 连接池中的最小空闲连接
|
min-idle: 0 # 连接池中的最小空闲连接
|
||||||
max-idle: 10 # 连接池中的最大空闲连接
|
max-idle: 10 # 连接池中的最大空闲连接
|
||||||
|
ai:
|
||||||
|
openai:
|
||||||
|
api-key: your_api_key_here
|
||||||
|
base-url: http://124.220.58.5:2233
|
||||||
|
audio:
|
||||||
|
speech:
|
||||||
|
options:
|
||||||
|
model: tts-1
|
||||||
|
voice: alloy
|
||||||
|
|
||||||
|
|
||||||
templates:
|
templates:
|
||||||
word: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\assessment_v5.docx
|
word: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\assessment_v9.docx
|
||||||
count: 100
|
count: 100
|
||||||
data: C:\project\tess
|
data: C:\project\tess
|
||||||
plan:
|
plan:
|
||||||
weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v1.docx
|
weekday: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\tem_study_plan_v7.docx
|
||||||
weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v1.docx
|
weekend: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\study_plan_review_v3.docx
|
||||||
plan_day: 7
|
plan_day: 7
|
||||||
tmp:
|
tmp:
|
||||||
png: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\tmp\png\
|
png:
|
||||||
|
|
||||||
ai:
|
ai:
|
||||||
key: app-loC6IrJpj4cS54MAYp73QtGl
|
key: app-loC6IrJpj4cS54MAYp73QtGl
|
||||||
|
analyzeKey: app-hrUFcopdcpnflsvpHWRuBfCp
|
||||||
|
sentenceKey: app-Emk5YQBaD2YruRXuE5sK1vEU
|
||||||
url: https://chat.cosonggle.com/v1/chat-messages
|
url: https://chat.cosonggle.com/v1/chat-messages
|
||||||
|
|
||||||
|
aliyun:
|
||||||
|
accessKeyId:
|
||||||
|
accessKeySecret:
|
||||||
|
|||||||
52
enlish-service/src/main/resources/config/application-pro.yml
Normal file
52
enlish-service/src/main/resources/config/application-pro.yml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver # 指定数据库驱动类
|
||||||
|
# 数据库连接信息
|
||||||
|
url: jdbc:mysql://124.220.58.5:3306/enlish?allowMultiQueries=true
|
||||||
|
username: root # 数据库用户名
|
||||||
|
password: YLHP@admin123 # 数据库密码
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
database: 6 # Redis 数据库索引(默认为 0)
|
||||||
|
host: 124.220.58.5 # Redis 服务器地址
|
||||||
|
port: 6543 # Redis 服务器连接端口
|
||||||
|
password: 741963 # Redis 服务器连接密码(默认为空)
|
||||||
|
timeout: 5s # 读超时时间
|
||||||
|
connect-timeout: 5s # 链接超时时间
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
max-active: 200 # 连接池最大连接数
|
||||||
|
max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
min-idle: 0 # 连接池中的最小空闲连接
|
||||||
|
max-idle: 10 # 连接池中的最大空闲连接
|
||||||
|
ai:
|
||||||
|
openai:
|
||||||
|
api-key: your_api_key_here
|
||||||
|
base-url: http://124.220.58.5:2233
|
||||||
|
audio:
|
||||||
|
speech:
|
||||||
|
options:
|
||||||
|
model: tts-1
|
||||||
|
voice: alloy
|
||||||
|
|
||||||
|
|
||||||
|
templates:
|
||||||
|
word: assessment_v9.docx
|
||||||
|
count: 100
|
||||||
|
data:
|
||||||
|
plan:
|
||||||
|
weekday: tem_study_plan_v7.docx
|
||||||
|
weekend: study_plan_review_v3.docx
|
||||||
|
plan_day: 7
|
||||||
|
tmp:
|
||||||
|
png:
|
||||||
|
|
||||||
|
ai:
|
||||||
|
key: app-loC6IrJpj4cS54MAYp73QtGl
|
||||||
|
analyzeKey: app-hrUFcopdcpnflsvpHWRuBfCp
|
||||||
|
sentenceKey: app-Emk5YQBaD2YruRXuE5sK1vEU
|
||||||
|
url: https://chat.cosonggle.com/v1/chat-messages
|
||||||
|
|
||||||
|
aliyun:
|
||||||
|
accessKeyId:
|
||||||
|
accessKeySecret:
|
||||||
@@ -3,8 +3,34 @@ server:
|
|||||||
|
|
||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
active: dev # 默认激活 dev 本地开发环境
|
active: pro # 默认激活 dev 本地开发环境
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 30MB
|
||||||
|
max-request-size: 30MB
|
||||||
mybatis:
|
mybatis:
|
||||||
# MyBatis xml 配置文件路径
|
# MyBatis xml 配置文件路径
|
||||||
mapper-locations: classpath:/mapper/**/*.xml
|
mapper-locations: classpath:/mapper/**/*.xml
|
||||||
|
|
||||||
|
|
||||||
|
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
|
||||||
|
sa-token:
|
||||||
|
# token 名称(同时也是 cookie 名称)
|
||||||
|
token-name: Authorization
|
||||||
|
# token 前缀
|
||||||
|
token-prefix: Bearer
|
||||||
|
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
|
||||||
|
token-style: random-128
|
||||||
|
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
|
||||||
|
timeout: 2592000
|
||||||
|
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
|
||||||
|
active-timeout: -1
|
||||||
|
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
|
||||||
|
is-concurrent: true
|
||||||
|
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
|
||||||
|
is-share: true
|
||||||
|
# 是否输出操作日志
|
||||||
|
is-log: true
|
||||||
|
#logging:
|
||||||
|
# level:
|
||||||
|
# com.yinlihupo.enlish.service.domain.mapper: debug
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
targetProject="src/main/java"/>
|
targetProject="src/main/java"/>
|
||||||
|
|
||||||
<!-- 需要生成的表-实体类 -->
|
<!-- 需要生成的表-实体类 -->
|
||||||
<table tableName="student_lesson_plans" domainObjectName="StudentLessonPlansDO"
|
<table tableName="plan_exam" domainObjectName="PlanExamDO"
|
||||||
enableCountByExample="false"
|
enableCountByExample="false"
|
||||||
enableUpdateByExample="false"
|
enableUpdateByExample="false"
|
||||||
enableDeleteByExample="false"
|
enableDeleteByExample="false"
|
||||||
|
|||||||
@@ -14,10 +14,16 @@
|
|||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||||
insert into exam_words (grade_id, level, title, word_ids, created_at)
|
insert into exam_words (grade_id, level, title, word_ids, type, created_at)
|
||||||
VALUES (#{gradeId}, #{level}, #{title}, #{wordIds, typeHandler=com.yinlihupo.enlish.service.config.ListWordIdTypeHandler}, #{createdAt})
|
VALUES (#{gradeId}, #{level}, #{title}, #{wordIds, typeHandler=com.yinlihupo.enlish.service.config.ListWordIdTypeHandler}, #{type}, #{createdAt})
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateWordIdsOrder">
|
||||||
|
update exam_words
|
||||||
|
set word_ids = #{wordIds, typeHandler=com.yinlihupo.enlish.service.config.ListWordIdTypeHandler}
|
||||||
|
where id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
<select id="selectById" resultMap="ResultMapWithBLOBs">
|
<select id="selectById" resultMap="ResultMapWithBLOBs">
|
||||||
select * from exam_words where id = #{id}
|
select * from exam_words where id = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
select count(1)
|
select count(1)
|
||||||
from exam_words_judge_result
|
from exam_words_judge_result
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="selectDetailById" resultMap="ResultMapWithBLOBs">
|
<select id="selectDetailById" resultMap="ResultMapWithBLOBs">
|
||||||
select *
|
select *
|
||||||
from exam_words_judge_result
|
from exam_words_judge_result
|
||||||
@@ -74,5 +75,29 @@
|
|||||||
order by start_date desc
|
order by start_date desc
|
||||||
limit 500;
|
limit 500;
|
||||||
</select>
|
</select>
|
||||||
|
<select id="selectByStudentIdAndLimitTime" resultMap="ResultMapWithBLOBs">
|
||||||
|
select *
|
||||||
|
from exam_words_judge_result
|
||||||
|
where student_id = #{studentId}
|
||||||
|
and start_date between date_sub(now(), interval 7 day) and now()
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByPageAndStudentIds" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from exam_words_judge_result
|
||||||
|
where student_id in
|
||||||
|
<foreach item="item" index="index" collection="studentIds"
|
||||||
|
open="(" separator="," close=")">
|
||||||
|
#{item}
|
||||||
|
</foreach>
|
||||||
|
order by start_date
|
||||||
|
limit #{startIndex}, #{pageSize}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectUnfinishedCount" resultType="java.lang.Integer">
|
||||||
|
select count(*)
|
||||||
|
from exam_words_judge_result
|
||||||
|
where is_finished = 0
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
@@ -39,4 +39,15 @@
|
|||||||
where id = #{lessonId}
|
where id = #{lessonId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectByStudentId" resultMap="BaseResultMap">
|
||||||
|
select *
|
||||||
|
from lesson_plans
|
||||||
|
where id in (
|
||||||
|
select student_lesson_plans.plan_id
|
||||||
|
from student_lesson_plans
|
||||||
|
where student_id = #{studentId}
|
||||||
|
and is_finished = 0
|
||||||
|
)
|
||||||
|
</select>
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user