[TOC]
项目使用 java 11 编写,请使用 java 11 编译器进行编译
-
基准资费
考虑到基准资费可能发生变化,同时又需要记录历史的基准资费,故单独创建一张表来记录基准资费,要查找当前基准资费可通过查找最大生效时间的记录来获得
-
套餐
记录套餐提供的通话时间、短信条数、本地流量和全国流量
-
流量使用记录、通话记录和短信记录
消费记录主要用于支持生成用户历史月账单
-
用户
用户本身记录的消费信息主要用于计算当前套餐使用情况和生成当前月账单
-
订购
订购记录用户所有的订购套餐记录,通过正在订单状态字段来判断订单状态
数据库中总共有 7 张表,介绍如下
表名称 | 说明 |
---|---|
basic_charge | 用来记录基准资费的表,可以通过查找最近生效时间的记录来获得当前使用的基准资费 |
call_record | 用户的通话记录,用户的通话资费情况生成基于这张表 |
data_usage_record | 用户流量使用记录,用户的流量资费情况生成基于此表 |
message_record | 用户的短信使用记录 |
package | 记录套餐的信息 |
package_order | 记录订单信息 |
user | 记录用户信息,同时也记录了用户本月的各项花费指标 |
-
输入:用户号码
-
采用策略:连接
package
和package_order
两张表,筛选出状态为正在生效的套餐(正在生效或者已经退订单下月失效),然后计算出当前套餐使用情况,然后打印生效套餐的信息及其消耗信息 -
计算当前套餐使用情况思路及说明:
user
表记录了用户本月的通话、短信和流量的使用情况(套餐内的和套餐外的分开记录),按照套餐订购的时间顺序用这些记录在相应数据上做减法,如果当前套餐不够减,则交给下一个套餐,如果所有套餐都不够减,则将超出部分记录到用户的额外消耗字段上。- 用户订购的套餐按照套餐订购的顺序进行消耗(即先定的套餐会先被消耗)
- 如果用户立即退订了某个套餐,该套餐提供的通话、短信、流量都会按照基准资费进行收费,需要再次执行 1 中描述的计算来获得该套餐的消耗情况,然后将这些消耗从
user
记录的套餐内的通话、短信和流量减去,转而加到套餐外的通话、短信和流量上。所以如果立即退订了某个套餐不会对后面的套餐使用产生影响。
此次运行使用了用户 18851822896
,使用的方法是 PlanService
中的 effectivePlans
planService.effectivePlans("18851822896");
- 输入:用户号码
- 采用策略:查询
packaeg_orders
表中用户的订购历史信息,结果按照订单状态和订购时间排序后输出
此次运行使用了用户 18851822896
,使用的方法是 PlanService
中的 orderInforMation
planService.orderInformation("18851822896"); //执行订单查询操作
- 输入:用户的手机号码和套餐的 id
- 采用策略:从
package
表中获得套餐信息,然后从user
表中获得用户信息,然后测试用户余额是否足够,如果足够则创建订单信息将订单状态设置为生效,然后写入package_orders
表中;如果用户余额不足够,则不执行任何操作
此次运行使用用户 18851822896
订购了 10
号套餐,使用的方法是 PlanService
中的 subscribeInTime
方法
planService.subscribeInTime(10, "18851822896");
- 输入:用户手机号和订单编号
- 采用策略:从数据库中获得套餐信息和用户信息,然后创建订单,将订单生效时间设置为下个月的第一天的 0 点,将订单状态设置为“等待生效”
- 此操作不会减少用户当前余额
- 支持套餐叠加
此次运行使用用户 18851822896
订购了 17
号套餐,使用的方法是 PlanService
中的 subscribeNextMonth
方法
planService.subscribeNextMonth(10, "18851822896");
- 输入是用户号码和套餐编号
- 采取策略:如果套餐还未生效,则直接修改订单状态为“失效”,如果订单已经生效,则将套餐费全部退还给用户,同时将用户的已经使用的套餐内使用的通话、短信、流量按照当前的基准资费进行计算后要求用户额外支付
- 如果存在重复套餐,那么按照订购套餐的时间顺序依次退订
- 计算套餐使用的思路同套餐查询操作中描述的一致
此次运行使用了用户 18851822896
退订 9
号套餐,使用的方法是 PlanService
中的 unsubscribeInTime
planService.unsubscribeInTime(9, "18851822896");
-
执行退订,运行时间是
298ms
-
执行后,用户余额被扣除
2500 - 90
元,余额是100 - 2410 = -2310
元 -
执行前的订单情况
执行后的订单情况,可以看到订单状态已经为“失效”即
out
- 输入是用户号码和套餐编号
- 采取策略:如果订单还未生效,直接修改订单状态为失效;如果订单正在生效,则将订单状态修改为“等待失效”后写入数据库。然后每个月的第一天对数据库进行扫描,将所有等待失效的订单的状态全部改为失效。
- 用户不需要付出额外的代价
- 如果存在重复套餐,那么按照订购套餐的时间顺序依次退订
此次运行使用了用户 18851822896
退订 10
号套餐,使用的方法是 PlanService
中的 unsubscribeNextMonth
- 输入:用户号码和查询的月范围(多少个月之内)
- 采取策略:直接从
call_record
表中获取用户通话记录信息后显示
此次运行使用了用户 18851822896
,查询的月范围是2个月之内,使用的方法是 BillService
中的 showCallBills
billService.showCallBills(2, "18851822896");
- 输入:用户号码和查询的月范围(多少个月之内)
- 采取策略:直接从
data_usage_record
表中获取用户通话记录信息后显示
此次运行使用了用户 18851822896
,查询的月范围是2个月之内,使用的方法是 BillService
中的 showDataBills
billService.showDataBills(2, "18851822896");
- 输入:用户号码
- 采用策略:从用户表获取本月通话、短信和流量(套餐内和套餐外)信息,从订单表和套餐表中获取用户当前正在使用的套餐信息,从基准资费表中获取当前基准资费,然后计算用户账单中的各项指标
此次使用用户 18851822896
,使用的方法是 BillService
的 showMonthBill
方法
billService.showMonthBill("18851822896");
user
表- 在
user
表中记录用户本月用户的通话、短信和流量使用情况,最初的想法是这样便于计算用户的套餐使用情况,但是没有考虑到一旦数据量过大之后由于既需要对user
表进行频繁的更新操作,又要对call_record
,message_record
和data_usage_record
做频繁的插入操作,而用户查询账单的操作有可能不会太频繁,这可能使得效率很低。优化的思路是去掉user
表中记录的本月用户的通话、短信和流量使用情况,而通过三张记录表获得相关消费信息。
- 在
package_orders
表- 每个用户订购的订单相对全部订单来说应该很小,这使得
user_phone
的选择性很好,检索比率很低,同时又有多个操作将user_phone
作为筛选条件,所以可以考虑在user_phone
一列上建立索引
- 每个用户订购的订单相对全部订单来说应该很小,这使得
call_record
,data_usage_record
,message_record
表- 操作中通话、短信、流量记录通常需要按照时间筛选和排序,所以如果实际数据中
start_time
的检索比率很低的话可以建立索引
- 操作中通话、短信、流量记录通常需要按照时间筛选和排序,所以如果实际数据中
项目使用 java 11 编写,请使用 java 11 编译器进行编译
首先需要修改 mybatis
的配置文件,该文件位于 src/main/resources
路径下,对 properties
中的 url
, username
和 password
修改为本地数据库相应信息,以便能够使 mybatis
生效
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/assignment2"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
main
方法中已经列出了所有可用的方法,需要运行某方法只需将该方法注释去掉即可,每个方法的作用在代码中有相应的注释,也可以参考操作设计中的运行标题下面关于调用方法的说明
此次作业实现了命令行操控的方式,如果需要使用命令行,只需要在 main
方法中调用 commandLine
即可
public static void main(String[] args) {
commandLine();
}
- 命令行首先要求输入用户号码,需要输入
user
表中有的号码才能进入下一步操作 - 输入
help
得到当前命令行支持的命令信息
-
计算操作运行时间方式
Instant start = Instant.now(); billService.showMonthBill("18851822896"); // 此处执行需要计时的操作 Instant end = Instant.now(); System.out.println("spend time is " + Duration.between(start, end).toMillis() + " ms");
Field | Type | Null | Key | Default | Extra | Description |
---|---|---|---|---|---|---|
id | int(11) | NO | PRI | NULL | auto_increment | id |
local_data_charge | double | NO | NULL | 本地流量计费 | ||
domestic_data_charge | double | NO | NULL | 全国流量计费 | ||
call_charge | double | NO | NULL | 通话计费 | ||
message_charge | double | NO | NULL | 短信计费 | ||
effective_time | date | NO | NULL | 生效时间 |
Field | Type | Null | Key | Default | Extra | Description |
---|---|---|---|---|---|---|
start_time | datetime | NO | NULL | 通话开始时间 | ||
duration | int(11) | NO | NULL | 通话时长/min | ||
expense | double | NO | NULL | 此次通话计费 | ||
user_phone | char(11) | NO | NULL | 用户号码 |
Field | Type | Null | Key | Default | Extra | Description |
---|---|---|---|---|---|---|
start_time | datetime | NO | NULL | 使用开始时间 | ||
end_time | datetime | NO | NULL | 使用结束时间 | ||
consumption | double | NO | NULL | 消耗流量/MB | ||
type | enum('Domestic','Local') | NO | NULL | 流量类型 | ||
expense | double | NO | NULL | 花费 | ||
user_phone | char(11) | NO | NULL | 用户号码 |
Field | Type | Null | Key | Default | Extra | Description |
---|---|---|---|---|---|---|
send_time | datetime | NO | NULL | 发送时间 | ||
expense | double | NO | NULL | 花费 | ||
user_phone | char(11) | NO | NULL | 用户号码 |
Field | Type | Null | Key | Default | Extra | Description |
---|---|---|---|---|---|---|
id | int(11) | NO | PRI | NULL | auto_increment | id |
expense | double | NO | NULL | 套餐费用 | ||
call_minutes | int(11) | NO | NULL | 提供通话/min | ||
messages | int(11) | NO | NULL | 短信条数 | ||
local_data | double | NO | NULL | 本地流量/MB | ||
domestic_data | double | NO | NULL | 全国流量/MB |
Field | Type | Null | Key | Default | Extra | Description |
---|---|---|---|---|---|---|
id | int(11) | NO | PRI | NULL | auto_increment | id |
order_time | datetime | NO | NULL | 订单下单时间 | ||
effective_time | datetime | NO | NULL | 套餐生效时间 | ||
user_phone | char(11) | NO | NULL | 用户号码 | ||
package_id | int(11) | NO | NULL | 套餐编号 | ||
state | enum('effective','waiting_effective','out','waiting_out') | YES | NULL | 订单状态(生效中,次月生效,次月失效,已失效) |
Field | Type | Null | Key | Default | Extra | Description |
---|---|---|---|---|---|---|
phone_number | char(11) | NO | PRI | NULL | 用户号码 | |
call_minutes | int(11) | NO | NULL | 套餐内已通话 | ||
messages | int(11) | NO | NULL | 套餐内已发短信 | ||
local_data | double | NO | NULL | 套餐内已用本地流量 | ||
domestic_data | double | NO | NULL | 套餐内已用全国流量 | ||
balance | double | NO | NULL | 余额 | ||
out_call | int(11) | NO | NULL | 额外已用通话 | ||
out_messages | int(11) | NO | NULL | 额外已用短信 | ||
out_local_data | double | NO | NULL | 额外已用本地流量 | ||
out_domestic_data | double | NO | NULL | 额外已用国内流量 |