2020-11-26

/*
 *  Copyright (c) 2017-2019, bruce.ge.
 *    This program is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU General Public License
 *    as published by the Free Software Foundation; version 2 of
 *    the License.
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *    GNU General Public License for more details.
 *    You should have received a copy of the GNU General Public License
 *    along with this program;
 */

package com.bruce.intellijplugin.generatesetter.actions;

import com.bruce.intellijplugin.generatesetter.CommonConstants;
import com.bruce.intellijplugin.generatesetter.GetInfo;
import com.bruce.intellijplugin.generatesetter.Parameters;
import com.bruce.intellijplugin.generatesetter.complexreturntype.*;
import com.bruce.intellijplugin.generatesetter.utils.PsiClassUtils;
import com.bruce.intellijplugin.generatesetter.utils.PsiDocumentUtils;
import com.bruce.intellijplugin.generatesetter.utils.PsiToolUtils;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.util.IncorrectOperationException;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;

import java.util.*;

/**
 * @author bruce ge
 */
public abstract class GenerateAllSetterBase extends PsiElementBaseIntentionAction {
    public static final String IS = "is";
    public static final String GET = "get";
    private static final String SET_SETTER_PREFIX = "set";
    private static final String WITH_SETTER_PREFIX = "with";
    public static final String STATIC = "static";
    private final GenerateAllHandler generateAllHandler;

    public GenerateAllSetterBase(GenerateAllHandler generateAllHandler) {
        this.generateAllHandler = generateAllHandler;
    }

    private static Map<String, String> typeGeneratedMap = new HashMap<String, String>() {
        {
            put("boolean", "false");
            put("java.lang.Boolean", "false");
            put("int", "0");
            put("byte", "(byte)0");
            put("java.lang.Byte", "(byte)0");
            put("java.lang.Integer", "0");
            put("java.lang.String", "\"\"");
            put("java.math.BigDecimal", "new BigDecimal(\"0\")");
            put("java.lang.Long", "0L");
            put("long", "0L");
            put("short", "(short)0");
            put("java.lang.Short", "(short)0");
            put("java.util.Date", "new Date()");
            put("float", "0.0F");
            put("java.lang.Float", "0.0F");
            put("double", "0.0D");
            put("java.lang.Double", "0.0D");
            put("java.lang.Character", "\'\'");
            put("char", "\'\'");
            put("java.time.LocalDateTime", "LocalDateTime.now()");
            put("java.time.LocalDate", "LocalDate.now()");

        }
    };

    private static Map<String, ComplexReturnTypeHandler> handlerMap = new HashMap<String, ComplexReturnTypeHandler>() {
        {
            put("java.util.List", new ListReturnTypeHandler());
            put("java.util.Set", new SetReturnTypeHandler());
            put("java.util.Map", new MapReturnTypeHandler());
        }
    };

    private static Map<String, String> typeGeneratedImport = new HashMap<String, String>() {
        {
            put("java.math.BigDecimal", "java.math.BigDecimal");
            put("java.util.Date", "java.util.Date");
            put("java.time.LocalDateTime", "java.time.LocalDateTime");
            put("java.time.LocalDate", "java.time.LocalDate");
        }
    };

    private static Map<String, String> guavaTypeMaps = new HashMap<String, String>() {
        {
            put("List", "Lists.newArrayList()");
            put("Map", "Maps.newHashMap()");
            put("Set", "Sets.newHashSet()");
        }
    };

    private static Map<String, String> guavaImportMap = new HashMap<String, String>() {
        {
            put("List", "com.google.common.collect.Lists");
            put("Map", "com.google.common.collect.Maps");
            put("Set", "com.google.common.collect.Sets");
        }
    };

    private static Map<String, String> defaultCollections = new HashMap<String, String>() {
        {
            put("List", "ArrayList");
            put("Map", "HashMap");
            put("Set", "HashSet");
        }
    };

    private static Map<String, String> defaultImportMap = new HashMap<String, String>() {
        {
            put("List", "java.util.ArrayList");
            put("Map", "java.util.HashMap");
            put("Set", "java.util.HashSet");
        }
    };

    private static Map<String, String> defaultPacakgeValues = new HashMap<String, String>() {
        {
            put("java.sql.Date", "new Date(new java.util.Date().getTime())");
            put("java.sql.Timestamp",
                    "new Timestamp(new java.util.Date().getTime())");
        }
    };

