心理测评全系统设计与代码实现

在这里插入图片描述

心理评测不同于考试系统, 答案是没有对错的,且都是选择题, 是根据被评测者所选的选项综合加权得分后,测试出被评测者的性格、爱好、职业等更近哪一种类型。
这里分别从数据库设计、后台代码实现、和小程序开发, 和评测报告的生成介绍

– 系统为本人从0开始搭建,持续同步更新, 欢迎各位大侠拍砖。

一、数据库设计篇

数据库— Mysql,表1:测评主表、表2:问题表、表3:回答选项表、表4:测试结果表、其它:报告、资源等

  1. 测评主表
CREATE TABLE `t_test_m` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(20) DEFAULT NULL COMMENT '问卷编码',
  `title` varchar(50) DEFAULT NULL COMMENT '问卷标题',
  `sub_title` varchar(50) DEFAULT NULL COMMENT '副标题',
  `sub_cn` varchar(30) DEFAULT NULL COMMENT '简称中文',
  `sub_en` varchar(30) DEFAULT NULL COMMENT '简称英文',
  `direction` text COMMENT '问卷说明(以MD读法保存)',
  `isfree` int(11) DEFAULT '1' COMMENT '是否免费(1免费,其它为收费)',
  `money` int(11) DEFAULT NULL COMMENT '收费金额,单位分',
  `thkMsg` text COMMENT '结束感谢语言',
  `ctime` datetime DEFAULT NULL COMMENT '添加时间',
  `startTime` datetime DEFAULT NULL COMMENT '开始时间',
  `endTime` datetime DEFAULT NULL,
  `editor` varchar(30) DEFAULT NULL COMMENT '添加人',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  1. 问题表
CREATE TABLE `t_test_q` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(50) DEFAULT NULL COMMENT '问题编号',
  `qnum` varchar(100) NOT NULL COMMENT '题号',
  `title` varchar(100) NOT NULL COMMENT '测试标题',
  `direction` varchar(200) DEFAULT NULL COMMENT '描述',
  `m_code` varchar(50) DEFAULT NULL COMMENT '评测主表ID',
  `qtype` int(11) DEFAULT '1' COMMENT '该问题题型,1表示单选,9表示多选,0表示填空',
  `ctime` datetime DEFAULT NULL COMMENT '创建时间',
  `order` int(11) DEFAULT NULL COMMENT '排序号',
  `isdel` int(11) DEFAULT '0' COMMENT '是否删除 0否,1删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.问题选项表

CREATE TABLE `t_test_a` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '选项ID',
  `q_code` varchar(50) NOT NULL COMMENT '问题Code',
  `direction` varchar(200) DEFAULT NULL COMMENT '选项描述',
  `score` int(11) DEFAULT NULL COMMENT '得分,用于计算',
  `iscorrect` int(11) DEFAULT '0' COMMENT '是否为正确的选项,0表示该回答无准答案,-1表示错误,1表示正确',
  `order` int(11) DEFAULT NULL COMMENT '排序',
  `isdel` int(11) DEFAULT NULL COMMENT '是否删除,0否,1是',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  1. 评测结果
