liziyu 发布的文章

HTML

<form class="layui-form layui-form-pane">
   <div class="layui-form-item" pane>
   <label class="layui-form-label">考次选择:</label>
       <div class="layui-input-block">
         <select name="publish_id" id="publish_id" class="layui-select input-pane" lay-verify="required" lay-filter="publish_id">
             <option value="">请选择</option>
             <?php foreach($publish_array as $id => $title) {?>
             <option value="<?=$id?>"><?=$title?></option>
             <?php }?>
          </select>
        </div>
     </div>
     <div class="layui-form-item" pane>
     <label class="layui-form-label">班级选择:</label>
         <div class="layui-input-block">
             <select name="class_id" id="class_id" class="layui-select input-pane" lay-verify="required">
             <option value="">请选择</option>
            </select>
         </div>
      </div>
</form>

JS

<script>
    layui.use(['table', 'jquery', 'form', 'layer', 'laydate'], function () {
        var $ = layui.jquery;
        var layer = layui.layer;
        var form = layui.form;

        //监听select选择
        form.on('select(publish_id)', function (data) {
            //发异步请求
            let url = "/xxx/xxx?publish_id=" + data.value;
            $.get(url, function (res, status) {
                //清空下拉框
                const selector = $('#class_id');
                selector.empty().prepend("<option value='0'>--请选择班级--</option>");
                for (let i = 0; i < res.data.length; i++) {
                    selector.append("<option value='" + res.data[i].id + "'>" + res.data[i].name + "</option>");
                }
                //刷新select选择框渲染
                form.render('select');
            });
        });
    });
</script>

如图:

QQ20230514-230237@2x.png

栈和堆是计算机内存中存储数据的两种方式。

栈是一种数据结构, 采用先进后出 (LIFO) 的方式存储数据。栈的操作只允许在栈顶插入和删除元素,因此栈的访问速度较快。栈本身分配在内存的高地址空间,向低地址扩展,而具体的数据值则按照定义的方式存储在栈帧中。

堆是另一种内存数据存储方式,它在程序运行时动态分配内存,用于存储程序运行期间的数据。堆的内存分配是在程序运行时进行的,并且随着程序执行的进展而不断地增加或减少。由于堆的动态特性,访问堆中的变量速度比访问栈中的变量要慢。

在C#中,所有值类型的变量都存储在栈中,而所有引用类型的变量在栈中只存放引用,而对象则在堆中分配空间。换句话说,引用类型的变量存储的是指向堆中对象的指针。

找到文件 vendor/topthink/think-cache/src/cache/Driver.php文件,如下方法,先将:

//判断$data是否为序列化字符串,如果不是直接返回不需要反序列化
if (!preg_match( '/^[asO]:[0-9]+:/s', $data)) {
    return $data;
}

添加,如下。然后正常的方问网站如登陆后台等操作,待操作完成以后,即完成了字符序列化入数据库,然后会出现\u5148\u950b\u7f51\u7edcUnicode代码,将其修改为正常的中文以后,再将下面新增的代码删除即可。
原因就是升级前的序列化入库与升级的序列化不同了。

/**
 * 反序列化数据
 * @access protected
 * @param  string $data 缓存数据
 * @return mixed
 */
protected function unserialize(string $data)
{
    //判断$data是否为序列化字符串,如果不是直接返回不需要反序列化
    if (!preg_match( '/^[asO]:[0-9]+:/s', $data)) {
        return $data;
    }
    
    $unserialize = $this->options['serialize'][1] ?? function ($data) {
        SerializableClosure::enterContext();
        $data = \unserialize($data);
        SerializableClosure::unwrapClosures($data);
        SerializableClosure::exitContext();
        return $data;
    };

    return $unserialize($data);
}

直接上js代码,如下:

