基于 FastAPI 的房源租赁系统设计与实现

项目背景

传统的线下租房不便、途径少、信息更新慢,导致房屋租赁效率低。为了有效的提升租赁效率和房源信息管理、提供更优质的租赁服务。让房东出租宣传展示与房源管理、租客更好的检索房源信息、发布租房需求以及入住预定、后台房源管理、审核等一站式租赁服务平台。

  • 租客:浏览房源、收藏房源、预定房源、发布租房需求、查看电子合同。
  • 房东:发布房源、订单管理、查看电子合同。
  • 管家:查看房源信息、回复咨询、线下带看房源。
  • 管理员:用户管理、房源管理、订单管理、租房需求、实名认证、系统公告管理。

TODO

  • 房源全文检索
  • 租房需求支持评论
  • 日租、合租模式
  • 房源推荐系统(Go开发)

项目特色

  • 采用了七牛云OSS、CDN服务加速一些图片资源。
  • 采用 FastAPI 的后台任务实现异步发送短信验证码。
  • 采用 tortoise-orm 完成数据库操作的封装。
  • 通过模板字符串动态渲染富文本实现电子合同功能。
  • 对接阿里支付实现了订单、支付模块,对接百度地图实现当前城市定位、房源附近信息查询等功能。
  • 前端界面采用 Vue.js + Element ui 实现数据渲染,Bootstrap 实现自适应布局。

项目体验

项目体验地址 http://43.138.220.206:9999/huihome 由于注册需要发送短信验证码,而手机验证码服务现在只能给我的测试手机号发送验证码,因此不能使用注册服务。大家可以使用已有账号去登录体验。

账号类别用户名密码备注
用户账号hui123456租客账号
用户账号wang123456房东账号

项目源码:HuiDBK/HuiHome: 基于FastAPI的房屋租赁系统 (github.com)

项目还没有太完善,服务器也只是学习级别的,可能会出现很多异常,望大家多担待。

项目启动

  1. 准备好MySQL 与 Redis数据库服务,修改相关数据库配置信息
  2. 申请第三方服务:七牛云的OSS服务、容联云的短信服务、阿里的支付服务、百度地图服务
  3. 依赖于 Python 3.7.9 编程环境
  4. 安装 requirements.txt 项目依赖 pip install -r requirements.txt
  5. 启动项目 python run.py
  6. 如果成功在本地启动项目,访问 http://127.0.0.1:8080/docs 地址查看接口文档

 

项目部署

  1. 确保Mysql、Redis服务正常
  2. 在存在Dockerfile文件的项目目录下构建镜像 docker build -t house_rental_image . (最后.不要忘记)
  3. 运行镜像产生容器 docker run -d --name house_rental_container -p 80:80 house_rental_image
  4. docker ps 查看容器是否启动

系统整体功能图

 

项目结构

项目开发整体采用的是Python的FastAPI框架来搭建系统的接口服务,接口设计遵循 Restful API接口规范。接口前后端交互都采用json格式进行数据交互,项目整体的结构如下:

 

项目Redis缓存设计

Redis key 规范:

project : module : business : unique key项目名 :  模块名 :  业务    : 唯一区别key 例如:用户手机短信验证码缓存
house_rental:user:sms_code:13022331752
复制代码

用户模块缓存

房源模块缓存

 其他缓存

 系统整体ER图

 房屋属性太多故在整体ER图省略

实际表属性更多进行了垂直分表。

代码细节

实名认证装饰器

def real_auth_required(func):""" 实名认证装饰器 """@wraps(func)async def warp(*args, **kwargs):"""通过请求上下文的user对象来判断用户有没有实名认证"""cur_request = context_util.REQUEST_CONTEXT.get()user = cur_request.user or Noneif not user:raise AuthorizationException()if user.role == UserRole.admin.value:# 管理员不需要实名认证return await func(*args, **kwargs)# 此时不同直接通过 user.auth_status 来验证# 应该通过 user_id 去数据库中查询最新的状态user_profile = await UserProfileManager.get_by_id(user.id)if user_profile.auth_status != UserAuthStatus.authorized.value:raise BusinessException().exc_data(ErrorCodeEnum.REALNAME_AUTH_ERR)return await func(*args, **kwargs)return warp

分页数据封装装饰器

from pydantic import BaseModel, Fieldclass ResponseBaseModel(BaseModel):""" 统一响应模型 """code: intmessage: strdata: dictclass ListResponseDataModel(BaseModel):""" 分页列表响应data模型 """total: int = Field(default=0, description="数据总数量")data_list: list = Field(default=[], description='数据列表')has_more: bool = Field(default=False, description="是否有下一页")next_offset: int = Field(default=0, description="offset下次起步")def list_page(func):""" 分页数据封装装饰器 """@wraps(func)async def warp(*args, **kwargs):"""寻找函数参数 ListPageRequestModel 的实例 有获取 limit、offset所有分页请求入参都继承 ListPageRequestModel"""limit, offset = None, None# 位置参数中寻找for arg in args:if limit is not None and offset is not None:breakif isinstance(arg, ListPageRequestModel):limit, offset = arg.limit, arg.offset# 关键字参数中寻找for key, value in kwargs.items():if limit is not None and offset is not None:breakif isinstance(value, ListPageRequestModel):# 关键字参数值是否是 ListPageRequestModellimit, offset = value.limit, value.offsetelif key == 'limit':# 也支持关键参数 key 为 limit 和 offset的情况limit = valueelif key == 'offset':offset = valueif limit is None or offset is None:# 没有成功赋值, 则不支持logger.debug('不支持分页数据封装')# 执行函数获取分页响应的数据, 有两种情况# 1 返回使用了pydantic model ListResponseDataModel (尽量使用这种来返回业务数据)# 2 返回 total data_list (元组)data_obj = await func(*args, **kwargs)# 分页数据返回的参数都必须遵守 ListResponseDataModelif isinstance(data_obj, ListResponseDataModel):# ListResponseDataModel 处理data_obj.next_offset = offset + limitdata_obj.has_more = False if data_obj.next_offset > data_obj.total else Trueelif isinstance(data_obj, tuple):# 元组 处理total = data_obj[0] if isinstance(data_obj[0], int) else data_obj[1]data_list = data_obj[1] if isinstance(data_obj[1], list) else data_obj[0]data_obj = ListResponseDataModel(total=total,data_list=data_list,next_offset=offset + limit,has_more=False if offset + limit > total else True)list_page_resp = data_objreturn list_page_respreturn warp

json数据缓存装饰器

def cache_json(cache_info=None, key=None, timeout=60):"""缓存装饰器 (适合缓存字符串json数据):param key: 缓存的key:param timeout: 缓存的时间 默认60秒:param cache_info: 封装好的缓存信息对象 RedisCacheInfo:return:"""if cache_info:# 有封装的缓存对象key = cache_info.keytimeout = cache_info.timeoutdef cache_decorator(api_func):@wraps(api_func)async def warp(*args, **kwargs):# 1、没有设置key则根据接口函数的信息和系统密钥自动生成(尽量设置key)nonlocal keyif not key:# 应用名:函数所在模块:函数名:函数位置参数:函数关键字参数:系统密钥 进行hashparam_args_str = ','.join([str(arg) for arg in args])param_kwargs_str = ','.join(sorted([f'{k}:{v}' for k, v in kwargs.items()]))hash_str = f'{constants.APP_NAME}:{api_func.__module__}:{api_func.__name__}:' \f'{param_args_str}:{param_kwargs_str}:{settings.SECRET}'has_result = hashlib.md5(hash_str.encode()).hexdigest()# 根据哈希结果生成keykey = f'{constants.APP_NAME}:{api_func.__module__}:{api_func.__name__}:{has_result}'# 2、先查看是否有缓存from house_rental.commons.utils.redis_util import RedisUtilredis_client = await RedisUtil().get_redis_conn()cache_data = await redis_client.get(key)if cache_data:return json.loads(cache_data)# 3、执行接口函数获取结果api_result = await api_func(*args, **kwargs)# 4、设置缓存if isinstance(api_result, BaseModel):# 结果是pydantic的模型对象处理api_result_json = api_result.json()elif isinstance(api_result, dict):# 字典api_result_json = json.dumps(api_result)else:# 其他可以json序列化的api_result_json = json.dumps(api_result)await redis_client.setex(key, timeout, api_result_json)return api_resultreturn warpreturn cache_decorator

项目接口依赖(Depends)

