Spring web mvc + Thymeleaf ModelAttribute 编辑对象中的列表
Spring web mvc + Thymeleaf ModelAttribute editing the list in the object
我有一个用于编辑用户的表单,用户有角色,这是权限类型的对象列表,我希望能够使用复选框(可选)来设置用户将拥有的角色,但我不知道如何在 thymeleaf 中实现表单以及如何将具有给定角色的用户对象传递给控制器。
这是我的用户
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name="username", nullable = false, unique = true)
@NotBlank @Size(min=5, message = "Не менeе 5 знаков")
private String username;
@NotBlank @Size(min=5, message = "Не менeе 5 знаков")
@Column(name = "password")
private String password;
@Column(name = "enabled")
private boolean enabled;
@Column(name = "name")
private String name;
@Column(name = "surname")
private String surname;
@Column(name = "email", nullable = false, unique = true)
private String email;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
}, fetch = FetchType.EAGER)
@JoinTable(
name = "users_authorities",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "authority_id")
)
private Set<Authority> authorities = new HashSet<>();
public User(String username, String password, boolean enabled,
String name, String surname, String email) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.name = name;
this.surname = surname;
this.email = email;
}
public void addAuthority(Authority authority) {
this.authorities.add(authority);
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
}
这是我的编辑用户表单控制器
@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findById(id));
model.addAttribute("allAuthorities", authorityService.findAll());
return "users/edit-user";
}
这是我的编辑用户表单视图
<body>
<form th:method="PUT" th:action="@{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
<input type="hidden" th:field="*{id}" id="id">
<label for="username">Username: </label>
<input type="text" th:field="*{username}" id="username" placeholder="username">
<br><br>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<label for="enabled">Enabled </label>
<input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
<br><br>
</div>
<label for="name">Name: </label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<br><br>
<label for="surname">Surname: </label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<br><br>
<label for="email">Email: </label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<br><br>
<div th:each="auth:${allAuthorities}">
<label>
<span th:text="${auth.authority}"></span>
<input type="checkbox" name="authorities" th:checked="${user.authorities.contains(auth)}">
</label>
</div>
<input type="submit" value="Edit">
</form>
</body>
它是控制器,它从我的表单中获取数据
@PutMapping("/users/{id}")
public String editUser(@PathVariable("id") Long id,
@ModelAttribute("user") User user,
@RequestParam("authorities") List<Authority> authorities) {
user.setId(id);
userService.update(user);
return "redirect:/admin/users";
}
如果你需要,这是我的权限class
@Entity
@Table(name = "authorities")
@Data
@NoArgsConstructor
public class Authority implements GrantedAuthority {
@Id
private Long id;
@Column(name = "authority")
private String authority;
@Transient
@ManyToMany(mappedBy = "authorities")
private Set<User> users;
public Authority(Long id, String authority) {
this.id = id;
this.authority = authority;
}
}
我尝试将角色列表与用户对象分开传递,但这也不起作用,并给出了错误的请求错误。
为了解决这个问题,我在实体权限中添加了一个新的检查字段
@Entity
@Table(name = "authorities")
@Data
@NoArgsConstructor
public class Authority implements GrantedAuthority {
@Id
private Long id;
@Column(name = "authority", nullable = false, unique = true)
private String authority;
@Transient
private boolean checked;
public Authority(Long id, String authority) {
this.id = id;
this.authority = authority;
}
}
在将视图发送给它之前,我更改了用户对象的 authorites 字段。
@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable("id") Long id, Model model) {
User user = userService.findById(id);
List<Authority> allAuthorities = authorityService.findAll();
for(Authority auth : allAuthorities) {
if(user.getAuthorities().contains(auth)) {
auth.setChecked(true);
}
}
user.setAuthorities(allAuthorities);
model.addAttribute("user", user);
return "users/edit-user";
}
我还更改了 thymeleaf 模板
<form th:method="PUT" th:action="@{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
<label for="username">Username: </label>
<input type="text" th:field="*{username}" id="username" placeholder="username">
<br><br>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<label for="enabled">Enabled </label>
<input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
<br><br>
</div>
<label for="name">Name: </label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<br><br>
<label for="surname">Surname: </label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<br><br>
<label for="email">Email: </label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<br><br>
<div th:each="auth, itemStat: ${user.authorities}">
<label>
<span th:text="${auth.authority}"></span>
<input type="hidden"
th:field="*{authorities[__${itemStat.index}__].id}">
<input type="hidden"
th:field="*{authorities[__${itemStat.index}__].authority}">
<input type="checkbox" th:checked="${auth.checked}"
th:field="*{authorities[__${itemStat.index}__].checked}">
</label>
</div>
<input type="submit" value="Edit">
</form>
我的 put 控制器看起来像这样
@PutMapping("/users/{id}")
public String editUser(@PathVariable("id") Long id,
@ModelAttribute("user") User user) {
List<Authority> userAuthorities = user.getAuthorities();
userAuthorities.removeIf(auth -> !auth.isChecked());
user.setId(id);
userService.update(user);
return "redirect:/admin/users";
}
我在 UserService 中的保存和删除方法如下所示(如果有用的话)
@Override
@Transactional
public User save(User user) {
Optional<User> userFromDB = userRepository.findByUsername(user.getUsername());
Optional<Authority> userRole;
if(userFromDB.isPresent()) {
return null;
}
userRole = authorityRepository.findByAuthority("ROLE_USER");
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRole.ifPresent(user::addAuthority);
return userRepository.save(user);
}
@Override
@Transactional
public User update(User user) {
Optional<User> userFromDB = userRepository.findById(user.getId());
if(userFromDB.isPresent()){
//Adding the password for successfully update user in DB
user.setPassword(userFromDB.get().getPassword());
return userRepository.save(user);
}
return null;
}
这可能不是最优雅的解决方案,但我还没有找到另一个解决方案
我有一个用于编辑用户的表单,用户有角色,这是权限类型的对象列表,我希望能够使用复选框(可选)来设置用户将拥有的角色,但我不知道如何在 thymeleaf 中实现表单以及如何将具有给定角色的用户对象传递给控制器。
这是我的用户
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;
@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name="username", nullable = false, unique = true)
@NotBlank @Size(min=5, message = "Не менeе 5 знаков")
private String username;
@NotBlank @Size(min=5, message = "Не менeе 5 знаков")
@Column(name = "password")
private String password;
@Column(name = "enabled")
private boolean enabled;
@Column(name = "name")
private String name;
@Column(name = "surname")
private String surname;
@Column(name = "email", nullable = false, unique = true)
private String email;
@ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.DETACH,
CascadeType.REFRESH
}, fetch = FetchType.EAGER)
@JoinTable(
name = "users_authorities",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "authority_id")
)
private Set<Authority> authorities = new HashSet<>();
public User(String username, String password, boolean enabled,
String name, String surname, String email) {
this.username = username;
this.password = password;
this.enabled = enabled;
this.name = name;
this.surname = surname;
this.email = email;
}
public void addAuthority(Authority authority) {
this.authorities.add(authority);
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
}
这是我的编辑用户表单控制器
@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable("id") Long id, Model model) {
model.addAttribute("user", userService.findById(id));
model.addAttribute("allAuthorities", authorityService.findAll());
return "users/edit-user";
}
这是我的编辑用户表单视图
<body>
<form th:method="PUT" th:action="@{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
<input type="hidden" th:field="*{id}" id="id">
<label for="username">Username: </label>
<input type="text" th:field="*{username}" id="username" placeholder="username">
<br><br>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<label for="enabled">Enabled </label>
<input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
<br><br>
</div>
<label for="name">Name: </label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<br><br>
<label for="surname">Surname: </label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<br><br>
<label for="email">Email: </label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<br><br>
<div th:each="auth:${allAuthorities}">
<label>
<span th:text="${auth.authority}"></span>
<input type="checkbox" name="authorities" th:checked="${user.authorities.contains(auth)}">
</label>
</div>
<input type="submit" value="Edit">
</form>
</body>
它是控制器,它从我的表单中获取数据
@PutMapping("/users/{id}")
public String editUser(@PathVariable("id") Long id,
@ModelAttribute("user") User user,
@RequestParam("authorities") List<Authority> authorities) {
user.setId(id);
userService.update(user);
return "redirect:/admin/users";
}
如果你需要,这是我的权限class
@Entity
@Table(name = "authorities")
@Data
@NoArgsConstructor
public class Authority implements GrantedAuthority {
@Id
private Long id;
@Column(name = "authority")
private String authority;
@Transient
@ManyToMany(mappedBy = "authorities")
private Set<User> users;
public Authority(Long id, String authority) {
this.id = id;
this.authority = authority;
}
}
我尝试将角色列表与用户对象分开传递,但这也不起作用,并给出了错误的请求错误。
为了解决这个问题,我在实体权限中添加了一个新的检查字段
@Entity
@Table(name = "authorities")
@Data
@NoArgsConstructor
public class Authority implements GrantedAuthority {
@Id
private Long id;
@Column(name = "authority", nullable = false, unique = true)
private String authority;
@Transient
private boolean checked;
public Authority(Long id, String authority) {
this.id = id;
this.authority = authority;
}
}
在将视图发送给它之前,我更改了用户对象的 authorites 字段。
@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable("id") Long id, Model model) {
User user = userService.findById(id);
List<Authority> allAuthorities = authorityService.findAll();
for(Authority auth : allAuthorities) {
if(user.getAuthorities().contains(auth)) {
auth.setChecked(true);
}
}
user.setAuthorities(allAuthorities);
model.addAttribute("user", user);
return "users/edit-user";
}
我还更改了 thymeleaf 模板
<form th:method="PUT" th:action="@{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
<label for="username">Username: </label>
<input type="text" th:field="*{username}" id="username" placeholder="username">
<br><br>
<div sec:authorize="hasRole('ROLE_ADMIN')">
<label for="enabled">Enabled </label>
<input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
<br><br>
</div>
<label for="name">Name: </label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<br><br>
<label for="surname">Surname: </label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<br><br>
<label for="email">Email: </label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<br><br>
<div th:each="auth, itemStat: ${user.authorities}">
<label>
<span th:text="${auth.authority}"></span>
<input type="hidden"
th:field="*{authorities[__${itemStat.index}__].id}">
<input type="hidden"
th:field="*{authorities[__${itemStat.index}__].authority}">
<input type="checkbox" th:checked="${auth.checked}"
th:field="*{authorities[__${itemStat.index}__].checked}">
</label>
</div>
<input type="submit" value="Edit">
</form>
我的 put 控制器看起来像这样
@PutMapping("/users/{id}")
public String editUser(@PathVariable("id") Long id,
@ModelAttribute("user") User user) {
List<Authority> userAuthorities = user.getAuthorities();
userAuthorities.removeIf(auth -> !auth.isChecked());
user.setId(id);
userService.update(user);
return "redirect:/admin/users";
}
我在 UserService 中的保存和删除方法如下所示(如果有用的话)
@Override
@Transactional
public User save(User user) {
Optional<User> userFromDB = userRepository.findByUsername(user.getUsername());
Optional<Authority> userRole;
if(userFromDB.isPresent()) {
return null;
}
userRole = authorityRepository.findByAuthority("ROLE_USER");
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRole.ifPresent(user::addAuthority);
return userRepository.save(user);
}
@Override
@Transactional
public User update(User user) {
Optional<User> userFromDB = userRepository.findById(user.getId());
if(userFromDB.isPresent()){
//Adding the password for successfully update user in DB
user.setPassword(userFromDB.get().getPassword());
return userRepository.save(user);
}
return null;
}
这可能不是最优雅的解决方案,但我还没有找到另一个解决方案