maven中jar包传输原则(了解)

问题:jar包文件 如何保证在远程传输的过程中不被别人篡改???
算法介绍: SHA1算法

SHA-1(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数,美国国家安全局设计,并由美国国家标准技术研究所(NIST)发布为联邦数据处理标准(FIPS)。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。

配置文件写法

1.YML文件写法 层级关系 空格 连接符:号 注意缩进.
2.pro文件 本身都是字符串不需要添加""号 注意字符集编码iso-8859-1 转化为utf-8 重新编辑.

为属性赋值

目的:动态配置属性信息.
两种方式:
取值前提: 先赋值,再取值
1.@value spel表达式
2@ConfigurationProperties(prefix = “msg”) 一般配合@data注解.
指定配置文件进行加载. @PropertySource(“classpath:/xxxxxxxx.properties”)

@PropertySource
(value = "classpath:/properties/msg.properties",encoding="UTF-8")/*** 1.之前通过yml配置文件赋值* 2.现在通过pro文件方式赋值*/@Value("${msg.username2}")

环境切换

通过—实现配置文件分割
spring.profiles: prod 定义环境名称
spring.profiles.active: prod 指定默认的环境 注意缩进!!!

lombok环境插件配置

面试题:
lombok:可以自动的生成get/set等方法.但是使用lombok时需要安装插件!!!
问题:如果要在Linux系统中运行java项目.是否需要安装lombok插件??? 不需要!!!
答: lombok在程序编译期有效,当程序由.java文件编译为.class文件时,
lombok插件开始工作.动态生成了get/set等方法.
而Linux中运行的项目直接.jar包文件里边包含了.class类型文件.
所以不需要lombok插件再次编译即可运行.

关于配置文件说明:

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=trueusername: rootpassword: root

serverTimezone=GMT%2B8 东8区
useUnicode=true&characterEncoding=utf8 使用指定的字符集编码
&autoReconnect=true 数据库连接断开之后,是否自动重连
&allowMultiQueries=true 是否允许批量操作sql语句. 一般查询居多 允许

开启驼峰映射

mybatis:configuration:map-underscore-to-camel-case: true

关于驼峰映射说明:
字段信息:
user_id , user_age , user_name
对象属性:
userId , userAge ,userName
如果需要实现属性与字段的映射,则必须开启驼峰规则.
字段信息~~~~ user_id ~~~~ 去掉_线 ~~~~之后首字母大写 ~~~~ userId
改名字可以与对象的属性映射.
注意事项: 如果一旦开启驼峰规则映射,则必须按照要求执行.
字段:user_id 不能映射 属性:user_id

MybatisPlus 介绍

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,
在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

MybatisPlus实现原理

核心:

  1. 表与对象建立关联关系
    对象名称 ---------> 表名
    对象的属性 -------> 数据表中的字段.
  2. 采用第三方接口 规范所有的单表操作规则.(封装思想)
  3. 将CURD接口方法,必须按照sql的规范转化为指定的sql语句.

理论依据:

userMapper.insert(user); //程序员只写到这里.
sql: insert into 表名(字段名…) values (属性值…);
按照用户的调用发方法,动态拼接sql.之后交给Mybatis去执行.
拼接sql:
insert into user表(字段A,字段B,字段C…) values (属性值A,属性B,属性C…);

@Data
@Accessors(chain = true)
@TableName   //与表进行关联  如果名称一致,则可以省略不写.
public class User {//属性一般都与表字段对应@TableId(type = IdType.AUTO)   //主键自增标识.private Integer id;//@TableField(value = "name")  //如果属性名称与字段名称一致(包含驼峰规则) 可以省略不写private String name;private Integer age;private String sex;
}

!!!继承BaseMapper接口!!!

public interface UserMapper extends BaseMapper<User> {  }

queryWrapper:条件构造器 拼接where条件
逻辑运算符 = eq, > gt , < lt, >= ge, <= le
案例:查询用户age<18岁或者age>100岁

QueryWrapper<User> queryWrapper = new QueryWrapper<User>();queryWrapper.lt("age", 18).or().gt("age", 100);List<User> userList = userMapper.selectList(queryWrapper);
//queryWrapper.between("字段", "值1", "值2");//queryWrapper.like("name", "精");//包含精字 %精%
queryWrapper.likeRight("name", "精");//以精开头 精% 左右是指%的位置//queryWrapper.groupBy(column) //分组查询!!!

SpringBoot 整合JSP

1.添加jsp页面(将页面资料导入webapp中)
2.添加jsp相关jar包文件
3.配置视图解析器

spring:mvc:         #引入mvn配置view:prefix: /WEB-INF/     # /默认代表根目录 src/main/webappsuffix: .jsp

ajax原理

AJAX特点: 局部刷新,异步访问.
知识点: ajax为什么可以异步 源于ajax调用方式.(ajax引擎)

jQuery中发起ajax请求

1. $.get()  负责查询             返回数据任意  人工解析
2. $.post() 表单数据提交等           返回值类型任意 人工解析
3. $.getJSON() 查询获取json数据
4. $.getScript()  获取js代码片段
5. $.ajax({.....})  万能用法

常规ajax写法

$.ajax({type: "post",         //methodurl: "/findAjax",        //网址//data: "name=John&location=Boston",  //传递数据dataType: "json",success: function(result){//准备一个函数,实现表格数据的添加.addTable("tab2",result);//提示成功信息},error :function(data){alert("提示信息: 当前网络正忙,请稍后再试");},async : true,    //默认条件下都是异步.    false表示请求同步.cache : false //缓存页面信息          false表示不缓存});

分布式思想

!!!为什么分布式!!!

说明: 由于程序将所有的功能模块放到同一台tomcat服务器中,那么如果服务器内部出现了问题,
则直接导致整个服务器不能正常执行. 系统架构的耦合性高

分布式系统

核心理念: 按照指定的规则,将系统进行拆分.各自独立运行,减少架构的耦合性.
按照模块拆分
优点: 如果其中一个服务器出现了问题,则不会影响整个项目的正常运行.
按照层级拆分
说明:有时代码的业务逻辑特别的复杂.如何减少开发的耦合性.可以按照层级的方式进行拆分.
关于分布式总结
优点: 可以将大型项目按照指定规则拆分.降低了系统架构的耦合性.方便开发和"维护".
弊端: 拆分完成之后由于项目个数重点 运维不易. 可以接受!!!
分布式另外表现形式: 准备多台服务器一起为用户提供服务.

集群

什么是集群
说明:采用多台服务器部署项目,共同为用户提供服务的.同时可以满足高可用(HA)的需求时,称之为集群.
什么是高可用: 当服务器发生宕机的现象时,可以自动的实现故障迁移.保证服务运算能力.

如何管理JAR包

问题说明: 由于分布式的项目构建,导致服务器中jar包维护的份数变多.如果长时间按照该方式运行,则必然导致jar包管理混乱.
解决方案: 准备一个公共的项目,由该项目管理所需要的jar包文件. 其它项目继承该项目即可.

如何管理工具API

项目划分

1.父级工程 jt 项目打包类型: pom
2.工具API jt-common 打包类型:jar
3.业务系统 jt-manage 打包类型:war

EasyUI树形结构

   <body class="easyui-layout"><div data-options="region:'west',title:'菜单',split:true"style="width: 10%;"><ul class="easyui-tree"><li><!--一级标题元素  --><span>商品管理</span><ul><li>商品查询</li><li>商品新增</li></ul></li><li><span>人员管理</span><ul><li>保安</li><li>员工</li></ul></li></ul></div><div data-options="region:'center',title:'首页'"></div></body>   

EasyUI选项卡技术

   <link rel="stylesheet" type="text/css" href="/js/jquery-easyui-1.4.1/themes/default/easyui.css" />
<link rel="stylesheet" type="text/css" href="/js/jquery-easyui-1.4.1/themes/icon.css" />
<link rel="stylesheet" type="text/css" href="/css/jt.css" />
<script type="text/javascript" src="/js/jquery-easyui-1.4.1/jquery.min.js"></script>
<script type="text/javascript" src="/js/jquery-easyui-1.4.1/jquery.easyui.min.js"></script>
<script type="text/javascript" src="/js/jquery-easyui-1.4.1/locale/easyui-lang-zh_CN.js"></script>
</head>
<body class="easyui-layout"><div data-options="region:'west',title:'菜单',split:true" style="width:10%;"><ul class="easyui-tree"><li><span>商品管理</span><ul><li><a onclick="addTab('商品新增','/item-add')">商品新增</a></li><li><a onclick="addTab('商品查询','/item-list')">商品查询</a></li><li><a onclick="addTab('商品更新','/item-update')">商品更新</a></li></ul></li><li><span>网站内容管理</span><ul><li>内容新增</li><li>内容修改</li></ul></li></ul></div><div id="tt" class="easyui-tabs" data-options="region:'center',title:'首页'"></div></body>
<script type="text/javascript">//addTab('商品新增','/item-add')
function addTab(title, url){  //框架自己提供的函数方法.tabsif ($('#tt').tabs('exists', title)){  $('#tt').tabs('select', title);  //选中} else {  /*<iframe 画中画效果/>  */var url2 = "https://map.baidu.com/@12954908,4837179,13z";var content = '<iframe scrolling="auto" frameborder="0"  src="'+url2+'" style="width:100%;height:100%;"></iframe>';  $('#tt').tabs('add',{  title:title,  content:content,  //页面加载的内容closable:true  });  }
} </script>

什么是JSON?有什么格式?

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。
Object格式

{"id":"1","name":"tomcat猫"}

Array格式

["100","A","B"]

"复杂"格式 (嵌套格式)

["1","2","3",true,false,["吃","喝",{"id":"101","hobbys":["打游戏","敲代码","看美女"]}]]

关于ajax嵌套问题说明 一般将内部ajax设置为同步

分页实现:

VO对象:

public class EasyUITable {private Integer total;private List<Item> rows;
}
//标识配置类 .xml文件(MP分页要配置一个配置类)
//PaginationInterceptor 分页拦截器
@Configuration
public class MybatisPlusConfig {//一般和@Bean注解联用,标识将返回的对象实例化,交给spring容器管理@Beanpublic PaginationInterceptor paginationInterceptor() {return new PaginationInterceptor();}
}

正常分页和MP分页(两种都可!)

@Overridepublic EasyUITable findItemByPage(int page, int rows) {//1.获取记录总数//int total = itemMapper.selectCount(null);//2.查询分页后的结果//int start=(page-1)*rows;//List<Item> itemList = itemMapper.selectItemByPage(start,rows);//return new EasyUITable(total, itemList);/*** 利用MP的方式查询数据记录* current:查询第几页* size:每页记录数* *///1.定义分页对象Page<Item> mpPage=new Page<>(page, rows);//2.定义条件构造器QueryWrapper<Item> queryWrapper=new QueryWrapper<>();//3.要求:按照更新时间降序排序queryWrapper.orderByDesc("updated");//4.执行分页查询 封装page对象数据mpPage = itemMapper.selectPage(mpPage, queryWrapper);int total=(int)mpPage.getTotal();List<Item> records = mpPage.getRecords();return new EasyUITable(total,records);}

全局异常处理机制

如果代码中频繁出现try-catch,则可能影响代码结构.
不便于阅读. 能否利用全局的异常的捕获机制,简化try-catch个数!!!
捕获位置: 常规捕获的位置是Controller层,因为Controller层是业务调用的最后的控制层.

 //标识该类是全局异常处理机制的配置类
@RestControllerAdvice //advice通知   返回的数据都是json串
@Slf4j    //添加日志
public class SystemExceptionAOP {/** 添加通用异常返回的方法.* 底层原理:AOP的异常通知.* */@ExceptionHandler({RuntimeException.class}) //拦截运行时异常public Object systemResultException(Exception exception) {//exception.printStackTrace(); //如果有问题,则直接在控制台打印log.error("{~~~~~~"+exception.getMessage()+"}", exception); //输出日志return SysResult.fail();     //返回统一的失败数据}
}

富文本编辑器:

KindEditor 是一套开源的在线HTML编辑器,主要用于让用户在网站上获得所见即所得编辑效果,
开发人员可以用 KindEditor 把传统的多行文本输入框(textarea)替换为可视化的富文本输入框。
KindEditor 使用 JavaScript 编写,可以无缝地与 Java、.NET、PHP、ASP 等程序集成,
比较适合在 CMS、商城、论坛、博客、Wiki、电子邮件等互联网应用上使用。

富文本使用:

1:引入js
2:

3:js代码:

$(function(){KindEditor.ready(function(){KindEditor.create("#editor")})})

文件上传入门案例

编辑页面html:

<body><h1>实现文件长传</h1><!--enctype="开启多媒体标签" 字节信息 --><form action="http://localhost:8091/file" method="post" enctype="multipart/form-data"><input name="fileImage" type="file" /><input type="submit" value="提交"/></form>
</body>

编辑FileController:

RestController       //返回json数据
public class FileController {/*** url地址: http://localhost:8091/file* 参数:    File=fileImage* 返回值:   字符串* * 参数说明:MultipartFile 接口, 主要负责实现文件接收* 常识: *         1.必须指定文件上传的路径信息   D:\JT-SOFT\images\文件名称.jpg*       2.将字节信息利用outPutStream进行输出操作* * 说明:文件上传默认大小1M=1024*1024   * 具体参见CommonsFileUploadSupport类* * @throws IOException * @throws IllegalStateException */@RequestMapping("/file")public String file(MultipartFile fileImage) throws IllegalStateException, IOException {//1.定义文件目录信息String dirPath = "D:/JT-SOFT/images";File  fileDir = new File(dirPath);//2.校验图片目录是否存在.if(!fileDir.exists()) { //如果文件目录不存在,应该创建目录fileDir.mkdirs();   //创建多级目录}//3.获取文件信息. 一般都在上传提交的参数中     a.jpgString fileName = fileImage.getOriginalFilename();//4.实现文件上传. 指定文件真实路径File file = new File(dirPath+"/"+fileName);//5.利用api实现文件输出.fileImage.transferTo(file);return "恭喜你,文件上传成功!!!";}
}

封装文件上传VO–ImageVO

public class ImageVO {private Integer error;    //错误信息   0正确    1错误private String  url;    //url地址private Integer width;  //宽度private Integer height;    //高度//1.封装失败的方法public static ImageVO fail() {return new ImageVO(1, null, null, null);}//2.封装成功的方法public static ImageVO success(String url) {return new ImageVO(0, url, null, null);}public static ImageVO success(String url,Integer width,Integer height) {return new ImageVO(0, url, width, height);}}

编辑FileController:

/*** 业务分析:实现文件上传.* 1.url地址: http://localhost:8091/pic/upload* 2.参数:   uploadFile  文件上传对象* 3.返回值:  ImageVO对象*/@RequestMapping("/pic/upload")public ImageVO uploadFile(MultipartFile uploadFile) {return fileService.uploadFile(uploadFile);}

编辑FileService:

@Service
public class FileServiceImpl implements FileService {private String localDir = "D:/JT-SOFT/images";//bug:漏洞 一般没错  二般情况才会出错  传递特殊参数时报错//error: 错误/*** 1.如何校验上传的信息是图片??????*       通过后缀进行校验:  .jpg,.png,.gif........* 2.如何保证检索速度更快 ????*       分目录存储       1).hash   2).时间* 3.如何防止文件重名???*         重定义文件名称    uuid.jpg*/@Overridepublic ImageVO uploadFile(MultipartFile uploadFile) {//1.校验上传的信息 是否为图片//1.1初始化图片类型集合Set<String>  typeSet = new HashSet<>();typeSet.add(".jpg");typeSet.add(".png");typeSet.add(".gif");//1.2动态获取用户上传的图片类型          abc.jpg|ABC.JPGString fileName = uploadFile.getOriginalFilename();fileName = fileName.toLowerCase();  //将所有的字符转化为小写.int index = fileName.lastIndexOf(".");//.jpgString fileType = fileName.substring(index);//1.2校验图片类型是否有效if(!typeSet.contains(fileType)) {//表示类型不属于图片信息  则终止程序return ImageVO.fail();}//2.准备文件上传的目录结构.   文件上传根目录+动态变化的目录String dateDir = new SimpleDateFormat("/yyyy/MM/dd/").format(new Date());//D:/JT-SOFT/images/2020/7/10/String dirPath = localDir + dateDir;File dirFile = new File(dirPath);if(!dirFile.exists()) {dirFile.mkdirs();  //如果目录不存在则新建目录.}//3.重新指定文件名称String uuid = UUID.randomUUID().toString();String realFileName = uuid + fileType;//4.执行文件上传代码    目录+文件名称File imageFile = new File(dirPath+realFileName);try {uploadFile.transferTo(imageFile);String url = "https://img14.360buyimg.com/n0/jfs/t1/71310/32/5640/402976/5d3a654eE0489baf9/fd8eafe74ef8779c.jpg";return ImageVO.success(url);} catch (IllegalStateException | IOException e) {e.printStackTrace();return ImageVO.fail();}}
}

反向代理机制

什么是反向代理

业务分析
说明:图片如果需要展现,则通过网络虚拟地址进行访问
虚拟路径: http://image.jt.com/2020/07/11/39ff8758-57bb-4452-bf29-db6061fff24a.jpg
磁盘地址: D:\JT-SOFT\images/2020/07/11/39ff8758-57bb-4452-bf29-db6061fff24a.jpg
为了让所有的用户都能访问图片信息,则准备虚拟地址,并且实现虚拟地址与本地磁盘地址之间的映射关系.该功能采用反向代理技术实现.

反向代理说明
反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,
即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。
反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。
总结:
1).反向代理服务器位于目标服务器与用户之间.
2).对于用户而言,反向代理服务器就是目标服务器.
3).用户访问时根本不清楚真实的服务器资源是谁,保护了真实服务器资源信息.
4).反向代理服务器一般是服务器端代理,保护真实服务器信息.

正向代理(知识补充)

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,
客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。
客户端才能使用正向代理。
知识点:
1).代理服务器位于用户与服务器之间
2).用户发起请求时,清楚的知道自己访问的真实服务器是谁.
3).代理服务器将用户的请求转交给服务器获取数据.
4).正向代理是客户端代理,保护了用户的信息.

