dailybird

dailybird 查看完整档案

北京编辑中国科学技术大学  |  软件工程 编辑京东  |  实习 编辑填写个人主网站
编辑

I wanna.

个人动态

dailybird 赞了文章 · 2019-10-21

Const扩展

一、Object.defineProperty()函数和Object.hasOwnProperty()函数

  1. Object.defineProperty()函数

(1)概念:将属性添加到对象上,或修改现有的属性
(2)语法:Object.definedProperty(object,propertyname,descriptor)
(3)参数:

  • object:必需。被添加或被修改的属性。
  • propertyname:必需。一个包含属性名的字符串。
  • descriptor:属性描述符。

(4)返回值:已修改的对象。
(5)用法:

  • 向对象添加新属性。当对象不具有指定的属性名称时,发生此操作。
  • 修改现有的属性特征。当对象具有指定的属性名称时,发生此操作。

(6)举例:

  • 设置一个新的属性
    代码如下:
       var obj={name:'John'};
       //设置一个新属性
       Object.defineProperty(obj,'newDateProperty',{
           value:101,
       });
       console.log(obj);

结果如下:
D1E802FB-4935-485F-A499-54011961CF63.png

  • 设置新属性并修改新属性的值
    代码如下:
       var obj={name:'John'};
       //设置一个新属性
       Object.defineProperty(obj,'newDateProperty',{
           value:101,
           writable:true
       });
       //修改新属性的值
       obj.newDateProperty=111;
       console.log('property value:'+obj.newDateProperty);

结果:设置了writable属性为true才可以修改新属性的值。
7DAD95BE-50DF-453C-8949-1973EE901D09.png
2.Object.hasOwnProperty()函数
(1)概念:检测是否是原型链的属性,如果是则返回false,不是则返回true(也就是自己自定义的属性)。
(2)语法:object.hasOwnProperty(propertyName)
(3)参数:propertyName:String类型,指定的属性名称。
(4)例子:

  • 是否可以检测出自定义的属性和方法
    代码如下:
//定义一个对象并设置属性
const person={
    name:'小明',
    say:function(){
        console.log('说话啦')
    }
}
//打印是否存在name属性
console.log("name:"+person.hasOwnProperty("name"));
//打印是否存在say方法
console.log("say:"+person.hasOwnProperty("say"));

结果:可以检测出自定义存在的属性和方法
image.png

  • 是否可以检测出原型链中定义的属性和方法

代码如下:

//定义一个对象并设置属性
const person={
    name:'小明',
    say:function(){
        console.log('说话啦')
    }
}
//在原型链中添加一个属性
person.__proto__.age=20;
//在原型链中添加一个方法
person.prototype={
    run:function(){
        console.log('跑步啦');
    }
}
//打印是否存在name属性
console.log("name:"+person.hasOwnProperty("name"));
//打印是否存在say属性
console.log("say:"+person.hasOwnProperty("say"));
//打印是否存在age属性
console.log("age:"+person.hasOwnProperty("age"));
//打印是否存在run属性
console.log("run:"+person.hasOwnProperty("run"));

结果:自能检测出自定义的属性和方法,不能检测出原型链中的属性和方法
14C68514-DA21-4265-A017-0C7B184DC06D.png

二、对象的密封、冻结
1.Object.seal()密封对象
(1)概念:让一个对象密封,并返回被密封的对象。密封对象是指那些不能添加新的属性,不能删除已有的属性,以及不能修改已有属性的可枚举性、可配置性、可写性,但是可以修改已有属性对象的值。
(2)例子:

  • 添加一个新属性,修改已有属性的值,删除已有属性。

代码如下:

onst obj={
    name:'John',
    sex:'Lady'
}
//添加一个新属性
obj.age=30;
//修改已有属性的值
obj.name="Rose";
//删除哟有属性
delete obj.sex;
//打印obj对象
console.log(obj);

结果:可以添加一个新属性,修改已有属性的值,删除已有属性。
B3229995-C4ED-4F26-BAAB-F86A3D68086A.png

  • 密封对象并添加一个新的属性

代码如下:

const obj={ name:'John'}
//密封
Object.seal(obj);
//添加一个新属性
obj.age=30;
console.log("obj.age:"+obj.age);

结果:密封对象后就不能添加新的属性了。
98916F19-0ADC-4222-BF9D-C3C7A3466ED1.png

  • 密封对象并修改已有属性的值

代码如下:

const obj={ name:'John'}
//密封
Object.seal(obj);
//可修改已有属性的值
obj.name='Bckus';
console.log(obj.name);

结果:密封对象之后可以修改已有属性的值。
B8A12391-0D8E-44B4-9E2D-BE5BEE30AE06.png

  • 密封对象并删除已有属性

代码如下:

const obj={ name:'John'};
//密封
Object.seal(obj);
//删除已有属性
delete obj.name;
//打印Obj对象
console.log(obj);

结果:密封对象之后不能删除已有的属性。
C6983A7B-32EC-4298-AE4B-1F9E98306AA8.png

  • 密封对象并修改已有属性的可枚举性、可配置性、可写性

代码如下:

const obj={ name:'John'};
//密封
Object.seal(obj);
//删除已有的配置属性
Object.defineProperty(obj,'name',{
    configurable:true,
    writable:true,
    enumerable:true
})

结果:浏览器报错,密封对象之后不能修改已有属性的可枚举性、可配置性、可写性。
BF3780E4-B1E3-44AC-9F98-AE0DE567E946.png
2.Object.freeze()冻结对象
(1)概念:这个方法比object.seal()更厉害,冻结对象是指那些不能添加新属性,不能修改已有属性的值,不能删除已有属性,以及不能修改已有属性的可枚举性、可配置性、可写性的对象。也就是说,这个对象永远不可改变。
(2)例子:只测试与object.seal()不同的地方,也就是不能修改已有属性的值。

  • 冻结对象并修改已有属性的值
    代码如下:
