• 项目开发流程

    1.需求分析:品经理与架构师,根据客户的需求,理出一套比较容易编写的流程
    2.架构设计:架构师根据具体的业务需求选择 具体的开发编程语言与项目框架,所需要的数据库(主库,从库)。与开发目录规范,项目功能划分。项目的报价
    3.分组开发:将项目拆分成多个小项目交给不同开发部门下的多个程序员
    4.提交测试:测试人员对项目进行全方面的测试
    5.交付上线:打包给运维人员进行维护
  • 项目需求分析

    大致功能如下(可拓展):
    1.注册功能:支持多账户注册
    2.登陆功能
    3.查看余额
    4.提现功能:提现需要设置手续费,比例可以由管理员定义
    5.充值功能
    6.转账功能:支持给已注册用户转账
    7.查看流水
    8.添加购物车
    9.查看购物车
    10.结算购物车
    11.管理员功能
  • 项目涉及内容

1.数据类型内置方法
2.函数的基本用法及特点
3.内置模块,自定义模块用法
  • 项目架构设计

    三层架构
    1.用户交互层:也称展示层,主要功能是和用户交互,展示功能
    2.核心逻辑层:也称逻辑层,存放所有逻辑判断功能
    3.数据处理层:数据层,做数据的增删改查并且和系统交互,返回给第二层
  • 项目目录搭建

1.bin
start.py # 存放启动脚本
2.conf
settings.py # 存放配置文件
3.lib
common # 存放公共功能
4.log # 存放项目日志
5.core
src.py # 展示层
6.interface # 核心逻辑层
shop_interface # 购物车接口
bank_interface # 银行接口
user_interface # 用户接口
admin_interface # 管理员接口 db
db_handlerl.py # 数据处理层
readme # 说明
  • 项目功能搭建

    空函数 功能字典 循环匹配
  • 框架搭建(src.py文件代码:)

is_login= {'username':''}  # 全局变量 保存登录信息

# 用户注册
def register():
pass
# 用户登录
def login():
pass # 查看余额
def check_balance():
pass # 余额提现
def Withdraw():
pass # 账户充值
def pay():
pass # 转账
def tranfer():
pass # 查看流水
def check_flow():
pass # 添加购物车
def add_shop_car():
pass # 查看购物车
def check_shop_car():
pass # 结算
def pay_shop_car():
pass # 管理员
def admin():
pass # 功能字典
func_dict = {
'1': register,
'2': login,
'3': look_balance,
'4': Withdraw,
'5': pay,
'6': tranfer,
'7': cheak_water,
'8': add_shop_car,
'9': view_shop_car,
'10': clear_shop_car,
'11': admin } while True:
print("""
1.用户注册
2.登录功能
3.查看余额
4.余额提现
5.账户充值
6.金额转账
7.查看流水
8.添加购物车
9.查看购物车
10.结算购物车
11.管理员功能
""") choice = input('请输入功能编号>>>:').strip()
if choice in func_dict:
func_name = func_dict.get(choice)
func_name()
else:
print('好好输!')
  • 启动文件(start.py文件代码)
import os
import sys base_dir = os.path.dirname(os.path.dirname(__file__)) # 1.找到根目录
sys.path.append(base_dir) # 2.在将根目录添加至环境变量,不受用户和路径限制 if __name__ == '__main__':
from core import src
src.run() # 3.调用run()函数,开始展示功能
  • 配置文件(setting.py文件代码)
import os

#  获取根目录路径,如果不存在则创建
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
DB_DIR = os.path.join(BASE_DIR, 'db')
if not os.path.exists(DB_DIR):
os.mkdir(DB_DIR) MONETY_RATE = 0.05 # 定义手续费费率,因为不常修改所以放在配置文件大写 # 记录日志字典
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
'[%(levelname)s][%(message)s]' # 其中name为getlogger指定的名字
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' LOG_DIR = os.path.join(BASE_DIR, 'log')
if not os.path.isdir(LOG_DIR):
os.mkdir(LOG_DIR)
LOGFILE_PATH = os.path.join(LOG_DIR, 'ATM.log') LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
},
'filters': {}, # 过滤日志
'handlers': {
# 打印到终端的日志
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 打印到屏幕
'formatter': 'simple'
},
# 打印到文件的日志,收集info及以上的日志
'default': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'formatter': 'standard',
'filename': LOGFILE_PATH, # 日志文件
'maxBytes': 1024 * 1024 * 5, # 日志大小 5M
'backupCount': 5,
'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了
},
},
'loggers': {
# logging.getLogger(__name__)拿到的logger配置
'': {
'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)传递
}, # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
# '购物车记录': {
# 'handlers': ['default','console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
# 'level': 'WARNING',
# 'propagate': True, # 向上(更高level的logger)传递
# }, # 当键不存在的情况下 (key设为空字符串)默认都会使用该k:v配置
},
}
  • 公共文件(common文件代码)
"""
代码中加密,装饰器,判断数字是否符合要求,日志都是公共功能,因此放在common中
"""
import hashlib
import logging
import logging.config
from core import src
from conf import settings def get_hash(user_pwd):
md5 = hashlib.md5()
md5.update(user_pwd.encode('utf8'))
return md5.hexdigest() def login_auth(func):
def inner(*args, **kwargs):
if src.is_login.get('username'):
res = func(*args, **kwargs)
return res
else:
print('您尚未登陆,请先登陆')
src.login()
return inner def get_num(target_money):
try:
target_money = float(target_money)
except:
return False, '请输入整数或小数'
else:
return True, target_money def get_logger(msg):
logging.config.dictConfig(settings.LOGGING_DIC) # 自动加载字典中的配置
logger1 = logging.getLogger(msg)
return logger1
  • 数据库操作相关代码(db_handler文件代码)
"""
此文件主要用于数据库文件的存取,存取需要多次用到,所以直接封成函数放在数据库操作文件中方便使用
"""
import os
import json
from conf import settings def save(user_dict):
user_name = user_dict.get('username')
user_file_path = os.path.join(settings.DB_DIR, f'{user_name}.json')
with open(user_file_path, 'w', encoding='utf8') as f:
json.dump(user_dict, f, ensure_ascii=False) def select(user_name):
user_file_path = os.path.join(settings.DB_DIR, f'{user_name}.json')
if os.path.exists(user_file_path):
with open(user_file_path, 'r', encoding='utf8') as f:
return json.load(f)
  • 注册功能
"""
注册功能应首先在第一层获取用户输入,简单逻辑判断之后传给第二层,进行逻辑判断,涉及是否已注册功能及存取需要用到db_handler中的函数
"""
src代码:
def register():
user_name = input('请输入您的用户名>>>:').strip() # 1.获取用户输入
user_pwd = input('请输入您的密码>>>:').strip()
confirm_pwd = input('请再次输入您的密码>>>:').strip()
if not user_pwd == confirm_pwd: # 2.将用户两次输入的密码进行对比,如果不一致则退出运行,重新选择任务编号
print('两次密码输入不一致,请再次输入')
return
flag, msg = user_interface.register_interface(user_name, user_pwd) # 3.用两个变量名接收interface层注册函数的返回值
"""
由于本项目部分函数需要返回两个返回值,因此统一用两个变量名来接收函数的返回值,部分函数返回值可能用不上,只是为了统一格式
"""
print(msg) user_interface代码:
def register_interface(user_name, user_pwd): # 1.定义注册接口函数
user_dict = db_handler.select(user_name) # 2.通过db_handler中的select()函数获取函数的返回值
if user_dict: # 3.如果有返回值,说明用户已存在,结束注册功能
return False, f'用户名{user_name}已注册'
user_pwd = common.get_hash(user_pwd) # 4.给传入的密码加密
user_dict = { # 5.构造字典
'username': user_name,
'password': user_pwd,
'balance': 15000,
'shop_car': {},
'is_lock': False,
'water_flow': []
}
db_handler.save(user_dict) # 6.调用db_handler中的save()函数来保存构造的字典
logger.info(f'用户{user_name}注册成功')
return True, f'用户{user_name}注册成功' # 7.返回返回值
  • 登陆功能
src代码:
def login():
user_name = input('请输入您的用户名>>>:').strip() # 1.获取用户输入
user_pwd = input('请输入您的密码>>>:').strip()
flag, msg = user_interface.login_inrterface(user_name, user_pwd)
if flag: # 3.用两个变量名接收interface层注册函数的返回值
is_login['username'] = user_name # 4.记录登录状态
print(msg) interface代码:
def login_inrterface(user_name, user_pwd):
user_dict = db_handler.select(user_name) # 1.通过select()函数来返回用户字典或None
if not user_dict: # 2.逻辑判断:如果没有返回字典,说明用户未注册,将合适的返回值返回给用户
return False, f'用户{user_name}未注册'
user_pwd = common.get_hash(user_pwd) # 3.如果用户已注册,先获取加密后的密码
if user_pwd == user_dict.get('password'): # 4.将用户输入后的密码和存储的密码进行对比(都是加密后的密码)
logger.info(f'用户{user_name}登陆成功')
return True, '登陆成功'
return False, '密码错误' # 5.如果对比不成功返回布尔值和密码错误
  • 查看余额

src代码:
@common.login_auth # 1.通过添加装饰器来限制用户执行该功能前必须先登陆
def check_balance():
user_name = is_login.get('username') # 2.因为用户登录成功时修改了登录状态,所以可以直接通过is_login拿到用户的用户名
flag, msg = bank_interface.check_balance_interface(user_name) # 3.用2个变量名来接收interface()函数的返回值
print(msg) # 4.打印返回的信息 interface代码:
def check_balance_interface(username):
user_dict = db_handler.select(username) # 1.通过传入的用户名从select()函数中通过返回值拿到用户字典
user_balance = user_dict.get('balance') # 2.获取到用户的余额
logger.debug(f'用户{username}查看了自己的账户余额')
return True, f'尊敬的{username},您的账户余额为{user_balance}' # 3.将布尔值True和账户余额返回给src中的msg
  • 提现功能
src代码:
@common.login_auth
def withdraw():
user_name = is_login.get('username') # 1.通过is_login拿到用户的用户名
target_money = input('请输入您想要提现的金额>>>:').strip() # 2.获取用户提现金额
flag, msg = bank_interface.withdraw_interface(user_name, target_money) # 3.用2个变量名来接收interface()函数的返回值
print(msg) interface代码:
def withdraw_interface(username, target_money):
flag, value = common.get_num(target_money) # 1.通过common中的get_num()函数来判断数字是否符合要求
if not flag: # 2.如果不符合将返回值返回给第一层,重新输入
return False, '请输入符合要求的金额'
user_dict = db_handler.select(username) # 3.通过select()获取用户字典,因为此时是从is_login获取到的用户名,所以字典肯定存在,无需判断
user_balance = user_dict.get('balance') # 4.获取用户余额
if user_balance >= value * (1 + settings.MONETY_RATE): # 5.判断用户余额是否大于提现金额加手续费,因为手续费不常修改,所以放在settings中大写
user_dict['balance'] -= value * (1 + settings.MONETY_RATE) # 6.修改用户字典中的余额,考虑手续费
ctime = time.strftime('%Y-%m-%d %H:%M:%S') # 7.记录时间,用于记录流水
user_dict['water_flow'].append(
f'{username}于{ctime}提现{value},手续费{value * settings.MONETY_RATE},账户余额{user_dict.get("balance")}')
db_handler.save(user_dict) # 8.添加流水
logger.debug(f'用户{username}提现{value},手续费{value * settings.MONETY_RATE},账户余额{user_dict.get("balance")}')
return True, f'尊敬的{username}成功提现{value},手续费{value * settings.MONETY_RATE},账户余额{user_dict.get("balance")}'
return False, f'尊敬的{username}, 您的账户余额不足,无法提现' # 9.通过逻辑判断返回不同的返回值
  • 充值功能
