简介本资源是一套基于Python开发的轻量级资产管理系统完整源码面向Python初学者、Web开发入门者及企业IT资产管理员解决组织内部资产全生命周期购置、使用、维护、报废的数字化跟踪与管理问题。压缩包共21个文件含2个核心Python后端脚本实现业务逻辑与数据库交互、6个Word文档含系统设计说明、部署指南与用户手册、3个PDF技术文档涵盖架构图与API说明、2个Excel资产模板用于批量导入导出以及JPG界面截图、TXT配置示例等整体6.88MB结构清晰、开箱即用。已有441人学习下载读者可直接运行调试Flask/Django风格的Web服务深入理解SQLAlchemy ORM建模、用户权限控制、RESTful接口设计及Jinja2模板渲染等关键实践环节同时获得可二次开发的工程化项目骨架与规范化的文档配套。1. 这不是个“增删改查”Demo一个能真正在中小团队跑通资产台账、折旧计算、责任人联动和导出审计的Python系统你手头那个用Flask搭了三个路由、前端套了个Bootstrap表格、数据库只存了“名称/型号/数量”的“资产管理系统”大概率撑不过财务年审前的交叉核验。真实场景里一台2021年采购的戴尔笔记本要自动按直线法计提月折旧残值率5%使用年限36个月在调拨给新部门时同步更新责任人、领用日期、存放位置并触发邮件通知原负责人当某台设备报废时系统得生成带唯一编号的《固定资产处置单》PDF可打印、字段可追溯、操作留痕不可删——这些不是功能点列表而是财务合规与IT运维的硬性交界线。本项目源码包assetsystem_v2.3.zip正是为这类需求而生它不依赖Docker或K8s纯Python 3.9环境即可启动核心逻辑全部封装在core/目录下含折旧引擎、状态机驱动、Excel模板渲染器所有数据库操作经由SQLModel抽象支持SQLite开箱即用、PostgreSQL平滑迁移。适合刚接手IT资产管理模块的开发同学、需要快速交付内部工具的运维工程师以及想把纸质台账数字化但又不愿买SaaS服务的行政主管。它解决的不是“能不能显示数据”而是“财务来查账时你敢不敢把数据库直连给他们看”。2. 从解压到可运行本地最小化部署四步走2.1 解压与环境隔离为什么必须用venv而不是直接pip install不要跳过这一步。我见过太多人直接pip install -r requirements.txt后因全局环境中已存在旧版Pydantic2.0导致SQLModel初始化失败报错信息却指向core/models.py第47行的Field(default_factory...)——实际是Pydantic v1不支持该语法。正确做法是# 创建独立虚拟环境Python 3.9 python -m venv venv_assets source venv_assets/bin/activate # Linux/macOS # venv_assets\Scripts\activate.bat # Windows # 升级pip并安装依赖注意requirements.txt中已锁定版本 pip install --upgrade pip pip install -r requirements.txt提示requirements.txt中关键依赖已严格锁定sqlmodel0.0.16适配Pydantic v2、openpyxl3.1.2避免Excel公式解析异常、weasyprint62.2PDF生成稳定版。若手动升级其中任一包极可能触发连锁兼容问题。2.2 初始化数据库SQLite路径、表结构与初始数据注入系统默认使用SQLite数据库文件路径由环境变量ASSET_DB_PATH控制。首次运行前需执行初始化脚本它会创建6张核心表asset,department,employee,depreciation_record,maintenance_log,disposal_record并注入3条测试资产、2个部门、4名员工# 设置数据库路径建议放在项目根目录下便于备份 export ASSET_DB_PATH./data/assets.db # 执行初始化会自动创建data/目录及assets.db文件 python scripts/init_db.py # 验证表是否创建成功 sqlite3 ./data/assets.db .tables # 应输出asset department depreciation_record disposal_record employee maintenance_loginit_db.py的核心逻辑是调用core/db_init.py中的init_database()函数该函数不仅建表还预置了折旧策略枚举StraightLine,DecliningBalance、资产状态机InStock,InUse,UnderRepair,Disposed及部门树形结构父ID约束。关键参数说明--sample-data开关决定是否注入测试数据生产环境部署时务必关闭--force-recreate会删除现有db文件重来仅调试用。2.3 启动Web服务Flask开发服务器配置要点系统使用Flask作为Web框架但未采用默认的flask run命令而是通过app.py入口统一管理配置。启动前需确认两个环境变量# 必须设置指定运行模式dev/test/prod export FLASK_ENVdev # 必须设置Secret Key用于session加密生产环境请换为32位随机字符串 export SECRET_KEYdev-secret-key-change-in-prod # 启动服务默认监听127.0.0.1:5000 python app.py此时访问http://127.0.0.1:5000将看到登录页。默认账号密码为admin / admin123首次登录后强制修改。注意app.py中create_app()函数会根据FLASK_ENV加载不同配置类——DevConfig启用调试模式、SQLAlchemy echo日志ProdConfig则关闭调试、启用Werkzeug超时保护、强制HTTPS重定向需Nginx前置。若需修改端口在app.py第28行调整app.run(port5000)即可。2.4 登录与首屏验证检查核心功能链路是否打通登录后首页/dashboard会展示4个关键指标卡片在用资产总数SELECT COUNT(*) FROM asset WHERE status InUse本月折旧总额SELECT SUM(monthly_depreciation) FROM depreciation_record WHERE month 2024-06待处理维修单SELECT COUNT(*) FROM maintenance_log WHERE status Pending近30天调拨记录SELECT COUNT(*) FROM asset_history WHERE action Transfer AND created_at datetime(now, -30 days)点击任一卡片应跳转至对应列表页且列表页URL含查询参数如/assets?statusInUse。若卡片数字为0但数据库中有测试数据请立即检查core/services/dashboard_service.py中get_dashboard_stats()函数的SQL查询条件——常见错误是status字段值写成in_use小写而非枚举定义的InUse驼峰。3. 资产全生命周期操作从录入到报废的代码级实现逻辑3.1 录入新资产表单验证、折旧策略绑定与自动生成编码新增资产页面/assets/new提交后请求由routes/asset_routes.py中的create_asset()函数处理。其核心流程分三步表单解析与业务校验使用Pydantic模型AssetCreateSchema进行强类型校验关键约束包括purchase_date不能晚于当前日期field_validator(purchase_date)unit_price必须大于0ge0.01depreciation_method必须是枚举值StraightLine或DecliningBalance资产编码生成编码规则为ASSET-{YYYY}{MM}-{3位序列号}如ASSET-202406-001。序列号从asset_sequence表获取并原子递增# core/services/asset_service.py 第89行 def generate_asset_code() - str: with Session(engine) as session: stmt text(UPDATE asset_sequence SET counter counter 1 RETURNING counter) result session.execute(stmt) seq_num result.scalar_one() return fASSET-{datetime.now().strftime(%Y%m)}-{seq_num:03d}折旧计划预计算根据depreciation_method、useful_life_months、residual_value_rate调用core/calculators/depreciation_calculator.py生成未来36个月的折旧记录存入depreciation_record表避免每次查询实时计算。参数说明residual_value_rate默认0.055%可在AssetCreateSchema中覆盖useful_life_months若为空则按资产类别category字段查core/config/asset_category_rules.json映射表自动填充如Laptop→36个月。3.2 资产调拨状态变更、历史记录与责任人联动调拨操作/assets/{id}/transfer是状态机驱动的关键场景。AssetTransferSchema要求必填to_department_id和to_employee_id后端执行# core/services/asset_service.py transfer_asset() 函数节选 def transfer_asset(asset_id: int, to_dept_id: int, to_emp_id: int): with Session(engine) as session: # 1. 查询原资产记录 asset session.get(Asset, asset_id) if not asset: raise HTTPException(status_code404, detailAsset not found) # 2. 状态机校验仅允许从 InUse/UnderRepair 调拨 if asset.status not in [InUse, UnderRepair]: raise HTTPException(status_code400, detailfCannot transfer asset in status {asset.status}) # 3. 更新资产主表 asset.department_id to_dept_id asset.employee_id to_emp_id asset.status InUse # 调拨后默认为在用 asset.last_transfer_date datetime.now() # 4. 写入历史记录表asset_history history AssetHistory( asset_idasset_id, actionTransfer, from_department_idasset.department_id, # 注意此处是调拨前的旧值 from_employee_idasset.employee_id, to_department_idto_dept_id, to_employee_idto_emp_id, notesAuto-generated by system ) session.add(history) session.commit()关键设计asset_history表不存储冗余字段所有关联ID均引用主表外键确保数据一致性。调拨成功后系统自动向to_employee_id发送站内信非邮件内容为“您已接收资产【{asset.code}】请于3个工作日内确认”。3.3 报废处置多级审批流模拟与PDF单据生成报废流程/assets/{id}/dispose需模拟三级审批申请人提交 → 部门负责人审批 → 财务部终审。源码中通过disposal_status字段Draft/ApprovedByDept/ApprovedByFinance/Rejected实现状态流转。PDF单据生成由core/reports/disposal_report.py完成# core/reports/disposal_report.py 第42行 def generate_disposal_pdf(disposal_id: int) - bytes: with Session(engine) as session: disposal session.get(DisposalRecord, disposal_id) # 1. 查询关联资产、申请人、审批人信息 asset session.get(Asset, disposal.asset_id) applicant session.get(Employee, disposal.applicant_id) dept_approver session.get(Employee, disposal.dept_approver_id) if disposal.dept_approver_id else None # 2. 渲染HTML模板templates/reports/disposal.html html_content render_template( reports/disposal.html, disposaldisposal, assetasset, applicantapplicant, dept_approverdept_approver, # 生成唯一防伪码基于disposal_idtimestampsecret_key的HMAC anti_fraud_codegenerate_anti_fraud_code(disposal_id) ) # 3. 转PDFWeasyPrint pdf_bytes HTML(stringhtml_content).write_pdf() return pdf_bytesanti_fraud_code使用hmac.new()生成确保每份PDF单据具备可验证性。财务人员可通过系统内“单据验真”功能输入防伪码反查原始记录。4. 避坑指南生产环境部署必踩的5个深坑与血泪解法4.1 现象Excel导入后中文字段全变成乱码如“戴尔”→“æ´å°”原因openpyxl读取.xlsx文件时默认使用utf-8编码但Windows用户用Excel另存的文件常为gbk编码且openpyxl不提供编码指定参数。解决在core/importers/excel_importer.py中改用pandas.read_excel()替代原生openpyxl.load_workbook()并显式指定engineopenpyxl和encodinggbk自动检测编码的方案见下文补丁# 替换原load_workbook逻辑 df pd.read_excel(file_path, engineopenpyxl, dtypestr) # 若仍乱码加编码探测需安装chardet import chardet with open(file_path, rb) as f: raw_data f.read(10000) encoding chardet.detect(raw_data)[encoding] or utf-8 df pd.read_excel(file_path, engineopenpyxl, encodingencoding)4.2 现象定时任务cron_job.py在Linux后台运行时折旧计算结果为空原因cron环境变量缺失ASSET_DB_PATH且python路径未用绝对路径导致找不到数据库文件。解决在crontab中写绝对路径并显式导出变量# 编辑crontabcrontab -e 0 2 * * * cd /opt/assetsystem /opt/assetsystem/venv_assets/bin/python scripts/cron_job.py /var/log/assetsystem_cron.log 21 # 或在cron_job.py开头强制设置 import os os.environ.setdefault(ASSET_DB_PATH, /opt/assetsystem/data/assets.db)4.3 现象PDF导出的中文不显示方块或空白原因WeasyPrint默认字体不支持中文需指定中文字体路径。解决在core/reports/base_report.py中注册字体from weasyprint import CSS from weasyprint.fonts import FontConfiguration font_config FontConfiguration() css CSS(string font-face { font-family: SimSun; src: url(file:///usr/share/fonts/truetype/simsun.ttc); } body { font-family: SimSun, sans-serif; } , font_configfont_config) # 生成PDF时传入css参数 HTML(stringhtml_content).write_pdf(stylesheets[css], font_configfont_config)注意Linux服务器需提前安装simsun.ttc字体sudo apt-get install fonts-wqy-zenhei并确认路径正确。4.4 现象多人同时提交资产报废出现重复审批单或状态错乱原因disposal_status更新未加数据库行锁导致并发更新丢失。解决在core/services/disposal_service.py的update_disposal_status()中使用SELECT ... FOR UPDATE# 修改前危险 stmt update(DisposalRecord).where(DisposalRecord.id disposal_id).values(statusnew_status) # 修改后安全 with Session(engine) as session: # 先锁定记录 disposal session.execute( select(DisposalRecord).where(DisposalRecord.id disposal_id).with_for_update() ).scalar_one() disposal.status new_status session.commit()4.5 现象搜索框输入“笔记本”返回空结果但数据库明确存在原因全文检索使用SQLite的LIKE %笔记本%但未对搜索词做%包裹且未处理前后空格。解决在core/search/asset_search.py中修正build_search_condition()函数def build_search_condition(keyword: str) - BinaryExpression: keyword keyword.strip() # 去除空格 if not keyword: return true() # 返回True条件避免空搜索报错 # 使用ILIKESQLite不支持改用LOWER LIKE return or_( func.lower(Asset.name).like(f%{keyword.lower()}%), func.lower(Asset.model).like(f%{keyword.lower()}%), Asset.code.like(f%{keyword}%) # 编码通常为英文数字不转小写 )5. 审计就绪让财务和内审人员愿意点开你的数据库5.1 导出符合《企业会计准则第4号——固定资产》的折旧明细表财务最关心的不是界面有多炫而是“这个月计提的12,345.67元到底怎么算出来的”。系统提供/reports/depreciation_detail?month2024-06接口返回标准CSV格式字段严格对应准则要求资产编码资产名称入账日期原值累计折旧本月折旧折旧方法使用年限月残值率期末账面价值ASSET-202401-001戴尔XPS132024-01-158999.001249.86249.97直线法360.057749.14生成逻辑在core/reports/depreciation_report.py中关键点期末账面价值 原值 - 累计折旧非实时计算取depreciation_record表中截至当月的SUM累计折旧字段为子查询结果确保与财务系统一致CSV使用utf-8-sig编码BOM头保障Excel双击打开不乱码实操技巧将此URL加入Nginx反向代理配置/finance/export/路径财务人员直接浏览器访问下载无需登录系统。5.2 数据库审计视图用视图固化“谁在何时干了什么”内审人员需要快速定位操作痕迹但asset_history表字段分散。系统预建了审计视图v_audit_asset_actions-- 在init_db.py中执行 CREATE VIEW v_audit_asset_actions AS SELECT h.id AS history_id, h.action, h.notes, h.created_at, a.code AS asset_code, a.name AS asset_name, e1.name AS operator_name, e2.name AS target_employee_name, d1.name AS from_department, d2.name AS to_department FROM asset_history h LEFT JOIN asset a ON h.asset_id a.id LEFT JOIN employee e1 ON h.operator_id e1.id LEFT JOIN employee e2 ON h.to_employee_id e2.id LEFT JOIN department d1 ON h.from_department_id d1.id LEFT JOIN department d2 ON h.to_department_id d2.id;审计时只需执行SELECT * FROM v_audit_asset_actions WHERE created_at 2024-06-01 AND action IN (Transfer, Dispose) ORDER BY created_at DESC LIMIT 100;视图将所有关联表JOIN固化避免内审人员写错JOIN条件导致漏查。5.3 敏感操作二次确认与操作留痕强化财务最反感“误点删除”。系统对DELETE类操作强制二次确认前端/assets/{id}/delete按钮点击后弹出Modal要求输入当前登录用户名非密码后端routes/asset_routes.py中delete_asset()函数校验request.form[confirm_username] current_user.username留痕无论删除成功与否均写入audit_log表字段含ip_addressrequest.client.host、user_agent、operation_resultsuccess/failed我的习惯每周五下班前我会手动执行一次SELECT COUNT(*) FROM audit_log WHERE operation_type DELETE AND DATE(created_at) CURDATE();如果结果非0立刻查日志确认是否为误操作——这比等财务打电话来问强十倍。希望帮到你。本文还有配套的精品资源点击获取