You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

132 lines
6.0 KiB
Java

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);
}
}
}