是一种软件架构模式,把整个软件分为三层:Model,view,controller
Model:模型---获取数据,并处理,返回给 controller
entity:数据库实体类 User --- user表
service:业务控制层,其余的活都交给 service
dao:数据模型层 --- 操作数据库,执行 sql
View:视图 --- 看得见的页面,渲染数据,页面
controller:控制器 --- nservlet,接请求,给响应
耦合度:代码之间的关联关系
为什么分层:降低耦合度
View 层发起请求 -- Controller -- Service -- Dao -- Service -- Controller -- View
可以用一个 servlet 处理多个 post 请求:利用反射
可以定一个约定,必须提交到 /admin/xx.do
当前的servlet只接受 *.do 结尾的请求
写 *.do时,前面不能写 /
配置servlet映射时,* 和 / 不能同时出现,/* 除外
MVC 设计模式理念:一张表,一个 entity,一个servlet
工程目录如下
进行一个用户注册的界面,并指定接受请求并处理该页面的Servlet,View主要由就是jsp,html页面构成。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="register"> <form> <p> 账号:<input type="text" name="username" v-model="user.username" @blur="verifyUsername" placeholder="用户名在6-12位"> <span>{{msg}}</span> </p> <p> 密码:<input type="password" name="password" v-model="user.password"> </p> <p> 姓名:<input type="text" name="name" v-model="user.name"> </p> <p> 性别:<input type="radio" value="男" name="gender" v-model="user.gender"> 男 <input type="radio" value="女" name="gender" v-model="user.gender"> 女 </p> <p> 头像:<input type="file" name="profile" ref="profile"> </p> <p> <input type="button" value="提交" @click="registerData"> </p> </form> </div> <script src="static/js/vue.js"></script> <script src="static/js/axios.min.js"></script> <script> const register = new Vue({ el:"#register", data:{ msg:"", user:{ gender:'男' } }, methods:{ async verifyUsername() { if(this.user.username.length < 6 || this.user.username.length > 12){ this.msg = "用户名必须在6-12位"; return false; } // 定义一个标记 let flag = false; // 在这个函数中,验证用户名 // 发ajax请求到后台,验证用户名 // ajax是异步请求,ajax会单独开辟一个线程来自己走,和我们的主JS程序不在同一个线程内 // 我们需要让我们的ajax和我们的主JS在同一个线程内 // 通过async 和 await修饰符就把ajax改成了同步请求 await axios.get("admin/checkUser.do?username=" + this.user.username).then(response=>{ // console.log(response.data); this.msg = response.data.message; if(response.data.code == '0'){ flag = false; } if(response.data.code == '1'){ flag = true; } }); // 返回值是一个Promise对象。是一个特殊的对象。3个属性 return flag; }, registerData(){ // 这个result就是上面函数返回的Promise对象 let result = this.verifyUsername(); // ES6语法的“解构”,把上面函数的返回值拿到 result.then(r => { // 带有文件上传的数据提交 if(r) { // 要求,如果上传的数据中包含了二进制数据(文件),需要使用formData,来封装数据 let formData = new FormData(); formData.append("username",this.user.username); formData.append("password",this.user.password); formData.append("name",this.user.name); formData.append("gender",this.user.gender); // 头像,文件怎么拼? // this.$refs.profile.files[0],获取对应的文件的二进制形式 // $refs:代表设置了ref属性的表单元素 // profile:找到ref属性为profile的表单元素 // files[0]:找到ref属性为profile的第一个表单元素 formData.append("profile",this.$refs.profile.files[0]); // 发请求 // axios的完整写法 axios({ method:"post", url:"admin/addVip.do", data:formData, // 请求头 /* * 'content-Type':'multipart/form-data' * 代表我要传输的数据以多部分的格式来传输。 * HTML要求提交文件:multipart/form-data * 提交普通的数据:application/x-www-form-urlencoded * */ headers:{ 'content-Type':'multipart/form-data' } }).then(response => { let data = response.data; alert(data.message); if(data.code == '1'){ location.href = "login.html"; } }) } }); } } }); </script> </body> </html>
Controller接受View层请求的Servlet
package com.jsoft.mvc.controller; import com.alibaba.fastjson.JSON; import com.jsoft.mvc.entity.Vip; import com.jsoft.mvc.service.VipService; import com.jsoft.mvc.service.impl.VipServiceImpl; import com.jsoft.mvc.util.MessageUtil; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.time.LocalDate; import java.util.UUID; @WebServlet("*.do") @MultipartConfig // 用来标记当前的servlet要接收多部分的数据格式,当前的servlet可以接收文件 public class VipController extends HttpServlet { // Controller调用service private VipService vipService = new VipServiceImpl(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 利用反射取到对应的方法名 String servletPath = req.getServletPath(); String methodName = servletPath.substring(1); methodName = methodName.substring(methodName.lastIndexOf("/") + 1,methodName.length() - 3); try { Method method = getClass().getDeclaredMethod(methodName, HttpServletRequest.class, HttpServletResponse.class); method.invoke(this,req,resp); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } private void addVip(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter out = resp.getWriter(); String username = req.getParameter("username"); String password = req.getParameter("password"); String name = req.getParameter("name"); String gender = req.getParameter("gender"); Vip vip = new Vip(username,password,name,gender); // 处理文件上传 Part part = req.getPart("profile"); String fileName = part.getSubmittedFileName(); InputStream inputStream = part.getInputStream(); // 文件名,防止出现一样的名字 fileName = UUID.randomUUID() + fileName; // 根据日期创建图片所放的位置 LocalDate localDate = LocalDate.now(); String prePath = "/" + localDate.getYear() + "/" + localDate.getMonthValue() + "/"; File file = new File("E:/upload" + prePath); if(!file.exists()){ // 创建多级目录 file.mkdirs(); } fileName = prePath + fileName; OutputStream outputStream = new FileOutputStream("E:/upload/" + fileName); byte [] b = new byte[1024]; int len; while((len = inputStream.read(b))!= -1){ outputStream.write(b,0,len); } // 在数据库中同步保存文件的路径,只保存文件名 vip.setProfile(fileName); int i = vipService.register(vip); if(i > 0) { out.write(JSON.toJSONString(new MessageUtil(1,"注册成功,请登录!"))); } else { out.write(JSON.toJSONString(new MessageUtil(0,"注册失败,请重新填写!"))); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Model层在具体的编写中包括dao层和bean层还有service层。
(1)bean层的编写
package com.jsoft.mvc.entity; import java.io.Serializable; /* * 和表对应的实体类,entity * 要求: * 1.类名和表名相同 * 2.类中的属性名和表中的字段名相同 * 3.类中只能有对应的set,get方法和需要用到的构造器,如果有需要,可以写toString。 * 4.序列化。实现序列化接口 * */ public class Vip implements Serializable { private static final long serialVersionUID = -2352642267221918764L; private Integer id; private String username; private String password; private String name; private String gender; private String profile; private String salt; public Vip() {} public Vip(String username, String password,String name ,String gender ) { this.username = username; this.password = password; this.name = name; this.gender = gender; } public Vip(String username, String password) { this.username = username; this.password = password; } public String getSalt() { return salt; } public void setSalt(String salt) { this.salt = salt; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getProfile() { return profile; } public void setProfile(String profile) { this.profile = profile; } @Override public String toString() { return "Vip{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", gender='" + gender + '\'' + ", name='" + name + '\'' + ", profile='" + profile + '\'' + ", salt='" + salt + '\'' + '}'; } }
(2)dao层负责与数据库交互
public interface VipDao { int save(Vip vip) throws Exception; Vip findUserByUsername(String username) throws Exception; }
return后的方式是工程目录下util工具类的连接数据库的方法
public class VipDaoImpl extends DAOImpl<Vip> implements VipDao { @Override public int save(Vip vip) throws Exception { String sql = "insert into vip(username,password,name,gender,salt,profile) values(?,?,?,?,?,?)"; return update(sql,vip.getUsername(),vip.getPassword(),vip.getName(),vip.getGender(),vip.getSalt(),vip.getProfile()); } @Override public Vip findUserByUsername(String username) throws Exception { String sql = "select id,username,password,name,gender,profile,salt from vip where username = ? "; return get(sql,username); } }
public interface VipService { // 注册的方法 int register(Vip vip); boolean checkUserIsExists(String username); boolean login(Vip vip); }
public class VipServiceImpl implements VipService { private VipDao dao = new VipDaoImpl(); @Override public int register(Vip vip) { // 注册的业务执行的就是保存的操作 try { // 密码的加密处理 // 生成盐 String salt = MD5Util.getSalt(); // 对密码进行加密 // 加密过后还要重新赋值给vip vip.setPassword(MD5Util.stringToMD5(vip.getPassword() + salt)); vip.setSalt(salt); return dao.save(vip); } catch (Exception e) { throw new RuntimeException(e); } } @Override public boolean checkUserIsExists(String username) { try { Vip vip = dao.findUserByUsername(username); /* * 如果根据用户名查到的vip是null,说明用户名可用,用户名在数据中是没有的 * 如果返回值是true,则用户名可用 * */ return Objects.isNull(vip); } catch (Exception e) { throw new RuntimeException(e); } } }
对密码进行加密,使用MD5 + 盐的方式,利用工具类的 MD5Util (需要导包)
public class MD5Util { /* 获取加密的盐 */ public static String getSalt(){ String words = "qwertyuiopasdfghjklzxcvbnm,./;'[]=/*-+!@#$^&%*()<>?"; StringBuilder strb = new StringBuilder(); // 随机获取8个字符,当作盐 for (int i = 0; i < 8; i++) { strb.append(words.charAt((int) Math.floor(Math.random() * words.length()))); } return strb.toString(); } /* MD5加密 MD5 + 盐 加密 */ public static String stringToMD5(String str){ return DigestUtils.md2Hex(str.getBytes()); } }