var obj={name:'John'}
//冻结
Object.freeze(obj)
//修改已有属性的值
obj.name='Backus';
console.log('obj.name:'+obj.name);

结果:冻结对象之后不能修改已有属性的值。
B8E28B34-6C23-4AD0-9892-C68B29D5020D.png

查看原文

赞 1 收藏 0 评论 0

dailybird 关注了用户 · 2019-10-17

六月青鸾 @liuyueqing

关注 1

dailybird 发布了文章 · 2019-08-25

WeCenter 迁移踩坑备忘

最近需要将 WeCenter 服务迁移到另一台服务器,期间遇到了一些问题,以下备忘这些问题的解决方式。

0. 调整数据库配置

如果迁移后,数据库配置发生了变更,需要在对应文件中进行修改:

vim system/config/database.php

1. An Error Occurred 500 报错

之前已经考虑到 WeCenter 会将上传的文件存储在本地磁盘,故而直接将整个服务的文件夹打包迁移,( 同时迁移数据库 )。在配置好了 Nginx 后,打开 URL,发现出现如下报错:

clipboard.png

该页面并没有给出有参考意义的错误提示。我们需要按住 command + alt + I 打开浏览器检查面板,在隐藏 DOM 元素中会存在一些错误提示:

clipboard.png

如上所示,这里报错的原因是部分文件夹没有赋予写权限。

此外,PHP 版本过高也可能导致该问题出现,大家可以依据报错提示逐一解决。

2. 文件夹权限问题

WeCenter 需要对以下文件夹赋予权限,否则会出现无法上传等问题:

chmod -R 777 cache/ tmp/ uploads/

3. 图片无法显示问题

由于 WeCenter 在安装时,会将上传域名和上传文件夹所在绝对路径存入数据库。故而在迁移后,如果这两处地址发生了变化,需要进入管理员后台重新进行设置:

  1. 进入管理员后台:domain.com/?/admin/settings/category-site
  2. 修改「上传目录外部访问 URL 地址」以及「上传文件存放绝对路径」配置:

clipboard.png

参考链接

  1. 问答社区wecenter调试,如何显示错误信息
  2. wecenter3.2.1 An error occurred (500 Error)问题 - WeCenter
查看原文

赞 0 收藏 0 评论 0

dailybird 发布了文章 · 2019-08-16

Spring Boot 连接 Elasticsearch 备忘

以下备忘 Spring Boot 项目连接 Elasticsearch 的方式。

1. Transport Client 方式

1.1 Pom.xml

添加如下依赖:

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>5.4.1</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>transport</artifactId>
    <version>5.4.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>

注意,这里对 log4j 的依赖引用,是为了解决以下报错:

NoClassDefFoundError: org/apache/logging/log4j/Logger

1.2 添加 Client Bean

@Configuration
public class ESClientConfiguration {

    @Bean
    public Client client() {
        TransportClient client = null;
        try {
            Settings settings = Settings.builder()
                    .put("client.transport.sniff", true)
                    .put("cluster.name", "xxxx")
                    .put("client.transport.ping_timeout", "3s")
                    .build();
            client = new PreBuiltTransportClient(settings)
                    .addTransportAddress(new InetSocketTransportAddress(new InetSocketAddress("xxx.xxx.xxx.xxx", 9300)))
                    .addTransportAddress(new InetSocketTransportAddress(new InetSocketAddress("xxx.xxx.xxx.xxx", 9300)))
                    .addTransportAddress(new InetSocketTransportAddress(new InetSocketAddress("xxx.xxx.xxx.xxx", 9300)));
            return client;
        } catch (Throwable e) {
            log.error("@@@@", e);
        }
        return client;
    }
}

注:【 Transport Client 使用 TCP 端口 】Elasticsearch Http 端口和 TCP 端口不同,切勿写错。

1.3 使用

@Autowired
private Client client;

SearchResponse searchResponse = client.prepareSearch("index").setQuery(
   QueryBuilders.boolQuery().should(xxx).should(xxx)
).get();
SearchHit[] hits = searchResponse.getHits().getHits();

2. Jest 方式

2.1 Pom.xml

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>5.4.1</version>
</dependency>
<dependency>
    <groupId>io.searchbox</groupId>
    <artifactId>jest</artifactId>
    <version>5.3.3</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>

2.2 添加 Client Bean

@Configuration
public class ESJestClientConfiguration {

    @Bean("JestClient")
    public JestClient client() {
        Set<String> hosts = new HashSet<>(Arrays.asList("host1:port1", "host2:port2"));
        JestClientFactory factory = new JestClientFactory();
        Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
        factory.setHttpClientConfig(
                new HttpClientConfig.Builder(hosts)
                        .gson(gson)
                        .multiThreaded(true)
                        .defaultMaxTotalConnectionPerRoute(2)
                        .maxTotalConnection(10)
                        .build()
        );
        return factory.getObject();
    }
}

注:【 Jest 使用 HTTP 端口 】Elasticsearch Http 端口和 TCP 端口不同,切勿写错。

2.3 使用

@Autowired
private JestClient jestClient;

SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(
    QueryBuilders.boolQuery().should(xxx).should(xxx)
);
Search search = new Search.Builder(searchSourceBuilder.toString())
        .addIndex("index")
        .build();
JestResult result = jestClient.execute(search);
List<Model> models = result.getSourceAsObjectList(Model.class);

