/*

kurento nodejs 开发常用库

web server

websocket server

mqtt

ms sql server

mysql server

kms

*/

var mysql = require('mysql');
const mssql = require('mssql')
var path = require('path');
var express = require('express');
var uuid = require('node-uuid');
var bodyParser = require('body-parser');
var ws = require('ws');
var minimist = require('minimist');
var url = require('url');
var kurento = require('kurento-client');
var fs    = require('fs');
var https = require('https');
var http = require('http');
var Mqtt = require('mqtt');
var format = require('string-format');
format.extend(String.prototype);
var AliyunCore = require('@alicloud/pop-core');
var request = require('request');
var urlencode = require('urlencode');

class comm{
    constructor(){

};
    static  cb(callback,  p0 , p1 , p2) {
        try{
            if( callback){
                callback(p0,p1,p2);
            }
        }
        catch (e) {
            console.log()
        }
    };
    static extend ( a , b) {
        try{
            if( a==null || !a){
                a = {};
            }
            if( b && b!=null){
                for( p in b ){
                    a[p] = b[p];
                }
            }
        }
        catch (e) {
            console.log(e);
        }
        return a;
    };
    static loopCall( items , actionFunction , onComplete){
        try{
            if( items  && items!= null && items.length  && items.length>0){
                var acItems = [];
                var acResults=[]
                items.forEach(function (item) {
                    acItems.push(item);
                });
                var loadItem = function () {
                    if( acItems.length>0) {
                        var item = acItems.shift();
                        actionFunction(item, function (itemResult) {
                            acResults.push({i: item, r: itemResult});
                            loadItem();
                        })
                    }
                    else{
                        comm.cb(onComplete);
                    }
                }
                loadItem();
            }
            else{
                comm.cb(onComplete);
            }
        }
        catch (e) {
            console.log(e);
        }
    }
}

/*
* web server
* */
function  TWebSvrConfig() {
    this.port="20022";
    this.domain="wss.hhdata.cn";
    this.keyFn="E:/app/kmsdev/dev/svr/appModules/common/4195813_wss.hhdata.cn.key";
    this.certFn="E:/app/kmsdev/dev/svr/appModules/common/4195813_wss.hhdata.cn.pem";
    this.staticPaths={
        "/":"E:/app/kmsdev/dev/static"
    };
    this.apiRootPath ="hhu"
}
function TWebSvr() {
    this.config = new TWebSvrConfig();
    this.routes = {
        defaultApi: function (resApi , request , response ) {
            resApi.error={errCode:0 , errMsg:"."} ;
            resApi.data = "default."+ ( new Date()).getTime().toString();
            response.send(JSON.stringify(resApi));
        },
        acHello: function (resApi , request , response ) {
            resApi.error={errCode:0 , errMsg:"hello error."} ;
            resApi.data = "hello."+ ( new Date()).getTime().toString();
            response.send(JSON.stringify(resApi));
        }
    };
    this.objs ={
        app: null,
        server:null
    };
    this.getApp = function () {
        var me = this;
        var app = express();
        try{
            //设置静态虚拟目录
            for (sp in me.config.staticPaths) {
                app.use(sp, express.static(me.config.staticPaths[sp]));
            }
            //设置编码
            var urlencodedParser = bodyParser.urlencoded({extended: false});
            app.use(urlencodedParser);
            app.use(bodyParser.json());
            var jsonParser = bodyParser.json();
            //设置动态路由
            var apiPath = ("/" + [me.config.apiRootPath, ":ac"].join("/")).replace("//", "/");
            app.all(apiPath, jsonParser, function (req, res) {
                res.header("Access-Control-Allow-Origin", "*");
                res.header("Access-Control-Allow-Headers", "content-type");
                res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS");
                //API命令
                var ac = req.params.ac;
                //GET参数
                var ps = comm.extend({}, req.query);
                //返回结果
                var result = {
                    ac: ac,
                    ps: ps,
                    error: null,
                    data: null
                };
                //响应入口
                var handler = function (respResult) {
                    if (me.routes[respResult.ac]) {
                        me.routes[respResult.ac](respResult, req, res);
                    }
                };
                //POST参数调用
                if (req.method == "POST") {
                    if( req.headers["content-type"].indexOf('application/json')>=0 ){
                        ps = comm.extend(ps, req.body);
                        handler(result);
                    }
                    else {
                        req.setEncoding('utf8');
                        req.on('data', function (data) {
                            try {
                                var obj = JSON.parse(data);
                                ps = comm.extend(ps, obj);
                            } catch (ee) {
                                console.log(ee)
                            }
                        });
                        req.on('end', function () {
                            handler(result);
                        });
                    }
                } else if (req.method == "GET") {
                    //GET 方法调用
                    handler(result);
                }
            });
        }
        catch (e) {
            console.log(e);
        }
        return app;
    };
    this.start = function (callback) {
        var me = this;
        try{
            var options = {};
            if (me.config.keyFn && me.config.keyFn != null && me.config.keyFn.length > 0) {
                options = {
                    key: fs.readFileSync(me.config.keyFn),
                    cert: fs.readFileSync(me.config.certFn)
                };
            }
            var app = me.getApp();
            var httpsServer = https.createServer(options, app);
            httpsServer.listen(me.config.port, function (err) {
                me.objs.app = app;
                var apiPath = ("/"+me.config.apiRootPath+"/<ac>").replace("//","/").replace("//","/");
                var path = "https://{domain}:{port}{apiPath}".format({
                    domain: me.config.domain ,
                    port: me.config.port ,
                    apiPath: apiPath
                });
                console.log('start web server. api:', path);
                me.objs.server = httpsServer;
                comm.cb(callback , me.objs.server);
            });
        }
        catch (e) {
            console.log(e);
        }
    }
}

