clipboard js复制粘贴插件

clipboard 的一个修改版,解决了手机上会出现光标的问题。


使用方法:


 var clipboard= new Clipboard('#copyText')
    $("#copyText").click(function () {
        SetMsg("已复制");
    })
    clipboard.on('success', function(e) {
        e.clearSelection();
    });


 <div  id="copyText" data-clipboard-action="copy" class="operatePopItem">复制</div>



clipboard.min.zip


松鼠

前段时间买的松鼠,今天也不知道是什么时候死亡的。回到屋子里灯还没开就闻到了死亡的味道。开灯一看笼子,松鼠已经翻身死亡。唉。。。。

删除JavaScript调试函数

此代码为Java语言编写,如果错误还望指出。

import org.omg.CORBA.INTERNAL;
public class Main {
    public static   boolean isJo(int i){
        if(i % 2 == 0){
            return true;
        }else{
            return false;
        }
    }
   public static int[] getConsolePoint(String con) {
        int[] p = new int[]{0, 0};
        try {
            String orignal = new String(con.getBytes(), "UTF-8");
            p[0] = con.toLowerCase().indexOf("console.log");
            int s = 0, e = 0,st=0,ov=0;
            for (int i = p[0]; i < orignal.length(); i++) {
                char ch = orignal.charAt(i);
                if(String.valueOf(ch).equals("\"") || String.valueOf(ch).equals("'")){
                    st++;
                }
                if (String.valueOf(ch).equals("(") && isJo(st)){ s++;}
                if (String.valueOf(ch).equals(")") && isJo(st)){ e++;}
                if (s == e && s != 0 && e != 0 && ov==0) {
                    ov=-1; //匹配完毕
                    p[1] = i;
                    continue;
                }
                if(String.valueOf(ch).equals(",") || String.valueOf(ch).equals(";")){
                    if(ov==-1){
                        p[1]=i;
                        return p;
                    }
                }else if(ov==-1){
                    return p;
                }
            }
        } catch (Exception error) {
        }
        return p;
    }
    public static void main(String[] args) {
        String codeText = "123console.log('((22222我');456"+"\n"+"123Console.log('123')456";
        String[] stringList = codeText.split("\n");
        for (int i = 0; i < stringList.length; i++) {
            if (stringList[i].toLowerCase().indexOf("console.log") > -1) {
                int[] p= getConsolePoint(stringList[i]);
                System.out.println("s:"+String.valueOf(p[0])+"\n"+"e:"+String.valueOf(p[1])+"\n"+"str:"+stringList[i].substring(0,p[0])+stringList[i].substring(p[1]+1,stringList[i].length()));
            }
        }
    }
}


结果:

s:3

e:26

str:123456

s:3

e:20

str:123456


Process finished with exit code 0


PhpStorm 插件开发

Phpstorm 的内核其实是:IntelliJ IDEA。隶属于同一个软件公司,所以要开发Phpstorm插件的话,还是得需要IntelliJ IDEA。其实jetbrains开发的ide都可以用IntelliJ IDEA来开发插件。


IntelliJ IDEA:http://www.jetbrains.com/idea/download/

开发文档:http://www.jetbrains.org/intellij/sdk/docs/welcome.html

官网步骤:http://www.jetbrains.org/intellij/sdk/docs/phpstorm/setting_up_environment.html



创建工程的时候选择:intellij platform plugin ,在project sdk的时候选择 intellij idea iu 具体的版本号可以按你电脑里安装的为准。


屏幕快照 2018-09-03 下午9.50.20.png


Next后会出现一个空的工程结构界面,并且会帮你打开plugin.xml描述文件。


屏幕快照 2018-09-03 下午9.52.45.png


<idea-plugin>
  <id>com.your.company.unique.plugin.id</id>
  <name>Plugin display name here</name>
  <version>1.0</version>
  <vendor email="support@yourcompany.com" url="http://www.yourcompany.com">YourCompany</vendor>
  <description><![CDATA[
      Enter short description for your plugin here.<br>
      <em>most HTML tags may be used</em>
    ]]></description>
  <change-notes><![CDATA[
      Add change notes here.<br>
      <em>most HTML tags may be used</em>
    ]]>
  </change-notes>
  <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->
  <idea-version since-build="173.0"/>
  <!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html
       on how to target different products -->
  <!-- uncomment to enable plugin in all products
  <depends>com.intellij.modules.lang</depends>
  -->
  <extensions defaultExtensionNs="com.intellij">
    <!-- Add your extensions here -->
  </extensions>
  <actions>
    <!-- Add your actions here -->
  </actions>
</idea-plugin>


在上面补充如下代码:

<depends>com.jetbrains.php</depends>
<depends>com.intellij.modules.platform</depends>


注意第二行的id。这里面要填写你的实际包名。如果这个填不对的话,插件是不被ide识别的。其余的可以参考节点名来编写。


