mongodb入门## 简介

MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。
在高负载的情况下,添加更多的节点,可以保证服务器性能。
MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

使用

下载&安装

https://www.mongodb.com/download-center#community

  • 创建数据目录

    MongoDB将数据目录存储在db目录下。但是这个数据目录不会主动创建,我们在安装完成后需要创建它。

启动MongoDB服务

1
2
3
4
5
6
7
8
创建数据库目录
例如:E:\mongodb\db

执行如下命令 (启动服务器) (进入到mongodb的bin目录)
> mongod --dbpath E:\mongodb\db

使用客户端连接 (到mongodb的安装目录下)
> mongo.exe

使用配置文件

  1. 在安装目录下创建配置文件 config.cfg

  2. 编辑内容

1
2
3
4
5
6
#数据库数据存放目录
dbpath=E:\mongodb\db
#端口号 默认为27017
port=27017
#mongodb所绑定的ip地址(运行本机与100两台电脑访问)
bind_ip=127.0.0.1,192.168.1.100
  1. 启动命令
1
> mongod -f config.cfg

概念解析

SQL术语/概念 MongoDB术语/概念 解释/说明
database database 数据库
table collection 数据库表/集合
row document 数据记录行/文档
column field 数据字段/域
index index 索引
table joins 不支持 表连接,MongoDB不支持
primary key primary key 主键,MongoDB自动将_id字段设置为主键

java 发邮件 javax.mail## 依赖

1
2
3
4
5
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.5.5</version>
</dependency>

代码演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.panshi.utils;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Date;
import java.util.Properties;

/**
* @description: 发送邮箱验证码类
* @author: 蒋文豪
* @create: 2019/06/13
*/
public class SendEmail {

public static final String SMTPSERVER = "smtp.163.com";
public static final String SMTPPORT = "465";
public static final String ACCOUT = "xxx@163.com";
public static final String PWD = "密码";

/**
* 发送验证码
* @param address 地址
* @param code 验证码
*/
public static void sendVerification(String address, String code){
// 创建邮件配置
Properties props = new Properties();
props.setProperty("mail.transport.protocol", "smtp"); // 使用的协议(JavaMail规范要求)
props.setProperty("mail.smtp.host", SMTPSERVER); // 发件人的邮箱的 SMTP 服务器地址
props.setProperty("mail.smtp.port", SMTPPORT); //端口
props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
props.setProperty("mail.smtp.auth", "true"); // 需要请求认证
props.setProperty("mail.smtp.ssl.enable", "true");// 开启ssl

// 根据邮件配置创建会话,注意session别导错包
Session session = Session.getDefaultInstance(props);
//创建邮件
MimeMessage message = new MimeMessage(session);;
try {
// 发件人邮件地址, 发件人别名(收件邮箱显示), charset编码方式
InternetAddress fromAddress = new InternetAddress(ACCOUT,"小尾巴96", "utf-8");
// 设置发送邮件方
message.setFrom(fromAddress);
//收件地址 发件人别名(自己邮箱显示)
InternetAddress receiveAddress = new InternetAddress("xxx@qq.com", "VinhoJiang", "utf-8");
// 设置邮件接收方
message.setRecipient(Message.RecipientType.TO, receiveAddress);
// 设置邮件标题
message.setSubject("验证码", "utf-8");
//邮件内容
//msg.setText("您的验证码是4528");//普通文字
String htmlText = "您的验证码是<H2 style='color:pink'>"+code+"</H2>";
//以网页形式发送 UTF-8
message.setContent(htmlText, "text/html;charset=UTF-8");
// 设置显示的发件时间
message.setSentDate(new Date());
// 保存设置
message.saveChanges();
} catch (Exception e) {
e.printStackTrace();
}
//获取传输通道
Transport transport = null;
try {
transport = session.getTransport();
} catch (NoSuchProviderException e) {
e.printStackTrace();
}
try {
transport.connect(SMTPSERVER,ACCOUT, PWD);
} catch (MessagingException e) {
e.printStackTrace();
}
//连接,并发送邮件
try {
transport.sendMessage(message, message.getAllRecipients());
} catch (MessagingException e) {
e.printStackTrace();
} finally {
try {
transport.close();
} catch (MessagingException e) {
e.printStackTrace();
}
}

}

}

