zoukankan      html  css  js  c++  java
  • Online Judge(OJ)搭建——3、MVC架构

    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>

    MyBatis的可以处理基本类型,但有些类型需要自定义转换,就需要 MyBatis 提供的 BaseTypeHandler 进行转换。首先编写 BaseTypeHandler(见下),之后在 MyBatis 配置文件中注册(见上)。

    package per.piers.onlineJudge.handler;
    
    import org.apache.ibatis.type.BaseTypeHandler;
    import org.apache.ibatis.type.JdbcType;
    import per.piers.onlineJudge.model.Sex;
    
    import java.sql.CallableStatement;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    public class SexTypeHandler extends BaseTypeHandler<Sex> {
    
        @Override
        public void setNonNullParameter(PreparedStatement preparedStatement, int i, Sex sex, JdbcType jdbcType) throws SQLException {
            preparedStatement.setInt(i, sex.getId());
        }
    
        @Override
        public Sex getNullableResult(ResultSet resultSet, String s) throws SQLException {
            int sex = resultSet.getInt(s);
            if (resultSet.wasNull()) {
                return null;
            } else {
                return Sex.getSexType(sex);
            }
        }
    
        @Override
        public Sex getNullableResult(ResultSet resultSet, int i) throws SQLException {
            int sex = resultSet.getInt(i);
            if (resultSet.wasNull()) {
                return null;
            } else {
                return Sex.getSexType(i);
            }
        }
    
        @Override
        public Sex getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
            int sex = callableStatement.getInt(i);
            if (callableStatement.wasNull()) {
                return null;
            } else {
                return Sex.getSexType(i);
            }
        }
    
    }

    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="[dw]+" 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
    ", 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();
        }
    
    }
  • 相关阅读:
    poj 3243 Clever Y(BabyStep GiantStep)
    poj 2417 Discrete Logging
    poj 3481 Double Queue
    hdu 4046 Panda
    hdu 2896 病毒侵袭
    poj 1442 Black Box
    hdu 2815 Mod Tree
    hdu 3065 病毒侵袭持续中
    hdu 1576 A/B
    所有控件
  • 原文地址:https://www.cnblogs.com/Piers/p/6942430.html
Copyright © 2011-2022 走看看