苍穹外卖学习笔记(六)

一.公共字段自动填充

方法一:使用AOP切面编程方式

自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法
自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
在Mapper的方法上加入AutoFill注解
  1. AutoFill 注解类
import com.sky.enumeration.OperationType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自动填充注解
 */
@Target(ElementType.METHOD)//方法上
@Retention(RetentionPolicy.RUNTIME)//运行时
public @interface AutoFill {
    //数据库操作类型
    OperationType value();
}
  1. AutoFillAspect切面类
import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自动填充切面
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.service.impl.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void AutoFillPointcut() {
    }

    /**
     * 前置通知
     */
    @Before("AutoFillPointcut()")
    public void autoFill(JoinPoint joinPoint) {
        log.info("自动填充切面执行");
        //获取当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//获取当前被拦截方法的签名
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获取当前被拦截方法的方法名
        OperationType value = autoFill.value();//获取当前被拦截方法的数据库操作类型
        //获取当前被拦截方法的参数-实体对象
        Object[] args = joinPoint.getArgs();//获取当前被拦截方法的参数
        if (args == null || args.length == 0) {
            return;
        }
        Object entity = args[0];
        //准备填充的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();
        //填充数据,通过反射
        if (value == OperationType.INSERT) {
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射调用方法
                setCreateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if (value == OperationType.UPDATE) {
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射调用方法
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
  1. impl
package com.sky.service.impl;

import com.alibaba.druid.util.Utils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sky.annotation.AutoFill;
import com.sky.constant.MessageConstant;
import com.sky.constant.PasswordConstant;
import com.sky.constant.StatusConstant;
import com.sky.context.BaseContext;
import com.sky.dto.EmployeeDTO;
import com.sky.dto.EmployeeLoginDTO;
import com.sky.dto.EmployeePageQueryDTO;
import com.sky.dto.EmployeeUppswdDTO;
import com.sky.entity.Employee;
import com.sky.enumeration.OperationType;
import com.sky.exception.AccountLockedException;
import com.sky.exception.AccountNotFoundException;
import com.sky.exception.PasswordErrorException;
import com.sky.mapper.EmployeeMapper;
import com.sky.result.PageResult;
import com.sky.result.Result;
import com.sky.service.EmployeeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import javax.swing.text.Utilities;
import java.time.LocalDateTime;
import java.util.Base64;

import java.security.SecureRandom;
import java.util.List;

@Slf4j
@Service
public class EmployeeServiceImpl implements EmployeeService {

    @Autowired
    private EmployeeMapper employeeMapper;

    private static final SecureRandom RANDOM = new SecureRandom();//随机数生成器
    private static final int SALT_LENGTH = 16;//盐的长度

    /**
     * 员工登录
     *
     * @param employeeLoginDTO
     * @return
     */


    public Employee login(EmployeeLoginDTO employeeLoginDTO) {
        String username = employeeLoginDTO.getUsername();
        String password = employeeLoginDTO.getPassword();

        //1、根据用户名查询数据库中的数据
        // mybatis-plus的方法
        log.info("username:{}", username);
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Employee::getUsername, username);
        Employee employee = employeeMapper.selectOne(queryWrapper);
        log.info("employee:{}", employee);

        //2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
        if (employee == null) {
            //账号不存在
            throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
        }

        //密码比对
        //对前端传来的密码进行md5加盐处理,然后再进行比对
        String salt = employee.getSalt();//盐
        String hashedPassword = hashPassword(password, salt);//加密后的密码

        if (!hashedPassword.equals(employee.getPassword())) {
            //密码错误
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }

        if (employee.getStatus() == StatusConstant.DISABLE) {
            //账号被锁定
            throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
        }

        //3、返回实体对象
        return employee;
    }

    //对密码进行加密
    private String hashPassword(String password, String salt) {
        String saltedPassword = salt + password;
        return DigestUtils.md5DigestAsHex(saltedPassword.getBytes());
    }

    /**
     * 添加员工
     *
     * @param employeeDTO
     * @return
     */
    @Transactional
    @Override
//    @AutoFill(OperationType.INSERT)
    public void save(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO, employee);
        employee.setStatus(StatusConstant.ENABLE);//默认启用
        //生成盐
        byte[] salt = new byte[SALT_LENGTH];
        RANDOM.nextBytes(salt);
        employee.setSalt(Base64.getEncoder().encodeToString(salt));
        //对密码进行加密,默认密码为123456
        String password = hashPassword(PasswordConstant.DEFAULT_PASSWORD, employee.getSalt());
        employee.setPassword(password);
        //通过注释的方式进行填充
//        //设置创建时间和更新时间
//        employee.setCreateTime(LocalDateTime.now());
//        employee.setUpdateTime(LocalDateTime.now());
//        //设置创建人和更新人
//        employee.setCreateUser(BaseContext.getCurrentId());
//        employee.setUpdateUser(BaseContext.getCurrentId());
        employeeMapper.insert(employee);
    }

    /**
     * 分页查询员工
     *
     * @param employeePageQueryDTO
     * @return
     */
    @Transactional
    @Override
    public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
        // 创建分页对象
        Page<Employee> page = new Page<>(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
        // 创建查询条件
        LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
        // 排除姓名为null的记录
        queryWrapper.isNotNull(Employee::getName);
        // 使用like方法进行模糊查询,参数化防止SQL注入
        if (StringUtils.isNotBlank(employeePageQueryDTO.getName())) {
            queryWrapper.like(Employee::getName, employeePageQueryDTO.getName());
        }
        // 根据创建时间降序排序
        queryWrapper.orderByDesc(Employee::getCreateTime);
        // 执行分页查询
        Page<Employee> employeePage = employeeMapper.selectPage(page, queryWrapper);
        // 提取结果
        long total = employeePage.getTotal();
        List<Employee> records = employeePage.getRecords();
        // 返回结果
        return new PageResult(total, records);
    }

    /**
     * 修改员工状态
     *
     * @param id
     * @param status
     * @return
     */
    @Override
    @AutoFill(OperationType.UPDATE)
    public void startOrStop(Long id, Integer status) {
//        Employee employee = new Employee();
//        employee.setId(id);
//        employee.setStatus(status);
//        employee.setUpdateTime(LocalDateTime.now());
//        employee.setUpdateUser(BaseContext.getCurrentId());
        Employee employee = Employee.builder()
                .id(id)
                .status(status)
//                .updateTime(LocalDateTime.now())
//                .updateUser(BaseContext.getCurrentId())
                .build();
        employeeMapper.updateById(employee);
    }

    /**
     * 根据id查询员工
     *
     * @param id
     * @return
     */
    @Override
    public Employee getById(Long id) {
        Employee employee = employeeMapper.selectById(id);
        employee.setPassword("********");
        return employee;
    }

    /**
     * 修改员工
     *
     * @param employeeDTO
     */
    @Override
    @AutoFill(OperationType.UPDATE)
    public void update(EmployeeDTO employeeDTO) {
        Employee employee = new Employee();
        BeanUtils.copyProperties(employeeDTO, employee);
//        employee.setUpdateTime(LocalDateTime.now());
//        employee.setUpdateUser(BaseContext.getCurrentId());
        employeeMapper.updateById(employee);
    }

    /**
     * 修改密码
     *
     * @param employeeUppswdDTO
     */
    @Override
    @AutoFill(OperationType.UPDATE)
    public void editPassword(EmployeeUppswdDTO employeeUppswdDTO) {
        //1.根据id查询员工
        Employee employee = employeeMapper.selectById(employeeUppswdDTO.getEmpId());
        if (employee == null) {
            throw new AccountNotFoundException("Employee not found");
        }
        //2.对原密码进行加密
        String oldPassword = hashPassword(employeeUppswdDTO.getOldPassword(), employee.getSalt());
        //3.比对原密码是否正确
        if (!oldPassword.equals(employee.getPassword())) {
            throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
        }
        //4.对新密码进行加密
        String newPassword = hashPassword(employeeUppswdDTO.getNewPassword(), employee.getSalt());
        //5.修改密码
        employee.setPassword(newPassword);
//        employee.setUpdateTime(LocalDateTime.now());
//        employee.setUpdateUser(BaseContext.getCurrentId());
        employeeMapper.updateById(employee);
    }
}

由于我前面使用的MP,所以注解需要放在impl中,并且由于DTO没有对应属性,需要在controller中将DTO参数换成entity实体才行,我这里不再详细写出

方法二:使用mybatis-plus自带的公共字段自动填充

1. 创建实体类并使用注解指定需要自动填充的字段
2. 创建自定义的字段填充处理器
注入Bean然后就可以自动填充了(不需要标注解哦 是不是方便许多😘)
  1. employee
package com.sky.entity;

import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
import java.time.LocalDateTime;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("employee")
public class Employee implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    private String username;

    private String name;

    private String password;

    private String phone;

    private String sex;

    private String idNumber;

    private Integer status;

//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    @TableField(fill = FieldFill.INSERT)
    private Long createUser;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;

    private String salt;

}

  1. MyMetaObjectHandler

@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("开始插入填充...");
        this.strictInsertFill(metaObject, AutoFillConstant.CREATE_TIME, LocalDateTime.class, LocalDateTime.now());//参数:实体类属性名,属性类型,属性值
        this.strictInsertFill(metaObject, AutoFillConstant.CREATE_USER, Long.class, BaseContext.getCurrentId());
        this.strictInsertFill(metaObject, AutoFillConstant.UPDATE_TIME, LocalDateTime.class, LocalDateTime.now());
        this.strictInsertFill(metaObject, AutoFillConstant.UPDATE_USER, Long.class, BaseContext.getCurrentId());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("开始更新填充...");
        this.strictUpdateFill(metaObject, AutoFillConstant.UPDATE_TIME, LocalDateTime.class, LocalDateTime.now());
        this.strictUpdateFill(metaObject, AutoFillConstant.UPDATE_USER, Long.class, BaseContext.getCurrentId());
    }
}

二.新增菜品

1.图片上传

这里采用了阿里云oss对象存储服务

  1. application.yml
  alioss:
    endpoint: ${sky.alioss.endpoint}
    access-key-id: ${sky.alioss.access-key-id}
    access-key-secret: ${sky.alioss.access-key-secret}
    bucket-name: ${sky.alioss.bucket-name}
  1. OssConfiguration 用来初始化,具体的配置文件在common中properties中