一般用户网络通信的使用.(路由器)

!!!Nginx!!!

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器
其特点是!!!占有内存少,并发能力强!!!
内存: 不到2M
并发能力强: 3-5万次/秒 理想环境下 /tomcat服务器 150-220/秒

Nginx命令

关于Nginx进程项说明
Nginx每次启动时会生成2个进程项
1).主进程: 主要提供nginx反向代理服务的.
2).守护进程 防止主进程意外关闭的.
如果需要关闭nginx,则应该先关闭守护进程,再关闭主进程.
基本命令
前提条件: 要求在nginx的根目录中执行.
规范: 启动nginx之后,执行nginx的重启指令,检查是否有异常.
1). 启动nginx start nginx 即使启动不成功,也不会报错!!!
2).重启nginx nginx -s reload 如果配置文件编辑异常,则会显示报错信息
3).停止nginx nginx -s stop

商品图片回显实现

利用nginx服务器实现反向代理机制, 当用户访问http://image.jt.com时 要求跳转到路径 D:\JT-SOFT\images\

编辑Nginx的配置文件:

server{listen     80; server_name     image.jt.com;location / {#映射到目录中root    D:\JT-SOFT\images;}}

编辑HOSTS文件

利用switchHosts编辑文件

实现域名代理

业务需求
要求: 用户通过http://manage.jt.com:80 访问localhost:8091服务器.
配置域名代理

  server {listen      80;server_name  manage.jt.com;location / {#代理的是服务器地址proxy_pass   http://localhost:8091;}}

!!!Nginx负载均衡策略!!!

什么是负载均衡
说明:在分布式条件下,为了提高用户请求的响应能力,准备多台服务器.一起抗击高并发.
需要用户通过同一个网址访问不同服务器的技术称之为负载均衡机制

负载均衡策略----轮询 upstream url{}
负载均衡策略----权重 weight
负载均衡策略----IPHASH策略
说明: 如果需要用户与后端服务器进行绑定时,可以使用IPhash策略.
案例A: 有时用户可能做登录操作,可能将用户信息保存到session对象 中,
如果这时采用轮询/权重的策略,可能访问其他的业务服务器.导致用户频繁的登录.

关于Linux防火墙说明:

防火墙的工作原理
说明:一般防火墙只拦截远程请求本服务器的请求.
配置以后不开启防火墙
systemctl disable firewalld.service
配置以后开启防火墙
systemctl enable firewalld.service
防火墙开关配置
1).检查防火墙工作状态
firewall-cmd --state
2).关闭防火墙
改操作只能控制现在.当Linux系统重启时,改操作失效.
systemctl stop firewalld.service
systemctl start firewalld.service
指定端口号开放
firewall-cmd --zone=public --add-port=3306/tcp --permanent
命令含义:
–zone #作用域
–add-port=80/tcp #添加端口,格式为:端口/通讯协议
–permanent #永久生效,没有此参数重启后失效

linux中关于tomcat的命令

批量启动tomcat服务器
java -jar 8081.war & java -jar 8082.war & java -jar 8083.war &
关闭tomcat服务器
命令:
ps -ef | grep java
kill -9 4379
*
批量启动tomcat服务器
项目后台运行设定
说明: 通过java -jar xxxx.war 方式表示前台运行. 改方式不允许控制台关闭,如果控制台关闭之后,所有的服务都将停止.
所以需要开启后台运行的方式.
命令:
nohup java -jar 8081.war -> 8081.log & nohup java -jar 8082.war -> 8082.log & nohup java -jar 8083.war -> 8083.log &

安装Linux nginx服务器

安装准备
1).解压nginx tar -zxvf nginx-1.19.1.tar.gz
2).删除多余文件 rm -f nginx-1.19.1.tar.gz
3).修改文件名称 mv nginx-1.19.1 nginx
nginx的环境配置有2个环境.
环境1: /usr/local/src/nginx 该路径是nginx的源文件路径 主要负责编译/安装等工作 (安装)
环境2: /usr/local/nginx 该路径是nginx的工作路径 主要实现反向代理配置工作 (工作)

