Initial commit: 课程任务进度管理系统

This commit is contained in:
Hvemi_han
2024-12-18 13:30:51 +08:00
commit 2efddcfe2e
47 changed files with 3400 additions and 0 deletions

BIN
src/main/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,13 @@
package com.huertian.jinduguanli;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JinduguanliApplication {
public static void main(String[] args) {
SpringApplication.run(JinduguanliApplication.class, args);
}
}

View File

@ -0,0 +1,95 @@
package com.huertian.jinduguanli.common;
/**
*
*
* @param <T>
*/
public class ApiResponse<T> {
private int code;
private String message;
private T data;
public ApiResponse() {
}
public ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> ApiResponse<T> success() {
return new ApiResponse<>(10000, "成功", null);
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(10000, "成功", data);
}
public static <T> ApiResponse<T> error(int code) {
return new ApiResponse<>(code, getMessageByCode(code), null);
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
private static String getMessageByCode(int code) {
switch (code) {
case 10001:
return "参数无效";
case 10002:
return "邮箱已存在";
case 10003:
return "用户不存在";
case 10004:
return "密码错误";
case 10005:
return "用户已禁用";
case 10006:
return "未授权";
case 10007:
return "令牌已过期";
case 10008:
return "无效的令牌";
case 10009:
return "系统错误";
default:
return "未知错误";
}
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "ApiResponse{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}

View File

@ -0,0 +1,14 @@
package com.huertian.jinduguanli.common;
public class ErrorCode {
public static final int SUCCESS = 10000;
public static final int SYSTEM_ERROR = 10001;
public static final int INVALID_PARAM = 10002;
public static final int USER_NOT_FOUND = 10003;
public static final int USER_NOT_FOUND_OR_DISABLED = 10004;
public static final int EMAIL_EXISTS = 10005;
public static final int PASSWORD_INCORRECT = 10006;
public static final int INVALID_TOKEN = 10007;
public static final int UNAUTHORIZED = 10008;
public static final int USERNAME_EXISTS = 10009;
}

View File

@ -0,0 +1,28 @@
package com.huertian.jinduguanli.common;
import lombok.Data;
@Data
public class Result<T> {
private Integer code;
private String message;
private T data;
public Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public static <T> Result<T> success(T data) {
return new Result<>(0, "success", data);
}
public static <T> Result<T> error(String message) {
return new Result<>(10009, message, null);
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
}

View File

@ -0,0 +1,44 @@
package com.huertian.jinduguanli.config;
import com.huertian.jinduguanli.common.ApiResponse;
import jakarta.persistence.EntityNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(EntityNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<Void> handleEntityNotFoundException(EntityNotFoundException e) {
logger.error("Entity not found: {}", e.getMessage());
return ApiResponse.error(10003, e.getMessage());
}
@ExceptionHandler(IllegalArgumentException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleIllegalArgumentException(IllegalArgumentException e) {
logger.error("Invalid argument: {}", e.getMessage());
return ApiResponse.error(10001, e.getMessage());
}
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
logger.error("Invalid request body: {}", e.getMessage());
return ApiResponse.error(10001, "请求体格式不正确");
}
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<Void> handleException(Exception e) {
logger.error("Unexpected error: ", e);
return ApiResponse.error(10009, "系统错误");
}
}

View File

@ -0,0 +1,27 @@
package com.huertian.jinduguanli.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {
private long defaultExpirationMs = 86400000; // 默认24小时
private long rememberExpirationMs = 604800000; // 默认7天
public long getDefaultExpirationMs() {
return defaultExpirationMs;
}
public void setDefaultExpirationMs(long defaultExpirationMs) {
this.defaultExpirationMs = defaultExpirationMs;
}
public long getRememberExpirationMs() {
return rememberExpirationMs;
}
public void setRememberExpirationMs(long rememberExpirationMs) {
this.rememberExpirationMs = rememberExpirationMs;
}
}

View File

@ -0,0 +1,81 @@
package com.huertian.jinduguanli.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
public class RedisConfig {
private static final Logger logger = LoggerFactory.getLogger(RedisConfig.class);
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Value("${spring.redis.password}")
private String redisPassword;
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort);
if (redisPassword != null && !redisPassword.isEmpty()) {
config.setPassword(redisPassword);
}
logger.info("Configuring Redis connection to {}:{}", redisHost, redisPort);
return new LettuceConnectionFactory(config);
}
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 设置key的序列化方式
template.setKeySerializer(new StringRedisSerializer());
// 设置value的序列化方式
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 设置hash key的序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
// 设置hash value的序列化方式
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
// 测试连接
try {
template.getConnectionFactory().getConnection().ping();
logger.info("Redis连接测试成功");
} catch (Exception e) {
logger.error("Redis连接测试失败: {}", e.getMessage(), e);
}
return template;
}
}

View File

@ -0,0 +1,73 @@
package com.huertian.jinduguanli.config;
import com.huertian.jinduguanli.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers("/api/users/login").permitAll()
.requestMatchers(HttpMethod.POST, "/api/users").permitAll()
.requestMatchers(HttpMethod.GET, "/api/lesson/task/**").authenticated()
.anyRequest().authenticated())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setExposedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}

