帮朋友写一个定制的后台管理和对外开放的API接口的项目,因为在之前的工作中没有使用到Spring boot等对我来说比较新的框架知识,为了学习,就边学边做,过程中肯定遇到了一些小问题,不过还是提前很多天完成了,然后在这里记录一下遇到的那些问题,以及怎么解决的,如果同行有更好的解决办法,请多指教。
我这里采用的框架是Springboot+mysql+mybatis+thymeleaf+bootstrap+maven的框架,因为是小项目,因此web后台和接口API没有独立开,直接在一个工程里面完成的。下面是整个工程的结构:
最开始是直接用idea给的Spring initlializr创建的Springboot web工程,并勾上mysql和mybatis需要的starter,过程选择自己的jdk版本,输入工程名称,直接next到最后就可以了,非常方便。
在做这个项目时没有分那么多层,mapper接口文件和SQL没有分开,也就是没有使用xxxMapper.xml文件来写SQL,直接使用的mybatis的@select,@Param等注解,遇到过参数没传进去报错的情况,这里就需要把单个的参数前加@Param(“xxx”)注解来指明才是是谁。当然,这里不只有@Select这个注解,还有@Insert、@Delete等,我就不多说了。
/**
* @author Joey Zeng
* @version 1.0.0, 2018-12-20
*/
@Component(value = "configMapper")
public interface ConfigMapper {
@Select("select * from config where type=#{type} and status=0")
public List<Config> selectConfig(@Param("type") Integer type);
}
为了工程能扫描到mapper,需要在工程的启动类上加上mapper的路径的注解@MapperScan(“cn.zyj.platform.mapper”),这样的话就不需要像以前那样在每个mapper类上写@Mapper注解了,省了很多事。
@MapperScan("cn.zyj.platform.mapper")
@SpringBootApplication
public class PlatformApplication {
public static void main(String[] args) {
SpringApplication.run(PlatformApplication.class, args);
}
}
在写列表带条件查询的时候,SQL需要动态加入查询条件,需要用到if或者when,如下:
@Select("<script> select * from table_weibo where 1=1 and del_or_not=0"
+ "<if test='unBunned != 0'> and status !=1 and used=1 </if>"
+ " <when test='day!=0 and day!=16'> and TO_DAYS(now())-TO_DAYS(createTime) = ${day} </when>"
+ " <when test='day!=0 and day==16'> and TO_DAYS(now())-TO_DAYS(createTime) >=${day} </when>"
+ " order by createTime asc </script>")
public List<Weibo> selectWeiboLists(@Param("day")Integer day,@Param("unBunned")Integer unBunned);
一般web后台的列表都需要分页,我这里用的是PageHelper来做的,一开始是在web用js来做的分页,需要一次把数据全部查出来,不过数据量很大的情况,就影响了首次加载的体验了,然后没考虑就用了PageHelper。
首先引入依赖:
<!-- mybatis分页功能 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.1.6</version>
</dependency>
这里需要自定义Configuration。
/**
* TODO
* mybatis 分页帮助类配置
* @author Joey Zeng
* @version 1.0.0, 2018-12-24
*/
@Configuration
public class MybatisPageHelperConfig {
@Bean( name = "pageHelper" )
public PageHelper pageHelper() {
PageHelper pageHelper = new PageHelper();
//添加配置,也可以指定文件路径
Properties p = new Properties();
p.setProperty("helperDialect", "mysql");
p.setProperty("reasonable", "true");
p.setProperty("supportMethodsArguments", "true");
p.setProperty("params", "count=countSql");
p.setProperty("autoRuntimeDialect", "true");
pageHelper.setProperties(p);
return pageHelper;
}
}
这里配置自定义的configuration类需要加@Configuration注解和@Bean注解,不然不会生效。
PageHelper的使用,controller 如下:
@Controller
@RequestMapping("/ad")
public class AdvertiseManageController {
Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
AdvertiseService advertiseService;
@RequestMapping("/list")
public String weiboLists(Map<String, Object> map, @RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "14") Integer pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<Advertise> advLists = advertiseService.getAdvertiseLists();
PageInfo pageInfo = new PageInfo<>(advLists, pageSize);
map.put("page",pageInfo);
return "ad_list";
}
}
使用了thymeleaf模版引擎:
首先依赖引入,或者在创建工程时直接勾上:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
页面语法:
<!--分页-->
<div class="row">
<div class="col-sm-5" style="margin-top:7px;">
<div class="dataTables_info" id="datatable_info" style="width: 80%;" role="status" aria-live="polite">
显示 <span th:text="${page.getStartRow()}"></span>
到 <span th:text="${page.getEndRow()}"></span>
总共 <span th:text="${page.getTotal()}"></span> 条
当前第: <span th:text="${page.getPageNum()}"></span> 页,
总共 <span th:text="${page.getPages()}"></span> 页
</div>
</div>
<div class="col-sm-7">
<div class="dataTables_paginate paging_simple_numbers" id="datatable_paginate">
<ul class="pagination" style="margin: 2px 0;">
<li class="paginate_button previous" id="datatable_previous">
<a href="#" aria-controls="datatable" data-dt-idx="0" tabindex="0" th:if="${not page.isIsFirstPage()}" th:href="@{${'/ad/list'} (pageNum=${page.getPrePage()},pageSize=${page.getPageSize()})}">上一页</a>
<a href="#" aria-controls="datatable" data-dt-idx="0" tabindex="0" th:if="${page.isIsFirstPage()}" th:href="'javascript:void(0);'">上一页</a>
</li>
<li class="paginate_button next" id="datatable_next">
<a href="#" aria-controls="datatable" data-dt-idx="8" tabindex="0" th:if="${not page.isIsLastPage()}" th:href="@{${'/ad/list'}(pageNum=${page.getNextPage()},pageSize=${page.getPageSize()})}">下一页</a>
<a href="#" aria-controls="datatable" data-dt-idx="8" tabindex="0" th:if="${page.isIsLastPage()}" th:href="'javascript:void(0);'">下一页</a>
</li>
</ul>
</div>
</div>
</div>
<!--分页结束-->
页面效果如图:
这里总结一些页面中thymeleaf的一些使用语法:
首先得在页面引入thymeleaf,在html标签中加入:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 普通的获取值:
<input type="hidden" id="pageSize" th:value="${page.getPageSize()}">`
- href中跳转controller地址:
th:href="@{${'/xxx/xxx'}(pageNum=${page.getPrePage()},pageSize=${page.getPageSize()})}"
- 标签里面调用js的function并传参数:
<a th:onclick="edit_ad([[${adv.id}]],[[${adv.detail}]],[[${adv.phoneMark}]],[[${adv.remarks}]]);">
<i class="fa fa-pencil"></i> 编辑
</a>
- 判断boolean类型的值:
<a href="#" h:if="${not page.isIsFirstPage()}" th:href="@{${'/xxx/xxx'}(pageNum=${page.getPrePage()},pageSize=${page.getPageSize()})}">上一页</a>
<a th:if="${page.isIsFirstPage()}" th:href="'javascript:void(0);'">上一页</a>
- 遍历数据:
<tr th:if="${page!=null}" th:each="aaa,aaaStatu:${page.getList()}">
<td th:text="${aaaStatu.index+1}"></td>
<td th:text="${aaa.name1}"></td>
<td th:text="${aaa.name2}"></td>
<td th:if="${aaa.name3==0}" th:text="未下载"></td>
<td th:if="${aaa.name3==1}" th:text="已下载"></td>
<td th:text="${#dates.format(aaa.createTime,'yyyy-MM-dd HH:mm:ss')}"></td>
<td th:text="${aaa.name5}"></td>
</tr>
- 格式化时间:
<td th:text="${#dates.format(aaa.createTime,'yyyy-MM-dd HH:mm:ss')}"></td>
- 引用js/css:
<script th:src="@{/static/js/xxx/xxx.js}"></script>
<link th:href="@{/static/css/xxx.css}" rel="stylesheet">
- include公共页面:
<div th:include="common::commonFooter"></div>
这其中common是提取的公共页面,commonFooter指的是给公共部分命的名:
以上这是比较简单的分页操作,没有条件查询,因此很容易实现,业务需求也没有条件查询,如果需要加入条件查询不知道有没有很好的解决办法?
虽然业务上没有条件查询,但是我用ajax来做了带条件查询动态生成的table行数据的效果,包括上下页都是js写的,不然条件参数不好带过去。如果有更好的办法就很好了。
然后在做ajax请求获取数据时就遇到了问题,json数据返回遇到问题,看了一下报错,应该是拦截器或者mvc配置的地方有问题,然后查看对应的地方,发现mvc继承的WebMvcConfigurationSupport,里面重写的configureMessageConverters方法里面确实只配置了对String相关的,因为我在写接口是接口返回中文时出现乱码,就在这儿设置了UTF-8编码,以及在aplication.properties里面配置了编码格式:
spring.http.encoding.force=true
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
server.tomcat.uri-encoding=UTF-8
因此,为了解决在我自己的WebMvcConfiguration的配置时,我没有继承WebMvcConfigurationSupport,而是实现了他父类WebMvcConfigurer,实现了里面configureMessageConverters接口,添加了json相关的配置:
/**
* webmvc 配置类
* @author Joey Zeng
* @version 1.0.0, 2018-12-22
*/
@Configuration
public class MyWebMvcConfigurerAdapter implements WebMvcConfigurer {
// 继承WebMvcConfigurationSupport时为了注入自己的配置
// @Bean
// public HttpMessageConverter<String> responseBodyConverter() {
// StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
// return converter;
// }
/**
* 配置接口中文乱码问题,以及ajax返回json数据拦截问题
* @param converters
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter converter = new StringHttpMessageConverter(Charset.forName("UTF-8"));
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
converters.add(converter);
converters.add(mappingJackson2HttpMessageConverter);
}
@Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
/**
* 配置静态资源的访问
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX+"/static/");
}
/**
* 配置拦截规则
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**") //用于添加拦截规则
.excludePathPatterns("/login") // 用于开放登录页
.excludePathPatterns("/index") // 用于排除拦截登录请求
.excludePathPatterns("/static/**"); // 用于排除拦截静态资源
}
}
然后这个问题就这样解决了,当然,得注意controller层,不管是接口返回数据还是ajax请求返回数据,都要加@ResponseBody注解。
另外在做导出数据功能时,当时遇到点击导出按钮并带上分页参数,没反应的情况,按钮是写的a标签,然后这里需要用到location.href :
location.href=exportExcelUrl+"?"+"pageNum="+pageNum+"&pageSize="+pageSize+"&day="+day+"&unbanned="+unbanned;
这个项目就到此为止了,希望各位大牛多提提意见。