JSON工具类的构建前端版本

前言在前后端交互的选择上,之前一直采用的是模板引擎(因为我只负责后端)。而这次的一个算是作业吧,前后端都是我,所以就研究了一下JSON交互在java web的应用(主要是前端)。 优缺点因为我是先写后端版本的,所以优缺点部分请跳转至JSON工具类的构建(后端版本)查看。 对比因为我对js也是几天速成系列,所以框架型的jq以及ajax也是处于概念上的理解以及皮毛型的应用。我所认识的主流的前端处理的ajax,所以就选用了ajax进行发送/接收解析json的处理。 需求拆分因为没有打算深入学习前端,所以对需求的划分以及功能实现只限于满足这次的作业。而我定义的功能/需求主要有两个 外部函数只需进行传入接口url以及数据,接收并初步解析结果必须能进行get/post请求的发送以及接收只需要发送单实体数据格式设计因为是请求型报文,所以只需要形成普通的json格式 { "name": "name", "password":"password", "account":"account"}代码设计因为我希望传入接口url以及数据既能完成数据的发送与接收,而个人觉得ajax的请求响应参数中不同的只是url以及发送/接收数据不同了(限本次的需要),所以就在内部构建了一个通用的ajax的函数。 实际代码function transfer(url, data){ var result=new Array(); $.ajax({ type:"POST", url:url, dataType:"json", async:false, contentType:"application/json;charset=utf-8", data:JSON.stringify(data), success:function (dataReturn) { var temp=JSON.stringify(dataReturn); var dataObj = JSON.parse(temp); result[0]=dataObj.status; result[1]=dataObj.object; } }); return result;}代码思路外部函数传入url以及数据data,data为对象类型的数据构建ajax请求发送数据因返回的json报文主要有两项:状态status以及数据object,所以采用数组型的返回结果通过JSON.stringify以及JSON.parse将返回的json字符串转换成对象根据键值拆分返回结果到3中定义的数组返回调用函数通过result[0]判断获取操作结果,result[1]获取操作的详细数据结果展示function clickLogin() { var userName=$("#userName").val(); var password=$("#password").val(); var data={}; data['number']=userName; data['password']=password; var result=transfer("/text_project/login",data); if(result[0]=='200'){ window.location.href="/text_project/main"; } else{ console.log("error"); }}不足之处get请求的需求没有解决,曾经花了7个小时去研究解决,但是因为对js以及ajax不熟悉,知道问题所在,但是没有解决不同结果的处理没有过多的处理原本想构建一个统一的网络访问函数,但是由于get请求的未解决这个想法失败了后记因为这次时间赶,所以只是几天速成系列,只追求能用。但是通过这次的速成,也学习到了怎么去快速掌握或满足自身需要的一些方法,也算是接触到不同方面,能在以后的JAVA后端的学习中有所对比借鉴吧。 相关链接这是我在前台发送/接收解析json的一点经验,我另外写了一篇文章:JSON工具类的构建(后端版本),配合使用会很香哦~ 本文首发于cartoon的博客转载请注明出处:https://cartoonyu.github.io/cartoon-blog/post/json/json工具类的构建前端版本/

October 5, 2019 · 1 min · jiezi

Jsoncpp-所见即所得使用范例

jsoncpp版本: jsoncpp-1.8.4 基本操作#include <json/json.h>int main() { Json::Value root; root["key1"] = 1; root["key2"] = "good"; root["indent"]["length"] = 2; root["indent"]["use_space"] = true; Json::Value v(Json::arrayValue); v[0] = "clojure"; v[1] = "json"; v[2] = "xml"; root["key3"] = v; return 0;}遍历与类型 for (auto i = root.begin(); i != root.end(); i++) { Json::Value& obj = *i; const std::string name = i.name(); std::cout << name << "-" << i.key() << ": " << obj.isArray() << "\n"; }读入 Json::Value root; std::ifstream infile; infile.open("your.json", std::ios::in); try { infile >> root; } catch (std::exception& e) { root = Json::objectValue; } infile.close();这里用的是文件的读入流, 也可以是文本的stringstream ...

October 3, 2019 · 1 min · jiezi

go-json数据转发

案例例如,有个 GET 接口,可以批量获取用户信息???? > curl 'http://localhost:8080/user/1,2,3'[ { "user_id":1, "other_suff":... }, { "user_id":2, "other_suff":... }, { "user_id":3, "other_suff":... }]同时,我们要将用户信息和他们的某些订单信息放在一起,组装成为????的接口,满足其他业务需求。 [ { "user_info":{ "user_id":1, "other_suff":... }, "order_info":{ "order_id":1, "user_id":1, "other_suff":... } }, { "user_info":{ "user_id":2, "other_suff":... }, "order_info":{ "order_id":2, "user_id":2, "other_suff":... } }, { "user_info":{ "user_id":3, "other_suff":... }, "order_info":{ "order_id":3, "user_id":3, "other_suff":... } }]分析解决这个问题很简单:把user信息和order信息的json用工具解析得到结构体,然后调用他们的接口得到数据,根据id关联和拼装,最后返回。 这样的做法存在的一个问题是,代码解析了user和order的完整结构。如果user接口返回的用户信息增加了字段,我们这里的结构体要同步更新,否则我们给出的数据就是不完整的。(这可能是很痛苦的,你要求别的团队加字段,得排期...) 其实我们作为数据的“中间商”,只关心user接口json里的 user_id ,我们使用这个字段关联order数据。对于user信息里的 other_suff 或者其他数据,我们并不关心,只要保证完整传出去就好了。 根据 https://golang.org/pkg/encodi... ,可以知道直接丢一个 map[string]interface{} 给 json.Unmarshal 也可以正常解析的,于是我们可以写出比较通用的透传代码。 type Content []map[string]interface{}func (c Content) GetByFieldName(name string, defaultVal interface{}) infterface{} { for _, item := range c { val, ok := item[name] if !ok { continue } if val == nil { return defaultVal } return val } return defaultVal}func getUserContentByIDs(ids []int) Content { ... var c Content err := json.Unmarshal(jsonData, &c) ... return c}func getOrderContentByUserIDs(ids []int) Content {.../*同上*/}func Handler(userIDs []int) []Combine { users := getUserContentByIDs(userIDs) orders := getOrderContentByUserIDs(userIDs) // 这里假设用户和订单是一对一的关系 ret := make([]Combine, 0, len(users)) for _, u := range users { for _, o := range orders { userID := u.GetByFieldName("user_id", 0) orderUserID := o.GetByFieldName("user_id", 0) if userID != 0 && userID == orderUserID { ret = append(ret, Combine{ UserInfo: u, OrderInfo: o, }) break } } } return ret}P.S. 在上面的例子中,每次查询Content都要遍历数组。如果数据量大或者查询频繁,可以在初始化Content的时候,根据item的唯一标标识,再给Content根据封装一个map,提高查询效率。 ...

September 10, 2019 · 1 min · jiezi

Unity-NewtonsoftJsonLitJson和SimpleJSON性能对比

Unity中Json库性能对比测试 类库大小对比: 类库文件类型大小NewtonsoftJson.dll353KBLitJson.dll56KBSimpleJSON.cs68KB解析时间对比:执行次数:10000次 测试方法NewtonsoftJsonLitJsonSimpleJSON测试1114ms158ms52ms测试2136ms288ms126ms测试3263ms542ms169ms测试4333ms747ms200ms测试代码: using UnityEngine;using System.Diagnostics;using LitJson;using SimpleJSON;using Newtonsoft.Json.Linq;/// <summary>/// JsonTest/// ZhangYu 2019-07-11/// <para>文章地址:https://segmentfault.com/a/1190000019731298</para>/// </summary>public class JsonTest : MonoBehaviour { public int count = 10000; private Stopwatch watch; private void Start () { watch = new Stopwatch(); string json1 = "{\"id\":10001,\"name\":\"test\"}"; string json2 = "[1,2,3,4,5,6,7,8,9,10]"; string json3 = "{\"id\":10000,\"username\":\"zhangyu\",\"password\":\"123456\",\"nickname\":\"冰封百度\",\"age\":20,\"gender\":1,\"phone\":12345678910,\"email\":\"zhangyu@xx.com\"}"; string json4 = "[\"test2\",[[\"key1\", \"id\"],[\"key2\", \"hp\"],[\"key3\", \"mp\"],[\"key4\", \"exp\"],[\"key5\", \"money\"],[\"key6\", \"point\"],[\"key7\", \"age\"],[\"key8\", \"sex\"]]]"; JsonParseTest(json1); JsonParseTest(json2); JsonParseTest(json3); JsonParseTest(json4); } private void JsonParseTest(string json) { print("json:" + json); bool isArray = json[0] == '['; NewtonsoftJsonTest(json, isArray); LiteJsonTest(json); SimpleJsonTest(json); print("======================"); } private void NewtonsoftJsonTest(string json, bool isArray) { watch.Reset(); watch.Start(); if (isArray) { for (int i = 0; i < count; i++) { JArray jArray = JArray.Parse(json); } } else { for (int i = 0; i < count; i++) { JObject jObj = JObject.Parse(json); } } watch.Stop(); print("NewtonsoftJson Parse Time(ms):" + watch.ElapsedMilliseconds); } private void LiteJsonTest(string json) { watch.Reset(); watch.Start(); for (int i = 0; i < count; i++) { JsonData jData = JsonMapper.ToObject(json); } watch.Stop(); print("LiteJson Parse Time(ms):" + watch.ElapsedMilliseconds); } private void SimpleJsonTest(string json) { watch.Reset(); watch.Start(); for (int i = 0; i < count; i++) { JSONNode jNode = JSON.Parse(json); } watch.Stop(); print("SimpleJson Parse Time(ms):" + watch.ElapsedMilliseconds); }} ...

July 11, 2019 · 1 min · jiezi

golang-json-编码-map-与struct-结构的对比

本文对比试验采用官方包做json map 和struct 编码。 原文连接:http://blog.lpflpf.cn/passage... 数据构造map 数据类型为map[string]string , key 长度为10, val 长度为100struct 定义如下: type Object struct { Xvlbzgbaic string `json:"xvlbzgbaic"` Krbemfdzdc string `json:"krbemfdzdc"` Rzlntxyeuc string `json:"rzlntxyeuc"` Ctzkjkziva string `json:"ctzkjkziva"` Orsufumaps string `json:"orsufumaps"` Hyevwbtcml string `json:"hyevwbtcml"` Baatlyhdao string `json:"baatlyhdao"` Fkfohsvvxs string `json:"fkfohsvvxs"` Pqwarpxptp string `json:"pqwarpxptp"` Orvaukawww string `json:"orvaukawww"`}对比程序如下: obj := Object{} json.Unmarshal([]byte(str), &obj) start := time.Now() for i := 0; i < 1000000; i++ { json.Marshal(obj) } fmt.Println(time.Since(start)) maps := map[string]string{} json.Unmarshal([]byte(str), &maps) start = time.Now() for i := 0; i < 1000000; i++ { json.Marshal(maps) } // fmt.Println(time.Since(start))其中,str 为生成好的固定json数据, 我们对相同的数据做json 编码, 运行结果可以看出,时间差距大约为1倍,若将map的key 个数调整为100个 ...

July 5, 2019 · 2 min · jiezi

Express-的使用

以下内容,基于 Express 4.x 版本 Node.js 的 ExpressExpress 估计是那种你第一次接触,就会喜欢上用它的框架。因为它真的非常简单,直接。 在当前版本上,一共才这么几个文件: lib/├── application.js├── express.js├── middleware│   ├── init.js│   └── query.js├── request.js├── response.js├── router│   ├── index.js│   ├── layer.js│   └── route.js├── utils.js└── view.js这种程度,说它是一个“框架”可能都有些过了,几乎都是工具性质的实现,只限于 Web 层。 当然,直接了当地实现了 Web 层的基本功能,是得益于 Node.js 本身的 API 中,就提供了 net 和 http 这两层, Express 对 http 的方法包装一下即可。 不过,本身功能简单的东西,在 package.json 中却有好长一串 dependencies 列表。 Hello World在跑 Express 前,你可能需要初始化一个 npm 项目,然后再使用 npm 安装 Express: mkdir pcd pnpm initnpm install express --save新建一个 app.js : const express = require('express');const app = express();app.all('/', (req, res) => res.send('hello') );app.listen(8888);调试信息是通过环境变量 DEBUG 控制的: const process = require('process');process.env['DEBUG'] = 'express:*';这样就可以在终端看到带颜色的输出了,嗯,是的,带颜色控制字符,vim 中直接跑就 SB 了。 应用 ApplicationApplication 是一个上层统筹的概念,整合“请求-响应”流程。 express() 的调用会返回一个 application ,一个项目中,有多个 app 是没问题的: ...

June 26, 2019 · 7 min · jiezi

Gson-解析-Json举几个常用的实例

一. 序前几天写了一篇,关于利用 GSON 在 JSON 序列化和反序列化之间,数据容错的文章。最简单的利用 @SerializedName 注解来配置多个不同 JSON Key 值,或者再使用 @Expose 来配置一些例外的情况。更复杂一些的数据,可以使用 TypeAdapter 来解决,TypeAdapter 可以说是一颗 GSON 解析 JSON 的银弹,所有复杂数据解析以及容错问题,都可以通过它来解决。还不了解的可以先看看之前的文章《利用 Gson 做好 JSON 数据容错》。 文章评论里和公众号后台有一些小伙伴,针对具体数据容错的场景,提出了具体的问题。今天就在这篇文章里统一解答,并且给出解决方案。 二. GSON 数据容错实例就像前文中介绍的一样,GSON 已经提供了一些简单的注解,去做数据的容错处理。更复杂的操作,就需要用到 TypeAdapter 了,需要注意的是,一旦上了 TypeAdapter 之后,注解的配置就会失效。 2.1 什么是 TypeAdapterTypeAdapter 是 GSON 2.1 版本开始支持的一个抽象类,用于接管某些类型的序列化和反序列化。TypeAdapter 最重要的两个方法就是 write() 和 read() ,它们分别接管了序列化和反序列化的具体过程。 如果想单独接管序列化或反序列化的某一个过程,可以使用 JsonSerializer 和 JsonDeserializer 这两个接口,它们组合起来的效果和 TypeAdapter 类似,但是其内部实现是不同的。 简单来说,TypeAdapter 是支持流的,所以它比较省内存,但是使用起来有些不方便。而 JsonSerializer 和 JsonDeserializer 是将数据都读到内存中再进行操作,会比 TypeAdapter 更费内存,但是 API 使用起来更清晰一些。 虽然 TypeAdapter 更省内存,但是通常我们业务接口所使用的那点数据量,所占用的内存其实影响不大,可以忽略不计。 因为 TypeAdapter、JsonSerializer 以及 JsonDeserializer 都需要配合 GsonBuilder.registerTypeAdapter() 方法,所以在本文中,此种接管方式,统称为 TypeAdapter 接管。 ...

June 25, 2019 · 3 min · jiezi

自定义表单生成器formcreate-v2介绍

