上文回顾

上节 我们实现了根据搜索关键词查询商品列表和根据商品分类查询,并且使用到了mybatis-pagehelper插件,讲解了如何使用插件来帮助我们快速实现分页数据查询。本文我们将继续开发商品详情页面和商品留言功能的开发。

需求分析

关于商品详情页,和往常一样,我们先来看一看jd的示例:





从上面2张图,我们可以看出来,大体上需要展示给用户的信息。比如:商品图片,名称,价格,等等。在第二张图中,我们还可以看到有一个商品评价页签,这些都是我们本节要实现的内容。

商品详情

开发梳理

我们根据上图(权当是需求文档,很多需求文档写的比这个可能还差劲很多...)分析一下,我们的开发大致都要关注哪些points:

  • 商品标题
  • 商品图片集合
  • 商品价格(原价以及优惠价)
  • 配送地址(我们的实现不在此,我们后续直接实现在下单逻辑中)
  • 商品规格
  • 商品分类
  • 商品销量
  • 商品详情
  • 商品参数(生产场地,日期等等)
  • ...

根据我们梳理出来的信息,接下来开始编码就会很简单了,大家可以根据之前课程讲解的,先自行实现一波,请开始你们的表演~

编码实现

DTO实现

因为我们在实际的数据传输过程中,不可能直接把我们的数据库entity之间暴露到前端,而且我们商品相关的数据是存储在不同的数据表中,我们必须要封装一个ResponseDTO来对数据进行传递。

  • ProductDetailResponseDTO包含了商品主表信息,以及图片列表、商品规格(不同SKU)以及商品具体参数(产地,生产日期等信息)
  1. @Data
  2. @ToString
  3. @Builder
  4. @AllArgsConstructor
  5. @NoArgsConstructor
  6. public class ProductDetailResponseDTO {
  7. private Products products;
  8. private List<ProductsImg> productsImgList;
  9. private List<ProductsSpec> productsSpecList;
  10. private ProductsParam productsParam;
  11. }

Custom Mapper实现

根据我们之前表的设计,这里使用生成的通用mapper就可以满足我们的需求。

Service实现

从我们封装的要传递到前端的ProductDetailResponseDTO就可以看出,我们可以根据商品id分别查询出商品的相关信息,在controller进行数据封装就可以了,来实现我们的查询接口。

  • 查询商品主表信息(名称,内容等)

    com.liferunner.service.IProductService中添加接口方法:

    1. /**
    2. * 根据商品id查询商品
    3. *
    4. * @param pid 商品id
    5. * @return 商品主信息
    6. */
    7. Products findProductByPid(String pid);

    接着,在com.liferunner.service.impl.ProductServiceImpl中添加实现方法:

    1. @Override
    2. @Transactional(propagation = Propagation.SUPPORTS)
    3. public Products findProductByPid(String pid) {
    4. return this.productsMapper.selectByPrimaryKey(pid);
    5. }

    直接使用通用mapper根据主键查询就可以了。

    同上,我们依次来实现图片、规格、以及商品参数相关的编码工作

  • 查询商品图片信息列表

    1. /**
    2. * 根据商品id查询商品规格
    3. *
    4. * @param pid 商品id
    5. * @return 规格list
    6. */
    7. List<ProductsSpec> getProductSpecsByPid(String pid);
    8. ----------------------------------------------------------------
    9. @Override
    10. public List<ProductsSpec> getProductSpecsByPid(String pid) {
    11. Example example = new Example(ProductsSpec.class);
    12. val condition = example.createCriteria();
    13. condition.andEqualTo("productId", pid);
    14. return this.productsSpecMapper.selectByExample(example);
    15. }
  • 查询商品规格列表

    1. /**
    2. * 根据商品id查询商品规格
    3. *
    4. * @param pid 商品id
    5. * @return 规格list
    6. */
    7. List<ProductsSpec> getProductSpecsByPid(String pid);
    8. ------------------------------------------------------------------
    9. @Override
    10. public List<ProductsSpec> getProductSpecsByPid(String pid) {
    11. Example example = new Example(ProductsSpec.class);
    12. val condition = example.createCriteria();
    13. condition.andEqualTo("productId", pid);
    14. return this.productsSpecMapper.selectByExample(example);
    15. }
  • 查询商品参数信息

    1. /**
    2. * 根据商品id查询商品参数
    3. *
    4. * @param pid 商品id
    5. * @return 参数
    6. */
    7. ProductsParam findProductParamByPid(String pid);
    8. ------------------------------------------------------------------
    9. @Override
    10. public ProductsParam findProductParamByPid(String pid) {
    11. Example example = new Example(ProductsParam.class);
    12. val condition = example.createCriteria();
    13. condition.andEqualTo("productId", pid);
    14. return this.productsParamMapper.selectOneByExample(example);
    15. }