View File

@ -0,0 +1,89 @@
package com.huertian.jinduguanli.controller;
import com.huertian.jinduguanli.common.ApiResponse;
import com.huertian.jinduguanli.common.ErrorCode;
import com.huertian.jinduguanli.dto.LessonTaskRequest;
import com.huertian.jinduguanli.entity.LessonTask;
import com.huertian.jinduguanli.service.LessonTaskService;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/lesson-tasks")
public class LessonTaskController {
private static final Logger logger = LoggerFactory.getLogger(LessonTaskController.class);
private final LessonTaskService lessonTaskService;
@Autowired
public LessonTaskController(LessonTaskService lessonTaskService) {
this.lessonTaskService = lessonTaskService;
}
@GetMapping
public ApiResponse<Page<LessonTask>> listTasks(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) Long userId) {
logger.info("收到课程任务列表查询请求,页码: {},每页数量: {}用户ID: {}", page, size, userId);
try {
Page<LessonTask> tasks = lessonTaskService.findAll(PageRequest.of(page - 1, size), userId);
return ApiResponse.success(tasks);
} catch (Exception e) {
logger.error("查询课程任务列表失败", e);
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "获取课程任务列表失败", null);
}
}
@GetMapping("/{id}")
public ApiResponse<LessonTask> getTask(@PathVariable Long id) {
logger.info("收到课程任务查询请求任务ID: {}", id);
try {
LessonTask task = lessonTaskService.findById(id);
return ApiResponse.success(task);
} catch (Exception e) {
logger.error("查询课程任务失败", e);
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "获取课程任务失败", null);
}
}
@PostMapping
public ApiResponse<LessonTask> createTask(@Valid @RequestBody LessonTaskRequest request) {
logger.info("收到创建课程任务请求");
try {
LessonTask task = lessonTaskService.create(request);
return ApiResponse.success(task);
} catch (Exception e) {
logger.error("创建课程任务失败", e);
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "创建课程任务失败", null);
}
}
@PutMapping("/{id}")
public ApiResponse<LessonTask> updateTask(@PathVariable Long id, @Valid @RequestBody LessonTaskRequest request) {
logger.info("收到更新课程任务请求任务ID: {}", id);
try {
LessonTask task = lessonTaskService.update(id, request);
return ApiResponse.success(task);
} catch (Exception e) {
logger.error("更新课程任务失败", e);
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "更新课程任务失败", null);
}
}
@DeleteMapping("/{id}")
public ApiResponse<Void> deleteTask(@PathVariable Long id) {
logger.info("收到删除课程任务请求任务ID: {}", id);
try {
lessonTaskService.delete(id);
return ApiResponse.success();
} catch (Exception e) {
logger.error("删除课程任务失败", e);
return new ApiResponse<>(ErrorCode.SYSTEM_ERROR, "删除课程任务失败", null);
}
}
}

View File

@ -0,0 +1,88 @@
package com.huertian.jinduguanli.controller;
import com.huertian.jinduguanli.common.ApiResponse;
import com.huertian.jinduguanli.dto.CreateUserRequest;
import com.huertian.jinduguanli.dto.LoginResponse;
import com.huertian.jinduguanli.dto.UserLoginRequest;
import com.huertian.jinduguanli.entity.User;
import com.huertian.jinduguanli.security.service.JwtService;
import com.huertian.jinduguanli.security.service.TokenBlacklistService;
import com.huertian.jinduguanli.service.UserService;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/users")
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
private final UserService userService;
private final JwtService jwtService;
private final TokenBlacklistService tokenBlacklistService;
@Autowired
public UserController(
UserService userService,
JwtService jwtService,
TokenBlacklistService tokenBlacklistService) {
this.userService = userService;
this.jwtService = jwtService;
this.tokenBlacklistService = tokenBlacklistService;
}
@PostMapping("/login")
public ApiResponse<LoginResponse> login(@Valid @RequestBody UserLoginRequest request) {
logger.info("收到登录请求,邮箱: {}", request.getEmail());
ApiResponse<LoginResponse> response = userService.login(request);
logger.info("登录响应: {}", response);
return response;
}
@PostMapping("/logout")
public ApiResponse<String> logout(@RequestHeader("Authorization") String authHeader) {
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// 获取token的过期时间
long expiration = jwtService.extractExpiration(token).getTime();
// 将token加入黑名单
tokenBlacklistService.addToBlacklist(token, expiration);
return ApiResponse.success("登出成功");
}
return ApiResponse.success("登出成功");
}
@PostMapping
public ApiResponse<Void> createUser(@Valid @RequestBody CreateUserRequest request) {
logger.info("收到创建用户请求,用户名: {}", request.getUsername());
return userService.createUser(request);
}
@GetMapping("/current")
public ApiResponse<User> getCurrentUser(@RequestHeader("Authorization") String authHeader) {
logger.info("收到获取当前用户信息请求Authorization: {}", authHeader);
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
logger.info("解析出的token: {}", token);
return userService.getCurrentUser(token);
}
logger.warn("未提供有效的认证令牌");
return ApiResponse.error(401, "未提供有效的认证令牌");
}
@GetMapping("/list")
public ApiResponse<?> listUsers(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer limit) {
logger.info("收到用户列表分页查询请求,页码: {},每页数量: {}", page, limit);
return userService.listUsers(page, limit);
}
@PostMapping("/disable/{userId}")
public ApiResponse<String> disableUser(@PathVariable Long userId) {
logger.info("收到禁用用户请求用户ID: {}", userId);
return userService.disableUser(userId);
}
}

