乐优商城(五)商品管理

本文详细描述了商品新增、商品分类选择、品牌数据获取、规格参数查询、SKU属性处理以及商品信息提交和修改的前端与后端接口设计,同时介绍了如何通过live-server进行开发和使用Nginx配置实现域名访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 商品新增

1.1 商品新增前端

  1. 进入商品列表,点击新增商品

  2. 出现一个弹窗

里面把商品的数据分为了 4 部分来填写:

基本信息:主要是一些简单的文本数据,包含了 SPU 和 SpuDetail 的部分数据

  • 商品分类:是 Spu 中的 cid1,cid2,cid3 属性
  • 品牌:是 Spu 中的 brandId 属性
  • 标题:是 Spu 中的 title 属性
  • 子标题:是 Spu 中的 subTitle 属性
  • 售后服务:是 SpuDetail 中的 afterService 属性
  • 包装列表:是 SpuDetail 中的 packingList 属性

商品描述:是 SpuDetail 中的 description 属性,数据较多,所以单独放一个页面

规格参数:商品规格信息,对应 SpuDetail 中的 genericSpec 属性

SKU 属性:SPU 下的所有 SKU 信息

1.2 基本信息

1.2.1 商品分类

商品分类的前端

商品分类的查询之前做过了,所以这里点击就能选择商品分类

1.2.2 品牌选择

品牌选择前端

选择分类后,我们需要这个分类下的所有品牌,所以会发送一个根据分类 id 查询品牌的请求

我们找到前端请求品牌数据的代码

由此可以得知:

  • 请求方式:GET
  • 请求路径:brand/cid
  • 请求参数:分类 id,这里用的是 Rest 风格的占位符
  • 返回参数:品牌的集合

后台接口

在 BrandController 中添加 queryBrandsByCid 方法

/**
 * 根据分类 id 查询品牌信息
 * @param cid
 * @return
 */