async def jwt_authentication(request: Request):""" jwt 鉴权"""# for api_url in settings.API_URL_WHITE_LIST:#     # 在白名单的接口无需token验证#     if str(request.url.path).startswith(api_url):#         returntoken = request.headers.get('Authorization') or Noneif not token:raise AuthorizationException()# Bearer 占了7位if not str(token).startswith('Bearer '):raise AuthorizationException()token = str(token)[7:]user_info = jwt_util.verify_jwt(token)if not user_info:# 无效tokenraise AuthorizationException()# 校验通过保存到request.user中user_id = user_info.get('user_id')user = await UserBasicManager.get_by_id(user_id)if user.role != UserRole.admin.value and str(request.url.path).startswith('/api/v1/admin'):# 不是管理员无法访问了后台模块接口raise AuthorizationException()request.scope['user'] = userasync def request_context(request: Request):""" 保存当前request对象到上下文中 """context_util.REQUEST_CONTEXT.set(request)async def login_required(request: Request):""" 登录权限校验 """try:user = request.userexcept:raise AuthorizationException().exc_data(ErrorCodeEnum.AUTHORIZATION_ERR)if not user:raise AuthorizationException().exc_data(ErrorCodeEnum.AUTHORIZATION_ERR)

响应序列化递归工具函数

def obj2DataModel(data_obj: Union[Dict,Type[BaseOrmModel],List[Dict],List[BaseOrmModel]],data_model: Type[BaseModel]
) -> Union[BaseModel, List[BaseModel], None]:"""将数据对象转换成 pydantic的响应模型对象, 如果是数据库模型对象则调用to_dict()后递归:param data_obj: 支持 字典对象, 数据库模型对象, 列表对象:param data_model: 转换后数据模型:return:"""if isinstance(data_obj, dict):# 字典处理return data_model(**data_obj)elif isinstance(data_obj, BaseOrmModel):# 数据模型对象处理, to_dict()后递归调用return obj2DataModel(data_obj.to_dict(), data_model=data_model)elif isinstance(data_obj, list):# 列表处理return [obj2DataModel(item, data_model=data_model) for item in data_obj]else:logger.debug(f'不支持此{data_obj}类型的转换')return

入参、出参代码样式

未调整前:

 调整后:

 

 

不要为了单独一个而全部调整,只要达到了美观就行无需全部对齐,重要是关注每个入参和出参有没有关联等,一些必传啊,枚举参数等,相关联的放到一起,这样的代码才更赏心悦目。

房源设施双色图标展示

首先我可以获取所有房源设施的信息,接口返回当前房源有的房源信息,只要判断不在总房源设施列表里的就显示 灰色图标、文字下划线 在则显示不同的颜色(数据库只存了灰色图标)

1、通过 filter 函数滤镜函数实现图标不同颜色的阴影,然后原图标偏移图标宽度然后隐藏,就只剩下带颜色的图标阴影(本项目所采用的方案)

