接口返回大量json数据

最近在做前端的一些事情。
使用echart绘图。遇到一个问题,就是用ajax 接收后端返回的json数据。测试发现速度很慢,调试发现后端返回的数据有54.7M,ajax接收时间在32-43秒左右,如图:
工作中遇到的问题 - 图1
项目使用spring mvc框架,服务端使用@ResponseBody 自动打包 HttpServletResponse的返回内容,return HashMap,返回类型是application/json
这是在使用Ehcache 后的结果。令笔者想不到的是,返回的数据竟有54.7M大小,由于前端等待时间较长,因此需要做些优化。
首先,有哪些优化手段呢?ajax的格式是这样的:

  1. $.ajax({
  2. type: "POST",
  3. url: '**/getModelData',
  4. data: {jobId:jobId},
  5. dataType:'json',
  6. cache: false,
  7. async: true,
  8. success: function(data){
  9. //
  10. }
  11. });

可以从同步/异步,cache入手。然而,异步通常用于加载dom,并不适用这里,网上一些异步方式讨论的也跟这里无关。笔者把cache 设置为true后,速度并没有提高。按理说,cache在接收第一次同样的数据后,会把数据临时缓存,下一次请求速度会快一些,实际发现,请求仍然是在返回后端的数据。没有看出明显提升。这让笔者有点奇怪。
既然无效,可不可以用一个js全局变量,临时存储后端返回的数据呢?这里每一个请求返回的数据大小都在几十M的规模,多请求几次,页面临时内存会有达到几百M的可能,这样是不是有些笨拙?
总之,并没有使用这样方式。剩下还有几种方式:
(1)压缩
(2)缓存
(3)服务端做优化。
首先是压缩。这是比较好的思路。tomcat,spring mvc,nginx 都提供压缩配置,主流的压缩格式是Gzip,恰好以上三者都提供。这里并没有用到nginx,所以,只考虑spring mvc和tomcat。
spring mvc 使用Gzip 需要一个GZIPResponseWrapper 类来继承HttpServletRespose,另外,fliter层需要GZIPFilter 实现Filter接口,简单说,就是再封装HttpServletResponse.
具体代码如下:

  1. import java.io.*;
  2. import javax.servlet.*;
  3. import javax.servlet.http.*;
  4. public class GZIPFilter implements Filter {
  5. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  6. throws IOException, ServletException {
  7. if (req instanceof HttpServletRequest) {
  8. HttpServletRequest request = (HttpServletRequest) req;
  9. HttpServletResponse response = (HttpServletResponse) res;
  10. String ae = request.getHeader("accept-encoding");
  11. if (ae != null && ae.indexOf("gzip") != -1) {
  12. GZIPResponseWrapper wrappedResponse = new GZIPResponseWrapper(response);
  13. chain.doFilter(req, wrappedResponse);
  14. wrappedResponse.finishResponse();
  15. return;
  16. }
  17. chain.doFilter(req, res);
  18. }
  19. }
  20. public void init(FilterConfig filterConfig) {
  21. // noop
  22. }
  23. public void destroy() {
  24. // noop
  25. }
  26. }
  27. public class GZIPResponseWrapper extends HttpServletResponseWrapper {
  28. protected HttpServletResponse origResponse = null;
  29. protected ServletOutputStream stream = null;
  30. protected PrintWriter writer = null;
  31. public GZIPResponseWrapper(HttpServletResponse response) {
  32. super(response);
  33. origResponse = response;
  34. }
  35. public ServletOutputStream createOutputStream() throws IOException {
  36. return (new GZIPResponseStream(origResponse));
  37. }
  38. public void finishResponse() {
  39. try {
  40. if (writer != null) {
  41. writer.close();
  42. } else {
  43. if (stream != null) {
  44. stream.close();
  45. }
  46. }
  47. } catch (IOException e) {}
  48. }
  49. public void flushBuffer() throws IOException {
  50. stream.flush();
  51. }
  52. public ServletOutputStream getOutputStream() throws IOException {
  53. if (writer != null) {
  54. throw new IllegalStateException("getWriter() has already been called!");
  55. }
  56. if (stream == null)
  57. stream = createOutputStream();
  58. return (stream);
  59. }
  60. public PrintWriter getWriter() throws IOException {
  61. if (writer != null) {
  62. return (writer);
  63. }
  64. if (stream != null) {
  65. throw new IllegalStateException("getOutputStream() has already been called!");
  66. }
  67. stream = createOutputStream();
  68. writer = new PrintWriter(new OutputStreamWriter(stream, "UTF-8"));
  69. return (writer);
  70. }
  71. public void setContentLength(int length) {}
  72. }
  73. public class GZIPResponseStream extends ServletOutputStream {
  74. protected ByteArrayOutputStream baos = null;
  75. protected GZIPOutputStream gzipstream = null;
  76. protected boolean closed = false;
  77. protected HttpServletResponse response = null;
  78. protected ServletOutputStream output = null;
  79. public GZIPResponseStream(HttpServletResponse response) throws IOException {
  80. super();
  81. closed = false;
  82. this.response = response;
  83. this.output = response.getOutputStream();
  84. baos = new ByteArrayOutputStream();
  85. gzipstream = new GZIPOutputStream(baos);
  86. }
  87. public void close() throws IOException {
  88. if (closed) {
  89. throw new IOException("This output stream has already been closed");
  90. }
  91. gzipstream.finish();
  92. byte[] bytes = baos.toByteArray();
  93. response.addHeader("Content-Length",
  94. Integer.toString(bytes.length));
  95. response.addHeader("Content-Encoding", "gzip");
  96. output.write(bytes);
  97. output.flush();
  98. output.close();
  99. closed = true;
  100. }
  101. public void flush() throws IOException {
  102. if (closed) {
  103. throw new IOException("Cannot flush a closed output stream");
  104. }
  105. gzipstream.flush();
  106. }
  107. public void write(int b) throws IOException {
  108. if (closed) {
  109. throw new IOException("Cannot write to a closed output stream");
  110. }
  111. gzipstream.write((byte)b);
  112. }
  113. public void write(byte b[]) throws IOException {
  114. write(b, 0, b.length);
  115. }
  116. public void write(byte b[], int off, int len) throws IOException {
  117. if (closed) {
  118. throw new IOException("Cannot write to a closed output stream");
  119. }
  120. gzipstream.write(b, off, len);
  121. }
  122. public boolean closed() {
  123. return (this.closed);
  124. }
  125. public void reset() {
  126. //noop
  127. }
  128. }

