老尸 发表于 2015-2-3 23:30:05

PHP网站制作之一个典范PHP付出体系的设计与完成

左手拿着MOTOLOLA右手拿着NOKIA,要多潇洒,有多潇洒,哈哈,终于学会了,但是可能这个时候,又会有人不经意的拍拍肩膀对你说:哥们,别高兴的太早,你还是菜鸟,离学会还差着一大截呢!   
因为公司营业需求,花两周工夫完成了一个小型的付出体系,麻雀虽小五脏俱全,各类必需的模块如账户加锁,事务性包管,流水对帐等都是有完全完成的,全部开辟过程当中有良多经历堆集,再加上在网上搜刮了一下,大局部都是些研讨性的论文,对实践利用价值不大,所以此次特地拿出来和人人分享一下。
这个体系可以用作小型付出体系,也能够用做第三方使用接入开放平台时的付出流水体系。
本来的需求对照担任,我简化一点说:

[*] 对每一个使用,对外需求供应 获得余额,付出装备,充值 等接口
[*] 后台有法式,每个月一号停止清理
[*] 账户可以被解冻
[*] 需求纪录每次操作的流水,天天的流水都要和倡议方停止对账
针对下面的需求,咱们设置以下数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 CREATE TABLE `app_margin`.`tb_status` ( `appid` int(10) UNSIGNED NOT NULL, `freeze` int(10) NOT NULL DEFAULT 0, `create_time` datetime NOT NULL, `change_time` datetime NOT NULL,   PRIMARY KEY (`appid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;   CREATE TABLE `app_margin`.`tb_account_earn` ( `appid` int(10) UNSIGNED NOT NULL, `create_time` datetime NOT NULL, `balance` bigint(20) NOT NULL, `change_time` datetime NOT NULL, `seqid` int(10) NOT NULL DEFAULT 500000000,   PRIMARY KEY (`appid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;   CREATE TABLE `app_margin`.`tb_bill` ( `id` int AUTO_INCREMENT NOT NULL, `bill_id` int(10) NOT NULL, `amt` bigint(20) NOT NULL, `bill_info` text,   `bill_user` char(128), `bill_time` datetime NOT NULL, `bill_type` int(10) NOT NULL, `bill_channel` int(10) NOT NULL, `bill_ret` int(10) NOT NULL,   `appid` int(10) UNSIGNED NOT NULL, `old_balance` bigint(20) NOT NULL, `price_info` text,   `src_ip` char(128),   PRIMARY KEY (`id`), UNIQUE KEY `unique_bill` (`bill_id`,`bill_channel`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;   CREATE TABLE `app_margin`.`tb_assign` ( `id` int AUTO_INCREMENT NOT NULL, `assign_time` datetime NOT NULL,   PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;   CREATE TABLE `app_margin`.`tb_price` ( `name` char(128) NOT NULL, `price` int(10) NOT NULL, `info` text NOT NULL,   PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;   CREATE TABLE `app_margin`.`tb_applock` ( `appid` int(10) UNSIGNED NOT NULL, `lock_mode` int(10) NOT NULL DEFAULT 0, `change_time` datetime NOT NULL,   PRIMARY KEY (`appid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;   INSERT `app_margin`.`tb_assign` (`id`,`assign_time`) VALUES (100000000,now());具体注释以下:


[*] tb_status 使用的形态表。担任账户是不是被解冻,账户的类型是甚么(真实的需求是使用能够有两种账户,这里为复杂所以没有列出)

[*] appid 使用id
[*] freeze 是不是解冻
[*] create_time 创立工夫
[*] change_time 最初一次修正工夫

[*] tb_account_earn 使用的账户余额表

[*] appid 使用id
[*] balance 余额(单元为分,不要用小数存储,由于小数自己不准确;别的php要在64位机下才干撑持bigint)
[*] create_time 创立工夫
[*] change_time 最初一次修正工夫
[*] seqid 操作序列号(防并发,每次update城市+1)

[*] tb_assign 分派流水id的表,tb_bill的bill_id必需是有tb_assign分派的

[*] id 自增id
[*] create_time 创立工夫

[*] tb_bill 流水表。担任纪录每条操作流水,这里的bill_id不是主键,由于统一个bill_id能够会有付出和回滚两条流水

[*] id 自增序列号
[*] bill_id 流水号
[*] amt 操作的金额(这个是要区分正负的,次要是为了select all的时分可以直接盘算出某段工夫的金额变更)
[*] bill_info 操作的具体信息,好比3台webserver,2台db
[*] bill_user 操感化户
[*] bill_time 流水工夫
[*] bill_type 流水类型,辨别是加钱仍是减钱
[*] bill_channel 流水来历,如充值,付出,回滚,结算仍是其他
[*] bill_ret 流水的前往码,包含未处置、胜利、掉败,这里的逻辑会在前面解说
[*] appid 使用id
[*] old_balance 操作产生前的账户余额
[*] price_info 纪录操作产生时,纪录被付出物品的单价
[*] src_ip 客户端ip

[*] tb_price 单价表,纪录了机械的单价

[*] name 机械独一标识
[*] price 价钱
[*] info 描写

[*] tb_applock 锁定表,这是为了不并发对某一个使用停止写操作设计的,详细的代码会在前面展现

[*] appid 使用id
[*] lock_mode 锁定形态。为0则为锁定,为1则为锁定
[*] change_time 最初一次修正工夫

OK,库表设计出来以后,咱们就来看一下最典范的几个操作.
一. 付出操作
我这里只列出了我今朝完成的体例,能够不是最好的,但应当是最经济又知足需求的。
先说挪用方这里,逻辑以下:


然后对应的付出体系外部逻辑以下(只列出付出操作,回滚逻辑差不多,流水反省是要反省对应的付出流水是不是存在):


经常使用的毛病前往码能够以下就足够了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $g_site_error = array( -1 => '办事
器忙碌
', -2 => '数据库读取毛病
', -3 => '数据库写入毛病
',   0 => '胜利
',   1 => '没无数
据', 2 => '没有权限', 3 => '余额缺乏
', 4 => '账户被解冻
', 5 => '账户被锁定', 6 => '参数毛病
', );
[*] 关于大于0的毛病都算是逻辑毛病,履行付出操作,挪用方是不必纪录流水的。由于账户并没有产生任何改动。
[*] 关于小于0的毛病是体系外部毛病,由于不晓得是不是产生了数据更改,所以挪用方和付出体系都要纪录流水。
[*] 关于等于0的前往,代表胜利,双方也一定要纪录流水。
而在付出体系外部,之所以采取先写入流水,再停止账户更新的体例也是有缘由的,复杂来讲就是尽可能防止丧失流水。
最初总结一下,这类先扣钱,再发货,出成绩再回滚的体例是一种形式;还有一种是先预扣,后发货,没有出成绩则挪用付出确认来扣款,出了成绩就挪用付出回滚来作废,假如预扣以后很长工夫不做任何确认,那末金额会主动回滚。
二. 账户锁定的完成
这里使用了数据库的加锁机制,详细逻辑就不说了,代码以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 class AppLock { function __construct($appid) { $this->m_appid = $appid; //初始化数据 $this->get(); }   function __destruct() { $this->free(); }   public function alloc() { if ($this->m_bGot == true) { return true; }   $this->repairData();   $appid = $this->m_appid; $ret = $this->update($appid,APPLOCK_MODE_FREE,APPLOCK_MODE_ALLOC); if ($ret === false) { app_error_log("applock alloc fail"); return false; } if ($ret <= 0) { app_error_log("applock alloc fail,affected_rows:$ret"); return false; } $this->m_bGot = true; return true; }   public function free() { if ($this->m_bGot != true) { return true; }   $appid = $this->m_appid; $ret = $this->update($appid,APPLOCK_MODE_ALLOC,APPLOCK_MODE_FREE); if ($ret === false) { app_error_log("applock free fail"); return false; } if ($ret <= 0) { app_error_log("applock free fail,affected_rows:$ret"); return false; } $this->m_bGot = false; return true; }   function repairData() { $db = APP_DB();   $appid = $this->m_appid;   $now = time();   $need_time = $now - APPLOCK_REPAIR_SECS;   $str_need_time = date("Y-m-d H:i:s", $need_time);   $db->where("appid",$appid); $db->where("lock_mode",APPLOCK_MODE_ALLOC); $db->where("change_time <=",$str_need_time);   $db->set("lock_mode",APPLOCK_MODE_FREE); $db->set("change_time","NOW()",false);   $ret = $db->update(TB_APPLOCK); if ($ret === false) { app_error_log("repair applock error,appid:$appid"); return false; } return true; }   private function get() { $db = APP_DB();   $appid = $this->m_appid;   $db->where('appid', $appid);   $query = $db->get(TB_APPLOCK);   if ($query === false) { app_error_log("AppLock get fail.appid:$appid"); return false; }   if (count($query->result_array()) <= 0) { $applock_data = array( 'appid'=>$appid, 'lock_mode'=>APPLOCK_MODE_FREE, ); $db->set('change_time','NOW()',false); $ret = $db->insert(TB_APPLOCK, $applock_data); if ($ret === false) { app_error_log("applock insert fail:$appid"); return false; }   //从头
获得
数据 $db->where('appid', $appid); $query = $db->get(TB_APPLOCK);   if ($query === false) { app_error_log("AppLock get fail.appid:$appid"); return false; } if (count($query->result_array()) <= 0) { app_error_log("AppLock not data,appid:$appid"); return false; } } $applock_data = $query->row_array(); return $applock_data; }   private function update($appid,$old_lock_mode,$new_lock_mode) { $db = APP_DB();   $db->where('appid',$appid); $db->where('lock_mode',$old_lock_mode);   $db->set('lock_mode',$new_lock_mode); $db->set('change_time','NOW()',false);   $ret = $db->update(TB_APPLOCK); if ($ret === false) { app_error_log("update applock error,appid:$appid,old_lock_mode:$old_lock_mode,new_lock_mode:$new_lock_mode"); return false; } return $db->affected_rows(); }   //是不是
获得
到了锁 public $m_bGot = false;   public $m_appid; }为了避免逝世锁的成绩,获得锁的逻辑中到场了超不时间的判别,人人看代码应当就可以看懂
三. 对帐逻辑
假如依照下面的体系来设计,那末对帐的时分,只需对一下双方胜利(即bill_ret=0)的流水便可,假如完整分歧那末账户应当是没有成绩的,假如纷歧致,那就要去查询题了。
关于包管账户准确性这里,也有同事跟我说,之前在公司做的时分,是接纳只需有任何写操作之前,都先取一下贱水表中一切的流水纪录,将amt的值累加起来,看失掉的了局是不是和余额不异。假如不不异应当就是出成绩了。
1 select sum(amt) from tb_bill where appid=1;所以这也是为何我在流水表中,amt字段是要辨别正负的缘由。
OK,整篇文章写的很长,但愿对保持读完的同窗有所匡助。
一下弹出N多页面!很明显,你的留言本并没有做好安全防范,被人用JS代码小小的耍了一下,我很同情你这个时候的感受,但是没有别的办法了,继续努力吧!

飘飘悠悠 发表于 2015-2-4 00:03:03

使用 jquery 等js框架的时候,要随时注意浏览器的更新情况,不然很容易发生框架不能使用。

飘灵儿 发表于 2015-2-9 07:39:04

最后介绍一个代码出错,但是老找不到错误方法,就是 go to wc (囧),出去换换气没准回来就找到错误啦。

简单生活 发表于 2015-2-11 08:43:21

学好程序语言,多些才是王道,写两个小时代码的作用绝对超过看一天书,这个我是深有体会(顺便还能练打字速度)。

兰色精灵 发表于 2015-2-26 06:44:51

环境搭建好,当你看见你的浏览器输出“it works\\\\\\\"时你一定是喜悦的。在你解决问题的时候,我强烈建议多读php手册。

仓酷云 发表于 2015-3-6 21:40:47

首推的搜索引擎当然是Google大神,其次我比较喜欢 百度知道。不过搜出来的结果往往都是 大家copy来copy去的,运气的的概率很大。

谁可相欹 发表于 2015-3-9 04:00:12

开发工具也会慢慢的更专业,每个公司的可能不一样,但是zend studio是个大伙都会用的。

乐观 发表于 2015-3-10 21:34:27

爱上php,他也会爱上你。

只想知道 发表于 2015-3-17 10:29:58

基础有没有对学习php没有太大区别,关键是兴趣。

小魔女 发表于 2015-3-24 07:20:17

先学习php和mysql,还有css(html语言很简单)我认为现在的效果比以前的方法好。

若天明 发表于 2015-4-13 01:27:27

要进行开发,搭建环境是首先需要做的事,windows下面我习惯把环境那个安装在C盘下面,因为我配的环境经常出现诡异事件,什么事都没做环境有的时候就不能用啦。

海妖 发表于 2015-4-22 09:13:31

这些都是最基本最常用功能,我们这些菜鸟在系统学习后,可以先对这些功能深入研究。

不帅 发表于 2015-4-27 22:07:40

环境搭建好,当你看见你的浏览器输出“it works\\\\\\\"时你一定是喜悦的。在你解决问题的时候,我强烈建议多读php手册。

柔情似水 发表于 2015-5-4 02:37:56

php是动态网站开发的优秀语言,在学习的时候万万不能冒进。在系统的学习前,我认为不应该只是追求实现某种效果,因为即使你复制他人的代码调试成功,实现了你所期望的效果,你也不了解其中的原理。

爱飞 发表于 2015-6-9 03:05:04

再就是混迹于论坛啦,咱们的phpchina的论坛就很强大,提出的问题一般都是有达人去解答的,以前的帖子也要多看看也能学到不少前辈们的经验。别的不错的论坛例如php100,javaeye也是很不错的。

老尸 发表于 2015-6-27 22:23:23

使用zendstdio 写代码的的时候,把tab 的缩进设置成4个空格是很有必要的

第二个灵魂 发表于 2015-7-10 15:30:11

最后介绍一个代码出错,但是老找不到错误方法,就是 go to wc (囧),出去换换气没准回来就找到错误啦。

金色的骷髅 发表于 2015-7-16 04:42:44

我要在声明一下:我是个菜鸟!!我对php这门优秀的语言也是知之甚少。但是我要在这里说一下php在网站开发中最常用的几个功能:

小女巫 发表于 2015-7-28 02:11:14

当留言板完成的时候,下步可以把做1个单人的blog程序,做为目标,
页: [1]
查看完整版本: PHP网站制作之一个典范PHP付出体系的设计与完成