利用观察者模式,实现qq邮箱推送(可利用定时器添加多个推送)

业务需求

1.要有一个关于超时推送的定时器,可随时关闭及维护
2.推送类型: 体检报告、资料审核、培训审核等等
3.推送的内容做成模板(创建一张表)要求可以维护而且模板可分为多个用户类型模板
4.推送可以维护提醒间隔时间(创建一张表),例如:下次体检为2020-10-09,距离下次体检30天提醒一次,15天提醒一次
5.可以根据提醒间隔来设置是否生成工单
6.而且要有发送记录(创建一张表)

注意:可能写的不好,还望多指教,框架用的ssm,因为在自己开发中遇到的需求,做一下笔记,完整代码有点多,有很多实体类没有写,就是那些表的截图,大致实现方式就是这样的,也可以再添加其它的推送技术,如:短信、公众号,都可以,只要推送类继承Observer 观察者实现update方法即可

使用的工具类为hutool工具类连接:https://www.hutool.cn/


设计模式之观察者模式理论:https://blog.csdn.net/weixin_43861630/article/details/108978667

1.在resources文件下的config文件中新建mail.setting文件

hutool文档地址:https://www.hutool.cn/docs/#/extra/%E9%82%AE%E4%BB%B6%E5%B7%A5%E5%85%B7-MailUtil?id=%e5%8f%91%e9%80%81%e9%82%ae%e4%bb%b6

1)mail.setting

# 邮件服务器的SMTP地址,可选,默认为smtp.<发件人邮箱后缀>
host = smtp.qq.com
# 邮件服务器的SMTP端口,可选,默认25
port = 25
# 发件人(必须正确,否则发送失败)
from = 发件人邮箱   例如: 昵称<qq号@qq.com>
# 用户名,默认为发件人邮箱前缀
user = qq号
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码---在这里是SMTP授权码)
pass = 授权码

2.pom

要引入第三方jar

      <!-- hutool 工具类 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.4.0</version>
        </dependency>
        <!-- 邮箱第三方依赖 -->
        <dependency>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
            <version>1.4.7</version>
        </dependency>

3.实体类创建

1.)创建Observer 抽象观察者

package com.hx.slj.modules.zyb.ObserverModel;

/**
 * @author qb
 * @version 1.0
 * @Description
 * 抽象观察者
 * @date 2020/8/24 13:51
 */
public interface Observer {

    public void update(PushEntity pushEntity);
}

2.)创建Observerable抽象被观察者

package com.hx.slj.modules.zyb.ObserverModel;

/**
 * @author qb
 * @version 1.0
 * @Description
 * 抽象被观察者
 * 声明添加、删除、通知观察者方法
 * @date 2020/8/24 13:49
 */
public interface Observerable {

    /**
     * 添加
     * @param o
     */
    public void registerObserver(Observer o);

    /**
     * 删除
     * @param o
     */
    public void removeObserver(Observer o);

    /**
     * 通知观察者
     */
    public void notifyObserver(PushEntity pushEntity);

}

3.)创建PushEntity 观察者通知参数

最后用于通知企业用到的相关字段,没有将qq邮箱写入,业务量不多,所以就每次执行通知创建线程后查询出qq邮箱执行发送即可。

package com.hx.slj.modules.zyb.ObserverModel;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author qb
 * @version 1.0
 * @Description 观察者通知参数
 * @date 2020/8/24 17:52
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PushEntity {

    /**
     * 企业id
     */
    private String id;


    /**
     * 触发的提醒间隔id
     */
    private String txjgId;


    /**
     * 各种报告id
     */
    private String bgId;

    /**
     * 是否生成工单
     */
    private String hasWo;

    /**
     * 推送类型
     */
    private String pushType;

}

4.)创建OvertimePushServer 观察者主题

package com.hx.slj.modules.zyb.ObserverModelImpl;

