Cris 小哥哥的大数据项目之 HBase 模拟微博核心功能

Cris 小哥哥的大数据项目之 HBase 模拟微博核心功能

Author:Cris

0. 序

  1. 通过使用 HBase 模拟完成微博核心业务的后台数据操作,对 HBase 的 API 操作更加熟练
  2. 熟悉需要使用 HBase 的业务场景,以及使用 HBase 的流程
  3. 项目代码满足阿里代码规约,每个功能点的实现代码均经过 junit 测试以及测试效果图
  4. 封装一些有用的类,避免重复造轮子,提高工作效率

1. 需求分析

先思考一下,微博的核心功能有哪些?

每天打开微博,第一件事肯定就是你关注的人又发了哪些微博吧;

然后你刷微博看见有意思的用户就会关注他吧,再顺便进去他的空间看看他之前发的微博吧;

心情不错,想发个微博让自己的粉丝点赞;

发现自己关注的明星又出轨了 or 吸毒了,取消关注!

然后一天就这么刷刷刷的过去了…

虽然 Cris 不玩微博,但是基本的微博功能还是知道的,从上面的场景,我们可以分析出微博的哪些核心功能呢~

  • 发微博,这是微博用户内容输出最核心的功能点
  • 关注和取消关注,这是微博用户之间连接的核心
  • 刷或者是查看微博内容,这是微博用户花费时间最多的功能点

以上三点,基本涵盖了微博的核心功能,让我们重新做一个微博项目,这肯定不现实,但是从某些方面模拟微博的核心功能点,这还是没有问题的,接下来我们使用 HBase 完成微博核心功能点的数据层操作,跟着 Cris 小哥哥来,带你装B带你飞~

2. 项目流程

2.1 项目结构设计

先说点题外话,我们都知道开发常用的架构都是三层架构体系(Controller,Service,Dao),然后这个架构上的表现层一个经典的实现就是 MVC(Model,View,Controller)

本项目则是基于三层架构体系完成,其中 Controller 层并没有实现 MVC,悉知

关于三层架构和 MVC 之间的区别,建议参考这篇文章,个人觉得介绍的很不错哦

先来创建我们的项目,这里 Cris 选择创建一个模块来开发

然后选择父模块导入 pom.xml

项目结果图如下:

pom.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<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">
    <parent>
        <artifactId>testParent</artifactId>
        <groupId>com.cris</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../testParent/pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>weibo</artifactId>
</project>

当然你可以重新创建一个 Proj 来完成改项目