启动nginx服务器

跳转到nginx工作目录中 : cd /usr/local/nginx/
跳转到sbin 目录中 执行启动命令
命令:
1.启动nginx ./nginx
2.重启nginx ./nginx -s reload
3.关闭nginx ./nginx -s stop

配置winscp

修改Linux的nginx配置信息

#配置图片服务器server {listen 80;server_name  image.jt.com;location / {#配置反向代理的路径root  /usr/local/src/images;}}#配置域名代理server {listen 80;server_name  manage.jt.com;location / {#代理tomcat服务器proxy_pass  http://tomcats;}}#配置tomcat集群  默认是轮询策略upstream tomcats {server localhost:8081;server localhost:8082;server localhost:8083;}

修改windows中的hosts文件

# 京淘环境配置
#127.0.0.1     image.jt.com
#127.0.0.1     manage.jt.com#测试inux项目发布
192.168.126.129    image.jt.com
192.168.126.129    manage.jt.com
127.0.0.1     www.jt.com
127.0.0.1     sso.jt.com

实现数据库高可用

项目部署之后弊端
问题描述1: 当数据库宕机之后,可能导致数据丢失.必须通过某些策略,保证数据的有效性.
问题描述2: 如果后端数据库宕机,则通过某些技术手段可以实现高可用(可以实现自动的故障迁移)

