feat: add teacher student demo sessions

main
chenyuan 1 month ago
parent 52ba31731c
commit 0ef5edd1f3

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS student_demo_session (
id varchar(64) NOT NULL,
ticket_hash char(64) NOT NULL,
teacher_user_id varchar(64) NOT NULL,
teacher_name varchar(128) NULL,
school_id varchar(64) NOT NULL,
teaching_class_id varchar(64) NOT NULL,
teaching_class_name varchar(255) NULL,
expires_at datetime NOT NULL,
used_at datetime NULL,
create_time datetime NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_student_demo_session_ticket_hash (ticket_hash),
KEY idx_student_demo_session_expires_at (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

@ -33,6 +33,9 @@ public class JwtUser implements UserDetails {
private Date authorizeTime;
private Date authorizeEndTime;
private List<String> authorityCodes;
private boolean demoMode;
private String demoTeachingClassId;
private String sourceTeacherId;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {

@ -22,6 +22,7 @@ public class TokenProvider {
private final static String SECRET = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAghgsx+OLLThi7c+/HgCaRla8n+/QLHt++uscoK22dMeaVa3WJvRb5C5qtHGzj9V74pI96JpxdWWCN3Zr1QtanQIDAQABAkB2fWRUD1jVMBVS+rPctLnPNPMKTRdzXGv0SC5D3tRfGZqdcfsmZg8hzBVndHxngmwZS73d+hPjofcdefDDnXq1AiEAz/r1AMF7fweN70mbKgBJuTja37puJMsH2Szwrvb5jlcCIQCgIaLZ5d9J7OomL0NwH8ghba/xgBEpKzErfGbFnTfeKwIgc8ptczFFV1DWZb2eJIEqinOr79l0jwl2YiQSD+cyw1sCIHqxCvv1Wx5jPgy/zeYNE+Q5DMP3Ii7u/x+Yk436JiI9AiEAio84hFm44YTUuV7YCYsbyvnCn/vtcAaqZlczbeHlP1I=";
private final static long EXP_TIME = 1000 * 60 * 60 * 12;
private final static long DEMO_EXP_TIME = 1000 * 60 * 30;
private final static String SECRET_ZHIYUN = "zy_szmy_mnjy_fp76ckwuczzmb67w0a8x0";
@ -63,6 +64,9 @@ public class TokenProvider {
jwtUser.setUsername(claims.get("username")==null?"":claims.get("username").toString());
jwtUser.setAuthorityCodes((List<String>) claims.get("authorityCodes"));
jwtUser.setDemoMode(Boolean.TRUE.equals(claims.get("demoMode")));
jwtUser.setDemoTeachingClassId(claims.get("demoTeachingClassId") == null ? null : claims.get("demoTeachingClassId").toString());
jwtUser.setSourceTeacherId(claims.get("sourceTeacherId") == null ? null : claims.get("sourceTeacherId").toString());
return jwtUser;
} catch (ExpiredJwtException e1) {
throw new ExpiredJwtException(null, null, "token过期");
@ -140,8 +144,16 @@ public class TokenProvider {
public static String createToken(JwtUser jwtUser) {
return createToken(jwtUser, EXP_TIME);
}
public static String createDemoToken(JwtUser jwtUser) {
return createToken(jwtUser, DEMO_EXP_TIME);
}
private static String createToken(JwtUser jwtUser, long expirationMillis) {
Key key = Keys.hmacShaKeyFor(SECRET.getBytes());
Date expiration = new Date(System.currentTimeMillis() + EXP_TIME);
Date expiration = new Date(System.currentTimeMillis() + expirationMillis);
return Jwts.builder()
.claim("userId", jwtUser.getUserId())
.claim("roleId", jwtUser.getRoleId())
@ -150,6 +162,9 @@ public class TokenProvider {
.claim("schoolId", jwtUser.getSchoolId())
.claim("username", jwtUser.getUsername())
.claim("authorityCodes", jwtUser.getAuthorityCodes())
.claim("demoMode", jwtUser.isDemoMode())
.claim("demoTeachingClassId", jwtUser.getDemoTeachingClassId())
.claim("sourceTeacherId", jwtUser.getSourceTeacherId())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(expiration)
.signWith(key, SignatureAlgorithm.HS512)

@ -0,0 +1,49 @@
package com.sztzjy.linkCommerce.controller.stu;
import com.sztzjy.linkCommerce.annotation.AnonymousAccess;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.config.security.TokenProvider;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionCreateRequest;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionExchangeRequest;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionLogin;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionTicket;
import com.sztzjy.linkCommerce.service.StudentDemoSessionService;
import com.sztzjy.linkCommerce.util.ResultEntity;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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 javax.servlet.http.HttpServletRequest;
@Api(tags = "教师学生端讲解会话")
@RestController
public class StudentDemoSessionController {
@Autowired
public StudentDemoSessionService studentDemoSessionService;
@PostMapping("/api/teacher/student-demo-sessions")
@ApiOperation("教师创建学生端讲解票据")
public ResultEntity<StudentDemoSessionTicket> create(@RequestBody StudentDemoSessionCreateRequest body,
HttpServletRequest request) {
StudentDemoSessionTicket ticket = studentDemoSessionService.create(currentUser(request),
body == null ? null : body.getTeachingClassId());
return new ResultEntity<>(HttpStatus.OK, "Demo ticket created", ticket);
}
@PostMapping("/api/student-demo-sessions/exchange")
@AnonymousAccess
@ApiOperation("学生端讲解票据兑换")
public ResultEntity<StudentDemoSessionLogin> exchange(@RequestBody StudentDemoSessionExchangeRequest body) {
StudentDemoSessionLogin login = studentDemoSessionService.exchange(body == null ? null : body.getTicket());
return new ResultEntity<>(HttpStatus.OK, "Demo session created", login);
}
protected JwtUser currentUser(HttpServletRequest request) {
return TokenProvider.getJWTUser(request);
}
}

@ -1,6 +1,8 @@
package com.sztzjy.linkCommerce.controller.stu;
import com.sztzjy.linkCommerce.annotation.AnonymousAccess;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.config.security.TokenProvider;
import com.sztzjy.linkCommerce.entity.SchoolClass;
import com.sztzjy.linkCommerce.entity.TaskAllocation;
import com.sztzjy.linkCommerce.entity.TaskAllocationExample;
@ -9,6 +11,7 @@ import com.sztzjy.linkCommerce.mapper.SchoolClassMapper;
import com.sztzjy.linkCommerce.mapper.TaskAllocationMapper;
import com.sztzjy.linkCommerce.mapper.TeachingClassStudentMapper;
import com.sztzjy.linkCommerce.service.SchoolDefaultTaskService;
import com.sztzjy.linkCommerce.service.StudentTeachingClassResolver;
import com.sztzjy.linkCommerce.util.ResultEntity;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -17,6 +20,8 @@ import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -33,6 +38,8 @@ public class TaskAllocationController {
TeachingClassStudentMapper teachingClassStudentMapper;
@Autowired(required = false)
SchoolDefaultTaskService schoolDefaultTaskService;
@Autowired(required = false)
StudentTeachingClassResolver studentTeachingClassResolver;
@PostMapping("/selectTaskAllocationByClassId")
@ApiOperation("根据班级ID查询任务分配")
@ -57,7 +64,12 @@ public class TaskAllocationController {
@PostMapping("/selectTaskAllocationByStudentUserId")
@ApiOperation("学生端-根据当前教学班查询任务分配")
@AnonymousAccess
public ResultEntity<List<TaskAllocation>> selectTaskAllocationByStudentUserId(@RequestParam String userId) {
public ResultEntity<List<TaskAllocation>> selectTaskAllocationByStudentUserId(@RequestParam String userId,
HttpServletRequest request) {
JwtUser currentUser = currentStudent(request);
if (currentUser != null && studentTeachingClassResolver != null) {
return selectTaskAllocationByClassId(studentTeachingClassResolver.resolveRequired(currentUser));
}
TeachingClassStudent member = teachingClassStudentMapper == null
? null
: teachingClassStudentMapper.selectActiveByStudentUserId(userId);
@ -67,6 +79,15 @@ public class TaskAllocationController {
return selectTaskAllocationByClassId(member.getTeachingClassId());
}
private JwtUser currentStudent(HttpServletRequest request) {
try {
JwtUser user = TokenProvider.getJWTUser(request);
return user != null && user.getRoleId() == 4 ? user : null;
} catch (RuntimeException ignored) {
return null;
}
}
@PostMapping("/updateTaskAllocationByClassId")
@ApiOperation("编辑任务分配")
@AnonymousAccess

@ -42,7 +42,7 @@ public class TrainingTaskController {
JwtUser user = TokenProvider.getJWTUser(request);
if (isStudent(user)) {
return new ResultEntity<>(HttpStatus.OK, "Query success",
trainingTaskService.listForStudent(user.getUserId(), enabledOnly));
trainingTaskService.listForStudent(user, enabledOnly));
}
return new ResultEntity<>(HttpStatus.OK, "Query success",
trainingTaskService.listDefaults(projectName, taskName, enabledOnly));
@ -64,7 +64,7 @@ public class TrainingTaskController {
public ResultEntity<TrainingTask> detailByTaskKey(@PathVariable String taskKey, HttpServletRequest request) {
JwtUser user = TokenProvider.getJWTUser(request);
TrainingTask task = isStudent(user)
? trainingTaskService.getStudentTaskByTaskKey(taskKey, user.getUserId())
? trainingTaskService.getStudentTaskByTaskKey(taskKey, user)
: trainingTaskService.getByTaskKey(taskKey);
if (task == null) {
return new ResultEntity<>(HttpStatus.OK, "Training task not configured", null);

@ -20,6 +20,7 @@ import com.sztzjy.linkCommerce.entity.importDto.TeacherImportDTO;
import com.sztzjy.linkCommerce.mapper.*;
import com.sztzjy.linkCommerce.service.ImportService;
import com.sztzjy.linkCommerce.service.SchoolService;
import com.sztzjy.linkCommerce.service.StudentTeachingClassResolver;
import com.sztzjy.linkCommerce.service.SchoolProductConfigService;
import com.sztzjy.linkCommerce.service.UserInfoService;
import com.sztzjy.linkCommerce.service.ValidateService;
@ -69,6 +70,8 @@ public class UserController {
ValidateService validateService;
@Autowired
ImportService importService;
@Autowired(required = false)
StudentTeachingClassResolver studentTeachingClassResolver;
@PostMapping("/addSchool")
@ -2164,7 +2167,16 @@ public class UserController {
@PostMapping("/selectCurrentTeachingClass")
@ApiOperation("学生-查询当前在读教学班")
@AnonymousAccess
public ResultEntity<SchoolClass> selectCurrentTeachingClass(@RequestParam String userId) {
public ResultEntity<SchoolClass> selectCurrentTeachingClass(@RequestParam String userId, HttpServletRequest request) {
try {
JwtUser currentUser = TokenProvider.getJWTUser(request);
if (currentUser != null && currentUser.getRoleId() == 4 && studentTeachingClassResolver != null) {
SchoolClass teachingClass = schoolClassMapper.selectByPrimaryKey(studentTeachingClassResolver.resolveRequired(currentUser));
return new ResultEntity<>(HttpStatus.OK, "查询成功", teachingClass);
}
} catch (RuntimeException ignored) {
// 保留旧版匿名调用的兼容路径。
}
TeachingClassStudent member = teachingClassStudentMapper == null
? null
: teachingClassStudentMapper.selectActiveByStudentUserId(userId);

@ -0,0 +1,37 @@
package com.sztzjy.linkCommerce.entity;
import java.util.Date;
public class StudentDemoSession {
private String id;
private String ticketHash;
private String teacherUserId;
private String teacherName;
private String schoolId;
private String teachingClassId;
private String teachingClassName;
private Date expiresAt;
private Date usedAt;
private Date createTime;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getTicketHash() { return ticketHash; }
public void setTicketHash(String ticketHash) { this.ticketHash = ticketHash; }
public String getTeacherUserId() { return teacherUserId; }
public void setTeacherUserId(String teacherUserId) { this.teacherUserId = teacherUserId; }
public String getTeacherName() { return teacherName; }
public void setTeacherName(String teacherName) { this.teacherName = teacherName; }
public String getSchoolId() { return schoolId; }
public void setSchoolId(String schoolId) { this.schoolId = schoolId; }
public String getTeachingClassId() { return teachingClassId; }
public void setTeachingClassId(String teachingClassId) { this.teachingClassId = teachingClassId; }
public String getTeachingClassName() { return teachingClassName; }
public void setTeachingClassName(String teachingClassName) { this.teachingClassName = teachingClassName; }
public Date getExpiresAt() { return expiresAt; }
public void setExpiresAt(Date expiresAt) { this.expiresAt = expiresAt; }
public Date getUsedAt() { return usedAt; }
public void setUsedAt(Date usedAt) { this.usedAt = usedAt; }
public Date getCreateTime() { return createTime; }
public void setCreateTime(Date createTime) { this.createTime = createTime; }
}

@ -0,0 +1,8 @@
package com.sztzjy.linkCommerce.entity.dto;
public class StudentDemoSessionCreateRequest {
private String teachingClassId;
public String getTeachingClassId() { return teachingClassId; }
public void setTeachingClassId(String teachingClassId) { this.teachingClassId = teachingClassId; }
}

@ -0,0 +1,8 @@
package com.sztzjy.linkCommerce.entity.dto;
public class StudentDemoSessionExchangeRequest {
private String ticket;
public String getTicket() { return ticket; }
public void setTicket(String ticket) { this.ticket = ticket; }
}

@ -0,0 +1,35 @@
package com.sztzjy.linkCommerce.entity.dto;
public class StudentDemoSessionLogin {
private String token;
private String userId;
private String name;
private String username;
private String roleId;
private String schoolId;
private String classId;
private String className;
private boolean demoMode;
private String demoTeachingClassId;
public String getToken() { return token; }
public void setToken(String token) { this.token = token; }
public String getUserId() { return userId; }
public void setUserId(String userId) { this.userId = userId; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getRoleId() { return roleId; }
public void setRoleId(String roleId) { this.roleId = roleId; }
public String getSchoolId() { return schoolId; }
public void setSchoolId(String schoolId) { this.schoolId = schoolId; }
public String getClassId() { return classId; }
public void setClassId(String classId) { this.classId = classId; }
public String getClassName() { return className; }
public void setClassName(String className) { this.className = className; }
public boolean isDemoMode() { return demoMode; }
public void setDemoMode(boolean demoMode) { this.demoMode = demoMode; }
public String getDemoTeachingClassId() { return demoTeachingClassId; }
public void setDemoTeachingClassId(String demoTeachingClassId) { this.demoTeachingClassId = demoTeachingClassId; }
}

@ -0,0 +1,19 @@
package com.sztzjy.linkCommerce.entity.dto;
import java.util.Date;
public class StudentDemoSessionTicket {
private String ticket;
private Date expiresAt;
private String teachingClassId;
private String className;
public String getTicket() { return ticket; }
public void setTicket(String ticket) { this.ticket = ticket; }
public Date getExpiresAt() { return expiresAt; }
public void setExpiresAt(Date expiresAt) { this.expiresAt = expiresAt; }
public String getTeachingClassId() { return teachingClassId; }
public void setTeachingClassId(String teachingClassId) { this.teachingClassId = teachingClassId; }
public String getClassName() { return className; }
public void setClassName(String className) { this.className = className; }
}

@ -0,0 +1,16 @@
package com.sztzjy.linkCommerce.mapper;
import com.sztzjy.linkCommerce.entity.StudentDemoSession;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
@Mapper
public interface StudentDemoSessionMapper {
int insertSelective(StudentDemoSession record);
StudentDemoSession selectByTicketHash(@Param("ticketHash") String ticketHash);
int markUsedIfActive(@Param("id") String id, @Param("usedAt") Date usedAt);
}

@ -0,0 +1,11 @@
package com.sztzjy.linkCommerce.service;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionLogin;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionTicket;
public interface StudentDemoSessionService {
StudentDemoSessionTicket create(JwtUser teacher, String teachingClassId);
StudentDemoSessionLogin exchange(String ticket);
}

@ -0,0 +1,9 @@
package com.sztzjy.linkCommerce.service;
import com.sztzjy.linkCommerce.config.security.JwtUser;
public interface StudentTeachingClassResolver {
String resolveRequired(JwtUser user);
String resolveRequested(JwtUser user, String requestedTeachingClassId);
}

@ -1,5 +1,6 @@
package com.sztzjy.linkCommerce.service;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.TrainingTask;
import com.sztzjy.linkCommerce.entity.importDto.TrainingTaskImportDTO;
@ -38,5 +39,9 @@ public interface TrainingTaskService {
TrainingTask getStudentTaskByTaskKey(String taskKey, String studentUserId);
TrainingTask getStudentTaskByTaskKey(String taskKey, JwtUser student);
List<TrainingTask> listForStudent(String studentUserId, Boolean enabledOnly);
List<TrainingTask> listForStudent(JwtUser student, Boolean enabledOnly);
}

@ -132,6 +132,7 @@ public class AiTrainingEvaluationServiceImpl implements AiTrainingEvaluationServ
private EvaluationContext resolveContext(String taskKey, JwtUser user, boolean requireAnswer) {
requireStudent(user);
requireNotDemo(user);
TrainingTask task = resolveTask(taskKey);
TeachingClassStudent member = teachingClassStudentMapper.selectActiveByStudentUserId(user.getUserId());
if (member == null || StringUtils.isBlank(member.getTeachingClassId())) {
@ -201,6 +202,12 @@ public class AiTrainingEvaluationServiceImpl implements AiTrainingEvaluationServ
}
}
private void requireNotDemo(JwtUser user) {
if (user != null && user.isDemoMode()) {
throw new ServiceException(HttpStatus.FORBIDDEN, "演示模式不保存数据");
}
}
private TrainingTask resolveTask(String taskKey) {
String normalizedTaskKey = StringUtils.trimToEmpty(taskKey);
if (StringUtils.isBlank(normalizedTaskKey)) {

@ -0,0 +1,131 @@
package com.sztzjy.linkCommerce.service.impl;
import com.sztzjy.linkCommerce.config.exception.handler.ServiceException;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.config.security.TokenProvider;
import com.sztzjy.linkCommerce.entity.SchoolClass;
import com.sztzjy.linkCommerce.entity.StudentDemoSession;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionLogin;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionTicket;
import com.sztzjy.linkCommerce.mapper.SchoolClassMapper;
import com.sztzjy.linkCommerce.mapper.StudentDemoSessionMapper;
import com.sztzjy.linkCommerce.service.StudentDemoSessionService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
@Service
public class StudentDemoSessionServiceImpl implements StudentDemoSessionService {
private static final long TICKET_EXPIRATION_MILLIS = 5 * 60 * 1000L;
private static final SecureRandom RANDOM = new SecureRandom();
@Autowired
public SchoolClassMapper schoolClassMapper;
@Autowired
public StudentDemoSessionMapper studentDemoSessionMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public StudentDemoSessionTicket create(JwtUser teacher, String teachingClassId) {
requireTeacher(teacher);
SchoolClass schoolClass = schoolClassMapper.selectByPrimaryKey(StringUtils.trimToEmpty(teachingClassId));
if (schoolClass == null || !"TEACHING".equals(schoolClass.getClassType())
|| !StringUtils.equals(teacher.getUserId(), schoolClass.getCreatedBy())
|| !StringUtils.equals(teacher.getSchoolId(), schoolClass.getSchoolId())) {
throw new ServiceException(HttpStatus.FORBIDDEN, "Only the creator can demonstrate this teaching class");
}
Date now = new Date();
Date expiresAt = new Date(now.getTime() + TICKET_EXPIRATION_MILLIS);
String ticket = nextTicket();
StudentDemoSession session = new StudentDemoSession();
session.setId(UUID.randomUUID().toString());
session.setTicketHash(hash(ticket));
session.setTeacherUserId(teacher.getUserId());
session.setTeacherName(teacher.getName());
session.setSchoolId(teacher.getSchoolId());
session.setTeachingClassId(schoolClass.getSchoolClassId());
session.setTeachingClassName(schoolClass.getClassName());
session.setExpiresAt(expiresAt);
session.setCreateTime(now);
studentDemoSessionMapper.insertSelective(session);
StudentDemoSessionTicket result = new StudentDemoSessionTicket();
result.setTicket(ticket);
result.setExpiresAt(expiresAt);
result.setTeachingClassId(session.getTeachingClassId());
result.setClassName(session.getTeachingClassName());
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public StudentDemoSessionLogin exchange(String ticket) {
String normalizedTicket = StringUtils.trimToEmpty(ticket);
if (StringUtils.isBlank(normalizedTicket)) {
throw new ServiceException(HttpStatus.BAD_REQUEST, "Demo ticket is required");
}
StudentDemoSession session = studentDemoSessionMapper.selectByTicketHash(hash(normalizedTicket));
Date now = new Date();
if (session == null || session.getUsedAt() != null || session.getExpiresAt() == null || !session.getExpiresAt().after(now)
|| studentDemoSessionMapper.markUsedIfActive(session.getId(), now) != 1) {
throw new ServiceException(HttpStatus.FORBIDDEN, "Demo ticket is invalid or expired");
}
JwtUser demoUser = new JwtUser();
demoUser.setUserId("demo:" + session.getId());
demoUser.setName(StringUtils.defaultIfBlank(session.getTeacherName(), "Teacher") + "(演示)");
demoUser.setUsername("demo:" + session.getTeacherUserId());
demoUser.setRoleId(4);
demoUser.setSchoolId(session.getSchoolId());
demoUser.setDemoMode(true);
demoUser.setDemoTeachingClassId(session.getTeachingClassId());
demoUser.setSourceTeacherId(session.getTeacherUserId());
StudentDemoSessionLogin result = new StudentDemoSessionLogin();
result.setToken(TokenProvider.createDemoToken(demoUser));
result.setUserId(demoUser.getUserId());
result.setName(demoUser.getName());
result.setUsername(demoUser.getUsername());
result.setRoleId("4");
result.setSchoolId(session.getSchoolId());
result.setClassId(session.getTeachingClassId());
result.setClassName(session.getTeachingClassName());
result.setDemoMode(true);
result.setDemoTeachingClassId(session.getTeachingClassId());
return result;
}
private void requireTeacher(JwtUser teacher) {
if (teacher == null || teacher.getRoleId() != 3 || StringUtils.isBlank(teacher.getUserId()) || StringUtils.isBlank(teacher.getSchoolId())) {
throw new ServiceException(HttpStatus.FORBIDDEN, "Teacher only");
}
}
private String nextTicket() {
byte[] bytes = new byte[32];
RANDOM.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
private String hash(String value) {
try {
byte[] bytes = MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8));
StringBuilder builder = new StringBuilder(bytes.length * 2);
for (byte valueByte : bytes) {
builder.append(String.format("%02x", valueByte));
}
return builder.toString();
} catch (Exception e) {
throw new IllegalStateException("Unable to hash demo ticket", e);
}
}
}

@ -0,0 +1,65 @@
package com.sztzjy.linkCommerce.service.impl;
import com.sztzjy.linkCommerce.config.exception.handler.ServiceException;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.SchoolClass;
import com.sztzjy.linkCommerce.entity.TeachingClassStudent;
import com.sztzjy.linkCommerce.mapper.SchoolClassMapper;
import com.sztzjy.linkCommerce.mapper.TeachingClassStudentMapper;
import com.sztzjy.linkCommerce.service.StudentTeachingClassResolver;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@Service
public class StudentTeachingClassResolverImpl implements StudentTeachingClassResolver {
@Autowired
public SchoolClassMapper schoolClassMapper;
@Autowired
public TeachingClassStudentMapper teachingClassStudentMapper;
@Override
public String resolveRequired(JwtUser user) {
requireStudent(user);
String teachingClassId;
if (user.isDemoMode()) {
teachingClassId = StringUtils.trimToNull(user.getDemoTeachingClassId());
if (teachingClassId == null) {
throw new ServiceException(HttpStatus.FORBIDDEN, "Demo teaching class is required");
}
} else {
TeachingClassStudent membership = teachingClassStudentMapper.selectActiveByStudentUserId(user.getUserId());
teachingClassId = membership == null ? null : StringUtils.trimToNull(membership.getTeachingClassId());
if (teachingClassId == null) {
throw new ServiceException(HttpStatus.BAD_REQUEST, "Student has no active teaching class");
}
}
validateTeachingClass(user, teachingClassId);
return teachingClassId;
}
@Override
public String resolveRequested(JwtUser user, String requestedTeachingClassId) {
String resolved = resolveRequired(user);
String requested = StringUtils.trimToNull(requestedTeachingClassId);
if (requested != null && !StringUtils.equals(resolved, requested)) {
throw new ServiceException(HttpStatus.FORBIDDEN, "Teaching class does not match current student context");
}
return resolved;
}
private void validateTeachingClass(JwtUser user, String teachingClassId) {
SchoolClass schoolClass = schoolClassMapper.selectByPrimaryKey(teachingClassId);
if (schoolClass == null || !"TEACHING".equals(schoolClass.getClassType())
|| !StringUtils.equals(user.getSchoolId(), schoolClass.getSchoolId())) {
throw new ServiceException(HttpStatus.FORBIDDEN, "Teaching class is unavailable");
}
}
private void requireStudent(JwtUser user) {
if (user == null || user.getRoleId() != 4 || StringUtils.isBlank(user.getUserId())) {
throw new ServiceException(HttpStatus.FORBIDDEN, "Student only");
}
}
}

@ -46,6 +46,7 @@ public class StudentTrainingAnswerServiceImpl implements StudentTrainingAnswerSe
public StudentTrainingAnswer get(String taskKey, String teachingClassId, JwtUser user) {
ensureStudentTrainingAnswerTable();
requireStudent(user);
requireNotDemo(user);
TrainingTask task = resolveTask(taskKey);
String resolvedTeachingClassId = resolveTeachingClassId(user.getUserId(), teachingClassId);
return studentTrainingAnswerMapper.selectByStudentClassAndTask(
@ -57,6 +58,7 @@ public class StudentTrainingAnswerServiceImpl implements StudentTrainingAnswerSe
public StudentTrainingAnswer save(String taskKey, StudentTrainingAnswer answer, JwtUser user) {
ensureStudentTrainingAnswerTable();
requireStudent(user);
requireNotDemo(user);
TrainingTask task = resolveTask(taskKey);
String resolvedTeachingClassId = resolveTeachingClassId(user.getUserId(),
answer == null ? null : answer.getTeachingClassId());
@ -89,6 +91,7 @@ public class StudentTrainingAnswerServiceImpl implements StudentTrainingAnswerSe
public void delete(String taskKey, String teachingClassId, JwtUser user) {
ensureStudentTrainingAnswerTable();
requireStudent(user);
requireNotDemo(user);
TrainingTask task = resolveTask(taskKey);
String resolvedTeachingClassId = resolveTeachingClassId(user.getUserId(), teachingClassId);
studentTrainingAnswerMapper.deleteByStudentClassAndTask(user.getUserId(), resolvedTeachingClassId, task.getId());
@ -121,6 +124,12 @@ public class StudentTrainingAnswerServiceImpl implements StudentTrainingAnswerSe
return task;
}
private void requireNotDemo(JwtUser user) {
if (user != null && user.isDemoMode()) {
throw new ServiceException(HttpStatus.FORBIDDEN, "演示模式不保存数据");
}
}
private String resolveTeachingClassId(String studentUserId, String requestedTeachingClassId) {
String trimmedClassId = StringUtils.trimToNull(requestedTeachingClassId);
if (trimmedClassId != null) {

@ -1,6 +1,7 @@
package com.sztzjy.linkCommerce.service.impl;
import com.alibaba.fastjson.JSON;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.SchoolClass;
import com.sztzjy.linkCommerce.entity.SchoolClassExample;
import com.sztzjy.linkCommerce.entity.TeachingClassStudent;
@ -12,6 +13,7 @@ import com.sztzjy.linkCommerce.mapper.TeachingClassStudentMapper;
import com.sztzjy.linkCommerce.mapper.TrainingTaskClassConfigMapper;
import com.sztzjy.linkCommerce.mapper.TrainingTaskMapper;
import com.sztzjy.linkCommerce.service.TrainingTaskService;
import com.sztzjy.linkCommerce.service.StudentTeachingClassResolver;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
@ -46,6 +48,9 @@ public class TrainingTaskServiceImpl implements TrainingTaskService {
@Autowired
public TeachingClassStudentMapper teachingClassStudentMapper;
@Autowired(required = false)
public StudentTeachingClassResolver studentTeachingClassResolver;
@Autowired(required = false)
public JdbcTemplate jdbcTemplate;
@ -266,9 +271,17 @@ public class TrainingTaskServiceImpl implements TrainingTaskService {
@Override
public TrainingTask getStudentTaskByTaskKey(String taskKey, String studentUserId) {
return getStudentTaskByTeachingClass(taskKey, resolveStudentTeachingClassId(studentUserId));
}
@Override
public TrainingTask getStudentTaskByTaskKey(String taskKey, JwtUser student) {
return getStudentTaskByTeachingClass(taskKey, requireStudentTeachingClass(student));
}
private TrainingTask getStudentTaskByTeachingClass(String taskKey, String teachingClassId) {
ensureDefaultTasks(null);
String normalizedTaskKey = StringUtils.trimToEmpty(taskKey);
String teachingClassId = resolveStudentTeachingClassId(studentUserId);
TrainingTask defaultTask = trainingTaskMapper.selectByTaskKey(normalizedTaskKey);
if (StringUtils.isNotBlank(teachingClassId)) {
ensureClassTasksInitialized(teachingClassId, trainingTaskMapper.selectList(null, null, null), null);
@ -282,9 +295,17 @@ public class TrainingTaskServiceImpl implements TrainingTaskService {
@Override
public List<TrainingTask> listForStudent(String studentUserId, Boolean enabledOnly) {
return listForStudentTeachingClass(resolveStudentTeachingClassId(studentUserId), enabledOnly);
}
@Override
public List<TrainingTask> listForStudent(JwtUser student, Boolean enabledOnly) {
return listForStudentTeachingClass(requireStudentTeachingClass(student), enabledOnly);
}
private List<TrainingTask> listForStudentTeachingClass(String teachingClassId, Boolean enabledOnly) {
ensureDefaultTasks(null);
List<TrainingTask> defaults = trainingTaskMapper.selectList(null, null, null);
String teachingClassId = resolveStudentTeachingClassId(studentUserId);
if (StringUtils.isBlank(teachingClassId)) {
return filterEnabled(sortTasks(defaults), enabledOnly);
}
@ -292,6 +313,13 @@ public class TrainingTaskServiceImpl implements TrainingTaskService {
return filterEnabled(toTasks(classTasks), enabledOnly);
}
private String requireStudentTeachingClass(JwtUser student) {
if (studentTeachingClassResolver == null) {
return resolveStudentTeachingClassId(student == null ? null : student.getUserId());
}
return studentTeachingClassResolver.resolveRequired(student);
}
private SchoolClass requireOwnedTeachingClass(String teachingClassId, String operatorId) {
SchoolClass schoolClass = schoolClassMapper.selectByPrimaryKey(StringUtils.trimToEmpty(teachingClassId));
if (schoolClass == null || !"TEACHING".equals(schoolClass.getClassType())) {

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sztzjy.linkCommerce.mapper.StudentDemoSessionMapper">
<resultMap id="BaseResultMap" type="com.sztzjy.linkCommerce.entity.StudentDemoSession">
<id column="id" jdbcType="VARCHAR" property="id" />
<result column="ticket_hash" jdbcType="VARCHAR" property="ticketHash" />
<result column="teacher_user_id" jdbcType="VARCHAR" property="teacherUserId" />
<result column="teacher_name" jdbcType="VARCHAR" property="teacherName" />
<result column="school_id" jdbcType="VARCHAR" property="schoolId" />
<result column="teaching_class_id" jdbcType="VARCHAR" property="teachingClassId" />
<result column="teaching_class_name" jdbcType="VARCHAR" property="teachingClassName" />
<result column="expires_at" jdbcType="TIMESTAMP" property="expiresAt" />
<result column="used_at" jdbcType="TIMESTAMP" property="usedAt" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
</resultMap>
<select id="selectByTicketHash" resultMap="BaseResultMap">
select id, ticket_hash, teacher_user_id, teacher_name, school_id, teaching_class_id,
teaching_class_name, expires_at, used_at, create_time
from student_demo_session where ticket_hash = #{ticketHash,jdbcType=VARCHAR} limit 1
</select>
<insert id="insertSelective" parameterType="com.sztzjy.linkCommerce.entity.StudentDemoSession">
insert into student_demo_session
(id, ticket_hash, teacher_user_id, teacher_name, school_id, teaching_class_id, teaching_class_name, expires_at, used_at, create_time)
values (#{id,jdbcType=VARCHAR}, #{ticketHash,jdbcType=VARCHAR}, #{teacherUserId,jdbcType=VARCHAR},
#{teacherName,jdbcType=VARCHAR}, #{schoolId,jdbcType=VARCHAR}, #{teachingClassId,jdbcType=VARCHAR},
#{teachingClassName,jdbcType=VARCHAR}, #{expiresAt,jdbcType=TIMESTAMP}, #{usedAt,jdbcType=TIMESTAMP},
#{createTime,jdbcType=TIMESTAMP})
</insert>
<update id="markUsedIfActive">
update student_demo_session set used_at = #{usedAt,jdbcType=TIMESTAMP}
where id = #{id,jdbcType=VARCHAR} and used_at is null and expires_at &gt; #{usedAt,jdbcType=TIMESTAMP}
</update>
</mapper>

@ -0,0 +1,79 @@
package com.sztzjy.linkCommerce.controller.stu;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionCreateRequest;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionExchangeRequest;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionLogin;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionTicket;
import com.sztzjy.linkCommerce.service.StudentDemoSessionService;
import com.sztzjy.linkCommerce.util.ResultEntity;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class StudentDemoSessionControllerTest {
@Test
void teacherCanCreateDemoTicketForSelectedTeachingClass() {
TestController controller = new TestController(teacher());
controller.studentDemoSessionService = mock(StudentDemoSessionService.class);
StudentDemoSessionTicket ticket = new StudentDemoSessionTicket();
ticket.setTicket("one-time-ticket");
ticket.setTeachingClassId("class-1");
ticket.setExpiresAt(new Date());
when(controller.studentDemoSessionService.create(controller.user, "class-1")).thenReturn(ticket);
StudentDemoSessionCreateRequest body = new StudentDemoSessionCreateRequest();
body.setTeachingClassId("class-1");
ResultEntity<StudentDemoSessionTicket> result = controller.create(body, mock(HttpServletRequest.class));
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("one-time-ticket", result.getBody().getData().getTicket());
verify(controller.studentDemoSessionService).create(controller.user, "class-1");
}
@Test
void exchangeDoesNotAcceptClassOrTeacherFromBrowser() {
StudentDemoSessionController controller = new StudentDemoSessionController();
controller.studentDemoSessionService = mock(StudentDemoSessionService.class);
StudentDemoSessionLogin login = new StudentDemoSessionLogin();
login.setToken("demo-token");
when(controller.studentDemoSessionService.exchange("one-time-ticket")).thenReturn(login);
StudentDemoSessionExchangeRequest body = new StudentDemoSessionExchangeRequest();
body.setTicket("one-time-ticket");
ResultEntity<StudentDemoSessionLogin> result = controller.exchange(body);
assertEquals(HttpStatus.OK, result.getStatusCode());
assertEquals("demo-token", result.getBody().getData().getToken());
verify(controller.studentDemoSessionService).exchange("one-time-ticket");
}
private JwtUser teacher() {
JwtUser user = new JwtUser();
user.setUserId("teacher-1");
user.setRoleId(3);
user.setSchoolId("school-1");
return user;
}
private static class TestController extends StudentDemoSessionController {
private final JwtUser user;
private TestController(JwtUser user) {
this.user = user;
}
@Override
protected JwtUser currentUser(HttpServletRequest request) {
return user;
}
}
}

@ -188,6 +188,19 @@ class AiTrainingEvaluationServiceImplTest {
verify(service.qwenChatClient, never()).completeJson(any(), any());
}
@Test
void demoStudentCannotCallAiHelpOrCreateEvaluationData() {
AiTrainingEvaluationServiceImpl service = serviceWithContext(answer("answer-1", "step one", null, null, null));
JwtUser demo = student();
demo.setDemoMode(true);
demo.setDemoTeachingClassId("class-1");
assertThrows(ServiceException.class, () -> service.generateHelp("new-product-survey", demo));
verify(service.qwenChatClient, never()).completeJson(any(), any());
verify(service.aiTrainingEvaluationMapper, never()).insertIgnore(any());
}
@Test
void persistsIndependentHelpAndAssessmentFieldsAndAnswerScore() {
AiTrainingEvaluation evaluation = new AiTrainingEvaluation();

@ -0,0 +1,138 @@
package com.sztzjy.linkCommerce.service.impl;
import com.sztzjy.linkCommerce.config.exception.handler.ServiceException;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.config.security.TokenProvider;
import com.sztzjy.linkCommerce.entity.SchoolClass;
import com.sztzjy.linkCommerce.entity.StudentDemoSession;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionLogin;
import com.sztzjy.linkCommerce.entity.dto.StudentDemoSessionTicket;
import com.sztzjy.linkCommerce.mapper.SchoolClassMapper;
import com.sztzjy.linkCommerce.mapper.StudentDemoSessionMapper;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class StudentDemoSessionServiceImplTest {
@Test
void createsShortLivedTicketForTeachersOwnTeachingClass() {
StudentDemoSessionServiceImpl service = buildService();
when(service.schoolClassMapper.selectByPrimaryKey("class-1")).thenReturn(teachingClass());
StudentDemoSessionTicket result = service.create(teacher(), "class-1");
assertNotNull(result.getTicket());
assertTrue(result.getTicket().length() >= 40);
assertEquals("class-1", result.getTeachingClassId());
assertEquals("Teaching Class 1", result.getClassName());
assertTrue(result.getExpiresAt().after(new Date()));
ArgumentCaptor<StudentDemoSession> stored = ArgumentCaptor.forClass(StudentDemoSession.class);
verify(service.studentDemoSessionMapper).insertSelective(stored.capture());
assertEquals("teacher-1", stored.getValue().getTeacherUserId());
assertEquals("school-1", stored.getValue().getSchoolId());
assertEquals("class-1", stored.getValue().getTeachingClassId());
assertNotEquals(result.getTicket(), stored.getValue().getTicketHash());
assertEquals(64, stored.getValue().getTicketHash().length());
}
@Test
void rejectsTeacherTryingToCreateDemoForAnotherTeachersClass() {
StudentDemoSessionServiceImpl service = buildService();
SchoolClass anotherTeachersClass = teachingClass();
anotherTeachersClass.setCreatedBy("teacher-2");
when(service.schoolClassMapper.selectByPrimaryKey("class-1")).thenReturn(anotherTeachersClass);
assertThrows(ServiceException.class, () -> service.create(teacher(), "class-1"));
}
@Test
void rejectsAdministrativeClassForDemo() {
StudentDemoSessionServiceImpl service = buildService();
SchoolClass administrativeClass = teachingClass();
administrativeClass.setClassType("ADMIN");
when(service.schoolClassMapper.selectByPrimaryKey("class-1")).thenReturn(administrativeClass);
assertThrows(ServiceException.class, () -> service.create(teacher(), "class-1"));
}
@Test
void exchangesUnusedTicketExactlyOnceForDemoStudentToken() {
StudentDemoSessionServiceImpl service = buildService();
StudentDemoSession session = activeSession();
when(service.studentDemoSessionMapper.selectByTicketHash(any())).thenReturn(session);
when(service.studentDemoSessionMapper.markUsedIfActive(eq("session-1"), any(Date.class))).thenReturn(1);
StudentDemoSessionLogin result = service.exchange("ticket-from-browser");
JwtUser tokenUser = TokenProvider.getJWTUser(result.getToken());
assertEquals(4, tokenUser.getRoleId());
assertTrue(tokenUser.isDemoMode());
assertEquals("class-1", tokenUser.getDemoTeachingClassId());
assertEquals("teacher-1", tokenUser.getSourceTeacherId());
assertEquals("class-1", result.getDemoTeachingClassId());
assertEquals("Teaching Class 1", result.getClassName());
verify(service.studentDemoSessionMapper).markUsedIfActive(eq("session-1"), any(Date.class));
}
@Test
void rejectsTicketThatWasAlreadyExchanged() {
StudentDemoSessionServiceImpl service = buildService();
when(service.studentDemoSessionMapper.selectByTicketHash(any())).thenReturn(activeSession());
when(service.studentDemoSessionMapper.markUsedIfActive(eq("session-1"), any(Date.class))).thenReturn(0);
assertThrows(ServiceException.class, () -> service.exchange("ticket-from-browser"));
}
private StudentDemoSessionServiceImpl buildService() {
StudentDemoSessionServiceImpl service = new StudentDemoSessionServiceImpl();
service.schoolClassMapper = mock(SchoolClassMapper.class);
service.studentDemoSessionMapper = mock(StudentDemoSessionMapper.class);
return service;
}
private JwtUser teacher() {
JwtUser user = new JwtUser();
user.setUserId("teacher-1");
user.setName("Teacher One");
user.setUsername("teacher.one");
user.setSchoolId("school-1");
user.setRoleId(3);
return user;
}
private SchoolClass teachingClass() {
SchoolClass schoolClass = new SchoolClass();
schoolClass.setSchoolClassId("class-1");
schoolClass.setClassName("Teaching Class 1");
schoolClass.setSchoolId("school-1");
schoolClass.setClassType("TEACHING");
schoolClass.setCreatedBy("teacher-1");
return schoolClass;
}
private StudentDemoSession activeSession() {
StudentDemoSession session = new StudentDemoSession();
session.setId("session-1");
session.setTeacherUserId("teacher-1");
session.setTeacherName("Teacher One");
session.setSchoolId("school-1");
session.setTeachingClassId("class-1");
session.setTeachingClassName("Teaching Class 1");
session.setExpiresAt(new Date(System.currentTimeMillis() + 60_000));
return session;
}
}

@ -0,0 +1,81 @@
package com.sztzjy.linkCommerce.service.impl;
import com.sztzjy.linkCommerce.config.exception.handler.ServiceException;
import com.sztzjy.linkCommerce.config.security.JwtUser;
import com.sztzjy.linkCommerce.entity.SchoolClass;
import com.sztzjy.linkCommerce.entity.TeachingClassStudent;
import com.sztzjy.linkCommerce.mapper.SchoolClassMapper;
import com.sztzjy.linkCommerce.mapper.TeachingClassStudentMapper;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class StudentTeachingClassResolverImplTest {
@Test
void normalStudentResolvesTheirActiveTeachingClassMembership() {
StudentTeachingClassResolverImpl resolver = buildResolver();
TeachingClassStudent membership = new TeachingClassStudent();
membership.setTeachingClassId("class-1");
when(resolver.teachingClassStudentMapper.selectActiveByStudentUserId("student-1")).thenReturn(membership);
when(resolver.schoolClassMapper.selectByPrimaryKey("class-1")).thenReturn(teachingClass("class-1"));
assertEquals("class-1", resolver.resolveRequired(normalStudent()));
}
@Test
void demoStudentUsesSignedTeachingClassWithoutMembershipLookup() {
StudentTeachingClassResolverImpl resolver = buildResolver();
when(resolver.schoolClassMapper.selectByPrimaryKey("class-1")).thenReturn(teachingClass("class-1"));
assertEquals("class-1", resolver.resolveRequired(demoStudent()));
verify(resolver.teachingClassStudentMapper, never()).selectActiveByStudentUserId("demo:session-1");
}
@Test
void demoStudentCannotRequestDifferentTeachingClass() {
StudentTeachingClassResolverImpl resolver = buildResolver();
when(resolver.schoolClassMapper.selectByPrimaryKey("class-1")).thenReturn(teachingClass("class-1"));
assertThrows(ServiceException.class, () -> resolver.resolveRequested(demoStudent(), "class-2"));
}
private StudentTeachingClassResolverImpl buildResolver() {
StudentTeachingClassResolverImpl resolver = new StudentTeachingClassResolverImpl();
resolver.schoolClassMapper = mock(SchoolClassMapper.class);
resolver.teachingClassStudentMapper = mock(TeachingClassStudentMapper.class);
return resolver;
}
private JwtUser normalStudent() {
JwtUser user = new JwtUser();
user.setUserId("student-1");
user.setSchoolId("school-1");
user.setRoleId(4);
return user;
}
private JwtUser demoStudent() {
JwtUser user = new JwtUser();
user.setUserId("demo:session-1");
user.setSchoolId("school-1");
user.setRoleId(4);
user.setDemoMode(true);
user.setDemoTeachingClassId("class-1");
return user;
}
private SchoolClass teachingClass(String id) {
SchoolClass schoolClass = new SchoolClass();
schoolClass.setSchoolClassId(id);
schoolClass.setSchoolId("school-1");
schoolClass.setClassType("TEACHING");
return schoolClass;
}
}

@ -18,6 +18,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ -102,6 +104,21 @@ class StudentTrainingAnswerServiceImplTest {
() -> service.save("new-product-survey", new StudentTrainingAnswer(), student()));
}
@Test
void demoStudentCannotPersistAnswerEvenWhenTheirDemoClassIsSupplied() {
StudentTrainingAnswerServiceImpl service = buildService();
StudentTrainingAnswer request = new StudentTrainingAnswer();
request.setTeachingClassId("class-1");
request.setSaveAction("SAVE");
assertThrows(ServiceException.class,
() -> service.save("new-product-survey", request, demoStudent()));
verify(service.studentTrainingAnswerMapper, never()).insertSelective(any());
verify(service.studentTrainingAnswerMapper, never()).updateByPrimaryKeySelective(any());
verifyNoInteractions(service.trainingTaskMapper);
}
@Test
void getInitializesAllAnswerTablesBeforeQuerying() {
StudentTrainingAnswerServiceImpl service = buildService();
@ -144,4 +161,11 @@ class StudentTrainingAnswerServiceImplTest {
user.setRoleId(4);
return user;
}
private JwtUser demoStudent() {
JwtUser user = student();
user.setDemoMode(true);
user.setDemoTeachingClassId("class-1");
return user;
}
}

Loading…
Cancel
Save