发起问卷,管理问卷信息,把问卷结果通过可视化方式呈现出来
表和表直接关系(E-R)图
E:实体 R:关系
题目中的选项和结果中的答案 ,都由JSON 格式保存
关于POST 和 GET
1. POST请求是HTPP协议中一种常用的请求方法,它的使用场景是向客户端向服务器提交数据,比如登录、注册、添加等场景。另一种常用的请求方法是GET,它的使用场景是向服务器获取数据。
2. get是从服务器上获取数据,post是向服务器传送数据。
3. get 和 post只是一种传递数据的方式。4. 对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
5. Servlet的doGet/doPost 是在 javax.servlet.http.HttpServlet 中实现的
doGet:处理GET请求
doPost:处理POST请求
从用户视角,我需要一个可输入的框来输入我想要注册的用户名和密码
因此,首先有一个静态资源(/user/register.html),里面用 form 表单,通过 post 方法,提交的是想要注册的用户名和密码 给后端。
后端实现就是,需要一个动态资源(/user/register.do),用 from 表单提交上来的用户名和密码进行注册,显示注册成功。
- @WebServlet("/user/register.do")
- public class RegisterDoServlet extends HttpServlet {
- }
在 RegisterDoServlet 中重写 doPost 方法,来读取用户的输入,进行注册。
补充一点:
- 一般来说我们是用不到doGet方法的,doGet方法提交表单的时候会在url后边显示提交的内容,所以不安全。而且doGet方法只能提交256个字符(1024字节),而doPost没有限制,因为get方式数据的传输载体是URL(提交方式能form,也能任意的URL链接),而POST是HTTP头键值对(只能以form方式提交)。通常我们使用的都是doPost方法。
- 在做数据查询时,建议用Get方式(效率高);而在做数据添加、修改或删除时,建议用Post方式(安全性高);
Session
用浏览器打开一个网页,用到的是HTTP协议,它是无状态的,就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。
当我们想让两次HTTP的请求有关联时,就很难办了。
因此,一个新的客户端存储数据方式出现了:cookie。cookie是把少量的信息存储在用户自己的电脑上,它在一个域名下是一个全局的,只要设置它的存储路径在域名www.a.com下 ,那么当用户用浏览器访问时,php就可以从这个域名的任意页面读取cookie中的信息。所以就很好的解决了我在www.a.com/login.php页面登陆了,我也可以在www.a.com/index.php获取到这个登陆信息了。同时又不用反复去查询数据库。
但是由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。所以就有了session,它就是在一次会话中解决2次HTTP的请求的关联,让它们产生联系,让2两个页面都能读取到找个这个全局的session信息。session信息存在于服务器端,所以也就很好的解决了安全问题。
在 serivce 所要做的工作:
1.对密码加密,我们使用java中别人写好的 BCrypt算法:jBCrypt
2.将数据插入到 数据库的 users 表中 —— 通过 repository 对象完成(UserRepo)
insert into users(username,password) values (?,?)
我们所有的 repository 对象 都是代表针对一张数据库表做 增删改查操作的
在对象的处理上我们用到两个对象 UserDO(数据库直接管理的对象,包含用户名和密码) 和 UserVO(对外展示的对像,只包含用户名) 。
当用到 sql 时,我们提前准备好一个 DBUtil,执行完 sql 之后,我们要得到插入的自增 id 作为 userDO 的 uid
登录中使用的静态资源和注册时的相同
动态资源(/user/login.do),在 LoginDoServlet 中重写 doPost 方法:
1.读取用户名 和密码——( req.getParameter() )
2.进行登陆验证(验证用户名 + 密码是否正确)在 UserService 中去验证
select uid, username, password from users where username = ?
3.将用户对象放入 session 中
4.响应“登录成功”
需要一个动态资源(/user/quit.do) ,重写doGet 方法,从session 中把存留的用户删除。
小结:在用户管理模块我们写了三个动态资源,两个静态资源
要求用户登录后才可以使用
目前只实现了题目和四个选项的情况
1.读取用户输入的题目、选项
- req.setCharacterEncoding("utf-8");
- String question = req.getParameter("question");
- // 使用这个,可以将请求参数中 name 同名的读到一个数组中
- String[] options = req.getParameterValues("option");
2.还要判断用户是否已经登录——session
3.进行格式处理(选项的格式),把数据保存到表中(question表)——在service中完成
①将 options 这个数组,通过 JSON 格式,变成一个字符串,最后保存到数据库中
String optionsJsonString = objectMapper.writeValueAsString(options);
②使用 QuestionRepo 对象,对数据进行插入表操作
insert into questions (uid, question, options) values (?, ?, ?)
这里使用了 window 的 load 事件,当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件。在这里面,首先发起Ajax请求,之后根据得到的响应,进行结果的渲染。
select count(*) from questions where uid = ?
select qid, uid, question, options from questions where uid = ? order by qid desc limit ? offset ?
select qid, count(*) as ref_count from relations where qid in (%s) group by qid order by qid
《格式组装》:
List [ QuestionDO{ qid = 1,uid = 7,question = "今天晚上吃什么" ,options='["饺子","包子","米饭","面条"]'} ]
QuestionListView{
currentUser = UserVO{ uid = 7,username = "张三"},
questionList = List{
QuestionView{qid=3,question="今天晚上吃什么”,options=List{"饺子","包子","米饭","面条"}}
}}
{"currentUser":{"uid":7,"username":"张三"},questionList":[{"qid":1,"question":"今天晚上吃什么","options":["饺子","包子","米饭","面条"]}]}
在此基础上进行分页
从之前请求/question/list.json 就变成了 /question/list.json?page=...
{"currentUser": ...,"questionList" : ...} 变成了 {"currentUser": ...,"questionList" : ...,"pagination": {每页有多少条,当前是第几页,共有多少页}}
因此在 QuestionListView这个类中就要有所修改,需要添加 PaginationView
- @Data
- public class PaginationView {
- public Integer countPerPage; // 每页多少条
- public Integer currentPage; // 当前是第几页
- public Integer totalPage; // 一共多少页
- }
因此在获取当前登录用户之前,我们先 读取 page 参数
String pageString = req.getParameter("page");
如果出现 page 的相关错误,暂时不做出错处理了,而是给 page 一个相对正确的值
在 QuestionService 中,我们定义好关于分页的基本信息,规定每页就 5 条记录,根据已知信息去算 SQL 中 limit + offset
当我们写完去点击下一页的时候发现一直都停留在第一页,这是为什么?
我们访问的时候是带page的,但js去访问的时候没有带page参数,所以需要修改js
xhr.open('get', '/question/list.json' + location.search)
我们每道题目还可以和问卷进行关联,题目和问卷是多对多的关系,一道题可以关联到多个问卷,一个问卷中可以关联多个题。数据源就来自 relations 表,可以进行聚合查询。
select qid, count(*) as ref_count from relations where qid in (%s) group by qid order by qid
首先读取用户参数,之后验证用户是否登录——session,如果未登录要做出提示,第三步就是把拿到的数据插入到数据库中,最后响应一下插入完成。
insert into surveys (uid, title, brief) values (?, ?, ?)
设置序列化的格式,还要判断用户是否登录,登录之后,在数据库中查询信息
select sid, uid, title, brief from surveys where uid = ? order by sid desc
拿到的信息是一个 List
把拿出来的数据进行 JSON 序列化,最后把结果响应出来
1.问卷详情页(展示该问卷自己已经绑定的题目)
2.列出所有可以选择的题目
后端的json格式:
我们在 SurveyService 中来查询这些数据
1. 首先要得到 sid, uid, title, brief (在surveys表中),然后拿到 SurveyDO,判断一下如果surveyDO不存在也就是要绑定的sid(问卷)是不存在的,我们就抛出 404 异常
2. 如果存在的话,就去查询这个问卷都绑定了那些题目,得到一个已绑定的题目链表
3. 做一个分页,每页显示 5 条
4. 最后查询所有候选题目,得到一个题目链表
5. 得到所有的数据之后(user, surveyDO, qidBoundenList, questionDOList),通过遍历转换成SurveyBindView
这个数据涉及到三张表:
表:surveys(查 uid 是确保,只有调查问卷的创建者本人可以做绑定)
在 SurveyRepo.selectOneBySidAndUid 中完成
select sid,uid,title,brief from surveys where sid = 1 and uid = 7;
表:relations(查询这个问卷绑定了哪些题目)
在 RelationRepo.selectQidListBySid 中完成
select qid from relations where sid = 1;
表:questions(查询所有候选题目(uid = 7 创建的题目))
在 surveyRepo.selectOneBySidAndUid 中完成
select qid,question from questions where uid = 7;
select sid, uid, title, brief from surveys where uid = ? order by sid desc
发起doPost请求,拿到sid和输入的开始时间和结束时间,然后把数据插入到数据库中
insert into activities (uid, sid, started_at, ended_at) values (?, ?, ?, ?)
我们需要的数据源:
activities(acid,开始时间,结束时间,状态,调查链接)
surveys(问卷标题)
这里使用连表查询
select aid, a.sid, s.title, a.started_at, a.ended_at from activities a join surveys s on a.sid = s.sid and a.uid = s.uid where a.uid = ? order by a.aid desc
json格式规定成:
在doGet方法中,session 先判断是否登录,登录后,拿到 uid 根据 uid,在 ActivitySurveyRepo 中执行 SQL 操作(上面的连表查询)。使用 Map 替代专门定义一个类,放入我们的 currentUser 和 activityViewList(连表查询的结果)。对拿到的数据 进行 JSON 格式化。
判断时间状态(在ActivityView中写):
- private String calcState(Timestamp startedAt, Timestamp endedAt) {
- Instant now = Instant.now();
- Timestamp nowTS = Timestamp.from(now);
- if (nowTS.before(startedAt)) {
- return "未开始";
- } else if (nowTS.after(endedAt)) {
- return "已结束";
- } else {
- return "进行中";
- }
- }
-
- private String timestampToString(Timestamp ts) {
- LocalDateTime localDateTime = ts.toLocalDateTime();
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
- return formatter.format(localDateTime);
- }
json的格式:
关于数据:我们手上有 aid
开头的标题和简介都是动态加载的,选项也是动态加载的,填写完之后,可以提交了一个 form 表单,提交到 (/activity/exam.do)中
首先可以得到 aid,根据 aid 来进行查询
- select title, brief, q.qid, question, options " +
- "from activities a " +
- "join surveys s on a.sid = s.sid " +
- "join relations r on s.sid = r.sid " +
- "join questions q on r.qid = q.qid " +
- "where aid = ? order by q.qid
最后拿到的数据是 resultView 类型的,拿到的数据进行 JSON 格式化。
发起 ajxa 请求,拿到 aid ,标题,简介这些数据,进行 DOM 渲染
拿到问题列表进行遍历,动态加载每一个问题和选项。
1.读取参数(aid,nickname,phone)
answer的保存,每次调查、题目数量、题目id 都是不同的,把回答的结果按照一定格式序列化成 JSON 字符串,保存起来。这里不是最优解,但相对来说,是目前比较好实现的方法
2. 拿到参数之后,遍历,准备一个 Map,key是qid,value是answer
3.把 answer 进行 json 序列化
4.把数据保存到数据库中
insert into results (aid, nickname, phone, answer) values (?, ?, ?, ?)
5.输出响应
使用 Apache Echarts
引入echarts.min.js
我们需要得到用户名,aid,开始时间,结束时间,sid,标题,简介,问题列表(问题的qid,问题,每个选项)
这些数据涉及到:activities、surveys、relations、questions、results 表
1.获取 aid 之后,判断 aid 是否为空,就是想让它必须带有 aid 参数且aid 必须是数字
2.session 判断用户是否登录
3.执行 SQL
select aid, started_at, ended_at, a.sid, title, brief from activities a join surveys s on a.sid = s.sid where a.uid = ? and aid = ?
把得到的 aid, started_at, ended_at, sid, title, brief 放入 resultView 中
执行 SQL
select qid from relations where sid = ? order by rid
得到 qid 之后,如果 qidList 是空的,说明一个题都没有关联
执行 SQL
select qid, question, options from questions where qid in (%s)
把qid 和 question 放入 qidToQuestionMap(这是一个map,qid -> Question)
执行 SQL
select answer from results where aid = ?
拿到 answer,根据转换可以拿到一个answerMap,map中key就是 qid ,value就是 选项的index(0,1,2,3)。
根据 qid,得到这个问题,这个问题的选项放在一个数组中,根据他的下标,每拿一次,让count++,意思就是选这个选项的人又多了一个。
这里面实际上有两层循环,第一层循环 N: 循环本次参与调查的人数,第二层循环 M: 本次调查的题目数量。
最后进行格式转换 qidToQuestionMap -> ResultView
- <div class="resultList">
- div>
让 js 往里面插入,有一个题就写一个 div
这里面要引入echarts.min.js,引入一个 result.js
写一个 function initOption(question),里面的
text: question.question data: question.results
把我们的问题和答案的数据加载进去
写一个 function renderQuestion(question) ,把问题传入,我传入一个问题,就渲染一个
后面进行 title, brief ,startedAt, endedAt 的渲染
最后进行 window.onload
整体代码:https://gitee.com/hlingqi/questionnaire
参考链接:
session的到底是做什么的?_如丝袜一般丝滑的博客-CSDN博客_session
servlet中doGet()和doPost()的用法 - fen斗 - 博客园