!!!数据库备份!!!

冷备份说明: 定期将数据库内容进行转储. 弊端:可能丢失数据. 公司中也会采用冷备份的方式以防万一.

数据库热备份原理:
特点:可以保证数据的实时备份.
工作原理说明:
1.数据库主库将更新的数据信息写入到二进制日志文件中.
2.数据库从库通过IO线程去主库中获取二进制文件修改内容. 之后写入到中继日志中
3.数据库从库中的Sql线程读取中继日志中的信息,实现数据的同步.
并且为了降低组件之间的耦合性,采用异步的方式处理.

准备第二台Linux操作系统
规定: 主机IP地址 192.168.126.129 从机 192.168.126.130

在slave中安装数据库
利用sqlYog工具链接从库
实现数据库导入导出(说明:在实现数据库主从之前,最好让主库和从库的数据一致)

数据库主从配置
开启数据库二进制文件

!!!主从数据库挂载!!!
说明:如果需要实现数据库的主从同步,应该由!!!从库!!!向!!!主库!!!进行挂载

change MASTER to MASTER_HOST="192.168.126.129",
MASTER_PORT=3306,
MASTER_USER="root",
MASTER_PASSWORD="root",
MASTER_LOG_FILE="mysql-bin.000001",
MASTER_LOG_POS=245;/*开启主从服务*/
start  slave;/*检查主从同步状态*/
show SLAVE status;