/*
* mqtt
* */
function  TMqClient( ) {
    this.config ={
        server: 'ssl://gmggf8g.mqtt.iot.bj.baidubce.com:1884',
        options: {
            username: 'gmggf8g/peer',
            password: 'a52bwKg22dCAuQzB',
            clientId: 'busi_s_' + (new Date()).getTime().toString()
        },
        autoTopics:[]
    };
    this.client = null;
    this.events={
        onConnect: function () {},
        onMsg: function (topic, msg) {}
    };
    this.getClientId = function () {
        var res = "cid_";
        res += ( new Date()).getTime().toString();
        res += "_"+ parseInt( Math.random()*10000).toString();
        return res;
    }
    this.disconnect = function () {
        var me = this;
        try{
            if( me.client && me.client!=null && me.client.isConnected()){
                me.client.disconnect();
                me.client = null;
            }
        }
        catch (e) {
            console.log(e)
        }
    };
    this.sendMsg = function (topic, msgInfo) {
        var me = this;
        try{
            me.client.publish(topic , JSON.stringify(msgInfo));
        }
        catch (e) {
            console.log(e)
        }
    };
    this.initConfig = function (svr , uid , pwd , autoTopics) {
        var me = this;
        me.config.server = svr ;
        me.config.options.username = uid;
        me.config.options.password = pwd;
        me.config.autoTopics = autoTopics;
    };
    this.reConnect = function (svr , uid , pwd , autoTopics) {
        var me = this;
        me.initConfig(svr, uid , pwd , autoTopics);
        me.connect()
    };
    this.connect = function () {
        var me = this;
        try{
            me.disconnect();
            me.config.options.clientId = me.getClientId();
            var client = Mqtt.connect(me.config.server, me.config.options);
            client.on('error', function(error) {
                console.error(error);
                client= null;
            });
            client.on('connect', function() {
                try {
                    console.log('Connected. Client id is: ' + me.config.options.clientId);
                    if( me.config.autoTopics){
                        try {
                            me.config.autoTopics.forEach(function (topic) {
                                client.subscribe(topic);
                            })
                        }
                        catch (er) {
                            console.log(er);
                        }
                    }
                    comm.cb(me.events.onConnect);
                }
                catch (er) {
                    console.log(er)
                }
            });
            client.on('disconnect', function() {
                try {
                    console.log('disconnect. Client id is: ' + me.config.options.clientId);
                }
                catch (er) {
                    console.log(er)
                }
            });
            client.on('message', function(topic, data) {
                try {
                   var msgInfo = JSON.parse(data);
                   me.events.onMsg(topic , msgInfo);
                }
                catch (er) {
                    console.log(er)
                }
            });
            me.client = client;
        }
        catch (e) {
            console.log(e)
        }
    };
}

