后台管理

czq
2991692032 3 weeks ago
parent 0dd026279b
commit 6921e89a77

@ -2,8 +2,6 @@
<project version="4"> <project version="4">
<component name="SqlDialectMappings"> <component name="SqlDialectMappings">
<file url="file://$USER_HOME$/AppData/Local/Temp/mybatisCodeHelper_unilife_CategoryMapper.sql" dialect="MySQL" /> <file url="file://$USER_HOME$/AppData/Local/Temp/mybatisCodeHelper_unilife_CategoryMapper.sql" dialect="MySQL" />
<file url="file://$PROJECT_DIR$/unilife-server/src/main/resources/db/init.sql" dialect="MySQL" />
<file url="file://$PROJECT_DIR$/unilife-server/src/main/resources/db/rebuild-database.sql" dialect="MySQL" /> <file url="file://$PROJECT_DIR$/unilife-server/src/main/resources/db/rebuild-database.sql" dialect="MySQL" />
<file url="file://$PROJECT_DIR$/unilife-server/src/main/resources/db/test-data.sql" dialect="MySQL" />
</component> </component>
</project> </project>

@ -50,7 +50,8 @@
- [5. 学习资源共享模块](#5-学习资源共享模块) - [5. 学习资源共享模块](#5-学习资源共享模块)
- [6. 课程表与日程管理模块](#6-课程表与日程管理模块) - [6. 课程表与日程管理模块](#6-课程表与日程管理模块)
- [7. AI辅助学习模块](#7-AI辅助学习模块) - [7. AI辅助学习模块](#7-AI辅助学习模块)
- [8. 待实现模块](#8-待实现模块) - [8. 管理员权限管理模块](#8-管理员权限管理模块)
- [9. 待实现模块](#9-待实现模块)
## 1. 基础信息 ## 1. 基础信息
@ -1693,8 +1694,6 @@ sessionId=session_1234567890
学好Python编程需要循序渐进地学习首先掌握基础语法然后通过实际项目练习... 学好Python编程需要循序渐进地学习首先掌握基础语法然后通过实际项目练习...
``` ```
#### 7.1.2 获取聊天会话列表 #### 7.1.2 获取聊天会话列表
- **URL**: `/ai/sessions` - **URL**: `/ai/sessions`
- **方法**: GET - **方法**: GET
@ -1951,7 +1950,223 @@ public class AiServiceImpl implements AiService {
} }
``` ```
## 8. 待实现模块 ## 8. 管理员权限管理模块
### 8.1 权限说明
系统采用基于角色的权限控制RBAC用户角色分为
- **0**: 普通用户
- **1**: 版主
- **2**: 管理员
管理员拥有系统的最高权限,可以访问后台管理界面,对所有数据进行管理。
### 8.2 权限验证
所有管理员接口都需要通过以下验证:
1. **JWT Token验证**:确保用户已登录
2. **角色权限验证**确保用户角色为管理员role = 2
3. **账号状态验证**:确保账号未被禁用
### 8.3 管理员登录
管理员使用普通登录接口,系统会根据用户角色自动跳转:
- 管理员登录后跳转到 `/admin` 后台管理页面
- 普通用户登录后跳转到 `/forum` 论坛页面
**默认管理员账号**
- 用户名:`admin` 密码:`123456`
- 用户名:`superadmin` 密码:`admin123`
### 8.4 系统统计
#### 8.4.1 获取系统统计数据
- **URL**: `/admin/stats`
- **方法**: GET
- **描述**: 获取系统整体数据统计
- **认证**: 需要管理员权限
响应结果:
```json
{
"code": 200,
"message": "success",
"data": {
"totalUsers": 1250,
"activeUsers": 856,
"newUsersToday": 23,
"totalPosts": 3420,
"newPostsToday": 45,
"totalComments": 8760,
"newCommentsToday": 128,
"totalResources": 567,
"newResourcesToday": 8,
"totalCategories": 8
}
}
```
### 8.5 用户管理
#### 8.5.1 获取用户列表
- **URL**: `/admin/users`
- **方法**: GET
- **描述**: 获取用户列表,支持搜索和筛选
- **认证**: 需要管理员权限
请求参数:
- **page** (query, 可选): 页码默认为1
- **size** (query, 可选): 每页大小默认为10
- **keyword** (query, 可选): 搜索关键词(用户名、昵称、邮箱)
- **role** (query, 可选): 角色筛选0-普通用户, 1-版主, 2-管理员)
- **status** (query, 可选): 状态筛选0-禁用, 1-启用)
响应结果:
```json
{
"code": 200,
"message": "success",
"data": {
"list": [
{
"id": 12345,
"username": "student123",
"nickname": "学生昵称",
"email": "student@school.edu",
"role": 0,
"status": 1,
"isVerified": 1,
"studentId": "20220101001",
"department": "计算机学院",
"major": "软件工程",
"grade": "2023级",
"points": 150,
"loginIp": "北京市",
"loginTime": "2023-05-01T12:00:00",
"createdAt": "2023-01-01T10:00:00"
}
],
"total": 1250,
"pages": 125
}
}
```
#### 8.5.2 更新用户状态
- **URL**: `/admin/users/{userId}/status`
- **方法**: PUT
- **描述**: 启用或禁用用户账号
- **认证**: 需要管理员权限
请求参数:
```json
{
"status": 0
}
```
#### 8.5.3 更新用户角色
- **URL**: `/admin/users/{userId}/role`
- **方法**: PUT
- **描述**: 修改用户角色权限
- **认证**: 需要管理员权限
请求参数:
```json
{
"role": 1
}
```
#### 8.5.4 删除用户
- **URL**: `/admin/users/{userId}`
- **方法**: DELETE
- **描述**: 删除用户及其相关数据(不能删除管理员)
- **认证**: 需要管理员权限
### 8.6 内容管理
#### 8.6.1 获取帖子列表
- **URL**: `/admin/posts`
- **方法**: GET
- **描述**: 获取帖子列表,支持搜索和筛选
- **认证**: 需要管理员权限
请求参数:
- **page** (query, 可选): 页码默认为1
- **size** (query, 可选): 每页大小默认为10
- **keyword** (query, 可选): 搜索关键词(标题、内容)
- **categoryId** (query, 可选): 分类筛选
- **status** (query, 可选): 状态筛选0-删除, 1-正常, 2-置顶)
#### 8.6.2 更新帖子状态
- **URL**: `/admin/posts/{postId}/status`
- **方法**: PUT
- **描述**: 修改帖子状态(置顶、隐藏等)
- **认证**: 需要管理员权限
#### 8.6.3 删除帖子
- **URL**: `/admin/posts/{postId}`
- **方法**: DELETE
- **描述**: 删除帖子及其相关数据
- **认证**: 需要管理员权限
### 8.7 分类管理
#### 8.7.1 获取分类列表
- **URL**: `/admin/categories`
- **方法**: GET
- **描述**: 获取所有分类列表
- **认证**: 需要管理员权限
#### 8.7.2 创建分类
- **URL**: `/admin/categories`
- **方法**: POST
- **描述**: 创建新分类
- **认证**: 需要管理员权限
请求参数:
```json
{
"name": "新分类",
"description": "分类描述",
"icon": "📚",
"sort": 10,
"status": 1
}
```
#### 8.7.3 更新分类
- **URL**: `/admin/categories/{categoryId}`
- **方法**: PUT
- **描述**: 更新分类信息
- **认证**: 需要管理员权限
#### 8.7.4 删除分类
- **URL**: `/admin/categories/{categoryId}`
- **方法**: DELETE
- **描述**: 删除分类(需确保分类下无内容)
- **认证**: 需要管理员权限
### 8.8 前端管理界面
管理员登录后可访问 `/admin` 路径的后台管理界面,包含:
1. **数据概览**:系统整体数据统计和图表
2. **用户管理**:用户列表、状态管理、角色分配
3. **帖子管理**:帖子审核、状态管理、批量操作
4. **评论管理**:评论审核、删除管理
5. **分类管理**:分类的增删改查
6. **资源管理**:资源文件的管理和审核
### 8.9 安全特性
1. **权限隔离**:管理员接口与普通接口完全隔离
2. **操作日志**:所有管理操作都会记录日志
3. **防护机制**:防止删除管理员账号等危险操作
4. **会话管理**:管理员会话有独立的安全策略
## 9. 待实现模块
以下模块尚未实现,将在后续开发中完成: 以下模块尚未实现,将在后续开发中完成:

@ -0,0 +1,120 @@
import request from '@/utils/request'
import type { ApiResponse } from '@/types'
export const adminApi = {
// 获取系统统计数据
getSystemStats(): Promise<ApiResponse> {
return request.get('/admin/stats')
},
// 获取用户列表
getUserList(params: {
page?: number
size?: number
keyword?: string
role?: number | null
status?: number
}): Promise<ApiResponse> {
return request.get('/admin/users', { params })
},
// 更新用户状态
updateUserStatus(userId: number, data: { status: number }): Promise<ApiResponse> {
return request.put(`/admin/users/${userId}/status`, data)
},
// 更新用户角色
updateUserRole(userId: number, data: { role: number }): Promise<ApiResponse> {
return request.put(`/admin/users/${userId}/role`, data)
},
// 删除用户
deleteUser(userId: number): Promise<ApiResponse> {
return request.delete(`/admin/users/${userId}`)
},
// 获取帖子列表
getPostList(params: {
page?: number
size?: number
keyword?: string
categoryId?: number
status?: number
}): Promise<ApiResponse> {
return request.get('/admin/posts', { params })
},
// 更新帖子状态
updatePostStatus(postId: number, data: { status: number }): Promise<ApiResponse> {
return request.put(`/admin/posts/${postId}/status`, data)
},
// 删除帖子
deletePost(postId: number): Promise<ApiResponse> {
return request.delete(`/admin/posts/${postId}`)
},
// 获取评论列表
getCommentList(params: {
page?: number
size?: number
keyword?: string
postId?: number
status?: number
}): Promise<ApiResponse> {
return request.get('/admin/comments', { params })
},
// 删除评论
deleteComment(commentId: number): Promise<ApiResponse> {
return request.delete(`/admin/comments/${commentId}`)
},
// 获取分类列表
getCategoryList(): Promise<ApiResponse> {
return request.get('/admin/categories')
},
// 创建分类
createCategory(data: {
name: string
description?: string
icon?: string
sort?: number
status?: number
}): Promise<ApiResponse> {
return request.post('/admin/categories', data)
},
// 更新分类
updateCategory(categoryId: number, data: {
name: string
description?: string
icon?: string
sort?: number
status?: number
}): Promise<ApiResponse> {
return request.put(`/admin/categories/${categoryId}`, data)
},
// 删除分类
deleteCategory(categoryId: number): Promise<ApiResponse> {
return request.delete(`/admin/categories/${categoryId}`)
},
// 获取资源列表
getResourceList(params: {
page?: number
size?: number
keyword?: string
categoryId?: number
status?: number
}): Promise<ApiResponse> {
return request.get('/admin/resources', { params })
},
// 删除资源
deleteResource(resourceId: number): Promise<ApiResponse> {
return request.delete(`/admin/resources/${resourceId}`)
}
}

@ -66,6 +66,12 @@ const router = createRouter({
name: 'profile', name: 'profile',
component: () => import('@/views/profile/ProfileView.vue'), component: () => import('@/views/profile/ProfileView.vue'),
meta: { requiresAuth: true } meta: { requiresAuth: true }
},
{
path: '/admin',
name: 'admin',
component: () => import('@/views/admin/AdminDashboard.vue'),
meta: { requiresAuth: true, requiresAdmin: true }
} }
// { // {
// path: '/courses', // path: '/courses',
@ -77,14 +83,37 @@ const router = createRouter({
}) })
// 路由守卫 // 路由守卫
router.beforeEach((to) => { router.beforeEach(async (to) => {
const userStore = useUserStore() const userStore = useUserStore()
// 如果需要认证但未登录,跳转到登录页
if (to.meta.requiresAuth && !userStore.isLoggedIn) { if (to.meta.requiresAuth && !userStore.isLoggedIn) {
return '/login' return '/login'
} }
// 如果已登录但用户信息为空,先获取用户信息
if (userStore.isLoggedIn && !userStore.user) {
try {
await userStore.fetchUserInfo()
} catch (error) {
// 如果获取用户信息失败可能token已过期清除登录状态
userStore.logout()
return '/login'
}
}
// 检查管理员权限
if (to.meta.requiresAdmin && userStore.user?.role !== 2) {
return '/forum'
}
// 只限制已登录用户访问登录页面,允许访问注册页面
if (to.name === 'login' && userStore.isLoggedIn) { if (to.name === 'login' && userStore.isLoggedIn) {
// 如果是管理员,跳转到管理后台
if (userStore.user?.role === 2) {
return '/admin'
}
// 普通用户跳转到论坛
return '/forum' return '/forum'
} }
}) })

