HTML – Native Form 原生表单功能集
前言
以前写过 form 表单, 但很不齐全, 这篇想做一个大整理. 主要讲讲在网站中使用原生 Form 的功能, 不足和扩展.
前端是原生的 HTML/JS, 后端是 ASP.NET Core Razor Pages.
Simplest Form Overview
form 的职责是让 user 可以把信息传递到服务端. 常见的使用场景是 contact / enquiry form.
结构大概长这样
<form method="post">
<input type="text" name="username">
<button type="submit">Submit</button>
</form>
一个 form tag 把所有信息包裹在里面
input text 作为 accessor 信息读写器. 还有很多种 accessor 用来输出输入不同种类的信息. 后面会详细讲到.
一个 submit button.
ASP.NET Core
public class FormData
{
public string Username { get; set; } = "";
}
public class IndexModel : PageModel
{
public void OnGet()
{ } public void OnPost([FromForm] FormData formData)
{
var value = formData.Username;
}
}
一个对象从 form 获取信息, 然后就可以做各做操作了, 比如存入数据库, 发电邮等等.
Form Attribute
form 有 3 个常用的 attribute
method
有 3 种 get, post, dialog
get 我没有用过, 也不知道什么时候会用到.
post 是每次用的
dialog 很新, Safari 15.4 (14-03-2022) 才支持, 我没有用过这篇就不介绍了 (看这篇)
action
action 用来声明 post 去服务端的地址. 没有填写的话就是和当前页面相同地址.
比如 current url : /contact, 那么就是 post to /contact
Razor Pages 注意事项
在 Razor Pages, 如果 post to other page 需要加多一个 attribute asp-antiforgery, 不然会报错 400 error 哦, 详情可以看这篇 ASP.NET Core – CSRF
<form method="post" action="/other-page" asp-antiforgery="true">
在 Razor Pages, 如果 post to same page 但不同 method 的话, 要添加 query params handler
<form method="post" action="?Handler=ExternalLogin" asp-antiforgery="true">
注: 直接添加 handler 会把其它 query params 给覆盖掉哦. 更好的方式是用 QueryHelpers 和 QueryBuilder 做一个完整的
enctype
声明 form 的格式, 类似 Content-Type
application/x-www-form-urlencoded (默认): 信息会以 key-value encodeURIComponent 方式发送出去, 这个格式不支持文件上传哦.
multipart/form-data: 想上传文件就要用这个 (即使没有文件也是可以用的, 只是会有一些多余的信息, 比如分隔符号, 那个是为了 upload file 才需要的)
例子
<form action="/" method="post" enctype="multipart/form-data" asp-antiforgery="true">
<input type="text" name="username">
<input type="file" name="attachment">
<input type="submit" value="Submit">
</form>
ASP.Net Core
public class FormData
{
public string Username { get; set; } = "";
public IFormFile Attachment { get; set; } = null!;
}
public class IndexModel : PageModel
{
public void OnGet()
{ } public void OnPost([FromForm] FormData formData)
{
var value = formData.Username;
var fileSize = formData.Attachment.Length;
}
}
Form Submission
Submit Button
<button>Submit</button>
<button type="submit">Submit</button>
<input type="submit" value="Submit">
这 3 个效果是一样的.
button 默认的 type 就是 submit, 所以 1 和 2 是一样的.
input:submit 和 button 也是一样的, style 都一样.
通常我会用第 2 个. 比较明确.
multiple button in form
复杂的 form 里面会有多个 button 出现. 但通常只会有一个用来 submit.
所以其余的记得要写上 type="button".
input enter trigger submit button click
在任何一个 input (accessor) 按 enter 键, 游览器会找到 form 里面第一个 submit button (type=submit / image / no defined) 点击.
multiple submit button
一个常见的例子是让用户做简单的选择提交. 比如 external login.
<form method="post">
<button type="submit" name="provider" value="Google">Login with Google</button>
<button type="submit" name="provider" value="Microsoft">Login with Microsoft Account</button>
</form>
在 button 声明 name 和 value, 用户点击后, 被点击的那一个 submit value 会被放入信息中, 一起发送出去
还有一个用法是这样
<form method="post">
<input type="text" name="dataName">
<button type="submit" name="deleteType" value="SoftDelete">Soft Delete Data</button>
<button type="submit" name="deleteType" value="HardDelete">Hard Delete Data</button>
</form>
这个比较少见. 而且交互有点复杂. 不鼓励使用.
ASP.NET Core
public enum DeleteType
{
SoftDelete,
HardDelete
}
public class FormData
{
public string? DataName { get; set; }
public DeleteType? DeleteType { get; set; }
}
public class IndexModel : PageModel
{
public void OnGet()
{ } public void OnPost([FromForm] FormData formData)
{
}
}
listen to submission event
通过 JS 可以拦截 submit event
document.querySelector("form").addEventListener("submit", (e) => {
e.preventDefault();
alert("submit");
});
通过 preventDefault 可以阻止游览器发送到服务端. 通常目的是想改成使用 Ajax 发送, 下面会说到细节.
JS trigger submission
document.querySelector("form").submit();
document.querySelector("form").dispatchEvent(new Event("submit"));
.submit() 不会触发 submission event, 它会直接发送到服务端.
相反, dispatch 只会触发 submit event, 而不会发送到服务端 (不管有没有 prevent default).
所以 2 个的用法是不同的哦, 要依据场景来运用. 一般上 Ajax Form 会用 dispatchEvent, 刷新 form 会用 .submit().
Ajax Form
所谓 Ajax Form 就是替代原本的 form submit 刷新体验, 改成通过 Ajax 发送 form 的信息.
XMLHttpRequest
这是平常用来发 Ajax 的方式, Content-Type: application/json
var request = new XMLHttpRequest();
request.onreadystatechange = () => {
if (request.readyState == 4 && request.status == 200) {
alert("done");
}
};
request.open("POST", "/api/create-enquiry");
request.setRequestHeader("Content-Type", "application/json");
const dataJson = JSON.stringify({ username: "Derrick" });
request.send(dataJson);
ASP.NET Core
public class CreateEnquiryData
{
public string Username { get; set; } = "";
}
public class EnquiryController
{
[HttpPost("api/create-enquiry")]
public void CreateEnquiry([FromBody] CreateEnquiryData createEnquiryData)
{ }
}
form 的话, 它不是 JSON
request.open("POST", "/api/create-enquiry");
const searchParams = new URLSearchParams({ username: "Derrick" });
request.send(searchParams);
直接发送 URLSearchParams 相等于是 application/x-www-form-urlencoded.
multipart/form-data 则是发送 FormData
request.open("POST", "/api/create-enquiry");
const formData = new FormData();
formData.append("username", "Derrick");
request.send(formData);
注:
1.在发送 URLSearchParams 和 FormData 时, 不要去设置 Content-Type Header, 游览器会依据类型自动设定好. 诺我们自己去设置反而会破坏掉游览器的机制, 比如 FormData 会自动创建分隔符, 如果手动设置 Content-Type 则不会.
2.JSON 则需要手动设置 Content-Type, 不然会是 text/plain.
3. 小总结
<form> 必须去手动设置 application/x-www-form-urlencoded 或者 multipart/form-data
JSON 必须手动设置 application/json
request.send(formData or searchParams) 不要手动设置, 让游览器自己判断.
FormData From Form Tag
const formData = new FormData(document.querySelector("form"));
直接把 element 丢进去就可以了. (注: disabled 的 input 会自动过滤掉, 不会放入 FormData 的 key value 里)
要添加额外的信息也可以
formData.append('extraInfo', 'value');
有一种 upload 体验是这样的
它的原理是 HTML file 不要放 name, 这样它就排除在 submit 信息里
然后用 JS 去拦截它, 存取来
const files = [];
document.querySelector('input[type="file"]').addEventListener("input", (e) => {
files.push(e.target.files[0]); // save the file
});
最后, 通过 extra info 的方式 append 进去 formData
const formData = new FormData(document.querySelector("form"));
for (const file of files) {
formData.append("Attachments", file);
}
request.send(formData);
ASP.NET Core


