Java消息系統(tǒng)簡單設計與實現(xiàn)

前言:由于導師在我的畢設項目里加了消息系統(tǒng)(本來想水水就過的..)蔓罚,沒辦法...來稍微研究研究吧..簡單簡單...

需求分析

我的畢設是一個博客系統(tǒng),類似于簡書這樣的瞻颂,所以消息系統(tǒng)也類似豺谈,在用戶的消息里包含了有:喜歡和贊、評論贡这、關注茬末、私信這樣的一類東西,這樣的一個系統(tǒng)應該包含以下的功能:

    1. 當用戶評論/關注/點贊時能夠通知到被評論/關注/點贊的用戶盖矫,并生成像如下格式的提示信息(允許取消關注/點贊但不收到通知):

    我沒有 關注了 你
    三顆 喜歡了你的文章 《Java消息系統(tǒng)簡單設計與實現(xiàn)》
    心臟 評論了你的文章 《Java消息系統(tǒng)簡單設計與實現(xiàn)》

    1. 用戶之間能夠發(fā)送/接受私信丽惭,不需要像QQ那樣建立長連接實現(xiàn)實時通信,但刷新列表能看到新消息辈双,并且界面類似QQ聊天界面一左一右责掏,允許刪除私信
    1. 管理員能發(fā)送通告湃望,其實就像是用管理員的賬號給每一個用戶發(fā)送私信换衬;
    1. 可以查看關注的用戶最新發(fā)表的文章,得到類似推送的效果证芭;
    1. 所有消息當然也要標注好消息已讀or未讀瞳浦,登錄就能得到消息提醒標識好有多少未讀消息,像是QQ消息右上角的小紅點那樣類似废士;

OK叫潦,大致就是以上的功能,那么問題來了:這要怎么設計肮傧酢矗蕊?

進一步分析

其實可以根據(jù)上面的需求分析,把上面的消息大致可以分為公告(Announcement)氢架、提醒(Remind)拔妥、私信(Message)三類,我們可以大致抽象出一個 通知(Notify) 模型:

發(fā)送者 接受者 信息類型 動作類型 通知內(nèi)容 是否已讀 消息創(chuàng)建時間
粉絲1號 我沒有三顆心臟 提醒 關注 粉絲1號 關注了 你 xx:xx:xx
粉絲1號 我沒有三顆心臟 提醒 喜歡和贊 粉絲1號 喜歡了你的文章 《Java消息系統(tǒng)簡單設計與實現(xiàn)》 xx:xx:xx
粉絲1號 我沒有三顆心臟 提醒 評論 粉絲1號 評論了你的文章 《Java消息系統(tǒng)簡單設計與實現(xiàn)》 xx:xx:xx
粉絲2號 我沒有三顆心臟 私信 你收到了來自 粉絲2號 的 1 條私信 xx:xx:xx

上面加了一些數(shù)據(jù)以便理解达箍,不過話說粉絲1號果然是真愛粉没龙,又關注又喜歡又評論,嘻嘻嘻嘻...

emm.這樣的模型能夠勝任我們的工作嗎缎玫?我也不知道..不過根據(jù)這個模型能夠想出大概的這樣的創(chuàng)建通知的邏輯:

似乎看上去也沒有什么大問題..不過既然消息內(nèi)容都可以根據(jù)動作類型自動生成的了硬纤,加上私信和公告的內(nèi)容因為長度問題也肯定不保存在這張表里的好,所以我們在設計數(shù)據(jù)庫時干脆把通知內(nèi)容這條去掉不要赃磨,當信息類型是公告或者私信時可以根據(jù)這條通知的 id 在相應的表中找到相應的數(shù)據(jù)就可以了筝家,emm..我覺得可以

順下去想想其實腦中有了一個大概,這樣的模型還容易設計和想到邻辉,其實主要的問題還是下面的那些

問題一:單表數(shù)據(jù)大了怎么辦溪王?

如果當用戶量上去到一定量的時候腮鞍,那么這張 通知表 勢必會變得巨大,因為不管是我們的公告莹菱、提醒還是私信都會在這個通知表上創(chuàng)建一條數(shù)據(jù)移国,到時候就會面臨查詢慢的問題,問題的答案是:我也不知道..

所以我們的規(guī)定是:不考慮像簡書這樣超大用戶量道伟,能夠應付畢設就好啦..簡單設計迹缀,嘻嘻嘻..不過也不要太不相信MySQL的性能,還是有一定容納能力的蜜徽!