参考链接:
http://www.javablog.fr/javaweb-gzip-compression-protocol-http-filter-gzipresponsewrapper-gzipresponsewrapper.html
这是别人写好的,也是可用的。不过相对这个问题,改动比较大,改完还需要调试。因此,并没有采用。
有没有改动小一点的? tomcat,nginx也内置了Gzip压缩配置方式:

  1. <Connector port="8888" protocol="org.apache.coyote.http11.Http11NioProtocol"
  2. connectionTimeout="21000"
  3. redirectPort="28080"
  4. maxThreads="500"
  5. minSpareThreads="50" maxIdleTime="60000
  6. URIEncoding="UTF-8"
  7. compression="on"
  8. compressionMinSize="50"
  9. noCompressionUserAgents="gozilla, traviata" compressableMimeType="text/html,text/xml,text/plain,text/javascript,text/csv,application/javascript" />

加上去之后,发现没有效果。原因是compressableMimeType=”text/html,text/xml,text/plain,text/javascript,text/csv,application/javascript”
并不支持这里的数据类型,这里的数据类型是:application/json
在浏览器调试器可查看数据类型:
工作中遇到的问题 - 图2
觉得有点奇怪,这里用的是tomcat7,是否不支持application/json类型?又或者这个问题本身很少见,ajax就是用来传输少量数据的?于是又在stackoverflow上面找相关讨论。
找到一个帖子,发现一个逗比,他的服务端返回的数据高达2GB,问题跟笔者的类似。
工作中遇到的问题 - 图3
链接:
https://stackoverflow.com/questions/47991007/compress-and-send-large-string-as-spring-http-response
同行讨论说有提供Gzip压缩的,有提供其它方式优化的,但是一下子没看明白,改动比较大。之后又看到这样一段话:
工作中遇到的问题 - 图4
链接:
https://stackoverflow.com/questions/21410317/using-gzip-compression-with-spring-boot-mvc-javaconfig-with-restful?noredirect=1&lq=1
说的正是tomcat压缩方式,不巧的是,笔者没有注意到,他说的是tomcat默认支持这些数据类型,隐含意思是,不止这些数据类型。
而笔者误解tomcat只支持这些数据类型,不支持application/json 的Gzip压缩。
带着这个误解,笔者又查了一些资料,也没有找到简单的方式。既然没找到,笔者想是不是加上去试一下看看,于是便加上去,发现果然有效果,如图:
工作中遇到的问题 - 图5
而且压缩率惊人,压缩了17倍。有点怀疑,又用echart绘了图,并没发现数据异常。数据传输速度,提高了2-10秒。应该是解压的步骤也会耗时。
回去后,找了下笔者之前做的资料,发现Gzip压缩之前就有做过,按理说,也做过类似的优化方面的思考。可是,时间一长,反而什么也不记得了。所以还是写下来吧。
(2)缓存
可是即便这样,浏览器要对gzip数据解压,耗时也是挺大的。还需要再优化。想了几个小时,笔者尝试使用js 全局变量数组,把服务端的数据暂时存在全局变量,这样第一次请求跟之前一样,之后请求会快很多。问题是,这个会增加页面内存消耗,虽然有限制存取的数据对象个数。还是没有解决第一次请求的速度。这是全局变量(包含一个modelData)的占用内存:
工作中遇到的问题 - 图6
然后笔者又查询html5的函数,发现localStorage 可以尝试。localStorage 容量比cookie,session 都要大,有5M。尝试了一下,由于笔者这里的数据解压后会有几十M,所以localStorage 不适合。
不过笔者发现一个别人写好的js 库,是对localStorage 的一个应用。
链接地址:
https://github.com/WQTeam/jquery-ajax-cache
(3)服务器端优化
剩下的便只有服务器端优化。思考再三,笔者尝试减少服务器端返回的数据大小。经过分析,发现,是有减小空间的。于是针对每次请求,尽量只返回必要的数据,那些该次请求没有用到的数据就不返回,经过整理,返回的json数据有明显减小,反映在前端,就是响应变快很多。这是优化后的:
工作中遇到的问题 - 图7

