spring - 反应中的Spring Boot分页实现
问题描述
大家好 !我希望你一切都好 ?
我目前正在开发一个简单的应用程序来了解更多关于 spring-boot、spring-security、jpa 和服务器端分页的信息。
我成功构建了我的 API,并且我有一个使用分页的控制器。为了使用这个 API,我开发了一个 react 应用程序,一切都按照我的意愿运行(2FA、帐户创建、登录、忘记密码、更改密码……)。
但是现在,我想开发一个管理界面,我希望能够在其中管理用户帐户。
因此,我创建了一个需要管理员角色并进行连接的控制器。要返回用户列表,我使用扩展 PagingAndSortingRepository 的 DAO,它工作正常!
现在我想在我的反应应用程序中实现这个分页,这就是我遇到问题的地方。
我已经尝试了许多分页库来做出反应。但是他们都需要在一个列表中恢复所有数据,这是我不想要的。于是,我开始开发自己的分页组件。我可以轻松地进行正面分页,但仅用于访问第一页、最后一页、前一页和后一页,但是我不能像这样添加页面选择按钮:1 3 [...][n- 2][n-1][n]。
这是我的代码:
用户.java
@Entity
@Table(
name = "USER",
uniqueConstraints = {
@UniqueConstraint(columnNames = "username"),
@UniqueConstraint(columnNames = "email")
}
)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Size(min = 3, max = 16)
private String username;
@NaturalId
@NotBlank
@Email
private String email;
@NotBlank
@Size(max = 100)
private String password;
private boolean isUsingTwoFA;
private String twoFASecret;
@Column(columnDefinition = "DATE NOT NULL")
private LocalDate accountCreationDate;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "USER_ROLE",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
}
UserDto.java
public class UserDto {
private String username;
private String email;
private String accountCreationDate;
private boolean isUsingTwoFA;
List<String> roles;
}
PagedUserRepository.java
public interface PagedUserRepository extends PagingAndSortingRepository<User, Long> {
Page<User> findAll(Pageable pageable);
}
仪表板用户服务.java
@Service
public class DashboardUsersService {
private UserRepository userRepository;
private PagedUserRepository pagedUserRepository;
@Autowired
public DashboardUsersService(UserRepository userRepository, PagedUserRepository pagedUserRepository) {
this.userRepository = userRepository;
this.pagedUserRepository = pagedUserRepository;
}
public ResponseEntity<?> getUsers(int page, int size) {
Pageable pageable = PageRequest.of(page, size);
Page<User> usersPage = pagedUserRepository.findAll(pageable);
if (usersPage.getContent().isEmpty()) {
return new ResponseEntity<>(new ApiResponseDto(false, "Unable to retrieve any user"), HttpStatus.INTERNAL_SERVER_ERROR);
}
final List<UserDto> users = usersPage.getContent()
.stream()
.map(UserDto::new)
.collect(Collectors.toList());
return new ResponseEntity<>(new PagedResponseDto(users, usersPage), HttpStatus.OK);
}
}
仪表板用户控制器.java
@CrossOrigin(maxAge = 36000)
@RestController
@RequestMapping(path = "/api/secure/admin/dashboard/users")
public class DashboardUsersController {
@Autowired
private DashboardUsersService dashboardUsersService;
@Secured("ROLE_ADMIN")
@GetMapping
public ResponseEntity<?> getUsers(
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "size", defaultValue = "10") int size
) {
return dashboardUsersService.getUsers(page, size);
}
}
用户.js
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import {getPageUsers} from "../../../../api/AdminApi";
import UserTableLine from "./component/UserTableLine";
import UserPagination from "./component/UserPagination";
class Users extends Component{
state = {
pagedResponse: {},
users: [],
showLoading: false
};
constructor(props){
super(props);
this.getFirstPageUsers = this.getFirstPageUsers.bind(this);
this.handleChangePage = this.handleChangePage.bind(this);
}
componentDidMount(){
document.title = "Users management";
this.getFirstPageUsers();
}
getFirstPageUsers(){
const defaultPageable = {
pageNumber: 0
};
this.setState({showLoading: true});
getPageUsers(defaultPageable).then(res => {
this.setState({
pagedResponse: res,
users: res.content,
showLoading: false
});
}).catch(error => {
if(error.message && error.success === false){
this.props.showAlert(error.message, "error");
} else {
this.props.showAlert("Sorry! Something went wrong. Please try again!", "error");
}
this.setState({showLoading: false});
console.log(error);
});
}
handleChangePage(pageable){
this.setState({showLoading: true});
getPageUsers(pageable).then(res => {
this.setState({
pagedResponse: res,
users: res.content,
showLoading: false
});
}).catch(error => {
if(error.message && error.success === false){
this.props.showAlert(error.message, "error");
} else {
this.props.showAlert("Sorry! Something went wrong. Please try again!", "error");
}
this.setState({showLoading: false});
console.log(error);
});
}
render(){
let tableLines = [];
if(this.state.pagedResponse && this.state.users.length > 0){
tableLines = Object.keys(this.state.users)
.map(key => <UserTableLine key={key} user={this.state.users[key]}/>);
}
return(
<div>
<h1>Users <span className="text-muted" style={{fontSize: 11}}>management</span></h1>
<hr/>
{
this.state.showLoading
?
<div className="align-content-center text-center">
<h4 className="text-muted">Loading. Please Wait...</h4>
<i className="material-icons w3-xxxlarge w3-spin align-content-center">refresh</i>
</div>
:
<div>
<table className="table table-hover">
<thead>
<tr>
<th scope="col">Avatar</th>
<th scope="col">Username</th>
<th scope="col">email</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{tableLines}
</tbody>
</table>
<UserPagination
showAlert={this.props.showAlert}
page={this.state.pagedResponse}
handleChangePage={this.handleChangePage}
/>
</div>
}
</div>
);
}
}
export default withRouter(Users);
UserTableLine.js
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
import {Modal, ModalBody, ModalHeader} from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import {faSearch} from '@fortawesome/free-solid-svg-icons';
class UserTableLine extends Component {
state = {
showModalUserInfo: false,
user: {}
};
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
}
componentDidMount() {
this.setState({
user: this.props.user
});
}
toggle() {
this.setState({
showModalUserInfo: !this.state.showModalUserInfo
});
}
render() {
let roles;
if (this.state.user && this.state.user.roles) {
roles = Object.keys(this.state.user.roles).map(
key => " " + this.state.user.roles[key]
);
}
return (
<tr>
<th scope="row">
<img src={"http://cravatar.eu/helmavatar/" + this.state.user.username + "/32.png"}
alt={this.state.user.username} className="img-fluid"/>
</th>
<th>
{this.state.user.username}
</th>
<th>
{this.state.user.email}
</th>
<th>
<button className="btn btn-dark" onClick={this.toggle}><FontAwesomeIcon icon={faSearch}/></button>
</th>
<Modal isOpen={this.state.showModalUserInfo} toggle={this.toggle} className={this.props.className}>
<ModalHeader toggle={this.toggle}>
<div className="align-content-center align-items-center align-self-center text-center">
<img src={"http://cravatar.eu/helmavatar/" + this.state.user.username + "/50.png"}
alt={this.state.user.username} className="img-fluid rounded align-self-center"/>
{" " + this.state.user.username + ' { ' + roles + ' }'}
</div>
</ModalHeader>
<ModalBody>
<p>
<b>Email adresse:</b> {this.state.user.email}
</p>
<p>
<b>Account creation date:</b> {this.state.user.accountCreationDate}
</p>
<p>
<b>2FA status:</b>
{
this.state.user.usingTwoFA
?
<span className="badge badge-success">enabled</span>
:
<span className="badge badge-danger">disabled</span>
}
</p>
</ModalBody>
</Modal>
</tr>
);
}
}
export default withRouter(UserTableLine);
最后,UserPagination.js
import React, {Component} from 'react';
import {withRouter} from 'react-router-dom';
class UserPagination extends Component {
state = {
pagination: {}
};
constructor(props) {
super(props);
this.onPageChange = this.onPageChange.bind(this);
this.goToFirstPage = this.goToFirstPage.bind(this);
this.goToLastPage = this.goToLastPage.bind(this);
this.goToPreviousPage = this.goToPreviousPage.bind(this);
this.goToNextPage = this.goToNextPage.bind(this);
this.setStatePromise = this.setStatePromise.bind(this);
}
componentDidMount() {
const pagination = {
firstPage: this.props.page.firstPage,
lastPage: this.props.page.lastPage,
currentPageable: {
sort: {
sorted: false,
unsorted: true
},
offset: this.props.page.offset,
pageSize: this.props.page.pageSize,
pageNumber: this.props.page.number
},
previousPageable: this.props.page.previousPageable,
nextPageable: this.props.page.nextPageable,
totalPages: this.props.page.totalPages,
totalElement: this.props.page.totalElement
};
this.setState({pagination});
}
setStatePromise(newState) {
return new Promise((resolve) => {
this.setState(newState, () => {
resolve();
});
});
}
onPageChange = (pageable) => {
this.props.handleChangePage(pageable);
};
goToFirstPage() {
const firstPage = {
sort: {
sorted: false,
unsorted: true
},
offset: 0,
pageSize: 10,
pageNumber: 0
};
this.onPageChange(firstPage);
}
goToLastPage() {
const lastPage = {
sort: {
sorted: false,
unsorted: true
},
pageSize: 10,
pageNumber: this.state.pagination.totalPages - 1
};
this.onPageChange(lastPage);
}
goToPreviousPage() {
const previousPage = this.state.pagination.previousPageable;
if (previousPage !== "INSTANCE") {
this.onPageChange(previousPage);
}
}
goToNextPage() {
const nextPage = this.state.pagination.nextPageable;
if (nextPage !== "INSTANCE") {
this.onPageChange(nextPage);
}
}
getPagesNumberButtons(){
let pages = [];
if (this.state.pagination) {
pages.push(
<li key={1} className="page-item active">
<p className="page-link">{this.state.pagination.currentPageable.pageNumber}</p>
</li>
);
}
return pages;
}
render() {
return (
<div>
<ul className="pagination">
<li className="page-item" onClick={this.goToFirstPage}>
<p className="page-link">«</p>
</li>
<li className="page-item" onClick={this.goToPreviousPage}>
<p className="page-link">Prev</p>
</li>
{this.getPagesNumberButtons}
<li id="nextPage" className="page-item" onClick={this.goToNextPage}>
<p className="page-link">Next</p>
</li>
<li id="lastPage" className="page-item" onClick={this.goToLastPage}>
<p className="page-link">»</p>
</li>
</ul>
</div>
);
}
}
export default withRouter(UserPagination);
但如果你更喜欢完整的代码:Github
如果您有解决我的问题的想法,或者您知道处理服务器端分页的库,我很感兴趣 :)
预先感谢您花时间阅读所有这些内容,更感谢您的帮助。
亚历克西斯
编辑 1:这是我从控制器得到的响应:
{
"content": [
{
"username": "test1",
"email": "test1@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test2",
"email": "test2@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test3",
"email": "test3@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test4",
"email": "test4@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test5",
"email": "test5@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test6",
"email": "test6@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test7",
"email": "test7@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test8",
"email": "test8@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test9",
"email": "test9@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
},
{
"username": "test10",
"email": "test10@gmail.com",
"accountCreationDate": "2018-08-22",
"roles": [
"USER"
],
"usingTwoFA": false
}
],
"offset": 0,
"pageNumber": 0,
"pageSize": 10,
"lastPage": false,
"totalElement": 24,
"totalPages": 3,
"size": 10,
"number": 0,
"numberOfElements": 10,
"firstPage": true,
"previousPageable": "INSTANCE",
"nextPageable": {
"sort": {
"sorted": false,
"unsorted": true
},
"offset": 10,
"pageSize": 10,
"pageNumber": 1,
"paged": true,
"unpaged": false
}
}
解决方案
在与@SGhaleb 互动后,我终于为这种分页开发了自己的组件并且它可以工作。这不是一个最佳解决方案(+ 1k 行代码),但对于第一个版本来说已经足够了,我稍后会对其进行优化。
这是所述组件的代码:
import React, {Component} from 'react';
class UserPagination extends Component {
constructor(props) {
super(props);
this.state = {
page: props.page,
pageSize: props.pageSize,
currentPage: props.currentPage,
totalNumberOfElements: props.totalNumberOfElements
};
this.onPageChange = this.onPageChange.bind(this);
this.goToFirstPage = this.goToFirstPage.bind(this);
this.goToLastPage = this.goToLastPage.bind(this);
this.goToPreviousPage = this.goToPreviousPage.bind(this);
this.goToNextPage = this.goToNextPage.bind(this);
this.buildPagination = this.buildPagination.bind(this);
}
onPageChange = (pageNumber) => {
this.props.handleChangePage(pageNumber);
};
static getDerivedStateFromProps(props, state) {
state = props;
return state;
}
goToFirstPage() {
this.onPageChange(0);
}
goToLastPage() {
this.onPageChange(this.state.page.totalNumberOfPages - 1);
}
goToPreviousPage() {
const previousPage = this.state.page.previousPageable;
if (previousPage !== "INSTANCE") {
this.onPageChange(previousPage.pageNumber);
}
}
goToNextPage() {
const {currentPage, page} = this.state;
const nextPage = page.nextPageable;
if (nextPage !== "INSTANCE") {
this.onPageChange(currentPage + 1);
}
}
buildPagination(page, currentPage) {
//PAGINATION LOGIC
//SEE LINK TO PASTEBIN.COM
}
render() {
const {page, currentPage} = this.state;
let pagination = this.buildPagination(page, currentPage);
return (
<ul className="pagination">
{pagination}
</ul>
);
}
}
export default UserPagination;
现在分页看起来像我想要的:
推荐阅读
- php - WampServer 3.2.3 无法访问局域网,
- python - .env 从 CLI 运行时未加载,但从 VSCode 运行时
- go - 如何使用 pgx 记录查询?
- javascript - 使用 XMLHttpRequest,我正在尝试建立一个旁边有评论的博客
- python - 来自 scipy 的 jarque_bera 计算
- android - KeyEvents在android中的文本之后没有占用空间
- c# - 使用自定义方法结合 Where 和 OrderByDescending
- sql - 这是一个 Sql-Server 查询,如何在 Postgresql 中执行?
- java - Quarkus 和 Redis
- java - Why Selection event not triggered when selection is done using keyboard?