一、项目背景
随着移动互联网的发展,校园社交网络平台成为高校师生信息交流、资源共享和兴趣互动的重要工具。本文以Java为主要开发语言,介绍一个校园社交网络平台的设计与实现过程,涵盖系统架构、核心功能、技术选型及关键实现细节。
二、需求分析
1. 用户需求
- 学生、教师注册与登录
- 个人信息管理与展示
- 好友添加与分组
- 动态发布与评论
- 消息通知与私信
- 社团/兴趣小组管理
- 活动报名与签到
2. 系统需求
- 支持高并发访问
- 数据安全与隐私保护
- 良好的可扩展性和可维护性
三、系统架构设计
1. 总体架构
采用B/S(浏览器/服务器)架构,前端使用React,后端采用Spring Boot框架,数据库选用MySQL,文件存储采用阿里云OSS或本地服务器。
[客户端] ←→ [Nginx负载均衡] ←→ [Spring Boot服务集群] ←→ [MySQL/Redis/OSS]
2. 主要技术栈
- 后端:Java 17、Spring Boot、Spring Security、MyBatis Plus、Redis、OSS SDK
- 前端:React、Element UI、Axios
- 数据库:MySQL 8.x
- 其他:JWT鉴权、WebSocket实时通信、Docker容器部署
四、核心功能模块
1. 用户与权限管理
- 支持学生、教师多角色注册与登录
- 基于JWT的Token认证机制
- 用户信息完善与隐私设置
2. 好友与社交关系
- 好友申请、同意、分组管理
- 黑名单与举报功能
3. 动态与互动
- 发布图文/多媒体动态
- 点赞、评论、转发
- 热门话题与内容推荐
4. 消息与通知
- 私信聊天(WebSocket实现)
- 系统通知、活动提醒
5. 社团与活动
- 社团/兴趣小组创建与管理
- 活动发布、报名、签到与统计
6. 后台管理
- 用户、内容、社团审核
- 数据统计与系统日志
五、数据库设计
采用E-R建模,主要表结构如下:
- 用户表(user)
- 好友关系表(friend)
- 动态内容表(post)
- 评论表(comment)
- 消息表(message)
- 社团表(club)
- 活动表(activity)
六、关键实现细节
1. JWT鉴权流程
- 用户登录成功后生成JWT Token
- 前端每次请求携带Token
- 后端拦截器验证Token有效性
2. WebSocket实时通信
- 基于Spring Boot内置WebSocket支持
- 实现私信、系统通知的实时推送
3. 文件上传与多媒体处理
- 采用阿里云OSS存储图片、视频等资源
- 后端提供统一上传接口,支持分片上传
七、系统部署与运维
- 使用Docker容器化部署后端服务和数据库
- Nginx实现负载均衡和静态资源代理
- 日志采集与监控(ELK、Prometheus)
八、总结与展望
本文介绍了基于Java的校园社交网络平台的设计与实现思路。未来可进一步集成AI推荐算法、移动端小程序、校园一卡通等功能,持续提升用户体验和系统智能化水平。
源代码:
Directory Content Summary
Source Directory: ./campus-social-network
Directory Structure
campus-social-network/
pom.xml
README.md
db/
schema.sql
frontend/
package.json
src/
api.js
App.js
modules/
ActivityModule.js
ClubModule.js
FriendModule.js
MessageModule.js
PostModule.js
UserModule.js
src/
main/
java/
com/
example/
campus/
CampusSocialNetworkApplication.java
controller/
ActivityController.java
AuthController.java
ClubController.java
FriendController.java
MessageController.java
PostController.java
UserController.java
dto/
AddFriendRequest.java
CreateActivityRequest.java
CreateClubRequest.java
CreateCommentRequest.java
CreatePostRequest.java
JoinClubRequest.java
LoginRequest.java
MoveFriendRequest.java
RegisterRequest.java
SendMessageRequest.java
UpdateProfileRequest.java
entity/
Activity.java
ActivitySignup.java
Club.java
ClubMember.java
Comment.java
Friend.java
Message.java
Post.java
User.java
repository/
ActivityRepository.java
ActivitySignupRepository.java
ClubMemberRepository.java
ClubRepository.java
CommentRepository.java
FriendRepository.java
MessageRepository.java
PostRepository.java
UserRepository.java
service/
ActivityService.java
ClubService.java
FriendService.java
MessageService.java
PostService.java
UserService.java
resources/
application.yml
schema.sql
File Contents
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>campus-social-network</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Campus Social Network</name>
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.7.5</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
README.md
# Campus Social Network
基于Spring Boot的校园社交网络平台示例项目。
## 功能简介
- 学生、教师注册与登录
- 用户信息管理
- 后端接口示例
## 目录结构
campus-social-network/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/campus/
│ │ │ ├── CampusSocialNetworkApplication.java
│ │ │ ├── controller/
│ │ │ │ └── AuthController.java
│ │ │ ├── entity/
│ │ │ │ └── User.java
│ │ │ ├── repository/
│ │ │ │ └── UserRepository.java
│ │ │ ├── service/
│ │ │ │ └── UserService.java
│ │ │ └── dto/
│ │ │ ├── RegisterRequest.java
│ │ │ └── LoginRequest.java
│ │ └── resources/
│ │ ├── application.yml
│ │ └── schema.sql
├── pom.xml
└── README.md
快速开始
- 创建数据库
campus_social_network
,导入src/main/resources/schema.sql
- 修改
application.yml
数据库配置 - 使用IDEA或Maven命令行运行项目
接口示例
- POST
/api/auth/register
注册 - POST
/api/auth/login
登录
### db\schema.sql
```sql
-- 用户表
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
role VARCHAR(20),
email VARCHAR(100),
real_name VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 好友表
CREATE TABLE friend (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
friend_id BIGINT NOT NULL,
group_name VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 帖子表
CREATE TABLE post (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
content VARCHAR(1000) NOT NULL,
image_url VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 评论表
CREATE TABLE comment (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
post_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
content VARCHAR(1000) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 消息表
CREATE TABLE message (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
sender_id BIGINT NOT NULL,
receiver_id BIGINT NOT NULL,
content VARCHAR(1000) NOT NULL,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 社团表
CREATE TABLE club (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL UNIQUE,
description VARCHAR(255),
creator_id BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 社团成员表
CREATE TABLE club_member (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
club_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
role VARCHAR(20) NOT NULL,
UNIQUE(club_id, user_id)
);
-- 活动表
CREATE TABLE activity (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100) NOT NULL,
description VARCHAR(255),
club_id BIGINT,
start_time TIMESTAMP,
end_time TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 活动报名表
CREATE TABLE activity_signup (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
activity_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
checked_in BOOLEAN DEFAULT FALSE,
signup_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
checkin_time TIMESTAMP,
UNIQUE(activity_id, user_id)
);
frontend\package.json
{
"name": "campus-social-network-frontend",
"version": "1.0.0",
"private": true,
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.0",
"axios": "^1.6.0",
"antd": "^5.13.0"
},
"scripts": {
"start": "webpack serve --mode development --open",
"build": "webpack --mode production"
}
}
frontend\src\api.js
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
});
// 请求拦截器
api.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器
api.interceptors.response.use(
response => response,
error => {
if (error.response && error.response.status === 401) {
// 未登录或 token 过期
localStorage.removeItem('token');
window.location.href = '/user/login';
}
return Promise.reject(error);
}
);
// 用户模块 API
export const userApi = {
register: (data) => api.post('/user/register', data),
login: (data) => api.post('/user/login', data),
getProfile: (userId) => api.get(`/user/${userId}`),
updateProfile: (userId, data) => api.put(`/user/${userId}`, data),
};
// 好友模块 API
export const friendApi = {
addFriend: (data) => api.post('/friend/add', data),
listFriends: (userId) => api.get(`/friend/${userId}`),
moveFriend: (data) => api.put('/friend/move', data),
};
// 动态模块 API
export const postApi = {
createPost: (data) => api.post('/post/create', data),
listPosts: (userId) => api.get(`/post/user/${userId}`),
createComment: (data) => api.post('/post/comment', data),
};
// 消息模块 API
export const messageApi = {
sendMessage: (senderId, data) => api.post(`/message/${senderId}/send`, data),
listReceivedMessages: (receiverId) => api.get(`/message/received/${receiverId}`),
listConversation: (userId1, userId2) => api.get(`/message/conversation/${userId1}/${userId2}`),
markAsRead: (messageId) => api.put(`/message/read/${messageId}`),
};
// 社团模块 API
export const clubApi = {
createClub: (data) => api.post('/club/create', data),
listClubs: () => api.get('/club/list'),
listMembers: (clubId) => api.get(`/club/${clubId}/members`),
joinClub: (data) => api.post('/club/join', data),
listClubsByUser: (userId) => api.get(`/club/user/${userId}`),
};
// 活动模块 API
export const activityApi = {
createActivity: (data) => api.post('/activity/create', data),
listAllActivities: () => api.get('/activity/list'),
listActivitiesByClub: (clubId) => api.get(`/activity/club/${clubId}`),
signup: (activityId, userId) => api.post(`/activity/${activityId}/signup/${userId}`),
listSignups: (activityId) => api.get(`/activity/${activityId}/signups`),
checkin: (activityId, userId) => api.put(`/activity/${activityId}/checkin/${userId}`),
};
export default api;
frontend\src\App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import UserModule from './modules/UserModule';
import FriendModule from './modules/FriendModule';
import PostModule from './modules/PostModule';
import MessageModule from './modules/MessageModule';
import ClubModule from './modules/ClubModule';
import ActivityModule from './modules/ActivityModule';
import 'antd/dist/reset.css';
import { Layout, Menu } from 'antd';
const { Header, Content } = Layout;
export default function App() {
return (
<Router>
<Layout style={{ minHeight: '100vh' }}>
<Header>
<Menu theme="dark" mode="horizontal" defaultSelectedKeys={['user']}>
<Menu.Item key="user"><a href="/user">用户</a></Menu.Item>
<Menu.Item key="friend"><a href="/friend">好友</a></Menu.Item>
<Menu.Item key="post"><a href="/post">动态</a></Menu.Item>
<Menu.Item key="message"><a href="/message">消息</a></Menu.Item>
<Menu.Item key="club"><a href="/club">社团</a></Menu.Item>
<Menu.Item key="activity"><a href="/activity">活动</a></Menu.Item>
</Menu>
</Header>
<Content style={{ padding: 24 }}>
<Routes>
<Route path="/user/*" element={<UserModule />} />
<Route path="/friend/*" element={<FriendModule />} />
<Route path="/post/*" element={<PostModule />} />
<Route path="/message/*" element={<MessageModule />} />
<Route path="/club/*" element={<ClubModule />} />
<Route path="/activity/*" element={<ActivityModule />} />
<Route path="*" element={<Navigate to="/user" />} />
</Routes>
</Content>
</Layout>
</Router>
);
}
frontend\src\modules\ActivityModule.js
import React, { useState, useEffect } from 'react';
import { Card, List, Avatar, Button, Input, Form, Modal, message, Tabs, Typography, Divider, Tag, DatePicker, Space } from 'antd';
import { CalendarOutlined, UserOutlined, PlusOutlined, CheckOutlined, TeamOutlined, ScheduleOutlined } from '@ant-design/icons';
import { activityApi, clubApi } from '../api';
import moment from 'moment';
const { TabPane } = Tabs;
const { Title, Text, Paragraph } = Typography;
const { TextArea } = Input;
const { RangePicker } = DatePicker;
export default function ActivityModule() {
const [activities, setActivities] = useState([]);
const [clubActivities, setClubActivities] = useState([]);
const [loading, setLoading] = useState(true);
const [createModalVisible, setCreateModalVisible] = useState(false);
const [signupModalVisible, setSignupModalVisible] = useState(false);
const [checkinModalVisible, setCheckinModalVisible] = useState(false);
const [selectedActivity, setSelectedActivity] = useState(null);
const [signups, setSignups] = useState([]);
const [signupsModalVisible, setSignupsModalVisible] = useState(false);
const [clubs, setClubs] = useState([]);
const [myClubs, setMyClubs] = useState([]);
const [createForm] = Form.useForm();
const userId = localStorage.getItem('userId');
// 获取活动列表
const fetchActivities = async () => {
try {
setLoading(true);
const response = await activityApi.listAllActivities();
setActivities(response.data || []);
} catch (error) {
message.error('获取活动列表失败');
} finally {
setLoading(false);
}
};
// 获取我的社团
const fetchMyClubs = async () => {
try {
const response = await clubApi.listClubsByUser(userId);
setMyClubs(response.data || []);
// 获取社团列表
const clubsResponse = await clubApi.listClubs();
setClubs(clubsResponse.data || []);
// 获取我的社团的活动
const clubIds = response.data.map(item => item.clubId);
const activitiesPromises = clubIds.map(clubId => activityApi.listActivitiesByClub(clubId));
const activitiesResponses = await Promise.all(activitiesPromises);
const allClubActivities = activitiesResponses.flatMap(res => res.data || []);
setClubActivities(allClubActivities);
} catch (error) {
message.error('获取社团活动失败');
}
};
useEffect(() => {
fetchActivities();
if (userId) {
fetchMyClubs();
}
}, [userId]);
// 创建活动
const handleCreateActivity = async (values) => {
try {
const { timeRange, ...rest } = values;
await activityApi.createActivity({
...rest,
startTime: timeRange[0].format(),
endTime: timeRange[1].format(),
});
message.success('创建活动成功');
setCreateModalVisible(false);
createForm.resetFields();
fetchActivities();
fetchMyClubs(); // 刷新社团活动
} catch (error) {
message.error('创建活动失败');
}
};
// 活动报名
const handleSignup = async () => {
try {
await activityApi.signup(selectedActivity.id, userId);
message.success('报名成功');
setSignupModalVisible(false);
} catch (error) {
message.error(error.response?.data?.message || '报名失败');
}
};
// 活动签到
const handleCheckin = async () => {
try {
await activityApi.checkin(selectedActivity.id, userId);
message.success('签到成功');
setCheckinModalVisible(false);
} catch (error) {
message.error(error.response?.data?.message || '签到失败');
}
};
// 查看报名名单
const viewSignups = async (activity) => {
try {
setSelectedActivity(activity);
setLoading(true);
const response = await activityApi.listSignups(activity.id);
setSignups(response.data || []);
setSignupsModalVisible(true);
} catch (error) {
message.error('获取报名名单失败');
} finally {
setLoading(false);
}
};
// 获取社团名称
const getClubName = (clubId) => {
const club = clubs.find(c => c.id === clubId);
return club ? club.name : `社团 ${clubId}`;
};
return (
<div>
<Tabs defaultActiveKey="all">
<TabPane
tab={<span><CalendarOutlined />所有活动</span>}
key="all"
>
<Card
title="活动列表"
extra={
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setCreateModalVisible(true)}
>
创建活动
</Button>
}
>
<List
loading={loading}
grid={{ gutter: 16, column: 3 }}
dataSource={activities}
renderItem={activity => (
<List.Item>
<Card
hoverable
actions={[
<Button
onClick={() => {
setSelectedActivity(activity);
setSignupModalVisible(true);
}}
>
报名
</Button>,
<Button
onClick={() => {
setSelectedActivity(activity);
setCheckinModalVisible(true);
}}
>
签到
</Button>,
<Button onClick={() => viewSignups(activity)}>名单</Button>
]}
>
<Card.Meta
avatar={<Avatar icon={<ScheduleOutlined />} />}
title={activity.title}
description={
<div>
<Paragraph ellipsis={{ rows: 2 }}>
{activity.description || '暂无描述'}
</Paragraph>
<div>社团: {getClubName(activity.clubId)}</div>
<div>开始: {new Date(activity.startTime).toLocaleString()}</div>
<div>结束: {new Date(activity.endTime).toLocaleString()}</div>
</div>
}
/>
</Card>
</List.Item>
)}
/>
</Card>
</TabPane>
<TabPane
tab={<span><TeamOutlined />社团活动</span>}
key="club"
>
<Card title="我的社团活动">
<List
loading={loading}
grid={{ gutter: 16, column: 3 }}
dataSource={clubActivities}
renderItem={activity => (
<List.Item>
<Card
hoverable
actions={[
<Button
onClick={() => {
setSelectedActivity(activity);
setSignupModalVisible(true);
}}
>
报名
</Button>,
<Button
onClick={() => {
setSelectedActivity(activity);
setCheckinModalVisible(true);
}}
>
签到
</Button>,
<Button onClick={() => viewSignups(activity)}>名单</Button>
]}
>
<Card.Meta
avatar={<Avatar icon={<ScheduleOutlined />} />}
title={activity.title}
description={
<div>
<Paragraph ellipsis={{ rows: 2 }}>
{activity.description || '暂无描述'}
</Paragraph>
<div>社团: {getClubName(activity.clubId)}</div>
<div>开始: {new Date(activity.startTime).toLocaleString()}</div>
<div>结束: {new Date(activity.endTime).toLocaleString()}</div>
</div>
}
/>
</Card>
</List.Item>
)}
/>
</Card>
</TabPane>
</Tabs>
{/* 创建活动对话框 */}
<Modal
title="创建新活动"
open={createModalVisible}
onCancel={() => setCreateModalVisible(false)}
footer={null}
>
<Form form={createForm} onFinish={handleCreateActivity} layout="vertical">
<Form.Item
name="title"
label="活动名称"
rules={[{ required: true, message: '请输入活动名称' }]}
>
<Input placeholder="请输入活动名称" />
</Form.Item>
<Form.Item
name="description"
label="活动描述"
>
<TextArea rows={4} placeholder="请输入活动描述" />
</Form.Item>
<Form.Item
name="clubId"
label="所属社团"
rules={[{ required: true, message: '请选择所属社团' }]}
>
<Select placeholder="请选择所属社团">
{myClubs.map(item => {
const club = clubs.find(c => c.id === item.clubId) || {};
return (
<Select.Option key={item.clubId} value={item.clubId}>
{club.name || `社团 ${item.clubId}`}
</Select.Option>
);
})}
</Select>
</Form.Item>
<Form.Item
name="timeRange"
label="活动时间"
rules={[{ required: true, message: '请选择活动时间范围' }]}
>
<RangePicker showTime format="YYYY-MM-DD HH:mm:ss" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block>
创建
</Button>
</Form.Item>
</Form>
</Modal>
{/* 活动报名对话框 */}
<Modal
title={`报名活动: ${selectedActivity?.title}`}
open={signupModalVisible}
onCancel={() => setSignupModalVisible(false)}
footer={null}
>
<Paragraph>确定要报名参加该活动吗?</Paragraph>
<Button type="primary" onClick={handleSignup} block>
确认报名
</Button>
</Modal>
{/* 活动签到对话框 */}
<Modal
title={`活动签到: ${selectedActivity?.title}`}
open={checkinModalVisible}
onCancel={() => setCheckinModalVisible(false)}
footer={null}
>
<Paragraph>确定要签到该活动吗?</Paragraph>
<Button type="primary" onClick={handleCheckin} block>
确认签到
</Button>
</Modal>
{/* 查看报名名单对话框 */}
<Modal
title={`${selectedActivity?.title} 的报名名单`}
open={signupsModalVisible}
onCancel={() => setSignupsModalVisible(false)}
footer={null}
width={600}
>
<List
loading={loading}
dataSource={signups}
renderItem={signup => (
<List.Item>
<List.Item.Meta
avatar={<Avatar icon={<UserOutlined />} />}
title={signup.username || `用户 ${signup.userId}`}
description={
<div>
<div>报名时间: {new Date(signup.signupTime).toLocaleString()}</div>
<div>
签到状态:
{signup.checkedIn ? (
<Tag color="green">已签到 ({new Date(signup.checkinTime).toLocaleString()})</Tag>
) : (
<Tag color="red">未签到</Tag>
)}
</div>
</div>
}
/>
</List.Item>
)}
/>
</Modal>
</div>
);
}
frontend\src\modules\ClubModule.js
import React, { useState, useEffect } from 'react';
import { Card, List, Avatar, Button, Input, Form, Modal, message, Tabs, Typography, Divider, Tag } from 'antd';
import { UserOutlined, TeamOutlined, PlusOutlined, HomeOutlined } from '@ant-design/icons';
import { clubApi } from '../api';
const { TabPane } = Tabs;
const { Title, Text, Paragraph } = Typography;
const { TextArea } = Input;
export default function ClubModule() {
const [clubs, setClubs] = useState([]);
const [myClubs, setMyClubs] = useState([]);
const [loading, setLoading] = useState(true);
const [createModalVisible, setCreateModalVisible] = useState(false);
const [joinModalVisible, setJoinModalVisible] = useState(false);
const [selectedClub, setSelectedClub] = useState(null);
const [members, setMembers] = useState([]);
const [membersModalVisible, setMembersModalVisible] = useState(false);
const [createForm] = Form.useForm();
const [joinForm] = Form.useForm();
const userId = localStorage.getItem('userId');
// 获取社团列表
const fetchClubs = async () => {
try {
setLoading(true);
const response = await clubApi.listClubs();
setClubs(response.data || []);
} catch (error) {
message.error('获取社团列表失败');
} finally {
setLoading(false);
}
};
// 获取我的社团
const fetchMyClubs = async () => {
try {
const response = await clubApi.listClubsByUser(userId);
setMyClubs(response.data || []);
} catch (error) {
message.error('获取我的社团失败');
}
};
useEffect(() => {
fetchClubs();
if (userId) {
fetchMyClubs();
}
}, [userId]);
// 创建社团
const handleCreateClub = async (values) => {
try {
await clubApi.createClub({
...values,
creatorId: userId
});
message.success('创建社团成功');
setCreateModalVisible(false);
createForm.resetFields();
fetchClubs();
fetchMyClubs();
} catch (error) {
message.error(error.response?.data?.message || '创建社团失败');
}
};
// 加入社团
const handleJoinClub = async (values) => {
try {
await clubApi.joinClub({
clubId: selectedClub.id,
userId: userId
});
message.success('加入社团成功');
setJoinModalVisible(false);
joinForm.resetFields();
fetchMyClubs();
} catch (error) {
message.error(error.response?.data?.message || '加入社团失败');
}
};
// 查看社团成员
const viewMembers = async (club) => {
try {
setSelectedClub(club);
setLoading(true);
const response = await clubApi.listMembers(club.id);
setMembers(response.data || []);
setMembersModalVisible(true);
} catch (error) {
message.error('获取社团成员失败');
} finally {
setLoading(false);
}
};
return (
<div>
<Tabs defaultActiveKey="all">
<TabPane
tab={<span><HomeOutlined />所有社团</span>}
key="all"
>
<Card
title="社团列表"
extra={
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setCreateModalVisible(true)}
>
创建社团
</Button>
}
>
<List
loading={loading}
grid={{ gutter: 16, column: 3 }}
dataSource={clubs}
renderItem={club => (
<List.Item>
<Card
hoverable
actions={[
<Button
onClick={() => {
setSelectedClub(club);
setJoinModalVisible(true);
}}
>
加入
</Button>,
<Button onClick={() => viewMembers(club)}>成员</Button>
]}
>
<Card.Meta
avatar={<Avatar icon={<TeamOutlined />} />}
title={club.name}
description={
<div>
<Paragraph ellipsis={{ rows: 2 }}>
{club.description || '暂无描述'}
</Paragraph>
<div>创建于: {new Date(club.createdAt).toLocaleDateString()}</div>
</div>
}
/>
</Card>
</List.Item>
)}
/>
</Card>
</TabPane>
<TabPane
tab={<span><TeamOutlined />我的社团</span>}
key="my"
>
<Card title="我加入的社团">
<List
loading={loading}
grid={{ gutter: 16, column: 3 }}
dataSource={myClubs}
renderItem={item => {
const club = clubs.find(c => c.id === item.clubId) || {};
return (
<List.Item>
<Card
hoverable
actions={[
<Button onClick={() => viewMembers({ id: item.clubId, name: club.name })}>成员</Button>
]}
>
<Card.Meta
avatar={<Avatar icon={<TeamOutlined />} />}
title={
<div>
{club.name || `社团 ${item.clubId}`}
<Tag color={item.role === 'president' ? 'gold' : 'blue'} style={{ marginLeft: 8 }}>
{item.role === 'president' ? '会长' : '成员'}
</Tag>
</div>
}
description={club.description || '暂无描述'}
/>
</Card>
</List.Item>
);
}}
/>
</Card>
</TabPane>
</Tabs>
{/* 创建社团对话框 */}
<Modal
title="创建新社团"
open={createModalVisible}
onCancel={() => setCreateModalVisible(false)}
footer={null}
>
<Form form={createForm} onFinish={handleCreateClub} layout="vertical">
<Form.Item
name="name"
label="社团名称"
rules={[{ required: true, message: '请输入社团名称' }]}
>
<Input placeholder="请输入社团名称" />
</Form.Item>
<Form.Item
name="description"
label="社团描述"
>
<TextArea rows={4} placeholder="请输入社团描述" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block>
创建
</Button>
</Form.Item>
</Form>
</Modal>
{/* 加入社团对话框 */}
<Modal
title={`加入社团: ${selectedClub?.name}`}
open={joinModalVisible}
onCancel={() => setJoinModalVisible(false)}
footer={null}
>
<Form form={joinForm} onFinish={handleJoinClub} layout="vertical">
<Paragraph>确定要加入该社团吗?</Paragraph>
<Form.Item>
<Button type="primary" htmlType="submit" block>
确认加入
</Button>
</Form.Item>
</Form>
</Modal>
{/* 查看成员对话框 */}
<Modal
title={`${selectedClub?.name} 的成员`}
open={membersModalVisible}
onCancel={() => setMembersModalVisible(false)}
footer={null}
width={600}
>
<List
loading={loading}
dataSource={members}
renderItem={member => (
<List.Item>
<List.Item.Meta
avatar={<Avatar icon={<UserOutlined />} />}
title={
<div>
{member.username || `用户 ${member.userId}`}
<Tag color={member.role === 'president' ? 'gold' : 'blue'} style={{ marginLeft: 8 }}>
{member.role === 'president' ? '会长' : '成员'}
</Tag>
</div>
}
/>
</List.Item>
)}
/>
</Modal>
</div>
);
}
frontend\src\modules\FriendModule.js
import React, { useState, useEffect } from 'react';
import { Card, List, Avatar, Button, Input, Modal, Form, Select, Tabs, message, Typography, Divider } from 'antd';
import { UserOutlined, UserAddOutlined, TeamOutlined } from '@ant-design/icons';
import { friendApi } from '../api';
const { TabPane } = Tabs;
const { Title, Text } = Typography;
export default function FriendModule() {
const [friends, setFriends] = useState([]);
const [loading, setLoading] = useState(true);
const [addModalVisible, setAddModalVisible] = useState(false);
const [moveModalVisible, setMoveModalVisible] = useState(false);
const [selectedFriend, setSelectedFriend] = useState(null);
const [groups, setGroups] = useState(['default', '学习', '生活', '兴趣']);
const [addForm] = Form.useForm();
const [moveForm] = Form.useForm();
const userId = localStorage.getItem('userId');
// 获取好友列表
const fetchFriends = async () => {
try {
setLoading(true);
const response = await friendApi.listFriends(userId);
setFriends(response.data || []);
} catch (error) {
message.error('获取好友列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (userId) {
fetchFriends();
}
}, [userId]);
// 添加好友
const handleAddFriend = async (values) => {
try {
await friendApi.addFriend({
userId: userId,
friendId: values.friendId,
groupName: values.groupName || 'default'
});
message.success('添加好友成功');
fetchFriends();
setAddModalVisible(false);
addForm.resetFields();
} catch (error) {
message.error(error.response?.data?.message || '添加好友失败');
}
};
// 移动好友到其他分组
const handleMoveFriend = async (values) => {
try {
await friendApi.moveFriend({
id: selectedFriend.id,
groupName: values.groupName
});
message.success('移动好友成功');
fetchFriends();
setMoveModalVisible(false);
moveForm.resetFields();
} catch (error) {
message.error('移动好友失败');
}
};
// 按分组组织好友
const groupedFriends = friends.reduce((acc, friend) => {
const group = friend.groupName || 'default';
if (!acc[group]) acc[group] = [];
acc[group].push(friend);
return acc;
}, {});
return (
<div>
<Card
title="好友管理"
extra={
<Button
type="primary"
icon={<UserAddOutlined />}
onClick={() => setAddModalVisible(true)}
>
添加好友
</Button>
}
>
<Tabs defaultActiveKey="all">
<TabPane tab="全部好友" key="all">
<List
loading={loading}
dataSource={friends}
renderItem={item => (
<List.Item
actions={[
<Button
onClick={() => {
setSelectedFriend(item);
setMoveModalVisible(true);
}}
>
移动到分组
</Button>
]}
>
<List.Item.Meta
avatar={<Avatar icon={<UserOutlined />} />}
title={item.friendUsername}
description={`分组: ${item.groupName || '默认'}`}
/>
</List.Item>
)}
/>
</TabPane>
{Object.keys(groupedFriends).map(group => (
<TabPane tab={group === 'default' ? '默认分组' : group} key={group}>
<List
loading={loading}
dataSource={groupedFriends[group]}
renderItem={item => (
<List.Item
actions={[
<Button
onClick={() => {
setSelectedFriend(item);
setMoveModalVisible(true);
}}
>
移动到分组
</Button>
]}
>
<List.Item.Meta
avatar={<Avatar icon={<UserOutlined />} />}
title={item.friendUsername}
/>
</List.Item>
)}
/>
</TabPane>
))}
</Tabs>
</Card>
{/* 添加好友对话框 */}
<Modal
title="添加好友"
open={addModalVisible}
onCancel={() => setAddModalVisible(false)}
footer={null}
>
<Form form={addForm} onFinish={handleAddFriend} layout="vertical">
<Form.Item
name="friendId"
label="好友 ID"
rules={[{ required: true, message: '请输入好友 ID' }]}
>
<Input placeholder="请输入好友的用户 ID" />
</Form.Item>
<Form.Item name="groupName" label="分组">
<Select placeholder="请选择分组">
{groups.map(group => (
<Select.Option key={group} value={group}>
{group === 'default' ? '默认分组' : group}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block>
添加
</Button>
</Form.Item>
</Form>
</Modal>
{/* 移动好友对话框 */}
<Modal
title="移动好友到分组"
open={moveModalVisible}
onCancel={() => setMoveModalVisible(false)}
footer={null}
>
<Form form={moveForm} onFinish={handleMoveFriend} layout="vertical">
<Form.Item name="groupName" label="选择分组" rules={[{ required: true, message: '请选择分组' }]}>
<Select placeholder="请选择分组">
{groups.map(group => (
<Select.Option key={group} value={group}>
{group === 'default' ? '默认分组' : group}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block>
移动
</Button>
</Form.Item>
</Form>
</Modal>
</div>
);
}
frontend\src\modules\MessageModule.js
import React, { useState, useEffect } from 'react';
import { Card, List, Avatar, Button, Input, Form, message, Tabs, Badge, Typography, Divider } from 'antd';
import { UserOutlined, MessageOutlined, SendOutlined, BellOutlined } from '@ant-design/icons';
import { messageApi } from '../api';
const { TabPane } = Tabs;
const { TextArea } = Input;
const { Title, Text } = Typography;
export default function MessageModule() {
const [messages, setMessages] = useState([]);
const [loading, setLoading] = useState(true);
const [selectedUser, setSelectedUser] = useState(null);
const [conversationVisible, setConversationVisible] = useState(false);
const [conversation, setConversation] = useState([]);
const [form] = Form.useForm();
const userId = localStorage.getItem('userId');
// 获取消息列表
const fetchMessages = async () => {
try {
setLoading(true);
const response = await messageApi.listReceivedMessages(userId);
setMessages(response.data || []);
} catch (error) {
message.error('获取消息列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (userId) {
fetchMessages();
}
}, [userId]);
// 获取与特定用户的会话
const fetchConversation = async (otherUserId) => {
try {
setLoading(true);
const response = await messageApi.listConversation(userId, otherUserId);
setConversation(response.data || []);
} catch (error) {
message.error('获取会话失败');
} finally {
setLoading(false);
}
};
// 标记消息为已读
const markAsRead = async (messageId) => {
try {
await messageApi.markAsRead(messageId);
fetchMessages(); // 刷新消息列表
} catch (error) {
message.error('标记已读失败');
}
};
// 发送消息
const handleSendMessage = async (values) => {
try {
await messageApi.sendMessage(userId, {
receiverId: selectedUser.senderId, // 对方ID
content: values.content
});
message.success('发送消息成功');
form.resetFields();
// 刷新会话
fetchConversation(selectedUser.senderId);
} catch (error) {
message.error('发送消息失败');
}
};
// 查看会话
const viewConversation = (msg) => {
setSelectedUser(msg);
fetchConversation(msg.senderId);
setConversationVisible(true);
// 如果是未读消息,标记为已读
if (!msg.isRead) {
markAsRead(msg.id);
}
};
// 计算未读消息数
const unreadCount = messages.filter(msg => !msg.isRead).length;
return (
<div>
<Tabs defaultActiveKey="inbox">
<TabPane
tab={
<span>
<BellOutlined />
消息通知 {unreadCount > 0 && <Badge count={unreadCount} />}
</span>
}
key="inbox"
>
<Card title="消息列表">
<List
loading={loading}
itemLayout="horizontal"
dataSource={messages}
renderItem={msg => (
<List.Item
actions={[
<Button
type="primary"
onClick={() => viewConversation(msg)}
>
查看
</Button>
]}
>
<List.Item.Meta
avatar={<Avatar icon={<UserOutlined />} />}
title={
<div>
<span>{msg.senderUsername || `用户 ${msg.senderId}`}</span>
{!msg.isRead && <Badge color="blue" style={{ marginLeft: 8 }} />}
</div>
}
description={<div style={{ width: '300px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{msg.content}</div>}
/>
<div>{new Date(msg.createdAt).toLocaleString()}</div>
</List.Item>
)}
/>
</Card>
</TabPane>
</Tabs>
{/* 会话对话框 */}
{conversationVisible && selectedUser && (
<Card
title={`与 ${selectedUser.senderUsername || `用户 ${selectedUser.senderId}`} 的对话`}
style={{ marginTop: 20 }}
extra={<Button onClick={() => setConversationVisible(false)}>关闭</Button>}
>
<div style={{ height: '300px', overflowY: 'auto', marginBottom: 16, padding: 16, background: '#f5f5f5', borderRadius: 4 }}>
{conversation.map((msg, index) => (
<div
key={index}
style={{
display: 'flex',
justifyContent: msg.senderId.toString() === userId.toString() ? 'flex-end' : 'flex-start',
marginBottom: 16
}}
>
<div
style={{
background: msg.senderId.toString() === userId.toString() ? '#1890ff' : '#fff',
color: msg.senderId.toString() === userId.toString() ? '#fff' : '#000',
padding: '8px 12px',
borderRadius: 8,
maxWidth: '70%',
boxShadow: '0 1px 2px rgba(0,0,0,0.1)'
}}
>
<div>{msg.content}</div>
<div style={{ fontSize: '12px', marginTop: 4, opacity: 0.7 }}>
{new Date(msg.createdAt).toLocaleString()}
</div>
</div>
</div>
))}
</div>
<Form form={form} onFinish={handleSendMessage}>
<Form.Item
name="content"
rules={[{ required: true, message: '请输入消息内容' }]}
>
<TextArea rows={3} placeholder="输入消息..." />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" icon={<SendOutlined />}>
发送
</Button>
</Form.Item>
</Form>
</Card>
)}
</div>
);
}
frontend\src\modules\PostModule.js
import React, { useState, useEffect } from 'react';
import { Card, List, Avatar, Button, Input, Form, message, Divider, Upload, Modal, Image, Typography, Comment } from 'antd';
import { UserOutlined, PictureOutlined, SendOutlined, CommentOutlined, LikeOutlined } from '@ant-design/icons';
import { postApi } from '../api';
const { TextArea } = Input;
const { Title, Text, Paragraph } = Typography;
export default function PostModule() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [commentForm] = Form.useForm();
const [selectedPost, setSelectedPost] = useState(null);
const [commentModalVisible, setCommentModalVisible] = useState(false);
const userId = localStorage.getItem('userId');
// 获取动态列表
const fetchPosts = async () => {
try {
setLoading(true);
const response = await postApi.listPosts(userId);
setPosts(response.data || []);
} catch (error) {
message.error('获取动态列表失败');
} finally {
setLoading(false);
}
};
useEffect(() => {
if (userId) {
fetchPosts();
}
}, [userId]);
// 发布新动态
const handleCreatePost = async (values) => {
try {
await postApi.createPost({
userId: userId,
content: values.content,
imageUrl: values.imageUrl || ''
});
message.success('发布动态成功');
fetchPosts();
form.resetFields();
} catch (error) {
message.error('发布动态失败');
}
};
// 发表评论
const handleComment = async (values) => {
try {
await postApi.createComment({
postId: selectedPost.id,
userId: userId,
content: values.content
});
message.success('评论成功');
setCommentModalVisible(false);
commentForm.resetFields();
fetchPosts(); // 刷新帖子列表
} catch (error) {
message.error('评论失败');
}
};
// 创建新动态表单
const [form] = Form.useForm();
return (
<div>
{/* 发布新动态 */}
<Card title="发布新动态">
<Form form={form} onFinish={handleCreatePost} layout="vertical">
<Form.Item
name="content"
rules={[{ required: true, message: '请输入动态内容' }]}
>
<TextArea rows={4} placeholder="分享你的想法..." />
</Form.Item>
<Form.Item name="imageUrl">
<Input placeholder="图片URL(可选)" prefix={<PictureOutlined />} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" icon={<SendOutlined />}>
发布
</Button>
</Form.Item>
</Form>
</Card>
<Divider />
{/* 动态列表 */}
<Card title="动态列表">
<List
loading={loading}
itemLayout="vertical"
dataSource={posts}
renderItem={post => (
<List.Item
key={post.id}
actions={[
<Button
icon={<CommentOutlined />}
onClick={() => {
setSelectedPost(post);
setCommentModalVisible(true);
}}
>
评论
</Button>,
<Button icon={<LikeOutlined />}>点赞</Button>
]}
>
<List.Item.Meta
avatar={<Avatar icon={<UserOutlined />} />}
title={post.username || `用户 ${post.userId}`}
description={new Date(post.createdAt).toLocaleString()}
/>
<Paragraph>
{post.content}
</Paragraph>
{post.imageUrl && (
<div style={{ marginTop: 16 }}>
<Image
width={200}
src={post.imageUrl}
alt="动态图片"
fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="
/>
</div>
)}
{/* 显示评论 */}
{post.comments && post.comments.length > 0 && (
<div style={{ marginTop: 16, background: '#f9f9f9', padding: 16, borderRadius: 4 }}>
<Text strong>评论:</Text>
<List
itemLayout="horizontal"
dataSource={post.comments}
renderItem={comment => (
<Comment
author={comment.username || `用户 ${comment.userId}`}
avatar={<Avatar icon={<UserOutlined />} />}
content={comment.content}
datetime={new Date(comment.createdAt).toLocaleString()}
/>
)}
/>
</div>
)}
</List.Item>
)}
/>
</Card>
{/* 评论对话框 */}
<Modal
title="发表评论"
open={commentModalVisible}
onCancel={() => setCommentModalVisible(false)}
footer={null}
>
<Form form={commentForm} onFinish={handleComment} layout="vertical">
<Form.Item
name="content"
rules={[{ required: true, message: '请输入评论内容' }]}
>
<TextArea rows={4} placeholder="输入你的评论..." />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block>
提交评论
</Button>
</Form.Item>
</Form>
</Modal>
</div>
);
}
frontend\src\modules\UserModule.js
import React, { useState, useEffect } from 'react';
import { Routes, Route, useNavigate } from 'react-router-dom';
import { Form, Input, Button, Card, message, Tabs, Avatar, Typography } from 'antd';
import { UserOutlined, LockOutlined, MailOutlined, IdcardOutlined } from '@ant-design/icons';
import { userApi } from '../api';
const { Title, Text } = Typography;
// 登录页面
function Login() {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const onFinish = async (values) => {
try {
setLoading(true);
const response = await userApi.login(values);
localStorage.setItem('token', response.data.token);
localStorage.setItem('userId', response.data.id);
message.success('登录成功');
navigate('/user/profile');
} catch (error) {
message.error(error.response?.data?.message || '登录失败');
} finally {
setLoading(false);
}
};
return (
<Card title="用户登录" style={{ maxWidth: 400, margin: '0 auto' }}>
<Form name="login" onFinish={onFinish}>
<Form.Item
name="username"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input prefix={<UserOutlined />} placeholder="用户名" />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password prefix={<LockOutlined />} placeholder="密码" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block loading={loading}>
登录
</Button>
<div style={{ marginTop: 16, textAlign: 'center' }}>
<a onClick={() => navigate('/user/register')}>没有账号?立即注册</a>
</div>
</Form.Item>
</Form>
</Card>
);
}
// 注册页面
function Register() {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const onFinish = async (values) => {
try {
setLoading(true);
await userApi.register(values);
message.success('注册成功,请登录');
navigate('/user/login');
} catch (error) {
message.error(error.response?.data?.message || '注册失败');
} finally {
setLoading(false);
}
};
return (
<Card title="用户注册" style={{ maxWidth: 400, margin: '0 auto' }}>
<Form name="register" onFinish={onFinish}>
<Form.Item
name="username"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input prefix={<UserOutlined />} placeholder="用户名" />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password prefix={<LockOutlined />} placeholder="密码" />
</Form.Item>
<Form.Item
name="email"
rules={[
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' }
]}
>
<Input prefix={<MailOutlined />} placeholder="邮箱" />
</Form.Item>
<Form.Item
name="realName"
rules={[{ required: true, message: '请输入真实姓名' }]}
>
<Input prefix={<IdcardOutlined />} placeholder="真实姓名" />
</Form.Item>
<Form.Item
name="role"
initialValue="student"
hidden
>
<Input />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block loading={loading}>
注册
</Button>
<div style={{ marginTop: 16, textAlign: 'center' }}>
<a onClick={() => navigate('/user/login')}>已有账号?立即登录</a>
</div>
</Form.Item>
</Form>
</Card>
);
}
// 个人资料页面
function Profile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const userId = localStorage.getItem('userId');
useEffect(() => {
const fetchProfile = async () => {
try {
const response = await userApi.getProfile(userId);
setUser(response.data);
} catch (error) {
message.error('获取个人资料失败');
} finally {
setLoading(false);
}
};
if (userId) {
fetchProfile();
}
}, [userId]);
if (loading) return <div>加载中...</div>;
if (!user) return <div>请先登录</div>;
return (
<Card title="个人资料" style={{ maxWidth: 600, margin: '0 auto' }}>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: 20 }}>
<Avatar size={64} icon={<UserOutlined />} />
<div style={{ marginLeft: 20 }}>
<Title level={4}>{user.username}</Title>
<Text type="secondary">{user.role === 'student' ? '学生' : '教师'}</Text>
</div>
</div>
<Form layout="vertical">
<Form.Item label="真实姓名">
<Input value={user.realName} readOnly />
</Form.Item>
<Form.Item label="邮箱">
<Input value={user.email} readOnly />
</Form.Item>
</Form>
</Card>
);
}
// 用户模块主组件
export default function UserModule() {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/profile" element={<Profile />} />
<Route path="*" element={<Login />} />
</Routes>
);
}
src\main\java\com\example\campus\CampusSocialNetworkApplication.java
package com.example.campus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CampusSocialNetworkApplication {
public static void main(String[] args) {
SpringApplication.run(CampusSocialNetworkApplication.class, args);
}
}
src\main\java\com\example\campus\controller\ActivityController.java
package com.example.campus.controller;
import com.example.campus.dto.CreateActivityRequest;
import com.example.campus.entity.Activity;
import com.example.campus.entity.ActivitySignup;
import com.example.campus.service.ActivityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/activity")
public class ActivityController {
@Autowired
private ActivityService activityService;
// 创建活动
@PostMapping("/create")
public Activity createActivity(@RequestBody CreateActivityRequest req) {
return activityService.createActivity(req);
}
// 查询所有活动
@GetMapping("/list")
public List<Activity> listAllActivities() {
return activityService.listAllActivities();
}
// 查询社团的活动
@GetMapping("/club/{clubId}")
public List<Activity> listActivitiesByClub(@PathVariable Long clubId) {
return activityService.listActivitiesByClub(clubId);
}
// 活动报名
@PostMapping("/{activityId}/signup/{userId}")
public ActivitySignup signup(@PathVariable Long activityId, @PathVariable Long userId) {
return activityService.signup(activityId, userId);
}
// 查看活动报名名单
@GetMapping("/{activityId}/signups")
public List<ActivitySignup> listSignups(@PathVariable Long activityId) {
return activityService.listSignups(activityId);
}
// 活动签到
@PutMapping("/{activityId}/checkin/{userId}")
public ActivitySignup checkin(@PathVariable Long activityId, @PathVariable Long userId) {
return activityService.checkin(activityId, userId);
}
}
src\main\java\com\example\campus\controller\AuthController.java
package com.example.campus.controller;
import com.example.campus.dto.RegisterRequest;
import com.example.campus.dto.LoginRequest;
import com.example.campus.entity.User;
import com.example.campus.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserService userService;
@PostMapping("/register")
public User register(@RequestBody RegisterRequest request) {
return userService.register(request);
}
@PostMapping("/login")
public User login(@RequestBody LoginRequest request) {
return userService.login(request);
}
}
src\main\java\com\example\campus\controller\ClubController.java
package com.example.campus.controller;
import com.example.campus.dto.CreateClubRequest;
import com.example.campus.dto.JoinClubRequest;
import com.example.campus.entity.Club;
import com.example.campus.entity.ClubMember;
import com.example.campus.service.ClubService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/club")
public class ClubController {
@Autowired
private ClubService clubService;
// 创建社团
@PostMapping("/create")
public Club createClub(@RequestBody CreateClubRequest req) {
return clubService.createClub(req);
}
// 查询所有社团
@GetMapping("/list")
public List<Club> listClubs() {
return clubService.listClubs();
}
// 查询社团成员
@GetMapping("/{clubId}/members")
public List<ClubMember> listMembers(@PathVariable Long clubId) {
return clubService.listMembers(clubId);
}
// 加入社团
@PostMapping("/join")
public ClubMember joinClub(@RequestBody JoinClubRequest req) {
return clubService.joinClub(req);
}
// 查询用户加入的所有社团
@GetMapping("/user/{userId}")
public List<ClubMember> listClubsByUser(@PathVariable Long userId) {
return clubService.listClubsByUser(userId);
}
}
src\main\java\com\example\campus\controller\FriendController.java
package com.example.campus.controller;
import com.example.campus.dto.AddFriendRequest;
import com.example.campus.dto.MoveFriendRequest;
import com.example.campus.entity.Friend;
import com.example.campus.service.FriendService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/friend")
public class FriendController {
@Autowired
private FriendService friendService;
// 添加好友
@PostMapping("/{userId}/add")
public Friend addFriend(@PathVariable Long userId, @RequestBody AddFriendRequest req) {
return friendService.addFriend(userId, req);
}
// 查询好友列表
@GetMapping("/{userId}/list")
public List<Friend> listFriends(@PathVariable Long userId) {
return friendService.listFriends(userId);
}
// 按分组查询
@GetMapping("/{userId}/group/{groupName}")
public List<Friend> listFriendsByGroup(@PathVariable Long userId, @PathVariable String groupName) {
return friendService.listFriendsByGroup(userId, groupName);
}
// 移动分组
@PutMapping("/{userId}/move")
public Friend moveFriend(@PathVariable Long userId, @RequestBody MoveFriendRequest req) {
return friendService.moveFriend(userId, req);
}
}
src\main\java\com\example\campus\controller\MessageController.java
package com.example.campus.controller;
import com.example.campus.dto.SendMessageRequest;
import com.example.campus.entity.Message;
import com.example.campus.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/message")
public class MessageController {
@Autowired
private MessageService messageService;
// 发送私信
@PostMapping("/{senderId}/send")
public Message sendMessage(@PathVariable Long senderId, @RequestBody SendMessageRequest req) {
return messageService.sendMessage(senderId, req);
}
// 查询收到的消息
@GetMapping("/received/{receiverId}")
public List<Message> listReceivedMessages(@PathVariable Long receiverId) {
return messageService.listReceivedMessages(receiverId);
}
// 查询与某用户的会话
@GetMapping("/conversation/{userId1}/{userId2}")
public List<Message> listConversation(@PathVariable Long userId1, @PathVariable Long userId2) {
return messageService.listConversation(userId1, userId2);
}
// 标记消息为已读
@PutMapping("/read/{messageId}")
public Message markAsRead(@PathVariable Long messageId) {
return messageService.markAsRead(messageId);
}
}
src\main\java\com\example\campus\controller\PostController.java
package com.example.campus.controller;
import com.example.campus.dto.CreatePostRequest;
import com.example.campus.dto.CreateCommentRequest;
import com.example.campus.entity.Post;
import com.example.campus.entity.Comment;
import com.example.campus.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/post")
public class PostController {
@Autowired
private PostService postService;
// 发布动态
@PostMapping("/create")
public Post createPost(@RequestBody CreatePostRequest req) {
return postService.createPost(req);
}
// 查询所有动态
@GetMapping("/list")
public List<Post> listAllPosts() {
return postService.listAllPosts();
}
// 查询某用户动态
@GetMapping("/user/{userId}")
public List<Post> listPostsByUser(@PathVariable Long userId) {
return postService.listPostsByUser(userId);
}
// 发布评论
@PostMapping("/comment")
public Comment createComment(@RequestBody CreateCommentRequest req) {
return postService.createComment(req);
}
// 查询某动态下所有评论
@GetMapping("/{postId}/comments")
public List<Comment> listCommentsByPost(@PathVariable Long postId) {
return postService.listCommentsByPost(postId);
}
}
src\main\java\com\example\campus\controller\UserController.java
package com.example.campus.controller;
import com.example.campus.entity.User;
import com.example.campus.service.UserService;
import com.example.campus.dto.UpdateProfileRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
// 获取个人信息
@GetMapping("/{id}")
public User getProfile(@PathVariable Long id) {
return userService.getUserById(id);
}
// 更新个人信息
@PutMapping("/{id}")
public User updateProfile(@PathVariable Long id, @RequestBody UpdateProfileRequest request) {
return userService.updateProfile(id, request);
}
}
src\main\java\com\example\campus\dto\AddFriendRequest.java
package com.example.campus.dto;
public class AddFriendRequest {
private Long friendId;
private String groupName;
public Long getFriendId() { return friendId; }
public void setFriendId(Long friendId) { this.friendId = friendId; }
public String getGroupName() { return groupName; }
public void setGroupName(String groupName) { this.groupName = groupName; }
}
src\main\java\com\example\campus\dto\CreateActivityRequest.java
package com.example.campus.dto;
import java.sql.Timestamp;
public class CreateActivityRequest {
private String title;
private String description;
private Long clubId;
private Timestamp startTime;
private Timestamp endTime;
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Long getClubId() { return clubId; }
public void setClubId(Long clubId) { this.clubId = clubId; }
public Timestamp getStartTime() { return startTime; }
public void setStartTime(Timestamp startTime) { this.startTime = startTime; }
public Timestamp getEndTime() { return endTime; }
public void setEndTime(Timestamp endTime) { this.endTime = endTime; }
}
src\main\java\com\example\campus\dto\CreateClubRequest.java
package com.example.campus.dto;
public class CreateClubRequest {
private String name;
private String description;
private Long creatorId;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Long getCreatorId() { return creatorId; }
public void setCreatorId(Long creatorId) { this.creatorId = creatorId; }
}
src\main\java\com\example\campus\dto\CreateCommentRequest.java
package com.example.campus.dto;
public class CreateCommentRequest {
private Long postId;
private Long userId;
private String content;
public Long getPostId() { return postId; }
public void setPostId(Long postId) { this.postId = postId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
src\main\java\com\example\campus\dto\CreatePostRequest.java
package com.example.campus.dto;
public class CreatePostRequest {
private Long userId;
private String content;
private String imageUrl;
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
}
src\main\java\com\example\campus\dto\JoinClubRequest.java
package com.example.campus.dto;
public class JoinClubRequest {
private Long clubId;
private Long userId;
public Long getClubId() { return clubId; }
public void setClubId(Long clubId) { this.clubId = clubId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
}
src\main\java\com\example\campus\dto\LoginRequest.java
package com.example.campus.dto;
public class LoginRequest {
private String username;
private String password;
// getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
src\main\java\com\example\campus\dto\MoveFriendRequest.java
package com.example.campus.dto;
public class MoveFriendRequest {
private Long friendId;
private String newGroupName;
public Long getFriendId() { return friendId; }
public void setFriendId(Long friendId) { this.friendId = friendId; }
public String getNewGroupName() { return newGroupName; }
public void setNewGroupName(String newGroupName) { this.newGroupName = newGroupName; }
}
src\main\java\com\example\campus\dto\RegisterRequest.java
package com.example.campus.dto;
public class RegisterRequest {
private String username;
private String password;
private String role; // student or teacher
private String email;
private String realName;
// getters and setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getRealName() { return realName; }
public void setRealName(String realName) { this.realName = realName; }
}
src\main\java\com\example\campus\dto\SendMessageRequest.java
package com.example.campus.dto;
public class SendMessageRequest {
private Long receiverId;
private String content;
public Long getReceiverId() { return receiverId; }
public void setReceiverId(Long receiverId) { this.receiverId = receiverId; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
src\main\java\com\example\campus\dto\UpdateProfileRequest.java
package com.example.campus.dto;
public class UpdateProfileRequest {
private String email;
private String realName;
// 可扩展更多字段
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getRealName() { return realName; }
public void setRealName(String realName) { this.realName = realName; }
}
src\main\java\com\example\campus\entity\Activity.java
package com.example.campus.entity;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@Table(name = "activity")
public class Activity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
private String description;
private Long clubId;
@Column(name = "start_time")
private Timestamp startTime;
@Column(name = "end_time")
private Timestamp endTime;
@Column(name = "created_at")
private Timestamp createdAt;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Long getClubId() { return clubId; }
public void setClubId(Long clubId) { this.clubId = clubId; }
public Timestamp getStartTime() { return startTime; }
public void setStartTime(Timestamp startTime) { this.startTime = startTime; }
public Timestamp getEndTime() { return endTime; }
public void setEndTime(Timestamp endTime) { this.endTime = endTime; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}
src\main\java\com\example\campus\entity\ActivitySignup.java
package com.example.campus.entity;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@Table(name = "activity_signup")
public class ActivitySignup {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long activityId;
@Column(nullable = false)
private Long userId;
@Column(nullable = false)
private Boolean checkedIn = false;
@Column(name = "signup_time")
private Timestamp signupTime;
@Column(name = "checkin_time")
private Timestamp checkinTime;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getActivityId() { return activityId; }
public void setActivityId(Long activityId) { this.activityId = activityId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public Boolean getCheckedIn() { return checkedIn; }
public void setCheckedIn(Boolean checkedIn) { this.checkedIn = checkedIn; }
public Timestamp getSignupTime() { return signupTime; }
public void setSignupTime(Timestamp signupTime) { this.signupTime = signupTime; }
public Timestamp getCheckinTime() { return checkinTime; }
public void setCheckinTime(Timestamp checkinTime) { this.checkinTime = checkinTime; }
}
src\main\java\com\example\campus\entity\Club.java
package com.example.campus.entity;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@Table(name = "club")
public class Club {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String name;
private String description;
private Long creatorId;
@Column(name = "created_at")
private Timestamp createdAt;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Long getCreatorId() { return creatorId; }
public void setCreatorId(Long creatorId) { this.creatorId = creatorId; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}
src\main\java\com\example\campus\entity\ClubMember.java
package com.example.campus.entity;
import javax.persistence.*;
@Entity
@Table(name = "club_member")
public class ClubMember {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long clubId;
@Column(nullable = false)
private Long userId;
@Column(nullable = false)
private String role; // president/member
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getClubId() { return clubId; }
public void setClubId(Long clubId) { this.clubId = clubId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
}
src\main\java\com\example\campus\entity\Comment.java
package com.example.campus.entity;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@Table(name = "comment")
public class Comment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long postId;
@Column(nullable = false)
private Long userId;
@Column(nullable = false, length = 500)
private String content;
@Column(name = "created_at")
private Timestamp createdAt;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getPostId() { return postId; }
public void setPostId(Long postId) { this.postId = postId; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}
src\main\java\com\example\campus\entity\Friend.java
package com.example.campus.entity;
import javax.persistence.*;
@Entity
@Table(name = "friend")
public class Friend {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long userId; // 自己的ID
@Column(nullable = false)
private Long friendId; // 好友的ID
@Column(nullable = false)
private String groupName; // 分组名
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public Long getFriendId() { return friendId; }
public void setFriendId(Long friendId) { this.friendId = friendId; }
public String getGroupName() { return groupName; }
public void setGroupName(String groupName) { this.groupName = groupName; }
}
src\main\java\com\example\campus\entity\Message.java
package com.example.campus.entity;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@Table(name = "message")
public class Message {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long senderId;
@Column(nullable = false)
private Long receiverId;
@Column(nullable = false, length = 1000)
private String content;
@Column(nullable = false)
private Boolean isRead = false;
@Column(name = "created_at")
private Timestamp createdAt;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getSenderId() { return senderId; }
public void setSenderId(Long senderId) { this.senderId = senderId; }
public Long getReceiverId() { return receiverId; }
public void setReceiverId(Long receiverId) { this.receiverId = receiverId; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public Boolean getIsRead() { return isRead; }
public void setIsRead(Boolean isRead) { this.isRead = isRead; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}
src\main\java\com\example\campus\entity\Post.java
package com.example.campus.entity;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@Table(name = "post")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private Long userId;
@Column(nullable = false, length = 1000)
private String content;
private String imageUrl;
@Column(name = "created_at")
private Timestamp createdAt;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}
src\main\java\com\example\campus\entity\User.java
package com.example.campus.entity;
import javax.persistence.*;
import java.sql.Timestamp;
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String role; // student or teacher
private String email;
@Column(name = "real_name")
private String realName;
@Column(name = "created_at")
private Timestamp createdAt;
// getters and setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getRealName() { return realName; }
public void setRealName(String realName) { this.realName = realName; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}
src\main\java\com\example\campus\repository\ActivityRepository.java
package com.example.campus.repository;
import com.example.campus.entity.Activity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ActivityRepository extends JpaRepository<Activity, Long> {
List<Activity> findByClubId(Long clubId);
}
src\main\java\com\example\campus\repository\ActivitySignupRepository.java
package com.example.campus.repository;
import com.example.campus.entity.ActivitySignup;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ActivitySignupRepository extends JpaRepository<ActivitySignup, Long> {
List<ActivitySignup> findByActivityId(Long activityId);
ActivitySignup findByActivityIdAndUserId(Long activityId, Long userId);
}
src\main\java\com\example\campus\repository\ClubMemberRepository.java
package com.example.campus.repository;
import com.example.campus.entity.ClubMember;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface ClubMemberRepository extends JpaRepository<ClubMember, Long> {
List<ClubMember> findByClubId(Long clubId);
List<ClubMember> findByUserId(Long userId);
ClubMember findByClubIdAndUserId(Long clubId, Long userId);
}
src\main\java\com\example\campus\repository\ClubRepository.java
package com.example.campus.repository;
import com.example.campus.entity.Club;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ClubRepository extends JpaRepository<Club, Long> {
Club findByName(String name);
}
src\main\java\com\example\campus\repository\CommentRepository.java
package com.example.campus.repository;
import com.example.campus.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByPostId(Long postId);
}
src\main\java\com\example\campus\repository\FriendRepository.java
package com.example.campus.repository;
import com.example.campus.entity.Friend;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface FriendRepository extends JpaRepository<Friend, Long> {
List<Friend> findByUserId(Long userId);
List<Friend> findByUserIdAndGroupName(Long userId, String groupName);
Friend findByUserIdAndFriendId(Long userId, Long friendId);
}
src\main\java\com\example\campus\repository\MessageRepository.java
package com.example.campus.repository;
import com.example.campus.entity.Message;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface MessageRepository extends JpaRepository<Message, Long> {
List<Message> findByReceiverId(Long receiverId);
List<Message> findBySenderIdAndReceiverId(Long senderId, Long receiverId);
}
src\main\java\com\example\campus\repository\PostRepository.java
package com.example.campus.repository;
import com.example.campus.entity.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByUserId(Long userId);
}
src\main\java\com\example\campus\repository\UserRepository.java
package com.example.campus.repository;
import com.example.campus.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
src\main\java\com\example\campus\service\ActivityService.java
package com.example.campus.service;
import com.example.campus.dto.CreateActivityRequest;
import com.example.campus.entity.Activity;
import com.example.campus.entity.ActivitySignup;
import com.example.campus.repository.ActivityRepository;
import com.example.campus.repository.ActivitySignupRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.List;
@Service
public class ActivityService {
@Autowired
private ActivityRepository activityRepository;
@Autowired
private ActivitySignupRepository activitySignupRepository;
public Activity createActivity(CreateActivityRequest req) {
Activity activity = new Activity();
activity.setTitle(req.getTitle());
activity.setDescription(req.getDescription());
activity.setClubId(req.getClubId());
activity.setStartTime(req.getStartTime());
activity.setEndTime(req.getEndTime());
activity.setCreatedAt(new Timestamp(System.currentTimeMillis()));
return activityRepository.save(activity);
}
public List<Activity> listActivitiesByClub(Long clubId) {
return activityRepository.findByClubId(clubId);
}
public List<Activity> listAllActivities() {
return activityRepository.findAll();
}
public ActivitySignup signup(Long activityId, Long userId) {
if (activitySignupRepository.findByActivityIdAndUserId(activityId, userId) != null) {
throw new RuntimeException("已报名");
}
ActivitySignup signup = new ActivitySignup();
signup.setActivityId(activityId);
signup.setUserId(userId);
signup.setCheckedIn(false);
signup.setSignupTime(new Timestamp(System.currentTimeMillis()));
return activitySignupRepository.save(signup);
}
public List<ActivitySignup> listSignups(Long activityId) {
return activitySignupRepository.findByActivityId(activityId);
}
public ActivitySignup checkin(Long activityId, Long userId) {
ActivitySignup signup = activitySignupRepository.findByActivityIdAndUserId(activityId, userId);
if (signup == null) throw new RuntimeException("未报名");
signup.setCheckedIn(true);
signup.setCheckinTime(new Timestamp(System.currentTimeMillis()));
return activitySignupRepository.save(signup);
}
}
src\main\java\com\example\campus\service\ClubService.java
package com.example.campus.service;
import com.example.campus.dto.CreateClubRequest;
import com.example.campus.dto.JoinClubRequest;
import com.example.campus.entity.Club;
import com.example.campus.entity.ClubMember;
import com.example.campus.repository.ClubRepository;
import com.example.campus.repository.ClubMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.List;
@Service
public class ClubService {
@Autowired
private ClubRepository clubRepository;
@Autowired
private ClubMemberRepository clubMemberRepository;
public Club createClub(CreateClubRequest req) {
if (clubRepository.findByName(req.getName()) != null) {
throw new RuntimeException("社团名已存在");
}
Club club = new Club();
club.setName(req.getName());
club.setDescription(req.getDescription());
club.setCreatorId(req.getCreatorId());
club.setCreatedAt(new Timestamp(System.currentTimeMillis()));
club = clubRepository.save(club);
// 创始人自动成为社团会长
ClubMember member = new ClubMember();
member.setClubId(club.getId());
member.setUserId(req.getCreatorId());
member.setRole("president");
clubMemberRepository.save(member);
return club;
}
public List<Club> listClubs() {
return clubRepository.findAll();
}
public List<ClubMember> listMembers(Long clubId) {
return clubMemberRepository.findByClubId(clubId);
}
public ClubMember joinClub(JoinClubRequest req) {
if (clubMemberRepository.findByClubIdAndUserId(req.getClubId(), req.getUserId()) != null) {
throw new RuntimeException("已加入该社团");
}
ClubMember member = new ClubMember();
member.setClubId(req.getClubId());
member.setUserId(req.getUserId());
member.setRole("member");
return clubMemberRepository.save(member);
}
public List<ClubMember> listClubsByUser(Long userId) {
return clubMemberRepository.findByUserId(userId);
}
}
src\main\java\com\example\campus\service\FriendService.java
package com.example.campus.service;
import com.example.campus.dto.AddFriendRequest;
import com.example.campus.dto.MoveFriendRequest;
import com.example.campus.entity.Friend;
import com.example.campus.repository.FriendRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class FriendService {
@Autowired
private FriendRepository friendRepository;
public Friend addFriend(Long userId, AddFriendRequest req) {
// 检查是否已是好友
if (friendRepository.findByUserIdAndFriendId(userId, req.getFriendId()) != null) {
throw new RuntimeException("已添加为好友");
}
Friend friend = new Friend();
friend.setUserId(userId);
friend.setFriendId(req.getFriendId());
friend.setGroupName(req.getGroupName() != null ? req.getGroupName() : "默认分组");
return friendRepository.save(friend);
}
public List<Friend> listFriends(Long userId) {
return friendRepository.findByUserId(userId);
}
public List<Friend> listFriendsByGroup(Long userId, String groupName) {
return friendRepository.findByUserIdAndGroupName(userId, groupName);
}
public Friend moveFriend(Long userId, MoveFriendRequest req) {
Friend friend = friendRepository.findByUserIdAndFriendId(userId, req.getFriendId());
if (friend == null) throw new RuntimeException("好友不存在");
friend.setGroupName(req.getNewGroupName());
return friendRepository.save(friend);
}
}
src\main\java\com\example\campus\service\MessageService.java
package com.example.campus.service;
import com.example.campus.dto.SendMessageRequest;
import com.example.campus.entity.Message;
import com.example.campus.repository.MessageRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.List;
@Service
public class MessageService {
@Autowired
private MessageRepository messageRepository;
public Message sendMessage(Long senderId, SendMessageRequest req) {
Message msg = new Message();
msg.setSenderId(senderId);
msg.setReceiverId(req.getReceiverId());
msg.setContent(req.getContent());
msg.setIsRead(false);
msg.setCreatedAt(new Timestamp(System.currentTimeMillis()));
return messageRepository.save(msg);
}
public List<Message> listReceivedMessages(Long receiverId) {
return messageRepository.findByReceiverId(receiverId);
}
public List<Message> listConversation(Long userId1, Long userId2) {
return messageRepository.findBySenderIdAndReceiverId(userId1, userId2);
}
public Message markAsRead(Long messageId) {
Message msg = messageRepository.findById(messageId)
.orElseThrow(() -> new RuntimeException("消息不存在"));
msg.setIsRead(true);
return messageRepository.save(msg);
}
}
src\main\java\com\example\campus\service\PostService.java
package com.example.campus.service;
import com.example.campus.dto.CreatePostRequest;
import com.example.campus.dto.CreateCommentRequest;
import com.example.campus.entity.Post;
import com.example.campus.entity.Comment;
import com.example.campus.repository.PostRepository;
import com.example.campus.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.Timestamp;
import java.util.List;
@Service
public class PostService {
@Autowired
private PostRepository postRepository;
@Autowired
private CommentRepository commentRepository;
public Post createPost(CreatePostRequest req) {
Post post = new Post();
post.setUserId(req.getUserId());
post.setContent(req.getContent());
post.setImageUrl(req.getImageUrl());
post.setCreatedAt(new Timestamp(System.currentTimeMillis()));
return postRepository.save(post);
}
public List<Post> listPostsByUser(Long userId) {
return postRepository.findByUserId(userId);
}
public List<Post> listAllPosts() {
return postRepository.findAll();
}
public Comment createComment(CreateCommentRequest req) {
Comment comment = new Comment();
comment.setPostId(req.getPostId());
comment.setUserId(req.getUserId());
comment.setContent(req.getContent());
comment.setCreatedAt(new Timestamp(System.currentTimeMillis()));
return commentRepository.save(comment);
}
public List<Comment> listCommentsByPost(Long postId) {
return commentRepository.findByPostId(postId);
}
}
src\main\java\com\example\campus\service\UserService.java
package com.example.campus.service;
import com.example.campus.dto.RegisterRequest;
import com.example.campus.dto.LoginRequest;
import com.example.campus.entity.User;
import com.example.campus.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public User register(RegisterRequest req) {
if (userRepository.findByUsername(req.getUsername()).isPresent()) {
throw new RuntimeException("用户名已存在");
}
User user = new User();
user.setUsername(req.getUsername());
user.setPassword(passwordEncoder.encode(req.getPassword()));
user.setRole(req.getRole());
user.setEmail(req.getEmail());
user.setRealName(req.getRealName());
return userRepository.save(user);
}
public User login(LoginRequest req) {
Optional<User> userOpt = userRepository.findByUsername(req.getUsername());
if (!userOpt.isPresent()) {
throw new RuntimeException("用户不存在");
}
User user = userOpt.get();
if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) {
throw new RuntimeException("密码错误");
}
return user;
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
}
public User updateProfile(Long id, com.example.campus.dto.UpdateProfileRequest req) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("用户不存在"));
if (req.getEmail() != null) user.setEmail(req.getEmail());
if (req.getRealName() != null) user.setRealName(req.getRealName());
// 可扩展更多字段
return userRepository.save(user);
}
}
src\main\resources\application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/campus_social_network?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: Asia/Shanghai
server:
port: 8080
src\main\resources\schema.sql
CREATE TABLE user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('student', 'teacher') NOT NULL,
email VARCHAR(100),
real_name VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);