|
|
基于PHP+MySQL的Web项目开发总结
一、整体开发流程
1、需求分析
项目最重要的是了解用户需求,在此步骤中,我们要彻底的、完完全全地了解项目的方方面面。
了解客户在功能上的要求,挖掘潜在的风险。
这一步,我不准备详细展开,具体的参见相关的书籍和文档。
2、系统整体设计
本阶段将对需求分析结果进行抽象、建模。
本阶段将根据项目规模选定操作系统、服务器、数据库类型、脚本语言。针对本文,项目部署的时候建议使用LAMP(Linux+Apache+MySQL+PHP)环境,开发可在Window平台下完成。
接下来就是根据业务逻辑来做系统的整体设计了,简单通俗点来说就是数据表及字段设计、类及类字段和类方法设计、系统整体流程规划和设计。
3、功能拆分,任务分配
往往项目可能并非一个人能够在项目计划的交付时间内能够完成的,这就需要将项目拆分成为独立的子模块或子系统,交给项目组的成员分别开发。
功能拆分,任务分配是建立在系统整体设计的基础上的。
具体实施的话,还需要做一些前期准备工作,例如:编码风格、文件编码、开发工具的统一约定;基础类的编写;前提美工和后台开发人员的协商等等。
这个阶段还有一个工作就是对项目各个子系统的开发难度及开发时间和开发人力、物力的安排
4、代码编写
美工、程序员都明白各自的任务之后,即可开始编码了。
本文将第二部分对自己平时的编码做一个详细介绍。
5、整合测试
当各个子系统都完成之后,进一步就需要对他们进行整合,使其成为一个完整可用的整体。
整合测试是建立在功能拆分,任务分配的时候的那些约定的基础上的,所以第三步的事先约定是很重要的一件事情,不然的话,整合起来难度就很大,会脱项目进度的。
6、扫尾、交付
二、编码
1、先讲对开发人员知识储备的要求吧
美工一(偏图片设计类):熟练掌握或精通Photoshop等常用作图软件,能够按照项目要求在有效时间内设计出可用的原始PSD图。
美工二(偏动画设计类):熟练掌握或精通Flash使用及开发,要能够满足开发需求。
美工三(偏网页设计类):熟练掌握或精通HTML、JavaScript、DIV+CSS、以及一种Ajax框架。
程序员:熟练掌握或精通PHP+MySQL程序设计,能够熟练手写代码。对于美工三的要求需要至少需要了解,如果项目中使用框架,那务必具备在短时间内掌握该开发框架的学习能力。
2、代码编写规划(该内容只是本人为研究本人开发过程是否规范、合理而写,如模仿,请斟酌)
(1)、根据项目规划,选定编码类型,优先使用UTF-8编码
(2)、目录结构
/application 存放应用
/application/view 视图
/application/model 模型
/application/controller 控制器
/library 存放第三方库,如编辑器、模板引擎
/includes 要用到的基础方法、基本流程
config.php 配置文件
index.php 前台入口
admin.php 后台入口
(3)、Obecjt-Action设想
如果允许我去抽象的话,那我的世界简单的表示为Object-Action即可
Object代表着对象,Action代表着对象所有拥有的方法,在详细一点的话,加上一系列的条件(如对象的属性以及其他的参数,可抽象为Message)
对于Web来讲,多了一个要展示给浏览者的页面,即视图或者说模板。
一个Application对应着一个Object或者说多个Object的有机组合。每个Object的Action在对应的参数下产生一个结果(抽象为Message),我们将产生的Message去替换掉视图(View或者说是Template)中的预留的变量(或者标签)。然后输出即是我们要的结果了。
上面共涉及到下面几个内容:
Object
Action
Message(记住上面的描述里面涉及两个Message,一个数Action的条件,一个是Action动作产生的结果)
Template(也可以说是View)
这里我想到程序的最原始的描述:输入-处理过程-输出
实际上我们用Message去替换视图中的变量到渲染输出,其实也可以抽像作:输入-处理过程-输出。
(4)、单一入口原则
index.php 前台入口
admin.php 后台入口
(5)、命名规则
命名主要针对MVC三部分而言,因为对三者的调用主要是依赖于其名称的。
ObjectModel.php
class ObjectModel{
function aperate(){}
}
如:ArticleModel.php
class ArticleModel{
function add(){}
function delete(){}
function change(){}
function getArticleInformation(){}
}
AppController.php
class AppController{
function actAction(){}
}
如:ArticleController.php
class ArticleController{
function addAction(){
if(是否提交){
//未提交前显示articleAddPage.php
}else{
$article = new ArticleModel();
$article->add();
}
}
function deleteAction(){
$article = new ArticleModel();
$article->delete();
}
function changeAction(){
$article = new ArticleModel();
if(是否提交){
//未提交前需要准备原始数据
$articleInfo = $article->getArticleInformation;
//显示articleChange.php
}else{
$article->add();
}
}
}
appAct.php
如:
提交添加文章前要显示的页面名称articleAdd.php
一般提交后,都只是给一个统一message.php提示页面
(6)、URL命名规则 ./?app=appName&act=actName
如添加文章的URL为:./?app=article&act=add
对于有些URL可能并不止上面的两个参数,如编辑某条文章的链接为:./?app=article&act=change&id=1
文章列表页可能更复杂一些,如:./?app=article&act=list&page=1
如果要根据发表日期字段进行排序,则./?app=article&act=lis&page=1t&orderby=addTime
假设上面默认是降序,那升序则还要加参数./?app=article&act=list&page=1&orderby=addTime&orderType=desc
(7)、对于提交前显示一个页面,提交后显示操作结果的页面(如:addAction、updateAction等操作),采取下面的语句
function actAction(){
if(empty($_POST)){
//准备数据
//请求appAct.php页面
}else{
$this->appDo();
}
}
(8)、数据缓存设计
目的:尽量地检查对数据库的查询,对于不是经常发生变化的数据,将其以PHP变量的形式用文件保存。
(9)、模板设计:采用PHP+HTML的格式,不考虑使用模板引擎(不是绝对人家模板引擎写的不好,而是觉得没有必要)。
(10)、数据验证:前台js验证一次,后台服务脚本验证一次(防止跳过前台js验证的恶意提交)
(11)、静态化与伪静态化:对于最终页面一律实行静态化,对于首页、列表页采取伪静态化的方案,由于前面的已经对URL进行了规划,所以实施难度较低。
(12)、大访问量应对策略。
当访问量过大的时候,在程序上、数据库设计上已经考虑过众多优化后
考虑服务器、带宽、硬件(此处以后撰文讨论)。
(13)、对于一些功能尽量使用现有的成熟的开源的程序,如邮件发送模块、数据备份/还原功能、编辑器等。
(14)、页面美化、用户体验方面
(15)、HTML最终显示
务必HTML、CSS、JS脚本分开,在部署的时候考虑源文件的压缩处理
JS/Ajax首选jQuery框架
要做一个搜索引擎,考虑到自己的需求,打算重新设计一个。
首先确定以下几个配置项:抓取深度、线程数(一级线程数、二级线程数)
主要算法:
1、稍作修改的广度优先算法
2、队列操作
几个比较鸡毛的问题:
1、如何维护链接队列?这个问题远远没有你想象的那么简单,要考虑链接数目过多,占用内存的问题。
2、如何把一个相对路径的链接转化为绝对路径,如何判断一个链接是否是外链?
有时间把类图贴出来吧。
如果一个变量名(如a)已经被定义为非数组类型,例如integer,那么a可以被转为floating point、string(甚至是object类型),但不可以是数组,即a[0]=1;是错误的,php会报出这样的警告“Cannot use a scalar value as an array“。即使a被定义为一维数组,也不能转为高维数组。
个人觉得MVC模型往往将问题复杂化,对于理解和开发并无多大益处,于是提出对象-行为(Object-Action)这个更适合自己思维习惯的开发模型,并决定用几篇文章去介绍它。
几篇文章的安排如下:
1、MVC简介。
2、MVC在Web开发中的缺点。
3、Object-Action模型简介。
4、一个简单的Object-Action框架的实现及应用。
今天早晨到福州,标记一下纪念。
继续做PHP的工作。
今天,准媳妇说让我做一个细心胆大的男孩子,我一定要做到。
完成公司的工作,要把PHP进一步学精(学无止境,即便是你最最最擅长的东西,偶尔也会蹦出你陌生的东西)。
进一步优化我的Python搜索引擎。
connect()方法用于连接数据库,返回一个数据库连接对象。如果要连接一个位于host.remote.com服务器上名为fourm的MySQL数据库,连接串可以这样写:
db = MySQLdb.connect(host=”remote.com”,user=”user”,passwd=”xxx”,db=”fourm” )
connect()的参数列表如下:
host,连接的数据库服务器主机名,默认为本地主机(localhost)。
user,连接数据库的用户名,默认为当前用户。
passwd,连接密码,没有默认值。
db,连接的数据库名,没有默认值。
conv,将文字映射到Python类型的字典。默认为MySQLdb.converters.conversions
cursorclass,cursor()使用的种类,默认值为MySQLdb.cursors.Cursor。
compress,启用协议压缩功能。
named_pipe,在windows中,与一个命名管道相连接。
init_command,一旦连接建立,就为数据库服务器指定一条语句来运行。
read_default_file,使用指定的MySQL配置文件。
read_default_group,读取的默认组。
unix_socket,在unix中,连接使用的套接字,默认使用TCP。
port,指定数据库服务器的连接端口,默认是3306。
连接对象的db.close()方法可关闭数据库连接,并释放相关资源。
连接对象的db.cursor([cursorClass])方法返回一个指针对象,用于访问和操作数据库中的数据。
连接对象的db.begin()方法用于开始一个事务,如果数据库的AUTOCOMMIT已经开启就关闭它,直到事务调用commit()和rollback()结束。
连接对象的db.commit()和db.rollback()方法分别表示事务提交和回退。
指针对象的cursor.close()方法关闭指针并释放相关资源。
指针对象的cursor.execute(query[,parameters])方法执行数据库查询。
指针对象的cursor.fetchall()可取出指针结果集中的所有行,返回的结果集一个元组(tuples)。
指针对象的cursor.fetchmany([size=cursor.arraysize])从查询结果集中取出多行,我们可利用可选的参数指定取出的行数。
指针对象的cursor.fetchone()从查询结果集中返回下一行。
指针对象的cursor.arraysize属性指定由cursor.fetchmany()方法返回行的数目,影响fetchall()的性能,默认值为1。
指针对象的cursor.rowcount属性指出上次查询或更新所发生行数。-1表示还没开始查询或没有查询到数据。
下面是来自网上的一个示例:
#-*- encoding: gb2312 -*-
import os, sys, string
import MySQLdb
# 连接数据库
try:
conn = MySQLdb.connect(host=’localhost’,user=’root’,passwd=’xxxx’,db=’test1′)
except Exception, e:
print e
sys.exit()
# 获取cursor对象来进行操作
cursor = conn.cursor()
# 创建表
sql = “create table if not exists test1(name varchar(128) primary key, age int(4))”
cursor.execute(sql)
# 插入数据
sql = “insert into test1(name, age) values (‘%s’, %d)” % (“zhaowei”, 23)
try:
cursor.execute(sql)
except Exception, e:
print e
sql = “insert into test1(name, age) values (‘%s’, %d)” % (“张三”, 21)
try:
cursor.execute(sql)
except Exception, e:
print e
# 插入多条
sql = “insert into test1(name, age) values (%s, %s)”
val = ((“李四”, 24), (“王五”, 25), (“洪六”, 26))
try:
cursor.executemany(sql, val)
except Exception, e:
print e
#查询出数据
sql = “select * from test1″
cursor.execute(sql)
alldata = cursor.fetchall()
# 如果有数据返回,就循环输出, alldata是有个二维的列表
if alldata:
for rec in alldata:
print rec[0], rec[1]
cursor.close()
conn.close()
日,搞了好一会也没有安装好,NND。
终于搞定了。写下步骤,特作纪念:
平台:Windows XP + Python 2.6
不成功的过程:
下载了http://nchc.dl.sourceforge.net/project/mysql-python/mysql-python-test/1.2.3c1/MySQL-python-1.2.3c1.tar.gz解压执行的时候说setuptools不存在,然后哥就去下载来了http://www.python.org/ftp/python/2.6.4/python-2.6.4.msi
安装好了,可是有是乱七八糟的问题,网上似乎有用这种办法安装成功过,不过那个教程中关键的一个链接打不开,更无语地是SB转帖几乎清一色的不看贴,NND,都照转不误,真TMD的搞啊,一词,无语。
这个不成功的安装过程至此结束(我想连哥都安装不下去了,Win平台下看官还是尝试下面一种办法吧)。
不过这个setuptools还是建议大家安装一下,比较有用的以modules。
下载http://home.netimperia.com/files/misc/MySQL-python-1.2.2.win32-py2.6.exe安装。
okay,貌似是没有什么问题。
那我们看一下现在MySQLdb模块是否可以使用。
在python解释器中输入:import MySQLdb
事与愿违:一个Traceback
IDLE 2.6.4
>>> import MySQLdb
Traceback (most recent call last):
File "
", line 1, in
import MySQLdb
File "D:\Python26\lib\site-packages\MySQLdb\__init__.py", line 19, in
import _mysql
ImportError: DLL load failed: 找不到指定的模块。
我们根据前人的经验去下载两个dll文件回来(用下面关键字在G或B上查下,有的)。然后复制到
D:\Python26\Lib\site-packages
libmmd.dll libguide40.dll MySQLdb
接下来
>>> import MySQLdb
Warning (from warnings module):
File "D:\Python26\lib\site-packages\MySQLdb\__init__.py", line 34
from sets import ImmutableSet
DeprecationWarning: the sets module is deprecated
这次我们做如下修改(下面文件都是在D:\Python26\lib\site-packages\MySQLdb目录下)
1) 把文件 “__init__.py”中的
from sets import ImmutableSet
class DBAPISet(ImmutableSet):
替换为:
class DBAPISet(frozenset)
2) 删除文件“converters.py”中下面一行语句
from sets import BaseSet, Set
3) 文件 “converters.py”中
替换 "Set" 为 "set" (IMPORTANT: only two places):
line 48: return set([ i for i in s.split(',') if i ])
line 128: set: Set2Str,
测试import MySQLdb
么得错误了
NND,终于搞定,睡觉去。
我学习及使用的是3.1版本的解释器,发现教程以及一些低版本的代码在该解释器下报错,下面是一些改进办法。
1、以前的代码支持形如print __doc__这种格式的输出,新版本的解释器在这种情况下会报错。我们只需加上两个小括号即可,如print(__doc__)
2、import Tkinter语句在解释的时候会提示说Tkinter的module不存在。原因很简单,新版本的解释器中这个包的名称发生变化了,可修改为:import tkinter as Tkinter(幸亏python支持as语法,否则修改起来就辛苦了)
3、形如import tkModuleName(如import tkColorChoose、import tkFileDialog等)语句会出现名为xxx的模块不存在的错误,对此我们要找到该模块是属于那个上级模块,现在该模块名称是什么,然后修改,修改格式为:from supermodulename import modulename as tkModuleName(如from tkinter import colorchoose as tkColorChoose、from tkinter import filedialog as tkFileDialog)。
4、在1.x或2.x版本中许多标准python模块到3.1之后就被拖到test模块中去了,很郁闷。比如urllib、urllib2等,补救办法:import test.test_urllib as urllib、import test.test_urllib2 as urllib2,呃,真不晓得以后版本升级之后这些模块会不会莫名其妙地消失。
Dictionary是python的内置数据类型之一,它定义可键和值之间一对一的关系。
在python中,变量可以任意取名,并且python在内部会记录下其数据类型
se={‘name’:'Search Engine’, ‘author’:'Jiang Ming’, ‘version’:'0.01′}
其中name是一个key,它所关联的值是通过se['name']来引用的,为’Search Engine’
一个dictionary中不能有重复的key。给一个存在的key赋值会覆盖原有的值。
在任何时候都可以加入新的key-value对。
dictionary没有元素顺序的概念。说元素“顺序乱了”是不正确的,它们只是序偶的简单排序。这是一个重要的特性,它会在您想要一种特定的,可重现的顺序像以key的字母表顺序存取dictionary元素的时候骚扰您。有一些实现这些要求的方法,它们只是没有加到dictionary中去。
注意,dictionary中的key是大小写敏感的。
为一个已存在的dictionary key赋值,将简单覆盖原有的值
dictionary不只是用于存储字符串。dictionary的值可以是任意数据类型,包括字符串、整数、对象,甚至其他的dictionary。在单个dictionary里,dictionary的值并不需要全都是同一数据类型。可以根据需要混用和匹配
从dictionary中删除元素
del se['version']#删除se中key值为version的键值对
se.clear()#清除se中所有键值对
se.pop(‘name’)#删除se中key值为name的键值对
se.popitem()#删除第一个键值对,但是对于到底那一个键值对是第一组键值对是第一组尚无明确的排序规则
List是python中使用最为频繁的数据类型。
在python中,变量可以任意取名,并且python内部会记录下其数据类型
li=['a','b','c','d','e']
上面定义了一个包含5个元素的数组。
list可以作为以0下标开始的数组,任何一个非空的list的第一个元素总是li[0]
负数索引从list的尾部开始向前计数来存取元素。任何一非空list最后一个元素总是li[-1]
li[1:5:2]的意思是从索引为1(注意开始的索引为0)的元素开始,每2个索引取一次值,直到索引5为止(不包括索引5)所构成的子集。其结果是['b','d']
向list中增加元素
li.append(‘new’)#向list的末尾追加单个元素
li.insert(1,’new’)#将单个元素插入到list中。数值参数是插入点的索引。list中的元素不必唯一,现在有两个独立的元素具有new这个值
li.extend(['two','elements'])#用来链接list。
他们的运行结束如下
>>> li=['a','b','c','d','e']
>>> li[1:5:2]
['b', 'd']
>>> li.append(‘new’)
>>> li
['a', 'b', 'c', 'd', 'e', 'new']
>>> li.insert(2,’new’)
>>> li
['a', 'b', 'new', 'c', 'd', 'e', 'new']
>>> li.extend(['two','elements'])
>>> li
['a', 'b', 'new', 'c', 'd', 'e', 'new', 'two', 'elements']
>>>
list的两个方法extend和append看起来类似,但实际上完全不同。extend接受一个参数,这个参数本身要求是一个list型的,并且把这个list中的每个元素添加到原list中去
搜索list:index在list中查找一个值的首次出现并返回索引值。如果在list中没有找到值,python会引发一个异常。
要测试一个值是否在list内,使用in,如果值存在,他返回True,否则返回False
>>> li
['a', 'b', 'new', 'c', 'd', 'e', 'new', 'two', 'elements']
>>> li.index(‘a’)
0
>>> li.index(‘new’)
2
>>> li.index(‘ddd’)
Traceback (most recent call last):
File “
“, line 1, in
li.index(‘ddd’)
ValueError: list.index(x): x not in list
>>> ‘a’ in li
True
>>> ” in li
False
>>>
由于li中不存在值为’ddd’的元素,所以诱发了一个异常
从list中删除元素:remove从list中删除一个值的首次出现。如果list中没有找到这个值,python将会引发一个异常来相应remove操作。如果要删除的元素在list中有多处,则只删除第一处的。
pop模拟了堆的出栈操作,删除list的最后一个元素,并返回。
>>> li
['a', 'b', 'new', 'c', 'd', 'e', 'new', 'two', 'elements']
>>> li.remove(‘a’)
>>> li
['b', 'new', 'c', 'd', 'e', 'new', 'two', 'elements']
>>> li.remove(‘new’)
>>> li
['b', 'c', 'd', 'e', 'new', 'two', 'elements']
>>> li.remove(‘a’)
Traceback (most recent call last):
File “
“, line 1, in
li.remove(‘a’)
ValueError: list.remove(x): x not in list
>>> li.pop()
‘elements’
>>> li
['b', 'c', 'd', 'e', 'new', 'two']
上例中,将元素a做第二次删除的时候引发了一个错误
li中值为new的元素有两个,做remove(‘new’)操作之后第二个new依然存在
另外还有几个方法:
li.count(‘a’)统计list中’a'元素的次数
li.reverse()将li逆序排列
li.sort()对li排序
Tuple是不可变的list。一旦创建了一个tuple,就不能以任何方式改变它。
tuple提供count和index方法,用法与list一样
tuple的不能做改变结构类型的操作,其他的如引用、分片用法与list一样。
EP-Blog的角色管理主要包括两部分:用户管理、用户组管理。
用户管理较为简单,无非常规的CURD,重点放在用户组管理上。
用户组涉及权限分配,添加一个新的分组包括一下内容的设置,主要分四大项:基本信息、浏览权限、撰写权限和管理权限。
基本信息中只需填写分组名称。
浏览权限需要设置网站、文章、相册、音乐、留言、链接等项目是否允许浏览。
撰写权限需要设置是否允许添加文章、相册、音乐、留言、链接及他们的分组等项目是否允许添加。
管理权限需要设置是否允许管理各个项目。
对于信息的存储,用户需数据表存储,对于用户组的存储,有两种方案一种存储在数据库中,另一种以PHP变量的格式用PHP文件存储。
在Bo-Blog中用户组的信息便是以PHP文件存储的,毕竟作为一个单用户博客而言用户组并不多。
但是考虑到EP-Blog的实用性以及二次开发,加上EP-Blog中优秀的缓存功能,我们还是以数据库的形式存储。
本文至此结束。
|
|
最近评论