java使用HttpClient发送http请求```java

package com.pp.locan.shared.utils;

import com.alibaba.fastjson.JSON;
import org.apache.http.HttpEntity;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class HttpClient {

/**
 * get请求带参数
 * @param map
 */
public static String sendGetParam(Map map, String uri) {
    //设置返回对象
    String returnJson = null;
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();

    // 参数
    StringBuffer params = new StringBuffer();

    // 字符数据最好encoding以下;这样一来,某些特殊字符才能传过去(如:某人的名字就是“&”,不encoding的话,传不过去)
    Set<String> strings = map.keySet();
    Iterator<String> iterator = strings.iterator();
    boolean flag = false;
    while (iterator.hasNext()){
        if (flag){
            params.append("&");
        }
        String key = iterator.next();
        params.append(key + "=" + map.get(key));

        flag= true;
    }


    // 创建Get请求
    HttpGet httpGet = new HttpGet(uri + "?" + params.toString());
    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 配置信息
        RequestConfig requestConfig = RequestConfig.custom()
                // 设置连接超时时间(单位毫秒)
                .setConnectTimeout(5000)
                // 设置请求超时时间(单位毫秒)
                .setConnectionRequestTimeout(5000)
                // socket读写超时时间(单位毫秒)
                .setSocketTimeout(5000)
                // 设置是否允许重定向(默认为true)
                .setRedirectsEnabled(true).build();

        // 将上面的配置信息 运用到这个Get请求里
        httpGet.setConfig(requestConfig);

        // 由客户端执行(发送)Get请求
        response = httpClient.execute(httpGet);

        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
        if (responseEntity != null) {
            returnJson = EntityUtils.toString(responseEntity);
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return returnJson;
}


/**
 * 发送post请求 - json
 * @param uri 发送请求的地址
 * @param object 请求体
 * @return json字符串
 */
public static String sendPost(String uri, Object object){
    //设置返回对象
    String returnJson = null;
    // 获得Http客户端(可以理解为:你得先有一个浏览器;注意:实际上HttpClient与浏览器是不一样的)
    CloseableHttpClient httpClient = HttpClientBuilder.create().build();
    // 创建Post请求
    HttpPost httpPost = new HttpPost(uri);

    //将Object转换为json字符串;
    String jsonString = JSON.toJSONString(object);
    StringEntity entity = new StringEntity(jsonString, "UTF-8");

    // post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中
    httpPost.setEntity(entity);
    httpPost.setHeader("Content-Type", "application/json;charset=utf8");

    // 响应模型
    CloseableHttpResponse response = null;
    try {
        // 由客户端执行(发送)Post请求
        response = httpClient.execute(httpPost);
        // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
        if (responseEntity != null) {
            returnJson = EntityUtils.toString(responseEntity);
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (ParseException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 释放资源
            if (httpClient != null) {
                httpClient.close();
            }
            if (response != null) {
                response.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return returnJson;
}

}

```

Centos7下docker使用## 安装

  1. 安装需要的软件包

    1
    yum install -y yum-utils device-mapper-persistent-data 
  2. 设置yum源

    1
    yum-config-manager --add-repo
  3. 查看docker版本

    1
    yum list docker-ce --showduplicates | sort -r
  4. 安装docker

    1
    yum install docker-ce-版本号

容器使用

1
2
3
4
5
6
# 启动
systemctl start docker
# 停止
systemctl stop docker
# 查看所有容器的状态
docker ps -a

查看容器

  • docker ps

    查看当前正在运行的容器

  • docker ps -a

    查看所有容器的状态

1
2
# 容器编号      图片     命令      已创建    状态    映射端口  名字
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

操作容器

  • docker start|stop|restart id/name

    根据id或者name启动、停止、重启容器

    • -d 后台运行容器
    • -p 8800:80 指定对外暴露的端口,容器内部用80,对外8800
    • –name 指定容器的名字
  • docker rm id/name

    删除某个容器

  • docker rename old_name new_name

    给这个容器命名。再次启动或停止容器时,就可以直接使用这个名字。

镜像使用

查看镜像

  • docker images

    查看所有镜像

操作镜像

  • docker run 镜像名

    将进行放到容器中, 变成运行时容器

  • docker rmi id/name

    删除某个镜像

