6
十一/11
1

用Drupal快速实现mobile平台服务端

用Drupal很容易实现一个API,让手机平台或者其他系统使用json的格式进行通信。
<?php
define('API_ERRORNO_INVALID_ACTION', 1);
define('API_ERRORNO_INVALID_PARAM', 2);
define('API_ERRORNO_NO_RECORD', 3);
define('API_ERRORNO_INVALID_NAME', 4);
define('API_ERRORNO_USER_BLOCKED', 5);
define('API_ERRORNO_LOGIN_FAILED', 6);

// 根据错误代码返回错误信息
function api_message_wrapper($errno) {
  $message = array(
    API_ERRORNO_INVALID_ACTION => '非法的请求动作',
    API_ERRORNO_INVALID_PARAM => '非法的请求参数',
    API_ERRORNO_NO_RECORD => '查找不到数据',
    API_ERRORNO_INVALID_NAME => '非法的手机号码',
    API_ERRORNO_USER_BLOCKED => '当前用户已经被禁用',
    API_ERRORNO_LOGIN_FAILED => '登陆失败',
  );
  return isset($message[$errno]) ? $message[$errno] : '未知的错误';
}

// 验证合法的action
function api_action_route($action) {
  $valid_actions = array(
      'login' => 'login',
      'register' => 'register',
      'getCardInfos' => 'get_card_info',
  );
  return isset($valid_actions[$action]) ? $valid_actions[$action] : false;
}

function api_menu() {
  $items['api'] = array(
    'page callback' => 'api_dispatch',
    'access callback' => TRUE,
    //'access arguments' => array('使用API'),
    'type' => MENU_CALLBACK,
    'file' => 'api.functions.inc',
  );
  return $items;
}

// 成功返回
function api_response_sucess($data) {
  drupal_json(array('ERRNO' => 0, 'DATA' => $data));
}

// 失败返回
function api_response_error($errno) {
  if ($errno && is_numeric($errno)) {
    drupal_json(array(
      'ERRNO' => $errno,
      'MSG' => api_message_wrapper($errno),
    ));
  }
}

// API调度入口
function api_dispatch() {
  if (!isset($_POST['ACTION']) || !api_action_route($_POST['ACTION'])) {
    api_response_error(API_ERRORNO_INVALID_ACTION);
  }
  elseif (!isset($_POST['PARAM'])) {
    api_response_error(API_ERRORNO_INVALID_PARAM);
  }
  else {
    $param = json_decode($_POST['PARAM']);
    call_user_func('api_call_'.  api_action_route($_POST['ACTION']), $param);
  }
}
关键字:
23
十二/10
0

Drupal编程总结

从2007年末接触Drupal开始,已经3年时间了。Drupal已经从5.x版本升级到了7.x,模块开发也发生了很多变化。

1. 在查询数据库的时候,考虑到一些特殊情况尽量使用静态变量,这相当于drupal的第一层缓存,在drupal的很多load相关函数中到处可见。比如:

function terrysco_get_group($lgid, $reset = false) {

// 使用静态变量做缓存

static $linkedin = array();

if ($reset) {

$terrysco = array();

}

if (!isset($terrysco[$lgid]) && is_numeric($lgid)) {

$terrysco[$lgid] = db_fetch_array(db_query(‘SELECT * FROM {linkedin_groups} WHERE lgid = %d’, $lgid));

}

return isset($terrysco[$lgid]) ? $terrysco[$lgid] : false;

}

这样,在要处理一个大的结果集的时候,在循环中调用该函数,就不会每次循环都查询数据库了。

2. 深入理解drupal的hook机制,用好module_invoke_all来设计你的模块机制,大大增强扩展性。其他方面,多想想怎么和别的模块工作的更好,不要进行hack的工作,要给别的模块留下更改你输出主题,更改你返回数据的机会。

3. 多使用表单给用户进行参数配置,将这些配置用variable_set存起来随时使用,生成配置表单的时候可以使用system_settings_form()将表单数组包装起来,省去了写提交函数的部分。另外,将一些数据分析后可以适当存储在session或者cookie中,不一定非要写数据库。

4. 不要过于依赖node机制。node给我们提供了很多方便,很多第三方模块装上后就能很好的工作,比如fivestar,views之类的东西,但也要考虑实际项目需要。很多小的数据如果量很大,不建议放在node表中。而且node表以nid作为auto_increment的primary key,给我们日后做扩展留下了很多麻烦。

5. 不要过于依赖第三方模块。第三方模块的一个共同点就是功能强大,代码质量还算不错,但是正因为如此,模块开发者考虑到的是通用性,一个大的模块可能很多功能我们使用不了,这时候就不要偷懒了,自己动手吧。而且很多第三方模块是有问题的,自己细心研究下就能发现很多。

6. Drupal本身支持快速切换数据库,这就使得我们可以在开始的时候就做好设计,看看是否将某些业务分离。当然,如果是中小型的项目就不用了。

未完待续。。。

关键字:
11
十二/09
2

关于drupal架构的思考

自从drupalchina推荐我的blog后,很多drupal的开发者和我讨论drupal的性能,drupal的缺陷,大有感触。

很多人都说drupal站点不适合扩展,或者简单的做到数据库和代码分离后就茫然了。最近参考了很多软件和web架构的书,反过来对drupal这个系统做了一些关于架构扩展的思考。

以自己现有水平来看,drupal的瓶颈似乎在PHP代码执行层面。网上说使用APC加速可以大大加强drupal的性能,自己还没有手动进行测试,所以没有发言权。但目前在自己使用过程中,还是看到drupal一些小尴尬。