    @Override
    public void invoke(@NotNull Project project, Editor editor,
                       @NotNull PsiElement element) throws IncorrectOperationException {
        PsiElement psiParent = PsiTreeUtil.getParentOfType(element,
                PsiLocalVariable.class, PsiMethod.class);
        if (psiParent == null) {
            return;
        }
        if (psiParent instanceof PsiLocalVariable) {
            PsiLocalVariable psiLocal = (PsiLocalVariable) psiParent;
            if (!(psiLocal.getParent() instanceof PsiDeclarationStatement)) {
                return;
            }

            handleWithLocalVariable(psiLocal, project, psiLocal);

        } else if (psiParent instanceof PsiMethod) {
            PsiMethod method = (PsiMethod) psiParent;
            if (method.getReturnType() == null) {
                return;
            }

            handleWithMethod(method, project, method);
        }
    }

    private void handleWithMethod(PsiMethod method, Project project,
                                  PsiElement element) {
        PsiDocumentManager psiDocumentManager = PsiDocumentManager
                .getInstance(project);
        Document document = psiDocumentManager
                .getDocument(element.getContainingFile());
        String splitText = extractSplitText(method, document);
        Parameters returnTypeInfo = PsiToolUtils
                .extractParamInfo(method.getReturnType());
        InsertDto dto = null;
        boolean hasGuava = PsiToolUtils.checkGuavaExist(project, element);
        if (returnTypeInfo.getCollectPackege() != null
                && handlerMap.containsKey(returnTypeInfo.getCollectPackege())) {
            //
            dto = handlerMap.get(returnTypeInfo.getCollectPackege()).handle(
                    returnTypeInfo, splitText,
                    method.getParameterList().getParameters(), hasGuava);
        } else {
            PsiClass returnTypeClass = PsiTypesUtil
                    .getPsiClass(method.getReturnType());
            dto = getBaseInsertDto(splitText, hasGuava,
                    method.getParameterList().getParameters(), returnTypeClass);
        }
        if (dto.getAddedText() != null) {
            document.insertString(method.getBody().getTextOffset() + 1,
                    dto.getAddedText());
        }
        PsiDocumentUtils.commitAndSaveDocument(psiDocumentManager, document);

        if (dto.getImportList() != null) {
            PsiToolUtils.addImportToFile(psiDocumentManager,
                    (PsiJavaFile) element.getContainingFile(), document,
                    dto.getImportList());
        }
    }

    @NotNull
    private InsertDto getBaseInsertDto(String splitText,
                                       boolean hasGuava, PsiParameter[] parameters1, PsiClass psiClass) {
        InsertDto dto = new InsertDto();
        PsiParameter[] parameters = parameters1;
        List<PsiMethod> methods = PsiClassUtils.extractSetMethods(psiClass);
        Set<String> importList = Sets.newHashSet();
        String generateName = PsiToolUtils.lowerStart(psiClass.getName());
        GetInfo info = null;
        if (parameters.length > 0) {
            for (PsiParameter parameter : parameters) {
                PsiType type = parameter.getType();
                PsiClass parameterClass = PsiTypesUtil.getPsiClass(type);
                if (parameterClass == null || parameterClass.getQualifiedName()
                        .startsWith("java.")) {
                    continue;
                } else {
                    List<PsiMethod> getMethods = PsiClassUtils
                            .extractGetMethod(parameterClass);
                    // TODO: 2017/1/20 may be i can extract get memthod from all
                    // parameter
                    if (getMethods.size() > 0) {
                        info = buildInfo(parameter, getMethods);
                        break;
                    }
                }
            }
        }
        // TODO: 2017/8/2 what if two class has the same name
        String insertText = splitText + psiClass.getName() + " " + generateName
                + " = new " + psiClass.getName() + "();";
        if (info == null) {
            insertText += generateStringForNoParam(generateName, methods,
                    splitText, importList, hasGuava);
        } else {
            insertText += generateStringForParam(generateName, methods,
                    splitText, importList, hasGuava, info);
        }
        insertText += "return " + generateName + ";";
        dto.setAddedText(insertText);
        dto.setImportList(importList);
        return dto;
    }

