后端的MVC设计模式
-
把实现一个业务的代码划分为三部分 , 分别是 : 页面相关(V),业务逻辑相关©,数据相关(M)
-
M : Model 数据模型, 对应的代码是数据库相关的Mapper部分
-
V : View 视图, 对应所有页面相关内容
-
C : Controller 控制器, 对应的是Controller相关代码
-
实现一个业务的顺序 : V页面相关代码 -> C Controller相关代码 -> M 数据库Mapper相关代码
-
排错时也是从这三部分代码中找问题
-
后端MVC涉及模式中的V页面相关,前端工程师将页面又划分为了MVC三部分
今天任务 :
将商品表的同步请求改为异步请求 , 将之前的操作成功显示到一个单独的页面修改为在操作界面中显示
前后端分离
- 如果前后端不分离, 后端服务器需要两套代码来应对, 手机客户端和浏览器客户端, 因为不同的客户端的需求内容是不一样的,这样后端的开发效率就会受影响.
- 前后端分离 : 指在Controller中不再处理页面相关内容, 浏览器客户端需要先请求页面,页面加载完之后从页面中再次发出请求获取数据, 得到数据后把数据展示在页面中,这个过程属于页面的局部刷新, 同步请求只能实现页面的整体刷新无法实现局部刷新, 所以以后不再使用同步请求, 全部使用异步请求,因为以后工作基本全是前后端分离思想.
商品管理添加商品步骤:
-
创建boot4-1工程 , 添加3个依赖 , 修改application.properties配置文件, 启动工程测试是否成功
-
创建index.html首页 和 insert.html页面
-
在insert.html页面中添加三个文本框和一个添加按钮 , 通过Vue对页面内容进行管理 , 此时需要把之前工程中的js文件夹复制到新工程 , 当点击添加按钮时向/insert地址发出异步post请求把用户输入的商品信息提交
-
创建controller.ProductController , 添加insert方法处理/insert请求 , 创建Product实体类 , 在insert方法中声明用来接收传递过来的参数(此参数需要通过@RequestBody注解进行修饰)
-
创建ProductMapper , 在里面添加insert方法通过@Insert注解修饰
-
在Controller中将ProductMapper装配进来 , 在insert方法中调用mapper的insert方法把接收到的Product对象传递过去
index.html
1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>商品管理首页</h1> <a href="/insert.html">添加商品</a> <a href="/list.html">商品列表</a> </body> </html>
|
将之前项目的js目录复制到新项目中
insert.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>添加商品</h1> <div> <input type="text" v-model="p.title" placeholder="商品标题"> <input type="text" v-model="p.price" placeholder="商品价格"> <input type="text" v-model="p.saleCount" placeholder="商品销量"> <input type="button" value="添加" @click="insert()"> </div> <script src="js/vue.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el : "div", data : { p : { title : "", price : "", saleCount : "" } }, methods : { insert() { axios.post("/insert",v.p).then(function (response) { alert("添加完成!"); location.href = "/"; }) }
}
}) </script> </body> </html>
|
list.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>商品列表</h1> <table border="1"> <tr> <th>商品id</th> <th>商品标题</th> <th>商品价格</th> <th>商品销量</th> </tr> <tr v-for="p in arr"> <td>{{p.id}}</td> <td>{{p.title}}</td> <td>{{p.price}}</td> <td>{{p.saleCount}}</td> </tr> </table> <script src="js/vue.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el : "table", data : { arr : [] }, created : function () { axios.get("/select").then(function (response) {
v.arr = response.data; }) } }) </script> </body> </html>
|
ProductController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package cn.tedu.boot41.controller;
import java.util.List;
@RestController public class ProductController {
@Autowired ProductMapper mapper;
@RequestMapping("insert") public void insert(@RequestBody Product product) { System.out.println("product="+product); mapper.insert(product); }
@RequestMapping("select") public List<Product> select() { List<Product> list = mapper.select(); return list; } }
|
Product
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package cn.tedu.boot41.entity;
public class Product { private Integer id; private String title; private Double price; private Integer saleCount;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public Double getPrice() { return price; }
public void setPrice(Double price) { this.price = price; }
public Integer getSaleCount() { return saleCount; }
public void setSaleCount(Integer saleCount) { this.saleCount = saleCount; }
@Override public String toString() { return "Product{" + "id=" + id + ", title='" + title + '\'' + ", price=" + price + ", saleCount=" + saleCount + '}'; } }
|
ProductMapper
1 2 3 4 5 6 7 8 9 10 11 12
| package cn.tedu.boot41.mapper;
@Mapper public interface ProductMapper { @Insert("insert into product values(null,#{title},#{price},#{saleCount})") void insert(Product product);
@Select("select * from product") @Result(property = "saleCount",column = "sale_count") List<Product> select(); }
|
JSON
-
JSON是一种轻量级的数据交换格式(数据封装格式)
-
客户端和服务器之间需要互相传递数据,当需要传递复杂数据时需要按照特定的格式将数据进行封装,JSON就是这样一个通用格式.
1
| [{"id":1,"title":"阿迪袜子","price":10.0,"saleCount":1000},{"id":3,"title":"裤子","price":50.0,"saleCount":400},{"id":4,"title":"袜子","price":5.0,"saleCount":100}]
|
服务器和客户端之间如何传递复杂数据
删除操作
list.html中添加删除部分代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>商品列表</h1> <table border="1"> <tr> <th>商品id</th> <th>商品标题</th> <th>商品价格</th> <th>商品销量</th> <th>操作</th> </tr> <tr v-for="p in arr"> <td>{{p.id}}</td> <td>{{p.title}}</td> <td>{{p.price}}</td> <td>{{p.saleCount}}</td> <td> <a href="javascript:void(0)" @click="del(p.id)">删除</a> </td> </tr> </table> <script src="js/vue.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el : "table", data : { arr : [] }, created : function () { axios.get("/select").then(function (response) {
v.arr = response.data; }) }, methods : { del(id) { axios.get("/delete?id="+id).then(function (response){ alert("删除成功!"); location.reload(); }) } } }) </script> </body> </html>
|
ProductController
1 2 3 4
| @RequestMapping("/delete") public void delete(int id){ mapper.deleteById(id); }
|
ProductMapper
1 2
| @Delete("delete from product where id=#{id}") void deleteById(int id);
|
商品管理删除商品步骤:
-
在商品列表页面中添加删除的超链接 ,废掉超链接的跳转功能添加点击事件,调用del方法, 在方法中向/delete发出异步get请求并且把商品的id传递过去
-
在ProductController中 添加delete方法处理/delete请求在方法中调用mapper的deleteById方法把接收到的id传递进去
-
实现mapper里面的deleteById方法
修改操作
list.html
1 2 3 4 5 6
| <td><a href="javascript:void(0)" @click="del(p.id)">删除</a> <a :href="'/update.html?id='+p.id">修改</a> </td>
|
update.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>修改商品页面</h1> <div> <input type="text" v-model="product.id" placeholder="id" readonly> <input type="text" v-model="product.title" placeholder="标题"> <input type="text" v-model="product.price" placeholder="价格"> <input type="text" v-model="product.saleCount" placeholder="销量"> <input type="button" value="修改" @click="update()"> </div> <script src="js/vue.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el:"div", data:{ product:{} }, created:function () { let id = location.search.split("=")[1]; axios.get("/selectById?id="+id).then(function (response) { v.product = response.data; }) }, methods:{ update(){ axios.post("/update",v.product).then(function (response) { alert("修改完成!"); location.href="/list.html"; }) } } }) </script> </body> </html>
|
ProductController
1 2 3 4 5 6 7 8 9 10 11
| @RequestMapping("/selectById") public Product selectById(int id){ System.out.println("id = " + id); return mapper.selectById(id); }
@RequestMapping("/update") public void update(@RequestBody Product product){ mapper.update(product); }
|
ProductMapper
1 2 3 4 5 6
| @Select("select * from product where id=#{id}") @Result(property = "saleCount",column = "sale_count") Product selectById(int id);
@Update("update product set title=#{title},price=#{price},sale_count=#{saleCount} where id=#{id}") void update(Product product);
|
商品管理修改商品步骤:
-
给列表页面添加 修改超链接 往/update.html页面跳转 并且传递过去商品id
-
创建update.html页面 在Vue的created方法中得到传递过来的id 然后向/selectById 发出异步的get请求 把id传递过去 把服务返回的数据用一个product对象接收, 并且页面内容和此对象进行绑定 这样对象有值页面就能跟着显示
-
在ProductController里面添加selectById方法 处理/selectById请求, 在方法中调用Mapper的selectById方法
-
实现mapper里面的selectById方法
-
给update.html页面中的修改按钮添加点击事件, 点击时向/update地址发出异步的post请求把商品的信息一起提交
-
在ProductController里面添加update方法处理/update请求,在方法中调用mapper的update方法
-
实现mapper里面的update方法 .
文件上传
创建boot4-2工程 , 添加3个依赖 , 修改application.properties配置文件, 启动工程测试是否成功
在static目录下复制两个文件夹js和css文件夹(已经上传)
index.html
1 2 3 4 5 6 7 8 9 10 11
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>文件上传测试</h1> <a href="/upload.html">文件上传页面</a> </body> </html>
|
1 2
| spring.servlet.multipart.max-file-size=10MB
|
复制HelloEUI.html模板到新项目中,改名为upload.html,并进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="css/eui.css"> </head> <body> <div id="app"> <el-upload action="/upload" name="picFile" list-type="picture-card" :on-preview="handlePictureCardPreview" :on-remove="handleRemove"> <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogVisible"> <img width="100%" :src="dialogImageUrl" alt=""> </el-dialog> </div> </body>
<script src="js/vue.js"></script>
<script src="js/eui.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el: '#app', data: function() { return { dialogImageUrl: '', dialogVisible: false } }, methods: { handleRemove(file, fileList) { console.log(file, fileList); console.log("文件名="+file.response); axios.get("/remove?name="+file.response).then(function (response) { console.log("服务器图片已经删除!") }) }, handlePictureCardPreview(file) { this.dialogImageUrl = file.url; this.dialogVisible = true; } } }) </script> </html>
|
UploadController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package cn.tedu.boot42.controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile;
import java.io.File; import java.io.IOException; import java.util.UUID;
@RestController public class UploadController {
@RequestMapping("upload") public String upload(MultipartFile picFile) throws IOException { System.out.println(picFile); String filename = picFile.getOriginalFilename(); String suffix = filename.substring(filename.lastIndexOf(".")); filename = UUID.randomUUID() + suffix; System.out.println("文件名:"+filename); String dirPath = "D:/files"; File dirFile = new File(dirPath); if (!dirFile.exists()) { dirFile.mkdirs(); } String filePath = dirPath+"/"+filename; picFile.transferTo(new File(filePath)); System.out.println("文件保存成功!请去此路径查看文件是否存在:"+filePath); return filename; }
@RequestMapping("remove") public void remove(String name) { String filePath = "D:/files/"+name; new File(filePath).delete(); } }
|
微博练习步骤
-
创建boot5-1工程 添加三个依赖
-
配置文件中添加以下内容(empdb改成weibo) ,并启动工程检查是否能够成功运行
1 2 3 4 5 6
| spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql://localhost:3306/weibo?characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false
spring.servlet.multipart.max-file-size=10MB
|
- 创建数据库和表
1 2 3 4 5 6 7
| create database weibo charset=utf8;
use weibo;
create table user(id int primary key auto_increment,username varchar(50),password varchar(50),nickname varchar(50))charset=utf8;
create table weibo(id int primary key auto_increment,content varchar(100),url varchar(255),nickname varchar(50),created timestamp,user_id int)charset=utf8;
|
- 在工程中添加首页index.html , 在工程中添加js和css文件夹(从4-2工程中复制),添加完之后需编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>微博首页</h1> <div> <a href="/reg.html">注册</a> <a href="/login.html">登录</a> </div> </body> </html>
|
注册功能 : reg.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>注册页面</h1> <div> <input type="text" v-model="user.username" placeholder="用户名"> <input type="text" v-model="user.password" placeholder="密码"> <input type="text" v-model="user.nickname" placeholder="昵称"> <input type="button" value="注册" @click="reg()"> </div> <script src="js/vue.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el:"div", data:{ user:{ username:"", password:"", nick:"" } }, methods:{ reg(){ axios.post("/reg",v.user).then(function (response) { if (response.data==1){ alert("注册成功!"); location.href="/"; }else{ alert("用户名已存在!"); }
}) } } }) </script>
</body> </html>
|
UserController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package cn.tedu.boot51.controller;
@RestController public class UserController { @Autowired(required = false) UserMapper mapper;
@RequestMapping("/reg") public int reg(@RequestBody User user){ System.out.println("user = " + user); User u = mapper.selectByUsername(user.getUsername()); if (u!=null){ return 2; } mapper.insert(user); return 1; } }
|
UserMapper
1 2 3 4 5 6 7 8 9 10 11 12
| package cn.tedu.boot51.mapper;
@Mapper public interface UserMapper { @Select("select * from user where username=#{username}") User selectByUsername(String username);
@Insert("insert into user values(null,#{username},#{password},#{nickname})") void insert(User user);
}
|
登录功能 : login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录页面</h1> <div> <input type="text" v-model="user.username" placeholder="用户名"> <input type="password" v-model="user.password" placeholder="密码"> <input type="button" value="登录" @click="login()"> </div> <script src="js/vue.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el:"div", data:{ user:{username:"",password:""} }, methods:{ login(){ axios.post("/login",v.user).then(function (response) { if (response.data==1){ alert("登录成功!"); location.href="/"; }else if(response.data==2){ alert("用户名不存在!"); }else{ alert("密码错误!"); } }) } } }) </script> </body> </html>
|
UserController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package cn.tedu.boot51.controller;
@RestController public class UserController { @Autowired(required = false) UserMapper mapper;
@RequestMapping("/login") public int login(@RequestBody User user){ System.out.println("user = " + user); User u = mapper.selectByUsername(user.getUsername()); if (u!=null){ if (user.getPassword().equals(u.getPassword())){ return 1; } return 3; } return 2; } }
|
登录成功后,除了页面显示不同外,还需要显示用户名 : index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>微博首页</h1> <div> <div v-if="isLogin"> <p>欢迎{{user.nickname}}回来!</p> <a href="/insert.html">发布微博</a> <a href="javascript:void(0)" @click="logout()">登出</a> </div> <div v-else> <a href="/reg.html">注册</a> <a href="/login.html">登录</a> </div> </div> <script src="js/vue.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el:"body>div", data:{ isLogin:false, user:{nickname:"刘德华"} }, created:function (){ axios.get("/currentUser").then(function (response) { v.user = response.data; v.isLogin = v.user==""?false:true; }) }, methods:{ logout(){ axios.get("/logout").then(function (response) { location.reload(); }) } } }) </script> </body> </html>
|
UserController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package cn.tedu.boot51.controller;
import cn.tedu.boot51.entity.User; import cn.tedu.boot51.mapper.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
@RestController public class UserController { @Autowired(required = false) UserMapper mapper;
@RequestMapping("/reg") public int reg(@RequestBody User user){ System.out.println("user = " + user); User u = mapper.selectByUsername(user.getUsername()); if (u!=null){ return 2; } mapper.insert(user); return 1; }
@RequestMapping("/login") public int login(@RequestBody User user, HttpSession session){ System.out.println("user = " + user); User u = mapper.selectByUsername(user.getUsername()); if (u!=null){ if (user.getPassword().equals(u.getPassword())){ session.setAttribute("user",u); return 1; } return 3; } return 2; }
@RequestMapping("currentUser") public User currentUser(HttpSession session) { return (User) session.getAttribute("user"); }
@RequestMapping("logout") public void logout(HttpSession session) { session.removeAttribute("user"); } }
|
如何通过Session对象记住登录状态
-
在登录成功时把当前客户端登录的user用户对象保存到当前客户端所对应的Session对象里面
-
每个客户端进入到首页index.html时会立即发请求获取当前客户端登录的用户对象 , 服务器接收到请求后会从当前客户端所对应的Session对象里面获取曾经保存过的用户对象(前提是登陆过) , 如果没有登录直接获取得到的是null返回给客户端 , 此时客户端得到的是""空字符串 , 客户端判断是否是空字符来表示是否登录过 , 通过给isLogin赋值true或false来控制页面显示的内容
会话管理
-
客户端和服务器之间进行数据传输遵循的是HTTP协议 , 此协议属于无状态协议(一次请求对应一次响应,响应完之后链接就会断开) 服务器是无法跟踪客户端的请求 , 通过Cookie服务器可以给客户端添加一个标识 , 当客户端再次发出请求时会带着这个Cookie这样服务器就能识别此客户端了, 但是由于Cookie是保存在客户端的存在被篡改的风险 , Session的出现解决了此问题
-
Cookie : 打孔式会员卡 , 数据保存在客户端
- 只能保存字符串数据
- 默认数据是保存在浏览器内存中 , 当会话结束(浏览器关闭)数据会删除 , 也可以设置自定义的保存时长 , 设置完之后数据会保存在磁盘中时间到了之后清除
- 应用场景 : 需要长时间保存的数据 , 比如 : 记住用户名和密码
-
Session : 相当于银行账户 , 数据保存在服务器内存中(工程重新启动会清空所有Session)
- 可以保存任意对象类型的数据
- 默认数据只能保存半小时左右 , 而且不建议修改保存时长 , 因为数据是保存在服务器的内存中的 , 服务器只有一个所以不能占用太多的内存
- 应用场景 : 保存登录状态,涉及敏感数据 , 因为数据保存在服务器会更安全
insert.html (发布微博页面)
首先整个复制4-2中上传图片的代码(upload),也需要将UploadController复制过来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="css/eui.css"> </head> <body> <h1>发布微博页面</h1> <div id="app"> <input type="text" v-model="weibo.content" placeholder="说点儿什么...">
<el-upload action="upload" name = "picFile" :limit="1" list-type="picture-card" :on-preview="handlePictureCardPreview" :on-success="handleSuccess" :on-remove="handleRemove"> <i class="el-icon-plus"></i> </el-upload> <el-dialog :visible.sync="dialogVisible"> <img width="100%" :src="dialogImageUrl" alt=""> </el-dialog> <input type="button" value="发布微博" @click="insert()"> </div> </body>
<script src="js/vue.js"></script>
<script src="js/eui.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el: '#app', data: function() { return { dialogImageUrl: '', dialogVisible: false, weibo:{ content:"", url:"" } } }, methods: { handleRemove(file, fileList) {
console.log(file, fileList); axios.get("/remove?name="+file.response).then(function (response){ alert("服务器图片删除成功!") } ) }, handlePictureCardPreview(file) { this.dialogImageUrl = file.url; this.dialogVisible = true; }, insert() { if (v.weibo.content.trim()==""||v.weibo.url=="") { alert("微博内容或者图片不能为空!") return; } axios.post("/insert",v.weibo).then(function (response) { alert("添加成功!"); location.href="/"; }) }, handleSuccess(response,file, fileList) { console.log("文件上传成功后,图片名="+response); v.weibo.url = response; } } }) </script> </html>
|
WeiboController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package cn.tedu.boot51.controller;
@RequestMapping("insert") public int insert(@RequestBody Weibo weibo, HttpSession session) { User u = (User) session.getAttribute("user"); if(u == null) { return 2; } weibo.setCreated(new Date()); weibo.setUserId(u.getId()); weibo.setNickname(u.getNickname()); System.out.println("weibo : "+weibo); mapper.insert(weibo); return 1; }
|
Weibo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| package cn.tedu.boot51.entity;
import java.util.Date;
public class Weibo { private Integer id; private String content; private String url; private String nickname; private Date created; private Integer userId;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getNickname() { return nickname; }
public void setNickname(String nickname) { this.nickname = nickname; }
public Date getCreated() { return created; }
public void setCreated(Date created) { this.created = created; }
public Integer getUserId() { return userId; }
public void setUserId(Integer userId) { this.userId = userId; }
@Override public String toString() { return "Weibo{" + "id=" + id + ", content='" + content + '\'' + ", url='" + url + '\'' + ", nickname='" + nickname + '\'' + ", created=" + created + ", userId=" + userId + '}'; } }
|
WeiboMapper
1 2 3 4 5
| @Mapper public interface WeiboMapper { @Insert("insert into weibo values (null,#{content},#{url},#{nickname},#{created},#{userId})") void insert(Weibo weibo); }
|
查询并显示微博
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>微博首页</h1> <div> <div v-if="isLogin"> <p>欢迎{{user.nickname}}回来!</p> <a href="insert.html">发布微博</a> <a href="javascript:void(0)" @click="logout()">登出</a> </div> <div v-else> <a href="reg.html">注册</a> <a href="login.html">登录</a> </div> <hr> <div v-for="weibo in arr"> <h3>{{weibo.nickname}}说:{{weibo.content}}</h3> <img :src="weibo.url" width="100"> </div> </div>
<script src="js/vue.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el:"body>div", data:{ isLogin:false, user:{nickname:"TH"}, arr:[] }, created:function (){ axios.get("currentUser").then(function (response) { v.user = response.data; v.isLogin = v.user == "" ? false : true; }), axios.get("select").then(function (response) { v.arr = response.data; }) }, methods:{ logout(){ axios.get("logout").then(function (response){ location.reload(); }) } } }) </script> </body> </html>
|
WeiboController
1 2 3 4
| @RequestMapping("select") public List<Weibo> select() { return mapper.select(); }
|
WeiboMapper
1 2
| @Select("select id,content,url,nickname from weibo order by created desc") List<Weibo> select();
|
需要将图片展示到页面中,由于浏览器无法直接访问磁盘,所以需要修改静态资源文件夹
1 2 3
| spring.web.resources.static-locations=file:F:/files,classpath:static
|
酷鲨商城项目步骤 :
-
创建coolshark项目, 选中三个依赖
-
从上一个工程中复制application.propertise 里面所有内容到新工程 , 然后停掉之前工程 , 运行新工程测试是否能够正常启动(如果不能启动 , 删除工程重新创)
-
添加3个文件夹和4个页面(已经上传) , 添加完之后Rebuild工程 , 然后运行工程访问首页检查是否正常显示
-
修改application.propertise里面数据库的名字为cs
-
建库建表
1 2 3 4 5 6 7
| create database cs charset=utf8;
use cs;
create table user(id int primary key auto_increment,username varchar(50),password varchar(50),nickname varchar(50))charset=utf8;
insert into user values(null,'admin','123456','管理员');
|
登录功能步骤:
a. 修改登录页面 , 给按钮添加点击事件 , 添加axios引入及login声明方法 , 点击时向/login发出请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="css/eui.css"> <style> body{ margin: 0; background-image: url("imgs/bg.jpg"); background-size: cover; text-align: center; } h1{ font-size: 72px; color: rgb(0,150,215); margin-bottom: 0; } img{ width: 100px; } h2{ font-size: 32px; color: #0095d7; margin:0; }
</style> </head> <body> <div id="app"> <h1>欢迎来到酷鲨商城</h1> <img src="imgs/shark.png" alt=""> <h2>CoolSharkMall</h2> <el-card style="width: 600px;height: 300px; margin: 0 auto;background-color: rgba(255,255,255,0.3)"> <el-form style="width: 400px;margin: 30px auto" label-width="60px" > <el-form-item label="用户名"> <el-input type="text" v-model="user.username" placeholder="请输入用户名"></el-input> </el-form-item> <el-form-item label="密码"> <el-input type="password" v-model="user.password" placeholder="请输入密码"></el-input> </el-form-item> <el-form-item> <el-button style="position: relative;right: 35px" type="primary" @click="login()">登录</el-button> </el-form-item> </el-form> </el-card> </div> </body>
<script src="js/vue.js"></script>
<script src="js/eui.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el: '#app', data: function() { return { user:{ username:"", password:"" } } }, methods:{ login() { axios.post("login",v.user).then(function (response) { if (response.data==1) { location.href="/admin.html"; }else if(response.data==2) { v.$message.error("用户名不存在"); }else { v.$message.error("密码错误"); } }) } } }) </script> </html>
|
b. 创建UserController , 添加login方法处理/login请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package cn.tedu.coolshark.controller;
@RestController public class UserController { @Autowired UserMapper mapper;
@RequestMapping("login") public int login(@RequestBody User user, HttpSession session) { User u = mapper.selectByUserName(user.getUsername()); if(u != null) { if (u.getPassword().equals(user.getPassword())) { session.setAttribute("user",u); return 1; } return 3; } return 2; } }
|
c. 创建User实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package cn.tedu.coolshark.entity;
public class User { private Integer id; private String username; private String password; private String nickname;
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 getNickname() { return nickname; }
public void setNickname(String nickname) { this.nickname = nickname; }
@Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", nickname='" + nickname + '\'' + '}'; } }
|
d. 创建UserMapper 添加 登录相关方法
1 2 3 4 5 6 7 8
| package cn.tedu.coolshark.mapper;
@Mapper public interface UserMapper { @Select("select * from user where username = #{username}") User selectByUserName(String username); }
|
首页分类展示功能步骤:
a. 创建表和准备数据
1 2 3
| create table category(id int primary key auto_increment,name varchar(50))charset=utf8;
insert into category values(null,"男装"),(null,"女装"),(null,"医药"),(null,"美食"),(null,"百货"),(null,"数码");
|
b. 创建Category实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package cn.tedu.coolshark.entity;
public class Category { private Integer id; private String name;
@Override public String toString() { return "Category{" + "id=" + id + ", name='" + name + '\'' + '}'; }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
|
c. 创建CategoryMapper,里面提供select方法
1 2 3 4 5
| @Mapper public interface CategoryMapper { @Select("select * from category") List<Category> select(); }
|
d. 创建CategoryController 里面添加select方法处理/category/select
1 2 3 4 5 6 7 8 9 10
| @RestController public class CategoryController { @Autowired(required = false) CategoryMapper mapper;
@RequestMapping("/category/select") public List<Category> select(){ return mapper.select(); } }
|
e. 在首页index.html页面中的created方法中向/category/select发出请求获取所有分类信息, 把查询到的数据赋值给一个数组变量 , 让页面中显示分类的地方 和数组进行绑定 通过v-for指令显示出所有分类信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <!DOCTYPE html> <html> <body> <div id="app"> <div style="width: 100%;height: 60px;background-color: #82c8ec"> <el-menu style="width: 1200px;margin: 0 auto" class="el-menu-demo" mode="horizontal" @select="handleSelect" background-color="#82c8ec" text-color="#fff" active-text-color="#fff"> <el-menu-item v-for="c in categoryArr" :index="c.id+''">{{c.name}}</el-menu-item> </el-menu> </div> </body>
<script src="js/vue.js"></script>
<script src="js/eui.js"></script> <script src="js/axios.min.js"></script> <script> let v = new Vue({ el: '#app', data: function() { return { categoryArr:[] bannerArr:[], topArr:[], productArr:[], wd:"" } }, created:function () { axios.get("/category/select").then(function (response) { v.categoryArr = response.data; }) }, methods:{ handleSelect(index){ console.log(index); } } }) </script> </html>
|
首页轮播图展示步骤:
a. 创建保存轮播图的表和轮播图数据
1 2 3
| create table banner(id int primary key auto_increment,url varchar(255))charset=utf8;
insert into banner values(null,'/imgs/b1.jpg'),(null,'/imgs/b2.jpg'),(null,'/imgs/b3.jpg'),(null,'/imgs/b4.jpg');
|
b. 在首页index.html页面中的created方法里面向/banner/select发出请求把得到的数据赋值给vue里面的 bannerArr数组
1 2 3 4
| axios.get("/banner/select").then(function (response) { v.bannerArr = response.data; })
|
c. 创建BannerController 添加select方法处理/banner/select请求
1 2 3 4
| @RequestMapping("/banner/select") public List<Banner> select(){ return mapper.select(); }
|
d. 创建Banner实体类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package cn.tedu.coolshark.entity;
public class Banner { private Integer id; private String url;
@Override public String toString() { return "Banner{" + "id=" + id + ", url='" + url + '\'' + '}'; }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; } }
|
e. 创建BannerMapper 里面提供select方法
1 2
| @Select("select * from banner") List<Banner> select();
|
后台管理页面分类管理步骤
-
在admin.html页面中先引入 axios框架
-
在页面中添加created方法 在里面向/category/select发请求获取所有分类数据, 把得到的数据赋值给categoryArr数组,页面会自动展示
1 2 3 4 5 6
| created:function () { axios.get("/category/select").then(function (response) { v.categoryArr = response.data; }) }
|
- 给删除添加点击事件调用categoryDelete方法
1 2
| <el-popconfirm @confirm="categoryDelete(scope.$index, scope.row)" title="这是一段内容确定删除吗?">
|
- 在方法中向/category/delete发出请求并且把分类的id传递过去
1 2 3 4 5 6
| categoryDelete(index,category){ axios.get("/category/delete?id="+category.id).then(function () { <!-- location.reload();刷新会发起多重请求,并回到类目页面 --> v.categoryArr.splice(index,1); }) }
|
- 在CategoryController中添加delete方法 处理/category/delete请求,方法中调用mapper里面的deleteById方法
1 2 3 4
| @RequestMapping("/category/delete") public void delete(int id ){ mapper.deleteById(id); }
|
- 实现mapper里面的deleteById方法
1 2
| @Delete("delete from category where id=#{id}") void deleteById(int id);
|
添加分类步骤:
- 在点击添加分类时弹出文本输入框
1 2 3 4
| v.$prompt("请输入分类名称","提示",{confirmButtonText:"确定",cancelButtonText:"取消"}).then(function (object) { console.log("分类名称:"+ object.value); })
|
- 在then方法里面 向/category/insert发出异步请求 把分类的名称拼接到请求地址的后面
1 2 3 4 5 6 7 8 9 10 11 12 13
| then(function (object) { console.log("分类名称:"+ object.value); let name = object.value; if (name==null||name.trim()==""){ v.$message.error("分类名称不能为空"); return; } axios.get("/category/insert?name="+name).then(function (response) { location.reload(); }) }).catch(function(){ console.log("取消了"); })
|
- CategoryController里面创建insert方法处理/category/insert, 在参数列表中声明Category对象 用来接受传递过来的参数,调用mapper里面的insert方法
1 2 3 4
| @RequestMapping("/category/insert") public void insert(Category category){ mapper.insert(category); }
|
- 实现CategoryMapper里面的insert方法
1 2
| @Insert("insert into category values(null,#{name})") void insert(Category category);
|
管理页面轮播图展示
- 在admin.html页面中的created方法中 向/banner/select发出请求获取所有轮播图数据 把请求到的数据赋值给bannerArr数组,页面即可显示正确数据
1 2 3
| axios.get("banner/select").then(function (response) { v.bannerArr = response.data; })
|
删除轮播图
- 在admin.html页面中给删除按钮添加点击事件调用bannerDelete方法
1 2 3 4 5
| <el-popconfirm @confirm="bannerDelete(scope.$index, scope.row)" title="这是一段内容确定删除吗?"> <el-button size="mini" type="danger" slot="reference">删除</el-button> </el-popconfirm>
|
- 在方法中发出删除请求
1 2 3 4 5 6
| bannerDelete(index,banner){ axios.get("/banner/delete?id="+banner.id).then(function () { <!-- location.reload();刷新会发起多重请求,并回到类目页面 --> v.bannerArr.splice(index,1); }) }
|
- 在BannerController中添加delete方法处理/banner/delete请求 方法中调用mapper的deleteById方法
1 2 3 4
| @RequestMapping("/banner/delete") public void delete(int id){ mapper.deleteById(id); }
|
- 实现mapper中的deleteById方法
1 2
| @Delete("delete from banner where id=#{id}") void deleteById(int id);
|
网站图标favicon.ico
- 在工程的static里面添加favicon.ico图片文件
删除轮播图同时删除文件
1 2 3 4 5 6 7 8 9 10 11 12
| @RequestMapping("/banner/delete") public void delete(int id){ String url = mapper.selectUrlById(id); String filePath = "D:/files/"+url; new File(filePath).delete();
mapper.deleteById(id); }
|