Java项目——校园社交网络平台的设计与实现

一、项目背景

随着移动互联网的发展,校园社交网络平台成为高校师生信息交流、资源共享和兴趣互动的重要工具。本文以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鉴权流程

  1. 用户登录成功后生成JWT Token
  2. 前端每次请求携带Token
  3. 后端拦截器验证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

快速开始

  1. 创建数据库 campus_social_network,导入 src/main/resources/schema.sql
  2. 修改 application.yml 数据库配置
  3. 使用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
);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值