乐优商城笔记二:分类与品牌模块

分类模块与品牌模块实现

准备工作

使用nginx,通过域名访问本地项目

  1. 下载nginx压缩包(我这里直接下载的windows包,解压后编写配置文件,即可使用)
  2. nginx.conf
...
# 配置两个server节点即可
    server {
        listen       80;
        server_name  manage.leyou.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

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

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

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

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

跨域问题解决(采用CORS)

  1. ly-gateway中配置bean即可
package com.leyou.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {

    @Bean
    public CorsFilter corsFilter() {
        //1.添加CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        //1) 允许的域,不要写*,否则cookie就无法使用了
        config.addAllowedOrigin("http://manage.leyou.com");
        //2) 是否发送Cookie信息
        config.setAllowCredentials(true);
        //3) 允许的请求方式
        config.addAllowedMethod("OPTIONS");
        config.addAllowedMethod("HEAD");
        config.addAllowedMethod("GET");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("DELETE");
        config.addAllowedMethod("PATCH");
        // 4)允许的头信息
        config.addAllowedHeader("*");
        // 5) 设置第一次跨域通过后,往后的请求不需要再次发送跨域预请求
        config.setMaxAge(7200L);

        //2.添加映射路径,我们拦截一切请求
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config);

        //3.返回新的CorsFilter.
        return new CorsFilter(configSource);
    }
}

创建数据库

  1. 创建数据库leyou

  2. 导入数据库文件

    leyou.sql

分类管理模块

后端

添加实体类
package com.leyou.pojo;

import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;

@Table(name="tb_category")
@Data
public class Category {
    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    private String name;
    private Long parentId;
    private Boolean isParent;
    private Integer sort;
}

注意:

​ 实体类放到ly-item-interface

​ 实体类中使用的注解需要引入mapper-core的依赖。

编写mapper
package com.leyou.item.mapper;

import com.leyou.pojo.Category;
import tk.mybatis.mapper.common.Mapper;

public interface CategoryMapper extends Mapper<Category> {
}

注意:

​ 需要在启动类上加入@MapperScan(com.leyou.item.mapper)注解

编写service
package com.leyou.item.service;

import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.item.mapper.CategoryMapper;
import com.leyou.pojo.Category;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

@Service
public class CategoryService {

    @Autowired
    private CategoryMapper categoryMapper;

    /**
     * 按分类父ID查询分类列表
     * @param pid 父ID
     * @return 分类列表
     */
    public List<Category> queryCategoryListByPid(Long pid) {
        Category category = new Category();
        category.setParentId(pid);
        List<Category> categoryList = categoryMapper.select(category);
        if (CollectionUtils.isEmpty(categoryList)) {
            throw new LyException(LyExceptionEnum.CATEGORY_LIST_NOT_FOUND);
        }
        return categoryList;
    }

    /**
     * 新增分类
     * @param category 分类
     * @return 分类
     */
    public Category addCategory(Category category) {
        // 判断其父节点是否为父节点
        if (category.getParentId() != CategoryConstans.FIRST_CATEGORY_PARENT_ID) {
            Category pCategory = categoryMapper.selectByPrimaryKey(category.getParentId());
            if (pCategory != null) {
                // 判断是否已经是父菜单
                if (!pCategory.getIsParent()) {// 不为父菜单
                    // 设置为父菜单并保存
                    pCategory.setIsParent(true);
                    categoryMapper.updateByPrimaryKey(pCategory);
                }
            } else {
                throw new LyException(LyExceptionEnum.PARENT_CATEGORY_NOT_FOUND);
            }
        }
        if (categoryMapper.insert(category) != 1) {
            throw new LyException(LyExceptionEnum.SAVE_FAILURE);
        }
        return category;
    }

    /**
     * 编辑分类
     * @param category 分类
     * @return 分类
     */
    public Category saveCategory(Category category) {
        if (categoryMapper.updateByPrimaryKey(category) != 1) {
            throw new LyException(LyExceptionEnum.SAVE_FAILURE);
        }
        return category;
    }