View File

@ -0,0 +1,87 @@
package com.huertian.jinduguanli.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class CreateUserRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "密码不能为空")
private String password;
@NotNull(message = "部门ID不能为空")
private Long departmentId;
@NotNull(message = "角色不能为空")
private Integer roles;
@NotNull(message = "岗位不能为空")
private Integer jobs;
@NotNull(message = "创建者ID不能为空")
private Long creatorId;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
public Integer getRoles() {
return roles;
}
public void setRoles(Integer roles) {
this.roles = roles;
}
public Integer getJobs() {
return jobs;
}
public void setJobs(Integer jobs) {
this.jobs = jobs;
}
public Long getCreatorId() {
return creatorId;
}
public void setCreatorId(Long creatorId) {
this.creatorId = creatorId;
}
}

View File

@ -0,0 +1,99 @@
package com.huertian.jinduguanli.dto;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class LessonTaskRequest {
private String courseName;
private String microLessonName;
private Long userId;
private Integer progressStatus;
private Long scriptUploadTime;
private Long scriptConfirmTime;
private Long videoCaptureTime;
private Long videoConfirmTime;
private Long finishTime;
private String advise;
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getMicroLessonName() {
return microLessonName;
}
public void setMicroLessonName(String microLessonName) {
this.microLessonName = microLessonName;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Integer getProgressStatus() {
return progressStatus;
}
public void setProgressStatus(Integer progressStatus) {
this.progressStatus = progressStatus;
}
public Long getScriptUploadTime() {
return scriptUploadTime;
}
public void setScriptUploadTime(Long scriptUploadTime) {
this.scriptUploadTime = scriptUploadTime;
}
public Long getScriptConfirmTime() {
return scriptConfirmTime;
}
public void setScriptConfirmTime(Long scriptConfirmTime) {
this.scriptConfirmTime = scriptConfirmTime;
}
public Long getVideoCaptureTime() {
return videoCaptureTime;
}
public void setVideoCaptureTime(Long videoCaptureTime) {
this.videoCaptureTime = videoCaptureTime;
}
public Long getVideoConfirmTime() {
return videoConfirmTime;
}
public void setVideoConfirmTime(Long videoConfirmTime) {
this.videoConfirmTime = videoConfirmTime;
}
public Long getFinishTime() {
return finishTime;
}
public void setFinishTime(Long finishTime) {
this.finishTime = finishTime;
}
public String getAdvise() {
return advise;
}
public void setAdvise(String advise) {
this.advise = advise;
}
}

View File

@ -0,0 +1,17 @@
package com.huertian.jinduguanli.dto;
public class LoginResponse {
private String token;
public LoginResponse(String token) {
this.token = token;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

View File

@ -0,0 +1,39 @@
package com.huertian.jinduguanli.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
public class UserLoginRequest {
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
@NotBlank(message = "密码不能为空")
private String password;
private Boolean remember = false;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getRemember() {
return remember;
}
public void setRemember(Boolean remember) {
this.remember = remember != null ? remember : false;
}
}

View File

@ -0,0 +1,46 @@
package com.huertian.jinduguanli.dto;
import com.huertian.jinduguanli.entity.User;
import lombok.Data;
import java.util.List;
@Data
public class UserPageResponse {
private List<User> list;
private Long total;
private Integer currentPage;
private Integer pageSize;
public List<User> getList() {
return list;
}
public void setList(List<User> list) {
this.list = list;
}
public Long getTotal() {
return total;
}
public void setTotal(Long total) {
this.total = total;
}
public Integer getCurrentPage() {
return currentPage;
}
public void setCurrentPage(Integer currentPage) {
this.currentPage = currentPage;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
}

View File

@ -0,0 +1,68 @@
package com.huertian.jinduguanli.entity;
import jakarta.persistence.*;
import lombok.Data;
import lombok.ToString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
@Entity
@Table(name = "lesson_tasks")
@Data
@ToString
public class LessonTask implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(LessonTask.class);
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String courseName;
@Column(nullable = false)
private String microLessonName;
@Column(nullable = false)
private Long userId;
@Column(nullable = false)
private Integer progressStatus;
private Long scriptUploadTime;
private Long scriptConfirmTime;
private Long videoCaptureTime;
private Long videoConfirmTime;
private Long finishTime;
private String advise;
@Column(nullable = false)
private Long createdAt;
@Column(nullable = false)
private Long updatedAt;
@PrePersist
protected void onCreate() {
logger.info("创建新课程任务 - 课程名称: {}, 小课名称: {}, 用户ID: {}",
this.courseName, this.microLessonName, this.userId);
progressStatus = 1;
long now = System.currentTimeMillis();
createdAt = now;
updatedAt = now;
logger.info("创建课程任务成功 - 进度状态: {}, 创建时间: {}, 更新时间: {}",
progressStatus, createdAt, updatedAt);
}
@PreUpdate
protected void onUpdate() {
logger.info("更新课程任务 - ID: {}, 课程名称: {}, 小课名称: {}",
this.id, this.courseName, this.microLessonName);
updatedAt = System.currentTimeMillis();
logger.info("更新课程任务成功 - ID: {}, 更新时间: {}", id, updatedAt);
}
}

View File

@ -0,0 +1,173 @@
package com.huertian.jinduguanli.entity;
import jakarta.persistence.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
@Entity
@Table(name = "users")
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String username;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@Column(name = "department_id")
private Long departmentId;
@Column
private Integer roles = 0;
@Column
private Integer jobs = 0;
private String avatar;
@Column(name = "creator_id")
private Long creatorId = 0L;
@Column(nullable = false)
private Integer status;
@Column(name = "created_at", nullable = false)
private Long createdAt;
@Column(name = "updated_at", nullable = false)
private Long updatedAt;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username; // 返回 username 而不是 email
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return status == 1; // 假设 status == 1 表示账户未锁定
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return status == 1; // 假设 status == 1 表示账户启用
}
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
public Integer getRoles() {
return roles;
}
public void setRoles(Integer roles) {
this.roles = roles;
}
public Integer getJobs() {
return jobs;
}
public void setJobs(Integer jobs) {
this.jobs = jobs;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public Long getCreatorId() {
return creatorId;
}
public void setCreatorId(Long creatorId) {
this.creatorId = creatorId;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getCreatedAt() {
return createdAt;
}
public void setCreatedAt(Long createdAt) {
this.createdAt = createdAt;
}
public Long getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(Long updatedAt) {
this.updatedAt = updatedAt;
}
}

View File

@ -0,0 +1,12 @@
package com.huertian.jinduguanli.repository;
import com.huertian.jinduguanli.entity.LessonTask;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface LessonTaskRepository extends JpaRepository<LessonTask, Long> {
Page<LessonTask> findByUserId(Long userId, Pageable pageable);
}

View File

@ -0,0 +1,20 @@
package com.huertian.jinduguanli.repository;
import com.huertian.jinduguanli.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByUsername(String username);
boolean existsByEmail(String email);
@Query(value = "SELECT * FROM users LIMIT :limit OFFSET :offset", nativeQuery = true)
List<User> findAllWithPagination(@Param("offset") int offset, @Param("limit") int limit);
}

View File

@ -0,0 +1,43 @@
package com.huertian.jinduguanli.security;
import com.huertian.jinduguanli.entity.User;
import com.huertian.jinduguanli.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(CustomUserDetailsService.class);
private final UserRepository userRepository;
@Autowired
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
logger.info("Loading user details for email: {}", email);
User user = userRepository.findByEmail(email)
.orElseThrow(() -> {
logger.error("User not found with email: {}", email);
return new UsernameNotFoundException("User not found with email: " + email);
});
logger.info("User found with email: {}", email);
return new org.springframework.security.core.userdetails.User(
user.getEmail(),
user.getPassword(),
new ArrayList<>());
}
}