JAVA配合Redis实现削峰## 限流思路图

限流思路图

实现代码

生产者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* 提交订单队列
*
* @param userId 用户id
* @param commodityId 订单id
* @param key 唯一key值
* @param request 请求
* @return 返回提交结果
*/
@PostMapping("/participate")
public ResultMsg participate(@RequestParam("userId") Long userId
, @RequestParam("commodityId") Long commodityId
, @RequestParam("key") String key
, HttpServletRequest request) {

//获取用户排队状态
BoundHashOperations<String, Object, Object> boundHashOps = redisTemplate.boundHashOps("active:kill");

//1. 查询当前秒杀状态
String status = (String) boundHashOps.get(userId + "");
//2. 判断是否已有状态保存,如果等于null表示没有在排队
if (null == status) {
//进入队列
if (addQueue(userId, commodityId)){
// 排队成功
redisTemplate.opsForValue().set("active:kill:" + userId, "2", 1, TimeUnit.DAYS);
status="2";
} else {
// 排队失败
redisTemplate.opsForValue().set("active:kill:" + userId, "1", 1, TimeUnit.SECONDS);
status="1";
}
}

ResultMsg msg = null;
if ("2".equals(status)){
msg = new ResultMsg(200, true, "正在排队中");
} else if ("3".equals(status)){
msg = new ResultMsg(200, false, "购买成功");
} else {
msg = new ResultMsg(402, false, "当前人数过多");
}
request.setAttribute("body", JSON.toJSONString(msg));
request.setAttribute("key", key);

return msg;
}

/**
* 进入队列
* @param userId 用户id
* @param commodityId 商品id
* @return 返回进入状态 true成功 false失败
*/
private boolean addQueue(Long userId, Long commodityId) {
//判断当前队列长度
Long size = redisTemplate.boundListOps("active:exChange").size();
if (size >= 1000) {
return false;
}
System.out.println("进入排队状态");
// 返回值的判断
redisTemplate.opsForList().rightPush("active:exChange", userId + "_" + commodityId);
return true;
}

消费者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.panshi.qssys.tesk;

import com.panshi.qssys.marketing.service.SecondKillService;
import com.panshi.qssys.share.exception.BusinessException;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.concurrent.ExecutorService;

/**
* @description: 商品秒杀定时任务
* @author: 蒋文豪
* @create: 2019/08/05
*/
@Component
public class SecondTask {

/**
* redis操作对象
*/
@Autowired
private StringRedisTemplate redisTemplate;

/**
* 秒杀功能服务接口
*/
@Autowired
private SecondKillService secondKillService;

@Autowired
private ExecutorService executorService;

/**
* 秒杀队列
*/
@Scheduled(cron = "0/1 * * * * ?")
public void secondKillQueue() {
System.out.println("定时任务启动");
//获取到队列消息
BoundListOperations<String, String> boundListOps = redisTemplate.boundListOps("active:exChange");
//排队状态
if (boundListOps.size() <= 0) {
return;
}
System.out.println(boundListOps.size());
System.out.println("获取到任务消息");
//循环10次
for (int i = 0; i < 10; i++) {
//使用线程执行
executorService.submit(() -> {
//1. 获取到队列中最左边的节点,并删除
String ids = boundListOps.leftPop();
System.out.println("ids" + ids);
if (null == ids) {
return;
}
//2. 将节点拆分出用户id和商品id
String[] idArray = ids.split("_");
if (null == idArray || 2 != idArray.length) {
return;
}
BoundValueOperations<String, String> valueOperations = redisTemplate.boundValueOps("active:kill:" + idArray[0]);
//3. 对商品进行消费
boolean b = secondKillService.submitOrder(Long.parseLong(idArray[0]), Long.parseLong(idArray[1]));
//4. 修改用户排队状态未已完成
if (b) {
valueOperations.set("3");
}
});
}

}
}

JAVA分布式锁## zk分布式锁实现原理

zk分布式锁流程图

  1. 客户端调用方法
  2. 服务端在zookeeper下创建临时节点并获取节点名
  3. 判断创建的节点是否为第一个节点
  4. 如果是,则拥有锁,使用完后释放锁
  5. 如果不是,等待监听其他节点释放。

