修改图片路径
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
横切关注点可以被描述为影响应用多处的功能,切面能够帮助我们模块化横切关注点。下图直观呈现了横切关注点的概念:
|
横切关注点可以被描述为影响应用多处的功能,切面能够帮助我们模块化横切关注点。下图直观呈现了横切关注点的概念:
|
||||||
|
|
||||||
![横切关注点](./3.面向切面的Spring/切面示例.png)
|
![横切关注点](./picFolder/切面示例.png)
|
||||||
|
|
||||||
途中CourseService,StudentService,MiscService都需要类似安全、事务这样的辅助功能,这些辅助功能就被称为横切关注点。
|
途中CourseService,StudentService,MiscService都需要类似安全、事务这样的辅助功能,这些辅助功能就被称为横切关注点。
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ public class Audience{
|
|||||||
|
|
||||||
  既然可以用AOP为对象拥有的方法添加新功能,那为什么不能为对象增加新的方法呢?利用被称为**引入**的AOP概念,切面可以为Spring Bean添加新的方法,示例图如下:
|
  既然可以用AOP为对象拥有的方法添加新功能,那为什么不能为对象增加新的方法呢?利用被称为**引入**的AOP概念,切面可以为Spring Bean添加新的方法,示例图如下:
|
||||||
|
|
||||||
![引入](.\3.面向切面的Spring\引入新功能.png)
|
![引入](./picFolder/引入新功能.png)
|
||||||
|
|
||||||
当引入接口的方法被调用时,代理将此调用委托给实现了新接口的某个其他对象。实际上,Bean的实现被拆分到了多个类。
|
当引入接口的方法被调用时,代理将此调用委托给实现了新接口的某个其他对象。实际上,Bean的实现被拆分到了多个类。
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@ -1,48 +0,0 @@
|
|||||||
  前面的博客有说到spring boot搭建见另一篇博文,其实那篇博文还没写,现在来填个坑。我们使用spring initializr来构建,idea和eclipse都支持这种方式,构建过程类似,这里以idea为例,详细记录构建过程。
|
|
||||||
|
|
||||||
###1.选择spring initializr
|
|
||||||
|
|
||||||
![1532967570728](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967570728.png)
|
|
||||||
|
|
||||||
next
|
|
||||||
|
|
||||||
#### 2.设置参数
|
|
||||||
|
|
||||||
![1532967772110](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967772110.png)
|
|
||||||
|
|
||||||
next
|
|
||||||
|
|
||||||
#### 3.选择依赖
|
|
||||||
|
|
||||||
  在这里选择spring boot版本和web依赖(忽略sql的依赖,如有需要[点击这里](f),单独将mybatis的整合),后面也可手动编辑pom文件修改增加删除依赖
|
|
||||||
|
|
||||||
![1532967938985](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967938985.png)
|
|
||||||
|
|
||||||
这里我们选择web搭建一个简单的REST风格demo。然后next。
|
|
||||||
|
|
||||||
####4.设置项目存放地址
|
|
||||||
|
|
||||||
![1532968024509](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532968024509.png)
|
|
||||||
|
|
||||||
这样就成功构建了一个springboot项目。
|
|
||||||
|
|
||||||
#### 5.测试
|
|
||||||
|
|
||||||
  现在新建一个controller包,包下新建一个HelloController,创建之后项目目录结构如下:
|
|
||||||
|
|
||||||
![1532969025023](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532969025023.png)
|
|
||||||
|
|
||||||
HelloController代码如下:
|
|
||||||
|
|
||||||
```java
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/home")
|
|
||||||
public class HelloController{
|
|
||||||
@GetMapping("/hello")
|
|
||||||
public String sayHello(){
|
|
||||||
return "hello";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
然后运行项目,访问localhost:8080/home/hello即可看到hello字符串。
|
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
###1.选择spring initializr
|
###1.选择spring initializr
|
||||||
|
|
||||||
![1532967570728](.\springboot搭建.assets\1532967570728.png)
|
![1532967570728](./picFolder/1532967570728.png)
|
||||||
|
|
||||||
next
|
next
|
||||||
|
|
||||||
#### 2.设置参数
|
#### 2.设置参数
|
||||||
|
|
||||||
![1532967772110](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967772110.png)
|
![1532967772110](./picFolder/1532967772110.png)
|
||||||
|
|
||||||
next
|
next
|
||||||
|
|
||||||
@ -16,13 +16,13 @@ next
|
|||||||
|
|
||||||
  在这里选择spring boot版本和web依赖(忽略sql的依赖,如有需要[点击这里](f),单独将mybatis的整合),后面也可手动编辑pom文件修改增加删除依赖
|
  在这里选择spring boot版本和web依赖(忽略sql的依赖,如有需要[点击这里](f),单独将mybatis的整合),后面也可手动编辑pom文件修改增加删除依赖
|
||||||
|
|
||||||
![1532967938985](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532967938985.png)
|
![1532967938985](./picFolder/1532967938985.png)
|
||||||
|
|
||||||
这里我们选择web搭建一个简单的REST风格demo。然后next。
|
这里我们选择web搭建一个简单的REST风格demo。然后next。
|
||||||
|
|
||||||
####4.设置项目存放地址
|
####4.设置项目存放地址
|
||||||
|
|
||||||
![1532968024509](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532968024509.png)
|
![1532968024509](./picFolder/1532968024509.png)
|
||||||
|
|
||||||
这样就成功构建了一个springboot项目。
|
这样就成功构建了一个springboot项目。
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ next
|
|||||||
|
|
||||||
  现在新建一个controller包,包下新建一个HelloController,创建之后项目目录结构如下:
|
  现在新建一个controller包,包下新建一个HelloController,创建之后项目目录结构如下:
|
||||||
|
|
||||||
![1532969025023](D:\笔记\markdown\springboot系列\springboot搭建.assets\1532969025023.png)
|
![1532969025023](./picFolder/1532969025023.png)
|
||||||
|
|
||||||
HelloController代码如下:
|
HelloController代码如下:
|
||||||
|
|
||||||
|
BIN
springboot系列/springsecurity/picFolder/pic1.png
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
springboot系列/springsecurity/picFolder/pic2.png
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
springboot系列/数据库/picFolder/pic1.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
springboot系列/数据库/picFolder/pic2.png
Normal file
After Width: | Height: | Size: 87 KiB |
BIN
springboot系列/数据库/picFolder/pic3.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
springboot系列/数据库/picFolder/pic4.png
Normal file
After Width: | Height: | Size: 117 KiB |
BIN
springboot系列/消息队列/picFolder/pic1.png
Normal file
After Width: | Height: | Size: 7.5 KiB |
BIN
springboot系列/消息队列/picFolder/pic2.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
springboot系列/消息队列/picFolder/pic3.png
Normal file
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
然后重启数据库,使用`show master status;`语句查看主库状态,如下所示:
|
然后重启数据库,使用`show master status;`语句查看主库状态,如下所示:
|
||||||
|
|
||||||
![主库状态](.\读写分离配置\pic1.png)
|
![主库状态](./picFolder/pic1.png)
|
||||||
|
|
||||||
- 从库配置
|
- 从库配置
|
||||||
|
|
||||||
@ -59,7 +59,7 @@
|
|||||||
|
|
||||||
接着运行`start slave;`开启备份,正常情况如下图所示:Slave_IO_Running和Slave_SQL_Running都为yes。
|
接着运行`start slave;`开启备份,正常情况如下图所示:Slave_IO_Running和Slave_SQL_Running都为yes。
|
||||||
|
|
||||||
![1536223020742](.\读写分离配置\pic2.png)
|
![1536223020742](./picFolder/pic2.png)
|
||||||
|
|
||||||
可以用这个步骤开启多个从库。
|
可以用这个步骤开启多个从库。
|
||||||
|
|
||||||
@ -306,6 +306,6 @@ public class ReadOnlyInterceptor implements Ordered {
|
|||||||
|
|
||||||
  编写好代码来试试结果如何,下面是运行截图:
|
  编写好代码来试试结果如何,下面是运行截图:
|
||||||
|
|
||||||
![1536312274474](.\读写分离配置\pic3.png)
|
![1536312274474](./picFolder/pic3.png)
|
||||||
|
|
||||||
  断断续续写了好几天终于是写完了,,,如果有帮助到你,,欢迎star哦,,这里是完整代码地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/dxfl)
|
  断断续续写了好几天终于是写完了,,,如果有帮助到你,,欢迎star哦,,这里是完整代码地址:[点击跳转](https://github.com/FleyX/demo-project/tree/master/dxfl)
|
17
其他各种/git crlf、lf自动转换引起的问题.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
  项目组最近加了一个新功能到代码中,使用flyway记录数据库版本变更,,,该工具会记录每次数据库结构的修改并生成sql文件存在指定目录上(当然必须用它来变更数据库,外部的变更它是无法感知的),然后每次启动时flyway会检查使用的数据库和当前项目代码中的sql变更版本是否一致,一致正常启动,不一致中如果是数据库落后将会更新数据库(这样能够保证代码在任何地方运行数据库都是一致的),否则就报错了。数据库中有一张表记录版本信息,如下图:
|
||||||
|
|
||||||
|
![版本记录](./picFolder/版本记录.PNG),同时本地代码中也有一个文件夹保存每次操作的sql语句,如下图:
|
||||||
|
|
||||||
|
![版本sql](./picFolder/版本sql.PNG)
|
||||||
|
|
||||||
|
通过对比checksum值来判断当前sql语句和生成数据库的执行语句是否一致,checksum值由CRC32计算后处理得出。
|
||||||
|
|
||||||
|
  然后问题就来了,组中的其他人搭建好flyway后,项目文件生成了两个sql文件,我用git拉下来后启动报错,checkupsum值对不上,,然后我又不懂这个flyway完全不知道咋回事,然后就根据报错的位置一点点找到checkup值生成的代码,发现是CRC32计算的,,(就这么搞了一两个小时才发现是文件不一致了),但是都是从git拉的怎么就我不一致呢???想到可能是文件换行符的问题,遂把那几个sql文件的文件换行符全换成了crlf(windows中的换行符),然后居然就能够运行。。。关于为啥都从git拉取的文件换行符会不一样原因是:他们都用的那个小乌龟的可视化,我用的命令行。可视化工具自动配置了文件换行符的自动转换(这是git的一个智能功能,上传时将文件换行符替换为lf,,拉取时再替换为crlf,,这样保证中心仓库使用UNIX风格的换行符,,本地能够根据运行环境使用相对应的换行符风格),但是命令行并没有配置。
|
||||||
|
|
||||||
|
  解决办法也很简单,开启git 的自动转换。
|
||||||
|
|
||||||
|
```
|
||||||
|
git config --global core.autocrlf true //开启换行符自动转换
|
||||||
|
git config --global core.safecrlf true //禁止混用换行符
|
||||||
|
```
|
||||||
|
|
177
其他各种/java导出EXCEL文件.md
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
## 一、背景
|
||||||
|
|
||||||
|
  最近在java上做了一个EXCEL的导出功能,写了一个通用类,在这里分享分享,该类支持多sheet,且无需手动进行复杂的类型转换,只需提供三个参数即可:
|
||||||
|
|
||||||
|
- `fileName`
|
||||||
|
|
||||||
|
excel文件名
|
||||||
|
|
||||||
|
- `HasMap<String,List<?>> data`
|
||||||
|
|
||||||
|
具体的数据,每个List代表一张表的数据,?表示可为任意的自定义对象
|
||||||
|
|
||||||
|
- `LinkedHashMap<String,String[][]> headers`
|
||||||
|
|
||||||
|
`Stirng`代表sheet名。每个`String[][] `代表一个sheet的定义,举个例子如下:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String[][] header = {
|
||||||
|
{"field1","参数1"}
|
||||||
|
,{"field2","参数2"}
|
||||||
|
,{"field3","参数3"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
其中的field1,field2,field3为对象中的属性名,参数1,参数2,参数3为列名,实际上这个指定了列的名称和这个列用到数据对象的哪个属性。
|
||||||
|
|
||||||
|
## 二、怎么用
|
||||||
|
|
||||||
|
  以一个例子来说明怎么用,假设有两个类A和B定义如下:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class A{
|
||||||
|
private String name;
|
||||||
|
private String address;
|
||||||
|
}
|
||||||
|
public class B{
|
||||||
|
private int id;
|
||||||
|
private double sum;
|
||||||
|
private String cat;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
现在我们通过查询数据库获得了A和B的两个列表:
|
||||||
|
|
||||||
|
```java
|
||||||
|
List<A> dataA = .....;
|
||||||
|
List<B> dataB = .....;
|
||||||
|
```
|
||||||
|
|
||||||
|
我们将这两个导出到excel中,首先需要定义sheet:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String[][] sheetA = {
|
||||||
|
{"name","姓名"}
|
||||||
|
,{"address","住址"}
|
||||||
|
}
|
||||||
|
String[][] sheetB = {
|
||||||
|
{"id","ID"}
|
||||||
|
,{"sum","余额"}
|
||||||
|
,{"cat","猫的名字"}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后将数据汇总构造一个ExcelUtil:
|
||||||
|
|
||||||
|
```java
|
||||||
|
String fileName = "测试Excel";
|
||||||
|
HashMap<String,List<?>> data = new HashMap<>();
|
||||||
|
//ASheet为表名,后面headers里的key要跟这里一致
|
||||||
|
data.put("ASheet",dataA);
|
||||||
|
data.put("BSheet",dataB);
|
||||||
|
LinkedHashMap<String,String[][]> headers = new LinkedHashMap<>();
|
||||||
|
headers.put("ASheet",sheetA);
|
||||||
|
headers.put("BSheet",sheetB);
|
||||||
|
ExcelUtil excelUtil = new ExcelUtil(fileName,data,headers);
|
||||||
|
//获取表格对象
|
||||||
|
HSSFWorkbook workbook = excelUtil.createExcel();
|
||||||
|
//这里内置了一个写到response的方法(判断浏览器类型设置合适的参数),如果想写到文件也是类似的
|
||||||
|
workbook.writeToResponse(workbook,request,response);
|
||||||
|
```
|
||||||
|
|
||||||
|
当然通常数据是通过数据库查询的,这里为了演示方便没有从数据库查找。
|
||||||
|
|
||||||
|
## 三、实现原理
|
||||||
|
|
||||||
|
  这里简单说明下实现过程,从调用`createExcel()`这里开始
|
||||||
|
|
||||||
|
####1、遍历headers创建sheet
|
||||||
|
|
||||||
|
```java
|
||||||
|
public HSSFWorkbook createExcel() throws Exception {
|
||||||
|
try {
|
||||||
|
HSSFWorkbook workbook = new HSSFWorkbook();
|
||||||
|
//遍历headers创建表格
|
||||||
|
for (String key : headers.keySet()) {
|
||||||
|
this.createSheet(workbook, key, headers.get(key), this.data.get(key));
|
||||||
|
}
|
||||||
|
return workbook;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建表格失败:{}", e.getMessage());
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
将workbook,sheet名,表头数据,行数据传入crateSheet方法中创建sheet。
|
||||||
|
|
||||||
|
#### 2、创建表头
|
||||||
|
|
||||||
|
  表头也就是一个表格的第一行,通常用来对列进行说明
|
||||||
|
|
||||||
|
```java
|
||||||
|
HSSFSheet sheet = workbook.createSheet(sheetName);
|
||||||
|
// 列数
|
||||||
|
int cellNum = header.length;
|
||||||
|
// 单元行,单元格
|
||||||
|
HSSFRow row;
|
||||||
|
HSSFCell cell;
|
||||||
|
// 表头单元格样式
|
||||||
|
HSSFCellStyle columnTopStyle = this.getColumnTopStyle(workbook);
|
||||||
|
// 设置表头
|
||||||
|
row = sheet.createRow(0);
|
||||||
|
for (int i = 0; i < cellNum; i++) {
|
||||||
|
cell = row.createCell(i);
|
||||||
|
cell.setCellStyle(columnTopStyle);
|
||||||
|
String str = header[i][1];
|
||||||
|
cell.setCellValue(str);
|
||||||
|
// 设置列宽为表头的文字宽度+6个半角符号宽度
|
||||||
|
sheet.setColumnWidth(i, (str.getBytes("utf-8").length + 6) * 256);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3、插入行数据
|
||||||
|
|
||||||
|
  这里是最重要的部分,首先通过数据的类对象获取它的反射属性Field类,然后将属性名和Field做一个hash映射,避免循环查找,提高插入速度,接着通过一个switch语句,根据属性类别设值,主要代码如下:
|
||||||
|
|
||||||
|
```java
|
||||||
|
/**
|
||||||
|
* 设置单元格,根据fieldName获取对应的Field类,使用反射得到值
|
||||||
|
*
|
||||||
|
* @param cell 单元格实例
|
||||||
|
* @param obj 存有属性的对象实例
|
||||||
|
* @param fieldMap 属性名与Field的映射
|
||||||
|
* @param fieldName 属性名
|
||||||
|
*/
|
||||||
|
private void setCell(HSSFCell cell, Object obj, Map<String, Field> fieldMap, String fieldName) throws Exception {
|
||||||
|
//获取该属性的Field对象
|
||||||
|
Field field = fieldMap.get(fieldName);
|
||||||
|
//通过反射获取属性的值,由于不能确定该值的类型,用下面的判断语句进行合适的转型
|
||||||
|
Object value = field.get(obj);
|
||||||
|
if (value == null) {
|
||||||
|
cell.setCellValue("");
|
||||||
|
} else {
|
||||||
|
switch (field.getGenericType().getTypeName()) {
|
||||||
|
case "java.lang.String":
|
||||||
|
cell.setCellValue((String) value);
|
||||||
|
break;
|
||||||
|
case "java.lang.Integer":
|
||||||
|
case "int":
|
||||||
|
cell.setCellValue((int) value);
|
||||||
|
break;
|
||||||
|
case "java.lang.Double":
|
||||||
|
case "double":
|
||||||
|
cell.setCellValue((double) value);
|
||||||
|
break;
|
||||||
|
case "java.util.Date":
|
||||||
|
cell.setCellValue(this.dateFormat.format((Date) value));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cell.setCellValue(obj.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
完整代码可以到github上查看下载,这里就不列出来了。
|
||||||
|
|
||||||
|
github地址:[点击跳转]()
|
BIN
其他各种/picFolder/版本sql.PNG
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
其他各种/picFolder/版本记录.PNG
Normal file
After Width: | Height: | Size: 8.8 KiB |
48
其他各种/正则表达式在密码匹配中的使用.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
##一、背景
|
||||||
|
|
||||||
|
  今天领导让我写几个正则表达式来对密码做强度验证,听到写正则表达式内心是这样的感觉(哈哈,三分钟搞定,今天又可以打鱼了)。需求如下:密码组成只能是**数字**,**字母**,**英文可见半角符号**,然后需要如下4个表达式:
|
||||||
|
|
||||||
|
- 长度6位及以上
|
||||||
|
- 长度6位及以上,包含数字,包含字母
|
||||||
|
- 长度6位及以上,包含数字,包含字母,包含半角符号
|
||||||
|
- 长度六位及以上,包含数字,包含大写字母,包含小写字母,包含半角符号
|
||||||
|
|
||||||
|
  看完需求我就有点懵了,包含数字或者字母我会写,但是同时存在还要在一个表达式中就有点懵了。
|
||||||
|
|
||||||
|
##二、解决方法
|
||||||
|
|
||||||
|
  以第三种为例,这个可以分解为如下需求:
|
||||||
|
|
||||||
|
- 存在数字
|
||||||
|
- 存在字母
|
||||||
|
- 存在半角符号
|
||||||
|
- 长度六位及以上
|
||||||
|
|
||||||
|
关键是如何同时满足前三个条件,在我有限的知识里并不知道怎么搞,然后只好求助于万能的百度了,最终在找了几个小时后发现如下几个关键词,来源[菜鸟教程](http://www.runoob.com/java/java-regular-expressions.html) :
|
||||||
|
|
||||||
|
- (?=*pattern*) :正向预测先行搜索
|
||||||
|
|
||||||
|
名字看着高大上,不明所以,看完示例大概明白什么意思,这个表达式匹配从这个表达式起始的字符串(我也不知道咋解释),就是假设这样一个表达式abc(?=[abc]) ,用它来匹配abc123字符串,(?=[abc])只会对作用于后面的123,这个显然是不匹配的后整个就不匹配了,然后关键来了名字里有**预测**两个字,这两个字表名了这个表达式的特性:不占用字符,匹配后如果匹配成功就继续匹配了好像从来不存在这个东西一样,匹配失败就立即返回失败了。利用这个特性我们就可以给正则加限制条件了。
|
||||||
|
|
||||||
|
- (?!*pattern*) :反向预测先行搜索
|
||||||
|
|
||||||
|
概念和上面一样,但是效果是相反的,abc(?![abc]),对于abc123是匹配成功的,对于abca匹配失败,如下所示:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
reg = /abc(?![abc])/;
|
||||||
|
reg.test("abc123")
|
||||||
|
//返回true
|
||||||
|
reg.test("abca")
|
||||||
|
//返回false
|
||||||
|
```
|
||||||
|
|
||||||
|
  有了上面的知识就能搞定需求啦。
|
||||||
|
|
||||||
|
## 三、结果
|
||||||
|
|
||||||
|
  对于存在字母我们可以用这样的表达式`(?=.\*?[a-zA-Z]+.\*?),来检查是否存在至少一个字母,最后对于需求3的表达式如下:(半角字符我用的ASCII码里的16进制表示的)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
^(?=.*?\d+.*?)(?=.*?[a-zA-Z]+.*?)(?=.*?[\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]+.*?)[\da-zA-Z\x21-\x2F\x3A-\x40\x5B-\x60\x7B-\x7E]{6,}$
|
||||||
|
```
|
||||||
|
|
452
其他各种/毕设.md
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
  毕业才刚刚两个多月而已,现在想想大学生活是那么的遥不可及,感觉已经过了好久好久,社会了两个月才明白学校的好啊。。。额,扯远了,自从毕业开始就想找个时间写下毕设的记录总结,结果找了好久好久到今天才开始动笔。
|
||||||
|
|
||||||
|
  我的毕业设计题目是:教学辅助系统的设计与实现,,是不是很俗。。。至于为啥是这个题目呢,完全是被导师坑了。。。。。
|
||||||
|
|
||||||
|
## 1、需求分析
|
||||||
|
|
||||||
|
  拿到这个题目想着这个可能被做了无数次了,就像着哪里能够做出点创新,,最后强行创新出了一个个性化组题(根据学生水平出题)和徽章激励(达到某个要求给予一个徽章)。最后就产生了如下需求,系统有学生端和管理端:
|
||||||
|
|
||||||
|
学生端:
|
||||||
|
|
||||||
|
- 个人资料设置
|
||||||
|
- 徽章激励机制
|
||||||
|
- 查看课程信息,下载课程资料
|
||||||
|
- 知识点检测及针对性训练
|
||||||
|
- 在线作业,考试
|
||||||
|
- 在线答疑,向老师或者学生提问
|
||||||
|
|
||||||
|
管理端:
|
||||||
|
|
||||||
|
- 课程管理,用户管理(需要管理员权限)
|
||||||
|
- 课程信息管理
|
||||||
|
- 课程公告管理
|
||||||
|
- 题库管理,支持单选,多选,填空,编程题,支持题目编组
|
||||||
|
- 发布作业,包括个性组题和手动组题
|
||||||
|
- 发布考试,包括随机出题和手动出题
|
||||||
|
- 自动判题,支持编程题判重
|
||||||
|
- 在线答疑,给学生解答
|
||||||
|
- 统计分析,包含测试统计和课程统计
|
||||||
|
|
||||||
|
洋洋洒洒需求列了一大堆,后面才发现是给自己挖坑,,答辩老师一看这类的题目就不感兴趣了,不论你做的咋样(况且我的演讲能力真的很一般),最后累死累活写了一大堆功能也没太高的分,,不过倒是让我的系统设计能力和代码能力有了不少的提高。
|
||||||
|
|
||||||
|
## 2、架构选择
|
||||||
|
|
||||||
|
  大三的时候了解到Node.js这个比较“奇葩"的异步语言,再加上在公司实习了三个月也是用的node开发,对node已经比较熟悉了,于是就用它做了后台,前端用最近比较火的vue.js做单页应用。当时还想着负载均衡啥的,就没有用传统的session,cookie机制,转而用jwt做的基于token的身份认证,同时后台接口也是类Restful风格的(因为纯正的Rest接口太难设计了)。
|
||||||
|
|
||||||
|
总的来说后台用了以下技术和框架:
|
||||||
|
|
||||||
|
  总的来说后台用了以下技术和框架:
|
||||||
|
|
||||||
|
- 语言:Node.js
|
||||||
|
- web框架:kOA
|
||||||
|
- 前后台传输协议:jwt
|
||||||
|
- 缓存:redis
|
||||||
|
- 数据库:mysql
|
||||||
|
- 编程题判题核心:[青岛大学OJ判题核心](https://github.com/QingdaoU/JudgeServer)
|
||||||
|
- 代码判重:[SIM](https://dickgrune.com/Programs/similarity_tester/)
|
||||||
|
|
||||||
|
前台技术如下:
|
||||||
|
|
||||||
|
- 框架:Vue.js
|
||||||
|
- UI框架:Element-UI
|
||||||
|
- 图表组件:G2
|
||||||
|
|
||||||
|
## 3、系统基础框架搭建
|
||||||
|
|
||||||
|
  本系统是前后端分离的,下面分别介绍前后端的实现基础。
|
||||||
|
|
||||||
|
### 1、后台
|
||||||
|
|
||||||
|
  一个web后台最重要的无非那么几个部分:路由;权限验证;数据持久化。
|
||||||
|
|
||||||
|
#### a、路由
|
||||||
|
|
||||||
|
KOA作为一个web框架其实它本身并没有提供路由功能,需要配合使用koa-router来实现路由,koa-router以类似下面这样的风格来进行路由:
|
||||||
|
|
||||||
|
  KOA作为一个web框架其实它本身并没有提供路由功能,需要配合使用koa-router来实现路由,koa-router以类似下面这样的风格来进行路由:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const app = require("koa");
|
||||||
|
const router = require("koa-router");
|
||||||
|
router.get("/hello",koa=>{
|
||||||
|
koa.response="hello";
|
||||||
|
});
|
||||||
|
app.use(router.routes())
|
||||||
|
```
|
||||||
|
|
||||||
|
显然这样在项目中是很不方便的,如果每个路由都要手动进行挂载,很难将每个文件中的路由都挂载到一个router中。因此在参考网上的实现后,我写了一个方法在启动时自动扫描某个文件夹下所有的路由文件并挂载到router中,代码如下:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const koaBody = require('koa-body');
|
||||||
|
const config = require('../config/config.js');
|
||||||
|
|
||||||
|
function addMapping(router, filePath) {
|
||||||
|
let mapping = require(filePath);
|
||||||
|
for (let url in mapping) {
|
||||||
|
if (url.startsWith('GET ')) {
|
||||||
|
let temp = url.substring(4);
|
||||||
|
router.get(temp, mapping[url]);
|
||||||
|
console.log(`----GET:${temp}`);
|
||||||
|
} else if (url.startsWith('POST ')) {
|
||||||
|
let temp = url.substring(5);
|
||||||
|
router.post(temp, mapping[url]);
|
||||||
|
console.log(`----POST:${temp}`);
|
||||||
|
} else if (url.startsWith('PUT ')) {
|
||||||
|
let temp = url.substring(4);
|
||||||
|
router.put(temp, mapping[url]);
|
||||||
|
console.log(`----PUT:${temp}`)
|
||||||
|
} else if (url.startsWith('DELETE ')) {
|
||||||
|
let temp = url.substring(7);
|
||||||
|
router.delete(temp, mapping[url]);
|
||||||
|
console.log(`----DELETE: ${temp}`);
|
||||||
|
} else {
|
||||||
|
console.log(`xxxxx无效路径:${url}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addControllers(router, filePath) {
|
||||||
|
let files = fs.readdirSync(filePath);
|
||||||
|
files.forEach(element => {
|
||||||
|
let temp = path.join(filePath, element);
|
||||||
|
let state = fs.statSync(temp);
|
||||||
|
if (state.isDirectory()) {
|
||||||
|
addControllers(router, temp);
|
||||||
|
} else {
|
||||||
|
if (!temp.endsWith('Helper.js')) {
|
||||||
|
console.log('\n--开始处理: ' + element + "路由");
|
||||||
|
addMapping(router, temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function engine(router, folder) {
|
||||||
|
addControllers(router, folder);
|
||||||
|
return router.routes();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = engine;
|
||||||
|
```
|
||||||
|
|
||||||
|
然后在index.js中use此方法:
|
||||||
|
|
||||||
|
```
|
||||||
|
const RouterMW = require("./middleware/controllerEngine.js");
|
||||||
|
app.use(RouterMW(router,path.join(config.rootPath, 'api')));
|
||||||
|
```
|
||||||
|
|
||||||
|
然后路由文件以下面的形式编写:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const knowledgePointDao = require('../dao/knowledgePointDao.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回某门课的全部知识点,按章节分类
|
||||||
|
*/
|
||||||
|
exports["GET /course/:c_id/knowledge_point"] = async (ctx, next) => {
|
||||||
|
let res = await knowledgePointDao.getPontsOrderBySection(ctx.params.c_id);
|
||||||
|
ctx.onSuccess(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
//返回某位学生知识点答题情况
|
||||||
|
exports["GET /user/:u_id/course/:c_id/knowledge_point/condition"]=async(ctx,next)=>{
|
||||||
|
let {u_id,c_id}=ctx.params;
|
||||||
|
let res = await knowledgePointDao.getStudentCondition(u_id,c_id);
|
||||||
|
ctx.onSuccess(res);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### b、权限验证
|
||||||
|
|
||||||
|
  权限管理是一个系统最重要的部分之一,目前主流的方式为**基于角色的权限管理**, 一个用户对应多个角色,每个角色对应多个权限(本系统中每个用户对应一个身份,每个身份对应多个角色)。我们的系统如何实现的呢?先从登录开始说起,本系统抛弃了传统的cookie,session模式,使用json web token(JWT)来做身份认证,用户登录后返回一个token给客户端,代码如下所示:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
//生成随机盐值
|
||||||
|
let str = StringHelper.getRandomString(0, 10);
|
||||||
|
//使用该盐值生成token
|
||||||
|
let token = jwt.sign({
|
||||||
|
u_id: userInfo.u_id,
|
||||||
|
isRememberMe
|
||||||
|
}, str, {
|
||||||
|
expiresIn: isRememberMe ? config.longTokenExpiration:config.shortTokenExpiration
|
||||||
|
});
|
||||||
|
//token-盐值存入redis,如想让该token过期,redis中清楚该token键值对即可
|
||||||
|
await RedisHelper.setString(token, str, 30 * 24 * 60 * 60);
|
||||||
|
res.code = 1;
|
||||||
|
res.info = '登录成功';
|
||||||
|
res.data = {
|
||||||
|
u_type: userInfo.u_type,
|
||||||
|
u_id: userInfo.u_id,
|
||||||
|
token
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
以后每次客户端请求都要在header中设置该token,然后每次服务端收到请求都先验证是否拥有权限,验证代码使用`router.use(auth)`,挂载到koa-router中,这样每次在进入具体的路由前都要先执行auth方法进行权限验证,主要验证代码逻辑如下:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* 1 验证成功
|
||||||
|
* 2 登录信息无效 401
|
||||||
|
* 3 已登录,无操作权限 403
|
||||||
|
* 4 token已过期
|
||||||
|
*/
|
||||||
|
let verify = async (ctx) => {
|
||||||
|
let token = ctx.headers.authorization;
|
||||||
|
if (typeof (token) != 'string') {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
let yan = await redisHelper.getString(token);
|
||||||
|
if (yan == null) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = jwt.verify(token, yan);
|
||||||
|
} catch (e) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (data.exp * 1000 < Date.now()) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
//判断是否需要刷新token,如需要刷新将新token写入响应头
|
||||||
|
if (!data.isRememberMe && (data.exp * 1000 - Date.now()) < 30 * 60 * 1000) {
|
||||||
|
//token有效期不足半小时,重新签发新token给客户端
|
||||||
|
let newYan = StringHelper.getRandomString(0, 10);
|
||||||
|
let newToken = jwt.sign({
|
||||||
|
u_id: data.u_id,
|
||||||
|
isRememberMe:false
|
||||||
|
}, newYan, {
|
||||||
|
expiresIn: config.shortTokenExpiration
|
||||||
|
});
|
||||||
|
// await redisHelper.deleteKey(token);
|
||||||
|
await redisHelper.setString(newToken, newYan,config.shortTokenExpiration);
|
||||||
|
ctx.response.set('new-token', newToken);
|
||||||
|
ctx.response.set('Access-Control-Expose-Headers','new-token');
|
||||||
|
}
|
||||||
|
//获取用户信息
|
||||||
|
let userInfoKey = data.u_id + '_userInfo';
|
||||||
|
let userInfo = await redisHelper.getString(userInfoKey);
|
||||||
|
if (userInfo == null || Object.keys(userInfo).length != 3) {
|
||||||
|
userInfo = await mysqlHelper.first(`select u_id,u_type,j_id from user where u_id=?`, data.u_id);
|
||||||
|
await redisHelper.setString(userInfoKey, JSON.stringify(userInfo), 24 * 60 * 60);
|
||||||
|
}else{
|
||||||
|
userInfo = JSON.parse(userInfo);
|
||||||
|
}
|
||||||
|
ctx.userInfo = userInfo;
|
||||||
|
//更新用户上次访问时间
|
||||||
|
mysqlHelper.execute(`update user set last_login_time=? where u_id=?`,Date.now(),userInfo.u_id);
|
||||||
|
//管理员拥有全部权限
|
||||||
|
if (userInfo.u_type == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
//获取该用户类型权限
|
||||||
|
let authKey = userInfo.j_id + '_authority';
|
||||||
|
let urls = await redisHelper.getObject(authKey);
|
||||||
|
// let urls = null;
|
||||||
|
if (urls == null) {
|
||||||
|
urls = await mysqlHelper.row(`
|
||||||
|
select b.r_id,b.url,b.method from jurisdiction_resource a inner join resource b on a.r_id = b.r_id where a.j_id=?
|
||||||
|
`, userInfo.j_id);
|
||||||
|
let temp = {};
|
||||||
|
urls.forEach(item => {
|
||||||
|
temp[item.url + item.method] = true;
|
||||||
|
})
|
||||||
|
await redisHelper.setObject(authKey, temp);
|
||||||
|
urls = temp;
|
||||||
|
}
|
||||||
|
//判断是否拥有权限
|
||||||
|
if (urls.hasOwnProperty(ctx._matchedRoute.replace(config.url_prefix, '') + ctx.method)) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
根据用户id获取用户身份id,根据用户身份id从redis中获取拥有的权限,如为null,从mysql数据库中拉取,并存入redis中,然后判断是否拥有要访问的url权限。
|
||||||
|
|
||||||
|
#### c、数据持久化
|
||||||
|
|
||||||
|
  本系统中使用mysql存储数据,redis做缓存,由于当时操作库不支持promise,故对它两做了个promise封装,方便代码中调用,参见:[MysqlHelper](https://github.com/FleyX/teach_system/tree/master/teachSystem/util/MysqlHelper.js),[RedisHelper.js](https://github.com/FleyX/teach_system/tree/master/teachSystem/util/RedisHelper.js)。
|
||||||
|
|
||||||
|
### 2、前端
|
||||||
|
|
||||||
|
  前端使用vue-cli构建vue项目,主要用到了vue-router,element-ui,axios这三个组件。
|
||||||
|
|
||||||
|
#### a、路由组织
|
||||||
|
|
||||||
|
  单页应用需要前端自己组织路由。本系统将路由分成了三个部分:公共,管理端,学生端。index.js如下:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default new Router({
|
||||||
|
mode: 'history',
|
||||||
|
base: '/app/',
|
||||||
|
routes: [{
|
||||||
|
path: '',
|
||||||
|
name: 'indexPage',
|
||||||
|
component: IndexPage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'about',
|
||||||
|
component: About
|
||||||
|
},
|
||||||
|
Admin,
|
||||||
|
Client,
|
||||||
|
Public,
|
||||||
|
{
|
||||||
|
path: '*',
|
||||||
|
name: "NotFound",
|
||||||
|
component: NotFound
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
其中的Admin,Client,Public分别为各部分的路由,以子路由的形式一级级组织。如下所示:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default {
|
||||||
|
path: "/client",
|
||||||
|
component: Client,
|
||||||
|
beforeEnter: (to, from, next) => {
|
||||||
|
if (getClientUserInfo() == null) {
|
||||||
|
next({
|
||||||
|
path: '/public/client_login',
|
||||||
|
replace: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: [{
|
||||||
|
//学生端主页
|
||||||
|
path: '',
|
||||||
|
name: "ClientMain",
|
||||||
|
component: ClientHome
|
||||||
|
}, {
|
||||||
|
//学生个人资料页面
|
||||||
|
path: 'person/student_info',
|
||||||
|
name: "StudentInfo",
|
||||||
|
component: StudentInfo
|
||||||
|
}, {
|
||||||
|
//公告页面
|
||||||
|
path: 'course/:c_id/announcement',
|
||||||
|
name: 'Main',
|
||||||
|
component: Announcement
|
||||||
|
}, {
|
||||||
|
//课程基本信息
|
||||||
|
path: 'course/:c_id/base',
|
||||||
|
component: ClientMain,
|
||||||
|
children: [{
|
||||||
|
path: 'course_intro',
|
||||||
|
name: "ClientCourseIntro",
|
||||||
|
component: CourseIntro
|
||||||
|
}, {
|
||||||
|
path: 'exam_type',
|
||||||
|
name: "ClientExamType",
|
||||||
|
component: ExamType
|
||||||
|
}
|
||||||
|
......
|
||||||
|
```
|
||||||
|
|
||||||
|
其中的beforEnter为钩子函数,每次进入路由时执行该函数,用于判断用户是否登录。这里涉及到了一个前端鉴权的概念,由于前后端分离了,前端也必须做鉴权以免用户进入到了无权限的页面,这里我只是简单的做了登录判断,更详细的url鉴权也可实现,只需在对应的钩子函数中进行鉴权操作,更多关于钩子函数信息[点击这里](https://router.vuejs.org/zh/guide/advanced/navigation-guards.html)。
|
||||||
|
|
||||||
|
#### b、请求封装
|
||||||
|
|
||||||
|
  前端还有一个比较重要的部分是ajax请求的处理,请求处理还保护错误处理,有些错误只需要统一处理,而有些又需要独立的处理,这样一来就需要根据业务需求进行一下请求封装了,对结果进行处理后再返回给调用者。我的实现思路是发起请求,收到响应后先对错误进行一个同意弹窗提示,然后再将错误继续向后传递,调用者可选择性的捕获错误进行针对性处理,主要代码如下:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
request = (url, method, params, form, isFormData, type) => {
|
||||||
|
let token;
|
||||||
|
if (type == 'admin')
|
||||||
|
token = getToken();
|
||||||
|
else
|
||||||
|
token = getClientToken();
|
||||||
|
let headers = {
|
||||||
|
'Authorization': token
|
||||||
|
};
|
||||||
|
if (isFormData) {
|
||||||
|
headers['Content-Type'] = "multipart/form-data";
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios({
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
params,
|
||||||
|
data: form,
|
||||||
|
headers,
|
||||||
|
// timeout:2000
|
||||||
|
}).then(res => {
|
||||||
|
resolve(res.data);
|
||||||
|
//检查是否有更新token
|
||||||
|
// console.log(res);
|
||||||
|
if (res.headers['new-token'] != undefined) {
|
||||||
|
console.log('set new token');
|
||||||
|
if (vm.$route.path.startsWith('/admin')){
|
||||||
|
localStorage.setItem("token",res.headers['new-token']);
|
||||||
|
window.token = undefined;
|
||||||
|
}else if(vm.$route.path.startsWith('/client')){
|
||||||
|
localStorage.setItem("clientToken",res.headers['new-token']);
|
||||||
|
window.clientToken = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
reject(err);
|
||||||
|
if (err.code == 'ECONNABORTED') {
|
||||||
|
alertNotify("错误", "请求超时", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err.message == 'Network Error') {
|
||||||
|
alertNotify("错误", "无法连接服务器", 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (err.response != undefined) {
|
||||||
|
switch (err.response.status) {
|
||||||
|
case 401:
|
||||||
|
if (window.isGoToLogin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//使用该变量表示是否已经弹窗提示了,避免大量未登录弹窗堆积。
|
||||||
|
window.isGoToLogin = true;
|
||||||
|
vm.$alert(err.response.data, "警告", {
|
||||||
|
type: "warning",
|
||||||
|
showClose: false
|
||||||
|
}).then(res => {
|
||||||
|
window.isGoToLogin = false;
|
||||||
|
if (vm.$route.path.startsWith('/admin/')) {
|
||||||
|
clearInfo();
|
||||||
|
vm.$router.replace("/public/admin_login");
|
||||||
|
} else {
|
||||||
|
clearClientInfo();
|
||||||
|
vm.$router.replace("/public/client_login");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 403:
|
||||||
|
alertNotify("Error:403", '拒绝执行:' + err.response.data, "error");
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
alertNotify("Error:404", "找不到资源:" + url.substr(0, url.indexOf('?')), 'error');
|
||||||
|
break;
|
||||||
|
case 400:
|
||||||
|
alertNotify("Error:400", "请求参数错误:" + err.response.data, 'error');
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
alertNotify("Error:500", "服务器内部错误:" + err.response.data, 'error');
|
||||||
|
default:
|
||||||
|
console.log('存在错误未处理:' + err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
  到这里就算是简单介绍完了,,想要更加深入了解的可以去github查看源代码,地址如下:[https://github.com/FleyX/teach_system,](https://github.com/FleyX/teach_system)记得star哦!
|
57
网络/nat模式虚拟机主机相互ping通.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
## 1、wmware设置
|
||||||
|
|
||||||
|
  这篇记录下nat网络模式下虚拟机与主机的相互ping通。首先使用wmware建立一个ubuntu虚拟机,网络模式选择nat模式。然后点击虚拟网络编辑:
|
||||||
|
|
||||||
|
![1536153781962](./picFolder/pic1.png)
|
||||||
|
|
||||||
|
接下来点击nat设置:
|
||||||
|
|
||||||
|
![1536153954193](./picFolder/pic2.png)
|
||||||
|
|
||||||
|
看到如下:
|
||||||
|
|
||||||
|
![pic3](./picFolder/pic3.png)
|
||||||
|
|
||||||
|
上面红框是关键,记录这个值,下面虚拟机设置静态ip要用到。
|
||||||
|
|
||||||
|
## 2、window网络设置
|
||||||
|
|
||||||
|
  打开网络适配器页面,选择VMnet,右键->属性->Internet协议版本 4(TCP/IPV4)->属性,设置ip地址为上面上面网关地址最后一个数改成1,比如192.168.128.2就要设置为192.168.128.1,同时设置子网掩码为255.255.255.0,默认网关不要填。我的如下:
|
||||||
|
|
||||||
|
![pic4](./picFolder/pic4.png)
|
||||||
|
|
||||||
|
**如果想让虚拟机能够访问主机需要关闭主机的防火墙**
|
||||||
|
|
||||||
|
## 3、ubuntu设置
|
||||||
|
|
||||||
|
  编辑/etc/network/interfaces
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vim /etc/network/interfaces
|
||||||
|
# This file describes the network interfaces available on your system
|
||||||
|
# and how to activate them. For more information, see interfaces(5).
|
||||||
|
|
||||||
|
source /etc/network/interfaces.d/*
|
||||||
|
|
||||||
|
# The loopback network interface
|
||||||
|
auto lo
|
||||||
|
iface lo inet loopback
|
||||||
|
|
||||||
|
# The primary network interface
|
||||||
|
auto ens33
|
||||||
|
# dhcp 改成static,然后设置下面的address,netmask,gateway
|
||||||
|
iface ens33 inet static
|
||||||
|
address 192.168.128.129
|
||||||
|
netmask 255.255.255.0
|
||||||
|
gateway 192.168.128.2
|
||||||
|
# 设置dns
|
||||||
|
dns-nameservers 192.168.128.2
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
然后执行`/etc/init.d/networking restart`,或者重启虚拟机以启用网络设置。
|
||||||
|
|
||||||
|
## 3、验证
|
||||||
|
|
||||||
|
  现在虚拟机中`ping 192.168.128.1`可以ping通,主机中`ping 192.168.128.129`也可ping通。
|
BIN
网络/picFolder/pic1.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
网络/picFolder/pic2.png
Normal file
After Width: | Height: | Size: 83 KiB |
BIN
网络/picFolder/pic3.png
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
网络/picFolder/pic4.png
Normal file
After Width: | Height: | Size: 67 KiB |