参考连接

  1. elasticsearch bool query combine must with OR - Stack Overflow
  2. How to query for null values? - Elasticsearch - Grafana Community
  3. elasticsearch - How to connect Elastic search 5.4 to tcp in java? - Stack Overflow
  4. Issue with elastic search 5.0.0 - NoClassDefFoundError: org/apache/logging/log4j/Logger - Elasticsearch - Discuss the Elastic Stack
  5. How to use Java API to build a nested query with filter query combination in 5.6.x? - Elasticsearch - Discuss the Elastic Stack
  6. Combining bool and range queries - Elasticsearch - Discuss the Elastic Stack
  7. Elasticsearch 范围查询
  8. es 多条件查询 or或 查询
  9. 使用JestClient操作ElasticSearch
  10. Jest - Elasticsearch Java Client | Baeldung
  11. elasticsearch - How to pass multiple node's address in jest (Elastic Search) - Stack Overflow
  12. how to write code for search in elasticsearch using jest client in java - Stack Overflow
  13. android - NoClassDefFoundError: Failed resolution of: Lorg/apache/http/conn/ssl/DefaultHostnameVerifier; - Stack Overflow
查看原文

赞 0 收藏 0 评论 0

dailybird 发布了文章 · 2019-06-24

Nginx 支持单域名多 Vue 服务配置备忘

最近开发时,遇到需要使用同一域名承载多个前端项目的场景,具体需求如下:

  1. /v2 访问新版本前端项目
  2. /api 访问后端 Spring Boot 接口服务
  3. / 访问默认前端项目

1. Nginx 配置内容

server {
    listen       80;
    listen       [::]:80;
    server_name  _;

    server_name_in_redirect off;
    proxy_set_header Host $host;

    location /api {
        proxy_pass http://0.0.0.0:0000;
    }

    location / {
        index  index.html;
        root /path/to/main/web/app;
    }

    location /v2 {
        index  index.html;
        alias /path/to/v2/web/app;
    }
}

注意 Nginx 的 alias 配置。此时,新前端项目需要被放在 /path/to/v2/web/app 路径下。

2. 修改 publicPath 配置

仅仅通过上述配置,在访问新版前端时,会遇到资源文件无法找到的问题。

此时,可以通过对新版前端 vue.config.js 文件中的 publicPath 进行配置,以规避这一问题( 注:该方法仅适用于 Vue-Cli 3.x 构建的项目 ):

module.exports = {
    ...
    
    publicPath: '/v2/',
    
    ...
};

参考链接

  1. Understanding the difference between the root and alias directives in Nginx
  2. Can't get two single page applications to run together on one server using nginx
查看原文

赞 1 收藏 1 评论 0

dailybird 赞了文章 · 2019-06-17

JS 中为啥 ['1', '7', '11'].map(parseInt) 返回 [1, NaN, 3]

阿里云最近在做活动,低至2折,有兴趣可以看看:
https://promotion.aliyun.com/...

为了保证的可读性,本文采用意译而非直译。

Javascript 一直是神奇的语言。 不相信我? 尝试使用mapparseInt将字符串数组转换为整数。打开 Chrome 的控制台(F12),粘贴以下内容,然后按回车,查看输出结果:

['1', '7', '11'].map(parseInt);

我们得到的不是一个整数数组[1,7,11],而是[1,NAN, 3],要了解究竟发生了什么,我们首先要讨论一些Javascript概念。

想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!

真值(truthy) & 虚值(falsy)

以下是 JS 中一个简单的if-else语句:

if (true) {
    // this always runs
} else {
    // this never runs
}

在上例中,if 条件为 true,因此总是执行if块,忽略else块。这是一个简单的例子,因为true是一个布尔值。如果我们把非布尔值作为条件呢 ?

if ("hello world") {
    console.log("Condition is truthy");
} else {
    console.log("Condition is falsy");
}  

打开控制台并运行上述代码,会打印 Condition is truthy,说明条件 "hello world"真(true)值。

在 JavaScript 中,Truthy (真值)指的是在 布尔值 上下文中转换后的值为真的值。所有值都是真值,除非它们被定义为 falsy (即除了 false0""nullundefinedNaN 外)。

falsy(虚值)是在 Boolean 上下文中已认定可转换为‘假‘的值。

JS中的对象不是真值就是虚值。

令人困惑的是,这意味着字符串“false”,字符串“0”,空对象{}和空数组[]都是真的。 使用使用 Boolean 方法来验证,如 Boolean("0")

出于我们的目的,接下来只要记住0是假的就行了。

基数

在数学上,基数(cardinal number)是集合论中刻画任意集合大小的一个概念。两个能够建立元素间一一对应的集合称为互相对等集合。例如3个人的集合和3匹马的集合可以建立一一对应,是两个对等的集合。

0 1 2 3 4 5 6 7 8 9 10

当我们从0数到9时,每个数字(0-9)都有不同的符号,但是当我们数到10时,我们需要两个不同的符号(10)来表示这个数字。这是因为我们的十进制计数系统的基数是10

基数是最小的数字,只能由多个符号表示。 不同的计数系统具有不同的基数,因此,相同的数字在不同的计数系统中可以表示不同的数字。

十进制   二进制    十六进制
RADIX=10  RADIX=2   RADIX=16
0         0         0
1         1         1
2         10        2
3         11        3
4         100       4
5         101       5
6         110       6
7         111       7
8         1000      8
9         1001      9
10        1010      A
11        1011      B
12        1100      C
13        1101      D
14        1110      E
15        1111      F
16        10000     10
17        10001     11

看上表,可以看到相同的数字11在不同的计数系统中可以表示不同的数字。如果基数是2,那么它表示数字为 3。如果基数是16,那么它指的是数字17

你可能已经注意到,在我们的示例中,当输入为11时,parseInt返回3,这对应于上表中的二进制列。

函数参数

JS 中函数调用,我们可以传入任意的参数,即使它们不等于声明时的函数参数的数量。缺少的参数被视为undefined 的,并且会忽略额外的参数,但会保存在类似数组的arguments对象中。


function foo(x, y) {
    console.log(x);
    console.log(y);
}
foo(1, 2);      // 打印 1, 2
foo(1);         // 打印 1, undefined
foo(1, 2, 3);   // 打印 1, 2    

