代码改变世界

仿花田:相亲网站 意中人 已在GitHub上开源

2013-12-04 10:26  stoneniqiu  阅读(11571)  评论(134编辑  收藏

     在园友的强烈呼唤下,我还是负责任的分享给大家,因为对代码比较熟悉一下,还是有些问题要说明,不然别人看起来会比较费劲。说实话除了这个bootstrap的界面风格和这件事情本身对大家有吸引力之外,内部的逻辑,结构,可能有些捉襟见肘,会让大家见笑,大牛们完全可以略过.现在源码已经让我推到GitHub上面去了。我先从头到尾的介绍一下,然后说一些存在的问题。也希望大家给出更好的建议。

一、结构说明

    1.MVC 我先说模型,共有20几张表,支持数据迁移。BaseInfos(基本资料),DetailInfos(详细资料),LoveViews(恋爱观) 应该是做到一张User表中的,脑抽的我把他们分成了三张表,因为他们是分别存储的。下载之后,改成你自己的数据库,运行就能创建新表了。

using System;
using System.Collections.Generic;
using System.Data.Entity;
using Findlover.Migrations;

namespace Findlover.Models
{
    public class LoveDb:DbContext
    {
        private const string DbNameOrDbConnectionstring =
           
          "Data Source=your-pc;Initial Catalog=LoveDB;User ID=sa;Password=code;Persist Security Info=True";  
        //Persist Security Info=True
        public DbSet<BaseInfo> BaseInfos { get; set; }// 基本资料
        public DbSet<User> Users { get; set; }// 用户
        public DbSet<Requirement> Requirements { get; set; }//择偶需求
        public DbSet<DetailInfo> DetailInfos { get; set; }//详细资料
        public DbSet<LoveView> LoveViews { get; set; }//恋爱观
        public DbSet<InfoStatistic> InfoStatistics { get; set; }// 信息统计,统计用户资料的完整度。避免每次都要去三张表数一遍。
        public DbSet<UserHot> UserHots { get; set; }//用户的人热度
        public DbSet<Praise> Praises { get; set; }//赞 也就是喜欢
        public DbSet<State> States { get; set; }// 状态
        public DbSet<Iamgbox> Iamgboxes { get; set; }// 相册
        public DbSet<Message> Messages { get; set; }//私信
        public DbSet<LoginLog> LoginLogs { get; set; }// 登录日志记录
        public DbSet<Role> Roles { get; set; }//角色
        public DbSet<Hello> Hellos { get; set; }//打招呼
        public DbSet<RoleLog> RoleLogs { get; set; }// 角色的日志
        public DbSet<Authority> Authoritys { get; set; }// 权限
        public DbSet<AdminStatistic> AdminStatistics { get; set; }//管理员操作统计 这个功能没有多大意义 你可拿掉
        public DbSet<VisitLog> VisitLogs { get; set; }// 访问记录
        public DbSet<Report> Reports { get; set; }//举报
        public DbSet<ReportLog> ReportLogs { get; set; }//举报日志
        public DbSet<MyLove> MyLoves { get; set; }// 我喜欢的人
        public DbSet<DisLove> DisLoves { get; set; }//我不喜欢的人 相当于黑名单
        public DbSet<Topic> Topics { get; set; }// 话题
        public DbSet<Comment> Comments { get; set; }//评论
        public DbSet<EnjoyTopic> EnjoyTopics { get; set; }//感兴趣的话题
        public LoveDb() : base(DbNameOrDbConnectionstring)
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<LoveDb, Configuration<LoveDb>>());
        }
    }
}

 2. 控制器: 共有8个控制器,其中BaseController 里面集合一些常用的方法和属性,其他都继承这个控制器,其他看名字你也知道是干啥的,Interactive是对应的Topic,本来是打算一个专门用来做交互的控制器。现在只有话题。但可能让大家恼火的就是,这分的还不细致,不准确。之前自己偷懒,什么方法写在什么控制器没有明确的定义清楚,这对以后维护和扩展造成不方便。

   