View File

@ -0,0 +1,109 @@
package com.huertian.jinduguanli.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.huertian.jinduguanli.common.ApiResponse;
import com.huertian.jinduguanli.common.ErrorCode;
import com.huertian.jinduguanli.security.service.JwtService;
import com.huertian.jinduguanli.security.service.TokenBlacklistService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.UserDetailsService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
private final TokenBlacklistService tokenBlacklistService;
private final ObjectMapper objectMapper;
public JwtAuthenticationFilter(
JwtService jwtService,
UserDetailsService userDetailsService,
TokenBlacklistService tokenBlacklistService,
ObjectMapper objectMapper) {
this.jwtService = jwtService;
this.userDetailsService = userDetailsService;
this.tokenBlacklistService = tokenBlacklistService;
this.objectMapper = objectMapper;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain) throws ServletException, IOException {
try {
// 允许OPTIONS请求通过
if (request.getMethod().equals(HttpMethod.OPTIONS.name())) {
logger.debug("OPTIONS请求直接放行");
filterChain.doFilter(request, response);
return;
}
final String authHeader = request.getHeader("Authorization");
logger.debug("处理请求认证 - 请求路径: {}", request.getRequestURI());
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
logger.debug("未提供认证令牌或格式不正确");
filterChain.doFilter(request, response);
return;
}
final String jwt = authHeader.substring(7);
final String username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
logger.debug("开始验证用户令牌 - 用户邮箱: {}", username);
if (tokenBlacklistService.isBlacklisted(jwt)) {
logger.warn("令牌已被加入黑名单");
handleJwtException(response, "令牌已被加入黑名单");
return;
}
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
logger.debug("令牌验证成功,设置安全上下文");
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
} else {
logger.warn("令牌验证失败");
handleJwtException(response, "令牌验证失败");
return;
}
}
filterChain.doFilter(request, response);
} catch (Exception e) {
logger.error("处理认证时发生错误", e);
handleJwtException(response, "认证处理失败");
}
}
private void handleJwtException(HttpServletResponse response, String message) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType("application/json;charset=UTF-8");
ApiResponse<?> apiResponse = ApiResponse.error(ErrorCode.INVALID_TOKEN, "token已过期请重新登录");
response.getWriter().write(objectMapper.writeValueAsString(apiResponse));
}
}