点击文件(File)-> Project Structure ->libraries  点击+号,然后选择 java  这里要添加两个文件,这两个文件在phpstorm的安装目录下的plugins里的php目录然后lib目录里。从这个目录结构也可以看出,你编写的插件也可以这样放:plugins/xxx(这个名字你自己取)/lib/目录下。


言归正传,打开plugins/php/lib你会看到几个jar。选择:php.jar 和 php-openapi.jar。这两个就好了。


屏幕快照 2018-09-03 下午10.02.50.png


再次打开Modules-dependencies。然后将compile改为provided。


屏幕快照 2018-09-03 下午10.04.09.png


在你的工程目录下的src右键选择创建一个包


屏幕快照 2018-09-03 下午10.05.08.png

还记得上面提到的id吗?那个id就填写这个包名。这个包名建议格式:com.xxx。例如 com.myplug。这样的话,你的plugin.xml的描述文件里的第二行id也要填写这个。


下面我们来创建一个action。


屏幕快照 2018-09-03 下午10.07.58.png


如下填写


屏幕快照 2018-09-03 下午10.10.32.png


第一个是你的id或者说你的包名

第二个是你的实现类名

第三个是你右键的提示文本

第四个是你的描述文本


由于groups选择的事editorpopupmenu 所以只会在代码编辑区域右键才会弹出这个菜单。下面的keyboard shortcuts是快捷键。可设置可不设置。


下面是编辑器帮你初始化的一个类,包含了一个魔术函数actionPerformed:


package com.myplug;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
public class delline extends AnAction {
    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
    }
}


actionPerformed 这个方法调用的事件是:弹出右键菜单,单击后才会调用。


下面这个例子是我写的一个删除空行的例子,仅供参考:



package com.x;
import com.intellij.codeInsight.editorActions.PasteHandler;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.messages.MessagesService;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.rpc.CommandProcessor;
import java.util.List;
import java.awt.*;
import java.util.ArrayList;
public class delBlankLine extends AnAction {
    @Override
    public void actionPerformed(AnActionEvent e) {
        // TODO: insert action logic here
        final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
        CaretModel caretModel = editor.getCaretModel();
        SelectionModel selectionModel = editor.getSelectionModel();
        Document document = editor.getDocument();
        LogicalPosition logicalPosition = caretModel.getLogicalPosition(); //不带自动换行
        VisualPosition visualPosition = caretModel.getVisualPosition(); //带自动换行产生的行
        String codeText = document.getText().toString();
        String[] stringList = codeText.split("\n");
//      +"\n"+String.valueOf(logicalPosition.column)+"\n"+String.valueOf(logicalPosition.line)
        String lineText = stringList[logicalPosition.line];
        String lineTexts = lineText.replaceAll(" ", "");
        if (lineTexts.equals("")) {
             selectionModel.selectLineAtCaret(); //设置当前行选中
//            EditorActionManager actionManager = EditorActionManager.getInstance();
//            //Insert one more caret below the active caret
//            EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_DELETE_LINE);
//            actionHandler.execute(editor, caretModel.getCurrentCaret(), e.getDataContext());
            String[] d_arr = delete(logicalPosition.line, stringList);
            final Project project = e.getRequiredData(CommonDataKeys.PROJECT);
            //Access document, caret, and selection
            final int start = selectionModel.getSelectionStart();
            final int end = selectionModel.getSelectionEnd();
            //New instance of Runnable to make a replacement
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    document.setText(arrToString(d_arr).toString());
                }
            };
            WriteCommandAction.runWriteCommandAction(project, runnable);
            selectionModel.removeSelection();
//            Messages.showMessageDialog(arrToString(stringList).toString(), "info", Messages.getInformationIcon());
        }
//        Messages.showMessageDialog(stringList[logicalPosition.line].toString(), "info", Messages.getInformationIcon());
    }
    public String[] delete(int index, String array[]) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < array.length; i++) {
            list.add(array[i]);
        }
        list.remove(index);
        String[] newStr = list.toArray(new String[1]); //返回一个包含所有对象的指定类型的数组
        return newStr;
    }
    public String arrToString(String array[]) {
        StringBuffer s_b = new StringBuffer();
        for (int i = 0; i < array.length; i++) {
            s_b.append(array[i].toString() + "\n");
        }
        return s_b.toString();
//        return  StringUtils.join(array, "\n");
    }
}



屏幕快照 2018-09-03 下午10.18.28.png




js屏蔽html冒泡

html冒泡普遍是从子孙节点冒泡到根节点。所以需要对冒泡进行终止。否则容易点击事件混乱。

    question.clickImgHref= function (that) {
        var event = window.event || arguments.callee.caller.arguments[0];
        event.stopPropagation();
        window.location.href="peopleDetail.html?w="+$(that).attr("id");
    }


