【Project】原生JavaWeb工程 01 概述,搭建

一、环境准备:

操作系统:Windows7 或者 Windows10

IDE集成环境:IDEA 2018版本或者更高

数据库:MySQL 5版本或者更高

服务器:Tomcat 8版本或者更高

 

二、数据库的表设计:

# 年级表 - 班级表 - 课程表 - 章节表 - 题目表
# 暂不涉及RBAC
就只给一个用户表,设置简单的访问权限控制即可
# 所有表都具备主键
这里省略不谈了 [主键,非空,自增,无符号]
# 命名规范
建议是同一个模块内的数据表使用相同的前缀
# 例如:module1_tableA,module1_tableB
# 标识符的命名不要使用大小写区分,例如Oracle的数据库默认使用全大写标识

# 不建议使用数据库的外键关联强制约束
这么做看起来数据维护非常的准确,但是这对我们后段程序的编写是极其不利的

# 是否设置is_del属性?
我们希望数据提供给使用者访问时,不应该使用真正的删除,为保留数据,会使用这一字段来实现

# 接上面的is_del属性的软删除,我们是否还需要支持硬删除
【就我自己考虑而言,我认为这功能不会出现,但是可以给管理员使用,由管理员来抉择】

# 对于元数据的一些设置: 基于所有开发者共同遵守的不成文约定
整数取INT,字符串取VARCHAR,日期时间DATETIME 【其他的不是很常见,类型得看情况】
偶然看到一个博友的元数据范围喜欢以2的次幂来设置,这看起来很棒,所以我也这么做了
对主键的设置使用8,不要超过Int范围就好了
对字符串类型的范围一般来说使用64,如果是这种描述的文本内容,可以设置128,224。。。

1、年级表

年级名称,顺序编号, 如果需要更多描述,创建时间,修改时间,年级描述

t_grade

grade_id,
grade_name,
grade_order_id,
grade_is_del,

2、班级表

即每一个年级有多少个班级,和年级同理
一个年级具有若干个班级
但是一个班级只能对应所属的一个年级
1、班级所属的年级编号 2、班级名称 3、班级顺序编号 班级描述 创建时间 修改时间 t_class class_id,
class_grade_id, class_name, class_order_id,
class_is_del 注意班级的英文单词,和Java的保留字相冲突,可以使用clasz、clazz、clazs相替换

在这里的疑问?
虽然大学使用的是XXX大学XXX院校XXX系XXX级的XXX这样的
如果想我们之前的小中高中这样的,每一年就会升级,又如何考虑呢?
可以确定的是我们设置的年级应该是可以被确定的...先想到这里把

3、课程表

课程是提供给年级设计的,
即一个年级需要多个课程,
但是一个课程只能对应一个年级

t_course

course_id,
course_grade_id,
course_name,
course_order_id,
course_is_del

course_description,
course_gen_time,
course_alt_time

可以设想一下课程和班级的关系
似乎,多个课程对于多个班级,相反这道理也是一样,两者没有直接的关系
多对多,就只有这样了

4、章节表

章节表 只对应所属的课程
一个课程具有多个章节
一个章节对应一个课程

t_chapter

chapter_id,
chapter_course_id,
chapter_name,
chapter_order_id,
chapter_is_del,

chapter_gen_time,
chapter_alt_time


注:
设计之初忽略了书这个玩意
也许可以创建一个书的表
应该是一个课程需要多本书,一本书对应一个课程,如此这样
书和章节的关系也是如此,看起来就说得通了

5、题目表

就是一些测试,考试的题目,应该简称测验
英文好简单写一些:exam
我们希望题目可以直接被一些关键字段直接检索
简单点说就是:
我要XXX年级的题目
我要XXX班的题目
我要XXX课程的题目
我要XXX章节的题目
... ...


t_exam

exam_id,
exam_grade_id, 所属的年级
exam_class_id, 所属的班级
exam_course_id, 所属的课程
exam_chapter_id, 所属的章节
exam_title, 题目的标题
exam_type, 题目的类型,1选择题,2填空题,3简答题,4...
exam_score, 题目分值
exam_option_a, 选项... 和对应的选择描述
exam_option_b,
exam_option_c,
exam_option_d,
exam_answer, 答案
exam_difficulty, 难易程度 0简单的离谱 1简单 2正常 3困难 4很困难 5困难的离谱
exam_is_del,

上述的这些仅作为基础数据查询,或者应该说作为对于这个系统的元数据

 

6、用户表

简单的也有,复杂的也有,只谈目前阶段需要设置的东西则非常简单

sys_user
sys表示系统的意思,rbac权限是和系统的模块相关

user_id,
user_name,
user_password,
user_status,
user_is_del

user_phone,
user_email,
user_gen_time,
user_alt_time,
user_login_time,
user_last_login_time,
user_login_count,

三、工程目录划分:

使用IDEA创建JavaWeb工程:

声明项目名称:

[OA-Project-Phase1]

项目的初始结构:

所有的资源,应该被明确且优雅的管理归类,以便于我们后期的管理

src目录用于存放我们编写的Java源码,即后台程序

web目录一般用于存放网页静态资源,不过这是基于Tomcat所规定的规范

另外基于原生的JavaWeb,在不采用框架技术的情况,只能使用JSP完成页面和后台的交互

关于这些目录的一些概念

src目录可能需要存放的一些目录,:

pojo  Plain Ordinary Java Object       
实体类,存放一些数据模型类,又称为domain,entity,model等等

po  Persistant Object
持久化对象  
持久层对象的意思,对应数据库中表的字段,
数据库表中的记录在java对象中的显示状态,最形象的理解就是一个PO就是数据库中的一条记录。
好处是可以把一条记录作为一个对象处理,可以方便的转为其它对象。
Vo和Po,都是属性加上属性的get和set方法;表面看没什么不同,但代表的含义是完全不同的

dto Data Transport Object
数据传输对象 提高数据传输的速度(减少了传输字段),二能隐藏后端表结构
dao  Data Access Object       
数据访问层,存放和数据库访问相关的访问类
bo  Business Object
业务对象 把业务逻辑封装为一个对象(注意是逻辑,业务逻辑),
这个对象可以包括一个或多个其它的对象。通过调用Dao方法,结合Po或Vo进行业务操作

vo  View Object
视图对象 对于一个WEB页面将整个页面的属性封装成一个对象,
然后用一个VO对象在控制层与视图层进行传输交换
service  Business     
业务逻辑层,存放业务实际处理相关

serverModel 消息模型层,例如JSON,翻页等等,消息数据的模型层

servlet  manager  controller
视图处理层,存放转发和数据携带相关的servlet规范类
filter
过滤器,存放一些拦截过滤的处理
util
工具包目录,提高开发效率的工具类

web目录

/static        静态文件目录(如果采用模版或者其他文件,这目录的命名看情况,不完全要求)
/WEB-INF/jsp   /view  /...    所有jsp文件都会放在这里面,访问安全限制,至于二级目录各种命名,习惯就好
/WEB-INF/lib  存放jar依赖组件

构建工具和IDEA提供的一些规范目录

- SourcesRoot

  源文件根目录,也就是指类路径,存放所有Java源码的地方,其内部的包必须遵守Java命名规范

- TestSourcesRoot 

  测试根目录,用来存放测试代码的地方,在生成实际工程文件时,这个测试目录内的代码不会被添加进去

- ResourcesRoot

  资源根目录,用以存放各种各样的配置文件

- Test ResourcesRoot

  测试资源目录,用以存放测试需要的配置文件,可以把单元测试组件和测试用的连接配置单独放在这里面

- Excluded

  被排除目录,放在这里面的资源在工程生成时不会被添加进去

 

四、主要开发组件【简单的轮子】:

前后端使用Ajax数据交互的统一格式要求使用JSON,

基于这个规范,我们设置一个统一的后端处理的对象:JsonResult

前端也默认只通过这个对象获取需要的数据在页面渲染

package cn.dzz.util.common;

/**
 * @author DaiZhiZhou
 * @file OA-Project-Phase1
 * @create 2020-07-02 8:34
 */
public class JsonResult {
    
    private Integer status;
    private String message;
    private Object data;

    public JsonResult() {
    }


    public JsonResult(Object data) {
        this.data = data;
    }

    public JsonResult(Integer status, String message) {
        this.status = status;
        this.message = message;
    }

    public JsonResult(Integer status, String message, Object data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "JsonResult{" +
                "status=" + status +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }
}

要注意的是这里没有实现序列化,但相对于这个类的成员不会发生变化的情况来说,这个设置可有可无,非必要

简单的请求分发处理器BaseServlet:

package cn.dzz.util.common;

import com.alibaba.fastjson.JSON;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;

/**
 * @author DaiZhiZhou
 * @file OA-Project-Phase1
 * @create 2020-07-02 8:34
 */
