基于Servlet的技术问答网站系统实现(附源码)

news/2024/5/21 4:50:12/文章来源:https://blog.csdn.net/weixin_34133829/article/details/90538313

这一篇博客将详细介绍一个基于Servlet的问答网站的实现,有详细的代码。

可能篇幅较长,以代码为主,有兴趣的童鞋看完可以尝试动手搭建一个属于自己的问答社区。

工具:Eclipse,数据库用到了MySQL,这次项目中未使用jsp,全部以Servlet注解的方式连接HTML和Servlet,JDK最好使用1.8,tomcat使用8.0。(注解方式为JDK1.5后的特性,最低要求1.5+,本项目使用JDK1.8)。

在这篇博客中可以学习到:

1,Servlet中关于注解的使用,本项目没有使用到传统的Servlet配置web.xml,全部使用注解的形式。

2,了解Font Awesome这一矢量图标库的使用,他基本提供了项目中所要使用到的所有图标,方便,快捷。

3,了解simditor这一富文本编辑器的使用,网站中直接嵌入富文本编辑器,再也不用为读取出来的文字格式不对发愁了。

4,关于项目中如何加入验证码,数据库查询之前先进行验证码验证。

5,关于MVC框架显示层——Velocity技术的使用。

先看一下大体项目图(由于主要做后台,前台可能略丑,大家可以自行找网站模板)


登录界面:


注册界面:



首页,展示了大家的提问:



解答界面,点击别人的提问后进入解答界面,使用了富文本编辑器。



我的解答界面,展示了我回答的历史:


我的提问界面,展示了我提问的所有问题:


提问界面:进入网站点击我要提问,加入当前页编辑问题:



下面介绍主要代码(代码中加入了详细注释,所以不再做说明)

主页列表Servlet:

@WebServlet( "/list.do" )
public class ListServlet  extends HttpServlet {private static final long serialVersionUID = 810339694607399128L;@Overrideprotected void service( HttpServletRequest request , HttpServletResponse response )throws ServletException, IOException {String question=request.getParameter("quest");System.out.println(question);if(StringHelper.notEmpty(question)){final String SQL = "SELECT id , title ,content, publish_time , publish_ip , user_id FROM t_topic where title =? " ;ResultSet rs = JdbcHelper.query( SQL,question );// 创建一个 List 对象,用来保存一批 Topic 对象final List<Topic> topics = new ArrayList<>();try {// 每循环一次,光标下移一行,如果该行有数据返回 truewhile( rs.next() ){Topic t = new Topic(); // 创建对象t.setId( rs.getInt( 1 ) ); // 将 结果集 中的 该行数据 封装到 t 对象的 id 属性中t.setTitle( rs.getString( 2 ) );t.setContent(rs.getString(3));t.setPublishTime( rs.getTimestamp( 4 ));t.setPublishIp( rs.getString( 5 ) );User u = new User(); // 创建 一个 User 对象u.setId( rs.getInt( 6 ) ); // 将 t_topic 表中的 user_id 放入到 User 对象的 id 属性中t.setUser( u );  // 将 User 对象 设置到 Topic 对象上/** 将 本次循环 创建的对象(已经封装数据) 添加到 List 集合中 */topics.add( t );}} catch (SQLException e) {e.printStackTrace();}JdbcHelper.release( rs ); // 关闭 结果集,释放相关的资源/**** 为每个问题寻找提问者  ***********************************///for( int i = 0 ; i < topics.size() ; i++ ){for( int i = 0 , len = topics.size() ; i < len ; i++ ){Topic t = topics.get( i ) ; // 获得 题目User u = t.getUser(); // 获得当前题目的User对象 ( 该对象中只有 id 没有其它数据 )// 根据 用户对象的 id 来查询 用户的信息String querySQL = "SELECT id , username , password FROM t_user WHERE id = ? " ;ResultSet userRs = JdbcHelper.query( querySQL , u.getId() );try {if( userRs.next() ) { // 如果查询到用户信息// 注意,这里应该使用 userRsu.setUsername( userRs.getString( 2 ) ); // 将 username 列的值设置到 用户对象的 username 属性中u.setPassword( userRs.getString( 3 )); // 将 password 列的值设置到 用户对象的 password 属性中}} catch (SQLException e) {e.printStackTrace();}JdbcHelper.release( userRs ); // 关闭 结果集,释放相关的资源}ServletContext application = request.getServletContext();/** 将这些数据保存到 application **/application.setAttribute( "topics" , topics );System.out.println( "问题列表: " + topics );// 去 list.html 页面response.sendRedirect( request.getContextPath() + "/list.html");}else{/**** 查询数据库中的所有问题  ***********************************/final String SQL = "SELECT id , title ,content ,publish_time , publish_ip , user_id FROM t_topic ORDER BY publish_time DESC" ;ResultSet rs = JdbcHelper.query( SQL );// 创建一个 List 对象,用来保存一批 Topic 对象final List<Topic> topics = new ArrayList<>();try {// 每循环一次,光标下移一行,如果该行有数据返回 truewhile( rs.next() ){Topic t = new Topic(); // 创建对象t.setId( rs.getInt( 1 ) ); // 将 结果集 中的 该行数据 封装到 t 对象的 id 属性中t.setTitle( rs.getString( 2 ) );t.setContent(rs.getString(3));t.setPublishTime( rs.getTimestamp( 4 ));t.setPublishIp( rs.getString( 5 ) );User u = new User(); // 创建 一个 User 对象u.setId( rs.getInt( 6) ); // 将 t_topic 表中的 user_id 放入到 User 对象的 id 属性中t.setUser( u );  // 将 User 对象 设置到 Topic 对象上/** 将 本次循环 创建的对象(已经封装数据) 添加到 List 集合中 */topics.add( t );}} catch (SQLException e) {e.printStackTrace();}JdbcHelper.release( rs ); // 关闭 结果集,释放相关的资源/**** 为每个问题寻找提问者  ***********************************///for( int i = 0 ; i < topics.size() ; i++ ){for( int i = 0 , len = topics.size() ; i < len ; i++ ){Topic t = topics.get( i ) ; // 获得 题目User u = t.getUser(); // 获得当前题目的User对象 ( 该对象中只有 id 没有其它数据 )// 根据 用户对象的 id 来查询 用户的信息String querySQL = "SELECT id , username , password FROM t_user WHERE id = ? " ;ResultSet userRs = JdbcHelper.query( querySQL , u.getId() );try {if( userRs.next() ) { // 如果查询到用户信息// 注意,这里应该使用 userRsu.setUsername( userRs.getString( 2 ) ); // 将 username 列的值设置到 用户对象的 username 属性中u.setPassword( userRs.getString( 3 )); // 将 password 列的值设置到 用户对象的 password 属性中}} catch (SQLException e) {e.printStackTrace();}JdbcHelper.release( userRs ); // 关闭 结果集,释放相关的资源}ServletContext application = request.getServletContext();/** 将这些数据保存到 application **/application.setAttribute( "topics" , topics );System.out.println( "问题列表: " + topics );// 去 list.html 页面response.sendRedirect( request.getContextPath() + "/list.html");}}
}

主页列表显示界面代码:

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>首页</title><link rel="stylesheet" href="$path/styles/general.css"><link rel="stylesheet" href="$path/styles/cell.css"><link rel="stylesheet" href="$path/styles/wen.css"></head><body>## 登录状态栏 开始<div class="login-status-container  auto-height"><span class="welcome cell-8">## 在 $ 之后 表达式之前使用 ! 表示 安静模式 ( 静默模式 )<b>欢迎$!user.username来到爱问社区</b></span><span class="login-operation cell-4">#if( $user )<a href="$path/ask.html">提问</a><em>|</em><a href="$path/myQuestion.do">我的提问</a><em>|</em><a href="$path/logout.do">注销</a>#else<a href="$path/login.html">登录</a><em>|</em><a href="$path/regist.html">注册</a>#end</span></div> ## 登录状态栏 结束## 标志区域<div class="brand-container  auto-height"><div class="cell-2"><a href="$path"></a></div><div class="slogan cell-10"><div>爱问社区,这里可放logo</div></div></div>## 列表区域<div class="topic-list-container clear"><!-- 问题列表的标题 --><div class="topic-list-header row clear"><span class="topic-item-index cell-1">序号</span><span class="topic-item-title cell-5" style="text-align: left ;">标题</span><span class="topic-item-time cell-3">提问时间</span><span class="topic-item-user cell-2">提问者</span><span class="topic-item-operation cell-1">#if( $user )解答问题#end</span></div>## 问题列表开始##  每循环一次从 $topics 集合中取出一个 Topic 对象 放到 $topic 中#foreach( $topic in $topics )<div class="topic-item row clear odd"><span class="topic-item-index cell-1">$topic.id</span><span class="topic-item-title cell-5" style="text-align: left ;"><a href="$path/detail.do?id=$topic.id">$topic.title</a></span><span class="topic-item-time cell-3"> $topic.publishTime </span><span class="topic-item-user cell-2"> $topic.user.username</span><span class="topic-item-operation cell-1"> #if( $user )<a href="$path/answer.do?id=$topic.id">解答</a>#end</span></div>#end## 问题列表结束</div>## 列表区域结束<div class="line"></div><div class="container link-container"><a href="$path/ask.html" >发起新问题</a>|<a href="$path/index.html" >返回首页</a></div><div class="container copyright-container">&copy; 2017 爱问社区版权所有</div></body>
</html>

提问Servlet处理代码(对用户进行了过滤,未登录用户不能发起提问)

@WebServlet( "/ask.do" )
public class AskServlet   extends HttpServlet {private static final long serialVersionUID = -4777642507114213231L;@Overrideprotected void service( HttpServletRequest request , HttpServletResponse response ) throws ServletException, IOException {HttpSession session = request.getSession();User user = (User) session.getAttribute( "user" ); // 在登录时将 User 对象放入了 会话 中// 先检查用户是否已经登录 ( 看 会话 中是否 拥有 User 对象 )if( user != null ) {// 接受来自页面的数据String title = request.getParameter( "title" );String content = request.getParameter( "content" );System.out.println( "title : " + title );System.out.println( "content : " + content );// 判断 title 和 content 是否不为 空 ( 不等于 null 不是空白字符串, 也不是空串)if( StringHelper.notEmpty( title ) && StringHelper.notEmpty( content ) ){// 获得 客户端 的 IP 地址String ip = request.getRemoteAddr();System.out.println( "ip : " + ip );// 获得 从 历元 到当前时刻所经历的毫米数long ms = System.currentTimeMillis();Timestamp currentTime = new Timestamp( ms ) ;final String SQL = "INSERT INTO t_topic ( title , content , publish_time , publish_ip , user_id ) VALUES ( ? , ? , ? , ? , ? )" ;int n = JdbcHelper.insert(SQL, false, title , content , currentTime , ip ,  user.getId() );if( n > 0 ){// 如果提问成功,则去往列表页面response.sendRedirect( request.getContextPath() + "/list.do" );} else {// 用户已经登录了,但是没有填入 title 和 contentsession.setAttribute( "askFail" , "提问失败了" );response.sendRedirect( request.getContextPath() + "/ask.html" );}} else {// 用户已经登录了,但是没有填入 title 和 contentsession.setAttribute( "askFail" , "标题和内容都不能为空" );response.sendRedirect( request.getContextPath() + "/ask.html" );}} else {// 用户没有登录,需要给出提示并回到登录页面去session.setAttribute( "loginFail" , "没有登录不能提问,请先登录" );response.sendRedirect( request.getContextPath() + "/login.html" );}}}

提问前台界面代码:

<!DOCTYPE html><html><head><meta charset="UTF-8"><title>提问</title><link rel="stylesheet" href="$path/styles/general.css"><link rel="stylesheet" href="$path/styles/cell.css"><link rel="stylesheet" href="$path/styles/wen.css"><link rel="stylesheet" href="$path/styles/btn.css"><!-- 链接 simditor 的样式库 --><link rel="stylesheet"  href="$path/simditor/styles/simditor.css" type="text/css"><!-- 导入 simditor 的 JavaScript 库 --><script type="text/javascript" src="$path/simditor/scripts/jquery.min.js"></script><script type="text/javascript" src="$path/simditor/scripts/module.js"></script><script type="text/javascript" src="$path/simditor/scripts/hotkeys.js"></script><script type="text/javascript" src="$path/simditor/scripts/uploader.js"></script><script type="text/javascript" src="$path/simditor/scripts/simditor.min.js"></script></head><body>## 登录状态栏 开始<div class="id="topnav" class="f_r"><span class="welcome cell-8">## 在 $ 之后 表达式之前使用 ! 表示 安静模式 ( 静默模式 )<b>欢迎$!user.username来到爱问社区</b></span><span class="login-operation cell-4">#if( $user )<a href="$path/ask.html">提问</a><em>|</em><a href="$path/myQuestion.do">我的提问</a><em>|</em><a href="$path/logout.do">注销</a>#else<a href="$path/login.html">登录</a><em>|</em><a href="$path/regist.html">注册</a>#end</span></div> ## 登录状态栏 结束<div class="brand-container  auto-height"><div class="cell-2"><a href="$path"></a></div><div class="slogan cell-10"><div>爱问社区,这里可以logo</div></div></div><div class="container message-container"><!-- 显示提示信息的地方 -->#if( $askFail ) $askFail$scope.remove( $session , 'askFail' )#end</div>#if( $user ) <!-- 提问表单 提交给 ask.do 对应的 Servlet 处理  --><form action="$path/ask.do" method="post" ><!-- 提问区域 开始 --><div class="container  topic-ask-container clear shadow-outside auto-height" ><!-- 问题的标题 和 提问按钮 --><div class="container  title-container"><div class="cell-11"><input type="text" name="title" placeholder="请输入你要提问的问题的标题" class="u-ipt"></div><div class="cell-1"><input type="submit" value="提问" class="u-btn u-btn-c4"></div></div><div class="line"></div><!-- 问题的内容 --><div><textarea name="content" id="contentEditor" ></textarea><script type="text/javascript" >var editor = new Simditor( {textarea : $('#contentEditor'),placeholder : '请在这里输入问题的内容...',toolbar : true } );</script></div><div class="line"></div><!-- 最底部的提问按钮 --><div class="container  title-container"><div class="cell-11" style="height: 1px ;"></div><div class="cell-1"><input type="submit" value="提问" class="u-btn u-btn-c4"></div></div></div> <!-- 提问区域 结束 --></form>#else<div style="text-align:center ; min-height: 400px ; line-height: 400px ;">登录以后才能发起提问,请<a href="$path/login.html" >登录</a></div>#end<div class="line"></div><div class="container link-container"><a href="$path/list.do" >问题列表</a>|<a href="$path/index.html" >返回首页</a></div><div class="container copyright-container">&copy; 2017 爱问社区版权所有</div></body>
</html>

回答Servlet处理代码:

@WebServlet("/answer.do")
public class AnswerServlet extends HttpServlet {private static final long serialVersionUID = 8578962149437664830L;@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 从 请求中获得请求参数的值String id = request.getParameter("id");if (StringHelper.notEmpty(id)) {try {int topicId = Integer.parseInt(id); // 将字符串按照 十进制 解析问 int 类型数值// 根据得到的 问题的 主键 查询数据库,得到 详细信息final String SQL = "SELECT  id , title , content , publish_time , publish_ip , user_id  FROM t_topic WHERE id = ? ";ResultSet rs = JdbcHelper.query(SQL, topicId);Topic t = null;int userId = -1;if (rs.next()) { // 当 根据 问题的主键 获取到 问题信息时t = new Topic(); // 创建 Topic 对象t.setId(rs.getInt(1)); // 将 结果集 中的 该行数据 封装到 t 对象的 id 属性中t.setTitle(rs.getString(2));t.setContent(rs.getString(3));t.setPublishTime(rs.getTimestamp(4));t.setPublishIp(rs.getString(5));userId = rs.getInt(6);}JdbcHelper.release(rs); // 关闭结果集// 获得提问者final String getUserSQL = "SELECT id , username , password FROM t_user WHERE id = ? ";rs = JdbcHelper.query(getUserSQL, userId);if (userId != -1 && rs.next()) {User u = new User();// 封装数据u.setId(rs.getInt(1));u.setUsername(rs.getString(2));u.setPassword(rs.getString(3));// 将获取到的用户数据设置到 Topic 对象的 user 属性中t.setUser(u);}HttpSession session = request.getSession();session.setAttribute("topic", t);response.sendRedirect(request.getContextPath() + "/answer.html");return; // 让方法立即结束} catch (NumberFormatException e) {e.printStackTrace();// response.sendRedirect( request.getContextPath() + "/list.do" );} catch (SQLException e) {e.printStackTrace();// response.sendRedirect( request.getContextPath() + "/list.do" );}} else {// response.sendRedirect( request.getContextPath() + "/list.do" );}response.sendRedirect(request.getContextPath() + "/list.do");}}


回答前台代码:

<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>解答: $topic.title</title><link rel="stylesheet" href="$path/styles/general.css"><link rel="stylesheet" href="$path/styles/cell.css"><link rel="stylesheet" href="$path/styles/wen.css"><!-- 链接 simditor 的样式库 --><link rel="stylesheet"  href="$path/simditor/styles/simditor.css" type="text/css"><!-- 导入 simditor 的 JavaScript 库 --><script type="text/javascript" src="$path/simditor/scripts/jquery.min.js"></script><script type="text/javascript" src="$path/simditor/scripts/module.js"></script><script type="text/javascript" src="$path/simditor/scripts/hotkeys.js"></script><script type="text/javascript" src="$path/simditor/scripts/uploader.js"></script><script type="text/javascript" src="$path/simditor/scripts/simditor.min.js"></script></head><body>## 登录状态栏 开始<div class="login-status-container  auto-height"><span class="welcome cell-8">## 在 $ 之后 表达式之前使用 ! 表示 安静模式 ( 静默模式 )<b>欢迎$!user.username来到爱问社区</b></span><span class="login-operation cell-4">#if( $user )<a href="$path/ask.html">提问</a><em>|</em><a href="">我的提问</a><em>|</em><a href="$path/logout.do">注销</a>#else<a href="$path/login.html">登录</a><em>|</em><a href="$path/regist.html">注册</a>#end</span></div> ## 登录状态栏 结束## 标志区域<div class="brand-container  auto-height"><div class="logo cell-2"><a href="$path"></a></div><div class="slogan cell-10"><div>解答问题</div></div></div><div><h3>$topic.title</h3><div class="line"></div><div>$topic.content</div><div>提问时间: $topic.publishTime / 提问者: $topic.user.username</div></div><!-- 解答的内容 --><form action="$path/explain.do" method="post" ><input type="hidden" name="id" value="$topic.id" ><div><textarea name="content" id="contentEditor" ></textarea><script type="text/javascript" >var editor = new Simditor( {textarea : $('#contentEditor'),placeholder : '请在这里输入问题的内容...',toolbar : true } );</script></div><input type="submit" value="提交解答"></form>$scope.remove( $session , 'topic' );<div class="line"></div><div class="container link-container"><a href="$path/ask.html" >发起新问题</a>|<a href="$path/index.html" >返回首页</a></div><div class="container copyright-container">&copy; 2017 爱问社区版权所有</div></body>
</html>


以下是使用simditor的方法,需要引入simditor中的css和js样式。simditor下载地址:http://download.csdn.net/detail/weixin_36380516/9813448
<textarea name="content" id="contentEditor" ></textarea><script type="text/javascript" >var editor = new Simditor( {textarea : $('#contentEditor'),placeholder : '请在这里输入问题的内容...',toolbar : true } );</script>


解答问题Servlet处理,这里需要获取提问者,提问问题以及该问题的已有答案,已有答案回答者。

@WebServlet("/detail.do")
public class DetailServlet extends HttpServlet {private static final long serialVersionUID = -3202278077673657729L;@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 从 请求中获得请求参数的值String id = request.getParameter("id");if (StringHelper.notEmpty(id)) {try {int topicId = Integer.parseInt(id); // 将字符串按照 十进制 解析问 int 类型数值// 根据得到的 问题的 主键 查询数据库,得到 详细信息final String SQL = "SELECT  id , title , content , publish_time , publish_ip , user_id  FROM t_topic WHERE id = ? ";ResultSet rs = JdbcHelper.query(SQL, topicId);Topic t = null;/*int userId = -1;*/if (rs.next()) { // 当 根据 问题的主键 获取到 问题信息时t = new Topic(); // 创建 Topic 对象t.setId(rs.getInt(1)); // 将 结果集 中的 该行数据 封装到 t 对象的 id 属性中t.setTitle(rs.getString(2));t.setContent(rs.getString(3));t.setPublishTime(rs.getTimestamp(4));t.setPublishIp(rs.getString(5));User user = new User();user.setId(rs.getInt(6));t.setUser(user);}JdbcHelper.release(rs); // 关闭结果集// 获得提问者final String getUserSQL = "SELECT id , username , password FROM t_user WHERE id = ? ";rs = JdbcHelper.query(getUserSQL, t.getUser().getId());if(rs.next()) {User u = new User();// 封装数据u.setId(rs.getInt(1));u.setUsername(rs.getString(2));u.setPassword(rs.getString(3));// 将获取到的用户数据设置到 Topic 对象的 user 属性中t.setUser(u);System.out.println("message username:" + rs.getString(2));}JdbcHelper.release(rs); // 关闭结果集// 获得当前的问题的所有解答String explainSQL = "SELECT  id , content , explain_time , explain_ip , user_id  from t_explain where topic_id = ? ";rs = JdbcHelper.query(explainSQL, topicId);@SuppressWarnings("unused")int explainerId = -1;List<Explain> explains = new ArrayList<>();while (rs.next()) {Explain e = new Explain();e.setId(rs.getInt(1));e.setContent(rs.getString(2));e.setExplainTime(rs.getTimestamp(3));e.setExplainIp(rs.getString(4));User user=new User();user.setId(rs.getInt(5));e.setUser(user);explains.add(e);System.out.println("explain userID:" + rs.getInt(5));}// 获得解答者List<Explain>explainList = new ArrayList();for(int i=0;i<explains.size();i++){Explain explain1 = explains.get(i);final String getExplainerSQL = "SELECT id , username , password FROM t_user WHERE id = ? ";rs = JdbcHelper.query(getExplainerSQL, explain1.getUser().getId());if (rs.next()) {User u = new User();// 封装数据u.setId(rs.getInt(1));u.setUsername(rs.getString(2));u.setPassword(rs.getString(3));// 将获取到的用户数据设置到 Topic 对象的 user 属性中explain1.setUser(u);explainList.add(explain1);System.out.println("explain username:" + rs.getString(2));}JdbcHelper.release(rs); // 关闭结果集/*t.setExplains(explains); // 将解答设置到 Topic 对象上
*/		}t.setExplains(explainList);/*** 为 所有的解答,获取解答者的详细信息 ***/HttpSession session = request.getSession();session.setAttribute("topic", t);response.sendRedirect( request.getContextPath() + "/detail.html" );return; // 让方法立即结束} catch (NumberFormatException e) {e.printStackTrace();} catch (SQLException e) {e.printStackTrace();}} else {}response.sendRedirect(request.getContextPath() + "/list.do");}}

解答问题前台显示界面代码;

<!DOCTYPE html><html><head><meta charset="UTF-8"><title>$topic.title</title><link rel="shortcut icon" href="$path/images/icon.png" type="image/png"><link rel="stylesheet" href="$path/styles/general.css"><link rel="stylesheet" href="$path/styles/cell.css"><link rel="stylesheet" href="$path/styles/wen.css"></head><body>## 登录状态栏 开始<div class="login-status-container  auto-height"><span class="welcome cell-8">## 在 $ 之后 表达式之前使用 ! 表示 安静模式 ( 静默模式 )<b>欢迎$!user.username来到问道</b></span><span class="login-operation cell-4">#if( $user )<a href="$path/ask.html">提问</a><em>|</em><a href="">我的提问</a><em>|</em><a href="$path/logout.do">注销</a>#else<a href="$path/login.html">登录</a><em>|</em><a href="$path/regist.html">注册</a>#end</span></div> ## 登录状态栏 结束## 标志区域<div class="brand-container  auto-height"><div class="logo cell-2"><a href="$path"></a></div><div class="slogan cell-10"><div></div></div></div><div><h3>$topic.title</h3><div class="line"></div><div>$topic.content</div><div>提问时间: $topic.publishTime / 提问者: $topic.user.username</div></div><div>#foreach( $ex in $topic.explains)<div> $ex.content  </div><div class="line"></div>#end</div>$scope.remove( $session , 'topic' );<div class="line"></div><div class="container link-container"><a href="$path/ask.html" >发起新问题</a>|<a href="$path/index.html" >返回首页</a></div><div class="container copyright-container">&copy; 2017 爱问社区版权所有</div></body>
</html>

我的解答Servlet处理代码:

@WebServlet("/myAnswer.do")
public class MyAnswerServlet extends HttpServlet {private static final long serialVersionUID = -3020889403557912216L;@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	    HttpSession session = request.getSession();User user = (User) session.getAttribute( "user" ); // 在登录时将 User 对象放入了 会话 中Explain ex=null;final List<Explain> exp = new ArrayList<>();if( user != null ) {int myid=user.getId();final String SQL = "SELECT  id ,content ,explain_time , explain_ip , user_id ,topic_id  FROM t_explain WHERE user_id = ? ";ResultSet rs = JdbcHelper.query(SQL, myid);//Topic t=null;//final List<Explain> explains = new ArrayList<>();@SuppressWarnings("unused")int topicId=-1;try {while( rs.next() ){ex=new Explain();ex.setId(rs.getInt(1));ex.setContent(rs.getString(2));ex.setExplainTime(rs.getTimestamp( 3 ));ex.setExplainIp(rs.getString(4));ex.setUser(user);Topic to=new Topic();to.setId(rs.getInt(6));ex.setTopic(to);topicId=rs.getInt(6);exp.add(ex);}JdbcHelper.release(rs); // 关闭结果集for(int i = 0 , len = exp.size() ; i <  len ; i++){Explain explain=exp.get(i);Topic tid=explain.getTopic();final String tSQL = "SELECT  id , title , content , publish_time , publish_ip , user_id  FROM t_topic WHERE id = ? ";ResultSet trs = JdbcHelper.query(tSQL, tid.getId());while(trs.next()){Topic t=new Topic();t.setId(1);t.setTitle(trs.getString(2));t.setContent(trs.getString(3));t.setPublishTime(trs.getTimestamp(4));t.setPublishIp(trs.getString(5));ex.setTopic(t);
//			    explains.add(ex);System.out.println( "我的tid: " + tid.getId());}JdbcHelper.release(trs); // 关闭结果集}} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}session.setAttribute("explains", exp);System.out.println( "我的解答列表: " + exp );System.out.println( "我的id: " + myid);response.sendRedirect( request.getContextPath() + "/myAnswer.html");}}}

我的解答前台展示页面代码:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>解答</title><link rel="stylesheet" href="$path/styles/top.css"></head>
<body>
<div style="margin-left:320px;">
<nav id="topnav" class="f_r"><ul><a href="$path/index.html"">首页</a> <a href="$path/myQuestion.do" >我的提问</a><a href="$path/ask.html" >提问</a> <a href="$path/logout.do">注销</a></ul></nav></div>
#if( $user )
$user.username的所有回答:
#end
#foreach( $ex in $explains)
<div class="blogs">
<ul><p>$ex.content</p><div class="content_time"><p> 解答时间:<span class="dtime f_l"> $ex.explainTime</span></p></div><div class="line"></div>
</ul>
</div>
#end</body>
</html>

我的提问Servlet处理:

@WebServlet("/myQuestion.do")
public class MyQuestionServlet extends HttpServlet {private static final long serialVersionUID = -4110483126223561394L;@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	    HttpSession session = request.getSession();User user = (User) session.getAttribute( "user" ); // 在登录时将 User 对象放入了 会话 中if( user != null ) {int myid=user.getId();final String SQL = "SELECT id , title , content, publish_time , publish_ip FROM t_topic WHERE user_id = ? ";ResultSet rs = JdbcHelper.query(SQL, myid);final List<Topic> qtopics = new ArrayList<>();try {while( rs.next() ){Topic t=new Topic();t.setId(rs.getInt(1));t.setTitle(rs.getString(2));t.setContent(rs.getString(3));t.setPublishTime(rs.getTimestamp(4));t.setPublishIp(rs.getString(5));qtopics.add(t);}} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}session.setAttribute("qtopics", qtopics);System.out.println( "我的提问列表: " + qtopics );System.out.println( "我的id: " + myid);response.sendRedirect( request.getContextPath() + "/myQuestion.html");}}}

我的提问展示代码:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>$user.username的提问 </title><link rel="stylesheet" href="$path/styles/general.css"><link rel="stylesheet" href="$path/styles/cell.css"><link rel="stylesheet" href="$path/styles/wen.css"><link rel="stylesheet" href="$path/styles/top.css">
</head>
<body><div style="margin-left:320px;"><ul>#if($user)<nav id="topnav" class="f_r"><ul><a href="$path/index.html">首页</a><a href="$path/myAnswer.do" >我的解答</a><a href="$path/ask.html">提问</a><a href="$path/logout.do" >注销</a></ul></nav>#else<li class="presentation"><a href="$path/login.html" id="link" title="提问">登录</a></li><li class="presentation"><a href="$path/regist.do" id="tools" title="exit">注册</a></li>#end</ul>
</div>#if( $user )$user.username的所有提问:#end#foreach( $qto in $qtopics)<div class="blogs"><ul><p>$qto.content</p><p class="autor"><span class="lm f_l"><a>提问者:$user.username</a></span>  <span class="dtime f_l">$qto.publishTime</span></p></ul></div>#end</body>
</html>


验证码处理的Servlet代码:

@WebServlet(urlPatterns= { "/verify/login.do" , "/verify/regist.do" } )
public class VerifyCodeServlet extends HttpServlet {private static final long serialVersionUID = 3398560501558431737L;@Overrideprotected void service( HttpServletRequest request , HttpServletResponse response ) throws ServletException, IOException {// 获得 当前请求 对应的 会话对象HttpSession session = request.getSession();// 从请求中获得 URI ( 统一资源标识符 )String uri = request.getRequestURI();System.out.println( "hello : " + uri );final int width = 180 ;  // 图片宽度final int height = 40 ; // 图片高度final String imgType = "jpeg" ; // 指定图片格式 (不是指MIME类型)final OutputStream output = response.getOutputStream(); // 获得可以向客户端返回图片的输出流 (字节流)// 创建验证码图片并返回图片上的字符串String code = GraphicHelper.create( width, height, imgType, output );System.out.println( "验证码内容: " + code );// 建立 uri 和 相应的 验证码 的关联 ( 存储到当前会话对象的属性中 )session.setAttribute( uri , code );System.out.println( session.getAttribute( uri ) );}}

注册前台界面,有验证码验证功能:

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>注册爱问社区</title><link rel="stylesheet" href="$path/styles/general.css"><link rel="stylesheet" href="$path/styles/cell.css"><link rel="stylesheet" href="$path/styles/form.css"><link rel="stylesheet" href="$path/awesome/css/font-awesome.min.css"><script type="text/javascript" src="$path/js/wen.js"></script><style type="text/css" >.logo-container {margin-top: 50px ;}.logo-container img {width: 100px ;}.message-container {height: 80px ;}.link-container {height: 40px ;line-height: 40px ;}.link-container a {text-decoration: none ;}</style></head>
<body><div class="container title-container" style="color:blue; margin-top:60px;">加入爱问社区,为您答疑解惑</div>
<div class="container form-container"><form action="$path/regist.do" method="post"><div class="form"> <!-- 注册表单开始 --><div class="form-row"><span class="cell-1"><i class="fa fa-user"></i></span><span class="cell-11" style="text-align: left;"><input type="text" name="username" placeholder="请输入用户名"></span></div><div class="form-row"><span class="cell-1"><i class="fa fa-key"></i></span><span class="cell-11" style="text-align: left;"><input type="password" name="password" placeholder="请输入密码"></span></div><div class="form-row"><span class="cell-1"><i class="fa fa-keyboard-o"></i></span><span class="cell-11" style="text-align: left;"><input type="password" name="confirm" placeholder="请确认密码"></span></div><div class="form-row"><span class="cell-7"><input type="text" name="verifyCode" placeholder="请输入验证码"></span><span class="cell-5" style="text-align: center;"><img src="$path/verify/regist.do"  οnclick="myRefersh(this)"></span></div><div class="form-row" style="border: none;"><span class="cell-6" style="text-align: left"><input type="reset" value="重置"></span><span class="cell-6"  style="text-align:right;"><input type="submit" value="注册"></span></div></div> <!-- 注册表单结束 --></form>
</div>
<div class="container message-container"><!-- 显示提示信息的地方 -->#if( $registFail ) $registFail$scope.remove( $session , 'registFail' )#end
</div>
<div class="container link-container"><a href="$path/login.html" > 已注册爱问帐号,点击这里登录</a>|<a href="$path/index.html" >返回首页</a>
</div>
<div class="container copyright-container">&copy; 2017 爱问社区版权所有
</div></body>
</html>

Servlet处理注册,实现验证码验证:

@WebServlet("/regist.do")
public class RegistServlet extends HttpServlet {private static final long serialVersionUID = 7493633832455111617L;@Overrideprotected void service( HttpServletRequest request , HttpServletResponse response ) throws ServletException, IOException {// 获得来自 页面 表单上的数据String verifyCode = request.getParameter( "verifyCode" ) ; // 获得由用户输入的那个验证码String username = request.getParameter( "username" ) ;String password = request.getParameter( "password" ) ;String confirm = request.getParameter( "confirm" ) ;System.out.println( "username : " + username );System.out.println( "password : " + password );System.out.println( "confirm : " + confirm );System.out.println( "verifyCode : " + verifyCode );HttpSession session = request.getSession();// 获得 在 会话 中存储的那个 为登录进行验证的 验证码final String code = (String)session.getAttribute( "/wendao/verify/regist.do" );System.out.println( "session code : " + code );// 比较验证码if( StringHelper.equals( verifyCode , code ) ){// 要保证 用户名 不为空 、密码不能为空 、两次输入的密码必须一致if( StringHelper.notEmpty( username ) && StringHelper.notEmpty( password ) && StringHelper.equals( password , confirm) ) {// 可以保存了 String SQL = "INSERT INTO t_user ( username , password ) VALUES ( ? , ? ) " ;int n = JdbcHelper.insert( SQL , false , username , StringHelper.MD5(password));if( n > 0 ) { // 如果 insert 返回 大于 0 的数字 , 则表示 插入成功// 保存成功以后,应该去一个新的页面 ( 比如去 登录页面 )response.sendRedirect( request.getContextPath() + "/login.html" );} else {// 回到注册页面去session.setAttribute( "registFail" , "注册失败,可能是用户名被占用了" );response.sendRedirect( request.getContextPath() + "/regist.html" );}} else {// 回到注册页面去session.setAttribute( "registFail" , "用户名或密码为空,或者密码不一致" );response.sendRedirect( request.getContextPath() + "/regist.html" );}} else {// 如果验证码不一致,设置提示信息后回到注册页面去session.setAttribute( "registFail" , "验证码输入错误,请重新输入" );response.sendRedirect( request.getContextPath() + "/regist.html" );}}}

登录Servlet处理代码:

@WebServlet("/login.do")
public class LoginServlet extends HttpServlet {private static final long serialVersionUID = 18854422651747352L;@Overrideprotected void service( HttpServletRequest request , HttpServletResponse response ) throws ServletException, IOException {// 获得来自 页面 表单上的数据String username = request.getParameter( "username" ) ;String password = StringHelper.MD5(request.getParameter( "password" )) ;System.out.println( "username : " + username );System.out.println( "password : " + password );HttpSession session = request.getSession();// 登录 : 根据 用户名 和 密码 从数据库中查询数据,如果都正确,就将这些数据放入到会话中,最后进入到指定页面( list.html )String SQL = "SELECT id , username , password FROM t_user WHERE username = ? and password = ? " ;ResultSet rs = JdbcHelper.query( SQL,  username , password ) ;try{// 如果查询到数据,就包装到一个对象中if( rs.next() ) {User user = new User(); // 创建对象// 封装数据user.setId( rs.getInt( 1 ) );user.setUsername( rs.getString( 2 ));user.setPassword( rs.getString( 3 ) ) ;//System.out.println("测试"+MD5.convertMD5(MD5.convertMD5(password)));/** 将 User 对象 放入到 会话中 **/session.setAttribute( "user" , user );// 重定向到 list.do ( list.do 会先查询数据后 再 重定向到 list.html )response.sendRedirect( request.getContextPath() + "/list.do" );} else {// 如果 用户名 或 密码 错误,重新返回到 登录页面session.setAttribute( "loginFail" , "用户名或密码错误" );response.sendRedirect( request.getContextPath() + "/login.html" );}} catch ( SQLException e ){e.printStackTrace();}}    
}

登录前台展示界面代码;

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>登录</title><link rel="stylesheet" href="styles/general.css"><link rel="stylesheet" href="styles/cell.css"><link rel="stylesheet" href="styles/form.css"><link rel="stylesheet" href="awesome/css/font-awesome.min.css"><link rel="stylesheet" type="text/css" href="css/login.css"><script type="text/javascript" src="js/wen.js"></script></head>
<body><div class="logina-logo" style="height: 55px"><div id="venusLogo"><p><span>爱问社区</span></p></div></div><div class="logina-main main clearfix"><div class="tab-con"><form action="$path/login.do" method="post"><div id='login-error' class="error-tip"></div><table border="0" cellspacing="0" cellpadding="0"><tbody><tr><th>账户</th><td width="245"><input type="text" name="username" id="username" placeholder="昵称" /> <td></td></tr><tr><th>密码</th><td width="245"><input type="password" name="password" id="password" placeholder="密码" /></td><td></td></tr><tr><th></th><td width="245"><input class="confirm" type="submit" value="登  录"></td><td></td></tr></tbody></table></form></div><div class="reg"><p>还没有账号?<br>赶快免费注册一个吧!</p><a class="reg-btn" href="regist.html">立即免费注册</a></div></div><div id="footer"><div class="copyright">Copyright © 爱问社区 版权所有</div></div>   
</body>
</html>

好啦,基本的代码就展示完了,还有比较通用的POJO类和jdbc连接数据库的类就不做展示了,贴上数据库SQL代码,需要的可以根据字段来写

DROP TABLE IF EXISTS `t_explain`;
CREATE TABLE `t_explain` (`id` int(11) NOT NULL AUTO_INCREMENT,`content` text,`explain_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,`explain_ip` varchar(50) DEFAULT NULL,`user_id` int(8) DEFAULT NULL,`topic_id` int(8) DEFAULT NULL,PRIMARY KEY (`id`),KEY `ex_id` (`user_id`),KEY `t_id` (`topic_id`),CONSTRAINT `ex_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`),CONSTRAINT `t_id` FOREIGN KEY (`topic_id`) REFERENCES `t_topic` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for t_topic
-- ----------------------------
DROP TABLE IF EXISTS `t_topic`;
CREATE TABLE `t_topic` (`id` int(10) NOT NULL AUTO_INCREMENT,`title` varchar(255) DEFAULT NULL,`content` text,`publish_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,`publish_ip` varchar(100) DEFAULT NULL,`user_id` int(10) DEFAULT NULL,PRIMARY KEY (`id`),KEY `cid` (`user_id`),CONSTRAINT `cid` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user` (`id` int(10) NOT NULL AUTO_INCREMENT,`username` varchar(20) NOT NULL,`password` varchar(255) NOT NULL,PRIMARY KEY (`id`),UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8;

源码工程地址:https://github.com/guodalin8/wendao      哪里有问题欢迎留言,看到第一时间解答。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_742876.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LAMP-----3、wordpress网站从web01复制到web02中

需要把web01里面的wordpress程序拷贝到web02中 在web01上操作&#xff1a;[rootweb01 ~]# cd /application/nginx/html/[rootweb01 html]# lltotal 52-rw-r--r-- 1 root root 537 Feb 13 10:41 50x.htmldrwxr-xr-x 2 root root 4096 Feb 22 11:35 bbsdrwxr-xr-x 7 www www 4096…

网站时常出现too many connection的错误

安装了一个程序&#xff0c;大访问量测试的时候发现竟然连接不上数据库了&#xff0c;仔细检查发现MySQL数据库出现1040错误&#xff0c;提示“too many connections”。那么改如何解决这个问题呢&#xff1f; 其实MySQL默认的最大连接数为100&#xff0c;可能在大访问量的时候…

IIS 发布添加网站错误:HTTP 错误 500.21 - Internal Server Error 解决方案

原因&#xff1a;在安装Framework v4.0之后&#xff0c;再启用IIS&#xff0c;导致Framework没有完全安装 解决&#xff1a;开始->所有程序->附件->鼠标右键点击“命令提示符”->以管理员身份运行->%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.…

Nginx 配置 SSL 证书 + 搭建 HTTPS 网站教程

2019独角兽企业重金招聘Python工程师标准>>> 一、HTTPS 是什么&#xff1f; 根据维基百科的解释&#xff1a; 超文本传输安全协议&#xff08;缩写&#xff1a;HTTPS&#xff0c;英语&#xff1a;Hypertext Transfer Protocol Secure&#xff09;是超文本传输协议和…

30个免费下载高质量精美照片素材的网站

在设计中使用免费的照片素材可以节省大量的时间。网络有有很多分享照片资源的网站&#xff0c;有些是免费的&#xff0c;有些则需要付费&#xff0c;当你在设计中使用网上的照片素材的时候要注意使用协议以免带来麻烦。今天这篇文章向大家推荐30个国外的免费下载高质量精美图片…

网站技术架构学习整体贴-《大型网站技术架构》

大牛整理图 (来自https://blog.csdn.net/kissqw/article/details/45198639) 整体总结帖 大型网站技术架构&#xff1a;摘要与读书笔记&#xff1a; https://www.cnblogs.com/xybaby/p/8907880.html 作者专访 Csdn专访李智慧&#xff1a; https://www.csdn.net/article/2015-0…

分享13个自学编程的优质网站

后台经常有粉丝发问&#xff1a;编程适合零基础的人自学么&#xff1f;自学编程的学习资源应该去哪里找&#xff1f;网络上免费的编程资源价值大么&#xff1f;...... 如今&#xff0c;很大一部分人在学习编程的道路上都选择自学&#xff0c;但都苦于找不到适合自己的学习资源…

【网络安全】两招识破钓鱼网站

㈠ 查 Alexa 排名 http://www.alexa.com 越靠前、真实性越高以 中国建设银行为例 Alexa Traffic Rank&#xff1a;Alexa网站流量排名 Traffic Rank in CN&#xff1a;国内流量排名 ㈡ 查 Google 的 PR 值 http://pr.chinaz.com PR(Page Rank)∈[0,10] 越大、表明该网页越重要 小…

我的网站搭建 (第十天) Ueditor编辑器

2019独角兽企业重金招聘Python工程师标准>>> 之前说过&#xff0c;我的网站编辑器一开始是tinymce&#xff0c;然后才用的ckeditor。可是最近我发现&#xff0c;ckeditor的小图标不是很美观&#xff0c;看久了有点low的样子。我是不是应该换一个编辑器呢&#xff0c…

如何屏蔽网站发起favicon.ico的请求

2019独角兽企业重金招聘Python工程师标准>>> favicon.ico 图标用于收藏夹图标和浏览器标签上的显示&#xff0c;如果不设置&#xff0c;浏览器会请求网站根目录的这个图标&#xff0c;如果网站根目录也没有这图标会产生 404。更有甚者&#xff0c;比如我司&#xff…

关于资源类网站我就只服这4个,每一个都能让你大开眼界!

对于经常从事互联网行业的工作者来说&#xff0c;储藏一些资源网站是必备的&#xff0c;方便我们随时将网上的资源一扫而进。今天小编给大家推荐4个实用性超强的资源网站&#xff01;GrowthTools 在线极客工具大全GrowthTools是一个帮助网络工作者收集实用工具的网站&#xff0…

Java Web架构篇之浅谈大型网站分布式架构

大型网站技术架构概述 图片摘自&#xff1a; 大型网站技术架构&#xff1a;摘要与读书笔记 更多&#xff1a; 《大型网站技术架构》——第一章 大型网站架构演化 架构模式 分层&#xff1a;横向切分 – 应用层、服务层、数据层分割&#xff1a;纵向切分 – 业务切分分布式&a…

对公司网站DNS解析异常的排查与处理

这周可谓是屋漏连夜雨&#xff0c;先是nfs挂载出现问题&#xff0c;紧接着住处的厕所堵了&#xff0c;然后又是今天的域名解析异常导致服务访问返回404。 我们的域名是解析到两个IP&#xff0c;分别是电信跟网通。 异常情景&#xff1a; 用户打开页面登录后图片上传跟显示出现异…

分享一个定制生成前端配置文件的网站

网址https://webpack.jakoblind.no/ 这个网站可以根据需要&#xff0c;基于webpack4下定制配置文件&#xff0c;无需理解webpack架构的细节 网页分三个部分&#xff1a; 选择配置&#xff1a;选择您所需要安装的内容可视化配置&#xff1a;根据上图所选的内容&#xff0c;显示所…

javaweb:在线聊天网站

前言&#xff1a; 最近学了一堆网页相关的内容&#xff0c;html,js,jquery,javaweb,servlet,websocket等东西&#xff0c;所以就试着集合起来写个简单的网页&#xff0c;于是就决定就是你了&#xff1a;在线聊天网站。 而且还写了3个版本。。。 版本1&#xff1a;全是用的js…

零基础搭建PHP本地环境并安装WordPress网站(图文指导)

搭建PHP本地环境前言 以前在大学课堂上学过一点JAVA, PHP. 因为那时是零基础&#xff0c;需要自己搭建APACH, MYSQL, PHPADMIN过程挺烦的&#xff0c;本地环境都不知道是啥&#xff0c;但是做出来结果却很高兴。出来后也做过网站&#xff0c;但是很久不弄就忘记了&#xff0c;我…

javaweb:在线聊天网站(框架版)

之前写过一次在线聊天网站&#xff0c;不过那次是无框架版的&#xff0c;这次用框架构建网站&#xff0c;基本功能和上次差不多 涉及知识 java spring&#xff08;4.3.5&#xff09;&#xff1a;spring、spring MVC hibernate bootstrap jsp JavaScript&#xff0c;jquer…

利用TortoiseGit(小乌龟)将项目上传至GitHub网站

2019独角兽企业重金招聘Python工程师标准>>> 准备 1.拥有一个GitHub账户2.安装了TortoiseGit&#xff08;小乌龟&#xff09;具体过程 一、在GitHub上建立新的仓库 起好仓库名&#xff0c;填好描述&#xff0c;在Add .gitgnore中选择Java&#xff08;根据你自己需求…

一些缩短树莓派学习曲线的书籍、课程和网站

2019独角兽企业重金招聘Python工程师标准>>> 树莓派是一款小型单板计算机&#xff0c;最初用于教学和学习编程和计算机科学。但如今它有更多用处。它是一种经济的低功耗计算机&#xff0c;人们将它用于各种各样的事情 —— 从家庭娱乐到服务器应用&#xff0c;再到物…

大型分布式网站架构实战项目分析

2019独角兽企业重金招聘Python工程师标准>>> 大型分布式网站架构实战项目分析 转载于:https://my.oschina.net/u/1168056/blog/1802675