该版本新增了发表评论功能,在install/create_table.zl脚本中,新增了评论相关的表结构,游客可以在前台访问文章页面时发表评论。评论发表成功后,需要进行审核,在后台新增了评论列表,用于对评论进行审核...

    页面导航: 项目下载地址:

    zenglBlog源代码的相关地址:https://github.com/zenglong/zenglBlog  当前版本对应的tag标签为:v0.5.0

zenglBlog v0.5.0:

    该版本新增了发表评论功能,在install/create_table.zl脚本中,新增了评论相关的表结构:

......................................................

// 创建评论表
if(mysqlQuery(con, "DROP TABLE IF EXISTS comment"))
	finish_with_error(con);
endif
if(mysqlQuery(con, "CREATE TABLE comment(
	id int NOT NULL AUTO_INCREMENT, 
	nickname varchar(80) NOT NULL DEFAULT '' COMMENT '评论人昵称',
	aid int NOT NULL DEFAULT '0' COMMENT '评论所属文章ID',
	pid int NOT NULL DEFAULT '0' COMMENT '回复所属的评论ID',
	content varchar(3000) NOT NULL DEFAULT '' COMMENT '评论内容',
	status tinyint NOT NULL DEFAULT '0' COMMENT '评论状态:0表示待审核,1表示审核通过,2表示审核不通过',
	reject_reason varchar(100) NOT NULL DEFAULT '' COMMENT '拒绝理由(不通过理由)',
	`created_at` timestamp NULL DEFAULT NULL COMMENT '创建时间',
	PRIMARY KEY (id)
	) ENGINE=MyISAM DEFAULT CHARSET utf8 COLLATE utf8_general_ci COMMENT='评论表'"))
	finish_with_error(con);
endif

......................................................


    需要先运行该脚本创建评论表,才可以使用评论相关的功能。如果之前创建过用户之类的表结构,可以将这些表结构的创建语句和相关的数据插入语句注释掉,再将install目录中的install.lock锁文件删除掉,再运行此脚本,就可以只创建评论表。评论表中的pid字段目前暂时没使用。

    游客可以在前台访问文章页面时发表评论。评论发表成功后,需要进行审核,在后台新增了评论列表,用于对评论进行审核。



图1:前台文章页面发表评论按钮
 


图2:点击发表评论按钮,弹出的发表评论对话框

    在评论对话框中,需要输入评论人昵称,评论内容,以及图形验证码。在输入了评论信息,同时输入了正确的图形验证码后,点击提交,就可以发表评论:



图3:发布评论成功

    评论发表后,还需要在后台评论列表中对这些评论进行审核:



图4:后台评论列表
 


图5:评论列表的操作栏

    在后台评论列表中,可以看到评论所属的文章标题,评论人昵称,评论内容,评论状态,发表时间等。在评论列表的操作栏,点击第一个眼睛图标会跳转到评论所属的文章页面,点击第二个图标可以审核通过评论,点击最后一个图标可以审核不通过某条评论,并附加上审核不通过的原因。

    在后台审核通过某条评论后,该评论就会出现在前台文章页面的评论列表中:



图6:前台文章页面的评论列表

    和后台评论列表相关的脚本为 admin/comment.zl:

inc 'common.zl';
inc 'helper.zl';

querys = rqtGetQuery();
action = querys['act'] ? querys['act'] : 'list';

if(action == 'list')
	Comment.list();
elif(action == 'pass')
	Comment.pass();
elif(action == 'reject')
	Comment.reject();
else
	print 'invalid act';
endif

class Comment
	// 评论列表
	fun list()
		global menus, querys;
		data['title'] = '评论列表:';
		setCurMenu(menus, 'comment.zl');
		data['menus'] = menus;
		data['csses', 0] = 'comment_list.css';
		db = Comment.initDB();
		where = " where 1 ";
		stitle = bltStr(&querys['stitle']);
		if(stitle) // 根据标题搜索
			where += ' and a.title like "%' + Mysql.Escape(db, stitle) + '%"';
			data['stitle'] = stitle;
		endif

		snickname = bltStr(&querys['snickname']);
		if(snickname) // 根据昵称搜索
			where += ' and c.nickname like "%' + Mysql.Escape(db, snickname) + '%"';
			data['snickname'] = snickname;
		endif

		if(bltIsNone(&querys['search_status']))
			data['search_status'] = -1;
		else
			search_status = bltInt(querys['search_status']);
			if(search_status >= 0) // 根据状态搜索
				where += ' and c.status = ' + search_status;
			endif
			data['search_status'] = search_status;
		endif

		tmp = Mysql.fetchOne(db, "select count(1) as cnt from comment c inner join article a on a.id=c.aid "  + where);
		total = bltInt(tmp['cnt']);
		page = get_page(10, total, 10);
		for(i=page['start']; i <= page['end'];i++)
			cur = (page['curpage'] == i) ? ' class="active"' : '';
			pages[] = '<li'+cur+'><a href="'+page['link']+'page='+i+'">'+i+'</a></li>';
		endfor
		data['page'] = page;
		data['pages'] = pages;
		data['comments'] = Mysql.fetchAll(db, "select c.id, c.nickname, c.content, c.status, c.reject_reason, c.created_at, c.aid, a.title from comment c " +
					" inner join article a on a.id=c.aid " + where + " order by c.id desc limit " + page['offset'] + "," + page['limit']);
		for(i=0;bltIterArray(data['comments'],&i,&v);)
			switch(v['status'])
			case 0:
				v['status_text'] = '<span class="label label-default">待审核</span>';
				break;
			case 1:
				v['status_text'] = '<span class="label label-success">审核通过</span>';
				break;
			case 2:
				v['status_text'] = '<span class="label label-danger">审核不通过</span>';
				break;
			default:
				v['status_text'] = '<span class="label label-warning">未知状态</span>';
			endswitch
			bltHtmlEscape(&v['content'], TRUE);
			bltStrReplace(&v['content'], '\n', '<br/>', TRUE);
		endfor
		print bltMustacheFileRender("tpl/comment_list.tpl", data);
	endfun

	// 初始化数据库连接
	fun initDB()
		global config;
		db = bltArray();
		Mysql.init(db, config, "tpl/error.tpl");
		return db;
	endfun

	// 审核通过
	fun pass()
		global querys;
		db = Comment.initDB();
		id = querys['id'] ? bltInt(querys['id']) : 0;
		if(id <= 0) 
			ajax_return('无效的评论id');
		endif
		Mysql.Exec(db, "UPDATE comment SET status=1,reject_reason='' WHERE id=" + id);
		ajax_return();
	endfun

	// 拒绝,审核不通过
	fun reject()
		posts = rqtGetBodyAsArray();
		db = Comment.initDB();
		id = posts['id'] ? bltInt(posts['id']) : 0;
		if(id <= 0)
			ajax_return('无效的评论id');
		endif
		data['reject_reason'] = bltStr(&posts['reject_reason']);
		data['status'] = 2;
		Mysql.Update(db, 'comment', data, " id=" + id);
		ajax_return();
	endfun
endclass


    和前台发表评论以及显示评论列表相关的脚本为根目录中的 comment.zl:

use builtin, request, mysql, session;
def TRUE 1;
def FALSE 0;
inc 'config.zl';
inc 'mysql.zl';

querys = rqtGetQuery();
action = querys['act'] ? querys['act'] : 'list';
if(action == 'list')
	Comment.list();
elif(action == 'add')
	Comment.add();
else
	print 'invalid act';
endif

class Comment
	// 显示评论列表
	fun list()
		global querys;
		page = bltInt(querys['page']);
		if(page <= 0)
			page = 1;
		endif
		per_page = 10;
		offset = (page - 1) * per_page;
		aid = bltInt(querys['aid']);
		db = Comment.initDB();
		data['comments'] = Mysql.fetchAll(db, "select id,nickname,content,created_at from comment where status=1 and aid="+aid+" order by id desc limit " + offset + ', ' + per_page);
		for(i=0;bltIterArray(data['comments'],&i,&v);)
			bltHtmlEscape(&v['nickname'], TRUE);
			bltHtmlEscape(&v['content'], TRUE);
		endfor
		data['per_page'] = per_page;
		data['msg'] = 'success';
		rqtSetResponseHeader("Content-Type: application/json");
		print bltJsonEncode(data);
	endfun

	// 添加评论
	fun add()
		global sess_id;
		cookies = rqtGetCookie();
		sess_id = cookies['SESSION'];
		sess_data = sessGetData(sess_id);
		posts = rqtGetBodyAsArray();
		bltStr(&posts['captcha'], TRUE);
		bltStr(&sess_data['captcha'], TRUE);
		if(sess_data['captcha'] && sess_data['captcha'] == posts['captcha'])
			insert_data['nickname'] = bltStr(&posts['nickname']);
			if(insert_data['nickname'] == '')
				Comment.error('昵称不能为空!');
			endif
			insert_data['content'] = bltStr(&posts['content']);
			if(insert_data['content'] == '')
				Comment.error('内容不能为空');
			endif
			if(bltUtfStrLen(insert_data['content']) < 5)
				Comment.error('内容太短');
			endif
			if(bltUtfStrLen(insert_data['content']) > 500)
				Comment.error('内容太长');
			endif
			db = Comment.initDB();
			insert_data['aid'] = bltInt(posts['aid']);
			article_data = Mysql.fetchOne(db, "select id from article where id='"+insert_data['aid']+"'");
			if(!bltCount(article_data))
				Comment.error('无效的文章ID');
			endif
			insert_data['created_at'] = bltDate('%Y-%m-%d %H:%M:%S');
			Mysql.Insert(db, 'comment', insert_data);
			data['msg'] = 'success';
			rqtSetResponseHeader("Content-Type: application/json");
			print bltJsonEncode(data);
		else
			Comment.error('无效的图形验证码');
		endif
	endfun

	// 初始化数据库连接
	fun initDB()
		global config;
		db = bltArray();
		Mysql.init(db, config, "tpl/error.tpl");
		return db;
	endfun

	fun error(errmsg)
		global sess_id;
		data['msg'] = 'failed';
		data['errmsg'] = errmsg;
		rqtSetResponseHeader("Content-Type: application/json");
		print bltJsonEncode(data);
		sessDelete(sess_id);
		bltExit();
	endfun
endclass


    和生成图形验证码相关的脚本为根目录中的 captcha.zl:

use builtin, magick, request, session;
def TRUE 1;
def FALSE 0;

wand = magickNewWand();
p_wand = magickNewPixelWand();

magickPixelSetColor(p_wand, "white");
magickNewImage(wand, 85, 30, p_wand);
d_wand = magickNewDrawingWand();
// magickDrawSetFont(d_wand, "Helvetica Regular");
magickDrawSetFont(d_wand, "assets/font/xerox_serif_narrow.ttf");
magickDrawSetFontSize(d_wand, 24);
magickDrawSetTextAntialias(d_wand, TRUE);
// magickDrawSetTextAntialias(d_wand, FALSE);
captcha = bltRandomStr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 6);
magickDrawAnnotation(d_wand, 4, 20, captcha);
magickDrawImage(wand, d_wand);
magickSwirlImage(wand, 20);