redis分布式锁实现原理

单个redis实例

  • 加锁

    1. 服务端获取到唯一的value值,即能代表自己唯一身份的特征

    2. set值到redis中,如果值key不存在就插入并且设定好过期时间。

    代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class RedisTool {

    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";
    /**
    * 尝试获取分布式锁
    * @param jedis Redis客户端
    * @param lockKey 锁
    * @param requestId 请求标识
    * @param expireTime 超期时间
    * @return 是否获取成功
    */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
    if (LOCK_SUCCESS.equals(result)) {
    return true;
    }
    return false;
    }
    }
  • 解锁

    1. 首先获取锁对应的value值
    2. 检查是否与requestId相等
    3. 如果相等则删除锁(解锁)。
    4. 要确保上述操作是原子性的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;
    /**
    * 释放分布式锁
    * @param jedis Redis客户端
    * @param lockKey 锁
    * @param requestId 请求标识
    * @return 是否释放成功
    */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

    if (RELEASE_SUCCESS.equals(result)) {
    return true;
    }
    return false;
    }
    }

疑问点:

如果设置锁之后再设置过期时间会怎么样?

​ 有可能在获取锁之后服务挂掉,导致过期时间还未设置,结果锁一直不释放。

使用jedis.setnx()命令实现加锁,其中key是锁,value是锁的过期时间。有可能导致的问题

执行过程:

  1. 通过setnx()方法尝试加锁,如果当前锁不存在,返回加锁成功。
  2. 如果锁已经存在则获取锁的过期时间,和当前时间比较,如果锁已经过期,则设置新的过期时间,返回加锁成功。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime) {

long expires = System.currentTimeMillis() + expireTime;
String expiresStr = String.valueOf(expires);
// 如果当前锁不存在,返回加锁成功
if (jedis.setnx(lockKey, expiresStr) == 1) {
return true;
}
// 如果锁存在,获取锁的过期时间
String currentValueStr = jedis.get(lockKey);
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
// 锁已过期,获取上一个锁的过期时间,并设置现在锁的过期时间
String oldValueStr = jedis.getSet(lockKey, expiresStr);
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
// 考虑多线程并发的情况,只有一个线程的设置值和当前值相同,它才有权利加锁
return true;
}
}

// 其他情况,一律返回加锁失败
return false;

}

错误点

  1. 客户端设置的时间,可能导致时间不同步。
  2. 如果某一个客户端卡了很长一点时间,导致实际的锁已经过期,并且出现了下一个锁。
  3. 因为没有一个唯一标识,所以可能上一个客户端在解锁下一个客户端的锁。

多个redis实例

  1. 客户端调用方法
  2. 服务端获取当前Unix时间,以毫秒为单位。
  3. 依次尝试从多个实例,使用相同的key和具有唯一性的value(例如UUID)获取锁。向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试去另外一个Redis实例请求获取锁。
  4. 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(N/2+1,这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功
  5. 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  6. 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。

疑问点

如果获取到锁之后挂掉了怎么办?

​ 因为设置了有效时间,所有在多少时间后会自动删除掉key,这样就不会导致死锁

会不会出现两把锁的情况?

因为每个使用锁的对象需要获取到 (N/2+1),也就是一半的实例,所以就不会出现两把锁的情况

参考:

Redis分布式锁的正确实现方式(Java版)

windows设置只有指定IP段才走VPN

  1. 在VPN连接属性–>网络–>Internet协议版本4–>属性–>高级
    去掉《在远程网络上使用默认网关》,这样做让所有连接都走本地网络连接,不走VPN
  2. 管理员身份打开cmd,执行命令:ipconfig,找到VPN连接的IP地址
1
route -p add 192.168.8.0 mask 255.255.0.0 192.168.7.204

其中192.168.8.0是需要通过VPN访问的IP段,192.168.7.204是VPN连接的IP地址,这两个IP需要修改为自己实际的IP
这样做是让192.168.8.1至192.168.8.255的IP地址通过VPN访问

  1. 重新连接VPN

Redis实现Redlock分布式锁

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--Springboot环境下 redis分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.11.1</version>
</dependency>

<!-- Springboot环境下 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

public void test(){
//创建连接1
RedissonClient redissonClient1 = getRedissonClient("redis://127.0.0.1:6379", 0);
//创建连接2
RedissonClient redissonClient2 = getRedissonClient("redis://127.0.0.1:6379", 1);
//创建连接3
RedissonClient redissonClient3 = getRedissonClient("redis://127.0.0.1:6379", 2);

//设置锁的key
String resourceName = "REDLOCK_KEY";

//获取锁
RLock lock1 = redissonClient1.getLock(resourceName);
RLock lock2 = redissonClient2.getLock(resourceName);
RLock lock3 = redissonClient3.getLock(resourceName);
// 向3个redis实例尝试加锁
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
boolean isLock;

List<MaxLimitRankingVo> list = null;
try {
// 500ms拿不到锁, 就认为获取锁失败。10000ms即10s是锁失效时间。
isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS);
System.out.println("isLock = "+isLock);
if (isLock) {
Thread.sleep(8000);
}
} catch (Exception e) {
} finally {
// 无论如何, 最后都要解锁
redLock.unlock();
}
}