jq append追加html到节点 加强版

    $.fn.append_plus = function(append_text,append_array){
     //  console.time("time");
        var a_text=append_text;
        Object.keys(append_array).forEach(function(key){
            regExp = new RegExp(key, "g");
            a_text= a_text.replace(regExp, append_array[key]);
        });
        this.append(a_text);
     //   console.timeEnd("time");
    };

使用方法

$(".divClass").append_plus("<div>wadwa</div>",{"a":"b"})


Mysql 多条件排序原理

表结构如下:


+-------+------------------+------+-----+---------+----------------+
| Field | Type             | Null | Key | Default | Extra          |
+-------+------------------+------+-----+---------+----------------+
| id    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| one   | tinyint(1)       | YES  |     | NULL    |                |
| two   | int(10)          | YES  |     | NULL    |                |
| three | int(10)          | YES  |     | NULL    |                |
+-------+------------------+------+-----+---------+----------------+


数据列表

 

mysql> select * from code_test;
+----+------+------+-------+
| id | one  | two  | three |
+----+------+------+-------+
|  1 |    1 |    2 |     0 |
|  2 |    0 |    1 |     1 |
|  3 |    1 |    4 |     0 |
|  4 |    1 |    3 |     0 |
|  5 |    0 |    5 |     1 |
+----+------+------+-------+
5 rows in set (0.00 sec)


单字段排序


mysql> select * from code_test ORDER BY one DESC;
+----+------+------+-------+
| id | one  | two  | three |
+----+------+------+-------+
|  1 |    1 |    2 |     0 |
|  3 |    1 |    4 |     0 |
|  4 |    1 |    3 |     0 |
|  2 |    0 |    1 |     1 |
|  5 |    0 |    5 |     1 |
+----+------+------+-------+
5 rows in set (0.00 sec)


两个字段排序


mysql> select * from code_test ORDER BY one DESC, two DESC;
+----+------+------+-------+
| id | one  | two  | three |
+----+------+------+-------+
|  3 |    1 |    4 |     0 |
|  4 |    1 |    3 |     0 |
|  1 |    1 |    2 |     0 |
|  5 |    0 |    5 |     1 |
|  2 |    0 |    1 |     1 |
+----+------+------+-------+
5 rows in set (0.00 sec)


多字段排序


mysql> select * from code_test ORDER BY one DESC, two DESC, three DESC
    -> ;
+----+------+------+-------+
| id | one  | two  | three |
+----+------+------+-------+
|  3 |    1 |    4 |     0 |
|  4 |    1 |    3 |     0 |
|  1 |    1 |    2 |     0 |
|  5 |    0 |    5 |     1 |
|  2 |    0 |    1 |     1 |
+----+------+------+-------+
5 rows in set (0.00 sec)




多字段排序先根据第一个字段进行排序,然后将这个字段相同的结构进行第二个字段排序。第三个字段是根据前两个字段相同的排序结果进行排序。


图标如下:


微信截图_20180822190847.png


首先排序第一个one字段,在排序第二个two字段的时候分两步:第一步排序one字段为1的,第二步排序one字段为0的值。多字段排序是看前面字段排序的相同值在进行排序。否则无法排序。

js寻找字符串​indexOf的一个问题

定义和用法

indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置。

语法


String.indexOf(searchvalue,fromindex)


参数描述
searchvalue必需。规定需检索的字符串值。
fromindex可选的整数参数。规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。

说明

该方法将从头到尾地检索字符串 stringObject,看它是否含有子串 searchvalue。开始检索的位置在字符串的 fromindex 处或字符串的开头(没有指定 fromindex 时)。如果找到一个 searchvalue,则返回 searchvalue 的第一次出现的位置。stringObject 中的字符位置是从 0 开始的。


提示和注释

注释:indexOf() 方法对大小写敏感!

注释:如果要检索的字符串值没有出现,则该方法返回 -1。



---------------


hotdata.forEach(function (value) {
    if(value['name'].toLowerCase().indexOf($(".searchInp").val().toLowerCase())>-1)
    { 
        /** 查找到字符串 **/
    }
})


这里是搜索框输入的一个判断过程。注意的是如果searchInp的val值为空,那么会返回 0。 即使你的value['name']不是空的值,它依旧返回 0

visual basic测试JavaScript 代码随机数重复

