码迷,mamicode.com
首页 > Web开发 > 详细

Online Judge(OJ)搭建——3、MVC架构

时间:2017-06-05 19:58:48      阅读:227      评论:0      收藏:0      [点我收藏+]

标签:sem   lstat   trim   粘合剂   finally   names   trigger   default   htm   

Model

技术分享

Model 层主要包含数据的类,这些数据一般是现实中的实体,所以,Model 层中类的定义常常和数据库 DDL 中的 create 语句类似。

通常数据库的表和类是一对一的关系,但是有的时候由于需求变化或者方便起见,Model 层的类有时不和数据库中表相互对应。比如面向对象之组合属性,在 Java 中可以用一个类组合另一个类,表示测试信息、对应多组测试用例的组合,(正常情况下,应该是一张表而不是两张表),而数据库是用两张表存储数据,利用外键关系表示测试信息、对应多组测试用例的关系。 

技术分享

技术分享

技术分享

由于数据繁多,为了简化对象的映射,不使用JDBC,而采用持久化框架 MyBatis。

MyBatis 首先需要配置数据源:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- property from external resources -->
    <properties resource="config/mybatis/applications.properties"/>
    <!-- settings -->
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--
        <setting name="lazyLoadingEnabled" value="true" />
        <setting name="multipleResultSetsEnabled" value="true" />
        <setting name="useColumnLabel" value="true" />
        <setting name="useGeneratedKeys" value="false" />
        <setting name="autoMappingBehavior" value="PARTIAL" />
        <setting name="defaultExecutorType" value="SIMPLE" />
        <setting name="defaultStatementTimeout" value="25000" />
        <setting name="safeRowBoundsEnabled" value="false" />
        <setting name="mapUnderscoreToCamelCase" value="false" />
        <setting name="localCacheScope" value="SESSION" />
        <setting name="jdbcTypeForNull" value="OTHER" />
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode ,toString" />
        -->
    </settings>
    <!-- type aliases(full class name -> simple class name) -->
    <typeAliases>
        <!--
        <typeAlias alias="Student" type="com.mybatis3.domain.Student" />
        -->
        <package name="per.piers.onlineJudge.model"/>
    </typeAliases>
    <!-- type handlers -->
    <typeHandlers>
        <typeHandler handler="per.piers.onlineJudge.handler.SexTypeHandler" javaType="per.piers.onlineJudge.model.Sex"
                     jdbcType="BOOLEAN"/>
    </typeHandlers>
    <!-- environment -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        <environment id="production">
            <transactionManager type="MANAGED"/>
            <dataSource type="JNDI">
                <property name="data_source" value="java:comp/jdbc/mybatis"/>
            </dataSource>
        </environment>
    </environments>
    <!-- mappers location -->
    <mappers>
        <!--
        <mapper url="file:///D:/mybatisdemo/app/mappers/TutorMapper.xml" />
        <mapper class="com.mybatis3.mappers.TutorMapper" />
        -->
        <mapper resource="mapper/UserMapper.xml"/>
        <mapper resource="mapper/QuestionMapper.xml"/>
        <mapper resource="mapper/CategoryMapper.xml"/>
        <mapper resource="mapper/TestDataMapper.xml"/>
        <mapper resource="mapper/TestInfoMapper.xml"/>
        <mapper resource="mapper/ScoreMapper.xml"/>
        <mapper resource="mapper/AdvisorMapper.xml"/>
    </mappers>
</configuration>

之后创建工厂对象,再用它创建数据访问对象(DataAccessObject,DAO):

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws IOException {
        ClassLoader classLoader = RootConfig.class.getClassLoader();
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(classLoader.getResourceAsStream("config/mybatis/mybatis-config.xml"));
        return sqlSessionFactory;
    }

    @Autowired
    @Bean
    public DataAccessObject dataAccessObject(SqlSessionFactory sqlSessionFactory) {
        return new DataAccessObject(sqlSessionFactory);
    }

DAO 对象负责数据访问。首先以 Mapper 接口的方式定义访问数据库的函数,之后在 XML 文件中实现该函数,并提供具体实现(SQL 语句细节)。这样做的好处一方面是防止命名错误,传统 MyBatis 方式是根据函数名执行相关 SQL 语句的,不用接口书写很容易出错;另一方面有助于设计(接口)和实现分离,降低耦合性。