function Push(options) {
    this.doNotConnect = 0;
    options = options || {};
    options.heartbeat  = options.heartbeat || 25000;
    options.pingTimeout = options.pingTimeout || 10000;
    this.config = options;
    this.uid = 0;
    this.channels = {};
    this.connection = null;
    this.pingTimeoutTimer = 0;
    Push.instances.push(this);
    this.createConnection();
  }
  
  Push.prototype.checkoutPing = function() {
    var _this = this;
    setTimeout(function () {
        if (_this.connection.state === 'connected') {
            _this.connection.send('{"event":"pusher:ping","data":{}}');
            if (_this.pingTimeoutTimer) {
                clearTimeout(_this.pingTimeoutTimer);
                _this.pingTimeoutTimer = 0;
            }
            _this.pingTimeoutTimer = setTimeout(function () {
                _this.connection.closeAndClean();
                if (!_this.connection.doNotConnect) {
                    _this.connection.waitReconnect();
                }
            }, _this.config.pingTimeout);
        }
    }, this.config.heartbeat);
  };
  
  Push.prototype.channel = function (name) {
    return this.channels.find(name);
  };
  Push.prototype.allChannels = function () {
    return this.channels.all();
  };
  Push.prototype.createConnection = function () {
    if (this.connection) {
        throw Error('Connection already exist');
    }
    var _this = this;
    var url = this.config.url;
    function updateSubscribed () {
        for (var i in _this.channels) {
            _this.channels[i].subscribed = false;
        }
    }
    this.connection = new Connection({
        url: url,
        app_key: this.config.app_key,
        onOpen: function () {
            _this.connection.state  = 'connecting';
            _this.checkoutPing();
        },
        onMessage: function(params) {
            if(_this.pingTimeoutTimer) {
                clearTimeout(_this.pingTimeoutTimer);
                _this.pingTimeoutTimer = 0;
            }
  
            params = JSON.parse(params.data);
            var event = params.event;
            var channel_name = params.channel;
  
            if (event === 'pusher:pong') {
                _this.checkoutPing();
                return;
            }
            if (event === 'pusher:error') {
                throw Error(params.data.message);
            }
            var data = JSON.parse(params.data), channel;
            if (event === 'pusher_internal:subscription_succeeded') {
                channel = _this.channels[channel_name];
                channel.subscribed = true;
                channel.processQueue();
                channel.emit('pusher:subscription_succeeded');
                return;
            }
            if (event === 'pusher:connection_established') {
                _this.connection.socket_id = data.socket_id;
                _this.connection.state = 'connected';
                _this.subscribeAll();
            }
            if (event.indexOf('pusher_internal') !== -1) {
                console.log("Event '"+event+"' not implement");
                return;
            }
            channel = _this.channels[channel_name];
            if (channel) {
                channel.emit(event, data);
            }
        },
        onClose: function () {
            updateSubscribed();
        },
        onError: function () {
            updateSubscribed();
        }
    });
  };
  Push.prototype.disconnect = function () {
    this.connection.doNotConnect = 1;
    this.connection.close();
  };
  
  Push.prototype.subscribeAll = function () {
    if (this.connection.state !== 'connected') {
        return;
    }
    for (var channel_name in this.channels) {
        //this.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
        this.channels[channel_name].processSubscribe();
    }
  };
  
  Push.prototype.unsubscribe = function (channel_name) {
    if (this.channels[channel_name]) {
        delete this.channels[channel_name];
        if (this.connection.state === 'connected') {
            this.connection.send(JSON.stringify({event:"pusher:unsubscribe", data:{channel:channel_name}}));
        }
    }
  };
  Push.prototype.unsubscribeAll = function () {
    var channels = Object.keys(this.channels);
    if (channels.length) {
        if (this.connection.state === 'connected') {
            for (var channel_name in this.channels) {
                this.unsubscribe(channel_name);
            }
        }
    }
    this.channels = {};
  };
  Push.prototype.subscribe = function (channel_name) {
    if (this.channels[channel_name]) {
        return this.channels[channel_name];
    }
    if (channel_name.indexOf('private-') === 0) {
        return createPrivateChannel(channel_name, this);
    }
    if (channel_name.indexOf('presence-') === 0) {
        return createPresenceChannel(channel_name, this);
    }
    return createChannel(channel_name, this);
  };
  Push.instances = [];
  
  function createChannel(channel_name, push)
  {
    var channel = new Channel(push.connection, channel_name);
    push.channels[channel_name] = channel;
    channel.subscribeCb = function () {
        push.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
    }
    return channel;
  }
  
  function createPrivateChannel(channel_name, push)
  {
    var channel = new Channel(push.connection, channel_name);
    push.channels[channel_name] = channel;
    channel.subscribeCb = function () {
        __ajax({
            url: push.config.auth,
            type: 'POST',
            data: {channel_name: channel_name, socket_id: push.connection.socket_id},
            success: function (data) {
                data = JSON.parse(data);
                data.channel = channel_name;
                push.connection.send(JSON.stringify({event:"pusher:subscribe", data:data}));
            },
            error: function (e) {
                throw Error(e);
            }
        });
    };
    channel.processSubscribe();
    return channel;
  }
  
  function createPresenceChannel(channel_name, push)
  {
    return createPrivateChannel(channel_name, push);
  }
  
  /*window.addEventListener('online',  function(){
    var con;
    for (var i in Push.instances) {
        con = Push.instances[i].connection;
        con.reconnectInterval = 1;
        if (con.state === 'connecting') {
            con.connect();
        }
    }
  });*/
  
  
  function Connection(options) {
    this.dispatcher = new Dispatcher();
    __extends(this, this.dispatcher);
    var properies = ['on', 'off', 'emit'];
    for (var i in properies) {
        this[properies[i]] = this.dispatcher[properies[i]];
    }
    this.options = options;
    this.state = 'initialized'; //initialized connecting connected disconnected
    this.doNotConnect = 0;
    this.reconnectInterval = 1;
    this.connection = null;
    this.reconnectTimer = 0;
    this.connect();
  }
  
  Connection.prototype.updateNetworkState = function(state){
    var old_state = this.state;
    this.state = state;
    if (old_state !== state) {
        this.emit('state_change', { previous: old_state, current: state });
    }
  };
  
  Connection.prototype.connect = function () {
    this.doNotConnect = 0;
    if (this.networkState == 'connecting' || this.networkState == 'established') {
        console.log('networkState is ' + this.networkState + ' and do not need connect');
        return;
    }
    if (this.reconnectTimer) {
        clearTimeout(this.reconnectTimer);
        this.reconnectTimer = 0;
    }
  
    this.closeAndClean();
  
    var options = this.options;
    var _this = this;
    _this.updateNetworkState('connecting');
    var cb = function(){
         wx.onSocketOpen(function (res) {
            _this.reconnectInterval = 1;
            if (_this.doNotConnect) {
                _this.updateNetworkState('closing');
                wx.closeSocket();
                return;
            }
            _this.updateNetworkState('established');
            if (options.onOpen) {
                options.onOpen(res);
            }
        });
  
        if (options.onMessage) {
            wx.onSocketMessage(options.onMessage);
        }
  
        wx.onSocketClose(function (res) {
            _this.updateNetworkState('disconnected');
            if (!_this.doNotConnect) {
                _this.waitReconnect();
            }
            if (options.onClose) {
                options.onClose(res);
            }
        });
  
        wx.onSocketError(function (res) {
            _this.close();
            if (!_this.doNotConnect) {
                _this.waitReconnect();
            }
            if (options.onError) {
                options.onError(res);
            }
        });
    };
    wx.connectSocket({
        url: options.url,
        fail: function (res) {
            console.log('wx.connectSocket fail');
            console.log(res);
            _this.updateNetworkState('disconnected');
            _this.waitReconnect();
        },
        success: function() {
  
        }
    });
    cb();
  }
  
  Connection.prototype.connect = function () {
    this.doNotConnect = 0;
    if (this.state === 'connected') {
        console.log('networkState is "' + this.state + '" and do not need connect');
        return;
    }
    if (this.reconnectTimer) {
        clearTimeout(this.reconnectTimer);
        this.reconnectTimer = 0;
    }
  
    this.closeAndClean();
  
    var options = this.options;
  
    this.updateNetworkState('connecting');
  
    var _this = this;
    var cb = function(){
         wx.onSocketOpen(function (res) {
            _this.reconnectInterval = 1;
            if (_this.doNotConnect) {
                _this.updateNetworkState('disconnected');
                wx.closeSocket();
                return;
            }
            if (options.onOpen) {
                options.onOpen(res);
            }
        });
  
        if (options.onMessage) {
            wx.onSocketMessage(options.onMessage);
        }
  
        wx.onSocketClose(function (res) {
            _this.updateNetworkState('disconnected');
            if (!_this.doNotConnect) {
                _this.waitReconnect();
            }
            if (options.onClose) {
                options.onClose(res);
            }
        });
  
        wx.onSocketError(function (res) {
            _this.close();
            if (!_this.doNotConnect) {
                _this.waitReconnect();
            }
            if (options.onError) {
                options.onError(res);
            }
        });
    };
    wx.connectSocket({
        url: options.url+'/app/'+options.app_key,
        fail: function (res) {
            console.log('wx.connectSocket fail');
            console.log(res);
            _this.updateNetworkState('disconnected');
            _this.waitReconnect();
        },
        success: function() {
  
        }
    });
    cb();
  }
  
  Connection.prototype.closeAndClean = function () {
    if (this.state === 'connected') {
        wx.closeSocket();
    }
    this.updateNetworkState('disconnected');
  };
  
  Connection.prototype.waitReconnect = function () {
    if (this.state === 'connected' || this.state === 'connecting') {
        return;
    }
    if (!this.doNotConnect) {
        this.updateNetworkState('connecting');
        var _this = this;
        if (this.reconnectTimer) {
            clearTimeout(this.reconnectTimer);
        }
        this.reconnectTimer = setTimeout(function(){
            _this.connect();
        }, this.reconnectInterval);
        if (this.reconnectInterval < 1000) {
            this.reconnectInterval = 1000;
        } else {
            // 每次重连间隔增大一倍
            this.reconnectInterval = this.reconnectInterval * 2;
        }

        let online = false;
        wx.getNetworkType({
            success (res) {
              console.log(res)
              if(res.networkType !== 'none'){
                online = true;
              }
            }
        })
        // 有网络的状态下,重连间隔最大2秒
        if (this.reconnectInterval > 2000 && online) {
            _this.reconnectInterval = 2000;
        }
    }
  }
  
  Connection.prototype.send = function(data) {
    if (this.state !== 'connected') {
        console.trace('networkState is "' + this.state + '", can not send ' + data);
        return;
    }
    wx.sendSocketMessage({
        data: data
    });
  }
  
  Connection.prototype.close = function(){
    this.updateNetworkState('disconnected');
    wx.closeSocket();
  }
  
  var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) {d[p] = b[p];}
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  };
  
  function Channel(connection, channel_name) {
    this.subscribed = false;
    this.dispatcher = new Dispatcher();
    this.connection = connection;
    this.channelName = channel_name;
    this.subscribeCb = null;
    this.queue = [];
    __extends(this, this.dispatcher);
    var properies = ['on', 'off', 'emit'];
    for (var i in properies) {
        this[properies[i]] = this.dispatcher[properies[i]];
    }
  }
  
  Channel.prototype.processSubscribe = function () {
    if (this.connection.state !== 'connected') {
        return;
    }
    this.subscribeCb();
  };
  
  Channel.prototype.processQueue = function () {
    if (this.connection.state !== 'connected' || !this.subscribed) {
        return;
    }
    for (var i in this.queue) {
        this.queue[i]();
    }
    this.queue = [];
  };
  
  Channel.prototype.trigger = function (event, data) {
    if (event.indexOf('client-') !== 0) {
        throw new Error("Event '" + event + "' should start with 'client-'");
    }
    var _this = this;
    this.queue.push(function () {
        _this.connection.send(JSON.stringify({ event: event, data: data, channel: _this.channelName }));
    });
    this.processQueue();
  };
  
  ////////////////
  var Collections = (function () {
    var exports = {};
    function extend(target) {
        var sources = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            sources[_i - 1] = arguments[_i];
        }
        for (var i = 0; i < sources.length; i++) {
            var extensions = sources[i];
            for (var property in extensions) {
                if (extensions[property] && extensions[property].constructor &&
                    extensions[property].constructor === Object) {
                    target[property] = extend(target[property] || {}, extensions[property]);
                }
                else {
                    target[property] = extensions[property];
                }
            }
        }
        return target;
    }
  
    exports.extend = extend;
    function stringify() {
        var m = ["Push"];
        for (var i = 0; i < arguments.length; i++) {
            if (typeof arguments[i] === "string") {
                m.push(arguments[i]);
            }
            else {
                m.push(safeJSONStringify(arguments[i]));
            }
        }
        return m.join(" : ");
    }
  
    exports.stringify = stringify;
    function arrayIndexOf(array, item) {
        var nativeIndexOf = Array.prototype.indexOf;
        if (array === null) {
            return -1;
        }
        if (nativeIndexOf && array.indexOf === nativeIndexOf) {
            return array.indexOf(item);
        }
        for (var i = 0, l = array.length; i < l; i++) {
            if (array[i] === item) {
                return i;
            }
        }
        return -1;
    }
  
    exports.arrayIndexOf = arrayIndexOf;
    function objectApply(object, f) {
        for (var key in object) {
            if (Object.prototype.hasOwnProperty.call(object, key)) {
                f(object[key], key, object);
            }
        }
    }
  
    exports.objectApply = objectApply;
    function keys(object) {
        var keys = [];
        objectApply(object, function (_, key) {
            keys.push(key);
        });
        return keys;
    }
  
    exports.keys = keys;
    function values(object) {
        var values = [];
        objectApply(object, function (value) {
            values.push(value);
        });
        return values;
    }
  
    exports.values = values;
    function apply(array, f, context) {
        for (var i = 0; i < array.length; i++) {
            f.call(context || (window), array[i], i, array);
        }
    }
  
    exports.apply = apply;
    function map(array, f) {
        var result = [];
        for (var i = 0; i < array.length; i++) {
            result.push(f(array[i], i, array, result));
        }
        return result;
    }
  
    exports.map = map;
    function mapObject(object, f) {
        var result = {};
        objectApply(object, function (value, key) {
            result[key] = f(value);
        });
        return result;
    }
  
    exports.mapObject = mapObject;
    function filter(array, test) {
        test = test || function (value) {
            return !!value;
        };
        var result = [];
        for (var i = 0; i < array.length; i++) {
            if (test(array[i], i, array, result)) {
                result.push(array[i]);
            }
        }
        return result;
    }
  
    exports.filter = filter;
    function filterObject(object, test) {
        var result = {};
        objectApply(object, function (value, key) {
            if ((test && test(value, key, object, result)) || Boolean(value)) {
                result[key] = value;
            }
        });
        return result;
    }
  
    exports.filterObject = filterObject;
    function flatten(object) {
        var result = [];
        objectApply(object, function (value, key) {
            result.push([key, value]);
        });
        return result;
    }
  
    exports.flatten = flatten;
    function any(array, test) {
        for (var i = 0; i < array.length; i++) {
            if (test(array[i], i, array)) {
                return true;
            }
        }
        return false;
    }
  
    exports.any = any;
    function all(array, test) {
        for (var i = 0; i < array.length; i++) {
            if (!test(array[i], i, array)) {
                return false;
            }
        }
        return true;
    }
  
    exports.all = all;
    function encodeParamsObject(data) {
        return mapObject(data, function (value) {
            if (typeof value === "object") {
                value = safeJSONStringify(value);
            }
            return encodeURIComponent(base64_1["default"](value.toString()));
        });
    }
  
    exports.encodeParamsObject = encodeParamsObject;
    function buildQueryString(data) {
        var params = filterObject(data, function (value) {
            return value !== undefined;
        });
        return map(flatten(encodeParamsObject(params)), util_1["default"].method("join", "=")).join("&");
    }
  
    exports.buildQueryString = buildQueryString;
    function decycleObject(object) {
        var objects = [], paths = [];
        return (function derez(value, path) {
            var i, name, nu;
            switch (typeof value) {
                case 'object':
                    if (!value) {
                        return null;
                    }
                    for (i = 0; i < objects.length; i += 1) {
                        if (objects[i] === value) {
                            return {$ref: paths[i]};
                        }
                    }
                    objects.push(value);
                    paths.push(path);
                    if (Object.prototype.toString.apply(value) === '[object Array]') {
                        nu = [];
                        for (i = 0; i < value.length; i += 1) {
                            nu[i] = derez(value[i], path + '[' + i + ']');
                        }
                    }
                    else {
                        nu = {};
                        for (name in value) {
                            if (Object.prototype.hasOwnProperty.call(value, name)) {
                                nu[name] = derez(value[name], path + '[' + JSON.stringify(name) + ']');
                            }
                        }
                    }
                    return nu;
                case 'number':
                case 'string':
                case 'boolean':
                    return value;
            }
        }(object, '$'));
    }
  
    exports.decycleObject = decycleObject;
    function safeJSONStringify(source) {
        try {
            return JSON.stringify(source);
        }
        catch (e) {
            return JSON.stringify(decycleObject(source));
        }
    }
  
    exports.safeJSONStringify = safeJSONStringify;
    return exports;
  })();
  
  var Dispatcher = (function () {
    function Dispatcher(failThrough) {
        this.callbacks = new CallbackRegistry();
        this.global_callbacks = [];
        this.failThrough = failThrough;
    }
    Dispatcher.prototype.on = function (eventName, callback, context) {
        this.callbacks.add(eventName, callback, context);
        return this;
    };
    Dispatcher.prototype.on_global = function (callback) {
        this.global_callbacks.push(callback);
        return this;
    };
    Dispatcher.prototype.off = function (eventName, callback, context) {
        this.callbacks.remove(eventName, callback, context);
        return this;
    };
    Dispatcher.prototype.emit = function (eventName, data) {
        var i;
        for (i = 0; i < this.global_callbacks.length; i++) {
            this.global_callbacks[i](eventName, data);
        }
        var callbacks = this.callbacks.get(eventName);
        if (callbacks && callbacks.length > 0) {
            for (i = 0; i < callbacks.length; i++) {
                callbacks[i].fn.call(callbacks[i].context || (window), data);
            }
        }
        else if (this.failThrough) {
            this.failThrough(eventName, data);
        }
        return this;
    };
    return Dispatcher;
  }());
  
  var CallbackRegistry = (function () {
    function CallbackRegistry() {
        this._callbacks = {};
    }
    CallbackRegistry.prototype.get = function (name) {
        return this._callbacks[prefix(name)];
    };
    CallbackRegistry.prototype.add = function (name, callback, context) {
        var prefixedEventName = prefix(name);
        this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || [];
        this._callbacks[prefixedEventName].push({
            fn: callback,
            context: context
        });
    };
    CallbackRegistry.prototype.remove = function (name, callback, context) {
        if (!name && !callback && !context) {
            this._callbacks = {};
            return;
        }
        var names = name ? [prefix(name)] : Collections.keys(this._callbacks);
        if (callback || context) {
            this.removeCallback(names, callback, context);
        }
        else {
            this.removeAllCallbacks(names);
        }
    };
    CallbackRegistry.prototype.removeCallback = function (names, callback, context) {
        Collections.apply(names, function (name) {
            this._callbacks[name] = Collections.filter(this._callbacks[name] || [], function (oning) {
                return (callback && callback !== oning.fn) ||
                    (context && context !== oning.context);
            });
            if (this._callbacks[name].length === 0) {
                delete this._callbacks[name];
            }
        }, this);
    };
    CallbackRegistry.prototype.removeAllCallbacks = function (names) {
        Collections.apply(names, function (name) {
            delete this._callbacks[name];
        }, this);
    };
    return CallbackRegistry;
  }());
  function prefix(name) {
    return "_" + name;
  }
  
  function __ajax(options){
    options=options||{};
    options.type=(options.type||'GET').toUpperCase();
    options.dataType=options.dataType||'json';
    params=formatParams(options.data);
  
    var xhr;
    if(window.XMLHttpRequest){
        xhr=new XMLHttpRequest();
    }else{
        xhr=ActiveXObject('Microsoft.XMLHTTP');
    }
  
    xhr.onreadystatechange=function(){
        if(xhr.readyState === 4){
            var status=xhr.status;
            if(status>=200 && status<300){
                options.success&&options.success(xhr.responseText,xhr.responseXML);
            }else{
                options.error&&options.error(status);
            }
        }
    }
  
    if(options.type==='GET'){
        xhr.open('GET',options.url+'?'+params,true);
        xhr.send(null);
    }else if(options.type==='POST'){
        xhr.open('POST',options.url,true);
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
        xhr.send(params);
    }
  }
  
  function formatParams(data){
    var arr=[];
    for(var name in data){
        arr.push(encodeURIComponent(name)+'='+encodeURIComponent(data[name]));
    }
    return arr.join('&');
  }
  
  export default Push

