MongoHQ服务是类似于Amazon S3的云服务,只不过它专注于在云中托管MongoDB实例。可以访问http://mongohq.com来注册服务。小于16MB的数据库是免费的,可以用它来运行本章的示例。在线用户界面易于使用,让你可以快速地浏览数据。

设置好MongoHQ账户之后,应该使用在线的用户界面创建名为Lifestream的数据库。你会得到数据库服务器的名称和端口号,对于每个MongoHQ数据库,这些信息是不同的。还必须输入访问数据库的用户名和密码。在线界面将提供数据库登录的详细信息。

在下面的示例中,会把完整的Lifestream应用程序所需的所有文件放在一起。首先,需要得到配置信息,测试到MongoHQ服务的连接,并验证最基本的功能可以实际工作。在第9章,构建了图片上传功能。现在,可以将它放到一边,集中精力完成用户注册功能。介绍将用户添加到系统的基本概念是引出其余功能必不可少的。在本示例中专注于核心功能,下面将完成一个非常简单的用户注册功能。这里不使用密码,而是为每个物理设备分配一个唯一的令牌。最终,将使用OAuth令牌进行用户注册,但现在要实现一个唯一的设备令牌。下面是具体操作步骤。

(1) 进入lifestream/server文件夹(继续使用和第8章、第9章一样的文件夹结构),运行下面的npm模块安装命令。

npm install connect
npm install mongodb
npm install knox
npm install uuid
npm install oauth
npm install url
npm install request
npm install cookies

前面已经安装了一些模块,npm将报告已安装的版本信息或将模块升级到最新的版本。

(2) 新建一个名为config.js的文件来存储服务器的配置信息,将下面的代码插入到文件中,用自己的配置信息替换突出显示的内容。

exports.mongohq   = { username:''YOUR_DB_USERNAME',     password: 'YOUR_DB_PASSWORD',name:     'YOUR_DB_NAME',host:     'YOUR_DB_HOST',port:     YOUR_DB_PORT
}exports.amazon  = { s3bucket: 'YOUR_S3_BUCKET_NAME',keyid:    'YOUR_AWS_KEY_ID',     secret:   'YOUR_AWS_SECRET'
}exports.twitter = { keyid:  'YOUR_TWITTER_KEY_ID', secret: 'YOUR_TWITTER_SECRET'
}exports.facebook = { keyid:  'YOUR_FACEBOOK_KEY_ID',secret: 'YOUR_FACEBOOK_SECRET'
}exports.server = 'YOUR_IP_ADDRESS'
exports.max_stream_size = 100

代码片段位于lifestream/server/config.js

(3) 使用下面的更新版本替换lifestream/server文件夹下common.js文件的内容。

var util                        = exports.util                  = require('util')
var connect         =exports.connect       = require('connect')
var knox                        = exports.knox                  = require('knox')
var uuid                        =exports.uuid                  =require('node-uuid')
var oauth                   =exports.oauth             =require('oauth')
var url                         =exports.url                       =require('url')
var request         =exports.request       = require('request')
var Cookies         =exports.Cookies       = require('Cookies')var config = exports.config =require('./config.js')// JSON functionsexports.readjson = function(req,win,fail) {var bodyarr= [];req.on('data',function(chunk){bodyarr.push(chunk);})req.on('end',function(){varbodystr = bodyarr.join('');util.debug('READJSON:'+req.url+':'+bodystr);try {varbody = JSON.parse(bodystr);win&& win(body);}catch(e){fail&& fail(e)}})
}exports.sendjson = function(res,obj){res.writeHead(200,{'Content-Type': 'text/json','Cache-Control': 'private, max-age=0'});var objstr= JSON.stringify(obj);util.debug('SENDJSON:'+objstr);res.end(objstr );
}// mongo functionsvar mongodb = require('mongodb')var mongo = {mongo:mongodb,db: null,
}mongo.init = function( opts, win, fail ){util.log('mongo: '+opts.host+':'+opts.port+'/'+opts.name)mongo.db = newmongodb.Db(opts.name, newmongodb.Server(opts.host, opts.port, {}),{native_parser:true,auto_reconnect:true});mongo.db.open(function(){if(opts.username ) {mongo.db.authenticate(opts.username,opts.password,function(err){if(err) {fail && fail(err)}else {win && win(mongo.db)}})}else {win&& win(mongo.db)}},fail)
}mongo.res = function( win, fail ){returnfunction(err,res) {if( err ){util.log('mongo:err:'+JSON.stringify(err));fail&& 'function' == typeof(fail) && fail(err);}else {win&& 'function' == typeof(win) && win(res);}}
}mongo.open = function(win,fail){mongo.db.open(mongo.res(function(){util.log('mongo:ok');win&& win();},fail))
}mongo.coll = function(name,win,fail){mongo.db.collection(name,mongo.res(win,fail));
}exports.mongo = mongo