VERSION 5.00
Begin VB.Form Form1 
   Caption         =   "Form1"
   ClientHeight    =   8625
   ClientLeft      =   120
   ClientTop       =   465
   ClientWidth     =   14880
   LinkTopic       =   "Form1"
   ScaleHeight     =   8625
   ScaleWidth      =   14880
   StartUpPosition =   3  '窗口缺省
   Begin VB.CommandButton Command2 
      Caption         =   "Command2"
      Height          =   495
      Left            =   1920
      TabIndex        =   7
      Top             =   4320
      Width           =   1935
   End
   Begin VB.TextBox Text3 
      Height          =   495
      Left            =   720
      TabIndex        =   6
      Text            =   "100"
      Top             =   4440
      Width           =   1095
   End
   Begin VB.CommandButton Command1 
      Caption         =   "Command1"
      Height          =   495
      Left            =   1920
      TabIndex        =   3
      Top             =   3600
      Width           =   1935
   End
   Begin VB.TextBox Text2 
      Height          =   7575
      Left            =   4800
      MultiLine       =   -1  'True
      ScrollBars      =   2  'Vertical
      TabIndex        =   1
      Top             =   480
      Width           =   9375
   End
   Begin VB.TextBox Text1 
      Height          =   2655
      Left            =   360
      MultiLine       =   -1  'True
      ScrollBars      =   2  'Vertical
      TabIndex        =   0
      Text            =   "Form1.frx":0000
      Top             =   360
      Width           =   4215
   End
   Begin VB.Label Label2 
      Caption         =   "Label3"
      BeginProperty Font 
         Name            =   "宋体"
         Size            =   14.25
         Charset         =   134
         Weight          =   700
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      Height          =   735
      Left            =   120
      TabIndex        =   5
      Top             =   6120
      Width           =   1815
   End
   Begin VB.Label Label3 
      Caption         =   "Label3"
      BeginProperty Font 
         Name            =   "宋体"
         Size            =   14.25
         Charset         =   134
         Weight          =   700
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      ForeColor       =   &H008080FF&
      Height          =   735
      Left            =   2400
      TabIndex        =   4
      Top             =   6120
      Width           =   1815
   End
   Begin VB.Label Label1 
      Caption         =   "Label1"
      BeginProperty Font 
         Name            =   "微软雅黑"
         Size            =   15.75
         Charset         =   134
         Weight          =   700
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      Height          =   1095
      Left            =   1080
      TabIndex        =   2
      Top             =   7320
      Width           =   2775
   End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private Function Script(code As String) As String
    Dim obj As Object
    Set obj = CreateObject("MSScriptControl.ScriptControl")
    obj.AllowUI = True
    obj.Language = "JavaScript"
    Script = obj.Eval(code)
End Function
Private Sub Command1_Click()
    Label1.Caption = ""
    Label2.Caption = "拟定:" + Text3.Text
    Text2.Text = 2
    Dim i As Long, b As String, s As String
    For i = 1 To Val(Text3.Text)
        b = Script(Text1.Text)
        If InStr(s, b) > 0 Then
            Label1.Caption = "重复"
            Exit For
        Else
            s = s + b + vbCrLf
        End If
        
    Next
    Label1.Caption = "不重复"
    DoEvents '交出时间片
    Text2.Text = s
    Label3.Caption = "真实:" + CStr(UBound(Split(s, vbCrLf)))
End Sub
Private Sub Command2_Click()
    Label1.Caption = ""
    Label2.Caption = ""
    Label3.Caption = ""
End Sub



实际那种语言都能测,只不过这里是用了vb。

JavaScript 播放音乐

    $(".music").on("timeupdate", function () {
        if (this.currentTime / this.duration === 1) { //播放完毕
            //改变为原来状态
            ...
        }
        var percentage = this.currentTime / this.duration * 100;
        $(".progressBar").css("width", percentage.toFixed(2) + "%") //一个进度条的特效 可以用span来做进度条
    });


这里主要注意一个方法:toFixed:可把 Number 四舍五入为指定小数位数的数字。如果不对这个进度进行精确,会出现进度条已经停止,但是播放还没有完毕。实际进度条并没有停止,只不过肉眼看它的宽度值已经100%。


这里的播放标签是(H5):audio

JavaScript 数组删除元素

Object.defineProperty(Array.prototype,'del',{
    writable:false,
    enumerable:false,//不可遍历
    configurable:true,
    value:function(em){
        // console.log('I am an user defined method!');
        this.splice(this.indexOf(em),1);
    }
});


上述代码是对js原生的array对象增加一个方法:del

H5 聊天框 图片 滚动条无法到最底部

未命名.jpg


在开发中,遇到了一个问题:img标签渲染图片,而图片没有全部展示的时候是无法获取滚动层的实际高度的,也就是无法让滚动条达到最底部。总是会有一部分被遮盖。


解决办法:


img onload 事件:当对象载入完全后调用的一个方法。


通过这个onload事件,在事件里获取图片高度,然后设置img的实际高度就可以解决这个问题了。





JavaScript 时间戳转日期

