项目课程链接:https://www.bilibili.com/video/BV1CE411E7h4
完整课程连接:https://www.bilibili.com/video/BV1uJ411k7wy
十一小长假期间在网上找了一个 Web 开发项目公开课:黑马旅游网。一方面,这个项目中涉及到大量 JavaSE、数据库、HTTP协议、Servlet 等基础知识,项目整体综合性较强。另一方面,这个案例尚未涉及现今比较高级热门的后端开发技术栈(SSM、SpringBoot等)以及以在高并发场景下的应对策略。因此比较适合具备一定 Java 语言和网络编程基础但是还未接触高级开发框架的读者进行阅读学习。
整个项目中包含了当今 Web 应用中许多典型的业务场景,例如注册、登陆、收藏等……笔者学习和实践的过程中,在跟随授课老师的讲解完成课上内容开发的基础上,自己也在课后补充了一些其它有趣的功能,同时也解决了项目中一些 BUG,这些在后续的系列博客中都会涉及,也算是对自己学习的检验,以及加深对后端业务逻辑的理解。
项目的第三方依赖维护跟随课上要求:使用 Maven 配置和管理。在实践的过程中也确实体会到 Maven 的强大。另外,笔者使用 Git 建立仓库和维护整个项目开发周期。整个项目代码仓库也已经上传至我的 GitHub 仓库 GitHub 仓库。在此我安利一个高效的 Git GUI 软件:GitKraken,它的可视化界面和交互操作使得分支管理和版本维护更加直观,并且支持关联 GitHub 账户,支持一键 Push 至远程仓库等功能。关于 Git 以及 GitKraken 的使用教学可以参考这个视频教程:Git + GitHub 10分钟完全入门 和 10分钟 Git 进阶教程。
笔者是通过这个简单的练习项目对过往学习的基础知识进行总结和回顾。引述最后,也期待每一位阅读此系列博客的读者在阅读以及课堂学习实践过程中能够加深对基础知识的理解和梳理。我不希望您直接将我的仓库 fork 下来能运行就算了,我的 Git 仓库、博客,甚至 commit message 都可以作为提高学习效率的辅助内容。但是,只有真正的跟着任课老师从零开始,一步一步将整个项目敲出来,并理解其中的每一个业务逻辑和实现细节才能加深对技术的理解,提高分析问题、解决问题的能力。
项目在开发过程中采用经典的三层架构(Web - Service - Dao),且前后端分离。因此,一些实时的前端渲染不再使用 JSP 实现,而通过 AJAX 实现。各层技术选型归纳如下:
代码文件结构如下图,以下对 java
文件夹下的不同目录中的代码功能做简要介绍
com.abe.domain 中定义具体的 Bean 封装类,这些类中的私有 fields 与数据库中对应表的字段一一对应。
com.abe.dao 和 com.abe.dao.impl 对应项目架构中的 Dao 层,dao 中定义的数据库操作接口,dao.impl 中定义具体的实现类。
com.abe.service 和 com.abe.service.impl 对应项目架构中的 Service 层。service 中定义业务逻辑接口,service.impl 中定义具体的业务逻辑实现类,根据不同的业务需求调用不同的 dao 实现类对象对数据库做 CRUD。
com.abe.web.servlet 和 com.abe.web.filter 对应项目架构中的 Web 层。根据不同的 Web 应用定义相应的 Servlet 代码。项目中依据不同的执行实体分别定义了对应的 Servlet 类。后续开发还将抽取 Servlet 中的通用功能,并单独封装为一个抽象类 BaseServlet,并作为其它 Servlet 的父类。使得代码结构更加整洁,便于维护和扩展。
/**
* Servlet基类
* 该Servlet不需要被访问,所以不加虚拟路径注解
*/
public abstract class BaseServlet extends HttpServlet {
private ObjectMapper mapper = new ObjectMapper();
/**
* baseServlet方法分发
*/
protected void service(HttpServletRequest req, HttpServletResponse resp) {
// 1.获取请求路径
String uri = req.getRequestURI(); // 例如:/travel/user/add
// 2.获取方法名称
String methodName = uri.substring(uri.lastIndexOf('/') + 1);
// 3.获取方法对象(反射)
/*this指向真正的实例化对象,即BaseServlet下面的继承类:com.abe.web.servlet.UserServlet@5fccaa6f*/
/*this: 谁调用我,我代表谁*/
try {
Method method = this.getClass().getMethod(methodName,
HttpServletRequest.class, HttpServletResponse.class);
// 4.执行方法
method.invoke(this, req, resp);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 将输入对象序列化为json,并回写客户端浏览器
* @param object 输入对象
* @param response Http响应对象
*/
public void writeValue(Object object, HttpServletResponse response) throws IOException {
response.setContentType("application/json;charset=utf-8");
mapper.writeValue(response.getOutputStream(), object);
}
/**
* 将输入对象序列化为json并返回给调用者
* @param object 输入对象
* @return json字符串
*/
public String writeValueAsString(Object object) throws JsonProcessingException {
return mapper.writeValueAsString(object);
}
/**
* 向客户端浏览器回传Cookie(持久化登录状态)
* @param cookie Cookie对象
* @param maxAge Cookie生命周期
* @param response Http响应对象
*/
public void writeCookie(Cookie cookie, int maxAge, HttpServletResponse response) {
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}
/**
* 字符串数字类型转换方法
* 将数字字符串转换为数字,例如:"5" -> 5
* @param numStr 数字字符串
* @return 整型数字
* @throws ClassCastException
* 类型转换异常,避免传入的字符串内容不是数字
*/
protected int parseInt(String numStr, int defaultValue) throws ClassCastException {
return (numStr != null && numStr.length() > 0 && !"null".equalsIgnoreCase(numStr) && !"NaN".equalsIgnoreCase(numStr))
? Integer.parseInt(numStr) : defaultValue; // 注意:浏览器提交的空值到后台会被识别为<字符串"null">
}
/**
* 非数字字符串参数处理方法
* @param str 字符串对象
* @return 处理后的字符串对象
*/
protected String parseStr(String str) {
/*tomcat7存在中文乱码问题*/
if (str != null && str.length() > 0 && !"null".equalsIgnoreCase(str) && !"NaN".equalsIgnoreCase(str)) {
str = new String(str.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
return str;
}
return null;
}
}
com.abe.util 中定义了许多静态工具类,例如 MySQL 链接、Redis链接、邮件发送等。这些代码复用度很高,且执行时无需专门实例化对象。
课程中直接提供的建立表及添加数据的 sql 代码以及对应的 domain 代码包,按照课程视频逐步操作即可很快实现建库和建表。数据库中所有表格的结构以及相互关系如下图:
以下对每张表的简要介绍,并给出对应的 Bean 封装类,其中字段的含义说明待写道涉及开发相关功能的博客时在详细介绍。
注册用户信息表
/**
* 用户实体类
*/
public class User implements Serializable {
private int uid; // 用户id
private String username; // 用户名,账号
private String password; // 密码
private String name; // 真实姓名
private String birthday; // 出生日期
private String sex; // 男或女
private String telephone; // 手机号
private String email; // 邮箱
private String status; // 激活状态,Y代表激活,N代表未激活
private String code; // 激活码(要求唯一)
/* setter & getter 方法 */
}
关联用户和旅游线路的表格,记录了用户的旅游路线收藏信息
/**
* 收藏实体类
*/
public class Favorite implements Serializable {
private Route route;//旅游线路对象
private String date;//收藏时间
private User user;//所属用户
/* setter & getter 方法 */
}
旅游线路信息表
/**
* 旅游线路商品实体类
*/
public class Route implements Serializable {
private int rid;//线路id,必输
private String rname;//线路名称,必输
private double price;//价格,必输
private String routeIntroduce;//线路介绍
private String rflag; //是否上架,必输,0代表没有上架,1代表是上架
private String rdate; //上架时间
private String isThemeTour;//是否主题旅游,必输,0代表不是,1代表是
private int count;//收藏数量
private int cid;//所属分类,必输
private String rimage;//缩略图
private int sid;//所属商家
private String sourceId;//抓取数据的来源id
private Category category;//所属分类
private Seller seller;//所属商家
private List<RouteImg> routeImgList;//商品详情图片列表
/* setter & getter 方法 */
}
商家信息表
/**
* 商家实体类
*/
public class Seller implements Serializable {
private int sid;//商家id
private String sname;//商家名称
private String consphone;//商家电话
private String address;//商家地址
/* setter & getter 方法 */
}
记录旅游线路相应页面的图片存储路径信息(项目图片存储路径一定要严格遵循 Maven 的保存规范,或者按照课上讲述存放,否则会导致后期开发过程中图片加载出现问题)。
/**
* 旅游线路图片实体类
*/
public class RouteImg implements Serializable {
private int rgid;//商品图片id
private int rid;//旅游商品id
private String bigPic;//详情商品大图
private String smallPic;//详情商品小图
/* setter & getter 方法 */
}
记录网页
标签中分类栏内容的表格,即:
/**
* 分类实体类
*/
public class Category implements Serializable {
private int cid;//分类id
private String cname;//分类名称
/* setter & getter 方法 */
}
本文作为整个综合练习案例系列博客的首篇,首先介绍了笔者的学习目的;而后介绍了项目的技术栈、开发架构、数据库设计等概要性介绍。后续的该系列博客将对其中每一个功能的业务逻辑进行梳理总结并给出关键的实现代码。
我的黑马旅游网项目代码仓库链接:https://github.com/Abexope/ItCast-HeiMa-Travel