    private String generateStringForParam(String generateName,
                                          List<PsiMethod> methodList, String splitText,
                                          Set<String> newImportList, boolean hasGuava, GetInfo info) {
        StringBuilder builder = new StringBuilder();
        builder.append(splitText);
        for (PsiMethod method : methodList) {
            String setterMethodNamePrefix =
                    method.getName().startsWith(SET_SETTER_PREFIX)
                            ? SET_SETTER_PREFIX
                            : method.getName().startsWith(WITH_SETTER_PREFIX)
                            ? WITH_SETTER_PREFIX
                            : null;
            if (setterMethodNamePrefix != null) {
                String fieldToLower = method.getName().substring(setterMethodNamePrefix.length())
                        .toLowerCase();
                PsiMethod s = info.getNameToMethodMap().get(fieldToLower);
                if (s != null) {
                    // TODO: 2017/8/2 check the get method return type and set
                    // method param type.
                    if (method.getParameterList().getParameters().length == 1) {
                        PsiParameter psiParameter = method.getParameterList()
                                .getParameters()[0];
                        PsiType type = psiParameter.getType();
                        PsiType returnType = s.getReturnType();
                        String setTypeText = type.getCanonicalText();
                        String getTypeText = returnType.getCanonicalText();
                        String getMethodText = info.getParamName() + "."
                                + s.getName() + "()";
                        String startText = generateName + "." + method.getName()
                                + "(";
                        builder.append(generateSetterString(setTypeText,
                                getTypeText, getMethodText, startText));
                    }
                } else {
                    generateDefaultForOneMethod(generateName, newImportList,
                            hasGuava, builder, method);
                }
            }
            builder.append(splitText);
        }
        return builder.toString();
    }

    private static String generateSetterString(String setTypeText,
                                               String getTypeText, String getMethodText, String startText) {
        if (setTypeText.equals(getTypeText)) {
            return startText + getMethodText + ");";
        } else {
            if (setTypeText.equals("java.lang.String")) {
                return startText + "String.valueOf(" + getMethodText + "));";
            } else if (setTypeText.equals("java.util.Date")
                    && checkMethodIsLong(getTypeText)) {
                return startText + "new Date(" + getMethodText + "));";
            } else if (checkMethodIsLong(setTypeText)
                    && getTypeText.equals("java.util.Date")) {
                return startText + getMethodText + ".getTime());";
            } else if (setTypeText.equals("java.sql.Timestamp")
                    && checkMethodIsLong(getTypeText)) {
                return startText + "new Timestamp(" + getMethodText + "));";
            } else if (checkMethodIsLong(setTypeText)
                    && getTypeText.equals("java.sql.Timestamp")) {
                return startText + getMethodText + ".getTime());";
            }
        }
        return startText + getMethodText + ");";
    }

    private static boolean checkMethodIsLong(String getMethodText) {
        return getMethodText.equals("java.lang.Long")
                || getMethodText.equals("long");
    }

    @NotNull
    private static GetInfo buildInfo(PsiParameter parameter,
                                     List<PsiMethod> getMethods) {
        GetInfo info;
        info = new GetInfo();
        info.setParamName(parameter.getName());
        info.setGetMethods(getMethods);
        Map<String, PsiMethod> nameToMethodMaps = Maps.newHashMap();
        for (PsiMethod getMethod : getMethods) {
            if (getMethod.getName().startsWith(IS)) {
                nameToMethodMaps.put(
                        getMethod.getName().substring(2).toLowerCase(),
                        getMethod);
            } else if (getMethod.getName().startsWith(GET)) {
                nameToMethodMaps.put(
                        getMethod.getName().substring(3).toLowerCase(),
                        getMethod);
            }
        }
        info.setNameToMethodMap(nameToMethodMaps);
        return info;
    }

    @NotNull
    private static String extractSplitText(PsiMethod method,
                                           Document document) {
        int startOffset = method.getTextRange().getStartOffset();
        int lastLine = startOffset - 1;
        String text = document.getText(new TextRange(lastLine, lastLine + 1));
        boolean isTable = false;
        while (!text.equals("\n")) {
            if (text.equals('\t')) {
                isTable = true;
            }
            lastLine--;
            text = document.getText(new TextRange(lastLine, lastLine + 1));
        }
        String methodStartToLastLineText = document
                .getText(new TextRange(lastLine, startOffset));
        String splitText = null;
        if (isTable) {
            splitText += methodStartToLastLineText + "\t";
        } else {
            splitText = methodStartToLastLineText + "    ";
        }
        return splitText;
    }