介绍form-create 是一个可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的表单生成器。并且支持生成任何 Vue 组件。结合内置17种常用表单组件和自定义组件,再复杂的表单都可以轻松搞定。 文档 | github 功能自定义组件 可生成任何Vue组件自带数据验证轻松转换为表单组件通过 JSON 生成表单通过 Maker 生成表单强大的API,可快速操作表单双向数据绑定事件扩展局部更新数据验证栅格布局内置组件17种常用表单组件对比 1.x速度更快体积更小更强大的全局配置自定义组件更容易扩展更容易支持第三方 UI 库更少的 bug示例通过 JSON 创建表单 通过 API 操作表单 @form-create包说明名称说明@form-create/iviewiview 版表单生成器@form-create/element-uielement-ui 版表单生成器@form-create/coreform-create 核心包@form-create/utilsform-create 工具包@form-create/data省市区多级联动数据使用以element-ui版本为例介绍如何在项目中使用 form-create 安装npm i @form-create/element-ui挂载全局注册 import formCreate form '@form-create/element-ui';Vue.use(formCreate);局部挂载 import formCreate form '@form-create/element-ui';export default { components:{ formCreate:formCreaet.$form() }}生成表单<template> <form-create v-model="$f" :rule="rule" @on-submit="onSubmit"></form-create></template>export default { data () { return { //表单实例对象 $f:{}, //表单生成规则 rule:[ { type:'input', field:'goods_name', title:'商品名称' }, { type:'datePicker', field:'created_at', title:'创建时间' } ] }; }, methods:{ onSubmit(formData){ //TODO 提交表单 } }};效果 ...

June 24, 2019 · 1 min · jiezi

DLA-SQL技巧行列转换和JSON数据列展开

1. 简介在数据库SQL处理中,常常有行转列(Pivot)和列转行(Unpivot)的数据处理需求。本文以示例说明在Data Lake Analytics(https://www.aliyun.com/product/datalakeanalytics)中,如何使用SQL的一些技巧,达到行转列(Pivot)和列转行(Unpivot)的目的。另外,DLA支持函数式表达式的处理逻辑、丰富的JSON数据处理函数和UNNEST的SQL语法,结合这些功能,能够实现非常丰富、强大的SQL数据处理语义和能力,本文也以JSON数据列展开为示例,说明在DLA中使用这种SQL的技巧。 2. 行转列(Pivot)2.1 样例数据 test_pivot表内容: +------+----------+---------+--------+| id | username | subject | source |+------+----------+---------+--------+| 1 | 张三 | 语文 | 60 || 2 | 李四 | 数学 | 70 || 3 | 王五 | 英语 | 80 || 4 | 王五 | 数学 | 75 || 5 | 王五 | 语文 | 57 || 6 | 李四 | 语文 | 80 || 7 | 张三 | 英语 | 100 |+------+----------+---------+--------+2.2 方法一:通过CASE WHEN语句 ...

June 20, 2019 · 4 min · jiezi

simdjsonphp-高速解析json

介绍simdjson_php(https://github.com/crazyxman/...,它绑定simdjson来实现快速解析,simdjson是一个高速的json解析器,它使用了大多数SIMD单一指令。simdjson介绍:https://github.com/lemire/sim... 环境依赖php7+带有AVX2的处理器(即,2013年发布的Haswell微体系结构的Intel处理器和2017年发布的Zen微体系结构的AMD处理器),大多数cpu都是支持的最近的C ++编译器(例如,GNU GCC或LLVM CLANG或Visual Studio 2017),我们假设C ++ 17。GNU GCC 7或更高版本或LLVM的clang 6或更高版本检查操作系统/处理器是否支持它: OS X: sysctl -a | grep machdep.cpu.leaf7_featuresLinux: grep avx2 /proc/cpuinfo使用简介当需要获取一个较大json串中的某个key时 使用simdjson_key_value() 是比较合适的,不像json_decode() 把整个json串解析成数组,开辟不必要的内存,当然在性能上是略逊于hash查找的。当验证一个字符串是否为json时simdjson_isvaild() 是比较合适的,并且是非常快的,同样不需要通过json_decode()来验证。//检查字符串是否为一个有效的json:$isValid = simdjson_isvalid($jsonString); //return bool//解析一个json字符串,返回数组,对象,null,类似json_decode(),第三个参数为解析的深度$parsedJSON = simdjson_decode($jsonString, true, 512); //return array|object|null. "null" string is not a standard json/*{ "Image": { "Width": 800, "Height": 600, "Title": "View from 15th Floor", "Thumbnail": { "Url": "http://www.example.com/image/481989943", "Height": 125, "Width": 100 }, "Animated" : false, "IDs": [116, 943, 234, 38793, {"p": "30"}] }}*///注意. "\t" 是一个分割符. 它必须是一个控制字符. 它用来分割对象的key或数组的下标//例如. "Image\tThumbnail\tUrl" 是正确. 'Image\tThumbnail\tUrl' 是错误的//根据json串获取指定key的值$value = simdjson_key_value($jsonString, "Image\tThumbnail\tUrl");var_dump($value); // string(38) "http://www.example.com/image/481989943"$value = simdjson_key_value($jsonString, "Image\tIDs\t4", true);var_dump($value); /*array(1) { ["p"]=> string(2) "30"}*///获取json解析后的资源,只解析一次,后续使用不再解析$resource = simdjson_resource($jsonString);//根据json资源获取指定key的值$value = simdjson_key_value($resource, "Image\tThumbnail\tUrl");var_dump($value); // string(38) "http://www.example.com/image/481989943"$value = simdjson_key_value($resource, "Image\tIDs\t4", true);var_dump($value); /*array(1) { ["p"]=> string(2) "30"}*///检查key是否存在,参数可以是一个json串也可以是一个json资源,返回true,false,null。当第一个参数是字符串时返回null代表解析失败$res = simdjson_key_exists($jsonString, "Image\tIDs\t1");var_dump($res) //bool(true)$res = simdjson_key_exists($resource, "Image\tIDs\t1");var_dump($res) //bool(true)性能测试(秒)filenamejson_decodesimdjson_decodesimdjson_isvalidapache_builds.json0.003073000.002252000.00018100canada.json0.139550000.027739000.00358300citm_catalog.json0.030309000.013340000.00117000github_events.json0.002941000.000904000.00008500gsoc-2018.json0.042925000.011120000.00186700instruments.json0.005097000.002318000.00017500marine_ik.json0.098336000.044175000.00463400mesh.json0.018692000.007226000.00114800mesh.pretty.json0.035762000.007381000.00163400numbers.json0.002636000.000699000.00018200random.json0.017135000.009739000.00063000twitter.json0.012586000.006184000.00057400twitterescaped.json0.014359000.006504000.00074300update-center.json0.015060000.008691000.00047800You may run the benchmarks by running the commands: ...

June 12, 2019 · 1 min · jiezi

什么是JSON

JSON 实例<!DOCTYPE html><html><head><meta charset="utf-8"><title>阿里云大学(edu.aliyun.com)</title></head><body><h2>JavaScript 创建 JSON 对象</h2><p>网站名称: <span id="jname"></span><br /> 网站地址: <span id="jurl"></span><br /> 网站 slogan: <span id="jslogan"></span><br /> </p><script>var JSONObject= { "name":"阿里云大学", "url":"edu.aliyun.com", "slogan":"学的不仅是技术,更是梦想!"};document.getElementById("jname").innerHTML=JSONObject.name document.getElementById("jurl").innerHTML=JSONObject.url document.getElementById("jslogan").innerHTML=JSONObject.slogan</script></body></html>运行结果 JavaScript 创建 JSON 对象网站名称: 阿里云大学网站地址: edu.aliyun.com网站 slogan: 学的不仅是技术,更是梦想!与 XML 相同之处 JSON 是纯文本 JSON 具有"自我描述性"(人类可读) JSON 具有层级结构(值中存在值) JSON 可通过 JavaScript 进行解析 JSON 数据可使用 AJAX 进行传输 与 XML 不同之处 没有结束标签 更短 读写的速度更快 能够使用内建的 JavaScript eval() 方法进行解析 使用数组 不使用保留字 为什么使用 JSON?对于 AJAX 应用程序来说,JSON 比 XML 更快更易使用:使用 XML:1 读取 XML 文档2 使用 XML DOM 来循环遍历文档3 读取值并存储在变量中使用 JSON:1 读取 JSON 字符串2 用 eval() 处理 JSON 字符串 ...

June 12, 2019 · 1 min · jiezi

前端必备基础

1、DOM结构 —— 两个节点之间可能存在哪些关系以及如何在节点之间任意移动。 1、包含与被包含A.contains(B)检测B节点是否是A节点的子节点,返回布尔值2、父与子获取父节点:node.parentNode,node.parentElement,两者区别在于后者只能获取元素获取子节点:childNodes(NodeList对象存在的子节点集合),firstChild,lastChild3、同辈(兄弟节点)nextSibling,previousSibling4、Element Travel API给DOM添加的属性childElementCount,firstElementChild,lastElementChild,nextElementSilbling,previousElementSibling,他们与之前的方法的区别是多了Element,保证只返回元素节点,而之前的方法普通的文本节点及注释节点也会返回,之前的方法在非IE浏览器中还会把元素之间的空白符当文本节点返回5、children属性与childNodes不同的地方在于:children只包含元素子节点2、DOM操作 —— 如何添加、移除、移动、复制、创建和查找节点等。 1、创建新节点createDocumentFragment() 创建一个DOM片段creatElement() 创建一个具体的元素creatTextNode() 创建一个文本节点2、添加、移除、替换、插入appendChild()removeChild()replaceChild()insertBefore() 在已有的子节点前插入一个新的子节点3、查找getElementsByTagName() 通过标签名称getElementsByName() 通过元素的name属性的值getElementById() 通过元素id,唯一性3、事件 —— 如何使用事件,以及IE和标准DOM事件模型之间存在的差别。 1、关于事件流(1)ie事件流:事件冒泡 由事件的目标(event.srcElement)接受事件,然后逐级向上(例如:下一个为包含event.srcElement的节点传递事件,直到文档节点document) (2) 其他浏览器支持的另一种事件流:事件捕获 与事件冒泡正好相反,文档节点document先监听到事件,然后把事件逐级向下传递事件,直到节点目标节点event.target (3) dom事件流 dom2级事件规范的事件流综合了以上两种,把事件流分为了以下三个阶段: 一、事件捕获阶段:不涉及事件目标,或者说这个阶段在目标之前就结束了 二、处于目标阶段:被看作冒泡阶段的一部分,所以可以在冒泡阶段在目标事件上操作事件 三、事件冒泡阶段 (4) 规范和浏览器实现的差别 dom2级事件规范的捕获阶段,事件从文档节点document开始传播,现代浏览器大多数是从window对象开始传播事件的 dom2级事件规范捕获阶段不涉及事件目标,现代浏览器大多数都在这个阶段包含事件目标2、如何使用事件?以下几种用来响应事件的事件处理程序 html事件处理程序|dom0级事件处理程序|dom2级事件处理程序|ie的事件处理程序 html事件处理程序: 指定事件:<button type="button" onclick="alert('响应事件的处javascript代码,可以用全局的函数')">Click Me!</button> 优缺点:简单,但是与HTML代码紧密耦合,更改不方便; 删除事件:同DOM0的删除事件处理方式; dom0级事件处理程序 指定事件:document.onclick = function(){alert("document has been clicked")}; 优缺点:简单且跨浏览器 删除事件:document.onclick = null; 实质: 为元素指定方法(栗子中为document指定onclick方法),移除方法,所以其处理程序是在元素的作用域运行的; dom2级事件处理程序: 指定事件:addEventListener("引号括起来的事件名", 触发事件后调用的事件处理程序, 是否在捕获节点调用时间处理程序的布尔值) 栗子:var funA = function(){alert(" DOM2级事件处理程序")}; document.addEventListener("click", funA, false); 删除事件: document.removeEventListener("click", funA, false); 如果指定的处理程序是匿名函数则不能删除,因为没有函数名; 优缺点: 可以添加多个监听事件,缺点必须指定函数名才能删除 ie事件处理程序(IE11以下,IE11及Edge用的DOM2级事件处理程序) 指定事件: attachEvent("onclick", function(){alert("is no longer supported in ie11")}); 删除事件: detachEvent("onclick", funA); 如果指定的处理程序是匿名函数则不能删除,因为没有函数名; 优缺点:可以添加多个监听事件,缺点必须指定函数名才能删除3、ie和标准dom事件模型之间存在的差别 这里的IE是IE11以下; 参数的差别: attachEvent()的第一个参数比addEventListener()的事件名多一个"on",且没有第三个参数,因为IE事件模型只支持冒泡事件流; 事件处理函数作用域的区别: IE中事件处理程序处于全局作用域,其内的this会指向window;而用DOM(0或2)级事件的事件处理程序的作用域是元素作用域,其内的this指向其所属的元素 例: document.addEventListener("click", function(){ if(this == document){ alert("此时this指向document"); } }, false); 事件对象event的属性方法的差别 IE DOM cancelBubble = true stopPropagation() //停止冒泡 returnValue = false preventDefault() //阻止元素默认事件 srcEelement target //事件目标4、XMLHttpRequest —— 这是什么、怎样完整地执行一次GET请求、怎样检测错误。 ...

June 11, 2019 · 3 min · jiezi

vue-formData上传图片以及其他表单数据

formData可以实现图片上传,但是如果在上传图片的同时也要上传其他数据呢? 且其他数据不通过对象方式传给后台,而是依然通过json数据格式传递 代码如下: submitBtn () { let formData = new FormData() formData.append("file",this.file)//图片 let params = { contentId: this.pictureId, description: this.detailForm.desc, }//其他数据 formData.append('contentId',params.contentId)//json格式上传 formData.append('description',params.description) maintain.addFasMaintainInfoRecord(formData)//上传接口 .then((res) => { this.$Message.success('添加成功') }) .catch((err) => { this.$Message.error('添加失败') }) },

June 10, 2019 · 1 min · jiezi

JSON格式校验

json-schema { "$schema":"http://json-schema.org/draft-04/schema", "type":"object", "properties":{ "a":{ "type":"array", "items":{ "type":"object", "properties":{ "b":{ "type":"integer" } } } } }, "required":["a"]}以上规则可以校验以下的json数据 { "a":[ { "b":9 } ]}

June 9, 2019 · 1 min · jiezi

如何提升JSONstringify的性能

1. 熟悉的JSON.stringify()在浏览器端或服务端,JSON.stringify()都是我们很常用的方法: 将 JSON object 存储到 localStorage 中;POST 请求中的 JSON body;处理响应体中的 JSON 形式的数据;甚至某些条件下,我们还会用它来实现一个简单的深拷贝;……在一些性能敏感的场合下(例如服务端处理大量并发),或面对大量 stringify 的操作时,我们会希望它的性能更好,速度更快。这也催生了一些优化的 stringify 方案/库,下图是它们与原生方法的性能对比: 绿色部分时原生JSON.stringify(),可见性能相较这些库都要低很多。那么,在大幅的性能提升背后的技术原理是什么呢? 2. 比 stringify 更快的 stringify由于 JavaScript 是动态性很强的语言,所以对于一个 Object 类型的变量,其包含的键名、键值、键值类型最终只能在运行时确定。因此,执行JSON.stringify()时会有很多工作要做。在一无所知的情况下,我们想要大幅优化显然无能为力。 那么如果我们知道这个 Object 中的键名、键值信息呢 —— 也就是知道它的结构信息,这会有帮助么? 看个例子: 下面这个 Object, const obj = { name: 'alienzhou', status: 6, working: true};我们对它应用JSON.stringify(),得到结果为 JSON.stringify(obj);// {"name":"alienzhou","status":6,"working":true}现在如果我们知道这个obj的结构是固定的: 键名不变键值的类型一定那么其实,我可以创建一个“定制化”的 stringify 方法 function myStringify(o) { return ( '{"name":"' + o.name + '","status":' + o.status + ',"isWorking":' + o.working + '}' );}看看我们的myStringify方法的输出: ...

June 5, 2019 · 3 min · jiezi

python-str-转换-json

使用Python编码和解析JsonPython内置了json包来帮助我们完成对json的操作。 针对str操作:将Python的字典结构导出到json使用json.dumps() ,将json读成Python的字典结构,使用json.loads() 。 针对对文件操作:分别使用json.load()函数和json.dump()函数。 # coding=gbkimport tracebackimport jsonfile_name = '广州市_农贸市场_1717.txt'f = open(file_name, 'r')tmp = json.load(f)print(type(tmp))data = []for i in tmp:# print(i)#测试做一下格式转换 dl = { "name":i["name"], "location_lat" : i["location"]["lat"], "location_lng" : i["location"]["lng"] } data.append(dl)print(data)with open('ceshi.txt', 'w', encoding="utf-8") as f: f.write(json.dumps(data, ensure_ascii=False)) print('---wirte success---')附上 广州市_农贸市场_1717.txt [{"name": "江南市场", "location": {"lat": 23.179127, "lng": 113.236457}, "address": "广东省广州市白云区增槎路926号", "province": "广东省", "city": "广州市", "area": "白云区", "street_id": "9fbfeef24609615cece4a59b", "detail": 1, "uid": "9fbfeef24609615cece4a59b"}, {"name": "广州番禺清河市场", "location": {"lat": 22.938501, "lng": 113.401308}, "address": "广东省广州市番禺区亚运大道94号", "province": "广东省", "city": "广州市", "area": "番禺区", "street_id": "bc57b5426b43342922f01731", "telephone": "(020)84629663", "detail": 1, "uid": "bc57b5426b43342922f01731"}]

June 4, 2019 · 1 min · jiezi

利用Packer自定义镜像创建容器集群

阿里云容器服务Kubernetes集群支持CentOS操作系统,在绝大多数情况下可以满足客户的要求。但是有些客户由于业务系统对操作系统依赖比较高,希望定制化一些操作系统参数,则可以用自定义镜像来创建Kubernetes集群。 创建自定义操作系统镜像有两种方式,一是在控制台上通过为一台ECS创建快照的方式创建镜像,注意一定要基于阿里云CentOS作为基础镜像,把对操作系统的定制化更新完打成镜像即可。但这种方式的不便之处在于,如果每次对操作系统镜像有更新,则都要手动操作一遍,很难自动化。而且如果是从已有的Kubernetes节点制作镜像,还需要把Docker,Kubelet等清理干净才能制作镜像,步骤繁琐且容易遗漏。 另外一种方式就是本文介绍的用Packer构建镜像。相关的参考文档:使用Packer创建自定义镜像。采用Packer构建镜像的好处是可以把构建方式自动化,构建所需的参数文件中包含了对干净的基础镜像所做的修改,一目了然,并且可以把配置进行版本化管理。后期需要构建新的镜像,只需改变配置重新执行一下Packer构建即可,非常方便,是在生产环境中使用自定义镜像的推荐方式。 那么有没有一个针对容器服务集群的Packer配置模版呢?容器服务团队开源的ack-image-builder就是一个这样的示例项目。下面我们就来一起动手实践一下。 安装Packer可以根据官方文档安装Packer https://www.packer.io/intro/getting-started/install.html 。 创建自定义镜像克隆ack-image-builder项目到本地,可以看到config和scripts目录下是一些示例定制化脚本,读者可以根据自己的需求更新改。 $ git clone https://github.com/AliyunContainerService/ack-image-builder.git$ cd ack-image-builderack-image-builder $ tree.├── LICENSE.txt├── README.md├── ack-centos.json├── config│   └── default.sh└── scripts ├── cleanUpKerneles.sh ├── reboot.sh ├── updateKernel.sh └── verify.sh2 directories, 8 files在ack-centos.json 可以配置在把生成好的自定义镜像存在哪个区(示例中为cn-hangzhou)。 { "variables": { "region": "cn-hangzhou", "image_name": "ack_test_image{{timestamp}}", "source_image": "centos_7_06_64_20G_alibase_20190218.vhd", ... },配置好阿里云账号的AK,然后执行构建命令。 export ALICLOUD_ACCESS_KEY=XXXexport ALICLOUD_SECRET_KEY=XXXpacker build ack-centos.json大约7-8分钟一个新的自定义镜像就构建成功了。可以进入ECS控制台查看新生成的镜像。 利用自定义镜像创建容器集群开通自定义镜像白名单 读者如果需要尝试自定义镜像能力,需要先开工单,申请在容器服务控制台上开通自定义镜像的白名单。 创建容器集群 白名单开通后进入容器服务控制台 https://cs.console.aliyun.com/#/k8s/cluster/list,创建Kubernetes集群。选择自定义镜像所在的区,在示例中是cn-hangzhou。 在创建集群的页面中点击"显示高级选项",会出现"自定义镜像"的选择界面: 如果在选择中找不到刚创建的镜像,请检查一下集群和自定义镜像是否在同一个Region。 选择了自定义镜像后点击创建集群即可完成一个自定义镜像集群的创建。 集群扩容与自动伸缩 使用自定义镜像创建集群后,集群的扩容与自动伸缩中所用的都是自定义镜像。 Terraform 中自定义镜像支持利用Terraform创建容器集群也可以使用自定义镜像,具体参数是: image_id - The ID of node image.相关链接如下: ...

May 16, 2019 · 1 min · jiezi

sublime正则搜索替换修改数据

手动挡经常会改不齐全,发现能写正则还是很方便的。 find> replace调出替换面板也可以快捷键 command+alt+F 这个引用到复杂点的数据里面 真的省事不要太多 方便不要太多 我知道得太迟了。。。。。。 mark一下 仅供参考 欢迎更正补充 end

May 14, 2019 · 1 min · jiezi

5分钟了解JSON那些事儿

JSON简介JSON是JavaScript Object Notation(JavaScript对象表示法)的缩写JSON是一种数据格式, 而不是一种编程语言, 用来表示结构化数据JSON是JavaScript的一个严格子集JSON并不从属于JavaScript, 很多编程语言都可以用JSON数据格式语法JSON语法可以表示以下三种类型的值:简单值: 字符串/数值/布尔值/null, 但是不支持undefined对象数组JSON不支持变量/函数/对象实例简单值字符串"Hello JSON"数字66布尔值truenullnull对象和JavaScript对比来看 ->JavaScript表示对象键值对的键可以加引号也可以不加, 如果加引号, 可以加单引号也可以加双引号// 最常见就这么写const obj1 = { foo: ‘bar’, num: 66, status: true};// 这么写也oconstet obj2 = { ‘foo’: ‘bar’, ’num’: 66, ‘status’: true};// 这么写也okconst obj1 = { “foo”: “bar”, “num”: 66, “status”: true};JSON表示对象键值对的键必须加双引号(手写JSON时一定要注意)对象没有变量声明, 因为JSON根本就没有变量的概念(它不是一个编程语言)末尾没有分号{ “foo”: “bar”, “num”: 66, “status”: true}和JavaScript相似, 对象可以嵌套对象{ “foo”: “bar”, “num”: 66, “status”: true, “baz”: { “num”: 88 }}注, 同名属性可以在不同的对象中, 但是不能出现在同一个对象中数组和JavaScript对比来看 ->JavaScript表示数组let arr = [‘hello’, 66, true];JSON表示数组同样没有变量声明和结尾的分号, 同时注意字符串简单值要加双引号[“hello”, 66, true]数组和对象结合起来可以构成复杂的集合, 比如students.json文件中可能是这样婶儿的[ { “name”: “小明”, “age”: 10, “score”: { “math”: 88, “english”: 99 } }, { “name”: “小强”, “age”: 11, “score”: { “math”: 98, “english”: 96 } }]看到以上同JavaScript的不同之处, 我们可以知道为什么说JSON是JavaScript的一个严格子集了吧JSON序列化与解析基本用法ECMAScript5定义了全局对象JSON, 用来解析JSON字符串简单来说, JSON对象有两个方法JSON.stringify(): 把JavaScript对象序列化为JSON字符串JSON.parse(): 把JSON字符串解析为原生JavaScript值const book = { name: ‘Learn JavaScript in One Day’, pages: 1};const jsonText = JSON.stringify(book); // 序列化// “{“name”:“Learn JavaScript in One Day”,“pages”:1}“const parseText = JSON.parse(jsonText); // 解析// {name: “Learn JavaScript in One Day”, pages: 1}默认情况下, JSON.stringify()输出的JSON字符串不包含任何空格字符或缩进(是不是给我们提供了一种将去除数据中无用的空白和缩进的方法呢).在序列化JavaScript对象时, 所有函数及原型成员都会被有意忽略, 不体现在结果中. 此外, 值为undefined的任何属性也都会被跳过. 结果中最终都是值为有效JSON数据类型的实例属性.const book = { name: ‘Learn JavaScript in One Day’, pages: 1, foo: undefined};const jsonText = JSON.stringify(book); // 序列化// “{“name”:“Learn JavaScript in One Day”,“pages”:1}”// foo的值为undefined, 所被忽略掉了将JSON字符串直接传递给JSON.parse()就可以得到相应的JavaScript值.JSON.parse()就是JSON.stringify()的逆向操作. 将一个JavaScript值序列化之后再解析JSON.parse(JSON.stringify(foo))和原来的foo几乎一样.注意, 为什么说是几乎一样呢?如果foo是一个对象或数组, 那么这么一折腾就变成了两个不同的对象或数组了;这就提供了一种克隆对象和数组的方法????如果foo是一个对象, 并且foo中有值为undefined的属性, 那么在序列化的过程中它会被筛掉;如果foo是一个数组, 并且其中有undefined的项, 那么在序列化过程中undefined的项会变成null;如果foo是一个简单值(数字/布尔值/字符串), 那么这么一折腾结果完全是相等的.const foo1 = { a: 1 };JSON.parse(JSON.stringify(foo1)) === foo1; // falseconst foo2 = [1, 2];JSON.parse(JSON.stringify(foo2)) === foo2; // falseconst foo3 = { a: 1, b: undefined };JSON.parse(JSON.stringify(foo3)); // {a: 1}const foo4 = [1, true, undefined];JSON.parse(JSON.stringify(foo4)); // [1, true, null]const foo5 = true;JSON.parse(JSON.stringify(foo5)) === foo5; // true如果传给JSON.parse()的字符串不是有效的JSON, 该方法会抛出错误.进阶用法序列化选项JSON.stringify()除了要序列化的JavaScript对象外, 还可以接收另外两个参数, 这两个参数用于指定以不同的方式序列化JavaScript对象.第一个参数是个过滤器, 可以是一个数组, 也可以是一个函数;第二个参数是一个选项, 表示是否在JSON字符串中保留缩进.1.过滤结果如果过滤器参数是数组, 那么JSON.stringify()的结果中将只包含数组中列出的属性const book = { name: ‘Learn JavaScript in One Day’, pages: 1};JSON.stringify(book, [’name’]);// “{“name”:“Learn JavaScript in One Day”}“再看下面的例子????:const stu = { name: “小明”, age: 10, score: { math: 88, english: 99 }};JSON.stringify(stu, [’name’, ‘score’]);// “{“name”:“小明”,“score”:{}}“注意, 最后结果中score是一个空对象, 那么小明的成绩哪儿去了?好像我们的目的是要显示name和score属性, 只去掉age就行了, 但是score对象中咋啥也没有了?那我们把stu对象重新改改试试:var stu = { name: “小明”, age: 10, score: { math: 88, english: 99, name: ‘xiuxiu~’, score: 100 }};JSON.stringify(stu, [’name’, ‘score’]);// “{“name”:“小明”,“score”:{“name”:“xiuxiu~”,“score”:100}}“这下看明白了么, 原来这个过滤是对每一层级的对象都过滤一遍.如果第二个参数是函数, 行为会稍有不同. 传入的函数接收两个参数, 属性名(键)和属性值(值). 根据属性名可以知道应该如何处理要序列化的对象中的属性. 属性名只能是字符串, 而在值并非键值对儿结构的值时, 键名可以是空字符串. 函数返回的值就是相应键的值, 如果函数返回了undefined, 那么相应的属性会被忽略.const stu = { name: “小明”, age: 10, score: { math: 88, english: 99 }};const newStu = JSON.stringify(stu, (key, value) => { switch(key) { case ‘math’: return 100; case ’english’: return 100; default: return value; }});// “{“name”:“小明”,“age”:10,“score”:{“math”:100,“english”:100}}”// 成功将小明的成绩改成了100分, 哈哈哈~2.字符串缩进JSON.stringify()方法的第三个参数用于控制结果中的缩进和空白符. 如果这个参数是一个数值, 那它表示的是每个级别缩进的空格数.JSON.stringify()会在结果字符串中插入换行符以提高可读性.最大缩进空格数为10, 所有大于10的值都会自动转换为10.const stu = { name: “小明”, age: 10, score: { math: 88, english: 99 }};JSON.stringify(stu, null, 4); // 4个空格的缩进/* 序列化后的结果”{ “name”: “小明”, “age”: 10, “score”: { “math”: 88, “english”: 99 }}”/如果缩进参数是一个字符串而非数值, 则这个字符串将在JSON字符串中被用作缩进字符(不再使用空格).缩进字符串最长不能超过10个字符长. 如果字符串长度超过了10个, 结果中将只出现前10个字符.JSON.stringify(stu, null, ‘–’);/ 序列化后的结果”{–“name”: “小明”,–“age”: 10,–“score”: {—-“math”: 88,—-“english”: 99–}}”/3.toJSON有时候, JSON.stringify()还是不能满足对某些对象进行自定义序列化的需求. 在这些情况下, 可以给对象定义toJSON()方法, 返回其自身的JSON数据格式. 原生Date对象有一个toJSON()方法,能够将JavaScript的Date对象自动转换成ISO8601日期字符串(与在Date对象上调用toISOString()的结果完全一样).JSON.stringify(new Date());// 序列化后的结果: ““2019-04-08T11:31:05.778Z"“new Date().toJSON();new Date().toISOString();// 直接调用toJSON和toISOString方法同样能得到字符串: “2019-04-08T11:31:44.432Z"可以让toJSON()方法返回任何值, 它都能正常工作.const stu = { name: “小明”, age: 10, score: { math: 88, english: 99 }, toJSON() { return this.name; }};JSON.stringify(stu);// 序列化后的结果: ““小明"“toJSON()可以作为函数过滤器的补充, 因此理解序列化的内部顺序十分重要. 假设把一个对象传入JSON.stringify(), 序列化该对象的顺序如下:(1) 如果存在toJSON()方法而且能通过它取得有效的值, 则调用该方法. 否则, 返回对象本身;(2) 如果提供了第二个参数, 应用这个函数过滤器. 传入函数过滤器的值是第(1)步返回的值;(3) 对第(2)步返回的每个值进行相应的序列化;(4) 如果提供了第三个参数, 执行相应的格式化.解析选项JSON.parse()方法也可以接收另一个参数, 该参数是一个函数, 将在每个键值对儿上调用. 为了区别 JSON.stringify()接收的替换(过滤)函数(replacer), 这个函数被称为还原函数(reviver), 但实际上这两个函数的签名是相同的——它们都接收两个参数, 一个键和一个值, 而且都需要返回一个值.如果还原函数返回undefined, 则表示要从结果中删除相应的键; 如果返回其他值, 则将该值插入到结果中.const stu = { name: “小明”, age: 10, score: { math: 88, english: 99 }};const jsonText = JSON.stringify(stu);JSON.parse(jsonText, (key, value) => { if (key === ’name’) { return value + ‘牛逼了!’; } else { return value; }});/ 解析结果{ age: 10 name: “小明牛逼了!” score: { math: 88, english: 99 }}*/小结JSON是一个轻量级的数据格式, 可以简化表示复杂数据结构的工作量. JSON使用JavaScript语法的子集表示对象、数组、字符串、数值、布尔值和null.ECMAScript5定义了一个原生的JSON对象, 可以用来将对象序列化为JSON字符串或者将JSON数据解析为JavaScript对象. JSON.stringify()和JSON.parse()方法分别用来实现上述两项功能. 这两个方法都有一些选项, 通过它们可以改变过滤的方式, 或者改变序列化的过程. ...

April 8, 2019 · 3 min · jiezi

php 使用Curl传递json资料给对方及显示对方回传的json(Json格式/ API串接/ HttpRequest)

本教学使用环境介绍伺服器端:Ubuntu 18.04 LTS资料库:Mariadb 10.1.34(Mysql)语言版本:php 7.3本机端:MacOS High Sierrafunction httpRequest($api, $data_string) { $ch = curl_init($api); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, “POST”); curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, array( ‘Content-Type: application/json’, ‘Content-Length: ’ . strlen($data_string)) ); $result = curl_exec($ch); curl_close($ch); return json_decode($result);}将以下资料变成json格式传输传给对方接应的 <https-api-url>$data = array( “id” => $id, “field” => $field);$data = httpRequest(’<https-api-url>’, json_encode($data));要印出对方回的 json key and value 内容时echo $data->{‘message’};如果对方回的是json array,使用foreach接应即可就能够印出回圈,对方回传多少笔就印多少笔foreach ($data as $value) { echo $value[‘message’];}可以使用sizeof查看object的长度,轻松做判断echo sizeof($data); // int如果对方回的不是json只是直接传 body 过来将上面的function中的return json_decode($result);改为return $result;然后直接印出即可echo $data;Line ID:ianmacQQ:1258554508 ...

April 2, 2019 · 1 min · jiezi

前端、后端、运维都能用的动态json数据管理神器,节省你大量的开发、设计数据库、运维的时间

基于Json Schema的动态数据管理神器-DMS介绍什么是DMS?DMS Github:基于Json Schema/UI Schema模块化的Json动态数据管理平台。什么是Json Schema/UI Schema?用于动态生成表单的Schema,参考 Json Schema使用案例官方文档使用场景有哪些?无论前端、后端、移动端、运维,理论上所有需要动态配置数据的场景都可以使用。针对前端、移动端:可以配置页面每个模块展示型数据,也可以配置各种版本号用于动态更新,各种功能开关、页面主题等。针对后端:可以配置业务相关的ID,配置类目,城市列表,热门等。针对运维:可以作为区分环境的配置中心等。当然使用场景远不止这些……可以运用到生产环境吗?当然可以,DMS存储的数据读写是完全分开的,目前支持通过Redis、CDN(推荐)两种获取数据方式。即使DMS自身服务器挂掉,也不会影响数据的读取。强烈推荐使用CDN的方式,这样稳定性和使用的CDN是一样的。DMS应用、模块、参数介绍应用:包含一个或多个模块,包含一个或多个参数模块:配置数据的最小单位参数:使模块根据不同参数配置不同数据(如:每个城市展示的频道页不一样)DMS特性实时表单预览;模块化(组件化)数据管理;支持表单数据逻辑判断、数据验证;Schema数据自动保存,防止误操作及未知异常;支持动态增加参数,参数本身也可以为DMS生成的配置数据;配合dms-upload可以快速将通过表单上传的文件传入CDN/云存储符合实际场景的权限控制:开发只负责schema编写,需求方配置所有数据;支持Schema生成所有基本表单类型及高级控件,如:日期选择器、进度条、密码框、颜色选择器等;实时数据预览/审核(配合dms-fetch,同时支持服务端代理请求,及浏览器端请求的数据预览与审核)快速开始请先确保已经安装好:nodejs8+、mysql、redis,并已开启相关服务安装DMS> git clone https://github.com/win-winFE/dms.git> yarn # 若没有yarn,请使用 npm install创建日志目录> mkdir /opt/logs/nodejs -p执行初始化sql使用mysql执行 dms/database/dms.sql修改项目中mysql/redis相关配置dms/config/config.default.js(mysql默认密码为:root1234)启动/停止/调试启动端口默认为:7101,需要修改请修改dms/package.json文件start部分的7101> yarn start # 启动,若没有yarn,请使用 npm run start> yarn stop # 停止, npm run stop> yarn dev # 调试,npm run dev注册进入:http://localhost:7101,将自动跳转到登录页,选择【注册】,按要求填写相关数据,注册成功将自动跳转到【应用管理】页面新建示例应用点击【新建应用】,新建如下应用新建示例模块点击“淘宝首页”的【模块列表】,点击【新建模块】编写该模块Schema点击“首页banner”的【编辑Schema定义】,复制如下Schema到【Schema定义】中并【保存Schema】{ “title”: “示例”, “description”: “视频/图片展示配置示例”, “type”: “array”, “minItems”: 3, “items”: { “type”: “object”, “properties”: { “url”: { “title”: “跳转链接”, “type”: “string” }, “imgs”: { “title”: “轮播图片”, “type”: “string”, “format”: “file” } } }}添加一个参数进入【参数列表】,添加如下参数【编辑参数】,【提交】如下参数编辑数据点击左侧菜单,进入【数据管理】,进入“淘宝首页”应用的【模块列表】,选择城市后点击【进入】,再选择“首页banner”的【编辑模块数据】,此时还不能上传图片、保存数据,需要启用dms-upload启动dms-upload> git clone https://github.com/win-winFE/dms-upload.git> yarn # npm install执行初始化sql使用mysql执行 dms-upload/database/dms-upload.sql使用mysql执行 dms-upload/database/init.sql(用于上传时的权限验证,默认:root root1234)修改项目中mysql/redis相关配置dms/config/config.default.js(mysql默认密码为:root1234)配置dms-upload启动端口(默认7100):dms-upload/package.json start部分,若修改端口。请修改 dms/app/util/constants.js dmsUploadAPI 中的请求地址前缀数据库配置:dms-upload/config/config.defult.jsCDN文件保存目录(默认/usr/local/services/cdn/dms):dms-upload/config/config.defult.js cdnDirCDN文件访问地址前缀(默认//127.0.0.1:5000/dms):dms-upload/config/config.defult.js cdnPrefix新建CDN文件(图片、json数据)保存目录> mkdir /usr/local/services/cdn/dms/data -p # 若未使用默认cdnDir,请修改data前面部分> mkdir /usr/local/services/cdn/dms/res -p # 若未使用默认cdnDir,请修改res前面部分启动dms-upload> yarn start # npm run start本地调试上传图片回显> cd /usr/local/services/cdn> python -m SimpleHTTPServer 5000 # python3 请使用: python3 -m http.server 5000继续回到DMS平台编辑数据提交下列数据直接访问数据(用于非js使用场景)临时数据:提交后复制成功Toast中的链接,可以直接访问临时数据数据正式数据:将临时数据审核为正式数据,也可以通过Toast中的链接直接访问正式数据使用dms-fetch访问数据(用于js使用场景)1.项目中安装dms-fetch(不建议,强依赖axios,说明见Github)> yarn add dms-fetch # npm install dms-fetch –save2.带参数使用示例(伪代码)import { getDMSDataByCDN } from ‘dms-fetch’;import …// 复制编辑数据页面的唯一标示,下面是React应用配合使用DMS参数的示例export default class extends React.Component { … fetchData = async () => { const { city } = getParams(this.props.location.search); const dmsData = await getDMSDataByCDN(/7/10/city/${city}, this.props.location.search); this.setState({ dmsData, }); }; … render() { … }}更多高级用法请参考 DMS Github ...

March 29, 2019 · 1 min · jiezi

让前端小姐姐愉快地开发表单

前端小姐姐:“新业务功能又有大量的表单要开发了,有没让我又高效又愉快地完成这个任务的方案呢?”哦,我想想,配置开发理念,应该比较适合,且社区也有好些这种理念的开源项目前端小姐姐:“什么叫配置开发理念呢?”就是只需要简单地定义一份JSON配置数据来开发表单前端小姐姐:“太棒了,我去search下先哈”过了一会~~前端小姐姐:“是真有不少,但我选哪个好呢?”恩,那我们就来聊聊配置理念表单开发的选型吧1. 帮助文档这是必不可少的啦,没有文档,谁敢用呢2. 直观特性展示我可不想一开始就要花大量时间去读冷冰冰的文字,然后还要发挥自己的想像力最好有超直观超形象超方便的方式展示大部分甚至全部的特性,可能是这样的:3. 开箱即用最好官方自带一整套常用的表单控件和校验规则,满足大部分常见场景,就不用去自行扩展太多了比如以下的组件就经常用到了:比如以下的校验规则就经常用到了:4. 扩展能力官方就是提供再多的组件和校验规则,也是无法满足所有的业务场景的,所以必须 友好(简单方便) 支持开发者自定义自己的表单组件和校验规则5. 支持复杂数据结构一个表单数据结构,除了简单的只有一级属性的对象类型外(如 {name: ‘daniel’, age: 18}),实际很多场景可能是这样的:{ “name”: { “firstName”: “daniel”, “lastname”: “xiao” }}可能是这样的:{ “name”: “daniel”, “hobbies”: [ { “id”: 1, “name”: “Coding” }, { “id”: 2, “name”: “Singing” } ]}一句话总结就是:支持数组类型,对象嵌套对象,对象嵌套数组,数组的项是普通类型 或 对象类型 或 数组类型6. 表单控件间交互我想说,一个表单,不是把表单控件按位置静静放在那就行了,控件之间并不独立,控件之间是有交互的这里列举下具有代表性的一些场景:“同意才能继续” 类型“城市选择器” 类型“大于18岁必填” 类型“日期比较” 类型“全名自动填写” 类型感觉已经挺多了,就不一一列举了。所以,前端小姐姐,如果候选开源项目能够满足以上所提的条件,那就可以用了前端小姐姐:“哦,那你有推荐吗?”当然。。。哈哈,接下来就是广告时间了,如果各位看官们觉得以上的选型条件合情合理,那 ncform 就是一个不错的选择了ncform,一种令人愉悦的表单开发方式,仅需配置即可生成表单UI及其交互行为。自带丰富的 标准组件 和 校验规则,开箱即用。具备强大的 控件交互 和 扩展能力,做你所想。访问官方Github了解更多呗:https://github.com/ncform/ncformtags: vue, form, json-schema, generator

March 26, 2019 · 1 min · jiezi

关于JSON.parse()和JSON.stringify()的性能小测试

JSON.parse(JSON.stringify(obj))我们一般用来深拷贝,其过程说白了,就是利用 JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象。至于这行代码为什么能实现深拷贝,以及它有什么局限性等等,不是本文要介绍的。本文要探究的是,这行代码的执行效率如何?如果随意使用会不会造成一些问题?先上两个js性能测试的依赖函数/** * 一个简单的断言函数 * @param value {Boolean} 断言条件 * @param desc {String} 一个消息 /function assert(value, desc) { let li = document.createElement(’li’); li.className = value ? ‘pass’ : ‘fail’; li.appendChild(document.createTextNode(desc)); document.getElementById(‘results’).appendChild(li);}/* * 一个测试套件,定时器是为了多次执行减少误差 * @param fn {Function} 需要多次执行的代码块(需要测试、比对性能的代码块) * @param config {Object} 配置项,maxCount: 执行代码块的for循环次数,times: 定时器执行次数 /function intervalTest(fn, config = {}) { let maxCount = config.maxCount || 1000; let times = config.times || 10; let timeArr = []; let timer = setInterval(function () { let start = new Date().getTime(); for (let i = 0; i < maxCount; i++) { fn.call(this); } let elapsed = new Date().getTime() - start; assert(true, ‘Measured time: ’ + elapsed + ’ ms’); timeArr.push(elapsed); if (timeArr.length === times) { clearInterval(timer); let average = timeArr.reduce((p, c) => p + c) / times; let p = document.createElement(‘p’); p.innerHTML = for循环:${maxCount}次,定时器执行:${times}次,平均值:${average} ms; document.body.appendChild(p); } }, 1000);}定义一些初始数据let jsonData = { title: ‘hhhhh’, dateArr: [], series: [ { name: ’line1’, data: [] }, { name: ’line2’, data: [] }, { name: ’line3’, data: [] }, ]};let res = [ { name: ’line1’, value: 1 }, { name: ’line2’, value: 2 }, { name: ’line3’, value: 3 },];场景1:模拟真实环境中图表数据实时更新数据处理函数/* * 处理json数据的函数。模拟真实环境中图表数据实时更新 * @param lastData {Object} 上一次的数据 * @param res {Array} 当前数据 * @returns data 处理完成后的结果集 */function handleJsonData(lastData, res) { // 1. 使用 JSON.parse(JSON.stringify()) 深拷贝 let data = JSON.parse(JSON.stringify(lastData)); // 2. 不使用JSON序列化,直接修改参数 // let data = lastData; if (data.dateArr.length > 60) { data.dateArr.shift(); for (let i = 0; i < data.series.length; i++) { data.series[i].data.shift(); } } data.dateArr.push(new Date().toTimeString().substr(0, 8)); for (let i = 0; i < data.series.length; i++) { data.series[i].data.push(res[i].value); } return data;}maxCount=100跑起来,先让maxCount=100,for循环100次let jsonTest = function () { jsonData = handleJsonData(jsonData, res);};intervalTest(jsonTest, {maxCount: 100});1.使用 JSON.parse(JSON.stringify()) 深拷贝 的结果:2.不使用JSON序列化,直接修改参数 的结果:function handleJsonData(lastData, res) { // 1. 使用 JSON.parse(JSON.stringify()) 深拷贝 // let data = JSON.parse(JSON.stringify(lastData)); // 2. 不使用JSON序列化,直接修改参数 let data = lastData; // …}maxCount=1000intervalTest(jsonTest, {maxCount: 1000});1.使用 JSON.parse(JSON.stringify()) 深拷贝 的结果:2.不使用JSON序列化,直接修改参数 的结果:maxCount=10000intervalTest(jsonTest, {maxCount: 10000});1.使用 JSON.parse(JSON.stringify()) 深拷贝 的结果:2.不使用JSON序列化,直接修改参数 的结果:场景2:判断一个对象是否为空对象// 1. 使用 JSON.stringify() 判断一个对象是否为空对象let isEmptyObject1 = function () { if (JSON.stringify(jsonData) === ‘{}’) { // do something }};// 2. 使用 Object.keys().length 判断一个对象是否为空对象let isEmptyObject2 = function () { if (Object.keys(jsonData).length === 0) { // do something }};只是走了一下判断条件,if内部没有执行代码maxCount=10001.使用 JSON.stringify() 判断一个对象是否为空对象 的结果:intervalTest(isEmptyObject1, {maxCount: 1000});2.使用 Object.keys().length 判断一个对象是否为空对象 的结果:intervalTest(isEmptyObject2, {maxCount: 1000});maxCount=100001.使用 JSON.stringify() 判断一个对象是否为空对象 的结果:2.使用 Object.keys().length 判断一个对象是否为空对象 的结果:maxCount=1000001.使用 JSON.stringify() 判断一个对象是否为空对象 的结果:2.使用 Object.keys().length 判断一个对象是否为空对象 的结果:关于JSON.parse()和JSON.stringify()的测试先到此为止,变换参数、更改执行的代码块可能会有不同结果,以上结果仅供参考。小结论:能不用JSON.parse()和JSON.stringify()就不用,采用替代方案且性能更优的。PS:特别是需要多次执行的代码块,特别是这个JSON数据比较庞大时还有更多关于JSON.parse()和JSON.stringify()的知识等着大家去挖掘,欢迎大家一起交流学习~关于JSON.parse(JSON.stringify(obj)):关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑关于深拷贝:深拷贝的终极探索(99%的人都不知道) ...

March 14, 2019 · 2 min · jiezi

biaoti

unit_list: [{ id: “2”, //目录ID name: “目录名称1”, //目录名称 seq: 1, //目录的顺序 chapter_list: [{ id: 1259, name: “大哭大哭大哭大哭”, learn_method:1, //课堂模式 1.现场视频直播+讲义;2.录像视频直播+讲义;3.现场视频直播,无讲义;4.录像视频直播,无讲义;5.现场音频直播+讲义;6.双视频直播;7.视频点播;8.音频点播; 9考试; video_num: 0, live_status: 0, start_up_status: 0, resource_count: 1, //资料数 exam_count: 1, //练习数 handout_count: 1, //讲义数 status_mean: “未直播”, start_time: “12444444”, end_time: “12444499”, class_teacher_list: [{ teacher_name: “大灯”, teacher_uid: 30209136 }], allow_test: 0, //是否允许试听,1 允许 0 不允许 attachment_list: [{ //附件列表 type: “test”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:’-1’ }], },{ //附件列表 type: “handout”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘0’ }], }, { //附件列表 type: “video”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘http://cdn.xnwimg.com/down/f:{5FBD356B-4CA0-21EF-5CE9-CBE3A7710DD2}/ct:0/网络爬虫项目实训:看我如何下载韩寒博客文章Python视频 02.mp4’ }], },{ //附件列表 type: “handout”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘123’ }], }], is_delete: true, is_modify: true },{ id: 1259, name: “大哭大哭大哭大哭2”, learn_method:1, //课堂模式 1.现场视频直播+讲义;2.录像视频直播+讲义;3.现场视频直播,无讲义;4.录像视频直播,无讲义;5.现场音频直播+讲义;6.双视频直播;7.视频点播;8.音频点播; 9考试; video_num: 0, live_status: 0, start_up_status: 0, resource_count: 1, //资料数 exam_count: 1, //练习数 handout_count: 1, //讲义数 status_mean: “未直播”, start_time: “12444444”, end_time: “12444499”, class_teacher_list: [{ teacher_name: “大灯”, teacher_uid: 30209136 }], allow_test: 0, //是否允许试听,1 允许 0 不允许 attachment_list: [{ //附件列表 type: “test”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:’-1’ }], },{ //附件列表 type: “handout”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘0’ }], }, { //附件列表 type: “video”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘http://cdn.xnwimg.com/down/f:{5FBD356B-4CA0-21EF-5CE9-CBE3A7710DD2}/ct:0/网络爬虫项目实训:看我如何下载韩寒博客文章Python视频 02.mp4’ }], }], is_delete: true, is_modify: true }, { id: 1254, name: “大哭大哭大哭大哭3”, learn_method:1, //课堂模式 1.现场视频直播+讲义;2.录像视频直播+讲义;3.现场视频直播,无讲义;4.录像视频直播,无讲义;5.现场音频直播+讲义;6.双视频直播;7.视频点播;8.音频点播; 9考试; video_num: 0, live_status: 0, start_up_status: 0, resource_count: 1, //资料数 exam_count: 1, //练习数 handout_count: 1, //讲义数 status_mean: “未直播”, start_time: “12444444”, end_time: “12444499”, class_teacher_list: [{ teacher_name: “大灯”, teacher_uid: 30209136 }], allow_test: 0, //是否允许试听,1 允许 0 不允许 attachment_list: [{ //附件列表 type: “video”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘http://cdn.xnwimg.com/down/f:%7B5B6C7C72-39CF-7043-5742-F3879F6DAA41%7D/%E9%BB%84%E7%B2%A4%E6%80%9D%20-%20%E6%83%85%E6%AD%8C%E7%8E%8B%20%28%E5%A5%B3%E7%89%88%29.mp3.mp4’ }], }], is_delete: true, is_modify: true }] }, { id: “3”, //目录ID name: “目录名称2”, //目录名称 seq: 2, //目录的顺序 chapter_list: [{ id: 1259, name: “大哭大哭大哭大哭”, learn_method:1, //课堂模式 1.现场视频直播+讲义;2.录像视频直播+讲义;3.现场视频直播,无讲义;4.录像视频直播,无讲义;5.现场音频直播+讲义;6.双视频直播;7.视频点播;8.音频点播; 9考试; video_num: 0, live_status: 0, start_up_status: 0, resource_count: 1, //资料数 exam_count: 1, //练习数 handout_count: 1, //讲义数 status_mean: “未直播”, start_time: “12444444”, end_time: “12444499”, class_teacher_list: [{ teacher_name: “大灯”, teacher_uid: 30209136 }], allow_test: 0, //是否允许试听,1 允许 0 不允许 attachment_list: [{ //附件列表 type: “resource”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘123’ }], },{ //附件列表 type: “test”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘1212’ }], }], is_delete: true, is_modify: true }, { id: 1259, name: “大哭大哭大哭大哭4”, learn_method:1, //课堂模式 1.现场视频直播+讲义;2.录像视频直播+讲义;3.现场视频直播,无讲义;4.录像视频直播,无讲义;5.现场音频直播+讲义;6.双视频直播;7.视频点播;8.音频点播; 9考试; video_num: 0, live_status: 0, start_up_status: 0, resource_count: 1, //资料数 exam_count: 1, //练习数 handout_count: 1, //讲义数 status_mean: “未直播”, start_time: “12444444”, end_time: “12444499”, class_teacher_list: [{ teacher_name: “大灯”, teacher_uid: 30209136 }], allow_test: 0, //是否允许试听,1 允许 0 不允许 attachment_list: [{ //附件列表 type: “audio”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 data_list: [{ fileid: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, filename: “\u4f3c\u4e4e\u662f\u73a9\u7b11”, file_size: 12, name: “\u6570\u5b66\u8003\u8bd5.sql”, s_thumb: “{EE3F3CF3-C6B6-4A22-2B94-7E99B3205570}”, seq: 1, paper_id: 122, answer_time: 20, time_type: 1, duration: 300000, url:‘http://cdn.xnwimg.com/down/f:{5FBD356B-4CA0-21EF-5CE9-CBE3A7710DD2}/ct:0/网络爬虫项目实训:看我如何下载韩寒博客文章Python视频 02.mp4’ }], }], is_delete: true, is_modify: true }] }],======================================================================== attachmentList:[ [ { type: “–”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 url:’’ }, { type: “–”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 url:’’ }, { type: “播放”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 url:‘2’ } ], [ { type: “播放”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 url:‘3’ }, { type: “–”, //resource:资料;handout:讲义;test:练习;video:视频;audio:音频;exam:试卷 url:’’ } ] ], ...

March 1, 2019 · 3 min · jiezi

Jackson 序列化对象成 JSON 字符串,忽略部分字段(属性)

1、属性上 加 @JsonIgnore这种方式作用于全局,只要是有这个对象的序列化,就会忽略注解过的这部分字段。2、上面那种方式需要在 bean 上加注解,作用于全局,但是有的时候,我们可能不需要在所有情况下都忽略这个对象的这些字段,下面这种方式可以支持定制过滤方式。public final class JsonFilterUtil { /** * 添加过滤的字段,这里过滤的是 ThinActivityInfo 这个 bean 下的 * “startAt”, “expiredAt”, “extra” 三个字段 / public static void addFilterForMapper(ObjectMapper mapper) { SimpleBeanPropertyFilter fieldFilter = SimpleBeanPropertyFilter.serializeAllExcept( Sets.newHashSet(“startAt”, “expiredAt”, “extra”)); SimpleFilterProvider filterProvider = new SimpleFilterProvider().addFilter(“fieldFilter”, fieldFilter); mapper.setFilterProvider(filterProvider).addMixIn(ThinActivityInfo.class, FieldFilterMixIn.class); } /* * 定义一个类或接口 */ @JsonFilter(“fieldFilter”) interface FieldFilterMixIn{ }}原文链接http://zhige.me/2019/02/28/20…

February 28, 2019 · 1 min · jiezi

技本功丨收藏!斜杠青年与你共探微信小程序云开发(上篇)

人设千万种,“高危”的大概有两种:好老公/老婆 & 学霸。猪年第一瓜,演艺事业一帆风顺的翟XX,栽在了学霸的人设上,这件事深刻地教育了我们:1、学习这件事情来不得一点虚假2、学无止境,空杯心态So,忘掉翟XX吧!让我们好好学习,跟着#技本功#,学点真材实料的东西~-2019年第7期-前 言1、云开发是什么?云开发是小程序为开发者提供的完整的云端支持。2、云开发能做什么?开发者可以使用云开发进行数据持久化(数据库),保存文件(存储)以及带有天然鉴权特性的云函数。3、怎么使用云开发?当然是先看微信的官方文档啦!查看地址:https://dwz.cn/ioeWuJ9a云开发共提供了三种能力,分别是:云函数:在云端运行的代码,微信私有协议天然鉴权,小程序随时调用。数据库:云端JSON数据库,小程序前端和云函数都可以进行操作。存储:在小程序前端直接上传/下载文件。要开始使用云开发,需要先开通云开发环境,每个小程序账号可以免费创建两个环境,目前小程序(2018.11.09)还没有提供删除环境的功能,所以建议先创建一个dev环境进行日常的开发和测试。云开发环境目前是免费使用,且只有一种套餐,其中数据库配额如下:容量:2GBQPS:30同时连接数:20读操作次数:5万/天写操作次数:3万/天集合限制:100个单集合索引限制:10个以上的配额满足了一般个人开发者使用并且目前是免费的,如果资源不够的可以联系微信团队进行上调。具体看这里:https://dwz.cn/vWxrz5Vr本文使用云开发的数据库特性来写一个“吃什么”,该小程序具有下面几个功能点:1、添加页面:增加新菜名2、查看所有数据页面:查看菜单并可以删除菜品类目3、首页:随机抽出一条数据本文分为两部分:第1部分主要带大家了解云开发的基本步骤和配置,从环境搭建到完成简单的菜品增加页面;第2部分带大家了解云开发详细使用和不足,从首页到删除页面。下面我们开始“吃什么”小程序的前期准备工作。Step1:创建云开发环境1、在微信开发者工具中点击云开发按钮打开云开发控制台,新建环境(目前一个账号仅能创建两个环境,环境之间相互隔离且目前不能删除2018.10.23,建议先创建一个dev测试环境)。开发控制台在开发者工具中的入口2、点击数据库创建一个集合collection,(可把集合看成’表’)。开发控制台-数据库tab有了这两个,我们就可以进行基本的数据库操作了。为了让每个用户都可以看到彼此录入的菜名,我们这里设置集合的权限为所有人都可读。Step2:搭建文件结构在根文件夹下面新建文件夹images、pages、style、unilt四个文件夹。完整的结构如下图:结构图其中app.js是微信生成的一个初始化js文件,app.json用于配置小程序路由等信息,app.wxss为全局css文件。project.config.json用于配置项目信息,包括根目录、云函数目录、项目设置以及项目名称和appid等基础信息。我们页面代码写在pages文件下,目前有新增add与首页index两个页面,每个页面包含 wxml、wxss和一个js文件。wxml写页面结构,wxss写样式,js作为控制。Step3:初始化数据库连接为了方便所有页面引用数据库对象且目前本小程序不需要操作额外的集合,所以在app.js里我们在程序加载的时候初始化好数据库连接。微信官方提供的示例是使用wx.cloud对象的init方法,接受一个json对象,json里填入我们环境id即可完成初始化。env字段填写自己云开发环境的环境id,在云开发控制台-概览可以找到。traceUser选项表明是否追踪用户,开启的时候我们可以在控制台查看访问数据库的用户信息。初始化完成后我们连接到我们的数据库,并创建一个全局的TB对象指向对food集合的引用。然后将数据库引用和集合引用赋给全局数据的DB和TB。这样一来我们就可以在所有js里使用数据库连接了,下面我们进行ajax的编写。Step4:编写ajax方法为了方便所有页面使用,我们将基本的增删查功能单独为一个文件,这样在页面的js里直接引用就可以了。通用的ajax方法就是常见的增删改查。说是ajax,其实只是调用微信的云开发接口,不需要我们手动编写ajax请求。常用的云开发数据库api有获取get、查询where、删除remove、获取数量count、更新update以及分页时使用的skip和limit函数。我们在unitl文件夹下面新建一个ajax.js文件。我们根据目前情况先编写一个获取所有菜单的方法和一个添加新菜名的方法。目前微信云开发的数据库get方法只能每次最多20条数据。如果我们想获取集合中的所有数据,就要联合使用count、skip和limit函数进行递归获取。count函数:返回指定条件下(where)的内容数量。skip函数:接受一个int类型的参数n,代表第n个结果后开始返回。limit函数:接受一个int类型参数m,代表每次获取m个item。首先我们创建一个根据条件、页码和每页大小获取数据的方法:这里我们使用了全局变量TB,使用skip、limit和get方法实现分页获取数据。我们这里创建一个flag_pop的函数用于递归,通过page变量决定是否递归,page是通过count函数和size进行计算而来的页数,向前进一确保数据不会遗漏。下面我们来写增加新数据方法,增加数据使用add方法即可,add方法接受一个对象数据,里面有data(要保存的数据)、success(成功后的执行函数)、error(失败后的执行函数)。代码如下:Step5:编写增加页面在add文件夹下面增加相应文件:下面编写增加页面,目前写一个输入框和一个保存按钮即可。输入框绑定handleInput方法获取输入,保存调用handleSave方法即可。完成后页面是这样的:下面我们编写add.js文件,包含一个输入变量、handleInput函数和handleSave函数。我们在handleSave函数中直接调用ajax.js中的addFood函数进行数据保存:下面我们编写add.wxss文件进行页面美化,请自己斟酌,笔者美化后增加页面长这样的:下面我们进行测试,随意输入一下点击保存。提示保存成功后查看云开发控制台验证是否保存成功。总 结 添加页面编写好了以后我们就完成了这个小程序的前期工作了。云开发使用起来非常方便,添加一条记录使用add方法即可,可以看到云开发的api设计的非常易于使用,我们将在第二部分使用其他的的云开发api进行首页和删除功能的编写。

February 22, 2019 · 1 min · jiezi

基于Spring Security Role过滤Jackson JSON输出内容

在本文中,我们将展示如何根据Spring Security中定义的用户角色过滤JSON序列化输出。为什么我们需要过滤?让我们考虑一个简单但常见的用例,我们有一个Web应用程序,为不同角色的用户提供服务。例如,这些角色为User和Admin。首先,让我们定义一个要求,即Admin可以完全访问通过公共REST API公开的对象的内部状态。相反,User用户应该只看到一组预定义的对象属性。我们将使用Spring Security框架来防止对Web应用程序资源的未授权访问。让我们定义一个对象,我们将在API中作为REST响应返回数据:class Item { private int id; private String name; private String ownerName; // getters}当然,我们可以为应用程序中的每个角色定义一个单独的数据传输对象类。但是,这种方法会为我们的代码库引入无用的重复或复杂的类层次结构。另一方面,我们可以使用Jackson库的JSON View功能。正如我们将在下一节中看到的那样,它使得自定义JSON表示就像在字段上添加注释一样简单。@JsonView注释Jackson库支持通过使用@JsonView注解标记我们想要包含在JSON表示中的字段来定义多个序列化/反序列化上下文。此注解具有Class类型的必需参数,用于区分上下文。使用@JsonView在我们的类中标记字段时,我们应该记住,默认情况下,序列化上下文包括未明确标记为视图一部分的所有属性。为了覆盖此行为,我们可以禁用DEFAULT_VIEW_INCLUSION映射器功能。首先,让我们定义一个带有一些内部类的View类,我们将它们用作@JsonView注解的参数:class View { public static class User {} public static class Admin extends User {}}接下来,我们将@JsonView注解添加到我们的类中,使ownerName只能访问admin角色:@JsonView(View.User.class)private int id;@JsonView(View.User.class)private String name;@JsonView(View.Admin.class)private String ownerName;如何将@JsonView注解与Spring Security 集成现在,让我们添加一个包含所有角色及其名称的枚举。之后,让我们介绍JSONView和安全角色之间的映射:enum Role { ROLE_USER, ROLE_ADMIN} class View { public static final Map<Role, Class> MAPPING = new HashMap<>(); static { MAPPING.put(Role.ADMIN, Admin.class); MAPPING.put(Role.USER, User.class); } //…}最后,我们来到了整合的中心点。为了绑定JSONView和Spring Security角色,我们需要定义适用于我们应用程序中所有控制器方法的控制器。到目前为止,我们唯一需要做的就是覆盖AbstractMappingJacksonResponseBodyAdvice类的 beforeBodyWriteInternal方法:@RestControllerAdviceclass SecurityJsonViewControllerAdvice extends AbstractMappingJacksonResponseBodyAdvice { @Override protected void beforeBodyWriteInternal( MappingJacksonValue bodyContainer, MediaType contentType, MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { if (SecurityContextHolder.getContext().getAuthentication() != null && SecurityContextHolder.getContext().getAuthentication().getAuthorities() != null) { Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities(); List<Class> jsonViews = authorities.stream() .map(GrantedAuthority::getAuthority) .map(AppConfig.Role::valueOf) .map(View.MAPPING::get) .collect(Collectors.toList()); if (jsonViews.size() == 1) { bodyContainer.setSerializationView(jsonViews.get(0)); return; } throw new IllegalArgumentException(“Ambiguous @JsonView declaration for roles " + authorities.stream() .map(GrantedAuthority::getAuthority).collect(Collectors.joining(”,"))); } }}这样,我们的应用程序的每个响应都将通过这个路由,它将根据我们定义的角色映射找到合适的返回结果。请注意,此方法要求我们在处理具有多个角色的用户时要小心。 ...

February 15, 2019 · 1 min · jiezi

json to graphql schema: json2graphql

json2graphqljson2graphql 是一个根据 json 生成 GraphQL Schema 的工具。可在 https://luojilab.github.io/js… 在线体验其功能。关于 GraphQLGraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。由于其强类型,返回结果可定制,自带聚合功能等特性,由 facebook 开源后,被 github 等各大厂广泛使用。核心概念:TypeFieldQuery/MutationArgumentsInputVariablesAliases更多请参考 https://graphql.cn/为什么选用 GraphQL相比 REST API, GraphQL 提供了更高的灵活性。接口调用方能够精确的定义其所需数据,并通知服务方只返回这部分数据,该功能是 REST API 无法提供的。GraphQL 能够使客户端只进行一次接口调用,即可获取多个 REST API 请求返回的数据。这种数据聚合的能力,正是我们所需要的。json protobuf 与 GraphQL由于 protobuf 和 GraphQL 都是强类型的,所以可以直接从 protobuf 的 schema 生成 GraphQL Schema,因而才能有自动聚合 grpc 服务生成 GraphQL 接口的框架 rejoiner。但同样的方法不适用于 json,因为标准的 json 并不包含 schema,单纯根据 json 文件无法确定知道每个字段的类型(因为有空值,以及嵌套的情况)。因而目前无法实现类似 rejoiner for json 这样的全自动框架。我们虽不能生成最终的 GraphQL Schema,但是基于对 json 的解析和一些约定,我们可以生成一个 GraphQL Schema 的草稿,生成 Schema 的绝大部分内容,并将有疑问的地方标记出来。json2graphql 就是一个用 golang 实现的 json 生成 schema 的工具。如果你不熟悉 golang,可以使用其在线版本 https://luojilab.github.io/js…在从 REST API 迁移到 GraphQL 的过程中,我们有很多接口会返回大量字段(几十个),如果完全手动编写这些 Schema,将是非常痛苦的,我们开发 json2graphql 的初衷就是解决这个问题,大大缩短开发时间。以下介绍该工具用法。Usagego run main.go -hNAME: inspect - generate a graphql schema based on jsonUSAGE: main [global options] command [command options] [arguments…]DESCRIPTION: inspect json and generate draft schema.graphqlCOMMANDS: inspect generate a graphql schema based on json help, h Shows a list of commands or help for one commandGLOBAL OPTIONS: –verbose, -v show logs –input value, -i value the json filename –output value, -o value the target filename to store generated schema –help, -h show helpExamplego run main.go -i example.jsonLive Demohttps://luojilab.github.io/js…TODO[x] build it as a web service that render schema on the fly like json.cn[ ] support to read from multi json files.[ ] get input from http request rather than local file.[ ] integrate with graphql server frameworks like gqlgen and auto generate resolver ...

February 2, 2019 · 1 min · jiezi

【Go】优雅的读取http请求或响应的数据-续

原文链接:https://blog.thinkeridea.com/…之前发布 【Go】优雅的读取http请求或响应的数据 文章,网友 “wxe” 咨询:“优化前后的请求耗时变化有多大”,之前只分析了内存分配,这篇文章用单元测试的方式分析优化前后的耗时情况,本文源码。非常感谢 “wxe” 网友的提问,让我在测试过程中发现一个 json 序列化的问题。之前我们优化了两个部分,json 与 ioutil.ReadAll, 先对比 ioutil.ReadAll, 这里测试的代码分成两个部分做对比,一部分单纯对比 ioutil.ReadAll 和 io.Copy + sync.Pool,另一部分增加 jsoniter.Unmarshal 来延迟 pool.Put(buffer) 的执行, 源码。package iouitl_readallimport ( “bytes” “io” “io/ioutil” “sync” jsoniter “github.com/json-iterator/go”)var pool = sync.Pool{ New: func() interface{} { return bytes.NewBuffer(make([]byte, 4096)) },}func IoCopyAndJson(r io.Reader) error { buffer := pool.Get().(*bytes.Buffer) buffer.Reset() defer pool.Put(buffer) res := Do(r) _, err := io.Copy(buffer, res) if err != nil { return err } m := map[string]string{} err = jsoniter.Unmarshal(buffer.Bytes(), &m) return err}func IouitlReadAllAndJson(r io.Reader) error { res := Do(r) data, err := ioutil.ReadAll(res) if err != nil { return err } m := map[string]string{} err = jsoniter.Unmarshal(data, &m) return err}func IoCopy(r io.Reader) error { buffer := pool.Get().(*bytes.Buffer) buffer.Reset() defer pool.Put(buffer) res := Do(r) _, err := io.Copy(buffer, res) if err != nil { return err } return err}func IouitlReadAll(r io.Reader) error { res := Do(r) data, err := ioutil.ReadAll(res) if err != nil { return err } _ = data return err}测试代码如下源码:package iouitl_readallimport ( “bytes” “testing”)var data = bytes.Repeat([]byte(“ABCD”), 1000)func BenchmarkIouitlReadAll(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := IouitlReadAll(bytes.NewReader(data)) if err != nil { b.Error(err.Error()) } } })}func BenchmarkIoCopy(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := IoCopy(bytes.NewReader(data)) if err != nil { b.Error(err.Error()) } } })}func BenchmarkIouitlReadAllAndJson(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := IouitlReadAllAndJson(bytes.NewReader(data)) if err != nil { b.Error(err.Error()) } } })}func BenchmarkIoCopyAndJson(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := IoCopyAndJson(bytes.NewReader(data)) if err != nil { b.Error(err.Error()) } } })}测试结果如下:goos: darwingoarch: amd64pkg: github.com/thinkeridea/example/iouitl_readallBenchmarkIouitlReadAll-8 500000 2752 ns/op 14496 B/op 6 allocs/opBenchmarkIoCopy-8 20000000 65.2 ns/op 48 B/op 1 allocs/opBenchmarkIouitlReadAllAndJson-8 100000 20022 ns/op 46542 B/op 616 allocs/opBenchmarkIoCopyAndJson-8 100000 17615 ns/op 32102 B/op 611 allocs/op结论:可以发现 IoCopy 方法是 IouitlReadAll 方法效率的 40 倍,内存分配也很少,而 IoCopyAndJson 和 IouitlReadAllAndJson 的效率差异极小仅有 2407ns,大约是 1.13倍,不过内存分配还是少了很多的,为什么会这样呢,这就是 sync.Pool 的导致的,sync.Pool 每次获取使用时间越短,命中率就越高,就可以减少创建新的缓存,这样效率就会大大提高,而 jsoniter.Unmarshal 很耗时,就导致 sync.Pool 的命中率降低了,所以性能下降极其明显.使用 io.Copy + sync.Pool 表面上执行效率不会有很大提升,但是会大幅度减少内存分配,从而可以减少 GC 的负担,在单元测试中我们并没有考虑 GC 的问题,而 GC 能带来的性能提升会更有优势。在看一下 json 使用 sync.Pool 的效果吧 源码package iouitl_readallimport ( “bytes” “encoding/json” jsoniter “github.com/json-iterator/go”)func Json(r map[string]string) error { data, err := json.Marshal(r) if err != nil { return err } _ = data return nil}func JsonPool(r map[string]string) error { buffer := pool.Get().(*bytes.Buffer) buffer.Reset() defer pool.Put(buffer) e := json.NewEncoder(buffer) err := e.Encode(r) if err != nil { return err } return nil}func JsonIter(r map[string]string) error { data, err := jsoniter.Marshal(r) if err != nil { return err } _ = data return nil}func JsonIterPool(r map[string]string) error { buffer := pool.Get().(*bytes.Buffer) buffer.Reset() defer pool.Put(buffer) e := jsoniter.NewEncoder(buffer) err := e.Encode(r) if err != nil { return err } return nil}性能测试代码源码:package iouitl_readallimport ( “strconv” “strings” “testing”)var request map[string]stringfunc init() { request = make(map[string]string, 100) for i := 0; i < 100; i++ { request[“X”+strconv.Itoa(i)] = strings.Repeat(“A”, i/2) }}func BenchmarkJson(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := Json(request) if err != nil { b.Error(err.Error()) } } })}func BenchmarkJsonIter(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := JsonIter(request) if err != nil { b.Error(err.Error()) } } })}func BenchmarkJsonPool(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := JsonPool(request) if err != nil { b.Error(err.Error()) } } })}func BenchmarkJsonIterPool(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { err := JsonIterPool(request) if err != nil { b.Error(err.Error()) } } })}测试结果如下:goos: darwingoarch: amd64pkg: github.com/thinkeridea/example/iouitl_readallBenchmarkJson-8 100000 13297 ns/op 13669 B/op 207 allocs/opBenchmarkJsonPool-8 100000 13310 ns/op 10218 B/op 206 allocs/opBenchmarkJsonIter-8 500000 2948 ns/op 3594 B/op 4 allocs/opBenchmarkJsonIterPool-8 200000 6126 ns/op 6040 B/op 144 allocs/opPASSok github.com/thinkeridea/example/iouitl_readall 12.716s这里使用了两个 json 包, 一个是标准库的,一个是 jsoniter (也是社区反馈效率最高的),对比两个包使用 sync.Pool 和不使用之间的差异,发现标准库 json 包使用后内存有少量减少,但是运行效率稍微下降了,差异不是很大,jsoniter 包差异之所谓非常明显,发现使用 sync.Pool 之后不仅内存分配更多了,执行效率也大幅度下降,差了将近3倍有余。是不是很奔溃,这是啥情况 jsoniter 本身就使用了 sync.Pool 作缓冲,我们使用 jsoniter.NewEncoder(buffer) 创建一个序列化实例,但是其内部并没有直接使用 io.Writer 而是先使用缓冲序列化数据,之后写入 io.Writer, 具体代码如下:// Flush writes any buffered data to the underlying io.Writer.func (stream *Stream) Flush() error { if stream.out == nil { return nil } if stream.Error != nil { return stream.Error } n, err := stream.out.Write(stream.buf) if err != nil { if stream.Error == nil { stream.Error = err } return err } stream.buf = stream.buf[n:] return nil}这样一来我们使用 buffer 做 json 序列化优化效果就大打折扣,甚至适得其反了。再次感谢 “wxe” 网友的提问,这里没有使用实际的应用场景做性能测试,主要发现在性能测试中使用 http 服务会导致 connect: can’t assign requested address 问题,所以测试用使用了函数模拟,如果有朋友有更好的测试方法欢迎一起交流。转载:本文作者: 戚银(thinkeridea)本文链接: https://blog.thinkeridea.com/201902/go/you_ya_de_du_qu_http_qing_qiu_huo_xiang_ying_de_shu_ju_2.html版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 CN协议 许可协议。转载请注明出处! ...

February 1, 2019 · 4 min · jiezi

如何在Flutter上优雅地序列化一个对象

序列化一个对象才是正经事对象的序列化和反序列化是我们日常编码中一个非常基础的需求,尤其是对一个对象的json encode/decode操作。每一个平台都会有相关的库来帮助开发者方便得进行这两个操作,比如Java平台上赫赫有名的GSON,阿里巴巴开源的fastJson等等。而在flutter上,借助官方提供的JsonCodec,只能对primitive/Map/List这三种类型进行json的encode/decode操作,对于复杂类型,JsonCodec提供了receiver/toEncodable两个函数让使用者手动“打包”和“解包”。显然,JsonCodec提供的功能看起来相当的原始,在闲鱼app中存在着大量复杂对象序列化需求,如果使用这个类,就会出现集体“带薪序列化”的盛况,而且还无法保证正确性。来自官方推荐聪明如Google官方,当然不会坐视不理。json_serializable的出现就是官方给出的推荐,它借助Dart Build System中的build_runner和json_annotation库,来自动生成fromJson/toJson函数内容。(关于使用build_runner生成代码的原理,之前兴往同学的文章已经有所提及)关于如何使用json_serializable网上已经有很多文章了,这里只简单提一些步骤:Step 1 创建一个实体类Step 2 生成代码:来让build runner生成序列化代码。运行完成后文件夹下会出现一个xxx.g.dart文件,这个文件就是生成后的文件。Step 3 代理实现:把fromJson和toJson操作代理给上面生成出来的类我们为什么不用这个实现json_serializable完美实现了需求,但它也有不满足需求的一面:使用起来有些繁琐,多引入了一个类很重要的一点是,大量的使用"as"会给性能和最终产物大小产生不小的影响。实际上闲鱼内部的《flutter编码规范》中,是不建议使用"as"的。(对包大小的影响可以参见三笠同学的文章,同时dart linter也对as的性能影响有所描述)一种正经的方式基于上面的分析,很明显的,需要一种新的方式来解决我们面临的问题,我们暂且叫它,fish-serializable需要实现的功能我们首先来梳理一下,一个序列化库需要用到:获取可序列化对象的所有field以及它们的类型信息能够构造出一个可序列化对象,并对它里面的fields赋值,且类型正确支持自定义类型最好能够解决泛型的问题,这会让使用更加方便最好能够轻松得在不同的序列化/反序列化方式中切换,例如json和protobuf。困难在哪里flutter禁用了dart:mirrors,反射API无法使用,也就无法通过反射的方式new一个instance、扫描class的fields。泛型的问题由于dart不进行类型擦出,可以获取,但泛型嵌套后依然无法解开。Let’s rock无法使用dart:mirrors是个“硬”问题,没有反射的支持,类的内容就是一个黑盒。于是我们在迈出第一步的时候就卡壳了- -!这个时候笔者脑子里闪过了很多画面,白驹过隙,乌飞兔走,啊,不是…是c++,c++作为一种无法使用反射的语言,它是如何实现对象的 序列化/反序列化 操作的呢?一顿搜索猛如虎之后,发现大神们使用创建类对象的回调函数配合宏的方式来实现c++中类似反射这样的操作。这个时候,笔者又想到了曾经朝夕相处的Android(现在已经变成了flutter),Android中的Parcelable序列化协议就是一个很好的参照,它通过writeXXXAPIs将类的数据写入一个中间存储进行序列化,再通过readXXXAPIs进行反序列化,这就解决了我们上面提到的第一个问题,既如何将一个类的“黑盒子”打开。同时,Parcelable协议中还需要使用者提供一个叫做CREATOR的静态内部类,用来在反序列化的时候反射创建一个该类的对象或对象数组,对于没有反射可用的我们来说,用c++的那种回调函数的方式就可以完美解决反序列化中对象创建的问题。于是最终我们的基本设计就是:ValueHolder这是一个数据中转存储的基类,它内部的writeXXX APIs提供展开类内部的fields的能力,而readXXX则用来将ValueHolder中的内容读取赋值给类的fields。readList/readMap/readSerializable函数中的type argument,我们把它作为外部想要解释数据的方式,比如readSerializable<T>(key: ‘object’),表示外部想要把key为object的值解释为T类型。FishSerializableFishSerializable是一个interface,creator是个一个get函数,用来返回一个“创建类对象的回调”,writeTo函数则用来在反序列化的时候放置ValueHoder->fields的代码。JsonSerializer它继承于FishSerializer接口,实现了encode/decode函数,并额外提供encodeToMap和decodeFromMap功能。JsonSerializer类似JsonCodec,直接面向使用者用来json encode/decode以上,我们已经基本做好了一个flutter上支持对象序列化/反序列化操作的库的基本架构设计,对象的序列化过程可以简化为:由于ValueHolder中间存储的存在,我们可以很方便得切换 序列化/反序列器,比如现有的JsonSerializer用来实现json的encode/decode,如果有类似protobuf的需求,我们则可以使用ProtoBufSerializer来将ValueHolder中的内容转换成我们需要的格式。困难是不存在的有了基本的结构设计之后,实现的过程并非一帆风顺。如何匹配类型?为了能支持泛型容器的解析,我们需要类似下面这样的逻辑:List<SerializableObject> list = holder.readList<SerializableObject>(key: ’list’);List<E> readList<E>({String key}){ List<dynamic> list = _read(key);}E _flattenList<E>(List<dynamic> list){ list?.map<E>((dynamic item){ // 比较E是否属于某个类型,然后进行对应类型的转换 });}在Java中,可以使用Class#isAssignableFrom,而在flutter中,我们没有发现类似功能的API提供。而且,如果做下面这个测试,你还会发现一些很有意思的细节:void main() { print(‘int test’); test<int>(1); print(’\r\nint list test’); test<List<int>>(<int>[]); print(’\r\nobject test’); test<A<int>>(A<int>());}void test<T>(T t){ print(T); print(t.runtimeType); print(T == t.runtimeType); print(identical(T, t.runtimeType));}class A<T>{}输出的结果是:可以看到,对于List这样的容器类型,函数的type argument与instance的runtimeType无法比较,当然如果使用t is T,是可以返回正确的值的,但需要构造大量的对象。所以基本上,我们无法进行类型匹配然后做类型转换。如何解析泛型嵌套?接下去就是如何分解泛型容器嵌套的问题,考虑如下场景:Map<String, List<int>> listMap;listMap = holder.readMap<String, List<int>>(key: ’listMap’);readMap中得到的value type是一个List<int>,而我们没有API去切割这个type argument。所以我们采用了一种比较“笨”也相对实用的方式。我们使用字符串切割了type argument,比如:List<int> => <String>[List<int>, List, int]然后在内部展开List或Map的时候,使用字符串匹配的方式匹配类型,在目前的使用中,完美得支持了标准List和Map容器互相嵌套。但目前无法支持标准List和Map之外的其他容器类型。What’s moreIDE插件辅助写过Android的Parcelable的同学应该有种很深刻的体会,Parcelable协议中有大量的“机械”代码需要写,类似设计的fish-serializable也一样。为了不被老板和使用库的同学打死,同时开发了fish-serializable-intelij-plugin来自动生成这些“机械”代码。与json_serializable的对比fish-serializable在使用上配合IDE插件,减少了大量的"as"操作符的使用,同时在步骤上也更加简短方便。相比于json_annotation生成的代码,fish-serializable生成的代码也更具可读性,方便手动修改一些代码实现。fish-serializable可以通过手动接管 序列化/反序列化 过程的方式完美兼容json_annotation等其他方案。目前闲鱼app中已经开始大量使用。开源计划fish-serializable和fish-serializable-intelij-plugin都在开源计划中,相信不久就可以与大家见面,尽请期待~本文作者:闲鱼技术-海潴阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

January 30, 2019 · 1 min · jiezi

输出 JSON 数据时的 Content-Type

导语缘由是这样的,给前端返回 json 数据,反馈说数据不对(前端是在原代码基础上复制修改的,去掉 jsonp)。看了下后端代码,发现是两次返回 json 的代码不同。但这不是问题的原因,然后再一看 JS 代码,是前端没有进行转换。也正好借此机会,看了下平时没有注意到的点。分析日常写代码,返回一些简短数据的时候,没有使用框架封装的方法,如下exit(json_encode([‘message’ => ’test’]));这时候响应头的 Content-Type 是 text/html 。如果用框架(ThinkPHP 3.0)的返回方式,$this->ajaxReturn([‘message’ => ’test’]);这时候响应头的 Content-Type 是 text/html; charset=utf-8 。看了源码,是有这行代码的header(‘Content-Type:text/html; charset=utf-8’);结论实际上,json 数据是有特定的 Content-Type,是 application/json 。后来又去看了 Laravel ,返回 json 的时候, Content-Type 就是 application/json 。出现这样的问题,是因为自己不够严谨,而且使用的框架有些老旧。补充一下,后来又看了前端的代码。实际上用 JQuery 封装的 ajax 方法,dataType 设置为 jsonp 或者 json,都可以自动转换为 json 格式,即使 Content-Type 是 text/html; charset=utf-8,这时用 typeof 函数打印返回值,是 object 类型,这也是之前代码没有问题的原因。或者使用 JSON.parse() 转换一下返回值,也是可以的。当然最稳妥的方案还是后端设置 Content-Type 为 application/json。参考资料:Content-Type。

January 29, 2019 · 1 min · jiezi

让前端面试不在难(一)

今天开始,我从面试题切入开始做一些详解和记录,争取每个工作日一篇!欢迎关注吐槽!const obj = { a: 1, b: 3, c: -3, f: 5, d: 8 }要求以对象value的大小排序返回[c,a,b,f,d]问题解析: 1、对象是无序的,我们需要转为有序数据结构,其实也就是转为数组然后后再去排序。 2.按value排序简单,但要求是输入key对应的排序,我们需要想办法做对应关系 function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } console.log(arr) } sortObj(obj)打印数组:接下来就简单多了,多于的数组排序方法我就不一一写了,本次只为解决问题function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } arr = arr.sort((a, b) => { return a.value - b.value }) console.log(arr) } sortObj(obj)此时结果为以value有序的数组了接下来遍历数组生成结果function sortObj(obj) { //先转为数组 let arr = [] // 遍历json 方法有 Object.keys() for in 用keys以后还得继续遍历key数组,在这我们选用for in for (let item in obj) { // 这一步很关键,我们需要能按照value排序,有需要做key的对应关系,我的做法是这样的 // 把json的每一项push到数组里,并拆分原对象key和value分别对应 arr.push({ key: item, value: obj[item] }) } arr = arr.sort((a, b) => { return a.value - b.value }) console.log(arr) return arr.map((item) => { return item.key }) } console.log(sortObj(obj))测试ok!在来个es6的方法 let newArr = Object.entries(obj).sort((a, b) => { return a[1] - b[1] }).map((item) => { return item[0] }) console.log(newArr)这个方法看起来很骚,其实原理和最开始的解析类似,Object.entries(obj) 会输入一个数组,数组的每一项是一个数组,内容每一项是原对象每一项的key和value,看下图:解析完毕!您的点赞or吐槽是我持续下去的动力! ...

January 24, 2019 · 1 min · jiezi

mysql 之 json 数据类型的使用及高效检索(配合虚拟列 virtual generated column)

mysql 5.7+ 版本开始支持 json 数据类型,可以方便的存储JSON格式的数据,同时配合虚拟列 (virtual generated column),可以方便的为 json 列数据的某属性映射虚拟列,建立索引,高效检索。构造json数据方法:json_array() / json_object()json_array / json_object 用于组装 json 数据,json 说的简单些json就是由标量(int, float, string) + 数组 + 对象组合而成的,这两个函数可以方便的用于构造数组和对象的json格式串json_array(item1, item2, item3, …) =>[item1, item2, item3]json_object(key1, val1[, [key2, val2]…]) => {“key1”: “val1”, “key2”: “val2”,…}使用场景例如:select json_object( “username”, “big_cat”, “favorites”, json_array( json_object(“article_id”, 1, “favorited_at”, “2019-01-18”), json_object(“article_id”, 2, “favorited_at”, “2019-01-18”), json_object(“article_id”, 3, “favorited_at”, “2019-01-18”), json_object(“article_id”, 4, “favorited_at”, “2019-01-18”) ));// result{ “username”: “big_cat”, “favorites”: [ {“article_id”: 1, “favorited_at”: “2019-01-18”}, {“article_id”: 2, “favorited_at”: “2019-01-18”}, {“article_id”: 3, “favorited_at”: “2019-01-18”}, {“article_id”: 4, “favorited_at”: “2019-01-18”} ]} 读取json数据方法:json_extract() /col->"$.{property_name}“json_extract 用于读取 json 列的某字段,或者也可以使用 col->"$.{property_name}” 的方式访问json_extract(col, ‘$.{property_name}’) / col->’$.{property_name}‘create table users (id int unsigned not null auto_increment primary key,doc json);insert into users(doc)values (json_object(“name”, “big_cat”, “age”, 28)), (’{“name”: “james”, “age”: 29}’);select json_extract(doc, “$.name”) as name, json_extract(doc, “$.age”) as age from users;select doc->"$.name" as name, doc->"$.age" as age from users;高效检索json数据mysql 提供的一些函数是可以方便我们条件检索json数据的,但无法使用索引,数据量大的时候难免低效。select id, doc->"$.age" from users where json_extract(doc, “$.name”) = “big_cat”;select id, doc->"$.age" from users where doc->"$.name" = “big_cat”;这时我们可以利用同 json 一同新增的特性:虚拟列(virtual generated column)。将需要参与检索的 json 属性映射为 虚拟列,在虚拟列上建立索引,便可参与高效检索。另外补充一下,在mysql 5.7+中,支持两种Generated Column,即Virtual Generated Column和Stored Generated Column。前者不存储元数据,后者会将 expression 的计算结果实际的存储下来。其实二者性能差距并不大,若对二者建立索引进行检索操作,前者性能可能会略低于后者,因为前者要对结果集即时的进行 expression 的演算,但后者需要消耗额外的存储空间。需要注意的有:不存储数据的特性也导致只能在虚拟列上建立二级索引,插入数据时不可以向虚拟列插入数值(mysql自行负责演算)。#虚拟列创建ALTER TABLE table_name ADD COLUMN col_name <type> [ GENERATED ALWAYS ] AS ( <expression> ) [ VIRTUAL|STORED ][ UNIQUE [KEY] ] [ [PRIMARY] KEY ] [ NOT NULL ] [ COMMENT <text> ]# 为 user 表的 json 字段的 name 创建虚拟列alter table users add column user_name varchar(10) generated always as (doc->"$.name");# 为虚拟列添加索引alter table users add index index_u_n(user_name);# 检索时可以使用索引explain select id, user_name, doc->"$.age" as age from users where user_name = “big_cat” \G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: users partitions: NULL type: refpossible_keys: index_u_n key: index_u_n key_len: 43 ref: const rows: 1 filtered: 100.00 Extra: NULL1 row in set, 1 warning (0.00 sec)下面直接对 json 解析检索的方式是无法用到索引的 explain select id from users where doc->"$.user_name" = “big_cat” \G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: users partitions: NULL type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 2 filtered: 100.00 Extra: Using where1 row in set, 1 warning (0.00 sec)explain select id from users where json_extract(doc, ‘$.username’) = “big_cat” \G*************************** 1. row *************************** id: 1 select_type: SIMPLE table: users partitions: NULL type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 2 filtered: 100.00 Extra: Using where1 row in set, 1 warning (0.00 sec)还有其他 json 的使用这里就不说明了,大家可以参考一下文章:mysql json 使用 类型 查询 函数:https://www.cnblogs.com/ooo0/…MySQL 5.7 虚拟列 (virtual columns):https://www.cnblogs.com/raich…MySQL 5.7原生JSON格式支持:https://www.cnblogs.com/zouca… ...

January 18, 2019 · 2 min · jiezi

使用form-create动态生成vue组件,支持json格式

[github] | [说明文档]示例let rule = [ { type:‘row’, children:[ { type:‘i-col’, props:{ span:12 }, children:[ formCreate.maker.input(‘商品名称’,‘goods_name’,‘iphone’), formCreate.maker.number(‘商品加个’,‘goods_price’,8688) ] }, { type:‘i-col’, props:{ span:12 }, children:[ formCreate.maker.dateTime(‘创建时间’,‘create_at’), formCreate.maker.radio(‘是否显示’,‘is_show’).options([ {value:1,label:‘显示’}, {value:0,label:‘不显示’} ]) ] } ] }]maker.create通过建立一个虚拟 DOM的方式生成自定义组件生成Makerlet rule = [ formCreate.maker.create(‘i-button’).props({ type:‘primary’, field:‘btn’ loading:true })]$f = formCreate.create(rule);上面的代码是通过 maker 生成器动态生成一个正在加载的 iview 按钮组件Jsonlet rule = [ { type:‘i-button’, field:‘btn’ props:{ type:‘primary’, field:‘btn’, loading:true } }]$f = formCreate.create(rule);上面的代码是通过json方式动态生成一个iview 按钮组件修改可以通过一下两种方式动态修改组件的配置项通过rule修改组件生成规则rule[0].props.loading = false;通过$f.component()方法获取组件的生成规则并修改$f.component().btn.props.loading = false;maker.template通过模板的方式生成自定义组件,maker.createTmp方法是该方法的别名生成Makerlet rule = [ formCreate.maker.template(’<i-button :loading=“loading”>{{text}}<i-button>’,new Vue({ data:{ loading:true, text:‘正在加载中…’ } }))]上面的代码是通过 maker 生成器动态生成一个正在加载的 iview 按钮组件Jsonlet rule = [ { type:’template’, template:’<i-button :loading=“loading”>{{text}}<i-button>’, vm:new Vue({ data:{ loading:true, text:‘正在加载中’ } }) }]$f = formCreate.create(rule);上面的代码是通过Json方式动态生成一个iview 按钮组件修改可以通过一下两种方式动态修改vm组件内部的值通过rule获取自定义组件的vm并修改rule[0].vm.text = ‘加载完毕’;rule[0].vm.loading = false;通过$f.component()方法获取自定义组件的vm并修改$f.component().btn.vm.text = ‘加载完毕’;$f.component().btn.vm.loading = false; ...

January 18, 2019 · 1 min · jiezi

XML已死 ?

近十几年来唱衰XML的声音一直不曾间断过,作为曾经风靡一时的可扩展标记语言,似乎真的江河日下了。近期在跨部门合作过程中,有幸得以接触到以XML作为通用配置的大型工程项目,然而在开发过程中逐渐感受到这种面向xml配置的编程方式使得简单的问题变得复杂。同时,这种自定义的高度可配置化的方式对于项目管理又特别清晰高效。于是对关于如何正确看待XML这个问题陷入了思考….1、XML发展历史首先来简单回顾下XML几个重要发展节点:1978 年,ANSI 将 IBM二十世纪60年代发展起来的GML(Generalized Markup Language)标准化后,发布成为 SGML(The Standard Generalized Markup Language),这成为XML的前身。1986年,SGML作为一种通用标记语言,为国际标准化组织ISO所采用,但由于SGML定义过于复杂,无法普及。1995年,考虑到当时HTML的缺陷,作为简化与优化的XML雏形已经形成,并向W3C提案1998年2月,XML1.0发布,正式成为W3C的标准。虽然XML生而是为解决HTML的一些弊病,并取而代之。但事与愿违,众所周知XML并没有取代HTML成为新的超文本标记语言。不过这丝毫不妨碍XML不断发展演化并被广泛用作跨平台数据交换的格式:主要针对数据的内容,通过不同的格式化描述手段(XSLT,CSS等)可以完成最终的形式表达(生成对应的HTML,PDF或者其他的文件格式)。注:HTML也是基于SGML,准确来说HTML是SGML的一个应用(基于SGML的超文本版本)。没错,JSON是一种优秀的数据交换格式,但是它仅仅是一种数据交换格式(与XML对比)。2、XML VS JSON很多开发者唱衰XML也并不是空穴来风,在WEB技术如此炙热的年代,JSON似乎已经成为了一种事实上的WEB交换数据格式标准。如果严格地从定义上来讲,XML与JSON并不是同一样东西。XML是一种标记元语言,而JSON则是一种轻量级的数据交换格式。从这个点上说,XML与JSON是无法比较的。很多时候时候我们在说XML VS JSON,实际上是指XML作为数据交换格式与JSON这种数据交换格式的比较。虽然JSON是作为JavsScript的一个子集诞生的,但是它独立于语言。JSON得益于其本身的小巧、简单以及浏览器内建快速解析支持的特性,使得其更适用于网络数据传输领域,在web2.0时代更是一路高歌猛进。下面是Google Trends和StackOverflow Insights上的数据:Google Trends:StackOverflow Insights:可以看出,在2013年左右JSON热度逐渐超过XML。尽管,JSON在语义表达、数据存储以及检索方面远不及XML,但就是因为其简单、轻量使得其大肆流行于市场。注:从这个点上的启发是简单好用比全面更容易普及。比如当年的OSI七层网络协议与TCP/IP四层协议之争,同样的道理:简单是流行的前提。3、XML真的死了吗?看着如此趋势,不免会心生疑问:XML真的死了吗?答案当然是否定的。这其实是一种认知偏见,见得少不代表事实上不多。比如segmentfault上大都是偏前端类的技术文章,这说明难道后端技术都消失了嘛?明显不是。曲线只能说明某种场景下未来的一种趋势,比如在web领域的确xml是不如json使用的广泛。但是在面向SOA架构的企业级软件应用中,XML仍是信息交换的事实标准。XML作为一个可以自定义标签的元标记语言,在描述复杂的业务逻辑时尤其特别适用。并且, 在当今的出版业中,整个文档处理工作流程都要使用XML。它也是标准的Office文件格式,例如Word、Excel、PowerPoint或Google Docs等。故,XML并没有死,只是某些领域应用减少,但是其他领域特别是企业级仍然有很多应用。Referenceshttps://www.quora.com/Is-XML-…https://zh.wikipedia.org/zh-h...https://www.itcodemonkey.com/...http://www.cftea.com/c/444.asphttp://siddim.com/archives/47...https://zh.wikipedia.org/zh-h…

January 14, 2019 · 1 min · jiezi

JSON.parse()与JSON.stringify()的用法,以及eval()和new Function

JSON.parse()1、我们可以使用 JSON.parse() 方法将数据转换为 JavaScript 对象。语法JSON.parse(text[, reviver])参数说明:text:必需, 一个有效的 JSON 字符串。reviver: 可选,一个转换结果的函数, 将为对象的每个成员调用此函数。2、JSON 解析实例使用 JSON.parse 的 reviver 函数时一定要注意遍历到最后的顶层对象 key 为 “",需要返回 value。不然报错var json = ‘{“name”:“Harvy”, “age”:36, “gender”:“male”}’;var person = JSON.parse(json, function (key, value) { if(key != “”) return “<font color="blue">"+value+"</font>”; else return value;});JSON 不能存储 Date 对象。// Sat Dec 14 2013 08:00:00 GMT+0800 (中国标准时间)如果你需要存储 Date 对象,需要将其转换为字符串。// 2013-12-14之后再将字符串转换为 Date 对象。// new Date(“2013-12-14”);我们可以启用 JSON.parse 的第二个参数 reviver,一个转换结果的函数,对象的每个成员调用此函数。实例var text = ‘{ “name”:“Runoob”, “initDate”:“2013-12-14”, “site”:“www.runoob.com”}’; var obj = JSON.parse(text, function (key, value) { if (key == “initDate”) { return new Date(value);//将日期字符串转换为 Date 对象 } else { return value; }}); //Sat Dec 14 2013 08:00:00 GMT+0800 (中国标准时间)document.getElementById(“demo”).innerHTML = obj.name + “创建日期:” + obj.initDate;3、解析函数JSON 不允许包含函数,但你可以将函数作为字符串存储,之后再将函数字符串用eval解析。实例var text = ‘{ “name”:“Runoob”, “alexa”:“function () {return 10000;}”, “site”:“www.runoob.com”}’; var obj = JSON.parse(text); obj.alexa = eval(”(" + obj.alexa + “)”); //用eval解析函数字符串document.getElementById(“demo”).innerHTML = obj.name + " Alexa 排名:" + obj.alexa();4、eval()除了JSON.parse(),以下二种方法也可以将json字符串转化为json 对象。var str1 = ‘{ “name”: “deyuyi”, “sex”: “man” }’;console.log(eval("("+str1+")"));console.log((new Function("",“return “+str1))());//str1 = { “name”: “deyuyi”, “sex”: “man” }Eval由于json是以”{}”的方式来开始以及结束的,在JS中,{}会被当成一个语句块来处理,加上圆括号的目的是迫使eval函数在处理JavaScript代码的时候强制将括号内的表达式(expression)转化为对象,而不是作为语句(statement)来执行。console.log(eval(”{}”));// return undefinedconsole.log(eval("({})"));// return object[Object]将age的值换成了Javascript代码,eval依然可以解析,假如有人恶意修改这个代码,那么就会造成严重后果。//例如:var jsonData = ‘{“student” : [{“name”:“鸣人”,“age”:17}, {“name”:“小樱”,“age”:alert(“hehe”)},{“name”:“佐助”,“age”:17}]}’;//先弹出一个提示框输出hehe的字符串console.log(eval("("+jsonData+")"));但JSON.parse会报错。显示错误信息为当前字符串不符合json格式即JSON.parse()方法会检查需要转换的字符串是否符合json格式相比较而言eval方法是很危险的特别是当涉及到第三方时我们需要确保传给eval的参数是我们可以控制的不然里面插入比如window.location指向一个恶意的连接那就不好了从这个层面讲还是推荐使用JSON.parse来实现json格式字符串的解析。//会报错~显示错误信息为当前字符串不符合json格式console.log(JSON.parse(jsonData));JSON.stringify()我们可以使用 JSON.stringify() 方法将 JavaScript 对象转换为字符串。1、语法JSON.stringify(value[, replacer[, space]])参数说明:value:必需, 一个有效的 JSON 对象。replacer:可选。用于转换结果的函数或数组。如果 replacer 为函数,则 JSON.stringify 将调用该函数,并传入每个成员的键和值。使用返回值而不是原始值。如果此函数返回 undefined,则排除成员。根对象的键是一个空字符串:""。如果 replacer 是一个数组,则仅转换该数组中具有键值的成员。成员的转换顺序与键在数组中的顺序一样。当 value 参数也为数组时,将忽略 replacer 数组。space:可选,文本添加缩进、空格和换行符,如果 space 是一个数字,则返回值文本在每个级别缩进指定数目的空格,如果 space 大于 10,则文本缩进 10 个空格。space 有可以使用非数字,如:t。2、JavaScript 对象转换实例var obj = { “name”:“runoob”, “alexa”:10000, “site”:“www.runoob.com”}; document.write(obj);//[object Object] var myJSON = JSON.stringify(obj);document.write(myJSON );//输出字符串{“name”:“runoob”,“alexa”:10000,“site”:“www.runoob.com”}3、JavaScript 数组转换实例var arr = [ “Google”, “Runoob”, “Taobao”, “Facebook” ]; document.write(arr);//Google,Runoob,Taobao,Facebookvar myJSON = JSON.stringify(arr);document.write(myJSON );// [ “Google”, “Runoob”, “Taobao”, “Facebook” ]4、将Date 对象转化为字符串JSON 不能存储 Date 对象。JSON.stringify() 会将所有日期转换为字符串。实例var obj = { “name”:“Runoob”, “initDate”:new Date(), “site”:“www.runoob.com”};var myJSON = JSON.stringify(obj.initDate); document.write(myJSON );//“2018-11-08T03:00:40.443Z"5、解析函数JSON 不允许包含函数,JSON.stringify() 会删除 JavaScript 对象的函数,包括 key 和 value。实例var obj = { “name”:“Runoob”, “alexa”:function () {return 10000;}, “site”:“www.runoob.com”}; var myJSON = JSON.stringify(obj);document.write(myJSON );//{“name”:“Runoob”,“site”:“www.runoob.com”}我们可以在执行 JSON.stringify() 函数前将函数转换为字符串来避免以上问题的发生:实例var obj = { “name”:“Runoob”, “alexa”:function () {return 10000;}, “site”:“www.runoob.com”}; obj.alexa = obj.alexa.toString(); //将函数转换为字符串var myJSON = JSON.stringify(obj);//{“name”:“Runoob”,“alexa”:“function () {return 10000;}”,“site”:“www.runoob.com”} document.write(myJSON );不建议在 JSON 中使用函数。 ...

January 3, 2019 · 2 min · jiezi

JSON的基本操作,重点访问对象值点号(.)来访问对象的值和中括号([ ])的区别

访问对象值1、你可以使用点号(.)来访问对象的值:实例 var myObj, x; myObj = { “name”:“runoob”, “alexa”:10000, “site”:null }; x = myObj.name;2、你也可以使用中括号([ ])来访问对象的值:实例var myObj, x; myObj = { “name”:“runoob”, “alexa”:10000, “site”:null }; x = myObj[“name”];3、从功能上说,这两种方法没有任何区别。但方括号语法有一个优点:可以通过变量来访问属性,如:var propertyName = ’name’;alert(person[propertyName]); //gogojson 在进行通过键名来获取值时,需要特别注意一下。把键名赋值给另外一个变量,然后通过.方式去获取值。这种方式是行不通的。var myObj, x;myObj = { “name”:“runoob”, “alexa”:10000, “site”:null };x = “name”;document.getElementById(“demo”).innerHTML = myObj.x; // 结果是 undefined只能通过 [] 方式去访问:var myObj, x;myObj = { “name”:“runoob”, “alexa”:10000, “site”:null };x = “name”;document.getElementById(“demo”).innerHTML = myObj[x]; // 结果是 runoob还有在使用for遍历时,只能通过 myObj[x] 来获取相应属性的值,而不能使用 myObj.x总结,键名为变量时只能用 []来获取相应属性值。还有!如果属性名中包含会导致语法错误的字符,或者属性名是关键字或者保留字,也是使用方括号表示法。如: var response = { “awards”:{‘105’:50, ‘107’:10,‘108’:5,‘110’:3,‘111’:2, ‘112’:1} };console.log(response.awards[‘105’]) //50console.log(response.awards.105) //报错循环对象1、你可以使用 for-in 来循环对象的属性:key实例var myObj = { “name”:“runoob”, “alexa”:10000, “site”:null };for (x in myObj) { document.getElementById(“demo”).innerHTML += x + “<br>”; }2、在 for-in 循环对象的属性时,使用中括号([])来访问属性的值:value在使用for遍历时,只能通过 myObj[x] 来获取相应属性的值,而不能使用 myObj.x实例var myObj = { “name”:“runoob”, “alexa”:10000, “site”:null }; for (x in myObj) { document.getElementById(“demo”).innerHTML += myObj[x] + “<br>”; }嵌套 JSON 对象 value 可以是合法的 JSON 数据类型1、JSON 对象中可以包含另外一个 JSON 对象:实例myObj = { “name”:“runoob”, “alexa”:10000, “sites”: { “site1”:“www.runoob.com”, “site2”:“m.runoob.com” } }2、你可以使用点号(.)或者中括号([])来访问嵌套的 JSON 对象。实例x = myObj.sites.site1; // 或者 x = myObj.sites[“site1”];修改值1、你可以使用点号(.)来修改 JSON 对象的值:实例myObj.sites.site1 = “www.google.com”;2、你可以使用中括号([])来修改 JSON 对象的值:实例myObj.sites[“site1”] = “www.google.com”;删除对象属性1、我们可以使用 delete 关键字来删除 JSON 对象的属性:实例delete myObj.sites.site1;2、你可以使用中括号([])来删除 JSON 对象的属性:实例delete myObj.sites[“site1”] ...

January 3, 2019 · 1 min · jiezi

JSON.stringify和fast-json-stringify的比较

JSONJSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一个子集。JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)JSON 是轻量级的文本数据交换格式JSON 独立于语言:JSON 使用 Javascript语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。 目前非常多的动态(PHP,JSP,.NET)编程语言都支持JSON。JSON 具有自我描述性,更易理解JSON数据格式JSON大致3种结构,JSON对象、JSON数组和JSON对象和数组嵌套。Object对象Object是一个无序‘名称/值’对集合,一个对象以{(左括号)开始,}(右括号)结束。每个名称后跟一个:(冒号);‘名称/值’ 对之间使用,(逗号)分隔。其中键值对中的键必须是string类型,值可以是json三种Object|Array|value类型中的一种,string属于一种value,value包含string、null、number等,后面第三种会讲实例:{}{“aa”: true}{“bb”: “3333”}{“cc”: null}{“dd”: {“ee”: [“ff”,“gg”]}}Array数组数组是值(value)的有序集合。一个数组以[(左中括号)开始,](右中括号)结束。值之间使用,(逗号)分隔。示例:[][“aa”,“bb”][[1,2],[3,4]][{“aa”: []},{bb: []}]值Value值(value)可以是双引号括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。stringstring是value中比较特殊的需要注意的一种;字符串(string)是由双引号包围的任意数量Unicode字符的集合,使用反斜线转义。一个字符(character)即一个单独的字符串(character string)。示例:“aaa”"\ttab""\nnew line"看起来很简单啊;结果我们去V8环境中运行了一下,JSON.parse("\nnewline");VM5802:2 Uncaught SyntaxError: Unexpected token e in JSON at position 2 at JSON.parse (<anonymous>) at <anonymous>:1:6(anonymous) @ VM5801:1JSON.parse("\ttab");VM5836:1 Uncaught SyntaxError: Unexpected token a in JSON at position 2 at JSON.parse (<anonymous>) at <anonymous>:1:6蓝瘦了,不是说好的格式吗?反向测试:JSON.stringify(‘2 2’)““2\t2"“JSON.parse(JSON.stringify(‘2 2’))“2 2"JSON.stringify(‘2 2’)““2\t2"“JSON.parse(’“2\t2”’)VM330:1 Uncaught SyntaxError: Unexpected number in JSON at position 2 at JSON.parse (<anonymous>) at <anonymous>:1:6问题好像有点眉头了,为什么直接使用字符串的时候会有问题呢?是不是js的字符串定义和解析的锅?MDN上的js的string转义字符节除了普通的可打印字符以外,一些特殊有特殊功能的字符可以通过转义字符的形式放入字符串中:Code Output\0 空字符' 单引号" 双引号\ 反斜杠\n 换行\r 回车\v 垂直制表符\t 水平制表符\b 退格\f 换页\uXXXX unicode 码\u{X} … \u{XXXXXX} unicode codepoint \xXX Latin-1 字符(x小写)问题的根本原因在于使用V8的JSON.parse的时候,参数会被V8先解析为字符串;’"\ttab”’ =>”” tab””’"\nnewline”’ =>““newline””’“2\t2”’ =>““2 2"“被解析后的字符串变了样子,这些解析后的字符串不再符合JSON的格式,所以解析就会报错了。详情参考:从一个 JSON.parse 错误深入研究 JavaScript 的转义字符number数值(number)也与C或者Java的数值非常相似。除去未曾使用的八进制与十六进制格式。除去一些编码细节。javascript的JSON对象JSON对象包含两个方法: 用于解析 JavaScript Object Notation (JSON) 的 parse() 方法,以及将对象/值转换为 JSON字符串的 stringify() 方法。除了这两个方法, JSON这个对象本身并没有其他作用,也不能被调用或者作为构造函数调用。parseJSON.parse() 方法用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)。parse语法介绍和使用mdnJSON.parse(text[, reviver])>如果指定了 reviver 函数,则解析出的 JavaScript 值(解析值)会经过一次转换后才将被最终返回(返回值)。更具体点讲就是:解析值本身以及它所包含的所有属性,会按照一定的顺序(从最最里层的属性开始,一级级往外,最终到达顶层,也就是解析值本身)分别的去调用 reviver 函数,在调用过程中,当前属性所属的对象会作为 this 值,当前属性名和属性值会分别作为第一个和第二个参数传入 reviver 中。如果 reviver 返回 undefined,则当前属性会从所属对象中删除,如果返回了其他值,则返回的值会成为当前属性新的属性值。>当遍历到最顶层的值(解析值)时,传入 reviver 函数的参数会是空字符串 “"(因为此时已经没有真正的属性)和当前的解析值(有可能已经被修改过了),当前的 this 值会是 {””: 修改过的解析值},在编写 reviver 函数时,要注意到这个特例。(这个函数的遍历顺序依照:从最内层开始,按照层级顺序,依次向外遍历)stringifyJSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,如果指定了replacer是一个函数,则可以替换值,或者如果指定了replacer是一个数组,可选的仅包括指定的属性。mdn文档JSON.stringify(value[, replacer [, space]])需要注意后面连个参数,replacer可以是函数或者数组;数组的值代表将被序列化成JSON字符串的属性名。作为函数,它有两个参数,键(key)值(value)都会被序列化。如果返回一个 Number, 转换成相应的字符串被添加入JSON字符串。如果返回一个 String, 该字符串作为属性值被添加入JSON。如果返回一个 Boolean, “true” 或者 “false"被作为属性值被添加入JSON字符串。如果返回任何其他对象,该对象递归地序列化成JSON字符串,对每个属性调用replacer方法。除非该对象是一个函数,这种情况将不会被序列化成JSON字符串。如果返回undefined,该属性值不会在JSON字符串中输出。注意: 不能用replacer方法,从数组中移除值(values),如若返回undefined或者一个函数,将会被null取代。space参数用来控制结果字符串里面的间距。如果是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格(最多10个空格);如果是一个字符串,则每一级别会比上一级别多缩进用该字符串(或该字符串的前十个字符)。fast-json-stringify最近发现一个库fast-json-stringify,因为他的介绍很简洁:2x faster than JSON.stringify();需要一探究竟。 ...

December 23, 2018 · 1 min · jiezi

状态码为200时 jQuery ajax报错

问题现象HTTP 状态码为 200 OK 时, jquery ajax报错2. 问题原因jquery ajax的dataType字段包含:json, 但是服务端返回的数据不是规范的json格式,导致jquery解析json字符串报错,最终导致ajax报错。jQuery ajax 官方文档上说明:“json”: Evaluates the response as JSON and returns a JavaScript object. Cross-domain “json” requests are converted to “jsonp” unless the request includes jsonp: false in its request options. The JSON data is parsed in a strict manner; any malformed JSON is rejected and a parse error is thrown. As of jQuery 1.9, an empty response is also rejected; the server should return a response of null or {} instead. (See json.org for more information on proper JSON formatting.)设置dataType为json时,jquery就会去解析响应体为JavaScript对象。跨域的json请求会被转化成jsonp, 除非设置了jsonp: false。JSON数据会以严格模式去解析,任何不规范的JSON字符串都会解析异常并抛出错误。从jQuery 1.9起,一个空的响应也会被抛出异常。服务端应该返回一个null或者{}去代替空响应。参考json.org, 查看更多内容3. 解决方案这个问题的原因有两个。后端返回的json数据格式不规范HTTP状态码为200,但是返回空的响应所以后端在返回结果时,不要使用空的响应,也不应该去手动拼接JSON字符串,而应该交给响应的库来实现JSON序列化字符串工作。方案1:如果后端确定响应体中不返回数据,那么就把状态码设置为204,而不是200。状态码为204时,jQuery就知道响应体中没有数据,也不需要去解析。我一直逼着后端同事这么做。方案2:如果后端接口想返回200,那么请返回一个null或者{}去代替空响应方案3:别用jQuery的ajax,换个其他的库试试4. 参考Ajax request returns 200 OK, but an error event is fired instead of successjQuery.ajax

December 14, 2018 · 1 min · jiezi

JWT - just what?

初见JWT,不知所云,赶紧Google(百度)一下,原来是跨域身份验证解决方案。JWT只是缩写,全拼则是 JSON Web Tokens ,是目前流行的跨域认证解决方案,一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT 原理jwt验证方式是将用户信息通过加密生成token,每次请求服务端只需要使用保存的密钥验证token的正确性,不用再保存任何session数据了,进而服务端变得无状态,容易实现拓展。加密前的用户信息,如:{ “username”: “vist”, “role”: “admin”, “expire”: “2018-12-08 20:20:20”}客户端收到的token:7cd357af816b907f2cc9acbe9c3b4625JWT 结构一个token分为3部分:头部(header)载荷(payload)签名(signature)3个部分用“.”分隔,如:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 头部JWT的头部分是一个JSON对象,描述元数据,通常是:{ “typ”: “JWT”, “alg”: “HS256”}typ 为声明类型,指定 “JWT"alg 为加密的算法,默认是 “HS256"也可以是下列中的算法:JWS算法名称描述HS256HMAC256HMAC with SHA-256HS384HMAC384HMAC with SHA-384HS512HMAC512HMAC with SHA-512RS256RSA256RSASSA-PKCS1-v1_5 with SHA-256RS384RSA384RSASSA-PKCS1-v1_5 with SHA-384RS512RSA512RSASSA-PKCS1-v1_5 with SHA-512ES256ECDSA256ECDSA with curve P-256 and SHA-256ES384ECDSA384ECDSA with curve P-384 and SHA-384ES512ECDSA512ECDSA with curve P-521 and SHA-512载荷载荷(payload)是数据的载体,用来存放实际需要传递的数据信息,也是一个JSON对象。JWT官方推荐字段:iss: jwt签发者sub: jwt所面向的用户aud: 接收jwt的一方exp: jwt的过期时间,这个过期时间必须要大于签发时间nbf: 定义在什么时间之前,该jwt都是不可用的.iat: jwt的签发时间jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。也可以使用自定义字段,如:{ “username”: “vist”, “role”: “admin”}签名签名部分是对前两部分(头部,载荷)的签名,防止数据篡改。按下列步骤生成:1、先指定密钥(secret)2、把头部(header)和载荷(payload)信息分别base64转换3、使用头部(header)指定的算法加密最终,签名(signature) = HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret)客户端得到的签名:header.payload.signature也可以对JWT进行再加密。JWT 使用1、服务端根据用户登录状态,将用户信息加密到token中,返给客户端2、客户端收到服务端返回的token,存储在cookie中3、客户端和服务端每次通信都带上token,可以放在http请求头信息中,如:Authorization字段里面4、服务端解密token,验证内容,完成相应逻辑JWT 特点JWT更加简洁,更适合在HTML和HTTP环境中传递JWT适合一次性验证,如:激活邮件JWT适合无状态认证JWT适合服务端CDN分发内容相对于数据库Session查询更加省时JWT默认不加密使用期间不可取消令牌或更改令牌的权限JWT建议使用HTTPS协议来传输代码原文:https://www.bestvist.com/p/62

December 10, 2018 · 1 min · jiezi

初识区块链 - 用JS构建你自己的区块链

前言区块链太复杂,那我们就讲点简单的。用JS来构建你自己的区块链系统,寥寥几行代码就可以说明区块链的底层数据结构,POW挖矿思想和交易过程等。当然了,真实的场景远远远比这复杂。本文的目的仅限于让大家初步了解,初步认识区块链。文章内容主要参考视频:使用Javascript建立区块链(https://www.youtube.com/playlist?list=PLzvRQMJ9HDiTqZmbtFisdXFxul5k0F-Q4)感谢原作者,本文在原视频基础上做了修改补充,并加入了个人理解。认识区块链区块链顾名思义是由区块连接而成的链,因此最基本的数据结构是块。每个块都含有时间戳,数据,散列,previousHash等信息。其中数据用来存储数据,previousHash是前一个区块的哈希值示意如下:哈希是对区块信息的摘要存储,哈希的好处是任意长度的信息经过哈希都可以映射成固定长度的字符串,如可用SHA256:calculateHash() { return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();}块的数据结构块的最基本数据结构如下:class Block { constructor(timestamp, data, previousHash = ‘’) { this.timestamp = timestamp; this.data = data; this.previousHash = previousHash; //对hash的计算必须放在最后,保证所有数据赋值正确后再计算 this.hash = this.calculateHash(); } calculateHash() { return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString(); }}BlockChain的数据结构多个区块链接而成BlockChain,显然可用用数组或链表来表示,如:class BlockChain { constructor() { this.chain = []; }}创世区块正所谓万物始于一,区块链的第一个区块总是需要人为来手动创建,这个区块的previousHash为空,如:createGenesisBlock() { return new Block(“2018-11-11 00:00:00”, “Genesis block of simple chain”, “”);}区块链的构造方法也应改为:class BlockChain { constructor() { this.chain = [this.createGenesisBlock()]; }}添加区块每新加一个区块,必须保证与原有区块链连接起来,即:class BlockChain { getLatestBlock() { return this.chain[this.chain.length - 1]; } addBlock(newBlock) { //新区块的前一个hash值是现有区块链的最后一个区块的hash值; newBlock.previousHash = this.getLatestBlock().hash; //重新计算新区块的hash值(因为指定了previousHash); newBlock.hash = newBlock.calculateHash(); //把新区块加入到链中; this.chain.push(newBlock); } …}校验区块链区块链数据结构的核心是保证前后链接,无法篡改,但是如果有人真的篡改了某个区块,我们该如何校验发现呢?最笨也是最自然是想法就是遍历所有情况,逐一校验,如:isChainValid() { //遍历所有区块 for (let i = 1; i < this.chain.length; i++) { const currentBlock = this.chain[i]; const previousBlock = this.chain[i - 1]; //重新计算当前区块的hash值,若发现hash值对不上,说明该区块有数据被篡改,hash值未重新计算 if (currentBlock.hash !== currentBlock.calculateHash()) { console.error(“hash not equal: " + JSON.stringify(currentBlock)); return false; } //判断当前区块的previousHash是否真的等于前一个区块的hash,若不等,说明前一个区块被篡改,虽然hash值被重新计算正确,但是后续区块的hash值并未重新计算,导致整个链断裂 if (currentBlock.previousHash !== previousBlock.calculateHash) { console.error(“previous hash not right: " + JSON.stringify(currentBlock)); return false; } } return true;}跑吧跑起来看看,即:let simpleChain = new BlockChain();simpleChain.addBlock(new Block(“2018-11-11 00:00:01”, {amount: 10}));simpleChain.addBlock(new Block(“2018-11-11 00:00:02”, {amount: 20}));console.log(JSON.stringify(simpleChain, null, 4));console.log(“is the chain valid? " + simpleChain.isChainValid());结果如下:ali-186590cc4a7f:simple-chain shanyao$ node main_1.js { “chain”: [ { “timestamp”: “2018-11-11 00:00:00”, “data”: “Genesis block of simple chain”, “previousHash”: “”, “hash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89” }, { “timestamp”: “2018-11-11 00:00:01”, “data”: { “amount”: 10 }, “previousHash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”, “hash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529” }, { “timestamp”: “2018-11-11 00:00:02”, “data”: { “amount”: 20 }, “previousHash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”, “hash”: “274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34” } ]}is the chain valid? true注意看其中的previousHash与哈希,确实是当前区块的previousHash指向前一个区块的哈希值。篡改下试试都说区块链不可篡改,是真的吗让我们篡改第2个区块试试,如?let simpleChain = new BlockChain();simpleChain.addBlock(new Block(“2018-11-11 00:00:01”, {amount: 10}));simpleChain.addBlock(new Block(“2018-11-11 00:00:02”, {amount: 20}));console.log(“is the chain valid? " + simpleChain.isChainValid());//将第2个区块的数据,由10改为15simpleChain.chain[1].data = {amount: 15};console.log(“is the chain still valid? " + simpleChain.isChainValid());console.log(JSON.stringify(simpleChain, null, 4));结果如下:ali-186590cc4a7f:simple-chain shanyao$ node main_1.js is the chain valid? truehash not equal: {“timestamp”:“2018-11-11 00:00:01”,“data”:{“amount”:15},“previousHash”:“fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”,“hash”:“150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”}is the chain still valid? false{ “chain”: [ { “timestamp”: “2018-11-11 00:00:00”, “data”: “Genesis block of simple chain”, “previousHash”: “”, “hash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89” }, { “timestamp”: “2018-11-11 00:00:01”, “data”: { “amount”: 15 }, “previousHash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”, “hash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529” }, { “timestamp”: “2018-11-11 00:00:02”, “data”: { “amount”: 20 }, “previousHash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”, “hash”: “274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34” } ]}显然,篡改了数据之后,哈希值并未重新计算,导致该区块的哈希值对不上。再篡改下试试那么,如果我们聪明点,篡改后把哈希值也重新计算会如何?let simpleChain = new BlockChain();simpleChain.addBlock(new Block(“2018-11-11 00:00:01”, {amount: 10}));simpleChain.addBlock(new Block(“2018-11-11 00:00:02”, {amount: 20}));console.log(“is the chain valid? " + simpleChain.isChainValid());//篡改后重新计算hash值simpleChain.chain[1].data = {amount: 15};simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash();console.log(“is the chain still valid? " + simpleChain.isChainValid());console.log(JSON.stringify(simpleChain, null, 4));结果如下:ali-186590cc4a7f:simple-chain shanyao$ node main_1.js is the chain valid? trueprevious hash not right: {“timestamp”:“2018-11-11 00:00:02”,“data”:{“amount”:20},“previousHash”:“150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”,“hash”:“274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34”}is the chain still valid? false{ “chain”: [ { “timestamp”: “2018-11-11 00:00:00”, “data”: “Genesis block of simple chain”, “previousHash”: “”, “hash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89” }, { “timestamp”: “2018-11-11 00:00:01”, “data”: { “amount”: 15 }, “previousHash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”, “hash”: “74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1” }, { “timestamp”: “2018-11-11 00:00:02”, “data”: { “amount”: 20 }, “previousHash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”, “hash”: “274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34” } ]}显然,第3个区块的previousHash并未指向第2个区块的哈希值。是真的无法篡改吗其实并不是,如果我们再聪明一点,把后续区块的哈希值也重新计算一下,不就OK了吗?确实如此,如:let simpleChain = new BlockChain();simpleChain.addBlock(new Block(“2018-11-11 00:00:01”, {amount: 10}));simpleChain.addBlock(new Block(“2018-11-11 00:00:02”, {amount: 20}));console.log(“is the chain valid? " + simpleChain.isChainValid());//篡改第2个区块simpleChain.chain[1].data = {amount: 15};simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash();//并把第3个区块也重新计算simpleChain.chain[2].previousHash = simpleChain.chain[1].hash;simpleChain.chain[2].hash = simpleChain.chain[2].calculateHash();console.log(“is the chain still valid? " + simpleChain.isChainValid());console.log(JSON.stringify(simpleChain, null, 4本文作者:扁鹊他大哥阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

December 4, 2018 · 3 min · jiezi

JSON数据从MongoDB迁移到MaxCompute最佳实践

摘要: 本文为您介绍如何利用DataWorks数据集成直接从MongoDB提取JSON字段到MaxCompute。数据及账号准备首先您需要将数据上传至您的MongoDB数据库。本例中使用阿里云的云数据库 MongoDB 版,网络类型为VPC(需申请公网地址,否则无法与DataWorks默认资源组互通),测试数据如下。{ “store”: { “book”: [ { “category”: “reference”, “author”: “Nigel Rees”, “title”: “Sayings of the Century”, “price”: 8.95 }, { “category”: “fiction”, “author”: “Evelyn Waugh”, “title”: “Sword of Honour”, “price”: 12.99 }, { “category”: “fiction”, “author”: “J. R. R. Tolkien”, “title”: “The Lord of the Rings”, “isbn”: “0-395-19395-8”, “price”: 22.99 } ], “bicycle”: { “color”: “red”, “price”: 19.95 } }, “expensive”: 10}登录MongoDB的DMS控制台,本例中使用的数据库为 admin,集合为 userlog,您可以在查询窗口使用db.userlog.find().limit(10)命令查看已上传好的数据,如下图所示。 此外,需提前在数据库内新建用户,用于DataWorks添加数据源。本例中使用命令db.createUser({user:“bookuser”,pwd:“123456”,roles:[“root”]}),新建用户名为 bookuser,密码为 123456,权限为root。使用DataWorks提取数据到MaxCompute新增MongoDB数据源进入DataWorks数据集成控制台,新增MongoDB类型数据源。 具体参数如下所示,测试数据源连通性通过即可点击完成。由于本文中MongoDB处于VPC环境下,因此 数据源类型需选择 有公网IP。 访问地址及端口号可通过在MongoDB管理控制台点击实例名称获取,如下图所示。 新建数据同步任务在DataWorks上新建数据同步类型节点。 新建的同时,在DataWorks新建一个建表任务,用于存放JSON数据,本例中新建表名为mqdata。 表参数可以通过图形化界面完成。本例中mqdata表仅有一列,类型为string,列名为MQ data。 完成上述新建后,您可以在图形化界面进行数据同步任务参数的初步配置,如下图所示。选择目标数据源名称为odps_first,选择目标表为刚建立的mqdata。数据来源类型为MongoDB,选择我们刚创建的数据源mongodb_userlog。完成上述配置后, 点击转换为脚本,跳转到脚本模式。 脚本模式代码示例如下。{ “type”: “job”, “steps”: [ { “stepType”: “mongodb”, “parameter”: { “datasource”: “mongodb_userlog”, //数据源名称 “column”: [ { “name”: “store.bicycle.color”, //JSON字段路径,本例中提取color值 “type”: “document.document.string” //本栏目的字段数需和name一致。假如您选取的JSON字段为一级字段,如本例中的expensive,则直接填写string即可。 } ], “collectionName //集合名称”: “userlog” }, “name”: “Reader”, “category”: “reader” }, { “stepType”: “odps”, “parameter”: { “partition”: “”, “isCompress”: false, “truncate”: true, “datasource”: “odps_first”, “column”: [ //MaxCompute表列名 “mqdata” ], “emptyAsNull”: false, “table”: “mqdata” }, “name”: “Writer”, “category”: “writer” } ], “version”: “2.0”, “order”: { “hops”: [ { “from”: “Reader”, “to”: “Writer” } ] }, “setting”: { “errorLimit”: { “record”: "" }, “speed”: { “concurrent”: 2, “throttle”: false, “dmu”: 1 } }}完成上述配置后,点击运行接即可。运行成功日志示例如下所示。 结果验证在您的业务流程中新建一个ODPS SQL节点。 您可以输入 SELECT * from mqdata;语句,查看当前mqdata表中数据。当然这一步您也可以直接在MaxCompute客户端中输入命令运行。 本文作者:付帅阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 28, 2018 · 1 min · jiezi

一种巧妙的对象映射关系设计--JSON-ORM

项目介绍这是标准数据库封装的上半部分,智能查询(JSON-ORM)的实现。完整代码:https://github.com/zhoutk/gels设计思路我们通用的ORM,基本模式都是想要脱离数据库的,几乎都在编程语言层面建立模型,由程序去与数据库打交道。虽然脱离了数据库的具体操作,但我们要建立各种模型文档,用代码去写表之间的关系等等操作,让初学者一时如坠云雾。我的想法是,将关系数据库拥有的完善设计工具之优势与微服务结合起来,数据设计提供结构信息;前端送到后端的json对象自动映射成为标准的SQL查询语句。只要我们理解了标准的SQL语言,我们就能够完成数据库查询操作。我的这种ORM方式,服务端不需要写一行代码,只需完成关系数据库的设计,就能为前端提供标准服务接口。并且遵循一套统一的接口(已经实践检验,满足百分之九九的查询需求)来实现数据库封装,达到业务层可以随意切换数据库的目的。数据库查询操作接口。export default interface IDao { select(tablename: string, params: object, fields?: Array<string>): Promise<any>; //自动生成sql语句 execSql(sql: string, values: Array<any>, params: object, fields?: Array<string>): Promise<any>; //执行手动sql语句}智能查询(JSON-ORM)查询保留字:fields, page, size, sort, search, lks, ins, ors, count, sum, groupfields, 定义查询结果字段,支持数组和逗号分隔字符串两种形式由前端来确定返回的数据库字段信息,这样后端的设计可以适用面更广泛,而不会造成网络带宽的浪费。在KOA2的框架下,GET请求要支持输入数组,只能把同一个key多次输入,如:age=11&age=22。这样很不方便,我实现了一个参数转换函数,针对数组提供多种输入形式。arryParse arryParse(arr): Array<any>|null { //返回值为数据或空值 try { if (Array.isArray(arr) || G.L.isNull(arr)) { //如果输入是数组或空,直接返回 return arr } else if (typeof arr === ‘string’) { //若是字符串 if (arr.startsWith(’[’)) { //数组的字符串形式,进行转换 arr = JSON.parse(arr) } else { //逗号拼接的字符串,mysql的驱动同时支持参数以字符串形式或数组形式提供, //所以这里可以不加判断,直接用split函数将字符串转化为数组 arr = arr.split(’,’) } } } catch (err) { arr = null //数组的字符串形式转换失败,刘明输入参数是错误的 } return arr }查询示例:请求URL: /rs/users?username=white&age=22&fields=[“username”,“age”]生成sql: SELECT username,age FROM users WHERE username = ? and age = ?page, size, sort, 分页排序在mysql中这比较好实现,limit来分页是很方便的,排序只需将参数直接拼接到order by后就好了。 查询示例:请求URL: /rs/users?page=1&size=10&sort=age desc生成sql: SELECT * FROM users ORDER BY age desc LIMIT 0,10search, 模糊查询切换参数,不提供时为精确匹配提供字段查询的精确匹配与模糊匹配的切换,实现过程中,注意参数化送入参数时,like匹配,是要在参数两边加%,而不是在占位符两边加%。另外,同一个字段匹配两次模糊查询,需要特别处理,我提供了一种巧妙的方法:value = pool.escape(value).replace(/', '/g, “%’ and " + key + " like ‘%”) //将值用escape编码,数组将转化为逗号连接的字符串,用正则全局替换,变成and连接value = value.substring(1, value.length - 1) //去掉两头多余的引号where += key + " like ‘%" + value + “%’” //补齐条件查询语句,这种方式,比用循环处理来得快捷,它统一了数组与其它形式的处理方式查询示例请求URL: /rs/users?username=i&password=1&searchins, lks, ors这是最重要的三种查询方式,如何找出它们之间的共同点,减少冗余代码是关键。ins, 数据库表单字段in查询,一字段对多个值,例: 查询示例:请求URL: /rs/users?ins=[“age”,11,22,26]生成sql: SELECT * FROM users WHERE age in ( ? )ors, 数据库表多字段精确查询,or连接,多个字段对多个值,支持null值查询,例: 查询示例:请求URL: /rs/users?ors=[“age”,1,“age”,22,“password”,null]生成sql: SELECT * FROM users WHERE ( age = ? or age = ? or password is null )lks, 数据库表多字段模糊查询,or连接,多个字段对多个值,支持null值查询,例:查询示例:请求URL: /rs/users?lks=[“username”,“i”,“password”,null]生成sql: SELECT * FROM users WHERE ( username like ? or password is null )count, sum这两个统计求和,处理方式也类似,查询时一般要配合group与fields使用。count, 数据库查询函数count,行统计,例:查询示例:请求URL: /rs/users?count=[“1”,“total”]&fields=[“username”]生成sql: SELECT username,count(1) as total FROM userssum, 数据库查询函数sum,字段求和,例:查询示例:请求URL: /rs/users?sum=[“age”,“ageSum”]&fields=[“username”]group, 数据库分组函数group,例: 查询示例:请求URL: /rs/users?group=age&count=["",“total”]&fields=[“age”]生成sql: SELECT age,count() as total FROM users GROUP BY age不等操作符查询支持支持的不等操作符有:>, >=, <, <=, <>, =;逗号符为分隔符,一个字段支持一或二个操作。 特殊处:使用"=“可以使某个字段跳过search影响,让模糊匹配与精确匹配同时出现在一个查询语句中一个字段一个操作,示例:查询示例:请求URL: /rs/users?age=>,10生成sql: SELECT * FROM users WHERE age> ?一个字段二个操作,示例:查询示例:请求URL: /rs/users?age=>,10,<=,35生成sql: SELECT * FROM users WHERE age> ? and age<= ?使用”=“去除字段的search影响,示例:查询示例:请求URL: /rs/users?age==,22&username=i&search生成sql: SELECT * FROM users WHERE age= ? and username like ?相关视频课程运用typescript进行node.js后端开发精要 nodejs实战之智能微服务快速开发框架 JSON-ORM(对象关系映射)设计与实现资源地址凝胶(gels)项目: https://github.com/zhoutk/gels视频讲座资料: https://github.com/zhoutk/sifou个人博客: https://github.com/zhoutk/blog ...

November 23, 2018 · 2 min · jiezi

JSON数据从OSS迁移到MaxCompute最佳实践

本文为您介绍如何利用DataWorks数据集成将JSON数据从OSS迁移到MaxCompute,并使用MaxCompute内置字符串函数GET_JSON_OBJECT提取JSON信息。数据上传OSS将您的JSON文件重命名后缀为TXT文件,并上传到OSS。本文中使用的JSON文件示例如下。{ “store”: { “book”: [ { “category”: “reference”, “author”: “Nigel Rees”, “title”: “Sayings of the Century”, “price”: 8.95 }, { “category”: “fiction”, “author”: “Evelyn Waugh”, “title”: “Sword of Honour”, “price”: 12.99 }, { “category”: “fiction”, “author”: “J. R. R. Tolkien”, “title”: “The Lord of the Rings”, “isbn”: “0-395-19395-8”, “price”: 22.99 } ], “bicycle”: { “color”: “red”, “price”: 19.95 } }, “expensive”: 10}将applog.txt文件上传到OSS,本文中OSS Bucket位于华东2区。 使用DataWorks导入数据到MaxCompute新增OSS数据源进入DataWorks数据集成控制台,新增OSS类型数据源。 具体参数如下所示,测试数据源连通性通过即可点击完成。Endpoint地址请参见OSS各区域的外网、内网地址,本例中为http://oss-cn-shanghai.aliyun… http://oss-cn-shanghai-internal.aliyuncs.com(由于本文中OSS和DataWorks项目处于同一个region中,本文选用后者,通过内网连接)。 新建数据同步任务在DataWorks上新建数据同步类型节点。 新建的同时,在DataWorks新建一个建表任务,用于存放JSON数据,本例中新建表名为mqdata。 表参数可以通过图形化界面完成。本例中mqdata表仅有一列,类型为string,列名为MQ data。 完成上述新建后,您可以在图形化界面配置数据同步任务参数,如下图所示。选择目标数据源名称为odps_first,选择目标表为刚建立的mqdata。数据来源类型为OSS,Object前缀可填写文件路径及名称。列分隔符使用TXT文件中不存在的字符即可,本文中使用 ^(对于OSS中的TXT格式数据源,Dataworks支持多字符分隔符,所以您可以使用例如 %&%#^$$^%这样很难出现的字符作为列分隔符,保证分割为一列)。 映射方式选择默认的同行映射即可。 点击左上方的切换脚本按钮,切换为脚本模式。修改fileFormat参数为: “fileFormat”:“binary”。该步骤可以保证OSS中的JSON文件同步到MaxCompute之后存在同一行数据中,即为一个字段。其他参数保持不变,脚本模式代码示例如下。{ “type”: “job”, “steps”: [ { “stepType”: “oss”, “parameter”: { “fieldDelimiterOrigin”: “^”, “nullFormat”: “”, “compress”: “”, “datasource”: “OSS_userlog”, “column”: [ { “name”: 0, “type”: “string”, “index”: 0 } ], “skipHeader”: “false”, “encoding”: “UTF-8”, “fieldDelimiter”: “^”, “fileFormat”: “binary”, “object”: [ “applog.txt” ] }, “name”: “Reader”, “category”: “reader” }, { “stepType”: “odps”, “parameter”: { “partition”: “”, “isCompress”: false, “truncate”: true, “datasource”: “odps_first”, “column”: [ “mqdata” ], “emptyAsNull”: false, “table”: “mqdata” }, “name”: “Writer”, “category”: “writer” } ], “version”: “2.0”, “order”: { “hops”: [ { “from”: “Reader”, “to”: “Writer” } ] }, “setting”: { “errorLimit”: { “record”: "" }, “speed”: { “concurrent”: 2, “throttle”: false, “dmu”: 1 } }}完成上述配置后,点击运行接即可。运行成功日志示例如下所示。 获取JSON字段信息在您的业务流程中新建一个ODPS SQL节点。 您可以首先输入 SELECT*from mqdata;语句,查看当前mqdata表中数据。当然这一步及后续步骤,您也可以直接在MaxCompute客户端中输入命令运行。 确认导入表中的数据结果无误后,您可以使用MaxCompute内建字符串函数GET_JSON_OBJECT获取您想要的JSON数据。本例中使用 SELECT GET_JSON_OBJECT(mqdata.MQdata,’$.expensive’) FROM mqdata;获取JSON文件中的 expensive值。如下图所示,可以看到已成功获取数据。 本文作者:付帅阅读原文本文为云栖社区原创内容,未经允许不得转载。 ...

November 15, 2018 · 1 min · jiezi

Python3爬取英雄联盟英雄皮肤大图

前言上篇文章,说到了,爬取LOL英雄皮肤的高清图片,最近有事,也没怎么去研究,所以,现在才去看了下,并且写了Python脚本来抓取皮肤图片。需要说明一下,这个脚本有部分英雄没有抓取到,但是具体原因,我目前还没搞懂,我是相当纳闷的。大家有兴趣的,可以看看后面遗留问题,一起研究下。爬虫思路初步尝试我先查看了network,并没有发现有可用的API;然后又用bs4去分析英雄列表页,但是请求到html里面,并没有英雄列表,在英雄列表的节点上,只有“正在加载中”这样的字样;同样的方法,分析英雄详情也是这种情况,所以我猜测,这些数据应该是Javascript负责加载的。继续尝试然后我就查看了英雄列表的源代码,查看外部引入的js文件,以及行内的js脚本,大概在368行,发现了有处理英雄列表的js注释,然后继续往下读这些代码,发现了第一个彩蛋,也就是他引入了一个champion.js的文件,我猜测,这个应该就是英雄列表大全了,然后我打开了这个链接的js,一眼看过去,黑麻麻一片,然后格式化了一下压缩的js,确定这就是英雄列表的js数据文件了。接着尝试前面通过查看列表的源代码,找到了英雄列表的js数据文件,那么,我继续随机点开了一个英雄的详情,然后查看英雄详情源代码,然后大概在568行看到有一个showSkin的js方法,通过这里,发现了第二个彩蛋,也就是皮肤图片的URL地址拼接方法。最后尝试上面找到了皮肤图片URL的拼接方法,并且发现了一行很关键的代码var skin =LOLherojs.champion[heroid].data.skins,也就是,这个skin变量,就是英雄皮肤的所有图片数组,但是这个文件内,并没有LOLherojs这个变量,也就是外部引入的,所以,需要继续查看下面的源代码,找到引入这个变量的位置,果不其然,在757行,发现了最后一个彩蛋,也就是,英雄皮肤的js文件,通过这里可以知道,每个英雄都有一个单独的js文件,并且知道了这个js文件的URL拼接方法。思路总结通过上面的分析,我们就得到了爬取LOL皮肤图片的所有数据准备了,也就是,直接,只需要提取js中的英雄列表以及英雄详情数据,就可实现我们的需求了。下面是运行后抓取到的图片……运行环境Python运行环境:python3.6 用到的模块:requests、json、urllib、os 未安装的模块,请使用pip instatll进行安装,例如:pip install requests完整代码其他啥的废话就不多说了,直接上完整代码,有问题,直接留言给我就行,另外,代码已上传GitHub。再说明一下,那些有问题的英雄详情的js文件,大家有时间也可以琢磨下,或者有其他的更加快捷的爬取这些图片的方法,也可以拿出来交流和讨论,谢谢。#!/usr/bin/env python# -- coding: utf-8 --“““抓取英雄联盟英雄全皮肤author: gxcuizydate: 2018-11-13"““import requestsimport jsonfrom urllib import parseimport osclass GetLolSkin(object): “““抓取LOL英雄皮肤””” def init(self): “““初始化变量””” self.hero_url = ‘https://lol.qq.com/biz/hero/champion.js' self.hero_detail_url = ‘http://lol.qq.com/biz/hero/' self.skin_folder = ‘skin’ self.skin_url = ‘https://ossweb-img.qq.com/images/lol/web201310/skin/big' @staticmethod def get_html(url): “““下载html””” request = requests.get(url) request.encoding = ‘gbk’ if request.status_code == 200: return request.text else: return “{}” def get_hero_list(self): “““获取英雄的完整信息列表””” hero_js = self.get_html(self.hero_url) # 删除左右的多余信息,得到json数据 out_left = “if(!LOLherojs)var LOLherojs={};LOLherojs.champion=” out_right = ‘;’ hero_list = hero_js.replace(out_left, ‘’).rstrip(out_right) return json.loads(hero_list) def get_hero_info(self, hero_id): “““获取英雄的详细信息””” # 获取js详情 detail_url = parse.urljoin(self.hero_detail_url, hero_id + ‘.js’) detail_js = self.get_html(detail_url) # 删除左右的多余信息,得到json数据 out_left = “if(!herojs)var herojs={champion:{}};herojs[‘champion’][%s]=” % hero_id out_right = ‘;’ hero_info = detail_js.replace(out_left, ‘’).rstrip(out_right) return json.loads(hero_info) def download_skin_list(self, skin_list, hero_name): “““下载皮肤列表””” # 循环下载皮肤 for skin_info in skin_list: # 拼接图片名字 if skin_info[’name’] == ‘default’: skin_name = ‘默认皮肤’ else: if ’ ’ in skin_info[’name’]: name_info = skin_info[’name’].split(’ ‘) skin_name = name_info[0] else: skin_name = skin_info[’name’] hero_skin_name = hero_name + ‘-’ + skin_name + ‘.jpg’ self.download_skin(skin_info[‘id’], hero_skin_name) def download_skin(self, skin_id, skin_name): “““下载皮肤图片””” # 下载图片 img_url = self.skin_url + skin_id + ‘.jpg’ request = requests.get(img_url) if request.status_code == 200: print(‘downloading……%s’ % skin_name) img_path = os.path.join(self.skin_folder, skin_name) with open(img_path, ‘wb’) as img: img.write(request.content) else: print(‘img error!’) def make_folder(self): “““初始化,创建图片文件夹””” if not os.path.exists(self.skin_folder): os.mkdir(self.skin_folder) def run(self): # 获取英雄列表信息 hero_json = self.get_hero_list() hero_keys = hero_json[‘keys’] # 循环遍历英雄 for hero_id, hero_code in hero_keys.items(): hero_name = hero_json[‘data’][hero_code][’name’] hero_info = self.get_hero_info(hero_id) if hero_info: skin_list = hero_info[‘result’][hero_id][‘skins’] # 下载皮肤 self.download_skin_list(skin_list, hero_name) else: print(‘英雄【%s】的皮肤获取有问题……’ % hero_name)# 程序执行入口if name == ‘main’: lol = GetLolSkin() # 创建图片存储文件 lol.make_folder() # 执行脚本 lol.run() ...

November 14, 2018 · 2 min · jiezi

Characters_of_the_Three_Kingdoms - 三国人物结构化数据

Characters_of_the_Three_Kingdoms - 三国人物结构化数据三国人物结构化数据为什么会有这个项目需求1:摆脱网上那些长篇累牍的文章;需求2:只是想简单查看下人物姓甚名谁、生辰八字、家住何地、三姑六婆;需求3:只是想简单查看下人物的历史简介、演义简介;需求4:只是想简单查看下人物的历史评价;需求5:只是想简单查看下人物的…需求6:想集中查看多个人物的资料;需求7:想获取完整而不累赘的结构化数据,自己开发应用尽情发挥;…需求N:…有了数据能干嘛有了数据,除了不能上天入地,剩下的就看少年你自己的活泼思想了。数据来源数据主要整理自 维基百科 、百度百科 和其他网络资源。数据展示 DEMO所有已经完成的人物数据可查看数据展示 DEMO。DEMO 页面使用 ajax 获取 characters 文件夹的 json 文件,若要本地运行 DEMO 页面,需本地启动 server。将项目 clone 到本地后,执行:npm run start或gulp然后浏览器打开 localhost:4300 即可。数据示例{ // 姓名 “name”: “刘备”, // 字 “courtesyName”: “玄德”, // 号 “pseudonym”: null, // 其他称谓 “aliase”: [ { “name”: “汉先主”, “desc”: null }, { “name”: “先主”, “desc”: “三国志、华阳国志等称为先主” }, { “name”: “汉主”, “desc”: “资治通鉴称刘备父子为汉主” } ], // 乳名、小名、小字 “infantName”: null, // 性别:1 男,2 女 “gender”: 1, // 头像 “avatar”: “./images/avatars/刘备.jpg”, // 所属势力 “faction”: “蜀汉”, // 出生日期 “birthdate”: “161年”, // 出生地点:古时地名 “birthplace”: “幽州涿郡涿县”, // 出生地点:现在地名 “birthplacePresentDay”: “河北省涿州市”, // 逝世日期 “deathdate”: “223年6月10日”, // 逝世地点:古时地名 “deathplace”: “白帝城永安宫”, // 逝世地点:现在地名 “deathplacePresentDay”: “重庆市奉节县”, // 在位时期 “tenure”: “汉中王:219年-221年;蜀主:221年5月15日-223年6月10日”, // 职位 “position”: [“蜀国皇帝”], // 封爵 “peerage”: null, // 封地 “enfeoffment”: null, // 侍奉的帝王 “monarch”: null, // 谥号 “posthumousName”: [“昭烈皇帝”], // 庙号 “templeName”: [“烈祖”], // 世系、氏族 “genealogy”: null, // 历史上的简介 “historicalBriefIIntroduction”: “蜀汉的开国皇帝,相传是汉景帝之子中山靖王刘胜的后代…”, // 演义上的简介 “novelisticBriefIIntroduction”: “刘备,蜀汉的开国皇帝,汉景帝之子中山靖王刘胜的后代…”, // 家庭成员 // 若名不详,则 name 字段为 名不详 “family”: { “father”: { “character”: [ { “name”: “刘弘”, “desc”: “东汉末年的州郡小官” } ], “desc”: null }, “mother”: { “character”: [ { “name”: “名不详”, “desc”: null } ], “desc”: null }, “brothers”: null, “sisters”: null, “spouse”: { “character”: [ { “name”: “甘夫人”, “desc”: “沛人,妾室,刘禅生母,曾于长阪被困,幸得赵云解救。后病死,谥皇思夫人,后再追谥昭烈皇后,与刘备合葬。” }, { “name”: “糜夫人”, “desc”: “麋竺之妹,于刘备在豫州落难时,麋竺将她嫁给刘备。” }, { “name”: “孙夫人”, “desc”: “孙权之妹,与刘备结为政治婚姻,后刘备入蜀,孙权接回她,再无记录。” }, { “name”: “穆皇后”, “desc”: “吴氏,吴懿之妹,刘瑁遗孀,刘备入蜀后纳为夫人,后为汉中王后。刘禅即位时,尊她为皇太后,称长乐宫。延熙八年病死,与刘备合葬。” } ], “desc”: “甘夫人被刘备纳为妾室时,因他“数丧嫡室”,而主内事。数位嫡室的身份已不可考。仅知建安元年(196年),吕布曾俘虏刘备的妻儿[32],转至广陵郡海西县时,又娶了麋夫人。次子刘永和三子刘理各自的生母亦不可考,仅知非正室且非同一人。” }, “sons”: { “character”: [ { “name”: “刘禅”, “desc”: “字公嗣,刘备长子。后登上皇位。乳名阿斗。” }, { “name”: “刘永”, “desc”: “字公寿,刘备次子。先为鲁王,后封为甘陵王。与刘禅宠臣黄皓不和,被刘禅疏远。后东迁洛阳,拜奉车都尉,封为乡侯。” }, { “name”: “刘理”, “desc”: “字奉孝,刘备三子。先为梁王,后封为安平王。早卒,谥为悼王。” }, { “name”: “刘封”, “desc”: “刘备养子。本姓寇,刘备入蜀后委任为将,但因关羽兵败时不予救援及逼反孟达丧失上庸之责遭赐死。” } ], “desc”: null }, “daughters”: { “character”: [ { “name”: “名不详”, “desc”: null }, { “name”: “名不详”, “desc”: null } ], “desc”: “有二女于刘备南逃至长坂时被曹将曹纯所俘。” } }, // 历史评价 “historicalEvaluations”: [ “刘元起:“吾宗中有此儿,非常人也。”(《三国志·蜀书·先主传第二》)”, “陈登:“雄姿杰出,有王霸之略,吾敬刘玄德。”(《三国志·魏书·桓二陈徐卫卢传第二十二》)”, “袁绍:“刘玄德弘雅有信义,今徐州乐戴之,诚副所望也。”(《三国志·蜀书·先主传第二》)” ]}已经完成的人物数据所有已经完成的人物数据可查看 DEMO。刘备 诸葛亮 曹操 孙权 张让 张角 张宝 张梁 张飞 张钧 张举 张纯 张济 张辽 张郃 张邈 张超 张杨 张虎 张统 张闿 张燕 张昭 张纮 张英 张勋 张绣 张鲁 张道陵 张衡 张???? 张南 张南 张武 张温 张温 张允 张横 张既 张卫 张松 张任 张肃 张翼 张著 张音 张爽 张裔 张达 张苞 张嶷 张休 张茂 张当 张特 张约 张缉 意见建议所有数据整理自网络,且鄙人才疏学浅,一定会有疏忽错误,欢迎指正。如果你有好的建议或意见,欢迎提 issue 反馈。联系方式Email: 851399101@qq.comLicenseMITCopyright (c) 2018-present, myvin ...

November 13, 2018 · 2 min · jiezi

浅谈easy-mock 最好的备胎没有之一

引言 今天我们来聊聊Mock,随着互联网发展,这两年前后端分离的开发模式兴起,Mock也从以住的幕后走上了台面,让更多的人而得知,以前传统的开发方式Mock大多局限在后端人员接触较多一些。 Mock已经是老生常谈了,网上一搜索就很多,各位前辈们都讲的很到位,但今天我只讲它——easy-mock。 为什么会突然来聊它,这个就说来话长了,个人简介里就说过,专注于分享工作中遇到的坑,但这一次不是我的坑,来源于QQ群友(# 如果您有想知道的故事,而正好我也会,那么就由我为您讲出来吧,欢迎留言哦 # ),请看下图:这里是@IT·平头哥联盟,我是首席填坑官—苏南,用心分享 做有温度的攻城狮。什么是Mock 什么是Mock?? Mock其实就是真实数据存在之前,即调试期间的代替品,是个虚拟的存在,用人话讲它就是个备胎,如女生长的好看,追她的人多,但又不是很满意但也不拒绝,在自己心仪的小哥哥出现之前,一直吊着你????!如何Mock数据?不要告诉我 new 一个哦,对象可以 new,备胎可new不出来呢????;方法一:最low的方式将 Mock 数据写在代码里、json文件里;方法二:利用 Charles 、Fiddler等代理工具,将 URL 映射到本地文件;方法三:本地起 Mock Server,即mockjs,有点麻烦每次修改了后还要重启服务,nodemon能解决,但开的东西多了,电脑卡出翔,维护也麻烦;方法四:规范些的公司自己已经集成了一套mock数据体系;重点来了:easy-mock一个在线 Mock 平台,活儿好又性感是你备胎的最佳选择。当然优秀的你可能还有很多其他的方式,欢迎补充。//mock 基本使用示例import Mock from “mockjs”;Mock.mock({ “code”: 0, “message”: “请求成功”, “data|20”: [{ “name”: “@cname”,//cname 中文,name 英文 “userId”: “@id”, “lastDate”: “@datetime” }]})什么是easy-mock,能给我们带来什么?Easy Mock 是一个可视化,并且能快速生成 模拟数据 的持久化服务,Easy Mock 支持基于 Swagger 创建项目,以节省手动创建接口的时间;简单点说:Easy Mock就是一个在线创建mock的服务平台,帮你省去你 配置、安装、起服务、维护、多人协作Mock数据不互通等一系列繁琐的操作, 它能在不用1秒钟的时间内给你所要的一切,呼之即来、挥之即去的2018最优秀备胎没有之一,完全不用担心负任何责任哦。更多优点它在等你去发现哦……深入浅出 - 简介就跟人一样长的再好看,了解过后才懂,一样东西也是如何,谁用谁知道;Easy Mock支持团队协作,也可以是个人项目,以下以个人项目为例,与多人协作没有区别,只是少了成员邀请;深入浅出 - Mock语法回顾@ip -> 随机输出一个ip;@id -> 随机输出长度18的字符,不接受参数;“array|1-10” -> 随机输出1-10长度的数组,也可以直接是固定长度;“object|2” -> 输出一个两个key值的对象,"@image()" 返回一个占位图url,支持size, background, foreground, format, text;等等,这里就不再一一介绍。深入浅出 - 创建一个接口它的写法,跟Mock.js一模一样,上面代码已经展示过,更多示例使用Easy Mock创建一个接口,请看下图:深入浅出 - 高阶用法 Function在线编辑,它也能支持 function ,是不是很优秀,能获取到全部请求头,可以让我们像写在js里一样写逻辑,写运算,当然它肯定是还有很多局限性的,如并不支持ES6,有一点需要注意的是 function 里要写传出Mock对象,不能直接@…,来看示例:对象描述MockMock 对象_req.url获得请求 url 地址_req.method获取请求方法_req.params获取 url 参数对象_req.querystring获取查询参数字符串(url中?后面的部分),不包含 ?_req.query将查询参数字符串进行解析并以对象的形式返回,如果没有查询参数字字符串则返回一个空对象_req.body当 post 请求以 x-www-form-urlencoded 方式提交时,我们可以拿到请求的参数对象…_req.cookies、ip、host等等,我只是一个代码的搬运,更详细请看这里//简单模拟登录,根据用户传入的参数,返回不同逻辑数据{ defaultName:function({_req}){ return _req.query.name; }, code: function({_req}){ return this.defaultName ? 0 : -97; }, message: function({_req}) { return this.defaultName ? “登录成功” : “参数错误”; }, data: function({req,Mock}){ return this.defaultName ? { token: Mock.mock("@guid()"), userId: Mock.mock("@id(5)"), cname: Mock.mock("@cname()"), name: Mock.mock("@name()"), avatar: Mock.mock("@image(200x100, #FF6600)"), other:"@IT·平头哥联盟-首席填坑官∙苏南 带你再谈Mock数据之easy-mock" }:{} }}深入浅出 - 在线调试再优秀的工程师写出的代码也一样要测试,没有人敢说自己的代码无Bug,Easy Mock 它是真的懂你的,已经为你准备好了,接口编写好后,立马就能让你测试,是不是觉得很棒棒呢??如果是你自己本地写mock数据,你需要重启服务、手动写参数、可能还需要整个测试页,知道你已经非常饥渴迫切的想要知道,具体的调试方式了:看不清吗??已经为你备好在线调试链接,支持POST、GET、PUT等方式,因gif图加载比较大,就不一一演示了结尾: 天下无不散之宴席,又到说再见的时候了,以上就是今天苏南为大家带来的分享,您GET到了吗?Easy Mock更多强大之处自己去折腾吧,#用心分享 做有温度的攻城狮#,希望今天的分享能给您带来些许成长,如果觉得不错记得点个赞哦,,顺便关注下方公众号就更棒了呢,每周为您推最新分享????????。更多文章:immutability因React官方出镜之使用总结分享!小程序项目之做完项目老板给我加了6k薪资~小程序项目之填坑小记面试踩过的坑,都在这里了~你应该做的前端性能优化之总结大全!如何给localStorage设置一个过期时间?动画一点点 - 如何用CSS3画出懂你的3D魔方?动画一点点 - 手把手教你如何绘制一辆会跑车SVG Sprites Icon的使用技巧作者:苏南 - 首席填坑官链接:https://blog.csdn.net/weixin…交流群:912594095、公众号:honeyBadger8本文原创,著作权归作者所有。商业转载请联系@IT·平头哥联盟获得授权,非商业转载请注明原链接及出处。 ...

November 7, 2018 · 1 min · jiezi