3.视图:主要是基于Bootstrap2,Jquery1.7.2.,form.js  注册的日期控件和Topic的编辑器用的是KendoUi, 小伙伴们可以去换成Ckeditor,而Bootstrap用的地方主要是在

  Layout的导航,后台导航,举报、私信、图片上传的模态对话框,在主页面使用它的栅格比较少,因为老是和导航对不齐,就放弃了。或许是我用的不对。Jquery用来交互,获取数据。form.js 用的比较多,很多地方是form提交,没有用MVC自带的Create。原因就是不好看,而且不好控制,ie下面会走样。

 

二、前端特别说明

  这里特别要说明的就是一些函数和窗口的触发

  1.图片上传和举报框。

 在用户主页的视图和PersonalInfo视图中会有这两个相似的html代码,是用来触发图片上传框的。

  <div class="infopercentright">上传4张照片,资料完整度+8%  <a class="infotag" id="imgup"  href="#imgupload" data-toggle="modal" title="上传照片"><i class="icon-camera"></i></a> </div>
   <a class="infotag" id="imgupload"  href="#imgupload" data-toggle="modal" title="上传照片"><i class="icon-camera"></i></a>  

图片上传的模态对话框:

    <div id="imgupload" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
        <div class="modal-header">
            <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
            <h4>上传图片</h4>  
        </div>
        <div class="modal-body">
            <div class="uploadbox" data-count="0" style="display: inline;">
                <span class="closespan" title="删除照片">&times;</span>
                <div class="imgcontainer">
                    <div class="add">+</div>
                    <div class="stt">点击上传</div>
                </div>
                <span class="infospan">
                    <img src='../../Content/Photos/loading.gif' />正在上传...</span>
            </div> 
            <div class="uploadbox" data-count="0"  >
                <span class="closespan" title="删除照片">&times;</span>
                <div class="imgcontainer">
                    <div class="add">+</div>
                    <div class="stt">点击上传</div>
                </div>
                <span class="infospan"> <img src='../../Content/Photos/loading.gif' />正在上传...</span>
            </div> 
            <div class="uploadbox"data-count="0" >
                <span class="closespan" title="删除照片">&times;</span>
                <div class="imgcontainer">
                    <div class="add">+</div>
                    <div class="stt">点击上传</div>
                </div>
                <span class="infospan"> <img src='../../Content/Photos/loading.gif' />正在上传...</span>
            </div> 
            <div class="uploadbox"data-count="0"  >
                <span class="closespan" title="删除照片">&times;</span>
                <div class="imgcontainer">
                    <div class="add">+</div>
                    <div class="stt">点击上传</div>
                </div>
                <span class="infospan"> <img src='../../Content/Photos/loading.gif' />正在上传...</span>
            </div> 
            <div class="inputdiv"><input type="text" disabled="disabled" id="Remark" name="Remark" placeholder="补充说明下~"/></div>
        </div>
        <div class="modal-footer">
            上传大小在8k-10M之间 <span class="imguploadmessage"></span>
            <button class="btn btn-success "  disabled="disabled"   id="imgsubmit">发布</button>
            <form action="/User/UpLoadPhoto" method="POST" enctype="multipart/form-data" name="ImgForm" id="ImgForm">
                <input type="file" name="file" id="imgFlie"  required="required" />
                <input type="submit" name="subt" value="上传图片" />
            </form>
        </div>
    </div>
View Code

生成的图像如下:

举报也同样的做法: 但举报需要提供更多的信息,所以加了很多data属性。用脚本触发对话框

  <span class="report" data-reportType="personal" data-relateId="@Model.UserId" data-content="@Model.UserName" data-userId="@Model.UserId" data-userName="@Model.UserName" data-sextag="@ViewBag.Sex">拉黑/举报</span>    
 //举报框----------------------report--------------------------------------------
    $(".report").each(function () {
        $(this).click(function () {
            var id = $(this).attr("data-userId");
            var name = $(this).attr("data-userName");
            var sextag = $(this).attr("data-sextag");//对方性别
            var type = $(this).attr("data-reportType");
            var relateId = $(this).attr("data-relateId");
            var content = $(this).attr("data-content");

            $("#reportedname").html(name).attr("data-id", id);//被举报人id
            $(".reportinfo span").html(sextag);
            $("#reportName").html(name);
            $("#reporttype").val(type);//举报类型
            $("#reporttype").attr("data-relateId", relateId);
            $("#reporttype").attr("data-content", content);

            //值是举报或者拉黑这个人 初始化
            $(".divrow:eq(1)").show();
            $("#reportcontent").hide();
            $(".reportmessage").html();
            $("#reportcontent").html(content);
            //不同类型,不同对话
            switch (type) {
                case "personal":
                    $(".divrow:eq(1)").hide();
                    break;
                case "message":
                    $("#reportType").html("发给我的私信");
                    $("#IsReport").attr("checked", "checked");
                    $("#reportcontent").show();
                    break;
                default:
            }

            $("#IsReport").click(function () {
                var ss = $(this).is(':checked');
                if (ss) {
                    $(".divrow:eq(1),#reportcontent").slideDown();
                } else {
                    $(".divrow:eq(1),#reportcontent").slideUp();
                }
            });
            $('#report_box').modal('show');
        });
    });

 举报框:

 我想大家也明白了,就是这样的一回事,脚本没有压缩,大家可以去看。其他像 喜欢,私信,都是一个道理,定义好一个样式,主要是样式名称,在layout里面写脚本,这样就很方便了。

2.form提交:

 用form.js,能很好的控制整个提交过程,BeginForm,隐藏的form,图片上传的form。

 编辑基本资料:

 <div class="infoedit hide" >
            @using (Html.BeginForm("UpdateBaseInfo", "User", FormMethod.Post, new { name = "Form1", id = "Form1" }))
            {
                <table class="">
                    <tr>
                        <td>身高:</td>
                        <td>
                            <select name="Height" id="Height"></select></td>
                        <td>学历:</td>
                        <td>
                            <select name="Education" id="Education"></select></td>
                    </tr>
                    <tr>
                        <td>月收入:</td>
                        <td>
                            <select name="MonthlyIncome" id="MonthlyIncome"></select></td>
                        <td>毕业院校:</td>
                        <td>
                            <input type="text" name="School" id="School" value="@Model.BaseInfo.School"/></td>
                    </tr>
                    <tr>
                        <td>工作单位:</td>
                        <td>
                            <select name="Company" id="Company"></select></td>
                        <td>居住地:</td>
                        <td>
                          <select name="ResidenceProvince" id="ResidenceProvince" style="width: 90px"></select>   <select name="ResidenceCity" id="ResidenceCity" style="width: 90px"></select></td>
                    </tr>
                    <tr>
                        <td>目前职位:</td>
                        <td>
                            <select name="Position" id="Position"></select></td>
                        <td>行业:</td>
                        <td>
                            <input type="text" id="Profession" readonly="readonly" name="Profession" value="生产/工艺/制造"></td>
                    </tr>
                    <tr>
                        <td>当前状态:</td>
                        <td>
                            <select name="State" id="State"></select></td>
                    </tr>
                </table>
              <button type="submit"  class="btn btn-info"> 保存</button> <span class="cancel-eidt">取消编辑</span>
            }
           
        </div>
View Code

 这样的方式用了很多,脚本基本上是下面这样,还可以加上beforeSend,complete. 其他项获取json数据这些,就是家常便饭了,没什么说的。

   $('#Form1').submit(function () {
        $(this).ajaxSubmit(options);
        return false;
    });
  var options = {
        dataType: 'json',
        success: processJson,
    };
function processJson(data) {
        // 还是返回json的好,再去更新。
        $(".infowrap").show();
        //......
        $("#sProfession").html(data.Profession);
        $("#sPosition").html(data.Position);
        $("#sSchool").html(data.School);
        $("#sState").html(data.State);
    }

 

二、后台逻辑

 其实这个部分是最没有含金量的,只要大家清楚业务流程了,逻辑再怎么也能写出来。我也没有用AOP来记录日志或者处理异常,用IOC/DI来解耦。这也是我的弱点。所以我就简单说明下。