問題二:用戶要怎樣正確得到自己的未讀消息呢祝懂?

暴力一點方法是,反正通知表里有用戶所有的消息拘鞋,直接讀取完忘巧,然后通過是否已讀字段就能夠找到正確的所有未讀消息了习勤,這..這么簡單嗎谴忧?

其實有思考過使用時間或者另建一張保存有最新已讀到哪條消息的表汞贸,但用戶可以選擇有一些讀有一些不讀虾宇,這兩個似乎都很難達到目的...還是暴力吧

問題三:私信消息該怎么設計元莫?

發(fā)送者 接受者 內(nèi)容 發(fā)送時間
粉絲1號 我沒有三顆心臟 我是你的真愛粉鞍饨恕宪郊!我要給你生猴子峡扩! 2019年1月7日11:34:23
我沒有三顆心臟 粉絲1號 已閱...下一個... 2019年1月7日11:34:53

就像 QQ消息 一樣嘛蹭越,包含一個內(nèi)容、時間教届、發(fā)送者和接受者响鹃,然后前端直接根據(jù)時間或者 id 排序生成一左一右的消息對話框,不過比較特殊的一點就是私信是一個雙向交流的過程案训,在一個對話框中我可能既是接受者也是發(fā)送者买置,這也無所謂嘛,稍微分析分析場景:

  • 讀取私信列表時:按照接受者和發(fā)送者一起查詢的原則强霎,也就是查詢接受者是自己和發(fā)送者是自己的數(shù)據(jù)忿项,然后根據(jù)時間和已讀未讀來建立私信列表;
  • 讀取私信時:這時已經(jīng)有了明確的接受者和發(fā)送者城舞,那就查詢所有 發(fā)送者是對方接受者是自己 Or 發(fā)送者是自己接受者是對方 的數(shù)據(jù)轩触,然后在前端拼湊出一左一右的聊天框;
  • 發(fā)送私信時:先查詢之前是否有記錄家夺,然后同上建立聊天框脱柱,點擊發(fā)送之后把發(fā)送方設為自己接收方設為私信對象,然后在通知表中新建一條未讀數(shù)據(jù)通知私信對象有私信來了拉馋;

這完全能滿足要求榨为,只不過感覺查詢多了些..

數(shù)據(jù)庫設計

簡單弄了弄弄..看著挺難受的惨好,不過能簡單實現(xiàn)功能,并且為了演示随闺,這里是做了一張user_follow表日川,表示用戶之間的關聯(lián)關系,點贊和評論與這個類似板壮,就不多弄了..下面給一下建表語句吧:

user表:

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `username` varchar(50) NOT NULL COMMENT '用戶姓名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

user_follow表:

CREATE TABLE `user_follow` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `uid` bigint(20) NOT NULL COMMENT '用戶ID',
  `follow_uid` bigint(20) NOT NULL COMMENT '關注的用戶id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶關注表,記錄了所有用戶的關注信息';

notify表:

CREATE TABLE `notify` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `sender_id` bigint(20) NOT NULL COMMENT '發(fā)送者用戶ID',
  `reciver_id` bigint(20) NOT NULL COMMENT '接受者用戶ID',
  `type` varchar(50) NOT NULL COMMENT '消息類型:announcement公告/remind提醒/message私信',
  `is_read` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已讀,0未讀,1已讀',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間:按當前時間自動創(chuàng)建',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用戶通知表,包含了所有用戶的消息';

message表:

CREATE TABLE `message` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `notify_id` bigint(20) NOT NULL COMMENT '對應通知消息的id',
  `sender_id` bigint(20) NOT NULL COMMENT '發(fā)送者用戶ID',
  `reciver_id` bigint(20) NOT NULL COMMENT '接受者用戶ID',
  `content` varchar(1000) NOT NULL COMMENT '消息內(nèi)容,最長長度不允許超過1000',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間:按當前時間自動創(chuàng)建',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='私信信息表,包含了所有用戶的私信信息';

根據(jù)《Java開發(fā)手冊》5.3 第六條 沒有使用任何級聯(lián)和外鍵逗鸣,bingo!

Spring Boot + MyBatis 實例

第一步:基礎環(huán)境搭建

SpringBoot項目怎么搭就不說了吧绰精,給一給幾個關鍵的配置文件:

