首页 文章 精选 留言 我的

精选列表

搜索[云数据库 Redis®],共10034篇文章
优秀的个人博客,低调大师

依赖注入 in 前端 && Typescript 实现依赖注入

背景 最近因为工作需要,研究了vscode的代码,看文档时发现其中提到了Dependency Injection,并且在类的构造函数中看到了这样的写法。 constructor( id: string, @IMessageService messageService: IMessageService, @IStorageService storageService: IStorageService, @ITelemetryService telemetryService: ITelemetryService, @IContextMenuService contextMenuService: IContextMenuService, @IPa

优秀的个人博客,低调大师

Elasticsearch Index API & Aggregations API & Query DSL

这篇小菜给大家演示和讲解一些Elasticsearch的API,如在工作中用到时,方便查阅。 一、Index API 创建索引库 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 curl-XPUT 'http://127.0.0.1:9200/test_index/' -d'{ "settings" :{ "index" :{ "number_of_shards" :3, "number_of_replicas" :1 } }, "mappings" :{ "type_test_01" :{ "properties" :{ "field1" :{ "type" : "string" }, "field2" :{ "type" : "string" } } }, "type_test_02" :{ "properties" :{ "field1" :{ "type" : "string" }, "field2" :{ "type" : "string" } } } } }' 验证索引库是否存在 1 curl–XHEAD-i 'http://127.0.0.1:9200/test_index?pretty' 注: 这里加上的?pretty参数,是为了让输出的格式更好看。 查看索引库的mapping信息 1 curl–XGET-i 'http://127.0.0.1:9200/test_index/_mapping?pretty' 验证当前库type为article是否存在 1 curl-XHEAD-i 'http://127.0.0.1:9200/test_index/article' 查看test_index索引库type为type_test_01的mapping信息 1 curl–XGET-i 'http://127.0.0.1:9200/test_index/_mapping/type_test_01/?pretty' 测试索引分词器 1 2 3 4 5 curl-XGET 'http://127.0.0.1:9200/_analyze?pretty' -d' { "analyzer" : "standard" , "text" : "thisisatest" }' 输出索引库的状态信息 1 curl 'http://127.0.0.1:9200/test_index/_stats?pretty' 输出索引库的分片相关信息 1 curl-XGET 'http://127.0.0.1:9200/test_index/_segments?pretty' 删除索引库 1 curl-XDELETEhttp: //127 .0.0.1:9200 /logstash-nginxacclog-2016 .09.20/ 二、Count API 简易语法 curl -XGET 'http://elasticsearch_server:port/索引库名称/_type(当前索引类型,没有可以不写)/_count 用例: 1、统计 logstash-nginxacclog-2016.10.09 索引库有多少条记录 1 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_count' 2、统计 logstash-nginxacclog-2016.10.09 索引库status为200的有多少条记录 1 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_count?q=status:200' DSL 写法 1 2 3 4 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_count' -d' { "query" : { "term" :{ "status" : "200" }} }' 三、Aggregations API (数据分析和统计) 注: 聚合相关的API只能对数值、日期 类型的字段做计算。 1、求平均数 业务场景: 统计访问日志中的平均响应时长 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "avg_num" :{ "avg" :{ "field" : "responsetime" }} }, "size" :0 #这里的size:0表示不输出匹配到数据,只输出聚合结果。 }' { "took" :598, "timed_out" : false , "_shards" :{ "total" :5, "successful" :5, "failed" :0 }, "hits" :{ "total" :32523067, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "avg_num" :{ "value" :0.0472613558675975 } } } #得到平均响应时长为0.0472613558675975秒 2、求最大值 业务场景:获取访问日志中最长的响应时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "max_num" :{ "max" :{ "field" : "responsetime" }} }, "size" :0 }' { "took" :29813, "timed_out" : false , "_shards" :{ "total" :431, "successful" :431, "failed" :0 }, "hits" :{ "total" :476952009, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "max_num" :{ "value" :65.576 } } } #得到最大响应时长为65.576秒 3、求最小值 业务场景: 获取访问日志中最快的响应时间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "min_num" :{ "min" :{ "field" : "responsetime" }} }, "size" :0 }' { "took" :2145, "timed_out" : false , "_shards" :{ "total" :431, "successful" :431, "failed" :0 }, "hits" :{ "total" :477156773, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "min_num" :{ "value" :0.0 } } } #看来最快的响应时间竟然是0,笔者通过查询日志发现,原来这些响应时间为0的请求是被nginx拒绝掉的。 4、数值求和 业务场景: 统计一天的访问日志中为响应请求总共输出了多少流量。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "sim_num" :{ "sum" :{ "field" : "size" }} }, "size" :0 }' { "took" :1226, "timed_out" : false , "_shards" :{ "total" :5, "successful" :5, "failed" :0 }, "hits" :{ "total" :32523067, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "sim_num" :{ "value" :6.9285945505E10 } } } #这个数有点大,后面的E10表示6.9285945505X10^10,笔者算了下,大概70GB流量。 5、获取常用的数据统计指标 其中包括( 最大值、最小值、平均值、求和、个数 ) 业务场景: 求访问日志中的 responsetime ( 最大值、最小值、平均值、求和、个数 ) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "like_stats" :{ "stats" :{ "field" : "responsetime" }} }, "size" :0 }' { "took" :2868, "timed_out" : false , "_shards" :{ "total" :431, "successful" :431, "failed" :0 }, "hits" :{ "total" :477797577, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "like_stats" :{ "count" :469345191, "min" :0.0, "max" :65.576, "avg" :0.06088492952649428, "sum" :2.8576048877634E7 } } } 这个是上面统计方式的增强版,新增了几个统计数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "like_stats" :{ "extended_stats" :{ "field" : "responsetime" }} }, "size" :0 }' { "took" :2830, "timed_out" : false , "_shards" :{ "total" :431, "successful" :431, "failed" :0 }, "hits" :{ "total" :478145456, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "like_stats" :{ "count" :469687072, "min" :0.0, "max" :65.576, "avg" :0.06087745173159307, "sum" :2.859335205463328E7, "sum_of_squares" :1.3162790273264633E7, "variance" :0.02431853151732958, "std_deviation" :0.1559440012226491, "std_deviation_bounds" :{ "upper" :0.3727654541768913, "lower" :-0.2510105507137051 } } } } #其中新增的三个返回结果分别是: #sum_of_squares平方和 #variance方差 #std_deviation标准差 6、统计数据在某个区间所占的百分比 业务场景: 求出访问日志中响应时间的各个区间,所占的百分比 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "outlier" :{ "percentiles" :{ "field" : "responsetime" }} }, "size" :0 }' { "took" :60737, "timed_out" : false , "_shards" :{ "total" :431, "successful" :431, "failed" :0 }, "hits" :{ "total" :478287997, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "outlier" :{ "values" :{ "1.0" :0.0, "5.0" :0.0, "25.0" :0.02, "50.0" :0.038999979789136247, "75.0" :0.06247223731250421, "95.0" :0.16479760590682113, "99.0" :0.520510492464275 } } } } #values对应的列为所占的百分比,右边则是对应的数据值。表示: #响应时间小于或等于0的请求占1% #响应时间小于或等于0的请求占5% #响应时间小于或等于0.02的请求占25% #响应时间小于或等于0.038999979789136247的请求占50% #响应时间小于或等于0.06247223731250421的请求占75% #响应时间小于或等于0.16479760590682113的请求占95% #响应时间小于或等于0.520510492464275的请求占99% #还可以通过percents参数,自定义一些百分比区间,如10%,30%,60%,90%等。 #注:经笔者测试,这个方法只能对数值类型的字段进行统计,无法操作字符串类型的字段。 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "outlier" :{ "percentiles" :{ "field" : "status" , "percents" :[5,10,20,50,99.9] } } }, "size" :0 }' 7、求指定字段数值在各个区间所占的百分比 业务场景:求响应时间 0, 0.01, 0.1, 0.2 在整个日志文件中,分别所占的百分比。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "outlier" :{ "percentile_ranks" :{ "field" : "responsetime" , "values" :[0,0.01,0.1,0.2] } } }, "size" :0 }' { "took" :6950, "timed_out" : false , "_shards" :{ "total" :5, "successful" :5, "failed" :0 }, "hits" :{ "total" :32523067, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "outlier" :{ "values" :{ "0.0" :8.79897648675993, "0.01" :17.90331319256336, "0.1" :91.18297638776373, "0.2" :98.22564774611764 } } } } #响应时间小于或等于0的请求占8.7% #响应时间小于或等于0.01的请求占17.9% #响应时间小于或等于0.1的请求占91.1% #响应时间小于或等于0.2的请求占98.2% 8、求该数值范围内有多少文档匹配 业务场景: 求访问日志中的响应时间为,0 ~ 0.02、0.02 ~ 0.1 、大于 0.1 这三个数值区间内,各有多少文档匹配。 "ranges":[{"to": 0.02}, {"from":0.02,"to":0.1},{"from":0.1}] {"to": 0.02} 求响应时间 0 ~ 0.02 区间内的匹配文档数 {"from":0.02,"to":0.1} 求响应时间 0.02 ~ 0.1 区间内匹配的文档数 {"from":0.1} 求响应时间大于 0.1 匹配的文档数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "range_info" :{ "range" :{ "field" : "responsetime" , "ranges" :[{ "to" :0.02},{ "from" :0.02, "to" :0.1},{ "from" :0.1}] } } }, "size" :0 }' { "took" :474, "timed_out" : false , "_shards" :{ "total" :5, "successful" :5, "failed" :0 }, "hits" :{ "total" :32523067, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "range_info" :{ "buckets" :[{ "key" : "*-0.02" , "to" :0.02, "to_as_string" : "0.02" , "doc_count" :9093600 },{ "key" : "0.02-0.1" , "from" :0.02, "from_as_string" : "0.02" , "to" :0.1, "to_as_string" : "0.1" , "doc_count" :20547128 },{ "key" : "0.1-*" , "from" :0.1, "from_as_string" : "0.1" , "doc_count" :2879418 }] } } } "aggregations" :{ "range_info" :{ "buckets" :[{ "key" : "*-0.02" , "to" :0.02, "to_as_string" : "0.02" , "doc_count" :9093600 } #响应时间在0~0.02的文档数是9093600 ,{ "key" : "0.02-0.1" , "from" :0.02, "from_as_string" : "0.02" , "to" :0.1, "to_as_string" : "0.1" , "doc_count" :20547128 } #响应时间在0.02~0.1的文档数是20547128 ,{ "key" : "0.1-*" , "from" :0.1, "from_as_string" : "0.1" , "doc_count" :2879418 } #响应时间在大于0.1的文档数是2879418 ] } } 9、求时间范围内有多少文档匹配 业务场景:求访问日志中,在 2016-10-09T01:00:00 之前的文档有多少。 和在 2016-10-09T02:00:00 之后的文档有多少。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "match_all" :{}}, "aggs" :{ "range_info" :{ "date_range" :{ "field" : "@timestamp" , "ranges" :[{ "to" : "2016-10-09T01:00:00" },{ "from" : "2016-10-09T02:00:00" }] } } }, "size" :0 }' { "took" :432, "timed_out" : false , "_shards" :{ "total" :5, "successful" :5, "failed" :0 }, "hits" :{ "total" :32523067, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "range_info" :{ "buckets" :[{ "key" : "*-2016-10-09T01:00:00.000Z" , "to" :1.4759748E12, "to_as_string" : "2016-10-09T01:00:00.000Z" , "doc_count" :613460 }, #在2016-10-09T01:00:00之前的文档数有613460 { "key" : "2016-10-09T02:00:00.000Z-*" , "from" :1.4759784E12, "from_as_string" : "2016-10-09T02:00:00.000Z" , "doc_count" :31264881 } #在2016-10-09T02:00:00之后的文档数有31264881 ] } } } 10、聚合结果不依赖于查询结果集 "global":{} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "term" :{ "status" : "200" }}, "aggs" :{ "all_articles" :{ "global" :{}, "aggs" :{ "sum_like" :{ "sum" :{ "field" : "responsetime" }} } } }, "size" :0 }' { "took" :1519, "timed_out" : false , "_shards" :{ "total" :5, "successful" :5, "failed" :0 }, "hits" :{ "total" :26686196, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "all_articles" :{ "doc_count" :32523067, "sum_like" :{ "value" :1536946.1929722272 } } } } #可以看到查询结果集hitstotal部分才匹配到26686196条记录。而聚合的文档数则是32523067多于查询结果匹配到的文档。 #聚合结果为1536946.1929722272 #我们再看看没有引用"global":{}参数的方式 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "query" : { "term" :{ "status" : "200" }}, "aggs" :{ "sum_like" :{ "sum" :{ "field" : "responsetime" }} }, "size" :0 }' { "took" :1326, "timed_out" : false , "_shards" :{ "total" :5, "successful" :5, "failed" :0 }, "hits" :{ "total" :26686196, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "sum_like" :{ "value" :1526710.3929916811 } } } #聚合结果小于上诉的结果。表示这次的聚合的值,是依赖于检索匹配到的文档。 11、分组聚合 用于统计指定字段在自定义的固定增长区间下,每个增长后的值,所匹配的文档数量。 1 2 3 4 5 6 7 8 9 10 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "aggs" :{ "like_histogram" :{ "histogram" :{ "field" : "status" , "interval" :200, "min_doc_count" :1} } }, "size" :0 }' #对status字段操作,增长区间为200,为了避免有的区间匹配为0所导致空数据,所以这里指定最小文档数为1"histogram":{"field":"status","interval":200,"min_doc_count":1} 12、分组聚合-基于时间做分组 "date_histogram":{"field": "@timestamp", "interval": "1d","format": "yyyy-MM-dd",} "field": "@timestamp" 指定记录时间的字段 "interval": "1d" 分组区间为每天. 1M 每月、1H 每小时、1m 每分钟 "format": "yyyy-MM-dd" 指定时间的输出格式 统计每天产生的日志数量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-*/_search?pretty' -d'{ "aggs" :{ "date_histogram_info" :{ "date_histogram" :{ "field" : "@timestamp" , "interval" : "1d" , "format" : "yyyy-MM-dd" , "min_doc_count" :1} } } }' "aggregations" :{ "date_histogram_info" :{ "buckets" :[{ "key_as_string" : "2016-09-27" , "key" :1474934400000, "doc_count" :6895375 },{ "key_as_string" : "2016-09-28" , "key" :1475020800000, "doc_count" :1255775 },{ "key_as_string" : "2016-09-29" , "key" :1475107200000, "doc_count" :38512862 },{ "key_as_string" : "2016-09-30" , "key" :1475193600000, "doc_count" :35314225 },{ "key_as_string" : "2016-10-01" , "key" :1475280000000, "doc_count" :45358162 },{ "key_as_string" : "2016-10-02" , "key" :1475366400000, "doc_count" :42058056 },{ "key_as_string" : "2016-10-03" , "key" :1475452800000, "doc_count" :39945587 },{ "key_as_string" : "2016-10-04" , "key" :1475539200000, "doc_count" :39509128 },{ "key_as_string" : "2016-10-05" , "key" :1475625600000, "doc_count" :40506342 },{ "key_as_string" : "2016-10-06" , "key" :1475712000000, "doc_count" :43303499 },{ "key_as_string" : "2016-10-07" , "key" :1475798400000, "doc_count" :44234780 },{ "key_as_string" : "2016-10-08" , "key" :1475884800000, "doc_count" :32880600 },{ "key_as_string" : "2016-10-09" , "key" :1475971200000, "doc_count" :32523067 },{ "key_as_string" : "2016-10-10" , "key" :1476057600000, "doc_count" :31454044 },{ "key_as_string" : "2016-10-11" , "key" :1476144000000, "doc_count" :2018401 }] } } } #基于小时做分组 #统计当天每小时产生的日志数量 curl-XGET 'http://127.0.0.1:9200/logstash-nginxacclog-2016.10.09/_search?pretty' -d'{ "aggs" :{ "date_histogram_info" :{ "date_histogram" :{ "field" : "@timestamp" , "interval" : "1H" , "format" : "yyyy-MM-dd-H" , "min_doc_count" :1} } }, "size" :0 }' { "took" :530, "timed_out" : false , "_shards" :{ "total" :5, "successful" :5, "failed" :0 }, "hits" :{ "total" :32523067, "max_score" :0.0, "hits" :[] }, "aggregations" :{ "date_histogram_info" :{ "buckets" :[{ "key_as_string" : "2016-10-09-0" , "key" :1475971200000, "doc_count" :613460 },{ "key_as_string" : "2016-10-09-1" , "key" :1475974800000, "doc_count" :644726 },{ "key_as_string" : "2016-10-09-2" , "key" :1475978400000, "doc_count" :687196 },{ "key_as_string" : "2016-10-09-3" , "key" :1475982000000, "doc_count" :730831 },{ "key_as_string" : "2016-10-09-4" , "key" :1475985600000, "doc_count" :1460320 },{ "key_as_string" : "2016-10-09-5" , "key" :1475989200000, "doc_count" :1469098 },{ "key_as_string" : "2016-10-09-6" , "key" :1475992800000, "doc_count" :1004399 },{ "key_as_string" : "2016-10-09-7" , "key" :1475996400000, "doc_count" :962843 },{ "key_as_string" : "2016-10-09-8" , "key" :1476000000000, "doc_count" :1232560 },{ "key_as_string" : "2016-10-09-9" , "key" :1476003600000, "doc_count" :1809741 },{ "key_as_string" : "2016-10-09-10" , "key" :1476007200000, "doc_count" :2802804 },{ "key_as_string" : "2016-10-09-11" , "key" :1476010800000, "doc_count" :3941192 },{ "key_as_string" : "2016-10-09-12" , "key" :1476014400000, "doc_count" :4631032 },{ "key_as_string" : "2016-10-09-13" , "key" :1476018000000, "doc_count" :3651968 },{ "key_as_string" : "2016-10-09-14" , "key" :1476021600000, "doc_count" :2079933 },{ "key_as_string" : "2016-10-09-15" , "key" :1476025200000, "doc_count" :973578 },{ "key_as_string" : "2016-10-09-16" , "key" :1476028800000, "doc_count" :517435 },{ "key_as_string" : "2016-10-09-17" , "key" :1476032400000, "doc_count" :388382 },{ "key_as_string" : "2016-10-09-18" , "key" :1476036000000, "doc_count" :361296 },{ "key_as_string" : "2016-10-09-19" , "key" :1476039600000, "doc_count" :345926 },{ "key_as_string" : "2016-10-09-20" , "key" :1476043200000, "doc_count" :342214 },{ "key_as_string" :

优秀的个人博客,低调大师

Android -- 重置Bitmap大小&&Bitmap转角度

重置Bitmap大小 Bitmap bitMap = BitmapFactory.decodeFile(path); int width = bitMap.getWidth(); int height = bitMap.getHeight(); // 设置想要的大小 int newWidth = 500; int newHeight = 400; // 计算缩放比例 float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; // 取得想要缩放的matrix参数 Matrix matrix = new Matrix(); matrix.postScale(scaleWidth, scaleHeight); // 得到新的图片 bitMap = Bitmap.createBitmap(bitMap, 0, 0, width, height, matrix, true); Bitmap转角度 Bitmap bm = BitmapFactory.decodeByteArray(imgdata, 0,imgdata.length); Matrix matrix = new Matrix(); matrix.preRotate(270); bm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(),bm.getHeight(), matrix, true); imgdata为camera开发的时候的图片数据。 这里其实找到了bitmap就OK了。 本文转自我爱物联网博客园博客,原文链接:http://www.cnblogs.com/yydcdut/p/3744875.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

Callable、Future&阻塞队列&阻塞栈

Callable、Future 简单应用 在Java5之前,线程是没有返回值的,常常为了“有”返回值,破费周折,而且代码很不好写。或者干脆绕过这道坎,走别的路了。现在Java终于有可返回值的任务(也可以叫做线程)了。 可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。 Code public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { //创建一个线程池 ExecutorService pool = Executors.newFixedThreadPool(2); //创建两个有返回值的任务 Callable c1 = new MyCallable("A"); Callable c2 = new MyCallable("B"); //执行任务并获取Future对象 Future f1 = pool.submit(c1); Future f2 = pool.submit(c2); //从Future对象上获取任务的返回值,并输出到控制台 System.out.println(">>>"+f1.get().toString()); System.out.println(">>>"+f2.get().toString()); //关闭线程池 pool.shutdown(); } } class MyCallable implements Callable{ private String oid; MyCallable(String oid) { this.oid = oid; } @Override public Object call() throws Exception { return oid+"任务返回的内容"; } } >>>A任务返回的内容 >>>B任务返回的内容 阻塞队列 简单应用 阻塞队列是Java5线程新特征中的内容,Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素的操作会被阻塞等待,直到有空位为止。同样,当队列为空时候,请求队列元素的操作同样会阻塞等待,直到有可用元素为止。 有了这样的功能,就为多线程的排队等候的模型实现开辟了便捷通道,非常有用。 Code public class Test { public static void main(String[] args) throws InterruptedException { BlockingQueue bqueue = new ArrayBlockingQueue(20); for (int i = 0; i < 30; i++) { //将指定元素添加到此队列中,如果没有可用空间,将一直等待(如果有必要)。 bqueue.put(i); System.out.println("向阻塞队列中添加了元素:" + i); } System.out.println("程序到此运行结束,即将退出----"); } } 向阻塞队列中添加了元素:0 向阻塞队列中添加了元素:1 向阻塞队列中添加了元素:2 向阻塞队列中添加了元素:3 向阻塞队列中添加了元素:4 向阻塞队列中添加了元素:5 向阻塞队列中添加了元素:6 向阻塞队列中添加了元素:7 向阻塞队列中添加了元素:8 向阻塞队列中添加了元素:9 向阻塞队列中添加了元素:10 向阻塞队列中添加了元素:11 向阻塞队列中添加了元素:12 向阻塞队列中添加了元素:13 向阻塞队列中添加了元素:14 向阻塞队列中添加了元素:15 向阻塞队列中添加了元素:16 向阻塞队列中添加了元素:17 向阻塞队列中添加了元素:18 向阻塞队列中添加了元素:19 可以看出,输出到元素19时候,就一直处于等待状态,因为队列满了,程序阻塞了。 阻塞栈 简单应用 对于阻塞栈,与阻塞队列相似。不同点在于栈是“后入先出”的结构,每次操作的是栈顶,而队列是“先进先出”的结构,每次操作的是队列头。Java为阻塞栈定义了接口:java.util.concurrent.BlockingDeque。 Code public class Test { public static void main(String[] args) throws InterruptedException { BlockingDeque bDeque = new LinkedBlockingDeque(20); for (int i = 0; i < 30; i++) { //将指定元素添加到此阻塞栈中,如果没有可用空间,将一直等待(如果有必要)。 bDeque.putFirst(i); System.out.println("向阻塞栈中添加了元素:" + i); } System.out.println("程序到此运行结束,即将退出----"); } } 向阻塞栈中添加了元素:0 向阻塞栈中添加了元素:1 向阻塞栈中添加了元素:2 向阻塞栈中添加了元素:3 向阻塞栈中添加了元素:4 向阻塞栈中添加了元素:5 向阻塞栈中添加了元素:6 向阻塞栈中添加了元素:7 向阻塞栈中添加了元素:8 向阻塞栈中添加了元素:9 向阻塞栈中添加了元素:10 向阻塞栈中添加了元素:11 向阻塞栈中添加了元素:12 向阻塞栈中添加了元素:13 向阻塞栈中添加了元素:14 向阻塞栈中添加了元素:15 向阻塞栈中添加了元素:16 向阻塞栈中添加了元素:17 向阻塞栈中添加了元素:18 向阻塞栈中添加了元素:19 从上面结果可以看到,程序并没结束,二是阻塞住了,原因是栈已经满了,后面追加元素的操作都被阻塞了。 我是天王盖地虎的分割线 本文转自我爱物联网博客园博客,原文链接:http://www.cnblogs.com/yydcdut/p/4104014.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

Docker &amp; Consul &amp; Fabio &amp; ASP.NET Core 2.0 微服务跨平台实践

相关博文: Ubuntu 简单安装 Docker Mac OS、Ubuntu 安装及使用 Consul Consul 服务注册与服务发现 Fabio 安装和简单使用 阅读目录: Docker 运行 Consul 环境 Docker 运行 Fabio 环境 使用 Consul 注册 ASP.NET Core 2.0 服务 使用 Docker 发布部署 ASP.NET Core 2.0 服务 本篇博文的目的:在 Mac OS 中使用 VS Code 开发 ASP.NET Core 2.0 应用程序,然后在 Ubuntu 服务器配置 Docker 环境,并使用 Docker 运行 Consul 和 Fabio 环境,最后使用 Docker 运行 ASP.NET Core 2.0 应用程序。 你要的项目源码:https://github.com/yuezhongxin/HelloDocker.Sample 上面配置看起来还蛮简单,但实际去操作的时候,还是遇到了蛮多的问题,并且花了很多的时间去解决,比如 Docker 运行 Consul 和 Fabio,下面详细说下过程。 1. Docker 运行 Consul 环境 关于Consul的概念: Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。与其他分布式服务注册与发现的方案,比如 Airbnb 的 SmartStack 等相比,Consul 的方案更“一站式”,内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等)。使用起来也较 为简单。Consul 用 Golang 实现,因此具有天然可移植性(支持 Linux、windows 和 Mac OS X);安装包仅包含一个可执行文件,方便部署,与 Docker 等轻量级容器可无缝配合。 Consul Docker 镜像地址:https://hub.docker.com/_/consul/ 配置 Consul 的微服务集群环境,需要先配置下 Server 服务端(需要独立服务器环境),配置命令(没有使用 Docker): $ consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -ui-dir=./dist -config-dir /etc/consul.d -bind=10.9.10.110 一开始,我想在 Mac OS 环境中使用 Docker 配置 Consul Client 客户端,但遇到了一些问题,配置命令: $ docker run -d --net=host --name=consul-client consul agent -bind=10.9.10.190 -client=0.0.0.0 -node=mac-xishuai -retry-join=10.9.10.236 先解析下命令的意思: --net=host:host网络模式,容器的网络接口和主机一样,也就是共享一个 IP 地址,如果没有此命令,默认是bridge网络模式,也就是我们常用的桥接模式,Docker 会分配给容器一个独立的 IP 地址(端口也是独立的),并且容器和主机之间可以相互访问。 -bind=:Consul Client 绑定的 IP 地址,一般是内网的私有 IP 地址,需要内网服务器之前可以相互访问到,注意并不是127.0.0.1。 -retry-join=:加入 Consul 集群中,地址是 Consul Server 的 IP 地址,也可以是-join=,加上retry会不断进行重试。 一台服务器一般会配置一个 Consul Client,所以我们可以直接让 Consul 容器和主机的 IP 地址一样(我使用的),但使用了 Docker 之后,一台服务器就可以配置多个 Consul Client,我们就可以使用bridge网络模式,一台服务器可以完成配置整个 Consul 集群环境。 这里需要再重点说下-client=,一开始我没有理解,先看下官方说明: If you want to expose the Consul interfaces to other containers via a different network, such as the bridge network, use the-clientoption for Consul. With this configuration, Consul's client interfaces will be bound to the bridge IP and available to other containers on that network, but not on the host network. Note that we still keep the cluster address out on the host network for performance. Consul will also accept the-client=0.0.0.0option to bind to all interfaces. 啥意思呢?Consul 服务注册的时候,一般是通过HTTP API接口进行注册,比如:http://10.9.10.190:8500/v1/agent/service/register,就是往 Consul 集群中注册服务,需要注意的是,10.9.10.190一般是 Consul Client 的 IP 地址(也可以是 Consul Server),-client配置的就是此地址,简单来说,就是用来服务注册并能访问到的地址,换句话说,服务注册可以跨服务器(服务和 Consul Client 并不需要在同一台服务器上),0.0.0.0表示任何本机的相关 IP 地址都可以访问,推荐此配置。 这里需要再说明下,Docker 部署 ASP.NET Core 2.0、Consul 和 Fabio 有两种方式: 使用一个 Docker 容器:很简单,在一个容器中完成服务部署,并且配置 Consul 和 Fabio 环境,这样容器就会很臃肿,并且每次发布的时候都得重新配置 Consul 和 Fabio 环境,如果服务很多的话,想想就觉得恐怖。 分别独立 Docker 容器:服务部署、配置 Consul 和 Fabio 环境,都是独立容器实现,互不影响,也可以跨服务实现,简单灵活。 显而易见,推荐第二种方式。 回到正题,上面配置命令,在 Mac OS 报如下错误: $ docker logs consul-client ==> Starting Consul agent... ==> Error starting agent: Failed to start Consul client: Failed to start lan serf: Failed to create memberlist: Could not set up network transport: failed to obtain an address: Failed to start TCP listener on "10.9.10.190" port 8301: listen tcp 10.9.10.190:8301: bind: cannot assign requested address 这个问题花了很多时间也没有解决,奇怪的是不使用 Docker,直接运行 Consul Client 配置命令,却是可以的,后来没办法,我就在 Mac OS 中使用 Ubuntu 虚拟机了(版本 14.04),使用的Vagrant管理工具。 再重新运行配置命令: $ docker run -d --net=host --name=consul-client consul agent -bind=10.9.10.89 -client=0.0.0.0 -node=vagrant-ubuntu-xishuai -retry-join=10.9.2.236 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9c4988cf475f consul "docker-entrypoint..." 2 seconds ago Up 2 seconds consul-client $ docker logs consul-client ==> Starting Consul agent... ==> Consul agent running! Version: 'v1.0.0' Node ID: '34e63f0a-d361-f152-3803-b9fda0642e4d' Node name: 'vagrant-ubuntu-xishuai' Datacenter: 'dc1' (Segment: '') Server: false (Bootstrap: false) Client Addr: [0.0.0.0] (HTTP: 8500, HTTPS: -1, DNS: 8600) Cluster Addr: 10.9.10.89 (LAN: 8301, WAN: 8302) Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false ==> Log data will now stream in as it occurs: 2017/11/14 06:40:52 [INFO] serf: EventMemberJoin: vagrant-ubuntu-xishuai 10.9.10.89 2017/11/14 06:40:52 [INFO] agent: Started DNS server 0.0.0.0:8600 (udp) 2017/11/14 06:40:52 [INFO] agent: Started DNS server 0.0.0.0:8600 (tcp) 2017/11/14 06:40:52 [INFO] agent: Started HTTP server on [::]:8500 (tcp) 2017/11/14 06:40:52 [INFO] agent: Retry join LAN is supported for: aws azure gce softlayer 2017/11/14 06:40:52 [INFO] agent: Joining LAN cluster... 2017/11/14 06:40:52 [INFO] agent: (LAN) joining: [10.9.2.236] 2017/11/14 06:40:52 [WARN] manager: No servers available 2017/11/14 06:40:52 [ERR] agent: failed to sync remote state: No known Consul servers 2017/11/14 06:40:52 [INFO] serf: EventMemberJoin: agent_1 10.9.2.236 2017/11/14 06:40:52 [INFO] agent: (LAN) joined: 1 Err: <nil> 2017/11/14 06:40:52 [INFO] agent: Join LAN completed. Synced with 1 initial agents 2017/11/14 06:40:52 [INFO] consul: adding server agent_1 (Addr: tcp/10.9.2.236:8300) (DC: dc1) 打开 Consul UI 界面,就可以看到我们配置的 Consul Client 了: 2. Docker 运行 Fabio 环境 Fabio是一个快速、现代、zero-conf 负载均衡 HTTP(S) 路由器,用于部署 Consul 管理的微服务。 Fabio Docker 镜像地址:https://hub.docker.com/r/magiconair/fabio/ 配置命令: $ docker run -d --net=host --name=fabio -e 'registry_consul_addr=10.9.10.89:8500' magiconair/fabio 执行日志: $ docker logs fabio 2017/11/14 09:43:49 [INFO] Setting log level to INFO 2017/11/14 09:43:49 [INFO] Runtime config { "Proxy": { "Strategy": "rnd", "Matcher": "prefix", "NoRouteStatus": 404, "MaxConn": 10000, "ShutdownWait": 0, "DialTimeout": 30000000000, "ResponseHeaderTimeout": 0, "KeepAliveTimeout": 0, "FlushInterval": 1000000000, "LocalIP": "10.0.2.15", "ClientIPHeader": "", "TLSHeader": "", "TLSHeaderValue": "", "GZIPContentTypes": null, "RequestID": "" }, "Registry": { "Backend": "consul", "Static": { "Routes": "" }, "File": { "Path": "" }, "Consul": { "Addr": "10.9.10.89:8500", "Scheme": "http", "Token": "", "KVPath": "/fabio/config", "TagPrefix": "urlprefix-", "Register": true, "ServiceAddr": ":9998", "ServiceName": "fabio", "ServiceTags": null, "ServiceStatus": [ "passing" ], "CheckInterval": 1000000000, "CheckTimeout": 3000000000, "CheckScheme": "http", "CheckTLSSkipVerify": false }, "Timeout": 10000000000, "Retry": 500000000 }, "Listen": [ { "Addr": ":9999", "Proto": "http", "ReadTimeout": 0, "WriteTimeout": 0, "CertSource": { "Name": "", "Type": "", "CertPath": "", "KeyPath": "", "ClientCAPath": "", "CAUpgradeCN": "", "Refresh": 0, "Header": null }, "StrictMatch": false, "TLSMinVersion": 0, "TLSMaxVersion": 0, "TLSCiphers": null } ], "Log": { "AccessFormat": "common", "AccessTarget": "", "RoutesFormat": "delta", "Level": "INFO" }, "Metrics": { "Target": "", "Prefix": "{{clean .Hostname}}.{{clean .Exec}}", "Names": "{{clean .Service}}.{{clean .Host}}.{{clean .Path}}.{{clean .TargetURL.Host}}", "Interval": 30000000000, "Timeout": 10000000000, "Retry": 500000000, "GraphiteAddr": "", "StatsDAddr": "", "Circonus": { "APIKey": "", "APIApp": "fabio", "APIURL": "", "CheckID": "", "BrokerID": "" } }, "UI": { "Listen": { "Addr": ":9998", "Proto": "http", "ReadTimeout": 0, "WriteTimeout": 0, "CertSource": { "Name": "", "Type": "", "CertPath": "", "KeyPath": "", "ClientCAPath": "", "CAUpgradeCN": "", "Refresh": 0, "Header": null }, "StrictMatch": false, "TLSMinVersion": 0, "TLSMaxVersion": 0, "TLSCiphers": null }, "Color": "light-green", "Title": "", "Access": "rw" }, "Runtime": { "GOGC": 800, "GOMAXPROCS": 1 }, "ProfileMode": "", "ProfilePath": "/tmp" } 需要注意的两个属性值: Proxy.LocalIP:10.0.2.15:绑定本机的 IP 地址,服务器的 IP 地址是10.9.10.89,所以配置的10.0.2.15是错误的,这个 IP 地址内网是访问不了的。 Registry.Consul.Addr:10.9.10.89:8500:绑定 Consul 地址,我们上面已经完成的 Consul Client 地址就是10.9.10.89:8500,所以是正确的。 这个配置命令研究了好久,也没有解决绑定本机 IP 地址的问题,后来又找到了另外一种方式。 首先,在/etc/fabio/目录下创建一个fabio.properties文件(示例配置),然后vim fabio.properties增加下面配置: registry.consul.register.addr = 10.9.10.89:9998 registry.consul.addr = 10.9.10.89:8500 registry.consul.register.addr绑定 Fabio 地址(本机 IP 地址),registry.consul.addr绑定 Consul 地址。 然后切换到/etc/fabio/目录,执行配置命令: $ docker run -d -p 9999:9999 -p 9998:9998 --net=host --name=fabio -v $PWD/fabio.properties:/etc/fabio/fabio.properties magiconair/fabio $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 301fe4a5b40b magiconair/fabio "/fabio -cfg /etc/..." About a minute ago Up About a minute fabio 9c4988cf475f consul "docker-entrypoint..." 4 hours ago Up 4 hours consul-client 查看执行日志: $ docker logs fabio 2017/11/14 10:10:58 [INFO] Version 1.5.3 starting 2017/11/14 10:10:58 [INFO] Go runtime is go1.9.2 2017/11/14 10:10:58 [INFO] Metrics disabled 2017/11/14 10:10:58 [INFO] Setting GOGC=800 2017/11/14 10:10:58 [INFO] Setting GOMAXPROCS=1 2017/11/14 10:10:58 [INFO] consul: Connecting to "10.9.10.89:8500" in datacenter "dc1" 2017/11/14 10:10:58 [INFO] Admin server access mode "rw" 2017/11/14 10:10:58 [INFO] Admin server listening on ":9998" 2017/11/14 10:10:58 [INFO] Waiting for first routing table 2017/11/14 10:10:58 [INFO] consul: Using dynamic routes 2017/11/14 10:10:58 [INFO] consul: Using tag prefix "urlprefix-" 2017/11/14 10:10:58 [INFO] consul: Watching KV path "/fabio/config" 2017/11/14 10:10:58 [INFO] consul: Manual config changed to #3723938 2017/11/14 10:10:58 [INFO] HTTP proxy listening on :9999 2017/11/14 10:10:58 [INFO] Access logging disabled 2017/11/14 10:10:58 [INFO] Using routing strategy "rnd" 2017/11/14 10:10:58 [INFO] Using route matching "prefix" 2017/11/14 10:10:58 [INFO] consul: Health changed to #3727339 2017/11/14 10:10:59 [INFO] consul: Registered fabio with id "fabio-vagrant-ubuntu-trusty-9998" 2017/11/14 10:10:59 [INFO] consul: Registered fabio with address "10.9.10.89" 2017/11/14 10:10:59 [INFO] consul: Registered fabio with tags "" 2017/11/14 10:10:59 [INFO] consul: Registered fabio with health check to "http://[10.9.10.89]:9998/health" 2017/11/14 10:11:00 [INFO] Config updates 可以通过 Consul UI,进行查看 Fabio 是否正常: 也可以直接浏览http://10.9.10.89:9998/routes?filter=,查看已经注册的服务: 其实,如果不清楚配置命令的话,我们也可以查看 Consul 源码,有可能会帮助我们熟悉命令,比如(https://github.com/fabiolb/fabio/blob/master/registry/consul/register.go): 3. 使用 Consul 注册 ASP.NET Core 2.0 服务 在 Mac OS 中使用 VS Code 开发 ASP.NET Core 2.0 应用程序,就像写 Markdown 一样方便。 Consul 注册 ASP.NET Core 2.0 服务,使用的是 Consul 组件,地址:https://github.com/PlayFab/consuldotnet 安装程序包(VS Code 需要使用NuGet Pakcage Manager命令安装): > install-package Conusl 然后添加一个RegisterWithConsul扩展服务: using System; using System.Collections.Generic; using System.Linq; using Consul; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.DependencyInjection; public static class ApplicationBuilderExtensions { public static IApplicationBuilder RegisterWithConsul(this IApplicationBuilder app, IApplicationLifetime lifetime) { //var consulClient = new ConsulClient(x => x.Address = new Uri($"http://{Program.IP}:8500"));//如果服务和 Consul 在同一台服务器上,使用此代码 var consulClient = new ConsulClient(x => x.Address = new Uri($"http://10.9.10.89:8500"));//请求注册的 Consul 地址 var httpCheck = new AgentServiceCheck() { DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(5),//服务启动多久后注册 Interval = TimeSpan.FromSeconds(10),//健康检查时间间隔,或者称为心跳间隔 HTTP = $"http://{Program.IP}:{Program.Port}/health",//健康检查地址 Timeout = TimeSpan.FromSeconds(5) }; // Register service with consul var registration = new AgentServiceRegistration() { Checks = new[] { httpCheck }, ID = Guid.NewGuid().ToString(), Name = Program.ServiceName, Address = Program.IP, Port = Program.Port, Tags = new[] { $"urlprefix-/{Program.ServiceName}" }//添加 urlprefix-/servicename 格式的 tag 标签,以便 Fabio 识别 }; consulClient.Agent.ServiceRegister(registration).Wait();//服务启动时注册,内部实现其实就是使用 Consul API 进行注册(HttpClient发起) lifetime.ApplicationStopping.Register(() => { consulClient.Agent.ServiceDeregister(registration.ID).Wait();//服务停止时取消注册 }); return app; } } Start.cs配置代码: public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime lifetime) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); app.RegisterWithConsul(lifetime);//here } Program.cs配置代码: public class Program { public static string IP = ""; public const int Port = 54917; public const string ServiceName = "hello-docker"; public const string Version = "v1"; public static void Main(string[] args) { //Program.IP = LocalIPAddress;//使用 Docker 的时候,获取的是 IP 地址不正确,需要进行完善 Program.IP = "10.9.10.190";//Docker 容器中的 IP 地址,如果使用 host 网络模式,也是主机的 IP 地址 BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseUrls($"http://*:{Program.Port}") .UseStartup<Startup>() .Build(); public static string LocalIPAddress { get { UnicastIPAddressInformation mostSuitableIp = null; var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (var network in networkInterfaces) { if (network.OperationalStatus != OperationalStatus.Up) continue; var properties = network.GetIPProperties(); if (properties.GatewayAddresses.Count == 0) continue; foreach (var address in properties.UnicastAddresses) { if (address.Address.AddressFamily != AddressFamily.InterNetwork) continue; if (IPAddress.IsLoopback(address.Address)) continue; return address.Address.ToString(); } } return mostSuitableIp != null ? mostSuitableIp.Address.ToString() : ""; } } } 健康检查配置代码: [Route("[controller]")] public class HealthController : Controller { private static readonly HttpClient _httpClient; static HealthController() { _httpClient = new HttpClient(); } [HttpGet] public IActionResult Get() => Ok("ok"); //或者使用fabio进行健康检查 //[Route("")] //[HttpGet] //public async Task<HttpResponseMessage> GetWithFabio() => await _httpClient.GetAsync("http://127.0.0.1:9998/health"); } 4. 使用 Docker 发布部署 ASP.NET Core 2.0 服务 我们需要在 ASP.NET Core 2.0 应用程序目录下,添加一个Dockerfile文件,用来构建自定义镜像(命令参考:使用 Dockerfile 定制镜像),示例: FROM microsoft/aspnetcore-build AS build-env WORKDIR /app # copy csproj and restore as distinct layers COPY *.csproj ./ RUN dotnet restore # copy everything else and build COPY . ./ RUN dotnet publish -c Release -o out # build runtime image FROM microsoft/aspnetcore WORKDIR /app COPY --from=build-env /app/out . ENTRYPOINT ["dotnet", "HelloDocker.Web.dll"] 简单来说,就是先使用microsoft/aspnetcore-build镜像,还原程序包并发布,然后再使用microsoft/aspnetcore镜像,运行 ASP.NET Core 2.0 应用程序。 构建镜像命令: $ docker build -t hello-docker:v1 . Sending build context to Docker daemon 41.98kB Step 1/10 : FROM microsoft/aspnetcore-build AS build-env ---> d6273f7c44d4 Step 2/10 : WORKDIR /app ---> e37f90cd9aaf Removing intermediate container a4e4db93ea06 Step 3/10 : COPY *.csproj ./ ---> 56278755f94c Step 4/10 : RUN dotnet restore ---> Running in 3b7e8c5d01f4 Restoring packages for /app/HelloDocker.Web.csproj... Restore completed in 411.99 ms for /app/HelloDocker.Web.csproj. Installing System.Security.Principal.Windows 4.4.0-preview1-25305-02. Installing Microsoft.NETCore.Platforms 2.0.0-preview1-25305-02. Installing System.Security.AccessControl 4.4.0-preview1-25305-02. Installing Microsoft.Win32.Registry 4.3.0. Installing System.Security.Permissions 4.4.0-preview1-25305-02. Installing System.Diagnostics.Process 4.3.0. Installing Newtonsoft.Json 10.0.2. Installing System.Net.Http.WinHttpHandler 4.0.0. Installing CoreCompat.System.Drawing.v2 5.2.0-preview1-r131. Installing System.Data.Common 4.3.0. Installing System.Security.Cryptography.Pkcs 4.3.0. Installing System.Xml.XPath.XmlDocument 4.3.0. Installing Pomelo.EntityFrameworkCore.MySql 2.0.0. Installing MySqlConnector 0.26.4. Installing Pomelo.JsonObject 1.1.1. Installing Consul 0.7.2.3. Installing EPPlus.Core 1.5.2. Generating MSBuild file /app/obj/HelloDocker.Web.csproj.nuget.g.props. Generating MSBuild file /app/obj/HelloDocker.Web.csproj.nuget.g.targets. Restore completed in 5.71 sec for /app/HelloDocker.Web.csproj. ---> cdf6ca65acf6 Removing intermediate container 3b7e8c5d01f4 Step 5/10 : COPY . ./ ---> fffa81d15ddc Step 6/10 : RUN dotnet publish -c Release -o out ---> Running in 291c8eea750f Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core Copyright (C) Microsoft Corporation. All rights reserved. HelloDocker.Web -> /app/bin/Release/netcoreapp2.0/HelloDocker.Web.dll HelloDocker.Web -> /app/out/ ---> 078311772175 Removing intermediate container 291c8eea750f Step 7/10 : FROM microsoft/aspnetcore ---> b97d3cf55223 Step 8/10 : WORKDIR /app ---> b0637e3d706b Removing intermediate container 7095565fbbca Step 9/10 : COPY --from=build-env /app/out . ---> c3cb8a708c4b Step 10/10 : ENTRYPOINT dotnet HelloDocker.Web.dll ---> Running in d4111dc055f8 ---> 29121f0eb2b0 Removing intermediate container d4111dc055f8 Successfully built 29121f0eb2b0 Successfully tagged hello-docker:v1 上面构建镜像的过程,非常详细,我们可以得到很多的信息,这边就不叙述了,构建完镜像之后,我们可以查看下是否成功: $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-docker v1 29121f0eb2b0 Less than a second ago 284MB <none> <none> 078311772175 Less than a second ago 1.9GB microsoft/aspnetcore-build latest d6273f7c44d4 4 days ago 1.85GB microsoft/aspnetcore latest b97d3cf55223 4 days ago 280MB consul latest dff07cab6abd 9 days ago 51.8MB magiconair/fabio latest b0d96559369f 10 days ago 11.8MB hello-docker下面没命名的镜像,是临时生成的,作用是使用microsoft/aspnetcore-build镜像,还原程序包的时候,不需要重新安装了。 另外,我们可以在 ASP.NET Core 2.0 应用程序目录下,添加.dockerignore文件,来减少我们构建的镜像文件大小,示例: bin/* obj/* 需要说明下,Consul 和 Fabio 我都是部署在虚拟机的 Docker 容器中,ASP.NET Core 2.0 应用程序,我打算运行在 Mac OS 系统中,也就是说服务和 Consul 是跨服务器的。 运行命令: $ docker run -d -p 54917:54917 --name hello-docker-web hello-docker:v1 查看下是否运行成功: $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 396d42f37185 hello-docker:v1 "dotnet Hello-Dock..." Less than a second ago Up 2 seconds 0.0.0.0:54917->54917/tcp hello-docker-web $ docker logs hello-docker-web Hosting environment: Production Content root path: /app Now listening on: http://[::]:54917 Application started. Press Ctrl+C to shut down. 可以看到,运行是成功的。 我们可以请求验证下: $ curl http://10.9.10.190:54917/api/values ["value1","value2"] 或者使用 Fabio 的网关请求(Fabio 可以用作负载均衡): $ curl http://10.9.10.89:9999/hello-docker/api/values ["value1","value2"] 我们可以查看 Consul UI 中的服务是否运行正常: 或者查看 Fabio UI 中的服务是否存在(通过健康检查后会出现): 本文转自田园里的蟋蟀博客园博客,原文链接:http://www.cnblogs.com/xishuai/p/ubuntu-docker-consul-fabio-aspnet-core.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

VueJS &amp;&amp; ReactJS 如何?听听别人怎么说。

使用所有新的库和框架,很难跟上所有这些库和框架,也就是说,这就需要您决定哪些是值得花时间的。 让我们看看人们说什么: “和Vue.JS一起工作是很愉快的,我发现学习曲线很浅,然而,这纯粹是基于我的经验,如前所述,我过去曾和一些人一起工作过。 默认的模板结构工作原理和Angular非常类似,所以有这个背景的开发人员可能会React上手更快。如果React像Vue.js那么容易学习的话,我真的不会不学习React,它的影响很大。 Reme Le Hane “比起React我更喜欢vue.js。在JavaScript领域vue.js是镇上新来的小子。所以我想说学习的简单性可能是第一。简洁是设计的基础。 Vue是简单的,它具有很多从Angular1和react来的概念。你可以使用它来建立任何系统 - 只是把它包含到HTML文件中。 React更大,更复杂一点(例如在设置方面)。 所以如果你想只想学一个框架,并且没有一个已经建立的系统那就选择Vue。vue.js很平易近人,提供如何把各种事情做好的更多的结构。如果你还不确定,两者都试试。也可以点击这里来阅读,ReactJS vs Vue.js vs AngularJS: https://www.agriya.com/blog/2017/03/08/angularjs-vs-vue-js/ Kathreen Riya “回答你的问题,我认为Vue.js是了解React系统的一个好方法。然而,你看完指南之前你可能会爱上Vue.js。让我告诉你,整件内容都值得一读。找个星期六开始试试吧! 在你读我的回答,我非常鼓励你看看Vue的框架比较。它非常体贴,涉及许多重要的考虑因素。 您想用哪一个平台开发?选择你自己的毒药吧。 React是几乎所有平台的理想选择。无论是Web还是原生开发,React都已经覆盖并在两个方向都成熟了。在UI开发方面,在任何上下文中都是一个灵活的开发人员。它甚至进入了虚拟现实。 Vue 2太走向原生。Evan和他的团队正在与阿里巴巴合作创造Weex,这将允许本地渲染就像React Natvie。Vue 2现在主要集中在Web开发,但也承诺支持其他平台。根据Evan的最新的Vue的博客,2017对Weex是重要的一年。 你想多久才开始开发呢?为什么不今天就开始,而要等几个星期? React有陡峭的学习曲线。它的文件和术语在某些概念是不同寻常,而凌乱,在Vue中指南,属性名称,安装过程,等一切感觉很熟悉,与更广泛的HTML,CSS和ES6标准一致。感谢这两个框架,他们的社区欣欣向荣。你可以找到大量的资源来启动。 我面临的最大障碍是理解构建工具链:Webpack,JSX,我的文本编辑器插件等等。 然而,Vue不需要Webpack,或任何其他事情。你可以马上通过ES5快速的写应用包括使用你最喜爱的软件包管理器或CDN的框架。这意味着你可以用CodePen和其他代码共享的环境。 React最近推出了create-React-app。你可以将这个和Vue的命令行工具进行对比。 在专业领域… Vue一直有大牌公司像gitlab,Laravel,PageKit等采用。 Vue仍然感觉像在我的开发周期上新的孩子。然而,许多开发商在我公司(IHS Markit)已经开始采用Vue,在考虑Angular和React太重。 React很受欢迎,而且已经持续很久了。可能更多的企业使用了React。如果你现在找一份新工作,那就记住这一点。 在过去的几个月里(下降2016),我看过很多文章说:“为什么我们选择vue.js超过React和Angular…”。vue.js获得牵引力,快捷,越来越多的人意识到它是多么容易使用。 再来看看Vue迷… 包括状态管理和路由!你已经知道了关于Flux/Redux,这就是Vue所提供的: Vuex是Vue的Flux/Redux/Elm-inspired的实现,专门针对使用Vue的响应系统。其系统的mutations, getters,actions非常协调,与您的组件集成很容易。不过,如果你更喜欢用Redux,Vue用Revue也是很酷的。无论哪种方式,知道如何使用状态管理系统是一种很有价值的模式。 如果你需要URL路径和参数Vue-Router是该死的简单而强大。只要检查一下文档就ok了。 其他值得注意的事情… Vue的文档是一个梦。我不能告诉足够多的人它的指南和API参考有多大。Evan You(作者和项目负责人)有一种将现代开发提供给门外汉的方式。即使是经验丰富的开发人员,这也是令人耳目一新的。 写单文件vue组件感觉很自然 - 我从来没有回头,因为。这不需要Webpack,但没有恐惧,Vue提供了一个非常方便的命令行工具来帮助你产生很小的安装骨干项目。如果你喜欢Browserify,也不用担心。炼狱,你可以写你自己的CLI模板,如果你想要的话!提供的模板非常灵活。 Vue 2支持服务器端渲染,帮你做SEO,让你的网站可以呈现给禁用javascript的人。 最后,不断有什么给我印象深刻的东西,再次回来的Vue.js是其突出的社区,Evan和他的贡献很专业,它的易用性,其显而易见的对齐与Web部件标准。我被这个项目的成功所震惊,创造出几乎任何我能梦想得到的前端都是多么令人愉快的事情。 Andy Merskin 我也来这儿。 我花了大约一年的时间来处理这个问题。我最近换了Vue在过去的几个月里,我没有回头。关于Vue的一些事情让我沉迷过度了,即: JSX - 我讨厌JSX。它总是花费我几秒钟的时间,如果不是几分钟就万幸了。我每次打开项目要接受这个事实,我一直在寻找一个JavaScript组件包括风格、传统的JavaScript方法和UI元素都支持的一个模块化的野兽。它很快就老化了。 重命名传统HTML属性 - 传统HTML类和风格的元素属性。哦,React也能做到,你所要做的就是确保你所有的类的属性称为类名和样式属性没有字符串值了。想象一下,试图创建一个基于购买的现有主题的网站。把它换成React就像用刀子刺眼球一样。而对Vue,现有的HTML只是普通的工作。你想“reactify”吗?继续,但这不是React中所要求的。 打包需要 - 所以React就只是UI层吗?他们是正确的;你甚至也可以不需要WebPack或任何其他的打包机。但是如果没有它们,你可以尝试建造任何实质性的东西,你都会很难过的。你开始拉动左右,使功能和所有那些假定WebPack包。如果你只是写“hello world”,然后只使用React可以。别的,你就得开始阅读所有的打包内容。 josullivan “我用Angular1一年多一点,然后切换到React。我已经看了Vue几次却从来没有建立任何东西,那就是说我什么也没听到,但伟大的事情,从外观上来看,它是很容易。 我准备采取行动,原因有3: 这是目前最流行的:这很重要,最受欢迎的是它拥有最多的在线资源,更好的工作机会,更有可能获得长期的成功(除非他们使用谷歌的Angular1)。 它是通用的:有库允许您使用React来编写移动甚至桌面应用程序(尽管我还没有亲自尝试构建桌面应用程序)。Vue也将推出自己的混合移动框架,但即使当它来到的时候,我严重怀疑它会赶上React,有多少人考虑重头开始考虑到RN有及其社区的规模。 这很复杂,很难学:我不同意大多数人的观点,说这个不是学习React的好理由,而且实际上是我喜欢React的原因之一。学习React会让你成为更好的JavaScript开发人员。React很难,因为: 它使用了很多中间高级JS概念。像一个深入了解的对象,“this”关键词和一些功能的编程概念是必要的工作,React的效率(最后一个是更相关的如果你使用Redux)。它也将更可能迫使你学习ES6如果你还不知道。 你需要的工具只有1件套,所以你必须依靠终极版的其他库。开始时这可能很痛苦,但它教你如何混合、匹配和连接不同的库。也让你能够定制你的工具,你认为合适或甚至将React放进Angular/Vue应用如果你只想用它在你的应用程序的某些部分的优势。 它通常用到构建工具像WebPack(虽然它实际上并不需要他们)。Vue是建立更加容易没有编译工具,但是相当多的任何真实世界的应用都将是最终使用编译工具,无论如何,这是唯一的一个优势,如果你还在学习,只是想跳过麻烦去更快建设。即使是这样的话,你仍然可以通过使用项目生成器,比如创建React应用程序来跳过所有配置并直接构建。 前端开发的问题是js生态系统可能非常不稳定,框架在不断变化。因此,我认为,不是寻找最简单、最强大、最流行的框架,而是作为一个开发人员长期学习有用的东西(尽管React也有最流行的东西),这可能更有用。即使React最终变得过时,我也不会对学习它感到后悔,因为当我开始学习JavaScript的时候,我知道的比我想知道的还要多。 Ahmed Wagdi “这些天我很少做网页,大多工作在后端数据处理、网络I/O和分布式通信。 一年多一点以前,我想要的是一个实时的Web UI想象一些我对服务器端的数据,我想用SignalR。我回到了一些流行的框架,有一个非常简单的思维方式:“我可以阅读‘入门’,然后在15分钟内开始一些基本的工作吗?”。 我最终选择了Vue,主要是因为它使用简单的对象模型和我真的只是过去的东西我可以从SignalR直接进入并显示。我所尝试的几乎所有其他的东西围绕着数据都有某种类型的打包器/代理,这意味着你必须运行一些映射才可以使模型正常工作。我几乎决定用Mithril,但是当我发现这只是我的需要与Vue重合更多。其实我真的想用React,但Vue只是更平易近人,我不需要花费额外的时间学习React。 然而,真正的考验是几个月后,我去修改和添加更多的功能到我的简单调试UI中。我几乎可以立即拿起它,甚至做了相当大的改动。 与我的经历相比,Ember。我们有一个大的应用程序基于Ember编写,每一次我试图做的是我认为应该是一个简单的改变(不接触的几个月后),我花了比我想象的要长5倍,我花了大部分的时间与它作战之前实现我忘了5个的地方,你必须修改参考额外的依赖或其它一些同样平凡但真气人的细节。 你可以学习Vue在分钟的基础知识,并在几个小时内很内行。这是不是很多的框架,可以说,这是一个被严重低估的好处。 gregmac 这篇文章最初发表在bootstrapbay博客 - https://bootstrapbay.com/blog/vuejs-vs-reactjs/ 各位,你们怎么看?在这场辩论中,请随意点儿,别忘了分享你的体验,这样其他人就能了解到你的想法。 汇智网(www.hubwiz.com,大量最新的vue/react/angular课程),小智翻译。 原文发布时间为:2018年01月05日原文作者:笔阁 本文来源:开源中国 如需转载请联系原作者

优秀的个人博客,低调大师

BuildTask &amp; BuildType

Build Tasks 在build文件中使用了Android或者Java插件之后就会自动创建一系列可以运行的任务。 Gradle中有如下一下默认约定的任务: assemble 该任务包含了项目中的所有打包相关的任务,比如java项目中打的jar包,Android项目中打的apk check 该任务包含了项目中所有验证相关的任务,比如运行测试的任务 build 该任务包含了assemble和check clean 该任务会清空项目的所有的输出,删除所有在assemble任务中打的包 assemble, check 和 build 任务实际上并不做任何事情,它们其实只是为插件提供了一个钩子,真正的事情都是由插件来完成的。这样的话,开发人员就不需要关心我到底运行的是一个java项目还是一个Android项目,也不用关心我到底使用了哪些gradle插件,因为我都可以调用这些约定的任务来完成构建。 注意:Gradle会自动检查一个任务的输入和输出。比如连续两次运行build任务的,Gradle会报告所有的任务都已经是最新刚运行过的了,不需要再次运行。这样的话,任务之间就算是有相互依赖,也不会导致重复的执行。 Java项目常用的任务 Java plugin 主要创建了两个任务: jar assemble任务会依赖jar任务,看名字就知道这是负责打jar包的任务。jar任务本身又会依赖很多其他的任务,比如classes任务,classes任务会编译java代码 test check任务会依赖test任务,这个任务会运行所有的测试。测试代码使用testClasses任务编译,但是我们基本不用手动运行testClasses任务因为test任务已经添加了对它的依赖。 通常情况下,我们只要运行assemble和check任务就够了。 Android项目常用的任务 和其他gradle插件一样,Android插件也提供了一些默认的任务,比如assemble,check,build,clean,同时它也提供了一些自己特有的任务,比如: connectedCheck 运行那些需要在真机或者模拟器上执行的检查任务,这些任务会并行地在所有连接的设备上运行 deviceCheck 使用APIs连接远程设备执行检查.主要用于CI(持续集成)服务上. 上面两个任务都会执行 assemble 和 check任务。新加这两个任务是很有必要的,这样可以保证我们可以运行那些不需要连接设备的检查任务。 注意:build任务并不依赖于deviceCheck或者connectedCheck 一个Android项目通常至少会有两种输出:debug apk和release apk。对应的gradle中有两个任务可以分别输出不同的apk:assembleDebug/assembleRelease。 这两个任务又会依赖其他的任务来构建一个apk。assemble任务依赖这两个任务,调用assemble任务就会生成两种apk。 BuildType 默认情况下,Android插件自动为项目构建一个debug和一个release版本的应用。这两个版本的不同主要体现在在非开发机上的调试功能以及APK的签名方式。debug版本使用一个用公开的name/password创建的key来签名(这样构建的时候就不需要提示输入密码了)。release版本在构建的时候不会进行签名,而是稍后在做。 这个可以使用gradle中的BuildType对象来进行配置。默认情况下,2个BuildType的实例会被创建,一个debug,一个release。Android插件允许自定义这两个实例,当然你也可以创建其他的build type。配置由buildTypes这个DSL来完成: android { buildTypes { debug { applicationIdSuffix ".debug" } jnidebug.initWith(buildTypes.debug) jnidebug { packageNameSuffix ".jnidebug" jniDebuggable true } } } 上面的代码完成了下列配置: 配置默认的debug Build Type 设置debug版本的包名为<应用id>.debug,这样就可以在设备上同时安装debug和release版本了。 创建一个新的BuildType,名字是jnidebug,同时配置它是复制自debug Build Type。 配置jnidebug开启debug版本的JNI组件,添加一个不同的包名后缀。 创建一个新的的Build Types非常简单,只需要在buildTypes下面通过调用initWith或者使用闭包添加一个新的元素。下表是可以配置的属性以及默认值: 属性名 debug版本默认值 release或其他版本默认值 debuggable true false jniDebuggable false false renderscriptDebuggable false false renderscriptOptimLevel 3 3 applicationIdSuffix null null versionNameSuffix null null signingConfig android.signingConfigs.debug null zipAlignEnabled false true minifyEnabled false false 除了这些属性,Build Types还可以用来配置代码和资源文件。针对每一个Build Type,一个新的对应的sourceSet会被创建,这个sourceSet使用一个默认的路径src/< buildtype名字>/。这就意味着Build Type的名字不能是main或者androidTest(这是由插件强制的),同时每个Build Type的名字必须是唯一的。 和其他的source sets一样,Build Type中的source set路径也是可以配置 android { sourceSets.jnidebug.setRoot('foo/jnidebug') } 另外,对每一个Build Type,一个新的assemble< BuildType名字>任务会被创建.当debug和release Build Types被预先创建的时候,他们对应的assemble任务也会被一起创建. 上面的build.gradle代码片段还会创建一个assembleJnidebug任务,assemble任务也会添加对assembleJniDebug任务的依赖,正如它对assembleDebug和assembleRelease任务的依赖一样。 可能是使用场景: 仅在debug模式下使用的权限,release模式不使用。 debug时使用不同的实现 debug模式使用不同的资源(比如当一个资源的值依赖于签名的时候) BuildType中的 代码/资源文件 将会被按照下列方式处理: manifest配置会被merge到app manifest中 代码不会被merge,会被当做另一个代码目录(source folder)来使用 资源文件会覆盖同名的main中的资源文件 作者: 我爱物联网 本文转自我爱物联网博客园博客,原文链接:http://www.cnblogs.com/yydcdut/p/4821156.html,如需转载请自行联系原作者

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册