function timestampToTime(timestamp) {
    var time_var = timestamp;
    if (String(time_var).length === 10)
        time_var = time_var * 1000;
    var date = new Date(Number(time_var));
    var Y = date.getFullYear() + '-';
    var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
    var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
    var h = (date.getHours() < 10 ? '0' + (date.getHours()) : date.getHours()) + ':';
    var m = (date.getMinutes() < 10 ? '0' + (date.getMinutes()) : date.getMinutes()) + ' ';
    var v_time = Y + M + D + h + m;
    var date_n = new Date();
    Y = date_n.getFullYear() + '-';
    M = (date_n.getMonth() + 1 < 10 ? '0' + (date_n.getMonth() + 1) : date_n.getMonth() + 1) + '-';
    D = (date_n.getDate() < 10 ? '0' + (date_n.getDate()) : date_n.getDate()) + ' ';
    return v_time.replace(Y + M + D, "");
}



JavaScript 时间戳转换时间的代码,如果是当前日期,只会返回小时与分钟。

JavaScript 模板引擎

这是一款以JavaScript语言编写的模板引擎,将字符串形式的html文本,解析成DOC对象。然后...

var results =  "<p class='one'>aaaaaaaaa</p><p class='two'>hello 
<b style='test foo' disabled align=\"b\\\"ar\">john 
<a href='http://ejohn.org/'>resig</b> </p>
<div><img src='xw' /></div><p>'hello world'</p>";

html = HTMLtoDOM2(results);
console.log(html);

this.HTMLtoDOM2 = function( html, doc ) {
// 创建唯一性元素结构
var one =  makeMap("html,head,body,title");
// 定义头部标签
var structure = {
    link: "head",
    base: "head"
};
//节点临时存储标签
var  parentNode={
    all_paren:[], //所有的父节点
    current_paren:null //当前的父节点
};
//标签闭合判断
var  closTag={
    star:0, //开始节点数
    end:0 //结束节点数
};
 
if ( !doc ) {
if ( typeof DOMDocument != "undefined" )
    doc = new DOMDocument();
else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
    doc = document.implementation.createDocument("", "", null);
else if ( typeof ActiveX != "undefined" )
    doc = new ActiveXObject("Msxml.DOMDocument");
} else
    doc = doc.ownerDocument ||
        doc.getOwnerDocument && doc.getOwnerDocument() ||
            doc;
var elems = [],
documentElement = doc.documentElement ||
    doc.getDocumentElement && doc.getDocumentElement();
// 如果是一个空文档,那么我们就用预先的html唯一元素标签去填充它
if ( !documentElement && doc.createElement ) (function(){
    var html = doc.createElement("html");
    var head = doc.createElement("head");
    head.appendChild( doc.createElement("title") );
    html.appendChild( head );
    html.appendChild( doc.createElement("body") );
    doc.appendChild( html );
})();
 
// 找到所有唯一的元素
if ( doc.getElementsByTagName )
    for ( var i in one )
        one[ i ] = doc.getElementsByTagName( i )[0]; //原有文档存放入one数组里
 
HTMLParser( html, {
    start: function( tagName, attrs, unary ) {
    // tagName : 元素名
    // attrs : 元素属性
    // unary : 元素是否无结束标签 例如:img
    closTag['star']+=1;
    if ( one[ tagName ] ) {  //判断标签在不在一开始定义的里面 如果在的话 就把标签写到curParentNode 然后判断有没有结束标签
        parentNode['current_paren']  = one[ tagName ];
        if ( !unary ) {
            elems.push( parentNode['current_paren'] );
        }
    return;
    }
    var elem = doc.createElement( tagName ); //实例化这个标签 
     
    for ( var attr in attrs ) //填充属性
        elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
        //判断标签是否是 link 或者 base 并且这个tag在预先设置的不为空 则 追加这个elem到预先设置的节点上
        //主要是head头的提取
    if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
        one[ structure[ tagName ] ].appendChild( elem );
        //如果这个父节点不为空 并且 存在appendchild方法 则父节点加入这个elem标签
    else if ( parentNode['current_paren']  && parentNode['current_paren'].appendChild )
        parentNode['current_paren'].appendChild( elem );
    
        if ( !unary ) {  //不是结束标签 则加入列表 
            elems.push( elem );
            parentNode['current_paren']  = elem;
        }else{ //如果没有</tag>结束标签 则也加1 认为 标签结束
            closTag['end']+=1;
        }
    },
    end: function( tag ) { // 遇到结束标签
        closTag['end']+=1;
        if(closTag['star']==closTag['end']){ //如果结束标签和开始标签一样。那就放在一起
            parentNode['all_paren'].push(parentNode['current_paren']);
            one.body.appendChild(parentNode['current_paren'])
        }
        elems.length -= 1; //去掉列表的最后一个 如果碰到结束标签。
        // 创建一个新的父节点
        parentNode['current_paren']  = elems[ elems.length - 1 ]; //这里减一是从0开始所以减一
    },
    chars: function( text ) {
        parentNode['current_paren'] .appendChild( doc.createTextNode( text ) );
    },
    comment: function( text ) {
        // 过滤注释标签
    }
});
for(var j = 0,len = parentNode['all_paren'].length; j < len; j++)
    one.body.appendChild(parentNode['all_paren'][j])
return doc;
};