    /**
     * 删除费雷
     * @param categoryId 分类id
     * @return 被删除的分类
     */
    public Category deleteCategory(Long categoryId) {
        Category category = categoryMapper.selectByPrimaryKey(categoryId);
        if (category == null) {
            throw new LyException(LyExceptionEnum.CATEGORY_NOT_FOUND);
        }
        // 判断该分类是否为父分类,若为父分类判断分类下是否有子分类
        if (category.getIsParent()) {
            Category param = new Category();
            param.setParentId(categoryId);
            if (categoryMapper.select(param).size() > 0) {
                throw new LyException(LyExceptionEnum.DELETE_INVALID);
            }
        }
        if (categoryMapper.delete(category) != 1) {
            throw new LyException(LyExceptionEnum.SAVE_FAILURE);
        }
        return category;
    }
}

新增异常枚举:

CATEGORY_LIST_NOT_FOUND(404, "未查询到分类列表"),
CATEGORY_NOT_FOUND(404, "该分类不存在"),
PARENT_CATEGORY_NOT_FOUND(404, "该父分类不存在"),
SAVE_FAILURE(500, "保存失败"),
DELETE_FAILURE(500, "删除失败"),
DELETE_INVALID(500, "该分类下含有子分类,请先删除其子分类"),

新增分类常量类:

package com.leyou.pojo;

public final class CategoryConstans {
    // 分类一级菜单的父ID
    public static final long FIRST_CATEGORY_PARENT_ID = 0;
}
编写controller
package com.leyou.item.cotroller;

import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.item.service.CategoryService;
import com.leyou.pojo.Category;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/category")
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    @GetMapping("/list")
    public ResponseEntity<List<Category>> queryCategoryListByPid(@RequestParam(value = "pid", defaultValue = "0") Long pid) {
        return ResponseEntity.ok(categoryService.queryCategoryListByPid(pid));
    }

    @PostMapping()
    public ResponseEntity<Category> addCategory(@RequestBody Category category) {
        if (category == null) {
            throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
        }
        return ResponseEntity.status(HttpStatus.CREATED).body(categoryService.addCategory(category));
    }

    @PutMapping()
    public ResponseEntity<Category> saveCategory(@RequestBody Category category) {
        if (category == null) {
            throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
        }
        return ResponseEntity.ok(categoryService.saveCategory(category));
    }

    @DeleteMapping("/{categoryId}")
    public ResponseEntity<Category> deleteCategory(@PathVariable("categoryId") Long categoryId) {
        if (categoryId == null) {
            throw new LyException(LyExceptionEnum.PARAM_CANNOT_BE_NULL);
        }
        return ResponseEntity.ok(categoryService.deleteCategory(categoryId));
    }
}

前端

Category.vue
<template>
  <v-card>
      <v-flex xs12 sm10>
        <v-tree url="/item/category/list"
                :isEdit="isEdit"
                @handleAdd="handleAdd"
                @handleEdit="handleEdit"
                @handleDelete="handleDelete"
                @handleClick="handleClick"
        />
      </v-flex>
  </v-card>
</template>

<script>
  export default {
    name: "category",
    data() {
      return {
        isEdit:true
      }
    },
    methods: {
      handleAdd(node) {
        // console.log("add .... ");
        console.log(node);
        this.$http.post('/item/category', {
          "name": node.name,
          "sort": node.sort,
          "parentId": node.parentId,
          "isParent": node.isParent
        }).then((response) => {
          if (response.status === 201) {
            this.$message.info('添加成功');
          } else {
            this.$message.error(response['message']);
          }
        }).catch(() => {
          this.$message.error('添加失败');
        })
      },
      handleEdit(id, name) {
        console.log("edit... id: " + id + ", name: " + name)
        // 这个树菜单的编辑,我也没玩明白。23333333

      },
      handleDelete(id) {
        // console.log("delete ... " + id)
        this.$http.delete('/item/category/' + id)
          .then((response) => {
            if (response.status === 200) {
              this.$message.info("删除成功")
            } else {
              this.$message.error(response['message'])
            }
          })
          .catch(() => {
            this.$message.error("删除失败")
          })
      },
      handleClick(node) {
      }
    }
  };
