上一篇文章,主要分析了怎么建立一个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
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8"></meta>
<meta name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"></meta> <!-- Bootstrap CSS -->
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous"></link> <title>Index Page</title>
</head>
<body>
<div class="container">
<h1>Springboot进阶系列二</h1> <!-- Content here -->
<div class="table-responsive">
<table class="table table-striped table-sm" id="books">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Category</th>
<th>Price</th>
<th>Operation</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div> <button type="button" class="btn btn-outline-secondary" data-toggle="modal" data-target="#addModal">Add Book</button> <!-- Add Model -->
<div class="modal fade" id="addModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Add Book</h5>
</div>
<div class="modal-body">
<div class="form-group row">
<label for="inputName" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputName" placeholder="Name">
</div>
</div>
<div class="form-group row">
<label for="categorySelect" class="col-sm-2 col-form-label">Category</label>
<div class="col-sm-10">
<select class="form-control" id="categorySelect">
<option>武侠</option>
<option>历史</option>
<option>军事</option>
<option>国学</option>
<option>投资</option>
<option>管理</option>
<option>传记</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="inputPrice" class="col-sm-2 col-form-label">Price</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputPrice" placeholder="Price">
</div>
</div>
<div class="form-group row">
<label for="inputDate" class="col-sm-2 col-form-label">Publish Date</label>
<div class="col-sm-10">
<input type="date" class="form-control" id="inputDate" />
</div>
</div>
<div class="form-group row">
<label for="inputPoster" class="col-sm-2 col-form-label">Poster</label>
<div class="col-sm-10">
<input type="file" class="form-control" id="inputPoster" />
</div>
</div>
</div> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="saveBook">Save</button>
</div>
</div>
</div>
</div> <!-- View Model -->
<div class="modal fade" id="viewModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content" style="width:768px">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">View Book</h5>
</div>
<div class="modal-body">
<div class="form-group row">
<label for="nameView" class="col-sm-2 col-form-label">Name</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="nameView" readonly>
</div>
</div>
<div class="form-group row">
<label for="categoryView" class="col-sm-2 col-form-label">Category</label>
<div class="col-sm-10">
<select class="form-control" id="categoryView" disabled>
<option>武侠</option>
<option>历史</option>
<option>军事</option>
<option>国学</option>
<option>投资</option>
<option>管理</option>
<option>传记</option>
</select>
</div>
</div>
<div class="form-group row">
<label for="priceView" class="col-sm-2 col-form-label">Price</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="priceView" readonly>
</div>
</div>
<div class="form-group row">
<label for="dateView" class="col-sm-2 col-form-label">Publish Date</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="dateView" readonly>
</div>
</div>
<div class="form-group row">
<label for="posterView" class="col-sm-2 col-form-label">Poster</label>
<div class="col-sm-10">
<img src="..." alt="Poster" id="posterView">
</div>
</div>
</div> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div> <!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="js/jquery-3.4.1.js"></script>
<script src="js/book.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
</body>
</html>
3.2 在static/js文件夹里面添加book.js
$(document).ready(function() {
$.ajax({
url: "/greeting"
}).then(function(data) {
$('#greeting-content').append(data.id + '---');
$('#greeting-content').append(data.content);
}); // Clean table
$('#books tr:not(:first)').empty(); $.getJSON('book/all').done(function (data) {
$.each(data, function (key, item) {
if (item != null) {
var tmp = JSON.stringify(item);
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>";
$('#books tr:last').after(row);
}
});
});
}); $('#saveBook').on('click', function () {
// 处理图片上传
var fd = new FormData();
fd.append('name', $('#inputName').val());
fd.append('category', $('#categorySelect').val());
fd.append('price', $('#inputPrice').val());
fd.append('publishDate', $('#inputDate').val());
fd.append('poster', $('#inputPoster').get(0).files[0]); $.ajax({
url: 'book/save',
type: 'POST',
data: fd,
cache: false,
processData: false,
contentType: false,
success: function () {
alert('Add new book successfully!');
// 刷新父窗口
$('#addModel').modal('hide');
window.location.reload();
},
error: function (xhr,status,errorThrown) {
alert('Failed to add new book.Error :' + errorThrown);
}
});
}); function viewBook(id) {
$.ajax({
url: '/book/one/' + id,
// Whether this is a POST or GET request
type: "GET",
// The type of data we expect back
dataType : "json"
})
// Code to run if the request succeeds (is done);
// The response is passed to the function
.done(function(data) {
$('#viewModel').modal('show');
$('#nameView').val(data.name);
$('#categoryView').val(data.category);
$('#priceView').val(data.price);
$('#dateView').val(data.publish_date);
$('#posterView').attr('src',data.poster);
})
// Code to run if the request fails; the raw request and
// status codes are passed to the function
.fail(function( xhr, status, errorThrown ) {
alert( "Sorry, there was a problem!" );
console.log( "Error: " + errorThrown );
console.log( "Status: " + status );
console.dir( xhr );
})
// Code to run regardless of success or failure;
.always(function( xhr, status ) {
//alert( "The request is complete!" );
});
} function delBook(id) {
if(true == confirm("Are you sure to delete it?")) {
$.ajax({
url:'book/del/' + id,
type:'DELETE',
// The type of data we expect back
dataType : "json"
})
// Code to run if the request succeeds (is done);
// The response is passed to the function
.done(function(data) {
alert(data);
window.location.reload();
})
// Code to run if the request fails; the raw request and
// status codes are passed to the function
.fail(function( xhr, status, errorThrown ) {
alert( "Sorry, there was a problem!" );
console.log( "Error: " + errorThrown );
console.log( "Status: " + status );
console.dir( xhr );
})
// Code to run regardless of success or failure;
.always(function( xhr, status ) {
//alert( "The request is complete!" );
});
}
}
4. 运行程序
浏览器中输入http://localhost:8080/index.html,程序默认输出所有记录,按Id大小降序排列。
程序最终实现了添加,查看,删除功能。