!!!数据库读写分离-负载均衡机制!!!

读写分离说明
当数据库执行写操作时,应该操作主库. 如果用户进行读操作时应该读从库.
实现该机制需要准备一个代理数据库服务器

!!!Mycat介绍!!!

数据库分库分表的中间件
注意事项: 数据库代理的端口号 8066端口
schema.xml
配置说明:该配置表示了读写分离/负载均衡的设置.

<writeHost host="hostM1" url="192.168.126.129:3306" user="root" password="root"><!--读数据库1--><readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" /><!--读数据库2--><readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" /></writeHost>

启动mycat服务器: ./mycat start

修改数据源配置

spring:datasource:#引入druid数据源#type: com.alibaba.druid.pool.DruidDataSource#driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://192.168.126.129:8066/jtdb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=trueusername: rootpassword: root

!!!数据库的双机热备!!!

双主模式配置
1)检查主库的状态:SHOW MASTER STATUS;
2)实现主从挂载

/*我之前是主库   现在是从库*/
CHANGE MASTER TO
MASTER_HOST="192.168.126.130",
MASTER_PORT=3306,
MASTER_user="root",
MASTER_PASSWORD="root",
MASTER_LOG_FILE="mysql-bin.000001",
MASTER_LOG_POS=482;/* 启动主从服务*/
start  slave    show SLAVE status;

配置数据库高可用

什么是数据库高可用
说明: 当其中有一台数据库宕机之后,用户依然可以正确的访问数据库不受任何影响,(实现了故障迁移).
主要数据库能够正常的工作,则重新启动数据库之后则可以实现自动的数据的同步.

Redis缓存

为什么需要使用缓存
说明:使用缓存可以有效的降低用户访问物理设备的频次,有效的减少并发的压力.保护后端真实的服务器.

Redis介绍

Redis是一个开源的,内存中的!!!数据结构存储系统!!!,
它可以用作!!!数据库!!!丶!!!缓存!!!和消息中间件
它支持多种类型的数据结构:(5种)
字符串(String)
散列(hash):存储一些结构化的数据,比如用户的昵称、年龄、性别、积分等,
存储一个用户信息对象数据
列表(list)
集合(set)
有序集合(sorted set)

关于redis服务器命令

1.启动redis redis-server redis.conf
2.进入客户端 redis-cli -p 6379
3.关闭redis redis-cli -p 6379 shutdown
简化操作: 如果操作的redis是默认的端口 则可以省略不写.

Redis入门案例

导入jar包

      <!--spring整合redis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-redis</artifactId></dependency>