map()

map是 Es6 中新出的一个数组方法,它是一个高阶函数,通过传入一个函数进行逻辑操作,并返回一个数组, 例如,以下代码将数组中的每个元素乘以3

function multiplyBy3(x) {
    return x * 3;
}
const result = [1, 2, 3, 4, 5].map(multiplyBy3);
console.log(result);   // logs [3, 6, 9, 12, 15];


现在,将console.log作为参数传给 map,来打印数组的元素:

[1, 2, 3, 4, 5].map(console.log);

等价于
 
 [1, 2, 3, 4, 5].map((val, index, array) => 
     console.log(val, index, array));

clipboard.png

所以 map 回调方法中会传入三个参数,分别是 当前遍历的项,当前索引,及遍历的整个数组。

原因

ParseInt有两个参数:stringradix。 如果提供的基数是虚值,则默认情况下,基数设置为10

parseInt('11');                => 11
parseInt('11', 2);             => 3
parseInt('11', 16);            => 17
parseInt('11', undefined);     => 11 (radix is falsy)
parseInt('11', 0);             => 11 (radix is falsy)

现在一步一步解析开头的事例。

['1', '7', '11'].map(parseInt);       => [1, NaN, 3]

// 第一次迭代: val = '1', index = 0, array = ['1', '7', '11']
parseInt('1', 0, ['1', '7', '11']);   => 1

因为0是虚值,基数设置为默认值10parseInt()只接受两个参数,因此忽略了第三个参数['1'、'7'、'11']。以10为基数的字符串“1”表示数字1

 // 第二次迭代: val = '7', index = 1, array = ['1', '7', '11']
parseInt('7', 1, ['1', '7', '11']);   => NaN

在基数1系统中,符号“7”不存在。与第一次迭代一样,忽略最后一个参数。因此,parseInt()返回NaN

 // Third iteration: val = '11', index = 2, array = ['1', '7', '11']
parseInt('11', 2, ['1', '7', '11']);   => 3

在基数2(二进制)系统中,符号“11”表示数字3

至此原因已经明了了。 有兴趣可以试着写下下列的打印结果:

['1', '7', '11'].map(numStr => parseInt(numStr));


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

## 交流

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

https://github.com/qq44924588...

我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!

关注公众号,后台回复福利,即可看到福利,你懂的。

clipboard.png

查看原文

赞 36 收藏 20 评论 7

dailybird 发布了文章 · 2019-06-05

利用 Nginx 处理 Vue 开发环境的跨域

1. 需求

本地测试域名与线上域名相同,以便正确传递 Cookie 和进行 SSO 测试。

注:由于 SSO 登录后,相关 Cookie 被加在四级域名上,因而需要做到本地测试域名和线上接口域名相同。

2. 方案

配置 Host 文件使线上域名指向 Localhost:

127.0.0.1 product.xxx.xxx.com

配置 Nginx 进行对应转发:

server {
    listen       80;
    listen       [::]:80;
    server_name  ${product.xxx.xxx.com};

    location /api {
        proxy_pass https://${ip.ip.ip.ip};
        proxy_set_header Host $host;
    }

    location / {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
    }    
}

配置 vue.config.js 以免出现 Invalid Host header 报错:

{
    devServer: {
        disableHostCheck: true
    }
}
查看原文

赞 1 收藏 1 评论 0

dailybird 赞了文章 · 2019-05-20

vue项目实现按需加载的3种方式:vue异步组件、es提案的import()、webpack的require.ensure()

1. vue异步组件技术

  • vue-router配置路由,使用vue的异步组件技术,可以实现按需加载。

但是,这种情况下一个组件生成一个js文件。
举例如下:

        {
            path: '/promisedemo',
            name: 'PromiseDemo',
            component: resolve => require(['../components/PromiseDemo'], resolve)
        }

2. es提案的import()

vue官方文档:路由懒加载(使用import())

  • vue-router配置路由,代码如下:
// 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。
const ImportFuncDemo1 = () => import('../components/ImportFuncDemo1')
const ImportFuncDemo2 = () => import('../components/ImportFuncDemo2')
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。
// const ImportFuncDemo = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo')
// const ImportFuncDemo2 = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/ImportFuncDemo2')
export default new Router({
    routes: [
        {
            path: '/importfuncdemo1',
            name: 'ImportFuncDemo1',
            component: ImportFuncDemo1
        },
        {
            path: '/importfuncdemo2',
            name: 'ImportFuncDemo2',
            component: ImportFuncDemo2
        }
    ]
})

3. webpack提供的require.ensure()

  • vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。

这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
举例如下:

        {
            path: '/promisedemo',
            name: 'PromiseDemo',
            component: resolve => require.ensure([], () => resolve(require('../components/PromiseDemo')), 'demo')
        },
        {
            path: '/hello',
            name: 'Hello',
            // component: Hello
            component: resolve => require.ensure([], () => resolve(require('../components/Hello')), 'demo')
        }

项目路由配置文件:https://github.com/cag2050/vu...

查看原文

赞 104 收藏 104 评论 10

dailybird 发布了文章 · 2019-05-20

Vue CLI 2 升级至 Vue CLI 3

Vue CLI 2 升级至 Vue CLI 3

以下备忘升级至 Vue CLI 3.x 版本后,将项目目录改为新结构时所需做的一些改动。

1. 卸载与安装

npm uninstall vue-cli -g
npm install -g @vue/cli

注:若要使用 Vue CLI 3,需将 Node 版本升级至 8.9 及以上。

当使用 nvm 管理 node 版本时,可以使用如下方式切换至需求的 Node 版本:

# 安装 >= 8.9 的某个版本
nvm install 8.12.0

# 在当前 session 中使用该版本
nvm use 8.12.0

# 设置默认的 Node 版本
nvm alias default 8.12.0

