3. 工单创建功能开发:前端表单设计、后端路由与数据入库

好,咱们今天来聊聊工单创建这个核心功能。说实话,这是整个系统里用户打交道最多的一个环节,也是我当年踩坑最多的地方。

工单创建,说白了就是让用户填一张表单,然后我们把它存到数据库里。听起来简单?嗯,但细节决定成败。我见过太多项目,表单设计得花里胡哨,结果用户填到一半就放弃了。或者后端验证没做好,数据库里一堆脏数据。

今天咱们就一步步来,把这件事做扎实。

3.1 前端表单设计:用户第一眼看到的东西

我个人习惯,设计表单前先想清楚用户要填什么。工单创建,核心字段就三个:标题描述优先级。别贪多,用户没耐心填十几个字段。

来看一个我常用的表单结构:

<!-- templates/create_ticket.html -->
<form method="POST" action="/tickets/create">
    <div class="form-group">
        <label for="title">工单标题 <span class="required">*</span></label>
        <input type="text" id="title" name="title" 
               placeholder="请简要描述问题,例如:无法登录系统"
               maxlength="100" required>
    </div>

    <div class="form-group">
        <label for="description">详细描述</label>
        <textarea id="description" name="description" 
                  rows="6" 
                  placeholder="请详细描述您遇到的问题,包括:
1. 出现问题的具体操作步骤
2. 期望的结果是什么
3. 实际出现的结果是什么"></textarea>
    </div>

    <div class="form-group">
        <label for="priority">优先级 <span class="required">*</span></label>
        <select id="priority" name="priority" required>
            <option value="">-- 请选择 --</option>
            <option value="low">低 - 不影响正常使用</option>
            <option value="medium" selected>中 - 有变通方案</option>
            <option value="high">高 - 功能受阻</option>
            <option value="urgent">紧急 - 系统不可用</option>
        </select>
    </div>

    <button type="submit" class="btn btn-primary">提交工单</button>
</form>
我的小技巧: 描述框里的 placeholder 我故意写了换行和编号。用户看到这个,就知道该写什么。我在项目中遇到过,很多用户不知道「详细描述」要写多详细,给个模板能省去大量沟通成本。

这里有个细节:优先级下拉框。我见过有人用数字 1、2、3、4 来表示优先级。你想想看,用户看到「请选择优先级:1」,他哪知道 1 是最高还是最低?所以一定要用文字描述,而且每个选项后面加一句说明。这样用户一眼就能看懂。

3.2 后端路由与视图函数:接收数据的第一道关卡

前端表单写好了,接下来就是后端接收。我用 Flask 框架来演示,因为轻量、直观。

# app.py
from flask import Flask, render_template, request, redirect, url_for, flash
from datetime import datetime

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'  # 实际项目中别写死

@app.route('/tickets/create', methods=['GET', 'POST'])
def create_ticket():
    if request.method == 'GET':
        # 显示表单页面
        return render_template('create_ticket.html')
    
    # POST 请求:处理表单提交
    title = request.form.get('title', '').strip()
    description = request.form.get('description', '').strip()
    priority = request.form.get('priority', '')
    
    # 这里先简单打印,后面再处理入库
    print(f"收到工单:标题={title}, 优先级={priority}")
    
    return redirect(url_for('create_ticket'))

注意看,我用了 request.form.get() 而不是 request.form['title']。为什么?因为 get() 方法在字段不存在时返回空字符串,不会报错。这是我在项目中吃过亏才养成的习惯——永远假设用户可能不按套路出牌。

3.3 数据验证:别让脏数据进数据库

嗯,这里要重点讲。数据验证是工单系统的生命线。我见过一个项目,因为没做验证,数据库里存了标题为空的工单,导致后续所有查询都报错。

我个人习惯,验证分两步:前端验证后端验证。前端验证是为了用户体验,后端验证是为了数据安全。记住:永远不要信任前端传来的数据

来看一个完整的验证逻辑:

from flask import Flask, render_template, request, redirect, url_for, flash