pom包依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
<!-- SpringBoot - MyBatis 逆向工程 -->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>5.1.18</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

這里有一個巨坑撒璧,耗費了我好半天的時間,不知道為什么我明明引入的 5.1.18 版本的 mysql-connector-java笨使,可 Maven 就是非要給我比較新版本的 8.0.13卿樱,這導致了在我使用 MyBatis 逆向工程生成 domain 和 mapper 的過程中出現(xiàn)了以下的問題:

  • 1、提示我數(shù)據(jù)庫連接的驅動名稱需要改成com.mysql.cj.jdbc.Driver而不是之前的com.mysql.jdbc.Driver硫椰,不然就報錯:

Loading class com.mysql.jdbc.Driver'. This is deprecated. The new driver class iscom.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.

  • 2繁调、還需要設置 mysql 的時區(qū),也就是需要將connectionURL屬性寫成"jdbc:mysql://localhost:3306/test?serverTimezone=UTC"靶草。如果不指定serverTimezone=UTC(還必須大寫)蹄胰,將報錯:

java.sql.SQLException: The server time zone value '?й???????' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
??at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:695)
??at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:663)

  • 3、逆向工程會去找 MySQL 其他庫的相同表名的表奕翔,然后生成一堆亂七八糟的東西裕寨,還由于找不到主鍵 id 生成了只含 inser() 方法而不含刪除、更新方法的 Mapper 文件派继;

解決方法就只有自己手動去調低 mysql-connector-java 的版本到 5.xx宾袜,還找到一個跟我情況類似:https://blog.csdn.net/angel_xiaa/article/details/52474022

application.properties:

## 數(shù)據(jù)庫連接配置
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/message_system?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
## MyBatis相關配置
mybatis.type-aliases-package=com.wmyskxz.demo.messagesystem.domain
mybatis.mapper-locations=classpath:mapper/*.xml

在啟動類上加上注解:

@EnableTransactionManagement  // 啟注解事務管理,等同于xml配置方式的 <tx:annotation-driven />
@MapperScan("com.wmyskxz.demo.messagesystem.dao")
@SpringBootApplication
public class MessageSystemApplication {
        ....
}

第二步:MyBatis 逆向工程

新建【util】包驾窟,在下面新建兩個類:

MybatisGenerator類:

public class MybatisGenerator {

    public static void main(String[] args) throws Exception {
        String today = "2019-1-7";

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date now = sdf.parse(today);
        Date d = new Date();

        if (d.getTime() > now.getTime() + 1000 * 60 * 60 * 24) {
            System.err.println("——————未成成功運行——————");
            System.err.println("——————未成成功運行——————");
            System.err.println("本程序具有破壞作用庆猫,應該只運行一次,如果必須要再運行绅络,需要修改today變量為今天月培,如:" + sdf.format(new Date()));
            return;
        }

        if (false)
            return;
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        InputStream is = MybatisGenerator.class.getClassLoader().getResource("generatorConfig.xml").openStream();
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(is);
        is.close();
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);

        System.out.println("生成代碼成功,只能執(zhí)行一次恩急,以后執(zhí)行會覆蓋掉mapper,pojo,xml 等文件上做的修改");

    }
}

OverIsMergeablePlugin類:

/**
 * 解決 MyBatis 逆向工程重復生成覆蓋問題的工具類
 */
public class OverIsMergeablePlugin extends PluginAdapter {
    @Override
    public boolean validate(List<String> warnings) {
        return true;
    }