import com.hx.slj.modules.zyb.ObserverModel.Observer;
import com.hx.slj.modules.zyb.ObserverModel.Observerable;
import com.hx.slj.modules.zyb.ObserverModel.PushEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;


/**
 * @author qb
 * @version 1.0
 * @Description 观察主题
 * @date 2020/8/24 14:10
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OvertimePushServer implements Observerable {

    private List<Observer> list = new ArrayList<>();

    @Override
    public void registerObserver(Observer o) {
        System.err.println(o);
        list.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        if(!list.isEmpty()){
           list.remove(o);
        }
    }

    @Override
    public void notifyObserver(PushEntity pushEntity) {
        for(int i=0;i<list.size();i++){
            Observer observer = list.get(i);
            observer.update(pushEntity);
        }
    }
}

5.)创建zybPushEntity 超时推送实体类

此实体类是查询的数据库返回的数据实体,sql在下面,在OvertimePush超时推送的定时器类(foreachList方法)中要用到,这样写的原因是所有的推送类型如:体检、培训等都可以公用此实体,

package com.hx.slj.modules.zyb.ObserverModel;

import com.hx.slj.modules.zyb.entity.ZybWoTxjg;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * @author qb
 * @version 1.0
 * @Description 超时推送公用实体
 * @date 2020/9/1 10:28
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class zybPushEntity {

    private String id;

    /**
     * 企业id
     */
    private String enterpriseid;

    /**
     * 下次时间
     */
    private Date nextTime;

    /**
     * 实体类型
     */
    private String entityType;

    /**
     * 提醒间隔 实体类
     */
    private ZybWoTxjg zybWoTxjg;

}

6.)创建TemplateParsing 自定义模板解析类

自定义模板解析,详细讲解连接:https://blog.csdn.net/weixin_43861630/article/details/108241924

package com.hx.slj.modules.zyb.TemplateParsing;

import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author qb
 * @version 1.0
 * @Description 模板解析 {{name}} ==》 name(赋值后)
 * @date 2020/8/24 15:51
 */
public class TemplateParsing {

    /**
     * 模板解析
     * @param content
     * @param map
     * @return
     */
    public static String renderString(String content, Map<String,String> map){
        Set<Map.Entry<String,String>> sets = map.entrySet();
        try{
            for(Map.Entry<String,String> entry : sets){
                //定义正则
                String regex = "\\{\\{" + entry.getKey() + "\\}\\}";
                //创建匹配模式
                Pattern pattern = Pattern.compile(regex);
                //将要匹配的内容加入进行匹配的
                Matcher matcher = pattern.matcher(content);
                //将匹配的结果替换为map的value值
                content = matcher.replaceAll(entry.getValue() == null ? "null" : entry.getValue());
            }
        }catch (ClassCastException e){
            new ClassCastException("格式错误,模板解析时map都应为string类型");
            return "格式错误,模板解析时map中的value都应为string类型";
        }
        return content;
    }


}

7.)创建推送工具类 只写了qq邮箱

package com.hx.slj.modules.zyb.utils;

import cn.hutool.extra.mail.MailUtil;

import java.io.File;
import java.util.ArrayList;

/**
 * @author qb
 * @version 1.0
 * @Description 推送工具类
 * @date 2020/8/19 15:24
 */
public class PushUtil {

    /**
     * 邮箱工具类,mail配置文件中,指定邮箱类型
     * @param to   收件人
     * @param subject 邮件主题
     * @param content  邮寄内容(可以是文本,也可以是html)
     * @param isHtml  是否是html
     * @param files   附件
     */
    public static void SendMail(String to, String subject, String content, Boolean isHtml, File... files){
        if(files == null || files.length <=0){
            MailUtil.send(to, subject, content, isHtml);
        }else{
            MailUtil.send(to, subject, content, isHtml,files);
        }
    }

}

4.创建qq推送类继承观察者

package com.hx.slj.modules.zyb.ObserverModelImpl;