View File

@ -0,0 +1,106 @@
package com.huertian.jinduguanli.security.service;
import com.huertian.jinduguanli.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Service
public class JwtService {
private static final Logger logger = LoggerFactory.getLogger(JwtService.class);
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.expiration}")
private long jwtExpiration;
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
Date issuedAt = new Date(System.currentTimeMillis());
Date expiration = new Date(System.currentTimeMillis() + jwtExpiration);
logger.debug("Generating token for user: {}, expiration: {}, jwtExpiration: {}", userDetails.getUsername(),
expiration, jwtExpiration);
return Jwts.builder()
.setClaims(extraClaims)
.setSubject(((User) userDetails).getEmail())
.setIssuedAt(issuedAt)
.setExpiration(expiration)
.signWith(getSignInKey(), SignatureAlgorithm.HS384)
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
try {
final String username = extractUsername(token);
Date expiration = extractExpiration(token);
Date now = new Date();
boolean isExpired = expiration.before(now);
boolean isValid = username.equals(((User) userDetails).getEmail()) && !isExpired;
logger.debug("Token validation for user {}: valid={}, expiration={}, current={}, isExpired={}",
username, isValid, expiration, now, isExpired);
return isValid;
} catch (Exception e) {
logger.error("Token validation failed: {}", e.getMessage());
return false;
}
}
// private boolean isTokenExpired(String token) {
// Date expiration = extractExpiration(token);
// Date now = new Date();
// boolean isExpired = expiration.before(now);
// logger.debug("Token expiration check: expiration={}, current={},
// isExpired={}",
// expiration, now, isExpired);
// return isExpired;
// }
private Claims extractAllClaims(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(getSignInKey())
.build()
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
logger.error("解析token失败: {}", e.getMessage());
throw e;
}
}
private Key getSignInKey() {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}

View File

