JavaEE开发-第六节 JavaEE项目实战(一)
功能简介:
1.个人简历管理系统,分为后台管理端和个人简历展示页面
2.后台管理端: 一张简历由n个模块组成,只能用户本人登录,然后才能编辑简历的各个部分:
个人信息、专业技能、修改密码、教育经历、公司信息、工作经验、获奖成就、留言信息、网站信息
3.个人简历展示:根据后台配置的各个模块展示出一张简历
4.本项目通过项目实战的方式回顾了前面讲的知识点,同时也讲了一些新的知识点:反射、代码整合抽取、Cookie/Session、Filter、Listener等
项目初始化
- 初始化项目
- IDEA从0新建一个web的maven项目(步骤前面讲过略),项目名称为ZHResume
-
在pom.xml中添加配置
<!--打包方式--> <packaging>war</packaging> <!--源码编译的编码--> <properties> <project.build.sourceEncoding>UTF8</project.build.sourceEncoding> </properties> <dependencies> <!--tomcat中的servlet依赖--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies> <build> <!--打包名称--> <finalName>ZHResume</finalName> </build>
- Tomcat中部署该项目ZHResume:war exploded,Application Context设置为xr
- 设计项目使用的表
- 个人信息:user
- 专业技能:skill
- 专业信息:education
- 公司信息:company
- 工作经验:experience
- 项目经验:project
- 获奖成就:award
- 留言信息:contact
- 网站信息:website
# 如果这些表存在都删除 DROP TABLE IF EXISTS award; DROP TABLE IF EXISTS education; DROP TABLE IF EXISTS skill; DROP TABLE IF EXISTS website; DROP TABLE IF EXISTS experience; DROP TABLE IF EXISTS project; DROP TABLE IF EXISTS company; DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS contact; # 重新创建所有表 # user CREATE TABLE user ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, password VARCHAR(32) NOT NULL, email VARCHAR(50) NOT NULL UNIQUE, birthday DATE, photo VARCHAR(100), intro VARCHAR(1000), name VARCHAR(20), address VARCHAR(100), phone VARCHAR(20), job VARCHAR(20), trait VARCHAR(100), interests VARCHAR(100), PRIMARY KEY (id) ); # skill CREATE TABLE skill ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, name VARCHAR(20) NOT NULL, level INT NOT NULL, PRIMARY KEY (id) ); # website CREATE TABLE website ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, footer VARCHAR(1000), PRIMARY KEY (id) ); # company CREATE TABLE company ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, name VARCHAR(20) NOT NULL UNIQUE, logo VARCHAR(100), website VARCHAR(50), intro VARCHAR(1000), PRIMARY KEY (id) ); # award CREATE TABLE award ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, name VARCHAR(20) NOT NULL, image VARCHAR(100), intro VARCHAR(1000), PRIMARY KEY (id) ); # contact CREATE TABLE contact ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, name VARCHAR(20) NOT NULL, email VARCHAR(50) NOT NULL, comment VARCHAR(1000) NOT NULL, subject VARCHAR(20), already_read INT NOT NULL DEFAULT 0, PRIMARY KEY (id) ); # education CREATE TABLE education ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, name VARCHAR(20) UNIQUE NOT NULL, type INT NOT NULL, intro VARCHAR(1000), begin_day DATE NOT NULL, end_day DATE, PRIMARY KEY (id) ); # experience CREATE TABLE experience ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, job VARCHAR(20) NOT NULL, intro VARCHAR(1000), begin_day DATE NOT NULL, end_day DATE, company_id INT NOT NULL, PRIMARY KEY (id), FOREIGN KEY (company_id) REFERENCES company(id) ); # project CREATE TABLE project ( id INT AUTO_INCREMENT, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, name VARCHAR(20) NOT NULL, intro VARCHAR(1000), website VARCHAR(50), image VARCHAR(100), begin_day DATE NOT NULL, end_day DATE, company_id INT NOT NULL, PRIMARY KEY (id), FOREIGN KEY (company_id) REFERENCES company(id) ); # 初始化 INSERT INTO user(email, name, password, job, phone, birthday, address, trait, interests, intro) VALUES( 'mj@qq.com', 'coderZhong', '3f05d332600fa9d9b7837172521ffa60', '程序员', '9527', '1988-01-02', '天朝广州', '活泼,可爱', '足球,台球,电玩', '本人学识渊博、经验丰富,代码风骚、效率恐怖,C/C++ C#、Java、PHP、Android、iOS、Python、JavaScript,无不精通玩转,熟练掌握各种框架,并自写语言,创操作系统,写CPU处理器构架,做指令集成。深山苦练20余年,一天只睡3小时,千里之外定位问题,瞬息之间修复上线。身体强壮、健步如飞,可连续工作100小时不休息,讨论技术方案9小时不喝水,上至研发CPU芯片、带项目、出方案、弄计划,下至盗账号、黑网站、Shell提权挂马、攻击同行、拍片摄影、泡妞把妹纸、开挖掘机、威胁PM,啥都能干。' ); INSERT INTO website(footer) VALUES( '<a href="https://space.bilibili.com/325538782" target="_blank">华仔ZH</a> © All Rights Reserved 2020' );
- 打开Navicat Premium->右击左边的连接->New Database… -> 数据名称为xr->ok
- 右击xr -> Execute SQL File … ->找到上面的sql语句文件
zh_resume.sql
->执行
-
项目目录如下:
main java com.zh.xr bean dao servlet util resources webapp asset page WEB-INF
- 项目整体架构
- 整体思路:页面->Servlet->service->Dao->数据库
网站信息页面开发
- 页面:存放客户端展示的页面
- 在page/admin目录下新增website.jsp
- Servlet:监听客户端发送的服务器请求
- 在servlet文件夹下新增BaseServlet基类,处理分发所有的servlet
- 新建WebsiteServlet继承自BaseServlet,处理网站信息页面的事件(查询、更新、添加)
- Dao:JDBC封装,与数据库交互
- 在dao文件夹下新建BaseDao基类
- 新建WebsiteDao类继承BaseDao,封装处理获取数据库的数据,将数据转模型然后输出
- bean: 数据模型,将数据库返回的原始数据转化为模型
- 在bean文件夹下新建BaseBean基类,添加各个模型公用的字段成员,以及实现其对应get、set方法,供后序模块继承共用
- 新建Website继承BaseBean,添加数据库对应的成员字段,同时实现get、set方法
-
pom.xml中添加数据库依赖:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.6.RELEASE</version> </dependency>
- 导入访问数据库资源文件
druid.properties
到resources中
代码示例:
-
BaseServlet
public class BaseServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { request.setCharacterEncoding("UTF-8"); String uri = request.getRequestURI(); String[] comps = uri.split("/"); String methodName = comps[comps.length - 1]; //反射 //获取方法实例 Method method = getClass().getMethod(methodName,HttpServletRequest.class,HttpServletResponse.class); //方法调用 method.invoke(this,request,response); } catch (Exception e) { e.printStackTrace(); } } }
-
BaseDao
public class BaseDao { protected static JdbcTemplate tpl; //类第一次加载的时候读取db.properties static { try(InputStream is = WebsiteDao.class.getClassLoader().getResourceAsStream("druid.properties")){ Properties properties = new Properties(); properties.load(is); //创建连接池 DataSource ds = DruidDataSourceFactory.createDataSource(properties); //连接池与jdbc无缝结合,将连接池给jdbc tpl = new JdbcTemplate(ds); }catch (Exception e){ e.printStackTrace(); } } }
-
BaseBean
public class BaseBean { private Integer id; private Date createTime; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } }
-
WebsiteServlet
@WebServlet("/website/*") public class WebsiteServlet extends BaseServlet { WebsiteDao dao = new WebsiteDao(); public void admin(HttpServletRequest request, HttpServletResponse response) throws Exception { System.out.println("website-admin"); List<Website> websites = dao.list(); Website website = (websites != null && !websites.isEmpty()) ? websites.get(0) : null; request.setAttribute("website",website); //转发 request.getRequestDispatcher("/page/admin/website.jsp").forward(request,response); } public void save(HttpServletRequest request, HttpServletResponse response) throws Exception { Website website = new Website(); BeanUtils.populate(website,request.getParameterMap()); if (dao.save(website)) {//保存成功 //重定向到admin response.sendRedirect(request.getContextPath() + "/website/admin"); }else {//保存失败 request.setAttribute("error","网站信息保存失败"); request.getRequestDispatcher("/page/error.jsp").forward(request,response); } } }
-
WebsiteDao
public class WebsiteDao extends BaseDao{ public static JdbcTemplate getTpl() { return tpl; } /** * 删除 */ public boolean remove(Integer id) { return false; } /** * 保存或更新 */ public boolean save(Website website) { Integer id = website.getId(); List<Object> args = new ArrayList<>(); args.add(website.getFooter()); String sql = ""; if(id == null || id <1){ sql = "INSERT INTO website(footer) VALUES(?)"; }else { sql = "UPDATE website SET footer = ? WHERE id = ?"; args.add(id); } return tpl.update(sql,args.toArray()) >0 ; } /** * 获取单个对象 */ public Website get(Integer id) { return null; } /** * 获取多个对象 */ public List<Website> list() { String sql = "SELECT id, created_time, footer FROM website"; return tpl.query(sql,new BeanPropertyRowMapper<>(Website.class)); } /** * 获取统计值 */ public int count() { return 0; } }
-
Website
public class Website extends BaseBean { private String footer; public String getFooter() { return footer; } public void setFooter(String footer) { this.footer = footer; } }
教育经验模块开发
列表展示、添加、删除页面
数据转模型:map->model
- 场景:客户端直接输入的是日期字符串,服务端拿到的也是字符串,但是我们数据库以及bean模型都是Date类型,那么如何将字符串转化为Date呢?
- 只需要全局实现如下代码即可,整个项目只需要执行一次,在BaseServlet添加
```
static {
//将客户端输入到服务端的日期字符串,自动转化为Date类型,项目全局代码只需要执行一次,来源于commons-beanutils第三方框架
// null参数表示允许值为null
DateConverter dateConverter = new DateConverter(null);
dateConverter.setPatterns(new String[]{"yyyy-MM-dd"});
ConvertUtils.register(dateConverter, Date.class);
}
``` 2. 场景:服务器返回的日期格式是Date格式,而且很长,那么客户端jsp中有没有好的办法转化为字符串展示呢?
```
//1. jsp中引入,该标签也来自于jstl标签库
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
//2. 使用
<td><fmt:formatDate pattern="yyyy-MM-dd" value="${education.beginDay}"/></td>
<td><fmt:formatDate pattern="yyyy-MM-dd" value="${education.endDay}"/></td>
``` 3. pom引入如下第三方库库
1. jstl:jsp中使用for循环特殊标签,日期转化标签fmt
2. commons-beanutils:实现服务端的数模转化,将客户端传递过来的参数转化为bean模型数据、字符串自动转化Date类型
```
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
```
代码举例:
-
EducationServlet
@WebServlet("/education/*") public class EducationServlet extends BaseServlet { private EducationDao dao = new EducationDao(); public void admin(HttpServletRequest request, HttpServletResponse response) throws Exception { request.setAttribute("educations",dao.list()); request.getRequestDispatcher("/page/admin/education.jsp").forward(request,response); } public void save(HttpServletRequest request, HttpServletResponse response) throws Exception { Education education = new Education(); //数模转化 BeanUtils.populate(education, request.getParameterMap()); if (dao.save(education)) {//保存成功 //重定向到admin response.sendRedirect(request.getContextPath() + "/education/admin"); }else {//保存失败 request.setAttribute("error","教育信息保存失败"); request.getRequestDispatcher("/page/error.jsp").forward(request,response); } } public void remove(HttpServletRequest request, HttpServletResponse response) throws Exception { String[] idStrs = request.getParameterValues("id"); List<Integer> ids = new ArrayList<>(); for (String idStr : idStrs) { ids.add(Integer.valueOf(idStr)); } if (dao.remove(ids)){ //重定向到admin response.sendRedirect(request.getContextPath() + "/education/admin"); }else { request.setAttribute("error","教育信息删除失败"); request.getRequestDispatcher("/page/error.jsp").forward(request,response); } } }
-
EducationDao
public class EducationDao extends BaseDao { public static JdbcTemplate getTpl() { return tpl; } /** * 删除 */ public boolean remove(Integer id) { String sql = "DELETE FROM education WHERE id = ? "; return tpl.update(sql,id) >0 ; } /** * 批量删除 */ public boolean remove(List<Integer> ids) { List<Object> args = new ArrayList<>(); StringBuilder sql = new StringBuilder(); sql.append("DELETE FROM education WHERE id in ("); for (Integer id: ids) { args.add(id); sql.append("?, "); } sql.replace(sql.length()-2,sql.length(),")"); return tpl.update(sql.toString(),args.toArray()) >0 ; } /** * 保存或更新 */ public boolean save(Education education) { Integer id = education.getId(); List<Object> args = new ArrayList<>(); args.add(education.getName()); args.add(education.getType()); args.add(education.getIntro()); args.add(education.getBeginDay()); args.add(education.getEndDay()); String sql = ""; if(id == null || id <1){ sql = "INSERT INTO education(name,type,intro,begin_day,end_day) VALUES(?, ?, ?, ?, ?)"; }else { sql = "UPDATE education SET name = ?, type = ?, intro = ?, begin_day = ?, end_day = ? WHERE id = ?"; args.add(id); } return tpl.update(sql,args.toArray()) >0 ; } /** * 获取单个对象 */ public Education get(Integer id) { String sql = "SELECT id, created_time, name, type, intro, begin_day, end_day FROM education WHERE id = ?"; return tpl.queryForObject(sql,new BeanPropertyRowMapper<>(Education.class),id); } /** * 获取多个对象 */ public List<Education> list() { String sql = "SELECT id, created_time, name, type, intro, begin_day, end_day FROM education"; return tpl.query(sql,new BeanPropertyRowMapper<>(Education.class)); } /** * 获取统计值 */ public int count() { return 0; } }
-
Education
public class Education extends BaseBean { private String name; private String intro; private Date beginDay; private Date endDay; private Integer type; public String getTypeString() { switch (type) { case 1: return "小学"; case 2: return "初中"; case 3: return "高中"; case 4: return "中专"; case 5: return "大专"; case 6: return "本科"; case 7: return "硕士"; case 8: return "博士"; default: return "其它"; } } public String getName() {return name;} public void setName(String name) {this.name = name;} public String getIntro() {return intro;} public void setIntro(String intro) {this.intro = intro;} public Date getBeginDay() {return beginDay;} public void setBeginDay(Date beginDay) {this.beginDay = beginDay;} public Date getEndDay() {return endDay;} public void setEndDay(Date endDay) {this.endDay = endDay;} public Integer getType() {return type;} public void setType(Integer type) {this.type = type;} }
教育经验编辑功能
模型转数据:model->json
- 如何拿到编辑的教育经验数据?
- 需要将模型对象转为json对象才能在education.jsp中取值使用
```
<button type="button" class="btn bg-blue waves-effect btn-xs" onclick="edit(${education.json})">
<i class="material-icons">edit</i>
<span>编辑</span>
</button>
<script>
const $addFormBox = $('#add-form-box')
const $addForm = $addFormBox.find('form')
// 函数 - 方法
function edit(json) {
add()
// 填充表单信息
for (const k in json) {
$addForm.find('[name=' + k + ']').val(json[k])
}
}
</script>
```
2. 模型转json使用第三方库jackson-databind,在pom中添加依赖
```
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
```
3. 由于所有模型都可能使用,因此模型转json的方法封装到基类baseBean中
```
@JsonIgnore //必须忽略,否则会死循环
//将当前模型转为json对象
public String getJson() throws Exception {
ObjectMapper mapper = new ObjectMapper();
//遇到日期,自动转为yyyy-MM-dd字符串格式
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
//该方法默认会去调用模型的所有get方法,调用getJson,会造成死循环,所以必须加@JsonIgnore
return mapper.writeValueAsString(this).replace("\"", "'");
}
```
4. **注意事项:**
```
Education education = new Education();
education.setName("黄金小学");
education.setIntro("还不错的小学");
education.setType(4);
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(education);
//typeString不应该有,说明该框架的本质是调用get方法来获取成员,因此需要忽略该get方法
// {"id":null,"createdTime":null,"name":"黄金小学","intro":"还不错的小学","beginDay":null,"endDay":null,"type":4,"typeString":"中专"}
System.out.println(jsonString);
```
1. writeValueAsString方法的本质是调用对象的所有**get方法**,将get方法对应的成员作为key值,get方法获取的值,作为value生产json字符串
2. 在模型转json过程中,如果某个成员不想转化可以在其**get方法**前添加该字段:`@JsonIgnore`
3. 如果将writeValueAsString方法封装在get方法中,get方法**必须**用`@JsonIgnore`修饰,否则会造成死循环。 2. 如何将时间的属性值转为字符串
```
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd");
String beginDayString = fmt.format(beginDay);
``` 3. jsp中很多重复代码也需要抽取,在admin中添加common文件夹,将各个jsp页面相同的方法抽取到comm下的jsp文件中
WEB-INF的作用
- 教育经验模块是如何访问的呢?
- 输入
http://localhost:8080/xr/education/admin
,然后到达EducationServlet类的admin方法 - 然后admin方法到数据库查询数据,然后通过转发(getRequestDispatcher)到education.jsp页面,同时将数据传递到jsp中,才能正常显示页面
- 如果用户直接访问jsp呢?
http://localhost:8080/xr/page/admin/education.jsp
,就会出现错误,因为没有数据 - 因此就需要禁止用户直接访问education.jsp,那么如何禁止?
- 将不想让用户访问的jsp文件放到WEB-INF文件夹下即可
- WEB-INF下的jsp文件只能通过转发才能访问
- 即将page文件夹直接全部拖到WEB-INF下即可
-
修改servlet下的转发路径
//所有转发jsp路径前加上 /WEB-INF request.getRequestDispatcher("/WEB-INF/page/admin/education.jsp").forward(request,response);
- 注意:assert中的资源(图片、css、js等)仍然放到webapp下
Service层的引入
- 层划分
- Servelt:控制器层
- 接收客户端的请求,接到请求去干一些事情
- Service:业务层
- 完成具体的业务,就是封装了一些具体的业务,比如更新用户信息(可能同时用到dao的get、save、update等方法)
- 封装的api可以直接服务于控制器层来使用
- Dao的api有时候不一定一次能满足Servelt的需要,因此出现了Service层
- Dao:数据访问层
- 访问数据库获取数据,就是封装了一些get、list、save、remove等方法
- 就是免于每次值接使用SQL语句访问
- Servelt:控制器层
- 在dao文件夹同级新建一个service文件夹
- 分别新建WebsiteService、EducationService两个类
- 代码分别如下:
-
WebsiteService
package com.zh.xr.service; import com.zh.xr.bean.Website; import com.zh.xr.dao.WebsiteDao; import java.util.List; public class WebsiteService { private WebsiteDao dao = new WebsiteDao(); /** * 删除 */ public boolean remove(Integer id) { return dao.remove(id); } /** * 保存或更新 */ public boolean save(Website website) { return dao.save(website); } /** * 获取单个对象 */ public Website get(Integer id) { return dao.get(id); } /** * 获取多个对象 */ public List<Website> list() { return dao.list(); } /** * 获取统计值 */ public int count() { return dao.count(); } }
-
EducationService代码如下:
package com.zh.xr.service; import com.zh.xr.bean.Education; import com.zh.xr.dao.EducationDao; import java.util.List; public class EducationService { private EducationDao dao = new EducationDao(); /** * 删除 */ public boolean remove(Integer id) { return dao.remove(id); } /** * 批量删除 */ public boolean remove(List<Integer> ids) { return dao.remove(ids); } /** * 保存或更新 */ public boolean save(Education education) { return dao.save(education) ; } /** * 获取单个对象 */ public Education get(Integer id) { return dao.get(id); } /** * 获取多个对象 */ public List<Education> list() { return dao.list(); } /** * 获取统计值 */ public int count() { return dao.count(); } }
-
-
将EducationServlet与WebsiteServlet中的dao全部换成service
WebsiteService service = new WebsiteService(); private EducationService service = new EducationService();