</script>

<style scoped>

</style>

树菜单的编辑,我也没玩明白,有玩明白了可以留言一下。

品牌管理模块

后端

添加实体类
package com.leyou.pojo;

import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;

@Table(name = "tb_brand")
@Data
public class Brand {
    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    private String name;
    private String image;
    private Character letter;
}
编写mapper
package com.leyou.item.mapper;

import com.leyou.pojo.Brand;
import tk.mybatis.mapper.common.Mapper;

public interface BrandMapper extends Mapper<Brand> {
}
编写service
package com.leyou.item.service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.vo.PageResult;
import com.leyou.item.mapper.BrandMapper;
import com.leyou.pojo.Brand;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.util.List;

@Service
@Transactional
public class BrandService {

    @Autowired
    private BrandMapper brandMapper;

    /**
     * 分页查询品牌列表
     *
     * @param page   当前页码
     * @param rows   每页记录数
     * @param sortBy 排序字段
     * @param desc   是否降序
     * @param key    查询关键字
     * @return 品牌分页列表
     */
    public PageResult<Brand> queryBrandByPage(int page, int rows, String sortBy, Boolean desc, String key) {
        // 分页查询
        PageHelper.startPage(page, rows);
        // 封装查询条件
        Example example = new Example(Brand.class);
        if (StringUtils.isNoneBlank(key)) {
            example.createCriteria().andLike("name", "%" + key + "%");
        }
        // 排序
        if (StringUtils.isNoneBlank(sortBy)) {
            example.setOrderByClause(sortBy + (desc ? " DESC" : " ASC"));
        }
        List<Brand> brands = brandMapper.selectByExample(example);
        if (brands.size() < 1) {
            throw new LyException(LyExceptionEnum.BRAND_LIST_NOT_FOUND);
        }
        // 获取分页信息并返回
        PageInfo<Brand> pageInfo = new PageInfo<>(brands);
        return new PageResult<>(pageInfo.getTotal(), brands);
    }

    /**
     * 新增品牌
     *
     * @param brand      品牌
     * @param categories 品牌所属分类
     * @return 品牌
     */
    public Brand addBrand(Brand brand, List<Long> categories) {
        // 新增品牌
        if (brandMapper.insert(brand) != 1) {
            throw new LyException(LyExceptionEnum.SAVE_FAILURE);
        }
        // 添加关联的品牌分类
        insertCategoryBrand(brand, categories);

        return brand;
    }

    /**
     * 编辑品牌
     *
     * @param brand      品牌
     * @param categories 品牌所属分类
     * @return 品牌
     */
    public Brand saveBrand(Brand brand, List<Long> categories) {
        // 更新品牌
        if (brandMapper.updateByPrimaryKeySelective(brand) != 1) {
            throw new LyException(LyExceptionEnum.SAVE_FAILURE);
        }
        // 更新关联的品牌分类
        brandMapper.deleteCategoryBrandByBrandId(brand.getId());
        insertCategoryBrand(brand, categories);
        return brand;
    }

    /**
     * 新增品牌分类关联数据(品牌 1 -> n 分类)
     *
     * @param brand      品牌
     * @param categories 分类
     */
    private void insertCategoryBrand(Brand brand, List<Long> categories) {
        categories.forEach(categoryId -> {
            if (brandMapper.insertCategoryBrand(categoryId, brand.getId()) != 1) {
                throw new LyException(LyExceptionEnum.SAVE_FAILURE);
            }
        });
    }

    /**
     * 删除品牌
     *
     * @param brandId 品牌ID
     * @return 被删除的品牌
     */
    public Brand deleteBrand(long brandId) {
        // 查询要删除的品牌
        Brand brand = brandMapper.selectByPrimaryKey(brandId);
        if (brand == null) {
            throw new LyException(LyExceptionEnum.BRAND_LIST_NOT_FOUND);
        }
        // 删除品牌
        if(brandMapper.delete(brand) != 1){
            throw new LyException(LyExceptionEnum.DELETE_FAILURE);
        }
        // 删除品牌的关联分类数据
        brandMapper.deleteCategoryBrandByBrandId(brandId);
        return brand;
    }
}
编写controller
package com.leyou.item.cotroller;

