Day134-136.尚品汇:平台属性接口、SPU、跨域问题、配置持久化、MinIO 分布式文件存储系统

目录

Day 02 商品后台管理系统

1. 商品基本知识

2. 回顾Mybatis 

3. 添加平台属性接口 (多表查询)

Day 03 完成后台平台属性管理、SPU

1. 修改平台属性

2. gateway网关 

3. 跨域问题 

4. Nacos 配置中心,持久化配置 

5. spu相关业务介绍 

6. 根据三级分类Id 查询spu 列表

Day 04 制作SKU

1. 回顾 (表结构、Redis、JVM)

2. 品牌CRUD \ 三级分类ID与品牌关系CRUD

3. MinIO 分布式文件存储系统


Day 02 商品后台管理系统

1. 商品基本知识

商品详情页面:销售属性,讲解spu时会说,作用:给用户提供自己喜欢的商品

SKU:每种商品均对应有唯一的SKU编号。(具体到唯一商品)

SPU:一组可复用,易检索的标准化信息集合。 (手机)

分类:

basecategory1

basecategory2

basecategory3

平台属性:

baseAttrInfo: -- categoryId  categoryLeve1

baseAttrValue

2. 回顾Mybatis 

1. 如何配置1对n?多对1? 在实体类增加List属性,封装n的数据,

xml中 用 <collection> 配置

2. 两个重要的配置文件

映射文件:aseAttrInfoMapper.xml 配置方法的名称以及方法的实现

核心配置文件:mybatis-cfg.xml    配置的数据源以及读取那个配置映射文件

<mappers>
	<mapper resource=/com/atguigu/mapper/BaseAttrInfoMapper.xml />
	<mapper resource=/com/atguigu/mapper/BaseAttrInfoMapper.xml />
	
	<mapper package = "com.atguigu.mapper"/>
</mappers>

mybatis 默认有个规定:
BaseAttrInfoMapper.java 文件与 BaseAttrInfoMapper.xml 通常在一起 (可以包扫描)

BaseAttrInfoMapper.java 中定义接口,
BaseAttrInfoMapper.xml 是接口的实现类

spring boot 整合mybatis-plus!
mybatis-plus:

3. mybatis 在传递多个参数的时候,建议使用@Param()注解标识用#{}填充占位符,

4. mybatis 的动态sql 标签

<if> : 进行条件的判断

<where>:自动添加where,处理 and、or 连接符

<trim>:在SQL语句前后添加前缀,后缀

<set>:  修改操作时出现的逗号问题

<choose> <when> <otherwise>:类似于switch,所有的条件中选择其一

<foreach>:循环遍历;可通过拼装SQL完成批量操作

5. 全注解开发可读性差,不容易维护,开发的时候建议写 xxx.xml

6. 有关于mybatis 的相关源码:底层源码用的cglib动态代理
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.java);

7. Mybatis如何生成自增主键值

 a. 实体类id上加入注解属性 @TableId(type = IdType.AUTO)

 b. xml 在<insert>标签中使用 useGeneratedKeys 和 keyProperty 两个属性来获取自动生成的主键值

3. 添加平台属性接口 (多表查询)

思路:
平台属性对象 ---{平台属性值集合}

1. 先获取页面传递的过来的数据,Json格式

2. 考虑在后台如何接收Json!
    使用spring mvc 常用注解:
        @RequestBody    --- Json ---->javaObject        
3  调用服务层方法保存数据!
    数据库表:baseAttrInfo , baseAttrValue   

多表插入时使用事务 @Transactional(rollbackFor = Exception.class) //当发生异常直接回滚
4. debug:
    a. 在控制器断点 有效代码!
            如果根本不进入断点,说明mapping路径有问题!
    b. 进入实现类
            直接打断点!
                F8    单行调试,也可以直接进入下一个断点
                F7  进入方法内部,单步调试
            如果出现错误,单独调试这一行代码即可!
JSON格式报错:Caused by: com.fasterxml.jackson.core.JsonParseException: Unexpected character ('}' (code 125)): 

@Service
public class ManageServiceImpl implements ManageService {