@ -0,0 +1,40 @@
import axios from 'axios'
// 创建axios实例
const request = axios.create({
baseURL: 'http://localhost:8087',
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
// 请求拦截器
request.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
request.interceptors.response.use(
(response) => {
return response.data
},
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token')
window.location.href = '/login'
}
return Promise.reject(error)
}
)
export default request

@ -0,0 +1,453 @@
<template>
<div class="admin-dashboard">
<!-- 顶部导航 -->
<div class="admin-header">
<div class="header-left">
<h1 class="admin-title">UniLife 管理后台</h1>
</div>
<div class="header-right">
<span class="admin-user">{{ userStore.user?.nickname }}</span>
<el-button @click="logout" type="danger" size="small">退出登录</el-button>
</div>
</div>
<!-- 主要内容区域 -->
<div class="admin-content">
<!-- 侧边栏 -->
<div class="admin-sidebar">
<el-menu
:default-active="activeMenu"
class="admin-menu"
@select="handleMenuSelect"
>
<el-menu-item index="dashboard">
<el-icon><DataBoard /></el-icon>
<span>数据概览</span>
</el-menu-item>
<el-menu-item index="users">
<el-icon><User /></el-icon>
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="posts">
<el-icon><Document /></el-icon>
<span>帖子管理</span>
</el-menu-item>
<el-menu-item index="comments">
<el-icon><ChatDotRound /></el-icon>
<span>评论管理</span>
</el-menu-item>
<el-menu-item index="categories">
<el-icon><FolderOpened /></el-icon>
<span>分类管理</span>
</el-menu-item>
<el-menu-item index="resources">
<el-icon><Files /></el-icon>
<span>资源管理</span>
</el-menu-item>
</el-menu>
</div>
<!-- 主内容区 -->
<div class="admin-main">
<!-- 数据概览 -->
<div v-if="activeMenu === 'dashboard'" class="dashboard-content">
<h2>系统数据概览</h2>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon user-icon">
<el-icon><User /></el-icon>
</div>
<div class="stat-info">
<div class="stat-number">{{ stats.totalUsers || 0 }}</div>
<div class="stat-label">总用户数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon post-icon">
<el-icon><Document /></el-icon>
</div>
<div class="stat-info">
<div class="stat-number">{{ stats.totalPosts || 0 }}</div>
<div class="stat-label">总帖子数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon comment-icon">
<el-icon><ChatDotRound /></el-icon>
</div>
<div class="stat-info">
<div class="stat-number">{{ stats.totalComments || 0 }}</div>
<div class="stat-label">总评论数</div>
</div>
</div>
<div class="stat-card">
<div class="stat-icon resource-icon">
<el-icon><Files /></el-icon>
</div>
<div class="stat-info">
<div class="stat-number">{{ stats.totalResources || 0 }}</div>
<div class="stat-label">总资源数</div>
</div>
</div>
</div>
</div>
<!-- 用户管理 -->
<div v-if="activeMenu === 'users'" class="users-content">
<h2>用户管理</h2>
<div class="table-toolbar">
<el-input
v-model="userSearch"
placeholder="搜索用户..."
style="width: 300px"
@input="searchUsers"
/>
<el-select v-model="userRoleFilter" placeholder="角色筛选" @change="searchUsers">
<el-option label="全部" :value="null" />
<el-option label="普通用户" :value="0" />
<el-option label="版主" :value="1" />
<el-option label="管理员" :value="2" />
</el-select>
</div>
<el-table :data="users" style="width: 100%">
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="username" label="用户名" width="120" />
<el-table-column prop="nickname" label="昵称" width="120" />
<el-table-column prop="email" label="邮箱" width="200" />
<el-table-column prop="role" label="角色" width="100">
<template #default="scope">
<el-tag :type="getRoleType(scope.row.role)">
{{ getRoleText(scope.row.role) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">
{{ scope.row.status === 1 ? '正常' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="createdAt" label="注册时间" width="180" />
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button
size="small"
:type="scope.row.status === 1 ? 'warning' : 'success'"
@click="toggleUserStatus(scope.row)"
>
{{ scope.row.status === 1 ? '禁用' : '启用' }}
</el-button>
<el-button
size="small"
type="danger"
@click="deleteUser(scope.row)"
:disabled="scope.row.role === 2"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:current-page="userPage"
v-model:page-size="userPageSize"
:total="userTotal"
@current-change="loadUsers"
layout="total, prev, pager, next"
/>
</div>
<!-- 其他管理模块的占位符 -->
<div v-if="activeMenu === 'posts'" class="posts-content">
<h2>帖子管理</h2>
<p>帖子管理功能开发中...</p>
</div>
<div v-if="activeMenu === 'comments'" class="comments-content">
<h2>评论管理</h2>
<p>评论管理功能开发中...</p>
</div>
<div v-if="activeMenu === 'categories'" class="categories-content">
<h2>分类管理</h2>
<p>分类管理功能开发中...</p>
</div>
<div v-if="activeMenu === 'resources'" class="resources-content">
<h2>资源管理</h2>
<p>资源管理功能开发中...</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
DataBoard,
User,
Document,
ChatDotRound,
FolderOpened,
Files
} from '@element-plus/icons-vue'
import { useUserStore } from '@/stores/user'
import { adminApi } from '@/api/admin'
//
interface SystemStats {
totalUsers?: number
totalPosts?: number
totalComments?: number
totalResources?: number
}
const router = useRouter()
const userStore = useUserStore()
//
const activeMenu = ref('dashboard')
//
const stats = ref<SystemStats>({})
//
const users = ref([])
const userSearch = ref('')
const userRoleFilter = ref(null)
const userPage = ref(1)
const userPageSize = ref(10)
const userTotal = ref(0)
//
const handleMenuSelect = (index: string) => {
activeMenu.value = index
if (index === 'dashboard') {
loadStats()
} else if (index === 'users') {
loadUsers()
}
}
//
const loadStats = async () => {
try {
const response = await adminApi.getSystemStats()
if (response.code === 200) {
stats.value = response.data
}
} catch (error) {
ElMessage.error('加载统计数据失败')
}
}
//
const loadUsers = async () => {
try {
const response = await adminApi.getUserList({
page: userPage.value,
size: userPageSize.value,
keyword: userSearch.value || undefined,
role: userRoleFilter.value
})
if (response.code === 200) {
users.value = response.data.list
userTotal.value = response.data.total
}
} catch (error) {
ElMessage.error('加载用户列表失败')
}
}
//
const searchUsers = () => {
userPage.value = 1
loadUsers()
}
//
const toggleUserStatus = async (user: any) => {
try {
const newStatus = user.status === 1 ? 0 : 1
const response = await adminApi.updateUserStatus(user.id, { status: newStatus })
if (response.code === 200) {
user.status = newStatus
ElMessage.success('用户状态更新成功')
}
} catch (error) {
ElMessage.error('更新用户状态失败')
}
}
//
const deleteUser = async (user: any) => {
try {
await ElMessageBox.confirm('确定要删除该用户吗?此操作不可恢复!', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
const response = await adminApi.deleteUser(user.id)
if (response.code === 200) {
ElMessage.success('用户删除成功')
loadUsers()
}
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除用户失败')
}
}
}
//
const getRoleType = (role: number) => {
switch (role) {
case 0: return ''
case 1: return 'warning'
case 2: return 'danger'
default: return ''
}
}
//
const getRoleText = (role: number) => {
switch (role) {
case 0: return '普通用户'
case 1: return '版主'
case 2: return '管理员'
default: return '未知'
}
}
// 退
const logout = () => {
userStore.logout()
router.push('/login')
}
//
onMounted(() => {
loadStats()
})
</script>
<style scoped>
.admin-dashboard {
min-height: 100vh;
background-color: #f5f5f5;
}
.admin-header {
background: white;
padding: 0 20px;
height: 60px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.admin-title {
margin: 0;
color: #409EFF;
font-size: 20px;
}
.header-right {
display: flex;
align-items: center;
gap: 15px;
}
.admin-user {
color: #666;
}
.admin-content {
display: flex;
height: calc(100vh - 60px);
}
.admin-sidebar {
width: 200px;
background: white;
border-right: 1px solid #e6e6e6;
}
.admin-menu {
border: none;
height: 100%;
}
.admin-main {
flex: 1;
padding: 20px;
overflow-y: auto;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: flex;
align-items: center;
gap: 15px;
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: white;
}
.user-icon { background: #409EFF; }
.post-icon { background: #67C23A; }
.comment-icon { background: #E6A23C; }
.resource-icon { background: #F56C6C; }
.stat-number {
font-size: 24px;
font-weight: bold;
color: #333;
}
.stat-label {
color: #666;
font-size: 14px;
}
.table-toolbar {
display: flex;
gap: 15px;
margin-bottom: 20px;
}
.users-content,
.posts-content,
.comments-content,
.categories-content,
.resources-content {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>

@ -161,7 +161,15 @@ const handleLogin = async () => {
await userStore.userLogin(loginForm) await userStore.userLogin(loginForm)
ElMessage.success('登录成功!欢迎回来 🎉') ElMessage.success('登录成功!欢迎回来 🎉')
//
if (userStore.user?.role === 2) {
//
router.push('/admin')
} else {
//
router.push('/forum') router.push('/forum')
}
} catch (error: any) { } catch (error: any) {
ElMessage.error(error.message || '登录失败,请检查账号和密码') ElMessage.error(error.message || '登录失败,请检查账号和密码')
} finally { } finally {

@ -1,5 +1,6 @@
package com.unilife.config; package com.unilife.config;
import com.unilife.interceptor.AdminInterceptor;
import com.unilife.interceptor.JwtInterceptor; import com.unilife.interceptor.JwtInterceptor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -15,8 +16,17 @@ public class WebMvcConfig implements WebMvcConfigurer {
@Autowired @Autowired
private JwtInterceptor jwtInterceptor; private JwtInterceptor jwtInterceptor;
@Autowired
private AdminInterceptor adminInterceptor;
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
// 管理员权限拦截器 - 优先级最高
registry.addInterceptor(adminInterceptor)
.addPathPatterns("/admin/**")
.order(1);
// JWT拦截器
registry.addInterceptor(jwtInterceptor).addPathPatterns("/**") registry.addInterceptor(jwtInterceptor).addPathPatterns("/**")
.excludePathPatterns( .excludePathPatterns(
// 用户登录注册相关 // 用户登录注册相关
@ -28,6 +38,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
// 静态资源访问 // 静态资源访问
"/api/files/**", "/api/files/**",
// 管理员接口由AdminInterceptor处理
"/admin/**",
// Swagger文档相关 // Swagger文档相关
"/swagger-resources/**", "/swagger-resources/**",
"/v3/api-docs/**", "/v3/api-docs/**",
@ -35,7 +48,8 @@ public class WebMvcConfig implements WebMvcConfigurer {
"/webjars/**", "/webjars/**",
"/favicon.ico", "/favicon.ico",
"/knife4j/**" "/knife4j/**"
); )
.order(2);
} }
@Override @Override

@ -0,0 +1,140 @@
package com.unilife.controller;
import com.unilife.common.result.Result;
import com.unilife.service.AdminService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/admin")
@Tag(name = "管理员接口", description = "后台管理相关接口")
public class AdminController {
@Autowired
private AdminService adminService;
@Operation(summary = "获取系统统计数据")
@GetMapping("/stats")
public Result getSystemStats() {
return adminService.getSystemStats();
}
@Operation(summary = "获取用户列表")
@GetMapping("/users")
public Result getUserList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Integer role,
@RequestParam(required = false) Integer status) {
return adminService.getUserList(page, size, keyword, role, status);
}
@Operation(summary = "更新用户状态")
@PutMapping("/users/{userId}/status")
public Result updateUserStatus(@PathVariable Long userId, @RequestBody Map<String, Integer> request) {
Integer status = request.get("status");
return adminService.updateUserStatus(userId, status);
}
@Operation(summary = "更新用户角色")
@PutMapping("/users/{userId}/role")
public Result updateUserRole(@PathVariable Long userId, @RequestBody Map<String, Integer> request) {
Integer role = request.get("role");
return adminService.updateUserRole(userId, role);
}
@Operation(summary = "删除用户")
@DeleteMapping("/users/{userId}")
public Result deleteUser(@PathVariable Long userId) {
return adminService.deleteUser(userId);
}
@Operation(summary = "获取帖子列表")
@GetMapping("/posts")
public Result getPostList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long categoryId,
@RequestParam(required = false) Integer status) {
return adminService.getPostList(page, size, keyword, categoryId, status);
}
@Operation(summary = "更新帖子状态")
@PutMapping("/posts/{postId}/status")
public Result updatePostStatus(@PathVariable Long postId, @RequestBody Map<String, Integer> request) {
Integer status = request.get("status");
return adminService.updatePostStatus(postId, status);
}
@Operation(summary = "删除帖子")
@DeleteMapping("/posts/{postId}")
public Result deletePost(@PathVariable Long postId) {
return adminService.deletePost(postId);
}
@Operation(summary = "获取评论列表")
@GetMapping("/comments")
public Result getCommentList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long postId,
@RequestParam(required = false) Integer status) {
return adminService.getCommentList(page, size, keyword, postId, status);
}
@Operation(summary = "删除评论")
@DeleteMapping("/comments/{commentId}")
public Result deleteComment(@PathVariable Long commentId) {
return adminService.deleteComment(commentId);
}
@Operation(summary = "获取分类列表")
@GetMapping("/categories")
public Result getCategoryList() {
return adminService.getCategoryList();
}
@Operation(summary = "创建分类")
@PostMapping("/categories")
public Result createCategory(@RequestBody Map<String, Object> request) {
return adminService.createCategory(request);
}
@Operation(summary = "更新分类")
@PutMapping("/categories/{categoryId}")
public Result updateCategory(@PathVariable Long categoryId, @RequestBody Map<String, Object> request) {
return adminService.updateCategory(categoryId, request);
}
@Operation(summary = "删除分类")
@DeleteMapping("/categories/{categoryId}")
public Result deleteCategory(@PathVariable Long categoryId) {
return adminService.deleteCategory(categoryId);
}
@Operation(summary = "获取资源列表")
@GetMapping("/resources")
public Result getResourceList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String keyword,
@RequestParam(required = false) Long categoryId,
@RequestParam(required = false) Integer status) {
return adminService.getResourceList(page, size, keyword, categoryId, status);
}
@Operation(summary = "删除资源")
@DeleteMapping("/resources/{resourceId}")
public Result deleteResource(@PathVariable Long resourceId) {
return adminService.deleteResource(resourceId);
}
}

@ -0,0 +1,95 @@
package com.unilife.interceptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unilife.common.result.Result;
import com.unilife.mapper.UserMapper;
import com.unilife.model.entity.User;
import com.unilife.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Slf4j
@Component
public class AdminInterceptor implements HandlerInterceptor {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserMapper userMapper;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 预检请求直接放行
if ("OPTIONS".equals(request.getMethod())) {
return true;
}
try {
// 获取token
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
writeErrorResponse(response, 401, "未登录或token格式错误");
return false;
}
token = token.substring(7); // 移除 "Bearer " 前缀
// 验证token
if (!jwtUtil.verifyToken(token)) {
writeErrorResponse(response, 401, "token无效或已过期");
return false;
}
// 获取用户ID
Long userId = jwtUtil.getUserIdFromToken(token);
if (userId == null) {
writeErrorResponse(response, 401, "无法获取用户信息");
return false;
}
// 查询用户信息
User user = userMapper.getUserById(userId);
if (user == null) {
writeErrorResponse(response, 401, "用户不存在");
return false;
}
// 检查用户状态
if (user.getStatus() != 1) {
writeErrorResponse(response, 403, "账号已被禁用");
return false;
}
// 检查是否为管理员
if (user.getRole() != 2) {
writeErrorResponse(response, 403, "权限不足,需要管理员权限");
return false;
}
// 将用户信息存储到request中供后续使用
request.setAttribute("currentUser", user);
return true;
} catch (Exception e) {
log.error("管理员权限验证失败", e);
writeErrorResponse(response, 500, "权限验证失败");
return false;
}
}
private void writeErrorResponse(HttpServletResponse response, int code, String message) throws Exception {
response.setStatus(200); // HTTP状态码设为200错误信息在响应体中
response.setContentType("application/json;charset=UTF-8");
Result result = Result.error(code, message);
ObjectMapper objectMapper = new ObjectMapper();
String jsonResponse = objectMapper.writeValueAsString(result);
response.getWriter().write(jsonResponse);
}
}

@ -49,4 +49,36 @@ public interface CategoryMapper {
* @return * @return
*/ */
Integer getCount(@Param("status") Byte status); Integer getCount(@Param("status") Byte status);
// ========== 管理员后台相关方法 ==========
/**
*
*/
int getTotalCount();
/**
*
*/
List<Category> getAllCategories();
/**
* ID
*/
Category getCategoryById(Long id);
/**
*
*/
void insertCategory(Category category);
/**
*
*/
void updateCategory(Category category);
/**
*
*/
void deleteCategory(Long categoryId);
} }

@ -68,4 +68,42 @@ public interface CommentMapper {
* @param id ID * @param id ID
*/ */
void decrementLikeCount(Long id); void decrementLikeCount(Long id);
// ========== 管理员后台相关方法 ==========
/**
*
*/
int getTotalCount();
/**
*
*/
int getNewCommentCountToday();
/**
* ID
*/
Comment getCommentById(Long id);
/**
*
*/
List<Comment> getAdminCommentList(@Param("offset") int offset,
@Param("size") int size,
@Param("keyword") String keyword,
@Param("postId") Long postId,
@Param("status") Integer status);
/**
*
*/
int getAdminCommentCount(@Param("keyword") String keyword,
@Param("postId") Long postId,
@Param("status") Integer status);
/**
*
*/
void deleteComment(Long commentId);
} }

@ -105,4 +105,52 @@ public interface PostMapper {
List<Post> searchPosts(@Param("keyword") String keyword, List<Post> searchPosts(@Param("keyword") String keyword,
@Param("categoryId") Long categoryId, @Param("categoryId") Long categoryId,
@Param("sortBy") String sortBy); @Param("sortBy") String sortBy);
// ========== 管理员后台相关方法 ==========
/**
*
*/
int getTotalCount();
/**
*
*/
int getNewPostCountToday();
/**
* ID
*/
Post getPostById(Long id);
/**
*
*/
List<Post> getAdminPostList(@Param("offset") int offset,
@Param("size") int size,
@Param("keyword") String keyword,
@Param("categoryId") Long categoryId,
@Param("status") Integer status);
/**
*
*/
int getAdminPostCount(@Param("keyword") String keyword,
@Param("categoryId") Long categoryId,
@Param("status") Integer status);
/**
*
*/
void updatePostStatus(@Param("postId") Long postId, @Param("status") Integer status);
/**
*
*/
void deletePost(Long postId);
/**
*
*/
int getCountByCategoryId(Long categoryId);
} }

@ -100,4 +100,42 @@ public interface ResourceMapper {
List<Resource> searchResources(@Param("keyword") String keyword, List<Resource> searchResources(@Param("keyword") String keyword,
@Param("categoryId") Long categoryId, @Param("categoryId") Long categoryId,
@Param("sortBy") String sortBy); @Param("sortBy") String sortBy);
// ========== 管理员后台相关方法 ==========
/**
*
*/
int getTotalCount();
/**
*
*/
int getNewResourceCountToday();
/**
* ID
*/
Resource getResourceById(Long id);
/**
*
*/
List<Resource> getAdminResourceList(@Param("offset") int offset,
@Param("size") int size,
@Param("keyword") String keyword,
@Param("categoryId") Long categoryId,
@Param("status") Integer status);
/**
*
*/
int getAdminResourceCount(@Param("keyword") String keyword,
@Param("categoryId") Long categoryId,
@Param("status") Integer status);
/**
*
*/
void deleteResource(Long resourceId);
} }