echarts的GeoJSON文件压缩

echarts为了加快GeoJSON的传输速度,对GeoJSON文件进行了压缩, 对GeoJSON中的坐标信息进行了压缩了,减少了坐标的数据量。
echarts中的GeoJSON数据格式:
工作中遇到的问题 - 图8
采用的是zigzag的算法进行压缩的,“zigzag 的原理就是压缩多余的因补位造成数据变大的问题,它的原理是把符号位向右移到在最前一位,对负数除最后一位经行求为非;正数求不变。”
echarts内置解码算法,将坐标信息进行解析,在图上进行绘制。
压缩之前的中国行政区划数据:
工作中遇到的问题 - 图9
压缩之后的数据:
工作中遇到的问题 - 图10
压缩比例是相当高的,这在网络传输上能够加快传输速度,现在客户端的计算机性能都是可以的,解析并不需要很长的时间。
关键的压缩代码(摘自echarts源码):
工作中遇到的问题 - 图11
关键的解压代码(摘自echarts):
工作中遇到的问题 - 图12
在其他类型的地图开发时,涉及到大量的坐标信息,也可以按照此种方式进行压缩,之后在客户端解析数据,地图上显示,达到减少网络数据传输的目的。

http请求过多耗时

网站性能优化是属于每一个网站的必备过程,而最直接简单的就是http请求的优化,包含了减少http请求的数量,加快http请求的速度。
而本篇文章,将从http请求数量进行入手,将无用、无效请求全部过滤掉

  • 开始

