
1. 固定资产管理的真实痛点为什么Excel台账永远对不上账我做行政和信息化这行有几年了最头疼的事情之一就是固定资产盘点。公司几百号人几十个部门电脑、打印机、会议设备、办公家具、实验室仪器……每一样东西买回来的时候登记一次然后它就散落江湖了。到年底盘点财务拿着Excel表一个个对结果永远是那几句这台笔记本谁拿走了那个投影仪在几楼去年报废的三台机器怎么还在账上很多人会问ERP系统不是有固定资产模块吗是大公司有钱上SAP、用友、金蝶一套下来几十万甚至上百万。但中小型公司、事业单位、学校、医院科室根本没有这个预算也不一定有人力去维护那么重的系统。Excel是免费的但Excel是死的——资产在流动表格是静态的这就是所有对不上账的根源。我做的这套Python Flask Django 微信小程序的固定资产管理系统一开始的思路就很明确不要重要轻不要贵要够用。用微信小程序做终端员工打开微信就能扫码、查资产、提领用申请后端用Python生态Flask负责轻量API接口Django负责管理后台和报表数据库用MySQL或者SQLite都行。整套系统开发成本低、部署简单、使用门槛几乎为零。这篇文章我会把整个系统的设计思路、核心功能实现、微信小程序端的对接细节、以及部署上线过程中的实用经验一次性讲清楚。适合正在被固定资产管理困扰的行政、财务、IT运维人员也适合想练手做一套前后端分离 微信小程序完整项目的Python开发者。2. 技术选型Flask与Django如何分工以及小程序端的接入方案2.1 为什么同时用Flask和Django而不是只选一个很多初学者会纠结说好的Python后端框架二选一你怎么两个都用这不是炫技是业务需求推着走的。这套系统里用户面对的是两块完全不同的界面。员工用微信小程序操作路径很短扫码、查看资产信息、提交领用申请。这种场景下接口要轻、要快、要简单用Flask最合适。Flask的灵活性强路由写起来干净SQLAlchemy做ORM也顺手一台普通云服务器跑几十个并发接口毫无压力。管理端则完全相反。行政人员需要录入资产、审批申请、导出报表、管理部门人员、查看盘点差异界面复杂、逻辑嵌套多。如果用Flask硬写这些页面工作量会翻好几倍。Django自带Admin后台和完整的ORM、表单、权限体系特别是Django Admin稍微配置一下就能生成一个像模像样的管理界面输入、筛选、分页、导出都是现成的。所以我的方案是Django管后台管理端Flask管小程序API端两者共用同一个MySQL数据库。可能有朋友会问直接用一个Django写全部不行吗当然行但Flask API和Django Admin混在一个项目里用蓝图和app隔离不是不行只是耦合度高后来改起来麻烦。拆成两个服务各自独立部署反而清爽。实际上这就是微服务思想的雏形——按业务边界拆服务而不是按技术偏好选框架。2.2 微信小程序端的技术定位前端选择微信小程序是综合考虑了开发成本和使用便利之后的决定。现在几乎没有员工愿意为了查一件资产再装一个独立APP但微信人人都有。小程序的主要优势是不用安装扫一扫/搜索即用分享到企业微信群就能打开微信登录免密通过wx.login获取code后端换取openid自动识别员工身份原生扫码APIwx.scanCode非常成熟扫码识别资产编号的交互体验几乎完美版本迭代快小程序后台支持一键发布不需要走应用商店审核当然小程序也有限制比如请求域名必须配置HTTPS白名单、部分API需要用户授权、审核对类目有要求。对于企业内部闭环系统如果小程序不面向公众开放审核也没有那么严主要是域名校验这一关要处理好后面部署章节我会细说。这里需要提一下uniapp。最近几年uniapp很火一套代码编译到微信小程序、H5、App多端。如果你未来有做App或者H5的打算可以一开始就用uniapp。我这套系统当时为了快速上线和简化调试直接用原生微信小程序写的结构简单、依赖少实测开发效率也不低。二者各有取舍不是非此即彼的选择。3. 数据库设计资产台账的字段到底怎么定才能兼顾统计和追溯3.1 资产主表的设计思路数据库是整个系统的地基。固定资产管理的本质就是回答四个问题有什么、在哪儿、谁在用、什么状态。所以资产主表asset的字段设计必须围绕这四个问题展开。我最终的资产表核心字段如下字段名类型说明asset_novarchar(32)资产编号唯一索引系统自动生成namevarchar(128)资产名称categoryvarchar(32)资产分类电脑、办公设备、家具、仪器等specificationvarchar(255)规格型号department_idint归属部门外键custodian_idint当前使用人可为空locationvarchar(128)存放位置statustinyint状态1在用 2闲置 3维修 4报废purchase_datedate购入日期original_valuedecimal(10,2)原值current_valuedecimal(10,2)当前净值折旧后qrcode_urlvarchar(255)二维码/条形码图片路径remarkvarchar(255)备注这里有几个很容易踩坑的地方。第一资产编号不要用自增ID。自增ID一旦资产报废删除编号就断了财务对账会很痛苦。我是用拼音分类缩写 年月 流水号生成的比如DN-2025-0001代表2025年第1台电脑。第二状态字段一定用数字枚举不要直接存中文否则后面做统计时SQL写得要疯。第三原值和净值必须分开存净值可以定时通过折旧任务计算但不能每次查询时实时算否则报表接口会很慢。为了确保唯一性和可追溯性资产表的asset_no建立了唯一索引同时保留created_at和updated_at时间戳这样即便出了数据异常也能通过审计日志排查问题。3.2 领用、流转与审批记录表固定资产不可能一直躺在仓库里日常最多的是领用和归还、调拨和报修这些操作如果只在asset表上改一个字段回头看的时候什么痕迹都没有。所以我还设计了一张资产流水表(asset_log)和一张审批表(asset_apply)。资产流水表记录每个资产从入库开始的每一次状态变化字段名说明id自增主键asset_id关联资产operate_type1入库 2领用 3归还 4调拨 5维修 6报废operator_id操作人target_user_id目标使用人领用/调拨时有效old_status操作前状态new_status操作后状态remark操作备注created_at操作时间领用审批表则用于保存员工提交申请、管理员审核的完整链路。员工在小程序端提交领用申请后端写入asset_apply表状态为pending管理员在Django后台审批通过后系统自动变更资产状态并写入asset_log同时在小程序端通过订阅消息或刷新机制告知员工结果。整个流程实现了权责可追溯年底审计时把这两张表导出来就是一份完整台账。4. 核心业务逻辑实现入库、领用、折旧与盘点4.1 资产入库与二维码生成新采购的资产到货后行政人员要做的第一件事是在管理后台录入系统自动生成资产编号和对应的二维码。二维码我用的qrcode这个Python库一行代码生成图片然后上传到OSS或本地存储同时把路径存进asset表。这样员工拿到实物小程序扫码就能看到完整的资产档案。import qrcode import os def generate_qrcode(asset_no): # 生成包含资产编号的二维码图片 qr qrcode.QRCode(version1, error_correctionqrcode.constants.ERROR_CORRECT_M) qr.add_data(asset_no) qr.make(fitTrue) img qr.make_image(fill_colorblack, back_colorwhite) img_path fmedia/qrcodes/{asset_no}.png img.save(img_path) return img_path二维码内容不要放太多字段只需放一个asset_no小程序扫码后拿到编号再去请求后端详细接口这样出错的概率最低也方便后期系统迁移。很多新手会想把详情都编码进去结果二维码密到扫不出来这是实操中最容易犯的错。资产入库时还需要校验有没有重复编号、分类是否合法、原值是否为合理数字。这些校验在后端做一遍前端做一遍不能只依赖前端。4.2 折旧计算不同资产类别要用不同规则固定资产管理避不开折旧。财务要求核算净值但不同资产的折旧年限不同电脑一般是3-5年办公家具是5年房屋是20年以上。如果统一用一种算法财务那边直接不认账。我的做法是在分类表里加一个depreciation_years字段然后写一个定时任务Django的manage.py命令通过crontab每天执行按平均年限法逐日计算净值from datetime import date def calc_depreciation(asset): years asset.category.depreciation_years if not years or years 0: return asset.original_value months_used (date.today() - asset.purchase_date).days / 30.44 if months_used years * 12: return 0 monthly_depreciation asset.original_value / (years * 12) return round(asset.original_value - monthly_depreciation * months_used, 2)注意折旧归零后仍在用的情况要标记为已折旧完毕但仍在用这类资产在统计报表里要单独归类不能直接当成报废资产。我的方案是在折旧任务里把净值小于等于0的资产状态改为折旧完毕代码层面叫status5和报废分开。4.3 盘点流程从对账靠肉眼到扫码即核盘点一直是固定资产管理里最痛苦的环节。以前是拿着纸质清单挨个房间看编号在不在然后回来对着Excel改又慢又容易出错。现在我的方案是管理员在后台发起盘点任务生成一个盘点计划盘点的范围、部门、时间然后拿手机到现场用小程序逐件扫码。每扫一件资产后端就记录一条盘点明细并更新资产表上的last_inventory_date字段。盘点结束后系统自动比对账上有但没扫到的资产标记为盘亏扫到了但账上没有的资产标记为盘盈。最后把这些差异清单导出成Excel交给财务和行政确认。这种扫码盘点的效率比人工对账高得多。一栋五层办公楼的全部资产原来三个人对一天的账现在一个人拿着手机两个小时就能扫完而且不会看错编号。小程序端做扫码盘点时要注意摄像头权限申请的时机必须在用户点击扫一扫按钮之后再调用wx.scanCode不能在页面加载时就申请否则审核容易被拒。5. 微信小程序端开发的关键细节登录态、请求封装与扫码处理5.1 微信登录与后端会话绑定小程序端最基础也是最容易被忽视的是登录态。wx.login获取到临时code后需要发给后端后端拿着code调用微信的接口换取openid。openid是一个用户在小程序里的唯一标识我们就用它来识别员工身份。# Flask端 import requests app.route(/api/auth/login, methods[POST]) def login(): code request.json.get(code) appid current_app.config[WX_APPID] secret current_app.config[WX_SECRET] resp requests.get( https://api.weixin.qq.com/sns/jscode2session, params{appid: appid, secret: secret, js_code: code, grant_type: authorization_code} ).json() openid resp.get(openid) if not openid: return jsonify({code: 1, msg: 登录失败}) # 查库确认员工身份没有则自动注册 user User.query.filter_by(openidopenid).first() if not user: user User(openidopenid, roleemployee) db.session.add(user) db.session.commit() token jwt_encode({user_id: user.id}) return jsonify({code: 0, data: {token: token, user: user.to_dict()}})登录成功后后端签发一个JWT token小程序端把token存进Storage后续所有请求的header里都带上。这里要提醒一点JWT的过期时间建议设短一些比如2小时配合refresh_token或者到期后重新静默登录否则安全隐患很突出。5.2 请求封装统一处理loading和错误码小程序原生的wx.request每次都要写一堆配置如果每个页面都复制粘贴后患无穷。我的建议是抽一个api.js模块统一封装请求。// utils/request.js const BASE_URL https://your-api-domain.com; function request(method, path, data) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data || {}, header: { content-type: application/json, Authorization: Bearer wx.getStorageSync(token), }, success(res) { if (res.statusCode 200) { if (res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(res.data); } } else if (res.statusCode 401) { // token过期重新登录 wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); } else { wx.showToast({ title: 服务器开小差了, icon: none }); reject(res); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { get: (path, data) request(GET, path, data), post: (path, data) request(POST, path, data), };这里有个小细节所有接口的返回格式统一约定为{code, msg, data}code为0表示成功。这样前端处理逻辑非常统一不会因为某个接口返回结构不同而写一堆特判。5.3 扫码识别与资产详情展示小程序扫资产二维码的流程是这样的用户在首页点击扫码调用wx.scanCode拿到result就是那个asset_no带着编号去请求/api/asset/detail拿到资产详细信息然后渲染到页面上。scanAsset() { wx.scanCode({ onlyFromCamera: true, success: (res) { const assetNo res.result; this.fetchAssetDetail(assetNo); } }); }拿到资产详情后页面可以展示基本信息、当前状态、使用人、最近流水。如果资产是闲置状态员工可以直接在详情页上发起领用申请填写用途说明、预计使用时长如果资产状态是在用则展示当前使用人信息方便跨部门协调。如果想要更高级一点还可以在小程序里展示资产的维修历史和折旧曲线不过这是后话第一版可以先不加。6. 从开发到上线的完整链路环境搭建、前后端联调与部署避坑6.1 开发环境的搭建这套系统的开发环境其实很常规Python 3.10虚拟环境用venv或conda数据库本地随意项目结构分成flask_server/和django_admin/两个目录小程序端单独一个目录miniprogram/。Flask端需要安装的依赖主要是flask、flask-sqlalchemy、flask-cors、pyjwt、requests、qrcode、pymysql。第一次搭环境时可以把依赖写进requirements.txt换机器或换服务器时直接pip install -r requirements.txt别一句句敲。Django端则用django-admin startproject创建项目然后创建asset_admin这个app。Django Admin自带的后台虽然不那么花哨但胜在省事在admin.py里把模型注册进去分分钟就有增删改查。再配置好django-cors-headers允许小程序域名跨域访问。这里特别提醒一下CORS配置。小程序端请求Flask接口时虽然微信小程序本身不受浏览器同源策略限制但在开发工具里调试、或者后续做H5版本时跨域问题一定会冒出来。Flask用flask-corsDjango用django-cors-headers都要提前配好。两个服务的白名单注意区分管理端后台给内部人员用可以只允许内网IP小程序API端要面向公网但CORS里也可以只放行你实际使用的域名不要图省事配成*。6.2 生产环境部署两个后端服务如何共用一个域名生产部署是我这次要重点分享的环节。很多人做完项目本地跑得好好的一上线就到处报错。核心问题通常集中在域名校验、HTTPS证书、两个服务如何统一对外。小程序端的request合法域名必须是HTTPS而且不能带路径。所以我的方案是用Nginx反向代理一个域名对外按路径分流到两个后端服务https://your-domain.com/api/转到Flask服务端口5000https://your-domain.com/admin/转到Django服务端口8000。Nginx配置核心段大致如下server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/ssl/your-domain.pem; ssl_certificate_key /etc/nginx/ssl/your-domain.key; location /api/ { proxy_pass http://127.0.0.1:5000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /admin/ { proxy_pass http://127.0.0.1:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配好Nginx后小程序后台的request合法域名只填https://your-domain.com这一个Flask和Django的接口都走这个域名省去了管理多个域名的麻烦。还有一点如果Flask代码里返回的图片URL是相对路径比如/media/qrcodes/xxx.png前端拿到后也要拼上域名前缀才能访问。我建议后端返回完整URL省得小程序端每处都去拼接。6.3 进程守护与日志部署完成后两个后端服务不能直接裸跑python app.py万一进程挂了没人管第二天整个系统就瘫了。我用supervisor来守护进程配置起来非常简单[program:flask_api] command/home/ubuntu/venv/bin/python /home/ubuntu/flask_server/app.py directory/home/ubuntu/flask_server autostarttrue autorestarttrue stderr_logfile/var/log/flask_api.err.log stdout_logfile/var/log/flask_api.out.log [program:django_admin] command/home/ubuntu/venv/bin/python /home/ubuntu/django_admin/manage.py runserver 0.0.0.0:8000 directory/home/ubuntu/django_admin autostarttrue autorestarttrue stderr_logfile/var/log/django_admin.err.log stdout_logfile/var/log/django_admin.out.log生产环境如果追求更高性能可以用gunicorn替代runserver启动DjangoFlask端也可以用gunicorn -w 4 -b 0.0.0.0:5000 app:app。公司内部系统并发量不大gunicornsupervisor完全够了没必要上Docker化除非你后续要频繁弹性扩缩容。7. 项目上线后的数据表现与真实反馈系统上线几个月后效果比预想的好。资产盘点效率直接翻了好几倍。以前行政部每次盘点要停下手头工作专门折腾两三天现在一个下午基本就搞定而且盘亏漏项明显减少。员工找资产也不用再到处问人小程序里搜一下就知道在哪个部门、谁在用、当前什么状态。年终财务审计要的数据报表Django后台一键导出Excel再也不用从各种Excel文件里手动汇总。当然也有一些当初没考虑到的细节暴露出来了。一个比较典型的问题是有一些老旧资产根本没有二维码标签补打标签这个工作要做一轮扫尾。另外员工离职的时候如果行政忘在小程序里做资产归还操作这个资产就还会挂在他名下所以我在员工模块里加了一个离职盘点提醒功能只要账号被停用系统自动列出该员工名下所有未归还资产。项目成型后我把它整理成了开源代码结构包括Flask API端、Django管理端和微信小程序端三个目录后续有类似需求的朋友可以直接在此基础上二次开发。固定资产管理系统这种项目看起来不起眼但真正把里面的数据模型、业务流程理清楚是很锻炼人的。它没有什么高深算法却要求你对业务两个字有足够敬畏——字段多一个少一个流程多一步少一步业务部门用起来的感觉完全不一样。最后分享一个我个人的实操体会做这类管理系统前期沟通一定比写代码时间更长。我第一版的时候只顾着按自己的想象设计字段和流程结果给行政部试用时对方提了一堆需求什么新增供应商信息、维修要关联费用金额、部门要按层级管理前后改了四轮才勉强满意。后来我学乖了动手之前先拉着使用方开一小时的会把所有流程从头到尾过一遍把对方心里的账本摸清楚后面开发就顺畅得多。代码写得再漂亮不好用就是零。