ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django银行信贷管理系统设计与实现:从数据库建模到还款计划

Django银行信贷管理系统设计与实现:从数据库建模到还款计划 简介这套Python基于Django的银行信贷管理系统毕业设计源码面向计算机相关专业毕业生及需要项目实战练习的Python学习者定位是解决信贷业务系统设计、前后端联调与数据库整合等毕业设计中的常见问题。系统围绕贷款申请、客户信息管理、审核与放款等典型模块展开整体难度适中属于经导师指导并获得98分评审的毕业设计项目源码均经过本地编译调试具备直接运行与演示条件。压缩包共约2000个文件以JavaScript、HTML、CSS前端资源为主配合Python后台脚本、JSON数据、XML配置及说明文档整体仅6.32MB体量小、目录清晰便于快速部署和二次开发。目前已有314人学习下载。借助这套源码可系统掌握Django项目结构、ORM数据操作、前端资源组织方式以及银行信贷业务流程设计其中的页面框架、数据交互与后台逻辑也为功能扩展、文档撰写和答辩演示提供参考。1. 银行信贷管理系统高分毕设真正在考什么银行信贷管理系统是计算机专业毕业设计里典型的“业务闭环型”题目客户、授信、申请、审批、放款、还款每一步都有状态变化和数字变化。很多人一上来就按增删改查堆页面结果做出来的东西像员工信息管理答辩老师一句“你的借据和还款计划对得上吗”就卡住。这个题目真正的分水岭在于状态可追溯、资金数字用对类型、权限边界清楚。Python的Django恰好在这三件事上都有现成底座ORM处理资金字段精度Auth和Group解决角色权限Migration管理数据库版本。接下来按我实际做这类项目的路径讲一遍从数据库设计到源码交付的关键环节适合用Django 4.x及以上做毕设的读者。2. 用Django拆分信贷业务数据库设计与核心模型怎么写2.1 三个App把信贷系统拆开而不是一个models.py到底毕设系统体量不大但信贷业务天然分属三个域客户信息、授信审批、借据还款。我一般会创建三个独立App而不是把模型都堆进一个models.py后续管理后台和答辩画架构图都会好说很多。django-admin startproject credit_system . python manage.py startapp customer python manage.py startapp credit python manage.py startapp ledgercustomer管客户资料credit管贷款申请与审批状态ledger管借据和还款计划。这样划分后credit依赖customerledger只依赖credit依赖方向是单向的。答辩时老师问“你们的业务边界在哪里”你直接拿这层依赖关系回答比空说微服务清楚得多。App之间不要互相乱查跨App只通过外键或明确的服务函数访问。2.2 信贷核心模型的必选字段从Customer到RepaymentPlan无论后端是Django还是其他框架信贷系统核心逃不开这几张表。下面是一段简化但能跑的模型示例直接用ORM表达。# customer/models.py from django.db import models class Customer(models.Model): name models.CharField(姓名, max_length50) id_number models.CharField(证件号码, max_length18, uniqueTrue) phone models.CharField(手机号, max_length20) created_at models.DateTimeField(建档时间, auto_now_addTrue) class Meta: db_table cus_customer# credit/models.py from django.db import models from django.conf import settings class CreditApplication(models.Model): STATUS_DRAFT 0 STATUS_SUBMITTED 10 STATUS_APPROVED 20 STATUS_REJECTED 30 STATUS_CHOICES [ (STATUS_DRAFT, 草稿), (STATUS_SUBMITTED, 已提交), (STATUS_APPROVED, 已通过), (STATUS_REJECTED, 已拒绝), ] customer models.ForeignKey( customer.Customer, on_deletemodels.PROTECT, verbose_name客户 ) amount models.DecimalField(申请金额, max_digits12, decimal_places2) term_months models.PositiveSmallIntegerField(期限月) annual_rate models.DecimalField(年利率, max_digits5, decimal_places4) status models.PositiveSmallIntegerField( 状态, choicesSTATUS_CHOICES, defaultSTATUS_DRAFT ) apply_time models.DateTimeField(申请时间, auto_now_addTrue) updated_at models.DateTimeField(更新时间, auto_nowTrue) applicant models.ForeignKey( settings.AUTH_USER_MODEL, on_deletemodels.PROTECT, verbose_name经办人 )# ledger/models.py from django.db import models class LoanAccount(models.Model): application models.OneToOneField( credit.CreditApplication, on_deletemodels.PROTECT, verbose_name关联申请 ) contract_no models.CharField(借据编号, max_length32, uniqueTrue) principal models.DecimalField(放款本金, max_digits12, decimal_places2) annual_rate models.DecimalField(执行年利率, max_digits5, decimal_places4) term_months models.PositiveSmallIntegerField(期限月) begin_date models.DateField(起息日) due_date models.DateField(到期日) class RepaymentPlan(models.Model): loan models.ForeignKey( LoanAccount, on_deletemodels.CASCADE, verbose_name借据 ) term_no models.PositiveSmallIntegerField(期次) due_date models.DateField(应还日期) repay_principal models.DecimalField(应还本金, max_digits12, decimal_places2) repay_interest models.DecimalField(应还利息, max_digits12, decimal_places2) status models.BooleanField(是否已还, defaultFalse)这里需要说透几个选择。金额一律用DecimalField(max_digits12, decimal_places2)整数部分十亿级足够毕设演示小数两位对应分。状态用数字而不是字符串因为数据库索引更小也方便后面写状态迁移表。客户身份证号加uniqueTrue这是业务上的天然唯一键。外键的on_delete是答辩常问点PROTECT表示有借据的客户不能被删避免脏数据CASCADE用在还款计划上借据删除时计划跟着删。外键引settings.AUTH_USER_MODEL而不是直接引auth.User这是Django推荐写法也是一眼能看到的专业细节。上面的模型没有单独做授信额度表。如果毕设只是演示一次申请一次放款不建也可以但答辩老师但凡问到“这个客户能不能连续贷多笔”你可以在Customer上增加credit_limit字段或者单独建CreditLimit记录总额度与已用额度。已用额度不要直接存一个余额字段而是用关联申请金额汇总后反写这类“余额由流水计算得出”的思路在信贷系统里很常见建议在README里写明这个扩展方向。2.3 用Migration管理数据库不手动粘贴SQL数据库类型怎么选不重要开发阶段用SQLite、提交时换MySQL都行关键是建表必须走Django迁移。手动复制SQL的常见后果是本地和线上字段不一致答辩演示时翻车。python manage.py makemigrations customer credit ledger python manage.py migrate执行后每个App下的migrations目录会生成编号文件。如果导师要求必须提供.sql用python manage.py sqlmigrate customer 0001查看DDL再另存但源码里还是以迁移文件为准。迁移文件是数据库结构的唯一真相来源不要维护两份建表脚本。可以把生成的SQL放进docs/database/当辅助材料但后续字段变更只改models再迁移。模型写好后记得在admin.py里注册。Django Admin自带界面虽然朴素但完全够展示数据毕设阶段不要花太多时间美化后台数据关系清楚比好看重要。数据库设计评审重点看字段类型选择参考下表字段含义推荐类型理由金额、本金、利息DecimalField避免二进制浮点误差客户证件号CharField uniqueTrue证件号不参与数值运算业务状态PositiveSmallIntegerField范围够用索引更小业务日期DateField信贷日期不含时分秒创建时间DateTimeField(auto_now_addTrue)由ORM维护后台可读3. 审批状态机与角色权限Django里怎么把流程写清楚3.1 复用Django自带User和Group不要再建员工表很多学生习惯建一张Employee表再把登录账号和业务员工分开结果一个用户两套身份。Django的auth模块已经包含用户、组、权限直接扩展即可。常见角色有客户经理发起申请、风控、审批主管、查询员。python manage.py shell -c from django.contrib.auth.models import User, Group for g in [customer_manager, risk_officer, credit_director, viewer]: Group.objects.get_or_create(nameg) 创建好分组后在Django管理后台把不同账号放进对应分组。不要用is_staff当角色判断条件它只决定能否进Admin。装饰器写法是from django.contrib.auth.decorators import login_required, permission_required login_required permission_required(credit.change_creditapplication, raise_exceptionTrue) def credit_approve(request, application_id): # 审批逻辑 ...permission_required的参数形如应用名.操作模型名例如credit.change_creditapplication。查询员角色不要分配delete_权限这些细粒度控制在源码里一条条写清楚就是答辩能讲的内容。3.2 审批状态机用状态表角色白名单而不是一堆if贷款申请审批至少四个状态草稿、已提交、审批通过、已拒绝。有些项目会补一个已放款。状态与角色之间的迁移约束我建议写成一张显式的状态迁移表而不是在每个视图里写三层if。当前状态执行角色目标状态草稿客户经理已提交已提交风控已通过 / 已拒绝已通过信贷主管已放款已拒绝客户经理草稿代码落地方案# credit/flow.py from django.core.exceptions import PermissionDenied TRANSITIONS { customer_manager: { 0: {10}, # 草稿 - 已提交 30: {0}, # 已拒绝 - 草稿重新发起 }, risk_officer: { 10: {20, 30}, # 已提交 - 已通过 / 已拒绝 }, credit_director: { 20: {40}, # 已通过 - 已放款 }, } def transition_allowed(user, current_status, target_status): group_names set(user.groups.values_list(name, flatTrue)) for group in group_names: if current_status in TRANSITIONS.get(group, {}) and target_status in TRANSITIONS[group][current_status]: return True return False然后在处理审批的视图里调用def change_status(request, application_id, target_status): app CreditApplication.objects.select_related(applicant).get(pkapplication_id) if not transition_allowed(request.user, app.status, target_status): raise PermissionDenied(当前用户不能执行该状态变更) old_status app.status app.status target_status app.save(update_fields[status, updated_at]) return redirect(credit_detail, application_idapp.pk)状态表TRANSITIONS用当前状态映射目标状态集合角色映射放在最外层判断时直接查集合。它的可读性比一连串if user.role xxx and app.status x好以后新增状态不会改到一堆视图。业务上只有客户经理能把“已拒绝”拉回“草稿”重新走流程这个约束在状态表里一眼可见。目标状态从页面传参时必须是白名单里的数字不要在视图里直接接受前端传来的任意状态否则审批会被绕过。如果一个人同时属于多个组状态表会同时放开多条路径实际业务中账号应只保留单一角色分配权限时要注意排他性。3.3 操作日志表把业务动作变成可审计的数据信贷是强监管业务答辩老师很容易问“这笔审批是谁在什么时候批的”。Django Admin自带LogEntry能记录后台操作但业务前台的自定义状态变更也要留痕。自己建一张日志表反而更好说明。# credit/models.py from django.conf import settings from django.db import models class LoanOperationLog(models.Model): application models.ForeignKey( CreditApplication, on_deletemodels.CASCADE, verbose_name申请 ) user models.ForeignKey( settings.AUTH_USER_MODEL, on_deletemodels.PROTECT, verbose_name操作用户 ) old_status models.PositiveSmallIntegerField(原状态) new_status models.PositiveSmallIntegerField(新状态) remark models.CharField(备注, max_length255, blankTrue) created_at models.DateTimeField(操作时间, auto_now_addTrue)在change_status保存状态后插入一行LoanOperationLog.objects.create( applicationapp, userrequest.user, old_statusold_status, new_statustarget_status, )日志不要从界面上提供删除入口也不要在Admin里开放日志表的删除权限。用CASCADE会导致申请单删除时日志一并删掉如果希望日志永久保留可以用PROTECT毕设场景里CASCADE可以接受但答辩时要说清取舍生产环境通常做归档而不是物理删除能讲出这一句源码完成度的评价会明显不一样。提示on_deletePROTECT与CASCADE是Django模型设计里最常被追问的一组概念。能说明“业务日志需要留底所以生产环境不会级联删除”的人和背文档的人一眼就能区分。使用Django Admin的LogEntry也可以LogEntry.objects.log_action(...)能写入后台操作记录但它在自定义视图里要额外填content_type和object_id不如自建表直观。这两种方案选一个即可关键是日志动作本身不能散落在视图里。4. 利息计算与还款计划生成Django代码里的资金精度坑4.1 等额本息用Decimal算不用Float信贷系统最容易被扣分的是利息算错。常见错误是年利率除以12之后用float当期数多、金额大时会差几分钱。银行系统的规则是每期计算都用Decimal并在最后一步四舍五入到分。# ledger/services.py from decimal import Decimal, ROUND_HALF_UP def calc_monthly_payment(amount, annual_rate, months): monthly_rate annual_rate / Decimal(12) if monthly_rate 0: return (amount / Decimal(months)).quantize( Decimal(0.01), roundingROUND_HALF_UP ) factor (Decimal(1) monthly_rate) ** months return (amount * monthly_rate * factor / (factor - Decimal(1))).quantize( Decimal(0.01), roundingROUND_HALF_UP )调用示例amount Decimal(100000.00) annual_rate Decimal(0.0500) # 年利率5% months 36 print(calc_monthly_payment(amount, annual_rate, months))入参必须全部转成DecimalDecimal(0.0500)的精度在计算过程中会被保留。ROUND_HALF_UP是银行常用的四舍五入不是Python原生round()的“银行家舍入”。这里很多人会踩坑round(2.675, 2)结果是2.67因为二进制浮点无法精确表示2.675。从DecimalField读出来的对象本身就是Decimal不要再用float()转一次。4.2 用信号在放款后自动生成还款计划放款动作确认后就要生成整张还款计划表。最稳妥的做法不是在视图里循环插入而是用Django信号监听LoanAccount创建后自动生成。这样无论从哪个入口放款都会触发计划生成。# ledger/signals.py from decimal import Decimal, ROUND_HALF_UP from dateutil.relativedelta import relativedelta from django.db.models.signals import post_save from django.dispatch import receiver from .models import LoanAccount, RepaymentPlan from .services import calc_monthly_payment receiver(post_save, senderLoanAccount) def create_repayment_plan(sender, instance, created, **kwargs): if not created: return monthly_payment calc_monthly_payment( instance.principal, instance.annual_rate, instance.term_months ) balance instance.principal for term in range(1, instance.term_months 1): interest (balance * instance.annual_rate / Decimal(12)).quantize( Decimal(0.01), roundingROUND_HALF_UP ) principal_part monthly_payment - interest if term instance.term_months: principal_part balance monthly_payment principal_part interest balance - principal_part RepaymentPlan.objects.create( loaninstance, term_noterm, due_dateinstance.begin_date relativedelta(monthsterm), repay_principalprincipal_part, repay_interestinterest, )在post_save里判断created可以避免修改借据时重复生成。最后一期修正本金尾差让剩余本金归零。每期利息按“剩余本金×月利率”计算而不是把总利息平均分摊。这样报表里会出现利息递减、本金递增的序列答辩时把还款计划表调出来给老师看比空讲公式有说服力。dateutil.relativedelta用来处理跨月加日期Python标准库timedelta做不到“加一个月”的语义。记得在requirements.txt里加入python-dateutil。信号还需要在AppConfig里注册否则不会生效# ledger/apps.py from django.apps import AppConfig class LedgerConfig(AppConfig): name ledger def ready(self): import ledger.signals # noqa然后INSTALLED_APPS里写ledger.apps.LedgerConfig而不是ledger。这是Django信号最容易踩的坑模型建好、迁移跑了但还款计划始终不生成多半就是AppConfig没有加载。还款计划生成后的核心字段如下答辩讲解时可以按这个顺序说字段示例值说明term_no1从1开始递增due_date2024-06-01每月固定日repay_principal2581.21每期递增repay_interest416.67每期递减statusFalse还款后置True4.3 逾期罚息和提前还款的两个计算边界罚息一般按“逾期本金×日罚息率×逾期天数”计算。年化罚息率转日利率时不同银行有360天或365天两种基准代码里必须显式写成常量from datetime import date from decimal import Decimal, ROUND_HALF_UP DAYS_IN_YEAR Decimal(360) # 信贷常用360天基准 def calc_penalty(overdue_principal, annual_penalty_rate, days): daily_rate annual_penalty_rate / DAYS_IN_YEAR return (overdue_principal * daily_rate * Decimal(days)).quantize( Decimal(0.01), roundingROUND_HALF_UP ) overdue_days (date.today() - RepaymentPlan.due_date).days计算前要判断这个还款计划是否已还以及到期日是否真的早于今天未来日期相减会出现负数天数业务上要先过滤。开发时容易被忽略的是“自然日”与“工作日”的区别银行信贷一般按自然日计息这个约定可以写在函数注释上。参数常规取值代码影响计息基准360或365日利率年利率/基准逾期利率合同利率×1.5从借据字段读取提前还款违约金剩余本金×1%视产品而定还款日规则每月固定日期跨月用relativedelta提前还款如果要收违约金简单做法是取未还本金的一定比例更完整的做法是把剩余计划中的未来利息按天折算后重算。不建议把这个算法放进主流程可作为扩展点在README中说明让答辩变成“我能讲清楚为什么不做”。5. 把源码和数据库整理成能拿高分的毕业设计交付物源码能不能拿高分最后取决于三个公开产出迁移脚本是否干净、演示数据是否齐全、README是否能按步骤跑起来。很多人写了一个月代码却忘了把数据库初始化过程写清楚。5.1 数据库交付用Fixture不要只发一个db文件SQLite的.db文件确实能直接跑但评审电脑环境不对就打不开。更职业的做法是把核心数据导出成Django fixture让评审用命令初始化python manage.py dumpdata --natural-foreign --natural-primary -o demo_data.json customer credit ledger python manage.py loaddata demo_data.jsondumpdata输出JSON--natural-foreign会把外键序列化成可读的别名避免换环境时ID冲突。提交目录里同时保留demo_data.json和db.sqlite3但README里明确写“导入演示数据请先运行loaddata”。源码评审会认为你理解环境一致性。5.2 README必须写清楚运行步骤和演示账号README里至少要有四段Python环境与依赖安装、迁移数据库、导入演示数据、创建管理员账号。依赖安装直接给命令pip install django python-dateutil python manage.py makemigrations customer credit ledger python manage.py migrate python manage.py loaddata demo_data.json python manage.py createsuperuser python manage.py runserver如果你的环境命令是python3把上面所有python换成python3即可。演示账号写在README里例如客户经理manager / Demo12345、风控专员risk / Demo12345、审批主管director / Demo12345。明文密码只用于本地演示这一点要在README开头注明。5.3 答辩演示顺序从建档到还清只演示十分钟演示时不要全页面点一遍建议按业务链路走先建客户再发起申请切换风控账号通过申请以主管账号放款然后展示自动生成的还款计划最后把第一期标记为逾期看罚息金额。状态一旦走完不要回头改如果现场被要求改状态打开Admin后台演示权限控制会更快。本文还有配套的精品资源点击获取
返回列表