搜索词>>form 耗时0.0030
  • Form 获取JSON参数和URL参数

    Form前置准备首先我们有一个表单<form id="params">l; <input name="userName" value="123">l; <input name="userSex" value="男">l; </form>l;方式Form前置准备首先我们有一个表单<form id="params"> <input name="userName" value="123"> <input name="userSex" value="男"> </form>方式一:获取URL参数,用问号连接let params=$('#params').serialize(); //使用参数 let reqUrl='xxx.com/xxx.do?'+params实际得params内容格式为:userName=123&userSex=男方式二:获取JSON参数首先需要一个工具方法function getFormToJson(formObj){ let formData=$(formObj).serializeArray(); var obj={} for (var i in formData) { obj[formData[i].name]=formData[i]['value']; } return obj; }具体使用:let params=getFormJSON($('#params'));上述params参数实际为:{ "userName":"123", "userSex":"男" }
  • Jquery form 表单 reset()方法不生效解决

    Jquery 对form表单对象直接使用reset()方法是无法生效得,而且可能报错没有reset()这个方法Jquery 对form表单对象直接使用reset()方法是无法生效得,而且可能报错没有reset()这个方法。解决办法:$(form)[0].reset() 搞定收工​
  • FormData 使用详解

    如何创建FormData创建FormData对象有两种方式,一个是通过已有得form表单进行创建,另一个就是直接创建如何创建FormData创建FormData对象有两种方式,一个是通过已有得form表单进行创建,另一个就是直接创建。直接创建FormData并赋值var formData=new FormData(); formData.append("name","左搜"); console.log(formData.get("name"));输出结果为:>左搜通过已有from表单进行创建FormData对象//var form=$('#formId')[0];// var form=document.querySelector("#formId"); var fromData=new FormData(form); console.log(formData.get("name"))注意:jQuery方式获取得form默认是个数组,需要对其选择第一个数据。FormData如何取值输出FormData对象取值需要只用它得get("filedName")方法。方法参数为FormData对象里面得字段名称。注意:直接对FormData对象进行console.log(formData)是看不到值得,应该使用get方法来测试值是否设置成功。!!!
  • IE9 jQuery ajax文件上传兼容问题解决

    IE9 jQuery ajax文件上传兼容问题解决。主要通过jQuery的jquery.form插件解决的IE9不支持formData的文件上传问题。<h2>1.准备</h2> 下载jQuery1.x版本(测试用v1.12.4)<br /> 下载jQuery的form插件(<a href="http://plugins.jquery.com/form/" rel="external nofollow" target="_blank">下载地址</a>)<br /> 本例子是通过Java后端进行测试的,后端为spring boot框架 <h2>2.后端代码</h2> 1测试的controller代码 <pre> <code class="language-java">package com.example; import javax.servlet.http.HttpServletResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.ModelAndView; @Controller public class TestController { @GetMapping(value = { "/", "/index.html" }) public Object index() { return new ModelAndView("index"); } @PostMapping(value = { "/fileupload.do" }, produces = { "text/html;charset=UTF-8" }) @ResponseBody public Object fileUpload(String name, MultipartFile file) { try { if (file.getBytes().length > 0) { return "{\"message\":\"success\",\"name\":\"" + name + "\"}"; } else { return "{\"message\":\"fail\"}"; } } catch (Exception e) { throw new RuntimeException("文件上传异常"); } } @PostMapping(value = { "/fileupload2.do" }/*, produces = { "text/html;charset=UTF-8" }*/) public void fileUpload2(String name, MultipartFile file,HttpServletResponse response) { try { if (file.getBytes().length > 0) { response.setContentType(""); response.setHeader("Content-Type", "text/html;charset=UTF-8"); response.getWriter().write("{\"message\":\"success\",\"name\":\"" + name + "\"}"); } else { response.setHeader("Content-Type", "text/html;charset=UTF-8"); response.getWriter().write("{\"message\":\"fail\"}"); } } catch (Exception e) { throw new RuntimeException("文件上传异常"); } } } </code></pre> <span style="color:#e74c3c">注意:上面的第一种写法<strong>produces = { "text/html;charset=UTF-8" }</strong>必须这样写,其他写法则会出现各种问题</span> <p> </p> <h2>3.后端代码</h2> 1HTML页面代码 <pre> <code class="language-html"><!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title>测试</title> </head> <body> <form method="post" enctype="multipart/form-data" id="upload-form"> <input type="text" name="name" /> <input type="file" name="file"/> </form> <button type="button" id="btn-upload">上传测试</button> <script type="text/javascript" src="/libs/jquery.min.js"></script> <script type="text/javascript" src="/libs/jquery.form.js"></script> <script type="text/javascript" src="/libs/index.js"></script> </body> </html></code></pre> <br /> 2.index.js文件代码 <pre> <code class="language-javascript">/** * */ $(function() { $('#btn-upload').click(function(){ var options = { url : "/fileupload.do", success : function(data) { var returnData = JSON.parse(data); alert(data); }, resetForm : true, }; $("#upload-form").ajaxSubmit(options); }) })</code></pre> <h2>4jQuery form控件API</h2> <h2>Options</h2> <p><strong>Note:</strong> All standard <a href="http://api.jquery.com/jQuery.ajax" rel="external nofollow" target="_blank">$.ajax</a> options can be used.</p> <h3>beforeSerialize</h3> <p>Callback function invoked prior to form serialization. Provides an opportunity to manipulate the form before its values are retrieved. Returning <code>false</code> from the callback will prevent the form from being submitted. The callback is invoked with two arguments: the jQuery wrapped form object and the options object.</p> <pre> <code class="language-javascript">beforeSerialize: function($form, options) { // return false to cancel submit }</code></pre> <h3>beforeSubmit</h3> <p>Callback function invoked prior to form submission. Returning <code>false</code> from the callback will prevent the form from being submitted. The callback is invoked with three arguments: the form data in array format, the jQuery wrapped form object, and the options object.</p> <pre> <code class="language-javascript">beforeSubmit: function(arr, $form, options) { // form data array is an array of objects with name and value properties // [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] // return false to cancel submit }</code></pre> <h3>filtering</h3> <p>Callback function invoked before processing fields. This provides a way to filter elements.</p> <pre> <code class="language-javascript">filtering: function(el, index) { if ( !$(el).hasClass('ignore') ) { return el; } }</code></pre> <h3>clearForm</h3> <p>Boolean flag indicating whether the form should be cleared if the submit is successful</p> <h3>data</h3> <p>An object containing extra data that should be submitted along with the form.</p> <pre> <code>data: { key1: 'value1', key2: 'value2' } </code></pre> <h3>dataType</h3> <p>Expected data type of the response. One of: null, 'xml', 'script', or 'json'. The dataType option provides a means for specifying how the server response should be handled. This maps directly to jQuery's dataType method. The following values are supported:</p> <ul> <li>'xml': server response is treated as XML and the 'success' callback method, if specified, will be passed the responseXML value</li> <li>'json': server response will be evaluted and passed to the 'success' callback, if specified</li> <li>'script': server response is evaluated in the global context</li> </ul> <h3>delegation</h3> <p>true to enable support for event delegation <em>requires jQuery v1.7+</em></p> <pre> <code class="language-javascript">// prepare all existing and future forms for ajax submission $('form').ajaxForm({ delegation: true });</code></pre> <h3>error</h3> <p>Callback function to be invoked upon error.</p> <h3>forceSync</h3> <p>Only applicable when explicity using the iframe option or when uploading files on browses that don't support XHR2. Set to <code>true</code> to remove the short delay before posting form when uploading files. The delay is used to allow the browser to render DOM updates prior to performing a native form submit. This improves usability when displaying notifications to the user, such as "Please Wait..."</p> <h3>iframe</h3> <p>Boolean flag indicating whether the form should <em>always</em> target the server response to an iframe instead of leveraging XHR when possible.</p> <h3>iframeSrc</h3> <p>String value that should be used for the iframe's src attribute when an iframe is used.</p> <h3>iframeTarget</h3> <p>Identifies the iframe element to be used as the response target for file uploads. By default, the plugin will create a temporary iframe element to capture the response when uploading files. This options allows you to use an existing iframe if you wish. When using this option the plugin will make no attempt at handling the response from the server.</p> <h3>method</h3> <p>The HTTP method to use for the request (e.g. 'POST', 'GET', 'PUT').</p> <h3>replaceTarget</h3> <p>Optionally used along with the the target option. Set to true if the target should be replaced or false if only the target contents should be replaced.</p> <h3>resetForm</h3> <p>Boolean flag indicating whether the form should be reset if the submit is successful</p> <h3>semantic</h3> <p>Boolean flag indicating whether data must be submitted in strict semantic order (slower). Note that the normal form serialization is done in semantic order with the exception of input elements of <code>type="image"</code>. You should only set the semantic option to true if your server has strict semantic requirements and your form contains an input element of <code>type="image"</code>.</p> <h3>success</h3> <p>Callback function to be invoked after the form has been submitted. If a 'success' callback function is provided it is invoked after the response has been returned from the server. It is passed the following standard jQuery arguments:</p> <ol> <li><code>data</code>, formatted according to the dataType parameter or the dataFilter callback function, if specified</li> <li><code>textStatus</code>, string</li> <li><code>jqXHR</code>, object</li> <li><code>$form</code> jQuery object containing form element</li> </ol> <h3>target</h3> <p>Identifies the element(s) in the page to be updated with the server response. This value may be specified as a jQuery selection string, a jQuery object, or a DOM element.</p> <h3>type</h3> <p>The HTTP method to use for the request (e.g. 'POST', 'GET', 'PUT').<br /> An alias for <code>method</code> option. Overridden by the <code>method</code> value if both are present.</p> <h3>uploadProgress</h3> <p>Callback function to be invoked with upload progress information (if supported by the browser). The callback is passed the following arguments:</p> <ol> <li>event; the browser event</li> <li>position (integer)</li> <li>total (integer)</li> <li>percentComplete (integer)</li> </ol> <h3>url</h3> <p>URL to which the form data will be submitted.</p> <hr /> <h2>Utility Methods</h2> <h3>formSerialize</h3> <p>Serializes the form into a query string. This method will return a string in the format: <code>name1=value1&name2=value2</code></p> <pre> <code>var queryString = $('#myFormId').formSerialize();</code></pre> <h3>fieldSerialize</h3> <p>Serializes field elements into a query string. This is handy when you need to serialize only part of a form. This method will return a string in the format: <code>name1=value1&name2=value2</code></p> <pre> <code>var queryString = $('#myFormId .specialFields').fieldSerialize();</code></pre> <h3>fieldValue</h3> <p>Returns the value(s) of the element(s) in the matched set in an array. This method always returns an array. If no valid value can be determined the array will be empty, otherwise it will contain one or more values.</p> <h3>resetForm</h3> <p>Resets the form to its original state by invoking the form element's native DOM method.</p> <h3>clearForm</h3> <p>Clears the form elements. This method emptys all of the text inputs, password inputs and textarea elements, clears the selection in any select elements, and unchecks all radio and checkbox inputs. It does <em>not</em> clear hidden field values.</p> <h3>clearFields</h3> <p>Clears selected field elements. This is handy when you need to clear only a part of the form.</p> <hr /> <h2>File Uploads</h2> <p>The Form Plugin supports use of XMLHttpRequest Level 2 and <a href="https://developer.mozilla.org/en/XMLHttpRequest/FormData" rel="external nofollow" target="_blank">FormData</a> objects on browsers that support these features. As of today (March 2012) that includes Chrome, Safari, and Firefox. On these browsers (and future Opera and IE10) files uploads will occur seamlessly through the XHR object and progress updates are available as the upload proceeds. For older browsers, a fallback technology is used which involves iframes. <a href="http://malsup.com/jquery/form/#file-upload" rel="external nofollow" target="_blank">More Info</a></p>
  • spring mvc传递list集合对象到后端方法实现

    本文主要说明在spring mvc框架中如何接收前端发过来的集合对象和相关的处理。主要讲解两种方式,form表单提交方式和ajax提交的方式<h2>1.spring mvc 接收form表单传递的list对象集合处理</h2> 首先是前端页面代码: <pre> <code class="language-html"><!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>list对象参数测试</title> </head> <body> <h1>list对象参数测试</h1> <form action="/sendFormData.do" method="post"> 用户1姓名: <input type="text" name="param.users[0].name" /> 用户1性别 <input type="text" name="users[0].sex" /> 用户1年龄 <input type="number" name="users[0].age" /> <hr /> 用户2姓名: <input type="text" name="param.users[1].name" /> 用户2性别 <input type="text" name="users[1].sex" /> 用户2年龄 <input type="number" name="users[1].age" /> <button type="submit">提交</button> </form> <button type="button" id="json-btn">JSON集合对象参数测试</button> </body> </html></code></pre> <strong><span style="color:#e67e22">注意:这里只用看form表单包裹的内容js部分是后面讲解ajax做的准备</span></strong><br /> <br /> spring mvc 的controller处理方法 <pre> <code class="language-java">@PostMapping("/sendFormData.do") public Object sendFormData(ParamVO users) { System.out.println(users.getUsers().size()); return "/test"; } </code></pre> 这里或许你看出来了使用的一个对象去接收form表单提交的数组。这个对象的内容如下 <pre> <code class="language-java">package com.example.vo; import java.io.Serializable; import java.util.List; /** * 接收数组对象必须使用对象封装一次 * @author xq * */ public class ParamVO implements Serializable { /** * */ private static final long serialVersionUID = 1L; private List<UserVO> users; public List<UserVO> getUsers() { return users; } public void setUsers(List<UserVO> users) { this.users = users; } } </code></pre> spring mvc里面必须进行封装一次List<UserVO>集合就是我们需要得到的前端数据 <h2>2.spring mvc 接收ajax传递list集合对象方法实现</h2> 页面: <pre> <code class="language-html"><!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title>list对象参数测试</title> </head> <body> <h1>list对象参数测试</h1> <button type="button" id="json-btn">JSON集合对象参数测试</button> <script type="text/javascript" src="/jquery.min.js"></script> <script type="text/javascript"> $(function() { $('#json-btn').click(function() { send1(); }); }); function send1() { var saveDataAry = []; var data1 = { "name" : "test", "sex" : "男", "age" : 20 }; var data2 = { "name" : "ququ", "sex" : "女", "age" : 30 }; saveDataAry.push(data1); saveDataAry.push(data2); $.ajax({ type : "POST", url : "/sendData.json", dataType : "json",//必须json contentType : "application/json", // 指定这个协议很重要 data : JSON.stringify(saveDataAry), success : function(res) { alert("后端返回请求数据为:" + JSON.stringify(res)); } }); } </script> </body> </html></code></pre> <br /> 注意点是ajax的设置 <pre> <code class="language-html">dataType : "json",//必须json contentType : "application/json", // 指定这个协议很重要</code></pre> 后端spring mvc 接收方法: <pre> <code class="language-java"> @PostMapping("/sendData.json") @ResponseBody public Object sendData(@RequestBody List<UserVO> users) {// @RequestBody该注解很重要 System.out.println(users.size()); for (UserVO userVO : users) { System.out.println(userVO.toString()); } return users; }</code></pre>
  • HTML5+ajax上传图片/文件以及FormData使用简单讲解

    HTML5+ajax上传图片/文件以及FormData使用简单讲解,HTML5,ajax上传文件,ajaxHTML5+ajax上传图片/文件以及FormData使用简单讲解<br /> <br /> HTML5中FormData的使用,解决ajax上传文件/图片难题<br /> <strong>1.FormData的创建和使用方式一</strong><br /> 创建一个空的 FormData <pre> <code class="language-javascript">var fd= new FormData();</code></pre> <br /> 使用 append() 方法向该对象里添加字段 <pre> <code class="language-javascript">fd.append("username", "Groucho"); fd.append("userfile", fileInputElement.files[0]);</code></pre> <br /> <strong>2.FormData的创建和使用方式二</strong><br /> 使用HTML表单来初始化一个FormData对象 <pre> <code class="language-html"><form id="test-form"> <input type="text" name="name"/> <input type="password" name="pwd"/> </form></code></pre>   <pre> <code class="language-javascript">var formElement = document.getElementById("test-form"); var newFormData = new FormData(formElement);</code></pre> <br /> 在有了FormData对象之后使用ajax(这里简单引用的jQuery的ajax)<br />   <pre> <code class="language-javascript">$.ajax({     url:'Your Post Url',     type:'POST',//上传文件必须为POST,查看解释一     data:newFormData,//FormData对象     processData:false,//不转换请求数据     contentType:false,//contentType 设置为 false 是为了避免 JQuery 对其操作,从而失去分界符     success:function(data){         //成功回调     },     error:function(ex){         //错误回调     } });</code></pre> <br /> <em>解释一:这里我们就要先说说在 http 中传输文件的问题。起初,http 协议中没有上传文件方面的功能,直到 rfc1867 为 http 协议添加了这个功能。当然在 rfc1867 中限定 form 的 method 必须为 POST , enctype = “multipart/form-data” 以及<input type = "file">.</em>
  • thymeleaf textarea 赋值

    本文说一下在thymeleaf模板引擎中,如何给 textarea 赋值本文说一下在thymeleaf模板引擎中,如何给 textarea 赋值。普通的input赋值我们是这样的<input type="text" class="form-control" placeholder="搜索标题" name="searchTitle" th:value="${site.searchTitle}">但是textarea标签的赋值方式并非上面这样,而是以下面的方式赋值:<textarea name="searchDescription" type="text" style="width: 100%;resize: none;" rows="3" class="form-control" placeholder="搜索描述" th:text="${site.searchDescription}"></textarea>​​​​​​​好了搞定!!!
  • spring boot 文件上传 REST风格API ajax方式-Java编程

    Java编程之Spring Boot 文件上传 REST风格API ajax方式<h2>一、摘要说明</h2>   这篇文章将告诉你在spring boot(REST 风格)WEB项目中如何用ajax请求上传文件。<br /> <br />   本文章中需要使用的工具: <ol> <li>Spring Boot 1.4.3.RELEASE</li> <li>Spring 4.3.5.RELEASE</li> <li>Thymeleaf</li> <li>jQuery (webjars)</li> <li>Maven</li> <li>Embedded Tomcat 8.5.6</li> <li>Google Chrome 浏览器(Network Inspect)</li> </ol> <h2>二、项目结构</h2> 一个标准的maven项目结构<br /> <img alt="项目结构" class="img-thumbnail" src="/assist/images/blog/811600c9be364e56b3c68ebbe78e0b07.png" /> <h2>三、项目依赖</h2> 声明一个外部的jQuery webjar依赖项,用于HTML表单中的Ajax请求。<br /> <br /> <strong>pom.xml</strong> <pre> <code class="language-xml"><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mkyong</groupId> <artifactId>spring-boot-file-upload</artifactId> <packaging>jar</packaging> <version>1.0</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.3.RELEASE</version> </parent> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- hot swapping, disable cache for template, enable live reload --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.2.4</version> </dependency> </dependencies> <build> <plugins> <!-- Package as an executable jar/war --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project></code></pre> <h2>四、文件上传</h2> 为了支持AJAX请求和响应, 最简单的方式是返回<code>ResponseEntity</code>. <h2>4.1下面的例子演示了上传文件的三种可能方式:</h2> <ol> <li>单个文件上传 – <code>MultipartFile</code></li> <li>多个文件上传– <code>MultipartFile[]</code></li> <li>Map file upload to a Model – <code>@ModelAttribute</code></li> </ol> <strong>RestUploadController.java</strong> <pre> <code class="language-java">import com.mkyong.model.UploadModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @RestController public class RestUploadController { private final Logger logger = LoggerFactory.getLogger(RestUploadController.class); //Save the uploaded file to this folder private static String UPLOADED_FOLDER = "F://temp//"; // 3.1.1 Single file upload @PostMapping("/api/upload") // If not @RestController, uncomment this //@ResponseBody public ResponseEntity<?> uploadFile( @RequestParam("file") MultipartFile uploadfile) { logger.debug("Single file upload!"); if (uploadfile.isEmpty()) { return new ResponseEntity("please select a file!", HttpStatus.OK); } try { saveUploadedFiles(Arrays.asList(uploadfile)); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded - " + uploadfile.getOriginalFilename(), new HttpHeaders(), HttpStatus.OK); } // 3.1.2 Multiple file upload @PostMapping("/api/upload/multi") public ResponseEntity<?> uploadFileMulti( @RequestParam("extraField") String extraField, @RequestParam("files") MultipartFile[] uploadfiles) { logger.debug("Multiple file upload!"); // Get file name String uploadedFileName = Arrays.stream(uploadfiles).map(x -> x.getOriginalFilename()) .filter(x -> !StringUtils.isEmpty(x)).collect(Collectors.joining(" , ")); if (StringUtils.isEmpty(uploadedFileName)) { return new ResponseEntity("please select a file!", HttpStatus.OK); } try { saveUploadedFiles(Arrays.asList(uploadfiles)); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded - " + uploadedFileName, HttpStatus.OK); } // 3.1.3 maps html form to a Model @PostMapping("/api/upload/multi/model") public ResponseEntity<?> multiUploadFileModel(@ModelAttribute UploadModel model) { logger.debug("Multiple file upload! With UploadModel"); try { saveUploadedFiles(Arrays.asList(model.getFiles())); } catch (IOException e) { return new ResponseEntity<>(HttpStatus.BAD_REQUEST); } return new ResponseEntity("Successfully uploaded!", HttpStatus.OK); } //save file private void saveUploadedFiles(List<MultipartFile> files) throws IOException { for (MultipartFile file : files) { if (file.isEmpty()) { continue; //next pls } byte[] bytes = file.getBytes(); Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); Files.write(path, bytes); } } }</code></pre> <h2>4.2选择4.1中的第三种方式上传文件并且使用注解@ModelAttribute</h2> <strong>UploadModel.java</strong> <pre> <code class="language-java">import org.springframework.web.multipart.MultipartFile; public class UploadModel { private String extraField; private MultipartFile[] files; //getters and setters }</code></pre> <h2>五、视图文件的编写</h2> HTML form for multiple file uploads.<br /> <strong>upload.html</strong> <pre> <code class="language-html"><!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <body> <h1>Spring Boot - Multiple file upload example - AJAX</h1> <form method="POST" enctype="multipart/form-data" id="fileUploadForm"> <input type="text" name="extraField"/><br/><br/> <input type="file" name="files"/><br/><br/> <input type="file" name="files"/><br/><br/> <input type="submit" value="Submit" id="btnSubmit"/> </form> <h1>Ajax Post Result</h1> <pre> <span id="result"></span> </pre> <script type="text/javascript" src="webjars/jquery/2.2.4/jquery.min.js"></script> <script type="text/javascript" src="js/main.js"></script> </body> </html></code></pre> <h2>六、jQuery – Ajax Request</h2> jQuery通过f<code>#id获取form</code>,并且发送multipart form 数据通过ajax请求.<br /> <strong>resources/static/js/main.js</strong> <pre> <code class="language-javascript">$(document).ready(function () { $("#btnSubmit").click(function (event) { //stop submit the form, we will post it manually. event.preventDefault(); fire_ajax_submit(); }); }); function fire_ajax_submit() { // Get form var form = $('#fileUploadForm')[0]; var data = new FormData(form); data.append("CustomField", "This is some extra data, testing"); $("#btnSubmit").prop("disabled", true); $.ajax({ type: "POST", enctype: 'multipart/form-data', url: "/api/upload/multi", data: data, //http://api.jquery.com/jQuery.ajax/ //https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects processData: false, //prevent jQuery from automatically transforming the data into a query string contentType: false, cache: false, timeout: 600000, success: function (data) { $("#result").text(data); console.log("SUCCESS : ", data); $("#btnSubmit").prop("disabled", false); }, error: function (e) { $("#result").text(e.responseText); console.log("ERROR : ", e); $("#btnSubmit").prop("disabled", false); } }); }</code></pre> <h2>七、Exception Handler异常拦截</h2> 拦截来自 Ajax请求的异常, 只需要继承 <code>ResponseEntityExceptionHandler</code> 并且返回 <code>ResponseEntity</code>.<br /> <strong>RestGlobalExceptionHandler.java</strong> <pre> <code class="language-java">import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletRequest; //http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling @ControllerAdvice public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler { // Catch file size exceeded exception! @ExceptionHandler(MultipartException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity(ex.getMessage(), status); // example //return new ResponseEntity("success", responseHeaders, HttpStatus.OK); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } return HttpStatus.valueOf(statusCode); } }</code></pre> <h2>八、演示</h2> 使用默认的嵌入式Tomcat启动Spring启动<code>mvn spring-boot:run</code>.<br /> 8.1访问http://localhost:8080/,选择几个文件并单击submit以触发ajax请求。<br /> <img alt="访问并上传" class="img-thumbnail" src="/assist/images/blog/19d6d8c691d04a079fd5a18adc7560fa.png" /><br /> 8.2 Google Chrome浏览器,检查“网络检查”中的请求和响应<br /> <img alt="观察请求" class="img-thumbnail" src="/assist/images/blog/ae51363c32cf415b943dbdc57d659470.png" /><br /> 8.4 Google Chrome, “Request Payload”<br /> <img alt="上传内容" class="img-thumbnail" src="/assist/images/blog/7f1ff5d5dde64d89ae7afd664c5df0f5.png" /> <h2>9.curl工具测试</h2> More testing with <code>cURL</code> command.<br /> 9.1测试单个文件上传 <pre> <code>$ curl -F file=@"f:\\data.txt" http://localhost:8080/api/upload/ Successfully uploaded - data.txt</code></pre> <br /> 9.2.测试多个文件上传 <pre> <code>$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt" http://localhost:8080/api/upload/multi/ Successfully uploaded - data.txt , data2.txt</code></pre> 9.3测试多个文件上传使用maps to Model模式 <pre> <code>$ curl -F extraField="abc" -F files=@"f://data.txt" -F files=@"f://data2.txt" http://localhost:8080/api/upload/multi/model Successfully uploaded! </code></pre> <br /> 9.4测试一个大文件(超过100MB),将会提示下面的错误信息 <pre> <code>$ curl -F file=@"F://movies//300//Sample.mkv" http://localhost:8080/api/upload/ Attachment size exceeds the allowable limit! (10MB)</code></pre> <h2>十、curl测试加自定义错误对象测试</h2> 10.1创建一个自定义对象去存放异常信息<br /> <strong>CustomError.java</strong> <pre> <code class="language-java">public class CustomError { String errCode; String errDesc; public CustomError(String errCode, String errDesc) { this.errCode = errCode; this.errDesc = errDesc; } //getters and setters }</code></pre> 10.2更新全局的异常拦截支持自定义异常信息<br /> <strong>RestGlobalExceptionHandler.java</strong> <pre> <code class="language-java">import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartException; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import javax.servlet.http.HttpServletRequest; @ControllerAdvice public class RestGlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(MultipartException.class) @ResponseBody ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) { HttpStatus status = getStatus(request); return new ResponseEntity(new CustomError("0x000123", "Attachment size exceeds the allowable limit! (10MB)"), status); //return new ResponseEntity("Attachment size exceeds the allowable limit! (10MB)", status); } private HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } return HttpStatus.valueOf(statusCode); } }</code></pre> <br /> 10.3再次上传一个大文件 <pre> <code>$ curl -F file=@"F://movies//300//Sample.mkv" http://localhost:8080/api/upload/ {"errCode":"0x000123","errDesc":"Attachment size exceeds the allowable limit! (10MB)"}</code></pre> <br /> <a href="http://www.mkyong.com/wp-content/uploads/2017/01/spring-boot-file-upload-ajax-rest.zip" rel="external nofollow" target="_blank">demo项目下载</a>
  • bootstrap table插件使用说明demo

    bootstrap table插件使用说明demo该插件是基于bootstrap框架开发的一个table插件,功能强大实用性强<br /> 展示结果截图:<br /> <img alt="bootstrap table插件使用效果图" class="img-thumbnail" src="/assist/images/blog/6a731324-c558-4ca7-8e9a-08affc0a941c.png" style="height:561px; width:1117px" /><br /> 1.下载bootstrap table插件,<a href="https://github.com/wenzhixin/bootstrap-table" rel="nofollow" target="_blank">下载</a><br /> 2.导入插件相关的css和js文件,当然bootstrap框架的基本组件必须导入 <pre> <code class="language-html"><link rel="stylesheet" href="bootstrap3/plugins/bootstrap-table/dist/bootstrap-table.min.css"> <script src="bootstrap3/plugins/bootstrap-table/dist/bootstrap-table.js"></script> <script src="bootstrap3/plugins/bootstrap-table/dist/locale/bootstrap-table-zh-CN.js"></script></code></pre> 3.body中添加一个table,给一个id,添加一个搜索的工具栏 <pre> <code class="language-html"><div class="search_bar form-inline" id="search_bar"> <span>著作:</span> <select class="form-control" id="search_copyright"> <option value="">全部</option> <option value="Original">原创</option> <option value="Reproduced">转载</option> <option value="Translation">翻译</option> </select> <span>状态:</span> <select class="form-control" id="search_status"> <option value="">全部</option> <option value="Normal">已发布</option> <option value="UnAudit">待审核</option> </select> <span>排序:</span> <select class="form-control" id="search_order"> <option value="createTime" title="创建时间排序">创时</option> <option value="updateTime" title="更新时间排序" selected="selected">更时</option> </select> <span>标题:</span> <input type="text" class="form-control" placeholder="输入博客标题" id="serach_title">&nbsp;<button class="btn btn-default" id="search_btn">查询</button>&nbsp; </div> <table id="table"></table></code></pre> 4.写js <pre> <code class="language-javascript">$(function(){     $('#table').bootstrapTable({         url:'manager/blog/list.json',         method:'get',         contentType:'application/json',         showColumns: true, //显示隐藏列           showRefresh: true,  //显示刷新按钮         toolbar:'#search_bar',         queryParams:queryPrams,          pagination: true,           pageSize: 10,          pageList:[10],          sidePagination: "server",         columns: [{             field: 'title',             title: '博客标题'         },{             field: 'authorName',             title: '作者'         },{             field:'copyright',             title:'版权',             formatter:function(value,row,index){                 var cp=value;                 if(value=='Reproduced'){                     cp='转载';                 }else if(value=='Translation'){                     cp='翻译';                 }else if(value=='Original'){                     cp='原创';                 }                 return cp;             },              width:50         },{             field: 'autoAuditDate',             title: '自动审核日期'         },{             field: 'status',             title: '状态',             formatter:function(value,row,index){                 var status=value;                 if(value=='Normal'){                     status='正常';                 }else if(value=='UnAudit'){                     status='待审核';                 }else if(value=='Refuse'){                     status='驳回';                 }else if(value=='AutoAudit'){                     status='自审核';                 }                 return status;             },             width:60         },{             field: 'updateTime',             title: '更新时间',             formatter:function(value,row,index){                 return $.timestamp2string(value,'yyyy-MM-dd hh:mm:ss');             },             width:170         },{             field: 'id',              title:'操作',             formatter:function(value,row,index){                 var p='<a class="btn btn-xs btn-default" title="预览博客" href="manager/blog/view/'+value+'.html" target="_blank">预</a>&nbsp;';                 var a='<button class="btn btn-xs btn-success" title="通过审核" onclick="audit('+value+')">审</button>&nbsp;';                 var auto='<button class="btn btn-xs btn-success" title="定时自动审核" onclick="auditAuto('+value+')">定</button>&nbsp;';                 var r='<button class="btn btn-xs btn-default" title="拒绝审核" onclick="auditCanncel('+value+')">驳</button>&nbsp;';                 return p+a+auto+r;             },             width:130         }]     }) //查询事件绑定     $('#search_btn').click(function(){         $("#table").bootstrapTable('refresh');     });     $('#search_status').change(function(){         $("#table").bootstrapTable('refresh');     });     $('#search_copyright').change(function(){         $("#table").bootstrapTable('refresh');     });     $('#search_order').change(function(){         $("#table").bootstrapTable('refresh');     });</code></pre> <br /> <br />  
  • Spring Boot Security 数据库方式入门案例

    spring boot 整合spring security采用mongodb数据库方式<p>案例中有使用mongodb数据库,具体的spring boot整合mongodb的案例参考地址<a href="http://www.leftso.com/blog/135.html" rel="nofollow" target="_blank">http://www.leftso.com/blog/135.html</a><br /> <br /> <a href="http://projects.spring.io/spring-security/" rel="nofollow" target="_blank">Spring Security</a>有一些使用复杂的意见。那当然,当你看的时候,它的范围很复杂,因为它的范围涵盖了大量的用例。事实上,真正的spring的精神,你不必一次使用你所拥有的用例的一切功能。事实上,当你开始使用<a href="http://projects.spring.io/spring-boot/" rel="nofollow" target="_blank">Spring Boot</a>进行樱桃挑选并将其恢复时,它似乎并不复杂。</p> <p>我们从使用情况开始,我在想一些比较常见的东西,几乎每个项目都出现一些基本的访问限制。所以,这样一个应用程序的要求可以是:</p> <ul> <li>该应用将有用户,每个用户角色为管理员或用户</li> <li>他们通过电子邮件和密码登录</li> <li>非管理员用户可以查看他们的信息,但不能窥视其他用户</li> <li>管理员用户可以列出并查看所有用户,并创建新的用户</li> <li>定制表单登录</li> <li>“记住我”验证懒惰</li> <li>注销的可能性</li> <li>主页将提供给所有人,不经过验证</li> </ul> <p>这样的<a href="https://github.com/bkielczewski/example-spring-boot-security" rel="nofollow" target="_blank">应用程序</a>的<a href="https://github.com/bkielczewski/example-spring-boot-security" rel="nofollow" target="_blank">源代码在GitHub上</a>,供您查看。建议您将源代码打开,以下将是某些关键点的评论。啊,和安全相关的东西接近尾声,所以只要知道基础知识就可以向下滚动。</p> <h4>依赖关系</h4> <p>此外,标准的Spring Boot依赖关系,最重要的依赖关系是Spring Security,Spring Data JPA的启动器模块,因为我们需要某处存储用户,而嵌入式内存中的HSQLDB作为存储引擎。</p> <pre> <code class="language-xml"><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> </dependency> </code></pre> <p>在现实生活中,您可能会更有兴趣连接到某种外部数据库引擎,例如<a href="http://kielczewski.eu/2014/05/database-connection-pooling-with-bonecp-in-spring-boot-application/" rel="nofollow" target="_blank">本文中关于使用BoneCP的数据库连接池的描述</a>。我也使用<a href="http://freemarker.org/" rel="nofollow" target="_blank">Freemarker</a>作为模板引擎,但是如果这不是你的事情,那么对于JSP来说,重写它也应该很简单,就像在<a href="http://kielczewski.eu/2014/04/spring-boot-mvc-application/" rel="nofollow" target="_blank">本文中关于Spring MVC应用程序一样</a>。</p> <h4>域模型</h4> <p>所以我们会有这样的<code>User</code>实体:</p> <pre> <code class="language-java">@Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false, updatable = false) private Long id; @Column(name = "email", nullable = false, unique = true) private String email; @Column(name = "password_hash", nullable = false) private String passwordHash; @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) private Role role; // getters, setters } </code></pre> <p>如您所见,只有密码的哈希将存储在数据库中,这通常是一个好主意。该<code>email</code>领域还有一个独特的约束,但它不是主要的关键。由于<code>id</code>某个原因,电子邮件地址是您不想在访问日志中显示的相当敏感的信息,因此用户被标识,所以我们将尽可能地使用id。</p> <p><code>Role</code> 是一个简单的枚举:</p> <pre> <code class="language-java">public enum Role { USER, ADMIN } </code></pre> <p>除此之外,创建新用户的表单将是很好的:</p> <pre> <code class="language-java">public class UserCreateForm { @NotEmpty private String email = ""; @NotEmpty private String password = ""; @NotEmpty private String passwordRepeated = ""; @NotNull private Role role = Role.USER; } </code></pre> <p>这将用作Web层和服务层之间的数据传输对象(DTO)。它由Hibernate Validator验证约束注释,并设置一些合理的默认值。请注意,它与<code>User</code>对象略有不同,因此我希望将<code>User</code>实体“泄漏” 到Web层中,我真的不能。</p> <p>这就是我们现在所需要的。</p> <h4>服务层</h4> <p>在服务层,业务逻辑应该是什么,我们需要一些东西来检索<code>User</code>他的id,电子邮件,列出所有的用户并创建一个新的用户。</p> <p>所以接口将是:</p> <pre> <code class="language-java">public interface UserService { Optional<User> getUserById(long id); Optional<User> getUserByEmail(String email); Collection<User> getAllUsers(); User create(UserCreateForm form); } </code></pre> <p>服务的实现:</p> <pre> <code class="language-java">@Service public class UserServiceImpl implements UserService { private final UserRepository userRepository; @Autowired public UserServiceImpl(UserRepository userRepository) { this.userRepository = userRepository; } @Override public Optional<User> getUserById(long id) { return Optional.ofNullable(userRepository.findOne(id)); } @Override public Optional<User> getUserByEmail(String email) { return userRepository.findOneByEmail(email); } @Override public Collection<User> getAllUsers() { return userRepository.findAll(new Sort("email")); } @Override public User create(UserCreateForm form) { User user = new User(); user.setEmail(form.getEmail()); user.setPasswordHash(new BCryptPasswordEncoder().encode(form.getPassword())); user.setRole(form.getRole()); return userRepository.save(user); } } </code></pre> <p>这里不值得一个评论 - 服务代理到<code>UserRepository</code>大部分时间。值得注意的是,在该<code>create()</code>方法中,该表单用于构建一个新<code>User</code>对象。哈希是从使用的密码<code>BCryptPasswordEncoder</code>生成的,这应该比臭名昭着的MD5产生更好的哈希。</p> <p>的<code>UserRepository</code>定义如下:</p> <pre> <code class="language-java">public interface UserRepository extends JpaRepository<User, Long> { Optional<User> findOneByEmail(String email); } </code></pre> <p>这里只添加一个非默认方法<code>findOneByEmail</code>。请注意,我希望它返回<code>User</code>包装在JDK8中<code>Optional</code>,这是Spring的一个新功能,并且使处理<code>null</code>值更容易。</p> <h4>Web层</h4> <p>这是控制者及其观点。为了满足应用的要求,我们至少需要一对夫妇。</p> <h4>主页</h4> <p>我们将处理<code>/</code>网站的根<code>HomeController</code>,其中只返回一个<code>home</code>视图:</p> <pre> <code class="language-java">@Controller public class HomeController { @RequestMapping("/") public String getHomePage() { return "home"; } } </code></pre> <h4>用户列表</h4> <p>同样,用户的列表将被映射到<code>/users</code>并由其处理<code>UsersController</code>。它<code>UserService</code>注入,要求它返回<code>Collection</code>的<code>User</code>对象,将它们放入了<code>users</code>模型属性,然后调用<code>users</code>视图名称:</p> <pre> <code class="language-java">@Controller public class UsersController { private final UserService userService; @Autowired public UsersController(UserService userService) { this.userService = userService; } @RequestMapping("/users") public ModelAndView getUsersPage() { return new ModelAndView("users", "users", userService.getAllUsers()); } } </code></pre> <h4>查看和创建用户</h4> <p>接下来,我们需要一个控制器来处理查看和创建一个新的用户,我称之为它<code>UserController</code>,它更复杂:</p> <pre> <code class="language-java">@Controller public class UserController { private final UserService userService; private final UserCreateFormValidator userCreateFormValidator; @Autowired public UserController(UserService userService, UserCreateFormValidator userCreateFormValidator) { this.userService = userService; this.userCreateFormValidator = userCreateFormValidator; } @InitBinder("form") public void initBinder(WebDataBinder binder) { binder.addValidators(userCreateFormValidator); } @RequestMapping("/user/{id}") public ModelAndView getUserPage(@PathVariable Long id) { return new ModelAndView("user", "user", userService.getUserById(id) .orElseThrow(() -> new NoSuchElementException(String.format("User=%s not found", id)))); } @RequestMapping(value = "/user/create", method = RequestMethod.GET) public ModelAndView getUserCreatePage() { return new ModelAndView("user_create", "form", new UserCreateForm()); } @RequestMapping(value = "/user/create", method = RequestMethod.POST) public String handleUserCreateForm(@Valid @ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return "user_create"; } try { userService.create(form); } catch (DataIntegrityViolationException e) { bindingResult.reject("email.exists", "Email already exists"); return "user_create"; } return "redirect:/users"; } } </code></pre> <p>视图被映射到<code>/user/{id}</code>URL,由<code>getUserPage()</code>方法处理。它要求<code>UserService</code>一个用户<code>id</code>,它是从URL中提取的,并作为参数传递给此方法。你记得,<code>UserService.getUserById()</code>返回一个<code>User</code>包装的实例<code>Optional</code>。因此,<code>.orElseThrow()</code>被要求<code>Optional</code>获得一个<code>User</code> 实例,或者在它的时候抛出异常<code>null</code>。</p> <p>创建一个新<code>User</code>的映射到<code>/user/create</code>并由两种方法处理:<code>getUserCreatePage()</code>和<code>handleUserCreateForm()</code>。第一个只返回一个<code>user_create</code>具有空格式的视图作为<code>form</code>模型的属性。另一个响应<code>POST</code>请求,并<code>UserCreateForm</code>作为参数进行验证。如果表单中有错误,则由<code>BindingResult</code>该视图返回。如果表单确定,则将其传递给<code>UserService.create()</code>方法。</p> <p>还有一个额外的检查<code>DataIntegrityViolationException</code>。如果发生这种情况,则认为是因为尝试<code>User</code>使用数据库中已经存在的电子邮件地址来创建,因此再次呈现该表单。</p> <p>在现实生活中,知道哪个约束被严重违反是非常困难的(或者与ORM无关),所以当做这些假设时,至少应该记录异常情况以便进一步检查。应该注意防止这种异常发生在第一位,如对于重复的电子邮件的形式的正确验证。</p> <p>否则,如果一切正常,则重定向到<code>/users</code>URL。</p> <h3>定制验证 <code>UserCreateForm</code></h3> <p>该<code>@InitBinder</code>在注解的方法<code>UserController</code>添加<code>UserCreateFormValidator</code>到<code>form</code>参数,告诉它应该由它来验证。这样做的原因在于,<code>UserCreateForm</code>需要对两种可能的情况进行整体验证:</p> <ul> <li>检查密码和重复密码是否匹配</li> <li>检查电子邮件是否存在于数据库中</li> </ul> <p>这样做,<code>UserCreateFormValidator</code>实现如下:</p> <pre> <code class="language-java">@Component public class UserCreateFormValidator implements Validator { private final UserService userService; @Autowired public UserCreateFormValidator(UserService userService) { this.userService = userService; } @Override public boolean supports(Class<?> clazz) { return clazz.equals(UserCreateForm.class); } @Override public void validate(Object target, Errors errors) { UserCreateForm form = (UserCreateForm) target; validatePasswords(errors, form); validateEmail(errors, form); } private void validatePasswords(Errors errors, UserCreateForm form) { if (!form.getPassword().equals(form.getPasswordRepeated())) { errors.reject("password.no_match", "Passwords do not match"); } } private void validateEmail(Errors errors, UserCreateForm form) { if (userService.getUserByEmail(form.getEmail()).isPresent()) { errors.reject("email.exists", "User with this email already exists"); } } } </code></pre> <h4>在登录</h4> <p>它将映射到<code>/login</code>URL并处理<code>LoginController</code>:</p> <pre> <code class="language-java">@Controller public class LoginController { @RequestMapping(value = "/login", method = RequestMethod.GET) public ModelAndView getLoginPage(@RequestParam Optional<String> error) { return new ModelAndView("login", "error", error); } } </code></pre> <p>请注意,它只处理<code>GET</code>请求方法,通过<code>error</code>在模型中返回具有可选参数的视图。<code>POST</code>表单的部分和实际处理将由Spring Security完成。</p> <p>表单的模板如下所示:</p> <pre> <code class="language-html"><form role="form" action="/login" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <div> <label for="email">Email address</label> <input type="email" name="email" id="email" required autofocus> </div> <div> <label for="password">Password</label> <input type="password" name="password" id="password" required> </div> <div> <label for="remember-me">Remember me</label> <input type="checkbox" name="remember-me" id="remember-me"> </div> <button type="submit">Sign in</button> </form> <#if error.isPresent()> <p>The email or password you have entered is invalid, try again.</p> </#if> </code></pre> <p>它具有普通<code>email</code>,<code>password</code>输入字段,一个<code>remember-me</code>复选框和一个提交按钮。如果出现错误,将显示说明认证失败的消息。</p> <p>这有效地包含了该应用程序功能所需的一切。剩下的是添加一些安全功能。</p> <h4>CSRF保护</h4> <p>关于<code>_csrf</code>上面窗体视图中出现的内容。它是由应用程序生成的用于验证请求的CSRF令牌。这是为了确保表单数据来自您的应用程序,而不是来自其他地方。这是Spring Security的一个功能,默认情况下由Spring Boot启动。</p> <p>对于JSP和Freemarker,<code>_csrf</code>变量只是暴露在视图中。它包含一个<code>CsrfToken</code>对象,该对象具有一个<code>getParameterName()</code>方法来获取一个CSRF参数名称(默认情况下是这个名称<code>_csrf</code>)和一个<code>getToken()</code>获取实际令牌的方法。然后,您可以将其放在一个隐藏的领域以及其余的表单中:</p> <pre> <code class="language-html"><input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </code></pre> <h4>认证</h4> <p>现在我们有一个应用程序准备好了,是时候设置身份验证。当我们使用我们的登录表单识别那个人,作为现有用户,并且获得足够的信息以授权他们的进一步请求,认证意味着这一部分。</p> <h4>组态</h4> <p>需要添加Spring Security的此配置:</p> <pre> <code class="language-java">@Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .formLogin() .loginPage("/login") .failureUrl("/login?error") .usernameParameter("email") .permitAll() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/") .permitAll(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(userDetailsService) .passwordEncoder(new BCryptPasswordEncoder()); } } </code></pre> <p>这肯定需要一个解释。</p> <p>首先要提到的是<code>@Order</code>注释,它基本上保留了Spring Boot设置的所有默认值,只是在这个文件中覆盖它们。</p> <p>该<code>configure(HttpSecurity http)</code>方法是设置实际的基于URL的安全性。我们来看看它在这里做什么</p> <ul> <li>登录表格在下<code>/login</code>,允许所有。在登录失败时,<code>/login?error</code>会发生重定向。我们<code>LoginController</code>映射了这个URL。</li> <li>以登录形式保存用户名的参数称为“电子邮件”,因为这是我们用作用户名。</li> <li>注销URL是<code>/logout</code>允许的。之后,用户将被重定向到<code>/</code>。这里一个重要的意见是,如果CSRF保护开启,请求<code>/logout</code>应该是<code>POST</code>。</li> </ul> <p>该<code>configure(AuthenticationManagerBuilder auth)</code>方法是设置认证机制的地方。它的设置使得认证将被处理<code>UserDetailsService</code>,哪个实现被注入,并且密码预期被加密<code>BCryptPasswordEncoder</code>。</p> <p>值得一提的是还有很多其他的认证方法<code>UserDetailsService</code>。这只是一种使我们能够使用现有的服务层对象来实现的方法,因此它很适合这个应用。</p> <h3>UserDetailsS​​ervice</h3> <p>这<code>UserDetailsService</code>是Spring Security使用的接口,用于了解用户是否使用登录表单,他们的密码应该是什么,以及系统中有哪些权限。它有一个单一的方法:</p> <pre> <code class="language-java">public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; } </code></pre> <p>如果用户名存在,则该方法<code>loadUserByUsername()</code>返回<code>UserDetails</code>实例,如果不存在则返回实例<code>UsernameNotFoundException</code>。</p> <p>我的实现,注入到同一个<code>SecurityConfig</code>如下:</p> <pre> <code class="language-java">@Service public class CurrentUserDetailsService implements UserDetailsService { private final UserService userService; @Autowired public CurrentUserDetailsService(UserService userService) { this.userService = userService; } @Override public CurrentUser loadUserByUsername(String email) throws UsernameNotFoundException { User user = userService.getUserByEmail(email) .orElseThrow(() -> new UsernameNotFoundException(String.format("User with email=%s was not found", email))); return new CurrentUser(user); } } </code></pre> <p>正如你所看到的,只是要求<code>UserService</code>有一个电子邮件的用户。如果不存在,抛出异常。如果是,<code>CurrentUser</code>则返回对象。</p> <p>但是呢<code>CurrentUser</code>?它应该是<code>UserDetails</code>。那么,首先,这<code>UserDetails</code>只是一个接口:</p> <pre> <code class="language-java">public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); } </code></pre> <p>它描述了具有用户名,密码,<code>GrantedAuthority</code>对象列表以及某些不明确标志的用户,这些标志由于各种帐户无效的原因而被描述。</p> <p>所以我们需要返回一个实现。至少有一个是Spring Security提供的<code>org.springframework.security.core.userdetails.User</code>。</p> <p>可以使用它,但棘手的部分是将我们的<code>User</code>域对象与<code>UserDetails</code>授权可能需要相关联。它可以通过多种方式完成:</p> <ul> <li>使<code>User</code>域对象<code>UserDetails</code>直接实现 - 它将允许返回<code>User</code>完全按照收到的方式<code>UserService</code>。缺点就是用与Spring Security相关的代码来“污染”域对象。</li> <li>使用提供的实现<code>org.springframework.security.core.userdetails.User</code>,只需将<code>User</code>实体映射到它。这很好,但是有一些关于用户可用的附加信息,如<code>id</code>直接访问<code>role</code>或其他任何东西是很好的。</li> <li>因此,第三个解决方案是扩展提供的实现,并添加可能需要的任何信息,或者只是一个完整的<code>User</code>对象。</li> </ul> <p>最后一个选择是我在这里使用的,所以<code>CurrentUser</code>:</p> <pre> <code class="language-java">public class CurrentUser extends org.springframework.security.core.userdetails.User { private User user; public CurrentUser(User user) { super(user.getEmail(), user.getPasswordHash(), AuthorityUtils.createAuthorityList(user.getRole().toString())); this.user = user; } public User getUser() { return user; } public Long getId() { return user.getId(); } public Role getRole() { return user.getRole(); } } </code></pre> <p>...扩展<code>org.springframework.security.core.userdetails.User</code>,实现<code>UserDetails</code>。此外,它只是包装我们的<code>User</code>域对象,加入(可选)方便的方法是代理给它(<code>getRole()</code>,<code>getId()</code>,等)。</p> <p>这个映射很简单,就是在构造函数中发生的:</p> <ul> <li>在<code>UserDetails.username</code>从填充<code>User.email</code></li> <li>在<code>UserDetails.password</code>从填充<code>User.passwordHash</code></li> <li>被<code>User.role</code>转换为<code>String</code>,由<code>AuthorityUtils</code>辅助类包装在GrantedAuthority对象中。它将作为列表的唯一元素<code>UserDetails.getAuthorities()</code>被调用时可用。</li> <li>我决定忘记这些标志,因为我没有使用它们,它们都是按照原样返回<code>true</code>的<code>org.springframework.security.core.userdetails.User</code></li> </ul> <p>话虽如此,我必须提到,当你传递这样的包裹实体时,要小心,或者直接实现它们<code>UserDetails</code>。像这样泄漏域对象并不是一件正确的事情。这也可能是有问题的,即如果实体有一些LAZY获取的关联,那么在尝试获取Hibernate会话之外时可能遇到问题。</p> <p>或者,只需从实体复制足够的信息<code>CurrentUser</code>。足够这里足以授权用户,而无需为<code>User</code>实体调用数据库,因为这将增加执行授权的成本。</p> <p>无论如何,这应该使我们的登录表单工作。总而言之,一步一步地发生的是:</p> <ul> <li>用户打开<code>/login</code>URL。</li> <li>的<code>LoginController</code>返回与登录形式的图。</li> <li>用户填写表单,提交。</li> <li>Spring Security调用<code>CurrentUserDetailsService.loadUserByUsername()</code>用户名(在这种情况下为电子邮件)刚刚输入到表单中。</li> <li><code>CurrentUserDetailsService</code>从中获取用户<code>UserDetailsService</code>并返回<code>CurrentUser</code>。</li> <li>Spring Security调用<code>CurrentUser.getPassword()</code>并将密码哈希与表单中提供的散列密码进行比较。</li> <li>如果没关系,用户被重定向到他来的地方<code>/login</code>,如果不是,重定向到<code>/login?error</code>,再次处理<code>LoginController</code>。</li> <li>Spring Security'保持' <code>CurrentUser</code>对象(包裹<code>Authentication</code>)用于授权检查,或者您应该需要它。</li> </ul> <h4>记住我的身份验证</h4> <p>有时候,对于那些不想输入登录表单的懒惰者,即使他们的会话过期,也可以“记住我”身份验证。这样会造成安全隐患,所以只要建议您使用它。</p> <p>它的工作原理如下:</p> <ul> <li>用户登录,表单发布一个<code>remember-me</code>参数(在此应用程序中有一个复选框)</li> <li>Spring Security生成一个令牌,保存它,并将其发送给一个名为cookie的用户<code>remember-me</code>。</li> <li>下一次访问应用程序时,Spring Security会查找该cookie,如果它保存有效和未过期的令牌,它将自动验证该用户。</li> <li>此外,您可以通过“记住我”来确定用户是否被认证。</li> </ul> <p>为了启用它,它只需要一些补充<code>SecurityConfig.configure(HttpSecurity http)</code>,所以它将看起来像这样:</p> <pre> <code class="language-java">@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .formLogin() .loginPage("/login") .failureUrl("/login?error") .usernameParameter("email") .permitAll() .and() .logout() .logoutUrl("/logout") .deleteCookies("remember-me") .logoutSuccessUrl("/") .permitAll() .and() .rememberMe(); } </code></pre> <p>注意<code>rememberMe()</code>到底。它使整个事情。值得注意的是,默认情况下,令牌存储在内存中。这意味着当应用程序重新启动时,令牌将丢失。你可以使它们持久化,即存储在数据库中,但对于这个应用程序来说,这是足够的。</p> <p>另一项新的事情是<code>deleteCookies()</code>对<code>logout()</code>。<code>remember-me</code>一旦用户从应用程序中注销,就会强制删除该cookie。</p> <h4>授权</h4> <p>授权是一个过程,找出那个已经被授权的人,我们知道他的一切,可以访问应用程序提供的指定资源。</p> <p>在这个应用程序中,我们将这些信息包含在一个<code>CurrentUser</code>对象中。从安全的角度来说,重要的是<code>GrantedAuthority</code>他所拥有的。你记得我们直接从<code>User.role</code>字段填充,所以他们将是“USER”或“ADMIN”。</p> <h4>基于URL的授权</h4> <p>现在,根据要求,我们希望所有人都可以访问某些网址,有些则由授权人员管理,还有一些由管理员访问。</p> <p>它需要一些补充<code>SecurityConfig</code>:</p> <pre> <code class="language-java">@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/public/**").permitAll() .antMatchers("/users/**").hasAuthority("ADMIN") .anyRequest().fullyAuthenticated() .and() .formLogin() .loginPage("/login") .failureUrl("/login?error") .usernameParameter("email") .permitAll() .and() .logout() .logoutUrl("/logout") .deleteCookies("remember-me") .logoutSuccessUrl("/") .permitAll() .and() .rememberMe(); } </code></pre> <p>新的东西是电话<code>antMatchers()</code>。这强制执行:</p> <ul> <li>网址匹配<code>/</code>模式(此应用中的主页)和<code>/public/**</code>所有匹配的网址都将被允许。</li> <li>URL匹配<code>/users/**</code>模式(此应用程序中的列表视图)将仅允许具有“ADMIN”权限的用户。</li> <li>任何其他请求都需要经过身份验证的用户(“ADMIN”或“USER”)。</li> </ul> <h4>方法级授权</h4> <p>以上所有步骤都满足了大部分的要求,但是我们只能通过基于URL的授权无法解决。</p> <p>那是:</p> <ul> <li>管理员用户可以列出并查看所有用户,并创建新的用户</li> </ul> <p>这里的问题是,创建新用户的表单被映射到<code>/user/create</code>。我们可以添加这个URL <code>SecurityConfig</code>,但是如果我们做出一个习惯,我们最终会在配置文件中使用微型管理URL。我们也可以将其映射到<code>/users/create</code>这里,这可能在这里是有意义的,但是想到即将<code>/user/{id}/edit</code>来可能出现的那样。这是很好的离开它,因为它只是为了添加方法级别的授权给现有的方法<code>UserController</code>。</p> <p>要使方法级授权工作,<code>@EnableGlobalMethodSecurity(prePostEnabled = true)</code>需要将注释添加到<code>SecurityConfig</code>:</p> <pre> <code class="language-java">@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) class SecurityConfig extends WebSecurityConfigurerAdapter { // contents as before } </code></pre> <p>这使您可以在应用程序的任何层中对公共方法使用两个注释:</p> <ul> <li><code>@PreAuthorize("expression")</code>- 如果<code>expression</code>以<a href="http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html" rel="nofollow" target="_blank">Spring表达式语言(Sprint)</a>编写的,它将评估为“true”,方法将被调用。</li> <li><code>@PostAuthorize("expression")</code> - 首先调用该方法,当检查失败时,403状态返回给用户。</li> </ul> <p>因此,为了解决第一个要求,处理用户创建的方法<code>UserController</code>需要注释<code>@PreAuthorize</code>:</p> <pre> <code class="language-java">@PreAuthorize("hasAuthority('ADMIN')") @RequestMapping(value = "/user/create", method = RequestMethod.GET) public ModelAndView getUserCreatePage() { // contents as before } @PreAuthorize("hasAuthority('ADMIN')") @RequestMapping(value = "/user/create", method = RequestMethod.POST) public String handleUserCreateForm(@Valid @ModelAttribute("form") UserCreateForm form, BindingResult bindingResult) { // contents as before } </code></pre> <p>该<code>hasAuthority()</code>规划环境地政司表达是由Spring Security的提供等等,即:</p> <ul> <li><code>hasAnyAuthority()</code>或者<code>hasAnyRole()</code>(“权限”和“角色”是Spring Security lingo中的同义词!) - 检查当前用户是否具有列表中的GrantedAuthority之一。</li> <li><code>hasAuthority()</code>或<code>hasRole()</code>- 如上所述,仅为一个。</li> <li><code>isAuthenticated()</code>或<code>isAnonymous()</code>- 当前用户是否被认证。</li> <li><code>isRememberMe()</code>或者<code>isFullyAuthenticated()</code>- 当前用户是否通过“记住我”令牌进行身份验证。</li> </ul> <h4>域对象安全</h4> <p>最后的要求是这样的:</p> <ul> <li>非管理员用户可以查看他们的信息,但不能窥视其他用户</li> </ul> <p>换句话说,我们需要一种方法来告诉用户什么时候可以请求<code>/user/{id}</code>。当用户是“ADMIN”(可以通过基于URL的身份验证来解决),而且当当前用户对自己发出请求时也是如此,这是不能解决的。</p> <p>这适用于以下方法<code>UserController</code>:</p> <pre> <code class="language-java"> @RequestMapping("/user/{id}") public ModelAndView getUserPage(@PathVariable Long id) { // contents as before } </code></pre> <p>我们需要检查当前用户是“管理” <strong>或</strong>在<code>id</code>传递给方法是一样的<code>id</code>从<code>User</code>与关联的域对象<code>CurrentUser</code>。由于<code>CurrentUser</code>有<code>User</code>实例包裹,我们有这方面的信息。所以我们可以用至少两种方法解决它:</p> <ul> <li>您可以使用Spel表达式来比较<code>id</code>传递给当前用户的方法<code>id</code>。</li> <li>您可以将此检查委托给服务 - 这是首选的,因为将来可能会改变条件,更好地在一个位置进行更改,而不是在使用注释的任何位置。</li> </ul> <p>要做到这一点,我<code>CurrentUserService</code>用这个界面创建了</p> <pre> <code class="language-java">public interface CurrentUserService { boolean canAccessUser(CurrentUser currentUser, Long userId); } </code></pre> <p>而这个实现:</p> <pre> <code class="language-java">@Service public class CurrentUserServiceImpl implements CurrentUserService { @Override public boolean canAccessUser(CurrentUser currentUser, Long userId) { return currentUser != null && (currentUser.getRole() == Role.ADMIN || currentUser.getId().equals(userId)); } } </code></pre> <p>现在在<code>@PreAuthorize</code>注释中使用它,只需放:</p> <pre> <code class="language-java">@PreAuthorize("@currentUserServiceImpl.canAccessUser(principal, #id)") @RequestMapping("/user/{id}") public ModelAndView getUserPage(@PathVariable Long id) { // contents as before } </code></pre> <p>哪个是用于调用具有principal(<code>CurrentUser</code>)实例的服务的SpEL表达式,并将<code>id</code>参数传递给方法。</p> <p>如果安全模型比较简单,这种方法可以正常工作。如果您发现自己写的东西类似于给予和检查多个访问权限,例如每个用户对多个域对象的查看/编辑/删除,那么最好是进入使用<a href="http://docs.spring.io/spring-security/site/docs/3.0.x/reference/domain-acls.html" rel="nofollow" target="_blank">Spring Security Domain对象ACL的方向</a>。</p> <p>这满足了所有的要求,此时应用程序得到了保障。</p> <h4>访问Spring Bean中的当前用户</h4> <p>无论何时需要从Controller或Service访问当前用户,都可以注入<code>Authentication</code>对象。可以通过调用获取当前的用户实例<code>Authentication.getPrincipal()</code>。</p> <p>这适用于构造函数或属性,如下所示:</p> <pre> <code class="language-java">@Autowired private Authentication authentication; void someMethod() { UserDetails currentUser = (UserDetails) authentication.getPrincipal(); } </code></pre> <p>此外,这适用于由<code>@RequestMapping</code>或者注释的控制器方法中的参数<code>@ModelAttribute</code>:</p> <pre> <code class="language-java">@RequestMapping("/") public String getMainPage(Authentication authentication) { UserDetails currentUser = (UserDetails) authentication.getPrincipal(); // something } </code></pre> <p>在这个应用程序中,可以直接转换<code>CurrentUser</code>,因为它<code>UserDetails</code>是正在使用的实际实现:</p> <pre> <code class="language-java">CurrentUser currentUser = (CurrentUser) authentication.getPrincipal(); </code></pre> <p>这样你也可以访问它所包围的域对象。</p> <h4>访问视图中的当前用户</h4> <p>访问当前身份验证的用户在视图中是有用的,即当为具有一定权限的用户呈现UI的某些元素时。</p> <p>在JSP中,可以使用安全标签库来完成,如下所示:</p> <pre> <code class="language-xml"><%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <sec:authentication property="principal.username" /> </code></pre> <p>这应该打印当前用户的名字。对于Freemarker,也可以使用该taglib,尽管它需要更多的功能。</p> <p>您还可以拧紧taglib,并将<code>Authentication</code>对象作为视图的模型属性传递:</p> <pre> <code class="language-java">@RequestMapping("/") public String getMainPage(@ModelProperty("authentication") Authentication authentication) { return "some_view"; } </code></pre> <p>但是为什么不为所有的观点,使用<code>@ControllerAdvice</code>,只是<code>UserDetails</code>从<code>Authentication</code>:</p> <pre> <code class="language-java">@ControllerAdvice public class CurrentUserControllerAdvice { @ModelAttribute("currentUser") public UserDetails getCurrentUser(Authentication authentication) { return (authentication == null) ? null : (UserDetails) authentication.getPrincipal(); } } </code></pre> <p>之后,您可以通过<code>currentUser</code>所有视图中的属性访问它。</p>