本篇将基于axios开源库,对http请求进行封装,包含请求的缓存、重复请求的过滤两个小优化。

  • 第一步

首先建立一个http-helper.js文件,里面将基于axios进行上述相关功能的封装
首先里面的内容大概是这样的:

  1. import axios from 'axios';
  2. const http = axios.create();
  3. export default http

上述就是简单地导出了一个axios实例,供项目使用

  • 增加请求缓存功能

那么有了缓存功能,就要对缓存命中进行定义,我定义的缓存命中是指:http请求的url相同、请求参数相同、请求类型相同,以上三者都相同的情况下,就视为缓存允许命中,最后根据缓存过期时间,判断是否获取最新数据,还是从缓存中取。
下面理一下流程:

  1. 发起请求,设置请求是否缓存,缓存多长时间
  2. axios请求拦截,判断该请求是否设置缓存,是?则判断是否缓存命中、是否过期,否?则继续发起请求
  3. axios响应拦截,判断该请求结果是否缓存,是?则缓存数据,并设置key值、过期时间

针对上面的流程,需要有几点确认一下:

  1. 当缓存命中时,如何终止请求
    axios中,可以为每一个请求设置一个cancleToken,当调用请求取消方法的时候,则请求终止,并将终止的消息通过reject回传给请求方法。
  2. 当缓存命中时,并将缓存的数据通过resolve()返回给请求方法,而不是在reject中获取缓存数据

那么具体的代码可以是这样的:

  1. // http-helper.js
  2. import axios from 'axios';
  3. const http = axios.create();
  4. http.interceptors.request.use((config) => {
  5. /**
  6. * 为每一次请求生成一个cancleToken
  7. */
  8. const source = axios.CancelToken.source();
  9. config.cancelToken = source.token;
  10. /**
  11. * 尝试获取缓存数据
  12. */
  13. const data = storage.get(cryptoHelper.encrypt(
  14. config.url + JSON.stringify(config.data) + (config.method || ''),
  15. ));
  16. /**
  17. * 判断缓存是否命中,是否未过期
  18. */
  19. if (data && (Date.now() <= data.exppries)) {
  20. console.log(`接口:${config.url} 缓存命中 -- ${Date.now()} -- ${data.exppries}`);
  21. /**
  22. * 将缓存数据通过cancle方法回传给请求方法
  23. */
  24. source.cancel(JSON.stringify({
  25. type: CANCELTTYPE.CACHE,
  26. data: data.data,
  27. }));
  28. }
  29. return config;
  30. });
  31. http.interceptors.response.use((res) => {
  32. if (res.data && res.data.type === 0) {
  33. if (res.config.data) {
  34. /**
  35. * 获取请求体参数
  36. */
  37. const dataParse = JSON.parse(res.config.data);
  38. if (dataParse.cache) {
  39. if (!dataParse.cacheTime) {
  40. dataParse.cacheTime = 1000 * 60 * 3;
  41. }
  42. /**
  43. * 加密
  44. * 缓存
  45. */
  46. storage.set(cryptoHelper.encrypt(res.config.url + res.config.data + (res.config.method || '')), {
  47. data: res.data.data, // 响应体数据
  48. exppries: Date.now() + dataParse.cacheTime, // 设置过期时间
  49. });
  50. console.log(`接口:${res.config.url} 设置缓存,缓存时间: ${dataParse.cacheTime}`);
  51. }
  52. }
  53. return res.data.data;
  54. } else {
  55. return Promise.reject('接口报错了!');
  56. }
  57. });
  58. /**
  59. * 封装 get、post 请求
  60. * 集成接口缓存过期机制
  61. * 缓存过期将重新请求获取最新数据,并更新缓存
  62. * 数据存储在localstorage
  63. * {
  64. * cache: true
  65. * cacheTime: 1000 * 60 * 3 -- 默认缓存3分钟
  66. * }
  67. */
  68. const httpHelper = {
  69. get(url, params) {
  70. return new Promise((resolve, reject) => {
  71. http.get(url, params).then(async (res) => {
  72. resolve(res);
  73. }).catch((error) => {
  74. if (axios.isCancel(error)) {
  75. const cancle = JSON.parse(error.message);
  76. if (cancle.type === CANCELTTYPE.REPEAT) {
  77. return resolve([]);
  78. } else {
  79. return resolve(cancle.data);
  80. }
  81. } else {
  82. return reject(error);
  83. }
  84. });
  85. });
  86. },
  87. post(url: string, params: any) {
  88. return new Promise((resolve, reject) => {
  89. http.post(url, params).then(async (res) => {
  90. resolve(res);
  91. }).catch((error: AxiosError) => {
  92. if (axios.isCancel(error)) {
  93. const cancle = JSON.parse(error.message);
  94. if (cancle.type === CANCELTTYPE.REPEAT) {
  95. return resolve(null);
  96. } else {
  97. return resolve(cancle.data);
  98. }
  99. } else {
  100. return reject(error);
  101. }
  102. });
  103. });
  104. },
  105. };
  106. export default httpHelper