public class CreateEnquiryData
{
public string Username { get; set; } = "";
public List<IFormFile> Attachments { get; set; } = new List<IFormFile>();
}
public class EnquiryController
{
[HttpPost("api/create-enquiry")]
public void CreateEnquiry([FromForm] CreateEnquiryData createEnquiryData)
{ }
}
FormData value
FormData 的 value 只能是 string | File
通常服务端会 convert from string, 比如 parse string number, parse string date
另外, ASP.NET Core parse boolean 是 "True", "False", 而不是 "0", "1" 哦.
Accessor
原生 form accessor 虽然挺多的, 但是一般上网站的 form 不会太复杂 (不像 control panel), 所以常用到的就那几个.
input: text, email, number, date, checkbox, file
textarea, select
radio group
checkbox group
input text
<input type="text" name="username" autofocus autocomplete="on" readonly disabled placeholder="e.g. example.com">
排除 validation attribute (下面会详细讲 validation part), 常用的 attribute 有
autofocus 进入页面后自动 focus 到当前的 input
autocomplete 游览器会缓存之前填写过的记入, name, phone, email, address 都会, 如果不希望这样就通过 off 把它关掉, 也可以直接在 <form autocomplete="off" > 把所有的关掉.
readonly & disabled
这 2 个共同点是让用户只能看无法修改, 比较大的区别是 readonly 的信息会提交到服务端, 而 disabled 则不会. (注: select 是没有 readonly 的哦).
另外 CSS display none 只能让元素看不到, 依然会提交到服务端哦, 只有 disabled 或者把 name 去掉才能阻止提交.
placeholder 用来写提示的
maxlength 通过 UI 限制 value length, 注: 它不是 validation 哦, 如果通过 JS input.value = 'value' 是可以超过 max length 的, 而且 submit 也不会有 validation error.
size 用来设置 input 的 width, size="4" 不等于 style: 4ch 哦, 游览器有自己的算法, 大约是 8ch.
input text + datalist
类似 autocomplete 的效果, 但是资料由自己设定而不是游览器缓存.
<input list="browsers">
<datalist id="browsers">
<option value="Internet Explorer">
<option value="Firefox">
<option value="Chrome">
<option value="Opera">
<option value="Safari">
</datalist>
input email
email 的特色就是自动加了一个 email 的 validation, 其余的和 text 一样.
input number
number 限制了 value 只能是数字, a-z, 符号都不能输入, JS input.value 也输入不了. 但它接收 e, 因为这个是合格的数字, 但大部分情况业务是不允许 e 的.
右边多了一个上下箭头, 可以 increase 和 decrease value, 很方便
step 设置上下箭头按一下 +- 多少. 比如 step="30" 那么按 2 下加 value 就是 60.
input date
min, max 限制 UI 不能选大过或效果 min, max, 同时 validation 不允许大过或小过 min, max.
注: 限制往往有 2 种方式, 一种是 block from UI, 操作上无法输入不允许的值, 另一种是 validation, 可以输入, 但是提交的时候会 error.
注: date format 是 yyyy-mm-dd 只支持一种 format, 输入其它的会被无视. 参考这篇
input file
multiple 允许一次 select 多个文件
accept 支持的类型, 常用的有:
<input type="file" name="attachment" accept="image/png, image/jpeg, image/*, .sql">
用逗号做分隔符 (可以放空格, 比较好看, 它会清掉的), 也可以写 extension 哦
textarea
<textarea name="text" cols="20" rows="2"></textarea>
rows, cols 类似 input 的 size, 用来控制 width, height
textarea 默认是可以 resize 的, 想关掉可以通过 CSS style resize:none; 或者 vertical / horizontal 表示只允许某一边 resize.
select
<select name="cars">
<option value="">--Select--</option>
<optgroup label="Swedish Cars">
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
</optgroup>
<optgroup label="German Cars">
<option value="mercedes">Mercedes</option>
<option value="audi">Audi</option>
</optgroup>
</select>
select 有几个局限, 所以不是很好用, 如果内容不多建议用 radio group 替代.
1. --select-- 由于它不能 cancel 所以只能通过一个 select empty 让用户清空.
2. value must be string, 不接受 null, number
3. multiple 在 PC 很丑, 手机还可以
4. search 体验差
checkbox
<input type="checkbox" name="rememberMe" value="True" checked>
for ASP.NET Core value 放 "True" 去到 C# 会 binding 成 bool. 当 checked 时, 它才会有 key 传到服务器. 所以服务端不可以 set default true 哦.
radio group
<input type="radio" id="html" name="fav_language" value="HTML">
<label for="html">HTML</label><br>
<input type="radio" id="css" name="fav_language" value="CSS">
<label for="css">CSS</label><br>
<input type="radio" id="javascript" name="fav_language" value="JavaScript">
<label for="javascript">JavaScript</label>
same name 表示 same group, 最终被选中的 radio 会成为唯一的 fav_language 值
checkbox group
<input type="checkbox" name="phones" value="iPhone">
<input type="checkbox" name="phones" value="HuaWei">
当多选时, 最终的会有多个 phones key-vlaue, 游览器其实并没有正真的 checkbox group, 它只是允许放重复名字的 checkbox 而已.
ASP.NET Core 会把这些放入 List 中
public class FormData
{
public List<string> Phones { get; set; } = new List<string>();
}
Semantic HTML
参考: MDN – How to structure a web form
所有内容包裹在 form 里, form 不要嵌套 (以前好像 ok, 现在不鼓励了)
<form></form>
分组使用 section
<form>
<section>
<h2>Contact Information</h2>
<!-- accessor here -->
</section>
<section>
<h2>Contact Information</h2>
<!-- accessor here -->
</section>
<p><button type="submit">Submit</button></p>
</form>
submit button 用 p 抱着. 如果没有分组则可以不需要 section (不过我觉得 p > button 感觉怪怪的)
accessor 和 label 用 p 包裹 (也有人用 div 或者 ul > li 来包, Exabyte 是用 p 哦)
参考: stackoverflow1 和 stackoverflow2
<p>
<label for="number">
<span>Card number:</span>
<strong><abbr title="required">*</abbr></strong>
</label>
<input type="tel" id="number" name="cardnumber">
</p>
input, select 做法一样.
radio, checkbox list 用 fieldset > ul > li
<fieldset>
<legend>Title</legend>
<ul>
<li>
<label for="title_1">
<input type="radio" id="title_1" name="title" value="K">
King
</label>
</li>
<li>
<label for="title_2">
<input type="radio" id="title_2" name="title" value="Q">
Queen
</label>
</li>
<li>
<label for="title_3">
<input type="radio" id="title_3" name="title" value="J">
Joker
</label>
</li>
</ul>
</fieldset>
fieldset 长这样, 一个大筐筐加一个 legend title
Validation
参考: W3Schools – JavaScript Validation API
Overview
validation 的玩法大概是这样:
1. 声明条件, 比如在 input 写上 required, pattern="\d" type="number | email" 这些都是条件.
2. 游览器会依据条件来限制用户的输入, 比如 type="number" keydown 不接受 a-z (除了 e) 和符号.
3. 除了阻止用户输入, 另一种方式是 popup error message 告诉用户虽然你成功输入了值, 但值我不接受请你修改, 不然就无法提交.
虽然游览器 build-in 了许多条件, 限制,验证 error, 但依然满足不了所有的需求, 所以我们可以通过 JS 去完善它.
manual trigger validation
虽然可以 manual trigger, 但有些时候会失灵, 比如 minlength, maxlength 只能通过 UI 才会有 error (挺奇葩的)
const valid = input.checkValidity(); // boolean
get error message
console.log(input.validationMessage);
set error or error message
不管是 custom error 还是想修改原本的 error message 都可以用这个接口
input.setCustomValidity('error message');
popup error message
input.reportValidity();
check validation state
console.log('input.validity', input.validity); // validation info
所有条件都在这里了
interface ValidityState {
readonly badInput: boolean;
readonly customError: boolean;
readonly patternMismatch: boolean;
readonly rangeOverflow: boolean;
readonly rangeUnderflow: boolean;
readonly stepMismatch: boolean;
readonly tooLong: boolean;
readonly tooShort: boolean;
readonly typeMismatch: boolean;
readonly valid: boolean;
readonly valueMissing: boolean;
}
badInput input type="number" UI 输入 e 就会是 badInput. 但是 input.value = 'e' 这样是输入不到值的哦, 只有通过 UI 才可以输入 e.
customError 如果有调用 setCustomValidity 就会是 true
patternMismatch 对应 pattern=“\d” input text 的正则验证
rangeOverflow 和 rangeUnderflow 对应 min, max (date or number)
stepMismatch 对应 type="number" step="20" value="18"
tooLong 和 tooShort 对应 maxlength 和 minlength
typeMismatch 对应 type="email"
valueMissing 对应 required。
注: badInput 和 valueMissing 是可能同时发生的,比如 input type number 输入 'e'
由于格式不对,所以 badInput = true,同时格式不对会导致 value = empty,所以 valueMissing 也等于 true。
valid 表示是否全部条件都满足,valid 和 checkValidity 的区别是 checkValidity 会 trigger invalid event,而 valid 只是一个属性。
注: 这些 value 都是 live 的哦, 并不需要调用 checkValidity()。比如我突然 required = true; valueMissing 会立马变成 true,valid 会立马变成 false。
奇葩现象
有时候 validation 会失灵, 唯一确保它可以跑的方式是通过 UI 去操作. JS 输入值会有许多奇葩现象:
JS 无法输入 e
const input = document.createElement('input');
input.type = 'number';
input.value = 'e';
console.log(input.value); // ''
console.log(input.valueAsNumber); // 'NaN'
UI 是可以输入 e 的, 但是会 badInput
stepMismatch 完全正常
const input = document.createElement('input');
input.type = 'number';
input.step = '20';
input.value = '19';
console.log('validity', input.validity.stepMismatch); // true
甚至不需要调用 checkValidity
typeMismatch 完全正常
const input = document.createElement('input');
input.type = 'email';
input.value = 'abc';
console.log('validity', input.validity.typeMismatch); // true
tooShort, tooLong 完成失灵
const input = document.createElement('input');
input.type = 'text';
input.minLength = 3;
input.maxLength = 5;
input.value = 'a';
console.log('validity', input.validity.tooShort); // false
input.value = '123456';
console.log('validity', input.validity.tooLong); // false
input.checkValidity();
console.log(input.value); // 123456
console.log('validity', input.validity.tooLong); // false
如果通过 UI 操作的话是可以弄出 error 的.
替换 error message
原生的 error message 有 3 个大问题,
language problem
它依据游览器的设置来提供语言, 但通常网站的语言是通过 URL 控制的.
consistency problem
不同游览器出的 error message 是不同的. 统一管理会比较方便
checkbox group no build-in
上面有提到, checkbox list 是没有 build-in 的, 它只是没有阻止你 multiple same name 而已
如果想做一个 required 它就不会像 radio group 那样. 所以只能自己处理.
Bootstrap 有 2 种 validation, 一种是原生, 一种是完全用它的 custom
但它的原生也是支持 set error message 的. 由此推断这是很 popular 的需求.
拦截 input event 然后判断当前 validity state 再通过 setCustomValidaity 替换掉 message.
input.setCustomValidity('error message');
注: 如果遇到 conditional validation,那还需要用到 MutationObserver 监听 attribute 变化,比如 required 等等。
ASP.NET Core form string empty become null
踩到一个坑, 前端 submit form string value = '' empty string, 后端接收以后变成了 null
原来是 [FromForm] 搞的鬼, JSON 则不会.
参考:
Asp.Net Core Model binding, how to get empty field to bind as a blank string?
Handling multipart requests with JSON and file uploads in ASP.NET Core
ConvertEmptyStringToNull stopped being honored after upgrading from RC1
2 个 solution, 一个是在 property 上面加 attribute
public class FormData
{
[DisplayFormat(ConvertEmptyStringToNull = false)]
public string Name { get; set; } = "";
}
另一个是在 Program.cs 配置 MvcOptions (参考上面的链接, 这个动作很大, 不推荐)
HTML – Native Form 原生表单功能集的更多相关文章
- React利用Antd的Form组件实现表单功能(转载)
一.构造组件 1.表单一定会包含表单域,表单域可以是输入控件,标准表单域,标签,下拉菜单,文本域等. 这里先引用了封装的表单域 <Form.Item /> 2.使用Form.create处 ...
- Flask入门 表单Flask-wtf form原生 Bootstrap渲染(七)
(1) 原生的表单 模板页面,form表单form.html <form action="{{ url_for('/check/') }}" method='post'> ...
- elementui 使用Form表单 的 resetForm表单功能出现的问题
代码因为在保密机上,这里只进行描述并截取elemen文档中的代码作为参考 今天在开发一个很简单需求的时候遇到的问题,在使用elementui的表单功能,将增和改的表单进行了复用,是在表单的父组件 dr ...
- Django form表单功能的引用(注册,复写form.clean方法 增加 验证密码功能)
1. 在app下 新建 forms.py 定义表单内容,类型models from django import forms class RegisterForm(forms.Form): userna ...
- ExtJs 第二章,Ext.form.Basic表单操作
1.认识Ext.form.Panel表单面板 Ext.form.field.CheckBox 复选框 checkboxfield Ext.form.CheckBoxGroup 复选框组 ...
- React Native 导入原生Xcode项目总结与记录
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...
- layui的表单功能
作为一个phper还是非常喜欢这个插件的~虽然在vue的群里面说这个插件好被人怼过..废话不多说, 这次使用到的是layui的表单功能.上次的日历忘记做笔记了非常可惜,大部分其实跟着文档撸就可以,这次 ...
- 解析:使用easyui的form提交表单,在IE下出现类似附件下载时提示是否保存的现象
之前开发时遇到的一个问题,使用easyui的form提交表单,在Chrome下时没问题的,但是在IE下出现类似附件下载时提示是否保存的现象. 这里记录一下如何解决的.其实这个现象不光是easyui的f ...
- wemall app商城源码Android之Native(原生)支付模式一demo
wemall-mobile是基于WeMall的Android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享Native(原生)支付模式一demo,供技术 ...
- MySQL表空间集
--MySQL表空间集 ----------------------2014-09-20 1. 收缩ibdata的方法,目前MySQL依然没有提供收缩ibdata的方法,只能重构,下面是5.7的步骤. ...
随机推荐
- [oeasy]python0050_动态类型_静态类型_编译_运行
动态类型_静态类型 回忆上次内容 上次了解了 帮助文档的 生成 开头的三引号注释 可以生成 帮助文档 文档 可以写成网页 python3 本身 也有 在线的帮助手册 目前的程序 提高了 可读性 ...
- 通过Jupyter Notebook+OpenAI+ollama简单的调用本地模型
通过Jupyter Notebook+OpenAI+ollama简单的调用本地模型 起因是收到了ollama的邮件,貌似支持使用openai来调用本地的ollama下载的模型为自己用 想了下正好试下, ...
- 实验6-使用TensorFlow完成线性回归 cannot import name ‘OrderedDict‘ from ‘typing‘错误的解决方法
找到对应的报错方法 删除 再添加from typing_extensions import OrderedDict
- M1 Mac安装anaconda3
1.正常安装 首先进入官网https://www.anaconda.com/ 下载,安装 自行大胆的安装 2.环境配置 直接安装完成后,代码文件的存储路径为默认路径,为了更好的管理代码文件我们需要更换 ...
- EdgeOne安全专项实践:上传文件漏洞攻击详解与防范措施
前言 今天,我们将深入探讨上传文件漏洞攻击,这部分内容是EdgeOne专项实践篇的一部分.在本章中,我们不会涉及文件漏洞的含义.原理或站点配置等基础教程,如果你对这些内容感兴趣,可以参考这篇文章:探索 ...
- 【H5】08 图片
摘自: https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding 在这份教程中,到目前为止我们已经看到了 ...
- 【Ubuntu】下载安装 20.04.版本 桌面端
下载 Download 这次的是直接在界面上下载的,我都不知道为什么怎么点到之前的版本去了 12.04.5远古版本界面怪难看的... [下载地址:点我访问] https://cn.ubuntu.com ...
- 【Spring-Security】Re03 认证参数修改与跨域跳转处理
一.请求参数名设置 之前的表单信息有一些要求: 1.action属性发送的地址是Security设置的URL 2.发送的请求方式是POST 3.请求的账户信息,也就是表单发送的参数,必须对应的是use ...
- Java数组小白版
一.数组概念 一.数组定义 数组就是指在计算机内存中开辟的连续存储空间,用于存放程序运行中需要用到的一组相同类型数据的容器. 二.数组的声明 +数组的长度 定义数组时需要确定数组的长度(元素的个数), ...
- springcloud feign集成hystrix
本章介绍feign集成hystrix 1.增加pom依赖` <dependency> <groupid>org.springframework.cloud</groupi ...