/*
* websocket
* */
function TWebSocketConfig() {
    this.path ="/wss"
}
function  TWebSocket() {
    this.config = new TWebSocketConfig();
    this.webServer = null;
    this.websocketServer = null;
    this.sessions = {};
    this.events = {
        onsessionConnected : function (session) {
            
        },
        onsessionDisConnected : function (sessionId) {

},
        onRecMsg : function (session , msg) {

}
    };
    this.start = function (server) {
        var me = this;
        try{
            me.webServer = server;
            var wsSvr = new ws.Server({
                server:server ,
                path: me.config.path
            });
            wsSvr.Server.on('connection', function (ws) {
                me.websocketServer = wsSvr;
                var sessionId = "sid_"+(new Date()).getTime().toString()+"_"+
                    parseInt(math.random()*1000).toString();
                ws.sessionId = sessionId;
                me.sessions[sessionId] = ws;
                comm.cb(me.events.onsessionConnected(ws));
                ws.on('error', function(error) {
                    console.log(error);
                    me.closeSession(ws.sessionId);
                });
                ws.on('close', function () {
                    try{
                        if(me.sessions[ws.sessionId]){
                            delete me.sessions[ws.sessionId];
                        }
                        ws.close();
                        comm.cb(me.events.onsessionDisConnected(ws.sessionId));
                    }
                    catch (eer) {
                        console.log(eer)
                    }
                });
                ws.on('message', function (_message) {
                    try{
                        var message = JSON.parse(_message);
                        comm.cb(me.events.onRecMsg, message);
                    }
                    catch (er) {
                        console.log(er);
                    }
                });
            })
        }
        catch (e) {
            console.log(e);
        }
    };
}

/*
* ms sql server
* */
function  TMsDB () {
    this.config = {
        msDB: {
            server        : 'video.xmgkfw.cn',
            user        : 'sa',
            password    : 'ABCabc123',
            //database    : 'rtcChannel',
            database    : 'rtcChannel',
            port        : 8201,
            options: {
                encrypt: true //使用windows azure,需要设置次配置。
            },
            pool: {
                min: 0,
                idleTimeoutMillis: 3000
            }
        },
        sqlPath:"e:/app/kmsdev/dev/svr/sqls"
    };
    this.pool=null;
    this.query = function (sql , ps , callback) {
        var me = db;
        var queryRes ={
            result:null
        };
        try{
            if( me.pool==null){
                me.pool = new mssql.ConnectionPool(me.config.msDB);
            }
            me.pool.connect(function (errConn , connection) {
                if( errConn ){
                    queryRes.error = errConn;
                    callback (queryRes);
                }
                else{
                    var dbReq = connection.request();
                    if( ps && ps!=null){
                        var getType = function (arg) {
                            var dataType= mssql.NVarChar;
                            if( arg && typeof(arg) == "string"){
                                dataType= mssql.NVarChar;
                            }
                            else if( arg && typeof(arg) == "number" && !isNaN(arg)){
                                if( Number.isInteger(arg)){
                                    dataType= mssql.Int;
                                }
                                else{
                                    dataType = mssql.Float;
                                }
                            }
                            return dataType;
                        }
                        for ( var p in ps){
                            var v = ps[p];
                            var vType = getType(v);
                            dbReq.input(p,vType,v);
                        }
                    }
                    dbReq.query(sql , function (erQuery, resQuery) {
                        try{
                            queryRes.result = resQuery;
                            callback (queryRes);
                        }
                        catch (eer) {
                            console.log(eer);
                        }
                    })
                    connection.release();
                }
            })
        }
        catch (e) {
            console.log(e)
        }
    };
    this.queryByFn= function (sqlFn , ps , callback) {
        var me = db;
        var queryRes ={
            result:null
        };
        try{
            var fn = [me.config.sqlPath , sqlFn].join('/');
            fn = fn.replace("\\",'/').replace('//','/');
            fn = fn + (fn.indexOf('.txt')>0?"":".txt");
            if( fs.existsSync(fn)){
                var sql = fs.readFileSync(fn, 'utf-8');
                me.query(sql , ps , callback);
            }
            else{
                queryRes.error="query Key is error.["+sqlFn+"]";
                callback(queryRes);
            }
        }
        catch (e) {
            console.log(e)
        }
    }
}

