在线交易平台---配额管理模块的设计

    1、 配额管理模块设计的思路:

          a、引入账户模型。通过引入账户模型,可以通过控制配额账户的活动,我们可以允许透支,可以规定额度,也可以冻结其账户。另外系统可以追溯配额的使用情况,来龙去脉。

          b、引入生产者和消费者模型。配额的产生和配额的使用是两个不同的活动。我把前一种活动产生的配额叫生产配额,后一种叫消费配额。

          生产配额是有期限的,过期的生产配额是无法使用的,同时生产配额必须记录它已经使用了多少配额,这通过持有消费配额的集合 来达到目的。

         与此相反,消费配额没有有效期,但是它必须记录配额在什么时候被使用。

         通过这种方式,可以计算某个消费配额的剩余额度,也容易地作废某些过期配额。

 

 

    2、集体实现

        首先是 QuotaEntery(配额条目),做为配额增加、减少、使用、调整、作废等情况的。

/**
 * 配额条目
 */
public class QuotaEntry {
    /** 项目类型*/
    protected QuotaEntryType entryType;
    /** 额度,可正,可负 */
    protected int amount;
    /** 创建时间*/
    private Date createTime;

    protected QuotaAccount account;


    public QuotaEntryType getEntryType() {
        return entryType;
    }