上面代码中,有些东西没有解释到:

  1. 其中storage是自己封装的缓存数据类,可以有.get、.set等方法,cryptoHelper是封装的MD5加密库,主要是通过MD5加密请求url、请求数据、请求类型等拼接的字符串,通过加密后的key来获取缓存中的数据(因为拼接后的字符串太长,通过MD5加密一下,会短很多)
  2. 为什么要单独封装一个 httpHelper,因为axios.CancelToken.source().cancle(***)中的信息,只能在reject中取到,为了缓存命中时,仍然能在then中获取到正确的数据,则需要单独处理一下这个情况。
  • 增加重复请求过滤功能

规则: 以最新的请求为主,即最新的重复请求,会将之前的重复请求中断掉
大概流程如下:

  1. 发起请求
  2. axios请求拦截,判断请求列表数组中,是否存在相同的请求,是?终止之前所有重复请求,否?将当次请求添加进请求数组中,最终都继续会请求
  3. axios响应拦截器,将当次请求从请求数组中删除

具体代码如下:

  1. // http-helper.js
  2. import axios from 'axios';
  3. const http = axios.create();
  4. const pendingRequests = [];
  5. http.interceptors.request.use((config) => {
  6. /**
  7. * 为每一次请求生成一个cancleToken
  8. */
  9. const source = axios.CancelToken.source();
  10. config.cancelToken = source.token;
  11. // .... 省略部分代码
  12. /**
  13. * 重复请求判断
  14. * 同url,同请求类型判定为重复请求
  15. * 以最新的请求为准
  16. */
  17. const md5Key = cryptoHelper.encrypt(config.url + (config.method || ''));
  18. /**
  19. * 将之前的重复且未完成的请求全部取消
  20. */
  21. const hits = pendingRequests.filter((item) => item.md5Key === md5Key);
  22. if (hits.length > 0) {
  23. hits.forEach((item) => item.source.cancel(JSON.stringify({
  24. type: CANCELTTYPE.REPEAT,
  25. data: '重复请求,以取消',
  26. })));
  27. }
  28. /**
  29. * 将当前请求添加进请求对列中
  30. */
  31. pendingRequests.push({
  32. md5Key,
  33. source,
  34. });
  35. return config;
  36. });
  37. http.interceptors.response.use((res) => {
  38. /**
  39. * 不论请求是否成功,
  40. * 将本次完成的请求从请求队列中移除
  41. */
  42. // 以同样的加密方式(MD5)获取加密字符串
  43. const md5Key = cryptoHelper.encrypt(res.config.url + (res.config.method || ''));
  44. const index = pendingRequests.findIndex((item) => item.md5Key === md5Key);
  45. if (index > -1) {
  46. pendingRequests.splice(index, 1);
  47. }
  48. // .... 省略部分代码
  49. });
  50. // .... 省略部分代码