感谢:
https://www.workerman.net/plugin/2
https://github.com/ljnchn/push/blob/main/src/push-wx.js

QueryList 是什么

QueryList是一套用于内容DOM解析的PHP工具,它使用更加现代化的开发思想,语法简洁、优雅,可扩展性强。相比传统的使用晦涩的正则表达式来做DOM解析,QueryList使用了更加强大而优雅的CSS选择器来做DOM解析,大大降低了PHP做DOM解析的门槛,同时也让DOM解析代码易读易维护,让你从此告别晦涩难懂且不易维护的正则表达式😀。

http://querylist.cc/docs/guide/v4

15个协程

package main

import (
    "fmt"
)

func inputNum(inChan chan int, n int) {
    for i := 1; i <= n; i++ {
        inChan <- i
    }
    fmt.Println("inChan 写入完成")
    close(inChan)
}

func countNum(inChan chan int, primeChan chan int, exitChan chan bool) {
    for {
        //time.Sleep(time.Millisecond * 10)
        num, ok := <-inChan
        if !ok {
            break
        }
        flag := true
        for i := 2; i < num; i++ {
            if num%i == 0 {
                flag = false
                break
            }
        }
        if flag {
            primeChan <- num
        }
    }
    fmt.Println("有一个countNum被关闭了")
    exitChan <- true
}