代码片段位于lifestream/server/common.js

这个common.js的更新版本支持MongoDB验证,这需要使用MongoHQ的云数据库服务。

(4) 在文件夹lifestream/server下新建一个名为server.mongo.js的文件,将下面的代码插入到该文件中。

var common  = require('./common.js')
var config  = common.config
var mongo   = common.mongovar util                 = common.util
var connect     = common.connect
var knox                    = common.knox
var uuid                    = common.uuid
var oauth               = common.oauth
var url                     = common.url
var request     = common.request
var Cookies     = common.Cookies// API functionsfunction search(req,res){var merr = mongoerr400(res)mongo.coll('user',function(coll){coll.find({username:{$regex:new RegExp('^'+req.params.query)}},{fields:['username']},merr(function(cursor){var list = []cursor.each(merr(function(user){if( user ) {list.push(user.username)}else {common.sendjson(res,{ok:true,list:list})}}))}))})
}function loaduser(req,res) {var merr = mongoerr400(res)finduser(true,['username','name','following','followers','stream'],req,res,function(user){var userout = { username:  user.username,name:      user.name,followers: user.followers,following: user.following,stream:     user.stream}common.sendjson(res,userout)})
}function register(req,res) {var merr = mongoerr400(res)mongo.coll('user',function(coll){coll.findOne({username:req.json.username},merr(function(user){if( user ) {err400(res)()}else {var token = common.uuid()coll.insert({ username:     req.json.username,token:                        token,followers:        [],following:       [],stream:                  []},merr(function(){common.sendjson(res,{ok:true,token:token})}))}}))})
}// utility functionsfunction finduser(mustfind,fields,req,res,found){var merr = mongoerr400(res)mongo.coll('user',
function(coll){var options = {}if( fields ) {options.fields = fields}coll.findOne({username:req.params.username},options,merr(function(user){if( mustfind && !user ) {err400(res)}else {found(user,coll)}}))})
}function mongoerr400(res){return function(win){return mongo.res(win,function(dataerr) {err400(res)(dataerr)})}
}function err400(res,why) {return function(details) {util.debug('ERROR 400 '+why+' '+details)res.writeHead(400,''+why)res.end(''+details)}
}function collect() {return function(req,res,next) {if( 'POST' == req.method ) {common.readjson(req,function(input) {req.json = inputnext()},err400(res,'read-json'))}else {next()}}
}function auth() {return function(req,res,next) {var merr = mongoerr400(res)mongo.coll('user',function(coll){coll.findOne({token:req.headers['x-lifestream-token']},{fields:['username']},merr(function(user){          if( user ) {next()}else {res.writeHead(401)res.end(JSON.stringify({ok:false,err:'unauthorized'}))}}))})}
}var db                 = null
var server  = nullmongo.init({name:                            config.mongohq.name,host:                           config.mongohq.host,port:                           config.mongohq.port,username:           config.mongohq.username,password:           config.mongohq.password,}, function(res){db = resvar prefix = '/lifestream/api/user/'server = connect.createServer(connect.logger(),collect(),connect.router(function(app){app.post( prefix+'register', register),app.get(  prefix+'search/:query', search)}),auth(),connect.router(function(app){app.get(  prefix+':username', loaduser)}))server.listen(3009)},function(err){util.debug(err)}
)