package com.sky.config;

import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 阿里云OSS配置
 */
@Configuration
@Slf4j
public class OssConfiguration {

    @Bean
    @ConditionalOnMissingBean//当容器里没有指定的Bean的情况下创建该对象
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties) {
        log.info("初始化阿里云OSS配置");
        return new AliOssUtil(aliOssProperties.getEndpoint(),
                              aliOssProperties.getAccessKeyId(),
                              aliOssProperties.getAccessKeySecret(),
                              aliOssProperties.getBucketName());
    }
}
  1. CommonController.java
package com.sky.controller.admin;

import com.sky.constant.MessageConstant;
import com.sky.result.Result;
import com.sky.utils.AliOssUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.UUID;

@Api(tags = "通用接口")
@RestController
@RequestMapping("/admin/common")
@Slf4j
public class CommonController {

    @Autowired
    private AliOssUtil aliOssUtil;

    /**
     * 文件上传
     *
     * @param file
     * @return
     */
    @PostMapping("/upload")
    @ApiOperation("文件上传")
    public Result<String> upload(MultipartFile file) {
        log.info("文件上传:{}", file);
        try {
            String originalFilename = file.getOriginalFilename();
            //截取文件后缀
            String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
            String objectName = UUID.randomUUID().toString() + suffix;
            String url = aliOssUtil.upload(file.getBytes(), objectName);
            return Result.success(url);
        } catch (IOException e) {
            log.error("文件上传失败: {}", e.getMessage(), e);
        }
        return Result.error(MessageConstant.UPLOAD_FAILED);
    }
}

2.具体新增菜品

  1. DishController
package com.sky.controller.admin;

import com.sky.dto.DishDTO;
import com.sky.result.Result;
import com.sky.service.DishService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 菜品管理
 */
@RestController
@RequestMapping("/admin/dish")
@Slf4j
@Api(tags = "菜品管理")
public class DishController {

    @Autowired
    private DishService dishService;

    /**
     * 新增菜品
     */
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品:{}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
        return Result.success();
    }
}
  1. DishService
public interface DishService {

    /**
     * 新增菜品和口味
     */
    void saveWithFlavor(DishDTO dishDTO);
}
  1. DishServiceImpl
package com.sky.service.impl;

