【发布时间】:2019-01-30 07:39:21
【问题描述】:
大家好!希望你一切都好?
我目前正在开发一个简单的应用程序,以了解有关 spring-boot、spring-security、jpa 和服务器端分页的更多信息。
我成功构建了我的 API,并且我有一个使用分页的控制器。 为了使用这个 API,我开发了一个 react 应用程序,一切都按照我的意愿运行(2FA、帐户创建、登录、忘记密码、更改密码……)。
但是现在,我想开发一个管理界面,我希望能够在其中管理用户帐户。
所以,我创建了一个需要管理员角色并进行连接的控制器。 为了返回用户列表,我使用了扩展 PagingAndSortingRepository 的 DAO,它工作正常!
现在我想在我的反应应用程序中实现这个分页,这就是我遇到问题的地方。
我已经尝试了许多用于响应的分页库。但是他们都需要在一个列表中恢复所有数据,这是我不想要的。于是,我开始开发自己的分页组件。我很容易进行正面分页,但仅用于访问第一页、最后一页、前一页和后一页,但我无法添加这样的页面选择按钮:13[... ][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);
}
DashboardUserService.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);
}
}
DashboardUserController.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);
}
}
Users.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
}
}
【问题讨论】:
-
所以你想创建一个数字分页来滚动和导航?没有“上一个”/“下一个”?
-
就是这样,但保留'prev' / 'next',以获得类似imgur.com/05ilghx
-
您说“但他们都需要恢复单个列表中的所有数据”,但您要实现什么目标?
-
嗨@SGhaleb!感谢您提供这两个链接。我认为第一个是我正在寻找的更多。今晚我将尝试实施此解决方案。当我测试此解决方案以提供有关此主题的反馈时,我会回到这个问题。
-
您好@SGhaleb 经过几次尝试,您提供给我的示例最终没有对应。所以我为这种类型的分页开发了一个通用组件,它可以工作!再次感谢您的帮助,因为您给我的链接帮助我开发了这个组件。这不是一个最佳解决方案(1k 行代码),但对于第一个版本来说已经足够了,我稍后会对其进行优化。
标签: spring reactjs spring-boot pagination spring-data-jpa