func main() {

    inChan := make(chan int, 1000)
    primeChan := make(chan int, 1000)
    exitChan := make(chan bool, 15)

    go inputNum(inChan, 80000)

    for i := 0; i < 15; i++ {
        go countNum(inChan, primeChan, exitChan)
    }

    go func() {
        for i := 0; i < 4; i++ {
            <-exitChan
        }
        close(primeChan)
    }()

    for {
        v, ok := <-primeChan
        if !ok {
            break
        }
        fmt.Println(v)
    }
}


输入流(读):从文件读到内存的流向。
输出流(写):从内存写入文件的方向。

输入流与输出流.png

HTTP 报文头也叫报文首部。

HTTP 头部字段是构成 HTTP 报文的要素之一。在客户端与服务器之间以 HTTP 协议进行通信的过程中,无论是请求还是响应都会使用头部字段,它能起到传递额外重要信息的作用。

HTTP 首部字段是由首部字段名和字段值构成的,中间用冒号: 分隔,下面是一个 HTTP 报文头的例子:

GET / HTTP/1.1
Host: hackr.jp
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Ge 
Accept: text/html,application/xhtml+xml,application/xml;q=0 
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
If-Modified-Since: Fri, 31 Aug 2007 02:02:20 GMT 
If-None-Match: "45bae1-16a-46d776ac"
Cache-Control: max-age=0