import com.baomidou.mybatisplus.core.batch.MybatisBatch;
import com.sky.dto.DishDTO;
import com.sky.entity.Dish;
import com.sky.entity.DishFlavor;
import com.sky.mapper.DishFlavorMapper;
import com.sky.mapper.DishMapper;
import com.sky.service.DishService;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.List;

@Service
@Slf4j
public class DishServiceImpl implements DishService {

    @Autowired
    private DishMapper dishMapper;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    /**
     * 新增菜品和口味
     */
    @Override
    @Transactional
    public void saveWithFlavor(DishDTO dishDTO) {
        //向菜品表插入一条数据
        Dish dish = new Dish();
        BeanUtils.copyProperties(dishDTO, dish);
        dishMapper.insert(dish);
        dishMapper.selectById(dish.getId()); // 获取插入后的主键值
        //获取菜品id
        Long dishId = dish.getId();
        //向口味表插入多条数据
        List<DishFlavor> flavors = dishDTO.getFlavors();
        if (flavors != null && !flavors.isEmpty()) {
            // 设置 dishId
            flavors.forEach(flavor -> flavor.setDishId(dishId));
            // 批量插入
            MybatisBatch<DishFlavor> mybatisBatch = new MybatisBatch<>(sqlSessionFactory, flavors);
            MybatisBatch.Method<DishFlavor> method = new MybatisBatch.Method<>(DishFlavorMapper.class);
            mybatisBatch.execute(method.insert());
        }
    }
}

  1. DishMapper
@Mapper
public interface DishMapper extends BaseMapper<Dish> {
}
  1. DishFlavorMapper
@Mapper
public interface DishFlavorMapper extends BaseMapper<DishFlavor> {

}
  1. 注意需要在实体类中添加主键自增策略,也可以配置主键全局自增,如果还是默认的雪花算法,需要把数据库重建
# mybatis-plus配置
mybatis-plus:
  configuration:
    # 驼峰命名
    map-underscore-to-camel-case: true
    # 日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: auto

三.菜品分页查询

  1. Controller
 /**
     * 分页查询菜品
     */
    @GetMapping("/page")
    @ApiOperation("分页查询菜品")
    public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO) {
        log.info("分页查询菜品:{}", dishPageQueryDTO);
        PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
        return Result.success(pageResult);
    }
  1. Service
/**
     * 分页查询菜品
     */
    PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);
  1. Impl
/**
     * 分页查询菜品
     */
    @Override
    @Transactional
    public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
        // 创建分页对象
        Page<DishVO> page = new Page<>(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
        // 创建查询条件包装器
        QueryWrapper<Dish> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNotNull("d.name");
        // 如果提供了名称,则添加模糊查询条件
        if (StringUtils.isNotBlank(dishPageQueryDTO.getName())) {
            queryWrapper.like("d.name", dishPageQueryDTO.getName());
        }
        // 如果提供了分类id,则添加等值查询条件
        if (dishPageQueryDTO.getCategoryId() != null) {
            queryWrapper.eq("d.category_id", dishPageQueryDTO.getCategoryId());
        }
        // 如果提供了状态,则添加等值查询条件
        if (dishPageQueryDTO.getStatus() != null) {
            queryWrapper.eq("d.status", dishPageQueryDTO.getStatus());
        }
        // 按创建时间降序排序
        queryWrapper.orderByDesc("d.create_time");
        // 执行查询
        System.out.println(queryWrapper.getCustomSqlSegment());
        Page<DishVO> dishVOPage = dishMapper.selectPage(page, queryWrapper);
        log.info("分页查询菜品:{}", dishVOPage);
        // 获取总记录数和记录列表
        long total = dishVOPage.getTotal();
        List<DishVO> records = dishVOPage.getRecords();
        // 返回分页结果
        return new PageResult(total, records);
    }
  1. mapper
@Mapper
public interface DishMapper extends BaseMapper<Dish> {

    /**
     * 分页查询菜品
     */
    @Select("SELECT d.*, c.name AS categoryName FROM dish d LEFT JOIN category c ON d.category_id = c.id ${ew.customSqlSegment}")
    Page<DishVO> selectPage(Page<DishVO> page, @Param(Constants.WRAPPER) QueryWrapper<Dish> queryWrapper);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晚睡早起₍˄·͈༝·͈˄*₎◞ ̑̑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值