去年参与了一个新农村自建房改造管理系统的开发项目,这个系统主要解决农村地区自建房审批流程繁琐、改造进度不透明、资源对接困难等问题。在实际开发过程中,我们发现传统纸质审批方式存在诸多痛点:农户需要多次往返村镇部门,审批进度无法实时查询,建材采购渠道分散导致成本不可控。
系统需要实现的核心功能包括:
经过对比Django、FastAPI等框架,最终选择Flask主要基于以下考虑:
实际项目结构如下:
code复制project/
├── app/
│ ├── auth/ # 认证模块
│ ├── application/ # 建房申请模块
│ ├── approval/ # 审批流程模块
│ ├── static/ # 静态资源
│ └── templates/ # Jinja2模板
├── config.py # 配置文件
└── run.py # 启动文件
对比了SQLite和MySQL后,选择MySQL的原因:
关键数据表设计:
python复制class ConstructionApplication(db.Model):
id = db.Column(db.Integer, primary_key=True)
applicant_id = db.Column(db.Integer, db.ForeignKey('user.id'))
house_type = db.Column(db.String(50)) # 房屋类型
area = db.Column(db.Float) # 建筑面积
budget = db.Column(db.Float) # 预算金额
status = db.Column(db.String(20)) # 审批状态
create_time = db.Column(db.DateTime, default=datetime.utcnow)
审批流程设计为"村级初审→镇级终审"两级机制,通过状态模式实现:
python复制class ApplicationStatus:
def __init__(self, application):
self.application = application
def approve(self, user):
raise NotImplementedError
def reject(self, user):
raise NotImplementedError
class VillagePendingStatus(ApplicationStatus):
def approve(self, user):
if user.role == 'village_admin':
self.application.status = 'town_pending'
db.session.commit()
def reject(self, user):
if user.role == 'village_admin':
self.application.status = 'rejected'
db.session.commit()
关键点:状态变更必须保证事务完整性,使用Flask-SQLAlchemy的db.session.commit()确保数据一致性
农户需要上传房产证明、设计图纸等文件,安全处理要点:
实现代码片段:
python复制def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in {'pdf', 'jpg', 'png'}
@app.route('/upload', methods=['POST'])
@login_required
def upload_file():
if 'file' not in request.files:
flash('未选择文件')
return redirect(request.url)
file = request.files['file']
if file.filename == '':
flash('未选择文件')
return redirect(request.url)
if file and allowed_file(file.filename):
user_dir = os.path.join(current_app.config['UPLOAD_FOLDER'], str(current_user.id))
os.makedirs(user_dir, exist_ok=True)
filename = secure_filename(file.filename)
file.save(os.path.join(user_dir, filename))
return redirect(url_for('application_detail'))
选择ECharts的原因:
后端数据接口示例:
python复制@app.route('/api/approval_stats')
@login_required
def approval_stats():
data = db.session.query(
func.date_format(ConstructionApplication.create_time, '%Y-%m'),
func.count(ConstructionApplication.id),
func.sum(ConstructionApplication.budget)
).group_by(
func.date_format(ConstructionApplication.create_time, '%Y-%m')
).all()
return jsonify({
'months': [d[0] for d in data],
'counts': [d[1] for d in data],
'budgets': [float(d[2]) for d in data]
})
前端调用方式:
javascript复制fetch('/api/approval_stats')
.then(response => response.json())
.then(data => {
const chart = echarts.init(document.getElementById('chart'));
chart.setOption({
xAxis: { data: data.months },
series: [
{ data: data.counts, type: 'bar' },
{ data: data.budgets, type: 'line', yAxisIndex: 1 }
]
});
});
推荐部署方案:
典型Nginx配置:
nginx复制server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static {
alias /path/to/your/static/files;
expires 30d;
}
}
数据库查询优化:
缓存策略:
python复制from flask_caching import Cache
cache = Cache(config={'CACHE_TYPE': 'RedisCache',
'CACHE_REDIS_URL': 'redis://localhost:6379/0'})
@app.route('/expensive_report')
@cache.cached(timeout=3600)
def generate_report():
# 耗时计算逻辑
return render_template('report.html')
初期遇到多个文件同时上传时出现冲突,解决方案:
原设计只有单向审批流,实际需要支持"打回修改"场景:
python复制class ApprovalLog(db.Model):
id = db.Column(db.Integer, primary_key=True)
application_id = db.Column(db.Integer, db.ForeignKey('construction_application.id'))
action = db.Column(db.String(20)) # approve/reject/return
comment = db.Column(db.Text)
operator_id = db.Column(db.Integer, db.ForeignKey('user.id'))
operate_time = db.Column(db.DateTime, default=datetime.utcnow)
后期扩展的微信端功能要点:
python复制from flask_jwt_extended import JWTManager, jwt_required, create_access_token
jwt = JWTManager(app)
@app.route('/api/login', methods=['POST'])
def login():
username = request.json.get('username')
password = request.json.get('password')
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
return jsonify(access_token=create_access_token(identity=user.id))
return jsonify({"msg": "Bad credentials"}), 401
使用高德地图API实现房屋位置标注:
javascript复制const map = new AMap.Map('map-container', {
zoom: 13,
center: [116.397428, 39.90923]
});
fetch('/api/house_locations')
.then(res => res.json())
.then(data => {
data.forEach(item => {
new AMap.Marker({
position: [item.lng, item.lat],
map: map,
title: item.address
});
});
});
这个项目从技术选型到最终上线历时3个月,最大的体会是:在政府类信息化项目中,业务流程的准确理解比技术实现更重要。我们花了近一个月时间与各层级用户沟通,才理清完整的审批流程规则。Flask的灵活性在这个过程中发挥了很大价值,可以快速调整架构适应需求变化。