import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.hx.slj.common.utils.CacheUtils;
import com.hx.slj.modules.zyb.ObserverModel.Observer;
import com.hx.slj.modules.zyb.ObserverModel.PushEntity;
import com.hx.slj.modules.zyb.TemplateParsing.TemplateParsing;
import com.hx.slj.modules.zyb.config.WoProjectTypeEnum;
import com.hx.slj.modules.zyb.entity.*;
import com.hx.slj.modules.zyb.service.*;
import com.hx.slj.modules.zyb.utils.BeanUtil;
import com.hx.slj.modules.zyb.utils.PushUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

/**
 * @author qb
 * @version 1.0
 * @Description
 * @date 2020/8/24 13:54
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QQPush implements Observer {

    @Autowired
    private zybEnterpriseVoService zybEnterpriseVoService;

    @Autowired
    private zybWoService zybWoService;

    @Autowired
    private ZybWoFsjlService zybWoFsjlService;
    /**
     * 是否是html标识
     */
    public Boolean isHtml = false;


    @Override
    public void update(PushEntity pushEntity) {
        //创建线程  所有推送不共用一个线程,
       new Thread(() -> {
       //相关业务实现,给成自己的业务
           if(zybEnterpriseVoService == null){
               zybEnterpriseVoService = BeanUtil.getBean(zybEnterpriseVoService.class);
           }
           zybEnterpriseVo zybEnterprise01 = new zybEnterpriseVo();
           //设置企业id
           zybEnterprise01.setId(pushEntity.getId());
           //设置报告id
           zybEnterprise01.setTbgId(pushEntity.getBgId());
           //根据id获取企业信息及执法人员邮箱
           zybEnterpriseVo zybEnterprise;
           switch (pushEntity.getPushType()){
               case "ZYB_QYTJXX" :
                   zybEnterprise = zybEnterpriseVoService.selectQyItemAndZfryEamilById(zybEnterprise01);
                   break;
               case "ZYB_PJXX" :
                   zybEnterprise = zybEnterpriseVoService.selectQyItemAndZfryEamilByIdPjxx(zybEnterprise01);
                   break;
               default:
                   zybEnterprise = null;
                   break;
           }
           if(zybEnterprise == null){
               System.err.println("查询的企业信息为null");
           }else{
                    //获取动态泛型
                    String PushType = pushEntity.getPushType();
                    //设置项目名称
                    zybEnterprise.setXmmc(WoProjectTypeEnum.matchOpCode(PushType).getName());
                    //设置下次上传报告时间
                   if(zybEnterprise.getNextTime() != null){
                       //date时间转str时间
                       String dateStr = DateUtil.format(zybEnterprise.getNextTime(),"yyyy-MM-dd");
                       zybEnterprise.setTDate(dateStr);
                       //解决模板解析时,因为value不是string型而产生报错
                       zybEnterprise.setNextTime(null);
                   }
                   //将数据序列化成键值对  注:序列化时value只能使用string型,如果是别的类型 模板解析时会报错
                   HashMap hashMap = JSON.parseObject(JSON.toJSONString(zybEnterprise,SerializerFeature.WriteMapNullValue),HashMap.class);
                   //System.err.println("数据序列化后的map:"+hashMap);
                   List<ZybWoMb> zybWoMbList = (List<ZybWoMb>)CacheUtils.get("ZYB_WO_MB","ZYB_WO_MB_LIST");
                   //System.err.println("从缓存中获取qq发送时的模板 zybWoMbList:   "+zybWoMbList);
                   zybWoMbList.forEach((item) ->{
                       if(item.getTxjgId().equals(pushEntity.getTxjgId())){
                           //将序列化好的数据进行解析
                           String result = TemplateParsing.renderString(item.getMbNr(),hashMap);
                           //根据mb_status状态执行模板
                           switch (item.getMbStatus()){
                               case "0" :
                                   if(!StrUtil.hasEmpty(zybEnterprise.getEamil())){
                                       //发送邮箱
                                       System.err.println("企业人员邮箱: "+zybEnterprise.getEamil()+
                                               "   邮箱主题: "+item.getMbZt()+"   邮箱模板内容: "+result+"  是否是html:"+isHtml);
                                       PushUtil.SendMail(zybEnterprise.getEamil(),item.getMbZt(),result,isHtml);
                                       System.err.println("企业发送完毕!");

                                   }else{
                                       System.err.println("企业邮箱为空或者null!");
                                   }
                               break;
                               case "1" :
                                   if(!StrUtil.hasEmpty(zybEnterprise.getZfryEmail())){
                                       //发送邮箱
                                       System.err.println("执法人员邮箱: "+zybEnterprise.getZfryEmail()+
                                               "   邮箱主题: "+item.getMbZt()+"   邮箱模板内容: "+result+"  是否是html:"+isHtml);
                                       PushUtil.SendMail(zybEnterprise.getZfryEmail(),item.getMbZt(),result,isHtml);
                                       System.err.println("执法人员发送完毕!");
                                   }else{
                                       System.err.println("执法人员邮箱为空或者null!");
                                   };
                                   //判断是否生成功工单
                                   if("1".equals(pushEntity.getHasWo())){
                                       //String zybWoId = IdUtil.simpleUUID();
                                        zybEnterprise.setTyZd(item.getMbZt());
                                        int row =createWo(zybEnterprise,pushEntity);
                                        System.err.println("创建工单返回值 row: "+row);
                                   }
                               default:
                                   break;
                           }

                       }
                   });
               //插入发送记录
               int row = createFsjl(zybEnterprise,pushEntity);
               System.err.println("插入发送记录返回结果 row:  "+row);
           }
       }).start();
    }

    /**
     * 创建工单
     * @param zybEnterprise  企业实体
     * @param pushEntity   观察者参数
     * @return
     */
    public int createWo(zybEnterpriseVo zybEnterprise,PushEntity pushEntity){
        if(zybWoService == null){
            zybWoService = BeanUtil.getBean(zybWoService.class);
        }
        //创建工单
        zybWo zybWo = new zybWo();
        String StrType;
        switch (pushEntity.getPushType()){
            case "ZYB_QYTJXX":
                StrType = "体检";
                break;
            case "ZYB_PJXX" :
                StrType = "评价";
                break;
            case "ZYB_JCXX" :
                StrType = "检测";
                break;
            case "ZYB_PXXX" :
                StrType = "培训";
                break;
            case  "ZYB_STS" :
                StrType ="三同时";
                break;
            default:
                StrType = "其他";
                break;
        }
        zybWo.setWoName(zybEnterprise.getCompanyName()+"-"+StrType+"("+zybEnterprise.getTyZd()+")");
        zybWo.setWoType("0");
        zybWo.setEnterpriseId(zybEnterprise.getId());
        zybWo.setReceiveUserId(zybEnterprise.getZfryId());
        zybWo.setCreateTime(new Date());
        zybWo.setProjectId(pushEntity.getBgId());
        zybWo.setProjectType(WoProjectTypeEnum.matchOpCode(pushEntity.getPushType()).toString());
        //插入工单记录
        ZybWoItem zybWoItem = new ZybWoItem();
        String zybWoItemId = IdUtil.simpleUUID();
        zybWoItem.setId(zybWoItemId);
        zybWoItem.setCreateTime(new Date());
        zybWoItem.setWoType("0");
        zybWoItem.setCreateBy("1");
        return zybWoService.insertSelective(zybWo,zybWoItem);
    }

    /**
     * 插入发送记录
     * @param zybEnterpriseVo  企业实体类
     * @param pushEntity  观察者参数
     * @return
     */
    public int createFsjl(zybEnterpriseVo zybEnterpriseVo,PushEntity pushEntity){
        if(zybWoFsjlService == null){
            zybWoFsjlService = BeanUtil.getBean(ZybWoFsjlService.class);
        }
        ZybWoFsjl zybWoFsjl = new ZybWoFsjl();
        String zybWofsjlId = IdUtil.simpleUUID();
        zybWoFsjl.setId(zybWofsjlId);
        zybWoFsjl.setEnterpriseId(zybEnterpriseVo.getId());
        zybWoFsjl.setTxjgId(pushEntity.getTxjgId());
        zybWoFsjl.setFssj(new Date());
        zybWoFsjl.setProjectType(WoProjectTypeEnum.matchOpCode(pushEntity.getPushType()).toString());
        zybWoFsjl.setCflx("0");
        zybWoFsjl.setProjectId(pushEntity.getBgId());
        return zybWoFsjlService.insertSelective(zybWoFsjl);
    }





}