src代码:
@common.login_auth
def pay_back():
user_name = is_login.get('username')
target_money = input('请输入您想充值的金额>>>:').strip()
flag, msg = bank_interface.pay_back_interface(user_name, target_money)
print(msg) interface代码:
def pay_back_interface(user_name, target_money):
flag, value = common.get_num(target_money)
if not flag:
return False, '请输入符合要求的金额数字'
user_dict = db_handler.select(user_name)
user_dict['balance'] += value
ctime = time.strftime('%Y-%m-%d %H:%M:%S')
user_dict['water_flow'].append(f'{user_name}于{ctime}充值{value},账户余额{user_dict.get("balance")}')
db_handler.save(user_dict)
logger.debug(f'用户{user_name}充值{value},账户余额{user_dict.get("balance")}')
return True, f'尊敬的{user_name},您于{ctime}成功充值{value},账户余额{user_dict.get("balance")}'
  • 转账功能
src代码:
@common.login_auth
def transfer():
user_name = is_login.get('username')
target_name = input('请输入您想转账的用户名>>>:').strip()
target_money = input('请输入您想转账的金额>>>:').strip()
flag, msg = bank_interface.transfer_interface(user_name, target_name, target_money)
print(msg) interface代码:
def transfer_interface(user_name, target_name, target_money):
target_dict = db_handler.select(target_name)
if not target_dict:
return False, f'用户{target_name}未注册'
flag, value = common.get_num(target_money)
if not flag:
return False, '请输入符合要求的数字'
user_dict = db_handler.select(user_name)
if user_dict.get('balance') >= value:
user_dict['balance'] -= value
ctime = time.strftime('%Y-%m-%d %H:%M:%S')
user_dict['water_flow'].append(
f'尊敬的{user_name},您于{ctime}给{target_name}转账{value},账户余额{user_dict.get("balance")}')
target_dict['balance'] += value
target_dict['water_flow'].append(
f'尊敬的{target_name},{user_name}于{ctime}给您转账{value},账户余额{target_dict.get("balance")}')
db_handler.save(user_dict)
db_handler.save(target_dict)
logger.debug(
f'用户{user_name}给{target_name}转账{value},{user_name}账户余额{user_dict.get("balance")},{target_name}账户余额{target_dict.get("balance")}')
return True, f'尊敬的{user_name}, 您于{ctime}给{target_name}转账{value},账户余额{user_dict.get("balance")}'
return False, f'您的账户余额不足,无法转账'
  • 查看流水
src代码:
@common.login_auth
def check_flow():
user_name = is_login.get('username')
flag, msg = bank_interface.check_flow_interface(user_name)
if flag:
for data in msg:
print(data)
else:
print(msg) interface代码:
def check_flow_interface(username):
user_dict = db_handler.select(username)
if user_dict.get('water_flow'):
logger.debug(f'用户{username}查看了流水')
return True, user_dict.get('water_flow')
else:
logger.debug(f'用户{username}查看了流水')
return False, '您暂无流水'
  • 添加购物车
"""
添加购物车主要功能可以写在第二层,第一层主药方用户交互功能
"""
src代码:
@common.login_auth
def add_shop_car():
user_name = is_login.get('username')
flag, msg = shop_interface.add_shop_car_interface(user_name)
print(msg) interface代码:
def add_shop_car_interface(user_name):
tem_list = {}
while True:
good_list = [
['挂壁面', 3],
['印度飞饼', 22],
['极品木瓜', 666],
['土耳其土豆', 999],
['伊拉克拌面', 1000],
['董卓戏张飞公仔', 2000],
['仿真玩偶', 10000]
]
for i, j in enumerate(good_list, start=1):
print(f"""
商品编号:{i} | 商品名称:{j[0]} | 商品单价:{j[1]}
""")
choice = input('请输入您的商品编号(q)>>>:').strip()
if choice == 'q':
user_dict = db_handler.select(user_name)
shop_car = user_dict.get('shop_car')
for i in tem_list:
if i in shop_car:
shop_car.get(i)[0] += tem_list.get(i)[0]
else:
shop_car[i] = tem_list.get(i)
user_dict['shop_car'] = shop_car
db_handler.save(user_dict)
logger.info(f'用户{user_name}添加了购物车')
return True, f'用户{user_name}购物车添加完毕' if not choice.isdigit():
print('商品编号必须是纯数字')
continue
choice = int(choice)
if not choice in range(1, len(good_list) + 1):
print('没有该商品')
continue
num = input(f'请输入您购买{good_list[choice - 1][0]}的数量>>>:')
if not num.isdigit():
print('商品数量必须是纯数字')
continue
num = int(num)
target_list = good_list[choice - 1]
target_good = target_list[0]
if target_good in tem_list:
tem_list.get(target_good)[0] += num
else:
tem_list[target_good] = [num, target_list[1]]
  • 查看购物车功能
