谷粒商城项目笔记之分布式基础hxld

我们在这一阶段正式迈入实战项目---谷粒商城。接下来我们迈入分布式基础之项目环境搭建吧。

谷粒商城是一个B2C模式的电商平台,销售自营商品给客户。

在微服务架构中,微服务之间通过网络进行通信,存在相互依赖,当其中一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。

默认虚拟机的ip地址不是固定ip,开发不方便.

重新使用vagrantup启动机器即可。然后再vagrantssh连接机器。

成功。

注意要在root用户下。

sudosystemctlenabledocker

**注意:docker要在root用户下进行测试。

dockerpullmysql:5.7

vi/mydata/mysql/conf/my.cnf[client]default-character-set=utf8[mysql]default-character-set=utf8[mysqld]init_connect='SETcollation_connection=utf8_unicode_ci'init_connect='SETNAMESutf8'character-set-server=utf8collation-server=utf8_unicode_ciskip-character-set-client-handshakeskip-name-resolve注意:解决MySQL连接慢的问题在配置文件中加入如下,并重启mysql[mysqld]skip-name-resolve解释:skip-name-resolve:跳过域名解析

dockerexec-itmysqlmysql-uroot-proot

如果还需要设置其他的可以去上面这个文件中看。

dockerexec-itredisredis-cli

在maven的配置文件中修改镜像源和jdk版本。

整合mybatis-plus

com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery在应用的/src/main/resources/application.properties配置文件中配置NacosServer地址spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

启动应用,观察nacos服务列表是否已经注册上服务.

Nacos使用三步1、导包nacos-discovery2、写配置,指定nacos地址,指定应用的名字3、开启服务注册发现功能@EnableDiscoveryClient

记住:

添加匹配当前OpenFeign的负载均衡依赖

由于SpringCloudFeign在Hoxton.M2RELEASED版本之后不再使用Ribbon而是使用spring-cloud-loadbalancer,所以不引入spring-cloud-loadbalancer会报错解决方法:加入spring-cloud-loadbalancer依赖,并且在nacos中排除ribbon依赖,不然loadbalancer无效

com.alibaba.cloudspring-cloud-starter-alibaba-nacos-discoverycom.netflix.ribbonribbonorg.springframework.cloudspring-cloud-loadbalancer3.1.1gulimall-comon中添加以上注解解决问题。再次进行测试,如下图,测试成功。

Feign使用三步1、导包openfeign2、开启@EnableFeignClients功能3、编写接口,进行远程调用

NacosConfig数据结构NacosConfig主要通过dataId和group来唯一确定一条配置。NacosClient从NacosServer端获取数据时,调用的是此接口ConfigService.getConfig(StringdataId,Stringgroup,longtimeoutMs)。

id:路由的id,没有固定规则但要求唯一uri:匹配后提供服务的路由地址path:断言,路径相匹配的进行路由Query:查询相匹配的进行路由

如果我们在地址栏中访问的地址有qq,或者baidu就会进行转发。4.启动测试

测试报错:

网关启动报错坑:Errorcreatingbeanwithname'routeDefinitionRouteLocator'definedinclasspathresource[org/springframework/cloud/gateway/config/GatewayAutoConfiguration.class]:Unsatisfieddependencyexpressedthroughmethod'routeDefinitionRouteLocator'parameter4;nestedexceptionisorg.springframework.beans.factory.NoSuchBeanDefinitionException:Noqualifyingbeanoftype'org.springframework.core.convert.ConversionService'available:expe

网上搜索到的解决办法,我采用的是排除web内置容器的解决办法。

org.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-tomcat2)解决办法2:使用spring-webflux模块,webflux有一个全新的非堵塞的函数式ReactiveWeb框架,可以用来构建异步的、非堵塞的、事件驱动的服务

org.springframework.bootspring-boot-starter-webflux最后主启动类还要排除数据源:@SpringBootApplication(exclude={DruidDataSourceAutoConfigure.class,DataSourceAutoConfiguration.class})

排除的原因是我们将gateway也引入了gulimall-common,而这里面又有数据源配置(mybatis-plus),启动找不到对应的配置就会报错。解决办法就是排除数据源。

