一个投票系统的开发
需求
该投票系统用户群为手机客户,也即该投票要以手机版的展示形式。
具备单选、多选和填空功能。
每个问题和选项可以上传一张配图,上传的图片需要多尺寸进行缩放,以适合手机端展示。
用户以手机号码为用户标识。
用户的认证使用无线城市的单点登录功能;系统接入无线城市。
投票具有白名单功能,白名单用户才具有某投票的权限。
新增的投票活动需要审核才能发布。
投票结果可以查看和导出。
后台管理为web版。
后台管理具有多用户、多权限。
详细的操作记录日志。
分析
这个系统不算复杂,核心就是一个投票功能。接入无线城市其实就是使用无线城市的一个单点登录,通过跳转携带token的方式获取用户手机号码,这个也不难。
设计
这里先上数据库,再慢慢来说。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
use [survey]
go
create table tsurvey(
fid int PRIMARY KEY identity(1,1) ,
ftitle varchar(500),
fdescription varchar(5000),
fstartDate datetime,
fendDate datetime,
fauthor int,
faudit int default(0)
)
create table tquestion(
fid int PRIMARY KEY identity(1,1) ,
ftitle varchar(500),
ftype varchar(50),
frequired char(1) default '1',
fsurvey int,
fpic varchar(500),
fmulti varchar(50)
)
create table toptions(
fid int PRIMARY KEY identity(1,1) ,
fquestion int,
fsort int,
ftitle varchar(500),
fpic varchar(500),
fscore decimal(18, 0),
fattr varchar(500)
)
create table tuser(
fid int primary key identity(1,1),
fname varchar(50) not null,
fpassword varchar(50),
fenable char(1) default '1' ,
frole varchar(500),
ftoken varchar(50)
)
create table tpower(
fid int PRIMARY KEY identity(1,1) ,
fname varchar(50) ,
fdescription varchar(500)
)
create table trole(
fid int PRIMARY KEY identity(1,1) ,
fname varchar(50) ,
fdescription varchar(500),
fpower varchar(500)
)
create table tuserSel(
fid int PRIMARY KEY identity(1,1) ,
fuser varchar(50) ,
foptions int
)
create table tsyslog(
fid int PRIMARY KEY identity(1,1) ,
fcreateDate datetime default (getdate()),
flevel int,
fdescription varchar(5000)
)
create table twhiteList(
fid int PRIMARY KEY identity(1,1) ,
fsurvey int,
fmobile varchar(50)
)
create table totherAnswer(
fid int PRIMARY KEY identity(1,1) ,
foption int,
fcontent varchar(500),
fuser varchar(50)
)
--下面是创建存储过程
-- =============================================
-- Author:
-- Create date: 2011-09-13
-- Description: 删除一个问卷,同时清除问卷相关数据
-- =============================================
CREATE PROCEDURE DeleteSurvey
@survey int
AS
BEGIN
create table #tquestion(fid int)
insert into #tquestion
select fid from tquestion where fsurvey =@survey
create table #toptions(fid int)
insert into #toptions
select fid from toptions where fquestion in(select fid from #tquestion )
delete twhiteList where fsurvey=@survey
delete totherAnswer where foption in (select fid from #toptions )
delete tuserSel where foptions in(select fid from #toptions )
delete toptions where fid in (select fid from #toptions )
delete tquestion where fid in (select fid from #tquestion )
delete tsurvey where fid=@survey
END
CREATE PROCEDURE DeleteQuestion
@question int
AS
BEGIN
create table #options(
fid int
)
insert into #options
select fid from toptions where fquestion =@question
delete tuserSel where foptions in (select fid from #options )
delete totherAnswer where foption in (select fid from #options )
delete toptions where fquestion =@question
delete tquestion where fid=@question
END
CREATE PROCEDURE DeleteOption
@option int
AS
BEGIN
delete tuserSel where foptions =@option
delete totherAnswer where foption =@option
delete toptions where fid=@option
END
Tsurvey Tquestion Toptions 表
这3张表是存放投票信息的表。Tsurvey为投票主题表,存放一次活动的信息,如“幸福感调查”这样的大主题;Tquestion表存放问题标题,如“今天天气怎么样?”这样的问题;Toptions表存放对问题的对应选项内容,如“天气不错啊”、“天下雨呢”这样的选项。他们的对应是Tsurvey(fid)=(fsurvey)Tquestion(fid)=(tquestion)Toptions 这样的关系。
细说Trequest表字段
ftype:该问题的选择类型,分为单选和多项。
frequired:是否必填
fmulti:如果多项时,最多可选个数。
细说Toptions表字段
fattr:扩展字段,目前只有一个功能,如果为1,则该选项旁边与个文本框,可供用户填写其他
Tuser表
后台管理的用户表。
fenable:不对用户表进行物理删除,以标记删除。
ftoken:每次用户登录系统都会生成一个随机的token,用于校对身份的时候使用。
TuserSel表
用户投票时的登记表。
TotherAnswer表
这个是保存用户自己填写的文本框的内容,如果某个选项option的fattr=1,那么这个选项是允许用户自己填写文本,填写的内容会保存在这里。
TwhiteList表
白名单
3个存储过程DeleteSurvey DeleteQuestion DeleteOption
3个存储过程主要用户在删除活动,问题,选项的时候关联删除相关数据。
系统设计
该系统使用的是asp.net+sqlserver2005+jquery+easyui。
整个后台管理界面有jquery easyui 构建,通过ajax与后端交互。
交互接口
前端的js在与后台交互的时候都是用同一个地址作为数据的交互。使用这个地址“server.aspx?method={1}&arg1=Value1&arg2=Value2” ,{1}是要执行的方法名称,后面跟着的是若干个参数名及参数值。假设后台有个函数 string Hello(string msg),那么js就应该向这个地址发送post请求"server.aspx?method=Hello" 。
这样做主要是因为asp.net webform 没有路由功能,不能将url映射到逻辑代码上,其次集中入口,方便权限的管理和日志的记录。在server.aspx页面逻辑中再根据url参数使用反射的方法执行逻辑代码并返回数据。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
string reqMethod = Request.QueryString["method"];
Helper myHelper = new Helper();
Type t = myHelper.GetType();
MethodInfo myMethodInfo = t.GetMethod(reqMethod);
ParameterInfo[] myArgs = myMethodInfo.GetParameters();
object[] myNewArgs = new object[myArgs.Length];
for (int i = 0; i < myArgs.Length; i++)
{
myNewArgs[i] = Request.Form[myArgs[i].Name].ToString();
}
上面的代码,就是从method参数中获取要执行的方法,和在post数据中获得参数,并传给具体方法执行。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
var result = myMethodInfo.Invoke(myHelper, myNewArgs);
Response.Clear();
Response.Write(result.ToString());
Response.End();
在这个过程中,可以集中的记录下用户执行了什么方法,和判断是否具备执行该方法的权限。
权限的判断我使用的是给每个具体逻辑函数增加一个Atrribute,如一个函数如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[HasRight(MyPower.删除选项)]
public string DeleteOption(string fid)
{
DataBase db = new DataBase();
db.ExecuteStoredProcedure("DeleteOption", new SqlParameter[] { new SqlParameter("@option", fid) });
return "";
}
那么我在server.aspx页面要执行该函数时,读取用户的frole角色字段,获取用户具有的所有权限,再与该函数的HasRight对比,如果具有,则允许执行,否则就不执行。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
Bo.Trole myRole =(Bo.Trole) Bo.Trole.Find(typeof(Bo.Trole), user.frole);
foreach (var attr in myMethodInfo.GetCustomAttributes(true))
{
if (attr is HasRight)
{
if (!((HasRight)attr).allow(myRole.fpower ))
{
AuthFalse();
return;
}
}
}
以上代码就完成了权限的判断。
用户身份验证
在保存用户数据的时候,最简单就是保存在session里面,需要时就拿出来,也不会被用户端修改。但是很多例子表明,不少用户打开个页面就挂它一个小时不操作的现象常有,这样一来session常丢失,导致出错,最常见的就是“未将对象引用实例化”,每次索取的时候都加判断也挺麻烦的,要是发现超时还得引导用户去重新登录。再加上使用了多台服务器均衡,session保存也麻烦(当然asp.net有很好的机制解决这个问题)。所以我在这个系统上没有使用session。
使用cookie保存用户信息就怕给修改,所以我使用了token机制。用户每次登录后,生成一个随机token发送到cookie中,同时这个token保存到用户信息表的ftoken列,这样用户每次交互,都验证一下这个token是否对应某个用户,是则验证通过。这样子的好处显然不怕session丢失了,用户想呆多久就多久。其次我还发现了一个意外的好处,即不能同时在多处以同一用户名登录上去,后到的一定会把先来的踢掉。这样如果你的密码被盗,被人登录了上去,那你下次打开的时候会提示你token错误重新登录,这样起码你知道有人用这个帐号在别处登录过。
功能的分开
如果在一个页面中要实现新增一个投票活动,那么至少新增一个tsurvey记录,若干个tquestion和toptions记录,还包括比较复杂的各种属性的设置和图片的上传界面的现实。都在一个页面实现估计htmlDOM和js代码都很多,看起来和修改都麻烦,尤其是将来修改。所以我基本上一个操作就是分开一个页面来做,这样每个页面都清新简洁,整体代码量也不见增加,反之还少了很多的if判断现在是哪个哪个。例如同一个页面完成上面3个内容,有一个提交按钮,那么我就要if3次判断是提交的是tuservey还是tquestion还是toption数据内容。而把它们分在3个页面来做,每个按钮只做一件事。然后通过页面的iframe来展示,就会给用户都在同一页面操作的感觉。
外观展示
几张页面剪影
在展示问题和选项的时候,我选择easyui的treegrid,如第一张图显示的就是一个问题,这种表现形式还是ok的。
2个jquery插件
经常需要从url中取得参数,这里我使用了jquery.query-2.1.7.js 这个东西。使用非常简单 $.query.get('id')。id为你的url参数名。
同时需要上传文件,也选了个简单的 ajaxfileupload.js。