import com.leyou.common.enums.LyExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.vo.PageResult;
import com.leyou.item.service.BrandService;
import com.leyou.pojo.Brand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("brand")
public class BrandController {

    @Autowired
    private BrandService brandService;

    @GetMapping("/page")
    public ResponseEntity<PageResult<Brand>> queryBrandByPage(@RequestParam(value = "page", defaultValue = "1") int page,
                                                              @RequestParam(value = "rows", defaultValue = "5") int rows,
                                                              @RequestParam("sortBy") String sortBy,
                                                              @RequestParam(value = "desc",defaultValue = "false") Boolean desc,
                                                              @RequestParam("key") String key) {
        return ResponseEntity.ok(brandService.queryBrandByPage(page, rows, sortBy, desc, key));
    }

    @PostMapping
    public ResponseEntity<Brand> addBrand(@RequestBody Brand brand,
                                          @RequestParam("categories") List<Long> categories) {
        if (CollectionUtils.isEmpty(categories)) {
            throw new LyException(LyExceptionEnum.PARAM_INVALID);
        }
        return ResponseEntity.status(HttpStatus.CREATED).body(brandService.addBrand(brand, categories));
    }

    @PutMapping
    public ResponseEntity<Brand> editBrand(@RequestBody Brand brand,
                                           @RequestParam("categories") List<Long> categories) {
        if (CollectionUtils.isEmpty(categories) || brand.getId() == null) {
            throw new LyException(LyExceptionEnum.PARAM_INVALID);
        }
        return ResponseEntity.ok(brandService.saveBrand(brand, categories));
    }

    @DeleteMapping("/{brandId}")
    public ResponseEntity<Brand> deleteBrand(@PathVariable("brandId") long brandId) {
        return ResponseEntity.ok(brandService.deleteBrand(brandId));
    }


}

文件上传微服务

在品牌管理模块的新增和修改中,需要进行上传图片。所以我们将其抽离出来,形成专门的微服务,以后所有文件上传,无论是图片或者是其他文件,都可以通过此文件上传微服务完成。

搭建项目

创建maven项目
  • GroupId:com.leyou.service
  • ArtifactId:ly-upload
编写pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>parent</artifactId>
        <groupId>com.leyou</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.service</groupId>
    <artifactId>ly-upload</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.leyou.common</groupId>
            <artifactId>ly-common</artifactId>
            <version>${leyou.latest.version}</version>
        </dependency>
    </dependencies>
</project>
启动类
package com.leyou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class LyUpload {
    public static void main(String[] args) {
        SpringApplication.run(LyUpload.class, args);
    }
}

注意在网关中添加文件上传微服务的路由

编写application.yml
server:
  port: 8082
spring:
  application:
    name: upload-service
  servlet:
    multipart:
      # 限制单个文件的大小
      max-file-size: 5MB
# Eureka
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:9999/eureka

ly:
  upload:
    imageServer: http://image.leyou.com/
    imageTypes:
        - image/jpeg
        - image/bmp
        - image/png

优化

默认情况下,请求通过Zuul网关,Spring MVC会对请求做预处理和缓存,但是文件一旦缓存,会给服务器造成不必要的负担。所以我们需要上传文件的请求不进行缓存。官方给出的解决方案就是在请求的最前面加上/zuul这样就可以绕开请求的缓存,直接将请求路由到指定的微服务。

配置nginx路径重写

修改nginx.conf配置文件

分布式文件系统FastDFS

FastDFS简介

参考:http://www.xushuai.fun/2018/12/22/FastDFS简介/

FastDFS安装

参考:http://www.xushuai.fun/2018/12/22/FastDFS安装/