CREATE TABLE `t_test_r` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userid` varchar(50) DEFAULT NULL COMMENT '评测用户ID',
  `username` varchar(50) DEFAULT NULL COMMENT '用户名',
  `orgname` varchar(100) DEFAULT NULL COMMENT '所属组织',
  `mcode` varchar(50) DEFAULT NULL COMMENT '测试主表Code',
  `source` varchar(30) DEFAULT NULL COMMENT '评测来源:如小程序、官网、线下二维码',
  `resutJson` text COMMENT '评测结果, 以Json方式保存',
  `startTime` datetime DEFAULT NULL COMMENT '测试开始时间',
  `endTime` datetime DEFAULT NULL COMMENT '测试结束时间',
  `costTime` int(11) DEFAULT NULL COMMENT '耗时,单位秒。(注意耗时≠结束时间-开始时间)',
  `reportUrl` varchar(300) DEFAULT NULL COMMENT '评测报告地址:报告生成的地址, 或则预留好的文件地址',
  `reportText` text COMMENT '报告内容:以MarkDown格式保存',
  `ispay` int(11) DEFAULT '0' COMMENT '是否已支付,0未支付,1已支付',
  `amount` int(11) DEFAULT NULL COMMENT '实际支付金额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

到此测试评系统数据库设计的主要几张表都已完成了, 接下来以“PSTR心理压力测试”案例导入数据, 完成SQL初始化数据。

主表:

INSERT INTO t_test_m (
	CODE,
	title,
	sub_title,
	sub_cn,
	sub_en,
	direction,
	isfree,
	money,
	ctime,
	testminute,
	startTime,
	endTime,
	editor
)
VALUES
	('PSTR',
		'心理压力测试(PSTR专业版)',
		'PSTR',
		'心理压力测试',
		'PSTR',
		'压力既可以是你生活的助手也可以变成生活的阻碍。PSTR心理压力测试,帮助你了解自己面临的心理压力程度。'
		,1
		,0
		,now()
		,10
		,now()
		,'2050-01-01',
		' admin '
		)
  1. 问题表初始化数据
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR1','1','____我受背痛之苦','','PSTR','1','2022-3-17','1','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR26','26','____我喝酒','','PSTR','1','2022-3-17','26','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR2','2','____我的睡眠不定且睡不安稳','','PSTR','1','2022-3-17','2','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR27','27','____我很自觉(自己需要、情绪、动机、计划、目标等)','','PSTR','1','2022-3-17','27','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR3','3','____我有头痛','','PSTR','1','2022-3-17','3','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR28','28','____我觉得己像四分五裂','','PSTR','1','2022-3-17','28','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR4','4','____我颚部疼痛','','PSTR','1','2022-3-17','4','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR29','29','____我的眼睛又酸又累','','PSTR','1','2022-3-17','29','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR5','5','____若须等候,我会不安','','PSTR','1','2022-3-17','5','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR30','30','____我的腿,或脚抽筋','','PSTR','1','2022-3-17','30','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR6','6','____我的後颈感到疼痛','','PSTR','1','2022-3-17','6','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR31','31','____我的心跳快速','','PSTR','1','2022-3-17','31','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR7','7','____我比多数人更神经紧张','','PSTR','1','2022-3-17','7','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR32','32','____我怕结识人','','PSTR','1','2022-3-17','32','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR8','8','____我很难入睡','','PSTR','1','2022-3-17','8','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR33','33','____我手脚冰冷','','PSTR','1','2022-3-17','33','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR9','9','____我的头感到紧或痛','','PSTR','1','2022-3-17','9','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR34','34','____我患便秘','','PSTR','1','2022-3-17','34','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR10','10','____我的胃有毛病','','PSTR','1','2022-3-17','10','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR35','35','____我发现自己很容易哭','','PSTR','1','2022-3-17','35','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR11','11','____我对自己没有信心','','PSTR','1','2022-3-17','11','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR36','36','____我消化不良','','PSTR','1','2022-3-17','36','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR12','12','____我对自己说话','','PSTR','1','2022-3-17','12','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR37','37','____我咬指甲','','PSTR','1','2022-3-17','37','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR13','13','____我忧虑财务问题','','PSTR','1','2022-3-17','13','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR38','38','____我耳中有嗡嗡声','','PSTR','1','2022-3-17','38','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR14','14','____与人见面时,我会窘怯','','PSTR','1','2022-3-17','14','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR39','39','____我小便频密','','PSTR','1','2022-3-17','39','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR15','15','____我怕发生可怕的事','','PSTR','1','2022-3-17','15','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR40','40','____我有胃溃疡的毛病','','PSTR','1','2022-3-17','40','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR16','16','____白天我觉得累','','PSTR','1','2022-3-17','16','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR41','41','____我有皮肤方面的毛病','','PSTR','1','2022-3-17','41','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR17','17','____下午我感到喉咙痛,','','PSTR','1','2022-3-17','17','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR42','42','____我未经医师指示使用各种但并非由於染上感冒药物','','PSTR','1','2022-3-17','42','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR18','18','____我心情不安、无法静坐','','PSTR','1','2022-3-17','18','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR43','43','____我的咽喉很紧','','PSTR','1','2022-3-17','43','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR19','19','____我感到非常口乾','','PSTR','1','2022-3-17','19','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR44','44','____我有十二指肠溃疡的毛病','','PSTR','1','2022-3-17','44','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR20','20','____我有心脏毛病','','PSTR','1','2022-3-17','20','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR45','45','____我担心我的工作','','PSTR','1','2022-3-17','45','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR21','21','____我觉得自己不是很有用','','PSTR','1','2022-3-17','21','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR46','46','____我口腔溃烂','','PSTR','1','2022-3-17','46','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR22','22','____我吸烟','','PSTR','1','2022-3-17','22','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR47','47','____我为琐事忧虑','','PSTR','1','2022-3-17','47','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR23','23','____我肚子不舒服','','PSTR','1','2022-3-17','23','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR48','48','____我呼吸浅促','','PSTR','1','2022-3-17','48','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR24','24','____我觉得不快乐','','PSTR','1','2022-3-17','24','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR49','49','____我觉得胸部紧迫','','PSTR','1','2022-3-17','49','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR25','25','____我流汗','','PSTR','1','2022-3-17','25','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR50','50','____我发现难做决定','','PSTR','1','2022-3-17','50','0');

  1. 问题选项表
insert into t_test_a(q_code,direction,score,iscorrect,`order`,isdel) values('PSTR1','从未',0,0,5,0);
insert into t_test_a(q_code,direction,score,iscorrect,`order`,isdel) values('PSTR1','从未',0,0,5,0);
……
-- 省掉248条

频率: 总是–4 经常–3 有时–2 很少–1 从未–0

问题选项表脚本没有撸完, 全部一样5个答案, 50道题共添加250条记录, 会不会有键盘侠认为我很250呢, 可以5条记录搞定的, 非要来一个250; 可能真是多余的, 但为了测评系统的通用性, 多存几条记录对数据库来说很easy。

二、后台设计与实现(.Net Core)

1、环境准备

  • Startup.cs
public void ConfigureServices(IServiceCollection services)
        { 
            //注册编码提供程序
            System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
            //装载配置信息
            object obj =  Newtonsoft.Json.JsonConvert.DeserializeObject(System.IO.File.ReadAllText("appsettings.json"));
            services.AddDbContextPool<DataBase>(options =>
            options.UseMySql(Configuration.GetConnectionString("MySqlConnection")));
            //注入Controller
            services.AddControllers();   
            //增加Swagger
            services.AddSwaggerGen(c =>
            {
                c.SwaggerDoc("v1", new OpenApiInfo
                {
                    Version = "v1.1.0",
                    Title = "评测系统",
                    Description = "",

                });

            });
        }
  • appsettion
  "ConnectionStrings": {
    "MySqlConnection": "database=luqub;server=IP;port=3306;uid=root;password=abc0755..;CharSet=utf8;sslMode=None;Pooling=true"
  }

2、基类实现

  • 接下来写一个BaseController.cs 基类
    这里可以实现认证拦截, 日报记录等操作,IsDevelopment 用于在本地调试时不验证token
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; 
using System;
using System.Collections.Generic; 

namespace yourspace
{
    /// <summary>
    /// 控制器基类
    /// </summary>
    [FilterException]
    [Produces("application/json")]
    [Route("api/[controller]")]
    [ApiController]
    public class BaseController : Controller
    {
        /// <summary>
        /// 服务器部署环境redis配置
        /// </summary>
        public static string redisSeting = "IP:6380,password=***,poolsize=50,ssl=false,writeBuffer=10240";
        private static bool isInit = false;
        public static bool IsDevelopment = false;

        /// <summary>
        /// 用户Token,用于关键接口校验
        /// </summary>
        public string UserToken = "";

        /// <summary>
        /// Redis操作类
        /// </summary>
        public static CSRedis.CSRedisClient csredis = null;

        /// <summary>
        /// 执行控制器(Start)重写,拦截未登录认证的用户请求, Http中结尾带"_"则不需要登录访问
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            context.HttpContext.Items["StartTime"] = DateTime.Now;
            //获取请求头
            if (!IsDevelopment && !context.HttpContext.Request.Path.Value.EndsWith('_'))
            {
                if (!isInit)
                {
                    csredis = new CSRedis.CSRedisClient(redisSeting);
                    RedisHelper.Initialization(csredis);
                    //初始化 RedisHelper
                    isInit = true;
                }
                if (!context.HttpContext.Request.Headers.ContainsKey("authorization"))
                {
                    context.Result = Redirect("/api/Common/NoToken_");
                }
                else
                {
                    if (!RedisHelper.Exists(context.HttpContext.Request.Headers["authorization"][0]))
                    {
                        context.Result = Redirect("/api/Common/AuthError_");
                    }
                    else
                    {
                        UserToken = context.HttpContext.Request.Headers["authorization"][0];
                    }

                }
            }
        }
        ///
        /// <summary>        
        /// 执行控制器(End)重写,错误日志记录到文件、数据库,可选, 如请求量过大, 可修改为异步操作
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuted(ActionExecutedContext context)
        {
            string log = "";
            if (context.Exception != null)
            {
                log = "[Error] " + DateTime.Now;
                Common.WriteErrorLogWP("请求出错", context.Exception);
            }
            else
            {
                log = "[Info] " + DateTime.Now;
            }
            if (context.HttpContext.Items["StartTime"]!=null)
            {
                log += " times:" + DateTime.Now.Subtract((DateTime)context.HttpContext.Items["StartTime"]).TotalMilliseconds;
            }
            log += " UserID:" + UserID + " Token:"+UserToken ;
            log += " Url:"+ context.HttpContext.Request.Path.Value;
            log += " Parms:" + context.HttpContext.Request.QueryString.Value;
            if (context.Exception != null)
            {
                log += "错误详情:"+ context.Exception;
            }
            Common.WriteLog(log);

        }

        private string _userid = null;

        /// <summary>
        /// 用户ID,前提需要有Token
        /// </summary>
        public string UserID
        {
            get
            {

                if (UserToken == "" && IsDevelopment)
                {
                    _userid = "a36925400ddc872";
                }

                if (_userid == null)
                {

                    if (!isInit)
                    {
                        csredis = new CSRedis.CSRedisClient(redisSeting);
                        RedisHelper.Initialization(csredis);
                        //初始化 RedisHelper
                        isInit = true;
                    }
                    string id = "";
                    Dictionary<string, object> dic = new Dictionary<string, object>();
                    Newtonsoft.Json.Linq.JObject obj = Newtonsoft.Json.JsonConvert.DeserializeObject(RedisHelper.Get(UserToken)) as Newtonsoft.Json.Linq.JObject;
                    Common.JObjectToDictionary(dic, obj);
                    if (dic.Count > 0)
                    {
                        id = dic["USER_ID"].ToString();
                    }
                    _userid = id;
                    return id;
                }
                else
                {
                    return _userid;
                }
            }
        }
        private Dictionary<string, object> _userDic = null;

        public Dictionary<string, object> UserDic
        {
            get
            {
                string userStrs = @"{
                  'USER_ID': 'a36925400ddc872',
                  'USER_OPENID': 'oB9sa40jdG5atBx3PjpE6c',
                  'USER_WX_NAME': '评测用户1',
                  'USER_TYPE': '9',
                  'USER_STATUS': '1',
                  'USER_ISPAY': '0',
                  'TOKEN': 'b3cad2-525400ddc872'
                }";
                if (IsDevelopment)
                {
                    _userid = "a36925400ddc872";

                    Dictionary<string, object> dic = new Dictionary<string, object>();
                    Newtonsoft.Json.Linq.JObject obj = Newtonsoft.Json.JsonConvert.DeserializeObject(userStrs) as Newtonsoft.Json.Linq.JObject;
                    Common.JObjectToDictionary(dic, obj);
                    _userDic = dic;

                }

                if (_userDic == null)
                {
                    if (!isInit)
                    {
                        csredis = new CSRedis.CSRedisClient(redisSeting);
                        RedisHelper.Initialization(csredis);
                        //初始化 RedisHelper
                        isInit = true;
                    }
                    Dictionary<string, object> dic = new Dictionary<string, object>();
                    Newtonsoft.Json.Linq.JObject obj = Newtonsoft.Json.JsonConvert.DeserializeObject(RedisHelper.Get(UserToken)) as Newtonsoft.Json.Linq.JObject;
                    Common.JObjectToDictionary(dic, obj);
                    _userDic = dic;

                }
                return _userDic;
            }
            set
            {
                //在Redis中更新用户
                if (!isInit)
                {
                    csredis = new CSRedis.CSRedisClient(redisSeting);
                    RedisHelper.Initialization(csredis);
                    //初始化 RedisHelper
                    isInit = true;
                }
                RedisHelper.Set(UserToken, value);
                _userDic = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public override JsonResult Json(object data)
        {
            object retunData = data;
            if (data is ResultData || data is RowsData)
            {
                return base.Json(data);
            }
            else
            {
                ResultData rd = new ResultData() { code = "1", data = retunData, msg = (data is string) ? data.ToString() : "" };
                return base.Json(rd);
            }
        }

         
    }

}

3、API服务

  • API实现,创建一个MynatureController类
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Data;

namespace yourspace
{
    public class MynatureController : BaseController
    {
        /// <summary>
        /// 评测试卷
        /// </summary>
        /// <param name="mcode"></param>
        /// <returns></returns>
        [HttpGet("getPaper")]
        public JsonResult GetPaper(string mcode)
        {
            //直接上SQL
            string sql = @"SELECT
				id,
				`code`,
				title,
				sub_title,
				sub_cn,
				sub_en,
				direction,
				isfree,
				money,
				thkMsg,
				testminute,
				autosubmit
			FROM
				t_test_m
			WHERE
				`code` = @mcode
			AND now() BETWEEN startTime
			AND endTime";
            DataTable dt = Common.GetDataTable(sql, new Dictionary<string, object>() {
               {
                    "mcode", mcode
               }
           });
            return Json(dt.ToJson());
        }
        /// <summary>
        /// 返回问题列表
        /// </summary>
        /// <param name="mcode">问卷编号</param>
        /// <param name="random">是否随机出题</param>
        /// <param name="qnum">取出问题号</param>
        /// <returns></returns>
        [HttpGet("getQuestions")]
        public JsonResult GetQuestions(string mcode, bool random = true, int qnum = 0)
        {
            string sql;
            //随机出题
            if (random) 
            {
                sql = @"SELECT * FROM
	                    (
		                    SELECT
			                    m_code,
			                    q.title,
			                    q.id,
			                    q.qnum,
			                    a.direction,
			                    a.score,
			                    a.`order` AS aorder,
			                    a.id AS aid
		                    FROM
			                    t_test_q q
		                    INNER JOIN t_test_a a ON q.`code` = a.q_code
	                    ) tb
                    WHERE
	                    m_code = @mcode
                    ORDER BY
	                    aorder,
	                    rand() ";
            }
            //顺序取, 当取题号为0时标示全部
            else if (qnum == 0) 
            {
                sql = @"SELECT
					*
				FROM
					(
						SELECT
							m_code,
							q.title,
							q.id,
							q.qnum,
							a.direction,
							a.score,
							a.`order` AS aorder,
							a.id AS aid
						FROM
							t_test_q q
						INNER JOIN t_test_a a ON q.`code` = a.q_code
					) tb
				WHERE
					m_code = @mcode
				ORDER BY
				qnum,aorder;";
            }
            //指定题号
            else
            {
                sql = @"SELECT
					*
				FROM
					(
						SELECT
							m_code,
							q.title,
							q.id,
							q.qnum,
							a.direction,
							a.score,
							a.`order` AS aorder,
							a.id AS aid
						FROM
							t_test_q q
						INNER JOIN t_test_a a ON q.`code` = a.q_code
					) tb
				WHERE
					m_code = @mcode and qnum=@qnum
				ORDER BY aorder";
            }     
            DataTable dt = Common.GetDataTable(sql, new Dictionary<string, object>() {
               {"mcode", mcode},{"qnum",qnum}
           });

            #region 组装返回Json格式

            Dictionary<string, object> pairs = new Dictionary<string, object>();
            foreach (DataRow dataRow in dt.Rows)
            {
                string key = "q" + dataRow["qnum"].ToString();

                if (pairs.ContainsKey(key))
                {
                    Dictionary<string, object> qdic = pairs[key] as Dictionary<string, object>;
                    List<Dictionary<string, string>> items = qdic["items"] as List<Dictionary<string, string>>;
                    Dictionary<string, string> cdic = new Dictionary<string, string>();
                    cdic.Add("direction", dataRow["direction"].ToString());
                    cdic.Add("score", dataRow["score"].ToString());
                    cdic.Add("order", dataRow["aorder"].ToString());
                    cdic.Add("id", dataRow["aid"].ToString());
                    items.Add(cdic);
                }
                else
                {
                    List<Dictionary<string, string>> items = new List<Dictionary<string, string>>();
                    Dictionary<string, string> cdic = new Dictionary<string, string>();
                    cdic.Add("direction", dataRow["direction"].ToString());
                    cdic.Add("score", dataRow["score"].ToString());
                    cdic.Add("order", dataRow["aorder"].ToString());
                    cdic.Add("id", dataRow["aid"].ToString());
                    items.Add(cdic);
                    Dictionary<string, object> qdic = new Dictionary<string, object>();
                    qdic.Add("m_code", dataRow["m_code"].ToString());
                    qdic.Add("title", dataRow["title"].ToString());
                    qdic.Add("id", dataRow["id"].ToString());
                    qdic.Add("qnum", dataRow["qnum"].ToString());
                    qdic.Add("items", items);
                    pairs.Add(key, qdic);
                }
            }
            List<object> returList = new List<object>();
            foreach (var item in pairs)
            {
                returList.Add(item.Value);
            }

            #endregion
            return Json(returList);
        }
    }
}

4、预览截图

在这里插入图片描述

到此数据准备与API接口已基本完成, 后面开始着手小程序开发了

三、前端开发与实现(wx小程序)

1、准备

为了布局灵活,维护简单,在wx小程序中的富文本我们将采用MarkDown格式来呈现。 这里我使用的是wemark, 可以去gitee下载
使用方式:

1、下载并拷贝wemark目录到小程序根目录
2、在页面的配置文件中引用wemark组件
{
	"usingComponents": {
		"wemark": "../wemark/wemark"
	}
}
3、WXML中使用:<wemark md="{{md}}" link highlight type="wemark"></wemark>

2、Coding wx

.json

{
  "usingComponents": {
    "wemark": "../../component/wemark/wemark"
  },
  "navigationBarTitleText": "性格评测系统"
}

.wxml

<!--
  这样页面可以清爽到爆, 把布局交给md
  把评测介绍, 评测, 结果都在这一页展示吧!
-->
<view class="page">
  <view class="mtitle">{{info.TITLE}}</view>
  <view class="info" wx:if="{{step==1}}">
    <wemark md="{{info.DIRECTION}}" link highlight type="wemark"></wemark>
    <view class="testing">
      <button bindtap="onbtntesting" type="primary">请尊重你内心里第一选择…</button>
    </view>
  </view>
  <view class="doing" wx:if="{{step==2}}">

    <view class="answers">
      <!-- 显示进度条 -->
      <view class="progress">
        <view class="progress-bar" style="width:{{progress}}%"></view>
      </view>
      <view class="item">
        <view>
          <view class="page-section">
            <view class="page-section-title atitle"> {{currentIndex}}、{{currentAnswer.title}}</view>
            <view class="weui-cells weui-cells_after-title">
              <radio-group>
                <label bindtap="onradioChange" data-sel="{{c}}" class="weui-cell weui-check__label" wx:for-item="c"
                  wx:for="{{currentAnswer.items}}" wx:key="{{c.score}}">
                  <view class="weui-cell__hd">
                    <radio disabled="{{isFinish}}" value="{{c.score}}" />
                  </view>
                  <view class="weui-cell__bd">{{c.direction}}</view>
                </label>
              </radio-group>
            </view>
          </view>
        </view>
      </view>
    </view>
    <view class="next">
      <button wx:if="{{isFinish}}" bindtap="onbtnNext">提交</button>
    </view>
  </view>
  <view class="result" wx:if="{{step==3}}">
  <!-- 一般选择类测评前面两步可通用, 但报告展示会各不相同故分开展示 -->
    <view wx:if="{{myNature.mcode=='PSTR'}}">
      你的得分是{{myNature.my}},人群平均分是{{myNature.avg}}。 
      <wemark md="{{myNature.result}}" link highlight type="wemark"></wemark>
    </view>
  </view>
  <view class="footInfo">学子宏图</view>
</view>

.js

Page({
  /**
   * 页面的初始数据
   */
  data: {
    info: {},
    mcode: 'PSTR',  //试卷号
    answerList: [],  //选项列表
    currentAnswer: null,
    currentIndex: 1, // 当前题号
    totalIndex: 1,  //总计题号
    step: 1,  //执行到的步骤,1=info,2=doing,3=result
    progress: 1,
    isFinish: false,
    selList: [],
    myNature: {}
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

    if (options.code != null) {
      this.setData({
        mcode: options.code,
      })
    }
    //页面资源加载完成后执行
    app.loginComplete(() => {
      let that = this;
      //装载试卷基本信息
      app.wxRequest("api/Mynature/getPaper", "GET", {
        mcode: that.data.mcode
      }, (res) => {
        that.setData({
          info: res.data
        })
      })
    })
  },
  //开始评测
  onbtntesting: function () {
    let that = this;
    //加载问题与回答
    app.wxRequest("api/Mynature/getQuestions", "GET", {
      mcode: that.data.mcode,
      random: true
    }, (res) => { 
      let current = res.data[0];
      that.setData(
        {
          answerList:res.data,
          currentAnswer: current,
          totalIndex: res.data.length
        }
      )
    })

    this.setData({
      step: 2
    })
  }
  ,
  onradioChange: function (e) {

    if (this.data.isFinish) return;

    //保存上一题选中
    let sel = e.currentTarget.dataset.sel;
    this.data.selList.push({
      qid: this.data.currentAnswer.qnum,
      score: sel.score
    });
    this.setData({
      selList: this.data.selList
    })

    if (this.data.currentIndex < this.data.totalIndex) {
      //特意增加一个加载遮罩,避免用户快速盲点
      wx.showToast({
        title: "进入下一题…",
        icon: 'loading',
        mask: true,
        duration: 500
      });
      let current = this.data.answerList[this.data.currentIndex];
      this.setData({
        currentIndex: this.data.currentIndex + 1,
        currentAnswer: current,
        progress: this.data.currentIndex / this.data.totalIndex * 100,
        isFinish: false
      })
    }
    else {
      console.log('选中得分', this.data.selList)
      this.setData({
        isFinish: true,
        progress: 100
      })
    }
  },
  onbtnpre: function () {
    this.setData({
      step: 1,
      currentIndex: 1,
      isFinish: false,
      currentAnswer: this.data.answerList[0],
      totalIndex: this.data.answerList.length
    })
  },
  onbtnNext: function () {
    console.log(this.answerList);
    app.wxRequest("api/Mynature/postReport", "POST", { mcode: this.data.info.CODE, sel: JSON.stringify(this.data.selList), y: 2 }, (r) => {
      this.setData({
        myNature: r.data,
        step: 3
      })
    });

  }
}

3、上效果截图

介绍页
在这里插入图片描述
测试中
在这里插入图片描述

结下来就是最的报告部分了, 也是测评的核心,会涉及到各自算法。

四、评测报告

同样的数据库与后台服务逻辑实现多种不同的评测, 只需要调整展示评测报告页。

  <view class="result" wx:if="{{step==3}}">
   <!-- 测试1:PSTR规则与算法 -->
    <view wx:if="{{myNature.mcode=='PSTR'}}"> 
      你的得分是{{myNature.my}},人群平均分是{{myNature.avg}}。
      <wemark md="{{myNature.result}}" link highlight type="wemark"></wemark>
    </view>
    
   <!-- 测试2:霍兰德职业兴趣测试 -->
    <view wx:if="{{myNature.mcode=='HOLLAND'}}">
      <canvas canvas-id="canvas" class="canvas" style="width: 700rpx; height: 600rpx;background:#fff;;" />
      你的职业趋向为:{{myNature.my}}
      <wemark md="{{myNature.result}}" link highlight type="wemark"></wemark>
    </view>
  </view>

测试1:PSTR规则与算法

总是–4 经常–3 有时–2 很少–1 从未–0
98(93或以上) 这个分数表你确实正以极度的压力反应在伤害你自己的健康。你需要专业心理治疗师给予一些忠告, 他可以帮助你消减你对於压力器的知觉, 并帮助你改良生活的品质。
87(82-92)这个分数表示你正经历太多的压力, 这正在损害你的健康,并令你的人际关系发生问题。 你的行为会伤己, 也可能会影响其他人。 因此, 对你来说, 学习如何减除自己的压力反应是非常重要的。 你可能必须花许多时间做练习, 学习控制压力, 也可以寻求专业的帮助。
76(71-81)这个分数显示的压力程度中等, 可能正开始对健康不利。 你可以仔细反省自己对压力器如何作出反应, 并学习在压力器出现时, 控制自己的肌肉紧张, 以消除生理激活反应。 好老师对你有帮助,要不然就选用适合的肌肉松弛录音带。
65(60-70)这个分数指出你生活中的兴奋与压力量也许是相当适中的。 偶而会有一段时间压力太多, 但你也许有能力去享受压力, 并且很快回到平静的状态, 因此对你的健康并不会造成威胁。 做一些松弛的练习仍是有益的。
54(49-59)这个分数表示你能控制你自己的压力反应, 你是一个相当放松的人。 也许你对於所遇到的各种压力器, 并没有将它们解释为威胁, 所以你很容易与人相处, 可以毫无惧怕地胜任工作, 也没有失去自信。
43(38-48)这个分数表示你对所遭遇的压力器很不易为所动, 甚至是不当一回事, 好像并没有发生过一样。 这对你的健康不会有甚麼负面的影响, 但你的生活缺乏适度的兴奋, 因此趣味也就有限。
32(27-37)这个分数表示你的生活可能是相当沉闷的, 即使刺激或有趣的事情发生了, 你也很少作反应。 可能你必须参与更多的社会活动或娱乐活动, 以增加你的压力激活反应。
21(16-26)如果你的分数只落在这个范围内, 也许意味你在生活中所经历的压力经验不够, 或是你并没有正确地分析自己。 你最好更主动些, 在工作、 社交、 娱乐等活动上多寻求些刺激。 做松弛练习对没有甚麼用, 但找一些辅导也许会有帮助。

根据以上规则写一个C#的If判断即可。 测评完成, 欢迎拍砖,后续会有《霍兰德职业兴趣测试》、《九型人格》、《 DISC职业个性评估》、《MBTI职业性格评估》等, 有兴趣的我们可以交流

在这里插入图片描述

测试2:霍兰德职业兴趣测试

- 介紹页面

在这里插入图片描述

- 答题页

在这里插入图片描述

- 评测结果展示

在这里插入图片描述
雷达图代码见https://blog.csdn.net/daengwei/article/details/123791337

  • 37
    点赞
  • 108
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
目录 第一章 需求分析…………………………………………………………1 1.1 项目名称……………………………………………………………1 1.2 系统开发的背景……………………………………………………1 1.3 系统开发的现状……………………………………………………1 1.4 系统开发的目标……………………………………………………1 1.5 系统开发的可行性分析………………………………………………1 第二章 系统分析…………………………………………………………2 2.1 系统分析方法…………………………………………………………2 2.2 数据流程分析…………………………………………………………2 第三章 系统设计与实施…………………………………………………6 3.1 系统设计……………………………………………………………6 3.2 总体设计……………………………………………………………6 3.3 详细设计……………………………………………………………7 3.4 程序设计……………………………………………………………8 3.5 系统实施……………………………………………………………8 3.6 系统测试……………………………………………………………9 第四章 系统运行………………………………………………………11 4.1 系统运行…………………………………………………………11 4.2 结论………………………………………………………………11
在线考试系统是一种通过互联网进行考试的系统。它基于Java编程语言设计实现,具有以下功能和特点。 首先,在线考试系统具有用户管理功能。系统管理员可以创建、编辑和删除考生账号,为考生分配考试科目和考试时间。考生可以根据自己的需求选择考试科目和时间。 其次,系统具有试题管理功能。管理员可以上传试题,包括选择题、填空题和问答题。试题可以根据科目和难度级别进行分类,方便考生选择合适的考试内容。管理员还可以编辑和删除试题,并进行试题的随机抽取和组卷。 再次,系统具有考试功能。考生可以登录系统,在规定的时间内进行考试。系统会根据考生的选择生成试卷,考生需要在规定的时间内完成答题。考生可以随时交卷,系统会自动计算得分并将考试结果保存。 此外,系统还具有成绩管理功能。考试结束后,系统会自动生成考试成绩和排名,并将成绩保存在数据库中。考生可以查看自己的成绩和排名,系统管理员可以查看所有考生的成绩统计和分析,以便于评估考试质量。 在线考试系统实现代码主要包括前端和后端两部分。前端使用HTML、CSS和JavaScript等前端技术实现用户界面和交互功能。后端使用Java编程语言,搭建基于Spring框架的Web应用,通过Servlet和JSP等技术实现用户请求的处理和响应。 总之,基于Java的在线考试系统设计实现代码,能够方便地管理用户、试题和成绩,并提供稳定可靠的考试服务。这种系统可以应用于各种机构的考试和测评,提高效率和准确度,方便考生和管理员的操作。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值