浏览 1899 次
锁定老帖子 主题:在线交易平台---配额管理模块的设计
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2011-06-08
最后修改:2011-06-24
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<ProduceQuotaEntry>{ /** * 有效期 */ public static int DEFALUT_LIFE_YEAR = 2; /** * 配额的有效期 */ private Date beginTime; private Date endTime; private List<ConsumeQuotaEntry> consumeEntries = new ArrayList<ConsumeQuotaEntry>(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<QuotaEntry> quotaEntries = new ArrayList<QuotaEntry>(16); /** * 购买商品后,扣减配额 * @param amount 额度 */ public List<ConsumeQuotaEntry> deduct(int amount) throws QuotaException { checkAccount(amount); //可用的生产配额 List<ProduceQuotaEntry> avaiEntries = findAvaiEntry(); //生成的消费配额 List<ConsumeQuotaEntry> 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<ConsumeQuotaEntry> 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<ProduceQuotaEntry> avaiEntries = findAvaiEntry(); List<ConsumeQuotaEntry> consumeList= reduceQuota(avaiEntries,amount,QuotaEntryType.TransferTo); account.transferFrom(consumeList); } /** * 接收 从某个账户转移过来的额度 * */ public void transferFrom(List<ConsumeQuotaEntry> 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() <amount) throw new QuotaException("生产配额少于消费配额"); ConsumeQuotaEntry consume = QuotaEntryFactory.getDeductEntry(amount); addQuotaEntry(consume); } /** * 找到可用的配额账目 * @return */ private List<ProduceQuotaEntry> findAvaiEntry(){ List<ProduceQuotaEntry> avaiEntries = new ArrayList<ProduceQuotaEntry>(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<ConsumeQuotaEntry> reduceQuota(List<ProduceQuotaEntry> avaiEntries,int amount,QuotaEntryType type) throws QuotaException { List<ConsumeQuotaEntry> consumeList= new ArrayList<ConsumeQuotaEntry>(4); int remain =amount; int toReduce = amount; Iterator<ProduceQuotaEntry> 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<QuotaEntry> getQuotaEntries() { return quotaEntries; } public void setQuotaEntries(List<QuotaEntry> 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<ConsumeQuotaEntry> 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<ConsumeQuotaEntry> 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()); } }
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |