ObjectexecuteAndDecode(RequestTemplatetemplate,Optionsoptions)throwsThrowable{//构造出请求Requestrequest=this.targetRequest(template);if(this.logLevel!=Level.NONE){//打印日志this.logger.logRequest(this.metadata.configKey(),this.logLevel,request);}longstart=System.nanoTime();Responseresponse;try{//执行。client是LoadBalancerFeignClient。跳转到远程response=this.client.execute(request,options);response=response.toBuilder().request(request).requestTemplate(template).build();}catch(IOExceptionvar16){if(this.logLevel!=Level.NONE){this.logger.logIOException(this.metadata.configKey(),this.logLevel,var16,this.elapsedTime(start));}throwFeignException.errorExecuting(request,var16);}。。。公共返回类R因为是个hashmap,所以setData不成功
publicclassR
P136
不使用前后端分离开发了,管理后台用vue
静态资源处理
nginx发给网关集群,网关再路由到微服务
静态资源放到nginx中,后面的很多服务都需要放到nginx中
html首页资源index放到gulimall-product下的static文件夹
把index.html放到templates中
pom依赖
导入thymeleaf依赖、热部署依赖devtools使页面实时生效
thymeleaf:cache:falsesuffix:.htmlprefix:classpath:/templates/web开发放到web包下,原来的controller是前后分离对接手机等访问的,所以可以改成app,对接app应用
刚导入index.html时,里面的分类菜单都是写死的,我们要访问数据库拿到放到model中,然后在页面foreach填入
@GetMapping({"/","index.html"})publicStringgetIndex(Modelmodel){//获取所有的一级分类List
利用nginx转到网关(记得关防火墙)
要实现的逻辑:本机浏览器请求gulimall.com,通过配置hosts文件之后,那么当你在浏览器中输入gulimall.com的时候,相当于域名解析DNS服务解析得到ip192.168.56.10,也就是并不是访问java服务,而是先去找nginx。什么意思呢?是说如果某一天项目上线了,gulimall.com应该是nginx的ip,用户访问的都是nginx
请求到了nginx之后,
nginx.conf:
监听来自gulimall:80的请求,
在这里最重要的是这个再转给网关的配置
nginx.conf
请求结果相同
此时请求接口和请求页面都是gulimall.com
JVM参数、工具、调优笔记:
下载:
创建测试计划,添加线程组
线程数==用户
添加-取样器-HTTP请求
添加-监听器-查看结果树
添加-监听器-汇总报告
报错原因:
修改操作系统注册表1、打开注册表:运行-regedit2、直接输入找到HKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesTCPIPParameters3、右击Parameters新建DWORD32值,name:TcpTimedWaitDelay,value:30(十进制)——>设置为30秒回收(默认240)4、新建DWORD值,name:MaxUserPort,value:65534(十进制)——>设置最大连接数65534注意:修改时先选择十进制,再填写数字。5、重启系统
运行状态:
要监控GC,安装插件:工具-插件。可用插件-检查最新版本报错的时候百度“插件中心”,改个JVM对应的插件中心url.xml.z
安装visualGC
视频教程中的测试结果
product微服务的-Xmx1024m-Xms1024m-Xmn512m
由于动态资源和静态资源目前都处于服务端,所以为了减轻服务器压力,我们将js、css、img等静态资源放置在Nginx端,以减轻服务器压力
优化前
对二级菜单的每次遍历都需要查询数据库,浪费大量资源
优化后
仅查询一次数据库,剩下的数据通过遍历得到并封装
线程基础百度吧
异步编排参考网上链接即可:
CompletableFuture介绍
Future是Java5添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。
很多语言,比如Node.js,采用回调的方式实现异步编程。Java的一些框架,比如Netty,自己扩展了Java的Future接口,提供了addListener等多个扩展方法;Googleguava也提供了通用的扩展Future;Scala也提供了简单易用且功能强大的Future/Promise异步编程模式。
作为正统的Java类库,是不是应该做点什么,加强一下自身库的功能呢?
在Java8中,新增加了一个包含50个方法左右的类:CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
创建异步对象
CompletableFuture提供了四个静态方法来创建一个异步操作。
staticCompletableFuture
计算完成时回调方法:当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:
publicCompletableFuture
whenComplete和whenCompleteAsync的区别:
方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)
publicclassCompletableFutureDemo{publicstaticvoidmain(String[]args)throwsExecutionException,InterruptedException{CompletableFuturefuture=CompletableFuture.supplyAsync(newSupplier
publicCompletionStagehandle(BiFunction
thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。
thenRun方法:只要上面的任务执行完成,就开始执行thenRun,只是处理完任务后,执行thenRun的后续操作
带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。
publicCompletableFuturethenApply(Function
修改网关使item路由到product
复制详情页的html到product,静态文件放到nginx
观察我们要建立怎样的VO
@DatapublicclassSkuItemVo{/***1sku基本信息的获取:如标题*/SkuInfoEntityinfo;booleanhasStock=true;/***2sku的图片信息*/List
我们在url中首先有sku_id,在从sku_info表查标题的时候,顺便查到了spu_id、catelog_id,这样我们就可以操作剩下表了。
在5查询规格参数中
根据spu获取销售属性对应的所有值。首先知道spu是没有销售属性的,而是spu对应sku[]的销售属性
根据各种选项决定一个sku是如何做到的?我们可以利用一下ES的倒排索引。比较难想到,先正序看一下吧
查询得到的结果特别像ES中的倒排索引
调用thenAcceptAsync()可以接受上一步的结果且没有返回值。
最后调用get()方法使主线程阻塞到其他线程完成任务。
页面上1245渲染都比较简单,我们需要看看4是如何渲染的。
之前拿到的sku_ids是用,分隔的,
通过控制class中是否包换checked属性来控制显示样式,因此要根据skuId判断
离线笔记均为markdown格式,图片也是云图,10多篇笔记20W字,压缩包仅500k,推荐使用typora阅读。也可以自己导入有道云笔记等软件中
技术人就该干点技术人该干的事
如果帮到了你,留下赞吧,谢谢支持
基础篇文档太长,就分P了吧
P49。删除前提:没有子菜单、没有被其他菜单引用
render-content:
在element-ui的tree中,有2个非常重要的属性
使用scopedslot
created(){//生命周期this.getMenus();//会设置"menus"变量的值},注意到tree标签上有一个 要调整按钮的显示情况,用v-if=“node.level<=2” 增加复选框show-checkbox 结点唯一id:node-key=“catId” 由于delete请求接收的是一个数组,所以这里使用JSON方式,传入了一个数组: 点击删除后再次查询数据库能够看到cat_id为1000的数据已经被删除了。 但是我们需要修改检查当前菜单是否被引用 修改CategoryController类,添加如下代码: @RequestMapping("/delete")publicRdelete(@RequestBodyLong[]catIds){//删除之前需要判断待删除的菜单那是否被别的地方所引用。// categoryService.removeByIds(Arrays.asList(catIds));categoryService.removeMenuByIds(Arrays.asList(catIds));returnR.ok();}@Override//CategoryServiceImplpublicvoidremoveMenuByIds(List 假设数据库中有字段show_status为0,标记它已经被删除。 配置全局的逻辑删除规则,在“src/main/resources/application.yml”文件中添加如下内容: mybatis-plus:mapper-locations:classpath:/mapper/**/*.xmlglobal-config:db-config:id-type:autologic-delete-value:1logic-not-delete-value:0修改product.entity.CategoryEntity实体类,添加上@TableLogic,表明使用逻辑删除: /** *是否显示[0-不显示,1显示] */ @TableLogic(value="1",delval="0") privateIntegershowStatus;然后在POSTMan中测试一下是否能够满足需要。 另外在“src/main/resources/application.yml”文件中,设置日志级别,打印出SQL语句: logging:level:com.atguigu.gulimall.product:debug打印的日志: 修改P53 1)拖拽与数据库关联的内容: 4)先了解一下如何获取结点的深度 在拖拽的时候首先会自动调用allowDrop()函数,他在第一句就调用了this.countNodeLevel(draggingNode);-----------------------------------------;//countNodeLevel()函数的作用是遍历拖拽结点的【子节点】,找到其中的最大层级countNodeLevel(node){//找到所有子节点,求出最大深度if(node.childNodes!=null&&node.childNodes.length>0){for(leti=0;i 我们得到了子树的深度deep,就可以判断这个拖拽合不合法: 拖拽类型:以拖拽后新的父结点为基准分为: 6)拖拽合法后的操作 对于后端更新数据库,加入controller。用postman测试, /***批量修改层级{["catId":1,"sort":0],["catId":2,"catLevel":2]}*/@RequestMapping("/update/sort")publicRupdateSort(@RequestBodyCategoryEntity[]category){categoryService.updateBatchById(Arrays.asList(category));returnR.ok();}8)拖拽开关 P57 为了防止误操作,我们通过edit把拖拽功能开启后才能进行操作。所以添加switch标签,操作是否可以拖拽。我们也可以体会到el-switch这个标签是一个开关 现在想要实现一个拖拽过程中不更新数据库,拖拽完成后,统一提交拖拽后的数据。 现在还存在一个问题,如果是将一个菜单连续的拖拽,最终还放到了原来的位置,但是updateNode中却出现了很多节点更新信息,这样显然也是一个问题。 红框 getCheckedNodes()返回当前选中的所有结点 如何调用内 (2)将逆向工程product得到的resourcessrcviewsmodulesproduct文件拷贝到gulimall/renren-fast-vue/src/views/modules/product目录下,也就是下面的两个文件 但是显示的页面没有新增和删除功能,这是因为权限控制的原因, 它是在“index.js”中定义,暂时将它设置为返回值为true,即可显示添加和删除功能。 再次刷新页面能够看到,按钮已经出现了: 进行添加测试成功,进行修改也会自动回显 brand.vue /** *三级分类修改的时候回显路径 */@TableField(exist=false)//数据库中不存在privateLong[]catelogPath;修改controller,找到属性分组id对应的分类,然后把该分类下的所有属性分组都填充好 /***信息*/@RequestMapping("/info/{attrGroupId}")//@RequiresPermissions("product:attrgroup:info")publicRinfo(@PathVariable("attrGroupId")LongattrGroupId){AttrGroupEntityattrGroup=attrGroupService.getById(attrGroupId);//用当前当前分类id查询完整路径并写入attrGroupLong[]paths=categoryService.findCateLogPath(attrGroup.getCatelogId())attrGroup.setCatelogPath(paths);returnR.ok().put("attrGroup",attrGroup);}添加service, @Override//CategoryServiceImplpublicLong[]findCateLogPath(LongcatelogId){List 这个大概是因为你复制了别人的github代码,而CategoryController他的controller没有写好。我当然图省事复制了一段代码,结果controller和service层都写了重复的逻辑。 正确方法是把controller逻辑去掉,直接返回即可。 下面的代码是返回父类信息,是父子结点的关系 @Override//service层publicList 新建mapper类 publicinterfaceUserMapperextendsBaseMapper @RunWith(SpringRunner.class)@SpringBootTestpublicclassSampleTest{@AutowiredprivateUserMapperuserMapper;@TestpublicvoidtestSelect(){//方法是mp自动生成的List 也可以使用@TableField映射属性和数据库字段 @TableLogic用于逻辑删除 查询条件用QueryWrapper包装 wrapper.allEq(map);用于指定字段值 wrapper.gt(“age”,2);//大于//用于指定字段与常数关系 QueryWrapperwrapper=newQueryWrapper();wrapper.orderByDesc("age");wrapper.orderByAsc("age");wrapper.having("id>8");mapper.selectList(wrapper).forEach(System.out::println);mapper.selectBatchIds(Arrays.asList(7,8,9));mp分页使用mp自带的分页是内存分页,性能低,所以需要手动写分页配置,使用物理分页需要先添加个mybatis的拦截器 packagecom.atguigu.gulimall.product.config;@EnableTransactionManagement@MapperScan("com.atguigu.gulimall.product.dao")@ConfigurationpublicclassMybatisConfig{@BeanpublicPaginationInterceptorpaginationInterceptor(){PaginationInterceptorpaginationInterceptor=newPaginationInterceptor();//设置请求的页面大于最大页后操作,true调回到首页,false继续请求默认falsepaginationInterceptor.setOverflow(true);//设置最大单页限制数量,默认500条,-1不受限制paginationInterceptor.setLimit(1000);returnpaginationInterceptor;}}接口IPage 项目中用的分页方式,不是自己创建page对象,而是根据url的参数自动封装 多对多的关系应该有relation表 修改CategoryBrandRelationController的逻辑 /***获取当前品牌的所有分类列表*/@GetMapping("/catelog/list")publicRlist(@RequestParam("brandId")LongbrandId){//根据品牌id获取其分类信息List 所以像name这种冗余字段可以保存,优化save,保存时用关联表存好,但select时不用关联 @RequestMapping("/save")publicRsave(@RequestBodyCategoryBrandRelationEntitycategoryBrandRelation){categoryBrandRelationService.saveDetail(categoryBrandRelation);returnR.ok();}/***根据获取品牌id、三级分类id查询对应的名字保存到数据库*/@Override//CategoryBrandRelationServiceImplpublicvoidsaveDetail(CategoryBrandRelationEntitycategoryBrandRelation){//获取品牌id、三级分类idLongbrandId=categoryBrandRelation.getBrandId();LongcatelogId=categoryBrandRelation.getCatelogId();//根据id查品牌名字、分类名字,统一放到一个表里,就不关联分类表查了BrandEntitybrandEntity=brandDao.selectById(brandId);CategoryEntitycategoryEntity=categoryDao.selectById(catelogId);//把查到的设置到要保存的哪条数据里categoryBrandRelation.setBrandName(brandEntity.getName());categoryBrandRelation.setCatelogName(categoryEntity.getName());this.save(categoryBrandRelation);}最终效果: 但是如果分类表里的name发送变化,那么品牌表里的分类name字段应该同步变化。 所以应该修改brand-controller,使之update时检测分类表里的name进行同步 属性分组 P76 问题:查询所有时没有模糊查询 还是像之前一样解决一些问题 获取属性分组的关联的所有属性 发送请求:/product/attrgroup/{attrgroupId}/attr/relation 获取当前属性分组所关联的属性 如何查找:既然给出了attr_group_id,那么到中间表中查询出来所关联的attr_id,然后得到最终的所有属性即可。 可能出现null值的问题,提前返回null 关联属性的时候让他显示未关联的属性,而且还要只显示分组内的属性/product/attrgroup/{attrgroupId}/noattr/relation 规格参数新增时,请求的URL:RequestURL: 当有新增字段时,我们往往会在entity实体类中新建一个字段,并标注数据库中不存在该字段 如在一些Entity中,为了让mybatis-plus与知道某个字段不与数据库匹配,那么就加个@TableField(exist=false)privateLongattrGroupId;然而这种方式并不规范,比较规范的做法是,新建一个vo文件夹,将每种不同的对象,按照它的功能进行了划分。在java中,涉及到了这几种类型 PO、DO、TO、DTO 1.PO持久对象 2、DO(Domain0bject)领域对象 3.TO(Transfer0bject),数据传输对象传输的对象 4.DTO(DataTransferObiect)数据传输对象 5.VO(valueobject)值对象 Viewobject:视图对象 接受页面传递来的对象,封装对象 将业务处理完成的对象,封装成页面要用的数据 6.BO(businessobject)业务对象 7、POJO简单无规则java对象 8、DAO 通过"BeanUtils.copyProperties(attr,attrEntity);"能够实现在两个Bean之间属性对拷 @Transactional@OverridepublicvoidsaveAttr(AttrVoattrVo){AttrEntityattrEntity=newAttrEntity();//重要的工具BeanUtils.copyProperties(attrVo,attrEntity);//1、保存基本数据this.save(attrEntity);//2、保存关联关系if(attrVo.getAttrType()==ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()&&attrVo.getAttrGroupId()!=null){AttrAttrgroupRelationEntityrelationEntity=newAttrAttrgroupRelationEntity();relationEntity.setAttrGroupId(attrVo.getAttrGroupId());relationEntity.setAttrId(attrEntity.getAttrId());relationEntity.setAttrSort(0);relationDao.insert(relationEntity);}}问题:现在有两个查询,一个是查询部分,另外一个是查询全部,但是又必须这样来做吗?还是有必要的,但是可以在后台进行设计,两种查询是根据catId是否为零进行区分的。 这个是spring的工具类,用于拷贝同名属性 先用mp的正常分页查出来数据,得到Page对象 然后用PageUtils把分页信息得到,但里面的数据需要替换一下 替换数据是为了解决“不使用联表查询” 最终要的效果是:上传成功提示: 获取所有会员等级:/member/memberlevel/list API: 开启编写member项目 在“gulimall-gateway”中修改“”文件,添加对于member的路由 -id:gulimall-memberuri:lb://gulimall-memberpredicates:-Path=/api/member/**filters:-RewritePath=/api/( spring.cloud.nacos.config.name=gulimall-memberspring.cloud.nacos.config.server-addr=192.168.137.14:8848spring.cloud.nacos.config.namespace=795521fa-77ef-411e-a8d8-0889fdfe6964spring.cloud.nacos.config.extension-configs[0].data-id=gulimall-member.ymlspring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUPspring.cloud.nacos.config.extension-configs[0].refresh=true获取分类关联的品牌:/product/categorybrandrelation/brands/list P85 P86如果遇到图片上传不成功的问题, 添加json生成的vo 最终保存spu信息:观察下面的步骤与db表 请求类型:/product/attrgroup/{catelogId}/withattr 请求方式:GET 主键不是自增的话,需要加@TableId(type=IdType.INPUT) URL:/product/spuinfo/list 请求参数 {page:1,//当前页码limit:10,//每页记录数sidx:id,//排序字段order:asc/desc,//排序方式key:华为,//检索关键字catelogId:6,//三级分类idbrandId:1,//品牌idstatus:0,//商品状态}状态: 当下架时: t:1588983789089status:2//状态key:brandId:0catelogId:0page:1limit:10修改日期:问题:在SPU中,写出的日期数据都不符合规则:想要符合规则,可以设置写出数据的规则:spring.jacksonspring:jackson:date-format:yyyy-MM-ddHH:mm:ssSKU检索:P94 请求体: 库存信息表: 创建项目后nacos注册、网关重写等 提供模糊查询仓库信息 @RequestMapping("/list")publicRlist(@RequestParamMap 功能:查询sku+库存id+库存数等信息 数据库表:wms_ware_sku指明每个仓库有什么sku 采购需求的生成方式可能有两种: 总的流程: 【1】仓库列表功能: 【2】查询商品库存: 【3】查询采购需求:/ware/purchase/unreceive/list 【4】合并采购需求: 新建采购需求后还要可以提供合并采购单,比如一个仓库的东西可以合并到一起,让采购人员一趟采购完 请求数据: {purchaseId:1,#采购单id,没有携带就新建采购单items:[1,2]#采购商品}涉及到两张表:wms_purchase_detail,wms_purchase先在采购单中填写数据,然后关联用户,关联用户后,如果不选择整单直接点击确定,将弹出【没有分配采购人员】提示。 某个人领取了采购单后,先看采购单是否处于未分配状态,只有采购单是新建或以领取状态时,才更新采购单的状态 后台系统里没有领取采购单这个功能,我们暂时通过postman手动领取采购单 然后用POSTMAN发送请求模拟APP发送请求 可以多选采购单里哪些采购项(需求)完成了 后端代码为: 只要异常被捕获,事务是不会滚的(这里需要优化,是高级篇消息队列实现一致性事务的内容) @OverridepublicvoidaddStock(LongskuId,LongwareId,IntegerskuNum){List 这里时区显示不太正常,可以在配置中添加spring.jackson.time-zone=GMT+8 GET:/product/attr/base/listforspu/{spuId} 响应: URL:/product/attr/update/{spuId} [{ "attrId":7, "attrName":"入网型号", "attrValue":"LIO-AL00", "quickShow":1},{ "attrId":14, "attrName":"机身材质工艺", "attrValue":"玻璃", "quickShow":0},{ "attrId":16, "attrName":"CPU型号", "attrValue":"HUAWEIKirin980", "quickShow":1}]