src.py代码:
@common.login_auth
def check_shop_car():
user_name = is_login.get('username')
flag, msg = shop_interface.check_shop_car_interface(user_name)
if flag:
for i in msg:
print(f"""
商品名称:{i} | 商品数量:{msg.get(i)[0]} | 商品单价:{msg.get(i)[1]}
""")
else:
print(msg) interface代码:
def check_shop_car_interface(user_name):
user_dict = db_handler.select(user_name)
shop_car = user_dict.get('shop_car')
if shop_car:
logger.info(f'用户{user_name}查看了购物车')
return True, shop_car
else:
logger.info(f'用户{user_name}查看了购物车')
return False, '您的购物车为空'
  • 结算购物车功能
src代码:
@common.login_auth
def pay_shop_car():
user_name = is_login.get('username')
flag, msg = shop_interface.pay_shop_car_interface(user_name)
print(msg) interface代码:
def pay_shop_car_interface(user_name):
user_dict = db_handler.select(user_name)
shop_car = user_dict.get('shop_car') # {'极品木瓜': [9, 666], '伊拉克拌面': [5, 1000]}
current_balance = user_dict.get('balance')
money = 0
for i in shop_car.values(): # i:[9, 666]
money += i[0] * i[1]
if money > current_balance:
return False, f'您的账户余额不足,无法结算'
current_balance -= money
shop_car = {}
user_dict['balance'] = current_balance
user_dict['shop_car'] = {}
user_dict['water_flow'].append(f'{user_name},您本次消费{money},账户余额{user_dict.get("balance")}')
db_handler.save(user_dict)
logger.info(f'用户{user_name}结算了购物车,本次消费{money},账户余额{user_dict.get("balance")}')
return True, f'尊敬的{user_name},您本次消费{money},账户余额{user_dict.get("balance")}'

