zoukankan      html  css  js  c++  java
  • RuPengWang项目

    项目


    Day1-------------------------


    说明:建外键约束、ashx+Razor

    RupengWang
    创建三个类库Model DAL BLL
    后台:RupengWang.Admin
    前台:RupengWang.Front
    建一个后台管理员的表T_AdminUsers(Id UserName Password)
    打开动软,生成三层代码 (报错,是因为UAC是不用随意的写入数据,方法是以管理员运行,没有就去属性兼容性中找),(工具-选项-配置-命令规则),(新建net项目-工厂模式-生成项目-需要DbHelperSql.cs),(去掉Oarcle,自己添加一个连接字符串),DataCache.cs,(key="ModelCache" value="30"),
    //作业:用Razor做一个比动软更好的,允许用户自定义模板的代码生成器
    RupengWang.Razor类库:其中有RPHelper.cs,HttpContext需要添加System.Web程序集
    参数默认值,有点重载的感觉,有默认值的参数可参数可不传值,(不为null才能拼接可扩展属性)
    RupengWang.Common类库:放常用的一些类
    jQuery EasyUI用html画出的一个后台模板界面
    编码问题,需要都修改为UTF-8或者其它
    AdminUser的CRUD,CRUD都在AdminUserController.ashx,Model是弱类型转为User会有自动提示,(Ctrl+J可以快速定位行号)
    需要客服端提交数据的,用ajax来做
    AjaxHelper.cs用来专门给浏览器返回一些消息,(引用最好用根目录,不容易乱掉)
    public string void WriteJson(HttpContext context,string status,string msg,object data) //需要引用System.Web.Script.Serialization
    {
    }
    该代码生成器生成的代码没有检查用户名是否存在这个方法(有但是sql拼接有注入攻击问题),需要自己去写,不要去改代码生成器生成的代码,但是他们都是部分类,可以再写一个同名部分类,这样他们就可以合并为一个类
    MD5处理放到BLL中,UI层代码越少越好,最好只有用户输入,检查合法性等
    //作业:后台用户的删除
    用户的禁用 IsEnabled,可以再BLL中先查询再更新
    用户批量删除btnBatchDelete
    var inputs = $(":checkbox[name='selectUserId']:checked");
    var strs = inputs.map(function(){return $(this).val()}); //对于每一项都获得一个数据,获得的是数组
    把这个数组拼接为一个带空格的字符串,发给服务器
    Delete from T whele Id in idArray //idArray这个数字数组过滤掉了strs中除了数字的其它危险字符
    //作业:批量的禁用 (反选全选也用上)
    //作业:后台登陆:(UserName,Password,YanzhengCode),用AJAX、Form各一个版本;服务器检查:用户名是否存在,密码是否正确,用户名是否已经存在


    Day1代码:

    第一天项目任务:

    项目任务:后台用户单个的删除;批量禁用。
    项目任务:后台登陆:用户名、密码、验证码。登陆的时候要求使用AJAX、Form表单两种方式各做一个版本。服务器端检查:用户是否存在;密码是否正确;验证码是否正确;用户是否被禁用。
    任务提交到这个帖子中。

    Day2-----------------------

    <one>后台登录:

    后台登录:用户名、密码、验证码ValidCode(汉字)、自动登录
    //0 汉字验证码:验证码可以用Guid.NewGuid().ToString(),去后四位,但不是随机的;可以用一个常用汉字字符串来随机取四个汉字作为验证码,new Font(new FontFamily("宋体"),12) ,g.Clear(Color:red); //用指定颜色清空画布背景颜色
    Random是依赖于当前时间的,必须放到for循环外面
    for(int i=0;i<500;i++) //画上500个随机点
    {
    int x=rand.Next(0,100);
    int y=rand.Next(0,30);
    g.DrawLine(Pens.Red,x,y,x,y); //画一个点
    }
    g.Clear(Color.red)
    string.IsNullOrWhiteSpace(username) //是否是连续的空白字符串
    //1 登录所有情况都写入BLL
    //2 用枚举判断登陆结果: public enum LoginResult {OK,UserNameNotFond,PasswordError} //用枚举来表示返回结果
    //3 自动刷新验证码: 点击登陆后,如果登陆失败,自动刷新一次验证码
    //4 重置验证码:一旦点击登陆就重置验证码,这样即使浏览器不刷新验证码,该验证码也将失效;如果正常情况,该验证码是不会被使用的,因为接下来服务器响应后浏览器会再次刷新(客服端不可信)
    //5 自动登陆:如果选择记住密码,把用户名、密码存入Cookie中,密码需要MD5加密
    LoginHelper.cs //记住用户名Remenber(),尝试自动登陆TryAutoLogin() (与Cookie相关,必须写在UI),(把登陆的Id和用户名放入Session存起来StoreInSession(),后面用,设置用户名为唯一索引),工具类不需要实现Session接口
    //6 退出登陆:销毁Session,Cookie,把有效时间设置为过去时间,就是销毁Cookie的作用 ---Session.Abandon() ---Expores=DateTime.Now.AddDays(-1);
    //7 权限检查:进入任何一个页面,如果用户名没有登陆,则让她重新登陆(如果Session中登陆Id为null,就跳转到登陆页面) 除了检查用户是否登陆、后面还要检查是否有权限

    <two>系统日志:(程序级别日志,面向程序员的)

    Log4net(Log for net:为.net提供的日志,开源工具):可以帮助开发人员快速定位错误
    如果访问量大,用IO写入会造成堵塞
    Log4Net的好处:方便;实现各种需求只要改配置文件就可以了
    文件日志:把日志记入文件中.(放入App_Code原代码、App_Data文件,不让访问者访问,保证安全)
    滚动日志文件:每个日志最多100M,一个日志满了,就往新的日志文件中保存,最多保存10个日志文件,如果再有日志就把最旧的日志删除,依次循环;
    日志级别:修改level可以控制哪些级别的信息显示
    //1 添加log4net.dll
    //2 添加configSections节点(在所有节点之前),添加log4net节点
    <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
    </configSections>
    <log4net>
    <!-- OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL -->
    <!-- Set root logger level to ERROR and its appenders -->
    <root>
    <level value="DEBUG" />
    <appender-ref ref="RollingFileTracer" />
    </root>
    <!-- Print only messages of level DEBUG or above in the packages -->
    <appender name="RollingFileTracer" type="log4net.Appender.RollingFileAppender,log4net">
    <param name="File" value="App_Data/Log/" />
    <param name="AppendToFile" value="true" />
    <param name="RollingStyle" value="Date" />
    <param name="MaxSizeRollBackups" value="10" />
    <param name="MaximumFileSize" value="1MB" />
    <param name="DatePattern" value="&quot;Logs_&quot;yyyyMMdd&quot;.txt&quot;" />
    <param name="StaticLogFileName" value="false" />
    <layout type="log4net.Layout.PatternLayout,log4net">
    <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
    </layout>
    </appender>
    </log4net>
    //3 例子:Console程序
    在Global.asax(全局应用程序类),Application_Start时,执行log4net.Config.XmlConfigurator.Configure();
    ILog log=LogManager.GetLogger(typeof(Test1)); //记录指定文件的日志
    log.Debug("调试信息");
    log.Warn("警告信息");
    log.Error("错误信息");
    log.Fatal("严重错误");
    级别:Fatal>Error>Warn>DbBug(属于程序的一些调试信息,系统开发过成功中用Debug把尽可能多的信息打印出来,使用时就用Error)
    WebClient wc=new WebClient(); //下载网页html
    string s=wc.DrowLoadString(url);
    log.Debug("下载结束"); 如果异常,记录异常log.Error("下载失败",ex);
    //4 添加Global.asax(全局应用程序类),当系统发生未处理异常时,在Application_Error()中记录日志
    ILog log = LogManager.GetLogger(typeof(Global));
    log.Error("系统发生未处理异常:",Context.Error);


    系统操作日志:面向业务人员
    T_AdminOperationLogs(Id,UserId,CreateDateTime,Description),UserId在关系中建外键约束
    RecordOperationLog(string description)//记录当前用户操作日志

    问题1: iframe嵌套问题? (通过js判断是否是在iframe中,如果是在iframe中,就跳转到父类iframe)
    问题2:检测是否登陆,是否有权限
    作业1:查询操作日志(User,Date,Discription) AdminOperationLogSearch.ashx
    作业2:后台密码重置(888888),用户自己修改密码(essayUI,取当前用户登陆的Id)

    <three>权限管理

    //有外键约束的CRUD+RPHelpler.cs
    用户User(yzk,admin),角色Role(网站编辑,班主任,系统管理员),权限Power(删除用户,重置密码,查看订单):一个人可以有多个角色,一个角色可以有多个权限
    T_Powers T_Roles T_RolePowers (某角色可以有哪些权限)
    T_Roles T_AdminUsers T_AdminUserRoles (某用户可以有哪些角色)
    Constatins(selctedValue,itemValue) //如果这些项在选中的值集合中就selected,(选中的为空就传一个空的long[]{})
    用name+i作为id
    //获得选中的checkbox的value数组
    var selectedPowerIds=new Array();
    $(":checkbox[name='selectUserId']:checked").each(function(){
    selectedPowerIds.push($(this).val()); //遍历元素,往数组中加这些元素的值
    });
    alert(selectedPowerIds.join(","));
    //查询刚刚插入的id,Add()本身就有返回id
    insert into output
    selected @IDENTITY;
    //新增角色权限对应关系


    项目任务:
    1 角色的删除(先删除引用的表,在删除被引用的表)
    2 T_Power权限CRUD
    3 后台用户的修改、新增,
    4 后台系统日志说明

    //超链接不跳转
    <a href="#"></a>
    <a href="javascript:;"></a>
    <a href="javascript:void(0);"></a>
    以上方法都可以,推荐二,三方法,第一种方法页面可能会跳到其它位置.


    Day3----------30150325----------------


    <one>配置文件的继承和覆盖

    问题1:未能加载文件或程序集(有可能是MySql.Web程序集不存在,程序集存在没找到,程序集的依赖项有问题)
    连接mysql时,报错,因为它的程序集自动加了一个MySqlMembershipProvider,在machine.config中把这个配置信息注释掉
    .net中的config有几个级别:机器级别,应用程序级别,子目录级别
    Web.config是从machine.config继承的
    直接去读取machine.config中的配置信息connectionString (可以证明是继承关系)
    MySqlMembershipProvider、MySqlRoleProvider启用后进行傻瓜化:把用户管理,权限管理,登陆都被做好了.
    如果machine.config中已经默认一个配置项name,那么web.config中就不能再用这个name,
    如果一定要再用这个配置项,那么在当前config中此配置项前面加<remove name="之前的配置项"/>
    或者用<clear/> (把之前父类的所有的信息都清除掉),才可以再次使用这个name
    string age=ConfigurationManager.AppSettings["age"]; //读取AppSettings中的age
    子文件的web.config继承父文件的web.config,所以子文件的可以读取父文件的配置信息,父文件不可以读取子文件中的配置信息


    <two>权限检查:判断用户是否有某个权限

    (教学总监,班主任,网站管理员,网站编辑)
    @两个作用:后面字符串不转义,后面字符串是个多行文本
    public bool HasPower(long adminUserId,string powerName)
    {
    //获得当前登录用户的Id
    //获得powerName对应的权限Id
    //T_RolePower中查询有哪些角色RoleId有这个权限
    //判断当前用户是否拥有这些角色RoleId中至少一个
    }

    //例子:
    select COUNT(*) from (
    select AdminUserId from T_AdminUserRole where RoleId in (
    select RoleId from T_RolePower where PowerId=(
    select Id from T_Power where Name='编辑管理用户'
    )
    )
    ) au
    where au.AdminUserId='34'

    context.Response.End(); //输出当前缓存,停止该页执行,发出终止请求命令
    没有权限也可以新增用户:直接向ashx发送请求AdminController.ashx?action=addnewSave&username=aa&password=123来新增用户,所以必须在保存时也进行权限验证
    无论新增还是保存都需要判断是否有“新增用户”权限,(避免用户直接向addnewSave发请求实现新增)
    安全不是建立在不透明的前提下,而是在别人知道你在怎么做,也没法黑你
    任务1:给程序中所有的需要权限控制的地方加上权限限制

    <three>课程管理


    课程 章节 段落(多个课程,多个章节,多个段落(视频笔记))
    T_Course (Id Name)
    T_Chapter (Id Name ChapterNo CourseId ) //序号,用于排序
    T_Segment (Id Name SegmentNo VideoCode Note ChapterId)

    关联字段关联表
    一对多只需要关联字段,多对多才需要关系表
    一对多(0…N):一个父亲有N个孩子,一个孩子只属于一个父亲;一个课程有N个章节,一个章节只属于一个课程。只要在“N这一端”增加一个指向“1这一端”的外键即可。Father:Id,Name.Child:id,Name,FatherId。
    多对多:一个用户有N个角色,一个角色可允许被多个用户使用;一个老师有N个学生,一个学生有N个老师。需要额外的关系表。Teacher:Id,Name;Student:Id,Name;TeacherStudent:Id,TeacherId,StudentId

    任务2:Course、Chapter、Segment的CRUD //IEnumerable<Course> 遍历

    <four>用反射封装Handler

    检查是否登陆、检查是否有权限、都实现IRequiresSessionState (每个页面都要做,所以可以放到一起)
    public BaseHandler:IHttpHandler,IRequiresSessionState
    public void ProcessRequest()
    //检查是否登陆
    //我约定,参数中都要有一个action参数,表示执行什么方法
    //方法名与action值一样
    由父类处理(登陆检查,权限检查),然后调用子类的CRUD方法

    //BaseController.cs
    public class BaseController:IHttpHandler,IRequiresSessionState
    {
    public bool IsReusable //bool属性,是否可重复使用
    {
    get { return true; }
    }
    public void ProcessRequest(HttpContext context)
    {
    context.Response.ContentType = "text/html";
    //判断是否登陆
    AdminHelper.CheckAdminUserIdAccess(context);
    string action = context.Request["action"];
    if (action == null)
    {
    throw new Exception("action错误");
    }
    //刚进是new子类,执行父类时的this当前对象未子类对象
    Type type = this.GetType();
    MethodInfo method = type.GetMethod(action); //约定:方法名与action 值相同
    method.Invoke(this, new object[] { context }); //执行当前对象的method方法
    }
    }


    //CourseController.ashx
    public class CourseController : BaseHandler
    {
    //面向对象中继承的优点:把通用的工作交给父类完成
    public void list(HttpContext context)
    {
    }
    public void addnew(HttpContext context)
    {
    }
    public void addnewSave(HttpContext context)
    {
    }
    ...
    }


    扩展任务:T_Power增加ConrollerName,
    配置进程外的Session:以管理员权限运行命令提示符...
    以管理员身份运行 C:WindowsMicrosoft.NETFrameworkv4.0.30319>aspnet_regsql.
    exe -ssadd -sstype p -S 127.0.0.1 -U sa -P abcd5226584
    windows的Dos复制:标记+左键选中+右键(到粘贴板)+ctrlV

    Front前端展示
    ViewSegment.cshtml
    error.cshtml
    视频提交代码中未检测的代码---从客服端检测到存在危险的request值---4.0或更高版本--方法3--在web.config加上<system.web><httpRuntime requestValidationMode="2.0" /></system.web> //如果4.0已经有这个节点加个属性就行了--------------???------------
    在.cshmtl中直接输出字符串的特殊字符会被转义字符,所以必须通过RPHelper.Raw()表示不转义进行原样输出 ,Note:显示超链接和ul
    把手写编辑html代码进行可视化的编辑(所键即所得编辑器)

    <five>UEditor
    1 下载UEDitor
    百度UEditor.baidu.com,下载NET的UTF8版本(用IE可以看到默认版本)
    UE.getEditor('containerid'):获得UEditor的内容
    ueditor本质就是动态生成的一个textarea

    var ue = UE.getContent();
    //对编辑器的操作最好在编辑器ready之后再做
    $(function () {
    ue.ready(function () {
    ue.setContent('@RupengWangRazor.RPHelper.Raw(Model.note)');
    });
    $("#btnSave").click(function () {
    var html = ue.getContent();
    ...});
    });

    <script id="container" name="content" type="text/plain">

    </script>

    <!-- 配置文件 -->
    <script src="/ueditor/ueditor.config.js"></script>
    <!-- 编辑器源码文件 -->
    <script src="/ueditor/ueditor.all.js"></script>
    <!-- 实例化编辑器 -->
    <script type="text/javascript">
    var ue = UE.getEditor('container');
    </script>


    用IE可以看到UEditor所发送请求报文
    任务:配置文件的上传,云存储
    服务器请求统一路径
    案例是php/config --->当前为net/config
    访问ueditor/net/controller.ashx?action=config 看是否正常--->抛出:未能找到类型或命名空间Newtonsoft
    引用Newtonsoft.Json.dll 这是一个第三方序列化或反序列化
    为什么图片没显示出来 --->因为相对路径在另一个网站发生了变化,前台根本没有这个路径
    imageUrlPrefix: 图片访问路径前缀,可是使上传的文件保存在前台去;但是后台的imagePathFormat必须是虚拟路径,因为在前台MapPath了,所以无法用全路径直接上传保存到前台去

    2 云存储的上传
    上传文件需要查看上传配置说明
    ueditor.config中配置,上传路径配置,访问查看是否返回json,如果不能返回json就引用newtonjson.dll
    把文件放到单独域名
    相对路径在前台看不到,
    大型网站架构,为什么大型网站都把图片放到一个单独的一个域名(单独的服务器):降低服务器压力,降低Cookie的流量占用,发送Cookie会浪费流量;还可以使用CDN分发流量;安全性的好处,图片都放到了一个单独的静态域名中,就算是个程序伪装的也没有权限执行代码
    CDN:内容分发流量(有很多服务器,会智能的挑选一台距离最近的让你访问)
    云存储:把图片放到单独的服务器,价格低
    FTP上传文件
    云存储:七牛,亚马逊的AWS,又拍云
    云计算:租,按需使用,按需付费。虚拟主机(不推荐)
    云主机:盛大云,阿里云,百度云
    云视频:保利威视
    //注册又拍云---创建空间---空间名yangguodemo/操作员名yangguodemo/密码***********/已绑定域名yangguodemo.b0.upaiyun.com---
    1--ftp上传:
    //notepad库---用ftp的方式来访问这个upyun---右键登陆(用户名:操作员名/空间名,如:operator/mybucket)---复制一个图片---然后可以通过这个默认域名访问这个图片
    2--http上传:怎样通过http上传一个文件
    通过什么方式上传文件:FTP(File transfer protocal 文件传输协议),HTTP,API
    3--api:HTTP REST API
    使用 REST API,您可以使用任何方式发送 HTTP 请求与 UPYUN 服务器通信。因此,你可以使用任何编程语言来使用 REST API。
    已经提供了现成的C# SDK,下载下来把Program.cs拷贝到程序中
    键一个UpYun,拷贝所有UpYun.cs
    流读取某个文件到byte[]中,设置内容的md5中,是否成功
    下载SDK开发包
    问题:又拍云对接
    完善文件上传
    大系统怎么进行架构
    上传到又拍云
    不能用用户上传的文件名作为文件名,可能文件重名,用MD5值做文件名可以避免重复

    //Program.cs
    UpYun upyun = new UpYun("yangguodemo", "yangguodemo", "abcd5226584");
    using (FileStream fs = new FileStream(@"G:RuPeng_YZK_150107Rupeng_20150320_ASPNET_RupengWangTestApi.csprojyuyan.png", FileMode.Open, FileAccess.Read))
    {
    BinaryReader r = new BinaryReader(fs);
    byte[] postArray = r.ReadBytes((int)fs.Length);
    /// 设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误)
    upyun.setContentMD5(UpYun.md5_file(@"G:RuPeng_YZK_150107Rupeng_20150320_ASPNET_RupengWangTestApi.csprojyuyan.png"));
    Console.WriteLine("上传文件");
    bool b = upyun.writeFile("/a/yuyan.png", postArray, true);
    // 上传文件时可使用 upyun.writeFile("/a/test.jpg",postArray, true); //进行父级目录的自动创建(最深10级目录)
    Console.WriteLine(b);
    Console.ReadKey();
    }


    上传到upyun,文件按年、月为文件夹来存储,便于管理和定位,文件名用文件的md5表示,避免文件名的重复
    //首先获得上传文件后的的路径
    //如果上传失败,需要进行日志记录


    //上传到upyun
    //上传文件目录为年月日、文件为文件的md5值
    DateTime today = DateTime.Today;
    string uploadYunFileName = CommonHelper.MD5Encrypt(uploadFileBytes) + Path.GetExtension(uploadFileName); //上传云的文件
    string uploadYunFilePath = "/upload/" + today.Year + "/" + today.Month + "/" + today.Day + "/" + uploadYunFileName; //上传到云得路径
    try
    {
    UpYun upyun = new UpYun("yangguodemo", "yangguodemo", "abcd5226584"); //上传指定云服务域名
    /// 设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误)
    upyun.setContentMD5(CommonHelper.MD5Encrypt(uploadFileBytes)); //对上传字节进行md5加密
    bool uploadResult = upyun.writeFile(uploadYunFilePath, uploadFileBytes, true); //把上传字节写入指定云路径中 //----------------???-----------------------
    if (uploadResult) //如果上传成功
    {
    Result.Url = "http://yangguodemo.b0.upaiyun.com" + uploadYunFilePath; //返回插入编辑器的图片url
    Result.State = UploadState.Success;
    }
    else
    {
    Result.State = UploadState.FileAccessError;
    Result.ErrorMessage = "上传upyun服务器失败";
    log.Error("上传upyun服务器失败");
    }
    }
    catch (Exception e)
    {
    Result.State = UploadState.FileAccessError;
    Result.ErrorMessage = e.Message;
    log.Error("上传文件失败:发生异常:" + e);
    }
    finally
    {
    WriteResult();
    }


    XSS 跨站脚本攻击
    过滤script标签
    如果允许网友上传任何信息而又原样显示,就会可能出现跨站脚本攻击
    js显示不出来,在config.js中取消script
    //在config.js中的,allHtmlEnabled:false 将false-->true依然没效果 //提交到后台的数据是否包含整个html字符串
    获得编辑器的var ue=UE.getUEditor('container',{"allowDivTransToP":true}); //依然没作用
    测试按钮,ue.getContent(); //可以获得script标签
    asp.net帮我们自动阻拦了这些特殊字符,如果在编辑时没有限制,那么显示时应该进行转义显示
    这就是如果不在根的web.config中修改requestValidationMode="2.0"就会报错


    Day4--------------20150326-------------


    查看课程


    <one> url重写:

    Front ViewCourse.ashx?id=1
    FrontHelper.cs //包含章节错误信息
    对请求的地址进行重写
    用正则匹配,重写路径
    string url = Context.Request.Url.ToString();
    Match match = Regex.Match(url, @"Segment(d)+.ashx");
    if(match.Success)
    {
    string value = match.Groups[1].Value;
    Context.RewritePath("ViewSegment.ashx?segmentId=" + value);
    }

    Segement(@segment.Id) 如果不能识别就加个括号


    <two> 缓存优化:

    降低数据库压力
    <appSettings><add key="ModelCache" value="1"/></appSettings> //设置实体缓存时间

    public RupengWang.Model.Course GetModelByCache(long Id)
    {
    string CacheKey = "CourseModel-" + Id;
    object objModel = Maticsoft.Common.DataCache.GetCache(CacheKey);
    if (objModel == null)
    {
    objModel = dal.GetModel(Id);
    if (objModel != null)
    {
    int ModelCache = Maticsoft.Common.ConfigHelper.GetConfigInt("ModelCache");
    Maticsoft.Common.DataCache.SetCache(CacheKey, objModel, DateTime.Now.AddMinutes(ModelCache), TimeSpan.Zero);
    }
    }
    return (RupengWang.Model.Course)objModel;
    }

    <three> shtml技术:

    http://pan.baidu.com/s/1mgwvV4G //切图工具
    后台程序员 前端程序员 切图工程师 页面设计
    BootStrap:前端框架随着设备大小进行变化
    aboutus.html,joinus.html
    article:HTML5的语义话标签
    避免静态页面的头和尾重复,使用SSI,除了Cassini的主流浏览器都是支持的
    ServerSideInclude:SSI 服务器端包含,默认为.shtml
    joinus.shtml+head.html+foot.html //服务端用SSI完成的的拼接,客服端是不知道有这么回事的
    如果每个页面需要的中间的标题<title>是不一样的,可以通过进一步拆分进行实现:headstart.html,linkscript.html,headend.html,navbar.html(导航条) (错位:增加聊天室的原因)
    高级保存选项里保存:UTF-8 无签名(去掉BOM),这样页面头部就没有空白
    Razor不认识<!--#include file="/head.html"-->
    对于纯静态页面用<!--#include file="/head.html"-->,对于动态页面要自己写Include的Raw方法原样返回html @RupengWangRazor.RPHelper.Include("/headstart.html")
    shtml是静态的,不需要asp.net引擎处理;而cshtml是动态的,需要引擎

    如鹏网公开课里去考视频代码


    项目任务:把课程页面中的章节、段落按照SeqNo排序;在课程、章节、段落的后台List页面中也是按照SeqNo排序显示。

    今天的代码:http://pan.baidu.com/s/1hqiWTgw


    Day5----------------------20150330-------------------------

    新闻管理


    新闻分类管理:类别是无限数次、树状结构:
    T_NewsCategory(Id Name ParentId)
    T_News(Id Title NewsContent PostDateTIme CategoryId)
    军事新闻、体育新闻
    categoryList()
    如果没有parentId就赋值为-1,显示根目录
    项目任务1:类别的CRUD(如果有子类别,则不能删除)
    newsList()
    根据类别Id查询所有新闻
    项目任务1:新闻的CRUD

    <one> 静态化:

    新闻静态化:
    新闻基本是不变的,缓存虽然降低了数据库压力,但是依然会访问web服务器,可以页面静态化,;查看课程不能静态化的,因为有业务逻辑,没有购买不能查看
    生成静态化页面:解析获得html,保存到指定文件中去,如果指定文件的文件夹不存在就创建文件夹

    //生成静态化页面
    //获得ViewNews.cshtml页面,保存到Front的.shtml静态页面中
    string cshtml = RPHelper.RPGetHtml(context, "~/NewsFile/ViewNews.cshtml", new {
    title = title ,
    newsContent = newsContent,
    postDateTime=news.PostDateTime
    });
    string filePath = @"G:RuPeng_YZK_150107Rupeng_20150320_ASPNET_RupengWangRupengWang.FrontNewsFile" + categoryId + "\" + newsId + ".shtml";
    string shtmlDirect = Path.GetDirectoryName(filePath); //获得这个全路径静态文件的目录
    if(!Directory.Exists(shtmlDirect))
    {
    Directory.CreateDirectory(shtmlDirect);
    }
    File.WriteAllText(filePath, cshtml); //把动态的cshtml页面写入静态的shtml文件页面中


    一键静态化:

    把所有的文章都进行静态化得生成 rebuildStatic
    <app key="ViewStaticDirect" value=""/> //静态页面目录

    public static void CreateStaticPage(News news)
    {
    //生成静态化页面
    //获得ViewNews.cshtml页面,保存到Front的.shtml静态页面中
    string cshtml = RPHelper.RPGetHtml(HttpContext.Current, "~/NewsFile/ViewNews.cshtml", new
    {
    title = news.Title,
    newsContent = news.NewsContent,
    postDateTime = news.PostDateTime
    });
    string ViewStaticDirecPre = ConfigurationManager.AppSettings["ViewStaticDirecPre"]; //配置静态化目录前缀
    string filePath = ViewStaticDirecPre + news.CategoryId + "\" + news.Id + ".shtml";
    string shtmlDirect = Path.GetDirectoryName(filePath); //获得这个全路径静态文件的目录
    if (!Directory.Exists(shtmlDirect))
    {
    Directory.CreateDirectory(shtmlDirect);
    }
    File.WriteAllText(filePath, cshtml); //把动态的cshtml页面写入静态的shtml文件页面中
    }

    <two> 分页:


    文章分页:

    因为分页通用,所以写一个通用的分页组件
    list.ashx?pagenum={pagenum},{pagenum}作为当前页码的占位符
    string urlFormat,long totalSize,long pageSize,long currentPage :超链接格式,总条数,每页的条数,当前页码
    string totalPageCount = Math.Ceiling((totalSize*1.0f)/(pageSize*1.0f)) //取天花板数:如果整除就是本身,不能整除就+1
    string url=urlFormat.Replace("{pagenum}",i.ToString()); //超链接格式

    //RPHelper.cs
    /// <summary>
    /// 分页组件
    /// </summary>
    /// <param name="urlFormat">超链接格式</param>
    /// <param name="totalSize">总数据条数</param>
    /// <param name="pageSize">每页的条数</param>
    /// <param name="currentPage">当前页</param>
    /// <returns>原样返回分页的html(不转义)</returns>
    public static RawString Pager(string urlFormat,long totalSize,long pageSize,long currentPage)
    {
    //<ul>
    //<li>1</li><li>2</li>...
    //</ul>
    //总页数
    long totalPageCount = (long)Math.Ceiling((totalSize * 1.0f) / (pageSize * 1.0f)); //取天花板数:如果刚好整除,就是她本身;不能整除就+1 ---> 大于等于这个浮点数的最小整数
    //第一页
    long firstPage = Math.Max(currentPage - 5, 1);
    //最后页
    long lastPage=Math.Min(currentPage+5,totalPageCount);
    //currentPage+5
    StringBuilder sb = new StringBuilder();
    sb.Append("<ul>");
    for (long i = firstPage; i <= lastPage; i++)
    {
    string url=urlFormat.Replace("{pagenum}",i.ToString());
    if (i == currentPage)
    {
    sb.Append("<li>" + i + "</li>");
    }
    else
    {
    sb.Append("<li><a href="" + url + "">" + i + "</a></li>");
    }
    }
    return new RawString(sb.ToString());
    }

    //Test5/TestPager.ashx
    public void ProcessRequest(HttpContext context)
    {
    context.Response.ContentType = "text/html";
    long pagenum = Convert.ToInt64(context.Request["pagenum"]); //当前页
    long totalSize = 105, pageSize = 10;
    string[] strs;
    if (pagenum <= totalSize / pageSize ) //当页数<=整除页
    {
    strs = new string[pageSize]; //展示的数据,每页10条数据
    for (long i = 0; i < strs.Length; i++)
    {
    long index = (pagenum - 1) * pageSize + (i + 1);
    strs[i] = "第" + index + "条数据";
    }
    }
    else //当页数>整除页
    {
    long duo = totalSize % pageSize; //最后页有几条数据
    strs = new string[duo]; //展示的数据,每页10条数据
    for (long i = 0; i < strs.Length; i++)
    {
    long index = (pagenum - 1) * pageSize + (i + 1);
    strs[i] = "第" + index + "条数据";
    }
    }
    RPHelper.RPOutputHtml(context, "~/Test5/TestPager.cshtml", new {
    strs=strs,
    currentPage=pagenum,
    totalSize = totalSize,
    pageSize = pageSize
    });
    }

    //Test5/TestPager.cshtml
    <ul>
    @{
    foreach(var str in Model.strs)
    {
    <li>@str</li>
    }
    }
    </ul>
    @RupengWangRazor.RPHelper.Pager("TestPager.ashx?pagenum={pagenum}",Model.totalSize,Model.pageSize,Model.currentPage)

    列表分页:

    insert into T(Name,Age)
    select Name,Age from T //把查询结果批量插入T
    获得行号:(Id不能做行号,因为不连续)
    select * from
    (
    select ROW_NUMBER() over(order by Id) rownum,* from T_News //根据title排序所获得的行号
    ) t
    where t.rownum>=3 and t.rownum<=5 //获取根据title排序的3-5行的数据

    //获得某个类别下的总条数(已有) bll.GetRecordCount()
    //获得介于startrownum,endrownum之间的数据
    public GetPageNews(long categoryId,long startrownum,long endrownum){...}
    //总页数:天花板数
    //每一个页下生成一个静态页面
    //获得当前页得数据:
    var newses=bll.GetPageNews(categoryId,(i-1)*10+1,i*10);
    //静态化:解析cshtml返回一个html,写入到指定文件夹
    //每次新增一篇文章都把该类别下的所有静态列表页面生成
    mysql获得部分数据是limit
    预期的查询结果非常多就需要分页

    步骤: ViewNewsList.cshtml(新闻列表)--->RPHelper(分页组件)--->NewsController.ashx(GetNewsesPageByRowNum()获得指定类别指定某页得新闻列表集合--->解析cshtml并获得cshtml生成静态shtml)

    //RPHelper.cs
    /// <summary>
    /// 分页组件
    /// </summary>
    /// <param name="urlFormat">超链接格式</param>
    /// <param name="totalSize">总数据条数</param>
    /// <param name="pageSize">每页的条数</param>
    /// <param name="currentPage">当前页</param>
    /// <returns>原样返回分页的html(不转义)</returns>
    public static RawString Pager(string urlFormat,long totalSize,long pageSize,long currentPage)
    {
    //<ul>
    //<li>1</li><li>2</li>...
    //</ul>
    //总页数
    long totalPageCount = (long)Math.Ceiling((totalSize * 1.0f) / (pageSize * 1.0f)); //取天花板数:如果刚好整除,就是她本身;不能整除就+1 ---> 大于等于这个浮点数的最小整数
    //第一页
    long firstPage = Math.Max(currentPage - 5, 1);
    //最后页
    long lastPage=Math.Min(currentPage+5,totalPageCount);
    //currentPage+5
    StringBuilder sb = new StringBuilder();
    sb.Append("<li><a href="index_1.shtml">首页</a></li>");
    for (long i = firstPage; i <= lastPage; i++)
    {
    string url=urlFormat.Replace("{pagenum}",i.ToString());
    if (i == currentPage)
    {
    sb.Append("<li class='active'><a>第" + i + "页</a></li>");
    }
    else
    {
    sb.Append("<li><a href="" + url + "">第" + i + "页</a></li>");
    }
    }
    sb.Append("<li><a href="index_"+totalPageCount+".shtml">末页</a></li>");
    return new RawString(sb.ToString());
    }

    //NewsController.ashx
    /// <summary>
    /// 重新生成所有新闻静态列表
    /// </summary>
    /// <param name="context"></param>
    public void reBuildAllStaticNewsList(long categoryId)
    {
    NewsCategory newCategory = new NewsCategoryBLL().GetModel(categoryId);
    //获得指定类别的新闻总条数
    long totalNewsCount = new NewsBLL().GetRecordCount("CategoryId=" + categoryId);
    long pageSize = 10;
    //总页数
    long totalPageCount = (long)Math.Ceiling((totalNewsCount * 1.0f) / (pageSize * 1.0f));
    //遍历每一页
    for (long i = 1; i <= totalPageCount;i++ )
    {
    //每一页,获得该页得新闻列表集合 //根据rownum获得新闻
    List<News> newses = new NewsBLL().GetNewsesPagerByRowNum(categoryId, (i - 1) * pageSize + 1, i * pageSize); //就算最后行没有满,依然满足<=虚拟最大行
    //每一页,其页面解析并获得该页html
    string cshtml = RPHelper.RPGetHtml(HttpContext.Current, "~/NewsFile/ViewNewsList.cshtml", new
    {
    newCategoeyName = newCategory.Name,
    newses=newses,
    CategoryId=categoryId,
    totalSize=totalNewsCount,
    pageSize=pageSize,
    currentPage=i
    });
    //静态化该列表页
    string ViewStaticDirecPre=ConfigurationManager.AppSettings["ViewStaticDirecPre"];
    string fullPath = ViewStaticDirecPre + "/" + categoryId + "/index_" + i + ".shtml"; //每一页的静态文件全路劲
    string directName = Path.GetDirectoryName(fullPath);
    if(!Directory.Exists(directName))
    {
    Directory.CreateDirectory(directName);
    }
    File.WriteAllText(fullPath, cshtml);
    }

    //NewsDAL.cs
    /// <summary>
    /// 获得指定类别、指定起始行数的新闻集合
    /// </summary>
    /// <param name="categoryId"></param>
    /// <param name="startRowNum"></param>
    /// <param name="endRowNum"></param>
    /// <returns></returns>
    public List<News> GetNewsesPagerByRowNum(long categoryId, long startRowNum, long endRowNum)
    {
    StringBuilder sb = new StringBuilder();
    sb.Append("select * from (select ROW_NUMBER() over(order by Id asc ) rownum,* from T_News ) t ");
    sb.Append("where t.rownum>=@startRowNum and t.rownum<=@endRowNum ");
    DataSet ds = DbHelperSQL.Query(sb.ToString(), new SqlParameter() { ParameterName = "@startRowNum", Value = startRowNum },
    new SqlParameter() { ParameterName = "@endRowNum", Value = endRowNum });
    List<News> list = new List<News>();
    foreach(DataRow row in ds.Tables[0].Rows)
    {
    list.Add(DataRowToModel(row));
    }
    return list;
    }

    今日项目任务:
    1、类别的新增、类别的删除(如果有子类别,则不能删除),类别的修改。新闻的编辑、删除。
    2、完成后台“文章管理”的分页、后台用户管理以及分页等等
    3、前台用户登陆,完成验证码、DAL、BLL完善,符合三层的规则。注册;密码要用MD5散列处理。
    4、注册的时候账户默认处于“未激活状态”,系统给用户的邮箱发送一封激活邮件(程序如何发送邮件,自己研究),用户点击邮件后才能进行进入“激活状态”,才能进行后续的操作。
    5、所有页面都是静态页面,所以通过ajax检查用户是否登陆,如果登陆则显示用户名/【退出登录】链接,否则显示【登陆/注册】链接。可以参考目前如鹏网的功能来实现。
    6、把前台的登陆改成进程外Session:SQLServer。


    /// <summary>
    /// 发送邮箱,指定发送方和接收方
    /// </summary>
    public static void SendMail()
    {
    //简单邮件传输协议类
    System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient();
    client.Host = "smtp.163.com";//邮件服务器
    client.Port = 25;//smtp主机上的端口号,默认是25.
    client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;//邮件发送方式:通过网络发送到SMTP服务器
    client.Credentials = new System.Net.NetworkCredential("yingxinggedou", "abcd5226584");//凭证,发件人登录邮箱的用户名和密码

    //电子邮件信息类
    System.Net.Mail.MailAddress fromAddress = new System.Net.Mail.MailAddress("yingxinggedou@163.com", "杨国");//发件人Email,在邮箱是这样显示的,[发件人:小明<panthervic@163.com>;]
    System.Net.Mail.MailAddress toAddress = new System.Net.Mail.MailAddress("adolphyangguo@163.com");//收件人Email,在邮箱是这样显示的, [收件人:小红<43327681@163.com>;]
    System.Net.Mail.MailMessage mailMessage = new System.Net.Mail.MailMessage(fromAddress, toAddress);//创建一个电子邮件类
    mailMessage.Subject = "邮件的主题:如鹏网账户激活";
    string filePath = HttpContext.Current.Server.MapPath("/index.shtml");//邮件的内容可以是一个html文本.
    System.IO.StreamReader read = new System.IO.StreamReader(filePath, System.Text.Encoding.UTF8); //System.Text.Encoding.GetEncoding("GB2312")
    string mailBody = read.ReadToEnd();
    read.Close();
    mailMessage.Body = mailBody;//可为html格式文本
    //mailMessage.Body = "邮件的内容";//可为html格式文本
    mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;//邮件主题编码
    mailMessage.BodyEncoding = System.Text.Encoding.UTF8;//邮件内容编码
    mailMessage.IsBodyHtml = true;//邮件内容是否为html格式
    mailMessage.Priority = System.Net.Mail.MailPriority.High;//邮件的优先级,有三个值:高(在邮件主题前有一个红色感叹号,表示紧急),低(在邮件主题前有一个蓝色向下箭头,表示缓慢),正常(无显示).
    try
    {
    client.Send(mailMessage);//发送邮件
    //client.SendAsync(mailMessage, "ojb");异步方法发送邮件,不会阻塞线程.
    }
    catch (Exception e)
    {
    throw new Exception(e.Message);
    }
    }


    //Day6------------------20150404--------------


    邮件激活:


    任何页面都可以是动态页面,经过ashx进行动态页面的请求处理: ashx---> cshtml---> ashx
    任何页面也都可以是静态页面,经过shtml的ajax进行静态页面的请求处理: shtml---> (ajax ---> ashx ---> shtml)
    静态页面用替换、用ajax从服务器拿到数据
    把登陆注册请求都放入一个ashx
    密码需要输入2次
    焦点离开时,正则表达式验证邮箱的格式,及用户名是否存在(可用)
    新增和修改应该放在BLL中
    项目任务: 加一个注册日期

    邮箱激活:目的是验证这个邮件地址是存在的,
    激活码:目的是必须进入这个邮箱才可以激活controller.ashx?action=active?username=yang&activeCode=007008
    激活码如果放入session,在其他浏览器就不是一个激活码了,应存入数据库中,因为只使用一次所以建单独表
    username=" +context.Server.UrlEncode(username)+"&activeCode="+activeCode 如果不能激活就黏贴下面的链接
    邮件客服端:很久以前是用邮件客服端发送邮件,很少使用Web ---Foxmail:原理是Smtp邮件发送协议;POP3邮件接收协议 ---发送邮件方一定要开启SMTP协议
    yzk PHONE:13401087865
    项目任务:一个邮箱只能注册一个账号
    项目任务:用qq服务器发送邮件
    不可能用163、qq等免费邮箱发大量的邮件,smtp:163.com等服务器会限制邮件数量,只有Edm专用服务器掏钱才可以发送,但要保证不发垃圾邮件(大公司的白名单联盟; SendCloud、 等适用于小网站大量发送邮件)

    找回密码也需要发送邮件,所以把发送邮件封装起来,再把发件人等配置一下
    项目任务:激活码有效期是30分钟,如果已经激活则显示“已经激活不需要重复激活”
    个人任务:自己写个收发邮件的客服端软件

    应该有个报错页面,显示一些信息(激活成功、激活失败)
    加载每个页面都显示已登录的用户名,用ajax -- $("#uid").show(); //show()就是把display去掉
    把这个ajax放入一个单独的文件中,这样就只需要引用这个文件就可以了
    项目中一般吧js文件放入一个单独的文件中,如果用户再次访问不会再次下载,只会从缓存只会获得
    项目任务:找回密码(注册邮箱)

    Day7------------20150405----------------


    <one>学习卡:

    免费的课程不用学习卡也可以看,收费课程必须有学习卡才可以看
    T_LearnCard(Id CourseId CardNum ExpireDays有效天数 Password UserId卡贝谁激活 ActiveDateTime什么时候激活) //激活过程用,激活后就可以扔掉
    T_UserCourse(Id CourseId ExpireDate UserId) //激活之后用
    1 检查学习卡密是否正确,如果正确则激活成功
    bool CheckLearnCardIsRight(string cardNum,string password,long userId) //CardNum加上唯一约束
    2 生成学习卡
    bool GenerateCards(cardNumPrefix,int expireDays,int startNo,int endNo,List<LearnCard> LearnCards) //返回是否生成成功,集合用户接收生成的学习卡
    cmd.Transaction=tx; //使用sqlserver需sqlserver需要注意 //借用事务来避免卡号冲突时,已经生成的卡号回滚 tx.Commit() tx.RllBack() //把一场记录到日志]
    cmd,Parameters.Clear();//每次添加新的参数都需要把旧的参数去掉
    output inserted.Id values() //获得新增数据的id
    把异常信息记入日志
    3 获得某用户的所有可用(在有效期中的)课程
    List<UserCourse> GetUsableCourses(long userId) //GetDate() //sql语句中获得当前时间
    4 用户userId是否激活了某个课程
    bool IsActiveCourse(long userId,long courseId)
    字符串拼接有注入漏洞,int没有
    CardNum加上唯一约束,该列就不能重复了
    多加防护,虽然麻烦,但是更安全
    把要加入的所有激活卡信息加入一个字符串,再附加
    当发现function嵌套太多,可以抽出来var fun=function(){...}; 然后$("#").click(fun);
    onkeyup="this.value=this.value.replace(/[^d]*/g,'')" //替换非数字
    下拉列表:可以通过ajax直接加载(easyui);可以用Razor引擎解析Model


    新人:加小功能,改bug
    项目任务:
    1 加课程名称;
    2 学习卡激活,然后进入我的课程页面;
    3 在线报名的功能,然后发送邮件(接收者邮箱配置到Web.config中);
    4 完善后台操作日志Log4net程序日志;
    5 学习记录的功能,谁在某某时候学了某课程,用户每次ViewSegment.ashx查看课程时往表中插入一条记录,
    ServerPush(性能比较差)/计时器,缓存,如果没有该数据就加入最前面prependTo,
    界面中定时获得学习记录,动态添加到界面中(display:none slideDown() $("li[rid='"+record.Id+"']").length<=0学习记录的id不存在才添加);slidedown()
    6 用户表再增加几个字段(Mobile,QQ,Shcool)
    7 虚拟班级T_Class(Id Name) T_StudentClass 班级成员管理-->
    like查询的占位符:
    {where Mobile like @Moblie
    new sqlparameter("@","%data%");
    不用写 ' '}

    适合于批量查询:where charindex(ltrim(Id),@idStrList)>0 //@idStrList就是 1,2,3
    相当于:where UserId in (1,2,3)

    <two>Redis:


    NOSQL:Not Only SQL,存储键值对,数据过期处理,适合存储零时的数据
    Redis服务器Linux(正式服务器),Windows版;
    1 解压Redis安装包redisbin_x32.zip到一个文件下--
    以普通的ext程序运行:redis-server.exe运行redis服务器(不推荐,需要长期手动开启Redis Watcher服务)---
    以windows服务运行:安装应该以window服务装起来(这样系统启动起来之后就运行,不需要登录)
    2 解压RedisWatcher1.zip:把Redis注册成系统服务--
    --到安装目录RedisWatcher下修改watcher.conf配置文件--
    ---配置文件中exepath和workingdir 这个路径指向radis解压的文件夹 (解压路劲不能有中文)
    到windows服务器启动这个服务并为自动, 这样确保windows服务装RedisWatcher处于启动状态
    3 Redis驱动添加到项目中

    添加一个类RedisManager.cs ,配置了最大写入大小,最大读取大小
    获得一个RadisClient连接,存数据(Set设置值),Get读取,设值时可以设置超时

    public static PooleRedisClientManager ClientManager{get;private set;} //内部可写,外部只能读
    static RedisManager()
    {
    redisClientManagerConfig redisConfig=new RedisClientManagerConfig();
    redisConfig.MaxWritePoolSize=128;
    redisConfig.MaxReadPoolSize=128;
    ClientManager=new PoolRedisClientManager(new string[]{"127.0.0.1"},
    new string[]{"127.0.0.1'"},redisConfig); //radis的读写分离,通过集群在多台服务器进行读写
    }

    using(IRedisClient client = RedisManager.ClientManager.GetClient()) //调用ClientManager的GetClient()得到一个连接
    {
    client.Set<int>("age",18,DateTime.Now.AddSeconds(30)); //30s后过期
    Dictionary<string,string> dict=new Dictionary<string,string>();
    dict.Add("aaa","bbb");
    client.Set<Dictionary<string,string>>("dict",dict);
    }

    using (iredisclient client = redismanager.clientmanager.getclient())
    {
    int age = client.get<int>("age");
    dictionary<string, string> dict = client.get<dictionary<string, string>>("dict");
    string iilove = dict["tuhan"];
    console.writeline(age + "=====" + iilove);
    }


    读写主机的地址,读写分离,多台Redis组成集群
    内部赋值外部取值
    Redis可以吧数据持久化到磁盘中,Memcached则是存到内存(分布式缓存,不适合保存很重要的东西)
    Redis的key加个前缀,避免冲突
    可以接受自己的请求存数据,也可以接受别人的,大家都可以从中读取,是大家共同的存储空间,A写入,B可以读出,也可以去覆盖,需要加个前后缀


    //Redis应用1:
    Redis服务器代替数据库存储用户激活码

    //Redis应用1:
    一个用户名只能登陆一次(同一时间一个用户只能在一个session中登录)
    获得当前用户的SessionID: context.Session.SessionID

    //LoginController.ashx
    //把用户名存入Session,判断每个页面是否登陆
    LoginHelper.StoreInSession(context, username, password);
    //每次登录都需要把当前用户名所在session的sessionId存入Redis
    using(IRedisClient client = RedisManager.ClientManager.GetClient())
    {
    client.Set<string>(USERNAME_SESSIONID + username, context.Session.SessionID);
    }


    //Glibal.asax
    public override void Init()
    {
    base.Init();
    //必须到Init来监听
    //每个需要Session页面启动都会执行AcquireRequestState事件
    //执行该事件的时候session已经准备好
    //客服端每次访问实现了IRequiresSessionState的接口都会触发
    this.AcquireRequestState+=Global_AcquireRequestState; //执行该事件,session已准备好
    }

    //访问任何一个页面都要检测当前请求的sessionId是否是Redsis中的sssionId,如果不是说明在别处(不同session中登录),需要退出(自杀)
    private void Global_AcquireRequestState(object sender, EventArgs e)
    {
    if(HttpContext.Current.Session==null) //-----!!!---
    {
    return;
    }
    string username = LoginHelper.GetUserNameInSession();
    if(username==null)
    {
    return;
    }
    using(ServiceStack.Redis.IRedisClient client = RedisManager.ClientManager.GetClient())
    {
    string sessionIdInRedis = client.Get<string>(LoginController.USERNAME_SESSIONID+username);
    if(sessionIdInRedis!=null && sessionIdInRedis!= HttpContext.Current.Session.SessionID)
    {
    //redis中当前用户的sessionId存在 并与当前用户的sessionId不一致,说明redis中sessionId被覆盖,有人又登录,以前登录的当前用户会退出
    HttpContext.Current.Session.Clear();
    HttpContext.Current.Session.Abandon();
    }
    }
    }


    //Day8----------------20150414-----------------


    <one>查询操作日志

    封装AJAX
    function CommonAjax(url,data,success){
    ...
    }

    left join冗余字段问题:
    $("#").empty(); //清空旧数据
    没有反应就看是否发出请求,如果没有发出请求就是js的问题,切换到Console可以看到错误信息
    left join查询的Model可以重新写
    item.UserName=(string)row["UserName"]; //row["UserName"].ToString(); //前者只能转string类型的(效率高些),后者可以转任何类型。
    CreateDateTime=log.CreateDateTime.ToString(); //日期格式转换

    日期问题:
    jquery easyui 日期选择DateBox,如果没有找到,有可能是easyui的版本太低没有datebox(function(){})这个方法,可以下载一个新的easyui
    查找替换---文件替换--在文件中查找--替换所有低版本的easyui-1.4
    DateTime opEndTIme=DateTime.Parse(strOpEndTime);

    My97DatePicker:
    My97DatePicker是一款非常灵活好用的日期控件。使用非常简单。
    1、下载My97DatePicker组件包
    2、在页面中引入该组件js文件:
    <script type="text/javascript" src="My97DatePicker/WdatePicker.js"></script>
    3、页面使用两个方式:
    常规调用: <input id="d11" type="text" onClick="WdatePicker()"/>
    图标触发:
    <input id="d12" type="text"/>
    <img onclick="WdatePicker({el:$dp.$('d12')})" src="My97DatePicker/skin/datePicker.gif" _fcksavedurl="My97DatePicker/skin/datePicker.gif" width="16" height="22" align="absmiddle">
    注:$dp.$ 相当于 document.getElementById

    搜索条件相当于一个对象吧 如果不赋值就不拼接select(obj) {//便利属性以及属性的值 如果值为空就不拼接 }
    if(Session==null){return;}报错(此时Session还没有准备好),用HttpContext.Current.Session 处理

    <two>Attribute:

    Attribute注解,是附加上方法、属性、类等上面的标签,
    可以通过方法的GetCustomAttribute获得粘贴的这个Attribute对象
    通过反射调用到粘贴到属性、方法、类等等的对象
    任务:改造ORM
    ORM约定:类得名字与表的名字一样,主键必须是Id,
    改造一下ORM(类名还可以和表名不一样,Id可以与主键不一样)

    //namespace TestAttribute.csProj

    //只能只能粘贴方法上
    [AttributeUsage(AttributeTargets.Method)]
    class RupengAttribute : Attribute
    {
    public RupengAttribute() { }
    public RupengAttribute(string name)
    {
    this.Name = name;
    }
    public string Name { get; set; }
    }
    //如果不加Attribute,则使用这个Attribute粘贴标签时必须写全名
    class TeXing : Attribute
    {
    public string Name { get; set; }
    }


    class Program
    {
    static void Main(string[] args)
    {
    //Attribute注解:Attribute是附加到方法、属性、类等上面的特殊标签,在类Type信息初始化加载,无法在运行时修改
    Type type = typeof(Person);
    /*
    object[] objs = type.GetMethod("SayHello").GetCustomAttributes(typeof(RupengAttribute), true);
    foreach(object obj in objs)
    {
    RupengAttribute rp = (RupengAttribute)obj;
    Console.WriteLine(rp.Name);
    }
    F1();
    */
    MethodInfo[] methods = type.GetMethods();
    foreach (MethodInfo method in methods)
    {
    object[] obAttrs = method.GetCustomAttributes(typeof(ObsoleteAttribute),true);
    if(obAttrs.Length>0)
    {
    ObsoleteAttribute ob = (ObsoleteAttribute)obAttrs[0];
    Console.WriteLine(method.Name + ":该方法不可用,因为" + ob.Message);
    }
    }
    Console.ReadKey();
    }
    }
    class Person
    {
    //在sayHello方法的描述信息MethodInfo上粘了一个RupengAttribute对象
    //注解的值必须是常量,不能是动态算出来的 [RupengAttribute(Name=DateTime.Now.ToString())]
    //[RupengAttribute(Name="rupengwnag")]
    //一般特性Attribute的类名都以Attribute结尾,这样用的时候就不用谢"Attribute"了
    [RupengAttribute("ruepngiloveyou")]
    public void SayHello()
    {
    }
    [Obsolete("这个方法已过时,拒绝访问")]
    public void F1()
    {
    }
    [TeXing(Name="texing")]
    public void Love()
    {
    }
    }


    //Attribute在权限控制方面的一个应用:
    PermissionActionAttribute.cs
    PermissionAction("保存新增新闻");
    真正气作用的不是Atrribute,而是读取并解释Attribute的代码 //"拆"-->拆迁队


    [AttributeUsage(AttributeTargets.Method)]
    public class PermissionActionAttribute:Attribute
    {
    public string Name { get; set; }
    public PermissionActionAttribute(string name)
    {
    this.Name = name;
    }
    }


    public class BaseController:IHttpHandler,IRequiresSessionState
    {
    public bool IsReusable //bool属性,是否可重复使用
    {
    get { return true; }
    }
    public void ProcessRequest(HttpContext context)
    {
    context.Response.ContentType = "text/html";
    //判断是否登陆
    AdminHelper.CheckAdminUserIdAccess(context);
    string action = context.Request["action"];
    if (action == null)
    {
    throw new Exception("action错误");
    }
    //刚进是new子类,执行父类时的this当前对象未子类对象
    Type type = this.GetType();
    MethodInfo method = type.GetMethod(action); //约定:方法名与action 值相同
    object[] obAttrs = method.GetCustomAttributes(typeof(PermissionActionAttribute), t
    if (obAttrs.Length > 0)
    {
    PermissionActionAttribute attr = (PermissionActionAttribute)obAttrs[0];
    AdminHelper.CheckAdminUserHasPower(context, attr.Name);
    }
    method.Invoke(this, new object[] { context }); //执行当前对象的method方法
    }
    }


    <three>二维码:


    添加并引用二维码的组件
    .net生成二维码
    遇到一个新的组件,可以先用一个测试项目把它调通
    如果数据经常被用户访问,而且大量的访问,而且内容一般不变就可以用缓存(静态页也可以看作一种缓存)

    http://www.cnblogs.com/Soar1991/archive/2012/03/30/2426115.html

    //TestQrCode.csProj
    QrEncoder qrEncoder = new QrEncoder(ErrorCorrectionLevel.H);
    QrCode qrCode = new QrCode();
    qrEncoder.TryEncode("菡,现在想起你,真的是太突然了,你跳个舞给我看嘛,能不能不要这么诱人啊,我已经逻辑混乱了", out qrCode);
    int ModuleSize = 12; //大小
    QuietZoneModules QuietZones = QuietZoneModules.Two; //空白区域
    var render = new GraphicsRenderer(new FixedModuleSize(ModuleSize, QuietZones));
    using (System.IO.Stream stream = File.OpenWrite("d:/1.png"))
    {
    render.WriteToStream(qrCode.Matrix, System.Drawing.Imaging.ImageFormat.Png, stream);
    }

    /// 生成文章的二维码
    public static void CreateQrCode(string ViewStaticDirecPre,long categoryId,long id)
    {
    string newsUrl = "http://localhost:9172/NewsFile/"+categoryId + "/" + id + ".shtml"; //文章地址路径
    string qrCodePath = Path.Combine(ViewStaticDirecPre, categoryId + "\" + id + ".png"); //生成的二维码路径
    QrEncoder qrEncoder = new QrEncoder(ErrorCorrectionLevel.H);
    QrCode qrCode = new QrCode();
    qrEncoder.TryEncode(newsUrl, out qrCode);
    int ModuleSize = 6; //大小
    QuietZoneModules QuietZones = QuietZoneModules.Two; //空白区域
    var render = new GraphicsRenderer(new FixedModuleSize(ModuleSize, QuietZones));
    using (System.IO.Stream stream = File.OpenWrite(qrCodePath))
    {
    render.WriteToStream(qrCode.Matrix, System.Drawing.Imaging.ImageFormat.Png, stream);
    }
    }


    分享一下:jq生成二维码,不用再生成一堆png了。
    <script src="/js/jquery.qrcode-0.11.0.min.js"></script>
    <script type="text/javascript">
    $(function () {
    $("#qrcode").qrcode({
    //render: "table", //table方式
    200, //宽度
    height: 200, //高度
    text: "http://localhost:6158/News/@(Model.CategoryId)/@(Model.Id).shtml" //任意内容
    });
    })
    </script>


    <four>网上支付:


    支付宝模拟器 http://paytest.rupeng.cn/

    怎么防止顾客在票上盖一个章,Nike专柜与收银员没有实时通信?
    暗号: Nike和收银员约定密码:abc888
    记录进票据上
    "md5(编号+金额+Nike名称+abc888暗号)"
    对约定的暗号进行加密,通过暗号是否改变来确定有没有被篡改

    项目任务:调通网银在线的接口
    在线购买课程的功能


    网关地址,向网关地址发请求


    //1 支付宝:

    流程参考《实物商品交易服务集成技术文档2.0.pdf》
    网关地址http://paytest.rupeng.cn/AliPay/PayGate.ashx

    网关参数说明:
    partner:商户编号
    return_url:回调商户地址(通过商户网站的哪个页面来通知支付成功!)
    subject:商品名称
    body:商品描述
    out_trade_no:订单号!!!(由商户网站生成,支付宝不确保正确性,只负责转发。)
    total_fee:总金额
    seller_email:卖家邮箱
    sign:数字签名。为按顺序连接 总金额、 商户编号、订单号、商品名称、商户密钥的MD5值。

    回调商户接口地址参数说明:
    out_trade_no:订单号。给PayGate.ashx传过去的out_trade_no再传回来
    returncode:返回码,字符串。ok为支付成功,error为支付失败。
    total_fee:支付金额
    sign:数字签名。为按顺序连接 订单号、返回码、支付金额、商户密钥为新字符串的MD5值。


    //Zhihubao.ashx
    public void ProcessRequest(HttpContext context)
    {
    context.Response.ContentType = "text/html";
    string action = context.Request["action"];
    if(action=="zhihubao") //支付宝支付
    {
    ZhihuParam zpara = new ZhihuParam();
    zpara.body = "学编程那家强,请到如鹏找老杨";
    zpara.out_trade_no = "qw0011wd";
    zpara.partner = "5";
    zpara.return_url = "http://localhost:19262/Zhihubao.ashx?action=zhihubaoResult";
    zpara.seller_email = "adolphyangguo@163.com";
    zpara.subject = "中科电子";
    zpara.total_fee = "1100";
    zpara.key="abc123";
    string signStr = zpara.total_fee + zpara.partner + zpara.out_trade_no + zpara.subject + zpara.key;
    zpara.sign = CommonHelper.MD5EncryptByUTF8(signStr).ToLower();
    context.Response.Redirect(@"http://paytest.rupeng.cn/AliPay/PayGate.ashx?body=" + context.Server.UrlEncode(zpara.body) +
    "&out_trade_no=" + zpara.out_trade_no +
    "&partner=" + zpara.partner +
    "&return_url=" + zpara.return_url +
    "&seller_email=" + zpara.seller_email +
    "&subject=" + context.Server.UrlEncode(zpara.subject) +
    "&total_fee=" + zpara.total_fee +
    "&sign=" + zpara.sign);
    }
    else if (action == "zhihubaoResult") //支付宝支付成功后返回
    {
    ZhihuParam zParam = new ZhihuParam();
    zParam.key = "abc123";
    zParam.out_trade_no = context.Request["out_trade_no"];
    zParam.total_fee = context.Request["total_fee"];
    string returncode = context.Request["returncode"];
    zParam.sign = context.Request["sign"]; //数字签名。为按顺序连接 订单号、返回码、支付金额、商户密钥为新字符串的MD5值。
    string newSignStr = zParam.out_trade_no + returncode + zParam.total_fee + zParam.key;
    string newSign = CommonHelper.MD5EncryptByUTF8(newSignStr).ToLower();
    if (newSign != zParam.sign)
    {
    context.Response.Write("<h1 style='color:green;'>支付失败</h1>");
    return;
    }
    context.Response.Write("<h1 style='color:red;'>支付成功</h1>");
    }
    }

    //2 网银:

    流程参考《网银在线支付B2C系统商户接口文档.zip》
    网关地址http://paytest.rupeng.cn/ChinaBank/PayGate.ashx

    网关参数说明:
    v_mid:商户编号
    v_oid:订单号
    v_amount:总金额
    v_moneytype:币种。0为人民币,1为外币。
    v_url:回调商户地址
    v_md5info:数字签名。为按顺序连接 总金额、币种、订单号、商户编号、商户密钥为新字符串的MD5值。
    style:网关模式:0(普通列表),2(银行列表中带外卡)
    remark1:备注1。可空。
    remark2:备注2。可空。

    回调商户接口地址参数说明:
    v_oid:订单号
    v_pmode:支付银行。目前值衡为0.
    v_pstatus:支付结果。20为成功,30为支付失败
    v_amount:总金额
    v_moneytype:币种。0为人民币,1为外币。
    remark1:传递的备注1。
    remark2:传递的备注1。
    v_md5str:数字签名。为按顺序连接 订单号、支付结果、总金额、币种、商户密钥为新字符串的MD5值。


    //EBankZhifu.ashx
    public void ProcessRequest(HttpContext context)
    {
    context.Response.ContentType = "text/html";
    string action = context.Request["action"];
    if (action == "ebankzhifu") //网银支付 //目的只是获得md5
    {
    EBank ebank = new EBank();
    ebank.v_mid = "2";
    ebank.v_oid = "19990720-20000400-000001234";
    ebank.v_amount = "10000.01";
    ebank.v_moneytype = "0";
    ebank.v_url = "http://localhost:9940/EBankZhifu.ashx?action=ebankzhifuResult"; //支付动作完成后返回到该url,支付结果以POST方式发送
    ebank.v_key="abc123";
    ebank.v_style = "0";
    ebank.v_md5info = CommonHelper.MD5EncryptByUTF8(ebank.v_amount + ebank.v_moneytype + ebank.v_oid + ebank.v_mid + ebank.v_key).ToLower(); //"1630DC083D70A1E8AF60F49C143A7B95";
    context.Response.Redirect("http://paytest.rupeng.cn/ChinaBank/PayGate.ashx?v_mid=" + ebank.v_mid +
    "&v_oid=" + context.Server.UrlEncode(ebank.v_oid) +
    "&v_amount="+ebank.v_amount+
    "&v_moneytype="+ebank.v_moneytype+
    "&v_url="+ebank.v_url+
    "&style="+ebank.v_style+
    "&v_md5info=" + ebank.v_md5info);
    }
    else if (action == "ebankzhifuResult") //网银支付返回
    {
    EBank ebank = new EBank();
    ebank.v_oid = context.Request["v_oid"];
    ebank.v_pmode = context.Request["v_pmode"];
    ebank.v_pstatus = context.Request["v_pstatus"];
    ebank.v_amount = context.Request["v_amount"];
    ebank.v_moneytype = context.Request["v_moneytype"];
    ebank.v_key="abc123";
    ebank.v_md5str = context.Request["v_md5str"];
    string newMd5Str = CommonHelper.MD5EncryptByUTF8(context.Server.UrlDecode(ebank.v_oid) + ebank.v_pstatus + ebank.v_amount + ebank.v_moneytype + ebank.v_key).ToLower();
    if(newMd5Str!=ebank.v_md5str)
    {
    context.Response.Write("<h1 style='color:green;'>支付失败</h1>");
    return;
    }
    context.Response.Write("<h1 style='color:red;'>支付成功</h1>");
    }
    }


    //项目任务:未进行激活码激活课程的用户,需要每次购买课程(应该增加课程价格)

    //Day9----------20150421--------------


    <one>网上支付(改进):

    T_OrderCourse(Id CourseId UserId CreateDateTime PayDateTime IsPay) //支付时间允许为空
    给商户编号加上唯一约束

    项目任务:ewS课程页面可以预览,即ViewCourse谁都可以进入,但是Viegment只有购买了(在)其所在的Course之后才可以看(免费课程除外)。怎么判断用户购买了?在T_UserCourses中能查到这个这个用户的这个课程,并且没有过期。
    项目任务: ViewCourse中,如果课程为免费,则不显示“购买课程”,显示为“免费课程”,否则还显示“课程价格”;如果是收费课程并且已经购买,则显示“本课程已购买”。
    项目任务:我已经购买的课程的列表页面。 http://www.rupeng.com/BuyCourses/MyCourse
    项目任务:生成订单后,让用户选择不同的支付方式。


    <two>Redis消息队列:

    MQ:Message Queue
    消息队列服务器:MSMQ、ActiveMQ、Redis等
    项目任务:确定邮件的发送,重置密码的发送(发送可能会很慢,而且有可能还需要重试),用消息队列把注册过程和邮件发送过程分开

    TestMessageQueue_Enqueue.csProj
    //生产者,入队列
    static void Main(string[] args)
    {
    while (true) //while all the time,wait input email --生产邮箱
    {
    string input = Console.ReadLine();
    using(ServiceStack.Redis.IRedisClient client = RedisManager.ClientManager.GetClient())
    {
    client.EnqueueItemOnList("emails", input); //向集合emails中入队用户输入的邮箱
    }
    }
    }

    TestMessageQueue_Dequeue.csProj
    //消费者,出队列
    static void Main(string[] args)
    {
    using(ServiceStack.Redis.IRedisClient client = RedisManager.ClientManager.GetClient())
    {
    while (true) //while all the time,wait output email --消费邮箱
    {
    string email = client.DequeueItemFromList("emails"); //从指定集合emails出队邮箱
    if (email == null)
    {
    Console.WriteLine("没找到");
    Thread.Sleep(500);
    continue;
    }
    Console.WriteLine("fount email :" + email + ",active this email");
    }
    }
    }

    <three>Quartz.Net:定时任务框架

    每隔一段时间执行一次任务
    给计划者IScheduler一个工作IJob,让她在Trigger这个条件下执行这个工作d
    添加Quartz.Net的两个dll的引用---用一段固定代码进行配置(不需要记)---
    建一个任务,实现了IJob接口
    Timer十分不精准,且只适用于控制台程序,Quartz.Net比WebForm的Timer更精准
    创建一个计划,
    得到这个计划者,
    创建一个任务

    //TestQuartz.NET(定时任务)
    private void button1_Click(object sender, EventArgs e)
    {
    //每隔一段时间执行一个任务
    ISchedulerFactory sf = new StdSchedulerFactory();
    IScheduler sched = sf.GetScheduler(); //获得一个计划任务
    JobDetail job = new JobDetail("job1", "group1", typeof(MyJog)); ////MyJog为实现了IJob接口的类
    DateTime dt = TriggerUtils.GetNextGivenSecondDate(null, 5); //5秒后开始第一次运行
    TimeSpan interval = TimeSpan.FromSeconds(5); //时间段--每隔5s执行一次
    //每若干小时运行一次,小时间隔由appsettings中的IndexIntervalHour参数指定
    Trigger trigger = new SimpleTrigger("trigger1", "group1", "job1", "group1", dt, null, SimpleTrigger.RepeatIndefinitely, interval);
    sched.AddJob(job,true);
    sched.ScheduleJob(trigger);
    sched.Start();
    }

    class MyJog : IJob
    {
    public void Execute(JobExecutionContext context)
    {
    MessageBox.Show("我执行了l");
    }
    }

    Quartz.Net实现:每10分钟定时发送系统数据:新增用户数据
    每次用户注册的时候,都把用户的注册信息:用户名、邮箱、手机号等.除了正常的注册之外,额外再把这些数据放入消息队列。
    每隔5分钟,定时从消息队列中取出新注册用户信息,然后发送邮件给业务人员.
    SendNewRegUserEmailJob.cs
    1 直接Global不行: RupengTimer类库 //如鹏定时任务类库(因为在Global中进行会有兼容性问题)
    2 Console控制台程序也不可以: Application.Run(new FormMain()); //加载WinForm时直接启动WinForm程序,因为上面是在WinForm中测试成功的
    3 WinForm中执行定时任务还是不行: 因为时间(与世界标准时间是差8h) ---革零为致时间与北京(东八区)时间差8h
    DateTime.Now 应该是: DateTime dt = TriggerUtils.GetNextGivenSecondDate(null, 1);

    项目任务:确定邮件的发送;重置密码邮件的发送都放入单独的定时任务类库中.

    //namespace RupengWangTimer
    public class SendNewRegisterUM //用于发送新注册用户信息
    {
    /// <summary>
    /// 定时执行计划--把队列中的用户信息定时发送给指定人员
    /// </summary>
    public static void TimeExecuteSchedule()
    {
    //每隔一段时间执行一个任务
    ISchedulerFactory sf = new StdSchedulerFactory();
    IScheduler sched = sf.GetScheduler(); //获得一个计划任务
    JobDetail job = new JobDetail("jobTSUM", "groupTSUM", typeof(SendFromQueueUserMessage)); ////MyJog为实现了IJob接口的类
    //DateTime dt = TriggerUtils.GetNextGivenSecondDate(null, 5); //5秒后开始第一次运行
    //DateTime dt = DateTime.Now; //立即执行
    DateTime dt = TriggerUtils.GetNextGivenSecondDate(null, 1); //1s后开始执行
    TimeSpan interval = TimeSpan.FromHours(1); //时间段--每隔50s执行一次
    //每若干小时运行一次,小时间隔由appsettings中的IndexIntervalHour参数指定
    Trigger trigger = new SimpleTrigger("triggerTSUM", "groupTSUM", "jobTSUM", "groupTSUM", dt, null, SimpleTrigger.RepeatIndefinitely, interval);
    sched.AddJob(job, true);
    sched.ScheduleJob(trigger);
    sched.Start();
    }

    /// <summary>
    /// 把队列中的用户信息定时发送给指定人员
    /// </summary>
    class SendFromQueueUserMessage : IJob
    {
    public void Execute(JobExecutionContext context)
    {
    using(IRedisClient client = RedisManager.ClientManager.GetClient())
    {
    string newUserMessages = "";
    while(true)
    {
    string newUserMessage = client.DequeueItemFromList("NewUserMessageQueue.");
    if (newUserMessage == null)
    {
    if (newUserMessages.Length<=0)
    {
    return;
    }
    //把所有新注册用户信息作为指定邮件发送
    MailSendHelper.MailSend("新注册用户信息", "245573276@qq.com", newUserMessages);
    //Thread.Sleep(500);
    //continue;
    }
    else
    {
    newUserMessages += newUserMessage + " ";
    }
    }
    }
    }
    }

    //Day10---------------20150422---------------------------


    关于搜索:站内搜索技术

    1 全文检索
    like查询是全表扫描(为性能杀手)
    Lucene.Net搜索引擎,开源,比sql搜索引擎是收费的
    Lucene.Net只是一个全文检索开发包(只是帮我们存数据取数据,并没有界面,可以看作一个数据库,只能对文本信息进行检索)
    Lucene.Net原理:把文本切词保存,然后根据词汇表的页来找到文章
    分词算法:
    引用Lucene.Net


    2 一元分词算法 //StandardAnalyzer默认的分词算法
    Analyzer analyzer=new StandardAnalyzer();
    TokenStream tokenStream=analyzer.TokenStream("",new StringReader("北京,HI欢饮你hello word"));
    Lucene.Net.Analysis.Token token=null;
    while((token=tokenStream.Next())!=null)
    {
    Console.WriteLine(token.TernText());
    }
    Console.ReadKey();


    3 二元分词算法 //CJKAnalyzer.cs和CJKTokenizer.cs //CJK:China Japan Korean
    Analyzer analyzer=new CJKAnalyzer(); // new StandardAnalyzer();
    TokenStream tokenStream=analyzer.TokenStream("",new StringReader("北京,HI欢饮你"));
    Lucene.Net.Analysis.Token token=null;
    while((token=tokenStream.Next())!=null)
    {
    Console.WriteLine(token.TernText());
    }
    Console.ReadKey();
    基于词库的分词算法(庖丁解牛盘古分词算法)


    4 盘古分词算法
    打开PanGu4LueneWebDemoBin,将Dictionaries添加到项目根路径(改名Dict),
    添加PanGu.dll的引用(如果直接引用PanGu.dll则必须不带PanGu.xml)、
    添加PanGu4LueneRelease中PanGu.Luene.Analyzer.dll的引用
    Analyzer analyzer=new PanGuAnalyzer();
    TokenStream tokenStream=analyzer.TokenStream("",new StringReader("北京,HI欢饮你hello word"));
    Lucene.Net.Analysis.Token token=null;
    while((token=tokenStream.Next())!=null)
    {
    Console.WriteLine(token.TernText());
    }
    //报错:文件属性复制到输出目录如果较新则复制(可以把文件文档拷贝到Debug中)
    其中PanGu_Release_V2.3.1.0ReleaseDictManage.exe可以查看Dict.dct二进制词库,既可以查看词汇也可以加入词汇


    5 Luene.Net写入类介绍
    步骤:
    打开文件夹,指定要写入的文件夹
    文件加锁,避免两个人同时写入文件(并发)
    判断是否文件中有数据,有的话就更新数据,没有就创建
    逐一读取待读文件中文本并写入文档
    写之后进行close,则表示解锁,可以由其他人写入(加锁写入过程中程序出现bug需要强制解锁时可能出问题)
    各种类的作用:
    Directory保存数据:FSDirectory(文件中),RAMDirectory(内存中)
    IndexReader对索引库进行读取的类,IndexWriter对索引库进行写的类
    IndexReader的bool IndexExists(Directory directory)判断目录是否是一个索引目录
    IndexWriter的bool IsLocked(Directory directory)判断目录是否是锁定的
    IndexWriter在进行写操作时会自动加锁,close的时候会自动解锁.IndexWriter.Unlock方法手动解锁(比如还没来得及close IndexWriter程序就崩溃了,可能造成一直被锁定)
    IndexWriter(Directory dir,Analyzer a,bool create,MaxFieldLength mfl)写入哪个文件夹,采用什么分词算法,是否是创建,最大大小
    void AddDocument(Document doc),向索引中添加文档
    Add(Field field)向文档中添加字段
    DeleteAll()删除所有文档,DeleteDocuments按照条件删除文档
    File类得构造函数 Field(string name,string value,Field.Store store,Field.Index index,Field.TermVector termVector)
    上面依次表示:(字段名,字段值,是否把原文保存到索引中,index表示如何创建索引(Field.Index需要进行全文检索,NOT_ANALYZED不需要的),termVector表示索引词之间的距离,超出则关联度低)
    处理并发(写的时候只能逐一写入):用消息队列保证只有一个程序(线程)对索引操作,其他程序不直接进行索引库的写入,而是把要写入的数据放入消息队列,由单独的程序从消息队列中取数据进行索引库的写入


    6 文章写入索引集成
    文章新增编辑时把新闻放入消息队列
    NewsIndexer.cs对新闻消息队列进行处理,然后写入索引库的类

    /// <summary>
    /// 对新闻队列进行处理,然后加入新闻索引
    /// </summary>
    class NewsIndexer
    {
    public void Start()
    {
    while(true)
    {
    using(var client = RedisManager.ClientManager.GetClient())
    {
    string json = client.DequeueItemFromList("NewsQueue");
    if(json==null)
    {
    Thread.Sleep(100);
    }
    else
    {
    //把新闻队列中的json反序列化,加入索引
    Dictionary<string, object> dict = (Dictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json);
    long id = Convert.ToInt64(dict["Id"]);
    long categoryId = Convert.ToInt64(dict["CategoryId"]);
    string title = dict["Title"].ToString();
    string newsContent = dict["NewsContent"].ToString();
    //写入索引库
    WriteToIndex(id, categoryId, title, newsContent);
    }
    }
    }
    }

    /// <summary>
    /// 写入索引库
    /// </summary>
    static void WriteToIndex(long id, long categoryId, string title, string newsContent)
    {
    FSDirectory directory = null;
    IndexWriter writer = null;
    try
    {
    string indexPath = "d:rupeng_news_index"; //索引库路径
    directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory()); //索引保存到指定文件目录,并加锁
    bool isExist = IndexReader.IndexExists(directory); //读索引时,指定目录中索引是否存在
    if (isExist)
    {
    if(IndexWriter.IsLocked(directory)) //写索引时,如果索引目录是被锁定(比如索引过程中程序异常退出),需要先解锁
    {
    IndexWriter.Unlock(directory);
    }
    }
    //一条一条写入目录索性
    writer = new IndexWriter(directory, new PanGuAnalyzer(), !isExist, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);
    Document document = new Document();
    document.Add(new Field("id", id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); //不需要索引
    document.Add(new Field("categoryId", id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
    document.Add(new Field("title", id.ToString(), Field.Store.YES, Field.Index.ANALYZED,Lucene.Net.Documents.Field.TermVector.WITH_OFFSETS));//需要索引
    document.Add(new Field("newsContent", id.ToString(), Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_OFFSETS));
    writer.AddDocument(document);
    }
    finally
    {
    if(writer!=null) //不要忘了回收资源,否则搜索不到
    {
    writer.Close();
    }
    if (directory!=null)
    {
    directory.Close();
    }
    }
    }
    }

    //用多线程避免页面卡死,避免每篇都写入---???---


    7 文章的搜索
    query.Add(new Term("字段名","关键词"))
    query.Add(new Term("字段名2","关键词2"))
    类似于:where 字段名contains关键词 and 字段名2contains关键词2
    PhraseQuery用于进行多个关键词的检索
    PhraseQuery.SetSlop(int slop)用来设置单词之间的最大距离
    BooleanQuery可以实现字段名contains关键词or字段名2contains关键词2

    NewsSearchController.ashx
    /// 文章搜索
    public void Search(HttpContext context)
    {
    string keyword = context.Request["keyword"];
    string[] words = keyword.Split(' ');
    PhraseQuery query = new PhraseQuery();
    foreach(string word in words)
    {
    query.Add(new Term("newsContent", word)); //新闻索引中字段名newsContent包含关键字word
    }
    query.SetSlop(1000);

    List<SearchResult> results = new List<SearchResult>();

    FSDirectory directory = FSDirectory.Open(new DirectoryInfo("D:/rupeng_news_index"), new NoLockFactory());
    IndexReader reader = IndexReader.Open(directory, true);//采用IndexReader来打开索引目录
    IndexSearcher searcher = new IndexSearcher(reader);//通过IndexSearcher来进行搜索
    TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);//通过TopScoreDocCollector来获得查询结果,最多100条结果
    searcher.Search(query, null, collector);//开始搜索,使用query这个条件进行搜索
    ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;//取得其中的多少条,collector.TopDocs(m,n)获得结果中第m到n条结果
    for (int i = 0; i < docs.Length;i++ )
    {
    int docId = docs[i].doc;//ScoreDoc是查询的文档的结果的数据,ScoreDoc.doc是获得lucene为每个document分配的主键
    Document doc = searcher.Doc(docId);//根绝docId再次查询文档的信息
    long id = Convert.ToInt64(doc.Get("id"));
    long categoryId = Convert.ToInt64(doc.Get("categoryId"));
    string title = doc.Get("title");
    //string newsContent = doc.Get("newsContent");
    SearchResult result = new SearchResult();
    result.Url = "/NewsFile/" + categoryId + "/" + id + ".shtml";
    result.Title = title;
    results.Add(result);
    }
    RPHelper.RPOutputHtml(context, "~/SearchFile/NewsSearch.cshtml", new { results = results, keyword = keyword });

    }
    }
    public class SearchResult
    {
    public string Url { get; set; }
    public string Title { get; set; }
    }


    8 帮用分词和分页
    //分词
    加入索引说采用的分词算法需要与搜索的分词算法一致
    用盘古分词算法的Segment类进行切词
    PanGu.Segment segment = new PanGu.Segment();
    var wordInfos = segment.DoSegment(keyword);//切分关键词得到关键词集合
    foreach(var wordInfo in wordInfos)
    {
    query.Add(new Term("newsContent", wordInfo.Word));
    //where contains(newsContent,"北京") and contains(newsContent,"工程师")
    }
    //分页
    NewsSearchController.ashx
    //点击页码时跳转到所点击的页面
    string pagenumStr = context.Request["pagenum"];
    int pagenum = 1;//默认为初始页
    if(!string.IsNullOrWhiteSpace(pagenumStr))
    {
    pagenum = Convert.ToInt32(pagenumStr);
    }

    //获得当前页所有文档
    int pageSize=5; //如果pageSize=5; 0--4 5--9
    //查询结果集合应该是从(pagenum-1)*5,pagenum*5-1,但是collector.TopDocs(m,n)的n是条数
    ScoreDoc[] docs = collector.TopDocs((pagenum - 1) * 5, pageSize).scoreDocs;

    RPHelper.RPOutputHtml(context, "~/SearchFile/NewsSearchResult.cshtml", new {
    results = results,
    keyword = keyword,
    totalSize = collector.GetTotalHits(),
    pageSize=pageSize,
    currentPage=pagenum
    });

    NewsSearchResult.cshtml
    //在结果列表时,可以直接调用RPHelper中的Pager方法确定页码格式
    <ul class='pagination col-md-12' style='auto'>
    @RupengWangRazor.RPHelper.Pager("/SearchFile/NewsSearchController.ashx?action=Search&keyword="+RupengWangRazor.RPHelper.UrlEncode(Model.keyword)+"&pagenum={pagenum}",Model.totalSize,Model.pageSize,Model.currentPage)
    </ul>

    //搜索不出来:索引库路径是否正确,写入索引与搜索的分词算法是否一致


    9 通过多线程避免界面卡死
    耗时操作阻塞了主线程
    Thread thread=new Thread(F1); //委托
    thread.IsBackground=true;//主线程(界面线程)指向结束后子线程自动结束,因为把子线程设置为了后台线程
    thread.Start(); //在子线程中指向F1方法
    //子线程不能直接操作界面控件
    //textBox1.Text="正在读取第"+i+"次"
    //在子线程中操作界面控件的时候必须通过BeginInvoke
    textBox1.BeginInvoke(new Action(()=>
    {
    textBox1.Text="正在读取第"+i+"次"
    }));

    TestThread.Form1.cs
    private void button1_Click(object sender, EventArgs e)
    {
    Thread thread = new Thread(F1);//将F1的耗时操作委托给子线程
    //主线程结束后子线程自动结束(关闭窗口后,子线程依然在执行,因为子线程也在前台,需要将子线程设置到后台)
    thread.IsBackground = true;
    thread.Start();//在子线程中执行F1
    }
    public void F1()
    {
    //点击时不能进行任何操作,因为耗时操作阻塞了主线程,需要将耗时操作委托给子线程
    for (int i = 1; i < 100; i++)
    {
    File.ReadAllBytes(@"D:NET Framework 4.5Net Framework 4.5dotnetfx45_full_x86_x64_XiaZaiBa.zip");
    //子线程不能直接操作界面控件
    //textBox1.Text = "正在读取第" + i + "次";
    //在子线程中操作界面控件必须通过BeginInvoke
    textBox1.BeginInvoke(new Action(() => {
    textBox1.Text = "正在读取第" + i + "次";
    }));
    }
    }

    RuPengWangTimerDingShi.csProj---Form1.cs---SendNewRegisterUM.TimeExecuteWriteNewsIndex();
    //定时任务,子进程中出队列,然后写入文章索引,关闭窗口时终止子进程(出队列)和quartz.net进程
    首先,启动窗体,执行定时任务,而定时的任务是进行新闻的出队列
    然后,新闻的出队列是耗时操作,需要委托子进程,并设为后台进程,然后开始执行进程,其中出队列进程的控制由while(IsRunning)控制,先预先设置IsRunning=true
    IsRunning = true;
    Thread thread = new Thread(RunScan);//委托给子线程去RunScan
    thread.IsBackground = true;//该子线程为后台线程
    thread.Start();//执行该后台子线程,去执行RunScan方法
    然后,执行出队列这个后台子进程
    public static bool IsRunning { get; set; }//是否继续线程
    public void RunScan()
    {
    while (IsRunning)//一旦窗体关闭,IsRunning=false,该进程终止
    {...
    然后,一直执行这个子进程,直到窗体被关闭,这时设置IsRunning=false使还在执行的这个后台子进程Thread的RunScan()终止,同时还需终止后台Quartz.net进程,避免窗体关闭而进程还在
    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
    NewsIndexer.IsRunning = false;//终止后台子进程RunScan方法
    SendNewRegisterUM.schedWNI.Shutdown();//还需要终止后台Quartz.net进程,避免窗体已关闭,但是进程依然在
    }

    10 获取html的InnerText

    搜索出来的不仅只是Title,还需要预览一部分内容body
    用Lucene.net放入索引的时候需要过滤html标签
    解决索引中body中全是html标签的问题,不利于搜索,很多垃圾信息,显示不方便。
    使用HtmlAgilityPack进行innerText处理.
    考虑文章编辑重新索引等问题,需要先把旧的文档删除,再增加新的(等价于update)HTML解析器:输入一个html文档,提供对html文档操作的接口
    开发包HtmlAgilityPack.1.4.0.zip,用于把html标签进行innerText后再放入索引库

    TestHtmlAgilityPack.csProg---Program.cs
    HtmlDocument htmlDoc = new HtmlDocument();
    htmlDoc.Load(@"D: emphtmlAgilityPack.txt");
    //HtmlNode node = htmlDoc.GetElementbyId("p11");//获得hmtl文档中id为p11的标签节点
    //Console.WriteLine(node.InnerText);
    Console.WriteLine(htmlDoc.DocumentNode.InnerText);//获得html文档中的文档节点的innerText显示
    //htmlDoc.DocumentNode.DescendantNodes()
    Console.ReadKey();

    11 一键重建全文检索
    HtmlAgilityPack.dll提供操作Html文档的标签方法
    获得网页title:doc.DocumentNode.SelectSingleNode("//title").InnerText;//XPath中"//title"表示所有title节点;SelectSingleNode用于获取满足条件的唯一节点
    获得所有超链接:doc.DocumentNode.Descendants("a");
    获得name为kw的input,相当于getElementByName();
    var kwBox=doc.DocumentNode.SelectSingleNode("//input[@name='kw']");//"//input[@name='kw']"也是XPath语法,表示name=kw的input标签

    首先 把news中body的html标签去掉后加入队列,
    NewsController.ashx
    /// 一键重新加入全文检索索引
    public void ReJoinAllNewIndex(HttpContext context)
    {
    //1
    //List<News> list = new NewsBLL().GetModelList("");
    ////获得所有新闻,但是如果大量新闻,需要分页获取新闻,如每次100条,暂时将就----???-----
    //foreach(News news in list)
    //{
    // //把新闻的body中html标签去掉hmtl标签,加入队列,然后建立索引
    // AdminHelper.JoinAllNewQueue(news.Id, news.CategoryId, news.Title, news.NewsContent);
    //}
    //AjaxHelper.WriteJson(context, "ok", "", null);

    //2
    //获得所有新闻,但是如果大量新闻,需要分页获取新闻,如每次100条,暂时将就----!!!-----
    //select * from (select ROW_NUMBER() over(order by Id asc) rownum,* from T_News) t where rownum>-1 and rownum<=20 ------***-----
    //1-100,101-200,201-300,(i-1)*100+1,i*100
    //int total=220;
    int total = new NewsBLL().GetRecordCount("");//总的新闻条数
    int n = (int)Math.Ceiling(total/100.0);//新闻条数/100的水仙花数
    for (int i = 1; i <= n;i++ )//如果新闻数量太多,每次获取100条数据,总获取n次
    {
    List<News> list = new NewsBLL().GetNewsesPagerByRowNum(((i - 1) * 100 + 1), i * 100);
    foreach (News news in list)
    {
    //把新闻的body中html标签去掉hmtl标签,加入队列,然后建立索引
    AdminHelper.JoinAllNewQueue(news.Id, news.CategoryId, news.Title, news.NewsContent);
    }
    }
    AjaxHelper.WriteJson(context, "ok", "", null);
    }

    AdminHelper.cs
    /// 将每个新闻的body中html标签去掉后加入队列,然后建立索引
    public static void JoinAllNewQueue(long id,long categoeyId,string title,string newsContent)
    {
    News news = new News();//该新闻对象的newsContent会被innerText
    news.Id = id;
    news.CategoryId = categoeyId;
    news.Title = title;
    HtmlDocument htmlDoc = new HtmlDocument();
    htmlDoc.LoadHtml(newsContent);
    news.NewsContent = htmlDoc.DocumentNode.InnerText;//用HtmlAgilityPack解析器将去掉文档中的html,变为innerText
    NewsQueue(news);//加入新闻队列
    }

    然后 从出队列后写入检索索引之前,删除重复id的索引
    NewsIndexer.cs
    //每次出队列加入检索索引之前,都需要删除文档索引中的相同id的文档索引,因为"编辑新闻"和"一键重建全文索引"都会再次加入同id的索引
    writer.DeleteDocuments(new Term("id", id.ToString()));

    然后 优化:每次出队列都进行一次索引路径的打开读取和关闭,效率低
    全部出队列之前先打开索引目录,之后才关闭索引目录,最后才等待下一次client的队列中新数据
    public static bool IsRunning { get; set; }//是否继续线程
    public void RunScan()
    {
    while (IsRunning)//一旦窗体关闭,IsRunning=false,该进程终止
    {
    using (var client = RedisManager.ClientManager.GetClient())
    {
    ProessQueue(client);//一直进行队列,结束后才回收队列
    }
    }
    }

    //全部出队列之前先打开索引目录,之后才关闭索引目录,最后等待队列中新数据
    private static void ProessQueue(ServiceStack.Redis.IRedisClient client)
    {
    FSDirectory directory = null;
    IndexWriter writer = null;
    try
    {
    string indexPath = "d:rupeng_news_index"; //索引库路径
    directory = FSDirectory.Open(new DirectoryInfo(indexPath), new NativeFSLockFactory()); //索引保存到指定文件目录,并加锁
    bool isExist = IndexReader.IndexExists(directory); //读索引时,指定目录中索引是否存在
    if (isExist)
    {
    if (IndexWriter.IsLocked(directory)) //写索引时,如果索引目录是被锁定(比如索引过程中程序异常退出),需要先解锁
    {
    IndexWriter.Unlock(directory);
    }
    }
    writer = new IndexWriter(directory, new PanGuAnalyzer(), !isExist, Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED);

    //一直进行队列,结束后才关闭索引目录
    while (true)
    {
    string json = client.DequeueItemFromList("NewsQueue");
    if (json == null)
    {
    //如果json 这个队列李没有数据,线程就一直sleep、
    //这里应该把代码放到另一个线程中,让另一个线程去执行
    //主线程仍然继续执行程序
    Thread.Sleep(100);
    return;//全部出队列后,在finally中关闭索引目录,最后回收队列,然后等待队列,直到IsRunning=false
    }
    else
    {
    //把新闻队列中的json反序列化,加入索引
    Dictionary<string, object> dict = (Dictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json);
    long id = Convert.ToInt64(dict["Id"]);
    long categoryId = Convert.ToInt64(dict["CategoryId"]);
    string title = dict["Title"].ToString();
    string newsContent = dict["NewsContent"].ToString();
    //写入索引库
    WriteToIndex(writer,id, categoryId, title, newsContent);
    //File.AppendAllText(@"D: emp1.txt","id="+id);//用于判断写入索引速度,很快
    }
    }

    //全部出队列后关闭索引目录
    }
    finally
    {
    if (writer != null) //不要忘了回收资源,否则搜索不到
    {
    writer.Close();
    }
    if (directory != null)
    {
    directory.Close();
    }
    }
    }

    /// 写入索引库
    static void WriteToIndex(IndexWriter writer,long id, long categoryId, string title, string newsContent)
    {
    //每次出队列加入检索索引之前,都需要删除文档索引中的通id索引,因为"编辑新闻"和"一键重建全文索引"都会再次加入同id的索引
    writer.DeleteDocuments(new Term("id", id.ToString()));

    Document document = new Document();
    document.Add(new Field("id", id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); //不需要索引
    document.Add(new Field("categoryId", categoryId.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
    document.Add(new Field("title", title.ToString(), Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_OFFSETS));//需要索引
    document.Add(new Field("newsContent", newsContent.ToString(), Field.Store.YES, Field.Index.ANALYZED, Lucene.Net.Documents.Field.TermVector.WITH_OFFSETS));
    writer.AddDocument(document);
    }

    NewsSearchController.ashx
    /// 文章搜索
    public void Search(HttpContext context)
    {
    string keyword = context.Request["keyword"];
    string pagenumStr = context.Request["pagenum"];
    int pagenum = 1;//默认为初始页
    if(!string.IsNullOrWhiteSpace(pagenumStr))
    {
    pagenum = Convert.ToInt32(pagenumStr);
    }

    PhraseQuery query = new PhraseQuery();
    //因为加入索引时是用盘古分词算法,所以切词时也应该是盘古分词算法
    //盘古分词算法中的Segment类可以更好的进行切词
    PanGu.Segment segment = new PanGu.Segment();
    var wordInfos = segment.DoSegment(keyword);//切分关键词得到关键词集合
    foreach(var wordInfo in wordInfos)
    {
    query.Add(new Term("newsContent", wordInfo.Word));
    //where contains(newsContent,"北京") and contains(newsContent,"工程师")
    }
    //string[] words = keyword.Split(' ');
    //foreach(string word in words)
    //{
    // query.Add(new Term("newsContent", word)); //新闻索引中字段名newsContent包含关键字word
    //}
    query.SetSlop(1000);

    List<SearchResult> results = new List<SearchResult>();

    FSDirectory directory = FSDirectory.Open(new DirectoryInfo("D:temp/rupeng_news_index"), new NoLockFactory());
    IndexReader reader = IndexReader.Open(directory, true);//采用IndexReader来打开索引目录
    IndexSearcher searcher = new IndexSearcher(reader);//通过IndexSearcher来进行搜索
    TopScoreDocCollector collector = TopScoreDocCollector.create(1000, true);//通过TopScoreDocCollector来获得查询结果,最多100条结果
    searcher.Search(query, null, collector);//开始搜索,使用query这个条件进行搜索
    //ScoreDoc[] docs = collector.TopDocs(0, collector.GetTotalHits()).scoreDocs;//取得其中的多少条,collector.TopDocs(m,n)获得结果中第m到n条结果
    int pageSize=5; //如果pageSize=5; 0--4 5--9
    //查询结果集合应该是从(pagenum-1)*5,pagenum*5-1,但是collector.TopDocs(m,n)的n是条数
    ScoreDoc[] docs = collector.TopDocs((pagenum - 1) * 5, pageSize).scoreDocs;

    for (int i = 0; i < docs.Length;i++ )
    {
    int docId = docs[i].doc;//ScoreDoc是查询的文档的结果的数据,ScoreDoc.doc是获得lucene为每个document分配的主键
    Document doc = searcher.Doc(docId);//根绝docId再次查询文档的信息
    long id = Convert.ToInt64(doc.Get("id"));
    long categoryId = Convert.ToInt64(doc.Get("categoryId"));
    string title = doc.Get("title");
    string newsContent = doc.Get("newsContent");
    SearchResult result = new SearchResult();
    result.Url = "/NewsFile/" + categoryId + "/" + id + ".shtml";
    result.Title = title;
    //result.NewContent = newsContent;
    result.NewContent = HighLight(keyword, newsContent);
    results.Add(result);
    }
    RPHelper.RPOutputHtml(context, "~/SearchFile/NewsSearchResult.cshtml", new {
    results = results,
    keyword = keyword,
    totalSize = collector.GetTotalHits(),
    pageSize=pageSize,
    currentPage=pagenum
    });
    }

    12 搜索结果高亮显示
    添加PanGu.HighLight.dll引用

    /// 显示的内容:关键字高亮显示,以及获得最匹配摘要段
    /// <param name="keyword">关键字</param>
    /// <param name="content">新闻内容body</param>
    public string HighLight(string keyword, string content)
    {
    //创建HTMLFormatter,参数为高亮单词的前后缀
    //PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter =
    // new PanGu.HighLight.SimpleHTMLFormatter("<font color="red">", "</font>");
    PanGu.HighLight.SimpleHTMLFormatter simpleHTMLFormatter =
    new PanGu.HighLight.SimpleHTMLFormatter("<span class="keywordHightlight">", "</span>");//正规网站的颜色应该用css表示
    //创建Highlighter,输入HTMLFormatter和盘古分词对象Segment
    PanGu.HighLight.Highlighter highlighter = new PanGu.HighLight.Highlighter(
    simpleHTMLFormatter, new Segment());
    //设置每个摘要段得字符数
    highlighter.FragmentSize = 100;
    //获得最匹配的摘要段
    return highlighter.GetBestFragment(keyword, content);
    }

    项目任务:完成新闻搜索、视频笔记搜索功能,而且是综合搜索
    //搜索(分页高亮显示)-->建立索引-->出队列-->入队列T_Segment(Id,Name,note,ChapterId)T_News(Id,Title,NewsContent,CategoryId)


    13 短信验证码调用接口
    yuntongxun.com有8元得免费短信
    调用短信接口大量发送短信,对于短信模板进行审核,重要短信模板没有非法信息就可以通过
    模板短信--Rest API
    短信运营商提供的接口,其实就是http接口,把要发送的手机号模板短信id通过http协议发送这个接口(发给运营商,运营商去发请求)
    API--体验及SDK下载--短信验证码--Demo下载--Rest Server Demo--下载NET--CCPRestSDK.dll

    TestRestServer.csProj--Program.cs
    protected void Page_Load(object sender, EventArgs e)
    {
    string ret = null;
    CCPRestSDK.CCPRestSDK api = new CCPRestSDK.CCPRestSDK();
    bool isInit = api.init("sandboxapp.cloopen.com", "8883");
    api.setAccount(主帐号, 主帐号令牌);
    api.setAppId(应用ID);
    try
    {
    if (isInit)
    {
    Dictionary<string, object> retData = api.SendTemplateSMS(短信接收号码, 短信模板id, 内容数据);
    ret = getDictionaryData(retData);
    }
    else
    {
    ret = "初始化失败";
    }
    }
    catch (Exception exc)
    {
    ret = exc.Message;
    }
    finally
    {
    Response.Write(ret);
    }
    }

    private string getDictionaryData(Dictionary<string, object> data)
    {
    string ret = null;
    foreach (KeyValuePair<string, object> item in data)
    {
    if (item.Value != null && item.Value.GetType() == typeof(Dictionary<string, object>))
    {
    ret += item.Key.ToString() + "={";
    ret += getDictionaryData((Dictionary<string, object>)item.Value);
    ret += "};";
    }
    else
    {
    ret += item.Key.ToString() + "=" + (item.Value == null ? "null" : item.Value.ToString()) + ";";
    }
    }
    return ret;
    }
    }

    //ret=statusCode=000000;statusMsg=成功;data={TemplateSMS={dateCreated=20150531193711;smsMessageSid=201505311937105998598;};};

    项目任务:
    注册时短信获取验证码;
    虚拟班级内群发短信活动通知(各位同学,今天{0}点班级举办线上活动,请准备参加)


    Lucene.Net:开源免费

    找出关键词,记录位置(页码),根绝关键词所在的位置去查找


    分词算法:把一句话分成一个个单词(盘古分词算法)

    把一个文件复制到输出目录,如果较新则复制


    HtmlDocument htmlDoc = new HtmlDocument();
    htmlDoc.Load("d:/temp/1.html");
    // HtmlNode node = htmlDoc.GetElementbyId("p1");
    // Console.WriteLine(node.InnerText);
    Console.WriteLine(htmlDoc.DocumentNode.InnerText);*/

  • 相关阅读:
    IOS数组
    caffe-fasterrcnn程序理解
    pytorch官网上两个例程
    python:批量修改文件名批量修改图片尺寸
    faster rcnn报错:TypeError: slice indices must be integers or None or have an __index__ method
    faster-rcnn原理讲解
    caffe + ssd网络训练过程
    运行Keras版本的Faster R-CNN(1)
    Ubuntu16.04 faster-rcnn+caffe+gpu运行环境配置以及解决各种bug
    ubuntu16+caffe fast-rcnnCPU运行步骤
  • 原文地址:https://www.cnblogs.com/adolphyang/p/4979686.html
Copyright © 2011-2022 走看看