    public void setEntryType(QuotaEntryType entryType) {
        this.entryType = entryType;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public QuotaAccount getAccount() {
        return account;
    }

    public void setAccount(QuotaAccount account) {
        this.account = account;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

 

    其次是ConsumeQuotaEntry(消费者)和ProduceQuotaEntry(生产者)。他们继承QuotaEntry,不同的是ProdcuceQuotaEntry记录配额的产生,ConsumeQuotaEntry则是记录配额的减少,

 

/**
 * 生产配额 条目
 */
public class ProduceQuotaEntry extends QuotaEntry implements Comparable{
    /**
     * 有效期
     */
    public static int DEFALUT_LIFE_YEAR = 2;
    /**
     * 配额的有效期
     */
    private Date beginTime;

    private Date endTime;

    private List consumeEntries = new ArrayList(10);

    public Date getBeginTime() {
        return beginTime;
    }

    public void setBeginTime(Date beginTime) {
        this.beginTime = beginTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public void setEndTime(Date endTime) {
        this.endTime = endTime;
    }

    /**
     * 是否是可用的配额条目
     */
    public boolean isUsable() {
        Date current = new Date();
        return  current.getTime() >= beginTime.getTime() && current.getTime() <= endTime.getTime() &&
                 caclRemainQuota() > 0;
    }

    /**
     * 是否是过期的配额
     * @return
     */
    public boolean isOverdue(){
        return new Date().getTime() > endTime.getTime();
    }

    /**
     * 计算当前条目剩下可用的配额
     * @return
     */
    public int caclRemainQuota(){
       int used = 0;
       for(ConsumeQuotaEntry entry:consumeEntries){
             used += entry.getAmount();
       }
       return amount + used;
    }


    public int compareTo(ProduceQuotaEntry o) {
        if(this.getEndTime().getTime() > o.getEndTime().getTime()){
            return 1;
        }
        if(this.getEndTime().getTime() < o.getEndTime().getTime()){
            return -1;
        }
        return 0;
    }

    public void addConsumeEntry(ConsumeQuotaEntry consume) {
        consumeEntries.add(consume);
        consume.setProduce(this);
    }


}

 

/**
 * 消费配额
 */
public class ConsumeQuotaEntry extends QuotaEntry {

    /**减少配额 所依据的 消费配额额 */
    private ProduceQuotaEntry produce;

    public ProduceQuotaEntry getProduce() {
        return produce;
    }
    public void setProduce(ProduceQuotaEntry produce) {
        this.produce = produce;
    }
}

 

    QuotaEntryType,枚举类,记录 导致配额增加,减少的业务种类

public enum QuotaEntryType {
    TransferFrom, //转入
    TransferTo,   //转出
    Deduct,       //扣减
    Restore,      //归还
    Init,             //初始
    Decrease,     //减少
    Increase,      //增加
    Repeal;         //作废
}

 

   QuotaEntryFactory,配额条目工厂,屏蔽创建配额条目的细节

 

public class QuotaEntryFactory {
     /**
     * 返回 过期作废的配额条目
     * @param amount
     * @return
     */
    public static ConsumeQuotaEntry getRepealEntry(int amount) throws QuotaException {
       return getReducedEntry(amount,QuotaEntryType.Repeal);
    }

    /**
     * 返回 转出的 配额
     * @param amount
     * @return
     * @throws QuotaException
     */
    public static ConsumeQuotaEntry getTransferToEntry(int amount) throws QuotaException {
       return getReducedEntry(amount,QuotaEntryType.TransferTo);
    }

    /**
     * 返回 扣减的配额
     * @param amount
     * @return
     * @throws QuotaException
     */
    public static ConsumeQuotaEntry getDeductEntry(int amount) throws QuotaException {
        return getReducedEntry(amount,QuotaEntryType.Deduct);
    }

    /**
     * 返回 减少的配额
     * @param amount
     * @return
     * @throws QuotaException
     */
    public static ConsumeQuotaEntry getDecreaseEntry(int amount) throws QuotaException {
        return getReducedEntry(amount,QuotaEntryType.Decrease);
    }
    /**
     * 返回 归还的配额
     * @param consume
     * @return
     * @throws QuotaException
     */
    public static ConsumeQuotaEntry getRestoreEntry(ConsumeQuotaEntry consume) throws QuotaException {
        ConsumeQuotaEntry newConsume = new ConsumeQuotaEntry();
        newConsume.setEntryType(QuotaEntryType.Restore);
        newConsume.setAmount(-consume.getAmount());
        newConsume.setCreateTime(new Date());
        return newConsume;

    }

    public static ConsumeQuotaEntry getReducedEntry(int amount,QuotaEntryType type) throws QuotaException {
        check(amount);
        ConsumeQuotaEntry consume = new ConsumeQuotaEntry();
        consume.setEntryType(type);
        consume.setCreateTime(new Date());
        consume.setAmount(-amount);
        return consume;
   }


    public static ProduceQuotaEntry getInitEntry(int amount) throws QuotaException {
        check(amount);
        return getRaisedEntry(amount,QuotaEntryType.Init);

    }

    public static ProduceQuotaEntry getIncreaseQuotaEntry(int amount) throws QuotaException {
        check(amount);
        return getRaisedEntry(amount,QuotaEntryType.Increase);
    }

    public static ProduceQuotaEntry getTransferFromEntry(int amount) throws QuotaException {
           check(amount);
           return getRaisedEntry(amount,QuotaEntryType.TransferFrom);
    }
    public static ProduceQuotaEntry getRaisedEntry(int amount,QuotaEntryType type){
           ProduceQuotaEntry entry = new ProduceQuotaEntry();
           entry.setEntryType(type);
           Calendar c = Calendar.getInstance();
           entry.setBeginTime(c.getTime());
           c.add(Calendar.YEAR, ProduceQuotaEntry.DEFALUT_LIFE_YEAR);
           entry.setEndTime(c.getTime());
           entry.setAmount(amount);
           entry.setCreateTime(new Date());
           return entry;
    }


     private static void check(int amount) throws QuotaException {
        if (amount < 0) throw new QuotaException(QuotaMsg.QuotaMustBeMoreThanZero.getName());
    }
}

 

    QuotaException(配额异常消息类)

 

public class QuotaException extends Exception {
    public QuotaException(String s) {
        super(s);
    }
}

 

   QuotaMsg(各种异常消息)

 

public enum QuotaMsg {
    AccountHasBeenFrozen {
        public String getName() {
            return "账户已被冻结";
        }
    },
    AccountCantBeOverDraft {
        public String getName() {
            return "账户不可透支";
        }
    },
    ItHasBeyondLimitQuota {
        public String getName() {
            return "已经超过透支额度";
        }
    },
    QuotaMustBeMoreThanZero {
        public String getName() {
            return "配额必须大于0";
        }
    },
    AnAccountOnlyHasAnInitEntry {
        public String getName() {
            return "一个账户只有一条Init记录";
        }
    };

    public abstract String getName();
}

 

    QuotaAccount,业务逻辑发生的地方

 

/**
 * 配额账户..
 */
public class QuotaAccount {
    /** 账户名称 */
    private String name;
    /** 是否冻结 */
    private boolean frozen;
    /** 是否可以透支 */
    private boolean overdrawn;
    /** 透支额度 */
    private Long limit;
    /** 配额条目 */
    private List quotaEntries = new ArrayList(16);

     /**
     * 购买商品后,扣减配额
     * @param amount 额度
     */
    public List deduct(int amount) throws QuotaException {
        checkAccount(amount);
        //可用的生产配额
        List avaiEntries = findAvaiEntry();
        //生成的消费配额
        List consumeList = reduceQuota(avaiEntries,amount,QuotaEntryType.Deduct);
        return consumeList;

    }

    /**
     * 计算剩余配额
     * @return
     */
    public int caclOddQuota(){
        int count = 0;
        for(QuotaEntry entry:quotaEntries){
            count += entry.getAmount();
        }
        return count;
    }

    /**
     * 归还配额
     * @param consume 消费配额
     * @throws QuotaException
     */
    private void restore(ConsumeQuotaEntry consume) throws QuotaException {
        //1、找到对应的生产积分条目
        ProduceQuotaEntry produce = consume.getProduce();
        //2、生成一条 归还条目
        ConsumeQuotaEntry newConsume = QuotaEntryFactory.getRestoreEntry(consume);
        //3、把积分写到指定生产配额下
        produce.addConsumeEntry(newConsume);
        //4、添加到账户下
        addQuotaEntry(newConsume);
    }
    /**
     * 归还配额
     * @param consumeList 消费条目列表
     * @throws QuotaException
     */
    public void restore(List consumeList) throws QuotaException {
        for(ConsumeQuotaEntry consume:consumeList){
            restore(consume);
        }
    }

    /**
     * 作废生产配额
     * @param produce 生产配额
     * @return
     * @throws QuotaException
     */
    public ConsumeQuotaEntry repeal(ProduceQuotaEntry produce) throws QuotaException {
        int remain = produce.caclRemainQuota();
        ConsumeQuotaEntry consume = QuotaEntryFactory.getRepealEntry(remain);
        produce.addConsumeEntry(consume);
        addQuotaEntry(consume);
        return consume;
    }

    /**
     * 作废所有过期的配额
     * @return
     */
    public void repeal() throws QuotaException {
        for(QuotaEntry entry:quotaEntries) {
            if(ProduceQuotaEntry.class.isInstance(entry)){
                ProduceQuotaEntry produce = (ProduceQuotaEntry)entry;
                if(produce.isOverdue() && produce.caclRemainQuota() > 0){
                    repeal(produce);
                }
            }
        }
    }


    /**
     * 把 一定数量的配额 转移到 另一个账户上
     *
     * @param account 目标账户
     * @param amount  额度
     * @throws QuotaException
     */
    public void transferTo(QuotaAccount account, int amount) throws QuotaException {
        checkAccount(amount);
        List avaiEntries = findAvaiEntry();
        List consumeList= reduceQuota(avaiEntries,amount,QuotaEntryType.TransferTo);
        account.transferFrom(consumeList);
    }

    /**
     * 接收 从某个账户转移过来的额度
     *
     */
    public void transferFrom(List  consumeList) throws QuotaException {
        int amount =  0;
        for(ConsumeQuotaEntry consume:consumeList){
           amount += consume.getAmount();
        }
        amount = Math.abs(amount);
        transferFrom(amount);

    }
    private void transferFrom(int amount) throws QuotaException {
        ProduceQuotaEntry produce = QuotaEntryFactory.getTransferFromEntry(amount);
        addQuotaEntry(produce);
    }


    /**
     * 增加 配额
     *
     * @param amount
     */
    public ProduceQuotaEntry increaseQuota(int amount) throws QuotaException {
        ProduceQuotaEntry produce =  QuotaEntryFactory.getIncreaseQuotaEntry(amount);
        addQuotaEntry(produce);
        return produce;
    }
    /**
     * 减少 配额
     * produce 生产配额
     * @param amount
     */
    public void decreaseQuota(ProduceQuotaEntry produce,int amount) throws QuotaException {
        checkAccount(amount);
        if(produce.caclRemainQuota()  findAvaiEntry(){
        List avaiEntries = new ArrayList(10);
        for(QuotaEntry entry:quotaEntries){
            if(!ProduceQuotaEntry.class.isInstance(entry)) break;
            ProduceQuotaEntry produce = (ProduceQuotaEntry) entry;
            if(produce.caclRemainQuota() > 0 && produce.isUsable()){
                avaiEntries.add(produce);
            }
        }
        Collections.sort(avaiEntries);
        return avaiEntries;
    }
    /**
     * 创建初始配额账户
     *
     * @param amount
     */
    public void init(int amount) throws QuotaException {
        if (hasInitEntry()) throw new QuotaException(QuotaMsg.AnAccountOnlyHasAnInitEntry.getName());
       addQuotaEntry(QuotaEntryFactory.getInitEntry(amount));
    }

    public boolean hasInitEntry() {
        boolean has = false;
        for (QuotaEntry entry : quotaEntries) {
            if (entry.getEntryType().equals(QuotaEntryType.Init)) {
                has = true;
                break;
            }
        }
        return has;
    }

    /**
     * 从一群 可用的而生产配额中,减去 配额
     * @param avaiEntries
     * @param amount
     * @return  扣减记录
     * @throws QuotaException
     */
    private List reduceQuota(List avaiEntries,int amount,QuotaEntryType type) throws QuotaException {
        List consumeList= new ArrayList(4);
        int remain =amount;
        int toReduce = amount;
        Iterator iterator = avaiEntries.iterator();
        while(iterator.hasNext()){
            ProduceQuotaEntry produce = iterator.next();
            remain -=  produce.caclRemainQuota();
            ConsumeQuotaEntry consume= null;
            //如果还没有扣完,而且还有配额条目
            if(remain > 0 &&  iterator.hasNext()){
                 consume = QuotaEntryFactory.getReducedEntry(produce.getAmount(),type);
                 toReduce = amount - produce.getAmount();
            //如果没有扣完,也没有其他配额条目,
            }else if(remain > 0 && !iterator.hasNext()){
                 consume = QuotaEntryFactory.getDeductEntry(toReduce);
            }else if(remain <= 0){
                 consume = QuotaEntryFactory.getDeductEntry(toReduce);
            }
            produce.addConsumeEntry(consume);
            addQuotaEntry(consume);
            consumeList.add(consume);

        }
        return consumeList;
    }

    /**
     * 检验配额是是否足够
     * @param amount
     * @throws QuotaException
     */
    private void checkAccount(int amount) throws QuotaException {
        if (isFrozen()) throw new QuotaException(QuotaMsg.AccountHasBeenFrozen.getName());
        if (!isOverdrawn() && caclOddQuota() < amount)
            throw new QuotaException(QuotaMsg.AccountCantBeOverDraft.getName());
        if (isOverdrawn() && (caclOddQuota() + limit < amount))
            throw new QuotaException(QuotaMsg.ItHasBeyondLimitQuota.getName());
    }

    /**
     * 添加配额条目
     * @param quotaEntry
     */
    private void addQuotaEntry(QuotaEntry quotaEntry){
        quotaEntries.add(quotaEntry);
        quotaEntry.setAccount(this);
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isFrozen() {
        return frozen;
    }

    public void setFrozen(boolean frozen) {
        this.frozen = frozen;
    }

    public boolean isOverdrawn() {
        return overdrawn;
    }

    public void setOverdrawn(boolean overdrawn) {
        this.overdrawn = overdrawn;
    }

    public Long getLimit() {
        return limit;
    }

    public void setLimit(Long limit) {
        this.limit = limit;
    }


    public List getQuotaEntries() {
        return quotaEntries;
    }

    public void setQuotaEntries(List quotaEntries) {
        this.quotaEntries = quotaEntries;
    }
}

    测试类

 

public class QuotaAccountTest {

    private QuotaAccount account;
    private QuotaAccount accountTwo;

    @Before
    public void setUp() throws QuotaException, ParseException {
        account = new QuotaAccount();
        account.increaseQuota(34);
        account.setFrozen(false);
        account.setLimit(10L);
        account.setOverdrawn(true);
        account.setName("小明的账户");

        accountTwo = new QuotaAccount();
        accountTwo.increaseQuota(34);
        accountTwo.setFrozen(false);
        accountTwo.setLimit(10L);
        accountTwo.setOverdrawn(true);
        accountTwo.setName("小明的账户2");

    }
    @After
    public void tearDown(){
        account =null;
        accountTwo = null;
    }
    @Test
    public void caclRemainQuota(){
        assertEquals(34, account.caclOddQuota());

    }

    @Test
    public void caclRemainQuotaAfterDeducting4Quotas() throws QuotaException {
        account.deduct(4);
        assertEquals(30,account.caclOddQuota());
    }
    @Test
    public void caclRemainQuotaAfterDeducting44Quotas() throws QuotaException {
        account.deduct(44);
        assertEquals(-10,account.caclOddQuota());
    }

    @Test(expected = QuotaException.class)
    public void caclRemainQuotaAfterDeductingAndAccountItIsNotOverdrawn() throws QuotaException {
        account.setOverdrawn(false);
        account.deduct(44);
    }

    @Test
    public void caclRemainQuotaAfterRestoreQuota() throws QuotaException {
        List consumes =account.deduct(44);
        assertEquals(-10,account.caclOddQuota());
        account.restore(consumes);
        assertEquals(34,account.caclOddQuota());

    }
       @Test
    public void caclRemainQuotaAferIncreasingQuotaAndDecutingQuota() throws QuotaException {
        account.increaseQuota(20);
        List consumes =account.deduct(44);
        assertEquals(10,account.caclOddQuota());

    }

    @Test
    public void transferSmallQuotas() throws QuotaException {
        account.transferTo(accountTwo,12);
        assertEquals(22,account.caclOddQuota());
        assertEquals(46,accountTwo.caclOddQuota());
    }

    @Test
    public void transferMiddleQuotas() throws QuotaException {
        account.transferTo(accountTwo,34);
        assertEquals(0,account.caclOddQuota());
        assertEquals(68,accountTwo.caclOddQuota());
    }
      @Test
    public void transferLargeQuotas() throws QuotaException {
        account.transferTo(accountTwo,44);
        assertEquals(-10,account.caclOddQuota());
        assertEquals(78,accountTwo.caclOddQuota());
    }
       @Test
    public void transferLargeLargeQuota() throws QuotaException {
        account.increaseQuota(30);
        account.transferTo(accountTwo,44);
        assertEquals(20,account.caclOddQuota());
        assertEquals(78,accountTwo.caclOddQuota());
    }



    @Test
    public void testRepealEntry() throws QuotaException {
        ProduceQuotaEntry produce = new ProduceQuotaEntry();
        produce.setAmount(34);
        ConsumeQuotaEntry consume =account.repeal(produce);
        assertEquals(0, produce.getAmount() + consume.getAmount());
    }
}

 

 

你可能感兴趣的:(案例分析设计)