    // select * from base_category1 where is deleted = 0
    // Mybatis-Plus执行上述sql语句,mapper封装了单表的crud(泛型对应的表)
    @Autowired
    private BaseCategory1Mapper baseCategory1Mapper;
    @Autowired
    private BaseCategory2Mapper baseCategory2Mapper;
    @Autowired
    private BaseCategory3Mapper baseCategory3Mapper;
    @Autowired
    private BaseAttrInfoMapper baseAttrInfoMapper;
    @Autowired
    private BaseAttrValueMapper baseAttrValueMapper;

    //查询一级分类id数据
    @Override
    public List<BaseCategory1> getCategory1() {
        return baseCategory1Mapper.selectList(null);
    }

    //根据以及分类id查询二级分类数据
    @Override
    public List<BaseCategory2> getCategory2(Long category1Id) {
        return baseCategory2Mapper.selectList(new QueryWrapper<BaseCategory2>().eq("category1_id",category1Id));
    }

    //根据二级分类id查询三级分类数据
    @Override
    public List<BaseCategory3> getCategory3(Long category2Id) {
        return baseCategory3Mapper.selectList(new QueryWrapper<BaseCategory3>().eq("category2_id",category2Id));
    }

    //select * from ... where category2_id = ?
    //根据分类id查询平台属性
    @Override
    public List<BaseAttrInfo> getAttrInfoList(Long category1Id, Long category2Id, Long category3Id) {
        return baseAttrInfoMapper.selectAttrInfoList(category1Id, category2Id, category3Id);
    }
    //保存平台属性方法
    @Override
    @Transactional(rollbackFor = Exception.class) //当发生异常直接回滚
    public void saveAttrInfo(BaseAttrInfo baseAttrInfo) {
        // 1.插入数据库 base_attr_info
        baseAttrInfoMapper.insert(baseAttrInfo);
        // 2.从baseAttrInfo 获取平台属性值集合!
        List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();
        // 3.遍历循环,插入数据库 base_attr_value
        if(!CollectionUtils.isEmpty(attrValueList)){
            attrValueList.forEach(baseAttrValue -> {
                // attr_id = base_attr_info.id ,界面传递时没有传递attr_id,
                // baseAttrInfo.getId() 为什么能获取主键值? 实体类 type = IdType.AUTO 能够自动获取自增主键
                // 前提: 这个表必须先执行insert
                baseAttrValue.setAttrId(baseAttrInfo.getId());
                baseAttrValueMapper.insert(baseAttrValue);
            });
        }
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper SYSTEM "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--namespace 定义接口的全路径-->
<mapper namespace="com.atguigu.gmall.product.mapper.BaseAttrInfoMapper">

    <!--
        resultMap: 表示返回自定义的映射结果集 - 将数据库中的字段与实体类的属性关联
        id: 表示唯一标识
        type: 这个返回结果集的数据类型 : 判断方法 集合的泛型
        autoMapping: 表示自动映射,按照驼峰命名法自动封装
    -->
    <resultMap id="baseAttrInfoMap" type="com.atguigu.gmall.model.product.BaseAttrInfo"
               autoMapping="true">
        <!--id:主键 property:实体类的属性名 column:数据库字段名{包括字段起的别名} result:普通字段-->
        <id property="id" column="id"></id>

        <collection property="attrValueList" ofType="com.atguigu.gmall.model.product.BaseAttrValue">
            <!--实体键的主键id可以重复,但是sql语句映射出来的别名不能重复 需要别名-->
            <id property="id" column="attr_value_id"></id>
        </collection>
    </resultMap>

    <!--哪个方法的实现
        id: 表示方法名
        resultMap: 表示返回自定义的映射结果集 通常配置1:n 获取其他较为复杂关系
        resultType: 表示返回具体的数据类型 表示中返回单个实体类的时候,或者明确具体是什么类型
        3.mybatis 在执行SQL时,;不要加,order by 会出错
        4.#(占位符) and $(字符串拼接,注入问题) $在order by 排序字段时使用
        5.mybatis 在传递多个参数时,建议使用@Param注解表示
        6.mybatis 的动态sql标签
    -->
    <select id="selectAttrInfoList" resultMap="baseAttrInfoMap">
        select bai.id,
               bai.attr_name,
               bai.category_id,
               bai.category_level,
               bav.id attr_value_id,
               bav.value_name,
               bav.attr_id
        from base_attr_info bai join
             base_attr_value bav on bai.id = bav.attr_id
        <where>
            <trim prefix="(" suffix=")">
                <if test="category1Id!=null and category1Id !=0">
                    (bai.category_id = #{category1Id} and bai.category_level = 1)
                </if>
                <if test="category2Id!=null and category2Id !=0">
                    or (bai.category_id = #{category2Id} and bai.category_level = 2)
                </if>
                <if test="category3Id!=null and category3Id !=0">
                    or (bai.category_id = #{category3Id} and bai.category_level = 3)
                </if>
            </trim>
        </where>
        and bai.is_deleted = 0
        and bav.is_deleted = 0
        order by bai.id,bav.id
    </select>

</mapper>

Day 03 完成后台平台属性管理、SPU

数据持久化:

1. 修改平台属性

前端回显数据

baseAttrInfo    ---    直接执行update 语句,因为这个id 可以确定!
baseAttrValue    --- 不能执行update 语句,无法确定用户的具体操作

策略:先逻辑删除 ,再新增
缺点:会导致一部分 id 号段无效!

    //保存-修改平台属性方法
    @Override
    @Transactional(rollbackFor = Exception.class) //当发生异常直接回滚
    public void saveAttrInfo(BaseAttrInfo baseAttrInfo) {
        //判断什么时候修改,什么时候新增
        if(baseAttrInfo.getId()!=null){
            //修改数据 base_attr_info
            baseAttrInfoMapper.updateById(baseAttrInfo);
            //修改平台属性值需要先删除数据,逻辑删除本质是修改
            QueryWrapper<BaseAttrValue> wrapper = new QueryWrapper<>();
            wrapper.eq("attr_id",baseAttrInfo.getId());
            baseAttrValueMapper.delete(wrapper);
        }else {
            //新增
            // 1.保存数据 base_attr_info
            baseAttrInfoMapper.insert(baseAttrInfo);
        }
        // 2.从baseAttrInfo 获取平台属性值集合,遍历循环,插入数据库
        List<BaseAttrValue> attrValueList = baseAttrInfo.getAttrValueList();
        if(!CollectionUtils.isEmpty(attrValueList)){
            attrValueList.forEach(baseAttrValue -> {
                // attr_id = base_attr_info.id ,界面传递时没有传递attr_id,
                // baseAttrInfo.getId() 为什么能获取主键值? 实体类 type = IdType.AUTO 能够自动获取自增主键
                // 前提: 这个表必须先执行insert
                baseAttrValue.setAttrId(baseAttrInfo.getId());
                baseAttrValueMapper.insert(baseAttrValue);
            });
        }
    }

2. gateway网关 

service-product 与 后台vue 项目进行整合

后台无法获取数据,和端口号有关系
访问一级分类数据:
vue url:      http://localhost/admin/product/getCategory1  默认80    没有数据:
controller  :http://localhost:8206/admin/product/getCategory1        有数据:

所以需要加入网关,用户发起数据访问的时候默认访问gateway : 80,由网关根据路径:找到 service-product 8206

server:
  port: 80
spring:
  redis:
    host: 192.168.86.90
    port: 6379
    database: 0
    timeout: 1800000
    password:
  cloud:
    gateway:
      discovery:      #是否与服务发现组件进行结合,通过 serviceId(必须设置成大写) 转发到具体的服务实例。默认为false,设为true便开启通过服务中心的自动根据 serviceId 创建路由的功能。
        locator:      #路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,其中微服务应用名默认大写访问。
          enabled: true
      routes:
        - id: service-product
          uri: lb://service-product
          predicates:
            - Path=/*/product/** # 路径匹配

3. 跨域问题 

创建网关项目后,访问请求路径有数据了,但vue 项目依然访问失败
(Ctrl + f5 清空浏览器缓存)
跨域问题:请求的域名、端口、协议不同都会引发跨域问题 (http和https)

为了防止跨站攻击,浏览器对于ajax请求的一种安全限制:只能是与当前页域名相同的路径,这能有效的阻止跨站攻击。

解决跨域问题

1. jsonp(杰森):最早的解决方式, 只支持 GET 请求

2. nginx:反向代理,但是 在nginx.conf 配置不易理解,语义不清,不方便后期维护

3. springMVC提供@CrossOrigin 注解,只支持GET请求

4. cors:跨域资源共享  (原理:预检请求,预检响应)
        预检请求: 客户端
            OPTIONS /cors HTTP/1.1
            Origin: http://localhost:1000
            Access-Control-Request-Method: GET
            Access-Control-Request-Headers: X-Custom-Header
            User-Agent: Mozilla/5.0... 

        预检响应: 服务器端设置的
            Access-Control-Allow-Origin: http://localhost:1000
            Access-Control-Allow-Credentials: true
            Access-Control-Allow-Methods: GET, POST, PUT
            Access-Control-Allow-Headers: X-Custom-Header
            Access-Control-Max-Age: 1728000

4. Nacos 配置中心,持久化配置 

3. 持久化配置,Nacos配置中心,本质是将配置写在数据库里

nacos 做配置中心,并且能够持久化,将配置中心写入Mysql,表名config_info
1. 依赖jar 包!
    server-gateway ,service-product 解开注释的jar 
2. 重新创建一个容器,并指定好 nacos 读取的数据库
        先创建一个nacos 数据库,导入nacos.sql 文件!
        docker run -d  \
            -e MODE=standalone  \
            -e PREFER_HOST_MODE=hostname  \
            -e SPRING_DATASOURCE_PLATFORM=mysql  \
            -e MYSQL_SERVICE_HOST=192.168.200.129  \
            -e MYSQL_SERVICE_PORT=3306  \
            -e MYSQL_SERVICE_USER=root  \
            -e MYSQL_SERVICE_PASSWORD=root  \
            -e MYSQL_SERVICE_DB_NAME=nacos  \
            -p 8848:8848  \
            --name nacos  \
            --restart=always  \
            nacos/nacos-server:1.4.1
3. 在 nacos 中添加配置
    抽象出一个公共的配置文件,编写对应的项目配置文件

4. 修改配置文件指定读取nacos 的配置

5. spu相关业务介绍 

制作SPU!
业务介绍:
SPU : 一组可复用,易检索的标准化信息集合,这个描述了产品的特性!

SPU: 手机

华为手机,小米手机,锤子手机,vivo 手机
华为,小米,锤子,vivo: 品牌

SPU 红旗--手机
销售属性:
颜色:红色,绿色
版本:256G + 1T  512G + 1.5T

SPU相关表结构

商品和品牌什么关系?多对多;平时检索的时候,看到的是一对多

多对多关系如何建表?

三级分类Id 下有很多spu,spu 又对应很多品牌。我们可以通过三级分类Id和品牌id将两张表关联起来,建立多对多关系。

6. 根据三级分类Id 查询spu 列表

http://api.gmall.com/admin/product/1/10?category3Id=61
        如何获取category3Id?
            1.HttpServletRequest.getParameter("category3Id");
            2.@RequestParam Long category3Id
            3.象属传值 如果页面提交过来的参数与实体类的参数一致,自动注入

mybatis-plus 提供了一个分页对象

page 对象,只封装了current,size,total等 基本属性

IPage 对象,封装了更多信息,可调用getRecords() 得到分页数据

    @ApiOperation("根据三级分类id查询spu列表")
    @GetMapping("{page}/{size}")
    public Result getSpuInfoPage(@PathVariable Long page,
                                 @PathVariable Long size,
                                 SpuInfo spuInfo){
        // mybatis-plus 提供了一个分页对象
        // select * from sku_info where 如何获取category3_id = ? limit(?,?)
        Page<SpuInfo> spuInfoPage = new Page<>(page,size);
        IPage<SpuInfo> spuInfoPageList = manageService.getSpuInfoPage(spuInfoPage, spuInfo);
        return Result.ok(spuInfoPageList);
    }

BUG 可能自己都能解决,遇到的大多是环境问题

1. 先确定是不是IDEA问题;外部用text手写一段Java代码,放在工程中判断是否乱码。

2. 和公司磁盘加密有关,换个盼复就好了 (把每个文件都编译成二进制了)

Day 04 制作SKU

1. 回顾 (表结构、Redis、JVM)

相关表结构

三级分类:
    baseCategory1;
    baseCategory2;
    baseCategory3;
平台属性:
    baseAttrInfo;
    baseAttrValue;
SPU:
    spuInfo;    商品表
    spuImage;    

    spuPoster;
    spuSaleAttr;
    spuSaleAttrValue;
    baseSaleAttr;
    baseTradeMark;
    baseCategoryTradeMark;    

redis 是什么?

redis 五种数据类型

项目中采用redis 做缓存的原因是

redis 优点:快  (什么是多路io复用)

项目中是如何应用的?

        单节点:aof,rdb

        集群:集群原理 16384/key crc32 算法:数值 ==》key value 存储在哪组 (集群为什么三主三从?)

        主从复制:

可能出现的问题: 缓存击穿,缓存穿透,缓存雪崩

mysql redis 树同步问题?

本地锁:sync lock

分布式锁:zk、redis、mysql

类加载器:将Class加载入内存,双亲委派(类重复加载、安全),沙箱安全机制

本地方法栈:Native修饰的方法,需要操作硬件,Java无能为力

        本地方法接口:

        本地方法库:C++实现,.DLL

栈:实例对象的地址;本地变量,栈操作,栈数据(栈帧,先进后出)

        程序计数器:记录程序运行的指针,可以忽略不计

方法区:类信息(元数据模板)、静态常量、Integer-128-127

:实例对象的数据;

        新生区:伊甸区、幸存者一、幸存者二、轻GC,TO区总为空

        老年区:重GC

jdk8 中 有什么更新?

去永久代变元空间:

元空间指的是本地的物理内存 RAM 

2. 品牌CRUD \ 三级分类ID与品牌关系CRUD

Mybatis三接口:IService、ServiceImpl、BaseMapper

分类与品牌关系:
1.    根据三级分类Id 查询品牌数据

2.    给三级分类Id 绑定品牌!
获取可选品牌列表

3.    保存分类Id 与品牌的关系!
4.    删除分类Id 与品牌的关系!

3. MinIO 分布式文件存储系统

官方文档:MinIO | The MinIO Quickstart Guide 

 MinIO Quickstart Guide| Minio中文文档 (旧版本)

分布式文件存储系统
本质:将本地图片数据读取到服务器上,文件上传
java 基础 io 流 
ssm 框架  springmvc 文件上传类  MultipartFile

注意事项
1.文件的名称不能重复

2.文件的后缀名

3.文件的字符集编码

4.文件的大小等

minio 开源的分布式文件存储系统,市场占用率高;

特点:性能高;可扩展;sdk;有界面;防止丢失数据;

存储机制:纠删码在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据!

纠删码是一种恢复丢失和损坏数据的数学算法。

三种模式:

单主机单硬盘模式 (我们用的)

单主机多硬盘模式
多主机多硬盘分布式 (高可用集群版)

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.0</version>
</dependency>
minio:
  endpointUrl: http://IP:9000
  accessKey: admin
  secreKey: admin123456
  bucketName: gmall

1.    添加依赖

2.    添加配置文件 : 指定文件服务器的地址,用户名,密码,桶名称

3.    编写文件上传控制器
        1.    创建客户端minioClient
        2.    创建桶
        3.    调用上传文件方法!

测试的时候需要处理的细节
1.修改桶的权限 由private ---> public 

2.回显的时候要使用拼接的方式获取图片的全路径

3.Linux时间要与当前系统时间保持一致;解决方案:参考课件

@Api(description = "MinIO文件上传")
@RestController
@RequestMapping("admin/product")
@RefreshScope //热部署读取到nacos配置文件的数据(配置文件改动直接热部署)
public class FileUploadController {

    //获取文件上传对应的地址
    @Value("${minio.endpointUrl}")
    public String endpointUrl;
    //用户名
    @Value("${minio.accessKey}")
    public String accessKey;
    //密码
    @Value("${minio.secreKey}")
    public String secreKey;
    //桶名
    @Value("${minio.bucketName}")
    public String bucketName;

    //  文件上传控制器:MultipartFile
    @PostMapping("fileUpload")
    public Result fileUpload(MultipartFile file){
        //声明一个遍历去接收url
        String url = "";

        try {
            // Create a minioClient with the MinIO server playground, its access key and secret key.
            MinioClient minioClient =
                    MinioClient.builder()
                            .endpoint(endpointUrl)
                            .credentials(accessKey, secreKey)
                            .build();

            // Make 'asiatrip' bucket if not exist.
            boolean found =
                    minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!found) {
                // Make a new bucket called 'asiatrip'.
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            } else {
                System.out.println("Bucket "+bucketName+" already exists.");
            }

            // Upload '/home/user/Photos/asiaphotos.zip' as object name 'asiaphotos-2015.zip' to bucket
            // 'asiatrip'.
            //
            /*minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket("asiatrip")
                            .object("asiaphotos-2015.zip")
                            .filename("/home/user/Photos/asiaphotos.zip")
                            .build());*/
            //新的上传方法base_category_trademark
            String fileName = System.currentTimeMillis()+ UUID.randomUUID().toString();

            // 后缀名:minio 可以用后缀名 xxx.png
            // file.getOriginalFilename() 截取
            System.out.println("后缀名"+FilenameUtils.getExtension(file.getOriginalFilename()));
            minioClient.putObject(
                    PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(
                                    file.getInputStream(), file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build());
            // 上传之后的url;
            //为什么要拼接?因为url后面有很多全权限设置,默认情况下7天有效,超过七天图片访问不了!
            url = endpointUrl+"/"+bucketName+"/"+fileName;
            System.out.println("url = " + url);

        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (InsufficientDataException e) {
            e.printStackTrace();
        } catch (InternalException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidResponseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (ServerException e) {
            e.printStackTrace();
        } catch (XmlParserException e) {
            e.printStackTrace();
        }
        //返回数据
        return Result.ok(url);
    }
}

如果业务字段在表中没有怎么办?

自己组装Vo,或者使用别名

制作SPU:注意加入事物 @Transactional(rollbackFor = Exception.class)

4. SKU

SPU:一组可复用易检索的标准化信息集合

SKU:每种商品均对应唯一的编号

sku 图片 都应该是基于 SPU 选择的!

sku 应该有属于自己的url 地址!

SKU 相关表结构:
spuInfo:
id,spuName,category3Id,tmId,createTime,updateTime,isDelete
1, 红旗手机, 61  ,       1 

skuInfo:    库存单元表
id,skuName,spuId,price,category3Id,defaultImage,isSale,createTime,updateTime,isDelete
1 ,红旗1  , 1                         0/1
2 ,红旗2  , 1 
skuImage:    库存单元图片表
id,imgName,skuId,imgUrl,createTime,updateTime,isDelete
1,  xx1        1      http://
2,    xx2        1
3,    xx3        1

sku 与 销售属性值Id 有关系么?
skuSaleAttrValue:    sku 与 销售属性值Id 中间表    spuSaleAttrValue
id,skuId,spuSaleAttrId, spuSaleAttrValueId,createTime,updateTime,isDelete
1, 1 ,        1,                1
2, 1 ,        2,                1

sku 与 平台属性值Id 有关系么?    
skuAttrValue:    sku 与 平台属性值Id 中间表    baseAttrValue 
id,skuId,saleAttrId, saleAttrValueId,createTime,updateTime,isDelete
1, 1 ,        1,                1
2, 1 ,        2,                1

商品数量:会单独有一个库存系统
        skuId ,    skuNum
            1,    1000
            2,    10000

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值