编辑测试类

 package com.jt.test;import org.junit.jupiter.api.Test;import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;public class TestRedis {/*** 1.spring整合redis* 报错说明:*   1).如果测试过程中报错 则检查redis配置文件  改3处*  2).检查redis启动方式   redis-server redis.conf*  3).检查Linux的防火墙*     做完的 测试其他命令.*/@Testpublic void testString01() {//1.创建jedis对象Jedis jedis = new Jedis("192.168.126.129", 6379);//2.操作redisjedis.set("a", "redis入门案例");String value = jedis.get("a");System.out.println(value);}@Testpublic void testString02() {//1.创建jedis对象Jedis jedis = new Jedis("192.168.126.129", 6379);//2.判断当前数据是否存在if(jedis.exists("a")) {System.out.println(jedis.get("a"));}else {jedis.set("a", "测试是否存在的方法");}}/*** 1.能否简化是否存在的判断* 2.如果该数据不存在时修改数据,否则不修改* setnx方法:  只有当数据不存在时赋值.*/@Testpublic void testString03() {//1.创建jedis对象Jedis jedis = new Jedis("192.168.126.129", 6379);jedis.flushAll(); //清空所有的redis缓存jedis.setnx("a", "测试setnx方法1");jedis.setnx("a", "测试setnx方法2");System.out.println(jedis.get("a"));}/*** 为数据添加超时时间* @throws InterruptedException * setex方法  保证赋值操作和添加超时时间的操作的原子性* 原子性: 要么同时成功,要么同时失败(类似事务)*/@Testpublic void testString04() throws InterruptedException {//1.创建jedis对象Jedis jedis = new Jedis("192.168.126.129", 6379);jedis.flushAll(); //清空所有的redis缓存jedis.set("a", "aaaa"); //如果程序报错,则超时方法将不会执行,改数据将永不超时//程序报错,意外终止!!!!!!!jedis.expire("a", 20);    //添加超时时间    不是原子性操作Thread.sleep(2000);System.out.println("剩余存活时间:"+jedis.ttl("a"));//2.实现原子性操作jedis.setex("b", 20, "原子性测试");System.out.println(jedis.get("b"));}/**** 1.只有数据不存在时允许修改* 2.要求实现添加超时时间,并且是原子性操作* SetParams 参数说明:*  1.NX   只有key不存在时才能修改*   2.XX   只有key存在时,才能修改*  3.PX   添加的时间单位是毫秒*  4.EX   添加的时间单位是秒*/@Testpublic void testString05(){//1.创建jedis对象Jedis jedis = new Jedis("192.168.126.129", 6379);jedis.flushAll(); //清空所有的redis缓存SetParams params = new SetParams();params.xx().ex(20);jedis.set("aa", "测试A", params);  jedis.set("aa", "测试B", params);   System.out.println(jedis.get("aa"));}/*** 存储一类数据时,可以使用hash.*/@Testpublic void testHASH(){//1.创建jedis对象Jedis jedis = new Jedis("192.168.126.129", 6379);jedis.flushAll(); //清空所有的redis缓存jedis.hset("user", "name", "tomcat");jedis.hset("user", "id", "100");System.out.println(jedis.hgetAll("user"));}@Testpublic void testList(){//1.创建jedis对象Jedis jedis = new Jedis("192.168.126.129", 6379);jedis.flushAll(); //清空所有的redis缓存jedis.lpush("list", "1","2","3","4");System.out.println(jedis.rpop("list"));}//控制事务@Testpublic void testTx(){//1.创建jedis对象Jedis jedis = new Jedis("192.168.126.129", 6379);jedis.flushAll(); //清空所有的redis缓存Transaction transaction = jedis.multi();     //2.开启事务try {transaction.set("aaa", "aaa");transaction.set("bbb", "bbbbb");transaction.set("ccc", "cccccc");transaction.exec();           //事务提交} catch (Exception e) {e.printStackTrace();transaction.discard();     //事务回滚}}
}

json格式转化问题

利用ObjectMapper 工具API实现
封装ObjectMapperUtil
说明:该API主要负责将对象转化为JSON,将JSON转化为对象,同时优化异常处理

 package com.jt.util;import org.springframework.util.StringUtils;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;public class ObjectMapperUtil {//json与对象的转化   优化异常处理private static final ObjectMapper MAPPER = new ObjectMapper();//1.将对象转化为JSONpublic static String toJSON(Object target) {if(target == null) {throw new NullPointerException("taget数据为null");}try {return MAPPER.writeValueAsString(target);} catch (JsonProcessingException e) {e.printStackTrace();throw new RuntimeException(e); //如果转化过程中有问题则直接抛出异常}}//2. 将json串转化为对象   用户传递什么样的类型,就返回什么样的对象!!!// <T>  定义了一个泛型对象  代表任意类型public static <T> T toObject(String json,Class<T> targetClass) {if(StringUtils.isEmpty(json) || targetClass == null) {throw new NullPointerException("参数不能为null");}try {return MAPPER.readValue(json, targetClass);} catch (JsonProcessingException e) {e.printStackTrace();throw new RuntimeException(e);}}}

商品分类缓存实现

业务说明:商品分类信息可以使用redis缓存实现该功能.
步骤:
1.当用户点击商品分类按钮时开始获取服务端数据.
2.先查询redis缓存是否有数据
3.如果redis中没有数据,则查询数据库.之后将查询的结果保存到redis中
4.如果redis中有数据,则直接返回缓存记录.
编辑ItemCatController:

/*** 业务:查询商品分类信息,返回VO对象* url地址: /item/cat/list* 参数:  id:一级分类id值* 返回值: EasyUITree对象     * json格式:*    [{"id":"2","text":"王者荣耀","state":"closed"},{"id":"3","text":"王者荣耀","state":"closed"}]`* sql语句:*         一级商品分类信息 parent_id=0 SELECT * FROM tb_item_cat WHERE parent_id=0*/@RequestMapping("/list")public List<EasyUITree>  findItemCatByParentId(@RequestParam(value = "id",defaultValue = "0") Long parentId){//初始化时应该设定默认值.//1.查询一级商品分类信息//Long parentId = id==null?0L:id;//return itemCatService.findItemCatByParentId(parentId);//通过缓存的方式获取数据.return itemCatService.findItemCatByCache(parentId);}

编辑ItemCatService:

 /*** 通过缓存的方式查询数据库.* 1).定义key* 2).根据key查询redis.*/@SuppressWarnings("unchecked")//告诉编译器忽略指定的警告,不用在编译完成后出现警告信息@Overridepublic List<EasyUITree> findItemCatByCache(Long parentId) {//1.定义keyString key = "ITEM_CAT_LIST::"+parentId;List<EasyUITree> treeList = new ArrayList<EasyUITree>();Long  startTime = System.currentTimeMillis();//2.判断redis中是否有值if(jedis.exists(key)) {//不是第一次查询,则获取缓存数据之后直接返回String json = jedis.get(key);Long endTime = System.currentTimeMillis();treeList = ObjectMapperUtil.toObject(json, treeList.getClass());System.out.println("redis查询缓存的时间为:"+(endTime-startTime)+"毫秒");}else {//redis中没有这个key,表示用户第一次查询.treeList = findItemCatByParentId(parentId);Long endTime = System.currentTimeMillis();//需要将list集合转化为jsonString json = ObjectMapperUtil.toJSON(treeList);//将数据保存到redis中jedis.set(key, json);System.out.println("查询数据库的时间为:"+(endTime-startTime)+"毫秒");}return treeList;}

利用AOP实现redis缓存

1 传统项目弊端

说明:
1).由于将redis的操作写到service层中,必须导致业务的耦合性高
2).如果采用上述的方式完成缓存,则改缓存不通用,并且代码冗余.效率低.

2 AOP的核心理念


公式: AOP = 切入点表达式 + 通知方法

3 切入点表达式

1). bean(bean的ID) 按照指定的bean名称拦截用户的请求,之后执行通知方法. 只能匹配单个bean对象
2).within(包名.类名) 可以按照类通配的方式去拦截用户的请求. 控制粒度较粗.
3).execution(返回值类型 包名.类名.方法名(参数列表)) 方法参数级别 控制粒度较细
4).@annotation(包名.注解名称) 按照注解的方式去拦截用户请求.