ATM购物车大作业的更多相关文章

  1. java大作业博客--购物车

    Java 大作业----使用MySQL的购物车 一.团队介绍 姓名 任务 李天明.康友煌 GUI设计及代码编写 谢晓淞 业务代码编写.MySQL服务器平台部署.git代码库 严威 类和包的结构关系设计 ...

  2. day19 十九、ATM+购物车

    项目:ATM+购物车 作业需求:模拟实现一个ATM + 购物商城程序1.额度 15000或自定义 2.实现购物商城,买东西加入 购物车,调用信用卡接口结账 3.可以提现,手续费5% 4.每月22号出账 ...

  3. 数据库大作业--由python+flask

    这个是项目一来是数据库大作业,另一方面也算是再对falsk和python熟悉下,好久不用会忘很快. 界面相比上一个项目好看很多,不过因为时间紧加上只有我一个人写,所以有很多地方逻辑写的比较繁琐,如果是 ...

  4. 程设大作业xjb写——魔方复原

    鸽了那么久总算期中过[爆]去[炸]了...该是时候写写大作业了 [总不能丢给他们不会写的来做吧 一.三阶魔方的几个基本定义 ↑就像这样,可以定义面的称呼:上U下D左L右R前F后B UD之间的叫E,LR ...

  5. 大作业NABC分析结果

    大作业NABC分析结果 这次的大作业计划制作一款关于七巧板的游戏软件.关于编写的APP的NABC需求分析: N:需求 ,本款软件主要面向一些在校的大学生,他们在校空闲时间比较多,而且热衷于一些益智类游 ...

  6. [留念贴] C#开发技术期末大作业——星月之痕

    明天就要去上海大学参加 2015赛季 ACM/ICPC最后一场比赛 —— EC-Final,在这之前,顺利地把期末大作业赶出来了. 在这种期末大作业10个人里面有9个是从网上下载的国内计算机水平五六流 ...

  7. Hadoop综合大作业

    Hadoop综合大作业 要求: 用Hive对爬虫大作业产生的文本文件(或者英文词频统计下载的英文长篇小说)词频统计. 用Hive对爬虫大作业产生的csv文件进行数据分析 1. 用Hive对爬虫大作业产 ...

  8. 爬虫综合大作业——网易云音乐爬虫 & 数据可视化分析

    作业要求来自于https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/3075 爬虫综合大作业 选择一个热点或者你感兴趣的主题. 选择爬取的对象 ...

  9. 期末Java Web大作业----简易的学生管理系统

    学生信息管理系统(大作业) 2018-12-21:此文章已在我的网站更新,添加视图介绍等信息,源码请移步下载https://www.jeson.xin/javaweb-sims.html PS:首先不 ...

  10. CSAPP HITICS 大作业 hello's P2P by zsz

    摘 要 摘要是论文内容的高度概括,应具有独立性和自含性,即不阅读论文的全文,就能获得必要的信息.摘要应包括本论文的目的.主要内容.方法.成果及其理论与实际意义.摘要中不宜使用公式.结构式.图表和非公知 ...

随机推荐

  1. 3.JS

    1.简介 JavaScript 因为互联网而生,紧随着浏览器的出现而问世 1997年7月,ECMAScript 1.0发布. 1998年6月,ECMAScript 2.0版发布. 1999年12月,E ...

  2. Python基础之网络编程:2、OSI协议之七层协议

    目录 Python基础之网络编程 一.网络编程前戏 二.OSI七层协议 简介: 1.物理连接层 2.数据链路层 网络相关专业名词 3.网络层 4.传输层 Python基础之网络编程 一.网络编程前戏 ...

  3. Atlassian Confluence 远程代码执行漏洞(CVE-2022-26134)漏洞复现

    目录 免责声明: Atlassian Confluence 远程代码执行漏洞(CVE-2022-26134)漏洞复现 漏洞概述: 影响版本: 漏洞复现: 利用POC: 利用过程: 修复建议: 参考 免 ...

  4. python安装/环境变量配置/多版本共存

    Python学习之路Day02: 一.今日学习内容概括: 计算机五大组成部分详解 计算机三大核心硬件 操作系统 编程与编程语言 编程语言的发展 编程语言分类 python解释器 Python Pyth ...

  5. 在链表上实现 Partition 以及荷兰国旗问题

    在链表上实现 Partition 以及荷兰国旗问题 作者:Grey 原文地址: 博客园:在链表上实现 Partition 以及荷兰国旗问题 CSDN:在链表上实现 Partition 以及荷兰国旗问题 ...

  6. python实现AES加密解密

    1. 前言 AES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个. 之前写过一片关于python AES加密解密的文章,但是这里面细节实在很多,这次我从 参数类型.加密模式.编码模式.补全 ...

  7. CTF隐写术总结

    CTF隐写术总结 1.查看图像属性及详细信息 或者查看图像的高度,利用winhex等工具改变图像宽度或高度,查看是否有隐藏信息. 2.利用winhex或nodepad++打开搜索ctf,flag,ke ...

  8. Django框架:2、静态文件配置、form表单、request对象、pycharm链接数据库、django链接数据库、ORM框架

    Django框架 目录 Django框架 一.静态文件配置 1.静态文件 2.配置方法 二.form表单 1.action属性 2.method属性 三.request对象 1.基本用法 四.pych ...

  9. 静态文件配置 django连接MySQL django模型层初识 ORM基本语句

    目录 静态文件配置 1. 问题描述 2. 静态文件 3. 资源访问 form表单属性补充 action method 请求方法 get请求 post请求 views层 request对象 reques ...

  10. 如何使用Abstract类?抽象类的威力

    简介: 今天我想谈谈如何使用抽象类,以及抽象类真正的威力.本文将结合具体业务来说明如何使用抽象类. 业务简述: 本人目前只接触过PMS(物业管理系统),公司主要业务的是美国的租房业务.由于美国租房和中 ...