Django银行信贷管理系统设计与实现:从数据库建模到还款计划
2026/9/17 5:37:24 网站建设 项目流程

简介:这套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 ledger

customer管客户资料,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_length=50) id_number = models.CharField('证件号码', max_length=18, unique=True) phone = models.CharField('手机号', max_length=20) created_at = models.DateTimeField('建档时间', auto_now_add=True) 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_delete=models.PROTECT, verbose_name='客户' ) amount = models.DecimalField('申请金额', max_digits=12, decimal_places=2) term_months = models.PositiveSmallIntegerField('期限(月)') annual_rate = models.DecimalField('年利率', max_digits=5, decimal_places=4) status = models.PositiveSmallIntegerField( '状态', choices=STATUS_CHOICES, default=STATUS_DRAFT ) apply_time = models.DateTimeField('申请时间', auto_now_add=True) updated_at = models.DateTimeField('更新时间', auto_now=True) applicant = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.PROTECT, verbose_name='经办人' )
# ledger/models.py from django.db import models class LoanAccount(models.Model): application = models.OneToOneField( 'credit.CreditApplication', on_delete=models.PROTECT, verbose_name='关联申请' ) contract_no = models.CharField('借据编号', max_length=32, unique=True) principal = models.DecimalField('放款本金', max_digits=12, decimal_places=2) annual_rate = models.DecimalField('执行年利率', max_digits=5, decimal_places=4) term_months = models.PositiveSmallIntegerField('期限(月)') begin_date = models.DateField('起息日') due_date = models.DateField('到期日') class RepaymentPlan(models.Model): loan = models.ForeignKey( LoanAccount, on_delete=models.CASCADE, verbose_name='借据' ) term_no = models.PositiveSmallIntegerField('期次') due_date = models.DateField('应还日期') repay_principal = models.DecimalField('应还本金', max_digits=12, decimal_places=2) repay_interest = models.DecimalField('应还利息', max_digits=12, decimal_places=2) status = models.BooleanField('是否已还', default=False)

这里需要说透几个选择。金额一律用DecimalField(max_digits=12, decimal_places=2),整数部分十亿级足够毕设演示,小数两位对应分。状态用数字而不是字符串,因为数据库索引更小,也方便后面写状态迁移表。客户身份证号加unique=True,这是业务上的天然唯一键。外键的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 + unique=True证件号不参与数值运算
业务状态PositiveSmallIntegerField范围够用,索引更小
业务日期DateField信贷日期不含时分秒
创建时间DateTimeField(auto_now_add=True)由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(name=g) "

创建好分组后,在Django管理后台把不同账号放进对应分组。不要用is_staff当角色判断条件,它只决定能否进Admin。装饰器写法是:

from django.contrib.auth.decorators import login_required, permission_required @login_required @permission_required('credit.change_creditapplication', raise_exception=True) 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', flat=True)) 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(pk=application_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_id=app.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_delete=models.CASCADE, verbose_name='申请' ) user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.PROTECT, verbose_name='操作用户' ) old_status = models.PositiveSmallIntegerField('原状态') new_status = models.PositiveSmallIntegerField('新状态') remark = models.CharField('备注', max_length=255, blank=True) created_at = models.DateTimeField('操作时间', auto_now_add=True)

change_status保存状态后插入一行:

LoanOperationLog.objects.create( application=app, user=request.user, old_status=old_status, new_status=target_status, )

日志不要从界面上提供删除入口,也不要在Admin里开放日志表的删除权限。用CASCADE会导致申请单删除时日志一并删掉,如果希望日志永久保留,可以用PROTECT;毕设场景里CASCADE可以接受,但答辩时要说清取舍:生产环境通常做归档而不是物理删除,能讲出这一句,源码完成度的评价会明显不一样。

提示:on_delete=PROTECTCASCADE是Django模型设计里最常被追问的一组概念。能说明“业务日志需要留底,所以生产环境不会级联删除”的人,和背文档的人一眼就能区分。

使用Django Admin的LogEntry也可以,LogEntry.objects.log_action(...)能写入后台操作记录,但它在自定义视图里要额外填content_typeobject_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'), rounding=ROUND_HALF_UP ) factor = (Decimal(1) + monthly_rate) ** months return (amount * monthly_rate * factor / (factor - Decimal(1))).quantize( Decimal('0.01'), rounding=ROUND_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, sender=LoanAccount) 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'), rounding=ROUND_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( loan=instance, term_no=term, due_date=instance.begin_date + relativedelta(months=term), repay_principal=principal_part, repay_interest=interest, )

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还款后置True

4.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'), rounding=ROUND_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.json

dumpdata输出JSON,--natural-foreign会把外键序列化成可读的别名,避免换环境时ID冲突。提交目录里同时保留demo_data.jsondb.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 / Demo@12345、风控专员risk / Demo@12345、审批主管director / Demo@12345。明文密码只用于本地演示,这一点要在README开头注明。

5.3 答辩演示顺序:从建档到还清只演示十分钟

演示时不要全页面点一遍,建议按业务链路走:先建客户,再发起申请,切换风控账号通过申请,以主管账号放款,然后展示自动生成的还款计划,最后把第一期标记为逾期看罚息金额。状态一旦走完不要回头改,如果现场被要求改状态,打开Admin后台演示权限控制会更快。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询