业务背景

最近一段时间,都在做接口对接,项目也差不多上线了,正好也总结下自己的想法和思考。

项目的主要目的是给我公司店铺的店长、员工、和业务品牌领导提供一个入口,可以很方便直观地看到店铺之间,员工之间的一些排名情况,比如展示某一段时间内品牌内店员销售排行、店员当月kpi数据、店铺所有店员单月销售排行、还有一些PK接口(店铺pk和员工pk)pk维度目前包括如下。

销售金额
销售订单数
客单价
附加值/连带率
VIP会员数
普通会员注册数
指定款号+色号销售
指定销售单价+1分
指定款号销售
指定SKU规则销售积分
普通会员注册数,销售额大于0
每个店铺销售TOP名次列表
会员入会率
正价销售额度PK积分
区域开卡销售额PK积分
复购率
留存率

说到底我的工作就是根据用户的请求参数,根据入参判断做一些逻辑的判断,分别调用不同的业务,返回对应的json数据给前端(这里指企业微信公众号)展示。

一般做法

上面大致了解了大致的业务需求,下面开始剖析接口设计的方法,一般情况下接口开发的流程大致如下所示:

这里写图片描述

上面这种逻辑应付下面两种类型的接口开发是没问题的
1. 品牌内店员销售排行
2. 业绩查询接口

因为这两种类型的接口都比较单一,客户端可以把所有的参数发送到后台即可,比如
http://host/shoppk/PersonalCompass.action?data={“shopNumber”:”1072”,”clerkNumber”:”1072_018”,”year”:”2017”,”month”:”1”}&actType=list 表示店员当月每天销售数据
这里写图片描述

这种方式的好处:简单并且方便

但是对于上面的pk接口来说,就会存在以下问题:
1. 大量代码重复

上面pk接口已经列举了17种不同的pk维度,员工、店铺之间的pk 2*17 = 34 这样就需要提供34个接口,如果按照上面这种形式来开发的话,起码要提供34个接口,这里可能就会存在很多重复性的代码。

2.http请求参数过多,占用网络资源,传输速度慢

比如以店铺之间进行销售金额pk,那么按照传统方法的话,http请求中需要穿 开始时间、结束时间、参与pk的店铺编号shopNumber
(”3602”,”3604”,”3607”,”3608”,”3609”,”3610”,”3611”,”3613”) ;如果pk的店铺过多的话,这个shopNumber 可能会过长。 如果这些参数全部通过http传输,肯定会有效率问题

3.扩展性不好

起初pk的维度并没有这么多,起初可能就前面几个常用的维度,随着各品牌事业部对于店铺、店员的考核需求不同,后面会陆续增加其它的维度,如果按照传统设计,那么新增一个维度pk就需要开发一个新接口。

4.不够灵活

对于前端来讲,需要根据不同的pk维度传输不同的参数,这其实也是一个不好的设计,客户端的职责过多,对于前端界面设计也不美观。

改进做法

如上面所列举的一些缺点,很明显pk接口明显不能用一般方法的去设计开发。其实仔细分析,我们可以发现一些共同点,都是pk业务,不同的地方是pk的类型(店铺、员工),和pk的时间范围,还有pk的关联sku等。

其实可以把每个pk请求表示一个活动,不同pk业务参数可以放在活动关联的配置详情表,活动关联的sku表中。以后调用pk接口的时候,只需要提供一个统一的pk接口,不同的pk维度传输不同的activityId,后台拿到activityId后,可以判断activityId的类型,是店铺还是员工? 还可以查询activityId关联的的详情(开始日期,介绍日期,哪些sku参与pk,哪些店铺参与pk)。改进后的接口流程图如下所示:
这里写图片描述

业务层的逻辑判断:

这里写图片描述

下面以复购率,留存率这两个pk维度来讲解下我这边设计的开发工作。

下面是几个数据库表的设计

1 数据库设计

活动配置表:

CREATE TABLE
    tb_activity_config
    (
        id INT NOT NULL AUTO_INCREMENT COMMENT 'ID',
        activity_id INT NOT NULL COMMENT '活动ID',
        pk_unit INT NOT NULL COMMENT 'PK单位(1:店铺之间PK  2:员工之间PK)',
        pk_type INT NOT NULL COMMENT 'PK类型',
        pk_dimension INT NOT NULL COMMENT 'PK维度',
        range_region INT COMMENT '参与活动区域',
        range_city VARCHAR(30) COMMENT '参与活动的城市',
        ranking_top INT DEFAULT '1500' COMMENT '排行榜显示名次',
        show_top INT DEFAULT '100' COMMENT '显示排名数',
        sale_top TINYINT DEFAULT '3' COMMENT '销售显示的TOP名额',
        sale_amount DECIMAL(10,2) COMMENT '销售单金额',
        sale_number INT DEFAULT '0' COMMENT '销售件数',
        sale_discount DOUBLE DEFAULT '0.895' COMMENT '正价销售折扣',
        sale_denominator DECIMAL(10,2) DEFAULT '1200.00' COMMENT '销售基数分母',
        is_nickname TINYINT(3) DEFAULT '1' COMMENT '是否显示昵称(0:不显示 1:显示)',
        del_flg TINYINT(3) DEFAULT '0' COMMENT '删除FLG  (0:正常  1:已删除)',
        create_date INT COMMENT '创建时间',
        update_date INT COMMENT '更新时间',
        PRIMARY KEY (id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='店铺活动设置表';

活动详情表:

CREATE TABLE
    tb_activity_desc
    (
        activity_id INT NOT NULL AUTO_INCREMENT COMMENT '活动ID',
        activity_name VARCHAR(50) NOT NULL COMMENT '活动名称',
        t_brand VARCHAR(10) COMMENT '所属品牌',
        start_time DATETIME NOT NULL COMMENT '开始时间',
        end_time DATETIME NOT NULL COMMENT '结束时间',
        award VARCHAR(100) COMMENT '活动奖品',
        picurl VARCHAR(150) NOT NULL COMMENT '图文封面图',
        thumb_picurl VARCHAR(150) NOT NULL COMMENT '封面缩略图',
        activity_desc text COMMENT '活动描述',
        end_flg TINYINT(3) DEFAULT '0' COMMENT '结果公布FLG (0: 未公布 1:已公布)',
        result_image VARCHAR(90) COMMENT '结果公布图片',
        orderby INT COMMENT '排序号',
        del_flg TINYINT(3) DEFAULT '0' COMMENT '删除FLG(0:正常; 1:已删除)',
        create_date INT COMMENT '创建时间',
        update_date INT COMMENT '更新时间',
        PRIMARY KEY (activity_id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='店铺活动表';

活动维度配置表:

CREATE TABLE
    tb_activity_dimen
    (
        id INT NOT NULL AUTO_INCREMENT COMMENT 'ID',
        pk_type INT NOT NULL COMMENT 'PK类型ID(1:销售PK ; 2:CRM PK ; 3:分享PK)',
        pk_type_name VARCHAR(10) COMMENT 'PK类型名称',
        pk_dimension_id INT NOT NULL COMMENT 'PK维度ID',
        pk_dimension VARCHAR(30) NOT NULL COMMENT 'PK维度',
        del_flg TINYINT(3) DEFAULT '0' COMMENT '删除FLG  (0:正常  1:已删除)',
        create_date INT COMMENT '创建时间',
        update_date INT COMMENT '更新时间',
        PRIMARY KEY (id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='店铺活动PK维度表';

活动店铺关联表:

CREATE TABLE
    tb_activity_shopids
    (
        id INT NOT NULL AUTO_INCREMENT COMMENT 'ID',
        activity_id INT NOT NULL COMMENT '活动ID',
        shop_id VARCHAR(10) NOT NULL COMMENT '店铺ID',
        shop_name VARCHAR(50) NOT NULL COMMENT '店铺名称',
        create_date DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
        PRIMARY KEY (id),
        INDEX shop_id (shop_id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='活动参与店铺名单表';

活动关联的商品sku表:

CREATE TABLE
    tb_activity_sku
    (
        id INT NOT NULL AUTO_INCREMENT COMMENT 'ID',
        activity_id INT COMMENT '活动ID',
        commodity_number VARCHAR(15) COMMENT '货品编号',
        product_color_id VARCHAR(4) COMMENT '商品色号',
        PRIMARY KEY (id),
        INDEX commodity_number (commodity_number, product_color_id)
    )
    ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='PK活动商品SKU表';

2 活动配置表造数据

通过活动配置界面, 新增一个员工 复购率pk的活动(包含pk店铺,开始日期、介绍日期等)

3 通过维度得知对应的activity_id
通过sql查询:

 // 根据维度查询活动配置
    SELECT  c.activity_id,d.* FROM tb_activity_config c left join  tb_activity_desc d
    on c.activity_id = d.activity_id 
    WHERE pk_unit = 2 and pk_dimension = 116 

这里写图片描述

4 编写业务sql计算复购率

原始复购率表结构

这里写图片描述

由于计算业务比较复杂,这边就不多做介绍,直接贴上sql,其实就是对一张单表的各种过滤查询

select a.ClerkCode,sum((b.RePurchaseCount -a.RePurchaseCount)*0.5 + (b.RePurchaseRate -a.RePurchaseRate)*0.5) as totalScore from
 (SELECT a.ClerkCode,a.RePurchaseCount,a.RePurchaseRate
FROM report_rerurchase a
where 1=1 and  a.StatisticsDate = 
(SELECT distinct( a.StatisticsDate) FROM report_rerurchase a
 where 1=1 and  a.StatisticsDate>= 20180101  and a.StatisticsDate<= 20180110
 and a.StoreCode in ('3602','3604','3607') 
 and a.ClerkCode!='' order by 1 asc limit 1 )  
and a.StoreCode in ('3602','3604','3607')
and a.ClerkCode!='') a inner join   
( SELECT b.ClerkCode,b.RePurchaseCount,b.RePurchaseRate
FROM report_rerurchase b
where 1=1 and  b.StatisticsDate =
(SELECT distinct( a.StatisticsDate)
 FROM report_rerurchase a
 where 1=1 and  a.StatisticsDate>= 20180101  and a.StatisticsDate<= 20180110
 and a.StoreCode in ('3602','3604','3607')
 and a.ClerkCode!=''
 order by 1 desc
 limit 1 
)  
and b.StoreCode in ('3602','3604','3607') 
and b.ClerkCode!='') b

on a.ClerkCode =  b.ClerkCode  
group by a.ClerkCode
order by totalScore desc

统计结果如下:

这里写图片描述

5 编写Java后台业务逻辑

确定sql无误后,可以开始编写Java后台的业务逻辑代码,这里只贴出几个关键的逻辑代码

控制层:

这里写图片描述

缓存层:

这里写图片描述

因为从复购率这些数据从crm表中每天凌晨抽取到中间表report_rerurchase中,所以查出来的数据当天是不会变的,这里可以把数据放到本地缓存中 ,等下一次查询的时候,直接从缓存中拿数据。加快查询速度,并且减少数据库服务器的压力。

业务层:

这里写图片描述

数据层:

/****
     * 店员复购率查询 
     * @param startTime  开始日期
     * @param endTime   结束日期
     * @param shopIds   店铺ID 集合
     * @param rankingTop 排名
     * @return
     */
    public List<Map<String, Object>> getRePurchaseRateTopListByClerk(String startTime, String endTime,
            List<String> shopIds, int rankingTop) {
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        DBFactory dbFactory = new ShoppkCrmTestDBFactory();
        DBObject db = null;
        try {
            db = dbFactory.getDBObjectR();

            startTime = startTime.split(" ")[0].replace("-", "");
            Integer start = Integer.parseInt(startTime);
            endTime = endTime.split(" ")[0].replace("-", "");
            Integer end = Integer.parseInt(endTime);
            StringBuffer sb = new StringBuffer();

            sb.append(" select a.ClerkCode as ClerkCode,sum((b.RePurchaseCount -a.RePurchaseCount)*0.5 + (b.RePurchaseRate -a.RePurchaseRate)*0.5) as totalScore from");
            sb.append(" (SELECT a.ClerkCode,a.RePurchaseCount,a.RePurchaseRate ");
            sb.append(" FROM report_rerurchase a ");
            sb.append(" where 1=1 and  a.StatisticsDate= ");
            sb.append(" (SELECT distinct( a.StatisticsDate) FROM report_rerurchase a ");
            sb.append("  where 1=1 and  a.StatisticsDate>=?  and a.StatisticsDate<=? ");
            sb.append(" and a.StoreCode in ("+SqlKit.parseInQuery(shopIds.size())+")");
            sb.append(" and a.ClerkCode!='' order by 1 asc limit 1 ) ");
            sb.append(" and a.StoreCode in ("+SqlKit.parseInQuery(shopIds.size())+")");
            sb.append(" and a.ClerkCode!='') a inner join  ");
            sb.append(" (SELECT b.ClerkCode,b.RePurchaseCount,b.RePurchaseRate ");
            sb.append(" FROM report_rerurchase b ");
            sb.append(" where 1=1 and  b.StatisticsDate=");
            sb.append(" (SELECT distinct( a.StatisticsDate) FROM report_rerurchase a ");
            sb.append("  where 1=1 and  a.StatisticsDate>=?  and a.StatisticsDate<=? ");
            sb.append(" and a.StoreCode in ("+SqlKit.parseInQuery(shopIds.size())+")");
            sb.append(" and a.ClerkCode!='' order by 1 desc limit 1 ) ");
            sb.append(" and b.StoreCode in ("+SqlKit.parseInQuery(shopIds.size())+")");
            sb.append(" and b.ClerkCode!='') b on a.ClerkCode =  b.ClerkCode  ");
            sb.append(" group by a.ClerkCode ");
            sb.append(" order by totalScore desc ");


            PreparedStatement ps = db.getPreparedStatement(sb.toString());
            ps.setInt(1, start);
            ps.setInt(2, end);
            for (int i = 0; i < shopIds.size(); i++) {
                ps.setString(i + 3, shopIds.get(i));
            }
            for (int i = 0; i < shopIds.size(); i++) {
                ps.setString(shopIds.size() + 3 + i, shopIds.get(i));
            }

            ps.setInt(shopIds.size()*2 + 3, start);
            ps.setInt(shopIds.size()*2 + 4, end);
            for (int i = 0; i < shopIds.size(); i++) {
                ps.setString(shopIds.size()*2 + 5 + i, shopIds.get(i));
            }
            for (int i = 0; i < shopIds.size(); i++) {
                ps.setString(shopIds.size()*3 + 5 + i, shopIds.get(i));
            }

            ResultSet rs = db.executePstmtQuery();

            int top = 1;
            while (rs.next()) {
                Map<String, Object> map=new HashMap<String, Object>();
                map.put("clerkCode", rs.getString("ClerkCode"));
                map.put("totalScore", rs.getDouble("totalScore"));
                map.put("top", String.valueOf(top));
                top++ ;
                list.add(map);
            }
            rs.close();
        } catch (Exception e) {
            log.error("", e);
        } finally {
            try {
                db.close();
            } catch (SQLException sqle) {
                log.error("", sqle);
            }
        }
        return list;
    }

业务代码ok,启动服务器,发送http请求http://test.applets.teadmin.net:8080/shoppk/ShopPkTopList.action?activityId=314,只需要传输activityId=314参数 即可

响应结果是所有店铺根据复购率大小进行倒序排序.

这里写图片描述

总结

传统的流程不能满足的话,就需要思考能不能有其他方法解决这个问题,上面的思路是多了一个中间层的概念,加了活动配置表,所有的请求参数都封装在这些配置中,后台只需要拿到activityId 即可。大大简化了前端的工作,也提升了系统的扩展性、灵活性。

GitHub 加速计划 / js / json
41.72 K
6.61 K
下载
适用于现代 C++ 的 JSON。
最近提交(Master分支:1 个月前 )
960b763e 4 个月前
8c391e04 7 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