public abstract class BaseServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        
        // 设置统一的异常处理,所有的需要通过BaseServlet调用的方法一定会到这里
        try {
            
            // 我们对需要调用的方法做一个统一的访问标识,供反射获取
            String act = req.getParameter("act");
            
            // 这个this不是本BaseServlet,一定是继承的子类的实例获取的子类字节对象
            Class<? extends BaseServlet> subClass = this.getClass();
            
            // 通过上面的约束规范获取封装的方法对象
            Method subServletDeclaredMethod = subClass.getDeclaredMethod(act, HttpServletRequest.class, HttpServletResponse.class);
            
            // 打印查看
            System.out.println("[请求访问地址:" + req.getRequestURL() + " \n访问的类:" + subClass.getName() + ",访问的方法" + subServletDeclaredMethod.getName() + "]");
            
            // 通过这个方法对象调用方法
            Object afterThings = subServletDeclaredMethod.invoke(this, req, resp);
            
            // 如果返回String我们就知道是需要重定向和转发了,因为会返回一个资源路径
            if (afterThings instanceof String) {
                
                // 类型转换,或者得到需要的内容
                String accessCoordinates = afterThings.toString();
                
                // 是否包含redirect:信息
                if (accessCoordinates.contains("redirect:")) {
                    
                    // 砍掉redirect: 获取地址
                    String visitLocate = accessCoordinates.substring(9);
                    
                    // 响应给客户端重定向访问的地址, 因为重定向不是从工程路径发送,是根/地址,所以需要动态补充
                    resp.sendRedirect(req.getContextPath() + visitLocate);

                    // 打印查看,结束方法
                    System.out.println("响应,重定向地址 -> " + visitLocate);
                    return;
                }
                
                // 转发使用频率非常的频繁
                req.getRequestDispatcher(accessCoordinates).forward(req,resp);
                System.out.println("请求,转发地址 -> " + accessCoordinates);
            } else if (afterThings instanceof JsonResult) {
                
                // 响应给前端的数据会乱码,这里单独设置响应的编码
                resp.setContentType("text/html;charset=UTF-8");
                
                // 把数据对象交给FastJson处理成json字符串
                String jsonString = JSON.toJSONString(afterThings);
                
                // 获取响应写入流,谁发出的请求,就响应写入给谁
                PrintWriter writer = resp.getWriter();
                
                // 写入Json字符串
                writer.write(jsonString);

                // 释放资源
                writer.close();
                
                // 结束方法
                return;
            }

        } catch (Exception exception) {
           exception.printStackTrace();
           // req.setAttribute("errorMessage",exception);
           // req.getRequestDispatcher("/一个访问错误的404页面").forward(req, resp);
        }
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 代码可重用, GET请求 - doGet, POST请求 - doPost -> doGet
        doGet(req, resp);
    }
}

分页模型:

package cn.dzz.util.common;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * @author DaiZhiZhou
 * @file OA-Project-Phase1
 * @create 2020-07-02 9:07
 *
 * 分页,翻页的模型对象
 */
public class Page<Entity> {

    // 总记录数量,如果是带有筛选条件查询的结果,此变量也应该是根据那个结果求出的总记录数量
    private Long total;

    // 每页显示的记录数量
    private Integer pageSize;

    // 总页数,使用上述的两个变量求得
    private Integer totalPage;

    // 从第几页开始
    private Integer pageIndex;

    // 对于不同的请求参数,我们需要动态的拼接处理传输,因此要求表单必须以Get方式发送
    private String url;

    // 当前页的记录列表
    private List<Entity> currentPageList;

    public Long getTotal() {
        return total;
    }
    public Integer getPageSize() {
        return pageSize;
    }
    public Integer getTotalPage() {
        return totalPage;
    }
    public Integer getPageIndex() {
        return pageIndex;
    }
    public String getUrl() {
        return url;
    }
    public List<Entity> getCurrentPageList() {
        return currentPageList;
    }