drupal的缓存系统比较简单,只是对匿名用户(因为匿名用户访问页面变更比较少)进行页面缓存,并且是以数据库的形式进行缓存。直接序列化页面到cache_page表中,这种性能可想而知,如果对百万级的访问量来说,db将会是个噩梦。

drupal用apache的rewrite模块实现了简洁链接,伪静态页面是提高了SEO效果,但是针对生成静态页面来说,还是比较困难的。另外,drupal的代码可以更加精简,否则为了强大的扩展性损失大幅性能也是不可取的。

有人说代码和架构扩展没有关系(忘记哪位大牛说的),其实我不是很赞同这句话,代码和数据库刚开始的设计决定或者制约了很多后面需要扩展的条件,比如水平划分数据库表的时候,最好是选取primary key,但是这个字段最好不是auto_increment的(道理很容易想到),再比如数据库抽象层的代码如果做memcache缓存,好修改吗?

很多时候,代码级别制约扩展性的很大一个因素就是联合查询,因为在做垂直划分数据库的时候,分离的都是一些关联性不大,并且不会牵扯到联合查询的库表,但drupal很多地方都使用的inner join(几乎每个模块都能看到)。当用户并发数越来越大,服务器最大的压力来源于数据库,这时,有些朋友就说了”负载均衡“啊。

没有那么简单,首先我们考虑基于重定向的负载均衡,多做几个后端服务器,使用RR或者简单随即抽取机制来轮流使用服务器,drupal和其他站点不同的一点小小特性就是唯一入口index.php,也就是说在这个文件我们可以写入一些代码,实现重定向的负载均衡,这点是比较合适的。或者您为了避免不是真正的压力平衡,可以使用DNS负载均衡,这样下来,稍微大点的数据量我们勉强顶得住了。

从水平划分的方面来看,首先最大的表应该是drupal的node表,nid字段是primary key,并且auto_increment的,这就很难进行分表存储。另外就算使用了一些别的算法,分N个表出来存储node,问题是如果使那些众多的node相关操作代码使用不会出问题?

另外一种做法就是使用ngnix的反向代理功能,很多java的开发者叫分布式。就算后端服务器性能差异,我们还是可以通过配置文件来决定权重,能者多得的方式,让每个机器发挥到极限。还可以考虑的方式就是使用apache加载我们定义的lua脚本,匹配select和insert,update,delete等字符串,用来区分属于何种sql查询,进一步实现读写分离。不过在机器之间的数据同步也是个麻烦的事情,我没有在运维方面有多少经验,最多就是使用过rsync。

分析种种情景,使用drupal的用户不要担心,只要我们想的到,可扩展的办法还是很多的。性能决定在你自己手里。

关键字:
16
十/09
0

给drupal批量导入外部数据

很多时候,我们想给自己的drupal站点批量导入节点数据,其中包括很多方法,例如csv格式文件的导入,通过别的站点抓取到得信息,整合别的系统时,相互数据的迁移都要用到这些内容。我们建立一个简单的模型,只考虑节点基本数据,不考虑其他因素(比如节点图片,节点术语分类等等),将模型简单化有助于我们解决问题。

以拿抓取页面为例,我们使用正则表达式过滤出需要的信息后,将其存放在一个一维关联数组中,其中键对应数据库中的节点字段,比如title,body,name。。。然后我们手动调用node_submit方法,将一维数组传进去,这个方法会对我们的数组做一些存表之前的工作,我们直接可以在api.drupal.org查看这个方法的代码。

<?php
function node_submit($node) {
  global $user;

  // Convert the node to an object, if necessary.
  $node = (object)$node;

  // Auto-generate the teaser, but only if it hasn't been set (e.g. by a
  // module-provided 'teaser' form item).
  if (!isset($node->teaser)) {
    $node->teaser = isset($node->body) ? node_teaser($node->body, isset($node->format) ? $node->format : NULL) : '';
  }

  if (user_access('administer nodes')) {
    // Populate the "authored by" field.
    if ($account = user_load(array('name' => $node->name))) {
      $node->uid = $account->uid;
    }
    else {
      $node->uid = 0;
    }
  }

  $node->created = !empty($node->date) ? strtotime($node->date) : time();

  // Do node-type-specific validation checks.
  node_invoke($node, 'submit');
  node_invoke_nodeapi($node, 'submit');

  $node->validated = TRUE;

  return $node;
}
?>

除了一些数据的可用性检查外,该方法调用了所有实现submit和nodeapi中实现submit的函数,之后就简单了,再调用node_save即可。

关键字:
4
八/09
1

剖析ubercart

ubercart是一套很好的搭建电子商务平台代码,被很好的以模块形式应用到了drupal上,接合drupal的强大功能很容易生成电子商务站点。

下载了最新版本的模块看了看,代码量确实巨大。要先学会怎么用,再去看代码我想工作效率会更高,所以启用模块建了几个产品试试,购物车和付费平台什么的功能都走通了之后开始挑几个重要的代码段看了看。因为我要实现一个充值后增加userpoints积分的功能,找了半天终于找到一个相关模块,但是问题很多,issue页面也有人提出了类似的问题。没办法只能靠自己了,了解product模块和feature相关特性后,很轻松的就找到了问题所在。大家需要类似功能的可以参考下。

http://drupal.org/node/514534

解决办法在评论里面,不过我没有测试,只是看到代码流程没有问题了。首先建立一个feature,将产品节点绑定到这个feature里面,再实现order这个hook,在里面判断当订单完成状态后,使用userpoints模块的接口给购买用户增加分数。至于points的分类和数目完全可以自己在后台设置,还是不错的功能,只是纳闷作者为什么这么粗心,错了一个变量,错了一个表名。

关键字: