Saya cuba menulis semula borang saya dengan cara yang tidak melibatkan sebarang muat semula halaman. Dalam erti kata lain, saya tidak mahu penyemak imbas membuat sebarang permintaan GET/POST semasa diserahkan. jQuery sepatutnya dapat membantu saya menyelesaikan masalah ini. Inilah meja saya (saya ada beberapa):
<!-- I guess this action doesn't make much sense anymore --> <form action="/save-user" th:object="${user}" method="post"> <input type="hidden" name="id" th:value="${user.id}"> <input type="hidden" name="username" th:value="${user.username}"> <input type="hidden" name="password" th:value="${user.password}"> <input type="hidden" name="name" th:value="${user.name}"> <input type="hidden" name="lastName" th:value="${user.lastName}"> <div class="form-group"> <label for="departments">Department: </label> <select id="departments" class="form-control" name="department"> <option th:selected="${user.department == 'accounting'}" th:value="accounting">Accounting </option> <option th:selected="${user.department == 'sales'}" th:value="sales">Sales </option> <option th:selected="${user.department == 'information technology'}" th:value="'information technology'">IT </option> <option th:selected="${user.department == 'human resources'}" th:value="'human resources'">HR </option> <option th:selected="${user.department == 'board of directors'}" th:value="'board of directors'">Board </option> </select> </div> <div class="form-group"> <label for="salary">Salary: </label> <input id="salary" class="form-control" name="salary" th:value="${user.salary}" min="100000" aria-describedby="au-salary-help-block" required/> <small id="au-salary-help-block" class="form-text text-muted">100,000+ </small> </div> <input type="hidden" name="age" th:value="${user.age}"> <input type="hidden" name="email" th:value="${user.email}"> <input type="hidden" name="enabledByte" th:value="${user.enabledByte}"> <!-- I guess I should JSON it somehow instead of turning into regular strings --> <input type="hidden" th:name="authorities" th:value="${#strings.toString(user.authorities)}"/> <input class="btn btn-primary d-flex ml-auto" type="submit" value="Submit"> </form>
Ini JS saya:
$(document).ready(function () { $('form').on('submit', async function (event) { event.preventDefault(); let user = { id: $('input[name=id]').val(), username: $('input[name=username]').val(), password: $('input[name=password]').val(), name: $('input[name=name]').val(), lastName: $('input[name=lastName]').val(), department: $('input[name=department]').val(), salary: $('input[name=salary]').val(), age: $('input[name=age]').val(), email: $('input[name=email]').val(), enabledByte: $('input[name=enabledByte]').val(), authorities: $('input[name=authorities]').val() /* ↑ i tried replacing it with authorities: JSON.stringify($('input[name=authorities]').val()), same result */ }; await fetch(`/users`, { method: 'PUT', headers: { ...getCsrfHeaders(), 'Content-Type': 'application/json', }, body: JSON.stringify(user) // tried body : user too }); }); }); function getCsrfHeaders() { let csrfToken = $('meta[name="_csrf"]').attr('content'); let csrfHeaderName = $('meta[name="_csrf_header"]').attr('content'); let headers = {}; headers[csrfHeaderName] = csrfToken; return headers; }
Ini ialah pengendali pengawal REST saya:
// maybe I'll make it void. i'm not sure i actually want it to return anything @PutMapping("/users") public User updateEmployee(@RequestBody User user) { service.save(user); // it's JPARepository's regular save() return user; }
User
Entiti:
@Entity @Table(name = "users") @Data @EqualsAndHashCode public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column private long id; @Column(nullable = false, unique = true) private String username; @Column(nullable = false) private String password; @Column private String name; @Column(name = "last_name") private String lastName; @Column private String department; @Column private int salary; @Column private byte age; @Column private String email; @Column(name = "enabled") private byte enabledByte; @ManyToMany @JoinTable(name = "user_role", joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id"), @JoinColumn(name = "username", referencedColumnName = "username")}, inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id"), @JoinColumn(name = "role", referencedColumnName = "role")}) @EqualsAndHashCode.Exclude private Set<Role> authorities;
角色
Entiti:
@Entity @Table(name = "roles") @Data @EqualsAndHashCode public class Role implements GrantedAuthority { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column private long id; @Column(name = "role", nullable = false, unique = true) private String authority; @ManyToMany(mappedBy = "authorities") @EqualsAndHashCode.Exclude private Set<User> userList;
Apabila saya menekan butang hantar saya melihat ini dalam konsol
WARN 18252 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.HashSet<pp.spring_bootstrap.models.Role>` from String value (token `JsonToken.VALUE_STRING`)]
Nampaknya saya entah bagaimana harus lulus Collection
的 JSON 表示,而不仅仅是 String
。在我之前的项目中,没有使用 jQuery,String
已使用我的自定义 Formatter
penyahserikatan yang berjaya
@Override public void addFormatters(FormatterRegistry registry) { registry.addFormatter(new Formatter<Set<Role>>() { @Override public Set<Role> parse(String text, Locale locale) { Set<Role> roleSet = new HashSet<>(); String[] roles = text.split("^\[|]$|(?<=]),\s?"); for (String roleString : roles) { if (roleString.length() == 0) continue; String authority = roleString.substring(roleString.lastIndexOf("=") + 2, roleString.indexOf("]") - 1); roleSet.add(service.getRoleByName(authority)); } return roleSet; } @Override public String print(Set<Role> object, Locale locale) { return null; } }); }
Saya telah google dan nampaknya Thymeleaf tidak mempunyai sebarang kaedah toJson()
. Maksud saya saya boleh menulis kaedah saya sendiri tetapi saya tidak tahu cara menggunakannya dalam templat Thymeleaf. Juga, ini mungkin bukan penyelesaian yang optimum
Ini ialah projek Boot, jadi saya mempunyai perpustakaan pengikat data Jackson
Bagaimanakah cara saya menghantar Koleksi dengan betul dari borang saya kepada pengendali acara JS dan kemudian kepada pengawal REST?
Saya menyemak berbilang soalan serupa yang dicadangkan oleh StackOverflow. Ia tidak kelihatan berkaitan (contohnya, ia melibatkan bahasa pengaturcaraan yang berbeza seperti C# atau PHP)
UPD: Saya baru mencuba ini. Malangnya ia tidak berjaya juga! (Mesej ralat yang sama)
// inside my config @Bean public Function<Set<Role>, String> jsonify() { return s -> { StringJoiner sj = new StringJoiner(", ", "{", "}"); for (Role role : s) { sj.add(String.format("{ \"id\" : %d, \"authority\" : \"%s\" }", role.getId(), role.getAuthority())); } return sj.toString(); }; }
<input type="hidden" th:name="authorities" th:value="${@jsonify.apply(user.authorities)}"/>
Namun, kaedahnya berfungsi seperti yang diharapkan
$(document).ready(function () { $('form').on('submit', async function (event) { /* ↓ logs: authorities input: {{ "id" : 1, "authority" : "USER" }} */ console.log('authorities input: ' + $('input[name=authorities]').val());
UPD2: GPT4 mengesyorkan ini
authorities: JSON.parse($('input[name=authorities]').val())
Sungguh pelik sekarang. Pangkalan data masih tidak berubah, walaupun! Konsol IDE kini tidak mempunyai ralat dan tidak menyebut sama sekali permintaan PUT (yang turut hadir dalam percubaan sebelumnya)! Selain itu, log penyemak imbas mempunyai mesej ini
Uncaught (in promise) SyntaxError: Expected property name or '}' in JSON at position 1 at JSON.parse (<anonymous>) at HTMLFormElement.<anonymous> (script.js:28:31) at HTMLFormElement.dispatch (jquery.slim.min.js:2:43114) at v.handle (jquery.slim.min.js:2:41098)
Saya tidak tahu apa maksudnya!
UPD3: GPT4 bijak. Lagi pintar dari saya. Ini benar sekali. Sebab ia tidak berfungsi di UPD2 ialah saya mengabaikan perkara lain yang dikatakan:
Medan kebenaran hendaklah dihantar sebagai tatasusunan objek dan bukannya rentetan.
Ini bermakna saya harus menggunakan kurungan segi empat sama, bukan pendakap, untuk StringJoiner
awalan dan akhiran saya:
// I also added some line breaks, but I doubt it was necessary @Bean public Function<Set<Role>, String> jsonify() { return s -> { StringJoiner sj = new StringJoiner(",\n", "[\n", "\n]"); for (Role role : s) { sj.add(String.format("{\n\"id\" : %d,\n\"authority\" : \"%s\"\n}", role.getId(), role.getAuthority())); } return sj.toString(); }; }
Saya pun tukar, macam ni
username: $('input[name=username]').val()
Untuk ini (Saya bodoh kerana tidak melakukan ini dengan segera)
username: $(this).find('input[name=username]').val()
Dan - viola - ia kini tersedia!
GPT4 pun perasan dah guna
'input[name=department]'
bukannya
'select[name=department]'
Saya juga menyelesaikan masalah ini
Collection
, bukan tatasusunan), jadinew StringJoiner(", ", "{", "}")
→new StringJoiner(", ", "[", "]")
p>用户名:$('input[name=username]').val()
→用户名:$(this).find('input[name=username]')。 val()
或更好用户名:$(this).find('[name=username]').val()
Tunggudepartment
由perwakilan elemen, jadi
'input[name=department]'
→'select[name=department]'
或'[name=department]'