2. 环境变量与多环境配置

2.1 环境变量

在 Vue CLI 2.x 中,如果需要定义环境变量,需要在 build/webpack.dev.conf.js 中加入:

plugins: [
    new webpack.DefinePlugin({
      'process.xxx': "'some value'",
    })
]

而在 Vue CLI 3.x 中,我们可以使用配置文件的方式便捷的进行配置:

在项目中新建 .env 文件,写入

VUE_APP_KEY=VALUE

即可在需要的地方使用 process.env.VUE_APP_KEY 调用了。注意,这里环境变量必须以 VUE_APP_ 开头。

2.2 多环境配置

配置文件同样支持多环境,即 .env.development 文件表示 development 环境;.env.production 文件表示 production 环境。

在使用 npm 命令时,可以通过指定 --mode xxx 来启用某一环境的环境变量。

注:.env 文件为所有环境的公用环境变量。

2.3 本地多环境配置

在 Vue CLI 3 中,声明了对 .env.*.local 不进行 Git 版本控制。

对于一些无需上传到代码仓库的配置,可以使用这一方式。

3. 静态资源文件

Vue CLI 3.x 将默认资源文件根路径放到了 /public 目录下,而默认精简掉了 2.x 版本中的 /static 目录。因而之前放置于 /static 目录中的资源文件及其引用位置需要做些调整。

4. 在 WebStorm 中配置对 @ 符号的支持

默认情况下,JetBrains 系列的 IDE 无法对 Vue 指定的 @ 符号进行正确的路径识别。此时我们可以在项目根文件夹下创建 webpack.config.js 文件,并写入:

module.exports = {
    resolve: {
        alias: {
            '@': require('path').resolve(__dirname, 'src')
        }
    }
};

之后,在 IDE 中指定该文件路径:

Ej6i36.jpg

之后,IDE 便能正确识别 @ 所表示的路径了。

5. 添加全局 Scss 文件

在前端项目中,经常会用到相同的主题色。此时,我们需要存储这些变量,且将其全局引入。

在 Vue CLI 3 中,我们可以在根目录下新建一个 vue.config.js 文件,写入如下内容:

module.exports = {
    css: {
        loaderOptions: {
            sass: {
                data: `@import "@/styles/settings.scss";`
            }
        }
    }
};

此时,settings.scss 该文件中的变量值便能在任意 Vue 组件中使用了。

当然,如果要在 .vue 文件中使用 SCSS 语法,需要在 <style> 标签中增加如下属性:

<style scoped lang="scss" type="text/scss">

</style>

6. 调整 ESLint 配置

ESLint 对未使用的变量和函数参数都做了限制,但原项目中确实有些地方需要保留这些 “暂时用不上” 的变量,因而这里对默认的 ESLint 设置做了调整,即修改 .eslintrc.js 文件:

{
    ...
    
    rules: {
        'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
        'vue/no-unused-vars': 'off',
        'vue/no-empty-pattern': 'off'
    },
    
    ...
}

7. Compiler 模式变更为 Runtime 模式

在升级至 Vue CLI 3 之后,直接运行可能会出现如下报错:

[Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

(found in <Root>)

这是因为 3.x 版本默认使用的是运行时模式,需要对 main.js 文件进行修改:

new Vue({
    router,
    store,
    render: h => h(App)
}).$mount('#app');

将其改为上述方式即可。

8. 配置 lodash 使其模块化加载

在项目中,如果使用如下方式引入 lodash:

import _ from 'lodash';

那么,即使只使用了其中的 _.get() 方法,也会将全部的 lodash 依赖压缩到 .js 文件中。这不是我们期望的。

此时,我们可以通过如下方式,使其能够在这种引入方式下,也能自动实现模块加载:

首先,安装如下依赖:

npm install babel-plugin-lodash --save-dev

然后在 babel.config.js 中添加如下内容:

module.exports = {
    ...
    
    plugins: [
        'lodash'
    ]
    
    ...
};

9. 配置 analyzer

我们可以使用 analyzer 分析项目编译后的文件组成,以便进行加载速度优化。

首先安装依赖:

npm install webpack-bundle-analyzer --save-dev

然后在 vue.config.js 中添加如下配置:

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
    ...
    
    configureWebpack: {
        plugins: [
            new BundleAnalyzerPlugin()
        ]
    },
    
    ...
};

然后在 package.json 中添加新的命令:

"analyze": "npm_config_report=true npm run build"

之后,便可以执行以下语句来查看项目编译后文件大小组成了:

npm run analyze

注:采用这种方式后,每次 npm run devnpm run build 都会自动弹出分析页面。

如果不想这么做,可以直接使用如下方式( 无需安装 webpack-bundle-analyzer 依赖 ):

"analyze": "vue-cli-service build --report"

当执行 npm run analyze 后,/dist 文件夹下会生成 report.html 分析报告页面。

10. 引入外部 CDN

我们可以使用 CDN 来加速部分第三方依赖的加载速度,而不是把它们全部打包到一起。

在使用 script 标签引入需要的 .js 文件后,在 vue.config.js 文件增加如下配置:

module.exports = {
    ...
    
    configureWebpack: {
        externals: {
           "echarts": "echarts",
        }
    },
    
    ...
}

即可在需要的地方按如下方式使用了:

import echarts from 'echarts';

11. 忽略编译文件大小限制警告

当执行 npm run build 时,会出现警告信息:

asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).

此时,我们可以在 vue.config.js 中添加如下配置,忽略这条警告信息:

module.exports = {
    ...
    
    performance: {
        hints: false
    }
    
    ...
};

12. 使用 webpackChunkName 注释减少编译的文件个数

为了避免将所有组件打包在一起而造成单个文件过大,通常我们会使用按需加载的方式引入组件:

const ComponentXXX = () => import('../../pages/xxx/index.vue');