/**
* 获取 RedissonClient
* @param address 地址
* @return
*/
public RedissonClient getRedissonClient(String address, int database){
Config config = new Config();
config.useSingleServer().setAddress(address).setDatabase(database);
return Redisson.create(config);

}

文档参考

Redis分布式锁的正确实现方式

spring-boot 经典布局 及 启动配置

依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!--
spring-boot-starter-parent是一个特殊启动器,提供一些Maven的默认值。
它还提供依赖管理 dependency-management 标签,以便您可以省略子模块依赖关系的版本标签。
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
</parent>
<dependencies>


<!-- 依赖Spring boot web 启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--配置文件提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<!-- 可执行jar插件; 运行jar就等于运行了一个web项目-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

布局

com

  • example
    • myproject
      • Application.java
      • domain
        • Customer.java
        • CustomerRepository.java
      • service
        • CustomerService.java
      • web
        • CustomerController.java

启动函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.example.myproject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

//@Configuration
//@EnableAutoConfiguration
//@ComponentScan
//以上注解可直接换成@SpringBootApplication使用
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

静态资源

  • 默认情况下,SpringBoot将从类路径或ServletContext的根目录中的名为/static(或/public或/resources或/META-INF/resources)的目录提供静态内容
  • 它使用Spring MVC中的ResourceHttpRequestHandler,因此您可以通过添加自己的WebMvcConfigurerAdapter并覆盖addResourceHandlers方法来修改该行为。
  • 默认情况下,资源映射到/ ,但可以通过spring.mvc.static-path-pattern调整。 例如,将所有资源重定位到 /resources/可以配置如下:
    1
    2
    3
    4
    5
    6
    7
    spring.mvc.static‐path‐pattern=/resources/**


    # 修改前
    127.0.0.1:8080/xxx.html
    # 修改后
    127.0.0.1:8080/resources/xxx.html

错误页

resources.static.error.404.html

自定义图标

resources.static.favicon.ico

热部署

pom.xml依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

IDEA配置

当我们修改了Java类后,IDEA默认是不自动编译的,而spring-boot-devtools又是监测classpath下的文件发生变化才会重启应用,所以需要设置IDEA的自动编译:

(1)File-Settings-Compiler-Build Project automatically

(2)ctrl + shift + alt + /,选择Registry,勾上 Compiler autoMake allow when app running

你可以实时自动重启了

tomcat中html乱码

tomcat中html乱码问题下面的配置都在conf文件夹里面配置

  1. 在web.xml中配置
    1
    2
    3
    4
    5
    6
    7
    8
    <mime-mapping>
    <extension>htm</extension>
    <mime-type>text/html;charset=UTF-8</mime-type>
    </mime-mapping>
    <mime-mapping>
    <extension>html</extension>
    <mime-type>text/html;charset=UTF-8</mime-type>
    </mime-mapping>
  2. Tomcat server.xml中设置编码Tomcat
    添加一个 URIEncoding=”UTF-8”
    1
    2
    <Connector port="8080" protocol="HTTP/1.1"  connectionTimeout="20000"
    redirectPort="8443" URIEncoding="UTF-8"/>
  3. 在idea中配置
    1
    -Dfile.encoding=UTF8
    tomcat1.jpeg
    tomcat2.jpeg