@ -72,4 +72,47 @@ public interface UserMapper {
* @param userId ID * @param userId ID
*/ */
void deleteUserLikes(Long userId); void deleteUserLikes(Long userId);
// ========== 管理员后台相关方法 ==========
/**
*
*/
int getTotalCount();
/**
* 30
*/
int getActiveUserCount();
/**
*
*/
int getNewUserCountToday();
/**
*
*/
List<User> getAdminUserList(@Param("offset") int offset,
@Param("size") int size,
@Param("keyword") String keyword,
@Param("role") Integer role,
@Param("status") Integer status);
/**
*
*/
int getAdminUserCount(@Param("keyword") String keyword,
@Param("role") Integer role,
@Param("status") Integer status);
/**
*
*/
void updateUserStatus(@Param("userId") Long userId, @Param("status") Integer status);
/**
*
*/
void updateUserRole(@Param("userId") Long userId, @Param("role") Integer role);
} }

@ -0,0 +1,88 @@
package com.unilife.service;
import com.unilife.common.result.Result;
import java.util.Map;
public interface AdminService {
/**
*
*/
Result getSystemStats();
/**
*
*/
Result getUserList(Integer page, Integer size, String keyword, Integer role, Integer status);
/**
*
*/
Result updateUserStatus(Long userId, Integer status);
/**
*
*/
Result updateUserRole(Long userId, Integer role);
/**
*
*/
Result deleteUser(Long userId);
/**
*
*/
Result getPostList(Integer page, Integer size, String keyword, Long categoryId, Integer status);
/**
*
*/
Result updatePostStatus(Long postId, Integer status);
/**
*
*/
Result deletePost(Long postId);
/**
*
*/
Result getCommentList(Integer page, Integer size, String keyword, Long postId, Integer status);
/**
*
*/
Result deleteComment(Long commentId);
/**
*
*/
Result getCategoryList();
/**
*
*/
Result createCategory(Map<String, Object> request);
/**
*
*/
Result updateCategory(Long categoryId, Map<String, Object> request);
/**
*
*/
Result deleteCategory(Long categoryId);
/**
*
*/
Result getResourceList(Integer page, Integer size, String keyword, Long categoryId, Integer status);
/**
*
*/
Result deleteResource(Long resourceId);
}