    @Override
    public boolean sqlMapGenerated(GeneratedXmlFile sqlMap, IntrospectedTable introspectedTable) {
        try {
            Field field = sqlMap.getClass().getDeclaredField("isMergeable");
            field.setAccessible(true);
            field.setBoolean(sqlMap, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }
}

在【resrouces】資源文件下新建逆向工程配置文件【generatorConfig.xml】:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="DB2Tables" targetRuntime="MyBatis3">

        <!--避免生成重復代碼的插件-->
        <plugin type="com.wmyskxz.demo.messagesystem.util.OverIsMergeablePlugin"/>

        <!-- 是否去除自動生成的代碼中的注釋 true:是 false:否-->
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!--數(shù)據(jù)庫鏈接地址賬號密碼-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/message_system?serverTimezone=UTC"
                        userId="root" password="123456">
        </jdbcConnection>
        <!-- 默認 false节视,把 JDBC DECIMAL 和 NUMERIC 類型解析為 Integer
             為 true 時解析為 java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!--生成pojo類存放位置-->
        <javaModelGenerator targetPackage="com.wmyskxz.demo.messagesystem.domain" targetProject="src/main/java">
            <!-- enableSubPackages:是否讓 schema 作為包的后綴-->
            <property name="enableSubPackages" value="true"/>
            <!-- trimStrings:從數(shù)據(jù)庫返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true"/>
            <!-- 是否對model添加 構造函數(shù) -->
            <property name="constructorBased" value="true"/>
        </javaModelGenerator>
        <!--生成xml映射文件存放位置-->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--生成mapper類存放位置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.wmyskxz.demo.messagesystem.dao"
                             targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!--生成對應表及類名
            tableName:要生成的表名
            domainObjectName:生成后的實例名
            enableCountByExample:Count語句中加入where條件查詢,默認為true開啟
            enableUpdateByExample:Update語句中加入where條件查詢假栓,默認為true開啟
            enableDeleteByExample:Delete語句中加入where條件查詢寻行,默認為true開啟
            enableSelectByExample:Select多條語句中加入where條件查詢,默認為true開啟
            selectByExampleQueryId:Select單個對象語句中加入where條件查詢匾荆,默認為true開啟
        -->
        <table tableName="user" domainObjectName="User" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="true"
               selectByExampleQueryId="false" enableDeleteByPrimaryKey="true" enableUpdateByPrimaryKey="true">
            <property name="my.isgen.usekeys" value="true"/>
            <property name="useActualColumnNames" value="false"/>
            <generatedKey column="id" sqlStatement="JDBC"/>
        </table>
        <table tableName="notify" domainObjectName="Notify" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="true"
               selectByExampleQueryId="false" enableDeleteByPrimaryKey="true" enableUpdateByPrimaryKey="true">
            <property name="my.isgen.usekeys" value="true"/>
            <property name="useActualColumnNames" value="false"/>
            <generatedKey column="id" sqlStatement="JDBC"/>
        </table>
        <table tableName="user_follow" domainObjectName="UserFollow" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="true"
               selectByExampleQueryId="false" enableDeleteByPrimaryKey="true" enableUpdateByPrimaryKey="true">
            <property name="my.isgen.usekeys" value="true"/>
            <property name="useActualColumnNames" value="false"/>
            <generatedKey column="id" sqlStatement="JDBC"/>
        </table>
        <table tableName="message" domainObjectName="Message" enableCountByExample="false"
               enableUpdateByExample="false" enableDeleteByExample="false" enableSelectByExample="true"
               selectByExampleQueryId="false" enableDeleteByPrimaryKey="true" enableUpdateByPrimaryKey="true">
            <property name="my.isgen.usekeys" value="true"/>
            <property name="useActualColumnNames" value="false"/>
            <generatedKey column="id" sqlStatement="JDBC"/>
        </table>
    </context>
</generatorConfiguration>

運行我們的【MybatisGenerator】類中的 main 方法就能看到自動生成的實體拌蜘、Xml文件以及 Mapper 類

第三步:Service 層

不給接口了杆烁,直接給實現(xiàn)吧,方法都很簡單简卧,而且沒有做任何的安全限制兔魂,只是為了實現(xiàn)簡單的消息系統(tǒng),看效果

UserServiceImpl:

@Service
public class UserServiceImpl implements UserService {
    @Resource
    UserMapper userMapper;
    @Override
    public void addUserByUsername(String username) {
        userMapper.insert(new User(null, username));// 主鍵自增長.
    }
    @Override
    public User findUserById(Long id) {
        return userMapper.selectByPrimaryKey(id);
    }
}

UserFollowServiceImpl:

@Service
public class UserFollowServiceImpl implements UserFollowService {
    @Resource
    UserFollowMapper userFollowMapper;
    @Autowired
    NotifyService notifyService;
    @Override
    public void userAFollowUserBById(Long userAId, Long userBId) {
        // 先要創(chuàng)建一條提示消息
        notifyService.addNotify(userAId, userBId, "follow");// 關注信息
        UserFollow userFollow = new UserFollow();
        userFollow.setUid(userAId);
        userFollow.setFollowUid(userBId);
        userFollowMapper.insertSelective(userFollow);
    }
    @Override
    public void userAUnfollowUserBById(Long userAId, Long userBId) {
        // 首先查詢到相關的記錄
        UserFollowExample example = new UserFollowExample();
        example.or().andUidEqualTo(userAId).andFollowUidEqualTo(userBId);
        UserFollow userFollow = userFollowMapper.selectByExample(example).get(0);
        // 刪除關注數(shù)據(jù)
        userFollowMapper.deleteByPrimaryKey(userFollow.getId());
    }
}

NotifyServiceImpl:

@Service
public class NotifyServiceImpl implements NotifyService {
    @Resource
    NotifyMapper notifyMapper;
    @Override
    public int addNotify(Long senderId, Long reciverId, String type) {
        Notify notify = new Notify(null, senderId, reciverId, type, false, null);
        return notifyMapper.insertSelective(notify);// id和creatTime自動生成.
    }
    @Override
    public void readNotifyById(Long id) {
        Notify notify = notifyMapper.selectByPrimaryKey(id);
        notify.setIsRead(true);
        notifyMapper.updateByPrimaryKey(notify);
    }
    @Override
    public List<Notify> findAllNotifyByReciverId(Long id) {
        List<Notify> notifies = new LinkedList<>();
        NotifyExample example = new NotifyExample();
        example.setOrderByClause("`id` DESC");// 按id倒敘,也就是第一個數(shù)據(jù)是最新的.
        example.or().andReciverIdEqualTo(id);
        notifies.addAll(notifyMapper.selectByExample(example));
        return notifies;
    }
    @Override
    public List<Notify> findAllUnReadNotifyByReciverId(Long id) {
        List<Notify> notifies = new LinkedList<>();
        NotifyExample example = new NotifyExample();
        example.setOrderByClause("`id` DESC");// 按id倒敘,也就是第一個數(shù)據(jù)是最新的.
        example.or().andReciverIdEqualTo(id).andIsReadEqualTo(false);
        notifies.addAll(notifyMapper.selectByExample(example));
        return notifies;
    }
}

MessageServiceImpl:

@Service
public class MessageServiceImpl implements MessageService {
    @Resource
    MessageMapper messageMapper;
    @Resource
    NotifyService notifyService;
    @Override
    public void addMessage(Long senderId, Long reciverId, String content) {
        // 先創(chuàng)建一條 notify 數(shù)據(jù)
        Long notifyId = (long) notifyService.addNotify(senderId, reciverId, "message");// message表示私信
        // 增加一條私信信心
        Message message = new Message(null, notifyId, senderId, reciverId, content, null);
        messageMapper.insertSelective(message);// 插入非空項,id/createTime數(shù)據(jù)庫自動生成
    }
    @Override
    public void deleteMessageById(Long id) {
        messageMapper.deleteByPrimaryKey(id);
    }
    @Override
    public Message findMessageByNotifyId(Long id) {
        // 觸發(fā)方法時應把消息置為已讀
        notifyService.readNotifyById(id);
        MessageExample example = new MessageExample();
        example.or().andNotifyIdEqualTo(id);
        return messageMapper.selectByExample(example).get(0);
    }
}

第四步:Controller 層

也很簡單举娩,只是為了看效果

UserController:

@RestController
public class UserController {
    @Autowired
    UserService userService;
    @PostMapping("/addUser")
    public String addUser(@RequestParam String username) {
        userService.addUserByUsername(username);
        return "Success!";
    }
    @GetMapping("/findUser")
    public User findUser(@RequestParam Long id) {
        return userService.findUserById(id);
    }
}

UserFollowController :

@RestController
public class UserFollowController {
    @Autowired
    UserFollowService userFollowService;
    @PostMapping("/follow")
    public String follow(@RequestParam Long userAId,
                         @RequestParam Long userBId) {
        userFollowService.userAFollowUserBById(userAId, userBId);
        return "Success!";
    }
    @PostMapping("/unfollow")
    public String unfollow(@RequestParam Long userAId,
                           @RequestParam Long userBId) {
        userFollowService.userAUnfollowUserBById(userAId, userBId);
        return "Success!";
    }
}

NotifyController :

@RestController
public class NotifyController {
    @Autowired
    NotifyService notifyService;
    @PostMapping("/addNotify")
    public String addNotify(@RequestParam Long senderId,
                            @RequestParam Long reciverId,
                            @RequestParam String type) {
        notifyService.addNotify(senderId, reciverId, type);
        return "Success!";
    }
    @PostMapping("/readNotify")
    public String readNotify(@RequestParam Long id) {
        notifyService.readNotifyById(id);
        return "Success!";
    }
    @GetMapping("/listAllNotify")
    public List<Notify> listAllNotify(@RequestParam Long id) {
        return notifyService.findAllNotifyByReciverId(id);
    }
    @GetMapping("/listAllUnReadNotify")
    public List<Notify> listAllUnReadNotify(@RequestParam Long id) {
        return notifyService.findAllUnReadNotifyByReciverId(id);
    }
}

MessageController :

@RestController
public class MessageController {
    @Autowired
    MessageService messageService;
    @PostMapping("/addMessage")
    public String addMessage(@RequestParam Long senderId,
                             @RequestParam Long reciverId,
                             @RequestParam String content) {
        messageService.addMessage(senderId, reciverId, content);
        return "Success!";
    }
    @DeleteMapping("/deleteMessage")
    public String deleteMessage(@RequestParam Long id) {
        messageService.deleteMessageById(id);
        return "Success!";
    }
    @GetMapping("/findMessage")
    public Message findMessage(@RequestParam Long id) {
        return messageService.findMessageByNotifyId(id);
    }
}

第五步:測試

通過 REST 測試工具析校,可以看到正確的效果,這里就不給出所有的測試了铜涉。

總結

以上的項目簡單而且沒有任何的安全驗證智玻,不過能夠基本完成我們的需求,還有一些功能沒有實現(xiàn)芙代,例如管理員發(fā)通告(上面只演示了私信和關注信息)吊奢,按照上面的系統(tǒng)就直接暴力給每個用戶都加一條通知消息,感覺有點自閉..我也不知道怎么設計好..希望有經(jīng)驗的大大能指條路拔婆搿页滚!

其實關于這個簡單的系統(tǒng)我查了好多好多資料..把自己都看自閉了,后來我干脆把所有網(wǎng)頁都關掉铺呵,開始用 JPA 自己開始抽象實體裹驰,把各個實體寫出來并把所有實體需要的數(shù)據(jù)啊相互之間的關聯(lián)關系啊寫清楚,然后再從自動生成的數(shù)據(jù)庫中找思路...hhh...要不是我 JPA 不是很熟我覺得用 JPA 就能寫出來了片挂,不用 JPA 的原因在于一些數(shù)據(jù)的懶加載不知道怎么處理幻林,還有就是查詢語句太復雜,免不了要浪費一些資源...emmm..說到底還是不是特別懂 JPA宴卖,下面給一張復雜的用 JPA 建立的 User 實體吧(隨手截的..hhh...很亂..):


按照慣例黏一個尾巴:

歡迎轉載滋将,轉載請注明出處邻悬!
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關注公眾微信號:wmyskxz
分享自己的學習 & 學習資料 & 生活
想要交流的朋友也可以加qq群:3382693

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末症昏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子父丰,更是在濱河造成了極大的恐慌肝谭,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛾扇,死亡現(xiàn)場離奇詭異攘烛,居然都是意外死亡,警方通過查閱死者的電腦和手機镀首,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門坟漱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人更哄,你說我怎么就攤上這事芋齿⌒瓤埽” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵觅捆,是天一觀的道長赦役。 經(jīng)常有香客問我,道長栅炒,這世上最難降的妖魔是什么掂摔? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮赢赊,結果婚禮上乙漓,老公的妹妹穿的比我還像新娘。我一直安慰自己域携,他們只是感情好簇秒,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著秀鞭,像睡著了一般趋观。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锋边,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天皱坛,我揣著相機與錄音,去河邊找鬼豆巨。 笑死剩辟,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的往扔。 我是一名探鬼主播贩猎,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萍膛!你這毒婦竟也來了吭服?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤蝗罗,失蹤者是張志新(化名)和其女友劉穎艇棕,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體串塑,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡沼琉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了桩匪。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片打瘪。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闺骚,到底是詐尸還是另有隱情桃移,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布葛碧,位于F島的核電站借杰,受9級特大地震影響,放射性物質發(fā)生泄漏进泼。R本人自食惡果不足惜蔗衡,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乳绕。 院中可真熱鬧绞惦,春花似錦、人聲如沸洋措。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽菠发。三九已至王滤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間滓鸠,已是汗流浹背雁乡。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留糜俗,地道東北人踱稍。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像悠抹,于是被迫代替她去往敵國和親珠月。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容