4 通知方法

1.前置通知: 主要在 目标方法执行之前执行
2.后置通知: 在目标方法执行之后执行
3.异常通知: 在目标方法执行的过程中报了异常之后执行.
4.最终通知: 无论什么时候都要执行的通知方法.
上述的通知方法,无法控制目标方法是否执行.所以一般"只做记录不做改变"
5.环绕通知: 一般采用环绕通知 实现对业务的控制.

AOP入门案例

//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {//公式:   切面 = 切入点表达式 + 通知方法./*** 业务需求: 要求拦截ItemCatServiceImpl类中的业务* @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.*///@Pointcut("bean(itemCatServiceImpl)")  //按类匹配,控制的粒度较粗   单个bean//@Pointcut("within(com.jt.service..*)")  //按类匹配,控制的粒度较粗     多个bean@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式public void pointCut() {}//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.@Before("pointCut()")public void before(JoinPoint joinPoint) {System.out.println("我是前置通知!!!!");String typeName = joinPoint.getSignature().getDeclaringTypeName();String methodName = joinPoint.getSignature().getName();Object[] objs = joinPoint.getArgs();Object target = joinPoint.getTarget();System.out.println("方法执行的全路径为:"+typeName+"."+methodName);System.out.println("获取方法参数:"+objs);System.out.println("获取目标对象:"+target);}//添加环绕通知  可以控制目标方法执行 要求添加参数@Around("pointCut()")public Object around(ProceedingJoinPoint joinPoint) {System.out.println("我是环绕通知开始");try {//Object result = joinPoint.proceed();System.out.println("我是环绕通知结束");return null;} catch (Throwable e) {e.printStackTrace();throw new RuntimeException(e);}  //指定目标方法}}

实现AOP 缓存处理

自定义注解

@Target(ElementType.METHOD)   //标识注解 对谁生效
@Retention(RetentionPolicy.RUNTIME) //注解使用的有效期
public @interface CacheFind {public String key();              //标识存入redis的key的前缀public int seconds() default 0;  //标识保存的时间 单位是秒}

注解标识

AOP实现缓存业务处理

 package com.jt.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import com.jt.anno.CacheFind;
import com.jt.util.ObjectMapperUtil;import redis.clients.jedis.Jedis;//1.将对象交给容器管理
@Component
//2.定义aop切面
@Aspect
public class CacheAOP {@Autowired(required = false)private Jedis jedis;/*** 实现思路:  拦截被@CacheFind标识的方法 之后利用aop进行缓存的控制* 通知方法:  环绕通知* 实现步骤:*       1.准备查询redis的key   ITEM_CAT_LIST::第一个参数*      2.@annotation(cacheFind) 动态获取注解的语法.*        拦截指定注解类型的注解并且将注解对象当做参数进行传递.*/@SuppressWarnings("unchecked")@Around("@annotation(cacheFind)")public Object around(ProceedingJoinPoint joinPoint,CacheFind cacheFind) {//1.获取用户注解中的key     ITEM_CAT_LIST::0String key = cacheFind.key();//2.动态获取第一个参数当做keyString firstArg = joinPoint.getArgs()[0].toString();key += "::"+firstArg; Object result = null;//3.根据key查询redis.if(jedis.exists(key)) {//根据redis获取数据信息String json = jedis.get(key);//如何获取返回值类型MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();result = ObjectMapperUtil.toObject(json, methodSignature.getReturnType());System.out.println("aop查询redis缓存");}else {//如果key不存在,则证明是第一次查询.  应该查询数据库try {result = joinPoint.proceed(); //目标方法返回值System.out.println("AOP查询数据库获取返回值结果");//将数据保存到redis中String json = ObjectMapperUtil.toJSON(result);int seconds = cacheFind.seconds();if(seconds>0) jedis.setex(key, seconds, json);else jedis.set(key, json); } catch (Throwable e) {e.printStackTrace();throw new RuntimeException(e);}}return result;}//公式:   切面 = 切入点表达式 + 通知方法./*** 业务需求: 要求拦截ItemCatServiceImpl类中的业务* @Pointcut 切入点表达式 可以理解为就是一个if判断,只有满足条件,才能执行通知方法.*//**//@Pointcut("bean(itemCatServiceImpl)")  //按类匹配,控制的粒度较粗   单个bean//@Pointcut("within(com.jt.service..*)")  //按类匹配,控制的粒度较粗     多个bean@Pointcut("execution(* com.jt.service..*.*(..))") //细粒度的匹配方式public void pointCut() {}//joinPoint 方法执行切恰好被切入点表达式匹配,该方法的执行就称之为连接点.@Before("pointCut()")public void before(JoinPoint joinPoint) {System.out.println("我是前置通知!!!!");String typeName = joinPoint.getSignature().getDeclaringTypeName();String methodName = joinPoint.getSignature().getName();Object[] objs = joinPoint.getArgs();Object target = joinPoint.getTarget();System.out.println("方法执行的全路径为:"+typeName+"."+methodName);System.out.println("获取方法参数:"+objs);System.out.println("获取目标对象:"+target);}//添加环绕通知  可以控制目标方法执行 要求添加参数@Around("pointCut()")public Object around(ProceedingJoinPoint joinPoint) {System.out.println("我是环绕通知开始");try {//Object result = joinPoint.proceed();System.out.println("我是环绕通知结束");return null;} catch (Throwable e) {e.printStackTrace();throw new RuntimeException(e);}  //指定目标方法}**/
}

AOP和代理的关系

完成商品分类的缓存实现

在itemCatServiceImpl中添加注解。实现商品列表查询时的缓存处理。

Redis分片实现

1.为什么使用分片

1).说明: 虽然redis可以扩展内存空间的大小.但是如果需要存储海量的数据一味的扩大内存,其实效率不高.
2).分片介绍: 准备多台redis,共同为用户提供缓存服务.在保证效率的前提下,实现了内存的扩容.
用户在使用分片机制时,将多台redis当做1台使用.