接下来就是完成代码模块分析(这里没有使用 Spring / Spring MVC /Spring Boot,因为该项目核心是使用 HBase 完成后台的数据处理

先导入 log4j.properties 和 hdfs-site.xml

我的 hdfs-site.xml 如下:

<configuration>
	<property>     
		<name>hbase.rootdir</name>     
		<value>hdfs://hadoop101:9000/hbase</value>
	</property>

	<property>   
		<name>hbase.cluster.distributed</name>
		<value>true</value>
	</property>

   <!-- 0.98后的新变动,之前版本没有.port,默认端口为60000 -->
	<property>
		<name>hbase.master.port</name>
		<value>16000</value>
	</property>

	<property>   
		<name>hbase.zookeeper.quorum</name>
	     <value>hadoop101:2181,hadoop102:2181,hadoop103:2181</value>
	</property>

	<property>   
		<name>hbase.zookeeper.property.dataDir</name>
	     <value>/opt/module/zookeeper/zkdata</value>
	</property>
</configuration>

其实不导入也是也可以的,只要你的 Linux 服务器上的 HBase 配置文件都已经配置了以上内容

然后建包

TestWb 用于测试我们的功能

WbService 层用于写具体的逻辑

WbController 作为控制器

dao 层则是我们的核心层,和 HBase 做直接交互的都是这层,所以这层设计尤为重要,我们结合数据库的建设来分析

  1. WbContentDao

    主要用于记录当用户发微博时的微博内容,HBase 需要有专门的表来存储,rowkey 设计为用户 id + 时间戳,列族设计则为发微博的内容

  2. WbRelationDao

    记录被关注者和粉丝之间的关系(这里我们使用 star 来表示被关注者,用 fan 表示粉丝),rowkey 为用户 id,列族设计为该用户的 fan 的 id 以及该用户关注的 star 的 id

  3. WbInboxDao

    表示用户的微博收件箱,用于存储用户可以接收的微博,可以自动获取该用户关注的 start 发布的微博,rowkey 为用户 id,列族设计为接收的微博,列族每个列为(star 的id及其发布的微博 id)

  4. WbHbaseDao

    和 HBase 的基本操作类,例如创建连接,关闭连接,获取 Admin 服务器等

备注:用户信息通常存储在关系型数据库,这里不涉及用户信息,在测试中直接给出即可

2.2 项目代码完成

① 项目所用表初始化
  1. 首先完成我们的工具类 WbHbaseDao

    /**
     * 和 HBase 交互的工具类
     *
     * @author cris
     * @version 1.0
     **/
    @SuppressWarnings("SpellCheckingInspection")
    public class WbHbaseDao {
    
        /**
         * 协助当前线程存储数据到一个map中,key 就是当前 ThreadLocal 对象,值就是要存储的数据
         **/
        private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();
        /**
         * 默认列数据的最大版本数为1,HBase 默认就是1
         **/
        private static final int DEFAULT_MAX_VERSION = 1;
        
        public static final String WB_CONTENT_TABLENAME = "cris:wb_content";
        public static final String WB_RELATION_TABLENAME = "cris:wb_relation";
        public static final String WB_INBOX_TABLENAME = "cris:wb_inbox";
        public static final String WB_NAMESPACENAME = "cris";
    
    
        /**
         * 获取连接,结合 Optional 可以确保得到的连接一定不为空!!!如果连接有问题就直接报错了~
         *
         * @return 连接
         */
        public static Connection getConnection() {
            Optional<Connection> optionalConnection = Optional.ofNullable(THREAD_LOCAL.get());
    
            /*如果 optionalConnection 有值,那么返回容器中的值,否则为当前线程的 ThreadLocalMap 存储一个 connection*/
            return optionalConnection.orElseGet(() -> {
                try {
                    /*创建 Connection 过程中报错就直接抛出空指针异常,确保每个当前线程都应该有 connection
                     * 并且需要保证从当前线程得到的 onnection 一定是非空值*/
                    Connection conn = ConnectionFactory.createConnection();
                    THREAD_LOCAL.set(conn);
                    return conn;
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new NullPointerException();
                }
            });
    
        }
    
        /**
         * 关闭连接
         */
        public static void closeConnection() throws IOException {
            Connection connection = THREAD_LOCAL.get();
            closeAdmin(connection.getAdmin());
            connection.close();
            /*注意:一定要移除线程中绑定的 Connection 对象*/
            THREAD_LOCAL.remove();
        }
    
        public static void closeAdmin(Admin admin) throws IOException {
            if (admin != null) {
                admin.close();
            }
        }
    
        public static void closeTable(Table table) throws IOException {
            if (table != null) {
                table.close();
            }
        }
    
        /**
         * 初始化命名空间
         *
         * @param nameSpaceName 命名空间名字
         * @throws IOException
         */
        public static void initNameSpace(String nameSpaceName) throws IOException {
            Connection connection = getConnection();
            Admin admin = connection.getAdmin();
            // 先要判断 nameSpace 是否存在,如果不存在那么创建
            if (!existNameSpace(nameSpaceName)) {
                NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create(nameSpaceName).build();
                admin.createNamespace(namespaceDescriptor);
            }
        }
    
        /**
         * 判断命名空间是否存在
         *
         * @param nameSpace 命名空间名字
         * @return 命名空间存在返回 true
         * @throws IOException
         */
        public static boolean existNameSpace(String nameSpace) throws IOException {
            Connection connection = getConnection();
            Admin admin = connection.getAdmin();
            try {
                /*如果命名空间不存在将会抛出 NamespaceNotFoundException */
                NamespaceDescriptor namespaceDescriptor = admin.getNamespaceDescriptor(nameSpace);
                return true;
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
    
        }
    
        /**
         * 移除命名空间
         *
         * @param nameSpaceName 命名空间名字
         * @throws IOException
         */
        public static void removeNameSpace(String nameSpaceName) throws IOException {
            Connection connection = getConnection();
            Admin admin = connection.getAdmin();
            // 先要判断 nameSpace 是否存在,如果存在那么删除
            boolean existNameSpace = existNameSpace(nameSpaceName);
            if (existNameSpace) {
                admin.deleteNamespace(nameSpaceName);
            }
        }
    
        /**
         * 移除 HBase 表
         *
         * @param tableName 表名
         * @throws IOException
         */
        public static void removeTable(String tableName) throws IOException {
            Connection connection = getConnection();
            Admin admin = connection.getAdmin();
            TableName name = TableName.valueOf(tableName);
            boolean tableExists = admin.tableExists(name);
            if (tableExists) {
                admin.disableTable(name);
                admin.deleteTable(name);
            }
        }
    
    
        /**
         * 初始化项目所需要的 HBase 表
         *
         * @param tableName
         * @param maxVersion
         * @param columnNames
         * @throws IOException
         */
        public static void initTables(String tableName, int maxVersion, String... columnNames) throws IOException {
            Connection connection = getConnection();
            Admin admin = connection.getAdmin();
            createTable(admin, tableName, maxVersion, columnNames);
        }
    
        /**
         * 初始化项目所需要的 HBase 表
         *
         * @param tableName
         * @param columnNames
         * @throws IOException
         */
        public static void initTables(String tableName, String... columnNames) throws IOException {
            initTables(tableName, DEFAULT_MAX_VERSION, columnNames);
        }
    
        /**
         * 实际的创建 HBase 表的方法
         *
         * @param admin       HBase 集群的主机(HMaster 节点),负责协调 HBase 集群
         * @param tableName   表名
         * @param columnNames 列名(可能多个)
         * @param maxVersion  可手动设置最大版本号
         * @throws IOException
         */
        public static void createTable(Admin admin, String tableName, int maxVersion, String... columnNames) throws IOException {
            TableName name = TableName.valueOf(tableName);
            boolean tableExists = admin.tableExists(name);
            if (!tableExists) {
                HTableDescriptor hTableDescriptor = new HTableDescriptor(name);
                for (String columnName : columnNames) {
                    HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(columnName);
                    hColumnDescriptor.setMaxVersions(maxVersion);
                    hTableDescriptor.addFamily(hColumnDescriptor);
                }
                admin.createTable(hTableDescriptor);
            }
        }
    }
    

    这里使用了 ThreadLocal 类来完成将 Connection 连接存储在当前线程的功能(Java 框架底层大都如此实现,当然比我写的这个要复杂多了)?

    然后尝试引入了 Java 8 的 Optional 类完成对对象的封装,确保得到的 Connection 对象必须非空,否则就会报错

    本工具类参考以下博客:

    ThreadLocal 参考

    静态方法参考

    Java 8 Optional 参考

    静态方法本来使用静态变量是有线程安全问题的,但是这里并没有修改静态变量,所以个人感觉是不会产生线程安全问题,也就没有加锁

    HBase 表的草图如下:

    然后是测试代码

    /**
     * 测试代码
     *
     * @author cris
     * @version 1.0
     **/
    @SuppressWarnings("JavaDoc")
    public class WbTest {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(String.valueOf(WbTest.class));
    
        private WbController wbController = new WbController();
    
        @Before
        public void testStart() {
            wbController.getWbService().start();
        }
    
        @After
        public void testEnd() throws IOException {
            wbController.getWbService().end();
        }
    
        /**
         * 测试 ok
         *
         * @throws IOException
         */
        @Test
        public void testInitNameSpace() throws IOException {
            WbHbaseDao.initNameSpace(WbHbaseDao.WB_NAMESPACENAME);
            LOGGER.warn("命名空间创建成功!");
        }
    
        /**
         * 测试 ok
         *
         * @throws IOException
         */
        @Test
        public void testRemoveNameSpace() throws IOException {
            WbHbaseDao.removeNameSpace(WbHbaseDao.WB_NAMESPACENAME);
            LOGGER.warn("命名空间删除成功");
        }
    
    
        /**
         * 初始化项目所需要的三张表:微博内容表,微博用户关系表,微博用户收件箱表
         * 测试 ok
         *
         * @throws IOException
         */
        @Test
        public void testInitTables() throws IOException {
            WbHbaseDao.initTables(WbHbaseDao.WB_CONTENT_TABLENAME, "info");
            WbHbaseDao.initTables(WbHbaseDao.WB_RELATION_TABLENAME, WbRelationDao.COLUMN_FAMILY_ATTENDS, WbRelationDao.COLUMN_FAMILY_FANS);
            WbHbaseDao.initTables(WbHbaseDao.WB_INBOX_TABLENAME, 5, "info");
            LOGGER.warn("创建表成功!");
        }
    
        /**
         * 删除 HBase 表(测试ok,记得带上命名空间)
         *
         * @throws IOException
         */
        @Test
        public void testDeleteTables() throws IOException {
            WbHbaseDao.removeTable("cris:wb_content");
            WbHbaseDao.removeTable("cris:wb_relation");
            WbHbaseDao.removeTable("cris:wb_inbox");
            LOGGER.warn("删除表成功!");
        }
    

    测试效果如下:

② 发布微博功能实现

a、微博内容表中添加1条数据

b、微博收件箱表对所有粉丝用户添加数据

  1. Controller 层
/**
 * 控制层
 *
 * @author cris
 * @version 1.0
 **/
@SuppressWarnings("JavaDoc")
public class WbController {


    private WbService wbService = new WbService();

    public WbService getWbService() {
        return wbService;
    }

    /**
     * star 发布微博
     *
     * @param star
     * @param content
     * @throws IOException
     */
    public void publishWb(String star, String content) throws IOException {
        wbService.publishWb(star, content);
    }
  1. Service 层
/**
 * 业务逻辑层
 *
 * @author cris
 * @version 1.0
 **/
@SuppressWarnings("JavaDoc")
@Data
@AllArgsConstructor
@Accessors(chain = true)
public class WbService {

    private final WbContentDao wbContentDao = new WbContentDao();
    private final WbRelationDao wbRelationDao = new WbRelationDao();
    private final WbInboxDao wbInboxDao = new WbInboxDao();


    public void start() {
        WbHbaseDao.getConnection();
    }

    public void end() throws IOException {
        WbHbaseDao.closeConnection();
    }

    /**
     * star 发布微博 ok
     *
     * @param star    明星
     * @param content 发布的微博内容
     */
    public void publishWb(String star, String content) throws IOException {
        Connection connection = WbHbaseDao.getConnection();
        // 微博内容表增加一条记录 ok
        byte[] rowkey = wbContentDao.addContent(connection, star, content);
        // 获取该 star 的所有粉丝 fans ok
        List<String> fans = wbRelationDao.getAllFans(connection, star);
        // 向这些粉丝的收件箱发送微博消息 ok
        wbInboxDao.receiveMessage(connection, star, rowkey, fans);
    }
  1. Dao 层
  • WbContentDao
/**
 * 微博内容表交互类
 *
 * @author cris
 * @version 1.0
 **/
public class WbContentDao {

    public static final String COLUMN_FAMILY_NAME = "info";
    public static final String COLUMN_NAME = "content";

    /**
     * 微博内容表增加一条记录
     *
     * @param connection
     * @param star
     * @param content
     */
    public byte[] addContent(Connection connection, String star, String content) throws IOException {
        Table table = connection.getTable(TableName.valueOf(WbHbaseDao.WB_CONTENT_TABLENAME));

        /*HBase 表存储数据默认按照 rowkey 的字符串大小比较排序,即字符串小的 rowkey 排在上面
         * 为了取出 star 最新发布的微博内容,这里需要使用 Long 的最大值减去时间戳得到 rowkey:
         * 微博发布时间越新,时间戳越大,Long 的最大值减去时间戳得到的 rowkey 就越小,在 Hbase 表排序越往上*/
        byte[] rowkey = Bytes.toBytes(star + (Long.MAX_VALUE - System.currentTimeMillis()));
        Put put = new Put(rowkey);
        put.addColumn(Bytes.toBytes(COLUMN_FAMILY_NAME), Bytes.toBytes(COLUMN_NAME), Bytes.toBytes(content));
        table.put(put);
        WbHbaseDao.closeTable(table);
        return rowkey;
    }
}
  • WbRelationDao
/**
 * 用来表示用户之间关系的数据表,这里使用用户名字作为 rowkey,使用用户名字作为 fans 或者 attends 的列
 * 实际开发中应该使用用户 id,这里为了方便展示,故使用用户姓名
 *
 * @author cris
 * @version 1.0
 **/
@SuppressWarnings("JavaDoc")
public class WbRelationDao {

    public static final String COLUMN_FAMILY_FANS = "fans";
    public static final String COLUMN_FAMILY_ATTENDS = "attends";


    /**
     * 根据 star 的名字获取他的所有粉丝名字
     *
     * @param connection
     * @param star
     * @return 粉丝列表
     * @throws IOException
     */
    public List<String> getAllFans(Connection connection, String star) throws IOException {
        Table table = connection.getTable(TableName.valueOf(WbHbaseDao.WB_RELATION_TABLENAME));
        // star 名字作为 rowkey ,搜索对应的所有粉丝列表
        Get get = new Get(Bytes.toBytes(star));
        get.addFamily(Bytes.toBytes(COLUMN_FAMILY_FANS));
        //Result result = table.get(get);

        //这里使用 Optional 包装一下,空指针直接报错
        Optional<Result> result = Optional.of(table.get(get));
        /*如果 star 没有粉丝列表也不会报错,返回的是一个没有数据的 cell[]*/
        Cell[] cells = result.get().rawCells();
        List<String> list = new ArrayList<>();
        for (Cell cell : cells) {
            list.add(new String(CellUtil.cloneQualifier(cell), StandardCharsets.UTF_8));
        }

        return list;
    }
  • WbInboxDao
/**
 * 和 inbox 包交互类
 *
 * @author cris
 * @version 1.0
 **/
@SuppressWarnings("JavaDoc")
public class WbInboxDao {

    private static final String COLUMN_FAMILY_INFO = "info";
    private static final int DEFAULT_MESSAGE_COUNT = 5;

    /**
     * 通知所有粉丝的收件箱查看关注的 star 新发布的微博
     *
     * @param connection 连接
     * @param star       star 名字
     * @param rowkey     star 新发布的微博 rowkey
     * @param fans       粉丝列表们
     */
    public void receiveMessage(Connection connection, String star, byte[] rowkey, List<String> fans) throws IOException {
        Table table = connection.getTable(TableName.valueOf(WbHbaseDao.WB_INBOX_TABLENAME));
        Put put;
        for (String fan : fans) {
            put = new Put(Bytes.toBytes(fan));
            put.addColumn(Bytes.toBytes(COLUMN_FAMILY_INFO), Bytes.toBytes(star), rowkey);
            table.put(put);
        }
        WbHbaseDao.closeTable(table);
    }
  1. 测试
    /**
     * 获取指定 star 的所有粉丝列表,测试 ok
     *
     * @throws IOException
     */
    @Test
    public void testGetAllFans() throws IOException {
        List<String> allFans = wbController.getWbService().getWbRelationDao().getAllFans(WbHbaseDao.getConnection(), "james");
        allFans.forEach(System.out::println);

//        List<String> allFans = wbController.getWbService().getWbRelationDao().getAllFans(WbHbaseDao.getConnection(), "curry");
//        allFans.forEach(System.out::println);
    }

    /**
     * 整合功能测试(发布微博) ok
     *
     * @throws IOException
     */
    @Test
    public void testPublishWb() throws IOException {
        wbController.publishWb("james", "i like f");
        LOGGER.warn("发布成功!");
    }

③ 添加关注用户功能实现

a、在微博用户关系表中,对当前主动操作的用户添加新关注的好友

b、在微博用户关系表中,对被关注的用户添加新的粉丝

c、微博收件箱表中添加所关注的用户发布的微博

  1. Service 层
    /**
     * 用户关注 star
     *
     * @param star 名字
     * @param fan  粉丝
     * @throws IOException
     */
    public void attend(String star, String fan) throws IOException {
        Connection connection = WbHbaseDao.getConnection();
        // star 的粉丝列表更新 ok
        wbRelationDao.addFans(connection, star, fan);
        // fan 的 attend 列表更新 ok
        wbRelationDao.addAttend(connection, star, fan);
        // fan 的收件箱收到 star 最近更新的 5 条微博
        wbInboxDao.flush(connection, star, fan);
    }
  1. Dao 层
  • WbRelationDao
    /**
     * 用户的 fans 列组添加粉丝
     *
     * @param connection
     * @param star       明星
     * @param fan        粉丝
     * @throws IOException
     */
    public void addFans(Connection connection, String star, String fan) throws IOException {
        addFansOrStar(connection, fan, star, COLUMN_FAMILY_FANS);
    }

    /**
     * 用户的 attends 列组添加 star
     *
     * @param connection
     * @param star
     * @param fan
     * @throws IOException
     */
    public void addAttend(Connection connection, String star, String fan) throws IOException {
        addFansOrStar(connection, star, fan, COLUMN_FAMILY_ATTENDS);
    }

    /**
     * 实际的添加 fan 或者 star 的方法
     *
     * @param connection   连接
     * @param column       star 或 fan 的名字
     * @param rowkey       用户名字
     * @param columnFamily 列族(每个人的 fans 列族或者 attends 列族)
     * @throws IOException
     */
    private void addFansOrStar(Connection connection, String column, String rowkey, String columnFamily) throws IOException {
        Table table = connection.getTable(TableName.valueOf(WbHbaseDao.WB_RELATION_TABLENAME));
        Put put = new Put(Bytes.toBytes(rowkey));
        put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column), null);
        table.put(put);
        WbHbaseDao.closeTable(table);
    }
  • WbInboxDao
    /**
     * 用户关注某个用户后,收件箱自动获取该 star 最新的 5 条微博数据
     *
     * @param connection 连接
     * @param star       明星名字
     * @param fan        粉丝名字
     */
    public void flush(Connection connection, String star, String fan) throws IOException {
        Table table = connection.getTable(TableName.valueOf(WbHbaseDao.WB_CONTENT_TABLENAME));
        Scan scan = new Scan();
        scan.setStartRow(Bytes.toBytes(star));
        /*这里使用倒数第三大的 | 符号来组装 rowkey,需要根据开始的 rowkey 和 结束的 rowkey 得到该明星发布的最新的 5 条微博*/
        scan.setStopRow(Bytes.toBytes(star + "|"));
        ResultScanner resultScanner = table.getScanner(scan);
        List<Put> puts = new ArrayList<>(DEFAULT_MESSAGE_COUNT);
         /*最好在添加每条微博数据的时候自定义时间戳,否则极易因为程序执行太快导致时间戳相同而无法插入多条数据*/
        int i = 0;
        /*这里得到的每一个 result 其实就是每一个 rowkey 对应的数据*/
        for (Result result : resultScanner) {
            String message = new String(result.getRow(), StandardCharsets.UTF_8);
            Put put = new Put(Bytes.toBytes(fan));
			put.addColumn(Bytes.toBytes(COLUMN_FAMILY_INFO), Bytes.toBytes(star), 
                    		System.currentTimeMillis() + (++i), Bytes.toBytes(message));
            puts.add(put);
            if (puts.size() == 5) {
                break;
            }
        }

        table = connection.getTable(TableName.valueOf(WbHbaseDao.WB_INBOX_TABLENAME));
        table.put(puts);
        WbHbaseDao.closeTable(table);
    }
  1. 测试
    /**
     * 测试 star 的粉丝添加 ok
     *
     * @throws IOException
     */
    @Test
    public void testAddFans() throws IOException {
        wbController.getWbService().getWbRelationDao().addFans(WbHbaseDao.getConnection(), "james", "cris");
        wbController.getWbService().getWbRelationDao().addFans(WbHbaseDao.getConnection(), "james", "curry");
        wbController.getWbService().getWbRelationDao().addFans(WbHbaseDao.getConnection(), "james", "harden");
        LOGGER.warn("star 的粉丝列表添加成功!");
    }

    /**
     * 测试粉丝的 attend 添加 ok
     *
     * @throws IOException
     */
    @Test
    public void testAddAttend() throws IOException {
        wbController.getWbService().getWbRelationDao().addAttend(WbHbaseDao.getConnection(), "james", "cris");
        wbController.getWbService().getWbRelationDao().addAttend(WbHbaseDao.getConnection(), "james", "curry");
        wbController.getWbService().getWbRelationDao().addAttend(WbHbaseDao.getConnection(), "james", "harden");
        LOGGER.warn("用户的关注列表添加成功");
    }

    /**
     * 整合功能测试(关注 star) ok
     *
     * @throws IOException
     */
    @Test
    public void testAttend() throws IOException {
        wbController.attend("james", "durant");
        LOGGER.warn("关注成功!");
    }

④ 移除(取关)用户功能实现

a、在微博用户关系表中,对当前主动操作的用户移除取关的好友(attends)

b、在微博用户关系表中,对被取关的用户移除粉丝

c、微博收件箱中删除取关的用户发布的微博

  1. Controller 层
    /**
     * 用户取消关注 star
     *
     * @param star
     * @param fan
     * @throws IOException
     */
    public void removeAttend(String star, String fan) throws IOException {
        wbService.removeAttend(star, fan);
    }
  1. Service 层
    /**
     * 用户取消关注
     *
     * @param star
     * @param fan
     */
    public void removeAttend(String star, String fan) throws IOException {
        Connection connection = WbHbaseDao.getConnection();
        wbRelationDao.removeFan(connection, star, fan);
        wbRelationDao.removeAttend(connection, star, fan);
        wbInboxDao.removeFlush(connection, star, fan);
    }
  1. Dao 层
  • wbRelationDao
    /**
     * star 的粉丝列表移除粉丝
     *
     * @param connection
     * @param star
     * @param fan
     * @throws IOException
     */
    public void removeFan(Connection connection, String star, String fan) throws IOException {
        removeFansOrStar(connection, fan, star, COLUMN_FAMILY_FANS);
    }

    /**
     * fan 的 attend 列表移除 star
     *
     * @param connection
     * @param star
     * @param fan
     * @throws IOException
     */
    public void removeAttend(Connection connection, String star, String fan) throws IOException {
        removeFansOrStar(connection, star, fan, COLUMN_FAMILY_ATTENDS);
    }

    /**
     * 实际的移除 fan 或者 star 的方法
     *
     * @param connection   连接
     * @param column       star 或 fan 的名字
     * @param rowkey       用户名字
     * @param columnFamily 列族(每个人的 fans 列族或者 attends 列族)
     * @throws IOException
     */
    private void removeFansOrStar(Connection connection, String column, String rowkey, String columnFamily) throws IOException {
        Table table = connection.getTable(TableName.valueOf(WbHbaseDao.WB_RELATION_TABLENAME));
        Delete delete = new Delete(Bytes.toBytes(rowkey));
        delete.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column));
        table.delete(delete);
        WbHbaseDao.closeTable(table);
    }
  • wbInboxDao
    /**
     * 粉丝取消关注后自动移除该粉丝收件箱,关于取消关注的 star 的所有消息
     *
     * @param connection 连接
     * @param star       明星名字
     * @param fan        粉丝名字
     * @throws IOException
     */
    public void removeFlush(Connection connection, String star, String fan) throws IOException {
        Table table = connection.getTable(TableName.valueOf(WbHbaseDao.WB_INBOX_TABLENAME));
        Delete delete = new Delete(Bytes.toBytes(fan));
        delete.addColumn(Bytes.toBytes(COLUMN_FAMILY_INFO), Bytes.toBytes(star));
        table.delete(delete);
        WbHbaseDao.closeTable(table);
    }

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值