Controller实现

在上面将我们需要的信息查询实现之后,然后我们需要在controller对数据进行包装,之后再返回到前端,供用户来进行查看,在com.liferunner.api.controller.ProductController中添加对外接口/detail/{pid},实现如下:

  1. @GetMapping("/detail/{pid}")
  2. @ApiOperation(value = "根据商品id查询详情", notes = "根据商品id查询详情")
  3. public JsonResponse findProductDetailByPid(
  4. @ApiParam(name = "pid", value = "商品id", required = true)
  5. @PathVariable String pid) {
  6. if (StringUtils.isBlank(pid)) {
  7. return JsonResponse.errorMsg("商品id不能为空!");
  8. }
  9. val product = this.productService.findProductByPid(pid);
  10. val productImgList = this.productService.getProductImgsByPid(pid);
  11. val productSpecList = this.productService.getProductSpecsByPid(pid);
  12. val productParam = this.productService.findProductParamByPid(pid);
  13. val productDetailResponseDTO = ProductDetailResponseDTO
  14. .builder()
  15. .products(product)
  16. .productsImgList(productImgList)
  17. .productsSpecList(productSpecList)
  18. .productsParam(productParam)
  19. .build();
  20. log.info("============查询到商品详情:{}==============", productDetailResponseDTO);
  21. return JsonResponse.ok(productDetailResponseDTO);
  22. }

从上述代码中可以看到,我们分别查询了商品、图片、规格以及参数信息,使用ProductDetailResponseDTO.builder().build()封装成返回到前端的对象。

Test API

按照惯例,写完代码我们需要进行测试。

  1. {
  2. "status": 200,
  3. "message": "OK",
  4. "data": {
  5. "products": {
  6. "id": "smoke-100021",
  7. "productName": "(奔跑的人生) - 中华",
  8. "catId": 37,
  9. "rootCatId": 1,
  10. "sellCounts": 1003,
  11. "onOffStatus": 1,
  12. "createdTime": "2019-09-09T06:45:34.000+0000",
  13. "updatedTime": "2019-09-09T06:45:38.000+0000",
  14. "content": "吸烟有害健康“
  15. },
  16. "productsImgList": [
  17. {
  18. "id": "1",
  19. "productId": "smoke-100021",
  20. "url": "http://www.life-runner.com/product/smoke/img1.png",
  21. "sort": 0,
  22. "isMain": 1,
  23. "createdTime": "2019-07-01T06:46:55.000+0000",
  24. "updatedTime": "2019-07-01T06:47:02.000+0000"
  25. },
  26. {
  27. "id": "2",
  28. "productId": "smoke-100021",
  29. "url": "http://www.life-runner.com/product/smoke/img2.png",
  30. "sort": 1,
  31. "isMain": 0,
  32. "createdTime": "2019-07-01T06:46:55.000+0000",
  33. "updatedTime": "2019-07-01T06:47:02.000+0000"
  34. },
  35. {
  36. "id": "3",
  37. "productId": "smoke-100021",
  38. "url": "http://www.life-runner.com/product/smoke/img3.png",
  39. "sort": 2,
  40. "isMain": 0,
  41. "createdTime": "2019-07-01T06:46:55.000+0000",
  42. "updatedTime": "2019-07-01T06:47:02.000+0000"
  43. }
  44. ],
  45. "productsSpecList": [
  46. {
  47. "id": "1",
  48. "productId": "smoke-100021",
  49. "name": "中华",
  50. "stock": 2276,
  51. "discounts": 1.00,
  52. "priceDiscount": 7000,
  53. "priceNormal": 7000,
  54. "createdTime": "2019-07-01T06:54:20.000+0000",
  55. "updatedTime": "2019-07-01T06:54:28.000+0000"
  56. },
  57. ],
  58. "productsParam": {
  59. "id": "1",
  60. "productId": "smoke-100021",
  61. "producPlace": "中国",
  62. "footPeriod": "760天",
  63. "brand": "中华",
  64. "factoryName": "中华",
  65. "factoryAddress": "陕西",
  66. "packagingMethod": "盒装",
  67. "weight": "100g",
  68. "storageMethod": "常温",
  69. "eatMethod": "",
  70. "createdTime": "2019-05-01T09:38:30.000+0000",
  71. "updatedTime": "2019-05-01T09:38:34.000+0000"
  72. }
  73. },
  74. "ok": true
  75. }