在 Vue CLI 3 默认情况下,每个如上方式引入的组件会被编译为一个单独的 JS 和一个单独的 CSS 的文件。

如果不加处理,当采用如上方式引入的组件数量增多时,可能会在编译后,得到几百个小文件,而每个文件的大小可能还不到 1 KB。

虽然小文件加载速度快,但由于浏览器每次能建立的连接数量有限,大量的小文件同样会导致首次加载变慢。

此时,我们可以使用如下的方式,将多个组件打包成一个文件:

const ComponentXXX = () => import(/* webpackChunkName: "xxx" */ '../../pages/xxx/index.vue');

如此,我们便可以在保证分块打包的前提下,减少编译的小文件数量。

参考链接

  1. https://webpack.js.org/config...
  2. npm run build: WARNING in asset size limit: The following asset(s) exceed the recommended size limit - yun_hou的博客 - CSDN博客
  3. https://stackoverflow.com/que...
  4. vue-cli3使用cdn方式引入moment.js - Happy222~ - 博客园
  5. https://github.com/facebook/c...
  6. mini-css-extract-plugin Conflicting order between 和Entrypoint mini-css-extract-plugin = * - dk123sw的博客 - CSDN博客
  7. You are using the runtime-only build of Vue where the template compiler is not available. Either pre - 积少成多 - CSDN博客
  8. Vue CLI 配置 webpack-bundle-analyzer 插件 - 前端人公众号作者 - CSDN博客
  9. vue-cli3 配置 webpack-bundle-analyzer 插件 - 简书
  10. https://cli.vuejs.org/zh/guid...
  11. javascript - vue-cli3 npm run build --report没有生成那种分析图 - SegmentFault 思否
  12. vue-cli3实现分环境打包步骤(给不同的环境配置相对应的打包命令) - Happy222~ - 博客园
  13. vue-cli如何添加多种环境变量 - 木石心 - 博客园
  14. Vue CLI 3 环境变量和模式配置实践与源码分析 - 个人文章 - SegmentFault 思否
  15. nvm 设置 nodejs 默认版本 - Joans - 博客园
  16. vue-cli3设置sass/scss全局变量 - 游龙翔隼 - 博客园
  17. https://medium.com/@markni/ma...
  18. https://forum.vuejs.org/t/how...
  19. https://gist.github.com/Justi...
  20. 一步一步的了解webpack4的splitChunk插件 - 掘金
  21. Disable code splitting in @vue/cli 3 - Get Help - Vue Forum
  22. vue项目实现按需加载的3种方式:vue异步组件、es提案的import()、webpack的require.ensure() - 前端技术 - SegmentFault 思否
查看原文

赞 7 收藏 7 评论 1

dailybird 发布了文章 · 2019-05-19

Spring Boot 接口层公共能力抽取

在前后端分离的主流架构下,前端代码和后端逻辑主要依靠已约定的格式进行交互。在这一前提下,如果后端代码没有进行一定的配置,就很容易出现大量重复代码。本文以 Spring Boot 为例,记录一些可以减少冗余代码的方案。

1. 使用 Filter 提供跨域支持

前后端分离后,如果不采用相同域名,跨域便是首先需要解决的问题。关于跨域方案,先前撰写的文章中有比较详细的方案罗列:跨域解决方案 - DB.Reid - SegmentFault 思否

这里介绍在 SpringBoot 中采用 Filter 方式实现跨域的代码:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsEnableFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String domain = httpServletRequest.getHeader("Origin");
        String method = httpServletRequest.getMethod();
        httpServletResponse.setHeader("Access-Control-Allow-Origin", domain);
        httpServletResponse.setHeader("Access-Control-Allow-Methods", method);
        httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpServletResponse.setHeader("Access-Control-Allow-Headers",
                "Client-Info, Captcha, X-Requested-With, Authorization, Content-Type, Credential, X-XSRF-TOKEN");

        if (StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "OPTIONS")) {
            httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
    }
}

注:由于浏览器对 * 通配符有各种限制,因而这里采用的方式是先获取请求的方法类型和域名,再在 OPTION 响应中允许相同的内容。如果是线上服务,建议指定固定的前端域名。

2. 使用 ResponseBodyAdvice 处理全局返回值格式

前后端分离后,接口返回的数据需要有更强的表现力,以便前端能够进行更多提升用户体验的处理。

一般情况下,对于正常情况,和后端可预期的提示性错误,建议返回的 HTTP 状态码全部为 2xx( 当遇到某些因 BUG 导致的异常时,再返回 5xx 错误以表示是由后端代码导致的问题 )。而把带有语义的状态类型标识符放在响应体 JSON 的某个字段中。

目前比较常用的响应类型为:

{
    "status_code":0,
    "data":{

    },
    "message":""
}

2.1 定义状态码枚举类

关于返回格式中 status_code 的设定,这里建议使用 0 表示正常情况;大于 0 的数字表示需前端额外处理的情况,比如跳转操作;小于 0 的数字表示异常。

@Getter
public enum StatusCode implements Constant {

    SUCCESS(0, "success", "成功"),

    ERROR(-1, "unknown error", "未知异常"),
    NO_PERMISSION(-2, "no permission", "无权限访问"),
    NOT_FOUND(-3, "api not found", "接口不存在"),
    INVALID_REQUEST(-4, "invalid request", "请求类型不支持或缺少必要参数"),
    ;

    StatusCode(Integer value, String name, String cnName) {
        this.value = value;
        this.name = name;
        this.cnName = cnName;
    }

    private Integer value;
    private String name;
    private String cnName;
}

如上述代码所示,枚举类中包含三个字段,其中,value 用于状态码唯一标识,namecn_name 可作为文本提示赋值给返回值对象的 message 字段。

其中,value 可以根据业务情况进行合理的组织,比如 1xxxxx 表示用户类业务异常;2xxxxx 表示邮件短信类业务异常等。这种组织方式更易于错误定位和排查。