屏幕快照 2018-07-23 下午9.39.43.png

阅读全文»

腾讯云通讯IM使用方法之独立模式

官方文档:https://cloud.tencent.com/document/product/269/3794

1、独立模式

独立模式是指用户注册和身份验证由开发者负责,开发者和腾讯之间通过签名验证建立信任关系。开发者在申请接入时,直接 下载公私钥 用于开发,而后用私钥加密指定数据生成签名交由腾讯服务器验证合法性。


上述说明是腾讯的官方说明。这里补一张更直观的图,了解独立模式登录方法,实际只要登录方法明白了,后续的也很简单。


屏幕快照 2018-07-12 下午10.01.23.png


首先用户登录一般都会提交:用户名和密码。提交到我们的服务器,然后判断是否正确。正确则设置缓存或者session,返回json正确信息。错误直接返回json错误信息。一般的逻辑是这样。但是腾讯云通讯独立模式多出来一步,就是当我们所有的验证都正确以后,我们要访问一个服务器。这个服务器是自己搭建的。功能主要是用来生成sig。腾讯也提供了这个服务器的搭建方法。地址:https://cloud.tencent.com/document/product/269/1510 各种平台都有,可以自行斟酌使用。实际提供的都是一些后台代码。还是要部署到我们服务器上的。搞好以后,我们需要利用搭建的sig服务器来进行生成sig。

生成的参数有几个:

1、私钥文件

2、sdkappid

3、用户id

这三个东西去哪里获取,实际很简单,前两个直接通过注册腾讯云通讯的账号就可以获取。

地址如下:https://cloud.tencent.com/document/product/269/1504

按照上述步骤进行操作,就可以获取,而且其中会让你设置一个管理员用户。记住这个非常关键。

第3个用户id就更简单了,你可以设置任意的id,例如纯字符串、字符串加数字等等一些用户id。为了和你的注册用户相关联,建议你用用户提交的用户名来做这个用户id参数。这样通过你的服务器生成sig就可以了。现在我们有了如下文件:

1、用户账户名

2、用户密码

3、sdkappid

4、用户账户名对于的sig文件(实际是一串字符串)

有了这么多文件我们就可以尝试登陆了,在云通讯后台,我们新建一个群组。

1531404796756.jpg现在我们来总结下我们已有的工件:

1、用户账户名

2、用户密码

3、sdkappid

4、用户账户名对于的sig文件(实际是一串字符串)

5、群ID

6、管理员ID

7、群主ID(实际你可以设置成你的管理员id)


然后来打开官方给的demo

//帐号模式,0-表示独立模式,1-表示托管模式
        var accountMode = 0;
        //官方 demo appid,需要开发者自己修改(托管模式)
        var sdkAppID = 云通讯后台查看;
        var accountType = 云通讯后台查看;
        //当前用户身份
        var loginInfo = {
            'sdkAppID': sdkAppID, //云通讯后台查看
            'identifier': '用户id', //当前用户ID,必须是否字符串类型,必填
            'accountType': accountType, //云通讯后台查看
            'userSig': '这个就是你用户id对应的sig文件', //当前用户身份凭证,必须是字符串类型,必填
            'identifierNick': null, //当前用户昵称,不用填写,登录接口会返回用户的昵称,如果没有设置,则返回用户的id
            'headurl': 'img/me.jpg' //当前用户默认头像,选填,如果设置过头像,则可以通过拉取个人资料接口来得到头像信息
        };


这样修改一下,我们在运行会发现出现一个登录框。

屏幕快照 2018-07-12 下午10.17.56.png

第一个就是你的用户id   第二个就是你的用户id对应的sig文件里的字符串。输入后直接确定,就可以进入聊天窗口了。

WX20180712-221958.png


当你这个逻辑明白以后,你就可以去看各种回调了。因为所有的东西都离不开这几个工件。

实际独立模式下用户的账户注册、登录、获取sig都跟腾讯不沾边。只有最后你生成好了用户id和sig你才可以去访问腾讯的服务器,其他的操作都是在你自己的服务器上操作的。

ThinkApiAdmin 回调机制实现

ThinkApiAdmin 是糅合了thinkadmin和apiadmin的一个合体版本。均采用上述两种框架的v2版本来进行整合的。其中有一个比较重要的父类:BasicAdmin

此时仅来说一下它其中的一个父类方法:_list

/**
 * 列表集成处理方法
 * @param Query $dbQuery 数据库查询对象
 * @param bool $isPage 是启用分页
 * @param bool $isDisplay 是否直接输出显示
 * @param bool $total 总记录数
 * @param array $result
 * @return array|mixed
 * @throws \think\db\exception\DataNotFoundException
 * @throws \think\db\exception\ModelNotFoundException
 * @throws \think\exception\DbException
 */
