• 自己的项目 调查问卷Web (详细版)


    一、项目介绍

    发起问卷,管理问卷信息,把问卷结果通过可视化方式呈现出来

    二、技术栈

    • Servlet
    • MySQL
    • Apache Echarts

    三、项目功能

    • 用户管理:注册、登录、登出
    • 题库管理:录制题目、我的题目列表
    • 问卷管理:新建问卷、问卷列表、关联题目
    • 活动管理:创建活动、活动列表、结果回收

    四、数据库中表于表之间的关系

    表和表直接关系(E-R)图

    E:实体    R:关系

     题目中的选项和结果中的答案 ,都由JSON 格式保存

    五、具体实现


    1.用户管理

    关于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请求

     1.1注册:

    从用户视角,我需要一个可输入的框来输入我想要注册的用户名和密码

    因此,首先有一个静态资源(/user/register.html),里面用 form 表单,通过 post 方法,提交的是想要注册的用户名和密码 给后端。

    后端实现就是,需要一个动态资源(/user/register.do),用 from 表单提交上来的用户名和密码进行注册,显示注册成功。

    1. @WebServlet("/user/register.do")
    2. public class RegisterDoServlet extends HttpServlet {
    3. }

    在 RegisterDoServlet 中重写 doPost 方法,来读取用户的输入,进行注册。

    补充一点:

    1. 一般来说我们是用不到doGet方法的,doGet方法提交表单的时候会在url后边显示提交的内容,所以不安全。而且doGet方法只能提交256个字符(1024字节),而doPost没有限制,因为get方式数据的传输载体是URL(提交方式能form,也能任意的URL链接),而POST是HTTP头键值对(只能以form方式提交)。通常我们使用的都是doPost方法。
    2. 在做数据查询时,建议用Get方式(效率高);而在做数据添加、修改或删除时,建议用Post方式(安全性高);
    1. 读取用户的用户名和密码( req.getParameter() )
    2. 进行注册,这里我们要对密码进行加密,还要对表进行插入操作,因此我们单独写出来,写在 service 中
    3. 注册成功之后,同步进行登录,将当前注册用户,放入session 中。
    4. 在响应中,写下注册成功

    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

    1.2登录:

    登录中使用的静态资源和注册时的相同

    动态资源(/user/login.do),在 LoginDoServlet 中重写 doPost 方法:

    1.读取用户名 和密码——( req.getParameter() )

    2.进行登陆验证(验证用户名 + 密码是否正确)在 UserService 中去验证

    1. 先根据用户名从数据库表中查出对应的记录 —— userRepo 得到 userDO——查不到就登陆失败
      select uid, username, password from users where username = ?
    2. 判断密码是否正确——BCrypt

    3.将用户对象放入 session 中

    4.响应“登录成功”

    1.3退出:

    需要一个动态资源(/user/quit.do) ,重写doGet 方法,从session 中把存留的用户删除。

    小结:在用户管理模块我们写了三个动态资源,两个静态资源


     2.题目管理

     2.1新建题目

    要求用户登录后才可以使用

    • 前端需要一个静态资源 GET (/question/create.html)  提供一个form表单 POST提交  /question/create.do

    目前只实现了题目和四个选项的情况

    • 后端需要一个动态资源(/question/create.do),接收form表单提交的数据

    1.读取用户输入的题目、选项

    1. req.setCharacterEncoding("utf-8");
    2. String question = req.getParameter("question");
    3. // 使用这个,可以将请求参数中 name 同名的读到一个数组中
    4. 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 (?, ?, ?)

    2.2 我的题目列表(带有分页功能) 

    • 首先需要一个静态资源(/question/list.html),提供一个空的标签,元素容器,用于成呈现所有的题目(全部由 js 执行后 ,进行 dom 修改,动态的修改插入进来)。,引入JS 资源
    • 静态资源(/question/js/list.js) ,发起Ajax(xhr)请求 ,向后端请求我的所有问题列表(/question/list.json?page=...),根据得到的结果,进行渲染(给tbody 标签下,插入资源...)

    这里使用了 window 的 load 事件,当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件。在这里面,首先发起Ajax请求,之后根据得到的响应,进行结果的渲染。

    • 还需要一个动态资源(/question/list.json?page=...),get请求
    1. 从 session 对象中获取当前登录用户
    2. 读取 page 参数
    3. 查询用户有多少问题,为了方便计算有多少页
      select count(*) from questions where uid = ?
    4. 根据分页信息,查询这一页的所有问题
      select qid, uid, question, options from questions where uid = ? order by qid desc limit ? offset ?
    5.  查询每个问题有多少关联条目——题目和问卷多对多关系,有一个 relations 表保存关系
      select qid, count(*) as ref_count from relations where qid in (%s) group by qid order by qid
    6.  将第 3、4、5 步查到的数据,进行需要格式的组装
    7. ObjectMapper 进行 JSON 序列化
    8. 输出响应

    《格式组装》:

    • 经过QuestionRepo 中的某个方法,得到了List结果

    List [ QuestionDO{ qid = 1,uid = 7,question = "今天晚上吃什么" ,options='["饺子","包子","米饭","面条"]'} ]

    • 经过 QuestionService 中的格式转换,得到 QuestionListView 结果

    QuestionListView{

            currentUser = UserVO{ uid = 7,username = "张三"},

            questionList = List{

                    QuestionView{qid=3,question="今天晚上吃什么”,options=List{"饺子","包子","米饭","面条"}}

    }}

    • 经过 Jackson (ObjectMapper)处理,得到JSON 格式的字符串
    {"currentUser":{"uid":7,"username":"张三"},questionList":[{"qid":1,"question":"今天晚上吃什么","options":["饺子","包子","米饭","面条"]}]}
    • 响应给前端

    在此基础上进行分页

    从之前请求/question/list.json  就变成了  /question/list.json?page=...

    {"currentUser": ...,"questionList" : ...}  变成了 {"currentUser": ...,"questionList" : ...,"pagination": {每页有多少条,当前是第几页,共有多少页}}

    因此在 QuestionListView这个类中就要有所修改,需要添加 PaginationView

    1. @Data
    2. public class PaginationView {
    3. public Integer countPerPage; // 每页多少条
    4. public Integer currentPage; // 当前是第几页
    5. public Integer totalPage; // 一共多少页
    6. }

    因此在获取当前登录用户之前,我们先 读取 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

    3.问卷管理

    3.1新建问卷

    • 前端需要一个静态资源(/survey/create.html),有一个form表单来提交title 和 brief
    • 后端需要一个动态资源(/survey/create.do)

    首先读取用户参数,之后验证用户是否登录——session,如果未登录要做出提示,第三步就是把拿到的数据插入到数据库中,最后响应一下插入完成。

    insert into surveys (uid, title, brief) values (?, ?, ?)

    3.2我的问卷列表

    • 前端需要一个静态资源(/survey/list.html),提供好一个空的标签,这个标签的子元素,全部由JS 执行后(/survey/js/list.js),动态修改插入进来
    • 后端需要一个动态资源(/survey/list.json)

    设置序列化的格式,还要判断用户是否登录,登录之后,在数据库中查询信息

    select sid, uid, title, brief from surveys where uid = ? order by sid desc

    拿到的信息是一个 List 类型,遍历拿到的 list ,转化成 SurveyListView的resultView

    把拿出来的数据进行 JSON 序列化,最后把结果响应出来

    3.3关联问卷和题目

    1.问卷详情页(展示该问卷自己已经绑定的题目)

    2.列出所有可以选择的题目

    • 前端需要一个静态资源(/survey/bind.html?sid=?&page=?),这里需要带上sid,因为要知道是被哪个问卷所绑定的,通过(/survey/js/bind.js)来访问后端的动态资源(/survey/bind.json?sid=1),得到题目列表和是否绑定的情况
    • 后端就需要一个动态资源(/survey/bind.json),获取 page 和 sid,session 判断 currentUser 是否为空,为空和不为空的情况进行 json。

    后端的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;

    4.活动管理

    4.1创建活动

    • 前端需要一个静态资源(/survey/create.html),有一个form表单来提交开始时间和结束时间,最后交给 js
    • 需要一个静态资源(/activity/js/create.js),发ajxa 请求,拿到数据后,遍历修改 DOM 树 (sid 和 title)
    • 后端需要一个动态资源(/survey/simple-list.json),get请求
    1. 读取用户信息
    2. 根据用户信息,查询问卷的标题和简介
      select sid, uid, title, brief from surveys where uid = ? order by sid desc
    3. 遍历拿到的surveyDOList
    4. 转换成 resultView 的形式
    5. 进行 JSON 序列化
    • 后端需要一个动态资源(/activity/create.do)

    发起doPost请求,拿到sid和输入的开始时间和结束时间,然后把数据插入到数据库中

    insert into activities (uid, sid, started_at, ended_at) values (?, ?, ?, ?)

    4.2我的活动列表

    我们需要的数据源:

    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格式规定成:

    •  后端动态资源(/activity/list.json)

    在doGet方法中,session 先判断是否登录,登录后,拿到 uid 根据 uid,在 ActivitySurveyRepo 中执行 SQL 操作(上面的连表查询)。使用 Map 替代专门定义一个类,放入我们的 currentUser 和 activityViewList(连表查询的结果)。对拿到的数据 进行 JSON 格式化。

    判断时间状态(在ActivityView中写):

    1. private String calcState(Timestamp startedAt, Timestamp endedAt) {
    2. Instant now = Instant.now();
    3. Timestamp nowTS = Timestamp.from(now);
    4. if (nowTS.before(startedAt)) {
    5. return "未开始";
    6. } else if (nowTS.after(endedAt)) {
    7. return "已结束";
    8. } else {
    9. return "进行中";
    10. }
    11. }
    12. private String timestampToString(Timestamp ts) {
    13. LocalDateTime localDateTime = ts.toLocalDateTime();
    14. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    15. return formatter.format(localDateTime);
    16. }
    • 前端的静态资源(/activity/list.html),放一个 container 元素容器,交给 js,由 js 执行后 ,进行 dom 修改,动态的修改插入进来 (修改的是 aid ,标题,状态,开始时间,结束时间)

    4.3调查问卷场景(给普通用户填写调查问卷的地方)

    json的格式:

     关于数据:我们手上有 aid

    • 静态资源(/activity/exam.html?aid=...)

    开头的标题和简介都是动态加载的,选项也是动态加载的,填写完之后,可以提交了一个 form 表单,提交到 (/activity/exam.do)中

    • 动态资源(/activity/exam.json)

    首先可以得到 aid,根据 aid 来进行查询

    1. select title, brief, q.qid, question, options " +
    2. "from activities a " +
    3. "join surveys s on a.sid = s.sid " +
    4. "join relations r on s.sid = r.sid " +
    5. "join questions q on r.qid = q.qid " +
    6. "where aid = ? order by q.qid

    最后拿到的数据是 resultView 类型的,拿到的数据进行 JSON 格式化。

    • 静态资源(/activity/js/exam.js)

    发起 ajxa 请求,拿到 aid ,标题,简介这些数据,进行 DOM 渲染

    拿到问题列表进行遍历,动态加载每一个问题和选项。

    • 动态资源(/activity/exam.do)用户填写完之后提交到这里

    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.输出响应

    4.4结果回收场景

    使用 Apache Echarts

    引入echarts.min.js

     我们需要得到用户名,aid,开始时间,结束时间,sid,标题,简介,问题列表(问题的qid,问题,每个选项)

    这些数据涉及到:activities、surveys、relations、questions、results 表

    • 动态资源(/activity/result.json)

    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

    • 静态资源(/activity/result.html)
    1. <div class="resultList">
    2. div>

    让 js 往里面插入,有一个题就写一个 div

    这里面要引入echarts.min.js,引入一个 result.js

    • 静态资源(/activity/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斗 - 博客园

  • 相关阅读:
    OpenCV+Tensorflow的手势识别
    铱钌合金/氧化铱仿生纳米酶|钯纳米酶|GMP-Pd纳米酶|金钯复合纳米酶|三元金属Pd-M-Ir纳米酶|中空金铂合金纳米笼核-多空二氧化硅壳纳米酶
    构建微波和毫米波自动测试系统需要考虑哪些因素?(二)
    线性代数视频笔记
    10:00面试,10:06就出来了,问的问题有点变态。。。
    JEB反编译器crack版功能和选项
    HarmonyOS鸿蒙原生应用开发设计- 服务组件库
    WPF/C#:在WPF中如何实现依赖注入
    Nvidia Jetson Nano学习笔记--使用C语言实现GPIO 输入输出
    股票交易接口使用步骤
  • 原文地址:https://blog.csdn.net/weixin_61567666/article/details/126081370