1.符合基本条件的用户: 因为是相亲交友,所以要筛选用户。在BaseController中有个GetBaseUsers

/// <summary>
        /// 获取基本的异性集合 不可以允许未审核图片的人进来! 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        protected IEnumerable<User> GetBaseUsers(int id)
        {
            var lz = LoveDb.One((User n) => n.UserId == id);
            var dislikelist = GetDislikeList();//过滤的名单。 过掉我不喜欢的人。  //还要过掉不喜欢我的人?
          
            if (lz != null)
            {
                //选出所有异性中图片审核过的,资料审核过的人,且资料开放,没有被禁止的人
                return
                    LoveDb.UserAll()
                          .Where(n => n.Sex != lz.Sex  && n.IsVerified && n.IsVerifiedImg && n.IsOpen && n.Enable == 1&&!dislikelist.Contains(n.UserId)) .ToList(); } return null; }

 2.推荐的用户:推荐,动态,搜索的基本用户都是来自这里。 你可以稍微改下代码让他可以选择同性。而推荐和动态的用户 还需要满足一个用户的基本择偶要求。 就是比如我择偶的要求是

 158以上,江苏,22岁以上。那出现在这两个页面的用户就必须满足这三个条件是硬性指标,但学历,薪水是可以不匹配的。所以在推荐页面是这样的:

  public ActionResult RecommendPage()
        {
            var id = CheckValid();
            if (id == -1 || Session["uid"] == null)
            {
                return RedirectToAction("Logon", "User");//不知道有没有效果
            }
            var require = LoveDb.One((Requirement r) => r.UserId == id);
            var baseusers = GetBaseUsers(id).Where(n => PartMathUser(require, LoveDb.GetUninUser(n.UserId)));//找出所有正规的异性符合需求的。 
            ViewBag.Sex = GetMyself().User.Sex == "man" ? "" : "";
            var userlist = baseusers.Select(baseuser => GetRecommendUser(baseuser.UserId)).ToList();
            return PartialView(userlist.OrderByDescending(n => n.Rate.TotalRate));
        }

partMathUser 就是一个专门用来比对的函数。原理就是捞出来一个一个比对,三个条件全部对了就是true。搜索也是一个道理,前台把条件传过来,后台来处理。我想这个地方应该是很有优化的空间。

 3.推荐度和热度

  计算推荐度,就是看两者资料的匹配度,而热度大家可以去看模型就知道了,就是统计数据再综合排名。换成百分率。

        /// <summary>
        /// 用户热度,相互符合度,生活观契合度
        /// </summary>
        /// <param name="selfid">本人</param>
        /// <param name="otherid">比较者</param>
        /// <returns></returns>
        public RecommendRate GetRecommendRate(int selfid, int otherid)
        {
            var usersum = LoveDb.UserAll().Count;
            //得到两个人的需求表 来计算相互符合度
            var myre = LoveDb.One((Requirement r) => r.UserId == selfid);
            var youre = LoveDb.One((Requirement r) => r.UserId == otherid);
            var yourUnin = LoveDb.GetUninUser(otherid);
            var me = GetMyself();
            var rr = new RecommendRate
            {
                //用户热度排名UserHotRate
                UserHotRate = (double)(usersum - LoveDb.UserHotAllDes().FindIndex(n => n.UserId == otherid)) / usersum,
                ForMeRate = TomeRate(yourUnin, myre),
                ForOtherRate = TomeRate(me, youre),
                LoveViwRate = GetLoveViewRate(yourUnin, me)
            };
            rr.TotalRate = (rr.UserHotRate + rr.LoveViwRate + rr.ForMeRate + rr.ForOtherRate) / 4;

            return rr;
        }

其他就没啥好说的,各种return json,return partview() 我想这个是容易看明白的。

 