商品评价

在文章一开始我们就看过jd详情页面,有一个详情页签,我们来看一下:



它这个实现比较复杂,我们只实现相对重要的几个就可以了。

开发梳理

针对上图中红色方框圈住的内容,分别有:

  • 评价总数
  • 好评度(根据好评总数,中评总数,差评总数计算得出)
  • 评价等级
  • 以及用户信息加密展示
  • 评价内容
  • ...

我们来实现上述分析的相对必要的一些内容。

编码实现

查询评价

根据我们需要的信息,我们需要从用户表、商品表以及评价表中来联合查询数据,很明显单表通用mapper无法实现,因此我们先来实现自定义查询mapper,当然数据的传输对象是我们需要先来定义的。

Response DTO实现

创建com.liferunner.dto.ProductCommentDTO.

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @Builder
  5. public class ProductCommentDTO {
  6. //评价等级
  7. private Integer commentLevel;
  8. //规格名称
  9. private String specName;
  10. //评价内容
  11. private String content;
  12. //评价时间
  13. private Date createdTime;
  14. //用户头像
  15. private String userFace;
  16. //用户昵称
  17. private String nickname;
  18. }

Custom Mapper实现

com.liferunner.custom.ProductCustomMapper中添加查询接口方法:

  1. /***
  2. * 根据商品id 和 评价等级查询评价信息
  3. * <code>
  4. * Map<String, Object> paramMap = new HashMap<>();
  5. * paramMap.put("productId", pid);
  6. * paramMap.put("commentLevel", level);
  7. *</code>
  8. * @param paramMap
  9. * @return java.util.List<com.liferunner.dto.ProductCommentDTO>
  10. * @throws
  11. */
  12. List<ProductCommentDTO> getProductCommentList(@Param("paramMap") Map<String, Object> paramMap);

mapper/custom/ProductCustomMapper.xml中实现该接口方法的SQL:

  1. <select id="getProductCommentList" resultType="com.liferunner.dto.ProductCommentDTO" parameterType="Map">
  2. SELECT
  3. pc.comment_level as commentLevel,
  4. pc.spec_name as specName,
  5. pc.content as content,
  6. pc.created_time as createdTime,
  7. u.face as userFace,
  8. u.nickname as nickname
  9. FROM items_comments pc
  10. LEFT JOIN users u
  11. ON pc.user_id = u.id
  12. WHERE pc.item_id = #{paramMap.productId}
  13. <if test="paramMap.commentLevel != null and paramMap.commentLevel != ''">
  14. AND pc.comment_level = #{paramMap.commentLevel}
  15. </if>
  16. </select>

如果没有传递评价级别的话,默认查询全部评价信息。

Service 实现

com.liferunner.service.IProductService中添加查询接口方法:

  1. /**
  2. * 查询商品评价
  3. *
  4. * @param pid 商品id
  5. * @param level 评价级别
  6. * @param pageNumber 当前页码
  7. * @param pageSize 每页展示多少条数据
  8. * @return 通用分页结果视图
  9. */
  10. CommonPagedResult getProductComments(String pid, Integer level, Integer pageNumber, Integer pageSize);

com.liferunner.service.impl.ProductServiceImpl实现该方法:

  1. @Override
  2. public CommonPagedResult getProductComments(String pid, Integer level, Integer pageNumber, Integer pageSize) {
  3. Map<String, Object> paramMap = new HashMap<>();
  4. paramMap.put("productId", pid);
  5. paramMap.put("commentLevel", level);
  6. // mybatis-pagehelper
  7. PageHelper.startPage(pageNumber, pageSize);
  8. val productCommentList = this.productCustomMapper.getProductCommentList(paramMap);
  9. for (ProductCommentDTO item : productCommentList) {
  10. item.setNickname(SecurityTools.HiddenPartString4SecurityDisplay(item.getNickname()));
  11. }
  12. // 获取mybatis插件中获取到信息
  13. PageInfo<?> pageInfo = new PageInfo<>(productCommentList);
  14. // 封装为返回到前端分页组件可识别的视图
  15. val commonPagedResult = CommonPagedResult.builder()
  16. .pageNumber(pageNumber)
  17. .rows(productCommentList)
  18. .totalPage(pageInfo.getPages())
  19. .records(pageInfo.getTotal())
  20. .build();
  21. return commonPagedResult;
  22. }

