个人建议 这个项目 还是蛮不错的 有很多注意思考的地方
所用技术 以及环境介绍
环境搭建 配置
技术选层
-
Web层
Servlet 前端控制器 Html 视图 数据展示 效果展示 Filter 过滤器 Beanutils 数据封装 Jackson json序列化工具
-
Service层
Javamail 邮件发送邮件 Redis nosql内存数据库 Jedis java的redis的客户端
-
Dao层 数据访问层
Mysql 数据库 Druid数据库连接池 JdbcTempalte jdbc的工具
服务器使用的是Tomcat 数据库mysql
导入对应的pom.xml时候 对应加载Maven
过滤器 防止出现乱码
//将父接口转为子接口
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) rep;
//获取请求方法
String method = request.getMethod();
//解决post请求中文数据乱码问题
if(method.equalsIgnoreCase("post")){
request.setCharacterEncoding("utf-8");
}
//处理响应乱码
response.setContentType("text/html;charset=utf-8");
filterChain.doFilter(request,response);
项目结构
注册功能 表单展示
Register.html
使用js完成表单检验
使用ajax完成表单提交
注册完成跳转到成功页面
regisUserServlet
获取数据
封装User对象
调用service完成注册
根据service的返回 提示信息
1. 将提示信息转为json
2. 设置响应头contertType
对应的注册提交表单后 后端处理 核心代码 清楚session会话域 防止验证码因为刷新失效
@WebServlet("/registerUserServlet")
public class RegisUserServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 验证码
String check = req.getParameter("check");
// 从session中获取
HttpSession session = req.getSession();
String checkcode_server = (String) session.getAttribute("CHECKCODE_SERVER");
// 清楚Session会话域 保证验证码只能使用一次
session.removeAttribute("CHECKCODE_SERVER");
// 比较
if(checkcode_server == null || !checkcode_server.equalsIgnoreCase(check)){
// 验证码 错误
ResultInfo info = new ResultInfo();
// 注册失败
info.setFlag(false);
info.setErrorMsg("验证码错误");
// 将info对象序列化为json
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(info);
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write(json);
return ;
}
Map<String, String[]> map = req.getParameterMap();
User user = new User();
try {
BeanUtils.populate(user,map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 调用service完成注册
UserService service = new UserServiceImpl();
boolean flag= service.regist(user);
ResultInfo info = new ResultInfo();
// 响应结果
if(flag){
// 注册成功
info.setFlag(true);
}else{
// 注册失败
info.setFlag(false);
info.setErrorMsg("注册失败");
}
// 将info对象序列化为json
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(info);
// 将json数据写回客户端
// 设置content-type
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write(json);
}
注册完成之后会进行一个邮箱的注册激活认证 需要邮箱的验证
工具类的展示
/**
* 发邮件工具类
*/
public final class MailUtils {
private static final String USER = ""; // 发件人称号,同邮箱地址
private static final String PASSWORD = ""; // 如果是qq邮箱可以使户端授权码,或者登录密码
/**
*
* @param to 收件人邮箱
* @param text 邮件正文
* @param title 标题
*/
/* 发送验证信息的邮件 */
public static boolean sendMail(String to, String text, String title){
try {
final Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.host", "smtp.qq.com");
// 发件人的账号
props.put("mail.user", USER);
//发件人的密码
props.put("mail.password", PASSWORD);
// 构建授权信息,用于进行SMTP进行身份验证
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// 用户名、密码
String userName = props.getProperty("mail.user");
String password = props.getProperty("mail.password");
return new PasswordAuthentication(userName, password);
}
};
// 使用环境属性和授权信息,创建邮件会话
Session mailSession = Session.getInstance(props, authenticator);
// 创建邮件消息
MimeMessage message = new MimeMessage(mailSession);
// 设置发件人
String username = props.getProperty("mail.user");
InternetAddress form = new InternetAddress(username);
message.setFrom(form);
// 设置收件人
InternetAddress toAddress = new InternetAddress(to);
message.setRecipient(Message.RecipientType.TO, toAddress);
// 设置邮件标题
message.setSubject(title);
// 设置邮件的内容体
message.setContent(text, "text/html;charset=UTF-8");
// 发送邮件
Transport.send(message);
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}
再用户激活的时候
进入UserDaoimpl中进行修改 加入存储 status 和 code的代码逻辑
后台代码实现
编写 RegisUserServlet
编写UserService以及UserSericelmpl
编写UserDao 以及UserDaoImpl
登录功能 表单展示
login页面中的表单提交
$(function () {
// 给登录按钮绑定单击事件
$("#btn_sub").click(function(){
// 发送ajax请求 提交表单数据
$.post("user/login",$("#loginForm").serialize(),function (data){
// data : {flag:false ,errorMsg:''}
if(data.flag){
// 登录成功
location.href="index.html"
}else{
// 登录失败
$("#errorMsg").html(data.errorMsg);
}
});
});
});
index页面中用户姓名的提示信息功能 ‘
再登录完成之后 需要将用户名 存入到session中 对后面的欢迎: 用户 使用
if(u !=null && "Y".equals(u.getStatus())){
// 登录成功
info.setFlag(true);
// 登录成功之后 将页面中中获得名字存入到session 为之后登录之后名字进行显示
req.getSession().setAttribute("user",u);
}
对应的findUserServlet
// 从session中获取登录用户
Object user = req.getSession().getAttribute("user");
// 将user写回客户端
ObjectMapper mapper = new ObjectMapper();
resp.setContentType("application/json;charset=utf-8");
mapper.writeValue(resp.getOutputStream(),user);
登录的条件就是 session 中有user对象
实现步骤
访问servlet 将session销毁
跳转到登录页面
登录成功判断的三种对应的情况’
// 登录对应的三种登录可能型
// 1 可能不存在对饮固定用户名
// 2 存在账号和密码 正确 但是没有正确的激活状态
// 3 账号和密码 都存正确
优化Servelt 进行方法的抽取
减少Servlet 的数量 现在是一个Servlet 将其中优化为 一个模块 一个Servlet 相当于在数据库中一张表对应一个Servlet 在Servlet中提供不同的方法 完成用户的请求
中间继承方法 BaseServlet
完成方法的分发 拿到最后的路径方法名 获取方法对象 通过反射方式来执行这个方法
// 继承HttpServlet 实现其中对应的service 方法
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 完成方法的分发 拿到路径最后的方法名 获取方法对象 通过反射方式来执行这个方法
// 获取请求路径
String uri=req.getRequestURI(); // user/add
// 获取方法名称
String methodName = uri.substring(uri.lastIndexOf('/')+1);
// 获取方法对象Method
try {
// 忽略访问权限修饰符 获取方法
Method method = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
// 执行方法
// 暴力反射
method.setAccessible(true);
method.invoke(this,req,resp);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 方法的抽取 直接提高对应方法的使用 减少代码的冗余
// 直接将传入的对象序列化为json 并且写回客户端
public void writeValue(Object obj, HttpServletResponse resp) throws IOException {
// 序列化 json返回
ObjectMapper mapper = new ObjectMapper();
resp.setContentType("application/json;charset=utf-8");
mapper.writeValue(resp.getOutputStream(),obj);
}
// 将传入的对象序列化为json 返回
public String writeValueAsString(Object obj) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(obj);
设置字符参数 控制台乱码问题解决: 会出现乱码
File—Build —Maven — Runner –VM Options --Dfile.encodeing=gb2312
分类数据展示功能
存在问题:
每次刷新都会请求数据库加载 分类数据 分类数据不会经常产生变化,
对数据库的压力大 采用缓存 减少对数据库的访问次数
查询效率高 故使用redis进行缓存
Index.html 包含 header.html
发送ajax请求访问服务器 加载真正的分类数据
遍历数组
CateaggeoryServlet
FindAll(){
调用Servlet查询list
将List集合序列化为json返回
}
CateaggeoryServlet
private CategoryService service = new CategoryServiceImpl();
// 查询所有分类数据展示
public void findAll(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 调用serice 查询所有
List<Category> cs = service.findAll();
// 序列化json返回
ObjectMapper mapper = new ObjectMapper();
resp.setContentType("application/json;charset=utf-8");
mapper.writeValue(resp.getOutputStream(),cs);
对应的FindAll
// 控制对应的 cid rname 以及对应的页码显示
@Override
public List<Category> findAll() {
// 从redis中查询
// 获取jedis客户端
Jedis jedis = JedisUtil.getJedis();
// 查询sortedset中的分数(cid)和值(cname)
Set<Tuple> categorys = jedis.zrangeWithScores("category", 0, -1);
List<Category> cs = null;
// 判断查询的集合是否为空
if (categorys == null || categorys.size() == 0) {
// 如果为空,需要从数据库查询,在将数据存入redis
// 从数据库查询
cs = categoryDao.findAll();
// 将集合数据存储到redis中的 category的key
for (int i = 0; i < cs.size(); i++) {
jedis.zadd("category", cs.get(i).getCid(), cs.get(i).getCname());
}
} else {
// 如果不为空,将set的数据存入list 查询的是set 但是返回需要List集合 所以进行集合之间的转换
cs = new ArrayList<Category>();
for (Tuple tuple : categorys) {
Category category = new Category();
category.setCname(tuple.getElement());
category.setCid((int)tuple.getScore());
cs.add(category);
}
}
return cs;
对分类的数据进行缓存优化
分类的数据在每一次页面加载后都会重新请求数据库加载 对数据库的压力比较大 而且分类的数据不经常产生变化 所以可以使用redis来缓存这个数据
用redis进行缓存的优化
在CategoryService
从redis中查询是否为null
是
第一次访问查询数据库 将数据库存入redis中
否
不是第一次访问
返回集合
CateoryDao
均执行dindAll() 方法
redis使用的时候 一定不要忘记打开redis
reids 打开的 win+R cd 进入对应的redis文件夹
使用 redis-server.exe redis.windows.conf
reids 打开的 win+R cd 进入对应的redis文件夹
使用 redis-server.exe redis.windows.conf
如果不可以则使用一下命令
redis-client.exe
shutdown
exit
然后重新用以上的命令
旅游线路的分页展示
点击发现不同的分类后 将来看到的旅游线路不一样的 通过分析数据库表结构 发 旅游线路表和分类表一个 多对一的关系
通过以下三个类功能进行实现
RouteSerlvet
RouteService
RouteDao
Select * from tab_route where cid = ? ;
Redis中查询score(cid)
‘
- <a Href =”source_list.html?cid = ‘+data[i].cid+’”>’+data[i].cname+’
-
类别id的传递
Redis中查询score(cid) 页面传递cid Header.html传递cid
分页展示旅游线路数据
同步方式 可以将数据放入 request域中 在jsp中使用foreach标签和el 表达式 进行遍历数据 生成数据
Html只能通过异步的方式 完成前台页面的展示
客户端页面
客户端页面发送的ajax请求PageBean的数据
携带参数
currentPage (当前页码)
pageSize(每页显示的条数)
cid(分页cid)在RouteServlet进行响应和处理 此处的判断条件需要判空处理 防止出现空指针
public void pageQuery(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 接受 三个参数 String currentPageStr = request.getParameter("currentPage"); String pageSizeStr = request.getParameter("pageSize"); String cidStr = request.getParameter("cid"); //接受rname 线路名称 // 再获取对应的rname时候需要做出对应的 条件限制 否在再不输入rname的时候 会造成对应的空指针异常 // 尤其是再分页查询的时候 再点击第二页的时候 会出现 不能正常显示到数据的现象 String rname = request.getParameter("rname"); if(rname!= null && rname.length()>0 && !"null".equals(rname)){ rname = new String(rname.getBytes("iso-8859-1"),"utf-8"); } int cid = 0;//类别id // 处理参数 防止出现空指针异常 if(cidStr != null && cidStr.length() > 0 && !"null".equals(cidStr)){ cid = Integer.parseInt(cidStr); } //当前页码,如果不传递,则默认为第一页 int currentPage = 0; if(currentPageStr != null && currentPageStr.length() > 0 ){ currentPage = Integer.parseInt(currentPageStr); }else{ currentPage = 1; } //每页显示条数,如果不传递,默认每页显示5条记录 int pageSize = 0; if(pageSizeStr != null && pageSizeStr.length() > 0 ){ pageSize = Integer.parseInt(pageSizeStr); }else{ pageSize = 5; } // 调用service查询PageBean对象 PageBean<Route> pb = routeService.pageQuery(cid, currentPage, pageSize,rname); // 将pageBean对象序列化为json,返回 writeValue(pb,response);
服务器端 PageBean 将被序列化为json 返回到客户端
// 分页对象 // 将参数封装到PageBean中 public class PageBean<T> { private int totalCount; // 总记录数 private int totalPage; // 总页数 private int currentPage; // 当前页码 private int pageSize; // 每页显示的条数 private List<T> list;//每页显示的数据集合
对应的PgeQuery
public void pageQuery(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 接受 三个参数 String currentPageStr = request.getParameter("currentPage"); String pageSizeStr = request.getParameter("pageSize"); String cidStr = request.getParameter("cid"); //接受rname 线路名称 // 再获取对应的rname时候需要做出对应的 条件限制 否在再不输入rname的时候 会造成对应的空指针异常 // 尤其是再分页查询的时候 再点击第二页的时候 会出现 不能正常显示到数据的现象 String rname = request.getParameter("rname"); if(rname!= null && rname.length()>0 && !"null".equals(rname)){ rname = new String(rname.getBytes("iso-8859-1"),"utf-8"); } int cid = 0;//类别id // 处理参数 防止出现空指针异常 if(cidStr != null && cidStr.length() > 0 && !"null".equals(cidStr)){ cid = Integer.parseInt(cidStr); } //当前页码,如果不传递,则默认为第一页 int currentPage = 0; if(currentPageStr != null && currentPageStr.length() > 0 ){ currentPage = Integer.parseInt(currentPageStr); }else{ currentPage = 1; } //每页显示条数,如果不传递,默认每页显示5条记录 int pageSize = 0; if(pageSizeStr != null && pageSizeStr.length() > 0 ){ pageSize = Integer.parseInt(pageSizeStr); }else{ pageSize = 5; } // 调用service查询PageBean对象 PageBean<Route> pb = routeService.pageQuery(cid, currentPage, pageSize,rname); // 将pageBean对象序列化为json,返回 writeValue(pb,response); }
旅游线路名称查询 模糊查询
查询参数的传递 -------------------- 点击搜索按钮 触发搜索事件
在header.html中
给按钮设置id 绑定响应事件$(function () { // 查询分类数据 // 设置确定数据访问的路径 $.post("category/findAll",{},function (data) { var lis = ' <li class="nav-active"><a href="index.html">首页</a></li>'; // 遍历数组 拼接字符串 for(var i =0; i<data.length; i++){ // 后面根据 cid 进行访问查询对应的 数据库内容 var li = '<li><a href="route_list.html?cid='+data[i].cid+'">'+data[i].cname+'</a><li>'; lis += li; } //拼接收藏排行榜 lis+'<li><a href="favoriterank.html">收藏排行榜</a></li>'; // 将lis字符串 设置到ul的html内容中 $("#category").html(lis); }); // 给搜索按钮绑定单击事件 获取搜索输入框的内容 $("#search-button").click(function () { // 需要线路名称 用作模糊匹配 var rname=$("#search_input").val(); // 跳转路径 进行拼接 var cid=getParameter("cid"); location.href="http://localhost/travel/route_list.html?cid="+cid+"&rname="+rname; }); });
修改后台代码
先进入CategoryServlet 调用service.findAll()
对应进入categoryDao 调用categoryDao.findAll();// 调用serice 查询所有 List<Category> cs = service.findAll();
对应的findAll
private CategoryDao categoryDao = new CategoryDaoImpl(); // 控制对应的 cid rname 以及对应的页码显示 @Override public List<Category> findAll() { // 从redis中查询 // 获取jedis客户端 Jedis jedis = JedisUtil.getJedis(); // 查询sortedset中的分数(cid)和值(cname) Set<Tuple> categorys = jedis.zrangeWithScores("category", 0, -1); List<Category> cs = null; // 判断查询的集合是否为空 if (categorys == null || categorys.size() == 0) { // 如果为空,需要从数据库查询,在将数据存入redis // 从数据库查询 cs = categoryDao.findAll(); // 将集合数据存储到redis中的 category的key for (int i = 0; i < cs.size(); i++) { jedis.zadd("category", cs.get(i).getCid(), cs.get(i).getCname()); } } else { // 如果不为空,将set的数据存入list 查询的是set 但是返回需要List集合 所以进行集合之间的转换 cs = new ArrayList<Category>(); for (Tuple tuple : categorys) { Category category = new Category(); category.setCname(tuple.getElement()); category.setCid((int)tuple.getScore()); cs.add(category); } } return cs; }
Dao数据库种的查询
// 查询所有对应的数据 public List<Category> findAll() { List<Category> list = new ArrayList<>(); try{ String sql = "select * from tab_category"; list = template.query(sql,new BeanPropertyRowMapper<>(Category.class)); }catch (Exception e){ } return list; }
对结果集合进行返回 将对应的数据填充到前端位置
查看详情
Route_detail.html? rid =1
当页码加载时 发送ajax 请求 查询Route对象查看详情 所对应的 超链接 再超连接上面绑定ird 当页码加载时发送ajax 请求 查询route对象 RouteServlet findOne(){ 接收rid 调用service查询 转换json返回 } RouteService Findone( int rid){ 1 根据id查询route对象RouteDao 2根据rid路线id查询Tab_route_img 将集合设置到route对象 3 根据sid卖家id查询 tab_seller 查询卖家信息 将其设置到route对象 }
对应的findOne方法
public void findOne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 接收参数id String rid = request.getParameter("rid"); // 调用service 查询route对象 Route route = routeService.finOne(rid); // 转为json写回客户端 writeValue(route,response); }
对应的 rid条件 进行查询 routeService
Route route = routeDao.finOne(Integer.parseInt(rid)); // 根据route的id查询图片集合信息 List<RouteImg> routeImgList = routeImgDao.finByRid(route.getRid()); // 将集合 设置到route对象 route.setRouteImgList(routeImgList); Seller seller = sellerDao.findByid(route.getSid()); route.setSeller(seller);
访问以下三个Dao
RouteDao
RouteImplDao
SellerDao调用三个Dao
前台代码 Route_detail.html中加载后 获取rid 发送ajax请求 获取route对象 解析对象的数据
旅游线路收藏功能
分析
判断当前登录用户是否收藏过该线路
当页面加载完成后 发送ajax请求 获取用户是否收藏的标记
传递线路id rid
根据标记 展示不用的按钮样式 请求对应的RouteServlet
获取rid线路id
获取当前登录的用户对象 session
如果user对象为null 设置条uid =0调用FavoriteService 查询 传递rid uid 写回客户端flag标记
FavoriteService
isFavorite(rid , uid)
FavoriteDao
findByUi dan Rid(uid rid)Route_detai.html
收藏次数的动态展示
点击收藏线路的按钮
前台判断用户是否登录
如果登录可以点击按钮
没有登录 给出提示信息
发送AJAX请求
RouteServlet
获取线路的rid
获取用户的uid
调用service添加FavoriteServlet add方法
// 获取线路id String rid = request.getParameter("rid"); // 获取当前登录的用户 user User user = (User) request.getSession().getAttribute("user"); int uid; if(user == null){ return ; }else{ uid= user.getUid(); } // 调用service 添加 favoriteService.add(rid,uid); }
FavoriteDao add方法
public void add(int rid, int uid) { String sql = "insert into tab_favorite value(?,?,?)"; template.update(sql,rid,new Date(),uid); }
个人总结 整体项目
我个人犯错误和难点集合
配置文件版本
对应的空指针报错异常处理
细节上的字符输入错误 大小写出错
分页的代码处理
queryForObject有且只能查询一条数据 如果数据库中没有这条数据或者数据库中这条数据又相同的异常也会抛出
主要因为是 数据查询时 出现 不能有正确的对应信息
抛出异常 使用try catch
BaseServlet 进行抽取 种的暴力注解
先是servlet层 调用 service层进行实现类 调用dao层 进行查表 调用执行sql语句 进行返回查询结果欢迎遇到问题进行留言一起讨论