某马旅游网学习分享
一、介绍
- 本文主要是想在学习的过程中做一些笔记方便自己以后查询,同时对于初学者也是一个很好的练手项目,可以加深对JavaWeb相关技术的应用。
- 使用到的技术:js、html、bootstrap;Java、Servlet、JdbcTemplate、MySQL、Redis
- 项目源码地址下载:https://gitee.com/sxd123456/travel.git
二、注册模块
1、前端对用户名、姓名、密码、邮箱做非空和正则校验。
2、验证码实现
- 思路:加载页面时向CheckCodeServlet发送请求,后端接收请求后生成一个随机的4位数校验码,生成一张图片,将图片返回至前端。
public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
//服务器通知浏览器不要缓存
response.setHeader("pragma","no-cache");
response.setHeader("cache-control","no-cache");
response.setHeader("expires","0");
//在内存中创建一个长80,宽30的图片,默认黑色背景
//参数一:长
//参数二:宽
//参数三:颜色
int width = 80;
int height = 30;
BufferedImage image = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
//获取画笔
Graphics g = image.getGraphics();
//设置画笔颜色为灰色
g.setColor(Color.GRAY);
//填充图片
g.fillRect(0,0, width,height);
//产生4个随机验证码,12Ey
String checkCode = getCheckCode();
//将验证码放入HttpSession中
request.getSession().setAttribute("CHECKCODE_SERVER",checkCode);
//设置画笔颜色为黄色
g.setColor(Color.YELLOW);
//设置字体的小大
g.setFont(new Font("黑体",Font.BOLD,24));
//向图片上写入验证码
g.drawString(checkCode,15,25);
//将内存中的图片输出到浏览器
//参数一:图片对象
//参数二:图片的格式,如PNG,JPG,GIF
//参数三:图片输出到哪里去
ImageIO.write(image,"PNG",response.getOutputStream());
}
/**
* 产生4位随机字符串
*/
private String getCheckCode() {
String base = "0123456789ABCDEFGabcdefg";
int size = base.length();
Random r = new Random();
StringBuffer sb = new StringBuffer();
for(int i=1;i<=4;i++){
//产生0到size-1的随机值
int index = r.nextInt(size);
//在base字符串中获取下标为index的字符
char c = base.charAt(index);
//将c放入到StringBuffer中去
sb.append(c);
}
return sb.toString();
}
- 前端通过img标签加载图片,点击图片可以刷新校验码。
<td class="td_right check">
<input type="text" id="check" name="check" class="check">
<img src="checkCode" height="32px" alt="" onclick="changeCheckCode(this)">
<script type="text/javascript">
//图片点击事件
function changeCheckCode(img) {
img.src="checkCode?"+new Date().getTime();
}
</script>
</td>
3、注册代码实现
- 输入完相关校验后点击注册通过post请求将表单数据提交至UserServlet, UserServlet首先判断校验码是否正确
//判断验证码是否正确
String checkcode_server = request.getParameter("check");
HttpSession session = request.getSession();
//从session中获取验证码
String checkcodeServer = (String) session.getAttribute("CHECKCODE_SERVER");
//获取后移除改验证码
session.removeAttribute("CHECKCODE_SERVER");
if(checkcode_server==null || !checkcode_server.equalsIgnoreCase(checkcodeServer)){
ResultInfo info=new ResultInfo();
info.setFlag(false);
info.setErrorMsg("注册失败!");
//将info序列化为json发送至前端
ObjectMapper mapper=new ObjectMapper();
String info_json = mapper.writeValueAsString(info);
response.setContentType("application/json;charzset=utf-8");
response.getWriter().write(info_json);
return;
}
- 然后根据用户名查询数据库中是否存在相同用户名称的用户,存在则注册失败
//根据用户名查找用户
public User findByName(String username) {
String sql="select * from tab_user where username=?";
User user=null;
try {
user = template.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), username);
}catch (Exception e){
e.printStackTrace();
}
return user;
}
- 全部校验通过后将用户信息存入数据库,同时根据用户注册时填写的邮箱发送一份邮件,用户需要通过邮件激活账户。具体邮箱如何发送邮件已经封装为工具类MailUtils,大家可以自行下载查看。
- 如果注册失败则会在页面提示信息,关于前端页面的调试问题在本文不做太多的阐述,我们主要关心后端的逻辑以及代码实现。
三、登录和退出模块
1、登录
- 首先输入账户密码以及验证码,通过post请求将表单数据提交至LoginServlet,通过用户和账号查询数据中是否存在已经激活了的用户,然后将user设置入session。代码详解看注释。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取登录信息
Map<String, String[]> map = request.getParameterMap();
//用User对象封装
User user =new User();
try {
//如果user中属性和map中key相匹配,则将map中的value赋值给user相关属性
BeanUtils.populate(user,map);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//调用service根据用户名以及密码查询是否注册和激活
RegistService service=new RegistServiceImpl();
User user1= service.findByNameAndPassword(user.getUsername(),user.getPassword());
HttpSession session = request.getSession();
//封装提交给前端的数据
ResultInfo info=new ResultInfo();
if(user1==null){
//数据库不存在该用户
info.setFlag(false);
info.setErrorMsg("用户名或密码错误");
}
if(user1!=null && "N".equalsIgnoreCase(user1.getStatus())){
//未激活
info.setFlag(false);
info.setErrorMsg("尚未激活,请激活");
}
if(user1!=null && "Y".equalsIgnoreCase(user1.getStatus())){
//登录成功
info.setFlag(true);
}
//将用户设置入session
session.setAttribute("user",user1);
//序列化为json
ObjectMapper mapper=new ObjectMapper();
String json = mapper.writeValueAsString(info);
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(json);
}
- 退出登录成功后将session取出,放置欢迎页。
2、退出
- 退出功能实现也简单,就是点击退出时销毁session,同时重定向跳转至登录页。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//销毁session
request.getSession().invalidate();
//重定向
response.sendRedirect(request.getContextPath()+"/login.html");
}
四、分类栏目
- 效果图
1、所有分类栏目查询展示实现
- 思路:页面加载时发送Ajax请求至CategoryServlet,后端接收请求后查询数据表tab_category,将数据封装后返回至前端,前端解析数据,拼装html。
- 本系统实现做了一个优化,也算是对redis的一个应用。就是在查询数据库时先判断reids中是否有值,有的话就直接取redis中数据,速度会比从数据库中查询快的多。因为分类栏目一般变动是很少的,放redis中提高用户体验。
public List<Category> findAll() {
//查找redis缓存是否有数据
Jedis jedis = JedisUtil.getJedis();
jedis.auth("1234");
//Set<String> categorys = jedis.zrange("category", 0, -1);
Set<Tuple> categorys = jedis.zrangeWithScores("category", 0, -1);
List<Category> category=new ArrayList<>();
if(categorys.size()==0 || categorys==null){
//说明缓存中没有数据,则查询数据库
System.out.println("查询数据库。。。。");
category = dao.findAll();
//将查询的数据存入redis
for (int i = 0; i < category.size(); i++) {
jedis.zadd("category",category.get(i).getCid(),category.get(i).getCname());
}
}else {
//缓存中有数据,把set集合转化为list集合
System.out.println("查询缓存。。。。");
for (Tuple tuple : categorys) {
Category category1=new Category();
category1.setCname(tuple.getElement());
category1.setCid((int) tuple.getScore());
category.add(category1);
}
}
return category;
}
五、旅游线路
1、旅游线路分页展示分页展示
效果图
- 实现思路:点击国内游时,发送请求(http://localhost:8086/route/pageQuery?cid=5¤tPage=&rname=)至RouteServlet,请求的参数有类别编号cid,当前页码currentPage,路线查询名称rname。根据传入的参数查询数据表tab_route,查出数据封装返回至前端解析。
- 分页查询思路:MySQL分页查询的语句(select * from 表 limt 起始页 每页显示条数),因此我们根据页面传入的条件便可以查询出相关数据。
- 条数、页码以及上一页下一页实现思路:总条数(select count (*) from tab_route where cid=?)
总页数=total%pageSize==0 ? total/pageSize:total/pageSize+1;
下一页则拿取当前页码加一,Ajax请求一次数据,上一页实现也是类似,同时需要考虑边界值,如在第一页点击上一页和在最后一页点击下一页的情况。部分代码实现如下:
@WebServlet( "/route/*")
public class RouteServlet extends BaseServlet {
private RouteService service=new RouteServiceImpl();
public void pageQuery(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String cidStr = request.getParameter("cid");//类别编号
String currentPageStr = request.getParameter("currentPage");//当前页码
String pageSizeStr = request.getParameter("pageSize");//每页显示条数
//接受rname 线路名称
String rname = request.getParameter("rname");
rname = new String(rname.getBytes("iso-8859-1"),"utf-8");
int cid=0;
if(cidStr!=null && cidStr.length()>0 && !"null".equals(cidStr)){
cid=Integer.parseInt(cidStr);
}
int currentPage=1;
if(currentPageStr!=null && currentPageStr.length()>0){
currentPage=Integer.parseInt(currentPageStr);
}
int pageSize=5;
if(pageSizeStr!=null && pageSizeStr.length()>0){
pageSize=Integer.parseInt(pageSizeStr);
}
//获取参数
//调用service获取pageBean
PageBean<Route> pageBean=service.findPageQuery(cid,currentPage,pageSize,rname);
writeValue(pageBean,response);
}
public void findOne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String rid = request.getParameter("rid");//获取route的id
Route route=service.findOne(rid);
writeValue(route,response);
}
}
2、路线详情实现
效果图:
- 思路:每个路线都有一个rid,点击详情时将rid传后台就可以查出该条数据,然后将相关信息拼装到html(http://localhost:8086/route/findOne?rid=1)。这块后端实现很简单,主要是前端html的拼接繁琐了一点。
- 后端代码
public void findOne(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String rid = request.getParameter("rid");//获取route的id
Route route=service.findOne(rid);
writeValue(route,response);
}
- 前端代码
//1.获取rid
var rid = getParameter("rid");
//2.发送请求请求 route/findOne
$.get("route/findOne",{rid:rid},function (route) {
//3.解析数据填充html
$("#rname").html(route.rname);
$("#routeIntroduce").html(route.routeIntroduce);
$("#price").html("¥"+route.price);
$("#sname").html(route.seller.sname);
$("#consphone").html(route.seller.consphone);
$("#address").html(route.seller.address);
//图片展示
var ddstr = '<a class="up_img up_img_disable"></a>';
//遍历routeImgList
for (var i = 0; i < route.routeImgList.length; i++) {
var astr ;
if(i >= 4){
astr = '<a title="" class="little_img" data-bigpic="'+route.routeImgList[i].bigPic+'" style="display:none;">\n' +
' <img src="'+route.routeImgList[i].smallPic+'">\n' +
' </a>';
}else{
astr = '<a title="" class="little_img" data-bigpic="'+route.routeImgList[i].bigPic+'">\n' +
' <img src="'+route.routeImgList[i].smallPic+'">\n' +
' </a>';
}
ddstr += astr;
}
ddstr+='<a class="down_img down_img_disable" style="margin-bottom: 0;"></a>';
$("#dd").html(ddstr);
//图片展示和切换代码调用
goImg();
});