.facility_no {filter: grayscale(100%);-webkit-filter: grayscale(100%);-moz-filter: grayscale(100%);-o-filter: grayscale(100%);text-decoration: line-through;
}.facility_yes {filter: drop-shadow(46px 0px 0px #fd5332);backdrop-filter: blur(0px);
}.facility_text {width: 46px;text-align: center;
}.facility_hidden {width: 46px;height: 46px;text-indent: -46px;overflow: hidden;
}<li v-for="item in all_house_facility"><div class="facility_no"v-if="house_facility_ids.indexOf(item.facility_id) == -1"><div><img :src="item.icon" :title="item.name" width="46" :alt="item.name"height="46"></div><p class="facility_text">{{ item.name }}</p></div><div v-else><div class="facility_hidden"><img :src="item.icon" :title="item.name" class="facility_yes"width="46"height="46"></div><p class="facility_text">{{ item.name }}</p></div>
</li>

2、数据库存存储两张不同颜色的图标

3、数据库还是存储一张图标但一张图标包含两种图标,前端通过切图来分割图标 background-image 属性搭配

background-positon:x轴起点 y轴起点;
background-size:背景图片的大小;
width:终点x轴位置;
height:终点y轴位置;

支付状态章印显示

通过 position 属性实现子绝父相定位章印元素,border-radius 控制边框圆角

 

.seal{width: 115px;height: 115px;border: solid 5px #B4B4B4;border-radius: 100%;background-color: rgba(255, 255, 255, 0.8);position: relative;display: flex;justify-content: center;align-items: center;
}
.seal-son{width: 110px;height: 110px;border: solid 2px #B4B4B4;border-radius: 100%;background-color: rgba(255, 255, 255, 0.8);position: relative;
}.seal-lg-text{position: absolute;top: 32px;text-align: center;font-size: 18px;transform: rotate(-45deg);right: 40px;color: #B4B4B4;font-weight: 900;
}
.seal-sm-text{position: absolute;top: 66px;text-align: center;font-size: 10px;transform: rotate(-45deg);left: 40px;color: #B4B4B4;
}

实际使用如果位置不够好,通过style重写覆盖css属性调整,有点像类继承一样

<div class="seal" style="position: absolute;right: -12px;top: 45px;"><div class="seal-son"><span class="seal-lg-text"><span v-if="order_detail_item.state === order_state_enum.payed">已支付</span><span v-else-if="order_detail_item.state === order_state_enum.ordered">已预订</span><span v-else-if="order_detail_item.state === order_state_enum.no_pay">未支付</span><span v-else-if="order_detail_item.state === order_state_enum.finished">已完成</span><span v-else-if="order_detail_item.state === order_state_enum.canceled">已取消</span></span><span class="seal-sm-text">{{ order_detail_item.update_ts }}</span></div>
</div>

元素大小缩放提高交互

.el_scale:hover {transform: scale(1.03)
}

项目界面展示

首页

 

 登录注册

 房源列表

 房源收藏

房源详情

  

房源地图服务

房源订单

电子合同

求租管理

系统公告

房东房源管理

房东发布房源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/24540.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8.3.9 加载租赁数据至租赁事实表

1.创建转换 使用Kettle工具&#xff0c;创建一个转换load_fact_rental&#xff0c;并添加表输入控件、字段选择控件、过滤记录控件、计算器控件、增加常量控件、数据库查询控件、维度查询/更新控件、插入/更新控件以及Hop跳连接线 2.配置表输入 3.配置表输入2 4.配置字段选择 …

特色租房管理系统/租房管理系统/房屋租赁管理系统

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

房租租赁租房系统都包含哪些功能?

俗话说成家立业&#xff0c;现在一提到婚姻无非就是房车等&#xff0c;房子一直是排在首位的&#xff0c;但是伴随着连年上涨的房价&#xff0c;从两三千到现在得一两万甚至更高。一套房子买下来动辄上百万&#xff0c;要掏空两个家庭多个钱包还要欠银行一笔贷款&#xff0c;于…

我的 SAP 技术交流群里的真实案例,看样子 ChatGPT 能帮我回答不少问题了

请问下大家&#xff0c;Fiori Elements 里面我要怎么知道页面上的东西的 element ID 是啥&#xff1f;就是 byId 方法调用需要传入的那个参数… 我的人工回复&#xff1a; 不管是 freestyle 还是 Fiori Elements&#xff0c;SAP UI5 控件最后渲染出来的 HTML 原生代码里的 id…

chatgpt赋能python:Python交流微信群:打开学习和分享的大门

Python交流微信群&#xff1a;打开学习和分享的大门 Python是一种高级编程语言&#xff0c;得益于其灵活性&#xff0c;易学性和强大的社区支持&#xff0c;已经成为许多程序员首选的开发语言。但是&#xff0c;学习Python并没有那么容易&#xff0c;很多人可能会面临困惑和挫…

chatgpt赋能python:Python交流群:分享经验、解决问题、结交朋友

Python 交流群&#xff1a;分享经验、解决问题、结交朋友 Python 是一种高级编程语言&#xff0c;被广泛使用于数据科学、机器学习、人工智能、网络开发、游戏开发等众多领域。作为一个有着10年 Python 编程经验的工程师&#xff0c;我深感 Python 社区的活力和创新力。其中&a…

Android 动画(七)AnimatorSet组合动画

概述&#xff1a; ValueAnimator和ObjectAnimator都是针对单个动画的&#xff0c;虽然可以用PropertyValuesHolder实现一个View的多种动画&#xff0c;但是没办法实现多个View同时动画。如果要对多个View做动画&#xff0c;并且单个View上存在多种动画效果&#xff0c;这时候就…

chatgpt赋能python:Python做动画视频教程-如何入门

Python做动画视频教程-如何入门 Python是一种非常强大的编程语言&#xff0c;它可以用于很多领域&#xff0c;比如数据科学、机器学习和计算机视觉等。但是你曾想过用Python来制作动画视频吗&#xff1f;这不仅可以增强你的编程技能&#xff0c;而且还可以让你的创意更加丰富。…

chatgpt赋能python:Python做动画需要学习哪些东西?

Python做动画需要学习哪些东西&#xff1f; Python是一种高级编程语言&#xff0c;具有易读易学的特点&#xff0c;因此被广泛应用于图形设计、动画、游戏和科学计算等领域。Python使动画制作变得更加简单和快速&#xff0c;并且允许用户在更广泛的平台上实现创意想法。 在本…

chatgpt赋能python:Python做动画演示

Python做动画演示 Python是一种脚本语言&#xff0c;适用于广泛的应用&#xff0c;例如数据分析和机器学习。但是&#xff0c;Python也可以用于创建动画演示。本文将介绍Python如何用于制作动画演示&#xff0c;并提供一些最佳实践来帮助您获得最佳效果。 起步 首先&#xf…

不会做动画的程序猿不是好的动画师(如何用css3动画做动画)

“看清animation&#xff0c;transform&#xff0c; keyframes&#xff0c;transition这四个的脸&#xff0c;以后这四个来了就是要做动画了&#xff0c;看好你们的网页&#xff0c;除了这四个&#xff0c;谁管你们都不好使。” 一.transition&#xff08;过渡&#xff09;&…

[一起来做动图吧]Animate制作简单动图,包教包会,不会举报

这个是目录 首先&#xff0c;认识一下An吧①区&#xff0c;我不怎么用但其实很重要②区&#xff0c;要和④区混合食用选择&#xff0c;自由变形&#xff0c;套索工具绘图工具&#xff08;以线条为例&#xff09;填充和笔触&#xff1a;对象绘制模式&#xff1a;笔触、样式和宽度…

chatgpt赋能python:用Python来制作动画

用Python来制作动画 Python是一种高级编程语言&#xff0c;可以用于许多任务&#xff0c;包括数据分析、网络编程&#xff0c;甚至是制作动画。在这篇文章中&#xff0c;我们将讨论如何使用Python来制作动画。 Python中的动画库 Python中有许多用于制作动画的库。其中最流行…

ping命令 网络抓包 分析

首先&#xff0c;执行ipconfig确认自己电脑的ip地址 可以得到我的电脑的ip地址为192.168.43.15&#xff0c;网关地址为192.168.43.1 打开wireshark抓包工具&#xff0c;ping网关&#xff0c;看看会发生什么 命令行中&#xff0c;我们发送了4个具有32B的数据&#xff0c;从抓…

网络抓包-抓包工具tcpdump的使用与数据分析

1.测试背景 本次测试选用两台不同的服务器&#xff0c;ip分别为.233和.246,233服务器为客户端&#xff0c;246服务器为服务端。利用tcp协议就行socket通信。socket网络编程部分示例代码为基本的通信代码&#xff0c;需要了解tcp网络通讯的基本协议与过程。服务器上采用tcpdump…

通过抓包研究TCP的连接、传输、断开

1-建立连接TCP三次握手 建立一个 TCP 连接需要“三次握手”&#xff0c;缺一不可 &#xff1a; 一次握手:客户端发送带有 SYN&#xff08;SEQx&#xff09; 标志的数据包 -> 服务端&#xff0c;然后客户端进入 SYN_SEND 状态&#xff0c;等待服务器的确认&#xff1b;二次握…

Winpcap进行抓包,分析数据包结构并统计IP流量

2020年华科计算机网络实验 文末有完整代码&#xff0c;仅限参考 一.实验目的 随着计算机网络技术的飞速发展&#xff0c;网络为社会经济做出越来越多的贡献&#xff0c;可以说计算机网络的发展已经成为现代社会进步的一个重要标志。但同时&#xff0c;计算机犯罪、黑客攻击、…

HttpCanary抓包断网问题解决方式

以上操作步骤完成&#xff0c;即可完成抓包操作

原生JS实现代码高亮功能

实现步骤 分析如何实现该功能了解词法结构Javascript的产生式少废话&#xff0c;上代码 分析如何实现该功能 平时我们在使用一些代码编辑器或者Markdown时很好奇它的代码高亮是如何 实现的。其实原理也挺简单的,就是区分代码内容的不同token并加以颜色标识。 我们将以js规则为例…

从六个维度来分析:代码、无代码、低代码、AI提示代码、AI低代码

IT行业最不缺少概念&#xff0c;再多几个也无妨&#xff0c;反正大部分的概念大部分人都不会真正弄懂。AI低代码是我们新创的&#xff0c;AIGC低代码、AI低代码、智能开发、AI生成式开发、AIGS(AI生成软件)等等&#xff0c;这些概念已经呼之欲出了&#xff0c;不过还是觉得AI低…