@app.route('/tickets/create', methods=['GET', 'POST'])
def create_ticket():
    if request.method == 'GET':
        return render_template('create_ticket.html')
    
    # 获取并清理数据
    title = request.form.get('title', '').strip()
    description = request.form.get('description', '').strip()
    priority = request.form.get('priority', '')
    
    # 验证数据
    errors = []
    
    # 标题验证
    if not title:
        errors.append('工单标题不能为空')
    elif len(title) > 100:
        errors.append('工单标题不能超过100个字符')
    
    # 描述验证(可选字段,但如果有值则检查长度)
    if description and len(description) > 2000:
        errors.append('工单描述不能超过2000个字符')
    
    # 优先级验证
    valid_priorities = ['low', 'medium', 'high', 'urgent']
    if priority not in valid_priorities:
        errors.append('请选择有效的优先级')
    
    # 如果有错误,返回表单并显示错误信息
    if errors:
        for error in errors:
            flash(error, 'error')
        return render_template('create_ticket.html', 
                             title=title, 
                             description=description, 
                             priority=priority)
    
    # 验证通过,准备入库
    # ...(下一步处理)
避坑指南: 我曾经犯过一个错误——只在前端用 JavaScript 做了验证,后端直接入库。结果有人用 Postman 直接发请求,绕过了前端验证,往数据库里塞了一堆乱码。从那以后,我坚持「后端验证是最后一道防线」的原则。

3.4 数据入库:把工单存起来

验证通过后,就该入库了。我用 SQLite 做演示,因为零配置,适合教学。实际生产环境换成 MySQL 或 PostgreSQL 就行,逻辑一样。

先建表:

-- schema.sql
CREATE TABLE IF NOT EXISTS tickets (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title VARCHAR(100) NOT NULL,
    description TEXT,
    priority VARCHAR(10) NOT NULL DEFAULT 'medium',
    status VARCHAR(20) NOT NULL DEFAULT 'open',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

然后写入库逻辑:

import sqlite3
from datetime import datetime

def save_ticket(title, description, priority):
    """保存工单到数据库"""
    conn = sqlite3.connect('tickets.db')
    cursor = conn.cursor()
    
    try:
        cursor.execute('''
            INSERT INTO tickets (title, description, priority, created_at, updated_at)
            VALUES (?, ?, ?, ?, ?)
        ''', (title, description, priority, datetime.now(), datetime.now()))
        
        conn.commit()
        ticket_id = cursor.lastrowid
        return ticket_id
    except Exception as e:
        conn.rollback()
        print(f"保存工单失败:{e}")
        return None
    finally:
        conn.close()

# 在视图函数中调用
@app.route('/tickets/create', methods=['POST'])
def create_ticket():
    # ...(前面的验证代码)
    
    # 验证通过,入库
    ticket_id = save_ticket(title, description, priority)
    
    if ticket_id:
        flash('工单创建成功!', 'success')
        return redirect(url_for('view_ticket', ticket_id=ticket_id))
    else:
        flash('工单创建失败,请稍后重试', 'error')
        return render_template('create_ticket.html', 
                             title=title, 
                             description=description, 
                             priority=priority)
关键点: 注意我用了参数化查询(? 占位符),而不是直接拼接字符串。这是防止 SQL 注入的基本操作。我见过有人偷懒用 f-string 拼接 SQL,结果被黑客注入了恶意代码。记住:永远不要拼接 SQL 字符串

3.5 完整流程串起来

好了,现在我们把所有环节串起来。整个流程是这样的:

  1. 用户访问 /tickets/create,看到表单
  2. 用户填写标题、描述、选择优先级,点击提交
  3. 前端 HTML5 验证(比如 required 属性)先做一轮检查
  4. 后端收到数据,做更严格的验证
  5. 验证通过,数据入库
  6. 跳转到工单详情页,告诉用户「创建成功」

这个流程看起来简单,但每一步都有坑。我建议你动手写一遍,把每个环节的边界情况都测试一下:

  • 标题为空会怎样?
  • 描述填了 3000 字会怎样?
  • 优先级传了个「abc」会怎样?
  • 数据库连接失败会怎样?

把这些情况都测一遍,你的工单创建功能就稳了。

我的经验: 刚开始做这个功能时,我总觉得「用户不会那么傻」。直到有一天,一个用户提交了标题为「.」的工单,我才意识到——用户的行为永远超出你的想象。所以,防御性编程不是过度设计,而是基本素养。

下一章,咱们聊聊工单列表展示和分页。到时候你会看到,今天入库的数据,明天怎么优雅地展示出来。