protected function _list($dbQuery = null, $isPage = true, $isDisplay = true, $total = false, $result = [])
{
    $db = is_null($dbQuery) ? Db::name($this->table) : (is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery);
    // 列表排序默认处理
    if ($this->request->isPost() && $this->request->post('action') === 'resort') {
        $data = $this->request->post();
        unset($data['action']);
        foreach ($data as $key => &$value) {
            if (false === $db->where('id', intval(ltrim($key, '_')))->setField('sort', $value)) {
                $this->error('列表排序失败, 请稍候再试');
            }
        }
        $this->success('列表排序成功, 正在刷新列表', '');
    }
    // 列表数据查询与显示
    if (null === $db->getOptions('order')) {
        $fields = $db->getTableFields($db->getTable());
        in_array('sort', $fields) && $db->order('sort asc');
    }
    if ($isPage) {
        $rows = intval($this->request->get('rows', cookie('rows')));
        cookie('rows', $rows >= 10 ? $rows : 20);
        $page = $db->paginate($rows, $total, ['query' => $this->request->get('', '', 'urlencode')]);
        list($pattern, $replacement) = [['|href="(.*?)"|', '|pagination|'], ['data-open="$1"', 'pagination pull-right']];
        list($result['list'], $result['page']) = [$page->all(), preg_replace($pattern, $replacement, $page->render())];
    } else {
        $result['list'] = $db->select();
    }
    if (false !== $this->_callback('_data_filter', $result['list']) && $isDisplay) {
        !empty($this->title) && $this->assign('title', $this->title);
        return $this->fetch('', $result);
    }
    return $result;
}

前面基本是都是数据的刷新,排序等一些方法。注意最后面几行

if (false !== $this->_callback('_data_filter', $result['list']) && $isDisplay) {
    !empty($this->title) && $this->assign('title', $this->title);
    return $this->fetch('', $result);
}

这几行代码就是实现回调机制的,回调如果用C/C++来理解的话应该是指针传值,通过指针访问函数。例如下面的代码:

void callBack(l callback)
{
    /** NULL */
}
void subCallback(char *s) 
{
    /** NULL */
} 
callBack(&subCallback);

上述代码是一段回调,如果可以理解,那么就可以继续下去。如果没有学过指针,那就不要在意了。只要知道回调函数是指的是将函数已参数的形式传递并调用就好了。

可以看到它是首先调用了callback这个函数传递了一个字符串和一个关联数组的内容,掉用完后判断一下是否等于true 并且 判断是否可以显示。那么关键的就是看看这个callback函数了。

    /**
     * 当前对象回调成员方法
     * @param string $method
     * @param array|bool $data
     * @return bool
     */
    protected function _callback($method, &$data)
    {
        foreach ([$method, "_" . $this->request->action() . "{$method}"] as $_method) {
            if (method_exists($this, $_method) && false === $this->$_method($data)) {
                return false;
            }
        }
        return true;
    }

这一句话:[$method, "_" . $this->request->action() . "{$method}"]  实际是一个索引数组。

array(2) {
  [0] => string(12) "_data_filter"
  [1] => string(18) "_index_data_filter"
}

当第二次循环到_index_data_filter的时候就可以进行调用了

 /**
     * 列表数据处理
     * @param $list
     */
    protected function _index_data_filter(&$list)
    {
        dump("www");die();
        $alert = [
            'type' => 'danger',
            'title' => '操作安全警告(谨慎刷新接口路由)',
            'content' => '新增或编辑接口后必须刷新接口路由才能正常访问!请根据实际情况刷新路由!'
        ];
        $tags = Db::name($this->table_api_group)->column('id,name');
        $handlers = Db::name($this->table_admin)->column('id,username');
        foreach ($list as &$vo) {
            $vo['tags_list'] = [];
            $vo['handler_name'] = Db::name($this->table_admin)->where('id', $vo['handler'])->value('username');
            foreach (explode(',', $vo['gid']) as $tag) {
                if ($tag !== '' && isset($tags[$tag])) {
                    $vo['tags_list'][$tag] = $tags[$tag];
                } elseif ($tag !== '') {
                    $vo['tags_list'][$tag] = $tag;
                }
            }
        }
        $this->assign(['handlers' => $handlers, 'alert' => $alert, 'tags' => $tags]);
    }

实际就是执行了上述代码。具体的执行指令就是: $this->$_method($data)php5+这种写法是允许的即直接指向一个变量,这个变量如果是函数名的话,则会直接运行这个函数。虽然运行这个函数的上下文句柄是Row.php文件,但是其实你是基于BasicAdmin这个类的。而这个函数定义在BasicAdmin类中,所以你可以进行引用。