5.集成quartz定时器

quartz换成你写好的定时器

package com.hx.slj.modules.zyb.job;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.hx.slj.common.utils.CacheUtils;
import com.hx.slj.modules.zyb.ObserverModel.PushEntity;
import com.hx.slj.modules.zyb.ObserverModel.zybPushEntity;
import com.hx.slj.modules.zyb.ObserverModelImpl.OvertimePushServer;
import com.hx.slj.modules.zyb.ObserverModelImpl.QQPush;
import com.hx.slj.modules.zyb.entity.ZybWoMb;
import com.hx.slj.modules.zyb.entity.zybEnterpriseVo;
import com.hx.slj.modules.zyb.entity.zybPjxx;
import com.hx.slj.modules.zyb.entity.zybQytjxx;
import com.hx.slj.modules.zyb.service.*;
import com.hx.slj.modules.zyb.utils.BeanUtil;
import com.hx.slj.modules.zyb.utils.PushUtil;
import net.sf.ehcache.Cache;
import org.apache.poi.ss.formula.functions.T;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.quartz.QuartzJobBean;

import java.util.*;

/**
 * @author qb
 * @version 1.0
 * @Description 超时推送
 * @date 2020/8/19 17:35
 */
public class OvertimePush  extends QuartzJobBean {

    @Autowired
    private ZybQyTtxxVoService zybQyTtxxVoService;