    private void handleWithLocalVariable(PsiLocalVariable localVariable,
                                         Project project, PsiElement element) {
        PsiElement parent1 = localVariable.getParent();
        if (!(parent1 instanceof PsiDeclarationStatement)) {
            return;
        }
        PsiClass psiClass = PsiTypesUtil.getPsiClass(localVariable.getType());
        String generateName = localVariable.getName();
        List<PsiMethod> methodList;
        if (generateAllHandler.isSetter()) {
            methodList = PsiClassUtils.extractSetMethods(psiClass);
        } else {
            methodList = PsiClassUtils.extractGetMethod(psiClass);
        }
        if (methodList.size() == 0) {
            return;
        }
        PsiDocumentManager psiDocumentManager = PsiDocumentManager
                .getInstance(project);
        PsiFile containingFile = element.getContainingFile();
        Document document = psiDocumentManager.getDocument(containingFile);
        String splitText = PsiToolUtils.calculateSplitText(document, parent1.getTextOffset());

        Set<String> newImportList = new HashSet<>();
        boolean hasGuava = PsiToolUtils.checkGuavaExist(project, element);

        String buildString = generateStringForNoParam(generateName, methodList,
                splitText, newImportList, hasGuava);
        document.insertString(parent1.getTextOffset() + parent1.getText().length(),
                buildString);
        PsiDocumentUtils.commitAndSaveDocument(psiDocumentManager, document);
        PsiToolUtils.addImportToFile(psiDocumentManager,
                (PsiJavaFile) containingFile, document, newImportList);
        return;
    }

    @NotNull
    private String generateStringForNoParam(String generateName,
                                            List<PsiMethod> methodList, String splitText,
                                            Set<String> newImportList, boolean hasGuava) {
        StringBuilder builder = new StringBuilder();
        builder.append(splitText);
        for (PsiMethod method : methodList) {
            generateDefaultForOneMethod(generateName, newImportList, hasGuava,
                    builder, method);
            builder.append(splitText);
        }

        return builder.toString();
    }

    private void generateDefaultForOneMethod(String generateName,
                                             Set<String> newImportList, boolean hasGuava, StringBuilder builder,
                                             PsiMethod method) {
        PsiParameter[] parameters = method.getParameterList().getParameters();

        if (!generateAllHandler.shouldAddDefaultValue()) {
            builder.append(generateAllHandler.formatLine("." + method.getName() + "()"));
            return;
        }

        builder.append("." + method.getName() + "(");

        int u = parameters.length;
        int h = 0;
        for (PsiParameter parameter : parameters) {
            h++;
            String classType = parameter.getType().getCanonicalText();
            String ss = typeGeneratedMap.get(classType);
            if (ss != null) {
                builder.append(ss);
                String importType = typeGeneratedImport.get(classType);
                if (importType != null) {
                    newImportList.add(importType);
                }
            } else {
                // shall check which import list to use.
                Parameters paramInfo = PsiToolUtils
                        .extractParamInfo(parameter.getType());
                if (paramInfo.getCollectName() != null && guavaTypeMaps
                        .containsKey(paramInfo.getCollectName())) {
                    if (hasGuava) {
                        builder.append(
                                guavaTypeMaps.get(paramInfo.getCollectName()));
                        newImportList.add(
                                guavaImportMap.get(paramInfo.getCollectName()));
                    } else {
                        // handleWithoutGuava.
                        String defaultImpl = defaultCollections
                                .get(paramInfo.getCollectName());
                        appendCollectNotEmpty(builder, paramInfo, defaultImpl,
                                newImportList);
                        newImportList.add(defaultImportMap
                                .get(paramInfo.getCollectName()));
                    }
                    // using default to do it.
                } else {
                    if (paramInfo.getCollectName() != null) {
                        String defaultImpl = defaultCollections
                                .get(paramInfo.getCollectName());
                        if (defaultImpl != null) {
                            appendCollectNotEmpty(builder, paramInfo,
                                    defaultImpl, newImportList);
                            newImportList.add(defaultImportMap
                                    .get(paramInfo.getCollectName()));
                        } else {
                            appendCollectNotEmpty(builder, paramInfo,
                                    paramInfo.getCollectName(), newImportList);
                            newImportList.add(paramInfo.getCollectPackege());
                        }
                    } else {
                        // may be could get the construct of the class. get the
                        // constructor of the class.
                        String realPackage = paramInfo.getParams().get(0)
                                .getRealPackage();
                        // todo could add more to the default package values.
                        String s = defaultPacakgeValues.get(realPackage);
                        final PsiClass psiClassOfParameter = PsiTypesUtil.getPsiClass(parameter.getType());
                        if (s != null) {
                            builder.append(s);
                        }
                        else if (psiClassOfParameter!=null && psiClassOfParameter.isEnum()) {
                            final PsiField[] allFields = psiClassOfParameter.getAllFields();
                            Arrays.stream(allFields).findFirst().ifPresent(field -> builder.append(psiClassOfParameter.getName()).append(".").append(field.getName()));
                        }
                        else {
                            builder.append("new "
                                    + paramInfo.getParams().get(0).getRealName()
                                    + "()");
                        }
                        newImportList.add(realPackage);
                    }
                }

            }
            if (h != u) {
                builder.append(",");
            }
        }
        builder.append(")");
    }