看不懂这些字段代表什么意思对吧?读完本文就全弄懂了。

通用首部字段

通用首部字段是指:请求报文和响应报文双方都会使用的字段。

Cache-Control

Cache-Control 通过设置不同的指令可以控制缓存的行为,指令格式为:

Cache-Control: no-cache 
Cache-Control: no-store
Cache-Control: max-age=<seconds>

指令参数大概可以分为三类:

  1. 控制可缓存性。
  2. 控制到期时间。
  3. 控制重新验证 & 重新加载。

控制缓存

  1. no-cache 会强制验证数据的有效期,以防止获取到过期资源。
  2. no-store 会禁止缓存服务器缓存数据。(一般意味着数据中含有机密信息
  3. only-if-cached:仅从缓存服务器获取数据,保证请求绝不会达到源服务器,如果缓存服务器没有该资源,则返回状态码 504(GateWay Timeout)。
  4. public:表明响应可以被任何对象(包括:发送请求的客户端,代理服务器,等等)缓存
  5. private:表明响应只能被单个用户缓存,不能作为共享缓存(即代理服务器不能缓存它)。

控制到期时间

  1. max-age:设置缓存存储的最大周期,超过这个时间缓存被认为过期 (单位秒)。客户端只接收缓存时间<max-age 的数据。
  2. s-maxage:覆盖 max-age 或 Expires 字段,但它只适用于共享缓存(例如代理服务器),私有服务器会忽略这个字段。
  3. max-stale:表示客户端愿意接收一个过期的资源,只要资源的过期时间<max-stale的值
  4. min-fresh:表示客户想要的资源至少在指定的秒数内仍然是新鲜的。

HTTP/1.1 版本的缓存服务器遇到同时存在 Expires 首部字段的情况时,会优先处理 max-age 指令,而忽略掉 Expires 首部字段。而 HTTP/1.0 版本的缓存服务器的情况却相反,max-age 指令会被忽略

重新验证和重新加载#

  1. proxy-revalidate 指令要求所有的缓存服务器在接收到客户端带有该指令的请求返回响应之前,必须再次验证缓存的有效性,与 must-revalidate 的功能相同。
  2. no-transform:禁止对资源进行转换。Content-Encoding ,Content-Range ,Content-Type 标头不得被代理修改。例如,一个不透明的代理可能会在图像格式之间进行转换,以节省缓存空间或减少慢速链接上的流量,而这个行为会被 no-transform 禁止。

示例#

  1. 禁止缓存:Cache-Control: no-store

  2. 缓存静态资源,例如图像,CSS 文件和 JavaScript 文件:

    Cache-Control:public, max-age=31536000 
  3. 需要重新验证

指定 no-cache 或 max-age=0, must-revalidate 表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。这意味着每次都会发起 HTTP 请求,但当缓存内容仍有效时可以跳过 HTTP 响应体的下载。

Cache-Control: no-cache 0
Cache-Control: max-age=0, must-revalidate 

Connection#

Connection 头(header)决定当前的事务完成后,是否会关闭网络连接。如果该值是 keep-alive,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。

从 HTTP/1.1 版本开始,所有连接默认为持久连接。

Date#

表明 HTTP 报文的创建日期和时间。日期时间格式有好几种,这里列出一种常见格式:Mon, 06 Feb 2023 01:19:14 GMT

Pragma#

Pragma 是一个在 HTTP/1.0 中规定的通用首部,在 HTTP/1.1 协议被 Cache-Control 代替。现在它用来向后兼容只支持 HTTP/1.0 协议的缓存服务器。

Trailer#

Trailer 在分块传输编码时会被用到,它用来在消息块后面添加额外的元信息。

举个例子:下面的报文中 Trailer 指定了 Expires 字段,在消息块后面出现了 Expires 字段。

HTTP/1.1 200 OK
Date: Tue, 03 Jul 2012 04:40:56 GMT 
Content-Type: text/html
...
Transfer-Encoding: chunked 
Trailer: Expires
...(报文主体)...

Expires: Tue, 28 Sep 2004 23:59:59 GMT

Transfer-Encoding#

规定传输报文时采用的编码方式,可选的编码方式如下:

  1. chunked

数据以一系列分块的形式进行发送。在每一个分块的开头需要添加当前分块的长度(十六进制),后面紧跟着 ‘\r\n’ ,之后是分块本身,后面也是’\r\n’ 。终止块是一个常规的分块,不同之处在于其长度为 0。

HTTP/1.1 200 OK
Date: Tue, 03 Jul 2012 04:40:56 GMT 
Cache-Control: public, max-age=604800 
Content-Type: text/javascript; charset=utf-8 
Expires: Tue, 10 Jul 2012 04:40:56 GMT 
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block 
Content-Encoding: gzip
Transfer-Encoding: chunked
Connection: keep-alive

cf0\r\n                                 ←16进制(10进制为3312) 
3312字节数据\r\n
392\r\n                                 ←16进制(10进制为914) 
914字节数据\r\n
0\r\n
\r\n
  1. compress

采用 Lempel-Ziv-Welch (LZW) 压缩算法。这种内容编码方式已经被大部分浏览器弃用。

  1. deflate

采用 zlib 结构,和 deflate 压缩算法。

  1. gzip

表示采用 Lempel-Ziv coding (LZ77) 压缩算法,以及 32 位 CRC 校验的编码方式。这个编码方式最初由 UNIX 平台上的 gzip 程序采用。处于兼容性的考虑,HTTP/1.1 标准提议支持这种编码方式的服务器应该识别作为别名的 x-gzip 指令。

  1. identity

用于表明自身未经过压缩和修改。

Transfer-Encoding 是一个逐跳传输消息首部,即仅应用于两个节点之间的消息传递。如果想要将压缩后的数据应用于整个连接,那么应该使用端到端传输消息首部 Content-Encoding 。

Upgrade#

用于检测是否有可用的、更高版本的 HTTP 协议。

如果使用了 Upgrade 字段,Connection 字段的值会被指定为 Upgrade,比如:
Upgrade:TLS/1.0
Connection:Upgrade

Via

Via 用于追踪客户端和服务器之间报文的传输路径,也可用于防止循环请求。格式:

Via: [ <protocol-name> "/" ] <protocol-version> <host> [ ":" <port> ]
or
Via: [ <protocol-name> "/" ] <protocol-version> <pseudonym>
  1. :所使用的协议名称,如 “HTTP”。
  2. :所使用的协议版本号,例如 “1.1”。
  3. and :公共代理的 URL 及端口号。
  4. :内部代理的名称或别名。

Warning

警告报文出现了问题,格式:Warning: <warn-code> <warn-agent> <warn-text> [<warn-date>]

请求报文字段#

Accept#

用来告知服务器可以处理的内容类型,例如:

Accept: <MIME_type>/<MIME_subtype>
Accept: <MIME_type>/*
Accept: */*
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
  1. /,比如 text/html。
  2. * 代表任意类型,比如 image/* 可以用来指代 image/png、image/svg、image/gif 以及任何其他的图片类型。
  3. q 代表权重。

Accept-Charset#

用于声明客户端可以处理的字符集类型,例如:

Accept-Charset: <charset>
Accept-Charset: utf-8, iso-8859-1;q=0.5, *;q=0.1

Accept-Encoding#

用于声明客户端能够处理的编码方式,例如:

Accept-Encoding: deflate, gzip;q=1.0, *;q=0.5
  1. gzip 表示采用 Lempel-Ziv coding (LZ77) 压缩算法,以及 32 位 CRC 校验的编码方式。
  2. compress 表示采用 Lempel-Ziv-Welch (LZW) 压缩算法。
  3. deflate 表示采用 zlib 结构和 deflate 压缩算法。
  4. br 表示采用 Brotli 算法的编码方式。
  5. identity 表示自身未经过压缩和修改。

Accept-Language#

用于声明客户端可以理解的语言,比如:

Accept-Language: <language>
Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5

Authorization#

用于提供给服务器验证身份的凭据,允许其访问受保护的资源,比如:

Authorization: <auth-scheme> <authorization-parameters>
Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l

Basic 身份验证:首先将用户名和密码使用一个冒号拼接(username:password),然后将生成的字符串进行 base64 编码。
除了 Basic 编码方式,还有 Digest、Negotiate 等方式。

Expect

客户端发送带有 Expect 消息头的请求,等服务器回复后再发送消息体,例如:

Expect: 100-continue

服务器检查请求消息头,可能会返回一个状态码为 100 (Continue) 的回复来告知客户端继续发送消息体,也可能会返回一个状态码为 417 (Expectation Failed) 的回复来告知对方要求不能得到满足。

From#

附带一个电子邮箱地址,例如:

From: webmaster@example.org

Host#

指明本次请求的目标服务器主机名和端口号。

  1. 如果没有包含端口号,会自动使用被请求服务的默认端口
  2. 所有 HTTP/1.1 请求报文中必须包含一个 Host 字段。对于缺少 Host 头或者含有多个 Host 头的 HTTP/1.1 请求,可能会收到 400(Bad Request)状态码。

If-Match#

形如 If-xxx 这种样式的请求首部字段,都可称为条件请求。服务器接收到附带条件的请求后,只有判断指定条件为真时,才会执行请求。 例如:

If-Match: "bfc13a64729c4290ef5b2c2730249c88ca92d82d"

服务器会比对 If-Match 的字段值和资源的 ETag 值,仅当两者一致时,才会执行请求。反之,则返回状态码 412 Precondition Failed 的响应。

If-Modified-Since#

If-Modified-Since 用于确认客户端拥有的本地资源的有效性,例如:

If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT

服务器在收到带有 If-Modified-Since 字段的请求后,会将该字段值和资源更新时间做比较,若资源没有更新,则返回 304 状态码(Not Modified)。

If-None-Match#

与 If-Match 的作用相反。

If-Range

If-Range HTTP 请求头字段用来使得 Range 头字段在一定条件下起作用:当字段值中的条件得到满足时,Range 头字段才会起作用,例如:

If-Range: Wed, 21 Oct 2015 07:28:00 GMT 

字段值中既可以用 Last-Modified 时间值用作验证,也可以用 ETag 标记作为验证,但不能将两者同时使用。

If-Unmodified-Since#

与 If-Modified-Since 作用相反。

Max-Forwards#

用于限制 TRACE 方法可经过的服务器(通常指代理服务器)数目。

发送包含首部字段 Max- Forwards 的请求时,该字段以十进制整数形式指定可经过的服务器最大数目。每经过一个服务器,Max-Forwards 的值减 1。当服务器接收到 Max-Forwards 值为 0 的请求时,则不再进行转发,而是直接返回响应。

Proxy-Authorization#

用于客户端和代理服务器之间的认证,例如:

Proxy-Authorization: Basic dGlwOjkpNLAGfFY5

Range#

Range 字段用于分批请求资源,下面的示例表示请求获取从第 5001 字节至第 10000 字节的资源。

Range: bytes=5001-10000

接收到附带 Range 首部字段请求的服务器,会在处理请求之后返回状态码为 206 Partial Content 的响应。无法处理该范围请求时,则会返回状态码 200 OK 的响应及全部资源。

Referer#

Referer 请求头包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进入的。服务端一般使用 Referer 请求头识别访问来源,可能会以此进行统计分析、日志记录以及缓存优化等。

Referer 请求头可能暴露用户的浏览历史,涉及到用户的隐私问题,所以一般用于 HTTPS 协议。

TE#

表明客户端能够处理的传输编码方式及相对优先级,例如:

TE: gzip, deflate;q=0.5

User-Agent#

User-Agent 首部包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。比如 Google 的 UA 字符串:

Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36

响应报文字段#

Accept-Ranges#

表明服务器是否支持范围请求,可以处理范围请求时指定字段值为 bytes,反之则为 none,指令格式:

Accept-Ranges: bytes

Age#

表明资源在代理服务器缓存了多久,以秒为单位,指令格式:

Age: 24

ETag#

ETag 是资源的唯一标识符,使用 ETag 能快速确定资源是否发生过变化。(可以理解为资源的 “指纹”)

ETag 有强弱之分,资源即使发生了一丁点的变化也会改变强ETag值,对应的,只有资源发生较大变化才会改变弱ETag值,此时会在字段开始处附加 W/

ETag: W/"0815"

Location#

用于将页面重定向至新的地址,一般与 3xx 状态码配合使用,例如:

Location: <url>

Proxy-Authenticate#

Proxy-Authenticate 会把由代理服务器所要求的认证信息发送给客户端,例如:

Proxy-Authenticate: Basic realm="Access to the internal site"

Retry-After#

用于告知客户端应该在多久之后再次发送请求。主要配合状态码 503 Service Unavailable,或 3xx Redirect 响应一起使用,例如:

Retry-After: 120(以秒为单位)

Server#

表明服务器的软件和版本信息,指令格式:

Server: Apache/2.2.6 (Unix) PHP/5.2.5

Vary

决定缓存能否使用,关于缓存,建议用 cache-control 而非 vary。

WWW-Authenticate#

WWW-Authenticate 定义了应该使用何种验证方式去获取对资源的连接,例如服务器利用该字段规定了 Basic 认证 :

WWW-Authenticate: Basic realm="Access to the staging site"

WWW-Authenticate header 通常会和一个 401 Unauthorized 的响应一同被发送。

报文实体字段#

Allow#

用于枚举资源所支持的 HTTP 方法的集合,指令格式:

Allow: GET, HEAD

如果 Allow 字段的值为空,说明资源不接受使用任何 HTTP 方法的请求,这可能是因为服务器需要临时禁止对资源的任何访问。

Cotent-Encoding#

消息文本的编码类型,指令格式:

Content-Encoding: deflate, gzip

指令参数:

  1. gzip 表示采用 Lempel-Ziv coding (LZ77) 压缩算法,以及 32 位 CRC 校验的编码方式。
  2. compress 表示采用 Lempel-Ziv-Welch (LZW) 压缩算法。
  3. deflate 表示采用 zlib 结构和 deflate 压缩算法。
  4. br 表示采用 Brotli 算法的编码方式。

Cotent-Language#

Content-Language 用来说明服务器希望访问者采用的语言或语言组合,例如报文的 Content-Language 字段值为 de,那么说明这份文件是为说德语的人提供的,但是这并不意味着文本是德文,它也可能是英文等其他语言:

Content-Language: de

Content-Length#

用于指明发送给客户端的消息主体的大小,用十进制数字表示,例如:

Content-Length: 15900

Content-Location#

对应资源的 URL。

Content-Range#

用来表示一个数据片段在整个文件中的位置,例如:

Content-Range: bytes 200-1000/67589

Content-Type#

用于告知客户端响应报文内容的内容类型,例如:

Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something

指令参数:

  1. media-type

资源的 MIME 类型,比如 text/html、multipart/form-data。

  1. charset

字符编码标准。

Expires

用于告知客户端缓存的失效日期,指令格式:

Expires: Wed, 21 Oct 2015 07:28:00 GMT

如果在 Cache-Control 响应头设置了 max-age 或者 s-max-age 指令,那么 Expires 头会被忽略。

Last-Modified#

资源最后一次修改的时间,指令格式:

Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

Cookie 相关字段#

Set-Cookie#

服务器利用 Set-Cookie 字段来告知客户端 cookie,例如设置一个永久 cookie:

Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
  1. expires: 表明 Cookie 的有效期。 当省略 expires 属性时,其有效期仅限于当前浏览器会话 (Session) 时间段内。浏览器一旦关闭则 cookie 失效。
  2. path: 指定一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 标头。
  3. domain: 指定 cookie 可以送达的主机名。
  4. secure:表明 cookie 只能在 HTTPS 使用,HTTP 不可以。
  5. HttpOnly:禁止 JS 脚本获得 Cookie。其主要目的为防止跨站脚本攻击 (Cross-site scripting,XSS) 对 Cookie 的信息窃取。

示例#

  1. 会话期 cookie

    Set-Cookie: sessionId=38afes7a8 

    会话期 cookie 将会在客户端关闭时被移除。会话期 cookie 不设置 Expires 或 Max-Age 属性。

  2. 持久化 cookie

    Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT 
    Set-Cookie: id=a3fWa; Max-Age=2592000

    持久化 cookie 不会在客户端关闭时失效,而是在特定的日期(Expires)或者经过一段特定的时间之后(Max-Age)才会失效。

Cookie#

客户端在向服务器发 HTTP 请求时附带 Cookie 以获得服务器的认证。(Cookie 值来源于上文的 Set-Cookie 字段值)

报文头部字段的转发策略#

一个 HTTP 请求要跨过多个代理服务器,经过多次代理服务器的转发才能到达目标服务器。

代理服务器做转发时,对原 HTTP 报文头部字段有两种处理方式:

  1. 逐跳策略:单次转发有效。
  2. 端到端策略:一直有效,确保字段被发送到目标服务器。

哪些字段是逐条策略?哪些又是端到端策略?

除了下面这些字段为逐跳策略外,其他字段都属于端到端策略:

  1. Connection
  2. Keep-Alive
  3. Proxy-Authenticate
  4. Proxy-Authorization
  5. Trailer
  6. TE
  7. Transfer-Encoding
  8. Upgrade

区分容易混淆的字段#

TE、Accept-Encoding、Transfer-Encoding、Content-Encoding#

  1. Transfer-Encoding:用于指定传输报文主体时使用的编码方式,属于逐跳首部,即只在两个节点间有效。
  2. TE:用于告知服务器客户端能够处理的编码方式和相对优先级,属于逐跳首部,即只在两个节点间有效。
  3. Content-Encoding:用于指定报文主体已经采用的编码方式,属于端到端首部,即在整个传输过程中有效。
  4. Accept-Encoding:用于告知服务器客户端能够处理的编码方式和相对优先级,属于端到端首部,即在整个传输过程中有效。

Loaction 与 Content-Location#

Location 指定的是一个重定向请求的目的地址(或者新创建的文件的 URL)。

Content-Location 指向的是可供访问的资源的直接地址。

Proxy-Authenticate、WWW-Authenticate#

Proxy-Authenticate 规定了客户端与代理服务器的认证方式,而 WWW-Authenticate 规定了客户端与服务器的认证方式。

有关联的字段#

ETag、If-Match、If-None-Match#

Etag 由服务器端生成,客户端通过 If-Match 或者说 If-None-Match 这个条件判断请求来验证资源是否修改。常见的是使用 If-None-Match,比如请求一个文件的流程可能如下:

第一次请求:

  1. 客户端发起 HTTP GET 请求一个文件;
  2. 服务器处理请求,返回文件内容和一堆 Header,当然包括 Etag (例如”2e681a-6-5d044840”)。

第二次请求:

  1. 客户端发起 HTTP GET 请求一个文件,注意这个时候客户端同时发送一个 If-None-Match 头,这个头的内容就是第一次请求时服务器返回的 Etag:2e681a-6-5d044840。
  2. 服务器判断发送过来的 Etag 和计算出来的 Etag 匹配,因此 If-None-Match 为 False,不返回 200,返回 304,客户端继续使用本地缓存;

Cache-Control、max-age、Expires#

当首部字段 Cache-Control 有指定 max-age 指令时,会优先处理 max-age 指令,而忽略 Expires 字段。


参考:

  1. 《图解 HTTP》
  2. developer.mozilla.org/zh-CN/docs/W...
  3. cloud.tencent.com/developer/chapte...
  4. blog.csdn.net/swt369/article/detai...
https://learnku.com/articles/75429

简单总结一下 Go 语言中 make 和 new 关键字的实现原理:
make 关键字的作用是创建切片、哈希表和 Channel 等内置的数据结构。
new 的作用是为类型申请一片内存空间,并返回指向这片内存的指针。