@ -0,0 +1,62 @@
package com.huertian.jinduguanli.security.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Service
public class TokenBlacklistService {
private final StringRedisTemplate redisTemplate;
private static final String BLACKLIST_PREFIX = "token:blacklist:";
private static final Logger logger = LoggerFactory.getLogger(TokenBlacklistService.class);
public TokenBlacklistService(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 将令牌添加到黑名单
*
* @param token JWT令牌
* @param expiration 过期时间(毫秒)
*/
public void addToBlacklist(String token, long expiration) {
logger.info("将令牌加入黑名单 - 过期时间: {}", new Date(expiration));
try {
String key = BLACKLIST_PREFIX + token;
long ttl = expiration - System.currentTimeMillis();
if (ttl > 0) {
redisTemplate.opsForValue().set(key, "blacklisted", ttl, TimeUnit.MILLISECONDS);
logger.info("令牌已成功加入黑名单");
} else {
logger.warn("令牌已过期,无需加入黑名单");
}
} catch (Exception e) {
logger.error("将令牌加入黑名单时发生错误", e);
throw new RuntimeException("无法将令牌加入黑名单", e);
}
}
/**
* 检查令牌是否在黑名单中
*
* @param token JWT令牌
* @return 如果在黑名单中返回true
*/
public boolean isBlacklisted(String token) {
logger.debug("检查令牌是否在黑名单中");
try {
String key = BLACKLIST_PREFIX + token;
Boolean exists = redisTemplate.hasKey(key);
logger.debug("令牌黑名单检查结果: {}", exists);
return Boolean.TRUE.equals(exists);
} catch (Exception e) {
logger.error("检查令牌黑名单状态时发生错误", e);
return false;
}
}
}

View File

@ -0,0 +1,33 @@
package com.huertian.jinduguanli.security.service;
import com.huertian.jinduguanli.entity.User;
import com.huertian.jinduguanli.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
private final Logger logger = LoggerFactory.getLogger(UserDetailsService.class);
private final UserRepository userRepository;
public UserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
logger.info("开始加载用户信息 - 邮箱: {}", email);
User user = userRepository.findByEmail(email)
.orElseThrow(() -> {
logger.error("未找到用户 - 邮箱: {}", email);
return new UsernameNotFoundException("未找到用户");
});
logger.info("成功加载用户信息 - 用户名: {}", user.getUsername());
return user;
}
}

View File