其实逻辑很简单,通过一个数组去维护请求列表即可

  • 最终成果物

是用ts写的,需要使用可以改成js
由于缓存和终止重复请求,都需要用到source.cancle,因此需要一个type值,区分是缓存命中终止,还是重复请求终止,代码中是CANCELTTYPE常量。
http-helper.ts

  1. import axios, {CancelTokenSource, AxiosResponse, AxiosRequestConfig, AxiosError} from 'axios';
  2. import Storage from './storage-helper';
  3. import CryptoHelper from './cryptoJs-helper';
  4. const CANCELTTYPE = {
  5. CACHE: 1,
  6. REPEAT: 2,
  7. };
  8. interface ICancel {
  9. data: any;
  10. type: number;
  11. }
  12. interface Request {
  13. md5Key: string;
  14. source: CancelTokenSource;
  15. }
  16. const pendingRequests: Request[] = [];
  17. const http = axios.create();
  18. const storage = new Storage();
  19. const cryptoHelper = new CryptoHelper('cacheKey');
  20. http.interceptors.request.use((config: AxiosRequestConfig) => {
  21. /**
  22. * 为每一次请求生成一个cancleToken
  23. */
  24. const source = axios.CancelToken.source();
  25. config.cancelToken = source.token;
  26. /**
  27. * 缓存命中判断
  28. * 成功则取消当次请求
  29. */
  30. const data = storage.get(cryptoHelper.encrypt(
  31. config.url + JSON.stringify(config.data) + (config.method || ''),
  32. ));
  33. if (data && (Date.now() <= data.exppries)) {
  34. console.log(`接口:${config.url} 缓存命中 -- ${Date.now()} -- ${data.exppries}`);
  35. source.cancel(JSON.stringify({
  36. type: CANCELTTYPE.CACHE,
  37. data: data.data,
  38. }));
  39. }
  40. /**
  41. * 重复请求判断
  42. * 同url,同请求类型判定为重复请求
  43. * 以最新的请求为准
  44. */
  45. const md5Key = cryptoHelper.encrypt(config.url + (config.method || ''));
  46. /**
  47. * 将之前的重复且未完成的请求全部取消
  48. */
  49. const hits = pendingRequests.filter((item) => item.md5Key === md5Key);
  50. if (hits.length > 0) {
  51. hits.forEach((item) => item.source.cancel(JSON.stringify({
  52. type: CANCELTTYPE.REPEAT,
  53. data: '重复请求,以取消',
  54. })));
  55. }
  56. /**
  57. * 将当前请求添加进请求对列中
  58. */
  59. pendingRequests.push({
  60. md5Key,
  61. source,
  62. });
  63. return config;
  64. });
  65. http.interceptors.response.use((res: AxiosResponse) => {
  66. /**
  67. * 不论请求是否成功,
  68. * 将本次完成的请求从请求队列中移除
  69. */
  70. // 以同样的加密方式(MD5)获取加密字符串
  71. const md5Key = cryptoHelper.encrypt(res.config.url + (res.config.method || ''));
  72. const index = pendingRequests.findIndex((item) => item.md5Key === md5Key);
  73. if (index > -1) {
  74. pendingRequests.splice(index, 1);
  75. }
  76. if (res.data && res.data.type === 0) {
  77. if (res.config.data) {
  78. const dataParse = JSON.parse(res.config.data);
  79. if (dataParse.cache) {
  80. if (!dataParse.cacheTime) {
  81. dataParse.cacheTime = 1000 * 60 * 3;
  82. }
  83. storage.set(cryptoHelper.encrypt(res.config.url + res.config.data + (res.config.method || '')), {
  84. data: res.data.data,
  85. exppries: Date.now() + dataParse.cacheTime,
  86. });
  87. console.log(`接口:${res.config.url} 设置缓存,缓存时间: ${dataParse.cacheTime}`);
  88. }
  89. }
  90. return res.data.data;
  91. } else {
  92. return Promise.reject('接口报错了!');
  93. }
  94. });
  95. /**
  96. * 封装 get、post 请求
  97. * 集成接口缓存过期机制
  98. * 缓存过期将重新请求获取最新数据,并更新缓存
  99. * 数据存储在localstorage
  100. * {
  101. * cache: true
  102. * cacheTime: 1000 * 60 * 3 -- 默认缓存3分钟
  103. * }
  104. */
  105. const httpHelper = {
  106. get(url: string, params: any) {
  107. return new Promise((resolve, reject) => {
  108. http.get(url, params).then(async (res: AxiosResponse) => {
  109. resolve(res);
  110. }).catch((error: AxiosError) => {
  111. if (axios.isCancel(error)) {
  112. const cancle: ICancel = JSON.parse(error.message);
  113. if (cancle.type === CANCELTTYPE.REPEAT) {
  114. return resolve([]);
  115. } else {
  116. return resolve(cancle.data);
  117. }
  118. } else {
  119. return reject(error);
  120. }
  121. });
  122. });
  123. },
  124. post(url: string, params: any) {
  125. return new Promise((resolve, reject) => {
  126. http.post(url, params).then(async (res: AxiosResponse) => {
  127. resolve(res);
  128. }).catch((error: AxiosError) => {
  129. if (axios.isCancel(error)) {
  130. const cancle: ICancel = JSON.parse(error.message);
  131. if (cancle.type === CANCELTTYPE.REPEAT) {
  132. return resolve(null);
  133. } else {
  134. return resolve(cancle.data);
  135. }
  136. } else {
  137. return reject(error);
  138. }
  139. });
  140. });
  141. },
  142. };
  143. export default httpHelper;