@ -0,0 +1,339 @@
package com.unilife.service.impl;
import com.unilife.common.result.Result;
import com.unilife.mapper.*;
import com.unilife.model.entity.*;
import com.unilife.service.AdminService;
import com.unilife.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class AdminServiceImpl implements AdminService {
@Autowired
private UserMapper userMapper;
@Autowired
private PostMapper postMapper;
@Autowired
private CommentMapper commentMapper;
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private ResourceMapper resourceMapper;
@Autowired
private UserService userService;
@Override
public Result getSystemStats() {
try {
Map<String, Object> stats = new HashMap<>();
// 用户统计
stats.put("totalUsers", userMapper.getTotalCount());
stats.put("activeUsers", userMapper.getActiveUserCount());
stats.put("newUsersToday", userMapper.getNewUserCountToday());
// 帖子统计
stats.put("totalPosts", postMapper.getTotalCount());
stats.put("newPostsToday", postMapper.getNewPostCountToday());
// 评论统计
stats.put("totalComments", commentMapper.getTotalCount());
stats.put("newCommentsToday", commentMapper.getNewCommentCountToday());
// 资源统计
stats.put("totalResources", resourceMapper.getTotalCount());
stats.put("newResourcesToday", resourceMapper.getNewResourceCountToday());
// 分类统计
stats.put("totalCategories", categoryMapper.getTotalCount());
return Result.success(stats);
} catch (Exception e) {
log.error("获取系统统计数据失败", e);
return Result.error(500, "获取系统统计数据失败");
}
}
@Override
public Result getUserList(Integer page, Integer size, String keyword, Integer role, Integer status) {
try {
int offset = (page - 1) * size;
List<User> users = userMapper.getAdminUserList(offset, size, keyword, role, status);
int total = userMapper.getAdminUserCount(keyword, role, status);
Map<String, Object> result = new HashMap<>();
result.put("list", users);
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 updateUserStatus(Long userId, Integer status) {
try {
User user = userMapper.getUserById(userId);
if (user == null) {
return Result.error(404, "用户不存在");
}
userMapper.updateUserStatus(userId, status);
return Result.success(null, "用户状态更新成功");
} catch (Exception e) {
log.error("更新用户状态失败", e);
return Result.error(500, "更新用户状态失败");
}
}
@Override
public Result updateUserRole(Long userId, Integer role) {
try {
User user = userMapper.getUserById(userId);
if (user == null) {
return Result.error(404, "用户不存在");
}
userMapper.updateUserRole(userId, role);
return Result.success(null, "用户角色更新成功");
} catch (Exception e) {
log.error("更新用户角色失败", e);
return Result.error(500, "更新用户角色失败");
}
}
@Override
public Result deleteUser(Long userId) {
try {
User user = userMapper.getUserById(userId);
if (user == null) {
return Result.error(404, "用户不存在");
}
// 检查是否为管理员
if (user.getRole() == 2) {
return Result.error(400, "不能删除管理员账号");
}
// 调用UserService的完整删除逻辑
return userService.deleteUser(userId);
} catch (Exception e) {
log.error("删除用户失败", e);
return Result.error(500, "删除用户失败");
}
}
@Override
public Result getPostList(Integer page, Integer size, String keyword, Long categoryId, Integer status) {
try {
int offset = (page - 1) * size;
List<Post> posts = postMapper.getAdminPostList(offset, size, keyword, categoryId, status);
int total = postMapper.getAdminPostCount(keyword, categoryId, status);
Map<String, Object> result = new HashMap<>();
result.put("list", posts);
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 updatePostStatus(Long postId, Integer status) {
try {
Post post = postMapper.getPostById(postId);
if (post == null) {
return Result.error(404, "帖子不存在");
}
postMapper.updatePostStatus(postId, status);
return Result.success(null, "帖子状态更新成功");
} catch (Exception e) {
log.error("更新帖子状态失败", e);
return Result.error(500, "更新帖子状态失败");
}
}
@Override
public Result deletePost(Long postId) {
try {
Post post = postMapper.getPostById(postId);
if (post == null) {
return Result.error(404, "帖子不存在");
}
postMapper.deletePost(postId);
return Result.success(null, "帖子删除成功");
} catch (Exception e) {
log.error("删除帖子失败", e);
return Result.error(500, "删除帖子失败");
}
}
@Override
public Result getCommentList(Integer page, Integer size, String keyword, Long postId, Integer status) {
try {
int offset = (page - 1) * size;
List<Comment> comments = commentMapper.getAdminCommentList(offset, size, keyword, postId, status);
int total = commentMapper.getAdminCommentCount(keyword, postId, status);
Map<String, Object> result = new HashMap<>();
result.put("list", comments);
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 deleteComment(Long commentId) {
try {
Comment comment = commentMapper.getCommentById(commentId);
if (comment == null) {
return Result.error(404, "评论不存在");
}
commentMapper.deleteComment(commentId);
return Result.success(null, "评论删除成功");
} catch (Exception e) {
log.error("删除评论失败", e);
return Result.error(500, "删除评论失败");
}
}
@Override
public Result getCategoryList() {
try {
List<Category> categories = categoryMapper.getAllCategories();
return Result.success(categories);
} catch (Exception e) {
log.error("获取分类列表失败", e);
return Result.error(500, "获取分类列表失败");
}
}
@Override
public Result createCategory(Map<String, Object> request) {
try {
Category category = new Category();
category.setName((String) request.get("name"));
category.setDescription((String) request.get("description"));
category.setIcon((String) request.get("icon"));
category.setSort((Integer) request.get("sort"));
Integer statusInt = (Integer) request.get("status");
category.setStatus(statusInt != null ? statusInt.byteValue() : (byte) 1);
categoryMapper.insertCategory(category);
return Result.success(null, "分类创建成功");
} catch (Exception e) {
log.error("创建分类失败", e);
return Result.error(500, "创建分类失败");
}
}
@Override
public Result updateCategory(Long categoryId, Map<String, Object> request) {
try {
Category category = categoryMapper.getCategoryById(categoryId);
if (category == null) {
return Result.error(404, "分类不存在");
}
category.setName((String) request.get("name"));
category.setDescription((String) request.get("description"));
category.setIcon((String) request.get("icon"));
category.setSort((Integer) request.get("sort"));
Integer statusInt = (Integer) request.get("status");
category.setStatus(statusInt != null ? statusInt.byteValue() : (byte) 1);
categoryMapper.updateCategory(category);
return Result.success(null, "分类更新成功");
} catch (Exception e) {
log.error("更新分类失败", e);
return Result.error(500, "更新分类失败");
}
}
@Override
public Result deleteCategory(Long categoryId) {
try {
Category category = categoryMapper.getCategoryById(categoryId);
if (category == null) {
return Result.error(404, "分类不存在");
}
// 检查是否有帖子或资源使用该分类
int postCount = postMapper.getCountByCategoryId(categoryId);
int resourceCount = resourceMapper.getCountByCategoryId(categoryId);
if (postCount > 0 || resourceCount > 0) {
return Result.error(400, "该分类下还有帖子或资源,无法删除");
}
categoryMapper.deleteCategory(categoryId);
return Result.success(null, "分类删除成功");
} catch (Exception e) {
log.error("删除分类失败", e);
return Result.error(500, "删除分类失败");
}
}
@Override
public Result getResourceList(Integer page, Integer size, String keyword, Long categoryId, Integer status) {
try {
int offset = (page - 1) * size;
List<Resource> resources = resourceMapper.getAdminResourceList(offset, size, keyword, categoryId, status);
int total = resourceMapper.getAdminResourceCount(keyword, categoryId, status);
Map<String, Object> result = new HashMap<>();
result.put("list", resources);
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 deleteResource(Long resourceId) {
try {
Resource resource = resourceMapper.getResourceById(resourceId);
if (resource == null) {
return Result.error(404, "资源不存在");
}
resourceMapper.deleteResource(resourceId);
return Result.success(null, "资源删除成功");
} catch (Exception e) {
log.error("删除资源失败", e);
return Result.error(500, "删除资源失败");
}
}
}

@ -17,7 +17,6 @@ import com.unilife.model.dto.UpdateProfileDTO;
import com.unilife.model.entity.User; import com.unilife.model.entity.User;
import com.unilife.model.entity.Post; import com.unilife.model.entity.Post;
import com.unilife.model.vo.LoginVO; import com.unilife.model.vo.LoginVO;
import com.unilife.model.vo.RegisterVO;
import com.unilife.service.IPLocationService; import com.unilife.service.IPLocationService;
import com.unilife.service.UserService; import com.unilife.service.UserService;
import com.unilife.utils.JwtUtil; import com.unilife.utils.JwtUtil;
@ -25,7 +24,6 @@ import com.unilife.utils.RegexUtils;
import jakarta.mail.MessagingException; import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;

@ -1,192 +0,0 @@
-- UniLife数据库初始化脚本
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS UniLife DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- 使用数据库
USE UniLife;
-- 用户表
CREATE TABLE IF NOT EXISTS `users` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
`email` VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱地址(学校邮箱)',
`password` VARCHAR(255) NOT NULL COMMENT '密码(加密存储)',
`nickname` VARCHAR(50) NOT NULL COMMENT '昵称',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
`bio` TEXT DEFAULT NULL COMMENT '个人简介',
`gender` TINYINT DEFAULT 0 COMMENT '性别0-未知, 1-男, 2-女)',
`student_id` VARCHAR(20) UNIQUE DEFAULT NULL COMMENT '学号',
`department` VARCHAR(100) DEFAULT NULL COMMENT '院系',
`major` VARCHAR(100) DEFAULT NULL COMMENT '专业',
`grade` VARCHAR(20) DEFAULT NULL COMMENT '年级',
`points` INT DEFAULT 0 COMMENT '积分',
`role` TINYINT DEFAULT 0 COMMENT '角色0-普通用户, 1-版主, 2-管理员)',
`status` TINYINT DEFAULT 1 COMMENT '状态0-禁用, 1-启用)',
`is_verified` TINYINT DEFAULT 0 COMMENT '是否验证0-未验证, 1-已验证)',
`login_ip` VARCHAR(50) DEFAULT NULL COMMENT '最近登录IP',
`login_time` DATETIME DEFAULT NULL COMMENT '最近登录时间',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_email` (`email`),
INDEX `idx_username` (`username`),
INDEX `idx_student_id` (`student_id`),
INDEX `idx_role` (`role`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- 分类表
CREATE TABLE IF NOT EXISTS `categories` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '分类ID',
`name` VARCHAR(50) NOT NULL UNIQUE COMMENT '分类名称',
`description` VARCHAR(255) DEFAULT NULL COMMENT '分类描述',
`icon` VARCHAR(255) DEFAULT NULL COMMENT '分类图标',
`sort` INT DEFAULT 0 COMMENT '排序',
`status` TINYINT DEFAULT 1 COMMENT '状态0-禁用, 1-启用)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_status` (`status`),
INDEX `idx_sort` (`sort`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分类表';
-- 帖子表
CREATE TABLE IF NOT EXISTS `posts` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '帖子ID',
`user_id` BIGINT NOT NULL COMMENT '发布用户ID',
`title` VARCHAR(100) NOT NULL COMMENT '帖子标题',
`content` TEXT NOT NULL COMMENT '帖子内容',
`category_id` BIGINT NOT NULL COMMENT '分类ID',
`view_count` INT DEFAULT 0 COMMENT '浏览次数',
`like_count` INT DEFAULT 0 COMMENT '点赞次数',
`comment_count` INT DEFAULT 0 COMMENT '评论次数',
`status` TINYINT DEFAULT 1 COMMENT '状态0-删除, 1-正常, 2-置顶)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_user_id` (`user_id`),
INDEX `idx_category_id` (`category_id`),
INDEX `idx_status` (`status`),
INDEX `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='帖子表';
-- 评论表
CREATE TABLE IF NOT EXISTS `comments` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '评论ID',
`post_id` BIGINT NOT NULL COMMENT '帖子ID',
`user_id` BIGINT NOT NULL COMMENT '评论用户ID',
`content` TEXT NOT NULL COMMENT '评论内容',
`parent_id` BIGINT DEFAULT NULL COMMENT '父评论ID回复某条评论',
`like_count` INT DEFAULT 0 COMMENT '点赞次数',
`status` TINYINT DEFAULT 1 COMMENT '状态0-删除, 1-正常)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_post_id` (`post_id`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_parent_id` (`parent_id`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评论表';
-- 点赞表(用户-帖子)
CREATE TABLE IF NOT EXISTS `post_likes` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '点赞ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`post_id` BIGINT NOT NULL COMMENT '帖子ID',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY `uk_user_post` (`user_id`, `post_id`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_post_id` (`post_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='帖子点赞表';
-- 点赞表(用户-评论)
CREATE TABLE IF NOT EXISTS `comment_likes` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '点赞ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`comment_id` BIGINT NOT NULL COMMENT '评论ID',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY `uk_user_comment` (`user_id`, `comment_id`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_comment_id` (`comment_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评论点赞表';
-- 点赞表(用户-资源)
CREATE TABLE IF NOT EXISTS `resource_likes` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '点赞ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`resource_id` BIGINT NOT NULL COMMENT '资源ID',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
UNIQUE KEY `uk_user_resource` (`user_id`, `resource_id`),
INDEX `idx_user_id` (`user_id`),
INDEX `idx_resource_id` (`resource_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='资源点赞表';
-- 资源表
CREATE TABLE IF NOT EXISTS `resources` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '资源ID',
`user_id` BIGINT NOT NULL COMMENT '上传用户ID',
`title` VARCHAR(100) NOT NULL COMMENT '资源标题',
`description` TEXT DEFAULT NULL COMMENT '资源描述',
`file_url` VARCHAR(255) NOT NULL COMMENT '文件URL',
`file_size` BIGINT NOT NULL COMMENT '文件大小(字节)',
`file_type` VARCHAR(50) NOT NULL COMMENT '文件类型',
`category_id` BIGINT NOT NULL COMMENT '分类ID',
`download_count` INT DEFAULT 0 COMMENT '下载次数',
`like_count` INT DEFAULT 0 COMMENT '点赞次数',
`status` TINYINT DEFAULT 1 COMMENT '状态0-删除, 1-正常)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_user_id` (`user_id`),
INDEX `idx_category_id` (`category_id`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='资源表';
-- 课程表
CREATE TABLE IF NOT EXISTS `courses` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '课程ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`name` VARCHAR(100) NOT NULL COMMENT '课程名称',
`teacher` VARCHAR(50) DEFAULT NULL COMMENT '教师姓名',
`location` VARCHAR(100) DEFAULT NULL COMMENT '上课地点',
`day_of_week` TINYINT NOT NULL COMMENT '星期几1-7',
`start_time` TIME NOT NULL COMMENT '开始时间',
`end_time` TIME NOT NULL COMMENT '结束时间',
`start_week` TINYINT NOT NULL COMMENT '开始周次',
`end_week` TINYINT NOT NULL COMMENT '结束周次',
`semester` VARCHAR(20) DEFAULT NULL COMMENT '学期2023-1',
`color` VARCHAR(20) DEFAULT NULL COMMENT '显示颜色',
`status` TINYINT DEFAULT 1 COMMENT '状态0-删除, 1-正常)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_user_id` (`user_id`),
INDEX `idx_semester` (`semester`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='课程表';
-- 日程表
CREATE TABLE IF NOT EXISTS `schedules` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '日程ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`title` VARCHAR(100) NOT NULL COMMENT '日程标题',
`description` TEXT DEFAULT NULL COMMENT '日程描述',
`start_time` DATETIME NOT NULL COMMENT '开始时间',
`end_time` DATETIME NOT NULL COMMENT '结束时间',
`location` VARCHAR(100) DEFAULT NULL COMMENT '地点',
`is_all_day` TINYINT DEFAULT 0 COMMENT '是否全天0-否, 1-是)',
`reminder` TINYINT DEFAULT NULL COMMENT '提醒时间(分钟)',
`color` VARCHAR(20) DEFAULT NULL COMMENT '显示颜色',
`status` TINYINT DEFAULT 1 COMMENT '状态0-删除, 1-正常)',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX `idx_user_id` (`user_id`),
INDEX `idx_start_time` (`start_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='日程表';
-- 初始化分类数据
INSERT INTO `categories` (`name`, `description`, `icon`, `sort`, `status`) VALUES
('学习交流', '讨论学习相关话题', 'icon-study', 1, 1),
('校园生活', '分享校园生活点滴', 'icon-campus', 2, 1),
('兴趣爱好', '交流各类兴趣爱好', 'icon-hobby', 3, 1),
('求职就业', '分享求职经验和就业信息', 'icon-job', 4, 1),
('资源共享', '分享各类学习资源', 'icon-resource', 5, 1);
-- 初始化管理员账号
INSERT INTO `users` (`username`, `email`, `password`, `nickname`, `role`, `status`, `is_verified`) VALUES
('admin', 'admin@unilife.com', '123456', '系统管理员', 2, 1, 1);

@ -203,8 +203,9 @@ INSERT INTO `categories` (`name`, `description`, `icon`, `sort`, `status`) VALUE
('资源共享', '分享各类学习资源', 'icon-resource', 5, 1); ('资源共享', '分享各类学习资源', 'icon-resource', 5, 1);
-- 初始化管理员账号 -- 初始化管理员账号
INSERT INTO `users` (`username`, `email`, `password`, `nickname`, `role`, `status`, `is_verified`) VALUES INSERT INTO `users` (`username`, `email`, `password`, `nickname`, `role`, `status`, `is_verified`, `created_at`) VALUES
('admin', 'admin@unilife.com', '123456', '系统管理员', 2, 1, 1); ('admin', 'admin@unilife.com', '123456', '系统管理员', 2, 1, 1, '2024-01-01 00:00:00'),
('superadmin', 'superadmin@unilife.com', 'admin123', '超级管理员', 2, 1, 1, '2024-01-01 00:00:00');
-- ========================================== -- ==========================================
-- 第四步:插入测试数据 -- 第四步:插入测试数据

@ -1,195 +0,0 @@
-- 武汉大学学生论坛测试数据
-- 使用数据库
USE UniLife;
-- 清空现有测试数据(保留表结构和管理员账号)
DELETE FROM resource_likes;
DELETE FROM post_likes;
DELETE FROM comment_likes;
DELETE FROM comments;
DELETE FROM posts;
DELETE FROM resources;
DELETE FROM schedules;
DELETE FROM courses;
DELETE FROM users WHERE id > 1; -- 保留管理员账号
-- 清空可能存在的重复分类
DELETE FROM categories WHERE name IN ('学院专区', '考研考公', '生活服务');
-- 重置自增ID
ALTER TABLE users AUTO_INCREMENT = 2;
ALTER TABLE posts AUTO_INCREMENT = 1;
ALTER TABLE comments AUTO_INCREMENT = 1;
ALTER TABLE post_likes AUTO_INCREMENT = 1;
ALTER TABLE comment_likes AUTO_INCREMENT = 1;
ALTER TABLE resources AUTO_INCREMENT = 1;
ALTER TABLE resource_likes AUTO_INCREMENT = 1;
ALTER TABLE courses AUTO_INCREMENT = 1;
ALTER TABLE schedules AUTO_INCREMENT = 1;
ALTER TABLE categories AUTO_INCREMENT = 6;
-- 更新现有分类数据(更贴合武汉大学实际)
UPDATE `categories` SET
`name` = '学术交流',
`description` = '学术讨论、科研分享、竞赛经验',
`icon` = '📚'
WHERE `name` = '学习交流';
UPDATE `categories` SET
`description` = '武汉大学校园生活、社团活动、文化娱乐',
`icon` = '🏫'
WHERE `name` = '校园生活';
UPDATE `categories` SET
`name` = '就业实习',
`description` = '实习经验、求职心得、职业规划',
`icon` = '💼'
WHERE `name` = '求职就业';
-- 插入新的分类
INSERT INTO `categories` (`name`, `description`, `icon`, `sort`, `status`) VALUES
('学院专区', '各学院学生交流专区', '🎓', 6, 1),
('考研考公', '研究生入学考试、公务员考试', '📖', 7, 1),
('生活服务', '二手交易、失物招领、校园服务', '🛍️', 8, 1);
-- 1. 插入武汉大学学生用户数据
INSERT INTO `users` (`username`, `email`, `password`, `nickname`, `bio`, `gender`, `student_id`, `department`, `major`, `grade`, `points`, `role`, `status`, `is_verified`, `created_at`) VALUES
-- 文理学部学生
('czq2024', 'czq@whu.edu.cn', '123456', '珞珈数学狗', '数学与统计学院2022级数学类热爱数学建模ACM银牌选手', 1, '2022301140001', '数学与统计学院', '数学类', '2022级', 150, 0, 1, 1, '2024-09-01 09:00:00'),
('lihua_cs', 'lihua@whu.edu.cn', '123456', '代码诗人', '计算机学院2021级软件工程全栈开发爱好者开源项目贡献者', 1, '2021301120001', '计算机学院', '软件工程', '2021级', 230, 0, 1, 1, '2024-09-01 10:00:00'),
('wangming_law', 'wangming@whu.edu.cn', '123456', '法学小白', '法学院2023级法学专业模拟法庭常客梦想成为大律师', 1, '2023301080001', '法学院', '法学', '2023级', 80, 0, 1, 1, '2024-09-01 11:00:00'),
('zhangwei_chem', 'zhangwei@whu.edu.cn', '123456', '化学实验员', '化学与分子科学学院2022级化学专业实验室常驻合成达人', 1, '2022301130001', '化学与分子科学学院', '化学', '2022级', 120, 0, 1, 1, '2024-09-01 12:00:00'),
('liuxin_econ', 'liuxin@whu.edu.cn', '123456', '经济观察者', '经济与管理学院2021级经济学关注宏观经济政策券商实习生', 2, '2021301110001', '经济与管理学院', '经济学', '2021级', 200, 0, 1, 1, '2024-09-01 13:00:00'),
-- 工学部学生
('chenfei_water', 'chenfei@whu.edu.cn', '123456', '水利工程师', '水利水电学院2022级水利水电工程三峡实习经历立志建设美丽中国', 1, '2022301320001', '水利水电学院', '水利水电工程', '2022级', 90, 0, 1, 1, '2024-09-01 14:00:00'),
('sunhao_power', 'sunhao@whu.edu.cn', '123456', '电气小子', '电气与自动化学院2023级电气工程及其自动化电力系统仿真专家', 1, '2023301330001', '电气与自动化学院', '电气工程及其自动化', '2023级', 70, 0, 1, 1, '2024-09-01 15:00:00'),
('wujing_civil', 'wujing@whu.edu.cn', '123456', '土木妹子', '土木建筑工程学院2022级土木工程桥梁设计爱好者BIM技术达人', 2, '2022301340001', '土木建筑工程学院', '土木工程', '2022级', 110, 0, 1, 1, '2024-09-01 16:00:00'),
-- 信息学部学生
('liqiang_remote', 'liqiang@whu.edu.cn', '123456', '遥感专家', '遥感信息工程学院2021级遥感科学与技术无人机航拍爱好者', 1, '2021301210001', '遥感信息工程学院', '遥感科学与技术', '2021级', 180, 0, 1, 1, '2024-09-01 17:00:00'),
('zhaoli_survey', 'zhaoli@whu.edu.cn', '123456', '测绘达人', '测绘学院2022级测绘工程GPS定位技术研究者野外作业经验丰富', 1, '2022301220001', '测绘学院', '测绘工程', '2022级', 95, 0, 1, 1, '2024-09-01 18:00:00'),
-- 医学部学生
('huangyan_med', 'huangyan@whu.edu.cn', '123456', '未来医生', '基础医学院2020级临床医学人民医院实习生立志救死扶伤', 2, '2020301410001', '基础医学院', '临床医学', '2020级', 250, 0, 1, 1, '2024-09-01 19:00:00'),
('wangpeng_dental', 'wangpeng@whu.edu.cn', '123456', '口腔医师', '口腔医学院2021级口腔医学口腔医院见习关注口腔健康科普', 1, '2021301420001', '口腔医学院', '口腔医学', '2021级', 160, 0, 1, 1, '2024-09-01 20:00:00'),
-- 人文社科学部学生
('luxiaoya_chinese', 'luxiaoya@whu.edu.cn', '123456', '文学少女', '文学院2022级汉语言文学古典文学爱好者诗词社社长', 2, '2022301050001', '文学院', '汉语言文学', '2022级', 140, 0, 1, 1, '2024-09-01 21:00:00'),
('zhoujie_history', 'zhoujie@whu.edu.cn', '123456', '史学研究生', '历史学院研究生,中国古代史方向,博物馆志愿者', 1, '2024302050001', '历史学院', '中国史', '2024级', 100, 0, 1, 1, '2024-09-01 22:00:00'),
('tanglei_news', 'tanglei@whu.edu.cn', '123456', '新传人', '新闻与传播学院2021级新闻学校媒记者关注社会热点', 1, '2021301070001', '新闻与传播学院', '新闻学', '2021级', 170, 0, 1, 1, '2024-09-01 23:00:00');
-- 2. 插入论坛帖子数据使用已存在的用户ID
INSERT INTO `posts` (`user_id`, `category_id`, `title`, `content`, `view_count`, `like_count`, `comment_count`, `status`, `created_at`) VALUES
-- 学术交流类帖子
(2, 1, '数学建模美赛经验分享', '刚刚结束的美国大学生数学建模竞赛我们团队获得了M奖分享一下参赛经验和技巧希望对学弟学妹们有帮助。数模比赛不仅考验数学能力更重要的是团队协作和论文写作能力。首先要选择合适的队友最好是数学、编程、英语各有所长的组合...', 256, 42, 8, 2, '2024-12-20 09:30:00'),
(3, 1, 'ACM-ICPC区域赛总结', '参加了西安站的ACM区域赛虽然没能拿到金牌但收获很大。分享一下刷题心得和比赛策略特别是动态规划和图论算法的练习方法。建议大家多在Codeforces和AtCoder上练习这些平台的题目质量很高...', 189, 35, 6, 1, '2024-12-19 16:45:00'),
(6, 1, '宏观经济学课程研讨:通胀与货币政策', '最近在学习宏观经济学,对当前的通胀形势和央行货币政策有一些思考。想和大家讨论一下利率调整对经济的影响机制,特别是在当前全球经济形势下的作用...', 145, 28, 4, 1, '2024-12-18 14:20:00'),
-- 校园生活类帖子
(14, 2, '武大樱花季摄影大赛作品展示', '樱花季刚过分享一些在樱花大道拍摄的照片。今年的樱花开得特别美虽然人很多但还是拍到了一些不错的角度。附上拍摄技巧分享使用的是佳能5D4光圈f/2.8ISO400后期用LR调色...', 1234, 156, 12, 2, '2024-04-10 10:15:00'),
(16, 2, '校运动会志愿者招募!', '第55届田径运动会即将开始现招募志愿者工作内容包括引导、记分、颁奖等。参与志愿服务可获得志愿时长认证还有纪念品哦有意向的同学请在评论区留言或私信联系我', 456, 89, 5, 1, '2024-12-15 08:00:00'),
(11, 2, '测绘学院野外实习日记', '刚从庐山实习回来分享一下野外测量的酸甜苦辣。早上5点起床背着仪器爬山虽然辛苦但收获满满。珞珈山的风景真是看不够啊学到了很多实际操作技能...', 234, 45, 7, 1, '2024-12-14 19:30:00'),
-- 学院专区类帖子
(4, 6, '法学院模拟法庭大赛预告', '一年一度的"枫叶杯"模拟法庭大赛即将开始!欢迎各年级同学组队参加。比赛分为民事组和刑事组,优胜者将代表学院参加全国比赛。这是提升法律实务能力的绝佳机会...', 345, 67, 9, 2, '2024-12-16 11:00:00'),
(5, 6, '化学实验安全注意事项提醒', '最近实验室发生了几起小事故,提醒大家一定要注意安全!特别是使用强酸强碱时,护目镜和手套必须佩戴。实验无小事,安全第一!同时要做好实验记录...', 178, 34, 3, 1, '2024-12-17 15:20:00'),
-- 就业实习类帖子
(6, 3, '券商实习面试经验分享', '刚刚拿到某头部券商的实习offer分享一下面试经验。金融行业对专业能力和综合素质要求都很高准备过程中要注意这几个方面扎实的专业基础、良好的表达能力、对市场的敏感度...', 423, 78, 11, 1, '2024-12-21 14:00:00'),
(3, 3, 'IT互联网春招总结', '经历了春招季最终选择了某大厂的后端开发岗位。分享一下投递简历、技术面试、HR面试的全流程经验希望对计算机专业的同学有帮助。技术面试主要考察数据结构、算法、系统设计...', 567, 89, 15, 2, '2024-05-18 09:15:00'),
-- 考研考公类帖子
(15, 7, '历史学考研经验贴', '成功上岸北师大中国史专业!分享一下备考经验:如何选择学校、如何制定复习计划、如何准备专业课等。考研路上不孤单,加油!专业课复习要注意史料分析和论述题...', 389, 72, 13, 1, '2024-12-10 22:00:00'),
-- 生活服务类帖子
(9, 8, '出售工科教材一批', '即将毕业,出售一些专业课教材:《结构力学》《材料力学》《工程制图》等,八成新,价格优惠。有需要的学弟学妹可以联系我~都是正版教材,保存得很好', 156, 12, 2, 1, '2024-12-22 18:30:00'),
(13, 8, '寻找珞珈山丢失的口腔器械包', '昨天在樱花大道丢失了一个蓝色器械包,里面有重要的口腔实习用具。如有好心人捡到,请联系我,必有重谢!器械包上有我的姓名标签', 89, 8, 1, 1, '2024-12-23 07:45:00');
-- 3. 插入评论数据
INSERT INTO `comments` (`post_id`, `user_id`, `content`, `parent_id`, `like_count`, `status`, `created_at`) VALUES
-- 对数学建模帖子的评论
(1, 3, '恭喜学长!我们正在准备下半年的国赛,请问有什么推荐的学习资料吗?', NULL, 5, 1, '2024-12-20 10:30:00'),
(1, 6, '数模确实需要很强的团队协作能力,我们当时就是沟通不够充分才没拿到好成绩', NULL, 3, 1, '2024-12-20 11:15:00'),
(1, 2, '推荐《数学建模方法与分析》这本书MATLAB和Python都要熟练掌握', 1, 2, 1, '2024-12-20 12:00:00'),
-- 对樱花帖子的评论
(4, 10, '照片拍得真美!求拍摄参数和后期处理方法', NULL, 8, 1, '2024-04-10 14:30:00'),
(4, 16, '武大的樱花确实是一绝,每年都要来打卡', NULL, 4, 1, '2024-04-10 15:45:00'),
-- 对法学院帖子的评论
(7, 15, '法学院的模拟法庭一直很有名,想去观摩学习', NULL, 3, 1, '2024-12-16 13:00:00'),
(7, 4, '欢迎其他学院的同学来观摩!比赛时间是下周五晚上', 6, 1, 1, '2024-12-16 14:30:00'),
-- 对实习帖子的评论
(9, 2, '金融行业竞争确实激烈,学长有什么建议给想进入这个行业的同学吗?', NULL, 4, 1, '2024-12-21 15:30:00'),
(9, 6, '建议先把CFA一级考出来然后多参加实习积累经验', 8, 6, 1, '2024-12-21 16:45:00');
-- 4. 插入点赞数据
INSERT INTO `post_likes` (`user_id`, `post_id`, `created_at`) VALUES
-- 用户点赞帖子
(2, 4, '2024-04-10 11:00:00'),
(2, 7, '2024-12-16 12:00:00'),
(3, 1, '2024-12-20 10:00:00'),
(3, 4, '2024-04-10 16:00:00'),
(4, 1, '2024-12-20 11:30:00'),
(4, 9, '2024-12-21 15:00:00'),
(5, 2, '2024-12-19 17:30:00'),
(6, 1, '2024-12-20 13:00:00'),
(6, 10, '2024-05-18 10:00:00');
-- 5. 插入学习资源数据
INSERT INTO `resources` (`user_id`, `title`, `description`, `file_url`, `file_size`, `file_type`, `category_id`, `download_count`, `like_count`, `status`) VALUES
(2, '数据结构课程设计报告', '包含完整的数据结构课程设计实验报告,涵盖栈、队列、树、图等数据结构的实现和应用。', '/files/data-structure-report.pdf', 2048576, 'application/pdf', 1, 15, 8, 1),
(3, '算法导论学习笔记', '详细的算法导论学习笔记,包含排序算法、图算法、动态规划等重要算法的分析和实现。', '/files/algorithm-notes.docx', 1572864, 'application/msword', 1, 25, 12, 1),
(2, '高等数学期末复习资料', '高等数学期末考试复习资料合集,包含重要公式、定理证明和典型习题解答。', '/files/calculus-review.pdf', 3145728, 'application/pdf', 1, 32, 18, 1),
(6, '宏观经济学PPT课件', '经济学专业课件,包含货币政策、财政政策等核心内容。', '/files/macro-economics.pptx', 5242880, 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 1, 20, 15, 1),
(14, '校园生活指南', '新生校园生活指南,包含宿舍管理、食堂介绍、图书馆使用等实用信息。', '/files/campus-guide.pdf', 1048576, 'application/pdf', 2, 45, 28, 1),
(3, '计算机网络实验代码', '计算机网络课程实验代码合集包含Socket编程、HTTP协议实现等。', '/files/network-lab-code.zip', 4194304, 'application/zip', 5, 18, 10, 1);
-- 6. 插入课程数据
INSERT INTO `courses` (`user_id`, `name`, `teacher`, `location`, `day_of_week`, `start_time`, `end_time`, `start_week`, `end_week`, `semester`, `color`, `status`) VALUES
-- 数学学院学生的课程
(2, '高等代数', '张教授', '数学学院楼201', 1, '08:00:00', '09:40:00', 1, 16, '2024-2', '#409EFF', 1),
(2, '实变函数', '李老师', '数学学院楼301', 3, '14:00:00', '15:40:00', 1, 16, '2024-2', '#67C23A', 1),
(2, '数学建模', '王教授', '计算中心机房', 5, '19:00:00', '21:00:00', 1, 16, '2024-2', '#E6A23C', 1),
-- 计算机学院学生的课程
(3, '数据结构与算法', '赵教授', '信息学部计算机楼', 2, '10:00:00', '11:40:00', 1, 16, '2024-2', '#409EFF', 1),
(3, '软件工程', '钱老师', '信息学部B楼302', 4, '14:00:00', '15:40:00', 1, 16, '2024-2', '#67C23A', 1),
(3, '算法竞赛训练', 'ACM教练', '信息学部机房', 6, '19:30:00', '21:30:00', 1, 16, '2024-2', '#E6A23C', 1),
-- 法学院学生的课程
(4, '民法学', '孙教授', '法学院模拟法庭', 1, '10:00:00', '11:40:00', 1, 16, '2024-2', '#409EFF', 1),
(4, '法理学', '周老师', '法学院研讨室', 3, '15:50:00', '17:30:00', 1, 16, '2024-2', '#67C23A', 1),
-- 经管学院学生的课程
(6, '宏观经济学', '吴教授', '经管大楼B201', 1, '08:00:00', '09:40:00', 1, 16, '2024-2', '#409EFF', 1),
(6, '计量经济学', '郑老师', '经管大楼机房', 2, '10:00:00', '11:40:00', 1, 16, '2024-2', '#67C23A', 1);
-- 7. 插入日程数据
INSERT INTO `schedules` (`user_id`, `title`, `description`, `start_time`, `end_time`, `location`, `is_all_day`, `reminder`, `color`, `status`) VALUES
-- 学习相关日程
(2, '高等代数期末复习', '准备高等代数期末考试,重点复习线性变换和特征值', '2025-01-10 19:00:00', '2025-01-10 22:00:00', '图书馆总馆3楼', 0, 30, '#409EFF', 1),
(3, '算法竞赛训练', 'ACM周赛讲解动态规划专题', '2025-01-12 19:30:00', '2025-01-12 21:30:00', '信息学部机房', 0, 15, '#67C23A', 1),
(4, '模拟法庭准备', '准备"枫叶杯"模拟法庭大赛材料', '2025-01-08 14:00:00', '2025-01-08 17:00:00', '法学院研讨室', 0, 60, '#E6A23C', 1),
-- 社团活动
(14, '诗词社例会', '讨论新学期诗词创作活动安排', '2025-01-15 18:30:00', '2025-01-15 20:00:00', '樱园学生活动中心', 0, 30, '#909399', 1),
(16, '校报编辑部会议', '讨论下期专题策划和采访安排', '2025-01-16 17:00:00', '2025-01-16 18:30:00', '学生事务中心', 0, 15, '#909399', 1),
-- 实习实践
(6, '券商实习面试', '参加XX证券公司实习生面试', '2025-01-20 14:00:00', '2025-01-20 16:00:00', '金融街', 0, 60, '#F56C6C', 1),
(12, '医院见习', '跟随带教老师查房学习', '2025-01-18 07:30:00', '2025-01-18 12:00:00', '人民医院', 0, 120, '#C0C4CC', 1),
-- 个人安排
(2, '期末考试', '高等代数期末考试', '2025-01-25 08:00:00', '2025-01-25 10:00:00', '数学学院楼201', 0, 1440, '#F56C6C', 1),
(3, '项目答辩', '软件工程课程设计项目答辩', '2025-01-22 14:00:00', '2025-01-22 17:00:00', '信息学部B楼', 0, 720, '#E6A23C', 1),
(6, '考研复试准备', '准备经济学研究生复试材料', '2025-01-30 09:00:00', '2025-01-30 18:00:00', '图书馆', 1, 2880, '#9B59B6', 1);

@ -64,4 +64,50 @@
</if> </if>
</where> </where>
</select> </select>
<!-- ========== 管理员后台相关方法 ========== -->
<select id="getTotalCount" resultType="int">
SELECT COUNT(*)
FROM categories
WHERE status != 0
</select>
<select id="getAllCategories" resultMap="categoryResultMap">
SELECT id, name, description, icon, sort, status, created_at, updated_at
FROM categories
ORDER BY sort ASC, id ASC
</select>
<select id="getCategoryById" resultMap="categoryResultMap">
SELECT id, name, description, icon, sort, status, created_at, updated_at
FROM categories
WHERE id = #{id}
</select>
<insert id="insertCategory" parameterType="com.unilife.model.entity.Category" useGeneratedKeys="true" keyProperty="id">
INSERT INTO categories (
name, description, icon, sort, status, created_at, updated_at
) VALUES (
#{name}, #{description}, #{icon}, #{sort}, #{status}, NOW(), NOW()
)
</insert>
<update id="updateCategory" parameterType="com.unilife.model.entity.Category">
UPDATE categories
SET name = #{name},
description = #{description},
icon = #{icon},
sort = #{sort},
status = #{status},
updated_at = NOW()
WHERE id = #{id}
</update>
<update id="deleteCategory">
UPDATE categories
SET status = 0,
updated_at = NOW()
WHERE id = #{categoryId}
</update>
</mapper> </mapper>

@ -72,4 +72,65 @@
SET like_count = GREATEST(like_count - 1, 0) SET like_count = GREATEST(like_count - 1, 0)
WHERE id = #{id} WHERE id = #{id}
</update> </update>
<!-- ========== 管理员后台相关方法 ========== -->
<select id="getTotalCount" resultType="int">
SELECT COUNT(*)
FROM comments
WHERE status != 0
</select>
<select id="getNewCommentCountToday" resultType="int">
SELECT COUNT(*)
FROM comments
WHERE status != 0 AND DATE(created_at) = CURDATE()
</select>
<select id="getCommentById" resultMap="commentResultMap">
SELECT id, post_id, user_id, content, parent_id, like_count, status, created_at, updated_at
FROM comments
WHERE id = #{id}
</select>
<select id="getAdminCommentList" resultMap="commentResultMap">
SELECT c.id, c.post_id, c.user_id, c.content, c.parent_id, c.like_count, c.status, c.created_at, c.updated_at
FROM comments c
<where>
<if test="keyword != null and keyword != ''">
AND c.content LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="postId != null">
AND c.post_id = #{postId}
</if>
<if test="status != null">
AND c.status = #{status}
</if>
</where>
ORDER BY c.created_at DESC
LIMIT #{offset}, #{size}
</select>
<select id="getAdminCommentCount" resultType="int">
SELECT COUNT(*)
FROM comments c
<where>
<if test="keyword != null and keyword != ''">
AND c.content LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test="postId != null">
AND c.post_id = #{postId}
</if>
<if test="status != null">
AND c.status = #{status}
</if>
</where>
</select>
<update id="deleteComment">
UPDATE comments
SET status = 0,
updated_at = NOW()
WHERE id = #{commentId}
</update>
</mapper> </mapper>

@ -155,4 +155,78 @@
</otherwise> </otherwise>
</choose> </choose>
</select> </select>
<!-- ========== 管理员后台相关方法 ========== -->
<select id="getTotalCount" resultType="int">
SELECT COUNT(*)
FROM posts
WHERE status != 0
</select>
<select id="getNewPostCountToday" resultType="int">
SELECT COUNT(*)
FROM posts
WHERE status != 0 AND DATE(created_at) = CURDATE()
</select>
<select id="getPostById" resultMap="postResultMap">
SELECT id, user_id, title, content, category_id, view_count, like_count, comment_count, status, created_at, updated_at
FROM posts
WHERE id = #{id}
</select>
<select id="getAdminPostList" resultMap="postResultMap">
SELECT p.id, p.user_id, p.title, p.content, p.category_id, p.view_count, p.like_count, p.comment_count, p.status, p.created_at, p.updated_at
FROM posts p
<where>
<if test="keyword != null and keyword != ''">
AND (p.title LIKE CONCAT('%', #{keyword}, '%') OR p.content LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="categoryId != null">
AND p.category_id = #{categoryId}
</if>
<if test="status != null">
AND p.status = #{status}
</if>
</where>
ORDER BY p.created_at DESC
LIMIT #{offset}, #{size}
</select>
<select id="getAdminPostCount" resultType="int">
SELECT COUNT(*)
FROM posts p
<where>
<if test="keyword != null and keyword != ''">
AND (p.title LIKE CONCAT('%', #{keyword}, '%') OR p.content LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="categoryId != null">
AND p.category_id = #{categoryId}
</if>
<if test="status != null">
AND p.status = #{status}
</if>
</where>
</select>
<update id="updatePostStatus">
UPDATE posts
SET status = #{status},
updated_at = NOW()
WHERE id = #{postId}
</update>
<update id="deletePost">
UPDATE posts
SET status = 0,
updated_at = NOW()
WHERE id = #{postId}
</update>
<select id="getCountByCategoryId" resultType="int">
SELECT COUNT(*)
FROM posts
WHERE category_id = #{categoryId} AND status != 0
</select>
</mapper> </mapper>

@ -116,4 +116,67 @@
FROM resources FROM resources
WHERE category_id = #{categoryId} AND status != 0 WHERE category_id = #{categoryId} AND status != 0
</select> </select>
<!-- ========== 管理员后台相关方法 ========== -->
<select id="getTotalCount" resultType="int">
SELECT COUNT(*)
FROM resources
WHERE status != 0
</select>
<select id="getNewResourceCountToday" resultType="int">
SELECT COUNT(*)
FROM resources
WHERE status != 0 AND DATE(created_at) = CURDATE()
</select>
<select id="getResourceById" resultMap="resourceResultMap">
SELECT id, user_id, title, description, file_url, file_size, file_type, category_id,
download_count, like_count, status, created_at, updated_at
FROM resources
WHERE id = #{id}
</select>
<select id="getAdminResourceList" resultMap="resourceResultMap">
SELECT r.id, r.user_id, r.title, r.description, r.file_url, r.file_size, r.file_type, r.category_id,
r.download_count, r.like_count, r.status, r.created_at, r.updated_at
FROM resources r
<where>
<if test="keyword != null and keyword != ''">
AND (r.title LIKE CONCAT('%', #{keyword}, '%') OR r.description LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="categoryId != null">
AND r.category_id = #{categoryId}
</if>
<if test="status != null">
AND r.status = #{status}
</if>
</where>
ORDER BY r.created_at DESC
LIMIT #{offset}, #{size}
</select>
<select id="getAdminResourceCount" resultType="int">
SELECT COUNT(*)
FROM resources r
<where>
<if test="keyword != null and keyword != ''">
AND (r.title LIKE CONCAT('%', #{keyword}, '%') OR r.description LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="categoryId != null">
AND r.category_id = #{categoryId}
</if>
<if test="status != null">
AND r.status = #{status}
</if>
</where>
</select>
<update id="deleteResource">
UPDATE resources
SET status = 0,
updated_at = NOW()
WHERE id = #{resourceId}
</update>
</mapper> </mapper>

@ -212,4 +212,83 @@
DELETE FROM comment_likes WHERE user_id = #{userId}; DELETE FROM comment_likes WHERE user_id = #{userId};
DELETE FROM resource_likes WHERE user_id = #{userId}; DELETE FROM resource_likes WHERE user_id = #{userId};
</delete> </delete>
<!-- ========== 管理员后台相关方法 ========== -->
<select id="getTotalCount" resultType="int">
SELECT COUNT(*)
FROM users
</select>
<select id="getActiveUserCount" resultType="int">
SELECT COUNT(*)
FROM users
WHERE status = 1 AND login_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
</select>
<select id="getNewUserCountToday" resultType="int">
SELECT COUNT(*)
FROM users
WHERE status != 0 AND DATE(created_at) = CURDATE()
</select>
<select id="getAdminUserList" resultType="com.unilife.model.entity.User">
SELECT id, username, email, nickname, avatar, bio, gender,
student_id as studentId, department, major, grade, points,
role, status, is_verified as isVerified,
login_ip as loginIp, login_time as loginTime,
created_at as createdAt, updated_at as updatedAt
FROM users
<where>
<if test="keyword != null and keyword != ''">
AND (username LIKE CONCAT('%', #{keyword}, '%')
OR nickname LIKE CONCAT('%', #{keyword}, '%')
OR email LIKE CONCAT('%', #{keyword}, '%')
OR department LIKE CONCAT('%', #{keyword}, '%')
OR major LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="role != null">
AND role = #{role}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
ORDER BY created_at DESC
LIMIT #{offset}, #{size}
</select>
<select id="getAdminUserCount" resultType="int">
SELECT COUNT(*)
FROM users
<where>
<if test="keyword != null and keyword != ''">
AND (username LIKE CONCAT('%', #{keyword}, '%')
OR nickname LIKE CONCAT('%', #{keyword}, '%')
OR email LIKE CONCAT('%', #{keyword}, '%')
OR department LIKE CONCAT('%', #{keyword}, '%')
OR major LIKE CONCAT('%', #{keyword}, '%'))
</if>
<if test="role != null">
AND role = #{role}
</if>
<if test="status != null">
AND status = #{status}
</if>
</where>
</select>
<update id="updateUserStatus">
UPDATE users
SET status = #{status},
updated_at = NOW()
WHERE id = #{userId}
</update>
<update id="updateUserRole">
UPDATE users
SET role = #{role},
updated_at = NOW()
WHERE id = #{userId}
</update>
</mapper> </mapper>

Loading…
Cancel
Save