@ -0,0 +1,108 @@
package com.huertian.jinduguanli.service;
import com.huertian.jinduguanli.dto.LessonTaskRequest;
import com.huertian.jinduguanli.entity.LessonTask;
import com.huertian.jinduguanli.repository.LessonTaskRepository;
import jakarta.persistence.EntityNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.BeanUtils;
@Service
public class LessonTaskService {
private static final Logger logger = LoggerFactory.getLogger(LessonTaskService.class);
private final LessonTaskRepository lessonTaskRepository;
public LessonTaskService(LessonTaskRepository lessonTaskRepository) {
this.lessonTaskRepository = lessonTaskRepository;
}
@Cacheable(value = "lessonTasks", key = "#userId != null ? 'user:' + #userId + ':page:' + #pageable.pageNumber : 'all:page:' + #pageable.pageNumber")
public Page<LessonTask> findAll(Pageable pageable, Long userId) {
logger.info("查询课程任务 - 页码: {}, 每页数量: {}, 用户ID: {}",
pageable.getPageNumber(), pageable.getPageSize(), userId);
Page<LessonTask> result;
if (userId != null) {
result = lessonTaskRepository.findByUserId(userId, pageable);
} else {
result = lessonTaskRepository.findAll(pageable);
}
logger.info("查询到 {} 条课程任务", result.getTotalElements());
return result;
}
@Cacheable(value = "lessonTask", key = "#id")
public LessonTask findById(Long id) {
logger.info("根据ID查询课程任务 - 任务ID: {}", id);
return lessonTaskRepository.findById(id)
.orElseThrow(() -> {
logger.error("未找到ID为 {} 的课程任务", id);
return new EntityNotFoundException("未找到ID为 " + id + " 的任务");
});
}
@Transactional
@CacheEvict(value = { "lessonTasks", "lessonTask" }, allEntries = true)
public LessonTask create(LessonTaskRequest request) {
logger.info("开始创建课程任务 - 课程名称: {}, 小课名称: {}, 用户ID: {}",
request.getCourseName(), request.getMicroLessonName(), request.getUserId());
validateRequest(request);
LessonTask task = new LessonTask();
BeanUtils.copyProperties(request, task);
task.setProgressStatus(1); // 初始状态
LessonTask savedTask = lessonTaskRepository.save(task);
logger.info("创建课程任务成功 - 任务ID: {}", savedTask.getId());
return savedTask;
}
@Transactional
@CacheEvict(value = { "lessonTasks", "lessonTask" }, allEntries = true)
public LessonTask update(Long id, LessonTaskRequest request) {
logger.info("开始更新课程任务 - 任务ID: {}, 课程名称: {}, 小课名称: {}",
id, request.getCourseName(), request.getMicroLessonName());
LessonTask task = findById(id);
// 保留原有的进度状态,如果请求中包含新的进度状态则更新
Integer originalStatus = task.getProgressStatus();
BeanUtils.copyProperties(request, task);
if (task.getProgressStatus() == null) {
task.setProgressStatus(originalStatus);
}
LessonTask updatedTask = lessonTaskRepository.save(task);
logger.info("更新课程任务成功 - 任务ID: {}", updatedTask.getId());
return updatedTask;
}
@Transactional
@CacheEvict(value = { "lessonTasks", "lessonTask" }, allEntries = true)
public void delete(Long id) {
logger.info("开始删除课程任务 - 任务ID: {}", id);
if (!lessonTaskRepository.existsById(id)) {
logger.error("未找到ID为 {} 的课程任务", id);
throw new EntityNotFoundException("未找到ID为 " + id + " 的任务");
}
lessonTaskRepository.deleteById(id);
logger.info("删除课程任务成功 - 任务ID: {}", id);
}
private void validateRequest(LessonTaskRequest request) {
if (request.getCourseName() == null || request.getCourseName().trim().isEmpty()) {
throw new IllegalArgumentException("课程名称不能为空");
}
if (request.getMicroLessonName() == null || request.getMicroLessonName().trim().isEmpty()) {
throw new IllegalArgumentException("小课名称不能为空");
}
if (request.getUserId() == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
}
}

View File

@ -0,0 +1,171 @@
package com.huertian.jinduguanli.service;
import com.huertian.jinduguanli.common.ApiResponse;
import com.huertian.jinduguanli.common.ErrorCode;
import com.huertian.jinduguanli.dto.CreateUserRequest;
import com.huertian.jinduguanli.dto.LoginResponse;
import com.huertian.jinduguanli.dto.UserLoginRequest;
import com.huertian.jinduguanli.dto.UserPageResponse;
import com.huertian.jinduguanli.entity.User;
import com.huertian.jinduguanli.repository.UserRepository;
import com.huertian.jinduguanli.security.service.JwtService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtService jwtService;
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
@Autowired
public UserService(JwtService jwtService, PasswordEncoder passwordEncoder, UserRepository userRepository) {
this.jwtService = jwtService;
this.passwordEncoder = passwordEncoder;
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<User> user = userRepository.findByEmail(email);
if (user.isEmpty()) {
throw new UsernameNotFoundException("User not found with email: " + email);
}
return user.get();
}
public ApiResponse<LoginResponse> login(UserLoginRequest request) {
Optional<User> userOpt = userRepository.findByEmail(request.getEmail());
if (userOpt.isEmpty() || userOpt.get().getStatus() != 1) {
return new ApiResponse<>(ErrorCode.USER_NOT_FOUND_OR_DISABLED, "用户不存在或已禁用", null);
}
User user = userOpt.get();
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
return new ApiResponse<>(ErrorCode.PASSWORD_INCORRECT, "密码错误", null);
}
String token = jwtService.generateToken(user);
return ApiResponse.success(new LoginResponse(token));
}
public ApiResponse<Void> createUser(CreateUserRequest request) {
// 检查邮箱是否已存在
if (userRepository.findByEmail(request.getEmail()).isPresent()) {
return new ApiResponse<>(ErrorCode.EMAIL_EXISTS, "邮箱已存在", null);
}
User user = new User();
user.setEmail(request.getEmail());
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setDepartmentId(request.getDepartmentId());
user.setRoles(request.getRoles());
user.setJobs(request.getJobs());
user.setCreatorId(request.getCreatorId());
user.setStatus(1);
user.setCreatedAt(System.currentTimeMillis());
user.setUpdatedAt(System.currentTimeMillis());
userRepository.save(user);
return ApiResponse.success();
}
public ApiResponse<User> getCurrentUser(String token) {
String email = jwtService.extractUsername(token);
logger.info("从token中提取的邮箱: {}", email);
Optional<User> userOpt = userRepository.findByEmail(email);
if (userOpt.isEmpty()) {
logger.warn("用户不存在: {}", email);
return new ApiResponse<>(ErrorCode.USER_NOT_FOUND, "用户不存在", null);
}
User user = userOpt.get();
logger.info("找到用户: {}", user.getUsername());
user.setPassword(null);
return ApiResponse.success(user);
}
public ApiResponse<String> disableUser(Long userId) {
Optional<User> userOpt = userRepository.findById(userId);
if (userOpt.isEmpty()) {
return new ApiResponse<>(ErrorCode.USER_NOT_FOUND, "用户不存在", null);
}
User user = userOpt.get();
user.setStatus(0);
userRepository.save(user);
return ApiResponse.success("用户已禁用");
}
public ApiResponse<UserPageResponse> listUsers(Integer currentPage, Integer pageSize) {
// 参数校验
if (currentPage < 1) {
currentPage = 1;
}
if (pageSize < 1) {
pageSize = 10;
}
// 查询数据
Page<User> userPage = userRepository.findAll(PageRequest.of(currentPage - 1, pageSize));
// 处理返回数据
UserPageResponse response = new UserPageResponse();
response.setTotal(userPage.getTotalElements());
response.setCurrentPage(currentPage);
response.setPageSize(pageSize);
List<User> users = userPage.getContent();
users.forEach(user -> user.setPassword(null));
response.setList(users);
return ApiResponse.success(response);
}
public User register(User user) {
// 检查邮箱是否已存在
if (userRepository.findByEmail(user.getEmail()).isPresent()) {
throw new RuntimeException("邮箱已存在");
}
// 检查用户名是否已存在
if (userRepository.findByUsername(user.getUsername()).isPresent()) {
throw new RuntimeException("用户名已存在");
}
// 设置默认值
user.setDepartmentId(0L);
user.setRoles(0);
user.setJobs(0);
user.setCreatorId(0L);
// 加密密码
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setStatus(1);
user.setCreatedAt(System.currentTimeMillis());
user.setUpdatedAt(System.currentTimeMillis());
// 保存用户
User savedUser = userRepository.save(user);
// 清除密码后返回
savedUser.setPassword(null);
return savedUser;
}
}