流程:parent::_list(ROW模块) -> _callback(BasicAdmin模块 -> _index_data_filter (ROW模块)


php5+的版本中你还可以直接 new $var 。这样来初始化类,即使这个变量只存了一个类名,但是你依然可以引用。

ThinkPHP5 Mac 安装 mkdir 错误

安装的时候提示:mkdir(): Permission denied 。大致看了一下说是没有权限进行文件读写,如果知道ThinkPHP5的目录结构的话,就会了解有一个runtime的文件夹,这个文件夹主要是运行时产生的一些临时文件的存放位置。于是给这个文件配置777的权限。

进入:cd /Applications/XAMPP/xamppfiles/htdocs/thinkphp5

输入:chmod -R 777 runtime




ThinkPHP5 Extend 扩展写法

首先ThinkPHP是基于PHP来编写的一套框架,所以它的扩展自动加载的方法也必须依托于PHP,而PHP对自动加载进行了PSR-4规则限定。下面两个是中英文说明。

英文:https://www.php-fig.org/psr/psr-4/

中文:https://www.jianshu.com/p/e0a33214688b


了解了规则后编写就相对简单,ThinkPHP扩展的编写过程,实际就是一个类文件的编写过程。只不过不能避免的要加上命名空间等等内容,这些内容上述说明也有写。这里主要说一下父类或者超类的问题。


父类是可以继承的类,在其他语言中可以存在多继承,而PHP中只能单继承,即只继承一个父类。


类的构造方法:__construct 这个方法是你在New一个类的时候,由解释器进行类初始化的时候第一个执行的页面函数。这里可以进行一些初始化的行为。


注意的是:如果在子类中定义与父类中相同的函数或者方法。那么父类中相同的函数或方法会被覆盖,即不会执行。哪怕他是构造函数等一些魔术方法。所以如果你在子类中定义了__construct那么父类不会被执行。


例子:

/** 父类 */
class TopParent
{
    public function __construct()
    {
        echo "TopParent";
    }
}
/** 子类 */
class SubClass extends TopParent
{
     public function __construct(Request $request = null)
     {
         echo "SubClass";
     }
}
/** Tp引用 */
class Index extends SubClass
{
    public function index()
    {
        echo "";
    }
}

结果输出:

*~:SubClass


这里定义了一个SubClass类,并且是基于Controller的子类。基于ThinkPHP5的运行机制,会将要给Controller的初始化参数,即__construct参数所需要的值传递给SubClass子类。在你新建这个类的时候IDE会自动帮你加上一句话:parent::__construct($request);这句话就是手动运行父类的构造方法。因为父类的构造方法不会再自己执行。当然你也可以不执行父类的构造方法。但是如果不执行的话,如果你的派生类用到了父类的方法。而这个方法恰恰需要父类自己进行初始化。那么很有可能会报错。所以在执行一遍父类的构造方法很有必要。


对于为什么控制器可以有一个默认的$request参数。这里可以参考如下代码:

文件路径:thinkphp\library\think\Loader.php

/**
     * 实例化(分层)控制器 格式:[模块名/]控制器名
     * @access public
     * @param  string $name         资源地址
     * @param  string $layer        控制层名称
     * @param  bool   $appendSuffix 是否添加类名后缀
     * @param  string $empty        空控制器名称
     * @return object
     * @throws ClassNotFoundException
     */
    public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '')
    {
        list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix);
        if (class_exists($class)) {
            return App::invokeClass($class);
        }
        if ($empty) {
            $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix);
            if (class_exists($emptyClass)) {
                return new $emptyClass(Request::instance());
            }
        }
        throw new ClassNotFoundException('class not exists:' . $class, $class);
    }


php cookie 与 session

一:cookie(需要浏览器支持cookie功能)

存储在客户端 由服务器返回 http响应头信息发送给客户端 下次访问自动携带。

设置函数:setCookie(由于http协议头的限定,必须在其他输出信息到浏览器前调用)多次设置相同字段的值会进行覆盖操作。

客户端发给服务器的信息中如果包含cookie信息, 则会被保存在$_COOKIE全局数组中(数组为关联数组)。

//设置多维数组的方法:
setCookie("user[username]","name");
setCookie("user[password]","pw");
//获取方法
$_COOKIE["user"]["username"]
...

删除方法:

1、键值覆盖为空

2、时间设置为过期



二:session(会话) 存储在服务端 由客户端提供

客户端提交信息,服务器将信息存储进session,但是会提供一个32位的十六进制字符串(永不重复)。然后将32位的字符串传递给客户端,客户端存储进cookie下次登录的时候会提供这个32位的十六进制字串。这样服务器在通过字串进行session信息提取。注意的是这个字串在客户端cookie里储存的期限是通过配置文件确定的即php.ini配置文件里的session.cookie_lifetime字段。


使用方法:

启动session:session_start(void); //函数是为了php将一些与session相关的内建环境变量预先载入到内存。


三:cache

存在于浏览器中。