2.2 定义返回格式类

按照预期的返回格式定义类:

@Data
public class SimpleResponse {

    protected Integer statusCode;
    protected Object data;
    protected String message;
    
    public SimpleResponse(StatusCode statusCode, Object data, String message) {
        if (message == null) message = "";
        this.statusCode = statusCode.getValue();
        this.data = data;
        this.message = message;
    }
}

同样的,我们也可以定义正常响应类和错误响应类:

@Data
@EqualsAndHashCode(callSuper = false)
public class SuccessResponse extends SimpleResponse {

    public SuccessResponse() {
        super(StatusCode.SUCCESS, null, "成功");
    }

    public SuccessResponse(Object data) {
        super(StatusCode.SUCCESS, data, "成功");
    }
}
@Data
@EqualsAndHashCode(callSuper = false)
public class ErrorResponse extends SimpleResponse {

    public ErrorResponse() {
        this(StatusCode.ERROR);
    }

    public ErrorResponse(StatusCode statusCode) {
        this(statusCode, null, statusCode.getCnName());
    }
}

注:接口返回的字段键一般为下划线,而 Java 对象属性名一般为驼峰体。在 Spring Boot 中,需要增加一些配置以实现这一转换过程。这一部分会在后文中进行介绍。

2.3 配置 ResponseBodyAdvice

接下来,我们添加一个配置,对所有 Controller 的返回值进行封装,将其变成我们想要的返回格式。

@ControllerAdvice
public class RestResponseConfiguration implements ResponseBodyAdvice {

    private static final Class[] annotations = {
            RequestMapping.class,
            GetMapping.class,
            PostMapping.class,
            DeleteMapping.class,
            PutMapping.class
    };

    /**
     * 需要限定方法,以便排除 ExceptionHandler 中的返回值
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {

        AnnotatedElement element = returnType.getAnnotatedElement();
        return Arrays.stream(annotations).anyMatch(annotation -> annotation.isAnnotation() && element.isAnnotationPresent(annotation));
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        // 默认返回成功响应
        // 错误响应由 exception -> @ControllerAdvice exceptionHandler  的方式响应
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        return new SuccessResponse(body);
    }
}

这样一来,所有 Controller、Service 等业务代码返回的对象就无需进行多余的格式封装工作了。

注:采用上述代码只会对正常结果进行处理,而要对异常情况进行格式化封装,则需要其他一些步骤。

3. 使用 ControllerAdvice 处理业务异常

一般思路下,我们需要对可能出现异常的地方进行捕获,然后设定单独的处理逻辑,返回特定的对象给调用者,以便前端能够收到对应的响应数据。

这一过程太过繁琐,且需要在业务代码中掺杂入许多无意义的分支代码。

Spring Boot 允许我们使用 @ControllerAdvice 处理异常。那么,我们就可以在业务代码处理的任一一个调用类中直接抛出运行时异常,然后利用上述配置统一处理。

3.1 添加 @ControllerAdvice

@Slf4j
@RestController
@ControllerAdvice
public static class RuntimeExceptionHandler {

    /**
     * 缺省运行时异常
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse runtimeException(RuntimeException exception) {

        log.error("Spring Boot 未知错误", exception);
        return new ErrorResponse(StatusCode.ERROR);
    }
}

通过上述配置,当业务代码中需要抛出异常时,可以直接 throw new RuntimeException()

3.2 组织可预期的业务异常

在实际业务中,很多 “异常” 是前后端都可以预期的。比如用户上传的文件数量超过了限制、在禁止变更时期进行了相关操作等。这类异常也不同于正常情况,需要后端进行检验并返回不同于正常情况的响应值。

此时,我们可以自定义一些业务运行时异常,以便也可以使用 ControllerAdvice 方式统一进行处理:

首先,我们定义一个基础业务异常类。定义基础类的好处在于,我们可以利用继承关系对这些业务异常统一进行处理:

@Data
@EqualsAndHashCode(callSuper = false)
public class BusinessException extends RuntimeException {

    private StatusCode exceptionCode;

    public BusinessException(StatusCode exceptionCode, String message) {
        super(message);
        this.exceptionCode = exceptionCode;
    }

    public BusinessException(StatusCode exceptionCode) {
        this(exceptionCode, exceptionCode.getCnName());
    }
}

接下来,我们定义某一场景下的业务异常,比如用户在同类申请单未完结的情况下、又提交了一个申请的异常:

public class UnfinishedApplicationExistsException extends BusinessException {

    private static final StatusCode statusCode = StatusCode.UNFINISHED_APPLICATION_EXISTS;

    public UnfinishedApplicationExistsException() {
        super(statusCode);
    }
}

上述 UNFINISHED_APPLICATION_EXISTS 枚举类的内容是:

UNFINISHED_APPLICATION_EXISTS(-12345, "unfinished application exists", "相同类型的申请正在处理,请勿重复提交"),

3.3 添加更多类型的 ControllerAdvice

基于此,我们便可以对不同类型的由 Controller 及其后续调用链抛出的异常进行分类处理了。

较常用的类型包括:已知的业务异常、MVC 异常( 如接口地址不存在等 )、数据库异常、未知的运行时异常等。

此时我们需要为不同类型的异常配置不同的 ControllerAdvice,为了更方便的在一个文件中进行配置,我们可以使用如下方式:

@Configuration
public class ExceptionHandlerConfiguration {

    @Slf4j
    @RestController
    @Order(1)
    @ControllerAdvice
    public static class BusinessExceptionHandler {

        /**
         * 业务异常处理( 可由前端指引用户修正输入值以规避该情况 )
         * 仍返回 200 状态码
         *
         * @param exception
         * @return
         */
        @ExceptionHandler(BusinessException.class)
        @ResponseStatus(HttpStatus.OK)
        public ErrorResponse defaultException(BusinessException exception) {

            log.error("业务异常: {}", exception);
            return new ErrorResponse(exception.getExceptionCode(), exception.getMessage());
        }