View File

@ -0,0 +1,121 @@
package com.huertian.jinduguanli.utils;
import com.huertian.jinduguanli.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
@Value("${jwt.secret}")
private String SECRET_KEY;
@Value("${jwt.expiration}")
private long jwtExpiration;
public String extractUsername(String token) {
logger.debug("开始从token中提取用户名");
try {
String username = extractClaim(token, Claims::getSubject);
logger.debug("提取的用户名: {}", username);
return username;
} catch (ExpiredJwtException e) {
logger.error("token已过期", e);
throw e;
} catch (Exception e) {
logger.error("从token中提取用户名时发生错误", e);
throw e;
}
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
logger.debug("开始从token中提取所有声明");
try {
Claims claims = Jwts
.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
logger.debug("声明提取成功");
return claims;
} catch (Exception e) {
logger.error("从token中提取声明时发生错误", e);
throw e;
}
}
private Boolean isTokenExpired(String token) {
try {
Date expiration = extractExpiration(token);
boolean isExpired = expiration.before(new Date());
logger.debug("token过期检查 - 过期时间: {}, 是否过期: {}", expiration, isExpired);
return isExpired;
} catch (Exception e) {
logger.error("检查token过期时发生错误", e);
throw e;
}
}
public String generateToken(String userName) {
logger.debug("开始为用户创建token: {}", userName);
try {
Map<String, Object> claims = new HashMap<>();
String token = Jwts.builder()
.setClaims(claims)
.setSubject(userName)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
.signWith(getSignKey(), SignatureAlgorithm.HS384)
.compact();
logger.debug("token创建成功");
return token;
} catch (Exception e) {
logger.error("创建token时发生错误", e);
throw e;
}
}
public Boolean validateToken(String token, User user) {
logger.debug("开始验证用户token: {}", user.getUsername());
try {
final String username = extractUsername(token);
boolean isValid = username.equals(user.getUsername()) && !isTokenExpired(token);
logger.debug("token验证结果 - 用户名匹配: {}, 未过期: {}",
username.equals(user.getUsername()), !isTokenExpired(token));
return isValid;
} catch (Exception e) {
logger.error("验证token时发生错误", e);
return false;
}
}
private Key getSignKey() {
byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
return Keys.hmacShaKeyFor(keyBytes);
}
}

View File

@ -0,0 +1,21 @@
# 数据库配置
spring.datasource.url=jdbc:mysql://172.16.215.132:3306/fenshenzhike?useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
server.port=1218
# JPA配置
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.format_sql=true
# JWT配置
jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
jwt.expiration=604800000
# 日志配置
logging.level.org.springframework.security=DEBUG
logging.level.com.huertian.jinduguanli=DEBUG

View File

@ -0,0 +1,48 @@
server:
port: 1218
spring:
main:
banner-mode: console
datasource:
url: jdbc:mysql://172.16.215.132:3306/fenshenzhike?useSSL=false&serverTimezone=UTC
username: root
password: 123
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
use_sql_comments: true
redis:
host: 172.16.215.132
port: 6379
database: 0
timeout: 10000
password: # 如果Redis设置了密码需要在这里添加
lettuce:
pool:
max-active: 8
max-wait: -1ms
max-idle: 8
min-idle: 0
shutdown-timeout: 100ms
client-name: jinduguanli
connect-timeout: 5000
socket-timeout: 5000
client-type: lettuce
jwt:
secret: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
expiration: 86400000 # 24小时
logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
com.huertian.jinduguanli: DEBUG
org.springframework.data.redis: DEBUG
io.lettuce.core: DEBUG

View File

@ -0,0 +1,8 @@
_________________
< Hi, 我是牛哥。。 >
-----------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||