    public void setTotal(Long total) {
        this.total = total;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    // 获取总页数就是求余,不能整除的再加一页,关于数据类型转换,就这样
    public void setTotalPage(Integer pageSize) {
        Long totalPage =
                total % pageSize == 0 ?
                        total / pageSize : total / pageSize + 1;

        this.totalPage = totalPage.intValue();
    }

    public void setPageIndex(Integer pageIndex) {
        this.pageIndex = pageIndex;
    }

    public void setUrl(HttpServletRequest request) {
        //String contextPath = request.getContextPath();

        String queryString = request.getQueryString();
        String servletPath = request.getServletPath();

        if (queryString != null && !queryString.trim().isEmpty()) {
            if (queryString.contains("&p=")) {
                int index = queryString.indexOf("&p=");
                queryString = queryString.substring(0,index);
            }
        }

        this.url = servletPath + "?" + queryString;
    }

    public void setCurrentPageList(List<Entity> currentPageList) {
        this.currentPageList = currentPageList;
    }
}

公用的分页页面片段:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<div class="dataTables_paginate paging_simple_numbers"
            id="DataTables_Table_0_paginate">
            <a class="paginate_button previous disabled"
                aria-controls="DataTables_Table_0" data-dt-idx="0" tabindex="0"
                id="DataTables_Table_0_previous" href="<c:url value="${requestScope.page.url }"/>">首页</a>
            <c:if test="${requestScope.page.pageIndex > 1 }">
            <a class="paginate_button previous disabled"
                aria-controls="DataTables_Table_0" data-dt-idx="0" tabindex="0"
                id="DataTables_Table_0_previous" href="<c:url value='${requestScope.page.url }p=${requestScope.page.pageIndex - 1}'/>">上一页</a>
            </c:if>

                    <c:choose>
                <c:when test="${requestScope.page.pageSize <= 10}">
                    <c:set var="begin" value="1" />
                    <c:set var="end" value="${requestScope.page.pageSize}" />
                </c:when>
                <c:otherwise>
                    <c:set var="begin" value="${requestScope.page.pageIndex-4 }" />
                    <c:set var="end" value="${requestScope.page.pageIndex + 5}" />
                    <c:choose>
                        <c:when test="${begin < 1}">
                            <c:set var="begin" value="1" />
                            <c:set var="end" value="10" />
                        </c:when>
                        <c:when test="${end > requestScope.page.pagecount }">
                            <c:set var="begin" value="${requestScope.page.pageSize - 9}" />
                            <c:set var="end" value="${requestScope.page.pageSize}" />
                        </c:when>
                    </c:choose>
                </c:otherwise>
            </c:choose>


            <c:forEach begin="${begin}" end="${end}" var="i">
                <c:choose>

                    <c:when test="${i == requestScope.page.pageIndex}" >${i}</c:when>

                    <c:otherwise>
                    <span>
                        <a
                                class="paginate_button current"
                                aria-controls="DataTables_Table_0"
                                data-dt-idx="1"
                                tabindex="0"
                                href="<c:url value='${requestScope.page.url }p=${i}'/>"
                        >${i}
                        </a>
                    </span>
                    </c:otherwise>
                </c:choose>
            </c:forEach>


            <c:if test="${requestScope.page.pageIndex < requestScope.page.pageSize}">
            <a
                    class="paginate_button next disabled"
                    aria-controls="DataTables_Table_0"
                    data-dt-idx="2"
                    tabindex="0"
                    id="DataTables_Table_0_next"
                    href="<c:url value='${requestScope.page.url }p=${requestScope.page.pageIndex + 1}'/>"
            >下一页</a>

            </c:if>
            <a
                    class="paginate_button next disabled"
                    aria-controls="DataTables_Table_0"
                    data-dt-idx="2"
                    tabindex="0"
                    id="DataTables_Table_0_next"
                    href="<c:url value='${requestScope.page.url }p=${requestScope.page.pageSize}'/>"
                >尾页</a>
                
        </div>

其他一些

JdbcUtil             单连接对象获取,CRUD的操作封装,
TransactionManager   事务管理器,保证业务层的业务逻辑安全     

各种数据源【连接池】的获取工具类,不详细赘述,百度吧
C3p0Util
HikariUtil
DruidUtil

关于使用事务执行Jbdc的概述:

数据源 - 事务管理器类 - JdbcUtil或者其他的SQL操作工具类

数据源:

- 连接参数的配置文件

- 对应的各种连接池组件【C3p0,Druid,Hikari,... ...】

hikari在连接参数上的问题得不到解决,所以放弃了,
事务调用事发生了找不到jdbc驱动的问题,可是hikari链接不允许设置驱动

事务管理器:

- 获取数据源,提供连接

- 只要能提供数据源,就应该和数据源解耦,注入谁的数据源都应该可以

- 但是我找不到合适的注入点位置,所以和数据源一样,采用了配置文件注入

- dataSource.propertis,利用反射动态注入

Jdbc的工具操作:

- 只要注入事务提供的数据源,才能够被动态事务代理

- 或者不需要事务,直接执行

 

五、前端模版:

H-ui Admin

http://www.h-ui.net/H-ui.admin.shtml

 

posted @ 2020-06-21 22:05  emdzz  阅读(330)  评论(0编辑  收藏  举报