2.分片搭建

分片规划
由3台redis构成 端口号分别为6379/6380/6381, 如果需要准备多台redis则准备多个配置文件即可,注意其中的端口号.
准备多台redis

修改redis端口
说明:修改redis的配置文件的端口号

vim    6381.conf


启动多台redis
命令: [root@localhost shards]# redis-server 6379.conf & redis-server 6380.conf & redis-server 6381.conf &
检查服务是否启动:

一致性hash原理说明

目的:解决数据如何在分布式环境下进行存储!!!
hash取值区间: 8位16进制数 共有 232种可能性!!! (24)8=232次方

2).当节点发生变化带来哪些影响
当节点的数量发生了变化时,则节点中的对应的数据可以动态的迁移.
原则: 当发生了节点变化时,应该尽可能小的影响其他节点.

一致性hash特性
一致性哈希算法是在哈希算法基础上提出的,在动态变化的分布式环境中,哈希算法应该满足的几个条件:平衡性、单调性和分散性 [4] 。

①平衡性(均衡性)是指hash的结果应该平均分配到各个节点,这样从算法上解决了负载均衡问题 [4] 。 利用虚拟节点实现数据平衡 (平衡数据不能做到绝对平均,只能是相对的)

②单调性是指在新增或者删减节点时,不影响系统正常运行 . 可以实现动态的数据迁移.

③分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据 [4]
鸡蛋不要放到一个篮子里。

零散知识点小结(nginx/linux/mycat/redis/douubo/zookeeper)相关推荐

  1. java如何保证redis设置过期时间的原子性_分布式锁用 Redis 还是 Zookeeper

    在讨论这个问题之前,我们先来看一个业务场景: 系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户下单. 由于系统有 ...

  2. redis中有key但是删不掉_分布式锁用 Redis 还是 Zookeeper

    为什么用分布式锁? 在讨论这个问题之前,我们先来看一个业务场景:系统A是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存足够了才会给用户 ...

  3. 【零散知识点总结1】

    大部分知识点来源于该博主--骆昊 知识点来源于网络,知道的可以在评论区贴上来源喔 <零散知识点总结1> 该文章涉及:Dubbo.HTTP和HTTPS.Mybatis.Hibernate. ...

  4. 【零散知识点总结4】

    大部分来源于网络 <零散知识点总结1> 该文章涉及:Dubbo.HTTP和HTTPS.Mybatis.Hibernate. Zookeeper.Kafka.Elasticsearch.Re ...

  5. 【零散知识点总结3】

    大部分知识点来源于该博主--骆昊 知识点来源于网络,知道的可以在评论区贴上来源喔 <零散知识点总结1> 该文章涉及:Dubbo.HTTP和HTTPS.Mybatis.Hibernate. ...

  6. 【零散知识点总结2】

    大部分知识点来源于该博主--骆昊 知识点来源于网络,知道的可以在评论区贴上来源喔 <零散知识点总结1> 该文章涉及:Dubbo.HTTP和HTTPS.Mybatis.Hibernate. ...

  7. 伍哥原创之安装nginx,mysql,php-fpm,redis

    为什么80%的码农都做不了架构师?>>>    [伍哥原创] v1.0 2012-6-4 初稿 v1.1 2012-6-5 更新,增加php-redis模块的编译安装 [正文] 本文 ...

  8. Nginx+Lua脚本+Redis 实现自动封禁访问频率过高IP

    前言:由于公司前几天短信接口被一直攻击,并且攻击者不停变换IP,导致阿里云短信平台上的短信被恶意刷取了几千条,然后在Nginx上对短信接口做了一些限制 临时解决方案: 1.查看Nginx日志发现被攻击 ...

  9. nginx linux 安装

    nginx linux 安装 进入http://nginx.org/en/download.html 下载 n  gcc 安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有gc ...

最新文章

  1. python中uniform(a、b)_关于uniform的详细介绍
  2. 新疆计算机证相关信息技术,2019新疆中小学教师计算机考试资料:信息技术课程基本理念...
  3. 如何进行手机web远程调试——chrome beta
  4. maven下载,安装与eclipse中maven配置
  5. 深入解析字符串的比较方法:“==”操作符;String.Equals方法;String.Compare方法;String.CompareOrdinal方法。...
  6. 51CTO专访:谈谈SOC安全管理平台
  7. 【.net core 跨平台】第一步 在Ubuntu16.04 配置.net core环境
  8. Framehawk技术-思杰HDX
  9. 你这还不精通NIO(Netty_1)
  10. 1 Spark机器学习 spark MLlib 入门
  11. python apply函数不打印_Python Pandas dataframe shift在apply函数中不起作用
  12. c++ 集合常用函数及集合排序
  13. 编程语言应该如何选择?
  14. 基于SSM框架和JSP的房屋租赁、合同签订系统
  15. java代码实现注册发送邮件激活账户
  16. mysql init 崩溃_MySQL · 引擎特性 · InnoDB崩溃恢复
  17. 计算机窗口关闭不了怎么办,电脑上一直出现这个窗口关都关不掉怎么处理
  18. python乒乓球比赛规则_python模拟体育竞技分析--采用乒乓球规则
  19. 乐视 无法播放服务器文件夹,乐视电视最新常见问题及解决方法分享!
  20. Android - Broadcasts overview(不完整)

热门文章

  1. 在本地安装 godoc
  2. 教你合约开源 bsc合约开源 heco开源
  3. 系统测试的策略:15个
  4. 高职扩招有计算机专业吗,高职扩招怎么样高职扩招计算机专业
  5. 学习和体验Ray on volcano
  6. Nordic蓝牙芯片上开发 4pin的0.96寸 OLED I2C屏幕
  7. 王者荣耀什么时候出新的服务器苹果微信,iOS全面封杀热更新?开发者:对微信、王者荣耀等应用并无影响...
  8. linux 关闭core,linux下core文件的控制
  9. 白帽子讲web安全 编码问题sql注入的 笔记
  10. Git——安装与使用