magickClearDrawingWand(d_wand);
magickDrawLine(d_wand, bltRand( 0, 70 ), bltRand( 0, 30 ), bltRand( 0, 70 ), bltRand( 0, 30 ));
magickDrawLine(d_wand, bltRand( 0, 70 ), bltRand( 0, 30 ), bltRand( 0, 70 ), bltRand( 0, 30 ));
magickDrawLine(d_wand, bltRand( 0, 70 ), bltRand( 0, 30 ), bltRand( 0, 70 ), bltRand( 0, 30 ));
magickDrawLine(d_wand, bltRand( 0, 70 ), bltRand( 0, 30 ), bltRand( 0, 70 ), bltRand( 0, 30 ));
magickDrawLine(d_wand, bltRand( 0, 70 ), bltRand( 0, 30 ), bltRand( 0, 70 ), bltRand( 0, 30 ));

magickDrawImage(wand, d_wand);

magickSetImageFormat(wand, "jpg");
output = magickGetImageBlob(wand, &length);
rqtSetResponseHeader("Content-Type: image/" + magickGetImageFormat(wand));
cookies = rqtGetCookie();
sess_id = cookies['SESSION'];
if(!sess_id)
	sess_id = sessMakeId();
	rqtSetResponseHeader("Set-Cookie: SESSION="+sess_id+"; path=/");
