项目技术沉淀
好久没有更新博客,如今项目的所有功能也基本完成,目前还差的就剩下权限管理那块了。所以,需要博客来将所有的技术,包括各个细小的技术进行沉淀下,做下笔记。
一、公告模块
刚接手项目,对一切需求都是陌生的,首先面对的是公告模块,从最简单的开始——公告,原因是公告不涉及多表的级联查询。
(一)、数据库
首先谈的就是数据库,由于刚开始想的没有那么齐全,所以数据有可能涉及的有些问题。后来新添加了几个需要的字段。根据阿里巴巴的开发手册中的数据库的要求,每个表都需要加入create_time和update_time。阿里巴巴的开发手册可以参考github地址。文末将贴出。
另外,可使用mybatis_generator自动生成各种文件。以下为mybatis代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- mybatis逆向生成xml配置 -->
<generatorConfiguration>
<properties resource="application.properties" /> <!-- 数据库连接配置文件 -->
<context id="sqlserverTables" targetRuntime="MyBatis3">
<!-- 生成的pojo,将implements Serializable-->
<plugin type="org.mybatis.generator.plugins.SerializablePlugin"></plugin>
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!-- 数据库链接URL、用户名、密码(这个就是你的spring boot项目自带的那个配置文件里面的数据库的配置) -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://202.113.127.236:3306/tjikc?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC"
userId="${spring.datasource.druid.username}"
password="${spring.datasource.druid.password}">
<property name ="nullCatalogMeansCurrent" value = "true"/>
</jdbcConnection>
<!--
默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer
true,把JDBC DECIMAL 和 NUMERIC 类型解析为java.math.BigDecimal
-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!--
生成model模型,对应的包路径,以及文件存放路径(targetProject),targetProject可以指定具体的路径,如./src/main/java,
也可以使用“MAVEN”来自动生成,这样生成的代码会在target/generatord-source目录下<br> (通俗的讲就是你想要把生成的实体类的放到哪里)
-->
<!--<javaModelGenerator targetPackage="com.joey.mybaties.test.pojo" targetProject="MAVEN">-->
<javaModelGenerator targetPackage="cn.tj.entity" targetProject="./src/main/java">
<property name="enableSubPackages" value="true"/>
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!--对应的mapper.xml文件(通俗的讲就是你要把mapper.xml文件放到什么地方去,我是放到resource下一个名叫mappers的文件夹里面了) -->
<sqlMapGenerator targetPackage="mappers" targetProject="./src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- 对应的Mapper接口类文件 (通俗的讲就是你要生成的稻城mapper接口的地方 需要根据自己的文件进行配置) -->
<javaClientGenerator type="XMLMAPPER" targetPackage="cn.tj.mapper" targetProject="./src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 列出要生成代码的所有表,这里配置的是不生成Example文件 -->
<!-- 这个地方呢 也是你需要自动修改的地方 第一个参数是你数据库的表名 第二个参数就是想要生成实体类的名称 -->
<table tableName="表名" domainObjectName="映射成为的实体类名" enableCountByExample="true" enableUpdateByExample="true" enableDeleteByExample="true" enableSelectByExample="true" selectByExampleQueryId="true" >
</table>
</context>
</generatorConfiguration>
文件名为generatorConfig.xml,然后可以在maven工程中启动generator插件,当然也需要添加该插件,坐标:
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
<!--可指定配置文件地址,默认地址resources/generatorConfig.xml -->
<configurationFile>src/main/resources/generatorConfig.xml</configurationFile>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--<scope>runtime</scope>-->
<version>8.0.13</version>
</dependency>
</dependencies>
</plugin>
(二)、代码
1. 前端
1. 页面设计:
* 刚开始的一个项目,没有设计目录结构,结果各种html文件,都没有归类好,这就直接导致项目后期维护,包括修改需求的难度。所以在每次前端页面设计的同时,需要对该模块所需要的页面进行一个大概的设计,这其中就包括你所需要的各种url跳转的链接,也就是你controller层的@requestMapping的内容。
* 首先在导航栏的有超链接的按钮,当点击的时候直接发出请求,请求到controller层,然后遍历数据库中的所有数据,并且返回给modelAndView视图层,将结果集存进去,返回给页面进行遍历。
* 数据量多的话肯定需要分页,分页选择[datatables](http://www.datatables.club/ "datatables"),里面有很多案例可以直接拿过来用。需要引入各种css和js文件。在这里不再赘述。
* 关于datatables出现的各种问题:
* 如果引入后只有表格,没有分页的样式,可以查看bootstrap4有没有引进去。每个datatables都必须经过初始化,这也是一种可能。
* 以下是初始化和将datatables中文化的代码:
$('#declarationList').DataTable({
destroy:true,
searching:true,
bAutoWidth:false,
language: {
"sProcessing": "处理中...",
"sLengthMenu": "显示 _MENU_ 项结果",
"sZeroRecords": "没有匹配结果",
"sInfo": "显示第 _START_ 至 _END_ 项结果,共 _TOTAL_ 项",
"sInfoEmpty": "显示第 0 至 0 项结果,共 0 项",
"sInfoFiltered": "(由 _MAX_ 项结果过滤)",
"sInfoPostFix": "",
"sSearch": "搜索:",
"sUrl": "",
"sEmptyTable": "表中数据为空",
"sLoadingRecords": "载入中...",
"sInfoThousands": ",",
"oPaginate": {
"sFirst": "首页",
"sPrevious": "上页",
"sNext": "下页",
"sLast": "末页"
},
"oAria": {
"sSortAscending": ": 以升序排列此列",
"sSortDescending": ": 以降序排列此列"
}
}
});
declarationList是table表的id,destory的作用是摧毁一个datatables,新创建一个datatables。总之,如果你复写搜索的话,必须加这个,如果不加,会直接报错。searching:datatables自带的搜索框。language:更改中文用的。另外,如果你要是复写了datatables的查询结果,则必须行列值必须对应,这就意味着你不能对th元素或者td元素进行hidden属性。否则报错。具体报错自己尝试。
公告中心的界面
2. 后台
1. 新增:
采用模态框的形式进行用户交互:呈简洁性交互。
添加公告界面
另外,模态框有点问题,背景颜色太深,具体原因没有深究,但是后面的都没有问题。具体模态框的使用代码可以参考
所有的数据应当在前端就应该控制了,例如用户传入的数据为空。在后台之后,就应该将所有的数据进行传输,而不进行判断了。具体前台判断用户传入数据为空需要用到validate插件。具体查看
代码例子:
html:
<form id="saveNoticeForm" method="post">
<@shiro.user>
<input id="addUserName" name="addUserName" class="form-control" type="hidden" value="${currentUser.username}">
</@shiro.user>
<div class="modal-content">
<div class="modal-header">
<div class="row container-fluid">
<label for="addNoticeTitle">
<h5 class="modal-title">公告标题:</h5>
</label>
<input name="addNoticeTitle" id="addNoticeTitle" style="width:69%;" type="text" class="form-control" placeholder="请输入公告标题">
</div>
</div>
<div class="modal-body">
<!--公告内容-->
<span>
<label for="addNoticeContent"></label><textarea id="addNoticeContent" name="addNoticeContent" rows="20" style="width: 100%; height: 100%;margin-top: -30px;" class="form-control" placeholder="请在这里输入公告内容"></textarea>
</span>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal" id="closeDialog">取消</button>
<button type="submit" class="btn btn-primary" id="btn_save" name="add">确定</button>
</div>
</div>
</form>
js:
$("#saveNoticeForm").validate({
rules:{
addNoticeTitle:{
required:true,
minlength: 1
},
addNoticeContent:{
required:true,
minlength: 1
}
},
messages:{
addNoticeTitle:{
required:"请输入公告标题",
minlength:"公告标题至少包含一个字"
},
addNoticeContent:{
required:"请输入公告内容",
minlength:"公告内容至少包含一个字"
}
}
});
作用:如果想要使用validate必须要使用form向后台传送数据,rules:拦截规则,输入框的内容必须用label包裹,否则出不来样式。required:必填项,如果用户没有填写,直接拦截。minlength:最短长度,不满足直接拦截。
message:表示出错的情况需要展示的信息。
然后使用js向后台进行发出请求。由于需要用户的信息,所以需要判断用户是否登录,如果用户没有登陆提示用户登录后进行添加公告。如果用户登录了,则使用ajax进行传输数据。回调函数为更新成功提示信息。正常存入公告。需要说明的是:如果正常访问到dao层,但是取到的数据都是为null或者是有某个字段为null,多半是因为你属性映射失败。有两种解决方案:
1. 可以采用基于注解的进行映射。
代码:
@Select("select mes_id as mesId,mes_name as mesName,mes_contents as mesContents,mes_createTime as mesCreateTime,mes_creator as mesCreator from message")
List<Message> findAllNotice();
这样的形式也可以进行另一种方式:
@Select("SELECT * FROM problem WHERE app_id = #{appId}")
@Results(id="problemMap",value = {
@Result(id = true,column = "132132", property = "1321", jdbcType = JdbcType.INTEGER, javaType = Integer.class),
@Result(column = "12321", property = "1321", jdbcType = JdbcType.VARCHAR, javaType = String.class),
@Result(column = "321321", property = "13213", jdbcType = JdbcType.VARCHAR, javaType = String.class),
})
List<Problem> selectByAppId(Integer appId);
column表示是数据库的属性名,property是实体类的对应的名字,形成映射关系。
2. 可以采用xml的方式:
<resultMap id="BaseResultMap" type="cn.tj.entity.Message" >
<id column="mes_id" property="mesId" jdbcType="INTEGER" />
<result column="mes_name" property="mesName" jdbcType="VARCHAR" />
<result column="mes_contents" property="mesContents" jdbcType="VARCHAR" />
<result column="mes_createTime" property="mesCreateTime" jdbcType="VARCHAR" />
<result column="mes_creator" property="mesCreator" jdbcType="VARCHAR" />
</resultMap>
2. 修改:
修改的话,当点击按钮的时候,进入js方法,执行两次post请求,第一次的请求是将数据展示待模态框内,然后如果请求成功的话,会在回调函数内进行执行两个主要过程,第一:将查询到的数据,放到模态框内进行展示,第二:展示的内容要求可以被修改,这就要求,必须是input框或者textarea,然后当点击模态框的确认按钮的时候,判断用户是否进行登录,如果登录,则发送第二次的post请求(ajax),将修改的内容传到后台。在这里,前台代码有点不好,没有进行查询用户是否输入内容,所以在后台进行了处理,进行了抽离了方法:
private int mesStr (String mesName,String mesContent){
if (mesName==null||"".equals(mesName)){
return 1;
}else if (mesContent==null||"".equals(mesContent)){
return 2;
}else{
return 3;
}
}
返回值1表示公告的标题为空,返回值为2表示内容为空,返回值为3表示其他情况。
还需要一提的是:怎样传输变量给请求,比如想查询id为1的公告所有信息,如何发送post请求:
$.post("/updateOneNotice/" + mesId,{},function(){});这是整个过程,那么问题又来了,后台如何接受?
后台的接受过程:
@RequestMapping("/displayOneNotice/{mesId}")
@ResponseBody
public Map<String,Object> displayOneNotice(@PathVariable("mesId") Integer mesId) {
Map<String,Object> returnMap=new HashMap<>();
System.out.println(mesId);
Message message = noticeService.findById(mesId);
System.out.println(message);
returnMap.put("message",message);
return returnMap;
}
需要提示的是:如果你希望给用户提示,就使用js的方式进行传输数据,但是如果你不需要进行提示用户,或者需要想后台传输的数据量太多,则可以采用form表单,使用这种方式的缺点是不能给用户反馈。这个很容易理解。最后,如果你希望前台(浏览器)获得回调函数,则必须要@ResponseBody注解。
接着刚才的说,如果返回值为3,则进行更新,由于需要更新时间,就需要对时间进行格式化:
//格式化时间
SimpleDateFormat sdf=new SimpleDateFormat();
sdf.applyPattern("yyyy-MM-dd HH:mm:ss");
Date date=new Date();
sdf.format(date)
修改的界面
3. 删除
删除的某个图片
先post一张照片,来展示缺点,影响用户体验度,当用户点击提交的时候,应当给用户提示,是否删除?如果点确定的话,则删除。否则容易造成误删,影响用户体验度。
删除的功能:在删除的button上进行定义方法,并将隐藏域中的公告id传进来。然后向后台传送id,也可以这样传:不用通过url的地址进行传送,而是将数据封装成为json数据传送:
function deleteNotice(mesId) {
//获取到notice的id
$.post("/deleteNotice", {
mesId: mesId,
}, function (data) {
alert(mesId + "删除成功");
//需要跳转到http://localhost:8080/messageMain
// 1、获取当前全路径,如: http://localhost:8080/springmvc/page/frame/test.html
var curWwwPath = window.location.href;
// 获取当前相对路径: /springmvc/page/frame/test.html
var pathName = window.location.pathname; // 获取主机地址,如: http://localhost:8080
var local = curWwwPath.substring(0, curWwwPath.indexOf(pathName));
// 获取带"/"的项目名,如:/springmvc
var projectName = pathName.substring(0, pathName.substr(1).indexOf('/') + 1);
var rootPath = local + projectName;
window.location.href = pathName;
})
}
4. 显示
显示也没有什么难点,基本思路就是想后台传送查询的公告id,返回给页面,进行展示/具体界面跟上述一样。
其他待总结:
freemarker语法,包括list,if,空字符串的判断。
以上为公告的基本总结。供以后参考使用。
具体还有什么注意点,在后面的文章中接着分析,下一个为认证案例管理。
博客网站 https://yamon.top
个人网站 https://yamon.top/resume
GitHub网站 https://github.com/yamonc
欢迎前来访问