Storm实战之频繁二项集挖掘

    【Storm】Storm实战之频繁二项集挖掘

    • 商品编号:
      #61548767_615
      • 原价:
        免费
      • 会员价:
        免费
    • 版本:
      • V1.0.0
    • 数量:
      库存(不限)

    购物车中已存在此商品,请在购物车中操作单击跳转购物车

    • 开发环境:Eclipse
    • 源码架构:B/S
    • 数据库:Redis
    • 开发者:leesf
    • 开发语言:Java
    • 大小(M):10
    • 演示地址:http://www.cnblogs.com/leesf456/p/7161995.html
    • 开源协议:BSD协议
    • 编码格式:UTF-8
    • 版本工具管理:GitHub

    一、前言

      针对大叔据实时处理的入门,除了使用WordCount示例之外,还需要相对更深入点的示例来理解Storm,因此,本篇博文利用Storm实现了频繁项集挖掘的案例,以方便更好的入门Storm。

    二、基础知识

      2.1 频繁二项集挖掘

      如顾客去超市购物时,牙膏和牙刷基本上都是摆放在一起,因为购买牙膏时,很有可能会购买牙刷。另外,“啤酒与尿布”的案例则是对订单进行分析挖掘后发现的规律,将啤酒和尿布一起摆放会促进啤酒的销量。

      2.2 算法设计

      本示例中不考虑太复杂的挖掘算法,只考虑将两个商品组合后的挖掘,设计如下

        · 将每笔订单的商品按照两两分组。

        · 将每个分组的频度进行统计(不考虑商品的次序)。

        · 根据频度计算支持度(每个组合出现的频率越高,更有可能是频繁组合)和置信度(商品组合出现的置信程度)。

        · 设置支持度和置信度阈值,过滤不达标的数据。

      2.3 Storm设计思路

        · 使用Redis作为存储订单数据的数据库。

        · 使用Spout从Redis中读取订单数据。

        · 使用Bolt计算分组频度。

        · 使用Bolt计算支持度和置信度。

        · 使用Bolt筛选结果并存储到Redis中。

      2.4 拓扑结构图

      根据程序思路设计如下所示的拓扑结构,其组件在之后进行介绍。

    20170706130928874_34163

    三、设计实现

      3.1 实现步骤

      1. 产生订单数据

      通过模拟程序产生订单数据,并存储Redis中,即使用OrderGenerator来生成订单数据并存入Redis中,每个订单有四种不同商品及其数量组成。

      2. 接入订单数据

      通过OrderSpout读取Redis中的订单数据,以供拓扑结构下游的Bolt使用。

      3. 对订单中商品进行分组

      通过SplitBolt对订单中的商品进行分组,两两分组并构建商品对,发送元组至下游Bolt。

      4. 统计商品对总数

      使用PairTotalCountBolt对所有商品对数量进行统计(用于计算支持度),并发送元组至下游Bolt。

      5. 统计商品对及其出现次数

      使用PairCountBolt对商品对出现的次数进行统计,并发送元组至下游Bolt。

      6. 计算商品对支持度

      使用SupportComputeBolt对商品对的支持度进行计算,并发送元组至下游Bolt。

      7. 计算商品对置信度

      使用ConfidenceComputeBolt对商品对的置信度进行计算,并发送元组至下游Bolt。

      8. 过滤符合条件的商品对

      使用FilterBolt对符合条件的商品对进行过滤并存入redis,并发送元组至下游Bolt。

         3.1 源码分析

      下面给出拓扑结构中的各组件的源码并进行分析。

     1. OrderSpout

    package com.hust.grid.leesf.ordertest.spout;
    import java.util.Map;
    
    import org.json.simple.JSONArray;
    import org.json.simple.JSONObject;
    import org.json.simple.JSONValue;
    
    import com.hust.grid.leesf.ordertest.common.ConfKeys;
    import com.hust.grid.leesf.ordertest.common.FieldNames;
    
    import backtype.storm.spout.SpoutOutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichSpout;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Values;
    import redis.clients.jedis.Jedis;
    
    /**
     * 数据源,从redis读取订单
     * 
     * @author leesf
     *
     */
    public class OrderSpout extends BaseRichSpout {
        private static final long serialVersionUID = 1L;
    
        private SpoutOutputCollector collector;
        private Jedis jedis;
        private String host;
        private int port;
    
        public void open(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, SpoutOutputCollector collector) {
            this.collector = collector;
            this.host = conf.get(ConfKeys.REDIS_HOST).toString();
            this.port = Integer.parseInt(conf.get(ConfKeys.REDIS_PORT).toString());
            connectToRedis();
        }
    
        private void connectToRedis() {
            jedis = new Jedis(host, port);
            jedis.connect();
        }
    
        public void nextTuple() {
            String content = jedis.rpop("orders"); // 获取一条订单数据
    
            if (null == content || "nil".equals(content)) { // 若无,则等待300ms
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else { // 对订单数据进行转化
                JSONObject object = (JSONObject) JSONValue.parse(content);
                String id = object.get(FieldNames.ID).toString(); // 获取ID
                JSONArray items = (JSONArray) object.get(FieldNames.ITEMS); // 获取订单中的商品
    
                for (Object obj : items) { // 遍历订单中的商品
                    JSONObject item = (JSONObject) obj;
                    String name = item.get(FieldNames.NAME).toString(); // 商品名称
                    int count = Integer.parseInt(item.get(FieldNames.COUNT).toString()); // 商品数量
                    collector.emit(new Values(id, name, count)); // 发射订单号、商品名称、商品数量
    
                    if (jedis.hexists("itemCounts", name)) { // redis中存在name字段
                        jedis.hincrBy("itemCounts", name, 1); // 商品对应数量(订单中多个商品当作1个)增加1
                    } else { // redis中不存在name字段
                        jedis.hset("itemCounts", name, "1"); // 将name字段的值(商品数量)设置为1
                    }
                }
            }
        }
    
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 声明发射元组字段
            declarer.declare(new Fields(FieldNames.ID, FieldNames.NAME, FieldNames.COUNT));
        }
    }
    
    OrderSpout

      说明:OrderSpout会从redis中读取订单数据,并遍历订单中每个商品并发射,同时会统计商品数据并存入redis。

      2. CommandSpout

    package com.hust.grid.leesf.ordertest.spout;
    
    import java.util.Map;
    
    import com.hust.grid.leesf.ordertest.common.FieldNames;
    
    import backtype.storm.spout.SpoutOutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichSpout;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Values;
    
    /**
     * 统计支持度和置信度
     * 
     * @author leesf
     */
    public class CommandSpout extends BaseRichSpout {
        private static final long serialVersionUID = 1L;
    
        private SpoutOutputCollector collector;
    
        public void open(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, SpoutOutputCollector collector) {
            this.collector = collector;
        }
    
        public void nextTuple() {
            // 休眠5S后发射“statistics”
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            collector.emit(new Values("statistics"));
        }
    
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 声明元组字段
            declarer.declare(new Fields(FieldNames.COMMAND));
        }
    }
    
    CommandSpout

      说明:下游Bolt根据其发射的元组信息来统计支持度和置信度,其每5秒发射一次统计信号。

      3. SplitBolt 

    package com.hust.grid.leesf.ordertest.bolt;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import com.hust.grid.leesf.ordertest.common.FieldNames;
    
    import backtype.storm.task.OutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichBolt;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Tuple;
    import backtype.storm.tuple.Values;
    
    /**
     * 对订单中的商品进行两两组合并发送
     * 
     * @author leesf
     *
     */
    public class SplitBolt extends BaseRichBolt {
        private static final long serialVersionUID = 1L;
    
        private OutputCollector collector;
        private Map<String, List<String>> orderItems; // 存储订单及其商品
    
        public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
            orderItems = new HashMap<String, List<String>>();
        }
    
        public void execute(Tuple tuple) {
            // 获取订单号和商品名称
            String id = tuple.getStringByField(FieldNames.ID);
            String newItem = tuple.getStringByField(FieldNames.NAME);
    
            if (!orderItems.containsKey(id)) { // 不包含该订单
                // 新生商品链表
                ArrayList<String> items = new ArrayList<String>();
                // 添加商品
                items.add(newItem);
    
                orderItems.put(id, items);
    
                return;
            }
            // 包含订单,取出订单中包含的商品
            List<String> items = orderItems.get(id);
            for (String existItem : items) { // 遍历商品
                // 将元组中提取的商品与订单中已存在的商品组合后发射
                collector.emit(createPair(newItem, existItem));
            }
            // 添加新的商品
            items.add(newItem);
        }
    
        private Values createPair(String item1, String item2) { // 按照指定顺序生成商品对
            if (item1.compareTo(item2) > 0) {
                return new Values(item1, item2);
            }
    
            return new Values(item2, item1);
        }
    
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 声明元组字段
            declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2));
        }
    }
    
    SplitBolt

      说明:其将每个订单的两两商品进行组合,然后发射。

      4. PairTotalCountBolt  

    package com.hust.grid.leesf.ordertest.bolt;
    import java.util.Map;
    
    import com.hust.grid.leesf.ordertest.common.FieldNames;
    
    import backtype.storm.task.OutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichBolt;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Tuple;
    import backtype.storm.tuple.Values;
    
    /**
     * 计算商品对总数
     * 
     * @author leesf
     *
     */
    public class PairTotalCountBolt extends BaseRichBolt {
        private static final long serialVersionUID = 1L;
    
        private OutputCollector collector;
        private int totalCount; // 商品对总数
    
        public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
            totalCount = 0;
        }
    
        public void execute(Tuple tuple) {
            totalCount++; // 每收到一个元组,便增加商品对总数
            collector.emit(new Values(totalCount)); // 发射商品对总数
        }
    
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 声明元组字段
            declarer.declare(new Fields(FieldNames.TOTAL_COUNT));
        }
    }
    
    PairTotalCountBolt

      说明:其用于统计所有商品对的数量(用于后面支持度的计算)。

      5. PairCountBolt

    package com.hust.grid.leesf.ordertest.bolt;
    import java.util.HashMap;
    import java.util.Map;
    
    import com.hust.grid.leesf.ordertest.common.FieldNames;
    import com.hust.grid.leesf.ordertest.common.ItemPair;
    
    import backtype.storm.task.OutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichBolt;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Tuple;
    import backtype.storm.tuple.Values;
    
    /**
     * 计算商品对出现的次数
     * 
     * @author leesf
     *
     */
    public class PairCountBolt extends BaseRichBolt {
        private static final long serialVersionUID = 1L;
    
        private OutputCollector collector;
        private Map<ItemPair, Integer> pairCounts; // 存储商品对及其出现的次数
    
        public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
            this.pairCounts = new HashMap<ItemPair, Integer>();
        }
    
        public void execute(Tuple tuple) {
            String item1 = tuple.getStringByField(FieldNames.ITEM1);
            String item2 = tuple.getStringByField(FieldNames.ITEM2);
    
            ItemPair itemPair = new ItemPair(item1, item2);
            int pairCount = 0;
    
            if (pairCounts.containsKey(itemPair)) { // 包含商品对
                // 取出商品对出现的次数
                pairCount = pairCounts.get(itemPair);
            }
            // 更新出现次数
            pairCount++;
    
            pairCounts.put(itemPair, pairCount);
    
            collector.emit(new Values(item1, item2, pairCount));
        }
    
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 声明元组字段
            declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2, FieldNames.PAIR_COUNT));
        }
    }
    
    PairCountBolt

      说明:其用于统计每个商品对出现的次数,然后发射。

      6. SupportComputeBolt  

    package com.hust.grid.leesf.ordertest.bolt;
    import java.util.HashMap;
    import java.util.Map;
    
    import com.hust.grid.leesf.ordertest.common.FieldNames;
    import com.hust.grid.leesf.ordertest.common.ItemPair;
    
    import backtype.storm.task.OutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichBolt;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Tuple;
    import backtype.storm.tuple.Values;
    
    /**
     * 计算商品对的支持度
     * 
     * @author leesf
     *
     */
    public class SupportComputeBolt extends BaseRichBolt {
        private static final long serialVersionUID = 1L;
    
        private OutputCollector collector;
        private Map<ItemPair, Integer> pairCounts; // 存储商品对及其出现的次数
        private int pairTotalCount; // 商品对总数
    
        public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
            pairCounts = new HashMap<ItemPair, Integer>();
            pairTotalCount = 0;
        }
    
        /**
         * 由于SupportComputeBolt订阅了多个流,其需要根据不同的字段做出不同的行为
         */
        public void execute(Tuple tuple) {
            if (tuple.getFields().get(0).equals(FieldNames.TOTAL_COUNT)) { // 对应PairTotalCountBolt
                // 取出商品对总数量
                pairTotalCount = tuple.getIntegerByField(FieldNames.TOTAL_COUNT);
            } else if (tuple.getFields().size() == 3) { // 对应PairCountBolt
                // 取出商品及其商品对出现的次数
                String item1 = tuple.getStringByField(FieldNames.ITEM1);
                String item2 = tuple.getStringByField(FieldNames.ITEM2);
                int pairCount = tuple.getIntegerByField(FieldNames.PAIR_COUNT);
                // 存储商品对及其次数
                pairCounts.put(new ItemPair(item1, item2), pairCount);
            } else if (tuple.getFields().get(0).equals(FieldNames.COMMAND)) { // 对应CommandSpout
                for (ItemPair itemPair : pairCounts.keySet()) { // 遍历商品对
                    // 计算商品支持度,使用商品对出现的次数除以商品对总数量
                    double itemSupport = (double) (pairCounts.get(itemPair).intValue()) / pairTotalCount;
    
                    collector.emit(new Values(itemPair.getItem1(), itemPair.getItem2(), itemSupport));
                }
            }
        }
    
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 定义元组字段
            declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2, FieldNames.SUPPORT));
        }
    
    }
    
    SupportComputeBolt

      说明:计算每个商品对的支持度,并且发射支持度。

      7. ConfidenceComputeBolt 

    package com.hust.grid.leesf.ordertest.bolt;
    import java.util.HashMap;
    import java.util.Map;
    
    import com.hust.grid.leesf.ordertest.common.ConfKeys;
    import com.hust.grid.leesf.ordertest.common.FieldNames;
    import com.hust.grid.leesf.ordertest.common.ItemPair;
    
    import backtype.storm.task.OutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichBolt;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Tuple;
    import backtype.storm.tuple.Values;
    import redis.clients.jedis.Jedis;
    
    /**
     * 计算商品对的置信度
     * 
     * @author leesf
     */
    public class ConfidenceComputeBolt extends BaseRichBolt {
        private static final long serialVersionUID = 1L;
    
        private OutputCollector collector;
        private Map<ItemPair, Integer> pairCounts; // 存储商品对及其出现的次数
    
        private String host;
        private int port;
        private Jedis jedis;
    
        public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
            this.host = conf.get(ConfKeys.REDIS_HOST).toString();
            this.port = Integer.parseInt(conf.get(ConfKeys.REDIS_PORT).toString());
            pairCounts = new HashMap<ItemPair, Integer>();
            connectToRedis();
        }
    
        private void connectToRedis() {
            jedis = new Jedis(host, port);
            jedis.connect();
        }
    
        /**
         * 由于ConfidenceComputeBolt订阅了多个流,其需要根据元组不同的字段做出不同的行为
         */
        public void execute(Tuple tuple) {
            if (tuple.getFields().size() == 3) { // 对应PairCountBolt
                // 取出商品对及其出现次数
                String item1 = tuple.getStringByField(FieldNames.ITEM1);
                String item2 = tuple.getStringByField(FieldNames.ITEM2);
                int pairCount = tuple.getIntegerByField(FieldNames.PAIR_COUNT);
    
                pairCounts.put(new ItemPair(item1, item2), pairCount);
            } else if (tuple.getFields().get(0).equals(FieldNames.COMMAND)) { // 对应CommandSpout,需要进行统计
                for (ItemPair itemPair : pairCounts.keySet()) { // 遍历商品对
                    // 从redis中取出商品对中商品出现的次数
                    double item1Count = Integer.parseInt(jedis.hget("itemCounts", itemPair.getItem1()));
                    double item2Count = Integer.parseInt(jedis.hget("itemCounts", itemPair.getItem2()));
                    double itemConfidence = pairCounts.get(itemPair).intValue();
    
                    // 计算商品对置信度
                    if (item1Count < item2Count) {
                        itemConfidence /= item1Count;
                    } else {
                        itemConfidence /= item2Count;
                    }
    
                    collector.emit(new Values(itemPair.getItem1(), itemPair.getItem2(), itemConfidence));
                }
            }
        }
    
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 声明元组字段
            declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2, FieldNames.CONFIDENCE));
        }
    }

      说明:计算商品对的置信度,并且发射置信度。

      8. FilterBolt

    package com.hust.grid.leesf.ordertest.bolt;
    import java.util.Map;
    
    import org.json.simple.JSONObject;
    
    import com.hust.grid.leesf.ordertest.common.ConfKeys;
    import com.hust.grid.leesf.ordertest.common.FieldNames;
    import com.hust.grid.leesf.ordertest.common.ItemPair;
    
    import backtype.storm.task.OutputCollector;
    import backtype.storm.task.TopologyContext;
    import backtype.storm.topology.OutputFieldsDeclarer;
    import backtype.storm.topology.base.BaseRichBolt;
    import backtype.storm.tuple.Fields;
    import backtype.storm.tuple.Tuple;
    import backtype.storm.tuple.Values;
    import redis.clients.jedis.Jedis;
    
    /**
     * 过滤符合条件的商品对并存入redis
     * 
     * @author leesf
     *
     */
    public class FilterBolt extends BaseRichBolt {
        private static final long serialVersionUID = 1L;
    
        // 商品对的支持度和置信度
        private static final double SUPPORT_THRESHOLD = 0.01;
        private static final double CONFIDENCE_THRESHOLD = 0.01;
    
        private OutputCollector collector;
    
        private Jedis jedis;
        private String host;
        private int port;
    
        public void prepare(@SuppressWarnings("rawtypes") Map conf, TopologyContext context, OutputCollector collector) {
            this.collector = collector;
            this.host = conf.get(ConfKeys.REDIS_HOST).toString();
            this.port = Integer.parseInt(conf.get(ConfKeys.REDIS_PORT).toString());
            connectToRedis();
        }
    
        private void connectToRedis() {
            jedis = new Jedis(host, port);
            jedis.connect();
        }
    
        @SuppressWarnings("unchecked")
        public void execute(Tuple tuple) {
            // 取出商品并构造商品对
            String item1 = tuple.getStringByField(FieldNames.ITEM1);
            String item2 = tuple.getStringByField(FieldNames.ITEM2);
            ItemPair itemPair = new ItemPair(item1, item2);
            String pairString = itemPair.toString();
    
            double support = 0;
            double confidence = 0;
    
            if (tuple.getFields().get(2).equals(FieldNames.SUPPORT)) { // 对应SupportComputeBolt
                // 获取支持度并存入redis
                support = tuple.getDoubleByField(FieldNames.SUPPORT);
                jedis.hset("supports", pairString, String.valueOf(support));
            } else if (tuple.getFields().get(2).equals(FieldNames.CONFIDENCE)) { // 对应ConfidenceComputeBolt
                // 获取置信度并存入redis
                confidence = tuple.getDoubleByField(FieldNames.CONFIDENCE);
                jedis.hset("confidences", pairString, String.valueOf(confidence));
            }
    
            if (!jedis.hexists("supports", pairString) || !jedis.hexists("confidences", pairString)) { // 商品对的支持度和置信度还未计算完成,返回
                return;
            }
            // 商品对的支持度和置信度已经计算完成
            support = Double.parseDouble(jedis.hget("supports", pairString));
            confidence = Double.parseDouble(jedis.hget("confidences", pairString));
    
            if (support >= SUPPORT_THRESHOLD && confidence >= CONFIDENCE_THRESHOLD) { // 支持度和置信度超过阈值
                // 将该商品对信息存入redis中
                JSONObject pairValue = new JSONObject();
                pairValue.put(FieldNames.SUPPORT, support);
                pairValue.put(FieldNames.CONFIDENCE, confidence);
    
                jedis.hset("recommendedPairs", pairString, pairValue.toJSONString());
    
                collector.emit(new Values(item1, item2, support, confidence));
            } else { // 不高于阈值,则从redis中删除
                jedis.hdel("recommendedPairs", pairString);
            }
        }
    
        public void declareOutputFields(OutputFieldsDeclarer declarer) {
            // 声明元组字段
            declarer.declare(new Fields(FieldNames.ITEM1, FieldNames.ITEM2, FieldNames.SUPPORT, FieldNames.CONFIDENCE));
        }
    }
    
    FilterBolt

      说明:判断支持度和置信度是否超过了阈值,若超过则需要存入redis,否则,从redis中删除。

    四、程序运行

      4.1. 环境依赖

      打开redis服务器、客户端(方便观看结果)和zookeeper。

      4.2. 写入订单数据

      运行OrderGenerator,生成并写入订单数据,通过redis查看,结果如下

        

    表示已经成功写入了订单数据。

      4.3. 运行任务拓扑

      运行OrderTopology,其会根据订单中的商品数据,生成并写入推荐商品对,通过redis查看,结果如下

     

        可以看到运行完成后,已经成功生成了推荐商品方案。

    使用说明

    1. 将项目导入Eclipse中
    2. 打开Redis服务器和客户端、Zookeeper服务器
    3. 运行OrderGenerator
    4. 运行OrderTopology


    权利声明:本站所有商品信息、客户评价等信息是初心商城重要的数据资源,未经许可,禁止非法转载使用。 注:本站商品信息均来自初心商城,其真实性、准确性和合法性由初心商城负责。

                    初心源说明:初心商城主要为程序员提供开发基础的代码源以及成熟项目,网站中所有的商品有提供收费版本的, 也有提供免费版本的,按照大家各自不同的需求进行购买。实实在在的让程序员只用专注于自己的业务实现你的小梦想, 如果您对我们的成果表示认同并且觉得对你有所帮助我们愿意接受来自各方面的支持^_^。

                    支持:用手机扫描二维码支付

                    支付宝支持我们 微信支持我们

                    您的支持将被用于:
                    1、持续深入的上传更多更好的源代码
                    2、建立更加完善的技术社区
                    3、完善现在系统出现各种问题
                    4、购买域名和租赁服务器

                    1、交易规则

                    2、发货方式

                    1、自动:在上方保障服务中标有自动发货的商品,拍下后,将会自动收到来自卖家的商品获取(下载)链接

                    2、手动:在上方保障服务中标有手动发货的商品,拍下后,卖家会收到邮件,也可通过QQ或订单中的电话联系对方。

                    3、退款说明

                    1、描述:源码描述(含标题)与实际源码不一致的(例:描述PHP实际为ASP、描述的功能实际缺少、版本不符等)

                    2、演示:有演示站时,与实际源码小于95%一致的(但描述中有"不保证完全一样、有变化的可能性"类似显著声明的除外)

                    3、发货:手动发货源码,在卖家未发货前,已申请退款的

                    4、服务:卖家不提供安装服务或需额外收费的(但描述中有显著声明的除外)

                    5、其它:如质量方面的硬性常规问题等

                    备注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。交易中的商品,卖家无法对描述进行修改!

                    4、注意事项

                    1、客户买完之后未确认收货,将不会收到下载地址和下载码,确认收货之后才能收到下载地址和下载码。

                    2、在未拍下前,双方在QQ上所商定的内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准);

                    3、在商品同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外);

                    4、在没有"无任何正当退款依据"的前提下,写有"一旦售出,概不支持退款"等类似的声明,视为无效声明;

                    5、虽然交易产生纠纷的几率很小,但请尽量保留如聊天记录这样的重要信息,以防产生纠纷时出现问题不明确的情况。

                    5、交易声明

                    1、本站作为直卖平台,依据交易合同(商品描述、交易前商定的内容)来保障交易的安全及买卖双方的权益;

                    2、非平台线上交易的商品,出现任何后果均与本站无关;无论卖家以何理由要求线下交易的,请联系管理举报。

                    初心Logo

                    初心商城| 初心系列| 初心博客| 版本历史| 通知公告| 系统反馈

                    © 2016-2019 上海吾永网络科技有限公司 保留所有权利 沪ICP备19028491号
                    违法和不良信息举报电话:186-2950-9347,本网站所列数据,除特殊说明,所有数据均出自我工作室
                    本网站兼容所有主流浏览器,不支持手机自适应

                    返回顶部小火箭