因为评价过多会使用到分页,这里使用通用分页返回结果,关于分页,可查看学习分页传送门

Controller实现

com.liferunner.api.controller.ProductController中添加对外查询接口:

  1. @GetMapping("/comments")
  2. @ApiOperation(value = "查询商品评价", notes = "根据商品id查询商品评价")
  3. public JsonResponse getProductComment(
  4. @ApiParam(name = "pid", value = "商品id", required = true)
  5. @RequestParam String pid,
  6. @ApiParam(name = "level", value = "评价级别", required = false, example = "0")
  7. @RequestParam Integer level,
  8. @ApiParam(name = "pageNumber", value = "当前页码", required = false, example = "1")
  9. @RequestParam Integer pageNumber,
  10. @ApiParam(name = "pageSize", value = "每页展示记录数", required = false, example = "10")
  11. @RequestParam Integer pageSize
  12. ) {
  13. if (StringUtils.isBlank(pid)) {
  14. return JsonResponse.errorMsg("商品id不能为空!");
  15. }
  16. if (null == pageNumber || 0 == pageNumber) {
  17. pageNumber = DEFAULT_PAGE_NUMBER;
  18. }
  19. if (null == pageSize || 0 == pageSize) {
  20. pageSize = DEFAULT_PAGE_SIZE;
  21. }
  22. log.info("============查询商品评价:{}==============", pid);
  23. val productComments = this.productService.getProductComments(pid, level, pageNumber, pageSize);
  24. return JsonResponse.ok(productComments);
  25. }

FBI WARNING:

@ApiParam(name = "level", value = "评价级别", required = false, example = "0")

@RequestParam Integer level

关于ApiParam参数,如果接收参数为非字符串类型,一定要定义example为对应类型的示例值,否则Swagger在访问过程中会报example转换错误,因为example缺省为""空字符串,会转换失败。例如我们删除掉level这个字段中的example=”0“,如下为错误信息(但是并不影响程序使用。)

  1. 2019-11-23 15:51:45 WARN AbstractSerializableParameter:421 - Illegal DefaultValue null for parameter type integer
  2. java.lang.NumberFormatException: For input string: ""
  3. at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
  4. at java.lang.Long.parseLong(Long.java:601)
  5. at java.lang.Long.valueOf(Long.java:803)
  6. at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412)
  7. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  8. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  9. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  10. at java.lang.reflect.Method.invoke(Method.java:498)
  11. at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:688)
  12. at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:721)
  13. at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166)
  14. at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)

Test API

福利讲解

添加Propagation.SUPPORTS和不加的区别

有心的小伙伴肯定又注意到了,在Service中处理查询时,我一部分使用了@Transactional(propagation = Propagation.SUPPORTS),一部分查询又没有添加事务,那么这两种方式有什么不一样呢?接下来,我们来揭开神秘的面纱。

  • Propagation.SUPPORTS

    1. /**
    2. * Support a current transaction, execute non-transactionally if none exists.
    3. * Analogous to EJB transaction attribute of the same name.
    4. * <p>Note: For transaction managers with transaction synchronization,
    5. * {@code SUPPORTS} is slightly different from no transaction at all,
    6. * as it defines a transaction scope that synchronization will apply for.
    7. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc)
    8. * will be shared for the entire specified scope. Note that this depends on
    9. * the actual synchronization configuration of the transaction manager.
    10. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
    11. */
    12. SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

    主要关注Support a current transaction, execute non-transactionally if none exists.从字面意思来看,就是如果当前环境有事务,我就加入到当前事务;如果没有事务,我就以非事务的方式执行。从这方面来看,貌似我们加不加这一行其实都没啥差别。

    划重点:NOTE,对于一个带有事务同步的管理器来说,这里有一丢丢的小区别啦。(所以大家在读注释的时候,一定要看这个Note.往往这里面会有好东西给我们,就相当于我们的大喇叭!)

    这个同步事务管理器定义了一个事务同步的一个范围,如果加了这个注解,那么就等同于我让你来管我啦,你里面的资源我想用就可以用(JDBC Connection, Hibernate Session).

结论1

SUPPORTS 标注的方法可以获取和当前事务环境一致的 Connection 或 Session,不使用的话一定是一个新的连接;

再注意下面又一个NOTE,即便上面的配置加入了,但是事务管理器的实际同步配置会影响到真实的执行到底是否会用你。看它的说明:@see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization.

  1. /**
  2. * Set when this transaction manager should activate the thread-bound
  3. * transaction synchronization support. Default is "always".
  4. * <p>Note that transaction synchronization isn't supported for
  5. * multiple concurrent transactions by different transaction managers.
  6. * Only one transaction manager is allowed to activate it at any time.
  7. * @see #SYNCHRONIZATION_ALWAYS
  8. * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION
  9. * @see #SYNCHRONIZATION_NEVER
  10. * @see TransactionSynchronizationManager
  11. * @see TransactionSynchronization
  12. */
  13. public final void setTransactionSynchronization(int transactionSynchronization) {
  14. this.transactionSynchronization = transactionSynchronization;
  15. }

描述信息只是说在同一个事务管理器才能起作用,并没有什么实际意义,我们来看一下TransactionSynchronization具体的内容:

  1. package org.springframework.transaction.support;
  2. import java.io.Flushable;
  3. public interface TransactionSynchronization extends Flushable {
  4. /** Completion status in case of proper commit. */
  5. int STATUS_COMMITTED = 0;
  6. /** Completion status in case of proper rollback. */
  7. int STATUS_ROLLED_BACK = 1;
  8. /** Completion status in case of heuristic mixed completion or system errors. */
  9. int STATUS_UNKNOWN = 2;
  10. /**
  11. * Suspend this synchronization.
  12. * Supposed to unbind resources from TransactionSynchronizationManager if managing any.
  13. * @see TransactionSynchronizationManager#unbindResource
  14. */
  15. default void suspend() {
  16. }
  17. /**
  18. * Resume this synchronization.
  19. * Supposed to rebind resources to TransactionSynchronizationManager if managing any.
  20. * @see TransactionSynchronizationManager#bindResource
  21. */
  22. default void resume() {
  23. }
  24. /**
  25. * Flush the underlying session to the datastore, if applicable:
  26. * for example, a Hibernate/JPA session.
  27. * @see org.springframework.transaction.TransactionStatus#flush()
  28. */
  29. @Override
  30. default void flush() {
  31. }
  32. /**
  33. * ...
  34. */
  35. default void beforeCommit(boolean readOnly) {
  36. }
  37. /**
  38. * ...
  39. */
  40. default void beforeCompletion() {
  41. }
  42. /**
  43. * ...
  44. */
  45. default void afterCommit() {
  46. }
  47. /**
  48. * ...
  49. */
  50. default void afterCompletion(int status) {
  51. }
  52. }

事务管理器可以通过org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization(int)来对当前事务进行行为干预,比如将它设置为1,可以执行事务回调,设置为2,表示出错了,但是如果没有加入PROPAGATION.SUPPORTS注解的话,即便你在当前事务中,你也不能对我进行操作和变更。

结论2

添加PROPAGATION.SUPPORTS之后,当前查询中可以对当前的事务进行设置回调动作,不添加就不行。

源码下载

Github 传送门

Gitee 传送门

下节预告

下一节我们将继续开发商品详情展示以及商品评价业务,在过程中使用到的任何开发组件,我都会通过专门的一节来进行介绍的,兄弟们末慌!

gogogo!

