Spring Boot进阶系列二
上一篇文章,主要分析了怎么建立一个Restful web service,系列二主要创建一个H5静态页面使用ajax请求数据,功能主要有添加一本书,请求所有书并且按照Id降序排列,以及查看,删除一本书。
1. 示例结构以及用到的技术点
1.1 项目逻辑架构
1.2 项目的技术选型
- Spring-Data-JPA
- H5
- Bootstrap
- jQuery + ajax
2. 后端服务
2.1 pom.xml依赖的jar包和系列一中结构完全一样,省略不表。
2.2 book表结构,book类定义和系列一中定义完全一样,此处省略1000+英文字母。
2.3 在com.example.demo.store包中的BooRepository定义如下,
public interface BookRepository extends JpaRepository<Book,Integer> {
@Query(value = "SELECT * FROM book order by Id desc", nativeQuery = true)
List<Book> findBooks();
}
2.4 因为项目简单的缘故,没有建立service包,在control中直接调用数据访问层接口。
@Controller
@RequestMapping(path = "/book")
public class BookController {
@Autowired
private BookRepository bookRepository;
@Value("${upload.path}")
private String filePath;
@PostMapping(path = "/save")
public @ResponseBody String save(HttpServletRequest request, @RequestParam("poster") MultipartFile poster) {
String tempPath = filePath;
String name = request.getParameter("name");
String category = request.getParameter("category");
Double price = Double.valueOf(request.getParameter("price"));
// Give today as a default date
Date publishDate = new Date();
String temp = request.getParameter("publishDate");
DateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd");
try {
publishDate = formatDate.parse(temp);
} catch (ParseException e2) {
// TODO Auto-generated catch block
e2.printStackTrace();
}
// Handle uploading picture
String fileName = poster.getOriginalFilename();
String suffixName = fileName.substring(fileName.lastIndexOf('.'));
fileName = UUID.randomUUID() + suffixName;
String posterPath = "image/" + fileName;
Book book = new Book();
book.setId(0);
book.setName(name);
book.setCategory(category);
book.setPrice(price);
book.setPublish_date(publishDate);
book.setPoster(posterPath);
tempPath += fileName;
try {
poster.transferTo(new File(tempPath));
bookRepository.save(book);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "Add new book successfully";
}
@GetMapping(path = "/one/{id}")
public @ResponseBody Book findOne(@PathVariable("id") Integer id) throws NotFoundException {
Optional<Book> book = this.bookRepository.findById(id);
if (book.isPresent()) {
return book.get();
} else {
throw new NotFoundException("Not found " + id);
}
}
@GetMapping(path = "/all")
public @ResponseBody Iterable<Book> findAll() {
return bookRepository.findBooks();
}
@DeleteMapping(path = "/del/{id}")
public @ResponseBody String deleteBook(@PathVariable("id") Integer id) {
bookRepository.deleteById(id);
return String.format("Delete book (%d) successfully!", id);
}
}
2.5 新建WebConfig文件,解决 springboot上传图片不能马上访问显示的问题
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${upload.path}")
private String filePath;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 映射图片保存地址
registry.addResourceHandler("/image/**").addResourceLocations("file:" + filePath);
}
}
// JSONWebConfig.java 详见系列一,完全一样,此处省略。
2.6 Application.properties内容如下:
值得一提的是连接MySQL数据库的url里面的时区用的是东八区的时间。
# DB connection configuration
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=Home2019
# JPA configuration
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
upload.path=D:/eclipse-workspace/ProductApp3/src/main/resources/static/image/
3. 前端H5
3.1 在public文件夹里面添加index.html
1 <!doctype html>
2 <html lang="en">
3 <head>
4 <!-- Required meta tags -->
5 <meta charset="utf-8"></meta>
6 <meta name="viewport"
7 content="width=device-width, initial-scale=1, shrink-to-fit=no"></meta>
8
9 <!-- Bootstrap CSS -->
10 <link rel="stylesheet"
11 href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
12 integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
13 crossorigin="anonymous"></link>
14
15 <title>Index Page</title>
16 </head>
17 <body>
18 <div class="container">
19 <h1>Springboot进阶系列二</h1>
20
21 <!-- Content here -->
22 <div class="table-responsive">
23 <table class="table table-striped table-sm" id="books">
24 <thead>
25 <tr>
26 <th>ID</th>
27 <th>Name</th>
28 <th>Category</th>
29 <th>Price</th>
30 <th>Operation</th>
31 </tr>
32 </thead>
33 <tbody></tbody>
34 </table>
35 </div>
36
37 <button type="button" class="btn btn-outline-secondary" data-toggle="modal" data-target="#addModal">Add Book</button>
38
39 <!-- Add Model -->
40 <div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
41 <div class="modal-dialog" role="document">
42 <div class="modal-content">
43 <div class="modal-header">
44 <h5 class="modal-title" id="exampleModalLabel">Add Book</h5>
45 </div>
46 <div class="modal-body">
47 <div class="form-group row">
48 <label for="inputName" class="col-sm-2 col-form-label">Name</label>
49 <div class="col-sm-10">
50 <input type="text" class="form-control" id="inputName" placeholder="Name">
51 </div>
52 </div>
53 <div class="form-group row">
54 <label for="categorySelect" class="col-sm-2 col-form-label">Category</label>
55 <div class="col-sm-10">
56 <select class="form-control" id="categorySelect">
57 <option>武侠</option>
58 <option>历史</option>
59 <option>军事</option>
60 <option>国学</option>
61 <option>投资</option>
62 <option>管理</option>
63 <option>传记</option>
64 </select>
65 </div>
66 </div>
67 <div class="form-group row">
68 <label for="inputPrice" class="col-sm-2 col-form-label">Price</label>
69 <div class="col-sm-10">
70 <input type="text" class="form-control" id="inputPrice" placeholder="Price">
71 </div>
72 </div>
73 <div class="form-group row">
74 <label for="inputDate" class="col-sm-2 col-form-label">Publish Date</label>
75 <div class="col-sm-10">
76 <input type="date" class="form-control" id="inputDate" />
77 </div>
78 </div>
79 <div class="form-group row">
80 <label for="inputPoster" class="col-sm-2 col-form-label">Poster</label>
81 <div class="col-sm-10">
82 <input type="file" class="form-control" id="inputPoster" />
83 </div>
84 </div>
85 </div>
86
87 <div class="modal-footer">
88 <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
89 <button type="button" class="btn btn-primary" id="saveBook">Save</button>
90 </div>
91 </div>
92 </div>
93 </div>
94
95 <!-- View Model -->
96 <div class="modal fade" id="viewModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
97 <div class="modal-dialog" role="document">
98 <div class="modal-content" style="width:768px">
99 <div class="modal-header">
100 <h5 class="modal-title" id="exampleModalLabel">View Book</h5>
101 </div>
102 <div class="modal-body">
103 <div class="form-group row">
104 <label for="nameView" class="col-sm-2 col-form-label">Name</label>
105 <div class="col-sm-10">
106 <input type="text" class="form-control" id="nameView" readonly>
107 </div>
108 </div>
109 <div class="form-group row">
110 <label for="categoryView" class="col-sm-2 col-form-label">Category</label>
111 <div class="col-sm-10">
112 <select class="form-control" id="categoryView" disabled>
113 <option>武侠</option>
114 <option>历史</option>
115 <option>军事</option>
116 <option>国学</option>
117 <option>投资</option>
118 <option>管理</option>
119 <option>传记</option>
120 </select>
121 </div>
122 </div>
123 <div class="form-group row">
124 <label for="priceView" class="col-sm-2 col-form-label">Price</label>
125 <div class="col-sm-10">
126 <input type="text" class="form-control" id="priceView" readonly>
127 </div>
128 </div>
129 <div class="form-group row">
130 <label for="dateView" class="col-sm-2 col-form-label">Publish Date</label>
131 <div class="col-sm-10">
132 <input type="text" class="form-control" id="dateView" readonly>
133 </div>
134 </div>
135 <div class="form-group row">
136 <label for="posterView" class="col-sm-2 col-form-label">Poster</label>
137 <div class="col-sm-10">
138 <img src="..." alt="Poster" id="posterView">
139 </div>
140 </div>
141 </div>
142
143 <div class="modal-footer">
144 <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
145 </div>
146 </div>
147 </div>
148 </div>
149 </div>
150
151
152 <!-- Optional JavaScript -->
153 <!-- jQuery first, then Popper.js, then Bootstrap JS -->
154 <script src="js/jquery-3.4.1.js"></script>
155 <script src="js/book.js"></script>
156 <script
157 src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
158 integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
159 crossorigin="anonymous"></script>
160 <script
161 src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
162 integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
163 crossorigin="anonymous"></script>
164 </body>
165 </html>
3.2 在static/js文件夹里面添加book.js
1 $(document).ready(function() {
2 $.ajax({
3 url: "/greeting"
4 }).then(function(data) {
5 $('#greeting-content').append(data.id + '---');
6 $('#greeting-content').append(data.content);
7 });
8
9 // Clean table
10 $('#books tr:not(:first)').empty();
11
12 $.getJSON('book/all').done(function (data) {
13 $.each(data, function (key, item) {
14 if (item != null) {
15 var tmp = JSON.stringify(item);
16 row = "<tr><td>" + item.id + "</td><td>" + item.name + "</td><td>" + item.category + "</td><td>" + item.price + "</td><td>" + '<button type="button" class="btn btn-outline-secondary" data-toggle="modal" data-target="#viewModal" onclick="viewBook('+item.id+')">View</button> <button type="button" class="btn btn-outline-secondary">Edit</button> <button type="button" class="btn btn-outline-secondary" onclick="delBook('+item.id+')">Delete</button>' + "</td><tr>";
17 $('#books tr:last').after(row);
18 }
19 });
20 });
21 });
22
23
24 $('#saveBook').on('click', function () {
25 // 处理图片上传
26 var fd = new FormData();
27 fd.append('name', $('#inputName').val());
28 fd.append('category', $('#categorySelect').val());
29 fd.append('price', $('#inputPrice').val());
30 fd.append('publishDate', $('#inputDate').val());
31 fd.append('poster', $('#inputPoster').get(0).files[0]);
32
33 $.ajax({
34 url: 'book/save',
35 type: 'POST',
36 data: fd,
37 cache: false,
38 processData: false,
39 contentType: false,
40 success: function () {
41 alert('Add new book successfully!');
42 // 刷新父窗口
43 $('#addModel').modal('hide');
44 window.location.reload();
45 },
46 error: function (xhr,status,errorThrown) {
47 alert('Failed to add new book.Error :' + errorThrown);
48 }
49 });
50 });
51
52
53 function viewBook(id) {
54 $.ajax({
55 url: '/book/one/' + id,
56 // Whether this is a POST or GET request
57 type: "GET",
58 // The type of data we expect back
59 dataType : "json"
60 })
61 // Code to run if the request succeeds (is done);
62 // The response is passed to the function
63 .done(function(data) {
64 $('#viewModel').modal('show');
65 $('#nameView').val(data.name);
66 $('#categoryView').val(data.category);
67 $('#priceView').val(data.price);
68 $('#dateView').val(data.publish_date);
69 $('#posterView').attr('src',data.poster);
70 })
71 // Code to run if the request fails; the raw request and
72 // status codes are passed to the function
73 .fail(function( xhr, status, errorThrown ) {
74 alert( "Sorry, there was a problem!" );
75 console.log( "Error: " + errorThrown );
76 console.log( "Status: " + status );
77 console.dir( xhr );
78 })
79 // Code to run regardless of success or failure;
80 .always(function( xhr, status ) {
81 //alert( "The request is complete!" );
82 });
83 }
84
85
86 function delBook(id) {
87 if(true == confirm("Are you sure to delete it?")) {
88 $.ajax({
89 url:'book/del/' + id,
90 type:'DELETE',
91 // The type of data we expect back
92 dataType : "json"
93 })
94 // Code to run if the request succeeds (is done);
95 // The response is passed to the function
96 .done(function(data) {
97 alert(data);
98 window.location.reload();
99 })
100 // Code to run if the request fails; the raw request and
101 // status codes are passed to the function
102 .fail(function( xhr, status, errorThrown ) {
103 alert( "Sorry, there was a problem!" );
104 console.log( "Error: " + errorThrown );
105 console.log( "Status: " + status );
106 console.dir( xhr );
107 })
108 // Code to run regardless of success or failure;
109 .always(function( xhr, status ) {
110 //alert( "The request is complete!" );
111 });
112 }
113 }
4. 运行程序
浏览器中输入http://localhost:8080/index.html,程序默认输出所有记录,按Id大小降序排列。
程序最终实现了添加,查看,删除功能。