cryptoJs-helper.ts

  1. import cryptoJs from 'crypto-js';
  2. class CryptoHelper {
  3. public key: string;
  4. constructor(key: string) {
  5. /**
  6. * 如需秘钥,可以在实例化时传入
  7. */
  8. this.key = key;
  9. }
  10. /**
  11. * 加密
  12. * @param word
  13. */
  14. public encrypt(word: string | undefined): string {
  15. if (!word) {
  16. return '';
  17. }
  18. const encrypted = cryptoJs.MD5(word);
  19. return encrypted.toString();
  20. }
  21. }
  22. export default CryptoHelper;

storage-helper.ts

  1. class Storage {
  2. public get(key: string | undefined) {
  3. if (!key) { return; }
  4. const text = localStorage.getItem(key);
  5. try {
  6. if (text) {
  7. return JSON.parse(text);
  8. } else {
  9. localStorage.removeItem(key);
  10. return null;
  11. }
  12. } catch {
  13. localStorage.removeItem(key);
  14. return null;
  15. }
  16. }
  17. public set(key: string | undefined, data: any) {
  18. if (!key) {
  19. return;
  20. }
  21. localStorage.setItem(key, JSON.stringify(data));
  22. }
  23. public remove(key: string | undefined) {
  24. if (!key) {
  25. return;
  26. }
  27. localStorage.removeItem(key);
  28. }
  29. }
  30. export default Storage;

**