引入FastDFS-client依赖
  1. java-客户端

  1. 编辑pom.xml文件,新增依赖

    <dependency>
        <groupId>com.github.tobato</groupId>
        <artifactId>fastdfs-client</artifactId>
    </dependency>
    
新增配置
fdfs:
  so-timeout: 1501
  connect-timeout: 601
  thumb-image:             #缩略图生成参数
    width: 150
    height: 150
  tracker-list:            #TrackerList参数,支持多个
    - 192.168.136.100:22122
引入配置类
package com.leyou.upload.config;

import com.github.tobato.fastdfs.FdfsClientConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableMBeanExport;
import org.springframework.context.annotation.Import;
import org.springframework.jmx.support.RegistrationPolicy;

/**
 * 导入FastDFS-Client组件
 * 
 * @author tobato
 *
 */
@Configuration
@Import(FdfsClientConfig.class)
// 解决jmx重复注册bean的问题
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class ComponetImport {
    // 导入依赖组件
}
编写上传逻辑
  • service

    package com.leyou.upload.service;
    
    import com.github.tobato.fastdfs.domain.StorePath;
    import com.github.tobato.fastdfs.service.FastFileStorageClient;
    import com.leyou.common.enums.LyExceptionEnum;
    import com.leyou.common.exception.LyException;
    import com.leyou.upload.constans.FileTypeConstans;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    
    @Service
    @Slf4j
    public class UploadService {
    
        @Autowired
        private FastFileStorageClient fastFileStorageClient;
    
        @Autowired
        private ImageTypeConfigProperty imageTypeConfigProperty;
    
        /**
         * 文件上传
         *
         * @param file 文件
         * @return 文件url
         */
        public String uploadFile(MultipartFile file, String fileType) {
            String url = null;
            switch (fileType) {
                case FileTypeConstans.IMAGE:
                    url = uploadImage(file);
                    break;
                default:
                    throw new LyException(LyExceptionEnum.FILE_TYPE_ERROR);
            }
            return url;
        }
    
        /**
         * 图片上传
         *
         * @param file 图片
         * @return 图片url
         */
        private String uploadImage(MultipartFile file) {
            try {
                // 校验图片内容,防止修改后缀名恶意上传
                BufferedImage image = ImageIO.read(file.getInputStream());
                if (image == null || image.getWidth() == 0 || image.getHeight() == 0) {
                   throw new LyException(LyExceptionEnum.FILE_TYPE_ERROR);
                }
                // 获取文件后缀名
                String filename = file.getOriginalFilename();
                String suffix = filename.substring(filename.indexOf(".") + 1);
    
                // 上传文件
                StorePath storePath = fastFileStorageClient.uploadFile(file.getInputStream(), file.getSize(), suffix, null);
                // 返回文件路径
                return imageTypeConfigProperty.getImageServer() + storePath.getFullPath();
            } catch (IOException e) {
                log.error("读取文件内容发生IO异常.  e = {}", e);
                throw new LyException(LyExceptionEnum.READ_FILE_FAILURE);
            }
        }
    }
    
  • controller

    package com.leyou.upload.controller;
    
    import com.leyou.common.enums.LyExceptionEnum;
    import com.leyou.common.exception.LyException;
    import com.leyou.upload.config.ImageTypeConfigProperty;
    import com.leyou.upload.constans.FileTypeConstans;
    import com.leyou.upload.service.UploadService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.util.List;
    
    @RestController
    @RequestMapping("/upload")
    public class UploadController {
    
        @Autowired
        private UploadService uploadService;
    
        @Autowired
        private ImageTypeConfigProperty imageTypeConfigProperty;
    
        @PostMapping("/image")
        public String uploadImage(@RequestParam("file") MultipartFile file) {
            // 判断图片类型
            if (!imageTypeConfigProperty.getImageTypes().contains(file.getContentType())) {
                throw new LyException(LyExceptionEnum.FILE_TYPE_ERROR);
            }
    
            return uploadService.uploadFile(file, FileTypeConstans.IMAGE);
        }
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值