    @Autowired
    private zybWombService zybWombService;

    @Autowired
    private zybPjxxService zybPjxxService;

    @Autowired
    private zybPxxxService zybPxxxService;

    @Autowired
    private zybJcxxVoService zybJcxxVoService;

    @Override
    protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
    //以下4个判断,是解决定时器注入的service为null,
        if(zybQyTtxxVoService == null){
            zybQyTtxxVoService = BeanUtil.getBean(ZybQyTtxxVoService.class);
        }
        if(zybPjxxService == null){
            zybPjxxService = BeanUtil.getBean(zybPjxxService.class);
        }
        if(zybPxxxService == null){
            zybPxxxService = BeanUtil.getBean(zybPxxxService.class);
        }
        if(zybJcxxVoService == null){
            zybJcxxVoService = BeanUtil.getBean(zybJcxxVoService.class);
        }
        //获取缓存  这是推送模板的缓存
        Cache cache = CacheUtils.getCacheN("ZYB_WO_MB");
        //System.err.println("获取cache:  "+ cache);
        //判断缓存是否存在
        if(ObjectUtil.isNull(cache)){
            if(zybWombService == null){
                zybWombService = BeanUtil.getBean(zybWombService.class);
            }
            //获取所有的模板
            List<ZybWoMb> zybWoMbList =  zybWombService.getwombListPush();
            //System.err.println("获取的模板zybWoMbList:  "+zybWoMbList);
            //创建cache
            CacheUtils.getCache("ZYB_WO_MB");
            //添加模板缓存  list
            CacheUtils.put("ZYB_WO_MB","ZYB_WO_MB_LIST",zybWoMbList);
            //System.err.println("设置缓存后获取的内容ZybWoMb  :"+CacheUtils.get("ZYB_WO_MB","ZYB_WO_MB_LIST"));
        }
        //后台获取企业即将超时体检数据
        List<zybPushEntity> qyList = zybQyTtxxVoService.selectOverTime();
        //获取企业即将超时评价数据
        List<zybPushEntity> zybPjxxList = zybPjxxService.selectOverTime();
        //获取企业即将超时数据  --培训
        List<zybPushEntity> zybPxxxList = zybPxxxService.selectOverTime();
        //获取企业即将超时数据  --检测
        List<zybPushEntity> zybJcxxList = zybJcxxVoService.selectOverTime();
        //企业是否有超时推送数据 ---体检
        if(qyList.size() > 0){
            foreachList(qyList,"ZYB_QYTJXX");
        }
        //企业是否有超时推送数据 ---评价
        if(zybPjxxList.size()>0){
            foreachList(zybPjxxList,"ZYB_PJXX");
        }
        //企业是否有超时推送数据 ---培训
        if(zybPxxxList.size()>0){
            foreachList(zybPxxxList,"ZYB_PXXX");
        }
        //企业是否有超时推送数据 ---检测
        if(zybJcxxList.size()>0){
            foreachList(zybJcxxList,"ZYB_JCXX");
        }
        System.err.println("执行推送定时!");
    }

    /**
     * 循环赋值
     * @param list  实体集合
     * @param pushType  推送类型
     */
    public void foreachList(List<zybPushEntity> list,String pushType){
        //实例化观察者
        QQPush qqPush = new QQPush();
        //实例化观察主题
        OvertimePushServer overtimePushServer = new OvertimePushServer();
        //添加观察者
        overtimePushServer.registerObserver(qqPush);
        //循环赋值观察者参数
        for(zybPushEntity item:list){
            //实例化观察者参数
            PushEntity pushEntity = new PushEntity();
            //观察者需要通知的企业id
            pushEntity.setId(item.getEnterpriseid());
            //观察者需要用的 提醒间隔id
            pushEntity.setTxjgId(item.getZybWoTxjg().getId());
            //观察者用到的各种报告id
            pushEntity.setBgId(item.getId());
            //观察者用到的推送类型
            pushEntity.setPushType(pushType);
            //观察者检查是否生成工单
            pushEntity.setHasWo(item.getZybWoTxjg().getHasWo().toString());
            //观察者执行通知
            overtimePushServer.notifyObserver(pushEntity);
        }

    }
}