最后成功启动项目

快捷键:shift+!---快速生成html模板代码格式化:shift+alt+f

letstr="hello.vue";console.log(str.startsWith("hello"));//trueconsole.log(str.endsWith(".vue"));//trueconsole.log(str.includes("e"));//trueconsole.log(str.includes("hello"));//true2)、字符串模板模板字符串相当于加强版的字符串,用反引号`,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。

//1、多行字符串letss=`

helloworld
`console.log(ss)//2、字符串插入变量和表达式。变量名写在${}中,${}中可以放入JavaScript表达式。letname="张三";letage=18;letinfo=`我是${name},今年${age}了`;console.log(info)//3、字符串中调用函数functionfun(){return"这是一个函数"}letsss=`O(∩_∩)O哈哈~,${fun()}`;console.log(sss);//O(∩_∩)O哈哈~,这是一个函数5、函数优化1)、函数参数默认值//在ES6以前,我们无法给一个函数参数设置默认值,只能采用变通写法:functionadd(a,b){//判断b是否为空,为空就给默认值1b=b||1;returna+b;}//传一个参数console.log(add(10));//现在可以这么写:直接给参数写上默认值,没传就会自动使用默认值functionadd2(a,b=1){returna+b;}//传一个参数console.log(add2(10));2)、不定参数不定参数用来表示不确定参数个数,形如,...变量名,由...加上一个具名参数标识符组成。具名参数只能放在参数列表的最后,并且有且只有一个不定参数。

functionfun(...values){console.log(values.length)}fun(1,2)//2fun(1,2,3,4)//43)、箭头函数ES6中定义函数的简写方式:

//1、拷贝对象(深拷贝)letperson1={name:"Amy",age:15}letsomeone={...person1}console.log(someone)//{name:"Amy",age:15}//2、合并对象letage={age:15}letname={name:"Amy"}letperson2={...age,...name}//如果两个对象的字段名重复,后面对象字段值会覆盖前面对象的字段值console.log(person2)//{age:15,name:"Amy"}7、map和reduce数组中新增了map和reduce方法。

map():接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回。

letarr=['1','20','-5','3'];console.log(arr)arr=arr.map(s=>parseInt(s));console.log(arr) 2)、reduce语法:arr.reduce(callback,[initialValue])reduce为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用reduce的数组。callback(执行数组中每个值的函数,包含四个参数)1、previousValue(上一次调用回调返回的值,或者是提供的初始值(initialValue))2、currentValue(数组中当前被处理的元素)3、index(当前元素在数组中的索引)4、array(调用reduce的数组)initialValue(作为第一次调用callback的第一个参数。)示例:

user.json:{"id":1,"name":"zhangsan","password":"123456"}user_corse_1.json:{"id":10,"name":"chinese"}corse_score_10.json:{"id":100,"score":90}//回调函数嵌套的噩梦:层层嵌套。$.ajax({url:"mock/user.json",success(data){console.log("查询用户:",data);$.ajax({url:`mock/user_corse_${data.id}.json`,success(data){console.log("查询到课程:",data);$.ajax({url:`mock/corse_score_${data.id}.json`,success(data){console.log("查询到分数:",data);},error(error){console.log("出现异常了:"+error);}});},error(error){console.log("出现异常了:"+error);}});},error(error){console.log("出现异常了:"+error);}});我们可以通过Promise解决以上问题。

constpromise=newPromise(function(resolve,reject){//执行异步操作if(/*异步操作成功*/){resolve(value);//调用resolve,代表Promise将返回成功的结果}else{reject(error);//调用reject,代表Promise会返回失败结果}});使用箭头函数可以简写为:

constpromise=newPromise((resolve,reject)=>{//执行异步操作if(/*异步操作成功*/){resolve(value);//调用resolve,代表Promise将返回成功的结果}else{reject(error);//调用reject,代表Promise会返回失败结果}});这样,在promise中就封装了一段异步执行的结果。

如果我们想要等待异步执行完成,做一些事情,我们可以通过promise的then方法来实现。如果想要处理promise异步执行失败的事件,还可以跟上catch:

promise.then(function(value){//异步执行成功后的回调}).catch(function(error){//异步执行失败后的回调})3)、Promise改造以前嵌套方式newPromise((resolve,reject)=>{$.ajax({url:"mock/user.json",success(data){console.log("查询用户:",data);resolve(data.id);},error(error){console.log("出现异常了:"+error);}});}).then((userId)=>{returnnewPromise((resolve,reject)=>{$.ajax({url:`mock/user_corse_${userId}.json`,success(data){console.log("查询到课程:",data);resolve(data.id);},error(error){console.log("出现异常了:"+error);}});});}).then((corseId)=>{console.log(corseId);$.ajax({url:`mock/corse_score_${corseId}.json`,success(data){console.log("查询到分数:",data);},error(error){console.log("出现异常了:"+error);}});});4)、优化处理优化:通常在企业开发中,会把promise封装成通用方法,如下:封装了一个通用的get请求方法

letget=function(url,data){//实际开发中会单独放到common.js中returnnewPromise((resolve,reject)=>{$.ajax({url:url,type:"GET",data:data,success(result){resolve(result);},error(error){reject(error);}});})}//使用封装的get方法,实现查询分数get("mock/user.json").then((result)=>{console.log("查询用户:",result);returnget(`mock/user_corse_${result.id}.json`);}).then((result)=>{console.log("查询到课程:",result);returnget(`mock/corse_score_${result.id}.json`)}).then((result)=>{console.log("查询到分数:",result);}).catch(()=>{console.log("出现异常了:"+error);});通过比较,我们知道了Promise的扁平化设计理念,也领略了这种上层设计带来的好处。我们的项目中会使用到这种异步处理的方式;

模块化就是把代码进行拆分,方便重复利用。类似java中的导包:要使用一个包,必须先导包。而JS中没有包的概念,换来的是模块。模块功能主要由两个命令构成:export和import。

比如我定义一个js文件:hello.js,里面有一个对象:

constutil={sum(a,b){returna+b;}}我可以使用export将这个对象导出:

constutil={sum(a,b){returna+b;}}export{util};当然,也可以简写为:

exportconstutil={sum(a,b){returna+b;}}export不仅可以导出对象,一切JS变量都可以导出。比如:基本类型变量、函数、数组、对象。当要导出多个值时,还可以简写。比如我有一个文件:user.js:

varname="jack"varage=21export{name,age}省略名称上面的导出代码中,都明确指定了导出的变量名,这样其它人在导入使用时就必须准确写出变量名,否则就会出错。因此js提供了default关键字,可以对导出的变量名进行省略例如:

使用export命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块。例如我要使用上面导出的util:

//导入utilimportutilfrom'hello.js'//调用util中的属性util.sum(1,2)要批量导入前面导出的name和age:

import{name,age}from'user.js'console.log(name+",今年"+age+"岁了")但是上面的代码暂时无法测试,因为浏览器目前还不支持ES6的导入和导出功能。除非借助于工具,把ES6的语法进行编译降级到ES5,比如Babel-cli工具我们暂时不做测试,大家了解即可。

而MVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用再关心Model和View之间是如何互相影响的:

官网文档提供了3种安装方式:

页面代码

我们对刚才的案例进行简单修改:

给页面添加一个按钮:

每个Vue应用都是通过用Vue函数创建一个新的Vue实例开始的:

每个Vue实例都需要关联一段Html模板,Vue会基于此模板进行视图渲染。我们可以通过el属性来指定。例如一段html模板:

然后创建Vue实例,关联这个div

letvm=newVue({el:"#app"})这样,Vue就可以基于id为app的div元素作为模板进行渲染了。在这个div范围以外的部分是无法使用vue特性的。

JS:

letvm=newVue({el:"#app",data:{name:"刘德华"}})4、方法Vue实例中除了可以定义data属性,也可以定义方法,并且在Vue实例的作用范围内使用。Html:

{{num}}

JS:

什么是指令?

格式:{{表达式}}说明:

可以使用v-text和v-html指令来替代{{}}说明:

并且不会出现插值闪烁,当没有数据时,会显示空白或者默认数据。

html属性不能使用双大括号形式绑定,我们使用v-bind指令给HTML标签属性绑定值;而且在将v-bind用于class和style时,Vue.js做了专门的增强。

2)、绑定stylev-bind:style的对象语法十分直观,看着非常像CSS,但其实是一个JavaScript对象。style属性名可以用驼峰式(camelCase)或短横线分隔(kebab-case,这种方式记得用单引号括起来)来命名。例如:font-size-->fontSize

结果:

4)、v-bind缩写3、v-model刚才的v-text、v-html、v-bind可以看做是单向绑定,数据影响了视图渲染,但是反过来就不行。接下来学习的v-model是双向绑定,视图(View)和模型(Model)之间会互相影响。既然是双向绑定,一定是在视图中可以修改数据,这样就限定了视图的元素类型。目前v-model的可使用元素有:

点赞取消

有{{num}}个赞

letvm=newVue({el:"#app",data:{num:100},methods:{decrement(){this.num--;//要使用data中的属性,必须this.属性名}}})另外,事件绑定可以简写,例如v-on:click='add'可以简写为@click='add'

在事件处理程序中调用event.preventDefault()或event.stopPropagation()是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理DOM事件细节。为了解决这个问题,Vue.js为v-on提供了事件修饰符。修饰符是由点开头的指令后缀来表示的。

点赞
取消

有{{num}}个赞

letapp=newVue({el:"#app",data:{num:100},methods:{decrement(ev){//ev.preventDefault();this.num--;}}})效果:右键“点赞”,不会触发默认的浏览器右击事件;右键“取消”,会触发默认的浏览器右击事件)

遍历数据渲染页面是非常常用的需求,Vue中通过v-for指令来实现。

语法:v-for="iteminitems"

v-else元素必须紧跟在带v-if或者v-else-if的元素的后面,否则它将不会被识别。示例:

点我呀{{random}}=0.75">看到我啦?!v-if>=0.750.5">看到我啦?!v-else-if>0.50.25">看到我啦?!v-else-if>0.25看到我啦?!v-elseletapp=newVue({el:"#app",data:{random:1}})6、计算属性和侦听器1、计算属性(computed)某些结果是基于之前数据实时计算出来的,我们可以利用计算属性。来完成示例:

watch可以让我们监控一个值的变化。从而做出相应的反应。示例:

{{user.id}}{{user.name}}{{user.gender===1"男":"女"}}
1、局部过滤器注册在当前vue实例中,只有当前实例能用

我们通过Vue的component方法来定义一个全局组件。

定义好的组件,可以任意复用多次:

全局安装webpack

全局安装vue脚手架

商城的商品页面展示是一个三级分类的。有一级分类、二级分类、三级分类。这就是我们接下来要进行的操作。

表名解释

gulimall-product中的controller包下的CategoryController

@RestController@RequestMapping("product/category")publicclassCategoryController{@AutowiredprivateCategoryServicecategoryService;/***查出所有分类以及子分类,以树形结构组装起来*/@RequestMapping("/list/tree")publicRlist(){Listentities=categoryService.listWithTree();returnR.ok().put("data",entities);}}2、CategoryService接着我们使用idea自带的工具帮助我们生成相应的方法。

我们启动gulimall-product微服务进行测试查询。在启动的时候发现有报错

Description:TheTomcatconnectorconfiguredtolistenonport10000failedtostart.Theportmayalreadybeinuseortheconnectormaybemisconfigured.Action:Verifytheconnector'sconfiguration,identifyandstopanyprocessthat'slisteningonport10000,orconfigurethisapplicationtolistenonanotherport.上面的意思是我们的10000端口被占用。接下来我们看看到底是什么服务占用了10000端口。

netstat-aon|findstr"12000"#找到12000端口taskkill/F/PID6164#杀死12000端口对应的线程记:深信服vpn进程在日常工作中需要,而且不知道为什么杀不死?

解决办法:暂时修改gulimall_product端口为11000,gulimall_ware端口为12000.

启动renren-fast微服务,这个是后台。将renren-fast-vue在vscode中启动,接着我们来到后台系统进行菜单模块的添加。

在商品系统中新增一个分类维护的菜单。菜单的路由其实就是我们商品微服务中的访问路径。

我们在后台系统中修改的,在数据库的gulimall-admin中也会同步进行修改。

我们可以看到如果我们点击角色管理的话,地址栏是/sys-role,但是我们实际发送的请求应该是/sys/role,所以由此可以知道后台会将/自动转换为-,同理我们去访问/product/category也会自动被转换为/product-category。

我们在renren-fast-vue中可以看到有一个文件,对应的其实就是/sys-role,即sys文件夹下的role.vue对应的就是角色管理这个页面的展示。所以对于商品分类/product/category,我们接下来要做的就是在renren-fast-vue下创建一个product文件夹,文件夹中创建一个category.vue来进行页面展示。

renren-fast-vue中有一个Index.js是管理api接口请求地址的,如下图。如果我们本次只是简单的将8080改为11000端口,那么当下次如果是12000呢?难道每次都要改吗?所以我们的下一步做法是使用网关进行路由。通过网关映射到具体的请求地址。

注意这里截图中vue的格式是有错误的,如果不进行修正的话,运行会报错。我们前端的代码中只要显示了红色的,就要检查是哪里错误

对于微服务,后面我们统一改为加api前缀才能路由过去。

在网关配置文件中加入:

-id:admin_routeuri:lb://renren-fast#路由给renren-fast,lb代表负载均衡predicates:#什么情况下路由给它-Path=/api/**#默认前端项目都带上api前缀,路径中带了api前缀的filters:-RewritePath=/api/(.*),/renren-fast/$\{segment}注意:

这个地方一定要格式对齐,否则启动后会出现下面这个问题:

启动报错:Causedby:org.yaml.snakeyaml.scanner.ScannerException:mappingvaluesarenotallowedhere

这个地方报错的原因大概率是yml文件语法错误:注意这个坑找了好久,iduripredicatesfilters都要对齐,同一层级。

记一次错误踩坑(这个在后来将gulimall下的所有依赖进行按照老师统一版本进行重构之后,还是使用的引入)

对于renren-fast中不要引入gulimall-common这个依赖,否则会出现依赖循环报错这个情况以及一系列的突发情况,正常应该是我们自己引入nacos的pom。

舍弃:

com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config 2.1.0.RELEASE com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 2.1.0.RELEASE 如此这样之后,启动renren-fast项目后发现仍然报错,报错的原因居然是:noserveravailablenacos

这样的错误可能是application.yml文件中nacos配置错误。回去一看,果然是写成了nacos-config-addr,其实应该是nacos-discovery-addr.(注意,细心啊。)

再次启动,启动成功。

鉴于上面出现很多错误,但是老师视频中没有出现这些错误,大概率是因为依赖的原因,所以对于gulimall中所有的依赖进行统一,按照老师的依赖进行配置。以防止后面出现很多突发的错误。

修改后运行成功,验证码出现。

我们在gulimall-gateway中创建一个config来存放GulimallCorsConfiguration。注意这个包一定是要在gateway这个包下,否则启动报错(坑)。

配置naocs标准步骤:

1)、bootstrap.properties中将命名空间添加上

2)、application.yml中配置nacos注册中心地址

3)、主启动类上加入服务注册发现注解

-id:product_routeuri:lb://gulimall-productpredicates:-Path=/api/product/**filters:-RewritePath=/api/(/.*),/$\{segment}注意:

修正:在路由规则的顺序上,将精确的路由规则放置到模糊的路由规则的前面,否则的话,精确的路由规则将不会被匹配到,类似于异常体系中trycatch子句中异常的处理顺序。

原因是:先访问网关88,网关路径重写后访问nacos8848,nacos找到服务

上面的测试,显访问正确。

我们使用{}将data的数据进行解构:

此时有了3级结构,但是没有数据,在category.vue的模板中,数据是menus,而还有一个props。这是element-ui的规则