/*
* my sql server
* */
function  TMyDB() {
    this.apps={
        kms:{
            host:"tjiot.hhdata.cn" ,
            port:53306,
            uid:"root",
            pwd:"hhuc1115",
            db:"kmsv10" ,
            sqlPath:"E:/app/kmsdev/dev/svr/sqls"
        },
        hhRemoteAssist:{
            host:"tjiot.hhdata.cn" ,
            port:53306,
            uid:"root",
            pwd:"hhuc1115",
            db:"hhRemoteAssist" ,
            sqlPath:"E:/app/kmsdev/dev/svr/sqls"
        }
    };
    this.pools={};
    this.getConn = function (appName , callback) {
        var me = this;
        var res ={ conn:null};
        try{
            if( me.apps[appName] ){
                var dbInfo = me.apps[appName];
                if( !dbInfo.pool){
                    dbInfo.pool = mysql.createPool({
                        host: dbInfo.host ,
                        port: dbInfo.port ,
                        user: dbInfo.uid ,
                        password: dbInfo.pwd ,
                        database: dbInfo.db,
                        connectionLimit:100
                    });
                }
                dbInfo.pool.getConnection(function (err, conn) {
                    res.error = err;
                    res.conn = conn;
                    callBack(callback , res);
                })
            }
            else{
                res.error="appName is error."
                callBack(callback , res);
            }
        }
        catch (e) {
            console.log(e);
        }
    };
    this.query = function (appName ,  sql , ps   , callback) {
        var res ={ result:null};
        var me = this;
        try{
            me.getConn(appName , function (resConn) {
                if( resConn.error){
                    callBack(callback, resConn);
                }
                else {
                    resConn.conn.query(sql, ps, function (errQuery, results, fields) {
                        resConn.conn.release();
                        if (errQuery) {
                            res.error = errQuery;
                            callBack(callback, res);
                            console.log(errQuery, sql, ps);
                        } else {
                            res.result = JSON.parse(JSON.stringify(results));
                            callBack(callback, res);
                        }
                    })
                }
            })
        }
        catch (e) {
            console.log(e);
        }
    };
    this.queryByPath = function (appName ,  sqlPath , ps   , callback) {
        var res ={  result:null};
        var me = this;
        try{
            if( me.apps[appName] ){
                var absPath = me.apps[appName].sqlPath+"/"+sqlPath;
                absPath = absPath.replace("//","/");
                if( absPath.indexOf(".txt")<=0 ){
                    absPath = absPath+".txt";
                }
                fs.exists(absPath, function (flag) {
                    if( flag){
                        var sql = fs.readFileSync(fn, 'utf-8');
                        me.query(appName, sql , ps , function (resDB) {
                            res = resDB;
                            callBack(callback , res);
                        })
                    }
                    else{
                        res.error = "sqlPath is error.";
                        callBack(callback , res);
                    }
                })
            }
            else{
                res.error="appName is error."
                callBack(callback , res);
            }
        }
        catch (e) {
            console.log(e);
        }
    };
}
/*
* kurento
* */
function TKurentoInf(){
    this.config={
        kmsUrl:"wss://ssl.hhdata.cn:40014/kurento"
    };
    this.tags={};
    this.objs={
        client: null,
        serverMsnager: null,
        kmsObjs:{}
    };
    this.getClient = function (callback) {
        var me = this;
        try{
            if( me.objs.client && me.objs.client!= null){
                comm.cb(callback , me.objs.client);
            }
            else{
                kurento(me.config.kmsUrl, function (erC , client) {
                    client.getServerManager(function (erS , svr) {
                        client.serverMsnager = svr;
                        me.objs.client = client;
                        me.objs.serverMsnager = svr;
                        me.getChilds(svr, function (kmsObjs) {
                            me.objs.kmsObjs = kmsObjs;
                            comm.cb(callback, client);
                        })
                    })
                })
            }
        }
        catch (e) {
            console.log(e)
        }
    };
    this.getChilds = function ( rootObj , callback) {
        var me = this;
        try{
            var resultItems={};
            var getItems = function (kmsObj, onGetItems) {
                var acFun = kmsObj.id.indexOf("ServerManager")>0?"getPipelines": "getChildren";
                kmsObj[acFun](function (erItems, items) {
                    var index =0;
                    var loadItem = function () {
                        if( index<items.length){
                            var item = items[index];
                            me.getObjInfo(item, function () {
                                resultItems[item.id] = item;
                                getItems(item, function () {
                                    index++;
                                    loadItem();
                                })

})
                        }
                        else{
                            //console.log(kmsObj.id, index, items.length, resultItems.length)
                            onGetItems();
                        }
                    }
                    loadItem();
                })
            }
            getItems(rootObj, function () {
                comm.cb(callback, resultItems);
            });
        }
        catch (e) {
            console.log(e)
        }
    };
    this.setObjInfo = function ( obj , name , tags, callback) {
        var me = this;
        try{
            obj.setName( name , function (erName ) {
                var ps = Object.keys(tags);
                var vs = Object.values(tags);
                var index =0;
                var acItem = function () {
                    if( index<ps.length){
                        obj.addTag((ps[index]).toString(), (vs[index]).toString(), function (erTag) {
                            index++;
                            acItem();
                        })
                    }
                    else{
                        obj.info={
                            id: obj.id,
                            name:name,
                            tags:tags
                        };
                        me.objs.kmsObjs[obj.id] = obj;
                        comm.cb(callback, obj)
                    }
                }
                acItem();
            })

}
        catch (e) {
            console.log(e)
        }
    };
    this.getObjInfo = function ( obj , callback) {
        var me = this;
        try{

var info ={
                id:obj.id ,
                name:"",
                tags:{}
            };
            obj.getName(function (erName, name) {
                info.name = name;
                obj.getTags(function (erTags, tags) {
                    tags.forEach(function (tag) {
                        info.tags[tag.key] = tag.value;
                    })
                    obj.info= info;
                    comm.cb(callback, info, obj);
                })
            })
        }
        catch (e) {
            console.log(e)
        }
    };
    this.createObject= function(objType , parent , url , name , tags , callback ){
        var me = this;
        try{
            var res= null;
            var err = {
                errorCode : -1 ,
                err:"objType is error."
            };
            me.getClient(function(client){
                var onCreate = function(obj){
                    me.setObjInfo(obj,name,tags,function () {
                        comm.cb(callback, obj);
                    })
                }
                if( objType=="MediaPipiline"){
                    client.create("MediaPipeline", function(erObj,obj){
                        onCreate(obj);
                    });
                }
                else if (
                    ["DispatcherOneToMany","Composite","Dispatcher","WebRtcEndpoint"].indexOf(objType)>=0
                ){
                    pipe.create(objType, function (erObj , obj) {
                        onCreate(obj);
                    })
                }
                else if (objType=="PlayerEndpoint"){
                    pipe.create("PlayerEndPoint",{uri: url}, function (erObj , obj) {
                        obj.on("EndOfStream", function () {
                            obj.play();
                        })
                        obj.play(function (erPlayer) {
                            if( erPlayer){
                                console.log('play error.', url , erPlayer);
                            }
                            onCreate(obj);
                        });
                    })
                }
                else{
                    comm.cb(callback, res , err);
                }
            });
        }
        catch(er){
            console.log(e);
        }
    };
    this.dispatch= function( srcId , targetId , callback ){
        var me = this;
        try{
            var res ={
                state:0,
                error:""
            } ;
            if( !me.objs.kmsObjs[srcId]){
                res.state = -1;
                res.error="srcId is error";
                comm.cb(callback,res)
            }
            else if( !me.objs.kmsObjs[targetId]){
                res.state = -2;
                res.error="targetId is error";
                comm.cb(callback,res)
            }
            else{
                var ids = srcId.split("/");
                var hubId = [ids[0],ids[1]].join("/");
                if( !me.objs.kmsObjs[hubId]){
                    res.state = -3;
                    res.error = "hub is empty."
                }
                else{
                    var hub = me.objs.kmsObjs[hubId];
                    var src = me.objs.kmsObjs[srcId];
                    var target = me.objs.kmsObjs[targetId];
                    hub.connect(src, target, function (erConnect) {
                        res.state = erConnect?-4:1;
                        res.error = erConnect;
                        comm.cb(callback, res);
                    })
                }
            }
        }
        catch(er){
            console.log(e);
        }
    }
}