难点怎么确定30天和15天发没发送过

sql

在写sql时遇到的难点,以下下sql,可以将发送记录中存在的数据(15、30天等)去除,只取发送记录中不存在的数据
project_type='ZYB_QYTJXX’此条件为获取体检数据

<select id="selectOverTime" resultType="com.hx.slj.modules.zyb.ObserverModel.zybPushEntity">
    select
    a.id as "id",
    a.enterpriseId as "enterpriseid",
    a.nexttjTime as "nextTime",
    b.id as "zybWoTxjg.id",
    b.txts as "zybWoTxjg.txts",
    b.has_wo as "zybWoTxjg.hasWo"

    from zyb_qytjxx a cross join zyb_wo_txjg b where nexttjTime &lt; date_add(SYSDATE(), INTERVAL b.txts day)
    and not exists(select 1 from zyb_wo_fsjl c where c.txjg_id=b.id and c.project_id=a.id and project_type='ZYB_QYTJXX')
    and  a.nexttjTime &gt; '2020-08-01'
  </select>

效果图及数据库截图

1.数据库目录截图
在这里插入图片描述

2.生成工单的效果图
在这里插入图片描述
3.发送记录数据库截图
在这里插入图片描述

4.模板截图
在这里插入图片描述
5.提醒间隔
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值