zoukankan      html  css  js  c++  java
  • 社交媒体(朋友圈、微博、QQ空间)开发一网打尽,PC端移动端都有!——源码来袭!

    一.应用场景

          曾几何时,社交媒体已经驻扎到了几乎每个人的生活中。看看你身边的朋友,有几个不玩朋友圈的?就算他不玩朋友圈,那也得玩微博吧。再没有底线,也得玩QQ空间。

          不过,作为程序员的我们,没事还是少上这些社交媒体为妙。反而,我们应该去考虑——如何实现这些社交媒体的开发呢?

          我相信一定有不少朋友跟我一样,思考过这个问题,今天我就把自己的成果分享给大家,不一定完美,但求抛砖引玉,请大家多指教!

    二.运行效果

          下面是我做好的PC端和安卓端的运行界面。让大家先有一个直观的了解。后面我会进一步分享我设计的过程。

           1.查看我的“圈子”                         2.查看“testQQ”的主页                  3.发送动态

                      

          

    三.开发要点

    1.CS架构——服务端与客户端的核心类——两大引擎

          本文所实现的朋友圈功能是基于NPush构建的,其核心组件是用于社交媒体开发的服务端引擎IServerEngine和客户端引擎IMomentsClient 。

    (1)服务端引擎IServerEngine:

        //NPush服务端引擎接口。
        public interface IServerEngine
        {  
    //NPush服务器配置选项。必须在Initialize方法调用之前设置才有效。 NPushConfiguration Configuration { get; set; } //日志文件的路径。默认为运行目录下的AppLog.txt文件。 string LogFilePath { get; set; }
    //当前连接数。 int UserCount { get; }
    //获取在线用户列表。 List<string> GetUsers();
    //初始化。 // 参数: // port: // 为客户端提供服务的端口 // verifier: // 用户帐号密码验证器。如果不需要验证,可以传入null。 // friendProvider: // 好友关系提供者 void Initialize(int port, IUserVerifier verifier, IFriendProvider friendProvider, IMomentsPersister persister); }

    (2)客户端引擎IMomentsClient :

    public interface IMomentsClient : IDisposable
        {    
    //与节点服务器之间的TCP连接是否正常? bool Connected { get; }
    // 当前对象是否已经被释放? bool IsDisposed { get; }
    //当与节点服务器之间的TCP连接断开时,触发此事件。(连接断开将会自动重连) event CbGeneric ConnectionInterrupted;
    //当朋友圈有新的动态时,触发该事件。参数:ownerID - infoType - pullIndex event CbGeneric<string, int, long> NewMomentNotified;
    //当朋友圈有我相关的新评论时,触发该事件。参数:remarkUserID - ownerID - pushIndex - remarkType - //content - tosb event CbGeneric<string, string, long, int, string, string> NewRemarkNotified;
    //当同名的帐号登录当前连接的服务器时,自己将被挤掉线(不再自动重连),触发此事件。 event CbGeneric PushedOut;
    //当与服务器重连上并登录完成后,触发此事件。请注意查看事件参数LoginResultType,如果登录失败,则表示当前客户端实例已经失效。 event CbGeneric<LoginResultType> RelogonCompleted;
    //当朋友圈消息被删除时,触发此事件。参数:ownerID - pushIndex event CbGeneric<string, long> RemoveMomentNotified; //当评论被删除时,触发此事件。参数:ownerID - pushIndex - remarkPassiveID event CbGeneric<string, long, string> RemoveRemarkNotified; //当调用Pull方法请求moment列表,得到服务器的回复时,触发此事件。 event CbGeneric<UserMoments> ResponseMomentReceived;
    //初始化,并登录到服务器。 // 参数: // serverIP: // 服务器的IP // port: // 服务器的端口 // userID: // 登录帐号 // pwd: // 登录密码 // 返回结果: // 登录结果 LoginResultType Initialize(string serverIP, int port, string userID, string pwd);
    //拉取我的朋友们发的消息。当收到服务器回复,将触发ResponseMomentsReceived事件。 // 参数: // startPullIndex: // 从哪个index的消息开始拉取(不包含该index对应的那条)。如果startPullIndex小于0,表示拉取最新的N条。 // latest: // latest为true,表示拉取更新的(大于startPullIndex);为false表示拉取之前的(小于startPullIndex)。 void Pull(long startPullIndex, bool latest);
    //拉取目标用户所发的消息。当收到服务器回复,将触发ResponseMomentsReceived事件。 // 参数: // ownerID: // 所要拉取的目标用户的ID // startPushIndex: // 从哪个index的消息开始拉取(不包含该index对应的那条)。如果startPushIndex小于0,表示拉取最新的N条。 // latest: // latest为true,表示拉取更新的(大于startPullIndex);为false表示拉取之前的(小于startPullIndex)。 void Pull(string ownerID, long startPushIndex, bool latest); // 在朋友圈发送消息。 // 参数: // msgType: // 消息类型。 // content: // 消息内容 // tag: // 附加tag // 返回结果: // 返回PassiveID string Push(int msgType, byte[] content, string tag);
    //朋友圈某条消息评论、点赞、回复评论。 // 参数: // ownerID: // 目标消息的发送者ID // momentPushIndex: // 目标消息的PushIndex // remarkType: // 评论的类型。自定义。比如0表示赞,1表示评论,2表示回复。 // content: // 评论或回复的内容 // tosb: // 如果是评论回复,则@用户的帐号 // 返回结果: // 返回PassiveID string Remark(string ownerID, long momentPushIndex, int remarkType, string content, string tosb);
    //删除自己发的某条消息。 // 参数: // momentPushIndex: // 目标消息的PushIndex void RemoveMoment(long momentPushIndex);
    //删除自己发的某条消息。 // 参数: // momentPassiveID: // 目标消息的PassiveID void RemoveMoment(string momentPassiveID);// 删除自己发的某条评论。 // 参数: // ownerID: // 目标消息的发送者ID // momentPushIndex: // 目标消息的PushIndex // remarkPassiveID: // 目标评论的PassiveID void RemoveRemark(string ownerID, long momentPushIndex, string remarkPassiveID); }

           以上是两个类的定义,更具体的相关说明我将在下文中为大家逐一介绍。

    2.基本动作——推(push)与拉(pull)

          无论朋友圈、微博还是QQ空间,都有两个基本动作——推(push)与拉(pull)。推出消息,也就是发动态;拉取消息也就是刷动态。当然,更进一步来说,所谓的社交媒体系统,本质上还是一个通信系统。“发送”与“接收”是任何通信中亘古不变的主题。

          所以,首先得设计好这两个基本动作。

    (1)推(push)——API说明

            // 在朋友圈发送消息。     
            // 参数:
            //   msgType:
            //     消息类型。      
            //   content:
            //     消息内容       
            //   tag:
            //     附加tag     
            // 返回结果:
            //     返回PassiveID
            string Push(int msgType, byte[] content, string tag);

          push方法比较简单,msgType这个参数用在服务端对消息做一些自定义处理的时候。目前的例子中服务端对消息是自动转发的,不涉及到自定义处理。

    (2)拉(pull)——API说明

            // 拉取我的朋友们发的消息。当收到服务器回复,将触发ResponseMomentsReceived事件。    
            // 参数:
            //   startPullIndex:
            //     从哪个index的消息开始拉取(不包含该index对应的那条)。如果startPullIndex小于0,表示拉取最新的N条。     
            //   latest:
            //     latest为true,表示拉取更新的(大于startPullIndex);为false表示拉取之前的(小于startPullIndex)。
            void Pull(long startPullIndex, bool latest);
    // 拉取目标用户所发的消息。当收到服务器回复,将触发ResponseMomentsReceived事件。 // 参数: // ownerID: // 所要拉取的目标用户的ID // startPushIndex: // 从哪个index的消息开始拉取(不包含该index对应的那条)。如果startPushIndex小于0,表示拉取最新的N条。 // latest: // latest为true,表示拉取更新的(大于startPullIndex);为false表示拉取之前的(小于startPullIndex)。 void Pull(string ownerID, long startPushIndex, bool latest);

         pull方法的两个重载,前者是用于拉取“圈子”的动态,后者是用于拉取某位“个人”的动态,这两种模式我将在接下来的“3.“圈子”VS“个人”——两种模式”中进行介绍。

          至于startPushIndex,则涉及到我对于“动态”的设计——每条动态都有一个全局唯一的Index。而Pull方法每次都是默认拉取20条冬天,因此,在拉取动态的时候,我们就可以指定起始的Index,也就是startPushIndex,从而获取指定的20条动态。关于这个Index,在接下来的3中,我们可以进一步了解。

          而latest参数则是表示上拉还是下拉——上拉就是向上刷新,刷取新动态;下拉也就是刷取旧动态。经常玩朋友圈的朋友对这两个操作一定不会陌生。

    (3)消息接收的地方——ResponseMomentReceived事件

    // 当调用Pull方法请求moment列表,得到服务器的回复时,触发此事件。
     event CbGeneric<UserMoments> ResponseMomentReceived;

    正如注释所指出的,每次拉取的动态都将由这个事件来返回。我们进一步来看看事件参数UserMoments的定义:

      public class UserMoments
        {
            public UserMoments();
    //是否是请求目标用户所发的朋友圈列表。 public bool IsOwnerList { get; set; } public Dictionary<long, PushedMessage> MessageList { get; set; }
    //如果是请求自己的朋友圈动态,则为自己的ID。如果是请求某个好友Push的消息列表,则为好友的ID。 public string UserID { get; set; } }

          其中MessageList 就是动态信息的集合,这个字典的key就是我们之前说到的Index,字典的Value:PushedMessage,这个我们将在“4.数据库实体”中进一步说明。

    3.“圈子”VS“个人”——两种模式

          我们知道,朋友圈中既可以查看圈子中的动态,也可以查看某个人的动态。所谓圈子,在朋友圈和QQ空间中就是好友关系,在微博中就是关注关系——实质上并没有区别,所以我们就用好友来概括。

          这个好友关系定义在服务端。来看我的简单实现:

       public class TestFriendProvider : IFriendProvider
        {
            private List<string> list = new List<string>();
    
            public TestFriendProvider()
            {
                for (int i = 0; i < 99; i++)
                {
                    list.Add("test" + i.ToString("00"));
                }
                list.Add("testQQ");
            }
    
            public List<string> GetFriends(string userID)
            {
                if (string.Equals(userID, "testQQ"))
                {
                    List<string> l = new List<string>();
                    l.Add("test01");
                    l.Add("test02");
                    l.Add("test03");
                    return l;
                }
                return this.list;
            }
        }

          这个类的实例将注入到服务端引擎中

    this.serverEngine.Initialize(int.Parse(ConfigurationManager.AppSettings["Port"]), new MyUserVerifier(), new TestFriendProvider(), this.persister);

          服务端引擎届时将会回调GetFriends方法,从而将某用户发送的动态推送给他的好友。我在例子中采用的是简单的实现,大家要实现自己的朋友关系,只需要自定义IFriendProvider接口的实现即可。

          顺便介绍下服务端的另一个注入的接口,IUserVerifier,用于注入用户登录验证的逻辑。下面看下例子中的简单实现:

       class MyUserVerifier:IUserVerifier
        {
            private List<string> list = new List<string>();
    
            public MyUserVerifier()
            {
                for (int i = 0; i < 10; i++)
                {
                    list.Add("test" + i.ToString("00"));
                }
                list.Add("testQQ");
            }
            public bool VerifyUser(string userID, string password)
            {           
                return this.list.Contains(userID);
            }
        }

          至于某个人的动态则根据对方的ID进行获取即可。

    4.数据库实体

    (1)PushedMessage 

        [Serializable]
        public class PushedMessage : IEntity<long>
        {
            public const string _IsValid = "IsValid";
            public const string _OwnerID = "OwnerID";
            public const string _PassiveID = "PassiveID";
            public const string _PushIndex = "PushIndex";
            public const string TableName = "PushedMessage";
    
            public PushedMessage();
            public PushedMessage(string owner, long index, PushMomentsContract contract);
            //     持久化数据库中的自增ID。
            public long AutoID { get; set; }
            public byte[] Content { get; set; }
            public int InfoType { get; set; }
    // 是否有效。如果消息被发送者删除,则变成无效。 public bool IsValid { get; set; } public string OwnerID { get; set; } // 由客户端生成的ID。(UserID_时间序列号) public string PassiveID { get; set; } public long PushIndex { get; set; } public DateTime PushTime { get; set;
    // 评论列表。 [NotDBField] public List<Remark> RemarkList { get; set; } public string Tag { get; set; } public long GetPKeyValue(); public override string ToString(); }

        对应的数据库表结构:

    (2)PulledMessage 

       [Serializable]
        public class PulledMessage : IEntity<long>
        {
            public const string _GuestID = "GuestID";
            public const string _PullIndex = "PullIndex";
            public const string TableName = "PulledMessage";
    
            public PulledMessage();
            public PulledMessage(string guest, PushedMessage msg, long index);
    
            public long AutoID { get; set; }
            public string GuestID { get; set; }
            public string OwnerID { get; set; }
            public long PullIndex { get; set; }
            [NotDBField]
            public PushedMessage PushedMessage { get; set; }
            public long PushIndex { get; set; }
    
            public long GetPKeyValue();
            public override string ToString();
        }

    对应的数据库表结构:

    (3)momentremark

       [Serializable]
        public class MomentRemark : IEntity<long>
        {
            public const string _AutoID = "AutoID";
            public const string _IsValid = "IsValid";
            public const string _OwnerID = "OwnerID";
            public const string _PassiveID = "PassiveID";
            public const string _PushIndex = "PushIndex";
            public const string TableName = "MomentRemark";
    
            public MomentRemark();
            public MomentRemark(string remarkUser, RemarkContract contract);
    
            public long AutoID { get; set; }     

    // 评论的内容。 public string Content { get; set; }
    // 是否有效。如果消息被发送者删除,则变成无效。 public bool IsValid { get; set; }

    // 目标Moment的Owner的ID。 public string OwnerID { get; set; }
    // 由客户端生成的ID。(UserID_时间序列号) public string PassiveID { get; set; }
    // 目标Moment的PushIndex。 public long PushIndex { get; set; }

    // 评论的时间。 public DateTime RemarkTime { get; set; }
    // 评论的类型。 public int RemarkType { get; set; }

    // 评论者的ID。 public string RemarkUserID { get; set; }

    // 消息标记,10个字符(月日时分秒)。由客户端赋值。 public string Token { get; set; }

    // 如果是评论回复,则@用户的帐号 public string Tosb { get; set; } public long GetPKeyValue(); public Remark ToRemark(); public override string ToString(); }

    对应的数据库表结构:

          至于如何与数据库交互,这个我已经封装好了 

     this.persister 
    = new DBMomentsPersister(new MysqlDataConfiguration(ConfigurationManager.AppSettings["DBIP"], int.Parse(ConfigurationManager.AppSettings["DBPort"]), ConfigurationManager.AppSettings["UID"], ConfigurationManager.AppSettings["Pwd"], ConfigurationManager.AppSettings["DBName"])); this.serverEngine.Initialize(int.Parse(ConfigurationManager.AppSettings["Port"]), new MyUserVerifier(), new TestFriendProvider(), this.persister);

          只用构造好注入到服务端引擎即可。 

    四.部署说明

        源码下载 

        安卓客户端下载

        说明: 

        1.压缩包中有SQL脚本,我使用的是Mysql数据库。

        2.服务端客户端均有配置文件

        3.客户端登陆账号为test01~test10,以及默认的testQQ。 

  • 相关阅读:
    神奇的flex布局
    reset、revert、rebase
    Vue.filter过滤器
    moment.js时间格式化总结
    Vue之组件大全
    过滤器filter
    Vue之animate
    Vue之axios
    Mac OS系统上测试PHP代码前的准备工作 | 使用XAMPP搭建Apache服务器的步骤
    Python中的标识符、关键字、变量、语句、注释、模块
  • 原文地址:https://www.cnblogs.com/laisilaisi/p/6393200.html
Copyright © 2011-2022 走看看