[springboot 开发单体web shop] 8. 商品详情&评价展示的更多相关文章

  1. [springboot 开发单体web shop] 7. 多种形式提供商品列表

    上文回顾 上节 我们实现了仿jd的轮播广告以及商品分类的功能,并且讲解了不同的注入方式,本节我们将继续实现我们的电商主业务,商品信息的展示. 需求分析 首先,在我们开始本节编码之前,我们先来分析一下都 ...

  2. [springboot 开发单体web shop] 1. 前言介绍和环境搭建

    前言介绍和环境搭建 简述 springboot 本身是为了做服务化用的,我们为什么要反其道使用它来开发一份单体web应用呢? 在我们现实的开发工作中,还有大量的业务系统使用的是单体应用,特别是对于中小 ...

  3. [springboot 开发单体web shop] 6. 商品分类和轮播广告展示

    商品分类&轮播广告 因最近又被困在了OSGI技术POC,更新进度有点慢,希望大家不要怪罪哦. 上节 我们实现了登录之后前端的展示,如: 接着,我们来实现左侧分类栏目的功能. ## 商品分类|P ...

  4. [springboot 开发单体web shop] 4. Swagger生成Javadoc

    Swagger生成JavaDoc 在日常的工作中,特别是现在前后端分离模式之下,接口的提供造成了我们前后端开发人员的沟通 成本大量提升,因为沟通不到位,不及时而造成的[撕币]事件都成了日常工作.特别是 ...

  5. [springboot 开发单体web shop] 5. 用户登录及首页展示

    用户登录及前端展示 用户登录 在之前的文章中我们实现了用户注册和验证功能,接下来我们继续实现它的登录,以及登录成功之后要在页面上显示的信息. 接下来,我们来编写代码. 实现service 在com.l ...

  6. [springboot 开发单体web shop] 2. Mybatis Generator 生成common mapper

    Mybatis Generator tool 在我们开启一个新项目的研发后,通常要编写很多的entity/pojo/dto/mapper/dao..., 大多研发兄弟们都会抱怨,为什么我要重复写CRU ...

  7. [springboot 开发单体web shop] 3. 用户注册实现

    目录 用户注册 ## 创建数据库 ## 生成UserMapper ## 编写业务逻辑 ## 编写user service UserServiceImpl#findUserByUserName 说明 U ...

  8. 开发单体web shop] 6. 商品分类和轮播广告展示

    目录 商品分类&轮播广告 商品分类|ProductCategory 需求分析 开发梳理 编码实现 轮播广告|SlideAD 需求分析 开发梳理 编码实现 福利讲解 源码下载 下节预告 商品分类 ...

  9. iOS开发 仿淘宝,京东商品详情3D动画

    - (void)show { [[UIApplication sharedApplication].windows[0] addSubview:self.projectView]; CGRect fr ...

随机推荐

  1. Java学习笔记十二--集合(三)

    第一节课 返回值 方法名 作用 void add(index,elemnet) 在指定的索引处添加元素 object get(index) 返回指定索引处的元素 int indexOf(object) ...

  2. (一)django创建

    1.打开终端,安装django:输入pip install django 2.创建django项目:django-admin startproject myweb 3.启动项目:进入到myweb,输入 ...

  3. Vue学习系列(四)——理解生命周期和钩子

    前言 在上一篇中,我们对平时进行vue开发中遇到的常用指令进行归类说明讲解,大概已经学会了怎么去实现数据绑定,以及实现动态的实现数据展示功能,运用指令,可以更好更快的进行开发.而在这一篇中,我们将通过 ...

  4. JVM内存结构、参数调优和内存泄露分析

    1. JVM内存区域和参数配置 1.1 JVM内存结构 Java堆(Heap) Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都 ...

  5. Centos 7修改hostname浅析

    之前写过一篇博客"深入理解Linux修改hostname",里面总结了RHEL 5.7下面如何修改hostname,当然这篇博客的内容其实也适用于CentOS 6,但是自CentO ...

  6. 务必收藏备用:.net core中通过Json或直接获取图形验证码(数字验证码、字母验证码、混合验证码),有源代码全实战demo(开源代码.net core3.0)

    很多人写的博客大家看了会一知半解,不知道怎么用,应该引用什么类库或者代码不全,这样很多小白很是头疼,尤其是尝新技术更是如此.我们这边不止告诉你步骤,而且还提供开源demo.随着时间的推移,我们的dem ...

  7. DB2中的MQT优化机制详解和实践

    MQT :物化查询表.是以一次查询的结果为基础  定义创建的表(实表),以量取胜(特别是在百万,千万级别的量,效果更显著),可以更快的查询到我们需要的结果.MQT有两种类型,一种是系统维护的MQT , ...

  8. mock和axios常见的传参方式

    第一次接手项目,传参方式还有些吃力,因此做一下总结. 首先我们需要会看swagger中的接口.里面写了某个接口需要接收什么样的值,前端怎么传递这个值 在mock中的传参方式: mock中传参的方式有两 ...

  9. 在虚拟机上的关于FTP FTP访问模式(匿名)

    小知识 nfs和ftp类似另外一款共享软件 用21号端口 传控制 20号端口 传数据  Windows和虚拟机之间 接下来进行实验 首先在yum资源库中下载 输入命令:yum install vsft ...

  10. 第六篇 视觉slam中的优化问题梳理及雅克比推导

    优化问题定义以及求解 通用定义 解决问题的开始一定是定义清楚问题.这里引用g2o的定义. \[ \begin{aligned} \mathbf{F}(\mathbf{x})&=\sum_{k\ ...