        @ExceptionHandler(SQLException.class)
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public ErrorResponse defaultException(SQLException exception) {

            log.error("数据库异常", exception);
            return new ErrorResponse(StatusCode.DATABASE_ERROR);
        }
    }

    @Slf4j
    @RestController
    @Order(9)
    @ControllerAdvice
    public static class MVCExceptionHandler {

        /**
         * 404
         *
         * @return
         */
        @ExceptionHandler(NoHandlerFoundException.class)
        @ResponseStatus(HttpStatus.NOT_FOUND)
        public ErrorResponse notFoundException(NoHandlerFoundException exception) {

            log.info("请求地址不存在: {}", exception.getMessage());
            return new ErrorResponse(StatusCode.NOT_FOUND);
        }

        /**
         * 方法类型不允许、缺少参数等
         *
         * @return
         */
        @ExceptionHandler(ServletException.class)
        @ResponseStatus(HttpStatus.BAD_REQUEST)
        public ErrorResponse servletException(ServletException exception) {

            log.info("请求方式或参数不合法: {}", exception.getMessage());
            return new ErrorResponse(StatusCode.INVALID_REQUEST);
        }
    }

    @Slf4j
    @RestController
    @Order(98)
    @ControllerAdvice
    public static class RuntimeExceptionHandler {

        /**
         * 缺省运行时异常
         *
         * @param exception
         * @return
         */
        @ExceptionHandler(RuntimeException.class)
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        public ErrorResponse runtimeException(RuntimeException exception) {

            log.error("Spring Boot 未知错误", exception);
            return new ErrorResponse(StatusCode.ERROR);
        }
    }
}

进行上述配置后,在某个 Service 处理中,如果遇到可预期的异常,直接抛出对应的异常对象即可。Spring Boot 会自动对该异常对象进行处理,将其封装成标准输出格式,且在 message 中填充已定义的错误提示,以便前端向用户进行提示。

4. 使用 HandlerExceptionResolver 处理其他异常

在 Spring Boot 中,部分代码未经过 MVC 阶段便出现了异常,比如 Spring Security 的处理等。此种情况的异常无法利用 ControllerAdvice 进行统一处理,需要借助 HandlerExceptionResolver 进行配置:

@Slf4j
@Configuration
public class ExceptionConfiguration {

    @Component
    public class CustomExceptionResolver implements HandlerExceptionResolver {

        @Override
        public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception exception) {
            httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            httpServletResponse.setContentType("application/json;charset=UTF-8");
            try {
                log.error("服务未知错误:{}", exception);
                exception.printStackTrace();
                ErrorResponse errorResponse = new ErrorResponse(exception.getMessage());
                httpServletResponse.getWriter().write(JSONObject.toJSONString(errorResponse));
            } catch (IOException e) {
                log.error("未知异常响应错误: {}", e);
                e.printStackTrace();
            }
            return null;
        }
    }
}

5. 使用 WebMvcConfigurer 进行全局 JSON 配置

为了使得包括异常在内的返回值中,驼峰字段都能被正确转换为下划线,我们需要添加 WebMvcConfigurer 配置。

注意,直接在 .yml 文件中进行的配置无法在 ControllerAdvice 中生效。

/**
 * 增加 @EnableWebMvc 注解的目的是为了使 WebMvcConfigurer 配置生效
 */
@EnableWebMvc
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {

    /**
     * 增加这一配置,以便由 ControllerAdvice 统一处理的异常返回值也能进行驼峰转下划线等处理
     *
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        ObjectMapper objectMapper = new ObjectMapper();
        // 设置驼峰法与下划线法互相转换
        objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        // 设置忽略不存在的字段
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
    }
}

6. 使用 Filter 进行验证码等先行校验

很多接口需要使用验证码校验,但校验逻辑基本是相同的,所以也可以进行代码抽离以免产生冗余。

我们可以在需要使用校验步骤的接口之后添加特殊标识,以便程序进行统一处理( 当然,将需要校验的接口地址放入某个 Set 也可以 )。

比如,我们使用在接口地址后增加 /_captcha 的方式标识该接口需要进行验证码校验。

此时,验证码校验的整体步骤如下:

  1. 前端将校验信息加入某个请求头字段中;
  2. 后端过滤器对每个接口进行检测,当发现接口后存在 /_captcha 后缀时,检测请求头中的校验字段;
  3. 如果通过则放行,否则直接返回错误响应。

其中,Filter 检验逻辑如下:

@Slf4j
@Component
public class CaptchaValidatorFilter implements Filter {

    @Autowired
    private SiteProperties siteProperties;

    private static final String URI_SIGNATURE = "_captcha";

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        String uri = httpServletRequest.getRequestURI();
        if (StringUtils.contains(uri, URI_SIGNATURE)) {
        
            // 检验请求头中的字段
            
        } else {
            chain.doFilter(request, response);
        }
    }

    @Override
    public void destroy() {
    }
}

7. 使用 pagehelper 实现基于 MyBatis 的快捷分页

分页问题也是接口层实现时所需考虑的一大问题。当使用 MyBatis 进行 ORM 时,建议使用 pagehelper 进行分页处理:

MyBatis 分页插件 PageHelper

首先添加如下依赖:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>${pagehelper.version}</version>
</dependency>

然后在需要分页的代码前,加上如下语句即可:

PageHelper.startPage(pageNum, pageSize);

参考链接

  1. java - EnableWebMvc annotation meaning - Stack Overflow
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 220 次点赞
  • 获得 62 枚徽章 获得 3 枚金徽章, 获得 18 枚银徽章, 获得 41 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-08-17
个人主页被 1.6k 人浏览