2991692032 3 days ago
parent 9e0200a0e3
commit 6f1f955abf

@ -121,5 +121,23 @@ export const adminApi = {
// 删除资源
deleteResource(resourceId: number): Promise<ApiResponse> {
return request.delete(`/admin/resources/${resourceId}`)
},
// 获取系统状态
getSystemStatus(): Promise<ApiResponse> {
return request.get('/admin/monitor/status')
},
// ========== 课表管理相关接口 ==========
// 获取用户课表
getUserSchedule(userId: number, semester?: string): Promise<ApiResponse> {
const params = semester ? { semester } : {}
return request.get(`/admin/users/${userId}/schedule`, { params })
},
// 删除课程
deleteCourse(courseId: number): Promise<ApiResponse> {
return request.delete(`/admin/courses/${courseId}`)
}
}

@ -24,4 +24,9 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
const userStore = useUserStore()
userStore.init()
// 定期检查token有效性每分钟检查一次
setInterval(() => {
userStore.checkTokenValidity()
}, 60000)
app.mount('#app')

@ -72,6 +72,12 @@ const router = createRouter({
name: 'admin',
component: () => import('@/views/admin/AdminDashboard.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
},
{
path: '/debug-token',
name: 'debug-token',
component: () => import('@/views/DebugTokenView.vue'),
meta: { requiresAuth: true }
}
// {
// path: '/courses',
@ -86,6 +92,14 @@ const router = createRouter({
router.beforeEach(async (to) => {
const userStore = useUserStore()
// 首先检查token是否有效
if (!userStore.checkTokenValidity()) {
// token无效或过期如果要访问需要认证的页面跳转到登录页
if (to.meta.requiresAuth) {
return '/login'
}
}
// 如果需要认证但未登录,跳转到登录页
if (to.meta.requiresAuth && !userStore.isLoggedIn) {
return '/login'

@ -2,11 +2,12 @@ import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { User, LoginRequest, RegisterRequest, ApiResponse, LoginResponse } from '@/types'
import { login, register, getUserInfo } from '@/api/auth'
import { isTokenExpired } from '@/utils/jwt'
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const token = ref<string>(localStorage.getItem('token') || '')
const isLoggedIn = ref<boolean>(!!token.value)
const isLoggedIn = ref<boolean>(!!token.value && !isTokenExpired(token.value))
// 登录
const userLogin = async (loginData: LoginRequest) => {
@ -44,8 +45,12 @@ export const useUserStore = defineStore('user', () => {
if (response.code === 200) {
user.value = response.data
}
} catch (error) {
} catch (error: any) {
console.error('获取用户信息失败:', error)
// 如果获取用户信息失败可能是token过期清除登录状态
if (error?.response?.status === 401) {
logout()
}
}
}
@ -57,9 +62,19 @@ export const useUserStore = defineStore('user', () => {
localStorage.removeItem('token')
}
// 初始化时如果有token则获取用户信息
// 检查token是否有效
const checkTokenValidity = () => {
if (token.value && isTokenExpired(token.value)) {
console.log('Token已过期自动登出')
logout()
return false
}
return !!token.value
}
// 初始化时检查token并获取用户信息
const init = async () => {
if (token.value && !user.value) {
if (checkTokenValidity() && !user.value) {
await fetchUserInfo()
}
}
@ -71,6 +86,7 @@ export const useUserStore = defineStore('user', () => {
userLogin,
userRegister,
fetchUserInfo,
checkTokenValidity,
logout,
init
}

@ -0,0 +1,69 @@
/**
* JWT
*/
/**
* JWT token
* @param token JWT token
* @returns payloadnull
*/
export function parseJwtToken(token: string): any {
try {
const parts = token.split('.')
if (parts.length !== 3) {
return null
}
const payload = parts[1]
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'))
return JSON.parse(decoded)
} catch (error) {
console.error('解析JWT token失败:', error)
return null
}
}
/**
* JWT token
* @param token JWT token
* @returns truefalse
*/
export function isTokenExpired(token: string): boolean {
if (!token) {
return true
}
const payload = parseJwtToken(token)
if (!payload || !payload.exp) {
return true
}
// JWT的exp是秒级时间戳需要转换为毫秒
const expirationTime = payload.exp * 1000
const currentTime = Date.now()
// 提前30秒判断过期避免边界情况
return currentTime >= (expirationTime - 30000)
}
/**
* token
* @param token JWT token
* @returns 0
*/
export function getTokenRemainingTime(token: string): number {
if (!token) {
return 0
}
const payload = parseJwtToken(token)
if (!payload || !payload.exp) {
return 0
}
const expirationTime = payload.exp * 1000
const currentTime = Date.now()
const remainingTime = expirationTime - currentTime
return Math.max(0, remainingTime)
}

@ -1,4 +1,5 @@
import axios from 'axios'
import { isTokenExpired } from './jwt'
// 创建axios实例
const request = axios.create({
@ -14,6 +15,21 @@ request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) {
// 检查token是否过期
if (isTokenExpired(token)) {
console.warn('Token已过期清除本地存储并跳转登录页')
localStorage.removeItem('token')
// 清除用户store状态
import('@/stores/user').then(({ useUserStore }) => {
const userStore = useUserStore()
userStore.logout()
})
// 如果不是登录页面,则跳转到登录页
if (window.location.pathname !== '/login') {
window.location.href = '/login'
}
return Promise.reject(new Error('Token已过期'))
}
config.headers.Authorization = `Bearer ${token}`
}
return config

@ -0,0 +1,197 @@
<template>
<div class="debug-container">
<h2>JWT Token 调试页面</h2>
<div class="debug-section">
<h3>当前Token信息</h3>
<div class="token-info">
<p><strong>Token存在:</strong> {{ !!currentToken }}</p>
<p><strong>Token值:</strong> {{ currentToken ? currentToken.substring(0, 50) + '...' : '无' }}</p>
<p><strong>Token过期:</strong> {{ tokenExpired }}</p>
<p><strong>剩余时间:</strong> {{ remainingTime }}ms ({{ Math.floor(remainingTime / 1000) }})</p>
</div>
</div>
<div class="debug-section">
<h3>Token解析信息</h3>
<div class="token-payload" v-if="tokenPayload">
<pre>{{ JSON.stringify(tokenPayload, null, 2) }}</pre>
</div>
<p v-else>Token</p>
</div>
<div class="debug-section">
<h3>用户Store状态</h3>
<div class="store-info">
<p><strong>isLoggedIn:</strong> {{ userStore.isLoggedIn }}</p>
<p><strong>用户ID:</strong> {{ userStore.user?.id || '无' }}</p>
<p><strong>用户名:</strong> {{ userStore.user?.username || '无' }}</p>
</div>
</div>
<div class="debug-section">
<h3>操作</h3>
<div class="actions">
<el-button @click="refreshTokenInfo">Token</el-button>
<el-button @click="checkTokenValidity">Token</el-button>
<el-button @click="testApiCall">API</el-button>
<el-button @click="clearToken" type="danger">清除Token</el-button>
</div>
</div>
<div class="debug-section">
<h3>调试日志</h3>
<div class="debug-logs">
<div v-for="(log, index) in debugLogs" :key="index" class="log-item">
<span class="log-time">{{ log.time }}</span>
<span class="log-message">{{ log.message }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useUserStore } from '@/stores/user'
import { parseJwtToken, isTokenExpired, getTokenRemainingTime } from '@/utils/jwt'
import { getUserInfo } from '@/api/user'
const userStore = useUserStore()
const currentToken = ref<string>('')
const tokenPayload = ref<any>(null)
const debugLogs = ref<Array<{time: string, message: string}>>([])
const tokenExpired = computed(() => {
return currentToken.value ? isTokenExpired(currentToken.value) : true
})
const remainingTime = computed(() => {
return currentToken.value ? getTokenRemainingTime(currentToken.value) : 0
})
let interval: number | null = null
const addLog = (message: string) => {
const time = new Date().toLocaleTimeString()
debugLogs.value.unshift({ time, message })
if (debugLogs.value.length > 20) {
debugLogs.value = debugLogs.value.slice(0, 20)
}
console.log(`[${time}] ${message}`)
}
const refreshTokenInfo = () => {
currentToken.value = localStorage.getItem('token') || ''
if (currentToken.value) {
tokenPayload.value = parseJwtToken(currentToken.value)
addLog('Token信息已刷新')
} else {
tokenPayload.value = null
addLog('未找到Token')
}
}
const checkTokenValidity = () => {
const isValid = userStore.checkTokenValidity()
addLog(`Token有效性检查结果: ${isValid}`)
}
const testApiCall = async () => {
try {
addLog('开始测试API调用...')
const response = await getUserInfo()
addLog('API调用成功: ' + JSON.stringify(response).substring(0, 100))
} catch (error: any) {
addLog('API调用失败: ' + error.message)
}
}
const clearToken = () => {
localStorage.removeItem('token')
userStore.logout()
refreshTokenInfo()
addLog('Token已清除')
}
onMounted(() => {
refreshTokenInfo()
addLog('调试页面已加载')
//
interval = setInterval(() => {
refreshTokenInfo()
}, 1000)
})
onUnmounted(() => {
if (interval) {
clearInterval(interval)
}
})
</script>
<style scoped>
.debug-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.debug-section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.debug-section h3 {
margin-top: 0;
color: #333;
}
.token-info p,
.store-info p {
margin: 10px 0;
}
.token-payload {
background: #f5f5f5;
padding: 15px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
overflow: auto;
}
.actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.debug-logs {
max-height: 300px;
overflow-y: auto;
background: #f8f8f8;
padding: 10px;
border-radius: 5px;
}
.log-item {
display: block;
margin-bottom: 5px;
font-family: monospace;
font-size: 12px;
}
.log-time {
color: #666;
margin-right: 10px;
}
.log-message {
color: #333;
}
</style>

File diff suppressed because it is too large Load Diff

@ -332,6 +332,7 @@ const loading = ref(false)
const statsLoading = ref(false)
const postsLoading = ref(false)
const resourcesLoading = ref(false)
const selectedAvatarFile = ref<File | null>(null)
// - 使API
const userProfile = ref({
@ -602,17 +603,48 @@ const handleChangePassword = async () => {
const handleAvatarChange = (file: any) => {
console.log('选择的头像文件:', file)
// TODO:
//
if (!file.raw.type.startsWith('image/')) {
ElMessage.error('请选择图片文件')
return
}
// 2MB
if (file.raw.size > 2 * 1024 * 1024) {
ElMessage.error('文件大小不能超过2MB')
return
}
selectedAvatarFile.value = file.raw
}
const handleUploadAvatar = async () => {
if (!selectedAvatarFile.value) {
ElMessage.error('请先选择头像文件')
return
}
try {
// TODO:
const response = await uploadAvatar(selectedAvatarFile.value) as any as ApiResponse<{ avatarUrl: string }>
if (response.code === 200) {
//
userProfile.value.avatar = response.data.avatarUrl
showAvatarUpload.value = false
selectedAvatarFile.value = null
ElMessage.success('头像上传成功!')
// store
if (userStore.user) {
userStore.user.avatar = response.data.avatarUrl
}
//
await loadUserProfile()
} else {
ElMessage.error(response.message || '头像上传失败')
}
} catch (error) {
console.error('上传头像失败:', error)
ElMessage.error('头像上传失败')

@ -368,51 +368,25 @@ const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '
//
const todaySchedules = computed(() => {
const today = new Date()
// 使
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, '0')
const day = String(today.getDate()).padStart(2, '0')
const todayStr = `${year}-${month}-${day}`
console.log('=== 今日日程详细调试 ===')
console.log('浏览器当前时间:', today)
console.log('计算的今天日期字符串:', todayStr)
console.log('所有日程数量:', schedules.value.length)
if (schedules.value.length > 0) {
console.log('所有日程数据:', schedules.value)
}
console.log('今天日期:', todayStr)
console.log('所有日程:', schedules.value)
const filtered = schedules.value.filter(schedule => {
//
let startDate, endDate
if (schedule.startTime && schedule.endTime) {
// YYYY-MM-DD
startDate = schedule.startTime.split('T')[0] || schedule.startTime.split(' ')[0]
endDate = schedule.endTime.split('T')[0] || schedule.endTime.split(' ')[0]
console.log(`检查日程 "${schedule.title}":`)
console.log(` 开始日期: ${startDate}`)
console.log(` 结束日期: ${endDate}`)
console.log(` 今天日期: ${todayStr}`)
console.log(` 条件1 (今天 >= 开始): ${todayStr} >= ${startDate} = ${todayStr >= startDate}`)
console.log(` 条件2 (今天 <= 结束): ${todayStr} <= ${endDate} = ${todayStr <= endDate}`)
//
const isInRange = todayStr >= startDate && todayStr <= endDate
console.log(` 最终结果: ${isInRange}`)
return isInRange
if (schedule.startTime) {
// YYYY-MM-DD
const scheduleDate = schedule.startTime.substring(0, 10)
console.log(`日程"${schedule.title}"的日期: ${scheduleDate}, 是否为今天: ${scheduleDate === todayStr}`)
return scheduleDate === todayStr
}
console.log(`日程 "${schedule.title}" 缺少时间信息,跳过`)
return false
})
console.log('筛选后的今日日程数量:', filtered.length)
console.log('筛选后的今日日程:', filtered)
console.log('=== 调试结束 ===')
console.log('今日日程:', filtered)
return filtered.sort((a, b) => {
if (a.isAllDay === 1 && b.isAllDay !== 1) return -1

@ -1,8 +1,10 @@
/// <reference types="vitest" />
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
// @ts-ignore
plugins: [vue()],
test: {
environment: 'jsdom',

@ -143,4 +143,122 @@ public class AdminController {
public Result deleteResource(@PathVariable Long resourceId) {
return adminService.deleteResource(resourceId);
}
@Operation(summary = "获取系统监控信息")
@GetMapping("/monitor/status")
public Result getSystemStatus() {
return adminService.getSystemStatus();
}
@Operation(summary = "获取系统日志")
@GetMapping("/logs")
public Result getSystemLogs(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "20") Integer size,
@RequestParam(required = false) String level,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
return adminService.getSystemLogs(page, size, level, keyword, startDate, endDate);
}
@Operation(summary = "获取系统设置")
@GetMapping("/settings")
public Result getSystemSettings() {
return adminService.getSystemSettings();
}
@Operation(summary = "更新系统设置")
@PostMapping("/settings")
public Result updateSystemSettings(@RequestBody Map<String, Object> settings) {
return adminService.updateSystemSettings(settings);
}
@Operation(summary = "获取系统公告列表")
@GetMapping("/announcements")
public Result getAnnouncements() {
return adminService.getAnnouncements();
}
@Operation(summary = "创建系统公告")
@PostMapping("/announcements")
public Result createAnnouncement(@RequestBody Map<String, Object> announcement) {
return adminService.createAnnouncement(announcement);
}
@Operation(summary = "更新系统公告")
@PutMapping("/announcements/{id}")
public Result updateAnnouncement(@PathVariable Long id, @RequestBody Map<String, Object> announcement) {
return adminService.updateAnnouncement(id, announcement);
}
@Operation(summary = "删除系统公告")
@DeleteMapping("/announcements/{id}")
public Result deleteAnnouncement(@PathVariable Long id) {
return adminService.deleteAnnouncement(id);
}
@Operation(summary = "获取系统通知")
@GetMapping("/notifications")
public Result getNotifications() {
return adminService.getNotifications();
}
@Operation(summary = "标记通知已读")
@PostMapping("/notifications/{id}/read")
public Result markNotificationAsRead(@PathVariable Long id) {
return adminService.markNotificationAsRead(id);
}
@Operation(summary = "测试邮件发送")
@PostMapping("/settings/email/test")
public Result testEmail(@RequestBody Map<String, String> request) {
return adminService.testEmail(request);
}
@Operation(summary = "获取数据统计")
@GetMapping("/statistics")
public Result getStatistics() {
return adminService.getStatistics();
}
@Operation(summary = "数据备份")
@PostMapping("/backup")
public Result backupData() {
return adminService.backupData();
}
// ========== 课表管理相关接口 ==========
@Operation(summary = "获取课程列表")
@GetMapping("/courses")
public Result getCourseList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long userId,
@RequestParam(required = false) String semester,
@RequestParam(required = false) Integer status) {
return adminService.getCourseList(page, size, keyword, userId, semester, status);
}
@Operation(summary = "获取课程详情")
@GetMapping("/courses/{courseId}")
public Result getCourseDetail(@PathVariable Long courseId) {
return adminService.getCourseDetail(courseId);
}
@Operation(summary = "删除课程")
@DeleteMapping("/courses/{courseId}")
public Result deleteCourse(@PathVariable Long courseId) {
return adminService.deleteCourse(courseId);
}
@Operation(summary = "获取用户课表")
@GetMapping("/users/{userId}/schedule")
public Result getUserSchedule(
@PathVariable Long userId,
@RequestParam(required = false, defaultValue = "2024-2025-2") String semester) {
return adminService.getUserSchedule(userId, semester);
}
}

@ -25,15 +25,17 @@ public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("JwtInterceptor preHandle");
String requestPath = request.getRequestURI();
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
log.debug("OPTIONS请求跳过token验证");
return true; // 直接允许通过,不检查 token
}
String authHeader = request.getHeader("Authorization");
if(StrUtil.isBlank(authHeader)){
log.warn("请求头中缺少Authorization字段 - Path: {}", requestPath);
response.setStatus(401);
return false;
}
@ -43,10 +45,9 @@ public class JwtInterceptor implements HandlerInterceptor {
if(authHeader.startsWith("Bearer ")){
token = authHeader.substring(7);
}
log.info("Extracted token:{}", token);
boolean verified = jwtUtil.verifyToken(token);
if (!verified) {
log.warn("Token验证失败 - Path: {}", requestPath);
response.setStatus(401);
return false;
}
@ -54,9 +55,12 @@ public class JwtInterceptor implements HandlerInterceptor {
//从token中获取userid并存入threadlocal
Long userId = jwtUtil.getUserIdFromToken(token);
if(userId == null) {
log.warn("无法从Token获取用户ID - Path: {}", requestPath);
response.setStatus(401);
return false;
}
log.debug("Token验证成功用户ID: {} - Path: {}", userId, requestPath);
BaseContext.setId(userId);
return true;
}

@ -106,4 +106,9 @@ public interface CommentMapper {
*
*/
void deleteComment(Long commentId);
/**
*
*/
void permanentDeleteComment(Long commentId);
}

@ -74,4 +74,49 @@ public interface CourseMapper {
@Param("startTime") String startTime,
@Param("endTime") String endTime,
@Param("excludeCourseId") Long excludeCourseId);
// ========== 管理员后台相关方法 ==========
/**
*
*/
int getTotalCount();
/**
*
*/
int getNewCourseCountToday();
/**
* ID
*/
Course getCourseById(Long id);
/**
*
*/
List<Course> getAdminCourseList(@Param("offset") int offset,
@Param("size") int size,
@Param("keyword") String keyword,
@Param("userId") Long userId,
@Param("semester") String semester,
@Param("status") Integer status);
/**
*
*/
int getAdminCourseCount(@Param("keyword") String keyword,
@Param("userId") Long userId,
@Param("semester") String semester,
@Param("status") Integer status);
/**
*
*/
void deleteCourse(Long courseId);
/**
*
*/
void permanentDeleteCourse(Long courseId);
}

@ -138,4 +138,9 @@ public interface ResourceMapper {
*
*/
void deleteResource(Long resourceId);
/**
*
*/
void permanentDeleteResource(Long resourceId);
}

@ -1,5 +1,6 @@
package com.unilife.model.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@ -26,11 +27,13 @@ public class CreateScheduleDTO {
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime endTime;
/**

@ -1,5 +1,6 @@
package com.unilife.model.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -42,11 +43,13 @@ public class Schedule implements Serializable {
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime endTime;
/**
@ -77,10 +80,12 @@ public class Schedule implements Serializable {
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
}

@ -1,5 +1,6 @@
package com.unilife.model.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -38,11 +39,13 @@ public class ScheduleVO {
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime startTime;
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime endTime;
/**
@ -73,10 +76,12 @@ public class ScheduleVO {
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt;
/**
*
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt;
}

@ -90,4 +90,91 @@ public interface AdminService {
*
*/
Result deleteResource(Long resourceId);
/**
*
*/
Result getSystemStatus();
/**
*
*/
Result getSystemLogs(Integer page, Integer size, String level, String keyword, String startDate, String endDate);
/**
*
*/
Result getSystemSettings();
/**
*
*/
Result updateSystemSettings(Map<String, Object> settings);
/**
*
*/
Result getAnnouncements();
/**
*
*/
Result createAnnouncement(Map<String, Object> announcement);
/**
*
*/
Result updateAnnouncement(Long id, Map<String, Object> announcement);
/**
*
*/
Result deleteAnnouncement(Long id);
/**
*
*/
Result getNotifications();
/**
*
*/
Result markNotificationAsRead(Long id);
/**
*
*/
Result testEmail(Map<String, String> request);
/**
*
*/
Result getStatistics();
/**
*
*/
Result backupData();
// ========== 课表管理相关方法 ==========
/**
*
*/
Result getCourseList(Integer page, Integer size, String keyword, Long userId, String semester, Integer status);
/**
*
*/
Result getCourseDetail(Long courseId);
/**
*
*/
Result deleteCourse(Long courseId);
/**
*
*/
Result getUserSchedule(Long userId, String semester);
}

@ -44,4 +44,9 @@ public interface UserService {
* @return
*/
Result deleteUser(Long userId);
/**
* 线
*/
int getOnlineUserCount();
}

@ -3,8 +3,10 @@ package com.unilife.service.impl;
import com.unilife.common.result.Result;
import com.unilife.mapper.*;
import com.unilife.model.entity.*;
import com.unilife.model.vo.CourseVO;
import com.unilife.service.AdminService;
import com.unilife.service.UserService;
import com.unilife.utils.OssService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@ -12,6 +14,8 @@ import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.stream.Collectors;
@Slf4j
@Service
@ -32,8 +36,17 @@ public class AdminServiceImpl implements AdminService {
@Autowired
private ResourceMapper resourceMapper;
@Autowired
private CourseMapper courseMapper;
@Autowired
private UserService userService;
@Autowired
private PdfVectorAsyncService pdfVectorAsyncService;
@Autowired
private OssService ossService;
@Override
public Result getSystemStats() {
@ -57,9 +70,40 @@ public class AdminServiceImpl implements AdminService {
stats.put("totalResources", resourceMapper.getTotalCount());
stats.put("newResourcesToday", resourceMapper.getNewResourceCountToday());
// 课程统计
stats.put("totalCourses", courseMapper.getTotalCount());
stats.put("newCoursesToday", courseMapper.getNewCourseCountToday());
// 分类统计
stats.put("totalCategories", categoryMapper.getTotalCount());
// 简单的增长趋势计算(基于今日新增)
int totalUsers = userMapper.getTotalCount();
int newUsersToday = userMapper.getNewUserCountToday();
double userGrowth = totalUsers > 0 ? (double) newUsersToday / totalUsers * 100 : 0;
int totalPosts = postMapper.getTotalCount();
int newPostsToday = postMapper.getNewPostCountToday();
double postGrowth = totalPosts > 0 ? (double) newPostsToday / totalPosts * 100 : 0;
int totalComments = commentMapper.getTotalCount();
int newCommentsToday = commentMapper.getNewCommentCountToday();
double commentGrowth = totalComments > 0 ? (double) newCommentsToday / totalComments * 100 : 0;
int totalResources = resourceMapper.getTotalCount();
int newResourcesToday = resourceMapper.getNewResourceCountToday();
double resourceGrowth = totalResources > 0 ? (double) newResourcesToday / totalResources * 100 : 0;
int totalCourses = courseMapper.getTotalCount();
int newCoursesToday = courseMapper.getNewCourseCountToday();
double courseGrowth = totalCourses > 0 ? (double) newCoursesToday / totalCourses * 100 : 0;
stats.put("userGrowth", Math.round(userGrowth * 100.0) / 100.0);
stats.put("postGrowth", Math.round(postGrowth * 100.0) / 100.0);
stats.put("commentGrowth", Math.round(commentGrowth * 100.0) / 100.0);
stats.put("resourceGrowth", Math.round(resourceGrowth * 100.0) / 100.0);
stats.put("courseGrowth", Math.round(courseGrowth * 100.0) / 100.0);
return Result.success(stats);
} catch (Exception e) {
log.error("获取系统统计数据失败", e);
@ -67,6 +111,8 @@ public class AdminServiceImpl implements AdminService {
}
}
@Override
public Result getUserList(Integer page, Integer size, String keyword, Integer role, Integer status) {
try {
@ -131,7 +177,7 @@ public class AdminServiceImpl implements AdminService {
return Result.error(400, "不能删除管理员账号");
}
// 调用UserService的完整删除逻辑
// 调用UserService的完整删除逻辑(逻辑删除,保留用户删除的复杂逻辑)
return userService.deleteUser(userId);
} catch (Exception e) {
log.error("删除用户失败", e);
@ -182,7 +228,8 @@ public class AdminServiceImpl implements AdminService {
return Result.error(404, "帖子不存在");
}
postMapper.deletePost(postId);
// 物理删除帖子及相关数据
postMapper.permanentDeletePost(postId);
return Result.success(null, "帖子删除成功");
} catch (Exception e) {
log.error("删除帖子失败", e);
@ -234,7 +281,8 @@ public class AdminServiceImpl implements AdminService {
return Result.error(404, "评论不存在");
}
commentMapper.deleteComment(commentId);
// 物理删除评论
commentMapper.permanentDeleteComment(commentId);
return Result.success(null, "评论删除成功");
} catch (Exception e) {
log.error("删除评论失败", e);
@ -346,11 +394,313 @@ public class AdminServiceImpl implements AdminService {
return Result.error(404, "资源不存在");
}
resourceMapper.deleteResource(resourceId);
// 如果是PDF文件需要先删除向量数据库中的相关文档
if ("application/pdf".equals(resource.getFileType())) {
pdfVectorAsyncService.deleteVectorDocumentsAsync(resourceId, resource.getTitle());
log.info("PDF文件已提交异步删除向量文档资源ID: {}", resourceId);
}
// 删除OSS中的文件
try {
String fileUrl = resource.getFileUrl();
if (fileUrl != null && fileUrl.startsWith("http")) {
ossService.deleteFile(fileUrl);
log.info("OSS文件删除成功资源ID: {}", resourceId);
}
} catch (Exception e) {
log.error("删除OSS文件失败资源ID: {}", resourceId, e);
// 继续执行,不影响数据库记录的删除
}
// 最后物理删除资源数据库记录
resourceMapper.permanentDeleteResource(resourceId);
return Result.success(null, "资源删除成功");
} catch (Exception e) {
log.error("删除资源失败", e);
return Result.error(500, "删除资源失败");
}
}
@Override
public Result getSystemStatus() {
try {
Map<String, Object> status = new HashMap<>();
// 简单的应用状态
Map<String, Object> appStatus = new HashMap<>();
appStatus.put("online", true);
appStatus.put("onlineUsers", userService.getOnlineUserCount());
status.put("application", appStatus);
return Result.success(status);
} catch (Exception e) {
log.error("获取系统状态失败", e);
return Result.error(500, "获取系统状态失败");
}
}
@Override
public Result getSystemLogs(Integer page, Integer size, String level, String keyword, String startDate, String endDate) {
// 日志管理功能较为复杂,暂不实现
return Result.error(501, "日志管理功能暂未实现");
}
@Override
public Result getSystemSettings() {
// 系统设置功能较为复杂,暂不实现
return Result.error(501, "系统设置功能暂未实现");
}
@Override
public Result updateSystemSettings(Map<String, Object> settings) {
// 系统设置功能较为复杂,暂不实现
return Result.error(501, "系统设置功能暂未实现");
}
@Override
public Result getAnnouncements() {
// 公告功能较为复杂,暂不实现
return Result.error(501, "公告功能暂未实现");
}
@Override
public Result createAnnouncement(Map<String, Object> announcement) {
// 公告功能较为复杂,暂不实现
return Result.error(501, "公告功能暂未实现");
}
@Override
public Result updateAnnouncement(Long id, Map<String, Object> announcement) {
// 公告功能较为复杂,暂不实现
return Result.error(501, "公告功能暂未实现");
}
@Override
public Result deleteAnnouncement(Long id) {
// 公告功能较为复杂,暂不实现
return Result.error(501, "公告功能暂未实现");
}
@Override
public Result getNotifications() {
// 通知功能较为复杂,暂不实现
return Result.error(501, "通知功能暂未实现");
}
@Override
public Result markNotificationAsRead(Long id) {
// 通知功能较为复杂,暂不实现
return Result.error(501, "通知功能暂未实现");
}
@Override
public Result testEmail(Map<String, String> request) {
// 邮件测试功能较为复杂,暂不实现
return Result.error(501, "邮件测试功能暂未实现");
}
@Override
public Result getStatistics() {
// 统计图表功能较为复杂,暂不实现
return Result.error(501, "统计图表功能暂未实现");
}
@Override
public Result backupData() {
// 数据备份功能较为复杂,暂不实现
return Result.error(501, "数据备份功能暂未实现");
}
// ========== 课表管理相关方法 ==========
@Override
public Result getCourseList(Integer page, Integer size, String keyword, Long userId, String semester, Integer status) {
try {
int offset = (page - 1) * size;
List<Course> courses = courseMapper.getAdminCourseList(offset, size, keyword, userId, semester, status);
int total = courseMapper.getAdminCourseCount(keyword, userId, semester, status);
// 转换为VO并添加用户信息
List<Map<String, Object>> courseList = courses.stream().map(course -> {
Map<String, Object> courseInfo = new HashMap<>();
courseInfo.put("id", course.getId());
courseInfo.put("userId", course.getUserId());
courseInfo.put("name", course.getName());
courseInfo.put("teacher", course.getTeacher());
courseInfo.put("location", course.getLocation());
courseInfo.put("dayOfWeek", course.getDayOfWeek());
courseInfo.put("startTime", course.getStartTime());
courseInfo.put("endTime", course.getEndTime());
courseInfo.put("startWeek", course.getStartWeek());
courseInfo.put("endWeek", course.getEndWeek());
courseInfo.put("semester", course.getSemester());
courseInfo.put("color", course.getColor());
courseInfo.put("status", course.getStatus());
courseInfo.put("createdAt", course.getCreatedAt());
courseInfo.put("updatedAt", course.getUpdatedAt());
// 获取用户信息
try {
User user = userMapper.getUserById(course.getUserId());
if (user != null) {
courseInfo.put("username", user.getUsername());
courseInfo.put("nickname", user.getNickname());
courseInfo.put("studentId", user.getStudentId());
courseInfo.put("department", user.getDepartment());
courseInfo.put("major", user.getMajor());
}
} catch (Exception e) {
log.warn("获取课程用户信息失败课程ID: {}, 用户ID: {}", course.getId(), course.getUserId());
}
return courseInfo;
}).collect(Collectors.toList());
Map<String, Object> result = new HashMap<>();
result.put("list", courseList);
result.put("total", total);
result.put("pages", (total + size - 1) / size);
return Result.success(result);
} catch (Exception e) {
log.error("获取课程列表失败", e);
return Result.error(500, "获取课程列表失败");
}
}
@Override
public Result getCourseDetail(Long courseId) {
try {
Course course = courseMapper.getCourseById(courseId);
if (course == null) {
return Result.error(404, "课程不存在");
}
// 构建课程详情信息
Map<String, Object> courseDetail = new HashMap<>();
courseDetail.put("id", course.getId());
courseDetail.put("userId", course.getUserId());
courseDetail.put("name", course.getName());
courseDetail.put("teacher", course.getTeacher());
courseDetail.put("location", course.getLocation());
courseDetail.put("dayOfWeek", course.getDayOfWeek());
courseDetail.put("startTime", course.getStartTime());
courseDetail.put("endTime", course.getEndTime());
courseDetail.put("startWeek", course.getStartWeek());
courseDetail.put("endWeek", course.getEndWeek());
courseDetail.put("semester", course.getSemester());
courseDetail.put("color", course.getColor());
courseDetail.put("status", course.getStatus());
courseDetail.put("createdAt", course.getCreatedAt());
courseDetail.put("updatedAt", course.getUpdatedAt());
// 获取用户信息
User user = userMapper.getUserById(course.getUserId());
if (user != null) {
Map<String, Object> userInfo = new HashMap<>();
userInfo.put("id", user.getId());
userInfo.put("username", user.getUsername());
userInfo.put("nickname", user.getNickname());
userInfo.put("studentId", user.getStudentId());
userInfo.put("department", user.getDepartment());
userInfo.put("major", user.getMajor());
userInfo.put("grade", user.getGrade());
courseDetail.put("user", userInfo);
}
return Result.success(courseDetail);
} catch (Exception e) {
log.error("获取课程详情失败", e);
return Result.error(500, "获取课程详情失败");
}
}
@Override
public Result deleteCourse(Long courseId) {
try {
Course course = courseMapper.getCourseById(courseId);
if (course == null) {
return Result.error(404, "课程不存在");
}
// 物理删除课程
courseMapper.permanentDeleteCourse(courseId);
return Result.success(null, "课程删除成功");
} catch (Exception e) {
log.error("删除课程失败", e);
return Result.error(500, "删除课程失败");
}
}
@Override
public Result getUserSchedule(Long userId, String semester) {
try {
log.info("=== 管理员获取用户课表 ===");
log.info("用户ID: {}, 学期: {}", userId, semester);
User user = userMapper.getUserById(userId);
if (user == null) {
log.warn("用户不存在用户ID: {}", userId);
return Result.error(404, "用户不存在");
}
log.info("找到用户: {}, 昵称: {}, 学号: {}", user.getUsername(), user.getNickname(), user.getStudentId());
// 获取用户在指定学期的课程
List<Course> courses = courseMapper.getListByUserIdAndSemester(userId, semester);
log.info("查询到课程数量: {}", courses.size());
if (!courses.isEmpty()) {
log.info("课程详情:");
for (Course course : courses) {
log.info("- 课程: {}, 教师: {}, 星期: {}, 时间: {}-{}",
course.getName(), course.getTeacher(), course.getDayOfWeek(),
course.getStartTime(), course.getEndTime());
}
}
// 按星期几分组组织课表数据
Map<Integer, List<Map<String, Object>>> schedule = new HashMap<>();
for (int i = 1; i <= 7; i++) {
schedule.put(i, new ArrayList<>());
}
for (Course course : courses) {
Map<String, Object> courseInfo = new HashMap<>();
courseInfo.put("id", course.getId());
courseInfo.put("name", course.getName());
courseInfo.put("teacher", course.getTeacher());
courseInfo.put("location", course.getLocation());
courseInfo.put("startTime", course.getStartTime());
courseInfo.put("endTime", course.getEndTime());
courseInfo.put("startWeek", course.getStartWeek());
courseInfo.put("endWeek", course.getEndWeek());
courseInfo.put("color", course.getColor());
courseInfo.put("status", course.getStatus());
schedule.get((int) course.getDayOfWeek()).add(courseInfo);
}
Map<String, Object> result = new HashMap<>();
result.put("user", Map.of(
"id", user.getId(),
"username", user.getUsername(),
"nickname", user.getNickname(),
"studentId", user.getStudentId(),
"department", user.getDepartment(),
"major", user.getMajor(),
"grade", user.getGrade()
));
result.put("semester", semester);
result.put("schedule", schedule);
result.put("totalCourses", courses.size());
log.info("返回结果,总课程数: {}", courses.size());
return Result.success(result);
} catch (Exception e) {
log.error("获取用户课表失败", e);
return Result.error(500, "获取用户课表失败");
}
}
}

@ -20,6 +20,7 @@ import com.unilife.model.vo.LoginVO;
import com.unilife.service.IPLocationService;
import com.unilife.service.UserService;
import com.unilife.utils.JwtUtil;
import com.unilife.utils.OssService;
import com.unilife.utils.RegexUtils;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
@ -73,6 +74,9 @@ public class UserServiceImpl implements UserService {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private OssService ossService;
@Value("${spring.mail.username}")
private String from;
@ -455,26 +459,36 @@ public class UserServiceImpl implements UserService {
return Result.error(400, "只能上传图片文件");
}
try {
// 生成文件名
String originalFilename = file.getOriginalFilename();
String suffix = originalFilename != null ? originalFilename.substring(originalFilename.lastIndexOf(".")) : ".jpg";
String filename = "avatar_" + userId + "_" + System.currentTimeMillis() + suffix;
// 检查文件大小限制为2MB
if (file.getSize() > 2 * 1024 * 1024) {
return Result.error(400, "文件大小不能超过2MB");
}
// TODO: 实际项目中应该将文件保存到云存储或服务器指定目录
// 这里简化处理假设保存成功并返回URL
String avatarUrl = "https://example.com/avatars/" + filename;
try {
// 删除旧头像(如果存在且不是默认头像)
String oldAvatarUrl = user.getAvatar();
if (StringUtils.isNotEmpty(oldAvatarUrl) && oldAvatarUrl.contains("oss")) {
try {
ossService.deleteFile(oldAvatarUrl);
} catch (Exception e) {
log.warn("删除旧头像失败: {}", oldAvatarUrl, e);
}
}
// 上传新头像到OSS
String avatarUrl = ossService.uploadFile(file, "avatars");
// 更新用户头像URL
userMapper.updateAvatar(userId, avatarUrl);
Map<String, String> data = new HashMap<>();
data.put("avatar", avatarUrl);
data.put("avatarUrl", avatarUrl);
log.info("用户头像上传成功: userId={}, avatarUrl={}", userId, avatarUrl);
return Result.success(data, "头像上传成功");
} catch (Exception e) {
log.error("头像上传失败", e);
return Result.error(500, "头像上传失败");
log.error("头像上传失败: userId={}", userId, e);
return Result.error(500, "头像上传失败: " + e.getMessage());
}
}
@ -633,4 +647,16 @@ public class UserServiceImpl implements UserService {
return Result.error(500, "删除用户失败:" + e.getMessage());
}
}
@Override
public int getOnlineUserCount() {
try {
// 这里可以实现实际的在线用户统计逻辑
// 例如从Redis中获取在线session数量
return userMapper.getActiveUserCount();
} catch (Exception e) {
log.error("获取在线用户数量失败", e);
return 0;
}
}
}

@ -25,9 +25,10 @@ public class JwtUtil {
Map<String, Object> payload = new HashMap<>();
payload.put("userId", id);
payload.put("created", now.getTime());
payload.put("iat", now.getTime() / 1000); // 签发时间(秒)
payload.put("exp", expireTime.getTime() / 1000); // JWT标准过期时间字段
return JWTUtil.createToken(payload, secret.getBytes());
String token = JWTUtil.createToken(payload, secret.getBytes());
return token;
}
public boolean verifyToken(String token) {
@ -38,18 +39,26 @@ public class JwtUtil {
// 验证过期时间
JWT jwt = JWTUtil.parseToken(token);
Object expObj = jwt.getPayload("exp");
Object iatObj = jwt.getPayload("iat");
if (expObj != null) {
long exp = Long.parseLong(expObj.toString());
long currentTime = System.currentTimeMillis() / 1000;
if (currentTime > exp) {
log.debug("Token已过期: exp={}, current={}", exp, currentTime);
log.warn("Token已过期: exp={} ({}), current={} ({})",
exp, new DateTime(exp * 1000),
currentTime, new DateTime(currentTime * 1000));
return false;
}
} else {
log.warn("Token中没有过期时间字段");
return false;
}
return true;
} catch (Exception e) {
log.debug("Token验证失败: {}", e.getMessage());
log.warn("Token验证失败: {}", e.getMessage());
return false;
}
}
@ -58,11 +67,14 @@ public class JwtUtil {
try {
// 先验证token是否有效
if (!verifyToken(token)) {
log.warn("Token验证失败无法获取用户ID");
return null;
}
return Long.valueOf(JWTUtil.parseToken(token).getPayload("userId").toString());
Long userId = Long.valueOf(JWTUtil.parseToken(token).getPayload("userId").toString());
log.debug("从Token获取用户ID: {}", userId);
return userId;
} catch (Exception e) {
log.debug("从Token获取用户ID失败: {}", e.getMessage());
log.warn("从Token获取用户ID失败: {}", e.getMessage());
return null;
}
}

@ -8,6 +8,11 @@ server:
spring:
main:
allow-bean-definition-overriding: true # 允许Bean定义覆盖
# Jackson JSON配置
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
default-property-inclusion: non_null
ai:
openai:
base-url: https://dashscope.aliyuncs.com/compatible-mode
@ -100,7 +105,7 @@ logging:
org.springframework.ai: debug
jwt:
secret: qwertyuiopasdfghjklzxcvbnm
expiration: 300 # 5分钟过期时间5 * 60 = 300秒用于测试
expiration: 60 # 60秒过期时间用于测试JWT过期逻辑
# 添加阿里云OSS配置
aliyun:
oss:

@ -133,4 +133,9 @@
updated_at = NOW()
WHERE id = #{commentId}
</update>
<delete id="permanentDeleteComment">
DELETE FROM comments
WHERE id = #{commentId}
</delete>
</mapper>

@ -96,4 +96,78 @@
AND id != #{excludeCourseId}
</if>
</select>
<!-- ========== 管理员后台相关方法 ========== -->
<select id="getTotalCount" resultType="int">
SELECT COUNT(*)
FROM courses
WHERE status != 0
</select>
<select id="getNewCourseCountToday" resultType="int">
SELECT COUNT(*)
FROM courses
WHERE status != 0 AND DATE(created_at) = CURDATE()
</select>
<select id="getCourseById" resultMap="courseResultMap">
SELECT id, user_id, name, teacher, location, day_of_week, start_time, end_time,
start_week, end_week, semester, color, status, created_at, updated_at
FROM courses
WHERE id = #{id}
</select>
<select id="getAdminCourseList" resultMap="courseResultMap">
SELECT c.id, c.user_id, c.name, c.teacher, c.location, c.day_of_week, c.start_time, c.end_time,
c.start_week, c.end_week, c.semester, c.color, c.status, c.created_at, c.updated_at
FROM courses c
<where>
<if test="keyword != null and keyword != ''">
AND (c.name LIKE CONCAT('%', #{keyword}, '%') OR c.teacher LIKE CONCAT('%', #{keyword}, '%') OR c.location LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="userId != null">
AND c.user_id = #{userId}
</if>
<if test="semester != null and semester != ''">
AND c.semester = #{semester}
</if>
<if test="status != null">
AND c.status = #{status}
</if>
</where>
ORDER BY c.created_at DESC
LIMIT #{offset}, #{size}
</select>
<select id="getAdminCourseCount" resultType="int">
SELECT COUNT(*)
FROM courses c
<where>
<if test="keyword != null and keyword != ''">
AND (c.name LIKE CONCAT('%', #{keyword}, '%') OR c.teacher LIKE CONCAT('%', #{keyword}, '%') OR c.location LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="userId != null">
AND c.user_id = #{userId}
</if>
<if test="semester != null and semester != ''">
AND c.semester = #{semester}
</if>
<if test="status != null">
AND c.status = #{status}
</if>
</where>
</select>
<update id="deleteCourse">
UPDATE courses
SET status = 0,
updated_at = NOW()
WHERE id = #{courseId}
</update>
<delete id="permanentDeleteCourse">
DELETE FROM courses
WHERE id = #{courseId}
</delete>
</mapper>

@ -179,4 +179,9 @@
updated_at = NOW()
WHERE id = #{resourceId}
</update>
<delete id="permanentDeleteResource">
DELETE FROM resources
WHERE id = #{resourceId}
</delete>
</mapper>

@ -1,169 +0,0 @@
package com.unilife.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unilife.common.result.Result;
import com.unilife.model.dto.CreatePostDTO;
import com.unilife.model.dto.UpdatePostDTO;
import com.unilife.service.PostService;
import com.unilife.utils.BaseContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(PostController.class)
class PostControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private PostService postService;
@Autowired
private ObjectMapper objectMapper;
private CreatePostDTO createPostDTO;
private UpdatePostDTO updatePostDTO;
@BeforeEach
void setUp() {
createPostDTO = new CreatePostDTO();
createPostDTO.setTitle("测试帖子");
createPostDTO.setContent("测试内容");
createPostDTO.setCategoryId(1L);
updatePostDTO = new UpdatePostDTO();
updatePostDTO.setTitle("更新标题");
updatePostDTO.setContent("更新内容");
updatePostDTO.setCategoryId(1L);
}
@Test
void testCreatePost_Success() throws Exception {
// Mock用户已登录
try (var mockedStatic = mockStatic(BaseContext.class)) {
mockedStatic.when(BaseContext::getId).thenReturn(1L);
when(postService.createPost(eq(1L), any(CreatePostDTO.class)))
.thenReturn(Result.success("帖子发布成功"));
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createPostDTO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true))
.andExpect(jsonPath("$.message").value("帖子发布成功"));
verify(postService).createPost(eq(1L), any(CreatePostDTO.class));
}
}
@Test
void testCreatePost_Unauthorized() throws Exception {
// Mock用户未登录
try (var mockedStatic = mockStatic(BaseContext.class)) {
mockedStatic.when(BaseContext::getId).thenReturn(null);
mockMvc.perform(post("/posts")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(createPostDTO)))
.andExpect(status().isOk())
.andExpected(jsonPath("$.success").value(false))
.andExpected(jsonPath("$.code").value(401))
.andExpected(jsonPath("$.message").value("未登录"));
verify(postService, never()).createPost(anyLong(), any(CreatePostDTO.class));
}
}
@Test
void testGetPostDetail_Success() throws Exception {
when(postService.getPostDetail(eq(1L), any()))
.thenReturn(Result.success("帖子详情"));
mockMvc.perform(get("/posts/1"))
.andExpect(status().isOk())
.andExpected(jsonPath("$.success").value(true));
verify(postService).getPostDetail(eq(1L), any());
}
@Test
void testGetPostList_Success() throws Exception {
when(postService.getPostList(any(), any(), anyInt(), anyInt(), any(), any()))
.thenReturn(Result.success("帖子列表"));
mockMvc.perform(get("/posts")
.param("categoryId", "1")
.param("keyword", "测试")
.param("page", "1")
.param("size", "10")
.param("sort", "latest"))
.andExpected(status().isOk())
.andExpected(jsonPath("$.success").value(true));
verify(postService).getPostList(eq(1L), eq("测试"), eq(1), eq(10), eq("latest"), any());
}
@Test
void testUpdatePost_Success() throws Exception {
try (var mockedStatic = mockStatic(BaseContext.class)) {
mockedStatic.when(BaseContext::getId).thenReturn(1L);
when(postService.updatePost(eq(1L), eq(1L), any(UpdatePostDTO.class)))
.thenReturn(Result.success("帖子更新成功"));
mockMvc.perform(put("/posts/1")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatePostDTO)))
.andExpected(status().isOk())
.andExpected(jsonPath("$.success").value(true))
.andExpected(jsonPath("$.message").value("帖子更新成功"));
verify(postService).updatePost(eq(1L), eq(1L), any(UpdatePostDTO.class));
}
}
@Test
void testDeletePost_Success() throws Exception {
try (var mockedStatic = mockStatic(BaseContext.class)) {
mockedStatic.when(BaseContext::getId).thenReturn(1L);
when(postService.deletePost(eq(1L), eq(1L)))
.thenReturn(Result.success("帖子删除成功"));
mockMvc.perform(delete("/posts/1"))
.andExpected(status().isOk())
.andExpected(jsonPath("$.success").value(true))
.andExpected(jsonPath("$.message").value("帖子删除成功"));
verify(postService).deletePost(eq(1L), eq(1L));
}
}
@Test
void testLikePost_Success() throws Exception {
try (var mockedStatic = mockStatic(BaseContext.class)) {
mockedStatic.when(BaseContext::getId).thenReturn(1L);
when(postService.likePost(eq(1L), eq(1L)))
.thenReturn(Result.success("点赞成功"));
mockMvc.perform(post("/posts/1/like"))
.andExpected(status().isOk())
.andExpected(jsonPath("$.success").value(true))
.andExpected(jsonPath("$.message").value("点赞成功"));
verify(postService).likePost(eq(1L), eq(1L));
}
}
}

@ -1,287 +0,0 @@
package com.unilife.service;
import com.unilife.common.result.Result;
import com.unilife.mapper.PostMapper;
import com.unilife.mapper.UserMapper;
import com.unilife.mapper.CategoryMapper;
import com.unilife.model.dto.CreatePostDTO;
import com.unilife.model.dto.UpdatePostDTO;
import com.unilife.model.entity.Post;
import com.unilife.model.entity.User;
import com.unilife.model.entity.Category;
import com.unilife.service.impl.PostServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@SpringBootTest
class PostServiceTest {
@Mock
private PostMapper postMapper;
@Mock
private UserMapper userMapper;
@Mock
private CategoryMapper categoryMapper;
@InjectMocks
private PostServiceImpl postService;
private User testUser;
private Category testCategory;
private Post testPost;
private CreatePostDTO createPostDTO;
private UpdatePostDTO updatePostDTO;
@BeforeEach
void setUp() {
// 初始化测试数据
testUser = new User();
testUser.setId(1L);
testUser.setNickname("测试用户");
testUser.setAvatar("avatar.jpg");
testCategory = new Category();
testCategory.setId(1L);
testCategory.setName("学习讨论");
testCategory.setStatus(1);
testPost = new Post();
testPost.setId(1L);
testPost.setTitle("测试帖子");
testPost.setContent("这是一个测试帖子的内容");
testPost.setUserId(1L);
testPost.setCategoryId(1L);
testPost.setLikeCount(0);
testPost.setViewCount(0);
testPost.setCommentCount(0);
testPost.setCreatedAt(LocalDateTime.now());
testPost.setUpdatedAt(LocalDateTime.now());
createPostDTO = new CreatePostDTO();
createPostDTO.setTitle("新帖子标题");
createPostDTO.setContent("新帖子内容");
createPostDTO.setCategoryId(1L);
updatePostDTO = new UpdatePostDTO();
updatePostDTO.setTitle("更新后的标题");
updatePostDTO.setContent("更新后的内容");
updatePostDTO.setCategoryId(1L);
}
@Test
void testCreatePost_Success() {
// Mock 依赖方法
when(userMapper.findById(1L)).thenReturn(testUser);
when(categoryMapper.findById(1L)).thenReturn(testCategory);
when(postMapper.insert(any(Post.class))).thenReturn(1);
// 执行测试
Result<?> result = postService.createPost(1L, createPostDTO);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("帖子发布成功", result.getMessage());
// 验证方法调用
verify(userMapper).findById(1L);
verify(categoryMapper).findById(1L);
verify(postMapper).insert(any(Post.class));
}
@Test
void testCreatePost_UserNotFound() {
// Mock 用户不存在
when(userMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = postService.createPost(1L, createPostDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("用户不存在", result.getMessage());
// 验证不会尝试创建帖子
verify(postMapper, never()).insert(any(Post.class));
}
@Test
void testCreatePost_CategoryNotFound() {
// Mock 用户存在但分类不存在
when(userMapper.findById(1L)).thenReturn(testUser);
when(categoryMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = postService.createPost(1L, createPostDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("分类不存在", result.getMessage());
}
@Test
void testCreatePost_InvalidTitle() {
// 测试空标题
createPostDTO.setTitle("");
// 执行测试
Result<?> result = postService.createPost(1L, createPostDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertTrue(result.getMessage().contains("标题不能为空"));
}
@Test
void testGetPostDetail_Success() {
// Mock 依赖方法
when(postMapper.findById(1L)).thenReturn(testPost);
when(userMapper.findById(1L)).thenReturn(testUser);
when(categoryMapper.findById(1L)).thenReturn(testCategory);
// 执行测试
Result<?> result = postService.getPostDetail(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
// 验证浏览量增加
verify(postMapper).updateViewCount(1L);
}
@Test
void testGetPostDetail_PostNotFound() {
// Mock 帖子不存在
when(postMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = postService.getPostDetail(1L, 1L);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("帖子不存在", result.getMessage());
}
@Test
void testGetPostList_Success() {
// Mock 帖子列表
List<Post> posts = Arrays.asList(testPost);
when(postMapper.findByConditions(any(), any(), anyInt(), anyInt(), any())).thenReturn(posts);
when(postMapper.countByConditions(any(), any())).thenReturn(1);
// 执行测试
Result<?> result = postService.getPostList(1L, "测试", 1, 10, "latest", 1L);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
}
@Test
void testUpdatePost_Success() {
// Mock 依赖方法
when(postMapper.findById(1L)).thenReturn(testPost);
when(categoryMapper.findById(1L)).thenReturn(testCategory);
when(postMapper.update(any(Post.class))).thenReturn(1);
// 执行测试
Result<?> result = postService.updatePost(1L, 1L, updatePostDTO);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("帖子更新成功", result.getMessage());
// 验证方法调用
verify(postMapper).update(any(Post.class));
}
@Test
void testUpdatePost_Unauthorized() {
// Mock 其他用户的帖子
testPost.setUserId(2L);
when(postMapper.findById(1L)).thenReturn(testPost);
// 执行测试
Result<?> result = postService.updatePost(1L, 1L, updatePostDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(403, result.getCode());
assertEquals("无权限修改此帖子", result.getMessage());
}
@Test
void testDeletePost_Success() {
// Mock 依赖方法
when(postMapper.findById(1L)).thenReturn(testPost);
when(postMapper.delete(1L)).thenReturn(1);
// 执行测试
Result<?> result = postService.deletePost(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("帖子删除成功", result.getMessage());
// 验证方法调用
verify(postMapper).delete(1L);
}
@Test
void testLikePost_Success() {
// Mock 依赖方法
when(postMapper.findById(1L)).thenReturn(testPost);
when(postMapper.isLikedByUser(1L, 1L)).thenReturn(false);
when(postMapper.insertLike(1L, 1L)).thenReturn(1);
// 执行测试
Result<?> result = postService.likePost(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("点赞成功", result.getMessage());
// 验证方法调用
verify(postMapper).insertLike(1L, 1L);
verify(postMapper).updateLikeCount(1L, 1);
}
@Test
void testUnlikePost_Success() {
// Mock 已点赞状态
when(postMapper.findById(1L)).thenReturn(testPost);
when(postMapper.isLikedByUser(1L, 1L)).thenReturn(true);
when(postMapper.deleteLike(1L, 1L)).thenReturn(1);
// 执行测试
Result<?> result = postService.likePost(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("取消点赞成功", result.getMessage());
// 验证方法调用
verify(postMapper).deleteLike(1L, 1L);
verify(postMapper).updateLikeCount(1L, -1);
}
}

@ -1,348 +0,0 @@
package com.unilife.service;
import com.unilife.common.result.Result;
import com.unilife.mapper.ResourceMapper;
import com.unilife.mapper.UserMapper;
import com.unilife.mapper.CategoryMapper;
import com.unilife.model.dto.CreateResourceDTO;
import com.unilife.model.entity.Resource;
import com.unilife.model.entity.User;
import com.unilife.model.entity.Category;
import com.unilife.service.impl.ResourceServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockMultipartFile;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@SpringBootTest
class ResourceServiceTest {
@Mock
private ResourceMapper resourceMapper;
@Mock
private UserMapper userMapper;
@Mock
private CategoryMapper categoryMapper;
@InjectMocks
private ResourceServiceImpl resourceService;
private User testUser;
private Category testCategory;
private Resource testResource;
private CreateResourceDTO createResourceDTO;
private MockMultipartFile mockFile;
@BeforeEach
void setUp() {
// 初始化测试数据
testUser = new User();
testUser.setId(1L);
testUser.setNickname("测试用户");
testUser.setAvatar("avatar.jpg");
testCategory = new Category();
testCategory.setId(1L);
testCategory.setName("学习资料");
testCategory.setStatus(1);
testResource = new Resource();
testResource.setId(1L);
testResource.setTitle("测试资源");
testResource.setDescription("测试资源描述");
testResource.setFileName("test.pdf");
testResource.setFileUrl("http://example.com/test.pdf");
testResource.setFileSize(1024L);
testResource.setFileType("pdf");
testResource.setUserId(1L);
testResource.setCategoryId(1L);
testResource.setDownloadCount(0);
testResource.setLikeCount(0);
testResource.setCreatedAt(LocalDateTime.now());
testResource.setUpdatedAt(LocalDateTime.now());
createResourceDTO = new CreateResourceDTO();
createResourceDTO.setTitle("新资源标题");
createResourceDTO.setDescription("新资源描述");
createResourceDTO.setCategoryId(1L);
mockFile = new MockMultipartFile(
"file",
"test.pdf",
"application/pdf",
"test content".getBytes()
);
}
@Test
void testUploadResource_Success() {
// Mock 依赖方法
when(userMapper.findById(1L)).thenReturn(testUser);
when(categoryMapper.findById(1L)).thenReturn(testCategory);
when(resourceMapper.insert(any(Resource.class))).thenReturn(1);
// 执行测试
Result<?> result = resourceService.uploadResource(1L, createResourceDTO, mockFile);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("资源上传成功", result.getMessage());
// 验证方法调用
verify(userMapper).findById(1L);
verify(categoryMapper).findById(1L);
verify(resourceMapper).insert(any(Resource.class));
}
@Test
void testUploadResource_UserNotFound() {
// Mock 用户不存在
when(userMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = resourceService.uploadResource(1L, createResourceDTO, mockFile);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("用户不存在", result.getMessage());
// 验证不会尝试上传资源
verify(resourceMapper, never()).insert(any(Resource.class));
}
@Test
void testUploadResource_CategoryNotFound() {
// Mock 用户存在但分类不存在
when(userMapper.findById(1L)).thenReturn(testUser);
when(categoryMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = resourceService.uploadResource(1L, createResourceDTO, mockFile);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("分类不存在", result.getMessage());
}
@Test
void testUploadResource_EmptyFile() {
// 测试空文件
MockMultipartFile emptyFile = new MockMultipartFile(
"file",
"empty.pdf",
"application/pdf",
new byte[0]
);
// 执行测试
Result<?> result = resourceService.uploadResource(1L, createResourceDTO, emptyFile);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertEquals("文件不能为空", result.getMessage());
}
@Test
void testUploadResource_InvalidFileType() {
// 测试不支持的文件类型
MockMultipartFile invalidFile = new MockMultipartFile(
"file",
"test.exe",
"application/octet-stream",
"test content".getBytes()
);
// 执行测试
Result<?> result = resourceService.uploadResource(1L, createResourceDTO, invalidFile);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertTrue(result.getMessage().contains("不支持的文件类型"));
}
@Test
void testGetResourceDetail_Success() {
// Mock 依赖方法
when(resourceMapper.findById(1L)).thenReturn(testResource);
when(userMapper.findById(1L)).thenReturn(testUser);
when(categoryMapper.findById(1L)).thenReturn(testCategory);
// 执行测试
Result<?> result = resourceService.getResourceDetail(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
}
@Test
void testGetResourceDetail_ResourceNotFound() {
// Mock 资源不存在
when(resourceMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = resourceService.getResourceDetail(1L, 1L);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("资源不存在", result.getMessage());
}
@Test
void testGetResourceList_Success() {
// Mock 资源列表
List<Resource> resources = Arrays.asList(testResource);
when(resourceMapper.findByConditions(any(), any(), any(), anyInt(), anyInt())).thenReturn(resources);
when(resourceMapper.countByConditions(any(), any(), any())).thenReturn(1);
// 执行测试
Result<?> result = resourceService.getResourceList(1L, 1L, "测试", 1, 10, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
}
@Test
void testUpdateResource_Success() {
// Mock 依赖方法
when(resourceMapper.findById(1L)).thenReturn(testResource);
when(categoryMapper.findById(1L)).thenReturn(testCategory);
when(resourceMapper.update(any(Resource.class))).thenReturn(1);
// 执行测试
Result<?> result = resourceService.updateResource(1L, 1L, createResourceDTO);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("资源更新成功", result.getMessage());
// 验证方法调用
verify(resourceMapper).update(any(Resource.class));
}
@Test
void testUpdateResource_Unauthorized() {
// Mock 其他用户的资源
testResource.setUserId(2L);
when(resourceMapper.findById(1L)).thenReturn(testResource);
// 执行测试
Result<?> result = resourceService.updateResource(1L, 1L, createResourceDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(403, result.getCode());
assertEquals("无权限修改此资源", result.getMessage());
}
@Test
void testDeleteResource_Success() {
// Mock 依赖方法
when(resourceMapper.findById(1L)).thenReturn(testResource);
when(resourceMapper.delete(1L)).thenReturn(1);
// 执行测试
Result<?> result = resourceService.deleteResource(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("资源删除成功", result.getMessage());
// 验证方法调用
verify(resourceMapper).delete(1L);
}
@Test
void testDownloadResource_Success() {
// Mock 依赖方法
when(resourceMapper.findById(1L)).thenReturn(testResource);
// 执行测试
Result<?> result = resourceService.downloadResource(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
// 验证下载量增加
verify(resourceMapper).updateDownloadCount(1L);
}
@Test
void testLikeResource_Success() {
// Mock 依赖方法
when(resourceMapper.findById(1L)).thenReturn(testResource);
when(resourceMapper.isLikedByUser(1L, 1L)).thenReturn(false);
when(resourceMapper.insertLike(1L, 1L)).thenReturn(1);
// 执行测试
Result<?> result = resourceService.likeResource(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("点赞成功", result.getMessage());
// 验证方法调用
verify(resourceMapper).insertLike(1L, 1L);
verify(resourceMapper).updateLikeCount(1L, 1);
}
@Test
void testUnlikeResource_Success() {
// Mock 已点赞状态
when(resourceMapper.findById(1L)).thenReturn(testResource);
when(resourceMapper.isLikedByUser(1L, 1L)).thenReturn(true);
when(resourceMapper.deleteLike(1L, 1L)).thenReturn(1);
// 执行测试
Result<?> result = resourceService.likeResource(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("取消点赞成功", result.getMessage());
// 验证方法调用
verify(resourceMapper).deleteLike(1L, 1L);
verify(resourceMapper).updateLikeCount(1L, -1);
}
@Test
void testGetUserResources_Success() {
// Mock 用户资源列表
List<Resource> userResources = Arrays.asList(testResource);
when(resourceMapper.findByUserId(eq(1L), anyInt(), anyInt())).thenReturn(userResources);
when(resourceMapper.countByUserId(1L)).thenReturn(1);
// 执行测试
Result<?> result = resourceService.getUserResources(1L, 1, 10);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
// 验证方法调用
verify(resourceMapper).findByUserId(eq(1L), anyInt(), anyInt());
verify(resourceMapper).countByUserId(1L);
}
}

@ -1,370 +0,0 @@
package com.unilife.service;
import com.unilife.common.result.Result;
import com.unilife.mapper.ScheduleMapper;
import com.unilife.mapper.UserMapper;
import com.unilife.model.dto.CreateScheduleDTO;
import com.unilife.model.entity.Schedule;
import com.unilife.model.entity.User;
import com.unilife.service.impl.ScheduleServiceImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@SpringBootTest
class ScheduleServiceTest {
@Mock
private ScheduleMapper scheduleMapper;
@Mock
private UserMapper userMapper;
@InjectMocks
private ScheduleServiceImpl scheduleService;
private User testUser;
private Schedule testSchedule;
private CreateScheduleDTO createScheduleDTO;
@BeforeEach
void setUp() {
// 初始化测试数据
testUser = new User();
testUser.setId(1L);
testUser.setNickname("测试用户");
testUser.setAvatar("avatar.jpg");
testSchedule = new Schedule();
testSchedule.setId(1L);
testSchedule.setTitle("测试课程");
testSchedule.setDescription("测试课程描述");
testSchedule.setStartTime(LocalDateTime.of(2024, 1, 15, 9, 0));
testSchedule.setEndTime(LocalDateTime.of(2024, 1, 15, 10, 30));
testSchedule.setLocation("教学楼A101");
testSchedule.setType("COURSE");
testSchedule.setRepeatType("WEEKLY");
testSchedule.setRepeatEnd(LocalDateTime.of(2024, 6, 15, 10, 30));
testSchedule.setUserId(1L);
testSchedule.setCreatedAt(LocalDateTime.now());
testSchedule.setUpdatedAt(LocalDateTime.now());
createScheduleDTO = new CreateScheduleDTO();
createScheduleDTO.setTitle("新课程");
createScheduleDTO.setDescription("新课程描述");
createScheduleDTO.setStartTime(LocalDateTime.of(2024, 1, 16, 14, 0));
createScheduleDTO.setEndTime(LocalDateTime.of(2024, 1, 16, 15, 30));
createScheduleDTO.setLocation("教学楼B201");
createScheduleDTO.setType("COURSE");
createScheduleDTO.setRepeatType("WEEKLY");
createScheduleDTO.setRepeatEnd(LocalDateTime.of(2024, 6, 16, 15, 30));
}
@Test
void testCreateSchedule_Success() {
// Mock 依赖方法
when(userMapper.findById(1L)).thenReturn(testUser);
when(scheduleMapper.findConflictingSchedules(eq(1L), any(), any(), any())).thenReturn(Arrays.asList());
when(scheduleMapper.insert(any(Schedule.class))).thenReturn(1);
// 执行测试
Result<?> result = scheduleService.createSchedule(1L, createScheduleDTO);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("日程创建成功", result.getMessage());
// 验证方法调用
verify(userMapper).findById(1L);
verify(scheduleMapper).findConflictingSchedules(eq(1L), any(), any(), any());
verify(scheduleMapper).insert(any(Schedule.class));
}
@Test
void testCreateSchedule_UserNotFound() {
// Mock 用户不存在
when(userMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = scheduleService.createSchedule(1L, createScheduleDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("用户不存在", result.getMessage());
// 验证不会尝试创建日程
verify(scheduleMapper, never()).insert(any(Schedule.class));
}
@Test
void testCreateSchedule_TimeConflict() {
// Mock 时间冲突
Schedule conflictingSchedule = new Schedule();
conflictingSchedule.setId(2L);
conflictingSchedule.setTitle("冲突课程");
conflictingSchedule.setStartTime(LocalDateTime.of(2024, 1, 16, 14, 30));
conflictingSchedule.setEndTime(LocalDateTime.of(2024, 1, 16, 16, 0));
when(userMapper.findById(1L)).thenReturn(testUser);
when(scheduleMapper.findConflictingSchedules(eq(1L), any(), any(), any()))
.thenReturn(Arrays.asList(conflictingSchedule));
// 执行测试
Result<?> result = scheduleService.createSchedule(1L, createScheduleDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertTrue(result.getMessage().contains("时间冲突"));
}
@Test
void testCreateSchedule_InvalidTimeRange() {
// 测试结束时间早于开始时间
createScheduleDTO.setStartTime(LocalDateTime.of(2024, 1, 16, 16, 0));
createScheduleDTO.setEndTime(LocalDateTime.of(2024, 1, 16, 14, 0));
// 执行测试
Result<?> result = scheduleService.createSchedule(1L, createScheduleDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertEquals("结束时间不能早于开始时间", result.getMessage());
}
@Test
void testGetScheduleDetail_Success() {
// Mock 依赖方法
when(scheduleMapper.findById(1L)).thenReturn(testSchedule);
// 执行测试
Result<?> result = scheduleService.getScheduleDetail(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
}
@Test
void testGetScheduleDetail_NotFound() {
// Mock 日程不存在
when(scheduleMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = scheduleService.getScheduleDetail(1L, 1L);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("日程不存在", result.getMessage());
}
@Test
void testGetScheduleDetail_Unauthorized() {
// Mock 其他用户的日程
testSchedule.setUserId(2L);
when(scheduleMapper.findById(1L)).thenReturn(testSchedule);
// 执行测试
Result<?> result = scheduleService.getScheduleDetail(1L, 1L);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(403, result.getCode());
assertEquals("无权限查看此日程", result.getMessage());
}
@Test
void testGetScheduleList_Success() {
// Mock 日程列表
List<Schedule> schedules = Arrays.asList(testSchedule);
when(scheduleMapper.findByUserId(1L)).thenReturn(schedules);
// 执行测试
Result<?> result = scheduleService.getScheduleList(1L);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
// 验证方法调用
verify(scheduleMapper).findByUserId(1L);
}
@Test
void testGetScheduleListByTimeRange_Success() {
LocalDateTime startTime = LocalDateTime.of(2024, 1, 1, 0, 0);
LocalDateTime endTime = LocalDateTime.of(2024, 1, 31, 23, 59);
// Mock 时间范围内的日程列表
List<Schedule> schedules = Arrays.asList(testSchedule);
when(scheduleMapper.findByUserIdAndTimeRange(1L, startTime, endTime)).thenReturn(schedules);
// 执行测试
Result<?> result = scheduleService.getScheduleListByTimeRange(1L, startTime, endTime);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
// 验证方法调用
verify(scheduleMapper).findByUserIdAndTimeRange(1L, startTime, endTime);
}
@Test
void testUpdateSchedule_Success() {
// Mock 依赖方法
when(scheduleMapper.findById(1L)).thenReturn(testSchedule);
when(scheduleMapper.findConflictingSchedules(eq(1L), any(), any(), eq(1L))).thenReturn(Arrays.asList());
when(scheduleMapper.update(any(Schedule.class))).thenReturn(1);
// 执行测试
Result<?> result = scheduleService.updateSchedule(1L, 1L, createScheduleDTO);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("日程更新成功", result.getMessage());
// 验证方法调用
verify(scheduleMapper).update(any(Schedule.class));
}
@Test
void testUpdateSchedule_Unauthorized() {
// Mock 其他用户的日程
testSchedule.setUserId(2L);
when(scheduleMapper.findById(1L)).thenReturn(testSchedule);
// 执行测试
Result<?> result = scheduleService.updateSchedule(1L, 1L, createScheduleDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(403, result.getCode());
assertEquals("无权限修改此日程", result.getMessage());
}
@Test
void testDeleteSchedule_Success() {
// Mock 依赖方法
when(scheduleMapper.findById(1L)).thenReturn(testSchedule);
when(scheduleMapper.delete(1L)).thenReturn(1);
// 执行测试
Result<?> result = scheduleService.deleteSchedule(1L, 1L);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("日程删除成功", result.getMessage());
// 验证方法调用
verify(scheduleMapper).delete(1L);
}
@Test
void testCheckScheduleConflict_NoConflict() {
LocalDateTime startTime = LocalDateTime.of(2024, 1, 16, 14, 0);
LocalDateTime endTime = LocalDateTime.of(2024, 1, 16, 15, 30);
// Mock 无冲突
when(scheduleMapper.findConflictingSchedules(eq(1L), eq(startTime), eq(endTime), any()))
.thenReturn(Arrays.asList());
// 执行测试
Result<?> result = scheduleService.checkScheduleConflict(1L, startTime, endTime, null);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("无时间冲突", result.getMessage());
}
@Test
void testCheckScheduleConflict_HasConflict() {
LocalDateTime startTime = LocalDateTime.of(2024, 1, 16, 14, 0);
LocalDateTime endTime = LocalDateTime.of(2024, 1, 16, 15, 30);
// Mock 有冲突
when(scheduleMapper.findConflictingSchedules(eq(1L), eq(startTime), eq(endTime), any()))
.thenReturn(Arrays.asList(testSchedule));
// 执行测试
Result<?> result = scheduleService.checkScheduleConflict(1L, startTime, endTime, null);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertTrue(result.getMessage().contains("时间冲突"));
}
@Test
void testProcessScheduleReminders_Success() {
// Mock 需要提醒的日程
List<Schedule> upcomingSchedules = Arrays.asList(testSchedule);
when(scheduleMapper.findUpcomingSchedules(any())).thenReturn(upcomingSchedules);
// 执行测试
Result<?> result = scheduleService.processScheduleReminders();
// 验证结果
assertTrue(result.isSuccess());
assertEquals("提醒处理完成", result.getMessage());
// 验证方法调用
verify(scheduleMapper).findUpcomingSchedules(any());
}
@Test
void testCreateSchedule_WeeklyRepeat() {
// 测试周重复日程
createScheduleDTO.setRepeatType("WEEKLY");
createScheduleDTO.setRepeatEnd(LocalDateTime.of(2024, 3, 16, 15, 30));
when(userMapper.findById(1L)).thenReturn(testUser);
when(scheduleMapper.findConflictingSchedules(eq(1L), any(), any(), any())).thenReturn(Arrays.asList());
when(scheduleMapper.insert(any(Schedule.class))).thenReturn(1);
// 执行测试
Result<?> result = scheduleService.createSchedule(1L, createScheduleDTO);
// 验证结果
assertTrue(result.isSuccess());
// 验证会创建多个重复的日程实例
verify(scheduleMapper, atLeast(1)).insert(any(Schedule.class));
}
@Test
void testCreateSchedule_DailyRepeat() {
// 测试日重复日程
createScheduleDTO.setRepeatType("DAILY");
createScheduleDTO.setRepeatEnd(LocalDateTime.of(2024, 1, 20, 15, 30));
when(userMapper.findById(1L)).thenReturn(testUser);
when(scheduleMapper.findConflictingSchedules(eq(1L), any(), any(), any())).thenReturn(Arrays.asList());
when(scheduleMapper.insert(any(Schedule.class))).thenReturn(1);
// 执行测试
Result<?> result = scheduleService.createSchedule(1L, createScheduleDTO);
// 验证结果
assertTrue(result.isSuccess());
// 验证会创建多个重复的日程实例
verify(scheduleMapper, atLeast(1)).insert(any(Schedule.class));
}
}

@ -1,438 +0,0 @@
package com.unilife.service;
import com.unilife.common.result.Result;
import com.unilife.mapper.UserMapper;
import com.unilife.model.dto.CreateUserDTO;
import com.unilife.model.dto.UpdateUserDTO;
import com.unilife.model.dto.LoginDTO;
import com.unilife.model.entity.User;
import com.unilife.service.impl.UserServiceImpl;
import com.unilife.utils.JwtUtil;
import com.unilife.utils.PasswordUtil;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@SpringBootTest
class UserServiceTest {
@Mock
private UserMapper userMapper;
@Mock
private StringRedisTemplate redisTemplate;
@Mock
private JavaMailSender mailSender;
@InjectMocks
private UserServiceImpl userService;
private User testUser;
private CreateUserDTO createUserDTO;
private UpdateUserDTO updateUserDTO;
private LoginDTO loginDTO;
@BeforeEach
void setUp() {
// 初始化测试数据
testUser = new User();
testUser.setId(1L);
testUser.setUsername("testuser");
testUser.setEmail("test@example.com");
testUser.setNickname("测试用户");
testUser.setPassword("$2a$10$encrypted_password"); // 模拟加密后的密码
testUser.setAvatar("avatar.jpg");
testUser.setStatus(1);
testUser.setCreatedAt(LocalDateTime.now());
testUser.setUpdatedAt(LocalDateTime.now());
createUserDTO = new CreateUserDTO();
createUserDTO.setUsername("newuser");
createUserDTO.setEmail("newuser@example.com");
createUserDTO.setNickname("新用户");
createUserDTO.setPassword("password123");
updateUserDTO = new UpdateUserDTO();
updateUserDTO.setNickname("更新后的昵称");
updateUserDTO.setAvatar("new_avatar.jpg");
loginDTO = new LoginDTO();
loginDTO.setUsername("testuser");
loginDTO.setPassword("password123");
}
@Test
void testRegister_Success() {
// Mock 依赖方法
when(userMapper.findByUsername("newuser")).thenReturn(null);
when(userMapper.findByEmail("newuser@example.com")).thenReturn(null);
when(userMapper.insert(any(User.class))).thenReturn(1);
try (MockedStatic<PasswordUtil> passwordUtil = mockStatic(PasswordUtil.class)) {
passwordUtil.when(() -> PasswordUtil.encode("password123"))
.thenReturn("$2a$10$encrypted_password");
// 执行测试
Result<?> result = userService.register(createUserDTO);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("注册成功", result.getMessage());
// 验证方法调用
verify(userMapper).findByUsername("newuser");
verify(userMapper).findByEmail("newuser@example.com");
verify(userMapper).insert(any(User.class));
}
}
@Test
void testRegister_UsernameExists() {
// Mock 用户名已存在
when(userMapper.findByUsername("newuser")).thenReturn(testUser);
// 执行测试
Result<?> result = userService.register(createUserDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertEquals("用户名已存在", result.getMessage());
// 验证不会尝试插入用户
verify(userMapper, never()).insert(any(User.class));
}
@Test
void testRegister_EmailExists() {
// Mock 邮箱已存在
when(userMapper.findByUsername("newuser")).thenReturn(null);
when(userMapper.findByEmail("newuser@example.com")).thenReturn(testUser);
// 执行测试
Result<?> result = userService.register(createUserDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertEquals("邮箱已存在", result.getMessage());
}
@Test
void testLogin_Success() {
// Mock 依赖方法
when(userMapper.findByUsername("testuser")).thenReturn(testUser);
try (MockedStatic<PasswordUtil> passwordUtil = mockStatic(PasswordUtil.class);
MockedStatic<JwtUtil> jwtUtil = mockStatic(JwtUtil.class)) {
passwordUtil.when(() -> PasswordUtil.matches("password123", "$2a$10$encrypted_password"))
.thenReturn(true);
jwtUtil.when(() -> JwtUtil.generateToken(1L))
.thenReturn("mock_jwt_token");
// 执行测试
Result<?> result = userService.login(loginDTO);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("登录成功", result.getMessage());
assertNotNull(result.getData());
// 验证方法调用
verify(userMapper).findByUsername("testuser");
verify(userMapper).updateLastLoginTime(1L);
}
}
@Test
void testLogin_UserNotFound() {
// Mock 用户不存在
when(userMapper.findByUsername("testuser")).thenReturn(null);
// 执行测试
Result<?> result = userService.login(loginDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(401, result.getCode());
assertEquals("用户名或密码错误", result.getMessage());
}
@Test
void testLogin_PasswordIncorrect() {
// Mock 密码错误
when(userMapper.findByUsername("testuser")).thenReturn(testUser);
try (MockedStatic<PasswordUtil> passwordUtil = mockStatic(PasswordUtil.class)) {
passwordUtil.when(() -> PasswordUtil.matches("password123", "$2a$10$encrypted_password"))
.thenReturn(false);
// 执行测试
Result<?> result = userService.login(loginDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(401, result.getCode());
assertEquals("用户名或密码错误", result.getMessage());
}
}
@Test
void testLogin_UserDisabled() {
// Mock 用户被禁用
testUser.setStatus(0);
when(userMapper.findByUsername("testuser")).thenReturn(testUser);
try (MockedStatic<PasswordUtil> passwordUtil = mockStatic(PasswordUtil.class)) {
passwordUtil.when(() -> PasswordUtil.matches("password123", "$2a$10$encrypted_password"))
.thenReturn(true);
// 执行测试
Result<?> result = userService.login(loginDTO);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(403, result.getCode());
assertEquals("账户已被禁用", result.getMessage());
}
}
@Test
void testGetUserInfo_Success() {
// Mock 依赖方法
when(userMapper.findById(1L)).thenReturn(testUser);
// 执行测试
Result<?> result = userService.getUserInfo(1L);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
// 验证方法调用
verify(userMapper).findById(1L);
}
@Test
void testGetUserInfo_UserNotFound() {
// Mock 用户不存在
when(userMapper.findById(1L)).thenReturn(null);
// 执行测试
Result<?> result = userService.getUserInfo(1L);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(404, result.getCode());
assertEquals("用户不存在", result.getMessage());
}
@Test
void testUpdateUserInfo_Success() {
// Mock 依赖方法
when(userMapper.findById(1L)).thenReturn(testUser);
when(userMapper.update(any(User.class))).thenReturn(1);
// 执行测试
Result<?> result = userService.updateUserInfo(1L, updateUserDTO);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("用户信息更新成功", result.getMessage());
// 验证方法调用
verify(userMapper).update(any(User.class));
}
@Test
void testSendEmailVerificationCode_Success() {
String email = "test@example.com";
String verificationCode = "123456";
// Mock Redis操作
when(redisTemplate.opsForValue()).thenReturn(mock(org.springframework.data.redis.core.ValueOperations.class));
// 执行测试
Result<?> result = userService.sendEmailVerificationCode(email);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("验证码发送成功", result.getMessage());
// 验证邮件发送
verify(mailSender).send(any(SimpleMailMessage.class));
}
@Test
void testVerifyEmailCode_Success() {
String email = "test@example.com";
String code = "123456";
// Mock Redis操作
when(redisTemplate.opsForValue()).thenReturn(mock(org.springframework.data.redis.core.ValueOperations.class));
when(redisTemplate.opsForValue().get("email_code:" + email)).thenReturn(code);
// 执行测试
Result<?> result = userService.verifyEmailCode(email, code);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("验证码验证成功", result.getMessage());
// 验证删除验证码
verify(redisTemplate).delete("email_code:" + email);
}
@Test
void testVerifyEmailCode_CodeExpired() {
String email = "test@example.com";
String code = "123456";
// Mock 验证码不存在(已过期)
when(redisTemplate.opsForValue()).thenReturn(mock(org.springframework.data.redis.core.ValueOperations.class));
when(redisTemplate.opsForValue().get("email_code:" + email)).thenReturn(null);
// 执行测试
Result<?> result = userService.verifyEmailCode(email, code);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertEquals("验证码已过期", result.getMessage());
}
@Test
void testVerifyEmailCode_CodeIncorrect() {
String email = "test@example.com";
String code = "123456";
String wrongCode = "654321";
// Mock 验证码错误
when(redisTemplate.opsForValue()).thenReturn(mock(org.springframework.data.redis.core.ValueOperations.class));
when(redisTemplate.opsForValue().get("email_code:" + email)).thenReturn(wrongCode);
// 执行测试
Result<?> result = userService.verifyEmailCode(email, code);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertEquals("验证码错误", result.getMessage());
}
@Test
void testResetPassword_Success() {
String email = "test@example.com";
String newPassword = "newpassword123";
// Mock 依赖方法
when(userMapper.findByEmail(email)).thenReturn(testUser);
when(userMapper.updatePassword(eq(1L), anyString())).thenReturn(1);
try (MockedStatic<PasswordUtil> passwordUtil = mockStatic(PasswordUtil.class)) {
passwordUtil.when(() -> PasswordUtil.encode(newPassword))
.thenReturn("$2a$10$new_encrypted_password");
// 执行测试
Result<?> result = userService.resetPassword(email, newPassword);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("密码重置成功", result.getMessage());
// 验证方法调用
verify(userMapper).updatePassword(eq(1L), eq("$2a$10$new_encrypted_password"));
}
}
@Test
void testGetUserList_Success() {
// Mock 用户列表
List<User> users = Arrays.asList(testUser);
when(userMapper.findByConditions(any(), any(), anyInt(), anyInt())).thenReturn(users);
when(userMapper.countByConditions(any(), any())).thenReturn(1);
// 执行测试
Result<?> result = userService.getUserList("测试", 1, 1, 10);
// 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getData());
// 验证方法调用
verify(userMapper).findByConditions(any(), any(), anyInt(), anyInt());
verify(userMapper).countByConditions(any(), any());
}
@Test
void testChangePassword_Success() {
String oldPassword = "oldpassword";
String newPassword = "newpassword123";
// Mock 依赖方法
when(userMapper.findById(1L)).thenReturn(testUser);
when(userMapper.updatePassword(eq(1L), anyString())).thenReturn(1);
try (MockedStatic<PasswordUtil> passwordUtil = mockStatic(PasswordUtil.class)) {
passwordUtil.when(() -> PasswordUtil.matches(oldPassword, "$2a$10$encrypted_password"))
.thenReturn(true);
passwordUtil.when(() -> PasswordUtil.encode(newPassword))
.thenReturn("$2a$10$new_encrypted_password");
// 执行测试
Result<?> result = userService.changePassword(1L, oldPassword, newPassword);
// 验证结果
assertTrue(result.isSuccess());
assertEquals("密码修改成功", result.getMessage());
// 验证方法调用
verify(userMapper).updatePassword(eq(1L), eq("$2a$10$new_encrypted_password"));
}
}
@Test
void testChangePassword_OldPasswordIncorrect() {
String oldPassword = "wrongpassword";
String newPassword = "newpassword123";
// Mock 依赖方法
when(userMapper.findById(1L)).thenReturn(testUser);
try (MockedStatic<PasswordUtil> passwordUtil = mockStatic(PasswordUtil.class)) {
passwordUtil.when(() -> PasswordUtil.matches(oldPassword, "$2a$10$encrypted_password"))
.thenReturn(false);
// 执行测试
Result<?> result = userService.changePassword(1L, oldPassword, newPassword);
// 验证结果
assertFalse(result.isSuccess());
assertEquals(400, result.getCode());
assertEquals("原密码错误", result.getMessage());
// 验证不会更新密码
verify(userMapper, never()).updatePassword(anyLong(), anyString());
}
}
}

@ -0,0 +1,63 @@
package com.unilife.utils;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@TestPropertySource(properties = {
"jwt.secret=qwertyuiopasdfghjklzxcvbnm",
"jwt.expiration=2" // 2秒过期用于测试
})
public class JwtUtilTest {
@Autowired
private JwtUtil jwtUtil;
@Test
public void testTokenExpiration() throws InterruptedException {
Long userId = 123L;
// 生成token
String token = jwtUtil.generateToken(userId);
assertNotNull(token);
// 立即验证,应该有效
assertTrue(jwtUtil.verifyToken(token));
assertEquals(userId, jwtUtil.getUserIdFromToken(token));
// 等待3秒token应该过期
Thread.sleep(3000);
// 验证token已过期
assertFalse(jwtUtil.verifyToken(token));
assertNull(jwtUtil.getUserIdFromToken(token));
}
@Test
public void testValidToken() {
Long userId = 456L;
// 生成token
String token = jwtUtil.generateToken(userId);
assertNotNull(token);
// 验证token有效
assertTrue(jwtUtil.verifyToken(token));
assertEquals(userId, jwtUtil.getUserIdFromToken(token));
}
@Test
public void testInvalidToken() {
// 测试无效token
assertFalse(jwtUtil.verifyToken("invalid.token.here"));
assertNull(jwtUtil.getUserIdFromToken("invalid.token.here"));
// 测试空token
assertFalse(jwtUtil.verifyToken(""));
assertFalse(jwtUtil.verifyToken(null));
}
}

@ -1,117 +0,0 @@
package com.unilife.utils;
import com.unilife.model.dto.*;
import com.unilife.model.entity.*;
import java.time.LocalDateTime;
/**
*
* DTO
*/
public class TestDataBuilder {
/**
*
*/
public static User buildTestUser() {
User user = new User();
user.setId(1L);
user.setUsername("testuser");
user.setEmail("test@example.com");
user.setNickname("测试用户");
user.setPassword("$2a$10$encrypted_password");
user.setAvatar("avatar.jpg");
user.setStatus(1);
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return user;
}
/**
*
*/
public static Category buildTestCategory() {
Category category = new Category();
category.setId(1L);
category.setName("测试分类");
category.setDescription("测试分类描述");
category.setIcon("test-icon");
category.setSort(1);
category.setStatus(1);
category.setCreatedAt(LocalDateTime.now());
category.setUpdatedAt(LocalDateTime.now());
return category;
}
/**
*
*/
public static Post buildTestPost() {
Post post = new Post();
post.setId(1L);
post.setTitle("测试帖子");
post.setContent("测试帖子内容");
post.setUserId(1L);
post.setCategoryId(1L);
post.setLikeCount(0);
post.setViewCount(0);
post.setCommentCount(0);
post.setCreatedAt(LocalDateTime.now());
post.setUpdatedAt(LocalDateTime.now());
return post;
}
/**
*
*/
public static Resource buildTestResource() {
Resource resource = new Resource();
resource.setId(1L);
resource.setTitle("测试资源");
resource.setDescription("测试资源描述");
resource.setFileName("test.pdf");
resource.setFileUrl("http://example.com/test.pdf");
resource.setFileSize(1024L);
resource.setFileType("pdf");
resource.setUserId(1L);
resource.setCategoryId(1L);
resource.setDownloadCount(0);
resource.setLikeCount(0);
resource.setCreatedAt(LocalDateTime.now());
resource.setUpdatedAt(LocalDateTime.now());
return resource;
}
/**
* DTO
*/
public static CreatePostDTO buildCreatePostDTO() {
CreatePostDTO dto = new CreatePostDTO();
dto.setTitle("新帖子标题");
dto.setContent("新帖子内容");
dto.setCategoryId(1L);
return dto;
}
/**
* DTO
*/
public static CreateUserDTO buildCreateUserDTO() {
CreateUserDTO dto = new CreateUserDTO();
dto.setUsername("newuser");
dto.setEmail("newuser@example.com");
dto.setNickname("新用户");
dto.setPassword("password123");
return dto;
}
/**
* ID
*/
public static User buildTestUser(Long id) {
User user = buildTestUser();
user.setId(id);
return user;
}
}
Loading…
Cancel
Save