谷粒商城笔记基础篇2(2/4)

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不成功

publicclassRextendsHashMap{//把setData重写成PUTpublicRsetData(Objectdata){put("data",data);returnthis;}publicTgetData(TypeReferencetypeReference){//get("data")默认是map类型所以再由map转成string再转jsonObjectdata=get("data");//得到list,list每个值是map类型//list转jsonStrings=JSON.toJSONString(data);//json转listreturnJSON.parseObject(s,typeReference);}}在其他处是newTypeReference>data的值对应的是List,而list的每个值是map

P136

不使用前后端分离开发了,管理后台用vue

静态资源处理

nginx发给网关集群,网关再路由到微服务

静态资源放到nginx中,后面的很多服务都需要放到nginx中

html首页资源index放到gulimall-product下的static文件夹

把index.html放到templates中

pom依赖

导入thymeleaf依赖、热部署依赖devtools使页面实时生效

org.springframework.bootspring-boot-devtoolsorg.springframework.bootspring-boot-starter-thymeleaf关闭thymeleaf缓存,方便开发实时看到更新

thymeleaf:cache:falsesuffix:.htmlprefix:classpath:/templates/web开发放到web包下,原来的controller是前后分离对接手机等访问的,所以可以改成app,对接app应用

刚导入index.html时,里面的分类菜单都是写死的,我们要访问数据库拿到放到model中,然后在页面foreach填入

@GetMapping({"/","index.html"})publicStringgetIndex(Modelmodel){//获取所有的一级分类Listcatagories=categoryService.getLevel1Catagories();model.addAttribute("catagories",catagories);return"index";}页面遍历菜单数据

利用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提供了四个静态方法来创建一个异步操作。

staticCompletableFuturerunAsync(Runnablerunnable)publicstaticCompletableFuturerunAsync(Runnablerunnable,Executorexecutor)publicstaticCompletableFuturesupplyAsync(Suppliersupplier)publicstaticCompletableFuturesupplyAsync(Suppliersupplier,Executorexecutor)没有指定Executor的方法会使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。以下所有的方法都类同。

计算完成时回调方法:当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

publicCompletableFuturewhenComplete(BiConsumeraction);publicCompletableFuturewhenCompleteAsync(BiConsumeraction);publicCompletableFuturewhenCompleteAsync(BiConsumeraction,Executorexecutor);publicCompletableFutureexceptionally(Functionfn);whenComplete可以处理正常和异常的计算结果,exceptionally处理异常情况。BiConsumer可以定义处理业务

whenComplete和whenCompleteAsync的区别:

方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其他线程执行(如果是使用相同的线程池,也可能会被同一个线程选中执行)

publicclassCompletableFutureDemo{publicstaticvoidmain(String[]args)throwsExecutionException,InterruptedException{CompletableFuturefuture=CompletableFuture.supplyAsync(newSupplier(){@OverridepublicObjectget(){System.out.println(Thread.currentThread().getName()+" completableFuture");inti=10/0;return1024;}}).whenComplete(newBiConsumer(){@Overridepublicvoidaccept(Objecto,Throwablethrowable){System.out.println("-------o="+o.toString());System.out.println("-------throwable="+throwable);}}).exceptionally(newFunction(){@OverridepublicObjectapply(Throwablethrowable){System.out.println("throwable="+throwable);return6666;}});System.out.println(future.get());}}handle方法handle是执行任务完成时对结果的处理。handle是在任务完成后再执行,还可以处理异常的任务。

publicCompletionStagehandle(BiFunctionfn);publicCompletionStagehandleAsync(BiFunctionfn);publicCompletionStagehandleAsync(BiFunctionfn,Executorexecutor);线程串行化方法thenApply方法:当一个线程依赖另一个线程时,获取上一个任务返回的结果,并返回当前任务的返回值。

thenAccept方法:消费处理结果。接收任务的处理结果,并消费处理,无返回结果。

thenRun方法:只要上面的任务执行完成,就开始执行thenRun,只是处理完任务后,执行thenRun的后续操作

带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。

publicCompletableFuturethenApply(Functionfn)publicCompletableFuturethenApplyAsync(Functionfn)publicCompletableFuturethenApplyAsync(Functionfn,Executorexecutor)publicCompletionStagethenAccept(Consumeraction);publicCompletionStagethenAcceptAsync(Consumeraction);publicCompletionStagethenAcceptAsync(Consumeraction,Executorexecutor);publicCompletionStagethenRun(Runnableaction);publicCompletionStagethenRunAsync(Runnableaction);publicCompletionStagethenRunAsync(Runnableaction,Executorexecutor);九、商品详情添加hosts内容192.168.56.10item.gulimall.com

修改网关使item路由到product

复制详情页的html到product,静态文件放到nginx

观察我们要建立怎样的VO

@DatapublicclassSkuItemVo{/***1sku基本信息的获取:如标题*/SkuInfoEntityinfo;booleanhasStock=true;/***2sku的图片信息*/Listimages;/***3获取spu的销售属性组合。每个attrName对应一个value-list*/ListsaleAttr;/***4获取spu的介绍*/SpuInfoDescEntitydesc;/***5获取spu的规格参数信息,每个分组的包含list*/ListgroupAttrs;/***6秒杀信息*/SeckillInfoVoseckillInfoVo;}@ToString@DatapublicclassItemSaleAttrVo{ privateLongattrId; privateStringattrName; /**AttrValueWithSkuIdVo两个属性attrValue、skuIds*/ privateListattrValues;}@ToString@DatapublicclassSpuItemAttrGroup{ privateStringgroupName; /**两个属性attrName、attrValue*/ privateListattrs;}(2)sql构建我们观察商品页面与VO,可以大致分为5个部分需要封装。124比较简单,单表就查出来了。我们分析3、5

我们在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判断

选择[[${attr.attrName}]]
[[${vals.attrValue}]]
显示处理完了,下面编写选中某个售卖属性后如何变化页面元素

离线笔记均为markdown格式,图片也是云图,10多篇笔记20W字,压缩包仅500k,推荐使用typora阅读。也可以自己导入有道云笔记等软件中

技术人就该干点技术人该干的事

如果帮到了你,留下赞吧,谢谢支持

基础篇文档太长,就分P了吧

P49。删除前提:没有子菜单、没有被其他菜单引用

render-content:

//对应到函数,去得到数据并渲染匹配到了renderContent(h,{node,data,store}){return(//返回要显示的dom元素{node.label}this.append(data)}>Appendthis.remove(node,data)}>Delete);}scopedslot(插槽):在el-tree标签里把内容写到span标签栏里即可

在element-ui的tree中,有2个非常重要的属性

使用scopedslot

传入了2个参数,node代表当前结点(是否展开等信息),data是结点数据。data从哪里来:前面ajax发送请求,拿到data,赋值给menus属性,而menus属性绑定到标签的data属性。而node是ui的默认规则结点的标签{{node.label}}结点后面跟的2个按钮append(data)">Appendremove(node,data)">Delete初始化分类页面加载完后,自动调用

created(){//生命周期this.getMenus();//会设置"menus"变量的值},注意到tree标签上有一个

要调整按钮的显示情况,用v-if=“node.level<=2”

增加复选框show-checkbox

结点唯一id:node-key=“catId”

{{node.label}}append(data)">新增按钮Appendremove(node,data)">删除按钮Delete删除分类controller我们可以删除某个分类,要点如下:

由于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(ListasList){//TODO1检查当前的菜单是否被别的地方所引用//2baseMapper.deleteBatchIds(asList);}逻辑删除然而多数时候,我们并不希望删除数据,而是标记它被删除了,这就是逻辑删除;

假设数据库中有字段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)拖拽与数据库关联的内容:

3)函数参数:

4)先了解一下如何获取结点的深度

在拖拽的时候首先会自动调用allowDrop()函数,他在第一句就调用了this.countNodeLevel(draggingNode);-----------------------------------------;//countNodeLevel()函数的作用是遍历拖拽结点的【子节点】,找到其中的最大层级countNodeLevel(node){//找到所有子节点,求出最大深度if(node.childNodes!=null&&node.childNodes.length>0){for(leti=0;ithis.maxLevel){//是赋值给了共享变量maxLevelthis.maxLevel=node.childNodes[i].level;}//递归子节点this.countNodeLevel(node.childNodes[i]);}}}----------------------------------------;找到了拖拽结点的最大层级(深度),那么就可以计算拖拽结点作为根节点的子树深度deep。;另外注意maxLevel每次拖拽都会更新,是拖拽结点的最大层级;letdeep=Math.abs(this.maxLevel-draggingNode.level)+1;//draggingNode为正在拖拽的结点5)拖拽合法性

我们得到了子树的深度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这个标签是一个开关

另外导入了在init方法里进行回显但是分类的id还是不对,应该是用数组封装的路径根据属性分组id查到属性分组后填充到页面

/** *三级分类修改的时候回显路径 */@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){Listpaths=newArrayList<>();paths=findParentPath(catelogId,paths);//收集的时候是顺序前端是逆序显示的所以用集合工具类给它逆序一下//子父转父子Collections.reverse(paths);returnpaths.toArray(newLong[paths.size()]);//1级2级3级}/***递归收集所有父分类*/privateListfindParentPath(LongcatlogId,Listpaths){//1、收集当前节点idpaths.add(catlogId);//比如父子孙层级,返回的是孙子父CategoryEntityparent_Id=this.getById(catlogId);if(byId.getParentCid()!=0){//递归findParentPath(parent_Id.getParentCid(),paths);}returnpaths;}优化:会话关闭时清空内容,防止下次开启还遗留数据

这个大概是因为你复制了别人的github代码,而CategoryController他的controller没有写好。我当然图省事复制了一段代码,结果controller和service层都写了重复的逻辑。

正确方法是把controller逻辑去掉,直接返回即可。

下面的代码是返回父类信息,是父子结点的关系

@Override//service层publicListlistWithTree(){//怎么拿categoryDao?/**继承了ServiceImpl有个属性baseMapper,自动注入**///1查出所有分类ListcategoryEntities=baseMapper.selectList(null);//2组装成父子的树型结构//2.1找到所有一级分类Listlevel1Menus=categoryEntities.stream().filter(//找到一级categoryEntity->categoryEntity.getParentCid()==0).map(menu->{//把当前的child属性改了之后重新返回menu.setChildren(getChildren(menu,categoryEntities));returnmenu;}).sorted((menu1,menu2)->menu1.getSort()-menu2.getSort()).collect(Collectors.toList());returnlevel1Menus;//returncategoryEntities;}@RequestMapping("/list/tree")publicRlist(){Listentities=categoryService.listWithTree();returnR.ok().put("data",entities);}24、分页插件P75

com.baomidoumybatis-plus-boot-starter在SpringBoot启动类中添加@MapperScan注解,扫描Mapper文件夹:

新建mapper类

publicinterfaceUserMapperextendsBaseMapper{//实现BaseMapper<>}执行查询

@RunWith(SpringRunner.class)@SpringBootTestpublicclassSampleTest{@AutowiredprivateUserMapperuserMapper;@TestpublicvoidtestSelect(){//方法是mp自动生成的ListuserList=userMapper.selectList(null);Assert.assertEquals(5,userList.size());userList.forEach(System.out::println);}}mp常用注解比如@TableName,标注在实体类上,使用的时候定义mapper接口指定实体类泛型即可

也可以使用@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;}}接口IPageselectPageVo(Page<>page,Integerstate);xml:不变接收的返回值IPagePagepage=newPage<>(2,2);Pageresult=mapper.selectPage(page,null);result.getRecords()如果要自定义SQL,在接口里单独写@Select注解或者在xml里写好即可Query在Service实现层this.page(Page,QueryWrapper)

项目中用的分页方式,不是自己创建page对象,而是根据url的参数自动封装

多对多的关系应该有relation表

修改CategoryBrandRelationController的逻辑

/***获取当前品牌的所有分类列表*/@GetMapping("/catelog/list")publicRlist(@RequestParam("brandId")LongbrandId){//根据品牌id获取其分类信息Listdata=categoryBrandRelationService.list(newQueryWrapper().eq("brand_id",brandId));returnR.ok().put("data",data);}//获得分类列表后再继续进行后面的工作关联表的优化:分类名本可以在brand表中,但因为关联查询对数据库性能有影响,在电商中大表数据从不做关联,哪怕分步查也不用关联

所以像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/(/.*),/${segment}在“gulimall-member”中,创建“bootstrap.properties”文件,内容如下:

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(@RequestParamMapparams){PageUtilspage=wareInfoService.queryPage(params);returnR.ok().put("page",page);}@Override//WareInfoServiceImplpublicPageUtilsqueryPage(Mapparams){//传入分页信息QueryWrapperwrapper=newQueryWrapper<>();//查询关键字Stringkey=(String)params.get("key");if(!StringUtils.isEmpty(key)){//仓库编号、仓库名字、仓库地址、区域编号wrapper.eq("id",key).or().like("name",key).or().like("address",key).or().like("areacode",key);}//执行IPagepage=this.page(newQuery().getPage(params),wrapper);returnnewPageUtils(page);}可以打开日志查看查询sql

功能:查询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){ListwareSkuEntities=wareSkuDao.selectList(newQueryWrapper().eq("sku_id",skuId).eq("ware_id",wareId));if(wareSkuEntities==null||wareSkuEntities.size()==0){//新增库存WareSkuEntitywareSkuEntity=newWareSkuEntity();wareSkuEntity.setSkuId(skuId);wareSkuEntity.setWareId(wareId);wareSkuEntity.setStock(skuNum);wareSkuEntity.setStockLocked(0);//远程查询SKU的name,若失败无需回滚try{Rinfo=productFeignService.info(skuId);if(info.getCode()==0){Mapdata=(Map)info.get("skuInfo");wareSkuEntity.setSkuName((String)data.get("skuName"));}}catch(Exceptione){}wareSkuDao.insert(wareSkuEntity);}else{//新增库存//乐观锁//UPDATE`wms_ware_sku`SETstock=stock+#{skuNum}WHEREsku_id=#{skuId}ANDware_id=#{wareId}wareSkuDao.addStock(skuId,wareId,skuNum);}}//其实这有多线程问题,新增仓库时多个参数必须用@Param注解标明,这就不啰嗦了

这里时区显示不太正常,可以在配置中添加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}]

THE END
1.商品下架啥意思商品下架该词一般用于网店,用于提示用户不要再下单购买此件商品,已经卖光了。在现实生活中的一些店铺里偶尔也会出现“此商品已经下架”,一般用于出问题的商品,以此提示用户商店对此商品做了处理,将不再对外销售。下架,是一个汉语词语,意思是典当期满。 更多精彩资讯请关注七彩丝,我们将持续为您更新最新资讯! 查看...http://m.qicaisi.com/bk-1078144.shtml
2.库存中心操作指引文档注意:直邮模式的货品不可以使用预售,仅保税bbc模式的商品支持下文中的预售模式 0.创建货品 1.货品备案 2.注册登陆账号 3.如何操作货品预约入库/提货出库 4.如何修改库存(分配库存到商品宝贝) 4.1场景: 4.2路径: 4.3 SKU/商品链接的编辑变更 4.4 重点备注: 5.已经在途的库存赶不及入库要预售如何申请 5.1场景: 5.2...https://school.jinritemai.com/doudian/web/article/aHNuKGpC9fH1
3.饿了么餐饮服务商平台(3)企业应用承载的是大型商户的整体解决方案,平台应用承载的是中小型商户的痛点需求,平台应用会以商品的形式面向全量商户出现在应用市场中,企业应用仅对授权商户展示; (4)目前,平台应用仅面向1万家以上商户服务规模的ISV开放申请。 Q:饿了么API中的地理位置经纬度坐标是什么坐标系? https://open.shop.ele.me/base/documents/faq
4.分布式详解一致性算法全局唯一ID分布式锁分布式事务...这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入去去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。 插入或更新 这种方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了...https://developer.aliyun.com/article/1416839
1.高效有序商品流通新模式,最新清货群介绍家政服务摘要:最新的清货群作为一种高效、有序的商品流通新模式,致力于优化商品流通环节,提高销售效率。通过加入清货群,商家能够更快速地处理存货,减少库存积压,实现商品快速流通。该模式组织有序,确保商品流通的顺畅和高效,为商家和消费者带来更加便捷、高效的购物体验。 https://www.jiuzichankang.cn/post/29616.html
2.淘宝api商品详情返回值说明淘宝api商品详情返回值说明 淘宝API商品详情返回值提供了商品的详细信息,这些信息对于商家进行商品展示、库存管理、价格监控以及用户行为分析等工作至关重要。以下是对淘宝API商品详情返回值的详细说明: 一、请求状态与错误信息 status:表示请求的状态,0为成功,非0为失败。https://blog.itpub.net/70042344/viewspace-3050338/
3.学编程多看多动手系列:Haskell事件溯源模式实现购物篮这段Haskell代码实现了一个基于事件溯源(Event Sourcing)模式的购物篮系统。事件溯源是一种软件架构模式,其中系统的状态不是直接存储的,而是通过一系列不可变的事件(记录了系统状态的变化)来重建的。主要功能包括: 定义基本类型:定义了产品(Product)、价格(Price)和折扣(Discount)等基本类型。 https://blog.csdn.net/gzjimzhou/article/details/143923938
4.Java商品自动下架解决方案java商品超卖首先想到的解决办法就是通过事务来解决,先更新后查询,然后判断是否超库存,若发生超卖则抛出异常,通过spring事务回滚处理。但最后分析了这种方案的缺陷:spirng事务不能严格保证数据一致性和业务逻辑正确性,而且减库存的压力全部落在数据库身上,不能保证高并发。https://blog.51cto.com/u_16099343/7114915
5.2024年最新加密货币的行话术语大全币种百科区块链暗网不能够被常规的网络协议访问,只可使用非常规协议和端口以及可信节点进行连接的私有网络。 API 在数字货币交易中,API能够让用户和应用程序或服务数据进行即时沟通,从而自行操作交易、获取行情数据等。 阿尔法 常用于指软件开发的第一阶段内部测试的版本,含义与“开始”、“第一个”相近。 https://www.jb51.net/blockchain/931845.html
6.大数据常见面试问题汇总江阴雨辰互联2.15.7 哪个商品卖的好? 2.15.8 数据仓库每天跑多少张表,大概什么时候运行,运行多久? 2.15.9 哪张表数据量最大 2.15.10 哪张表最费时间,有没有优化 2.15.11 并发峰值多少?大概哪个时间点? 2.15.12 分析过最难的指标 2.15.13 数仓中使用的哪种文件存储格式 ...https://www.yc00.com/news/1698791689a880360.html
7.gpmall/PRDatmaster·qfmx/gpmall·GitHub共有7个状态来控制商品的上架流程分别是:刚创建、提交审核、审核通过、申请驳回、商品删除、上架、下架,只有处于已上架状态的产品,用户才可能看到商品,各个细节形成一个完整的闭环 二、交易管理 1. 全部订单 功能描述 查看所有的订单,并可以打印订单详情,查看订单具体内容,货到付款的订单可以设置为已经收款。 https://github.com/qfmx/gpmall/tree/master/PRD
8.MySQL三万字精华总结写在之前:不建议那种上来就是各种面试题罗列,然后背书式的去记忆,对技术的提升帮助很小,对正经面试也没什么帮助,有点东西的面试官深挖下就懵逼了。 个人建议把面试题看作是费曼学习法中的回顾、简化的环节,准http://it.sdmtkj.net/cbs/caodneg7-p-13353659
9.ES亿级商品索引拆分实战腾讯云开发者社区伴随政府采购业务的快速发展,政采云的商品数据量也在快速膨胀,其中由 ES 进行提供的商品检索服务压力,也越来越大。由于商品模型中基础商品和交易商品的定义,导致商品本身的量会相对一般的电商场景多出一倍。 商品模型简介 基础商品 真实的商品,拥有仓库,主图,属性等商品主体信息。 https://cloud.tencent.com/developer/article/2321886
10.《亿级流量网站架构核心技术》——读书笔记重试(等一会再试、尝试其他分组服务、尝试其他机房服务,重试算法可考虑使用如指数退避算法) 摘掉不存活节点(负载均衡/分布式缓存场景下) 托底数据(返回历史数据/静态数据/缓存数据/) 等待页或者错误页 第7章:回滚机制 1、回滚概念 当程序或数据出错时,将程序或数据恢复到最近的一个正确版本的行为。最常见的如事务回...https://maimai.cn/article/detail?fid=1314479668&efid=FAc2Rl0U8HrqVe_nhjXoZg
11.KingbaseEs事务支持DDL回滚吗?()环控专业大风机控制柜(软启动控制柜,变频控制柜)与电力专业双电源接口界定:大风机控制柜进线断路器上口三相固定螺丝为界,以上为电力专业负责,以下由环控专业负责,螺丝的紧固由环控专业负责,电力专业配合停、送电。https://www.shuashuati.com/ti/23084f7c535244d9a3e84389130a922fa1.html
12.MySql面经原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样,就好比买一件商品,购买成功时,则给商家付了钱,商品到手;购买失败时,则商品在商家手中,消费者的钱也没花出去。 https://www.jianshu.com/p/5244fc598cd0
13.微信,爆更!三?视频号商品可“先买后付” 这次更新后,视频号内商品可以先购买,收货后再付款。 不过这一功能有微信支付分的要求,如果微信支付分没有达标,无法使用这一功能。 四?个人名片显示视频号名称 有网友爆料,这次更新后如果你的好友有开通视频号功能,那么会在他的个人名片上显示视频号的名字以及认证信息。 https://m.jrj.com.cn/madapter/finance/2023/09/24131237902350.shtml