代码片段位于lifestream/server/server.mongo.js

(5) 在文件夹lifestream/server下新建一个名为accept.mongo.js的文件,将下面的代码插入到该文件中。

var common  = require('./common.js')
var config  = common.configvar util                    = common.util
var request     = common.request  var assert           = require('assert')
var eyes                    = require('eyes')var urlprefix       = 'http://'+config.server+':3009/lifestream/api'
var headers             = {}function handle(cb) {return function (error, response, body) {if( error ) {util.debug(error)}else {var code = response.statusCodevar json = JSON.parse(body)util.debug('  '+code+': '+JSON.stringify(json))assert.equal(null,error)assert.equal(200,code)cb(json)}}
}
function get(username,uri,cb){util.debug('GET '+uri)request.get({uri:uri,headers:headers[username] || {}}, handle(cb))
}function post(username, uri,json,cb){util.debug('POST '+uri+': '+JSON.stringify(json))request.post({uri:uri,json:json,headers:headers[username] || {}}, handle(cb))
}module.exports = {api:function() {var foo = (''+Math.random()).substring(10)var bar = (''+Math.random()).substring(10)// create and load;post(null,urlprefix+'/user/register',{username:foo},function(json){assert.ok(json.ok)headers[foo] = {'x-lifestream-token':json.token};get(foo, urlprefix+'/user/'+foo,function(json){assert.equal(foo,json.username)assert.equal(0,json.followers.length)assert.equal(0,json.following.length);post(null,urlprefix+'/user/register',{username:bar},function(json){assert.ok(json.ok)headers[bar] = {‚x-lifestream-token':json.token};get(bar, urlprefix+'/user/'+bar,function(json){assert.equal(bar,json.username)assert.equal(0,json.followers.length)assert.equal(0,json.following.length)// search;get(null,urlprefix+'/user/search/'+foo.substring(0,4),function(json){assert.ok(json.ok)assert.equal(1,json.list.length)assert.equal(json.list[0],foo);})  // search;})  // get ;})  // post;})  // get;})  // post}
}

代码片段位于lifestream/server/accept.mongo.js

这是一个验收测试,用于测试运行中的服务器。每个测试案例都在前一个测试案例的回调函数中运行,要确保测试按顺序运行。

(6) 安装expresso测试框架,需要用它来运行accept.mongo.js脚本。

npm install expresso

(7) 打开一个新的终端窗口,并启动服务器。

node server.mongo.js
21 Mar 13:39:47 - mongo: flame.mongohq.com:27044/lifestream

(8) 打开另一个新的终端窗口,运行验收测试。

expresso accept.mongo.js
DEBUG: POST http://192.168.100.112:3009/lifestream/api/user/register: {"username":"707915425"}
DEBUG:   200: {"ok":true,"token":"0C257205-AB94-4768-9FCC-A1B1321AD2A5"}
DEBUG: GET http://192.168.100.112:3009/lifestream/api/user/707915425
DEBUG:   200: {"username":"707915425","followers":[],"following":[],"stream":[]}
...

服务器和验收测试都会生成调试输出,按顺序显示HTTP请求和响应。

(9) 转到MongoHQ网站,检查user集合的内容。在user集合中,应该看到两个文档

警告:验收测试需要一个真实的网络连接,因为服务器必需和远程的MongoHQ服务通信,以存储和检索数据。这就是它被称为验收测试而非单元测试的原因。根据定义,验收测试要有外部依赖。

示例说明

本章中的应用程序是一个完整的应用程序,包含许多不同的功能,它依赖很多npm模块。前面几章已经用过大多数的模块。以前没有用过的模块包括url、request和 cookies模块,这些模块都是处理HTTP请求的辅助模块。

本章还介绍了使用config.js文件存储服务器配置的概念。这只是前面章节中所使用的keys.js文件的扩展。创建用于生产的应用程序时,从实现中分离出配置,并且不在代码中嵌入配置的设置是一个好主意。

在本章的前面注册了MongoHQ,在前面的章节中也应该有Amazon、Twitter和Facebook的键,可以使用这些键来填写设置。

本章的common.js文件包括了前几章的所有实用功能。这些实用功能让你很容易在HTTP API中处理JSON的请求和响应,以及使用MongoDB的API。还有一个额外的功能。为了使用MongoHQ服务,需要登录到数据库。可以使用下面的代码。

  mongo.db.open(function(){if( opts.username ) {mongo.db.authenticate(opts.username,opts.password,function(err){...

为了更便于管理,在本章中添加新功能时,服务器端的代码会存储在单独的server.*.js文件中。该示例的文件名为server.mongo.js。通过本章的介绍可以比较这些文件,以帮助理解。服务器端的代码遵循前面章节中使用的结构。首先,有主要的API函数,然后是一些实用功能,之后是connect模块配置。

在本示例中,实现了搜索函数、用户注册及获取用户详细信息的函数。搜索函数使用MongoDB的正则表达式搜索功能,寻找一个与给定前缀相匹配的用户名。这仅仅是在应用程序中实现用户搜索功能的一个简单方法。代码使用mongoerr400实用函数处理MongoDB出现错误,如果出现问题,将HTTP 400状态码返回给所有客户端。在本节的后面会解释这是如何工作的。下面的代码解释了搜索函数的工作原理。

      coll.find({username:{$regex:new RegExp('^'+req.params.query)}},{fields:['username']},merr(function(cursor){var list = []cursor.each(merr(function(user){if( user ) {list.push(user.username)}else {common.sendjson(res,{ok:true,list:list})}

这段代码中的第一行粗体行显示了在MongoDB中如何使用正则表达式查询。它遵循标准的MongoDB查询语法。查询的值是作为HTTP请求的参数提供的,由传递到函数的req对象暴露。

第二行粗体行显示了如何限制从MongoDB结果返回的字段。这样,可以避免返回每个用户的所有数据。如果只是想要匹配的用户名列表,返回数据的所有字段就是资源浪费。

查询的结果作为cursor对象返回。为了使用该对象,要为它的each函数提供一个回调。对于结果集中的每一项,都会调用回调函数。这和传统的SQL数据库游标工作的方式非常相似。当所有的项都被返回之后,将得到一个空(null)的对象,这是停止的信号。if语句检查用户参数是否为空,如果为空,返回JSON结果。否则,它持续追加用户名到JSON结果中。

loaduser函数将大部分的工作交给finduser实用函数。这个函数不返回纯粹的数据库结果,因为这样做可能会公开内部系统的细节,如MongoDB的id字段。相反,loaduser函数只返回指定的数据集。以这种方式显式地过滤数据可能看起来有点偏执,但它是一个很好的安全经验法则。

继续向下阅读脚本文件,在实用函数部分的finduser实用函数,完成实际到数据库中寻找用户的工作。关键的代码是调用集合对象的findOne函数,执行用户搜索。

      coll.findOne({username:req.params.username},

用户名被指定为HTTP请求的参数。在API使用的URL结构中,对于针对用户的请求,用户名必须是URL路径的一部分。

注册函数与finduser函数非常相似,不同之处在于:如果用户不存在,它会执行一个操作。如果无法找到给定的用户名,说明用户不存在,可以注册。注册是由下面的insert操作执行的。

            var token = common.uuid()coll.insert({ username: req.json.username,token:     token,followers:[],following:[],stream:    []},

token是一个特殊的字段,用来验证用户的身份。uuid模块提供了一种方式,可以生成一个长的、随机的、唯一的字符串,特别适合作为令牌。因为这个示例的重点放在构建应用程序,而不是用户管理功能,所以没有实现密码系统。相反,代码采用了一条捷径。注册使用了先到先得的机制。令牌返回到客户端应用程序,客户端永久保存它。此令牌可以用来访问API。实际上它是一个永久的登录令牌。在生产环境中不应使用这种设计,但在这里可以用它模拟用户管理的逻辑,从而演示注册和认证,以及后来与Facebook和Twitter的集成。在开发过程中,需要删除全部现有的登录。可以通过删除并重新安装应用程序来实现这一点。如果在浏览器中测试示例,则只需要从本地存储系统中删除user项。

用户名的值来自于标准Node请求对象的json属性,这看起来相当奇怪。json属性是collect实用函数注入请求对象中的自定义属性,它包含了请求提交的任何JSON内容的解析值。collect函数截获HTTP的POST请求,通过使用common.readjson函数取得其内容。

function collect() {return function(req,res,next) {if( 'POST' == req.method ) {common.readjson(req,function(input) {req.json = inputnext()},err400(res,'read-json'))}else {next()}}
}

collect函数特殊的另一个原因是,它实际上是connect模块中间件函数。中间件函数可以对HTTP请求做一些处理,然后将请求向前传递给其余的服务器。它处于请求的中间,因此而得名。connect模块是中间件函数的堆栈,每个函数都对请求做了一些工作。在前面的章节中,使用标准的router中间件定义自己的URL终点。在本示例中,建立了自己的中间件!

要定义connect中间件函数,需要编写一个函数,使用一些配置参数(collect还没有用到),并返回一个函数。函数接受三个参数:请求、响应和一个特殊的next函数。这是体现JavaScript强大功能的另一个示例:可以使用函数来动态构建另一个函数。

collect中间件函数的实际工作是在动态函数中完成的。检查POST请求,读取JSON信息,并设置req对象的自定义json属性。处理完后,调用特定的next函数。这样connect知道中间件已经完成处理工作,可以将请求传递到下一阶段进行处理。

如果出现错误该如何处理?因为正在建立一个可以独立于应用程序使用的API,所以需要确保很好地遵循HTTP协议。这意味着,如果是因为输入而产生错误,就需要返回一个400 BadRequest的状态代码。可以使用err400实用函数来执行这项任务,该函数创建了一个函数来完成实际工作。这样,就可以为代码不同的部分定义相应的错误消息。此外,mongoerr400函数针对MongoDB的错误创建了一个特殊的错误处理函数。正如在代码中看到的,可以使用这些函数,通过调用它们来为每个顶层API函数创建自定义的错误函数,如下所示。

  var merr = mongoerr400(res)

注意:在本示例中的错误处理代码总是返回400 Bad Request的状态代码。严格地说,如果是因为你而导致的错误(例如,如果数据库连接中断),应该返回一个500 Internal Server Error的状态代码。collect中间件函数不是该服务器代码中唯一的中间件函数。还有一个auth中间件函数用来处理用户身份验证。只有登录的用户才可以调用某些API。这可以防止其他用户访问他人的私人资料。auth中间件函数处于这些API调用之前,用于检查请求是否来自已经登录的用户。这就是为什么代码的connect部分被分成两个路由器部分:第一个是未经验证的动作,如登记和查询;第二个是已通过验证的动作,如获取用户的详细信息或关注其他用户。

auth函数与finduser函数类似,都是通过用户名查找用户。它也需要一个自定义的HTTP头X-Lifestream-Token,其中包含的注册令牌必须与用户存储的令牌匹配。如果验证失败,返回HTTP401状态代码,表示这是未经授权的访问。否则,调用next函数,请求开始处理。下面的代码执行令牌搜索。

        coll.findOne({token:req.headers['x-lifestream-token']},{fields:['username']},merr(function(user){       if( user ) {next()}else {res.writeHead(401)res.end(JSON.stringify({ok:false,err:'unauthorized'}))}}))

最后一部分代码,在创建到MongoDB数据库的连接后,设置了connect中间件堆栈。这些按顺序放在一起的函数实现了API结构的中间件。

不应该只是手动测试该服务器。也应该使用一套标准测试来验证API操作正常与否。可以通过构建验收测试实现这一点。使用Node的expresso模块,构建单元和验收测试。虽然应该创建单元测试和验收测试,但该示例的重点是验收测试。单元测试和验收测试之间的区别是什么呢?验收测试依赖于外部资源,而单元测试则不是。为了测试服务器可以正常使用MongoHQ(外部资源)工作,需要验收测试。

验收测试的代码位于accept.mongo.js脚本中。主服务器运行时,在一个单独的终端中运行该脚本。expresso模块将运行,测试任何被放置在特殊exports变量中的函数。在本示例中的代码只有一个主要测试:api函数。此函数包含了一组测试,按顺序运行并执行API的操作。

在此使用了common.js文件以避免重复代码。handle、get和post函数是实用函数,用来跟踪HTTP的请求,当它们到达时输出结果。因此,可以运行测试,看看直接会发生什么,这对于调试是非常有用的。

测试本身是API调用的序列。运行测试时,会注册两个用户,会请求它们的数据,并执行了一个搜索。

    ;post(null,urlprefix+'/user/register',{username:foo},function(json){...headers[foo] = {'x-lifestream-token':json.token};get(foo, urlprefix+'/user/'+foo,function(json){...;post(null,urlprefix+'/user/register',{username:bar},function(json){...headers[bar] = {'x-lifestream-token':json.token};get(bar, urlprefix+'/user/'+bar,function(json){...// search;get(null,urlprefix+'/user/search/'+foo.substring(0,4),

使用约定格式化代码,避免了很多恼人的缩进。因为每个测试必须在前一个测试的回调中执行,所以通常会在屏幕右侧结束代码缩进。为了避免这种情况,可以在行开始的地方使用分号(;)字符。这使你能重置缩进级别。要确保正确关闭了所有的括号,这样它们和注释在结尾以相反的顺序列出。还有其他的方法,通过使用各种库来解决这个格式的问题。它们在更复杂的情况是有用的,但在目前这种情况下,有一个简单的线性执行流程与约定,很容易就可以保持代码相对整洁。

《移动云计算应用开发入门经典》试读电子书免费提供,有需要的留下邮箱,一有空即发送给大家。 别忘啦顶哦!

移动云计算中开发和测试用户注册服务器相关推荐

  1. 【新书推荐】《ASP.NET Core微服务实战:在云环境中开发、测试和部署跨平台服务》 带你走近微服务开发...

    <ASP.NET Core 微服务实战>译者序:https://blog.jijiechen.com/post/aspnetcore-microservices-preface-by-tr ...

  2. mysql 查询分析器中使用if_查询分析器中开发代码测试检查_MySQL

    如果您像我一样,则可能已经花费了很多时间在查询分析器中开发代码.在您对代码感到满意之后,可以立即对开发服务器上的测试数据库运行一个或两个专设 测试.如果看起来没有什么问题,您便可以将代码投入生产.如果 ...

  3. linux节点测试,linux中speedtest-cli 选择测试节点(服务器)例子

    在使用speedtest-cli进行测试的时候,有些时候speedtest识别错误.原本是国内的服务器反而选择了香港或者是美国等地区的节点,所以测试结果可能就不准确.所以我们可以通过以下的方法获取sp ...

  4. USB设备仿真框架设计指南——11.在托管代码中开发DSF应用程序

    在DSF COM对象的托管代码中开发DSF测试应用程序有多种方法.对于托管代码与COM对象进行通信,必须将COM类型导入到COM类包装器中. 您可以使用以下任何方法创建COM类包装器: 通过类型库导入 ...

  5. 敏捷开发和测试中重现缺陷和验证缺陷的解决方案(3)

    简介:在作为系列的最后一篇覆盖的部分是缺陷生命周期的最后一个环节,缺陷的验证.本文主要描述了如何通过 Rational Team Concert(RTC).Rational Quality Manag ...

  6. 香港云服务器及云计算中的虚拟化

    虚拟化?虚拟化是在远离实际硬件的层中运行计算机系统的虚拟实例的过程. 虚拟化是在大型机时代开发的.最初,它创建了现有资源的虚拟副本,从而可以扩展现有基础结构.如今,虚拟化允许多个操作系统和应用程序在同 ...

  7. 敏捷开发和测试中重现缺陷和验证缺陷的解决方案(2)

    第二步:静默录制脚本 创建好项目之后,我们就不再需要 RFT 图形界面了,而是使用静默方式录制缺陷重现脚本. 静默方式录制脚本的优点在于不需要操作者对 RFT 有太多了解.只需简单一个命令及几个按钮动 ...

  8. 在开发流程中嵌入安全测试

    ContinuumSecurity创始人Stephen de Vries,在Velocity Europe 2014大会上提出了持续且可视化的安全测试的观点.Stephen表示,那些在敏捷开发过程中用 ...

  9. C#中的高级测试驱动开发

    目录 介绍 起源 目的 开发 调试 遗留代码 示例 假装! 三角测量 多个翻译 反向翻译 文件加载 DictionaryDataSourceTest DictionaryParserTest Dict ...

  10. [配置]VUE中通过process.env判断开发,测试和生产环境,并分环境配置不同的URL HOST

    [配置]VUE中通过process.env判断开发,测试和生产环境,并分环境配置不同的URL HOST process.env是什么? process.env 是 Node.js 中的一个环境对象.其 ...

最新文章

  1. C++类与static关键字
  2. 2011年最新使用CSS3实现各种独特悬浮效果的教程
  3. python magic文档
  4. [WP8.1UI控件编程]Windows Phone自定义布局规则
  5. 在Android中自定义捕获Application全局异常,可以替换掉系统的强制退出对话框(很有参考价值与实用价值)
  6. 2019春季总结报告
  7. linux驱动开发:mma7660 sensor的配置
  8. java保护表格_java poi Excel单元格保护
  9. 名人漏网之语 --联合早报2007-01-28
  10. 谷歌放弃火狐的谷歌工具栏产品
  11. 苹果开发那些事儿-D-U-N-S 号申请
  12. CSS之display用法
  13. arcgis小班编号问题 工具箱来喽
  14. ChinaGrid要建8朵“云”
  15. IEduChina2019国际教育展在北京完美落幕
  16. 【bzoj 1812】[Ioi2005]riv(树形dp)
  17. jenkins网页打不开问题解决方法
  18. CUDA 深入浅出谈[转]
  19. c语言交通违章编程代码,C语言程序设计之交通处罚单管理系统报告(内含代码)...
  20. php中可以用于执行sql语句的函数是,在PHP中,使用()函数执行SQL语句。

热门文章

  1. 【附安装包】CorelCAD2023安装教程
  2. CPSE安博会落幕,天诚NB物联网锁期待与您再次邂逅!
  3. Java源代码注释及关键字分析程序
  4. 【OpenCV新手教程之十八】OpenCV仿射变换 amp; SURF特征点描写叙述合辑
  5. python获取当前运行程序所在目录
  6. Cadence Allegro 导出Net Single Pin and No Pin报告详解
  7. 致逝去的青春岁月节选
  8. 支持向量机 的三种境界
  9. 深入解析 TiFlash丨多并发下线程创建、释放的阻塞问题
  10. 顶尖电商网站的设计趋势