else
	data = sessGetData(sess_id);
endif
data['captcha'] = captcha;
sessSetData(sess_id, data);
bltOutputBlob(output, length);


    上面在生成图形验证码时,会先使用 assets/font/xerox_serif_narrow.ttf 字体在画布中,绘制一个包含字母和数字的长度为6的随机字符串,并将位于画布中心的字符进行扭曲操作。接着,在画布的随机位置绘制5条干扰线,最后将随机字符串作为验证码写入会话文件,并将图形验证码的jpg格式的二进制数据输出到客户端。

结束语:

    Life's simple, You make choices and you don't look back: 生活很简单,作出选择,不回头

——  《速度与激情3》
 
上下篇

下一篇: zenglBlog v0.6.0-v0.6.2 生成静态页面

上一篇: zenglBlog v0.4.0 前台首页,文章列表,文章内容页

相关文章

zenglBlog v0.3.0 文章管理,图片上传,生成缩略图

zenglBlog v0.8.0 增加保存草稿,友情链接,内容页上下篇,手机端返回顶部图片

zenglBlog v0.4.0 前台首页,文章列表,文章内容页

zenglBlog v0.7.0 增加公告功能,内容页图片自适应,首页及列表页样式调整

zenglBlog v0.6.0-v0.6.2 生成静态页面

zenglBlog v0.2.0 分类管理