var kms = new TKurentoInf();
kms.getClient(function (client) {
        Object.values(kms.objs.kmsObjs).forEach(function (obj) {
            console.log(obj.info)
        })
})

module.exports={
    comm:comm ,
    TWebSvr: TWebSvr,
    TWebSocket:TWebSocket,
    TMsDB:TMsDB,
    TMyDB:TMyDB,
    TKurentoInf:TKurentoInf
};

Kurento nodejs 开发常用库相关推荐

  1. nodejs 前端 常用库

    目录 nodejs 通用 net tools dev-tools express 前端 工具链 UI 动效 工具 nodejs 通用 名称 能力 官网 passport 用户认证.支持 Auth Au ...

  2. Python游戏开发常用库

    PyWeek:编程挑战,主要是Python游戏开发方面的 PyGame:PyGame在优秀的SDL库之上添加了更多功能.允许使用python语言创建功能齐全的游戏和多媒体程序.具有高度的可移植性,几乎 ...

  3. iOS开发常用三方库、插件、知名博客

    TimLiu-iOS iOS开发常用三方库.插件.知名博客等等,期待大家和我们一起共同维护,同时也期望大家随时能提出宝贵的意见(直接提交Issues即可). 持续更新... 版本:Objective- ...

  4. Android开发常用开源库

    Android 开发常用开源库 一.网络请求 二.图片加载 三.数据库 四.通讯 五.注解 六.JSON解析 七.性能优化 八.性能优化之布局分析 九.工具类 十.状态栏 十一.扫码库 十二.播放器 ...

  5. CDN公共库、前端开发常用插件一览表(VendorPluginLib)

    ============================================================= ==========================前端CDN公共库==== ...

  6. 30组常用前端开发组件库,前端组件收集整理列表

    0. 前端自动化 前端构建工具 gulp – The streaming build system grunt – the JavaScript Task Runner 前端模块管理器 Bower – ...

  7. 指尖上行--移动前端开发进阶之路(读书笔记)----1.3常用库和框架

    chapter 1 移动页面开发 页面布局 页面调试 常用库和框架 1.3 常用库和框架 1.3.1 jQuery Mobile jQuery Mobile是jQuery 框架的一个组件(而非jque ...

  8. 开发常用镜像资源替换为国内开源镜像(yum,compose,maven,docker,android sdk,npm,国内开源镜像汇总)...

    一.国内开源镜像站点汇总 阿里云开源镜像站 (http://mirrors.aliyun.com/) 网易开源镜像站 (http://mirrors.163.com/) 中国科学技术大学开源镜像站 ( ...

  9. 《Nodejs开发加密货币》之三:Nodejs让您的前端开发像子弹飞一样

    关于 <Nodejs开发加密货币>,是一个加密货币产品的详细开发文档,涉及到使用Nodejs开发产品的方方面面,从前端到后台.从服务器到客户端.从PC到移动.加密解密等各个环节.代码完全开 ...

最新文章

  1. office2016word 每次打开都有进度条问题 解决方式
  2. 这位挪威博士是如何成为阿里云PolarDB资深架构师的?
  3. 用拖拉实现设备驱动配置(EsayHMI最新驱动配置方式)
  4. sql语言特殊字符处理
  5. java zip压缩_压缩工具
  6. 图像形状特征(一)--FD
  7. codeforces 360B
  8. 中文怎么设置 水晶报表 越南文_越南语到底是不是汉语的一门方言?为什么和粤语这么像?...
  9. 关于安装vs2015后, vs2013打开项目工程失败崩溃的问题!
  10. CentOS下IOZone编译安装
  11. Arduino+WZ指令+Onenet
  12. 有测试狗狗好坏的软件吗,6个测试判断狗狗性格,胆小或凶猛一测便知,你家狗狗是哪种?...
  13. 使用 screw 导出 数据库表结构文档
  14. 谈“太极起势”的练法
  15. 7-2 单位年会聚餐时的座次C位
  16. SSH框架报 org.hibernate.impl.QueryImpl cannot be cast to com.gao.ruan.pojos.Category
  17. 迅雷批处理:漫画下载利器!(娘王下载为例)
  18. 数据库date日期转String类型
  19. 年底了,PPT怎么做?用数据给老板讲故事,在职场上无往不利
  20. 记录M1Mac基础的Command快捷键

热门文章

  1. linux搭建kk教程,linux版本KK搭建视频教程+文字教程
  2. Android限制输入框输入中英文和数字,并限制个数
  3. html5中三级下拉菜单实现案例
  4. 双向链接的红黑树(二):直接删除
  5. 数字ic笔试2022小米提前批
  6. android+华为pad+自动对焦,华为手机的专业模式你会用吗?打开这6个参数,1秒变单反...
  7. plc程序中参数与变量有何区别
  8. html五星红旗写法,html+css生成五星红旗
  9. EA周报 | 滴滴回应或被下架APP;腾讯视频回应“push线团队全部被开”;外媒确认iPhone 11 9月10日发布...
  10. c语言编程简单小游戏坦克大战,坦克大战1990(c语言文件版)游戏