@GetMapping("/cid/{cid}")
public ResponseEntity<List<Brand>> queryBrandsByCid(@PathVariable("cid") Long cid) {
    List<Brand> brands = brandService.queryBrandsByCid(cid);
    if(CollectionUtils.isEmpty(brands)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(brands);
}

在 BrandService 中添加 queryBrandsByCid 方法

/**
 * 根据分类 id 查询品牌信息
 * @param cid
 * @return
 */
public List<Brand> queryBrandsByCid(Long cid) {
    List<Brand> brands = brandMapper.queryBrandsByCid(cid);
    return brands;
}

在 BrandMapper 中添加 queryBrandsByCid 方法

/**
 * 根据分类 id 查询品牌信息
 * @param cid
 * @return
 */
@Select("SELECT * FROM tb_brand WHERE id IN (SELECT brand_id FROM tb_category_brand WHERE category_id = #{cid})")
List<Brand> queryBrandsByCid(Long cid);

测试

成功得到手机分类下的所有品牌

1.2.3 其他文本框

剩下的都是普通的文本框,直接填写即可

1.3 商品描述

商品描述经常需要图文并茂,甚至视频,所以这里使用了一个富文本编辑器 Vue-Quill-Editor,它支持文字、图片、视频等功能。

1.4 规格参数

规格参数前端

选择分类后,我们需要这个分类下的所有规格参数,所以会发送一个根据分类 id 查询规格参数的请求

我们找到前端请求规格参数数据的代码

由此可以得知:

  • 请求方式:GET
  • 请求路径:spec/params
  • 请求参数:分类 id
  • 返回参数:规格参数集合

后台接口

前面在 SpecificationController 中已经写过一个 querySpecParams 方法,路径为 spec/params,参数为规格组 id,如下

/**
 * 根据条件查询规格参数
 *
 * @param gid
 * @return
 */
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> querySpecParams(@RequestParam("gid") Long gid) {
    List<SpecParam> params = specificationService.querySpecParams(gid);
    if (CollectionUtils.isEmpty(params)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(params);
}

所以我们就不用再写新的方法了,直接在 querySpecParams 方法中增加分类 id 参数。考虑到以后可能还会根据是否搜索、是否为通用属性等条件过滤,我们多添加几个过滤条件

/**
 * 根据条件查询规格参数
 *
 * @param gid
 * @return
 */
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> querySpecParams(
    @RequestParam(value = "gid", required = false) Long gid,
    @RequestParam(value = "cid", required = false) Long cid,
    @RequestParam(value = "generic", required = false) Boolean generic,
    @RequestParam(value = "searching", required = false) Boolean searching
) {
    List<SpecParam> params = specificationService.querySpecParams(gid, cid, generic, searching);
    if (CollectionUtils.isEmpty(params)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(params);
}

修改 SpecificationService 中的 querySpecParams 方法

/**
 * 根据条件查询规格参数
 * @param gid
 * @return
 */
public List<SpecParam> querySpecParams(Long gid,Long cid, Boolean generic, Boolean searching) {
    SpecParam specParam = new SpecParam();
    specParam.setGroupId(gid);
    specParam.setCid(cid);
    specParam.setGeneric(generic);
    specParam.setSearching(searching);
    List<SpecParam> params = specParamMapper.select(specParam);
    return params;
}

测试

成功得到规格参数,这部分规格参数是 sku 通用的属性,即 tb_spec_param 中 generic 为 1 的字段。

1.5 SKU 属性

成功得到 sku 属性,这部分规格参数是 sku 特有的属性,即 tb_spec_param 中 generic 为 0 的字段。

当我们填写一些 sku 属性后,会在页面下方生成一个 sku 表格,可以添加价格、库存、是否启用等

1.6 提交商品信息

1.6.1 提交表单前端

填写完所有商品信息后,只剩下最后一步了,那就是提交商品信息

点击提交商品信息后,可以看到发送给了一个提交商品信息的请求

再来看看请求参数

整体是一个 JSON 格式数据,包含了 tb_spu、tb_spu_detail、tb_sku、tb_stock 四张表的数据

  • brandId:品牌 id
  • cid1、cid2、cid3:商品分类 id
  • subTitle:副标题
  • title:标题
  • spuDetail:
    • afterService:售后服务
    • description:商品描述
    • packingList:包装列表
    • specialSpec:sku规格属性模板
    • genericSpec:通用规格参数
  • skus:
    • sku:
      • title:标题
      • images:图片
      • price:价格
      • stock:库存
      • ownSpec:特有规格参数
      • indexes:特有规格参数的下标

我们找到前端提交商品信息请求的代码

由此可以得知:

  • 请求方式:POST
  • 请求路径:spu
  • 请求参数:商品信息,JSON 格式
  • 返回参数:无
1.6.2 后台接口

实体类

在 leyou-item-interface 项目中添加实体类 Sku

@Table(name = "tb_sku")
public class Sku {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long spuId;
    private String title;
    private String images;
    private Long price;
    private String ownSpec;// 商品特殊规格的键值对
    private String indexes;// 商品特殊规格的下标
    private Boolean enable;// 是否有效,逻辑删除用
    private Date createTime;// 创建时间
    private Date lastUpdateTime;// 最后修改时间
    @Transient
    private Integer stock;// 库存
    
    // getter、setter、toString 方法省略
}

在 leyou-item-interface 项目中添加实体类 Stock

@Table(name = "tb_stock")
public class Stock {
    @Id
    private Long skuId;
    private Integer seckillStock;// 秒杀可用库存
    private Integer seckillTotal;// 已秒杀数量
    private Integer stock;// 正常库存
    
    // getter、setter、toString 方法省略
}

在实体类 SpuBo 中添加 spuDetail、skus 属性,用来封装前端提交过来的商品信息

public class SpuBo extends Spu{
    private String cname;
    private String bname;
    private SpuDetail spuDetail;
    private List<Sku> skus;
	
    // getter、setter、toString 方法省略
}

Mapper

在 leyou-item-service 项目中添加 SkuMapper

public interface SkuMapper extends Mapper<Sku> {
}

在 leyou-item-service 项目中添加 StockMapper

public interface StockMapper extends Mapper<Stock> {
}

Controller

在 SpuController 中添加 saveSpu 方法

/**
 * 新增商品
 * @param spuBo
 * @return
 */
@PostMapping
public ResponseEntity<Void> saveSpu(@RequestBody SpuBo spuBo) {
    spuService.saveSpu(spuBo);
    return ResponseEntity.status(HttpStatus.CREATED).build();
}

Service

在 SpuService 中添加 saveSpu 方法

/**
 * 新增商品
 * @param spuBo
 * @return
 */
@Transactional
public void saveSpu(SpuBo spuBo) {
    // 先新增 Spu
    spuBo.setId(null);
    spuBo.setSaleable(true);
    spuBo.setValid(true);
    spuBo.setCreateTime(new Date());
    spuBo.setLastUpdateTime(spuBo.getCreateTime());
    spuMapper.insertSelective(spuBo);

    // 再新增 SpuDetail
    SpuDetail spuDetail = spuBo.getSpuDetail();
    spuDetail.setSpuId(spuBo.getId());
    spuDetailMapper.insertSelective(spuDetail);

    // 再新增 Sku
    List<Sku> skus = spuBo.getSkus();
    for (Sku sku : skus) {
        sku.setId(null);
        sku.setSpuId(spuBo.getId());
        sku.setCreateTime(new Date());
        sku.setLastUpdateTime(sku.getCreateTime());
        skuMapper.insertSelective(sku);
        // 新增 Stock
        Stock stock = new Stock();
        stock.setSkuId(sku.getId());
        stock.setStock(sku.getStock());
        stockMapper.insertSelective(stock);
    }
}
1.6.3 测试

看到新增的商品了

2. 商品修改

商品修改需要以下步骤:

  1. 将商品信息回显到页面
  2. 修改商品信息
  3. 提交修改

2.1 商品回显

2.1.1 商品回显前端

点击修改按钮就会把商品信息回显到页面上

我们找到前端商品信息回显请求的代码

由此可知,发送了两个请求:

  • 查询 spuDetail
    • 请求方式:GET
    • 请求路径:spu/detail
    • 请求参数:spuId,这里用的是 Rest 风格的占位符
    • 返回参数:SpuDetail
  • 查询 Sku 集合
    • 请求方式:GET
    • 请求路径:spu/sku/list
    • 请求参数:spuId
    • 返回参数:Sku 集合
2.1.2 后台接口

Controller

在 SpuController 中添加 querySpuDetailBySpuId 方法

/**
 * 通过 spuId 查询 SpuDetail
 * @param spuId
 * @return
 */
@GetMapping("/detail/{spuId}")
public ResponseEntity<SpuDetail> querySpuDetailBySpuId(@PathVariable("spuId") Long spuId) {
    SpuDetail spuDetail = spuService.querySpuDetailBySpuId(spuId);
    if (spuDetail == null) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(spuDetail);
}

在 SpuController 中添加 querySkusBySpuId 方法

/**
 * 通过 spuId 查询 Sku 集合
 * @param spuId
 * @return
 */
@GetMapping("/sku/list")
public ResponseEntity<List<Sku>> querySkusBySpuId(@RequestParam("id") Long spuId) {
    List<Sku> skus = spuService.querySkusBySpuId(spuId);
    if(CollectionUtils.isEmpty(skus)) {
        return ResponseEntity.notFound().build();
    }
    return ResponseEntity.ok(skus);
}

Service

在 SpuService 中添加 querySpuDetailBySpuId 方法

/**
 * 通过 spuId 查询 SpuDetail
 * @param spuId
 * @return
 */
public SpuDetail querySpuDetailBySpuId(Long spuId) {
    SpuDetail spuDetail = spuDetailMapper.selectByPrimaryKey(spuId);
    return spuDetail;
}

在 SpuService 中添加 querySkusBySpuId 方法

/**
 * 通过 spuId 查询 Sku 集合
 * @param spuId
 * @return
 */
public List<Sku> querySkusBySpuId(Long spuId) {
    Sku sku = new Sku();
    sku.setSpuId(spuId);
    List<Sku> skus = skuMapper.select(sku);
    for (Sku sku1 : skus) {
        Stock stock = stockMapper.selectByPrimaryKey(sku1.getId());
        sku1.setStock(stock.getStock());
    }
    return skus;
}
2.1.3 测试

商品信息回显成功

2.2 提交修改

2.2.1 提交表单前端

我们找到前端提交商品信息请求的代码,商品新增时是 POST 请求,商品修改是 PUT 请求

由此可以得知:

  • 请求方式:PUT
  • 请求路径:spu
  • 请求参数:商品信息,JSON 格式
  • 返回参数:无
2.2.2 后台接口

Controller

在 SpuController 中添加 updateSpu 方法

/**
 * 更新商品
 * @param spuBo
 * @return
 */
@PutMapping
public ResponseEntity<Void> updateSpu(@RequestBody SpuBo spuBo) {
    spuService.updateSpu(spuBo);
    return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}

Service

在 SpuService 中添加 updateSpu 方法

/**
 * 更新商品
 * @param spuBo
 * @return
 */
@Transactional
public void updateSpu(SpuBo spuBo) {
    // 获取要删除的 Sku
    Sku sku = new Sku();
    sku.setSpuId(spuBo.getId());
    List<Sku> skus = skuMapper.select(sku);

    // 删除原来的 Stock
    for (Sku sku1 : skus) {
        Stock stock = new Stock();
        stock.setSkuId(sku1.getId());
        stockMapper.delete(stock);
    }

    // 删除原来的 Sku
    skuMapper.delete(sku);

    // 新增 Sku 和 Stock
    saveSkuAndStock(spuBo);

    // 更新 Spu
    spuBo.setCreateTime(null);
    spuBo.setLastUpdateTime(new Date());
    spuBo.setValid(null);
    spuBo.setSaleable(null);
    spuMapper.updateByPrimaryKeySelective(spuBo);

    // 更新 SpuDetail
    spuDetailMapper.updateByPrimaryKeySelective(spuBo.getSpuDetail());
}

新增 Sku 和 Stock 的代码之前写过了,为了避免代码冗余,所以抽取成 saveSkuAndStock 方法

/**
 * 新增 Sku 和 Stock
 * @param spuBo
 */
private void saveSkuAndStock(SpuBo spuBo) {
    // 再新增 Sku
    List<Sku> skus = spuBo.getSkus();
    for (Sku sku : skus) {
        sku.setId(null);
        sku.setSpuId(spuBo.getId());
        sku.setCreateTime(new Date());
        sku.setLastUpdateTime(sku.getCreateTime());
        skuMapper.insertSelective(sku);
        // 新增 Stock
        Stock stock = new Stock();
        stock.setSkuId(sku.getId());
        stock.setStock(sku.getStock());
        stockMapper.insertSelective(stock);
    }
}
2.2.3 测试

把小米 10 修改为小米 11

3. 搭建门户系统

后台系统的内容暂时告一段落,有了商品,接下来我们就要在页面展示商品,给用户提供浏览和购买的入口,那就是我们的门户系统。

门户系统面向的是用户,安全性很重要,而且搜索引擎对于单页应用并不友好。因此我们的门户系统不再采用与后台系统类似的 SPA(单页应用),而是采用多页应用,不过依旧是前后端分离。

3.1 创建工程

3.2 导入静态资源

将 leyou-portal 解压并复制到工程中

3.3 live-server

webpack 打包多页应用配置比较繁琐,所以我们使用另一种热部署的方式:live-server

3.3.1 live-server 简介

live-server 是一款带有热加载功能的小型开发服务器。用它来展示你的 HTML / JavaScript / CSS,但不能用于部署最终的网站。

3.3.2 安装 live-server

在 terminal 中输入以下命令:

npm install -g live-server

3.4 启动项目

在 terminal 中输入以下命令:

live-server --port=9002

启动成功

3.5 域名访问

现在访问:http://127.0.0.1:9002/

域名访问:http://www.leyou.com

接下来我们就来实现域名访问:

  1. 配置 host 文件
127.0.0.1 www.leyou.com

2.在 nginx.conf 添加以下配置,实现将 www.leyou.com 反向代理到 http://127.0.0.1:9002

server {
    listen       80;
    server_name  www.leyou.com;

    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        proxy_pass http://127.0.0.1:9002;
        proxy_connect_timeout 600;
        proxy_read_timeout 600;
    }
}

重新加载 nginx

nginx.exe -s reload

成功通过域名访问

自己做的乐优商城的XMIND文件,学习分享下。乐优商城 搭建父工程 pom.xml 添加依赖 springCloud mybatis启动器 通用Mapper启动器 mysql驱动 分页助手启动器 FastDFS客户端 其他配置 构建设置 环境设置 EurekaServer注册中心 添加的依赖 启动类 application.yml 创建Zuul网关 依赖 启动类 application.yml 创建商品微服务 ly-item-interface:主要是对外暴露的API接口及相关实体类 ly-item-service:所有业务逻辑及内部使用接口 创建父工程ly-item ly-item-interface ly-item-service 依赖 启动器 application.yml 添加商品微服务的路由规则 通用工具模块Common utils CookieUtils IdWorker JsonUtils NumberUtils 依赖 通用异常处理 测试结构 pojo service @Service web @RestController @RequestMapping @Autowired @PostMapping 引入Common依赖 Common advice 拦截异常、 CommonExceptionHandler ResponseEntity @ControllerAdvice @ExceptionHandler enums 异常的枚举 、ExceptionEnum exception 自定义异常、LyException 接口RuntimeException @Getter @NoArgsConstructor @AllArgsConstructor vo 异常结果处理对象、ExceptionResult @Data 构造方法ExceptionResult ly-item-service CategoryQuery 分类查询 实体类 @Table(name="tb_category") 声明此对象映射到数据库的数据表,通过它可以为实体指定表(talbe) @Data 注解在类上, 为类提供读写属性, 此外还提供了 equals()、hashCode()、toString() 方法 @Id & @GeneratedValue(strategy= GenerationType.IDENTITY) 自动增长,适用于支持自增字段的数据库 mapper Mapper IdListMapper 根据id操作数据库 @RequestMapping("category") Controller @RestController @Controller 处理HTTP请求 @ResponseBody 返回 json 数据 @GetMapping("list") ResponseEntity @ResponseBody可以直接返回Json结果 不仅可以返回json结果,还可以定义返回的HttpHeaders和HttpStatus service @Service 自动注册到Spring容器,不需要再在applicationContext.xml文件定义bean了 @Autowired 它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 select select * from category c where c.pid = #{pid} CollectionUtils.isnotblank 判断集合是否为空 测试 可以利用url直接查询数据库,能否访问得到数据 报错 启动类 没有扫描到 @MapperScan("com.leyou.item.mapper") ,目录结构关系 访问网页报错 CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 跨域问题 浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同域名的路径,这能有效的阻止跨站攻击。因此:跨域问题 是针对ajax的一种限制。 解决跨域问题的方案 CORS 规范化的跨域请求解决方案,安全可靠 什么是cors 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。 原理 简单请求 当浏览器发现发现的ajax请求是简单请求时,会在请求头中携带一个字段:Origin 如果服务器允许跨域,需要在返回的响应头中携带下面信息 Access-Control-Allow-Origin:可接受的域,是一个具体域名或者*,代表任意 Access-Control-Allow-Credentials:是否允许携带cookie,默认情况下,cors不会携带cookie,除非这个值是true 实现非常简单 gateway网关中编写一个配置类 GlobalCorsConfig 添加CORS配置信息 允许的域,不要写*,否则cookie就无法使用了 是否发送Cookie信息 允许的请求方式 允许的头信息 有效时长 添加映射路径,我们拦截一切请求 返回新的CorsFilter 提交方式 GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源 BUG 分类不能打开,当添加后却能打开。 修改一天的BUG 最后发现是实体类里属性大小写的问题引起。 注意 Bule_bird 就必须写成 BlueBird Brand 查询 实体类 PageResult 响应结果 分页结果一般至少需要两个数据 总条数 total 当前页数据 items 有些还需要总页数 总页数 totalPage Controller @RequestParam(value = "page",defaultValue = "1") Integer page GET和POST请求传的参数会自动转换赋值到@RequestParam 所注解的变量上 defaultValue 默认值 required 默认值为true , 当为false时 这个注解可以不传这个参数 null || .size()==0 ResponseEntity(HttpStatus.NOT_FOUND) 返回404没找到 ResponseEntity.ok 返回ok状态 service 开始分页 通用分页拦截器 PageHelper.startPage(page,row); 过滤 Example查询 Example example = new Example(Brand.class); mybatis的逆向工程中会生成实例及实例对应的example,example用于添加条件,相当where后面的部分 xxxExample example = new xxxExample(); Criteria criteria = new Example().createCriteria(); StringUtils.isNotBlank isNotBlank(str) 等价于 str != null && str.length > 0 && str.trim().length > 0 str.trim() 去掉字符串头尾的空格 测试 报错500 com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'idASC' in 'order clause' 错误:(desc ? "DESC" : "ASC"); 正确:(desc ? " DESC" : " ASC"); 字符串空格问题 新增 Controller (Brand brand,@RequestParam("cids") List cids) ResponseEntity 无返回值 new ResponseEntity(HttpStatus.CREATED); 201成功 service @Transactional 自动纳入 Spring 的事务管理 使用默认配置,抛出异常之后,事务会自动回滚,数据不会插入到数据库。 setId(null) insert(brand) 新增中间表 mapper @Insert (#{cid},#{bid}) @Param 表示给参数命名,名称就是括号中的内容 name 命名为aa,然后sql语句....where s_name= #{aa} 中就可以根据aa得到参数值 修改 回显 Controller @PathVariable("bid") 通过 @PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中:URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中。 select * from tb_category where id in (select category_id from tb_category_brand where brand_id = #{bid}) 测试 报错500 空指针异常 调用Service时候 忘记@Autowired 保存 VO视图对象 @NoArgsConstructor 生成一个无参数的构造方法 @AllArgsConstructor 会生成一个包含所有变量 Controller @PutMapping 添加信息,倾向于用@PostMapping,如果是更新信息,倾向于用@PutMapping。两者差别不是很明显 return ResponseEntity.ok().build(); 无返回值 service 根据id修改 先删除后新增 删除(前端有问题,待完善) spec Group 品牌分类id查询 实体类 @Transient 指定该属性或字段不是永久的。 它用于注释实体类,映射超类或可嵌入类的属性或字段。 @Column(name = "'numeric'") 用来标识实体类中属性与数据表中字段的对应关系 name 定义了被标注字段在数据库表中所对应字段的名称; mapper service Controller 测试 报错500 实体类@table路径写错 新增 Controller @RequestBody 常用其来处理application/json类型 子主题 2 将请求体中的JSON字符串绑定到相应的bean上 修改 Controller @PutMapping service updateByPrimaryKey 删除 Controller @DeleteMapping @PathVariable Param 规格组id查询规格 url:params?gid=14 @GetMapping("params") Controller @RequestParam 新增 @PostMapping("param") @RequestBody ResponseEntity.status(HttpStatus.CREATED).build(); 修改 @RequestBody 删除 @PathVariable 分支主题 3 遇到的问题 pom.xml 文件未下载完整,删掉后重新下载 能存在重复文件,IDEA不能确定文件路径,需要搜索删掉多余的 Param 删除 小问题:数据库删除后页面没有立即显示 Brand 删除(前端有问题,待完善)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值