三、数据层

 1.LoveDb仓库,像单例模式,获取实例,用反射封装了Add,Delete,One,All等常用方法。  

 public class LoveDbRepository
    {
        private static LoveDbRepository _loveDb;

        private LoveDbRepository()
        {
        }

        public static LoveDbRepository GetIntance()
        {
            return _loveDb ?? (_loveDb = new LoveDbRepository());
        }

        #region  常规方法 Add Delete All One  Ones LastOne

        #region Add
        
        public void Add<T>(T t)
        {
            using (var loveDb = new LoveDb())
            {
                switch (t.GetType().Name)
                {
                    case "User":
                        loveDb.Users.Add(t as User);
                        break;
                    case "BaseInfo":
                        loveDb.BaseInfos.Add(t as BaseInfo);
                        break;
                    case "Requirement":
                        loveDb.Requirements.Add(t as Requirement);
                        break;
                    case "DetailInfo":
                        loveDb.DetailInfos.Add(t as DetailInfo);
                        break;
                    case "LoveView":
                        loveDb.LoveViews.Add(t as LoveView);
                        break;
                    case "InfoStatistic":
                        loveDb.InfoStatistics.Add(t as InfoStatistic);
                        break;
                    case "UserHot":
                        loveDb.UserHots.Add(t as UserHot);
                        break;
                     //..................break;
                }
                loveDb.SaveChanges();
            }
        }
........................

2.数据迁移。修改model,自动更新数据库。这个也是老生常谈了,也可以看博客 http://www.cnblogs.com/stoneniqiu/archive/2013/06/04/3117499.html 

namespace Findlover.Migrations
{
    using System.Data.Entity;
    using System.Data.Entity.Migrations;

    internal sealed class Configuration<TContext> : DbMigrationsConfiguration<TContext> where TContext : DbContext
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;    
        }

        protected override void Seed(TContext context)
        {
            
        }
    }
}

 再就是Lambda ,linq。到处都是,没有写一句sql语句。这个没什么说的。

 

四、问题和扩展

     大大小小的问题还是蛮多的,兼容性的,脚本的,逻辑的,性能的,结构的。这些都还要慢慢的改。所以有问题不要惊奇,是很正常的。像上传和文本编辑应该换uploadfy和ckeditor控件。这个版本还么有加入分页,或者换成流行的滚动加载。增加一些好玩的功能,这些就要看你们的了,尽情的去折腾吧。小丘比特谢谢你!

  祝你幸福~

 

 

 

五、说下开源

 CodePlex和gitHub合作了,CodePlex可以用git上传,而git也变成了这个windows风格: 方便大方好用。

 

在github的工程名称叫FindLover(说DreamLover更合适些吧),因为FindLover是先取的,名字想了很久才叫意中人。

 

总结下:现在MVC5已经出来了,我还在看MVC4,EntityFrameWork6也来了,还有VS2013,.NetFrameWork4.5。Bootstrap也到3了。Html5动画研究的还不咋地,还有各种前端的,后端的框架,目不暇接,人艰不拆啊。

新的技术提供了一些更好的方式,但是你更需要的是一个方向,一个ideal,几个一条路上的小伙伴。

需要源码的同学请 猛击这里!!!! https://github.com/stoneniqiu/FindLover  (帮助文档还有其他说明)

( 下载后可能又的问题。

    1.缺少引用:请在package1中找缺少的引用。 建议安装Nuget ,会自动给你还原。

    2.不能生产表格。是因为没有EntityFramework.  没有启用codefirst 功能。可以参考博客:http://www.cnblogs.com/stoneniqiu/archive/2013/06/04/3117499.html

     我晚上再补上创建表的SQL命令。 数据库我不传了。

    3.缺少kendo.MVC.dll 这个dll没有包含在package1中,我也奇怪。漏掉了。可以在http://download.csdn.net/my/uploads 下载。 晚上我也会更新到github上去。

    4.缺少引用的dll中,只有Nlog,kendo.MVC,Web.Provider是有用的,其他的缺少的dll可以移除。是因为之前做过其他的测试留下的。

    还有问题可以给我留言

      )

最新版本:http://pan.baidu.com/s/1pJoiM1P

DB:http://pan.baidu.com/s/1gdkvvu7