    private static void appendCollectNotEmpty(StringBuilder builder,
                                              Parameters paramInfo, String defaultImpl,
                                              Set<String> newImportList) {
        builder.append("new " + defaultImpl + "<");
        for (int i = 0; i < paramInfo.getParams().size(); i++) {
            builder.append(paramInfo.getParams().get(i).getRealName());
            newImportList.add(paramInfo.getParams().get(i).getRealPackage());
            if (i != paramInfo.getParams().size() - 1) {
                builder.append(",");
            }
        }
        builder.append(">()");
    }

    private String findNextNotNull(PsiTypeElement psiType, String defaultName) {
        PsiElement nextSibling = psiType.getNextSibling();
        while (nextSibling != null
                && StringUtils.isBlank(nextSibling.getText())) {
            nextSibling = nextSibling.getNextSibling();
        }
        if (nextSibling == null) {
            return defaultName;
        }
        return nextSibling.getText().trim();
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor,
                               @NotNull PsiElement element) {
        boolean setter = generateAllHandler.isSetter();
        if (generateAllHandler.forBuilder()) {
            PsiClass localVarialbeContainingClass = getLocalVarialbeContainingClass(element);
            if (localVarialbeContainingClass == null) {
                return false;
            }
            PsiMethod[] methods = localVarialbeContainingClass.getMethods();
            for (PsiMethod method : methods) {
                if (method.getName().equals(CommonConstants.BUILDER_METHOD_NAME)&&method.hasModifierProperty(STATIC)) {
                    return true;
                }
            }
            return false;
        }
        Boolean validAsLocalVariableWithSetterOrGetterMethod = isValidAsLocalVariableWithSetterOrGetterMethod(element, setter);
        if (validAsLocalVariableWithSetterOrGetterMethod) {
            return validAsLocalVariableWithSetterOrGetterMethod;
        }
        if (generateAllHandler.isSetter() && generateAllHandler.isFromMethod()) {
            return isValidAsMethodWithSetterMethod(element);
        }

        return false;
    }

    @NotNull
    private Boolean isValidAsMethodWithSetterMethod(@NotNull PsiElement element) {
        PsiClass psiClass;
        PsiElement parentMethod = PsiTreeUtil.getParentOfType(element,
                PsiMethod.class);
        if (parentMethod == null) {
            return false;
        }
        PsiMethod method = (PsiMethod) parentMethod;
        // if is constructor the return type will be null.
        if (method.getReturnType() == null) {
            return false;
        }
        psiClass = PsiTypesUtil.getPsiClass(method.getReturnType());
        Parameters returnTypeInfo = PsiToolUtils
                .extractParamInfo(method.getReturnType());
        if (returnTypeInfo.getCollectPackege() != null && handlerMap
                .containsKey(returnTypeInfo.getCollectPackege())) {
            return true;
        }
        return PsiClassUtils.checkClassHasValidSetMethod(psiClass);
    }

    @NotNull
    private Boolean isValidAsLocalVariableWithSetterOrGetterMethod(@NotNull PsiElement element, boolean setter) {
        PsiClass psiClass = getLocalVarialbeContainingClass(element);
        if (psiClass == null) {
            return false;
        }
        if (setter) {
            return PsiClassUtils.checkClassHasValidSetMethod(psiClass);
        } else {
            return PsiClassUtils.checkClasHasValidGetMethod(psiClass);
        }
    }

    public static PsiClass getLocalVarialbeContainingClass(@NotNull PsiElement element) {
        PsiElement psiParent = PsiTreeUtil.getParentOfType(element,
                PsiLocalVariable.class);
        if (psiParent == null) {
            return null;
        }
        PsiLocalVariable psiLocal = (PsiLocalVariable) psiParent;
        if (!(psiLocal.getParent() instanceof PsiDeclarationStatement)) {
            return null;
        }
        PsiClass psiClass = PsiTypesUtil.getPsiClass(psiLocal.getType());
        return psiClass;
    }

    private static boolean notObjectClass(PsiClass psiClass) {
        if (psiClass.getQualifiedName().equals("java.lang.Object")) {
            return false;
        }
        return true;
    }

    @Nls
    @NotNull
    @Override
    public String getFamilyName() {
        return CommonConstants.GENERATE_SETTER_METHOD;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值