您的当前位置:首页>全部文章>文章详情

【Webman+MySQL答题系统四】题目管理模块:增删改查接口实战

果子发表于:2026-10-05 20:55:43浏览:1次TAG: #PHP #MySql #答题系统

本篇目标

实现题库管理:Question 模型 + 列表/新增/修改/删除四个接口,重点处理 JSON 选项字段与服务端校验。

第四篇封面

一、模型

app/model/Question.php:

<?php
namespace app\model;

use support\Model;

class Question extends Model
{
    protected $table = 'question';

    // 允许批量赋值
    protected $guarded = [];

    // options 存库是 JSON 字符串,取出自动转数组
    protected $casts = [
        'options' => 'array',
    ];
}

两个细节:表名单数,与 Illuminate 默认的复数猜测不一致,所以显式声明 $table;$guarded = [] 打开批量赋值,否则 ::create() 会抛 MassAssignmentException。

二、接口规划

方法路径说明
GET/api/question/list分页列表,可按 type 筛选
POST/api/question/add新增
POST/api/question/update修改
POST/api/question/delete删除(含关联清理)

题目管理接口图

三、控制器

app/controller/QuestionController.php:

<?php
namespace app\controller;

use support\Request;
use support\Db;
use app\model\Question;

class QuestionController
{
    public function list(Request $request)
    {
        $page  = max(1, (int)$request->get('page', 1));
        $limit = min(50, max(1, (int)$request->get('limit', 10)));
        $type  = (int)$request->get('type', 0);

        $query = Question::query();
        if ($type) {
            $query->where('type', $type);
        }
        $paginate = $query->orderBy('id', 'desc')
            ->paginate($limit, ['*'], 'page', $page);

        $items = array_map(function ($q) {
            return [
                'id'      => $q->id,
                'type'    => $q->type,
                'title'   => $q->title,
                'options' => $q->options,
                'answer'  => $q->answer,
                'score'   => $q->score,
            ];
        }, $paginate->items());

        return success(['total' => $paginate->total(), 'items' => $items]);
    }

    public function add(Request $request)
    {
        $type    = (int)$request->post('type', 1);
        $title   = trim((string)$request->post('title'));
        $options = $request->post('options');      // 数组: ['A'=>'内容', ...]
        $answer  = str_replace(' ', '', trim((string)$request->post('answer')));
        $score   = (float)$request->post('score', 2);

        if ($title === '') {
            return error('题干不能为空');
        }
        if (!is_array($options) || count($options) < 2) {
            return error('选项至少两个');
        }
        $keys = array_map('strval', array_keys($options));
        foreach (explode(',', $answer) as $k) {
            if ($k !== '' && !in_array($k, $keys, true)) {
                return error('答案必须在选项范围内');
            }
        }
        if ($answer === '') {
            return error('答案不能为空');
        }
        if ($type === 2 && strpos($answer, ',') === false) {
            return error('多选题答案至少两项');
        }

        $q = Question::create([
            'type'     => $type,
            'title'    => $title,
            'options'  => $options,
            'answer'   => $answer,
            'analysis' => trim((string)$request->post('analysis')),
            'score'    => $score,
        ]);
        return success(['id' => $q->id], '新增成功');
    }

    public function update(Request $request)
    {
        $id = (int)$request->post('id');
        $q  = Question::find($id);
        if (!$q) {
            return error('题目不存在', 404);
        }
        if ($title = trim((string)$request->post('title'))) {
            $q->title = $title;
        }
        if ($options = $request->post('options')) {
            $q->options = $options;
        }
        if ($answer = str_replace(' ', '', trim((string)$request->post('answer')))) {
            $q->answer = $answer;
        }
        if ($request->post('analysis') !== null) {
            $q->analysis = (string)$request->post('analysis');
        }
        if ($request->post('score') !== null) {
            $q->score = (float)$request->post('score');
        }
        $q->save();
        return success(['id' => $q->id], '修改成功');
    }

    public function delete(Request $request)
    {
        $id = (int)$request->post('id');
        $q  = Question::find($id);
        if (!$q) {
            return error('题目不存在', 404);
        }
        // 已被组卷引用的题先清理关联,放进一个事务
        Db::transaction(function () use ($q, $id) {
            Db::table('paper_question')->where('question_id', $id)->delete();
            $q->delete();
        });
        return success([], '删除成功');
    }
}

options 入库:$casts 声明后,给模型赋数组、save 时自动 json_encode,取出时自动 decode——业务代码里全程当数组用。

四、注册路由

use app\controller\QuestionController;

Route::group('/api/question', function () {
    Route::get('/list', [QuestionController::class, 'list']);
    Route::post('/add', [QuestionController::class, 'add']);
    Route::post('/update', [QuestionController::class, 'update']);
    Route::post('/delete', [QuestionController::class, 'delete']);
});

五、验证

curl -X POST http://127.0.0.1:8787/api/question/add \
  -d "type=1" -d "title=Webman 的内核是?" \
  -d "options[A]=Workerman" -d "options[B]=Swoole" \
  -d "answer=A" -d "score=2"

curl "http://127.0.0.1:8787/api/question/list?page=1&limit=10&type=1"

返回 {"code":0,...} 即成功。注意 PHP 的 form 数组语法 options[A]=... 正好能被 $request->post('options') 接成数组。

小结

题库模块完成:模型 casts 处理 JSON、四接口齐活、删除带事务清理关联。下一篇组卷,把题目拼成试卷。