public interface UserMapper {

    public int insertUser(@Param("user")User user);

    public int updateUser(@Param("user")User user);

    public int deleteUser(@Param("user") User user);

    public User selectUser(@Param("user") User user);

}
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="per.piers.onlineJudge.mapper.UserMapper">
    <insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="user.id">
        INSERT INTO users (email, password, name, sex, role, enabled)
        VALUES (#{user.email}, #{user.password}, #{user.name}, #{user.sex}, #{user.role}, #{user.enabled})
    </insert>
    <update id="updateUser" parameterType="User">
        UPDATE users
        <trim prefix="SET" suffixOverrides=",">
            <if test="user.email != null">email = #{user.email},</if>
            <if test="user.password != null">password = #{user.password},</if>
            <if test="user.name != null">name = #{user.name},</if>
            <if test="user.sex != null">sex = #{user.sex},</if>
            <if test="user.role != null">role = #{user.role},</if>
            <if test="user.enabled != null">enabled = #{user.enabled},</if>
        </trim>
        WHERE id = #{user.id}
    </update>
    <delete id="deleteUser" parameterType="User">
        DELETE FROM users
        WHERE id = #{user.id}
    </delete>
    <select id="selectUser" parameterType="User" resultMap="userResult">
        SELECT *
        FROM users
        <if test="user != null">
            <where>
                <if test="user.email != null">email = #{user.email}</if>
            </where>
        </if>
    </select>
    <resultMap id="userResult" type="User">
        <id column="id" property="id"/>
        <result column="email" property="email"/>
        <result column="password" property="password"/>
        <result column="name" property="name"/>
        <result column="sex" property="sex"/>
        <result column="enabled" property="enabled"/>
        <result column="role" property="role"/>
    </resultMap>
</mapper>

View

技术分享技术分享

view 层主要是界面(页面)。这里主要是 JSP 页面,因为需要动态展示一些内容。其中还运用了 JavaScript 技术和 AJAX 技术,JavaScript 主要用作页面输入域校验,AJAX 主要用于异步提交需要更新的内容。

<%@page contentType="text/html; charset=UTF-8" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="sf" %>
<%@taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
<!DOCTYPE html>
<html>
<head>
    <%@include file="../common/header.jspf" %>
    <title>注册</title>
</head>
<body>
<%@include file="../common/navbar.jspf" %>
<div class="container">
    <div class="page-header">
        <h1>注册</h1>
    </div>
    <div class="form-signin" oninput="satisfySubmit()">
        <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
        <div class="row">
            <div class="col-md-8">
                <div class="input-group">
                    <span class="input-group-addon" id="basic-addon1">邮箱*</span>
                    <input type="email" id="email" name="email" value="${email}" class="form-control" placeholder="邮箱长度不超过40个字符"
                           aria-describedby="basic-addon1" maxlength="40" required oninput="showEmailInputSuggestion()" disabled>
                </div>
            </div>
            <div class="col-md-4">
                <p id="emailError" class="text-danger"></p>
            </div>
        </div>
        <br/>
        <div class="row">
            <div class="col-md-8">
                <div class="input-group">
                    <span class="input-group-addon" id="basic-addon2">密码*</span>
                    <input type="password" id="password" name="password" class="form-control"
                           placeholder="密码长度不少于6个字符,不多于20个字符,只能包括数字和字母"
                           aria-describedby="basic-addon1" minlength="6" maxlength="20" required
                           oninput="showAllPasswordSuggestion()">
                </div>
            </div>
            <div class="col-md-4">
                <p id="passwordError" class="text-danger"></p>
            </div>
        </div>
        <br/>
        <div class="row">
            <div class="col-md-8">
                <div class="input-group">
                    <span class="input-group-addon" id="basic-addon3">确认密码*</span>
                    <input type="password" id="repassword" name="repassword" class="form-control" placeholder="再次输入密码"
                           aria-describedby="basic-addon1" minlength="6" maxlength="20" pattern="[\d\w]+" required
                           oninput="showAllPasswordSuggestion()">
                </div>
            </div>
            <div class="col-md-4">
                <p id="repasswordError" class="text-danger"></p>
            </div>
        </div>
        <br/>
        <div class="row">
            <div class="col-md-8">
                <div class="input-group">
                    <span class="input-group-addon" id="basic-addon4">姓名</span>
                    <input type="text" id="name" name="name" class="form-control"
                           placeholder="你的姓名,可以很酷,不过最多只能有20个字符(中英皆可)"
                           aria-describedby="basic-addon1" maxlength="20">
                </div>
            </div>
            <div class="col-md-4">
                <p></p>
            </div>
        </div>
        <br/>
        <div class="row">
            <div class="col-md-8">
                <div class="input-group">
                    <span class="input-group-addon" id="basic-addon5">性别</span>
                    <div class="form-control">
                        <div class="radio-inline">
                            <label>
                                <input type="radio" name="sex" id="optionsRadios1" value="MALE"></label>
                        </div>
                        <div class="radio-inline">
                            <label>
                                <input type="radio" name="sex" id="optionsRadios2" value="FEMALE"></label>
                        </div>
                    </div>
                </div>
            </div>
            <div class="col-md-4">
                <p></p>
            </div>
        </div>
        <br/>
        <input type="hidden" id="enabled" name="enabled" value="true"/>
        <input type="hidden" id="role" name="role" value="user"/>
        <input id="submit" type="button" value="不能注册,请检查相关项填写是否正确" class="btn btn-danger" disabled
               onclick="registerUser()">
        <br>
        <p id="success"></p>
    </div>
</div>
<%@include file="../common/footer.jspf" %>
<script src="${pageContext.request.contextPath}/js/user/user.js"></script>
<script src="${pageContext.request.contextPath}/js/user/register.js"></script>
</body>
</html>
function satisfySubmit() {
    var submit = document.getElementById("submit");
    if (isEmailValid() && isPasswordValid() && isRepasswordValid()) {
        submit.setAttribute("type", "submit");
        submit.setAttribute("value", "提交注册");
        submit.setAttribute("class", "btn btn-success");
        submit.removeAttribute("disabled")
    } else {
        submit.setAttribute("type", "button");
        submit.setAttribute("value", "不能注册,请检查相关项填写是否正确");
        submit.setAttribute("class", "btn btn-danger");
        submit.setAttribute("disabled", "");
    }
}
function registerUser() {
    xmlhttp = new XMLHttpRequest();
    if (xmlhttp != null) {
        var email = document.getElementById("email").value;
        var password = document.getElementById("password").value;
        var name = document.getElementById("name").value;
        var sexes = document.getElementsByName("sex");
        var sex;
        for (var i = 0; i < sexes.length; i++) {
            if (sexes[i].checked) sex = sexes[i].value.toUpperCase();
        }
        var enabled = document.getElementById("enabled").value;
        var role = document.getElementById("role").value;
        var csrf = document.getElementsByName("_csrf")[0].value;
        xmlhttp.onreadystatechange = stateChange;
        xmlhttp.open("POST", window.location.pathname, true);
        xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xmlhttp.send("&email=" + email + "&password=" + password + "&name=" + name + "&sex=" + sex + "&enabled=" + enabled + "&role=" + role + "&_csrf=" + csrf);
    }
}

function stateChange() {
    var success = document.getElementById("success");
    if (xmlhttp.readyState == 4) { // 4 = "loaded"
        if (xmlhttp.status == 200) { // 200 = "OK"
            success.setAttribute("class", "text-success");
            success.innerHTML = "注册成功";
            alert("注册成功,点击确定进行登录")
            window.location.href = getContextPath() + "/user/information"
        } else if (xmlhttp.status == 409) {
            success.setAttribute("class", "text-danger");
            success.innerHTML = "用户邮箱已存在";
        } else if (xmlhttp.status == 500) {
            success.setAttribute("class", "text-danger");
            success.innerHTML = "服务器可能出现了问题";
        }
    }
}

Controller

Controller 是 Model 和 View 的粘合剂。Model 的增删改查的操作由 Controller 负责,View 的显示由 Controller 负责。Controller 实质上是 Java EE 的 Servlet。

在 Spring MVC 中,首先配置相关 DispatcherServlet,之后再编写 Controller。

package per.piers.onlineJudge.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("per.piers.onlineJudge.controller")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/jsp/");
        resolver.setExposeContextBeansAsAttributes(true);
        resolver.setSuffix(".jsp");
        return resolver;
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

}
package per.piers.onlineJudge.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import per.piers.onlineJudge.model.User;
import per.piers.onlineJudge.util.DataAccessObject;
import per.piers.onlineJudge.util.ExcelUtil;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;

@Controller
@RequestMapping("/testManager")
public class UserImportController {

    private DataAccessObject dao;

    @Autowired
    public UserImportController(DataAccessObject dao) {
        this.dao = dao;
    }

    @RequestMapping("/import/user")
    public String importUser() {
        return "import/user";
    }

    @RequestMapping(path = "/import/user", method = RequestMethod.POST)
    public String importResult(@RequestPart("usersFile") MultipartFile usersFile, HttpServletRequest request, Model model) throws IOException {
        String path = request.getSession().getServletContext().getRealPath("/") + "/tmp/" + usersFile.getOriginalFilename();
        File file = new File(path);
        file.getParentFile().mkdirs();
        file.createNewFile();
        usersFile.transferTo(file);
        ExcelUtil excelUtil = new ExcelUtil();
        HashSet<String> emails = excelUtil.readColumns(file, "用户邮箱");
        try {
            if (emails == null) {
                model.addAttribute("failure", "读取列用户邮箱出错,可能是没有列用户邮箱");
            } else {
                User selectUser = new User();
                selectUser.setEmail(((UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
                Integer uidAdmin = dao.selectUser(selectUser).getId();
                HashMap<String, String> status = dao.importUser(emails, uidAdmin);
                StringBuilder builder = new StringBuilder();
                for (String key : status.keySet()) {
                    builder.append(String.format("%s,%s\n", key, status.get(key)));
                }
                model.addAttribute("success", builder.toString());
            }
        } catch (Exception e) {
            model.addAttribute("failure", e.getMessage());
        } finally {
            return "import/result";
        }
    }
}

Spring 技术:这里的 Controller 是由 Spring MVC 提供的。本系统还设计了一个 ErrorController,用户异常处理的 Controller,返回错误的页面。

package per.piers.onlineJudge.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class ErrorController {

    @RequestMapping(path = "/error/401")
    public String error401() {
        return "error/401";
    }

    @RequestMapping(path = "/error/403")
    public String error403() {
        return "error/403";
    }

    @RequestMapping(path = "/error/404")
    public String error404() {
        return "error/404";
    }

    @RequestMapping(path = "/error/409")
    public String error409() {
        return "error/409";
    }

    @RequestMapping(path = "/error/500")
    public String error500() {
        return "error/500";
    }

}

Spring 技术:异常的捕获和处理是由标有 @ControllerAdvice 注解的类处理,需要定义捕获的异常类型、如何处理(返回的 HTTP 状态码,返回的页面)。

package per.piers.onlineJudge.controller;

import org.apache.ibatis.exceptions.PersistenceException;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import per.piers.onlineJudge.Exception.CRUDException;
import per.piers.onlineJudge.Exception.ExistenceException;
import per.piers.onlineJudge.Exception.ExpiryException;

import javax.mail.MessagingException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(BadCredentialsException.class)
    public String badCredentialsExceptionHandler(Exception e, Model model) {
        model.addAttribute("exception", getExceptionMessage(e));
        return "error/401";
    }

    @ResponseStatus(value = HttpStatus.FORBIDDEN)
    @ExceptionHandler(value = {ExpiryException.class, IllegalArgumentException.class})
    public String illegalStateExceptionHandler(Exception e, Model model) {
        model.addAttribute("exception", getExceptionMessage(e));
        return "error/403";
    }

    @ResponseStatus(value = HttpStatus.CONFLICT)
    @ExceptionHandler(ExistenceException.class)
    public String existenceExceptionHandler(Exception e, Model model) {
        model.addAttribute("exception", getExceptionMessage(e));
        return "error/409";
    }

    @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = {CRUDException.class, IOException.class, IllegalStateException.class, MessagingException.class, PersistenceException.class})
    public String CRUDExceptionHandler(Exception e, Model model) {
        model.addAttribute("exception", getExceptionMessage(e));
        return "error/500";
    }

    public String getExceptionMessage(Exception e) {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        e.printStackTrace(printWriter);
        return stringWriter.toString();
    }

}

Online Judge(OJ)搭建——3、MVC架构

标签:sem   lstat   trim   粘合剂   finally   names   trigger   default   htm   

原文地址:http://www.cnblogs.com/Piers/p/6942430.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!